commit 5e4c5f9418474c8278707089292b47ce8986ab34 Author: Gauvain Boiché Date: Mon Mar 30 14:52:34 2020 +0200 Ajout du FR Ajout du FR + correction du "functions.php" diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..53bce76 --- /dev/null +++ b/.htaccess @@ -0,0 +1,85 @@ + +RewriteEngine on + +# +# Uncomment the statement below if URL rewriting doesn't +# work properly. If you installed phpBB in a subdirectory +# of your site, properly set the argument for the statement. +# e.g.: if your domain is test.com and you installed phpBB +# in http://www.test.com/phpBB/index.php you have to set +# the statement RewriteBase /phpBB/ +# +#RewriteBase / + +# +# Uncomment the statement below if you want to make use of +# HTTP authentication and it does not already work. +# This could be required if you are for example using PHP via Apache CGI. +# +#RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] + +# +# The following 3 lines will rewrite URLs passed through the front controller +# to not require app.php in the actual URL. In other words, a controller is +# by default accessed at /app.php/my/controller, but can also be accessed at +# /my/controller +# +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ app.php [QSA,L] + +# +# If symbolic links are not already being followed, +# uncomment the line below. +# http://anothersysadmin.wordpress.com/2008/06/10/mod_rewrite-forbidden-403-with-apache-228/ +# +#Options +FollowSymLinks + + +# With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from +# module mod_authz_host to a new module called mod_access_compat (which may be +# disabled) and a new "Require" syntax has been introduced to mod_authz_host. +# We could just conditionally provide both versions, but unfortunately Apache +# does not explicitly tell us its version if the module mod_version is not +# available. In this case, we check for the availability of module +# mod_authz_core (which should be on 2.4 or higher only) as a best guess. + + + + Order Allow,Deny + Deny from All + + + Order Allow,Deny + Deny from All + + + = 2.4> + + Require all denied + + + Require all denied + + + + + + + Order Allow,Deny + Deny from All + + + Order Allow,Deny + Deny from All + + + + + Require all denied + + + Require all denied + + + diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..e69de29 diff --git a/adm/images/alert_close.png b/adm/images/alert_close.png new file mode 100644 index 0000000..79750a0 Binary files /dev/null and b/adm/images/alert_close.png differ diff --git a/adm/images/arrow_down.gif b/adm/images/arrow_down.gif new file mode 100644 index 0000000..b7fbf7e Binary files /dev/null and b/adm/images/arrow_down.gif differ diff --git a/adm/images/arrow_left.gif b/adm/images/arrow_left.gif new file mode 100644 index 0000000..ac92cb4 Binary files /dev/null and b/adm/images/arrow_left.gif differ diff --git a/adm/images/arrow_right.gif b/adm/images/arrow_right.gif new file mode 100644 index 0000000..3a080ff Binary files /dev/null and b/adm/images/arrow_right.gif differ diff --git a/adm/images/arrow_up.gif b/adm/images/arrow_up.gif new file mode 100644 index 0000000..0ff5872 Binary files /dev/null and b/adm/images/arrow_up.gif differ diff --git a/adm/images/bg_button.gif b/adm/images/bg_button.gif new file mode 100644 index 0000000..03172ff Binary files /dev/null and b/adm/images/bg_button.gif differ diff --git a/adm/images/bg_hash1.gif b/adm/images/bg_hash1.gif new file mode 100644 index 0000000..6116367 Binary files /dev/null and b/adm/images/bg_hash1.gif differ diff --git a/adm/images/bg_hash2.gif b/adm/images/bg_hash2.gif new file mode 100644 index 0000000..d318403 Binary files /dev/null and b/adm/images/bg_hash2.gif differ diff --git a/adm/images/bg_hash3.gif b/adm/images/bg_hash3.gif new file mode 100644 index 0000000..40bc7e1 Binary files /dev/null and b/adm/images/bg_hash3.gif differ diff --git a/adm/images/bg_hash4.gif b/adm/images/bg_hash4.gif new file mode 100644 index 0000000..54e7f00 Binary files /dev/null and b/adm/images/bg_hash4.gif differ diff --git a/adm/images/bg_header.gif b/adm/images/bg_header.gif new file mode 100644 index 0000000..b95bf3b Binary files /dev/null and b/adm/images/bg_header.gif differ diff --git a/adm/images/bg_header.jpg b/adm/images/bg_header.jpg new file mode 100644 index 0000000..5ecec53 Binary files /dev/null and b/adm/images/bg_header.jpg differ diff --git a/adm/images/bg_tabs_alt1.gif b/adm/images/bg_tabs_alt1.gif new file mode 100644 index 0000000..335a72c Binary files /dev/null and b/adm/images/bg_tabs_alt1.gif differ diff --git a/adm/images/bg_tabs_alt2.gif b/adm/images/bg_tabs_alt2.gif new file mode 100644 index 0000000..a2142d5 Binary files /dev/null and b/adm/images/bg_tabs_alt2.gif differ diff --git a/adm/images/cellpic3.gif b/adm/images/cellpic3.gif new file mode 100644 index 0000000..be46fc6 Binary files /dev/null and b/adm/images/cellpic3.gif differ diff --git a/adm/images/file_conflict.gif b/adm/images/file_conflict.gif new file mode 100644 index 0000000..4458c4f Binary files /dev/null and b/adm/images/file_conflict.gif differ diff --git a/adm/images/file_modified.gif b/adm/images/file_modified.gif new file mode 100644 index 0000000..17e8f97 Binary files /dev/null and b/adm/images/file_modified.gif differ diff --git a/adm/images/file_new.gif b/adm/images/file_new.gif new file mode 100644 index 0000000..d0ec758 Binary files /dev/null and b/adm/images/file_new.gif differ diff --git a/adm/images/file_new_conflict.gif b/adm/images/file_new_conflict.gif new file mode 100644 index 0000000..84efde4 Binary files /dev/null and b/adm/images/file_new_conflict.gif differ diff --git a/adm/images/file_not_modified.gif b/adm/images/file_not_modified.gif new file mode 100644 index 0000000..8f9b3d3 Binary files /dev/null and b/adm/images/file_not_modified.gif differ diff --git a/adm/images/file_up_to_date.gif b/adm/images/file_up_to_date.gif new file mode 100644 index 0000000..c372342 Binary files /dev/null and b/adm/images/file_up_to_date.gif differ diff --git a/adm/images/gradient2b.gif b/adm/images/gradient2b.gif new file mode 100644 index 0000000..a810765 Binary files /dev/null and b/adm/images/gradient2b.gif differ diff --git a/adm/images/icon_delete.gif b/adm/images/icon_delete.gif new file mode 100644 index 0000000..57962d5 Binary files /dev/null and b/adm/images/icon_delete.gif differ diff --git a/adm/images/icon_delete_disabled.gif b/adm/images/icon_delete_disabled.gif new file mode 100644 index 0000000..da55bbb Binary files /dev/null and b/adm/images/icon_delete_disabled.gif differ diff --git a/adm/images/icon_down.gif b/adm/images/icon_down.gif new file mode 100644 index 0000000..793db26 Binary files /dev/null and b/adm/images/icon_down.gif differ diff --git a/adm/images/icon_down_disabled.gif b/adm/images/icon_down_disabled.gif new file mode 100644 index 0000000..3ba3697 Binary files /dev/null and b/adm/images/icon_down_disabled.gif differ diff --git a/adm/images/icon_edit.gif b/adm/images/icon_edit.gif new file mode 100644 index 0000000..c95cf98 Binary files /dev/null and b/adm/images/icon_edit.gif differ diff --git a/adm/images/icon_edit_disabled.gif b/adm/images/icon_edit_disabled.gif new file mode 100644 index 0000000..ac96b0e Binary files /dev/null and b/adm/images/icon_edit_disabled.gif differ diff --git a/adm/images/icon_folder.gif b/adm/images/icon_folder.gif new file mode 100644 index 0000000..845618c Binary files /dev/null and b/adm/images/icon_folder.gif differ diff --git a/adm/images/icon_folder_link.gif b/adm/images/icon_folder_link.gif new file mode 100644 index 0000000..efeaf0a Binary files /dev/null and b/adm/images/icon_folder_link.gif differ diff --git a/adm/images/icon_folder_lock.gif b/adm/images/icon_folder_lock.gif new file mode 100644 index 0000000..7afb092 Binary files /dev/null and b/adm/images/icon_folder_lock.gif differ diff --git a/adm/images/icon_subfolder.gif b/adm/images/icon_subfolder.gif new file mode 100644 index 0000000..7119486 Binary files /dev/null and b/adm/images/icon_subfolder.gif differ diff --git a/adm/images/icon_sync.gif b/adm/images/icon_sync.gif new file mode 100644 index 0000000..16223df Binary files /dev/null and b/adm/images/icon_sync.gif differ diff --git a/adm/images/icon_sync_disabled.gif b/adm/images/icon_sync_disabled.gif new file mode 100644 index 0000000..3998504 Binary files /dev/null and b/adm/images/icon_sync_disabled.gif differ diff --git a/adm/images/icon_trace.gif b/adm/images/icon_trace.gif new file mode 100644 index 0000000..5c622e9 Binary files /dev/null and b/adm/images/icon_trace.gif differ diff --git a/adm/images/icon_up.gif b/adm/images/icon_up.gif new file mode 100644 index 0000000..7daa95c Binary files /dev/null and b/adm/images/icon_up.gif differ diff --git a/adm/images/icon_up_disabled.gif b/adm/images/icon_up_disabled.gif new file mode 100644 index 0000000..2797192 Binary files /dev/null and b/adm/images/icon_up_disabled.gif differ diff --git a/adm/images/innerbox_bg.gif b/adm/images/innerbox_bg.gif new file mode 100644 index 0000000..460526f Binary files /dev/null and b/adm/images/innerbox_bg.gif differ diff --git a/adm/images/loading.gif b/adm/images/loading.gif new file mode 100644 index 0000000..e1ed088 Binary files /dev/null and b/adm/images/loading.gif differ diff --git a/adm/images/no_avatar.gif b/adm/images/no_avatar.gif new file mode 100644 index 0000000..ad73330 Binary files /dev/null and b/adm/images/no_avatar.gif differ diff --git a/adm/images/no_image.png b/adm/images/no_image.png new file mode 100644 index 0000000..1061ee3 Binary files /dev/null and b/adm/images/no_image.png differ diff --git a/adm/images/phpbb_logo.png b/adm/images/phpbb_logo.png new file mode 100644 index 0000000..2d76ef1 Binary files /dev/null and b/adm/images/phpbb_logo.png differ diff --git a/adm/images/progress_bar.gif b/adm/images/progress_bar.gif new file mode 100644 index 0000000..1ae2651 Binary files /dev/null and b/adm/images/progress_bar.gif differ diff --git a/adm/images/spacer.gif b/adm/images/spacer.gif new file mode 100644 index 0000000..cd29009 Binary files /dev/null and b/adm/images/spacer.gif differ diff --git a/adm/index.php b/adm/index.php new file mode 100644 index 0000000..d27f56f --- /dev/null +++ b/adm/index.php @@ -0,0 +1,91 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +*/ +define('IN_PHPBB', true); +define('ADMIN_START', true); +define('NEED_SID', true); + +// Include files +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +require($phpbb_root_path . 'common.' . $phpEx); +require($phpbb_root_path . 'includes/functions_acp.' . $phpEx); +require($phpbb_root_path . 'includes/functions_admin.' . $phpEx); +require($phpbb_root_path . 'includes/functions_module.' . $phpEx); + +// Start session management +$user->session_begin(); +$auth->acl($user->data); +$user->setup('acp/common'); +// End session management + +// Have they authenticated (again) as an admin for this session? +if (!isset($user->data['session_admin']) || !$user->data['session_admin']) +{ + login_box('', $user->lang['LOGIN_ADMIN_CONFIRM'], $user->lang['LOGIN_ADMIN_SUCCESS'], true, false); +} + +// Is user any type of admin? No, then stop here, each script needs to +// check specific permissions but this is a catchall +if (!$auth->acl_get('a_')) +{ + send_status_line(403, 'Forbidden'); + trigger_error('NO_ADMIN'); +} + +// We define the admin variables now, because the user is now able to use the admin related features... +define('IN_ADMIN', true); + +// Some oft used variables +$safe_mode = (@ini_get('safe_mode') == '1' || strtolower(@ini_get('safe_mode')) === 'on') ? true : false; +$file_uploads = (@ini_get('file_uploads') == '1' || strtolower(@ini_get('file_uploads')) === 'on') ? true : false; +$module_id = $request->variable('i', ''); +$mode = $request->variable('mode', ''); + +// Set custom style for admin area +$template->set_custom_style(array( + array( + 'name' => 'adm', + 'ext_path' => 'adm/style/', + ), +), $phpbb_admin_path . 'style'); + +$template->assign_var('T_ASSETS_PATH', $phpbb_root_path . 'assets'); +$template->assign_var('T_TEMPLATE_PATH', $phpbb_admin_path . 'style'); + +// Instantiate new module +$module = new p_master(); + +// Instantiate module system and generate list of available modules +$module->list_modules('acp'); + +// Select the active module +$module->set_active($module_id, $mode); + +// Assign data to the template engine for the list of modules +// We do this before loading the active module for correct menu display in trigger_error +$module->assign_tpl_vars(append_sid("{$phpbb_admin_path}index.$phpEx")); + +// Load and execute the relevant module +$module->load_active(); + +// Generate the page +adm_page_header($module->get_page_title()); + +$template->set_filenames(array( + 'body' => $module->get_tpl_name(), +)); + +adm_page_footer(); diff --git a/adm/style/acp_attachments.html b/adm/style/acp_attachments.html new file mode 100644 index 0000000..868e256 --- /dev/null +++ b/adm/style/acp_attachments.html @@ -0,0 +1,487 @@ + + + + + + « {L_BACK} + + +

{L_TITLE}

+ +

{L_TITLE_EXPLAIN}

+ + +
+

{L_WARNING}

+

{WARNING_MSG}

+
+ + + +
+

{L_NOTIFY}

+

{NOTIFY_MSG}

+
+ + + +

{L_UPLOADING_FILES}

+ + + :: {upload.FILE_INFO}
+ {upload.DENIED}{upload.ERROR_MSG}{L_SUCCESSFULLY_UPLOADED} +

+ + + + + + +
+ + + + + +
+ {options.LEGEND} + + +
+

{options.TITLE_EXPLAIN}
+
{options.CONTENT}
+
+ + + +
+ +
+ {L_SUBMIT} +   + +
+ + +
+

{L_SECURE_DOWNLOAD_NOTICE}

+
+ + +
+ {L_SECURE_TITLE} +

{L_DOWNLOAD_ADD_IPS_EXPLAIN}

+
+
+
+
+
+

{L_EXCLUDE_ENTERED_IP}
+
+
+
+ +

+ +

+
+ +
+ {L_REMOVE_IPS} + +

{L_DOWNLOAD_REMOVE_IPS_EXPLAIN}

+
+
+
+
+ +

+ +

+
+ + +

{L_NO_IPS_DEFINED}

+ + {S_FORM_TOKEN} + +
+ + + + + + +
+
+ + + + {L_LEGEND} +
+
+
+
+
+

{L_SPECIAL_CATEGORY_EXPLAIN}
+
{S_CATEGORY_SELECT}
+
+
+
+
checked="checked" />
+
+
+
+
checked="checked" />
+
+
+
+
+
 src="{ROOT_PATH}images/spacer.gif"src="{UPLOAD_ICON_SRC}" id="image_upload_icon" alt="" title="" /> 
+
+
+
+
+
+
+
+
{ASSIGNED_EXTENSIONS}
[{L_GO_TO_EXTENSIONS} ]
+
+
+
+

{L_ALLOWED_FORUMS_EXPLAIN}
+
+
+
+
+ +

+   + +

+ {S_FORM_TOKEN} +
+ +
+ + +
+
+ {L_TITLE} + + + + + + + + + + + + + + + + + + + + + + + + +
{L_EXTENSION_GROUP}{L_SPECIAL_CATEGORY}{L_OPTIONS}
 
{groups.GROUP_NAME} +
» {L_NOT_ALLOWED_IN_PM} +
» {L_ONLY_ALLOWED_IN_PM} +
» {L_NOT_ALLOWED_IN_PM_POST} +
» {L_ALLOWED_IN_PM_POST} +
{groups.CATEGORY} {ICON_EDIT}  {ICON_DELETE} 
+

+ {L_CREATE_GROUP}{L_COLON} + +

+ {S_FORM_TOKEN} +
+
+ + + + + +
+
+ {L_ADD_EXTENSION} +
+
+
+
+
+
+
{GROUP_SELECT_OPTIONS}
+
+ +

+ +

+ {S_FORM_TOKEN} +
+
+ +
+ +
+ {L_TITLE} + + + + + + + + + + + + + + + + + + + + + + + + +
{L_EXTENSION}{L_EXTENSION_GROUP}{L_DELETE}
 
{extensions.EXTENSION}{extensions.GROUP_OPTIONS}
+ +

+   + +

+ {S_FORM_TOKEN} +
+
+ + + +
+ +
+ {L_TITLE} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_FILENAME}{L_FILEDATE}{L_FILESIZE}{L_ATTACH_POST_ID}{L_ATTACH_TO_POST}{L_DELETE}
{orphan.REAL_FILENAME}{orphan.FILETIME}{orphan.FILESIZE}{L_ATTACH_ID}{L_COLON}
 {L_MARK_ALL} :: {L_UNMARK_ALL}{L_MARK_ALL} :: {L_UNMARK_ALL}
+ +
+

{L_NO_ATTACHMENTS}

+
+ + + + + + + +

+   + +

+ + + {S_FORM_TOKEN} +
+
+ + + +
+ +
+ {L_TITLE} + + + + + + + + + + + + + + + + + + + + + + + +
{L_FILENAME}{L_POSTED}{L_FILESIZE}{L_MARK}
+ {L_EXTENSION_GROUP}{L_COLON} {attachments.EXT_GROUP_NAME}{L_NO_EXT_GROUP}
{attachments.L_DOWNLOAD_COUNT}
{L_IN} {L_PRIVATE_MESSAGE} + {attachments.REAL_FILENAME}
{attachments.COMMENT}
{attachments.L_DOWNLOAD_COUNT}
{L_TOPIC}{L_COLON} {attachments.TOPIC_TITLE} +
{attachments.FILETIME}
{L_POST_BY_AUTHOR} {attachments.ATTACHMENT_POSTER}
{attachments.FILESIZE}
+ +
+

{L_NO_ATTACHMENTS}

+
+ + + + + + +
+ {L_DISPLAY_LOG}{L_COLON}  {S_LIMIT_DAYS} {L_SORT_BY}{L_COLON} {S_SORT_KEY} {S_SORT_DIR} + +
+ +
+ + +
+
+

+ {L_MARK_ALL} • + {L_UNMARK_ALL} +

+
+ + {S_FORM_TOKEN} +
+
+ + +
+ {L_RESYNC_STATS} +
+
+

{L_RESYNC_FILES_STATS_EXPLAIN}
+
+
+
+
+ + + + diff --git a/adm/style/acp_avatar_options_gravatar.html b/adm/style/acp_avatar_options_gravatar.html new file mode 100644 index 0000000..d5906ba --- /dev/null +++ b/adm/style/acp_avatar_options_gravatar.html @@ -0,0 +1,11 @@ +
+

{L_GRAVATAR_AVATAR_EMAIL_EXPLAIN}
+
+
+
+

{L_GRAVATAR_AVATAR_SIZE_EXPLAIN}
+
+ {L_PIXEL} ×  + {L_PIXEL} +
+
diff --git a/adm/style/acp_avatar_options_local.html b/adm/style/acp_avatar_options_local.html new file mode 100644 index 0000000..bee3c57 --- /dev/null +++ b/adm/style/acp_avatar_options_local.html @@ -0,0 +1,20 @@ +
+
+
 
+
+ + + diff --git a/adm/style/acp_avatar_options_remote.html b/adm/style/acp_avatar_options_remote.html new file mode 100644 index 0000000..e64d136 --- /dev/null +++ b/adm/style/acp_avatar_options_remote.html @@ -0,0 +1,11 @@ +
+

{L_LINK_REMOTE_AVATAR_EXPLAIN}
+
+
+
+

{L_LINK_REMOTE_SIZE_EXPLAIN}
+
+ {L_PIXEL} ×  + {L_PIXEL} +
+
diff --git a/adm/style/acp_avatar_options_upload.html b/adm/style/acp_avatar_options_upload.html new file mode 100644 index 0000000..63a734e --- /dev/null +++ b/adm/style/acp_avatar_options_upload.html @@ -0,0 +1,11 @@ +
+
+
+
+ + +
+

{L_UPLOAD_AVATAR_URL_EXPLAIN}
+
+
+ diff --git a/adm/style/acp_ban.html b/adm/style/acp_ban.html new file mode 100644 index 0000000..f224994 --- /dev/null +++ b/adm/style/acp_ban.html @@ -0,0 +1,131 @@ + + + + +

{L_ACP_BAN_EXPLAIN}

+ +

{L_TITLE}

+ +

{L_EXPLAIN}

+ + + +
+ +
+ {L_TITLE} +
+
+
+
[ {L_FIND_USERNAME} ]
+
+
+
+
+ +
+
+

{L_BAN_EXCLUDE_EXPLAIN}
+
+
+
+
+
+
+
+
+
+
+
+ +

+   + +

+{S_FORM_TOKEN} +
+
+ +

+ +

{L_UNBAN_TITLE}

+ +

{L_UNBAN_EXPLAIN}

+ +
+ +
+ {L_UNBAN_TITLE} + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +

+   + +

+ {S_FORM_TOKEN} +
+ + + +

{L_NO_BAN_CELL}

+ {S_FORM_TOKEN} + + + + +
+ + diff --git a/adm/style/acp_bbcodes.html b/adm/style/acp_bbcodes.html new file mode 100644 index 0000000..8c8b805 --- /dev/null +++ b/adm/style/acp_bbcodes.html @@ -0,0 +1,126 @@ + + + + + + + « {L_BACK} + +

{L_ACP_BBCODES}

+ +

{L_ACP_BBCODES_EXPLAIN}

+ +
+ +
+ {L_BBCODE_USAGE} +

{L_BBCODE_USAGE_EXPLAIN}

+
+


{L_BBCODE_USAGE_EXAMPLE}
+
+
+
+ +
+ {L_HTML_REPLACEMENT} +

{L_HTML_REPLACEMENT_EXPLAIN}

+
+


{L_HTML_REPLACEMENT_EXAMPLE}
+
+
+
+ +
+ {L_BBCODE_HELPLINE} +

{L_BBCODE_HELPLINE_EXPLAIN}

+
+
+
+
+
+ +
+ {L_SETTINGS} +
+
+
checked="checked" />
+
+
+ + + +
+ {L_SUBMIT} +   + + {S_FORM_TOKEN} +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
{L_TOKENS}
{L_TOKENS_EXPLAIN}
{L_TOKEN}{L_TOKEN_DEFINITION}
{token.TOKEN}{token.EXPLAIN}
+
+ + + +

{L_ACP_BBCODES}

+ +

{L_ACP_BBCODES_EXPLAIN}

+ +
+
+ {L_ACP_BBCODES} + + + + + + + + + + + + + + + + + + + + +
{L_BBCODE_TAG}{L_ACTION}
{bbcodes.BBCODE_TAG} {ICON_EDIT} {ICON_DELETE}
{L_ACP_NO_ITEMS}
+ +

+ +

+ {S_FORM_TOKEN} +
+ +
+ + + + diff --git a/adm/style/acp_board.html b/adm/style/acp_board.html new file mode 100644 index 0000000..fe3e250 --- /dev/null +++ b/adm/style/acp_board.html @@ -0,0 +1,53 @@ + + + + +

{L_TITLE}

+ +

{L_TITLE_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ + + + + + + +
+ {options.LEGEND} + + +
+

{options.TITLE_EXPLAIN}
+
{options.CONTENT}
+
+ + + + + +
+ + + +
+ {L_ACP_SUBMIT_CHANGES} + + +

+   + +

+ {S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/acp_bots.html b/adm/style/acp_bots.html new file mode 100644 index 0000000..b0e6101 --- /dev/null +++ b/adm/style/acp_bots.html @@ -0,0 +1,102 @@ + + + + + + + « {L_BACK} + +

{L_TITLE}

+ +

{L_BOT_EDIT_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ +
+ {L_TITLE} +
+

{L_BOT_NAME_EXPLAIN}
+
+
+
+

{L_BOT_STYLE_EXPLAIN}
+
+
+
+

{L_BOT_LANG_EXPLAIN}
+
+
+
+
+
+
+
+

{L_BOT_AGENT_EXPLAIN}
+
+
+
+

{L_BOT_IP_EXPLAIN}
+
+
+ +

+   + + {S_FORM_TOKEN} +

+
+
+ + + +

{L_BOTS}

+ +

{L_BOTS_EXPLAIN}

+ +
+ + + + + + + + + + + + + + + + + + + + + + +
{L_BOT_NAME}{L_BOT_LAST_VISIT}{L_OPTIONS}{L_MARK}
{bots.BOT_NAME} {bots.LAST_VISIT}  {bots.L_ACTIVATE_DEACTIVATE}  {L_EDIT}  {L_DELETE} 
+ +
+ +
+ +
+ + +

{L_MARK_ALL}{L_UNMARK_ALL}

+ {S_FORM_TOKEN} +
+
+ + + + diff --git a/adm/style/acp_captcha.html b/adm/style/acp_captcha.html new file mode 100644 index 0000000..4353bec --- /dev/null +++ b/adm/style/acp_captcha.html @@ -0,0 +1,79 @@ + + + + +

{L_ACP_VC_SETTINGS}

+ +

{L_ACP_VC_SETTINGS_EXPLAIN}

+ +

{L_ACP_VC_EXT_GET_MORE}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ +
+{L_GENERAL_OPTIONS} + +
+

{L_VISUAL_CONFIRM_REG_EXPLAIN}
+
+
+
+
+

{L_REG_LIMIT_EXPLAIN}
+
+
+
+

{L_MAX_LOGIN_ATTEMPTS_EXPLAIN}
+
+
+
+

{L_VISUAL_CONFIRM_POST_EXPLAIN}
+
+
+
+
+

{L_VISUAL_CONFIRM_REFRESH_EXPLAIN}
+
+
+
+
+ +
+{L_AVAILABLE_CAPTCHAS} +
+

{L_CAPTCHA_SELECT_EXPLAIN}
+
+
+ +
+

{L_CAPTCHA_CONFIGURE_EXPLAIN}
+
+
+ +
+ + +
+ {L_PREVIEW} + +
+ + +
+ {L_ACP_SUBMIT_CHANGES} +

+   +   +

+ {S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/acp_contact.html b/adm/style/acp_contact.html new file mode 100644 index 0000000..828fd4b --- /dev/null +++ b/adm/style/acp_contact.html @@ -0,0 +1,76 @@ + + + + + + +

{L_ACP_CONTACT_SETTINGS}

+ +

{L_ACP_CONTACT_SETTINGS_EXPLAIN}

+ +
+
+ {L_GENERAL_OPTIONS} +
+

{L_CONTACT_US_ENABLE_EXPLAIN}
+
+ + +
+
+
+ + +
+ {L_CONTACT_US_INFO_PREVIEW} +

{CONTACT_US_INFO_PREVIEW}

+
+ + +
+ {L_CONTACT_US_INFO} +

{L_CONTACT_US_INFO_EXPLAIN}

+ + + +
+
+
+ +
+ +
+ +
+ + + + + + + + + +
+
{L_OPTIONS}{L_COLON} {BBCODE_STATUS} :: {IMG_STATUS} :: {FLASH_STATUS} :: {URL_STATUS} :: {SMILIES_STATUS}
+
+
+ +
+   + + {S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/acp_database.html b/adm/style/acp_database.html new file mode 100644 index 0000000..ed0f4dd --- /dev/null +++ b/adm/style/acp_database.html @@ -0,0 +1,96 @@ + + + + + +

{L_ACP_RESTORE}

+ +

{L_ACP_RESTORE_EXPLAIN}

+ + +
+ +
+ {L_RESTORE_OPTIONS} +
+
+
+
+ +

+   +   +

+ {S_FORM_TOKEN} +
+
+ +
+

{L_ACP_NO_ITEMS}

+
+ + + +

{L_ACP_BACKUP}

+ +

{L_ACP_BACKUP_EXPLAIN}

+ + + +
+ +
+ {L_BACKUP_OPTIONS} +
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+
{L_SELECT_ALL} :: {L_DESELECT_ALL}
+
+ +

+   + +

+ {S_FORM_TOKEN} +
+
+ + + + diff --git a/adm/style/acp_disallow.html b/adm/style/acp_disallow.html new file mode 100644 index 0000000..0608f29 --- /dev/null +++ b/adm/style/acp_disallow.html @@ -0,0 +1,45 @@ + + + + +

{L_ACP_DISALLOW_USERNAMES}

+ +

{L_ACP_DISALLOW_EXPLAIN}

+ +
+ +
+ {L_ADD_DISALLOW_TITLE} +
+

{L_ADD_DISALLOW_EXPLAIN}
+
+
+ +

+ +

+
+ +

{L_DELETE_DISALLOW_TITLE}

+ +

{L_DELETE_DISALLOW_EXPLAIN}

+ +
+ {L_DELETE_DISALLOW_TITLE} + +
+
+
+
+ +

+ +

+ +

{L_NO_DISALLOWED}

+ + {S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/acp_email.html b/adm/style/acp_email.html new file mode 100644 index 0000000..e14c56a --- /dev/null +++ b/adm/style/acp_email.html @@ -0,0 +1,62 @@ + + + + +

{L_ACP_MASS_EMAIL}

+ +

{L_ACP_MASS_EMAIL_EXPLAIN}

+ + +
+

{L_WARNING}

+

{WARNING_MSG}

+
+ + +
+ +
+ {L_COMPOSE} +
+
+ +
+ +
+
+

{L_SEND_TO_USERS_EXPLAIN}
+
+
[ {L_FIND_USERNAME} ]
+
+
+
+
+
+
+

{L_MASS_MESSAGE_EXPLAIN}
+
+
+
+
+
+
+
+

{L_MAIL_BANNED_EXPLAIN}
+
+
+
+
+
+
+ + + +

+   + +

+{S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/acp_ext_delete_data.html b/adm/style/acp_ext_delete_data.html new file mode 100644 index 0000000..0f3adb7 --- /dev/null +++ b/adm/style/acp_ext_delete_data.html @@ -0,0 +1,40 @@ + + + + +

{L_EXTENSIONS_ADMIN}

+ +

{L_EXTENSIONS_EXPLAIN}

+

{L_EXTENSION_DELETE_DATA_EXPLAIN}

+ + +
+

{L_MIGRATION_EXCEPTION_ERROR}

+

{MIGRATOR_ERROR}

+

{L_RETURN_TO_EXTENSION_LIST}

+
+ +
+

{L_CONFIRM_MESSAGE}

+
+ +
+
+ {L_EXTENSION_DELETE_DATA} + + +
+
+ +
+

{L_EXTENSION_DELETE_DATA_IN_PROGRESS}

+
+ +
+

{L_EXTENSION_DELETE_DATA_SUCCESS}

+
+

{L_RETURN_TO_EXTENSION_LIST}

+
+ + + diff --git a/adm/style/acp_ext_details.html b/adm/style/acp_ext_details.html new file mode 100644 index 0000000..bbddf2e --- /dev/null +++ b/adm/style/acp_ext_details.html @@ -0,0 +1,139 @@ + + + + + « {L_BACK} + +

{L_EXTENSIONS_ADMIN}

+ + + +
+

{L_VERSIONCHECK_FAIL}

+

{VERSIONCHECK_FAIL_REASON}

+

{L_VERSIONCHECK_FORCE_UPDATE}

+
+ +
+

{UP_TO_DATE_MSG} - {L_VERSIONCHECK_FORCE_UPDATE}

+
+ + + + +
+ {L_EXT_DETAILS} + +
+
+
{META_DISPLAY_NAME}
+
+ +
+
+
{META_NAME}
+
+ +
+
+
{META_DESCRIPTION}
+
+ +
+
+
{META_VERSION}
+
+ +
+
+
{META_HOMEPAGE}
+
+ + +
+
+
{META_TIME}
+
+ +
+
+
{META_LICENSE}
+
+
+ + +
+ {L_LATEST_VERSION} + +
+
+
+
{updates_available.current}
+
+ +
+
+
{L_DOWNLOAD} {META_NAME} {LATEST_VERSION}
+
+ + +
+
+
{L_RELEASE_ANNOUNCEMENT}
+
+ +
+ +
+ + + +
+ {L_REQUIREMENTS} + + class="requirements_not_met"> +
+
{META_REQUIRE_PHPBB}
+ + + + class="requirements_not_met"> +
+
{META_REQUIRE_PHP}
+ + +
+ + +
+ {L_AUTHOR_INFORMATION} + +
+
+
+
{meta_authors.AUTHOR_NAME}
+
+ +
+
+
{meta_authors.AUTHOR_EMAIL}
+
+ + +
+
+
{meta_authors.AUTHOR_HOMEPAGE}
+
+ + +
+
+
{meta_authors.AUTHOR_ROLE}
+
+ +
+ +
+ + + diff --git a/adm/style/acp_ext_disable.html b/adm/style/acp_ext_disable.html new file mode 100644 index 0000000..d2b5c46 --- /dev/null +++ b/adm/style/acp_ext_disable.html @@ -0,0 +1,34 @@ + + + + +

{L_EXTENSIONS_ADMIN}

+ +

{L_EXTENSIONS_EXPLAIN}

+

{L_EXTENSION_DISABLE_EXPLAIN}

+ + +
+

{L_CONFIRM}

+

{L_CONFIRM_MESSAGE}

+
+ +
+
+ + +
+
+ +
+

{L_EXTENSION_DISABLE_IN_PROGRESS}

+
+ +
+

{L_EXTENSION_DISABLE_SUCCESS}

+
+

{L_RETURN_TO_EXTENSION_LIST}

+
+ + + diff --git a/adm/style/acp_ext_enable.html b/adm/style/acp_ext_enable.html new file mode 100644 index 0000000..8a4a353 --- /dev/null +++ b/adm/style/acp_ext_enable.html @@ -0,0 +1,40 @@ + + + + +

{L_EXTENSIONS_ADMIN}

+ +

{L_EXTENSIONS_EXPLAIN}

+

{L_EXTENSION_ENABLE_EXPLAIN}

+ + +
+

{L_MIGRATION_EXCEPTION_ERROR}

+

{MIGRATOR_ERROR}

+

{L_RETURN_TO_EXTENSION_LIST}

+
+ +
+

{L_CONFIRM}

+

{L_CONFIRM_MESSAGE}

+
+ +
+
+ + +
+
+ +
+

{L_EXTENSION_ENABLE_IN_PROGRESS}

+
+ +
+

{L_EXTENSION_ENABLE_SUCCESS}

+
+

{L_RETURN_TO_EXTENSION_LIST}

+
+ + + diff --git a/adm/style/acp_ext_list.html b/adm/style/acp_ext_list.html new file mode 100644 index 0000000..8e2c745 --- /dev/null +++ b/adm/style/acp_ext_list.html @@ -0,0 +1,123 @@ + + + + +

{L_EXTENSIONS_ADMIN}

+ +

{L_EXTENSIONS_EXPLAIN}

+ +
+ {L_BROWSE_EXTENSIONS_DATABASE}{L_VERSIONCHECK_FORCE_UPDATE_ALL}{L_SETTINGS} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_EXTENSION_NAME}{L_CURRENT_VERSION}{L_EXTENSION_OPTIONS}{L_EXTENSION_ACTIONS}
{L_EXTENSIONS_ENABLED}
{enabled.META_DISPLAY_NAME} + + {enabled.META_VERSION} + + + {enabled.META_VERSION} + + {L_DETAILS} + + title="{enabled.actions.L_ACTION_EXPLAIN}">{enabled.actions.L_ACTION} +  |  + +
{L_EXTENSIONS_DISABLED}
{disabled.META_DISPLAY_NAME} + + {disabled.META_VERSION} + + + {disabled.META_VERSION} + + + {L_DETAILS} + + + title="{disabled.actions.L_ACTION_EXPLAIN}">{disabled.actions.L_ACTION} +  |  + +
+ + + + + + + + + + + + + + + + + + + + + +
{L_EXTENSION_INSTALL_HEADLINE}
{L_EXTENSION_INSTALL_EXPLAIN}
{L_EXTENSION_UPDATE_HEADLINE}
{L_EXTENSION_UPDATE_EXPLAIN}
{L_EXTENSION_REMOVE_HEADLINE}
{L_EXTENSION_REMOVE_EXPLAIN}
+ + diff --git a/adm/style/acp_forums.html b/adm/style/acp_forums.html new file mode 100644 index 0000000..965438f --- /dev/null +++ b/adm/style/acp_forums.html @@ -0,0 +1,519 @@ + + + + + + + + + « {L_BACK} + +

{L_TITLE} :: {FORUM_NAME}

+ +

{L_FORUM_EDIT_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ +
+ {L_FORUM_SETTINGS} + +
+
+
+
+ +
+
+
+
+
+
+
+ + + + +
+
+
+
+ +
+

{L_COPY_PERMISSIONS_EXPLAIN}
+
+
+ +
+
+
+
+
+

{L_FORUM_DESC_EXPLAIN}
+
+
+ +
+
+
+

{L_FORUM_IMAGE_EXPLAIN}
+
+ +
{L_FORUM_IMAGE}
+ +
+
+

{L_FORUM_PASSWORD_EXPLAIN}
+
+
+
+

{L_FORUM_PASSWORD_CONFIRM_EXPLAIN}
+
+
+ +
+

{L_FORUM_PASSWORD_UNSET_EXPLAIN}
+
+
+ +
+
+
+
+ +
+ +
+
+ {L_GENERAL_FORUM_SETTINGS} +
+

{L_DISPLAY_ACTIVE_TOPICS_EXPLAIN}
+
+
+
+
+
+ +
+
+ {L_GENERAL_FORUM_SETTINGS} + +
+
+
+
+
+

{L_LIST_SUBFORUMS_EXPLAIN}
+
+
+
+
+

{L_LIST_INDEX_EXPLAIN}
+
+
+
+
+

{L_ENABLE_POST_REVIEW_EXPLAIN}
+
+
+
+
+

{L_ENABLE_QUICK_REPLY_EXPLAIN}
+
+
+
+
+

{L_ENABLE_INDEXING_EXPLAIN}
+
+
+
+
+
+
+
+
+
+

{L_ENABLE_RECENT_EXPLAIN}
+
+
+
+
+

{L_FORUM_TOPICS_PAGE_EXPLAIN}
+
+
+ +
+ +
+ {L_FORUM_PRUNE_SETTINGS} + +
+

{L_FORUM_AUTO_PRUNE_EXPLAIN}
+
+
+
+
+

{L_AUTO_PRUNE_FREQ_EXPLAIN}
+
{L_DAYS}
+
+
+

{L_AUTO_PRUNE_DAYS_EXPLAIN}
+
{L_DAYS}
+
+
+

{L_AUTO_PRUNE_VIEWED_EXPLAIN}
+
{L_DAYS}
+
+
+

{L_PRUNE_OLD_POLLS_EXPLAIN}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

{L_FORUM_PRUNE_SHADOW_EXPLAIN}
+
+
+
+
+

{L_AUTO_PRUNE_SHADOW_FREQ_EXPLAIN}
+
{L_DAYS}
+
+
+

{L_AUTO_PRUNE_SHADOW_DAYS_EXPLAIN}
+
{L_DAYS}
+
+ +
+
+ + + +
+
+ {L_FORUM_RULES} + +
+

{L_FORUM_RULES_LINK_EXPLAIN}
+
+
+ +
+
+
{FORUM_RULES_PREVIEW}
+
+ +
+

{L_FORUM_RULES_EXPLAIN}
+
+
+ +
+
+ +
+
+ + + +
+ {L_SUBMIT} +   + + {S_FORM_TOKEN} +
+
+ + + + « {L_BACK} + +

{L_FORUM_DELETE}

+ +

{L_FORUM_DELETE_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ +
+ {L_FORUM_DELETE} +
+
+
{FORUM_NAME}
+
+ +
+
+
+ +
+ +
+ + +
+
+
+ +
+ +
+ + +

+ +

+ {S_FORM_TOKEN} +
+
+ + + + + +

{L_FORUM_ADMIN}

+ +

{L_FORUM_ADMIN_EXPLAIN}

+ +

{L_PROGRESS_EXPLAIN}

+ + + + + +

{L_FORUM_ADMIN}

+ +

{L_FORUM_ADMIN_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + + + + +
+

{L_NOTIFY}

+

{L_FORUM_RESYNCED}

+
+ + +

{NAVIGATION} [{L_EDIT} | {L_DELETE} | {L_RESYNC}]

+ + + + + + + + + + + + + +
{forums.FOLDER_IMAGE} +
{forums.FORUM_IMAGE}
+ {forums.FORUM_NAME}{forums.FORUM_NAME} +
{forums.FORUM_DESCRIPTION} +

{L_TOPICS}{L_COLON} {forums.FORUM_TOPICS} / {L_POSTS}{L_COLON} {forums.FORUM_POSTS} +
+ + {ICON_MOVE_UP} + + {ICON_MOVE_DOWN} + {ICON_EDIT} + + {ICON_SYNC} + + {ICON_SYNC_DISABLED} + + {ICON_DELETE} +
+ + +
+ +
+ {L_SELECT_FORUM}{L_COLON} + + + {S_FORM_TOKEN} +
+
+ +
+ +
+ + + + + {S_FORM_TOKEN} +
+
+ + + + diff --git a/adm/style/acp_forums_copy_perm.html b/adm/style/acp_forums_copy_perm.html new file mode 100644 index 0000000..1fcf2f6 --- /dev/null +++ b/adm/style/acp_forums_copy_perm.html @@ -0,0 +1,22 @@ + + +

{L_COPY_PERMISSIONS}

+ +

{L_COPY_PERMISSIONS_EXPLAIN}

+

{L_ACL_LINK}

+ +
+ +
+
+

{L_COPY_PERMISSIONS_EXPLAIN}
+
+
+
{S_FORM_TOKEN}{S_HIDDEN_FIELDS} +   +
+
+ +
+ + diff --git a/adm/style/acp_groups.html b/adm/style/acp_groups.html new file mode 100644 index 0000000..d009637 --- /dev/null +++ b/adm/style/acp_groups.html @@ -0,0 +1,333 @@ + + + + + + + « {L_BACK} + +

{L_ACP_GROUPS_MANAGE}

+ +

{L_GROUP_EDIT_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ +
+ {L_GROUP_DETAILS} +
+
for="group_name">{L_GROUP_NAME}{L_COLON}
+
{GROUP_NAME}
+
+
+
+
+
+ +
+
+ +
+

{L_GROUP_TYPE_EXPLAIN}
+
+ + + + +
+
+ + + + + +
+

{L_COPY_PERMISSIONS_EXPLAIN}
+
+
+ +
+ +
+ {L_GROUP_OPTIONS_SAVE} + + +
+

{L_GROUP_FOUNDER_MANAGE_EXPLAIN}
+
+
+ +
+

{L_GROUP_SKIP_AUTH_EXPLAIN}
+
+
+
+
+
+
+
+
+
+
+
+

{L_GROUP_RECEIVE_PM_EXPLAIN}
+
+
+ +
+ +
+ {L_GROUP_SETTINGS_SAVE} +
+

{L_GROUP_MESSAGE_LIMIT_EXPLAIN}
+
+
+
+

{L_GROUP_MAX_RECIPIENTS_EXPLAIN}
+
+
+
+

{L_GROUP_COLOR_EXPLAIN}
+
+ +        + [ {L_COLOUR_SWATCH} ] + +
+
+
+
+
+
+
+ +
+ {L_GROUP_AVATAR} +
+

{L_AVATAR_EXPLAIN}
+
{AVATAR}
+
+
+
+
+
+
+
+ +
+

{avatar_drivers.L_EXPLAIN}

+ {avatar_drivers.OUTPUT} +
+ +
+
+ +
+ {L_SUBMIT} +   + + {S_FORM_TOKEN} +
+
+ + + + « {L_BACK} + +

{L_GROUP_MEMBERS} :: {GROUP_NAME}

+ +

{L_GROUP_MEMBERS_EXPLAIN}

+ +
+ +
+ » {L_MAKE_DEFAULT_FOR_ALL} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_GROUP_DEFAULT}{L_JOINED}{L_POSTS}{L_MARK}
{L_GROUP_LEAD}
{leader.USERNAME}{leader.USERNAME}{L_YES}{L_NO}{leader.JOINED}{leader.USER_POSTS}
{L_GROUPS_NO_MODS}
{L_GROUP_APPROVED}
{L_GROUP_PENDING}
{member.USERNAME}{member.USERNAME}{L_YES}{L_NO}{member.JOINED}{member.USER_POSTS}
{L_GROUPS_NO_MEMBERS}
+ + +
+ + +

{L_MARK_ALL}{L_UNMARK_ALL}

+
+ +

{L_ADD_USERS}

+ +

{L_ADD_USERS_EXPLAIN}

+ +
+ {L_ADD_USERS} +
+
+
+
+
+
+

{L_USER_GROUP_DEFAULT_EXPLAIN}
+
+
+
+
+

{L_USERNAMES_EXPLAIN}
+
+
[ {L_FIND_USERNAME} ]
+
+ +

+ +

+ {S_FORM_TOKEN} +
+
+ + + +

{L_ACP_GROUPS_MANAGE}

+ +

{L_ACP_GROUPS_MANAGE_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +

{L_USER_DEF_GROUPS}

+ +

{L_USER_DEF_GROUPS_EXPLAIN}

+ +
+ + + + + + + + + + + + + + + + + + + + + + +
{L_GROUP}{L_TOTAL_MEMBERS}{L_PENDING_MEMBERS}{L_OPTIONS}{L_ACTION}
{L_NO_GROUPS_CREATED}
+ + +
+ + {L_CREATE_GROUP}{L_COLON} + + + {S_FORM_TOKEN} +
+
+ +

{L_SPECIAL_GROUPS}

+ +

{L_SPECIAL_GROUPS_EXPLAIN}

+ + + + + + + + + + + + + + + + + + + + + + + + + +
{L_GROUP}{L_TOTAL_MEMBERS}{L_PENDING_MEMBERS}{L_OPTIONS}{L_ACTION}
style="color: #{groups.GROUP_COLOR}">{groups.GROUP_NAME}{groups.TOTAL_MEMBERS}{groups.PENDING_MEMBERS}{L_SETTINGS}{L_MEMBERS}{L_DELETE}{L_DELETE}
+ + + + diff --git a/adm/style/acp_groups_position.html b/adm/style/acp_groups_position.html new file mode 100644 index 0000000..20ed952 --- /dev/null +++ b/adm/style/acp_groups_position.html @@ -0,0 +1,175 @@ + + + + +

{L_MANAGE_LEGEND}

+ +
enctype="multipart/form-data"> + +
+ {L_LEGEND_SETTINGS} +
+

{L_LEGEND_SORT_GROUPNAME_EXPLAIN}
+
+ + +
+
+ +

+   + + + {S_FORM_TOKEN} +

+
+
+ +

{L_LEGEND_EXPLAIN}

+ + + + + + + + + + + + + + + + + + + + + + + +
{L_GROUP}{L_GROUP_TYPE}{L_ACTION}
style="color: {legend.GROUP_COLOUR}">{legend.GROUP_NAME}{legend.GROUP_TYPE} + + {ICON_MOVE_UP} + + {ICON_MOVE_DOWN} + {ICON_DELETE} +
{L_NO_GROUPS_ADDED}
+ +
+
+ + + + + + {S_FORM_TOKEN} +
+
+ +

{L_MANAGE_TEAMPAGE}

+ +
enctype="multipart/form-data"> + +
+ {L_TEAMPAGE_SETTINGS} +
+
+
+
+
+ +
+
+
+

{L_TEAMPAGE_FORUMS_EXPLAIN}
+
+ + +
+
+ +

+   + + + {S_FORM_TOKEN} +

+
+
+ +

{L_TEAMPAGE_EXPLAIN}

+ +

{L_TEAMPAGE} » {CURRENT_CATEGORY_NAME}

+ + + + + + + + + + + + + + + + + + + + + + + +
{L_GROUP}{L_GROUP_TYPE}{L_ACTION}
+ + {teampage.GROUP_NAME} + + style="color: {teampage.GROUP_COLOUR}">{teampage.GROUP_NAME} + + {teampage.GROUP_TYPE}- + + + {ICON_MOVE_UP} + + {ICON_MOVE_DOWN} + {ICON_DELETE} +
{L_NO_GROUPS_ADDED}
+ + +
+
+ + + + {S_FORM_TOKEN} +
+
+ + +
+
+ + + + + + {S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/acp_help_phpbb.html b/adm/style/acp_help_phpbb.html new file mode 100644 index 0000000..478ecc1 --- /dev/null +++ b/adm/style/acp_help_phpbb.html @@ -0,0 +1,61 @@ + + + + +

{L_ACP_HELP_PHPBB}

+ +
+
+ +
+

{L_SEND_STATISTICS}

+

{L_EXPLAIN_SEND_STATISTICS}

+
+ +
+
+ +
+ {providers.NAME} + +
+
{providers.values.KEY}
+
{providers.values.VALUE}
+
+ +
+ +
+
+
+
+
+ checked="checked" /> + +
+
{L_SEND_STATISTICS_LONG}
+
+
+ +
+

+ + + +

+ {S_FORM_TOKEN} +
+
+
+
+
+

+ + +

+
+
+ + diff --git a/adm/style/acp_icons.html b/adm/style/acp_icons.html new file mode 100644 index 0000000..5493cbd --- /dev/null +++ b/adm/style/acp_icons.html @@ -0,0 +1,282 @@ + + + + + + + + + « {L_BACK} + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ +
+ +
+ {L_TITLE} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_CONFIG}
{L_URL}{L_LOCATION}{L_SMILIES_CODE}{L_SMILIES_EMOTION}{L_WIDTH}{L_HEIGHT}{L_ALT_TEXT}{L_DISPLAY_ON_POSTING}{L_ORDER}{L_ADD} ({L_MARK_ALL})
{items.TEXT_ALT}[{items.IMG}] + + + + +
{L_ADD_SMILEY_CODE}
{L_NO_ICONS}
+ +

+   + +

+ {S_FORM_TOKEN} +
+
+ + + + « {L_BACK} + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ +
+ +
+ {L_IMPORT} + + +

{L_NO_PAK_OPTIONS}

+ + +
+
+
+
+

{L_CURRENT_EXPLAIN}
+
+ +
+ + +

+ +

+ + {S_FORM_TOKEN} +
+
+ + + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ + +
+

{L_NOTIFY}

+

{NOTICE}

+
+ + +
+ +
{L_IMPORT} | {L_EXPORT}
+ +
+ + {L_TITLE} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_TITLE}{L_CODE}{L_EMOTION}{L_OPTIONS}
{L_NOT_DISPLAYED}
{items.ALT_TEXT}{items.CODE}{items.EMOTION} + + {ICON_MOVE_UP} + + {ICON_MOVE_DOWN} + {ICON_EDIT} {ICON_DELETE} +
{L_ACP_NO_ITEMS}
+ +

+     +

+ {S_FORM_TOKEN} +
+
+ + + + diff --git a/adm/style/acp_inactive.html b/adm/style/acp_inactive.html new file mode 100644 index 0000000..2e17bea --- /dev/null +++ b/adm/style/acp_inactive.html @@ -0,0 +1,76 @@ + + + + +

{L_INACTIVE_USERS}

+ +

{L_INACTIVE_USERS_EXPLAIN}

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_EMAIL}{L_JOINED}{L_INACTIVE_DATE}{L_LAST_VISIT}{L_INACTIVE_REASON}{L_MARK}
+ {inactive.USERNAME_FULL} +
{L_POSTS}{L_COLON} {inactive.POSTS} [{L_SEARCH_USER_POSTS}] +
{inactive.USER_EMAIL}{inactive.JOINED}{inactive.INACTIVE_DATE}{inactive.LAST_VISIT} + {inactive.REASON} +
{inactive.REMINDED_EXPLAIN} +
  
{L_NO_INACTIVE_USERS}
+ +
+ {L_DISPLAY_LOG}{L_COLON}  {S_LIMIT_DAYS} {L_SORT_BY}{L_COLON} {S_SORT_KEY} {S_SORT_DIR} {L_USERS_PER_PAGE}{L_COLON} + +
+ +
+ + + + + +
+ + +

{L_MARK_ALL}{L_UNMARK_ALL}

+ {S_FORM_TOKEN} +
+ +
+ + diff --git a/adm/style/acp_jabber.html b/adm/style/acp_jabber.html new file mode 100644 index 0000000..e76c9a0 --- /dev/null +++ b/adm/style/acp_jabber.html @@ -0,0 +1,80 @@ + + + + +

{L_ACP_JABBER_SETTINGS}

+ +

{L_ACP_JABBER_SETTINGS_EXPLAIN}

+ + +
+

{L_WARNING}

+

{WARNING_MSG}

+
+ + +
+ +
+ {L_ACP_JABBER_SETTINGS} + +

{L_JAB_GTALK_NOTE}

+ +
+

{L_JAB_ENABLE_EXPLAIN}
+
+
+
+
+

{L_JAB_SERVER_EXPLAIN}
+
+
+
+

{L_JAB_PORT_EXPLAIN}
+
+
+
+

{L_JAB_USERNAME_EXPLAIN}
+
+
+
+

{L_JAB_PASSWORD_EXPLAIN}
+
+
+ +
+

{L_JAB_USE_SSL_EXPLAIN}
+
+
+
+
+

{L_JAB_VERIFY_PEER_EXPLAIN}
+
+
+
+
+

{L_JAB_VERIFY_PEER_NAME_EXPLAIN}
+
+
+
+
+

{L_JAB_ALLOW_SELF_SIGNED_EXPLAIN}
+
+
+
+ +
+

{L_JAB_PACKAGE_SIZE_EXPLAIN}
+
+
+ +
+ +
+   + + {S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/acp_language.html b/adm/style/acp_language.html new file mode 100644 index 0000000..79fef94 --- /dev/null +++ b/adm/style/acp_language.html @@ -0,0 +1,116 @@ + + + + + + + « {L_BACK} + +

{L_LANGUAGE_PACK_DETAILS}

+ +
+ +
+ {LANG_LOCAL_NAME} +
+
+
+
+
+
+
+
+
+
+
{LANG_ISO}
+
+
+
+
+
+ +

+ +

+ {S_FORM_TOKEN} +
+
+ + +

{L_MISSING_FILES}

+ +
+ {L_MISSING_LANG_FILES} + + » {missing_files.FILE_NAME}
+ +
+ + + +

{L_MISSING_VARS_EXPLAIN}

+ +
+ {L_MISSING_LANG_VARIABLES} + +
+
+ +
{missing_varfile.variable.VAR_NAME}
+ +
+ +
+ + + +

{L_ACP_LANGUAGE_PACKS}

+ +

{L_ACP_LANGUAGE_PACKS_EXPLAIN}

+ +
+ {L_BROWSE_LANGUAGE_PACKS_DATABASE} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_LANGUAGE_PACK_NAME}{L_LANGUAGE_PACK_LOCALNAME}{L_LANGUAGE_PACK_ISO}{L_LANGUAGE_PACK_USED_BY}{L_OPTIONS}
{L_INSTALLED_LANGUAGE_PACKS}
{lang.ENGLISH_NAME} {lang.TAG}{lang.LOCAL_NAME}{lang.ISO}{lang.USED_BY}{L_DELETE}
{L_UNINSTALLED_LANGUAGE_PACKS}
{notinst.NAME}{notinst.LOCAL_NAME}{notinst.ISO}{L_INSTALL}
+ + + + diff --git a/adm/style/acp_logs.html b/adm/style/acp_logs.html new file mode 100644 index 0000000..cb15a8f --- /dev/null +++ b/adm/style/acp_logs.html @@ -0,0 +1,90 @@ + + + + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_IP}{L_TIME}{L_ACTION}{L_MARK}
+ {log.USERNAME} + +
» {log.REPORTEE_USERNAME} + +
{log.IP}{log.DATE}{log.ACTION}
{log.DATA}
+ + + + + + +
+

{L_NO_ENTRIES}

+
+ + +
+ {L_DISPLAY_LOG}{L_COLON}  {S_LIMIT_DAYS} {L_SORT_BY}{L_COLON} {S_SORT_KEY} {S_SORT_DIR} + + {S_FORM_TOKEN} +
+
+ + +
+ {L_SELECT_FORUM}{L_COLON} + +
+ + + +
+   +
+

{L_MARK_ALL}{L_UNMARK_ALL}

+
+ + +
+ + diff --git a/adm/style/acp_main.html b/adm/style/acp_main.html new file mode 100644 index 0000000..12477a4 --- /dev/null +++ b/adm/style/acp_main.html @@ -0,0 +1,312 @@ + + + + + + +

{L_PERMISSIONS_TRANSFERRED}

+ +

{L_PERMISSIONS_TRANSFERRED_EXPLAIN}

+ + + +

{L_WELCOME_PHPBB}

+ +

{L_ADMIN_INTRO}

+ + +
+

{L_UPDATE_INCOMPLETE} {L_MORE_INFORMATION}

+
+ +
+

{L_VERSIONCHECK_FAIL}

+

{VERSIONCHECK_FAIL_REASON}

+

{L_VERSIONCHECK_FORCE_UPDATE} · {L_MORE_INFORMATION}

+
+ +
+

{L_VERSION_NOT_UP_TO_DATE_TITLE}

+

{L_VERSIONCHECK_FORCE_UPDATE} · {L_MORE_INFORMATION}

+
+ + +
+

{UPGRADE_INSTRUCTIONS}

+
+ + + +
+

{L_WARNING}

+

{L_NO_SEARCH_INDEX}

+
+ + + +
+

{L_WARNING}

+

{L_REMOVE_INSTALL}

+
+ + + + +
+

{L_ERROR_MBSTRING_FUNC_OVERLOAD}

+

{L_ERROR_MBSTRING_FUNC_OVERLOAD_EXPLAIN}

+
+ + + +
+

{L_ERROR_MBSTRING_ENCODING_TRANSLATION}

+

{L_ERROR_MBSTRING_ENCODING_TRANSLATION_EXPLAIN}

+
+ + + +
+

{L_ERROR_MBSTRING_HTTP_INPUT}

+

{L_ERROR_MBSTRING_HTTP_INPUT_EXPLAIN}

+
+ + + +
+

{L_ERROR_MBSTRING_HTTP_OUTPUT}

+

{L_ERROR_MBSTRING_HTTP_OUTPUT_EXPLAIN}

+
+ + + + +
+

{L_WRITABLE_CONFIG}

+
+ + + +
+

{L_PHP_VERSION_OLD}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_FORUM_STATS}
{L_STATISTIC}{L_VALUE}{L_STATISTIC}{L_VALUE}
{L_NUMBER_POSTS}{L_COLON} {TOTAL_POSTS}{L_POSTS_PER_DAY}{L_COLON} {POSTS_PER_DAY}
{L_NUMBER_TOPICS}{L_COLON} {TOTAL_TOPICS}{L_TOPICS_PER_DAY}{L_COLON} {TOPICS_PER_DAY}
{L_NUMBER_USERS}{L_COLON} {TOTAL_USERS}{L_USERS_PER_DAY}{L_COLON} {USERS_PER_DAY}
{L_NUMBER_FILES}{L_COLON} {TOTAL_FILES}{L_FILES_PER_DAY}{L_COLON} {FILES_PER_DAY}
{L_BOARD_STARTED}{L_COLON} {START_DATE}{L_AVATAR_DIR_SIZE}{L_COLON} {AVATAR_DIR_SIZE}
{L_DATABASE_SIZE}{L_COLON} {DBSIZE}{L_UPLOAD_DIR_SIZE}{L_COLON} {UPLOAD_DIR_SIZE}
{L_DATABASE_SERVER_INFO}{L_COLON} {DATABASE_INFO}{L_GZIP_COMPRESSION}{L_COLON} {GZIP_COMPRESSION}
{L_PHP_VERSION}{L_COLON} {PHP_VERSION_INFO}{L_NUMBER_ORPHAN}{L_COLON} + + {TOTAL_ORPHAN} + + {TOTAL_ORPHAN} + +   
{L_BOARD_VERSION}{L_COLON} + style="color: #228822;" style="color: #BC2A4D;" title="{L_MORE_INFORMATION}">{BOARD_VERSION}{L_VERSIONCHECK_FORCE_UPDATE} ] +   
+ + +
+ {L_STATISTIC_RESYNC_OPTIONS} + +
+
+

 
+
+
+
+ +
+
+

 
+
+
+
+ +
+
+

{L_RESYNC_STATS_EXPLAIN}
+
+
+
+ +
+
+

{L_RESYNC_POSTCOUNTS_EXPLAIN}
+
+
+
+ +
+
+

{L_RESYNC_POST_MARKING_EXPLAIN}
+
+
+
+ + +
+
+

{L_PURGE_SESSIONS_EXPLAIN}
+
+
+
+ + +
+
+

{L_PURGE_CACHE_EXPLAIN}
+
+
+
+ + +
+ + + +

{L_ADMIN_LOG}

+ +

{L_ADMIN_LOG_INDEX_EXPLAIN}

+ +
» {L_VIEW_ADMIN_LOG}
+ + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_IP}{L_TIME}{L_ACTION}
{log.USERNAME}{log.IP}{log.DATE}{log.ACTION}
+ + + +

{L_INACTIVE_USERS}

+ +

{L_INACTIVE_USERS_EXPLAIN_INDEX}

+ +
» {L_VIEW_INACTIVE_USERS}
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_JOINED}{L_INACTIVE_DATE}{L_LAST_VISIT}{L_INACTIVE_REASON}
+ {inactive.USERNAME_FULL} +
{L_POSTS}{L_COLON} {inactive.POSTS} [{L_SEARCH_USER_POSTS}] +
{inactive.JOINED}{inactive.INACTIVE_DATE}{inactive.LAST_VISIT} + {inactive.REASON} +
{inactive.REMINDED_EXPLAIN} +
{L_NO_INACTIVE_USERS}
+ + + + + diff --git a/adm/style/acp_modules.html b/adm/style/acp_modules.html new file mode 100644 index 0000000..3c97706 --- /dev/null +++ b/adm/style/acp_modules.html @@ -0,0 +1,203 @@ + + + + + + + + + « {L_BACK} + +

{L_TITLE} :: {MODULENAME}

+ +

{L_EDIT_MODULE_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ +
+ {L_GENERAL_OPTIONS} +
+

+ {L_MODULE_LANGNAME_EXPLAIN}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
style="display: none;"> +
+

{L_MODULE_DISPLAYED_EXPLAIN}
+
+
+
+
+

+ {L_CHOOSE_MODULE_EXPLAIN}
+
+
+
+

+ {L_CHOOSE_MODE_EXPLAIN}
+
+
+
+ +

+ + + +   + +

+ {S_FORM_TOKEN} +
+
+ + + +

{L_ACP_MODULE_MANAGEMENT}

+ +

{L_ACP_MODULE_MANAGEMENT_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + + + + + + + +
{NAVIGATION} [{L_EDIT} | {L_DELETE} | {L_DISABLE}{L_ENABLE}]
+ + + + + + + + + + + + + + +
{modules.MODULE_IMAGE}{modules.MODULE_TITLE} [{L_HIDDEN_MODULE}] {L_DISABLE}{L_ENABLE}  + + {ICON_MOVE_UP} + + {ICON_MOVE_DOWN} + {ICON_EDIT} + {ICON_DELETE} +
+ + +
 
+ +
+ +
+ + + + +
+ +
+ +
+ +
+ + + + + +
+ +
+ +
 

+ +
+
+ {L_SELECT_MODULE}{L_COLON} + + +
+
+ + + + diff --git a/adm/style/acp_permission_roles.html b/adm/style/acp_permission_roles.html new file mode 100644 index 0000000..b3137f1 --- /dev/null +++ b/adm/style/acp_permission_roles.html @@ -0,0 +1,191 @@ + + + + + + + + + + + « {L_BACK} + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ +
+ » {L_SET_ROLE_PERMISSIONS} + +
+ +
+ {L_ROLE_DETAILS} +
+
+
+
+
+

{L_ROLE_DESCRIPTION_EXPLAIN}
+
+
+ +

+ + {S_FORM_TOKEN} +

+
+ + + +

{L_ROLE_ASSIGNED_TO}

+ + + + + +

+ + + + » {L_BACK_TO_TOP}
+

+ +

+ +

{L_ACL_TYPE}

+ +
+ +
+
+ +
+ +
style="display: none;"> +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_ACL_SETTING}{L_ACL_YES}{L_ACL_NO}{L_ACL_NEVER}
{auth.mask.PERMISSION}
+
+
+ +
+ +
+ +
+ + {S_FORM_TOKEN} +
+
+ + » {L_BACK_TO_TOP}
+
+ + + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ +
+ + + + + + + + + + + + + + + + + + +
{L_ROLE_NAME}{L_OPTIONS}
{roles.ROLE_NAME} +
{roles.ROLE_DESCRIPTION} +
{L_VIEW_ASSIGNED_ITEMS}{L_VIEW_ASSIGNED_ITEMS} + + {ICON_MOVE_UP} + + {ICON_MOVE_DOWN} + {ICON_EDIT} + {ICON_DELETE} +
+ +
+ {L_CREATE_ROLE}{L_COLON}
+ {S_FORM_TOKEN} +
+
+ + + + + +

{L_ROLE_ASSIGNED_TO}

+ + + + + + + + diff --git a/adm/style/acp_permissions.html b/adm/style/acp_permissions.html new file mode 100644 index 0000000..7766052 --- /dev/null +++ b/adm/style/acp_permissions.html @@ -0,0 +1,354 @@ + + + + + + +

{L_ACP_PERMISSIONS}

+ + {L_ACP_PERMISSIONS_EXPLAIN} + + + + + + « {L_BACK} + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ + +

{L_FORUMS}{L_COLON} {FORUM_NAMES}

+ + + + +
+ +
+ {L_LOOK_UP_FORUM} +

{L_LOOK_UP_FORUMS_EXPLAIN}

+
+
+
+
+
+ +

+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +

+ +
+
+ + + +
+ +
+ {L_LOOK_UP_FORUM} +

{L_SELECT_FORUM_SUBFORUM_EXPLAIN}

+
+
+
+
+ +

+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +

+ +
+
+ + + + + +
+ +
+ {L_LOOK_UP_USER} +
+
+
+
[ {L_FIND_USERNAME} ]
+
+
+ +

+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +

+
+
+ + + +
+ +
+ {L_LOOK_UP_GROUP} +
+
+
+
+ +

+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +

+ +
+
+ + + +
+ + + +

{L_USERS}

+ +
+ +
+ {L_MANAGE_USERS} +
+
+
+
+
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} +   +
+
+ +
+ +
+ {L_ADD_USERS} +

{L_USERNAMES_EXPLAIN}

+
+
+
+
+
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +
+
+ + + +
+ +
+ + + +

{L_USERGROUPS}

+ +
+ +
+ {L_MANAGE_GROUPS} +
+
+
+
+
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} +   +
+
+ +
+ +
+ {L_ADD_GROUPS} +
+
+
+
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +
+
+ + + +
+ + + +
+ +

{L_USERS}

+ +
+ +
+ {L_MANAGE_USERS} +
+
+
+
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +
+
+ +
+ +
+ {L_LOOK_UP_USER} +
+
+
+
[ {L_FIND_USERNAME} ]
+
+
+
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +
+
+ +
+ +
+ +

{L_USERGROUPS}

+ +
+ +
+ {L_MANAGE_GROUPS} +
+
+
+
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +
+
+ +
+ +
+ {L_LOOK_UP_GROUP} +
+
+ +
+ +
 
+
+
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +
+
+ +
+ + + + + + + +

{L_ACL_VIEW}

+ +

{L_ACL_VIEW_EXPLAIN}

+ +
+ » {L_PERMISSION_TYPE} +
+ + + + + + + +

{L_ACL_SET}

+ +

{L_ACL_SET_EXPLAIN}

+ +
+ +
+ » {L_PERMISSION_TYPE} +
+ + +
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + {L_SELECT_TYPE}{L_COLON} + + +
+
+ + +

+ + + + +
+ + {S_HIDDEN_FIELDS} + + + +

+ +
+ + + {S_FORM_TOKEN} +
+ +

+ +
+ + + + diff --git a/adm/style/acp_php_info.html b/adm/style/acp_php_info.html new file mode 100644 index 0000000..760cd0e --- /dev/null +++ b/adm/style/acp_php_info.html @@ -0,0 +1,13 @@ + + + + +

{L_ACP_PHP_INFO}

+ +

{L_ACP_PHP_INFO_EXPLAIN}

+ +
+ {PHPINFO} +
+ + diff --git a/adm/style/acp_posting_buttons.html b/adm/style/acp_posting_buttons.html new file mode 100644 index 0000000..c3c42f8 --- /dev/null +++ b/adm/style/acp_posting_buttons.html @@ -0,0 +1,71 @@ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ diff --git a/adm/style/acp_profile.html b/adm/style/acp_profile.html new file mode 100644 index 0000000..25bf97e --- /dev/null +++ b/adm/style/acp_profile.html @@ -0,0 +1,250 @@ + + + + + + + « {L_BACK} + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ + + +
+ {L_TITLE} +
+

{L_FIELD_TYPE_EXPLAIN}
+
{FIELD_TYPE}
+
+ +
+

{L_FIELD_IDENT_EXPLAIN}
+
{FIELD_IDENT}
+
+ +
+

{L_FIELD_IDENT_EXPLAIN}
+
+
+ +
+

{L_DISPLAY_PROFILE_FIELD_EXPLAIN}
+
+
+
+
+ +
+ {L_VISIBILITY_OPTION} +
+

{L_DISPLAY_AT_PROFILE_EXPLAIN}
+
checked="checked" />
+
+
+

{L_DISPLAY_AT_REGISTER_EXPLAIN}
+
checked="checked" />
+
+
+

{L_DISPLAY_ON_PM_EXPLAIN}
+
checked="checked" />
+
+
+

{L_DISPLAY_ON_VT_EXPLAIN}
+
checked="checked" />
+
+
+

{L_DISPLAY_ON_MEMBERLIST_EXPLAIN}
+
checked="checked" />
+
+
+

{L_REQUIRED_FIELD_EXPLAIN}
+
checked="checked" />
+
+
+

{L_SHOW_NOVALUE_FIELD_EXPLAIN}
+
checked="checked" />
+
+
+

{L_HIDE_PROFILE_FIELD_EXPLAIN}
+
checked="checked" />
+
+ +
+

{L_FIELD_IS_CONTACT_EXPLAIN}
+
checked="checked" />
+
+
+ +
+
+ + +
+ +
+ + +
+ {L_LANG_SPECIFIC} +
+
+
+
+
+

{L_FIELD_DESCRIPTION_EXPLAIN}
+
+
+ +
+

{L_DEFAULT_VALUE_EXPLAIN}
+
+
+ + +
+
+ +
{L_EDIT_DROPDOWN_LANG_EXPLAIN} + +
{L_LANG_OPTIONS_EXPLAIN} + +
+ +
+ +
{L_FIRST_OPTION}
+
{L_SECOND_OPTION}
+ +
+ + +
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +
+ + + +
+ {L_TITLE} + +
+

{option.EXPLAIN}
+
{option.FIELD}
+
+ +
+ +
+ +
+ +
+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +
+ + + + +
+ {options.LANGUAGE} + +
+

{options.field.L_EXPLAIN}
+ {options.field.FIELD} +
+ +
+ + +
+ +
+ +
+ {S_HIDDEN_FIELDS} + + {S_FORM_TOKEN} +
+ + + +
+ + + +

{L_ACP_CUSTOM_PROFILE_FIELDS}

+ + +
+

{L_WARNING}

+

{L_CUSTOM_FIELDS_NOT_TRANSLATED}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_FIELD_IDENT}{L_FIELD_TYPE}{L_OPTIONS}
{fields.FIELD_IDENT}{fields.FIELD_TYPE}{fields.L_ACTIVATE_DEACTIVATE} | {L_TRANSLATE} + + {ICON_MOVE_UP} + + {ICON_MOVE_DOWN} + + {ICON_EDIT} + + {ICON_EDIT_DISABLED} + + {ICON_DELETE} +
{L_ACP_NO_ITEMS}
+ +
+ +
+ + + + {S_FORM_TOKEN} +
+
+ + + + diff --git a/adm/style/acp_prune_forums.html b/adm/style/acp_prune_forums.html new file mode 100644 index 0000000..ef3880e --- /dev/null +++ b/adm/style/acp_prune_forums.html @@ -0,0 +1,110 @@ + + + + + + +

{L_FORUM_PRUNE}

+ +

{L_PRUNE_SUCCESS}

+ + + + + + + + + + + + + + + + + + + + + + +
{L_FORUM}{L_TOPICS_PRUNED}{L_POSTS_PRUNED}
{pruned.FORUM_NAME}{pruned.NUM_TOPICS}{pruned.NUM_POSTS}
{L_NO_PRUNE}
+ + + +

{L_ACP_PRUNE_FORUMS}

+ +

{L_ACP_PRUNE_FORUMS_EXPLAIN}

+ +
+ +
+ {L_SELECT_FORUM} +

{L_LOOK_UP_FORUMS_EXPLAIN}

+
+
+
+
+
+ +

+ +

+
+ +
+ + + + « {L_BACK} + +

{L_ACP_PRUNE_FORUMS}

+ +

{L_ACP_PRUNE_FORUMS_EXPLAIN}

+ +

{L_FORUM}

+ +

{L_SELECTED_FORUMS}{L_COLON} {FORUM_LIST}

+ +
+ +
+ {L_FORUM_PRUNE} +
+
+
+
+
+
+
+
+
+

{L_PRUNE_OLD_POLLS_EXPLAIN}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + +

+ {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} + +

+
+
+ + + + diff --git a/adm/style/acp_prune_users.html b/adm/style/acp_prune_users.html new file mode 100644 index 0000000..6e8b2e4 --- /dev/null +++ b/adm/style/acp_prune_users.html @@ -0,0 +1,80 @@ + + + + +

{L_ACP_PRUNE_USERS}

+ +

{L_ACP_PRUNE_USERS_EXPLAIN}

+ +
+ +
+ {L_CRITERIA} +
+
+
+
+
+
+
+
+
+

{L_JOINED_EXPLAIN}
+
+ {L_AFTER} +

{L_BEFORE} +
+
+
+

{L_LAST_ACTIVE_EXPLAIN}
+
+
+
+
+
+
+
+
+
+
+ +
+

{L_PRUNE_USERS_GROUP_EXPLAIN}
+
+
+ +
+ +
+ {L_USERNAMES} +
+

{L_SELECT_USERS_EXPLAIN}
+
+
[ {L_FIND_USERNAME} ]
+
+
+ +
+ {L_OPTIONS} +
+

{L_DELETE_USER_POSTS_EXPLAIN}
+
+
+
+
+

{L_DEACTIVATE_DELETE_EXPLAIN}
+
+
+
+ +

+ + +   + + {S_FORM_TOKEN} +

+
+
+ + diff --git a/adm/style/acp_ranks.html b/adm/style/acp_ranks.html new file mode 100644 index 0000000..e67c9ac --- /dev/null +++ b/adm/style/acp_ranks.html @@ -0,0 +1,107 @@ + + + + + + + « {L_BACK} + + + +

{L_ACP_MANAGE_RANKS}

+ +

{L_ACP_RANKS_EXPLAIN}

+ +
+ +
+ {L_ACP_RANKS} + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
style="display: none;"> +
+
+
+
+
+ + + +

+ + +   + + {S_FORM_TOKEN} +

+
+
+ + + +

{L_ACP_MANAGE_RANKS}

+ +

{L_ACP_RANKS_EXPLAIN}

+ +
+
+ {L_ACP_MANAGE_RANKS} + + + + + + + + + + + + + + + + + + + + + + + + +
{L_RANK_IMAGE}{L_RANK_TITLE}{L_RANK_MINIMUM}{L_ACTION}
{ranks.RANK_TITLE}  -  {ranks.RANK_TITLE}  -  {ranks.MIN_POSTS}{ICON_EDIT} {ICON_DELETE}
+ +

+ + {S_FORM_TOKEN} +

+
+
+ + + + diff --git a/adm/style/acp_reasons.html b/adm/style/acp_reasons.html new file mode 100644 index 0000000..d629a95 --- /dev/null +++ b/adm/style/acp_reasons.html @@ -0,0 +1,121 @@ + + + + + + + « {L_BACK} + +

{L_TITLE}

+ +

{L_REASON_EDIT_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + + +

{L_AVAILABLE_TITLES}

+ +

{S_AVAILABLE_TITLES}

+ + +
+ +
+ {L_TITLE} +

{L_IS_TRANSLATED_EXPLAIN}{L_IS_NOT_TRANSLATED_EXPLAIN}

+
+
+
+
+ +
+
{L_REASON_TITLE_TRANSLATED}
+
{TRANSLATED_TITLE}
+
+ +
+
+
+
+ +
+
{L_REASON_DESC_TRANSLATED}
+
{TRANSLATED_DESCRIPTION}
+
+ + +

+   + + {S_FORM_TOKEN} +

+
+
+ + + +

{L_ACP_REASONS}

+ +

{L_ACP_REASONS_EXPLAIN}

+ +
+
+ {L_ACP_REASONS} + + + + + + + + + + + + + + + + + + + + +
{L_REASON}{L_USED_IN_REPORTS}{L_OPTIONS}
+ {L_IS_TRANSLATED}{L_IS_NOT_TRANSLATED} + {reasons.REASON_TITLE} * +
{reasons.REASON_DESCRIPTION} +
{reasons.REASON_COUNT} + + {ICON_MOVE_UP} + + {ICON_MOVE_DOWN} + {ICON_EDIT} + + {ICON_DELETE} + + {ICON_DELETE_DISABLED} + +
+ + + +

+ + + + + {S_FORM_TOKEN} +

+
+ +
+ + + + diff --git a/adm/style/acp_search.html b/adm/style/acp_search.html new file mode 100644 index 0000000..f7ad3c5 --- /dev/null +++ b/adm/style/acp_search.html @@ -0,0 +1,159 @@ + + + + + +

{L_ACP_SEARCH_SETTINGS}

+ +

{L_ACP_SEARCH_SETTINGS_EXPLAIN}

+ + + + + + + +

{L_ACP_SEARCH_INDEX}

+ + +

{L_CONTINUE_EXPLAIN}

+ +
+
+ {L_SUBMIT} +   + + {S_FORM_TOKEN} +
+
+ + +

{L_ACP_SEARCH_INDEX_EXPLAIN}

+ + + + + +
+ +
+ + {backend.S_HIDDEN_FIELDS} + + {L_INDEX_STATS}{L_COLON} {backend.L_NAME} ({L_ACTIVE}) + + + + + + + + + + + + + + + + + + + + + + +
{backend.L_NAME} ({L_ACTIVE})
{L_STATISTIC}{L_VALUE}{L_STATISTIC}{L_VALUE}
{backend.data.STATISTIC_1}{L_COLON}{backend.data.VALUE_1}{backend.data.STATISTIC_2}{L_COLON}{backend.data.VALUE_2}
+ + + +

+ + + + + + + +

+ {S_FORM_TOKEN} +
+ +
+ + + + + + + diff --git a/adm/style/acp_styles.html b/adm/style/acp_styles.html new file mode 100644 index 0000000..38bec5a --- /dev/null +++ b/adm/style/acp_styles.html @@ -0,0 +1,176 @@ + + + + + + « {L_BACK} + + + +
+ +
+

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+ + + + + {S_HIDDEN_FIELDS} + +
+   + +
+ +
+ +
+ + +

{L_TITLE}

+ +

{L_EXPLAIN}

+ +
+ {L_BROWSE_STYLES_DATABASE} +
+ +
+{S_HIDDEN_FIELDS} +{S_FORM_TOKEN} + + + +
+
+
+
+
+
+
+
{STYLE_PATH}
+
+
+
+
{STYLE_VERSION}
+
+
+
+
{STYLE_COPYRIGHT}
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+ +
+ +
+ {L_SUBMIT} +   + + {S_FORM_TOKEN} +
+ + + + + + + + + + + + {STYLES_LIST_EXTRA} + + + + + + + + + + + + + + + + + + {styles_list.EXTRA} + + + + +
{L_STYLE_NAME}{L_STYLE_PHPBB_VERSION}{L_STYLE_USED_BY}{L_ACTIONS} 
+ + + + + {styles_list.STYLE_NAME} +
{styles_list.STYLE_COPYRIGHT}
+ + {styles_list.STYLE_NAME} + + +
{styles_list.COMMENT}
+ + +
{L_STYLE_PATH}{L_COLON} {styles_list.STYLE_PATH_FULL}
+ +
{styles_list.STYLE_PHPBB_VERSION}{styles_list.USERS} + + | + + {styles_list.actions.L_ACTION} + + {styles_list.actions.HTML} + + + + + + +   + + + + +
+ + + +
+ + + +
+ + +
+ + + + diff --git a/adm/style/acp_update.html b/adm/style/acp_update.html new file mode 100644 index 0000000..5288833 --- /dev/null +++ b/adm/style/acp_update.html @@ -0,0 +1,72 @@ + + + + +

{L_VERSION_CHECK}

+ +

{L_VERSION_CHECK_EXPLAIN}

+ + +
+

{L_UPDATE_INCOMPLETE} {L_UPDATE_INCOMPLETE_MORE}

+
+ + +
+

{L_VERSION_UP_TO_DATE_ACP} - {L_VERSIONCHECK_FORCE_UPDATE}

+
+ +
+

{L_VERSION_NOT_UP_TO_DATE_ACP} - {L_VERSIONCHECK_FORCE_UPDATE}

+
+ + +
+

{UPGRADE_INSTRUCTIONS}

+
+ + +
+ + +
+
+
{CURRENT_VERSION}
+
+ +
+
+
{FILES_VERSION}
+
+
+
+
{CURRENT_VERSION}
+
+ +
+ + +
+ +
+
+
{updates_available.current}
+
+
+
+
{updates_available.announcement}
+
+
+ + + + {INCOMPLETE_INSTRUCTIONS} +
+ + + + {UPDATE_INSTRUCTIONS} +

+ + + diff --git a/adm/style/acp_users.html b/adm/style/acp_users.html new file mode 100644 index 0000000..50b6ec9 --- /dev/null +++ b/adm/style/acp_users.html @@ -0,0 +1,240 @@ + + + + + + +

{L_USER_ADMIN}

+ +

{L_USER_ADMIN_EXPLAIN}

+ +
+ +
+ {L_SELECT_USER} +
+
+
+
[ {L_FIND_USERNAME} ]
+
+
+ +

+ +

+
+ +
+ + + + « {L_BACK} + +

{L_USER_ADMIN}

+ +

{L_USER_ADMIN_EXPLAIN}

+ +
+ +
+ {L_USER_ADMIN_MOVE_POSTS} +
+

{L_MOVE_POSTS_EXPLAIN}
+
+
+
+ +
+ + {S_FORM_TOKEN} +
+
+ + + + « {L_BACK} + +

{L_USER_ADMIN} :: {MANAGED_USERNAME}

+ +

{L_USER_ADMIN_EXPLAIN}

+ + +
+

{L_WARNING}

+

{ERROR_MSG}

+
+ + +
+ +
+ {L_SELECT_FORM}{L_COLON} + {S_FORM_TOKEN} +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ {L_ACP_USER_RANK} +
+
+
+
+
+ +
+ + {S_FORM_TOKEN} +
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + +
{group.GROUP_TYPE}
{group.GROUP_NAME}{L_GROUP_DEFAULT}{L_GROUP_DEFAULT}{L_GROUP_APPROVE} {group.L_DEMOTE_PROMOTE} {L_GROUP_DELETE}
+ + +
+ + {L_USER_GROUP_ADD}{L_COLON} + + {S_FORM_TOKEN} +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_FILENAME}{L_POST_TIME}{L_FILESIZE}{L_DOWNLOADS}{L_MARK}
{attach.REAL_FILENAME}
{L_PM}{L_COLON} {L_POST}{L_COLON} {attach.TOPIC_TITLE}
{attach.POST_TIME}{attach.SIZE}{attach.DOWNLOAD_COUNT}
+ +
+

{L_USER_NO_ATTACHMENTS}

+
+ +
+ {L_SORT_BY}{L_COLON} + +
+
+ + +
+ +

{L_MARK_ALL}{L_UNMARK_ALL}

+ {S_FORM_TOKEN} +
+
+ + + +
+ » {L_SET_USERS_PERMISSIONS}
+ » {L_SET_USERS_FORUM_PERMISSIONS} +
+ +
+ +
+ {L_SELECT_FORUM}{L_COLON} + + {S_FORM_TOKEN} +
+
+ +
 
+ + + + + + + + + + diff --git a/adm/style/acp_users_avatar.html b/adm/style/acp_users_avatar.html new file mode 100644 index 0000000..8466985 --- /dev/null +++ b/adm/style/acp_users_avatar.html @@ -0,0 +1,36 @@ +
+ +
+ {L_ACP_USER_AVATAR} +

{ERROR}

+
+

{L_AVATAR_EXPLAIN}
+
{AVATAR}
+
+
+
+
+ {L_AVATAR_SELECT} +
+
+
+
+
+ +
+

{avatar_drivers.L_EXPLAIN}

+ {avatar_drivers.OUTPUT} +
+ +
+
+ +
+ + {S_FORM_TOKEN} +
+
diff --git a/adm/style/acp_users_feedback.html b/adm/style/acp_users_feedback.html new file mode 100644 index 0000000..f251724 --- /dev/null +++ b/adm/style/acp_users_feedback.html @@ -0,0 +1,75 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{L_REPORT_BY}{L_IP}{L_TIME}{L_FEEDBACK}{L_MARK}
{log.USERNAME}{log.IP}{log.DATE} + {log.ACTION} +
» [ {log.DATA} ] +
+ +
+

{L_NO_ENTRIES}

+
+ + +
+ {L_DISPLAY_LOG}{L_COLON}  {S_LIMIT_DAYS} {L_SORT_BY}{L_COLON} {S_SORT_KEY} {S_SORT_DIR} + +
+
+ + + +
+   + +

{L_MARK_ALL}{L_UNMARK_ALL}

+
+ + +

{L_ADD_FEEDBACK}

+ +

{L_ADD_FEEDBACK_EXPLAIN}

+ +
+ {L_ACP_USER_FEEDBACK} +
+
+
+
+ +
+ +
+ {S_FORM_TOKEN} +
diff --git a/adm/style/acp_users_overview.html b/adm/style/acp_users_overview.html new file mode 100644 index 0000000..506101c --- /dev/null +++ b/adm/style/acp_users_overview.html @@ -0,0 +1,169 @@ +
+ +
+ {L_ACP_USER_OVERVIEW} +
+

{L_NAME_CHARS_EXPLAIN}
+
+
[ {L_USE_PERMISSIONS} ]
+
+ +
+
+
{USER_INACTIVE_REASON}
+
+ +
+
+
{USER_REGISTERED}
+
+ +
+
+
{REGISTERED_IP}
+
[ {L_WHOIS} ]
+
+ +
+
+
{USER_LASTACTIVE}
+
+
+
+
+ + + {USER_POSTS} + + {USER_POSTS} + + + + ({L_POSTS_IN_QUEUE}) + + ({L_POSTS_IN_QUEUE}) + +
+
+
+
+
{USER_WARNINGS}
+
+
+

{L_FOUNDER_EXPLAIN}
+
+
+
+
+
+
+
+
+

{L_CHANGE_PASSWORD_EXPLAIN}
+
+
+
+

{L_CONFIRM_PASSWORD_EXPLAIN}
+
+
+ + +

+ + + {S_FORM_TOKEN} +

+ +
+
+ + + + + +
+ +
+ {L_USER_TOOLS} +
+
+
+
+ + +

+ + {S_FORM_TOKEN} +

+ +
+ +
+ + +
+
+ {L_DELETE_USER} +
+

{L_DELETE_USER_EXPLAIN}
+
+ + + + {L_USER_NO_POSTS_TO_DELETE} + +
+
+

+ + + {S_FORM_TOKEN} +

+
+
+ + diff --git a/adm/style/acp_users_prefs.html b/adm/style/acp_users_prefs.html new file mode 100644 index 0000000..484c5b3 --- /dev/null +++ b/adm/style/acp_users_prefs.html @@ -0,0 +1,151 @@ + + +
+ +
+ {L_UCP_PREFS_PERSONAL} + +
+
+
+
+
+
+
+
+
+
+
+

{L_ALLOW_PM_EXPLAIN}
+
+
+
+
+
+
+
+
+
+

{L_NOTIFY_METHOD_EXPLAIN}
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+

{L_BOARD_DATE_FORMAT_EXPLAIN}
+
+
style="display:none;">
+
+ +
+ +
+ {L_UCP_PREFS_POST} + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+ +
+ {L_UCP_PREFS_VIEW} + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{S_TOPIC_SORT_DAYS}
+
+
+
+
{S_TOPIC_SORT_KEY}
+
+
+
+
{S_TOPIC_SORT_DIR}
+
+
+
+
{S_POST_SORT_DAYS}
+
+
+
+
{S_POST_SORT_KEY}
+
+
+
+
{S_POST_SORT_DIR}
+
+ +
+ +
+ + {S_FORM_TOKEN} +
+ +
diff --git a/adm/style/acp_users_profile.html b/adm/style/acp_users_profile.html new file mode 100644 index 0000000..9296638 --- /dev/null +++ b/adm/style/acp_users_profile.html @@ -0,0 +1,36 @@ +
+ +
+ {L_USER_PROFILE} + +
+
+
+
+
+

{L_BIRTHDAY_EXPLAIN}
+
{L_DAY}{L_COLON} {L_MONTH}{L_COLON} {L_YEAR}{L_COLON}
+
+ +
+ + +
+ {L_USER_CUSTOM_PROFILE_FIELDS} + +
+
for="{profile_fields.FIELD_ID}">{profile_fields.LANG_NAME}{L_COLON}
{profile_fields.LANG_EXPLAIN}
+
{profile_fields.FIELD}
+ +
{profile_fields.ERROR}
+ +
+ +
+ + +
+ + {S_FORM_TOKEN} +
+
diff --git a/adm/style/acp_users_signature.html b/adm/style/acp_users_signature.html new file mode 100644 index 0000000..c7ec5cc --- /dev/null +++ b/adm/style/acp_users_signature.html @@ -0,0 +1,52 @@ + + +
+ + +
+ {L_ADMIN_SIG_PREVIEW} +

{SIGNATURE_PREVIEW}

+
+ + +
+ {L_SIGNATURE} +

{L_SIGNATURE_EXPLAIN}

+ + + +
+
+
+
+
+ + + + + + + + + +
+
{L_OPTIONS}{L_COLON} {BBCODE_STATUS} :: {IMG_STATUS} :: {FLASH_STATUS} :: {URL_STATUS} :: {SMILIES_STATUS}
+
+
+ +
+   + + {S_FORM_TOKEN} +
+
diff --git a/adm/style/acp_users_warnings.html b/adm/style/acp_users_warnings.html new file mode 100644 index 0000000..6e7f521 --- /dev/null +++ b/adm/style/acp_users_warnings.html @@ -0,0 +1,36 @@ +
+ + + + + + + + + + + + + + + + + + + + + +
{L_REPORT_BY}{L_TIME}{L_FEEDBACK}{L_MARK}
{warn.USERNAME}{warn.DATE}{warn.ACTION}
+ +
+

{L_NO_WARNINGS}

+
+ + +
+   + +

{L_MARK_ALL}{L_UNMARK_ALL}

+
+ {S_FORM_TOKEN} +
diff --git a/adm/style/acp_words.html b/adm/style/acp_words.html new file mode 100644 index 0000000..6038fc6 --- /dev/null +++ b/adm/style/acp_words.html @@ -0,0 +1,77 @@ + + + + + + + « {L_BACK} + +

{L_ACP_WORDS}

+ +

{L_ACP_WORDS_EXPLAIN}

+ +
+ +
+ {L_EDIT_WORD} +
+
+
+
+
+
+
+
+ {S_HIDDEN_FIELDS} + +

+   + + {S_FORM_TOKEN} +

+
+
+ + + +

{L_ACP_WORDS}

+ +

{L_ACP_WORDS_EXPLAIN}

+ +
+ +
+ {L_ACP_WORDS} +

+ {S_HIDDEN_FIELDS} + +

+ + + + + + + + + + + + + + + + + + + + + + +
{L_WORD}{L_REPLACEMENT}{L_ACTION}
{words.WORD}{words.REPLACEMENT} {ICON_EDIT}  {ICON_DELETE} 
{L_ACP_NO_ITEMS}
+ {S_FORM_TOKEN} +
+
+ + + diff --git a/adm/style/admin.css b/adm/style/admin.css new file mode 100644 index 0000000..7cf6c22 --- /dev/null +++ b/adm/style/admin.css @@ -0,0 +1,2720 @@ +/* phpBB 3.2 Admin Style Sheet + ------------------------------------------------------------------------ + Original author: subBlue ( http://www.subblue.com/ ) + Copyright (c) phpBB Limited + + For full copyright and license information, please see + the docs/CREDITS.txt file. + ------------------------------------------------------------------------ +*/ + +/* General markup styles +---------------------------------------- */ +* { + /* Reset browsers default margin, padding and font sizes */ + margin: 0; + padding: 0; + font-size: 100%; +} + +abbr { + text-decoration: none; +} + +body, div, p, th, td, li, dd { + font-size: x-small; + voice-family: "\"}\""; + voice-family: inherit; + font-size: small; +} + +html>body, html>div, html>p, html>th, html>td, html>li, html>dd { + font-size: small; +} + +html { + color: #536482; + background: #DBD7D1; + /* Always show a scrollbar for short pages - stops the jump when the scrollbar appears. non-ie browsers */ + height: 100%; + margin-bottom: 1px; + word-wrap: break-word; +} + +body { + /* Text-Sizing with ems: http://www.clagnut.com/blog/348/ */ + font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; + color: #536482; + background: #DBD7D1; + font-size: 62.5%; /* This sets the default font size to be equivalent to 10px */ + margin: 10px 15px; +} + +code, samp { + font-size: 1.2em; +} + +img { + border: 0; +} + +h1 { + font-family: "Trebuchet MS", Helvetica, sans-serif; + font-size: 1.70em; + font-weight: normal; + color: #333333; +} + +h2, caption { + font-family: "Trebuchet MS", Helvetica, sans-serif; + font-size: 1.40em; + font-weight: normal; + color: #115098; + text-align: left; + margin-top: 25px; +} + +.rtl h2, .rtl caption { + text-align: right; +} + +h3, h4 { + font-family: "Trebuchet MS", Helvetica, sans-serif; + font-size: 1.20em; + text-decoration: none; + line-height: 1.20em; + margin-top: 25px; +} + +p { + margin-bottom: 0.7em; + line-height: 1.40em; + font-size: 0.90em; +} + +ul { + list-style: disc; + margin: 0 0 1em 2em; +} + +.rtl ul { + margin: 0 2em 1em 0; +} + +hr { + border: 0 none; + border-top: 1px dashed #999999; + margin-bottom: 5px; + padding-bottom: 5px; + height: 1px; +} + +.centered-text { + text-align: center; +} + +.search-box { + float: left; +} + +.rtl .search-box { + float: right; +} + +.small { + font-size: 0.85em; +} + +.hidden { + display: none; +} + +@media only screen and (max-width: 800px), only screen and (max-device-width: 800px) +{ + body { + margin: 5px 5px 0; + } +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + html, body { + height: auto; + margin: 0; + padding: 0; + } +} + + +/* General links */ +a:link, a:visited { + color: #105289; + text-decoration: none; +} + +a:hover { + color: #BC2A4D; + text-decoration: underline; +} + +a:active { + color: #368AD2; + text-decoration: none; +} + +.install-body p a { + font-weight: bold; +} + +a#maincontent, a#acl, a#assigned_to { + display: block; +} + +/* List items */ +ul, ol { + list-style-position: inside; + margin-left: 1em; +} + +li { + display: list-item; + list-style-type: inherit; +} + + +/* Main blocks +---------------------------------------- */ +#wrap { + padding: 0 0 15px 0; + min-width: 615px; +} + +#page-header { + text-align: right; + background: url("../images/phpbb_logo.png") top left no-repeat; + height: 54px; + font-size: 0.85em; + margin-bottom: 10px; +} + +.rtl #page-header { + text-align: left; + background: url("../images/phpbb_logo.png") top right no-repeat; +} + +#page-header h1 { + color: #767676; + font-family: "Trebuchet MS",Helvetica,sans-serif; + font-size: 1.70em; + padding-top: 10px; +} + +#page-header p { + font-size: 1.00em; +} + +#page-header p#skip { + display: none; +} + +#page-body { + min-width: 650px; +} + +.copyright { + font-size: 0.75em; + text-align: center; +} + +#content { + padding: 30px 10px 10px; + position: relative; +} + +#content h1 { + color: #115098; + line-height: 1.2em; + margin-bottom: 0; +} + +#main { + float: right; + width: 100%; + margin: 0 0 0 -210px; +} + +.rtl #main { + float: left; + margin: 0 -210px 0 0; +} + +.main { + margin-left: 210px; +} + +.rtl .main { + margin-left: 0; + margin-right: 210px; +} + +#page-body.simple-page-body { + padding: 0; + padding-right: 10px; + min-width: 0; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + #wrap, #page-body, #page-body.simple-page-body { + padding: 0; + min-width: 300px; + } + + #page-header { + margin: 5px; + padding-left: 160px; + height: auto; + min-height: 54px; + overflow: hidden; + } + + .rtl #page-header { + padding-right: 160px; + padding-left: 0; + } + + #page-header h1 { + font-size: 1.2em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + #page-header fieldset { + margin-top: 5px; + } + + #main, .rtl #main, .main, .rtl .main { + float: none; + width: auto; + margin: 0; + } + + #content { + background: #F3F3F3 url("../images/innerbox_bg.gif") repeat-x top; + padding: 5px; + } + + #page-footer { + padding: 0 5px 5px; + } +} + +@media only screen and (max-width: 400px), only screen and (max-device-width: 400px) +{ + #page-header { + background-size: 76px 26.5px; + padding-left: 80px; + min-height: 30px; + } + + .rtl #page-header { + padding-right: 80px; + } + + #page-header h1 { + padding-top: 0; + font-size: 1.1em; + } +} + + +/* Tabbed menu +----------------------------------------*/ +#tabs { + font-family: Arial, Helvetica, sans-serif; + line-height: normal; + margin: 0 7px; + position: relative; + z-index: 2; +} + +#tabs > ul { + list-style: none; + margin: 0; + padding: 0; +} + +#tabs .tab { + display: inline-block; + float: left; + font-size: 0.85em; + font-weight: bold; + line-height: 14px; +} + +.rtl #tabs .tab { + float: right; +} + +#tabs .tab > a { + background: #D4D6DA; + background: -moz-linear-gradient(top, #CACBCF 0%, #D4D6DA 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #CACBCF), color-stop(100%, #D4D6DA)); + background: -webkit-linear-gradient(top, #CACBCF 0%, #D4D6DA 100%); + background: -o-linear-gradient(top, #CACBCF 0%, #D4D6DA 100%); + background: -ms-linear-gradient(top, #CACBCF 0%, #D4D6DA 100%); + background: linear-gradient(to bottom, #CACBCF 0%, #D4D6DA 100%); + border: 1px solid #BBB; + border-bottom-width: 0; + border-radius: 5px 5px 0 0; + color: #767676; + display: block; + font-weight: bold; + margin: 1px 1px 2px 0; + padding: 6px 9px 4px; + position: relative; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + cursor: pointer; +} + +#tabs .tab > a:hover { + background: #F1F1EE; + border-color: #C0BFBB; + color: #BC2A4D; +} + +#tabs .activetab > a, +#tabs .activetab > a:hover { + background: #DCDEE2; + background: -moz-linear-gradient(top, #F2F2F2 0%, #DCDEE2 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F2F2F2), color-stop(100%, #DCDEE2)); + background: -webkit-linear-gradient(top, #F2F2F2 0%, #DCDEE2 100%); + background: -o-linear-gradient(top, #F2F2F2 0%, #DCDEE2 100%); + background: -ms-linear-gradient(top, #F2F2F2 0%, #DCDEE2 100%); + background: linear-gradient(to bottom, #F2F2F2 0%, #DCDEE2 100%); + border-color: #999; + border-bottom: 2px solid #DCDEE2; + box-shadow: 0 1px 1px #FFF inset; + color: #23649F; + margin: 0 1px 0 0; + padding: 7px 10px 4px; +} + +#tabs .activetab > a:hover { + color: #115098; +} + +/* Responsive tabs +----------------------------------------*/ +.responsive-tab { + position: relative; +} + +.responsive-tab > a.responsive-tab-link { + display: block; + font-size: 16px; + position: relative; + width: 16px; + line-height: 14px; + text-decoration: none; + padding-left: 9px !important; + padding-right: 9px !important; +} + +.responsive-tab .responsive-tab-link:before { + content: ''; + position: absolute; + left: 10px; + top: 7px; + height: .125em; + width: 14px; + border-bottom: 0.125em solid #767676; + border-top: 0.375em double #767676; +} + +.responsive-tab .responsive-tab-link:hover:before { + border-color: #BC2A4D; +} + +.responsive-tab.activetab .responsive-tab-link:before { + border-color: #23649F; +} + +.responsive-tab.activetab .responsive-tab-link:hover:before { + border-color: #115098; +} + +#tabs .dropdown, #minitabs .dropdown { + top: 20px; + margin-right: -2px; + font-weight: normal; +} + +#tabs .dropdown-right .dropdown { + margin-left: -2px; +} + +#tabs .dropdown-contents { + list-style: none; + margin: 0; +} + +#tabs .dropdown li { + border-bottom: 1px dotted #DCDCDC; +} + +#tabs .dropdown li:last-child { + border-bottom: none; +} + +#tabs .dropdown a { + display: block; + padding: 4px 8px; + text-align: right; +} + +/* Main Panel +---------------------------------------- */ +#acp { + position: relative; + top: -2px; + margin: 0 0 2px; + padding: 3px 1px; + min-width: 550px; + background: #F3F3F3 url("../images/innerbox_bg.gif") repeat-x top; + border: 1px #999999 solid; + border-radius: 5px; + box-shadow: #FFF 0 0 0 1px inset; +} + +#acp:first-child { + top: 0; +} + +.panel { + background: #F3F3F3 url("../images/innerbox_bg.gif") repeat-x top; + padding: 5px 0; + border-radius: 5px; + overflow: hidden; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + #acp { + min-width: 0; + min-height: 0; + border-radius: 0; + border-width: 1px 0; + background: #fff; + padding: 1px 0; + box-shadow: none; + } +} + +/* Sub-navigation Menu +---------------------------------------- */ + +/* Menu */ +#menu { + float: left; + width: 200px; + font-size: 1.00em; + padding: 0; + border-right: 1px solid #CCCFD3; + position: relative; +} + +.rtl #menu { + float: right; + border: none; + border-left: 1px solid #CCCFD3; +} + +#menu p { + font-size: 0.85em; +} + +#menu ul { + list-style: none; + margin: 0; + padding: 0; + word-wrap: normal; +} + +/* Default list state */ +#menu li, #menu .header { + padding: 0; + margin: 0; + font-size: 0.85em; + font-weight: bold; + display: block; +} + +/* Link styles for the sub-section links */ +#menu li span { + display: block; + padding: 3px 3px 3px 8px; + margin: 1px 0; + text-decoration: none; + font-weight: normal; + color: #138ECB; +} + +.rtl #menu li span { + padding: 3px 8px 3px 3px; +} + +#menu li a:hover, #menu li a:hover span { + text-decoration: none; + background-color: #FFFFFF; + color: #BC2A4D; +} + +#menu li a:active, #menu li a:active span { + color: #F632A0; +} + +#menu li#activemenu a span { + text-decoration: none; + font-weight: bold; + color: #1180B7; + background: transparent url("../images/arrow_right.gif") 0% 50% no-repeat; +} + +.rtl #menu li#activemenu a span { + background: transparent url("../images/arrow_left.gif") 100% 50% no-repeat; +} + +#menu li#activemenu a:hover span, #menu li#activemenu span { + text-decoration: none; + font-weight: bold; + color: #BC2A4D; + background: #FFFFFF url("../images/arrow_right.gif") 1% 50% no-repeat; +} + +.rtl #menu li#activemenu a:hover span, .rtl #menu li#activemenu span { + background: #FFFFFF url("../images/arrow_left.gif") 99% 50% no-repeat; +} + +#menu li a:active, #menu li a:active span, #menu li#activemenu a:active span { + color: #F632A0; +} + +#menu li span.completed { + text-decoration: none; + padding: 3px 3px 3px 12px; + background: url("../images/arrow_down.gif") 1% 50% no-repeat; +} + +.rtl #menu li span.completed { + text-decoration: none; + padding: 3px 12px 3px 3px; + background: url("../images/arrow_down.gif") 99% 50% no-repeat; +} + +#menu .header { + font-family: Tahoma, Helvetica, sans-serif; + display: block; + font-weight: bold; + color: #115098; + border-bottom: 1px solid #327AA5; + padding: 4px 0 2px; + margin-top: 15px; + text-transform: uppercase; + font-size: 0.75em; + text-decoration: none; + cursor: inherit; + outline-style: none; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + #menu, .rtl #menu { + float: none; + width: auto; + border-width: 0; + max-width: 200px; + margin: 0 auto 10px; + } + + #menu p { + text-align: center; + } + + #menu .menu-block.active { + margin: 0 -5px; + padding: 0 5px 3px; + background: rgba(255, 255, 255, .5); + border-radius: 5px; + } + + #menu .menu-block.no-header.active { + padding-top: 3px; + } + + #menu .menu-block .header { + margin-top: 5px; + cursor: pointer; + border-bottom-width: 0; + position: relative; + text-decoration: underline; + } + + #menu .menu-block .header:focus, #menu .menu-block.active .header { + color: #D31141; + text-decoration: none; + } + + #menu .menu-block ul { + display: none; + } + + .nojs #menu .menu-block:hover ul, #menu .menu-block.active ul, #menu .menu-block.no-header ul { + display: block; + } + + #menu .menu-block li:last-child { + border-bottom: 1px solid #327AA5; + } + + #menu .menu-block:last-child li:last-child, #menu .menu-block.active li:last-child { + border-bottom-width: 0; + } + + #menu .menu-block li a span { + border-radius: 2px; + } +} + + +/* Table styles +---------------------------------------- */ + +table { + width: 100%; + border: 1px solid #CCCFD3; + background-color: #FFFFFF; + padding: 1px; +} + +th { + padding: 3px 4px; + color: #FFFFFF; + background: #70AED3 url("../images/gradient2b.gif") bottom left repeat-x; + border-top: 1px solid #6DACD2; + border-bottom: 1px solid #327AA5; + text-align: left; + font-size: 0.75em; + text-transform: uppercase; +} + +td { + text-align: left; + font-size: 0.85em; + padding: 4px; + line-height: 1.20em; +} + +.rtl th, .rtl td { + text-align: right; +} + +.table1 { + border-collapse: separate; + border-spacing: 1px; + clear: both; +} + +dt#color_palette_placeholder table { + margin-right: 5px; + width: 80px; +} + +#color_palette_placeholder td { + padding: 0; +} + +table.type2 { + border: none; + background: none; + padding: 0; +} + +table.type2 th { + background: none; + border-top: none; + text-align: center; + color: #115098; + padding: 2px 0; +} + +table.type2 td { + padding: 0; + font-size: 1em; +} + +table.type2 td.name { + padding: 2px; + vertical-align: middle; +} + +table.type3 { + float: right; + width: 300px; + border: none; + background-color: transparent; + padding: 0; +} + +.rtl table.type3 { + float: left; +} + +table.type3 thead th { + background-color: transparent; + border-top: none; + text-align: center; + color: #115098; + padding: 0 3px; + font-size: 0.85em; + font-weight: normal; + text-transform: none; +} + +table.type3 tbody th { + border-top: none; + text-align: left; + text-transform: none; + padding: 0; + border: none; + font-size: 0.90em; + font-weight: normal; + width: 100%; +} + +.rtl table.type3 tbody th { + text-align: right; +} + +table.type3 td { + text-align: center; + padding: 1px; +} + +th.name { + text-align: left; + width: auto; +} + +.rtl th.name { + text-align: right; +} + +td.name { + text-align: left; + font-weight: bold; +} + +.rtl td.name { + text-align: right; +} + +.entry { + text-align: left; + font-weight: normal; +} + +.rtl .entry { + text-align: right; +} + +.row1 { + background-color: #F9F9F9; +} + +table.zebra-table tbody tr:nth-child(odd) { + background-color: #F9F9F9; +} + +.row2 { + word-break: break-all; + background-color: #DCEBFE; +} + +table.zebra-table tbody tr:nth-child(even) { + background-color: #DCEBFE; +} + +.row3 { background-color: #DBDFE2; } +.row4 { background-color: #E4E8EB; } +.col1 { background-color: #DCEBFE; } +.col2 { background-color: #F9F9F9; } + +/* 4 row background colours for trees */ +.row1a { background-color: #F9F9F9; } +.row1b { background-color: #F6F6F6; } +.row2a { background-color: #E7EEF4; } +.row2b { background-color: #E3EBF2; } + +tr.row-highlight:hover td { background-color: #DBDFE2; } + +.spacer { + background-color: #DBDFE2; + height: 1px; + line-height: 1px; +} + +/* Deactivated row */ +.row-inactive { + color: #999; +} +.row-inactive a, .row-inactive strong { + color: #888; +} +.row-inactive a:hover { + color: #BC2A4D; +} + +/* Specific tables */ +table.forums td.folder { + width: 27px; + text-align: center; +} + +table td.actions { + vertical-align: middle; + width: 100px; + text-align: center; + white-space: nowrap; +} + +table tr:first-child td.actions .up, table tr:last-child td.actions .down { + display: none; +} + +table tr:first-child td.actions .up-disabled, table tr:last-child td.actions .down-disabled { + display: inline !important; +} + +table.styles td.users, table td.mark { + text-align: center; +} + +table.fixed-width-table { + table-layout: fixed; + word-break: break-word; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + table.responsive, table.responsive tbody, table.responsive tr, table.responsive td { + display: block; + } + + table.responsive thead, table.responsive th, table.responsive colgroup { + display: none; + } + + table.responsive.show-header thead, table.responsive.show-header th:first-child, table.responsive caption { + display: block; + width: auto !important; + text-align: left !important; + margin: 0; + } + + table.responsive { + background: transparent none; + border-width: 0; + padding: 0; + } + + table.responsive caption { + padding: 3px 4px; + color: #FFFFFF; + background: #70AED3 url("../images/gradient2b.gif") bottom left repeat-x; + border-top: 1px solid #6DACD2; + border-bottom: 1px solid #327AA5; + text-align: left; + font-size: 0.75em; + font-weight: bold; + text-transform: uppercase; + } + + table.responsive.show-header th:first-child span.rank-img, table.responsive.no-caption caption, table.responsive.no-header thead { + display: none; + } + + table.responsive tr { + margin: 2px 0; + border: 1px solid #CCCFD3; + background-color: #FFFFFF; + padding: 1px 1px 0; + overflow: hidden; + } + + table.responsive tr.row1 td { background-color: #F9F9F9; } + table.responsive tr.row2 td { background-color: #DCEBFE; } + table.responsive tr.row3 td { background-color: #DBDFE2; } + table.responsive tr.row4 td { background-color: #E4E8EB; } + table.responsive tr.col1 td { background-color: #DCEBFE; } + table.responsive tr.col2 td { background-color: #F9F9F9; } + table.responsive tr.row1a td { background-color: #F9F9F9; } + table.responsive tr.row1b td { background-color: #F6F6F6; } + table.responsive tr.row2a td { background-color: #E7EEF4; } + table.responsive tr.row2b td { background-color: #E3EBF2; } + + table.responsive td { + width: auto !important; + text-align: left !important; + padding: 4px; + margin-bottom: 1px; + } + + .rtl table.responsive td { + text-align: right !important; + } + + table.responsive td.empty { + display: none !important; + } + + table.responsive td > dfn { + display: inline-block !important; + } + + table.responsive td > dfn:after { + content: ':'; + padding-right: 5px; + } + + table.responsive.two-columns td { + width: 50% !important; + float: left; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + .rtl table.responsive.two-columns td { + float: right; + } + + table.responsive.two-columns td:nth-child(2n+1) { + clear: left; + } + + table.responsive span.rank-img { + float: none; + padding-right: 5px; + } + + table.responsive#memberlist td:first-child input[type="checkbox"] { + float: right; + } + + /* Specific tables */ + table.responsive.forums td.folder { + float: left; + width: 27px; + background: transparent; + } + .rtl table.responsive.forums td.folder { + float: right; + } + + table.responsive.forums td.forum-desc { + margin-left: 35px; + min-height: 27px; + background: transparent; + } + + .rtl table.responsive.forums td.forum-desc { + margin-left: 0; + margin-right: 35px; + } + + table.responsive td.actions { + clear: both; + text-align: right !important; + } + + .rtl table.responsive td.actions { + text-align: left !important; + } + + table.responsive.styles tr.responsive-style-row td:first-child { + padding-left: 4px !important; + padding-right: 4px !important; + } + + table.responsive.styles td:first-child > dfn, table.responsive td.actions > dfn { + display: none !important; + } + + .horizontal-palette td:nth-child(2n), .vertical-palette tr:nth-child(2n) { + display: none; + } + + .colour-palette a { + display: inline-block !important; + } +} + +/* General form styles +----------------------------------------*/ +fieldset { + margin: 15px 0; + padding: 10px; + border-top: 1px solid #D7D7D7; + border-right: 1px solid #CCCCCC; + border-bottom: 1px solid #CCCCCC; + border-left: 1px solid #D7D7D7; + background-color: #FFFFFF; + position: relative; + border-radius: 3px; +} + +fieldset h2 { + margin-top: 0; +} + +.rtl fieldset { + border-top: 1px solid #D7D7D7; + border-right: 1px solid #D7D7D7; + border-bottom: 1px solid #CCCCCC; + border-left: 1px solid #CCCCCC; +} + +fieldset p { + font-size: 0.85em; +} + +legend { + padding: 1px 0; + font-family: Tahoma,arial,Verdana,Sans-serif; + font-size: .9em; + font-weight: bold; + color: #115098; + margin-top: -.4em; + position: relative; + text-transform: none; + line-height: 1.2em; + top: -.2em; + vertical-align: middle; +} + +input, textarea { + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 0.90em; + font-weight: normal; + vertical-align: middle; + padding: 2px; + color: #111111; + border-left: 1px solid #AFAEAA; + border-top: 1px solid #AFAEAA; + border-right: 1px solid #D5D5C8; + border-bottom: 1px solid #D5D5C8; + background-color: #E3DFD8; +} + +.rtl input, .rtl textarea { + border-left: 1px solid #D5D5C8; + border-top: 1px solid #AFAEAA; + border-right: 1px solid #AFAEAA; + border-bottom: 1px solid #D5D5C8; +} + +input:hover, textarea:hover { + border-left: 1px solid #AFAEAA; + border-top: 1px solid #AFAEAA; + border-right: 1px solid #AFAEAA; + border-bottom: 1px solid #AFAEAA; + background-color: #E9E9E2; +} + +input.langvalue, textarea.langvalue { + width: 90%; +} + +input[type="number"] { + width: 60px; + -moz-padding-end: 0; +} + +optgroup, select { + background-color: #FAFAFA; + border: 1px solid #666666; + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 0.85em; + font-weight: normal; + font-style: normal; + cursor: pointer; + padding: 1px; + vertical-align: middle; + width: auto; + color: #000; +} + +select:focus { + outline-style: none; +} + +optgroup { + font-size: 1.00em; + font-weight: bold; +} + +optgroup.disabled-options { + display: none; + background-color: gray; +} + +option { + padding: 0 1em 0 0; + color: #000; +} + +option.disabled-option { + color: graytext; +} + +.rtl option { + padding: 0 0 0 1em; +} + +.sep { + font-weight: bold; +} + +.username-coloured { + font-weight: bold; +} + +textarea { + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 0.85em; + width: 60%; + padding: 2px; +} + +label { + cursor: pointer; + font-size: 0.85em; + padding: 0 5px 0 0; +} + +.rtl label { + padding: 0 0 0 5px; +} + +label input { + font-size: 1.00em; + vertical-align: middle; +} + +label img { + vertical-align: middle; +} + +fieldset.quick, p.quick { + margin: 0 0 5px; + padding: 5px 0 0; + border: none; + background-color: transparent; + text-align: right; +} + +.rtl fieldset.quick, .rtl p.quick { + text-align: left; +} + +fieldset.quick legend { + display: none; +} + +fieldset.tabulated { + background: none; + margin: 0; + margin-top: 5px; + padding: 0; + border: 0; +} + +fieldset.tabulated legend { + display: none; +} + +fieldset.nobg { + margin: 15px 0 0 0; + padding: 0; + border: none; + background-color: transparent; +} + +fieldset.display-options { + margin: 15px 0 2px 0; + padding: 0 0 4px 0; + border: none; + background-color: transparent; + text-align: center; + font-size: 0.85em; +} + +fieldset.display-options select, fieldset.display-options input, fieldset.display-options label { + font-size: 1.00em; + vertical-align: middle; +} + +select option.disabled { + background-color: #bbb; + color: #fff; +} + +/* Special case inputs */ +select#board_timezone, +select#full_folder_action { + width: 95%; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + fieldset { + padding: 5px; + } + + fieldset.quick, p.quick { + float: none !important; + text-align: center; + } + + fieldset.display-options { + clear: both; + } +} + +/* Definition list layout for forms + Other general def. list properties defined in prosilver_main.css +---------------------------------------- */ +dl { + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 1.00em; +} + +dt { + float: left; + width: auto; +} + +.rtl dt { + float: right; +} + +dd { color: #666666;} +dd + dd { padding-top: 5px;} +dt span { padding: 0 5px 0 0;} +.rtl dt span { padding: 0 0 0 5px;} + +dt .explain { font-style: italic;} + +dt label { + font-size: 1.00em; + text-align: left; + font-weight: bold; + color: #4A5A73; +} + +.rtl dt label { + text-align: right; +} + +dd label { + font-size: 1.00em; + white-space: nowrap; + margin: 0 10px 0 0; + color: #4A5A73; +} + +.rtl dd label { + margin: 0 0 0 10px; +} + +html>body dd label input { vertical-align: text-bottom;} /* Tweak for Moz to align checkboxes/radio buttons nicely */ + +dd input { + font-size: 1.00em; + max-width: 100%; + margin: 2px 0; +} + +dd select { + font-size: 100%; + font-size: 1em; + width: auto; + max-width: 100%; + margin: 2px 0; +} + +dd textarea { + font-size: 0.90em; + width: 90%; +} + +fieldset dl { + margin-bottom: 10px; + font-size: 0.85em; +} + +fieldset dt { + width: 45%; + text-align: left; + border: none; + border-right: 1px solid #CCCCCC; + padding-top: 3px; +} + +.rtl fieldset dt { + text-align: right; + border: none; + border-left: 1px solid #CCCCCC; +} + +fieldset #color_palette_placeholder { + padding-top: 0; +} + +fieldset dd { + margin: 0 0 0 45%; + padding: 0 0 0 5px; + border: none; + border-left: 1px solid #CCCCCC; + vertical-align: top; + font-size: 1.00em; +} + +.rtl fieldset dd { + margin: 0 45% 0 0; + padding: 0 5px 0 0; + border: none; + border-right: 1px solid #CCCCCC; +} + +dd.full, .rtl dd.full { + margin: 0; + border: 0; + padding: 0; + padding-top: 3px; + text-align: center; + width: 95%; +} + +/* Hover highlights for form rows */ +fieldset dl:hover dt, fieldset dl:hover dd { + border-color: #666666; +} + +fieldset dl:hover dt label { + color: #000000; +} + +fieldset dl dd label:hover { + color: #BC2A4D; +} + +input:focus, textarea:focus { + border: 1px solid #BC2A4D; + background-color: #E9E9E2; + color: #BC2A4D; + outline-style: none; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + fieldset dl { + margin-bottom: 5px; + padding-bottom: 5px; + border-bottom: 1px solid #e8e8e8; + } + + fieldset > dl:last-child, fieldset > form:last-child > dl:last-child { + border-bottom-width: 0; + margin-bottom: 0; + } + + fieldset dt, .rtl fieldset dt, fieldset dd, .rtl fieldset dd { + border-width: 0; + margin-left: 0; + margin-right: 0; + float: none; + width: auto; + } + + fieldset .responsive-columns dt { + float: left; + } + + .ltr fieldset dd { + padding-left: 20px; + } + + .rtl fieldset dd { + padding-right: 20px; + } + + select, dd select, dd input { + max-width: 300px; + } + + input[type="number"], dd input[type="number"] { + max-width: 70px; + } +} + +@media only screen and (max-width: 400px), only screen and (max-device-width: 400px) +{ + select, dd select, dd input { + max-width: 240px; + } +} + +/* Submit button fieldset or paragraph +---------------------------------------- */ +fieldset.submit-buttons { + text-align: center; + border: none; + background-color: transparent; + margin: 0; + padding: 4px; + margin-top: -1px; +} + +p.submit-buttons { + text-align: center; + margin: 0; + padding: 4px; + margin-top: 10px; +} + +fieldset.submit-buttons input, p.submit-buttons input { + padding: 3px 2px; +} + +fieldset.submit-buttons legend { + display: none; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + p.submit-buttons { + margin-top: 0; + } +} + +/* Input field styles +---------------------------------------- */ + +input.radio, input.checkbox, input.permissions-checkbox { + width: auto !important; + background-color: transparent; + border: none; + cursor: pointer; +} + +input.full, +textarea.full { + width: 99%; +} + +input.medium { width: 50%;} +input.narrow { width: 25%;} +input.tiny { width: 10%;} +input.autowidth { width: auto !important;} +.box2 .inputbox { background-color: #E9E9E9;} + +/* Form button styles +---------------------------------------- */ +a.button1, input.button1, +a.button2, input.button2 { + width: auto !important; + padding: 1px 3px 0 3px; + font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; + color: #000; + font-size: 0.85em; + background: #EFEFEF url("../images/bg_button.gif") repeat-x top; + cursor: pointer; +} + +a.button1, input.button1 { + font-weight: bold; + border: 1px solid #666666; +} + +/* Alternative button */ +a.button2, input.button2 { + border: 1px solid #666666; +} + +/* button in the style of the form buttons */ +a.button1, a.button1:link, a.button1:visited, a.button1:active, +a.button2, a.button2:link, a.button2:visited, a.button2:active { + text-decoration: none; + color: #000000; + padding: 4px 8px; +} + +/* Hover states */ +a.button1:hover, input.button1:hover, +a.button2:hover, input.button2:hover { + border: 1px solid #BC2A4D; + background: #EFEFEF url("../images/bg_button.gif") repeat bottom; + color: #BC2A4D; +} + +input.disabled { + font-weight: normal; + color: #666666; +} + +/* Focus states */ +input.button1:focus, input.button2:focus { + outline-style: none; +} + +/* jQuery popups +---------------------------------------- */ +.phpbb_alert { + background-color: #FFFFFF; + border: 1px solid #999999; + position: fixed; + display: none; + top: 150px; + left: 0; + right: 0; + width: 620px; + margin: 0 auto; + z-index: 50; + padding: 25px; + padding: 0 25px 20px 25px; +} + +.phpbb_alert .alert_close { + display: block; + float: right; + width: 16px; + height: 16px; + overflow: hidden; + text-decoration: none !important; + background: transparent url("../images/alert_close.png") 0 0 no-repeat; + margin-top: -7px; + margin-right: -31px; +} +.phpbb_alert .alert_close:hover { + background-position: 0 -16px; +} + + +.phpbb_alert p { + margin: 8px 0; + padding-bottom: 8px; +} + +.phpbb_alert label { + display: block; + margin: 8px 0; + padding-bottom: 8px; +} + +.phpbb_alert div.alert_text > p, +.phpbb_alert div.alert_text > label, +.phpbb_alert div.alert_text > select, +.phpbb_alert div.alert_text > textarea, +.phpbb_alert div.alert_text > input { + font-size: 0.9em; +} + +#darkenwrapper { + display: none; + position: relative; + z-index: 44; +} + +#darken { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: #000000; + opacity: 0.5; + z-index: 45; +} + +@media only screen and (max-height: 500px), only screen and (max-device-width: 500px) +{ + .phpbb_alert { + top: 25px; + } +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + .phpbb_alert { + width: auto; + margin: 0 25px; + } +} + +#loading_indicator { + background: #000000 url("../images/loading.gif") center center no-repeat; + border-radius: 5px; + display: none; + opacity: 0.8; + margin-top: -50px; + margin-left: -50px; + height: 50px; + width: 50px; + position: fixed; + left: 50%; + top: 50%; + z-index: 51; +} + +/* Pagination +---------------------------------------- */ +.pagination { + font-size: .85em; + height: 1%; /* IE tweak (holly hack) */ + width: auto; + text-align: right; + margin: 5px 0; +} + +.top-pagination { + float: right; + margin: 15px 0 5px 0; +} + +.rtl .pagination { + text-align: left; + float: left; +} + +li.pagination { + margin-top: 0; +} + +.pagination img { + vertical-align: middle; +} + +.pagination ul { + display: inline-block; + *display: inline; /* IE7 inline-block hack */ + *zoom: 1; + margin-left: 0; + margin-bottom: 0; +} + +li.pagination ul { + margin-top: -2px; + vertical-align: middle; +} + +.pagination ul li, dl .pagination ul li, dl.icon .pagination ul li { + display: inline; + padding: 0; + font-size: 100%; + line-height: normal; +} + +.pagination li a, .pagnation li span, li .pagination li a, li .pagnation li span, .pagination li.active span, .pagination li.ellipsis span { + font-weight: normal; + text-decoration: none; + padding: 0 2px; + border: 1px solid transparent; + font-size: 0.9em; + line-height: 1.5em; +} + +.pagination li a, .pagination li a:link, .pagination li a:visited { + color: #5C758C; + background-color: #ECEDEE; + border-color: #B4BAC0; +} + +.pagination li.ellipsis span { + background-color: transparent; + color: #000000; +} + +.pagination li.active span { + color: #FFFFFF; + background-color: #4692BF; + border-color: #4692BF; +} + +.pagination li a:hover, .pagination .active a:hover { + color: #FFFFFF; + background-color: #368AD2; + border-color: #368AD2; +} + +.pagination li a:active, .pagination li.active a:active { + color: #5C758C; + background-color: #ECEDEE; + border-color: #B4BAC0; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + .pagination, .rtl .pagination { + float: none; + text-align: center; + margin: 5px 0; + } + + .pagination li a, .pagination li span { + display: inline-block; + min-width: 10px; + } +} + +/* Action Highlighting +---------------------------------------- */ +.successbox, .errorbox, .warningbox { + padding: 8px; + margin: 10px 0; + color: #FFFFFF; + text-align: center; + clear: both; +} + +.success { + color: #228822; +} + +.error { + color: #BC2A4D; +} + +.successbox { + background-color: #228822; +} + +.errorbox { + background-color: #BC2A4D; +} + +.warningbox { + background-color: #fca600; +} + +.successbox h3, .errorbox h3 { + color: #FFFFFF; + margin: 0 0 0.5em; + font-size: 1.10em; + font-family: "Lucida Grande",Verdana,Helvetica,Arial,sans-serif; +} + +.successbox p, .errorbox p { + color: #FFFFFF; + font-size: 0.85em; + margin-bottom: 0; +} + +.errorbox a:link, .errorbox a:active, .errorbox a:visited, +.successbox a:link, .successbox a:active, .successbox a:visited { + color: #DBD7D1; + text-decoration: underline; + font-weight: bold; +} + +.errorbox a:hover, .successbox a:hover { + color: #FFFFFF; + text-decoration: none; + font-weight: bold; +} + +#log-container { + display: none; + max-height: 300px; + padding: 8px; + margin: 10px 0; + clear: both; + overflow-y: auto; + background-color: #FFFFFF; +} + +#log-container.show_log_container { + display: block; + border: 1px solid #DBD7D1; +} + +.log { + font-size: 0.8em; +} + +.notice { + background-color: #62A5CC; +} + +.download-box { + margin: 10px 0 10px 0; +} + +/* Special cases for the error page */ +#errorpage #page-header a { + font-weight: bold; + line-height: 6em; +} + +#errorpage #content { + padding-top: 10px; +} + +#errorpage #content h1 { + color: #DF075C; +} + +#errorpage #content h2 { + margin-top: 20px; + margin-bottom: 5px; + border-bottom: 1px solid #CCCCCC; + padding-bottom: 5px; + color: #333333; +} + +/* Tooltip for permission roles */ +.tooltip { + width: 200px; + color: #000; + text-align: center; + border: 1px solid #AAA; +} + +.tooltip span.top { + background: #EFEFEF; + font-weight: bold; + padding: 2px; +} + +.tooltip span.bottom { + padding: 5px; + color: #000000; + background: #FFFFFF; +} + +/* + Format Buttons for signature editor +*/ +#format-buttons { + margin: 15px 0 2px 0; +} + +#format-buttons input, #format-buttons select { + vertical-align: middle; +} + +.row, fieldset dl { + overflow: hidden; +} + +/* Syntax Highlighting +---------------------------------------- */ +.sourcenum { + color: gray; + font-family: Monaco, 'Courier New', monospace; + font-size: 1.25em; + font-weight: bold; + line-height: 1.20em; + text-align: right; + padding: 0; +} + +.rtl .sourcenum { + text-align: left; +} + +.source { + font-family: Monaco, 'Courier New', monospace; + font-size: 1.25em; + line-height: 1.20em; + padding: 0; +} + +.syntaxbg { + color: #FFFFFF; +} + +.syntaxcomment { + color: #FF8000; +} + +.syntaxdefault { + color: #0000BB; +} + +.syntaxhtml { + color: #000000; +} + +.syntaxkeyword { + color: #007700; +} + +.syntaxstring { + color: #DD0000; +} + +/* Permission interface +---------------------------------------- */ + +.column1, .column2 { + width: 48%; + float: left; +} + +.ltr .column2, .rtl .column1 { + float: right; +} + +fieldset.permissions legend { + text-transform: none; +} + +fieldset.permissions legend input{ + height: 1.1em; +} + +/* Permission sections */ +fieldset.permissions .permissions-simple { + text-align: left; + padding-top: 3px; +} + +.rtl fieldset.permissions .permissions-simple { + text-align: right; +} + +fieldset.permissions .permissions-advanced { + padding: 10px 0 0 5px; + vertical-align: top; + clear: right; +} + +.rtl fieldset.permissions .permissions-advanced { + padding: 10px 5px 0 0; + clear: left; +} + +fieldset.permissions .permissions-switch { + float: right; +} + +.rtl fieldset.permissions .permissions-switch { + float: left; +} + +fieldset.permissions .padding { +} + +.permissions-switch { + margin-top: -6px; + font-size: .9em; +} + +.permissions-switch a { + text-decoration: underline; +} + +.permissions-reset { + padding-bottom: 10px; +} + +.permissions-reset a { + font-size: .85em; +} + +/* Tabbed menu */ +.permissions-category { + line-height: normal; + margin: 0 0 -1px 7px; + min-width: 570px; + font-size: 0.85em; +} + +.rtl .permissions-category { + margin: 0 7px -1px 0; +} + +.permissions-category ul { + margin: 0; + padding: 0; + list-style: none; +} + +.permissions-category li { + display: inline; + margin: 0; + padding: 0; + font-size: 1em; + font-weight: bold; +} + +.permissions-category a { + float: left; + background: url("../images/bg_tabs_alt1.gif") no-repeat 0% -35px; + margin: 0 1px 0 0; + padding: 0 0 0 6px; + text-decoration: none; + position: relative; +} + +.rtl .permissions-category a { + float: right; +} + +.permissions-category a span.tabbg { + float: left; + display: block; + background: url("../images/bg_tabs_alt2.gif") no-repeat 100% -35px; + padding: 7px 12px 6px 6px; + color: #536482; + white-space: nowrap; +} + +.rtl .permissions-category a span.tabbg { + float: right; +} + +/* Commented Backslash Hack hides rule from IE5-Mac \*/ +.permissions-category a span.tabbg, .rtl .permissions-category a span.tabbg { float: none;} +/* End hack */ + +.permissions-category a:hover span.tabbg { + color: #DD6900; +} + +.permissions-category .activetab a { + background-position: 0 0; +} + +.permissions-category .activetab a span.tabbg { + background-position: 100% 0; + padding-bottom: 7px; + color: #333333; +} + +.permissions-category a:hover { + background-position: 0 -70px; +} + +.permissions-category a:hover span.tabbg { + background-position: 100% -70px; +} + +.permissions-category .activetab a:hover span.tabbg { + color: #333333; + background-position: 100% 0; +} + +.permissions-category .activetab a:hover { + background-position: 0 0; +} + +.permissions-category a span.colour { + border: 1px solid #536482; + display: block; + float: left; + width: 10px; + height: 10px; + margin: 0 5px 0 0; +} + +/* Most browsers will have to live with a left aligned icon in RTL mode, as (currently) only Firefox 3.0 Alpha 3 renders it correctly without destroying it +.rtl .permissions-category a span.colour { + float: right; + margin: 0 0 0 5px; +} +*/ + +.permissions-category .activetab span.colour { + border-color: #333333; +} + +.permissions-category a:hover span.colour { + border-color: #DD6900; +} + +.permissions-category .activetab a:hover span.colour { + border-color: #333333; +} + +/* Permission preset colours */ +.permissions-preset-yes span.colour, +.yes { + background-color: #86F786; +} + +.permissions-preset-custom span.colour { + background-color: #B2BBDD; +} + +.permissions-preset-never span.colour { + background-color: #DD0000; +} + +.permissions-preset-no span.colour, +.never { + background-color: #EFB0B2; +} + +/* Permission panel +---------------------------------------- */ +.permissions-panel { + float: left; + background-color: #CADCEB; + width: 100%; + border-radius: 5px; + overflow: hidden; + padding: 5px 0; +} + +.rtl .permissions-panel { + float: right; +} + +/* Permission table +---------------------------------------- */ +.permissions-panel .tablewrap { + margin: 0 10px; +} + +.permissions-panel table { + width: 100%; +} + +.permissions-panel th { + text-transform: none; +} + +.permissions-panel th.value { + text-align: center; +} + +.permissions-panel th.name { + text-align: left; + width: auto; + text-transform: none; +} + +.rtl .permissions-panel th.name { + text-align: right; +} + +.permissions-panel th.permissions-name { + border: none; + color: #536482; + font-weight: normal; +} + +.permissions-panel th.permissions-name a.trace { + display: inline; +} + +.permissions-panel th.row3 { + background-image: none; + background-color: #D1D7DC; + color: #536482; + border: none; +} + +.permissions-panel th.row4 { + background-image: none; + background-color: #E4E8EB; + color: #536482; + border: none; +} + +.permissions-panel th a:link, .permissions-panel th a:hover, .permissions-panel th a:visited { + display: block; + color: #FFFFFF; + text-decoration: underline; +} + +.permissions-panel td.permissions-yes label:hover { + background-color: #86F786; +} + +.permissions-panel td.permissions-no label:hover { + background-color: #EFB0B2; +} + +.permissions-panel td.permissions-never label:hover { + background-color: #DD0000; +} + +.permissions-panel td { + padding: 0; + text-align: center; + width: 10%; +} + +.permissions-panel td label { + display: block; + margin: 0; + padding: 0; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + .column1, .column2 { + float: none !important; + width: auto; + } + + .permissions-simple { + clear: both; + } + + .permissions-simple td, .permissions-simple dd { + width: auto !important; + margin-left: 0 !important; + margin-right: 0 !important; + } + + .permissions-simple dd { + margin-top: 5px; + } + + .permissions-panel .tablewrap { + margin: 0 5px; + } + + .permissions-category { + min-width: 0; + margin: 0 !important; + } + + .permissions-category a, .permissions-category a span.tabbg { + display: block; + float: none !important; + background: transparent none; + } + + .permissions-category a { + background: #d9e5ee; + margin: 5px 0; + padding: 0 !important; + border-radius: 3px; + text-decoration: underline; + } + + .permissions-category .activetab a { + background-color: #dd6900; + color: #fff; + } + + .permissions-category a span.tabbg { + color: inherit !important; + padding-top: 6px !important; + padding-bottom: 6px !important; + } + + .permissions-category .activetab span.colour { + border-color: #fff; + } +} + +/* Avatars gallery +---------------------------------------- */ +#gallery { + display: block; + margin: 0 -5px; + padding: 0; + overflow: hidden; +} + +#gallery li { + display: block; + float: left; + border: 1px solid #ccc; + border-radius: 2px; + background: #fff; + padding: 5px; + margin: 5px; +} + +#gallery li:hover { + background-color: #eee; +} + +#gallery li label { + display: block; + text-align: center; + padding: 0; +} + +/* Dropdown menu +----------------------------------------*/ +.dropdown { + position: absolute; + left: 0; + top: 22px; + z-index: 2; + border: 1px solid transparent; + border-radius: 5px; + padding: 9px 0 0; +} + +.dropdown-up .dropdown { + top: auto; + bottom: 18px; + padding: 0 0 9px; +} + +.dropdown-left .dropdown { + left: auto; + right: 0; +} + +.dropdown .pointer, .dropdown .pointer-inner { + position: absolute; + width: 0; + height: 0; + border-top-width: 0; + border-bottom: 10px solid transparent; + border-left: 10px dashed transparent; + border-right: 10px dashed transparent; + -webkit-transform: rotate(360deg); /* better anti-aliasing in webkit */ + display: block; +} + +.dropdown-up .pointer, .dropdown-up .pointer-inner { + border-bottom-width: 0; + border-top: 10px solid transparent; +} + +.dropdown .pointer { + right: auto; + left: 10px; + top: 0; + z-index: 3; +} + +.dropdown-up .pointer { + bottom: 0; + top: auto; +} + +.dropdown-left .dropdown .pointer { + left: auto; + right: 10px; +} + +.dropdown .pointer-inner { + top: auto; + bottom: -11px; + left: -10px; +} + +.dropdown-up .pointer-inner { + bottom: auto; + top: -11px; +} + +.dropdown .pointer { + border-color: #B9B9B9 transparent; +} + +.dropdown .pointer-inner { + border-color: #FFF transparent; +} + +.dropdown .dropdown-contents { + z-index: 2; + overflow: hidden; + overflow-y: auto; + background: #fff; + border: 1px solid #b9b9b9; + border-radius: 5px; + padding: 5px; + position: relative; + min-width: 40px; + max-height: 200px; + box-shadow: 1px 3px 5px rgba(0, 0, 0, 0.2); + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.dropdown-up .dropdown-contents { + box-shadow: 1px 0 5px rgba(0, 0, 0, 0.2); +} + +.dropdown li { + float: none; + margin: 0; + white-space: nowrap; + text-align: left; +} + +.rtl .dropdown li { + text-align: right; +} +.wrap .dropdown li, .dropdown.wrap li { + white-space: normal; +} + +.dropdown li:before, .dropdown li:after { + display: none !important; +} + +.roles-options > .dropdown { + left: auto; + top: 3.2em; + width: 250px; +} + +.rtl .roles-options > .dropdown { + right: auto; +} + +.roles-options { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + -o-user-select: none; + user-select: none; + width: 250px; +} + +.roles-options > span { + border: 1px solid #DEDEDE; + border-radius: 3px; + padding: 4px; + width: 250px; + display: none; + background: url('../images/arrow_down.gif') no-repeat 245px .7em; +} + +.rtl .roles-options > span { + background: url('../images/arrow_down.gif') no-repeat 7px .7em; +} + +.roles-options li { + list-style: none; +} + +.roles-highlight { + background-color: #1e90ff; + color: #fff; +} + + +/* Classes for additional tasks +---------------------------------------- */ + +.current-ext { + color: #228822; +} + +.outdated-ext { + color: #BC2A4D; +} + +.phpinfo { + overflow: auto; + width: 99%; + direction: ltr; +} + +.phpinfo td, .phpinfo th, .phpinfo h2, .phpinfo h1 { + text-align: left; +} + +.requirements_not_met { + padding: 5px; + background-color: #BC2A4D; +} + +.requirements_not_met dt label, .requirements_not_met dd p { + color: #FFFFFF; + font-size: 1.4em; +} + +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + .responsive-hide { display: none !important; } + .responsive-show { display: block !important; } + .responsive-show-inline { display: inline !important; } + .responsive-show-inline-block { display: inline-block !important; } +} + +.clearfix { + overflow: hidden; +} + +.pagination:after, +#page-header:after, +#page-body:after, +#tabs:after, +#tabs > ul:after, +#tabs li:after, +#acp:after, +#content:after { + content: ''; + clear: both; + display: block; +} + +#progress-bar { + position: relative; + width: 90%; + text-align: center; + height: 25px; + margin: 20px auto; + border: 1px solid #cecece; +} + +#progress-bar #progress-bar-text { + position: absolute; + top: 0; + width: 100%; + color: #000; +} + +#progress-bar #progress-bar-filler { + display: block; + position: relative; + top: 0; + left: 0; + background-color: #3c84ad; + width: 0; + height: 25px; + overflow: hidden; + color: #fff; +} + +#progress-bar p { + line-height: 25px; + font-weight: bold; +} + +.send-stats-row { + margin: 15px 0; +} + +.send-stats-row:before { + display: table; + content: " "; +} + +.send-stats-tile { + position: relative; + padding: 14px; + margin-bottom: 20px; + background-color: #eff0f2; + border-radius: 6px; + box-shadow: rgba(0,0,0,0.3) 1px 1px 5px; +} + +.send-stats-tile h2 { + margin-top: 0; + text-align: center; + padding-bottom: 1em; +} + +.send-stats-tile i { + padding-right: 0.3em; +} + +.icon { + font-family: FontAwesome; + font-style: normal; +} + +.send-stats-data-row { + background: #f9f9f9; + border-radius: 6px; + border: #DEDEDE 1px solid; + padding: 10px; + border-top-width: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; +} + +.send-stats-data-hidden .configlist { + display: none; +} + +.send-stats-data-only-row { + border-radius: 6px !important; + border-bottom-width: 1px !important; +} + +.send-stats-data-hidden { + padding: 0; + border: none; +} + +.send-stats-row > .send-stats-data-row:first-child { + background-color: #d9edf7; + border-bottom-width: 0; + border-top-right-radius: 6px; + border-top-left-radius: 6px; + border-top-width: 1px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +.send-stats-settings dt, .send-stats-settings dd { + min-width: 25px; +} + +.send-stats-settings dd { + line-height: 1.5em; +} + +.send-stats-settings input { + display: none; +} + +.send-stats-settings input[type=checkbox] + label:before { + content: "\f096"; + font-family: FontAwesome; + font-size: 1.5em; +} + +.send-stats-settings input[type=checkbox]:checked + label:before { + content: "\f14a"; + color: #3c763d; +} + +.send-stats-data-row a:hover span { + text-decoration: underline; +} + +.send-stats-data-row a { + text-decoration: none; + cursor: default; +} + +.send-stats-data-row i { + padding-left: 6px; +} + +.configlist { + word-wrap: break-word; + word-break: break-all; +} + +/* stylelint-disable declaration-property-unit-whitelist */ +.emoji { + min-height: 18px; + min-width: 18px; + height: 1em; + width: 1em; +} +/* stylelint-enable declaration-property-unit-whitelist */ diff --git a/adm/style/admin.js b/adm/style/admin.js new file mode 100644 index 0000000..551c78a --- /dev/null +++ b/adm/style/admin.js @@ -0,0 +1,258 @@ +/** +* phpBB3 ACP functions +*/ + +/** +* Parse document block +*/ +function parse_document(container) +{ + var test = document.createElement('div'), + oldBrowser = (typeof test.style.borderRadius == 'undefined'); + + delete test; + + /** + * Navigation + */ + container.find('#menu').each(function() { + var menu = $(this), + blocks = menu.children('.menu-block'); + + if (!blocks.length) { + return; + } + + // Set onclick event + blocks.children('a.header').click(function() { + var parent = $(this).parent(); + if (!parent.hasClass('active')) { + parent.siblings().removeClass('active'); + } + parent.toggleClass('active'); + }); + + // Set active menu + menu.find('#activemenu').parents('.menu-block').addClass('active'); + + // Check if there is active menu + if (!blocks.filter('.active').length) { + blocks.filter(':first').addClass('active'); + } + }); + + /** + * Responsive tables + */ + container.find('table').not('.not-responsive').each(function() { + var $this = $(this), + th = $this.find('thead > tr > th'), + columns = th.length, + headers = [], + totalHeaders = 0, + i, headersLength; + + // Find columns + $this.find('colgroup:first').children().each(function(i) { + var column = $(this); + $this.find('td:nth-child(' + (i + 1) + ')').addClass(column.prop('className')); + }); + + // Styles table + if ($this.hasClass('styles')) { + $this.find('td:first-child[style]').each(function() { + var style = $(this).attr('style'); + if (style.length) { + $(this).parent('tr').attr('style', style.toLowerCase().replace('padding', 'margin')).addClass('responsive-style-row'); + } + }); + } + + // Find each header + if (!$this.data('no-responsive-header')) + { + th.each(function(column) { + var cell = $(this), + colspan = parseInt(cell.attr('colspan')), + dfn = cell.attr('data-dfn'), + text = dfn ? dfn : $.trim(cell.text()); + + if (text == ' ') text = ''; + colspan = isNaN(colspan) || colspan < 1 ? 1 : colspan; + + for (i=0; i + $this.addClass('responsive'); + + if (totalHeaders < 2) { + $this.addClass('show-header'); + return; + } + + $this.find('tbody > tr').each(function() { + var row = $(this), + cells = row.children('td'), + column = 0; + + if (cells.length == 1) { + row.addClass('big-column'); + return; + } + + cells.each(function() { + var cell = $(this), + colspan = parseInt(cell.attr('colspan')), + text = $.trim(cell.text()); + + if (headersLength <= column) { + return; + } + + if ((text.length && text !== '-') || cell.children().length) { + if (headers[column] != '') { + cell.prepend('' + headers[column] + ''); + } + } + else { + cell.addClass('empty'); + } + + colspan = isNaN(colspan) || colspan < 1 ? 1 : colspan; + column += colspan; + }); + }); + + // Remove in disabled extensions list + $this.find('tr.ext_disabled > .empty:nth-child(2) + .empty').siblings(':first-child').children('dfn').remove(); + }); + + /** + * Hide empty responsive tables + */ + container.find('table.responsive > tbody').each(function() { + var items = $(this).children('tr'); + if (items.length == 0) + { + $(this).parent('table:first').addClass('responsive-hide'); + } + }); + + /** + * Fieldsets with empty + */ + container.find('fieldset dt > span:last-child').each(function() { + var $this = $(this); + if ($this.html() == ' ') { + $this.addClass('responsive-hide'); + } + + }); + + /** + * Responsive tabs + */ + container.find('#tabs').not('[data-skip-responsive]').each(function() { + var $this = $(this), + $body = $('body'), + ul = $this.children(), + tabs = ul.children().not('[data-skip-responsive]'), + links = tabs.children('a'), + item = ul.append('').find('li.responsive-tab'), + menu = item.find('.dropdown-contents'), + maxHeight = 0, + lastWidth = false, + responsive = false; + + links.each(function() { + var link = $(this); + maxHeight = Math.max(maxHeight, Math.max(link.outerHeight(true), link.parent().outerHeight(true))); + }) + + function check() { + var width = $body.width(), + height = $this.height(); + + if (arguments.length == 0 && (!responsive || width <= lastWidth) && height <= maxHeight) { + return; + } + + tabs.show(); + item.hide(); + + lastWidth = width; + height = $this.height(); + if (height <= maxHeight) { + responsive = false; + if (item.hasClass('dropdown-visible')) { + phpbb.toggleDropdown.call(item.find('a.responsive-tab-link').get(0)); + } + return; + } + + responsive = true; + item.show(); + menu.html(''); + + var availableTabs = tabs.filter(':not(.activetab, .responsive-tab)'), + total = availableTabs.length, + i, tab; + + for (i = total - 1; i >= 0; i --) { + tab = availableTabs.eq(i); + menu.prepend(tab.clone(true).removeClass('tab')); + tab.hide(); + if ($this.height() <= maxHeight) { + menu.find('a').click(function() { check(true); }); + return; + } + } + menu.find('a').click(function() { check(true); }); + } + + phpbb.registerDropdown(item.find('a.responsive-tab-link'), item.find('.dropdown'), {visibleClass: 'activetab', verticalDirection: 'down'}); + + check(true); + $(window).resize(check); + }); +} + +/** +* Run onload functions +*/ +(function($) { + $(document).ready(function() { + // Swap .nojs and .hasjs + $('body.nojs').toggleClass('nojs hasjs'); + + // Focus forms + $('form[data-focus]:first').each(function() { + $('#' + this.getAttribute('data-focus')).focus(); + }); + + parse_document($('body')); + + $('#questionnaire-form').css('display', 'none'); + var $triggerConfiglist = $('#trigger-configlist'); + + $triggerConfiglist.on('click', function () { + var $configlist = $('#configlist'); + $configlist.closest('.send-stats-data-row').toggleClass('send-stats-data-hidden'); + $configlist.closest('.send-stats-row').find('.send-stats-data-row:first-child').toggleClass('send-stats-data-only-row'); + $(this).find('i').toggleClass('fa-angle-down fa-angle-up'); + }); + + $('#configlist').closest('.send-stats-data-row').addClass('send-stats-data-hidden'); + }); +})(jQuery); diff --git a/adm/style/ajax.js b/adm/style/ajax.js new file mode 100644 index 0000000..895bb05 --- /dev/null +++ b/adm/style/ajax.js @@ -0,0 +1,332 @@ +/* global phpbb */ + +(function($) { // Avoid conflicts with other libraries + +'use strict'; + + +phpbb.prepareSendStats = function () { + var $form = $('#acp_help_phpbb'); + var $dark = $('#darkenwrapper'); + var $loadingIndicator; + + $form.on('submit', function (event) { + var $this = $(this), + currentTime = Math.floor(new Date().getTime() / 1000), + statsTime = parseInt($this.find('input[name=help_send_statistics_time]').val(), 10); + + event.preventDefault(); + $this.unbind('submit'); + + // Skip ajax request if form is submitted too early or send stats + // checkbox is not checked + if (!$this.find('input[name=help_send_statistics]').is(':checked') || + statsTime > currentTime) { + $form.find('input[type=submit]').click(); + setTimeout(function () { + $form.find('input[type=submit]').click(); + }, 300); + return; + } + + /** + * Handler for AJAX errors + */ + function errorHandler(jqXHR, textStatus, errorThrown) { + if (typeof console !== 'undefined' && console.log) { + console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown); + } + phpbb.clearLoadingTimeout(); + var errorText = ''; + + if (typeof errorThrown === 'string' && errorThrown.length > 0) { + errorText = errorThrown; + } else { + errorText = $dark.attr('data-ajax-error-text-' + textStatus); + if (typeof errorText !== 'string' || !errorText.length) { + errorText = $dark.attr('data-ajax-error-text'); + } + } + phpbb.alert($dark.attr('data-ajax-error-title'), errorText); + } + + /** + * This is a private function used to handle the callbacks, refreshes + * and alert. It calls the callback, refreshes the page if necessary, and + * displays an alert to the user and removes it after an amount of time. + * + * It cannot be called from outside this function, and is purely here to + * avoid repetition of code. + * + * @param {object} res The object sent back by the server. + */ + function returnHandler(res) { + phpbb.clearLoadingTimeout(); + + // If a confirmation is not required, display an alert and call the + // callbacks. + $dark.fadeOut(phpbb.alertTime); + + if ($loadingIndicator) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + + var $sendStatisticsSuccess = $('', { + type: 'hidden', + name: 'send_statistics_response', + value: res + }); + $sendStatisticsSuccess.appendTo('p.submit-buttons'); + + // Finish actual form submission + $form.find('input[type=submit]').click(); + } + + $loadingIndicator = phpbb.loadingIndicator(); + + $.ajax({ + url: $this.attr('data-ajax-action').replace('&', '&'), + type: 'POST', + data: 'systemdata=' + encodeURIComponent($this.find('input[name=systemdata]').val()), + success: returnHandler, + error: errorHandler, + cache: false + }).always(function() { + if ($loadingIndicator && $loadingIndicator.is(':visible')) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + }); + }); +}; + +/** + * The following callbacks are for reording items. row_down + * is triggered when an item is moved down, and row_up is triggered when + * an item is moved up. It moves the row up or down, and deactivates / + * activates any up / down icons that require it (the ones at the top or bottom). + */ +phpbb.addAjaxCallback('row_down', function(res) { + if (typeof res.success === 'undefined' || !res.success) { + return; + } + + var $firstTr = $(this).parents('tr'), + $secondTr = $firstTr.next(); + + $firstTr.insertAfter($secondTr); +}); + +phpbb.addAjaxCallback('row_up', function(res) { + if (typeof res.success === 'undefined' || !res.success) { + return; + } + + var $secondTr = $(this).parents('tr'), + $firstTr = $secondTr.prev(); + + $secondTr.insertBefore($firstTr); +}); + +/** + * This callback replaces activate links with deactivate links and vice versa. + * It does this by replacing the text, and replacing all instances of "activate" + * in the href with "deactivate", and vice versa. + */ +phpbb.addAjaxCallback('activate_deactivate', function(res) { + var $this = $(this), + newHref = $this.attr('href'); + + $this.text(res.text); + + if (newHref.indexOf('deactivate') !== -1) { + newHref = newHref.replace('deactivate', 'activate'); + } else { + newHref = newHref.replace('activate', 'deactivate'); + } + + $this.attr('href', newHref); +}); + +/** + * The removes the parent row of the link or form that triggered the callback, + * and is good for stuff like the removal of forums. + */ +phpbb.addAjaxCallback('row_delete', function(res) { + if (res.SUCCESS !== false) { + $(this).parents('tr').remove(); + } +}); + +/** + * Handler for submitting permissions form in chunks + * This call will submit permissions forms in chunks of 5 fieldsets. + */ +function submitPermissions() { + var $form = $('form#set-permissions'), + fieldsetList = $form.find('fieldset[id^=perm]'), + formDataSets = [], + dataSetIndex = 0, + $submitAllButton = $form.find('input[type=submit][name^=action]')[0], + $submitButton = $form.find('input[type=submit][data-clicked=true]')[0]; + + // Set proper start values for handling refresh of page + var permissionSubmitSize = 0, + permissionRequestCount = 0, + forumIds = [], + permissionSubmitFailed = false; + + if ($submitAllButton !== $submitButton) { + fieldsetList = $form.find('fieldset#' + $submitButton.closest('fieldset.permissions').id); + } + + $.each(fieldsetList, function (key, value) { + dataSetIndex = Math.floor(key / 5); + var $fieldset = $('fieldset#' + value.id); + if (key % 5 === 0) { + formDataSets[dataSetIndex] = $fieldset.find('select:visible, input:not([data-name])').serialize(); + } else { + formDataSets[dataSetIndex] += '&' + $fieldset.find('select:visible, input:not([data-name])').serialize(); + } + + // Find proper role value + var roleInput = $fieldset.find('input[name^=role][data-name]'); + if (roleInput.val()) { + formDataSets[dataSetIndex] += '&' + roleInput.attr('name') + '=' + roleInput.val(); + } else { + formDataSets[dataSetIndex] += '&' + roleInput.attr('name') + '=' + + $fieldset.find('select[name="' + roleInput.attr('name') + '"]').val(); + } + }); + + permissionSubmitSize = formDataSets.length; + + // Add each forum ID to forum ID list to preserve selected forums + $.each($form.find('input[type=hidden][name^=forum_id]'), function (key, value) { + if (value.name.match(/^forum_id\[([0-9]+)\]$/)) { + forumIds.push(value.value); + } + }); + + /** + * Handler for submitted permissions form chunk + * + * @param {object} res Object returned by AJAX call + */ + function handlePermissionReturn(res) { + permissionRequestCount++; + var $dark = $('#darkenwrapper'); + + if (res.S_USER_WARNING) { + phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); + permissionSubmitFailed = true; + } else if (!permissionSubmitFailed && res.S_USER_NOTICE) { + // Display success message at the end of submitting the form + if (permissionRequestCount >= permissionSubmitSize) { + var $alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); + var $alertBoxLink = $alert.find('p.alert_text > a'); + + // Create form to submit instead of normal "Back to previous page" link + if ($alertBoxLink) { + // Remove forum_id[] from URL + $alertBoxLink.attr('href', $alertBoxLink.attr('href').replace(/(&forum_id\[\]=[0-9]+)/g, '')); + var previousPageForm = '
'; + $.each(forumIds, function (key, value) { + previousPageForm += ''; + }); + previousPageForm += '
'; + + $alertBoxLink.on('click', function (e) { + var $previousPageForm = $(previousPageForm); + $('body').append($previousPageForm); + e.preventDefault(); + $previousPageForm.submit(); + }); + } + + // Do not allow closing alert + $dark.off('click'); + $alert.find('.alert_close').hide(); + + if (typeof res.REFRESH_DATA !== 'undefined') { + setTimeout(function () { + // Create forum to submit using POST. This will prevent + // exceeding the maximum length of URLs + var form = '
'; + $.each(forumIds, function (key, value) { + form += ''; + }); + form += '
'; + $form = $(form); + $('body').append($form); + + // Hide the alert even if we refresh the page, in case the user + // presses the back button. + $dark.fadeOut(phpbb.alertTime, function () { + if (typeof $alert !== 'undefined') { + $alert.hide(); + } + }); + + // Submit form + $form.submit(); + }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds + } + } + } + } + + // Create AJAX request for each form data set + $.each(formDataSets, function (key, formData) { + $.ajax({ + url: $form.action, + type: 'POST', + data: formData + '&' + $submitButton.name + '=' + encodeURIComponent($submitButton.value) + + '&creation_time=' + $form.find('input[type=hidden][name=creation_time]')[0].value + + '&form_token=' + $form.find('input[type=hidden][name=form_token]')[0].value + + '&' + $form.children('input[type=hidden]').serialize() + + '&' + $form.find('input[type=checkbox][name^=inherit]').serialize(), + success: handlePermissionReturn, + error: handlePermissionReturn + }); + }); +} + +$('[data-ajax]').each(function() { + var $this = $(this), + ajax = $this.attr('data-ajax'); + + if (ajax !== 'false') { + var fn = (ajax !== 'true') ? ajax : null; + phpbb.ajaxify({ + selector: this, + refresh: $this.attr('data-refresh') !== undefined, + callback: fn + }); + } +}); + +/** +* Automatically resize textarea +*/ +$(function() { + phpbb.resizeTextArea($('textarea:not(.no-auto-resize)'), {minHeight: 75}); + + var $setPermissionsForm = $('form#set-permissions'); + if ($setPermissionsForm.length) { + $setPermissionsForm.on('submit', function (e) { + submitPermissions(); + e.preventDefault(); + }); + $setPermissionsForm.find('input[type=submit]').click(function() { + $('input[type=submit]', $(this).parents($('form#set-permissions'))).removeAttr('data-clicked'); + $(this).attr('data-clicked', true); + }); + } + + if ($('#acp_help_phpbb')) { + phpbb.prepareSendStats(); + } +}); + + +})(jQuery); // Avoid conflicts with other libraries diff --git a/adm/style/auth_provider_ldap.html b/adm/style/auth_provider_ldap.html new file mode 100644 index 0000000..97684db --- /dev/null +++ b/adm/style/auth_provider_ldap.html @@ -0,0 +1,35 @@ +
+ {L_LDAP} +
+

{L_LDAP_SERVER_EXPLAIN}
+
+
+
+

{L_LDAP_PORT_EXPLAIN}
+
+
+
+

{L_LDAP_DN_EXPLAIN}
+
+
+
+

{L_LDAP_UID_EXPLAIN}
+
+
+
+

{L_LDAP_USER_FILTER_EXPLAIN}
+
+
+
+

{L_LDAP_EMAIL_EXPLAIN}
+
+
+
+

{L_LDAP_USER_EXPLAIN}
+
+
+
+

{L_LDAP_PASSWORD_EXPLAIN}
+
+
+
diff --git a/adm/style/auth_provider_oauth.html b/adm/style/auth_provider_oauth.html new file mode 100644 index 0000000..4c8ff4d --- /dev/null +++ b/adm/style/auth_provider_oauth.html @@ -0,0 +1,18 @@ + +
+

{L_AUTH_PROVIDER_OAUTH_EXPLAIN}

+ + +
+ {oauth_services.ACTUAL_NAME} +
+
+
+
+
+
+
+
+
+ +
diff --git a/adm/style/captcha_default_acp_demo.html b/adm/style/captcha_default_acp_demo.html new file mode 100644 index 0000000..0f137f2 --- /dev/null +++ b/adm/style/captcha_default_acp_demo.html @@ -0,0 +1,4 @@ +
+

{L_CAPTCHA_PREVIEW_EXPLAIN}
+
{L_PREVIEW}
+
diff --git a/adm/style/captcha_gd_acp.html b/adm/style/captcha_gd_acp.html new file mode 100644 index 0000000..43d54ad --- /dev/null +++ b/adm/style/captcha_gd_acp.html @@ -0,0 +1,74 @@ + + + + « {L_BACK} + +

{L_ACP_VC_SETTINGS}

+ +

{L_ACP_VC_SETTINGS_EXPLAIN}

+ + +
+ +
+{L_GENERAL_OPTIONS} + +
+

{L_CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN}
+
+
+
+
+

{L_CAPTCHA_GD_X_GRID_EXPLAIN}
+
+
+
+

{L_CAPTCHA_GD_Y_GRID_EXPLAIN}
+
+
+
+

{L_CAPTCHA_GD_WAVE_EXPLAIN}
+
+ +
+
+
+

{L_CAPTCHA_GD_3D_NOISE_EXPLAIN}
+
+ +
+
+
+

{L_CAPTCHA_GD_FONTS_EXPLAIN}
+
+ + + +
+
+ +
+
+ {L_PREVIEW} + + + + +
+ +
+ {L_ACP_SUBMIT_CHANGES} +

+   +   +   +

+ + + + + {S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/captcha_qa_acp.html b/adm/style/captcha_qa_acp.html new file mode 100644 index 0000000..6235f9a --- /dev/null +++ b/adm/style/captcha_qa_acp.html @@ -0,0 +1,90 @@ + + + + + + « {L_BACK} + +

{L_QUESTIONS}

+ +

{L_QUESTIONS_EXPLAIN}

+ +
+ +
+ {L_QUESTIONS} + + + + + + + + + + + + + + + + + + + + + +
{L_QUESTIONS}
{L_QUESTION_TEXT}{L_QUESTION_LANG}{L_ACTION}
{questions.QUESTION_TEXT}{questions.QUESTION_LANG}{ICON_EDIT} {ICON_DELETE}
+
+ + + + + + {S_FORM_TOKEN} +
+ {S_FORM_TOKEN} +
+
+ + +
+

{L_WARNING}

+

{L_QA_ERROR_MSG}

+
+ +
+
+ {L_EDIT_QUESTION} +
+

{L_QUESTION_STRICT_EXPLAIN}
+
+
+
+ +
+

{L_QUESTION_LANG_EXPLAIN}
+
+
+
+

{L_QUESTION_TEXT_EXPLAIN}
+
+
+
+

{L_ANSWERS_EXPLAIN}
+
+
+
+
+ + + + + + + {S_FORM_TOKEN} +
+
+ + + diff --git a/adm/style/captcha_qa_acp_demo.html b/adm/style/captcha_qa_acp_demo.html new file mode 100644 index 0000000..79170e2 --- /dev/null +++ b/adm/style/captcha_qa_acp_demo.html @@ -0,0 +1,7 @@ +
+

{L_CONFIRM_QUESTION_EXPLAIN}
+ +
+ +
+
diff --git a/adm/style/captcha_recaptcha.html b/adm/style/captcha_recaptcha.html new file mode 100644 index 0000000..3f61c76 --- /dev/null +++ b/adm/style/captcha_recaptcha.html @@ -0,0 +1,14 @@ + +
+
+ + + +
+
+
+ +{L_RECAPTCHA_NOT_AVAILABLE} + diff --git a/adm/style/captcha_recaptcha_acp.html b/adm/style/captcha_recaptcha_acp.html new file mode 100644 index 0000000..67176eb --- /dev/null +++ b/adm/style/captcha_recaptcha_acp.html @@ -0,0 +1,50 @@ + + + + +

{L_ACP_VC_SETTINGS}

+ +

{L_ACP_VC_SETTINGS_EXPLAIN}

+ + +
+ +
+{L_GENERAL_OPTIONS} + +
+

{L_RECAPTCHA_PUBLIC_EXPLAIN}
+
+
+
+

{L_RECAPTCHA_PRIVATE_EXPLAIN}
+
+
+ + +
+
+ {L_PREVIEW} + +
+

{L_WARNING}

+

{L_CAPTCHA_PREVIEW_MSG}

+
+ + +
+ +
+ {L_ACP_SUBMIT_CHANGES} +

+   +   +

+ + + + {S_FORM_TOKEN} +
+
+ + diff --git a/adm/style/confirm_bbcode.html b/adm/style/confirm_bbcode.html new file mode 100644 index 0000000..52a6523 --- /dev/null +++ b/adm/style/confirm_bbcode.html @@ -0,0 +1,22 @@ + + +
+
+

{L_WARNING}

+

{MESSAGE_TEXT}

+
+
+ + + {S_HIDDEN_FIELDS} + +
+   + +
+ +
+ +
+ + diff --git a/adm/style/confirm_body.html b/adm/style/confirm_body.html new file mode 100644 index 0000000..d0360d1 --- /dev/null +++ b/adm/style/confirm_body.html @@ -0,0 +1,32 @@ + + +

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+ +
+   + +
+ + + + + +
+ +
+

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+ + {S_HIDDEN_FIELDS} + +
+   + +
+ +
+
+ + + diff --git a/adm/style/confirm_body_prune.html b/adm/style/confirm_body_prune.html new file mode 100644 index 0000000..4c00ac2 --- /dev/null +++ b/adm/style/confirm_body_prune.html @@ -0,0 +1,36 @@ + + +
+ +
+

{L_PRUNE_USERS_LIST}

+

{L_PRUNE_USERS_LIST_DEACTIVATE}

{L_PRUNE_USERS_LIST_DELETE}

+ +
+ + » + {users.USERNAME} + [ {L_USER_ADMIN} ]
+ +
+ + {L_MARK_ALL} • + {L_UNMARK_ALL} + +
+ +
+

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+ + {S_HIDDEN_FIELDS} + +
+   + +
+
+ +
+ + diff --git a/adm/style/installer_convert.html b/adm/style/installer_convert.html new file mode 100644 index 0000000..aa16542 --- /dev/null +++ b/adm/style/installer_convert.html @@ -0,0 +1,87 @@ + +

{TITLE}

+ +
+

{ERROR_TITLE}

+

{ERROR_MSG}

+
+ + +
+ + {errors.TITLE} +

{errors.DESCRIPTION}

+ +
+ +

{BODY}

+{CONTENT} + +

onclick="return false;">{L_SUBMIT}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_AVAILABLE_CONVERTORS}
{L_SOFTWARE}{L_VERSION}{L_AUTHOR}{L_CONVERT_OPTIONS}
{convertors.SOFTWARE}{convertors.VERSION}{convertors.AUTHOR}{L_CONVERT}
{L_NO_CONVERTORS}---
+ + +
+ + + + +
+ +
+ + {checks.LEGEND} +

{checks.LEGEND_EXPLAIN}

+ + +
+

{checks.TITLE_EXPLAIN}
+
{checks.RESULT}
+
+ + + +
+ + + diff --git a/adm/style/installer_footer.html b/adm/style/installer_footer.html new file mode 100644 index 0000000..fefa8f6 --- /dev/null +++ b/adm/style/installer_footer.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + +{$SCRIPTS} + + + diff --git a/adm/style/installer_form.html b/adm/style/installer_form.html new file mode 100644 index 0000000..592d361 --- /dev/null +++ b/adm/style/installer_form.html @@ -0,0 +1,57 @@ +

{FORM_TITLE}

+
+ + +
+ + + + + +
+ +
+ + {options.LEGEND} + +
+

{options.TITLE_EXPLAIN}
+
+ + + + + + + + + + + + + + + checked /> {options.OPTIONS.label} + + +
+
+ + + +
+ + + +
+ {L_SUBMIT} + + disabled="disabled" /> + +
+ +
diff --git a/adm/style/installer_header.html b/adm/style/installer_header.html new file mode 100644 index 0000000..704db9e --- /dev/null +++ b/adm/style/installer_header.html @@ -0,0 +1,58 @@ + + + + + + + {META} + {PAGE_TITLE} + + + + + +
+ + +
+
+ +
+ +
+
+ + +
+
diff --git a/adm/style/installer_install.html b/adm/style/installer_install.html new file mode 100644 index 0000000..53a91f2 --- /dev/null +++ b/adm/style/installer_install.html @@ -0,0 +1,13 @@ + +

{TITLE}

+

{CONTENT}

+ +
+
+ {L_SUBMIT} + +
+
+ + + diff --git a/adm/style/installer_main.html b/adm/style/installer_main.html new file mode 100644 index 0000000..f14fe4d --- /dev/null +++ b/adm/style/installer_main.html @@ -0,0 +1,6 @@ + + +

{TITLE}

+

{BODY}

+ + diff --git a/adm/style/installer_update.html b/adm/style/installer_update.html new file mode 100644 index 0000000..48cc07f --- /dev/null +++ b/adm/style/installer_update.html @@ -0,0 +1,13 @@ + +

{TITLE}

+

{CONTENT}

+ +
+
+ {L_SUBMIT} + +
+
+ + + diff --git a/adm/style/installer_update_file_status.html b/adm/style/installer_update_file_status.html new file mode 100644 index 0000000..a27bfa6 --- /dev/null +++ b/adm/style/installer_update_file_status.html @@ -0,0 +1,80 @@ + +

{L_FILES_DELETED}

+ +

{L_FILES_DELETED_EXPLAIN}

+ +
+ {L_STATUS_DELETED} + +
+
{deleted.DIR_PART}{deleted.FILE_PART}
+
+ +
+ + + + +

{L_FILES_CONFLICT}

+ +

{L_FILES_CONFLICT_EXPLAIN}

+ +
+ {L_STATUS_CONFLICT} + +
+
{conflict.DIR_PART}{conflict.FILE_PART}
+
+ +
+ + + + +

{L_FILES_MODIFIED}

+ +

{L_FILES_MODIFIED_EXPLAIN}

+ +
+ {L_STATUS_MODIFIED} + +
+
{modified.DIR_PART}{modified.FILE_PART}
+
+ +
+ + + + +

{L_FILES_NEW}

+ +

{L_FILES_NEW_EXPLAIN}

+ + + + + + +

{L_FILES_NOT_MODIFIED}

+ +

{L_FILES_NOT_MODIFIED_EXPLAIN}

+ + + + diff --git a/adm/style/message_body.html b/adm/style/message_body.html new file mode 100644 index 0000000..3ea9e5b --- /dev/null +++ b/adm/style/message_body.html @@ -0,0 +1,8 @@ + + +
class="successbox"class="errorbox"> +

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+
+ + diff --git a/adm/style/overall_footer.html b/adm/style/overall_footer.html new file mode 100644 index 0000000..8745286 --- /dev/null +++ b/adm/style/overall_footer.html @@ -0,0 +1,46 @@ +
+
+
+
+
+ + +
+ + + + + + + + +{$SCRIPTS} + + + diff --git a/adm/style/overall_header.html b/adm/style/overall_header.html new file mode 100644 index 0000000..8279ac3 --- /dev/null +++ b/adm/style/overall_header.html @@ -0,0 +1,157 @@ + + + + + + +{META} +{PAGE_TITLE} + + + + + + + + +{$STYLESHEETS} + + + + + + + + + +
+ + +
+
+ +
+ +
+
+ + + +
+ +
+
+ {% if CONTAINER_EXCEPTION !== false %} +
+

{{ lang('CONTAINER_EXCEPTION') }}


+

{{ lang('EXCEPTION') }}{{ lang('COLON') }} {{ CONTAINER_EXCEPTION.getMessage() }}

+
{{ CONTAINER_EXCEPTION.getTraceAsString() }}
+
+ {% endif %} diff --git a/adm/style/pagination.html b/adm/style/pagination.html new file mode 100644 index 0000000..5e75572 --- /dev/null +++ b/adm/style/pagination.html @@ -0,0 +1,12 @@ + + {PAGE_NUMBER} • + diff --git a/adm/style/permission_forum_copy.html b/adm/style/permission_forum_copy.html new file mode 100644 index 0000000..b1539af --- /dev/null +++ b/adm/style/permission_forum_copy.html @@ -0,0 +1,40 @@ + + + + +

{L_ACP_FORUM_PERMISSIONS_COPY}

+ + {L_ACP_FORUM_PERMISSIONS_COPY_EXPLAIN} + +
+ +
+ {L_LOOK_UP_FORUM} + +
+

{L_COPY_PERMISSIONS_FORUM_FROM_EXPLAIN}
+
+
+
+ +
+ {L_LOOK_UP_FORUM} +

{L_LOOK_UP_FORUMS_EXPLAIN}

+ +
+

{L_COPY_PERMISSIONS_FORUM_TO_EXPLAIN}
+
+
+
+ +
+ {L_SUBMIT} +   + + {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} +
+ +
+ + diff --git a/adm/style/permission_mask.html b/adm/style/permission_mask.html new file mode 100644 index 0000000..c556664 --- /dev/null +++ b/adm/style/permission_mask.html @@ -0,0 +1,150 @@ + + + + + +
+

{p_mask.NAME} [{p_mask.L_ACL_TYPE}]

+ + +
+
+ + + + + + {p_mask.f_mask.PADDING}{p_mask.f_mask.PADDING}{p_mask.f_mask.NAME} + + + +
+
+ {% if p_mask.f_mask.role_options %} +
+ +
+ {% else %} +
{L_NO_ROLE_AVAILABLE}
+ {% endif %} +
+ + + + + +
+ + + diff --git a/adm/style/permission_roles_mask.html b/adm/style/permission_roles_mask.html new file mode 100644 index 0000000..3a14e65 --- /dev/null +++ b/adm/style/permission_roles_mask.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + +
{L_FORUM}{L_COLON} {role_mask.NAME}
{L_USERS}
+ + {role_mask.users.USERNAME} :: + + {L_USERS_NOT_ASSIGNED} + +
{L_GROUPS}
+ + {role_mask.groups.GROUP_NAME} :: + + {L_GROUPS_NOT_ASSIGNED} + +
+ + + +

{L_ROLE_NOT_ASSIGNED}

+ + diff --git a/adm/style/permission_trace.html b/adm/style/permission_trace.html new file mode 100644 index 0000000..7330ffe --- /dev/null +++ b/adm/style/permission_trace.html @@ -0,0 +1,58 @@ + + +
+ + « {L_BACK} + +

{L_TRACE_FOR}{L_COLON} {PERMISSION_USERNAME} / {FORUM_NAME} / {PERMISSION}

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_TRACE_WHO}{L_ACL_SETTING}{L_TRACE_TOTAL}{L_INFORMATION}
{trace.WHO}{L_ACL_NEVER}{L_ACL_YES}{L_ACL_NO}{L_ACL_NEVER}{L_ACL_YES}{L_ACL_NO}{trace.INFORMATION}
{L_TRACE_RESULT} + {L_ACL_NEVER}{L_ACL_YES}{L_ACL_NO} + {L_RESULTING_PERMISSION}
+ +
+ +
+ + diff --git a/adm/style/permissions.js b/adm/style/permissions.js new file mode 100644 index 0000000..9178ada --- /dev/null +++ b/adm/style/permissions.js @@ -0,0 +1,300 @@ +/** +* Hide and show all checkboxes +* status = true (show boxes), false (hide boxes) +*/ +function display_checkboxes(status) { + var form = document.getElementById('set-permissions'); + var cb = document.getElementsByTagName('input'); + var display; + + //show + if (status) { + display = 'inline'; + } + //hide + else { + display = 'none'; + } + + for (var i = 0; i < cb.length; i++ ) { + if (cb[i].className === 'permissions-checkbox') { + cb[i].style.display = display; + } + } +} + +/** +* Change opacity of element +* e = element +* value = 0 (hidden) till 10 (fully visible) +*/ +function set_opacity(e, value) { + e.style.opacity = value/10; + + //IE opacity currently turned off, because of its astronomical stupidity + //e.style.filter = 'alpha(opacity=' + value*10 + ')'; +} + +/** +* Reset the opacity and checkboxes +* block_id = id of the element that needs to be toggled +*/ +function toggle_opacity(block_id) { + var cb = document.getElementById('checkbox' + block_id); + var fs = document.getElementById('perm' + block_id); + + if (cb.checked) { + set_opacity(fs, 5); + } else { + set_opacity(fs, 10); + } +} + +/** +* Reset the opacity and checkboxes +* value = 0 (checked) and 1 (unchecked) +* except_id = id of the element not to hide +*/ +function reset_opacity(status, except_id) { + var perm = document.getElementById('set-permissions'); + var fs = perm.getElementsByTagName('fieldset'); + var opacity = 5; + + if (status) { + opacity = 10; + } + + for (var i = 0; i < fs.length; i++ ) { + if (fs[i].className !== 'quick') { + set_opacity(fs[i], opacity); + } + } + + if (typeof(except_id) !== 'undefined') { + set_opacity(document.getElementById('perm' + except_id), 10); + } + + //reset checkboxes too + marklist('set-permissions', 'inherit', !status); +} + +/** +* Check whether we have a full radiobutton row of true +* index = offset for the row of inputs (0 == first row, 1 == second, 2 == third), +* rb = array of radiobuttons +*/ +function get_radio_status(index, rb) { + for (var i = index; i < rb.length; i = i + 3 ) { + if (rb[i].checked !== true) { + if (i > index) { + //at least one is true, but not all (custom) + return 2; + } + //first one is not true + return 0; + } + } + + // all radiobuttons true + return 1; +} + +/** +* Set tab colours +* id = panel the tab needs to be set for, +* init = initialising on open, +* quick = If no calculation needed, this contains the colour +*/ +function set_colours(id, init, quick) { + var table = document.getElementById('table' + id); + var tab = document.getElementById('tab' + id); + + if (typeof(quick) !== 'undefined') { + tab.className = 'permissions-preset-' + quick + ' activetab'; + return; + } + + var rb = table.getElementsByTagName('input'); + var colour = 'custom'; + + var status = get_radio_status(0, rb); + + if (status === 1) { + colour = 'yes'; + } else if (status === 0) { + // We move on to No + status = get_radio_status(1, rb); + + if (status === 1) { + colour = 'no'; + } else if (status === 0) { + // We move on to Never + status = get_radio_status(2, rb); + + if (status === 1) { + colour = 'never'; + } + } + } + + if (init) { + tab.className = 'permissions-preset-' + colour; + } else { + tab.className = 'permissions-preset-' + colour + ' activetab'; + } +} + +/** +* Initialise advanced tab colours on first load +* block_id = block that is opened +*/ +function init_colours(block_id) { + var block = document.getElementById('advanced' + block_id); + var panels = block.getElementsByTagName('div'); + var tab = document.getElementById('tab' + id); + + for (var i = 0; i < panels.length; i++) { + if (panels[i].className === 'permissions-panel') { + set_colours(panels[i].id.replace(/options/, ''), true); + } + } + + tab.className = tab.className + ' activetab'; +} + +/** +* Show/hide option panels +* value = suffix for ID to show +* adv = we are opening advanced permissions +* view = called from view permissions +*/ +function swap_options(pmask, fmask, cat, adv, view) { + id = pmask + fmask + cat; + active_option = active_pmask + active_fmask + active_cat; + + var old_tab = document.getElementById('tab' + active_option); + var new_tab = document.getElementById('tab' + id); + var adv_block = document.getElementById('advanced' + pmask + fmask); + + if (adv_block.style.display === 'block' && adv === true) { + phpbb.toggleDisplay('advanced' + pmask + fmask, -1); + reset_opacity(1); + display_checkboxes(false); + return; + } + + // no need to set anything if we are clicking on the same tab again + if (new_tab === old_tab && !adv) { + return; + } + + // init colours + if (adv && (pmask + fmask) !== (active_pmask + active_fmask)) { + init_colours(pmask + fmask); + display_checkboxes(true); + reset_opacity(1); + } else if (adv) { + //Checkbox might have been clicked, but we need full visibility + display_checkboxes(true); + reset_opacity(1); + } + + // set active tab + old_tab.className = old_tab.className.replace(/\ activetab/g, ''); + new_tab.className = new_tab.className + ' activetab'; + + if (id === active_option && adv !== true) { + return; + } + + phpbb.toggleDisplay('options' + active_option, -1); + + //hiding and showing the checkbox + if (document.getElementById('checkbox' + active_pmask + active_fmask)) { + phpbb.toggleDisplay('checkbox' + pmask + fmask, -1); + + if ((pmask + fmask) !== (active_pmask + active_fmask)) { + document.getElementById('checkbox' + active_pmask + active_fmask).style.display = 'inline'; + } + } + + if (!view) { + phpbb.toggleDisplay('advanced' + active_pmask + active_fmask, -1); + } + + if (!view) { + phpbb.toggleDisplay('advanced' + pmask + fmask, 1); + } + phpbb.toggleDisplay('options' + id, 1); + + active_pmask = pmask; + active_fmask = fmask; + active_cat = cat; +} + +/** +* Mark all radio buttons in one panel +* id = table ID container, s = status ['y'/'u'/'n'] +*/ +function mark_options(id, s) { + var t = document.getElementById(id); + + if (!t) { + return; + } + + var rb = t.getElementsByTagName('input'); + + for (var r = 0; r < rb.length; r++) { + if (rb[r].id.substr(rb[r].id.length-1) === s) { + rb[r].checked = true; + } + } +} + +function mark_one_option(id, field_name, s) { + var t = document.getElementById(id); + + if (!t) { + return; + } + + var rb = t.getElementsByTagName('input'); + + for (var r = 0; r < rb.length; r++) { + if (rb[r].id.substr(rb[r].id.length-field_name.length-3, field_name.length) === field_name && rb[r].id.substr(rb[r].id.length-1) === s) { + rb[r].checked = true; + } + } +} + +/** +* Reset role dropdown field to Select role... if an option gets changed +*/ +function reset_role(id) { + var t = document.getElementById(id); + + if (!t) { + return; + } + + t.options[0].selected = true; +} + +/** +* Load role and set options accordingly +*/ +function set_role_settings(role_id, target_id) { + settings = role_options[role_id]; + + if (!settings) { + return; + } + + // Mark all options to no (unset) first... + mark_options(target_id, 'u'); + + for (var r in settings) { + mark_one_option(target_id, r, (settings[r] === 1) ? 'y' : 'n'); + } +} diff --git a/adm/style/profilefields/bool.html b/adm/style/profilefields/bool.html new file mode 100644 index 0000000..f1d7ba7 --- /dev/null +++ b/adm/style/profilefields/bool.html @@ -0,0 +1,7 @@ + + + + + checked="checked" /> + + diff --git a/adm/style/profilefields/date.html b/adm/style/profilefields/date.html new file mode 100644 index 0000000..5d5bc04 --- /dev/null +++ b/adm/style/profilefields/date.html @@ -0,0 +1,5 @@ + + + + + diff --git a/adm/style/profilefields/dropdown.html b/adm/style/profilefields/dropdown.html new file mode 100644 index 0000000..243b703 --- /dev/null +++ b/adm/style/profilefields/dropdown.html @@ -0,0 +1,5 @@ + + + diff --git a/adm/style/profilefields/int.html b/adm/style/profilefields/int.html new file mode 100644 index 0000000..d047c25 --- /dev/null +++ b/adm/style/profilefields/int.html @@ -0,0 +1,3 @@ + + + diff --git a/adm/style/profilefields/string.html b/adm/style/profilefields/string.html new file mode 100644 index 0000000..a8855f5 --- /dev/null +++ b/adm/style/profilefields/string.html @@ -0,0 +1,3 @@ + + + diff --git a/adm/style/profilefields/text.html b/adm/style/profilefields/text.html new file mode 100644 index 0000000..6334b61 --- /dev/null +++ b/adm/style/profilefields/text.html @@ -0,0 +1,3 @@ + + + diff --git a/adm/style/profilefields/url.html b/adm/style/profilefields/url.html new file mode 100644 index 0000000..8dd3a90 --- /dev/null +++ b/adm/style/profilefields/url.html @@ -0,0 +1,3 @@ + + + diff --git a/adm/style/progress_bar.html b/adm/style/progress_bar.html new file mode 100644 index 0000000..1822675 --- /dev/null +++ b/adm/style/progress_bar.html @@ -0,0 +1,40 @@ + + + + +
+

{L_PROGRESS}

+ {L_PROGRESS} +

{L_PROGRESS_EXPLAIN}

+
+ + + + diff --git a/adm/style/simple_body.html b/adm/style/simple_body.html new file mode 100644 index 0000000..ca06bc4 --- /dev/null +++ b/adm/style/simple_body.html @@ -0,0 +1,8 @@ + + +
+

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+
+ + diff --git a/adm/style/simple_footer.html b/adm/style/simple_footer.html new file mode 100644 index 0000000..08ee0a7 --- /dev/null +++ b/adm/style/simple_footer.html @@ -0,0 +1,27 @@ + +

+
+ + + + + + + + +{$SCRIPTS} + + + diff --git a/adm/style/simple_header.html b/adm/style/simple_header.html new file mode 100644 index 0000000..439645a --- /dev/null +++ b/adm/style/simple_header.html @@ -0,0 +1,96 @@ + + + + + + +{META} +{PAGE_TITLE} + + + + + +{$STYLESHEETS} + + + + + + + +
diff --git a/adm/style/timezone.js b/adm/style/timezone.js new file mode 100644 index 0000000..b5e27c9 --- /dev/null +++ b/adm/style/timezone.js @@ -0,0 +1,13 @@ +(function($) { // Avoid conflicts with other libraries + +"use strict"; + +$('#tz_date').change(function() { + phpbb.timezoneSwitchDate(false); +}); + +$(document).ready( + phpbb.timezoneEnableDateSelection +); + +})(jQuery); // Avoid conflicts with other libraries diff --git a/adm/style/timezone_option.html b/adm/style/timezone_option.html new file mode 100644 index 0000000..acfff30 --- /dev/null +++ b/adm/style/timezone_option.html @@ -0,0 +1,27 @@ +
+
+ + + +
+ + + +
+
diff --git a/adm/style/tooltip.js b/adm/style/tooltip.js new file mode 100644 index 0000000..7b7abb1 --- /dev/null +++ b/adm/style/tooltip.js @@ -0,0 +1,240 @@ +/* +javascript for Bubble Tooltips by Alessandro Fulciniti +- http://pro.html.it - http://web-graphics.com +obtained from: http://web-graphics.com/mtarchive/001717.php + +phpBB Development Team: + - modified to adhere to our coding guidelines + - integration into our design + - added ability to perform tooltips on select elements + - further adjustements +*/ + +(function($) { // Avoid conflicts with other libraries + +'use strict'; + +var tooltips = []; + +/** + * Enable tooltip replacements for selects + * @param {string} id ID tag of select + * @param {string} headline Text that should appear on top of tooltip + * @param {string} [subId] Sub ID that should only be using tooltips (optional) +*/ +phpbb.enableTooltipsSelect = function (id, headline, subId) { + var $links, hold; + + hold = $('', { + id: '_tooltip_container', + css: { + position: 'absolute' + } + }); + + $('body').append(hold); + + if (!id) { + $links = $('.roles-options li'); + } else { + $links = $('.roles-options li', '#' + id); + } + + $links.each(function () { + var $this = $(this); + + if (subId) { + if ($this.parent().attr('id').substr(0, subId.length) === subId) { + phpbb.prepareTooltips($this, headline); + } + } else { + phpbb.prepareTooltips($this, headline); + } + }); +}; + +/** + * Prepare elements to replace + * + * @param {jQuery} $element Element to prepare for tooltips + * @param {string} headText Text heading to display +*/ +phpbb.prepareTooltips = function ($element, headText) { + var $tooltip, text, $desc, $title; + + text = $element.attr('data-title'); + + if (text === null || text.length === 0) { + return; + } + + $title = $('', { + class: 'top', + css: { + display: 'block' + } + }) + .append(document.createTextNode(headText)); + + $desc = $('', { + class: 'bottom', + html: text, + css: { + display: 'block' + } + }); + + $tooltip = $('', { + class: 'tooltip', + css: { + display: 'block' + } + }) + .append($title) + .append($desc); + + tooltips[$element.attr('data-id')] = $tooltip; + $element.on('mouseover', phpbb.showTooltip); + $element.on('mouseout', phpbb.hideTooltip); +}; + +/** + * Show tooltip + * + * @param {object} $element Element passed by .on() +*/ +phpbb.showTooltip = function ($element) { + var $this = $($element.target); + $('#_tooltip_container').append(tooltips[$this.attr('data-id')]); + phpbb.positionTooltip($this); +}; + +/** + * Hide tooltip +*/ +phpbb.hideTooltip = function () { + var d = document.getElementById('_tooltip_container'); + if (d.childNodes.length > 0) { + d.removeChild(d.firstChild); + } +}; + +/** + * Correct positioning of tooltip container + * + * @param {jQuery} $element Tooltip element that should be positioned +*/ +phpbb.positionTooltip = function ($element) { + var offset; + + $element = $element.parent(); + offset = $element.offset(); + + if ($('body').hasClass('rtl')) { + $('#_tooltip_container').css({ + top: offset.top + 30, + left: offset.left + 255 + }); + } else { + $('#_tooltip_container').css({ + top: offset.top + 30, + left: offset.left - 205 + }); + } +}; + +/** + * Prepare roles drop down select + */ +phpbb.prepareRolesDropdown = function () { + var $options = $('.roles-options li'); + + // Display span and hide select + $('.roles-options > span').css('display', 'block'); + $('.roles-options > select').hide(); + $('.roles-options > input[type=hidden]').each(function () { + var $this = $(this); + + if ($this.attr('data-name') && !$this.attr('name')) { + $this.attr('name', $this.attr('data-name')); + } + }); + + // Prepare highlighting of select options and settings update + $options.each(function () { + var $this = $(this); + var $rolesOptions = $this.closest('.roles-options'); + var $span = $rolesOptions.children('span'); + + // Correctly show selected option + if (typeof $this.attr('data-selected') !== 'undefined') { + $rolesOptions + .children('span') + .text($this.text()) + .attr('data-default', $this.text()) + .attr('data-default-val', $this.attr('data-id')); + + // Save default text of drop down if there is no default set yet + if (typeof $span.attr('data-default') === 'undefined') { + $span.attr('data-default', $span.text()); + } + + // Prepare resetting drop down on form reset + $this.closest('form').on('reset', function () { + $span.text($span.attr('data-default')); + $rolesOptions.children('input[type=hidden]') + .val($span.attr('data-default-val')); + }); + } + + $this.on('mouseover', function () { + var $this = $(this); + $options.removeClass('roles-highlight'); + $this.addClass('roles-highlight'); + }).on('click', function () { + var $this = $(this); + var $rolesOptions = $this.closest('.roles-options'); + + // Update settings + set_role_settings($this.attr('data-id'), $this.attr('data-target-id')); + init_colours($this.attr('data-target-id').replace('advanced', '')); + + // Set selected setting + $rolesOptions.children('span') + .text($this.text()); + $rolesOptions.children('input[type=hidden]') + .val($this.attr('data-id')); + + // Trigger hiding of selection options + $('body').trigger('click'); + }); + }); +}; + +// Run onload functions for RolesDropdown and tooltips +$(function() { + // Enable tooltips + phpbb.enableTooltipsSelect('set-permissions', $('#set-permissions').attr('data-role-description'), 'role'); + + // Prepare dropdown + phpbb.prepareRolesDropdown(); + + // Reset role drop-down on modifying permissions in advanced tab + $('div.permissions-switch > a').on('click', function () { + $.each($('input[type=radio][name^="setting["]'), function () { + var $this = $(this); + $this.on('click', function () { + var $rolesOptions = $this.closest('fieldset.permissions').find('.roles-options'), + rolesSelect = $rolesOptions.find('select > option')[0]; + + // Set selected setting + $rolesOptions.children('span') + .text(rolesSelect.text); + $rolesOptions.children('input[type=hidden]') + .val(rolesSelect.value); + }); + }); + }); +}); + +})(jQuery); // Avoid conflicts with other libraries diff --git a/app.php b/app.php new file mode 100644 index 0000000..4873fb1 --- /dev/null +++ b/app.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); + +// Start session management +$user->session_begin(); +$auth->acl($user->data); +$user->setup('app'); + +/* @var $http_kernel \Symfony\Component\HttpKernel\HttpKernel */ +$http_kernel = $phpbb_container->get('http_kernel'); + +/* @var $symfony_request \phpbb\symfony_request */ +$symfony_request = $phpbb_container->get('symfony_request'); +$response = $http_kernel->handle($symfony_request); +$response->send(); +$http_kernel->terminate($symfony_request, $response); diff --git a/assets/cookieconsent/cookieconsent.min.css b/assets/cookieconsent/cookieconsent.min.css new file mode 100644 index 0000000..03c69fe --- /dev/null +++ b/assets/cookieconsent/cookieconsent.min.css @@ -0,0 +1,6 @@ +.cc-window{opacity:1;transition:opacity 1s ease}.cc-window.cc-invisible{opacity:0}.cc-animate.cc-revoke{transition:transform 1s ease}.cc-animate.cc-revoke.cc-top{transform:translateY(-2em)}.cc-animate.cc-revoke.cc-bottom{transform:translateY(2em)}.cc-animate.cc-revoke.cc-active.cc-bottom,.cc-animate.cc-revoke.cc-active.cc-top,.cc-revoke:hover{transform:translateY(0)}.cc-grower{max-height:0;overflow:hidden;transition:max-height 1s} +.cc-link,.cc-revoke:hover{text-decoration:underline}.cc-revoke,.cc-window{position:fixed;overflow:hidden;box-sizing:border-box;font-family:Helvetica,Calibri,Arial,sans-serif;font-size:16px;line-height:1.5em;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;z-index:9999}.cc-window.cc-static{position:static}.cc-window.cc-floating{padding:2em;max-width:24em;-ms-flex-direction:column;flex-direction:column}.cc-window.cc-banner{padding:1em 1.8em;width:100%;-ms-flex-direction:row;flex-direction:row}.cc-revoke{padding:.5em}.cc-header{font-size:18px;font-weight:700}.cc-btn,.cc-close,.cc-link,.cc-revoke{cursor:pointer}.cc-link{opacity:.8;display:inline-block;padding:.2em}.cc-link:hover{opacity:1}.cc-link:active,.cc-link:visited{color:initial}.cc-btn{display:block;padding:.4em .8em;font-size:.9em;font-weight:700;border-width:2px;border-style:solid;text-align:center;white-space:nowrap}.cc-banner .cc-btn:last-child{min-width:140px}.cc-highlight .cc-btn:first-child{background-color:transparent;border-color:transparent}.cc-highlight .cc-btn:first-child:focus,.cc-highlight .cc-btn:first-child:hover{background-color:transparent;text-decoration:underline}.cc-close{display:block;position:absolute;top:.5em;right:.5em;font-size:1.6em;opacity:.9;line-height:.75}.cc-close:focus,.cc-close:hover{opacity:1} +.cc-revoke.cc-top{top:0;left:3em;border-bottom-left-radius:.5em;border-bottom-right-radius:.5em}.cc-revoke.cc-bottom{bottom:0;left:3em;border-top-left-radius:.5em;border-top-right-radius:.5em}.cc-revoke.cc-left{left:3em;right:unset}.cc-revoke.cc-right{right:3em;left:unset}.cc-top{top:1em}.cc-left{left:1em}.cc-right{right:1em}.cc-bottom{bottom:1em}.cc-floating>.cc-link{margin-bottom:1em}.cc-floating .cc-message{display:block;margin-bottom:1em}.cc-window.cc-floating .cc-compliance{-ms-flex:1;flex:1}.cc-window.cc-banner{-ms-flex-align:center;align-items:center}.cc-banner.cc-top{left:0;right:0;top:0}.cc-banner.cc-bottom{left:0;right:0;bottom:0}.cc-banner .cc-message{-ms-flex:1;flex:1}.cc-compliance{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:justify;align-content:space-between}.cc-compliance>.cc-btn{-ms-flex:1;flex:1}.cc-btn+.cc-btn{margin-left:.5em} +@media print{.cc-revoke,.cc-window{display:none}}@media screen and (max-width:900px){.cc-btn{white-space:normal}}@media screen and (max-width:414px) and (orientation:portrait),screen and (max-width:736px) and (orientation:landscape){.cc-window.cc-top{top:0}.cc-window.cc-bottom{bottom:0}.cc-window.cc-banner,.cc-window.cc-left,.cc-window.cc-right{left:0;right:0}.cc-window.cc-banner{-ms-flex-direction:column;flex-direction:column}.cc-window.cc-banner .cc-compliance{-ms-flex:1;flex:1}.cc-window.cc-floating{max-width:none}.cc-window .cc-message{margin-bottom:1em}.cc-window.cc-banner{-ms-flex-align:unset;align-items:unset}} +.cc-floating.cc-theme-classic{padding:1.2em;border-radius:5px}.cc-floating.cc-type-info.cc-theme-classic .cc-compliance{text-align:center;display:inline;-ms-flex:none;flex:none}.cc-theme-classic .cc-btn{border-radius:5px}.cc-theme-classic .cc-btn:last-child{min-width:140px}.cc-floating.cc-type-info.cc-theme-classic .cc-btn{display:inline-block} +.cc-theme-edgeless.cc-window{padding:0}.cc-floating.cc-theme-edgeless .cc-message{margin:2em 2em 1.5em}.cc-banner.cc-theme-edgeless .cc-btn{margin:0;padding:.8em 1.8em;height:100%}.cc-banner.cc-theme-edgeless .cc-message{margin-left:1em}.cc-floating.cc-theme-edgeless .cc-btn+.cc-btn{margin-left:0} \ No newline at end of file diff --git a/assets/cookieconsent/cookieconsent.min.js b/assets/cookieconsent/cookieconsent.min.js new file mode 100644 index 0000000..8e44bdd --- /dev/null +++ b/assets/cookieconsent/cookieconsent.min.js @@ -0,0 +1 @@ +!function(e){if(!e.hasInitialised){var t={escapeRegExp:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},hasClass:function(e,t){var i=" ";return 1===e.nodeType&&(i+e.className+i).replace(/[\n\t]/g,i).indexOf(i+t+i)>=0},addClass:function(e,t){e.className+=" "+t},removeClass:function(e,t){var i=new RegExp("\\b"+this.escapeRegExp(t)+"\\b");e.className=e.className.replace(i,"")},interpolateString:function(e,t){var i=/{{([a-z][a-z0-9\-_]*)}}/gi;return e.replace(i,function(e){return t(arguments[1])||""})},getCookie:function(e){var t="; "+document.cookie,i=t.split("; "+e+"=");return 2!=i.length?void 0:i.pop().split(";").shift()},setCookie:function(e,t,i,n,o){var s=new Date;s.setDate(s.getDate()+(i||365));var r=[e+"="+t,"expires="+s.toUTCString(),"path="+(o||"/")];n&&r.push("domain="+n),document.cookie=r.join(";")},deepExtend:function(e,t){for(var i in t)t.hasOwnProperty(i)&&(i in e&&this.isPlainObject(e[i])&&this.isPlainObject(t[i])?this.deepExtend(e[i],t[i]):e[i]=t[i]);return e},throttle:function(e,t){var i=!1;return function(){i||(e.apply(this,arguments),i=!0,setTimeout(function(){i=!1},t))}},hash:function(e){var t,i,n,o=0;if(0===e.length)return o;for(t=0,n=e.length;t=128?"#000":"#fff"},getLuminance:function(e){var t=parseInt(this.normaliseHex(e),16),i=38,n=(t>>16)+i,o=(t>>8&255)+i,s=(255&t)+i,r=(16777216+65536*(n<255?n<1?0:n:255)+256*(o<255?o<1?0:o:255)+(s<255?s<1?0:s:255)).toString(16).slice(1);return"#"+r},isMobile:function(){return/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)},isPlainObject:function(e){return"object"==typeof e&&null!==e&&e.constructor==Object}};e.status={deny:"deny",allow:"allow",dismiss:"dismiss"},e.transitionEnd=function(){var e=document.createElement("div"),t={t:"transitionend",OT:"oTransitionEnd",msT:"MSTransitionEnd",MozT:"transitionend",WebkitT:"webkitTransitionEnd"};for(var i in t)if(t.hasOwnProperty(i)&&"undefined"!=typeof e.style[i+"ransition"])return t[i];return""}(),e.hasTransition=!!e.transitionEnd;var i=Object.keys(e.status).map(t.escapeRegExp);e.customStyles={},e.Popup=function(){function n(){this.initialise.apply(this,arguments)}function o(e){this.openingTimeout=null,t.removeClass(e,"cc-invisible")}function s(t){t.style.display="none",t.removeEventListener(e.transitionEnd,this.afterTransition),this.afterTransition=null}function r(){var t=this.options.onInitialise.bind(this);if(!window.navigator.cookieEnabled)return t(e.status.deny),!0;if(window.CookiesOK||window.navigator.CookiesOK)return t(e.status.allow),!0;var i=Object.keys(e.status),n=this.getStatus(),o=i.indexOf(n)>=0;return o&&t(n),o}function a(){var e=this.options.position.split("-"),t=[];return e.forEach(function(e){t.push("cc-"+e)}),t}function c(){var e=this.options,i="top"==e.position||"bottom"==e.position?"banner":"floating";t.isMobile()&&(i="floating");var n=["cc-"+i,"cc-type-"+e.type,"cc-theme-"+e.theme];e["static"]&&n.push("cc-static"),n.push.apply(n,a.call(this));p.call(this,this.options.palette);return this.customStyleSelector&&n.push(this.customStyleSelector),n}function l(){var e={},i=this.options;i.showLink||(i.elements.link="",i.elements.messagelink=i.elements.message),Object.keys(i.elements).forEach(function(n){e[n]=t.interpolateString(i.elements[n],function(e){var t=i.content[e];return e&&"string"==typeof t&&t.length?t:""})});var n=i.compliance[i.type];n||(n=i.compliance.info),e.compliance=t.interpolateString(n,function(t){return e[t]});var o=i.layouts[i.layout];return o||(o=i.layouts.basic),t.interpolateString(o,function(t){return e[t]})}function u(i){var n=this.options,o=document.createElement("div"),s=n.container&&1===n.container.nodeType?n.container:document.body;o.innerHTML=i;var r=o.children[0];return r.style.display="none",t.hasClass(r,"cc-window")&&e.hasTransition&&t.addClass(r,"cc-invisible"),this.onButtonClick=h.bind(this),r.addEventListener("click",this.onButtonClick),n.autoAttach&&(s.firstChild?s.insertBefore(r,s.firstChild):s.appendChild(r)),r}function h(n){var o=n.target;if(t.hasClass(o,"cc-btn")){var s=o.className.match(new RegExp("\\bcc-("+i.join("|")+")\\b")),r=s&&s[1]||!1;r&&(this.setStatus(r),this.close(!0))}t.hasClass(o,"cc-close")&&(this.setStatus(e.status.dismiss),this.close(!0)),t.hasClass(o,"cc-revoke")&&this.revokeChoice()}function p(e){var i=t.hash(JSON.stringify(e)),n="cc-color-override-"+i,o=t.isPlainObject(e);return this.customStyleSelector=o?n:null,o&&d(i,e,"."+n),o}function d(i,n,o){if(e.customStyles[i])return void++e.customStyles[i].references;var s={},r=n.popup,a=n.button,c=n.highlight;r&&(r.text=r.text?r.text:t.getContrast(r.background),r.link=r.link?r.link:r.text,s[o+".cc-window"]=["color: "+r.text,"background-color: "+r.background],s[o+".cc-revoke"]=["color: "+r.text,"background-color: "+r.background],s[o+" .cc-link,"+o+" .cc-link:active,"+o+" .cc-link:visited"]=["color: "+r.link],a&&(a.text=a.text?a.text:t.getContrast(a.background),a.border=a.border?a.border:"transparent",s[o+" .cc-btn"]=["color: "+a.text,"border-color: "+a.border,"background-color: "+a.background],"transparent"!=a.background&&(s[o+" .cc-btn:hover, "+o+" .cc-btn:focus"]=["background-color: "+v(a.background)]),c?(c.text=c.text?c.text:t.getContrast(c.background),c.border=c.border?c.border:"transparent",s[o+" .cc-highlight .cc-btn:first-child"]=["color: "+c.text,"border-color: "+c.border,"background-color: "+c.background]):s[o+" .cc-highlight .cc-btn:first-child"]=["color: "+r.text]));var l=document.createElement("style");document.head.appendChild(l),e.customStyles[i]={references:1,element:l.sheet};var u=-1;for(var h in s)s.hasOwnProperty(h)&&l.sheet.insertRule(h+"{"+s[h].join(";")+"}",++u)}function v(e){return e=t.normaliseHex(e),"000000"==e?"#222":t.getLuminance(e)}function f(i){if(t.isPlainObject(i)){var n=t.hash(JSON.stringify(i)),o=e.customStyles[n];if(o&&!--o.references){var s=o.element.ownerNode;s&&s.parentNode&&s.parentNode.removeChild(s),e.customStyles[n]=null}}}function m(e,t){for(var i=0,n=e.length;i=0&&(this.dismissTimeout=window.setTimeout(function(){t(e.status.dismiss)},Math.floor(i)));var n=this.options.dismissOnScroll;if("number"==typeof n&&n>=0){var o=function(i){window.pageYOffset>Math.floor(n)&&(t(e.status.dismiss),window.removeEventListener("scroll",o),this.onWindowScroll=null)};this.onWindowScroll=o,window.addEventListener("scroll",o)}}function y(){if("info"!=this.options.type&&(this.options.revokable=!0),t.isMobile()&&(this.options.animateRevokable=!1),this.options.revokable){var e=a.call(this);this.options.animateRevokable&&e.push("cc-animate"),this.customStyleSelector&&e.push(this.customStyleSelector);var i=this.options.revokeBtn.replace("{{classes}}",e.join(" "));this.revokeBtn=u.call(this,i);var n=this.revokeBtn;if(this.options.animateRevokable){var o=t.throttle(function(e){var i=!1,o=20,s=window.innerHeight-20;t.hasClass(n,"cc-top")&&e.clientYs&&(i=!0),i?t.hasClass(n,"cc-active")||t.addClass(n,"cc-active"):t.hasClass(n,"cc-active")&&t.removeClass(n,"cc-active")},200);this.onMouseMove=o,window.addEventListener("mousemove",o)}}}var g={enabled:!0,container:null,cookie:{name:"cookieconsent_status",path:"/",domain:"",expiryDays:365},onPopupOpen:function(){},onPopupClose:function(){},onInitialise:function(e){},onStatusChange:function(e,t){},onRevokeChoice:function(){},content:{header:"Cookies used on the website!",message:"This website uses cookies to ensure you get the best experience on our website.",dismiss:"Got it!",allow:"Allow cookies",deny:"Decline",link:"Learn more",href:"http://cookiesandyou.com",close:"❌"},elements:{header:'{{header}} ',message:'{{message}}',messagelink:'{{message}} {{link}}',dismiss:'{{dismiss}}',allow:'{{allow}}',deny:'{{deny}}',link:'{{link}}',close:'{{close}}'},window:'',revokeBtn:'
Cookie Policy
',compliance:{info:'
{{dismiss}}
',"opt-in":'
{{dismiss}}{{allow}}
',"opt-out":'
{{deny}}{{dismiss}}
'},type:"info",layouts:{basic:"{{messagelink}}{{compliance}}","basic-close":"{{messagelink}}{{compliance}}{{close}}","basic-header":"{{header}}{{message}}{{link}}{{compliance}}"},layout:"basic",position:"bottom",theme:"block","static":!1,palette:null,revokable:!1,animateRevokable:!0,showLink:!0,dismissOnScroll:!1,dismissOnTimeout:!1,autoOpen:!0,autoAttach:!0,whitelistPage:[],blacklistPage:[],overrideHTML:null};return n.prototype.initialise=function(e){this.options&&this.destroy(),t.deepExtend(this.options={},g),t.isPlainObject(e)&&t.deepExtend(this.options,e),r.call(this)&&(this.options.enabled=!1),m(this.options.blacklistPage,location.pathname)&&(this.options.enabled=!1),m(this.options.whitelistPage,location.pathname)&&(this.options.enabled=!0);var i=this.options.window.replace("{{classes}}",c.call(this).join(" ")).replace("{{children}}",l.call(this)),n=this.options.overrideHTML;if("string"==typeof n&&n.length&&(i=n),this.options["static"]){var o=u.call(this,'
'+i+"
");o.style.display="",this.element=o.firstChild,this.element.style.display="none",t.addClass(this.element,"cc-invisible")}else this.element=u.call(this,i);b.call(this),y.call(this),this.options.autoOpen&&this.autoOpen()},n.prototype.destroy=function(){this.onButtonClick&&this.element&&(this.element.removeEventListener("click",this.onButtonClick),this.onButtonClick=null),this.dismissTimeout&&(clearTimeout(this.dismissTimeout),this.dismissTimeout=null),this.onWindowScroll&&(window.removeEventListener("scroll",this.onWindowScroll),this.onWindowScroll=null),this.onMouseMove&&(window.removeEventListener("mousemove",this.onMouseMove),this.onMouseMove=null),this.element&&this.element.parentNode&&this.element.parentNode.removeChild(this.element),this.element=null,this.revokeBtn&&this.revokeBtn.parentNode&&this.revokeBtn.parentNode.removeChild(this.revokeBtn),this.revokeBtn=null,f(this.options.palette),this.options=null},n.prototype.open=function(t){if(this.element)return this.isOpen()||(e.hasTransition?this.fadeIn():this.element.style.display="",this.options.revokable&&this.toggleRevokeButton(),this.options.onPopupOpen.call(this)),this},n.prototype.close=function(t){if(this.element)return this.isOpen()&&(e.hasTransition?this.fadeOut():this.element.style.display="none",t&&this.options.revokable&&this.toggleRevokeButton(!0),this.options.onPopupClose.call(this)),this},n.prototype.fadeIn=function(){var i=this.element;if(e.hasTransition&&i&&(this.afterTransition&&s.call(this,i),t.hasClass(i,"cc-invisible"))){if(i.style.display="",this.options["static"]){var n=this.element.clientHeight;this.element.parentNode.style.maxHeight=n+"px"}var r=20;this.openingTimeout=setTimeout(o.bind(this,i),r)}},n.prototype.fadeOut=function(){var i=this.element;e.hasTransition&&i&&(this.openingTimeout&&(clearTimeout(this.openingTimeout),o.bind(this,i)),t.hasClass(i,"cc-invisible")||(this.options["static"]&&(this.element.parentNode.style.maxHeight=""),this.afterTransition=s.bind(this,i),i.addEventListener(e.transitionEnd,this.afterTransition),t.addClass(i,"cc-invisible")))},n.prototype.isOpen=function(){return this.element&&""==this.element.style.display&&(!e.hasTransition||!t.hasClass(this.element,"cc-invisible"))},n.prototype.toggleRevokeButton=function(e){this.revokeBtn&&(this.revokeBtn.style.display=e?"":"none")},n.prototype.revokeChoice=function(e){this.options.enabled=!0,this.clearStatus(),this.options.onRevokeChoice.call(this),e||this.autoOpen()},n.prototype.hasAnswered=function(t){return Object.keys(e.status).indexOf(this.getStatus())>=0},n.prototype.hasConsented=function(t){var i=this.getStatus();return i==e.status.allow||i==e.status.dismiss},n.prototype.autoOpen=function(e){!this.hasAnswered()&&this.options.enabled&&this.open()},n.prototype.setStatus=function(i){var n=this.options.cookie,o=t.getCookie(n.name),s=Object.keys(e.status).indexOf(o)>=0;Object.keys(e.status).indexOf(i)>=0?(t.setCookie(n.name,i,n.expiryDays,n.domain,n.path),this.options.onStatusChange.call(this,i,s)):this.clearStatus()},n.prototype.getStatus=function(){return t.getCookie(this.options.cookie.name)},n.prototype.clearStatus=function(){var e=this.options.cookie;t.setCookie(e.name,"",-1,e.domain,e.path)},n}(),e.Location=function(){function e(e){t.deepExtend(this.options={},s),t.isPlainObject(e)&&t.deepExtend(this.options,e),this.currentServiceIndex=-1}function i(e,t,i){var n,o=document.createElement("script");o.type="text/"+(e.type||"javascript"),o.src=e.src||e,o.async=!1,o.onreadystatechange=o.onload=function(){var e=o.readyState;clearTimeout(n),t.done||e&&!/loaded|complete/.test(e)||(t.done=!0,t(),o.onreadystatechange=o.onload=null)},document.body.appendChild(o),n=setTimeout(function(){t.done=!0,t(),o.onreadystatechange=o.onload=null},i)}function n(e,t,i,n,o){var s=new(window.XMLHttpRequest||window.ActiveXObject)("MSXML2.XMLHTTP.3.0");if(s.open(n?"POST":"GET",e,1),s.setRequestHeader("X-Requested-With","XMLHttpRequest"),s.setRequestHeader("Content-type","application/x-www-form-urlencoded"),Array.isArray(o))for(var r=0,a=o.length;r3&&t(s)}),s.send(n)}function o(e){return new Error("Error ["+(e.code||"UNKNOWN")+"]: "+e.error)}var s={timeout:5e3,services:["freegeoip","ipinfo","maxmind"],serviceDefinitions:{freegeoip:function(){return{url:"//freegeoip.net/json/?callback={callback}",isScript:!0,callback:function(e,t){try{var i=JSON.parse(t);return i.error?o(i):{code:i.country_code}}catch(n){return o({error:"Invalid response ("+n+")"})}}}},ipinfo:function(){return{url:"//ipinfo.io",headers:["Accept: application/json"],callback:function(e,t){try{var i=JSON.parse(t);return i.error?o(i):{code:i.country}}catch(n){return o({error:"Invalid response ("+n+")"})}}}},ipinfodb:function(e){return{url:"//api.ipinfodb.com/v3/ip-country/?key={api_key}&format=json&callback={callback}",isScript:!0,callback:function(e,t){try{var i=JSON.parse(t);return"ERROR"==i.statusCode?o({error:i.statusMessage}):{code:i.countryCode}}catch(n){return o({error:"Invalid response ("+n+")"})}}}},maxmind:function(){return{url:"//js.maxmind.com/js/apis/geoip2/v2.1/geoip2.js",isScript:!0,callback:function(e){return window.geoip2?void geoip2.country(function(t){try{e({code:t.country.iso_code})}catch(i){e(o(i))}},function(t){e(o(t))}):void e(new Error("Unexpected response format. The downloaded script should have exported `geoip2` to the global scope"))}}}}};return e.prototype.getNextService=function(){var e;do e=this.getServiceByIdx(++this.currentServiceIndex);while(this.currentServiceIndex=0,revokable:t.revokable.indexOf(e)>=0,explicitAction:t.explicitAction.indexOf(e)>=0}},e.prototype.applyLaw=function(e,t){var i=this.get(t);return i.hasLaw||(e.enabled=!1),this.options.regionalLaw&&(i.revokable&&(e.revokable=!0),i.explicitAction&&(e.dismissOnScroll=!1,e.dismissOnTimeout=!1)),e},e}(),e.initialise=function(t,i,n){var o=new e.Law(t.law);i||(i=function(){}),n||(n=function(){}),e.getCountryCode(t,function(n){delete t.law,delete t.location,n.code&&(t=o.applyLaw(t,n.code)),i(new e.Popup(t))},function(i){delete t.law,delete t.location,n(i,new e.Popup(t))})},e.getCountryCode=function(t,i,n){if(t.law&&t.law.countryCode)return void i({code:t.law.countryCode});if(t.location){var o=new e.Location(t.location);return void o.locate(function(e){i(e||{})},n)}i({})},e.utils=t,e.hasInitialised=!0,window.cookieconsent=e}}(window.cookieconsent||{}); \ No newline at end of file diff --git a/assets/css/font-awesome.min.css b/assets/css/font-awesome.min.css new file mode 100644 index 0000000..540440c --- /dev/null +++ b/assets/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/assets/fonts/FontAwesome.otf b/assets/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/assets/fonts/FontAwesome.otf differ diff --git a/assets/fonts/fontawesome-webfont.eot b/assets/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/assets/fonts/fontawesome-webfont.eot differ diff --git a/assets/fonts/fontawesome-webfont.svg b/assets/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/assets/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/fonts/fontawesome-webfont.ttf b/assets/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/assets/fonts/fontawesome-webfont.ttf differ diff --git a/assets/fonts/fontawesome-webfont.woff b/assets/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/assets/fonts/fontawesome-webfont.woff differ diff --git a/assets/fonts/fontawesome-webfont.woff2 b/assets/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/assets/fonts/fontawesome-webfont.woff2 differ diff --git a/assets/javascript/core.js b/assets/javascript/core.js new file mode 100644 index 0000000..5218a8c --- /dev/null +++ b/assets/javascript/core.js @@ -0,0 +1,1684 @@ +/* global bbfontstyle */ + +var phpbb = {}; +phpbb.alertTime = 100; + +(function($) { // Avoid conflicts with other libraries + +'use strict'; + +// define a couple constants for keydown functions. +var keymap = { + TAB: 9, + ENTER: 13, + ESC: 27 +}; + +var $dark = $('#darkenwrapper'); +var $loadingIndicator; +var phpbbAlertTimer = null; + +phpbb.isTouch = (window && typeof window.ontouchstart !== 'undefined'); + +// Add ajax pre-filter to prevent cross-domain script execution +$.ajaxPrefilter(function(s) { + if (s.crossDomain) { + s.contents.script = false; + } +}); + +/** + * Display a loading screen + * + * @returns {object} Returns loadingIndicator. + */ +phpbb.loadingIndicator = function() { + if (!$loadingIndicator) { + $loadingIndicator = $('
', { + 'id': 'loading_indicator', + 'class': 'loading_indicator' + }); + $loadingIndicator.appendTo('#page-footer'); + } + + if (!$loadingIndicator.is(':visible')) { + $loadingIndicator.fadeIn(phpbb.alertTime); + // Wait 60 seconds and display an error if nothing has been returned by then. + phpbb.clearLoadingTimeout(); + phpbbAlertTimer = setTimeout(function() { + phpbb.showTimeoutMessage(); + }, 60000); + } + + return $loadingIndicator; +}; + +/** + * Show timeout message + */ +phpbb.showTimeoutMessage = function () { + var $alert = $('#phpbb_alert'); + + if ($loadingIndicator.is(':visible')) { + phpbb.alert($alert.attr('data-l-err'), $alert.attr('data-l-timeout-processing-req')); + } +}; + +/** + * Clear loading alert timeout +*/ +phpbb.clearLoadingTimeout = function() { + if (phpbbAlertTimer !== null) { + clearTimeout(phpbbAlertTimer); + phpbbAlertTimer = null; + } +}; + + +/** +* Close popup alert after a specified delay +* +* @param {int} delay Delay in ms until darkenwrapper's click event is triggered +*/ +phpbb.closeDarkenWrapper = function(delay) { + phpbbAlertTimer = setTimeout(function() { + $('#darkenwrapper').trigger('click'); + }, delay); +}; + +/** + * Display a simple alert similar to JSs native alert(). + * + * You can only call one alert or confirm box at any one time. + * + * @param {string} title Title of the message, eg "Information" (HTML). + * @param {string} msg Message to display (HTML). + * + * @returns {object} Returns the div created. + */ +phpbb.alert = function(title, msg) { + var $alert = $('#phpbb_alert'); + $alert.find('.alert_title').html(title); + $alert.find('.alert_text').html(msg); + + $(document).on('keydown.phpbb.alert', function(e) { + if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { + phpbb.alert.close($alert, true); + e.preventDefault(); + e.stopPropagation(); + } + }); + phpbb.alert.open($alert); + + return $alert; +}; + +/** +* Handler for opening an alert box. +* +* @param {jQuery} $alert jQuery object. +*/ +phpbb.alert.open = function($alert) { + if (!$dark.is(':visible')) { + $dark.fadeIn(phpbb.alertTime); + } + + if ($loadingIndicator && $loadingIndicator.is(':visible')) { + $loadingIndicator.fadeOut(phpbb.alertTime, function() { + $dark.append($alert); + $alert.fadeIn(phpbb.alertTime); + }); + } else if ($dark.is(':visible')) { + $dark.append($alert); + $alert.fadeIn(phpbb.alertTime); + } else { + $dark.append($alert); + $alert.show(); + $dark.fadeIn(phpbb.alertTime); + } + + $alert.on('click', function(e) { + e.stopPropagation(); + }); + + $dark.one('click', function(e) { + phpbb.alert.close($alert, true); + e.preventDefault(); + e.stopPropagation(); + }); + + $alert.find('.alert_close').one('click', function(e) { + phpbb.alert.close($alert, true); + e.preventDefault(); + }); +}; + +/** +* Handler for closing an alert box. +* +* @param {jQuery} $alert jQuery object. +* @param {bool} fadedark Whether to remove dark background. +*/ +phpbb.alert.close = function($alert, fadedark) { + var $fade = (fadedark) ? $dark : $alert; + + $fade.fadeOut(phpbb.alertTime, function() { + $alert.hide(); + }); + + $alert.find('.alert_close').off('click'); + $(document).off('keydown.phpbb.alert'); +}; + +/** + * Display a simple yes / no box to the user. + * + * You can only call one alert or confirm box at any one time. + * + * @param {string} msg Message to display (HTML). + * @param {function} callback Callback. Bool param, whether the user pressed + * yes or no (or whatever their language is). + * @param {bool} fadedark Remove the dark background when done? Defaults + * to yes. + * + * @returns {object} Returns the div created. + */ +phpbb.confirm = function(msg, callback, fadedark) { + var $confirmDiv = $('#phpbb_confirm'); + $confirmDiv.find('.alert_text').html(msg); + fadedark = typeof fadedark !== 'undefined' ? fadedark : true; + + $(document).on('keydown.phpbb.alert', function(e) { + if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { + var name = (e.keyCode === keymap.ENTER) ? 'confirm' : 'cancel'; + + $('input[name="' + name + '"]').trigger('click'); + e.preventDefault(); + e.stopPropagation(); + } + }); + + $confirmDiv.find('input[type="button"]').one('click.phpbb.confirmbox', function(e) { + var confirmed = this.name === 'confirm'; + + callback(confirmed); + $confirmDiv.find('input[type="button"]').off('click.phpbb.confirmbox'); + phpbb.alert.close($confirmDiv, fadedark || !confirmed); + + e.preventDefault(); + e.stopPropagation(); + }); + + phpbb.alert.open($confirmDiv); + + return $confirmDiv; +}; + +/** + * Turn a querystring into an array. + * + * @argument {string} string The querystring to parse. + * @returns {object} The object created. + */ +phpbb.parseQuerystring = function(string) { + var params = {}, i, split; + + string = string.split('&'); + for (i = 0; i < string.length; i++) { + split = string[i].split('='); + params[split[0]] = decodeURIComponent(split[1]); + } + return params; +}; + + +/** + * Makes a link use AJAX instead of loading an entire page. + * + * This function will work for links (both standard links and links which + * invoke confirm_box) and forms. It will be called automatically for links + * and forms with the data-ajax attribute set, and will call the necessary + * callback. + * + * For more info, view the following page on the phpBB wiki: + * http://wiki.phpbb.com/JavaScript_Function.phpbb.ajaxify + * + * @param {object} options Options. + */ +phpbb.ajaxify = function(options) { + var $elements = $(options.selector), + refresh = options.refresh, + callback = options.callback, + overlay = (typeof options.overlay !== 'undefined') ? options.overlay : true, + isForm = $elements.is('form'), + isText = $elements.is('input[type="text"], textarea'), + eventName; + + if (isForm) { + eventName = 'submit'; + } else if (isText) { + eventName = 'keyup'; + } else { + eventName = 'click'; + } + + $elements.on(eventName, function(event) { + var action, method, data, submit, that = this, $this = $(this); + + if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') { + return; + } + + /** + * Handler for AJAX errors + */ + function errorHandler(jqXHR, textStatus, errorThrown) { + if (typeof console !== 'undefined' && console.log) { + console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown); + } + phpbb.clearLoadingTimeout(); + var responseText, errorText = false; + try { + responseText = JSON.parse(jqXHR.responseText); + responseText = responseText.message; + } catch (e) {} + if (typeof responseText === 'string' && responseText.length > 0) { + errorText = responseText; + } else if (typeof errorThrown === 'string' && errorThrown.length > 0) { + errorText = errorThrown; + } else { + errorText = $dark.attr('data-ajax-error-text-' + textStatus); + if (typeof errorText !== 'string' || !errorText.length) { + errorText = $dark.attr('data-ajax-error-text'); + } + } + phpbb.alert($dark.attr('data-ajax-error-title'), errorText); + } + + /** + * This is a private function used to handle the callbacks, refreshes + * and alert. It calls the callback, refreshes the page if necessary, and + * displays an alert to the user and removes it after an amount of time. + * + * It cannot be called from outside this function, and is purely here to + * avoid repetition of code. + * + * @param {object} res The object sent back by the server. + */ + function returnHandler(res) { + var alert; + + phpbb.clearLoadingTimeout(); + + // Is a confirmation required? + if (typeof res.S_CONFIRM_ACTION === 'undefined') { + // If a confirmation is not required, display an alert and call the + // callbacks. + if (typeof res.MESSAGE_TITLE !== 'undefined') { + alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); + } else { + $dark.fadeOut(phpbb.alertTime); + + if ($loadingIndicator) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + } + + if (typeof phpbb.ajaxCallbacks[callback] === 'function') { + phpbb.ajaxCallbacks[callback].call(that, res); + } + + // If the server says to refresh the page, check whether the page should + // be refreshed and refresh page after specified time if required. + if (res.REFRESH_DATA) { + if (typeof refresh === 'function') { + refresh = refresh(res.REFRESH_DATA.url); + } else if (typeof refresh !== 'boolean') { + refresh = false; + } + + phpbbAlertTimer = setTimeout(function() { + if (refresh) { + window.location = res.REFRESH_DATA.url; + } + + // Hide the alert even if we refresh the page, in case the user + // presses the back button. + $dark.fadeOut(phpbb.alertTime, function() { + if (typeof alert !== 'undefined') { + alert.hide(); + } + }); + }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds + } + } else { + // If confirmation is required, display a dialog to the user. + phpbb.confirm(res.MESSAGE_BODY, function(del) { + if (!del) { + return; + } + + phpbb.loadingIndicator(); + data = $('
' + res.S_HIDDEN_FIELDS + '
').serialize(); + $.ajax({ + url: res.S_CONFIRM_ACTION, + type: 'POST', + data: data + '&confirm=' + res.YES_VALUE + '&' + $('form', '#phpbb_confirm').serialize(), + success: returnHandler, + error: errorHandler + }); + }, false); + } + } + + // If the element is a form, POST must be used and some extra data must + // be taken from the form. + var runFilter = (typeof options.filter === 'function'); + data = {}; + + if (isForm) { + action = $this.attr('action').replace('&', '&'); + data = $this.serializeArray(); + method = $this.attr('method') || 'GET'; + + if ($this.find('input[type="submit"][data-clicked]')) { + submit = $this.find('input[type="submit"][data-clicked]'); + data.push({ + name: submit.attr('name'), + value: submit.val() + }); + } + } else if (isText) { + var name = $this.attr('data-name') || this.name; + action = $this.attr('data-url').replace('&', '&'); + data[name] = this.value; + method = 'POST'; + } else { + action = this.href; + data = null; + method = 'GET'; + } + + var sendRequest = function() { + var dataOverlay = $this.attr('data-overlay'); + if (overlay && (typeof dataOverlay === 'undefined' || dataOverlay === 'true')) { + phpbb.loadingIndicator(); + } + + var request = $.ajax({ + url: action, + type: method, + data: data, + success: returnHandler, + error: errorHandler, + cache: false + }); + + request.always(function() { + if ($loadingIndicator && $loadingIndicator.is(':visible')) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + }); + }; + + // If filter function returns false, cancel the AJAX functionality, + // and return true (meaning that the HTTP request will be sent normally). + if (runFilter && !options.filter.call(this, data, event, sendRequest)) { + return; + } + + sendRequest(); + event.preventDefault(); + }); + + if (isForm) { + $elements.find('input:submit').click(function () { + var $this = $(this); + + // Remove data-clicked attribute from any submit button of form + $this.parents('form:first').find('input:submit[data-clicked]').removeAttr('data-clicked'); + + $this.attr('data-clicked', 'true'); + }); + } + + return this; +}; + +phpbb.search = { + cache: { + data: [] + }, + tpl: [], + container: [] +}; + +/** + * Get cached search data. + * + * @param {string} id Search ID. + * @returns {bool|object} Cached data object. Returns false if no data exists. + */ +phpbb.search.cache.get = function(id) { + if (this.data[id]) { + return this.data[id]; + } + return false; +}; + +/** + * Set search cache data value. + * + * @param {string} id Search ID. + * @param {string} key Data key. + * @param {string} value Data value. + */ +phpbb.search.cache.set = function(id, key, value) { + if (!this.data[id]) { + this.data[id] = { results: [] }; + } + this.data[id][key] = value; +}; + +/** + * Cache search result. + * + * @param {string} id Search ID. + * @param {string} keyword Keyword. + * @param {Array} results Search results. + */ +phpbb.search.cache.setResults = function(id, keyword, results) { + this.data[id].results[keyword] = results; +}; + +/** + * Trim spaces from keyword and lower its case. + * + * @param {string} keyword Search keyword to clean. + * @returns {string} Cleaned string. + */ +phpbb.search.cleanKeyword = function(keyword) { + return $.trim(keyword).toLowerCase(); +}; + +/** + * Get clean version of search keyword. If textarea supports several keywords + * (one per line), it fetches the current keyword based on the caret position. + * + * @param {jQuery} $input Search input|textarea. + * @param {string} keyword Input|textarea value. + * @param {bool} multiline Whether textarea supports multiple search keywords. + * + * @returns string Clean string. + */ +phpbb.search.getKeyword = function($input, keyword, multiline) { + if (multiline) { + var line = phpbb.search.getKeywordLine($input); + keyword = keyword.split('\n').splice(line, 1); + } + return phpbb.search.cleanKeyword(keyword); +}; + +/** + * Get the textarea line number on which the keyword resides - for textareas + * that support multiple keywords (one per line). + * + * @param {jQuery} $textarea Search textarea. + * @returns {int} The line number. + */ +phpbb.search.getKeywordLine = function ($textarea) { + var selectionStart = $textarea.get(0).selectionStart; + return $textarea.val().substr(0, selectionStart).split('\n').length - 1; +}; + +/** + * Set the value on the input|textarea. If textarea supports multiple + * keywords, only the active keyword is replaced. + * + * @param {jQuery} $input Search input|textarea. + * @param {string} value Value to set. + * @param {bool} multiline Whether textarea supports multiple search keywords. + */ +phpbb.search.setValue = function($input, value, multiline) { + if (multiline) { + var line = phpbb.search.getKeywordLine($input), + lines = $input.val().split('\n'); + lines[line] = value; + value = lines.join('\n'); + } + $input.val(value); +}; + +/** + * Sets the onclick event to set the value on the input|textarea to the + * selected search result. + * + * @param {jQuery} $input Search input|textarea. + * @param {object} value Result object. + * @param {jQuery} $row Result element. + * @param {jQuery} $container jQuery object for the search container. + */ +phpbb.search.setValueOnClick = function($input, value, $row, $container) { + $row.click(function() { + phpbb.search.setValue($input, value.result, $input.attr('data-multiline')); + $container.hide(); + }); +}; + +/** + * Runs before the AJAX search request is sent and determines whether + * there is a need to contact the server. If there are cached results + * already, those are displayed instead. Executes the AJAX request function + * itself due to the need to use a timeout to limit the number of requests. + * + * @param {Array} data Data to be sent to the server. + * @param {object} event Onkeyup event object. + * @param {function} sendRequest Function to execute AJAX request. + * + * @returns {bool} Returns false. + */ +phpbb.search.filter = function(data, event, sendRequest) { + var $this = $(this), + dataName = ($this.attr('data-name') !== undefined) ? $this.attr('data-name') : $this.attr('name'), + minLength = parseInt($this.attr('data-min-length'), 10), + searchID = $this.attr('data-results'), + keyword = phpbb.search.getKeyword($this, data[dataName], $this.attr('data-multiline')), + cache = phpbb.search.cache.get(searchID), + proceed = true; + data[dataName] = keyword; + + if (cache.timeout) { + clearTimeout(cache.timeout); + } + + var timeout = setTimeout(function() { + // Check min length and existence of cache. + if (minLength > keyword.length) { + proceed = false; + } else if (cache.lastSearch) { + // Has the keyword actually changed? + if (cache.lastSearch === keyword) { + proceed = false; + } else { + // Do we already have results for this? + if (cache.results[keyword]) { + var response = { + keyword: keyword, + results: cache.results[keyword] + }; + phpbb.search.handleResponse(response, $this, true); + proceed = false; + } + + // If the previous search didn't yield results and the string only had characters added to it, + // then we won't bother sending a request. + if (keyword.indexOf(cache.lastSearch) === 0 && cache.results[cache.lastSearch].length === 0) { + phpbb.search.cache.set(searchID, 'lastSearch', keyword); + phpbb.search.cache.setResults(searchID, keyword, []); + proceed = false; + } + } + } + + if (proceed) { + sendRequest.call(this); + } + }, 350); + phpbb.search.cache.set(searchID, 'timeout', timeout); + + return false; +}; + +/** + * Handle search result response. + * + * @param {object} res Data received from server. + * @param {jQuery} $input Search input|textarea. + * @param {bool} fromCache Whether the results are from the cache. + * @param {function} callback Optional callback to run when assigning each search result. + */ +phpbb.search.handleResponse = function(res, $input, fromCache, callback) { + if (typeof res !== 'object') { + return; + } + + var searchID = $input.attr('data-results'), + $container = $(searchID); + + if (this.cache.get(searchID).callback) { + callback = this.cache.get(searchID).callback; + } else if (typeof callback === 'function') { + this.cache.set(searchID, 'callback', callback); + } + + if (!fromCache) { + this.cache.setResults(searchID, res.keyword, res.results); + } + + this.cache.set(searchID, 'lastSearch', res.keyword); + this.showResults(res.results, $input, $container, callback); +}; + +/** + * Show search results. + * + * @param {Array} results Search results. + * @param {jQuery} $input Search input|textarea. + * @param {jQuery} $container Search results container element. + * @param {function} callback Optional callback to run when assigning each search result. + */ +phpbb.search.showResults = function(results, $input, $container, callback) { + var $resultContainer = $('.search-results', $container); + this.clearResults($resultContainer); + + if (!results.length) { + $container.hide(); + return; + } + + var searchID = $container.attr('id'), + tpl, + row; + + if (!this.tpl[searchID]) { + tpl = $('.search-result-tpl', $container); + this.tpl[searchID] = tpl.clone().removeClass('search-result-tpl'); + tpl.remove(); + } + tpl = this.tpl[searchID]; + + $.each(results, function(i, item) { + row = tpl.clone(); + row.find('.search-result').html(item.display); + + if (typeof callback === 'function') { + callback.call(this, $input, item, row, $container); + } + row.appendTo($resultContainer).show(); + }); + $container.show(); +}; + +/** + * Clear search results. + * + * @param {jQuery} $container Search results container. + */ +phpbb.search.clearResults = function($container) { + $container.children(':not(.search-result-tpl)').remove(); +}; + +$('#phpbb').click(function() { + var $this = $(this); + + if (!$this.is('.live-search') && !$this.parents().is('.live-search')) { + $('.live-search').hide(); + } +}); + +phpbb.history = {}; + +/** +* Check whether a method in the native history object is supported. +* +* @param {string} fn Method name. +* @returns {bool} Returns true if the method is supported. +*/ +phpbb.history.isSupported = function(fn) { + return !(typeof history === 'undefined' || typeof history[fn] === 'undefined'); +}; + +/** +* Wrapper for the pushState and replaceState methods of the +* native history object. +* +* @param {string} mode Mode. Either push or replace. +* @param {string} url New URL. +* @param {string} [title] Optional page title. +* @param {object} [obj] Optional state object. +*/ +phpbb.history.alterUrl = function(mode, url, title, obj) { + var fn = mode + 'State'; + + if (!url || !phpbb.history.isSupported(fn)) { + return; + } + if (!title) { + title = document.title; + } + if (!obj) { + obj = null; + } + + history[fn](obj, title, url); +}; + +/** +* Wrapper for the native history.replaceState method. +* +* @param {string} url New URL. +* @param {string} [title] Optional page title. +* @param {object} [obj] Optional state object. +*/ +phpbb.history.replaceUrl = function(url, title, obj) { + phpbb.history.alterUrl('replace', url, title, obj); +}; + +/** +* Wrapper for the native history.pushState method. +* +* @param {string} url New URL. +* @param {string} [title] Optional page title. +* @param {object} [obj] Optional state object. +*/ +phpbb.history.pushUrl = function(url, title, obj) { + phpbb.history.alterUrl('push', url, title, obj); +}; + +/** +* Hide the optgroups that are not the selected timezone +* +* @param {bool} keepSelection Shall we keep the value selected, or shall the +* user be forced to repick one. +*/ +phpbb.timezoneSwitchDate = function(keepSelection) { + var $timezoneCopy = $('#timezone_copy'); + var $timezone = $('#timezone'); + var $tzDate = $('#tz_date'); + var $tzSelectDateSuggest = $('#tz_select_date_suggest'); + + if ($timezoneCopy.length === 0) { + // We make a backup of the original dropdown, so we can remove optgroups + // instead of setting display to none, because IE and chrome will not + // hide options inside of optgroups and selects via css + $timezone.clone() + .attr('id', 'timezone_copy') + .css('display', 'none') + .attr('name', 'tz_copy') + .insertAfter('#timezone'); + } else { + // Copy the content of our backup, so we can remove all unneeded options + $timezone.html($timezoneCopy.html()); + } + + if ($tzDate.val() !== '') { + $timezone.children('optgroup').remove(':not([data-tz-value="' + $tzDate.val() + '"])'); + } + + if ($tzDate.val() === $tzSelectDateSuggest.attr('data-suggested-tz')) { + $tzSelectDateSuggest.css('display', 'none'); + } else { + $tzSelectDateSuggest.css('display', 'inline'); + } + + var $tzOptions = $timezone.children('optgroup[data-tz-value="' + $tzDate.val() + '"]').children('option'); + + if ($tzOptions.length === 1) { + // If there is only one timezone for the selected date, we just select that automatically. + $tzOptions.prop('selected', true); + keepSelection = true; + } + + if (typeof keepSelection !== 'undefined' && !keepSelection) { + var $timezoneOptions = $timezone.find('optgroup option'); + if ($timezoneOptions.filter(':selected').length <= 0) { + $timezoneOptions.filter(':first').prop('selected', true); + } + } +}; + +/** +* Display the date/time select +*/ +phpbb.timezoneEnableDateSelection = function() { + $('#tz_select_date').css('display', 'block'); +}; + +/** +* Preselect a date/time or suggest one, if it is not picked. +* +* @param {bool} forceSelector Shall we select the suggestion? +*/ +phpbb.timezonePreselectSelect = function(forceSelector) { + + // The offset returned here is in minutes and negated. + var offset = (new Date()).getTimezoneOffset(); + var sign = '-'; + + if (offset < 0) { + sign = '+'; + offset = -offset; + } + + var minutes = offset % 60; + var hours = (offset - minutes) / 60; + + if (hours < 10) { + hours = '0' + hours.toString(); + } else { + hours = hours.toString(); + } + + if (minutes < 10) { + minutes = '0' + minutes.toString(); + } else { + minutes = minutes.toString(); + } + + var prefix = 'UTC' + sign + hours + ':' + minutes; + var prefixLength = prefix.length; + var selectorOptions = $('option', '#tz_date'); + var i; + + var $tzSelectDateSuggest = $('#tz_select_date_suggest'); + + for (i = 0; i < selectorOptions.length; ++i) { + var option = selectorOptions[i]; + + if (option.value.substring(0, prefixLength) === prefix) { + if ($('#tz_date').val() !== option.value && !forceSelector) { + // We do not select the option for the user, but notify him, + // that we would suggest a different setting. + phpbb.timezoneSwitchDate(true); + $tzSelectDateSuggest.css('display', 'inline'); + } else { + option.selected = true; + phpbb.timezoneSwitchDate(!forceSelector); + $tzSelectDateSuggest.css('display', 'none'); + } + + var suggestion = $tzSelectDateSuggest.attr('data-l-suggestion'); + + $tzSelectDateSuggest.attr('title', suggestion.replace('%s', option.innerHTML)); + $tzSelectDateSuggest.attr('value', suggestion.replace('%s', option.innerHTML.substring(0, 9))); + $tzSelectDateSuggest.attr('data-suggested-tz', option.innerHTML); + + // Found the suggestion, there cannot be more, so return from here. + return; + } + } +}; + +phpbb.ajaxCallbacks = {}; + +/** + * Adds an AJAX callback to be used by phpbb.ajaxify. + * + * See the phpbb.ajaxify comments for information on stuff like parameters. + * + * @param {string} id The name of the callback. + * @param {function} callback The callback to be called. + */ +phpbb.addAjaxCallback = function(id, callback) { + if (typeof callback === 'function') { + phpbb.ajaxCallbacks[id] = callback; + } + return this; +}; + +/** + * This callback handles live member searches. + */ +phpbb.addAjaxCallback('member_search', function(res) { + phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick')); +}); + +/** + * This callback alternates text - it replaces the current text with the text in + * the alt-text data attribute, and replaces the text in the attribute with the + * current text so that the process can be repeated. + */ +phpbb.addAjaxCallback('alt_text', function() { + var $anchor, + updateAll = $(this).data('update-all'), + altText; + + if (updateAll !== undefined && updateAll.length) { + $anchor = $(updateAll); + } else { + $anchor = $(this); + } + + $anchor.each(function() { + var $this = $(this); + altText = $this.attr('data-alt-text'); + $this.attr('data-alt-text', $.trim($this.text())); + $this.attr('title', altText); + $this.children('span').text(altText); + }); +}); + +/** + * This callback is based on the alt_text callback. + * + * It replaces the current text with the text in the alt-text data attribute, + * and replaces the text in the attribute with the current text so that the + * process can be repeated. + * Additionally it replaces the class of the link's parent + * and changes the link itself. + */ +phpbb.addAjaxCallback('toggle_link', function() { + var $anchor, + updateAll = $(this).data('update-all') , + toggleText, + toggleUrl, + toggleClass; + + if (updateAll !== undefined && updateAll.length) { + $anchor = $(updateAll); + } else { + $anchor = $(this); + } + + $anchor.each(function() { + var $this = $(this); + + // Toggle link url + toggleUrl = $this.attr('data-toggle-url'); + $this.attr('data-toggle-url', $this.attr('href')); + $this.attr('href', toggleUrl); + + // Toggle class of link parent + toggleClass = $this.attr('data-toggle-class'); + $this.attr('data-toggle-class', $this.children().attr('class')); + $this.children('.icon').attr('class', toggleClass); + + // Toggle link text + toggleText = $this.attr('data-toggle-text'); + $this.attr('data-toggle-text', $this.children('span').text()); + $this.attr('title', $.trim(toggleText)); + $this.children('span').text(toggleText); + }); +}); + +/** +* Automatically resize textarea +* +* This function automatically resizes textarea elements when user +* types text. +* +* @param {jQuery} $items jQuery object(s) to resize +* @param {object} [options] Optional parameter that adjusts default +* configuration. See configuration variable +* +* Optional parameters: +* minWindowHeight {number} Minimum browser window height when textareas are resized. Default = 500 +* minHeight {number} Minimum height of textarea. Default = 200 +* maxHeight {number} Maximum height of textarea. Default = 500 +* heightDiff {number} Minimum difference between window and textarea height. Default = 200 +* resizeCallback {function} Function to call after resizing textarea +* resetCallback {function} Function to call when resize has been canceled + +* Callback function format: function(item) {} +* this points to DOM object +* item is a jQuery object, same as this +*/ +phpbb.resizeTextArea = function($items, options) { + // Configuration + var configuration = { + minWindowHeight: 500, + minHeight: 200, + maxHeight: 500, + heightDiff: 200, + resizeCallback: function() {}, + resetCallback: function() {} + }; + + if (phpbb.isTouch) { + return; + } + + if (arguments.length > 1) { + configuration = $.extend(configuration, options); + } + + function resetAutoResize(item) { + var $item = $(item); + if ($item.hasClass('auto-resized')) { + $(item) + .css({ height: '', resize: '' }) + .removeClass('auto-resized'); + configuration.resetCallback.call(item, $item); + } + } + + function autoResize(item) { + function setHeight(height) { + height += parseInt($item.css('height'), 10) - $item.innerHeight(); + $item + .css({ height: height + 'px', resize: 'none' }) + .addClass('auto-resized'); + configuration.resizeCallback.call(item, $item); + } + + var windowHeight = $(window).height(); + + if (windowHeight < configuration.minWindowHeight) { + resetAutoResize(item); + return; + } + + var maxHeight = Math.min( + Math.max(windowHeight - configuration.heightDiff, configuration.minHeight), + configuration.maxHeight + ), + $item = $(item), + height = parseInt($item.innerHeight(), 10), + scrollHeight = (item.scrollHeight) ? item.scrollHeight : 0; + + if (height < 0) { + return; + } + + if (height > maxHeight) { + setHeight(maxHeight); + } else if (scrollHeight > (height + 5)) { + setHeight(Math.min(maxHeight, scrollHeight)); + } + } + + $items.on('focus change keyup', function() { + $(this).each(function() { + autoResize(this); + }); + }).change(); + + $(window).resize(function() { + $items.each(function() { + if ($(this).hasClass('auto-resized')) { + autoResize(this); + } + }); + }); +}; + +/** +* Check if cursor in textarea is currently inside a bbcode tag +* +* @param {object} textarea Textarea DOM object +* @param {Array} startTags List of start tags to look for +* For example, Array('[code]', '[code=') +* @param {Array} endTags List of end tags to look for +* For example, Array('[/code]') +* +* @returns {boolean} True if cursor is in bbcode tag +*/ +phpbb.inBBCodeTag = function(textarea, startTags, endTags) { + var start = textarea.selectionStart, + lastEnd = -1, + lastStart = -1, + i, index, value; + + if (typeof start !== 'number') { + return false; + } + + value = textarea.value.toLowerCase(); + + for (i = 0; i < startTags.length; i++) { + var tagLength = startTags[i].length; + if (start >= tagLength) { + index = value.lastIndexOf(startTags[i], start - tagLength); + lastStart = Math.max(lastStart, index); + } + } + if (lastStart === -1) { + return false; + } + + if (start > 0) { + for (i = 0; i < endTags.length; i++) { + index = value.lastIndexOf(endTags[i], start - 1); + lastEnd = Math.max(lastEnd, index); + } + } + + return (lastEnd < lastStart); +}; + + +/** +* Adjust textarea to manage code bbcode +* +* This function allows to use tab characters when typing code +* and keeps indentation of previous line of code when adding new +* line while typing code. +* +* Editor's functionality is changed only when cursor is between +* [code] and [/code] bbcode tags. +* +* @param {object} textarea Textarea DOM object to apply editor to +*/ +phpbb.applyCodeEditor = function(textarea) { + // list of allowed start and end bbcode code tags, in lower case + var startTags = ['[code]', '[code='], + startTagsEnd = ']', + endTags = ['[/code]']; + + if (!textarea || typeof textarea.selectionStart !== 'number') { + return; + } + + if ($(textarea).data('code-editor') === true) { + return; + } + + function inTag() { + return phpbb.inBBCodeTag(textarea, startTags, endTags); + } + + /** + * Get line of text before cursor + * + * @param {boolean} stripCodeStart If true, only part of line + * after [code] tag will be returned. + * + * @returns {string} Line of text + */ + function getLastLine(stripCodeStart) { + var start = textarea.selectionStart, + value = textarea.value, + index = value.lastIndexOf('\n', start - 1); + + value = value.substring(index + 1, start); + + if (stripCodeStart) { + for (var i = 0; i < startTags.length; i++) { + index = value.lastIndexOf(startTags[i]); + if (index >= 0) { + var tagLength = startTags[i].length; + + value = value.substring(index + tagLength); + if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) { + index = value.indexOf(startTagsEnd); + + if (index >= 0) { + value = value.substr(index + 1); + } + } + } + } + } + + return value; + } + + /** + * Append text at cursor position + * + * @param {string} text Text to append + */ + function appendText(text) { + var start = textarea.selectionStart, + end = textarea.selectionEnd, + value = textarea.value; + + textarea.value = value.substr(0, start) + text + value.substr(end); + textarea.selectionStart = textarea.selectionEnd = start + text.length; + } + + $(textarea).data('code-editor', true).on('keydown', function(event) { + var key = event.keyCode || event.which; + + // intercept tabs + if (key === keymap.TAB && + !event.ctrlKey && + !event.shiftKey && + !event.altKey && + !event.metaKey) { + if (inTag()) { + appendText('\t'); + event.preventDefault(); + return; + } + } + + // intercept new line characters + if (key === keymap.ENTER) { + if (inTag()) { + var lastLine = getLastLine(true), + code = '' + /^\s*/g.exec(lastLine); + + if (code.length > 0) { + appendText('\n' + code); + event.preventDefault(); + } + } + } + }); +}; + +/** + * Show drag and drop animation when textarea is present + * + * This function will enable the drag and drop animation for a specified + * textarea. + * + * @param {HTMLElement} textarea Textarea DOM object to apply editor to + */ +phpbb.showDragNDrop = function(textarea) { + if (!textarea) { + return; + } + + $('body').on('dragenter dragover', function () { + $(textarea).addClass('drag-n-drop'); + }).on('dragleave dragout dragend drop', function() { + $(textarea).removeClass('drag-n-drop'); + }); + $(textarea).on('dragenter dragover', function () { + $(textarea).addClass('drag-n-drop-highlight'); + }).on('dragleave dragout dragend drop', function() { + $(textarea).removeClass('drag-n-drop-highlight'); + }); +}; + +/** +* List of classes that toggle dropdown menu, +* list of classes that contain visible dropdown menu +* +* Add your own classes to strings with comma (probably you +* will never need to do that) +*/ +phpbb.dropdownHandles = '.dropdown-container.dropdown-visible .dropdown-toggle'; +phpbb.dropdownVisibleContainers = '.dropdown-container.dropdown-visible'; + +/** +* Dropdown toggle event handler +* This handler is used by phpBB.registerDropdown() and other functions +*/ +phpbb.toggleDropdown = function() { + var $this = $(this), + options = $this.data('dropdown-options'), + parent = options.parent, + visible = parent.hasClass('dropdown-visible'), + direction; + + if (!visible) { + // Hide other dropdown menus + $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); + + // Figure out direction of dropdown + direction = options.direction; + var verticalDirection = options.verticalDirection, + offset = $this.offset(); + + if (direction === 'auto') { + if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) { + direction = 'right'; + } else { + direction = 'left'; + } + } + parent.toggleClass(options.leftClass, direction === 'left') + .toggleClass(options.rightClass, direction === 'right'); + + if (verticalDirection === 'auto') { + var height = $(window).height(), + top = offset.top - $(window).scrollTop(); + + verticalDirection = (top < height * 0.7) ? 'down' : 'up'; + } + parent.toggleClass(options.upClass, verticalDirection === 'up') + .toggleClass(options.downClass, verticalDirection === 'down'); + } + + options.dropdown.toggle(); + parent.toggleClass(options.visibleClass, !visible) + .toggleClass('dropdown-visible', !visible); + + // Check dimensions when showing dropdown + // !visible because variable shows state of dropdown before it was toggled + if (!visible) { + var windowWidth = $(window).width(); + + options.dropdown.find('.dropdown-contents').each(function() { + var $this = $(this); + + $this.css({ + marginLeft: 0, + left: 0, + marginRight: 0, + maxWidth: (windowWidth - 4) + 'px' + }); + + var offset = $this.offset().left, + width = $this.outerWidth(true); + + if (offset < 2) { + $this.css('left', (2 - offset) + 'px'); + } else if ((offset + width + 2) > windowWidth) { + $this.css('margin-left', (windowWidth - offset - width - 2) + 'px'); + } + + // Check whether the vertical scrollbar is present. + $this.toggleClass('dropdown-nonscroll', this.scrollHeight === $this.innerHeight()); + + }); + var freeSpace = parent.offset().left - 4; + + if (direction === 'left') { + options.dropdown.css('margin-left', '-' + freeSpace + 'px'); + + // Try to position the notification dropdown correctly in RTL-responsive mode + if (options.dropdown.hasClass('dropdown-extended')) { + var contentWidth, + fullFreeSpace = freeSpace + parent.outerWidth(); + + options.dropdown.find('.dropdown-contents').each(function() { + contentWidth = parseInt($(this).outerWidth(), 10); + $(this).css({ marginLeft: 0, left: 0 }); + }); + + var maxOffset = Math.min(contentWidth, fullFreeSpace) + 'px'; + options.dropdown.css({ + width: maxOffset, + marginLeft: -maxOffset + }); + } + } else { + options.dropdown.css('margin-right', '-' + (windowWidth + freeSpace) + 'px'); + } + } + + // Prevent event propagation + if (arguments.length > 0) { + try { + var e = arguments[0]; + e.preventDefault(); + e.stopPropagation(); + } catch (error) { } + } + return false; +}; + +/** +* Toggle dropdown submenu +*/ +phpbb.toggleSubmenu = function(e) { + $(this).siblings('.dropdown-submenu').toggle(); + e.preventDefault(); +}; + +/** +* Register dropdown menu +* Shows/hides dropdown, decides which side to open to +* +* @param {jQuery} toggle Link that toggles dropdown. +* @param {jQuery} dropdown Dropdown menu. +* @param {Object} options List of options. Optional. +*/ +phpbb.registerDropdown = function(toggle, dropdown, options) { + var ops = { + parent: toggle.parent(), // Parent item to add classes to + direction: 'auto', // Direction of dropdown menu. Possible values: auto, left, right + verticalDirection: 'auto', // Vertical direction. Possible values: auto, up, down + visibleClass: 'visible', // Class to add to parent item when dropdown is visible + leftClass: 'dropdown-left', // Class to add to parent item when dropdown opens to left side + rightClass: 'dropdown-right', // Class to add to parent item when dropdown opens to right side + upClass: 'dropdown-up', // Class to add to parent item when dropdown opens above menu item + downClass: 'dropdown-down' // Class to add to parent item when dropdown opens below menu item + }; + if (options) { + ops = $.extend(ops, options); + } + ops.dropdown = dropdown; + + ops.parent.addClass('dropdown-container'); + toggle.addClass('dropdown-toggle'); + + toggle.data('dropdown-options', ops); + + toggle.click(phpbb.toggleDropdown); + $('.dropdown-toggle-submenu', ops.parent).click(phpbb.toggleSubmenu); +}; + +/** +* Get the HTML for a color palette table. +* +* @param {string} dir Palette direction - either v or h +* @param {int} width Palette cell width. +* @param {int} height Palette cell height. +*/ +phpbb.colorPalette = function(dir, width, height) { + var r, g, b, + numberList = new Array(6), + color = '', + html = ''; + + numberList[0] = '00'; + numberList[1] = '40'; + numberList[2] = '80'; + numberList[3] = 'BF'; + numberList[4] = 'FF'; + + var tableClass = (dir === 'h') ? 'horizontal-palette' : 'vertical-palette'; + html += ''; + + for (r = 0; r < 5; r++) { + if (dir === 'h') { + html += ''; + } + + for (g = 0; g < 5; g++) { + if (dir === 'v') { + html += ''; + } + + for (b = 0; b < 5; b++) { + color = '' + numberList[r] + numberList[g] + numberList[b]; + html += ''; + } + + if (dir === 'v') { + html += ''; + } + } + + if (dir === 'h') { + html += ''; + } + } + html += '
'; + html += '
'; + return html; +}; + +/** +* Register a color palette. +* +* @param {jQuery} el jQuery object for the palette container. +*/ +phpbb.registerPalette = function(el) { + var orientation = el.attr('data-orientation'), + height = el.attr('data-height'), + width = el.attr('data-width'), + target = el.attr('data-target'), + bbcode = el.attr('data-bbcode'); + + // Insert the palette HTML into the container. + el.html(phpbb.colorPalette(orientation, width, height)); + + // Add toggle control. + $('#color_palette_toggle').click(function(e) { + el.toggle(); + e.preventDefault(); + }); + + // Attach event handler when a palette cell is clicked. + $(el).on('click', 'a', function(e) { + var color = $(this).attr('data-color'); + + if (bbcode) { + bbfontstyle('[color=#' + color + ']', '[/color]'); + } else { + $(target).val(color); + } + e.preventDefault(); + }); +}; + +/** +* Set display of page element +* +* @param {string} id The ID of the element to change +* @param {int} action Set to 0 if element display should be toggled, -1 for +* hiding the element, and 1 for showing it. +* @param {string} type Display type that should be used, e.g. inline, block or +* other CSS "display" types +*/ +phpbb.toggleDisplay = function(id, action, type) { + if (!type) { + type = 'block'; + } + + var $element = $('#' + id); + + var display = $element.css('display'); + if (!action) { + action = (display === '' || display === type) ? -1 : 1; + } + $element.css('display', ((action === 1) ? type : 'none')); +}; + +/** +* Toggle additional settings based on the selected +* option of select element. +* +* @param {jQuery} el jQuery select element object. +*/ +phpbb.toggleSelectSettings = function(el) { + el.children().each(function() { + var $this = $(this), + $setting = $($this.data('toggle-setting')); + $setting.toggle($this.is(':selected')); + + // Disable any input elements that are not visible right now + if ($this.is(':selected')) { + $($this.data('toggle-setting') + ' input').prop('disabled', false); + } else { + $($this.data('toggle-setting') + ' input').prop('disabled', true); + } + }); +}; + +/** +* Get function from name. +* Based on http://stackoverflow.com/a/359910 +* +* @param {string} functionName Function to get. +* @returns function +*/ +phpbb.getFunctionByName = function (functionName) { + var namespaces = functionName.split('.'), + func = namespaces.pop(), + context = window; + + for (var i = 0; i < namespaces.length; i++) { + context = context[namespaces[i]]; + } + return context[func]; +}; + +/** +* Register page dropdowns. +*/ +phpbb.registerPageDropdowns = function() { + var $body = $('body'); + + $body.find('.dropdown-container').each(function() { + var $this = $(this), + $trigger = $this.find('.dropdown-trigger:first'), + $contents = $this.find('.dropdown'), + options = { + direction: 'auto', + verticalDirection: 'auto' + }, + data; + + if (!$trigger.length) { + data = $this.attr('data-dropdown-trigger'); + $trigger = data ? $this.children(data) : $this.children('a:first'); + } + + if (!$contents.length) { + data = $this.attr('data-dropdown-contents'); + $contents = data ? $this.children(data) : $this.children('div:first'); + } + + if (!$trigger.length || !$contents.length) { + return; + } + + if ($this.hasClass('dropdown-up')) { + options.verticalDirection = 'up'; + } + if ($this.hasClass('dropdown-down')) { + options.verticalDirection = 'down'; + } + if ($this.hasClass('dropdown-left')) { + options.direction = 'left'; + } + if ($this.hasClass('dropdown-right')) { + options.direction = 'right'; + } + + phpbb.registerDropdown($trigger, $contents, options); + }); + + // Hide active dropdowns when click event happens outside + $body.click(function(e) { + var $parents = $(e.target).parents(); + if (!$parents.is(phpbb.dropdownVisibleContainers)) { + $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); + } + }); +}; + +/** + * Handle avatars to be lazy loaded. + */ +phpbb.lazyLoadAvatars = function loadAvatars() { + $('.avatar[data-src]').each(function () { + var $avatar = $(this); + + $avatar + .attr('src', $avatar.data('src')) + .removeAttr('data-src'); + }); +}; + +$(window).on('load', phpbb.lazyLoadAvatars); + +/** +* Apply code editor to all textarea elements with data-bbcode attribute +*/ +$(function() { + $('textarea[data-bbcode]').each(function() { + phpbb.applyCodeEditor(this); + }); + + phpbb.registerPageDropdowns(); + + $('[data-orientation]').each(function() { + phpbb.registerPalette($(this)); + }); + + // Update browser history URL to point to specific post in viewtopic.php + // when using view=unread#unread link. + phpbb.history.replaceUrl($('#unread[data-url]').data('url')); + + // Hide settings that are not selected via select element. + $('select[data-togglable-settings]').each(function() { + var $this = $(this); + + $this.change(function() { + phpbb.toggleSelectSettings($this); + }); + phpbb.toggleSelectSettings($this); + }); +}); + +})(jQuery); // Avoid conflicts with other libraries diff --git a/assets/javascript/editor.js b/assets/javascript/editor.js new file mode 100644 index 0000000..23244f5 --- /dev/null +++ b/assets/javascript/editor.js @@ -0,0 +1,425 @@ +/** +* bbCode control by subBlue design [ www.subBlue.com ] +* Includes unixsafe colour palette selector by SHS` +*/ + +// Startup variables +var imageTag = false; +var theSelection = false; +var bbcodeEnabled = true; + +// Check for Browser & Platform for PC & IE specific bits +// More details from: http://www.mozilla.org/docs/web-developer/sniffer/browser_type.html +var clientPC = navigator.userAgent.toLowerCase(); // Get client info +var clientVer = parseInt(navigator.appVersion, 10); // Get browser version + +var is_ie = ((clientPC.indexOf('msie') !== -1) && (clientPC.indexOf('opera') === -1)); +var is_win = ((clientPC.indexOf('win') !== -1) || (clientPC.indexOf('16bit') !== -1)); +var baseHeight; + +/** +* Shows the help messages in the helpline window +*/ +function helpline(help) { + document.forms[form_name].helpbox.value = help_line[help]; +} + +/** +* Fix a bug involving the TextRange object. From +* http://www.frostjedi.com/terra/scripts/demo/caretBug.html +*/ +function initInsertions() { + var doc; + + if (document.forms[form_name]) { + doc = document; + } else { + doc = opener.document; + } + + var textarea = doc.forms[form_name].elements[text_name]; + + if (is_ie && typeof(baseHeight) !== 'number') { + textarea.focus(); + baseHeight = doc.selection.createRange().duplicate().boundingHeight; + + if (!document.forms[form_name]) { + document.body.focus(); + } + } +} + +/** +* bbstyle +*/ +function bbstyle(bbnumber) { + if (bbnumber !== -1) { + bbfontstyle(bbtags[bbnumber], bbtags[bbnumber+1]); + } else { + insert_text('[*]'); + document.forms[form_name].elements[text_name].focus(); + } +} + +/** +* Apply bbcodes +*/ +function bbfontstyle(bbopen, bbclose) { + theSelection = false; + + var textarea = document.forms[form_name].elements[text_name]; + + textarea.focus(); + + if ((clientVer >= 4) && is_ie && is_win) { + // Get text selection + theSelection = document.selection.createRange().text; + + if (theSelection) { + // Add tags around selection + document.selection.createRange().text = bbopen + theSelection + bbclose; + textarea.focus(); + theSelection = ''; + return; + } + } else if (textarea.selectionEnd && (textarea.selectionEnd - textarea.selectionStart > 0)) { + mozWrap(textarea, bbopen, bbclose); + textarea.focus(); + theSelection = ''; + return; + } + + //The new position for the cursor after adding the bbcode + var caret_pos = getCaretPosition(textarea).start; + var new_pos = caret_pos + bbopen.length; + + // Open tag + insert_text(bbopen + bbclose); + + // Center the cursor when we don't have a selection + // Gecko and proper browsers + if (!isNaN(textarea.selectionStart)) { + textarea.selectionStart = new_pos; + textarea.selectionEnd = new_pos; + } + // IE + else if (document.selection) { + var range = textarea.createTextRange(); + range.move("character", new_pos); + range.select(); + storeCaret(textarea); + } + + textarea.focus(); +} + +/** +* Insert text at position +*/ +function insert_text(text, spaces, popup) { + var textarea; + + if (!popup) { + textarea = document.forms[form_name].elements[text_name]; + } else { + textarea = opener.document.forms[form_name].elements[text_name]; + } + + if (spaces) { + text = ' ' + text + ' '; + } + + // Since IE9, IE also has textarea.selectionStart, but it still needs to be treated the old way. + // Therefore we simply add a !is_ie here until IE fixes the text-selection completely. + if (!isNaN(textarea.selectionStart) && !is_ie) { + var sel_start = textarea.selectionStart; + var sel_end = textarea.selectionEnd; + + mozWrap(textarea, text, ''); + textarea.selectionStart = sel_start + text.length; + textarea.selectionEnd = sel_end + text.length; + } else if (textarea.createTextRange && textarea.caretPos) { + if (baseHeight !== textarea.caretPos.boundingHeight) { + textarea.focus(); + storeCaret(textarea); + } + + var caret_pos = textarea.caretPos; + caret_pos.text = caret_pos.text.charAt(caret_pos.text.length - 1) === ' ' ? caret_pos.text + text + ' ' : caret_pos.text + text; + } else { + textarea.value = textarea.value + text; + } + + if (!popup) { + textarea.focus(); + } +} + +/** +* Add inline attachment at position +*/ +function attachInline(index, filename) { + insert_text('[attachment=' + index + ']' + filename + '[/attachment]'); + document.forms[form_name].elements[text_name].focus(); +} + +/** +* Add quote text to message +*/ +function addquote(post_id, username, l_wrote, attributes) { + var message_name = 'message_' + post_id; + var theSelection = ''; + var divarea = false; + var i; + + if (l_wrote === undefined) { + // Backwards compatibility + l_wrote = 'wrote'; + } + if (typeof attributes !== 'object') { + attributes = {}; + } + + if (document.all) { + divarea = document.all[message_name]; + } else { + divarea = document.getElementById(message_name); + } + + // Get text selection - not only the post content :( + // IE9 must use the document.selection method but has the *.getSelection so we just force no IE + if (window.getSelection && !is_ie && !window.opera) { + theSelection = window.getSelection().toString(); + } else if (document.getSelection && !is_ie) { + theSelection = document.getSelection(); + } else if (document.selection) { + theSelection = document.selection.createRange().text; + } + + if (theSelection === '' || typeof theSelection === 'undefined' || theSelection === null) { + if (divarea.innerHTML) { + theSelection = divarea.innerHTML.replace(/
/ig, '\n'); + theSelection = theSelection.replace(//ig, '\n'); + theSelection = theSelection.replace(/<\;/ig, '<'); + theSelection = theSelection.replace(/>\;/ig, '>'); + theSelection = theSelection.replace(/&\;/ig, '&'); + theSelection = theSelection.replace(/ \;/ig, ' '); + } else if (document.all) { + theSelection = divarea.innerText; + } else if (divarea.textContent) { + theSelection = divarea.textContent; + } else if (divarea.firstChild.nodeValue) { + theSelection = divarea.firstChild.nodeValue; + } + } + + if (theSelection) { + if (bbcodeEnabled) { + attributes.author = username; + insert_text(generateQuote(theSelection, attributes)); + } else { + insert_text(username + ' ' + l_wrote + ':' + '\n'); + var lines = split_lines(theSelection); + for (i = 0; i < lines.length; i++) { + insert_text('> ' + lines[i] + '\n'); + } + } + } +} + +/** +* Create a quote block for given text +* +* Possible attributes: +* - author: author's name (usually a username) +* - post_id: post_id of the post being quoted +* - user_id: user_id of the user being quoted +* - time: timestamp of the original message +* +* @param {!string} text Quote's text +* @param {!Object} attributes Quote's attributes +* @return {!string} Quote block to be used in a new post/text +*/ +function generateQuote(text, attributes) { + text = text.replace(/^\s+/, '').replace(/\s+$/, ''); + var quote = '[quote'; + if (attributes.author) { + // Add the author as the BBCode's default attribute + quote += '=' + formatAttributeValue(attributes.author); + delete attributes.author; + } + for (var name in attributes) { + if (attributes.hasOwnProperty(name)) { + var value = attributes[name]; + quote += ' ' + name + '=' + formatAttributeValue(value.toString()); + } + } + quote += ']'; + var newline = ((quote + text + '[/quote]').length > 80 || text.indexOf('\n') > -1) ? '\n' : ''; + quote += newline + text + newline + '[/quote]'; + + return quote; +} + +/** +* Format given string to be used as an attribute value +* +* Will return the string as-is if it can be used in a BBCode without quotes. Otherwise, +* it will use either single- or double- quotes depending on whichever requires less escaping. +* Quotes and backslashes are escaped with backslashes where necessary +* +* @param {!string} str Original string +* @return {!string} Same string if possible, escaped string within quotes otherwise +*/ +function formatAttributeValue(str) { + if (!/[ "'\\\]]/.test(str)) { + // Return as-is if it contains none of: space, ' " \ or ] + return str; + } + var singleQuoted = "'" + str.replace(/[\\']/g, '\\$&') + "'", + doubleQuoted = '"' + str.replace(/[\\"]/g, '\\$&') + '"'; + + return (singleQuoted.length < doubleQuoted.length) ? singleQuoted : doubleQuoted; +} + +function split_lines(text) { + var lines = text.split('\n'); + var splitLines = new Array(); + var j = 0; + var i; + + for(i = 0; i < lines.length; i++) { + if (lines[i].length <= 80) { + splitLines[j] = lines[i]; + j++; + } else { + var line = lines[i]; + var splitAt; + do { + splitAt = line.indexOf(' ', 80); + + if (splitAt === -1) { + splitLines[j] = line; + j++; + } else { + splitLines[j] = line.substring(0, splitAt); + line = line.substring(splitAt); + j++; + } + } + while(splitAt !== -1); + } + } + return splitLines; +} + +/** +* From http://www.massless.org/mozedit/ +*/ +function mozWrap(txtarea, open, close) { + var selLength = (typeof(txtarea.textLength) === 'undefined') ? txtarea.value.length : txtarea.textLength; + var selStart = txtarea.selectionStart; + var selEnd = txtarea.selectionEnd; + var scrollTop = txtarea.scrollTop; + + var s1 = (txtarea.value).substring(0,selStart); + var s2 = (txtarea.value).substring(selStart, selEnd); + var s3 = (txtarea.value).substring(selEnd, selLength); + + txtarea.value = s1 + open + s2 + close + s3; + txtarea.selectionStart = selStart + open.length; + txtarea.selectionEnd = selEnd + open.length; + txtarea.focus(); + txtarea.scrollTop = scrollTop; + + return; +} + +/** +* Insert at Caret position. Code from +* http://www.faqts.com/knowledge_base/view.phtml/aid/1052/fid/130 +*/ +function storeCaret(textEl) { + if (textEl.createTextRange && document.selection) { + textEl.caretPos = document.selection.createRange().duplicate(); + } +} + +/** +* Caret Position object +*/ +function caretPosition() { + var start = null; + var end = null; +} + +/** +* Get the caret position in an textarea +*/ +function getCaretPosition(txtarea) { + var caretPos = new caretPosition(); + + // simple Gecko/Opera way + if (txtarea.selectionStart || txtarea.selectionStart === 0) { + caretPos.start = txtarea.selectionStart; + caretPos.end = txtarea.selectionEnd; + } + // dirty and slow IE way + else if (document.selection) { + // get current selection + var range = document.selection.createRange(); + + // a new selection of the whole textarea + var range_all = document.body.createTextRange(); + range_all.moveToElementText(txtarea); + + // calculate selection start point by moving beginning of range_all to beginning of range + var sel_start; + for (sel_start = 0; range_all.compareEndPoints('StartToStart', range) < 0; sel_start++) { + range_all.moveStart('character', 1); + } + + txtarea.sel_start = sel_start; + + // we ignore the end value for IE, this is already dirty enough and we don't need it + caretPos.start = txtarea.sel_start; + caretPos.end = txtarea.sel_start; + } + + return caretPos; +} + +/** +* Allow to use tab character when typing code +* Keep indentation of last line of code when typing code +*/ +(function($) { + $(document).ready(function() { + var doc, textarea; + + // find textarea, make sure browser supports necessary functions + if (document.forms[form_name]) { + doc = document; + } else { + doc = opener.document; + } + + if (!doc.forms[form_name]) { + return; + } + + textarea = doc.forms[form_name].elements[text_name]; + + phpbb.applyCodeEditor(textarea); + if ($('#attach-panel').length) { + phpbb.showDragNDrop(textarea); + } + + $('textarea').on('keydown', function (e) { + if (e.which === 13 && (e.metaKey || e.ctrlKey)) { + $(this).closest('form').find(':submit').click(); + } + }); + }); +})(jQuery); + diff --git a/assets/javascript/installer.js b/assets/javascript/installer.js new file mode 100644 index 0000000..a11b76b --- /dev/null +++ b/assets/javascript/installer.js @@ -0,0 +1,615 @@ +/** + * Installer's AJAX frontend handler + */ + +(function($) { // Avoid conflicts with other libraries + 'use strict'; + + // Installer variables + var pollTimer = null; + var nextReadPosition = 0; + var progressBarTriggered = false; + var progressTimer = null; + var currentProgress = 0; + var refreshRequested = false; + var transmissionOver = false; + var statusCount = 0; + + // Template related variables + var $contentWrapper = $('.install-body').find('.main'); + + // Intercept form submits + interceptFormSubmit($('#install_install')); + + /** + * Creates an XHR object + * + * jQuery cannot be used as the response is streamed, and + * as of now, jQuery does not provide access to the response until + * the connection is not closed. + * + * @return XMLHttpRequest + */ + function createXhrObject() { + return new XMLHttpRequest(); + } + + /** + * Displays error, warning and log messages + * + * @param type + * @param messages + */ + function addMessage(type, messages) { + // Get message containers + var $errorContainer = $('#error-container'); + var $warningContainer = $('#warning-container'); + var $logContainer = $('#log-container'); + + var $title, $description, $msgElement, arraySize = messages.length; + for (var i = 0; i < arraySize; i++) { + $msgElement = $('
'); + $title = $(''); + $title.text(messages[i].title); + $msgElement.append($title); + + if (messages[i].hasOwnProperty('description')) { + $description = $('

'); + $description.html(messages[i].description); + $msgElement.append($description); + } + + switch (type) { + case 'error': + $msgElement.addClass('errorbox'); + $errorContainer.append($msgElement); + break; + case 'warning': + $msgElement.addClass('warningbox'); + $warningContainer.append($msgElement); + break; + case 'log': + $msgElement.addClass('log'); + $logContainer.prepend($msgElement); + $logContainer.addClass('show_log_container'); + break; + case 'success': + $msgElement.addClass('successbox'); + $errorContainer.prepend($msgElement); + break; + } + } + } + + /** + * Render a download box + */ + function addDownloadBox(downloadArray) + { + var $downloadContainer = $('#download-wrapper'); + var $downloadBox, $title, $content, $link; + + for (var i = 0; i < downloadArray.length; i++) { + $downloadBox = $('

'); + $downloadBox.addClass('download-box'); + + $title = $(''); + $title.text(downloadArray[i].title); + $downloadBox.append($title); + + if (downloadArray[i].hasOwnProperty('msg')) { + $content = $('

'); + $content.text(downloadArray[i].msg); + $downloadBox.append($content); + } + + $link = $(''); + $link.addClass('button1'); + $link.attr('href', downloadArray[i].href); + $link.text(downloadArray[i].download); + $downloadBox.append($link); + + $downloadContainer.append($downloadBox); + } + } + + /** + * Render update files' status + */ + function addUpdateFileStatus(fileStatus) + { + var $statusContainer = $('#file-status-wrapper'); + $statusContainer.html(fileStatus); + } + + /** + * Displays a form from the response + * + * @param formHtml + */ + function addForm(formHtml) { + var $formContainer = $('#form-wrapper'); + $formContainer.html(formHtml); + var $form = $('#install_install'); + interceptFormSubmit($form); + } + + /** + * Handles navigation status updates + * + * @param navObj + */ + function updateNavbarStatus(navObj) { + var navID, $stage, $stageListItem, $active; + $active = $('#activemenu'); + + if (navObj.hasOwnProperty('finished')) { + // This should be an Array + var navItems = navObj.finished; + + for (var i = 0; i < navItems.length; i++) { + navID = 'installer-stage-' + navItems[i]; + $stage = $('#' + navID); + $stageListItem = $stage.parent(); + + if ($active.length && $active.is($stageListItem)) { + $active.removeAttr('id'); + } + + $stage.addClass('completed'); + } + } + + if (navObj.hasOwnProperty('active')) { + navID = 'installer-stage-' + navObj.active; + $stage = $('#' + navID); + $stageListItem = $stage.parent(); + + if ($active.length && !$active.is($stageListItem)) { + $active.removeAttr('id'); + } + + $stageListItem.attr('id', 'activemenu'); + } + } + + /** + * Renders progress bar + * + * @param progressObject + */ + function setProgress(progressObject) { + var $statusText, $progressBar, $progressText, $progressFiller, $progressFillerText; + + if (progressObject.task_name.length) { + if (!progressBarTriggered) { + // Create progress bar + var $progressBarWrapper = $('#progress-bar-container'); + + // Create progress bar elements + $progressBar = $('

'); + $progressBar.attr('id', 'progress-bar'); + $progressText = $('

'); + $progressText.attr('id', 'progress-bar-text'); + $progressFiller = $('

'); + $progressFiller.attr('id', 'progress-bar-filler'); + $progressFillerText = $('

'); + $progressFillerText.attr('id', 'progress-bar-filler-text'); + + $statusText = $('

'); + $statusText.attr('id', 'progress-status-text'); + + $progressFiller.append($progressFillerText); + $progressBar.append($progressText); + $progressBar.append($progressFiller); + + $progressBarWrapper.append($statusText); + $progressBarWrapper.append($progressBar); + + $progressFillerText.css('width', $progressBar.width()); + + progressBarTriggered = true; + } else if (progressObject.hasOwnProperty('restart')) { + clearInterval(progressTimer); + + $progressFiller = $('#progress-bar-filler'); + $progressFillerText = $('#progress-bar-filler-text'); + $progressText = $('#progress-bar-text'); + $statusText = $('#progress-status-text'); + + $progressText.text('0%'); + $progressFillerText.text('0%'); + $progressFiller.css('width', '0%'); + + currentProgress = 0; + } else { + $statusText = $('#progress-status-text'); + } + + // Update progress bar + $statusText.text(progressObject.task_name + '…'); + incrementProgressBar(Math.round(progressObject.task_num / progressObject.task_count * 100)); + } + } + + // Set cookies + function setCookies(cookies) { + var cookie; + + for (var i = 0; i < cookies.length; i++) { + // Set cookie name and value + cookie = encodeURIComponent(cookies[i].name) + '=' + encodeURIComponent(cookies[i].value); + // Set path + cookie += '; path=/'; + document.cookie = cookie; + } + } + + // Redirects user + function redirect(url, use_ajax) { + if (use_ajax) { + resetPolling(); + + var xhReq = createXhrObject(); + xhReq.open('GET', url, true); + xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhReq.send(); + + startPolling(xhReq); + } else { + window.location.href = url; + } + } + + /** + * Parse messages from the response object + * + * @param messageJSON + */ + function parseMessage(messageJSON) { + $('#loading_indicator').css('display', 'none'); + var responseObject; + + try { + responseObject = JSON.parse(messageJSON); + } catch (err) { + if (window.console) { + console.log('Failed to parse JSON object\n\nMessage: ' + err.message + '\n\nServer Response: ' + messageJSON); + } else { + alert('Failed to parse JSON object\n\nMessage: ' + err.message + '\n\nServer Response: ' + messageJSON); + } + + resetPolling(); + return; + } + + // Parse object + if (responseObject.hasOwnProperty('errors')) { + addMessage('error', responseObject.errors); + } + + if (responseObject.hasOwnProperty('warnings')) { + addMessage('warning', responseObject.warnings); + } + + if (responseObject.hasOwnProperty('logs')) { + addMessage('log', responseObject.logs); + } + + if (responseObject.hasOwnProperty('success')) { + addMessage('success', responseObject.success); + } + + if (responseObject.hasOwnProperty('form')) { + addForm(responseObject.form); + } + + if (responseObject.hasOwnProperty('progress')) { + setProgress(responseObject.progress); + } + + if (responseObject.hasOwnProperty('download')) { + addDownloadBox(responseObject.download); + } + + if (responseObject.hasOwnProperty('file_status')) { + addUpdateFileStatus(responseObject.file_status); + } + + if (responseObject.hasOwnProperty('nav')) { + updateNavbarStatus(responseObject.nav); + } + + if (responseObject.hasOwnProperty('cookies')) { + setCookies(responseObject.cookies); + } + + if (responseObject.hasOwnProperty('refresh')) { + refreshRequested = true; + } + + if (responseObject.hasOwnProperty('redirect')) { + redirect(responseObject.redirect.url, responseObject.redirect.use_ajax); + } + + if (responseObject.hasOwnProperty('over')) { + if (responseObject.over) { + transmissionOver = true; + } + } + } + + /** + * Processes status data + * + * @param status + */ + function processTimeoutResponse(status) { + if (statusCount === 12) { // 1 minute hard cap + status = 'fail'; + } + + if (status === 'continue') { + refreshRequested = false; + doRefresh(); + } else if (status === 'running') { + statusCount++; + $('#loading_indicator').css('display', 'block'); + setTimeout(queryInstallerStatus, 5000); + } else { + $('#loading_indicator').css('display', 'none'); + addMessage('error', + [{ + title: installLang.title, + description: installLang.msg + }] + ); + } + } + + /** + * Queries the installer's status + */ + function queryInstallerStatus() { + var url = $(location).attr('pathname'); + var lookUp = 'install/app.php'; + var position = url.indexOf(lookUp); + + if (position === -1) { + lookUp = 'install'; + position = url.indexOf(lookUp); + + if (position === -1) { + return false; + } + } + + url = url.substring(0, position) + lookUp + '/installer/status'; + $.getJSON(url, function(data) { + processTimeoutResponse(data.status); + }); + } + + /** + * Process updates in streamed response + * + * @param xhReq XHR object + */ + function pollContent(xhReq) { + var messages = xhReq.responseText; + var msgSeparator = '}\n\n'; + var unprocessed, messageEndIndex, endOfMessageIndex, message; + + do { + unprocessed = messages.substring(nextReadPosition); + messageEndIndex = unprocessed.indexOf(msgSeparator); + + if (messageEndIndex !== -1) { + endOfMessageIndex = messageEndIndex + msgSeparator.length; + message = unprocessed.substring(0, endOfMessageIndex); + parseMessage($.trim(message)); + nextReadPosition += endOfMessageIndex; + } + } while (messageEndIndex !== -1); + + if (xhReq.readyState === 4) { + $('#loading_indicator').css('display', 'none'); + resetPolling(); + + var timeoutDetected = !transmissionOver; + + if (refreshRequested) { + refreshRequested = false; + doRefresh(); + } + + if (timeoutDetected) { + statusCount = 0; + queryInstallerStatus(); + } + } + } + + /** + * Animates the progress bar + * + * @param $progressText + * @param $progressFiller + * @param $progressFillerText + * @param progressLimit + */ + function incrementFiller($progressText, $progressFiller, $progressFillerText, progressLimit) { + if (currentProgress >= progressLimit || currentProgress >= 100) { + clearInterval(progressTimer); + return; + } + + var $progressBar = $('#progress-bar'); + + currentProgress++; + $progressFillerText.css('width', $progressBar.width()); + $progressFillerText.text(currentProgress + '%'); + $progressText.text(currentProgress + '%'); + $progressFiller.css('width', currentProgress + '%'); + } + + /** + * Wrapper function for progress bar rendering and animating + * + * @param progressLimit + */ + function incrementProgressBar(progressLimit) { + var $progressFiller = $('#progress-bar-filler'); + var $progressFillerText = $('#progress-bar-filler-text'); + var $progressText = $('#progress-bar-text'); + var progressStart = $progressFiller.width() / $progressFiller.offsetParent().width() * 100; + currentProgress = Math.floor(progressStart); + + clearInterval(progressTimer); + progressTimer = setInterval(function() { + incrementFiller($progressText, $progressFiller, $progressFillerText, progressLimit); + }, 10); + } + + /** + * Resets the polling timer + */ + function resetPolling() { + clearInterval(pollTimer); + nextReadPosition = 0; + } + + /** + * Sets up timer for processing the streamed HTTP response + * + * @param xhReq + */ + function startPolling(xhReq) { + resetPolling(); + transmissionOver = false; + pollTimer = setInterval(function () { + pollContent(xhReq); + }, 250); + } + + /** + * Refresh page + */ + function doRefresh() { + resetPolling(); + + var xhReq = createXhrObject(); + xhReq.open('GET', $(location).attr('pathname'), true); + xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhReq.send(); + + startPolling(xhReq); + } + + /** + * Renders the AJAX UI layout + */ + function setupAjaxLayout() { + progressBarTriggered = false; + + // Clear content + $contentWrapper.html(''); + + var $header = $('

'); + $header.attr('id', 'header-container'); + $contentWrapper.append($header); + + var $description = $('
'); + $description.attr('id', 'description-container'); + $contentWrapper.append($description); + + var $errorContainer = $('
'); + $errorContainer.attr('id', 'error-container'); + $contentWrapper.append($errorContainer); + + var $warningContainer = $('
'); + $warningContainer.attr('id', 'warning-container'); + $contentWrapper.append($warningContainer); + + var $progressContainer = $('
'); + $progressContainer.attr('id', 'progress-bar-container'); + $contentWrapper.append($progressContainer); + + var $logContainer = $('
'); + $logContainer.attr('id', 'log-container'); + $contentWrapper.append($logContainer); + + var $installerContentWrapper = $('
'); + $installerContentWrapper.attr('id', 'content-container'); + $contentWrapper.append($installerContentWrapper); + + var $installerDownloadWrapper = $('
'); + $installerDownloadWrapper.attr('id', 'download-wrapper'); + $installerContentWrapper.append($installerDownloadWrapper); + + var $updaterFileStatusWrapper = $('
'); + $updaterFileStatusWrapper.attr('id', 'file-status-wrapper'); + $installerContentWrapper.append($updaterFileStatusWrapper); + + var $formWrapper = $('
'); + $formWrapper.attr('id', 'form-wrapper'); + $installerContentWrapper.append($formWrapper); + + var $spinner = $('
'); + $spinner.attr('id', 'loading_indicator'); + $spinner.html(' '); + $contentWrapper.append($spinner); + } + + // Submits a form + function submitForm($form, $submitBtn) { + $form.css('display', 'none'); + + var xhReq = createXhrObject(); + xhReq.open('POST', $form.attr('action'), true); + xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhReq.send(getFormFields($form, $submitBtn)); + + // Disable language selector + $('#language_selector :input, label').css('display', 'none'); + + // Clear content + setupAjaxLayout(); + $('#loading_indicator').css('display', 'block'); + + startPolling(xhReq); + } + + /** + * Add submit button to the POST information + * + * @param $form + * @param $submitBtn + * + * @returns {*} + */ + function getFormFields($form, $submitBtn) { + var formData = $form.serialize(); + formData += ((formData.length) ? '&' : '') + encodeURIComponent($submitBtn.attr('name')) + '='; + formData += encodeURIComponent($submitBtn.attr('value')); + + return formData; + } + + /** + * Intercept form submit events and determine the submit button used + * + * @param $form + */ + function interceptFormSubmit($form) { + if (!$form.length) { + return; + } + + $form.find(':submit').bind('click', function (event) { + event.preventDefault(); + submitForm($form, $(this)); + }); + } +})(jQuery); // Avoid conflicts with other libraries diff --git a/assets/javascript/jquery.min.js b/assets/javascript/jquery.min.js new file mode 100644 index 0000000..e836475 --- /dev/null +++ b/assets/javascript/jquery.min.js @@ -0,0 +1,5 @@ +/*! jQuery v1.12.4 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="1.12.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(n.isPlainObject(c)||(b=n.isArray(c)))?(b?(b=!1,f=a&&n.isArray(a)?a:[]):f=a&&n.isPlainObject(a)?a:{},g[d]=n.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray||function(a){return"array"===n.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;try{if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(!l.ownFirst)for(b in a)return k.call(a,b);for(b in a);return void 0===b||k.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(b){b&&n.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(h)return h.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(f=a[b],b=a,a=f),n.isFunction(a)?(c=e.call(arguments,2),d=function(){return a.apply(b||this,c.concat(e.call(arguments)))},d.guid=a.guid=a.guid||n.guid++,d):void 0},now:function(){return+new Date},support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return n.inArray(a,b)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;e>b;b++)if(n.contains(d[b],this))return!0}));for(b=0;e>b;b++)n.find(a,d[b],c);return c=this.pushStack(e>1?n.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}if(f=d.getElementById(e[2]),f&&f.parentNode){if(f.id!==e[2])return A.find(a);this.length=1,this[0]=f}return this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b,c=n(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(n.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?n.inArray(this[0],n(a)):n.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return n.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||(e=n.uniqueSort(e)),D.test(a)&&(e=e.reverse())),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=!0,c||j.disable(),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.addEventListener?(d.removeEventListener("DOMContentLoaded",K),a.removeEventListener("load",K)):(d.detachEvent("onreadystatechange",K),a.detachEvent("onload",K))}function K(){(d.addEventListener||"load"===a.event.type||"complete"===d.readyState)&&(J(),n.ready())}n.ready.promise=function(b){if(!I)if(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll)a.setTimeout(n.ready);else if(d.addEventListener)d.addEventListener("DOMContentLoaded",K),a.addEventListener("load",K);else{d.attachEvent("onreadystatechange",K),a.attachEvent("onload",K);var c=!1;try{c=null==a.frameElement&&d.documentElement}catch(e){}c&&c.doScroll&&!function f(){if(!n.isReady){try{c.doScroll("left")}catch(b){return a.setTimeout(f,50)}J(),n.ready()}}()}return I.promise(b)},n.ready.promise();var L;for(L in n(l))break;l.ownFirst="0"===L,l.inlineBlockNeedsLayout=!1,n(function(){var a,b,c,e;c=d.getElementsByTagName("body")[0],c&&c.style&&(b=d.createElement("div"),e=d.createElement("div"),e.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(e).appendChild(b),"undefined"!=typeof b.style.zoom&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",l.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(e))}),function(){var a=d.createElement("div");l.deleteExpando=!0;try{delete a.test}catch(b){l.deleteExpando=!1}a=null}();var M=function(a){var b=n.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b},N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(O,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}n.data(a,b,c)}else c=void 0; +}return c}function Q(a){var b;for(b in a)if(("data"!==b||!n.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function R(a,b,d,e){if(M(a)){var f,g,h=n.expando,i=a.nodeType,j=i?n.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||n.guid++:h),j[k]||(j[k]=i?{}:{toJSON:n.noop}),"object"!=typeof b&&"function"!=typeof b||(e?j[k]=n.extend(j[k],b):j[k].data=n.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[n.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[n.camelCase(b)])):f=g,f}}function S(a,b,c){if(M(a)){var d,e,f=a.nodeType,g=f?n.cache:a,h=f?a[n.expando]:n.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){n.isArray(b)?b=b.concat(n.map(b,n.camelCase)):b in d?b=[b]:(b=n.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!Q(d):!n.isEmptyObject(d))return}(c||(delete g[h].data,Q(g[h])))&&(f?n.cleanData([a],!0):l.deleteExpando||g!=g.window?delete g[h]:g[h]=void 0)}}}n.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?n.cache[a[n.expando]]:a[n.expando],!!a&&!Q(a)},data:function(a,b,c){return R(a,b,c)},removeData:function(a,b){return S(a,b)},_data:function(a,b,c){return R(a,b,c,!0)},_removeData:function(a,b){return S(a,b,!0)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=n.data(f),1===f.nodeType&&!n._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));n._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){n.data(this,a)}):arguments.length>1?this.each(function(){n.data(this,a,b)}):f?P(f,a,n.data(f,a)):void 0},removeData:function(a){return this.each(function(){n.removeData(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=n._data(a,b),c&&(!d||n.isArray(c)?d=n._data(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return n._data(a,c)||n._data(a,c,{empty:n.Callbacks("once memory").add(function(){n._removeData(a,b+"queue"),n._removeData(a,c)})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},Z=/^(?:checkbox|radio)$/i,$=/<([\w:-]+)/,_=/^$|\/(?:java|ecma)script/i,aa=/^\s+/,ba="abbr|article|aside|audio|bdi|canvas|data|datalist|details|dialog|figcaption|figure|footer|header|hgroup|main|mark|meter|nav|output|picture|progress|section|summary|template|time|video";function ca(a){var b=ba.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}!function(){var a=d.createElement("div"),b=d.createDocumentFragment(),c=d.createElement("input");a.innerHTML="
a",l.leadingWhitespace=3===a.firstChild.nodeType,l.tbody=!a.getElementsByTagName("tbody").length,l.htmlSerialize=!!a.getElementsByTagName("link").length,l.html5Clone="<:nav>"!==d.createElement("nav").cloneNode(!0).outerHTML,c.type="checkbox",c.checked=!0,b.appendChild(c),l.appendChecked=c.checked,a.innerHTML="",l.noCloneChecked=!!a.cloneNode(!0).lastChild.defaultValue,b.appendChild(a),c=d.createElement("input"),c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),a.appendChild(c),l.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,l.noCloneEvent=!!a.addEventListener,a[n.expando]=1,l.attributes=!a.getAttribute(n.expando)}();var da={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:l.htmlSerialize?[0,"",""]:[1,"X
","
"]};da.optgroup=da.option,da.tbody=da.tfoot=da.colgroup=da.caption=da.thead,da.th=da.td;function ea(a,b){var c,d,e=0,f="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||n.nodeName(d,b)?f.push(d):n.merge(f,ea(d,b));return void 0===b||b&&n.nodeName(a,b)?n.merge([a],f):f}function fa(a,b){for(var c,d=0;null!=(c=a[d]);d++)n._data(c,"globalEval",!b||n._data(b[d],"globalEval"))}var ga=/<|&#?\w+;/,ha=/r;r++)if(g=a[r],g||0===g)if("object"===n.type(g))n.merge(q,g.nodeType?[g]:g);else if(ga.test(g)){i=i||p.appendChild(b.createElement("div")),j=($.exec(g)||["",""])[1].toLowerCase(),m=da[j]||da._default,i.innerHTML=m[1]+n.htmlPrefilter(g)+m[2],f=m[0];while(f--)i=i.lastChild;if(!l.leadingWhitespace&&aa.test(g)&&q.push(b.createTextNode(aa.exec(g)[0])),!l.tbody){g="table"!==j||ha.test(g)?""!==m[1]||ha.test(g)?0:i:i.firstChild,f=g&&g.childNodes.length;while(f--)n.nodeName(k=g.childNodes[f],"tbody")&&!k.childNodes.length&&g.removeChild(k)}n.merge(q,i.childNodes),i.textContent="";while(i.firstChild)i.removeChild(i.firstChild);i=p.lastChild}else q.push(b.createTextNode(g));i&&p.removeChild(i),l.appendChecked||n.grep(ea(q,"input"),ia),r=0;while(g=q[r++])if(d&&n.inArray(g,d)>-1)e&&e.push(g);else if(h=n.contains(g.ownerDocument,g),i=ea(p.appendChild(g),"script"),h&&fa(i),c){f=0;while(g=i[f++])_.test(g.type||"")&&c.push(g)}return i=null,p}!function(){var b,c,e=d.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(l[b]=c in a)||(e.setAttribute(c,"t"),l[b]=e.attributes[c].expando===!1);e=null}();var ka=/^(?:input|select|textarea)$/i,la=/^key/,ma=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,na=/^(?:focusinfocus|focusoutblur)$/,oa=/^([^.]*)(?:\.(.+)|)/;function pa(){return!0}function qa(){return!1}function ra(){try{return d.activeElement}catch(a){}}function sa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)sa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=qa;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=n.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return"undefined"==typeof n||a&&n.event.triggered===a.type?void 0:n.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(G)||[""],h=b.length;while(h--)f=oa.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=n.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=n.event.special[o]||{},l=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},i),(m=g[o])||(m=g[o]=[],m.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,l):m.push(l),n.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=n.hasData(a)&&n._data(a);if(r&&(k=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=oa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=m.length;while(f--)g=m[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(m.splice(f,1),g.selector&&m.delegateCount--,l.remove&&l.remove.call(a,g));i&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(k)&&(delete r.handle,n._removeData(a,"events"))}},trigger:function(b,c,e,f){var g,h,i,j,l,m,o,p=[e||d],q=k.call(b,"type")?b.type:b,r=k.call(b,"namespace")?b.namespace.split("."):[];if(i=m=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!na.test(q+n.event.triggered)&&(q.indexOf(".")>-1&&(r=q.split("."),q=r.shift(),r.sort()),h=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=r.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:n.makeArray(c,[b]),l=n.event.special[q]||{},f||!l.trigger||l.trigger.apply(e,c)!==!1)){if(!f&&!l.noBubble&&!n.isWindow(e)){for(j=l.delegateType||q,na.test(j+q)||(i=i.parentNode);i;i=i.parentNode)p.push(i),m=i;m===(e.ownerDocument||d)&&p.push(m.defaultView||m.parentWindow||a)}o=0;while((i=p[o++])&&!b.isPropagationStopped())b.type=o>1?j:l.bindType||q,g=(n._data(i,"events")||{})[b.type]&&n._data(i,"handle"),g&&g.apply(i,c),g=h&&i[h],g&&g.apply&&M(i)&&(b.result=g.apply(i,c),b.result===!1&&b.preventDefault());if(b.type=q,!f&&!b.isDefaultPrevented()&&(!l._default||l._default.apply(p.pop(),c)===!1)&&M(e)&&h&&e[q]&&!n.isWindow(e)){m=e[h],m&&(e[h]=null),n.event.triggered=q;try{e[q]()}catch(s){}n.event.triggered=void 0,m&&(e[h]=m)}return b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(n._data(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]","i"),va=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,wa=/\s*$/g,Aa=ca(d),Ba=Aa.appendChild(d.createElement("div"));function Ca(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function Da(a){return a.type=(null!==n.find.attr(a,"type"))+"/"+a.type,a}function Ea(a){var b=ya.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Fa(a,b){if(1===b.nodeType&&n.hasData(a)){var c,d,e,f=n._data(a),g=n._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)n.event.add(b,c,h[c][d])}g.data&&(g.data=n.extend({},g.data))}}function Ga(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!l.noCloneEvent&&b[n.expando]){e=n._data(b);for(d in e.events)n.removeEvent(b,d,e.handle);b.removeAttribute(n.expando)}"script"===c&&b.text!==a.text?(Da(b).text=a.text,Ea(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),l.html5Clone&&a.innerHTML&&!n.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&Z.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:"input"!==c&&"textarea"!==c||(b.defaultValue=a.defaultValue)}}function Ha(a,b,c,d){b=f.apply([],b);var e,g,h,i,j,k,m=0,o=a.length,p=o-1,q=b[0],r=n.isFunction(q);if(r||o>1&&"string"==typeof q&&!l.checkClone&&xa.test(q))return a.each(function(e){var f=a.eq(e);r&&(b[0]=q.call(this,e,f.html())),Ha(f,b,c,d)});if(o&&(k=ja(b,a[0].ownerDocument,!1,a,d),e=k.firstChild,1===k.childNodes.length&&(k=e),e||d)){for(i=n.map(ea(k,"script"),Da),h=i.length;o>m;m++)g=k,m!==p&&(g=n.clone(g,!0,!0),h&&n.merge(i,ea(g,"script"))),c.call(a[m],g,m);if(h)for(j=i[i.length-1].ownerDocument,n.map(i,Ea),m=0;h>m;m++)g=i[m],_.test(g.type||"")&&!n._data(g,"globalEval")&&n.contains(j,g)&&(g.src?n._evalUrl&&n._evalUrl(g.src):n.globalEval((g.text||g.textContent||g.innerHTML||"").replace(za,"")));k=e=null}return a}function Ia(a,b,c){for(var d,e=b?n.filter(b,a):a,f=0;null!=(d=e[f]);f++)c||1!==d.nodeType||n.cleanData(ea(d)),d.parentNode&&(c&&n.contains(d.ownerDocument,d)&&fa(ea(d,"script")),d.parentNode.removeChild(d));return a}n.extend({htmlPrefilter:function(a){return a.replace(va,"<$1>")},clone:function(a,b,c){var d,e,f,g,h,i=n.contains(a.ownerDocument,a);if(l.html5Clone||n.isXMLDoc(a)||!ua.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(Ba.innerHTML=a.outerHTML,Ba.removeChild(f=Ba.firstChild)),!(l.noCloneEvent&&l.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(d=ea(f),h=ea(a),g=0;null!=(e=h[g]);++g)d[g]&&Ga(e,d[g]);if(b)if(c)for(h=h||ea(a),d=d||ea(f),g=0;null!=(e=h[g]);g++)Fa(e,d[g]);else Fa(a,f);return d=ea(f,"script"),d.length>0&&fa(d,!i&&ea(a,"script")),d=h=e=null,f},cleanData:function(a,b){for(var d,e,f,g,h=0,i=n.expando,j=n.cache,k=l.attributes,m=n.event.special;null!=(d=a[h]);h++)if((b||M(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)m[e]?n.event.remove(d,e):n.removeEvent(d,e,g.handle);j[f]&&(delete j[f],k||"undefined"==typeof d.removeAttribute?d[i]=void 0:d.removeAttribute(i),c.push(f))}}}),n.fn.extend({domManip:Ha,detach:function(a){return Ia(this,a,!0)},remove:function(a){return Ia(this,a)},text:function(a){return Y(this,function(a){return void 0===a?n.text(this):this.empty().append((this[0]&&this[0].ownerDocument||d).createTextNode(a))},null,a,arguments.length)},append:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.appendChild(a)}})},prepend:function(){return Ha(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Ca(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ha(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&n.cleanData(ea(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&n.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return Y(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(ta,""):void 0;if("string"==typeof a&&!wa.test(a)&&(l.htmlSerialize||!ua.test(a))&&(l.leadingWhitespace||!aa.test(a))&&!da[($.exec(a)||["",""])[1].toLowerCase()]){a=n.htmlPrefilter(a);try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ea(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=[];return Ha(this,arguments,function(b){var c=this.parentNode;n.inArray(this,a)<0&&(n.cleanData(ea(this)),c&&c.replaceChild(b,this))},a)}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=0,e=[],f=n(a),h=f.length-1;h>=d;d++)c=d===h?this:this.clone(!0),n(f[d])[b](c),g.apply(e,c.get());return this.pushStack(e)}});var Ja,Ka={HTML:"block",BODY:"block"};function La(a,b){var c=n(b.createElement(a)).appendTo(b.body),d=n.css(c[0],"display");return c.detach(),d}function Ma(a){var b=d,c=Ka[a];return c||(c=La(a,b),"none"!==c&&c||(Ja=(Ja||n("',l=r.firstChild,i.appendChild(l),o.addEvent(l,"load",function(){var i;try{i=l.contentWindow.document||l.contentDocument||window.frames[l.id].document,/^4(0[0-9]|1[0-7]|2[2346])\s/.test(i.title)?u=i.title.replace(/^(\d+).*$/,"$1"):(u=200,c=t.trim(i.body.innerHTML),v.trigger({type:"progress",loaded:c.length,total:c.length}),x&&v.trigger({type:"uploadprogress",loaded:x.size||1025,total:x.size||1025}))}catch(r){if(!n.hasSameOrigin(d.url))return e.call(v,function(){v.trigger("error")}),void 0;u=404}e.call(v,function(){v.trigger("load")})},v.uid)}var f,p,g,x,v=this,w=v.getRuntime();if(u=c=null,m instanceof s&&m.hasBlob()){if(x=m.getBlob(),f=x.uid,g=i.get(f),p=i.get(f+"_form"),!p)throw new r.DOMException(r.DOMException.NOT_FOUND_ERR)}else f=t.guid("uid_"),p=document.createElement("form"),p.setAttribute("id",f+"_form"),p.setAttribute("method",d.method),p.setAttribute("enctype","multipart/form-data"),p.setAttribute("encoding","multipart/form-data"),w.getShimContainer().appendChild(p);p.setAttribute("target",f+"_iframe"),m instanceof s&&m.each(function(e,i){if(e instanceof a)g&&g.setAttribute("name",i);else{var n=document.createElement("input");t.extend(n,{type:"hidden",name:i,value:e}),g?p.insertBefore(n,g):p.appendChild(n)}}),p.setAttribute("action",d.url),h(),p.submit(),v.trigger("loadstart")},getStatus:function(){return u},getResponse:function(e){if("json"===e&&"string"===t.typeOf(c)&&window.JSON)try{return JSON.parse(c.replace(/^\s*]*>/,"").replace(/<\/pre>\s*$/,""))}catch(i){return null}return c},abort:function(){var t=this;l&&l.contentWindow&&(l.contentWindow.stop?l.contentWindow.stop():l.contentWindow.document.execCommand?l.contentWindow.document.execCommand("Stop"):l.src="about:blank"),e.call(this,function(){t.dispatchEvent("abort")})},destroy:function(){this.getRuntime().getShim().removeInstance(this.uid)}})}return e.XMLHttpRequest=u}),n("moxie/runtime/html4/image/Image",["moxie/runtime/html4/Runtime","moxie/runtime/html5/image/Image"],function(e,t){return e.Image=t}),a(["moxie/core/utils/Basic","moxie/core/utils/Encode","moxie/core/utils/Env","moxie/core/Exceptions","moxie/core/utils/Dom","moxie/core/EventTarget","moxie/runtime/Runtime","moxie/runtime/RuntimeClient","moxie/file/Blob","moxie/core/I18n","moxie/core/utils/Mime","moxie/file/FileInput","moxie/file/File","moxie/file/FileDrop","moxie/file/FileReader","moxie/core/utils/Url","moxie/runtime/RuntimeTarget","moxie/xhr/FormData","moxie/xhr/XMLHttpRequest","moxie/image/Image","moxie/core/utils/Events","moxie/runtime/html5/image/ResizerCanvas"])}(this)}); +/** + * Plupload - multi-runtime File Uploader + * v2.3.6 + * + * Copyright 2013, Moxiecode Systems AB + * Released under GPL License. + * + * License: http://www.plupload.com/license + * Contributing: http://www.plupload.com/contributing + * + * Date: 2017-11-03 + */ +!function(e,t){var i=function(){var e={};return t.apply(e,arguments),e.plupload};"function"==typeof define&&define.amd?define("plupload",["./moxie"],i):"object"==typeof module&&module.exports?module.exports=i(require("./moxie")):e.plupload=i(e.moxie)}(this||window,function(e){!function(e,t,i){function n(e){function t(e,t,i){var r={chunks:"slice_blob",jpgresize:"send_binary_string",pngresize:"send_binary_string",progress:"report_upload_progress",multi_selection:"select_multiple",dragdrop:"drag_and_drop",drop_element:"drag_and_drop",headers:"send_custom_headers",urlstream_upload:"send_binary_string",canSendBinary:"send_binary",triggerDialog:"summon_file_dialog"};r[e]?n[r[e]]=t:i||(n[e]=t)}var i=e.required_features,n={};return"string"==typeof i?l.each(i.split(/\s*,\s*/),function(e){t(e,!0)}):"object"==typeof i?l.each(i,function(e,i){t(i,e)}):i===!0&&(e.chunk_size&&e.chunk_size>0&&(n.slice_blob=!0),l.isEmptyObj(e.resize)&&e.multipart!==!1||(n.send_binary_string=!0),e.http_method&&(n.use_http_method=e.http_method),l.each(e,function(e,i){t(i,!!e,!0)})),n}var r=window.setTimeout,s={},a=t.core.utils,o=t.runtime.Runtime,l={VERSION:"2.3.6",STOPPED:1,STARTED:2,QUEUED:1,UPLOADING:2,FAILED:4,DONE:5,GENERIC_ERROR:-100,HTTP_ERROR:-200,IO_ERROR:-300,SECURITY_ERROR:-400,INIT_ERROR:-500,FILE_SIZE_ERROR:-600,FILE_EXTENSION_ERROR:-601,FILE_DUPLICATE_ERROR:-602,IMAGE_FORMAT_ERROR:-700,MEMORY_ERROR:-701,IMAGE_DIMENSIONS_ERROR:-702,moxie:t,mimeTypes:a.Mime.mimes,ua:a.Env,typeOf:a.Basic.typeOf,extend:a.Basic.extend,guid:a.Basic.guid,getAll:function(e){var t,i=[];"array"!==l.typeOf(e)&&(e=[e]);for(var n=e.length;n--;)t=l.get(e[n]),t&&i.push(t);return i.length?i:null},get:a.Dom.get,each:a.Basic.each,getPos:a.Dom.getPos,getSize:a.Dom.getSize,xmlEncode:function(e){var t={"<":"lt",">":"gt","&":"amp",'"':"quot","'":"#39"},i=/[<>&\"\']/g;return e?(""+e).replace(i,function(e){return t[e]?"&"+t[e]+";":e}):e},toArray:a.Basic.toArray,inArray:a.Basic.inArray,inSeries:a.Basic.inSeries,addI18n:t.core.I18n.addI18n,translate:t.core.I18n.translate,sprintf:a.Basic.sprintf,isEmptyObj:a.Basic.isEmptyObj,hasClass:a.Dom.hasClass,addClass:a.Dom.addClass,removeClass:a.Dom.removeClass,getStyle:a.Dom.getStyle,addEvent:a.Events.addEvent,removeEvent:a.Events.removeEvent,removeAllEvents:a.Events.removeAllEvents,cleanName:function(e){var t,i;for(i=[/[\300-\306]/g,"A",/[\340-\346]/g,"a",/\307/g,"C",/\347/g,"c",/[\310-\313]/g,"E",/[\350-\353]/g,"e",/[\314-\317]/g,"I",/[\354-\357]/g,"i",/\321/g,"N",/\361/g,"n",/[\322-\330]/g,"O",/[\362-\370]/g,"o",/[\331-\334]/g,"U",/[\371-\374]/g,"u"],t=0;t0?"&":"?")+i),e},formatSize:function(e){function t(e,t){return Math.round(e*Math.pow(10,t))/Math.pow(10,t)}if(e===i||/\D/.test(e))return l.translate("N/A");var n=Math.pow(1024,4);return e>n?t(e/n,1)+" "+l.translate("tb"):e>(n/=1024)?t(e/n,1)+" "+l.translate("gb"):e>(n/=1024)?t(e/n,1)+" "+l.translate("mb"):e>1024?Math.round(e/1024)+" "+l.translate("kb"):e+" "+l.translate("b")},parseSize:a.Basic.parseSizeStr,predictRuntime:function(e,t){var i,n;return i=new l.Uploader(e),n=o.thatCan(i.getOption().required_features,t||e.runtimes),i.destroy(),n},addFileFilter:function(e,t){s[e]=t}};l.addFileFilter("mime_types",function(e,t,i){e.length&&!e.regexp.test(t.name)?(this.trigger("Error",{code:l.FILE_EXTENSION_ERROR,message:l.translate("File extension error."),file:t}),i(!1)):i(!0)}),l.addFileFilter("max_file_size",function(e,t,i){var n;e=l.parseSize(e),t.size!==n&&e&&t.size>e?(this.trigger("Error",{code:l.FILE_SIZE_ERROR,message:l.translate("File size error."),file:t}),i(!1)):i(!0)}),l.addFileFilter("prevent_duplicates",function(e,t,i){if(e)for(var n=this.files.length;n--;)if(t.name===this.files[n].name&&t.size===this.files[n].size)return this.trigger("Error",{code:l.FILE_DUPLICATE_ERROR,message:l.translate("Duplicate file error."),file:t}),i(!1),void 0;i(!0)}),l.addFileFilter("prevent_empty",function(e,t,n){e&&!t.size&&t.size!==i?(this.trigger("Error",{code:l.FILE_SIZE_ERROR,message:l.translate("File size error."),file:t}),n(!1)):n(!0)}),l.Uploader=function(e){function a(){var e,t,i=0;if(this.state==l.STARTED){for(t=0;t0?Math.ceil(100*(e.loaded/e.size)):100,d()}function d(){var e,t,n,r=0;for(I.reset(),e=0;eS)&&(r+=n),I.loaded+=n):I.size=i,t.status==l.DONE?I.uploaded++:t.status==l.FAILED?I.failed++:I.queued++;I.size===i?I.percent=D.length>0?Math.ceil(100*(I.uploaded/D.length)):0:(I.bytesPerSec=Math.ceil(r/((+new Date-S||1)/1e3)),I.percent=I.size>0?Math.ceil(100*(I.loaded/I.size)):0)}function c(){var e=F[0]||P[0];return e?e.getRuntime().uid:!1}function f(){this.bind("FilesAdded FilesRemoved",function(e){e.trigger("QueueChanged"),e.refresh()}),this.bind("CancelUpload",b),this.bind("BeforeUpload",m),this.bind("UploadFile",_),this.bind("UploadProgress",E),this.bind("StateChanged",v),this.bind("QueueChanged",d),this.bind("Error",R),this.bind("FileUploaded",y),this.bind("Destroy",z)}function p(e,i){var n=this,r=0,s=[],a={runtime_order:e.runtimes,required_caps:e.required_features,preferred_caps:x,swf_url:e.flash_swf_url,xap_url:e.silverlight_xap_url};l.each(e.runtimes.split(/\s*,\s*/),function(t){e[t]&&(a[t]=e[t])}),e.browse_button&&l.each(e.browse_button,function(i){s.push(function(s){var u=new t.file.FileInput(l.extend({},a,{accept:e.filters.mime_types,name:e.file_data_name,multiple:e.multi_selection,container:e.container,browse_button:i}));u.onready=function(){var e=o.getInfo(this.ruid);l.extend(n.features,{chunks:e.can("slice_blob"),multipart:e.can("send_multipart"),multi_selection:e.can("select_multiple")}),r++,F.push(this),s()},u.onchange=function(){n.addFile(this.files)},u.bind("mouseenter mouseleave mousedown mouseup",function(t){U||(e.browse_button_hover&&("mouseenter"===t.type?l.addClass(i,e.browse_button_hover):"mouseleave"===t.type&&l.removeClass(i,e.browse_button_hover)),e.browse_button_active&&("mousedown"===t.type?l.addClass(i,e.browse_button_active):"mouseup"===t.type&&l.removeClass(i,e.browse_button_active)))}),u.bind("mousedown",function(){n.trigger("Browse")}),u.bind("error runtimeerror",function(){u=null,s()}),u.init()})}),e.drop_element&&l.each(e.drop_element,function(e){s.push(function(i){var s=new t.file.FileDrop(l.extend({},a,{drop_zone:e}));s.onready=function(){var e=o.getInfo(this.ruid);l.extend(n.features,{chunks:e.can("slice_blob"),multipart:e.can("send_multipart"),dragdrop:e.can("drag_and_drop")}),r++,P.push(this),i()},s.ondrop=function(){n.addFile(this.files)},s.bind("error runtimeerror",function(){s=null,i()}),s.init()})}),l.inSeries(s,function(){"function"==typeof i&&i(r)})}function g(e,n,r,s){var a=new t.image.Image;try{a.onload=function(){n.width>this.width&&n.height>this.height&&n.quality===i&&n.preserve_headers&&!n.crop?(this.destroy(),s(e)):a.downsize(n.width,n.height,n.crop,n.preserve_headers)},a.onresize=function(){var t=this.getAsBlob(e.type,n.quality);this.destroy(),s(t)},a.bind("error runtimeerror",function(){this.destroy(),s(e)}),a.load(e,r)}catch(o){s(e)}}function h(e,i,r){function s(e,i,n){var r=O[e];switch(e){case"max_file_size":"max_file_size"===e&&(O.max_file_size=O.filters.max_file_size=i);break;case"chunk_size":(i=l.parseSize(i))&&(O[e]=i,O.send_file_name=!0);break;case"multipart":O[e]=i,i||(O.send_file_name=!0);break;case"http_method":O[e]="PUT"===i.toUpperCase()?"PUT":"POST";break;case"unique_names":O[e]=i,i&&(O.send_file_name=!0);break;case"filters":"array"===l.typeOf(i)&&(i={mime_types:i}),n?l.extend(O.filters,i):O.filters=i,i.mime_types&&("string"===l.typeOf(i.mime_types)&&(i.mime_types=t.core.utils.Mime.mimes2extList(i.mime_types)),i.mime_types.regexp=function(e){var t=[];return l.each(e,function(e){l.each(e.extensions.split(/,/),function(e){/^\s*\*\s*$/.test(e)?t.push("\\.*"):t.push("\\."+e.replace(new RegExp("["+"/^$.*+?|()[]{}\\".replace(/./g,"\\$&")+"]","g"),"\\$&"))})}),new RegExp("("+t.join("|")+")$","i")}(i.mime_types),O.filters.mime_types=i.mime_types);break;case"resize":O.resize=i?l.extend({preserve_headers:!0,crop:!1},i):!1;break;case"prevent_duplicates":O.prevent_duplicates=O.filters.prevent_duplicates=!!i;break;case"container":case"browse_button":case"drop_element":i="container"===e?l.get(i):l.getAll(i);case"runtimes":case"multi_selection":case"flash_swf_url":case"silverlight_xap_url":O[e]=i,n||(u=!0);break;default:O[e]=i}n||a.trigger("OptionChanged",e,i,r)}var a=this,u=!1;"object"==typeof e?l.each(e,function(e,t){s(t,e,r)}):s(e,i,r),r?(O.required_features=n(l.extend({},O)),x=n(l.extend({},O,{required_features:!0}))):u&&(a.trigger("Destroy"),p.call(a,O,function(e){e?(a.runtime=o.getInfo(c()).type,a.trigger("Init",{runtime:a.runtime}),a.trigger("PostInit")):a.trigger("Error",{code:l.INIT_ERROR,message:l.translate("Init error.")})}))}function m(e,t){if(e.settings.unique_names){var i=t.name.match(/\.([^.]+)$/),n="part";i&&(n=i[1]),t.target_name=t.id+"."+n}}function _(e,i){function n(){c-->0?r(s,1e3):(i.loaded=p,e.trigger("Error",{code:l.HTTP_ERROR,message:l.translate("HTTP Error."),file:i,response:T.responseText,status:T.status,responseHeaders:T.getAllResponseHeaders()}))}function s(){var t,n,r={};i.status===l.UPLOADING&&e.state!==l.STOPPED&&(e.settings.send_file_name&&(r.name=i.target_name||i.name),d&&f.chunks&&o.size>d?(n=Math.min(d,o.size-p),t=o.slice(p,p+n)):(n=o.size,t=o),d&&f.chunks&&(e.settings.send_chunk_number?(r.chunk=Math.ceil(p/d),r.chunks=Math.ceil(o.size/d)):(r.offset=p,r.total=o.size)),e.trigger("BeforeChunkUpload",i,r,t,p)&&a(r,t,n))}function a(a,d,g){var m;T=new t.xhr.XMLHttpRequest,T.upload&&(T.upload.onprogress=function(t){i.loaded=Math.min(i.size,p+t.loaded),e.trigger("UploadProgress",i)}),T.onload=function(){return T.status<200||T.status>=400?(n(),void 0):(c=e.settings.max_retries,g=o.size?(i.size!=i.origSize&&(o.destroy(),o=null),e.trigger("UploadProgress",i),i.status=l.DONE,i.completeTimestamp=+new Date,e.trigger("FileUploaded",i,{response:T.responseText,status:T.status,responseHeaders:T.getAllResponseHeaders()})):r(s,1),void 0)},T.onerror=function(){n()},T.onloadend=function(){this.destroy()},e.settings.multipart&&f.multipart?(T.open(e.settings.http_method,u,!0),l.each(e.settings.headers,function(e,t){T.setRequestHeader(t,e)}),m=new t.xhr.FormData,l.each(l.extend(a,e.settings.multipart_params),function(e,t){m.append(t,e)}),m.append(e.settings.file_data_name,d),T.send(m,h)):(u=l.buildUrl(e.settings.url,l.extend(a,e.settings.multipart_params)),T.open(e.settings.http_method,u,!0),l.each(e.settings.headers,function(e,t){T.setRequestHeader(t,e)}),T.hasRequestHeader("Content-Type")||T.setRequestHeader("Content-Type","application/octet-stream"),T.send(d,h))}var o,u=e.settings.url,d=e.settings.chunk_size,c=e.settings.max_retries,f=e.features,p=0,h={runtime_order:e.settings.runtimes,required_caps:e.settings.required_features,preferred_caps:x,swf_url:e.settings.flash_swf_url,xap_url:e.settings.silverlight_xap_url};i.loaded&&(p=i.loaded=d?d*Math.floor(i.loaded/d):0),o=i.getSource(),l.isEmptyObj(e.settings.resize)||-1===l.inArray(o.type,["image/jpeg","image/png"])?s():g(o,e.settings.resize,h,function(e){o=e,i.size=e.size,s()})}function E(e,t){u(t)}function v(e){if(e.state==l.STARTED)S=+new Date;else if(e.state==l.STOPPED)for(var t=e.files.length-1;t>=0;t--)e.files[t].status==l.UPLOADING&&(e.files[t].status=l.QUEUED,d())}function b(){T&&T.abort()}function y(e){d(),r(function(){a.call(e)},1)}function R(e,t){t.code===l.INIT_ERROR?e.destroy():t.code===l.HTTP_ERROR&&(t.file.status=l.FAILED,t.file.completeTimestamp=+new Date,u(t.file),e.state==l.STARTED&&(e.trigger("CancelUpload"),r(function(){a.call(e)},1)))}function z(e){e.stop(),l.each(D,function(e){e.destroy()}),D=[],F.length&&(l.each(F,function(e){e.destroy()}),F=[]),P.length&&(l.each(P,function(e){e.destroy()}),P=[]),x={},U=!1,S=T=null,I.reset()}var O,S,I,T,w=l.guid(),D=[],x={},F=[],P=[],U=!1;O={chunk_size:0,file_data_name:"file",filters:{mime_types:[],max_file_size:0,prevent_duplicates:!1,prevent_empty:!0},flash_swf_url:"js/Moxie.swf",http_method:"POST",max_retries:0,multipart:!0,multi_selection:!0,resize:!1,runtimes:o.order,send_file_name:!0,send_chunk_number:!0,silverlight_xap_url:"js/Moxie.xap"},h.call(this,e,null,!0),I=new l.QueueProgress,l.extend(this,{id:w,uid:w,state:l.STOPPED,features:{},runtime:null,files:D,settings:O,total:I,init:function(){var e,t,i=this;return e=i.getOption("preinit"),"function"==typeof e?e(i):l.each(e,function(e,t){i.bind(t,e)}),f.call(i),l.each(["container","browse_button","drop_element"],function(e){return null===i.getOption(e)?(t={code:l.INIT_ERROR,message:l.sprintf(l.translate("%s specified, but cannot be found."),e)},!1):void 0}),t?i.trigger("Error",t):O.browse_button||O.drop_element?(p.call(i,O,function(e){var t=i.getOption("init");"function"==typeof t?t(i):l.each(t,function(e,t){i.bind(t,e)}),e?(i.runtime=o.getInfo(c()).type,i.trigger("Init",{runtime:i.runtime}),i.trigger("PostInit")):i.trigger("Error",{code:l.INIT_ERROR,message:l.translate("Init error.")})}),void 0):i.trigger("Error",{code:l.INIT_ERROR,message:l.translate("You must specify either browse_button or drop_element.")})},setOption:function(e,t){h.call(this,e,t,!this.runtime)},getOption:function(e){return e?O[e]:O},refresh:function(){F.length&&l.each(F,function(e){e.trigger("Refresh")}),this.trigger("Refresh")},start:function(){this.state!=l.STARTED&&(this.state=l.STARTED,this.trigger("StateChanged"),a.call(this))},stop:function(){this.state!=l.STOPPED&&(this.state=l.STOPPED,this.trigger("StateChanged"),this.trigger("CancelUpload"))},disableBrowse:function(){U=arguments[0]!==i?arguments[0]:!0,F.length&&l.each(F,function(e){e.disable(U)}),this.trigger("DisableBrowse",U)},getFile:function(e){var t;for(t=D.length-1;t>=0;t--)if(D[t].id===e)return D[t]},addFile:function(e,i){function n(e,t){var i=[];l.each(u.settings.filters,function(t,n){s[n]&&i.push(function(i){s[n].call(u,t,e,function(e){i(!e)})})}),l.inSeries(i,t)}function a(e){var s=l.typeOf(e);if(e instanceof t.file.File){if(!e.ruid&&!e.isDetached()){if(!o)return!1;e.ruid=o,e.connectRuntime(o)}a(new l.File(e))}else e instanceof t.file.Blob?(a(e.getSource()),e.destroy()):e instanceof l.File?(i&&(e.name=i),d.push(function(t){n(e,function(i){i||(D.push(e),f.push(e),u.trigger("FileFiltered",e)),r(t,1)})})):-1!==l.inArray(s,["file","blob"])?a(new t.file.File(null,e)):"node"===s&&"filelist"===l.typeOf(e.files)?l.each(e.files,a):"array"===s&&(i=null,l.each(e,a))}var o,u=this,d=[],f=[];o=c(),a(e),d.length&&l.inSeries(d,function(){f.length&&u.trigger("FilesAdded",f)})},removeFile:function(e){for(var t="string"==typeof e?e:e.id,i=D.length-1;i>=0;i--)if(D[i].id===t)return this.splice(i,1)[0]},splice:function(e,t){var n=D.splice(e===i?0:e,t===i?D.length:t),r=!1;return this.state==l.STARTED&&(l.each(n,function(e){return e.status===l.UPLOADING?(r=!0,!1):void 0}),r&&this.stop()),this.trigger("FilesRemoved",n),l.each(n,function(e){e.destroy()}),r&&this.start(),n},dispatchEvent:function(e){var t,i;if(e=e.toLowerCase(),t=this.hasEventListener(e)){t.sort(function(e,t){return t.priority-e.priority}),i=[].slice.call(arguments),i.shift(),i.unshift(this);for(var n=0;n +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +use Symfony\Component\Console\Input\ArgvInput; + +if (php_sapi_name() != 'cli') +{ + echo 'This program must be run from the command line.' . PHP_EOL; + exit(1); +} + +define('IN_PHPBB', true); + +$phpbb_root_path = __DIR__ . '/../'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +require($phpbb_root_path . 'includes/startup.' . $phpEx); +require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); + +$phpbb_class_loader = new \phpbb\class_loader('phpbb\\', "{$phpbb_root_path}phpbb/", $phpEx); +$phpbb_class_loader->register(); + +$phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx); +extract($phpbb_config_php_file->get_all()); + +if (!defined('PHPBB_ENVIRONMENT')) +{ + @define('PHPBB_ENVIRONMENT', 'production'); +} + +require($phpbb_root_path . 'includes/constants.' . $phpEx); +require($phpbb_root_path . 'includes/functions.' . $phpEx); +require($phpbb_root_path . 'includes/functions_admin.' . $phpEx); +require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); +require($phpbb_root_path . 'includes/functions_compatibility.' . $phpEx); + +$phpbb_container_builder = new \phpbb\di\container_builder($phpbb_root_path, $phpEx); +$phpbb_container = $phpbb_container_builder->with_config($phpbb_config_php_file); + +$input = new ArgvInput(); + +if ($input->hasParameterOption(array('--env'))) +{ + $phpbb_container_builder->with_environment($input->getParameterOption('--env')); +} + +if ($input->hasParameterOption(array('--safe-mode'))) +{ + $phpbb_container_builder->without_extensions(); + $phpbb_container_builder->without_cache(); +} +else +{ + $phpbb_class_loader_ext = new \phpbb\class_loader('\\', "{$phpbb_root_path}ext/", $phpEx); + $phpbb_class_loader_ext->register(); +} + +$phpbb_container = $phpbb_container_builder->get_container(); +$phpbb_container->get('request')->enable_super_globals(); +require($phpbb_root_path . 'includes/compatibility_globals.' . $phpEx); + +register_compatibility_globals(); + +/** @var \phpbb\config\config $config */ +$config = $phpbb_container->get('config'); + +/** @var \phpbb\language\language $language */ +$language = $phpbb_container->get('language'); +$language->set_default_language($config['default_lang']); +$language->add_lang(array('common', 'acp/common', 'cli')); + +/* @var $user \phpbb\user */ +$user = $phpbb_container->get('user'); +$user->data['user_id'] = ANONYMOUS; +$user->ip = '127.0.0.1'; + +$application = new \phpbb\console\application('phpBB Console', PHPBB_VERSION, $language); +$application->setDispatcher($phpbb_container->get('dispatcher')); +$application->register_container_commands($phpbb_container->get('console.command_collection')); +$application->run($input); diff --git a/cache/.htaccess b/cache/.htaccess new file mode 100644 index 0000000..aa5afc1 --- /dev/null +++ b/cache/.htaccess @@ -0,0 +1,4 @@ + + Order Allow,Deny + Deny from All + \ No newline at end of file diff --git a/cache/index.htm b/cache/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/cache/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/common.php b/common.php new file mode 100644 index 0000000..172503f --- /dev/null +++ b/common.php @@ -0,0 +1,164 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* Minimum Requirement: PHP 5.4.0 +*/ + +if (!defined('IN_PHPBB')) +{ + exit; +} + +require($phpbb_root_path . 'includes/startup.' . $phpEx); +require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); + +$phpbb_class_loader = new \phpbb\class_loader('phpbb\\', "{$phpbb_root_path}phpbb/", $phpEx); +$phpbb_class_loader->register(); + +$phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx); +extract($phpbb_config_php_file->get_all()); + +if (!defined('PHPBB_ENVIRONMENT')) +{ + @define('PHPBB_ENVIRONMENT', 'production'); +} + +if (!defined('PHPBB_INSTALLED')) +{ + // Redirect the user to the installer + require($phpbb_root_path . 'includes/functions.' . $phpEx); + + // We have to generate a full HTTP/1.1 header here since we can't guarantee to have any of the information + // available as used by the redirect function + $server_name = (!empty($_SERVER['HTTP_HOST'])) ? strtolower($_SERVER['HTTP_HOST']) : ((!empty($_SERVER['SERVER_NAME'])) ? $_SERVER['SERVER_NAME'] : getenv('SERVER_NAME')); + $server_port = (!empty($_SERVER['SERVER_PORT'])) ? (int) $_SERVER['SERVER_PORT'] : (int) getenv('SERVER_PORT'); + $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 1 : 0; + + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') + { + $secure = 1; + $server_port = 443; + } + + $script_name = (!empty($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : getenv('PHP_SELF'); + if (!$script_name) + { + $script_name = (!empty($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : getenv('REQUEST_URI'); + } + + // $phpbb_root_path accounts for redirects from e.g. /adm + $script_path = trim(dirname($script_name)) . '/' . $phpbb_root_path . 'install/app.' . $phpEx; + // Replace any number of consecutive backslashes and/or slashes with a single slash + // (could happen on some proxy setups and/or Windows servers) + $script_path = preg_replace('#[\\\\/]{2,}#', '/', $script_path); + + // Eliminate . and .. from the path + require($phpbb_root_path . 'phpbb/filesystem.' . $phpEx); + $phpbb_filesystem = new phpbb\filesystem\filesystem(); + $script_path = $phpbb_filesystem->clean_path($script_path); + + $url = (($secure) ? 'https://' : 'http://') . $server_name; + + if ($server_port && (($secure && $server_port <> 443) || (!$secure && $server_port <> 80))) + { + // HTTP HOST can carry a port number... + if (strpos($server_name, ':') === false) + { + $url .= ':' . $server_port; + } + } + + $url .= $script_path; + header('Location: ' . $url); + exit; +} + +// In case $phpbb_adm_relative_path is not set (in case of an update), use the default. +$phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relative_path : 'adm/'; +$phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path; + +// Include files +require($phpbb_root_path . 'includes/functions.' . $phpEx); +require($phpbb_root_path . 'includes/functions_content.' . $phpEx); +include($phpbb_root_path . 'includes/functions_compatibility.' . $phpEx); + +require($phpbb_root_path . 'includes/constants.' . $phpEx); +require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); + +if (PHPBB_ENVIRONMENT === 'development') +{ + \phpbb\debug\debug::enable(); +} +else +{ + set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'); +} + +$phpbb_class_loader_ext = new \phpbb\class_loader('\\', "{$phpbb_root_path}ext/", $phpEx); +$phpbb_class_loader_ext->register(); + +// Set up container +try +{ + $phpbb_container_builder = new \phpbb\di\container_builder($phpbb_root_path, $phpEx); + $phpbb_container = $phpbb_container_builder->with_config($phpbb_config_php_file)->get_container(); +} +catch (InvalidArgumentException $e) +{ + if (PHPBB_ENVIRONMENT !== 'development') + { + trigger_error( + 'The requested environment ' . PHPBB_ENVIRONMENT . ' is not available.', + E_USER_ERROR + ); + } + else + { + throw $e; + } +} + +$phpbb_class_loader->set_cache($phpbb_container->get('cache.driver')); +$phpbb_class_loader_ext->set_cache($phpbb_container->get('cache.driver')); + +require($phpbb_root_path . 'includes/compatibility_globals.' . $phpEx); + +register_compatibility_globals(); + +// Add own hook handler +require($phpbb_root_path . 'includes/hooks/index.' . $phpEx); +$phpbb_hook = new phpbb_hook(array('exit_handler', 'phpbb_user_session_handler', 'append_sid', array('template', 'display'))); + +/* @var $phpbb_hook_finder \phpbb\hook\finder */ +$phpbb_hook_finder = $phpbb_container->get('hook_finder'); + +foreach ($phpbb_hook_finder->find() as $hook) +{ + @include($phpbb_root_path . 'includes/hooks/' . $hook . '.' . $phpEx); +} + +/** +* Main event which is triggered on every page +* +* You can use this event to load function files and initiate objects +* +* NOTE: At this point the global session ($user) and permissions ($auth) +* do NOT exist yet. If you need to use the user object +* (f.e. to include language files) or need to check permissions, +* please use the core.user_setup event instead! +* +* @event core.common +* @since 3.1.0-a1 +*/ +$phpbb_dispatcher->dispatch('core.common'); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..d192fd5 --- /dev/null +++ b/composer.json @@ -0,0 +1,75 @@ +{ + "name": "phpbb/phpbb", + "description": "phpBB Forum Software application", + "type": "project", + "keywords": ["phpbb", "forum"], + "homepage": "https://www.phpbb.com", + "license": "GPL-2.0-only", + "authors": [ + { + "name": "phpBB Limited", + "email": "operations@phpbb.com", + "homepage": "https://www.phpbb.com/go/authors" + } + ], + "support": { + "issues": "https://tracker.phpbb.com", + "forum": "https://www.phpbb.com/community/", + "wiki": "https://wiki.phpbb.com", + "irc": "irc://irc.freenode.org/phpbb" + }, + "scripts": { + "post-update-cmd": "echo 'You MUST manually modify the clean-vendor-dir target in build/build.xml when adding or upgrading dependencies.'" + }, + "replace": { + "phpbb/phpbb-core": "self.version" + }, + "require": { + "php": ">=5.4", + "bantu/ini-get-wrapper": "1.0.*", + "google/recaptcha": "~1.1", + "guzzlehttp/guzzle": "~5.3", + "lusitanian/oauth": "^0.8.1", + "marc1706/fast-image-size": "^1.1", + "paragonie/random_compat": "^1.4", + "patchwork/utf8": "^1.1", + "s9e/text-formatter": "^1.3", + "symfony/config": "^2.8", + "symfony/console": "^2.8", + "symfony/debug": "^2.8", + "symfony/dependency-injection": "^2.8", + "symfony/event-dispatcher": "^2.8", + "symfony/filesystem": "^2.8", + "symfony/finder": "^2.8", + "symfony/http-foundation": "^2.8", + "symfony/http-kernel": "^2.8", + "symfony/proxy-manager-bridge": "^2.8", + "symfony/routing": "^2.8", + "symfony/twig-bridge": "^2.8", + "symfony/yaml": "^2.8", + "twig/twig": "^1.0" + }, + "require-dev": { + "fabpot/goutte": "~2.0", + "facebook/webdriver": "~1.1", + "laravel/homestead": "~2.2", + "phing/phing": "2.4.*", + "phpunit/dbunit": "1.3.*", + "phpunit/phpunit": "^4.1", + "sami/sami": "1.*", + "squizlabs/php_codesniffer": "2.*", + "symfony/browser-kit": "^2.8", + "symfony/css-selector": "^2.8", + "symfony/dom-crawler": "^2.8" + }, + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "config": { + "platform": { + "php": "5.4.7" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..2c338bf --- /dev/null +++ b/composer.lock @@ -0,0 +1,3644 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "6daa2f5f7a161377dee1835bd4d5b463", + "packages": [ + { + "name": "bantu/ini-get-wrapper", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/bantuXorg/php-ini-get-wrapper.git", + "reference": "4770c7feab370c62e23db4f31c112b7c6d90aee2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bantuXorg/php-ini-get-wrapper/zipball/4770c7feab370c62e23db4f31c112b7c6d90aee2", + "reference": "4770c7feab370c62e23db4f31c112b7c6d90aee2", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "bantu\\IniGetWrapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Convenience wrapper around ini_get()", + "time": "2014-09-15T13:12:35+00:00" + }, + { + "name": "google/recaptcha", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/google/recaptcha.git", + "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/recaptcha/zipball/2b7e00566afca82a38a1d3adb8e42c118006296e", + "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "ReCaptcha\\": "src/ReCaptcha" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Client library for reCAPTCHA, a free service that protect websites from spam and abuse.", + "homepage": "http://www.google.com/recaptcha/", + "keywords": [ + "Abuse", + "captcha", + "recaptcha", + "spam" + ], + "time": "2015-09-02T17:23:59+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "5.3.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "93bbdb30d59be6cd9839495306c65f2907370eb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/93bbdb30d59be6cd9839495306c65f2907370eb9", + "reference": "93bbdb30d59be6cd9839495306c65f2907370eb9", + "shasum": "" + }, + "require": { + "guzzlehttp/ringphp": "^1.1", + "php": ">=5.4.0", + "react/promise": "^2.2" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2018-07-31T13:33:10+00:00" + }, + { + "name": "guzzlehttp/ringphp", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/RingPHP.git", + "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/5e2a174052995663dd68e6b5ad838afd47dd615b", + "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b", + "shasum": "" + }, + "require": { + "guzzlehttp/streams": "~3.0", + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", + "time": "2018-07-31T13:22:33+00:00" + }, + { + "name": "guzzlehttp/streams", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/streams.git", + "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple abstraction over streams of data", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "stream" + ], + "time": "2014-10-12T19:18:40+00:00" + }, + { + "name": "ircmaxell/password-compat", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ], + "time": "2014-11-20T16:49:30+00:00" + }, + { + "name": "lusitanian/oauth", + "version": "v0.8.11", + "source": { + "type": "git", + "url": "https://github.com/Lusitanian/PHPoAuthLib.git", + "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/fc11a53db4b66da555a6a11fce294f574a8374f9", + "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "predis/predis": "0.8.*@dev", + "squizlabs/php_codesniffer": "2.*", + "symfony/http-foundation": "~2.1" + }, + "suggest": { + "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.", + "predis/predis": "Allows using the Redis storage backend.", + "symfony/http-foundation": "Allows using the Symfony Session storage backend." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-0": { + "OAuth": "src", + "OAuth\\Unit": "tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Desberg", + "email": "david@daviddesberg.com" + }, + { + "name": "Elliot Chance", + "email": "elliotchance@gmail.com" + }, + { + "name": "Pieter Hordijk", + "email": "info@pieterhordijk.com" + } + ], + "description": "PHP 5.3+ oAuth 1/2 Library", + "keywords": [ + "Authentication", + "authorization", + "oauth", + "security" + ], + "time": "2016-07-12T22:15:00+00:00" + }, + { + "name": "marc1706/fast-image-size", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/marc1706/fast-image-size.git", + "reference": "c4ded0223a4e49ae45a2183a69f6afac5baf7250" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc1706/fast-image-size/zipball/c4ded0223a4e49ae45a2183a69f6afac5baf7250", + "reference": "c4ded0223a4e49ae45a2183a69f6afac5baf7250", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "FastImageSize\\": "lib", + "FastImageSize\\tests\\": "tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marc Alexander", + "email": "admin@m-a-styles.de", + "homepage": "https://www.m-a-styles.de", + "role": "Developer" + } + ], + "description": "fast-image-size is a PHP library that does almost everything PHP's getimagesize() does but without the large overhead of downloading the complete file.", + "homepage": "https://www.m-a-styles.de", + "keywords": [ + "fast", + "getimagesize", + "image", + "imagesize", + "php", + "size" + ], + "time": "2017-10-23T18:52:01+00:00" + }, + { + "name": "ocramius/proxy-manager", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/ProxyManager.git", + "reference": "57e9272ec0e8deccf09421596e0e2252df440e11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/57e9272ec0e8deccf09421596e0e2252df440e11", + "reference": "57e9272ec0e8deccf09421596e0e2252df440e11", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-code": ">2.2.5,<3.0" + }, + "require-dev": { + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "1.5.*" + }, + "suggest": { + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", + "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", + "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)", + "zendframework/zend-stdlib": "To use the hydrator proxy", + "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "ProxyManager\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "homepage": "https://github.com/Ocramius/ProxyManager", + "keywords": [ + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" + ], + "time": "2015-08-09T04:28:19+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v1.4.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/9b3899e3c3ddde89016f576edb8c489708ad64cd", + "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-03-13T16:22:52+00:00" + }, + { + "name": "patchwork/utf8", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/tchwork/utf8.git", + "reference": "30ec6451aec7d2536f0af8fe535f70c764f2c47a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tchwork/utf8/zipball/30ec6451aec7d2536f0af8fe535f70c764f2c47a", + "reference": "30ec6451aec7d2536f0af8fe535f70c764f2c47a", + "shasum": "" + }, + "require": { + "lib-pcre": ">=7.3", + "php": ">=5.3.0" + }, + "suggest": { + "ext-iconv": "Use iconv for best performance", + "ext-intl": "Use Intl for best performance", + "ext-mbstring": "Use Mbstring for best performance", + "ext-wfio": "Use WFIO for UTF-8 filesystem access on Windows" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Patchwork\\": "src/Patchwork/" + }, + "classmap": [ + "src/Normalizer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "(Apache-2.0 or GPL-2.0)" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + } + ], + "description": "Portable and performant UTF-8, Unicode and Grapheme Clusters for PHP", + "homepage": "https://github.com/tchwork/utf8", + "keywords": [ + "grapheme", + "i18n", + "unicode", + "utf-8", + "utf8" + ], + "time": "2016-05-18T13:57:10+00:00" + }, + { + "name": "psr/log", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "react/promise", + "version": "v2.7.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "time": "2019-01-07T21:25:54+00:00" + }, + { + "name": "s9e/text-formatter", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/s9e/TextFormatter.git", + "reference": "dc7efff70b67b9cee00881ad3bef0a1da076b31e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/dc7efff70b67b9cee00881ad3bef0a1da076b31e", + "reference": "dc7efff70b67b9cee00881ad3bef0a1da076b31e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "lib-pcre": ">=7.2", + "php": ">=5.4.7" + }, + "require-dev": { + "matthiasmullie/minify": "*", + "php-coveralls/php-coveralls": "*", + "s9e/regexp-builder": "1.*" + }, + "suggest": { + "ext-curl": "Improves the performance of the MediaEmbed plugin and some JavaScript minifiers", + "ext-intl": "Allows international URLs to be accepted by the URL filter", + "ext-json": "Enables the generation of a JavaScript parser", + "ext-mbstring": "Improves the performance of the PHP renderer", + "ext-tokenizer": "Improves the performance of the PHP renderer", + "ext-xsl": "Enables the XSLT renderer", + "ext-zlib": "Enables gzip compression when scraping content via the MediaEmbed plugin" + }, + "type": "library", + "autoload": { + "psr-4": { + "s9e\\TextFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Multi-purpose text formatting and markup library. Plugins offer support for BBCodes, Markdown, emoticons, HTML, embedding media (YouTube, etc...), enhanced typography and more.", + "homepage": "https://github.com/s9e/TextFormatter/", + "keywords": [ + "bbcode", + "bbcodes", + "blog", + "censor", + "embed", + "emoji", + "emoticons", + "engine", + "forum", + "html", + "markdown", + "markup", + "media", + "parser", + "shortcodes" + ], + "time": "2019-03-27T14:19:41+00:00" + }, + { + "name": "symfony/config", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "7dd5f5040dc04c118d057fb5886563963eb70011" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/7dd5f5040dc04c118d057fb5886563963eb70011", + "reference": "7dd5f5040dc04c118d057fb5886563963eb70011", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3|~3.0.0", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/yaml": "~2.7|~3.0.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2018-11-26T09:38:12+00:00" + }, + { + "name": "symfony/console", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2|~3.0.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-11-20T15:55:20+00:00" + }, + { + "name": "symfony/debug", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/dependency-injection", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "a2f40df187f0053bc361bcea3b27ff2b85744d9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a2f40df187f0053bc361bcea3b27ff2b85744d9f", + "reference": "a2f40df187f0053bc361bcea3b27ff2b85744d9f", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/expression-language": "<2.6" + }, + "require-dev": { + "symfony/config": "~2.2|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/yaml": "~2.3.42|~2.7.14|~2.8.7|~3.0.7" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2018-11-21T14:20:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7ae46872dad09dffb7fe1e93a0937097339d0080", + "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/finder", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "d0ab719bedc9fc6748a95b2dcb04137292a27b92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0ab719bedc9fc6748a95b2dcb04137292a27b92", + "reference": "d0ab719bedc9fc6748a95b2dcb04137292a27b92", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php54": "~1.0", + "symfony/polyfill-php55": "~1.0" + }, + "require-dev": { + "symfony/expression-language": "~2.4|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2018-11-25T11:27:05+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "3df0207d4c973eb9c91b38a608aef4654dc256fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3df0207d4c973eb9c91b38a608aef4654dc256fa", + "reference": "3df0207d4c973eb9c91b38a608aef4654dc256fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0", + "symfony/debug": "^2.6.2", + "symfony/event-dispatcher": "^2.6.7|~3.0.0", + "symfony/http-foundation": "~2.7.36|~2.8.29|~3.1.6", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/config": "<2.7", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "symfony/browser-kit": "~2.3|~3.0.0", + "symfony/class-loader": "~2.1|~3.0.0", + "symfony/config": "~2.8", + "symfony/console": "~2.3|~3.0.0", + "symfony/css-selector": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.8|~3.0.0", + "symfony/dom-crawler": "^2.0.5|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "symfony/finder": "^2.0.5|~3.0.0", + "symfony/process": "^2.0.5|~3.0.0", + "symfony/routing": "~2.8|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0", + "symfony/templating": "~2.2|~3.0.0", + "symfony/translation": "^2.0.5|~3.0.0", + "symfony/var-dumper": "~2.6|~3.0.0" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2018-12-06T14:45:07+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "backendtea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-php54", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php54.git", + "reference": "2964b17ddc32dba7bcba009d5501c84d3fba1452" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/2964b17ddc32dba7bcba009d5501c84d3fba1452", + "reference": "2964b17ddc32dba7bcba009d5501c84d3fba1452", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php54\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/polyfill-php55", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php55.git", + "reference": "96fa25cef405ea452919559a0025d5dc16e30e4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/96fa25cef405ea452919559a0025d5dc16e30e4c", + "reference": "96fa25cef405ea452919559a0025d5dc16e30e4c", + "shasum": "" + }, + "require": { + "ircmaxell/password-compat": "~1.0", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php55\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-02-06T07:57:58+00:00" + }, + { + "name": "symfony/proxy-manager-bridge", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/proxy-manager-bridge.git", + "reference": "9c5f8d58e9c8017affdbeaec86c89d558aee4ec8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/9c5f8d58e9c8017affdbeaec86c89d558aee4ec8", + "reference": "9c5f8d58e9c8017affdbeaec86c89d558aee4ec8", + "shasum": "" + }, + "require": { + "ocramius/proxy-manager": "~0.4|~1.0|~2.0", + "php": ">=5.3.9", + "symfony/dependency-injection": "~2.8|~3.0.0" + }, + "require-dev": { + "symfony/config": "~2.3|~3.0.0" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\ProxyManager\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ProxyManager Bridge", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/routing", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "8b0df6869d1997baafff6a1541826eac5a03d067" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/8b0df6869d1997baafff6a1541826eac5a03d067", + "reference": "8b0df6869d1997baafff6a1541826eac5a03d067", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "psr/log": "~1.0", + "symfony/config": "~2.7|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "symfony/http-foundation": "~2.3|~3.0.0", + "symfony/yaml": "^2.0.5|~3.0.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2018-11-20T15:55:20+00:00" + }, + { + "name": "symfony/twig-bridge", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "ecc1e30d05fa99f25b504e2d6a8684555ae39f7c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ecc1e30d05fa99f25b504e2d6a8684555ae39f7c", + "reference": "ecc1e30d05fa99f25b504e2d6a8684555ae39f7c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "twig/twig": "~1.34|~2.4" + }, + "conflict": { + "symfony/form": "<2.8.23" + }, + "require-dev": { + "symfony/asset": "~2.7|~3.0.0", + "symfony/console": "~2.8|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "symfony/finder": "~2.3|~3.0.0", + "symfony/form": "^2.8.23", + "symfony/http-foundation": "^2.8.29|~3.0.0", + "symfony/http-kernel": "~2.8|~3.0.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.2|~3.0.0", + "symfony/security": "^2.8.31|^3.3.13", + "symfony/security-acl": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.2|~3.0.0", + "symfony/templating": "~2.1|~3.0.0", + "symfony/translation": "~2.7|~3.0.0", + "symfony/var-dumper": "~2.7.16|~2.8.9|~3.0.9", + "symfony/yaml": "^2.0.5|~3.0.0" + }, + "suggest": { + "symfony/asset": "For using the AssetExtension", + "symfony/expression-language": "For using the ExpressionExtension", + "symfony/finder": "", + "symfony/form": "For using the FormExtension", + "symfony/http-kernel": "For using the HttpKernelExtension", + "symfony/routing": "For using the RoutingExtension", + "symfony/security": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/templating": "For using the TwigEngine", + "symfony/translation": "For using the TranslationExtension", + "symfony/var-dumper": "For using the DumpExtension", + "symfony/yaml": "For using the YamlExtension" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Twig Bridge", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/yaml", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "twig/twig", + "version": "v1.39.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec", + "reference": "23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^2.7", + "symfony/phpunit-bridge": "^3.4.19|^4.1.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.39-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "time": "2019-04-16T17:12:57+00:00" + }, + { + "name": "zendframework/zend-code", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "5d998f261ec2a55171c71da57a11622745680153" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/5d998f261ec2a55171c71da57a11622745680153", + "reference": "5d998f261ec2a55171c71da57a11622745680153", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-eventmanager": "~2.5" + }, + "require-dev": { + "doctrine/common": ">=2.1", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "zendframework/zend-stdlib": "~2.5", + "zendframework/zend-version": "~2.5" + }, + "suggest": { + "doctrine/common": "Doctrine\\Common >=2.1 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "homepage": "https://github.com/zendframework/zend-code", + "keywords": [ + "code", + "zf2" + ], + "time": "2015-06-03T15:31:59+00:00" + }, + { + "name": "zendframework/zend-eventmanager", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "d94a16039144936f107f906896349900fd634443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/d94a16039144936f107f906896349900fd634443", + "reference": "d94a16039144936f107f906896349900fd634443", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-stdlib": "~2.5" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-eventmanager", + "keywords": [ + "eventmanager", + "zf2" + ], + "time": "2015-06-03T15:32:01+00:00" + }, + { + "name": "zendframework/zend-stdlib", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-stdlib.git", + "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cc8e90a60dd5d44b9730b77d07b97550091da1ae", + "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "zendframework/zend-config": "~2.5", + "zendframework/zend-eventmanager": "~2.5", + "zendframework/zend-filter": "~2.5", + "zendframework/zend-inputfilter": "~2.5", + "zendframework/zend-serializer": "~2.5", + "zendframework/zend-servicemanager": "~2.5" + }, + "suggest": { + "zendframework/zend-eventmanager": "To support aggregate hydrator usage", + "zendframework/zend-filter": "To support naming strategy hydrator usage", + "zendframework/zend-serializer": "Zend\\Serializer component", + "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-stdlib", + "keywords": [ + "stdlib", + "zf2" + ], + "time": "2015-06-03T15:32:03+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "fabpot/goutte", + "version": "v2.0.4", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/Goutte.git", + "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/0ad3ee6dc2d0aaa832a80041a1e09bf394e99802", + "reference": "0ad3ee6dc2d0aaa832a80041a1e09bf394e99802", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": ">=4,<6", + "php": ">=5.4.0", + "symfony/browser-kit": "~2.1", + "symfony/css-selector": "~2.1", + "symfony/dom-crawler": "~2.1" + }, + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Goutte\\": "Goutte" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A simple PHP Web Scraper", + "homepage": "https://github.com/FriendsOfPHP/Goutte", + "keywords": [ + "scraper" + ], + "time": "2015-05-05T21:14:57+00:00" + }, + { + "name": "facebook/webdriver", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-webdriver.git", + "reference": "b7186fb1bcfda956d237f59face250d06ef47253" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/b7186fb1bcfda956d237f59face250d06ef47253", + "reference": "b7186fb1bcfda956d237f59face250d06ef47253", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "php": ">=5.3.19" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^1.11", + "phpunit/phpunit": "4.6.* || ~5.0", + "squizlabs/php_codesniffer": "^2.6" + }, + "suggest": { + "phpdocumentor/phpdocumentor": "2.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "A PHP client for WebDriver", + "homepage": "https://github.com/facebook/php-webdriver", + "keywords": [ + "facebook", + "php", + "selenium", + "webdriver" + ], + "time": "2016-08-10T00:44:08+00:00" + }, + { + "name": "laravel/homestead", + "version": "v2.2.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/homestead.git", + "reference": "f4e45f895d8707042c2d0698627d33c484e7c6ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/homestead/zipball/f4e45f895d8707042c2d0698627d33c484e7c6ba", + "reference": "f4e45f895d8707042c2d0698627d33c484e7c6ba", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "symfony/console": "~2.0 || ~3.0", + "symfony/process": "~2.0 || ~3.0" + }, + "bin": [ + "homestead" + ], + "type": "library", + "autoload": { + "psr-4": { + "Laravel\\Homestead\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "A virtual machine for web artisans.", + "time": "2016-09-17T04:42:33+00:00" + }, + { + "name": "michelf/php-markdown", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/michelf/php-markdown.git", + "reference": "01ab082b355bf188d907b9929cd99b2923053495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/michelf/php-markdown/zipball/01ab082b355bf188d907b9929cd99b2923053495", + "reference": "01ab082b355bf188d907b9929cd99b2923053495", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Michelf\\": "Michelf/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "description": "PHP Markdown", + "homepage": "https://michelf.ca/projects/php-markdown/", + "keywords": [ + "markdown" + ], + "time": "2018-01-15T00:49:33+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v0.9.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ef70767475434bdb3615b43c327e2cae17ef12eb", + "reference": "ef70767475434bdb3615b43c327e2cae17ef12eb", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.9-dev" + } + }, + "autoload": { + "psr-0": { + "PHPParser": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2014-07-23T18:24:17+00:00" + }, + { + "name": "phing/phing", + "version": "2.4.14", + "source": { + "type": "git", + "url": "https://github.com/phingofficial/phing.git", + "reference": "41075d93ca254f1c90c79ec7ce81be2b2629e138" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phingofficial/phing/zipball/41075d93ca254f1c90c79ec7ce81be2b2629e138", + "reference": "41075d93ca254f1c90c79ec7ce81be2b2629e138", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "bin": [ + "bin/phing" + ], + "type": "library", + "autoload": { + "classmap": [ + "classes/phing/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "classes" + ], + "license": [ + "LGPL3" + ], + "authors": [ + { + "name": "Michiel Rook", + "email": "mrook@php.net" + }, + { + "name": "Phing Community", + "homepage": "http://www.phing.info/trac/wiki/Development/Contributors" + } + ], + "description": "PHing Is Not GNU make; it's a PHP project build system or build tool based on Apache Ant.", + "homepage": "http://www.phing.info/", + "keywords": [ + "build", + "task", + "tool" + ], + "time": "2012-11-29T21:23:47+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "reference": "e6a969a640b00d8daa3c66518b0405fb41ae0c4b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2016-01-25T08:17:30+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-08-05T17:53:17+00:00" + }, + { + "name": "phpunit/dbunit", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/dbunit.git", + "reference": "1507040c2541bdffd7fbd71fc792cecdea6a7c61" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/1507040c2541bdffd7fbd71fc792cecdea6a7c61", + "reference": "1507040c2541bdffd7fbd71fc792cecdea6a7c61", + "shasum": "" + }, + "require": { + "ext-pdo": "*", + "ext-simplexml": "*", + "php": ">=5.3.3", + "phpunit/phpunit": "~3.7|~4.0", + "symfony/yaml": "~2.1" + }, + "bin": [ + "composer/bin/dbunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "DbUnit port for PHP/PHPUnit to support database interaction testing.", + "homepage": "https://github.com/sebastianbergmann/dbunit/", + "keywords": [ + "database", + "testing", + "xunit" + ], + "abandoned": true, + "time": "2015-03-29T14:23:04+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "abandoned": true, + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "pimple/pimple", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "ae11e57e8c2bb414b2ff93396dbbfc0eb92feb94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/ae11e57e8c2bb414b2ff93396dbbfc0eb92feb94", + "reference": "ae11e57e8c2bb414b2ff93396dbbfc0eb92feb94", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2013-03-08T08:21:40+00:00" + }, + { + "name": "sami/sami", + "version": "v1.4.1", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/Sami.git", + "reference": "160018bfefffa730dc35a2c606691a45acbf41a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/Sami/zipball/160018bfefffa730dc35a2c606691a45acbf41a1", + "reference": "160018bfefffa730dc35a2c606691a45acbf41a1", + "shasum": "" + }, + "require": { + "michelf/php-markdown": "~1.3", + "nikic/php-parser": "0.9.*", + "php": ">=5.3.0", + "pimple/pimple": "1.0.*", + "symfony/console": "~2.1", + "symfony/filesystem": "~2.1", + "symfony/finder": "~2.1", + "symfony/process": "~2.1", + "symfony/yaml": "~2.1", + "twig/twig": "1.*" + }, + "bin": [ + "sami.php" + ], + "type": "application", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-0": { + "Sami": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Sami, an API documentation generator", + "homepage": "http://sami.sensiolabs.org", + "keywords": [ + "phpdoc" + ], + "abandoned": true, + "time": "2015-06-05T03:36:34+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21T13:59:46+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.9.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/2acf168de78487db620ab4bc524135a13cfe6745", + "reference": "2acf168de78487db620ab4bc524135a13cfe6745", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-11-07T22:31:41+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "b507697225f32a76a9d333d0766fb46353e9d00d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/b507697225f32a76a9d333d0766fb46353e9d00d", + "reference": "b507697225f32a76a9d333d0766fb46353e9d00d", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/dom-crawler": "~2.1|~3.0.0" + }, + "require-dev": { + "symfony/css-selector": "^2.0.5|~3.0.0", + "symfony/process": "~2.3.34|^2.7.6|~3.0.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2018-11-26T06:55:10+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "7b1692e418d7ccac24c373528453bc90e42797de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/7b1692e418d7ccac24c373528453bc90e42797de", + "reference": "7b1692e418d7ccac24c373528453bc90e42797de", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "2cdc7d3909eea6f982a6298d2e9ab7db01b6403c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2cdc7d3909eea6f982a6298d2e9ab7db01b6403c", + "reference": "2cdc7d3909eea6f982a6298d2e9ab7db01b6403c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0.0" + }, + "suggest": { + "symfony/css-selector": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "https://symfony.com", + "time": "2018-11-24T22:30:19+00:00" + }, + { + "name": "symfony/process", + "version": "v2.8.49", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8", + "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2018-11-11T11:18:13+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.4.7" + } +} diff --git a/config.php b/config.php new file mode 100644 index 0000000..e69de29 diff --git a/config/.htaccess b/config/.htaccess new file mode 100644 index 0000000..4128d34 --- /dev/null +++ b/config/.htaccess @@ -0,0 +1,4 @@ + + Order Allow,Deny + Deny from All + diff --git a/config/default/config.yml b/config/default/config.yml new file mode 100644 index 0000000..e8d0536 --- /dev/null +++ b/config/default/config.yml @@ -0,0 +1 @@ +# phpBB's config file (This line is needed because of the packager) diff --git a/config/default/container/parameters.yml b/config/default/container/parameters.yml new file mode 100644 index 0000000..8ecc142 --- /dev/null +++ b/config/default/container/parameters.yml @@ -0,0 +1,20 @@ +parameters: + # Disable the usage of the super globals (_GET, _POST, _SERVER...) + core.disable_super_globals: true + + # Datetime class to use + datetime.class: \phpbb\datetime + + # Mimetype guesser priorities + mimetype.guesser.priority.lowest: -2 + mimetype.guesser.priority.low: -1 + mimetype.guesser.priority.default: 0 + mimetype.guesser.priority.high: 1 + mimetype.guesser.priority.highest: 2 + + # List of default password driver types + passwords.algorithms: + - passwords.driver.bcrypt_2y + - passwords.driver.bcrypt + - passwords.driver.salted_md5 + - passwords.driver.phpass diff --git a/config/default/container/services.yml b/config/default/container/services.yml new file mode 100644 index 0000000..9bb1d67 --- /dev/null +++ b/config/default/container/services.yml @@ -0,0 +1,169 @@ +imports: + - { resource: services_attachment.yml } + - { resource: services_auth.yml } + - { resource: services_avatar.yml } + - { resource: services_captcha.yml } + - { resource: services_console.yml } + - { resource: services_content.yml } + - { resource: services_cron.yml } + - { resource: services_db.yml } + - { resource: services_event.yml } + - { resource: services_feed.yml } + - { resource: services_files.yml } + - { resource: services_filesystem.yml } + - { resource: services_help.yml } + - { resource: services_hook.yml } + - { resource: services_http.yml } + - { resource: services_language.yml } + - { resource: services_migrator.yml } + - { resource: services_mimetype_guesser.yml } + - { resource: services_module.yml } + - { resource: services_notification.yml } + - { resource: services_password.yml } + - { resource: services_php.yml } + - { resource: services_profilefield.yml } + - { resource: services_report.yml } + - { resource: services_routing.yml } + - { resource: services_text_formatter.yml } + - { resource: services_text_reparser.yml } + - { resource: services_twig.yml } + - { resource: services_user.yml } + + - { resource: tables.yml } + - { resource: parameters.yml } + +services: + cache: + class: phpbb\cache\service + arguments: + - '@cache.driver' + - '@config' + - '@dbal.conn' + - '%core.root_path%' + - '%core.php_ext%' + + cache.driver: + class: '%cache.driver.class%' + + class_loader: + class: phpbb\class_loader + arguments: + - phpbb\ + - '%core.root_path%includes/' + - '%core.php_ext%' + calls: + - [register, []] + - [set_cache, ['@cache.driver']] + + class_loader.ext: + class: phpbb\class_loader + arguments: + - \ + - '%core.root_path%ext/' + - '%core.php_ext%' + calls: + - [register, []] + - [set_cache, ['@cache.driver']] + + config: + class: phpbb\config\db + arguments: + - '@dbal.conn' + - '@cache.driver' + - '%tables.config%' + + config.php: + synthetic: true + + config_text: + class: phpbb\config\db_text + arguments: + - '@dbal.conn' + - '%tables.config_text%' + + controller.helper: + class: phpbb\controller\helper + arguments: + - '@template' + - '@user' + - '@config' + - '@symfony_request' + - '@request' + - '@routing.helper' + + controller.resolver: + class: phpbb\controller\resolver + arguments: + - '@service_container' + - '%core.root_path%' + - '@template' + + ext.manager: + class: phpbb\extension\manager + arguments: + - '@service_container' + - '@dbal.conn' + - '@config' + - '@filesystem' + - '%tables.ext%' + - '%core.root_path%' + - '%core.php_ext%' + - '@cache' + + file_downloader: + class: phpbb\file_downloader + + file_locator: + class: phpbb\routing\file_locator + arguments: + - '@filesystem' + - '%core.root_path%' + + group_helper: + class: phpbb\group\helper + arguments: + - '@language' + + log: + class: phpbb\log\log + arguments: + - '@dbal.conn' + - '@user' + - '@auth' + - '@dispatcher' + - '%core.root_path%' + - '%core.adm_relative_path%' + - '%core.php_ext%' + - '%tables.log%' + + path_helper: + class: phpbb\path_helper + arguments: + - '@symfony_request' + - '@filesystem' + - '@request' + - '%core.root_path%' + - '%core.php_ext%' + - '%core.adm_relative_path%' + + plupload: + class: phpbb\plupload\plupload + arguments: + - '%core.root_path%' + - '@config' + - '@request' + - '@user' + - '@php_ini' + - '@mimetype.guesser' + + upload_imagesize: + class: FastImageSize\FastImageSize + + version_helper: + class: phpbb\version_helper + shared: false + arguments: + - '@cache' + - '@config' + - '@file_downloader' + - '@user' diff --git a/config/default/container/services_attachment.yml b/config/default/container/services_attachment.yml new file mode 100644 index 0000000..c56ced2 --- /dev/null +++ b/config/default/container/services_attachment.yml @@ -0,0 +1,40 @@ +services: + attachment.delete: + class: phpbb\attachment\delete + shared: false + arguments: + - '@config' + - '@dbal.conn' + - '@dispatcher' + - '@filesystem' + - '@attachment.resync' + - '%core.root_path%' + + attachment.manager: + class: phpbb\attachment\manager + shared: false + arguments: + - '@attachment.delete' + - '@attachment.resync' + - '@attachment.upload' + + attachment.resync: + class: phpbb\attachment\resync + shared: false + arguments: + - '@dbal.conn' + + attachment.upload: + class: phpbb\attachment\upload + shared: false + arguments: + - '@auth' + - '@cache' + - '@config' + - '@files.upload' + - '@language' + - '@mimetype.guesser' + - '@dispatcher' + - '@plupload' + - '@user' + - '%core.root_path%' diff --git a/config/default/container/services_auth.yml b/config/default/container/services_auth.yml new file mode 100644 index 0000000..ed8dc90 --- /dev/null +++ b/config/default/container/services_auth.yml @@ -0,0 +1,110 @@ +services: +# ----- Auth management ----- + auth: + class: phpbb\auth\auth + +# ----- Auth providers ----- + auth.provider_collection: + class: phpbb\auth\provider_collection + arguments: + - '@service_container' + - '@config' + tags: + - { name: service_collection, tag: auth.provider } + + auth.provider.db: + class: phpbb\auth\provider\db + arguments: + - '@dbal.conn' + - '@config' + - '@passwords.manager' + - '@request' + - '@user' + - '@service_container' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: auth.provider } + + auth.provider.apache: + class: phpbb\auth\provider\apache + arguments: + - '@dbal.conn' + - '@config' + - '@passwords.manager' + - '@request' + - '@user' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: auth.provider } + + auth.provider.ldap: + class: phpbb\auth\provider\ldap + arguments: + - '@dbal.conn' + - '@config' + - '@passwords.manager' + - '@user' + tags: + - { name: auth.provider } + + auth.provider.oauth: + class: phpbb\auth\provider\oauth\oauth + arguments: + - '@dbal.conn' + - '@config' + - '@passwords.manager' + - '@request' + - '@user' + - '%tables.auth_provider_oauth_token_storage%' + - '%tables.auth_provider_oauth_states%' + - '%tables.auth_provider_oauth_account_assoc%' + - '@auth.provider.oauth.service_collection' + - '%tables.users%' + - '@service_container' + - '@dispatcher' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: auth.provider } + +# ----- OAuth services providers ----- + auth.provider.oauth.service_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: auth.provider.oauth.service } + + auth.provider.oauth.service.bitly: + class: phpbb\auth\provider\oauth\service\bitly + arguments: + - '@config' + - '@request' + tags: + - { name: auth.provider.oauth.service } + + auth.provider.oauth.service.facebook: + class: phpbb\auth\provider\oauth\service\facebook + arguments: + - '@config' + - '@request' + tags: + - { name: auth.provider.oauth.service } + + auth.provider.oauth.service.google: + class: phpbb\auth\provider\oauth\service\google + arguments: + - '@config' + - '@request' + tags: + - { name: auth.provider.oauth.service } + + auth.provider.oauth.service.twitter: + class: phpbb\auth\provider\oauth\service\twitter + arguments: + - '@config' + - '@request' + tags: + - { name: auth.provider.oauth.service } diff --git a/config/default/container/services_avatar.yml b/config/default/container/services_avatar.yml new file mode 100644 index 0000000..d96aa62 --- /dev/null +++ b/config/default/container/services_avatar.yml @@ -0,0 +1,73 @@ +services: + avatar.manager: + class: phpbb\avatar\manager + arguments: + - '@config' + - '@dispatcher' + - '@avatar.driver_collection' + +# ----- Avatar drivers ----- + avatar.driver_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: avatar.driver } + + avatar.driver.gravatar: + class: phpbb\avatar\driver\gravatar + arguments: + - '@config' + - '@upload_imagesize' + - '%core.root_path%' + - '%core.php_ext%' + - '@path_helper' + - '@cache.driver' + calls: + - [set_name, [avatar.driver.gravatar]] + tags: + - { name: avatar.driver } + + avatar.driver.local: + class: phpbb\avatar\driver\local + arguments: + - '@config' + - '@upload_imagesize' + - '%core.root_path%' + - '%core.php_ext%' + - '@path_helper' + - '@cache.driver' + calls: + - [set_name, [avatar.driver.local]] + tags: + - { name: avatar.driver } + + avatar.driver.remote: + class: phpbb\avatar\driver\remote + arguments: + - '@config' + - '@upload_imagesize' + - '%core.root_path%' + - '%core.php_ext%' + - '@path_helper' + - '@cache.driver' + calls: + - [set_name, [avatar.driver.remote]] + tags: + - { name: avatar.driver } + + avatar.driver.upload: + class: phpbb\avatar\driver\upload + arguments: + - '@config' + - '%core.root_path%' + - '%core.php_ext%' + - '@filesystem' + - '@path_helper' + - '@dispatcher' + - '@files.factory' + - '@cache.driver' + calls: + - [set_name, [avatar.driver.upload]] + tags: + - { name: avatar.driver } diff --git a/config/default/container/services_captcha.yml b/config/default/container/services_captcha.yml new file mode 100644 index 0000000..e462c43 --- /dev/null +++ b/config/default/container/services_captcha.yml @@ -0,0 +1,59 @@ +services: + captcha.factory: + class: phpbb\captcha\factory + arguments: + - '@service_container' + - '@captcha.plugins.service_collection' + +# ----- Captcha plugins ----- +# Service MUST NOT be shared for all the plugins to work. + captcha.plugins.service_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: captcha.plugins } + + core.captcha.plugins.gd: + class: phpbb\captcha\plugins\gd + shared: false + calls: + - [set_name, [core.captcha.plugins.gd]] + tags: + - { name: captcha.plugins } + + core.captcha.plugins.gd_wave: + class: phpbb\captcha\plugins\gd_wave + shared: false + calls: + - [set_name, [core.captcha.plugins.gd_wave]] + tags: + - { name: captcha.plugins } + + core.captcha.plugins.nogd: + class: phpbb\captcha\plugins\nogd + shared: false + calls: + - [set_name, [core.captcha.plugins.nogd]] + tags: + - { name: captcha.plugins } + + core.captcha.plugins.qa: + class: phpbb\captcha\plugins\qa + shared: false + arguments: + - '%tables.captcha_qa_questions%' + - '%tables.captcha_qa_answers%' + - '%tables.captcha_qa_confirm%' + calls: + - [set_name, [core.captcha.plugins.qa]] + tags: + - { name: captcha.plugins } + + core.captcha.plugins.recaptcha: + class: phpbb\captcha\plugins\recaptcha + shared: false + calls: + - [set_name, [core.captcha.plugins.recaptcha]] + tags: + - { name: captcha.plugins } diff --git a/config/default/container/services_console.yml b/config/default/container/services_console.yml new file mode 100644 index 0000000..05e467f --- /dev/null +++ b/config/default/container/services_console.yml @@ -0,0 +1,295 @@ +services: + console.exception_subscriber: + class: phpbb\console\exception_subscriber + arguments: + - '@language' + tags: + - { name: kernel.event_subscriber } + + console.command_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: console.command } + + console.command.cache.purge: + class: phpbb\console\command\cache\purge + arguments: + - '@user' + - '@cache.driver' + - '@dbal.conn' + - '@auth' + - '@log' + - '@config' + tags: + - { name: console.command } + + console.command.config.delete: + class: phpbb\console\command\config\delete + arguments: + - '@user' + - '@config' + tags: + - { name: console.command } + + console.command.config.increment: + class: phpbb\console\command\config\increment + arguments: + - '@user' + - '@config' + tags: + - { name: console.command } + + console.command.config.get: + class: phpbb\console\command\config\get + arguments: + - '@user' + - '@config' + tags: + - { name: console.command } + + console.command.config.set: + class: phpbb\console\command\config\set + arguments: + - '@user' + - '@config' + tags: + - { name: console.command } + + console.command.config.set_atomic: + class: phpbb\console\command\config\set_atomic + arguments: + - '@user' + - '@config' + tags: + - { name: console.command } + + console.command.cron.list: + class: phpbb\console\command\cron\cron_list + arguments: + - '@user' + - '@cron.manager' + tags: + - { name: console.command } + + console.command.cron.run: + class: phpbb\console\command\cron\run + arguments: + - '@user' + - '@cron.manager' + - '@cron.lock_db' + tags: + - { name: console.command } + + console.command.db.list: + class: phpbb\console\command\db\list_command + arguments: + - '@user' + - '@migrator' + - '@ext.manager' + - '@config' + - '@cache' + tags: + - { name: console.command } + + console.command.db.migrate: + class: phpbb\console\command\db\migrate + arguments: + - '@user' + - '@language' + - '@migrator' + - '@ext.manager' + - '@config' + - '@cache' + - '@log' + - '@filesystem' + - '%core.root_path%' + tags: + - { name: console.command } + + console.command.db.revert: + class: phpbb\console\command\db\revert + parent: console.command.db.migrate + tags: + - { name: console.command } + + console.command.dev.migration_tips: + class: phpbb\console\command\dev\migration_tips + arguments: + - '@user' + - '@ext.manager' + tags: + - { name: console.command } + + console.command.extension.disable: + class: phpbb\console\command\extension\disable + arguments: + - '@user' + - '@ext.manager' + - '@log' + tags: + - { name: console.command } + + console.command.extension.enable: + class: phpbb\console\command\extension\enable + arguments: + - '@user' + - '@ext.manager' + - '@log' + tags: + - { name: console.command } + + console.command.extension.purge: + class: phpbb\console\command\extension\purge + arguments: + - '@user' + - '@ext.manager' + - '@log' + tags: + - { name: console.command } + + console.command.extension.show: + class: phpbb\console\command\extension\show + arguments: + - '@user' + - '@ext.manager' + - '@log' + tags: + - { name: console.command } + + console.command.fixup.recalculate_email_hash: + class: phpbb\console\command\fixup\recalculate_email_hash + arguments: + - '@user' + - '@dbal.conn' + tags: + - { name: console.command } + + console.command.fixup.update_hashes: + class: phpbb\console\command\fixup\update_hashes + arguments: + - '@config' + - '@user' + - '@dbal.conn' + - '@passwords.manager' + - '@passwords.driver_collection' + - '%passwords.algorithms%' + tags: + - { name: console.command } + + console.command.fixup.fix_left_right_ids: + class: phpbb\console\command\fixup\fix_left_right_ids + arguments: + - '@user' + - '@dbal.conn' + - '@cache.driver' + tags: + - { name: console.command } + + console.command.reparser.list: + class: phpbb\console\command\reparser\list_all + arguments: + - '@user' + - '@text_reparser_collection' + tags: + - { name: console.command } + + console.command.reparser.reparse: + class: phpbb\console\command\reparser\reparse + arguments: + - '@user' + - '@text_reparser.lock' + - '@text_reparser.manager' + - '@text_reparser_collection' + tags: + - { name: console.command } + + console.command.thumbnail.delete: + class: phpbb\console\command\thumbnail\delete + arguments: + - '@config' + - '@user' + - '@dbal.conn' + - '%core.root_path%' + tags: + - { name: console.command } + + console.command.thumbnail.generate: + class: phpbb\console\command\thumbnail\generate + arguments: + - '@config' + - '@user' + - '@dbal.conn' + - '@cache' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: console.command } + + console.command.thumbnail.recreate: + class: phpbb\console\command\thumbnail\recreate + arguments: + - '@user' + tags: + - { name: console.command } + + console.command.update.check: + class: phpbb\console\command\update\check + arguments: + - '@user' + - '@config' + - '@service_container' + - '@language' + tags: + - { name: console.command } + + console.command.user.activate: + class: phpbb\console\command\user\activate + arguments: + - '@user' + - '@dbal.conn' + - '@config' + - '@language' + - '@log' + - '@notification_manager' + - '@user_loader' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: console.command } + + console.command.user.add: + class: phpbb\console\command\user\add + arguments: + - '@user' + - '@dbal.conn' + - '@config' + - '@language' + - '@passwords.manager' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: console.command } + + console.command.user.delete: + class: phpbb\console\command\user\delete + arguments: + - '@user' + - '@dbal.conn' + - '@language' + - '@log' + - '@user_loader' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: console.command } + + console.command.user.reclean: + class: phpbb\console\command\user\reclean + arguments: + - '@user' + - '@dbal.conn' + - '@language' + tags: + - { name: console.command } diff --git a/config/default/container/services_content.yml b/config/default/container/services_content.yml new file mode 100644 index 0000000..6717c20 --- /dev/null +++ b/config/default/container/services_content.yml @@ -0,0 +1,73 @@ +services: + content.visibility: + class: phpbb\content_visibility + arguments: + - '@auth' + - '@config' + - '@dispatcher' + - '@dbal.conn' + - '@user' + - '%core.root_path%' + - '%core.php_ext%' + - '%tables.forums%' + - '%tables.posts%' + - '%tables.topics%' + - '%tables.users%' + + groupposition.legend: + class: phpbb\groupposition\legend + arguments: + - '@dbal.conn' + - '@user' + + groupposition.teampage: + class: phpbb\groupposition\teampage + arguments: + - '@dbal.conn' + - '@user' + - '@cache.driver' + + message.form.admin: + class: phpbb\message\admin_form + arguments: + - '@auth' + - '@config' + - '@config_text' + - '@dbal.conn' + - '@user' + - '@dispatcher' + - '%core.root_path%' + - '%core.php_ext%' + + message.form.topic: + class: phpbb\message\topic_form + arguments: + - '@auth' + - '@config' + - '@dbal.conn' + - '@user' + - '%core.root_path%' + - '%core.php_ext%' + + message.form.user: + class: phpbb\message\user_form + arguments: + - '@auth' + - '@config' + - '@dbal.conn' + - '@user' + - '%core.root_path%' + - '%core.php_ext%' + + pagination: + class: phpbb\pagination + arguments: + - '@template' + - '@user' + - '@controller.helper' + - '@dispatcher' + + viewonline_helper: + class: phpbb\viewonline_helper + arguments: + - '@filesystem' diff --git a/config/default/container/services_cron.yml b/config/default/container/services_cron.yml new file mode 100644 index 0000000..d7f6388 --- /dev/null +++ b/config/default/container/services_cron.yml @@ -0,0 +1,235 @@ +services: + cron.manager: + class: phpbb\cron\manager + arguments: + - '@cron.task_collection' + - '%core.root_path%' + - '%core.php_ext%' + + cron.lock_db: + class: phpbb\lock\db + arguments: + - cron_lock + - '@config' + - '@dbal.conn' + +# ----- Cron tasks ----- + cron.task_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: cron.task } + + cron.task.core.prune_all_forums: + class: phpbb\cron\task\core\prune_all_forums + arguments: + - '%core.root_path%' + - '%core.php_ext%' + - '@config' + - '@dbal.conn' + calls: + - [set_name, [cron.task.core.prune_all_forums]] + tags: + - { name: cron.task } + + cron.task.core.prune_forum: + class: phpbb\cron\task\core\prune_forum + arguments: + - '%core.root_path%' + - '%core.php_ext%' + - '@config' + - '@dbal.conn' + calls: + - [set_name, [cron.task.core.prune_forum]] + tags: + - { name: cron.task } + + cron.task.core.prune_shadow_topics: + class: phpbb\cron\task\core\prune_shadow_topics + arguments: + - '%core.root_path%' + - '%core.php_ext%' + - '@config' + - '@dbal.conn' + - '@log' + - '@user' + calls: + - [set_name, [cron.task.core.prune_shadow_topics]] + tags: + - { name: cron.task } + + cron.task.core.prune_notifications: + class: phpbb\cron\task\core\prune_notifications + arguments: + - '@config' + - '@notification_manager' + calls: + - [set_name, [cron.task.core.prune_notifications]] + tags: + - { name: cron.task } + + cron.task.core.queue: + class: phpbb\cron\task\core\queue + arguments: + - '%core.root_path%' + - '%core.php_ext%' + - '@config' + - '%core.cache_dir%' + calls: + - [set_name, [cron.task.core.queue]] + tags: + - { name: cron.task } + + cron.task.core.tidy_cache: + class: phpbb\cron\task\core\tidy_cache + arguments: + - '@config' + - '@cache.driver' + calls: + - [set_name, [cron.task.core.tidy_cache]] + tags: + - { name: cron.task } + + cron.task.core.tidy_database: + class: phpbb\cron\task\core\tidy_database + arguments: + - '%core.root_path%' + - '%core.php_ext%' + - '@config' + calls: + - [set_name, [cron.task.core.tidy_database]] + tags: + - { name: cron.task } + + cron.task.core.tidy_plupload: + class: phpbb\cron\task\core\tidy_plupload + arguments: + - '%core.root_path%' + - '@config' + - '@log' + - '@user' + calls: + - [set_name, [cron.task.core.tidy_plupload]] + tags: + - { name: cron.task } + + cron.task.core.tidy_search: + class: phpbb\cron\task\core\tidy_search + arguments: + - '%core.root_path%' + - '%core.php_ext%' + - '@auth' + - '@config' + - '@dbal.conn' + - '@user' + - '@dispatcher' + calls: + - [set_name, [cron.task.core.tidy_search]] + tags: + - { name: cron.task } + + cron.task.core.tidy_sessions: + class: phpbb\cron\task\core\tidy_sessions + arguments: + - '@config' + - '@user' + calls: + - [set_name, [cron.task.core.tidy_sessions]] + tags: + - { name: cron.task } + + cron.task.core.tidy_warnings: + class: phpbb\cron\task\core\tidy_warnings + arguments: + - '%core.root_path%' + - '%core.php_ext%' + - '@config' + calls: + - [set_name, [cron.task.core.tidy_warnings]] + tags: + - { name: cron.task } + + cron.task.text_reparser.pm_text: + class: phpbb\cron\task\text_reparser\reparser + arguments: + - '@config' + - '@config_text' + - '@text_reparser.lock' + - '@text_reparser.manager' + - '@text_reparser_collection' + calls: + - [set_name, [cron.task.text_reparser.pm_text]] + - [set_reparser, [text_reparser.pm_text]] + tags: + - { name: cron.task } + + cron.task.text_reparser.poll_option: + class: phpbb\cron\task\text_reparser\reparser + arguments: + - '@config' + - '@config_text' + - '@text_reparser.lock' + - '@text_reparser.manager' + - '@text_reparser_collection' + calls: + - [set_name, [cron.task.text_reparser.poll_option]] + - [set_reparser, [text_reparser.poll_option]] + tags: + - { name: cron.task } + + cron.task.text_reparser.poll_title: + class: phpbb\cron\task\text_reparser\reparser + arguments: + - '@config' + - '@config_text' + - '@text_reparser.lock' + - '@text_reparser.manager' + - '@text_reparser_collection' + calls: + - [set_name, [cron.task.text_reparser.poll_title]] + - [set_reparser, [text_reparser.poll_title]] + tags: + - { name: cron.task } + + cron.task.text_reparser.post_text: + class: phpbb\cron\task\text_reparser\reparser + arguments: + - '@config' + - '@config_text' + - '@text_reparser.lock' + - '@text_reparser.manager' + - '@text_reparser_collection' + calls: + - [set_name, [cron.task.text_reparser.post_text]] + - [set_reparser, [text_reparser.post_text]] + tags: + - { name: cron.task } + + cron.task.text_reparser.user_signature: + class: phpbb\cron\task\text_reparser\reparser + arguments: + - '@config' + - '@config_text' + - '@text_reparser.lock' + - '@text_reparser.manager' + - '@text_reparser_collection' + calls: + - [set_name, [cron.task.text_reparser.user_signature]] + - [set_reparser, [text_reparser.user_signature]] + tags: + - { name: cron.task } + + cron.task.core.update_hashes: + class: phpbb\cron\task\core\update_hashes + arguments: + - '@config' + - '@dbal.conn' + - '@passwords.update.lock' + - '@passwords.manager' + - '@passwords.driver_collection' + - '%passwords.algorithms%' + calls: + - [set_name, [cron.task.core.update_hashes]] + tags: + - { name: cron.task } diff --git a/config/default/container/services_db.yml b/config/default/container/services_db.yml new file mode 100644 index 0000000..d538177 --- /dev/null +++ b/config/default/container/services_db.yml @@ -0,0 +1,71 @@ +services: + dbal.conn: + class: phpbb\db\driver\factory + arguments: + - '@service_container' + + dbal.conn.driver: + synthetic: true + +# ----- DB Tools ----- + dbal.tools.factory: + class: phpbb\db\tools\factory + + dbal.tools: + class: phpbb\db\tools\tools_interface + factory: ['@dbal.tools.factory', get] + arguments: + - '@dbal.conn.driver' + +# ----- DB Extractor ----- + dbal.extractor.factory: + class: phpbb\db\extractor\factory + arguments: + - '@dbal.conn.driver' + - '@service_container' + + dbal.extractor: + class: phpbb\db\extractor\extractor_interface + factory: ['@dbal.extractor.factory', get] + +# ----- DB Extractors for different drivers ----- +# Service MUST NOT be shared for all the handlers to work correctly. + dbal.extractor.extractors.mssql_extractor: + class: phpbb\db\extractor\mssql_extractor + shared: false + arguments: + - '%core.root_path%' + - '@request' + - '@dbal.conn.driver' + + dbal.extractor.extractors.mysql_extractor: + class: phpbb\db\extractor\mysql_extractor + shared: false + arguments: + - '%core.root_path%' + - '@request' + - '@dbal.conn.driver' + + dbal.extractor.extractors.oracle_extractor: + class: phpbb\db\extractor\oracle_extractor + shared: false + arguments: + - '%core.root_path%' + - '@request' + - '@dbal.conn.driver' + + dbal.extractor.extractors.postgres_extractor: + class: phpbb\db\extractor\postgres_extractor + shared: false + arguments: + - '%core.root_path%' + - '@request' + - '@dbal.conn.driver' + + dbal.extractor.extractors.sqlite3_extractor: + class: phpbb\db\extractor\sqlite3_extractor + shared: false + arguments: + - '%core.root_path%' + - '@request' + - '@dbal.conn.driver' diff --git a/config/default/container/services_event.yml b/config/default/container/services_event.yml new file mode 100644 index 0000000..5696275 --- /dev/null +++ b/config/default/container/services_event.yml @@ -0,0 +1,26 @@ +services: + dispatcher: + class: phpbb\event\dispatcher + arguments: + - '@service_container' + + kernel_exception_subscriber: + class: phpbb\event\kernel_exception_subscriber + arguments: + - '@template' + - '@language' + - '%debug.exceptions%' + tags: + - { name: kernel.event_subscriber } + + kernel_terminate_subscriber: + class: phpbb\event\kernel_terminate_subscriber + tags: + - { name: kernel.event_subscriber } + + symfony_response_listener: + class: Symfony\Component\HttpKernel\EventListener\ResponseListener + arguments: + - UTF-8 + tags: + - { name: kernel.event_subscriber } diff --git a/config/default/container/services_feed.yml b/config/default/container/services_feed.yml new file mode 100644 index 0000000..e8bac4b --- /dev/null +++ b/config/default/container/services_feed.yml @@ -0,0 +1,126 @@ +services: + phpbb.feed.controller: + class: phpbb\feed\controller\feed + arguments: + - '@template.twig.environment' + - '@symfony_request' + - '@controller.helper' + - '@config' + - '@dbal.conn' + - '@service_container' + - '@feed.helper' + - '@user' + - '@auth' + - '@dispatcher' + - '%core.php_ext%' + + feed.helper: + class: phpbb\feed\helper + arguments: + - '@config' + - '@service_container' + - '@path_helper' + - '@text_formatter.renderer' + - '@user' + + feed.forum: + class: phpbb\feed\forum + shared: false + arguments: + - '@feed.helper' + - '@config' + - '@dbal.conn' + - '@cache.driver' + - '@user' + - '@auth' + - '@content.visibility' + - '@dispatcher' + - '%core.php_ext%' + + feed.forums: + class: phpbb\feed\forums + shared: false + arguments: + - '@feed.helper' + - '@config' + - '@dbal.conn' + - '@cache.driver' + - '@user' + - '@auth' + - '@content.visibility' + - '@dispatcher' + - '%core.php_ext%' + + feed.news: + class: phpbb\feed\news + shared: false + arguments: + - '@feed.helper' + - '@config' + - '@dbal.conn' + - '@cache.driver' + - '@user' + - '@auth' + - '@content.visibility' + - '@dispatcher' + - '%core.php_ext%' + + feed.overall: + class: phpbb\feed\overall + shared: false + arguments: + - '@feed.helper' + - '@config' + - '@dbal.conn' + - '@cache.driver' + - '@user' + - '@auth' + - '@content.visibility' + - '@dispatcher' + - '%core.php_ext%' + + feed.quote_helper: + class: phpbb\feed\quote_helper + parent: text_formatter.s9e.quote_helper + + feed.topic: + class: phpbb\feed\topic + shared: false + arguments: + - '@feed.helper' + - '@config' + - '@dbal.conn' + - '@cache.driver' + - '@user' + - '@auth' + - '@content.visibility' + - '@dispatcher' + - '%core.php_ext%' + + feed.topics: + class: phpbb\feed\topics + shared: false + arguments: + - '@feed.helper' + - '@config' + - '@dbal.conn' + - '@cache.driver' + - '@user' + - '@auth' + - '@content.visibility' + - '@dispatcher' + - '%core.php_ext%' + + feed.topics_active: + class: phpbb\feed\topics_active + shared: false + arguments: + - '@feed.helper' + - '@config' + - '@dbal.conn' + - '@cache.driver' + - '@user' + - '@auth' + - '@content.visibility' + - '@dispatcher' + - '%core.php_ext%' diff --git a/config/default/container/services_files.yml b/config/default/container/services_files.yml new file mode 100644 index 0000000..ba1fdb4 --- /dev/null +++ b/config/default/container/services_files.yml @@ -0,0 +1,57 @@ +services: + files.factory: + class: phpbb\files\factory + arguments: + - '@service_container' + + files.filespec: + class: phpbb\files\filespec + shared: false + arguments: + - '@filesystem' + - '@language' + - '@php_ini' + - '@upload_imagesize' + - '%core.root_path%' + - '@mimetype.guesser' + - '@plupload' + + files.upload: + class: phpbb\files\upload + shared: false + arguments: + - '@filesystem' + - '@files.factory' + - '@language' + - '@php_ini' + - '@request' + + files.types.form: + class: phpbb\files\types\form + shared: false + arguments: + - '@files.factory' + - '@language' + - '@php_ini' + - '@plupload' + - '@request' + + files.types.local: + class: phpbb\files\types\local + shared: false + arguments: + - '@files.factory' + - '@language' + - '@php_ini' + - '@request' + + files.types.remote: + class: phpbb\files\types\remote + shared: false + arguments: + - '@config' + - '@files.factory' + - '@language' + - '@php_ini' + - '@request' + - '%core.root_path%' diff --git a/config/default/container/services_filesystem.yml b/config/default/container/services_filesystem.yml new file mode 100644 index 0000000..828f907 --- /dev/null +++ b/config/default/container/services_filesystem.yml @@ -0,0 +1,3 @@ +services: + filesystem: + class: phpbb\filesystem\filesystem diff --git a/config/default/container/services_help.yml b/config/default/container/services_help.yml new file mode 100644 index 0000000..1bff001 --- /dev/null +++ b/config/default/container/services_help.yml @@ -0,0 +1,27 @@ +services: + phpbb.help.manager: + class: phpbb\help\manager + arguments: + - '@dispatcher' + - '@language' + - '@template' + + phpbb.help.controller.bbcode: + class: phpbb\help\controller\bbcode + arguments: + - '@controller.helper' + - '@phpbb.help.manager' + - '@template' + - '@language' + - '%core.root_path%' + - '%core.php_ext%' + + phpbb.help.controller.faq: + class: phpbb\help\controller\faq + arguments: + - '@controller.helper' + - '@phpbb.help.manager' + - '@template' + - '@language' + - '%core.root_path%' + - '%core.php_ext%' diff --git a/config/default/container/services_hook.yml b/config/default/container/services_hook.yml new file mode 100644 index 0000000..10a8418 --- /dev/null +++ b/config/default/container/services_hook.yml @@ -0,0 +1,7 @@ +services: + hook_finder: + class: phpbb\hook\finder + arguments: + - '%core.root_path%' + - '%core.php_ext%' + - '@cache.driver' diff --git a/config/default/container/services_http.yml b/config/default/container/services_http.yml new file mode 100644 index 0000000..49cfbf5 --- /dev/null +++ b/config/default/container/services_http.yml @@ -0,0 +1,23 @@ +services: + http_kernel: + class: Symfony\Component\HttpKernel\HttpKernel + arguments: + - '@dispatcher' + - '@controller.resolver' + - '@request_stack' + + # WARNING: The Symfony request does not escape the input and should be used very carefully + # prefer the phpbb request (service @request) as possible + symfony_request: + class: phpbb\symfony_request + arguments: + - '@request' + + request_stack: + class: Symfony\Component\HttpFoundation\RequestStack + + request: + class: phpbb\request\request + arguments: + - null + - '%core.disable_super_globals%' diff --git a/config/default/container/services_language.yml b/config/default/container/services_language.yml new file mode 100644 index 0000000..8201fbf --- /dev/null +++ b/config/default/container/services_language.yml @@ -0,0 +1,22 @@ +services: + language.helper.language_file: + class: phpbb\language\language_file_helper + arguments: + - '%core.root_path%' + + language: + class: phpbb\language\language + arguments: + - '@language.loader' + + language.loader_abstract: + abstract: true + class: phpbb\language\language_file_loader + arguments: + - '%core.root_path%' + - '%core.php_ext%' + + language.loader: + parent: language.loader_abstract + calls: + - [set_extension_manager, ['@ext.manager']] diff --git a/config/default/container/services_migrator.yml b/config/default/container/services_migrator.yml new file mode 100644 index 0000000..c63b087 --- /dev/null +++ b/config/default/container/services_migrator.yml @@ -0,0 +1,64 @@ +services: +# ----- Migrator ----- + migrator: + class: phpbb\db\migrator + arguments: + - '@service_container' + - '@config' + - '@dbal.conn' + - '@dbal.tools' + - '%tables.migrations%' + - '%core.root_path%' + - '%core.php_ext%' + - '%core.table_prefix%' + - '@migrator.tool_collection' + - '@migrator.helper' + + migrator.helper: + class: phpbb\db\migration\helper + +# ----- Migrator's tools ----- + migrator.tool_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: migrator.tool } + + migrator.tool.config: + class: phpbb\db\migration\tool\config + arguments: + - '@config' + tags: + - { name: migrator.tool } + + migrator.tool.config_text: + class: phpbb\db\migration\tool\config_text + arguments: + - '@config_text' + tags: + - { name: migrator.tool } + + migrator.tool.module: + class: phpbb\db\migration\tool\module + arguments: + - '@dbal.conn' + - '@cache' + - '@user' + - '@module.manager' + - '%core.root_path%' + - '%core.php_ext%' + - '%tables.modules%' + tags: + - { name: migrator.tool } + + migrator.tool.permission: + class: phpbb\db\migration\tool\permission + arguments: + - '@dbal.conn' + - '@cache' + - '@auth' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: migrator.tool } diff --git a/config/default/container/services_mimetype_guesser.yml b/config/default/container/services_mimetype_guesser.yml new file mode 100644 index 0000000..432470d --- /dev/null +++ b/config/default/container/services_mimetype_guesser.yml @@ -0,0 +1,36 @@ +services: + mimetype.guesser_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: mimetype.guessers } + + mimetype.fileinfo_mimetype_guesser: + class: Symfony\Component\HttpFoundation\File\MimeType\FileinfoMimeTypeGuesser + tags: + - { name: mimetype.guessers } + + mimetype.filebinary_mimetype_guesser: + class: Symfony\Component\HttpFoundation\File\MimeType\FileBinaryMimeTypeGuesser + tags: + - { name: mimetype.guessers } + + mimetype.content_guesser: + class: phpbb\mimetype\content_guesser + calls: + - [set_priority, ['%mimetype.guesser.priority.low%']] + tags: + - { name: mimetype.guessers } + + mimetype.extension_guesser: + class: phpbb\mimetype\extension_guesser + calls: + - [set_priority, ['%mimetype.guesser.priority.lowest%']] + tags: + - { name: mimetype.guessers } + + mimetype.guesser: + class: phpbb\mimetype\guesser + arguments: + - '@mimetype.guesser_collection' diff --git a/config/default/container/services_module.yml b/config/default/container/services_module.yml new file mode 100644 index 0000000..a057e55 --- /dev/null +++ b/config/default/container/services_module.yml @@ -0,0 +1,10 @@ +services: + module.manager: + class: phpbb\module\module_manager + arguments: + - '@cache.driver' + - '@dbal.conn' + - '@ext.manager' + - '%tables.modules%' + - '%core.root_path%' + - '%core.php_ext%' diff --git a/config/default/container/services_notification.yml b/config/default/container/services_notification.yml new file mode 100644 index 0000000..6c3cea3 --- /dev/null +++ b/config/default/container/services_notification.yml @@ -0,0 +1,224 @@ +services: + notification_manager: + class: phpbb\notification\manager + arguments: + - '@notification.type_collection' + - '@notification.method_collection' + - '@service_container' + - '@user_loader' + - '@dispatcher' + - '@dbal.conn' + - '@cache' + - '@language' + - '@user' + - '%tables.notification_types%' + - '%tables.user_notifications%' + +# ----- Notification's types ----- +# Service MUST NOT be shared for all the plugins to work. + notification.type_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: notification.type } + + notification.type.base: + abstract: true + arguments: + - '@dbal.conn' + - '@language' + - '@user' + - '@auth' + - '%core.root_path%' + - '%core.php_ext%' + - '%tables.user_notifications%' + + notification.type.admin_activate_user: + class: phpbb\notification\type\admin_activate_user + shared: false + parent: notification.type.base + calls: + - [set_user_loader, ['@user_loader']] + - [set_config, ['@config']] + tags: + - { name: notification.type } + + notification.type.approve_post: + class: phpbb\notification\type\approve_post + shared: false + parent: notification.type.post + tags: + - { name: notification.type } + + notification.type.approve_topic: + class: phpbb\notification\type\approve_topic + shared: false + parent: notification.type.topic + tags: + - { name: notification.type } + + notification.type.bookmark: + class: phpbb\notification\type\bookmark + shared: false + parent: notification.type.post + tags: + - { name: notification.type } + + notification.type.disapprove_post: + class: phpbb\notification\type\disapprove_post + shared: false + parent: notification.type.post + tags: + - { name: notification.type } + + notification.type.disapprove_topic: + class: phpbb\notification\type\disapprove_topic + shared: false + parent: notification.type.topic + tags: + - { name: notification.type } + + notification.type.group_request: + class: phpbb\notification\type\group_request + shared: false + parent: notification.type.base + calls: + - [set_user_loader, ['@user_loader']] + tags: + - { name: notification.type } + + notification.type.group_request_approved: + class: phpbb\notification\type\group_request_approved + shared: false + parent: notification.type.base + tags: + - { name: notification.type } + + notification.type.pm: + class: phpbb\notification\type\pm + shared: false + parent: notification.type.base + calls: + - [set_user_loader, ['@user_loader']] + - [set_config, ['@config']] + tags: + - { name: notification.type } + + notification.type.post: + class: phpbb\notification\type\post + shared: false + parent: notification.type.base + calls: + - [set_user_loader, ['@user_loader']] + - [set_config, ['@config']] + tags: + - { name: notification.type } + + notification.type.post_in_queue: + class: phpbb\notification\type\post_in_queue + shared: false + parent: notification.type.post + tags: + - { name: notification.type } + + notification.type.quote: + class: phpbb\notification\type\quote + shared: false + parent: notification.type.post + calls: + - [set_utils, ['@text_formatter.utils']] + tags: + - { name: notification.type } + + notification.type.report_pm: + class: phpbb\notification\type\report_pm + shared: false + parent: notification.type.pm + tags: + - { name: notification.type } + + notification.type.report_pm_closed: + class: phpbb\notification\type\report_pm_closed + shared: false + parent: notification.type.pm + tags: + - { name: notification.type } + + notification.type.report_post: + class: phpbb\notification\type\report_post + shared: false + parent: notification.type.post + tags: + - { name: notification.type } + + notification.type.report_post_closed: + class: phpbb\notification\type\report_post_closed + shared: false + parent: notification.type.post + tags: + - { name: notification.type } + + notification.type.topic: + class: phpbb\notification\type\topic + shared: false + parent: notification.type.base + calls: + - [set_user_loader, ['@user_loader']] + - [set_config, ['@config']] + tags: + - { name: notification.type } + + notification.type.topic_in_queue: + class: phpbb\notification\type\topic_in_queue + shared: false + parent: notification.type.topic + tags: + - { name: notification.type } + +# ----- Notification's methods ----- +# Service MUST NOT be shared for all the plugins to work. + notification.method_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: notification.method } + + notification.method.board: + class: phpbb\notification\method\board + shared: false + arguments: + - '@user_loader' + - '@dbal.conn' + - '@cache.driver' + - '@user' + - '@config' + - '%tables.notification_types%' + - '%tables.notifications%' + tags: + - { name: notification.method } + + notification.method.email: + class: phpbb\notification\method\email + shared: false + arguments: + - '@user_loader' + - '@user' + - '@config' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: notification.method } + + notification.method.jabber: + class: phpbb\notification\method\jabber + shared: false + arguments: + - '@user_loader' + - '@user' + - '@config' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: notification.method } diff --git a/config/default/container/services_password.yml b/config/default/container/services_password.yml new file mode 100644 index 0000000..d5f5fe2 --- /dev/null +++ b/config/default/container/services_password.yml @@ -0,0 +1,136 @@ +parameters: + passwords.driver.bcrypt_cost: 10 + +services: +# ----- Password management ----- + passwords.manager: + class: phpbb\passwords\manager + arguments: + - '@config' + - '@passwords.driver_collection' + - '@passwords.helper' + - '%passwords.algorithms%' + + passwords.helper: + class: phpbb\passwords\helper + + passwords.driver_helper: + class: phpbb\passwords\driver\helper + arguments: + - '@config' + +# ----- Password's drivers ----- + passwords.driver_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: passwords.driver } + + passwords.driver.bcrypt: + class: phpbb\passwords\driver\bcrypt + arguments: + - '@config' + - '@passwords.driver_helper' + - '%passwords.driver.bcrypt_cost%' + tags: + - { name: passwords.driver } + + passwords.driver.bcrypt_2y: + class: phpbb\passwords\driver\bcrypt_2y + arguments: + - '@config' + - '@passwords.driver_helper' + - '%passwords.driver.bcrypt_cost%' + tags: + - { name: passwords.driver } + + passwords.driver.bcrypt_wcf2: + class: phpbb\passwords\driver\bcrypt_wcf2 + arguments: + - '@passwords.driver.bcrypt' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.driver.salted_md5: + class: phpbb\passwords\driver\salted_md5 + arguments: + - '@config' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.driver.phpass: + class: phpbb\passwords\driver\phpass + arguments: + - '@config' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.driver.convert_password: + class: phpbb\passwords\driver\convert_password + arguments: + - '@config' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.driver.sha1_smf: + class: phpbb\passwords\driver\sha1_smf + arguments: + - '@config' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.driver.sha1_wcf1: + class: phpbb\passwords\driver\sha1_wcf1 + arguments: + - '@config' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.driver.sha1: + class: phpbb\passwords\driver\sha1 + arguments: + - '@config' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.driver.md5_phpbb2: + class: phpbb\passwords\driver\md5_phpbb2 + arguments: + - '@request' + - '@passwords.driver.salted_md5' + - '@passwords.driver_helper' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: passwords.driver } + + passwords.driver.md5_mybb: + class: phpbb\passwords\driver\md5_mybb + arguments: + - '@config' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.driver.md5_vb: + class: phpbb\passwords\driver\md5_vb + arguments: + - '@config' + - '@passwords.driver_helper' + tags: + - { name: passwords.driver } + + passwords.update.lock: + class: phpbb\lock\db + arguments: + - update_hashes_lock + - '@config' + - '@dbal.conn' diff --git a/config/default/container/services_php.yml b/config/default/container/services_php.yml new file mode 100644 index 0000000..2934996 --- /dev/null +++ b/config/default/container/services_php.yml @@ -0,0 +1,3 @@ +services: + php_ini: + class: bantu\IniGetWrapper\IniGetWrapper diff --git a/config/default/container/services_profilefield.yml b/config/default/container/services_profilefield.yml new file mode 100644 index 0000000..90b2283 --- /dev/null +++ b/config/default/container/services_profilefield.yml @@ -0,0 +1,102 @@ +services: + profilefields.manager: + class: phpbb\profilefields\manager + arguments: + - '@auth' + - '@dbal.conn' + - '@dispatcher' + - '@request' + - '@template' + - '@profilefields.type_collection' + - '@user' + - '%tables.profile_fields%' + - '%tables.profile_fields_language%' + - '%tables.profile_fields_data%' + + profilefields.lang_helper: + class: phpbb\profilefields\lang_helper + arguments: + - '@dbal.conn' + - '%tables.profile_fields_options_language%' + +# ----- Profile fields types ----- + profilefields.type_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: profilefield.type } + + profilefields.type.bool: + class: phpbb\profilefields\type\type_bool + arguments: + - '@profilefields.lang_helper' + - '@request' + - '@template' + - '@user' + tags: + - { name: profilefield.type } + + profilefields.type.date: + class: phpbb\profilefields\type\type_date + arguments: + - '@request' + - '@template' + - '@user' + tags: + - { name: profilefield.type } + + profilefields.type.dropdown: + class: phpbb\profilefields\type\type_dropdown + arguments: + - '@profilefields.lang_helper' + - '@request' + - '@template' + - '@user' + tags: + - { name: profilefield.type } + + profilefields.type.googleplus: + class: phpbb\profilefields\type\type_googleplus + arguments: + - '@request' + - '@template' + - '@user' + tags: + - { name: profilefield.type } + + profilefields.type.int: + class: phpbb\profilefields\type\type_int + arguments: + - '@request' + - '@template' + - '@user' + tags: + - { name: profilefield.type } + + profilefields.type.string: + class: phpbb\profilefields\type\type_string + arguments: + - '@request' + - '@template' + - '@user' + tags: + - { name: profilefield.type } + + profilefields.type.text: + class: phpbb\profilefields\type\type_text + arguments: + - '@request' + - '@template' + - '@user' + tags: + - { name: profilefield.type } + + profilefields.type.url: + class: phpbb\profilefields\type\type_url + arguments: + - '@request' + - '@template' + - '@user' + tags: + - { name: profilefield.type } diff --git a/config/default/container/services_report.yml b/config/default/container/services_report.yml new file mode 100644 index 0000000..2c5b3bf --- /dev/null +++ b/config/default/container/services_report.yml @@ -0,0 +1,53 @@ +services: +# ----- Report controller ----- + phpbb.report.controller: + class: phpbb\report\controller\report + arguments: + - '@config' + - '@user' + - '@template' + - '@controller.helper' + - '@request' + - '@captcha.factory' + - '@phpbb.report.handler_factory' + - '@phpbb.report.report_reason_list_provider' + - '%core.root_path%' + - '%core.php_ext%' + +# ----- Report handler factory ----- + phpbb.report.handler_factory: + class: phpbb\report\handler_factory + arguments: + - '@service_container' + +# ----- Report UI provider ----- + phpbb.report.report_reason_list_provider: + class: phpbb\report\report_reason_list_provider + arguments: + - '@dbal.conn.driver' + - '@template' + - '@user' + +# ----- Report handlers ----- +# Service MUST NOT be shared for all the handlers to work correctly. + phpbb.report.handlers.report_handler_pm: + class: phpbb\report\report_handler_pm + shared: false + arguments: + - '@dbal.conn.driver' + - '@dispatcher' + - '@config' + - '@auth' + - '@user' + - '@notification_manager' + + phpbb.report.handlers.report_handler_post: + class: phpbb\report\report_handler_post + shared: false + arguments: + - '@dbal.conn.driver' + - '@dispatcher' + - '@config' + - '@auth' + - '@user' + - '@notification_manager' diff --git a/config/default/container/services_routing.yml b/config/default/container/services_routing.yml new file mode 100644 index 0000000..3048145 --- /dev/null +++ b/config/default/container/services_routing.yml @@ -0,0 +1,79 @@ +services: + router: + class: phpbb\routing\router + arguments: + - '@service_container' + - '@routing.chained_resources_locator' + - '@routing.delegated_loader' + - '%core.php_ext%' + - '%core.cache_dir%' + + router.listener: + class: Symfony\Component\HttpKernel\EventListener\RouterListener + arguments: + - '@router' + - null + - null + - '@request_stack' + tags: + - { name: kernel.event_subscriber } + + routing.helper: + class: phpbb\routing\helper + arguments: + - '@config' + - '@router' + - '@symfony_request' + - '@request' + - '@filesystem' + - '%core.root_path%' + - '%core.php_ext%' + +# ---- Route loaders ---- + + routing.delegated_loader: + class: Symfony\Component\Config\Loader\DelegatingLoader + arguments: + - '@routing.resolver' + + routing.resolver: + class: phpbb\routing\loader_resolver + arguments: + - '@routing.loader.collection' + + routing.loader.collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: routing.loader } + + routing.loader.yaml: + class: Symfony\Component\Routing\Loader\YamlFileLoader + arguments: + - '@file_locator' + tags: + - { name: routing.loader } + +# ---- Resources Locators ---- + + routing.chained_resources_locator: + class: phpbb\routing\resources_locator\chained_resources_locator + arguments: + - '@routing.resources_locator.collection' + + routing.resources_locator.collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: routing.resources_locator } + + routing.resources_locator.default: + class: phpbb\routing\resources_locator\default_resources_locator + arguments: + - '%core.root_path%' + - '%core.environment%' + - '@ext.manager' + tags: + - { name: routing.resources_locator } diff --git a/config/default/container/services_text_formatter.yml b/config/default/container/services_text_formatter.yml new file mode 100644 index 0000000..07087cd --- /dev/null +++ b/config/default/container/services_text_formatter.yml @@ -0,0 +1,79 @@ +parameters: + text_formatter.cache.dir: '%core.cache_dir%' + text_formatter.cache.parser.key: _text_formatter_parser + text_formatter.cache.renderer.key: _text_formatter_renderer + +services: + text_formatter.cache: + alias: text_formatter.s9e.factory + + text_formatter.data_access: + class: phpbb\textformatter\data_access + arguments: + - '@dbal.conn' + - '%tables.bbcodes%' + - '%tables.smilies%' + - '%tables.styles%' + - '%tables.words%' + - '%core.root_path%styles/' + + text_formatter.parser: + alias: text_formatter.s9e.parser + + text_formatter.renderer: + alias: text_formatter.s9e.renderer + + text_formatter.utils: + alias: text_formatter.s9e.utils + + text_formatter.s9e.bbcode_merger: + class: phpbb\textformatter\s9e\bbcode_merger + arguments: + - '@text_formatter.s9e.factory' + + text_formatter.s9e.factory: + class: phpbb\textformatter\s9e\factory + arguments: + - '@text_formatter.data_access' + - '@cache.driver' + - '@dispatcher' + - '@config' + - '@text_formatter.s9e.link_helper' + - '@log' + - '%text_formatter.cache.dir%' + - '%text_formatter.cache.parser.key%' + - '%text_formatter.cache.renderer.key%' + + text_formatter.s9e.link_helper: + class: phpbb\textformatter\s9e\link_helper + + text_formatter.s9e.parser: + class: phpbb\textformatter\s9e\parser + arguments: + - '@cache.driver' + - '%text_formatter.cache.parser.key%' + - '@text_formatter.s9e.factory' + - '@dispatcher' + + text_formatter.s9e.quote_helper: + class: phpbb\textformatter\s9e\quote_helper + arguments: + - '@user' + - '%core.root_path%' + - '%core.php_ext%' + + text_formatter.s9e.renderer: + class: phpbb\textformatter\s9e\renderer + arguments: + - '@cache.driver' + - '%text_formatter.cache.dir%' + - '%text_formatter.cache.renderer.key%' + - '@text_formatter.s9e.factory' + - '@dispatcher' + calls: + - [configure_quote_helper, ['@text_formatter.s9e.quote_helper']] + - [configure_smilies_path, ['@config', '@path_helper']] + - [configure_user, ['@user', '@config', '@auth']] + + text_formatter.s9e.utils: + class: phpbb\textformatter\s9e\utils diff --git a/config/default/container/services_text_reparser.yml b/config/default/container/services_text_reparser.yml new file mode 100644 index 0000000..4bc49f5 --- /dev/null +++ b/config/default/container/services_text_reparser.yml @@ -0,0 +1,109 @@ +services: + text_reparser.manager: + class: phpbb\textreparser\manager + arguments: + - '@config' + - '@config_text' + - '@text_reparser_collection' + + text_reparser.lock: + class: phpbb\lock\db + arguments: + - reparse_lock + - '@config' + - '@dbal.conn' + + text_reparser_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: text_reparser.plugin } + + text_reparser.contact_admin_info: + class: phpbb\textreparser\plugins\contact_admin_info + arguments: + - '@config_text' + calls: + - [set_name, [contact_admin_info]] + tags: + - { name: text_reparser.plugin } + + text_reparser.forum_description: + class: phpbb\textreparser\plugins\forum_description + arguments: + - '@dbal.conn' + - '%tables.forums%' + calls: + - [set_name, [forum_description]] + tags: + - { name: text_reparser.plugin } + + text_reparser.forum_rules: + class: phpbb\textreparser\plugins\forum_rules + arguments: + - '@dbal.conn' + - '%tables.forums%' + calls: + - [set_name, [forum_rules]] + tags: + - { name: text_reparser.plugin } + + text_reparser.group_description: + class: phpbb\textreparser\plugins\group_description + arguments: + - '@dbal.conn' + - '%tables.groups%' + calls: + - [set_name, [group_description]] + tags: + - { name: text_reparser.plugin } + + text_reparser.pm_text: + class: phpbb\textreparser\plugins\pm_text + arguments: + - '@dbal.conn' + - '%tables.privmsgs%' + calls: + - [set_name, [pm_text]] + tags: + - { name: text_reparser.plugin } + + text_reparser.poll_option: + class: phpbb\textreparser\plugins\poll_option + arguments: + - '@dbal.conn' + calls: + - [set_name, [poll_option]] + tags: + - { name: text_reparser.plugin } + + text_reparser.poll_title: + class: phpbb\textreparser\plugins\poll_title + arguments: + - '@dbal.conn' + - '%tables.topics%' + calls: + - [set_name, [poll_title]] + tags: + - { name: text_reparser.plugin } + + text_reparser.post_text: + class: phpbb\textreparser\plugins\post_text + arguments: + - '@dbal.conn' + - '%tables.posts%' + calls: + - [set_name, [post_text]] + tags: + - { name: text_reparser.plugin } + + text_reparser.user_signature: + class: phpbb\textreparser\plugins\user_signature + arguments: + - '@dbal.conn' + - '%tables.users%' + calls: + - [set_name, [user_signature]] + tags: + - { name: text_reparser.plugin } diff --git a/config/default/container/services_twig.yml b/config/default/container/services_twig.yml new file mode 100644 index 0000000..a9b5b6d --- /dev/null +++ b/config/default/container/services_twig.yml @@ -0,0 +1,68 @@ +parameters: + core.template.cache_path: '%core.cache_dir%twig/' + +services: + template.twig.environment: + class: phpbb\template\twig\environment + arguments: + - '@config' + - '@filesystem' + - '@path_helper' + - '%core.template.cache_path%' + - '@ext.manager' + - '@template.twig.loader' + - '@dispatcher' + - [] + calls: + - [setLexer, ['@template.twig.lexer']] + + template.twig.lexer: + class: phpbb\template\twig\lexer + lazy: true + arguments: + - '@template.twig.environment' + + template.twig.loader: + class: phpbb\template\twig\loader + arguments: + - '@filesystem' + + template.twig.extensions.collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: twig.extension } + + template.twig.extensions.phpbb: + class: phpbb\template\twig\extension + arguments: + - '@template_context' + - '@language' + tags: + - { name: twig.extension } + + template.twig.extensions.routing: + class: phpbb\template\twig\extension\routing + arguments: + - '@routing.helper' + tags: + - { name: twig.extension } + + template.twig.extensions.debug: + class: Twig_Extension_Debug + + template: + class: phpbb\template\twig\twig + arguments: + - '@path_helper' + - '@config' + - '@template_context' + - '@template.twig.environment' + - '%core.template.cache_path%' + - '@user' + - '@template.twig.extensions.collection' + - '@ext.manager' + + template_context: + class: phpbb\template\context diff --git a/config/default/container/services_user.yml b/config/default/container/services_user.yml new file mode 100644 index 0000000..7e634c6 --- /dev/null +++ b/config/default/container/services_user.yml @@ -0,0 +1,20 @@ +services: + acl.permissions: + class: phpbb\permissions + arguments: + - '@dispatcher' + - '@user' + + user: + class: phpbb\user + arguments: + - '@language' + - '%datetime.class%' + + user_loader: + class: phpbb\user_loader + arguments: + - '@dbal.conn' + - '%core.root_path%' + - '%core.php_ext%' + - '%tables.users%' diff --git a/config/default/container/tables.yml b/config/default/container/tables.yml new file mode 100644 index 0000000..4aed357 --- /dev/null +++ b/config/default/container/tables.yml @@ -0,0 +1,78 @@ +parameters: + tables.acl_groups: '%core.table_prefix%acl_groups' + tables.acl_options: '%core.table_prefix%acl_options' + tables.acl_roles: '%core.table_prefix%acl_roles' + tables.acl_roles_data: '%core.table_prefix%acl_roles_data' + tables.acl_users: '%core.table_prefix%acl_users' + tables.attachments: '%core.table_prefix%attachments' + tables.auth_provider_oauth_token_storage: '%core.table_prefix%oauth_tokens' + tables.auth_provider_oauth_states: '%core.table_prefix%oauth_states' + tables.auth_provider_oauth_account_assoc: '%core.table_prefix%oauth_accounts' + tables.banlist: '%core.table_prefix%banlist' + tables.bbcodes: '%core.table_prefix%bbcodes' + tables.bookmarks: '%core.table_prefix%bookmarks' + tables.bots: '%core.table_prefix%bots' + tables.captcha_qa_questions: '%core.table_prefix%captcha_questions' + tables.captcha_qa_answers: '%core.table_prefix%captcha_answers' + tables.captcha_qa_confirm: '%core.table_prefix%qa_confirm' + tables.config: '%core.table_prefix%config' + tables.config_text: '%core.table_prefix%config_text' + tables.confirm: '%core.table_prefix%confirm' + tables.disallow: '%core.table_prefix%disallow' + tables.drafts: '%core.table_prefix%drafts' + tables.ext: '%core.table_prefix%ext' + tables.extensions: '%core.table_prefix%extensions' + tables.extension_groups: '%core.table_prefix%extension_groups' + tables.forums: '%core.table_prefix%forums' + tables.forums_access: '%core.table_prefix%forums_access' + tables.forums_track: '%core.table_prefix%forums_track' + tables.forums_watch: '%core.table_prefix%forums_watch' + tables.groups: '%core.table_prefix%groups' + tables.icons: '%core.table_prefix%icons' + tables.lang: '%core.table_prefix%lang' + tables.log: '%core.table_prefix%log' + tables.login_attempts: '%core.table_prefix%login_attempts' + tables.migrations: '%core.table_prefix%migrations' + tables.moderator_cache: '%core.table_prefix%moderator_cache' + tables.modules: '%core.table_prefix%modules' + tables.notification_types: '%core.table_prefix%notification_types' + tables.notifications: '%core.table_prefix%notifications' + tables.poll_options: '%core.table_prefix%poll_options' + tables.poll_votes: '%core.table_prefix%poll_votes' + tables.posts: '%core.table_prefix%posts' + tables.privmsgs: '%core.table_prefix%privmsgs' + tables.privmsgs_folder: '%core.table_prefix%privmsgs_folder' + tables.privmsgs_rules: '%core.table_prefix%privmsgs_rules' + tables.privmsgs_to: '%core.table_prefix%privmsgs_to' + tables.profile_fields: '%core.table_prefix%profile_fields' + tables.profile_fields_data: '%core.table_prefix%profile_fields_data' + tables.profile_fields_options_language: '%core.table_prefix%profile_fields_lang' + tables.profile_fields_language: '%core.table_prefix%profile_lang' + tables.ranks: '%core.table_prefix%ranks' + tables.reports: '%core.table_prefix%reports' + tables.reports_reasons: '%core.table_prefix%reports_reasons' + tables.search_results: '%core.table_prefix%search_results' + tables.search_wordlist: '%core.table_prefix%search_wordlist' + tables.search_wordmatch: '%core.table_prefix%search_wordmatch' + tables.sessions: '%core.table_prefix%sessions' + tables.sessions_keys: '%core.table_prefix%sessions_keys' + tables.sitelist: '%core.table_prefix%sitelist' + tables.smilies: '%core.table_prefix%smilies' + tables.sphinx: '%core.table_prefix%sphinx' + tables.styles: '%core.table_prefix%styles' + tables.styles_template: '%core.table_prefix%styles_template' + tables.styles_template_data: '%core.table_prefix%styles_template_data' + tables.styles_theme: '%core.table_prefix%styles_theme' + tables.styles_imageset: '%core.table_prefix%styles_imageset' + tables.styles_imageset_data: '%core.table_prefix%styles_imageset_data' + tables.teampage: '%core.table_prefix%teampage' + tables.topics: '%core.table_prefix%topics' + tables.topics_posted: '%core.table_prefix%topics_posted' + tables.topics_track: '%core.table_prefix%topics_track' + tables.topics_watch: '%core.table_prefix%topics_watch' + tables.user_group: '%core.table_prefix%user_group' + tables.user_notifications: '%core.table_prefix%user_notifications' + tables.users: '%core.table_prefix%users' + tables.warnings: '%core.table_prefix%warnings' + tables.words: '%core.table_prefix%words' + tables.zebra: '%core.table_prefix%zebra' diff --git a/config/default/routing/feed.yml b/config/default/routing/feed.yml new file mode 100644 index 0000000..22c9ea5 --- /dev/null +++ b/config/default/routing/feed.yml @@ -0,0 +1,35 @@ +phpbb_feed_forums: + path: /forums + defaults: { _controller: phpbb.feed.controller:forums } + +phpbb_feed_news: + path: /news + defaults: { _controller: phpbb.feed.controller:news } + +phpbb_feed_topics: + path: /topics + defaults: { _controller: phpbb.feed.controller:topics } + +phpbb_feed_topics_active: + path: /topics_active + defaults: { _controller: phpbb.feed.controller:topics_active } + +phpbb_feed_topics_new: + path: /topics_new + defaults: { _controller: phpbb.feed.controller:topics_new } + +phpbb_feed_forum: + path: /forum/{forum_id} + defaults: { _controller: phpbb.feed.controller:forum } + requirements: + forum_id: \d+ + +phpbb_feed_topic: + path: /topic/{topic_id} + defaults: { _controller: phpbb.feed.controller:topic } + requirements: + topic_id: \d+ + +phpbb_feed_overall: + path: /{mode} + defaults: { _controller: phpbb.feed.controller:overall } diff --git a/config/default/routing/help.yml b/config/default/routing/help.yml new file mode 100644 index 0000000..8d43839 --- /dev/null +++ b/config/default/routing/help.yml @@ -0,0 +1,7 @@ +phpbb_help_bbcode_controller: + path: /bbcode + defaults: { _controller: phpbb.help.controller.bbcode:handle } + +phpbb_help_faq_controller: + path: /faq + defaults: { _controller: phpbb.help.controller.faq:handle } diff --git a/config/default/routing/report.yml b/config/default/routing/report.yml new file mode 100644 index 0000000..c386770 --- /dev/null +++ b/config/default/routing/report.yml @@ -0,0 +1,17 @@ +phpbb_report_pm_controller: + path: /pm/{id}/report + methods: [GET, POST] + defaults: + _controller: phpbb.report.controller:handle + mode: 'pm' + requirements: + id: \d+ + +phpbb_report_post_controller: + path: /post/{id}/report + methods: [GET, POST] + defaults: + _controller: phpbb.report.controller:handle + mode: 'post' + requirements: + id: \d+ diff --git a/config/default/routing/routing.yml b/config/default/routing/routing.yml new file mode 100644 index 0000000..f381f02 --- /dev/null +++ b/config/default/routing/routing.yml @@ -0,0 +1,24 @@ +# Structure: +# +# foo_controller: +# path: /foo +# defaults: { _controller: foo_sevice:method } +# +# The above will be accessed via app.php?controller=foo and it will +# instantiate the 'foo_service' service and call the 'method' method. +# + +phpbb_feed_routing: + resource: feed.yml + prefix: /feed + +phpbb_feed_index: + path: /feed + defaults: { _controller: phpbb.feed.controller:overall } + +phpbb_help_routing: + resource: help.yml + prefix: /help + +phpbb_report_routing: + resource: report.yml diff --git a/config/installer/config.yml b/config/installer/config.yml new file mode 100644 index 0000000..979dbbc --- /dev/null +++ b/config/installer/config.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ../default/config.yml } diff --git a/config/installer/container/environment.yml b/config/installer/container/environment.yml new file mode 100644 index 0000000..40a3c7a --- /dev/null +++ b/config/installer/container/environment.yml @@ -0,0 +1,3 @@ +imports: + - { resource: services.yml } + - { resource: parameters.yml } diff --git a/config/installer/container/parameters.yml b/config/installer/container/parameters.yml new file mode 100644 index 0000000..f11f7e2 --- /dev/null +++ b/config/installer/container/parameters.yml @@ -0,0 +1,5 @@ +imports: + - { resource: ../../default/container/parameters.yml } + +parameters: + installer.create_config_file.options: [] diff --git a/config/installer/container/services.yml b/config/installer/container/services.yml new file mode 100644 index 0000000..7203c0a --- /dev/null +++ b/config/installer/container/services.yml @@ -0,0 +1,104 @@ +imports: + - { resource: services_installer.yml } + - { resource: ../../default/container/services_event.yml } + - { resource: ../../default/container/services_filesystem.yml } + - { resource: ../../default/container/services_http.yml } + - { resource: ../../default/container/services_language.yml } + - { resource: ../../default/container/services_php.yml } + - { resource: ../../default/container/services_routing.yml } + - { resource: ../../default/container/services_twig.yml } + +services: + cache.driver: + class: '%cache.driver.class%' + arguments: + - '%core.cache_dir%' + + config: + class: phpbb\config\config + arguments: + - + rand_seed: 'installer_seed' + rand_seed_last_update: 0 + + controller.resolver: + class: phpbb\controller\resolver + arguments: + - '@service_container' + - '%core.root_path%' + - '@template' + + file_locator: + class: phpbb\routing\file_locator + arguments: + - '@filesystem' + - '%core.root_path%' + + kernel_exception_subscriber: + class: phpbb\install\event\kernel_exception_subscriber + arguments: + - '@phpbb.installer.controller.helper' + - '@language' + - '@template' + tags: + - { name: kernel.event_subscriber } + + language.loader: + parent: language.loader_abstract + + path_helper: + class: phpbb\path_helper + arguments: + - '@symfony_request' + - '@filesystem' + - '@request' + - '%core.root_path%' + - '%core.php_ext%' + + routing.resources_locator.default: + class: phpbb\routing\resources_locator\installer_resources_locator + arguments: + - '@filesystem' + - '%core.root_path%' + - '%core.environment%' + tags: + - { name: routing.resources_locator } + + template: + class: phpbb\template\twig\twig + arguments: + - '@path_helper' + - '@config' + - '@template_context' + - '@template.twig.environment' + - '%core.template.cache_path%' + - null + - '@template.twig.extensions.collection' + + template.twig.environment: + class: phpbb\template\twig\environment + arguments: + - '@config' + - '@filesystem' + - '@path_helper' + - '%core.template.cache_path%' + - null + - '@template.twig.loader' + - null + - [] + calls: + - [setLexer, ['@template.twig.lexer']] + + user: + class: phpbb\user + arguments: + - '@language' + - '%datetime.class%' + + console.exception_subscriber: + class: phpbb\console\exception_subscriber + arguments: + - '@language' + - '%debug.exceptions%' + tags: + - { name: kernel.event_subscriber } diff --git a/config/installer/container/services_file_updater.yml b/config/installer/container/services_file_updater.yml new file mode 100644 index 0000000..9d39bb8 --- /dev/null +++ b/config/installer/container/services_file_updater.yml @@ -0,0 +1,38 @@ +services: + installer.file_updater.factory: + class: phpbb\install\helper\file_updater\factory + arguments: + - '@installer.file_updater.collection' + + installer.file_updater.collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: file_updater } + + installer.file_updater.compress: + class: phpbb\install\helper\file_updater\compression_file_updater + arguments: + - '@installer.helper.update_helper' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: file_updater } + + installer.file_updater.ftp: + class: phpbb\install\helper\file_updater\ftp_file_updater + arguments: + - '@installer.helper.update_helper' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: file_updater } + + installer.file_updater.file: + class: phpbb\install\helper\file_updater\file_updater + arguments: + - '@filesystem' + - '%core.root_path%' + tags: + - { name: file_updater } diff --git a/config/installer/container/services_install_console.yml b/config/installer/container/services_install_console.yml new file mode 100644 index 0000000..41d3aa4 --- /dev/null +++ b/config/installer/container/services_install_console.yml @@ -0,0 +1,61 @@ +services: + console.installer.command_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: console.installer.command } + + console.installer.command.install: + class: phpbb\install\console\command\install\install + arguments: + - '@language' + - '@installer.helper.iohandler_factory' + - '@installer.installer.install' + - '@installer.helper.install_helper' + tags: + - { name: console.installer.command } + + console.installer.command.config.show: + class: phpbb\install\console\command\install\config\show + arguments: + - '@language' + - '@installer.helper.iohandler_factory' + tags: + - { name: console.installer.command } + + + console.installer.command.config.validate: + class: phpbb\install\console\command\install\config\validate + arguments: + - '@language' + - '@installer.helper.iohandler_factory' + tags: + - { name: console.installer.command } + + console.updater.command.update: + class: phpbb\install\console\command\update\update + arguments: + - '@language' + - '@installer.helper.iohandler_factory' + - '@installer.installer.update' + - '@installer.helper.install_helper' + tags: + - { name: console.installer.command } + + console.updater.command.config.show: + class: phpbb\install\console\command\update\config\show + arguments: + - '@language' + - '@installer.helper.iohandler_factory' + tags: + - { name: console.installer.command } + + + console.updater.command.config.validate: + class: phpbb\install\console\command\update\config\validate + arguments: + - '@language' + - '@installer.helper.iohandler_factory' + tags: + - { name: console.installer.command } diff --git a/config/installer/container/services_install_controller.yml b/config/installer/container/services_install_controller.yml new file mode 100644 index 0000000..5aaba0f --- /dev/null +++ b/config/installer/container/services_install_controller.yml @@ -0,0 +1,73 @@ +services: + phpbb.installer.controller.welcome: + class: phpbb\install\controller\installer_index + arguments: + - '@phpbb.installer.controller.helper' + - '@language' + - '@template' + - '%core.root_path%' + + phpbb.installer.controller.helper: + class: phpbb\install\controller\helper + arguments: + - '@installer.helper.config' + - '@language' + - '@language.helper.language_file' + - '@installer.navigation.provider' + - '@template' + - '@path_helper' + - '@request' + - '@symfony_request' + - '@router' + - '%core.root_path%' + + phpbb.installer.controller.install: + class: phpbb\install\controller\install + arguments: + - '@phpbb.installer.controller.helper' + - '@installer.helper.iohandler_factory' + - '@installer.navigation.provider' + - '@language' + - '@template' + - '@request' + - '@installer.installer.install' + - '@installer.helper.install_helper' + + phpbb.installer.controller.update: + class: phpbb\install\controller\update + arguments: + - '@phpbb.installer.controller.helper' + - '@installer.installer.update' + - '@installer.helper.install_helper' + - '@installer.helper.iohandler_factory' + - '@language' + - '@installer.navigation.provider' + - '@request' + - '@template' + + phpbb.installer.controller.file_downloader: + class: phpbb\install\controller\archive_download + arguments: + - '@installer.helper.config' + + phpbb.installer.controller.convert: + class: phpbb\convert\controller\convertor + arguments: + - '@cache.driver' + - '@installer.helper.container_factory' + - '@installer.helper.database' + - '@phpbb.installer.controller.helper' + - '@installer.helper.install_helper' + - '@installer.helper.iohandler_factory' + - '@language' + - '@installer.navigation.provider' + - '@request' + - '@template' + - '%core.root_path%' + - '%core.php_ext%' + + phpbb.installer.controller.status: + class: phpbb\install\controller\timeout_check + arguments: + - '@phpbb.installer.controller.helper' + - '%core.root_path%' diff --git a/config/installer/container/services_install_data.yml b/config/installer/container/services_install_data.yml new file mode 100644 index 0000000..5de0017 --- /dev/null +++ b/config/installer/container/services_install_data.yml @@ -0,0 +1,55 @@ +services: + installer.install_data.add_bots: + class: phpbb\install\module\install_data\task\add_bots + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.container_factory' + - '@language' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: install_data_install, order: 20 } + + installer.install_data.add_languages: + class: phpbb\install\module\install_data\task\add_languages + arguments: + - '@installer.helper.iohandler' + - '@installer.helper.container_factory' + - '@language.helper.language_file' + tags: + - { name: install_data_install, order: 10 } + + installer.install_data.add_modules: + class: phpbb\install\module\install_data\task\add_modules + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.container_factory' + tags: + - { name: install_data_install, order: 30 } + + installer.install_data.create_search_index: + class: phpbb\install\module\install_data\task\create_search_index + arguments: + - '@config' + - '@installer.helper.container_factory' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: install_data_install, order: 40 } + + installer.module.data_install_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: install_data_install, class_name_aware: true } + + installer.module.data_install: + class: phpbb\install\module\install_data\module + parent: installer.module_base + arguments: + - '@installer.module.data_install_collection' + tags: + - { name: installer_install_module, order: 50 } diff --git a/config/installer/container/services_install_database.yml b/config/installer/container/services_install_database.yml new file mode 100644 index 0000000..33f5965 --- /dev/null +++ b/config/installer/container/services_install_database.yml @@ -0,0 +1,71 @@ +services: + installer.install_database.create_schema_file: + class: phpbb\install\module\install_database\task\create_schema_file + arguments: + - '@installer.helper.config' + - '@installer.helper.database' + - '@filesystem' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: install_database_install, order: 10 } + + installer.install_database.set_up_database: + class: phpbb\install\module\install_database\task\set_up_database + arguments: + - '@installer.helper.config' + - '@installer.helper.database' + - '@filesystem' + - '@installer.helper.iohandler' + - '%core.root_path%' + tags: + - { name: install_database_install, order: 20 } + + installer.install_database.add_tables: + class: phpbb\install\module\install_database\task\add_tables + arguments: + - '@installer.helper.config' + - '@installer.helper.database' + - '@filesystem' + - '%core.root_path%' + tags: + - { name: install_database_install, order: 30 } + + installer.install_database.add_default_data: + class: phpbb\install\module\install_database\task\add_default_data + arguments: + - '@installer.helper.database' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.container_factory' + - '@language' + - '%core.root_path%' + tags: + - { name: install_database_install, order: 40 } + + installer.install_database.add_config_settings: + class: phpbb\install\module\install_database\task\add_config_settings + arguments: + - '@filesystem' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.container_factory' + - '@language' + - '%core.root_path%' + tags: + - { name: install_database_install, order: 50 } + + installer.module.install_database_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: install_database_install, class_name_aware: true } + + installer.module.database_install: + class: phpbb\install\module\install_database\module + parent: installer.module_base + arguments: + - '@installer.module.install_database_collection' + tags: + - { name: installer_install_module, order: 40 } diff --git a/config/installer/container/services_install_filesystem.yml b/config/installer/container/services_install_filesystem.yml new file mode 100644 index 0000000..65b2a6e --- /dev/null +++ b/config/installer/container/services_install_filesystem.yml @@ -0,0 +1,28 @@ +services: + installer.install_filesystem.create_config_file: + class: phpbb\install\module\install_filesystem\task\create_config_file + arguments: + - '@filesystem' + - '@installer.helper.config' + - '@installer.helper.database' + - '@installer.helper.iohandler' + - '%core.root_path%' + - '%core.php_ext%' + - '%installer.create_config_file.options%' + tags: + - { name: install_filesystem_install, order: 10 } + + installer.module.install_filesystem_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: install_filesystem_install, class_name_aware: true } + + installer.module.filesystem_install: + class: phpbb\install\module\install_filesystem\module + parent: installer.module_base + arguments: + - '@installer.module.install_filesystem_collection' + tags: + - { name: installer_install_module, order: 30 } diff --git a/config/installer/container/services_install_finish.yml b/config/installer/container/services_install_finish.yml new file mode 100644 index 0000000..6a46d8f --- /dev/null +++ b/config/installer/container/services_install_finish.yml @@ -0,0 +1,44 @@ +services: + installer.install_finish.populate_migrations: + class: phpbb\install\module\install_finish\task\populate_migrations + arguments: + - '@installer.helper.config' + - '@installer.helper.container_factory' + tags: + - { name: install_finish, order: 10 } + + installer.install_finish.install_extensions: + class: phpbb\install\module\install_finish\task\install_extensions + arguments: + - '@installer.helper.container_factory' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '%core.root_path%' + tags: + - { name: install_finish, order: 20 } + + installer.install_finish.notify_user: + class: phpbb\install\module\install_finish\task\notify_user + arguments: + - '@installer.helper.container_factory' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: install_finish, order: 30 } + + installer.module.install_finish_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: install_finish, class_name_aware: true } + + installer.module.finish_install: + class: phpbb\install\module\install_finish\module + parent: installer.module_base + arguments: + - '@installer.module.install_finish_collection' + tags: + - { name: installer_install_module, order: 60 } diff --git a/config/installer/container/services_install_navigation.yml b/config/installer/container/services_install_navigation.yml new file mode 100644 index 0000000..d7151eb --- /dev/null +++ b/config/installer/container/services_install_navigation.yml @@ -0,0 +1,42 @@ +services: + installer.navigation.provider: + class: phpbb\install\helper\navigation\navigation_provider + arguments: + - '@installer.navigation.service_collection' + + installer.navigation.service_collection: + class: phpbb\di\service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: installer.navigation } + + installer.navigation.main_navigation: + class: phpbb\install\helper\navigation\main_navigation + shared: false + tags: + - { name: installer.navigation } + + installer.navigation.install_navigation: + class: phpbb\install\helper\navigation\install_navigation + arguments: + - '@installer.helper.install_helper' + shared: false + tags: + - { name: installer.navigation } + + installer.navigation.update_navigation: + class: phpbb\install\helper\navigation\update_navigation + arguments: + - '@installer.helper.install_helper' + shared: false + tags: + - { name: installer.navigation } + + installer.navigation.convertor_navigation: + class: phpbb\install\helper\navigation\convertor_navigation + arguments: + - '@installer.helper.install_helper' + shared: false + tags: + - { name: installer.navigation } diff --git a/config/installer/container/services_install_obtain_data.yml b/config/installer/container/services_install_obtain_data.yml new file mode 100644 index 0000000..010aba8 --- /dev/null +++ b/config/installer/container/services_install_obtain_data.yml @@ -0,0 +1,59 @@ +services: + installer.obtain_data.obtain_admin_data: + class: phpbb\install\module\obtain_data\task\obtain_admin_data + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + tags: + - { name: install_obtain_data, order: 10 } + + installer.obtain_data.obtain_board_data: + class: phpbb\install\module\obtain_data\task\obtain_board_data + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@language.helper.language_file' + tags: + - { name: install_obtain_data, order: 50 } + + installer.obtain_data.obtain_database_data: + class: phpbb\install\module\obtain_data\task\obtain_database_data + arguments: + - '@installer.helper.database' + - '@installer.helper.config' + - '@installer.helper.iohandler' + tags: + - { name: install_obtain_data, order: 20 } + + installer.obtain_data.obtain_email_data: + class: phpbb\install\module\obtain_data\task\obtain_email_data + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + tags: + - { name: install_obtain_data, order: 40 } + + installer.obtain_data.obtain_server_data: + class: phpbb\install\module\obtain_data\task\obtain_server_data + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + tags: + - { name: install_obtain_data, order: 30 } + + installer.module.install_obtain_data_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: install_obtain_data, class_name_aware: true } + + installer.module.obtain_data_install: + class: phpbb\install\module\obtain_data\install_module + parent: installer.module_base + arguments: + - '@installer.module.install_obtain_data_collection' + - true + - false + tags: + - { name: installer_install_module, order: 20 } diff --git a/config/installer/container/services_install_requirements.yml b/config/installer/container/services_install_requirements.yml new file mode 100644 index 0000000..c03eb1f --- /dev/null +++ b/config/installer/container/services_install_requirements.yml @@ -0,0 +1,37 @@ +services: + installer.requirements.check_filesystem: + class: phpbb\install\module\requirements\task\check_filesystem + arguments: + - '@filesystem' + - '@installer.helper.iohandler' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: installer_requirements, order: 10 } + + installer.requirements.check_server_environment: + class: phpbb\install\module\requirements\task\check_server_environment + arguments: + - '@installer.helper.database' + - '@installer.helper.iohandler' + tags: + - { name: installer_requirements, order: 20 } + - { name: update_requirements, order: 20 } + + installer.module.install_requirements_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: installer_requirements, class_name_aware: true } + +# Please note, that the name of this module is hard coded in the installer service + installer.module.requirements_install: + class: phpbb\install\module\requirements\install_module + parent: installer.module_base + arguments: + - '@installer.module.install_requirements_collection' + - true + - false + tags: + - { name: installer_install_module, order: 10 } diff --git a/config/installer/container/services_installer.yml b/config/installer/container/services_installer.yml new file mode 100644 index 0000000..57181b2 --- /dev/null +++ b/config/installer/container/services_installer.yml @@ -0,0 +1,119 @@ +imports: + - { resource: services_file_updater.yml } + - { resource: services_install_console.yml } + - { resource: services_install_controller.yml } + - { resource: services_install_data.yml } + - { resource: services_install_database.yml } + - { resource: services_install_filesystem.yml } + - { resource: services_install_finish.yml } + - { resource: services_install_navigation.yml } + - { resource: services_install_obtain_data.yml } + - { resource: services_install_requirements.yml } + - { resource: services_update_database.yml } + - { resource: services_update_filesystem.yml } + - { resource: services_update_obtain_data.yml } + - { resource: services_update_requirements.yml } + +services: +# -------- Installer helpers ------------------------ + installer.helper.config: + class: phpbb\install\helper\config + arguments: + - '@filesystem' + - '@php_ini' + - '%core.root_path%' + + installer.helper.database: + class: phpbb\install\helper\database + arguments: + - '@filesystem' + - '%core.root_path%' + + installer.helper.iohandler_factory: + class: phpbb\install\helper\iohandler\factory + arguments: + - '@service_container' + + installer.helper.iohandler_abstract: + abstract: true + calls: + - [set_language, ['@language']] + + installer.helper.iohandler_ajax: + class: phpbb\install\helper\iohandler\ajax_iohandler + parent: installer.helper.iohandler_abstract + arguments: + - '@path_helper' + - '@request' + - '@template' + - '@router' + - '%core.root_path%' + + installer.helper.iohandler_cli: + class: phpbb\install\helper\iohandler\cli_iohandler + parent: installer.helper.iohandler_abstract + + installer.helper.iohandler: + class: phpbb\install\helper\iohandler\iohandler_interface + factory: ['@installer.helper.iohandler_factory', get] + + installer.helper.container_factory: + class: phpbb\install\helper\container_factory + arguments: + - '@language' + - '@request' + - '@installer.helper.update_helper' + - '%core.root_path%' + - '%core.php_ext%' + + installer.helper.install_helper: + class: phpbb\install\helper\install_helper + arguments: + - '%core.root_path%' + - '%core.php_ext%' + + installer.helper.update_helper: + class: phpbb\install\helper\update_helper + arguments: + - '%core.root_path%' + +# -------- Installer -------------------------------- + installer.module_base: + abstract: true + calls: + - [setup, ['@installer.helper.config', '@installer.helper.iohandler']] + + installer.installer.abstract: + class: phpbb\install\installer + abstract: true + arguments: + - '@cache.driver' + - '@installer.helper.config' + - '@path_helper' + - '@installer.helper.container_factory' + + installer.install.module_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: installer_install_module } + + installer.update.module_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: installer_update_module } + + installer.installer.install: + parent: installer.installer.abstract + calls: + - [set_modules, ['@installer.install.module_collection']] + - [set_purge_cache_before, [false]] + + installer.installer.update: + parent: installer.installer.abstract + calls: + - [set_modules, ['@installer.update.module_collection']] + - [set_purge_cache_before, [true]] diff --git a/config/installer/container/services_update_database.yml b/config/installer/container/services_update_database.yml new file mode 100644 index 0000000..bb97cff --- /dev/null +++ b/config/installer/container/services_update_database.yml @@ -0,0 +1,40 @@ +services: + installer.update_database.update_task: + class: phpbb\install\module\update_database\task\update + arguments: + - '@installer.helper.container_factory' + - '@filesystem' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@language' + - '%core.root_path%' + tags: + - { name: update_database_task, order: 10 } + + installer.update_database.update_extensions: + class: phpbb\install\module\update_database\task\update_extensions + arguments: + - '@installer.helper.container_factory' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.update_helper' + - '%core.root_path%' + tags: + - { name: update_database_task, order: 20 } + + installer.module.update_database_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: update_database_task, class_name_aware: true } + + installer.module.update_database: + class: phpbb\install\module\update_database\module + parent: installer.module_base + arguments: + - '@installer.module.update_database_collection' + - true + - false + tags: + - { name: installer_update_module, order: 40 } diff --git a/config/installer/container/services_update_filesystem.yml b/config/installer/container/services_update_filesystem.yml new file mode 100644 index 0000000..c0a0467 --- /dev/null +++ b/config/installer/container/services_update_filesystem.yml @@ -0,0 +1,72 @@ +services: + installer.update_filesystem.check_task: + class: phpbb\install\module\update_filesystem\task\file_check + arguments: + - '@filesystem' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.update_helper' + - '%core.root_path%' + tags: + - { name: update_filesystem, order: 10 } + + installer.update_filesystem.diff_files: + class: phpbb\install\module\update_filesystem\task\diff_files + arguments: + - '@installer.helper.container_factory' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.update_helper' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: update_filesystem, order: 20 } + + installer.update_filesystem.show_file_status: + class: phpbb\install\module\update_filesystem\task\show_file_status + arguments: + - '@installer.helper.container_factory' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@filesystem' + - '@installer.file_updater.factory' + tags: + - { name: update_filesystem, order: 30 } + + installer.update_filesystem.update_files: + class: phpbb\install\module\update_filesystem\task\update_files + arguments: + - '@installer.helper.container_factory' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.file_updater.factory' + - '@installer.helper.update_helper' + - '%core.root_path%' + tags: + - { name: update_filesystem, order: 40 } + + installer.update_filesystem.download_updated_files: + class: phpbb\install\module\update_filesystem\task\download_updated_files + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@filesystem' + tags: + - { name: update_filesystem, order: 50 } + + installer.module.update_filesystem_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: update_filesystem, class_name_aware: true } + + installer.module.filesystem_update: + class: phpbb\install\module\update_filesystem\module + parent: installer.module_base + arguments: + - '@installer.module.update_filesystem_collection' + - true + - false + tags: + - { name: installer_update_module, order: 30 } diff --git a/config/installer/container/services_update_obtain_data.yml b/config/installer/container/services_update_obtain_data.yml new file mode 100644 index 0000000..999976a --- /dev/null +++ b/config/installer/container/services_update_obtain_data.yml @@ -0,0 +1,53 @@ +services: + installer.obtain_data.update_options: + class: phpbb\install\module\obtain_data\task\obtain_update_settings + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + tags: + - { name: update_obtain_data, order: 10 } + + installer.obtain_data.file_updater_method: + class: phpbb\install\module\obtain_data\task\obtain_file_updater_method + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + tags: + - { name: update_obtain_data, order: 20 } + + installer.obtain_data.update_files: + class: phpbb\install\module\obtain_data\task\obtain_update_files + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: update_obtain_data, order: 30 } + + installer.obtain_data.update_ftp_settings: + class: phpbb\install\module\obtain_data\task\obtain_update_ftp_data + arguments: + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.update_helper' + - '%core.php_ext%' + tags: + - { name: update_obtain_data, order: 40 } + + installer.module.update_obtain_data_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: update_obtain_data, class_name_aware: true } + + installer.module.obtain_data_update: + class: phpbb\install\module\obtain_data\update_module + parent: installer.module_base + arguments: + - '@installer.module.update_obtain_data_collection' + - true + - false + tags: + - { name: installer_update_module, order: 20 } diff --git a/config/installer/container/services_update_requirements.yml b/config/installer/container/services_update_requirements.yml new file mode 100644 index 0000000..6b851de --- /dev/null +++ b/config/installer/container/services_update_requirements.yml @@ -0,0 +1,41 @@ +services: + installer.requirements.check_filesystem_update: + class: phpbb\install\module\requirements\task\check_filesystem + arguments: + - '@filesystem' + - '@installer.helper.iohandler' + - '%core.root_path%' + - '%core.php_ext%' + - false + tags: + - { name: update_requirements, order: 10 } + + installer.requirements.update_requirements: + class: phpbb\install\module\requirements\task\check_update + arguments: + - '@installer.helper.container_factory' + - '@filesystem' + - '@installer.helper.config' + - '@installer.helper.iohandler' + - '@installer.helper.update_helper' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: update_requirements, order: 30 } + + installer.module.update_requirements_collection: + class: phpbb\di\ordered_service_collection + arguments: + - '@service_container' + tags: + - { name: service_collection, tag: update_requirements, class_name_aware: true } + + installer.module.requirements_update: + class: phpbb\install\module\requirements\update_module + parent: installer.module_base + arguments: + - '@installer.module.update_requirements_collection' + - true + - false + tags: + - { name: installer_update_module, order: 10 } diff --git a/config/installer/routing/environment.yml b/config/installer/routing/environment.yml new file mode 100644 index 0000000..7a1f588 --- /dev/null +++ b/config/installer/routing/environment.yml @@ -0,0 +1,2 @@ +core.default: + resource: installer.yml diff --git a/config/installer/routing/installer.yml b/config/installer/routing/installer.yml new file mode 100644 index 0000000..66b8893 --- /dev/null +++ b/config/installer/routing/installer.yml @@ -0,0 +1,67 @@ +phpbb_installer_index: + path: / + defaults: + _controller: phpbb.installer.controller.welcome:handle + mode: 'intro' + +phpbb_installer_license: + path: /license + defaults: + _controller: phpbb.installer.controller.welcome:handle + mode: 'license' + +phpbb_installer_support: + path: /support + defaults: + _controller: phpbb.installer.controller.welcome:handle + mode: 'support' + +phpbb_installer_install: + path: /install + defaults: + _controller: phpbb.installer.controller.install:handle + +phpbb_installer_update: + path: /update + defaults: + _controller: phpbb.installer.controller.update:handle + +phpbb_installer_update_file_download: + path: /download/updated + defaults: + _controller: phpbb.installer.controller.file_downloader:update_archive + +phpbb_installer_update_conflict_download: + path: /download/conflict + defaults: + _controller: phpbb.installer.controller.file_downloader:conflict_archive + +phpbb_convert_intro: + path: /convert/{start_new} + defaults: + _controller: phpbb.installer.controller.convert:intro + start_new: 0 + +phpbb_convert_settings: + path: /convert/settings/{converter} + defaults: + _controller: phpbb.installer.controller.convert:settings + requirements: + converter: "[a-zA-Z0-9_]+" + +phpbb_convert_convert: + path: /convert/in_progress/{converter} + defaults: + _controller: phpbb.installer.controller.convert:convert + requirements: + converter: "[a-zA-Z0-9_]+" + +phpbb_convert_finish: + path: /convert/finished + defaults: + _controller: phpbb.installer.controller.convert:finish + +phpbb_installer_status: + path: /installer/status + defaults: + _controller: phpbb.installer.controller.status:status diff --git a/config/production/config.yml b/config/production/config.yml new file mode 100644 index 0000000..979dbbc --- /dev/null +++ b/config/production/config.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ../default/config.yml } diff --git a/config/production/container/environment.yml b/config/production/container/environment.yml new file mode 100644 index 0000000..40a3c7a --- /dev/null +++ b/config/production/container/environment.yml @@ -0,0 +1,3 @@ +imports: + - { resource: services.yml } + - { resource: parameters.yml } diff --git a/config/production/container/parameters.yml b/config/production/container/parameters.yml new file mode 100644 index 0000000..0447646 --- /dev/null +++ b/config/production/container/parameters.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ../../default/container/parameters.yml } diff --git a/config/production/container/services.yml b/config/production/container/services.yml new file mode 100644 index 0000000..b302f0f --- /dev/null +++ b/config/production/container/services.yml @@ -0,0 +1,2 @@ +imports: + - { resource: ../../default/container/services.yml } diff --git a/config/production/routing/environment.yml b/config/production/routing/environment.yml new file mode 100644 index 0000000..301183b --- /dev/null +++ b/config/production/routing/environment.yml @@ -0,0 +1,2 @@ +core.default: + resource: ../../default/routing/routing.yml diff --git a/cron.php b/cron.php new file mode 100644 index 0000000..2f51994 --- /dev/null +++ b/cron.php @@ -0,0 +1,84 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +*/ +define('IN_PHPBB', true); +define('IN_CRON', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); + +// Do not update users last page entry +$user->session_begin(false); +$auth->acl($user->data); + +function output_image() +{ + // Output transparent gif + header('Cache-Control: no-cache'); + header('Content-type: image/gif'); + header('Content-length: 43'); + + echo base64_decode('R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='); + + // Flush here to prevent browser from showing the page as loading while + // running cron. + flush(); +} + +// Thanks to various fatal errors and lack of try/finally, it is quite easy to leave +// the cron lock locked, especially when working on cron-related code. +// +// Attempt to alleviate the problem by doing setup outside of the lock as much as possible. + +$cron_type = $request->variable('cron_type', ''); + +// Comment this line out for debugging so the page does not return an image. +output_image(); + +/* @var $cron_lock \phpbb\lock\db */ +$cron_lock = $phpbb_container->get('cron.lock_db'); +if ($cron_lock->acquire()) +{ + /* @var $cron \phpbb\cron\manager */ + $cron = $phpbb_container->get('cron.manager'); + + $task = $cron->find_task($cron_type); + if ($task) + { + /** + * This event enables you to catch the task before it runs + * + * @event core.cron_run_before + * @var \phpbb\cron\task\wrapper task Current Cron task + * @since 3.1.8-RC1 + */ + $vars = array( + 'task', + ); + extract($phpbb_dispatcher->trigger_event('core.cron_run_before', compact($vars))); + + if ($task->is_parametrized()) + { + $task->parse_parameters($request); + } + if ($task->is_ready()) + { + $task->run(); + } + } + $cron_lock->release(); +} + +garbage_collection(); diff --git a/docs/CHANGELOG.html b/docs/CHANGELOG.html new file mode 100644 index 0000000..b21c0ca --- /dev/null +++ b/docs/CHANGELOG.html @@ -0,0 +1,6657 @@ + + + + + + + +phpBB • Changelog + + + + + + + +
+ + + + + +
+ + + +

+ This is a non-exhaustive (but still near complete) changelog for phpBB 3.2.x including release candidate versions. + Our thanks to all those people who've contributed bug reports and code fixes. +

+ +

Changelog

+ + + +
+ +

1. Changelog

+ +
+
+ +
+

Changes since 3.2.6

+

Bug

+
    +
  • [PHPBB3-16034] - Links created with [url=] - are sometimes incorrectly shortened
  • +
  • [PHPBB3-16036] - Cannot login with 3.2.6
  • +
  • [PHPBB3-16037] - Private message ViewFolder Broken
  • +
  • [PHPBB3-16039] - Unable to change announcement to standard topic due to missing global
  • +
+

Improvement

+
    +
  • [PHPBB3-16042] - Use S_LOGIN_REDIRECT to output login form token
  • +
+ +

Changes since 3.2.6-RC1

+

Bug

+ +

Security Issue

+
    +
  • [SECURITY-231] - Remote avatar functionality allows checking for files and ports on local network
  • +
  • [SECURITY-235] - Fulltext native search can be used to cause long execution times
  • +
+

Hardening

+
    +
  • [SECURITY-228] - Require form token in login_box
  • +
  • [SECURITY-233] - SMTP auth data shouldn't be cached
  • +
  • [SECURITY-234] - Main website URL in Admin Control Panel should not support JS URLs
  • +
+ +

Changes since 3.2.5

+

Bug

+
    +
  • [PHPBB3-15509] - Update database: info message is to scary
  • +
  • [PHPBB3-15869] - Cookies Problem with domains with special chars
  • +
  • [PHPBB3-15876] - Mysql 5.7 support Q&A plugin
  • +
  • [PHPBB3-15883] - No error for invalid usernames on bulk add to usergroup
  • +
  • [PHPBB3-15904] - PHP warning when accessing modules in ACP System tab
  • +
  • [PHPBB3-15917] - Unapproved posts count towards forum post count
  • +
  • [PHPBB3-15918] - Ban reason messages show backslash (\) before apostrophe -- ex. (don\'t).
  • +
  • [PHPBB3-15919] - Lint test throws PHP warnings due to node modules folder
  • +
  • [PHPBB3-15931] - Issues in PM report emails
  • +
  • [PHPBB3-15954] - Some calls to include() don't have a safeguard
  • +
  • [PHPBB3-15957] - User preferences show notification method "both" with disabled Jabber in ACP
  • +
  • [PHPBB3-15959] - Travis Network Test is Failing for news.cnet.com
  • +
  • [PHPBB3-15965] - Console command to handle thumbnails have files directory hardcoded
  • +
  • [PHPBB3-15975] - Delete or prune an user doesn't remove its entries in the user_notifications table
  • +
  • [PHPBB3-15986] - Add missing language key for posting.php
  • +
  • [PHPBB3-15996] - Invalid data provider function name in migrator_tool_permission_test
  • +
  • [PHPBB3-16006] - Duplicate form IDs in UCP oauth form
  • +
+

Improvement

+
    +
  • [PHPBB3-15884] - Add memberlist_body_* events
  • +
  • [PHPBB3-15889] - Add core.memberlist_modify_memberrow_sql
  • +
  • [PHPBB3-15890] - Add core.memberlist_modify_viewprofile_sql
  • +
  • [PHPBB3-15891] - Add core.memberlist_modify_view_profile_template_vars
  • +
  • [PHPBB3-15898] - Add core.ucp_pm_compose_template
  • +
  • [PHPBB3-15899] - Add core.modify_attachment_sql_ary_on_* events
  • +
  • [PHPBB3-15901] - Add mcp_post_* template events
  • +
  • [PHPBB3-15910] - Pass object arguments by reference implicitly
  • +
  • [PHPBB3-15914] - Add core.modify_memberlist_viewprofile_group_sql and core.modify_memberlist_viewprofile_group_data
  • +
  • [PHPBB3-15915] - Add template events to posting_attach_body.html
  • +
  • [PHPBB3-15924] - Move from precise to trusty builds
  • +
  • [PHPBB3-15926] - Deny installs on PHP >= 7.3@dev - Increase min. req. to 5.4.7
  • +
  • [PHPBB3-15928] - Remove support for backup download
  • +
  • [PHPBB3-15939] - Pagination docblocks
  • +
  • [PHPBB3-15941] - Replace MAX SQL in functions_posting.php
  • +
  • [PHPBB3-15942] - Array to string conversion when permanently deleting a post
  • +
  • [PHPBB3-15948] - Add core.mcp_change_topic_type_after/before
  • +
  • [PHPBB3-15949] - [Template] - ucp_profile_signature_posting_editor_options_prepend
  • +
  • [PHPBB3-15950] - Add SQL transactions to mcp_main.php
  • +
  • [PHPBB3-15960] - Add SQL transactions to functions_admin.php
  • +
  • [PHPBB3-15970] - Add core.message_admin_form_submit_before
  • +
  • [PHPBB3-15972] - Add core.markread_after
  • +
  • [PHPBB3-15992] - Fix breadcrumb schema
  • +
  • [PHPBB3-15995] - Add core.memberlist_modify_sort_pagination_params
  • +
  • [PHPBB3-15997] - Increase webdriver timeout for UI tests
  • +
  • [PHPBB3-16001] - Append data to the OAuth's redirect URL
  • +
  • [PHPBB3-16009] - Display OAuth login's buttons in a row.
  • +
  • [PHPBB3-16010] - Automatically check order of events in events.md file
  • +
  • [PHPBB3-16018] - Update composer and dependencies for 3.2.6
  • +
  • [PHPBB3-16020] - Fix placement of event viewforum_body_topic_author_username_append
  • +
+

New Feature

+
    +
  • [PHPBB3-15944] - Add core.posting_modify_quote_attributes
  • +
+

Task

+ + +

Changes since 3.2.5-RC1

+

Bug

+
    +
  • [PHPBB3-15888] - Update link to user guide
  • +
  • [PHPBB3-15893] - Call to undefined $user in phpbb_format_quote() when BBCodes are disabled
  • +
  • [PHPBB3-15911] - SQL general error on DB update from 3.0 branch
  • +
+

Hardening

+
    +
  • [SECURITY-229] - Update to latest version of jQuery 1.x and add ajax prefilter
  • +
+ +

Changes since 3.2.4

+

Bug

+
    +
  • [PHPBB3-15665] - MSSQL implementation crashes when upload directory > 2GB
  • +
  • [PHPBB3-15858] - Unapproved User(s) appearing as Guest in Team Page.
  • +
  • [PHPBB3-15867] - Contact form without class
  • +
  • [PHPBB3-15871] - PHP 7.1+ warning in ACP extensions module
  • +
  • [PHPBB3-15875] - BBCode parsing error (PHP fatal error)
  • +
  • [PHPBB3-15881] - Login keys are not reset after password update in some cases
  • +
+

Improvement

+
    +
  • [PHPBB3-15542] - Some JS files being called without assets version
  • +
  • [PHPBB3-15859] - Modify the topic ordering if needed
  • +
  • [PHPBB3-15863] - Modify the topic sort ordering from the beginning
  • +
  • [PHPBB3-15870] - Modify the forum ID to handle the correct display of viewtopic if needed
  • +
  • [PHPBB3-15872] - Add show_user_activity to display_user_activity_modify_actives
  • +
  • [PHPBB3-15873] - Event to add/modify MCP report details template data.
  • +
  • [PHPBB3-15878] - Add attachment to core.ucp_pm_view_message
  • +
  • [PHPBB3-15879] - Modify attachment's poster_id for get_submitted_attachment_data
  • +
+ +

Changes since 3.2.4-RC1

+

Bug

+
    +
  • [PHPBB3-15860] - Backups filenames arent saved in the expected format
  • +
+

Security Issue

+
    +
  • [SECURITY-227] - Phar deserialization in ACP leads to Remote Code Execution
  • +
+ +

Changes since 3.2.3

+

Bug

+
    +
  • [PHPBB3-11453] - phpbb_notification_method_email unnecessarily loads data of banned users.
  • +
  • [PHPBB3-12430] - hilit not removed from URL after search
  • +
  • [PHPBB3-13043] - Fixing HTML5 conformance
  • +
  • [PHPBB3-13128] - sql_query_info, max_matches and charset_type removed from sphinxsearch 2.2.2-beta
  • +
  • [PHPBB3-14812] - No shadow pruning with system cron enabled
  • +
  • [PHPBB3-15329] - View/Edit drafts contain underlying HTML coding
  • +
  • [PHPBB3-15420] - Quote Notification Sent for Edited Posts by Non Author
  • +
  • [PHPBB3-15494] - Users can only be removed once from newly registered users
  • +
  • [PHPBB3-15507] - PHP 7.2 Warning
  • +
  • [PHPBB3-15544] - Migrations don't delete modules in every case
  • +
  • [PHPBB3-15552] - Private Message (PM) "find a member" button "select marked" not working
  • +
  • [PHPBB3-15557] - Used composer version has bug with PHP 7.2
  • +
  • [PHPBB3-15583] - Updating session time in AJAX request ignores 60 seconds check
  • +
  • [PHPBB3-15593] - Disabling "print view" (permission or private messages settings) actually doesn't block the feature
  • +
  • [PHPBB3-15600] - Ban reasons are not escaped in mcp_ban.html template
  • +
  • [PHPBB3-15604] - Appveyor builds unable to download and unpack MSSQL drivers
  • +
  • [PHPBB3-15606] - Hide/Reveal 'Profile' Link According to Permission Setting
  • +
  • [PHPBB3-15607] - Board's cookies not deleted on disabled board
  • +
  • [PHPBB3-15611] - Prosilver mobile layout: Misaligned text in user profile
  • +
  • [PHPBB3-15612] - PHP warning with MSSQL on PHP 7.2
  • +
  • [PHPBB3-15616] - Jumpbox doesn't display in the login forum page (access to forum with password)
  • +
  • [PHPBB3-15618] - Team page link always appears when you are logout (anonymous), even if you don't have the permission (unlike memberlist link)
  • +
  • [PHPBB3-15619] - Legends of custom profile fields could be hidden in memberlist, when viewing an user group
  • +
  • [PHPBB3-15620] - Avatar gallery can be unusable on multilingual boards, unless people use the board default language
  • +
  • [PHPBB3-15622] - Quoting messages (while viewing one, not inside post editor) can return a wrong chain
  • +
  • [PHPBB3-15637] - Event list only has first line of PHP event description
  • +
  • [PHPBB3-15651] - Migration 'if' conditions only support booleans
  • +
  • [PHPBB3-15659] - retrieve_block_vars generates warnings in PHP 7.2
  • +
  • [PHPBB3-15666] - Language system is not fully supported in Twig
  • +
  • [PHPBB3-15670] - Group forum permission: Can see forum gives NO SQL ERROR
  • +
  • [PHPBB3-15673] - Duplicated links for (ACP,MCP,FAQ) in QuickLinks and main nav bar
  • +
  • [PHPBB3-15680] - INSTALL.html should point to 3.2 documentation instead of 3.1
  • +
  • [PHPBB3-15693] - gen_rand_string() don't return a string with the expected length
  • +
  • [PHPBB3-15695] - gen_rand_string can return less characters than expected
  • +
  • [PHPBB3-15700] - {T_THEME_LANG_NAME} template variable could be wrong when log off
  • +
  • [PHPBB3-15705] - phpbbcli language parse error in PHP <= 5.5.38
  • +
  • [PHPBB3-15716] - OAuth link information remains after deleting a user, causes fatal exception
  • +
  • [PHPBB3-15717] - Old email address missing from log when user changes email address
  • +
  • [PHPBB3-15723] - gen_rand_string() return wrong number or characters sometimes
  • +
  • [PHPBB3-15733] - Remove unused variables related to deprecated flood control
  • +
  • [PHPBB3-15742] - Remove get_magic_quotes_gpc from type_cast_helper
  • +
  • [PHPBB3-15751] - Warning when update with CLI
  • +
  • [PHPBB3-15755] - Broken events in /phpbb/attachment/delete.php
  • +
  • [PHPBB3-15758] - String INSECURE_REDIRECT is not shown translated
  • +
  • [PHPBB3-15770] - Sphinx assertion fails on unread posts when exceeding an offset of 999
  • +
  • [PHPBB3-15788] - Return button from privacy policy shows wrong text
  • +
  • [PHPBB3-15817] - Unable to install in Oracle 11R2 Express
  • +
  • [PHPBB3-15824] - UI test framework Broken for extensions
  • +
  • [PHPBB3-15830] - 'core.modify_notification_message' event is useless
  • +
  • [PHPBB3-15849] - PHP 7.2 compat for bitfield class
  • +
  • [PHPBB3-15852] - IPv6 address not working in Whois
  • +
+

Improvement

+
    +
  • [PHPBB3-10432] - Don't require username when user forgets password
  • +
  • [PHPBB3-11500] - on Custom profile fields the field_ident field lacks name
  • +
  • [PHPBB3-12739] - Make the font color palette in ACP same as Prosilver
  • +
  • [PHPBB3-14656] - Add a list-unsubscribe header with the unsubscribe URL
  • +
  • [PHPBB3-14990] - Add core event to the Twig environment
  • +
  • [PHPBB3-15554] - Simple footer after load js
  • +
  • [PHPBB3-15579] - Add core.ucp_main_front_modify_sql and core.ucp_main_front_modify_template_vars
  • +
  • [PHPBB3-15590] - Add PHP events after adding, updating and deleting BBCodes
  • +
  • [PHPBB3-15628] - newtopic_notify.txt does not have directly link to the new topic
  • +
  • [PHPBB3-15638] - Add word-break for overflowing.
  • +
  • [PHPBB3-15642] - String to be used in HTML element contains ">"
  • +
  • [PHPBB3-15656] - Add "View post" link in the mod logs on the ACP
  • +
  • [PHPBB3-15661] - Add core.viewtopic_modify_poll_ajax_data
  • +
  • [PHPBB3-15662] - Add $this->template to core.modify_notification_message
  • +
  • [PHPBB3-15668] - Change JQuery .load(fn) event to .on('load',fn)
  • +
  • [PHPBB3-15674] - Edit language lines in file en\acp\profile.php
  • +
  • [PHPBB3-15683] - Better error message when commit message has CRLF
  • +
  • [PHPBB3-15696] - 'if' module tool should support calling other tools
  • +
  • [PHPBB3-15706] - [Template] - mcp_post_report_buttons_top_*
  • +
  • [PHPBB3-15719] - Add core event on viewtopic post_list query for query modification
  • +
  • [PHPBB3-15726] - Implement selective purge in APCu cache driver
  • +
  • [PHPBB3-15735] - [Template] - *_content_after (for posts)
  • +
  • [PHPBB3-15737] - [PHP] - Add $user_rows to core.delete_user_before
  • +
  • [PHPBB3-15762] - Topics per page Conformity
  • +
  • [PHPBB3-15768] - Add a license to a repository
  • +
  • [PHPBB3-15771] - Q&A configuration instructions not optilmal
  • +
  • [PHPBB3-15799] - Find correct poll for voting animation
  • +
  • [PHPBB3-15803] - Add core events on ucp_pm_compose for additional message list actions
  • +
  • [PHPBB3-15819] - Add core event to functions_posting to modify notifications
  • +
  • [PHPBB3-15825] - Add core.acp_manage_forums_move_content_sql_before
  • +
  • [PHPBB3-15826] - Add core.mcp_main_fork_sql_after
  • +
  • [PHPBB3-15827] - [Template] - Add *_username_{prepend/append} template events
  • +
  • [PHPBB3-15831] - ACP signature update should trigger event
  • +
  • [PHPBB3-15832] - ACP avatar update event
  • +
  • [PHPBB3-15833] - ACP and UCP avatar delete events
  • +
  • [PHPBB3-15837] - Add core.ucp_register_welcome_email_before
  • +
  • [PHPBB3-15838] - Add core.ucp_register_register_after
  • +
  • [PHPBB3-15839] - Add core.ucp_login_link_template_after
  • +
  • [PHPBB3-15841] - Allow postrow pm link to be modified by event
  • +
  • [PHPBB3-15848] - Up-version plupload to v2.3.6 to fix image rotation issues
  • +
  • [PHPBB3-15850] - Use standard SQL cache for notification types
  • +
+

New Feature

+
    +
  • [PHPBB3-15792] - [Template] - confirm_delete_body_delete_reason_before
  • +
+

Task

+
    +
  • [PHPBB3-15596] - Migrate from data-vocabulary.org to schema.org
  • +
  • [PHPBB3-15621] - Some graphical inconsistencies with colored users groups in posting, UCP and MCP
  • +
  • [PHPBB3-15701] - {SIGNATURE} variable is added in mcp_post.html but not defined in MCP
  • +
  • [PHPBB3-15809] - Allow events with twig syntax
  • +
  • [PHPBB3-15857] - Add rubencm to CREDITS.txt
  • +
+ +

Changes since 3.2.3-RC2

+

Bug

+ +

Improvement

+ + +

Changes since 3.2.3-RC1

+

Bug

+
    +
  • [PHPBB3-11847] - auth_provider_oauth migration must depend on at least one migration that ensures the module tables exist as expected
  • +
  • [PHPBB3-15548] - Dead link in ACP_COOKIE_SETTINGS_EXPLAIN language entry
  • +
  • [PHPBB3-15586] - When creating a module without the modes array a missing 'module_langname' index is accessed
  • +
  • [PHPBB3-15613] - Notification dropdown said to be not RTL compliant
  • +
  • [PHPBB3-15627] - Improve wording of YES_ACCURATE_PM_BUTTON + EXPLAIN
  • +
  • [PHPBB3-15678] - PHP warning in filesystem.php
  • +
  • [PHPBB3-15731] - Fix acp_search language parameters when deleting index
  • +
+

Improvement

+
    +
  • [PHPBB3-15630] - Change <b> to <strong>
  • +
  • [PHPBB3-15633] - Remove extra space in GROUP_MAX_RECIPIENTS_EXPLAIN
  • +
  • [PHPBB3-15657] - Add core.mcp_queue_get_posts_for_posts_query_before and core.mcp_queue_get_posts_modify_post_row
  • +
  • [PHPBB3-15676] - Display privacy policy & terms of use more prominently
  • +
+

Task

+ + +

Changes since 3.2.2

+

Bug

+
    +
  • [PHPBB3-14936] - Missing language variable INST_ERR_DB
  • +
  • [PHPBB3-15491] - Outdated linkes in installer support page
  • +
  • [PHPBB3-15492] - Permissions role combobox does not work in RTL
  • +
  • [PHPBB3-15500] - Docs outdated for new PHP 5.4.7 requirement
  • +
  • [PHPBB3-15502] - Errors in migrations in 3.2.2 release
  • +
  • [PHPBB3-15506] - Previewing new post empties attachment list of all but first attachment
  • +
  • [PHPBB3-15512] - Avoid reparsing non-existent polls
  • +
  • [PHPBB3-15513] - Signature edit in acp gives error
  • +
  • [PHPBB3-15520] - DbDriver->sql_build_query cant cope with sub-selects
  • +
  • [PHPBB3-15522] - Allow multiple color palettes per page
  • +
  • [PHPBB3-15523] - AdBlocker may cause JS error when using CookieConsent
  • +
  • [PHPBB3-15525] - composer.json License is Invalid/Deprecated
  • +
  • [PHPBB3-15526] - Cast bbcode ID to integer
  • +
  • [PHPBB3-15527] - Cannot interpret the BBCode definition
  • +
  • [PHPBB3-15532] - Update pull request template
  • +
  • [PHPBB3-15533] - Typo in viewtopic_topic_tools.html
  • +
  • [PHPBB3-15558] - phpbb\report\report_handler_post.php
  • +
  • [PHPBB3-15559] - phpbb\report\report_handler_pm.php:56
  • +
  • [PHPBB3-15595] - Migration Module Exists Tool Broken
  • +
+

Improvement

+
    +
  • [PHPBB3-12579] - Add BUTTON_ language strings for post & PM buttons
  • +
  • [PHPBB3-15495] - Use transactions for queries in move_forum
  • +
  • [PHPBB3-15499] - Drop HHVM support
  • +
  • [PHPBB3-15510] - Link Orphan attachments in ACP>General to Orphaned attachments page
  • +
  • [PHPBB3-15514] - Improve accessibility by adding vital info from explanation to a title
  • +
  • [PHPBB3-15518] - Do not attempt to accurately determine whether posters can read private messages in viewtopic
  • +
  • [PHPBB3-15528] - Display the version of the installed styles in acp
  • +
  • [PHPBB3-15529] - Color groups in ACP
  • +
  • [PHPBB3-15531] - Log malformed BBCodes
  • +
  • [PHPBB3-15534] - Outdated ACP extensions database link for phpBB 3.2
  • +
  • [PHPBB3-15535] - Add S_FIRST_POST to postrow on viewtopic
  • +
  • [PHPBB3-15537] - Add events core.search_(native|mysql|postgres|sphinx)_index_before
  • +
  • [PHPBB3-15547] - Add file object to event core.avatar_driver_upload_move_file_before
  • +
  • [PHPBB3-15561] - Add core events for adding columns to MySQL and Postgres search backends
  • +
  • [PHPBB3-15568] - Update depencies to latest versions
  • +
  • [PHPBB3-15569] - Adjust update instructions to suggest file replacement method
  • +
+

New Feature

+
    +
  • [PHPBB3-15398] - Add event to oauth login after ID check
  • +
+

Security Issue

+
    +
  • [PHPBB3-15570] - Extension version check is restricted to TLS 1.0
  • +
+

Task

+ + +

Changes since 3.2.1

+

Security Issue

+
    +
  • [SECURITY-211] - URLs with javascript scheme should not be made clickable
  • +
+

Bug

+
    +
  • [PHPBB3-7845] - Error on posting local image when script path is empty
  • +
  • [PHPBB3-13214] - Contact us page textarea looks narrow in responsive mode
  • +
  • [PHPBB3-14629] - acp global quick reply will not enable quick reply correctly
  • +
  • [PHPBB3-14857] - ordinal suffix in dateformat is not handled in translations
  • +
  • [PHPBB3-15041] - Cannot delete Orphaned Attachments when large number of attachments
  • +
  • [PHPBB3-15060] - Online user list fails on notifications
  • +
  • [PHPBB3-15089] - Enable/Disable settings backwards for Cookie Secure
  • +
  • [PHPBB3-15133] - Fast image size library sometimes returns no size or invalid sizes
  • +
  • [PHPBB3-15149] - Unexpected Ctrl+Enter behavior on reply
  • +
  • [PHPBB3-15171] - Confusing bitfield values
  • +
  • [PHPBB3-15172] - $request->server('server_port') is returning wrong port
  • +
  • [PHPBB3-15174] - Unable to purge cache (ext & acp)
  • +
  • [PHPBB3-15195] - Code direction in print view is not defined as "ltr"
  • +
  • [PHPBB3-15201] - Removing style sets user_style to 0
  • +
  • [PHPBB3-15224] - Advanced search in "message text only" crashes with SQL error when using Mysql fulltext search index
  • +
  • [PHPBB3-15245] - Relative URLs in atom feeds broken when accessing via app.php
  • +
  • [PHPBB3-15262] - WebFontConfig google families script issue in 3.2.1
  • +
  • [PHPBB3-15266] - Content visibility events do not allow what they describe
  • +
  • [PHPBB3-15273] - 'COOKIE_PATH_EXPLAIN' does not make sense
  • +
  • [PHPBB3-15285] - Travis tests are failing due to trusty changes
  • +
  • [PHPBB3-15292] - Retina imageset is blurry when displayed in Chrome browser
  • +
  • [PHPBB3-15297] - Current date in board index is broken into lines in RTL
  • +
  • [PHPBB3-15298] - Errors being suppressed in cli
  • +
  • [PHPBB3-15303] - Typo in memcached driver
  • +
  • [PHPBB3-15306] - Error and missing information in core.acp_users_profile_validate event
  • +
  • [PHPBB3-15309] - Improved fix for pagination layout in tables
  • +
  • [PHPBB3-15314] - Wrong class constructor definition for convertor component
  • +
  • [PHPBB3-15319] - Database update v310\style_update_p2 fails to drop sequences
  • +
  • [PHPBB3-15320] - Redis cache does not save keys with expiration date 0 (no expiration)
  • +
  • [PHPBB3-15322] - Wrong return Return-Path in emails
  • +
  • [PHPBB3-15331] - Gravatars cannot be overridden
  • +
  • [PHPBB3-15332] - Dark background is always removed after confirm popup
  • +
  • [PHPBB3-15333] - Callback isn't called when confirm dialog is canceled
  • +
  • [PHPBB3-15339] - Missing acp_send_statistics -> Upgrading to 3.2.0 fails for phpBB 3.0.5
  • +
  • [PHPBB3-15346] - The installer tries to enable all extensions even if they are not enableable
  • +
  • [PHPBB3-15347] - Password updater in cron generates invalid postgres SQL
  • +
  • [PHPBB3-15349] - Cli doesn't check if an extension is enableable before enable it
  • +
  • [PHPBB3-15350] - Links to Plural rules are outdated
  • +
  • [PHPBB3-15351] - Confirm box function does not work with symlink on server config
  • +
  • [PHPBB3-15353] - Invalid HTML in ACP board settings
  • +
  • [PHPBB3-15355] - Empty version field in versioncheck when using the latest version
  • +
  • [PHPBB3-15356] - Avatar remote upload doesn't work
  • +
  • [PHPBB3-15361] - Topic / Forum Icons Look Withered (on Safari)
  • +
  • [PHPBB3-15362] - Excessive value for {NOTIFICATION_TYPES_COLS}
  • +
  • [PHPBB3-15365] - Fix invalidating OPcache
  • +
  • [PHPBB3-15367] - Sphinx search backend doesn't escape special characters
  • +
  • [PHPBB3-15368] - Schema upgrade fails in 3.2.1 when using SQL Server
  • +
  • [PHPBB3-15379] - Reparser cron will always run
  • +
  • [PHPBB3-15381] - L_CONTACT_US_ENABLE_EXPLAIN should specify that "Enable board-wide emails" is also needed for it to work
  • +
  • [PHPBB3-15390] - Admin permissions role tooltip popup has vertical bar running through it.
  • +
  • [PHPBB3-15396] - revert_schema() steps not executed in correct order
  • +
  • [PHPBB3-15401] - Use separate constant for memcached driver config
  • +
  • [PHPBB3-15419] - Sphinx does not search UTF keywords in delta index
  • +
  • [PHPBB3-15423] - Wrong title for topic's "Unappproved posts" icon
  • +
  • [PHPBB3-15432] - Don't remove dark background if fadedark is false
  • +
  • [PHPBB3-15433] - phpbbcli can enable non-existent extension
  • +
  • [PHPBB3-15445] - Git Contribution Guidelines in README.md is outdated
  • +
  • [PHPBB3-15464] - Can't reparse [IMG] - in uppercase
  • +
  • [PHPBB3-15475] - Restore Travis PR commit message validation
  • +
  • [PHPBB3-15478] - core.js $loadingIndicator JavaScript errors
  • +
  • [PHPBB3-15489] - Wrong footer text on forum of type "category"
  • +
  • [PHPBB3-15496] - SQL Error in PostgreSQL Fulltext search when results displayed as topics
  • +
  • [PHPBB3-15497] - Declaration of admin_activate_user::create_insert_array not compatible with base
  • +
  • [PHPBB3-15498] - confirm_box() adds duplicate strings to URLs in extensions
  • +
+

Improvement

+
    +
  • [PHPBB3-7488] - View Only - Categories: No Message
  • +
  • [PHPBB3-9819] - Move functions definitions out of mcp.php and includes/mcp/mcp_*.php
  • +
  • [PHPBB3-12291] - Allow extensions to use custom topic icons
  • +
  • [PHPBB3-12939] - Drop support for IE <11 on January 2016
  • +
  • [PHPBB3-14677] - Extension update check is not very colorblind / colourblind friendly.
  • +
  • [PHPBB3-14820] - Style Version Missing
  • +
  • [PHPBB3-14919] - Inconsistent use of globals vs class elements in acp_extensions
  • +
  • [PHPBB3-14927] - event core.user_add_modify_data
  • +
  • [PHPBB3-14944] - Add possibility to search for template loop indexes by key
  • +
  • [PHPBB3-14950] - Add possibility to delete a template block with alter_block_array
  • +
  • [PHPBB3-14979] - Remove underline from unread icon
  • +
  • [PHPBB3-14994] - Refactor template->assign_block_var to be consistent with alter_block_array
  • +
  • [PHPBB3-14995] - Add ACP template events acp_ext_list_*_name_after
  • +
  • [PHPBB3-15111] - Fix the typo in ucp_pm_view_messsage
  • +
  • [PHPBB3-15134] - Avatar upload driver should use filesystem service
  • +
  • [PHPBB3-15247] - Add driver for APCu v5.x cache
  • +
  • [PHPBB3-15267] - Hide birthday block if the user cannot view profile
  • +
  • [PHPBB3-15291] - Allow short array notation in event declarations
  • +
  • [PHPBB3-15293] - Prevent skipping file changes in automatic updater
  • +
  • [PHPBB3-15307] - Allow extensions to add custom modes to acp_users module
  • +
  • [PHPBB3-15328] - Disable email/jabber checkbox if notification method isn't supported
  • +
  • [PHPBB3-15340] - Update to plupload 2.3.1, stable for one year
  • +
  • [PHPBB3-15352] - Add text to clarify forum descriptions won't display on categories
  • +
  • [PHPBB3-15374] - Add core event to modify page title in viewforum.php
  • +
  • [PHPBB3-15384] - Add linebreaks to SMTP configuration option explanations
  • +
  • [PHPBB3-15385] - nginx sample config: www redirection, security regex
  • +
  • [PHPBB3-15387] - prosilver: vertical bars on forum rows on index page not full height
  • +
  • [PHPBB3-15389] - Simplify migration between event names
  • +
  • [PHPBB3-15391] - Remove not needed image rendering from topic/forum images
  • +
  • [PHPBB3-15394] - Add $user_cache and $post_edit_list to core.viewtopic_modify_post_row
  • +
  • [PHPBB3-15408] - Reject duplicate BBCodes in ACP
  • +
  • [PHPBB3-15409] - Add u_action to core.acp_users_overview_run_quicktool
  • +
  • [PHPBB3-15442] - Allow unsafe HTML in bbcode.html
  • +
  • [PHPBB3-15444] - Merge duplicate BBCodes via a migration
  • +
  • [PHPBB3-15446] - Add event core.acp_profile_action
  • +
  • [PHPBB3-15447] - Add event core.acp_profile_modify_profile_row
  • +
  • [PHPBB3-15451] - [EVENT] - mcp_topic_postrow_attachments_before/after
  • +
  • [PHPBB3-15452] - [EVENT] - mcp_topic_postrow_post_before
  • +
  • [PHPBB3-15453] - Add event in acp_language after delete language
  • +
  • [PHPBB3-15454] - event - mcp_queue_approve_details_template
  • +
  • [PHPBB3-15470] - attachment boxes need there own font-size
  • +
  • [PHPBB3-15471] - Add core events to ACP when pruning a forum
  • +
  • [PHPBB3-15476] - Add core event before search rows are edited
  • +
  • [PHPBB3-15485] - Add template event to forumlist_body > forum images
  • +
  • [PHPBB3-15486] - Add core event to the function user_add() to modify notifications data
  • +
+

New Feature

+ +

Sub-task

+
    +
  • [PHPBB3-13150] - [Event] - core.phpbb_log_get_topic_auth_sql_after
  • +
  • [PHPBB3-15468] - Add a service to merge duplicate BBCodes
  • +
+

Task

+
    +
  • [PHPBB3-15179] - Update 3.2.x dependencies and fix Twig > 1.25 compatibility
  • +
  • [PHPBB3-15304] - Update s9e/text-formatter dependency
  • +
  • [PHPBB3-15455] - Margin discrepancy due to <!-- INCLUDE jumpbox.html -->
  • +
  • [PHPBB3-15457] - Update s9e/text-formatter dependency
  • +
+ +

Changes since 3.2.0

+

Bug

+
    +
  • [PHPBB3-7336] - Words in new topic title aren't found by search after topic is split
  • +
  • [PHPBB3-11076] - Update notification in ACP for minimum PHP version missing essential information
  • +
  • [PHPBB3-11611] - setup_github_network.php no longer creates a repository
  • +
  • [PHPBB3-13250] - File cache does not write entries starting with _ and containing a slash
  • +
  • [PHPBB3-14732] - Update/remove PHP Code syntax highlighting references
  • +
  • [PHPBB3-14790] - Nested color/list BBCode is not parsed correctly
  • +
  • [PHPBB3-14938] - Inconsistent data results from ext_mgr->all_available() vs ext_mgr->is_available()
  • +
  • [PHPBB3-14967] - Cookie Notice
  • +
  • [PHPBB3-14971] - PHP 7.1 warning on pagination
  • +
  • [PHPBB3-14975] - incorrect RTL style appearance
  • +
  • [PHPBB3-14984] - wrong arrow direction in PM inbox
  • +
  • [PHPBB3-14985] - Plain text is stored as HTML and not decoded before usage
  • +
  • [PHPBB3-14989] - url bbcode does not support irc protocol
  • +
  • [PHPBB3-14992] - User notifications table allowing duplicate entries
  • +
  • [PHPBB3-14999] - Next PM icon pointing in wrong direction
  • +
  • [PHPBB3-15002] - Topic icons not showing in search results
  • +
  • [PHPBB3-15003] - When using mark all, disabled check boxes should not become checked
  • +
  • [PHPBB3-15006] - Permission inheritance with checkbox not working
  • +
  • [PHPBB3-15008] - Disable emoji when smilies are disabled
  • +
  • [PHPBB3-15010] - Crash on user profile custom field
  • +
  • [PHPBB3-15011] - Error not checked on metadata load failure
  • +
  • [PHPBB3-15012] - Invalid constructor in FTP file updater
  • +
  • [PHPBB3-15015] - phpbb 3.2.0 install error in the board-wide emails description...
  • +
  • [PHPBB3-15016] - Smilies that contain some characters cause an exception to be thrown
  • +
  • [PHPBB3-15023] - Undo and properly fix post row paging
  • +
  • [PHPBB3-15025] - Use SSL in version check for extension
  • +
  • [PHPBB3-15035] - Add phpinfo.php to 3.2.x install directory
  • +
  • [PHPBB3-15036] - Failure at board setup in functional tests with install_config present
  • +
  • [PHPBB3-15044] - Installation does not create search index
  • +
  • [PHPBB3-15047] - Error migrating from 3.0.12 to 3.2. MS SQL Server
  • +
  • [PHPBB3-15050] - Updater incorrectly adds files when new file already exists
  • +
  • [PHPBB3-15056] - Mark forums read does not update subforum icons
  • +
  • [PHPBB3-15058] - At new feature release phpBB's CSS files should carry information for cache busting
  • +
  • [PHPBB3-15062] - Update to Rhea version number the CSS files
  • +
  • [PHPBB3-15079] - MySql Error when saving draft with Emoji
  • +
  • [PHPBB3-15083] - emoji's not always size constrained
  • +
  • [PHPBB3-15084] - Wrong order of breadcrumbs in module management
  • +
  • [PHPBB3-15090] - Missing acp_send_statistics -> Upgrading to 3.2.0 fails for phpBBs < 3.0.6-rc1
  • +
  • [PHPBB3-15102] - Missing parameter calling version_check
  • +
  • [PHPBB3-15103] - JPEG dimensions undetectable when JFIF header is missing
  • +
  • [PHPBB3-15118] - HTML error/typo in jumpbox.html
  • +
  • [PHPBB3-15124] - Navbar icon titles don't get hidden in responsive view
  • +
  • [PHPBB3-15126] - Incorrect links with clever quotes
  • +
  • [PHPBB3-15135] - Undefined $user in metadata manager
  • +
  • [PHPBB3-15137] - Global Announcements shouldn't always be never ending
  • +
  • [PHPBB3-15150] - Yabber SSL/TLS certification
  • +
  • [PHPBB3-15152] - Update Font Awesome
  • +
  • [PHPBB3-15158] - Facebook OAuth login results in fatal error
  • +
  • [PHPBB3-15163] - Braces in smilies "emotion" are not treated as literals
  • +
  • [PHPBB3-15173] - Resizing the posting editor's text area lags
  • +
  • [PHPBB3-15180] - Container broken because of template.twig.environment changes
  • +
  • [PHPBB3-15186] - The force_delete_allowed flag does not affect actual posts deletion ability
  • +
  • [PHPBB3-15187] - ACP Template files not purged during Extension Enable
  • +
  • [PHPBB3-15198] - Fix phpBB and PHP version info displayed in the ACP
  • +
  • [PHPBB3-15202] - Should not be possible to disable available extensions
  • +
  • [PHPBB3-15212] - Code box has double horizontal scrollbars
  • +
  • [PHPBB3-15217] - Allow extension to overwrite user::format_date()
  • +
  • [PHPBB3-15221] - missing commas in language/en/install.php
  • +
  • [PHPBB3-15222] - Typo in generate_text_for_display_test.php
  • +
  • [PHPBB3-15243] - Check permissions before installing with SQLite
  • +
  • [PHPBB3-15246] - Memcache driver incorrectly handles Unix sockets
  • +
  • [PHPBB3-15248] - Event core.modify_posting_auth does not honor its parameters
  • +
  • [PHPBB3-15252] - Wrapping poll title and options text by extra <t> tags if edited a topic by user having no f_poll permission
  • +
+

Improvement

+
    +
  • [PHPBB3-13603] - New event upon index_body_online_block_after
  • +
  • [PHPBB3-14557] - Simplify updating overloaded events for extensions
  • +
  • [PHPBB3-14849] - Add ACP extension event
  • +
  • [PHPBB3-14928] - Users will not understand the phrase '"%s" is not a valid stability.'
  • +
  • [PHPBB3-14973] - BC break with the rename of db/tools.php
  • +
  • [PHPBB3-14974] - Cookie notice link not configurable but links to english website
  • +
  • [PHPBB3-15037] - Make imageset retina capable
  • +
  • [PHPBB3-15068] - Add ability to retrieve template vars from the template object
  • +
  • [PHPBB3-15097] - Board statistics page should show PHP version
  • +
  • [PHPBB3-15123] - Check if extension was enabled/disabled before enable or disable
  • +
  • [PHPBB3-15142] - Extension Version Check Should Support Branches
  • +
  • [PHPBB3-15157] - Fix lack of proper font support
  • +
  • [PHPBB3-15176] - Add setting for the maximum number of posts a user must have to have his activity shown in profile
  • +
  • [PHPBB3-15199] - Add core event to the function send() in the messenger
  • +
  • [PHPBB3-15200] - Allow extensions using custom templates for help/faq controllers
  • +
  • [PHPBB3-15205] - Add template events to forumlist_body.html
  • +
  • [PHPBB3-15219] - Add cron to update passwords hashes to bcrypt
  • +
  • [PHPBB3-15226] - Add index for latest topics query in feeds
  • +
  • [PHPBB3-15227] - Remove unused code in startup
  • +
  • [PHPBB3-15237] - Unguarded includes functions_user
  • +
  • [PHPBB3-15238] - Add console command to fix left/right IDs for the forums and modules
  • +
  • [PHPBB3-15241] - Add ACP template event acp_profile_contact_last
  • +
  • [PHPBB3-15250] - Add core event to MCP at the end of merge_posts
  • +
+

New Feature

+
    +
  • [PHPBB3-13730] - [PHP] - core.delete_post_end
  • +
  • [PHPBB3-14498] - Not possible to deactivate display of "who is online" and birthdays for guests
  • +
+

Task

+
    +
  • [PHPBB3-15040] - Update s9e\TextFormatter to 0.9.1
  • +
  • [PHPBB3-15045] - Fix missing incorrect constructor for user object in version_test
  • +
  • [PHPBB3-15086] - Replace quote.gif with fontawesome icon
  • +
  • [PHPBB3-15125] - Remove the function play_qt_file in forum_fn.js
  • +
  • [PHPBB3-15144] - Bug - MCP Multiple attachments icon display
  • +
+ +

Changes since 3.2.0-RC1

+

Bug

+
    +
  • [PHPBB3-14588] - RTL Search Bar
  • +
  • [PHPBB3-14612] - Double .panel class on confirmation page (ajax error?)
  • +
  • [PHPBB3-14628] - CLI installer doesn't support the translatable error messages
  • +
  • [PHPBB3-14633] - Creating a new topic leaves a white page
  • +
  • [PHPBB3-14636] - BC compatibility broken using request_var
  • +
  • [PHPBB3-14640] - Wrong link to documentation in language/en/install.php
  • +
  • [PHPBB3-14660] - Emails are being sent unparsed
  • +
  • [PHPBB3-14663] - Incorrect unicode chars handling in custom BBCode
  • +
  • [PHPBB3-14665] - Invalid syntax in report_id_auto_increment migration
  • +
  • [PHPBB3-14684] - Extension Sniff script should use NOTESTS
  • +
  • [PHPBB3-14690] - Email queue cron task never runs for phpBB 3.2
  • +
  • [PHPBB3-14692] - Duplicate subexpression in questionnaire.php
  • +
  • [PHPBB3-14700] - Updating from 3.1 to 3.2, just stops
  • +
  • [PHPBB3-14706] - nested BB-Code [list] - shows different behaviour between 3.1 and 3.2
  • +
  • [PHPBB3-14709] - Deleting posts from mcp_main causes missing post_id notice
  • +
  • [PHPBB3-14714] - Update composer dependencies to latest versions
  • +
  • [PHPBB3-14716] - Impossible to install with open basedir restrictions
  • +
  • [PHPBB3-14717] - Quote any scalar in yaml files
  • +
  • [PHPBB3-14739] - Remove old SQLite 2.8.х database driver
  • +
  • [PHPBB3-14740] - BBcodes with quotes dont get parsed correctly
  • +
  • [PHPBB3-14742] - Improvements to migrator
  • +
  • [PHPBB3-14746] - Don't depend on container in installer msg_handler
  • +
  • [PHPBB3-14748] - Modify tests to pass PHP 7.1 tests
  • +
  • [PHPBB3-14763] - Files services definition specifies form for local type
  • +
  • [PHPBB3-14764] - Incomplete update notification points to wrong update-link
  • +
  • [PHPBB3-14765] - Parameter vs requirement spelling mismatch in installer routing config
  • +
  • [PHPBB3-14774] - Content-Range only supported for resuming downloads
  • +
  • [PHPBB3-14782] - Quick Links > Your Posts gives mysql error
  • +
  • [PHPBB3-14788] - Update developer list to reflect team changes
  • +
  • [PHPBB3-14791] - Trying to get form from wrong button in search test base
  • +
  • [PHPBB3-14793] - "A non-numeric value encountered" PHP warning on PHP 7.1+
  • +
  • [PHPBB3-14794] - Fix redirect behavior in according to parse_url() behavior changes in PHP 7.1+
  • +
  • [PHPBB3-14797] - Remove PHP 7.1 builds from allowed failures
  • +
  • [PHPBB3-14799] - purge_notifications() leaves open transaction for bad notification types.
  • +
  • [PHPBB3-14813] - functions_compatibility missing in phpbbcli
  • +
  • [PHPBB3-14814] - Text reparser reparses already correctly [re] -parsed objects
  • +
  • [PHPBB3-14821] - Do not expect parsed HTML in kernel subscriber output
  • +
  • [PHPBB3-14846] - Swapped variables in bbcode, first one doesn't get parsed
  • +
  • [PHPBB3-14873] - Missing width and height variables for smilies
  • +
  • [PHPBB3-14875] - Cannot use HTML entity type database passwords during installation
  • +
  • [PHPBB3-14883] - Text Reparser is Reparsing Empty Data
  • +
  • [PHPBB3-14892] - Assets paths broken on Windows instances
  • +
  • [PHPBB3-14894] - Update: download of conflict files offers .tar file without file extension
  • +
  • [PHPBB3-14896] - Link after installation fails at redirecting to ACP
  • +
  • [PHPBB3-14897] - IOHandler in the installer declares member variable only in the constructor
  • +
  • [PHPBB3-14900] - Disabled extension breakage in ACP
  • +
+

Improvement

+ +

New Feature

+ +

Task

+
    +
  • [PHPBB3-10809] - Remove PHP MSSQL Support
  • +
  • [PHPBB3-13573] - Investigate ability to use set_config() and similar compatibility functions.
  • +
  • [PHPBB3-14671] - Deduplicate database schema definiton
  • +
  • [PHPBB3-14696] - Fix email template test for '0' username
  • +
  • [PHPBB3-14807] - Updates dependencies
  • +
+ +

Changes since 3.2.0-b2

+

Bug

+
    +
  • [PHPBB3-9435] - "magic numbers" in message_parser.php/bbcode.php
  • +
  • [PHPBB3-13616] - Pass lexer directly to TWIG environment
  • +
  • [PHPBB3-13972] - 3.1.5 - Waiting time conflict
  • +
  • [PHPBB3-14136] - IE compatibility meta is missing in overall_header.html
  • +
  • [PHPBB3-14198] - Container cache filename doesn't depend on the build options
  • +
  • [PHPBB3-14260] - Right parenthesis breaks (some?) magic URLs
  • +
  • [PHPBB3-14318] - Board Notifications Config Migration Not Working
  • +
  • [PHPBB3-14329] - Updater Cannot remove files
  • +
  • [PHPBB3-14381] - Text Reparser fails with empty sql fields
  • +
  • [PHPBB3-14393] - Update 3.2.0a1 to 3.2.0a2 --> Error: CANNOT_DELETE_FILES
  • +
  • [PHPBB3-14426] - viewtopic error posts bbcode pregmatch
  • +
  • [PHPBB3-14481] - phpBB does not obey HTTP_X_FORWARDED_PORT header
  • +
  • [PHPBB3-14497] - Update nginx sample config for new installer
  • +
  • [PHPBB3-14527] - Dataloss caused by link shortening
  • +
  • [PHPBB3-14528] - Structured data - breadcrumbs error
  • +
  • [PHPBB3-14530] - Signature parsing inconsistant
  • +
  • [PHPBB3-14532] - Database column default incorrectly escaped on MSSQL
  • +
  • [PHPBB3-14550] - function unique_id()
  • +
  • [PHPBB3-14555] - Inconsistent usage of the cache directory
  • +
  • [PHPBB3-14559] - Attachments' behaviour in quotes
  • +
  • [PHPBB3-14562] - Extension's permissions don't have language fallback
  • +
  • [PHPBB3-14564] - config cookie domain is empty
  • +
  • [PHPBB3-14569] - Add a method for console progress bar initialisation
  • +
  • [PHPBB3-14572] - Quote notifications deleted on edit
  • +
  • [PHPBB3-14576] - Functional Test Framework should include functions.php
  • +
  • [PHPBB3-14577] - Stop using sizeof() inside for() loop
  • +
  • [PHPBB3-14589] - Requirements test showing required text for "yellow/amber" (optional) requirements
  • +
  • [PHPBB3-14590] - Installer gets stuck at sending notification e-mail
  • +
  • [PHPBB3-14591] - Some installation data not being inserted when running under MS SQL Server
  • +
  • [PHPBB3-14607] - Missing Auto Increment in Report Table
  • +
  • [PHPBB3-14619] - docs/ folder need work to change 3.1.x to 3.2.x in readme, install, changelog etc
  • +
  • [PHPBB3-14648] - Users don't receive default notifications if another setting is set.
  • +
  • [PHPBB3-14649] - Missing variable within event
  • +
+

Improvement

+
    +
  • [PHPBB3-13502] - controller resolver should handle callable functions and objects
  • +
  • [PHPBB3-14540] - Adjust class recursive_dot_prefix_filter_iterator to increase performance
  • +
  • [PHPBB3-14561] - Add additional commands for user actions
  • +
  • [PHPBB3-14664] - Fix PHPDoc comment in cron manager
  • +
+

New Feature

+
    +
  • [PHPBB3-12684] - Add a command to add a user from the CLI
  • +
+

Task

+ + +

Changes since 3.2.0-b1

+

Bug

+
    +
  • [PHPBB3-14307] - Incorrect wording used in installer
  • +
  • [PHPBB3-14315] - Changing multiple forum permissions at once forces identical permissions on all groups
  • +
  • [PHPBB3-14416] - navlnks in header have incorrect tool tip styling
  • +
  • [PHPBB3-14431] - Remote avatar uploading does not support https
  • +
  • [PHPBB3-14440] - Paths can break for extensions with deep route patterns/paths
  • +
  • [PHPBB3-14460] - Use the selected language in AJAX pages as well in the installer
  • +
  • [PHPBB3-14461] - Text reparser migration might finish too late
  • +
  • [PHPBB3-14488] - Grab correct session ID in ui tests
  • +
  • [PHPBB3-14489] - Extension compiler pass cannot find class
  • +
  • [PHPBB3-14503] - Allow superglobals in installer cli
  • +
  • [PHPBB3-14510] - Prevent infinite loop in adding bots task
  • +
+

Improvement

+
    +
  • [PHPBB3-14312] - Allow installer updating only without updating files
  • +
  • [PHPBB3-14448] - Use guzzle for remote files uploading
  • +
  • [PHPBB3-14462] - Add further measures to prevent timeouts in the installer
  • +
  • [PHPBB3-14478] - Move facebook/webdriver to main composer.json
  • +
+

New Feature

+ +

Task

+ + +

Changes since 3.2.0-a2

+

Bug

+
    +
  • [PHPBB3-10628] - http:// prepended to "www." urls (but not reflected in preview)
  • +
  • [PHPBB3-11875] - mediumint(8) too small for post_id
  • +
  • [PHPBB3-12221] - URLs Containing javascript: are Garbled
  • +
  • [PHPBB3-14129] - Adding custom extensions autoloaders feature slow downs the board
  • +
  • [PHPBB3-14321] - Fatal error on download the merged conflicts archive during update from 3.1.6 to 3.2-a1
  • +
  • [PHPBB3-14323] - Long urls are no longer shortened
  • +
  • [PHPBB3-14368] - Post Editors Text Should Be Black, Not Blue
  • +
  • [PHPBB3-14371] - Small fix for the aligment quick links
  • +
  • [PHPBB3-14373] - Do not use strpos() on arrays in iohandler_base::add_error_message()
  • +
  • [PHPBB3-14378] - Maintain consistent template var paths in 3.2 installer
  • +
  • [PHPBB3-14380] - Maintain consistent template var paths in 3.2 installer
  • +
  • [PHPBB3-14402] - Tidy plupload cron should not rely on user id/ip being available
  • +
  • [PHPBB3-14403] - phpbb\log should still work even when no user data is given
  • +
  • [PHPBB3-14405] - Text processor parses invalid use of url bbcode
  • +
  • [PHPBB3-14419] - Update composer dependencies and fix outdated composer lock file
  • +
  • [PHPBB3-14420] - Search Results pagination not up to date / broken
  • +
  • [PHPBB3-14428] - Use other links for files remote test
  • +
  • [PHPBB3-14431] - Remote avatar uploading does not support https
  • +
  • [PHPBB3-14432] - The lang() function needs to handle key array
  • +
  • [PHPBB3-14434] - Schema generator fails if extensions have non-migrations in migrations dir
  • +
  • [PHPBB3-14436] - Default data type migration misses oauth_states dependency
  • +
  • [PHPBB3-14442] - Use Goutte ~2.0 after allowing PHP >= 5.4
  • +
  • [PHPBB3-14444] - Fatal error in functions_messenger.php
  • +
  • [PHPBB3-14446] - Add predefined placeholder variables to twig
  • +
  • [PHPBB3-14452] - Undefined $progressFillerText in installer.js
  • +
  • [PHPBB3-14453] - Missing dependency in the text_reparser migration
  • +
+

Improvement

+
    +
  • [PHPBB3-13454] - Remove unused variables, globals, parameters
  • +
  • [PHPBB3-13733] - Allow non-migration files inside migrations folder
  • +
  • [PHPBB3-14177] - Catch exceptions and display a nice error page
  • +
  • [PHPBB3-14253] - Show group requests pending aproval at the ACP groups summary
  • +
  • [PHPBB3-14377] - Allow extensions to register custom compiler pass
  • +
  • [PHPBB3-14418] - Add missing language string PM_TOOLS
  • +
  • [PHPBB3-14445] - Force page refresh before the installer generates the schema
  • +
+

Task

+ + +

Changes since 3.2.0-a1

+

Bug

+
    +
  • [PHPBB3-9791] - invalid [] - chars on search URI
  • +
  • [PHPBB3-13451] - A very long string inside message crashes PHP
  • +
  • [PHPBB3-14273] - Unnecessary core.root_path dependency in files.upload service
  • +
  • [PHPBB3-14293] - Responsive .postbody width not expanding
  • +
  • [PHPBB3-14295] - Icon on left side of "Post awaiting approval" inside messages is missing
  • +
  • [PHPBB3-14311] - Installer error messages not being reported back to client properly
  • +
  • [PHPBB3-14317] - Run lang_migrate_help_lang script on master
  • +
  • [PHPBB3-14320] - Language selector is broken in the installer
  • +
  • [PHPBB3-14325] - Renamed Event Variables Backwards Incompatible
  • +
  • [PHPBB3-14326] - Diffed files are not decoded at update
  • +
  • [PHPBB3-14339] - State support for PHP 7.0 in docs
  • +
  • [PHPBB3-14344] - Improve formatting of errors during install
  • +
  • [PHPBB3-14345] - Check if message description exists in install cli iohandler
  • +
  • [PHPBB3-14349] - Improve error messages and remove output of suppressed messages
  • +
  • [PHPBB3-14350] - Remove duplicate semi-colon from functions_user
  • +
  • [PHPBB3-14358] - Fatal error when running create_schema_files.php (missing composer autoloader)
  • +
  • [PHPBB3-14359] - Wrong service definition for console.command.reparser.reparse
  • +
+

Improvement

+
    +
  • [PHPBB3-12649] - Change sort & display options in footers to dropdown menu
  • +
  • [PHPBB3-14257] - Reparse after update
  • +
  • [PHPBB3-14269] - Use http_exceptions instead of die() in installer controllers
  • +
  • [PHPBB3-14310] - Progress bar in new installer not very clear
  • +
  • [PHPBB3-14340] - Revert the workaround fix for segmentation fault error on phpBB 3.2 PHP 7 tests
  • +
+

Task

+
    +
  • [PHPBB3-14247] - Usage of @ at begining of an unquoted string in a yaml file is deprecated
  • +
  • [PHPBB3-14341] - Update to Symfony 2.8
  • +
  • [PHPBB3-14348] - Add an index.html to the install folder
  • +
+ +

Changes since 3.1.x

+

Bug

+
    +
  • [PHPBB3-6466] - Permission Role Tooltips do not display in Safari
  • +
  • [PHPBB3-7187] - Quote smilies error
  • +
  • [PHPBB3-7275] - Custom bbodes trim('${1}')
  • +
  • [PHPBB3-8064] - forum author sort not using username_clean
  • +
  • [PHPBB3-8419] - custom tag eats up space character
  • +
  • [PHPBB3-8420] - emoticon removes space before itself when using preview
  • +
  • [PHPBB3-8613] - BBCode_uid- and censor-bug
  • +
  • [PHPBB3-9073] - Word censoring in URLs
  • +
  • [PHPBB3-9109] - Maximum allowed recepients restriction precedence
  • +
  • [PHPBB3-9377] - Custom BB Code Nesting
  • +
  • [PHPBB3-10002] - (incomplete) BBCode usage of [quote] - and [list] - forces closing [/list] - and [/quote] -s, ultimately breaking HTML/Design
  • +
  • [PHPBB3-10388] - Template engine should use json_encode() to properly escape JS properties
  • +
  • [PHPBB3-10572] - Unguarded includes in acp/
  • +
  • [PHPBB3-10989] - Bug in BBCode
  • +
  • [PHPBB3-11444] - Unnecessary notify column in phpbb_user_notifications table
  • +
  • [PHPBB3-11967] - Notification settings are not respected
  • +
  • [PHPBB3-12143] - Untranslated group name from index if not special group
  • +
  • [PHPBB3-12195] - Double-slash URLs not supported
  • +
  • [PHPBB3-12387] - mysqli_free_result is called with false value
  • +
  • [PHPBB3-12554] - Quotes do not display correctly as list elements
  • +
  • [PHPBB3-12698] - Replace all instances of magic numbers with constants in javascript
  • +
  • [PHPBB3-12957] - The template engine is not constructed properly in install/index
  • +
  • [PHPBB3-12974] - Update nightly version of develop to 3.2.0-a1-dev
  • +
  • [PHPBB3-13101] - Remove WLM contact from profile
  • +
  • [PHPBB3-13238] - \phpbb\db\migration\data\v310\mysql_fulltext_drop tries to drop non existent indexes
  • +
  • [PHPBB3-13359] - Develop tests fail due to wrong template set up in tests
  • +
  • [PHPBB3-13362] - The whole cache dir (excluding the .htaccess and index.html files) should be ignored by git
  • +
  • [PHPBB3-13371] - Lang vars not loaded during install process
  • +
  • [PHPBB3-13372] - Environment Bug - Routing broken for extensions
  • +
  • [PHPBB3-13377] - Function decode_message() incorrectly decodes www type URLs
  • +
  • [PHPBB3-13425] - Smiley code at start of text being quoted doesn't show smiley image in quote
  • +
  • [PHPBB3-13513] - Mixed routing file paths in the router
  • +
  • [PHPBB3-13555] - Poll options preview rendered incorrectly by <br /> collision
  • +
  • [PHPBB3-13619] - Remove unneeded folders/files from vendor folder in release package
  • +
  • [PHPBB3-13638] - INCLUDECSS and INCLUDEJS Broken in 3.2
  • +
  • [PHPBB3-13670] - Fix fatal function name must be a string in functional tests
  • +
  • [PHPBB3-13680] - Notification for a quote within a quote
  • +
  • [PHPBB3-13718] - Spambot countermeasures/CAPTCHA admin panel won't load
  • +
  • [PHPBB3-13749] - Add missing slash to base uri in helper route tests
  • +
  • [PHPBB3-13766] - Missing style_parent_id in textformater's data_access::get_styles()
  • +
  • [PHPBB3-13769] - bin/phpbbcli.php ignores PHPBB_ENVIRONMENT constant from config.php
  • +
  • [PHPBB3-13772] - Error in @param variable type for phpbb\passwords\manager
  • +
  • [PHPBB3-13782] - ACM null caching driver class is named with the reserved word 'null'
  • +
  • [PHPBB3-13792] - Travis fails installing hhvm-nigthly
  • +
  • [PHPBB3-13814] - phpbb_is_writable() method of the new filesystem class truncates files
  • +
  • [PHPBB3-13825] - create_thumbnail() incorrectly calls phpbb\filesystem::phpbb_chmod
  • +
  • [PHPBB3-13828] - Rename null driver to dummy for PHP7 compatibility in tests
  • +
  • [PHPBB3-13829] - Router requires cache directory to be writable
  • +
  • [PHPBB3-13839] - Failing test when the phpBB root directory isn't named phpbb
  • +
  • [PHPBB3-13849] - Development environment broken
  • +
  • [PHPBB3-13860] - Array to string conversion in parser service
  • +
  • [PHPBB3-13871] - Call to a member function realpath() on a non-object in functions.php on line 3474
  • +
  • [PHPBB3-13875] - Lint test should ignore cache, ext, and store folder
  • +
  • [PHPBB3-13890] - Failling tests in master
  • +
  • [PHPBB3-13896] - Coding style issue in master
  • +
  • [PHPBB3-13897] - Non-existent environment causes fatal error
  • +
  • [PHPBB3-13906] - BBCodes in signatures are not correctly parsed in post preview
  • +
  • [PHPBB3-13990] - Reparse markup inside of forum rules/description
  • +
  • [PHPBB3-13993] - Signature Editing Broken
  • +
  • [PHPBB3-14008] - Do not add a user_id value to quotes from guests
  • +
  • [PHPBB3-14033] - Correct docblock errors
  • +
  • [PHPBB3-14034] - Fix reparser names that contain "text_reparser" in the middle
  • +
  • [PHPBB3-14036] - Replace path_helper with a mock
  • +
  • [PHPBB3-14052] - New installer - The commands are not translated
  • +
  • [PHPBB3-14074] - Not possible to clear notification (mark as read)
  • +
  • [PHPBB3-14076] - Notifications settings are not correctly handled when a non default setting is set
  • +
  • [PHPBB3-14078] - Notification related sql general error on submission of editing post
  • +
  • [PHPBB3-14079] - Cannot mark notification as read from the dropdown
  • +
  • [PHPBB3-14094] - Current master branch is getting segmentation fault error on PHP 7 tests
  • +
  • [PHPBB3-14128] - Image posting overflow regression
  • +
  • [PHPBB3-14137] - Fix Notification Menu Settings spacing issue caused by new jump-box improvements
  • +
  • [PHPBB3-14138] - Use span tags instead of abbr tags in the footer
  • +
  • [PHPBB3-14165] - Fix layout of ucp/mcp after 14139
  • +
  • [PHPBB3-14178] - Installer database helper tests fail if sqlite3 is not present
  • +
  • [PHPBB3-14180] - Use unix line ending in files classes tests
  • +
  • [PHPBB3-14182] - Move the v310\notifications_board migration to v320\notifications_board
  • +
  • [PHPBB3-14183] - Remove deprecated max-device-width
  • +
  • [PHPBB3-14193] - Custom BBCode Buttons Broken
  • +
  • [PHPBB3-14194] - Responsive Quick Links Menu Broken
  • +
  • [PHPBB3-14195] - Plupload Attachments Not Quite Working
  • +
  • [PHPBB3-14199] - We need to hide icons from screen readers
  • +
  • [PHPBB3-14202] - Add missing sr-only classes to icons
  • +
  • [PHPBB3-14216] - Do not run thumbnail test if gd is not enabled
  • +
  • [PHPBB3-14221] - Fix viewforum header issue
  • +
  • [PHPBB3-14222] - ACP users page tries to cast deactivated_super_global to int
  • +
  • [PHPBB3-14225] - Inject the resolver in routing loaders when using them
  • +
  • [PHPBB3-14230] - Unread posts' icons don't have different color in viewtopic
  • +
  • [PHPBB3-14234] - Do not use references in trigger_event()
  • +
  • [PHPBB3-14235] - Font Awesome not available with simple_header.html
  • +
  • [PHPBB3-14240] - Always include all config files in the update package
  • +
  • [PHPBB3-14274] - New installer has weak inclusion of user functions
  • +
  • [PHPBB3-14277] - Don't use user_id in migrations as it is not defined
  • +
  • [PHPBB3-14278] - Don't use user_id in installer if not available
  • +
+

Improvement

+
    +
  • [PHPBB3-8672] - No file size limit in getimagesize() and remote upload
  • +
  • [PHPBB3-8708] - Splitting global announcements from f_announce
  • +
  • [PHPBB3-9485] - Please include link to specific post in moderator's activity logs.
  • +
  • [PHPBB3-10268] - extend function make_clickable() to also recognize semicolons as leading URL borders
  • +
  • [PHPBB3-10620] - Quote tag improvement
  • +
  • [PHPBB3-10922] - Allow parameters for [email] - BBCode content instead of addresses only
  • +
  • [PHPBB3-11649] - Move construction of twig environment to DIC
  • +
  • [PHPBB3-11742] - BBCode 'code' doesn't support tabs
  • +
  • [PHPBB3-12466] - Move classes from acp_database.php to their own files
  • +
  • [PHPBB3-12487] - Update PHP code
  • +
  • [PHPBB3-12608] - Improve notifications drop-down menu styling in header
  • +
  • [PHPBB3-12654] - Improve header search-box styling
  • +
  • [PHPBB3-12681] - Cache the compiled routes and dump the url_generator
  • +
  • [PHPBB3-12719] - Convert to Normalize & SUITCSS - base for reset
  • +
  • [PHPBB3-12745] - Allow Emoji characters in posts
  • +
  • [PHPBB3-12769] - Add and use Font-Awesome to handle proSilver icons
  • +
  • [PHPBB3-12821] - Use CSS instead of images for gradients
  • +
  • [PHPBB3-12958] - Remove subsilver2
  • +
  • [PHPBB3-13063] - Move functions_url_matcher to a proper class
  • +
  • [PHPBB3-13132] - Twig: move the loops content from loops. to the root context
  • +
  • [PHPBB3-13137] - Remove schema.json from repository
  • +
  • [PHPBB3-13178] - Allow posting Emoji characters if the database supports it
  • +
  • [PHPBB3-13206] - DEBUG mode should automatically recompile stale style components
  • +
  • [PHPBB3-13266] - Enabling twig dump function if DEBUG is defined
  • +
  • [PHPBB3-13388] - Integrate routing and di parameters resolution
  • +
  • [PHPBB3-13450] - Type-hint return value of $phpbb_container->get()
  • +
  • [PHPBB3-13455] - Change request_var() calls with $request->variable()
  • +
  • [PHPBB3-13468] - Change add_log() calls with $phpbb_log->add()
  • +
  • [PHPBB3-13494] - Replace set_config() calls with $config->set()
  • +
  • [PHPBB3-13496] - Replace set_config_count() calls with $config->increment()
  • +
  • [PHPBB3-13497] - Change get_tables() calls with $db_tools->sql_list_tables()
  • +
  • [PHPBB3-13498] - Change get_user_avatar() calls with phpbb_get_avatar()
  • +
  • [PHPBB3-13499] - Move get_remote_file() to functions_compatibility.php
  • +
  • [PHPBB3-13595] - Remove unused instances of the bbcode class
  • +
  • [PHPBB3-13614] - Remove phpbb_pcre_utf8_support() and assume Unicode is supported by PCRE
  • +
  • [PHPBB3-13652] - Extend SQL query builder functionality
  • +
  • [PHPBB3-13697] - Rewriting/moving file system functions to filesystem class
  • +
  • [PHPBB3-13740] - Refactoring installer
  • +
  • [PHPBB3-13762] - Moving translation loading and language related functions into a service
  • +
  • [PHPBB3-13770] - Make DI Container builder constructor dependency free
  • +
  • [PHPBB3-13789] - Implement Googles noCAPTCHA reCAPTCHA
  • +
  • [PHPBB3-13801] - Decouple the user object from the text_formatter.s9e.parser service
  • +
  • [PHPBB3-13803] - Implement a generic and scalable way to reparse rich text
  • +
  • [PHPBB3-13805] - Make generate_text_for_storage() match the signature and feature set of message_parser::parse()
  • +
  • [PHPBB3-13844] - Replace help_ language magic with a help manager and normal language files
  • +
  • [PHPBB3-13847] - Move quote generation to text_formatter.utils
  • +
  • [PHPBB3-13891] - Add CLI commands for reparsing text
  • +
  • [PHPBB3-13901] - Give quotes more whitespace in the posting form for readability
  • +
  • [PHPBB3-13902] - Increase the CSS margin around blockquote
  • +
  • [PHPBB3-13904] - Refactor attachment upload functions into service
  • +
  • [PHPBB3-13921] - Try harder to fix block elements BBCodes used inside of inline elements BBCodes
  • +
  • [PHPBB3-13946] - Increase the CSS margin around [code] - and [list] -
  • +
  • [PHPBB3-13970] - Save the value of the optional parameter in [code] - BBCode
  • +
  • [PHPBB3-13986] - Add --resume option to reparser CLI
  • +
  • [PHPBB3-13987] - Add --dry-run option to reparser CLI
  • +
  • [PHPBB3-14000] - Make it possible to use emojis in posts
  • +
  • [PHPBB3-14097] - Catch and show all exceptions
  • +
  • [PHPBB3-14107] - dropdown rendering on rtl
  • +
  • [PHPBB3-14124] - Automatically translate exception in CLI
  • +
  • [PHPBB3-14139] - stop styling IDs
  • +
  • [PHPBB3-14157] - Allow administrators to set an alt attribute for topic icons
  • +
  • [PHPBB3-14162] - Add CLI commands to manage migrations
  • +
  • [PHPBB3-14168] - Refactor attachment management functions into classes
  • +
  • [PHPBB3-14174] - issues in language\en\install_new.php
  • +
  • [PHPBB3-14175] - Refactor responsive implementation for easier manipulation
  • +
  • [PHPBB3-14206] - Fix jumpbox incosistencies
  • +
  • [PHPBB3-14220] - Adding routing file locator and route loader services
  • +
  • [PHPBB3-14231] - Fix double home icon in breadcrumbs
  • +
  • [PHPBB3-14237] - Use language class in the notifications component
  • +
  • [PHPBB3-14264] - Don't use constants in textreparser plugins
  • +
+

New Feature

+
    +
  • [PHPBB3-10165] - Send test email feature on email settings ACP page
  • +
  • [PHPBB3-11768] - Integrate s9e\TextFormatter
  • +
  • [PHPBB3-12516] - Always preview signature in UCP/ACP module
  • +
  • [PHPBB3-12620] - Allow the user to define multiples environments
  • +
  • [PHPBB3-12692] - Add a console command to manage the thumbnail
  • +
  • [PHPBB3-13329] - Rely on Intl and mbstring, use patchwork/utf8 as fallback
  • +
  • [PHPBB3-13641] - problem with custom BBCode
  • +
  • [PHPBB3-13961] - Add orderable service collections
  • +
  • [PHPBB3-14125] - Add --env option to all CLI commands
  • +
  • [PHPBB3-14158] - Add a lang_array function to the language service to avoid using call_user_func
  • +
+

Sub-task

+ +

Task

+
    +
  • [PHPBB3-9457] - [Accessibility] - Add WAI-ARIA landmarks to the Prosilver template files
  • +
  • [PHPBB3-11528] - Use mink for acceptance tests involving javascript execution
  • +
  • [PHPBB3-12384] - Run Travis CI HHVM tests against MySQLi instead of MySQL
  • +
  • [PHPBB3-12505] - Remove outdated media handling in attachment.html
  • +
  • [PHPBB3-12564] - Remove obsolete version definitions from module info files
  • +
  • [PHPBB3-12916] - Add separate nightly builds for 3.1 and 3.2
  • +
  • [PHPBB3-13130] - Update dependencies to Symfony 2.5
  • +
  • [PHPBB3-13139] - Update Twig to 1.18.0
  • +
  • [PHPBB3-13363] - Replace phpbb\php\ini with composer package bantu/ini-get-wrapper
  • +
  • [PHPBB3-13407] - Update Symfony Components to 2.7.x
  • +
  • [PHPBB3-13572] - Upgrade composer to 1.0.0-alpha9
  • +
  • [PHPBB3-13634] - Update README to show new branch names
  • +
  • [PHPBB3-13725] - Coding guidelines: static public
  • +
  • [PHPBB3-13767] - Remove .git from vendor/s9e/text-formatter
  • +
  • [PHPBB3-13768] - Update to Symfony 2.8@dev
  • +
  • [PHPBB3-13774] - Update s9e\TextFormatter
  • +
  • [PHPBB3-13777] - Move acp/modules module handling code into a service
  • +
  • [PHPBB3-13793] - Remove message translation from exceptions
  • +
  • [PHPBB3-13800] - Make extension manager an optional dependency for the router
  • +
  • [PHPBB3-13804] - Make template's user dependency optional
  • +
  • [PHPBB3-13832] - Replace use of deprecated e modifier of preg_replace in bbcode functions
  • +
  • [PHPBB3-13985] - Update s9e\TextFormatter
  • +
  • [PHPBB3-14015] - Update Symfony to the latest 2.8@dev
  • +
  • [PHPBB3-14053] - Remove @covers annotations from installer config test
  • +
  • [PHPBB3-14056] - Keep install schema resources in the install folder
  • +
  • [PHPBB3-14096] - Update Symfony to the latest 2.8@dev
  • +
  • [PHPBB3-14140] - Update Symfony to benefit from improvement to the console component
  • +
  • [PHPBB3-14150] - Update fast-image-size to newest release
  • +
  • [PHPBB3-14205] - Bump PHP requirement to 5.4
  • +
  • [PHPBB3-14238] - Update Symfony to the latest 2.8@dev
  • +
  • [PHPBB3-14243] - Exclude every .git directory from the pakages
  • +
  • [PHPBB3-14256] - Remove PHP7 from the allowed failures
  • +
  • [PHPBB3-14265] - Make all tables available in the container
  • +
+ +

Changes since 3.1.11

+ +

Security Issue

+
    +
  • [SECURITY-211] - URLs with javascript scheme should not be made clickable
  • +
+

Bug

+
    +
  • [PHPBB3-9533] - phpbb_own_realpath() doesn't always replicate realpath() behaviour
  • +
  • [PHPBB3-12835] - Jump-box dropdown menu doesn't expand with according to line length in IE8
  • +
  • [PHPBB3-13360] - rename_too_long_indexes migration never deleted the old unique index
  • +
  • [PHPBB3-13464] - problem with drop down options and Arabic letters in chrome
  • +
  • [PHPBB3-13574] - Last post not showing in "Active topics" when Prosilver goes responsive
  • +
  • [PHPBB3-15174] - Unable to purge cache (ext & acp)
  • +
  • [PHPBB3-15285] - Travis tests are failing due to trusty changes
  • +
  • [PHPBB3-15303] - Typo in memcached driver
  • +
  • [PHPBB3-15347] - Password updater in cron generates invalid postgres SQL
  • +
  • [PHPBB3-15367] - Sphinx search backend doesn't escape special characters
  • +
+

Improvement

+
    +
  • [PHPBB3-10122] - [list=] - should support "none", along with CSS2 types
  • +
  • [PHPBB3-11063] - Change version check to SSL
  • +
  • [PHPBB3-14820] - Style Version Missing
  • +
  • [PHPBB3-14919] - Inconsistent use of globals vs class elements in acp_extensions
  • +
  • [PHPBB3-14927] - event core.user_add_modify_data
  • +
  • [PHPBB3-14944] - Add possibility to search for template loop indexes by key
  • +
  • [PHPBB3-14995] - Add ACP template events acp_ext_list_*_name_after
  • +
+

New Feature

+ +

Sub-task

+
    +
  • [PHPBB3-11182] - Ensure that template files use L_COLON instead of colons.
  • +
  • [PHPBB3-11676] - generate_text_for_storage on includes/acp/acp_users.php
  • +
+

Task

+
    +
  • [PHPBB3-10758] - Improve Functional Test Code Coverage
  • +
  • [PHPBB3-10791] - Add a section for extensions to readme.html
  • +
  • [PHPBB3-10792] - Add a section for 3.0 to 3.1 upgrades to install.html
  • +
  • [PHPBB3-13874] - Add master to sami API docs
  • +
+ +

Changes since 3.1.10

+ +

Bug

+
    +
  • [PHPBB3-7336] - Words in new topic title aren't found by search after topic is split
  • +
  • [PHPBB3-8116] - Server timeout or browsercrash after viewing postdetails
  • +
  • [PHPBB3-8301] - admin log generate slow queries
  • +
  • [PHPBB3-9590] - Unable to update permissions for more than 6 forums at a time
  • +
  • [PHPBB3-11076] - Update notification in ACP for minimum PHP version missing essential information
  • +
  • [PHPBB3-11483] - Forced Activation needs looking at.
  • +
  • [PHPBB3-11611] - setup_github_network.php no longer creates a repository
  • +
  • [PHPBB3-13247] - Online indicator in post profile hides behind certain avatars
  • +
  • [PHPBB3-13250] - File cache does not write entries starting with _ and containing a slash
  • +
  • [PHPBB3-13429] - Changes tag in docblock of events should be unified
  • +
  • [PHPBB3-13558] - Error - stream_socket_enable_crypto()
  • +
  • [PHPBB3-13757] - Negative PM count
  • +
  • [PHPBB3-14468] - [php] - 'core.viewforum_modify_topics_data' add parameter forum_id
  • +
  • [PHPBB3-14549] - Correctly redirect back after topic merge in MCP
  • +
  • [PHPBB3-14770] - Plupload: WRONG_FILESIZE is used wrong
  • +
  • [PHPBB3-14795] - Topic merge bug
  • +
  • [PHPBB3-14801] - Search highlight option doesn't always highlight unicode strings
  • +
  • [PHPBB3-14802] - Empty/blank lines should not be additional poll options
  • +
  • [PHPBB3-14806] - Authentication for e-mail is not working
  • +
  • [PHPBB3-14819] - Soft deleted posts visible in topic review
  • +
  • [PHPBB3-14821] - Do not expect parsed HTML in kernel subscriber output
  • +
  • [PHPBB3-14830] - FORM_INVALID error on ACP search and CPF settings
  • +
  • [PHPBB3-14831] - Extension migration file fails
  • +
  • [PHPBB3-14838] - feeds.attachments_base - server 500 error for large attachment tables
  • +
  • [PHPBB3-14844] - BBcodes B and I return <strong> and <em> tags instead of CSS under inherited styles
  • +
  • [PHPBB3-14859] - PM report notifications only sent out to full Global Moderators
  • +
  • [PHPBB3-14860] - Broken link on subscriptions page on mobile devices
  • +
  • [PHPBB3-14863] - "Array" in message title when permanently deleting posts
  • +
  • [PHPBB3-14864] - ACP datefromat text input still has 30 max length while dateformat field had been expanded to 64
  • +
  • [PHPBB3-14876] - Multibyte message is not displayed properly on exception
  • +
  • [PHPBB3-14877] - CSS error in ".codebox code" definition
  • +
  • [PHPBB3-14881] - Problems using EVENT (overall_footer_content_after)
  • +
  • [PHPBB3-14888] - Missing check for disabled profile field types
  • +
  • [PHPBB3-14889] - Missing method declaration in profile fields type interface
  • +
  • [PHPBB3-14890] - Wrong validation of input field in profile field type string
  • +
  • [PHPBB3-14906] - Duplicated sig key in user_cache_data array
  • +
  • [PHPBB3-14923] - SQL PostgreSQL blocking errors during DB update installation
  • +
  • [PHPBB3-14938] - Inconsistent data results from ext_mgr->all_available() vs ext_mgr->is_available()
  • +
  • [PHPBB3-14941] - MySQL Fulltext search index creating still fails on InnoDB
  • +
  • [PHPBB3-14943] - Template loop access gives PHP error
  • +
  • [PHPBB3-14953] - Incorrect "order by" definition in ucp_pm_viewfolder
  • +
  • [PHPBB3-14968] - Version check marks 3.1.10 boards as outdated
  • +
  • [PHPBB3-14997] - Bad Position for topiclist_row_topic_title_after
  • +
  • [PHPBB3-14998] - ACP Update link is incorrect!
  • +
  • [PHPBB3-15003] - When using mark all, disabled check boxes should not become checked
  • +
  • [PHPBB3-15006] - Permission inheritance with checkbox not working
  • +
  • [PHPBB3-15011] - Error not checked on metadata load failure
  • +
  • [PHPBB3-15108] - Duplicate code in request->overwrite
  • +
  • [PHPBB3-15143] - version check on branch is broken
  • +
  • [PHPBB3-15146] - Date profile field validation incorrect
  • +
  • [PHPBB3-15150] - Yabber SSL/TLS certification
  • +
  • [PHPBB3-15186] - The force_delete_allowed flag does not affect actual posts deletion ability
  • +
  • [PHPBB3-15187] - ACP Template files not purged during Extension Enable
  • +
  • [PHPBB3-15246] - Memcache driver incorrectly handles Unix sockets
  • +
  • [PHPBB3-15248] - Event core.modify_posting_auth does not honor its parameters
  • +
+

Improvement

+
    +
  • [PHPBB3-9211] - List subforums-links separately in parent-forums' legend
  • +
  • [PHPBB3-12749] - core.submit_post_end add subject to the event data
  • +
  • [PHPBB3-13457] - New Hooks for ucp_main
  • +
  • [PHPBB3-13459] - New Template-Event in overall_header.html
  • +
  • [PHPBB3-13479] - Add hook for modifying highlighting on viewtopic
  • +
  • [PHPBB3-13601] - New event upon acl_clear_prefetch
  • +
  • [PHPBB3-13603] - New event upon index_body_online_block_after
  • +
  • [PHPBB3-13605] - New event upon ucp_pm_compose_predefined_message
  • +
  • [PHPBB3-13608] - New event upon ucp_restore_permissions
  • +
  • [PHPBB3-13609] - New event upon ucp_switch_permissions
  • +
  • [PHPBB3-13845] - Add event when user changes or delete avatar
  • +
  • [PHPBB3-14119] - [PHP] - (User) unban event request
  • +
  • [PHPBB3-14239] - [PHP] - Add event ucp_remind_modify_select_sql
  • +
  • [PHPBB3-14331] - Add rank calculation or result event access
  • +
  • [PHPBB3-14520] - [Template] - ucp_pm_viewmessage_message_body_after
  • +
  • [PHPBB3-14522] - [Template] - ucp_register_buttons_before
  • +
  • [PHPBB3-14524] - [PHP] - core.ucp_register_requests_after
  • +
  • [PHPBB3-14733] - Support increasing hashing cost factor
  • +
  • [PHPBB3-14750] - Fileupload form should not set invalid attributes for file input
  • +
  • [PHPBB3-14758] - ACP-Parameter "Maximum thumbnail width in pixel" should be "Maximum thumbnail width/heigth in pixel:"
  • +
  • [PHPBB3-14759] - Event core.mcp_main_modify_shadow_sql
  • +
  • [PHPBB3-14760] - Event core.mcp_main_modify_fork_sql
  • +
  • [PHPBB3-14786] - Add mcp_forum_actions_before/after events
  • +
  • [PHPBB3-14804] - Add core event to MCP after merging topics
  • +
  • [PHPBB3-14805] - Allow building package for previous versions on PHP 7
  • +
  • [PHPBB3-14808] - Add template event overall_header_searchbox_after
  • +
  • [PHPBB3-14817] - Add core event on includes/functions_download.php
  • +
  • [PHPBB3-14825] - Add OAuth events
  • +
  • [PHPBB3-14827] - Possibility to add multiple form keys
  • +
  • [PHPBB3-14842] - Avatar size 0 - unlimited
  • +
  • [PHPBB3-14847] - Add php event to add options in ACP Attachments
  • +
  • [PHPBB3-14848] - Add ACP template events after extensions list titles
  • +
  • [PHPBB3-14849] - Add ACP extension event
  • +
  • [PHPBB3-14850] - Add core events for smilies
  • +
  • [PHPBB3-14852] - Add core event to the function build_header()
  • +
  • [PHPBB3-14853] - Add core event to allow modifying PM attachments download auth
  • +
  • [PHPBB3-14855] - Update notifications and PM alert bubbles
  • +
  • [PHPBB3-14870] - Add php events to modify list of PMs
  • +
  • [PHPBB3-14872] - Remove count versus sizeof restriction in coding guidelines
  • +
  • [PHPBB3-14874] - Error on sending a .pak smiley
  • +
  • [PHPBB3-14882] - Add core event to MCP after move posts sync
  • +
  • [PHPBB3-14887] - ACP profile step 1 lang specific event
  • +
  • [PHPBB3-14918] - Provide quick access to extension version metadata
  • +
  • [PHPBB3-14940] - Add ACP template event acp_ext_details_end
  • +
  • [PHPBB3-14957] - Do not cache database config
  • +
  • [PHPBB3-14958] - Twig extension function lang() performs redundant template data copying
  • +
  • [PHPBB3-15020] - Add Events for mcp_topic_postrow_post_subject
  • +
  • [PHPBB3-15059] - Do not wrap content in code box
  • +
  • [PHPBB3-15081] - Add ACP template event acp_ext_details_notice
  • +
  • [PHPBB3-15107] - Add additional vars to event
  • +
  • [PHPBB3-15131] - Add variable to the 'core.mcp_main_modify_fork_sql' event
  • +
  • [PHPBB3-15142] - Extension Version Check Should Support Branches
  • +
  • [PHPBB3-15151] - ACP Cookie settings should contain explanatory text for all fields
  • +
  • [PHPBB3-15199] - Add core event to the function send() in the messenger
  • +
  • [PHPBB3-15200] - Allow extensions using custom templates for help/faq controllers
  • +
  • [PHPBB3-15205] - Add template events to forumlist_body.html
  • +
  • [PHPBB3-15219] - Add cron to update passwords hashes to bcrypt
  • +
  • [PHPBB3-15226] - Add index for latest topics query in feeds
  • +
  • [PHPBB3-15237] - Unguarded includes functions_user
  • +
  • [PHPBB3-15238] - Add console command to fix left/right IDs for the forums and modules
  • +
  • [PHPBB3-15241] - Add ACP template event acp_profile_contact_last
  • +
  • [PHPBB3-15250] - Add core event to MCP at the end of merge_posts
  • +
+

New Feature

+
    +
  • [PHPBB3-12545] - new pre-posting event
  • +
  • [PHPBB3-13730] - [PHP] - core.delete_post_end
  • +
  • [PHPBB3-14390] - [prosilver] - ucp_main_front_user_details_after
  • +
  • [PHPBB3-14498] - Not possible to deactivate display of "who is online" and birthdays for guests
  • +
  • [PHPBB3-14662] - [Template] - memberlist_team_username_prepend & append
  • +
  • [PHPBB3-14868] - [Template] - mcp_forum_modify_select_after
  • +
  • [PHPBB3-14996] - [event] - Add Event search_results_topictitle_after
  • +
+

Sub-task

+
    +
  • [PHPBB3-13149] - [Event] - core.phpbb_log_get_topic_auth_sql_before
  • +
+

Task

+ + +

Changes since 3.1.9

+ +

Bug

+
    +
  • [PHPBB3-11446] - Use sql_in_set as designed and consistent with the rest of phpBB code in phpbb_notification_manager
  • +
  • [PHPBB3-12230] - Do not auto remove user group when Newly Registered Users group was disabled
  • +
  • [PHPBB3-12925] - Use plural for permanent delete posts/topics confirmation
  • +
  • [PHPBB3-14109] - MySQL InnoDB does not support multiple index definitions on the same query.
  • +
  • [PHPBB3-14291] - Function send_file_to_browser() endlessly overwrites 'filesize' in ATTACHMENTS_TABLE
  • +
  • [PHPBB3-14610] - Q&A CAPTCHA logs error when it has been solved
  • +
  • [PHPBB3-14615] - delete avatar triggers the new min max value in the HTML5 inputs
  • +
  • [PHPBB3-14616] - Auto-prune fails on large forums
  • +
  • [PHPBB3-14631] - 3.1.9 DB cli update crashes with PHP Fatal error: Call to undefined function phpbb\truncate_string()
  • +
  • [PHPBB3-14654] - Imagemagick > ImageMagick
  • +
  • [PHPBB3-14661] - Fix a typo in twig.php
  • +
  • [PHPBB3-14673] - Missing Language Variable
  • +
  • [PHPBB3-14683] - Typos in operators in some email templates
  • +
  • [PHPBB3-14703] - module.add adds a module to the wrong parent
  • +
  • [PHPBB3-14704] - Remove unused language files and corresponding functions
  • +
  • [PHPBB3-14721] - New registrants choosing old deleted usernames get linked to old accounts with namechange
  • +
  • [PHPBB3-14742] - Improvements to migrator
  • +
  • [PHPBB3-14745] - "U_NOTIFICATION_SETTINGS" contains an HTML entity but is printed in a plaintext email
  • +
  • [PHPBB3-14755] - Error in MCP Move posts
  • +
  • [PHPBB3-14782] - Quick Links > Your Posts gives mysql error
  • +
  • [PHPBB3-14788] - Update developer list to reflect team changes
  • +
  • [PHPBB3-14796] - Log table is using constant in log delete method
  • +
+

Improvement

+
    +
  • [PHPBB3-13709] - Fallback to english in email templates by extensions
  • +
  • [PHPBB3-13716] - Check phpBB version constant against config version
  • +
  • [PHPBB3-13865] - Complement core event search_modify_param
  • +
  • [PHPBB3-14184] - Missing info on SMTP mail function option
  • +
  • [PHPBB3-14429] - core.obtain_users_online_string_modify
  • +
  • [PHPBB3-14466] - Add an event to cron.php
  • +
  • [PHPBB3-14469] - [Template] - <!-- EVENT viewforum_topicrow_before -->
  • +
  • [PHPBB3-14516] - [Template] - memberlist_email_before
  • +
  • [PHPBB3-14581] - Add core events relating to soft delete
  • +
  • [PHPBB3-14592] - [PHP] - core.search_backend_search_after
  • +
  • [PHPBB3-14596] - Prevent installs of 3.1 on PHP 7
  • +
  • [PHPBB3-14624] - Add event to ucp_profile in signature section
  • +
  • [PHPBB3-14630] - Add event to ucp_pm_compose
  • +
  • [PHPBB3-14638] - [PHP] - multiple UCP subscription events for form data and template variables
  • +
  • [PHPBB3-14643] - Select newest file in restore list
  • +
  • [PHPBB3-14652] - Typo birthdays
  • +
  • [PHPBB3-14664] - Fix PHPDoc comment in cron manager
  • +
  • [PHPBB3-14672] - Add template event to viewforum
  • +
  • [PHPBB3-14685] - PHP event for altering announcements sql
  • +
  • [PHPBB3-14687] - Modify viewforum_modify_topicrow
  • +
  • [PHPBB3-14688] - Add core events to the feeds
  • +
  • [PHPBB3-14689] - Build 3.2.x API docs
  • +
  • [PHPBB3-14695] - Add posting_editor_subject_prepend/append template events
  • +
  • [PHPBB3-14712] - Add search.php core event to allow modifying the forum select list
  • +
  • [PHPBB3-14713] - Add core event to the admin function get_forum_list()
  • +
  • [PHPBB3-14715] - Add template events in posting_topic_review & mcp_topic
  • +
  • [PHPBB3-14720] - Add global javascript variable 'phpbb' to jshint settings
  • +
  • [PHPBB3-14727] - Event core.search_modify_submit_parameters
  • +
  • [PHPBB3-14738] - Add core events to improve modifying forum lists
  • +
  • [PHPBB3-14747] - Add topic_last_poster_id and topic_last_post_time to Event core.modify_posting_auth
  • +
  • [PHPBB3-14762] - Add core event to session.php to alter IP address
  • +
  • [PHPBB3-14781] - Add core event to the function group_user_attributes()
  • +
  • [PHPBB3-14783] - Event - ACP Posting Buttons Before Custom BBCodes
  • +
  • [PHPBB3-14784] - missing rewrite for lighttpd
  • +
  • [PHPBB3-14785] - [Template event] - overall_header_headerbar_append
  • +
  • [PHPBB3-14787] - Add more parameters to the core.search_modify_url_parameters event
  • +
  • [PHPBB3-14789] - Add missing link hash and form token checks to ACP
  • +
+

New Feature

+ +

Task

+
    +
  • [PHPBB3-12133] - Update list of browsers supporting filename* in Content-Disposition
  • +
  • [PHPBB3-14538] - Update composer dependencies
  • +
  • [PHPBB3-14598] - Phing Sniffer Testing Use Statements in DocBlocks
  • +
  • [PHPBB3-14743] - Remove PHP7 from test matrix in 3.1.x
  • +
+ +

Changes since 3.1.8

+ +

Bug

+
    +
  • [PHPBB3-8058] - Default style in ACP->Board Settings not observing offset
  • +
  • [PHPBB3-13028] - "View unanswered posts" link should be called instead "View unanswered topics"
  • +
  • [PHPBB3-13264] - Editing an unapproved post as a moderator/admin approves it
  • +
  • [PHPBB3-13521] - Q&A Captcha ACP, required fields error corrupts inputted data
  • +
  • [PHPBB3-13630] - NULL value parsed into $select_single can cause 403 Forbidden on certain restrictive hosting environments for "Find a Member" function within Private Message composition
  • +
  • [PHPBB3-13681] - Email queue shouldn't be cached by opcache
  • +
  • [PHPBB3-13683] - Controller generates urls with absolute path of phpbb's root
  • +
  • [PHPBB3-13842] - Missing rewrite module on IIS7 leads to an error
  • +
  • [PHPBB3-13977] - Fatal error entering UCP if bookmarked topic was deleted
  • +
  • [PHPBB3-14132] - SQL Error when creating a new subject on fresh installation
  • +
  • [PHPBB3-14136] - IE compatibility meta is missing in overall_header.html
  • +
  • [PHPBB3-14241] - Security bug into Spambot control Questions
  • +
  • [PHPBB3-14272] - Use valid html5 input elements in forms
  • +
  • [PHPBB3-14290] - Function set_modified_headers() never sends 304 'Not Modified' header
  • +
  • [PHPBB3-14408] - Remove span corners
  • +
  • [PHPBB3-14422] - Support cmd+enter & ctrl+enter for submitting message
  • +
  • [PHPBB3-14437] - Place Inline Images on Post get scrambled up -- not follow the order you place them in.
  • +
  • [PHPBB3-14443] - jabber notification-template prefix "short" breaks resolution of paths in extensions
  • +
  • [PHPBB3-14475] - Do not log upon automatically removing users form newly registered users group
  • +
  • [PHPBB3-14481] - phpBB does not obey HTTP_X_FORWARDED_PORT header
  • +
  • [PHPBB3-14483] - call to header(arg, arg) function sendHeaders() in Response.php causes Error 500 in app.php generated links
  • +
  • [PHPBB3-14496] - Automatic update relies on cache creating files in cache folder
  • +
  • [PHPBB3-14500] - Duplicate newversion in build.xml
  • +
  • [PHPBB3-14514] - Users get skipped in passwords_convert_p1 migration
  • +
  • [PHPBB3-14519] - Do not query database for unread notifications if all are retrieved
  • +
  • [PHPBB3-14532] - Database column default incorrectly escaped on MSSQL
  • +
  • [PHPBB3-14533] - "U_NOTIFICATION_SETTINGS" doesn't return the correct URL
  • +
  • [PHPBB3-14536] - Force timestamp to be integer in user::format_date()
  • +
  • [PHPBB3-14559] - Attachments' behaviour in quotes
  • +
  • [PHPBB3-14562] - Extension's permissions don't have language fallback
  • +
  • [PHPBB3-14570] - Board versions for 3.2.x can be accidentally downgraded to 3.1.x
  • +
  • [PHPBB3-14577] - Stop using sizeof() inside for() loop
  • +
+

Improvement

+
    +
  • [PHPBB3-10356] - Username search should find all users for administrators instead of NORMALs and FOUNDERs only
  • +
  • [PHPBB3-12305] - Add new event core.viewforum_get_topic_id_sql to control forum topic listing
  • +
  • [PHPBB3-14134] - Send warning notification PM in user's language.
  • +
  • [PHPBB3-14316] - Add memberlist_view.html template events before/after the custom fields and zebra links
  • +
  • [PHPBB3-14365] - Add core event to the function topic_review()
  • +
  • [PHPBB3-14366] - Add core events to the function decode_message()
  • +
  • [PHPBB3-14395] - Add event core.viewtopic_add_quickmod_option_after
  • +
  • [PHPBB3-14471] - Add filedata var to the core.avatar_driver_upload_move_file_before event
  • +
  • [PHPBB3-14486] - Add an event and fix an event in login_box()
  • +
  • [PHPBB3-14508] - Change language notice on account activation
  • +
  • [PHPBB3-14540] - Adjust class recursive_dot_prefix_filter_iterator to increase performance
  • +
+

New Feature

+ +

Task

+ + +

Changes since 3.1.7-PL1

+ +

Bug

+
    +
  • [PHPBB3-12441] - Database-size in ACP missing after update MariaDB from 5.5 to 10.0
  • +
  • [PHPBB3-12618] - Extension Version Check does not support https
  • +
  • [PHPBB3-13180] - Increase the field size of date format to allow more syntax for other calendars
  • +
  • [PHPBB3-13908] - After clause in migration add_column schema tool not honored
  • +
  • [PHPBB3-14046] - Instant message (jabber) dialog says message sent on the creation screen
  • +
  • [PHPBB3-14303] - Some changes for UTF-8 variant on language pack?
  • +
  • [PHPBB3-14374] - Update dynamically generated jquery CDN script tag
  • +
  • [PHPBB3-14386] - open_basedir restriction in effect with remote upload avatar
  • +
  • [PHPBB3-14387] - Extend avatar-driver by extension in ACP not possible
  • +
  • [PHPBB3-14394] - Only purge cache in functional tests if necessary
  • +
  • [PHPBB3-14396] - Use VCHAR_UNI instead of VCHAR for user_dateformat
  • +
  • [PHPBB3-14397] - Fix @since tag in event 'core.ucp_prefs_view_after'
  • +
  • [PHPBB3-14403] - phpbb\log should still work even when no user data is given
  • +
  • [PHPBB3-14407] - Users not being removed from Newly Registered Users group
  • +
  • [PHPBB3-14409] - Update session page info before displaying online list
  • +
  • [PHPBB3-14411] - Delete permanently is not working as it should be
  • +
  • [PHPBB3-14423] - Display database size for Aria storage engine
  • +
  • [PHPBB3-14425] - Database tests do not allow using socket
  • +
  • [PHPBB3-14427] - Memberlist Display Wrong
  • +
  • [PHPBB3-14433] - Functional tests fail for extensions
  • +
  • [PHPBB3-14439] - Error page shown in Manage users -> Anonymous -> Select Form -> Avatar when board wide all avatar settings are disabled
  • +
  • [PHPBB3-14467] - Automatic resize of textarea calculates wrong height
  • +
  • [PHPBB3-14475] - Do not log removal of users from newly registered group
  • +
+

Improvement

+
    +
  • [PHPBB3-14289] - Add events in navbar header
  • +
  • [PHPBB3-14356] - Add template events to viewtopic around back2top link
  • +
  • [PHPBB3-14412] - Comment fixes for PHPDoc in the events
  • +
  • [PHPBB3-14458] - Explicitly state RewriteBase into .htaccess root file
  • +
+ +

Changes since 3.1.7

+ +

Security Issue

+
    +
  • [SECURITY-188] - Check form key in acp_bbcodes
  • +
+

Bug

+
    +
  • [PHPBB3-14343] - Undefined variable $phpbb_dispatcher when (un-)locking a topic or post
  • +
+ +

Changes since 3.1.6

+ +

Bug

+
    +
  • [PHPBB3-8839] - Wrong new status of subforumlink on index
  • +
  • [PHPBB3-8920] - PM-Report for every moderator
  • +
  • [PHPBB3-9153] - New member can delete pm just in one way
  • +
  • [PHPBB3-9252] - Conflict when (dis)approving a post by two moderators at the same time
  • +
  • [PHPBB3-11468] - Controllers can not set additional parameters of page_header()
  • +
  • [PHPBB3-11971] - Validating not correctly in Spambot countermeasures
  • +
  • [PHPBB3-12616] - Report notification is not removed when post is disapproved or deleted
  • +
  • [PHPBB3-13202] - dead code in sessions.php
  • +
  • [PHPBB3-13423] - Driver sqlite3 failed periodically
  • +
  • [PHPBB3-13636] - Unexpect return to previous page behaviour
  • +
  • [PHPBB3-13656] - database_upgrade.php fails when database password contains a % character
  • +
  • [PHPBB3-13748] - Wrong tooltip after poll vote change
  • +
  • [PHPBB3-13759] - submit_post doesn't take $data['post_time'] - into account
  • +
  • [PHPBB3-13799] - Avatar gallery subfolders paths are handled incorrectly
  • +
  • [PHPBB3-13831] - Post deletion reason is not appearing on moderation logs
  • +
  • [PHPBB3-13835] - File upload of large files where filename contains umlauts fails
  • +
  • [PHPBB3-13846] - Permissions around soft deleting are inconsistently handled
  • +
  • [PHPBB3-13851] - "Can ignore flood limit" permission not taking effect
  • +
  • [PHPBB3-13892] - "Someone reports a post" notification setting has no effect
  • +
  • [PHPBB3-13945] - Account re-activation does not create a notification
  • +
  • [PHPBB3-13950] - If disabled extension - no hidden permission set
  • +
  • [PHPBB3-13960] - Profile field validation may break
  • +
  • [PHPBB3-13976] - Fix comment typo in salted_md5 driver
  • +
  • [PHPBB3-13988] - Atom feeds use relative links for image attachments
  • +
  • [PHPBB3-13992] - Fix html5 error from output on w3.org its new validator
  • +
  • [PHPBB3-14058] - subsilver2 Contact us form doesn't have an email subject field
  • +
  • [PHPBB3-14070] - Disabled avatar types is still displayed on the forum
  • +
  • [PHPBB3-14106] - Sorting is unworkable while moderating forum (merge topics)
  • +
  • [PHPBB3-14114] - Inconsistency in install.html in 3.1.x Automatic uopdate package
  • +
  • [PHPBB3-14127] - Error in the BBCode FAQ in 'Linking to another site'
  • +
  • [PHPBB3-14142] - Remove unused ignore_configs from avatar drivers
  • +
  • [PHPBB3-14143] - Flush the in-memory mail queue when writing it to the disk
  • +
  • [PHPBB3-14153] - Notifications dropdown header doesn't clear floats
  • +
  • [PHPBB3-14159] - Not accessible link on main ACP page
  • +
  • [PHPBB3-14161] - The core.download_file_send_to_browser_before - $vars - 'extension' it does not exist
  • +
  • [PHPBB3-14163] - Select All in code bug in Edge
  • +
  • [PHPBB3-14181] - Custom report/denial reason not shown in user notifications
  • +
  • [PHPBB3-14186] - Incorrect string concatenation in phpbb_mcp_sorting()
  • +
  • [PHPBB3-14200] - Allow hidden users to see theself on viewonline
  • +
  • [PHPBB3-14215] - [ticket/14212] - Adding event after users have been removed to a group
  • +
  • [PHPBB3-14217] - [ticket/13591] - Change SQL query into array to allow
  • +
  • [PHPBB3-14224] - Fix trailing whitespaces
  • +
  • [PHPBB3-14228] - Vertical align of numbers in polls
  • +
  • [PHPBB3-14236] - Race condition in the functional tests
  • +
  • [PHPBB3-14242] - Fix on memberlist the sort method.
  • +
  • [PHPBB3-14249] - Online list isn't sorted anymore
  • +
  • [PHPBB3-14258] - Add event in auth::Login
  • +
  • [PHPBB3-14271] - Update nginx sample config
  • +
  • [PHPBB3-14276] - Function get_folder_status not setup for use of plurals
  • +
  • [PHPBB3-14287] - Loading indicator not removed after confirming action that does not produce a message
  • +
  • [PHPBB3-14297] - Uppercase and lowercase when sorting topics
  • +
  • [PHPBB3-14334] - Do not use deprecated function get_user_avatar() in user_loader
  • +
  • [PHPBB3-14339] - State support for PHP 7.0 in docs
  • +
  • [PHPBB3-14346] - Improve version check output when phpbb.com is unreachable
  • +
+

Improvement

+
    +
  • [PHPBB3-7362] - Title/Post Icons Need Attribute Text
  • +
  • [PHPBB3-8800] - Add "mark topics read" link to "View unread posts"
  • +
  • [PHPBB3-10343] - ACP: searching for users does not show inactive accounts
  • +
  • [PHPBB3-13684] - Only resize attached file comments vertically
  • +
  • [PHPBB3-13934] - Enctype clause for forms may be needed for profile fields
  • +
  • [PHPBB3-14066] - Add template events to search_body.html
  • +
  • [PHPBB3-14073] - Add core events to the several places in includes/functions_admin.php
  • +
  • [PHPBB3-14075] - Event in posting preview
  • +
  • [PHPBB3-14080] - Add template events to viewforum_body.html before/after/append/prepend the topic row
  • +
  • [PHPBB3-14088] - Add core events to the search.php
  • +
  • [PHPBB3-14089] - [Template] - posting_topic_title_after
  • +
  • [PHPBB3-14098] - Add core events to the search backends (fulltext_*.php)
  • +
  • [PHPBB3-14102] - Add core event to the mcp_topic.php
  • +
  • [PHPBB3-14113] - Add core events to the memberlist.php for customizing members search
  • +
  • [PHPBB3-14117] - Add core events to index.php to allow modifying birthdays list
  • +
  • [PHPBB3-14123] - Add more descriptive help to the CLI commands
  • +
  • [PHPBB3-14126] - Add viewtopic_topic_title_after template event
  • +
  • [PHPBB3-14133] - Comment fix for phpbb_get_user_rank()
  • +
  • [PHPBB3-14154] - Include "Clean Name" for disabled Extensions
  • +
  • [PHPBB3-14155] - Add row highlighting to extensions and style management
  • +
  • [PHPBB3-14156] - Add the Symfony ResponseListener
  • +
  • [PHPBB3-14164] - Helpful instructions for database updates
  • +
  • [PHPBB3-14170] - Fix mcp_change_poster_after event
  • +
  • [PHPBB3-14201] - Add ACP template events
  • +
  • [PHPBB3-14213] - [PHP] - core.group_add_user_after
  • +
  • [PHPBB3-14219] - Add email address into inactive user display in ACP
  • +
  • [PHPBB3-14261] - Pages served from app.php can't disable the update of session_page
  • +
  • [PHPBB3-14283] - Add a "Manage Group" link on a group page
  • +
  • [PHPBB3-14313] - Don't display quote button on unapproved posts
  • +
  • [PHPBB3-14343] - Add event when locking/unlocking posts/topics
  • +
+

New Feature

+
    +
  • [PHPBB3-14144] - [Template] - quickreply_editor_subject_before
  • +
  • [PHPBB3-14146] - [Template] - viewtopic_body_post_subject_before
  • +
  • [PHPBB3-14187] - [ACP Template] - acp_styles_before_table
  • +
  • [PHPBB3-14188] - [PHP] - core.acp_styles_action_before
  • +
  • [PHPBB3-14191] - [PHP] - core.get_gravatar_url_after
  • +
  • [PHPBB3-14192] - [PHP] - core.memberlist_memberrow_before
  • +
+

Task

+
    +
  • [PHPBB3-14140] - Update Symfony to benefit from improvement to the console component
  • +
+ +

Changes since 3.1.5

+ +

Bug

+
    +
  • [PHPBB3-10711] - SQL error DUPLICATE for KEY in FORUMS_TRACK_TABLE
  • +
  • [PHPBB3-13711] - disabled accounts receive mails
  • +
  • [PHPBB3-13815] - Event parameters in posting do not affect behavior as expected
  • +
  • [PHPBB3-13903] - Container naming doesn't allow Windows' semicolon
  • +
  • [PHPBB3-13930] - Check for correct spacing between keyword & parenthesis in codesniffer
  • +
  • [PHPBB3-13941] - One test is blocked in an inifinite loop on php 5.5+ and with sqlite3
  • +
  • [PHPBB3-13948] - Only reports top level WHOIS IP for a user in ACP
  • +
  • [PHPBB3-13949] - Replace colon with colon lang key in memberlist search page
  • +
  • [PHPBB3-13951] - Jump to page function does not work since 3.1.5 upgrade
  • +
  • [PHPBB3-13952] - Fulltext Native errors from tidy_search cron task
  • +
  • [PHPBB3-13955] - 3.1.5 not loading CDN resources
  • +
  • [PHPBB3-13962] - Forum dropdown in MCP queue could expand beyond screen size
  • +
  • [PHPBB3-13966] - Change post's author --> error message
  • +
  • [PHPBB3-13967] - BBCode guide - loading phpBB logo image breaks https
  • +
  • [PHPBB3-13980] - Current avatar not removed when uploading with different extension
  • +
  • [PHPBB3-13984] - AJAX Loading Indicator broken
  • +
  • [PHPBB3-14037] - Memberlist profile fields headlines not computed from same way as data
  • +
  • [PHPBB3-14049] - Plupload delete request should use config headers
  • +
  • [PHPBB3-14069] - Incorrect sql_fetchfield call in style_update_p1 migration
  • +
  • [PHPBB3-14077] - Select all code not work in Microsoft Edge
  • +
  • [PHPBB3-14082] - Fix wrong variables in fulltext native search for keyworded results
  • +
  • [PHPBB3-14083] - Fix wrong variables in fulltext mysql search for author search
  • +
  • [PHPBB3-14093] - Hardcoded language in ucp_pm_viewfolder.html
  • +
  • [PHPBB3-14103] - Underline in abbreviations in Firefox 40
  • +
  • [PHPBB3-14104] - Fix missing variable in fulltext native search query for total results for author
  • +
  • [PHPBB3-14115] - Microdata is not valid in prosilver
  • +
+

Improvement

+
    +
  • [PHPBB3-11530] - Remove quotes that are too deep automatically
  • +
  • [PHPBB3-12952] - Display HTTP Output along with Status code in case assertion fails in functional tests
  • +
  • [PHPBB3-13598] - Add an option to post a locked topic
  • +
  • [PHPBB3-13786] - Add core events to add MCP post options
  • +
  • [PHPBB3-13818] - Browse Extensions Database link in Extension Manager
  • +
  • [PHPBB3-13843] - Add templates events to insert custom panel-tab into posting editor
  • +
  • [PHPBB3-13863] - [Template] - topiclist_row_append/topiclist_row_prepend in mcp_forum.html
  • +
  • [PHPBB3-13879] - We're using px most places but ems in others
  • +
  • [PHPBB3-13882] - Avatars in notifications should be lazy loaded
  • +
  • [PHPBB3-13899] - Add items to core.search_results_modify_search_title
  • +
  • [PHPBB3-13911] - Add configuration options to profile fields
  • +
  • [PHPBB3-13931] - Wrong order in docs/events.md
  • +
  • [PHPBB3-13968] - [PHP] - core.user_setup_after
  • +
  • [PHPBB3-13971] - Add draft_id variable to event core.posting_modify_template_vars
  • +
  • [PHPBB3-13981] - Events to intercept uploaded avatar deletion
  • +
  • [PHPBB3-13995] - Invalid HTML using Projection Media Type
  • +
  • [PHPBB3-14002] - Add template events before/after user details in ucp_main_front.html
  • +
  • [PHPBB3-14005] - Allow extensions control post buttons displaying
  • +
  • [PHPBB3-14014] - [PHP] - mcp_forum_view_actions
  • +
  • [PHPBB3-14064] - Add template events to ucp_pm_history.html
  • +
  • [PHPBB3-14065] - Add template events to attachment.html
  • +
  • [PHPBB3-14067] - Add template events to overall_header.html around feeds
  • +
  • [PHPBB3-14068] - New events around poll panel in viewtopic
  • +
  • [PHPBB3-14072] - Add core event to the function format_display()
  • +
  • [PHPBB3-14085] - [Template] - posting_topic_title_prepend
  • +
  • [PHPBB3-14086] - [Template] - Add mcp_forum_topic_title_*
  • +
  • [PHPBB3-14090] - [Template] - mcp_topic_topic_title_*
  • +
  • [PHPBB3-14091] - [Template] - mcp_topic_subject_*
  • +
  • [PHPBB3-14101] - Add core event to the download/file.php
  • +
  • [PHPBB3-14116] - sql_affectedrows has no arguments
  • +
+

New Feature

+
    +
  • [PHPBB3-12692] - Add a console command to manage the thumbnail
  • +
  • [PHPBB3-13311] - Add php event to acp manage_forums when deleting content
  • +
  • [PHPBB3-13974] - Add php event for altering data when changing a post's poster
  • +
  • [PHPBB3-13997] - [Template] - posting_editor_submit_buttons
  • +
  • [PHPBB3-14006] - [PHP] - core.ucp_register_agreement
  • +
  • [PHPBB3-14041] - [Template] - viewtopic_dropdown_top_custom
  • +
  • [PHPBB3-14042] - [Template] - viewtopic_dropdown_bottom_custom
  • +
  • [PHPBB3-14043] - [PHP] - core.get_avatar_after
  • +
  • [PHPBB3-14087] - Add an event to ucp_activate.php.
  • +
+

Sub-task

+
    +
  • [PHPBB3-13694] - Allow modifying the Postgres author search query for results
  • +
+

Task

+ + + +

Changes since 3.1.4

+ +

Bug

+
    +
  • [PHPBB3-9563] - Empty categories showing up on index
  • +
  • [PHPBB3-11521] - Missing language string when migration is invalid
  • +
  • [PHPBB3-11532] - acp_users_overview.html autocompletes "confirm email" and "password" fields in chrome
  • +
  • [PHPBB3-13516] - icc-profiler check should skip extensions vendor
  • +
  • [PHPBB3-13564] - User is not removed from oauth tables upon deletion
  • +
  • [PHPBB3-13664] - Allow changing total list for unapproved posts in mcp_front
  • +
  • [PHPBB3-13755] - uploading attachments results in error: parsing server response.
  • +
  • [PHPBB3-13763] - Language Spelling Error: Completly
  • +
  • [PHPBB3-13771] - AJAX responses do not support exceptions messages
  • +
  • [PHPBB3-13772] - Error in @param variable type for phpbb\passwords\manager
  • +
  • [PHPBB3-13779] - Permission set migration tool grants regular users heightened permissions
  • +
  • [PHPBB3-13787] - Duplicate entry of poll_delete in prosilver template
  • +
  • [PHPBB3-13792] - Travis fails installing hhvm-nigthly
  • +
  • [PHPBB3-13819] - Add missing sql_freeresult() to files in includes/
  • +
  • [PHPBB3-13822] - Permissions are in the wrong category
  • +
  • [PHPBB3-13823] - Update package is missing file with inline whitespace changes
  • +
  • [PHPBB3-13827] - controller\helper::message does not return JSON object for AJAX requests
  • +
  • [PHPBB3-13830] - phpcs doesn't detect class using in catch blocks
  • +
  • [PHPBB3-13833] - Submit a lot of messages without timeout between messages
  • +
  • [PHPBB3-13838] - Add a sniff to ensure that the opening brace of a control statement is on the line after
  • +
  • [PHPBB3-13852] - Inconsistent tab navigation when login in
  • +
  • [PHPBB3-13861] - Old styles are not removed by style update migration
  • +
  • [PHPBB3-13868] - Adding multiple language files for acp/mcp/ucp modules is incorrectly handled for extensions
  • +
  • [PHPBB3-13873] - Remove broken print stylesheet in preference of &view=print
  • +
  • [PHPBB3-13875] - Lint test should ignore cache, ext, and store folder
  • +
  • [PHPBB3-13878] - Properly display background images when printing with webkit browser
  • +
  • [PHPBB3-13888] - "Couldn't fetch mysqli_result" error on username search and egosearch
  • +
  • [PHPBB3-13913] - Post subject of password protected and list-only forums shown on board index
  • +
  • [PHPBB3-13922] - Whitespace found at end of line phpBB/includes/functions_admin.php
  • +
  • [PHPBB3-13928] - Fix build_cfg_template_test after ticket/sec-184
  • +
  • [PHPBB3-13939] - phpBB no longer shows error messages when uploading files with drag/drop
  • +
  • [PHPBB3-13942] - phpbb\user::set_lang() adds extra path and PHP extension to ext lang files
  • +
+

Improvement

+
    +
  • [PHPBB3-12101] - Redirect for Microsoft servers in /includes/functions.php:redirect()
  • +
  • [PHPBB3-12542] - Highlight textarea when files are dragged over it
  • +
  • [PHPBB3-12717] - Improve the code sniffer
  • +
  • [PHPBB3-13200] - Add autocomplete="off" to additional password fields
  • +
  • [PHPBB3-13648] - Allow extensions using custom bbcode validation methods
  • +
  • [PHPBB3-13699] - Add template events in viewforum_body.html before and after the title
  • +
  • [PHPBB3-13745] - Add veiwtopic.php core event to allow manipulating poll data
  • +
  • [PHPBB3-13750] - Add generate_forum_nav() core event to allow modifying navlinks text
  • +
  • [PHPBB3-13752] - Add viewonline.php core event to allow modifying forum data SQL query
  • +
  • [PHPBB3-13753] - Add template events to forum category header
  • +
  • [PHPBB3-13778] - Misleading instruction text for recaptcha plugin
  • +
  • [PHPBB3-13790] - Update phpcs to 2.3.2
  • +
  • [PHPBB3-13791] - Add more post buttons template events to viewtopic_body.html
  • +
  • [PHPBB3-13808] - Add event before and after the search form
  • +
  • [PHPBB3-13809] - Test php parsing on php7 on travis
  • +
  • [PHPBB3-13841] - Add event when topics are moved
  • +
  • [PHPBB3-13858] - Make the Plupload uploader instance available in the global namespace
  • +
  • [PHPBB3-13872] - Allow template events to have a changed tag
  • +
  • [PHPBB3-13876] - Use async webfontloader to load webfont from googles CDN
  • +
  • [PHPBB3-13905] - loading.gif loaded before document load when it isn't needed
  • +
+

Security Issue

+
    +
  • [PHPBB3-13917] - Use hash_equals() if possible in password driver helper
  • +
  • [SECURITY-184] - Do not output passwords to HTML input fields
  • +
+

Sub-task

+
    +
  • [PHPBB3-13660] - Allow changing the query for total reports in mcp_front
  • +
  • [PHPBB3-13661] - Allow changing how and which logs are retrieved
  • +
  • [PHPBB3-13668] - Allow modifying the query to get details from the post report
  • +
  • [PHPBB3-13672] - Allow changing the query to obtain the user-submitted report.
  • +
  • [PHPBB3-13685] - Allow modifying the keywords search query for mysql fulltext search
  • +
  • [PHPBB3-13686] - Allow modifying the fulltext native search query for total results before before
  • +
  • [PHPBB3-13689] - Allow modifying the Postgres native search query for results
  • +
  • [PHPBB3-13691] - Allow modifying the fulltext native search query for total results for author
  • +
  • [PHPBB3-13693] - Allow modifying the MySQL author search query for results
  • +
+

Task

+
    +
  • [PHPBB3-13807] - Extend event exporter to filter by min or max version to allow generating event diffs for releases
  • +
  • [PHPBB3-13887] - JS could use some refactoring
  • +
+ + +

Changes since 3.1.3

+ +

Security

+
    +
  • [SECURITY-180] - An insufficient check allowed users of the Google Chrome browser to be redirected to external domains (e.g. on login)
  • +
+

Bug

+
    +
  • [PHPBB3-8050] - Avatar & Long PM recipients list break out of template
  • +
  • [PHPBB3-8250] - Forum selections in MCP queue not working
  • +
  • [PHPBB3-8494] - Cannot install two boards on the same postgresql database
  • +
  • [PHPBB3-11424] - Quick-Mod Tools race condition results in NO_MODE
  • +
  • [PHPBB3-12368] - Updating database fails in upgrade from 3.0 when trying twice without purging the cache
  • +
  • [PHPBB3-13348] - sql_freeresult() should be called in feed base class
  • +
  • [PHPBB3-13414] - download/file.php 304 Not Modified bug
  • +
  • [PHPBB3-13433] - A dot in email address leads to unwanted extraneous dot
  • +
  • [PHPBB3-13463] - Mark read icon displays on wrong side in RTL
  • +
  • [PHPBB3-13469] - Soft delete fails with error message
  • +
  • [PHPBB3-13472] - Notification for admin activation of user doesn't get pruned after the user is deleted
  • +
  • [PHPBB3-13477] - File caching of extensions' version check file doesn't work
  • +
  • [PHPBB3-13493] - $helper->route gives wrong path for guests with trailing slashes and mod_rewrite disabled
  • +
  • [PHPBB3-13522] - Q&A Captcha ACP, admins can add blank answers
  • +
  • [PHPBB3-13538] - Add tests for pagination in nested loops
  • +
  • [PHPBB3-13542] - Add $error to core UCP event for better validating of new UCP options
  • +
  • [PHPBB3-13550] - Invalid JSON response returned when plupload dir is not writable
  • +
  • [PHPBB3-13551] - Authentication method- LDAP- entered value 'ldap base dn' does not display
  • +
  • [PHPBB3-13555] - Poll options preview rendered incorrectly by <br /> collision
  • +
  • [PHPBB3-13563] - No Private Message button shown in memberlist for subsilver2
  • +
  • [PHPBB3-13568] - Imagick path validated as relative path although ACP asks for absolute path
  • +
  • [PHPBB3-13569] - Use sql_freeresult for $result assignments and remove unneeded $result assignments
  • +
  • [PHPBB3-13570] - Mysqli extension supports persistent connection since PHP 5.3.0
  • +
  • [PHPBB3-13577] - Skip tests requiring fileinfo if fileinfo is not enabled
  • +
  • [PHPBB3-13586] - Allow '0' as username with Jabber notifications
  • +
  • [PHPBB3-13587] - SQL syntax errors in get_prune_users()
  • +
  • [PHPBB3-13588] - Information message for disabled fsockopen() is not displayed correctly
  • +
  • [PHPBB3-13590] - Remember me login keys should be centered
  • +
  • [PHPBB3-13597] - Modify variable-variable syntax to be compatible with PHP7
  • +
  • [PHPBB3-13612] - Functional test of extension fails if ext requires page refresh
  • +
  • [PHPBB3-13615] - Avatar Gallery shows categories but no images in subsilver2
  • +
  • [PHPBB3-13617] - Bot session continuation with invalid f= query paramter causes SQL error
  • +
  • [PHPBB3-13618] - Small grammatical typo in the English FAQ regarding COPPA
  • +
  • [PHPBB3-13631] - Fix variable name in core.phpbb_content_visibility_get_global_visibility_before event
  • +
  • [PHPBB3-13639] - Unused class icon-search-advanced references nonexistent image
  • +
  • [PHPBB3-13644] - Type hint dispatcher_interface instead of dispatcher
  • +
  • [PHPBB3-13649] - Subforum tooltip always displays "no unread posts" on viewforum.php
  • +
  • [PHPBB3-13655] - $phpbb_dispatcher undefined in phpbb_mcp_sorting()
  • +
  • [PHPBB3-13657] - Start testing against PHP7
  • +
  • [PHPBB3-13666] - data-clicked attribute is not always removed on ajax form submissions
  • +
  • [PHPBB3-13667] - Big buttons are incorrectly aligned in Chrome on Windows
  • +
  • [PHPBB3-13670] - Fix fatal function name must be a string in functional tests
  • +
  • [PHPBB3-13698] - Incorrect password message shows unparsed "Board Administrator"-link
  • +
  • [PHPBB3-13702] - Page is zoomed in by default on iOS devices in landscape mode
  • +
  • [PHPBB3-13703] - Uploaded avatars are not loading correctly when passing through the events
  • +
  • [PHPBB3-13719] - Remove superfluous $search_options in acp_search.php
  • +
  • [PHPBB3-13721] - URL Rewriting doesn't work on IIS7
  • +
  • [PHPBB3-13723] - Update docs/AUTHORS for 3.1.4-RC1
  • +
  • [PHPBB3-13726] - Responsive breadcrumbs JavaScript incorrectly calculates width of hidden items
  • +
  • [PHPBB3-13727] - Responsive breadcrumbs JavaScript doesn't reset wrap- classes when resizing
  • +
  • [PHPBB3-13732] - Update composer for PHP7 compatibility
  • +
  • [PHPBB3-13736] - Replace colons with colon lang keys in Contact us page
  • +
  • [PHPBB3-13738] - Sami still refers to develop-* branches
  • +
  • [PHPBB3-13741] - Remove outdated comments in CSS files
  • +
  • [PHPBB3-13742] - Local avatar driver is not generating correct urls on index
  • +
  • [PHPBB3-13743] - Missing global vars $phpbb_root_path and $phpEx in message_parser.php
  • +
  • [PHPBB3-13747] - Fix test_validate_path_linux method
  • +
  • [PHPBB3-13749] - Add missing slash to base uri in helper route tests
  • +
+

Improvement

+
    +
  • [PHPBB3-13313] - Add a core php event to the mass email form
  • +
  • [PHPBB3-13467] - Add a CONTRIBUTING file to the project on Github
  • +
  • [PHPBB3-13510] - Add template event before/after the pagination on viewtopic
  • +
  • [PHPBB3-13512] - Add template events to viewtopic_body.html before/after the post details
  • +
  • [PHPBB3-13518] - Add core event to markread() in functions.php
  • +
  • [PHPBB3-13532] - Add core event to get_unread_topics() in functions.php
  • +
  • [PHPBB3-13533] - Add template events to the header of search_results.html
  • +
  • [PHPBB3-13535] - Add ucp_profile.php core event to allow modifying account settings on editing
  • +
  • [PHPBB3-13536] - Add UCP/ACP core events to allow modifying user profile data on editing
  • +
  • [PHPBB3-13537] - Add core events on mcp_queue for approval and disapproval
  • +
  • [PHPBB3-13540] - Add events to the topic review while posting and moderating posts
  • +
  • [PHPBB3-13578] - Add ucp_register.php core event
  • +
  • [PHPBB3-13591] - Add functions.php core event to the function obtain_users_online_string()
  • +
  • [PHPBB3-13595] - Remove unused instances of the bbcode class
  • +
  • [PHPBB3-13596] - Add display_forums() core event to allow modifying forums list data
  • +
  • [PHPBB3-13600] - Add core event to allow extensions to create a custom help page
  • +
  • [PHPBB3-13602] - Add template event overall_header_navbar_before
  • +
  • [PHPBB3-13628] - Add template events into ucp profile html files
  • +
  • [PHPBB3-13635] - Add sql_ary to UCP profile event
  • +
  • [PHPBB3-13637] - Add php event for modifying the data when composing a PM
  • +
  • [PHPBB3-13643] - kernel_terminate_subscriber should have a very low priority
  • +
  • [PHPBB3-13650] - New core event for UCP profile mode
  • +
  • [PHPBB3-13658] - [Event] - Before and after deletion of topics
  • +
  • [PHPBB3-13675] - Add validate to acp_profile event and add template events
  • +
  • [PHPBB3-13679] - Add template event overall_header_searchbox_before
  • +
  • [PHPBB3-13701] - New posting_pm_layout.html template events to wrap "include posting_pm_header.html"
  • +
  • [PHPBB3-13710] - Add template events around smilies display
  • +
+

New Feature

+ +

Sub-task

+
    +
  • [PHPBB3-13142] - [Event] - Before query to list unapproved and deleted posts
  • +
  • [PHPBB3-13592] - Add core event to allow changing get_visibility_sql's result
  • +
  • [PHPBB3-13621] - Fix event phpbb_content_visibility_get_forums_visibility_before to get where_sql working as specified
  • +
  • [PHPBB3-13625] - Add more variables to core.viewforum_get_topic_data
  • +
+

Task

+ + +

Changes since 3.1.3-RC1

+ +

Bug

+
    +
  • [PHPBB3-12933] - The search operator for partial matches does not work
  • +
  • [PHPBB3-13544] - Migrations' "permission.permission_unset" deletes all permissions instead of just the one stated
  • +
  • [PHPBB3-13556] - Translated exceptions from file_downloader are handled incorrectly
  • +
  • [PHPBB3-13557] - Migrations for 3.0.13 and PL1 are missing
  • +
+

Task

+
    +
  • [PHPBB3-13553] - Controller helper needs a message handler to replace error handler
  • +
+ +

Changes since 3.1.2

+ +

Security

+
    +
  • [PHPBB3-13519] - Correctly validate imagick path as path and not string
  • +
+

Bug

+
    +
  • [PHPBB3-9100] - Inline attachments are not being inserted at the cursor
  • +
  • [PHPBB3-11613] - Cookies do not work for netbios domain
  • +
  • [PHPBB3-12089] - Make HTTP status code error messages more informative
  • +
  • [PHPBB3-12642] - Custom profile field isn't displayed
  • +
  • [PHPBB3-12698] - Replace all instances of magic numbers with constants in javascript
  • +
  • [PHPBB3-12866] - Wrong profile field validation options
  • +
  • [PHPBB3-13098] - Repair Yahoo contact field
  • +
  • [PHPBB3-13192] - confirm_box() action contains app.php when enable_mod_rewrite is set
  • +
  • [PHPBB3-13238] - \phpbb\db\migration\data\v310\mysql_fulltext_drop tries to drop non existent indexes
  • +
  • [PHPBB3-13272] - Changed Files packages do not include vendor directory
  • +
  • [PHPBB3-13282] - PostgreSQL error when creating boolean/dropdown custom profile fields
  • +
  • [PHPBB3-13302] - ACP links to docs need to be updated to 3.1 URLs
  • +
  • [PHPBB3-13307] - develop/mysql_upgrader.php does not work anymore
  • +
  • [PHPBB3-13319] - Icons/smilies table improperly formed when no images present
  • +
  • [PHPBB3-13346] - Missing space in posting_editor.html
  • +
  • [PHPBB3-13357] - LOG_MOVED_TOPIC Language var missing
  • +
  • [PHPBB3-13358] - Add class for retrieving remote file data
  • +
  • [PHPBB3-13362] - The whole cache dir (excluding the .htaccess and index.html files) should be ignored by git
  • +
  • [PHPBB3-13366] - Dynamic config for "plupload_last_gc" is static
  • +
  • [PHPBB3-13381] - Code Sniffer complains about 3.1.2 migration
  • +
  • [PHPBB3-13385] - Add free result after running update query in $config->set_atomic()
  • +
  • [PHPBB3-13391] - subsilver2 poll options must have a setting of 1 when editing a post
  • +
  • [PHPBB3-13393] - Invalid parameters passed to call_user_func_array in version_helper.php
  • +
  • [PHPBB3-13396] - Multibyte chars cause attachment upload to fail
  • +
  • [PHPBB3-13400] - Add a new message for high server loads during search
  • +
  • [PHPBB3-13405] - A typo in style_update_p1.php migration may prevent updating in some circumstances
  • +
  • [PHPBB3-13406] - ADD INDEX syntax may cause an error in earlier MySQL versions
  • +
  • [PHPBB3-13420] - Prune users bug - filter all with 0 posts
  • +
  • [PHPBB3-13427] - Add template events to MCP front before/after each list
  • +
  • [PHPBB3-13431] - Wrong margin-left for RTL sites in Internet Explorer mobile view
  • +
  • [PHPBB3-13432] - Migrator module tool does not add the needed module language file
  • +
  • [PHPBB3-13441] - functions_convert fails to set global moderators default group
  • +
  • [PHPBB3-13442] - UTF-8 symbols for database host
  • +
  • [PHPBB3-13448] - \phpbb\messenger->template can't find email templates in extensions
  • +
  • [PHPBB3-13453] - Sort params in Canonical URL
  • +
  • [PHPBB3-13470] - Mass email says "user doesn't exist" when all users is selected
  • +
  • [PHPBB3-13486] - Call to undefined method phpbb\db\driver\factory::sql_escpape() on database update
  • +
  • [PHPBB3-13489] - Allow the migrations to use the DI container
  • +
  • [PHPBB3-13490] - Unicode chars are broken in edit message after preview
  • +
  • [PHPBB3-13492] - Custom BBCode URL tokens do not support IDN
  • +
  • [PHPBB3-13504] - "Array" is displayed when searching, or when unanswered posts or active topics are selected
  • +
  • [PHPBB3-13507] - Large images in posts can cause horizontal scrollbars
  • +
  • [PHPBB3-13511] - The "Unused Use" Sniff is broken
  • +
  • [PHPBB3-13530] - Fix undefined variables in migrations and tests
  • +
  • [PHPBB3-13534] - Non-existent path in attachment settings causes travis failure
  • +
  • [PHPBB3-13543] - Slow tests fail on 3.1 and 3.2
  • +
+

Improvement

+
    +
  • [PHPBB3-11033] - FULLTEXT_SPHINX_NO_CONFIG_DATA references unrequired field
  • +
  • [PHPBB3-12567] - [proSilver] - viewtopic_body.html: Change 'back2top' anchor in to '#top'
  • +
  • [PHPBB3-12926] - Support for IDN (IRI)
  • +
  • [PHPBB3-13266] - Enabling twig dump function if DEBUG is defined
  • +
  • [PHPBB3-13306] - Add error level to the error collector
  • +
  • [PHPBB3-13312] - [event] - Add core event to the mass email form
  • +
  • [PHPBB3-13368] - Add the forum_data var to the core.viewforum_get_topic_ids_data event
  • +
  • [PHPBB3-13370] - Add ability to call class methods more easily in the convertor framework
  • +
  • [PHPBB3-13389] - Replace pattern with path in routing.yml
  • +
  • [PHPBB3-13402] - Code Sniffer, unused use, check the function doc blocks
  • +
  • [PHPBB3-13409] - Add event to modify search parameters before searching
  • +
  • [PHPBB3-13419] - Add template event at the end of the file
  • +
  • [PHPBB3-13422] - Add new events in save custom cookies and set custom ban type
  • +
  • [PHPBB3-13428] - Add core events to memberlist.php for teampage
  • +
  • [PHPBB3-13430] - Add event for modifying prune SQL
  • +
  • [PHPBB3-13435] - Add core event to modify submit_post() sql data
  • +
  • [PHPBB3-13437] - [Template] - viewtopic_body_post_author_before/after
  • +
  • [PHPBB3-13439] - [event] - Add event to run code at beginning of ACP users overview
  • +
  • [PHPBB3-13440] - [event] - Add event to process when a user fails a login attempt
  • +
  • [PHPBB3-13449] - Add viewforum.php core event after the topic data has been assigned to template
  • +
  • [PHPBB3-13466] - Add bbcode_uid and bitfield to event core.message_parser_check_message
  • +
  • [PHPBB3-13478] - Add core event core.bbcode_cache_init_end
  • +
+

Sub-task

+
    +
  • [PHPBB3-13141] - Add an event to allow applying additional permissions to MCP access besides f_read
  • +
  • [PHPBB3-13146] - Add an event to allow changing the result of calling get_forums_visibility_sql()
  • +
  • [PHPBB3-13147] - Add an event to change get_global_visibility_sql()'s results
  • +
  • [PHPBB3-13148] - Add an event to creating a final way to modify edit logs output
  • +
  • [PHPBB3-13154] - Add an event to edit user list for notifications
  • +
  • [PHPBB3-13158] - Add an event to allow adding extra auth checks when the user is posting
  • +
  • [PHPBB3-13159] - Add an event to allow extra auth checks when reporting posts
  • +
  • [PHPBB3-13160] - Add an event to viewtopic before viewing permissions
  • +
+

Task

+
    +
  • [PHPBB3-12924] - Meta tags should be self-closing
  • +
  • [PHPBB3-13528] - Boolean checkbox profile fields display "1" instead of selected value
  • +
+ + +

Changes since 3.1.1

+ +

Security

+
    +
  • [SECURITY-171] - Version helper does not properly escape version info
  • +
  • [SECURITY-169] - AJAX request with unexpected referrer causes infinite loop
  • +
+ +

Bug

+
    +
  • [PHPBB3-10442] - XHTML is invalid when a forum link without redirect counter is present
  • +
  • [PHPBB3-10744] - Prevent user from installing styles with reserved directory names
  • +
  • [PHPBB3-11863] - User registration settings show incorrectly as disabled when board-wide emails are disabled
  • +
  • [PHPBB3-12703] - Notification System sends exact same SQL query multiple times
  • +
  • [PHPBB3-13083] - Language correction in NO_ENTRIES in acp_logs
  • +
  • [PHPBB3-13100] - Don't display "delete reason" dialog for shadow-topics
  • +
  • [PHPBB3-13193] - Post counts in Private Messages should link to the user's posts
  • +
  • [PHPBB3-13197] - Group Avatar not deleted from users
  • +
  • [PHPBB3-13204] - Login flood control error supresses incorrect credential error
  • +
  • [PHPBB3-13209] - Boolean (Yes/No) custom profile field doesn't show given name
  • +
  • [PHPBB3-13216] - Datetime tests fail randomly
  • +
  • [PHPBB3-13228] - "Code: Select all" font-size too big in Private Messages
  • +
  • [PHPBB3-13239] - Can´t upload Attachments on iOS
  • +
  • [PHPBB3-13241] - Topics are being duplicated in multipage forums
  • +
  • [PHPBB3-13242] - Validation error in Contact a Board Administrator
  • +
  • [PHPBB3-13243] - Debug error when clicking Re-check all versions on ACP manage extensions page
  • +
  • [PHPBB3-13251] - Database password containing special characters no longer accepted after upgrade to 3.1.0
  • +
  • [PHPBB3-13253] - MCP queue link in active topics search is missing
  • +
  • [PHPBB3-13265] - "Edit profile" link on view-own-profile page should only show if user has permission to edit
  • +
  • [PHPBB3-13270] - Upgrading from 3.0.12 to 3.1.1 does not display moderator soft delete permissions
  • +
  • [PHPBB3-13277] - Move Up & Down does not take work in Internet Explorer
  • +
  • [PHPBB3-13280] - $user->page['page'] - is invalid resulting in confirm_box() not working correctly
  • +
  • [PHPBB3-13284] - Message body not included in email topic message
  • +
  • [PHPBB3-13298] - Use mysql_free_result to free result sets which were requested using mysql_query()
  • +
  • [PHPBB3-13300] - Jabber field still shown in profile when feature is disabled
  • +
  • [PHPBB3-13301] - Apache Authentication is probably broken
  • +
  • [PHPBB3-13303] - Migrator caught in loop calculating dependencies
  • +
  • [PHPBB3-13315] - Upgrade from 3.0.12 to 3.1.1 resets CAPTCHA selection
  • +
  • [PHPBB3-13316] - reCAPTCHA does not work on secured connection
  • +
  • [PHPBB3-13318] - login_username doesn't have multibyte parameter set to true
  • +
  • [PHPBB3-13323] - posting.php can pass invalid auth option to acl_get()
  • +
  • [PHPBB3-13332] - Insufficient information passed to password drivers for converted boards
  • +
  • [PHPBB3-13337] - Mark subforums read triggers error if subforums contain no topics
  • +
  • [PHPBB3-13338] - Some tests fail when run on their own
  • +
  • [PHPBB3-13342] - 310/captcha_plugins migration changes recaptcha to nogd
  • +
  • [PHPBB3-13349] - Incorrect entities used for breadcrumb separator in CSS
  • +
  • [PHPBB3-13354] - Unknown column 'topic_logs' in 'where clause' when deleting topic log in MCP
  • +
  • [PHPBB3-13376] - deregister_globals() does not work correctly when $_COOKIE['GLOBALS'] is specified
  • +
+ +

Improvement

+
    +
  • [PHPBB3-12681] - Cache the compiled routes and dump the url_generator
  • +
  • [PHPBB3-12885] - Wrong index page title when using Board Index text
  • +
  • [PHPBB3-13023] - [event] - Add Event posting_editor_buttons_custom_tags_before
  • +
  • [PHPBB3-13133] - Allow @vendor_extname in INCLUDECSS
  • +
  • [PHPBB3-13182] - [event] - Add posting.php core event to allow modifying the message before parsing
  • +
  • [PHPBB3-13220] - [event] - Add template events to memberlist_search.html
  • +
  • [PHPBB3-13290] - [event] - Add template event index_body_forumlist_body_after
  • +
  • [PHPBB3-13294] - [event] - Add message_parser.php core event for additional message handling before parsing
  • +
  • [PHPBB3-13297] - Add unicode modifier to url/email regular expression patterns
  • +
  • [PHPBB3-13309] - [event] - Add ACP template event acp_email_options_after
  • +
  • [PHPBB3-13310] - [event] - Add core event core.acp_email_modify_sql
  • +
  • [PHPBB3-13326] - Add viewtopic_url variable to a viewtopic event
  • +
  • [PHPBB3-13328] - [event] - Add event core.mcp_view_forum_modify_sql
  • +
  • [PHPBB3-13347] - [event] - Add new template events to acp_forums.html
  • +
+ +

New Feature

+
    +
  • [PHPBB3-12962] - Use phantomjs and webdriver for UI testing
  • +
+ +

Task

+
    +
  • [PHPBB3-13324] - Composer no longer downloads sami/sami and fabpot/goutte
  • +
  • [PHPBB3-13325] - Make installing dependencies for tests more user friendly or optional
  • +
  • [PHPBB3-13331] - Sami run as part of phing MUST NOT switch branches
  • +
+ + +

Changes since 3.1.0

+ +

Security

+
    +
  • [SECURITY-164] - Cross Site Scripting via PATH_INFO in page_name variable
  • +
+

Bug

+
    +
  • [PHPBB3-13248] - Login functions need to use provider collection for retrieving provider
  • +
  • [PHPBB3-13267] - Automatic Update instructions indicate that only the install folder is necessary
  • +
  • [PHPBB3-13268] - MSSQL's get_existing_indexes() function improperly appends ternary result
  • +
  • [PHPBB3-13271] - Anonymous users can CC themselves on emails sent to admin via contact form
  • +
+

Task

+
    +
  • [PHPBB3-13262] - Add note to docs about htaccess file when upgrading 3.0 to 3.1
  • +
+ +

Changes since 3.1.0-RC6

+ +

Bug

+
    +
  • [PHPBB3-13126] - More detailed output for migrations needed
  • +
  • [PHPBB3-13208] - Security issues are not pulled into the changelog
  • +
  • [PHPBB3-13210] - Queue Cron Job checks for wrong config variable queue_interval_config
  • +
  • [PHPBB3-13211] - Add possibility to save migrations output to log
  • +
  • [PHPBB3-13221] - Can't upgrade to 3.1 from 3.0.11 and older
  • +
  • [PHPBB3-13223] - Using get_username_string() for email template variables causes HTML markup in emails
  • +
  • [PHPBB3-13225] - phpbb_hash() undefined in phpbb\db\migration\data\v30x\release_3_0_5_rc1.php
  • +
  • [PHPBB3-13226] - Stray $rank_img in memberlist.php
  • +
  • [PHPBB3-13227] - Remote avatars do not work with cURL wrapper
  • +
  • [PHPBB3-13229] - Memberlist is getting overloaded with redundant SQL queries
  • +
  • [PHPBB3-13230] - Deprecated phpbb_clean_path() does not work anymore
  • +
  • [PHPBB3-13231] - The migration contact_admin_form must depends on config_db_text
  • +
  • [PHPBB3-13232] - Email queue does not get run
  • +
  • [PHPBB3-13234] - Remember me cookie gets unset by admin reauthentication
  • +
+

Improvement

+
    +
  • [PHPBB3-13207] - Default subscription notification setting for new users does not include email
  • +
+

Task

+ + +

Changes since 3.1.0-RC5

+ +

Bug

+
    +
  • [PHPBB3-12530] - Visual confirmation is breaking layout in prosilver's mobile mode
  • +
  • [PHPBB3-12568] - docs/README.html references MODs instead of extensions
  • +
  • [PHPBB3-13124] - PHP event extractor too strict on doc blocks
  • +
  • [PHPBB3-13138] - Banned users cause infinite recursion
  • +
  • [PHPBB3-13140] - Header links don't re-appear on window size increase
  • +
  • [PHPBB3-13161] - PHP Warnings issued from phpbb database test case
  • +
  • [PHPBB3-13163] - Header Navbar Responsiveness Broken
  • +
  • [PHPBB3-13164] - Data sent to core.submit_post_end event does not include fresh post_visibility
  • +
  • [PHPBB3-13168] - Warning displayed in PHP 5.6 for mbstring.http_input
  • +
  • [PHPBB3-13169] - Responsive forms not displaying correctly in RTL
  • +
  • [PHPBB3-13171] - Can not delete posts and soft delete topics in MCP topic view
  • +
  • [PHPBB3-13174] - Minor HTML error in ucp_pm_viewmessage.html (needs closing </div>)
  • +
  • [PHPBB3-13177] - Post count-based ranks do not display in viewtopic
  • +
  • [PHPBB3-13181] - Sphinx config template should use place holders for database credentials
  • +
  • [PHPBB3-13186] - Do not link post count to author search if search disabled
  • +
  • [PHPBB3-13187] - INSTALL.html is not valid HTML
  • +
  • [PHPBB3-13188] - Sphinx index() function triggers slow queries that time out replies
  • +
  • [PHPBB3-13190] - phpbb_session_login_keys_test::test_reset_keys fails on develop-ascraeus
  • +
  • [PHPBB3-13194] - BBCode isn't parsed when issuing a warning for a post
  • +
  • [PHPBB3-13203] - Use constant time comparison method for comparing password hashes
  • +
  • [PHPBB3-13217] - Remember me cookie leak
  • +
  • [PHPBB3-13218] - Missing token check in acp_styles
  • +
  • [PHPBB3-13221] - Can't upgrade to 3.1 from 3.0.11 and older
  • +
  • [PHPBB3-13223] - Using get_username_string() for email template variables causes HTML markup in emails
  • +
+

Improvement

+
    +
  • [PHPBB3-12796] - View own Profile should have an edit button
  • +
  • [PHPBB3-12799] - Place the events for f_brunoais_read_other
  • +
  • [PHPBB3-13041] - help_faq.php language file needs to be revised
  • +
+

New Feature

+ + + +

Changes since 3.1.0-RC4

+ +

Bug

+
    +
  • [PHPBB3-10729] - Post editor information is not updated when user being deleted with posts
  • +
  • [PHPBB3-11224] - SQL cache destroy does not destroy queries to tables joined
  • +
  • [PHPBB3-12368] - Updating database fails in upgrade from 3.0 when trying twice without purging the cache
  • +
  • [PHPBB3-12489] - Description for Contact link in custom profile fields
  • +
  • [PHPBB3-12657] - Add a test file for cli command cache:purge
  • +
  • [PHPBB3-12858] - 'GMT' is hard coded and not pulled from language files
  • +
  • [PHPBB3-12889] - multi-select element in unban email-adresses not limited in width
  • +
  • [PHPBB3-13011] - Javascript bug when selecting first one or two characters of a post
  • +
  • [PHPBB3-13027] - PM folder full percentage could be a bit more accurate
  • +
  • [PHPBB3-13033] - Duplicate entry SQL error thrown when running notifications_use_full_name migration
  • +
  • [PHPBB3-13045] - Pragma header not specified as response header by RFC2616
  • +
  • [PHPBB3-13048] - AJAX requests are being stored to session_page
  • +
  • [PHPBB3-13055] - String profile fields validation limits content to latin chars only
  • +
  • [PHPBB3-13070] - Wrong hook name is registered for array(template, display)
  • +
  • [PHPBB3-13071] - total_match_count is used before core.search_get_posts_data event
  • +
  • [PHPBB3-13080] - Responsive design - language variable to long
  • +
  • [PHPBB3-13082] - Search box does not displayed proper in admin cp when no logs listed
  • +
  • [PHPBB3-13085] - Fix typo in oauth.php
  • +
  • [PHPBB3-13086] - Update ACP_MASS_EMAIL_EXPLAIN language key
  • +
  • [PHPBB3-13087] - WLM link is empty in viewtopic
  • +
  • [PHPBB3-13097] - Fix missing unit type in forms.css
  • +
  • [PHPBB3-13104] - Responsive tabs display bug in IE11
  • +
  • [PHPBB3-13105] - Future dates are displayed as "Tomorrow" with relative date format
  • +
  • [PHPBB3-13111] - Debug output when editing DropDown Custom Fields with multiple languages
  • +
  • [PHPBB3-13113] - The routes are not always generated for the good app.php file
  • +
  • [PHPBB3-13116] - Topic and post approval notifications got interchanged
  • +
  • [PHPBB3-13117] - Installation fails on MySQL 5.7 because auto_increment columns can be NULL
  • +
  • [PHPBB3-13118] - datetime.class parameter not used in user class
  • +
  • [PHPBB3-13120] - phpbb_load_extensions_autoloaders() doesn't work very well with the extensions behind a symlink
  • +
  • [PHPBB3-13121] - Customise Purge Cache always returns user to Styles page
  • +
  • [PHPBB3-13125] - Uploaded avatars are not displayed in IE <= 7
  • +
+

Improvement

+
    +
  • [PHPBB3-12408] - Add quick setting for "default guest style" to ACP board settings
  • +
  • [PHPBB3-12985] - Add event to modify the redirect after a successful login
  • +
  • [PHPBB3-12993] - Add event to get_user_rank() function
  • +
  • [PHPBB3-13025] - Add template events for buttons on viewforum and viewtopic
  • +
  • [PHPBB3-13059] - Add core event to generate_page_link() to allow modifying pagination URLs
  • +
  • [PHPBB3-13072] - Add total_match_count to core.search_get_topic_data event
  • +
  • [PHPBB3-13077] - Change module order in Customise tab for better usability
  • +
  • [PHPBB3-13094] - Remove the search box if there are no new posts to search for
  • +
  • [PHPBB3-13107] - Add template events to forum row in forumlist_body.html
  • +
  • [PHPBB3-13108] - Add core event to the parse_attachments() function to allow modifying files info
  • +
  • [PHPBB3-13110] - Add core event to the page_footer() function to allow handling overall output
  • +
  • [PHPBB3-13122] - phpbb_wrapper_gmgetdate_test intermittently fails
  • +
  • [PHPBB3-13134] - Add core event to the root of the function display_forums()
  • +
  • [PHPBB3-13137] - Remove schema.json from repository
  • +
+

Sub-task

+
    +
  • [PHPBB3-12929] - Add an event when getting the information for PM composal
  • +
  • [PHPBB3-12930] - Add an event to the query getting the post for quoting in a PM
  • +
+

Task

+
    +
  • [PHPBB3-10794] - Make schema available at runtime
  • +
  • [PHPBB3-12987] - Cleanup the services.yml file
  • +
  • [PHPBB3-13106] - Remove the \phpbb\di\pass\kernel_pass class
  • +
  • [PHPBB3-13119] - Add events to mcp and acp ban modules
  • +
  • [PHPBB3-13123] - Add events to allow post blocking and post pre/past processing
  • +
+ +

Changes since 3.1.0-RC3

+ +

Bug

+
    +
  • [PHPBB3-10472] - ACP "add multiple smilies" page is unusable on 1024x768 resolution
  • +
  • [PHPBB3-11909] - phpbb/db/migrator::load_migrations is never called
  • +
  • [PHPBB3-12258] - Add attachment: error alert popup on "empty.png" does not show up
  • +
  • [PHPBB3-12506] - Long post titles bump the username down an extra row beneath the edit/quote/delete buttons
  • +
  • [PHPBB3-12658] - Add tests for config:* cli commands
  • +
  • [PHPBB3-12661] - Extensions templates not loaded from "all" by helper/render()
  • +
  • [PHPBB3-12734] - Custom profile manager should not suppress errors when inserting user rows
  • +
  • [PHPBB3-12765] - acp_profile.php should use db/tools
  • +
  • [PHPBB3-12852] - \phpbb\path_helper get_url_parts does not handle get variable with no value
  • +
  • [PHPBB3-12856] - plupload images are not integrated into style-path
  • +
  • [PHPBB3-12862] - Smiley and Whos Onlike pop-up boxes are making styling difficult
  • +
  • [PHPBB3-12949] - Undefined function mime_content_type()
  • +
  • [PHPBB3-12966] - Undefined sorting of posts with same post_time on postgres
  • +
  • [PHPBB3-12975] - Catchable fatal error after update to RC3
  • +
  • [PHPBB3-12976] - Pagination of UCP manage attachments page in prosilver does not support plural forms
  • +
  • [PHPBB3-12983] - UCP preferences, Display posts ordering by: input is not properly validated
  • +
  • [PHPBB3-12984] - Index page: blank line when no forum description shown
  • +
  • [PHPBB3-12986] - Wrong functions call order breaks detection of common words in search
  • +
  • [PHPBB3-12990] - The prefix for the notification's services is harcoded
  • +
  • [PHPBB3-12996] - tests/lock/flock_test.php should use microtime() instead of time()
  • +
  • [PHPBB3-12998] - Undefined $lang in mcp_warn.php::add_warning()
  • +
  • [PHPBB3-13004] - Topic tools button is displayed even if dropdown is empty
  • +
  • [PHPBB3-13008] - Importing a resource in routing.yml breaks download/file.php and ACP
  • +
  • [PHPBB3-13009] - Cleanup Tweaks CSS, Remove outdated browser support
  • +
  • [PHPBB3-13018] - Key binding on AJAX popups are not unbound upon close/cancel
  • +
  • [PHPBB3-13019] - Don't display "Soft delete reason" dialog when moderator cannot soft-delete
  • +
  • [PHPBB3-13022] - "Return to advanced search" wrong assumption
  • +
  • [PHPBB3-13031] - Impossible to properly upload image if no proper mimetype guessers enabled
  • +
  • [PHPBB3-13035] - Empty meta tags in header
  • +
  • [PHPBB3-13040] - W3C validator warning in overral_footer.html
  • +
  • [PHPBB3-13042] - Unused var in login_box()
  • +
  • [PHPBB3-13044] - Expires header violates RFC 2616
  • +
  • [PHPBB3-13046] - In download/avatar we don't load the vendors added by the extensions
  • +
  • [PHPBB3-13050] - Allow topic/forum subscription when email and jabber are off
  • +
  • [PHPBB3-13051] - Fix broken viewonline core event
  • +
  • [PHPBB3-13052] - Remove additional parameters from check_form_key()
  • +
  • [PHPBB3-13057] - Fatal error on previous page link after closing a report
  • +
  • [PHPBB3-13065] - Unknown column 'user_pass_convert' in 'field list' [code] - => 1054
  • +
  • [PHPBB3-13068] - Language correction in FIELD_IS_CONTACT_EXPLAIN
  • +
  • [PHPBB3-13069] - Timezone selector does not filter locations on change
  • +
+

Improvement

+
    +
  • [PHPBB3-12416] - WhoIs Pop Up Page Details
  • +
  • [PHPBB3-12598] - Improve action-bar search box styling
  • +
  • [PHPBB3-12653] - Caches the informations about the listeners to be able to not load them everytime
  • +
  • [PHPBB3-12900] - 3.1.0 avatar scaling
  • +
  • [PHPBB3-12943] - Add core.phpbb_generate_debug_output core event
  • +
  • [PHPBB3-12961] - Add link in anti-spam ACP page which links directly to captchas in titania
  • +
  • [PHPBB3-12969] - Add template event around the find username link on composing pm
  • +
  • [PHPBB3-12982] - JS in 3.1 is nasty
  • +
  • [PHPBB3-12989] - Remove unused "created_by.jpg" from prosilver
  • +
  • [PHPBB3-12991] - Have events after/before "add warning" field - user and post
  • +
  • [PHPBB3-12994] - Add core event to viewtopic.php before sending vars to template
  • +
  • [PHPBB3-13003] - Missing language keys in command "extension:show"
  • +
  • [PHPBB3-13005] - Add core event to display_forums() to modify category template data
  • +
  • [PHPBB3-13006] - Add variables to the 'core.modify_quickmod_actions' event
  • +
  • [PHPBB3-13010] - phpbb_get_avatar() incorrectly refers to \phpbb\avatar\driver\driver::clean_row()
  • +
  • [PHPBB3-13020] - Add var to core.viewforum_get_topic_data event to allow modifying forum template data
  • +
  • [PHPBB3-13024] - Template event viewtopic_body_postrow_post_content_footer
  • +
  • [PHPBB3-13026] - Add template vars array to core.viewonline_overwrite_location to allow modifying/adding template vars
  • +
  • [PHPBB3-13034] - Add ability to access extensions outside the phpBB root
  • +
  • [PHPBB3-13036] - Controller helper route method should be able to generate absolute URL's
  • +
  • [PHPBB3-13038] - Link user post tally to their posts
  • +
  • [PHPBB3-13047] - Add $post_list array to core.viewtopic_modify_page_title core event
  • +
  • [PHPBB3-13056] - Move the arguments of the request class to the DI
  • +
  • [PHPBB3-13058] - The "jump to page" input does not have a min attribute.
  • +
  • [PHPBB3-13061] - Use a compiler pass to replace the service event.subscriber_loader
  • +
  • [PHPBB3-13062] - Add viewforum.php core event to allow modifying sql query to select topic ids
  • +
  • [PHPBB3-13066] - Add common search results core event to modify search title
  • +
+

New Feature

+ +

Sub-task

+
    +
  • [PHPBB3-12928] - [Event] - core.mcp_reports_gather_query_before
  • +
+

Task

+ + + +

Changes since 3.1.0-RC2

+ +

Bug

+
    +
  • [PHPBB3-11148] - Fix uploading attachments from Android 4 powered devices
  • +
  • [PHPBB3-11480] - Prevent Private Message system from returning "Unknown folder" when inbox folder is full
  • +
  • [PHPBB3-11520] - Post count not incremented when you fork a topic
  • +
  • [PHPBB3-11854] - Captcha code is still in includes/
  • +
  • [PHPBB3-12232] - MCP Banning: Max execution time exceeded
  • +
  • [PHPBB3-12448] - Migration tools don't allow adding columns with default == NULL
  • +
  • [PHPBB3-12492] - DB_TEST: Special chars are not supported.
  • +
  • [PHPBB3-12535] - The profile link should be better adjusted to the avatar image it surrounds in viewtopic.php
  • +
  • [PHPBB3-12685] - CLI doesn't load extension commands
  • +
  • [PHPBB3-12710] - phpBB 3.0.12 on Oracle fails to upgrade to 3.1.0
  • +
  • [PHPBB3-12718] - Hard Deleting Post does not Decrease Post Count
  • +
  • [PHPBB3-12742] - Notifications duplicate code that requires an event
  • +
  • [PHPBB3-12744] - RTL board header is one line higher than LTR
  • +
  • [PHPBB3-12748] - UCP Terms of Use Typo
  • +
  • [PHPBB3-12770] - search_wordmatch unique key name is too long
  • +
  • [PHPBB3-12776] - docs/INSTALL.html is missing 3.0 to 3.1 update instructions
  • +
  • [PHPBB3-12789] - Cached directories are not deleted when the cache is purged (with ACM memory enabled).
  • +
  • [PHPBB3-12791] - String profile fields do not use links, smilies and line breaks in memberlist
  • +
  • [PHPBB3-12792] - Profile field type strings function get_profile_contact_value duplicates raw()
  • +
  • [PHPBB3-12793] - String '0' does not display for string profile fields
  • +
  • [PHPBB3-12794] - Google+ profile field validation is too strict
  • +
  • [PHPBB3-12809] - RTL styling issues with new jump-box dropdown
  • +
  • [PHPBB3-12814] - Error while trying to setup LDAP Authentication in ACP
  • +
  • [PHPBB3-12822] - Preselect avatar type if only 1 type is enabled
  • +
  • [PHPBB3-12837] - Viewing contact form displays "VIEWING_MEMBERS" on viewonline
  • +
  • [PHPBB3-12842] - Out of memory issue in code sniffer call for extensions
  • +
  • [PHPBB3-12843] - When you click on "Mark forums read" in the browser, a console error occurs
  • +
  • [PHPBB3-12844] - $dbpasswd is cleared too early in connection manager
  • +
  • [PHPBB3-12845] - HTML5 Invalid using role="navigation"
  • +
  • [PHPBB3-12846] - SQLite3 bug in profilefield_base_migration.php
  • +
  • [PHPBB3-12849] - ReferenceError in core.js
  • +
  • [PHPBB3-12851] - Font colour button title is not consistent with other buttons
  • +
  • [PHPBB3-12853] - Problems with ACP/MCP links in mobile design with other translations
  • +
  • [PHPBB3-12854] - Admin contact page should not be used when board emails are disabled
  • +
  • [PHPBB3-12855] - Container is being recompiled on every page request although DEBUG_CONTAINER is not set
  • +
  • [PHPBB3-12859] - Missing post button events from view pm template
  • +
  • [PHPBB3-12870] - Console database migrate function can not update from 3.0.12 to 3.1
  • +
  • [PHPBB3-12873] - Wrong identifier tested in \db\tools\sql_create_(unique_)index()
  • +
  • [PHPBB3-12875] - Extension's info_acp_lang files don't fallback correctly when the user's language is not included in the extension
  • +
  • [PHPBB3-12881] - Debug error - Undefined index: mark_time
  • +
  • [PHPBB3-12882] - $config['search_type'] - is not correctly updated when updating from 3.0 to 3.1
  • +
  • [PHPBB3-12883] - Do not use basename() to get the search class in phpbb\cron\task\core\tidy_search
  • +
  • [PHPBB3-12886] - Redundant lock and unlock expressions for QuickMod in viewtopic.php
  • +
  • [PHPBB3-12887] - Typo in timezone handling code: 'offest' should be 'offset'
  • +
  • [PHPBB3-12891] - Long page generation time when banlist is long
  • +
  • [PHPBB3-12895] - ?style=1 appended to url after using the UCP
  • +
  • [PHPBB3-12898] - In cron.php we try to release the lock after the call to garbage_collection()
  • +
  • [PHPBB3-12901] - Wrong type hint in show_available_child_styles() doc block
  • +
  • [PHPBB3-12902] - Remove duplicate entry in build_cfg_template() switch statement
  • +
  • [PHPBB3-12903] - Remove unused method in phpBB/phpbb/extension/metadata_manager.php
  • +
  • [PHPBB3-12908] - Fix broken operator assignment in increment.php
  • +
  • [PHPBB3-12909] - Use correct lang vars in cli extension enable
  • +
  • [PHPBB3-12910] - Profile fields: Two "simple text field" in dropdown menu
  • +
  • [PHPBB3-12912] - Undefined index when adding logs from extensions
  • +
  • [PHPBB3-12918] - Functional Tests Suite does not run on its own
  • +
  • [PHPBB3-12919] - Allow using class names as module identifier for extensions
  • +
  • [PHPBB3-12922] - Posts per page in MCP should have a minimum value of zero
  • +
  • [PHPBB3-12931] - General error on user registration when domain name is not set
  • +
  • [PHPBB3-12932] - Easy support of non-gregorian calendars
  • +
  • [PHPBB3-12937] - Without config file, extract($phpbb_config_php_file->get_all()); breaks
  • +
  • [PHPBB3-12938] - Board floods "store"-directory with packed attachments
  • +
  • [PHPBB3-12940] - Unused 'use' statements in download/file.php
  • +
  • [PHPBB3-12950] - Functional tests can not be run anymore if another language is present
  • +
  • [PHPBB3-12954] - Nginx setup on travis returns wrong SCRIPT_NAME info
  • +
  • [PHPBB3-12956] - Unknown column 'field_is_contact' in 'field list'
  • +
  • [PHPBB3-12964] - Undefined $row in download/file.php
  • +
  • [PHPBB3-12967] - Undefined variable: phpbb_dispatcher in mcp_queue.php
  • +
  • [PHPBB3-12970] - chema.json is not up to date with migration files
  • +
+

Improvement

+
    +
  • [PHPBB3-9165] - Extend template loop to allow iteration step modification
  • +
  • [PHPBB3-10073] - Contact page
  • +
  • [PHPBB3-12051] - Optimise Composer Autoloader on Build
  • +
  • [PHPBB3-12560] - Add methods to set upload and temp paths in Plupload class
  • +
  • [PHPBB3-12656] - Translate existing CLI commands
  • +
  • [PHPBB3-12663] - Extract Command Line Interface language strings into their own file.
  • +
  • [PHPBB3-12671] - Possibility to use NOT LIKE expression
  • +
  • [PHPBB3-12693] - Add a travis test that checks file permissions
  • +
  • [PHPBB3-12738] - Move related code from functions_posting into remove_post_from_statistic()
  • +
  • [PHPBB3-12778] - The automatic updater should have an option to automatically delete removed files during update
  • +
  • [PHPBB3-12828] - Add includes/ucp/ucp_prefs.php common core event to allow additional actions before the page load
  • +
  • [PHPBB3-12832] - Add footer links to the Quick Links menu
  • +
  • [PHPBB3-12836] - [Event] - core.functions.redirect
  • +
  • [PHPBB3-12841] - Allow extensions to position new config vars in an array
  • +
  • [PHPBB3-12847] - Allow extensions to define if they can be enabled
  • +
  • [PHPBB3-12857] - Add template events overall_header_breadcrumbs_before/after
  • +
  • [PHPBB3-12864] - Have an EVENT tag after STYLESHEETS in overall_header
  • +
  • [PHPBB3-12871] - Add PHPBB_DISPLAY_LOAD_TIME constant to config.php on installation
  • +
  • [PHPBB3-12872] - Add poster_id to viewtopic_modify_post_row
  • +
  • [PHPBB3-12884] - Add core event to the function upload_attachment()
  • +
  • [PHPBB3-12892] - S_NUM_ROWS does not scale
  • +
  • [PHPBB3-12896] - Add event in acp_main to allow php checks for admin notices
  • +
  • [PHPBB3-12906] - Add rel="help" to FAQ link
  • +
  • [PHPBB3-12913] - Add more parameters to core.submit_post_end event
  • +
  • [PHPBB3-12942] - Add core.add_form_key core event
  • +
  • [PHPBB3-12944] - Add core.login_forum_box core event
  • +
  • [PHPBB3-12953] - Page title not updated when notifications are marked as read
  • +
+

Sub-task

+
    +
  • [PHPBB3-12800] - [Event] - Create core.functions_display.display_user_activity.actives_after
  • +
  • [PHPBB3-12801] - [Event] - core.functions_posting.load_drafts_draft_list_results
  • +
  • [PHPBB3-12876] - [Event] - core.mcp_mcp_front.mcp_front_view_queue_postid_list_after
  • +
  • [PHPBB3-12880] - [Event] - core.mcp_queue_!is_topics_query_before
  • +
  • [PHPBB3-12927] - [Event] - core.mcp_queue_get_posts2_query_before
  • +
+

Task

+
    +
  • [PHPBB3-10404] - Remove create_function from includes/acp/auth.php
  • +
  • [PHPBB3-12557] - Fix doc block errors found by Sami
  • +
  • [PHPBB3-12826] - Investigate "catch (Exception"
  • +
  • [PHPBB3-12860] - Add template events to mcp_ban.html
  • +
  • [PHPBB3-12861] - Add event before assigning the posts to the template
  • +
  • [PHPBB3-12917] - Move commit check and file executable checks to 5.3.3 build on travis
  • +
  • [PHPBB3-12920] - Create composer installable app and core packages with subtree split
  • +
  • [PHPBB3-12941] - Check for Sami documentation errors on Travis CI
  • +
  • [PHPBB3-12948] - Remove Travis CI "broken opcache on PHP 5.5.7 and 5.5.8" workaround.
  • +
+ +

Changes since 3.1.0-RC1

+ +

Bug

+
    +
  • [PHPBB3-9801] - Users on custom pages outside the board directory are being displayed on Index page
  • +
  • [PHPBB3-11392] - "Make normal" in quickmod tool does not ajaxify correctly
  • +
  • [PHPBB3-12325] - Automatic update should notify about outdated files
  • +
  • [PHPBB3-12420] - Reduce config.php inclusions in DIC code
  • +
  • [PHPBB3-12446] - Unnecessary db connect - function phpbb_bootstrap_enabled_exts
  • +
  • [PHPBB3-12462] - Do not use string "None" for different avatar options
  • +
  • [PHPBB3-12515] - FULLTEXT_POSTGRES_TS_NOT_USABLE and FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN redundant
  • +
  • [PHPBB3-12605] - Make dropdowns uniform
  • +
  • [PHPBB3-12674] - Last edited by in PMs doesn't show user colour
  • +
  • [PHPBB3-12680] - Contact icons in viewtopic are missing alternative text
  • +
  • [PHPBB3-12695] - Undefined index: MISSING_INLINE_ATTACHMENT notice given when viewing post details
  • +
  • [PHPBB3-12716] - Wrong method call in phpbb\auth\provider\oauth\token_storage
  • +
  • [PHPBB3-12759] - Profile fields lang value db queries can cause query flood
  • +
  • [PHPBB3-12760] - Post approval icon for wrong topics in Last Post column
  • +
  • [PHPBB3-12764] - Wrong error message on install if mysqli is selected and the credentials are wrong
  • +
  • [PHPBB3-12766] - Event exporter does not like RCx as version
  • +
  • [PHPBB3-12768] - 'NOTIFICATION_REPORT_CLOSED' entry has wrong indentation
  • +
  • [PHPBB3-12771] - Bug in \phpbb\db\migration\profilefield_base_migration when used in extensions
  • +
  • [PHPBB3-12772] - Fatal error when "Email topic" is used in topic tools
  • +
  • [PHPBB3-12773] - Fix language variable name in cli extension enable command
  • +
  • [PHPBB3-12774] - Undefined variable $phpbb_adm_relative_path in phpbb_create_install_container()
  • +
  • [PHPBB3-12781] - Template regex for DEFINE has problems when enclosed by other template conditionals
  • +
  • [PHPBB3-12783] - Remove require: phpbb/phpbb from Extensions composer.json files
  • +
  • [PHPBB3-12787] - Incorrect generated url when using ajax and routing
  • +
  • [PHPBB3-12788] - Update Symfony suite from 2.3.12 to 2.3.16
  • +
  • [PHPBB3-12790] - Always use the interface when available (and not directly the class)
  • +
  • [PHPBB3-12802] - Properly handle connection errors in SQLite3
  • +
  • [PHPBB3-12808] - Small gap between username and drop-down arrow
  • +
  • [PHPBB3-12810] - In acp_forums the displayed value for prune_shadow_freq is the value of prune_freq
  • +
  • [PHPBB3-12811] - Margin Bottom not taking effect in Safari with 101% height on same element
  • +
  • [PHPBB3-12815] - Members list link hidden from guests who have permission to view members list page
  • +
  • [PHPBB3-12816] - Fix comment about logs in user_ban function
  • +
  • [PHPBB3-12818] - Deleting a log entry in MCP produces a General error
  • +
  • [PHPBB3-12819] - Wrong text on hover over "Jump to page" field
  • +
  • [PHPBB3-12825] - Allow the extensions to be tested with the sniffer and skip the vendors
  • +
  • [PHPBB3-12830] - .postlink is in colours.css doubled, one should be deleted
  • +
  • [PHPBB3-12834] - Viewonline only matches routes
  • +
  • [PHPBB3-12839] - Missing stylesheet when it is not updated
  • +
+

Improvement

+
    +
  • [PHPBB3-12013] - Quickmod tools and jumpbox should use new dropdown
  • +
  • [PHPBB3-12099] - path_helper returns wrong web root path for ajax requests
  • +
  • [PHPBB3-12196] - Referer vs Referrer in language files
  • +
  • [PHPBB3-12197] - Fix misleading FAQ entries
  • +
  • [PHPBB3-12334] - Add raw values of profile fields to template
  • +
  • [PHPBB3-12562] - Prosilver has no max-width
  • +
  • [PHPBB3-12645] - Update support links to 3.1 forums
  • +
  • [PHPBB3-12662] - Reorganize and modernize the header navbar
  • +
  • [PHPBB3-12735] - Remove all :link, :visited, :active link states
  • +
  • [PHPBB3-12777] - Rename extension status functions and add is_configured()
  • +
  • [PHPBB3-12779] - Change order of file lists on automatic update
  • +
  • [PHPBB3-12782] - Use an interface for the phpbb event_dispatcher
  • +
  • [PHPBB3-12784] - Allow the extensions to add a custom auto loader
  • +
  • [PHPBB3-12786] - Extend profilefield_base_migration.php class
  • +
  • [PHPBB3-12812] - Add a migrator tool for config_text database changes
  • +
  • [PHPBB3-12813] - Improve responsive pagination location and fix page-jump title
  • +
  • [PHPBB3-12823] - Remove trailing whitespace from CSS files
  • +
  • [PHPBB3-12824] - Move ACP & MCP links in the header to the end of the link list
  • +
  • [PHPBB3-12827] - Reorder quick-links
  • +
+

Task

+
    +
  • [PHPBB3-12775] - Refactor functions_container into class container_builder
  • +
  • [PHPBB3-12829] - Remove check for pgsql 8.3/8.2
  • +
+ +

Changes since 3.1.0-b4

+ +

Bug

+
    +
  • [PHPBB3-8610] - Splitting and merging topics does not handle subscriptions and bookmarks correctly
  • +
  • [PHPBB3-10899] - Using Delete All in log viewer with keyword search
  • +
  • [PHPBB3-11331] - Inform admin of incorrect avatar path instead of stripping unexpected parts from destination path
  • +
  • [PHPBB3-11445] - Suboptimal number of query complexity in phpbb_notification_manager::get_global_subscriptions()
  • +
  • [PHPBB3-11467] - phpbb_extension_metadata_manager uses hard coded language for exceptions
  • +
  • [PHPBB3-11659] - Information about file_upload is missing from the requirement page
  • +
  • [PHPBB3-11942] - Delete post/topic reason should be added to the generated log entry
  • +
  • [PHPBB3-12109] - Bug when setting users forum permissions with "Select all users" checkbox
  • +
  • [PHPBB3-12209] - OAuth Authentication in ACP does not explain that it allows regular login too
  • +
  • [PHPBB3-12332] - Attachments with long file names break the Uploader layout
  • +
  • [PHPBB3-12352] - The service definition "auth.provider.smf" does not exist.
  • +
  • [PHPBB3-12443] - Responsive tabs broken in IE
  • +
  • [PHPBB3-12457] - Gallery avatar category "Main" is empty
  • +
  • [PHPBB3-12508] - phpbb\finder (currently phpbb\extension\finder) should not depend on extension manager
  • +
  • [PHPBB3-12521] - When selecting the target to merge two topics, the last column is pushed on a new line
  • +
  • [PHPBB3-12552] - [RTL] - Forum list content forced into single char column when responsive
  • +
  • [PHPBB3-12553] - Right-to-left language bugs in prosilver
  • +
  • [PHPBB3-12566] - "Private messages" in viewtopic.php should be "Send pivate message"
  • +
  • [PHPBB3-12589] - The finder should search directly in $directory if it's an absolute sub-path
  • +
  • [PHPBB3-12612] - Front-facing files should not contain functions
  • +
  • [PHPBB3-12615] - Improper clearing of .topic-actions leads to float problems
  • +
  • [PHPBB3-12637] - coding-guidelines.html contains invalid HTML
  • +
  • [PHPBB3-12638] - Call to undefined function phpbb\db\migration\data\v30x\phpbb_require_updated() when not using autoupdate package
  • +
  • [PHPBB3-12639] - Delete entry in admin-log leads to mysql-error
  • +
  • [PHPBB3-12640] - Posting editor tab "attachments" is unclicked in IE8
  • +
  • [PHPBB3-12641] - With IE8 logged in to ACP error message appears in the browser
  • +
  • [PHPBB3-12643] - Cannot convert SQLite DB from 3.0.12 to 3.1.0
  • +
  • [PHPBB3-12652] - Deleting marked log entries leads to MySQL Error
  • +
  • [PHPBB3-12660] - Undefined offset error when phpinfo() disabled and debug enabled
  • +
  • [PHPBB3-12665] - develop/migration_tips.php should not find extension tips
  • +
  • [PHPBB3-12667] - New posts in a topic cause undesirable results when clicking topic title or page button
  • +
  • [PHPBB3-12672] - Make <Tab> inside [code] - more user friendly
  • +
  • [PHPBB3-12673] - PLUPLOAD fails due missing constant IMAGETYPE_SWC
  • +
  • [PHPBB3-12675] - Fix code sniffer complaints in 3.1.0-b5-dev code
  • +
  • [PHPBB3-12682] - Code Sniffer does not work correctly on Travis CI.
  • +
  • [PHPBB3-12688] - Rank images do not work on routes
  • +
  • [PHPBB3-12694] - Remove whitespace at end of line from acp_groups
  • +
  • [PHPBB3-12697] - Database Test Case Must Purge Extension Schema When Done
  • +
  • [PHPBB3-12705] - make_clickable is using static variables incorrectly
  • +
  • [PHPBB3-12707] - \phpbb\db\driver\phpbb\db\driver\mysql is tried being loaded
  • +
  • [PHPBB3-12708] - [ROOT] -/install/install_main.php still uses docs/COPYING
  • +
  • [PHPBB3-12709] - Duplicate entries in the posting attachments tab
  • +
  • [PHPBB3-12711] - Contact form migration fails on MSSQL
  • +
  • [PHPBB3-12712] - Password conversion migration fails on MSSQL
  • +
  • [PHPBB3-12715] - Mistakes in the doc blocks
  • +
  • [PHPBB3-12720] - Git commit hook should not require commit message to start with a capital letter
  • +
  • [PHPBB3-12728] - Enforce box-model for image attachments
  • +
  • [PHPBB3-12741] - Functional tests on Travis fail since php update last night
  • +
  • [PHPBB3-12746] - Failing test: tests/content_visibility/delete_post_test.php
  • +
  • [PHPBB3-12747] - Drop Firebird support
  • +
  • [PHPBB3-12750] - Install/Update footer is not centered anymore
  • +
  • [PHPBB3-12752] - Cron list tests fail on windows with ansi support
  • +
  • [PHPBB3-12753] - FIELD_INVALID_CHARS_ALPHA_PUNCTUATION is not localized
  • +
  • [PHPBB3-12755] - Remote upload stuck in infinite loop if server sends keep-alive
  • +
  • [PHPBB3-12756] - Fatal error with mysqli_fetch_assoc on hhvm
  • +
  • [PHPBB3-12761] - Remove the execute bit from functions_user.php
  • +
  • [PHPBB3-12762] - Make ext.php optional for extensions
  • +
  • [PHPBB3-12763] - Do not unnecessarily rewrite install/schemas/*_schema.sql when updating schema.json file
  • +
+

Improvement

+
    +
  • [PHPBB3-11266] - Use our own error message when composer is not set up
  • +
  • [PHPBB3-12532] - Add header_navbar_username_prepend/append template events.
  • +
  • [PHPBB3-12541] - Activate attachments tab when files are dropped into textarea
  • +
  • [PHPBB3-12563] - Language key names in ACP styles are misleading
  • +
  • [PHPBB3-12606] - PHP events in /includes/acp/acp_groups.php
  • +
  • [PHPBB3-12608] - Improve notifications drop-down menu styling in header
  • +
  • [PHPBB3-12613] - Improve pagination styling and jump-to-page dialog
  • +
  • [PHPBB3-12646] - Add data attribute to breadcrumb links
  • +
  • [PHPBB3-12648] - Improve UCP/MCP/Posting tabs styling & problems
  • +
  • [PHPBB3-12655] - Allow the CLI to be used as a shell
  • +
  • [PHPBB3-12664] - Refactor develop/migration_tips.php into a console command
  • +
  • [PHPBB3-12666] - Use "None" for images in gallery avatar path root and rename lang key
  • +
  • [PHPBB3-12668] - Add ability to modify subforums template data for core.display_forums_modify_template_vars event
  • +
  • [PHPBB3-12669] - Add core event to the function display_forums() for additional template data
  • +
  • [PHPBB3-12677] - Remove comment about ‘threading’
  • +
  • [PHPBB3-12687] - Add a constant to dissociate the displaying of the load time from DEBUG
  • +
  • [PHPBB3-12690] - Add core.submit_pm_after event
  • +
  • [PHPBB3-12691] - Add core.delete_pm to funtion delete_pm.
  • +
  • [PHPBB3-12704] - Improve the load time information in the footer when enabled
  • +
  • [PHPBB3-12706] - Ignore additional languages and styles from git
  • +
  • [PHPBB3-12714] - Better align the description of radio buttons to avoid confusion with the forum list in attachments settings
  • +
  • [PHPBB3-12740] - Stop Using IDs as CSS selectors
  • +
  • [PHPBB3-12758] - Add 'show_results' mode var to event core.search_modify_rowset
  • +
+

New Feature

+
    +
  • [PHPBB3-12597] - Command for executing all available cron tasks
  • +
  • [PHPBB3-12602] - Add command to print the cron list
  • +
  • [PHPBB3-12607] - CLI command for running a specific cron task
  • +
+

Sub-task

+ +

Task

+
    +
  • [PHPBB3-11528] - Use mink for acceptance tests involving javascript execution
  • +
  • [PHPBB3-12514] - Add additional tests for custom profile fields type
  • +
  • [PHPBB3-12696] - Add events to ucp_register.html
  • +
  • [PHPBB3-12701] - Add events to user_add function
  • +
  • [PHPBB3-12723] - Add Code Sniffer sniff ensuring PHP files use the correct file header
  • +
  • [PHPBB3-12726] - Add Code Sniffer sniff ensuring PHP files do not contain any unused use statements
  • +
  • [PHPBB3-12757] - Add a Code Sniffer ruleset for PHP files of phpBB extensions
  • +
+ + +

Changes since 3.1.0-b3

+ +

Bug

+
    +
  • [PHPBB3-10176] - Imageset Appearance Problem with Google Chrome Browser.
  • +
  • [PHPBB3-11226] - filespec::move_file() from functions_upload.php does not error correctly
  • +
  • [PHPBB3-11232] - prosilver ajax.js does not respect PHPBB_USE_BOARD_URL_PATH
  • +
  • [PHPBB3-11366] - Add "MOD Version Check" for extensions to the core
  • +
  • [PHPBB3-11497] - The extension finder keeps state, so should be instantiated on each container request, not reset manually
  • +
  • [PHPBB3-12025] - Post Preview no longer shows UNAUTHORISED_BBCODE warning for disallowed BBcodes
  • +
  • [PHPBB3-12074] - Enabling/Disabling/Data deleting of an extension should generate a log entry
  • +
  • [PHPBB3-12174] - "Download all attachments" link displayed when the topic has attachments in hidden posts only
  • +
  • [PHPBB3-12185] - Topic header in viewforum too small for translation
  • +
  • [PHPBB3-12270] - Approving a topic triggers wrong notification
  • +
  • [PHPBB3-12275] - core.modify_username_string is not triggered everytime
  • +
  • [PHPBB3-12332] - Attachments with long file names break the Uploader layout
  • +
  • [PHPBB3-12357] - generate_smilies() does not work for routes
  • +
  • [PHPBB3-12402] - CAPTCHA plugin migration fails to detect missing plugins
  • +
  • [PHPBB3-12415] - Use private message instead of "pm" accronym in cpf visibility options
  • +
  • [PHPBB3-12421] - We try to display attachments in feed even for users who don't have the right to see them
  • +
  • [PHPBB3-12428] - Incorrect from version in database update log entry
  • +
  • [PHPBB3-12434] - No error message with Plupload except for files without extension
  • +
  • [PHPBB3-12435] - purge_notifications() fails for disabled extensions
  • +
  • [PHPBB3-12440] - Change URL in browsers addressbar when a view=unread#unread link was used
  • +
  • [PHPBB3-12451] - posting.php TOO_FEW_CHARS_LIMIT should be split for plurals
  • +
  • [PHPBB3-12459] - Unapproved posts/topics are not correctly handled in feeds
  • +
  • [PHPBB3-12460] - Soft deleted posts/topics are not correctly handled in feeds
  • +
  • [PHPBB3-12461] - Statistics are wrong for topic's based feeds
  • +
  • [PHPBB3-12476] - purge_cache should increase asset version
  • +
  • [PHPBB3-12486] - "Risky" tests from phpunit 4.1
  • +
  • [PHPBB3-12491] - Conflict between USERNAME_FULL in functions.php and mcp_notes.php
  • +
  • [PHPBB3-12493] - User can not send PMs to users with PMs disabled, but PM button is visible on posts
  • +
  • [PHPBB3-12498] - IE8 displays avatar in header with wrong width
  • +
  • [PHPBB3-12499] - Incorrect call to phpbb\log\log::add() in db:migrate console command
  • +
  • [PHPBB3-12500] - user.img does not set a title attribute for resulting span
  • +
  • [PHPBB3-12501] - Weird post attachment behavior in MCP report details
  • +
  • [PHPBB3-12503] - All test cases should extend phpbb_test_case instead of PHPUnit_Framework_TestCase
  • +
  • [PHPBB3-12504] - Avatar manager test is using undefined variables
  • +
  • [PHPBB3-12509] - Extentions can't send email with new notification system
  • +
  • [PHPBB3-12510] - build_url() is not encoding url entities
  • +
  • [PHPBB3-12511] - Missing language strings in memberlist group view for mobile prosilver
  • +
  • [PHPBB3-12513] - simple_headers do not support extension loaded CSS stylesheets
  • +
  • [PHPBB3-12517] - Missing argument in call to log.add() in prune_shadow_topics.php
  • +
  • [PHPBB3-12519] - m_approve language update from soft delete patch got nucked by a merge conflict
  • +
  • [PHPBB3-12520] - Can not move text with the mouse anymore in post-box when attachments are allowed
  • +
  • [PHPBB3-12522] - Add parameter description in guesser_interface
  • +
  • [PHPBB3-12525] - CONTACT_USER used with username
  • +
  • [PHPBB3-12526] - "Undefined index: filesize" error thrown when editing a PM with attachments
  • +
  • [PHPBB3-12527] - Remove translation editor from ACP
  • +
  • [PHPBB3-12529] - phpbb\controller\resolver::getController doesn't use $phpbb_root_path to check if the template dir exist
  • +
  • [PHPBB3-12533] - The notification link should fill the parent li-container in the notifications dialog
  • +
  • [PHPBB3-12534] - Enabling and disabling extensions should not abuse the red errorbox
  • +
  • [PHPBB3-12536] - Get Versions Should Not Require Both Stable and Unstable Branches
  • +
  • [PHPBB3-12540] - WRONG_FILESIZE contains broken placeholders
  • +
  • [PHPBB3-12543] - Enter key no longer works in posting when attachment error is triggered
  • +
  • [PHPBB3-12547] - Rename jquery.js to jquery.min.js in assets directory
  • +
  • [PHPBB3-12548] - [RTL] - Posting button icons should display on left side of text
  • +
  • [PHPBB3-12549] - [RTL] - Forumlist/topiclist <dfn> tags should not be visible
  • +
  • [PHPBB3-12550] - [RTL] - Last post column breaks into second line
  • +
  • [PHPBB3-12551] - [RTL] - Breadcrumb separator does not display correctly
  • +
  • [PHPBB3-12561] - Create_schema_files.php should process colum "after" instead of adding it to the JSON
  • +
  • [PHPBB3-12570] - db_text throws an error, when set() is called with the current value
  • +
  • [PHPBB3-12572] - JavaScript console throws error when alert message title is not defined (core.js bug)
  • +
  • [PHPBB3-12580] - :link Pseudo causing over specificity issues in the theme
  • +
  • [PHPBB3-12586] - \phpbb\extension\manager::all_available() should only locate ext files two levels deep and ignore dot-files/folders
  • +
  • [PHPBB3-12594] - File headers, credit lines, etc. should reflect updated legal info
  • +
  • [PHPBB3-12600] - The cli command extension:show is broken
  • +
  • [PHPBB3-12604] - Notifications Dropdown Padding Broken When Empty
  • +
  • [PHPBB3-12611] - phpBB Group copyright notice should be removed from all images
  • +
  • [PHPBB3-12614] - Do not hide post buttons before hovering over post
  • +
  • [PHPBB3-12621] - schema.json is not up to date with migration files
  • +
+

Improvement

+
    +
  • [PHPBB3-9388] - use DOM scripting to hide unnecessary input fields
  • +
  • [PHPBB3-11163] - Include ext/ directory in installation and update packages
  • +
  • [PHPBB3-12155] - Use CSS instead of translated images for the mini post buttons
  • +
  • [PHPBB3-12407] - Allow changing of post_data and other variables with core.posting_modify_template_vars
  • +
  • [PHPBB3-12431] - Add has_poll icon to topiclists
  • +
  • [PHPBB3-12433] - QUOTE_DEPTH_EXCEEDED needs a different string for '1'
  • +
  • [PHPBB3-12488] - Add user warning indication to viewtopic posts
  • +
  • [PHPBB3-12507] - Add console command to purge the cache
  • +
  • [PHPBB3-12518] - Allow extensions to overwrite CANNOT_EDIT_* checks in posting.php and viewtopic.php
  • +
  • [PHPBB3-12523] - Add search_results.html template events search_results_topic_(before/after)
  • +
  • [PHPBB3-12524] - Add search.php core event to modify search results rowset
  • +
  • [PHPBB3-12531] - Restore default topic title links in subsilver2
  • +
  • [PHPBB3-12555] - Make use of canonical urls to avoid duplicate content
  • +
  • [PHPBB3-12583] - Add event core.mcp_warn_post_before/after
  • +
  • [PHPBB3-12584] - Add event core.mcp_warn_user_before/after
  • +
+

Sub-task

+ +

Task

+
    +
  • [PHPBB3-10839] - Remove phpunit.xml.functional and always include functional tests
  • +
  • [PHPBB3-12384] - Run Travis CI HHVM tests against MySQLi instead of MySQL
  • +
  • [PHPBB3-12495] - Add Sami to composer dependencies and build script
  • +
  • [PHPBB3-12544] - Update Plupload to 2.1.2
  • +
  • [PHPBB3-12582] - Strip away copyrighted ICC profile from images
  • +
  • [PHPBB3-12592] - Run mysql driver on Travis CI
  • +
  • [PHPBB3-12603] - Remove hook_system.html from docs
  • +
+ + +

Changes since 3.1.0-b2

+ +

Bug

+
    +
  • [PHPBB3-7707] - Missing occurrences of get_username_string
  • +
  • [PHPBB3-8323] - Banned User (PMs and Mails)
  • +
  • [PHPBB3-8558] - Board Emails not setting a correct email header
  • +
  • [PHPBB3-8700] - Language file "acp/styles.php" contains many unused language entries
  • +
  • [PHPBB3-8960] - Allow changing allow_avatar_remote when images/avatars/upload is not writable
  • +
  • [PHPBB3-10423] - Searching for the term "test *" will highlight nearly every word and displays htmlspecialchars as htmlentities.
  • +
  • [PHPBB3-10687] - UNABLE_GET_IMAGE_SIZE text misleading for remote avatars
  • +
  • [PHPBB3-10851] - HTML files containing certain tags being rejected as possible attack vectors with "Check attachment file" set to "No"
  • +
  • [PHPBB3-11098] - New persistent login keys list should have (un)select all and order options.
  • +
  • [PHPBB3-11339] - Using AJAX calls one after another
  • +
  • [PHPBB3-11352] - Disapproving topic takes you to quick reply for that topic
  • +
  • [PHPBB3-11431] - All topic notifications are deleted if one reply is edited and needs to be approved
  • +
  • [PHPBB3-11508] - General error "not allowed as quickmod" when changing the forum while merging two topics
  • +
  • [PHPBB3-11772] - New topic notification triggered when editing an existing post with post-approval enabled
  • +
  • [PHPBB3-11860] - .htaccess not working for Apache 2.4
  • +
  • [PHPBB3-11881] - Timezone migration can take a long time
  • +
  • [PHPBB3-11917] - "Manage external account" shows when not activated
  • +
  • [PHPBB3-11978] - Text field for topic-search
  • +
  • [PHPBB3-12004] - Support empty routes to app.php/ in path_helper
  • +
  • [PHPBB3-12012] - DB Tools should correctly remove columns that are part of indexes
  • +
  • [PHPBB3-12043] - Sort Extensions by Name in ACP Ext Mgr
  • +
  • [PHPBB3-12052] - Post edited by user on moderation queue is not marked as unapproved.
  • +
  • [PHPBB3-12083] - "Select all" selects nothing in Webkit Browsers with only one character in [code] -
  • +
  • [PHPBB3-12097] - The validate_data() function doesn't work with class method
  • +
  • [PHPBB3-12113] - Deleting warnings does not use plurals correctly
  • +
  • [PHPBB3-12121] - Update process doesn't preserve total redirects for links
  • +
  • [PHPBB3-12130] - Bullet character disappears on mouse-over in IE8
  • +
  • [PHPBB3-12186] - MCP should open "Reported posts" instead of PM Reports
  • +
  • [PHPBB3-12191] - UCP should open with global settings instead of notification settings
  • +
  • [PHPBB3-12193] - Broken HTML when an SQL error occurs during migration
  • +
  • [PHPBB3-12211] - Attachment file names are run through htmlspecialchars twice
  • +
  • [PHPBB3-12254] - Language switching on Registration page doesn't work for Extensions
  • +
  • [PHPBB3-12265] - Contact profile fields icons should be hidden in a dropdown
  • +
  • [PHPBB3-12286] - Fix coding guidelines
  • +
  • [PHPBB3-12331] - Fix DB error in update_profile_field_data() with disabled fields
  • +
  • [PHPBB3-12342] - Javascript Bugs and Fixes
  • +
  • [PHPBB3-12348] - Make create_schema_files.php runnable when phpBB is not installed yet
  • +
  • [PHPBB3-12350] - tests/extension/modules_test.php can not be run alone
  • +
  • [PHPBB3-12351] - Ajax "Mark topics read" does not give feedback
  • +
  • [PHPBB3-12353] - User attachments in ACP are not displaying every attachment
  • +
  • [PHPBB3-12354] - passwords_manager_test::test_unique_id fails from time to time
  • +
  • [PHPBB3-12355] - Topic Tools not updated fully updated when subscribing/bookmarking
  • +
  • [PHPBB3-12356] - Plupload does not load in PM editor
  • +
  • [PHPBB3-12358] - data-refresh not working as expected for routes
  • +
  • [PHPBB3-12359] - Day and Month of Birthday Misaligned When Editing
  • +
  • [PHPBB3-12360] - User is displayed twice in online list after second login
  • +
  • [PHPBB3-12362] - Infinite loop in schema generator if dependency can't be resolved
  • +
  • [PHPBB3-12367] - Travis fails in phpbb_wrapper_gmgetdate_test::test_gmgetdate()
  • +
  • [PHPBB3-12372] - dE() function does not toggle in ACP
  • +
  • [PHPBB3-12373] - Add to/from forum ids to LOG_MOVE entries
  • +
  • [PHPBB3-12375] - Attachment deletion broken after jQuery update
  • +
  • [PHPBB3-12378] - Prosilver common.css has duplicate entries
  • +
  • [PHPBB3-12379] - Plupload labels duplicated when responsive
  • +
  • [PHPBB3-12380] - “Remember Me” login keys are not sorted in UCP
  • +
  • [PHPBB3-12381] - Broken error message when selecting invalid DB driver
  • +
  • [PHPBB3-12382] - Template event listners can not access subloops when loop is defined in the original file
  • +
  • [PHPBB3-12386] - Add DEBUG_EXTRA again and use it for container creation
  • +
  • [PHPBB3-12388] - Log entries without log_data display language key instead of translated string
  • +
  • [PHPBB3-12391] - core.posting_modify_template_vars pass some variables to listeners
  • +
  • [PHPBB3-12395] - Pagination tests fail on travis with postgresql
  • +
  • [PHPBB3-12397] - db_tools::sql_unique_index_exists() has wrong doc block
  • +
  • [PHPBB3-12398] - Prune shadow topics tests fail due to wrong forum_last_post_id
  • +
  • [PHPBB3-12405] - create_user() in functional tests uses invalid timezone and no dateformat
  • +
  • [PHPBB3-12406] - Fix description of page_title var in core.viewtopic_modify_page_title
  • +
  • [PHPBB3-12412] - Styling issue with pagination numbering for smilies and icons
  • +
  • [PHPBB3-12413] - Fatal error "Call to undefined method phpbb\feed\*::fetch_attachments()" for topic based feeds
  • +
  • [PHPBB3-12418] - Notice displayed for feed.php
  • +
  • [PHPBB3-12422] - Log searches error due to plural arrays in language files
  • +
  • [PHPBB3-12429] - Update phpunit to 3.8+
  • +
  • [PHPBB3-12432] - Migrator should not automatically revert custom functions defined in update_data()
  • +
  • [PHPBB3-12436] - Functional test framework's add_style() should not use sql_multi_insert()
  • +
  • [PHPBB3-12444] - The logs message aren't filled correctly when some values are missing.
  • +
  • [PHPBB3-12455] - Remove unused strings EXTENSION_CONTROLLER_MISSING and EXTENSION_CLASS_WRONG_TYPE from common.php
  • +
  • [PHPBB3-12456] - Missing new lines at the end of file in language files
  • +
  • [PHPBB3-12467] - Add config_*.php and tests_config_*.php to .gitignore
  • +
  • [PHPBB3-12469] - Convert Timezone test fails because of outdated schema
  • +
  • [PHPBB3-12470] - Move commands from .travis.yml to separate files to allow reuse
  • +
  • [PHPBB3-12472] - Set fast finish for .travis.yml
  • +
  • [PHPBB3-12474] - The console command for updating/migrating the db should display the error with the <error> tag
  • +
  • [PHPBB3-12475] - Undefined variable $log in db:migrate console command
  • +
  • [PHPBB3-12477] - PM link no longer displays in viewtopic
  • +
  • [PHPBB3-12478] - ucp_pm_viewmessage_contact_fields_before/after missing in PM page
  • +
  • [PHPBB3-12480] - \phpbb\extension\finder is finding too many routing files
  • +
  • [PHPBB3-12482] - Undefined variable: data in viewtopic when not logged in
  • +
  • [PHPBB3-12485] - Broken tests due to absolute exclude
  • +
  • [PHPBB3-12494] - Undefined index: user_type on viewtopic.php
  • +
+

Improvement

+
    +
  • [PHPBB3-9758] - Make users avatar available to the template
  • +
  • [PHPBB3-10521] - Override Board Language via URL
  • +
  • [PHPBB3-11962] - Resize images to 100% with in viewtopic
  • +
  • [PHPBB3-12150] - Automatically prune shadow topics
  • +
  • [PHPBB3-12201] - Clean up ACP attachment management page
  • +
  • [PHPBB3-12273] - Add a test to run the event exporter on travis
  • +
  • [PHPBB3-12282] - Add an interface for dbal driver to ensure that functions are there
  • +
  • [PHPBB3-12283] - Online status on posting review page.
  • +
  • [PHPBB3-12322] - Add CSS classes for post-profile <dd> elements
  • +
  • [PHPBB3-12323] - Add Template Event search_results_author_prepend
  • +
  • [PHPBB3-12327] - Changing poll result-bars width from absolute % to relative
  • +
  • [PHPBB3-12328] - Add Template Event index_body_stat_blocks_after
  • +
  • [PHPBB3-12329] - Add <div> container to index blocks (online-list, birthday-list, statistics)
  • +
  • [PHPBB3-12333] - Add Template Event overall_header_page_body_before
  • +
  • [PHPBB3-12335] - Add Events to phpbb\profilefields\manager (grab & show)
  • +
  • [PHPBB3-12336] - Add functions_module.php core events to allow adjusting parameters for custom ACP, MCP, UCP modules
  • +
  • [PHPBB3-12337] - Update jQuery to version 1.11.0
  • +
  • [PHPBB3-12338] - Add Template Event overall_footer_page_body_after
  • +
  • [PHPBB3-12339] - Add Event core.page_header_after
  • +
  • [PHPBB3-12344] - Add event to submit_pm()
  • +
  • [PHPBB3-12345] - Improve search flood interval message
  • +
  • [PHPBB3-12346] - Add Template Event overall_header_navlink_append/prepend
  • +
  • [PHPBB3-12347] - Move breadcrumb seperator from template to CSS
  • +
  • [PHPBB3-12361] - Replace the Google logo with the phpBB logo in the BBCode FAQ
  • +
  • [PHPBB3-12364] - Add template identifier var to all missing pages
  • +
  • [PHPBB3-12365] - Do not crop image attachment heights at 350px
  • +
  • [PHPBB3-12366] - Add Event core.search_get_posts_data
  • +
  • [PHPBB3-12369] - Add template variable for extensions to add classes to <body> element without JS
  • +
  • [PHPBB3-12374] - Add Template events index_body_block_<blockname>_append
  • +
  • [PHPBB3-12376] - Add template events viewtopic_body_polls
  • +
  • [PHPBB3-12377] - Move navbars to separate template files
  • +
  • [PHPBB3-12389] - Move "print topic" & "email topic" icons (and PM versions) to topic tools
  • +
  • [PHPBB3-12392] - Include $profile_fields in core.memberlist_view_profile event
  • +
  • [PHPBB3-12396] - Add Template events viewforum_forum_name_append/prepend
  • +
  • [PHPBB3-12400] - Add viewforum.php core event to allow modifying topics data before display the page
  • +
  • [PHPBB3-12401] - Add $topic_data array to core.viewtopic_modify_post_row event in viewtopic.php
  • +
  • [PHPBB3-12403] - Add template events to acp_users_prefs.html
  • +
  • [PHPBB3-12409] - Add acp_users.php core events to modify users preferences data
  • +
  • [PHPBB3-12410] - Add Template events search_results_post_before/after
  • +
  • [PHPBB3-12411] - Expand dispatch vars of event: core.search_modify_tpl_ary
  • +
  • [PHPBB3-12419] - Improve font size in notifications drop-down
  • +
  • [PHPBB3-12437] - Clean up redundant "clear" elements & "corners"
  • +
  • [PHPBB3-12438] - Add Template event memberlist_view_content_prepend
  • +
  • [PHPBB3-12442] - Add CSS classes to heading elements
  • +
  • [PHPBB3-12473] - Add console command for updating/migrating database
  • +
  • [PHPBB3-12484] - Template event ucp_agreement_terms_before/after
  • +
+

New Feature

+ +

Sub-task

+
    +
  • [PHPBB3-12349] - License in migrations header not linking to version 2 of GNU GPL
  • +
  • [PHPBB3-12370] - Editing a post removes topic notifications
  • +
  • [PHPBB3-12371] - Notifications are incorrectly updated when a post is deleted or moved to ModerationQueue
  • +
+

Task

+
    +
  • [PHPBB3-12071] - Test suite fails if Fileinfo isn't installed
  • +
  • [PHPBB3-12199] - Move deprecated functions to functions_compatibility.php
  • +
  • [PHPBB3-12318] - Correctly setup HHVM functional tests on Travis CI
  • +
  • [PHPBB3-12320] - No longer allow Travis CI HHVM environment to fail
  • +
  • [PHPBB3-12341] - Add tests for get_username_string()
  • +
  • [PHPBB3-12390] - Released packages MUST NOT contain vendor tests or other non-library code
  • +
  • [PHPBB3-12417] - hhvm-nightly 2014.04.16~precise breaks tests
  • +
  • [PHPBB3-12423] - Increase composer minimum-stability from beta to stable
  • +
  • [PHPBB3-12424] - Update Symfony Dependencies to latest 2.3 releaes
  • +
  • [PHPBB3-12458] - Apply Squiz.WhiteSpace.SuperfluousWhitespace.* sniffs to legacy codebase
  • +
+ + +

Changes since 3.1.0-b1

+ +

Bug

+
    +
  • [PHPBB3-8309] - Rename "Last visited" to "Last activity" on memberlist/viewprofile
  • +
  • [PHPBB3-9040] - Count HTML entities as single characters in Custom Profile fields
  • +
  • [PHPBB3-9725] - MSSQL Schema is not azure compatible
  • +
  • [PHPBB3-10769] - "ACP Access Denied" view includes ajax parts
  • +
  • [PHPBB3-11071] - User selection of an invalid style causes a general error
  • +
  • [PHPBB3-11357] - Invalid markup in installer
  • +
  • [PHPBB3-11959] - List of users in notifications should be trimmed
  • +
  • [PHPBB3-12031] - Bug language Notification list should trim users
  • +
  • [PHPBB3-12127] - Possible NOTIFICATION_QUOTE_TRIMMED miswording
  • +
  • [PHPBB3-12160] - Convert Page throws "invalid dbms driver" when no phpBB installation is present
  • +
  • [PHPBB3-12257] - MySQL Fulltext Tests are not run on Travis CI
  • +
  • [PHPBB3-12281] - Disable redis in travis setup on develop until redis fixes it
  • +
  • [PHPBB3-12292] - Buttons are mis-matched, unaligned in ACP Style Details
  • +
  • [PHPBB3-12293] - Core event listeners not working in log.php
  • +
  • [PHPBB3-12294] - phpbb_ in profile fields column names is incorrectly replaced with the table prefix
  • +
  • [PHPBB3-12297] - user_from column not deleted on update to b1
  • +
  • [PHPBB3-12310] - SMTP username and password should not autocomplete during install
  • +
  • [PHPBB3-12311] - Metadata Manager should require license instead of licence
  • +
  • [PHPBB3-12314] - HHVM SPL autoloader sometimes passes class name with leading backslash
  • +
  • [PHPBB3-12315] - NO_SEARCH_INDEX in language/en/acp/common.php uses invalid HTML
  • +
  • [PHPBB3-12316] - develop-ascraeus build status missing from "Automated Testing" section in README.md
  • +
  • [PHPBB3-12317] - Some notification tests fail on DBMS drivers using appropriate integer typing (MSSQL, Sqlite3, also MySQL on HHVM)
  • +
  • [PHPBB3-12321] - Remove execute bit from file ucp_main_subscribed.html and buttons.png in prosilver
  • +
  • [PHPBB3-12324] - Can not update from b1 to b2 because of missing install/update/new/config/ folder
  • +
  • [PHPBB3-12326] - Error while updating from b1 to b2 because of incomplete update files
  • +
  • [PHPBB3-12330] - Installation fails on MSSQL
  • +
+

Improvement

+
    +
  • [PHPBB3-10174] - Rename "Ban usernames" to "Ban users" in ACP
  • +
  • [PHPBB3-10590] - Skip confirmation page after successful posting of an approved post
  • +
  • [PHPBB3-11169] - Move prune users from security to users category
  • +
  • [PHPBB3-11230] - Use @inheritdoc in docblocks in cache drivers
  • +
  • [PHPBB3-11239] - Include username before the Overview title.
  • +
  • [PHPBB3-11336] - Rename mode parameter from "leaders" to something more accurate for the "The team" page
  • +
  • [PHPBB3-11360] - page_header() argument "display_online_list" should be FALSE by default
  • +
  • [PHPBB3-11666] - POST_DELETED should refer to posts instead of messages
  • +
  • [PHPBB3-12035] - Add a link to user's posts in the ACP user overview page
  • +
  • [PHPBB3-12198] - Unused ERR_TEMPLATE_EVENT_* language strings in en/common.php
  • +
  • [PHPBB3-12268] - Extension finder should not crawl through .git/ of extensions
  • +
  • [PHPBB3-12269] - Delete "mods" folder from the language folder
  • +
  • [PHPBB3-12274] - Updater is using old version of adm/style/admin.css
  • +
  • [PHPBB3-12276] - Expand core.memberlist_view_profile event to include Zebra state
  • +
  • [PHPBB3-12278] - Add mcp.php core event to allow setting display options for custom MCP modules
  • +
  • [PHPBB3-12284] - Make Extension Details page more readable
  • +
  • [PHPBB3-12289] - Add viewtopic_body.html template event to allow custom post notices
  • +
  • [PHPBB3-12300] - Revert and Reimagine Link to last read posts
  • +
  • [PHPBB3-12304] - Add CSS class to rules-link container
  • +
  • [PHPBB3-12308] - Add Template Event forumlist_body_last_row_after
  • +
  • [PHPBB3-12309] - Add Template Event quickreply_editor_panel_before/after
  • +
+

New Feature

+
    +
  • [PHPBB3-7580] - Allow isset() check in IF template syntax
  • +
  • [PHPBB3-12298] - Template Event memberlist_view_contact_before/after
  • +
  • [PHPBB3-12301] - Template Event overall_header_body_before
  • +
+

Task

+ + +

Changes since 3.1.0-a3

+ +

Bug

+
    +
  • [PHPBB3-8041] - Do not concatenate the destination to L_RETURN_TO to allow better translations
  • +
  • [PHPBB3-8122] - Reviewing topics/posts - not correctly displayed
  • +
  • [PHPBB3-8785] - PM recipients/bcc list looks bad
  • +
  • [PHPBB3-8919] - Localisation imageset being refreshed too frequently causing broken images
  • +
  • [PHPBB3-9106] - Upload fails to complete for large files.
  • +
  • [PHPBB3-9420] - BBCode - Unable to use a proper URI token
  • +
  • [PHPBB3-9459] - Visiting ACP with screen resolution 800x600px
  • +
  • [PHPBB3-10175] - Class loader cannot find a/b_foo.php when a/b/bar.php exists
  • +
  • [PHPBB3-10338] - Attachments list is displayed incorrectly
  • +
  • [PHPBB3-10722] - Code Coverage generation fails
  • +
  • [PHPBB3-11038] - Does not correctly find module info classes using new naming conventions
  • +
  • [PHPBB3-11146] - MCP topic/post approval error
  • +
  • [PHPBB3-11151] - Can use negative start for memberlist.php
  • +
  • [PHPBB3-11218] - File upload functional test fails
  • +
  • [PHPBB3-11255] - Some tests do not run standalone due to dbal dependency on global $cache
  • +
  • [PHPBB3-11271] - Images in ATOM feed display, but attached inline images do not.
  • +
  • [PHPBB3-11288] - "Fulltext native" search fooled by hyphens
  • +
  • [PHPBB3-11364] - Add an easy-to-switch system for app.php?controller= vs mod-rewrite usage in append_sid
  • +
  • [PHPBB3-11429] - Extensions should increment assets number on enable
  • +
  • [PHPBB3-11581] - General Error in UCP if PMs are disabled in ACP
  • +
  • [PHPBB3-11625] - General Error accessing MCP from Global Announcement
  • +
  • [PHPBB3-11648] - Test suite creates files in phpBB directory
  • +
  • [PHPBB3-11787] - Permission language strings use samp for highlight instead of strong
  • +
  • [PHPBB3-11880] - Schema changes can take too long and cause a timeout
  • +
  • [PHPBB3-11938] - Use of deprecated sql_attr_str2ordinal in sphinx.conf
  • +
  • [PHPBB3-12003] - ACP broken when error thrown
  • +
  • [PHPBB3-12072] - Missing word "send" in comment in schema_data.sql
  • +
  • [PHPBB3-12078] - "Can permanently delete own posts" permission has no effect
  • +
  • [PHPBB3-12080] - Responsive design in ACP / User Administration / Signature needs fixing
  • +
  • [PHPBB3-12093] - IE 11 javascript selection is no longer supported
  • +
  • [PHPBB3-12126] - Alert box is aligned to the left instead of center in IE
  • +
  • [PHPBB3-12132] - viewtopic_print_head_append template event is missing in subsilver2
  • +
  • [PHPBB3-12134] - ucp_pm_viewmessage_print_head_append template event is missing in subsilver2
  • +
  • [PHPBB3-12136] - Call to undefined function generate_pagination() in functions_posting.php
  • +
  • [PHPBB3-12138] - Information and pagination of filtered attachments
  • +
  • [PHPBB3-12139] - Spaces missing after $show_guests in viewonline.php
  • +
  • [PHPBB3-12141] - Travis tests fail on 5.5
  • +
  • [PHPBB3-12149] - Division by zero in [ROOT] -/phpbb/pagination.php
  • +
  • [PHPBB3-12153] - PAGE_NUMBER should be assigned by pagination.generate_template_pagination()
  • +
  • [PHPBB3-12158] - pagination.validate_start() returns negative value when $num_items = 0
  • +
  • [PHPBB3-12168] - Nav Header Links Alignment Issues without small-icon
  • +
  • [PHPBB3-12170] - Migration helper replaces table name with 0 when creating tables
  • +
  • [PHPBB3-12171] - Attachments from soft-deleted posts are still downloadable
  • +
  • [PHPBB3-12172] - "Download all attachments" feature leaks post/pm attachments when being able to download one of them
  • +
  • [PHPBB3-12175] - Fix and run functional upload tests
  • +
  • [PHPBB3-12176] - No error shown when attempting to delete a founder
  • +
  • [PHPBB3-12183] - Update user_newpasswd column in users table for passwords manager
  • +
  • [PHPBB3-12184] - Undefined variable tpl_fields in profile field manager on memberlist
  • +
  • [PHPBB3-12188] - Add php 5.6 to travis tests
  • +
  • [PHPBB3-12192] - Avatars in PM report closed notifications are broken
  • +
  • [PHPBB3-12194] - Unknown column 'field_show_novalue' in 'field list' [1054] -
  • +
  • [PHPBB3-12200] - Profile field template files missing in adm/style
  • +
  • [PHPBB3-12202] - Variables read from style.cfg etc. should be htmlspecialchared
  • +
  • [PHPBB3-12203] - user_occ does not have a default value when registering
  • +
  • [PHPBB3-12205] - Custom Profile Field display bug
  • +
  • [PHPBB3-12206] - Errors in plupload doesn't pull lang vars right
  • +
  • [PHPBB3-12207] - prosilver layout breaks when error outputted before <html>
  • +
  • [PHPBB3-12208] - Plupload uploads files even if they were deleted from queue
  • +
  • [PHPBB3-12210] - dbtools::sql_create_table incorrectly throws error related to auto-increment length on non auto-increment fields
  • +
  • [PHPBB3-12212] - HTML in attachment file name rendered before upload
  • +
  • [PHPBB3-12216] - Undefined index: lang_options when creating date profile field
  • +
  • [PHPBB3-12220] - Debug message if no unread messages are present
  • +
  • [PHPBB3-12222] - Replace hardoded comma with translatable separator in forumlist_body.html
  • +
  • [PHPBB3-12223] - Pagination displayes additional &bull; when PAGE_NUMBER is empty
  • +
  • [PHPBB3-12226] - Incorrectly used plurals in ucp.php MOVE_PM_ERROR and FOLDER_MESSAGE_STATUS
  • +
  • [PHPBB3-12227] - "X from Y messages" should be "X out of Y messages"
  • +
  • [PHPBB3-12243] - subsilver2 is missing profile field files
  • +
  • [PHPBB3-12245] - "Invalid id refernce" for label "joined" and "group_id" in acp_prune_users.html
  • +
  • [PHPBB3-12249] - Undefined variable: row when editing profile
  • +
  • [PHPBB3-12250] - Remove deprecated phpbb_clean_path function
  • +
  • [PHPBB3-12251] - Clean up and Enhancement of Custom Profile Fields
  • +
  • [PHPBB3-12256] - phpbb\notification\type\admin_activate_user uses $sql as fetchrow parameter
  • +
  • [PHPBB3-12261] - Login redirect from extension front controllers broken
  • +
  • [PHPBB3-12263] - \phpbb\db\migration\tool\module.php contains several errors
  • +
  • [PHPBB3-12267] - Functional testcase method get_extension_manager() uses undefined variable php_ext
  • +
  • [PHPBB3-12271] - Decouple dropdown header/footer from #notification_list
  • +
  • [PHPBB3-12272] - Hardcoded colon in subforums list
  • +
+

Improvement

+
    +
  • [PHPBB3-9255] - Friends Sidebar has scability issues..
  • +
  • [PHPBB3-9479] - Empty paragraphs for vertical spacing are rather old-fashioned
  • +
  • [PHPBB3-9709] - Missing language files for MCP could be substituted by default / english ones
  • +
  • [PHPBB3-9871] - Update version check file to use json format
  • +
  • [PHPBB3-10288] - Templates including other templates in loop can't access variables
  • +
  • [PHPBB3-10549] - Languages variables should be used, not hardcoded
  • +
  • [PHPBB3-10555] - Copyright notice in overall_header.html is not translatable
  • +
  • [PHPBB3-10945] - Show entered search query in the search box when no results are found.
  • +
  • [PHPBB3-11019] - Ability to store array of data in a log
  • +
  • [PHPBB3-11022] - Add "Tahoma" to used font families in css files
  • +
  • [PHPBB3-11040] - PostgreSQL fulltext search improvement
  • +
  • [PHPBB3-11111] - Allow only vertical resize on the textarea.
  • +
  • [PHPBB3-11179] - change the start parameter in search when out of bounds to display last(or first) page
  • +
  • [PHPBB3-11201] - MSNM / WLM closing - redundant profile field
  • +
  • [PHPBB3-11254] - Check CRLF line endings in the test suite
  • +
  • [PHPBB3-11297] - Running tests doc should mention dbunit dependency
  • +
  • [PHPBB3-11610] - Use a more secure hashing method like bcrypt
  • +
  • [PHPBB3-11645] - Rename ".MODs" tab in the ACP
  • +
  • [PHPBB3-11693] - Change phpbb_db_driver::sql_build_array() to support DELETE
  • +
  • [PHPBB3-11716] - Migration to convert soft delete/trash bin mods to 3.1 soft delete
  • +
  • [PHPBB3-12029] - Module names in the ACP should not be truncated when they are too long
  • +
  • [PHPBB3-12073] - Small text size in ACP between <samp></samp> and <code></code> tags
  • +
  • [PHPBB3-12106] - Document exceptions to "Disable Board" in ACP.
  • +
  • [PHPBB3-12114] - Convert current profile fields to custom-profile-field system
  • +
  • [PHPBB3-12117] - Nested Set Tree Classes Should be able to get all roots
  • +
  • [PHPBB3-12166] - Add template event "quickreply_editor_message_box_before"
  • +
  • [PHPBB3-12167] - Allow users to change style via style parameter by default
  • +
  • [PHPBB3-12190] - Add core event "core.modify_submit_post_data"
  • +
  • [PHPBB3-12217] - Add template events to viewtopic_body.html
  • +
  • [PHPBB3-12224] - Add template method to assign block arrays
  • +
  • [PHPBB3-12231] - Add template events to forumlist_body.html
  • +
  • [PHPBB3-12238] - There is no cancel input type.
  • +
  • [PHPBB3-12239] - Move deprecated password functions to compatibility file
  • +
  • [PHPBB3-12240] - Adding specific class names to buttons in posting_buttons.html
  • +
  • [PHPBB3-12241] - Event to add and/or modify acp_board configurations
  • +
  • [PHPBB3-12244] - Remove ambiguity between Extension Tab and Extensions Management links
  • +
  • [PHPBB3-12248] - Extension Details Homepage should be a clickable link
  • +
  • [PHPBB3-12259] - Too many redundant tests are run on Travis
  • +
  • [PHPBB3-12260] - Add core event to delete_posts() function
  • +
  • [PHPBB3-12262] - Adjust a script to export the events in wiki format
  • +
+

Sub-task

+
    +
  • [PHPBB3-12115] - Convert occupation/interests profile field to custom field
  • +
  • [PHPBB3-12169] - Convert location user field to profile field
  • +
  • [PHPBB3-12187] - Convert website user field to profile field
  • +
  • [PHPBB3-12233] - Allowing CPF to be have an icon on viewtopic and be listed in the contact section of the profile
  • +
  • [PHPBB3-12234] - Convert ICQ user field to profile field
  • +
  • [PHPBB3-12235] - Convert MSN/WLM user field to profile field
  • +
  • [PHPBB3-12236] - Convert AOL user field to profile field
  • +
  • [PHPBB3-12237] - Convert Yahoo user field to profile field
  • +
+

Task

+
    +
  • [PHPBB3-10763] - Audit code for non-static functions called statically
  • +
  • [PHPBB3-10793] - Use db_tools in create_schema_files
  • +
  • [PHPBB3-11509] - Travis should check commit message format
  • +
  • [PHPBB3-11764] - Remove Subsilver2 from installation packages
  • +
  • [PHPBB3-12177] - Add event ucp_zebra_friend_list_before/after
  • +
  • [PHPBB3-12180] - CodeSniffer: Ensure each file ends with a single newline
  • +
+ + +

Changes since 3.1.0-a2

+ +

Bug

+
    +
  • [PHPBB3-10810] - Manage group in ucp requires access to adm/
  • +
  • [PHPBB3-11246] - Misleading Message in New User Pruning Feature
  • +
  • [PHPBB3-11484] - Topic Reply Notification email links not requiring user to log in
  • +
  • [PHPBB3-11507] - Impossible to prune users by group only
  • +
  • [PHPBB3-11672] - AJAX alerts contain unnecessary messages/links
  • +
  • [PHPBB3-11709] - Bulletin points in navigation menu are misaligned in RTL languages
  • +
  • [PHPBB3-11842] - Avatar driver error thrown when creating group
  • +
  • [PHPBB3-11856] - Require composer.json for extensions
  • +
  • [PHPBB3-11859] - Avatar drivers should return template filename
  • +
  • [PHPBB3-11869] - Strict debug message when registering with profile fields displayed
  • +
  • [PHPBB3-11911] - ACP should warn if there is no search index created for the selected search engine
  • +
  • [PHPBB3-11912] - "Unable to guess the mime type as no guessers are available" when uploading files
  • +
  • [PHPBB3-11914] - plupload should be updated to 2.0.x
  • +
  • [PHPBB3-11915] - plupload should be restyled to match the design of prosilver better
  • +
  • [PHPBB3-11959] - List of users in notifications should be trimmed
  • +
  • [PHPBB3-11963] - Stale MCP notifications get left behind
  • +
  • [PHPBB3-11984] - Ticket for more bugs/improvements for responsive design for both prosilver and ACP
  • +
  • [PHPBB3-11988] - Active tab in ACP should not have hover effect
  • +
  • [PHPBB3-11997] - redirect() does not work for app.php/xyz pages
  • +
  • [PHPBB3-12001] - User input is ignored in AJAX confirmation forms
  • +
  • [PHPBB3-12006] - Disabled Extensions with Modules can break the ACP/UCP/MCP
  • +
  • [PHPBB3-12009] - Incorrectly installed extensions should be ignored
  • +
  • [PHPBB3-12011] - RTL languages have style bugs in prosilver/ACP
  • +
  • [PHPBB3-12015] - Enhance events in viewtopic to increase their usability
  • +
  • [PHPBB3-12026] - Unable to find template - in MCP/UCP, which are added via extensions
  • +
  • [PHPBB3-12027] - Redis tests failing
  • +
  • [PHPBB3-12028] - Don't use L_COLON in a JavaScript context
  • +
  • [PHPBB3-12030] - Include config directory completely in update packages when one file is being updated
  • +
  • [PHPBB3-12032] - Certain notifications incorrectly inherit read status from post/topic
  • +
  • [PHPBB3-12036] - Module Management items have 2 move down buttons and no up
  • +
  • [PHPBB3-12038] - Move up/down buttons are not AJAXified in various ACP pages.
  • +
  • [PHPBB3-12040] - ACP tabs flicker when AJAX background appears
  • +
  • [PHPBB3-12042] - Several strings of plupload are not being translated
  • +
  • [PHPBB3-12045] - Several HTML markup issues in adm/style/acp_prune_users.html
  • +
  • [PHPBB3-12049] - PLUPLOAD_ERR_UPLOAD_LIMIT should be using plurals
  • +
  • [PHPBB3-12050] - phpbb_notification_submit_post_base should be abstract
  • +
  • [PHPBB3-12055] - ACP BBCodes/Smilies Row Color Changes Needed After AJAX operations
  • +
  • [PHPBB3-12058] - Updating database fails in migration v310/avatar_types.php
  • +
  • [PHPBB3-12059] - ACP INCLUDECSS not working, missing {$STYLESHEETS}
  • +
  • [PHPBB3-12062] - INCLUDEJS broken for extension events in the ACP
  • +
  • [PHPBB3-12063] - <tr> tags use invalid valign attribute
  • +
  • [PHPBB3-12077] - General Error thrown when submitting ACP language pack editor
  • +
  • [PHPBB3-12079] - request.untrimmed_variable() $multibyte param does not have default value
  • +
  • [PHPBB3-12091] - Plupload - File upload does not work in Internet Explorer 11
  • +
  • [PHPBB3-12092] - Plupload - File info missing for several uploaded files
  • +
  • [PHPBB3-12095] - IE11 JavaScript plupload error
  • +
  • [PHPBB3-12096] - Not using plurals correctly in search.php MAX_NUM_SEARCH_KEYWORDS_REFINE
  • +
  • [PHPBB3-12100] - ACP Template files not purged during Extension Enable/Disable
  • +
  • [PHPBB3-12107] - Log table name is hard-coded in log.get_logs()
  • +
  • [PHPBB3-12110] - Update Plupload to 2.1.1
  • +
  • [PHPBB3-12123] - No category specified for soft-delete permission
  • +
  • [PHPBB3-12124] - Plupload broken on IE8
  • +
  • [PHPBB3-12125] - Topic rows are missing background in IE8
  • +
  • [PHPBB3-12131] - prosilver viewtopic print view has invalid imageset var
  • +
  • [PHPBB3-12140] - Avoid endless loop in build script
  • +
  • [PHPBB3-12141] - Travis tests fail on 5.5
  • +
  • [PHPBB3-12151] - Class 'phpbb\profilefields\profilefields' not found
  • +
  • [PHPBB3-12154] - DB Schema not built with develop script
  • +
  • [PHPBB3-12156] - Passwords Manager ~ Bug with OAuth
  • +
  • [PHPBB3-12159] - Fix code sniffer complaints after profilefields and passwords merge
  • +
  • [PHPBB3-12161] - build/save directories are no longer created
  • +
  • [PHPBB3-12162] - Binary files missing from update packages
  • +
  • [PHPBB3-12163] - Page number in viewtopic page title is incorrect
  • +
+

Improvement

+
    +
  • [PHPBB3-10910] - function build_cfg_template() allow $size for $tpl_type = select
  • +
  • [PHPBB3-11346] - Do not show "Mark topics as read" when there are no topics
  • +
  • [PHPBB3-11849] - Move pagination from functions.php into class
  • +
  • [PHPBB3-11950] - Add 'I forgot my password' link to index page login form
  • +
  • [PHPBB3-11961] - Plupload is not updating uploaded files panel
  • +
  • [PHPBB3-11966] - Add Template Event overall_header_navigation_prepend/append to Subsilver2
  • +
  • [PHPBB3-11969] - Link topic title to unread post instead of first post
  • +
  • [PHPBB3-11979] - Add basic dropdown menu framework to prosilver
  • +
  • [PHPBB3-12005] - Remove code responsible for PM popup completely
  • +
  • [PHPBB3-12010] - Navbar icons should be clickable
  • +
  • [PHPBB3-12014] - Add template event "index_body_forumlist_before"
  • +
  • [PHPBB3-12024] - Add Template Event overall_header_content_before and overall_footer_content_after
  • +
  • [PHPBB3-12034] - AJAXify notifications popup
  • +
  • [PHPBB3-12054] - Improve Index Body Template Events
  • +
  • [PHPBB3-12060] - Add BBCode system core and template events
  • +
  • [PHPBB3-12069] - Add PHP event core.submit_post_modify_return_url in submit_post()
  • +
  • [PHPBB3-12104] - Shadowtopics SQL used in core event should use the SQL builder
  • +
  • [PHPBB3-12135] - editor.js function bbfontstyle()
  • +
  • [PHPBB3-12146] - Add color demo when editing a group from the UCP
  • +
+

New Feature

+ +

Sub-task

+
    +
  • [PHPBB3-11241] - Improve user interface for mass attachment downloader (all posts, all topics)
  • +
  • [PHPBB3-12111] - Extract profile field types to individual classes
  • +
+

Task

+
    +
  • [PHPBB3-11432] - Travis should complain when CRLF files are added.
  • +
  • [PHPBB3-11509] - Travis should check commit message format
  • +
  • [PHPBB3-11985] - Enable APC module on Travis to run APC Cache tests
  • +
  • [PHPBB3-12064] - Remove obsolete viewsource.html from adm/style folder
  • +
  • [PHPBB3-12147] - Remove Travis CI notification configuration
  • +
+ +

Changes since 3.1.0-a1

+ +

Bug

+
    +
  • [PHPBB3-4776] - Long post gets hidden behind posting profile
  • +
  • [PHPBB3-10449] - Lines spilling in subscriptions view
  • +
  • [PHPBB3-10948] - Color swatch in 3.1 does not display properly
  • +
  • [PHPBB3-11030] - I beam cursor in prosilver when hovering on browse button for uploading attachments
  • +
  • [PHPBB3-11073] - Reported/Unapproved moderator information in viewtopic is striked through instead of underlined
  • +
  • [PHPBB3-11138] - Resync features in ACP should not use AJAX
  • +
  • [PHPBB3-11280] - Double clicking "mark topics read" produces an error the second time
  • +
  • [PHPBB3-11525] - phpbb_avatar_manager::clean_row collapses user_id and group_id
  • +
  • [PHPBB3-11534] - Remote avatar does not properly check if remote file is an image
  • +
  • [PHPBB3-11626] - Auth ACP options should be moved to separate html file
  • +
  • [PHPBB3-11663] - In generate_text_for_storage the function does not check for errors of parse_message:parse() and act accordingly
  • +
  • [PHPBB3-11691] - Soft delete migration conversion should be staggered
  • +
  • [PHPBB3-11739] - Wrong name for UCP Module "Edit "Remember Me" login keys"
  • +
  • [PHPBB3-11842] - Create a new group Error with avatar driver
  • +
  • [PHPBB3-11857] - Avatar manager must not depend on entire container
  • +
  • [PHPBB3-11872] - MCP: Users with most warnings list is invalid
  • +
  • [PHPBB3-11896] - "Mark all notifications read" does not work
  • +
  • [PHPBB3-11899] - New ajax poll vote should give feedback while waiting for servers response
  • +
  • [PHPBB3-11916] - Remove files from hidden attach list after deletion
  • +
  • [PHPBB3-11922] - Migrator fails to remove columns on MSSQL when they have/had an index
  • +
  • [PHPBB3-11923] - UCP avatar error when user has no permissions to change his/her avatar
  • +
  • [PHPBB3-11924] - Add a script to export the events in wiki format
  • +
  • [PHPBB3-11926] - Plupload Migration has a broken dependency.
  • +
  • [PHPBB3-11927] - Missing Files after 3.1.0-A1 Automatic Updater
  • +
  • [PHPBB3-11930] - Avatar paths are incorrect when using app.php
  • +
  • [PHPBB3-11935] - Invalid HTML in "Sort By" form elements in Prosilver
  • +
  • [PHPBB3-11936] - Fixes to Notifications Window
  • +
  • [PHPBB3-11939] - Quick reply editor has unnecessary data-ajax attribute
  • +
  • [PHPBB3-11943] - $VAR = false has unexpected result
  • +
  • [PHPBB3-11945] - Focused buttons are hard to notice
  • +
  • [PHPBB3-11947] - Notification popup does not appear when clicking number in text
  • +
  • [PHPBB3-11948] - Extensions should be allowed to have more then 1 routing file
  • +
  • [PHPBB3-11949] - cannot upgrade to 3.1
  • +
  • [PHPBB3-11960] - Responsive design removed teampage names
  • +
  • [PHPBB3-11972] - Add template event posting_editor_subject_after
  • +
  • [PHPBB3-11977] - Ajax delete should disable moving options
  • +
  • [PHPBB3-11982] - Navigation is shown above AJAX background in ACP
  • +
  • [PHPBB3-11983] - Subscriptions argument missing from docblock in ucp_notifications
  • +
  • [PHPBB3-11986] - Undefined index: poster_id in file.php
  • +
  • [PHPBB3-11987] - {ROOT_PATH} in ACP leads to adm/
  • +
  • [PHPBB3-11990] - Remove result_mssqlnative from acp_database
  • +
  • [PHPBB3-11991] - PHP notices when closing reported posts entries in MCP
  • +
  • [PHPBB3-11992] - Wrong variable to close "Users with most warnings" block at MCP
  • +
  • [PHPBB3-11994] - Admin options for extensions are bad/misleading
  • +
  • [PHPBB3-11995] - Reverting a config.remove fails
  • +
  • [PHPBB3-12002] - Extension management page should use generate/check link hash
  • +
  • [PHPBB3-12007] - Default last_result of callable steps must be integer instead of false
  • +
  • [PHPBB3-12008] - "Prune notifications" cron task always ran, blocking others
  • +
  • [PHPBB3-12016] - Event listeners should be services
  • +
  • [PHPBB3-12017] - Extension tests are broken on current develop
  • +
  • [PHPBB3-12018] - Use path_helper for admin style CSS in sql report
  • +
  • [PHPBB3-12023] - New css files missing after update
  • +
+

Improvement

+
    +
  • [PHPBB3-11552] - Responsive design for prosilver
  • +
  • [PHPBB3-11746] - Add "account activation required" notification for Administrators
  • +
  • [PHPBB3-11921] - Improve Notifications and PMs in the header
  • +
  • [PHPBB3-11928] - Replace AJAX loading info pop up with animation
  • +
  • [PHPBB3-11957] - Responsive design for admin control panel
  • +
  • [PHPBB3-11973] - Remove logic from language files where possible
  • +
  • [PHPBB3-11974] - All timezones should be translatable
  • +
  • [PHPBB3-11975] - Add ACP link next to MCP
  • +
+

Task

+ + +

Changes since 3.0.x

+ +

Bug

+
    +
  • [PHPBB3-4412] - MCP and viewtopic messed up when long profile fields used
  • +
  • [PHPBB3-6109] - MSN is now called Windows Live Messenger (WLM)
  • +
  • [PHPBB3-6855] - The word separator has been misspelled in the code
  • +
  • [PHPBB3-7070] - Making Font Type & Size Easy To modify
  • +
  • [PHPBB3-7252] - Use IMAGETYPE_ constants in get_supported_image_types()
  • +
  • [PHPBB3-7448] - Module management calling non-static method p_master::module_auth()
  • +
  • [PHPBB3-8212] - Comment spelling mistake in includes/acp/acp_board.php
  • +
  • [PHPBB3-8228] - Coding BBcode has whitespace before the code.
  • +
  • [PHPBB3-8542] - No custom profile fields in private messages
  • +
  • [PHPBB3-8641] - Extra and missing comma in acp_board.php
  • +
  • [PHPBB3-8713] - trimming login inputs isn't sensible
  • +
  • [PHPBB3-8841] - Unexpected results when using "PHPBB_USE_BOARD_URL_PATH"
  • +
  • [PHPBB3-8912] - phpbb_styles_template_data contans more than 4k records
  • +
  • [PHPBB3-9022] - Poll Deletion Problem on Global change
  • +
  • [PHPBB3-9163] - style.php cannot be properly cached
  • +
  • [PHPBB3-9341] - Incorrectly named template vars
  • +
  • [PHPBB3-9394] - wrong comment in functions_upload.php
  • +
  • [PHPBB3-9474] - Unnecessary mcp_viewlogs.html included
  • +
  • [PHPBB3-9492] - Group avatar overwrites currently defined user avatars
  • +
  • [PHPBB3-9766] - phpbb_default_captcha::delete_code() takes no argument but uses $confirm_id
  • +
  • [PHPBB3-9918] - $redirect variable set, but not used, in mcp functions
  • +
  • [PHPBB3-10006] - phpbb_config::delete is missing
  • +
  • [PHPBB3-10013] - Cache test writes to a temporary location deep in the source tree
  • +
  • [PHPBB3-10045] - Database updater version for changes between 3.0.x and 3.1.0 should be 3.1.0-dev
  • +
  • [PHPBB3-10070] - CRLF in develop
  • +
  • [PHPBB3-10080] - request_var tests fail when other tests using request_var are executed first
  • +
  • [PHPBB3-10103] - Fix remaining usage of flock() by converting it to use the lock class
  • +
  • [PHPBB3-10136] - Missing $request globalizations in includes/functions.php
  • +
  • [PHPBB3-10139] - Use of $cache for two different things in includes/config/db.php
  • +
  • [PHPBB3-10151] - Version number needs to be updated in installer
  • +
  • [PHPBB3-10152] - Installer places ?> in config.php
  • +
  • [PHPBB3-10156] - Cron tests fail on windows
  • +
  • [PHPBB3-10290] - Who's online broken due to malformed SQL
  • +
  • [PHPBB3-10300] - Groups teampage legends settings
  • +
  • [PHPBB3-10316] - Inconsistency between prosilver and subsilver when locking a topic while moving
  • +
  • [PHPBB3-10322] - No longer possible to include templates via variables, captcha broken
  • +
  • [PHPBB3-10329] - Function pcre_utf8_support() needs the "phpbb_" prefix
  • +
  • [PHPBB3-10350] - class phpbb_template_renderer_eval does not work
  • +
  • [PHPBB3-10355] - Template tests do not correctly end output buffering
  • +
  • [PHPBB3-10359] - Fix phpbb_request regression in download/file.php and style.php
  • +
  • [PHPBB3-10360] - search_results.html gone haywire!
  • +
  • [PHPBB3-10364] - ACP Permissions Roles Tabs Broken
  • +
  • [PHPBB3-10371] - $user->theme['imageset_path'] - doesn't exist anymore
  • +
  • [PHPBB3-10374] - Template cache viewer broken
  • +
  • [PHPBB3-10375] - Template Cache keeps regenerating cache files
  • +
  • [PHPBB3-10378] - Errors in imageset -> theme conversion
  • +
  • [PHPBB3-10380] - Imageset removal: bidi padding change
  • +
  • [PHPBB3-10384] - 1_DAY language variables are not working in templates anymore
  • +
  • [PHPBB3-10392] - Alternate nested block loop syntax broken for auto variables
  • +
  • [PHPBB3-10393] - Syntax error in cached template code
  • +
  • [PHPBB3-10409] - Running database_update.php multiple times breaks the update
  • +
  • [PHPBB3-10417] - phpbb_test_case_helpers::get_test_config() uses array_merge() on undefined $config
  • +
  • [PHPBB3-10444] - Edit reason remains if a post gets edited by a moderator for the second time
  • +
  • [PHPBB3-10457] - Undefined variable $request, when print-viewing PMs
  • +
  • [PHPBB3-10458] - Invalid html when print-viewing a PM
  • +
  • [PHPBB3-10459] - Make "Reply to all"on PMs as a button
  • +
  • [PHPBB3-10463] - Inherit template causes SQL error
  • +
  • [PHPBB3-10464] - Debug error in search settings/search index
  • +
  • [PHPBB3-10477] - Registration, forgot password broken
  • +
  • [PHPBB3-10481] - Test suite does not run on php 5.3
  • +
  • [PHPBB3-10495] - Tests have version checks against php 6.0.0
  • +
  • [PHPBB3-10500] - Miscellaneous issues in the new template engine
  • +
  • [PHPBB3-10541] - Empty "Select form:" dropdown when editing user
  • +
  • [PHPBB3-10547] - Log out instead of acp statistics page at the end of fresh install
  • +
  • [PHPBB3-10560] - Post count not updated to include unapproved posts (though topic count is) for moderators
  • +
  • [PHPBB3-10575] - UCP Register: Non-static method phpbb_captcha_factory::get_instance() should not be called statically
  • +
  • [PHPBB3-10579] - New license block has v2 duplicated
  • +
  • [PHPBB3-10602] - A bug in mail queue processing
  • +
  • [PHPBB3-10618] - Hardcoded phpBB 3.0 language in installer
  • +
  • [PHPBB3-10634] - cp modules: function loaded() only checks current module type
  • +
  • [PHPBB3-10636] - cache_moderators() generates invalid query
  • +
  • [PHPBB3-10637] - Extension manager: missed code changes in search
  • +
  • [PHPBB3-10641] - MCP still uses old plurality forms of language variables
  • +
  • [PHPBB3-10645] - Invalid CSS for checkboxes and radio buttons in ACP css
  • +
  • [PHPBB3-10652] - Template inheritance doesn't work
  • +
  • [PHPBB3-10655] - Bug in test case: phpbb_template_template_inheritance_test
  • +
  • [PHPBB3-10663] - Extension manager: finder class adds directories that should not match the query
  • +
  • [PHPBB3-10667] - Fix extensions and group positions tests under MySQL 5.5 strict mode
  • +
  • [PHPBB3-10671] - Integrity tests failing
  • +
  • [PHPBB3-10672] - Statistics .. Total posts
  • +
  • [PHPBB3-10678] - Provide Firebird, Oracle, and increased MSSQL support in unit tests
  • +
  • [PHPBB3-10685] - Template inheritance test produces a notice on php 5.4 due to an incompatible override of setup_engine
  • +
  • [PHPBB3-10690] - Undefined language string in MCP
  • +
  • [PHPBB3-10703] - extensions.php script dies miserably when ext directory is missing
  • +
  • [PHPBB3-10704] - Typo in a comment
  • +
  • [PHPBB3-10735] - Incorrect styles search order, locator's inability to use template inheritance for extensions
  • +
  • [PHPBB3-10736] - Composer is making tests fail
  • +
  • [PHPBB3-10742] - Tables in users list have incorrect width
  • +
  • [PHPBB3-10752] - acp_styles errors
  • +
  • [PHPBB3-10754] - $style should be renamed to $phpbb_style
  • +
  • [PHPBB3-10756] - Template classes don't belong in style directory
  • +
  • [PHPBB3-10759] - Database updater doesn't reset default style
  • +
  • [PHPBB3-10777] - Typo in viewtopic.php comment line 1632
  • +
  • [PHPBB3-10778] - Extra space on the link "Close Window" in prosilver/template/posting_smilies.html
  • +
  • [PHPBB3-10779] - "Serve jQuery using Google's CDN" Should be on ACP Load Settings, not Board Features
  • +
  • [PHPBB3-10781] - Quick Mod tools don't work
  • +
  • [PHPBB3-10784] - Do not show ajax overlay unless needed
  • +
  • [PHPBB3-10785] - Illegal use of $_REQUEST in develop/fill.php
  • +
  • [PHPBB3-10801] - Move topic in quickmod tools not functional
  • +
  • [PHPBB3-10802] - Splitting topics in quickmod tools not functional
  • +
  • [PHPBB3-10803] - When ajax requests fail, the failure message should remain visible until the user dismisses it
  • +
  • [PHPBB3-10805] - "Request timed out" ui appears over function ui in ajax
  • +
  • [PHPBB3-10807] - Deleting topics via quickmod provides no feedback
  • +
  • [PHPBB3-10808] - Locking topics via quickmod provides no feedback
  • +
  • [PHPBB3-10811] - AJAX callback "alt_text" insufficiently changes links
  • +
  • [PHPBB3-10813] - Installer does not check that php json extension is present
  • +
  • [PHPBB3-10818] - Global Announcements Update Dialog does not terminate with exit_handler()
  • +
  • [PHPBB3-10826] - 3.1-dev Database Updater keeps running group_legend query
  • +
  • [PHPBB3-10837] - Undefined index in extension tests
  • +
  • [PHPBB3-10844] - Extensions are not located when front-end file has a diffferent phpbb_root_path
  • +
  • [PHPBB3-10845] - Reported post text does not get bbcode-parsed when viewing a post report
  • +
  • [PHPBB3-10847] - Dependent is misspelled a number of times
  • +
  • [PHPBB3-10871] - In "Posts awaiting approval" if there's more than one post it shows no posts
  • +
  • [PHPBB3-10875] - SQL Cache is not used
  • +
  • [PHPBB3-10876] - Xampp crashes on Develop when template has too many ORs in an IF statement
  • +
  • [PHPBB3-10885] - UCP Main Error if no forums exist
  • +
  • [PHPBB3-10888] - cachepath in template class is set externally
  • +
  • [PHPBB3-10905] - Last topic title missing in subsilver
  • +
  • [PHPBB3-10906] - Last post title does not work for new installs of 3.1-dev due to missing change to schema_data.sql
  • +
  • [PHPBB3-10912] - Undefined variable: last_post_subject_truncated
  • +
  • [PHPBB3-10915] - Sort not installed styles list in admin control panel - styles
  • +
  • [PHPBB3-10943] - Search Box should display keywords entered by the user
  • +
  • [PHPBB3-10954] - Mark topics as read AJAX
  • +
  • [PHPBB3-10976] - Running tests using phpunit.xml.all fails
  • +
  • [PHPBB3-10982] - Allow setting dimming control overlay also as data-overlay
  • +
  • [PHPBB3-10998] - No border-radius for forum rules block
  • +
  • [PHPBB3-10999] - Asset version not defined in adm/
  • +
  • [PHPBB3-11002] - Etc/GMT timezones return wrong offset
  • +
  • [PHPBB3-11003] - Ability to show full list of timezones with JavaScript enabled
  • +
  • [PHPBB3-11004] - The timezone suggestion button is not a real button
  • +
  • [PHPBB3-11007] - Timezone selector shows all timezones
  • +
  • [PHPBB3-11014] - Previous/next links are no longer displayed.
  • +
  • [PHPBB3-11018] - Pagination in memberlist
  • +
  • [PHPBB3-11023] - Fix excess tabbing and spacing in functions.php
  • +
  • [PHPBB3-11029] - Cannot break/continue 1 level in includes\cache\service.php:340
  • +
  • [PHPBB3-11041] - global $php_ext instead of global $phpEx
  • +
  • [PHPBB3-11043] - Update template hook name
  • +
  • [PHPBB3-11046] - No border-radius for UCP message colours block
  • +
  • [PHPBB3-11047] - Unclosed variable shows incorrect language key
  • +
  • [PHPBB3-11052] - Search class changes not reflected in installer or convertor
  • +
  • [PHPBB3-11065] - li tag on topic display options at MCP is unclosed
  • +
  • [PHPBB3-11067] - Pagination in ACP is missing CSS code from pagination change
  • +
  • [PHPBB3-11077] - Feed still has fallback code for global announcement with forum_id = 0
  • +
  • [PHPBB3-11086] - Database Updater still relies on cache factory - needs to be updated to use DIC
  • +
  • [PHPBB3-11089] - Database type mysql sets unusable board
  • +
  • [PHPBB3-11092] - phpbb_gen_download_links strict standards errors
  • +
  • [PHPBB3-11095] - Jumpbox should use "get" method
  • +
  • [PHPBB3-11099] - Clicking Banning tab in MCP causes all tabs to collapse down
  • +
  • [PHPBB3-11100] - jabber::can_use_ssl() should not be called statically in includes/acp/acp_jabber.php on line 124
  • +
  • [PHPBB3-11101] - Container processors are executed before globals exist
  • +
  • [PHPBB3-11106] - Undefined index: EDITED_TIME_TOTAL
  • +
  • [PHPBB3-11139] - Colour swatch window with Fatal error
  • +
  • [PHPBB3-11140] - MCP Front errors on reported posts
  • +
  • [PHPBB3-11154] - Unable to upgrade 3.0 QI-installed board to 3.1
  • +
  • [PHPBB3-11157] - Strict spam renders spambot countermeasures page unusable
  • +
  • [PHPBB3-11159] - Coding guidelines: static public
  • +
  • [PHPBB3-11166] - AJAX confirm box is not using custom templates given
  • +
  • [PHPBB3-11171] - Copy bbcode fields, etc. for reported posts
  • +
  • [PHPBB3-11176] - Functional tests do not run
  • +
  • [PHPBB3-11177] - Postgres search when query has only negation
  • +
  • [PHPBB3-11187] - Functional tests broken by new config class
  • +
  • [PHPBB3-11188] - postgres search result count does not get the total count
  • +
  • [PHPBB3-11190] - Functional tests do not clear the cache between each test
  • +
  • [PHPBB3-11194] - PHP Notice: Undefined index "tag"
  • +
  • [PHPBB3-11198] - AJAX move up/down links not replaced correctly
  • +
  • [PHPBB3-11199] - Cache purge should remove dumped container
  • +
  • [PHPBB3-11200] - Container construction fails with non-MySQLi drivers
  • +
  • [PHPBB3-11204] - Wrong indentation in functional test case base class
  • +
  • [PHPBB3-11205] - Merge conflict in readme.html
  • +
  • [PHPBB3-11206] - Avatars are broken, includes non-existent files
  • +
  • [PHPBB3-11208] - Functional tests are broken
  • +
  • [PHPBB3-11209] - Always clone Ajax disable images to avoid problems if they are used multiple times on the same page
  • +
  • [PHPBB3-11211] - Call to undefined function phpbb_real_path() in /phpBB/includes/di/extension/ext.php on line 52
  • +
  • [PHPBB3-11212] - Catch non-existent globals if error occurs during container construction
  • +
  • [PHPBB3-11213] - Missing global in install_update.php
  • +
  • [PHPBB3-11227] - @return void -> @return null
  • +
  • [PHPBB3-11236] - Prune users requires group selection if any groups are defined
  • +
  • [PHPBB3-11237] - php spam when pruning users
  • +
  • [PHPBB3-11247] - php spam in flock class
  • +
  • [PHPBB3-11248] - CRLF line endings
  • +
  • [PHPBB3-11253] - Signature module acl is not going to be added to 3.1 boards
  • +
  • [PHPBB3-11256] - Unused service controller.route_collection
  • +
  • [PHPBB3-11257] - Using Service Collection requires set_name() method to exist
  • +
  • [PHPBB3-11263] - PHP Notice: in file functions_messenger.php on line 213: Undefined variable: extension_manager
  • +
  • [PHPBB3-11273] - Missing space after Sphinx "Indexer memory limit" input box
  • +
  • [PHPBB3-11277] - User DST column is not removed on update, and therefor user_timezone is updated everytime the update file is run
  • +
  • [PHPBB3-11279] - "Something went wrong when processing your request." is not acceptable language
  • +
  • [PHPBB3-11291] - "Could not open input file: ../composer.phar" error during phing's create-package
  • +
  • [PHPBB3-11298] - ACP_EXTENSIONS_MANAGEMENT missing string
  • +
  • [PHPBB3-11302] - No timezone is selected by default in registration form
  • +
  • [PHPBB3-11303] - Timezone selection is not preserved in registration form
  • +
  • [PHPBB3-11305] - Installer is broken on develop
  • +
  • [PHPBB3-11309] - phpbb_extension_interface::disable_step correct docblock
  • +
  • [PHPBB3-11310] - CSRF in style installation in acp
  • +
  • [PHPBB3-11311] - Include javascript core.js file in subsilver2 overall_footer.html
  • +
  • [PHPBB3-11313] - Typo in alt_text callback breaks replacement of text
  • +
  • [PHPBB3-11320] - Database test cases fail if functions file is not included and config file exists
  • +
  • [PHPBB3-11321] - Recreate schema files with develop/create_schema_files.php
  • +
  • [PHPBB3-11323] - Not possible to define template variable with other variables
  • +
  • [PHPBB3-11329] - Color values should be in colours.css, not buttons.css
  • +
  • [PHPBB3-11334] - Controller helper url() method still generates URLs like app.php/route/to/page instead of app.php?controller=route/to/page
  • +
  • [PHPBB3-11335] - Hook finder fails to load hooks
  • +
  • [PHPBB3-11342] - Marking subforums as read does not change the unread icons of active topics
  • +
  • [PHPBB3-11344] - ACP_STYLE_COMPONENTS
  • +
  • [PHPBB3-11345] - Font size on new buttons is too thin
  • +
  • [PHPBB3-11350] - $db should not be passed by reference to db_tools
  • +
  • [PHPBB3-11362] - Resource locator does not find admin template files from extensions
  • +
  • [PHPBB3-11363] - Migrations module tool does not load info files from extensions
  • +
  • [PHPBB3-11367] - Migrator throws error if migrations table does not exist
  • +
  • [PHPBB3-11369] - Reverting migration throws error (attempt to unserialize string)
  • +
  • [PHPBB3-11370] - Effectively installed migrations not inserted into migrations table
  • +
  • [PHPBB3-11372] - Migrator should only check if effectively installed if the migration is not installed at all
  • +
  • [PHPBB3-11381] - Finder cannot find items for non-enabled extensions (required during installation)
  • +
  • [PHPBB3-11383] - Uninstalling an extension that adds a module causes an error upon purge
  • +
  • [PHPBB3-11385] - get_module_infos from migrator does not return module info
  • +
  • [PHPBB3-11386] - Migrator can include files multiple times
  • +
  • [PHPBB3-11387] - Module add tool logs module as added even if it fails due to error (migrations)
  • +
  • [PHPBB3-11388] - Extension CSS files are not being auto-loaded
  • +
  • [PHPBB3-11394] - Migration Tools are too strict
  • +
  • [PHPBB3-11395] - acp_modules::get_module_infos can include files multiple times
  • +
  • [PHPBB3-11396] - insert_migration also updates migration (should rename function)
  • +
  • [PHPBB3-11398] - Migrations permission tool's method permission_set causes fatal error
  • +
  • [PHPBB3-11400] - Notification system assumes email is always available
  • +
  • [PHPBB3-11402] - Undefined offset 0 post/topic_in_queue (notifications)
  • +
  • [PHPBB3-11403] - Multiinsert for notifications should use batches
  • +
  • [PHPBB3-11404] - Can not access ACP "manage group" page while creating new groups
  • +
  • [PHPBB3-11405] - Users that are subscribed to a forum, don't receive notifications for new replies
  • +
  • [PHPBB3-11407] - UI notifications have wrong <dfn> info next to checkbox
  • +
  • [PHPBB3-11408] - Undefined index jabber (should be user_jabber)
  • +
  • [PHPBB3-11411] - Broken primary key on phpbb_notification_types table
  • +
  • [PHPBB3-11413] - phpbb_notification_types table should have an integer primary key
  • +
  • [PHPBB3-11415] - Accessing phpbb_migrations table from index page (or other pages)
  • +
  • [PHPBB3-11416] - Primary key on phpbb_notifications table too small
  • +
  • [PHPBB3-11417] - Notification Options page in User Control Panel has "Mark read" as submit button.
  • +
  • [PHPBB3-11420] - Notification methods are not (correctly) imported
  • +
  • [PHPBB3-11421] - Submit button missing from UCP notifications module
  • +
  • [PHPBB3-11422] - Assets should be incremented on database update
  • +
  • [PHPBB3-11423] - Email has HTML from username
  • +
  • [PHPBB3-11433] - Loading alert message is shown beneath overlay
  • +
  • [PHPBB3-11435] - Extension template files curly braces bug
  • +
  • [PHPBB3-11437] - Sphinx debug assert failed when no search results are found
  • +
  • [PHPBB3-11438] - Sphinx default config does not work properly
  • +
  • [PHPBB3-11439] - Notification tests are not run via phpunit.xml.dist
  • +
  • [PHPBB3-11440] - Mixed type (string vs integer) in SQL query against phpbb_users
  • +
  • [PHPBB3-11442] - AJAX in the ACP Does not work wherever Confirm Box modals are used
  • +
  • [PHPBB3-11443] - Two migration tests are not run via phpunit.xml.dist
  • +
  • [PHPBB3-11448] - phpbb_notification_manager::mark_notifications_read takes $user_id as a parameter but isn't using it
  • +
  • [PHPBB3-11450] - phpbb_extension_metadata_manager has wrong docs and too many arguments
  • +
  • [PHPBB3-11451] - class phpbb_notification_method_jabber extends phpbb_notification_method_email
  • +
  • [PHPBB3-11452] - phpbb_notification_method_email::is_available() should return false when user doesn't have an email address
  • +
  • [PHPBB3-11454] - Jabber notifications are not working at all
  • +
  • [PHPBB3-11455] - Sort phpBB/config/tables.yml in alphabetic order
  • +
  • [PHPBB3-11457] - class phpbb_notification_test should not require/use the global set_var() function
  • +
  • [PHPBB3-11460] - New installs have wrong data in phpbb_user_notifications table.
  • +
  • [PHPBB3-11464] - Config table load_cpf_pm is missing on new install
  • +
  • [PHPBB3-11466] - phpunit configs excludes non-existant files
  • +
  • [PHPBB3-11471] - Some email templates contain unrelated text
  • +
  • [PHPBB3-11474] - "Post in queue" notification send to too many moderators
  • +
  • [PHPBB3-11478] - Daylight Savingtime changes old posts in time, too.
  • +
  • [PHPBB3-11479] - Can not access install/index.php?mode=update
  • +
  • [PHPBB3-11485] - migration doesn't add necessary columns to phpbb_styles table when updating schemas.
  • +
  • [PHPBB3-11488] - Notification email error
  • +
  • [PHPBB3-11489] - Change forum list layout to work at any resolution
  • +
  • [PHPBB3-11491] - Functional test for extensions has wrong class name and is in the wrong directory
  • +
  • [PHPBB3-11492] - memberlist user_ids array uninitalized can lead to error
  • +
  • [PHPBB3-11494] - Fix memberlist leaders functional tests
  • +
  • [PHPBB3-11501] - Fix "This test did not perform any assertions" message on some tests
  • +
  • [PHPBB3-11503] - Implementation of DB drivers vary too much
  • +
  • [PHPBB3-11516] - .live() deprecated in new version of jquery
  • +
  • [PHPBB3-11535] - ACP und UCP avatar groups settings do not display an error upon submitting incorrect data
  • +
  • [PHPBB3-11549] - Resource locator does not find admin template files from extensions
  • +
  • [PHPBB3-11550] - Functional extension controller test should use $helpers to copy and remove files
  • +
  • [PHPBB3-11551] - ACP System tab errors: [phpBB Debug] - PHP Notice: in file [ROOT] -/includes/acp/acp_update.php on line 49: Undefined offset: 1
  • +
  • [PHPBB3-11553] - Links list errors
  • +
  • [PHPBB3-11554] - forum_fn.js should be moved to footer
  • +
  • [PHPBB3-11555] - Pagination page selection JS is broken
  • +
  • [PHPBB3-11556] - Remove non-jquery fallback code from forum_fn.js
  • +
  • [PHPBB3-11560] - Missing T_JQUERY_LINK template variable in installer
  • +
  • [PHPBB3-11561] - Notification functional tests fail using phpunit.xml.all
  • +
  • [PHPBB3-11562] - forum_fn.js cleanup
  • +
  • [PHPBB3-11563] - Move subPanels() code from html templates to forum_fn.js
  • +
  • [PHPBB3-11564] - Notifications list errors
  • +
  • [PHPBB3-11567] - Fatal error at database update
  • +
  • [PHPBB3-11569] - Database update fails at continuing on some environments
  • +
  • [PHPBB3-11570] - No way to get back to update routine when in database updater
  • +
  • [PHPBB3-11573] - Test Suite does not work with MySQL STRICT_TRANS_TABLES (MySQL 5.6)
  • +
  • [PHPBB3-11574] - Unable to "auto update" from 3.0 to 3.1
  • +
  • [PHPBB3-11585] - Strict Standars error when editing the role
  • +
  • [PHPBB3-11586] - Fix/cleanup some of upload_attachment()'s code
  • +
  • [PHPBB3-11587] - Group gets removed from legend when editing one in UCP
  • +
  • [PHPBB3-11593] - filter->compile_var_tags uses undefined variable
  • +
  • [PHPBB3-11594] - Template filter not aware of extension context
  • +
  • [PHPBB3-11599] - Tree tests are very slow
  • +
  • [PHPBB3-11602] - Do not call avatar_manager's localize_errors() if avatars are disabled
  • +
  • [PHPBB3-11605] - Order of files and directories given to phpbb_test_case_helpers->remove_files() matters.
  • +
  • [PHPBB3-11622] - Template events are loaded incorrectly
  • +
  • [PHPBB3-11647] - URLs are incorrectly handled by INCLUDEJS
  • +
  • [PHPBB3-11660] - Bootstrap container from config.php bugs
  • +
  • [PHPBB3-11664] - Creating php.html file in root path in tests
  • +
  • [PHPBB3-11665] - Can't change file names already sent to set_filenames
  • +
  • [PHPBB3-11675] - Uncaught exception 'Twig_Error_Syntax' -Permissions User roles-
  • +
  • [PHPBB3-11687] - assets_version is missing in phpbb_config
  • +
  • [PHPBB3-11688] - Cache purge does not purge the twig directory
  • +
  • [PHPBB3-11691] - Soft delete migration conversion should be staggered
  • +
  • [PHPBB3-11692] - Don't update search_type in dev migration if already appended with phpbb_search_
  • +
  • [PHPBB3-11694] - Twig Error on ACP Login page
  • +
  • [PHPBB3-11695] - Cannot edit first post in a topic more than once without manually changing "Options Per User" to 1 on second edit.
  • +
  • [PHPBB3-11696] - phpbb_db_tools does not autoload
  • +
  • [PHPBB3-11697] - fulltext_mysql.php - author_search() uses incorrect $m_approve_fid_ary parameter
  • +
  • [PHPBB3-11701] - TWIG breaks template events for events inside of looped template code
  • +
  • [PHPBB3-11702] - Forumlink gives general error
  • +
  • [PHPBB3-11706] - getimagesize() should be called with @ to prevent PHP warning in remote avatar
  • +
  • [PHPBB3-11707] - Twig DEFINE not working as expected
  • +
  • [PHPBB3-11708] - Invalid bulletin point in notifications
  • +
  • [PHPBB3-11712] - Typo in editor.js
  • +
  • [PHPBB3-11713] - Module gets removed by ajax even if removal was not successful
  • +
  • [PHPBB3-11717] - View unanswered posts = General error
  • +
  • [PHPBB3-11718] - Twig lexer only correcting statements in IF, not ELSEIF
  • +
  • [PHPBB3-11723] - Can not agree to Terms of Service if more than one language is installed
  • +
  • [PHPBB3-11725] - No Avatar image -upload avatar-
  • +
  • [PHPBB3-11727] - INCLUDECSS and INCLUDEJS incorrectly default to /template/ folder
  • +
  • [PHPBB3-11728] - Fix last occurance of topic_approved index
  • +
  • [PHPBB3-11729] - memberlist.php is empty white page
  • +
  • [PHPBB3-11731] - Make calls for captcha garbage collections non-static
  • +
  • [PHPBB3-11733] - Illegal offset type Warning in [ROOT] -/includes/auth/auth.php via [ROOT] -/feed.php
  • +
  • [PHPBB3-11734] - Missing permession in language file
  • +
  • [PHPBB3-11735] - Missing checkbox in User Control Panel / Manage notifications
  • +
  • [PHPBB3-11739] - Wrong name for UCP Module "Edit "Remember Me" login keys"
  • +
  • [PHPBB3-11741] - Brackets exist even without any text
  • +
  • [PHPBB3-11751] - MCP Softdelete Modules missing when updating board
  • +
  • [PHPBB3-11754] - Remove leftovers of style switcher
  • +
  • [PHPBB3-11755] - MySQL Upgrader out of date for 3.1
  • +
  • [PHPBB3-11757] - Typo in signature_module_auth migration
  • +
  • [PHPBB3-11759] - Migrations update to the wrong 3.0.x version numbers.
  • +
  • [PHPBB3-11761] - Example.org no longer serves blank responses, failing functional tests
  • +
  • [PHPBB3-11763] - Debug and SQL Error when reporting a PM
  • +
  • [PHPBB3-11767] - Unable to update DB from 3.0.X to 3.1.X because of post_visibility
  • +
  • [PHPBB3-11770] - Invalid class for pm list in outbox/sentbox
  • +
  • [PHPBB3-11774] - PHP errors on viewing reported post details
  • +
  • [PHPBB3-11777] - Extension template events are not loaded from subdirectories
  • +
  • [PHPBB3-11779] - Invalid class for unapproved posts in mcp index
  • +
  • [PHPBB3-11780] - Remove unused images from prosilver
  • +
  • [PHPBB3-11781] - update_post_information() is not defined in phpbb_content_visibility->set_post_visibility
  • +
  • [PHPBB3-11782] - Notices inside posts look messy
  • +
  • [PHPBB3-11791] - Template events are not loaded in ACP
  • +
  • [PHPBB3-11792] - core.user_setup event doesn't support loading language files from extensions
  • +
  • [PHPBB3-11796] - Duplicate code for pagination
  • +
  • [PHPBB3-11800] - Incorrect inclusion of scripts in popup window
  • +
  • [PHPBB3-11804] - </li> tag at overall_header.html (prosilver) which was not open
  • +
  • [PHPBB3-11805] - pagination does not support controller_helper urls
  • +
  • [PHPBB3-11809] - core.js should be loaded directly after jQuery and after every other JS file
  • +
  • [PHPBB3-11811] - Chrome 30 adds outline to focused elements
  • +
  • [PHPBB3-11812] - No longer able to define empty template variable using DEFINE
  • +
  • [PHPBB3-11816] - Twig regression: parentheses in IF statements stop DEFINE variables and loop lengths from working
  • +
  • [PHPBB3-11822] - Template include asset doesn't follow namespace lookup order
  • +
  • [PHPBB3-11824] - Controller URLs do not work anymore
  • +
  • [PHPBB3-11825] - phpBB/phpbb/ should only contain classes
  • +
  • [PHPBB3-11828] - Twig DEFINE is causing errors
  • +
  • [PHPBB3-11832] - URLs in tests aren't shortened, triggering errors in Composer files
  • +
  • [PHPBB3-11833] - Twig error while viewing user notes
  • +
  • [PHPBB3-11835] - phpbb_db_migration_data_310_auth_provider_oauth: A required module does not exist: UCP_AUTH_LINK
  • +
  • [PHPBB3-11836] - Manage external account associations error
  • +
  • [PHPBB3-11837] - UCP_AUTH_LINK_NOT_SUPPORTED (untranslated)
  • +
  • [PHPBB3-11843] - Regression: using underscores in template DEFINE variables yields unexpected behavior
  • +
  • [PHPBB3-11846] - Empty paragraph elements in jumpbox.html
  • +
  • [PHPBB3-11850] - $user->page contains bogus data on controller (app.php) pages
  • +
  • [PHPBB3-11852] - filesystem class must not depend on a web request
  • +
  • [PHPBB3-11862] - Events core.delete_user_before and core.delete_user_after compact the wrong vars
  • +
  • [PHPBB3-11865] - includes/bbcode.php is missing namespace change
  • +
  • [PHPBB3-11866] - "You have specified an invalid dbms driver" Error with config sample
  • +
  • [PHPBB3-11867] - Schema files are not created by create_schema_files.php
  • +
  • [PHPBB3-11868] - Class 'phpbb_request_interface' not found
  • +
  • [PHPBB3-11871] - Extension modules are not working with namespaces anymore
  • +
  • [PHPBB3-11874] - RSS Feed -Bug Url-
  • +
  • [PHPBB3-11878] - Missing leading \ in dependencies in soft_delete_mcp_modules
  • +
  • [PHPBB3-11882] - Incorrect dependency in signature module auth migration
  • +
  • [PHPBB3-11888] - On New installation the default search backend is not shown correctly.
  • +
  • [PHPBB3-11890] - Language key used when soft-deleting in MCP.
  • +
  • [PHPBB3-11892] - Undefined variable: to_forum_id after cancelling merge topic/move posts
  • +
  • [PHPBB3-11893] - Font size in "Edit 'Remember Me' login keys" is large
  • +
  • [PHPBB3-11895] - Forum feed stopped working
  • +
  • [PHPBB3-11898] - Unable to login to ACP on AREA51
  • +
  • [PHPBB3-11901] - Undefined offset: 3 in file [ROOT] -/includes/functions_content.php on line 776
  • +
  • [PHPBB3-11905] - Alpha 1 Migration
  • +
  • [PHPBB3-11906] - Missing database entries "read_notification"
  • +
  • [PHPBB3-11908] - class phpbb_auth_provider_oauth_service_exception not using namespaces
  • +
  • [PHPBB3-11918] - Can not update from 3.0.x to 3.1.x with MSSQL
  • +
  • [PHPBB3-11919] - Improper argument order for sql_fetchfield() in notification manager
  • +
+

Improvement

+
    +
  • [PHPBB3-7598] - Reminding inactive users
  • +
  • [PHPBB3-7938] - Display information about unread and new PMs in the header.
  • +
  • [PHPBB3-8065] - Add an option to lock topics while moving them.
  • +
  • [PHPBB3-8270] - Automatically loaded language-files for mods
  • +
  • [PHPBB3-8796] - Mark forum(s) read (or mark topics read) marks some topics you haven't read
  • +
  • [PHPBB3-9346] - Use different message template for jabber notification
  • +
  • [PHPBB3-9532] - Optimized Page Titles
  • +
  • [PHPBB3-9549] - Enhance teampage functionality
  • +
  • [PHPBB3-9596] - Modular cron and allowing cron tasks to be run from system cron
  • +
  • [PHPBB3-9608] - Cleanse object references from codebase for 3.1
  • +
  • [PHPBB3-9649] - Moderator queue does not display icon if the new post is made in old thread
  • +
  • [PHPBB3-9668] - Automatic UTF-8 normalization
  • +
  • [PHPBB3-9669] - Native UTF-8 normalization
  • +
  • [PHPBB3-9684] - Link global announcements to forums
  • +
  • [PHPBB3-9693] - generate_smilies clean up
  • +
  • [PHPBB3-9716] - Handle user input through a request class providing a more complete mechanism than request_var
  • +
  • [PHPBB3-9741] - Do not store any themes or templates in the database
  • +
  • [PHPBB3-9746] - Normalise internet protocol version 6 addresses
  • +
  • [PHPBB3-9792] - Move function definitions out of download/file.php
  • +
  • [PHPBB3-9823] - Move functions definitions out of adm/index.php
  • +
  • [PHPBB3-9979] - Autoloading for test suite
  • +
  • [PHPBB3-10001] - Class Based Forum/Topic Image
  • +
  • [PHPBB3-10076] - STARTTLS for emails
  • +
  • [PHPBB3-10143] - Delete-write-read test for config classes
  • +
  • [PHPBB3-10148] - Turn TEMPLATE_BITFIELD into an instance variable in acp_styles.php
  • +
  • [PHPBB3-10155] - Inclusion of jQuery
  • +
  • [PHPBB3-10161] - Use one of "email" or "e-mail"
  • +
  • [PHPBB3-10172] - Display empty birthday box in prosilver when birthdays are enabled but there are none today
  • +
  • [PHPBB3-10258] - Use HTML5 doctype
  • +
  • [PHPBB3-10271] - confirm_box operations should use AJAX
  • +
  • [PHPBB3-10272] - Simple operations should use AJAX
  • +
  • [PHPBB3-10273] - Accepting / denying posts should use AJAX
  • +
  • [PHPBB3-10281] - Reordering forums in the ACP should use AJAX
  • +
  • [PHPBB3-10312] - Un-check "Leave shadow topic in place" checkbox when moving topics
  • +
  • [PHPBB3-10325] - Ability to disable the "I forgot my password" feature
  • +
  • [PHPBB3-10336] - Removing imagesets in 3.1
  • +
  • [PHPBB3-10344] - Add attachment icons to list of reports and queue
  • +
  • [PHPBB3-10362] - HTML5 Fix - "name" attribute is deprecated in anchors. Use "id" attribute instead.
  • +
  • [PHPBB3-10383] - Polls should use AJAX
  • +
  • [PHPBB3-10387] - generate_pagination() should export the current page number as a template variable
  • +
  • [PHPBB3-10390] - Add an ACP option to use jQuery via local copy or remote CDN
  • +
  • [PHPBB3-10410] - Add option to display users in their first teampage group
  • +
  • [PHPBB3-10412] - Use memory_get_peak_usage for debug output instead of memory_get_usage
  • +
  • [PHPBB3-10431] - Remove language from the button-graphics and use text strings instead
  • +
  • [PHPBB3-10438] - Alligning the Smileys on the same line as the text.
  • +
  • [PHPBB3-10484] - Use variables for sql_build_query() calls, so mods/extensions can extend the arrays
  • +
  • [PHPBB3-10510] - Quick-mod markup should be in the template
  • +
  • [PHPBB3-10524] - Wrong version code name in Ascraeus coding guidelines document.
  • +
  • [PHPBB3-10535] - Remove "Confirm Email Address" field from registration form
  • +
  • [PHPBB3-10557] - Missing IN_PHPBB check in phpBB/includes/functions_acp.php
  • +
  • [PHPBB3-10601] - Move inbox to top module in UCP Private Messages Tab
  • +
  • [PHPBB3-10614] - Script to manage extensions
  • +
  • [PHPBB3-10617] - prosilver clean up: adding proper css reset
  • +
  • [PHPBB3-10619] - prosilver clean up: removing duplicate colors
  • +
  • [PHPBB3-10632] - Merging style components
  • +
  • [PHPBB3-10640] - Bigger Topic Title Length
  • +
  • [PHPBB3-10650] - Last Topic Title on Forum list
  • +
  • [PHPBB3-10659] - Allow all administrators to purge cache
  • +
  • [PHPBB3-10665] - INCLUDEJS template tag
  • +
  • [PHPBB3-10679] - Add new permission for changing profile field information
  • +
  • [PHPBB3-10705] - Replace WARNINGS_ZERO_TOTAL with NO_WARNINGS for consistency
  • +
  • [PHPBB3-10714] - Create a class for add_log() and unit tests
  • +
  • [PHPBB3-10726] - Preview from Quick Reply
  • +
  • [PHPBB3-10727] - Don't hide quickreply with javascript
  • +
  • [PHPBB3-10733] - Adding file locator function to style class
  • +
  • [PHPBB3-10734] - CSS3 rounded corners
  • +
  • [PHPBB3-10741] - Automatically resize textarea
  • +
  • [PHPBB3-10743] - Change theme to style in php code
  • +
  • [PHPBB3-10762] - Separate style and phpBB version numbers in style.cfg
  • +
  • [PHPBB3-10771] - Using Remember Me instead of autologin or persistent keys in the UI.
  • +
  • [PHPBB3-10780] - Move colons from template files to language files
  • +
  • [PHPBB3-10783] - assets_version config var appended to assets (css/js) URLs to prevent caching
  • +
  • [PHPBB3-10786] - Render search options by default on memberlist.php
  • +
  • [PHPBB3-10799] - includejs should not put phpbb root path in generated template code
  • +
  • [PHPBB3-10800] - includejs test confusingly includes an html file
  • +
  • [PHPBB3-10864] - Allow extensions to be accessed via controller with shorter access name than "vendor/extname"
  • +
  • [PHPBB3-10933] - Make style code more understandable
  • +
  • [PHPBB3-10936] - MySQL fulltext search improvement - removing check for PCRE UTF support
  • +
  • [PHPBB3-10938] - Display subforum listing on forumlist via template loop instead of PHP implode()
  • +
  • [PHPBB3-10947] - Quickmod tools have stopped autosubmitting
  • +
  • [PHPBB3-10955] - ajaxify should take options as the only argument
  • +
  • [PHPBB3-10966] - Remove code duplication from mysql* and mssql* dbal
  • +
  • [PHPBB3-10968] - Render pagination fully within the template
  • +
  • [PHPBB3-10970] - Allow INCLUDE template macros to accept paths of the form {FOO}/a/{BAR}/c
  • +
  • [PHPBB3-10972] - Add a new method to phpbb_functional_test_case to allow a new user to be created
  • +
  • [PHPBB3-10973] - Allow mocks to be autoloaded in tests
  • +
  • [PHPBB3-10975] - Memberlist functional tests
  • +
  • [PHPBB3-10990] - $user->lang['COMMA_SEPARATOR'] - is not uniformly used
  • +
  • [PHPBB3-11001] - html5 Placeholder for search box.
  • +
  • [PHPBB3-11008] - Get rid of eval in javascript
  • +
  • [PHPBB3-11010] - HTML5 input types for form fields
  • +
  • [PHPBB3-11011] - Using dependency injection for global variables in all search backends.
  • +
  • [PHPBB3-11012] - Member variable phpEx vs php_ext naming inconstistency
  • +
  • [PHPBB3-11013] - Allow arrays to be assigned and retrieved in templates
  • +
  • [PHPBB3-11015] - Make DBAL classes autoloadable
  • +
  • [PHPBB3-11021] - Link back to main site config setting
  • +
  • [PHPBB3-11025] - Make Last topic title in forum list Bold
  • +
  • [PHPBB3-11032] - Better error reporting for sphinx
  • +
  • [PHPBB3-11037] - Cache drivers require globals
  • +
  • [PHPBB3-11044] - Compress class should keep track of files added and deal with conflicts
  • +
  • [PHPBB3-11048] - Use access specifiers in search backends
  • +
  • [PHPBB3-11050] - Add docblocks missing in properties and methods in all search backends.
  • +
  • [PHPBB3-11051] - Add retrieval functions for all public properties in search backends
  • +
  • [PHPBB3-11068] - Hiding foes posts should use JS to display them, rather then reloading the whole page
  • +
  • [PHPBB3-11070] - Redundant background-position property in Prosilver button CSS?
  • +
  • [PHPBB3-11082] - Redis cache driver should not have executable permission
  • +
  • [PHPBB3-11083] - Abstract cache drivers should use abstract keyword
  • +
  • [PHPBB3-11088] - Combine Style and Extension Management into one Customisations tab in the ACP
  • +
  • [PHPBB3-11116] - Adjust Display of Warning/Error Messages in Extensions Controller
  • +
  • [PHPBB3-11129] - Misleading subscription state messages
  • +
  • [PHPBB3-11152] - Create cached, compiled container class rather than compiling it on every page load
  • +
  • [PHPBB3-11156] - Delete "Misc" tab of forum based permissions + move items
  • +
  • [PHPBB3-11174] - Unit tests for search backends
  • +
  • [PHPBB3-11181] - Bump PHP requirement to 5.3.3
  • +
  • [PHPBB3-11183] - Remove $load_extensions and weird dl() calls
  • +
  • [PHPBB3-11189] - Merge DEBUG and DEBUG_EXTRA
  • +
  • [PHPBB3-11193] - Generalize phpbb_di_pass_collection_pass to handle all collections using service tags
  • +
  • [PHPBB3-11197] - Prefix the css classes for the small arrow with "arrow".
  • +
  • [PHPBB3-11202] - Add response status checks to functional tests
  • +
  • [PHPBB3-11215] - Separate root path for filesystem and urls/assets
  • +
  • [PHPBB3-11217] - Prefix for template values to give back value URL encoded
  • +
  • [PHPBB3-11238] - Specify goutte version
  • +
  • [PHPBB3-11250] - Remake the unit tests for the BBCode parser.
  • +
  • [PHPBB3-11259] - Make $phpbb_admin_path available everywhere
  • +
  • [PHPBB3-11268] - Delete phpbb_db_driver_mysql4 case
  • +
  • [PHPBB3-11275] - editor.js::colorPalette() breaks page with document.write > add proper target
  • +
  • [PHPBB3-11283] - Extensions as symlinks
  • +
  • [PHPBB3-11294] - Update extension list in running tests doc
  • +
  • [PHPBB3-11306] - Container should be created by a phpbb_create_default_container() function
  • +
  • [PHPBB3-11314] - Improve readability and code cleanup in new JavaScript files
  • +
  • [PHPBB3-11328] - New language variables for buttons
  • +
  • [PHPBB3-11373] - Notifications - Purge old with cron
  • +
  • [PHPBB3-11390] - Remove pagination from ucp_notifications.html when list is empty.
  • +
  • [PHPBB3-11393] - Give more information on database_updater about what exactly happened
  • +
  • [PHPBB3-11409] - No feedback provided when updating group position settings in ACP
  • +
  • [PHPBB3-11458] - Automatically add extension permission language files
  • +
  • [PHPBB3-11461] - [Template Event] - viewtopic_body_footer
  • +
  • [PHPBB3-11463] - Add topic title attribute in search results
  • +
  • [PHPBB3-11477] - Allow customisation of "Board index"
  • +
  • [PHPBB3-11482] - Extend syntax for DEFINE tag
  • +
  • [PHPBB3-11495] - Add nested sets implementation to phpBB core
  • +
  • [PHPBB3-11519] - Rename test event template file
  • +
  • [PHPBB3-11533] - Notification settings page is using topiclist class incorrectly
  • +
  • [PHPBB3-11557] - Allow to use tab when typing code and keep indentation
  • +
  • [PHPBB3-11558] - Notifications link in header should not include [ and ] -
  • +
  • [PHPBB3-11577] - Topiclist/Forumlist Needs tweaking after PR 1331
  • +
  • [PHPBB3-11582] - Split permission logic from translations
  • +
  • [PHPBB3-11600] - Increase code test coverage of avatar manager
  • +
  • [PHPBB3-11606] - make_clickable() in includes/functions_content.php uses deprecated preg_replace() /e modifier (PREG_REPLACE_EVAL)
  • +
  • [PHPBB3-11615] - Partial refactoring of session tests
  • +
  • [PHPBB3-11620] - Improve session test coverage
  • +
  • [PHPBB3-11621] - Improve MySQL fulltext search indexes
  • +
  • [PHPBB3-11651] - Bootstrap container from config.php
  • +
  • [PHPBB3-11667] - phpbb_template_twig_node_includeasset should be abstract
  • +
  • [PHPBB3-11669] - Fix PHP bug #55124 (/./ in recursive mkdirs)
  • +
  • [PHPBB3-11684] - No utility to time-wasting user login confirmation message/screen
  • +
  • [PHPBB3-11685] - No utility to time-wasting user logout confirmation message/screen
  • +
  • [PHPBB3-11700] - Use namespaces rather than prefixes for class names
  • +
  • [PHPBB3-11703] - Make "Serve jQuery using Google’s CDN" generic
  • +
  • [PHPBB3-11724] - Support "ELSE IF" and "ELSEIF" in the same way
  • +
  • [PHPBB3-11744] - Group join request notification
  • +
  • [PHPBB3-11745] - Group join approved notification
  • +
  • [PHPBB3-11747] - UCP Prefs Core and Template Events
  • +
  • [PHPBB3-11749] - PHP and Template Event Requests for Topic Preview Extension
  • +
  • [PHPBB3-11784] - Remove naming redundancy for event listeners
  • +
  • [PHPBB3-11786] - Fix various defects in PHPDoc in-code documentation
  • +
  • [PHPBB3-11795] - Move all JavaScript from HTML code to external files
  • +
  • [PHPBB3-11813] - Mock authentication provider should implement base provider class
  • +
  • [PHPBB3-11831] - Update fabpot/goutte to 1.0.*
  • +
+

New Feature

+ +

Sub-task

+
    +
  • [PHPBB3-9556] - Drop php closing tags
  • +
  • [PHPBB3-9574] - Drop fallback implementations
  • +
  • [PHPBB3-9688] - update_session API
  • +
  • [PHPBB3-9738] - Make installer and updater use migrations
  • +
  • [PHPBB3-9797] - Adjust existing access to superglobals
  • +
  • [PHPBB3-10817] - Use valid composer.json instead of non-standard extension.json
  • +
  • [PHPBB3-10992] - Use updated Goutte in Fileupload tests
  • +
  • [PHPBB3-11109] - Create a separate set of compress tests for the develop branch
  • +
  • [PHPBB3-11243] - Topics with attachments only show "download all attachments" links on pages containing attachments.
  • +
  • [PHPBB3-11318] - Extensions use migrations
  • +
  • [PHPBB3-11351] - Add appropriate language strings for errors
  • +
  • [PHPBB3-11531] - Add functional tests for new avatar system
  • +
  • [PHPBB3-11637] - generate_text_for_display on search.php
  • +
  • [PHPBB3-11638] - generate_text_for_display on viewtopic.php
  • +
  • [PHPBB3-11639] - generate_text_for_display on includes/functions_posting.php
  • +
  • [PHPBB3-11640] - generate_text_for_display on includes/functions_privmsgs.php
  • +
  • [PHPBB3-11641] - generate_text_for_display on includes/mcp/mcp_pm_reports.php
  • +
  • [PHPBB3-11642] - generate_text_for_display on includes/mcp/mcp_post.php
  • +
  • [PHPBB3-11643] - generate_text_for_display on includes/mcp/mcp_queue.php
  • +
  • [PHPBB3-11653] - generate_text_for_display on includes/mcp/mcp_topic.php
  • +
  • [PHPBB3-11654] - generate_text_for_display on includes/mcp/mcp_warn.php
  • +
  • [PHPBB3-11655] - generate_text_for_display on includes/ucp/ucp_pm_viewmessage.php
  • +
  • [PHPBB3-11656] - generate_text_for_display on memberlist.php
  • +
+

Task

+
    +
  • [PHPBB3-7090] - Update minimum PHP version to 5.2
  • +
  • [PHPBB3-9557] - Update coding guidelines for 3.1 and PHP >= 5.2
  • +
  • [PHPBB3-9682] - Add a class loader for auto loading and define naming rules for new phpbb classes
  • +
  • [PHPBB3-9783] - Restore subsilver2
  • +
  • [PHPBB3-9867] - Adjust the implementation of error messages localization
  • +
  • [PHPBB3-9983] - Restructure ACM classes
  • +
  • [PHPBB3-9988] - Replace config with an instance of a class implementing ArrayAccess
  • +
  • [PHPBB3-10091] - Bump minimum required postgresql version for 3.1
  • +
  • [PHPBB3-10173] - Move birthday list logic into templates
  • +
  • [PHPBB3-10202] - Provide a mechanism to manually retrieve long configuration options from a TEXT column
  • +
  • [PHPBB3-10260] - Remove prosilver styleswitcher
  • +
  • [PHPBB3-10314] - Whitelist all files in includes for code coverage reports
  • +
  • [PHPBB3-10389] - JSON extension should be checked in the installer
  • +
  • [PHPBB3-10414] - Functional testing
  • +
  • [PHPBB3-10467] - Check extensions diff for classes/constants not existing in php 5.2
  • +
  • [PHPBB3-10609] - Prefix phpBB functions with phpbb_ to prevent compatibility issues with other software
  • +
  • [PHPBB3-10670] - Require PHP 5.3 at minimum for phpBB 3.1
  • +
  • [PHPBB3-10680] - Add ext/ to .gitignore
  • +
  • [PHPBB3-10688] - Change 3.0 language to 3.1
  • +
  • [PHPBB3-10693] - Change minimum PHP version for Ascraeus to 5.3.2
  • +
  • [PHPBB3-10719] - Remove second 5.2 test suite on ascreaus
  • +
  • [PHPBB3-10732] - Add config_dev.php and config_test.php to .gitignore
  • +
  • [PHPBB3-10855] - Coding guideline change - have curly brackets on same line in JS
  • +
  • [PHPBB3-10869] - Remove PHP 5.2 check from .travis.yml
  • +
  • [PHPBB3-10877] - Have bamboo generate and publish a phpBB package for every build.
  • +
  • [PHPBB3-10882] - Expand test coverage for template engine - add tests for invalid constructs
  • +
  • [PHPBB3-10893] - Update the Usage of Composer
  • +
  • [PHPBB3-10909] - Update Travis Test Configuration: Travis no longer supports PHP 5.3.2
  • +
  • [PHPBB3-10932] - Store composer.phar in the phpBB repository to make sure a working version is always available
  • +
  • [PHPBB3-10939] - Modify the phpbb_request class to handle the $_FILES superglobal as well
  • +
  • [PHPBB3-10941] - Write tests for includes/functions_upload.php
  • +
  • [PHPBB3-10944] - Allow INCLUDEJS to include javascript from the assets directory
  • +
  • [PHPBB3-10949] - 3.1 AJAX code should use new coding guidelines
  • +
  • [PHPBB3-10963] - Use fileinfo in filespec::is_image() instead of trusting the mimetype sent by the browser
  • +
  • [PHPBB3-10969] - Remove remove_comments() and remove_remarks()
  • +
  • [PHPBB3-10993] - Update README to use composer.phar from the repository
  • +
  • [PHPBB3-10994] - Revert changes in PHPBB3-10963
  • +
  • [PHPBB3-11054] - Improper @var documentation syntax in includes/extension/controller.php
  • +
  • [PHPBB3-11061] - Fix README composer instructions
  • +
  • [PHPBB3-11195] - Put conditional opening brace on its own line, as per guidelines
  • +
  • [PHPBB3-11225] - Delete subsilver2 mcp_jumpbox.html
  • +
  • [PHPBB3-11287] - Add Template Event naming guidelines to docs/coding-guidelines.html
  • +
  • [PHPBB3-11338] - Enable Redis tests on Travis
  • +
  • [PHPBB3-11441] - Refactor phpbb_user_loader tests
  • +
  • [PHPBB3-11476] - Remove pass-by-reference from phpbb_db_driver::sql_multi_insert
  • +
  • [PHPBB3-11481] - Feed classes are currently all in feed.php
  • +
  • [PHPBB3-11698] - Move all autoloadable files to phpbb/ rather than includes/
  • +
  • [PHPBB3-11722] - Remove reference assignment for $captcha in report.php
  • +
  • [PHPBB3-11760] - 3.0.x Migration should use the phpbb_version_compare() wrapper.
  • +
  • [PHPBB3-11818] - Upgrade Symfony to 2.3 LTS
  • +
  • [PHPBB3-11870] - No longer exclude ./phpBB/phpbb/search/fulltext_*.php from code coverage
  • +
  • [PHPBB3-11885] - Add migrations for 3.0.12-RCx and 3.0.12
  • +
  • [PHPBB3-11913] - Apply reorganisation of download.phpbb.com to build_announcement.php
  • +
+ +

Changes since 3.0.13-PL1

+ +

Security

+
    +
  • [SECURITY-180] - An insufficient check allowed users of the Google Chrome browser to be redirected to external domains (e.g. on login)
  • +
+

Bug

+
    +
  • [PHPBB3-13348] - sql_freeresult() should be called in feed base class
  • +
  • [PHPBB3-13414] - download/file.php sends Content-Length header even when issuing 304 Not Modified
  • +
  • [PHPBB3-13555] - Poll options preview rendered incorrectly by <br /> collision
  • +
  • [PHPBB3-13568] - Imagick path validated as relative path although ACP asks for absolute path
  • +
  • [PHPBB3-13617] - Bot session continuation with invalid f= query parameter causes SQL error
  • +
  • [PHPBB3-13738] - Sami still refers to develop-* branches
  • +
+

Improvement

+
    +
  • [PHPBB3-12089] - Make HTTP status code assertion failure messages more informative
  • +
  • [PHPBB3-13765] - Verify that SERVER_PROTOCOL has the expected format
  • +
+

Task

+ + +

Changes since 3.0.13

+ +

Bug

+
    +
  • [PHPBB3-12933] - The search operator for partial matches does not work
  • +
  • [PHPBB3-13549] - Compare ORIG_PATH_INFO with SCRIPT_NAME for checking trailing paths
  • +
  • [PHPBB3-13554] - Advertisement of feature release in red indicates a problem
  • +
+ +

Changes since 3.0.12

+ +

Security

+
    +
  • [PHPBB3-13531] - Disallow trailing paths (e.g. using the PATH_INFO feature) to prevent path-relative CSS injection
  • +
  • [PHPBB3-13526] - Correctly validate ucp_pm_options form key
  • +
+

Bug

+
    +
  • [PHPBB3-6703] - Problem with russian letter while converting from 2.0.x
  • +
  • [PHPBB3-8960] - Allow changing allow_avatar_remote when images/avatars/upload is not writable
  • +
  • [PHPBB3-9420] - BBCode - Unable to use a proper URI token
  • +
  • [PHPBB3-9724] - Wrong return "Return to ACP"
  • +
  • [PHPBB3-9725] - MSSQL Schema is not azure compatible
  • +
  • [PHPBB3-10023] - Password change requirement notification in UCP is not noticable
  • +
  • [PHPBB3-10423] - Searching for the term "test *" will highlight nearly every word and displays htmlspecialchars as htmlentities.
  • +
  • [PHPBB3-10442] - XHTML is invalid when a forum link without redirect counter is present
  • +
  • [PHPBB3-10687] - UNABLE_GET_IMAGE_SIZE text misleading for remote avatars
  • +
  • [PHPBB3-10729] - Post editor information is not updated when user being deleted with posts
  • +
  • [PHPBB3-10776] - Grammar errors in docs/README.html
  • +
  • [PHPBB3-10796] - SQL Azure does not allow SELECT FROM sysfiles
  • +
  • [PHPBB3-10851] - HTML files containing certain tags being rejected as possible attack vectors with "Check attachment file" set to "No"
  • +
  • [PHPBB3-10863] - Permission mask does not accurately show some forum permissions if user has MOD parmissions
  • +
  • [PHPBB3-10917] - Updater notice "Update files are out of date..." when updating to unreleased version
  • +
  • [PHPBB3-10985] - Error bbcode.html not found when updating with custom style inheriting from prosilver
  • +
  • [PHPBB3-11062] - In Automatic Update, new language strings from install.php are only loaded from English
  • +
  • [PHPBB3-11224] - SQL cache destroy does not destroy queries to tables joined
  • +
  • [PHPBB3-11288] - "Fulltext native" search fooled by hyphens
  • +
  • [PHPBB3-11480] - Prevent Private Message system from returning "Unknown folder" when inbox folder is full
  • +
  • [PHPBB3-11613] - Cookies do not work for netbios domain
  • +
  • [PHPBB3-11686] - Not checking for phpBB Debug errors on functional tests
  • +
  • [PHPBB3-11699] - PHP Lint Test should exclude selected subdirectories of the build directory.
  • +
  • [PHPBB3-11726] - Don't run lint tests on Travis on postgres
  • +
  • [PHPBB3-11762] - generate_text_for_display() treats "0" as an empty string
  • +
  • [PHPBB3-11789] - Inline css with color value in subsilver2
  • +
  • [PHPBB3-11794] - Coding Guidelines document says to place a comma after every array element, but fails to do so itself
  • +
  • [PHPBB3-11799] - Anti Abuse Headers missing for sendpassword
  • +
  • [PHPBB3-11811] - Chrome 30 adds outline to focused elements
  • +
  • [PHPBB3-11821] - Wrong comma usage "You are receiving this notification"
  • +
  • [PHPBB3-11823] - Travis-CI webserver not matching PHP files with anything after the .php
  • +
  • [PHPBB3-11829] - Closed reports may seem open in detailed view
  • +
  • [PHPBB3-11860] - .htaccess not working for Apache 2.4
  • +
  • [PHPBB3-11864] - Do not call exit after display_progress_bar in acp_forums
  • +
  • [PHPBB3-11879] - Compatibility error in forum_fn.js: .live should be replaced with .on
  • +
  • [PHPBB3-11968] - Travis Image are broken due to repository rename
  • +
  • [PHPBB3-12037] - acp_inactive.html has hard-coded text
  • +
  • [PHPBB3-12048] - Custom BBCodes Fail to Render Language Strings with a Number
  • +
  • [PHPBB3-12061] - Keyboard shortcut alt+h doesn't work properly in firefox
  • +
  • [PHPBB3-12072] - Missing word "send" in comment in schema_data.sql
  • +
  • [PHPBB3-12093] - IE 11 javascript selection is no longer supported
  • +
  • [PHPBB3-12118] - Add noindex meta tag to subsilver2 pm/topic view-print template
  • +
  • [PHPBB3-12119] - Remove keywords and description meta tags from prosilver view-print templates
  • +
  • [PHPBB3-12120] - Update docs/AUTHORS for 3.0.13-RC1
  • +
  • [PHPBB3-12140] - Avoid endless loop in build script
  • +
  • [PHPBB3-12161] - build/save directories are no longer created
  • +
  • [PHPBB3-12162] - Binary files missing from update packages
  • +
  • [PHPBB3-12176] - No error shown when attempting to delete a founder
  • +
  • [PHPBB3-12186] - MCP should open "Reported posts" instead of PM Reports
  • +
  • [PHPBB3-12188] - Add php 5.6 to travis tests
  • +
  • [PHPBB3-12202] - Variables read from style.cfg etc. should be htmlspecialchared
  • +
  • [PHPBB3-12205] - Custom Profile Field display bug
  • +
  • [PHPBB3-12210] - dbtools::sql_create_table incorrectly throws error related to auto-increment length on non auto-increment fields
  • +
  • [PHPBB3-12310] - SMTP username and password should not autocomplete during install
  • +
  • [PHPBB3-12316] - develop-ascraeus build status missing from "Automated Testing" section in README.md
  • +
  • [PHPBB3-12353] - User attachments in ACP are not displaying every attachment
  • +
  • [PHPBB3-12359] - Day and Month of Birthday Misaligned When Editing
  • +
  • [PHPBB3-12381] - Broken error message when selecting invalid DB driver
  • +
  • [PHPBB3-12397] - db_tools::sql_unique_index_exists() has wrong doc block
  • +
  • [PHPBB3-12429] - Update phpunit to 3.8+
  • +
  • [PHPBB3-12467] - Add config_*.php and tests_config_*.php to .gitignore
  • +
  • [PHPBB3-12472] - Set fast finish for .travis.yml
  • +
  • [PHPBB3-12485] - Broken tests due to absolute exclude
  • +
  • [PHPBB3-12492] - DB_TEST: Special chars are not supported.
  • +
  • [PHPBB3-12540] - WRONG_FILESIZE contains broken placeholders
  • +
  • [PHPBB3-12660] - Undefined offset error when phpinfo() disabled and debug enabled
  • +
  • [PHPBB3-12695] - Undefined index: MISSING_INLINE_ATTACHMENT notice given when viewing post details
  • +
  • [PHPBB3-12720] - Git commit hook should not require commit message to start with a capital letter
  • +
  • [PHPBB3-12741] - Functional tests on Travis fail since php update last night
  • +
  • [PHPBB3-12755] - Remote upload stuck in infinite loop if server sends keep-alive
  • +
  • [PHPBB3-13086] - Update ACP_MASS_EMAIL_EXPLAIN language key
  • +
  • [PHPBB3-13096] - ldap_escape() added to PHP 5.6.0
  • +
  • [PHPBB3-13138] - Banned users cause infinite recursion
  • +
  • [PHPBB3-13168] - Warning displayed in PHP 5.6 for mbstring.http_input
  • +
  • [PHPBB3-13234] - Remember me cookie gets unset by admin reauthentication
  • +
  • [PHPBB3-13341] - Tests fail when generating coverage report
  • +
  • [PHPBB3-13376] - deregister_globals() does not work correctly when $_COOKIE['GLOBALS'] - is specified
  • +
  • [PHPBB3-13519] - Correctly validate imagick path as path and not string
  • +
  • [PHPBB3-13523] - PHP 5.2 Unit Tests no longer work due to deprecated PHPUnit PEAR channel
  • +
  • [PHPBB3-13527] - Escape information received from version server
  • +
+

Improvement

+
    +
  • [PHPBB3-10037] - Add Smiley Buttons in Signature Editor
  • +
  • [PHPBB3-10174] - Rename "Ban usernames" to "Ban users" in ACP
  • +
  • [PHPBB3-10549] - Languages variables should be used, not hardcoded
  • +
  • [PHPBB3-10555] - Copyright notice in overall_header.html is not translatable
  • +
  • [PHPBB3-10945] - Show entered search query in the search box when no results are found.
  • +
  • [PHPBB3-11254] - Check CRLF line endings in the test suite
  • +
  • [PHPBB3-11295] - Drop tables for postgres in the test suite
  • +
  • [PHPBB3-11297] - Running tests doc should mention dbunit dependency
  • +
  • [PHPBB3-11704] - phing build script does not include vendor folder, even if there are dependencies
  • +
  • [PHPBB3-11766] - Remove Quote and Edit button when topic is lock
  • +
  • [PHPBB3-11801] - missing semi colons in css
  • +
  • [PHPBB3-11814] - Topic reply notification email text change
  • +
  • [PHPBB3-12035] - Add a link to user's posts in the ACP user overview page
  • +
  • [PHPBB3-12106] - Document exceptions to "Disable Board" in ACP.
  • +
  • [PHPBB3-12146] - Add color demo when editing a group from the UCP
  • +
  • [PHPBB3-12247] - include poster's username in email notifications of posts that get approved by moderators
  • +
  • [PHPBB3-12259] - Too many redundant tests are run on Travis
  • +
  • [PHPBB3-12468] - Allow mbstring.http_input='' besides 'pass' for PHP 5.6 compatibility
  • +
+

Task

+
    +
  • [PHPBB3-10839] - Remove phpunit.xml.functional and always include functional tests
  • +
  • [PHPBB3-11509] - Travis should check commit message format
  • +
  • [PHPBB3-11876] - Upgrade package checksums from MD5 to SHA256
  • +
  • [PHPBB3-11877] - Create package download links and checksums for announcement via script
  • +
  • [PHPBB3-11920] - Add MariaDB tests to Travis-CI
  • +
  • [PHPBB3-11951] - Add MariaDB to supported RDBMS list
  • +
  • [PHPBB3-11970] - Use 'set -x' in Travis CI setup scripts
  • +
  • [PHPBB3-12046] - Use PHP_BINARY environment variable in lint unit test
  • +
  • [PHPBB3-12056] - Make sure each unit test runs on its own
  • +
  • [PHPBB3-12147] - Remove Travis CI notification configuration
  • +
  • [PHPBB3-12302] - Upgrade composer.phar to 1.0.0-alpha8
  • +
  • [PHPBB3-12318] - Correctly setup HHVM functional tests on Travis CI
  • +
  • [PHPBB3-12319] - Backport Travis CI HHVM environment enabling to develop-olympus.
  • +
  • [PHPBB3-12320] - No longer allow Travis CI HHVM environment to fail
  • +
  • [PHPBB3-12341] - Add tests for get_username_string()
  • +
  • [PHPBB3-12384] - Run Travis CI HHVM tests against MySQLi instead of MySQL
  • +
  • [PHPBB3-12417] - hhvm-nightly 2014.04.16~precise breaks tests
  • +
  • [PHPBB3-12495] - Add Sami to composer dependencies and build script
  • +
  • [PHPBB3-12582] - Strip away copyrighted ICC profile from images
  • +
  • [PHPBB3-12917] - Move commit check and file executable checks to 5.3.3 build on travis
  • +
  • [PHPBB3-13324] - Composer no longer downloads sami/sami and fabpot/goutte
  • +
+ +

Changes since 3.0.11

+ +

Bug

+
    +
  • [PHPBB3-6723] - Empty message in deleted messages in PM history
  • +
  • [PHPBB3-7262] - Clarify docs about is_dynamic not being updated by set_config()
  • +
  • [PHPBB3-8319] - LOCAL_URL not enforced in bbcodes
  • +
  • [PHPBB3-9551] - Mysql fulltext index creation fails due to partial collation change
  • +
  • [PHPBB3-9975] - Hard coded language in sessions.php
  • +
  • [PHPBB3-10184] - Bots can be sent private messages
  • +
  • [PHPBB3-10491] - Fatal error in functional tests when server returns 404
  • +
  • [PHPBB3-10568] - Modify the trigger language when you edit a PM
  • +
  • [PHPBB3-10602] - A bug in mail queue processing
  • +
  • [PHPBB3-10661] - UCP > PM > Compose > enumerated recipients > BCC group misses a &nbsp; (prosilver)
  • +
  • [PHPBB3-10678] - Provide Firebird, Oracle, and increased MSSQL support in unit tests
  • +
  • [PHPBB3-10772] - trigger_error is using the default style
  • +
  • [PHPBB3-10789] - PM print template (prosilver) with unnecessary variables
  • +
  • [PHPBB3-10820] - Display images directly in IE9 and 10 instead of download
  • +
  • [PHPBB3-10828] - PostgreSQL dbal tests try to connect to the database named as user specified in configuration
  • +
  • [PHPBB3-10838] - Functional tests are not mentioned in RUNNING_TESTS.txt
  • +
  • [PHPBB3-10840] - If you add a member to a group, the form_token can be set to 0 if the creation_time is 0 too. Maybe even if creation_time is unchanged.
  • +
  • [PHPBB3-10848] - Wrong redirect to installer from acp
  • +
  • [PHPBB3-10850] - create_schema_files.php is not creating the oracle or postgres' schema file properly
  • +
  • [PHPBB3-10879] - prosilver: attachment-link will be displayed wrong, when filename is too long
  • +
  • [PHPBB3-10880] - m_approve should not imply f_noapprove
  • +
  • [PHPBB3-10896] - board_email & board_contact are not validated as email addresses in ACP
  • +
  • [PHPBB3-10897] - Bot Definitions are outdated
  • +
  • [PHPBB3-10918] - docs/INSTALL.html claims there are tar.gz packages
  • +
  • [PHPBB3-10943] - Search Box should display keywords entered by the user
  • +
  • [PHPBB3-10967] - PHPBB_USE_BOARD_URL_PATH not implemented in posting_gen_topic_icons
  • +
  • [PHPBB3-10986] - Invalid email message ids because config variable server_name is used even when force server URL settings is disabled
  • +
  • [PHPBB3-10995] - Return value of $db->sql_fetchrow() on empty tables is not consistent on mssqlnative
  • +
  • [PHPBB3-10996] - Travis tests fail on Postgres because database does not exist
  • +
  • [PHPBB3-11034] - The functional test case framework does not install a full board each time
  • +
  • [PHPBB3-11066] - MSSQLnative driver contains debug code error_reporting(E_ALL)
  • +
  • [PHPBB3-11069] - missing closing span in subsilver2 simple_footer.html
  • +
  • [PHPBB3-11081] - Duplicated /TD in styles/subsilver2/template/catpcha_qa.html
  • +
  • [PHPBB3-11093] - acp_users_overview.html has a wrongly placed </dd>
  • +
  • [PHPBB3-11094] - prosilver: searching for users: no textbox for Jabber
  • +
  • [PHPBB3-11105] - Missing mandatory space in meta http-equiv=refresh
  • +
  • [PHPBB3-11112] - phpBB Footer Link should be SSL
  • +
  • [PHPBB3-11122] - Update docs/AUTHORS for 3.0.12-RC1
  • +
  • [PHPBB3-11144] - {FORUM_NAME} is not filled in login mask when logging into a password protected forum
  • +
  • [PHPBB3-11145] - ATTACHED_IMAGE_NOT_IMAGE thrown because of file limit in php.ini
  • +
  • [PHPBB3-11158] - modules table lacks acl_u_sig for signature module
  • +
  • [PHPBB3-11159] - Coding guidelines: static public
  • +
  • [PHPBB3-11164] - Composer not finding symfony/config in PHP 5.3.3
  • +
  • [PHPBB3-11178] - database_update.php should not set error_reporting to E_ALL
  • +
  • [PHPBB3-11186] - Database unit tests fail on windows using sqlite2
  • +
  • [PHPBB3-11190] - Functional tests do not clear the cache between each test
  • +
  • [PHPBB3-11196] - /includes/session.php sends 401 HTTP status with "Not authorized" instead of "Unauthorized"
  • +
  • [PHPBB3-11219] - Database sequences are not updated for tests using fixtures with auto_incremented columns
  • +
  • [PHPBB3-11227] - @return void -> @return null
  • +
  • [PHPBB3-11233] - Anonymous can be selected as a PM recipient
  • +
  • [PHPBB3-11248] - CRLF line endings
  • +
  • [PHPBB3-11262] - .lock files are not in .gitignore
  • +
  • [PHPBB3-11265] - Functional tests do not assert that board installation succeeded
  • +
  • [PHPBB3-11269] - Travis functional test case errors
  • +
  • [PHPBB3-11278] - Firebird tables are not removed correctly on 3.0.9-rc1 update
  • +
  • [PHPBB3-11291] - "Could not open input file: ../composer.phar" error during phing's create-package
  • +
  • [PHPBB3-11292] - Newlines removed in display of PM reports, no clickable links in PM reports
  • +
  • [PHPBB3-11301] - "String offset cast occured" error on PHP 5.4
  • +
  • [PHPBB3-11304] - check_form_key breaks in tests when form is submitted in the same second it is retrieved
  • +
  • [PHPBB3-11343] - Loose string comparison during new password activation
  • +
  • [PHPBB3-11355] - Incorrect error message when no user selected for action on group membership management page
  • +
  • [PHPBB3-11358] - Success message even withot selecting a user and performing a group operation
  • +
  • [PHPBB3-11361] - "Array to string conversion" error in $user->format_date()
  • +
  • [PHPBB3-11493] - Functional tests should fail if any debug output is made
  • +
  • [PHPBB3-11517] - Numbering is wrong in coding guidelines
  • +
  • [PHPBB3-11536] - Installer incorrectly removes /install from script_path
  • +
  • [PHPBB3-11537] - UCP group manage page's error box differs heavily from the rest of the UCP
  • +
  • [PHPBB3-11538] - SQL error on UCP groups manage page caused by setting color to 7 characters long string
  • +
  • [PHPBB3-11544] - Add admin_login() to 3.0 functional test case
  • +
  • [PHPBB3-11545] - is_absolute() should not depend on DIRECTORY_SEPARATOR
  • +
  • [PHPBB3-11546] - is_absolute() throws E_NOTICE for empty string
  • +
  • [PHPBB3-11547] - Test fixtures do not support utf8 characters
  • +
  • [PHPBB3-11548] - Untranslated TOO_SHORT in UCP "Manage Groups"
  • +
  • [PHPBB3-11566] - Reporting a post should require a captcha to be solved by guests
  • +
  • [PHPBB3-11568] - Functional tests fail with retrieving install pages using file_get_contents
  • +
  • [PHPBB3-11575] - phpbb_dbal_order_lower_test::test_cross_join should be called test_order_lower
  • +
  • [PHPBB3-11578] - Missing underscore after function prefix in validate_data()
  • +
  • [PHPBB3-11579] - Add unit tests for validate_data()
  • +
  • [PHPBB3-11580] - Avoid API Limit from composer downloads on github
  • +
  • [PHPBB3-11588] - install/install_update.php should use version.phpbb.com instead of www
  • +
  • [PHPBB3-11590] - Close database connections from tests whenever possible
  • +
  • [PHPBB3-11601] - Allow manual resync of database columns in unit tests not only on fixture load
  • +
  • [PHPBB3-11603] - git-tools use invalid api urls
  • +
  • [PHPBB3-11604] - Functional tests fail when phpBB can not create the config file
  • +
  • [PHPBB3-11617] - Missing U_ACTION in acp_captcha.php
  • +
  • [PHPBB3-11618] - Template tests fail on some systems due to a PHP error in glob()
  • +
  • [PHPBB3-11619] - get_remote_file() should use HTTP 1.0
  • +
  • [PHPBB3-11630] - Improvements to the PHP lint pre-commit hook
  • +
  • [PHPBB3-11644] - Skip phpbb_dbal_order_lower_test on MySQL 5.6
  • +
  • [PHPBB3-11662] - "occured" should be "occurred"
  • +
  • [PHPBB3-11670] - Replace trademark ™ with ® on "Welcome to phpBB" install page
  • +
  • [PHPBB3-11674] - Do not include vendor folder if there are no dependencies.
  • +
  • [PHPBB3-11524] - MySQL Upgrader throws warnings on PHP 5.4
  • +
  • [PHPBB3-11720] - Reporting posts leads to white page error
  • +
  • [PHPBB3-11769] - Wrong poster in subscription email when poster is using the Quote button
  • +
  • [PHPBB3-11775] - Error while moving posts to a new topic
  • +
  • [PHPBB3-11802] - Undefined variable $browser in /download/file.php
  • +
+

Improvement

+
    +
  • [PHPBB3-8743] - New topic / reply notifications do not contain author's name.
  • +
  • [PHPBB3-10050] - subsilver2: Do not show "Mark topics as read" when there are no topics
  • +
  • [PHPBB3-10205] - More informative reporting of errors when database connection fails (MySQL and others)
  • +
  • [PHPBB3-10716] - PHP-parse all php files as part of the test suite
  • +
  • [PHPBB3-10841] - Disable style and language selectors if there's only one installed.
  • +
  • [PHPBB3-10854] - sql server drop default constraint when dropping column
  • +
  • [PHPBB3-10865] - Updated and Added to docs/INSTALL.html
  • +
  • [PHPBB3-10873] - Change language entry for deleted PMs
  • +
  • [PHPBB3-10981] - Upgrade Goutte and use Composer for Installation
  • +
  • [PHPBB3-11131] - Phrasing & semantics of Board settings
  • +
  • [PHPBB3-11162] - Get rid of $db->sql_return_on_error(true) trickery when splitting/merging topics
  • +
  • [PHPBB3-11192] - Add Tebibyte to get_formatted_filesize()
  • +
  • [PHPBB3-11202] - Add response status checks to functional tests
  • +
  • [PHPBB3-11220] - Improve tooltip explaining the [list=] - BBcode
  • +
  • [PHPBB3-11238] - Specify goutte version
  • +
  • [PHPBB3-11285] - Use more granularity in dependency checks in compress test
  • +
  • [PHPBB3-11293] - Prefer mysqli over mysql due to php 5.5 alpha 2 deprecating mysql
  • +
  • [PHPBB3-11294] - Update extension list in running tests doc
  • +
  • [PHPBB3-11368] - Latest pm reports row count
  • +
  • [PHPBB3-11583] - InnoDB supports FULLTEXT index since MySQL 5.6.4.
  • +
  • [PHPBB3-11740] - Update link in FAQ to Ideas Centre
  • +
  • [PHPBB3-11873] - Prevent expensive hash computation in phpbb_check_hash() by rejecting very long passwords
  • +
+

Sub-task

+
    +
  • [PHPBB3-10974] - Move tests/mock_user.php to tests/mock/user.php
  • +
  • [PHPBB3-11009] - Backport phing build.xml from develop to develop-olympus so it uses composer.
  • +
  • [PHPBB3-11540] - Add unit tests for (phpbb_)is_absolute()
  • +
  • [PHPBB3-11541] - Add unit tests for style_select() in functions.php
  • +
  • [PHPBB3-11542] - Add unit tests for language_select() in functions.php
  • +
  • [PHPBB3-11543] - Add unit tests for obtain online functions in functions.php
  • +
+

Task

+
    +
  • [PHPBB3-10877] - Have bamboo generate and publish a phpBB package for every build.
  • +
  • [PHPBB3-11045] - Add unit tests for the compress class
  • +
  • [PHPBB3-11059] - Fix README logo
  • +
  • [PHPBB3-11060] - Fix travis.yml pyrus config
  • +
  • [PHPBB3-11240] - Turn on PHPUnit's verbose mode on Travis
  • +
  • [PHPBB3-11324] - Add PHP 5.5 environment on Travis-CI
  • +
  • [PHPBB3-11337] - Run functional tests on Travis CI
  • +
  • [PHPBB3-11513] - Install PHPUnit via Composer's require-dev to simplify test running (no need for pear)
  • +
  • [PHPBB3-11526] - Increase composer minimum-stability from beta to stable
  • +
  • [PHPBB3-11527] - Upgrade composer.phar to 1.0.0-alpha7
  • +
  • [PHPBB3-11529] - Rename RUNNING_TESTS file to .md file to render it on GitHub
  • +
  • [PHPBB3-11576] - Make phpBB Test Suite MySQL behave at least as strict as phpBB MySQL driver
  • +
  • [PHPBB3-11671] - Add phing/phing to composer.json
  • +
  • [PHPBB3-11752] - Update phpBB.com URLs to https in email templates
  • +
  • [PHPBB3-11753] - Upgrade mysql_upgrader.php schema data.
  • +
+ +

Changes since 3.0.10

+ +

Bug

+
    +
  • [PHPBB3-7432] - Unclear language for Inactive Users on ACP main page
  • +
  • [PHPBB3-8652] - Duplicate Emails Sent When Subscribed to Forum and Topic
  • +
  • [PHPBB3-9079] - Display backtrace on all E_USER_ERROR errors, not only SQL errors (when DEBUG_EXTRA is enabled)
  • +
  • [PHPBB3-9084] - Unable to display 'option equal to non entered value' if dropdown CPF is not required
  • +
  • [PHPBB3-9089] - PM message title box not accessible via Tab key
  • +
  • [PHPBB3-9220] - Blue border width when table in a div
  • +
  • [PHPBB3-9681] - Password length not in security settings
  • +
  • [PHPBB3-9813] - fulltext_native.php on innodb loading deadly slow for big indexes
  • +
  • [PHPBB3-9831] - Cannot change default of Boolean checkbox custom profile field
  • +
  • [PHPBB3-10094] - Clear cache before phpBB installation
  • +
  • [PHPBB3-10129] - Missing apostrophes in ACP user management -> permissions
  • +
  • [PHPBB3-10349] - Unit tests do not remove comments from schemas
  • +
  • [PHPBB3-10399] - Special characters aren't parsed in style component variables
  • +
  • [PHPBB3-10401] - auth_ldap has an incorrect return value in login_ldap()
  • +
  • [PHPBB3-10407] - Incorrect check for empty image file paths during conversion
  • +
  • [PHPBB3-10428] - optionget/optionset functions in session.php and acp_users.php incorrectly check whether $data is at its default value
  • +
  • [PHPBB3-10456] - Subsilver2 does not define $CAPTCHA_TAB_INDEX
  • +
  • [PHPBB3-10508] - Marking forums as read displays misleading language
  • +
  • [PHPBB3-10511] - Grammar defect in permissions language
  • +
  • [PHPBB3-10512] - Test failure when no default timezone is set in php
  • +
  • [PHPBB3-10532] - Out of range $start causes a page with no search results but with pagination
  • +
  • [PHPBB3-10538] - Special character are not correctly parsed for SMTP protocol
  • +
  • [PHPBB3-10542] - Incorrect class="postlink" in styles/subsilver2/template/faq_body.html
  • +
  • [PHPBB3-10546] - Argument missing for adm_back_link() in acp_captcha.php
  • +
  • [PHPBB3-10561] - All users can choose deactivated styles.
  • +
  • [PHPBB3-10569] - template/ucp_main_front.html does not correctly handle active topic with the name "0"
  • +
  • [PHPBB3-10580] - Default tz in registration dropdown not the same as the board default tz
  • +
  • [PHPBB3-10589] - user_birthday does not use table alias in $leap_year_birthdays variable definition
  • +
  • [PHPBB3-10605] - Orpahned privmsgs are left in the prvmsgs table, with no ties in privmsgs_to table
  • +
  • [PHPBB3-10606] - $s_hidden_fields -> incorrect array name (3 files affected)
  • +
  • [PHPBB3-10611] - Add a check for selected tables existence for ACP database backup tool
  • +
  • [PHPBB3-10615] - Static calls in utf normalizer yield E_STRICT spam on php 5.4
  • +
  • [PHPBB3-10630] - Prune Users produced unnecessarily long query; Got a packet bigger than 'max_allowed_packet' bytes
  • +
  • [PHPBB3-10633] - Users are able to get the real filename of attachment
  • +
  • [PHPBB3-10639] - negative value of ranks message
  • +
  • [PHPBB3-10658] - Rank-item is not shown on team-list
  • +
  • [PHPBB3-10675] - Use more descriptive message when disk is out of space
  • +
  • [PHPBB3-10684] - Function user_notification() prevents notifications for users with stale bans
  • +
  • [PHPBB3-10689] - Bug in the popup " Find a member" when select by letter.
  • +
  • [PHPBB3-10691] - Search index creation CLI script incorrectly calculates indexing speed
  • +
  • [PHPBB3-10699] - Long h2 title breaks div.minitabs in MCP
  • +
  • [PHPBB3-10708] - After a conversion, passwords with UTF8 characters do not work when user_pass_convert is set.
  • +
  • [PHPBB3-10717] - memberlist_view.html: including admin defined profile fields doesnt work
  • +
  • [PHPBB3-10723] - Do not use SQLite on PHP 5.4 in Tests on Travis
  • +
  • [PHPBB3-10731] - JS function addquote() works incorrectly in Opera
  • +
  • [PHPBB3-10751] - MS SQL Error when searching Admin Log
  • +
  • [PHPBB3-10760] - In pre-commit git hook, syntax error is thrown, but is not specifically described
  • +
  • [PHPBB3-10767] - Git hooks do not work properly with git GUIs
  • +
  • [PHPBB3-10774] - db_tools::create_unique_index does not use specified index names on MySQL
  • +
  • [PHPBB3-10790] - Strict comparison on user_id for sending pms
  • +
  • [PHPBB3-10797] - Template var for user rank not filled
  • +
  • [PHPBB3-10835] - Misleading message in UCP when no permission to change password
  • +
  • [PHPBB3-10846] - Missing alias for MAX(post_id) in SQL query in acp_main.php
  • +
  • [PHPBB3-10849] - Missing BBCode Help Text in subsilver2
  • +
  • [PHPBB3-10858] - $db->sql_fetchfield returns false with mssqlnative
  • +
  • [PHPBB3-10860] - Side-by-side diff styling javascript bug
  • +
  • [PHPBB3-10881] - Some files use 0xA9 as the copyright symbol which is neither ASCII nor the UTF8 copyright symbol.
  • +
  • [PHPBB3-10887] - Auto increment tests depend on varbinary handling
  • +
  • [PHPBB3-10889] - Default value for c_char_size in database unit tests is an empty string instead of a char(4)
  • +
  • [PHPBB3-10890] - test_sql_fetchrow_returns_false_when_empty() fails on MSSQL and Oracle
  • +
  • [PHPBB3-10908] - No remote avatar size limit results in files limited only by PHP memory limit
  • +
  • [PHPBB3-10913] - Admin is logged out when accessing any url under adm/ without session id
  • +
  • [PHPBB3-10441] - Update to docs/README.html
  • +
  • [PHPBB3-10773] - ACP phpBB logo needs registered trademark symbol
  • +
  • [PHPBB3-10935] - Limit number of PM rules per user
  • +
  • [PHPBB3-10937] - Comment removal functions: Backward compatibility broken
  • +
  • [PHPBB3-10950] - Deleting user with undelivered PMs causes SQL error
  • +
  • [PHPBB3-10952] - includes/constants.php version number incorrect
  • +
  • [PHPBB3-10965] - Dropdown CPF now shows in profile when no value is selected
  • +
  • [PHPBB3-10978] - Typo in prosilvers ucp_groups_membership.html
  • +
+

Improvement

+
    +
  • [PHPBB3-8599] - Add "Select All" to "Add multiple smilies" screen
  • +
  • [PHPBB3-8636] - Add resync option to topic_view moderation page
  • +
  • [PHPBB3-9876] - Names and descriptions for roles "Newly registered User" in "User roles" and "Forum roles" must be different
  • +
  • [PHPBB3-9914] - Add backup warning to Automatic DB Updater
  • +
  • [PHPBB3-9916] - License in header not linking to version 2 of GNU GPL
  • +
  • [PHPBB3-10093] - Make commit-msg hook always not fatal
  • +
  • [PHPBB3-10162] - Allow TLDs over 6 characters in email addresses
  • +
  • [PHPBB3-10280] - Change the ACP user activation display
  • +
  • [PHPBB3-10308] - Disable Retain/Delete Posts selection if the user has no posts.
  • +
  • [PHPBB3-10453] - PM viewmessage page is misplacing the online icon
  • +
  • [PHPBB3-10492] - Port functional tests to develop-olympus
  • +
  • [PHPBB3-10507] - Sort installed styles list in admin control panel - styles
  • +
  • [PHPBB3-10550] - Sort not installed styles list in admin control panel - styles
  • +
  • [PHPBB3-10563] - ACP usability improvement: show deactivated styles below active styles in styles list
  • +
  • [PHPBB3-10565] - Performance: Unneeded GROUP BY in update_forum_tracking_info
  • +
  • [PHPBB3-10607] - phpBB Credit Line Hardcoded
  • +
  • [PHPBB3-10653] - Add ability to count table rows to database abstraction layer
  • +
  • [PHPBB3-10730] - Add label tags around "select" text in post splitting UI in MCP
  • +
  • [PHPBB3-10764] - FAQ mentions SourceForge
  • +
  • [PHPBB3-10812] - Installer should not display register globals UI for php 5.4+
  • +
  • [PHPBB3-10815] - Enable Feeds by default
  • +
  • [PHPBB3-10819] - Improve side-by-side diff styling
  • +
  • [PHPBB3-10834] - Backport general development language changes in readme files
  • +
  • [PHPBB3-10836] - Enable Avatars by default
  • +
  • [PHPBB3-10891] - Allow specifying test config file name via environment variable
  • +
  • [PHPBB3-10892] - Cosmetic improvements to RUNNING_TESTS.txt
  • +
  • [PHPBB3-10898] - Do not write ?> into config.php to avoid whitespace output
  • +
  • [PHPBB3-10925] - Clarify that SQLite3 is not supported for phpBB 3.0.x
  • +
+

New Feature

+ +

Sub-task

+
    +
  • [PHPBB3-10907] - Mark (var)binary tests as incomplete on non-MySQL DBMSes
  • +
+

Task

+
    +
  • [PHPBB3-9896] - Update links in docs/readme.html
  • +
  • [PHPBB3-10434] - Add a script that allows creating a search index from CLI
  • +
  • [PHPBB3-10455] - Remove NOTE from header files
  • +
  • [PHPBB3-10694] - Update notification in ACP (Olympus) for increase of minimum PHP version to 5.3.2
  • +
  • [PHPBB3-10718] - Add Travis CI
  • +
  • [PHPBB3-10788] - Update docs/AUTHORS for 3.0.11-RC1
  • +
  • [PHPBB3-10909] - Update Travis Test Configuration: Travis no longer supports PHP 5.3.2
  • +
+ +

Changes since 3.0.9

+ +

Bug

+
    +
  • [PHPBB3-5506] - Deleting all items from last page results in empty list display
  • +
  • [PHPBB3-6458] - Width of Topics and Posts columns in Board Index is causing problems with language packs
  • +
  • [PHPBB3-7138] - Cannot display simple header/footer with trigger_error()
  • +
  • [PHPBB3-7291] - Broken links of char selection in memberlist
  • +
  • [PHPBB3-7932] - Fix font size in select boxes
  • +
  • [PHPBB3-8094] - Text in the forums.php and install.php not matching
  • +
  • [PHPBB3-8173] - Redundant BBCode helpline in JS
  • +
  • [PHPBB3-8177] - February 29th birthdays not shown in non-leap year
  • +
  • [PHPBB3-8571] - Users can make their age a negative number on memberlist
  • +
  • [PHPBB3-8691] - Error creating log_time index
  • +
  • [PHPBB3-8937] - Code tags - single space indent
  • +
  • [PHPBB3-9008] - Incorrect unread topic tracking for unapproved topics
  • +
  • [PHPBB3-9066] - Invalid Prefix Names Allowed
  • +
  • [PHPBB3-9416] - HTML entities in poll titles and options incorrectly re-encoded
  • +
  • [PHPBB3-9525] - Minimum characters per post/message should never be '0'
  • +
  • [PHPBB3-9645] - XHTML error on phpinfo page in ACP
  • +
  • [PHPBB3-9776] - When deleting and recreating a poll, old options aren't deleted and reappear with the new ones
  • +
  • [PHPBB3-9956] - No error message displayed when disapprove reason is invalid or empty
  • +
  • [PHPBB3-9976] - Direct post links open the wrong page of viewtopic when multiple posts are posted in the same second
  • +
  • [PHPBB3-9978] - Missing semicolons in // <![CDATA[ part of overall_header.html
  • +
  • [PHPBB3-10087] - Limited browser support for ban exclusion emphasis
  • +
  • [PHPBB3-10157] - Missing error handling when a custom profile field is not defined for current language
  • +
  • [PHPBB3-10166] - Post-admin activation email confusingly refers to username
  • +
  • [PHPBB3-10187] - XHTML error in ucp_groups_manage.html
  • +
  • [PHPBB3-10190] - Misleading information about permissions displayed after editing forum settings
  • +
  • [PHPBB3-10212] - Captcha not displayed when username not exists
  • +
  • [PHPBB3-10216] - Updater's failed query language grammatically incorrect
  • +
  • [PHPBB3-10226] - Mysqli dbal extension does not allow connection via pipes
  • +
  • [PHPBB3-10227] - Mysqli dbal extension does not allow persistent connection for PHP >= 5.3.0
  • +
  • [PHPBB3-10237] - Unwatching a forum/topic does not check for correct hash parameter
  • +
  • [PHPBB3-10240] - Word filter evasion
  • +
  • [PHPBB3-10253] - IE9 Quote problem
  • +
  • [PHPBB3-10255] - gitignore ignores too much
  • +
  • [PHPBB3-10257] - AAAA record parsing fails on older versions of Windows
  • +
  • [PHPBB3-10259] - Incorrect email on joining Open group
  • +
  • [PHPBB3-10265] - Unit test tests/random/mt_rand.php is not run because of missing _test suffix.
  • +
  • [PHPBB3-10266] - Poor navigation links after reporting a post
  • +
  • [PHPBB3-10267] - Missing strlen() on $table_prefix in db tools index name length check
  • +
  • [PHPBB3-10274] - Hardcoded module ID in "Re-check version" link on ACP front page
  • +
  • [PHPBB3-10275] - Wrong information about sent passwords in FAQ
  • +
  • [PHPBB3-10292] - Whitespace inconsistency in acp_ranks
  • +
  • [PHPBB3-10293] - Jumpbox allows jumping to invalid forums in prosilver
  • +
  • [PHPBB3-10294] - sqlsrv_rows_affected non-functional in MSSQLNative.php
  • +
  • [PHPBB3-10296] - incorrect cross join in SQL Server
  • +
  • [PHPBB3-10298] - EMBED Tag Not Closed Properly In subSilver2 attachment.html
  • +
  • [PHPBB3-10299] - Typo in comment about $max_store_length in truncate_string() (in functions_content.php)
  • +
  • [PHPBB3-10303] - send_status_line() doesn't validate user input
  • +
  • [PHPBB3-10304] - Bad url in U_ICQ on /ucp_mp_viewmessage.php
  • +
  • [PHPBB3-10307] - Return value of $db->sql_fetchrow() on empty tables is not consistent
  • +
  • [PHPBB3-10309] - Utf tests download data into temporary locations deep in source tree
  • +
  • [PHPBB3-10320] - "Most active topic" can leak topic title of topics in password-protected forums
  • +
  • [PHPBB3-10321] - Link to page 1 of the Memberlist has a useless question mark at the end
  • +
  • [PHPBB3-10324] - XHTML error in Prosilver - MCP - User Notes
  • +
  • [PHPBB3-10339] - Typo in prosilver's mcp_front.html
  • +
  • [PHPBB3-10341] - Topic title of "0" does not show as "Most active topic"
  • +
  • [PHPBB3-10351] - Invalid syntax for Oracle's sql_column_remove()
  • +
  • [PHPBB3-10352] - Missing break for Oracle's sql_table_drop()
  • +
  • [PHPBB3-10365] - Moderators can view forbidden information
  • +
  • [PHPBB3-10377] - All moderators can change topic type
  • +
  • [PHPBB3-10394] - Tests use call-time pass by reference which results in Fatal error on PHP 5.4
  • +
  • [PHPBB3-10397] - Pagination code inconsistency
  • +
  • [PHPBB3-10400] - '0' (zero) not allowed as forum name
  • +
  • [PHPBB3-10413] - Make create_schema_files usable
  • +
  • [PHPBB3-10416] - Use dbport in phpbb_database_test_connection_manager::connect()
  • +
  • [PHPBB3-10420] - Update startup to account for PHP 5.4
  • +
  • [PHPBB3-10421] - Interchanged parameters in includes/acp/acp_users.php
  • +
  • [PHPBB3-10422] - Unnecessary <!-- IF --> statement in viewtopic_body.html
  • +
  • [PHPBB3-10435] - Topic count mismatch on viewforum
  • +
  • [PHPBB3-10437] - Announcements on moderation queue are not hidden
  • +
  • [PHPBB3-10446] - Unencoded 8bit characters in email headers
  • +
  • [PHPBB3-10452] - XHTML error when printing a PM
  • +
  • [PHPBB3-10461] - MCP's recent actions list is empty
  • +
  • [PHPBB3-10479] - Remove PostgreSQL version numbers from driver's language string
  • +
  • [PHPBB3-10485] - XHTML error in Prosilver - index and viewforum
  • +
  • [PHPBB3-10488] - Database updater for 3.0.10-RC1 overwrites config variable email_max_chunk_size without checking for custom value
  • +
  • [PHPBB3-10497] - SQL error when guest visits forum with unread topic
  • +
  • [PHPBB3-10319] - Missing hidden fields in search form
  • +
  • [PHPBB3-10501] - Description of table prefix is wrong
  • +
  • [PHPBB3-10502] - CHANGELOG.html has a typo: 'red' should be 'read'.
  • +
  • [PHPBB3-10503] - Debug error when previewing edits
  • +
  • [PHPBB3-10504] - MCP Layout STILL broken in ProSilver when screen is resized to less 1200 pixels
  • +
  • [PHPBB3-10531] - Last remaining style can be uninstalled
  • +
+

Improvement

+
    +
  • [PHPBB3-8616] - Add direct link to PM to notification message
  • +
  • [PHPBB3-9036] - Forums that can be listed but not read expose forum information
  • +
  • [PHPBB3-9297] - Add support for Extended Passive Mode (EPSV) in class ftp_fsock to better support IPv6 connections.
  • +
  • [PHPBB3-9307] - Mass email $max_chunk_size
  • +
  • [PHPBB3-9361] - Edit account settings - Improved clarification needed
  • +
  • [PHPBB3-9778] - Member Search from the Admin Control Panel is not Intuitive
  • +
  • [PHPBB3-9898] - Readme needs updating to reflect more opening for patches
  • +
  • [PHPBB3-9995] - Unnecessary coding in display_forums() in functions_display.php
  • +
  • [PHPBB3-10032] - BBCode Add List Item Control Name Contains Typo
  • +
  • [PHPBB3-10074] - Change default value of 'Set as special rank' to No for Add new rank
  • +
  • [PHPBB3-10185] - Board startdate not being set
  • +
  • [PHPBB3-10189] - Add "automatically generated" comment into schema-files.
  • +
  • [PHPBB3-10199] - Performance: viewtopic has a useless join
  • +
  • [PHPBB3-10222] - Also build language and styles changes in diff/patch format
  • +
  • [PHPBB3-10239] - Add "Are you sure" confirmation to backup restore in ACP
  • +
  • [PHPBB3-10243] - Add gmgetdate() wrapper for getdate() which returns dates in UTC.
  • +
  • [PHPBB3-10245] - Messenger uses output buffering for error collection, should use error collector instead
  • +
  • [PHPBB3-10246] - Remove VCS section from docs/coding-guidelines.html
  • +
  • [PHPBB3-10254] - Remove style names from themes and fix some information on it
  • +
  • [PHPBB3-10263] - Add phpbb_version_compare() wrapper for version_compare()
  • +
  • [PHPBB3-10278] - Improve timeout handling in get_remote_file()
  • +
  • [PHPBB3-10315] - Radio Buttons in ACP are clipped in Safari - Fix suggested
  • +
  • [PHPBB3-10327] - Use "ALTER TABLE ... ADD INDEX" instead of "CREATE INDEX"
  • +
  • [PHPBB3-10334] - Birthday List display not dependent on user privileges
  • +
  • [PHPBB3-10335] - Responses to bots should have extra header to be used by reverse proxies
  • +
  • [PHPBB3-10346] - Add drop_tables key for database updater
  • +
  • [PHPBB3-10354] - When template tests are skipped because cache is not writable, print cache directory path
  • +
  • [PHPBB3-10369] - Change error collector to always report errfile and errline
  • +
  • [PHPBB3-10370] - Various improvements for get_backtrace()
  • +
  • [PHPBB3-10402] - Displaying report texts with linebreaks and clickable links
  • +
  • [PHPBB3-10419] - Add mbstring PHP ini parameters checks to ACP
  • +
  • [PHPBB3-10430] - Some typos and the like in docs/coding-guidelines.html
  • +
+

New Feature

+
    +
  • [PHPBB3-8240] - Request: db_tools to have two additional functions, table list and column list
  • +
+

Task

+ + +

Changes since 3.0.8

+ +

Bug +

+
    +
  • [PHPBB3-217] - Multiline [url] not Converted +
  • +
  • [PHPBB3-6712] - Topic bumping does not create new topic icon on index +
  • +
  • [PHPBB3-7057] - Quicksearch uses POST, thus the page expires! +
  • +
  • [PHPBB3-7778] - Increase limit of custom BBcodes +
  • +
  • [PHPBB3-7834] - Correctly update topic_time when deleting first post in topic +
  • +
  • [PHPBB3-7888] - URL of search results page does not always contain all keywords of the search query +
  • +
  • [PHPBB3-7941] - mistake in description of function generate_board_url +
  • +
  • [PHPBB3-8138] - Browser autocompleton fills wrong fields in ACP +
  • +
  • [PHPBB3-8736] - Honour ACP settings for min/max username length when posting as a guest. +
  • +
  • [PHPBB3-8802] - Wrong confirmation text when clicking "mark forums read" in a category +
  • +
  • [PHPBB3-8904] - Show numeric CPF default value when editing +
  • +
  • [PHPBB3-9166] - Subsilver and prosilver CSS elements out of order. +
  • +
  • [PHPBB3-9348] - Correctly encode default_dateformat when converting from phpBB2 +
  • +
  • [PHPBB3-9575] - The word "administrate" is not correct. +
  • +
  • [PHPBB3-9630] - Naming inconsistency of Merging Posts / Topics in MCP +
  • +
  • [PHPBB3-9675] - Add option to delete template/theme/imageset when deleting style. +
  • +
  • [PHPBB3-9685] - Unable to create "Fulltext native" search index using the mssqlnative DBAL +
  • +
  • [PHPBB3-9751] - Password requirement "Must contain letters and numbers" is not working properly +
  • +
  • [PHPBB3-9764] - Empty value for CONFIG_TABLE config_name= 'mime_triggers' causes functions_fileupload.php->fileupload->check_content() to be too restrictive +
  • +
  • [PHPBB3-9851] - "Search new posts" should require login +
  • +
  • [PHPBB3-9872] - Total topics isn't correct after I deleted a user +
  • +
  • [PHPBB3-9874] - view_log() performs unneeded count query over all log entries. +
  • +
  • [PHPBB3-9892] - Firebird index name length limit is not taken into account +
  • +
  • [PHPBB3-9905] - DSN field should include SQLite +
  • +
  • [PHPBB3-9908] - Send "Moved Permanently" before stripping off session ids for Bots. +
  • +
  • [PHPBB3-9910] - Javascript bug in Subsilver2 PMs +
  • +
  • [PHPBB3-9911] - Incorrect open/close field in Manage ranks ACP +
  • +
  • [PHPBB3-9913] - currunt should be current +
  • +
  • [PHPBB3-9915] - "Length of ban:" is not displayed in ACP +
  • +
  • [PHPBB3-9924] - $template->display hook does not pass $template instance +
  • +
  • [PHPBB3-9925] - prosilver logo margin bug in IE 6-7-8 +
  • +
  • [PHPBB3-9928] - Do not link "login to your board" to the "send statistics" page after completed update. +
  • +
  • [PHPBB3-9930] - Redirect fails with open_basedir enabled +
  • +
  • [PHPBB3-9932] - The Bing bot is not added when converting. +
  • +
  • [PHPBB3-9933] - Wrong handling of consecutive multiple asterisks in word censor +
  • +
  • [PHPBB3-9934] - Mass Mail missing under the system tab on a fresh install +
  • +
  • [PHPBB3-9939] - JavaScript error in recaptcha ACP template +
  • +
  • [PHPBB3-9944] - Extension groups naming don't use users' language in ACP +
  • +
  • [PHPBB3-9946] - $inserts empty in sql_query() for oracle +
  • +
  • [PHPBB3-9948] - Inline quicktime files won't display +
  • +
  • [PHPBB3-9949] - $user->lang() is not handling arguments as per documentation +
  • +
  • [PHPBB3-9950] - Problem with localized button images after uprading from 3.0.7-PL1 to 3.0.8 +
  • +
  • [PHPBB3-9953] - Set focus to password on re-authentication +
  • +
  • [PHPBB3-9954] - u_masspm* permissions are forced to never for certain groups +
  • +
  • [PHPBB3-9961] - Inconsistent activation logs +
  • +
  • [PHPBB3-9966] - Language download in ACP creates index.html and misses captcha_* +
  • +
  • [PHPBB3-9970] - user_lang input not checked during registration +
  • +
  • [PHPBB3-9981] - Fix unit test dependencies on phpBB files +
  • +
  • [PHPBB3-9985] - 3D Wave CAPTCHA mt_rand() does not check order of min/max values +
  • +
  • [PHPBB3-9997] - Inconsistent approve/disapprove button order in modcp +
  • +
  • [PHPBB3-9999] - {forumrow.L_FORUM_FOLDER_ALT} and {SEARCH_IMG} only return a language key. +
  • +
  • [PHPBB3-10005] - users can register without custom profile field correctly entered +
  • +
  • [PHPBB3-10011] - __DIR__ in test suite renders it unusable on php < 5.3 +
  • +
  • [PHPBB3-10016] - set_config_count() fails on PostreSQL 7 +
  • +
  • [PHPBB3-10020] - ACP function validate_range() fails partially on non-32-bit systems +
  • +
  • [PHPBB3-10021] - "Find a member" generates SQL error when large dates are entered +
  • +
  • [PHPBB3-10029] - No such thing as $_SERVER['HTTP_VERSION'] +
  • +
  • [PHPBB3-10033] - "Disallow usernames" does not check already disallowed names +
  • +
  • [PHPBB3-10035] - ACP template edit feature allows to read any files on webserver and to upload/execute any script on it +
  • +
  • [PHPBB3-10036] - Use image from configuration file for displaying online-status. +
  • +
  • [PHPBB3-10038] - download/file.php uses $_GET value instead of function request_var() +
  • +
  • [PHPBB3-10039] - 2.x to 3.x conversion fails when using mssqlnative to connect to destination database +
  • +
  • [PHPBB3-10042] - GD captcha has invalid mt_rand calls +
  • +
  • [PHPBB3-10047] - Session ID always included in URL on posting.php +
  • +
  • [PHPBB3-10049] - Session test files are misnamed, session tests are not run +
  • +
  • [PHPBB3-10052] - Session tests are broken +
  • +
  • [PHPBB3-10056] - Firebird misspelled in database updater +
  • +
  • [PHPBB3-10058] - Root path is undefined in MySQL upgrader +
  • +
  • [PHPBB3-10059] - Consistent is misspelled twice +
  • +
  • [PHPBB3-10060] - Typo in tests database connection manager +
  • +
  • [PHPBB3-10068] - Firefox4 restrictions to :visited +
  • +
  • [PHPBB3-10078] - commit-msg hook prints \n on freebsd +
  • +
  • [PHPBB3-10081] - Cleanup Template Tests +
  • +
  • [PHPBB3-10084] - Add smilie errors out when image is missing +
  • +
  • [PHPBB3-10088] - Cache mock does not unset database versions other than mysqli +
  • +
  • [PHPBB3-10090] - cache/queue.php.lock isn't covered by .gitignore +
  • +
  • [PHPBB3-10092] - commit-msg hook aborts on overlength comment lines +
  • +
  • [PHPBB3-10096] - Wrong whitespace in functions.php +
  • +
  • [PHPBB3-10100] - Race condition in unique_id() on heavily busy database. +
  • +
  • [PHPBB3-10102] - member.S_PENDING_SET in styles/prosilver/template/ucp_groups_manage.html +
  • +
  • [PHPBB3-10104] - missing one intval() along with others already being there +
  • +
  • [PHPBB3-10109] - Errors while copying a topic +
  • +
  • [PHPBB3-10112] - Use of count() in captcha_gd.php and mssqlnative.php +
  • +
  • [PHPBB3-10115] - BBcodes not working if post contains about or more 55000 non-english symbols +
  • +
  • [PHPBB3-10117] - Big posts becomes empty if they have smilies on specified places. +
  • +
  • [PHPBB3-10121] - ICQ profile link leads to a webservice that is no longer active +
  • +
  • [PHPBB3-10123] - Inconsistent use of smilie/smiley +
  • +
  • [PHPBB3-10128] - Error message is on green background when trying to ban a nonexistent user +
  • +
  • [PHPBB3-10137] - Deleting an unintended space at the end of PHP_URL_FOPEN_SUPPORT_EXPLAIN +
  • +
  • [PHPBB3-10146] - Firebird cannot handle DECIMAL(255, 0) +
  • +
  • [PHPBB3-10147] - Typo in code comment in functions_template.php +
  • +
  • [PHPBB3-10149] - deregister_globals causes error when cookie called GLOBALS is set to scalar value +
  • +
  • [PHPBB3-10170] - reCAPTCHA address has changed +
  • +
  • [PHPBB3-10171] - Firefox4 displays grey pixels at PM message rows when message is neither marked nor replied +
  • +
  • [PHPBB3-10177] - phpBB package cannot be built with bsdtar +
  • +
  • [PHPBB3-10178] - build.xml does not specify path to find - breaks on FreeBSD +
  • +
  • [PHPBB3-10188] - Broken compressed output when errors/warnings are handled by phpbb and output_buffering is set to 4096 and phpbb gzip is enabled +
  • +
  • [PHPBB3-10191] - Duplicate output when output_handler is set in php.ini +
  • +
  • [PHPBB3-10192] - Missing semicolon in MySQL Upgrader +
  • +
  • [PHPBB3-10195] - Do not check DNS Blacklists if IPv6 address is passed to session::check_dnsbl(). +
  • +
  • [PHPBB3-10198] - Function validate_config_vars() improperly validates multibyte strings +
  • +
  • [PHPBB3-10203] - Fix quotations and hyphen in language strings for PHPBB3-10067 +
  • +
  • [PHPBB3-10204] - Package build tool does not detect binary file changes +
  • +
  • [PHPBB3-10206] - Normalization tests fail when unicode.org is not reachable +
  • +
  • [PHPBB3-10211] - Missing space on the recent PHPBB3-9992 changes +
  • +
  • [PHPBB3-10213] - IP limit index name too long on Oracle +
  • +
  • [PHPBB3-10214] - Cannot configure Q&A on Oracle +
  • +
  • [PHPBB3-10218] - STRIP is not defined in style.php causing a notice to be thrown +
  • +
  • [PHPBB3-10219] - Inappropriate character in web.config file +
  • +
  • [PHPBB3-10220] - Logging in with Mobile Device triggers SQL error on *_login_attempts. +
  • +
  • [PHPBB3-10221] - Inconsistent usage of "Seconds" in ACP Settings +
  • +
  • [PHPBB3-7729] - Prevent date/time functions from throwing E_WARNING on PHP 5.3 by setting a default timezone +
  • +
  • [PHPBB3-10188] - Broken compressed output when errors/warnings are handled by phpbb and output_buffering is set to 4096 and phpbb gzip is enabled +
  • +
  • [PHPBB3-10223] - Updater references startup.php from board path +
  • +
  • [PHPBB3-10228] - Typo in 3.0.9-RC1 user registration settings +
  • +
  • [PHPBB3-10229] - On languge/acp/styles.php "%s" should be "%s" +
  • +
  • [PHPBB3-10232] - Search within topic/forum searches all posts +
  • +
  • [PHPBB3-10233] - IE Emulation fix breaks posting layout when PMing +
  • +
  • [PHPBB3-10234] - msg_handler() reports E_WARNING as "PHP Notice: " +
  • +
  • [PHPBB3-10247] - mediumint(8) too small for phpbb_login_attempts.attempt_id +
  • +
  • [PHPBB3-10250] - phpBB Logo needs the Registered Trademark Symbol +
  • +
+ +

Improvement +

+
    +
  • [PHPBB3-9581] - Banned users get mass emails. +
  • +
  • [PHPBB3-9802] - Optimize session_begin REMOTE_ADDR validation +
  • +
  • [PHPBB3-9878] - Get rid of Internet Explorer 7 emulation +
  • +
  • [PHPBB3-9897] - Language typos in language/en/acp/board.php +
  • +
  • [PHPBB3-9922] - Posting URL in subsilver 2 +
  • +
  • [PHPBB3-9937] - Feed Icon displays on Forum links +
  • +
  • [PHPBB3-9980] - URLs to javascript should be T_SUPER_TEMPLATE_PATH instead of T_TEMPLATE_PATH +
  • +
  • [PHPBB3-9989] - Skip PM popup in overall_header.html, if there are no new PMs. +
  • +
  • [PHPBB3-10007] - Add directive 'internal' to blocked folders in nginx example configuration. +
  • +
  • [PHPBB3-10009] - Differentiate published/updated dates in Atom feed +
  • +
  • [PHPBB3-10014] - Make the error message when cache is not writable clearer +
  • +
  • [PHPBB3-10024] - Allow a Style to present Unread PM in different way than read PM +
  • +
  • [PHPBB3-10040] - Continuous integration on PHP 5.2 +
  • +
  • [PHPBB3-10041] - download/file.php needs more use of send_status_line +
  • +
  • [PHPBB3-10044] - Setup github network improvements +
  • +
  • [PHPBB3-10057] - More informative reporting of errors when database connection fails for Firebird and PostgreSQL. +
  • +
  • [PHPBB3-10067] - ACP options for account activation are confusing when emails are turned off board-wide +
  • +
  • [PHPBB3-10069] - Improvements in sample nginx config file +
  • +
  • [PHPBB3-10072] - Send the post number to the template as it relates to it's position in the topic +
  • +
  • [PHPBB3-10101] - Compatibility with native phpass hashes +
  • +
  • [PHPBB3-10126] - Replace ^ with &~ in error_reporting calls +
  • +
  • [PHPBB3-10141] - Performance improvement for $auth->_fill_acl() +
  • +
  • [PHPBB3-10145] - Ability to force recompilation of all templates on every page load +
  • +
  • [PHPBB3-10154] - Move "copy permissions from" to below "parent" in forum creation form +
  • +
  • [PHPBB3-10158] - Return link not really useful after sending a Private Message +
  • +
  • [PHPBB3-10186] - UCP signature panel displays when not authed for signatures +
  • +
+ +

New Feature +

+ + +

Task +

+
    +
  • [PHPBB3-9788] - Add README for GitHub +
  • +
  • [PHPBB3-9805] - Add a script for setting up git remotes for a github network +
  • +
  • [PHPBB3-9806] - Script for easy merging +
  • +
  • [PHPBB3-9824] - Git hook quirks +
  • +
  • [PHPBB3-9859] - Remove the years from visible copyright in the footer. +
  • +
  • [PHPBB3-9921] - Add sample configuration for lighttpd webserver +
  • +
  • [PHPBB3-9943] - Setup phpDocumentor API documentation generation +
  • +
  • [PHPBB3-9967] - Use phpunit.xml for test suite +
  • +
  • [PHPBB3-9987] - Enforce _test.php suffix for test files +
  • +
  • [PHPBB3-9990] - Integrate utf normalizer tests into test suite +
  • +
  • [PHPBB3-10043] - Refactor phpbb_database_test_case +
  • +
  • [PHPBB3-10046] - Getting rid of register_shutdown_function() in cron.php to prevent path disclosure (reported by lacton) +
  • +
  • [PHPBB3-10075] - Update docs/AUTHORS for 3.0.9-RC1 release +
  • +
  • [PHPBB3-10079] - Add gallery avatars to .gitignore. +
  • +
  • [PHPBB3-10082] - Fix Session Test Issues with CHAR vs. VARCHAR. +
  • +
  • [PHPBB3-10105] - Update AIM express link and "Download Application" links +
  • +
  • [PHPBB3-10107] - Improve docs for non-apache webserver configuration +
  • +
+ +

Sub-task +

+
    +
  • [PHPBB3-9732] - Cover session code extensively in tests +
  • +
  • [PHPBB3-9968] - Create unit test for word censor regular expression +
  • +
  • [PHPBB3-9969] - Move word censor regular expression creation into separate function definition in functions.php +
  • +
+ +

Changes since 3.0.7-PL1

+ +

Security +

+
    +
  • [PHPBB3-9903] - Execute javascript in [flash=] BBCode +
  • +
+ +

Bug +

+
    +
  • [PHPBB3-4923] - compress_tar incorrectly determines type +
  • +
  • [PHPBB3-5164] - Honor minimum and maximum password length in generated passwords as much as possible. +
  • +
  • [PHPBB3-6726] - Connecting to PostgreSQL using 'localhost' doesn't try to use a TCP connection +
  • +
  • [PHPBB3-6747] - word censoring * does not handle space for two or more words +
  • +
  • [PHPBB3-7260] - Do not delete polls if one exists and editing user lacks permissions +
  • +
  • [PHPBB3-7296] - Style export to tar(.*) does not work +
  • +
  • [PHPBB3-7369] - Custom Profile dates display incorrectly +
  • +
  • [PHPBB3-7417] - Search keywords field does not initially get focus +
  • +
  • [PHPBB3-7538] - Query exceeds maximum value for user_login_attempts +
  • +
  • [PHPBB3-7716] - Data too long for column 'message_subject' +
  • +
  • [PHPBB3-7720] - Fix alternative image-description for unread posts. +
  • +
  • [PHPBB3-7782] - Send HTTP 404 if topic, forum or user do not exist +
  • +
  • [PHPBB3-7972] - Copied topics are not indexed +
  • +
  • [PHPBB3-8169] - Parse CSS Regex accepts invalid code +
  • +
  • [PHPBB3-8792] - Misleading error message in auth_ldap.php, function init_ldap() +
  • +
  • [PHPBB3-8894] - JavaScript error and visible quote button on topic review if BBCodes disallowed +
  • +
  • [PHPBB3-8924] - spelling in admin_welcome_inactive.txt +
  • +
  • [PHPBB3-8929] - MS SQL error on view all smilies after 3.0.6 upgrade +
  • +
  • [PHPBB3-8935] - able to set minimal avatar size larger than maximum +
  • +
  • [PHPBB3-8944] - Error on database update (must specify size of index on MySQL4) +
  • +
  • [PHPBB3-9012] - Retain original topic title in shadow topic when moving a topic and editing the title. +
  • +
  • [PHPBB3-9034] - Redirect() fails with directory traversal +
  • +
  • [PHPBB3-9047] - Active topics and reported posts +
  • +
  • [PHPBB3-9049] - Password reminder system generates confusable passwords +
  • +
  • [PHPBB3-9053] - Correctly sort database backup file list by date on database restore page +
  • +
  • [PHPBB3-9061] - Race condition in queue locking +
  • +
  • [PHPBB3-9068] - Grammatical Error under Load Settings +
  • +
  • [PHPBB3-9075] - Missing / bad default values of CPFs result in SQL errors on registration of new users +
  • +
  • [PHPBB3-9091] - Wrong IP checking for IPv4 addresses mapped into IPv6 +
  • +
  • [PHPBB3-9094] - Hide "Copy permissions" message, when permissions were copied. +
  • +
  • [PHPBB3-9095] - Misleading setting text for CAPTCHA +
  • +
  • [PHPBB3-9099] - Missing comma in PASSWORD_EXPLAIN acp language strings +
  • +
  • [PHPBB3-9101] - Bad text placement for reCAPTCHA description +
  • +
  • [PHPBB3-9104] - Safari does not display box headers correctly in the ACP. +
  • +
  • [PHPBB3-9107] - Can't Set Parent Forum +
  • +
  • [PHPBB3-9108] - RSS feeds does not work on Postgres +
  • +
  • [PHPBB3-9112] - Most active forum post count does not respect m_approve permission +
  • +
  • [PHPBB3-9114] - Recent bug fix for smilies causing problems on older MySQL versions +
  • +
  • [PHPBB3-9117] - Wrong redirection after login +
  • +
  • [PHPBB3-9119] - Language selection is disregarded in automatic update +
  • +
  • [PHPBB3-9120] - Typo fix in a comment in functions.php +
  • +
  • [PHPBB3-9121] - Forum feed shows posts that are currently on the moderation queue +
  • +
  • [PHPBB3-9125] - ACP User Overview: Unmatched </form> tag when viewing own user +
  • +
  • [PHPBB3-9126] - Invalid redirection after login to forum not in web root +
  • +
  • [PHPBB3-9132] - Oracle CLOB support is broken, preventing storage of long strings +
  • +
  • [PHPBB3-9135] - Fix report-icon for moderators in PM folders. +
  • +
  • [PHPBB3-9140] - Check current board version in incremental update packages +
  • +
  • [PHPBB3-9145] - Fix open_basedir issues when accessing styles- and language-management +
  • +
  • [PHPBB3-9146] - Quick-Reply tabindex="6" set twice +
  • +
  • [PHPBB3-9147] - "Change topic type"-option "Normal" always selected. +
  • +
  • [PHPBB3-9154] - Correctly check for double inclusion in captcha garbage collection +
  • +
  • [PHPBB3-9158] - viewforum/viewtopic pages unnecessarily duplicated with start=0 +
  • +
  • [PHPBB3-9162] - BBCode in poll options is broken, when posting without question. +
  • +
  • [PHPBB3-9167] - Remove shadow topics from remaining forums when deleting a forum including posts +
  • +
  • [PHPBB3-9170] - Unable to get image size in img bbcode when URL has multiple parameters. +
  • +
  • [PHPBB3-9173] - sql_config_count() artificially limits number scope to 4byte-integer on PostgreSQL and Firebird +
  • +
  • [PHPBB3-9176] - When setting the board's date format the board's timezone settings aren't taken into account +
  • +
  • [PHPBB3-9451] - Unnecessary overhead in avatar_process_user function +
  • +
  • [PHPBB3-9478] - Validate maximum number of allowed recipients per PM value +
  • +
  • [PHPBB3-9495] - Loginbox <input /> redirect breaks xHTML +
  • +
  • [PHPBB3-9499] - Javascript function dE does not correctly detect element visibility +
  • +
  • [PHPBB3-9504] - Allow gallery avatars with whitespaces in the filename +
  • +
  • [PHPBB3-9509] - phpBB Coding Guidelines state subversion as the version control system for phpBB +
  • +
  • [PHPBB3-9510] - Unable to copy permissions from and to forums you cannot see +
  • +
  • [PHPBB3-9512] - Fix dead link in MCP on reports for global announcements in prosilver. +
  • +
  • [PHPBB3-9514] - Correctly delete big datasets when deleting a forum including topics/posts on non-MySQL databases +
  • +
  • [PHPBB3-9518] - Postgres DBAL does not correctly create a new database connection when passing $new_link as true +
  • +
  • [PHPBB3-9519] - Replace remaining is_writable() calls with phpbb_is_writable(). +
  • +
  • [PHPBB3-9521] - MSSQL error reporting returns String instead of an error +
  • +
  • [PHPBB3-9524] - IPv6 regular expression does not match addresses starting in :: +
  • +
  • [PHPBB3-9526] - User Preference to hide online status does not work for bots +
  • +
  • [PHPBB3-9528] - Quoting in a PM does not fall back to bbcode-less quotes using "> " when bbcodes are disabled +
  • +
  • [PHPBB3-9529] - Topic review does not display all selected posts +
  • +
  • [PHPBB3-9530] - subsilver2 missing fallback option on quoting when bbcodes are disabled +
  • +
  • [PHPBB3-9531] - BBCode-less fall back option for quotes is missing "Author wrote:" line when quoting from topic-review. +
  • +
  • [PHPBB3-9535] - Incorrect margins in RTL languages: signatures, permission ACP & updater +
  • +
  • [PHPBB3-9545] - 'Your first forum' should have 'Display active topics:' set to 'Yes' +
  • +
  • [PHPBB3-9546] - Moving all posts from one topic to another does not delete bookmarks +
  • +
  • [PHPBB3-9547] - Changing forum type applies FORUM_FLAG_ACTIVE_TOPICS to new forum type. +
  • +
  • [PHPBB3-9548] - Delete user quicktool drop down should have an empty or invalid selection as the default +
  • +
  • [PHPBB3-9559] - Messenger Queue Batch Size configuration option is overridden +
  • +
  • [PHPBB3-9567] - Newly registered users group ACP wording +
  • +
  • [PHPBB3-9582] - Missing MSSQL native driver case statements +
  • +
  • [PHPBB3-9587] - Prosilver overrides reCaptcha class. +
  • +
  • [PHPBB3-9592] - Test suite does not run on SQLite +
  • +
  • [PHPBB3-9593] - Missing documentation for running unit tests +
  • +
  • [PHPBB3-9599] - Windows workaround for checkdnsrr() returns wrong results +
  • +
  • [PHPBB3-9605] - Wrong class added to topiclist, when there's no announcement topic. +
  • +
  • [PHPBB3-9615] - When attaching a file whose name contains quotes, filename before last quote is cut off in display +
  • +
  • [PHPBB3-9623] - Strings not properly normalized - acp_prune.php +
  • +
  • [PHPBB3-9626] - Regular expressions from get_preg_expression() are untested. +
  • +
  • [PHPBB3-9628] - Add module function does not correctly insert a module after the specified one +
  • +
  • [PHPBB3-9633] - Newly registered users group color is not used in Our Newest Member +
  • +
  • [PHPBB3-9635] - Useless parameter $data['post_time'] in function submit_post. +
  • +
  • [PHPBB3-9637] - SET NAMES 'BINARY' error in convertor +
  • +
  • [PHPBB3-9643] - DB connection error when $dbhost is an IPv6 address +
  • +
  • [PHPBB3-9644] - submit_post shows support for options that cause a trigger_error in the call to user_notification +
  • +
  • [PHPBB3-9646] - Cant hide/outcomment @import in stylesheet.css +
  • +
  • [PHPBB3-9650] - It should not be possible to ban Anonymous +
  • +
  • [PHPBB3-9653] - xhtml errors in subsilver2 when using the bbcodes code and quote in signatures +
  • +
  • [PHPBB3-9655] - Selecting an unavailable captcha plugin looks like a successful action +
  • +
  • [PHPBB3-9656] - PHP Information in ACP always lists error_reporting as 0 +
  • +
  • [PHPBB3-9658] - Optimize topic splitting +
  • +
  • [PHPBB3-9662] - Search interval applied inconsistently +
  • +
  • [PHPBB3-9664] - Another duplicate accesskey: t = top and list item +
  • +
  • [PHPBB3-9665] - Signature "0" cannot be previewed +
  • +
  • [PHPBB3-9677] - Subsilver2 is missing the bbcode-helpline for inline-attachments. +
  • +
  • [PHPBB3-9678] - Flash attachments are not displayed in subsilver2. +
  • +
  • [PHPBB3-9679] - "Notify User" checkbox appears in MCP Queue even if no notification methods are enabled +
  • +
  • [PHPBB3-9686] - Unable to create data backup using the mssqlnative DBAL +
  • +
  • [PHPBB3-9694] - Calling download/file.php with empty avatar parameter can throw an E_NOTICE message +
  • +
  • [PHPBB3-9695] - Bad Display of User Input - mcp_ban +
  • +
  • [PHPBB3-9696] - Installation of phpBB with SQLite fails +
  • +
  • [PHPBB3-9697] - Backlink broken when the select parent forum does not exist. +
  • +
  • [PHPBB3-9698] - Returning result of new by reference is deprecated in php 5.3 +
  • +
  • [PHPBB3-9702] - "Ban until (date)" appears to be based on UTC time instead of local time +
  • +
  • [PHPBB3-9703] - Removing a user does not remove their private message folders or rules +
  • +
  • [PHPBB3-9704] - Coding guidelines typo +
  • +
  • [PHPBB3-9712] - Future dates display as "less than one minute ago" +
  • +
  • [PHPBB3-9714] - "Undefined variable: email" in email regular expression unit tests +
  • +
  • [PHPBB3-9715] - Fix email address regular expression or adjust email regular expression unit tests +
  • +
  • [PHPBB3-9722] - "New Topic" button title attribute mismatch in prosilver's viewforum +
  • +
  • [PHPBB3-9727] - Feed replaces ./ with board URL +
  • +
  • [PHPBB3-9743] - Fix background-position of top2-class in prosilver for RTL-languages. +
  • +
  • [PHPBB3-9744] - Mistyped word 'then' in FAQ. It should be 'than'. +
  • +
  • [PHPBB3-9748] - <br /> not being replaced in prepare_message +
  • +
  • [PHPBB3-9749] - fulltext_mysql.php overreacts on + and - characters in search words +
  • +
  • [PHPBB3-9752] - Misleading text when using Q&A CAPTCHA +
  • +
  • [PHPBB3-9754] - Template variable S_USER_POSTED always set to false in search.php +
  • +
  • [PHPBB3-9757] - Empty template variable HISTORY_TITLE in ucp_pm_history +
  • +
  • [PHPBB3-9760] - Fulltext native search, wildcard * does not get escaped leading to long execution time +
  • +
  • [PHPBB3-9761] - Quote nesting depth explanation is misleading +
  • +
  • [PHPBB3-9771] - build_url() doesn't ignore empty parameters +
  • +
  • [PHPBB3-9772] - Under some circumstances, email addresses are shown to undesired users +
  • +
  • [PHPBB3-9780] - gen_rand_string() not respecting $num_chars parameter anymore. +
  • +
  • [PHPBB3-9782] - Board disable radio in Board-Settings set on when server load high +
  • +
  • [PHPBB3-9793] - Undefined function send_status_line() in download/file.php when in avatar mode. +
  • +
  • [PHPBB3-9807] - Avatar tab displays when avatars are disabled +
  • +
  • [PHPBB3-9810] - Clicking on "Select All" of code tag on print page results in a javascript error when using prosilver +
  • +
  • [PHPBB3-9820] - Fix undefined indexes when trying to post a new topic +
  • +
  • [PHPBB3-9822] - Can not delete style-components from the file-system as per explanation. +
  • +
  • [PHPBB3-9829] - Recaptcha plugin result interpretation fault +
  • +
  • [PHPBB3-9835] - Login Confirm Explain Not Working +
  • +
  • [PHPBB3-9840] - Display view unread posts link for guests +
  • +
  • [PHPBB3-9841] - Change "Save" button to "Save draft" +
  • +
  • [PHPBB3-9847] - Language typo and written form (British/American) +
  • +
  • [PHPBB3-9854] - Auth API documentation is incomplete +
  • +
  • [PHPBB3-9855] - Tests don't run on PHPUnit 3.5 +
  • +
  • [PHPBB3-9879] - captcha_qa.php spelling, punctuation and grammar errors +
  • +
  • [PHPBB3-9883] - CAPTCHA uses american english +
  • +
  • [PHPBB3-9884] - Massive email delays +
  • +
  • [PHPBB3-9885] - Default file extension groups not properly updated by database updater. +
  • +
  • [PHPBB3-9886] - Database updater does not run on PostgreSQL because of an error in _add_module() +
  • +
  • [PHPBB3-9888] - Update fails when Bing [Bot] was already added to the users table +
  • +
  • [PHPBB3-9891] - Updater drops language-selection after database-update +
  • +
  • [PHPBB3-9509] - phpBB Coding Guidelines state subversion as the version control system for phpBB +
  • +
+ +

Improvement +

+
    +
  • [PHPBB3-7332] - MCP post details usability +
  • +
  • [PHPBB3-7717] - Use user's language for standard-extensions-group name +
  • +
  • [PHPBB3-8709] - Multibyte keys in request_var not possible +
  • +
  • [PHPBB3-8936] - subsilver2 missing reply-to-all feature +
  • +
  • [PHPBB3-9088] - Add missing semicolons in js files +
  • +
  • [PHPBB3-9179] - improve quasi-documentation of notify_status values +
  • +
  • [PHPBB3-9503] - Posts with empty titles in moderation queue are not easily approved +
  • +
  • [PHPBB3-9534] - user_ipwhois() does not support IPv6 addresses +
  • +
  • [PHPBB3-9536] - Small improvement for query against sessions table in acp_users.php +
  • +
  • [PHPBB3-9553] - Make git hooks run with /bin/sh instead of bash +
  • +
  • [PHPBB3-9570] - Change "system timezone" to "guest timezone" in acp, add explanation +
  • +
  • [PHPBB3-9578] - ACP Posting tab is missing "Post settings" module. +
  • +
  • [PHPBB3-9589] - Sample nginx configuration file +
  • +
  • [PHPBB3-9595] - Search settings in ACP: Add information on minimum word size indexed when using Fulltext MySQL backend +
  • +
  • [PHPBB3-9598] - Call checkdnsrr() on Windows with PHP 5.3 +
  • +
  • [PHPBB3-9609] - Use send_status_line instead of calling header +
  • +
  • [PHPBB3-9611] - Increase entropy in activation keys +
  • +
  • [PHPBB3-9612] - Split gen_rand_string() into gen_rand_string() and gen_rand_string_friendly() +
  • +
  • [PHPBB3-9629] - sid parameter forced for style.php makes caching difficult +
  • +
  • [PHPBB3-9659] - Default phpBB signature user_options need to be set for convertors +
  • +
  • [PHPBB3-9690] - MSN Bot will become Bing Bot +
  • +
  • [PHPBB3-9777] - Print useful error message in pre-commit hook when php is not installed. +
  • +
  • [PHPBB3-9785] - Not able to recover a password when board disabled +
  • +
  • [PHPBB3-9825] - Run tests on sqlite if available and no test db configured +
  • +
  • [PHPBB3-9827] - IE9 Beta fixes IE8 textarea bug +
  • +
  • [PHPBB3-9830] - Awkward message when config.php is missing +
  • +
  • [PHPBB3-9850] - Allow version checker to display information on multiple releases +
  • +
  • [PHPBB3-9853] - Change default reCAPTCHA theme in Prosilver & Subsilver2 to better coordinate with style color scheme +
  • +
  • [PHPBB3-9880] - Rename all mentions of CAPTCHA or visual confirmation to anti-bot +
  • +
  • [PHPBB3-9899] - Change the style in the ACP for the recaptcha to match that displayed on prosilver +
  • +
+ +

New Feature +

+
    +
  • [PHPBB3-9039] - Native SQL Server Support mssqlnative.php +
  • +
  • [PHPBB3-9511] - View note for moderators on unapproved posts/topics with unapproved posts in ATOM Feed. +
  • +
+ +

Task +

+
    +
  • [PHPBB3-9520] - Add web.config files for IIS +
  • +
  • [PHPBB3-9625] - Update database UNIT-test +
  • +
  • [PHPBB3-9701] - Enable notices in unit tests +
  • +
  • [PHPBB3-9768] - Create git commit-msg hook that verifies the commit message conforms to our standards +
  • +
  • [PHPBB3-9769] - Add install and uninstall scripts for the git hooks +
  • +
  • [PHPBB3-9770] - Git commit message should be prefilled with branch and ticket information +
  • +
  • [PHPBB3-9800] - Update tracker URL in docs/README.html +
  • +
  • [PHPBB3-9804] - Update docs/AUTHORS (DavidMJ & igorw) +
  • +
  • [PHPBB3-9808] - Git commit message hook depends on GNU wc +
  • +
  • [PHPBB3-9816] - Remove config.php from git repository +
  • +
  • [PHPBB3-9848] - Add phpBB data files to .gitignore. +
  • +
  • [PHPBB3-9849] - Create build script using phing +
  • +
  • [PHPBB3-9857] - Remove visible $Id$ from docs files. +
  • +
  • [PHPBB3-9868] - Make the test suite run and pass using the mssqlnative driver +
  • +
  • [PHPBB3-9904] - Update WebPI Parameters.xml +
  • +
+ +

Sub-task +

+
    +
  • [PHPBB3-9517] - Remote avatar upload does not check the filesize before and during transfer. +
  • +
  • [PHPBB3-9562] - Advanced Search is inaccessible using the mssqlnative DBAL +
  • +
  • [PHPBB3-9564] - Reported messages are not assigned the default report reason when a reason is removed from the ACP using the mssqlnative DBAL +
  • +
  • [PHPBB3-9565] - It is impossible to create a custom profile field using the mssqlnative DBAL +
  • +
  • [PHPBB3-9566] - Two debug notices are displayed when setting a custom profile field though the UCP using the mssqlnative DBAL +
  • +
  • [PHPBB3-9583] - MSSQL native backups cannot be restored +
  • +
  • [PHPBB3-9606] - Drop redundant SQL query for unreads fetching +
  • +
  • [PHPBB3-9613] - Implement a load switch for unreads search feature. +
  • +
  • [PHPBB3-9817] - Make build script create blank config.php +
  • +
+ +

Changes since 3.0.7

+ +
    +
  • [Sec] Do not expose forum content of forums with ACL entries but no actual permission in ATOM Feeds. (Bug #58595)
  • +
+ +

Changes since 3.0.6

+ +
    +
  • [Fix] Allow ban reason and length to be selected and copied in ACP and subsilver2 MCP. (Bug #51095)
  • +
  • [Fix] Force full date for board online record date.
  • +
  • [Fix] Correctly reset login keys if passed value is the current user. (Bug #54125)
  • +
  • [Fix] Correctly set last modified headers. (Bug #54245, thanks Paul.J.Murphy)
  • +
  • [Fix] Show correct HTML title when reporting private messages. (Bug #54375)
  • +
  • [Fix] Correctly exclude subforums from ATOM Feeds. (Bug #54285)
  • +
  • [Fix] Do not link to user profile in ATOM feed entry if post has been made by the guest user. (Bug #54275)
  • +
  • [Fix] Make word censoring case insensitive. (Bug #54265)
  • +
  • [Fix] Fulltext-MySQL search for keywords and username at the same time. (Bug #54325)
  • +
  • [Fix] Various XHTML and CSS mistakes in prosilver and subsilver2. (Bugs #54705, #55895, #57505, #57875 - Patch by HardStyle)
  • +
  • [Fix] Correctly show topic ATOM feed link when only post id is specified. (Bug #53025)
  • +
  • [Fix] Cleanly handle forum/topic not found in ATOM Feeds. (Bug #54295)
  • +
  • [Fix] PHP 5.3 compatibility: Check if function dl() exists before calling it. (Bug #54665)
  • +
  • [Fix] PHP 5.3 compatibility: Disable E_DEPRECATED on startup to keep set_magic_quotes_runtime(0) quiet. (Bug #54495)
  • +
  • [Fix] Correctly replace table prefix before inserting schema data into the database. (Bug #54815)
  • +
  • [Fix] Correctly take post time instead of topic time for the overall forum feed statistics row. (Bug #55005)
  • +
  • [Fix] Posting errors with CAPTCHAs using user::add_lang(). (Bug #55245)
  • +
  • [Fix] Use memcache::replace() instead of memcache::set() for existing keys to prevent problems.
  • +
  • [Fix] Check for required functions in eAccelerator. (Bug #54465)
  • +
  • [Fix] Use correct RFC 3339 date format in ATOM feed. (Bug #55005)
  • +
  • [Fix] Do not deliver topics from unreadable or passworded forums in the news feed. (Bug #54345)
  • +
  • [Fix] Restore user language choice to compiled stylesheets. (Bug #54035)
  • +
  • [Fix] Add missing language entries. (Bug #55095)
  • +
  • [Fix] Do not permit unauthorised users to delete private messages from folder listing. (Bug #54355)
  • +
  • [Fix] Correctly check for empty strings in custom profile fields. (Bug #55335)
  • +
  • [Fix] Use correct options to parse BBCodes in signatures when previewing PMs.
  • +
  • [Fix] Correct rendering of prosilver quick reply under IE6. (Bug #54115 - Patch by Raimon)
  • +
  • [Fix] Correct wording for "How do I show an image below my username" question answer in FAQ. (Bug #23935)
  • +
  • [Fix] Handle export of private messages where all recipients were deleted. (Bug #50985)
  • +
  • [Fix] Correctly get unread status information for global announcements in search results.
  • +
  • [Fix] Correctly handle global announcements in ATOM feeds.
  • +
  • [Fix] Use correct limit config parameter in the News feed.
  • +
  • [Fix] Restrict search for styles/../style.cfg to folders. (Bug #55665)
  • +
  • [Fix] Add ability to disable overall (aka board-wide) feed.
  • +
  • [Fix] Do not pass new_link parameter when creating a persistent connection with mysql. (Bug #55785)
  • +
  • [Fix] Improved search query performance through sorting words by their occurance. (Bug #21555)
  • +
  • [Fix] Correctly move sql_row_pointer forward when calling sql_fetchfield() on cached queries. (Bug #55865)
  • +
  • [Fix] Remove item limit from "All forums" feed.
  • +
  • [Fix] Do not use group colours for usernames on print view. (Bug #30315 - Patch by Pasqualle)
  • +
  • [Fix] Pagination of User Notes in MCP uses two different config values. (Bug #56025)
  • +
  • [Fix] List hidden groups on viewprofile where the viewing user is also a member. (Bug #31845)
  • +
  • [Fix] Sort viewprofile group list by group name.
  • +
  • [Fix] Strictly check whether a moderator can post in the destination forum when moving topic. (Bug #56255)
  • +
  • [Fix] Added some error handling to the compress class.
  • +
  • [Fix] Correctly determine permissions to show quick reply button. (Bug #56555)
  • +
  • [Fix] Do not unsubscribe users from topics replying with quickreply. (Bug #56235)
  • +
  • [Fix] Don't submit when pressing enter on preview button. (Bug #54395)
  • +
  • [Fix] Load reCAPTCHA over https when using a secure connection to the board. (Bug #55755)
  • +
  • [Fix] Clarify explanation of bump feature setting. (Bug #56075)
  • +
  • [Fix] Properly paginate unapproved posts in the MCP. (Bug #56285)
  • +
  • [Fix] Do not duplicate previous/next links in pagination text of moderator logs and user notes in MCP for subsilver2. (Bug #55045)
  • +
  • [Fix] Do not automatically unsubscribe users from topics, when email and jabber is disabled.
  • +
  • [Fix] Don't send activation email when user tries to change email without permission. (Bug #56335 - Fix by nrohler)
  • +
  • [Fix] Replace hard coded "px" with translated language-string. (Bug #52495)
  • +
  • [Fix] Correctly hover list menu in UCP and MCP for RTL languages. (Bug #49945)
  • +
  • [Fix] Correctly orientate quoted text image on RTL languages. (Bug #33745)
  • +
  • [Fix] Deprecate $allow_reply parameter to truncate_string() (Bug #56675)
  • +
  • [Fix] Fall back to default language email template if specified file does not exist. (Bug #35595)
  • +
  • [Fix] Update users last visit field correctly when changing activation status. (Bug #56185)
  • +
  • [Fix] Database updater now separates ADD COLUMN from SET NOT NULL and SET DEFAULT, when using PostgreSQL <= 7.4 (Bug #54435)
  • +
  • [Fix] Styles adjustment to correctly display an order of rtl/ltr mixed content. (Bugs #55485, #55545)
  • +
  • [Fix] Fix language string for PM-Reports refering to post-data. (Bug #54745)
  • +
  • [Fix] Do not store email templates in database. (Bug #54505)
  • +
  • [Fix] Fix javascript bug in the smilies ACP. (Bug #55725)
  • +
  • [Fix] Unify BBCode Selection across browsers. (Bug #38765)
  • +
  • [Fix] Allow convertors to read in configuration from files. (Bug #57265 - Patch by Dicky)
  • +
  • [Fix] Fix problems with firebird by no longer using 'count' as a column alias. (Bug #57455)
  • +
  • [Fix] Small language correction for the FAQ page. (Bug #57825)
  • +
  • [Fix] Restrict search for language/../iso.txt to folders. (Bug #57795)
  • +
  • [Fix] Make user_email_hash() function independent from system's architecture. (Bug #57755)
  • +
  • [Fix] Correct behavior of "force_approved_state" when value is false. (Bug #57715)
  • +
  • [Fix] Global announcements could not be accessed on a board using Firebird as the database server. (Bug #57525)
  • +
  • [Fix] BBCode parser now uses the user object for all settings rather than taking some from the template object (Bug #57365)
  • +
  • [Fix] Ensure a database connection is available before logging general errors. (Bug #57975)
  • +
  • [Fix] Do not delete unrelated attachments when deleting empty forums. (Bug #57375)
  • +
  • [Fix] Update: Store expected resulting file contents in cache and do not suggest further merges if the contents match, also fixes infinite merge loop (Bug #54075)
  • +
  • [Change] Move redirect into a hidden field to avoid issues with mod_security. (Bug #54145)
  • +
  • [Change] Log activation through inactive users ACP. (Bug #30145)
  • +
  • [Change] Send time of last item instead of current time in ATOM Feeds. (Bug #53305)
  • +
  • [Change] Use em dash instead of hyphen/minus as separator in ATOM Feeds item statistics. (Bug #53565)
  • +
  • [Change] Alter ACP user quick tools interface to reduce confusion with the delete operation.
  • +
  • [Change] Send statistics now check for IPv6 and send private network status as a boolean.
  • +
  • [Change] Split "All topics" feed into "New Topics" and "Active Topics" feeds.
  • +
  • [Change] Forum feed no longer includes posts of subforums.
  • +
  • [Change] Show login attempt CAPTCHA option in the captcha plugin module.
  • +
  • [Change] It is no longer possible to persist a solution for the login CAPTCHA.
  • +
  • [Change] SQLite is no longer autoloaded by the installer. (Bug #56105)
  • +
  • [Change] Friends and foes will not show up as private message rule options if their respective UCP modules are disabled. (Bug #51155)
  • +
  • [Change] Offer for guests to log in for egosearch and unreadposts search before the search permissions check. (Bug #51585)
  • +
  • [Change] Show warning box for users of PHP < 5.2.0 about phpBB ending support.
  • +
  • [Change] Disallow deleting the last question of the Q&A CAPTCHA.
  • +
  • [Change] Tweak Q&A CAPTCHA garbage collection.
  • +
  • [Change] Show a proper preview for the Q&A CAPTCHA. (Bug #56365)
  • +
  • [Change] Speed up topic move operation by adding an index for topic_id on the topics track table. (Bug #56545)
  • +
  • [Change] Warn users about potentially dangerous BBcodes.
  • +
  • [Feature] Ability to use HTTP authentication in ATOM feeds by passing the GET parameter "auth=http".
  • +
  • [Feature] Add INTTEXT token type to custom bbcodes to allow non-ASCII letters in html attributes.
  • +
  • [Feature] Add ability to enable quick reply in all forums.
  • + +
+ +

Changes since 3.0.5

+ +
    +
  • [Fix] Allow whitespaces in avatar gallery names. (Bug #44955)
  • +
  • [Fix] Sorting by author or subject on viewtopic now preserves the order. (Bug #44875)
  • +
  • [Fix] Correctly determine writable status of files on Windows operating systems. (Bug #39035)
  • +
  • [Fix] Show report button in prosilver for guests who are allowed to report posts. (Bug #45695)
  • +
  • [Fix] Correctly show private message history. (Bug #46065)
  • +
  • [Fix] Various XHTML mistakes in prosilver, subsilver2 and the ACP. (Bugs #25545, #26315, #38555, #45505 - Patch by Raimon, #45785, #45865, #47085 - Patch by Raimon)
  • +
  • [Fix] Fix some ACP style issues. (Bug #16109 - Patch by prototech)
  • +
  • [Fix] Move post bump information markup to the template. (Bug #34295)
  • +
  • [Fix] Show error in the ACP when template folder is not readable. (Bug #45705)
  • +
  • [Fix] Adjust viewonline filename regular expression to be less strict. (Bug #46215)
  • +
  • [Fix] Correctly apply the "can change vote" permission again. Regression introduced in r9470. (Bug #45895)
  • +
  • [Fix] Remove data from friend/foe table when deleting user. (Bug #45345)
  • +
  • [Fix] Correctly hide skiplink in prosilver right-to-left mode. (Bug #45765 - Patch by prototech and bantu)
  • +
  • [Fix] Fix dynamic config update routine error if firebird is used. (Bug #46315)
  • +
  • [Fix] Allow friends/foes to be added and removed at the same time. (Bug #46255)
  • +
  • [Fix] Only change topic/post icon on edit if icons are enabled and user is allowed to use icons. (Bug #46355)
  • +
  • [Fix] Fix saving custom profile fields in ACP if Oracle is used. (Bug #46015)
  • +
  • [Fix] Make view_log() more resilient to corrupt serialized data. (Bug #46545)
  • +
  • [Fix] Show error if hostname lookup doesn't return a valid IP address when banning. (Bug #45585)
  • +
  • [Fix] Fix incorrect layout when loading private message draft. (Bug #38435 - Patch by nickvergessen)
  • +
  • [Fix] Show proper error message when trying to add bots to friends/foes list. (Bug #40205)
  • +
  • [Fix] Fixed database backup and restore with Oracle DBMS. (Bug #46715)
  • +
  • [Fix] Update attachments table when deleting user and retaining his posts. (Bug #40245)
  • +
  • [Fix] Correctly detect files in subfolders when viewing cached template files. (Bug #46145)
  • +
  • [Fix] Display user's jabber address in popup if jabber functionality is disabled. (Bug #20775)
  • +
  • [Fix] Correctly exclude forums from active topics list. (Bug #19135)
  • +
  • [Fix] Do not display banned users in birthday list. (Bug #20625)
  • +
  • [Fix] Fix function to recalculate nested sets. (Bug #41555 - Patch by EXreaction)
  • +
  • [Fix] Display but also highlight already used rank images while assigning new ranks. (Bug #22665)
  • +
  • [Fix] Correctly orientate quoted text image on RTL languages. (Bug #33745)
  • +
  • [Fix] Do not display "View user notes" and "Warn user" links in user profile if corresponding MCP modules are disabled. (Bug #10519)
  • +
  • [Fix] Show proper error message when trying to create a private messages folder with an empty name. (Bug #39875)
  • +
  • [Fix] No longer state that it is possible to manage group leaders from the UCP. (Bug #19945)
  • +
  • [Fix] Do not throw an error when PDO is a shared module and not loaded preventing SQLite from being loaded.
  • +
  • [Fix] Fix censoring of unicode words. (Bug #16555)
  • +
  • [Fix] Display coloured usernames in ACP groups management screens.
  • +
  • [Fix] Correctly describe founder permissions on trace-information. (Bug #37235)
  • +
  • [Fix] Correct the width value for poll_center.gif omitted in imageset.cfg for subsilver2. (Bug #43005)
  • +
  • [Fix] Correctly load complex language variable using acp_language. (Bug #45735 - Patch by leviatan21)
  • +
  • [Fix] Fix reapply_sid() to correctly strip session id in certain circumstances. (Bug #43125 - Patch by leviatan21)
  • +
  • [Fix] Correctly state why one language pack is marked with an asterisk in the ACP. (Bug #37565)
  • +
  • [Fix] Correctly check if install directory is still present. (Bug #46965)
  • +
  • [Fix] Correct banned user behaviour when "force password change" is enabled. (Bug #47145 - Patch by nickvergessen and leviatan21)
  • +
  • [Fix] Correctly display ACP logs options, without permission to clear logs. (Bug #24155 - Patch by leviatan21)
  • +
  • [Fix] Display topic icons in MCP forum view again (only prosilver).
  • +
  • [Fix] Properly display post status messages in topic when post is reported and unapproved (Bug #44455 - Patch by leviatan21)
  • +
  • [Fix] Do not remove recipients when loading private message draft. (Bug #38395)
  • +
  • [Fix] Add author name to moderator log when deleting post/topic. (Bug #46225)
  • +
  • [Fix] Fix broken "Report details" link in the MCP. (Bug #46975)
  • +
  • [Fix] Resolve accesskey conflicts in prosilver. (Bug #44685)
  • +
  • [Fix] Check if template file is empty before trying to read from it. (Bug #47345)
  • +
  • [Fix] More descriptive descriptions for permissions to use BBCode, smilies, images and flash. (Bug #36065)
  • +
  • [Fix] Fix style issues in print mode. (Bug #26375 - Patch by leviatan21)
  • +
  • [Fix] Fix minor issue with L_QUOTE language string missing in several PM composing modes. (Bug #39625)
  • +
  • [Fix] Also fetch posts of guests and deleted or deactivated users while searching for author names. (Bug #36565, #47765)
  • +
  • [Fix] Show end of ban in MCP and ACP when user is banned by duration. (Bug #47815 - Patch by Pyramide)
  • +
  • [Fix] Correctly count posts awaiting approval in the MCP. (Bug #47685)
  • +
  • [Fix] Display user's posts count in private message when it is equal to 0 (prosilver). (Bug #40155)
  • +
  • [Fix] Only allow users to disable word censor if globally allowed. (Bug #47575 - Patch by 00mohgta7)
  • +
  • [Fix] Fix database updater and db tools to support multiple column changes/additions/removals with SQLite.
  • +
  • [Fix] Correctly detect GZIP status in debug mode. (Bug #24075)
  • +
  • [Fix] Posting smilies in view more smilies screen now works again in IE. (Bug #46025 - Patch by leviatan21)
  • +
  • [Fix] Properly convert and show filesize information. (Bug #47775)
  • +
  • [Fix] Add ability to prune users who never logged in. (Bug #44295)
  • +
  • [Fix] Show smilies and images in topic print view. (Bug #47265)
  • +
  • [Fix] Force full date in private message print view.
  • +
  • [Fix] Fix "Always show a scrollbar for short pages" for IE8 and Firefox 3.5. (Bug #47865 - Patch by stokerpiller)
  • +
  • [Fix] Do not allow setting group as default group for pending users. (Bug #45675)
  • +
  • [Fix] Fail gracefully if store folder is not writable during update. (Bugs #46615, #46945)
  • +
  • [Fix] Hide profile-icon from viewtopic page if user has no permissions (subsilver2 only). (Bug #37635 - Patch by leviatan21)
  • +
  • [Fix] Correct escaping/unescaping in the LDAP authentication plugin. (Bug #48175)
  • +
  • [Fix] Add hard limit for smilies.
  • +
  • [Fix] Remove redundant SQL query from ucp.php. (Bug #40305)
  • +
  • [Fix] Reorder frame order of animated subsilver2 topic icons to be useful when animations are disabled. (Bug #29385 - Patch by prototech)
  • +
  • [Fix] Ensure user errors are displayed regardless of PHP settings. (Bug #47505)
  • +
  • [Fix] Permit null values for non-required integer custom profile fields and ensure zero complies with the range limits. (Bug #40925)
  • +
  • [Fix] Allow changing forum from select box under certain circumstances. (Bug #37525)
  • +
  • [Fix] Display required fields notice on registration above the custom profile fields. (Bug #39665)
  • +
  • [Fix] Copy poll options properly when copying topic. (Bug #39065)
  • +
  • [Fix] Fix error with disapproval of topics having several queued posts only. (Bug #47705)
  • +
  • [Fix] Preserve newlines in template files (one newline had been always dropped after a template variable due to PHP's handling of closing tags).
  • +
  • [Fix] Be less strict with FTP daemons for getting directory filelists. (Bug #46295)
  • +
  • [Fix] Fix set_custom_template for database-stored styles. (Bug #40515)
  • +
  • [Fix] Banning an already banned user states to be successful, but has no effect. (Bug #47825 - Patch by Pyramide)
  • +
  • [Fix] Do not add style parameter again to URL after admin re-authentification. (Bug #18005 - Patch by leviatan21)
  • +
  • [Fix] Do not cut post-message in between HTML-Entities on search.php. (Bug #31505 - Patch by leviatan21)
  • +
  • [Fix] Correctly set attachment flag for topics, posts and pms after deleting attachments. (Bug #48265 - Patch by MarcoDM and nickvergessen)
  • +
  • [Fix] Display "Locked" button instead of "Reply" one for locked forum in viewtopic (prosilver). (Bug #38055 - Patch by Raimon)
  • +
  • [Fix] Correctly propagate umlauts over search result pages. (Bug #33755)
  • +
  • [Fix] Preserve post options when refusing to save the post as a draft. (Bug #39115)
  • +
  • [Fix] Do not send private message back to sender if sender is in the same group the private message was sent to.
  • +
  • [Fix] Correctly add user to a group making it a default one. (Bug #48345)
  • +
  • [Fix] Add log entry when copying forum permissions.
  • +
  • [Fix] Min/max characters per posts no longer affects poll options. (Bug #47295)
  • +
  • [Fix] Correctly log action when users request to join a group. (Bug #37585)
  • +
  • [Fix] Do not try to create thumbnails for images we cannot open properly. (Bug #48695)
  • +
  • [Fix] Apply locale-independent basename() to attachment filenames. New function added: utf8_basename(). (Bug #43335 - Patch by ocean=Yohsuke)
  • +
  • [Fix] Adjust build_url() to not prepend $phpbb_root_path if path returned from redirect() is an URL. This fixes redirect issues with some installations and bridges. (Bug #47535)
  • +
  • [Fix] Do not mark global announcements as read if all topics in a forum become read (Bug #15729).
  • +
  • [Fix] Fix general error in registration, caused by an undefined $config variable in validate_referer(). (Bug #49035 - Patch by wjvriend)
  • +
  • [Fix] Correctly extract column default value when exporting PostgreSQL tables. (Bug #48955)
  • +
  • [Fix] Allow updater to work correctly with PHP filename extensions other than ".php". (Bugs #15809, #49215)
  • +
  • [Fix] Update search index if only post subject changed. (Bug #49435)
  • +
  • [Fix] Fix who is online displaying incorrect data. (Bug #49485, thanks Brainy)
  • +
  • [Fix] Fixed incorrect "topic does not exist" if unapproved posts were visited without global moderator permissions. (Bug #47795)
  • +
  • [Fix] Prevent style switcher from blocking the tab key. (Bug #49335)
  • +
  • [Fix] Correctly redirect back to MCP main page after posts approval/disapproval from it. (Bug #49625)
  • +
  • [Fix] Do not display topic approval status image for shadow topic if a user is not a moderator in the forum the topic has been moved to. (Bug #43295)
  • +
  • [Fix] Fix email problems on servers with PHP installations not accepting RFC-compliant subject string passed to the mail()-function. (Bug #46725)
  • +
  • [Fix] Correctly orientate control panel navigation background-image on RTL languages. (Bug #49945)
  • +
  • [Fix] Sort private messages by message time and not message id. (Bug #50015)
  • +
  • [Fix] Make sure only logs for existing users are displayed and user-specific logs removed on user deletion. (Bug #49855)
  • +
  • [Fix] Only show "Add friend" and "Add foe" links if the specific module is enabled. (Bug #50475)
  • +
  • [Fix] Correctly display list items in forum description in prosilver and administration. (Bug #48055 - Patch by leviatan21)
  • +
  • [Fix] Only embed cron.php if there is no cron lock present to reduce overhead. (Bug #45725 - Patch by TerryE)
  • +
  • [Fix] Add header gradient back into subsilver2 but keep site logo easily replaceable with smaller and bigger ones. (Bug #11142 - Patch by dark/Rain and Raimon)
  • +
  • [Fix] Send activation email when activating user from user settings. (Bug #43145)
  • +
  • [Fix] Do not show resend activation email link when using admin activation. (Bug #44375 - Patch by bbrunnrman)
  • +
  • [Fix] Do not display links to user/post search if search is disabled. (Bug #50685 - Patch by HardStyle)
  • +
  • [Fix] Fix icon alignment for forums with large descriptions in subsilver2. (Bug #50445)
  • +
  • [Fix] Correctly display underlined links placed in last line in viewtopic. (Bug #14811 - Patch by primehalo)
  • +
  • [Fix] Only check whether forum image exists if forum image is specified. (Bug #51905)
  • +
  • [Fix] Fixed database updater for changes to columns having default value in MSSQL (adding/dropping constraints).
  • +
  • [Fix] Jabber SASL PLAIN authentication failures. (Bug #52995)
  • +
  • [Fix] Check sort options on memberlist to avoid a general error. (Bug #53655)
  • +
  • [Fix] Fix sql error in cache_moderators() if using postgresql. (Bug #53765)
  • +
  • [Change] Database updater now supports checking for existing/missing indexes.
  • +
  • [Change] submit_post() now accepts force_approved_state key passed to $data to indicate new posts being approved (true) or unapproved (false).
  • +
  • [Change] Change the data format of the default file ACM to be more secure from tampering and have better performance.
  • +
  • [Change] Template engine now permits variable includes to a limited extent.
  • +
  • [Change] Quote BBCode no longer requires the f_reply permission. (Bug #16079)
  • +
  • [Change] Banning/unbanning users now generates an entry in their user notes. (Bug #21825)
  • +
  • [Change] Smilies no longer require the f_bbcode permission. (Bug #26545)
  • +
  • [Change] Ability to define column split in FAQ/BBCode help. (Bug #31405)
  • +
  • [Change] Changed behaviour of group_create() function to support specifying additional group columns.
  • +
  • [Change] Hide avatar when avatar-type is not allowed. (Bug #46785 - Patch by cYbercOsmOnauT and nickvergessen)
  • +
  • [Change] INCLUDEPHP paths are now relative to $phpbb_root_path. (Bug #45805)
  • +
  • [Change] Ability to fetch moderators with get_moderators() even if load_moderators setting is off. (Bug #35955)
  • +
  • [Change] "Post details" links with image in MCP. (Bug #39845 - Patch by leviatan21)
  • +
  • [Change] PM history now only shows PMs of users you currently reply to. (Bug #39505)
  • +
  • [Change] Show quote button for own PMs in PM history. (Bug #37285)
  • +
  • [Change] Fetch requested cookie variables directly from cookie super global. (Bug #47785)
  • +
  • [Change] Add confirmation for deactivating styles. (Bug #14304 - Patch by leviatan21)
  • +
  • [Change] Add confirmation for deactivating language packs. (Patch by leviatan21)
  • +
  • [Change] Add confirmation for deleting permissions. (Bug #13673)
  • +
  • [Change] Add pagination for icons and smilies in the ACP and smilies in the smiley popup.
  • +
  • [Change] Cache get_username_string() function calls on viewtopic.
  • +
  • [Change] Cache version check.
  • +
  • [Change] When creating a new forum without copying permissions, ask again.
  • +
  • [Change] Introduce new parameter to page_header() for forum specific who is online listings.
  • +
  • [Change] Changed minimum requirement for Firebird DBMS from 2.0+ to 2.1+.
  • +
  • [Change] Unapproved topics can no longer be replied to. (Bug #44005, #47675, #23605)
  • +
  • [Change] Require user to be registered and logged in to search for unread posts if topic read tracking is disabled for guests. (Bug #49525)
  • +
  • [Change] Allow three-digit hex notation in color BBcode. (Bug #39965 - Patch by m0rpha)
  • +
  • [Change] Simplified login_box() and redirection after login. S_LOGIN_ACTION can now be used on every page. (Bug #50285)
  • +
  • [Change] Do not take edit post time into account for determining permission to delete last post in topic. (Bug #48615)
  • +
  • [Change] Resize oversized topic icons. (Bug #44415)
  • +
  • [Change] Banned IPs are now sorted. (Bug #43045 - Patch by DavidIQ)
  • +
  • [Change] phpBB updater now skips sole whitespace/tab changes while computing differences. This reduces the chance of conflicts tremendously.
  • +
  • [Change] phpBB updater now solves common conflicts on its own. This further reduces the chance of conflicts.
  • +
  • [Feature] Add language selection to the registration terms page. (Bug #15085 - Patch by leviatan21)
  • +
  • [Feature] Backported 3.2 captcha plugins: +
      +
    • Classic and GD CAPTCHA
    • +
    • reCaptcha (based on API from recaptcha.net by Mike Crawford and Ben Maurer)
    • +
    • Q&A CAPTCHA
    • +
    • 3D Wave (by Robert "Xore" Hetzler)
    • +
    +
  • +
  • [Feature] Introduced new ACM (Cache) plugins: + +
  • +
  • [Feature] ATOM Feeds (Idea from RSS Feed 2.0 MOD (Version 1.0.8/9) by leviatan21)
  • +
  • [Feature] New groups option to excempt group leaders from group permissions.
  • +
  • [Feature] New "Newly Registered Users" group for assigning permissions to newly registered users. They will be removed from this group once they reach a defineable amount of posts.
  • +
  • [Feature] Ability to define if the "Newly Registered Users" group will be assigned as the default group to newly registered users.
  • +
  • [Feature] Add new option to disable avatars board-wide. (Bug #46785 - Patch by cYbercOsmOnauT and nickvergessen)
  • +
  • [Feature] Enhance obtain_users_online_string to be able to return user-lists for other session items. (Bug #31975)
  • +
  • [Feature] Add unapproved topic icon for moderators on forum list. (Bug #46865)
  • +
  • [Feature] Ability to define minimum number of characters for posts/pms.
  • +
  • [Feature] Store signature configuration options in database. (Bug #45115)
  • +
  • [Feature] Add bare-bones quick-reply editor to viewtopic.
  • +
  • [Feature] Detect if a post has been altered by someone else while editing.
  • +
  • [Feature] Add unread posts quick search option. (Bug #46765)
  • +
  • [Feature] Add option to disable avatar uploads from remote locations. (Bug #45375)
  • +
  • [Feature] Ability to delete warnings and keep warnings permanently. (Bug #43375)
  • +
  • [Feature] Ability to empty a user's outbox from the user ACP quick tools.
  • +
  • [Feature] Ability to search ACP/MCP logs.
  • +
  • [Feature] Users can report PMs to moderators which are then visible in a new MCP module.
  • +
  • [Feature] Parse email text files with the template engine.
  • +
  • [Feature] Use email-style quoting when bbcodes are disabled.
  • +
  • [Feature] Added new functionality to inactive users module: +
      +
    • Ability to set users per page.
    • +
    • Ability to sort by posts/number of reminders/last reminded date.
    • +
    • Show number of posts and ability to search posts.
    • +
    • Show number of reminders sent to user.
    • +
    • Show date of last reminder sent to user.
    • +
    +
  • +
  • [Feature] Display version check on ACP main page.
  • +
  • [Feature] Ability to copy permissions from one forum to several other forums.
  • +
  • [Feature] Ability to control the display of custom profile fields on viewtopic. (Bug #48985)
  • +
  • [Feature] Fallback options for missing language files. (Bug #38575 - Patch by EXreaction)
  • +
  • [Feature] Separate "PM Reply" and "PM Reply to all" in prosilver.
  • +
  • [Feature] Place debug notices during captcha rendering in the error log - useful for debugging output already started errors.
  • +
  • [Feature] Ability to define constant PHPBB_USE_BOARD_URL_PATH to use board url for images/avatars/ranks/imageset (useful for bridges and applications using phpBB).
  • +
  • [Feature] Added function to generate email hash. (Bug #49195)
  • +
  • [Feature] Style authors are now able to define the default submit button used for form submission on ENTER keypress on forms using more than one submit button. Prosilver uses this for the posting page(s) and registration screen.
  • +
  • [Feature] Ability to specify amount of time user is able to delete his last post in topic.
  • +
  • [Feature] Send anonymous statistical information to phpBB on installation and update (optional).
  • +
+ +

Changes since 3.0.4

+ +
    +
  • [Fix] Delete user entry from ban list table upon user deletion (Bug #40015 - Patch by TerraFrost)
  • +
  • [Fix] Posts incremented for multiple approval of the same topic (Bug #40495 - Patch by TerraFrost)
  • +
  • [Fix] Missing end " in quote bb tag deletes text (Bug #40565 - Patch by TerraFrost)
  • +
  • [Fix] Friend/foe system displays posts made by foes while composing (Bug #40325 - Patch by TerraFrost and Highway of Life)
  • +
  • [Fix] Check forum_image whether it exists (Bug #39005 - Patch by TerraFrost)
  • +
  • [Fix] The sql query in acp_users.php lacks a condition (Bug #40275 - Patch by grimskies)
  • +
  • [Fix] Added missing read permission information for some phpbb_chmod() calls
  • +
  • [Fix] Correctly display future dates (Bug #38755)
  • +
  • [Fix] Fix guest/bot session problems with apache authentication plugin (Bug #41085)
  • +
  • [Fix] Whois now works reliably for RIRs other than APNIC and RIPE. (Bug #40085)
  • +
  • [Fix] Correctly convert Niels' Birthday MOD to the date format used in phpBB3. (Bug #32895)
  • +
  • [Fix] Changed the success message when requesting a new password to be more accurate. (Bug #41405)
  • +
  • [Fix] Add missing anti-abuse email headers to acp_inactive.php and ucp_resend.php.
  • +
  • [Fix] Only remind users in the correct inactive states depending on the board account activation level.
  • +
  • [Fix] Various XHTML mistakes in prosilver, subsilver2 and the ACP. (Bugs #41745, #42265 - Patch by nickvergessen, #38465, #43015, #46585 - Patch by Raimon)
  • +
  • [Fix] Log password changes via password reset function. (Bug #41365)
  • +
  • [Fix] Poll, negative durations generate error (Bug #41295 - Patch by TerraFrost)
  • +
  • [Fix] Visibility of custom field on registration is incorrectly controlled by setting "display" (Bug #41385 - Patch by Eelke and fade2gray)
  • +
  • [Fix] Smilies in username are misparsed on [quote=""] (Bug #41955 - Patch by TerraFrost)
  • +
  • [Fix] Deleting all posts in a topic - bad redirect (Bug #41705 - Patch by TerraFrost)
  • +
  • [Fix] Deleted users still appear logged in (Bug #41985 - Patch by TerraFrost)
  • +
  • [Fix] Removed redundant code and unnecessary queries in forum management. (Bug #42265 - Patch by nickvergessen)
  • +
  • [Fix] Correct mbstring regular expression for the allowable username characters, only affects USERNAME_LETTER_NUM_SPACERS. (Bug #42325)
  • +
  • [Fix] Fix infinite loop in message handler if cache directory is not writable. (Bug #38675)
  • +
  • [Fix] While post is awaiting approval it can still be edited even though it can not be seen (Bug #41435 - Patch by TerraFrost)
  • +
  • [Fix] Fix imageset editing for retaining and correctly setting dimensions for images, as well as displaying correct settings for first page load.
  • +
  • [Fix] Use OS-specific line endings for mail headers. (related to Bug #42755)
  • +
  • [Fix] Hide font size options which are bigger than the allowed size in the editor. (Bug #42615 - Patch by nickvergessen)
  • +
  • [Fix] Better thumbnail quality with imagemagick. (Bug #42565)
  • +
  • [Fix] Fix download count increments for image attachments without corresponding thumbnails. (Bug #42505)
  • +
  • [Fix] Fix wrong bot ip check if bot ip was wrongly entered by admin. (Bug #42485)
  • +
  • [Fix] Fix javascript errors in simple header (prosilver) by adding forum_fn.js and the corresponding variables. (Bug #42135)
  • +
  • [Fix] Set connection encoding for MySQL versions 4.1.0 to 4.1.2. This may fix some conversion issues with special characters. (Bug #41805)
  • +
  • [Fix] Deleting private message attachments could delete post attachments. (Bug #42815)
  • +
  • [Fix] Do not suppress PHP notices/errors in language packs if DEBUG_EXTRA mode enabled. (Bug #41485)
  • +
  • [Fix] Flash files do not display anymore after update to flash player 10 (Bug #41315)
  • +
  • [Fix] Use FQDN for SMTP EHLO/HELO command. (Bug #41025)
  • +
  • [Fix] Mass Email works again for users with empty jabber address but notification set to 'both'. (Bug #39755)
  • +
  • [Fix] Fix race condition for updating post/topic/etc. counter. (Reported by BartVB)
  • +
  • [Fix] Fix duplicate creation of acl options in acl_add_options() under certain conditions. (Bug #38385, #40225)
  • +
  • [Fix] Cancel when replying to global announcement redirects to first forum - not to the current forum (Bug #41225 - Patch by TerraFrost)
  • +
  • [Fix] Cursor Jumps on New Topic in IE (Bug #42455 - Patch by TerraFrost)
  • +
  • [Fix] Add indicator to be used in code if session was created (user visits the site for the first time).
  • +
  • [Fix] Correctly count topic views for guests visiting the website the first time by entering the topic directly (Bug #43445)
  • +
  • [Fix] Fix bug in postgresql db layer for LIMIT ALL clauses (Reported by JRSweets)
  • +
  • [Fix] Sort backups by date, newest first (Bug #14818)
  • +
  • [Fix] Prevent incomplete backups stored if option "store and download" is selected and admin cancel download by removing the option. (Bug #20325)
  • +
  • [Fix] Enforce correct case for template variables
  • +
  • [Fix] Set topic_last_view_time on post/reply/edit to circumvent race conditions in auto prune and false removal of topics for manual forum prune (Bug #18055, #43515)
  • +
  • [Fix] Correctly split long subject lines according to the used RFC. This fixes extra spaces within long subjects. (Bug #43715)
  • +
  • [Fix] Fix skipping messages if using next/prev PM in history links. (Bug #22205)
  • +
  • [Fix] Messenger now also able to use a custom language path. (Bug #36545)
  • +
  • [Fix] PM Export uses ISO 8601 date now. (Bug #32645)
  • +
  • [Fix] Apply append_sid() to newest/latest post links in viewforum/search and UCP main module. (Bug #26815)
  • +
  • [Fix] Do not create thumbnail if thumbnail would've the same size as the original image. (Bug #30725)
  • +
  • [Fix] Ability to vote in poll is now required for the ability to change existing vote. (Bug #38925)
  • +
  • [Fix] Search for 'topic title only' and 'first post' should work again for non-mysql dbms. (Bug #40605)
  • +
  • [Fix] Make sure additional information for accessibility is always exposed to screen readers (Bug #44335 - Patch by MarcoZ)
  • +
  • [Fix] Approving a topic when some of the posts within that topic have already been approved (Bug #42585 - Patch by TerraFrost)
  • +
  • [Fix] Online status shown when post hidden (Bug #35505 - Patch by Raimon)
  • +
  • [Fix] memberlist.php display formating can be distorted by posting long URL for website (Bug #36675 - Patch by TerraFrost)
  • +
  • [Fix] Display the online status of hidden users to users with the u_viewonline permission when viewing PMs.
  • +
  • [Fix] "Select all" selects much too much in Opera (Bug #42885 - Patch by TerraFrost and ToonArmy)
  • +
  • [Fix] Correct calculation of source/target forum statistics if mass moving topics with global announcements (Bug #44545)
  • +
  • [Fix] Fix column handling in db updater, custom profile fields an db tools for firebird DBMS (Bug #44555)
  • +
  • [Fix] IE8 textarea issues (Bug #43305)
  • +
  • [Fix] Prevent accounts from being activated by users when admin activation is turned on and the correct activation key is known.
  • +
  • [Fix] Allow the installer to operate under PHP 5.3. (Bug #45255)
  • +
  • [Change] Default difference view is now 'inline' instead of 'side by side'
  • +
  • [Change] Added new option for merging differences to conflicting files in automatic updater
  • +
  • [Change] Add link to user profile in the MCP for user notes and warn user.
  • +
  • [Change] Add IN_PHPBB check to generated cache files. (Reported by bantu)
  • +
  • [Change] Add topic icons to prosilver UCP main and subscribed templates (Bug #42735 - Patch by Raimon)
  • +
  • [Change] Add unique key to ACL options table to prevent duplicate permission options. (Bug #41835)
  • +
  • [Change] Redirect to relevant MCP page of multi-page topic if accessing quickmod tools (Split option for example)
  • +
  • [Change] Performance improvements for native fulltext search (Patch by Paul)
  • +
  • [Change] Changed jumpto() JS function to be more fail-safe. (But #27635 - Patch by peterkclee)
  • +
  • [Feature] Added new options for visual confirmation.
  • +
  • [Feature] Allow download of conflicting file for later reference in automatic updater
  • +
  • [Feature] Allow translation of custom BBCode help messages. (Patch by bantu)
  • +
  • [Feature] db_tools now support create table and drop table.
  • +
  • [Feature] Database updater checks for incompatible db schema (MySQL 3.x/4.x against MySQL 4.1.x/5.x/6.x)
  • +
  • [Feature] New search option: Maximum number of words allowed to search for.
  • +
  • [Sec] Only use forum id supplied for posting if global announcement detected. (Reported by nickvergessen)
  • +
+ +

Changes since 3.0.3

+ +
    +
  • [Fix] Allow mixed-case template directories to be inherited (Bug #36725)
  • +
  • [Fix] Regression bug from revision #8908 regarding log display in ACP
  • +
  • [Fix] Allow the UCP group management to work for groups with avatars. (Bug #37375)
  • +
  • [Fix] Fix header list build for replying oldest PM in PM history (Bug #37275)
  • +
  • [Fix] Do not display COPPA group in memberlist find member dialog if COPPA disabled (Bug #37175)
  • +
  • [Fix] Do not try to send jabber notifications if no jid entered (Bug #36775)
  • +
  • [Fix] Only display special ranks to guests; no longer display normal ranks for guests (Bug #36735)
  • +
  • [Fix] Properly treat punctuation marks after local urls (Bug #37055)
  • +
  • [Fix] Make searching for members by YIM address work in prosilver
  • +
  • [Fix] Tell users to recreate the search index after changing the common word threshold for fulltext_native (Bug #36345)
  • +
  • [Fix] Adjusted phpbb_chmod() to always set permissions for group bit.
  • +
  • [Fix] Do not increment users post count after post approval if post had been posted in a forum with no post count increasing set (Bug #37865)
  • +
  • [Fix] Extend vertical line for last post column if no posts in forum (Bug #37125)
  • +
  • [Fix] correctly update last topic/forum information if changing guest usernames through editing posts (Bug #38095)
  • +
  • [Fix] fix postcount resync for situations where low and high post ids are higher than step value, resulting in users having 0 posts. (Bug #38195)
  • +
  • [Fix] Use a left join for the topics table on search to avoid trouble with FROM syntax on some databases (Bug #37005)
  • +
  • [Fix] Do not show 'Forward' button if the user cannot send PM's
  • +
  • [Change] Alllow applications to set custom module inclusion path (idea by HoL)
  • +
  • [Change] Handle checking for duplicate usernames in chunks (Bug #17285 - Patch by A_Jelly_Doughnut)
  • +
  • [Change] Better handling and finer control for custom profile fields visibility options. (Patch by Highway of Life)
  • +
  • [Change] Performance increase for format_date() (Bug #37575 - Patch by BartVB)
  • +
  • [Change] Changed prosilver date separator from 'on' to '»'
  • +
  • [Change] Performance increase for get_username_string() (Bug #37545 - Patch by BartVB)
  • +
  • [Change] Slight performance increase for common parameter calls to append_sid() (Bug #37555 - Patch by BartVB)
  • +
  • [Feature] Added 'AGO' setting to relative date strings. For example: posted 14 minutes ago. (Patch by BartVB)
  • +
  • [Sec] Fixed an issue where deactivated accounts could be re-activated without the required privileges. (Reported by Jorick)
  • +
  • [Sec] Ask for forum password if post within passworded forum quoted in private message. (Reported by nickvergessen)
  • +
+ +

Changes since 3.0.2

+ +
    +
  • [Fix] Correctly set topic starter if first post in topic removed (Bug #30575 - Patch by blueray2048)
  • +
  • [Fix] Delete avatar files (Bug #29985).
  • +
  • [Fix] Preserve selection in the MCP. (Bug #31265).
  • +
  • [Fix] Added VST - Venezuela Standard Time (Bug #30545).
  • +
  • [Fix] Close DB connections in file.php.
  • +
  • [Fix] Correctly return results for nested cached queries (Bug #31445 - Patch by faw).
  • +
  • [Fix] Allow export of PM pages greater one. (#33155)
  • +
  • [Fix] Display coloured username of last poster in list of subscribed forums (prosilver).
  • +
  • [Fix] Added missing UCP language string NO_AUTH_READ_HOLD_MESSAGE.
  • +
  • [Fix] Do not jump back to page 1 when hiding member search in memberlist. (Bug #32515)
  • +
  • [Fix] Correctly limit input of the users location to 100 characters in the UCP and ACP. (Bug #32655)
  • +
  • [Fix] Sync reports when using the move all users posts tool in the ACP. (Bug #31165)
  • +
  • [Fix] Extra slash is included in the redirect url when redirecting to the forum root directory. (Bug #33605)
  • +
  • [Fix] Remove reported flag from shadow topics when closing reports. (Bug #19765)
  • +
  • [Fix] Do not show non indexed forums on the search page if they contain no subforums. (Bug #33125)
  • +
  • [Fix] Stop search bots incrementing topic views. (Bug #32675 - Patch by eviL<3)
  • +
  • [Fix] Use correct link for post author search. (Bug #32595)
  • +
  • [Fix] Do not decrease topics counter when deleting shadow topics. (Bug #26495)
  • +
  • [Fix] Send localised disapproval reasons in the recipients local language. (Bug #31645)
  • +
  • [Fix] Language typos/fixes. (Bugs #27625, #30755, #34185, #32795)
  • +
  • [Fix] Added missing terms parameter to search pagination. (Bug #34085)
  • +
  • [Fix] Wrong table order in query obtaining posts if post id given.
  • +
  • [Fix] Do not display reported topic icon for shadow topics. (Bug #13970)
  • +
  • [Fix] Display popular topic based on posts within topic instead of replies within topic. (Bug #16099)
  • +
  • [Fix] Expand shown ban reason in unban screen to fully show long entries. (Bug #16234)
  • +
  • [Fix] Preserve alpha transparency for created thumbnails. (Bug #16575)
  • +
  • [Fix] Use correct port delimiter for MSSQL connections in windows. (Bug #16615)
  • +
  • [Fix] Do not allow setting forums parent to the forum itself. (Bug #18855)
  • +
  • [Fix] Display assigned rank/avatar for guests. (Bug #19155)
  • +
  • [Fix] Set secure cookie for style switcher if required. (Bug #19625)
  • +
  • [Fix] Fix native full text search on postgresql while using excluding keyword matches. (Bug #19195)
  • +
  • [Fix] Pass S_SEARCH_ACTION through append_sid() in search.php. (Bug #21585)
  • +
  • [Fix] Correctly delete message attachments. (Bug #23755)
  • +
  • [Fix] Correctly handle unread status of subforums (that are not shown on the index) of forums that are shown on the index. (Bug #14589)
  • +
  • [Fix] Stop users from deleting posts after the edit time has passed or they have been locked. (Bug #19115)
  • +
  • [Fix] Split posts target forum requires 'f_post' now instead of 'm_split'. (Bug #31015)
  • +
  • [Fix] Duplicate log messages for deleting a topic ('LOG_TOPIC_DELETED' has been deprecated in favour of 'LOG_DELETE_TOPIC').
  • +
  • [Fix] Use a distinct log message for shadow topic deletions to differentiate between normal topic deletions. (Bug #34635)
  • +
  • [Fix] Fix problems with styles using an underscore within the filename. (Bug #34315)
  • +
  • [Fix] Better return links when deleting topics through the MCP. (Bug #34655)
  • +
  • [Fix] Add quoting support to PM history when composing a reply. (Bug #34285)
  • +
  • [Fix] Use phpBB 3.1.x method for storing cached data to prevent PHP bug with our usage of var_export(). (Thanks to Techie-Micheal and HoL for pointing out possible problems)
  • +
  • [Fix] Check users pm preferences for pm's sent to groups. (Bug #33245)
  • +
  • [Fix] Do not allow password reminders if u_passchg permission is not given. (Bug #14806)
  • +
  • [Fix] Implemented strict check for cached user permissions and existing ACL options. This fix makes sure cached permissions are valid, even if they got already cached.
  • +
  • [Fix] Do not show link to user/group profiles if user has no permission to view the linked page and gets a denied message anyway. (Bug #15088)
  • +
  • [Fix] Do not display last post link and sort display options for search engines. (Bug #15088)
  • +
  • [Fix] Make sure users still get notifications if they set to only be notified by Jabber, but Jabber service disabled. (Bug #29715 - Patch by Paul)
  • +
  • [Fix] Don't show forum subscription link on categories. (Bug #34895)
  • +
  • [Fix] Display a message if no topics or forums are selected when unsubscribing. (Bug #34855)
  • +
  • [Fix] Mark/unmark all links in UCP now select/unselect both subscribed topics and forums.
  • +
  • [Fix] Increase board topic counter when splitting topics. (Bug #32125)
  • +
  • [Fix] Display profile icons when viewing a topic, or PM when only the jabber icon is to be visible. (Bug #34755)
  • +
  • [Fix] Do not send PMs with warnings if the user cannot read PMs or they are disabled. (Bug #30815)
  • +
  • [Fix] Correctly convert Niels' Birthday MOD to the date format used in phpBB3. (Bug #32895)
  • +
  • [Fix] Parse BBCode lists of type square, circle and disc. (Bug #35295)
  • +
  • [Fix] Round the displayed percentages in polls. (Bug #32375)
  • +
  • [Fix] Disable mass e-mail when e-mail is disabled. (Bug #27385)
  • +
  • [Fix] Display coloured poster username of queued posts displayed on the front of the MCP.
  • +
  • [Fix] Moderators can only see reports/queue/logs from forums they can actually read. (Bug #31085)
  • +
  • [Fix] Correctly display topic when start parameter is equal to the number of posts.
  • +
  • [Fix] Correctly display topic in MCP when start parameter is equal to or greater than the number of posts. (Bug #30525)
  • + +
  • [Change] No longer allow the direct use of MULTI_INSERT in sql_build_array. sql_multi_insert() must be used.
  • +
  • [Change] Display warning in ACP if config.php file is left writable.
  • +
  • [Change] More restrictive chmod to new files being created. (phpbb_chmod() function mostly by faw)
  • +
  • [Change] Set headers to allow browsers to better cache attachments (Mylek pointed this out)
  • +
  • [Change] Hide parameters if they equal the default in viewforum/viewtopic (Bug #31185)
  • +
  • [Change] Various improvements to group listings (Bugs #32155, #32145, #32085, #26675, #26265)
  • +
  • [Change] Set headers for IE 8 in file.php
  • +
  • [Change] Do not count queued posts to user_posts.
  • +
  • [Change] Allow setting birth year to current year.
  • +
  • [Change] Do not use the topics posted table when performing an egosearch.
  • +
  • [Change] Log the forum name that topics are moved into.
  • +
  • [Change] Automatically add users/groups to the PM recipient list, if entered or selected.
  • +
  • [Change] Reply to PM now includes all previous recipients and not only the original sender.
  • +
  • [Change] Make topic selection for merge less confusing by removing unneeded controls. (Bug #21925)
  • +
  • [Change] MCP topic view checkboxes now default to unchecked.
  • +
  • [Change] Adjust language key SPLIT_AFTER to make the action clearer.
  • +
  • [Change] Add links to the post and forum when viewing a report from the MCP. (Bugs #33795, #33805)
  • +
  • [Change] Added CSRF protection to GET-only actions like marking forums.
  • +
  • [Change] Remove NUL-Bytes directly in request_var() for strings and within the custom DBAL sql_escape() functions (MSSQL, Firebird, Oracle) (reported by AdhostMikeSw)
  • + +
  • [Feature] Allow limited inheritance for template sets.
  • +
  • [Feature] Allow hard disabling of the template editor.
  • +
  • [Feature] Allow setting custom language path through $user->set_custom_lang_path(). $user->lang_path now also do not include the user language, but only the path.
  • +
  • [Feature] Ability to define nullar/singular/plural language entries
  • +
  • [Feature] Ability to mimic sprintf() calls with $user->lang() with the ability to correctly assign nullar/singular/plural language entries.
  • +
  • [Feature] Added the possibility to force user posts put in queue if post count is lower than an admin defined value. Guest posting is not affected by this setting.
  • +
  • [Feature] Added 'max_recipients' setting for private messages. This setting allows admins to define the maximum number of recipients per private message with a board-wide setting and a group-specific setting.
  • +
  • [Feature] Added new permission setting for sending private messages to groups. Now there are two permissions to define sending private messages to multiple recipients and private messages to groups.
  • +
  • [Feature] Allow specific connection to different server for jabber functionality by providing a valid JID as username. This also allows the use of talk.google.com as jabber server with gmail.com JIDs. (Bug #14989)
  • + +
  • [Sec Precaution] Stricter validation of the HTTP_HOST header (Thanks to Techie-Micheal et al for pointing out possible issues in derived code)
  • +
+ +

Changes since 3.0.1

+ +
    +
  • [Fix] Ability to set permissions on non-mysql dbms (Bug #24955)
  • +
  • [Fix] Fixed blank style on setups having no username defined within config.php (Bug #25065)
  • +
  • [Fix] Made the compress_tar class tolerate archives that do not properly have their archived contents listed (Bug #14429 / thanks to JRSweets for his patch)
  • +
  • [Fix] Moved topics should not count towards the number of topics in a forum (Bug #14648 / thanks to Schumi for his patch)
  • +
  • [Fix] Properly check for invalid characters in MySQL DB prefixes during install (Bug #18775)
  • +
  • [Fix] Bring the PostgreSQL backup system back to working order (Bug #22385)
  • +
  • [Fix] Update correct theme for cached styles in style.php (Bug #25805)
  • +
  • [Fix] Also add PHPBB_INSTALLED check to download/file.php for inline avatar delivery
  • +
  • [Fix] Unable to login to some jabber server, reverted previous change (Bug #25095)
  • +
  • [Fix] Do not return BMP as valid image type for GD image manipulation (Bug #25925)
  • +
  • [Fix] Correctly determine safe mode for temp file creation in functions_upload.php (Bug #23525)
  • +
  • [Fix] Correctly sort by rank in memberlist (Bug #24435)
  • +
  • [Fix] Purge cache after database restore (Bug #24245)
  • +
  • [Fix] Correctly display subforum read/unread icons from RTL in FF3, Konqueror and Safari3+. (thanks arod-1 for the fix, related to Bug #14830)
  • +
  • [Fix] Added missing form token in acp (thanks NBBN).
  • +
  • [Fix] Do not remove whitespace in front of url containing the boards url and no relative path appended (Bug #27355)
  • +
  • [Fix] reset forum notifications in viewtopic (Bug #28025)
  • +
  • [Fix] corrected link for searching post author's other posts (Bug #26455)
  • +
  • [Fix] HTTP Authentication supports UTF-8 usernames now (Bug #21135)
  • +
  • [Fix] Topic searches by author no longer return invalid results (Bug #11777)
  • +
  • [Fix] Delete drafts and bookmarks when deleting an user. (#27585, thanks Schumi for the fix)
  • +
  • [Fix] Set last_post_subject for new topics. (#23945)
  • +
  • [Fix] Allow moving posts to invisible forums. (#27325)
  • +
  • [Fix] Don't allow promoting unapproved group members (#16124)
  • +
  • [Fix] Correctly fetch server name if using non-standard port (#27395)
  • +
  • [Fix] Regular expression for email matching in posts will no longer die on long words.
  • +
  • [Fix] Do not display ban message if direct call to cron. (thanks Dog Cow for reporting)
  • +
  • [Fix] Correctly display double-colon on special conditions within highlighted php source (Bug #26795)
  • +
  • [Fix] Increase storage capacity of titles/subjects due to specialchared content (Bug #25235)
  • +
  • [Fix] Catch invalid username wildcard ban (we do not support these) (Bug #29305)
  • +
  • [Fix] Fix (email)-domain checks for those having DNS prefixes set (Bug #29635)
  • +
  • [Change] Adjust truncate_string() to be able to adjust the maximum storage length.
  • +
  • [Change] Generalize load check (Bug #21255 / thanks to Xipher)
  • +
  • [Change] Make utf8_htmlspecialchars not pass its argument by reference (Bug #21885)
  • +
  • [Change] Sort the tables at the database table backup screen
  • +
  • [Change] For determining the maximum number of private messages in one box, use the biggest value from all groups the user is a member of (Bug #24665)
  • +
  • [Change] Show email ban reason on registration. Additionally allow custom errors properly returned if using validate_data(). (Bug #26885)
  • +
  • [Change] Don't allow redirects to different domains. (thanks nookieman)
  • +
  • [Feature] Added optional referer validation of POST requests as additional CSRF protection.
  • +
  • [Feature] Added optional stricter upload validation to avoid mime sniffing in addition to the safeguards provided by file.php. (thanks to Nicolas Grekas for compiling the list).
  • +
  • [Feature] Streamlined banning via the MCP by adding a ban link to the user profile. Also pre-fills ban fields as far as possible.
  • +
  • [Feature] Added ACP logout to reset an admin session.
  • +
  • [Sec] Only allow urls gone through redirect() being used within login_box(). (thanks nookieman)
  • +
+ +

Changes since 3.0.0

+ +
    +
  • [Change] Validate birthdays (Bug #15004)
  • +
  • [Fix] Allow correct avatar caching for CGI installations. (thanks wildbill)
  • +
  • [Fix] Fix disabling of word censor, now possible again
  • +
  • [Fix] Allow single quotes in db password to be stored within config.php in installer
  • +
  • [Fix] Correctly quote db password for re-display in installer (Bug #16695 / thanks to m313 for reporting too - #s17235)
  • +
  • [Fix] Correctly handle empty imageset entries (Bug #16865)
  • +
  • [Fix] Correctly check empty subjects/messages (Bug #17915)
  • +
  • [Change] Do not check usernames against word censor list. Disallowed usernames is already checked and word censor belong to posts. (Bug #17745)
  • +
  • [Fix] Additionally include non-postable forums for moderators forums shown within the teams list. (Bug #17265)
  • +
  • [Change] Sped up viewforum considerably (also goes towards mcp_forum)
  • +
  • [Fix] Do not split topic list for topics being promoted to announcements after been moved to another forum (Bug #18635)
  • +
  • [Fix] Allow editing usernames within database_update on username cleanup (Bug #18415)
  • +
  • [Fix] Fixing wrong sync() calls if moving all posts by a member in ACP (Bug #18385)
  • +
  • [Fix] Check entered imagemagick path for trailing slash (Bug #18205)
  • +
  • [Fix] Use proper title on index for new/unread posts (Bug #13101) - patch provided by Pyramide
  • +
  • [Fix] Allow calls to $user->set_cookie() define no cookie time for setting session cookies (Bug #18025)
  • +
  • [Fix] Stricter checks on smilie packs (Bug #19675)
  • +
  • [Fix] Gracefully return from cancelling pm drafts (Bug #19675)
  • +
  • [Fix] Possible login problems with IE7 if browser check is activated (Bug #20135)
  • +
  • [Fix] Fix possible database transaction errors if code returns on error and rollback happened (Bug #17025)
  • +
  • [Change] Allow numbers in permission names for modifications, as well as uppercase letters for the request_ part (Bug #20125)
  • +
  • [Fix] Use HTTP_HOST in favor of SERVER_NAME for determining server url for redirection and installation (Bug #19955)
  • +
  • [Fix] Removing s_watching_img from watch_topic_forum() function (Bug #20445)
  • +
  • [Fix] Changing order for post review if more than one post affected (Bug #15249)
  • +
  • [Fix] Language typos/fixes (Bug #20425, #15719, #15429, #14669, #13479, #20795, #21095, #21405, #21715, #21725, #21755, #21865, #15689)
  • +
  • [Fix] Style/Template fixes (Bug #20065, #19405, #19205, #15028, #14934, #14821, #14752, #14497, #13707, #14738, #19725)
  • +
  • [Fix] Tiny code fixes (Bug #20165, #20025, #19795, #14804)
  • +
  • [Fix] Prepend phpbb_root_path to ranks path for displaying ranks (Bug #19075)
  • +
  • [Fix] Allow forum notifications if topic notifications are disabled but forum notifications enabled (Bug #14765)
  • +
  • [Fix] Fixing realpath issues for provider returning the passed value instead of disabling it. This fixes issues with confirm boxes for those hosted on Network Solutions for example. (Bug #20435)
  • +
  • [Fix] Try to sort last active date on memberlist correctly at least on current page (Bug #18665)
  • +
  • [Fix] Handle generation of form tokens when maximum time is set to -1
  • +
  • [Fix] Correctly delete unapproved posts without deleting the topic (Bug #15120)
  • +
  • [Fix] Respect signature permissions in posting (Bug #16029)
  • +
  • [Fix] Users allowed to resign only from open and freely open groups (Bug #19355)
  • +
  • [Fix] Assign a last viewed date to converted topics (Bug #16565)
  • +
  • [Fix] Many minor and/or cosmetic fixes (Including, but not limited to: #21315, #18575, #18435, #21215)
  • +
  • [Feature] New option to hide the entire list of subforums on listforums
  • +
  • [Fix] Custom BBCode {EMAIL}-Token usage (Bug #21155)
  • +
  • [Fix] Do not rely on parameter returned by unlink() for verifying cache directory write permission (Bug #19565)
  • +
  • [Change] Use correct string for filesize (MiB instead of MB for example)
  • +
  • [Change] Remove left join for query used to retrieve already assigned users and groups within permission panel (Bug #20235)
  • +
  • [Fix] Correctly return sole whitespaces if used with BBCodes (Bug #19535)
  • +
  • [Fix] Quote bbcode parsing adding too much closing tags on special conditions (Bug #20735)
  • +
  • [Change] Added sanity checks to various ACP settings
  • +
  • [Change] Removed minimum form times
  • +
  • [Fix] Check topics_per_page value in acp_forums (Bug #15539)
  • +
  • [Fix] Custom profile fields with date type should be timezone independend (Bug #15003)
  • +
  • [Fix] Fixing some XHTML errors/warnings within the ACP (Bug #22875)
  • +
  • [Fix] Warnings if poll title/options exceed maximum characters per post (Bug #22865)
  • +
  • [Fix] Do not allow selecting non-authorized groups within memberlist by adjusting URL (Bug #22805 - patch provided by ToonArmy)
  • +
  • [Fix] Correctly specify "close report action" (Bug #22685)
  • +
  • [Fix] Display "empty password error" within the login box instead of issuing a general error (Bug #22525)
  • +
  • [Fix] Clean up who is online code in page_header (Bug #22715, thanks HighwayofLife)
  • +
  • [Fix] Pertain select single link on memberlist (Bug #23235 - patch provided by Schumi)
  • +
  • [Fix] Allow & and | in local part of email addresses (Bug #22995)
  • +
  • [Fix] Do not error out if php_uname function disabled / Authenticating on SMTP Server (Bug #22235 - patch by HoL)
  • +
  • [Fix] Correctly obtain to be ignored users within topic/forum notification (Bug #21795 - patch provided by dr.death)
  • +
  • [Fix] Correctly update board statistics for attaching orphaned files to existing posts (Bug #20185)
  • +
  • [Fix] Do not detect the board URL as a link twice in posts (Bug #19215)
  • +
  • [Fix] Set correct error reporting in style.php to avoid blank pages after CSS changes (Bug #23885)
  • +
  • [Fix] If pruning users based on last activity, do not include users never logged in before (Bug #18105)
  • +
  • [Sec] Only allow searching by email address in memberlist for users having the a_user permission (reported by evil<3)
  • +
  • [Sec] Limit private message attachments to be viewable only by the recipient(s)/sender (Report #s23535) - reported by AlleyKat
  • +
  • [Sec] Check for non-empty config.php within style.php (Report #s24575) - reported by bantu
  • +
  • [Fix] Find and display colliding usernames correctly when converting from one database to another (Bug #23925)
  • +
+ +

Changes since 3.0.RC8

+ +
    +
  • [Fix] Cleaned usernames contain only single spaces, so "a_name" and "a__name" are treated as the same name (Bug #15634)
  • +
  • [Fix] Check "able to disable word censor" option while applying word censor on text (Bug #15974)
  • +
  • [Fix] Rollback changes on failed transaction if returning on sql error is set
  • +
  • [Fix] Call garbage_collection() within database updater to correctly close connections (affects Oracle for example)
  • +
+ +

Changes since 3.0.RC7

+ +
    +
  • [Fix] Fixed MSSQL related bug in the update system
  • +
  • [Fix] Display "Return to" links on unwritable forums (Bug #14824)
  • +
  • [Fix] Mitigating different realpath() handling between PHP versions (fixing confirm box redirects)
  • +
  • [Fix] Fix signature editing - ability to remove signature (Bug #14820)
  • +
  • [Fix] Send correct activation key by forcing reactivation for inactive user (Bug #14819)
  • +
  • [Fix] Adding correct IP for private messages sent by issuing warnings (Bug #14781)
  • +
  • [Fix] Open private message notification (Bug #14773)
  • +
  • [Fix] Fixing false new private message indicator (Bug #14627)
  • +
  • [Fix] Let newly activated passwords work if users were converted (Bug #14787)
  • +
  • [Fix] Quote bbcode fixes. Letting parse quote="[" and re-allowing whitelisted bbcodes within username portion (Bug #14770)
  • +
  • [Fix] Allow alternative text for styled buttons if images turned off, but CSS staying on
  • +
  • [Sec] Fix bbcode helpline display for custom bbcodes - this requires style changes for any custom style (Bug #14850)
  • +
  • [Fix] Correctly count announcements when filtering forums by date (Bug #14877)
  • +
  • [Fix] Allow charset names containing underscores or spaces
  • +
  • [Fix] Don't allow previous/next links for non-existing topics (Bug #15039)
  • +
  • [Change] Do not assign converted votes to the first option in a vote.
  • +
  • [Fix] Use correct RFC 2822 date format in emails (Bug #15042)
  • +
  • [Fix] Require founder status for some actions on founder-only groups (Bug #15119)
  • +
  • [Fix] Allow changing the "now" option of date CPFs (Bug #15111)
  • +
  • [Change] Some improvements to the caching of avatars
  • +
  • [Change] Set template recompilation to be disabled by default. All mod and style authors and all those who want to modify their styles should enabled it after installation.
  • +
  • [Change] Disable debug mode. All mod and style authors should enable DEBUG and DEBUG_EXTRA.
  • +
  • [Fix] Check error reporting level for all error level. This fixes a problem for hosts having manipulated the error handler. (Bug #14831)
  • +
  • [Feature] Constant PHPBB_DB_NEW_LINK introduced which can be used to force phpBB to create a new database connection instead of reusing an existing one if the dbms supports it (Bug #14927)
  • +
  • [Fix] Automatic URL parsing no longer allows dots in the schema but can parse URLs starting after a dot (Bug #15110)
  • +
  • [Fix] Dynamic width for birthday select boxes (Bug #15149)
  • +
  • [Fix] Recache Moderators when copying permissions. (Bug #15384)
  • +
  • [Fix] Propagate sort options in mcp_forums (Bug #15464)
  • +
  • [Change] Do not allow [size=0] bbcodes (font-size of 0)
  • +
  • [Fix] No duplication of active topics (Bug #15474)
  • +
+ +

Changes since 3.0.RC6

+ +
    +
  • [Fix] Submitting language changes using acp_language (Bug #14736)
  • +
  • [Fix] Fixed wrong bbcode handling for forum rules, forum descriptions and group descriptions
  • +
  • [Fix] Fixed faulty form tokens (Bug #14725, #14762 and #14755)
  • +
  • [Fix] Fixed bbcode uid generation in the phpBB2 converter (Bug #14722)
  • +
  • [Fix] Able to request new password (Bug #14743)
  • +
+ +

Changes since 3.0.RC5

+ +
    +
  • [Feature] Removing constant PHPBB_EMBEDDED in favor of using an exit_handler(); the constant was meant to achive this more or less.
  • +
  • [Feature] Constant PHPBB_ADMIN_PATH introduced, having the same purpose as PHPBB_ROOT_PATH, but for the ACP.
  • +
  • [Fix] Further fixing user profile view (please do not forget to update/refresh your template and style) (Bug #14230)
  • +
  • [Fix] Adjust google adsense bot information (Bug #14296)
  • +
  • [Fix] Fix horizontal scrollbar problem in IE6 (Bug #14228) - fix provided by Danny-dev
  • +
  • [Fix] Use correct size values in ACP user signature screen (Bug #13367)
  • +
  • [Fix] Attachment Place inline won't work with single quotes (Bug #14291)
  • +
  • [Fix] Unable to save email templates through ACP language page (Bug #14266)
  • +
  • [Fix] Correctly set user style for guest user (able to be changed within user management)
  • +
  • [Change] Moved note about dns_get_record function for using GTalk (Jabber) from Jabber log to Jabber ACP panel
  • +
  • [Fix] Do not use register_shutdown_function within cron.php if handling the queue and the mail function being used (Bug #14321)
  • +
  • [Fix] Fixing private message on-hold code if moving messages into folder based on rules (Bug #14309)
  • +
  • [Fix] Allow the merge selection screen to work (Bug #14363)
  • +
  • [Change] Require additional permissions for copying permission when editing forums
  • +
  • [Fix] Local magic URLs no longer get an additional trailing slash (Bug #14362)
  • +
  • [Fix] Do not let the cron script stale for one hour if register_shutdown_function is not able to be called (Bug #14436)
  • +
  • [Feature] Added /includes/db/db_tools.php file, which includes tools for handling cross-db actions such as altering columns, etc.
  • +
  • [Change] Reset the start parameter when the timeframe is changed in the mcp topic page (Ticket #14438)
  • +
  • [Change] Added Code for cleaning the confirm table to the session garbage collection
  • +
  • [Fix] Fixed token handling in jabber class for extremely spec-compliant XMPP server (Bug #14445)
  • +
  • [Fix] Disallowed galleries from using special characters (Bug #14466)
  • +
  • [Change] Listing the board url within the email text instead of appending it to the subject (Bug #14378)
  • +
  • [Fix] Always display the quote button as the most accessible one (this means edit is before quote in prosilver due to the way we lay out profiles)
  • +
  • [Fix] Use correct dimension (width x height) in ACP (Bug #14452)
  • +
  • [Fix] Only display PM history links if there are PM's to be displayed (Bug #14484)
  • +
  • [Feature] Added completely new hook system to allow better application/mod integration - see docs/hook_system.html
  • +
  • [Fix] Correctly delete excess poll options (Bug #14566)
  • +
  • [Fix] Allow names evaluating to false for poll options
  • +
  • [Change] use in-build functions for user online list (Bug #14596) - provided by rxu
  • +
  • [Fix] Fixing google cache display problems with Firefox (Bug #14472) - patch provided by Raimon
  • +
  • [Fix] Prevent topic unlocking if locked by someone else while posting (Bug #10307)
  • +
  • [Change] Allow years in future be selected for date custom profile field (Bug #14519)
  • +
  • [Fix] Don't display "Avatars Disabled" message on edit groups in UCP (Bug #14636)
  • +
  • [Change] Require confirm for deleting inactive users. (Bug #14641)
  • +
  • [Fix] Match custom BBCodes in the same way during first and second pass - patch provided by IBBoard (Bug #14268)
  • +
  • [Fix] Correct quote parsing if opening bracket before opening quote (Bug #14667)
  • +
  • [Fix] Clean post message for checking length to prevent posting empty messages
  • +
  • [Fix] Display jumpbox if needed for functionality (Bug #14702)
  • +
  • [Feature] Added an option to enforce that users spend a configurable amount of time on the terms page during registration
  • +
  • [Fix] Fixed copy permissions box in the ACP
  • +
  • [Fix] Enforce types for the user table during conversions
  • +
  • [Sec] Fixing possible XSS through compromised WHOIS server (#i63, #i64)
  • +
  • [Sec] Missing access control on whois in viewonline.php (#i51)
  • +
  • [Sec] Encoding some variables within user::page array correctly (to cope with browser not doing it correctly) to prevent XSS through functions re-using them (#i61)
  • +
  • [Sec] Fixed XSS through memberlist search feature (#i62)
  • +
  • [Sec] Fixed XSS through colour swatch (#i65)
  • +
  • [Sec] Fixed insecure attachment deletion (#i53)
  • +
  • [Sec] Only allow whitelisted protocols in meta_redirect/redirect (#i66)
  • +
  • [Sec] Check file names to be written in language management panel (#i52)
  • +
  • [Sec] Deregister globals if ini_get has been disabled (#i112)
  • +
  • [Sec] Added form tokens to most forms to enforce a lighter variant of CSRF protection (#i91 - #i96)
  • +
  • [Sec] Use new password hash method for forum passwords (#i43)
  • +
  • [Sec] Changed download file location to prevent flash crossdomain policies taking effect (#i8)
  • +
  • [Sec] Do not allow autocompletion for password on admin re-authentication (#i41)
  • +
  • [Sec] Made sure users are not completely locked out if they have a GLOBALS cookie (#i101)
  • +
  • [Sec] Use the secure hash to generate BBCODE_UIDs (#i71)
  • +
  • [Sec] Increase the length of BBCODE_UIDs (#i72)
  • +
  • [Sec] New password hashing mechanism for storing passwords (#i42)
  • +
+ +

Changes since 3.0.RC4

+ +
    +
  • [Fix] MySQL, PostgreSQL and SQLite related database fixes (Bug #13862)
  • +
  • [Fix] Allow MS SQL to properly connect when using the mssql driver and PHP is less than either 4.4.1 or 5.1 (Bug #13874)
  • +
  • [Fix] Ignore files containing HTML special chars in the filenames as gallery avatars (Bug #13906)
  • +
  • [Fix] Multiple PM recipients not separated (Bug #13876)
  • +
  • [Change] Split the select list for the smilie order to clarify which are feasible and which are not (Bug #13911)
  • +
  • [Fix] Convert empty homepage fields (Bug #13917)
  • +
  • [Fix] Use board default DST setting on creating new profiles (Bug #11563)
  • +
  • [Feature] New constant PHPBB_EMBEDDED can be used to let phpBB not call exit; if wrapped/embedded (We may re-check this constant on other code locations later too)
  • +
  • [Feature] append_sid() having a check for the function append_sid_phpbb_hook(). This function is called in favour of append_sid() with the exact same parameters if present.
  • +
  • [Fix] Only list enabled modes within the dropdown at user administration (Bug #13883) - patch provided by damnian
  • +
  • [Fix] Properly display ban reason if selecting banned entries within the ACP (Bug #13896)
  • +
  • [Fix] Properly parse SQL expressions for Oracle (Bug #13916)
  • +
  • [Fix] Added label bindings to the custom profile fields in the ACP (Bug #13936) - patch provided by damnian
  • +
  • [Change] Made group avatar/rank changes more intuitive
  • +
  • [Fix] Give more feedback in icon/smilie management (Bug #13295)
  • +
  • [Fix] Correctly set user::lang_id (Bug #14010)
  • +
  • [Fix] Properly display the smiley export screen (Bug #13968)
  • +
  • [Feature] Add "DECIMAL:", "PDECIMAL", and "PDECIMAL:" to the schema creation code (Bug #13999) - patch provided by poyntesm
  • +
  • [Fix] Don't show the notify checkbox in the approval queue if the only posts are written by ANONYMOUS (Bug #13973)
  • +
  • [Fix] Redirect to bots management page on edit/add (Bug #14073)
  • +
  • [Fix] Display locked icon in viewforum/prosilver if forum locked (Bug #14009)
  • +
  • [Feature] Display message history in compose PM screen
  • +
  • [Change] Do not force login on visiting topic/forum from notification emails (Bug #13818)
  • +
  • [Fix] Fixed cron_lock value for cron execution. This bug led to users having problems with the email queue and other cron related issues.
  • +
  • [Fix] Prevent white pages on php notices with gzip compression enabled (Bug #14096)
  • +
  • [Fix] Propagate the cleaned identifier for CFPs (Bug #14072)
  • +
  • [Fix] Do not display NO_TOPICS message if viewing non-postable category (Bug #13489)
  • +
  • [Fix] Let the theme immediately expire if changed from ACP for at least 30 minutes after change
  • +
  • [Fix] Do not append hilit= in search if highlighting term is empty (Bug #13910)
  • +
  • [Fix] Return to last page after voting in viewtopic instead of first page in topic (Bug #13976)
  • +
  • [Fix] If sending PM's to groups only include activated member (Bug #14040)
  • +
  • [Fix] Correctly wrap words in emails containing utf8 characters (Bug #14109)
  • +
  • [Change] For new posts or editing the first post topic titles have a maxlength of 60 characters. For any subsequent posts the length is extended to 64 to make room for the Re: part, but cutting at 60 characters. The maxlength need to be 64, else users using opera are unable to post (opera does not allow pre-filling a field with more characters than specified within the maxlength attribute)
  • +
  • [Fix] Disable gzip compression for cached stylesheet for Internet Explorer 6 or empty browser (IE6 is not able to properly display the compressed stylesheet) (Bug #14054)
  • +
  • [Fix] Header icons fixed in FF for RTL languages (Bug #14084)
  • +
  • [Change] Words in topic titles and post subjects are highlighted on the search results page and viewtopic too now (Bug #13383)
  • +
  • [Fix] Made sure strip_bbcode cannot get the idea that a smiley is a BBCode (Bug #14030)
  • +
  • [Change] Added a filter for user objects to LDAP configuration and improved explanations (Bug #12627)
  • +
  • [Fix] Display searchable subforums of invisible parents in advanced search forum selection (Bug #11395)
  • +
  • [Fix] Allow line breaks in custom BBCodes (Bug #10758)
  • +
  • [Fix] Ordered BBcode parsing functions in the same way everywhere where they are used
  • +
  • [Fix] Prevent {URL} token in custom BBCodes from make_clickable messing (Bug #14151)
  • +
  • [Sec] Added alternative tokens to custom BBCodes which are safe for CSS/Javascript and changed TEXT token to entitise opening and closing parantheses.
  • +
  • [Fix] Convert 2.0 moderator posting permissions (Bug #14105)
  • +
  • [Fix] Correctly apply PM box limit of 0 to custom folder (Bug #14154)
  • +
  • [Fix] odbc_autocommit causing existing result sets to be dropped (Bug #14182)
  • +
+ +

Changes since 3.0.RC3

+ +
    +
  • [Fix] Fixing some subsilver2 and prosilver style issues
  • +
  • [Fix] Parse error in MCP ban (Bug #13109)
  • +
  • [Fix] Correctly hide online status in the profile (Bug #13059)
  • +
  • [Feature] Let the user choose how to update modified files (merging, using new file or using old file) within automatic updater
  • +
  • [Fix] An extra \ in an Oracle SQL regex was corrected (Bug #13151)
  • +
  • [Fix] Added a missing global to get_file() (Bug #13149)
  • +
  • [Fix] Hide autologin box when autologin is disabled (Bug #13093)
  • +
  • [Fix] Account for the forum id not being part of the request uri in prosilver (Bug #13121)
  • +
  • [Fix] Properly alter PostgreSQL tables
  • +
  • [Fix] Properly cache template files that were stored in the database (Bug #12675)
  • +
  • [Fix] Do not count the deletion of an unapproved topic as a decrease in normally viewable posts (Bug #13167)
  • +
  • [Fix] Allow column_exists() to return true if the column exists but no data is in the table
  • +
  • [Fix] Allow setting the smiley order via the select. Also allow to add smileys at the top. (Bug #13199)
  • +
  • [Fix] Fix php notice on sending jabber messages (Bug #13201)
  • +
  • [Fix] Make the window showing file differences a little wider (Bug #13157)
  • +
  • [Fix] Preserve preview style on search form (Bug #13205)
  • +
  • [Fix] Place attachment filename in new line in posting editor (Bug #9726)
  • +
  • [Fix] Don't allow caching to occur in the update sequence (Bug #13207)
  • +
  • [Fix] Enforce the max password length for automatically generated password created by the password sender (Bug #13181)
  • +
  • [Fix] Handle phpinfo() when expose_php is false (Bug #12777)
  • +
  • [Fix] Allow managing of forum roles without global users (Bug #13249)
  • +
  • [Change] Do not run cron script if board is disabled
  • +
  • [Fix] Correctly destroy sql cache for some query combinations (Bug #13237)
  • +
  • [Fix] Allow link forums being password protected (Bug #12967)
  • +
  • [Fix] Allow wrapping topic/post icons in posting editor (Bug #12843)
  • +
  • [Fix] Display L_RANK only once in template if rank title and image defined (Bug #13231)
  • +
  • [Fix] Make sure selected transfer method exists before calling (Bug #13265)
  • +
  • [Fix] Correctly escape language keys in language editor (Bug #13279)
  • +
  • [Fix] Correctly hide post/reply buttons if permissions are not given (related to Bug #12809)
  • +
  • [Fix] Remove orphan/wrong permission entries for non-existent forums - self-repairing permissions if conversions went "crazy"
  • +
  • [Feature] Allow "older" updates applied with the automatic updater. This allows people using it for updating, say, from 3.0.0 to 3.0.1 (with the correct package of course) and then from 3.0.1 to 3.0.2 if the latest version at this time is 3.0.2. These changes take effect beginning with RC4 or people replacing install/install_update.php manually prior doing the updates.
  • +
  • [Fix] Present correct error message if user tries to edit already read private message (Bug #13271)
  • +
  • [Fix] Also display board disabled notice for admins/mods if board got disabled due to exceeding the load limit (Bug #13267)
  • +
  • [Fix] Correctly deliver avatar if readfile function has been disabled (Bug #13309)
  • +
  • [Fix] Display php information page with the correct direction (Bug #12557)
  • +
  • [Fix] Increased the number of style objects (styles, templates, themes and imagesets) possible from 127 to 65535 for MySQL (Bug #13179)
  • +
  • [Fix] Although theoretically impossible in our code, removed the chance of trying to open a file that does not exist (Bug #13327)
  • +
  • [Fix] Although theoretically impossible in our code, changed the handling of non-existent language files (Bug #13329, #13331)
  • +
  • [Fix] Removed extra ampersand from ACP link (Bug #13315)
  • +
  • [Fix] used cleaned up version of given field identification for pre-filling a new custom profile field (Bug #13319)
  • +
  • [Fix] Correctly convert 2.0 website profile fields. (Bug #13379)
  • +
  • [Fix] Fixed the "Alphanumeric" and "Alphanumeric and spacers" username selection limitations (Bug #13391)
  • +
  • [Fix] Make sure filelist() is only returning array types (Bug #13385)
  • +
  • [Fix] Correctly mark forums read if using cookie based topic tracking (Bug #13245)
  • +
  • [Change] Put custom profile fields into top box and signature into separate box in members profile view (Bug #13357)
  • +
  • [Fix] Only show moderator log entries for forums the user is having moderation rights in (Bug #12481)
  • +
  • [Feature] Show resulting permission alone in trace window (Bug #10952) - thanks to dark/rain for the proposal
  • +
  • [Fix] Fixed bug in realpath replacement letting it actually work again
  • +
  • [Change] Try to be a bit more specific regarding global/local permission trace (Bug #11032)
  • +
  • [Fix] Fixed some strangeness in password validation due to mb_ereg()
  • +
  • [Fix] Subforums of a forum would overwrite the latest post information even if they did not contain the latest post (Bug #11931)
  • +
  • [Fix] Use global username display function on several places (Bug #11080, #11098) - patch by HoL
  • +
  • [Fix] Several viewonline fixes and feature changes. Also displaying the users browser in viewonline list to let the admin easier spot additional search bots, connected to a_user permission (Bug #11088) - patch and suggestions provided by HoL
  • +
  • [Change] u_viewprofile permission also affecting viewonline list now
  • +
  • [Fix] Do not display return to search link in prosilver if search is not allowed (Bug #11393)
  • +
  • [Fix] Use global url validation for img bbcode tag (Bug #11935)
  • +
  • [Fix] Added proper unicode support to style names (Bug #12165)
  • +
  • [Fix] Search result extract should not end in the middle of a multibyte character (Bug #11863)
  • +
  • [Fix] Missing localisation for an imageset no longer triggers a lot of "imageset refreshed" log messages (Bug #12027)
  • +
  • [Fix] Explain that themes which need parsing cannot be stored on the filesystem (Bug #11134)
  • +
  • [Fix] Normalize usernames
  • +
  • [Change] Improved utf8_clean_string with a more complete list of homographs and NFKC normalization
  • +
  • [Fix] Fixed error messages that ACP Database can give (Bug #13463)
  • +
  • [Fix] Fixed potential issues with databases that use tables names is uppercase
  • +
  • [Fix] Handle forum links/redirects within viewforum if no read permission given (to display login box or error message) (Bug #13467)
  • +
  • [Fix] Prevent changing postable forum having subforums to link forum without moving subforums out first
  • +
  • [Fix] Do not display version in admin template (Bug #13495)
  • +
  • [Fix] Allow manual specification of remote avatar dimensions if getimagesize is disabled (Bug #13531)
  • +
  • [Fix] Make viewonline use the session page's added forum parameter (Bug #13597)
  • +
  • [Fix] Correcting BBCode FAQ (Bug #11180)
  • +
  • [Fix] Make to/bcc line in view private message display consistent with other username displays in prosilver (Bug #11989)
  • +
  • [Fix] Send out activation email if admin activation is enabled and user activated through inactive users panel upon registration (Bug #12065)
  • +
  • [Change] Re-implemented All Yes/No/Never links in permission panels for easier changing all categories at once
  • +
  • [Change] Advanced permission link now "marked" if no role is assigned and custom permissions set. With this an admin can instantly see if the object is not set at all or having custom permissions, something you only saw if advanced permissions were viewed before.
  • +
  • [Fix] Change misleading custom BBCodes explanation, regarding tokens and useable template variables (Bug #12403, #5660)
  • +
  • [Feature] Ability to disable birthdays completely with new board features setting
  • +
  • [Fix] Fix disallowed username check (Bug #13511)
  • +
  • [Fix] Allow for unicode usernames to be pruned (Bug #13643)
  • +
  • [Fix] Do not copy forum permissions from self (Bug #13663)
  • +
  • [Fix] Allow for polls to work during preview (Bug #13657) - thanks to Thatbitextra
  • +
  • [Fix] Finer error conditions for sending IM messages (Bugs #13681, #13683)
  • +
  • [Fix] Add a confirmation for log deletion in the MCP (Bug #13693)
  • +
  • [Fix] Do not erase ranks and avatars when changing default groups (Bugs #13701, #13697)
  • +
  • [Fix] Limit author searches to firstpost, if selected (Bug #13579)
  • +
  • [Fix] Properly resync user post counts for users that have no posts (Bug #13581)
  • +
  • [Fix] Do not require space after , in smiley pak files (Bug #13647)
  • +
  • [Fix] Properly display the subscribe link in topic and forum display for Oracle (Bug #13583)
  • +
  • [Change] Add version number to ACP index (Bug #13703)
  • +
  • [Fix] Several fixes for custom profile fields on multi-lingual boards (Bugs #13763, #13527, #13525, #11515)
  • +
  • [Fix] Return to the mode previously selected after disaproving a post (Bug #13796)
  • +
  • [Fix] Cron now uses a locking variable to make sure it does not spawn too many webserver processes (Bug #12741)
  • +
  • [Fix] Cached stylesheet now supporting gzip compression
  • +
  • [Fix] Added link to inbox for deleted PMs (Bug #13813)
  • +
  • [Fix] Re-syncing the board stats also refreshes the newest user (Bug #13831)
  • +
  • [Feature] Ability to externally set $phpbb_root_path if wrapping phpBB3 by defining constant PHPBB_ROOT_PATH
  • +
  • [Fix] Implemented correct left/right floating within ACP in regard to RTL languages (Bug #13777)
  • +
  • [Fix] Fixing session problems when using MySQL strict mode in conjunction with very long browser agent string (Bug #13827)
  • +
  • [Fix] Disallow post/pm subjects entirely made up from non-printable chars and whitespaces (Bug #13800)
  • +
  • [Fix] Allow moving private messages from the sentbox (Bug #13791)
  • +
  • [Fix] Properly export localized imagesets
  • +
  • [Feature] Show the size of Firebird databases
  • +
  • [Fix] Show error when moving topic into a category via quickmod (Bug #11611)
  • +
  • [Fix] Allow Oracle to install on a database without specify the database name
  • + +
+ +

Changes since 3.0.RC2

+ +
    +
  • [Fix] Re-allow searching within the memberlist
  • +
  • [Fix] Force prune related values to integers during conversions
  • +
  • [Fix] Updater now detects successfully merged files having conflicts and user chose to merge with modifications (Bug #12685)
  • +
  • [Fix] Updater is no longer listing missing language entries and styles if these had been removed (Bug #12655)
  • +
  • [Fix] Correct approval of posts in global announcements (Bug #12699)
  • +
  • [Sec] Do not allow setup spiders/robots to post, even if permissions are given. We see no reason why this should be possible. (Thanks to Frank Rizzo for convincing us regarding this)
  • +
  • [Sec] Do not display the last active column within the memberlist if u_viewonline permission is not given (Bug #12797)
  • +
  • [Fix] Display custom profile field "date" based on users language (Bug #12787)
  • +
  • [Fix] Allow adding of help language files within subdirectories (Bug #12783)
  • +
  • [Fix] Correctly apply smileys on posting having # within their emotion code
  • +
  • [Fix] Correctly convert smileys having double quotes within their emotion code (Bug #12731)
  • +
  • [Fix] The converter now adds the protocol to user website profile fields missing it (Bug #12819)
  • +
  • [Fix] Correctly escape banned ip/email using wildcard for ban check (Bug #12815)
  • +
  • [Fix] Fixed some very nasty opera bugs (dropdown list bug, cpu spike bug) (Bug #12763, #11609)
  • +
  • [Fix] Font colour list having the correct height in IE (Bug #9571)
  • +
  • [Feature] Added mark/unmark all links to the bots page (Bug #12461)
  • +
  • [Fix] Introduced check on duplicate usernames during bot creation/edit (Bug #12461)
  • +
  • [Fix] Allow multibyte letters for smilie codes(Bug #12321)
  • +
  • [Fix] Correctly chmod created cache files (Bug #12859)
  • +
  • [Fix] Use our global expression for checking email syntax in memberlist (Bug #12827)
  • +
  • [Fix] Correctly retrieve/refresh templates stored in database if using subdirectories within template directory (Bug #12839)
  • +
  • [Fix] Correctly translate special group names in ucp_groups.php (Bug #12597)
  • +
  • [Fix] Search boxes not losing session id (changing method from get to post) (Bug #12643)
  • +
  • [Fix] Make sure the automatic update is also working for those having fsockopen disabled
  • +
  • [Fix] Simulate recache of theme data on automatic update finished page - recaching it if css data changed
  • +
  • [Feature] Allow dropping in custom "info_[module class]_*.php" files to language/*/mods directory for inclusion into the menu structure without the need to modify phpBB language files for menu placements
  • +
  • [Fix] Added login box to egosearch (Bug #12941)
  • +
  • [Fix] Deal with slashed quotes during quote bbcode conversion (Bug #12607)
  • +
  • [Fix] Minor language and style fixes (Bugs #12235, #12493, #11949)
  • +
  • [Feature] Added backlinks to mcp_report (Bug #12905)
  • +
  • [Fix] Only check usernames in guest posts upon edit (Bug #11349)
  • +
  • [Fix] Consider viewonline permission when viewing friends/foes (Bug #12955)
  • +
  • [Fix] Added proper unicode support to ban reasons (Bug #12947)
  • +
  • [Fix] The forum/topic sync code now recognizes other ways that the latest post in a topic can be expressed (Bug #12947) - patch provided by APTX
  • +
  • [Fix] Topic deletion via the user post deletion mechanism did not take into account statistics for forums that hold shadow topics (Bug #12981)
  • +
  • [Fix] Allow for negative and decimal numbers to be in transposed queries in the Oracle and Firebird DBAL (Bug #13033)
  • +
  • [Fix] Some jabber related bugs (Bug #12989, #11805, #11809)
  • +
  • [Fix] Added UTF-8 support for banning via the MCP (Bug #13013)
  • +
  • [Fix] Properly detect the script name in session::extract_current_page() if PHP_SELF is not defined (Bug #12705) - patch provided by ToonArmy
  • +
  • [Fix] Show role mask for global permission class under Permissions->Permission Roles (Bug #13057)
  • + +
+ +

Changes since 3.0.RC1

+ +
    +
  • [Fix] (X)HTML issues within the templates (Bug #11255, #11255)
  • +
  • [Fix] Tiny language and grammar changes
  • +
  • [Fix] Several style related fixes, mainly fixing cross-browser issues
  • +
  • [Fix] Several RTL fixes within prosilver
  • +
  • [Fix] MCP looses forum_id in some panels (Bug #11255)
  • +
  • [Fix] Moderation queue used unfriendly notification of no posts/topics (Bug #11291)
  • +
  • [Fix] Array in Oracle DBAL not always set (Bug #11475)
  • +
  • [Fix] Improper continue; in acp_styles.php (Bug #11523)
  • +
  • [Fix] Imageset editor more friendly (Bug #11511)
  • +
  • [Fix] Made Custom BBCode validation more strict (Bug #11335)
  • +
  • [Fix] Proper sync of data on topic copy (Bug #11335)
  • +
  • [Fix] Introduced ORDER BY clauses to converter queries (Bug #10697)
  • +
  • [Fix] Stopped bots from getting added to the registered users group during conversion (Bug #11283)
  • +
  • [Fix] Filled "SMILIEYS_DISABLED" template variable (Bug #11257)
  • +
  • [Fix] Properly escaped the delimiter in disallowed username comparisons (Bug #11339)
  • +
  • [Fix] Check global purge setting (Bug #11555)
  • +
  • [Fix] Improper magic url parsing applied to already parsed [url=] bbcode tag (Bug #11429)
  • +
  • [Fix] Renamed two indicies for Oracle support (Bug #11457)
  • +
  • [Fix] Added support for ISO-8859-8(-i) in the character set convertor (Bug #11265, #12039)
  • +
  • [Fix] Added support for Oracle's "easy connect naming"
  • +
  • [Fix] Let Mark/Unmark All work in Manage Drafts (Bug #11679)
  • +
  • [Fix] Display correct message if no attachments found in user administration (Bug #11629)
  • +
  • [Fix] Let the "Delete all board cookies" being displayed for guests too (only prosilver) (Bug #11603)
  • +
  • [Fix] Do not display view topic link in MCP while there is no link present (Bug #11573)
  • +
  • [Fix] MySQL now properly sorts by post_subject (Bug #11637)
  • +
  • [Fix] Introduced checks to stop negative postcounts (Bug #11561, #11421)
  • +
  • [Fix] Allow IP v4/v6 urls for remote avatars (Bug #11633)
  • +
  • [Fix] Delete avatar files automatically (Bug #11631)
  • +
  • [Fix] Automatically add selected columns to group by statements in the converter (Bug #11465)
  • +
  • [Fix] Allow posts without subjects to be clicked in the MCP (Bug #11483)
  • +
  • [Fix] Sync the forums that shadow topics reside in when the topic that they point to is deleted
  • +
  • [Fix] Do not use the gen_random_string function to create cookie names during install (Bug #11431)
  • +
  • [Fix] Send stylesheet in style.php even without a valid session id (Bug #11531)
  • +
  • [Fix] request_var should strictly return the requested number of dimensions
  • +
  • [Fix] Correct assignment of custom width to $user->img(); / Correctly display poll bars in subsilver2 (Bug #11301)
  • +
  • [Fix] Allow removing polls in prosilver
  • +
  • [Fix] Correct link to post in managing users attachments (Bug #11765)
  • +
  • [Fix] Reload confirm screen for selecting new forum for global topic type change if not postable forum is chosen (Bug #11711)
  • +
  • [Fix] Correctly show system default color for disabled options in Internet Explorer which does not support disabled option fields
  • +
  • [Fix] Update query for custom profiles in user management used wrong order for left/right delimiter (affecting mssql) (Bug #11781)
  • +
  • [Fix] Inconsistent display of more smileys link fixed (Bug #11801)
  • +
  • [Fix] Outbox messages are no always neither new nor unread post-conversion (Bug #11461)
  • +
  • [Feature] Replaced outdated jabber class with the one from the flyspray project
  • +
  • [Feature] The converter no longer relies on the smiley ID to decide if it should be displayed on the posting page
  • +
  • [Change] Limit maximum number of allowed characters in messages to 60.000 by default. Admins should increase their PHP time limits if they want to raise this tremedously.
  • +
  • [Change] Some changes to the conversion documentation
  • +
  • [Fix] Only use permissions from existing forums during the conversion (Bug #11417)
  • +
  • [Fix] Do not permit the decimal as a valid prefix character (Bug #11967)
  • +
  • [Fix] Account for the fact that the IM fields might hold non-IM information
  • +
  • [Fix] Make the queue function on post details
  • +
  • [Fix] Check if there are active styles left before deleting a style
  • +
  • [Fix] Correctly update styles after the deletion of an imageset.
  • +
  • [Fix] Replaced jabber validation to use the method used by the new jabber class (Bug #9822)
  • +
  • [Sec] Adding confirm boxes to UCP group actions (ToonArmy)
  • +
  • [Feature] Added the option to disable the flash bbcode globally (DelvarWorld)
  • +
  • [Sec] Changed the embedding of Flash (NeoThermic, DelvarWorld)
  • +
  • [Fix] Use the signature setting for PMs (Bug #12001)
  • +
  • [Fix] Made the DBMS selection use language variables (Bug #11969)
  • +
  • [Fix] Make sure that a folder is used when viewing messages to oneself (Bug #12105)
  • +
  • [Fix] Account for the fact that a board might have no visible Admins (Bug #12185)
  • +
  • [Fix] Change group ranks even if empty (Bug #12231)
  • +
  • [Fix] Correctly propagate variables across the custom profile field wizard (Bug #12237)
  • +
  • [Fix] Correctly move pm's into folders if more than one is received (Bug #12135)
  • +
  • [Fix] Corrected various bugs with CPF on multi-language boards (Bug #11803)
  • +
  • [Fix] Disable notify checkbox in posting editor when board email is disabled (Bug #12263)
  • +
  • [Fix] Removed maxlength from password/username fields (Bugs #12215, #11797)
  • +
  • [Fix] Use icon-unsubscribe in prosilver (Bug #12211)
  • +
  • [Fix] Seperated PREVIOUS/NEXT language vars for pagination and next/previous step (Bug #12197)
  • +
  • [Feature] append_sid() supporting anchor (Bug #11535) - patch provided by Schumi and ToonArmy
  • +
  • [Fix] Remember selected language while registering after submit (Bug #11435)
  • +
  • [Fix] UTF-8 support in theme and template editors (Bug #12251)
  • +
  • [Fix] Allow for posts per page in the MCP to change during topic selection (Bug #12067)
  • +
  • [Fix] Remove group avatars upon deletion from all profiles, not just the people having the group as default (Bug #12275, #12267)
  • +
  • [Fix] Allow for conversions to SQLite (Bug #12279) - patch provided by ToonArmy
  • +
  • [Fix] Apply colours to guests (Bug #12219)
  • +
  • [Fix] Set the Admin group as founder_manage during conversion (Bug #12287)
  • +
  • [Fix] Fixed a special quote BBCode case (Bug #12189)
  • +
  • [Fix] Correctly parse BBCodes in a post when a poll is being used (Bug #11833)
  • +
  • [Fix] Remember attached files on PM edit (Bug #12019)
  • +
  • [Fix] Correctly display poll ending on preview (Bug #12303)
  • +
  • [Feature] Display the success message on posting longer for posts awaiting approval (Bug #12053)
  • +
  • [Fix] Display maximum and entered number of characters for the "maximum number of characters exceeded" error messages (Bug #11981)
  • +
  • [Fix] Wrongly applied setting for allowing links in private messages (used the signature setting instead of the post setting) (Bug #11945)
  • +
  • [Fix] Unread flag for multipage topic wrongly set under some conditions (Bug #12127) - fix provided by asinshesq
  • +
  • [Fix] Able to delete posts within user prune panel (Bug #11849)
  • +
  • [Feature] Allow to specify dimensions of images manually on imageset config (Bug #11675)
  • +
  • [Fix] Correctly re-assign query result id after seeking rows (MSSQL/Firebird) (Bug #12369)
  • +
  • [Feature] Make effect of a changed hideonline permission instantaneous
  • +
  • [Fix] Do not overwrite larger memory values in the installer (Bug #12195)
  • +
  • [Fix] Order forums on role permission mask (Bug #12337)
  • +
  • [Fix] Show "no image" image when a non-selectable item was selected in the acp imageset editor - IE (Bug #12423)
  • +
  • [Fix] Update session information without new pageload (Bug #12393, Bug #12441)
  • +
  • [Fix] Let polls be edited correctly (Bug #12433)
  • +
  • [Fix] Overcome Oracle's inability to handle IN() clauses with over one thousand elements (Bug #12449)
  • +
  • [Fix] Simulate Firebird's affected rows mechanism for older versions of PHP
  • +
  • [Fix] Custom BBCodes properly handle lowercasing of parameterized tags (Bug #12377)
  • +
  • [Fix] Update the forum_id sequence for PostgreSQL during conversion (Bug #11927)
  • +
  • [Fix] Allow for multiple tags containing URL and LOCAL_URL tokens (Bug #12473)
  • +
  • [Fix] Properly display forum list in the MCP Queue (Bug #11313)
  • +
  • [Fix] Use the localised guest name for quotes (Bug #12483)
  • +
  • [Fix] Added post anchor to links in default warning message (Bug #12489)
  • +
  • [Fix] Allow 5 digits in editing time fields (Bug #12489)
  • +
  • [Fix] MS SQL DBAL drivers now write over extension limitations, they are too low for most installations (Bug #12415)
  • +
  • [Fix] Fix sorting by author on "unanswered posts" (Bug #12545)
  • +
  • [Fix] Allow searching for multibyte authors (Bug #11793)
  • +
  • [Fix] Writing directories/files with correct permissions using FTP for transfers on PHP4
  • +
  • [Fix] Oracle sequences during conversions are now corrected (Bug #12555)
  • +
  • [Fix] Allow users to continue after selecting "No" in the merge quickmod confirmation (Bug #12577)
  • +
  • [Fix] Correctly check permissions on the UCP subscription/bookmark pages (Bug #12595)
  • +
  • [Fix] Only convert non-orphaned PMs
  • +
  • [Fix] Fixed a few Postgres related errors (Bug #12587)
  • +
  • [Feature] New DBAL wrapper for LIKE expressions / sql_like_expression()
  • +
  • [Sec] Stricter validation of language entries.
  • + +
+ +
+ + + +
+
+ +
+ +

2. Copyright and disclaimer

+ +
+
+ +
+ +

phpBB is free software, released under the terms of the GNU General Public License, version 2 (GPL-2.0). Copyright © phpBB Limited. For full copyright and license information, please see the docs/CREDITS.txt file.

+ +
+ + + +
+
+ + + + +
+ +
+ +
+ + + diff --git a/docs/CREDITS.txt b/docs/CREDITS.txt new file mode 100644 index 0000000..90e9a31 --- /dev/null +++ b/docs/CREDITS.txt @@ -0,0 +1,109 @@ +/** +* +* phpBB © Copyright phpBB Limited 2003-2016 +* http://www.phpbb.com +* +* phpBB is free software. You can redistribute it and/or modify it +* under the terms of the GNU General Public License, version 2 (GPL-2.0) +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* A copy of the license can be viewed in the docs/LICENSE.txt file. +* The same can be viewed at +* +*/ + + +phpBB Project Manager: Marshalrusty (Yuriy Rusko) + +phpBB Lead Developer: Marc (Marc Alexander) + +phpBB Developers: bantu (Andreas Fischer) + CHItA (Máté Bartus) + Derky (Derk Ruitenbeek) + Elsensee (Oliver Schramm) + Hanakin (Michael Miday) + MichaelC (Michael Cullum) + Nicofuma (Tristan Darricau) + rubencm (Rubén Calvo) + +For a list of phpBB Team members, please see: +http://www.phpbb.com/about/team/ + +For a full list of phpBB code contributors, please see: +https://github.com/phpbb/phpbb/graphs/contributors + +-- Former Developers -- + +phpBB Project Manager: theFinn (James Atkinson) [Founder - 04/2007] + SHS` (Jonathan Stanley) + +phpBB Product Manager: naderman (Nils Adermann) [02/2016 - 02/2017] + +phpBB Lead Developer: naderman (Nils Adermann) [01/2010 - 02/2016] + Acyd Burn (Meik Sievertsen) [09/2005 - 01/2010] + psoTFX (Paul S. Owen) [2001 - 09/2005] + +phpBB Developers: A_Jelly_Doughnut (Josh Woody) [01/2010 - 11/2010] + Acyd Burn (Meik Sievertsen) [02/2003 - 09/2005] + APTX (Marek A. Ruszczyński) [12/2007 - 04/2011] + Arty (Vjacheslav Trushkin) [02/2012 - 07/2012] + Ashe (Ludovic Arnaud) [10/2002 - 11/2003, 06/2006 - 10/2006] + BartVB (Bart van Bragt) [11/2000 - 03/2006] + ckwalsh (Cullen Walsh) [01/2010 - 07/2011] + DavidMJ (David M.) [12/2005 - 08/2009] + dhn (Dominik Dröscher) [05/2007 - 01/2011] + dhruv.goel92 (Dhruv Goel) [04/2013 - 05/2016] + EXreaction (Nathan Guse) [07/2012 - 05/2014] + GrahamJE (Graham Eames) [09/2005 - 11/2006] + igorw (Igor Wiedler) [08/2010 - 02/2013] + imkingdavid (David King) [11/2012 - 06/2014] + kellanved (Henry Sudhof) [04/2007 - 03/2011] + nickvergessen (Joas Schilling)[04/2010 - 12/2015] + Oleg (Oleg Pudeyev) [01/2011 - 05/2013] + prototech (Cesar Gallegos) [01/2014 - 12/2016] + rxu (Ruslan Uzdenov) [04/2010 - 12/2012] + TerraFrost (Jim Wigginton) [04/2009 - 01/2011] + ToonArmy (Chris Smith) [06/2008 - 11/2011] + Vic D'Elfant (Vic D'Elfant) [04/2007 - 04/2009] + +Major contributions by: leviatan21 (Gabriel Vazquez) + NeoThermic (Ashley Pinner) + Raimon (Raimon Meuldijk) + Xore (Robert Hetzler) + +-- Copyrights -- + +Visual Confirmation: Xore (Robert Hetzler) + +prosilver by subBlue Design, Tom Beddard, (c) 2004 phpBB Limited + +phpBB contains code from the following applications: + +LGPL licenced: +Smarty (c) 2001, 2002 by ispi of Lincoln, Inc, http://smarty.php.net/ + +GPL licenced: +phpMyAdmin (c) 2001, 2003 phpMyAdmin Devel team, http://www.phpmyadmin.net/ +Jabber Class (c) 2006 Flyspray.org, http://www.flyspray.org/ +Chora (c) 2000-2006, The Horde Project. http://horde.org/chora/ +Horde Project (c) 2000-2006, The Horde Project. http://horde.org/ +jQuery (c) 2011, John Resig. http://jquery.com/ +Sphinx Technologies Inc (c) 2001-2012 Andrew Aksyonoff, http://sphinxsearch.com/ +Plupload (c) 2010-2013 Moxiecode Systems AB, http://www.plupload.com/ + +PHP License, version 3.0: +Pear (c) 2001-2004 PHP Group, http://pear.php.net + +Text_Diff-0.2.1 http://pear.php.net/package/Text_Diff + +MIT licenced: +Symfony2 (c) 2004-2011 Fabien Potencier, https://symfony.com/ +Cookie Consent (c) 2015 Silktide Ltd, https://cookieconsent.insites.com + +Emoji by: +Twemoji (c) 2018 Twitter, Inc, https://twemoji.twitter.com/ diff --git a/docs/FAQ.html b/docs/FAQ.html new file mode 100644 index 0000000..09ee6c1 --- /dev/null +++ b/docs/FAQ.html @@ -0,0 +1,351 @@ + + + + + + + +phpBB • FAQ + + + + + + + +
+ + + + + +
+ + + +

+ This is a very basic Frequently Asked Questions (FAQ) page which attempts to answer some of the + more commonly asked questions. It is by no means exhaustive and should be used in combination with + the 'built-in' User FAQ within phpBB3, the community forums and our IRC channel + (see README for details). +

+ +

FAQ

+ + + +
+ +

I am finding phpBB too difficult to install. Will you do it for me?

+ +
+
+ +
+ +

Simple answer, no we will not. We are not being difficult when we say this we are actually trying to help you. phpBB has a reputation for being easy to install, that reputation is we believe well deserved. It is a simple process of unarchiving a single file, uploading the resulting directory/files to their intended location and entering some data in a web based form. The sequence of events, what to type where, etc. is covered in detail in the accompanying INSTALL.html documentation. If you cannot install phpBB the chances are you will be unable to administer or update it.

+ +

There are people, companies (unrelated to your hosting provider), etc. that will install your forum, either for free or for a payment. We do not recommend you make use of these offers. Unless the service is provided by your hosting company you will have to divulge passwords and other sensitive details. If you did not know how to use an ATM would you give a passer-by your bank card and PIN and ask them to show you what to do? No, probably not! The same applies to your hosting account details!

+ +

We think a better solution is for you to carefully read the enclosed documentation, read through our knowledge base at www.phpbb.com and if necessary ask for help on any thing you get stuck on. However, the decision is yours but please note we may not offer support if we believe you have had the board installed by a third party. In such cases you should direct your questions to that company or person/s.

+ +
+ + + +
+
+ +
+ +

I am having problems with the admin at a certain board, help!
+A board has ripped off my graphics/software/etc., stop them!
+A board is dealing in warez/porn/etc., you need to prevent them doing this!
+I want to sue you because i think you host an illegal board!

+ +
+
+ +
+ +

We provide the software, we have absolutely nothing to do with any board that runs it (beyond phpbb.com of course!) and we also do not host any site. The GPL grants the user an unlimited right of use subject to their adherence of that license. Therefore we cannot prevent, dictate, control or otherwise limit the use of phpBB software. So please do not contact us for such matters.

+ +

If you have a problem with a given board please take it up with them, not us. We are not and cannot be held legally responsible for any third party use of this software (much like Microsoft et al cannot be held responsible for the use of Windows in illegal activities, etc.). Additionally we do not track the use of phpBB software in any way. So please do not ask us for details on a "given" board we will not be able to help you. If any law firms or lawyers out there send us writs, cease and desist orders, etc. for third party website use of this software we reserve the right to charge for time wasted dealing with such issues...

+ +
+ + + +
+
+ +
+ +

According to viewonline a user is doing/reading something they should not be able to!

+ +
+
+ +
+ +

No, they probably are not. phpBB uses sessions to keep track of users as they move between pages. The session information tells us who this user is. Therefore in order to determine what a user can do on a page we first need the session details. Once this data is available we can check whether the user is permitted to do whatever it is they are trying to do. This can result in it appearing as if a user is reading a topic in a forum they should not be able to access. Or perhaps viewing private messages when they are only guests, etc. In practice the user is not doing these things, they are viewing a "You are not permitted to do this" type message. The session data has simply been updated before we were able to determine what the user could or could not do.

+ +

Of course this only applies where permissions have been set correctly!

+ +
+ + + +
+
+ +
+ +

I keep getting Mail sending errors when I (or my users) post/send PM's/etc.!

+ +
+
+ +
+ +

This error will occur if phpBB cannot send mail. phpBB can send email two ways; using the PHP mail() function or directly via SMTP. Some hosting providers limit the mail() function to prevent its use in spamming, others may rename it or limit its functionality. If the mail() function got renamed, you are able to enter the correct name within the administration control panel. In either case you may need to make use of SMTP. This requires that you have access to such a facility, e.g. your hosting provider may provide one (perhaps requiring specific written authorisation), etc. Please see www.phpbb.com for additional help on this matter.

+ +

If you do require SMTP services please do not ask (on our forums or elsewhere) for someone to provide you with one. Open relays are now things of the past thanks to the unthinking spammers out there. Therefore you are unlikely to find someone willing to offer you (free) services.

+ +
+ + + +
+
+ +
+ +

My users are complaining that emails are not in their selected language!

+ +
+
+ +
+ +

You must have deleted a language pack or the language pack is incomplete. phpBB will try to send emails in the users selected language. If it cannot find a suitable email template it will switch to the boards default language.

+ + + +
+ + + +
+
+ +
+ +

My AOL based users keep getting logged out!

+ +
+
+ +
+ +

phpBB uses sessions to keep track of users as they browse the board. These sessions use a combination of a unique session id, the users IP and if specified the users browser and/or the users x-forwarded-for header to identify each user. We make use of all of this as an extra safe-guard to help prevent sessions being hijacked (by discovering the unique session id).

+ +

Unfortunately this only works when the users IP is constant as they browse the board. For most users this will be the case. However certain providers route their users via a cluster of proxys. In some cases, particularly the AOL browser, this results in different IPs being forwarded as the user moves between pages. We take account of this by not checking the entire IP by default but only the first "three quads" (A.B.C). Again in most cases this will be fine. However again AOL uses IPs which can vary so much that checking only the first two quads results in a fairly static IP being available for session validation.

+ +

If you are experiencing problems related to this you can set the Session IP validation parameter found in Admin->General->Server Configuration->Security Settings to A.B. Please note that reducing the IP validation length does potentially increase the risk of sessions being hijacked (this is something for you to consider, phpBB Limited takes no responsibility should anything happen!). We suggest to at least additionally enable the browser validation.

+ +
+ + + +
+
+ +
+ +

I am unable to upload avatars from my computer, regardless of the settings.

+ +
+
+ +
+ +

There are two possibilities here, the first is you have not created the directory you specified as the storage location for avatars, ie. as specified in the Admin -> General -> Board Configuration -> Avatar settings section. If the directory does not exist uploadeable avatars are automatically disabled. You should create the required directory (ensuring it has global write access or other appropriate permissions to allow the webserver to write files to it).

+ +

The second possibility is that your provider has disabled file_upload support. You should contact your provider and ask them if this is the case. There is not a lot you can do, there are still three other avatar settings left to choose from including uploading via an URL which will work fine.

+ +
+ + + +
+
+ +
+ +

I just cannot get gallery avatars to appear!

+ +
+
+ +
+ +

phpBB categorises gallery avatars and it does this by reading through folders contained in the location you specified as being the gallery path. For example, if you set the gallery path to images/avatars/gallery phpBB will expect to find a series of folders within that path, e.g. images/avatars/gallery/moviestars, images/avatars/gallery/cartoons, images/avatars/gallery/misc, etc. Placing images directly in images/avatars/gallery/ will result in nothing being listed in your gallery.

+ +
+ + + +
+
+ +
+ +

How do I use/set permissions?

+ +
+
+ +
+ +

Please read the paragraph about permissions in our extensive online documentation.

+ +
+ + + +
+
+ +
+ +

I (or my users) cannot stay logged in to the forum!

+ +
+
+ +
+ +

If you (or your users) are, after attempting a login, being returned to the index (or other page) without appearing to be logged in the most likely problem is incorrect cookie settings. phpBB uses cookies to store a session id and a small amount of user data. For this data to be stored correctly the cookie domain, name, path and secure settings must be correct. You can check this in Admin->General->Server Configuration->Cookie Settings. Typically the cookie domain can be left blank and the cookie path set to / (a single forward slash). Do not set the cookie as being secure unless your board is running over a secure sockets layer connection, ie. https://

+ +

If you still have problems try setting the cookie domain to your full domain name, e.g. www.mysystem.tld, www.something.mydomain.tld. You must ensure the domain name contains at least two dots or browsers will be unlikely to recognise the cookie, e.g. .mydomain.com, mydomain.com. Do not add http:// or anything else to the domain name!

+ +
+ + + +
+
+ +
+ +

My users are complaining about being logged out too quickly!

+ +
+
+ +
+ +

You can increase the default length of sessions (ie. how long before a users session is considered 'dead') in Admin->General->Server Configuration->Load Settings. Set it to whatever value your users feel comfortable with, remember that security issues may affect your decision (ie. having too long a session may allow non-users to abuse your board should a user forget to logout or otherwise leave a current session on a public workstation).

+ +
+ + + +
+
+ +
+ +

My question isn't answered here!

+ +
+
+ +
+ +

Please read our extensive user documentation first, it may just explain what you want to know.

+ +

Feel free to search our community forum for the information you require. PLEASE DO NOT post your question without having first used search, chances are someone has already asked and answered your question. You can find our board here:

+ +

www.phpbb.com

+ +
+ + + +
+
+ +
+ +

Copyright and disclaimer

+ +
+
+ +
+ +

phpBB is free software, released under the terms of the GNU General Public License, version 2 (GPL-2.0). Copyright © phpBB Limited. For full copyright and license information, please see the docs/CREDITS.txt file.

+ +
+ + + +
+
+ + + + +
+ +
+ +
+ + + diff --git a/docs/INSTALL.html b/docs/INSTALL.html new file mode 100644 index 0000000..4f7f07d --- /dev/null +++ b/docs/INSTALL.html @@ -0,0 +1,527 @@ + + + + + + + +phpBB • Install + + + + + + + +
+ + + + + +
+ + + +
+

Please read this document completely before proceeding with installation, updating or converting.

+ +

This document will walk you through the basics on installing, updating and converting the forum software.

+ +

+ A basic overview of running phpBB can be found in the accompanying README file. + Please ensure you read that document in addition to this! For more detailed information on using, installing, + updating and converting phpBB you should read the documentation + available online. +

+
+

Install

+ + + +
+ +

1. Quick install

+ +
+
+ +
+ +

If you have basic knowledge of using FTP and are sure your hosting service or server will run phpBB3 you can use these steps to quickly get started. For a more detailed explanation you should skip this and go to section 2 below.

+ +
    +
  1. Decompress the phpBB3 archive to a local directory on your system.
  2. +
  3. Upload all the files contained in this archive (retaining the directory structure) to a web accessible directory on your server or hosting account.
  4. +
  5. Change the permissions on config.php to be writable by all (666 or -rw-rw-rw- within your FTP Client)
  6. +
  7. Change the permissions on the following directories to be writable by all (777 or -rwxrwxrwx within your FTP Client):
    + store/, cache/, files/ and images/avatars/upload/.
  8. +
  9. Point your web browser to the location where you uploaded the phpBB3 files with the addition of install/app.php or simply install/, e.g. http://www.example.com/phpBB3/install/app.php, http://www.example.com/forum/install/.
  10. +
  11. Click the INSTALL tab, follow the steps and fill out all the requested information.
  12. +
  13. Change the permissions on config.php to be writable only by yourself (644 or -rw-r--r-- within your FTP Client)
  14. +
  15. phpBB3 should now be available, please MAKE SURE you read at least Section 6 below for important, security related post-installation instructions, and also take note of Section 7 regarding anti-spam measures.
  16. +
+ +

If you experienced problems or do not know how to proceed with any of the steps above please read the rest of this document.

+ +
+ + + +
+
+ +
+ +

2. Requirements

+ +
+
+ +
+ +

phpBB 3.2.x has a few requirements which must be met before you are able to install and use it.

+ +
    +
  • A webserver or web hosting account running on any major Operating System with support for PHP
  • +
  • A SQL database system, one of: +
      +
    • MySQL 3.23 or above (MySQLi supported)
    • +
    • MariaDB 5.1 or above
    • +
    • PostgreSQL 8.3+
    • +
    • SQLite 3.6.15+
    • +
    • MS SQL Server 2000 or above (via ODBC or the native adapter)
    • +
    • Oracle
    • +
    +
  • +
  • PHP 5.4.7+ but less than PHP 7.3 with support for the database you intend to use.
  • +
  • The following PHP modules are required: +
      +
    • json
    • +
    +
  • +
  • getimagesize() function must be enabled.
  • +
  • Presence of the following modules within PHP will provide access to additional features, but they are not required: +
      +
    • zlib Compression support
    • +
    • Remote FTP support
    • +
    • XML support
    • +
    • GD Support
    • +
    +
  • +
+ +

If your server or hosting account does not meet the requirements above then you will be unable to install phpBB 3.2.x.

+ +
+ + + +
+
+ +
+ +

3. New installation

+ +
+
+ +
+ +

Installation of phpBB will vary according to your server and database. If you have shell access to your account (via telnet or ssh for example) you may want to upload the entire phpBB archive (in binary mode!) to a directory on your host and unarchive it there.

+ +

If you do not have shell access or do not wish to use it, you will need to decompress the phpBB archive to a local directory on your system using your favourite compression program, e.g. winzip, rar, zip, etc. From there you must FTP ALL the files it contains (being sure to retain the directory structure and filenames) to your host. Please ensure that the cases of filenames are retained, do NOT force filenames to all lower or upper case as doing so will cause errors later.

+ +

All .php, .sql, .cfg, .css, .js, .html, .htaccess and .txt files should be uploaded in ASCII mode, while all graphics should be uploaded in BINARY mode. If you are unfamiliar with what this means please refer to your FTP client documentation. In most cases this is all handled transparantly by your ftp client, but if you encounter problems later you should be sure the files were uploaded correctly as described here.

+ +

phpBB comes supplied with British English as its standard language. However, a number of separate packs for different languages are available. If you are not a native English speaker you may wish to install one or more of these packages before continuing. The installation process below will allow you to select a default language from those available (you can, of course, change this default at a later stage). For more details on language packs, where to obtain them and how to install them please see the README.

+ +

Once all the files have been uploaded to your site, you should point your browser at this location with the addition of /install/. For example, if your domain name is www.example.com and you placed the phpBB files in the directory /phpBB3 off your web root you would enter http://www.example.com/phpBB3/install/ or (alternatively) http://www.example.com/phpBB3/install/app.php into your browser. When you have done this, you should see the phpBB Introduction screen appear.

+ +

Introduction:

+ +

The initial screen gives you a short introduction into phpBB. It allows you to read the license phpBB is released under (General Public License v2) and provides information about how you can receive support. To start the installation, use the INSTALL tab.

+ +

Requirements

+ +

The first page you will see after starting the installation is the Requirements list. phpBB automatically checks whether everything that it needs to run properly is installed on your server. You need to have at least the minimum PHP version installed, and at least one database available to continue the installation. Also important, is that all shown folders are available and have the correct permissions. Please see the description of each section to find out whether they are optional or required for phpBB to run. If everything is in order, you can continue the installation with Start Install.

+ +

Database settings

+ +

You now have to decide which database to use. See the Requirements section for information on which databases are supported. If you do not know your database settings, please contact your host and ask for them. You will not be able to continue without them. You need:

+ +
    +
  • The Database Type - the database you will be using.
  • +
  • The Database server hostname or DSN - the address of the database server.
  • +
  • The Database server port - the port of the database server (most of the time this is not needed).
  • +
  • The Database name - the name of the database on the server.
  • +
  • The Database username and Database password - the login data to access the database.
  • +
+ +
+

Note: if you are installing using SQLite, you should enter the full path to your database file in the DSN field and leave the username and password fields blank. For security reasons, you should make sure that the database file is not stored in a location accessible from the web.

+
+ +

You don't need to change the Prefix for tables in database setting, unless you plan on using multipe phpBB installations on one database. In this case, you can use a different prefix for each installation to make it work.

+ +

After you entered your details, you can continue with the Proceed to next step button. Now phpBB will check whether the data you entered will lead to a successful database connection and whether tables with the same prefix already exist.

+ +

A Could not connect to the database error means that you didn't enter the database data correctly and it is not possible for phpBB to connect. Make sure that everything you entered is in order and try again. Again, if you are unsure about your database settings, please contact your host.

+ +

If you installed another version of phpBB before on the same database with the same prefix, phpBB will inform you and you just need to enter a different database prefix.

+ +

If you see the Successful Connection message, you can continue to the next step.

+ +

Administrator details

+ +

Now you have to create your administration user. This user will have full administration access and he/she will be the first user on your forum. All fields on this page are required. You can also set the default language of your forum on this page. In a vanilla phpBB installation, we only include British English. You can download further languages from www.phpbb.com, and add them before installing or later.

+ +

Configuration file

+ +

In this step, phpBB will try to write the configuration file automatically. The forum needs the configuration file in order to operate. It contains all the database settings, so without it, phpBB will not be able to access the database.

+ +

Usually, writing the configuration file automatically works fine. If the file permissions are not set correctly, this process can fail. In this case, you need to upload the file manually. phpBB asks you to download the config.php file and tells you what to do with it. Please read the instructions carefully. After you have uploaded the file, use Done to get to the last step. If Done returns you to the same page as before, and does not return a success message, you did not upload the file correctly.

+ +

Advanced settings

+ +

The Advanced settings allow you to set additional parameters of the board configuration. They are optional and you can always change them later. So, even if you are not sure what these settings mean, you can still proceed to the final step and finish the installation.

+ +

If the installation was successful, you can now use the Login button to visit the Administration Control Panel. Congratulations, you have installed phpBB successfully. But there is still work ahead!

+ +

If you are unable to get phpBB installed even after reading this guide, please look at the support section of the installer's introduction page to find out where you can ask for further assistance.

+ +

At this point if you are converting from phpBB 2.0.x, you should refer to the conversion steps for further information. If not, you should remove the install directory from your server as you will only be able to access the Administration Control Panel whilst it is present.

+ +
+ + + +
+
+ +
+ +

4. Updating from stable releases of phpBB 3.2.x

+ +
+
+ +
+ +

If you are currently using a stable release of phpBB, updating to this version is straightforward. You would have downloaded one of four packages and your choice determines what you need to do. Note: Before updating, we heavily recommend you do a full backup of your database and existing phpBB files! If you are unsure how to achieve this please ask your hosting provider for advice.

+ +

Please make sure you update your phpBB source files too, even if you just run the database updater. If you have shell access to your server, you may wish to update via the command line interface. From your board's root, execute the following command: php bin/phpbbcli.php --safe-mode db:migrate.

+ +

4.i. Full package

+ +

Updating using the full package is the recommended update method for boards without modifications to core phpBB files.

+ +

First, you should make a copy of your existing config.php file; keep it in a safe place! Next, delete all the existing phpBB files, you should leave your files/, images/ and ext/ directories in place, otherwise you will lose your file attachments, uploaded images and get errors due to missing extension files. You can leave alternative styles in place too. With this complete, you can upload the new phpBB files (see New installation for details if necessary). Once complete, copy back your saved config.php, replacing the new one. Another method is to just replace the existing files with the files from the full package - though make sure you do not overwrite your config.php file.

+ +

You should now got to /install/app.php/update which will display a warning: No valid update directory was found, please make sure you uploaded the relevant files. Beneath that warning you will see a radio button Update database only, just click Submit. Depending on your previous version this will make a number of database changes. You may receive FAILURES during this procedure. They should not be a cause for concern unless you see an actual ERROR, in which case the script will stop (in this case you should seek help via our forums or bug tracker). If you have shell access to your server, you may wish to update via the command line interface. From your board's root, execute the following command: php bin/phpbbcli.php --safe-mode db:migrate.

+ +

Once /install/app.php/update has completed, it displays the success message: The database update was successful. You may proceed to the Administration Control Panel and then remove the install directory as advised.

+ +

4.ii. Changed files

+ +

This package is meant for those wanting to only replace the files that were changed between a previous version and the latest version.

+ +

This package contains a number of archives, each contains the files changed from a given release to the latest version. You should select the appropriate archive for your current version, e.g. if you currently have 3.2.0 you should select the appropriate phpBB-3.2.1-files.zip/tar.bz2 file.

+ +

The directory structure has been preserved, enabling you (if you wish) to simply upload the uncompressed contents of the archive to the appropriate location on your server, i.e. simply overwrite the existing files with the new versions. Do not forget that if you have installed any modifications (MODs) these files will overwrite the originals, possibly destroying them in the process. You will need to re-add MODs to any affected file before uploading.

+ +

As for the other update procedures, you should go to /install/app.php/update, select "Update database only" and submit the page after you have finished updating the files. This will update your database schema and increment the version number. If you have shell access to your server, you may wish to update via the command line interface. From your board's root, execute the following command: php bin/phpbbcli.php --safe-mode db:migrate.

+ +

4.iii. Patch file

+ +

The patch file package is for those wanting to update through the patch application, and should only be used by those who are comfortable with it.

+ +

The patch file is one solution for those with changes in to the phpBB core files and do not want to re-add them back to all the changed files. To use this you will need command line access to a standard UNIX type patch application. If you do not have access to such an application, but still want to use this update approach, we strongly recommend the Automatic update package explained below. It is also the recommended update method.

+ +

A number of patch files are provided to allow you to update from previous stable releases. Select the correct patch, e.g. if your current version is 3.2.0, you need the phpBB-3.2.1-patch.zip/tar.bz2 file. Place the correct patch in the parent directory containing the phpBB core files (i.e. index.php, viewforum.php, etc.). With this done you should run the following command: patch -cl -d [PHPBB DIRECTORY] -p1 < [PATCH NAME] (where PHPBB DIRECTORY is the directory name your phpBB Installation resides in, for example phpBB, and where PATCH NAME is the relevant filename of the selected patch file). This should complete quickly, hopefully without any HUNK FAILED comments.

+ +

If you do get failures, you should look at using the Changed Files package to replace the files which failed to patch. Please note that you will need to manually re-add any MODs to these particular files. Alternatively, if you know how, you can examine the .rej files to determine what failed where and make manual adjustments to the relevant source.

+ +

You should, of course, delete the patch file (or files) after use. As for the other update procedures, you should navigate to /install/app.php/update, select "Update database only" and submit the page after you have finished updating the files. This will update your database schema and data (if appropriate) and increment the version number. If you have shell access to your server, you may wish to update via the command line interface. From your board's root, execute the following command: php bin/phpbbcli.php --safe-mode db:migrate.

+ +

4.iv. Automatic update package

+ +

This update method is only recommended for installations with modifications to core phpBB files. This package detects changed files automatically and merges in changes if needed.

+ +

The automatic update package will update the board from a given version to the latest version. A number of automatic update files are available, and you should choose the one that corresponds to the version of the board that you are currently running. For example, if your current version is 3.2.0, you need the phpBB-3.2.0_to_3.2.1.zip/tar.bz2 file.

+ +

To perform the update, either follow the instructions from the Administration Control Panel->System Tab - this should point out that you are running an outdated version and will guide you through the update - or follow the instructions listed below.

+ +
    +
  • Go to the downloads page and download the latest update package listed there, matching your current version.
  • +
  • Upload the uncompressed archive contents to your phpBB installation - only the install/ and vendor/ folders are required. Upload these folders in their entirety, retaining the file structure.
  • +
  • After the install folder is present, phpBB will go offline automatically.
  • +
  • Point your browser to the install directory, for example http://www.example.com/phpBB3/install/
  • +
  • Choose the "Update" Tab and follow the instructions
  • +
+ +

 

+ +

4.v. All package types

+ +

If you have non-English language packs installed, you may want to see if a new version has been made available. A number of missing strings may have been added which, though not essential, may be beneficial to users. Please note that at this time not all language packs have been updated so you should be prepared to periodically check for updates.

+ +

These update methods will only update the standard style prosilver, any other styles you have installed for your board will usually also need to be updated.

+ +
+ + + +
+
+ +
+ +

5. Updating from phpBB 3.0.x/3.1x to phpBB 3.2.x

+ +
+
+ +
+ +

Updating from phpBB 3.0.x or 3.1.x to 3.2.x is just the same as updating from stable releases of phpBB 3.2.x

+ +

However you can also start with a new set of phpBB 3.2.x files.

+ +
    +
  1. Delete all files EXCEPT for the following: + +
      +
    • The config.php file
    • +
    • The images/ directory
    • +
    • The files/ directory
    • +
    • The store/ directory
    • +
    • (The ext/ directory
    • +
  2. + +
  3. Upload the contents of the 3.2.x Full Package into your forum's directory. Make sure the root level .htaccess file is included in the upload.
  4. +
  5. Browse to /install/app.php/update
  6. +
  7. Read the notice Update database only and press Submit
  8. +
  9. Delete the install/ directory
  10. +
+
+ + + +
+
+ +
+ +

6. Conversion from phpBB 2.0.x to phpBB 3.2.x

+ +
+
+ +
+ +

This paragraph explains the steps necessary to convert your existing phpBB2 installation to phpBB3.

+ +

5.i. Requirements before converting

+ +

Before converting, we heavily recommend you do a full backup of your database and files! If you are unsure how to achieve this, please ask your hosting provider for advice. You basically need to follow the instructions given for New installations. Please do not overwrite any old files - install phpBB3 at a different location.

+ +

Once you made a backup of everything and also have a brand new phpBB3 installation, you can now begin the conversion.

+ +

Note that the conversion requires CREATE and DROP privileges for the phpBB3 database user account.

+ +

5.ii. Converting

+ +

To begin the conversion, visit the install/ folder of your phpBB3 installation (the same as you have done for installing). Now you will see a new tab Convert. Click this tab.

+ +

As with install, the conversion is automated. Your previous 2.0.x database tables will not be changed and the original 2.0.x files will remain unaltered. The conversion is actually only filling your phpBB3 database tables and copying additional data over to your phpBB3 installation. This has the benefit that if something goes wrong, you are able to either re-run the conversion or continue a conversion, while your old board is still accessible. We really recommend that you disable your old installation while converting, else you may have inconsistent data after the conversion.

+ +

Please note that this conversion process may take quite some time and depending on your hosting provider this may result in it failing (due to web server resource limits or other timeout issues). If this is the case, you should ask your provider if they are willing to allow the convert script to temporarily exceed their limits (be nice and they will probably be quite helpful). If your host is unwilling to increase the limits to run the convertor, please see this article for performing the conversion on your local machine: Knowledge Base - Offline Conversions

+ +

Once completed, your board should be immediately available. If you encountered errors, you should report the problems to our bug tracker or seek help via our forums (see README for details).

+ +

5.iii. Things to do after conversion

+ +

After a successful conversion, there may be a few items you need to do - apart from checking if the installation is accessible and everything displayed correctly.

+ +

The first thing you may want to do is to go to the administration control panel and check every configuration item within the general tab. Thereafter, you may want to adjust the forum descriptions/names if you entered HTML there. You also may want to access the other administrative sections, e.g. adjusting permissions, smilies, icons, ranks, etc.

+ +

During the conversion, the search index is not created or transferred. This means after conversion you are not able to find any matches if you want to search for something. We recommend you rebuild your search index within Administration Control Panel -> Maintenance -> Database -> Search Index.

+ +

After verifying the settings in the ACP, you can delete the install directory to enable the board. The board will stay disabled until you do so.

+ +

Once you are pleased with your new installation, you may want to give it the name of your old installation, changing the directory name. With phpBB3 this is possible without any problems, but you may still want to check your cookie settings within the administration panel; in case your cookie path needs to be adjusted prior to renaming.

+ +

5.iv. Common conversion problems

+ +

Broken non-latin characters The conversion script assumes that the database encoding in the source phpBB2 matches the encoding defined in the lang_main.php file of the default language pack of the source installation. Edit that file to match the database's encoding and re-start the conversion procedure.

+ +

http 500 / white pages The conversion is a load-heavy procedure. Restrictions imposed by some server hosting providers can cause problems. The most common causes are: values too low for the PHP settings memory_limit and max_execution_time. Limits on the allowed CPU time are also a frequent cause for such errors, as are limits on the number of database queries allowed. If you cannot change such settings, then contact your hosting provider or run the conversion procedure on a different computer. The phpBB.com forums are also an excellent location to ask for support.

+ +

Password conversion Due to the utf-8 based handling of passwords in phpBB3, it is not always possible to transfer all passwords. For passwords "lost in translation" the easiest workaround is to use the I forgot my password link on the login page.

+ +

Path to your former board The convertor expects the relative path to your old board's files. So, for instance, if the old board is located at http://www.yourdomain.com/forum and the phpBB3 installation is located at http://www.yourdomain.com/phpBB3, then the correct value would be ../forum. Note that the webserver user must be able to access the source installation's files.

+ +

Missing images If your default board language's language pack does not include all images, then some images might be missing in your installation. Always use a complete language pack as default language.

+ +

Smilies During the conversion you might see warnings about image files where the copying failed. This can happen if the old board's smilies have the same file names as those on the new board. Copy those files manually after the conversion, if you want to continue using the old smilies.

+ + +
+ + + +
+
+ +
+ +

7. Important (security related) post-Install tasks for all installation methods

+ +
+
+ +
+ +

Once you have successfully installed phpBB you MUST ensure you remove the entire install/ directory. Leaving the install directory in place is a very serious potential security issue which may lead to deletion or alteration of files, etc. Please note that until this directory is removed, phpBB will not operate and a warning message will be displayed. Beyond this essential deletion, you may also wish to delete the docs/ directory if you wish.

+ +

With these directories deleted, you should proceed to the administration panel. Depending on how the installation completed, you may have been directed there automatically. If not, login as the administrator you specified during install/conversion and click the Administration Control Panel link at the bottom of any page. Ensure that details specified on the General tab are correct!

+ +

6.i. Uploadable avatars

+ +

phpBB supports several methods for allowing users to select their own avatar (an avatar is a small image generally unique to a user and displayed just below their username in posts).

+ +

Two of these options allow users to upload an avatar from their machine or a remote location (via a URL). If you wish to enable this function you should first ensure the correct path for uploadable avatars is set in Administration Control Panel -> General -> Board Configuration -> Avatar settings. By default this is images/avatars/uploads, but you can set it to whatever you like, just ensure the configuration setting is updated. You must also ensure this directory can be written to by the webserver. Usually this means you have to alter its permissions to allow anyone to read and write to it. Exactly how you should do this depends on your FTP client or server operating system.

+ +

On UNIX systems, for example, you set the directory to a+rwx (or ugo+rwx or even 777). This can be done from a command line on your server using chmod or via your FTP client (using the Change Permissions, chmod or other Permissions dialog box, see your FTP client's documentation for help). Most FTP clients list permissions in the form of User (Read, Write, Execute), Group (Read, Write, Execute) and Other (Read, Write, Execute). You need to tick all of these boxes to set correct permissions.

+ +

On Windows systems, you need to ensure the directory is not write-protected and that it has global write permissions (see your server's documentation or contact your hosting provider if you are unsure on how to achieve this).

+ +

Please be aware that setting a directory's permissions to global write access is a potential security issue. While it is unlikely that anything nasty will occur (such as all the avatars being deleted) there are always people out there to cause trouble. Therefore you should monitor this directory and if possible make regular backups.

+ +

6.ii. Webserver configuration

+ +

Depending on your web server, you may have to configure your server to deny web access to the cache/, files/, includes, phpbb, store/, and vendor directories. This is to prevent users from accessing sensitive files.

+ +

+ For Apache there are .htaccess files already in place to do this for the most sensitive files and folders. We do however recommend to completely deny all access to the aforementioned folders and their respective subfolders in your Apache configuration.
+ On Apache 2.4, denying access to the phpbb folder in a phpBB instance located at /var/www/html/ would be accomplished by adding the following access rules to the Apache configuration file (typically apache.conf): +

+<Directory /var/www/html/phpbb/*>
+	Require all denied
+</Directory>
+<Directory /var/www/html/phpbb>
+	Require all denied
+</Directory>
+
+

The same settings can be applied to the other mentioned directories by replacing phpbb by the respective directory name. Please note that there are differences in syntax between Apache version 2.2 and 2.4.

+

For Windows based servers using IIS there are web.config files already in place to do this for you. For other webservers, you will have to adjust the configuration yourself. Sample files for nginx and lighttpd to help you get started may be found in the docs/ directory.

+ +
+ + + +
+
+ +
+ +

8. Anti-Spam Measures

+ +
+
+ +
+

Like any online site that allows user input, your board could be subject to unwanted posts; often referred to as forum spam. The vast majority of these attacks will be from automated computer programs known as spambots. The attacks, generally, are not personal as the spammers are just trying to find accessible targets. phpBB has a number of anti-spam measures built in, including a range of CAPTCHAs. However, administrators are strongly urged to read and follow the advice for Preventing Spam in phpBB as soon as possible after completing the installation of your board.

+
+ + + +
+
+ +
+ +

9. Copyright and disclaimer

+ +
+
+ +
+ +

phpBB is free software, released under the terms of the GNU General Public License, version 2 (GPL-2.0). Copyright © phpBB Limited. For full copyright and license information, please see the docs/CREDITS.txt file.

+ +
+ + + +
+
+ + + + +
+ +
+ +
+ + + diff --git a/docs/LICENSE.txt b/docs/LICENSE.txt new file mode 100644 index 0000000..ce992b2 --- /dev/null +++ b/docs/LICENSE.txt @@ -0,0 +1,281 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + diff --git a/docs/README.html b/docs/README.html new file mode 100644 index 0000000..8fb9036 --- /dev/null +++ b/docs/README.html @@ -0,0 +1,373 @@ + + + + + + + +phpBB • Readme + + + + + + + +
+ + + + + +
+ + + +

+ Thank you for downloading phpBB. This README will guide you through the basics of installation + and operation of phpBB. Please ensure you read this and the accompanying documentation fully + before proceeding with the installation. +

+ +

Readme

+ + + +
+ +

1. Installing phpBB

+ +
+
+ +
+

Installation, update and conversion instructions can be found in the INSTALL document in this directory. If you are intending on converting from a phpBB 2.0.x or 3.0.x installation we highly recommend that you backup any existing data before proceeding!

+ +

Users of phpBB 3.0, 3.1 and 3.2 Beta versions cannot directly update.

+ +

Please note that we don't support the following installation types:

+
    +
  • Updates from phpBB Beta versions and lower to phpBB Release Candidates and higher
  • +
  • Conversions from phpBB 2.0.x to phpBB 3.0 Beta, 3.1 Beta and 3.2 Beta versions
  • +
  • phpBB 3.0 Beta, 3.1 Beta or 3.2 beta installations
  • +
+ +

We give support for the following installation types:

+ +
    +
  • Updates from phpBB 3.0 RC1, 3.1 RC1 and 3.2 RC1 to the latest version
  • +
  • Note: if using the Automatic Update Package, updates are supported from phpBB 3.0.2 onward. To update a pre-3.0.2 installation, first update to 3.0.2 and then update to the current version.
  • +
  • Conversions from phpBB 2.0.x to the latest version
  • +
  • New installations of phpBB 3.1.x - only the latest released version
  • +
  • New installations of phpBB 3.2.x - only the latest released version
  • +
+ +
+ + + +
+
+ +
+ +

2. Running phpBB

+ +
+
+ +
+ +

Once installed, phpBB is easily managed via the Administration and Moderator Control Panels. If you need help or advice with phpBB, please see Section 3 below.

+ +

2.i. Languages (Internationalisation - i18n)

+ +

A number of language packs with included style localisations are available. You can find them listed in the Language Packs pages of our downloads section or from the Language Packs section of the Customisation Database.

+ +

For more information about language packs, please see: https://www.phpbb.com/languages/

+ +

This is the official location for all supported language sets. If you download a package from a 3rd party site you do so with the understanding that we cannot offer support. Please do not ask for support if you download a language pack from a 3rd party site.

+ +

Installation of these packages is straightforward: simply download the required language pack, uncompress (unzip) it and via FTP transfer the included language and styles folders to the root of your board installation. The language can then be installed via the Administration Control Panel of your board: Customise tab -> Language management -> Language packs. A more detailed description of the process is in the Knowledge Base article, How to Install a Language Pack.

+ +

If your language is not available, please visit our [3.2.x] Translations forum where you will find topics on translations in progress. Should you wish to volunteer to translate a language not currently available or assist in maintaining an existing language pack, you can Apply to become a translator.

+ +

2.ii. Styles

+ +

Although we are rather proud of the included styles, we realise that they may not be to everyone's taste. Therefore, phpBB allows styles to be switched with relative ease. First, you need to locate and download a style you like. You can find them listed in the Styles section of our Customisation Database.

+ +

For more information about styles, please see: https://www.phpbb.com/styles/

+ +

Please note that 3rd party styles downloaded for versions of phpBB2 will not work in phpBB3. It is also important to ensure that the style is updated to match the current version of the phpBB software you are using.

+ +

Once you have downloaded a style, the usual next step is to unarchive (or upload the unarchived contents of) the package into your styles/ directory. You then need to visit Administration Control Panel -> Customise tab -> Style management -> Install Styles where you should see the new style available. Click "Install style" to install the style.

+ +

Please note that to improve efficiency, the software caches certain data. For this reason, if you create your own style or modify existing ones, please remember to purge the board cache by clicking the Run now button next to the Purge the cache option in the index page of the Administration Control Panel. You may also need to reload the page you have changed in your web browser to overcome browser caching. If the cache is not purged, you will not see your changes taking effect.

+ +

2.iii. Extensions

+ +

We are proud to have a thriving extensions community. These third party extensions to the standard phpBB software, extend its capabilities still further. You can browse through many of the extensions in the Extensions section of our Customisation Database.

+ +

For more information about extensions, please see: https://www.phpbb.com/extensions

+ +

Please remember that any bugs or other issues that occur after you have added any extension should NOT be reported to the bug tracker (see below). First disable the extension and see if the problem is resolved. Any support for an extension should only be sought in the "Discussion/Support" forum for that extension.

+ +

Also remember that any extensions which modify the database in any way, may render upgrading your forum to future versions more difficult.

+ +
+ + + +
+
+ +
+ +

3. Getting help with phpBB

+ +
+
+ +
+ +

phpBB can sometimes seem a little daunting to new users, particularly with regards to the permission system. The first thing you should do is check the FAQ, which covers a few basic getting started questions. If you need additional help there are several places you can find it.

+ +

3.i. phpBB Documentation

+ +

Comprehensive documentation is now available on the phpBB website:

+ +

https://www.phpbb.com/support/docs/en/3.2/ug/

+ +

This covers everything from installation to setting permissions and managing users.

+ +

3.ii. Knowledge Base

+ +

The Knowledge Base consists of a number of detailed articles on some common issues phpBB users may encounter while using the product. The Knowledge Base can be found at:

+ +

https://www.phpbb.com/kb/

+ +

3.iii. Community Forums

+ +

The phpBB project maintains a thriving community where a number of people have generously decided to donate their time to help support users. This site can be found at:

+ +

https://www.phpbb.com/community/

+ +

If you do seek help via our forums please be sure to do a search before posting; if someone has experienced the issue before, then you may find that your question has already been answered. Please remember that phpBB is entirely staffed by volunteers, no one receives any compensation for the time they give, including moderators as well as developers; please be respectful and mindful when awaiting responses and receiving support.

+ +

3.iv Internet Relay Chat

+ +

Another place you may find help is our IRC channel. This operates on the Freenode IRC network, irc.freenode.net and the channel is #phpbb and can be accessed by any decent IRC client such as mIRC, XChat, etc. Again, please do not abuse this service and be respectful of other users.

+ +

There are other IRC channels available, please see https://www.phpbb.com/support/irc/ for the complete list.

+ +
+ + + +
+
+ +
+ +

4. Status of this version

+ +
+
+ +
+ +

This is a stable release of phpBB. The 3.2.x line is feature frozen, with point releases principally including fixes for bugs and security issues. Feature alterations and minor feature additions may be done if deemed absolutely required. The next major release will be phpBB 3.3 which is currently under development. Please do not post questions asking when 3.3 will be available, no release date has been set.

+ +

Those interested in the development of phpBB should keep an eye on the development forums to see how things are progressing:

+ +

http://area51.phpbb.com/phpBB/

+ +

Please note that the development forums should NOT be used to seek support for phpBB, the main community forums are the place for this.

+ +
+ + + +
+
+ +
+ +

5. Reporting Bugs

+ +
+
+ +
+ +

The phpBB developers use a bug tracking system to store, list and manage all reported bugs, it can be found at the location listed below. Please DO NOT post bug reports to our forums. In addition please DO NOT use the bug tracker for support requests. Posting such a request will only see you directed to the support forums (while taking time away from working on real bugs).

+ +

http://tracker.phpbb.com/browse/PHPBB3

+ +

While we very much appreciate receiving bug reports (the more reports the more stable phpBB will be) we ask you carry out a few steps before adding new entries:

+ +
    +
  • First, determine if your bug is reproduceable; how to determine this depends on the bug in question. Only if the bug is reproduceable is it likely to be a problem with phpBB (or in some way connected). If something cannot be reproduced it may turn out to have been your hosting provider working on something, a user doing something silly, etc. Bug reports for non-reproduceable events can slow down our attempts to fix real, reproduceable issues

  • +
  • Next, please read or search through the existing bug reports to see if your bug (or one very similar to it) is already listed. If it is please add to that existing bug rather than creating a new duplicate entry (all this does is slow us down).

  • +
  • Check the forums (use search!) to see if people have discussed anything that sounds similar to what you are seeing. However, as noted above please DO NOT post your particular bug to the forum unless it's non-reproduceable or you are sure it’s related to something you have done rather than phpBB

  • +
  • If no existing bug exists then please feel free to add it
  • +
+ +

If you do post a new bug (i.e. one that isn't already listed in the bug tracker) first make sure that you have logged in (your username and password are the same as for the community forums) then please include the following details:

+ +
    +
  • Your server type/version, e.g. Apache 2.2.3, IIS 7, Sambar, etc.
  • +
  • PHP version and mode of operation, e.g. PHP 5.4.0 as a module, PHP 5.4.0 running as CGI, etc.
  • +
  • DB type/version, e.g. MySQL 5.0.77, PostgreSQL 9.0.6, MSSQL Server 2000 (via ODBC), etc.
  • +
+ +

The relevant database type/version is listed within the administration control panel.

+ +

Please be as detailed as you can in your report, and if possible, list the steps required to duplicate the problem. If you have a patch that fixes the issue, please attach it to the ticket or submit a pull request to our repository on GitHub.

+ +

If you create a patch, it is very much appreciated (but not required) if you follow the phpBB coding guidelines. Please note that the coding guidelines are somewhat different between different versions of phpBB. For phpBB 3.2.x the coding guidelines may be found here: http://area51.phpbb.com/docs/32x/coding-guidelines.html

+ +

Once a bug has been submitted you will be emailed any follow up comments added to it. Please if you are requested to supply additional information, do so! It is frustrating for us to receive bug reports, ask for additional information but get nothing. In these cases we have a policy of closing the bug, which may leave a very real problem in place. Obviously we would rather not have this situation arise.

+ +

5.i. Security related bugs

+ +

If you find a potential security related vulnerability in phpBB please DO NOT post it to the bug tracker, public forums, etc.! Doing so may allow unscrupulous users to take advantage of it before we have time to put a fix in place. All security related bugs should be sent to our security tracker:

+ +

https://www.phpbb.com/security/

+ +
+ + + +
+
+ +
+ +

6. Overview of current bug list

+ +
+
+ +
+ +

This list is not complete but does represent those bugs which may affect users on a wider scale. Other bugs listed in the tracker have typically been shown to be limited to certain setups or methods of installation, updating and/or conversions.

+ +
    +
  • Conversions may fail to complete on large boards under some hosts.
  • +
  • Updates may fail to complete on large update sets under some hosts.
  • +
  • Smilies placed directly after bbcode tags will not get parsed. Smilies always need to be separated by spaces.
  • +
+ +
+ + + +
+
+ +
+ +

7. PHP compatibility issues

+ +
+
+ +
+ +

phpBB 3.2.x takes advantage of new features added in PHP 5.4. We recommend that you upgrade to the latest stable release of PHP to run phpBB. The minimum version required is PHP 5.4.7 and the maximum supported version is the latest stable version of PHP.

+ +

Please remember that running any application on a development (unstable, e.g. a beta release) version of PHP can lead to strange/unexpected results which may appear to be bugs in the application. Therefore, we recommend you upgrade to the newest stable version of PHP before running phpBB. If you are running a development version of PHP please check any bugs you find on a system running a stable release before submitting.

+ +

This board has been developed and tested under Linux and Windows (amongst others) running Apache using MySQL 3.23, 4.x, 5.x, MariaDB 5.x, PostgreSQL 8.x, Oracle 8 and SQLite 3. Versions of PHP used range from 5.4.7 above 5.6.x to 7.1.x and 7.2.x without problem.

+ +

7.i. Notice on PHP security issues

+ +

Currently there are no known issues regarding PHP security.

+ +
+ + + +
+
+ +
+ +

8. Copyright and disclaimer

+ +
+
+ +
+ +

phpBB is free software, released under the terms of the GNU General Public License, version 2 (GPL-2.0). Copyright © phpBB Limited. For full copyright and license information, please see the docs/CREDITS.txt file.

+ +
+ + + +
+
+ + + + +
+ +
+ +
+ + + diff --git a/docs/assets/css/stylesheet.css b/docs/assets/css/stylesheet.css new file mode 100644 index 0000000..c090ab7 --- /dev/null +++ b/docs/assets/css/stylesheet.css @@ -0,0 +1,337 @@ +/* + The original "prosilver" theme for phpBB3 + Created by subBlue design :: http://www.subBlue.com +*/ + +* { margin: 0; padding: 0; } + +html { font-size: 100%; height: 100%; margin-bottom: 1px; } + +body { + font-family: Verdana, Helvetica, Arial, sans-serif; + color: #828282; + background-color: #FFFFFF; + font-size: 10px; + margin: 0; + padding: 12px 0; +} + +img { border-width: 0; } + +ul, ol { + font-size: 1.1em; +} + +ul ul, ol ol { + font-size: inherit; +} + +p { + line-height: 1.3em; + font-size: 1.1em; + margin-bottom: 1.5em; +} + +hr { + border: 0 none #FFFFFF; + border-top: 1px solid #CCCCCC; + height: 1px; + margin: 5px 0; + display: block; + clear: both; +} + +html, body { + color: #536482; + background-color: #FFFFFF; +} + +#doc-description h1 { + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + margin-right: 200px; + color: #FFFFFF; + margin-top: 15px; + font-weight: bold; + font-size: 2em; + color: #fff; +} + +h1 { + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-weight: normal; + color: #000; + font-size: 2em; + margin: 0.8em 0 0.2em 0; +} + +h2 { + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-weight: normal; + color: #28313F; + font-size: 1.5em; + margin: 0.8em 0 0.2em 0; +} + +h3 { + font-family: Arial, Helvetica, sans-serif; + font-weight: bold; + border-bottom: 1px solid #CCCCCC; + margin-bottom: 3px; + padding-bottom: 2px; + font-size: 1.1em; + color: #115098; + margin-top: 20px; +} + +h4 { + font-family: Arial, Helvetica, sans-serif; + font-weight: bold; + margin-bottom: 3px; + padding-bottom: 2px; + font-size: 1.1em; + color: #115098; + margin-top: 20px; +} + +.good { color: green; } +.bad { color: red; } + +.version { + margin-top: 20px; + text-align: left; + font-size: 70%; + color: #006600; + border-top: 1px solid #ccc; +} + +code { + color: #006600; + font-weight: normal; + font-family: 'Courier New', monospace; + border-color: #D1D7DC; + border-width: 1px; + border-style: solid; + background-color: #FAFAFA; + padding: 0 4px; +} + +pre { + color: #006600; + font-weight: normal; + font-family: 'Courier New', monospace; + border-color: #D1D7DC; + border-width: 1px; + border-style: solid; + background-color: #FAFAFA; + padding: 0 4px +} + +#wrap { + padding: 0 20px; + min-width: 650px; +} + +#simple-wrap { + padding: 6px 10px; +} + +#page-body { + margin: 4px 0; + clear: both; +} + +#page-footer { + clear: both; +} + +#logo { + float: left; + width: auto; + padding: 10px 13px 0 10px; +} + +a#logo:hover { + text-decoration: none; +} + +#doc-description { + float: left; + width: 70%; +} + +#doc-description h1 { + margin-right: 0; +} + +.headerbar { + background: #ebebeb none repeat-x 0 0; + border-radius: 7px; + color: #FFFFFF; + margin-bottom: 4px; + padding: 5px; +} + +.paragraph { + border-radius: 7px; + font-size: 1.1em; + padding: 5px 10px; + margin-bottom: 4px; + background-repeat: no-repeat; + background-position: 100% 0; + background-color: #ECF3F7; +} + +.paragraph:target .content { + color: #000000; +} + +.paragraph:target h3 a { + color: #000000; +} + +.main-description { + font-size: 1.15em; +} + +.content { + color: #333333; +} + +.content h2, .panel h2 { + color: #115098; + border-bottom-color: #CCCCCC; +} + +a:link { color: #898989; text-decoration: none; } +a:visited { color: #898989; text-decoration: none; } +a:hover { color: #d3d3d3; text-decoration: underline; } +a:active { color: #d2d2d2; text-decoration: none; } + +hr { + border-color: #FFFFFF; + border-top-color: #CCCCCC; +} + +.menu { + background-color: #cadceb; +} + +.headerbar { + background-color: #12A3EB; + background-image: url("../images/bg_header.gif"); + color: #FFFFFF; +} + +.panel { + background-color: #ECF1F3; + color: #28313F; +} + +.error { + color: #BC2A4D; +} + +a:link { color: #105289; } +a:visited { color: #105289; } +a:hover { color: #D31141; } +a:active { color: #368AD2; } + +.content { + padding: 0; + line-height: 1.48em; + color: #333333; +} + +.content h2, .panel h2 { + color: #115098; + border-bottom-color: #CCCCCC; +} + +.notice { + border-top-color: #CCCCCC; +} + +.codebox { + padding: 3px; + background-color: #FFFFFF; + border: 1px solid #C9D2D8; + font-size: 1em; + margin-bottom: 10px; + display: block; + font: 0.9em Monaco, "Andale Mono","Courier New", Courier, mono; + line-height: 1.3em; +} + +* html hr { margin: 0; } + +.top { + background: url("../images/icon_back_top.gif") no-repeat top left; + text-decoration: none; + width: 11px; + height: 11px; + display: block; + float: right; + overflow: hidden; + letter-spacing: 1000px; + text-indent: 11px; +} + +.content ol, .content ul { + margin-left: 25px; + margin-top: 0; +} + +.content ul + p, .content ul + div { + margin-top: 20px; +} + +.comment { + color: green; +} + +.indent { + margin-left: 20px; +} + +.paragraph table { + font-size: 8pt; + border-collapse: collapse; + border: 1px solid #cfcfcf; + margin-bottom: 20px; +} + +.paragraph table caption { + display: none; +} + +.paragraph table thead { + background-color: #cadceb; + color: #000; +} + +.paragraph table td, .paragraph table th { + border: 1px solid #006699; + padding: 0.5em; + background-color: #e1ebf2; +} + +.paragraph table th { + background-color: #cadceb; +} + +.paragraph table td dl { + margin: 0; + padding: 0; +} + +.paragraph table td dl dt { + float: left; + clear: both; + margin-right: 1em; +} + +.inner:after { + clear: both; + content: ''; + display: block; +} diff --git a/docs/assets/images/bg_header.gif b/docs/assets/images/bg_header.gif new file mode 100644 index 0000000..351de9f Binary files /dev/null and b/docs/assets/images/bg_header.gif differ diff --git a/docs/assets/images/icon_back_top.gif b/docs/assets/images/icon_back_top.gif new file mode 100644 index 0000000..4d2b8f3 Binary files /dev/null and b/docs/assets/images/icon_back_top.gif differ diff --git a/docs/assets/images/site_logo.gif b/docs/assets/images/site_logo.gif new file mode 100644 index 0000000..2517fbe Binary files /dev/null and b/docs/assets/images/site_logo.gif differ diff --git a/docs/auth_api.html b/docs/auth_api.html new file mode 100644 index 0000000..960fa76 --- /dev/null +++ b/docs/auth_api.html @@ -0,0 +1,293 @@ + + + + + + + +phpBB3 • Auth API + + + + + + + +
+ + + + + +
+ + + +

This is an explanation of how to use the phpBB auth/acl API.

+ +

Auth API

+ + + +
+ +

1. Introduction

+ +
+
+ +
+ +

What is it?

+ +

The auth class contains methods related to authorisation users to access various board functions, e.g. posting, viewing, replying, logging in (and out), etc. If you need to check whether a user can carry out a task or handle user login/logouts this class is required.

+ +

Initialisation

+ +

To use any methods contained with the auth class it first needs to be instantiated. This is best achieved early in the execution of the script in the following manner:

+ +
+$auth = new phpbb\auth\auth();
+
+ +

Once an instance of the class has been created you are free to call the various methods it contains. Please note that should you wish to use the auth_admin methods you will need to instantiate this separately but in the same way.

+ +
+ + + +
+
+ +
+ +

2. Methods

+ +
+
+ +
+ +

Following are the methods you are able to use.

+ +

2.i. acl

+ +

The acl method is the initialisation routine for all the acl functions. If you intend calling any acl method you must first call this. The method takes as its one and only required parameter an associative array containing user information as stored in the database. This array must contain at least the following information; user_id, user_permissions and user_type. It is called in the following way:

+ +
+$auth->acl(userdata);
+
+ +

Where userdata is the array containing the aforementioned data.

+ +

2.ii. acl_get

+ +

This method is the primary way of determining what a user can and cannot do for a given option globally or in a given forum. The method should be called in the following way:

+ +
+$result = $auth->acl_get(option[, forum]);
+
+ +

Where option is a string representing the required option, e.g. 'f_list', 'm_edit', 'a_adduser', etc. By adding a ! in front of the option, e.g. '!f_list' the result of this method will be negated. The optional forum term is the integer forum_id.

+ +

The method returns a positive integer when the user is allowed to carry out the option and a zero if denied or the other way around if the option is prefixed with an exclamation mark.

+ +

If you specify a forum and there is also a global setting for the specified option then this method will return a positive integer if one of them evaluates to a positive integer. An example would be the m_approve option which can be set per forum but also globally. If a user has the global option he will automatically have m_approve in every forum.

+ +

There are some special options or flags which are used as prefixes for other options, e.g. 'f_' or 'm_'. These flags will automatically be set to a positive integer if the user has one or more permissions with the given prefix. A local setting will result in the flag being set only locally (so it will require a forum id to retrieve). If a user has one or more global permissions with the prefix acl_get will return a positive integer regardless of the forum id.

+ +

2.iii. acl_gets

+ +

This method is funtionally similar to acl_get in that it returns information on whether a user can or cannot carry out a given task. The difference here is the ability to test several different options in one go. This may be useful for testing whether a user is a moderator or an admin in one call. Rather than having to call and check acl_get twice.

+ +

The method should be called thus:

+ +
+$result = $auth->acl_gets(option1[, option2, ..., optionN, forum]);
+
+ +

As with the acl_get method the options are strings representing the required permissions to check. The forum again is an integer representing a given forum_id.

+ +

The method will return a positive integer if acl_get for one of the options evaluates to a positive integer (combines permissions with OR).

+ +

2.iv. acl_getf

+ +

This method is used to find out in which forums a user is allowed to carry out an operation or to find out in which forums he is not allowed to carry out an operation. The method should be called in the following way:

+ +
+$result = $auth->acl_getf(option[, clean]);
+
+ +

Just like in the acl_get method the option is a string specifying the permission which has to be checked (negation using ! is allowed). The second parameter is a boolean. If it is set to false this method returns all forums with either zero or a positive integer. If it is set to true only those forums with a positive integer as the result will be returned.

+ +

The method returns an associative array of the form:

+ +
+array(forum_id1 => array(option => integer), forum_id2 => ...)
+
+ +

Where option is the option passed to the method and integer is either zero or a positive integer and the same acl_get(option, forum_id) would return.

+ +

2.v. acl_getf_global

+ +

This method is used to find out whether a user has a permission in at least one forum or globally. This method is similar to checking whether acl_getf(option, true) returned one or more forums but it's faster. It should be called in the following way:

+ +
+$result = $auth->acl_getf_global(option)
+
+ +

As with the previous methods option is a string specifying the permission which has to be checked.

+ +

This method returns either zero or a positive integer.

+ +

2.vi. acl_cache

+ +

This should be considered a private method and not be called externally. It handles the generation of the user_permissions data from the basic user and group authorisation data. When necessary this method is called automatically by acl.

+ +

2.vii. acl_clear_prefetch

+ +

This method clears the user_permissions column in the users table for the given user. If the user ID passed is zero, the permissions cache is cleared for all users. This method should be called whenever permissions are set.

+ +
+// clear stored permissions for user 2
+$user_id = 2;
+$auth->acl_clear_prefetch($user_id);
+
+ +

This method returns null.

+ +

2.viii. acl_get_list

+ +

This method returns an an array describing which users have permissions in given fora. The resultant array contains an entry for permission that every user has in every forum when no arguments are passed.

+ +
+$user_id = array(2, 53);
+$permissions = array('f_list', 'f_read');
+$forum_id = array(1, 2, 3);
+$result = $auth->acl_get_list($user_id, $permissions, $forum_id);
+
+ +

The parameters may be of the following legal types:

+
    +
  • $user_id: false, int, array(int, int, int, ...)
  • +
  • $permissions: false, string, array(string, string, ...)
  • +
  • $forum_id: false, int, array(int, int, int, ...)
  • +
+ +

2.ix. Miscellaneous

+ +

There are other methods defined in the auth class which serve mostly as private methods, but are available for use if needed. Each of them is used to pull data directly from the database tables. They are:

+
    +
  • function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false)
  • +
  • function acl_user_raw_data($user_id = false, $opts = false, $forum_id = false)
  • +
  • function acl_raw_data_single_user($user_id)
  • +
  • function acl_raw_data($user_id = false, $opts = false, $forum_id = false)
  • +
  • function acl_role_data($user_type, $role_type, $ug_id = false, $forum_id = false)
  • +
+ +

Of these, acl_raw_data is the most general, but the others will be faster if you need a smaller amount of data.

+ +
+ + + +
+
+ +
+ +

3. Admin related functions

+ +
+
+ +
+ +

A number of additional methods are available related to auth. These handle more basic functions such as adding user and group permissions, new options and clearing the user cache. These methods are contained within a separate class, auth_admin. This can be found in includes/acp/auth.php.

+ +

To use any methods this class contains it first needs to be instantiated separately from auth. This is achieved in the same way as auth:

+ +
+$auth_admin = new auth_admin();
+
+ +

This instance gives you access to both the methods of this specific class and that of auth.

+ +
+ + + +
+
+ +
+ +

4. Copyright and disclaimer

+ +
+
+ +
+ +

phpBB is free software, released under the terms of the GNU General Public License, version 2 (GPL-2.0). Copyright © phpBB Limited. For full copyright and license information, please see the docs/CREDITS.txt file.

+ +
+ + + +
+
+ + + + +
+ +
+ +
+ + + diff --git a/docs/coding-guidelines.html b/docs/coding-guidelines.html new file mode 100644 index 0000000..569ffe6 --- /dev/null +++ b/docs/coding-guidelines.html @@ -0,0 +1,2573 @@ + + + + + + + +phpBB3 • Coding Guidelines + + + + + + + +
+ + + + + +
+ + + +

+ These are the phpBB Coding Guidelines for Rhea, all attempts should be made to follow them as closely as possible. +

+ +

Coding Guidelines

+ + + +
+ +

1. Defaults

+ +
+
+ +
+ +

1.i. Editor Settings

+ +

Tabs vs Spaces:

+

In order to make this as simple as possible, we will be using tabs, not spaces. We enforce 4 (four) spaces for one tab - therefore you need to set your tab width within your editor to 4 spaces. Make sure that when you save the file, it's saving tabs and not spaces. This way, we can each have the code be displayed the way we like it, without breaking the layout of the actual files.

+

Tabs in front of lines are no problem, but having them within the text can be a problem if you do not set it to the amount of spaces every one of us uses. Here is a short example of how it should look like:

+ +
+{TAB}$mode{TAB}{TAB}= $request->variable('mode', '');
+{TAB}$search_id{TAB}= $request->variable('search_id', '');
+
+ +

If entered with tabs (replace the {TAB}) both equal signs need to be on the same column.

+ +

Linefeeds:

+

Ensure that your editor is saving files in the UNIX (LF) line ending format. This means that lines are terminated with a newline, not with Windows Line endings (CR/LF combo) as they are on Win32 or Classic Mac (CR) Line endings. Any decent editor should be able to do this, but it might not always be the default setting. Know your editor. If you want advice for an editor for your Operating System, just ask one of the developers. Some of them do their editing on Win32.

+ +

1.ii. File Layout

+ +

Standard header for new files:

+

This template of the header must be included at the start of all phpBB files:

+ +
+/**
+*
+* This file is part of the phpBB Forum Software package.
+*
+* @copyright (c) phpBB Limited <https://www.phpbb.com>
+* @license GNU General Public License, version 2 (GPL-2.0)
+*
+* For full copyright and license information, please see
+* the docs/CREDITS.txt file.
+*
+*/
+
+ +

Please see the File Locations section for the correct package name.

+ +

PHP closing tags

+ +

A file containg only PHP code should not end with the optional PHP closing tag ?> to avoid issues with whitespace following it.

+ +

Newline at end of file

+ +

All files should end in a newline so the last line does not appear as modified in diffs, when a line is appended to the file.

+ +

Files containing inline code:

+ +

For those files you have to put an empty comment directly after the header to prevent the documentor assigning the header to the first code element found.

+ +
+/**
+* {HEADER}
+*/
+
+/**
+*/
+{CODE}
+
+ +

Files containing only functions:

+ +

Do not forget to comment the functions (especially the first function following the header). Each function should have at least a comment of what this function does. For more complex functions it is recommended to document the parameters too.

+ +

Files containing only classes:

+ +

Do not forget to comment the class. Classes need a separate @package definition, it is the same as the header package name. Apart from this special case the above statement for files containing only functions needs to be applied to classes and it's methods too.

+ +

Code following the header but only functions/classes file:

+ +

If this case is true, the best method to avoid documentation confusions is adding an ignore command, for example:

+ +
+/**
+* {HEADER}
+*/
+
+/**
+* @ignore
+*/
+Small code snipped, mostly one or two defines or an if statement
+
+/**
+* {DOCUMENTATION}
+*/
+class ...
+
+ +

1.iii. File Locations

+ +

Functions used by more than one page should be placed in functions.php, functions specific to one page should be placed on that page (at the bottom) or within the relevant sections functions file. Some files in /includes are holding functions responsible for special sections, for example uploading files, displaying "things", user related functions and so forth.

+ +

The following packages are defined, and related new features/functions should be placed within the mentioned files/locations, as well as specifying the correct package name. The package names are bold within this list:

+ +
    +
  • phpBB3
    Core files and all files not assigned to a separate package
  • +
  • acm
    /phpbb/cache
    Cache System
  • +
  • acp
    /adm, /includes/acp, /includes/functions_admin.php
    Administration Control Panel
  • +
  • dbal
    /phpbb/db, /includes/db
    Database Abstraction Layer. +
      +
    • /phpbb/db/driver/
      Database Abstraction Layer classes
    • +
    • /phpbb/db/migration/
      Migrations are used for updating the database from one release to another
    • +
    +
  • +
  • diff
    /includes/diff
    Diff Engine
  • +
  • docs
    /docs
    phpBB Documentation
  • +
  • images
    /images
    All global images not connected to styles
  • +
  • install
    /install
    Installation System
  • +
  • language
    /language
    All language files
  • +
  • login
    /phpbb/auth
    Login Authentication Plugins
  • +
  • VC
    /includes/captcha
    CAPTCHA
  • +
  • mcp
    mcp.php, /includes/mcp, report.php
    Moderator Control Panel
  • +
  • ucp
    ucp.php, /includes/ucp
    User Control Panel
  • +
  • utf
    /includes/utf
    UTF8-related functions/classes
  • +
  • search
    /phpbb/search, search.php
    Search System
  • +
  • styles
    /styles
    phpBB Styles/Templates/Themes
  • +
+ +

1.iv. Special Constants

+ +

There are some special constants application developers are able to utilize to bend some of phpBB's internal functionality to suit their needs.

+ +
+PHPBB_MSG_HANDLER          (overwrite message handler)
+PHPBB_DB_NEW_LINK          (overwrite new_link parameter for sql_connect)
+PHPBB_ROOT_PATH            (overwrite $phpbb_root_path)
+PHPBB_ADMIN_PATH           (overwrite $phpbb_admin_path)
+PHPBB_USE_BOARD_URL_PATH   (use generate_board_url() for image paths instead of $phpbb_root_path)
+PHPBB_DISABLE_ACP_EDITOR   (disable ACP style editor for templates)
+PHPBB_DISABLE_CONFIG_CHECK (disable ACP config.php writeable check)
+
+PHPBB_ACM_MEMCACHE_PORT     (overwrite memcached port, default is 11211)
+PHPBB_ACM_MEMCACHE_COMPRESS (overwrite memcached compress setting, default is disabled)
+PHPBB_ACM_MEMCACHE_HOST     (overwrite memcached host name, default is localhost)
+
+PHPBB_ACM_REDIS_HOST        (overwrite redis host name, default is localhost)
+PHPBB_ACM_REDIS_PORT        (overwrite redis port, default is 6379)
+PHPBB_ACM_REDIS_PASSWORD    (overwrite redis password, default is empty)
+PHPBB_ACM_REDIS_DB          (overwrite redis default database)
+
+PHPBB_QA                   (Set board to QA-Mode, which means the updater also checks for RC-releases)
+
+ +

PHPBB_USE_BOARD_URL_PATH

+ +

If the PHPBB_USE_BOARD_URL_PATH constant is set to true, phpBB uses generate_board_url() (this will return the boards url with the script path included) on all instances where web-accessible images are loaded. The exact locations are:

+ +
    +
  • /phpbb/user.php - \phpbb\user::img()
  • +
  • /includes/functions_content.php - smiley_text()
  • +
+ +

Path locations for the following template variables are affected by this too:

+ +
    +
  • {T_ASSETS_PATH} - assets (non-style specific, static resources)
  • +
  • {T_THEME_PATH} - styles/xxx/theme
  • +
  • {T_TEMPLATE_PATH} - styles/xxx/template
  • +
  • {T_SUPER_TEMPLATE_PATH} - styles/xxx/template
  • +
  • {T_IMAGES_PATH} - images/
  • +
  • {T_SMILIES_PATH} - $config['smilies_path']/
  • +
  • {T_AVATAR_PATH} - $config['avatar_path']/
  • +
  • {T_AVATAR_GALLERY_PATH} - $config['avatar_gallery_path']/
  • +
  • {T_ICONS_PATH} - $config['icons_path']/
  • +
  • {T_RANKS_PATH} - $config['ranks_path']/
  • +
  • {T_UPLOAD_PATH} - $config['upload_path']/
  • +
  • {T_STYLESHEET_LINK} - styles/xxx/theme/stylesheet.css
  • +
  • New template variable {BOARD_URL} for the board url + script path.
  • +
+ +
+ + + +
+
+ +
+ +

2. Code Layout/Guidelines

+ +
+
+ +
+ +

Please note that these guidelines apply to all php, html, javascript and css files.

+ +

2.i. Variable/Function/Class Naming

+ +

We will not be using any form of hungarian notation in our naming conventions. Many of us believe that hungarian naming is one of the primary code obfuscation techniques currently in use.

+ +

Variable Names:

+

In PHP, variable names should be in all lowercase, with words separated by an underscore, example:

+ +
+

$current_user is right, but $currentuser and $currentUser are not.

+
+ +

In JavaScript, variable names should use camel case:

+ +
+

currentUser is right, but currentuser and current_user are not.

+
+ +

Names should be descriptive, but concise. We don't want huge sentences as our variable names, but typing an extra couple of characters is always better than wondering what exactly a certain variable is for.

+ +

Loop Indices:

+

The only situation where a one-character variable name is allowed is when it's the index for some looping construct. In this case, the index of the outer loop should always be $i. If there's a loop inside that loop, its index should be $j, followed by $k, and so on. If the loop is being indexed by some already-existing variable with a meaningful name, this guideline does not apply, example:

+ +
+for ($i = 0; $i < $outer_size; $i++)
+{
+   for ($j = 0; $j < $inner_size; $j++)
+   {
+      foo($i, $j);
+   }
+}
+
+ +

Function Names:

+

Functions should also be named descriptively. We're not programming in C here, we don't want to write functions called things like "stristr()". Again, all lower-case names with words separated by a single underscore character in PHP, and camel caps in JavaScript. Function names should be prefixed with "phpbb_" and preferably have a verb in them somewhere. Good function names are phpbb_print_login_status(), phpbb_get_user_data(), etc. Constructor functions in JavaScript should begin with a capital letter.

+ +

Function Arguments:

+

Arguments are subject to the same guidelines as variable names. We don't want a bunch of functions like: do_stuff($a, $b, $c). In most cases, we'd like to be able to tell how to use a function by just looking at its declaration.

+ +

Class Names:

+ +

Apart from following the rules for function names, all classes should meet the following conditions:

+
    +
  • Every class must be defined in a separate file.
  • +
  • The classes have to be located in a subdirectory of phpbb/.
  • +
  • Classnames must be namespaced with \phpbb\ to avoid name clashes.
  • +
  • Class names/namespaces have to reflect the location of the file they are defined in. The namespace must be the directory in which the file is located. So the directory names must not contain any underscores, but the filename may.
  • +
  • Directories should typically be a singular noun (e.g. dir in the example below, not dirs.
  • +
+ +

So given the following example directory structure you would result in the below listed lookups

+
+phpbb/
+  class_name.php
+  dir/
+    class_name.php
+      subdir/
+        class_name.php
+
+ +
+\phpbb\class_name            - phpbb/class_name.php
+\phpbb\dir\class_name        - phpbb/dir/class_name.php
+\phpbb\dir\subdir\class_name - phpbb/dir/subdir/class_name.php
+
+ + +

Summary:

+

The basic philosophy here is to not hurt code clarity for the sake of laziness. This has to be balanced by a little bit of common sense, though; phpbb_print_login_status_for_a_given_user() goes too far, for example -- that function would be better named phpbb_print_user_login_status(), or just phpbb_print_login_status().

+ +

Special Namings:

+

For all emoticons use the term smiley in singular and smilies in plural. For emails we use the term email (without dash between “e” and “m”).

+ +

2.ii. Code Layout

+ +

Always include the braces:

+

This is another case of being too lazy to type 2 extra characters causing problems with code clarity. Even if the body of some construct is only one line long, do not drop the braces. Just don't, examples:

+ +

// These are all wrong.

+ +
+if (condition) do_stuff();
+
+if (condition)
+	do_stuff();
+
+while (condition)
+	do_stuff();
+
+for ($i = 0; $i < size; $i++)
+	do_stuff($i);
+
+ +

// These are all right.

+
+if (condition)
+{
+	do_stuff();
+}
+
+while (condition)
+{
+	do_stuff();
+}
+
+for ($i = 0; $i < size; $i++)
+{
+	do_stuff();
+}
+
+ +

Where to put the braces:

+

In PHP code, braces always go on their own line. The closing brace should also always be at the same column as the corresponding opening brace, examples:

+ +
+if (condition)
+{
+	while (condition2)
+	{
+		...
+	}
+}
+else
+{
+	...
+}
+
+for ($i = 0; $i < $size; $i++)
+{
+	...
+}
+
+while (condition)
+{
+	...
+}
+
+function do_stuff()
+{
+	...
+}
+
+ +

In JavaScript code, braces always go on the same line:

+ +
+if (condition) {
+	while (condition2) {
+		...
+	}
+} else {
+	...
+}
+
+for (var i = 0; i < size; i++) {
+	...
+}
+
+while (condition) {
+	...
+}
+
+function do_stuff() {
+	...
+}
+
+ +

Use spaces between tokens:

+

This is another simple, easy step that helps keep code readable without much effort. Whenever you write an assignment, expression, etc.. Always leave one space between the tokens. Basically, write code as if it was English. Put spaces between variable names and operators. Don't put spaces just after an opening bracket or before a closing bracket. Don't put spaces just before a comma or a semicolon. This is best shown with a few examples, examples:

+ +

// Each pair shows the wrong way followed by the right way.

+ +
+$i=0;
+$i = 0;
+
+if($i<7) ...
+if ($i < 7) ...
+
+if ( ($i < 7)&&($j > 8) ) ...
+if ($i < 7 && $j > 8) ...
+
+do_stuff( $i, 'foo', $b );
+do_stuff($i, 'foo', $b);
+
+for($i=0; $i<$size; $i++) ...
+for ($i = 0; $i < $size; $i++) ...
+
+$i=($j < $size)?0:1;
+$i = ($j < $size) ? 0 : 1;
+
+ +

Operator precedence:

+

Do you know the exact precedence of all the operators in PHP? Neither do I. Don't guess. Always make it obvious by using brackets to force the precedence of an equation so you know what it does. Remember to not over-use this, as it may harden the readability. Basically, do not enclose single expressions. Examples:

+ +

// what's the result? who knows.

+
+
$bool = ($i < 7 && $j > 8 || $k == 4);
+
+ +

// now you can be certain what I'm doing here.

+
+
$bool = (($i < 7) && (($j < 8) || ($k == 4)));
+
+ +

// But this one is even better, because it is easier on the eye but the intention is preserved

+
+
$bool = ($i < 7 && ($j < 8 || $k == 4));
+
+ +

Quoting strings:

+

There are two different ways to quote strings in PHP - either with single quotes or with double quotes. The main difference is that the parser does variable interpolation in double-quoted strings, but not in single quoted strings. Because of this, you should always use single quotes unless you specifically need variable interpolation to be done on that string. This way, we can save the parser the trouble of parsing a bunch of strings where no interpolation needs to be done.

+

Also, if you are using a string variable as part of a function call, you do not need to enclose that variable in quotes. Again, this will just make unnecessary work for the parser. Note, however, that nearly all of the escape sequences that exist for double-quoted strings will not work with single-quoted strings. Be careful, and feel free to break this guideline if it's making your code easier to read, examples:

+ +

// wrong

+
+$str = "This is a really long string with no variables for the parser to find.";
+
+do_stuff("$str");
+
+ +

// right

+
+$str = 'This is a really long string with no variables for the parser to find.';
+
+do_stuff($str);
+
+ +

// Sometimes single quotes are just not right

+
+$post_url = $phpbb_root_path . 'posting.' . $phpEx . '?mode=' . $mode . '&amp;start=' . $start;
+
+ +

// Double quotes are sometimes needed to not overcrowd the line with concatenations.

+
+$post_url = "{$phpbb_root_path}posting.$phpEx?mode=$mode&amp;start=$start";
+
+ +

In SQL statements mixing single and double quotes is partly allowed (following the guidelines listed here about SQL formatting), else one should try to only use one method - mostly single quotes.

+ +

Commas after every array element:

+

If an array is defined with each element on its own line, you still have to modify the previous line to add a comma when appending a new element. PHP allows for trailing (useless) commas in array definitions. These should always be used so each element including the comma can be appended with a single line. In JavaScript, do not use the trailing comma, as it causes browsers to throw errors.

+ +

// wrong

+
+$foo = array(
+	'bar' => 42,
+	'boo' => 23
+);
+
+ +

// right

+
+$foo = array(
+	'bar' => 42,
+	'boo' => 23,
+);
+
+ + +

Associative array keys:

+

In PHP, it's legal to use a literal string as a key to an associative array without quoting that string. We don't want to do this -- the string should always be quoted to avoid confusion. Note that this is only when we're using a literal, not when we're using a variable, examples:

+ +

// wrong

+
+
$foo = $assoc_array[blah];
+
+ +

// right

+
+
$foo = $assoc_array['blah'];
+
+ +

// wrong

+
+
$foo = $assoc_array["$var"];
+
+ +

// right

+
+
$foo = $assoc_array[$var];
+
+ +

Comments:

+

Each complex function should be preceded by a comment that tells a programmer everything they need to know to use that function. The meaning of every parameter, the expected input, and the output are required as a minimal comment. The function's behaviour in error conditions (and what those error conditions are) should also be present - but mostly included within the comment about the output.

Especially important to document are any assumptions the code makes, or preconditions for its proper operation. Any one of the developers should be able to look at any part of the application and figure out what's going on in a reasonable amount of time.

Avoid using /* */ comment blocks for one-line comments, // should be used for one/two-liners.

+ +

Magic numbers:

+

Don't use them. Use named constants for any literal value other than obvious special cases. Basically, it's ok to check if an array has 0 elements by using the literal 0. It's not ok to assign some special meaning to a number and then use it everywhere as a literal. This hurts readability AND maintainability. The constants true and false should be used in place of the literals 1 and 0 -- even though they have the same values (but not type!), it's more obvious what the actual logic is when you use the named constants. Typecast variables where it is needed, do not rely on the correct variable type (PHP is currently very loose on typecasting which can lead to security problems if a developer does not keep a very close eye on it).

+ +

Shortcut operators:

+

The only shortcut operators that cause readability problems are the shortcut increment $i++ and decrement $j-- operators. These operators should not be used as part of an expression. They can, however, be used on their own line. Using them in expressions is just not worth the headaches when debugging, examples:

+ +

// wrong

+
+$array[++$i] = $j;
+$array[$i++] = $k;
+
+ +

// right

+
+$i++;
+$array[$i] = $j;
+
+$array[$i] = $k;
+$i++;
+
+ +

Inline conditionals:

+

Inline conditionals should only be used to do very simple things. Preferably, they will only be used to do assignments, and not for function calls or anything complex at all. They can be harmful to readability if used incorrectly, so don't fall in love with saving typing by using them, examples:

+ +

// Bad place to use them

+
+($i < $size && $j > $size) ? do_stuff($foo) : do_stuff($bar);
+
+ +

// OK place to use them

+
+$min = ($i < $j) ? $i : $j;
+
+ +

Don't use uninitialized variables.

+

For phpBB3, we intend to use a higher level of run-time error reporting. This will mean that the use of an uninitialized variable will be reported as a warning. These warnings can be avoided by using the built-in isset() function to check whether a variable has been set - but preferably the variable is always existing. For checking if an array has a key set this can come in handy though, examples:

+ +

// Wrong

+
+
if ($forum) ...
+
+ +

// Right

+
+
if (isset($forum)) ...
+ +

// Also possible

+
+
if (isset($forum) && $forum == 5)
+
+ +

The empty() function is useful if you want to check if a variable is not set or being empty (an empty string, 0 as an integer or string, NULL, false, an empty array or a variable declared, but without a value in a class). Therefore empty should be used in favor of isset($array) && count($array) > 0 - this can be written in a shorter way as !empty($array).

+ +

Switch statements:

+

Switch/case code blocks can get a bit long sometimes. To have some level of notice and being in-line with the opening/closing brace requirement (where they are on the same line for better readability), this also applies to switch/case code blocks and the breaks. An example:

+ +

// Wrong

+
+switch ($mode)
+{
+	case 'mode1':
+		// I am doing something here
+		break;
+	case 'mode2':
+		// I am doing something completely different here
+		break;
+}
+
+ +

// Good

+
+switch ($mode)
+{
+	case 'mode1':
+		// I am doing something here
+	break;
+
+	case 'mode2':
+		// I am doing something completely different here
+	break;
+
+	default:
+		// Always assume that a case was not caught
+	break;
+}
+
+ +

// Also good, if you have more code between the case and the break

+
+switch ($mode)
+{
+	case 'mode1':
+
+		// I am doing something here
+
+	break;
+
+	case 'mode2':
+
+		// I am doing something completely different here
+
+	break;
+
+	default:
+
+		// Always assume that a case was not caught
+
+	break;
+}
+
+ +

Even if the break for the default case is not needed, it is sometimes better to include it just for readability and completeness.

+ +

If no break is intended, please add a comment instead. An example:

+ +

// Example with no break

+
+switch ($mode)
+{
+	case 'mode1':
+
+		// I am doing something here
+
+	// no break here
+
+	case 'mode2':
+
+		// I am doing something completely different here
+
+	break;
+
+	default:
+
+		// Always assume that a case was not caught
+
+	break;
+}
+
+ +

Class Members

+

Use the explicit visibility qualifiers public, private and protected for all properties instead of var. + +

Place the static qualifier before the visibility qualifiers.

+ +

//Wrong

+
+var $x;
+private static function f()
+
+ +

// Right

+
+public $x;
+static private function f()
+
+ +

Constants

+

Prefer class constants over global constants created with define().

+ +

2.iii. SQL/SQL Layout

+ +

Common SQL Guidelines:

+

All SQL should be cross-DB compatible, if DB specific SQL is used alternatives must be provided which work on all supported DB's (MySQL3/4/5, MSSQL (7.0 and 2000), PostgreSQL (8.3+), SQLite, Oracle8, ODBC (generalised if possible)).

+

All SQL commands should utilise the DataBase Abstraction Layer (DBAL)

+ +

SQL code layout:

+

SQL Statements are often unreadable without some formatting, since they tend to be big at times. Though the formatting of sql statements adds a lot to the readability of code. SQL statements should be formatted in the following way, basically writing keywords:

+ +
+$sql = 'SELECT *
+<-one tab->FROM ' . SOME_TABLE . '
+<-one tab->WHERE a = 1
+<-two tabs->AND (b = 2
+<-three tabs->OR b = 3)
+<-one tab->ORDER BY b';
+
+ +

Here the example with the tabs applied:

+ +
+$sql = 'SELECT *
+	FROM ' . SOME_TABLE . '
+	WHERE a = 1
+		AND (b = 2
+			OR b = 3)
+	ORDER BY b';
+
+ +

SQL Quotes:

+

Use double quotes where applicable. (The variables in these examples are typecasted to integers beforehand.) Examples:

+ +

// These are wrong.

+
+"UPDATE " . SOME_TABLE . " SET something = something_else WHERE a = $b";
+
+'UPDATE ' . SOME_TABLE . ' SET something = ' . $user_id . ' WHERE a = ' . $something;
+
+ +

// These are right.

+ +
+'UPDATE ' . SOME_TABLE . " SET something = something_else WHERE a = $b";
+
+'UPDATE ' . SOME_TABLE . " SET something = $user_id WHERE a = $something";
+
+ +

In other words use single quotes where no variable substitution is required or where the variable involved shouldn't appear within double quotes. Otherwise use double quotes.

+ +

Avoid DB specific SQL:

+

The "not equals operator", as defined by the SQL:2003 standard, is "<>"

+ +

// This is wrong.

+
+$sql = 'SELECT *
+	FROM ' . SOME_TABLE . '
+	WHERE a != 2';
+
+ +

// This is right.

+
+$sql = 'SELECT *
+	FROM ' . SOME_TABLE . '
+	WHERE a <> 2';
+
+ +

Common DBAL methods:

+ +

sql_escape():

+ +

Always use $db->sql_escape() if you need to check for a string within an SQL statement (even if you are sure the variable cannot contain single quotes - never trust your input), for example:

+ +
+$sql = 'SELECT *
+	FROM ' . SOME_TABLE . "
+	WHERE username = '" . $db->sql_escape($username) . "'";
+
+ +

sql_query_limit():

+ +

We do not add limit statements to the sql query, but instead use $db->sql_query_limit(). You basically pass the query, the total number of lines to retrieve and the offset.

+ +

Note: Since Oracle handles limits differently and because of how we implemented this handling you need to take special care if you use sql_query_limit with an sql query retrieving data from more than one table.

+ +

Make sure when using something like "SELECT x.*, y.jars" that there is not a column named jars in x; make sure that there is no overlap between an implicit column and the explicit columns.

+ +

sql_build_array():

+ +

If you need to UPDATE or INSERT data, make use of the $db->sql_build_array() function. This function already escapes strings and checks other types, so there is no need to do this here. The data to be inserted should go into an array - $sql_ary - or directly within the statement if one or two variables needs to be inserted/updated. An example of an insert statement would be:

+ +
+$sql_ary = array(
+	'somedata'		=> $my_string,
+	'otherdata'		=> $an_int,
+	'moredata'		=> $another_int,
+);
+
+$db->sql_query('INSERT INTO ' . SOME_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
+
+ +

To complete the example, this is how an update statement would look like:

+ +
+$sql_ary = array(
+	'somedata'		=> $my_string,
+	'otherdata'		=> $an_int,
+	'moredata'		=> $another_int,
+);
+
+$sql = 'UPDATE ' . SOME_TABLE . '
+	SET ' . $db->sql_build_array('UPDATE', $sql_ary) . '
+	WHERE user_id = ' . (int) $user_id;
+$db->sql_query($sql);
+
+ +

The $db->sql_build_array() function supports the following modes: INSERT (example above), INSERT_SELECT (building query for INSERT INTO table (...) SELECT value, column ... statements), UPDATE (example above) and SELECT (for building WHERE statement [AND logic]).

+ +

sql_multi_insert():

+ +

If you want to insert multiple statements at once, please use the separate sql_multi_insert() method. An example:

+ +
+$sql_ary = array();
+
+$sql_ary[] = array(
+	'somedata'		=> $my_string_1,
+	'otherdata'		=> $an_int_1,
+	'moredata'		=> $another_int_1,
+);
+
+$sql_ary[] = array(
+	'somedata'		=> $my_string_2,
+	'otherdata'		=> $an_int_2,
+	'moredata'		=> $another_int_2,
+);
+
+$db->sql_multi_insert(SOME_TABLE, $sql_ary);
+
+ +

sql_in_set():

+ +

The $db->sql_in_set() function should be used for building IN () and NOT IN () constructs. Since (specifically) MySQL tend to be faster if for one value to be compared the = and <> operator is used, we let the DBAL decide what to do. A typical example of doing a positive match against a number of values would be:

+ +
+$sql = 'SELECT *
+	FROM ' . FORUMS_TABLE . '
+	WHERE ' . $db->sql_in_set('forum_id', $forum_ids);
+$db->sql_query($sql);
+
+ +

Based on the number of values in $forum_ids, the query can look differently.

+ +

// SQL Statement if $forum_ids = array(1, 2, 3);

+ +
+SELECT FROM phpbb_forums WHERE forum_id IN (1, 2, 3)
+
+ +

// SQL Statement if $forum_ids = array(1) or $forum_ids = 1

+ +
+SELECT FROM phpbb_forums WHERE forum_id = 1
+
+ +

Of course the same is possible for doing a negative match against a number of values:

+ +
+$sql = 'SELECT *
+	FROM ' . FORUMS_TABLE . '
+	WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true);
+$db->sql_query($sql);
+
+ +

Based on the number of values in $forum_ids, the query can look differently here too.

+ +

// SQL Statement if $forum_ids = array(1, 2, 3);

+ +
+SELECT FROM phpbb_forums WHERE forum_id NOT IN (1, 2, 3)
+
+ +

// SQL Statement if $forum_ids = array(1) or $forum_ids = 1

+ +
+SELECT FROM phpbb_forums WHERE forum_id <> 1
+
+ +

If the given array is empty, an error will be produced.

+ +

sql_build_query():

+ +

The $db->sql_build_query() function is responsible for building sql statements for SELECT and SELECT DISTINCT queries if you need to JOIN on more than one table or retrieve data from more than one table while doing a JOIN. This needs to be used to make sure the resulting statement is working on all supported db's. Instead of explaining every possible combination, I will give a short example:

+ +
+$sql_array = array(
+	'SELECT'	=> 'f.*, ft.mark_time',
+
+	'FROM'		=> array(
+		FORUMS_WATCH_TABLE	=> 'fw',
+		FORUMS_TABLE		=> 'f',
+	),
+
+	'LEFT_JOIN'	=> array(
+		array(
+			'FROM'	=> array(FORUMS_TRACK_TABLE => 'ft'),
+			'ON'	=> 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id',
+		),
+	),
+
+	'WHERE'		=> 'fw.user_id = ' . $user->data['user_id'] . '
+		AND f.forum_id = fw.forum_id',
+
+	'ORDER_BY'	=> 'left_id',
+);
+
+$sql = $db->sql_build_query('SELECT', $sql_array);
+
+ +

The possible first parameter for sql_build_query() is SELECT or SELECT_DISTINCT. As you can see, the logic is pretty self-explaining. For the LEFT_JOIN key, just add another array if you want to join on to tables for example. The added benefit of using this construct is that you are able to easily build the query statement based on conditions - for example the above LEFT_JOIN is only necessary if server side topic tracking is enabled; a slight adjustement would be:

+ +
+$sql_array = array(
+	'SELECT'	=> 'f.*',
+
+	'FROM'		=> array(
+		FORUMS_WATCH_TABLE	=> 'fw',
+		FORUMS_TABLE		=> 'f',
+	),
+
+	'WHERE'		=> 'fw.user_id = ' . $user->data['user_id'] . '
+		AND f.forum_id = fw.forum_id',
+
+	'ORDER_BY'	=> 'left_id',
+);
+
+if ($config['load_db_lastread'])
+{
+	$sql_array['LEFT_JOIN'] = array(
+		array(
+			'FROM'	=> array(FORUMS_TRACK_TABLE => 'ft'),
+			'ON'	=> 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id',
+		),
+	);
+
+	$sql_array['SELECT'] .= ', ft.mark_time ';
+}
+else
+{
+	// Here we read the cookie data
+}
+
+$sql = $db->sql_build_query('SELECT', $sql_array);
+
+ +

2.iv. Optimizations

+ +

Operations in loop definition:

+

Always try to optimize your loops if operations are going on at the comparing part, since this part is executed every time the loop is parsed through. For assignments a descriptive name should be chosen. Example:

+ +

// On every iteration the count function is called

+
+for ($i = 0; $i < count($post_data); $i++)
+{
+	do_something();
+}
+
+ +

// You are able to assign the (not changing) result within the loop itself

+
+for ($i = 0, $size = count($post_data); $i < $size; $i++)
+{
+	do_something();
+}
+
+ +

Use of in_array():

+

Try to avoid using in_array() on huge arrays, and try to not place them into loops if the array to check consist of more than 20 entries. in_array() can be very time consuming and uses a lot of cpu processing time. For little checks it is not noticeable, but if checked against a huge array within a loop those checks alone can take several seconds. If you need this functionality, try using isset() on the arrays keys instead, actually shifting the values into keys and vice versa. A call to isset($array[$var]) is a lot faster than in_array($var, array_keys($array)) for example.

+ + +

2.v. General Guidelines

+ +

General things:

+

Never trust user input (this also applies to server variables as well as cookies).

+

Try to sanitize values returned from a function.

+

Try to sanitize given function variables within your function.

+

The auth class should be used for all authorisation checking.

+

No attempt should be made to remove any copyright information (either contained within the source or displayed interactively when the source is run/compiled), neither should the copyright information be altered in any way (it may be added to).

+ +

Variables:

+

Make use of the \phpbb\request\request class for everything.

+

The $request->variable() method determines the type to set from the second parameter (which determines the default value too). If you need to get a scalar variable type, you need to tell this the variable() method explicitly. Examples:

+ +

// Old method, do not use it

+
+$start = (isset($HTTP_GET_VARS['start'])) ? intval($HTTP_GET_VARS['start']) : intval($HTTP_POST_VARS['start']);
+$submit = (isset($HTTP_POST_VARS['submit'])) ? true : false;
+
+ +

// Use request var and define a default variable (use the correct type)

+
+$start = $request->variable('start', 0);
+$submit = $request->is_set_post('submit');
+
+ +

// $start is an int, the following use of $request->variable() therefore is not allowed

+
+$start = $request->variable('start', '0');
+
+ +

// Getting an array, keys are integers, value defaults to 0

+
+$mark_array = $request->variable('mark', array(0));
+
+ +

// Getting an array, keys are strings, value defaults to 0

+
+$action_ary = $request->variable('action', array('' => 0));
+
+ +

Login checks/redirection:

+

To show a forum login box use login_forum_box($forum_data), else use the login_box() function.

+ +

$forum_data should contain at least the forum_id and forum_password fields. If the field forum_name is available, then it is displayed on the forum login page.

+ +

The login_box() function can have a redirect as the first parameter. As a thumb of rule, specify an empty string if you want to redirect to the users current location, else do not add the $SID to the redirect string (for example within the ucp/login we redirect to the board index because else the user would be redirected to the login screen).

+ +

Sensitive Operations:

+

For sensitive operations always let the user confirm the action. For the confirmation screens, make use of the confirm_box() function.

+ +

Altering Operations:

+

For operations altering the state of the database, for instance posting, always verify the form token, unless you are already using confirm_box(). To do so, make use of the add_form_key() and check_form_key() functions.

+
+	add_form_key('my_form');
+
+	if ($submit)
+	{
+		if (!check_form_key('my_form'))
+		{
+			trigger_error('FORM_INVALID');
+		}
+	}
+
+ +

The string passed to add_form_key() needs to match the string passed to check_form_key(). Another requirement for this to work correctly is that all forms include the {S_FORM_TOKEN} template variable.

+ + +

Sessions:

+

Sessions should be initiated on each page, as near the top as possible using the following code:

+ +
+$user->session_begin();
+$auth->acl($user->data);
+$user->setup();
+
+ +

The $user->setup() call can be used to pass on additional language definition and a custom style (used in viewforum).

+ +

Errors and messages:

+

All messages/errors should be outputted by calling trigger_error() using the appropriate message type and language string. Example:

+ +
+trigger_error('NO_FORUM');
+
+ +
+trigger_error($user->lang['NO_FORUM']);
+
+ +
+trigger_error('NO_MODE', E_USER_ERROR);
+
+ +

Url formatting

+ +

All urls pointing to internal files need to be prepended by the $phpbb_root_path variable. Within the administration control panel all urls pointing to internal files need to be prepended by the $phpbb_admin_path variable. This makes sure the path is always correct and users being able to just rename the admin folder and the acp still working as intended (though some links will fail and the code need to be slightly adjusted).

+ +

The append_sid() function from 2.0.x is available too, though it does not handle url alterations automatically. Please have a look at the code documentation if you want to get more details on how to use append_sid(). A sample call to append_sid() can look like this:

+ +
+append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&amp;g=' . $row['group_id'])
+
+ +

General function usage:

+ +

Some of these functions are only chosen over others because of personal preference and have no benefit other than maintaining consistency throughout the code.

+ +
    +
  • +

    Use strpos instead of strstr

    +
  • +
  • +

    Use else if instead of elseif

    +
  • +
  • +

    Use false (lowercase) instead of FALSE

    +
  • +
  • +

    Use true (lowercase) instead of TRUE

    +
  • +
+ +

Exiting

+ +

Your page should either call page_footer() in the end to trigger output through the template engine and terminate the script, or alternatively at least call the exit_handler(). That call is necessary because it provides a method for external applications embedding phpBB to be called at the end of the script.

+ +

2.vi. Restrictions on the Use of PHP

+ +

Dynamic code execution:

+ +

Never execute dynamic PHP code (generated or in a constant string) using any of the following PHP functions:

+ +
    +
  • eval
  • +
  • create_function
  • +
  • preg_replace with the e modifier in the pattern
  • +
+ +

If absolutely necessary a file should be created, and a mechanism for creating this file prior to running phpBB should be provided as a setup process.

+ +

The e modifier in preg_replace can be replaced by preg_replace_callback and objects to encapsulate state that is needed in the callback code.

+ +

Other functions, operators, statements and keywords:

+ +

The following PHP statements should also not be used in phpBB:

+ +
    +
  • goto
  • +
+ +
+ + + +
+
+ +
+ +

3. Styling

+
+
+ +
+

3.i. Style Config Files

+

Style cfg files are simple name-value lists with the information necessary for installing a style. The important part of the style configuration file is assigning an unique name.

+
+# General Information about this style
+name = prosilver_duplicate
+copyright = © phpBB Limited, 2007
+style_version = 3.2.0
+phpbb_version = 3.2.0
+
+# Defining a different template bitfield
+# template_bitfield = lNg=
+
+# Parent style
+# Set value to empty or to this style's name if this style does not have a parent style
+parent = prosilver
+
+

3.2. General Styling Rules

+

Templates should be produced in a consistent manner. Where appropriate they should be based off an existing copy, e.g. index, viewforum or viewtopic (the combination of which implement a range of conditional and variable forms). Please also note that the indentation and coding guidelines also apply to templates where possible.

+ +

The outer table class forumline has gone and is replaced with tablebg.

+

When writing <table> the order <table class="" cellspacing="" cellpadding="" border="" align=""> creates consistency and allows everyone to easily see which table produces which "look". The same applies to most other tags for which additional parameters can be set, consistency is the major aim here.

+

Each block level element should be indented by one tab, same for tabular elements, e.g. <tr> <td> etc., whereby the intendiation of <table> and the following/ending <tr> should be on the same line. This applies not to div elements of course.

+

Don't use <span> more than is essential ... the CSS is such that text sizes are dependent on the parent class. So writing <span class="gensmall"><span class="gensmall">TEST</span></span> will result in very very small text. Similarly don't use span at all if another element can contain the class definition, e.g.

+ +
+<td><span class="gensmall">TEST</span></td>
+
+ +

can just as well become:

+
+<td class="gensmall">TEST</td>
+
+ +

Try to match text class types with existing useage, e.g. don't use the nav class where viewtopic uses gensmall for example.

+ +

Row colours/classes are now defined by the template, use an IF S_ROW_COUNT switch, see viewtopic or viewforum for an example.

+ +

Remember block level ordering is important.

+ +

Use a standard cellpadding of 2 and cellspacing of 0 on outer tables. Inner tables can vary from 0 to 3 or even 4 depending on the need.

+ +

Use div container/css for styling and table for data representation.

+ +

The separate catXXXX and thXXX classes are gone. When defining a header cell just use <th> rather than <th class="thHead"> etc. Similarly for cat, don't use <td class="catLeft"> use <td class="cat"> etc.

+ +

Try to retain consistency of basic layout and class useage, i.e. _EXPLAIN text should generally be placed below the title it explains, e.g. {L_POST_USERNAME}<br /><span class="gensmall">{L_POST_USERNAME_EXPLAIN}</span> is the typical way of handling this ... there may be exceptions and this isn't a hard and fast rule.

+ +

Try to keep template conditional and other statements tabbed in line with the block to which they refer.

+ +

this is correct

+
+<!-- BEGIN test -->
+	<tr>
+		<td>{test.TEXT}</td>
+	</tr>
+<!-- END test -->
+
+ +

this is also correct:

+
+<!-- BEGIN test -->
+<tr>
+	<td>{test.TEXT}</td>
+</tr>
+<!-- END test -->
+
+ +

it gives immediate feedback on exactly what is looping - decide which way to use based on the readability.

+ +
+ + + +
+
+ +
+ +

4. Templating

+
+
+ +
+

4.i. General Templating

+ +

File naming

+

Firstly templates now take the suffix ".html" rather than ".tpl". This was done simply to make the lives of some people easier wrt syntax highlighting, etc.

+ +

Variables

+

All template variables should be named appropriately (using underscores for spaces), language entries should be prefixed with L_, system data with S_, urls with U_, javascript urls with UA_, language to be put in javascript statements with LA_, all other variables should be presented 'as is'.

+ +

L_* template variables are automatically mapped to the corresponding language entry if the code does not set (and therefore overwrite) this variable specifically and if the language entry exists. For example {L_USERNAME} maps to $user->lang['USERNAME']. The LA_* template variables are handled within the same way, but properly escaped so they can be put in javascript code. This should reduce the need to assign loads of new language variables in MODifications. +

+ +

Blocks/Loops

+

The basic block level loop remains and takes the form:

+
+<!-- BEGIN loopname -->
+	markup, {loopname.X_YYYYY}, etc.
+<!-- END loopname -->
+
+ +

A bit later loops will be explained further. To not irritate you we will explain conditionals as well as other statements first.

+ +

Including files

+

Something that existed in 2.0.x which no longer exists in 3.x is the ability to assign a template to a variable. This was used (for example) to output the jumpbox. Instead (perhaps better, perhaps not but certainly more flexible) we now have INCLUDE. This takes the simple form:

+ +
+<!-- INCLUDE filename -->
+
+ +

You will note in the 3.x templates the major sources start with <!-- INCLUDE overall_header.html --> or <!-- INCLUDE simple_header.html -->, etc. In 2.0.x control of "which" header to use was defined entirely within the code. In 3.x the template designer can output what they like. Note that you can introduce new templates (i.e. other than those in the default set) using this system and include them as you wish ... perhaps useful for a common "menu" bar or some such. No need to modify loads of files as with 2.0.x.

+ +

Added in 3.0.6 is the ability to include a file using a template variable to specify the file, this functionality only works for root variables (i.e. not block variables).

+
+<!-- INCLUDE {FILE_VAR} -->
+
+ +

Template defined variables can also be utilised.

+ +
+<!-- DEFINE $SOME_VAR = 'my_file.html' -->
+<!-- INCLUDE {$SOME_VAR} -->
+
+ +

PHP

+

A contentious decision has seen the ability to include PHP within the template introduced. This is achieved by enclosing the PHP within relevant tags:

+ +
+<!-- PHP -->
+	echo "hello!";
+<!-- ENDPHP -->
+
+ +

You may also include PHP from an external file using:

+ +
+<!-- INCLUDEPHP somefile.php -->
+
+ +

it will be included and executed inline.

A note, it is very much encouraged that template designers do not include PHP. The ability to include raw PHP was introduced primarily to allow end users to include banner code, etc. without modifying multiple files (as with 2.0.x). It was not intended for general use ... hence www.phpbb.com will not make available template sets which include PHP. And by default templates will have PHP disabled (the admin will need to specifically activate PHP for a template).

+ +

Conditionals/Control structures

+

The most significant addition to 3.x are conditions or control structures, "if something then do this else do that". The system deployed is very similar to Smarty. This may confuse some people at first but it offers great potential and great flexibility with a little imagination. In their most simple form these constructs take the form:

+ +
+<!-- IF expr -->
+	markup
+<!-- ENDIF -->
+
+ +

expr can take many forms, for example:

+ +
+<!-- IF loop.S_ROW_COUNT is even -->
+	markup
+<!-- ENDIF -->
+
+ +

This will output the markup if the S_ROW_COUNT variable in the current iteration of loop is an even value (i.e. the expr is TRUE). You can use various comparison methods (standard as well as equivalent textual versions noted in square brackets) including (not, or, and, eq, neq, is should be used if possible for better readability):

+ +
+== [eq]
+!= [neq, ne]
+<> (same as !=)
+!== (not equivalent in value and type)
+=== (equivalent in value and type)
+> [gt]
+< [lt]
+>= [gte]
+<= [lte]
+&& [and]
+|| [or]
+% [mod]
+! [not]
++
+-
+*
+/
+,
+<< (bitwise shift left)
+>> (bitwise shift right)
+| (bitwise or)
+^ (bitwise xor)
+& (bitwise and)
+~ (bitwise not)
+is (can be used to join comparison operations)
+
+ +

Basic parenthesis can also be used to enforce good old BODMAS rules. Additionally some basic comparison types are defined:

+ +
+even
+odd
+div
+
+ +

Beyond the simple use of IF you can also do a sequence of comparisons using the following:

+ +
+<!-- IF expr1 -->
+	markup
+<!-- ELSEIF expr2 -->
+	markup
+	.
+	.
+	.
+<!-- ELSEIF exprN -->
+	markup
+<!-- ELSE -->
+	markup
+<!-- ENDIF -->
+
+ +

Each statement will be tested in turn and the relevant output generated when a match (if a match) is found. It is not necessary to always use ELSEIF, ELSE can be used alone to match "everything else".

So what can you do with all this? Well take for example the colouration of rows in viewforum. In 2.0.x row colours were predefined within the source as either row color1, row color2 or row class1, row class2. In 3.x this is moved to the template, it may look a little daunting at first but remember control flows from top to bottom and it's not too difficult:

+ +
+<table>
+	<!-- IF loop.S_ROW_COUNT is even -->
+		<tr class="row1">
+	<!-- ELSE -->
+		<tr class="row2">
+	<!-- ENDIF -->
+	<td>HELLO!</td>
+</tr>
+</table>
+
+ +

This will cause the row cell to be output using class row1 when the row count is even, and class row2 otherwise. The S_ROW_COUNT parameter gets assigned to loops by default. Another example would be the following:

+ +
+<table>
+	<!-- IF loop.S_ROW_COUNT > 10 -->
+		<tr bgcolor="#FF0000">
+	<!-- ELSEIF loop.S_ROW_COUNT > 5 -->
+		<tr bgcolor="#00FF00">
+	<!-- ELSEIF loop.S_ROW_COUNT > 2 -->
+		<tr bgcolor="#0000FF">
+	<!-- ELSE -->
+		<tr bgcolor="#FF00FF">
+	<!-- ENDIF -->
+	<td>hello!</td>
+</tr>
+</table>
+
+ +

This will output the row cell in purple for the first two rows, blue for rows 2 to 5, green for rows 5 to 10 and red for remainder. So, you could produce a "nice" gradient effect, for example.

What else can you do? Well, you could use IF to do common checks on for example the login state of a user:

+ +
+<!-- IF S_USER_LOGGED_IN -->
+	markup
+<!-- ENDIF -->
+
+ +

This replaces the existing (fudged) method in 2.0.x using a zero length array and BEGIN/END.

+ +

Extended syntax for Blocks/Loops

+ +

Back to our loops - they had been extended with the following additions. Firstly you can set the start and end points of the loop. For example:

+ +
+<!-- BEGIN loopname(2) -->
+	markup
+<!-- END loopname -->
+
+ +

Will start the loop on the third entry (note that indexes start at zero). Extensions of this are: +

+loopname(2): Will start the loop on the 3rd entry
+loopname(-2): Will start the loop two entries from the end
+loopname(3,4): Will start the loop on the fourth entry and end it on the fifth
+loopname(3,-4): Will start the loop on the fourth entry and end it four from last
+

+ +

A further extension to begin is BEGINELSE:

+ +
+<!-- BEGIN loop -->
+	markup
+<!-- BEGINELSE -->
+	markup
+<!-- END loop -->
+
+ +

This will cause the markup between BEGINELSE and END to be output if the loop contains no values. This is useful for forums with no topics (for example) ... in some ways it replaces "bits of" the existing "switch_" type control (the rest being replaced by conditionals).

+ +

Another way of checking if a loop contains values is by prefixing the loops name with a dot:

+ +
+<!-- IF .loop -->
+	<!-- BEGIN loop -->
+		markup
+	<!-- END loop -->
+<!-- ELSE -->
+	markup
+<!-- ENDIF -->
+
+ +

You are even able to check the number of items within a loop by comparing it with values within the IF condition:

+ +
+<!-- IF .loop > 2 -->
+	<!-- BEGIN loop -->
+		markup
+	<!-- END loop -->
+<!-- ELSE -->
+	markup
+<!-- ENDIF -->
+
+ +

Nesting loops cause the conditionals needing prefixed with all loops from the outer one to the inner most. An illustration of this:

+ +
+<!-- BEGIN firstloop -->
+	{firstloop.MY_VARIABLE_FROM_FIRSTLOOP}
+
+	<!-- BEGIN secondloop -->
+		{firstloop.secondloop.MY_VARIABLE_FROM_SECONDLOOP}
+	<!-- END secondloop -->
+<!-- END firstloop -->
+
+ +

Sometimes it is necessary to break out of nested loops to be able to call another loop within the current iteration. This sounds a little bit confusing and it is not used very often. The following (rather complex) example shows this quite good - it also shows how you test for the first and last row in a loop (i will explain the example in detail further down):

+ +
+<!-- BEGIN l_block1 -->
+	<!-- IF l_block1.S_SELECTED -->
+		<strong>{l_block1.L_TITLE}</strong>
+		<!-- IF S_PRIVMSGS -->
+
+			<!-- the ! at the beginning of the loop name forces the loop to be not a nested one of l_block1 -->
+			<!-- BEGIN !folder -->
+				<!-- IF folder.S_FIRST_ROW -->
+					<ul class="nav">
+				<!-- ENDIF -->
+
+				<li><a href="{folder.U_FOLDER}">{folder.FOLDER_NAME}</a></li>
+
+				<!-- IF folder.S_LAST_ROW -->
+					</ul>
+				<!-- ENDIF -->
+			<!-- END !folder -->
+
+		<!-- ENDIF -->
+
+		<ul class="nav">
+		<!-- BEGIN l_block2 -->
+			<li>
+				<!-- IF l_block1.l_block2.S_SELECTED -->
+					<strong>{l_block1.l_block2.L_TITLE}</strong>
+				<!-- ELSE -->
+					<a href="{l_block1.l_block2.U_TITLE}">{l_block1.l_block2.L_TITLE}</a>
+				<!-- ENDIF -->
+			</li>
+		<!-- END l_block2 -->
+		</ul>
+	<!-- ELSE -->
+		<a class="nav" href="{l_block1.U_TITLE}">{l_block1.L_TITLE}</a>
+	<!-- ENDIF -->
+<!-- END l_block1 -->
+
+ +

Let us first concentrate on this part of the example:

+ +
+<!-- BEGIN l_block1 -->
+	<!-- IF l_block1.S_SELECTED -->
+		markup
+	<!-- ELSE -->
+		<a class="nav" href="{l_block1.U_TITLE}">{l_block1.L_TITLE}</a>
+	<!-- ENDIF -->
+<!-- END l_block1 -->
+
+ +

Here we open the loop l_block1 and do some things if the value S_SELECTED within the current loop iteration is true, else we write the blocks link and title. Here, you see {l_block1.L_TITLE} referenced - you remember that L_* variables get automatically assigned the corresponding language entry? This is true, but not within loops. The L_TITLE variable within the loop l_block1 is assigned within the code itself.

+ +

Let's have a closer look at the markup:

+ +
+<!-- BEGIN l_block1 -->
+.
+.
+	<!-- IF S_PRIVMSGS -->
+
+		<!-- BEGIN !folder -->
+			<!-- IF folder.S_FIRST_ROW -->
+				<ul class="nav">
+			<!-- ENDIF -->
+
+			<li><a href="{folder.U_FOLDER}">{folder.FOLDER_NAME}</a></li>
+
+			<!-- IF folder.S_LAST_ROW -->
+				</ul>
+			<!-- ENDIF -->
+		<!-- END !folder -->
+
+	<!-- ENDIF -->
+.
+.
+<!-- END l_block1 -->
+
+ +

The <!-- IF S_PRIVMSGS --> statement clearly checks a global variable and not one within the loop, since the loop is not given here. So, if S_PRIVMSGS is true we execute the shown markup. Now, you see the <!-- BEGIN !folder --> statement. The exclamation mark is responsible for instructing the template engine to iterate through the main loop folder. So, we are now within the loop folder - with <!-- BEGIN folder --> we would have been within the loop l_block1.folder automatically as is the case with l_block2:

+ +
+<!-- BEGIN l_block1 -->
+.
+.
+	<ul class="nav">
+	<!-- BEGIN l_block2 -->
+		<li>
+			<!-- IF l_block1.l_block2.S_SELECTED -->
+				<strong>{l_block1.l_block2.L_TITLE}</strong>
+			<!-- ELSE -->
+				<a href="{l_block1.l_block2.U_TITLE}">{l_block1.l_block2.L_TITLE}</a>
+			<!-- ENDIF -->
+		</li>
+	<!-- END l_block2 -->
+	</ul>
+.
+.
+<!-- END l_block1 -->
+
+ +

You see the difference? The loop l_block2 is a member of the loop l_block1 but the loop folder is a main loop.

+ +

Now back to our folder loop:

+ +
+<!-- IF folder.S_FIRST_ROW -->
+	<ul class="nav">
+<!-- ENDIF -->
+
+<li><a href="{folder.U_FOLDER}">{folder.FOLDER_NAME}</a></li>
+
+<!-- IF folder.S_LAST_ROW -->
+	</ul>
+<!-- ENDIF -->
+
+ +

You may have wondered what the comparison to S_FIRST_ROW and S_LAST_ROW is about. If you haven't guessed already - it is checking for the first iteration of the loop with S_FIRST_ROW and the last iteration with S_LAST_ROW. This can come in handy quite often if you want to open or close design elements, like the above list. Let us imagine a folder loop build with three iterations, it would go this way:

+ +
+<ul class="nav"> <!-- written on first iteration -->
+	<li>first element</li> <!-- written on first iteration -->
+	<li>second element</li> <!-- written on second iteration -->
+	<li>third element</li> <!-- written on third iteration -->
+</ul> <!-- written on third iteration -->
+
+ +

As you can see, all three elements are written down as well as the markup for the first iteration and the last one. Sometimes you want to omit writing the general markup - for example:

+ +
+<!-- IF folder.S_FIRST_ROW -->
+	<ul class="nav">
+<!-- ELSEIF folder.S_LAST_ROW -->
+	</ul>
+<!-- ELSE -->
+	<li><a href="{folder.U_FOLDER}">{folder.FOLDER_NAME}</a></li>
+<!-- ENDIF -->
+
+ +

would result in the following markup:

+ +
+<ul class="nav"> <!-- written on first iteration -->
+	<li>second element</li> <!-- written on second iteration -->
+</ul> <!-- written on third iteration -->
+
+ +

Just always remember that processing is taking place from top to bottom.

+ +

Forms

+

If a form is used for a non-trivial operation (i.e. more than a jumpbox), then it should include the {S_FORM_TOKEN} template variable.

+
+<form method="post" id="mcp" action="{U_POST_ACTION}">
+
+	<fieldset class="submit-buttons">
+		<input type="reset" value="{L_RESET}" name="reset" class="button2" /> 
+		<input type="submit" name="action[add_warning]" value="{L_SUBMIT}" class="button1" />
+		{S_FORM_TOKEN}
+	</fieldset>
+</form>
+		

+ +

4.ii. Styles Tree

+

When basing a new style on an existing one, it is not necessary to provide all the template files. By declaring the base style name in the parent field in the style configuration file, the style can be set to reuse template files from the parent style.

+ +

The effect of doing so is that the template engine will use the template files in the new style where they exist, but fall back to files in the parent style otherwise.

+ +

We strongly encourage the use of parent styles for styles based on the bundled styles, as it will ease the update procedure.

+ +
+# General Information about this style
+name = Custom Style
+copyright = © phpBB Limited, 2007
+style_version = 3.2.0-b1
+phpbb_version = 3.2.0-b1
+
+# Defining a different template bitfield
+# template_bitfield = lNg=
+
+# Parent style
+# Set value to empty or to this style's name if this style does not have a parent style
+parent = prosilver
+		
+ +

4.iii. Template Events

+

Template events must follow this format: <!-- EVENT event_name -->

+

Using the above example, files named event_name.html located within extensions will be injected into the location of the event.

+ +

Template event naming guidelines:

+
    +
  • An event name must be all lowercase, with each word separated by an underscore.
  • +
  • An event name must briefly describe the location and purpose of the event.
  • +
  • + An event name must end with one of the following suffixes: +
      +
    • _prepend - This event adds an item to the beginning of a block of related items, or adds to the beginning of individual items in a block.
    • +
    • _append - This event adds an item to the end of a block of related items, or adds to the end of individual items in a block.
    • +
    • _before - This event adds content directly before the specified block
    • +
    • _after - This event adds content directly after the specified block
    • +
    +
  • +
+ +

Template event documentation

+

Events must be documented in phpBB/docs/events.md in alphabetical order based on the event name. The format is as follows:

+ +
  • An event found in only one template file: +
    event_name
    +===
    +* Location: styles/<style_name>/template/filename.html
    +* Purpose: A brief description of what this event should be used for.
    +This may span multiple lines.
    +* Since: Version since when the event was added
    +
  • +
  • An event found in multiple template files: +
    event_name
    +===
    +* Locations:
    +    + first/file/path.html
    +    + second/file/path.html
    +* Purpose: Same as above.
    +* Since: 3.2.0-b1
    +
    +
  • An event that is found multiple times in a file should have the number of instances in parenthesis next to the filename. +
    event_name
    +===
    +* Locations:
    +    + first/file/path.html (2)
    +    + second/file/path.html
    +* Purpose: Same as above.
    +* Since: 3.2.0-b1
    +
  • +
  • An actual example event documentation: +
    forumlist_body_last_post_title_prepend
    +====
    +* Locations:
    +    + styles/prosilver/template/forumlist_body.html
    +    + styles/subsilver2/template/forumlist_body.html
    +* Purpose: Add content before the post title of the latest post in a forum on the forum list.
    +* Since: 3.2.0-a1
    +

+ +
+ + + +
+
+ +
+ + + +

5. Character Sets and Encodings

+ +
+
+ +
+ + + +

What are Unicode, UCS and UTF-8?

+

The Universal Character Set (UCS) described in ISO/IEC 10646 consists of a large amount of characters. Each of them has a unique name and a code point which is an integer number. Unicode - which is an industry standard - complements the Universal Character Set with further information about the characters' properties and alternative character encodings. More information on Unicode can be found on the Unicode Consortium's website. One of the Unicode encodings is the 8-bit Unicode Transformation Format (UTF-8). It encodes characters with up to four bytes aiming for maximum compatibility with the American Standard Code for Information Interchange which is a 7-bit encoding of a relatively small subset of the UCS.

+ +

phpBB's use of Unicode

+

Unfortunately PHP does not faciliate the use of Unicode prior to version 6. Most functions simply treat strings as sequences of bytes assuming that each character takes up exactly one byte. This behaviour still allows for storing UTF-8 encoded text in PHP strings but many operations on strings have unexpected results. To circumvent this problem we have created some alternative functions to PHP's native string operations which use code points instead of bytes. These functions can be found in /includes/utf/utf_tools.php. They are also covered in the phpBB3 Sourcecode Documentation. A lot of native PHP functions still work with UTF-8 as long as you stick to certain restrictions. For example explode still works as long as the first and the last character of the delimiter string are ASCII characters.

+ +

phpBB only uses the ASCII and the UTF-8 character encodings. Still all Strings are UTF-8 encoded because ASCII is a subset of UTF-8. The only exceptions to this rule are code sections which deal with external systems which use other encodings and character sets. Such external data should be converted to UTF-8 using the utf8_recode() function supplied with phpBB. It supports a variety of other character sets and encodings, a full list can be found below.

+ +

With $request->variable() you can either allow all UCS characters in user input or restrict user input to ASCII characters. This feature is controlled by the method's third parameter called $multibyte. You should allow multibyte characters in posts, PMs, topic titles, forum names, etc. but it's not necessary for internal uses like a $mode variable which should only hold a predefined list of ASCII strings anyway.

+ +
+// an input string containing a multibyte character
+$_REQUEST['multibyte_string'] = 'Käse';
+
+// print request variable as a UTF-8 string allowing multibyte characters
+echo $request->variable('multibyte_string', '', true);
+// print request variable as ASCII string
+echo $request->variable('multibyte_string', '');
+
+ +

This code snippet will generate the following output:

+ +
+Käse
+K??se
+
+ +

Case Folding

+ +

Case insensitive comparison of strings is no longer possible with strtolower or strtoupper as some characters have multiple lower case or multiple upper case forms depending on their position in a word. The utf8_strtolower and the utf8_strtoupper functions suffer from the same problem so they can only be used to display upper/lower case versions of a string but they cannot be used for case insensitive comparisons either. So instead you should use case folding which gives you a case insensitive version of the string which can be used for case insensitive comparisons. An NFC normalized string can be case folded using utf8_case_fold_nfc().

+ +

// Bad - The strings might be the same even if strtolower differs

+ +
+if (strtolower($string1) == strtolower($string2))
+{
+	echo '$string1 and $string2 are equal or differ in case';
+}
+
+ +

// Good - Case folding is really case insensitive

+ +
+if (utf8_case_fold_nfc($string1) == utf8_case_fold_nfc($string2))
+{
+	echo '$string1 and $string2 are equal or differ in case';
+}
+
+ +

Confusables Detection

+ +

phpBB offers a special method utf8_clean_string which can be used to make sure string identifiers are unique. This method uses Normalization Form Compatibility Composition (NFKC) instead of NFC and replaces similarly looking characters with a particular representative of the equivalence class. This method is currently used for usernames and group names to avoid confusion with similarly looking names.

+ +
+ + + +
+
+ +
+ +

6. Translation (i18n/L10n) Guidelines

+ +
+
+ +
+ +

6.i. Standardisation

+ +

Reason:

+ +

phpBB is one of the most translated open-source projects, with the current stable version being available in over 60 localisations. Whilst the ad hoc approach to the naming of language packs has worked, for phpBB3 and beyond we hope to make this process saner which will allow for better interoperation with current and future web browsers.

+ +

Encoding:

+ +

With phpBB3, the output encoding for the forum in now UTF-8, a Universal Character Encoding by the Unicode Consortium that is by design a superset to US-ASCII and ISO-8859-1. By using one character set which simultaenously supports all scripts which previously would have required different encodings (eg: ISO-8859-1 to ISO-8859-15 (Latin, Greek, Cyrillic, Thai, Hebrew, Arabic); GB2312 (Simplified Chinese); Big5 (Traditional Chinese), EUC-JP (Japanese), EUC-KR (Korean), VISCII (Vietnamese); et cetera), we remove the need to convert between encodings and improves the accessibility of multilingual forums.

+ +

The impact is that the language files for phpBB must now also be encoded as UTF-8, with a caveat that the files must not contain a BOM for compatibility reasons with non-Unicode aware versions of PHP. For those with forums using the Latin character set (ie: most European languages), this change is transparent since UTF-8 is superset to US-ASCII and ISO-8859-1.

+ +

Language Tag:

+ +

The IETF recently published RFC 4646 for tags used to identify languages, which in combination with RFC 4647 obseletes the older RFC 3006 and older-still RFC 1766. RFC 4646 uses ISO 639-1/ISO 639-2, ISO 3166-1 alpha-2, ISO 15924 and UN M.49 to define a language tag. Each complete tag is composed of subtags which are not case sensitive and can also be empty.

+ +

Ordering of the subtags in the case that they are all non-empty is: language-script-region-variant-extension-privateuse. Should any subtag be empty, its corresponding hyphen would also be ommited. Thus, the language tag for English will be en and not en-----.

+ +

Most language tags consist of a two- or three-letter language subtag (from ISO 639-1/ISO 639-2). Sometimes, this is followed by a two-letter or three-digit region subtag (from ISO 3166-1 alpha-2 or UN M.49). Some examples are:

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Examples of various possible language tags as described by RFC 4646 and RFC 4647
Language tagDescriptionComponent subtags
enEnglishlanguage
masMasailanguage
fr-CAFrench as used in Canadalanguage+region
en-833English as used in the Isle of Manlanguage+region
zh-HansChinese written with Simplified scriptlanguage+script
zh-Hant-HKChinese written with Traditional script as used in Hong Konglanguage+script+region
de-AT-1996German as used in Austria with 1996 orthographylanguage+region+variant
+ +

The ultimate aim of a language tag is to convey the needed useful distingushing information, whilst keeping it as short as possible. So for example, use en, fr and ja as opposed to en-GB, fr-FR and ja-JP, since we know English, French and Japanese are the native language of Great Britain, France and Japan respectively.

+ +

Next is the ISO 15924 language script code and when one should or shouldn't use it. For example, whilst en-Latn is syntaxically correct for describing English written with Latin script, real world English writing is more-or-less exclusively in the Latin script. For such languages like English that are written in a single script, the IANA Language Subtag Registry has a "Suppress-Script" field meaning the script code should be ommitted unless a specific language tag requires a specific script code. Some languages are written in more than one script and in such cases, the script code is encouraged since an end-user may be able to read their language in one script, but not the other. Some examples are:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Examples of using a language subtag in combination with a script subtag
Language tagDescriptionComponent subtags
en-BraiEnglish written in Braille scriptlanguage+script
en-DsrtEnglish written in Deseret (Mormon) scriptlanguage+script
sr-LatnSerbian written in Latin scriptlanguage+script
sr-CyrlSerbian written in Cyrillic scriptlanguage+script
mn-MongMongolian written in Mongolian scriptlanguage+script
mn-CyrlMongolian written in Cyrillic scriptlanguage+script
mn-PhagMongolian written in Phags-pa scriptlanguage+script
az-Cyrl-AZAzerbaijani written in Cyrillic script as used in Azerbaijanlanguage+script+region
az-Latn-AZAzerbaijani written in Latin script as used in Azerbaijanlanguage+script+region
az-Arab-IRAzerbaijani written in Arabic script as used in Iranlanguage+script+region
+ +

Usage of the three-digit UN M.49 code over the two-letter ISO 3166-1 alpha-2 code should hapen if a macro-geographical entity is required and/or the ISO 3166-1 alpha-2 is ambiguous.

+ +

Examples of English using marco-geographical regions:

+ + + + + + + + + + + + + + + + + + + + + + + +
Coding for English using macro-geographical regions (examples for English of ISO 3166-1 alpha-2 vs. UN M.49 code)
ISO 639-1/ISO 639-2 + ISO 3166-1 alpha-2ISO 639-1/ISO 639-2 + UN M.49 (Example macro regions)
en-AU
English as used in Australia
en-053
English as used in Australia & New Zealand
en-009
English as used in Oceania
en-NZ
English as used in New Zealand
en-FJ
English as used in Fiji
en-054
English as used in Melanesia
+ +

Examples of Spanish using marco-geographical regions:

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Coding for Spanish macro-geographical regions (examples for Spanish of ISO 3166-1 alpha-2 vs. UN M.49 code)
ISO 639-1/ISO 639-2 + ISO 3166-1 alpha-2ISO 639-1/ISO 639-2 + UN M.49 (Example macro regions)
es-PR
Spanish as used in Puerto Rico
es-419
Spanish as used in Latin America & the Caribbean
es-019
Spanish as used in the Americas
es-HN
Spanish as used in Honduras
es-AR
Spanish as used in Argentina
es-US
Spanish as used in United States of America
es-021
Spanish as used in North America
+ +

Example of where the ISO 3166-1 alpha-2 is ambiguous and why UN M.49 might be preferred:

+ + + + + + + + + + + + + + + + + + + + + +
Coding for ambiguous ISO 3166-1 alpha-2 regions
CS assignment pre-1994CS assignment post-1994
+
+
CS
Czechoslovakia (ISO 3166-1)
+
200
Czechoslovakia (UN M.49)
+
+
+
+
CS
Serbian & Montenegro (ISO 3166-1)
+
891
Serbian & Montenegro (UN M.49)
+
+
+
+
CZ
Czech Republic (ISO 3166-1)
+
203
Czech Republic (UN M.49)
+
+
+
+
SK
Slovakia (ISO 3166-1)
+
703
Slovakia (UN M.49)
+
+
+
+
RS
Serbia (ISO 3166-1)
+
688
Serbia (UN M.49)
+
+
+
+
ME
Montenegro (ISO 3166-1)
+
499
Montenegro (UN M.49)
+
+
+ +

Macro-languages & Topolects:

+ +

RFC 4646 anticipates features which shall be available in (currently draft) ISO 639-3 which aims to provide as complete enumeration of languages as possible, including living, extinct, ancient and constructed languages, whether majour, minor or unwritten. A new feature of ISO 639-3 compared to the previous two revisions is the concept of macrolanguages where Arabic and Chinese are two such examples. In such cases, their respective codes of ar and zh is very vague as to which dialect/topolect is used or perhaps some terse classical variant which may be difficult for all but very educated users. For such macrolanguages, it is recommended that the sub-language tag is used as a suffix to the macrolanguage tag, eg:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Macrolanguage subtag + sub-language subtag examples
Language tagDescriptionComponent subtags
zh-cmnMandarin (Putonghau/Guoyu) Chinesemacrolanguage+sublanguage
zh-yueYue (Cantonese) Chinesemacrolanguage+sublanguage
zh-cmn-HansMandarin (Putonghau/Guoyu) Chinese written in Simplified scriptmacrolanguage+sublanguage+script
zh-cmn-HantMandarin (Putonghau/Guoyu) Chinese written in Traditional scriptmacrolanguage+sublanguage+script
zh-nan-Latn-TWMinnan (Hoklo) Chinese written in Latin script (POJ Romanisation) as used in Taiwanmacrolanguage+sublanguage+script+region
+ +

6.ii. Other considerations

+ +

Normalisation of language tags for phpBB:

+ +

For phpBB, the language tags are not used in their raw form and instead converted to all lower-case and have the hyphen - replaced with an underscore _ where appropriate, with some examples below:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Language tag normalisation examples
Raw language tagDescriptionValue of USER_LANG
in ./common.php
Language pack directory
name in /language/
enBritish Englishenen
de-ATGerman as used in Austriade-atde_at
es-419Spanish as used in Latin America & Caribbeanen-419en_419
zh-yue-Hant-HKCantonese written in Traditional script as used in Hong Kongzh-yue-hant-hkzh_yue_hant_hk
+ +

How to use iso.txt:

+ +

The iso.txt file is a small UTF-8 encoded plain-text file which consists of three lines:

+ +
    +
  1. Language's English name
  2. +
  3. Language's local name
  4. +
  5. Authors information
  6. +
+ +

iso.txt is automatically generated by the language pack submission system on phpBB.com. You don't have to create this file yourself if you plan on releasing your language pack on phpBB.com, but do keep in mind that phpBB itself does require this file to be present.

+ +

Because language tags themselves are meant to be machine read, they can be rather obtuse to humans and why descriptive strings as provided by iso.txt are needed. Whilst en-US could be fairly easily deduced to be "English as used in the United States", de-CH is more difficult less one happens to know that de is from "Deutsch", German for "German" and CH is the abbreviation of the official Latin name for Switzerland, "Confoederatio Helvetica".

+ +

For the English language description, the language name is always first and any additional attributes required to describe the subtags within the language code are then listed in order separated with commas and enclosed within parentheses, eg:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
English language description examples for iso.txt
Raw language tagEnglish description within iso.txt
enBritish English
en-USEnglish (United States)
en-053English (Australia & New Zealand)
deGerman
de-CH-1996German (Switzerland, 1996 orthography)
gws-1996Swiss German (1996 orthography)
zh-cmn-Hans-CNMandarin Chinese (Simplified, Mainland China)
zh-yue-Hant-HKCantonese Chinese (Traditional, Hong Kong)
+ +

For the localised language description, just translate the English version though use whatever appropriate punctuation typical for your own locale, assuming the language uses punctuation at all.

+ +

Unicode bi-directional considerations:

+ +

Because phpBB is now UTF-8, all translators must take into account that certain strings may be shown when the directionality of the document is either opposite to normal or is ambiguous.

+ +

The various Unicode control characters for bi-directional text and their HTML enquivalents where appropriate are as follows:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Unicode bidirectional control characters & HTML elements/entities
Unicode character
abbreviation
Unicode
code-point
Unicode character
name
Equivalent HTML
markup/entity
Raw character
(enclosed between '')
LRMU+200ELeft-to-Right Mark&lrm;'‎'
RLMU+200FRight-to-Left Mark&rlm;'‏'
LREU+202ALeft-to-Right Embeddingdir="ltr"'‪'
RLEU+202BRight-to-Left Embeddingdir="rtl"'‫'
PDFU+202CPop Directional Formatting</bdo>'‬'
LROU+202DLeft-to-Right Override<bdo dir="ltr">'‭'
RLOU+202ERight-to-Left Override<bdo dir="rtl">'‮'
+ +

For iso.txt, the directionality of the text can be explicitly set using special Unicode characters via any of the three methods provided by left-to-right/right-to-left markers/embeds/overrides, as without them, the ordering of characters will be incorrect, eg:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Unicode bidirectional control characters iso.txt
DirectionalityRaw character viewDisplay of localised
description in iso.txt
Ordering
dir="ltr"English (Australia & New Zealand)English (Australia & New Zealand)Correct
dir="rtl"English (Australia & New Zealand)English (Australia & New Zealand)Incorrect
dir="rtl" with LRMEnglish (Australia & New Zealand)U+200EEnglish (Australia & New Zealand)‎Correct
dir="rtl" with LRE & PDFU+202AEnglish (Australia & New Zealand)U+202C‪English (Australia & New Zealand)‬Correct
dir="rtl" with LRO & PDFU+202DEnglish (Australia & New Zealand)U+202C‭English (Australia & New Zealand)‬Correct
+ +

In choosing which of the three methods to use, in the majority of cases, the LRM or RLM to put a "strong" character to fully enclose an ambiguous punctuation character and thus make it inherit the correct directionality is sufficient.

+

Within some cases, there may be mixed scripts of a left-to-right and right-to-left direction, so using LRE & RLE with PDF may be more appropriate. Lastly, in very rare instances where directionality must be forced, then use LRO & RLO with PDF.

+

For further information on authoring techniques of bi-directional text, please see the W3C tutorial on authoring techniques for XHTML pages with bi-directional text.

+ +

6.iii. Working with placeholders

+ +

As phpBB is translated into languages with different ordering rules to that of English, it is possible to show specific values in any order deemed appropriate. Take for example the extremely simple "Page X of Y", whilst in English this could just be coded as:

+ +
+	...
+'PAGE_OF'	=>	'Page %s of %s',
+		/* Just grabbing the replacements as they
+		come and hope they are in the right order */
+	...
+
+ +

… a clearer way to show explicit replacement ordering is to do:

+ +
+	...
+'PAGE_OF'	=>	'Page %1$s of %2$s',
+		/* Explicit ordering of the replacements,
+		even if they are the same order as English */
+	...
+
+ +

Why bother at all? Because some languages, the string transliterated back to English might read something like:

+ +
+	...
+'PAGE_OF'	=>	'Total of %2$s pages, currently on page %1$s',
+		/* Explicit ordering of the replacements,
+		reversed compared to English as the total comes first */
+	...
+
+ +

6.iv. Using plurals

+ +

+ The english language is very simple when it comes to plurals.
+ You have 0 elephants, 1 elephant, or 2+ elephants. So basically you have 2 different forms: one singular and one plural.
+ But for some other languages this is quite more difficult. Let's take the Bosnian language as another example:
+ You have [1/21/31] slon, [2/3/4] slona, [0/5/6] slonova and [7/8/9/11] ... and some more difficult rules. +

+ +

The plural system takes care of this and can be used as follows:

+ +

The PHP code will basically look like this:

+ +
+	...
+	$user->lang('NUMBER_OF_ELEPHANTS', $number_of_elephants);
+	...
+
+ +

And the English translation would be:

+ +
+	...
+	'NUMBER_OF_ELEPHANTS'	=> array(
+		0	=> 'You have no elephants', // Optional special case for 0
+		1	=> 'You have 1 elephant', // Singular
+		2	=> 'You have %d elephants', // Plural
+	),
+	...
+
+ +

While the Bosnian translation can have more cases:

+ +
+	...
+	'NUMBER_OF_ELEPHANTS'	=> array(
+		0	=> 'You have no slonova', // Optional special case for 0
+		1	=> 'You have %d slon', // Used for 1, 21, 31, ..
+		2	=> 'You have %d slona', // Used for 5, 6,
+		3	=> ...
+	),
+	...
+
+ +

NOTE: It is okay to use plurals for an unknown number compared to a single item, when the number is not known and displayed:

+
+	...
+	'MODERATOR'	=> 'Moderator',  // Your board has 1 moderator
+	'MODERATORS'	=> 'Moderators', // Your board has multiple moderators
+	...
+
+ +

6.v. Writing Style

+ +

Miscellaneous tips & hints:

+ +

As the language files are PHP files, where the various strings for phpBB are stored within an array which in turn are used for display within an HTML page, rules of syntax for both must be considered. Potentially problematic characters are: ' (straight quote/apostrophe), " (straight double quote), < (less-than sign), > (greater-than sign) and & (ampersand).

+ +

// Bad - The un-escapsed straight-quote/apostrophe will throw a PHP parse error

+ +
+	...
+'CONV_ERROR_NO_AVATAR_PATH'
+	=>	'Note to developer: you must specify $convertor['avatar_path'] to use %s.',
+	...
+
+ +

// Good - Literal straight quotes should be escaped with a backslash, ie: \

+ +
+	...
+'CONV_ERROR_NO_AVATAR_PATH'
+	=>	'Note to developer: you must specify $convertor[\'avatar_path\'] to use %s.',
+	...
+
+ +

However, because phpBB3 now uses UTF-8 as its sole encoding, we can actually use this to our advantage and not have to remember to escape a straight quote when we don't have to:

+ +

// Bad - The un-escapsed straight-quote/apostrophe will throw a PHP parse error

+ +
+	...
+'USE_PERMISSIONS'	=>	'Test out user's permissions',
+	...
+
+ +

// Okay - However, non-programmers wouldn't type "user\'s" automatically

+ +
+	...
+'USE_PERMISSIONS'	=>	'Test out user\'s permissions',
+	...
+
+ +

// Best - Use the Unicode Right-Single-Quotation-Mark character

+ +
+	...
+'USE_PERMISSIONS'	=>	'Test out user’s permissions',
+	...
+
+ +

The " (straight double quote), < (less-than sign) and > (greater-than sign) characters can all be used as displayed glyphs or as part of HTML markup, for example:

+ +

// Bad - Invalid HTML, as segments not part of elements are not entitised

+ +
+	...
+'FOO_BAR'	=>	'PHP version < 5.3.3.<br />
+	Visit "Downloads" at <a href="http://www.php.net/">www.php.net</a>.',
+	...
+
+ +

// Okay - No more invalid HTML, but "&quot;" is rather clumsy

+ +
+	...
+'FOO_BAR'	=>	'PHP version &lt; 5.3.3.<br />
+	Visit &quot;Downloads&quot; at <a href="http://www.php.net/">www.php.net</a>.',
+	...
+
+ +

// Best - No more invalid HTML, and usage of correct typographical quotation marks

+ +
+	...
+'FOO_BAR'	=>	'PHP version &lt; 5.3.3.<br />
+	Visit “Downloads” at <a href="http://www.php.net/">www.php.net</a>.',
+	...
+
+ +

Lastly, the & (ampersand) must always be entitised regardless of where it is used:

+ +

// Bad - Invalid HTML, none of the ampersands are entitised

+ +
+	...
+'FOO_BAR'	=>	'<a href="http://somedomain.tld/?foo=1&bar=2">Foo & Bar</a>.',
+	...
+
+ +

// Good - Valid HTML, amperands are correctly entitised in all cases

+ +
+	...
+'FOO_BAR'	=>	'<a href="http://somedomain.tld/?foo=1&amp;bar=2">Foo &amp; Bar</a>.',
+	...
+
+ +

As for how these charcters are entered depends very much on choice of Operating System, current language locale/keyboard configuration and native abilities of the text editor used to edit phpBB language files. Please see http://en.wikipedia.org/wiki/Unicode#Input_methods for more information.

+ +

Spelling, punctuation, grammar, et cetera:

+ +

The default language pack bundled with phpBB is British English using Cambridge University Press spelling and is assigned the language code en. The style and tone of writing tends towards formal and translations should emulate this style, at least for the variant using the most compact language code. Less formal translations or those with colloquialisms must be denoted as such via either an extension or privateuse tag within its language code.

+ +
+ + + +
+
+ +
+ +

7. Copyright and disclaimer

+ +
+
+ +
+ +

phpBB is free software, released under the terms of the GNU General Public License, version 2 (GPL-2.0). Copyright © phpBB Limited. For full copyright and license information, please see the docs/CREDITS.txt file.

+ +
+ + + +
+
+ + + + +
+ +
+ +
+ + + diff --git a/docs/events.md b/docs/events.md new file mode 100644 index 0000000..fb782ee --- /dev/null +++ b/docs/events.md @@ -0,0 +1,3099 @@ +acp_ban_cell_append +=== +* Location: adm/style/acp_ban.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the end of the ban cell area + +acp_ban_cell_prepend +=== +* Location: adm/style/acp_ban.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the start of the ban cell area + +acp_bbcodes_actions_append +=== +* Location: adm/style/acp_bbcodes.html +* Since: 3.1.0-a3 +* Purpose: Add actions to the BBCodes page, after edit/delete buttons + +acp_bbcodes_actions_prepend +=== +* Location: adm/style/acp_bbcodes.html +* Since: 3.1.0-a3 +* Purpose: Add actions to the BBCodes page, before edit/delete buttons + +acp_bbcodes_edit_fieldsets_after +=== +* Location: adm/style/acp_bbcodes.html +* Since: 3.1.0-a3 +* Purpose: Add settings to BBCode add/edit form + +acp_email_find_username_append +=== +* Location: adm/style/acp_email.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the end of the find username link + +acp_email_find_username_prepend +=== +* Location: adm/style/acp_email.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the start of the find username link + +acp_email_group_options_append +=== +* Location: adm/style/acp_email.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the end of the group options select box + +acp_email_group_options_prepend +=== +* Location: adm/style/acp_email.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the start of the group options select box + +acp_email_options_after +=== +* Location: adm/style/acp_email.html +* Since: 3.1.2-RC1 +* Purpose: Add settings to mass email form + +acp_ext_details_end +=== +* Location: adm/style/acp_ext_details.html +* Since: 3.1.11-RC1 +* Purpose: Add more detailed information on extension after the available information. + +acp_ext_details_notice +=== +* Location: adm/style/acp_ext_details.html +* Since: 3.1.11-RC1 +* Purpose: Add extension detail notices after version check information. + +acp_ext_list_disabled_name_after +=== +* Location: adm/style/acp_ext_list.html +* Since: 3.1.11-RC1 +* Purpose: Add content after the name of disabled extensions in the list + +acp_ext_list_disabled_title_after +=== +* Location: adm/style/acp_ext_list.html +* Since: 3.1.11-RC1 +* Purpose: Add text after disabled extensions section title. + +acp_ext_list_enabled_name_after +=== +* Location: adm/style/acp_ext_list.html +* Since: 3.1.11-RC1 +* Purpose: Add content after the name of enabled extensions in the list + +acp_ext_list_enabled_title_after +=== +* Location: adm/style/acp_ext_list.html +* Since: 3.1.11-RC1 +* Purpose: Add text after enabled extensions section title. + +acp_forums_custom_settings +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.6-RC1 +* Purpose: Add its own box (fieldset) for extension settings + +acp_forums_main_settings_append +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.2-RC1 +* Purpose: Add settings to forums at end of main settings section + +acp_forums_main_settings_prepend +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.2-RC1 +* Purpose: Add settings to forums before main settings section + +acp_forums_normal_settings_append +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.0-a1 +* Purpose: Add settings to forums at end of normal settings section + +acp_forums_normal_settings_prepend +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.2-RC1 +* Purpose: Add settings to forums before normal settings section + +acp_forums_prune_settings_append +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.2-RC1 +* Purpose: Add settings to forums at end of prune settings section + +acp_forums_prune_settings_prepend +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.2-RC1 +* Purpose: Add settings to forums before prune settings section + +acp_forums_quick_select_button_append +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the quick select forum submit button + +acp_forums_quick_select_button_prepend +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the quick select forum submit button + +acp_forums_rules_settings_append +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.2-RC1 +* Purpose: Add settings to forums at end of rules settings section + +acp_forums_rules_settings_prepend +=== +* Location: adm/style/acp_forums.html +* Since: 3.1.2-RC1 +* Purpose: Add settings to forums before rules settings section + +acp_group_options_after +=== +* Location: adm/style/acp_groups.html +* Since: 3.1.0-b4 +* Purpose: Add additional options to group settings (after GROUP_RECEIVE_PM) + +acp_group_options_before +=== +* Location: adm/style/acp_groups.html +* Since: 3.1.0-b4 +* Purpose: Add additional options to group settings (before GROUP_FOUNDER_MANAGE) + +acp_groups_find_username_append +=== +* Location: adm/style/acp_groups.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the end of the find username link + +acp_groups_find_username_prepend +=== +* Location: adm/style/acp_groups.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the start of the find username link + +acp_groups_manage_after +=== +* Location: adm/style/acp_groups.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the manage groups table + +acp_groups_manage_before +=== +* Location: adm/style/acp_groups.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the manage groups table + +acp_groups_position_legend_add_button_after +=== +* Location: adm/style/acp_groups_position.html +* Since: 3.1.7-RC1 +* Purpose: Add content after adding group to legend submit button + +acp_groups_position_legend_add_button_before +=== +* Location: adm/style/acp_groups_position.html +* Since: 3.1.7-RC1 +* Purpose: Add content before adding group to legend submit button + +acp_groups_position_teampage_add_button_after +=== +* Location: adm/style/acp_groups_position.html +* Since: 3.1.7-RC1 +* Purpose: Add content after adding group to teampage submit button + +acp_groups_position_teampage_add_button_before +=== +* Location: adm/style/acp_groups_position.html +* Since: 3.1.7-RC1 +* Purpose: Add content before adding group to teampage submit button + +acp_help_phpbb_stats_after +=== +* Location: adm/style/acp_help_phpbb.html +* Since: 3.2.0-RC2 +* Purpose: Add content after send statistics tile + +acp_help_phpbb_stats_before +=== +* Location: adm/style/acp_help_phpbb.html +* Since: 3.2.0-RC2 +* Purpose: Add content before send statistics tile + +acp_logs_quick_select_forum_button_append +=== +* Location: adm/style/acp_logs.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the quick forum select form submit button + +acp_logs_quick_select_forum_button_prepend +=== +* Location: adm/style/acp_logs.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the quick forum select form submit button + +acp_main_actions_append +=== +* Location: adm/style/acp_main.html +* Since: 3.1.0-a1 +* Purpose: Add actions to the ACP main page below the cache purge action + +acp_main_notice_after +=== +* Location: adm/style/acp_main.html +* Since: 3.1.0-a1 +* Purpose: Add notices or other blocks in the ACP below other configuration notices + +acp_overall_footer_after +=== +* Location: adm/style/overall_footer.html +* Since: 3.1.0-a1 +* Purpose: Add content below the footer in the ACP + +acp_overall_header_body_before +=== +* Location: adm/style/overall_header.html +* Since: 3.1.0-b2 +* Purpose: Add content to the header body + +acp_overall_header_head_append +=== +* Location: adm/style/overall_header.html +* Since: 3.1.0-a1 +* Purpose: Add assets within the `` tags in the ACP + +acp_overall_header_stylesheets_after +=== +* Location: adm/style/overall_header.html +* Since: 3.1.0-RC3 +* Purpose: Add assets after stylesheets within the `` tags in the ACP. +Note that INCLUDECSS will not work with this event. + +acp_permission_forum_copy_dest_forum_append +=== +* Location: adm/style/permission_forum_copy.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the destination forum select form + +acp_permission_forum_copy_dest_forum_prepend +=== +* Location: adm/style/permission_forum_copy.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the destination forum select form + +acp_permission_forum_copy_src_forum_append +=== +* Location: adm/style/permission_forum_copy.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the source forum select form + +acp_permission_forum_copy_src_forum_prepend +=== +* Location: adm/style/permission_forum_copy.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the source forum select form + +acp_permissions_add_group_options_append +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the group multiple select form + +acp_permissions_add_group_options_prepend +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the group multiple select form + +acp_permissions_find_username_append +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the find username link + +acp_permissions_find_username_prepend +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the find username link + +acp_permissions_select_forum_append +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the forum select form label + +acp_permissions_select_forum_prepend +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the forum select form label + +acp_permissions_select_group_after +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the group select form in usergroup view + +acp_permissions_select_group_append +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the group select form label + +acp_permissions_select_group_before +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the group select form in usergroup view + +acp_permissions_select_group_prepend +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the group select form label + +acp_permissions_select_multiple_forum_append +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the forum multiple select form label + +acp_permissions_select_multiple_forum_prepend +=== +* Location: adm/style/acp_permissions.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the forum multiple select form label + +acp_posting_buttons_after +=== +* Locations: + + adm/style/acp_posting_buttons.html +* Since: 3.1.0-b4 +* Purpose: Add content after BBCode posting buttons in the ACP + +acp_posting_buttons_before +=== +* Locations: + + adm/style/acp_posting_buttons.html +* Since: 3.1.0-b4 +* Purpose: Add content before BBCode posting buttons in the ACP + +acp_posting_buttons_custom_tags_before +=== +* Locations: + + adm/style/acp_posting_buttons.html +* Since: 3.1.10-RC1 +* Purpose: Add content before the custom BBCodes in the ACP + +acp_profile_contact_before +=== +* Locations: + + adm/style/acp_profile.html +* Since: 3.1.6-RC1 +* Purpose: Add extra options to custom profile field configuration in the ACP + +acp_profile_contact_last +=== +* Locations: + + adm/style/acp_profile.html +* Since: 3.1.11-RC1 +* Purpose: Add contact specific options to custom profile fields in the ACP + +acp_profile_step_one_lang_after +=== +* Locations: + + adm/style/acp_profile.html +* Since: 3.1.11-RC1 +* Purpose: Add extra lang specific options to custom profile field step one configuration in the ACP + +acp_prune_forums_append +=== +* Locations: + + adm/style/acp_prune_forums.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the forum select form label + +acp_prune_forums_prepend +=== +* Locations: + + adm/style/acp_prune_forums.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the forum select form label + +acp_prune_forums_settings_append +=== +* Locations: + + adm/style/acp_prune_forums.html +* Since: 3.2.2-RC1 +* Purpose: Add content after the prune settings + +acp_prune_users_find_username_append +=== +* Locations: + + adm/style/acp_prune_users.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the find username link + +acp_prune_users_find_username_prepend +=== +* Locations: + + adm/style/acp_prune_users.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the find username link + +acp_ranks_edit_after +=== +* Locations: + + adm/style/acp_ranks.html +* Since: 3.1.0-RC3 +* Purpose: Add content after the rank details when editing a rank in the ACP + +acp_ranks_edit_before +=== +* Locations: + + adm/style/acp_ranks.html +* Since: 3.1.0-RC3 +* Purpose: Add content before the rank details when editing a rank in the ACP + +acp_ranks_list_column_after +=== +* Locations: + + adm/style/acp_ranks.html +* Since: 3.1.0-RC3 +* Purpose: Add content before the first column in the ranks list in the ACP + +acp_ranks_list_column_before +=== +* Locations: + + adm/style/acp_ranks.html +* Since: 3.1.0-RC3 +* Purpose: Add content after the last column (but before the action column) +in the ranks list in the ACP + +acp_ranks_list_header_after +=== +* Locations: + + adm/style/acp_ranks.html +* Since: 3.1.0-RC3 +* Purpose: Add content before the first header-column in the ranks list in the ACP + +acp_ranks_list_header_before +=== +* Locations: + + adm/style/acp_ranks.html +* Since: 3.1.0-RC3 +* Purpose: Add content after the last header-column (but before the action column) +in the ranks list in the ACP + +acp_simple_footer_after +=== +* Location: adm/style/simple_footer.html +* Since: 3.1.0-a1 +* Purpose: Add content below the simple footer in the ACP + +acp_simple_header_body_before +=== +* Location: adm/style/simple_header.html +* Since: 3.1.0-b2 +* Purpose: Add content to the header body + +acp_simple_header_head_append +=== +* Location: adm/style/simple_header.html +* Since: 3.1.0-a1 +* Purpose: Add assets within the `` tags in the simple header of the ACP + +acp_simple_header_stylesheets_after +=== +* Location: adm/style/simple_header.html +* Since: 3.1.0-RC3 +* Purpose: Add assets after stylesheets within the `` tags in the simple header +of the ACP. Note that INCLUDECSS will not work with this event. + +acp_styles_list_before +=== +* Locations: + + adm/style/acp_styles.html +* Since: 3.1.7-RC1 +* Purpose: Add content before list of styles + +acp_users_mode_add +=== +* Locations: + + adm/style/acp_users.html +* Since: 3.2.2-RC1 +* Purpose: Add extra modes to the ACP user page + +acp_users_overview_options_append +=== +* Location: adm/style/acp_users_overview.html +* Since: 3.1.0-a1 +* Purpose: Add options and settings on user overview page + +acp_users_prefs_append +=== +* Location: adm/style/acp_users_prefs.html +* Since: 3.1.0-b3 +* Purpose: Add user options fieldset to the bottom of ACP users prefs settings + +acp_users_prefs_personal_append +=== +* Location: adm/style/acp_users_prefs.html +* Since: 3.1.0-b3 +* Purpose: Add user options fieldset to the bottom of ACP users personal prefs settings + +acp_users_prefs_personal_prepend +=== +* Location: adm/style/acp_users_prefs.html +* Since: 3.1.0-b3 +* Purpose: Add user options fieldset to the top of ACP users personal prefs settings + +acp_users_prefs_post_append +=== +* Location: adm/style/acp_users_prefs.html +* Since: 3.1.0-b3 +* Purpose: Add user options fieldset to the bottom of ACP users post prefs settings + +acp_users_prefs_post_prepend +=== +* Location: adm/style/acp_users_prefs.html +* Since: 3.1.0-b3 +* Purpose: Add user options fieldset to the top of ACP users post prefs settings + +acp_users_prefs_prepend +=== +* Location: adm/style/acp_users_prefs.html +* Since: 3.1.0-b3 +* Purpose: Add user options fieldset to the top of ACP users prefs settings + +acp_users_prefs_view_append +=== +* Location: adm/style/acp_users_prefs.html +* Since: 3.1.0-b3 +* Purpose: Add user options fieldset to the bottom of ACP users view prefs settings + +acp_users_prefs_view_prepend +=== +* Location: adm/style/acp_users_prefs.html +* Since: 3.1.0-b3 +* Purpose: Add user options fieldset to the top of ACP users view prefs settings + +acp_users_profile_after +=== +* Locations: + + adm/style/acp_users_profile.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the profile details but before the custom profile fields when editing a user in the ACP + +acp_users_profile_before +=== +* Locations: + + adm/style/acp_users_profile.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the profile details when editing a user in the ACP + +acp_users_profile_custom_after +=== +* Locations: + + adm/style/acp_users_profile.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the the custom profile fields when editing a user in the ACP + +acp_users_select_group_after +=== +* Location: adm/style/acp_users.html +* Since: 3.1.7-RC1 +* Purpose: Add content after group select form + +acp_users_select_group_before +=== +* Location: adm/style/acp_users.html +* Since: 3.1.7-RC1 +* Purpose: Add content before group select form + +attachment_file_after +=== +* Locations: + + styles/prosilver/template/attachment.html +* Since: 3.1.6-RC1 +* Purpose: Add content after the attachment. + +attachment_file_append +=== +* Locations: + + styles/prosilver/template/attachment.html +* Since: 3.1.6-RC1 +* Purpose: Add custom attachment types displaying to the bottom of attachment block. + +attachment_file_before +=== +* Locations: + + styles/prosilver/template/attachment.html +* Since: 3.1.6-RC1 +* Purpose: Add content before the attachment. + +attachment_file_prepend +=== +* Locations: + + styles/prosilver/template/attachment.html +* Since: 3.1.6-RC1 +* Purpose: Add custom attachment types displaying to the top of attachment block. + +confirm_delete_body_delete_reason_before +=== +* Locations: + + styles/prosilver/template/confirm_delete_body.html +* Since: 3.2.4-RC1 +* Purpose: Add custom text to the confirmation of a post that is deleted. + +forumlist_body_category_header_after +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-a4 +* Purpose: Add content after the header of the category on the forum list. + +forumlist_body_category_header_before +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-a4 +* Purpose: Add content before the header of the category on the forum list. + +forumlist_body_category_header_row_append +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.5-RC1 +* Purpose: Add content after the header row of the category on the forum list. + +forumlist_body_category_header_row_prepend +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.5-RC1 +* Purpose: Add content before the header row of the category on the forum list. + +forumlist_body_forum_image_after +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.2.2-RC1 +* Purpose: Add content after the forum image on the forum list. + +forumlist_body_forum_image_append +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.2.2-RC1 +* Purpose: Add content at the start of the forum image on the forum list. + +forumlist_body_forum_image_before +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.2.2-RC1 +* Purpose: Add content before the forum image on the forum list. + +forumlist_body_forum_image_prepend +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.2.2-RC1 +* Purpose: Add content at the end of the forum image on the forum list. + +forumlist_body_forum_row_after +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-RC5 +* Purpose: Add content after the forum list item. + +forumlist_body_forum_row_append +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-RC5 +* Purpose: Add content at the start of the forum list item. + +forumlist_body_forum_row_before +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-RC5 +* Purpose: Add content before the forum list item. + +forumlist_body_forum_row_prepend +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-RC5 +* Purpose: Add content at the end of the forum list item. + +forumlist_body_last_post_title_prepend +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-a1 +* Purpose: Add content before the post title of the latest post in a forum on the forum list. + +forumlist_body_last_poster_username_append +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.2.4-RC1 +* Purpose: Append information to last poster username of member + +forumlist_body_last_poster_username_prepend +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.2.4-RC1 +* Purpose: Prepend information to last poster username of member + +forumlist_body_last_row_after +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-b2 +* Purpose: Add content after the very last row of the forum list. + +forumlist_body_subforum_link_append +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.11-RC1 +* Purpose: Add content at the end of subforum link item. + +forumlist_body_subforum_link_prepend +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.11-RC1 +* Purpose: Add content at the start of subforum link item. + +forumlist_body_subforums_after +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-a4 +* Purpose: Add content after the list of subforums (if any) for each forum on the forum list. + +forumlist_body_subforums_before +=== +* Locations: + + styles/prosilver/template/forumlist_body.html +* Since: 3.1.0-a4 +* Purpose: Add content before the list of subforums (if any) for each forum on the forum list. + +index_body_birthday_block_before +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.11-RC1 +* Purpose: Add new statistic blocks before the Birthday block + +index_body_block_birthday_append +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-b3 +* Purpose: Append content to the birthday list on the Board index + +index_body_block_birthday_prepend +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-b3 +* Purpose: Prepend content to the birthday list on the Board index + +index_body_block_online_append +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-b3 +* Purpose: Append content to the online list on the Board index + +index_body_block_online_prepend +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-b3 +* Purpose: Prepend content to the online list on the Board index + +index_body_block_stats_append +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-b3 +* Purpose: Append content to the statistics list on the Board index + +index_body_block_stats_prepend +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-b3 +* Purpose: Prepend content to the statistics list on the Board index + +index_body_forumlist_body_after +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.1 +* Purpose: Add content after the forum list body on the index page + +index_body_markforums_after +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-RC2 +* Purpose: Add content after the mark-read link above the forum list on Board index + +index_body_markforums_before +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-RC2 +* Purpose: Add content before the mark-read link above the forum list on Board index + +index_body_stat_blocks_after +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-b3 +* Purpose: Add new statistic blocks below the Who Is Online and Board Statistics blocks + +index_body_stat_blocks_before +=== +* Locations: + + styles/prosilver/template/index_body.html +* Since: 3.1.0-a1 +* Purpose: Add new statistic blocks above the Who Is Online and Board Statistics blocks + +mcp_ban_fields_after +=== +* Locations: + + styles/prosilver/template/mcp_ban.html +* Since: 3.1.0-RC3 +* Purpose: Add additional fields to the ban form in MCP + +mcp_ban_fields_before +=== +* Locations: + + styles/prosilver/template/mcp_ban.html +* Since: 3.1.0-RC3 +* Purpose: Add additional fields to the ban form in MCP + +mcp_ban_unban_after +=== +* Locations: + + styles/prosilver/template/mcp_ban.html +* Since: 3.1.0-RC3 +* Purpose: Add additional fields to the unban form in MCP + +mcp_ban_unban_before +=== +* Locations: + + styles/prosilver/template/mcp_ban.html +* Since: 3.1.0-RC3 +* Purpose: Add additional fields to the unban form in MCP + +mcp_forum_actions_after +=== +* Locations: + + styles/prosilver/template/mcp_forum.html +* Since: 3.1.11-RC1 +* Purpose: Add some information after actions fieldset + +mcp_forum_actions_append +=== +* Locations: + + styles/prosilver/template/mcp_forum.html +* Since: 3.1.11-RC1 +* Purpose: Add additional options to actions select + +mcp_forum_actions_before +=== +* Locations: + + styles/prosilver/template/mcp_forum.html +* Since: 3.1.11-RC1 +* Purpose: Add some information before actions fieldset + +mcp_forum_topic_title_after +=== +* Locations: + + styles/prosilver/template/mcp_forum.html +* Since: 3.1.6-RC1 +* Purpose: Add some information after the topic title + +mcp_forum_topic_title_before +=== +* Locations: + + styles/prosilver/template/mcp_forum.html +* Since: 3.1.6-RC1 +* Purpose: Add some information before the topic title + +mcp_front_latest_logs_after +=== +* Locations: + + styles/prosilver/template/mcp_front.html +* Since: 3.1.3-RC1 +* Purpose: Add content after latest logs list + +mcp_front_latest_logs_before +=== +* Locations: + + styles/prosilver/template/mcp_front.html +* Since: 3.1.3-RC1 +* Purpose: Add content before latest logs list + +mcp_front_latest_reported_before +=== +* Locations: + + styles/prosilver/template/mcp_front.html +* Since: 3.1.3-RC1 +* Purpose: Add content before latest reported posts list + +mcp_front_latest_reported_pms_before +=== +* Locations: + + styles/prosilver/template/mcp_front.html +* Since: 3.1.3-RC1 +* Purpose: Add content before latest reported private messages list + +mcp_front_latest_unapproved_before +=== +* Locations: + + styles/prosilver/template/mcp_front.html +* Since: 3.1.3-RC1 +* Purpose: Add content before latest unapproved posts list + +mcp_move_before +=== +* Locations: + + styles/prosilver/template/mcp_move.html +* Since: 3.1.10-RC1 +* Purpose: Add content before move topic/post form + +mcp_post_additional_options +=== +* Locations: + + styles/prosilver/template/mcp_post.html +* Since: 3.1.5-RC1 +* Purpose: Add content within the list of post moderation actions + +mcp_post_report_buttons_top_after +=== +* Locations: + + styles/prosilver/template/mcp_post.html +* Since: 3.2.4-RC1 +* Purpose: Add content after report buttons + +mcp_post_report_buttons_top_before +=== +* Locations: + + styles/prosilver/template/mcp_post.html +* Since: 3.2.4-RC1 +* Purpose: Add content before report buttons + +mcp_post_text_after +=== +* Locations: + + styles/prosilver/template/mcp_post.html +* Since: 3.2.6-RC1 +* Purpose: Add content after the post text + +mcp_post_text_before +=== +* Locations: + + styles/prosilver/template/mcp_post.html +* Since: 3.2.6-RC1 +* Purpose: Add content before the post text + +mcp_topic_options_after +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.1.6-RC1 +* Purpose: Add some options (field, checkbox, ...) after the subject field when split a subject + +mcp_topic_options_before +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.1.6-RC1 +* Purpose: Add some options (field, checkbox, ...) before the subject field when split a subject + +mcp_topic_postrow_attachments_after +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.2.2-RC1 +* Purpose: Show additional content after attachments in mcp topic review + +mcp_topic_postrow_attachments_before +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.2.2-RC1 +* Purpose: Show additional content before attachments in mcp topic review + +mcp_topic_postrow_post_before +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.2.2-RC1 +* Purpose: Show additional content after postrow begins in mcp topic review + +mcp_topic_postrow_post_details_after +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.1.10-RC1 +* Purpose: Add content after post details in topic moderation + +mcp_topic_postrow_post_details_before +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.1.10-RC1 +* Purpose: Add content before post details in topic moderation + +mcp_topic_postrow_post_subject_after +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.1.11-RC1 +* Purpose: Add content after post subject in topic moderation + +mcp_topic_postrow_post_subject_before +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.1.11-RC1 +* Purpose: Add content before post subject in topic moderation + +mcp_topic_topic_title_after +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.1.6-RC1 +* Purpose: Add some information after the topic title + +mcp_topic_topic_title_before +=== +* Locations: + + styles/prosilver/template/mcp_topic.html +* Since: 3.1.6-RC1 +* Purpose: Add some information before the topic title + +mcp_warn_post_add_warning_field_after +=== +* Locations: + + styles/prosilver/template/mcp_warn_post.html +* Since: 3.1.0-RC4 +* Purpose: Add content during warning for a post - after add warning field. + +mcp_warn_post_add_warning_field_before +=== +* Locations: + + styles/prosilver/template/mcp_warn_post.html +* Since: 3.1.0-RC4 +* Purpose: Add content during warning for a post - before add warning field. + +mcp_warn_user_add_warning_field_after +=== +* Locations: + + styles/prosilver/template/mcp_warn_user.html +* Since: 3.1.0-RC4 +* Purpose: Add content during warning a user - after add warning field. + +mcp_warn_user_add_warning_field_before +=== +* Locations: + + styles/prosilver/template/mcp_warn_user.html +* Since: 3.1.0-RC4 +* Purpose: Add content during warning a user - before add warning field. + +memberlist_body_group_desc_after +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data after the group description and type in the group profile page. + +memberlist_body_group_name_after +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data after the group name in the group profile page. + +memberlist_body_group_name_before +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data before the group name in the group profile page. + +memberlist_body_group_rank_after +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data after the group rank in the group profile page. + +memberlist_body_group_rank_before +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data before the group rank in the group profile page. + +memberlist_body_leaders_set_after +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data after the last row in the memberlist mode leaders. + +memberlist_body_memberlist_after +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data after the last row in the memberlist. + +memberlist_body_memberrow_after +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data after the last memberrow in the memberlist. + +memberlist_body_page_footer_before +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data before the page footer. + +memberlist_body_page_header_after +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data after the page header. + +memberlist_body_page_title_before +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data before the page title. + +memberlist_body_rank_append +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.1.6-RC1 +* Purpose: Add information after rank in memberlist. Works in +all display modes (leader, group and normal memberlist). + +memberlist_body_rank_prepend +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.1.6-RC1 +* Purpose: Add information before rank in memberlist. Works in +all display modes (leader, group and normal memberlist). + +memberlist_body_show_group_after +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.2.6-RC1 +* Purpose: Add data after the last row in the memberlist mode group. + +memberlist_body_username_append +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.1.0-a1 +* Purpose: Add information after every username in the memberlist. Works in +all display modes (leader, group and normal memberlist). + +memberlist_body_username_prepend +=== +* Locations: + + styles/prosilver/template/memberlist_body.html +* Since: 3.1.0-a1 +* Purpose: Add information before every username in the memberlist. Works in +all display modes (leader, group and normal memberlist). + +memberlist_email_before +=== +* Locations: + + styles/prosilver/template/memberlist_email.html +* Since: 3.1.10-RC1 +* Purpose: Allow adding customizations before the memberlist_email form. + +memberlist_search_fields_after +=== +* Locations: + + styles/prosilver/template/memberlist_search.html +* Since: 3.1.2-RC1 +* Purpose: Add information after the search fields column. + +memberlist_search_fields_before +=== +* Locations: + + styles/prosilver/template/memberlist_search.html +* Since: 3.1.2-RC1 +* Purpose: Add information before the search fields column. + +memberlist_search_sorting_options_before +=== +* Locations: + + styles/prosilver/template/memberlist_search.html +* Since: 3.1.2-RC1 +* Purpose: Add information before the search sorting options field. + +memberlist_team_username_append +=== +* Locations: + + styles/prosilver/template/memberlist_team.html +* Since: 3.1.11-RC1 +* Purpose: Append information to username of team member + +memberlist_team_username_prepend +=== +* Locations: + + styles/prosilver/template/memberlist_team.html +* Since: 3.1.11-RC1 +* Purpose: Add information before team user username + +memberlist_view_contact_after +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.0-b2 +* Purpose: Add content after the user contact part of any user profile + +memberlist_view_contact_before +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.0-b2 +* Purpose: Add content before the user contact part of any user profile + +memberlist_view_contact_custom_fields_after +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.9-RC1 +* Purpose: Add content after the user contact related custom fields + +memberlist_view_contact_custom_fields_before +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.9-RC1 +* Purpose: Add content before the user contact related custom fields + +memberlist_view_content_append +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.0-b2 +* Purpose: Add custom content to the user profile view after the main content + +memberlist_view_content_prepend +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.0-b3 +* Purpose: Add custom content to the user profile view before the main content + +memberlist_view_non_contact_custom_fields_after +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.9-RC1 +* Purpose: Add content after the user not contact related custom fields + +memberlist_view_non_contact_custom_fields_before +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.9-RC1 +* Purpose: Add content before the user not contact related custom fields + +memberlist_view_rank_avatar_after +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.6-RC1 +* Purpose: Add information after rank in memberlist (with avatar) + +memberlist_view_rank_avatar_before +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.6-RC1 +* Purpose: Add information before rank in memberlist (with avatar) + +memberlist_view_rank_no_avatar_after +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.6-RC1 +* Purpose: Add information after rank in memberlist (without avatar) + +memberlist_view_rank_no_avatar_before +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.6-RC1 +* Purpose: Add information before rank in memberlist (without avatar) + +memberlist_view_user_statistics_after +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.0-a1 +* Purpose: Add entries after the user statistics part of any user profile + +memberlist_view_user_statistics_before +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.0-a1 +* Purpose: Add entries before the user statistics part of any user profile + +memberlist_view_username_append +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.2.4-RC1 +* Purpose: Append information to username of member + +memberlist_view_username_prepend +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.2.4-RC1 +* Purpose: Prepend information to username of member + +memberlist_view_zebra_after +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.9-RC1 +* Purpose: Add content after the user friends/foes links + +memberlist_view_zebra_before +=== +* Locations: + + styles/prosilver/template/memberlist_view.html +* Since: 3.1.9-RC1 +* Purpose: Add content before the user friends/foes links + +navbar_header_logged_out_content +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC1 +* Purpose: Add text and HTML in place of the username when not logged in. + +navbar_header_profile_list_after +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC2 +* Purpose: Add links to the bottom of the profile drop-down menu in the header navbar + +navbar_header_profile_list_before +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC2 +* Purpose: Add links to the top of the profile drop-down menu in the header navbar + +navbar_header_quick_links_after +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC2 +* Purpose: Add links to the bottom of the quick-links drop-down menu in the header + +navbar_header_quick_links_before +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC2 +* Purpose: Add links to the top of the quick-links drop-down menu in the header + +navbar_header_user_profile_append +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.8-RC1 +* Purpose: Add links to the right of the user drop down area + +navbar_header_user_profile_prepend +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.8-RC1 +* Purpose: Add links to the left of the notification area + +navbar_header_username_append +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC1 +* Purpose: Add text and HTMl after the username shown in the navbar. + +navbar_header_username_prepend +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC1 +* Purpose: Add text and HTMl before the username shown in the navbar. + +overall_footer_after +=== +* Locations: + + styles/prosilver/template/overall_footer.html +* Since: 3.1.0-a1 +* Purpose: Add content at the end of the file, directly prior to the `` tag + +overall_footer_body_after +=== +* Locations: + + styles/prosilver/template/overall_footer.html +* Since: 3.1.3-RC1 +* Purpose: Add content before the `` tag but after the $SCRIPTS var, i.e. after the js scripts have been loaded + +overall_footer_breadcrumb_append +=== +* Locations: + + styles/prosilver/template/navbar_footer.html +* Since: 3.1.0-a1 +* Purpose: Add links to the list of breadcrumbs in the footer + +overall_footer_breadcrumb_prepend +=== +* Locations: + + styles/prosilver/template/navbar_footer.html +* Since: 3.1.0-RC3 +* Purpose: Add links to the list of breadcrumbs in the footer (after site-home, but before board-index) + +overall_footer_content_after +=== +* Locations: + + styles/prosilver/template/overall_footer.html +* Since: 3.1.0-a3 +* Purpose: Add content on all pages after the main content, before the footer + +overall_footer_copyright_append +=== +* Locations: + + styles/prosilver/template/overall_footer.html +* Since: 3.1.0-a1 +* Purpose: Add content after the copyright line (no new line by default), before the ACP link + +overall_footer_copyright_prepend +=== +* Locations: + + styles/prosilver/template/overall_footer.html +* Since: 3.1.0-a1 +* Purpose: Add content before the copyright line + +overall_footer_page_body_after +=== +* Locations: + + styles/prosilver/template/overall_footer.html +* Since: 3.1.0-b3 +* Purpose: Add content after the page-body, but before the footer + +overall_footer_teamlink_after +=== +* Locations: + + styles/prosilver/template/navbar_footer.html +* Since: 3.1.0-b3 +* Purpose: Add contents after the team-link in the footer + +overall_footer_teamlink_before +=== +* Locations: + + styles/prosilver/template/navbar_footer.html +* Since: 3.1.0-b3 +* Purpose: Add contents before the team-link in the footer + +overall_footer_timezone_after +=== +* Locations: + + styles/prosilver/template/navbar_footer.html +* Since: 3.1.0-b3 +* Purpose: Add content to the navbar in the page footer, after "Timezone" + +overall_footer_timezone_before +=== +* Locations: + + styles/prosilver/template/navbar_footer.html +* Since: 3.1.0-b3 +* Purpose: Add content to the navbar in the page footer, before "Timezone" + +overall_header_body_before +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.0-b2 +* Purpose: Add content to the header body + +overall_header_breadcrumb_append +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-a1 +* Purpose: Add links to the list of breadcrumbs in the header + +overall_header_breadcrumb_prepend +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC3 +* Purpose: Add links to the list of breadcrumbs in the header (after site-home, but before board-index) + +overall_header_breadcrumbs_after +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC3 +* Purpose: Add content after the breadcrumbs (outside of the breadcrumbs container) + +overall_header_breadcrumbs_before +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-RC3 +* Purpose: Add content before the breadcrumbs (outside of the breadcrumbs container) + +overall_header_content_before +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.0-a3 +* Purpose: Add content on all pages before the main content, after the header + +overall_header_feeds +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.6-RC1 +* Purpose: Add custom feeds + +overall_header_head_append +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.0-a1 +* Purpose: Add asset calls directly before the `` tag + +overall_header_headerbar_after +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.10-RC1 +* Purpose: Add content at the end of the headerbar + +overall_header_headerbar_before +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.10-RC1 +* Purpose: Add content at the beginning of the headerbar + +overall_header_navbar_before +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the navigation bar + +overall_header_navigation_append +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-a1 +* Purpose: Add links after the navigation links in the header + +overall_header_navigation_prepend +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-a1 +* Purpose: Add links before the navigation links in the header + +overall_header_navlink_append +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-b3 +* Purpose: Add content after each individual navlink (breadcrumb) + +overall_header_navlink_prepend +=== +* Locations: + + styles/prosilver/template/navbar_header.html +* Since: 3.1.0-b3 +* Purpose: Add content before each individual navlink (breadcrumb) + +overall_header_page_body_before +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.0-b3 +* Purpose: Add content after the page-header, but before the page-body + +overall_header_searchbox_after +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.11-RC1 +* Purpose: Add content after the search box in the header + +overall_header_searchbox_before +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the search box in the header + +overall_header_stylesheets_after +=== +* Locations: + + styles/prosilver/template/overall_header.html +* Since: 3.1.0-RC3 +* Purpose: Add asset calls after stylesheets within the `` tag. +Note that INCLUDECSS will not work with this event. + +posting_attach_body_attach_row_after +=== +* Locations: + + styles/prosilver/template/posting_attach_body.html +* Since: 3.2.6-RC1 +* Purpose: Add content after attachment row in the file list + +posting_attach_body_attach_row_append +=== +* Locations: + + styles/prosilver/template/posting_attach_body.html +* Since: 3.2.6-RC1 +* Purpose: Add content appending the attachment row in the file list + +posting_attach_body_attach_row_before +=== +* Locations: + + styles/prosilver/template/posting_attach_body.html +* Since: 3.2.6-RC1 +* Purpose: Add content before attachment row in the file list + +posting_attach_body_attach_row_controls_append +=== +* Locations: + + styles/prosilver/template/posting_attach_body.html +* Since: 3.2.2-RC1 +* Purpose: Add content after attachment control elements + +posting_attach_body_attach_row_controls_prepend +=== +* Locations: + + styles/prosilver/template/posting_attach_body.html +* Since: 3.2.2-RC1 +* Purpose: Add content before attachment control elements + +posting_attach_body_attach_row_prepend +=== +* Locations: + + styles/prosilver/template/posting_attach_body.html +* Since: 3.2.6-RC1 +* Purpose: Add content prepending attachment row in the file list + +posting_attach_body_file_list_after +=== +* Locations: + + styles/prosilver/template/posting_attach_body.html +* Since: 3.2.6-RC1 +* Purpose: Add content after attachments list + +posting_attach_body_file_list_before +=== +* Locations: + + styles/prosilver/template/posting_attach_body.html +* Since: 3.2.6-RC1 +* Purpose: Add content before attachments list + +posting_editor_add_panel_tab +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.6-RC1 +* Purpose: Add custom panel to post editor + +posting_editor_bbcode_status_after +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.4-RC1 +* Purpose: Add content after bbcode status + +posting_editor_buttons_after +=== +* Locations: + + styles/prosilver/template/posting_buttons.html +* Since: 3.1.0-a3 +* Purpose: Add content after the BBCode posting buttons + +posting_editor_buttons_before +=== +* Locations: + + styles/prosilver/template/posting_buttons.html +* Since: 3.1.0-a3 +* Purpose: Add content before the BBCode posting buttons + +posting_editor_buttons_custom_tags_before +=== +* Locations: + + styles/prosilver/template/posting_buttons.html +* Since: 3.1.2-RC1 +* Purpose: Add content inside the BBCode posting buttons and before the customs BBCode + +posting_editor_message_after +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.0-a2 +* Purpose: Add field (e.g. textbox) to the posting screen after the message + +posting_editor_message_before +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.0-a2 +* Purpose: Add field (e.g. textbox) to the posting screen before the message + +posting_editor_options_prepend +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.0-a1 +* Purpose: Add posting options on the posting screen + +posting_editor_smilies_after +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.4-RC1 +* Purpose: Add content after smilies + +posting_editor_smilies_before +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the smilies + +posting_editor_subject_after +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.0-a2 +* Purpose: Add field (e.g. textbox) to the posting screen after the subject + +posting_editor_subject_append +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.10-RC1 +* Purpose: Add field, text, etc. to the posting after the subject text box + +posting_editor_subject_before +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.0-a2 +* Purpose: Add field (e.g. textbox) to the posting screen before the subject + +posting_editor_subject_prepend +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.10-RC1 +* Purpose: Add field, text, etc. to the posting before the subject text box + +posting_editor_submit_buttons +=== +* Locations: + + styles/prosilver/template/posting_editor.html +* Since: 3.1.6-RC1 +* Purpose: Add custom buttons in the posting editor + +posting_layout_include_panel_body +=== +* Locations: + + styles/prosilver/template/posting_layout.html +* Since: 3.1.6-RC1 +* Purpose: Add include of custom panel template body in posting editor + +posting_pm_header_find_username_after +=== +* Locations: + + styles/prosilver/template/posting_pm_header.html +* Since: 3.1.0-RC4 +* Purpose: Add content after the find username link on composing pm + +posting_pm_header_find_username_before +=== +* Locations: + + styles/prosilver/template/posting_pm_header.html +* Since: 3.1.0-RC4 +* Purpose: Add content before the find username link on composing pm + +posting_pm_layout_include_pm_header_after +=== +* Locations: + + styles/prosilver/template/posting_pm_layout.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the include of posting_pm_header.html + +posting_pm_layout_include_pm_header_before +=== +* Locations: + + styles/prosilver/template/posting_pm_layout.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the include of posting_pm_header.html + +posting_poll_body_options_after +=== +* Locations: + + styles/prosilver/template/posting_poll_body.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the poll options on creating a poll + +posting_preview_content_after +=== +* Locations: + + styles/prosilver/template/posting_preview.html +* Since: 3.2.4-RC1 +* Purpose: Add content after the message content preview + +posting_preview_poll_after +=== +* Locations: + + styles/prosilver/template/posting_preview.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the poll preview block + +posting_topic_review_row_content_after +=== +* Locations: + + styles/prosilver/template/posting_topic_review.html +* Since: 3.2.4-RC1 +* Purpose: Add content after the message content in topic review + +posting_topic_review_row_post_details_after +=== +* Locations: + + styles/prosilver/template/posting_topic_review.html +* Since: 3.1.10-RC1 +* Purpose: Add content after post details in topic review + +posting_topic_review_row_post_details_before +=== +* Locations: + + styles/prosilver/template/posting_topic_review.html +* Since: 3.1.10-RC1 +* Purpose: Add content before post details in topic review + +posting_topic_title_after +=== +* Locations: + + styles/prosilver/template/posting_layout.html +* Since: 3.1.7-RC1 +* Purpose: Allows to add some information after the topic title in the posting form + +posting_topic_title_before +=== +* Locations: + + styles/prosilver/template/posting_layout.html +* Since: 3.1.6-RC1 +* Purpose: Allows to add some information on the left of the topic title in the posting form + +quickreply_editor_message_after +=== +* Locations: + + styles/prosilver/template/quickreply_editor.html +* Since: 3.1.0-a4 +* Purpose: Add content after the quick reply textbox + +quickreply_editor_message_before +=== +* Locations: + + styles/prosilver/template/quickreply_editor.html +* Since: 3.1.0-a4 +* Purpose: Add content before the quick reply textbox + +quickreply_editor_panel_after +=== +* Locations: + + styles/prosilver/template/quickreply_editor.html +* Since: 3.1.0-b2 +* Purpose: Add content after the quick reply panel (but inside the form) + +quickreply_editor_panel_before +=== +* Locations: + + styles/prosilver/template/quickreply_editor.html +* Since: 3.1.0-b2 +* Purpose: Add content before the quick reply panel (but inside the form) + +quickreply_editor_subject_before +=== +* Locations: + + styles/prosilver/template/quickreply_editor.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the quick reply subject textbox + +search_body_form_after +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the search form + +search_body_form_before +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.5-RC1 +* Purpose: Add content before the search form + +search_body_recent_search_after +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the recent search queries list + +search_body_recent_search_before +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the recent search queries list + +search_body_search_display_options_append +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Put content at the bottom of the search query display options fields set + +search_body_search_display_options_prepend +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Put content at the top of the search query display options fields set + +search_body_search_options_after +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the search query options fields set + +search_body_search_options_append +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Put content at the bottom of the search query options fields set + +search_body_search_options_before +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the search query options fields set + +search_body_search_options_prepend +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Put content at the top of the search query options fields set + +search_body_search_query_after +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the search query fields set + +search_body_search_query_append +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Put content at the bottom of the search query fields set + +search_body_search_query_before +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the search query fields set + +search_body_search_query_prepend +=== +* Locations: + + styles/prosilver/template/search_body.html +* Since: 3.1.7-RC1 +* Purpose: Put content at the top of the search query fields set + +search_results_content_after +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.2.4-RC1 +* Purpose: Add content after the message content in search results + +search_results_header_after +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the header of the search results + +search_results_header_before +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the header of the search results. + +search_results_last_post_author_username_append +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.2.4-RC1 +* Purpose: Append information to last post author username of member + +search_results_last_post_author_username_prepend +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.2.4-RC1 +* Purpose: Prepend information to last post author username of member + +search_results_post_after +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.0-b3 +* Purpose: Add data after search result posts + +search_results_post_author_username_append +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.2.4-RC1 +* Purpose: Append information to post author username of member + +search_results_post_author_username_prepend +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.2.4-RC1 +* Purpose: Prepend information to post author username of member + +search_results_post_before +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.0-b3 +* Purpose: Add data before search result posts + +search_results_postprofile_after +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.0-b3 +* Purpose: Add content after the post author and stats in search results (posts view mode) + +search_results_postprofile_before +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.0-b3 +* Purpose: Add content directly before the post author in search results (posts view mode) + +search_results_searchbox_after +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.4-RC1 +* Purpose: Add content right after the searchbox of the search results. + +search_results_topic_after +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.0-b4 +* Purpose: Add data after search result topics + +search_results_topic_author_username_append +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.2.4-RC1 +* Purpose: Append information to topic author username of member + +search_results_topic_author_username_prepend +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.2.4-RC1 +* Purpose: Prepend information to topic author username of member + +search_results_topic_before +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.0-b4 +* Purpose: Add data before search result topics + +search_results_topic_title_after +=== +* Locations: + + styles/prosilver/template/search_results.html +* Since: 3.1.11-RC1 +* Purpose: Add data after search results topic title + +simple_footer_after +=== +* Locations: + + styles/prosilver/template/simple_footer.html +* Since: 3.1.0-a1 +* Purpose: Add content prior to the scripts of the simple footer + +simple_footer_body_after +=== +* Locations: + + styles/prosilver/template/simple_footer.html +* Since: 3.2.4-RC1 +* Purpose: Add content directly prior to the `` tag of the simple footer + +simple_header_body_before +=== +* Locations: + + styles/prosilver/template/simple_header.html +* Since: 3.1.0-b2 +* Purpose: Add content to the header body + +simple_header_head_append +=== +* Locations: + + styles/prosilver/template/simple_header.html +* Since: 3.1.0-b4 +* Purpose: Add asset calls directly before the `` tag + +simple_header_stylesheets_after +=== +* Locations: + + styles/prosilver/template/simple_header.html +* Since: 3.1.0-RC3 +* Purpose: Add asset calls after stylesheets within the `` tag. +Note that INCLUDECSS will not work with this event. + +topiclist_row_append +=== +* Locations: + + styles/prosilver/template/search_results.html + + styles/prosilver/template/viewforum_body.html + + styles/prosilver/template/mcp_forum.html +* Since: 3.1.0-a1 +* Changed: 3.1.6-RC1 Added event to mcp_forum.html +* Purpose: Add content into topic rows (inside the elements containing topic titles) + +topiclist_row_prepend +=== +* Locations: + + styles/prosilver/template/search_results.html + + styles/prosilver/template/viewforum_body.html + + styles/prosilver/template/mcp_forum.html +* Since: 3.1.0-a1 +* Changed: 3.1.6-RC1 Added event to mcp_forum.html +* Purpose: Add content into topic rows (inside the elements containing topic titles) + +topiclist_row_topic_title_after +=== +* Locations: + + styles/prosilver/template/search_results.html + + styles/prosilver/template/viewforum_body.html + + styles/prosilver/template/mcp_forum.html +* Since: 3.1.10-RC1 +* Purpose: Add content into topic rows (after the elements containing the topic titles) + +ucp_agreement_terms_after +=== +* Locations: + + styles/prosilver/template/ucp_agreement.html +* Since: 3.1.0-b3 +* Purpose: Add content after the terms of agreement text at user registration + +ucp_agreement_terms_before +=== +* Locations: + + styles/prosilver/template/ucp_agreement.html +* Since: 3.1.0-b3 +* Purpose: Add content before the terms of agreement text at user registration + +ucp_friend_list_after +=== +* Locations: + + styles/prosilver/template/ucp_zebra_friends.html +* Since: 3.1.0-a4 +* Purpose: Add optional elements after list of friends in UCP + +ucp_friend_list_before +=== +* Locations: + + styles/prosilver/template/ucp_zebra_friends.html +* Since: 3.1.0-a4 +* Purpose: Add optional elements before list of friends in UCP + +ucp_main_front_user_activity_after +=== +* Locations: + + styles/prosilver/template/ucp_main_front.html +* Since: 3.1.6-RC1 +* Purpose: Add content right after the user activity info viewing UCP front page + +ucp_main_front_user_activity_append +=== +* Locations: + + styles/prosilver/template/ucp_main_front.html +* Since: 3.1.11-RC1 +* Purpose: Add content after last user activity info viewing UCP front page + +ucp_main_front_user_activity_before +=== +* Locations: + + styles/prosilver/template/ucp_main_front.html +* Since: 3.1.6-RC1 +* Purpose: Add content right before the user activity info viewing UCP front page + +ucp_main_front_user_activity_prepend +=== +* Locations: + + styles/prosilver/template/ucp_main_front.html +* Since: 3.1.11-RC1 +* Purpose: Add content before first user activity info viewing UCP front page + +ucp_pm_history_post_buttons_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_history.html +* Since: 3.1.6-RC1 +* Purpose: Add post button to private messages in history review (next to quote etc), at +the end of the list. + +ucp_pm_history_post_buttons_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_history.html +* Since: 3.1.6-RC1 +* Purpose: Add post button to private messages in history review (next to quote etc), at +the start of the list. + +ucp_pm_history_post_buttons_list_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_history.html +* Since: 3.1.6-RC1 +* Purpose: Add post button custom list to private messages in history review (next to quote etc), +after the original list. + +ucp_pm_history_post_buttons_list_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_history.html +* Since: 3.1.6-RC1 +* Purpose: Add post button custom list to private messages in history review (next to quote etc), +before the original list. + +ucp_pm_history_review_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_history.html +* Since: 3.1.6-RC1 +* Purpose: Add content after the private messages history review. + +ucp_pm_history_review_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_history.html +* Since: 3.1.6-RC1 +* Purpose: Add content before the private messages history review. + +ucp_pm_viewmessage_avatar_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.0-RC3 +* Purpose: Add content right after the avatar when viewing a private message + +ucp_pm_viewmessage_avatar_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.0-RC3 +* Purpose: Add content right before the avatar when viewing a private message + +ucp_pm_viewmessage_contact_fields_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.0-b1 +* Purpose: Add data after the contact fields on the user profile when viewing +a private message + +ucp_pm_viewmessage_contact_fields_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.0-b1 +* Purpose: Add data before the contact fields on the user profile when viewing +a private message + +ucp_pm_viewmessage_custom_fields_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.0-a1 +* Purpose: Add data after the custom fields on the user profile when viewing +a private message + +ucp_pm_viewmessage_custom_fields_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.0-a1 +* Purpose: Add data before the custom fields on the user profile when viewing +a private message + +ucp_pm_viewmessage_options_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.11-RC1 +* Purpose: Add content right before display options + +ucp_pm_viewmessage_post_buttons_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.0-RC3 +* Purpose: Add post button to private messages (next to edit, quote etc), at +the end of the list. + +ucp_pm_viewmessage_post_buttons_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.0-RC3 +* Purpose: Add post button to private messages (next to edit, quote etc), at +the start of the list. + +ucp_pm_viewmessage_post_buttons_list_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.6-RC1 +* Purpose: Add post button custom list to private messages (next to edit, quote etc), +after the original list. + +ucp_pm_viewmessage_post_buttons_list_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.6-RC1 +* Purpose: Add post button custom list to private messages (next to edit, quote etc), +before the original list. + +ucp_pm_viewmessage_print_head_append +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage_print.html +* Since: 3.1.0-a1 +* Purpose: Add asset calls directly before the `` tag of the Print PM screen + +ucp_pm_viewmessage_rank_after +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.6-RC1 +* Purpose: Add data after the rank on the user profile when viewing +a private message + +ucp_pm_viewmessage_rank_before +=== +* Locations: + + styles/prosilver/template/ucp_pm_viewmessage.html +* Since: 3.1.6-RC1 +* Purpose: Add data before the rank on the user profile when viewing +a private message + +ucp_prefs_personal_append +=== +* Locations: + + styles/prosilver/template/ucp_prefs_personal.html +* Since: 3.1.0-a1 +* Purpose: Add user options to the bottom of the Edit Global Settings block + +ucp_prefs_personal_prepend +=== +* Locations: + + styles/prosilver/template/ucp_prefs_personal.html +* Since: 3.1.0-a1 +* Purpose: Add user options to the top of the Edit Global Settings block + +ucp_prefs_post_append +=== +* Locations: + + styles/prosilver/template/ucp_prefs_post.html +* Since: 3.1.0-a1 +* Purpose: Add user options to the bottom of the Edit Posting Defaults block + +ucp_prefs_post_prepend +=== +* Locations: + + styles/prosilver/template/ucp_prefs_post.html +* Since: 3.1.0-a1 +* Purpose: Add user options to the top of the Edit Posting Defaults block + +ucp_prefs_view_radio_buttons_append +=== +* Locations: + + styles/prosilver/template/ucp_prefs_view.html +* Since: 3.1.0-a1 +* Purpose: Add options to the bottom of the radio buttons block of the Edit +Display Options screen + +ucp_prefs_view_radio_buttons_prepend +=== +* Locations: + + styles/prosilver/template/ucp_prefs_view.html +* Since: 3.1.0-a1 +* Purpose: Add options to the top of the radio buttons block of the Edit +Display Options screen + +ucp_prefs_view_select_menu_append +=== +* Locations: + + styles/prosilver/template/ucp_prefs_view.html +* Since: 3.1.0-a1 +* Purpose: Add options to the bottom of the drop-down lists block of the Edit +Display Options screen + +ucp_prefs_view_select_menu_prepend +=== +* Locations: + + styles/prosilver/template/ucp_prefs_view.html +* Since: 3.1.0-a1 +* Purpose: Add options to the top of the drop-down lists block of the Edit +Display Options screen + +ucp_profile_profile_info_after +=== +* Locations: + + styles/prosilver/template/ucp_profile_profile_info.html +* Since: 3.1.4-RC1 +* Purpose: Add options in profile page fieldset - after custom profile fields. + +ucp_profile_profile_info_before +=== +* Locations: + + styles/prosilver/template/ucp_profile_profile_info.html +* Since: 3.1.4-RC1 +* Purpose: Add options in profile page fieldset - before jabber field. + +ucp_profile_register_details_after +=== +* Locations: + + styles/prosilver/template/ucp_profile_reg_details.html +* Since: 3.1.4-RC1 +* Purpose: Add options in profile page fieldset - after confirm password field. + +ucp_profile_register_details_before +=== +* Locations: + + styles/prosilver/template/ucp_profile_reg_details.html +* Since: 3.1.4-RC1 +* Purpose: Add options in profile page fieldset - before first field. + +ucp_profile_signature_posting_editor_options_prepend +=== +* Locations: + + styles/prosilver/template/ucp_profile_signature.html +* Since: 3.2.6-RC1 +* Purpose: Add options signature posting editor - before first option. + +ucp_register_buttons_before +=== +* Locations: + + styles/prosilver/template/ucp_register.html +* Since: 3.1.11-RC1 +* Purpose: Add content before buttons in registration form. + +ucp_register_credentials_after +=== +* Locations: + + styles/prosilver/template/ucp_register.html +* Since: 3.1.0-b5 +* Purpose: Add options in registration page fieldset - after password field. + +ucp_register_credentials_before +=== +* Locations: + + styles/prosilver/template/ucp_register.html +* Since: 3.1.0-b5 +* Purpose: Add options in registration page fieldset - before first field. + +ucp_register_options_before +=== +* Locations: + + styles/prosilver/template/ucp_register.html +* Since: 3.1.0-b5 +* Purpose: Add options in registration page fieldset - before language selector. + +ucp_register_profile_fields_after +=== +* Locations: + + styles/prosilver/template/ucp_register.html +* Since: 3.1.0-b5 +* Purpose: Add options in registration page fieldset - after last field. + +ucp_register_profile_fields_before +=== +* Locations: + + styles/prosilver/template/ucp_register.html +* Since: 3.1.0-b5 +* Purpose: Add options in registration page fieldset - before profile fields. + +viewforum_body_last_post_author_username_append +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.2.4-RC1 +* Purpose: Append information to last post author username of member + +viewforum_body_last_post_author_username_prepend +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.2.4-RC1 +* Purpose: Prepend information to last post author username of member + +viewforum_body_topic_author_username_append +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.2.4-RC1 +* Purpose: Append information to topic author username of member + +viewforum_body_topic_author_username_prepend +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.2.4-RC1 +* Purpose: Prepend information to topic author username of member + +viewforum_body_topic_row_after +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content after the topic list item. + +viewforum_body_topic_row_append +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the start of the topic list item. + +viewforum_body_topic_row_before +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content before the topic list item. + +viewforum_body_topic_row_prepend +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content at the end of the topic list item. + +viewforum_body_topicrow_row_before +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.10-RC1 +* Purpose: Add content before list of topics. + +viewforum_buttons_bottom_after +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.0-RC5 +* Purpose: Add buttons after New Topic button on the bottom of the topic's list + +viewforum_buttons_bottom_before +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.0-RC5 +* Purpose: Add buttons before New Topic button on the bottom of the topic's list + +viewforum_buttons_top_after +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.0-RC5 +* Purpose: Add buttons after New Topic button on the top of the topic's list + +viewforum_buttons_top_before +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.0-RC5 +* Purpose: Add buttons before New Topic button on the top of the topic's list + +viewforum_forum_name_append +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.0-b3 +* Purpose: Add content directly after the forum name link on the View forum screen + +viewforum_forum_name_prepend +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.0-b3 +* Purpose: Add content directly before the forum name link on the View forum screen + +viewforum_forum_title_after +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.5-RC1 +* Purpose: Add content directly after the forum title on the View forum screen + +viewforum_forum_title_before +=== +* Locations: + + styles/prosilver/template/viewforum_body.html +* Since: 3.1.5-RC1 +* Purpose: Add content directly before the forum title on the View forum screen + +viewonline_body_username_append +=== +* Locations: + + styles/prosilver/template/viewonline_body.html +* Since: 3.2.4-RC1 +* Purpose: Append information to username of member + +viewonline_body_username_prepend +=== +* Locations: + + styles/prosilver/template/viewonline_body.html +* Since: 3.2.4-RC1 +* Purpose: Prepend information to username of member + +viewtopic_body_avatar_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-RC3 +* Purpose: Add content right after the avatar when viewing topics + +viewtopic_body_avatar_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-RC3 +* Purpose: Add content right before the avatar when viewing topics + +viewtopic_body_contact_fields_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b3 +* Purpose: Add data after the contact fields on the user profile when viewing +a post + +viewtopic_body_contact_fields_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b3 +* Purpose: Add data before the contact fields on the user profile when viewing +a post + +viewtopic_body_footer_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a1 +* Purpose: Add content to the bottom of the View topic screen below the posts +and quick reply, directly before the jumpbox in Prosilver. + +viewtopic_body_pagination_top_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the pagination at top + +viewtopic_body_poll_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.6-RC1 +* Purpose: Add content after the poll panel. + +viewtopic_body_poll_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.6-RC1 +* Purpose: Add content before the poll panel. + +viewtopic_body_poll_option_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b3 +* Purpose: Add content after the poll option +the list. + +viewtopic_body_poll_option_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b3 +* Purpose: Add content before the poll option +the list. + +viewtopic_body_poll_question_append +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b3 +* Purpose: Add content directly after the poll question on the View topic screen + +viewtopic_body_poll_question_prepend +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b3 +* Purpose: Add content directly before the poll question on the View topic screen + +viewtopic_body_post_author_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.3-RC1 +* Purpose: Add content directly after the post author on the view topic screen + +viewtopic_body_post_author_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.3-RC1 +* Purpose: Add content directly before the post author on the view topic screen + +viewtopic_body_post_buttons_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a1 +* Purpose: Add post button to posts (next to edit, quote etc), at the end of +the list. + +viewtopic_body_post_buttons_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a1 +* Purpose: Add post button to posts (next to edit, quote etc), at the start of +the list. + +viewtopic_body_post_buttons_list_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.5-RC1 +* Purpose: Add post button custom list to posts (next to edit, quote etc), +after the original list. + +viewtopic_body_post_buttons_list_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.5-RC1 +* Purpose: Add post button custom list to posts (next to edit, quote etc), +before the original list. + +viewtopic_body_post_subject_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.7-RC1 +* Purpose: Add data before post icon and subject + +viewtopic_body_postrow_back2top_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.8-RC1 +* Purpose: Add content to the post's bottom after the back to top link + +viewtopic_body_postrow_back2top_append +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.8-RC1 +* Purpose: Add content to the post's bottom directly after the back to top link + +viewtopic_body_postrow_back2top_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.8-RC1 +* Purpose: Add content to the post's bottom before the back to top link + +viewtopic_body_postrow_back2top_prepend +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.8-RC1 +* Purpose: Add content to the post's bottom directly before the back to top link + +viewtopic_body_postrow_content_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.2.4-RC1 +* Purpose: Add content after the message content in topics views + +viewtopic_body_postrow_custom_fields_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a1 +* Purpose: Add data after the custom fields on the user profile when viewing +a post + +viewtopic_body_postrow_custom_fields_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a1 +* Purpose: Add data before the custom fields on the user profile when viewing +a post + +viewtopic_body_postrow_post_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a4 +* Purpose: Add data after posts + +viewtopic_body_postrow_post_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a4 +* Purpose: Add data before posts + +viewtopic_body_postrow_post_content_footer +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-RC4 +* Purpose: Add data at the end of the posts. + +viewtopic_body_postrow_post_details_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.4-RC1 +* Purpose: Add content after the post details + +viewtopic_body_postrow_post_details_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.4-RC1 +* Purpose: Add content before the post details + +viewtopic_body_postrow_post_notices_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b2 +* Purpose: Add posts specific custom notices at the notices bottom. + +viewtopic_body_postrow_post_notices_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b2 +* Purpose: Add posts specific custom notices at the notices top. + +viewtopic_body_postrow_rank_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.6-RC1 +* Purpose: Add data after the rank on the user profile when viewing +a post + +viewtopic_body_postrow_rank_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.6-RC1 +* Purpose: Add data before the rank on the user profile when viewing +a post + +viewtopic_body_topic_actions_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a4 +* Purpose: Add data before the topic actions buttons (after the posts sorting options) + +viewtopic_buttons_bottom_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-RC5 +* Purpose: Add buttons after Post Reply button on the bottom of the posts's list + +viewtopic_buttons_bottom_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-RC5 +* Purpose: Add buttons before Post Reply button on the bottom of the posts's list + +viewtopic_buttons_top_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-RC5 +* Purpose: Add buttons after Post Reply button on the top of the posts's list + +viewtopic_buttons_top_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-RC5 +* Purpose: Add buttons before Post Reply button on the top of the posts's list + +viewtopic_dropdown_bottom_custom +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.6-RC1 +* Purpose: Create a custom dropdown menu + +viewtopic_dropdown_top_custom +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.6-RC1 +* Purpose: Create a custom dropdown menu + +viewtopic_print_head_append +=== +* Locations: + + styles/prosilver/template/viewtopic_print.html +* Since: 3.1.0-a1 +* Purpose: Add asset calls directly before the `` tag of the Print Topic screen + +viewtopic_topic_title_after +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.7-RC1 +* Purpose: Add content directly after the topic title link on the View topic screen (outside of the h2 HTML tag) + +viewtopic_topic_title_append +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-b3 +* Purpose: Add content directly after the topic title link on the View topic screen + +viewtopic_topic_title_before +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.2.2-RC1 +* Purpose: Add content directly before the topic title link on the View topic screen (outside of the h2 HTML tag) + +viewtopic_topic_title_prepend +=== +* Locations: + + styles/prosilver/template/viewtopic_body.html +* Since: 3.1.0-a1 +* Purpose: Add content directly before the topic title link on the View topic screen + +viewtopic_topic_tools_after +=== +* Locations: + + styles/prosilver/template/viewtopic_topic_tools.html +* Since: 3.1.0-a3 +* Purpose: Add a new topic tool after the rest of the existing ones + +viewtopic_topic_tools_before +=== +* Locations: + + styles/prosilver/template/viewtopic_topic_tools.html +* Since: 3.1.0-a3 +* Purpose: Add a new topic tool before the rest of the existing ones diff --git a/docs/install-config.sample.yml b/docs/install-config.sample.yml new file mode 100644 index 0000000..a354e52 --- /dev/null +++ b/docs/install-config.sample.yml @@ -0,0 +1,38 @@ +installer: + admin: + name: admin + password: adminadmin + email: admin@example.org + + board: + lang: en + name: My Board + description: My amazing new phpBB board + + database: + dbms: sqlite3 + dbhost: /tmp/phpbb.sqlite3 + dbport: ~ + dbuser: ~ + dbpasswd: ~ + dbname: ~ + table_prefix: phpbb_ + + email: + enabled: false + smtp_delivery : ~ + smtp_host: ~ + smtp_port: ~ + smtp_auth: ~ + smtp_user: ~ + smtp_pass: ~ + + server: + cookie_secure: false + server_protocol: http:// + force_server_vars: false + server_name: localhost + server_port: 80 + script_path: / + + extensions: ['phpbb/viglink'] diff --git a/docs/lighttpd.sample.conf b/docs/lighttpd.sample.conf new file mode 100644 index 0000000..f5b509e --- /dev/null +++ b/docs/lighttpd.sample.conf @@ -0,0 +1,79 @@ +# Sample lighttpd configuration file for phpBB. +# Global settings have been removed, copy them +# from your system's lighttpd.conf. +# Tested with lighttpd 1.4.35 + +# If you want to use the X-Sendfile feature, +# uncomment the 'allow-x-send-file' for the fastcgi +# server below and add the following to your config.php +# +# define('PHPBB_ENABLE_X_SENDFILE', true); +# +# See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile +# for the details on X-Sendfile. + +# Load moules +server.modules += ( + "mod_access", + "mod_fastcgi", + "mod_rewrite", + "mod_accesslog" +) + +# If you have domains with and without www prefix, +# redirect one to the other. +$HTTP["host"] =~ "^(myforums\.com)$" { + url.redirect = ( + ".*" => "http://www.%1$0" + ) +} + +$HTTP["host"] == "www.myforums.com" { + server.name = "www.myforums.com" + server.document-root = "/path/to/phpbb" + server.dir-listing = "disable" + + index-file.names = ( "index.php", "index.htm", "index.html" ) + accesslog.filename = "/var/log/lighttpd/access-www.myforums.com.log" + + # Deny access to internal phpbb files. + $HTTP["url"] =~ "^/(config\.php|common\.php|cache|files|images/avatars/upload|includes|phpbb|store|vendor)" { + url.access-deny = ( "" ) + } + + # Deny access to version control system directories. + $HTTP["url"] =~ "/\.svn|/\.git" { + url.access-deny = ( "" ) + } + + # Deny access to apache configuration files. + $HTTP["url"] =~ "/\.htaccess|/\.htpasswd|/\.htgroups" { + url.access-deny = ( "" ) + } + + # The following 3 lines will rewrite URLs passed through the front controller + # to not require app.php in the actual URL. In other words, a controller is + # by default accessed at /app.php/my/controller, but can also be accessed at + # /my/controller + url.rewrite-if-not-file = ( + "^/(.*)$" => "/app.php/$1" + ) + + fastcgi.server = ( ".php" => + (( + "bin-path" => "/usr/bin/php-cgi", + "socket" => "/tmp/php.socket", + "max-procs" => 4, + "idle-timeout" => 30, + "bin-environment" => ( + "PHP_FCGI_CHILDREN" => "10", + "PHP_FCGI_MAX_REQUESTS" => "10000" + ), + "bin-copy-environment" => ( + "PATH", "SHELL", "USER" + ), + #"allow-x-send-file" => "enable", + "broken-scriptfilename" => "enable" + )) + ) +} diff --git a/docs/nginx.sample.conf b/docs/nginx.sample.conf new file mode 100644 index 0000000..55c01a1 --- /dev/null +++ b/docs/nginx.sample.conf @@ -0,0 +1,127 @@ +# Sample nginx configuration file for phpBB. +# Global settings have been removed, copy them +# from your system's nginx.conf. +# Tested with nginx 0.8.35. + +# If you want to use the X-Accel-Redirect feature, +# add the following to your config.php. +# +# define('PHPBB_ENABLE_X_ACCEL_REDIRECT', true); +# +# See http://wiki.nginx.org/XSendfile for the details +# on X-Accel-Redirect. + +http { + # Compression - requires gzip and gzip static modules. + gzip on; + gzip_static on; + gzip_vary on; + gzip_http_version 1.1; + gzip_min_length 700; + + # Compression levels over 6 do not give an appreciable improvement + # in compression ratio, but take more resources. + gzip_comp_level 6; + + # IE 6 and lower do not support gzip with Vary correctly. + gzip_disable "msie6"; + # Before nginx 0.7.63: + #gzip_disable "MSIE [1-6]\."; + + # Catch-all server for requests to invalid hosts. + # Also catches vulnerability scanners probing IP addresses. + server { + # default specifies that this block is to be used when + # no other block matches. + listen 80 default; + + server_name bogus; + return 444; + root /var/empty; + } + + # If you have domains with and without www prefix, + # redirect one to the other. + server { + # Default port is 80. + #listen 80; + + server_name myforums.com; + + # A trick from http://wiki.nginx.org/Pitfalls#Taxing_Rewrites: + return 301 http://www.myforums.com$request_uri; + } + + # The actual board domain. + server { + #listen 80; + server_name www.myforums.com; + + root /path/to/phpbb; + + location / { + # phpBB uses index.htm + index index.php index.html index.htm; + try_files $uri $uri/ @rewriteapp; + } + + location @rewriteapp { + rewrite ^(.*)$ /app.php/$1 last; + } + + # Deny access to internal phpbb files. + location ~ /(config\.php|common\.php|cache|files|images/avatars/upload|includes|(?= $start AND p.post_id <= $end + sql_query_post = + sql_query_post_index = UPDATE phpbb_sphinx SET max_doc_id = $maxid WHERE counter_id = 1 + sql_attr_uint = forum_id + sql_attr_uint = topic_id + sql_attr_uint = poster_id + sql_attr_uint = post_visibility + sql_attr_bool = topic_first_post + sql_attr_bool = deleted + sql_attr_timestamp = post_time + sql_attr_timestamp = topic_last_post_time + sql_attr_string = post_subject +} +source source_phpbb_{SPHINX_ID}_delta : source_phpbb_{SPHINX_ID}_main +{ + sql_query_pre = + sql_query_range = + sql_range_step = + sql_query = SELECT \ + p.post_id AS id, \ + p.forum_id, \ + p.topic_id, \ + p.poster_id, \ + p.post_visibility, \ + CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, \ + p.post_time, \ + p.post_subject, \ + p.post_subject as title, \ + p.post_text as data, \ + t.topic_last_post_time, \ + 0 as deleted \ + FROM phpbb_posts p, phpbb_topics t \ + WHERE \ + p.topic_id = t.topic_id \ + AND p.post_id >= ( SELECT max_doc_id FROM phpbb_sphinx WHERE counter_id=1 ) + sql_query_pre = +} +index index_phpbb_{SPHINX_ID}_main +{ + path = {DATA_PATH}/index_phpbb_{SPHINX_ID}_main + source = source_phpbb_{SPHINX_ID}_main + docinfo = extern + morphology = none + stopwords = + min_word_len = 2 + charset_table = U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF + min_prefix_len = 0 + min_infix_len = 0 +} +index index_phpbb_{SPHINX_ID}_delta : index_phpbb_{SPHINX_ID}_main +{ + path = {DATA_PATH}/index_phpbb_{SPHINX_ID}_delta + source = source_phpbb_{SPHINX_ID}_delta +} +indexer +{ + mem_limit = 512M +} +searchd +{ + listen = localhost:9312 + log = {DATA_PATH}/log/searchd.log + query_log = {DATA_PATH}/log/sphinx-query.log + read_timeout = 5 + max_children = 30 + pid_file = {DATA_PATH}/searchd.pid + binlog_path = {DATA_PATH} +} diff --git a/docs/update-config.sample.yml b/docs/update-config.sample.yml new file mode 100644 index 0000000..caa1a9e --- /dev/null +++ b/docs/update-config.sample.yml @@ -0,0 +1,3 @@ +updater: + type: all + extensions: ['phpbb/viglink'] diff --git a/docs/vagrant.md b/docs/vagrant.md new file mode 100644 index 0000000..ac31827 --- /dev/null +++ b/docs/vagrant.md @@ -0,0 +1,123 @@ +## Using Vagrant with phpBB + +phpBB includes support for Vagrant. This allows developers and contributors to run phpBB without the need to set up their own local web server with traditional WAMP/MAMP stacks. It also provides a consistent environment between developers for writing and debugging code changes more productively. + +phpBB uses the [Laravel/Homestead](https://laravel.com/docs/5.1/homestead) Vagrant box. It runs a Linux server with Ubuntu 14.04, PHP 5.6, Nginx, SQLite3, MySQL, and a whole lot more (complete specs below). + +## Get Started + +* Download and Install [Vagrant](https://www.vagrantup.com/downloads.html) +* Download and Install [VirtualBox](https://www.virtualbox.org/wiki/Downloads) +* Run `vagrant up` from the root of your cloned fork of the phpBB Git repository + +```sh +$ vagrant up +``` + +* Access phpBB at `http://192.168.10.10/` +* Username: **admin** +* Password: **adminadmin** + +## Additional commands: +* Access your Linux server from the command line: + +```sh +$ vagrant ssh +``` + +* Pause your server: + +```sh +$ vagrant suspend +``` + +* Shut down your server: + +```sh +$ vagrant halt +``` + +* Delete and remove your server: + +```sh +$ vagrant destroy +``` + +> Note: destroying the vagrant server will remove all traces of the VM from your computer, reclaiming any disk space used by it. However, it also means the next time you vagrant up, you will be creating a brand new VM with a fresh install of phpBB and a new database. + +## Customising the phpBB configuration + +By default, phpBB is pre-configured to install with a MySQL database. You can, however, switch to PostegreSQL or SQLite3 by editing the `phpbb-install-config.yml` file in the vagrant directory. The next time you run `vagrant up` (or `vagrant provision`) it will be installed under the new configuration. + +If you prefer to access phpBB from the more friendly URL `http://phpbb.app` then you must update your computer's hosts file. This file is typically located at `/etc/hosts` for Mac/Linux or `C:\Windows\System32\drivers\etc\hosts` for Windows. Open this file and add the following line to it, at the very bottom, and save. + +``` +192.168.10.10 phpbb.app +``` + +## How it all works + +When you vagrant up, the Laravel/Homestead box is transparently loaded as a Virtual Machine on your computer (this may take several minutes the very first time while it downloads the VM image to your computer). Your local phpBB repository clone is mirrored/shared with the VM, so you can work on the phpBB code on your computer, and see the changes immediately when you browse to phpBB at the URL provided by the VM. + +This is very similar to traditional methods of working with a local WAMP/MAMP stack, except the webserver is now being provided by a VM of a Linux server. The advantages here are the exact same Linux server environment is being used by everybody who uses Vagrant with phpBB, so there will be consist behaviour unlike when everybody is developing on different versions of PHP, server configurations, etc. + +The environment is also "sandboxed" from your system. This means you don't need to worry about adjusting your own computer's internal PHP settings, setting up databases, or doing damage to your system or to phpBB. Other than the phpBB codebase, which lives on your computer, all execution is taking place within the VM and you can at any time, halt or destroy the VM and start a brand new one. + +There are some caveats, however. You can only run one vagrant VM for the phpBB repository. And of course, the database will be destroyed when you vagrant destroy. If the database is important, you should SSH into your vagrant VM and export/import the DB as needed using SSH commands. + +For example, to export/import a MySQL database (using phpBB's `store` directory): + +SSH into the VM + +```sh +$ vagrant ssh +``` + +Export MySQL: + +```sh +$ mysqldump -uhomestead -psecret phpbb > /home/vagrant/phpbb/phpBB/store/phpbb.sql +``` + +Import MySQL: + +```sh +$ mysql -uhomestead -psecret phpbb < /home/vagrant/phpbb/phpBB/store/phpbb.sql +``` + +--- + +## About the Laravel/Homestead box + +### Included Software + +* Ubuntu 14.04 +* Git +* PHP 5.6 +* HHVM +* Nginx +* MySQL +* Sqlite3 +* Postgres +* Composer +* Node (With PM2, Bower, Grunt, and Gulp) +* Redis +* Memcached +* Beanstalkd +* Blackfire Profiler + +### MySQL Access + +- Hostname: 127.0.0.1 +- Username: homestead +- Password: secret +- Database: phpbb +- Port: 3306 + +### PostgreSQL Access + +- Hostname: 127.0.0.1 +- Username: homestead +- Password: secret +- Database: phpbb +- Port: 5432 diff --git a/download/file.php b/download/file.php new file mode 100644 index 0000000..9ee489c --- /dev/null +++ b/download/file.php @@ -0,0 +1,326 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './../'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); + +// Thank you sun. +if (isset($_SERVER['CONTENT_TYPE'])) +{ + if ($_SERVER['CONTENT_TYPE'] === 'application/x-java-archive') + { + exit; + } +} +else if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Java') !== false) +{ + exit; +} + +if (isset($_GET['avatar'])) +{ + require($phpbb_root_path . 'includes/startup.' . $phpEx); + + require($phpbb_root_path . 'phpbb/class_loader.' . $phpEx); + $phpbb_class_loader = new \phpbb\class_loader('phpbb\\', "{$phpbb_root_path}phpbb/", $phpEx); + $phpbb_class_loader->register(); + + $phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx); + extract($phpbb_config_php_file->get_all()); + + if (!defined('PHPBB_ENVIRONMENT')) + { + @define('PHPBB_ENVIRONMENT', 'production'); + } + + if (!defined('PHPBB_INSTALLED') || empty($dbms) || empty($acm_type)) + { + exit; + } + + require($phpbb_root_path . 'includes/constants.' . $phpEx); + require($phpbb_root_path . 'includes/functions.' . $phpEx); + require($phpbb_root_path . 'includes/functions_download' . '.' . $phpEx); + require($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); + + // Setup class loader first + $phpbb_class_loader_ext = new \phpbb\class_loader('\\', "{$phpbb_root_path}ext/", $phpEx); + $phpbb_class_loader_ext->register(); + + // Set up container + $phpbb_container_builder = new \phpbb\di\container_builder($phpbb_root_path, $phpEx); + $phpbb_container = $phpbb_container_builder->with_config($phpbb_config_php_file)->get_container(); + + $phpbb_class_loader->set_cache($phpbb_container->get('cache.driver')); + $phpbb_class_loader_ext->set_cache($phpbb_container->get('cache.driver')); + + // set up caching + /* @var $cache \phpbb\cache\service */ + $cache = $phpbb_container->get('cache'); + + /* @var $phpbb_dispatcher \phpbb\event\dispatcher */ + $phpbb_dispatcher = $phpbb_container->get('dispatcher'); + + /* @var $request \phpbb\request\request_interface */ + $request = $phpbb_container->get('request'); + + /* @var $db \phpbb\db\driver\driver_interface */ + $db = $phpbb_container->get('dbal.conn'); + + /* @var $phpbb_log \phpbb\log\log_interface */ + $phpbb_log = $phpbb_container->get('log'); + + unset($dbpasswd); + + /* @var $config \phpbb\config\config */ + $config = $phpbb_container->get('config'); + + // load extensions + /* @var $phpbb_extension_manager \phpbb\extension\manager */ + $phpbb_extension_manager = $phpbb_container->get('ext.manager'); + + // worst-case default + $browser = strtolower($request->header('User-Agent', 'msie 6.0')); + + /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ + $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); + + $filename = $request->variable('avatar', ''); + $avatar_group = false; + $exit = false; + + if (isset($filename[0]) && $filename[0] === 'g') + { + $avatar_group = true; + $filename = substr($filename, 1); + } + + // '==' is not a bug - . as the first char is as bad as no dot at all + if (strpos($filename, '.') == false) + { + send_status_line(403, 'Forbidden'); + $exit = true; + } + + if (!$exit) + { + $ext = substr(strrchr($filename, '.'), 1); + $stamp = (int) substr(stristr($filename, '_'), 1); + $filename = (int) $filename; + $exit = set_modified_headers($stamp, $browser); + } + if (!$exit && !in_array($ext, array('png', 'gif', 'jpg', 'jpeg'))) + { + // no way such an avatar could exist. They are not following the rules, stop the show. + send_status_line(403, 'Forbidden'); + $exit = true; + } + + + if (!$exit) + { + if (!$filename) + { + // no way such an avatar could exist. They are not following the rules, stop the show. + send_status_line(403, 'Forbidden'); + } + else + { + send_avatar_to_browser(($avatar_group ? 'g' : '') . $filename . '.' . $ext, $browser); + } + } + file_gc(); +} + +// implicit else: we are not in avatar mode +include($phpbb_root_path . 'common.' . $phpEx); +require($phpbb_root_path . 'includes/functions_download' . '.' . $phpEx); + +$attach_id = $request->variable('id', 0); +$mode = $request->variable('mode', ''); +$thumbnail = $request->variable('t', false); + +// Start session management, do not update session page. +$user->session_begin(false); +$auth->acl($user->data); +$user->setup('viewtopic'); + +$phpbb_content_visibility = $phpbb_container->get('content.visibility'); + +if (!$config['allow_attachments'] && !$config['allow_pm_attach']) +{ + send_status_line(404, 'Not Found'); + trigger_error('ATTACHMENT_FUNCTIONALITY_DISABLED'); +} + +if (!$attach_id) +{ + send_status_line(404, 'Not Found'); + trigger_error('NO_ATTACHMENT_SELECTED'); +} + +$sql = 'SELECT attach_id, post_msg_id, topic_id, in_message, poster_id, is_orphan, physical_filename, real_filename, extension, mimetype, filesize, filetime + FROM ' . ATTACHMENTS_TABLE . " + WHERE attach_id = $attach_id"; +$result = $db->sql_query($sql); +$attachment = $db->sql_fetchrow($result); +$db->sql_freeresult($result); + +if (!$attachment) +{ + send_status_line(404, 'Not Found'); + trigger_error('ERROR_NO_ATTACHMENT'); +} +else if (!download_allowed()) +{ + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['LINKAGE_FORBIDDEN']); +} +else +{ + $attachment['physical_filename'] = utf8_basename($attachment['physical_filename']); + + if (!$attachment['in_message'] && !$config['allow_attachments'] || $attachment['in_message'] && !$config['allow_pm_attach']) + { + send_status_line(404, 'Not Found'); + trigger_error('ATTACHMENT_FUNCTIONALITY_DISABLED'); + } + + if ($attachment['is_orphan']) + { + // We allow admins having attachment permissions to see orphan attachments... + $own_attachment = ($auth->acl_get('a_attach') || $attachment['poster_id'] == $user->data['user_id']) ? true : false; + + if (!$own_attachment || ($attachment['in_message'] && !$auth->acl_get('u_pm_download')) || (!$attachment['in_message'] && !$auth->acl_get('u_download'))) + { + send_status_line(404, 'Not Found'); + trigger_error('ERROR_NO_ATTACHMENT'); + } + + // Obtain all extensions... + $extensions = $cache->obtain_attach_extensions(true); + } + else + { + if (!$attachment['in_message']) + { + phpbb_download_handle_forum_auth($db, $auth, $attachment['topic_id']); + + $sql = 'SELECT forum_id, post_visibility + FROM ' . POSTS_TABLE . ' + WHERE post_id = ' . (int) $attachment['post_msg_id']; + $result = $db->sql_query($sql); + $post_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$post_row || !$phpbb_content_visibility->is_visible('post', $post_row['forum_id'], $post_row)) + { + // Attachment of a soft deleted post and the user is not allowed to see the post + send_status_line(404, 'Not Found'); + trigger_error('ERROR_NO_ATTACHMENT'); + } + } + else + { + // Attachment is in a private message. + $post_row = array('forum_id' => false); + phpbb_download_handle_pm_auth($db, $auth, $user->data['user_id'], $attachment['post_msg_id']); + } + + $extensions = array(); + if (!extension_allowed($post_row['forum_id'], $attachment['extension'], $extensions)) + { + send_status_line(403, 'Forbidden'); + trigger_error(sprintf($user->lang['EXTENSION_DISABLED_AFTER_POSTING'], $attachment['extension'])); + } + } + + $download_mode = (int) $extensions[$attachment['extension']]['download_mode']; + $display_cat = $extensions[$attachment['extension']]['display_cat']; + + if (($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$user->optionget('viewimg')) + { + $display_cat = ATTACHMENT_CATEGORY_NONE; + } + + if ($display_cat == ATTACHMENT_CATEGORY_FLASH && !$user->optionget('viewflash')) + { + $display_cat = ATTACHMENT_CATEGORY_NONE; + } + + /** + * Event to modify data before sending file to browser + * + * @event core.download_file_send_to_browser_before + * @var int attach_id The attachment ID + * @var array attachment Array with attachment data + * @var int display_cat Attachment category + * @var int download_mode File extension specific download mode + * @var array extensions Array with file extensions data + * @var string mode Download mode + * @var bool thumbnail Flag indicating if the file is a thumbnail + * @since 3.1.6-RC1 + * @changed 3.1.7-RC1 Fixing wrong name of a variable (replacing "extension" by "extensions") + */ + $vars = array( + 'attach_id', + 'attachment', + 'display_cat', + 'download_mode', + 'extensions', + 'mode', + 'thumbnail', + ); + extract($phpbb_dispatcher->trigger_event('core.download_file_send_to_browser_before', compact($vars))); + + if ($thumbnail) + { + $attachment['physical_filename'] = 'thumb_' . $attachment['physical_filename']; + } + else if ($display_cat == ATTACHMENT_CATEGORY_NONE && !$attachment['is_orphan'] && !phpbb_http_byte_range($attachment['filesize'])) + { + // Update download count + phpbb_increment_downloads($db, $attachment['attach_id']); + } + + if ($display_cat == ATTACHMENT_CATEGORY_IMAGE && $mode === 'view' && (strpos($attachment['mimetype'], 'image') === 0) && (strpos(strtolower($user->browser), 'msie') !== false) && !phpbb_is_greater_ie_version($user->browser, 7)) + { + wrap_img_in_html(append_sid($phpbb_root_path . 'download/file.' . $phpEx, 'id=' . $attachment['attach_id']), $attachment['real_filename']); + file_gc(); + } + else + { + // Determine the 'presenting'-method + if ($download_mode == PHYSICAL_LINK) + { + // This presenting method should no longer be used + if (!@is_dir($phpbb_root_path . $config['upload_path'])) + { + send_status_line(500, 'Internal Server Error'); + trigger_error($user->lang['PHYSICAL_DOWNLOAD_NOT_POSSIBLE']); + } + + redirect($phpbb_root_path . $config['upload_path'] . '/' . $attachment['physical_filename']); + file_gc(); + } + else + { + send_file_to_browser($attachment, $config['upload_path'], $display_cat); + file_gc(); + } + } +} diff --git a/download/index.htm b/download/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/download/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ext/index.htm b/ext/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/ext/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ext/phpbb/viglink/.gitignore b/ext/phpbb/viglink/.gitignore new file mode 100644 index 0000000..9940a70 --- /dev/null +++ b/ext/phpbb/viglink/.gitignore @@ -0,0 +1,2 @@ +/build/ +/vendor/ diff --git a/ext/phpbb/viglink/acp/viglink_helper.php b/ext/phpbb/viglink/acp/viglink_helper.php new file mode 100644 index 0000000..86568de --- /dev/null +++ b/ext/phpbb/viglink/acp/viglink_helper.php @@ -0,0 +1,146 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\acp; + +/** + * Class to handle allowing or disallowing VigLink services + */ +class viglink_helper +{ + /** @var \phpbb\cache\driver\driver_interface $cache */ + protected $cache; + + /** @var \phpbb\config\config $config */ + protected $config; + + /** @var \phpbb\file_downloader $file_downloader */ + protected $file_downloader; + + /** @var \phpbb\language\language $language */ + protected $language; + + /** @var \phpbb\log\log $log */ + protected $log; + + /** @var \phpbb\user $user */ + protected $user; + + /** @var bool Use SSL or not */ + protected $use_ssl = false; + + /** + * Constructor + * + * @param \phpbb\cache\driver\driver_interface $cache + * @param \phpbb\config\config $config + * @param \phpbb\file_downloader $file_downloader + * @param \phpbb\language\language $language + * @param \phpbb\log\log $log + * @param \phpbb\user $user + */ + public function __construct(\phpbb\cache\driver\driver_interface $cache, \phpbb\config\config $config, \phpbb\file_downloader $file_downloader, \phpbb\language\language $language, \phpbb\log\log $log, \phpbb\user $user) + { + $this->cache = $cache; + $this->config = $config; + $this->file_downloader = $file_downloader; + $this->language = $language; + $this->log = $log; + $this->user = $user; + } + + /** + * Obtains the latest VigLink services information from phpBB + * + * @param bool $force_update Ignores cached data. Defaults to false. + * @param bool $force_cache Force the use of the cache. Override $force_update. + * + * @throws \RuntimeException + * + * @return void + */ + public function set_viglink_services($force_update = false, $force_cache = false) + { + $cache_key = '_versioncheck_viglink_' . $this->use_ssl; + + $info = $this->cache->get($cache_key); + + if ($info === false && $force_cache) + { + throw new \RuntimeException($this->language->lang('VERSIONCHECK_FAIL')); + } + else if ($info === false || $force_update) + { + try + { + $info = $this->file_downloader->get('www.phpbb.com', '/viglink', 'enabled', 443); + } + catch (\phpbb\exception\runtime_exception $exception) + { + $prepare_parameters = array_merge(array($exception->getMessage()), $exception->get_parameters()); + throw new \RuntimeException(call_user_func_array(array($this->language, 'lang'), $prepare_parameters)); + } + + if ($info === '0') + { + $this->set_viglink_configs(array( + 'allow_viglink_phpbb' => false, + )); + } + else + { + $info = '1'; + $this->set_viglink_configs(array( + 'allow_viglink_phpbb' => true, + )); + } + + $this->cache->put($cache_key, $info, 86400); // 24 hours + } + } + + /** + * Sets VigLink service configs as determined by phpBB + * + * @param array $data Array of VigLink file data. + * + * @return void + */ + protected function set_viglink_configs($data) + { + $viglink_configs = array( + 'allow_viglink_phpbb', + 'phpbb_viglink_api_key', + ); + + foreach ($viglink_configs as $cfg_name) + { + if (array_key_exists($cfg_name, $data) && ($data[$cfg_name] != $this->config[$cfg_name] || !isset($this->config[$cfg_name]))) + { + $this->config->set($cfg_name, $data[$cfg_name]); + } + } + + $this->config->set('viglink_last_gc', time(), false); + } + + /** + * Log a VigLink error message to the error log + * + * @param string $message The error message + */ + public function log_viglink_error($message) + { + $user_id = empty($this->user->data) ? ANONYMOUS : $this->user->data['user_id']; + $user_ip = empty($this->user->ip) ? '' : $this->user->ip; + + $this->log->add('critical', $user_id, $user_ip, 'LOG_VIGLINK_CHECK_FAIL', false, array($message)); + } +} diff --git a/ext/phpbb/viglink/acp/viglink_info.php b/ext/phpbb/viglink/acp/viglink_info.php new file mode 100644 index 0000000..a2c891a --- /dev/null +++ b/ext/phpbb/viglink/acp/viglink_info.php @@ -0,0 +1,32 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\acp; + +/** + * VigLink ACP module info + */ +class viglink_info +{ + public function module() + { + return array( + 'filename' => '\phpbb\viglink\acp\viglink_module', + 'title' => 'ACP_VIGLINK_SETTINGS', + 'modes' => array( + 'settings' => array( + 'title' => 'ACP_VIGLINK_SETTINGS', + 'auth' => 'ext_phpbb/viglink && acl_a_board', + 'cat' => array('ACP_BOARD_CONFIGURATION') + ), + ), + ); + } +} diff --git a/ext/phpbb/viglink/acp/viglink_module.php b/ext/phpbb/viglink/acp/viglink_module.php new file mode 100644 index 0000000..07753d1 --- /dev/null +++ b/ext/phpbb/viglink/acp/viglink_module.php @@ -0,0 +1,137 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\acp; + +use phpbb\request\type_cast_helper; + +/** + * VigLink ACP module + */ +class viglink_module +{ + /** @var string $page_title The page title */ + public $page_title; + + /** @var string $tpl_name The page template name */ + public $tpl_name; + + /** @var string $u_action Custom form action */ + public $u_action; + + public function main($id, $mode) + { + global $phpbb_container; + + /** @var \phpbb\config\config $config Config object */ + $config = $phpbb_container->get('config'); + + /** @var \phpbb\language\language $language Language object */ + $language = $phpbb_container->get('language'); + + /** @var \phpbb\request\request $request Request object */ + $request = $phpbb_container->get('request'); + + /** @var \phpbb\template\template $template Template object */ + $template = $phpbb_container->get('template'); + + $language->add_lang('viglink_module_acp', 'phpbb/viglink'); + + $this->tpl_name = 'acp_viglink'; + $this->page_title = $language->lang('ACP_VIGLINK_SETTINGS'); + + $submit = $request->is_set_post('submit'); + + if ($mode !== 'settings') + { + return; + } + + $form_key = 'acp_viglink'; + add_form_key($form_key); + + $error = array(); + + // Get stored config/default values + $cfg_array = array( + 'viglink_enabled' => isset($config['viglink_enabled']) ? $config['viglink_enabled'] : 0, + ); + + // Error if the form is invalid + if ($submit && !check_form_key($form_key)) + { + $error[] = $language->lang('FORM_INVALID'); + } + + // Do not process form if invalid + if (count($error)) + { + $submit = false; + } + + if ($submit) + { + // Get the VigLink form field values + $cfg_array['viglink_enabled'] = $request->variable('viglink_enabled', 0); + + // If no errors, set the config values + if (!count($error)) + { + foreach ($cfg_array as $cfg => $value) + { + $config->set($cfg, $value); + } + + trigger_error($language->lang('CONFIG_UPDATED') . adm_back_link($this->u_action)); + } + } + + if (!isset($config['questionnaire_unique_id'])) + { + $config->set('questionnaire_unique_id', unique_id()); + } + + // Set a general error message if VigLink has been disabled by phpBB + if (!$config['allow_viglink_phpbb']) + { + $error[] = $language->lang('ACP_VIGLINK_DISABLED_PHPBB'); + } + + // Try to get convert account key from .com + $sub_id = md5($config['viglink_api_siteid'] . $config['questionnaire_unique_id']); + $convert_account_link = $config->offsetGet('viglink_convert_account_url'); + + if (empty($convert_account_link) || strpos($config['viglink_convert_account_url'], 'subId=' . $sub_id) === false) + { + $convert_account_link = @file_get_contents('https://www.phpbb.com/viglink/convert?domain=' . urlencode($config['server_name']) . '&siteid=' . $config['viglink_api_siteid'] . '&uuid=' . $config['questionnaire_unique_id'] . '&key=' . $config['phpbb_viglink_api_key']); + if (!empty($convert_account_link) && strpos($convert_account_link, 'https://www.viglink.com/users/convertAccount') === 0) + { + $type_caster = new type_cast_helper(); + $type_caster->set_var($convert_account_link, $convert_account_link, 'string', false, false); + $config->set('viglink_convert_account_url', $convert_account_link); + } + else + { + $error[] = $language->lang('ACP_VIGLINK_NO_CONVERT_LINK'); + $convert_account_link = ''; + } + } + + $template->assign_vars(array( + 'S_ERROR' => (bool) count($error), + 'ERROR_MSG' => implode('
', $error), + + 'VIGLINK_ENABLED' => $cfg_array['viglink_enabled'], + + 'U_VIGLINK_CONVERT' => $convert_account_link, + 'U_ACTION' => $this->u_action, + )); + } +} diff --git a/ext/phpbb/viglink/adm/style/acp_viglink.html b/ext/phpbb/viglink/adm/style/acp_viglink.html new file mode 100644 index 0000000..086b552 --- /dev/null +++ b/ext/phpbb/viglink/adm/style/acp_viglink.html @@ -0,0 +1,48 @@ +{% INCLUDE 'overall_header.html' %} + + + +

{{ lang('ACP_VIGLINK_SETTINGS') }}

+ +

{{ lang('ACP_VIGLINK_SETTINGS_EXPLAIN') }}

+ +{% if S_ERROR %} +
+

{{ lang('WARNING') }}

+

{{ ERROR_MSG }}

+
+{% endif %} + + + +{% INCLUDE 'overall_footer.html' %} diff --git a/ext/phpbb/viglink/composer.json b/ext/phpbb/viglink/composer.json new file mode 100644 index 0000000..30312e3 --- /dev/null +++ b/ext/phpbb/viglink/composer.json @@ -0,0 +1,27 @@ +{ + "name": "phpbb/viglink", + "type": "phpbb-extension", + "description": "The VigLink extension for phpBB makes it possible to earn revenue, without any change to the user experience, when users post and follow links to commercial sites.", + "homepage": "https://www.phpbb.com", + "version": "1.0.4", + "keywords": ["phpbb", "extension", "viglink"], + "license": "GPL-2.0-only", + "authors": [ + { + "name": "phpBB Limited", + "email": "operations@phpbb.com", + "homepage": "https://www.phpbb.com/go/authors" + } + ], + "require": { + "php": ">=5.4", + "phpbb/phpbb": ">=3.2.0-b1", + "composer/installers": "~1.0" + }, + "extra": { + "display-name": "VigLink", + "soft-require": { + "phpbb/phpbb": ">=3.2.0-b1,<3.3" + } + } +} diff --git a/ext/phpbb/viglink/composer.lock b/ext/phpbb/viglink/composer.lock new file mode 100644 index 0000000..f03a73d --- /dev/null +++ b/ext/phpbb/viglink/composer.lock @@ -0,0 +1,115 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "155838fcd9206482cc645c3de6a7e8db", + "content-hash": "610135270d537f2e8580b747c6b57ce9", + "packages": [ + { + "name": "composer/installers", + "version": "v1.0.20", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "1bff8aa77a18f3616f468ed8000cf86a5725bac3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/1bff8aa77a18f3616f468ed8000cf86a5725bac3", + "reference": "1bff8aa77a18f3616f468ed8000cf86a5725bac3", + "shasum": "" + }, + "replace": { + "roundcube/plugin-installer": "*", + "shama/baton": "*" + }, + "require-dev": { + "composer/composer": "1.0.*@dev", + "phpunit/phpunit": "4.1.*" + }, + "type": "composer-installer", + "extra": { + "class": "Composer\\Installers\\Installer", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Composer\\Installers\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "http://composer.github.com/installers/", + "keywords": [ + "Craft", + "Dolibarr", + "Hurad", + "MODX Evo", + "OXID", + "SMF", + "Thelia", + "WolfCMS", + "agl", + "annotatecms", + "bitrix", + "cakephp", + "chef", + "codeigniter", + "concrete5", + "croogo", + "dokuwiki", + "drupal", + "elgg", + "fuelphp", + "grav", + "installer", + "joomla", + "kohana", + "laravel", + "lithium", + "magento", + "mako", + "mediawiki", + "modulework", + "moodle", + "phpbb", + "piwik", + "ppi", + "puppet", + "roundcube", + "shopware", + "silverstripe", + "symfony", + "typo3", + "wordpress", + "zend", + "zikula" + ], + "time": "2015-01-11 03:51:11" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.3" + }, + "platform-dev": [] +} diff --git a/ext/phpbb/viglink/config/cron.yml b/ext/phpbb/viglink/config/cron.yml new file mode 100644 index 0000000..85edc22 --- /dev/null +++ b/ext/phpbb/viglink/config/cron.yml @@ -0,0 +1,11 @@ +services: +# ----- Cron tasks ----- + phpbb.viglink.cron.task.viglink: + class: phpbb\viglink\cron\viglink + arguments: + - '@config' + - '@phpbb.viglink.helper' + calls: + - [set_name, [cron.task.viglink]] + tags: + - { name: cron.task } diff --git a/ext/phpbb/viglink/config/services.yml b/ext/phpbb/viglink/config/services.yml new file mode 100644 index 0000000..75cae88 --- /dev/null +++ b/ext/phpbb/viglink/config/services.yml @@ -0,0 +1,35 @@ +imports: + - { resource: cron.yml } + +services: + phpbb.viglink.listener: + class: phpbb\viglink\event\listener + arguments: + - '@config' + - '@template' + tags: + - { name: event.listener } + + phpbb.viglink.acp_listener: + class: phpbb\viglink\event\acp_listener + arguments: + - '@config' + - '@language' + - '@request' + - '@template' + - '@user' + - '@phpbb.viglink.helper' + - '%core.root_path%' + - '%core.php_ext%' + tags: + - { name: event.listener } + + phpbb.viglink.helper: + class: phpbb\viglink\acp\viglink_helper + arguments: + - '@cache.driver' + - '@config' + - '@file_downloader' + - '@language' + - '@log' + - '@user' diff --git a/ext/phpbb/viglink/cron/viglink.php b/ext/phpbb/viglink/cron/viglink.php new file mode 100644 index 0000000..f01bcf6 --- /dev/null +++ b/ext/phpbb/viglink/cron/viglink.php @@ -0,0 +1,67 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\cron; + +/** + * Viglink cron task. + */ +class viglink extends \phpbb\cron\task\base +{ + /** @var \phpbb\config\config $config Config object */ + protected $config; + + /** @var \phpbb\viglink\acp\viglink_helper $helper Viglink helper object */ + protected $helper; + + /** + * Constructor + * + * @param \phpbb\config\config $config Config object + * @param \phpbb\viglink\acp\viglink_helper $viglink_helper Viglink helper object + * @access public + */ + public function __construct(\phpbb\config\config $config, \phpbb\viglink\acp\viglink_helper $viglink_helper) + { + $this->config = $config; + $this->helper = $viglink_helper; + } + + /** + * {@inheritDoc} + */ + public function run() + { + try + { + $this->helper->set_viglink_services(true); + } + catch (\RuntimeException $e) + { + $this->helper->log_viglink_error($e->getMessage()); + } + } + + /** + * {@inheritDoc} + */ + public function is_runnable() + { + return (bool) $this->config['viglink_enabled']; + } + + /** + * {@inheritDoc} + */ + public function should_run() + { + return $this->config['viglink_last_gc'] < strtotime('24 hours ago'); + } +} diff --git a/ext/phpbb/viglink/event/acp_listener.php b/ext/phpbb/viglink/event/acp_listener.php new file mode 100644 index 0000000..9e1606c --- /dev/null +++ b/ext/phpbb/viglink/event/acp_listener.php @@ -0,0 +1,135 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\event; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * ACP Event listener + */ +class acp_listener implements EventSubscriberInterface +{ + /** @var \phpbb\config\config $config Config object */ + protected $config; + + /** @var \phpbb\request\request_interface $request Request interface */ + protected $request; + + /** @var \phpbb\template\template $template Template object */ + protected $template; + + /** @var \phpbb\language\language $language Language object */ + protected $language; + + /** @var \phpbb\user $user User object */ + protected $user; + + /** @var \phpbb\viglink\acp\viglink_helper $helper VigLink helper object */ + protected $helper; + + /** @var string $phpbb_root_path phpBB root path */ + protected $phpbb_root_path; + + /** @var string $php_ext PHP file extension */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\config\config $config + * @param \phpbb\language\language $language + * @param \phpbb\request\request_interface $request phpBB request + * @param \phpbb\template\template $template + * @param \phpbb\user $user User object + * @param \phpbb\viglink\acp\viglink_helper $viglink_helper Viglink helper object + * @param string $phpbb_root_path phpBB root path + * @param string $php_ext PHP file extension + */ + public function __construct(\phpbb\config\config $config, \phpbb\language\language $language, \phpbb\request\request_interface $request, + \phpbb\template\template $template, \phpbb\user $user, \phpbb\viglink\acp\viglink_helper $viglink_helper, + $phpbb_root_path, $php_ext) + { + $this->config = $config; + $this->language = $language; + $this->request = $request; + $this->template = $template; + $this->user = $user; + $this->helper = $viglink_helper; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents() + { + return array( + 'core.acp_main_notice' => 'set_viglink_services', + 'core.acp_help_phpbb_submit_before' => 'update_viglink_settings', + ); + } + + /** + * Check if phpBB is allowing VigLink services to run. + * + * VigLink will be disabled if phpBB is disallowing it to run. + * + * @return void + */ + public function set_viglink_services() + { + try + { + $this->helper->set_viglink_services(); + } + catch (\RuntimeException $e) + { + $this->helper->log_viglink_error($e->getMessage()); + } + + // Only redirect once every 24 hours + if (empty($this->config['viglink_ask_admin']) && $this->user->data['user_type'] == USER_FOUNDER && (time() - intval($this->config['viglink_ask_admin_last']) > 86400)) + { + $this->config->set('viglink_ask_admin_last', time()); + redirect(append_sid($this->phpbb_root_path . 'adm/index.' . $this->php_ext, 'i=acp_help_phpbb&mode=help_phpbb')); + } + } + + /** + * Update VigLink settings + * + * @param array $event Event data + * + * @return void + */ + public function update_viglink_settings($event) + { + $this->language->add_lang('viglink_module_acp', 'phpbb/viglink'); + + $viglink_setting = $this->request->variable('enable-viglink', false); + + if (!empty($event['submit'])) + { + $this->config->set('viglink_enabled', $viglink_setting); + if (empty($this->config['viglink_ask_admin'])) + { + $this->config->set('viglink_ask_admin', time()); + } + } + + $this->template->assign_vars(array( + 'S_ENABLE_VIGLINK' => !empty($this->config['viglink_enabled']) || !$this->config['help_send_statistics_time'], + 'S_VIGLINK_ASK_ADMIN' => empty($this->config['viglink_ask_admin']) && $this->user->data['user_type'] == USER_FOUNDER, + 'ACP_VIGLINK_SETTINGS_CHANGE' => $this->language->lang('ACP_VIGLINK_SETTINGS_CHANGE', append_sid($this->phpbb_root_path . 'adm/index.' . $this->php_ext, 'i=-phpbb-viglink-acp-viglink_module&mode=settings')), + )); + } +} diff --git a/ext/phpbb/viglink/event/listener.php b/ext/phpbb/viglink/event/listener.php new file mode 100644 index 0000000..63e0855 --- /dev/null +++ b/ext/phpbb/viglink/event/listener.php @@ -0,0 +1,69 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\event; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Event listener + */ +class listener implements EventSubscriberInterface +{ + /** @var \phpbb\config\config $config Config object */ + protected $config; + + /** @var \phpbb\template\template $template Template object */ + protected $template; + + /** + * Constructor + * + * @param \phpbb\config\config $config Config object + * @param \phpbb\template\template $template Template object + */ + public function __construct(\phpbb\config\config $config, \phpbb\template\template $template) + { + $this->config = $config; + $this->template = $template; + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents() + { + return array( + 'core.viewtopic_post_row_after' => 'display_viglink', + ); + } + + /** + * Insert the VigLink JS code into forum pages + * + * @return void + */ + public function display_viglink() + { + $viglink_key = ''; + + if ($this->config['allow_viglink_phpbb'] && $this->config['phpbb_viglink_api_key']) + { + // Use phpBB API key if VigLink is allowed for phpBB + $viglink_key = $this->config['phpbb_viglink_api_key']; + } + + $this->template->assign_vars(array( + 'VIGLINK_ENABLED' => $this->config['viglink_enabled'] && $viglink_key, + 'VIGLINK_API_KEY' => $viglink_key, + 'VIGLINK_SUB_ID' => md5(urlencode($this->config['viglink_api_siteid']) . $this->config['questionnaire_unique_id']), + )); + } +} diff --git a/ext/phpbb/viglink/ext.php b/ext/phpbb/viglink/ext.php new file mode 100644 index 0000000..f717b19 --- /dev/null +++ b/ext/phpbb/viglink/ext.php @@ -0,0 +1,69 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink; + +/** + * Extension class for custom enable/disable/purge actions + */ +class ext extends \phpbb\extension\base +{ + /** + * Check whether or not the extension can be enabled. + * The current phpBB version should meet or exceed + * the minimum version required by this extension: + * + * Requires phpBB 3.2.0-b1 or greater + * + * @return bool + */ + public function is_enableable() + { + return phpbb_version_compare(PHPBB_VERSION, '3.2.0-b1', '>='); + } + + /** + * Check phpBB's VigLink switches and set them during install + * + * @param mixed $old_state The return value of the previous call + * of this method, or false on the first call + * + * @return mixed Returns false after last step, otherwise + * temporary state which is passed as an + * argument to the next step + */ + public function enable_step($old_state) + { + if ($old_state === false) + { + $viglink_helper = new \phpbb\viglink\acp\viglink_helper( + $this->container->get('cache.driver'), + $this->container->get('config'), + $this->container->get('file_downloader'), + $this->container->get('language'), + $this->container->get('log'), + $this->container->get('user') + ); + + try + { + $viglink_helper->set_viglink_services(); + } + catch (\RuntimeException $e) + { + $viglink_helper->log_viglink_error($e->getMessage()); + } + + return 'viglink'; + } + + return parent::enable_step($old_state); + } +} diff --git a/ext/phpbb/viglink/language/en/info_acp_viglink.php b/ext/phpbb/viglink/language/en/info_acp_viglink.php new file mode 100644 index 0000000..cabe75f --- /dev/null +++ b/ext/phpbb/viglink/language/en/info_acp_viglink.php @@ -0,0 +1,43 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine +// +// Some characters you may want to copy&paste: +// ’ » “ ” … +// + +$lang = array_merge($lang, array( + 'ACP_VIGLINK_SETTINGS' => 'VigLink settings', + 'LOG_VIGLINK_CHECK_FAIL' => 'VigLink settings could not be verified with phpBB.com
» %s', +)); diff --git a/ext/phpbb/viglink/language/en/viglink_module_acp.php b/ext/phpbb/viglink/language/en/viglink_module_acp.php new file mode 100644 index 0000000..ca32006 --- /dev/null +++ b/ext/phpbb/viglink/language/en/viglink_module_acp.php @@ -0,0 +1,54 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine +// +// Some characters you may want to copy&paste: +// ’ » “ ” … +// + +$lang = array_merge($lang, array( + 'ACP_VIGLINK_SETTINGS' => 'VigLink settings', + 'ACP_VIGLINK_SETTINGS_EXPLAIN' => 'VigLink is third-party service that discretely monetises links posted by users of your forum without any change to the user experience. When users click on your outbound links to products or services and buy something, the merchants pay VigLink a commission, of which a share is donated to the phpBB project. By choosing to enable VigLink and donating proceeds to the phpBB project, you are supporting our open source organisation and ensuring our continued financial security.', + 'ACP_VIGLINK_SETTINGS_CHANGE' => 'You can change these settings at any time in the “VigLink settings” panel.', + 'ACP_VIGLINK_SUPPORT_EXPLAIN' => 'You will no longer be redirected to this page once you submit your preferred options below, by clicking the Submit button.', + 'ACP_VIGLINK_ENABLE' => 'Enable VigLink', + 'ACP_VIGLINK_ENABLE_EXPLAIN' => 'Enables use of VigLink services.', + 'ACP_VIGLINK_EARNINGS' => 'Claim your own earnings (optional)', + 'ACP_VIGLINK_EARNINGS_EXPLAIN' => 'You can claim your own earnings by signing up for a VigLink Convert account.', + 'ACP_VIGLINK_DISABLED_PHPBB' => 'VigLink services have been disabled by phpBB.', + 'ACP_VIGLINK_CLAIM' => 'Claim your earnings', + 'ACP_VIGLINK_CLAIM_EXPLAIN' => 'You can claim your forum’s earnings from VigLink monetised links, instead of donating the earnings to the phpBB project. To manage your account settings, sign up for a “VigLink Convert” account by clicking on “Convert account”.', + 'ACP_VIGLINK_CONVERT_ACCOUNT' => 'Convert account', + 'ACP_VIGLINK_NO_CONVERT_LINK' => 'VigLink convert account link could not be retrieved.', +)); diff --git a/ext/phpbb/viglink/language/fr/info_acp_viglink.php b/ext/phpbb/viglink/language/fr/info_acp_viglink.php new file mode 100644 index 0000000..19d2bd6 --- /dev/null +++ b/ext/phpbb/viglink/language/fr/info_acp_viglink.php @@ -0,0 +1,41 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_VIGLINK_SETTINGS' => 'Paramètres de VigLink', + 'LOG_VIGLINK_CHECK_FAIL' => 'Les paramètres de VigLink ne peuvent pas être vérifiés avec phpBB.com
» %s', +]); diff --git a/ext/phpbb/viglink/language/fr/viglink_module_acp.php b/ext/phpbb/viglink/language/fr/viglink_module_acp.php new file mode 100644 index 0000000..b0c55b3 --- /dev/null +++ b/ext/phpbb/viglink/language/fr/viglink_module_acp.php @@ -0,0 +1,52 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_VIGLINK_SETTINGS' => 'Paramètres de VigLink', + 'ACP_VIGLINK_SETTINGS_EXPLAIN' => 'VigLink est un service tiers qui permet de monétiser en toute tranquillité les liens publiés par les utilisateurs de votre forum sans impacter leur expérience utilisateur. Lorsque les utilisateurs accèderont aux liens menant à des produits ou des services et achèteront quelque chose au marchand partenaire, ce dernier versera à VigLink une commission dont une partie du montant sera partagée au projet phpBB. En choisissant d’activer VigLink tout en laissant les parts des commissions au projet phpBB, vous aidez considérablement notre organisation de logiciels libres et gratuits en assurant notre stabilité financière.', + 'ACP_VIGLINK_SETTINGS_CHANGE' => 'Vous pouvez modifier ces paramètres à tout moment depuis le panneau « Paramètres de VigLink ».', + 'ACP_VIGLINK_SUPPORT_EXPLAIN' => 'Vous ne serez plus redirigé sur cette page lorsque vous aurez renseigné vos préférences disponibles ci-dessous, en cliquant le bouton « Envoyer ».', + 'ACP_VIGLINK_ENABLE' => 'Activer VigLink', + 'ACP_VIGLINK_ENABLE_EXPLAIN' => 'Permet l’utilisation des services de VigLink.', + 'ACP_VIGLINK_EARNINGS' => 'Me reverser mes parts des commissions (optionnel)', + 'ACP_VIGLINK_EARNINGS_EXPLAIN' => 'Vous pouvez prétendre à vos parts des commissions sur les ventes générées en inscrivant un compte « VigLink Convert ».', + 'ACP_VIGLINK_DISABLED_PHPBB' => 'Les services VigLink ont été désactivés par phpBB.', + 'ACP_VIGLINK_CLAIM' => 'Me reverser toutes les parts des commissions', + 'ACP_VIGLINK_CLAIM_EXPLAIN' => 'Vous pouvez récupérer la totalité des parts des commissions sur les ventes générées à partir des liens affiliés de votre forum au lieu de verser ces parts comme donations au projet phpBB. Pour récupérer ces parts, vous devez posséder un compte « VigLink Convert ».', + 'ACP_VIGLINK_CONVERT_ACCOUNT' => 'Compte « VigLink Convert »', + 'ACP_VIGLINK_NO_CONVERT_LINK' => 'Le lien vers votre compte « VigLink Convert » est introuvable.', +]); diff --git a/ext/phpbb/viglink/license.txt b/ext/phpbb/viglink/license.txt new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/ext/phpbb/viglink/license.txt @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/ext/phpbb/viglink/migrations/viglink_ask_admin.php b/ext/phpbb/viglink/migrations/viglink_ask_admin.php new file mode 100644 index 0000000..cd2794d --- /dev/null +++ b/ext/phpbb/viglink/migrations/viglink_ask_admin.php @@ -0,0 +1,37 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\migrations; + +/** + * Migration to ask admin about viglink + */ +class viglink_ask_admin extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\viglink\migrations\viglink_data_v2'); + } + + public function effectively_installed() + { + return isset($this->config['viglink_ask_admin']); + } + + public function update_data() + { + return array( + array('if', array( + (!$this->config->offsetExists('viglink_ask_admin')), + array('config.add', array('viglink_ask_admin', '')), + )), + ); + } +} diff --git a/ext/phpbb/viglink/migrations/viglink_ask_admin_wait.php b/ext/phpbb/viglink/migrations/viglink_ask_admin_wait.php new file mode 100644 index 0000000..eb1d1b8 --- /dev/null +++ b/ext/phpbb/viglink/migrations/viglink_ask_admin_wait.php @@ -0,0 +1,37 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\migrations; + +/** + * Migration to only ask admin once per day + */ +class viglink_ask_admin_wait extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\viglink\migrations\viglink_ask_admin'); + } + + public function effectively_installed() + { + return isset($this->config['viglink_ask_admin_last']); + } + + public function update_data() + { + return array( + array('if', array( + (!$this->config->offsetExists('viglink_ask_admin_last')), + array('config.add', array('viglink_ask_admin_last', '0')), + )), + ); + } +} diff --git a/ext/phpbb/viglink/migrations/viglink_cron.php b/ext/phpbb/viglink/migrations/viglink_cron.php new file mode 100644 index 0000000..89306d9 --- /dev/null +++ b/ext/phpbb/viglink/migrations/viglink_cron.php @@ -0,0 +1,34 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\migrations; + +/** + * Migration to install VigLink cron task data + */ +class viglink_cron extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\viglink\migrations\viglink_data'); + } + + public function effectively_installed() + { + return isset($this->config['viglink_last_gc']); + } + + public function update_data() + { + return array( + array('config.add', array('viglink_last_gc', 0, true)), + ); + } +} diff --git a/ext/phpbb/viglink/migrations/viglink_data.php b/ext/phpbb/viglink/migrations/viglink_data.php new file mode 100644 index 0000000..406e38f --- /dev/null +++ b/ext/phpbb/viglink/migrations/viglink_data.php @@ -0,0 +1,53 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +*/ + +namespace phpbb\viglink\migrations; + +/** + * Migration to install VigLink data + */ +class viglink_data extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v312'); + } + + public function effectively_installed() + { + return isset($this->config['phpbb_viglink_api_key']); + } + + public function update_data() + { + return array( + // Basic config options + array('config.add', array('viglink_enabled', 0)), + array('config.add', array('viglink_api_key', '')), + + // Special config options for phpBB use + array('config.add', array('allow_viglink_phpbb', 1)), + array('config.add', array('allow_viglink_global', 1)), + array('config.add', array('phpbb_viglink_api_key', 'e4fd14f5d7f2bb6d80b8f8da1354718c')), + array('config.add', array('viglink_convert_account_url', '')), + array('config.add', array('viglink_api_siteid', md5($this->config['server_name']))), + + // Add the ACP module to Board Configuration + array('module.add', array( + 'acp', + 'ACP_BOARD_CONFIGURATION', + array( + 'module_basename' => '\phpbb\viglink\acp\viglink_module', + 'modes' => array('settings'), + ), + )), + ); + } +} diff --git a/ext/phpbb/viglink/migrations/viglink_data_v2.php b/ext/phpbb/viglink/migrations/viglink_data_v2.php new file mode 100644 index 0000000..2f126c6 --- /dev/null +++ b/ext/phpbb/viglink/migrations/viglink_data_v2.php @@ -0,0 +1,35 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + */ + +namespace phpbb\viglink\migrations; + +/** + * Migration to remove VigLink data + */ +class viglink_data_v2 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\viglink\migrations\viglink_data'); + } + + public function effectively_installed() + { + return !isset($this->config['viglink_api_key']); + } + + public function update_data() + { + return array( + array('config.remove', array('viglink_api_key')), + array('config.remove', array('allow_viglink_global')), + ); + } +} diff --git a/ext/phpbb/viglink/styles/all/template/event/acp_help_phpbb_stats_after.html b/ext/phpbb/viglink/styles/all/template/event/acp_help_phpbb_stats_after.html new file mode 100644 index 0000000..4bce536 --- /dev/null +++ b/ext/phpbb/viglink/styles/all/template/event/acp_help_phpbb_stats_after.html @@ -0,0 +1,13 @@ +
+ +

{{ lang('ACP_VIGLINK_SETTINGS_EXPLAIN') }}

{{ ACP_VIGLINK_SETTINGS_CHANGE }}

+
+
+ + +
+
{{ lang('ENABLE') }}
+
+
+ +{% INCLUDECSS '@phpbb_viglink/viglink.css' %} diff --git a/ext/phpbb/viglink/styles/all/template/event/acp_help_phpbb_stats_before.html b/ext/phpbb/viglink/styles/all/template/event/acp_help_phpbb_stats_before.html new file mode 100644 index 0000000..aa5a261 --- /dev/null +++ b/ext/phpbb/viglink/styles/all/template/event/acp_help_phpbb_stats_before.html @@ -0,0 +1,3 @@ +{% if S_VIGLINK_ASK_ADMIN %} +

{{ lang('ACP_VIGLINK_SUPPORT_EXPLAIN') }}

+{% endif %} diff --git a/ext/phpbb/viglink/styles/all/template/event/overall_footer_after.html b/ext/phpbb/viglink/styles/all/template/event/overall_footer_after.html new file mode 100644 index 0000000..d851812 --- /dev/null +++ b/ext/phpbb/viglink/styles/all/template/event/overall_footer_after.html @@ -0,0 +1,14 @@ +{% if VIGLINK_ENABLED %} + +{% endif %} diff --git a/ext/phpbb/viglink/styles/all/theme/images/VigLink_logo.png b/ext/phpbb/viglink/styles/all/theme/images/VigLink_logo.png new file mode 100644 index 0000000..9f80ae8 Binary files /dev/null and b/ext/phpbb/viglink/styles/all/theme/images/VigLink_logo.png differ diff --git a/ext/phpbb/viglink/styles/all/theme/viglink.css b/ext/phpbb/viglink/styles/all/theme/viglink.css new file mode 100644 index 0000000..c507a54 --- /dev/null +++ b/ext/phpbb/viglink/styles/all/theme/viglink.css @@ -0,0 +1,18 @@ +/* phpBB VigLink Extension Style Sheet + ------------------------------------------------------------------------ + Copyright (c) phpBB Limited + + For full copyright and license information, please see + the docs/CREDITS.txt file. + ------------------------------------------------------------------------ +*/ + +.viglink-header { + background: url('images/VigLink_logo.png') no-repeat 0 0; + padding-bottom: 18px; + padding-left: 142px; +} + +.send-stats-tile > .viglink-header-h2 { + margin-bottom: 10px; +} diff --git a/ext/planetstyles/flightdeck.zip b/ext/planetstyles/flightdeck.zip new file mode 100644 index 0000000..537cae0 Binary files /dev/null and b/ext/planetstyles/flightdeck.zip differ diff --git a/ext/planetstyles/flightdeck/acp/main_info.php b/ext/planetstyles/flightdeck/acp/main_info.php new file mode 100644 index 0000000..423aa37 --- /dev/null +++ b/ext/planetstyles/flightdeck/acp/main_info.php @@ -0,0 +1,21 @@ + '\planetstyles\flightdeck\acp\main_module', + 'title' => 'ACP_STYLE_SETTINGS_TITLE', + 'modes' => array( + 'settings' => array( + 'title' => 'ACP_STYLE_SETTINGS_SETTINGS', + 'auth' => 'ext_planetstyles/flightdeck && acl_a_board', + 'cat' => array('ACP_STYLE_SETTINGS_TITLE') + ), + ), + ); + } +} diff --git a/ext/planetstyles/flightdeck/acp/main_module.php b/ext/planetstyles/flightdeck/acp/main_module.php new file mode 100644 index 0000000..9bdf3bb --- /dev/null +++ b/ext/planetstyles/flightdeck/acp/main_module.php @@ -0,0 +1,286 @@ +cache = $phpbb_container->get('cache.driver'); + $this->config = $phpbb_container->get('config'); + $this->config_text = $phpbb_container->get('config_text'); + $this->request = $phpbb_container->get('request'); + $this->template = $phpbb_container->get('template'); + $this->user = $phpbb_container->get('user'); + $this->root_path = $phpbb_root_path; + + $this->user->add_lang_ext('planetstyles/flightdeck', 'acp_style_settings'); + $this->tpl_name = 'acp_style_settings_body'; + $this->page_title = $this->user->lang('ACP_STYLE_SETTINGS_TITLE'); + add_form_key('planetstyles/flightdeck'); + + try + { + $style_settings = $this->load_json_data(); + } + catch (\phpbb\exception\runtime_exception $e) + { + $style_settings = array(); + } + + if ($this->request->is_set_post('submit')) + { + if (!check_form_key('planetstyles/flightdeck')) + { + trigger_error('FORM_INVALID'); + } + + $this->config->set('style_settings_logo_path', $this->request->variable('style_settings_logo_path', '')); + $this->config->set('style_settings_logo_width', $this->request->variable('style_settings_logo_width', 0)); + $this->config->set('style_settings_logo_height', $this->request->variable('style_settings_logo_height', 0)); + + // Start PS-Test + $this->config->set('style_settings_header_path', $this->request->variable('style_settings_header_path', '')); + $this->config->set('style_settings_favicon_path', $this->request->variable('style_settings_favicon_path', '')); + // End PS-Test + + foreach ($style_settings as $style_setting) + { + $this->config->set('style_settings_config_' . $style_setting['name'], $this->request->variable($style_setting['name'], '')); + } + + $this->cache->destroy('_style_settings_html_data'); + $this->config_text->set_array(array( + 'style_settings_html_1' => $this->request->variable('style_settings_html_1', '', 'true'), + 'style_settings_html_2' => $this->request->variable('style_settings_html_2', '', 'true'), + 'style_settings_html_3' => $this->request->variable('style_settings_html_3', '', 'true'), + 'style_settings_html_4' => $this->request->variable('style_settings_html_4', '', 'true'), + )); + + + $file = $this->request->file('style_settings_logo_upload'); + if ($file['error'] == UPLOAD_ERR_OK) + { + $destination = 'ext/planetstyles/flightdeck/store/'; + if (!$this->upload($file, $this->root_path . $destination)) + { + trigger_error($this->user->lang('STYLE_SETTINGS_LOGO_ERROR', $file['name']) . adm_back_link($this->u_action), E_USER_WARNING); + } + $this->config->set('style_settings_logo_path', $destination . $file['name']); + } + else if ($file['error'] != UPLOAD_ERR_NO_FILE) + { + trigger_error($this->user->lang('STYLE_SETTINGS_LOGO_ERROR', $file['name']) . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Start PS-Test + $file = $this->request->file('style_settings_header_upload'); + if ($file['error'] == UPLOAD_ERR_OK) + { + $destination = 'ext/planetstyles/flightdeck/store/'; + if (!$this->upload($file, $this->root_path . $destination)) + { + trigger_error($this->user->lang('STYLE_SETTINGS_HEADER_ERROR', $file['name']) . adm_back_link($this->u_action), E_USER_WARNING); + } + $this->config->set('style_settings_header_path', $destination . $file['name']); + } + else if ($file['error'] != UPLOAD_ERR_NO_FILE) + { + trigger_error($this->user->lang('STYLE_SETTINGS_HEADER_ERROR', $file['name']) . adm_back_link($this->u_action), E_USER_WARNING); + } + + + // Favicon + $file = $this->request->file('style_settings_favicon_upload'); + if ($file['error'] == UPLOAD_ERR_OK) + { + $destination = 'ext/planetstyles/flightdeck/store/'; + if (!$this->upload($file, $this->root_path . $destination)) + { + trigger_error($this->user->lang('STYLE_SETTINGS_FAVICON_ERROR', $file['name']) . adm_back_link($this->u_action), E_USER_WARNING); + } + $this->config->set('style_settings_favicon_path', $destination . $file['name']); + } + else if ($file['error'] != UPLOAD_ERR_NO_FILE) + { + trigger_error($this->user->lang('STYLE_SETTINGS_HEADER_ERROR', $file['name']) . adm_back_link($this->u_action), E_USER_WARNING); + } + // End PS-Test + + trigger_error($this->user->lang('STYLE_SETTINGS_SAVED') . adm_back_link($this->u_action)); + } + + foreach ($style_settings as $name => $data) + { + $config_name = 'style_settings_config_' . $data['name']; + $this->template->assign_block_vars('style_settings_config', array( + 'VALUE' => $this->config->offsetExists($config_name) ? $this->config->offsetGet($config_name) : 0, + 'OPTIONS' => isset($data['values']) ? $data['values'] : null, + 'LABEL' => $name, + 'LABEL_EXPLAIN' => $this->user->lang('STYLE_SETTINGS_CONFIG_EXPLAIN', strtoupper($config_name)), + 'CONFIG_NAME' => $data['name'], + 'LABEL_HELP' => $data['help'], + 'S_BOOL' => $data['type'] === 'bool', + 'S_LIST' => $data['type'] === 'list', + 'S_STRING' => $data['type'] === 'string', + )); + } + + $html_code_block_data = $this->config_text->get_array(array( + 'style_settings_html_1', + 'style_settings_html_2', + 'style_settings_html_3', + 'style_settings_html_4', + )); + + ksort($html_code_block_data); + $index = 0; + foreach ($html_code_block_data as $key => $data) + { + $this->template->assign_block_vars('style_settings_html', array( + 'VALUE' => $data, + 'NAME' => 'style_settings_html_' . ++$index, + 'LABEL' => $this->user->lang('STYLE_SETTINGS_HTML_' . $index), + 'LABEL_HELP' => $this->user->lang('STYLE_SETTINGS_HTML_HELP_' . $index), + 'LABEL_EXPLAIN' => $this->user->lang('STYLE_SETTINGS_HTML_EXPLAIN', $index), + )); + } + + $this->template->assign_vars(array( + 'STYLE_SETTINGS_LOGO_PATH' => $this->config->offsetGet('style_settings_logo_path'), + 'STYLE_SETTINGS_LOGO_WIDTH' => $this->config->offsetGet('style_settings_logo_width'), + 'STYLE_SETTINGS_LOGO_HEIGHT' => $this->config->offsetGet('style_settings_logo_height'), + + // Start PS-Test + 'STYLE_SETTINGS_HEADER_PATH' => $this->config->offsetGet('style_settings_header_path'), + 'STYLE_SETTINGS_FAVICON_PATH' => $this->config->offsetGet('style_settings_favicon_path'), + // End PS-Test + + + 'U_ACTION' => $this->u_action, + )); + } + + /** + * Return decoded JSON data from a JSON file + * + * @return array JSON data + * @throws \phpbb\exception\runtime_exception + * @access protected + */ + protected function load_json_data() + { + $json_file = $this->root_path . 'ext/planetstyles/flightdeck/styles/style_config.json'; + + if (!file_exists($json_file)) + { + throw new \phpbb\exception\runtime_exception('FILE_NOT_FOUND', array($json_file)); + } + + if (!($file_contents = file_get_contents($json_file))) + { + throw new \phpbb\exception\runtime_exception('FILE_CONTENT_ERR', array($json_file)); + } + + if (($json_data = json_decode($file_contents, true)) === null) + { + throw new \phpbb\exception\runtime_exception('FILE_JSON_DECODE_ERR', array($json_file)); + } + + return $json_data; + } + + + /** + * Upload the file to the given directory + * + * @param array $fp File pointer data + * @param string $location Path to directory where to upload + * + * @return bool True if upload was successful, false otherwise + * @access protected + */ + protected function upload($fp, $location) + { + if ($this->allowedExtension($fp['name']) && $this->allowedSize($fp['size'])) + { + $destination = $location . basename($fp['name']); + if (move_uploaded_file($fp['tmp_name'], $destination)) + { + return true; + } + } + return false; + } + + /** + * Check if file is allowed by its extension + * + * @param string $filename File name + * + * @return bool True if file ext is allowed, false otherwise + * @access protected + */ + protected function allowedExtension($filename) + { + return in_array($this->getExtension($filename), array('gif', 'jpeg', 'jpg', 'png', 'svg'), true); + } + + /** + * Check file size in Mb, against php.ini upload_max_filesize setting + * + * @param string $filesize File size + * + * @return bool True if file size is allowed, false otherwise + * @access protected + */ + protected function allowedSize($filesize) + { + return ($filesize < ((int) ini_get('upload_max_filesize')) * 1000000); + } + + /** + * Get file extension + * + * @param string $filename Name of file + * + * @return string File's extension or nothing if not found + * @access protected + */ + protected function getExtension($filename) + { + if (strpos($filename, '.') === false) + { + return ''; + } + + $parts = explode('.', $filename); + return strtolower(array_pop($parts)); + } +} diff --git a/ext/planetstyles/flightdeck/adm/style/acp-logo.png b/ext/planetstyles/flightdeck/adm/style/acp-logo.png new file mode 100644 index 0000000..42561ae Binary files /dev/null and b/ext/planetstyles/flightdeck/adm/style/acp-logo.png differ diff --git a/ext/planetstyles/flightdeck/adm/style/acp_style_settings_body.html b/ext/planetstyles/flightdeck/adm/style/acp_style_settings_body.html new file mode 100644 index 0000000..f0cfaef --- /dev/null +++ b/ext/planetstyles/flightdeck/adm/style/acp_style_settings_body.html @@ -0,0 +1,92 @@ +{% include 'overall_header.html' %} +
+
+ +

Milk Theme Control Panel. Please refer to the documentation for help.

+
+
+

Important: Purge Cache in: "ACP → General » Purge Cache → Run Now" to remove this message and enable flightdeck.

+
+
+ +
+ {{ lang('STYLE_SETTINGS_LOGO') }} +

{{ lang('STYLE_SETTINGS_LOGO_EXPLAIN') }}

+
+

{{ lang('STYLE_SETTINGS_LOGO_UPLOAD_EXPLAIN') }}
+
+
+
+

{{ lang('STYLE_SETTINGS_LOGO_PATH_EXPLAIN') }}
+
+
+
+

{{ lang('STYLE_SETTINGS_LOGO_WIDTH_EXPLAIN') }}
+
{{ lang('PIXEL') }}
+
+
+

{{ lang('STYLE_SETTINGS_LOGO_HEIGHT_EXPLAIN') }}
+
{{ lang('PIXEL') }}
+
+
+
+

{{ lang('STYLE_SETTINGS_FAVICON_EXPLAIN') }}
+
+
+
+

{{ lang('STYLE_SETTINGS_FAVICON_PATH_EXPLAIN') }}
+
+
+
+
+

{{ lang('STYLE_SETTINGS_HEADER_UPLOAD_EXPLAIN') }}
+
+
+
+

{{ lang('STYLE_SETTINGS_HEADER_PATH_EXPLAIN') }}
+
+
+
+ +
+ {{ lang('STYLE_SETTINGS_CONFIG') }} + {% for style_settings_config in loops.style_settings_config %} +
+

{{ style_settings_config.LABEL_HELP }} + +
+ {% if style_settings_config.S_BOOL %} + + + {% elseif style_settings_config.S_LIST %} + {% for OPTION in style_settings_config.OPTIONS %} + + {% endfor %} + {% elseif style_settings_config.S_STRING %} + + {% endif %} +
+
+ {% endfor %} +
+ +
+

Refer to documentation for corresponding output locations.

+ {{ lang('STYLE_SETTINGS_HTML_CODE') }} + {% for style_settings_html in loops.style_settings_html %} +
+

{% if style_settings_html.LABEL_HELP %}{{ style_settings_html.LABEL_HELP }}{% endif %}
+
+
+ {% endfor %} +
+ +
+

+   +

+ {{ S_FORM_TOKEN }} +
+
+
+{% include 'overall_footer.html' %} diff --git a/ext/planetstyles/flightdeck/adm/style/event/acp_overall_footer_after.html b/ext/planetstyles/flightdeck/adm/style/event/acp_overall_footer_after.html new file mode 100644 index 0000000..6df9e88 --- /dev/null +++ b/ext/planetstyles/flightdeck/adm/style/event/acp_overall_footer_after.html @@ -0,0 +1,7 @@ + + + diff --git a/ext/planetstyles/flightdeck/adm/style/event/acp_overall_header_head_append.html b/ext/planetstyles/flightdeck/adm/style/event/acp_overall_header_head_append.html new file mode 100644 index 0000000..97e2d91 --- /dev/null +++ b/ext/planetstyles/flightdeck/adm/style/event/acp_overall_header_head_append.html @@ -0,0 +1,2 @@ + + diff --git a/ext/planetstyles/flightdeck/adm/style/flightdeck.css b/ext/planetstyles/flightdeck/adm/style/flightdeck.css new file mode 100644 index 0000000..51f82ab --- /dev/null +++ b/ext/planetstyles/flightdeck/adm/style/flightdeck.css @@ -0,0 +1,53 @@ +.flightdeck-wrapper, .flightdeck fieldset { + background-color: #FFFFFF; + padding: 30px; + border: none; + border-radius: 5px; + box-shadow: 0 0 5px 3px rgba(0, 0, 0, 0.02); +} + +.flightdeck * { + font-family: 'Arial', sans-serif; + font-size: 14px; +} + +.flightdeck fieldset { + margin-bottom: 30px; +} + +.flightdeck fieldset dl { + margin-bottom: 20px; +} + +.flightdeck fieldset dd { + padding-left: 20px; + border-left: none; +} + +.flightdeck fieldset dt,.flightdeck fieldset dt:hover { + border-right-color: #E3EBEF !important; +} + +.flightdeck fieldset legend, .flightdeck fieldset dt label { + font-weight: 700; + font-size: 16px; +} + +.flightdeck fieldset input[type=text], .flightdeck fieldset input[type=file], .flightdeck fieldset input[type=number], .flightdeck fieldset textarea { + background: rgba(0,0,0,0.05); + border: none; + padding: 10px; + border-radius: 5px; +} + +.flightdeck textarea { + font-family: "Courier New", Courier, monospace; +} + +.flightdeck .purgewarning { + display: none; +} + +fieldset dl span { + opacity: 0.6; +} diff --git a/ext/planetstyles/flightdeck/adm/style/jscolor.js b/ext/planetstyles/flightdeck/adm/style/jscolor.js new file mode 100644 index 0000000..2bdd460 --- /dev/null +++ b/ext/planetstyles/flightdeck/adm/style/jscolor.js @@ -0,0 +1,1844 @@ +/** + * jscolor - JavaScript Color Picker + * + * @link http://jscolor.com + * @license For open source use: GPLv3 + * For commercial use: JSColor Commercial License + * @author Jan Odvarko + * @version 2.0.4 + * + * See usage examples at http://jscolor.com/examples/ + */ + + +"use strict"; + + +if (!window.jscolor) { window.jscolor = (function () { + + +var jsc = { + + + register : function () { + jsc.attachDOMReadyEvent(jsc.init); + jsc.attachEvent(document, 'mousedown', jsc.onDocumentMouseDown); + jsc.attachEvent(document, 'touchstart', jsc.onDocumentTouchStart); + jsc.attachEvent(window, 'resize', jsc.onWindowResize); + }, + + + init : function () { + if (jsc.jscolor.lookupClass) { + jsc.jscolor.installByClassName(jsc.jscolor.lookupClass); + } + }, + + + tryInstallOnElements : function (elms, className) { + var matchClass = new RegExp('(^|\\s)(' + className + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i'); + + for (var i = 0; i < elms.length; i += 1) { + if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color') { + if (jsc.isColorAttrSupported) { + // skip inputs of type 'color' if supported by the browser + continue; + } + } + var m; + if (!elms[i].jscolor && elms[i].className && (m = elms[i].className.match(matchClass))) { + var targetElm = elms[i]; + var optsStr = null; + + var dataOptions = jsc.getDataAttr(targetElm, 'jscolor'); + if (dataOptions !== null) { + optsStr = dataOptions; + } else if (m[4]) { + optsStr = m[4]; + } + + var opts = {}; + if (optsStr) { + try { + opts = (new Function ('return (' + optsStr + ')'))(); + } catch(eParseError) { + jsc.warn('Error parsing jscolor options: ' + eParseError + ':\n' + optsStr); + } + } + targetElm.jscolor = new jsc.jscolor(targetElm, opts); + } + } + }, + + + isColorAttrSupported : (function () { + var elm = document.createElement('input'); + if (elm.setAttribute) { + elm.setAttribute('type', 'color'); + if (elm.type.toLowerCase() == 'color') { + return true; + } + } + return false; + })(), + + + isCanvasSupported : (function () { + var elm = document.createElement('canvas'); + return !!(elm.getContext && elm.getContext('2d')); + })(), + + + fetchElement : function (mixed) { + return typeof mixed === 'string' ? document.getElementById(mixed) : mixed; + }, + + + isElementType : function (elm, type) { + return elm.nodeName.toLowerCase() === type.toLowerCase(); + }, + + + getDataAttr : function (el, name) { + var attrName = 'data-' + name; + var attrValue = el.getAttribute(attrName); + if (attrValue !== null) { + return attrValue; + } + return null; + }, + + + attachEvent : function (el, evnt, func) { + if (el.addEventListener) { + el.addEventListener(evnt, func, false); + } else if (el.attachEvent) { + el.attachEvent('on' + evnt, func); + } + }, + + + detachEvent : function (el, evnt, func) { + if (el.removeEventListener) { + el.removeEventListener(evnt, func, false); + } else if (el.detachEvent) { + el.detachEvent('on' + evnt, func); + } + }, + + + _attachedGroupEvents : {}, + + + attachGroupEvent : function (groupName, el, evnt, func) { + if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) { + jsc._attachedGroupEvents[groupName] = []; + } + jsc._attachedGroupEvents[groupName].push([el, evnt, func]); + jsc.attachEvent(el, evnt, func); + }, + + + detachGroupEvents : function (groupName) { + if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) { + for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) { + var evt = jsc._attachedGroupEvents[groupName][i]; + jsc.detachEvent(evt[0], evt[1], evt[2]); + } + delete jsc._attachedGroupEvents[groupName]; + } + }, + + + attachDOMReadyEvent : function (func) { + var fired = false; + var fireOnce = function () { + if (!fired) { + fired = true; + func(); + } + }; + + if (document.readyState === 'complete') { + setTimeout(fireOnce, 1); // async + return; + } + + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireOnce, false); + + // Fallback + window.addEventListener('load', fireOnce, false); + + } else if (document.attachEvent) { + // IE + document.attachEvent('onreadystatechange', function () { + if (document.readyState === 'complete') { + document.detachEvent('onreadystatechange', arguments.callee); + fireOnce(); + } + }) + + // Fallback + window.attachEvent('onload', fireOnce); + + // IE7/8 + if (document.documentElement.doScroll && window == window.top) { + var tryScroll = function () { + if (!document.body) { return; } + try { + document.documentElement.doScroll('left'); + fireOnce(); + } catch (e) { + setTimeout(tryScroll, 1); + } + }; + tryScroll(); + } + } + }, + + + warn : function (msg) { + if (window.console && window.console.warn) { + window.console.warn(msg); + } + }, + + + preventDefault : function (e) { + if (e.preventDefault) { e.preventDefault(); } + e.returnValue = false; + }, + + + captureTarget : function (target) { + // IE + if (target.setCapture) { + jsc._capturedTarget = target; + jsc._capturedTarget.setCapture(); + } + }, + + + releaseTarget : function () { + // IE + if (jsc._capturedTarget) { + jsc._capturedTarget.releaseCapture(); + jsc._capturedTarget = null; + } + }, + + + fireEvent : function (el, evnt) { + if (!el) { + return; + } + if (document.createEvent) { + var ev = document.createEvent('HTMLEvents'); + ev.initEvent(evnt, true, true); + el.dispatchEvent(ev); + } else if (document.createEventObject) { + var ev = document.createEventObject(); + el.fireEvent('on' + evnt, ev); + } else if (el['on' + evnt]) { // alternatively use the traditional event model + el['on' + evnt](); + } + }, + + + classNameToList : function (className) { + return className.replace(/^\s+|\s+$/g, '').split(/\s+/); + }, + + + // The className parameter (str) can only contain a single class name + hasClass : function (elm, className) { + if (!className) { + return false; + } + return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' '); + }, + + + // The className parameter (str) can contain multiple class names separated by whitespace + setClass : function (elm, className) { + var classList = jsc.classNameToList(className); + for (var i = 0; i < classList.length; i += 1) { + if (!jsc.hasClass(elm, classList[i])) { + elm.className += (elm.className ? ' ' : '') + classList[i]; + } + } + }, + + + // The className parameter (str) can contain multiple class names separated by whitespace + unsetClass : function (elm, className) { + var classList = jsc.classNameToList(className); + for (var i = 0; i < classList.length; i += 1) { + var repl = new RegExp( + '^\\s*' + classList[i] + '\\s*|' + + '\\s*' + classList[i] + '\\s*$|' + + '\\s+' + classList[i] + '(\\s+)', + 'g' + ); + elm.className = elm.className.replace(repl, '$1'); + } + }, + + + getStyle : function (elm) { + return window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle; + }, + + + setStyle : (function () { + var helper = document.createElement('div'); + var getSupportedProp = function (names) { + for (var i = 0; i < names.length; i += 1) { + if (names[i] in helper.style) { + return names[i]; + } + } + }; + var props = { + borderRadius: getSupportedProp(['borderRadius', 'MozBorderRadius', 'webkitBorderRadius']), + boxShadow: getSupportedProp(['boxShadow', 'MozBoxShadow', 'webkitBoxShadow']) + }; + return function (elm, prop, value) { + switch (prop.toLowerCase()) { + case 'opacity': + var alphaOpacity = Math.round(parseFloat(value) * 100); + elm.style.opacity = value; + elm.style.filter = 'alpha(opacity=' + alphaOpacity + ')'; + break; + default: + elm.style[props[prop]] = value; + break; + } + }; + })(), + + + setBorderRadius : function (elm, value) { + jsc.setStyle(elm, 'borderRadius', value || '0'); + }, + + + setBoxShadow : function (elm, value) { + jsc.setStyle(elm, 'boxShadow', value || 'none'); + }, + + + getElementPos : function (e, relativeToViewport) { + var x=0, y=0; + var rect = e.getBoundingClientRect(); + x = rect.left; + y = rect.top; + if (!relativeToViewport) { + var viewPos = jsc.getViewPos(); + x += viewPos[0]; + y += viewPos[1]; + } + return [x, y]; + }, + + + getElementSize : function (e) { + return [e.offsetWidth, e.offsetHeight]; + }, + + + // get pointer's X/Y coordinates relative to viewport + getAbsPointerPos : function (e) { + if (!e) { e = window.event; } + var x = 0, y = 0; + if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { + // touch devices + x = e.changedTouches[0].clientX; + y = e.changedTouches[0].clientY; + } else if (typeof e.clientX === 'number') { + x = e.clientX; + y = e.clientY; + } + return { x: x, y: y }; + }, + + + // get pointer's X/Y coordinates relative to target element + getRelPointerPos : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + var targetRect = target.getBoundingClientRect(); + + var x = 0, y = 0; + + var clientX = 0, clientY = 0; + if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { + // touch devices + clientX = e.changedTouches[0].clientX; + clientY = e.changedTouches[0].clientY; + } else if (typeof e.clientX === 'number') { + clientX = e.clientX; + clientY = e.clientY; + } + + x = clientX - targetRect.left; + y = clientY - targetRect.top; + return { x: x, y: y }; + }, + + + getViewPos : function () { + var doc = document.documentElement; + return [ + (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0), + (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) + ]; + }, + + + getViewSize : function () { + var doc = document.documentElement; + return [ + (window.innerWidth || doc.clientWidth), + (window.innerHeight || doc.clientHeight), + ]; + }, + + + redrawPosition : function () { + + if (jsc.picker && jsc.picker.owner) { + var thisObj = jsc.picker.owner; + + var tp, vp; + + if (thisObj.fixed) { + // Fixed elements are positioned relative to viewport, + // therefore we can ignore the scroll offset + tp = jsc.getElementPos(thisObj.targetElement, true); // target pos + vp = [0, 0]; // view pos + } else { + tp = jsc.getElementPos(thisObj.targetElement); // target pos + vp = jsc.getViewPos(); // view pos + } + + var ts = jsc.getElementSize(thisObj.targetElement); // target size + var vs = jsc.getViewSize(); // view size + var ps = jsc.getPickerOuterDims(thisObj); // picker size + var a, b, c; + switch (thisObj.position.toLowerCase()) { + case 'left': a=1; b=0; c=-1; break; + case 'right':a=1; b=0; c=1; break; + case 'top': a=0; b=1; c=-1; break; + default: a=0; b=1; c=1; break; + } + var l = (ts[b]+ps[b])/2; + + // compute picker position + if (!thisObj.smartPosition) { + var pp = [ + tp[a], + tp[b]+ts[b]-l+l*c + ]; + } else { + var pp = [ + -vp[a]+tp[a]+ps[a] > vs[a] ? + (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : + tp[a], + -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? + (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : + (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) + ]; + } + + var x = pp[a]; + var y = pp[b]; + var positionValue = thisObj.fixed ? 'fixed' : 'absolute'; + var contractShadow = + (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) && + (pp[1] + ps[1] < tp[1] + ts[1]); + + jsc._drawPosition(thisObj, x, y, positionValue, contractShadow); + } + }, + + + _drawPosition : function (thisObj, x, y, positionValue, contractShadow) { + var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px + + jsc.picker.wrap.style.position = positionValue; + jsc.picker.wrap.style.left = x + 'px'; + jsc.picker.wrap.style.top = y + 'px'; + + jsc.setBoxShadow( + jsc.picker.boxS, + thisObj.shadow ? + new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) : + null); + }, + + + getPickerDims : function (thisObj) { + var displaySlider = !!jsc.getSliderComponent(thisObj); + var dims = [ + 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.width + + (displaySlider ? 2 * thisObj.insetWidth + jsc.getPadToSliderPadding(thisObj) + thisObj.sliderSize : 0), + 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.height + + (thisObj.closable ? 2 * thisObj.insetWidth + thisObj.padding + thisObj.buttonHeight : 0) + ]; + return dims; + }, + + + getPickerOuterDims : function (thisObj) { + var dims = jsc.getPickerDims(thisObj); + return [ + dims[0] + 2 * thisObj.borderWidth, + dims[1] + 2 * thisObj.borderWidth + ]; + }, + + + getPadToSliderPadding : function (thisObj) { + return Math.max(thisObj.padding, 1.5 * (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness)); + }, + + + getPadYComponent : function (thisObj) { + switch (thisObj.mode.charAt(1).toLowerCase()) { + case 'v': return 'v'; break; + } + return 's'; + }, + + + getSliderComponent : function (thisObj) { + if (thisObj.mode.length > 2) { + switch (thisObj.mode.charAt(2).toLowerCase()) { + case 's': return 's'; break; + case 'v': return 'v'; break; + } + } + return null; + }, + + + onDocumentMouseDown : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + + if (target._jscLinkedInstance) { + if (target._jscLinkedInstance.showOnClick) { + target._jscLinkedInstance.show(); + } + } else if (target._jscControlName) { + jsc.onControlPointerStart(e, target, target._jscControlName, 'mouse'); + } else { + // Mouse is outside the picker controls -> hide the color picker! + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + } + }, + + + onDocumentTouchStart : function (e) { + if (!e) { e = window.event; } + var target = e.target || e.srcElement; + + if (target._jscLinkedInstance) { + if (target._jscLinkedInstance.showOnClick) { + target._jscLinkedInstance.show(); + } + } else if (target._jscControlName) { + jsc.onControlPointerStart(e, target, target._jscControlName, 'touch'); + } else { + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + } + }, + + + onWindowResize : function (e) { + jsc.redrawPosition(); + }, + + + onParentScroll : function (e) { + // hide the picker when one of the parent elements is scrolled + if (jsc.picker && jsc.picker.owner) { + jsc.picker.owner.hide(); + } + }, + + + _pointerMoveEvent : { + mouse: 'mousemove', + touch: 'touchmove' + }, + _pointerEndEvent : { + mouse: 'mouseup', + touch: 'touchend' + }, + + + _pointerOrigin : null, + _capturedTarget : null, + + + onControlPointerStart : function (e, target, controlName, pointerType) { + var thisObj = target._jscInstance; + + jsc.preventDefault(e); + jsc.captureTarget(target); + + var registerDragEvents = function (doc, offset) { + jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType], + jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset)); + jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType], + jsc.onDocumentPointerEnd(e, target, controlName, pointerType)); + }; + + registerDragEvents(document, [0, 0]); + + if (window.parent && window.frameElement) { + var rect = window.frameElement.getBoundingClientRect(); + var ofs = [-rect.left, -rect.top]; + registerDragEvents(window.parent.window.document, ofs); + } + + var abs = jsc.getAbsPointerPos(e); + var rel = jsc.getRelPointerPos(e); + jsc._pointerOrigin = { + x: abs.x - rel.x, + y: abs.y - rel.y + }; + + switch (controlName) { + case 'pad': + // if the slider is at the bottom, move it up + switch (jsc.getSliderComponent(thisObj)) { + case 's': if (thisObj.hsv[1] === 0) { thisObj.fromHSV(null, 100, null); }; break; + case 'v': if (thisObj.hsv[2] === 0) { thisObj.fromHSV(null, null, 100); }; break; + } + jsc.setPad(thisObj, e, 0, 0); + break; + + case 'sld': + jsc.setSld(thisObj, e, 0); + break; + } + + jsc.dispatchFineChange(thisObj); + }, + + + onDocumentPointerMove : function (e, target, controlName, pointerType, offset) { + return function (e) { + var thisObj = target._jscInstance; + switch (controlName) { + case 'pad': + if (!e) { e = window.event; } + jsc.setPad(thisObj, e, offset[0], offset[1]); + jsc.dispatchFineChange(thisObj); + break; + + case 'sld': + if (!e) { e = window.event; } + jsc.setSld(thisObj, e, offset[1]); + jsc.dispatchFineChange(thisObj); + break; + } + } + }, + + + onDocumentPointerEnd : function (e, target, controlName, pointerType) { + return function (e) { + var thisObj = target._jscInstance; + jsc.detachGroupEvents('drag'); + jsc.releaseTarget(); + // Always dispatch changes after detaching outstanding mouse handlers, + // in case some user interaction will occur in user's onchange callback + // that would intrude with current mouse events + jsc.dispatchChange(thisObj); + }; + }, + + + dispatchChange : function (thisObj) { + if (thisObj.valueElement) { + if (jsc.isElementType(thisObj.valueElement, 'input')) { + jsc.fireEvent(thisObj.valueElement, 'change'); + } + } + }, + + + dispatchFineChange : function (thisObj) { + if (thisObj.onFineChange) { + var callback; + if (typeof thisObj.onFineChange === 'string') { + callback = new Function (thisObj.onFineChange); + } else { + callback = thisObj.onFineChange; + } + callback.call(thisObj); + } + }, + + + setPad : function (thisObj, e, ofsX, ofsY) { + var pointerAbs = jsc.getAbsPointerPos(e); + var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.insetWidth; + var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; + + var xVal = x * (360 / (thisObj.width - 1)); + var yVal = 100 - (y * (100 / (thisObj.height - 1))); + + switch (jsc.getPadYComponent(thisObj)) { + case 's': thisObj.fromHSV(xVal, yVal, null, jsc.leaveSld); break; + case 'v': thisObj.fromHSV(xVal, null, yVal, jsc.leaveSld); break; + } + }, + + + setSld : function (thisObj, e, ofsY) { + var pointerAbs = jsc.getAbsPointerPos(e); + var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; + + var yVal = 100 - (y * (100 / (thisObj.height - 1))); + + switch (jsc.getSliderComponent(thisObj)) { + case 's': thisObj.fromHSV(null, yVal, null, jsc.leavePad); break; + case 'v': thisObj.fromHSV(null, null, yVal, jsc.leavePad); break; + } + }, + + + _vmlNS : 'jsc_vml_', + _vmlCSS : 'jsc_vml_css_', + _vmlReady : false, + + + initVML : function () { + if (!jsc._vmlReady) { + // init VML namespace + var doc = document; + if (!doc.namespaces[jsc._vmlNS]) { + doc.namespaces.add(jsc._vmlNS, 'urn:schemas-microsoft-com:vml'); + } + if (!doc.styleSheets[jsc._vmlCSS]) { + var tags = ['shape', 'shapetype', 'group', 'background', 'path', 'formulas', 'handles', 'fill', 'stroke', 'shadow', 'textbox', 'textpath', 'imagedata', 'line', 'polyline', 'curve', 'rect', 'roundrect', 'oval', 'arc', 'image']; + var ss = doc.createStyleSheet(); + ss.owningElement.id = jsc._vmlCSS; + for (var i = 0; i < tags.length; i += 1) { + ss.addRule(jsc._vmlNS + '\\:' + tags[i], 'behavior:url(#default#VML);'); + } + } + jsc._vmlReady = true; + } + }, + + + createPalette : function () { + + var paletteObj = { + elm: null, + draw: null + }; + + if (jsc.isCanvasSupported) { + // Canvas implementation for modern browsers + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + var drawFunc = function (width, height, type) { + canvas.width = width; + canvas.height = height; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0); + hGrad.addColorStop(0 / 6, '#F00'); + hGrad.addColorStop(1 / 6, '#FF0'); + hGrad.addColorStop(2 / 6, '#0F0'); + hGrad.addColorStop(3 / 6, '#0FF'); + hGrad.addColorStop(4 / 6, '#00F'); + hGrad.addColorStop(5 / 6, '#F0F'); + hGrad.addColorStop(6 / 6, '#F00'); + + ctx.fillStyle = hGrad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height); + switch (type.toLowerCase()) { + case 's': + vGrad.addColorStop(0, 'rgba(255,255,255,0)'); + vGrad.addColorStop(1, 'rgba(255,255,255,1)'); + break; + case 'v': + vGrad.addColorStop(0, 'rgba(0,0,0,0)'); + vGrad.addColorStop(1, 'rgba(0,0,0,1)'); + break; + } + ctx.fillStyle = vGrad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }; + + paletteObj.elm = canvas; + paletteObj.draw = drawFunc; + + } else { + // VML fallback for IE 7 and 8 + + jsc.initVML(); + + var vmlContainer = document.createElement('div'); + vmlContainer.style.position = 'relative'; + vmlContainer.style.overflow = 'hidden'; + + var hGrad = document.createElement(jsc._vmlNS + ':fill'); + hGrad.type = 'gradient'; + hGrad.method = 'linear'; + hGrad.angle = '90'; + hGrad.colors = '16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0' + + var hRect = document.createElement(jsc._vmlNS + ':rect'); + hRect.style.position = 'absolute'; + hRect.style.left = -1 + 'px'; + hRect.style.top = -1 + 'px'; + hRect.stroked = false; + hRect.appendChild(hGrad); + vmlContainer.appendChild(hRect); + + var vGrad = document.createElement(jsc._vmlNS + ':fill'); + vGrad.type = 'gradient'; + vGrad.method = 'linear'; + vGrad.angle = '180'; + vGrad.opacity = '0'; + + var vRect = document.createElement(jsc._vmlNS + ':rect'); + vRect.style.position = 'absolute'; + vRect.style.left = -1 + 'px'; + vRect.style.top = -1 + 'px'; + vRect.stroked = false; + vRect.appendChild(vGrad); + vmlContainer.appendChild(vRect); + + var drawFunc = function (width, height, type) { + vmlContainer.style.width = width + 'px'; + vmlContainer.style.height = height + 'px'; + + hRect.style.width = + vRect.style.width = + (width + 1) + 'px'; + hRect.style.height = + vRect.style.height = + (height + 1) + 'px'; + + // Colors must be specified during every redraw, otherwise IE won't display + // a full gradient during a subsequential redraw + hGrad.color = '#F00'; + hGrad.color2 = '#F00'; + + switch (type.toLowerCase()) { + case 's': + vGrad.color = vGrad.color2 = '#FFF'; + break; + case 'v': + vGrad.color = vGrad.color2 = '#000'; + break; + } + }; + + paletteObj.elm = vmlContainer; + paletteObj.draw = drawFunc; + } + + return paletteObj; + }, + + + createSliderGradient : function () { + + var sliderObj = { + elm: null, + draw: null + }; + + if (jsc.isCanvasSupported) { + // Canvas implementation for modern browsers + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + var drawFunc = function (width, height, color1, color2) { + canvas.width = width; + canvas.height = height; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + + var grad = ctx.createLinearGradient(0, 0, 0, canvas.height); + grad.addColorStop(0, color1); + grad.addColorStop(1, color2); + + ctx.fillStyle = grad; + ctx.fillRect(0, 0, canvas.width, canvas.height); + }; + + sliderObj.elm = canvas; + sliderObj.draw = drawFunc; + + } else { + // VML fallback for IE 7 and 8 + + jsc.initVML(); + + var vmlContainer = document.createElement('div'); + vmlContainer.style.position = 'relative'; + vmlContainer.style.overflow = 'hidden'; + + var grad = document.createElement(jsc._vmlNS + ':fill'); + grad.type = 'gradient'; + grad.method = 'linear'; + grad.angle = '180'; + + var rect = document.createElement(jsc._vmlNS + ':rect'); + rect.style.position = 'absolute'; + rect.style.left = -1 + 'px'; + rect.style.top = -1 + 'px'; + rect.stroked = false; + rect.appendChild(grad); + vmlContainer.appendChild(rect); + + var drawFunc = function (width, height, color1, color2) { + vmlContainer.style.width = width + 'px'; + vmlContainer.style.height = height + 'px'; + + rect.style.width = (width + 1) + 'px'; + rect.style.height = (height + 1) + 'px'; + + grad.color = color1; + grad.color2 = color2; + }; + + sliderObj.elm = vmlContainer; + sliderObj.draw = drawFunc; + } + + return sliderObj; + }, + + + leaveValue : 1<<0, + leaveStyle : 1<<1, + leavePad : 1<<2, + leaveSld : 1<<3, + + + BoxShadow : (function () { + var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) { + this.hShadow = hShadow; + this.vShadow = vShadow; + this.blur = blur; + this.spread = spread; + this.color = color; + this.inset = !!inset; + }; + + BoxShadow.prototype.toString = function () { + var vals = [ + Math.round(this.hShadow) + 'px', + Math.round(this.vShadow) + 'px', + Math.round(this.blur) + 'px', + Math.round(this.spread) + 'px', + this.color + ]; + if (this.inset) { + vals.push('inset'); + } + return vals.join(' '); + }; + + return BoxShadow; + })(), + + + // + // Usage: + // var myColor = new jscolor( [, ]) + // + + jscolor : function (targetElement, options) { + + // General options + // + this.value = null; // initial HEX color. To change it later, use methods fromString(), fromHSV() and fromRGB() + this.valueElement = targetElement; // element that will be used to display and input the color code + this.styleElement = targetElement; // element that will preview the picked color using CSS backgroundColor + this.required = true; // whether the associated text can be left empty + this.refine = true; // whether to refine the entered color code (e.g. uppercase it and remove whitespace) + this.hash = false; // whether to prefix the HEX color code with # symbol + this.uppercase = true; // whether to uppercase the color code + this.onFineChange = null; // called instantly every time the color changes (value can be either a function or a string with javascript code) + this.activeClass = 'jscolor-active'; // class to be set to the target element when a picker window is open on it + this.minS = 0; // min allowed saturation (0 - 100) + this.maxS = 100; // max allowed saturation (0 - 100) + this.minV = 0; // min allowed value (brightness) (0 - 100) + this.maxV = 100; // max allowed value (brightness) (0 - 100) + + // Accessing the picked color + // + this.hsv = [0, 0, 100]; // read-only [0-360, 0-100, 0-100] + this.rgb = [255, 255, 255]; // read-only [0-255, 0-255, 0-255] + + // Color Picker options + // + this.width = 181; // width of color palette (in px) + this.height = 101; // height of color palette (in px) + this.showOnClick = true; // whether to display the color picker when user clicks on its target element + this.mode = 'HSV'; // HSV | HVS | HS | HV - layout of the color picker controls + this.position = 'bottom'; // left | right | top | bottom - position relative to the target element + this.smartPosition = true; // automatically change picker position when there is not enough space for it + this.sliderSize = 16; // px + this.crossSize = 8; // px + this.closable = false; // whether to display the Close button + this.closeText = 'Close'; + this.buttonColor = '#000000'; // CSS color + this.buttonHeight = 18; // px + this.padding = 12; // px + this.backgroundColor = '#FFFFFF'; // CSS color + this.borderWidth = 1; // px + this.borderColor = '#BBBBBB'; // CSS color + this.borderRadius = 8; // px + this.insetWidth = 1; // px + this.insetColor = '#BBBBBB'; // CSS color + this.shadow = true; // whether to display shadow + this.shadowBlur = 15; // px + this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color + this.pointerColor = '#4C4C4C'; // px + this.pointerBorderColor = '#FFFFFF'; // px + this.pointerBorderWidth = 1; // px + this.pointerThickness = 2; // px + this.zIndex = 1000; + this.container = null; // where to append the color picker (BODY element by default) + + + for (var opt in options) { + if (options.hasOwnProperty(opt)) { + this[opt] = options[opt]; + } + } + + + this.hide = function () { + if (isPickerOwner()) { + detachPicker(); + } + }; + + + this.show = function () { + drawPicker(); + }; + + + this.redraw = function () { + if (isPickerOwner()) { + drawPicker(); + } + }; + + + this.importColor = function () { + if (!this.valueElement) { + this.exportColor(); + } else { + if (jsc.isElementType(this.valueElement, 'input')) { + if (!this.refine) { + if (!this.fromString(this.valueElement.value, jsc.leaveValue)) { + if (this.styleElement) { + this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; + this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; + this.styleElement.style.color = this.styleElement._jscOrigStyle.color; + } + this.exportColor(jsc.leaveValue | jsc.leaveStyle); + } + } else if (!this.required && /^\s*$/.test(this.valueElement.value)) { + this.valueElement.value = ''; + if (this.styleElement) { + this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; + this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; + this.styleElement.style.color = this.styleElement._jscOrigStyle.color; + } + this.exportColor(jsc.leaveValue | jsc.leaveStyle); + + } else if (this.fromString(this.valueElement.value)) { + // managed to import color successfully from the value -> OK, don't do anything + } else { + this.exportColor(); + } + } else { + // not an input element -> doesn't have any value + this.exportColor(); + } + } + }; + + + this.exportColor = function (flags) { + if (!(flags & jsc.leaveValue) && this.valueElement) { + var value = this.toString(); + if (this.uppercase) { value = value.toUpperCase(); } + if (this.hash) { value = '#' + value; } + + if (jsc.isElementType(this.valueElement, 'input')) { + this.valueElement.value = value; + } else { + this.valueElement.innerHTML = value; + } + } + if (!(flags & jsc.leaveStyle)) { + if (this.styleElement) { + this.styleElement.style.backgroundImage = 'none'; + this.styleElement.style.backgroundColor = '#' + this.toString(); + this.styleElement.style.color = this.isLight() ? '#000' : '#FFF'; + } + } + if (!(flags & jsc.leavePad) && isPickerOwner()) { + redrawPad(); + } + if (!(flags & jsc.leaveSld) && isPickerOwner()) { + redrawSld(); + } + }; + + + // h: 0-360 + // s: 0-100 + // v: 0-100 + // + this.fromHSV = function (h, s, v, flags) { // null = don't change + if (h !== null) { + if (isNaN(h)) { return false; } + h = Math.max(0, Math.min(360, h)); + } + if (s !== null) { + if (isNaN(s)) { return false; } + s = Math.max(0, Math.min(100, this.maxS, s), this.minS); + } + if (v !== null) { + if (isNaN(v)) { return false; } + v = Math.max(0, Math.min(100, this.maxV, v), this.minV); + } + + this.rgb = HSV_RGB( + h===null ? this.hsv[0] : (this.hsv[0]=h), + s===null ? this.hsv[1] : (this.hsv[1]=s), + v===null ? this.hsv[2] : (this.hsv[2]=v) + ); + + this.exportColor(flags); + }; + + + // r: 0-255 + // g: 0-255 + // b: 0-255 + // + this.fromRGB = function (r, g, b, flags) { // null = don't change + if (r !== null) { + if (isNaN(r)) { return false; } + r = Math.max(0, Math.min(255, r)); + } + if (g !== null) { + if (isNaN(g)) { return false; } + g = Math.max(0, Math.min(255, g)); + } + if (b !== null) { + if (isNaN(b)) { return false; } + b = Math.max(0, Math.min(255, b)); + } + + var hsv = RGB_HSV( + r===null ? this.rgb[0] : r, + g===null ? this.rgb[1] : g, + b===null ? this.rgb[2] : b + ); + if (hsv[0] !== null) { + this.hsv[0] = Math.max(0, Math.min(360, hsv[0])); + } + if (hsv[2] !== 0) { + this.hsv[1] = hsv[1]===null ? null : Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1])); + } + this.hsv[2] = hsv[2]===null ? null : Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2])); + + // update RGB according to final HSV, as some values might be trimmed + var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]); + this.rgb[0] = rgb[0]; + this.rgb[1] = rgb[1]; + this.rgb[2] = rgb[2]; + + this.exportColor(flags); + }; + + + this.fromString = function (str, flags) { + var m; + if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) { + // HEX notation + // + + if (m[1].length === 6) { + // 6-char notation + this.fromRGB( + parseInt(m[1].substr(0,2),16), + parseInt(m[1].substr(2,2),16), + parseInt(m[1].substr(4,2),16), + flags + ); + } else { + // 3-char notation + this.fromRGB( + parseInt(m[1].charAt(0) + m[1].charAt(0),16), + parseInt(m[1].charAt(1) + m[1].charAt(1),16), + parseInt(m[1].charAt(2) + m[1].charAt(2),16), + flags + ); + } + return true; + + } else if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) { + var params = m[1].split(','); + var re = /^\s*(\d*)(\.\d+)?\s*$/; + var mR, mG, mB; + if ( + params.length >= 3 && + (mR = params[0].match(re)) && + (mG = params[1].match(re)) && + (mB = params[2].match(re)) + ) { + var r = parseFloat((mR[1] || '0') + (mR[2] || '')); + var g = parseFloat((mG[1] || '0') + (mG[2] || '')); + var b = parseFloat((mB[1] || '0') + (mB[2] || '')); + this.fromRGB(r, g, b, flags); + return true; + } + } + return false; + }; + + + this.toString = function () { + return ( + (0x100 | Math.round(this.rgb[0])).toString(16).substr(1) + + (0x100 | Math.round(this.rgb[1])).toString(16).substr(1) + + (0x100 | Math.round(this.rgb[2])).toString(16).substr(1) + ); + }; + + + this.toHEXString = function () { + return '#' + this.toString().toUpperCase(); + }; + + + this.toRGBString = function () { + return ('rgb(' + + Math.round(this.rgb[0]) + ',' + + Math.round(this.rgb[1]) + ',' + + Math.round(this.rgb[2]) + ')' + ); + }; + + + this.isLight = function () { + return ( + 0.213 * this.rgb[0] + + 0.715 * this.rgb[1] + + 0.072 * this.rgb[2] > + 255 / 2 + ); + }; + + + this._processParentElementsInDOM = function () { + if (this._linkedElementsProcessed) { return; } + this._linkedElementsProcessed = true; + + var elm = this.targetElement; + do { + // If the target element or one of its parent nodes has fixed position, + // then use fixed positioning instead + // + // Note: In Firefox, getComputedStyle returns null in a hidden iframe, + // that's why we need to check if the returned style object is non-empty + var currStyle = jsc.getStyle(elm); + if (currStyle && currStyle.position.toLowerCase() === 'fixed') { + this.fixed = true; + } + + if (elm !== this.targetElement) { + // Ensure to attach onParentScroll only once to each parent element + // (multiple targetElements can share the same parent nodes) + // + // Note: It's not just offsetParents that can be scrollable, + // that's why we loop through all parent nodes + if (!elm._jscEventsAttached) { + jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); + elm._jscEventsAttached = true; + } + } + } while ((elm = elm.parentNode) && !jsc.isElementType(elm, 'body')); + }; + + + // r: 0-255 + // g: 0-255 + // b: 0-255 + // + // returns: [ 0-360, 0-100, 0-100 ] + // + function RGB_HSV (r, g, b) { + r /= 255; + g /= 255; + b /= 255; + var n = Math.min(Math.min(r,g),b); + var v = Math.max(Math.max(r,g),b); + var m = v - n; + if (m === 0) { return [ null, 0, 100 * v ]; } + var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); + return [ + 60 * (h===6?0:h), + 100 * (m/v), + 100 * v + ]; + } + + + // h: 0-360 + // s: 0-100 + // v: 0-100 + // + // returns: [ 0-255, 0-255, 0-255 ] + // + function HSV_RGB (h, s, v) { + var u = 255 * (v / 100); + + if (h === null) { + return [ u, u, u ]; + } + + h /= 60; + s /= 100; + + var i = Math.floor(h); + var f = i%2 ? h-i : 1-(h-i); + var m = u * (1 - s); + var n = u * (1 - s * f); + switch (i) { + case 6: + case 0: return [u,n,m]; + case 1: return [n,u,m]; + case 2: return [m,u,n]; + case 3: return [m,n,u]; + case 4: return [n,m,u]; + case 5: return [u,m,n]; + } + } + + + function detachPicker () { + jsc.unsetClass(THIS.targetElement, THIS.activeClass); + jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap); + delete jsc.picker.owner; + } + + + function drawPicker () { + + // At this point, when drawing the picker, we know what the parent elements are + // and we can do all related DOM operations, such as registering events on them + // or checking their positioning + THIS._processParentElementsInDOM(); + + if (!jsc.picker) { + jsc.picker = { + owner: null, + wrap : document.createElement('div'), + box : document.createElement('div'), + boxS : document.createElement('div'), // shadow area + boxB : document.createElement('div'), // border + pad : document.createElement('div'), + padB : document.createElement('div'), // border + padM : document.createElement('div'), // mouse/touch area + padPal : jsc.createPalette(), + cross : document.createElement('div'), + crossBY : document.createElement('div'), // border Y + crossBX : document.createElement('div'), // border X + crossLY : document.createElement('div'), // line Y + crossLX : document.createElement('div'), // line X + sld : document.createElement('div'), + sldB : document.createElement('div'), // border + sldM : document.createElement('div'), // mouse/touch area + sldGrad : jsc.createSliderGradient(), + sldPtrS : document.createElement('div'), // slider pointer spacer + sldPtrIB : document.createElement('div'), // slider pointer inner border + sldPtrMB : document.createElement('div'), // slider pointer middle border + sldPtrOB : document.createElement('div'), // slider pointer outer border + btn : document.createElement('div'), + btnT : document.createElement('span') // text + }; + + jsc.picker.pad.appendChild(jsc.picker.padPal.elm); + jsc.picker.padB.appendChild(jsc.picker.pad); + jsc.picker.cross.appendChild(jsc.picker.crossBY); + jsc.picker.cross.appendChild(jsc.picker.crossBX); + jsc.picker.cross.appendChild(jsc.picker.crossLY); + jsc.picker.cross.appendChild(jsc.picker.crossLX); + jsc.picker.padB.appendChild(jsc.picker.cross); + jsc.picker.box.appendChild(jsc.picker.padB); + jsc.picker.box.appendChild(jsc.picker.padM); + + jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm); + jsc.picker.sldB.appendChild(jsc.picker.sld); + jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB); + jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB); + jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB); + jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS); + jsc.picker.box.appendChild(jsc.picker.sldB); + jsc.picker.box.appendChild(jsc.picker.sldM); + + jsc.picker.btn.appendChild(jsc.picker.btnT); + jsc.picker.box.appendChild(jsc.picker.btn); + + jsc.picker.boxB.appendChild(jsc.picker.box); + jsc.picker.wrap.appendChild(jsc.picker.boxS); + jsc.picker.wrap.appendChild(jsc.picker.boxB); + } + + var p = jsc.picker; + + var displaySlider = !!jsc.getSliderComponent(THIS); + var dims = jsc.getPickerDims(THIS); + var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); + var padToSliderPadding = jsc.getPadToSliderPadding(THIS); + var borderRadius = Math.min( + THIS.borderRadius, + Math.round(THIS.padding * Math.PI)); // px + var padCursor = 'crosshair'; + + // wrap + p.wrap.style.clear = 'both'; + p.wrap.style.width = (dims[0] + 2 * THIS.borderWidth) + 'px'; + p.wrap.style.height = (dims[1] + 2 * THIS.borderWidth) + 'px'; + p.wrap.style.zIndex = THIS.zIndex; + + // picker + p.box.style.width = dims[0] + 'px'; + p.box.style.height = dims[1] + 'px'; + + p.boxS.style.position = 'absolute'; + p.boxS.style.left = '0'; + p.boxS.style.top = '0'; + p.boxS.style.width = '100%'; + p.boxS.style.height = '100%'; + jsc.setBorderRadius(p.boxS, borderRadius + 'px'); + + // picker border + p.boxB.style.position = 'relative'; + p.boxB.style.border = THIS.borderWidth + 'px solid'; + p.boxB.style.borderColor = THIS.borderColor; + p.boxB.style.background = THIS.backgroundColor; + jsc.setBorderRadius(p.boxB, borderRadius + 'px'); + + // IE hack: + // If the element is transparent, IE will trigger the event on the elements under it, + // e.g. on Canvas or on elements with border + p.padM.style.background = + p.sldM.style.background = + '#FFF'; + jsc.setStyle(p.padM, 'opacity', '0'); + jsc.setStyle(p.sldM, 'opacity', '0'); + + // pad + p.pad.style.position = 'relative'; + p.pad.style.width = THIS.width + 'px'; + p.pad.style.height = THIS.height + 'px'; + + // pad palettes (HSV and HVS) + p.padPal.draw(THIS.width, THIS.height, jsc.getPadYComponent(THIS)); + + // pad border + p.padB.style.position = 'absolute'; + p.padB.style.left = THIS.padding + 'px'; + p.padB.style.top = THIS.padding + 'px'; + p.padB.style.border = THIS.insetWidth + 'px solid'; + p.padB.style.borderColor = THIS.insetColor; + + // pad mouse area + p.padM._jscInstance = THIS; + p.padM._jscControlName = 'pad'; + p.padM.style.position = 'absolute'; + p.padM.style.left = '0'; + p.padM.style.top = '0'; + p.padM.style.width = (THIS.padding + 2 * THIS.insetWidth + THIS.width + padToSliderPadding / 2) + 'px'; + p.padM.style.height = dims[1] + 'px'; + p.padM.style.cursor = padCursor; + + // pad cross + p.cross.style.position = 'absolute'; + p.cross.style.left = + p.cross.style.top = + '0'; + p.cross.style.width = + p.cross.style.height = + crossOuterSize + 'px'; + + // pad cross border Y and X + p.crossBY.style.position = + p.crossBX.style.position = + 'absolute'; + p.crossBY.style.background = + p.crossBX.style.background = + THIS.pointerBorderColor; + p.crossBY.style.width = + p.crossBX.style.height = + (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; + p.crossBY.style.height = + p.crossBX.style.width = + crossOuterSize + 'px'; + p.crossBY.style.left = + p.crossBX.style.top = + (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px'; + p.crossBY.style.top = + p.crossBX.style.left = + '0'; + + // pad cross line Y and X + p.crossLY.style.position = + p.crossLX.style.position = + 'absolute'; + p.crossLY.style.background = + p.crossLX.style.background = + THIS.pointerColor; + p.crossLY.style.height = + p.crossLX.style.width = + (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px'; + p.crossLY.style.width = + p.crossLX.style.height = + THIS.pointerThickness + 'px'; + p.crossLY.style.left = + p.crossLX.style.top = + (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px'; + p.crossLY.style.top = + p.crossLX.style.left = + THIS.pointerBorderWidth + 'px'; + + // slider + p.sld.style.overflow = 'hidden'; + p.sld.style.width = THIS.sliderSize + 'px'; + p.sld.style.height = THIS.height + 'px'; + + // slider gradient + p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000'); + + // slider border + p.sldB.style.display = displaySlider ? 'block' : 'none'; + p.sldB.style.position = 'absolute'; + p.sldB.style.right = THIS.padding + 'px'; + p.sldB.style.top = THIS.padding + 'px'; + p.sldB.style.border = THIS.insetWidth + 'px solid'; + p.sldB.style.borderColor = THIS.insetColor; + + // slider mouse area + p.sldM._jscInstance = THIS; + p.sldM._jscControlName = 'sld'; + p.sldM.style.display = displaySlider ? 'block' : 'none'; + p.sldM.style.position = 'absolute'; + p.sldM.style.right = '0'; + p.sldM.style.top = '0'; + p.sldM.style.width = (THIS.sliderSize + padToSliderPadding / 2 + THIS.padding + 2 * THIS.insetWidth) + 'px'; + p.sldM.style.height = dims[1] + 'px'; + p.sldM.style.cursor = 'default'; + + // slider pointer inner and outer border + p.sldPtrIB.style.border = + p.sldPtrOB.style.border = + THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor; + + // slider pointer outer border + p.sldPtrOB.style.position = 'absolute'; + p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; + p.sldPtrOB.style.top = '0'; + + // slider pointer middle border + p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor; + + // slider pointer spacer + p.sldPtrS.style.width = THIS.sliderSize + 'px'; + p.sldPtrS.style.height = sliderPtrSpace + 'px'; + + // the Close button + function setBtnBorder () { + var insetColors = THIS.insetColor.split(/\s+/); + var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1]; + p.btn.style.borderColor = outsetColor; + } + p.btn.style.display = THIS.closable ? 'block' : 'none'; + p.btn.style.position = 'absolute'; + p.btn.style.left = THIS.padding + 'px'; + p.btn.style.bottom = THIS.padding + 'px'; + p.btn.style.padding = '0 15px'; + p.btn.style.height = THIS.buttonHeight + 'px'; + p.btn.style.border = THIS.insetWidth + 'px solid'; + setBtnBorder(); + p.btn.style.color = THIS.buttonColor; + p.btn.style.font = '12px sans-serif'; + p.btn.style.textAlign = 'center'; + try { + p.btn.style.cursor = 'pointer'; + } catch(eOldIE) { + p.btn.style.cursor = 'hand'; + } + p.btn.onmousedown = function () { + THIS.hide(); + }; + p.btnT.style.lineHeight = THIS.buttonHeight + 'px'; + p.btnT.innerHTML = ''; + p.btnT.appendChild(document.createTextNode(THIS.closeText)); + + // place pointers + redrawPad(); + redrawSld(); + + // If we are changing the owner without first closing the picker, + // make sure to first deal with the old owner + if (jsc.picker.owner && jsc.picker.owner !== THIS) { + jsc.unsetClass(jsc.picker.owner.targetElement, THIS.activeClass); + } + + // Set the new picker owner + jsc.picker.owner = THIS; + + // The redrawPosition() method needs picker.owner to be set, that's why we call it here, + // after setting the owner + if (jsc.isElementType(container, 'body')) { + jsc.redrawPosition(); + } else { + jsc._drawPosition(THIS, 0, 0, 'relative', false); + } + + if (p.wrap.parentNode != container) { + container.appendChild(p.wrap); + } + + jsc.setClass(THIS.targetElement, THIS.activeClass); + } + + + function redrawPad () { + // redraw the pad pointer + switch (jsc.getPadYComponent(THIS)) { + case 's': var yComponent = 1; break; + case 'v': var yComponent = 2; break; + } + var x = Math.round((THIS.hsv[0] / 360) * (THIS.width - 1)); + var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); + var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); + var ofs = -Math.floor(crossOuterSize / 2); + jsc.picker.cross.style.left = (x + ofs) + 'px'; + jsc.picker.cross.style.top = (y + ofs) + 'px'; + + // redraw the slider + switch (jsc.getSliderComponent(THIS)) { + case 's': + var rgb1 = HSV_RGB(THIS.hsv[0], 100, THIS.hsv[2]); + var rgb2 = HSV_RGB(THIS.hsv[0], 0, THIS.hsv[2]); + var color1 = 'rgb(' + + Math.round(rgb1[0]) + ',' + + Math.round(rgb1[1]) + ',' + + Math.round(rgb1[2]) + ')'; + var color2 = 'rgb(' + + Math.round(rgb2[0]) + ',' + + Math.round(rgb2[1]) + ',' + + Math.round(rgb2[2]) + ')'; + jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); + break; + case 'v': + var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 100); + var color1 = 'rgb(' + + Math.round(rgb[0]) + ',' + + Math.round(rgb[1]) + ',' + + Math.round(rgb[2]) + ')'; + var color2 = '#000'; + jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); + break; + } + } + + + function redrawSld () { + var sldComponent = jsc.getSliderComponent(THIS); + if (sldComponent) { + // redraw the slider pointer + switch (sldComponent) { + case 's': var yComponent = 1; break; + case 'v': var yComponent = 2; break; + } + var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); + jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(sliderPtrSpace / 2)) + 'px'; + } + } + + + function isPickerOwner () { + return jsc.picker && jsc.picker.owner === THIS; + } + + + function blurValue () { + THIS.importColor(); + } + + + // Find the target element + if (typeof targetElement === 'string') { + var id = targetElement; + var elm = document.getElementById(id); + if (elm) { + this.targetElement = elm; + } else { + jsc.warn('Could not find target element with ID \'' + id + '\''); + } + } else if (targetElement) { + this.targetElement = targetElement; + } else { + jsc.warn('Invalid target element: \'' + targetElement + '\''); + } + + if (this.targetElement._jscLinkedInstance) { + jsc.warn('Cannot link jscolor twice to the same element. Skipping.'); + return; + } + this.targetElement._jscLinkedInstance = this; + + // Find the value element + this.valueElement = jsc.fetchElement(this.valueElement); + // Find the style element + this.styleElement = jsc.fetchElement(this.styleElement); + + var THIS = this; + var container = + this.container ? + jsc.fetchElement(this.container) : + document.getElementsByTagName('body')[0]; + var sliderPtrSpace = 3; // px + + // For BUTTON elements it's important to stop them from sending the form when clicked + // (e.g. in Safari) + if (jsc.isElementType(this.targetElement, 'button')) { + if (this.targetElement.onclick) { + var origCallback = this.targetElement.onclick; + this.targetElement.onclick = function (evt) { + origCallback.call(this, evt); + return false; + }; + } else { + this.targetElement.onclick = function () { return false; }; + } + } + + /* + var elm = this.targetElement; + do { + // If the target element or one of its offsetParents has fixed position, + // then use fixed positioning instead + // + // Note: In Firefox, getComputedStyle returns null in a hidden iframe, + // that's why we need to check if the returned style object is non-empty + var currStyle = jsc.getStyle(elm); + if (currStyle && currStyle.position.toLowerCase() === 'fixed') { + this.fixed = true; + } + + if (elm !== this.targetElement) { + // attach onParentScroll so that we can recompute the picker position + // when one of the offsetParents is scrolled + if (!elm._jscEventsAttached) { + jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); + elm._jscEventsAttached = true; + } + } + } while ((elm = elm.offsetParent) && !jsc.isElementType(elm, 'body')); + */ + + // valueElement + if (this.valueElement) { + if (jsc.isElementType(this.valueElement, 'input')) { + var updateField = function () { + THIS.fromString(THIS.valueElement.value, jsc.leaveValue); + jsc.dispatchFineChange(THIS); + }; + jsc.attachEvent(this.valueElement, 'keyup', updateField); + jsc.attachEvent(this.valueElement, 'input', updateField); + jsc.attachEvent(this.valueElement, 'blur', blurValue); + this.valueElement.setAttribute('autocomplete', 'off'); + } + } + + // styleElement + if (this.styleElement) { + this.styleElement._jscOrigStyle = { + backgroundImage : this.styleElement.style.backgroundImage, + backgroundColor : this.styleElement.style.backgroundColor, + color : this.styleElement.style.color + }; + } + + if (this.value) { + // Try to set the color from the .value option and if unsuccessful, + // export the current color + this.fromString(this.value) || this.exportColor(); + } else { + this.importColor(); + } + } + +}; + + +//================================ +// Public properties and methods +//================================ + + +// By default, search for all elements with class="jscolor" and install a color picker on them. +// +// You can change what class name will be looked for by setting the property jscolor.lookupClass +// anywhere in your HTML document. To completely disable the automatic lookup, set it to null. +// +jsc.jscolor.lookupClass = 'jscolor'; + + +jsc.jscolor.installByClassName = function (className) { + var inputElms = document.getElementsByTagName('input'); + var buttonElms = document.getElementsByTagName('button'); + + jsc.tryInstallOnElements(inputElms, className); + jsc.tryInstallOnElements(buttonElms, className); +}; + + +jsc.register(); + + +return jsc.jscolor; + + +})(); } diff --git a/ext/planetstyles/flightdeck/composer.json b/ext/planetstyles/flightdeck/composer.json new file mode 100644 index 0000000..0fdfe64 --- /dev/null +++ b/ext/planetstyles/flightdeck/composer.json @@ -0,0 +1,23 @@ +{ + "name": "planetstyles/flightdeck", + "type": "phpbb-extension", + "description": "Control Panel for Milk v2 Theme", + "homepage": "http://www.planetstyles.net", + "version": "2.0.3", + "license": "Private use only", + "authors": [ + { + "name": "PlanetStyles", + "homepage": "http://www.planetstyles.net" + } + ], + "require": { + "php": ">=5.3.3" + }, + "extra": { + "display-name": "PlanetStyles Flight Deck (v2)", + "soft-require": { + "phpbb/phpbb": ">=3.1.0" + } + } +} diff --git a/ext/planetstyles/flightdeck/config/services.yml b/ext/planetstyles/flightdeck/config/services.yml new file mode 100644 index 0000000..160cd7a --- /dev/null +++ b/ext/planetstyles/flightdeck/config/services.yml @@ -0,0 +1,10 @@ +services: + planetstyles.flightdeck.listener: + class: planetstyles\flightdeck\event\listener + arguments: + - '@cache.driver' + - '@config' + - '@config_text' + - '@template' + tags: + - { name: event.listener } diff --git a/ext/planetstyles/flightdeck/event/listener.php b/ext/planetstyles/flightdeck/event/listener.php new file mode 100644 index 0000000..31f6d59 --- /dev/null +++ b/ext/planetstyles/flightdeck/event/listener.php @@ -0,0 +1,149 @@ +cache = $cache; + $this->config = $config; + $this->config_text = $config_text; + $this->template = $template; + } + + /** + * Assign functions defined in this class to event listeners in the core + * + * @return array + * @static + * @access public + */ + static public function getSubscribedEvents() + { + return array( + 'core.page_header' => 'load_style_settings_data', + ); + } + + /** + * Load style settings data + * + * @return null + * @access public + */ + public function load_style_settings_data() + { + $this->get_style_configs(); + + $html_code_data = $this->get_html_code(); + + $this->template->assign_vars(array( + 'FORUM_LOGO' => $this->get_forum_logo(), + 'FORUM_HEADER' => $this->get_forum_header(), + 'FORUM_FAVICON' => $this->get_forum_favicon(), + + 'STYLE_SETTINGS_HTML_1' => $html_code_data['style_settings_html_1'], + 'STYLE_SETTINGS_HTML_2' => $html_code_data['style_settings_html_2'], + 'STYLE_SETTINGS_HTML_3' => $html_code_data['style_settings_html_3'], + 'STYLE_SETTINGS_HTML_4' => $html_code_data['style_settings_html_4'], + )); + } + + /** + * Get the forum logo IMG tag + * + * @return string + */ + protected function get_forum_logo() + { + $logo_src = $this->config['style_settings_logo_path']; + $logo_width = $this->config['style_settings_logo_width'] ? ' width="' . $this->config['style_settings_logo_width'] . '"' : ''; + $logo_height = $this->config['style_settings_logo_height'] ? ' height="' . $this->config['style_settings_logo_height'] . '"' : ''; + + return $logo_src ? '' : ''; + } + + /** + * Get the forum header image + * + * @return string + */ + protected function get_forum_header() + { + $header_src = $this->config['style_settings_header_path']; + + return $header_src ? generate_board_url() . '/' . $header_src : ''; + } + + /** + * Get the forum favicon + * + * @return string + */ + protected function get_forum_favicon() + { + $favicon_src = $this->config['style_settings_favicon_path']; + + return $favicon_src ? generate_board_url() . '/' . $favicon_src : ''; + } + + /** + * Get the HTML code blocks as an array + * + * @return array + */ + protected function get_html_code() + { + if (($html_code_data = $this->cache->get('_style_settings_html_data')) === false) + { + $html_code_data = $this->config_text->get_array(array( + 'style_settings_html_1', + 'style_settings_html_2', + 'style_settings_html_3', + 'style_settings_html_4', + )); + + $this->cache->put('_style_settings_html_data', $html_code_data); + } + + return array_map('htmlspecialchars_decode', $html_code_data); + } + + /** + * Get the style settings and output them to the template + */ + protected function get_style_configs() + { + foreach ($this->config as $key => $value) + { + if (strpos($key, 'style_settings_config_') === 0) + { + $this->template->assign_var(strtoupper($key), $value); + } + } + } +} diff --git a/ext/planetstyles/flightdeck/language/en/acp_style_settings.php b/ext/planetstyles/flightdeck/language/en/acp_style_settings.php new file mode 100644 index 0000000..669e399 --- /dev/null +++ b/ext/planetstyles/flightdeck/language/en/acp_style_settings.php @@ -0,0 +1,53 @@ + 'Logos & Header Background', + 'STYLE_SETTINGS_LOGO_EXPLAIN' => '', + 'STYLE_SETTINGS_LOGO_UPLOAD' => 'Logo image upload', + 'STYLE_SETTINGS_LOGO_UPLOAD_EXPLAIN'=> 'Upload a logo image. The logo location below will automatically be filled in when an image is uploaded. Accepted formats: gif, jpg, png, svg.', + 'STYLE_SETTINGS_LOGO_PATH' => 'Logo image location', + 'STYLE_SETTINGS_LOGO_PATH_EXPLAIN' => 'This will be filled in automatically. Delete and submit to remove logo.', + 'STYLE_SETTINGS_LOGO_WIDTH' => 'Logo width', + 'STYLE_SETTINGS_LOGO_WIDTH_EXPLAIN' => 'Leave blank to use default logo size', + 'STYLE_SETTINGS_LOGO_HEIGHT' => 'Logo height', + 'STYLE_SETTINGS_LOGO_HEIGHT_EXPLAIN' => 'Leave blank to use default logo size', + 'STYLE_SETTINGS_LOGO_ERROR' => 'The logo file %s failed to upload.', + + 'STYLE_SETTINGS_HEADER_ERROR' => 'The header file %s failed to upload.', + 'STYLE_SETTINGS_HEADER_UPLOAD' => 'Header background upload.', + 'STYLE_SETTINGS_HEADER_UPLOAD_EXPLAIN' => 'Upload a header image (leave blank to use theme accent colour as background). The header location below will automatically be filled in when an image is uploaded.', + 'STYLE_SETTINGS_HEADER_PATH' => 'Header image location', + 'STYLE_SETTINGS_HEADER_PATH_EXPLAIN' => 'This will be filled in automatically. Delete and submit to remove header image.', + + 'STYLE_SETTINGS_FAVICON_ERROR' => 'The favicon file %s failed to upload.', + 'STYLE_SETTINGS_FAVICON_UPLOAD' => 'Favicon upload.', + 'STYLE_SETTINGS_FAVICON_EXPLAIN' => 'Upload a favicon. Square images strongly recommended.', + 'STYLE_SETTINGS_FAVICON_PATH' => 'Favicon image location', + 'STYLE_SETTINGS_FAVICON_PATH_EXPLAIN' => 'This will be filled in automatically. Delete and submit to remove favicon.', + + 'STYLE_SETTINGS_HTML_CODE' => 'HTML code blocks', + 'STYLE_SETTINGS_HTML_1' => 'Left Sidebar Custom Content', + 'STYLE_SETTINGS_HTML_2' => 'Right Sidebar Custom Content', + 'STYLE_SETTINGS_HTML_3' => 'Social Footer Code', + 'STYLE_SETTINGS_HTML_4' => 'Global <head> Code', + 'STYLE_SETTINGS_HTML_HELP_1' => 'Supports HTML and special characters', + 'STYLE_SETTINGS_HTML_HELP_2' => 'Supports HTML and special characters', + 'STYLE_SETTINGS_HTML_HELP_3' => 'See documentation for examples', + 'STYLE_SETTINGS_HTML_HELP_4' => 'WARNING! Bad things will happen if you\'re inexperienced', + 'STYLE_SETTINGS_HTML_EXPLAIN' => 'Uses the template var {STYLE_SETTINGS_HTML_%d}', + + 'STYLE_SETTINGS_CONFIG' => 'Style Settings', + 'STYLE_SETTINGS_CONFIG_EXPLAIN' => 'Uses the template var {%s}', + + 'STYLE_SETTINGS_SAVED' => 'Style settings have been saved.', +)); diff --git a/ext/planetstyles/flightdeck/language/en/info_acp_style.php b/ext/planetstyles/flightdeck/language/en/info_acp_style.php new file mode 100644 index 0000000..1b3c508 --- /dev/null +++ b/ext/planetstyles/flightdeck/language/en/info_acp_style.php @@ -0,0 +1,16 @@ + 'Flight Deck (Control Panel)', + 'ACP_STYLE_SETTINGS_SETTINGS' => 'Theme Settings', +)); diff --git a/ext/planetstyles/flightdeck/migrations/install_style_settings.php b/ext/planetstyles/flightdeck/migrations/install_style_settings.php new file mode 100644 index 0000000..7e414f5 --- /dev/null +++ b/ext/planetstyles/flightdeck/migrations/install_style_settings.php @@ -0,0 +1,61 @@ + '\planetstyles\flightdeck\acp\main_module', + 'modes' => array('settings'), + ), + )), + ); + } + + public function revert_data() + { + return array( + array('custom', array(array($this, 'clean_style_configs'))), + ); + } + + public function clean_style_configs() + { + foreach ($this->config as $key => $value) + { + if (strpos($key, 'style_settings_config_') === 0) + { + $this->config->delete($key); + } + } + } +} diff --git a/ext/planetstyles/flightdeck/styles/style_config.json b/ext/planetstyles/flightdeck/styles/style_config.json new file mode 100644 index 0000000..3f08a82 --- /dev/null +++ b/ext/planetstyles/flightdeck/styles/style_config.json @@ -0,0 +1,263 @@ +{ + +"Logo Position": { + "name": "logo_position", + "type": "list", + "help": "Sets the position of the forum logo.
Default value: 'middle'.", + "values": [ + "Left", + "Middle", + "Right" + ] +}, + +"Header Background Repeat": { + "name": "header_repeat", + "type": "list", + "help": "Sets the repeat direction for the header background image.
Default value: 'no-repeat'.", + "values": [ + "repeat-x", + "repeat-y", + "repeat", + "no-repeat" + ] +}, + +"Header Background Position": { + "name": "header_position", + "type": "list", + "help": "Sets position for header background.
Default value: 'middle top'.", + "values": [ + "left top", + "left center", + "left bottom", + "center top", + "center center", + "center bottom", + "right top", + "right center", + "right bottom" + ] +}, + +"Stretch header background to fill space?": { + "name": "header_size", + "type": "bool", + "help": "Stretches background image to fill header space. Only recommended for large backgrounds that don't repeat.
Note: Automatically applied when parallax header is enabled.
Default value: No." +}, + +"Header Background Effect": { + "name": "header_effect", + "type": "list", + "help": "Applies a visual style to the header bg image (if uploaded).
Default value: 'none'.", + "values": [ + "None", + "Colour Overlay", + "Darken" + ] +}, + +"Parallax Header?": { + "name": "parallax_header", + "type": "bool", + "help": "Subtly moves the header background when scrolling.
Note: Requires a header image taller than the forum header. See documentation.
Default value: No." +}, + +"Enable Animated Header Particles?": { + "name": "header_particles", + "type": "bool", + "help": "Enables an animated geometric overlay across the header.
Default value: No." +}, + +"Header Format": { + "name": "header_format", + "type": "list", + "help": "Text uses forum's sitename and description.
Default value: Both.", + "values": [ + "Logo Only", + "Sitename and Description Only", + "Both" + ] +}, + +"Navbar Position": { + "name": "navbar_position", + "type": "list", + "help": "Defines where the primary navigation is displayed.
Default value: 'Outside Header'.", + "values": [ + "Outside Header", + "Inside Header" + ] +}, + +"Font": { + "name": "font_family", + "type": "list", + "help": "Set the font face. Previews can be found in description'
Default: Roboto.", + "values": [ + "Roboto", + "Roboto Slab", + "Bubbler One", + "Dosis", + "Exo 2", + "Raleway", + "Rubik", + "Quicksand" + ] +}, + + +"Layout Type": { + "name": "layout_type", + "type": "list", + "help": "Sets the width of your forum.
Fluid: Full width, resizes and scales with the browser.
Boxed: Fixed width, margins between content and browser edges.
Note: In boxed layout sidebars won't display on smaller devices
Default value: Fluid.", + "values": [ + "Fluid", + "Boxed" + ] +}, + +"Theme Base Colour": { + "name": "base_colour", + "type": "list", + "help": "Choose a light or dark base colour. See documentation for previews.
Default value: 'Light'.", + "values": [ + "Light", + "Dark" + ] +}, + +"Theme Preset Colour": { + "name": "colour_preset", + "type": "list", + "help": "Choose a predefined colour palette for your forum's accent colour. See documentation for previews.
Default value: 'No Preset'.", + "values": [ + "Custom (Use colour picker below)", + "No Custom Colour", + "Aqua", + "Blush", + "Blue_Lagoon", + "Calm_Darya", + "Electric_Violet", + "HoneyDew", + "Hot_Red", + "Ibiza_Sunset", + "Little_Leaf", + "Neon_Blue", + "Nighthawk", + "Pacific_Dream", + "Peach", + "Purple_Love", + "Rose_Water", + "Sahara", + "Titanium", + "Warm_Sunset" + ] +}, + +"Theme Accent Colour": { + "name": "colour_picker", + "help": "Select colour of category headers, links and buttons.
Note: Only applied if 'No Preset' is selected above.", + "type": "string" +}, + +"High Contrast Footer Links?": { + "name": "high_contrast_links", + "type": "bool", + "help": "Overrides custom colour above with white links. Enable if you've set a dark colour above.
Default Value: No" +}, + + +"Forumlist Display": { + "name": "forumlist_display", + "help": "Defines how forums are displayed on the index page.
Default setting: List.", + "type": "list", + "values": [ + "List", + "List (Simplified)", + "Grid" + ] +}, + +"Content Block Headers": { + "name": "content_block_header", + "type": "list", + "help": "Defines how category headers are displayed.
Default value: 'Block'.", + "values": [ + "Stripe", + "Block" + ] +}, + +"Enable Sidebars?": { + "name": "sidebars", + "type": "bool", + "help": "Choose to enable or disable sidebars. Further options configured below.
Default Value: No" +}, + +"Sidebar Placement": { + "name": "sidebar_placement", + "type": "list", + "help": "Choose which sidebars to display.
Note: Left Sidebar will be hidden on handheld devices and small desktops.
Default value: 'Right only'.", + "values": [ + "Left Only", + "Right Only", + "Both" + ] +}, + +"Enable Sidebar Profile Widget?": { + "name": "profile_widget", + "type": "bool", + "help": "Displays User Avatar and basic profile links in right sidebar. Guests see a login/registration form,
Default Value: No" +}, + +"Enable Sidebar Search Widget?": { + "name": "search_widget", + "type": "bool", + "help": "Displays a quick search form in the sidebar.
Default Value: No" +}, + +"Postprofile Side": { + "name": "postprofile_side", + "help": "Sets which side avatars / post counts display on when reading a topic.
Default setting: Right.", + "type": "list", + "values": [ + "Left", + "Right" + ] +}, + +"Enable Collapsible Panels?": { + "name": "collapsible_panels", + "type": "bool", + "help": "Allows end-users to collapse/hide categories and sidebar blocks.
Default Value: No" +}, + + +"Enable Scroll to Top button?": { + "name": "scroll_to_top", + "type": "bool", + "help": "Adds a scroll to top shortcut on long pages.
Default Value: No" +}, + + +"Hide Social Bar (footer)?": { + "name": "hide_social_bar", + "type": "bool", + "help": "Hides the social bar above the footer.
Default Value: No" +}, + +"Force Rounded 'Last Post' Avatars?": { + "name": "rounded_avatars", + "type": "bool", + "help": "Forces the avatars within the 'last post' column to be round.
Requires Avatar in last post extension.
Default Value: No" +}, + +"Hide PlanetStyles link in footer?": { + "name": "credit_line", + "type": "bool", + "help": "We'd rather you didn't, but it's OK if you really want to :)" +} + +} \ No newline at end of file diff --git a/faq.php b/faq.php new file mode 100644 index 0000000..36a33c9 --- /dev/null +++ b/faq.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); + +// Start session management +$user->session_begin(); +$auth->acl($user->data); +$user->setup(); + +/** @var \phpbb\controller\helper $controller_helper */ +$controller_helper = $phpbb_container->get('controller.helper'); + +$response = new \Symfony\Component\HttpFoundation\RedirectResponse( + $controller_helper->route( + $request->variable('mode', 'faq') === 'bbcode' ? 'phpbb_help_bbcode_controller' : 'phpbb_help_faq_controller' + ), + 301 +); +$response->send(); diff --git a/feed.php b/feed.php new file mode 100644 index 0000000..1480867 --- /dev/null +++ b/feed.php @@ -0,0 +1,58 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +* Idea and original RSS Feed 2.0 MOD (Version 1.0.8/9) by leviatan21 +* Original MOD: http://www.phpbb.com/community/viewtopic.php?f=69&t=1214645 +* MOD Author Profile: http://www.phpbb.com/community/memberlist.php?mode=viewprofile&u=345763 +* MOD Author Homepage: http://www.mssti.com/phpbb3/ +* +**/ + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\Routing\Exception\InvalidParameterException; + +/** +* @ignore +**/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); + +/** @var \phpbb\controller\helper $controller_helper */ +$controller_helper = $phpbb_container->get('controller.helper'); + +$forum_id = $request->variable('f', 0); +$topic_id = $request->variable('t', 0); +$mode = $request->variable('mode', ''); + +if ($forum_id !== 0) +{ + $url = $controller_helper->route('phpbb_feed_forum', array('forum_id' => $forum_id)); +} +else if ($topic_id !== 0) +{ + $url = $controller_helper->route('phpbb_feed_topic', array('topic_id' => $topic_id)); +} +else +{ + try + { + $url = $controller_helper->route('phpbb_feed_overall', array('mode' => $mode)); + } + catch (InvalidParameterException $e) + { + $url = $controller_helper->route('phpbb_feed_index'); + } +} + +$response = new RedirectResponse($url, 301); +$response->send(); diff --git a/files/.htaccess b/files/.htaccess new file mode 100644 index 0000000..aa5afc1 --- /dev/null +++ b/files/.htaccess @@ -0,0 +1,4 @@ + + Order Allow,Deny + Deny from All + \ No newline at end of file diff --git a/files/index.htm b/files/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/files/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/avatars/gallery/Civils_F/Civil_F_1.png b/images/avatars/gallery/Civils_F/Civil_F_1.png new file mode 100644 index 0000000..f17f125 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_1.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_10.png b/images/avatars/gallery/Civils_F/Civil_F_10.png new file mode 100644 index 0000000..d90af3b Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_10.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_11.png b/images/avatars/gallery/Civils_F/Civil_F_11.png new file mode 100644 index 0000000..70363de Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_11.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_12.png b/images/avatars/gallery/Civils_F/Civil_F_12.png new file mode 100644 index 0000000..c525dfa Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_12.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_13.png b/images/avatars/gallery/Civils_F/Civil_F_13.png new file mode 100644 index 0000000..f9dda67 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_13.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_14.png b/images/avatars/gallery/Civils_F/Civil_F_14.png new file mode 100644 index 0000000..92df834 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_14.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_15.png b/images/avatars/gallery/Civils_F/Civil_F_15.png new file mode 100644 index 0000000..5f3bc15 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_15.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_16.png b/images/avatars/gallery/Civils_F/Civil_F_16.png new file mode 100644 index 0000000..ad82d03 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_16.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_17.png b/images/avatars/gallery/Civils_F/Civil_F_17.png new file mode 100644 index 0000000..e534f6f Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_17.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_18.png b/images/avatars/gallery/Civils_F/Civil_F_18.png new file mode 100644 index 0000000..950204b Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_18.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_19.png b/images/avatars/gallery/Civils_F/Civil_F_19.png new file mode 100644 index 0000000..6cacaad Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_19.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_2.png b/images/avatars/gallery/Civils_F/Civil_F_2.png new file mode 100644 index 0000000..acfeec6 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_2.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_20.png b/images/avatars/gallery/Civils_F/Civil_F_20.png new file mode 100644 index 0000000..ea190da Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_20.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_21.png b/images/avatars/gallery/Civils_F/Civil_F_21.png new file mode 100644 index 0000000..c9706ff Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_21.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_22.png b/images/avatars/gallery/Civils_F/Civil_F_22.png new file mode 100644 index 0000000..4cfab6e Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_22.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_23.png b/images/avatars/gallery/Civils_F/Civil_F_23.png new file mode 100644 index 0000000..866d602 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_23.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_24.png b/images/avatars/gallery/Civils_F/Civil_F_24.png new file mode 100644 index 0000000..030c2af Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_24.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_25.png b/images/avatars/gallery/Civils_F/Civil_F_25.png new file mode 100644 index 0000000..133b3b7 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_25.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_26.png b/images/avatars/gallery/Civils_F/Civil_F_26.png new file mode 100644 index 0000000..1276922 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_26.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_27.png b/images/avatars/gallery/Civils_F/Civil_F_27.png new file mode 100644 index 0000000..389537a Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_27.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_28.png b/images/avatars/gallery/Civils_F/Civil_F_28.png new file mode 100644 index 0000000..bdc52c7 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_28.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_29.png b/images/avatars/gallery/Civils_F/Civil_F_29.png new file mode 100644 index 0000000..fb483b1 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_29.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_3.png b/images/avatars/gallery/Civils_F/Civil_F_3.png new file mode 100644 index 0000000..a6fa7ad Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_3.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_30.png b/images/avatars/gallery/Civils_F/Civil_F_30.png new file mode 100644 index 0000000..5337b64 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_30.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_4.png b/images/avatars/gallery/Civils_F/Civil_F_4.png new file mode 100644 index 0000000..e0fd088 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_4.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_5.png b/images/avatars/gallery/Civils_F/Civil_F_5.png new file mode 100644 index 0000000..a277367 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_5.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_6.png b/images/avatars/gallery/Civils_F/Civil_F_6.png new file mode 100644 index 0000000..3c6016b Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_6.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_7.png b/images/avatars/gallery/Civils_F/Civil_F_7.png new file mode 100644 index 0000000..f12b85c Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_7.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_8.png b/images/avatars/gallery/Civils_F/Civil_F_8.png new file mode 100644 index 0000000..b6a76d3 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_8.png differ diff --git a/images/avatars/gallery/Civils_F/Civil_F_9.png b/images/avatars/gallery/Civils_F/Civil_F_9.png new file mode 100644 index 0000000..0f2a6c1 Binary files /dev/null and b/images/avatars/gallery/Civils_F/Civil_F_9.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_1.png b/images/avatars/gallery/Civils_H/Civil_H_1.png new file mode 100644 index 0000000..530ea9d Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_1.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_10.png b/images/avatars/gallery/Civils_H/Civil_H_10.png new file mode 100644 index 0000000..be2d896 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_10.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_11.png b/images/avatars/gallery/Civils_H/Civil_H_11.png new file mode 100644 index 0000000..ce324e3 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_11.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_12.png b/images/avatars/gallery/Civils_H/Civil_H_12.png new file mode 100644 index 0000000..7aca8bf Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_12.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_13.png b/images/avatars/gallery/Civils_H/Civil_H_13.png new file mode 100644 index 0000000..85b30df Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_13.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_14.png b/images/avatars/gallery/Civils_H/Civil_H_14.png new file mode 100644 index 0000000..6c4a669 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_14.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_15.png b/images/avatars/gallery/Civils_H/Civil_H_15.png new file mode 100644 index 0000000..3a6f922 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_15.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_16.png b/images/avatars/gallery/Civils_H/Civil_H_16.png new file mode 100644 index 0000000..f6b17b1 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_16.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_17.png b/images/avatars/gallery/Civils_H/Civil_H_17.png new file mode 100644 index 0000000..5e9dcce Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_17.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_18.png b/images/avatars/gallery/Civils_H/Civil_H_18.png new file mode 100644 index 0000000..d79d375 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_18.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_19.png b/images/avatars/gallery/Civils_H/Civil_H_19.png new file mode 100644 index 0000000..d7c03a0 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_19.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_2.png b/images/avatars/gallery/Civils_H/Civil_H_2.png new file mode 100644 index 0000000..fcdc734 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_2.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_20.png b/images/avatars/gallery/Civils_H/Civil_H_20.png new file mode 100644 index 0000000..b46d55b Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_20.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_21.png b/images/avatars/gallery/Civils_H/Civil_H_21.png new file mode 100644 index 0000000..c1fc92b Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_21.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_22.png b/images/avatars/gallery/Civils_H/Civil_H_22.png new file mode 100644 index 0000000..857fad5 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_22.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_23.png b/images/avatars/gallery/Civils_H/Civil_H_23.png new file mode 100644 index 0000000..18f1fae Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_23.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_24.png b/images/avatars/gallery/Civils_H/Civil_H_24.png new file mode 100644 index 0000000..f8325c6 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_24.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_25.png b/images/avatars/gallery/Civils_H/Civil_H_25.png new file mode 100644 index 0000000..eedb63d Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_25.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_26.png b/images/avatars/gallery/Civils_H/Civil_H_26.png new file mode 100644 index 0000000..ca97ef3 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_26.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_27.png b/images/avatars/gallery/Civils_H/Civil_H_27.png new file mode 100644 index 0000000..a736fea Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_27.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_28.png b/images/avatars/gallery/Civils_H/Civil_H_28.png new file mode 100644 index 0000000..ac986d3 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_28.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_29.png b/images/avatars/gallery/Civils_H/Civil_H_29.png new file mode 100644 index 0000000..2e8a659 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_29.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_3.png b/images/avatars/gallery/Civils_H/Civil_H_3.png new file mode 100644 index 0000000..c365d80 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_3.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_30.png b/images/avatars/gallery/Civils_H/Civil_H_30.png new file mode 100644 index 0000000..a2f2d7d Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_30.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_31.png b/images/avatars/gallery/Civils_H/Civil_H_31.png new file mode 100644 index 0000000..661bf9b Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_31.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_32.png b/images/avatars/gallery/Civils_H/Civil_H_32.png new file mode 100644 index 0000000..dda9cae Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_32.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_33.png b/images/avatars/gallery/Civils_H/Civil_H_33.png new file mode 100644 index 0000000..dda9cae Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_33.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_34.png b/images/avatars/gallery/Civils_H/Civil_H_34.png new file mode 100644 index 0000000..26b3f87 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_34.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_35.png b/images/avatars/gallery/Civils_H/Civil_H_35.png new file mode 100644 index 0000000..ca93153 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_35.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_36.png b/images/avatars/gallery/Civils_H/Civil_H_36.png new file mode 100644 index 0000000..1430e5a Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_36.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_37.png b/images/avatars/gallery/Civils_H/Civil_H_37.png new file mode 100644 index 0000000..3352d32 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_37.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_38.png b/images/avatars/gallery/Civils_H/Civil_H_38.png new file mode 100644 index 0000000..ffb804c Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_38.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_39.png b/images/avatars/gallery/Civils_H/Civil_H_39.png new file mode 100644 index 0000000..abe561e Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_39.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_4.png b/images/avatars/gallery/Civils_H/Civil_H_4.png new file mode 100644 index 0000000..b636dde Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_4.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_40.png b/images/avatars/gallery/Civils_H/Civil_H_40.png new file mode 100644 index 0000000..fdd08ea Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_40.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_41.png b/images/avatars/gallery/Civils_H/Civil_H_41.png new file mode 100644 index 0000000..da66c0c Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_41.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_42.png b/images/avatars/gallery/Civils_H/Civil_H_42.png new file mode 100644 index 0000000..d3d407f Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_42.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_43.png b/images/avatars/gallery/Civils_H/Civil_H_43.png new file mode 100644 index 0000000..c81eece Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_43.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_44.png b/images/avatars/gallery/Civils_H/Civil_H_44.png new file mode 100644 index 0000000..6fb25fe Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_44.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_45.png b/images/avatars/gallery/Civils_H/Civil_H_45.png new file mode 100644 index 0000000..354674f Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_45.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_46.png b/images/avatars/gallery/Civils_H/Civil_H_46.png new file mode 100644 index 0000000..7164c9b Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_46.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_47.png b/images/avatars/gallery/Civils_H/Civil_H_47.png new file mode 100644 index 0000000..4ed7c92 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_47.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_48.png b/images/avatars/gallery/Civils_H/Civil_H_48.png new file mode 100644 index 0000000..14615e5 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_48.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_49.png b/images/avatars/gallery/Civils_H/Civil_H_49.png new file mode 100644 index 0000000..720df0b Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_49.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_5.png b/images/avatars/gallery/Civils_H/Civil_H_5.png new file mode 100644 index 0000000..0130105 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_5.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_50.png b/images/avatars/gallery/Civils_H/Civil_H_50.png new file mode 100644 index 0000000..123354f Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_50.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_51.png b/images/avatars/gallery/Civils_H/Civil_H_51.png new file mode 100644 index 0000000..029f30a Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_51.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_52.png b/images/avatars/gallery/Civils_H/Civil_H_52.png new file mode 100644 index 0000000..e98b6a9 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_52.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_53.png b/images/avatars/gallery/Civils_H/Civil_H_53.png new file mode 100644 index 0000000..bd08bfd Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_53.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_54.png b/images/avatars/gallery/Civils_H/Civil_H_54.png new file mode 100644 index 0000000..40d5c6a Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_54.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_55.png b/images/avatars/gallery/Civils_H/Civil_H_55.png new file mode 100644 index 0000000..c87948d Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_55.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_56.png b/images/avatars/gallery/Civils_H/Civil_H_56.png new file mode 100644 index 0000000..21c6183 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_56.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_57.png b/images/avatars/gallery/Civils_H/Civil_H_57.png new file mode 100644 index 0000000..2172de1 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_57.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_58.png b/images/avatars/gallery/Civils_H/Civil_H_58.png new file mode 100644 index 0000000..cde1758 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_58.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_59.png b/images/avatars/gallery/Civils_H/Civil_H_59.png new file mode 100644 index 0000000..4379604 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_59.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_6.png b/images/avatars/gallery/Civils_H/Civil_H_6.png new file mode 100644 index 0000000..bd0d1eb Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_6.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_60.png b/images/avatars/gallery/Civils_H/Civil_H_60.png new file mode 100644 index 0000000..73f0202 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_60.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_61.png b/images/avatars/gallery/Civils_H/Civil_H_61.png new file mode 100644 index 0000000..a92f14e Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_61.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_62.png b/images/avatars/gallery/Civils_H/Civil_H_62.png new file mode 100644 index 0000000..9dfa7fa Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_62.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_63.png b/images/avatars/gallery/Civils_H/Civil_H_63.png new file mode 100644 index 0000000..b74ae5c Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_63.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_64.png b/images/avatars/gallery/Civils_H/Civil_H_64.png new file mode 100644 index 0000000..ad4d91f Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_64.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_65.png b/images/avatars/gallery/Civils_H/Civil_H_65.png new file mode 100644 index 0000000..cde1758 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_65.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_66.png b/images/avatars/gallery/Civils_H/Civil_H_66.png new file mode 100644 index 0000000..9dfaf11 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_66.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_67.png b/images/avatars/gallery/Civils_H/Civil_H_67.png new file mode 100644 index 0000000..f904356 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_67.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_68.png b/images/avatars/gallery/Civils_H/Civil_H_68.png new file mode 100644 index 0000000..c802234 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_68.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_7.png b/images/avatars/gallery/Civils_H/Civil_H_7.png new file mode 100644 index 0000000..44f7490 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_7.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_8.png b/images/avatars/gallery/Civils_H/Civil_H_8.png new file mode 100644 index 0000000..9ec74a6 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_8.png differ diff --git a/images/avatars/gallery/Civils_H/Civil_H_9.png b/images/avatars/gallery/Civils_H/Civil_H_9.png new file mode 100644 index 0000000..4a0a4d0 Binary files /dev/null and b/images/avatars/gallery/Civils_H/Civil_H_9.png differ diff --git a/images/avatars/gallery/Flics/Flic_1.png b/images/avatars/gallery/Flics/Flic_1.png new file mode 100644 index 0000000..fceb15d Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_1.png differ diff --git a/images/avatars/gallery/Flics/Flic_10.png b/images/avatars/gallery/Flics/Flic_10.png new file mode 100644 index 0000000..87daed0 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_10.png differ diff --git a/images/avatars/gallery/Flics/Flic_100.png b/images/avatars/gallery/Flics/Flic_100.png new file mode 100644 index 0000000..650c32d Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_100.png differ diff --git a/images/avatars/gallery/Flics/Flic_101.png b/images/avatars/gallery/Flics/Flic_101.png new file mode 100644 index 0000000..9c76fd3 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_101.png differ diff --git a/images/avatars/gallery/Flics/Flic_102.png b/images/avatars/gallery/Flics/Flic_102.png new file mode 100644 index 0000000..0b46809 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_102.png differ diff --git a/images/avatars/gallery/Flics/Flic_103.png b/images/avatars/gallery/Flics/Flic_103.png new file mode 100644 index 0000000..1df04c6 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_103.png differ diff --git a/images/avatars/gallery/Flics/Flic_104.png b/images/avatars/gallery/Flics/Flic_104.png new file mode 100644 index 0000000..ee47e53 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_104.png differ diff --git a/images/avatars/gallery/Flics/Flic_105.png b/images/avatars/gallery/Flics/Flic_105.png new file mode 100644 index 0000000..b3b6538 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_105.png differ diff --git a/images/avatars/gallery/Flics/Flic_106.png b/images/avatars/gallery/Flics/Flic_106.png new file mode 100644 index 0000000..6593d2a Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_106.png differ diff --git a/images/avatars/gallery/Flics/Flic_107.png b/images/avatars/gallery/Flics/Flic_107.png new file mode 100644 index 0000000..f14c07a Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_107.png differ diff --git a/images/avatars/gallery/Flics/Flic_108.png b/images/avatars/gallery/Flics/Flic_108.png new file mode 100644 index 0000000..9ca09c2 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_108.png differ diff --git a/images/avatars/gallery/Flics/Flic_109.png b/images/avatars/gallery/Flics/Flic_109.png new file mode 100644 index 0000000..08f74af Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_109.png differ diff --git a/images/avatars/gallery/Flics/Flic_110.png b/images/avatars/gallery/Flics/Flic_110.png new file mode 100644 index 0000000..fcab689 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_110.png differ diff --git a/images/avatars/gallery/Flics/Flic_13.png b/images/avatars/gallery/Flics/Flic_13.png new file mode 100644 index 0000000..2f6213a Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_13.png differ diff --git a/images/avatars/gallery/Flics/Flic_14.png b/images/avatars/gallery/Flics/Flic_14.png new file mode 100644 index 0000000..9879aab Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_14.png differ diff --git a/images/avatars/gallery/Flics/Flic_15.png b/images/avatars/gallery/Flics/Flic_15.png new file mode 100644 index 0000000..1c4bf4b Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_15.png differ diff --git a/images/avatars/gallery/Flics/Flic_16.png b/images/avatars/gallery/Flics/Flic_16.png new file mode 100644 index 0000000..6490a3e Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_16.png differ diff --git a/images/avatars/gallery/Flics/Flic_19.png b/images/avatars/gallery/Flics/Flic_19.png new file mode 100644 index 0000000..ee62835 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_19.png differ diff --git a/images/avatars/gallery/Flics/Flic_2.png b/images/avatars/gallery/Flics/Flic_2.png new file mode 100644 index 0000000..3c11b70 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_2.png differ diff --git a/images/avatars/gallery/Flics/Flic_20.png b/images/avatars/gallery/Flics/Flic_20.png new file mode 100644 index 0000000..d566640 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_20.png differ diff --git a/images/avatars/gallery/Flics/Flic_21.png b/images/avatars/gallery/Flics/Flic_21.png new file mode 100644 index 0000000..261356c Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_21.png differ diff --git a/images/avatars/gallery/Flics/Flic_22.png b/images/avatars/gallery/Flics/Flic_22.png new file mode 100644 index 0000000..82be618 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_22.png differ diff --git a/images/avatars/gallery/Flics/Flic_25.png b/images/avatars/gallery/Flics/Flic_25.png new file mode 100644 index 0000000..8301890 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_25.png differ diff --git a/images/avatars/gallery/Flics/Flic_27.png b/images/avatars/gallery/Flics/Flic_27.png new file mode 100644 index 0000000..1b6694c Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_27.png differ diff --git a/images/avatars/gallery/Flics/Flic_28.png b/images/avatars/gallery/Flics/Flic_28.png new file mode 100644 index 0000000..0f591fc Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_28.png differ diff --git a/images/avatars/gallery/Flics/Flic_30.png b/images/avatars/gallery/Flics/Flic_30.png new file mode 100644 index 0000000..ffc3aa2 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_30.png differ diff --git a/images/avatars/gallery/Flics/Flic_31.png b/images/avatars/gallery/Flics/Flic_31.png new file mode 100644 index 0000000..9f1c51e Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_31.png differ diff --git a/images/avatars/gallery/Flics/Flic_32.png b/images/avatars/gallery/Flics/Flic_32.png new file mode 100644 index 0000000..dec4f91 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_32.png differ diff --git a/images/avatars/gallery/Flics/Flic_33.png b/images/avatars/gallery/Flics/Flic_33.png new file mode 100644 index 0000000..6859ca7 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_33.png differ diff --git a/images/avatars/gallery/Flics/Flic_34.png b/images/avatars/gallery/Flics/Flic_34.png new file mode 100644 index 0000000..30b6459 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_34.png differ diff --git a/images/avatars/gallery/Flics/Flic_35.png b/images/avatars/gallery/Flics/Flic_35.png new file mode 100644 index 0000000..b800d2f Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_35.png differ diff --git a/images/avatars/gallery/Flics/Flic_36.png b/images/avatars/gallery/Flics/Flic_36.png new file mode 100644 index 0000000..dcbfdf7 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_36.png differ diff --git a/images/avatars/gallery/Flics/Flic_38.png b/images/avatars/gallery/Flics/Flic_38.png new file mode 100644 index 0000000..38b1dce Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_38.png differ diff --git a/images/avatars/gallery/Flics/Flic_39.png b/images/avatars/gallery/Flics/Flic_39.png new file mode 100644 index 0000000..d8c488a Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_39.png differ diff --git a/images/avatars/gallery/Flics/Flic_4.png b/images/avatars/gallery/Flics/Flic_4.png new file mode 100644 index 0000000..6741716 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_4.png differ diff --git a/images/avatars/gallery/Flics/Flic_40.png b/images/avatars/gallery/Flics/Flic_40.png new file mode 100644 index 0000000..3412bdf Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_40.png differ diff --git a/images/avatars/gallery/Flics/Flic_41.png b/images/avatars/gallery/Flics/Flic_41.png new file mode 100644 index 0000000..77b7364 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_41.png differ diff --git a/images/avatars/gallery/Flics/Flic_43.png b/images/avatars/gallery/Flics/Flic_43.png new file mode 100644 index 0000000..d3064ee Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_43.png differ diff --git a/images/avatars/gallery/Flics/Flic_44.png b/images/avatars/gallery/Flics/Flic_44.png new file mode 100644 index 0000000..2d461d4 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_44.png differ diff --git a/images/avatars/gallery/Flics/Flic_45.png b/images/avatars/gallery/Flics/Flic_45.png new file mode 100644 index 0000000..eac009c Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_45.png differ diff --git a/images/avatars/gallery/Flics/Flic_46.png b/images/avatars/gallery/Flics/Flic_46.png new file mode 100644 index 0000000..e039b5c Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_46.png differ diff --git a/images/avatars/gallery/Flics/Flic_47.png b/images/avatars/gallery/Flics/Flic_47.png new file mode 100644 index 0000000..9497726 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_47.png differ diff --git a/images/avatars/gallery/Flics/Flic_48.png b/images/avatars/gallery/Flics/Flic_48.png new file mode 100644 index 0000000..ce7e0f9 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_48.png differ diff --git a/images/avatars/gallery/Flics/Flic_49.png b/images/avatars/gallery/Flics/Flic_49.png new file mode 100644 index 0000000..ff7f4f7 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_49.png differ diff --git a/images/avatars/gallery/Flics/Flic_5.png b/images/avatars/gallery/Flics/Flic_5.png new file mode 100644 index 0000000..ae5cad5 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_5.png differ diff --git a/images/avatars/gallery/Flics/Flic_50.png b/images/avatars/gallery/Flics/Flic_50.png new file mode 100644 index 0000000..45bc993 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_50.png differ diff --git a/images/avatars/gallery/Flics/Flic_51.png b/images/avatars/gallery/Flics/Flic_51.png new file mode 100644 index 0000000..7a625ce Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_51.png differ diff --git a/images/avatars/gallery/Flics/Flic_52.png b/images/avatars/gallery/Flics/Flic_52.png new file mode 100644 index 0000000..1a427af Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_52.png differ diff --git a/images/avatars/gallery/Flics/Flic_53.png b/images/avatars/gallery/Flics/Flic_53.png new file mode 100644 index 0000000..901a1c1 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_53.png differ diff --git a/images/avatars/gallery/Flics/Flic_54.png b/images/avatars/gallery/Flics/Flic_54.png new file mode 100644 index 0000000..2b7ff30 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_54.png differ diff --git a/images/avatars/gallery/Flics/Flic_55.png b/images/avatars/gallery/Flics/Flic_55.png new file mode 100644 index 0000000..ea47c9b Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_55.png differ diff --git a/images/avatars/gallery/Flics/Flic_56.png b/images/avatars/gallery/Flics/Flic_56.png new file mode 100644 index 0000000..2e7abe7 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_56.png differ diff --git a/images/avatars/gallery/Flics/Flic_57.png b/images/avatars/gallery/Flics/Flic_57.png new file mode 100644 index 0000000..4ade0f7 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_57.png differ diff --git a/images/avatars/gallery/Flics/Flic_58.png b/images/avatars/gallery/Flics/Flic_58.png new file mode 100644 index 0000000..14a2b66 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_58.png differ diff --git a/images/avatars/gallery/Flics/Flic_59.png b/images/avatars/gallery/Flics/Flic_59.png new file mode 100644 index 0000000..9afb293 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_59.png differ diff --git a/images/avatars/gallery/Flics/Flic_6.png b/images/avatars/gallery/Flics/Flic_6.png new file mode 100644 index 0000000..ea03575 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_6.png differ diff --git a/images/avatars/gallery/Flics/Flic_60.png b/images/avatars/gallery/Flics/Flic_60.png new file mode 100644 index 0000000..46f7c34 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_60.png differ diff --git a/images/avatars/gallery/Flics/Flic_61.png b/images/avatars/gallery/Flics/Flic_61.png new file mode 100644 index 0000000..af44776 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_61.png differ diff --git a/images/avatars/gallery/Flics/Flic_62.png b/images/avatars/gallery/Flics/Flic_62.png new file mode 100644 index 0000000..354fac5 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_62.png differ diff --git a/images/avatars/gallery/Flics/Flic_64.png b/images/avatars/gallery/Flics/Flic_64.png new file mode 100644 index 0000000..60b8c95 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_64.png differ diff --git a/images/avatars/gallery/Flics/Flic_65.png b/images/avatars/gallery/Flics/Flic_65.png new file mode 100644 index 0000000..46d540d Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_65.png differ diff --git a/images/avatars/gallery/Flics/Flic_66.png b/images/avatars/gallery/Flics/Flic_66.png new file mode 100644 index 0000000..34660be Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_66.png differ diff --git a/images/avatars/gallery/Flics/Flic_67.png b/images/avatars/gallery/Flics/Flic_67.png new file mode 100644 index 0000000..91b2fbe Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_67.png differ diff --git a/images/avatars/gallery/Flics/Flic_68.png b/images/avatars/gallery/Flics/Flic_68.png new file mode 100644 index 0000000..039150d Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_68.png differ diff --git a/images/avatars/gallery/Flics/Flic_69.png b/images/avatars/gallery/Flics/Flic_69.png new file mode 100644 index 0000000..d441301 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_69.png differ diff --git a/images/avatars/gallery/Flics/Flic_7.png b/images/avatars/gallery/Flics/Flic_7.png new file mode 100644 index 0000000..8956181 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_7.png differ diff --git a/images/avatars/gallery/Flics/Flic_70.png b/images/avatars/gallery/Flics/Flic_70.png new file mode 100644 index 0000000..955197c Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_70.png differ diff --git a/images/avatars/gallery/Flics/Flic_71.png b/images/avatars/gallery/Flics/Flic_71.png new file mode 100644 index 0000000..9bbbe94 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_71.png differ diff --git a/images/avatars/gallery/Flics/Flic_72.png b/images/avatars/gallery/Flics/Flic_72.png new file mode 100644 index 0000000..ceb8b71 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_72.png differ diff --git a/images/avatars/gallery/Flics/Flic_73.png b/images/avatars/gallery/Flics/Flic_73.png new file mode 100644 index 0000000..bb7a4e1 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_73.png differ diff --git a/images/avatars/gallery/Flics/Flic_74.png b/images/avatars/gallery/Flics/Flic_74.png new file mode 100644 index 0000000..3dc0709 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_74.png differ diff --git a/images/avatars/gallery/Flics/Flic_75.png b/images/avatars/gallery/Flics/Flic_75.png new file mode 100644 index 0000000..64b1619 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_75.png differ diff --git a/images/avatars/gallery/Flics/Flic_76.png b/images/avatars/gallery/Flics/Flic_76.png new file mode 100644 index 0000000..4287dde Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_76.png differ diff --git a/images/avatars/gallery/Flics/Flic_77.png b/images/avatars/gallery/Flics/Flic_77.png new file mode 100644 index 0000000..0003b96 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_77.png differ diff --git a/images/avatars/gallery/Flics/Flic_78.png b/images/avatars/gallery/Flics/Flic_78.png new file mode 100644 index 0000000..ff0941d Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_78.png differ diff --git a/images/avatars/gallery/Flics/Flic_79.png b/images/avatars/gallery/Flics/Flic_79.png new file mode 100644 index 0000000..6351c28 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_79.png differ diff --git a/images/avatars/gallery/Flics/Flic_8.png b/images/avatars/gallery/Flics/Flic_8.png new file mode 100644 index 0000000..d20d2ff Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_8.png differ diff --git a/images/avatars/gallery/Flics/Flic_80.png b/images/avatars/gallery/Flics/Flic_80.png new file mode 100644 index 0000000..72b1984 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_80.png differ diff --git a/images/avatars/gallery/Flics/Flic_81.png b/images/avatars/gallery/Flics/Flic_81.png new file mode 100644 index 0000000..997f3e5 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_81.png differ diff --git a/images/avatars/gallery/Flics/Flic_82.png b/images/avatars/gallery/Flics/Flic_82.png new file mode 100644 index 0000000..78fad6d Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_82.png differ diff --git a/images/avatars/gallery/Flics/Flic_83.png b/images/avatars/gallery/Flics/Flic_83.png new file mode 100644 index 0000000..abb9229 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_83.png differ diff --git a/images/avatars/gallery/Flics/Flic_84.png b/images/avatars/gallery/Flics/Flic_84.png new file mode 100644 index 0000000..0a3fc7b Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_84.png differ diff --git a/images/avatars/gallery/Flics/Flic_85.png b/images/avatars/gallery/Flics/Flic_85.png new file mode 100644 index 0000000..c8c52f8 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_85.png differ diff --git a/images/avatars/gallery/Flics/Flic_86.png b/images/avatars/gallery/Flics/Flic_86.png new file mode 100644 index 0000000..8093039 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_86.png differ diff --git a/images/avatars/gallery/Flics/Flic_87.png b/images/avatars/gallery/Flics/Flic_87.png new file mode 100644 index 0000000..0959643 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_87.png differ diff --git a/images/avatars/gallery/Flics/Flic_88.png b/images/avatars/gallery/Flics/Flic_88.png new file mode 100644 index 0000000..e0b62e0 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_88.png differ diff --git a/images/avatars/gallery/Flics/Flic_89.png b/images/avatars/gallery/Flics/Flic_89.png new file mode 100644 index 0000000..ebd3c10 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_89.png differ diff --git a/images/avatars/gallery/Flics/Flic_9.png b/images/avatars/gallery/Flics/Flic_9.png new file mode 100644 index 0000000..23b2ec6 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_9.png differ diff --git a/images/avatars/gallery/Flics/Flic_90.png b/images/avatars/gallery/Flics/Flic_90.png new file mode 100644 index 0000000..82d1e73 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_90.png differ diff --git a/images/avatars/gallery/Flics/Flic_91.png b/images/avatars/gallery/Flics/Flic_91.png new file mode 100644 index 0000000..2d4d83d Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_91.png differ diff --git a/images/avatars/gallery/Flics/Flic_92.png b/images/avatars/gallery/Flics/Flic_92.png new file mode 100644 index 0000000..4d2c19c Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_92.png differ diff --git a/images/avatars/gallery/Flics/Flic_93.png b/images/avatars/gallery/Flics/Flic_93.png new file mode 100644 index 0000000..0f1fda2 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_93.png differ diff --git a/images/avatars/gallery/Flics/Flic_94.png b/images/avatars/gallery/Flics/Flic_94.png new file mode 100644 index 0000000..8268452 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_94.png differ diff --git a/images/avatars/gallery/Flics/Flic_95.png b/images/avatars/gallery/Flics/Flic_95.png new file mode 100644 index 0000000..f6efce4 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_95.png differ diff --git a/images/avatars/gallery/Flics/Flic_96.png b/images/avatars/gallery/Flics/Flic_96.png new file mode 100644 index 0000000..e156470 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_96.png differ diff --git a/images/avatars/gallery/Flics/Flic_97.png b/images/avatars/gallery/Flics/Flic_97.png new file mode 100644 index 0000000..944fbe8 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_97.png differ diff --git a/images/avatars/gallery/Flics/Flic_98.png b/images/avatars/gallery/Flics/Flic_98.png new file mode 100644 index 0000000..c3f17a0 Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_98.png differ diff --git a/images/avatars/gallery/Flics/Flic_99.png b/images/avatars/gallery/Flics/Flic_99.png new file mode 100644 index 0000000..39f9a7e Binary files /dev/null and b/images/avatars/gallery/Flics/Flic_99.png differ diff --git a/images/avatars/gallery/index.htm b/images/avatars/gallery/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/images/avatars/gallery/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/avatars/index.htm b/images/avatars/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/images/avatars/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/avatars/upload/.htaccess b/images/avatars/upload/.htaccess new file mode 100644 index 0000000..aa5afc1 --- /dev/null +++ b/images/avatars/upload/.htaccess @@ -0,0 +1,4 @@ + + Order Allow,Deny + Deny from All + \ No newline at end of file diff --git a/images/avatars/upload/index.htm b/images/avatars/upload/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/images/avatars/upload/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/icons/index.htm b/images/icons/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/images/icons/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/icons/misc/fire.gif b/images/icons/misc/fire.gif new file mode 100644 index 0000000..4adba20 Binary files /dev/null and b/images/icons/misc/fire.gif differ diff --git a/images/icons/misc/heart.gif b/images/icons/misc/heart.gif new file mode 100644 index 0000000..c391c37 Binary files /dev/null and b/images/icons/misc/heart.gif differ diff --git a/images/icons/misc/index.htm b/images/icons/misc/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/images/icons/misc/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/icons/misc/radioactive.gif b/images/icons/misc/radioactive.gif new file mode 100644 index 0000000..6d7527c Binary files /dev/null and b/images/icons/misc/radioactive.gif differ diff --git a/images/icons/misc/star.gif b/images/icons/misc/star.gif new file mode 100644 index 0000000..15a96ef Binary files /dev/null and b/images/icons/misc/star.gif differ diff --git a/images/icons/misc/thinking.gif b/images/icons/misc/thinking.gif new file mode 100644 index 0000000..23f2267 Binary files /dev/null and b/images/icons/misc/thinking.gif differ diff --git a/images/icons/smile/alert.gif b/images/icons/smile/alert.gif new file mode 100644 index 0000000..b0e1fe1 Binary files /dev/null and b/images/icons/smile/alert.gif differ diff --git a/images/icons/smile/index.htm b/images/icons/smile/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/images/icons/smile/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/icons/smile/info.gif b/images/icons/smile/info.gif new file mode 100644 index 0000000..2b28d8c Binary files /dev/null and b/images/icons/smile/info.gif differ diff --git a/images/icons/smile/mrgreen.gif b/images/icons/smile/mrgreen.gif new file mode 100644 index 0000000..f6da47a Binary files /dev/null and b/images/icons/smile/mrgreen.gif differ diff --git a/images/icons/smile/question.gif b/images/icons/smile/question.gif new file mode 100644 index 0000000..2c82028 Binary files /dev/null and b/images/icons/smile/question.gif differ diff --git a/images/icons/smile/redface.gif b/images/icons/smile/redface.gif new file mode 100644 index 0000000..85c81f0 Binary files /dev/null and b/images/icons/smile/redface.gif differ diff --git a/images/index.htm b/images/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/images/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/ranks/index.htm b/images/ranks/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/images/ranks/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/images/smilies/icon_arrow.gif b/images/smilies/icon_arrow.gif new file mode 100644 index 0000000..c0f9117 Binary files /dev/null and b/images/smilies/icon_arrow.gif differ diff --git a/images/smilies/icon_cool.gif b/images/smilies/icon_cool.gif new file mode 100644 index 0000000..6dd1503 Binary files /dev/null and b/images/smilies/icon_cool.gif differ diff --git a/images/smilies/icon_cry.gif b/images/smilies/icon_cry.gif new file mode 100644 index 0000000..21a5a3c Binary files /dev/null and b/images/smilies/icon_cry.gif differ diff --git a/images/smilies/icon_e_biggrin.gif b/images/smilies/icon_e_biggrin.gif new file mode 100644 index 0000000..08be847 Binary files /dev/null and b/images/smilies/icon_e_biggrin.gif differ diff --git a/images/smilies/icon_e_confused.gif b/images/smilies/icon_e_confused.gif new file mode 100644 index 0000000..be5b583 Binary files /dev/null and b/images/smilies/icon_e_confused.gif differ diff --git a/images/smilies/icon_e_geek.gif b/images/smilies/icon_e_geek.gif new file mode 100644 index 0000000..535bc9f Binary files /dev/null and b/images/smilies/icon_e_geek.gif differ diff --git a/images/smilies/icon_e_sad.gif b/images/smilies/icon_e_sad.gif new file mode 100644 index 0000000..7cd3016 Binary files /dev/null and b/images/smilies/icon_e_sad.gif differ diff --git a/images/smilies/icon_e_smile.gif b/images/smilies/icon_e_smile.gif new file mode 100644 index 0000000..d1ec74c Binary files /dev/null and b/images/smilies/icon_e_smile.gif differ diff --git a/images/smilies/icon_e_surprised.gif b/images/smilies/icon_e_surprised.gif new file mode 100644 index 0000000..1be6041 Binary files /dev/null and b/images/smilies/icon_e_surprised.gif differ diff --git a/images/smilies/icon_e_ugeek.gif b/images/smilies/icon_e_ugeek.gif new file mode 100644 index 0000000..0d3c179 Binary files /dev/null and b/images/smilies/icon_e_ugeek.gif differ diff --git a/images/smilies/icon_e_wink.gif b/images/smilies/icon_e_wink.gif new file mode 100644 index 0000000..fb1c140 Binary files /dev/null and b/images/smilies/icon_e_wink.gif differ diff --git a/images/smilies/icon_eek.gif b/images/smilies/icon_eek.gif new file mode 100644 index 0000000..cbe9b7b Binary files /dev/null and b/images/smilies/icon_eek.gif differ diff --git a/images/smilies/icon_evil.gif b/images/smilies/icon_evil.gif new file mode 100644 index 0000000..98e6535 Binary files /dev/null and b/images/smilies/icon_evil.gif differ diff --git a/images/smilies/icon_exclaim.gif b/images/smilies/icon_exclaim.gif new file mode 100644 index 0000000..2b4a3df Binary files /dev/null and b/images/smilies/icon_exclaim.gif differ diff --git a/images/smilies/icon_idea.gif b/images/smilies/icon_idea.gif new file mode 100644 index 0000000..e51d542 Binary files /dev/null and b/images/smilies/icon_idea.gif differ diff --git a/images/smilies/icon_lol.gif b/images/smilies/icon_lol.gif new file mode 100644 index 0000000..3042b00 Binary files /dev/null and b/images/smilies/icon_lol.gif differ diff --git a/images/smilies/icon_mad.gif b/images/smilies/icon_mad.gif new file mode 100644 index 0000000..9942166 Binary files /dev/null and b/images/smilies/icon_mad.gif differ diff --git a/images/smilies/icon_mrgreen.gif b/images/smilies/icon_mrgreen.gif new file mode 100644 index 0000000..dcb44bb Binary files /dev/null and b/images/smilies/icon_mrgreen.gif differ diff --git a/images/smilies/icon_neutral.gif b/images/smilies/icon_neutral.gif new file mode 100644 index 0000000..41c3e14 Binary files /dev/null and b/images/smilies/icon_neutral.gif differ diff --git a/images/smilies/icon_question.gif b/images/smilies/icon_question.gif new file mode 100644 index 0000000..13936f7 Binary files /dev/null and b/images/smilies/icon_question.gif differ diff --git a/images/smilies/icon_razz.gif b/images/smilies/icon_razz.gif new file mode 100644 index 0000000..a262743 Binary files /dev/null and b/images/smilies/icon_razz.gif differ diff --git a/images/smilies/icon_redface.gif b/images/smilies/icon_redface.gif new file mode 100644 index 0000000..d23a139 Binary files /dev/null and b/images/smilies/icon_redface.gif differ diff --git a/images/smilies/icon_rolleyes.gif b/images/smilies/icon_rolleyes.gif new file mode 100644 index 0000000..0707821 Binary files /dev/null and b/images/smilies/icon_rolleyes.gif differ diff --git a/images/smilies/icon_twisted.gif b/images/smilies/icon_twisted.gif new file mode 100644 index 0000000..a555dd0 Binary files /dev/null and b/images/smilies/icon_twisted.gif differ diff --git a/images/spacer.gif b/images/spacer.gif new file mode 100644 index 0000000..cd29009 Binary files /dev/null and b/images/spacer.gif differ diff --git a/images/upload_icons/avi.gif b/images/upload_icons/avi.gif new file mode 100644 index 0000000..55f2116 Binary files /dev/null and b/images/upload_icons/avi.gif differ diff --git a/images/upload_icons/bmp.gif b/images/upload_icons/bmp.gif new file mode 100644 index 0000000..abd0571 Binary files /dev/null and b/images/upload_icons/bmp.gif differ diff --git a/images/upload_icons/doc.gif b/images/upload_icons/doc.gif new file mode 100644 index 0000000..078d9f7 Binary files /dev/null and b/images/upload_icons/doc.gif differ diff --git a/images/upload_icons/exe.gif b/images/upload_icons/exe.gif new file mode 100644 index 0000000..b77dd4d Binary files /dev/null and b/images/upload_icons/exe.gif differ diff --git a/images/upload_icons/flash.gif b/images/upload_icons/flash.gif new file mode 100644 index 0000000..00ee0f4 Binary files /dev/null and b/images/upload_icons/flash.gif differ diff --git a/images/upload_icons/gif.gif b/images/upload_icons/gif.gif new file mode 100644 index 0000000..5aa8d46 Binary files /dev/null and b/images/upload_icons/gif.gif differ diff --git a/images/upload_icons/html.gif b/images/upload_icons/html.gif new file mode 100644 index 0000000..eb948ea Binary files /dev/null and b/images/upload_icons/html.gif differ diff --git a/images/upload_icons/jpg.gif b/images/upload_icons/jpg.gif new file mode 100644 index 0000000..537de1c Binary files /dev/null and b/images/upload_icons/jpg.gif differ diff --git a/images/upload_icons/mid.gif b/images/upload_icons/mid.gif new file mode 100644 index 0000000..ea7302c Binary files /dev/null and b/images/upload_icons/mid.gif differ diff --git a/images/upload_icons/mov.gif b/images/upload_icons/mov.gif new file mode 100644 index 0000000..55f2116 Binary files /dev/null and b/images/upload_icons/mov.gif differ diff --git a/images/upload_icons/mp3.gif b/images/upload_icons/mp3.gif new file mode 100644 index 0000000..acf1a50 Binary files /dev/null and b/images/upload_icons/mp3.gif differ diff --git a/images/upload_icons/mpg.gif b/images/upload_icons/mpg.gif new file mode 100644 index 0000000..55f2116 Binary files /dev/null and b/images/upload_icons/mpg.gif differ diff --git a/images/upload_icons/netscape.gif b/images/upload_icons/netscape.gif new file mode 100644 index 0000000..0854440 Binary files /dev/null and b/images/upload_icons/netscape.gif differ diff --git a/images/upload_icons/pdf.gif b/images/upload_icons/pdf.gif new file mode 100644 index 0000000..8d0603a Binary files /dev/null and b/images/upload_icons/pdf.gif differ diff --git a/images/upload_icons/ppt.gif b/images/upload_icons/ppt.gif new file mode 100644 index 0000000..a3800d1 Binary files /dev/null and b/images/upload_icons/ppt.gif differ diff --git a/images/upload_icons/rar.gif b/images/upload_icons/rar.gif new file mode 100644 index 0000000..9c0bd56 Binary files /dev/null and b/images/upload_icons/rar.gif differ diff --git a/images/upload_icons/txt.gif b/images/upload_icons/txt.gif new file mode 100644 index 0000000..cc3639b Binary files /dev/null and b/images/upload_icons/txt.gif differ diff --git a/images/upload_icons/wav.gif b/images/upload_icons/wav.gif new file mode 100644 index 0000000..2119525 Binary files /dev/null and b/images/upload_icons/wav.gif differ diff --git a/images/upload_icons/xls.gif b/images/upload_icons/xls.gif new file mode 100644 index 0000000..187a19b Binary files /dev/null and b/images/upload_icons/xls.gif differ diff --git a/images/upload_icons/zip.gif b/images/upload_icons/zip.gif new file mode 100644 index 0000000..0141376 Binary files /dev/null and b/images/upload_icons/zip.gif differ diff --git a/img/Affaires/Affaire_Casino.png b/img/Affaires/Affaire_Casino.png new file mode 100644 index 0000000..79eb29d Binary files /dev/null and b/img/Affaires/Affaire_Casino.png differ diff --git a/img/Affaires/Affaire_Plateforme.png b/img/Affaires/Affaire_Plateforme.png new file mode 100644 index 0000000..e9b8bf5 Binary files /dev/null and b/img/Affaires/Affaire_Plateforme.png differ diff --git a/img/Affaires/Affaire_Port_Marchand.png b/img/Affaires/Affaire_Port_Marchand.png new file mode 100644 index 0000000..7c92b1d Binary files /dev/null and b/img/Affaires/Affaire_Port_Marchand.png differ diff --git a/img/Affaires/Affaire_Scierie.png b/img/Affaires/Affaire_Scierie.png new file mode 100644 index 0000000..31c8b86 Binary files /dev/null and b/img/Affaires/Affaire_Scierie.png differ diff --git a/img/Chefs/Irlandais/CLIMAX0008-resources.assets-524.png b/img/Chefs/Irlandais/CLIMAX0008-resources.assets-524.png new file mode 100644 index 0000000..a0f9f8a Binary files /dev/null and b/img/Chefs/Irlandais/CLIMAX0008-resources.assets-524.png differ diff --git a/img/Chefs/Irlandais/MEMBER1-resources.assets-251.png b/img/Chefs/Irlandais/MEMBER1-resources.assets-251.png new file mode 100644 index 0000000..08868b1 Binary files /dev/null and b/img/Chefs/Irlandais/MEMBER1-resources.assets-251.png differ diff --git a/img/Chefs/Irlandais/MEMBER2-resources.assets-286.png b/img/Chefs/Irlandais/MEMBER2-resources.assets-286.png new file mode 100644 index 0000000..51f55ba Binary files /dev/null and b/img/Chefs/Irlandais/MEMBER2-resources.assets-286.png differ diff --git a/img/Chefs/Irlandais/MEMBER4-resources.assets-234.png b/img/Chefs/Irlandais/MEMBER4-resources.assets-234.png new file mode 100644 index 0000000..666aa3a Binary files /dev/null and b/img/Chefs/Irlandais/MEMBER4-resources.assets-234.png differ diff --git a/img/Chefs/Irlandais/MEMBER5-resources.assets-1650.png b/img/Chefs/Irlandais/MEMBER5-resources.assets-1650.png new file mode 100644 index 0000000..080b948 Binary files /dev/null and b/img/Chefs/Irlandais/MEMBER5-resources.assets-1650.png differ diff --git a/img/Chefs/Irlandais/MEMBER5-resources.assets-2275.png b/img/Chefs/Irlandais/MEMBER5-resources.assets-2275.png new file mode 100644 index 0000000..1829754 Binary files /dev/null and b/img/Chefs/Irlandais/MEMBER5-resources.assets-2275.png differ diff --git a/img/Chefs/Italiens/CLIMAX0017-resources.assets-830.png b/img/Chefs/Italiens/CLIMAX0017-resources.assets-830.png new file mode 100644 index 0000000..a3f3c18 Binary files /dev/null and b/img/Chefs/Italiens/CLIMAX0017-resources.assets-830.png differ diff --git a/img/Chefs/Italiens/MEMBER1-resources.assets-105.png b/img/Chefs/Italiens/MEMBER1-resources.assets-105.png new file mode 100644 index 0000000..9ef39a1 Binary files /dev/null and b/img/Chefs/Italiens/MEMBER1-resources.assets-105.png differ diff --git a/img/Chefs/Italiens/MEMBER1-resources.assets-202.png b/img/Chefs/Italiens/MEMBER1-resources.assets-202.png new file mode 100644 index 0000000..07f1a3d Binary files /dev/null and b/img/Chefs/Italiens/MEMBER1-resources.assets-202.png differ diff --git a/img/Chefs/Italiens/MEMBER1-resources.assets-2039.png b/img/Chefs/Italiens/MEMBER1-resources.assets-2039.png new file mode 100644 index 0000000..be38184 Binary files /dev/null and b/img/Chefs/Italiens/MEMBER1-resources.assets-2039.png differ diff --git a/img/Chefs/Italiens/MEMBER3-resources.assets-819.png b/img/Chefs/Italiens/MEMBER3-resources.assets-819.png new file mode 100644 index 0000000..873bc52 Binary files /dev/null and b/img/Chefs/Italiens/MEMBER3-resources.assets-819.png differ diff --git a/img/Chefs/Italiens/MEMBER4-resources.assets-369.png b/img/Chefs/Italiens/MEMBER4-resources.assets-369.png new file mode 100644 index 0000000..0d0d843 Binary files /dev/null and b/img/Chefs/Italiens/MEMBER4-resources.assets-369.png differ diff --git a/img/Chefs/Siciliens/CLIMAX1008-resources.assets-1791.png b/img/Chefs/Siciliens/CLIMAX1008-resources.assets-1791.png new file mode 100644 index 0000000..f084356 Binary files /dev/null and b/img/Chefs/Siciliens/CLIMAX1008-resources.assets-1791.png differ diff --git a/img/Chefs/Siciliens/CLIMAX1013-resources.assets-301.png b/img/Chefs/Siciliens/CLIMAX1013-resources.assets-301.png new file mode 100644 index 0000000..0b3965a Binary files /dev/null and b/img/Chefs/Siciliens/CLIMAX1013-resources.assets-301.png differ diff --git a/img/Chefs/Siciliens/MEMBER1-resources.assets-2208.png b/img/Chefs/Siciliens/MEMBER1-resources.assets-2208.png new file mode 100644 index 0000000..bfd9f58 Binary files /dev/null and b/img/Chefs/Siciliens/MEMBER1-resources.assets-2208.png differ diff --git a/img/Chefs/Siciliens/MEMBER2-resources.assets-1197.png b/img/Chefs/Siciliens/MEMBER2-resources.assets-1197.png new file mode 100644 index 0000000..c7fd96e Binary files /dev/null and b/img/Chefs/Siciliens/MEMBER2-resources.assets-1197.png differ diff --git a/img/Chefs/Siciliens/MEMBER2-resources.assets-1249.png b/img/Chefs/Siciliens/MEMBER2-resources.assets-1249.png new file mode 100644 index 0000000..45e69b4 Binary files /dev/null and b/img/Chefs/Siciliens/MEMBER2-resources.assets-1249.png differ diff --git a/img/Chefs/Siciliens/MEMBER2-resources.assets-125.png b/img/Chefs/Siciliens/MEMBER2-resources.assets-125.png new file mode 100644 index 0000000..2214b1b Binary files /dev/null and b/img/Chefs/Siciliens/MEMBER2-resources.assets-125.png differ diff --git a/img/Chefs/Triade/MEMBER1-resources.assets-1708.png b/img/Chefs/Triade/MEMBER1-resources.assets-1708.png new file mode 100644 index 0000000..c8ee157 Binary files /dev/null and b/img/Chefs/Triade/MEMBER1-resources.assets-1708.png differ diff --git a/img/Chefs/Triade/MEMBER1-resources.assets-474.png b/img/Chefs/Triade/MEMBER1-resources.assets-474.png new file mode 100644 index 0000000..8adb644 Binary files /dev/null and b/img/Chefs/Triade/MEMBER1-resources.assets-474.png differ diff --git a/img/Chefs/Triade/MEMBER2-resources.assets-1039.png b/img/Chefs/Triade/MEMBER2-resources.assets-1039.png new file mode 100644 index 0000000..813b25e Binary files /dev/null and b/img/Chefs/Triade/MEMBER2-resources.assets-1039.png differ diff --git a/img/Chefs/Triade/MEMBER3-resources.assets-1431.png b/img/Chefs/Triade/MEMBER3-resources.assets-1431.png new file mode 100644 index 0000000..b4032a6 Binary files /dev/null and b/img/Chefs/Triade/MEMBER3-resources.assets-1431.png differ diff --git a/img/Chefs/Triade/MEMBER3-resources.assets-177.png b/img/Chefs/Triade/MEMBER3-resources.assets-177.png new file mode 100644 index 0000000..34b4d7d Binary files /dev/null and b/img/Chefs/Triade/MEMBER3-resources.assets-177.png differ diff --git a/img/Chefs/Triade/MEMBER3-resources.assets-2017.png b/img/Chefs/Triade/MEMBER3-resources.assets-2017.png new file mode 100644 index 0000000..2dd6148 Binary files /dev/null and b/img/Chefs/Triade/MEMBER3-resources.assets-2017.png differ diff --git a/img/Crimes/Crime_Alcool.png b/img/Crimes/Crime_Alcool.png new file mode 100644 index 0000000..95352bf Binary files /dev/null and b/img/Crimes/Crime_Alcool.png differ diff --git a/img/Crimes/Crime_Assassinat.png b/img/Crimes/Crime_Assassinat.png new file mode 100644 index 0000000..f2e1f11 Binary files /dev/null and b/img/Crimes/Crime_Assassinat.png differ diff --git a/img/Crimes/Crime_Attentat.png b/img/Crimes/Crime_Attentat.png new file mode 100644 index 0000000..13da21a Binary files /dev/null and b/img/Crimes/Crime_Attentat.png differ diff --git a/img/Crimes/Crime_Blanchiment.png b/img/Crimes/Crime_Blanchiment.png new file mode 100644 index 0000000..0e3cd5d Binary files /dev/null and b/img/Crimes/Crime_Blanchiment.png differ diff --git a/img/Crimes/Crime_Braquage.png b/img/Crimes/Crime_Braquage.png new file mode 100644 index 0000000..5377690 Binary files /dev/null and b/img/Crimes/Crime_Braquage.png differ diff --git a/img/Crimes/Crime_Cambriolage.png b/img/Crimes/Crime_Cambriolage.png new file mode 100644 index 0000000..b752592 Binary files /dev/null and b/img/Crimes/Crime_Cambriolage.png differ diff --git a/img/Crimes/Crime_Contrebande.png b/img/Crimes/Crime_Contrebande.png new file mode 100644 index 0000000..46cd250 Binary files /dev/null and b/img/Crimes/Crime_Contrebande.png differ diff --git a/img/Crimes/Crime_Corruption.png b/img/Crimes/Crime_Corruption.png new file mode 100644 index 0000000..0081826 Binary files /dev/null and b/img/Crimes/Crime_Corruption.png differ diff --git a/img/Crimes/Crime_Destruction.png b/img/Crimes/Crime_Destruction.png new file mode 100644 index 0000000..3463a7c Binary files /dev/null and b/img/Crimes/Crime_Destruction.png differ diff --git a/img/Crimes/Crime_Drogue.png b/img/Crimes/Crime_Drogue.png new file mode 100644 index 0000000..a48e9b5 Binary files /dev/null and b/img/Crimes/Crime_Drogue.png differ diff --git a/img/Crimes/Crime_Enlevement.png b/img/Crimes/Crime_Enlevement.png new file mode 100644 index 0000000..7f46c31 Binary files /dev/null and b/img/Crimes/Crime_Enlevement.png differ diff --git a/img/Crimes/Crime_Execution.png b/img/Crimes/Crime_Execution.png new file mode 100644 index 0000000..5923d72 Binary files /dev/null and b/img/Crimes/Crime_Execution.png differ diff --git a/img/Crimes/Crime_Intimidation.png b/img/Crimes/Crime_Intimidation.png new file mode 100644 index 0000000..161931e Binary files /dev/null and b/img/Crimes/Crime_Intimidation.png differ diff --git a/img/Crimes/Crime_Prostitution.png b/img/Crimes/Crime_Prostitution.png new file mode 100644 index 0000000..aa9abc3 Binary files /dev/null and b/img/Crimes/Crime_Prostitution.png differ diff --git a/img/Crimes/Crime_Protection.png b/img/Crimes/Crime_Protection.png new file mode 100644 index 0000000..57e4896 Binary files /dev/null and b/img/Crimes/Crime_Protection.png differ diff --git a/img/Crimes/Crime_Racket.png b/img/Crimes/Crime_Racket.png new file mode 100644 index 0000000..d1c7c7d Binary files /dev/null and b/img/Crimes/Crime_Racket.png differ diff --git a/img/Crimes/Crime_Syndicats.png b/img/Crimes/Crime_Syndicats.png new file mode 100644 index 0000000..6c71435 Binary files /dev/null and b/img/Crimes/Crime_Syndicats.png differ diff --git a/img/Crimes/Crime_Trafic_armes.png b/img/Crimes/Crime_Trafic_armes.png new file mode 100644 index 0000000..2c59c1f Binary files /dev/null and b/img/Crimes/Crime_Trafic_armes.png differ diff --git a/img/Crimes/Crime_Tripot.png b/img/Crimes/Crime_Tripot.png new file mode 100644 index 0000000..bdb3d57 Binary files /dev/null and b/img/Crimes/Crime_Tripot.png differ diff --git a/img/Ecussons/Civils.png b/img/Ecussons/Civils.png new file mode 100644 index 0000000..f30f3cc Binary files /dev/null and b/img/Ecussons/Civils.png differ diff --git a/img/Ecussons/Clan_O_Dubhain.png b/img/Ecussons/Clan_O_Dubhain.png new file mode 100644 index 0000000..54da2b7 Binary files /dev/null and b/img/Ecussons/Clan_O_Dubhain.png differ diff --git a/img/Ecussons/Clan_Vincenzo.png b/img/Ecussons/Clan_Vincenzo.png new file mode 100644 index 0000000..64b50aa Binary files /dev/null and b/img/Ecussons/Clan_Vincenzo.png differ diff --git a/img/Ecussons/Famille_Scarlata.png b/img/Ecussons/Famille_Scarlata.png new file mode 100644 index 0000000..8881167 Binary files /dev/null and b/img/Ecussons/Famille_Scarlata.png differ diff --git a/img/Ecussons/Police.png b/img/Ecussons/Police.png new file mode 100644 index 0000000..ad4980d Binary files /dev/null and b/img/Ecussons/Police.png differ diff --git a/img/Ecussons/Tigres_Jaunes.png b/img/Ecussons/Tigres_Jaunes.png new file mode 100644 index 0000000..8ed2cf3 Binary files /dev/null and b/img/Ecussons/Tigres_Jaunes.png differ diff --git a/img/Lieux/Lieu_Acierie_Jour.png b/img/Lieux/Lieu_Acierie_Jour.png new file mode 100644 index 0000000..dcb49f6 Binary files /dev/null and b/img/Lieux/Lieu_Acierie_Jour.png differ diff --git a/img/Lieux/Lieu_Acierie_Nuit.png b/img/Lieux/Lieu_Acierie_Nuit.png new file mode 100644 index 0000000..2c769b4 Binary files /dev/null and b/img/Lieux/Lieu_Acierie_Nuit.png differ diff --git a/img/Lieux/Lieu_Bois_Jour.png b/img/Lieux/Lieu_Bois_Jour.png new file mode 100644 index 0000000..f450732 Binary files /dev/null and b/img/Lieux/Lieu_Bois_Jour.png differ diff --git a/img/Lieux/Lieu_Bois_Nuit.png b/img/Lieux/Lieu_Bois_Nuit.png new file mode 100644 index 0000000..9c965e3 Binary files /dev/null and b/img/Lieux/Lieu_Bois_Nuit.png differ diff --git a/img/Lieux/Lieu_Casino_Jour.png b/img/Lieux/Lieu_Casino_Jour.png new file mode 100644 index 0000000..2e4f895 Binary files /dev/null and b/img/Lieux/Lieu_Casino_Jour.png differ diff --git a/img/Lieux/Lieu_Casino_Nuit.png b/img/Lieux/Lieu_Casino_Nuit.png new file mode 100644 index 0000000..0502dd9 Binary files /dev/null and b/img/Lieux/Lieu_Casino_Nuit.png differ diff --git a/img/Lieux/Lieu_Centrale_Jour.png b/img/Lieux/Lieu_Centrale_Jour.png new file mode 100644 index 0000000..b568cd5 Binary files /dev/null and b/img/Lieux/Lieu_Centrale_Jour.png differ diff --git a/img/Lieux/Lieu_Centrale_Nuit.png b/img/Lieux/Lieu_Centrale_Nuit.png new file mode 100644 index 0000000..b95ba56 Binary files /dev/null and b/img/Lieux/Lieu_Centrale_Nuit.png differ diff --git a/img/Lieux/Lieu_Commissariat_Jour.png b/img/Lieux/Lieu_Commissariat_Jour.png new file mode 100644 index 0000000..d02a1fd Binary files /dev/null and b/img/Lieux/Lieu_Commissariat_Jour.png differ diff --git a/img/Lieux/Lieu_Commissariat_Nuit.png b/img/Lieux/Lieu_Commissariat_Nuit.png new file mode 100644 index 0000000..45442f2 Binary files /dev/null and b/img/Lieux/Lieu_Commissariat_Nuit.png differ diff --git a/img/Lieux/Lieu_Ferme_Jour.png b/img/Lieux/Lieu_Ferme_Jour.png new file mode 100644 index 0000000..a76b69b Binary files /dev/null and b/img/Lieux/Lieu_Ferme_Jour.png differ diff --git a/img/Lieux/Lieu_Ferme_Nuit.png b/img/Lieux/Lieu_Ferme_Nuit.png new file mode 100644 index 0000000..ffc8775 Binary files /dev/null and b/img/Lieux/Lieu_Ferme_Nuit.png differ diff --git a/img/Lieux/Lieu_Gare_Jour.png b/img/Lieux/Lieu_Gare_Jour.png new file mode 100644 index 0000000..53e724c Binary files /dev/null and b/img/Lieux/Lieu_Gare_Jour.png differ diff --git a/img/Lieux/Lieu_Gare_Nuit.png b/img/Lieux/Lieu_Gare_Nuit.png new file mode 100644 index 0000000..a3aa377 Binary files /dev/null and b/img/Lieux/Lieu_Gare_Nuit.png differ diff --git a/img/Lieux/Lieu_GratteCiel_Appartements_Jour.png b/img/Lieux/Lieu_GratteCiel_Appartements_Jour.png new file mode 100644 index 0000000..2492220 Binary files /dev/null and b/img/Lieux/Lieu_GratteCiel_Appartements_Jour.png differ diff --git a/img/Lieux/Lieu_GratteCiel_Appartements_Nuit.png b/img/Lieux/Lieu_GratteCiel_Appartements_Nuit.png new file mode 100644 index 0000000..68e4d8d Binary files /dev/null and b/img/Lieux/Lieu_GratteCiel_Appartements_Nuit.png differ diff --git a/img/Lieux/Lieu_GratteCiel_Bureaux_Jour.png b/img/Lieux/Lieu_GratteCiel_Bureaux_Jour.png new file mode 100644 index 0000000..2c55715 Binary files /dev/null and b/img/Lieux/Lieu_GratteCiel_Bureaux_Jour.png differ diff --git a/img/Lieux/Lieu_GratteCiel_Bureaux_Nuit.png b/img/Lieux/Lieu_GratteCiel_Bureaux_Nuit.png new file mode 100644 index 0000000..d108ed0 Binary files /dev/null and b/img/Lieux/Lieu_GratteCiel_Bureaux_Nuit.png differ diff --git a/img/Lieux/Lieu_Hopital_Jour.png b/img/Lieux/Lieu_Hopital_Jour.png new file mode 100644 index 0000000..ba5d05f Binary files /dev/null and b/img/Lieux/Lieu_Hopital_Jour.png differ diff --git a/img/Lieux/Lieu_Hopital_Nuit.png b/img/Lieux/Lieu_Hopital_Nuit.png new file mode 100644 index 0000000..dcbf5e2 Binary files /dev/null and b/img/Lieux/Lieu_Hopital_Nuit.png differ diff --git a/img/Lieux/Lieu_Hotel_Jour.png b/img/Lieux/Lieu_Hotel_Jour.png new file mode 100644 index 0000000..1f9853a Binary files /dev/null and b/img/Lieux/Lieu_Hotel_Jour.png differ diff --git a/img/Lieux/Lieu_Hotel_Nuit.png b/img/Lieux/Lieu_Hotel_Nuit.png new file mode 100644 index 0000000..7ff2f89 Binary files /dev/null and b/img/Lieux/Lieu_Hotel_Nuit.png differ diff --git a/img/Lieux/Lieu_Mairie_Jour.png b/img/Lieux/Lieu_Mairie_Jour.png new file mode 100644 index 0000000..7f8c3fd Binary files /dev/null and b/img/Lieux/Lieu_Mairie_Jour.png differ diff --git a/img/Lieux/Lieu_Mairie_Nuit.png b/img/Lieux/Lieu_Mairie_Nuit.png new file mode 100644 index 0000000..59f719f Binary files /dev/null and b/img/Lieux/Lieu_Mairie_Nuit.png differ diff --git a/img/Lieux/Lieu_Palais_Jour.png b/img/Lieux/Lieu_Palais_Jour.png new file mode 100644 index 0000000..da3d360 Binary files /dev/null and b/img/Lieux/Lieu_Palais_Jour.png differ diff --git a/img/Lieux/Lieu_Palais_Nuit.png b/img/Lieux/Lieu_Palais_Nuit.png new file mode 100644 index 0000000..58ada34 Binary files /dev/null and b/img/Lieux/Lieu_Palais_Nuit.png differ diff --git a/img/Lieux/Lieu_Plage_Jour.png b/img/Lieux/Lieu_Plage_Jour.png new file mode 100644 index 0000000..b2e5149 Binary files /dev/null and b/img/Lieux/Lieu_Plage_Jour.png differ diff --git a/img/Lieux/Lieu_Plage_Nuit.png b/img/Lieux/Lieu_Plage_Nuit.png new file mode 100644 index 0000000..98b5579 Binary files /dev/null and b/img/Lieux/Lieu_Plage_Nuit.png differ diff --git a/img/Lieux/Lieu_Plateforme_Jour.png b/img/Lieux/Lieu_Plateforme_Jour.png new file mode 100644 index 0000000..1210ed8 Binary files /dev/null and b/img/Lieux/Lieu_Plateforme_Jour.png differ diff --git a/img/Lieux/Lieu_Plateforme_Nuit.png b/img/Lieux/Lieu_Plateforme_Nuit.png new file mode 100644 index 0000000..9b7879f Binary files /dev/null and b/img/Lieux/Lieu_Plateforme_Nuit.png differ diff --git a/img/Lieux/Lieu_Port_Jour.png b/img/Lieux/Lieu_Port_Jour.png new file mode 100644 index 0000000..2b06752 Binary files /dev/null and b/img/Lieux/Lieu_Port_Jour.png differ diff --git a/img/Lieux/Lieu_Port_Nuit.png b/img/Lieux/Lieu_Port_Nuit.png new file mode 100644 index 0000000..7dd36ca Binary files /dev/null and b/img/Lieux/Lieu_Port_Nuit.png differ diff --git a/img/Lieux/Lieu_Prison_Jour.png b/img/Lieux/Lieu_Prison_Jour.png new file mode 100644 index 0000000..ed1ed06 Binary files /dev/null and b/img/Lieux/Lieu_Prison_Jour.png differ diff --git a/img/Lieux/Lieu_Prison_Nuit.png b/img/Lieux/Lieu_Prison_Nuit.png new file mode 100644 index 0000000..e14103d Binary files /dev/null and b/img/Lieux/Lieu_Prison_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Allemand_Jour.png b/img/Lieux/Lieu_Quartier_Allemand_Jour.png new file mode 100644 index 0000000..de75f48 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Allemand_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Allemand_Nuit.png b/img/Lieux/Lieu_Quartier_Allemand_Nuit.png new file mode 100644 index 0000000..5c42063 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Allemand_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Centre_Ville_Jour.png b/img/Lieux/Lieu_Quartier_Centre_Ville_Jour.png new file mode 100644 index 0000000..64b349c Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Centre_Ville_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Centre_Ville_Nuit.png b/img/Lieux/Lieu_Quartier_Centre_Ville_Nuit.png new file mode 100644 index 0000000..dacb7e3 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Centre_Ville_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Chinois_Jour.png b/img/Lieux/Lieu_Quartier_Chinois_Jour.png new file mode 100644 index 0000000..1d62937 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Chinois_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Chinois_Nuit.png b/img/Lieux/Lieu_Quartier_Chinois_Nuit.png new file mode 100644 index 0000000..ae6a094 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Chinois_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Commerces_Jour.png b/img/Lieux/Lieu_Quartier_Commerces_Jour.png new file mode 100644 index 0000000..49dc0cd Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Commerces_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Commerces_Nuit.png b/img/Lieux/Lieu_Quartier_Commerces_Nuit.png new file mode 100644 index 0000000..36680e4 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Commerces_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Ecossais_Jour.png b/img/Lieux/Lieu_Quartier_Ecossais_Jour.png new file mode 100644 index 0000000..1c8ea94 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Ecossais_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Ecossais_Nuit.png b/img/Lieux/Lieu_Quartier_Ecossais_Nuit.png new file mode 100644 index 0000000..450af5f Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Ecossais_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Francais_Jour.png b/img/Lieux/Lieu_Quartier_Francais_Jour.png new file mode 100644 index 0000000..2ad5375 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Francais_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Francais_Nuit.png b/img/Lieux/Lieu_Quartier_Francais_Nuit.png new file mode 100644 index 0000000..307fdcf Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Francais_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Irlandais_Jour.png b/img/Lieux/Lieu_Quartier_Irlandais_Jour.png new file mode 100644 index 0000000..79c6ba0 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Irlandais_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Irlandais_Nuit.png b/img/Lieux/Lieu_Quartier_Irlandais_Nuit.png new file mode 100644 index 0000000..8b800c5 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Irlandais_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Italien_Jour.png b/img/Lieux/Lieu_Quartier_Italien_Jour.png new file mode 100644 index 0000000..63aa4ff Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Italien_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Italien_Nuit.png b/img/Lieux/Lieu_Quartier_Italien_Nuit.png new file mode 100644 index 0000000..0199990 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Italien_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Parc_Jour.png b/img/Lieux/Lieu_Quartier_Parc_Jour.png new file mode 100644 index 0000000..44031ff Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Parc_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Parc_Nuit.png b/img/Lieux/Lieu_Quartier_Parc_Nuit.png new file mode 100644 index 0000000..aef9f3e Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Parc_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Riche_Jour.png b/img/Lieux/Lieu_Quartier_Riche_Jour.png new file mode 100644 index 0000000..8e755b7 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Riche_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Riche_Nuit.png b/img/Lieux/Lieu_Quartier_Riche_Nuit.png new file mode 100644 index 0000000..b3e34c3 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Riche_Nuit.png differ diff --git a/img/Lieux/Lieu_Quartier_Slave_Jour.png b/img/Lieux/Lieu_Quartier_Slave_Jour.png new file mode 100644 index 0000000..25bd107 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Slave_Jour.png differ diff --git a/img/Lieux/Lieu_Quartier_Slave_Nuit.png b/img/Lieux/Lieu_Quartier_Slave_Nuit.png new file mode 100644 index 0000000..cc2cd15 Binary files /dev/null and b/img/Lieux/Lieu_Quartier_Slave_Nuit.png differ diff --git a/img/Lieux/Lieu_Ranch_Jour.png b/img/Lieux/Lieu_Ranch_Jour.png new file mode 100644 index 0000000..c36dbf4 Binary files /dev/null and b/img/Lieux/Lieu_Ranch_Jour.png differ diff --git a/img/Lieux/Lieu_Ranch_Nuit.png b/img/Lieux/Lieu_Ranch_Nuit.png new file mode 100644 index 0000000..660d52d Binary files /dev/null and b/img/Lieux/Lieu_Ranch_Nuit.png differ diff --git a/img/Lieux/Lieu_Retraite_Jour.png b/img/Lieux/Lieu_Retraite_Jour.png new file mode 100644 index 0000000..63b19e5 Binary files /dev/null and b/img/Lieux/Lieu_Retraite_Jour.png differ diff --git a/img/Lieux/Lieu_Retraite_Nuit.png b/img/Lieux/Lieu_Retraite_Nuit.png new file mode 100644 index 0000000..5c3e093 Binary files /dev/null and b/img/Lieux/Lieu_Retraite_Nuit.png differ diff --git a/img/Lieux/Lieu_Scierie_Jour.png b/img/Lieux/Lieu_Scierie_Jour.png new file mode 100644 index 0000000..1d03e5b Binary files /dev/null and b/img/Lieux/Lieu_Scierie_Jour.png differ diff --git a/img/Lieux/Lieu_Scierie_Nuit.png b/img/Lieux/Lieu_Scierie_Nuit.png new file mode 100644 index 0000000..bfa6d8e Binary files /dev/null and b/img/Lieux/Lieu_Scierie_Nuit.png differ diff --git a/img/Lieux/Lieu_Usine_Jour.png b/img/Lieux/Lieu_Usine_Jour.png new file mode 100644 index 0000000..2aade75 Binary files /dev/null and b/img/Lieux/Lieu_Usine_Jour.png differ diff --git a/img/Lieux/Lieu_Usine_Nuit.png b/img/Lieux/Lieu_Usine_Nuit.png new file mode 100644 index 0000000..d801b22 Binary files /dev/null and b/img/Lieux/Lieu_Usine_Nuit.png differ diff --git a/img/Objets/Objet_Alcool.png b/img/Objets/Objet_Alcool.png new file mode 100644 index 0000000..b9a0bab Binary files /dev/null and b/img/Objets/Objet_Alcool.png differ diff --git a/img/Objets/Objet_Argent_Sale.png b/img/Objets/Objet_Argent_Sale.png new file mode 100644 index 0000000..8e9ca42 Binary files /dev/null and b/img/Objets/Objet_Argent_Sale.png differ diff --git a/img/Objets/Objet_Armes.png b/img/Objets/Objet_Armes.png new file mode 100644 index 0000000..22a2817 Binary files /dev/null and b/img/Objets/Objet_Armes.png differ diff --git a/img/Objets/Objet_Bijoux.png b/img/Objets/Objet_Bijoux.png new file mode 100644 index 0000000..79087a1 Binary files /dev/null and b/img/Objets/Objet_Bijoux.png differ diff --git a/img/Objets/Objet_Cigarettes.png b/img/Objets/Objet_Cigarettes.png new file mode 100644 index 0000000..ca68159 Binary files /dev/null and b/img/Objets/Objet_Cigarettes.png differ diff --git a/img/Objets/Objet_Drogue.png.png b/img/Objets/Objet_Drogue.png.png new file mode 100644 index 0000000..58b3626 Binary files /dev/null and b/img/Objets/Objet_Drogue.png.png differ diff --git a/img/Situations/Situation_Arrestation_01.png b/img/Situations/Situation_Arrestation_01.png new file mode 100644 index 0000000..c500d7c Binary files /dev/null and b/img/Situations/Situation_Arrestation_01.png differ diff --git a/img/Situations/Situation_Arrestation_02.png b/img/Situations/Situation_Arrestation_02.png new file mode 100644 index 0000000..542d82b Binary files /dev/null and b/img/Situations/Situation_Arrestation_02.png differ diff --git a/img/Situations/Situation_Attentat.png b/img/Situations/Situation_Attentat.png new file mode 100644 index 0000000..fcfee7b Binary files /dev/null and b/img/Situations/Situation_Attentat.png differ diff --git a/img/Situations/Situation_Banque.png b/img/Situations/Situation_Banque.png new file mode 100644 index 0000000..446f18f Binary files /dev/null and b/img/Situations/Situation_Banque.png differ diff --git a/img/Situations/Situation_Banquet_Mondain.png b/img/Situations/Situation_Banquet_Mondain.png new file mode 100644 index 0000000..53babb9 Binary files /dev/null and b/img/Situations/Situation_Banquet_Mondain.png differ diff --git a/img/Situations/Situation_Cambriolage.png b/img/Situations/Situation_Cambriolage.png new file mode 100644 index 0000000..f02dd45 Binary files /dev/null and b/img/Situations/Situation_Cambriolage.png differ diff --git a/img/Situations/Situation_Corruption_01.png b/img/Situations/Situation_Corruption_01.png new file mode 100644 index 0000000..953db25 Binary files /dev/null and b/img/Situations/Situation_Corruption_01.png differ diff --git a/img/Situations/Situation_Corruption_02.png b/img/Situations/Situation_Corruption_02.png new file mode 100644 index 0000000..01ec197 Binary files /dev/null and b/img/Situations/Situation_Corruption_02.png differ diff --git a/img/Situations/Situation_Corruption_03.png b/img/Situations/Situation_Corruption_03.png new file mode 100644 index 0000000..90464d7 Binary files /dev/null and b/img/Situations/Situation_Corruption_03.png differ diff --git a/img/Situations/Situation_Corruption_04.png b/img/Situations/Situation_Corruption_04.png new file mode 100644 index 0000000..a012386 Binary files /dev/null and b/img/Situations/Situation_Corruption_04.png differ diff --git a/img/Situations/Situation_Corruption_05.png b/img/Situations/Situation_Corruption_05.png new file mode 100644 index 0000000..069b029 Binary files /dev/null and b/img/Situations/Situation_Corruption_05.png differ diff --git a/img/Situations/Situation_Corruption_06.png b/img/Situations/Situation_Corruption_06.png new file mode 100644 index 0000000..883bca3 Binary files /dev/null and b/img/Situations/Situation_Corruption_06.png differ diff --git a/img/Situations/Situation_Destruction.png b/img/Situations/Situation_Destruction.png new file mode 100644 index 0000000..0c4d5a4 Binary files /dev/null and b/img/Situations/Situation_Destruction.png differ diff --git a/img/Situations/Situation_Drogue_01.png b/img/Situations/Situation_Drogue_01.png new file mode 100644 index 0000000..e5ea446 Binary files /dev/null and b/img/Situations/Situation_Drogue_01.png differ diff --git a/img/Situations/Situation_Drogue_02.png b/img/Situations/Situation_Drogue_02.png new file mode 100644 index 0000000..68cba78 Binary files /dev/null and b/img/Situations/Situation_Drogue_02.png differ diff --git a/img/Situations/Situation_Echappe.png b/img/Situations/Situation_Echappe.png new file mode 100644 index 0000000..8ab368d Binary files /dev/null and b/img/Situations/Situation_Echappe.png differ diff --git a/img/Situations/Situation_Economie.png b/img/Situations/Situation_Economie.png new file mode 100644 index 0000000..0262ed4 Binary files /dev/null and b/img/Situations/Situation_Economie.png differ diff --git a/img/Situations/Situation_Execution_01.png b/img/Situations/Situation_Execution_01.png new file mode 100644 index 0000000..7620361 Binary files /dev/null and b/img/Situations/Situation_Execution_01.png differ diff --git a/img/Situations/Situation_Execution_02.png b/img/Situations/Situation_Execution_02.png new file mode 100644 index 0000000..7ad350e Binary files /dev/null and b/img/Situations/Situation_Execution_02.png differ diff --git a/img/Situations/Situation_Execution_03.png b/img/Situations/Situation_Execution_03.png new file mode 100644 index 0000000..92df4f1 Binary files /dev/null and b/img/Situations/Situation_Execution_03.png differ diff --git a/img/Situations/Situation_Intimidation_01.png b/img/Situations/Situation_Intimidation_01.png new file mode 100644 index 0000000..d68174b Binary files /dev/null and b/img/Situations/Situation_Intimidation_01.png differ diff --git a/img/Situations/Situation_Intimidation_02.png b/img/Situations/Situation_Intimidation_02.png new file mode 100644 index 0000000..7e5899a Binary files /dev/null and b/img/Situations/Situation_Intimidation_02.png differ diff --git a/img/Situations/Situation_Mairie_01.png b/img/Situations/Situation_Mairie_01.png new file mode 100644 index 0000000..2214449 Binary files /dev/null and b/img/Situations/Situation_Mairie_01.png differ diff --git a/img/Situations/Situation_Mairie_02.png b/img/Situations/Situation_Mairie_02.png new file mode 100644 index 0000000..859e745 Binary files /dev/null and b/img/Situations/Situation_Mairie_02.png differ diff --git a/img/Situations/Situation_Mairie_03.png b/img/Situations/Situation_Mairie_03.png new file mode 100644 index 0000000..2993c9c Binary files /dev/null and b/img/Situations/Situation_Mairie_03.png differ diff --git a/img/Situations/Situation_Manifestation.png b/img/Situations/Situation_Manifestation.png new file mode 100644 index 0000000..fb07512 Binary files /dev/null and b/img/Situations/Situation_Manifestation.png differ diff --git a/img/Situations/Situation_Message.png b/img/Situations/Situation_Message.png new file mode 100644 index 0000000..02fa37e Binary files /dev/null and b/img/Situations/Situation_Message.png differ diff --git a/img/Situations/Situation_Meurtre_01.png b/img/Situations/Situation_Meurtre_01.png new file mode 100644 index 0000000..7da205b Binary files /dev/null and b/img/Situations/Situation_Meurtre_01.png differ diff --git a/img/Situations/Situation_Meutre_02.png b/img/Situations/Situation_Meutre_02.png new file mode 100644 index 0000000..ddfbfd4 Binary files /dev/null and b/img/Situations/Situation_Meutre_02.png differ diff --git a/img/Situations/Situation_Meutre_03.png b/img/Situations/Situation_Meutre_03.png new file mode 100644 index 0000000..8d217bf Binary files /dev/null and b/img/Situations/Situation_Meutre_03.png differ diff --git a/img/Situations/Situation_Patron.png b/img/Situations/Situation_Patron.png new file mode 100644 index 0000000..a5e8492 Binary files /dev/null and b/img/Situations/Situation_Patron.png differ diff --git a/img/Situations/Situation_Police_Corruption_01.png b/img/Situations/Situation_Police_Corruption_01.png new file mode 100644 index 0000000..77ca575 Binary files /dev/null and b/img/Situations/Situation_Police_Corruption_01.png differ diff --git a/img/Situations/Situation_Police_Corruption_02.png b/img/Situations/Situation_Police_Corruption_02.png new file mode 100644 index 0000000..e7d9ec0 Binary files /dev/null and b/img/Situations/Situation_Police_Corruption_02.png differ diff --git a/img/Situations/Situation_Police_Corruption_03.png b/img/Situations/Situation_Police_Corruption_03.png new file mode 100644 index 0000000..4d14136 Binary files /dev/null and b/img/Situations/Situation_Police_Corruption_03.png differ diff --git a/img/Situations/Situation_Police_Enquete.png b/img/Situations/Situation_Police_Enquete.png new file mode 100644 index 0000000..bbe610c Binary files /dev/null and b/img/Situations/Situation_Police_Enquete.png differ diff --git a/img/Situations/Situation_Police_Fichiers.png b/img/Situations/Situation_Police_Fichiers.png new file mode 100644 index 0000000..fb02e48 Binary files /dev/null and b/img/Situations/Situation_Police_Fichiers.png differ diff --git a/img/Situations/Situation_Police_Meurtre.png b/img/Situations/Situation_Police_Meurtre.png new file mode 100644 index 0000000..6ba0c80 Binary files /dev/null and b/img/Situations/Situation_Police_Meurtre.png differ diff --git a/img/Situations/Situation_Police_Morgue.png b/img/Situations/Situation_Police_Morgue.png new file mode 100644 index 0000000..bc259f0 Binary files /dev/null and b/img/Situations/Situation_Police_Morgue.png differ diff --git a/img/Situations/Situation_Police_Paperasse.png b/img/Situations/Situation_Police_Paperasse.png new file mode 100644 index 0000000..ef158e1 Binary files /dev/null and b/img/Situations/Situation_Police_Paperasse.png differ diff --git a/img/Situations/Situation_Police_Violence.png b/img/Situations/Situation_Police_Violence.png new file mode 100644 index 0000000..3c3854b Binary files /dev/null and b/img/Situations/Situation_Police_Violence.png differ diff --git a/img/Situations/Situation_Prostitution.png b/img/Situations/Situation_Prostitution.png new file mode 100644 index 0000000..6f0bdc3 Binary files /dev/null and b/img/Situations/Situation_Prostitution.png differ diff --git a/img/Situations/Situation_Rencontre_01.png b/img/Situations/Situation_Rencontre_01.png new file mode 100644 index 0000000..af3cedf Binary files /dev/null and b/img/Situations/Situation_Rencontre_01.png differ diff --git a/img/Situations/Situation_Rencontre_02.png b/img/Situations/Situation_Rencontre_02.png new file mode 100644 index 0000000..f55ffd5 Binary files /dev/null and b/img/Situations/Situation_Rencontre_02.png differ diff --git a/img/Situations/Situation_Rencontre_03.png b/img/Situations/Situation_Rencontre_03.png new file mode 100644 index 0000000..7a1ea1c Binary files /dev/null and b/img/Situations/Situation_Rencontre_03.png differ diff --git a/img/Situations/Situation_Table_Mafia.png b/img/Situations/Situation_Table_Mafia.png new file mode 100644 index 0000000..1952b41 Binary files /dev/null and b/img/Situations/Situation_Table_Mafia.png differ diff --git a/img/Situations/Situation_Trafic_Humain.png b/img/Situations/Situation_Trafic_Humain.png new file mode 100644 index 0000000..9593d05 Binary files /dev/null and b/img/Situations/Situation_Trafic_Humain.png differ diff --git a/includes/.htaccess b/includes/.htaccess new file mode 100644 index 0000000..4128d34 --- /dev/null +++ b/includes/.htaccess @@ -0,0 +1,4 @@ + + Order Allow,Deny + Deny from All + diff --git a/includes/acp/acp_attachments.php b/includes/acp/acp_attachments.php new file mode 100644 index 0000000..5b1db5c --- /dev/null +++ b/includes/acp/acp_attachments.php @@ -0,0 +1,1732 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_attachments +{ + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\config\config */ + protected $config; + + /** @var ContainerBuilder */ + protected $phpbb_container; + + /** @var \phpbb\template\template */ + protected $template; + + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\filesystem\filesystem_interface */ + protected $filesystem; + + /** @var \phpbb\attachment\manager */ + protected $attachment_manager; + + public $id; + public $u_action; + protected $new_config; + + function main($id, $mode) + { + global $db, $user, $auth, $template, $cache, $phpbb_container, $phpbb_filesystem, $phpbb_dispatcher; + global $config, $phpbb_admin_path, $phpbb_root_path, $phpEx, $phpbb_log, $request; + + $this->id = $id; + $this->db = $db; + $this->config = $config; + $this->template = $template; + $this->user = $user; + $this->phpbb_container = $phpbb_container; + $this->filesystem = $phpbb_filesystem; + $this->attachment_manager = $phpbb_container->get('attachment.manager'); + + $user->add_lang(array('posting', 'viewtopic', 'acp/attachments')); + + $error = $notify = array(); + $submit = (isset($_POST['submit'])) ? true : false; + $action = $request->variable('action', ''); + + $form_key = 'acp_attach'; + add_form_key($form_key); + + if ($submit && !check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + switch ($mode) + { + case 'attach': + $l_title = 'ACP_ATTACHMENT_SETTINGS'; + break; + + case 'extensions': + $l_title = 'ACP_MANAGE_EXTENSIONS'; + break; + + case 'ext_groups': + $l_title = 'ACP_EXTENSION_GROUPS'; + break; + + case 'orphan': + $l_title = 'ACP_ORPHAN_ATTACHMENTS'; + break; + + case 'manage': + $l_title = 'ACP_MANAGE_ATTACHMENTS'; + break; + + default: + trigger_error('NO_MODE', E_USER_ERROR); + break; + } + + $this->tpl_name = 'acp_attachments'; + $this->page_title = $l_title; + + $template->assign_vars(array( + 'L_TITLE' => $user->lang[$l_title], + 'L_TITLE_EXPLAIN' => $user->lang[$l_title . '_EXPLAIN'], + 'U_ACTION' => $this->u_action) + ); + + switch ($mode) + { + case 'attach': + + if (!function_exists('get_supported_image_types')) + { + include($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + } + + $sql = 'SELECT group_name, cat_id + FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE cat_id > 0 + ORDER BY cat_id'; + $result = $db->sql_query($sql); + + $s_assigned_groups = array(); + while ($row = $db->sql_fetchrow($result)) + { + $row['group_name'] = (isset($user->lang['EXT_GROUP_' . $row['group_name']])) ? $user->lang['EXT_GROUP_' . $row['group_name']] : $row['group_name']; + $s_assigned_groups[$row['cat_id']][] = $row['group_name']; + } + $db->sql_freeresult($result); + + $l_legend_cat_images = $user->lang['SETTINGS_CAT_IMAGES'] . ' [' . $user->lang['ASSIGNED_GROUP'] . ': ' . ((!empty($s_assigned_groups[ATTACHMENT_CATEGORY_IMAGE])) ? implode($user->lang['COMMA_SEPARATOR'], $s_assigned_groups[ATTACHMENT_CATEGORY_IMAGE]) : $user->lang['NO_EXT_GROUP']) . ']'; + + $display_vars = array( + 'title' => 'ACP_ATTACHMENT_SETTINGS', + 'vars' => array( + 'legend1' => 'ACP_ATTACHMENT_SETTINGS', + + 'img_max_width' => array('lang' => 'MAX_IMAGE_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false,), + 'img_max_height' => array('lang' => 'MAX_IMAGE_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false,), + 'img_link_width' => array('lang' => 'IMAGE_LINK_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false,), + 'img_link_height' => array('lang' => 'IMAGE_LINK_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false,), + + 'allow_attachments' => array('lang' => 'ALLOW_ATTACHMENTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_pm_attach' => array('lang' => 'ALLOW_PM_ATTACHMENTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'upload_path' => array('lang' => 'UPLOAD_DIR', 'validate' => 'wpath', 'type' => 'text:25:100', 'explain' => true), + 'display_order' => array('lang' => 'DISPLAY_ORDER', 'validate' => 'bool', 'type' => 'custom', 'method' => 'display_order', 'explain' => true), + 'attachment_quota' => array('lang' => 'ATTACH_QUOTA', 'validate' => 'string', 'type' => 'custom', 'method' => 'max_filesize', 'explain' => true), + 'max_filesize' => array('lang' => 'ATTACH_MAX_FILESIZE', 'validate' => 'string', 'type' => 'custom', 'method' => 'max_filesize', 'explain' => true), + 'max_filesize_pm' => array('lang' => 'ATTACH_MAX_PM_FILESIZE','validate' => 'string', 'type' => 'custom', 'method' => 'max_filesize', 'explain' => true), + 'max_attachments' => array('lang' => 'MAX_ATTACHMENTS', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => false), + 'max_attachments_pm' => array('lang' => 'MAX_ATTACHMENTS_PM', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => false), + 'secure_downloads' => array('lang' => 'SECURE_DOWNLOADS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'secure_allow_deny' => array('lang' => 'SECURE_ALLOW_DENY', 'validate' => 'int', 'type' => 'custom', 'method' => 'select_allow_deny', 'explain' => true), + 'secure_allow_empty_referer' => array('lang' => 'SECURE_EMPTY_REFERRER', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'check_attachment_content' => array('lang' => 'CHECK_CONTENT', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + + 'legend2' => $l_legend_cat_images, + 'img_display_inlined' => array('lang' => 'DISPLAY_INLINED', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'img_create_thumbnail' => array('lang' => 'CREATE_THUMBNAIL', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'img_max_thumb_width' => array('lang' => 'MAX_THUMB_WIDTH', 'validate' => 'int:0:999999999999999', 'type' => 'number:0:999999999999999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + 'img_min_thumb_filesize' => array('lang' => 'MIN_THUMB_FILESIZE', 'validate' => 'int:0:999999999999999', 'type' => 'number:0:999999999999999', 'explain' => true, 'append' => ' ' . $user->lang['BYTES']), + 'img_max' => array('lang' => 'MAX_IMAGE_SIZE', 'validate' => 'int:0:9999', 'type' => 'dimension:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + 'img_link' => array('lang' => 'IMAGE_LINK_SIZE', 'validate' => 'int:0:9999', 'type' => 'dimension:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + ) + ); + + /** + * Event to add and/or modify acp_attachement configurations + * + * @event core.acp_attachments_config_edit_add + * @var array display_vars Array of config values to display and process + * @var string mode Mode of the config page we are displaying + * @var boolean submit Do we display the form or process the submission + * @since 3.1.11-RC1 + */ + $vars = array('display_vars', 'mode', 'submit'); + extract($phpbb_dispatcher->trigger_event('core.acp_attachments_config_edit_add', compact($vars))); + + $this->new_config = $config; + $cfg_array = (isset($_REQUEST['config'])) ? $request->variable('config', array('' => '')) : $this->new_config; + $error = array(); + + // We validate the complete config if whished + validate_config_vars($display_vars['vars'], $cfg_array, $error); + + // Do not write values if there is an error + if (count($error)) + { + $submit = false; + } + + // We go through the display_vars to make sure no one is trying to set variables he/she is not allowed to... + foreach ($display_vars['vars'] as $config_name => $null) + { + if (!isset($cfg_array[$config_name]) || strpos($config_name, 'legend') !== false) + { + continue; + } + + $this->new_config[$config_name] = $config_value = $cfg_array[$config_name]; + + if (in_array($config_name, array('attachment_quota', 'max_filesize', 'max_filesize_pm'))) + { + $size_var = $request->variable($config_name, ''); + $this->new_config[$config_name] = $config_value = ($size_var == 'kb') ? round($config_value * 1024) : (($size_var == 'mb') ? round($config_value * 1048576) : $config_value); + } + + if ($submit) + { + $config->set($config_name, $config_value); + } + } + + $this->perform_site_list(); + + if ($submit) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_ATTACH'); + + // Check Settings + $this->test_upload($error, $this->new_config['upload_path'], false); + + if (!count($error)) + { + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action)); + } + } + + $template->assign_var('S_ATTACHMENT_SETTINGS', true); + + // Secure Download Options - Same procedure as with banning + $allow_deny = ($this->new_config['secure_allow_deny']) ? 'ALLOWED' : 'DISALLOWED'; + + $sql = 'SELECT * + FROM ' . SITELIST_TABLE; + $result = $db->sql_query($sql); + + $defined_ips = ''; + $ips = array(); + + while ($row = $db->sql_fetchrow($result)) + { + $value = ($row['site_ip']) ? $row['site_ip'] : $row['site_hostname']; + if ($value) + { + $defined_ips .= '' . $value . ''; + $ips[$row['site_id']] = $value; + } + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'S_SECURE_DOWNLOADS' => $this->new_config['secure_downloads'], + 'S_DEFINED_IPS' => ($defined_ips != '') ? true : false, + 'S_WARNING' => (count($error)) ? true : false, + + 'WARNING_MSG' => implode('
', $error), + 'DEFINED_IPS' => $defined_ips, + + 'L_SECURE_TITLE' => $user->lang['DEFINE_' . $allow_deny . '_IPS'], + 'L_IP_EXCLUDE' => $user->lang['EXCLUDE_FROM_' . $allow_deny . '_IP'], + 'L_REMOVE_IPS' => $user->lang['REMOVE_' . $allow_deny . '_IPS']) + ); + + // Output relevant options + foreach ($display_vars['vars'] as $config_key => $vars) + { + if (!is_array($vars) && strpos($config_key, 'legend') === false) + { + continue; + } + + if (strpos($config_key, 'legend') !== false) + { + $template->assign_block_vars('options', array( + 'S_LEGEND' => true, + 'LEGEND' => (isset($user->lang[$vars])) ? $user->lang[$vars] : $vars) + ); + + continue; + } + + $type = explode(':', $vars['type']); + + $l_explain = ''; + if ($vars['explain'] && isset($vars['lang_explain'])) + { + $l_explain = (isset($user->lang[$vars['lang_explain']])) ? $user->lang[$vars['lang_explain']] : $vars['lang_explain']; + } + else if ($vars['explain']) + { + $l_explain = (isset($user->lang[$vars['lang'] . '_EXPLAIN'])) ? $user->lang[$vars['lang'] . '_EXPLAIN'] : ''; + } + + $content = build_cfg_template($type, $config_key, $this->new_config, $config_key, $vars); + if (empty($content)) + { + continue; + } + + $template->assign_block_vars('options', array( + 'KEY' => $config_key, + 'TITLE' => $user->lang[$vars['lang']], + 'S_EXPLAIN' => $vars['explain'], + 'TITLE_EXPLAIN' => $l_explain, + 'CONTENT' => $content, + ) + ); + + unset($display_vars['vars'][$config_key]); + } + + break; + + case 'extensions': + + if ($submit || isset($_POST['add_extension_check'])) + { + if ($submit) + { + // Change Extensions ? + $extension_change_list = $request->variable('extension_change_list', array(0)); + $group_select_list = $request->variable('group_select', array(0)); + + // Generate correct Change List + $extensions = array(); + + for ($i = 0, $size = count($extension_change_list); $i < $size; $i++) + { + $extensions[$extension_change_list[$i]]['group_id'] = $group_select_list[$i]; + } + + $sql = 'SELECT * + FROM ' . EXTENSIONS_TABLE . ' + ORDER BY extension_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['group_id'] != $extensions[$row['extension_id']]['group_id']) + { + $sql = 'UPDATE ' . EXTENSIONS_TABLE . ' + SET group_id = ' . (int) $extensions[$row['extension_id']]['group_id'] . ' + WHERE extension_id = ' . $row['extension_id']; + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACH_EXT_UPDATE', false, array($row['extension'])); + } + } + $db->sql_freeresult($result); + + // Delete Extension? + $extension_id_list = $request->variable('extension_id_list', array(0)); + + if (count($extension_id_list)) + { + $sql = 'SELECT extension + FROM ' . EXTENSIONS_TABLE . ' + WHERE ' . $db->sql_in_set('extension_id', $extension_id_list); + $result = $db->sql_query($sql); + + $extension_list = ''; + while ($row = $db->sql_fetchrow($result)) + { + $extension_list .= ($extension_list == '') ? $row['extension'] : ', ' . $row['extension']; + } + $db->sql_freeresult($result); + + $sql = 'DELETE + FROM ' . EXTENSIONS_TABLE . ' + WHERE ' . $db->sql_in_set('extension_id', $extension_id_list); + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACH_EXT_DEL', false, array($extension_list)); + } + } + + // Add Extension? + $add_extension = strtolower($request->variable('add_extension', '')); + $add_extension_group = $request->variable('add_group_select', 0); + $add = (isset($_POST['add_extension_check'])) ? true : false; + + if ($add_extension && $add) + { + if (!count($error)) + { + $sql = 'SELECT extension_id + FROM ' . EXTENSIONS_TABLE . " + WHERE extension = '" . $db->sql_escape($add_extension) . "'"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $error[] = sprintf($user->lang['EXTENSION_EXIST'], $add_extension); + } + $db->sql_freeresult($result); + + if (!count($error)) + { + $sql_ary = array( + 'group_id' => $add_extension_group, + 'extension' => $add_extension + ); + + $db->sql_query('INSERT INTO ' . EXTENSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACH_EXT_ADD', false, array($add_extension)); + } + } + } + + if (!count($error)) + { + $notify[] = $user->lang['EXTENSIONS_UPDATED']; + } + + $cache->destroy('_extensions'); + } + + $template->assign_vars(array( + 'S_EXTENSIONS' => true, + 'ADD_EXTENSION' => (isset($add_extension)) ? $add_extension : '', + 'GROUP_SELECT_OPTIONS' => (isset($_POST['add_extension_check'])) ? $this->group_select('add_group_select', $add_extension_group, 'extension_group') : $this->group_select('add_group_select', false, 'extension_group')) + ); + + $sql = 'SELECT * + FROM ' . EXTENSIONS_TABLE . ' + ORDER BY group_id, extension'; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $old_group_id = $row['group_id']; + do + { + $s_spacer = false; + + $current_group_id = $row['group_id']; + if ($old_group_id != $current_group_id) + { + $s_spacer = true; + $old_group_id = $current_group_id; + } + + $template->assign_block_vars('extensions', array( + 'S_SPACER' => $s_spacer, + 'EXTENSION_ID' => $row['extension_id'], + 'EXTENSION' => $row['extension'], + 'GROUP_OPTIONS' => $this->group_select('group_select[]', $row['group_id'])) + ); + } + while ($row = $db->sql_fetchrow($result)); + } + $db->sql_freeresult($result); + + break; + + case 'ext_groups': + + $template->assign_var('S_EXTENSION_GROUPS', true); + + if ($submit) + { + $action = $request->variable('action', ''); + $group_id = $request->variable('g', 0); + + if ($action != 'add' && $action != 'edit') + { + trigger_error('NO_MODE', E_USER_ERROR); + } + + if (!$group_id && $action == 'edit') + { + trigger_error($user->lang['NO_EXT_GROUP_SPECIFIED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if ($group_id) + { + $sql = 'SELECT * + FROM ' . EXTENSION_GROUPS_TABLE . " + WHERE group_id = $group_id"; + $result = $db->sql_query($sql); + $ext_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$ext_row) + { + trigger_error($user->lang['NO_EXT_GROUP_SPECIFIED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + else + { + $ext_row = array(); + } + + $group_name = $request->variable('group_name', '', true); + $new_group_name = ($action == 'add') ? $group_name : (($ext_row['group_name'] != $group_name) ? $group_name : ''); + + if (!$group_name) + { + $error[] = $user->lang['NO_EXT_GROUP_NAME']; + } + + // Check New Group Name + if ($new_group_name) + { + $sql = 'SELECT group_id + FROM ' . EXTENSION_GROUPS_TABLE . " + WHERE LOWER(group_name) = '" . $db->sql_escape(utf8_strtolower($new_group_name)) . "'"; + if ($group_id) + { + $sql .= ' AND group_id <> ' . $group_id; + } + $result = $db->sql_query($sql); + + if ($db->sql_fetchrow($result)) + { + $error[] = sprintf($user->lang['EXTENSION_GROUP_EXIST'], $new_group_name); + } + $db->sql_freeresult($result); + } + + if (!count($error)) + { + // Ok, build the update/insert array + $upload_icon = $request->variable('upload_icon', 'no_image'); + $size_select = $request->variable('size_select', 'b'); + $forum_select = $request->variable('forum_select', false); + $allowed_forums = $request->variable('allowed_forums', array(0)); + $allow_in_pm = (isset($_POST['allow_in_pm'])) ? true : false; + $max_filesize = $request->variable('max_filesize', 0); + $max_filesize = ($size_select == 'kb') ? round($max_filesize * 1024) : (($size_select == 'mb') ? round($max_filesize * 1048576) : $max_filesize); + $allow_group = (isset($_POST['allow_group'])) ? true : false; + + if ($max_filesize == $config['max_filesize']) + { + $max_filesize = 0; + } + + if (!count($allowed_forums)) + { + $forum_select = false; + } + + $group_ary = array( + 'group_name' => $group_name, + 'cat_id' => $request->variable('special_category', ATTACHMENT_CATEGORY_NONE), + 'allow_group' => ($allow_group) ? 1 : 0, + 'upload_icon' => ($upload_icon == 'no_image') ? '' : $upload_icon, + 'max_filesize' => $max_filesize, + 'allowed_forums'=> ($forum_select) ? serialize($allowed_forums) : '', + 'allow_in_pm' => ($allow_in_pm) ? 1 : 0, + ); + + if ($action == 'add') + { + $group_ary['download_mode'] = INLINE_LINK; + } + + $sql = ($action == 'add') ? 'INSERT INTO ' . EXTENSION_GROUPS_TABLE . ' ' : 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' SET '; + $sql .= $db->sql_build_array((($action == 'add') ? 'INSERT' : 'UPDATE'), $group_ary); + $sql .= ($action == 'edit') ? " WHERE group_id = $group_id" : ''; + + $db->sql_query($sql); + + if ($action == 'add') + { + $group_id = $db->sql_nextid(); + } + + $group_name = (isset($user->lang['EXT_GROUP_' . $group_name])) ? $user->lang['EXT_GROUP_' . $group_name] : $group_name; + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACH_EXTGROUP_' . strtoupper($action), false, array($group_name)); + } + + $extension_list = $request->variable('extensions', array(0)); + + if ($action == 'edit' && count($extension_list)) + { + $sql = 'UPDATE ' . EXTENSIONS_TABLE . " + SET group_id = 0 + WHERE group_id = $group_id"; + $db->sql_query($sql); + } + + if (count($extension_list)) + { + $sql = 'UPDATE ' . EXTENSIONS_TABLE . " + SET group_id = $group_id + WHERE " . $db->sql_in_set('extension_id', $extension_list); + $db->sql_query($sql); + } + + $cache->destroy('_extensions'); + + if (!count($error)) + { + $notify[] = $user->lang['SUCCESS_EXTENSION_GROUP_' . strtoupper($action)]; + } + } + + $cat_lang = array( + ATTACHMENT_CATEGORY_NONE => $user->lang['NO_FILE_CAT'], + ATTACHMENT_CATEGORY_IMAGE => $user->lang['CAT_IMAGES'], + ATTACHMENT_CATEGORY_FLASH => $user->lang['CAT_FLASH_FILES'], + ); + + $group_id = $request->variable('g', 0); + $action = (isset($_POST['add'])) ? 'add' : $action; + + switch ($action) + { + case 'delete': + + if (confirm_box(true)) + { + $sql = 'SELECT group_name + FROM ' . EXTENSION_GROUPS_TABLE . " + WHERE group_id = $group_id"; + $result = $db->sql_query($sql); + $group_name = (string) $db->sql_fetchfield('group_name'); + $db->sql_freeresult($result); + + $sql = 'DELETE + FROM ' . EXTENSION_GROUPS_TABLE . " + WHERE group_id = $group_id"; + $db->sql_query($sql); + + // Set corresponding Extensions to a pending Group + $sql = 'UPDATE ' . EXTENSIONS_TABLE . " + SET group_id = 0 + WHERE group_id = $group_id"; + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACH_EXTGROUP_DEL', false, array($group_name)); + + $cache->destroy('_extensions'); + + trigger_error($user->lang['EXTENSION_GROUP_DELETED'] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'group_id' => $group_id, + 'action' => 'delete', + ))); + } + + break; + + case 'edit': + + if (!$group_id) + { + trigger_error($user->lang['NO_EXT_GROUP_SPECIFIED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT * + FROM ' . EXTENSION_GROUPS_TABLE . " + WHERE group_id = $group_id"; + $result = $db->sql_query($sql); + $ext_group_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $forum_ids = (!$ext_group_row['allowed_forums']) ? array() : unserialize(trim($ext_group_row['allowed_forums'])); + + // no break; + + case 'add': + + if ($action == 'add') + { + $ext_group_row = array( + 'group_name' => $request->variable('group_name', '', true), + 'cat_id' => 0, + 'allow_group' => 1, + 'allow_in_pm' => 1, + 'upload_icon' => '', + 'max_filesize' => 0, + ); + + $forum_ids = array(); + } + + $sql = 'SELECT * + FROM ' . EXTENSIONS_TABLE . " + WHERE group_id = $group_id + OR group_id = 0 + ORDER BY extension"; + $result = $db->sql_query($sql); + $extensions = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + if ($ext_group_row['max_filesize'] == 0) + { + $ext_group_row['max_filesize'] = (int) $config['max_filesize']; + } + + $max_filesize = get_formatted_filesize($ext_group_row['max_filesize'], false, array('mb', 'kb', 'b')); + $size_format = $max_filesize['si_identifier']; + $ext_group_row['max_filesize'] = $max_filesize['value']; + + $img_path = $config['upload_icons_path']; + + $filename_list = ''; + $no_image_select = false; + + $imglist = filelist($phpbb_root_path . $img_path); + + if (!empty($imglist[''])) + { + $imglist = array_values($imglist); + $imglist = $imglist[0]; + + foreach ($imglist as $key => $img) + { + if (!$ext_group_row['upload_icon']) + { + $no_image_select = true; + $selected = ''; + } + else + { + $selected = ($ext_group_row['upload_icon'] == $img) ? ' selected="selected"' : ''; + } + + if (strlen($img) > 255) + { + continue; + } + + $filename_list .= ''; + } + } + + $i = 0; + $assigned_extensions = ''; + foreach ($extensions as $num => $row) + { + if ($row['group_id'] == $group_id && $group_id) + { + $assigned_extensions .= ($i) ? ', ' . $row['extension'] : $row['extension']; + $i++; + } + } + + $s_extension_options = ''; + foreach ($extensions as $row) + { + $s_extension_options .= '' . $row['extension'] . ''; + } + + $template->assign_vars(array( + 'IMG_PATH' => $img_path, + 'ACTION' => $action, + 'GROUP_ID' => $group_id, + 'GROUP_NAME' => $ext_group_row['group_name'], + 'ALLOW_GROUP' => $ext_group_row['allow_group'], + 'ALLOW_IN_PM' => $ext_group_row['allow_in_pm'], + 'UPLOAD_ICON_SRC' => $phpbb_root_path . $img_path . '/' . $ext_group_row['upload_icon'], + 'EXTGROUP_FILESIZE' => $ext_group_row['max_filesize'], + 'ASSIGNED_EXTENSIONS' => $assigned_extensions, + + 'S_CATEGORY_SELECT' => $this->category_select('special_category', $group_id, 'category'), + 'S_EXT_GROUP_SIZE_OPTIONS' => size_select_options($size_format), + 'S_EXTENSION_OPTIONS' => $s_extension_options, + 'S_FILENAME_LIST' => $filename_list, + 'S_EDIT_GROUP' => true, + 'S_NO_IMAGE' => $no_image_select, + 'S_FORUM_IDS' => (count($forum_ids)) ? true : false, + + 'U_EXTENSIONS' => append_sid("{$phpbb_admin_path}index.$phpEx", "i=$id&mode=extensions"), + 'U_BACK' => $this->u_action, + + 'L_LEGEND' => $user->lang[strtoupper($action) . '_EXTENSION_GROUP']) + ); + + $s_forum_id_options = ''; + + /** @todo use in-built function **/ + + $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id + FROM ' . FORUMS_TABLE . ' + ORDER BY left_id ASC'; + $result = $db->sql_query($sql, 600); + + $right = $cat_right = $padding_inc = 0; + $padding = $forum_list = $holding = ''; + $padding_store = array('0' => ''); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id'])) + { + // Non-postable forum with no subforums, don't display + continue; + } + + if (!$auth->acl_get('f_list', $row['forum_id'])) + { + // if the user does not have permissions to list this forum skip + continue; + } + + if ($row['left_id'] < $right) + { + $padding .= '   '; + $padding_store[$row['parent_id']] = $padding; + } + else if ($row['left_id'] > $right + 1) + { + $padding = empty($padding_store[$row['parent_id']]) ? '' : $padding_store[$row['parent_id']]; + } + + $right = $row['right_id']; + + $selected = (in_array($row['forum_id'], $forum_ids)) ? ' selected="selected"' : ''; + + if ($row['left_id'] > $cat_right) + { + // make sure we don't forget anything + $s_forum_id_options .= $holding; + $holding = ''; + } + + if ($row['right_id'] - $row['left_id'] > 1) + { + $cat_right = max($cat_right, $row['right_id']); + + $holding .= ''; + } + else + { + $s_forum_id_options .= $holding . ''; + $holding = ''; + } + } + + if ($holding) + { + $s_forum_id_options .= $holding; + } + + $db->sql_freeresult($result); + unset($padding_store); + + $template->assign_vars(array( + 'S_FORUM_ID_OPTIONS' => $s_forum_id_options) + ); + + break; + } + + $sql = 'SELECT * + FROM ' . EXTENSION_GROUPS_TABLE . ' + ORDER BY allow_group DESC, allow_in_pm DESC, group_name'; + $result = $db->sql_query($sql); + + $old_allow_group = $old_allow_pm = 1; + while ($row = $db->sql_fetchrow($result)) + { + $s_add_spacer = ($old_allow_group != $row['allow_group'] || $old_allow_pm != $row['allow_in_pm']) ? true : false; + + $template->assign_block_vars('groups', array( + 'S_ADD_SPACER' => $s_add_spacer, + 'S_ALLOWED_IN_PM' => ($row['allow_in_pm']) ? true : false, + 'S_GROUP_ALLOWED' => ($row['allow_group']) ? true : false, + + 'U_EDIT' => $this->u_action . "&action=edit&g={$row['group_id']}", + 'U_DELETE' => $this->u_action . "&action=delete&g={$row['group_id']}", + + 'GROUP_NAME' => (isset($user->lang['EXT_GROUP_' . $row['group_name']])) ? $user->lang['EXT_GROUP_' . $row['group_name']] : $row['group_name'], + 'CATEGORY' => $cat_lang[$row['cat_id']], + ) + ); + + $old_allow_group = $row['allow_group']; + $old_allow_pm = $row['allow_in_pm']; + } + $db->sql_freeresult($result); + + break; + + case 'orphan': + + /* @var $pagination \phpbb\pagination */ + $pagination = $this->phpbb_container->get('pagination'); + + if ($submit) + { + $delete_files = (isset($_POST['delete'])) ? array_keys($request->variable('delete', array('' => 0))) : array(); + $add_files = (isset($_POST['add'])) ? array_keys($request->variable('add', array('' => 0))) : array(); + $post_ids = $request->variable('post_id', array('' => 0)); + + if (count($delete_files)) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', $delete_files) . ' + AND is_orphan = 1'; + $result = $db->sql_query($sql); + + $delete_files = array(); + while ($row = $db->sql_fetchrow($result)) + { + $this->attachment_manager->unlink($row['physical_filename'], 'file'); + + if ($row['thumbnail']) + { + $this->attachment_manager->unlink($row['physical_filename'], 'thumbnail'); + } + + $delete_files[$row['attach_id']] = $row['real_filename']; + } + $db->sql_freeresult($result); + } + + if (count($delete_files)) + { + $sql = 'DELETE FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', array_keys($delete_files)); + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACH_ORPHAN_DEL', false, array(implode(', ', $delete_files))); + $notify[] = sprintf($user->lang['LOG_ATTACH_ORPHAN_DEL'], implode($user->lang['COMMA_SEPARATOR'], $delete_files)); + } + + $upload_list = array(); + foreach ($add_files as $attach_id) + { + if (!isset($delete_files[$attach_id]) && !empty($post_ids[$attach_id])) + { + $upload_list[$attach_id] = $post_ids[$attach_id]; + } + } + unset($add_files); + + if (count($upload_list)) + { + $template->assign_var('S_UPLOADING_FILES', true); + + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE; + $result = $db->sql_query($sql); + + $forum_names = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_names[$row['forum_id']] = $row['forum_name']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT forum_id, topic_id, post_id, poster_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_id', $upload_list); + $result = $db->sql_query($sql); + + $post_info = array(); + while ($row = $db->sql_fetchrow($result)) + { + $post_info[$row['post_id']] = $row; + } + $db->sql_freeresult($result); + + // Select those attachments we want to change... + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', array_keys($upload_list)) . ' + AND is_orphan = 1'; + $result = $db->sql_query($sql); + + $files_added = $space_taken = 0; + while ($row = $db->sql_fetchrow($result)) + { + $post_row = $post_info[$upload_list[$row['attach_id']]]; + + $template->assign_block_vars('upload', array( + 'FILE_INFO' => sprintf($user->lang['UPLOADING_FILE_TO'], $row['real_filename'], $post_row['post_id']), + 'S_DENIED' => (!$auth->acl_get('f_attach', $post_row['forum_id'])) ? true : false, + 'L_DENIED' => (!$auth->acl_get('f_attach', $post_row['forum_id'])) ? sprintf($user->lang['UPLOAD_DENIED_FORUM'], $forum_names[$row['forum_id']]) : '') + ); + + if (!$auth->acl_get('f_attach', $post_row['forum_id'])) + { + continue; + } + + // Adjust attachment entry + $sql_ary = array( + 'in_message' => 0, + 'is_orphan' => 0, + 'poster_id' => $post_row['poster_id'], + 'post_msg_id' => $post_row['post_id'], + 'topic_id' => $post_row['topic_id'], + ); + + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE attach_id = ' . $row['attach_id']; + $db->sql_query($sql); + + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_attachment = 1 + WHERE post_id = ' . $post_row['post_id']; + $db->sql_query($sql); + + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_attachment = 1 + WHERE topic_id = ' . $post_row['topic_id']; + $db->sql_query($sql); + + $space_taken += $row['filesize']; + $files_added++; + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACH_FILEUPLOAD', false, array($post_row['post_id'], $row['real_filename'])); + } + $db->sql_freeresult($result); + + if ($files_added) + { + $config->increment('upload_dir_size', $space_taken, false); + $config->increment('num_files', $files_added, false); + } + } + } + + $template->assign_vars(array( + 'S_ORPHAN' => true) + ); + + $attachments_per_page = (int) $config['topics_per_page']; + + // Get total number or orphans older than 3 hours + $sql = 'SELECT COUNT(attach_id) as num_files, SUM(filesize) as total_size + FROM ' . ATTACHMENTS_TABLE . ' + WHERE is_orphan = 1 + AND filetime < ' . (time() - 3*60*60); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $num_files = (int) $row['num_files']; + $total_size = (int) $row['total_size']; + $this->db->sql_freeresult($result); + + $start = $request->variable('start', 0); + $start = $pagination->validate_start($start, $attachments_per_page, $num_files); + + // Just get the files with is_orphan set and older than 3 hours + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE is_orphan = 1 + AND filetime < ' . (time() - 3*60*60) . ' + ORDER BY filetime DESC'; + $result = $db->sql_query_limit($sql, $attachments_per_page, $start); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('orphan', array( + 'FILESIZE' => get_formatted_filesize($row['filesize']), + 'FILETIME' => $user->format_date($row['filetime']), + 'REAL_FILENAME' => utf8_basename($row['real_filename']), + 'PHYSICAL_FILENAME' => utf8_basename($row['physical_filename']), + 'ATTACH_ID' => $row['attach_id'], + 'POST_IDS' => (!empty($post_ids[$row['attach_id']])) ? $post_ids[$row['attach_id']] : '', + 'U_FILE' => append_sid($phpbb_root_path . 'download/file.' . $phpEx, 'mode=view&id=' . $row['attach_id'])) + ); + } + $db->sql_freeresult($result); + + $pagination->generate_template_pagination( + $this->u_action, + 'pagination', + 'start', + $num_files, + $attachments_per_page, + $start + ); + + $template->assign_vars(array( + 'TOTAL_FILES' => $num_files, + 'TOTAL_SIZE' => get_formatted_filesize($total_size), + )); + + break; + + case 'manage': + + if ($submit) + { + $delete_files = (isset($_POST['delete'])) ? array_keys($request->variable('delete', array('' => 0))) : array(); + + if (count($delete_files)) + { + // Select those attachments we want to delete... + $sql = 'SELECT real_filename + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', $delete_files) . ' + AND is_orphan = 0'; + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) + { + $deleted_filenames[] = $row['real_filename']; + } + $db->sql_freeresult($result); + + if ($num_deleted = $this->attachment_manager->delete('attach', $delete_files)) + { + if (count($delete_files) != $num_deleted) + { + $error[] = $user->lang['FILES_GONE']; + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACHMENTS_DELETED', false, array(implode(', ', $deleted_filenames))); + $notify[] = sprintf($user->lang['LOG_ATTACHMENTS_DELETED'], implode($user->lang['COMMA_SEPARATOR'], $deleted_filenames)); + } + else + { + $error[] = $user->lang['NO_FILES_TO_DELETE']; + } + } + } + + if ($action == 'stats') + { + $this->handle_stats_resync(); + } + + $stats_error = $this->check_stats_accuracy(); + + if ($stats_error) + { + $error[] = $stats_error; + } + + $template->assign_vars(array( + 'S_MANAGE' => true, + )); + + $start = $request->variable('start', 0); + + // Sort keys + $sort_days = $request->variable('st', 0); + $sort_key = $request->variable('sk', 't'); + $sort_dir = $request->variable('sd', 'd'); + + // Sorting + $limit_days = array(0 => $user->lang['ALL_ENTRIES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('f' => $user->lang['FILENAME'], 't' => $user->lang['FILEDATE'], 's' => $user->lang['FILESIZE'], 'x' => $user->lang['EXTENSION'], 'd' => $user->lang['DOWNLOADS'],'p' => $user->lang['ATTACH_POST_TYPE'], 'u' => $user->lang['AUTHOR']); + $sort_by_sql = array('f' => 'a.real_filename', 't' => 'a.filetime', 's' => 'a.filesize', 'x' => 'a.extension', 'd' => 'a.download_count', 'p' => 'a.in_message', 'u' => 'u.username'); + + $s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param); + + $min_filetime = ($sort_days) ? (time() - ($sort_days * 86400)) : ''; + $limit_filetime = ($min_filetime) ? " AND a.filetime >= $min_filetime " : ''; + $start = ($sort_days && isset($_POST['sort'])) ? 0 : $start; + + $attachments_per_page = (int) $config['topics_per_page']; + + $stats = $this->get_attachment_stats($limit_filetime); + $num_files = $stats['num_files']; + $total_size = $stats['upload_dir_size']; + + // Make sure $start is set to the last page if it exceeds the amount + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $start = $pagination->validate_start($start, $attachments_per_page, $num_files); + + // If the user is trying to reach the second half of the attachments list, fetch it starting from the end + $store_reverse = false; + $sql_limit = $attachments_per_page; + + if ($start > $num_files / 2) + { + $store_reverse = true; + + // Select the sort order. Add time sort anchor for non-time sorting cases + $sql_sort_anchor = ($sort_key != 't') ? ', a.filetime ' . (($sort_dir == 'd') ? 'ASC' : 'DESC') : ''; + $sql_sort_order = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'ASC' : 'DESC') . $sql_sort_anchor; + $sql_limit = $pagination->reverse_limit($start, $sql_limit, $num_files); + $sql_start = $pagination->reverse_start($start, $sql_limit, $num_files); + } + else + { + // Select the sort order. Add time sort anchor for non-time sorting cases + $sql_sort_anchor = ($sort_key != 't') ? ', a.filetime ' . (($sort_dir == 'd') ? 'DESC' : 'ASC') : ''; + $sql_sort_order = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC') . $sql_sort_anchor; + $sql_start = $start; + } + + $attachments_list = array(); + + // Just get the files + $sql = 'SELECT a.*, u.username, u.user_colour, t.topic_title + FROM ' . ATTACHMENTS_TABLE . ' a + LEFT JOIN ' . USERS_TABLE . ' u ON (u.user_id = a.poster_id) + LEFT JOIN ' . TOPICS_TABLE . " t ON (a.topic_id = t.topic_id) + WHERE a.is_orphan = 0 + $limit_filetime + ORDER BY $sql_sort_order"; + $result = $db->sql_query_limit($sql, $sql_limit, $sql_start); + + $i = ($store_reverse) ? $sql_limit - 1 : 0; + + // Store increment value in a variable to save some conditional calls + $i_increment = ($store_reverse) ? -1 : 1; + while ($attachment_row = $db->sql_fetchrow($result)) + { + $attachments_list[$i] = $attachment_row; + $i = $i + $i_increment; + } + $db->sql_freeresult($result); + + $base_url = $this->u_action . "&$u_sort_param"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $num_files, $attachments_per_page, $start); + + $template->assign_vars(array( + 'TOTAL_FILES' => $num_files, + 'TOTAL_SIZE' => get_formatted_filesize($total_size), + + 'S_LIMIT_DAYS' => $s_limit_days, + 'S_SORT_KEY' => $s_sort_key, + 'S_SORT_DIR' => $s_sort_dir) + ); + + // Grab extensions + $extensions = $cache->obtain_attach_extensions(true); + + for ($i = 0, $end = count($attachments_list); $i < $end; ++$i) + { + $row = $attachments_list[$i]; + + $row['extension'] = strtolower(trim((string) $row['extension'])); + $comment = ($row['attach_comment'] && !$row['in_message']) ? str_replace(array("\n", "\r"), array('
', "\n"), $row['attach_comment']) : ''; + $display_cat = isset($extensions[$row['extension']]['display_cat']) ? $extensions[$row['extension']]['display_cat'] : ATTACHMENT_CATEGORY_NONE; + $l_downloaded_viewed = ($display_cat == ATTACHMENT_CATEGORY_NONE) ? 'DOWNLOAD_COUNTS' : 'VIEWED_COUNTS'; + + $template->assign_block_vars('attachments', array( + 'ATTACHMENT_POSTER' => get_username_string('full', (int) $row['poster_id'], (string) $row['username'], (string) $row['user_colour'], (string) $row['username']), + 'FILESIZE' => get_formatted_filesize((int) $row['filesize']), + 'FILETIME' => $user->format_date((int) $row['filetime']), + 'REAL_FILENAME' => (!$row['in_message']) ? utf8_basename((string) $row['real_filename']) : '', + 'PHYSICAL_FILENAME' => utf8_basename((string) $row['physical_filename']), + 'EXT_GROUP_NAME' => (!empty($extensions[$row['extension']]['group_name'])) ? $user->lang['EXT_GROUP_' . $extensions[$row['extension']]['group_name']] : '', + 'COMMENT' => $comment, + 'TOPIC_TITLE' => (!$row['in_message']) ? (string) $row['topic_title'] : '', + 'ATTACH_ID' => (int) $row['attach_id'], + 'POST_ID' => (int) $row['post_msg_id'], + 'TOPIC_ID' => (int) $row['topic_id'], + 'POST_IDS' => (!empty($post_ids[$row['attach_id']])) ? (int) $post_ids[$row['attach_id']] : '', + + 'L_DOWNLOAD_COUNT' => $user->lang($l_downloaded_viewed, (int) $row['download_count']), + + 'S_IN_MESSAGE' => (bool) $row['in_message'], + + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t={$row['topic_id']}&p={$row['post_msg_id']}") . "#p{$row['post_msg_id']}", + 'U_FILE' => append_sid($phpbb_root_path . 'download/file.' . $phpEx, 'mode=view&id=' . $row['attach_id'])) + ); + } + + break; + } + + if (count($error)) + { + $template->assign_vars(array( + 'S_WARNING' => true, + 'WARNING_MSG' => implode('
', $error)) + ); + } + + if (count($notify)) + { + $template->assign_vars(array( + 'S_NOTIFY' => true, + 'NOTIFY_MSG' => implode('
', $notify)) + ); + } + } + + /** + * Get attachment file count and size of upload directory + * + * @param $limit string Additional limit for WHERE clause to filter stats by. + * @return array Returns array with stats: num_files and upload_dir_size + */ + public function get_attachment_stats($limit = '') + { + $sql = 'SELECT COUNT(a.attach_id) AS num_files, SUM(a.filesize) AS upload_dir_size + FROM ' . ATTACHMENTS_TABLE . " a + WHERE a.is_orphan = 0 + $limit"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return array( + 'num_files' => (int) $row['num_files'], + 'upload_dir_size' => (float) $row['upload_dir_size'], + ); + } + + /** + * Set config attachment stat values + * + * @param $stats array Array of config key => value pairs to set. + * @return null + */ + public function set_attachment_stats($stats) + { + foreach ($stats as $key => $value) + { + $this->config->set($key, $value, true); + } + } + + /** + * Check accuracy of attachment statistics. + * + * @return bool|string Returns false if stats are correct or error message + * otherwise. + */ + public function check_stats_accuracy() + { + // Get fresh stats. + $stats = $this->get_attachment_stats(); + + // Get current files stats + $num_files = (int) $this->config['num_files']; + $total_size = (float) $this->config['upload_dir_size']; + + if (($num_files != $stats['num_files']) || ($total_size != $stats['upload_dir_size'])) + { + $u_resync = $this->u_action . '&action=stats'; + + return $this->user->lang( + 'FILES_STATS_WRONG', + (int) $stats['num_files'], + get_formatted_filesize($stats['upload_dir_size']), + '', + '' + ); + } + return false; + } + + /** + * Handle stats resync. + * + * @return null + */ + public function handle_stats_resync() + { + if (!confirm_box(true)) + { + confirm_box(false, $this->user->lang['RESYNC_FILES_STATS_CONFIRM'], build_hidden_fields(array( + 'i' => $this->id, + 'mode' => 'manage', + 'action' => 'stats', + ))); + } + else + { + $this->set_attachment_stats($this->get_attachment_stats()); + + /* @var $log \phpbb\log\log_interface */ + $log = $this->phpbb_container->get('log'); + $log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_RESYNC_FILES_STATS'); + } + + } + + /** + * Build Select for category items + */ + function category_select($select_name, $group_id = false, $key = '') + { + global $db, $user; + + $types = array( + ATTACHMENT_CATEGORY_NONE => $user->lang['NO_FILE_CAT'], + ATTACHMENT_CATEGORY_IMAGE => $user->lang['CAT_IMAGES'], + ATTACHMENT_CATEGORY_FLASH => $user->lang['CAT_FLASH_FILES'], + ); + + if ($group_id) + { + $sql = 'SELECT cat_id + FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE group_id = ' . (int) $group_id; + $result = $db->sql_query($sql); + + $cat_type = (!($row = $db->sql_fetchrow($result))) ? ATTACHMENT_CATEGORY_NONE : $row['cat_id']; + + $db->sql_freeresult($result); + } + else + { + $cat_type = ATTACHMENT_CATEGORY_NONE; + } + + $group_select = ''; + + return $group_select; + } + + /** + * Extension group select + */ + function group_select($select_name, $default_group = false, $key = '') + { + global $db, $user; + + $group_select = ''; + + return $group_select; + } + + /** + * Test Settings + */ + function test_upload(&$error, $upload_dir, $create_directory = false) + { + global $user, $phpbb_root_path; + + // Does the target directory exist, is it a directory and writable. + if ($create_directory) + { + if (!file_exists($phpbb_root_path . $upload_dir)) + { + @mkdir($phpbb_root_path . $upload_dir, 0777); + + try + { + $this->filesystem->phpbb_chmod($phpbb_root_path . $upload_dir, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + } + + if (!file_exists($phpbb_root_path . $upload_dir)) + { + $error[] = sprintf($user->lang['NO_UPLOAD_DIR'], $upload_dir); + return; + } + + if (!is_dir($phpbb_root_path . $upload_dir)) + { + $error[] = sprintf($user->lang['UPLOAD_NOT_DIR'], $upload_dir); + return; + } + + if (!$this->filesystem->is_writable($phpbb_root_path . $upload_dir)) + { + $error[] = sprintf($user->lang['NO_WRITE_UPLOAD'], $upload_dir); + return; + } + } + + /** + * Perform operations on sites for external linking + */ + function perform_site_list() + { + global $db, $user, $request, $phpbb_log; + + if (isset($_REQUEST['securesubmit'])) + { + // Grab the list of entries + $ips = $request->variable('ips', ''); + $ip_list = array_unique(explode("\n", $ips)); + $ip_list_log = implode(', ', $ip_list); + + $ip_exclude = (int) $request->variable('ipexclude', false, false, \phpbb\request\request_interface::POST); + + $iplist = array(); + $hostlist = array(); + + foreach ($ip_list as $item) + { + if (preg_match('#^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})[ ]*\-[ ]*([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$#', trim($item), $ip_range_explode)) + { + // Don't ask about all this, just don't ask ... ! + $ip_1_counter = $ip_range_explode[1]; + $ip_1_end = $ip_range_explode[5]; + + while ($ip_1_counter <= $ip_1_end) + { + $ip_2_counter = ($ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[2] : 0; + $ip_2_end = ($ip_1_counter < $ip_1_end) ? 254 : $ip_range_explode[6]; + + if ($ip_2_counter == 0 && $ip_2_end == 254) + { + $ip_2_counter = 256; + + $iplist[] = "'$ip_1_counter.*'"; + } + + while ($ip_2_counter <= $ip_2_end) + { + $ip_3_counter = ($ip_2_counter == $ip_range_explode[2] && $ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[3] : 0; + $ip_3_end = ($ip_2_counter < $ip_2_end || $ip_1_counter < $ip_1_end) ? 254 : $ip_range_explode[7]; + + if ($ip_3_counter == 0 && $ip_3_end == 254) + { + $ip_3_counter = 256; + + $iplist[] = "'$ip_1_counter.$ip_2_counter.*'"; + } + + while ($ip_3_counter <= $ip_3_end) + { + $ip_4_counter = ($ip_3_counter == $ip_range_explode[3] && $ip_2_counter == $ip_range_explode[2] && $ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[4] : 0; + $ip_4_end = ($ip_3_counter < $ip_3_end || $ip_2_counter < $ip_2_end) ? 254 : $ip_range_explode[8]; + + if ($ip_4_counter == 0 && $ip_4_end == 254) + { + $ip_4_counter = 256; + + $iplist[] = "'$ip_1_counter.$ip_2_counter.$ip_3_counter.*'"; + } + + while ($ip_4_counter <= $ip_4_end) + { + $iplist[] = "'$ip_1_counter.$ip_2_counter.$ip_3_counter.$ip_4_counter'"; + $ip_4_counter++; + } + $ip_3_counter++; + } + $ip_2_counter++; + } + $ip_1_counter++; + } + } + else if (preg_match('#^([0-9]{1,3})\.([0-9\*]{1,3})\.([0-9\*]{1,3})\.([0-9\*]{1,3})$#', trim($item)) || preg_match('#^[a-f0-9:]+\*?$#i', trim($item))) + { + $iplist[] = "'" . trim($item) . "'"; + } + else if (preg_match('#^([\w\-_]\.?){2,}$#is', trim($item))) + { + $hostlist[] = "'" . trim($item) . "'"; + } + else if (preg_match("#^([a-z0-9\-\*\._/]+?)$#is", trim($item))) + { + $hostlist[] = "'" . trim($item) . "'"; + } + } + + $sql = 'SELECT site_ip, site_hostname + FROM ' . SITELIST_TABLE . " + WHERE ip_exclude = $ip_exclude"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $iplist_tmp = array(); + $hostlist_tmp = array(); + do + { + if ($row['site_ip']) + { + if (strlen($row['site_ip']) > 40) + { + continue; + } + + $iplist_tmp[] = "'" . $row['site_ip'] . "'"; + } + else if ($row['site_hostname']) + { + if (strlen($row['site_hostname']) > 255) + { + continue; + } + + $hostlist_tmp[] = "'" . $row['site_hostname'] . "'"; + } + // break; + } + while ($row = $db->sql_fetchrow($result)); + + $iplist = array_unique(array_diff($iplist, $iplist_tmp)); + $hostlist = array_unique(array_diff($hostlist, $hostlist_tmp)); + unset($iplist_tmp); + unset($hostlist_tmp); + } + $db->sql_freeresult($result); + + if (count($iplist)) + { + foreach ($iplist as $ip_entry) + { + $sql = 'INSERT INTO ' . SITELIST_TABLE . " (site_ip, ip_exclude) + VALUES ($ip_entry, $ip_exclude)"; + $db->sql_query($sql); + } + } + + if (count($hostlist)) + { + foreach ($hostlist as $host_entry) + { + $sql = 'INSERT INTO ' . SITELIST_TABLE . " (site_hostname, ip_exclude) + VALUES ($host_entry, $ip_exclude)"; + $db->sql_query($sql); + } + } + + if (!empty($ip_list_log)) + { + // Update log + $log_entry = ($ip_exclude) ? 'LOG_DOWNLOAD_EXCLUDE_IP' : 'LOG_DOWNLOAD_IP'; + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log_entry, false, array($ip_list_log)); + } + + trigger_error($user->lang['SECURE_DOWNLOAD_UPDATE_SUCCESS'] . adm_back_link($this->u_action)); + } + else if (isset($_POST['unsecuresubmit'])) + { + $unip_sql = $request->variable('unip', array(0)); + + if (count($unip_sql)) + { + $l_unip_list = ''; + + // Grab details of ips for logging information later + $sql = 'SELECT site_ip, site_hostname + FROM ' . SITELIST_TABLE . ' + WHERE ' . $db->sql_in_set('site_id', $unip_sql); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $l_unip_list .= (($l_unip_list != '') ? ', ' : '') . (($row['site_ip']) ? $row['site_ip'] : $row['site_hostname']); + } + $db->sql_freeresult($result); + + $sql = 'DELETE FROM ' . SITELIST_TABLE . ' + WHERE ' . $db->sql_in_set('site_id', $unip_sql); + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DOWNLOAD_REMOVE_IP', false, array($l_unip_list)); + } + + trigger_error($user->lang['SECURE_DOWNLOAD_UPDATE_SUCCESS'] . adm_back_link($this->u_action)); + } + } + + /** + * Write display_order config field + */ + function display_order($value, $key = '') + { + $radio_ary = array(0 => 'DESCENDING', 1 => 'ASCENDING'); + + return h_radio('config[display_order]', $radio_ary, $value, $key); + } + + /** + * Adjust all three max_filesize config vars for display + */ + function max_filesize($value, $key = '') + { + // Determine size var and adjust the value accordingly + $filesize = get_formatted_filesize($value, false, array('mb', 'kb', 'b')); + $size_var = $filesize['si_identifier']; + $value = $filesize['value']; + + // size and maxlength must not be specified for input of type number + return ' '; + } + + /** + * Write secure_allow_deny config field + */ + function select_allow_deny($value, $key = '') + { + $radio_ary = array(1 => 'ORDER_ALLOW_DENY', 0 => 'ORDER_DENY_ALLOW'); + + return h_radio('config[' . $key . ']', $radio_ary, $value, $key); + } + +} diff --git a/includes/acp/acp_ban.php b/includes/acp/acp_ban.php new file mode 100644 index 0000000..5aed78b --- /dev/null +++ b/includes/acp/acp_ban.php @@ -0,0 +1,298 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_ban +{ + var $u_action; + + function main($id, $mode) + { + global $user, $template, $request, $phpbb_dispatcher; + global $phpbb_root_path, $phpEx; + + if (!function_exists('user_ban')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $bansubmit = $request->is_set_post('bansubmit'); + $unbansubmit = $request->is_set_post('unbansubmit'); + + $user->add_lang(array('acp/ban', 'acp/users')); + $this->tpl_name = 'acp_ban'; + $form_key = 'acp_ban'; + add_form_key($form_key); + + if (($bansubmit || $unbansubmit) && !check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Ban submitted? + if ($bansubmit) + { + // Grab the list of entries + $ban = $request->variable('ban', '', true); + $ban_length = $request->variable('banlength', 0); + $ban_length_other = $request->variable('banlengthother', ''); + $ban_exclude = $request->variable('banexclude', 0); + $ban_reason = $request->variable('banreason', '', true); + $ban_give_reason = $request->variable('bangivereason', '', true); + + if ($ban) + { + $abort_ban = false; + /** + * Use this event to modify the ban details before the ban is performed + * + * @event core.acp_ban_before + * @var string mode One of the following: user, ip, email + * @var string ban Either string or array with usernames, ips or email addresses + * @var int ban_length Ban length in minutes + * @var string ban_length_other Ban length as a date (YYYY-MM-DD) + * @var bool ban_exclude Are we banning or excluding from another ban + * @var string ban_reason Ban reason displayed to moderators + * @var string ban_give_reason Ban reason displayed to the banned user + * @var mixed abort_ban Either false, or an error message that is displayed to the user. + * If a string is given the bans are not issued. + * @since 3.1.0-RC5 + */ + $vars = array( + 'mode', + 'ban', + 'ban_length', + 'ban_length_other', + 'ban_exclude', + 'ban_reason', + 'ban_give_reason', + 'abort_ban', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_ban_before', compact($vars))); + + if ($abort_ban) + { + trigger_error($abort_ban . adm_back_link($this->u_action)); + } + user_ban($mode, $ban, $ban_length, $ban_length_other, $ban_exclude, $ban_reason, $ban_give_reason); + + /** + * Use this event to perform actions after the ban has been performed + * + * @event core.acp_ban_after + * @var string mode One of the following: user, ip, email + * @var string ban Either string or array with usernames, ips or email addresses + * @var int ban_length Ban length in minutes + * @var string ban_length_other Ban length as a date (YYYY-MM-DD) + * @var bool ban_exclude Are we banning or excluding from another ban + * @var string ban_reason Ban reason displayed to moderators + * @var string ban_give_reason Ban reason displayed to the banned user + * @since 3.1.0-RC5 + */ + $vars = array( + 'mode', + 'ban', + 'ban_length', + 'ban_length_other', + 'ban_exclude', + 'ban_reason', + 'ban_give_reason', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_ban_after', compact($vars))); + + trigger_error($user->lang['BAN_UPDATE_SUCCESSFUL'] . adm_back_link($this->u_action)); + } + } + else if ($unbansubmit) + { + $ban = $request->variable('unban', array('')); + + if ($ban) + { + user_unban($mode, $ban); + + trigger_error($user->lang['BAN_UPDATE_SUCCESSFUL'] . adm_back_link($this->u_action)); + } + } + + // Define language vars + $this->page_title = $user->lang[strtoupper($mode) . '_BAN']; + + $l_ban_explain = $user->lang[strtoupper($mode) . '_BAN_EXPLAIN']; + $l_ban_exclude_explain = $user->lang[strtoupper($mode) . '_BAN_EXCLUDE_EXPLAIN']; + $l_unban_title = $user->lang[strtoupper($mode) . '_UNBAN']; + $l_unban_explain = $user->lang[strtoupper($mode) . '_UNBAN_EXPLAIN']; + $l_no_ban_cell = $user->lang[strtoupper($mode) . '_NO_BANNED']; + + switch ($mode) + { + case 'user': + $l_ban_cell = $user->lang['USERNAME']; + break; + + case 'ip': + $l_ban_cell = $user->lang['IP_HOSTNAME']; + break; + + case 'email': + $l_ban_cell = $user->lang['EMAIL_ADDRESS']; + break; + } + + self::display_ban_options($mode); + + $template->assign_vars(array( + 'L_TITLE' => $this->page_title, + 'L_EXPLAIN' => $l_ban_explain, + 'L_UNBAN_TITLE' => $l_unban_title, + 'L_UNBAN_EXPLAIN' => $l_unban_explain, + 'L_BAN_CELL' => $l_ban_cell, + 'L_BAN_EXCLUDE_EXPLAIN' => $l_ban_exclude_explain, + 'L_NO_BAN_CELL' => $l_no_ban_cell, + + 'S_USERNAME_BAN' => ($mode == 'user') ? true : false, + + 'U_ACTION' => $this->u_action, + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=acp_ban&field=ban'), + )); + } + + /** + * Display ban options + */ + static public function display_ban_options($mode) + { + global $user, $db, $template; + + // Ban length options + $ban_end_text = array(0 => $user->lang['PERMANENT'], 30 => $user->lang['30_MINS'], 60 => $user->lang['1_HOUR'], 360 => $user->lang['6_HOURS'], 1440 => $user->lang['1_DAY'], 10080 => $user->lang['7_DAYS'], 20160 => $user->lang['2_WEEKS'], 40320 => $user->lang['1_MONTH'], -1 => $user->lang['UNTIL'] . ' -> '); + + $ban_end_options = ''; + foreach ($ban_end_text as $length => $text) + { + $ban_end_options .= ''; + } + + switch ($mode) + { + case 'user': + + $field = 'username'; + + $sql = 'SELECT b.*, u.user_id, u.username, u.username_clean + FROM ' . BANLIST_TABLE . ' b, ' . USERS_TABLE . ' u + WHERE (b.ban_end >= ' . time() . ' + OR b.ban_end = 0) + AND u.user_id = b.ban_userid + ORDER BY u.username_clean ASC'; + break; + + case 'ip': + + $field = 'ban_ip'; + + $sql = 'SELECT * + FROM ' . BANLIST_TABLE . ' + WHERE (ban_end >= ' . time() . " + OR ban_end = 0) + AND ban_ip <> '' + ORDER BY ban_ip"; + break; + + case 'email': + + $field = 'ban_email'; + + $sql = 'SELECT * + FROM ' . BANLIST_TABLE . ' + WHERE (ban_end >= ' . time() . " + OR ban_end = 0) + AND ban_email <> '' + ORDER BY ban_email"; + break; + } + $result = $db->sql_query($sql); + + $banned_options = $excluded_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + $option = ''; + + if ($row['ban_exclude']) + { + $excluded_options[] = $option; + } + else + { + $banned_options[] = $option; + } + + $time_length = ($row['ban_end']) ? ($row['ban_end'] - $row['ban_start']) / 60 : 0; + + if ($time_length == 0) + { + // Banned permanently + $ban_length = $user->lang['PERMANENT']; + } + else if (isset($ban_end_text[$time_length])) + { + // Banned for a given duration + $ban_length = $user->lang('BANNED_UNTIL_DURATION', $ban_end_text[$time_length], $user->format_date($row['ban_end'], false, true)); + } + else + { + // Banned until given date + $ban_length = $user->lang('BANNED_UNTIL_DATE', $user->format_date($row['ban_end'], false, true)); + } + + $template->assign_block_vars('bans', array( + 'BAN_ID' => (int) $row['ban_id'], + 'LENGTH' => $ban_length, + 'A_LENGTH' => addslashes($ban_length), + 'REASON' => $row['ban_reason'], + 'A_REASON' => addslashes($row['ban_reason']), + 'GIVE_REASON' => $row['ban_give_reason'], + 'A_GIVE_REASON' => addslashes($row['ban_give_reason']), + )); + } + $db->sql_freeresult($result); + + $options = ''; + if ($excluded_options) + { + $options .= ''; + $options .= implode('', $excluded_options); + $options .= ''; + } + + if ($banned_options) + { + $options .= ''; + $options .= implode('', $banned_options); + $options .= ''; + } + + $template->assign_vars(array( + 'S_BAN_END_OPTIONS' => $ban_end_options, + 'S_BANNED_OPTIONS' => ($banned_options || $excluded_options) ? true : false, + 'BANNED_OPTIONS' => $options, + )); + } +} diff --git a/includes/acp/acp_bbcodes.php b/includes/acp/acp_bbcodes.php new file mode 100644 index 0000000..1f7374a --- /dev/null +++ b/includes/acp/acp_bbcodes.php @@ -0,0 +1,620 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_bbcodes +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $template, $cache, $request, $phpbb_dispatcher, $phpbb_container; + global $phpbb_log; + + $user->add_lang('acp/posting'); + + // Set up general vars + $action = $request->variable('action', ''); + $bbcode_id = $request->variable('bbcode', 0); + $submit = $request->is_set_post('submit'); + + $this->tpl_name = 'acp_bbcodes'; + $this->page_title = 'ACP_BBCODES'; + $form_key = 'acp_bbcodes'; + + add_form_key($form_key); + + if ($submit && !check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Set up mode-specific vars + switch ($action) + { + case 'add': + $bbcode_match = $bbcode_tpl = $bbcode_helpline = ''; + $display_on_posting = 0; + break; + + case 'edit': + $sql = 'SELECT bbcode_match, bbcode_tpl, display_on_posting, bbcode_helpline + FROM ' . BBCODES_TABLE . ' + WHERE bbcode_id = ' . $bbcode_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['BBCODE_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $bbcode_match = $row['bbcode_match']; + $bbcode_tpl = htmlspecialchars($row['bbcode_tpl']); + $display_on_posting = $row['display_on_posting']; + $bbcode_helpline = $row['bbcode_helpline']; + break; + + case 'modify': + $sql = 'SELECT bbcode_id, bbcode_tag + FROM ' . BBCODES_TABLE . ' + WHERE bbcode_id = ' . $bbcode_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['BBCODE_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // No break here + + case 'create': + $display_on_posting = $request->variable('display_on_posting', 0); + + $bbcode_match = $request->variable('bbcode_match', ''); + $bbcode_tpl = htmlspecialchars_decode($request->variable('bbcode_tpl', '', true)); + $bbcode_helpline = $request->variable('bbcode_helpline', '', true); + break; + } + + // Do major work + switch ($action) + { + case 'edit': + case 'add': + + $tpl_ary = array( + 'S_EDIT_BBCODE' => true, + 'U_BACK' => $this->u_action, + 'U_ACTION' => $this->u_action . '&action=' . (($action == 'add') ? 'create' : 'modify') . (($bbcode_id) ? "&bbcode=$bbcode_id" : ''), + + 'L_BBCODE_USAGE_EXPLAIN'=> sprintf($user->lang['BBCODE_USAGE_EXPLAIN'], '', ''), + 'BBCODE_MATCH' => $bbcode_match, + 'BBCODE_TPL' => $bbcode_tpl, + 'BBCODE_HELPLINE' => $bbcode_helpline, + 'DISPLAY_ON_POSTING' => $display_on_posting, + ); + + $bbcode_tokens = array('TEXT', 'SIMPLETEXT', 'INTTEXT', 'IDENTIFIER', 'NUMBER', 'EMAIL', 'URL', 'LOCAL_URL', 'RELATIVE_URL', 'COLOR'); + + /** + * Modify custom bbcode template data before we display the add/edit form + * + * @event core.acp_bbcodes_edit_add + * @var string action Type of the action: add|edit + * @var array tpl_ary Array with custom bbcode add/edit data + * @var int bbcode_id When editing: the bbcode id, + * when creating: 0 + * @var array bbcode_tokens Array of bbcode tokens + * @since 3.1.0-a3 + */ + $vars = array('action', 'tpl_ary', 'bbcode_id', 'bbcode_tokens'); + extract($phpbb_dispatcher->trigger_event('core.acp_bbcodes_edit_add', compact($vars))); + + $template->assign_vars($tpl_ary); + + foreach ($bbcode_tokens as $token) + { + $template->assign_block_vars('token', array( + 'TOKEN' => '{' . $token . '}', + 'EXPLAIN' => ($token === 'LOCAL_URL') ? $user->lang(array('tokens', $token), generate_board_url() . '/') : $user->lang(array('tokens', $token)), + )); + } + + return; + + break; + + case 'modify': + case 'create': + + $sql_ary = $hidden_fields = array(); + + /** + * Modify custom bbcode data before the modify/create action + * + * @event core.acp_bbcodes_modify_create + * @var string action Type of the action: modify|create + * @var array sql_ary Array with new bbcode data + * @var int bbcode_id When editing: the bbcode id, + * when creating: 0 + * @var bool display_on_posting Display bbcode on posting form + * @var string bbcode_match The bbcode usage string to match + * @var string bbcode_tpl The bbcode HTML replacement string + * @var string bbcode_helpline The bbcode help line string + * @var array hidden_fields Array of hidden fields for use when + * submitting form when $warn_text is true + * @since 3.1.0-a3 + */ + $vars = array( + 'action', + 'sql_ary', + 'bbcode_id', + 'display_on_posting', + 'bbcode_match', + 'bbcode_tpl', + 'bbcode_helpline', + 'hidden_fields', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_bbcodes_modify_create', compact($vars))); + + $warn_text = preg_match('%<[^>]*\{text[\d]*\}[^>]*>%i', $bbcode_tpl); + if (!$warn_text || confirm_box(true)) + { + $data = $this->build_regexp($bbcode_match, $bbcode_tpl); + + // Make sure the user didn't pick a "bad" name for the BBCode tag. + $hard_coded = array('code', 'quote', 'quote=', 'attachment', 'attachment=', 'b', 'i', 'url', 'url=', 'img', 'size', 'size=', 'color', 'color=', 'u', 'list', 'list=', 'email', 'email=', 'flash', 'flash='); + + if (($action == 'modify' && strtolower($data['bbcode_tag']) !== strtolower($row['bbcode_tag'])) || ($action == 'create')) + { + $sql = 'SELECT 1 as test + FROM ' . BBCODES_TABLE . " + WHERE LOWER(bbcode_tag) = '" . $db->sql_escape(strtolower($data['bbcode_tag'])) . "'"; + $result = $db->sql_query($sql); + $info = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Grab the end, interrogate the last closing tag + if ($info['test'] === '1' || in_array(strtolower($data['bbcode_tag']), $hard_coded) || (preg_match('#\[/([^[]*)]$#', $bbcode_match, $regs) && in_array(strtolower($regs[1]), $hard_coded))) + { + trigger_error($user->lang['BBCODE_INVALID_TAG_NAME'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + if (substr($data['bbcode_tag'], -1) === '=') + { + $test = substr($data['bbcode_tag'], 0, -1); + } + else + { + $test = $data['bbcode_tag']; + } + + if (!preg_match('%\\[' . $test . '[^]]*].*?\\[/' . $test . ']%s', $bbcode_match)) + { + trigger_error($user->lang['BBCODE_OPEN_ENDED_TAG'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (strlen($data['bbcode_tag']) > 16) + { + trigger_error($user->lang['BBCODE_TAG_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (strlen($bbcode_match) > 4000) + { + trigger_error($user->lang['BBCODE_TAG_DEF_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (strlen($bbcode_helpline) > 255) + { + trigger_error($user->lang['BBCODE_HELPLINE_TOO_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql_ary = array_merge($sql_ary, array( + 'bbcode_tag' => $data['bbcode_tag'], + 'bbcode_match' => $bbcode_match, + 'bbcode_tpl' => $bbcode_tpl, + 'display_on_posting' => $display_on_posting, + 'bbcode_helpline' => $bbcode_helpline, + 'first_pass_match' => $data['first_pass_match'], + 'first_pass_replace' => $data['first_pass_replace'], + 'second_pass_match' => $data['second_pass_match'], + 'second_pass_replace' => $data['second_pass_replace'] + )); + + if ($action == 'create') + { + $sql = 'SELECT MAX(bbcode_id) as max_bbcode_id + FROM ' . BBCODES_TABLE; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $bbcode_id = (int) $row['max_bbcode_id'] + 1; + + // Make sure it is greater than the core bbcode ids... + if ($bbcode_id <= NUM_CORE_BBCODES) + { + $bbcode_id = NUM_CORE_BBCODES + 1; + } + } + else + { + $bbcode_id = NUM_CORE_BBCODES + 1; + } + + if ($bbcode_id > BBCODE_LIMIT) + { + trigger_error($user->lang['TOO_MANY_BBCODES'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql_ary['bbcode_id'] = (int) $bbcode_id; + + $db->sql_query('INSERT INTO ' . BBCODES_TABLE . $db->sql_build_array('INSERT', $sql_ary)); + $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); + + $lang = 'BBCODE_ADDED'; + $log_action = 'LOG_BBCODE_ADD'; + } + else + { + $sql = 'UPDATE ' . BBCODES_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE bbcode_id = ' . $bbcode_id; + $db->sql_query($sql); + $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); + + $lang = 'BBCODE_EDITED'; + $log_action = 'LOG_BBCODE_EDIT'; + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log_action, false, array($data['bbcode_tag'])); + + /** + * Event after a BBCode has been added or updated + * + * @event core.acp_bbcodes_modify_create_after + * @var string action Type of the action: modify|create + * @var int bbcode_id The id of the added or updated bbcode + * @var array sql_ary Array with bbcode data (read only) + * @since 3.2.4-RC1 + */ + $vars = array( + 'action', + 'bbcode_id', + 'sql_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_bbcodes_modify_create_after', compact($vars))); + + trigger_error($user->lang[$lang] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, $user->lang['BBCODE_DANGER'], build_hidden_fields(array_merge($hidden_fields, array( + 'action' => $action, + 'bbcode' => $bbcode_id, + 'bbcode_match' => $bbcode_match, + 'bbcode_tpl' => htmlspecialchars($bbcode_tpl), + 'bbcode_helpline' => $bbcode_helpline, + 'display_on_posting' => $display_on_posting, + ))) + , 'confirm_bbcode.html'); + } + + break; + + case 'delete': + + $sql = 'SELECT bbcode_tag + FROM ' . BBCODES_TABLE . " + WHERE bbcode_id = $bbcode_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + if (confirm_box(true)) + { + $bbcode_tag = $row['bbcode_tag']; + + $db->sql_query('DELETE FROM ' . BBCODES_TABLE . " WHERE bbcode_id = $bbcode_id"); + $cache->destroy('sql', BBCODES_TABLE); + $phpbb_container->get('text_formatter.cache')->invalidate(); + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_BBCODE_DELETE', false, array($bbcode_tag)); + + /** + * Event after a BBCode has been deleted + * + * @event core.acp_bbcodes_delete_after + * @var string action Type of the action: delete + * @var int bbcode_id The id of the deleted bbcode + * @var string bbcode_tag The tag of the deleted bbcode + * @since 3.2.4-RC1 + */ + $vars = array( + 'action', + 'bbcode_id', + 'bbcode_tag', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_bbcodes_delete_after', compact($vars))); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $user->lang['BBCODE_DELETED'], + 'REFRESH_DATA' => array( + 'time' => 3 + ) + )); + } + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'bbcode' => $bbcode_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } + } + + break; + } + + $u_action = $this->u_action; + + $template_data = array( + 'U_ACTION' => $this->u_action . '&action=add', + ); + + $sql_ary = array( + 'SELECT' => 'b.*', + 'FROM' => array(BBCODES_TABLE => 'b'), + 'ORDER_BY' => 'b.bbcode_tag', + ); + + /** + * Modify custom bbcode template data before we display the form + * + * @event core.acp_bbcodes_display_form + * @var string action Type of the action: modify|create + * @var array sql_ary The SQL array to get custom bbcode data + * @var array template_data Array with form template data + * @var string u_action The u_action link + * @since 3.1.0-a3 + */ + $vars = array('action', 'sql_ary', 'template_data', 'u_action'); + extract($phpbb_dispatcher->trigger_event('core.acp_bbcodes_display_form', compact($vars))); + + $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); + + $template->assign_vars($template_data); + + while ($row = $db->sql_fetchrow($result)) + { + $bbcodes_array = array( + 'BBCODE_TAG' => $row['bbcode_tag'], + 'U_EDIT' => $u_action . '&action=edit&bbcode=' . $row['bbcode_id'], + 'U_DELETE' => $u_action . '&action=delete&bbcode=' . $row['bbcode_id'], + ); + + /** + * Modify display of custom bbcodes in the form + * + * @event core.acp_bbcodes_display_bbcodes + * @var array row Array with current bbcode data + * @var array bbcodes_array Array of bbcodes template data + * @var string u_action The u_action link + * @since 3.1.0-a3 + */ + $vars = array('bbcodes_array', 'row', 'u_action'); + extract($phpbb_dispatcher->trigger_event('core.acp_bbcodes_display_bbcodes', compact($vars))); + + $template->assign_block_vars('bbcodes', $bbcodes_array); + + } + $db->sql_freeresult($result); + } + + /* + * Build regular expression for custom bbcode + */ + function build_regexp(&$bbcode_match, &$bbcode_tpl) + { + $bbcode_match = trim($bbcode_match); + $bbcode_tpl = trim($bbcode_tpl); + + // Allow unicode characters for URL|LOCAL_URL|RELATIVE_URL|INTTEXT tokens + $utf8 = preg_match('/(URL|LOCAL_URL|RELATIVE_URL|INTTEXT)/', $bbcode_match); + + $fp_match = preg_quote($bbcode_match, '!'); + $fp_replace = preg_replace('#^\[(.*?)\]#', '[$1:$uid]', $bbcode_match); + $fp_replace = preg_replace('#\[/(.*?)\]$#', '[/$1:$uid]', $fp_replace); + + $sp_match = preg_quote($bbcode_match, '!'); + $sp_match = preg_replace('#^\\\\\[(.*?)\\\\\]#', '\[$1:$uid\]', $sp_match); + $sp_match = preg_replace('#\\\\\[/(.*?)\\\\\]$#', '\[/$1:$uid\]', $sp_match); + $sp_replace = $bbcode_tpl; + + // @todo Make sure to change this too if something changed in message parsing + $tokens = array( + 'URL' => array( + '!(?:(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('url')) . ')|(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('www_url')) . '))!ie' => "\$this->bbcode_specialchars(('\$1') ? '\$1' : 'http://\$2')" + ), + 'LOCAL_URL' => array( + '!(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('relative_url')) . ')!e' => "\$this->bbcode_specialchars('$1')" + ), + 'RELATIVE_URL' => array( + '!(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('relative_url')) . ')!e' => "\$this->bbcode_specialchars('$1')" + ), + 'EMAIL' => array( + '!(' . get_preg_expression('email') . ')!ie' => "\$this->bbcode_specialchars('$1')" + ), + 'TEXT' => array( + '!(.*?)!es' => "str_replace(array(\"\\r\\n\", '\\\"', '\\'', '(', ')'), array(\"\\n\", '\"', ''', '(', ')'), trim('\$1'))" + ), + 'SIMPLETEXT' => array( + '!([a-zA-Z0-9-+.,_ ]+)!' => "$1" + ), + 'INTTEXT' => array( + '!([\p{L}\p{N}\-+,_. ]+)!u' => "$1" + ), + 'IDENTIFIER' => array( + '!([a-zA-Z0-9-_]+)!' => "$1" + ), + 'COLOR' => array( + '!([a-z]+|#[0-9abcdef]+)!i' => '$1' + ), + 'NUMBER' => array( + '!([0-9]+)!' => '$1' + ) + ); + + $sp_tokens = array( + 'URL' => '(?i)((?:' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('url')) . ')|(?:' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('www_url')) . '))(?-i)', + 'LOCAL_URL' => '(?i)(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('relative_url')) . ')(?-i)', + 'RELATIVE_URL' => '(?i)(' . str_replace(array('!', '\#'), array('\!', '#'), get_preg_expression('relative_url')) . ')(?-i)', + 'EMAIL' => '(' . get_preg_expression('email') . ')', + 'TEXT' => '(.*?)', + 'SIMPLETEXT' => '([a-zA-Z0-9-+.,_ ]+)', + 'INTTEXT' => '([\p{L}\p{N}\-+,_. ]+)', + 'IDENTIFIER' => '([a-zA-Z0-9-_]+)', + 'COLOR' => '([a-zA-Z]+|#[0-9abcdefABCDEF]+)', + 'NUMBER' => '([0-9]+)', + ); + + $pad = 0; + $modifiers = 'i'; + $modifiers .= ($utf8) ? 'u' : ''; + + if (preg_match_all('/\{(' . implode('|', array_keys($tokens)) . ')[0-9]*\}/i', $bbcode_match, $m)) + { + foreach ($m[0] as $n => $token) + { + $token_type = $m[1][$n]; + + reset($tokens[strtoupper($token_type)]); + list($match, $replace) = each($tokens[strtoupper($token_type)]); + + // Pad backreference numbers from tokens + if (preg_match_all('/(?lang['BBCODE_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $fp_match = preg_replace_callback('#\[/?' . $bbcode_search . '#i', function ($match) { + return strtolower($match[0]); + }, $fp_match); + $fp_replace = preg_replace_callback('#\[/?' . $bbcode_search . '#i', function ($match) { + return strtolower($match[0]); + }, $fp_replace); + $sp_match = preg_replace_callback('#\[/?' . $bbcode_search . '#i', function ($match) { + return strtolower($match[0]); + }, $sp_match); + $sp_replace = preg_replace_callback('#\[/?' . $bbcode_search . '#i', function ($match) { + return strtolower($match[0]); + }, $sp_replace); + + return array( + 'bbcode_tag' => $bbcode_tag, + 'first_pass_match' => $fp_match, + 'first_pass_replace' => $fp_replace, + 'second_pass_match' => $sp_match, + 'second_pass_replace' => $sp_replace + ); + } +} diff --git a/includes/acp/acp_board.php b/includes/acp/acp_board.php new file mode 100644 index 0000000..e348c76 --- /dev/null +++ b/includes/acp/acp_board.php @@ -0,0 +1,1178 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @todo add cron intervals to server settings? (database_gc, queue_interval, session_gc, search_gc, cache_gc, warnings_gc) +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_board +{ + var $u_action; + var $new_config; + + function main($id, $mode) + { + global $user, $template, $request, $language; + global $config, $phpbb_root_path, $phpEx; + global $cache, $phpbb_container, $phpbb_dispatcher, $phpbb_log; + + /** @var \phpbb\language\language $language Language object */ + $language = $phpbb_container->get('language'); + + $user->add_lang('acp/board'); + + $submit = (isset($_POST['submit']) || isset($_POST['allow_quick_reply_enable'])) ? true : false; + + $form_key = 'acp_board'; + add_form_key($form_key); + + /** + * Validation types are: + * string, int, bool, + * script_path (absolute path in url - beginning with / and no trailing slash), + * rpath (relative), rwpath (realtive, writable), path (relative path, but able to escape the root), wpath (writable) + */ + switch ($mode) + { + case 'settings': + $display_vars = array( + 'title' => 'ACP_BOARD_SETTINGS', + 'vars' => array( + 'legend1' => 'ACP_BOARD_SETTINGS', + 'sitename' => array('lang' => 'SITE_NAME', 'validate' => 'string', 'type' => 'text:40:255', 'explain' => false), + 'site_desc' => array('lang' => 'SITE_DESC', 'validate' => 'string', 'type' => 'text:40:255', 'explain' => false), + 'site_home_url' => array('lang' => 'SITE_HOME_URL', 'validate' => 'url', 'type' => 'url:40:255', 'explain' => true), + 'site_home_text' => array('lang' => 'SITE_HOME_TEXT', 'validate' => 'string', 'type' => 'text:40:255', 'explain' => true), + 'board_index_text' => array('lang' => 'BOARD_INDEX_TEXT', 'validate' => 'string', 'type' => 'text:40:255', 'explain' => true), + 'board_disable' => array('lang' => 'DISABLE_BOARD', 'validate' => 'bool', 'type' => 'custom', 'method' => 'board_disable', 'explain' => true), + 'board_disable_msg' => false, + 'default_lang' => array('lang' => 'DEFAULT_LANGUAGE', 'validate' => 'lang', 'type' => 'select', 'function' => 'language_select', 'params' => array('{CONFIG_VALUE}'), 'explain' => false), + 'default_dateformat' => array('lang' => 'DEFAULT_DATE_FORMAT', 'validate' => 'string', 'type' => 'custom', 'method' => 'dateformat_select', 'explain' => true), + 'board_timezone' => array('lang' => 'SYSTEM_TIMEZONE', 'validate' => 'timezone', 'type' => 'custom', 'method' => 'timezone_select', 'explain' => true), + + 'legend2' => 'BOARD_STYLE', + 'default_style' => array('lang' => 'DEFAULT_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array('{CONFIG_VALUE}', false), 'explain' => true), + 'guest_style' => array('lang' => 'GUEST_STYLE', 'validate' => 'int', 'type' => 'select', 'function' => 'style_select', 'params' => array($this->guest_style_get(), false), 'explain' => true), + 'override_user_style' => array('lang' => 'OVERRIDE_STYLE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + + 'legend3' => 'WARNINGS', + 'warnings_expire_days' => array('lang' => 'WARNINGS_EXPIRE', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['DAYS']), + + 'legend4' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + case 'features': + $display_vars = array( + 'title' => 'ACP_BOARD_FEATURES', + 'vars' => array( + 'legend1' => 'ACP_BOARD_FEATURES', + 'allow_privmsg' => array('lang' => 'BOARD_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_topic_notify' => array('lang' => 'ALLOW_TOPIC_NOTIFY', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_forum_notify' => array('lang' => 'ALLOW_FORUM_NOTIFY', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_namechange' => array('lang' => 'ALLOW_NAME_CHANGE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_attachments' => array('lang' => 'ALLOW_ATTACHMENTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_pm_attach' => array('lang' => 'ALLOW_PM_ATTACHMENTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_pm_report' => array('lang' => 'ALLOW_PM_REPORT', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_bbcode' => array('lang' => 'ALLOW_BBCODE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_smilies' => array('lang' => 'ALLOW_SMILIES', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_sig' => array('lang' => 'ALLOW_SIG', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_board_notifications' => array('lang' => 'ALLOW_BOARD_NOTIFICATIONS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_nocensors' => array('lang' => 'ALLOW_NO_CENSORS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_bookmarks' => array('lang' => 'ALLOW_BOOKMARKS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_birthdays' => array('lang' => 'ALLOW_BIRTHDAYS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'display_last_subject' => array('lang' => 'DISPLAY_LAST_SUBJECT', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_quick_reply' => array('lang' => 'ALLOW_QUICK_REPLY', 'validate' => 'bool', 'type' => 'custom', 'method' => 'quick_reply', 'explain' => true), + + 'legend2' => 'ACP_LOAD_SETTINGS', + 'load_birthdays' => array('lang' => 'YES_BIRTHDAYS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_moderators' => array('lang' => 'YES_MODERATORS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_jumpbox' => array('lang' => 'YES_JUMPBOX', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_cpf_memberlist' => array('lang' => 'LOAD_CPF_MEMBERLIST', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_cpf_pm' => array('lang' => 'LOAD_CPF_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_cpf_viewprofile' => array('lang' => 'LOAD_CPF_VIEWPROFILE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_cpf_viewtopic' => array('lang' => 'LOAD_CPF_VIEWTOPIC', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + + 'legend3' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + case 'avatar': + /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ + $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); + $avatar_drivers = $phpbb_avatar_manager->get_all_drivers(); + + $avatar_vars = array(); + foreach ($avatar_drivers as $current_driver) + { + /** @var \phpbb\avatar\driver\driver_interface $driver */ + $driver = $phpbb_avatar_manager->get_driver($current_driver, false); + + /* + * First grab the settings for enabling/disabling the avatar + * driver and afterwards grab additional settings the driver + * might have. + */ + $avatar_vars += $phpbb_avatar_manager->get_avatar_settings($driver); + $avatar_vars += $driver->prepare_form_acp($user); + } + + $display_vars = array( + 'title' => 'ACP_AVATAR_SETTINGS', + 'vars' => array( + 'legend1' => 'ACP_AVATAR_SETTINGS', + + 'avatar_min_width' => array('lang' => 'MIN_AVATAR_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false), + 'avatar_min_height' => array('lang' => 'MIN_AVATAR_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false), + 'avatar_max_width' => array('lang' => 'MAX_AVATAR_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false), + 'avatar_max_height' => array('lang' => 'MAX_AVATAR_SIZE', 'validate' => 'int:0', 'type' => false, 'method' => false, 'explain' => false), + + 'allow_avatar' => array('lang' => 'ALLOW_AVATARS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'avatar_min' => array('lang' => 'MIN_AVATAR_SIZE', 'validate' => 'int:0', 'type' => 'dimension:0', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + 'avatar_max' => array('lang' => 'MAX_AVATAR_SIZE', 'validate' => 'int:0', 'type' => 'dimension:0', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + ) + ); + + if (!empty($avatar_vars)) + { + $display_vars['vars'] += $avatar_vars; + } + break; + + case 'message': + $display_vars = array( + 'title' => 'ACP_MESSAGE_SETTINGS', + 'lang' => 'ucp', + 'vars' => array( + 'legend1' => 'GENERAL_SETTINGS', + 'allow_privmsg' => array('lang' => 'BOARD_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'pm_max_boxes' => array('lang' => 'BOXES_MAX', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'pm_max_msgs' => array('lang' => 'BOXES_LIMIT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'full_folder_action' => array('lang' => 'FULL_FOLDER_ACTION', 'validate' => 'int', 'type' => 'select', 'method' => 'full_folder_select', 'explain' => true), + 'pm_edit_time' => array('lang' => 'PM_EDIT_TIME', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true, 'append' => ' ' . $user->lang['MINUTES']), + 'pm_max_recipients' => array('lang' => 'PM_MAX_RECIPIENTS', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true), + + 'legend2' => 'GENERAL_OPTIONS', + 'allow_mass_pm' => array('lang' => 'ALLOW_MASS_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'auth_bbcode_pm' => array('lang' => 'ALLOW_BBCODE_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'auth_smilies_pm' => array('lang' => 'ALLOW_SMILIES_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_pm_attach' => array('lang' => 'ALLOW_PM_ATTACHMENTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_sig_pm' => array('lang' => 'ALLOW_SIG_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'print_pm' => array('lang' => 'ALLOW_PRINT_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'forward_pm' => array('lang' => 'ALLOW_FORWARD_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'auth_img_pm' => array('lang' => 'ALLOW_IMG_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'auth_flash_pm' => array('lang' => 'ALLOW_FLASH_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'enable_pm_icons' => array('lang' => 'ENABLE_PM_ICONS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + + 'legend3' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + case 'post': + $display_vars = array( + 'title' => 'ACP_POST_SETTINGS', + 'vars' => array( + 'legend1' => 'GENERAL_OPTIONS', + 'allow_topic_notify' => array('lang' => 'ALLOW_TOPIC_NOTIFY', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_forum_notify' => array('lang' => 'ALLOW_FORUM_NOTIFY', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_bbcode' => array('lang' => 'ALLOW_BBCODE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_post_flash' => array('lang' => 'ALLOW_POST_FLASH', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_smilies' => array('lang' => 'ALLOW_SMILIES', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_post_links' => array('lang' => 'ALLOW_POST_LINKS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allowed_schemes_links' => array('lang' => 'ALLOWED_SCHEMES_LINKS', 'validate' => 'string', 'type' => 'text:0:255', 'explain' => true), + 'allow_nocensors' => array('lang' => 'ALLOW_NO_CENSORS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_bookmarks' => array('lang' => 'ALLOW_BOOKMARKS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'enable_post_confirm' => array('lang' => 'VISUAL_CONFIRM_POST', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_quick_reply' => array('lang' => 'ALLOW_QUICK_REPLY', 'validate' => 'bool', 'type' => 'custom', 'method' => 'quick_reply', 'explain' => true), + + 'legend2' => 'POSTING', + 'bump_type' => false, + 'edit_time' => array('lang' => 'EDIT_TIME', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true, 'append' => ' ' . $user->lang['MINUTES']), + 'delete_time' => array('lang' => 'DELETE_TIME', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true, 'append' => ' ' . $user->lang['MINUTES']), + 'display_last_edited' => array('lang' => 'DISPLAY_LAST_EDITED', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'flood_interval' => array('lang' => 'FLOOD_INTERVAL', 'validate' => 'int:0:9999999999', 'type' => 'number:0:9999999999', 'explain' => true, 'append' => ' ' . $user->lang['SECONDS']), + 'bump_interval' => array('lang' => 'BUMP_INTERVAL', 'validate' => 'int:0', 'type' => 'custom', 'method' => 'bump_interval', 'explain' => true), + 'topics_per_page' => array('lang' => 'TOPICS_PER_PAGE', 'validate' => 'int:1:9999', 'type' => 'number:1:9999', 'explain' => false), + 'posts_per_page' => array('lang' => 'POSTS_PER_PAGE', 'validate' => 'int:1:9999', 'type' => 'number:1:9999', 'explain' => false), + 'smilies_per_page' => array('lang' => 'SMILIES_PER_PAGE', 'validate' => 'int:1:9999', 'type' => 'number:1:9999', 'explain' => false), + 'hot_threshold' => array('lang' => 'HOT_THRESHOLD', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'max_poll_options' => array('lang' => 'MAX_POLL_OPTIONS', 'validate' => 'int:2:127', 'type' => 'number:2:127', 'explain' => false), + 'max_post_chars' => array('lang' => 'CHAR_LIMIT', 'validate' => 'int:0:999999', 'type' => 'number:0:999999', 'explain' => true), + 'min_post_chars' => array('lang' => 'MIN_CHAR_LIMIT', 'validate' => 'int:1:999999', 'type' => 'number:1:999999', 'explain' => true), + 'max_post_smilies' => array('lang' => 'SMILIES_LIMIT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'max_post_urls' => array('lang' => 'MAX_POST_URLS', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'max_post_font_size' => array('lang' => 'MAX_POST_FONT_SIZE', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' %'), + 'max_quote_depth' => array('lang' => 'QUOTE_DEPTH_LIMIT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'max_post_img_width' => array('lang' => 'MAX_POST_IMG_WIDTH', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + 'max_post_img_height' => array('lang' => 'MAX_POST_IMG_HEIGHT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + + 'legend3' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + case 'signature': + $display_vars = array( + 'title' => 'ACP_SIGNATURE_SETTINGS', + 'vars' => array( + 'legend1' => 'GENERAL_OPTIONS', + 'allow_sig' => array('lang' => 'ALLOW_SIG', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_sig_bbcode' => array('lang' => 'ALLOW_SIG_BBCODE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_sig_img' => array('lang' => 'ALLOW_SIG_IMG', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_sig_flash' => array('lang' => 'ALLOW_SIG_FLASH', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_sig_smilies' => array('lang' => 'ALLOW_SIG_SMILIES', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_sig_links' => array('lang' => 'ALLOW_SIG_LINKS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + + 'legend2' => 'GENERAL_SETTINGS', + 'max_sig_chars' => array('lang' => 'MAX_SIG_LENGTH', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'max_sig_urls' => array('lang' => 'MAX_SIG_URLS', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'max_sig_font_size' => array('lang' => 'MAX_SIG_FONT_SIZE', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' %'), + 'max_sig_smilies' => array('lang' => 'MAX_SIG_SMILIES', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'max_sig_img_width' => array('lang' => 'MAX_SIG_IMG_WIDTH', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + 'max_sig_img_height' => array('lang' => 'MAX_SIG_IMG_HEIGHT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true, 'append' => ' ' . $user->lang['PIXEL']), + + 'legend3' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + case 'registration': + $display_vars = array( + 'title' => 'ACP_REGISTER_SETTINGS', + 'vars' => array( + 'legend1' => 'GENERAL_SETTINGS', + 'max_name_chars' => array('lang' => 'USERNAME_LENGTH', 'validate' => 'int:8:180', 'type' => false, 'method' => false, 'explain' => false,), + 'max_pass_chars' => array('lang' => 'PASSWORD_LENGTH', 'validate' => 'int:8:255', 'type' => false, 'method' => false, 'explain' => false,), + + 'require_activation' => array('lang' => 'ACC_ACTIVATION', 'validate' => 'int', 'type' => 'select', 'method' => 'select_acc_activation', 'explain' => true), + 'new_member_post_limit' => array('lang' => 'NEW_MEMBER_POST_LIMIT', 'validate' => 'int:0:255', 'type' => 'number:0:255', 'explain' => true, 'append' => ' ' . $user->lang['POSTS']), + 'new_member_group_default'=> array('lang' => 'NEW_MEMBER_GROUP_DEFAULT', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'min_name_chars' => array('lang' => 'USERNAME_LENGTH', 'validate' => 'int:1', 'type' => 'custom:5:180', 'method' => 'username_length', 'explain' => true), + 'min_pass_chars' => array('lang' => 'PASSWORD_LENGTH', 'validate' => 'int:1', 'type' => 'custom', 'method' => 'password_length', 'explain' => true), + 'allow_name_chars' => array('lang' => 'USERNAME_CHARS', 'validate' => 'string', 'type' => 'select', 'method' => 'select_username_chars', 'explain' => true), + 'pass_complex' => array('lang' => 'PASSWORD_TYPE', 'validate' => 'string', 'type' => 'select', 'method' => 'select_password_chars', 'explain' => true), + 'chg_passforce' => array('lang' => 'FORCE_PASS_CHANGE', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => true, 'append' => ' ' . $user->lang['DAYS']), + + 'legend2' => 'GENERAL_OPTIONS', + 'allow_namechange' => array('lang' => 'ALLOW_NAME_CHANGE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'allow_emailreuse' => array('lang' => 'ALLOW_EMAIL_REUSE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'enable_confirm' => array('lang' => 'VISUAL_CONFIRM_REG', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'max_login_attempts' => array('lang' => 'MAX_LOGIN_ATTEMPTS', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => true), + 'max_reg_attempts' => array('lang' => 'REG_LIMIT', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + + 'legend3' => 'COPPA', + 'coppa_enable' => array('lang' => 'ENABLE_COPPA', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'coppa_mail' => array('lang' => 'COPPA_MAIL', 'validate' => 'string', 'type' => 'textarea:5:40', 'explain' => true), + 'coppa_fax' => array('lang' => 'COPPA_FAX', 'validate' => 'string', 'type' => 'text:25:100', 'explain' => false), + + 'legend4' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + case 'feed': + $display_vars = array( + 'title' => 'ACP_FEED_MANAGEMENT', + 'vars' => array( + 'legend1' => 'ACP_FEED_GENERAL', + 'feed_enable' => array('lang' => 'ACP_FEED_ENABLE', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true ), + 'feed_item_statistics' => array('lang' => 'ACP_FEED_ITEM_STATISTICS', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true), + 'feed_http_auth' => array('lang' => 'ACP_FEED_HTTP_AUTH', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true), + + 'legend2' => 'ACP_FEED_POST_BASED', + 'feed_limit_post' => array('lang' => 'ACP_FEED_LIMIT', 'validate' => 'int:5:9999', 'type' => 'number:5:9999', 'explain' => true), + 'feed_overall' => array('lang' => 'ACP_FEED_OVERALL', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true ), + 'feed_forum' => array('lang' => 'ACP_FEED_FORUM', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true ), + 'feed_topic' => array('lang' => 'ACP_FEED_TOPIC', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true ), + + 'legend3' => 'ACP_FEED_TOPIC_BASED', + 'feed_limit_topic' => array('lang' => 'ACP_FEED_LIMIT', 'validate' => 'int:5:9999', 'type' => 'number:5:9999', 'explain' => true), + 'feed_topics_new' => array('lang' => 'ACP_FEED_TOPICS_NEW', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true ), + 'feed_topics_active' => array('lang' => 'ACP_FEED_TOPICS_ACTIVE', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true ), + 'feed_news_id' => array('lang' => 'ACP_FEED_NEWS', 'validate' => 'string', 'type' => 'custom', 'method' => 'select_news_forums', 'explain' => true), + + 'legend4' => 'ACP_FEED_SETTINGS_OTHER', + 'feed_overall_forums' => array('lang' => 'ACP_FEED_OVERALL_FORUMS', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true ), + 'feed_exclude_id' => array('lang' => 'ACP_FEED_EXCLUDE_ID', 'validate' => 'string', 'type' => 'custom', 'method' => 'select_exclude_forums', 'explain' => true), + ) + ); + break; + + case 'cookie': + $display_vars = array( + 'title' => 'ACP_COOKIE_SETTINGS', + 'vars' => array( + 'legend1' => 'ACP_COOKIE_SETTINGS', + 'cookie_domain' => array('lang' => 'COOKIE_DOMAIN', 'validate' => 'string', 'type' => 'text::255', 'explain' => true), + 'cookie_name' => array('lang' => 'COOKIE_NAME', 'validate' => 'string', 'type' => 'text::16', 'explain' => true), + 'cookie_path' => array('lang' => 'COOKIE_PATH', 'validate' => 'string', 'type' => 'text::255', 'explain' => true), + 'cookie_secure' => array('lang' => 'COOKIE_SECURE', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true), + 'cookie_notice' => array('lang' => 'COOKIE_NOTICE', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true), + ) + ); + break; + + case 'load': + $display_vars = array( + 'title' => 'ACP_LOAD_SETTINGS', + 'vars' => array( + 'legend1' => 'GENERAL_SETTINGS', + 'limit_load' => array('lang' => 'LIMIT_LOAD', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'session_length' => array('lang' => 'SESSION_LENGTH', 'validate' => 'int:60:9999999999', 'type' => 'number:60:9999999999', 'explain' => true, 'append' => ' ' . $user->lang['SECONDS']), + 'active_sessions' => array('lang' => 'LIMIT_SESSIONS', 'validate' => 'int:0:9999', 'type' => 'number:0:9999', 'explain' => true), + 'load_online_time' => array('lang' => 'ONLINE_LENGTH', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => true, 'append' => ' ' . $user->lang['MINUTES']), + 'read_notification_expire_days' => array('lang' => 'READ_NOTIFICATION_EXPIRE_DAYS', 'validate' => 'int:0', 'type' => 'number:0', 'explain' => true, 'append' => ' ' . $user->lang['DAYS']), + + 'legend2' => 'GENERAL_OPTIONS', + 'load_notifications' => array('lang' => 'LOAD_NOTIFICATIONS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_db_track' => array('lang' => 'YES_POST_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_db_lastread' => array('lang' => 'YES_READ_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_anon_lastread' => array('lang' => 'YES_ANON_READ_MARKING', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_online' => array('lang' => 'YES_ONLINE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_online_guests' => array('lang' => 'YES_ONLINE_GUESTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_onlinetrack' => array('lang' => 'YES_ONLINE_TRACK', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_birthdays' => array('lang' => 'YES_BIRTHDAYS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_unreads_search' => array('lang' => 'YES_UNREAD_SEARCH', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_moderators' => array('lang' => 'YES_MODERATORS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_jumpbox' => array('lang' => 'YES_JUMPBOX', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_user_activity' => array('lang' => 'LOAD_USER_ACTIVITY', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'load_user_activity_limit' => array('lang' => 'LOAD_USER_ACTIVITY_LIMIT', 'validate' => 'int:0:99999999', 'type' => 'number:0:99999999', 'explain' => true), + 'load_tplcompile' => array('lang' => 'RECOMPILE_STYLES', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_cdn' => array('lang' => 'ALLOW_CDN', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'enable_accurate_pm_button' => array('lang' => 'YES_ACCURATE_PM_BUTTON', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_live_searches' => array('lang' => 'ALLOW_LIVE_SEARCHES', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + + 'legend3' => 'CUSTOM_PROFILE_FIELDS', + 'load_cpf_memberlist' => array('lang' => 'LOAD_CPF_MEMBERLIST', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_cpf_pm' => array('lang' => 'LOAD_CPF_PM', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_cpf_viewprofile' => array('lang' => 'LOAD_CPF_VIEWPROFILE', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + 'load_cpf_viewtopic' => array('lang' => 'LOAD_CPF_VIEWTOPIC', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => false), + + 'legend4' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + case 'auth': + $display_vars = array( + 'title' => 'ACP_AUTH_SETTINGS', + 'vars' => array( + 'legend1' => 'ACP_AUTH_SETTINGS', + 'auth_method' => array('lang' => 'AUTH_METHOD', 'validate' => 'string', 'type' => 'select:1:toggable', 'method' => 'select_auth_method', 'explain' => false), + ) + ); + break; + + case 'server': + $display_vars = array( + 'title' => 'ACP_SERVER_SETTINGS', + 'vars' => array( + 'legend1' => 'ACP_SERVER_SETTINGS', + 'gzip_compress' => array('lang' => 'ENABLE_GZIP', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'use_system_cron' => array('lang' => 'USE_SYSTEM_CRON', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + + 'legend2' => 'PATH_SETTINGS', + 'enable_mod_rewrite' => array('lang' => 'MOD_REWRITE_ENABLE', 'validate' => 'bool', 'type' => 'custom', 'method' => 'enable_mod_rewrite', 'explain' => true), + 'smilies_path' => array('lang' => 'SMILIES_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true), + 'icons_path' => array('lang' => 'ICONS_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true), + 'upload_icons_path' => array('lang' => 'UPLOAD_ICONS_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true), + 'ranks_path' => array('lang' => 'RANKS_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true), + + 'legend3' => 'SERVER_URL_SETTINGS', + 'force_server_vars' => array('lang' => 'FORCE_SERVER_VARS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'server_protocol' => array('lang' => 'SERVER_PROTOCOL', 'validate' => 'string', 'type' => 'text:10:10', 'explain' => true), + 'server_name' => array('lang' => 'SERVER_NAME', 'validate' => 'string', 'type' => 'text:40:255', 'explain' => true), + 'server_port' => array('lang' => 'SERVER_PORT', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true), + 'script_path' => array('lang' => 'SCRIPT_PATH', 'validate' => 'script_path', 'type' => 'text::255', 'explain' => true), + + 'legend4' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + case 'security': + $display_vars = array( + 'title' => 'ACP_SECURITY_SETTINGS', + 'vars' => array( + 'legend1' => 'ACP_SECURITY_SETTINGS', + 'allow_autologin' => array('lang' => 'ALLOW_AUTOLOGIN', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'allow_password_reset' => array('lang' => 'ALLOW_PASSWORD_RESET', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'max_autologin_time' => array('lang' => 'AUTOLOGIN_LENGTH', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true, 'append' => ' ' . $user->lang['DAYS']), + 'ip_check' => array('lang' => 'IP_VALID', 'validate' => 'int', 'type' => 'custom', 'method' => 'select_ip_check', 'explain' => true), + 'browser_check' => array('lang' => 'BROWSER_VALID', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'forwarded_for_check' => array('lang' => 'FORWARDED_FOR_VALID', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'referer_validation' => array('lang' => 'REFERRER_VALID', 'validate' => 'int:0:3','type' => 'custom', 'method' => 'select_ref_check', 'explain' => true), + 'remote_upload_verify' => array('lang' => 'UPLOAD_CERT_VALID', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'check_dnsbl' => array('lang' => 'CHECK_DNSBL', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'email_check_mx' => array('lang' => 'EMAIL_CHECK_MX', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'max_pass_chars' => array('lang' => 'PASSWORD_LENGTH', 'validate' => 'int:8:255', 'type' => false, 'method' => false, 'explain' => false,), + 'min_pass_chars' => array('lang' => 'PASSWORD_LENGTH', 'validate' => 'int:1', 'type' => 'custom', 'method' => 'password_length', 'explain' => true), + 'pass_complex' => array('lang' => 'PASSWORD_TYPE', 'validate' => 'string', 'type' => 'select', 'method' => 'select_password_chars', 'explain' => true), + 'chg_passforce' => array('lang' => 'FORCE_PASS_CHANGE', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => true, 'append' => ' ' . $user->lang['DAYS']), + 'max_login_attempts' => array('lang' => 'MAX_LOGIN_ATTEMPTS', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => true), + 'ip_login_limit_max' => array('lang' => 'IP_LOGIN_LIMIT_MAX', 'validate' => 'int:0:999', 'type' => 'number:0:999', 'explain' => true), + 'ip_login_limit_time' => array('lang' => 'IP_LOGIN_LIMIT_TIME', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true, 'append' => ' ' . $user->lang['SECONDS']), + 'ip_login_limit_use_forwarded' => array('lang' => 'IP_LOGIN_LIMIT_USE_FORWARDED', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'tpl_allow_php' => array('lang' => 'TPL_ALLOW_PHP', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'form_token_lifetime' => array('lang' => 'FORM_TIME_MAX', 'validate' => 'int:-1:99999', 'type' => 'number:-1:99999', 'explain' => true, 'append' => ' ' . $user->lang['SECONDS']), + 'form_token_sid_guests' => array('lang' => 'FORM_SID_GUESTS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + + ) + ); + break; + + case 'email': + $display_vars = array( + 'title' => 'ACP_EMAIL_SETTINGS', + 'vars' => array( + 'legend1' => 'GENERAL_SETTINGS', + 'email_enable' => array('lang' => 'ENABLE_EMAIL', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true), + 'board_email_form' => array('lang' => 'BOARD_EMAIL_FORM', 'validate' => 'bool', 'type' => 'radio:enabled_disabled', 'explain' => true), + 'email_package_size' => array('lang' => 'EMAIL_PACKAGE_SIZE', 'validate' => 'int:0', 'type' => 'number:0:99999', 'explain' => true), + 'board_contact' => array('lang' => 'CONTACT_EMAIL', 'validate' => 'email', 'type' => 'email:25:100', 'explain' => true), + 'board_contact_name' => array('lang' => 'CONTACT_EMAIL_NAME', 'validate' => 'string', 'type' => 'text:25:50', 'explain' => true), + 'board_email' => array('lang' => 'ADMIN_EMAIL', 'validate' => 'email', 'type' => 'email:25:100', 'explain' => true), + 'email_force_sender' => array('lang' => 'EMAIL_FORCE_SENDER', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'board_email_sig' => array('lang' => 'EMAIL_SIG', 'validate' => 'string', 'type' => 'textarea:5:30', 'explain' => true), + 'board_hide_emails' => array('lang' => 'BOARD_HIDE_EMAILS', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'send_test_email' => array('lang' => 'SEND_TEST_EMAIL', 'validate' => 'bool', 'type' => 'custom', 'method' => 'send_test_email', 'explain' => true), + + 'legend2' => 'SMTP_SETTINGS', + 'smtp_delivery' => array('lang' => 'USE_SMTP', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'smtp_host' => array('lang' => 'SMTP_SERVER', 'validate' => 'string', 'type' => 'text:25:50', 'explain' => true), + 'smtp_port' => array('lang' => 'SMTP_PORT', 'validate' => 'int:0:99999', 'type' => 'number:0:99999', 'explain' => true), + 'smtp_auth_method' => array('lang' => 'SMTP_AUTH_METHOD', 'validate' => 'string', 'type' => 'select', 'method' => 'mail_auth_select', 'explain' => true), + 'smtp_username' => array('lang' => 'SMTP_USERNAME', 'validate' => 'string', 'type' => 'text:25:255', 'explain' => true), + 'smtp_password' => array('lang' => 'SMTP_PASSWORD', 'validate' => 'string', 'type' => 'password:25:255', 'explain' => true), + 'smtp_verify_peer' => array('lang' => 'SMTP_VERIFY_PEER', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'smtp_verify_peer_name' => array('lang' => 'SMTP_VERIFY_PEER_NAME', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'smtp_allow_self_signed'=> array('lang' => 'SMTP_ALLOW_SELF_SIGNED','validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + + 'legend3' => 'ACP_SUBMIT_CHANGES', + ) + ); + break; + + default: + trigger_error('NO_MODE', E_USER_ERROR); + break; + } + + /** + * Event to add and/or modify acp_board configurations + * + * @event core.acp_board_config_edit_add + * @var array display_vars Array of config values to display and process + * @var string mode Mode of the config page we are displaying + * @var boolean submit Do we display the form or process the submission + * @since 3.1.0-a4 + */ + $vars = array('display_vars', 'mode', 'submit'); + extract($phpbb_dispatcher->trigger_event('core.acp_board_config_edit_add', compact($vars))); + + if (isset($display_vars['lang'])) + { + $user->add_lang($display_vars['lang']); + } + + $this->new_config = clone $config; + $cfg_array = (isset($_REQUEST['config'])) ? $request->variable('config', array('' => ''), true) : $this->new_config; + $error = array(); + + // We validate the complete config if wished + validate_config_vars($display_vars['vars'], $cfg_array, $error); + + if ($submit && !check_form_key($form_key)) + { + $error[] = $user->lang['FORM_INVALID']; + } + // Do not write values if there is an error + if (count($error)) + { + $submit = false; + } + + // We go through the display_vars to make sure no one is trying to set variables he/she is not allowed to... + foreach ($display_vars['vars'] as $config_name => $data) + { + if (!isset($cfg_array[$config_name]) || strpos($config_name, 'legend') !== false) + { + continue; + } + + if ($config_name == 'auth_method' || $config_name == 'feed_news_id' || $config_name == 'feed_exclude_id') + { + continue; + } + + if ($config_name == 'guest_style') + { + if (isset($cfg_array[$config_name])) + { + $this->guest_style_set($cfg_array[$config_name]); + } + continue; + } + + $this->new_config[$config_name] = $config_value = $cfg_array[$config_name]; + + if ($submit) + { + if (strpos($data['type'], 'password') === 0 && $config_value === '********') + { + // Do not update password fields if the content is ********, + // because that is the password replacement we use to not + // send the password to the output + continue; + } + $config->set($config_name, $config_value); + + if ($config_name == 'allow_quick_reply' && isset($_POST['allow_quick_reply_enable'])) + { + enable_bitfield_column_flag(FORUMS_TABLE, 'forum_flags', round(log(FORUM_FLAG_QUICK_REPLY, 2))); + } + } + } + + // Invalidate the text_formatter cache when posting options are changed + if ($mode == 'post' && $submit) + { + $phpbb_container->get('text_formatter.cache')->invalidate(); + } + + // Store news and exclude ids + if ($mode == 'feed' && $submit) + { + $cache->destroy('_feed_news_forum_ids'); + $cache->destroy('_feed_excluded_forum_ids'); + + $this->store_feed_forums(FORUM_OPTION_FEED_NEWS, 'feed_news_id'); + $this->store_feed_forums(FORUM_OPTION_FEED_EXCLUDE, 'feed_exclude_id'); + } + + if ($mode == 'auth') + { + // Retrieve a list of auth plugins and check their config values + /* @var $auth_providers \phpbb\auth\provider_collection */ + $auth_providers = $phpbb_container->get('auth.provider_collection'); + + $updated_auth_settings = false; + $old_auth_config = array(); + foreach ($auth_providers as $provider) + { + /** @var \phpbb\auth\provider\provider_interface $provider */ + if ($fields = $provider->acp()) + { + // Check if we need to create config fields for this plugin and save config when submit was pressed + foreach ($fields as $field) + { + if (!isset($config[$field])) + { + $config->set($field, ''); + } + + if (!isset($cfg_array[$field]) || strpos($field, 'legend') !== false) + { + continue; + } + + if (substr($field, -9) === '_password' && $cfg_array[$field] === '********') + { + // Do not update password fields if the content is ********, + // because that is the password replacement we use to not + // send the password to the output + continue; + } + + $old_auth_config[$field] = $this->new_config[$field]; + $config_value = $cfg_array[$field]; + $this->new_config[$field] = $config_value; + + if ($submit) + { + $updated_auth_settings = true; + $config->set($field, $config_value); + } + } + } + unset($fields); + } + + if ($submit && (($cfg_array['auth_method'] != $this->new_config['auth_method']) || $updated_auth_settings)) + { + $method = basename($cfg_array['auth_method']); + if (array_key_exists('auth.provider.' . $method, $auth_providers)) + { + $provider = $auth_providers['auth.provider.' . $method]; + if ($error = $provider->init()) + { + foreach ($old_auth_config as $config_name => $config_value) + { + $config->set($config_name, $config_value); + } + trigger_error($error . adm_back_link($this->u_action), E_USER_WARNING); + } + $config->set('auth_method', basename($cfg_array['auth_method'])); + } + else + { + trigger_error('NO_AUTH_PLUGIN', E_USER_ERROR); + } + } + } + + if ($mode == 'email' && $request->is_set_post('send_test_email')) + { + if ($config['email_enable']) + { + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $messenger = new messenger(false); + $messenger->template('test'); + $messenger->set_addresses($user->data); + $messenger->anti_abuse_headers($config, $user); + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($user->data['username']), + )); + $messenger->send(NOTIFY_EMAIL); + + trigger_error($user->lang('TEST_EMAIL_SENT') . adm_back_link($this->u_action)); + } + else + { + $user->add_lang('memberlist'); + trigger_error($user->lang('EMAIL_DISABLED') . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + if ($submit) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_' . strtoupper($mode)); + + $message = $user->lang('CONFIG_UPDATED'); + $message_type = E_USER_NOTICE; + if (!$config['email_enable'] && in_array($mode, array('email', 'registration')) && + in_array($config['require_activation'], array(USER_ACTIVATION_SELF, USER_ACTIVATION_ADMIN))) + { + $message .= '

' . $user->lang('ACC_ACTIVATION_WARNING'); + $message_type = E_USER_WARNING; + } + trigger_error($message . adm_back_link($this->u_action), $message_type); + } + + $this->tpl_name = 'acp_board'; + $this->page_title = $display_vars['title']; + + $template->assign_vars(array( + 'L_TITLE' => $user->lang[$display_vars['title']], + 'L_TITLE_EXPLAIN' => $user->lang[$display_vars['title'] . '_EXPLAIN'], + + 'S_ERROR' => (count($error)) ? true : false, + 'ERROR_MSG' => implode('
', $error), + + 'U_ACTION' => $this->u_action) + ); + + // Output relevant page + foreach ($display_vars['vars'] as $config_key => $vars) + { + if (!is_array($vars) && strpos($config_key, 'legend') === false) + { + continue; + } + + if (strpos($config_key, 'legend') !== false) + { + $template->assign_block_vars('options', array( + 'S_LEGEND' => true, + 'LEGEND' => (isset($user->lang[$vars])) ? $user->lang[$vars] : $vars) + ); + + continue; + } + + $type = explode(':', $vars['type']); + + $l_explain = ''; + if ($vars['explain'] && isset($vars['lang_explain'])) + { + $l_explain = (isset($user->lang[$vars['lang_explain']])) ? $user->lang[$vars['lang_explain']] : $vars['lang_explain']; + } + else if ($vars['explain']) + { + $l_explain = (isset($user->lang[$vars['lang'] . '_EXPLAIN'])) ? $user->lang[$vars['lang'] . '_EXPLAIN'] : ''; + } + + $content = build_cfg_template($type, $config_key, $this->new_config, $config_key, $vars); + + if (empty($content)) + { + continue; + } + + $template->assign_block_vars('options', array( + 'KEY' => $config_key, + 'TITLE' => (isset($user->lang[$vars['lang']])) ? $user->lang[$vars['lang']] : $vars['lang'], + 'S_EXPLAIN' => $vars['explain'] && !empty($l_explain), + 'TITLE_EXPLAIN' => $l_explain, + 'CONTENT' => $content, + ) + ); + + unset($display_vars['vars'][$config_key]); + } + + if ($mode == 'auth') + { + $template->assign_var('S_AUTH', true); + + foreach ($auth_providers as $provider) + { + $auth_tpl = $provider->get_acp_template($this->new_config); + if ($auth_tpl) + { + if (array_key_exists('BLOCK_VAR_NAME', $auth_tpl)) + { + foreach ($auth_tpl['BLOCK_VARS'] as $block_vars) + { + $template->assign_block_vars($auth_tpl['BLOCK_VAR_NAME'], $block_vars); + } + } + $template->assign_vars($auth_tpl['TEMPLATE_VARS']); + $template->assign_block_vars('auth_tpl', array( + 'TEMPLATE_FILE' => $auth_tpl['TEMPLATE_FILE'], + )); + } + } + } + } + + /** + * Select auth method + */ + function select_auth_method($selected_method, $key = '') + { + global $phpbb_container; + + /* @var $auth_providers \phpbb\auth\provider_collection */ + $auth_providers = $phpbb_container->get('auth.provider_collection'); + $auth_plugins = array(); + + foreach ($auth_providers as $key => $value) + { + if (!($value instanceof \phpbb\auth\provider\provider_interface)) + { + continue; + } + $auth_plugins[] = str_replace('auth.provider.', '', $key); + } + + sort($auth_plugins); + + $auth_select = ''; + foreach ($auth_plugins as $method) + { + $selected = ($selected_method == $method) ? ' selected="selected"' : ''; + $auth_select .= "'; + } + + return $auth_select; + } + + /** + * Select mail authentication method + */ + function mail_auth_select($selected_method, $key = '') + { + global $user; + + $auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP'); + $s_smtp_auth_options = ''; + + foreach ($auth_methods as $method) + { + $s_smtp_auth_options .= ''; + } + + return $s_smtp_auth_options; + } + + /** + * Select full folder action + */ + function full_folder_select($value, $key = '') + { + global $user; + + return ''; + } + + /** + * Select ip validation + */ + function select_ip_check($value, $key = '') + { + $radio_ary = array(4 => 'ALL', 3 => 'CLASS_C', 2 => 'CLASS_B', 0 => 'NO_IP_VALIDATION'); + + return h_radio('config[ip_check]', $radio_ary, $value, $key); + } + + /** + * Select referer validation + */ + function select_ref_check($value, $key = '') + { + $radio_ary = array(REFERER_VALIDATE_PATH => 'REF_PATH', REFERER_VALIDATE_HOST => 'REF_HOST', REFERER_VALIDATE_NONE => 'NO_REF_VALIDATION'); + + return h_radio('config[referer_validation]', $radio_ary, $value, $key); + } + + /** + * Select account activation method + */ + function select_acc_activation($selected_value, $value) + { + global $user, $config; + + $act_ary = array( + 'ACC_DISABLE' => array(true, USER_ACTIVATION_DISABLE), + 'ACC_NONE' => array(true, USER_ACTIVATION_NONE), + 'ACC_USER' => array($config['email_enable'], USER_ACTIVATION_SELF), + 'ACC_ADMIN' => array($config['email_enable'], USER_ACTIVATION_ADMIN), + ); + + $act_options = ''; + foreach ($act_ary as $key => $data) + { + list($available, $value) = $data; + $selected = ($selected_value == $value) ? ' selected="selected"' : ''; + $class = (!$available) ? ' class="disabled-option"' : ''; + $act_options .= ''; + } + + return $act_options; + } + + /** + * Maximum/Minimum username length + */ + function username_length($value, $key = '') + { + global $user; + + return ' ' . $user->lang['MIN_CHARS'] . '   ' . $user->lang['MAX_CHARS']; + } + + /** + * Allowed chars in usernames + */ + function select_username_chars($selected_value, $key) + { + global $user; + + $user_char_ary = array('USERNAME_CHARS_ANY', 'USERNAME_ALPHA_ONLY', 'USERNAME_ALPHA_SPACERS', 'USERNAME_LETTER_NUM', 'USERNAME_LETTER_NUM_SPACERS', 'USERNAME_ASCII'); + $user_char_options = ''; + foreach ($user_char_ary as $user_type) + { + $selected = ($selected_value == $user_type) ? ' selected="selected"' : ''; + $user_char_options .= ''; + } + + return $user_char_options; + } + + /** + * Maximum/Minimum password length + */ + function password_length($value, $key) + { + global $user; + + return ' ' . $user->lang['MIN_CHARS'] . '   ' . $user->lang['MAX_CHARS']; + } + + /** + * Required chars in passwords + */ + function select_password_chars($selected_value, $key) + { + global $user; + + $pass_type_ary = array('PASS_TYPE_ANY', 'PASS_TYPE_CASE', 'PASS_TYPE_ALPHA', 'PASS_TYPE_SYMBOL'); + $pass_char_options = ''; + foreach ($pass_type_ary as $pass_type) + { + $selected = ($selected_value == $pass_type) ? ' selected="selected"' : ''; + $pass_char_options .= ''; + } + + return $pass_char_options; + } + + /** + * Select bump interval + */ + function bump_interval($value, $key) + { + global $user; + + $s_bump_type = ''; + $types = array('m' => 'MINUTES', 'h' => 'HOURS', 'd' => 'DAYS'); + foreach ($types as $type => $lang) + { + $selected = ($this->new_config['bump_type'] == $type) ? ' selected="selected"' : ''; + $s_bump_type .= ''; + } + + return ' '; + } + + /** + * Board disable option and message + */ + function board_disable($value, $key) + { + $radio_ary = array(1 => 'YES', 0 => 'NO'); + + return h_radio('config[board_disable]', $radio_ary, $value) . '
'; + } + + /** + * Global quick reply enable/disable setting and button to enable in all forums + */ + function quick_reply($value, $key) + { + global $user; + + $radio_ary = array(1 => 'YES', 0 => 'NO'); + + return h_radio('config[allow_quick_reply]', $radio_ary, $value) . + '

'; + } + + /** + * Select guest timezone + */ + function timezone_select($value, $key) + { + global $template, $user; + + $timezone_select = phpbb_timezone_select($template, $user, $value, true); + + return ''; + } + + /** + * Get guest style + */ + public function guest_style_get() + { + global $db; + + $sql = 'SELECT user_style + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . ANONYMOUS; + $result = $db->sql_query($sql); + + $style = (int) $db->sql_fetchfield('user_style'); + $db->sql_freeresult($result); + + return $style; + } + + /** + * Set guest style + * + * @param int $style_id The style ID + */ + public function guest_style_set($style_id) + { + global $db; + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_style = ' . (int) $style_id . ' + WHERE user_id = ' . ANONYMOUS; + $db->sql_query($sql); + } + + /** + * Select default dateformat + */ + function dateformat_select($value, $key) + { + global $user, $config; + + // Let the format_date function operate with the acp values + $old_tz = $user->timezone; + try + { + $user->timezone = new DateTimeZone($config['board_timezone']); + } + catch (\Exception $e) + { + // If the board timezone is invalid, we just use the users timezone. + } + + $dateformat_options = ''; + + foreach ($user->lang['dateformats'] as $format => $null) + { + $dateformat_options .= ''; + } + + $dateformat_options .= ''; + + // Reset users date options + $user->timezone = $old_tz; + + return " + "; + } + + /** + * Select multiple forums + */ + function select_news_forums($value, $key) + { + $forum_list = make_forum_select(false, false, true, true, true, false, true); + + // Build forum options + $s_forum_options = ''; + + return $s_forum_options; + } + + function select_exclude_forums($value, $key) + { + $forum_list = make_forum_select(false, false, true, true, true, false, true); + + // Build forum options + $s_forum_options = ''; + + return $s_forum_options; + } + + function store_feed_forums($option, $key) + { + global $db, $cache, $request; + + // Get key + $values = $request->variable($key, array(0 => 0)); + + // Empty option bit for all forums + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET forum_options = forum_options - ' . (1 << $option) . ' + WHERE ' . $db->sql_bit_and('forum_options', $option, '<> 0'); + $db->sql_query($sql); + + // Already emptied for all... + if (count($values)) + { + // Set for selected forums + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET forum_options = forum_options + ' . (1 << $option) . ' + WHERE ' . $db->sql_in_set('forum_id', $values); + $db->sql_query($sql); + } + + // Empty sql cache for forums table because options changed + $cache->destroy('sql', FORUMS_TABLE); + } + + /** + * Option to enable/disable removal of 'app.php' from URLs + * + * Note that if mod_rewrite is on, URLs without app.php will still work, + * but any paths generated by the controller helper url() method will not + * contain app.php. + * + * @param int $value The current config value + * @param string $key The config key + * @return string The HTML for the form field + */ + function enable_mod_rewrite($value, $key) + { + global $user; + + // Determine whether mod_rewrite is enabled on the server + // NOTE: This only works on Apache servers on which PHP is NOT + // installed as CGI. In that case, there is no way for PHP to + // determine whether or not the Apache module is enabled. + // + // To be clear on the value of $mod_rewite: + // null = Cannot determine whether or not the server has mod_rewrite + // enabled + // false = Can determine that the server does NOT have mod_rewrite + // enabled + // true = Can determine that the server DOES have mod_rewrite_enabled + $mod_rewrite = null; + if (function_exists('apache_get_modules')) + { + $mod_rewrite = (bool) in_array('mod_rewrite', apache_get_modules()); + } + + // If $message is false, mod_rewrite is enabled. + // Otherwise, it is not and we need to: + // 1) disable the form field + // 2) make sure the config value is set to 0 + // 3) append the message to the return + $value = ($mod_rewrite === false) ? 0 : $value; + $message = $mod_rewrite === null ? 'MOD_REWRITE_INFORMATION_UNAVAILABLE' : ($mod_rewrite === false ? 'MOD_REWRITE_DISABLED' : false); + + // Let's do some friendly HTML injection if we want to disable the + // form field because h_radio() has no pretty way of doing so + $field_name = 'config[enable_mod_rewrite]' . ($message === 'MOD_REWRITE_DISABLED' ? '" disabled="disabled' : ''); + + return h_radio($field_name, array(1 => 'YES', 0 => 'NO'), $value) . + ($message !== false ? '
' . $user->lang($message) . '' : ''); + } + + function send_test_email($value, $key) + { + global $user; + + return ''; + } +} diff --git a/includes/acp/acp_bots.php b/includes/acp/acp_bots.php new file mode 100644 index 0000000..8bd357b --- /dev/null +++ b/includes/acp/acp_bots.php @@ -0,0 +1,427 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_bots +{ + var $u_action; + + function main($id, $mode) + { + global $config, $db, $user, $template, $cache, $request, $phpbb_log; + global $phpbb_root_path, $phpEx; + + $action = $request->variable('action', ''); + $submit = (isset($_POST['submit'])) ? true : false; + $mark = $request->variable('mark', array(0)); + $bot_id = $request->variable('id', 0); + + if (isset($_POST['add'])) + { + $action = 'add'; + } + + $error = array(); + + $user->add_lang('acp/bots'); + $this->tpl_name = 'acp_bots'; + $this->page_title = 'ACP_BOTS'; + $form_key = 'acp_bots'; + add_form_key($form_key); + + if ($submit && !check_form_key($form_key)) + { + $error[] = $user->lang['FORM_INVALID']; + } + + // User wants to do something, how inconsiderate of them! + switch ($action) + { + case 'activate': + if ($bot_id || count($mark)) + { + $sql_id = ($bot_id) ? " = $bot_id" : ' IN (' . implode(', ', $mark) . ')'; + + $sql = 'UPDATE ' . BOTS_TABLE . " + SET bot_active = 1 + WHERE bot_id $sql_id"; + $db->sql_query($sql); + } + + $cache->destroy('_bots'); + break; + + case 'deactivate': + if ($bot_id || count($mark)) + { + $sql_id = ($bot_id) ? " = $bot_id" : ' IN (' . implode(', ', $mark) . ')'; + + $sql = 'UPDATE ' . BOTS_TABLE . " + SET bot_active = 0 + WHERE bot_id $sql_id"; + $db->sql_query($sql); + } + + $cache->destroy('_bots'); + break; + + case 'delete': + if ($bot_id || count($mark)) + { + if (confirm_box(true)) + { + // We need to delete the relevant user, usergroup and bot entries ... + $sql_id = ($bot_id) ? " = $bot_id" : ' IN (' . implode(', ', $mark) . ')'; + + $sql = 'SELECT bot_name, user_id + FROM ' . BOTS_TABLE . " + WHERE bot_id $sql_id"; + $result = $db->sql_query($sql); + + $user_id_ary = $bot_name_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_id_ary[] = (int) $row['user_id']; + $bot_name_ary[] = $row['bot_name']; + } + $db->sql_freeresult($result); + + $db->sql_transaction('begin'); + + $sql = 'DELETE FROM ' . BOTS_TABLE . " + WHERE bot_id $sql_id"; + $db->sql_query($sql); + + if (count($user_id_ary)) + { + $_tables = array(USERS_TABLE, USER_GROUP_TABLE); + foreach ($_tables as $table) + { + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + } + } + + $db->sql_transaction('commit'); + + $cache->destroy('_bots'); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_BOT_DELETE', false, array(implode(', ', $bot_name_ary))); + trigger_error($user->lang['BOT_DELETED'] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark, + 'id' => $bot_id, + 'mode' => $mode, + 'action' => $action)) + ); + } + } + break; + + case 'edit': + case 'add': + + if (!function_exists('user_update_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $bot_row = array( + 'bot_name' => $request->variable('bot_name', '', true), + 'bot_agent' => $request->variable('bot_agent', ''), + 'bot_ip' => $request->variable('bot_ip', ''), + 'bot_active' => $request->variable('bot_active', true), + 'bot_lang' => $request->variable('bot_lang', $config['default_lang']), + 'bot_style' => $request->variable('bot_style' , $config['default_style']), + ); + + if ($submit) + { + if (!$bot_row['bot_agent'] && !$bot_row['bot_ip']) + { + $error[] = $user->lang['ERR_BOT_NO_MATCHES']; + } + + if ($bot_row['bot_ip'] && !preg_match('#^[\d\.,:]+$#', $bot_row['bot_ip'])) + { + if (!$ip_list = gethostbynamel($bot_row['bot_ip'])) + { + $error[] = $user->lang['ERR_BOT_NO_IP']; + } + else + { + $bot_row['bot_ip'] = implode(',', $ip_list); + } + } + $bot_row['bot_ip'] = str_replace(' ', '', $bot_row['bot_ip']); + + // Make sure the admin is not adding a bot with an user agent similar to his one + if ($bot_row['bot_agent'] && substr($user->data['session_browser'], 0, 149) === substr($bot_row['bot_agent'], 0, 149)) + { + $error[] = $user->lang['ERR_BOT_AGENT_MATCHES_UA']; + } + + $bot_name = false; + if ($bot_id) + { + $sql = 'SELECT u.username_clean + FROM ' . BOTS_TABLE . ' b, ' . USERS_TABLE . " u + WHERE b.bot_id = $bot_id + AND u.user_id = b.user_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$bot_row) + { + $error[] = $user->lang['NO_BOT']; + } + else + { + $bot_name = $row['username_clean']; + } + } + if (!$this->validate_botname($bot_row['bot_name'], $bot_name)) + { + $error[] = $user->lang['BOT_NAME_TAKEN']; + } + + if (!count($error)) + { + // New bot? Create a new user and group entry + if ($action == 'add') + { + $sql = 'SELECT group_id, group_colour + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'BOTS' + AND group_type = " . GROUP_SPECIAL; + $result = $db->sql_query($sql); + $group_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$group_row) + { + trigger_error($user->lang['NO_BOT_GROUP'] . adm_back_link($this->u_action . "&id=$bot_id&action=$action"), E_USER_WARNING); + } + + $user_id = user_add(array( + 'user_type' => (int) USER_IGNORE, + 'group_id' => (int) $group_row['group_id'], + 'username' => (string) $bot_row['bot_name'], + 'user_regdate' => time(), + 'user_password' => '', + 'user_colour' => (string) $group_row['group_colour'], + 'user_email' => '', + 'user_lang' => (string) $bot_row['bot_lang'], + 'user_style' => (int) $bot_row['bot_style'], + 'user_allow_massemail' => 0, + )); + + $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'user_id' => (int) $user_id, + 'bot_name' => (string) $bot_row['bot_name'], + 'bot_active' => (int) $bot_row['bot_active'], + 'bot_agent' => (string) $bot_row['bot_agent'], + 'bot_ip' => (string) $bot_row['bot_ip']) + ); + $db->sql_query($sql); + + $log = 'ADDED'; + } + else if ($bot_id) + { + $sql = 'SELECT user_id, bot_name + FROM ' . BOTS_TABLE . " + WHERE bot_id = $bot_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['NO_BOT'] . adm_back_link($this->u_action . "&id=$bot_id&action=$action"), E_USER_WARNING); + } + + $sql_ary = array( + 'user_style' => (int) $bot_row['bot_style'], + 'user_lang' => (string) $bot_row['bot_lang'], + ); + + if ($bot_row['bot_name'] !== $row['bot_name']) + { + $sql_ary['username'] = (string) $bot_row['bot_name']; + $sql_ary['username_clean'] = (string) utf8_clean_string($bot_row['bot_name']); + } + + $sql = 'UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " WHERE user_id = {$row['user_id']}"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . BOTS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', array( + 'bot_name' => (string) $bot_row['bot_name'], + 'bot_active' => (int) $bot_row['bot_active'], + 'bot_agent' => (string) $bot_row['bot_agent'], + 'bot_ip' => (string) $bot_row['bot_ip']) + ) . " WHERE bot_id = $bot_id"; + $db->sql_query($sql); + + // Updated username? + if ($bot_row['bot_name'] !== $row['bot_name']) + { + user_update_name($row['bot_name'], $bot_row['bot_name']); + } + + $log = 'UPDATED'; + } + + $cache->destroy('_bots'); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_BOT_' . $log, false, array($bot_row['bot_name'])); + trigger_error($user->lang['BOT_' . $log] . adm_back_link($this->u_action)); + + } + } + else if ($bot_id) + { + $sql = 'SELECT b.*, u.user_lang, u.user_style + FROM ' . BOTS_TABLE . ' b, ' . USERS_TABLE . " u + WHERE b.bot_id = $bot_id + AND u.user_id = b.user_id"; + $result = $db->sql_query($sql); + $bot_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$bot_row) + { + trigger_error($user->lang['NO_BOT'] . adm_back_link($this->u_action . "&id=$bot_id&action=$action"), E_USER_WARNING); + } + + $bot_row['bot_lang'] = $bot_row['user_lang']; + $bot_row['bot_style'] = $bot_row['user_style']; + unset($bot_row['user_lang'], $bot_row['user_style']); + } + + $s_active_options = ''; + $_options = array('0' => 'NO', '1' => 'YES'); + foreach ($_options as $value => $lang) + { + $selected = ($bot_row['bot_active'] == $value) ? ' selected="selected"' : ''; + $s_active_options .= ''; + } + + $style_select = style_select($bot_row['bot_style'], true); + $lang_select = language_select($bot_row['bot_lang']); + + $l_title = ($action == 'edit') ? 'EDIT' : 'ADD'; + + $template->assign_vars(array( + 'L_TITLE' => $user->lang['BOT_' . $l_title], + 'U_ACTION' => $this->u_action . "&id=$bot_id&action=$action", + 'U_BACK' => $this->u_action, + 'ERROR_MSG' => (count($error)) ? implode('
', $error) : '', + + 'BOT_NAME' => $bot_row['bot_name'], + 'BOT_IP' => $bot_row['bot_ip'], + 'BOT_AGENT' => $bot_row['bot_agent'], + + 'S_EDIT_BOT' => true, + 'S_ACTIVE_OPTIONS' => $s_active_options, + 'S_STYLE_OPTIONS' => $style_select, + 'S_LANG_OPTIONS' => $lang_select, + 'S_ERROR' => (count($error)) ? true : false, + ) + ); + + return; + + break; + } + + if ($request->is_ajax() && ($action == 'activate' || $action == 'deactivate')) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'text' => $user->lang['BOT_' . (($action == 'activate') ? 'DE' : '') . 'ACTIVATE'], + )); + } + + $s_options = ''; + $_options = array('activate' => 'BOT_ACTIVATE', 'deactivate' => 'BOT_DEACTIVATE', 'delete' => 'DELETE'); + foreach ($_options as $value => $lang) + { + $s_options .= ''; + } + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'S_BOT_OPTIONS' => $s_options) + ); + + $sql = 'SELECT b.bot_id, b.bot_name, b.bot_active, u.user_lastvisit + FROM ' . BOTS_TABLE . ' b, ' . USERS_TABLE . ' u + WHERE u.user_id = b.user_id + ORDER BY u.user_lastvisit DESC, b.bot_name ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $active_lang = (!$row['bot_active']) ? 'BOT_ACTIVATE' : 'BOT_DEACTIVATE'; + $active_value = (!$row['bot_active']) ? 'activate' : 'deactivate'; + + $template->assign_block_vars('bots', array( + 'BOT_NAME' => $row['bot_name'], + 'BOT_ID' => $row['bot_id'], + 'LAST_VISIT' => ($row['user_lastvisit']) ? $user->format_date($row['user_lastvisit']) : $user->lang['BOT_NEVER'], + + 'U_ACTIVATE_DEACTIVATE' => $this->u_action . "&id={$row['bot_id']}&action=$active_value", + 'L_ACTIVATE_DEACTIVATE' => $user->lang[$active_lang], + 'U_EDIT' => $this->u_action . "&id={$row['bot_id']}&action=edit", + 'U_DELETE' => $this->u_action . "&id={$row['bot_id']}&action=delete") + ); + } + $db->sql_freeresult($result); + } + + /** + * Validate bot name against username table + */ + function validate_botname($newname, $oldname = false) + { + global $db; + + if ($oldname && utf8_clean_string($newname) === $oldname) + { + return true; + } + + // Admins might want to use names otherwise forbidden, thus we only check for duplicates. + $sql = 'SELECT username + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($newname)) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + return ($row) ? false : true; + } +} diff --git a/includes/acp/acp_captcha.php b/includes/acp/acp_captcha.php new file mode 100644 index 0000000..b49c5ca --- /dev/null +++ b/includes/acp/acp_captcha.php @@ -0,0 +1,190 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_captcha +{ + var $u_action; + + function main($id, $mode) + { + global $user, $template, $phpbb_log, $request; + global $config, $phpbb_container; + + $user->add_lang('acp/board'); + + /* @var $factory \phpbb\captcha\factory */ + $factory = $phpbb_container->get('captcha.factory'); + $captchas = $factory->get_captcha_types(); + + $selected = $request->variable('select_captcha', $config['captcha_plugin']); + $selected = (isset($captchas['available'][$selected]) || isset($captchas['unavailable'][$selected])) ? $selected : $config['captcha_plugin']; + $configure = $request->variable('configure', false); + + // Oh, they are just here for the view + if (isset($_GET['captcha_demo'])) + { + $this->deliver_demo($selected); + } + + // Delegate + if ($configure) + { + $config_captcha = $factory->get_instance($selected); + $config_captcha->acp_page($id, $this); + } + else + { + $config_vars = array( + 'enable_confirm' => array( + 'tpl' => 'REG_ENABLE', + 'default' => false, + 'validate' => 'bool', + 'lang' => 'VISUAL_CONFIRM_REG', + ), + 'enable_post_confirm' => array( + 'tpl' => 'POST_ENABLE', + 'default' => false, + 'validate' => 'bool', + 'lang' => 'VISUAL_CONFIRM_POST', + ), + 'confirm_refresh' => array( + 'tpl' => 'CONFIRM_REFRESH', + 'default' => false, + 'validate' => 'bool', + 'lang' => 'VISUAL_CONFIRM_REFRESH', + ), + 'max_reg_attempts' => array( + 'tpl' => 'REG_LIMIT', + 'default' => 0, + 'validate' => 'int:0:99999', + 'lang' => 'REG_LIMIT', + ), + 'max_login_attempts' => array( + 'tpl' => 'MAX_LOGIN_ATTEMPTS', + 'default' => 0, + 'validate' => 'int:0:99999', + 'lang' => 'MAX_LOGIN_ATTEMPTS', + ), + ); + + $this->tpl_name = 'acp_captcha'; + $this->page_title = 'ACP_VC_SETTINGS'; + $form_key = 'acp_captcha'; + add_form_key($form_key); + + $submit = $request->variable('main_submit', false); + $error = $cfg_array = array(); + + if ($submit) + { + foreach ($config_vars as $config_var => $options) + { + $cfg_array[$config_var] = $request->variable($config_var, $options['default']); + } + validate_config_vars($config_vars, $cfg_array, $error); + + if (!check_form_key($form_key)) + { + $error[] = $user->lang['FORM_INVALID']; + } + if ($error) + { + $submit = false; + } + } + + if ($submit) + { + foreach ($cfg_array as $key => $value) + { + $config->set($key, $value); + } + + if ($selected !== $config['captcha_plugin']) + { + // sanity check + if (isset($captchas['available'][$selected])) + { + $old_captcha = $factory->get_instance($config['captcha_plugin']); + $old_captcha->uninstall(); + + $config->set('captcha_plugin', $selected); + $new_captcha = $factory->get_instance($config['captcha_plugin']); + $new_captcha->install(); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); + } + else + { + trigger_error($user->lang['CAPTCHA_UNAVAILABLE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action)); + } + else + { + $captcha_select = ''; + foreach ($captchas['available'] as $value => $title) + { + $current = ($selected !== false && $value == $selected) ? ' selected="selected"' : ''; + $captcha_select .= ''; + } + + foreach ($captchas['unavailable'] as $value => $title) + { + $current = ($selected !== false && $value == $selected) ? ' selected="selected"' : ''; + $captcha_select .= ''; + } + + $demo_captcha = $factory->get_instance($selected); + + foreach ($config_vars as $config_var => $options) + { + $template->assign_var($options['tpl'], (isset($_POST[$config_var])) ? $request->variable($config_var, $options['default']) : $config[$config_var]) ; + } + + $template->assign_vars(array( + 'CAPTCHA_PREVIEW_TPL' => $demo_captcha->get_demo_template($id), + 'S_CAPTCHA_HAS_CONFIG' => $demo_captcha->has_config(), + 'CAPTCHA_SELECT' => $captcha_select, + 'ERROR_MSG' => implode('
', $error), + + 'U_ACTION' => $this->u_action, + )); + } + } + } + + /** + * Entry point for delivering image CAPTCHAs in the ACP. + */ + function deliver_demo($selected) + { + global $phpbb_container; + + $captcha = $phpbb_container->get('captcha.factory')->get_instance($selected); + $captcha->init(CONFIRM_REG); + $captcha->execute_demo(); + + garbage_collection(); + exit_handler(); + } +} diff --git a/includes/acp/acp_contact.php b/includes/acp/acp_contact.php new file mode 100644 index 0000000..1a4d5b9 --- /dev/null +++ b/includes/acp/acp_contact.php @@ -0,0 +1,138 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* @package acp +*/ +class acp_contact +{ + public $u_action; + + public function main($id, $mode) + { + global $user, $request, $template; + global $config, $phpbb_root_path, $phpEx, $phpbb_container; + + $user->add_lang(array('acp/board', 'posting')); + + $this->tpl_name = 'acp_contact'; + $this->page_title = 'ACP_CONTACT_SETTINGS'; + $form_name = 'acp_contact'; + add_form_key($form_name); + $error = ''; + + if (!function_exists('display_custom_bbcodes')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + if (!class_exists('parse_message')) + { + include($phpbb_root_path . 'includes/message_parser.' . $phpEx); + } + + /* @var $config_text \phpbb\config\db_text */ + $config_text = $phpbb_container->get('config_text'); + + $contact_admin_data = $config_text->get_array(array( + 'contact_admin_info', + 'contact_admin_info_uid', + 'contact_admin_info_bitfield', + 'contact_admin_info_flags', + )); + + $contact_admin_info = $contact_admin_data['contact_admin_info']; + $contact_admin_info_uid = $contact_admin_data['contact_admin_info_uid']; + $contact_admin_info_bitfield= $contact_admin_data['contact_admin_info_bitfield']; + $contact_admin_info_flags = $contact_admin_data['contact_admin_info_flags']; + + if ($request->is_set_post('submit') || $request->is_set_post('preview')) + { + if (!check_form_key($form_name)) + { + $error = $user->lang('FORM_INVALID'); + } + + $contact_admin_info = $request->variable('contact_admin_info', '', true); + + generate_text_for_storage( + $contact_admin_info, + $contact_admin_info_uid, + $contact_admin_info_bitfield, + $contact_admin_info_flags, + !$request->variable('disable_bbcode', false), + !$request->variable('disable_magic_url', false), + !$request->variable('disable_smilies', false) + ); + + if (empty($error) && $request->is_set_post('submit')) + { + $config->set('contact_admin_form_enable', $request->variable('contact_admin_form_enable', false)); + + $config_text->set_array(array( + 'contact_admin_info' => $contact_admin_info, + 'contact_admin_info_uid' => $contact_admin_info_uid, + 'contact_admin_info_bitfield' => $contact_admin_info_bitfield, + 'contact_admin_info_flags' => $contact_admin_info_flags, + )); + + trigger_error($user->lang['CONTACT_US_INFO_UPDATED'] . adm_back_link($this->u_action)); + } + } + + $contact_admin_info_preview = ''; + if ($request->is_set_post('preview')) + { + $contact_admin_info_preview = generate_text_for_display($contact_admin_info, $contact_admin_info_uid, $contact_admin_info_bitfield, $contact_admin_info_flags); + } + + $contact_admin_edit = generate_text_for_edit($contact_admin_info, $contact_admin_info_uid, $contact_admin_info_flags); + + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + + $template->assign_vars(array( + 'ERRORS' => $error, + 'CONTACT_ENABLED' => $config['contact_admin_form_enable'], + + 'CONTACT_US_INFO' => $contact_admin_edit['text'], + 'CONTACT_US_INFO_PREVIEW' => $contact_admin_info_preview, + + 'S_BBCODE_DISABLE_CHECKED' => !$contact_admin_edit['allow_bbcode'], + 'S_SMILIES_DISABLE_CHECKED' => !$contact_admin_edit['allow_smilies'], + 'S_MAGIC_URL_DISABLE_CHECKED' => !$contact_admin_edit['allow_urls'], + + 'BBCODE_STATUS' => $user->lang('BBCODE_IS_ON', '', ''), + 'SMILIES_STATUS' => $user->lang['SMILIES_ARE_ON'], + 'IMG_STATUS' => $user->lang['IMAGES_ARE_ON'], + 'FLASH_STATUS' => $user->lang['FLASH_IS_ON'], + 'URL_STATUS' => $user->lang['URL_IS_ON'], + + 'S_BBCODE_ALLOWED' => true, + 'S_SMILIES_ALLOWED' => true, + 'S_BBCODE_IMG' => true, + 'S_BBCODE_FLASH' => true, + 'S_LINKS_ALLOWED' => true, + )); + + // Assigning custom bbcodes + display_custom_bbcodes(); + } +} diff --git a/includes/acp/acp_database.php b/includes/acp/acp_database.php new file mode 100644 index 0000000..05f2b98 --- /dev/null +++ b/includes/acp/acp_database.php @@ -0,0 +1,622 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_database +{ + var $db_tools; + var $u_action; + public $page_title; + + function main($id, $mode) + { + global $cache, $db, $user, $template, $table_prefix, $request; + global $phpbb_root_path, $phpbb_container, $phpbb_log; + + $this->db_tools = $phpbb_container->get('dbal.tools'); + + $user->add_lang('acp/database'); + + $this->tpl_name = 'acp_database'; + $this->page_title = 'ACP_DATABASE'; + + $action = $request->variable('action', ''); + + $form_key = 'acp_database'; + add_form_key($form_key); + + $template->assign_vars(array( + 'MODE' => $mode + )); + + switch ($mode) + { + case 'backup': + + $this->page_title = 'ACP_BACKUP'; + + switch ($action) + { + case 'download': + $type = $request->variable('type', ''); + $table = array_intersect($this->db_tools->sql_list_tables(), $request->variable('table', array(''))); + $format = $request->variable('method', ''); + $where = $request->variable('where', ''); + + if (!count($table)) + { + trigger_error($user->lang['TABLE_SELECT_ERROR'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (!check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $store = $structure = $schema_data = false; + + if ($where == 'store') + { + $store = true; + } + + if ($type == 'full' || $type == 'structure') + { + $structure = true; + } + + if ($type == 'full' || $type == 'data') + { + $schema_data = true; + } + + @set_time_limit(1200); + @set_time_limit(0); + + $time = time(); + + $filename = 'backup_' . $time . '_' . unique_id(); + + /** @var phpbb\db\extractor\extractor_interface $extractor Database extractor */ + $extractor = $phpbb_container->get('dbal.extractor'); + $extractor->init_extractor($format, $filename, $time, false, $store); + + $extractor->write_start($table_prefix); + + foreach ($table as $table_name) + { + // Get the table structure + if ($structure) + { + $extractor->write_table($table_name); + } + else + { + // We might wanna empty out all that junk :D + switch ($db->get_sql_layer()) + { + case 'sqlite3': + $extractor->flush('DELETE FROM ' . $table_name . ";\n"); + break; + + case 'mssql_odbc': + case 'mssqlnative': + $extractor->flush('TRUNCATE TABLE ' . $table_name . "GO\n"); + break; + + case 'oracle': + $extractor->flush('TRUNCATE TABLE ' . $table_name . "/\n"); + break; + + default: + $extractor->flush('TRUNCATE TABLE ' . $table_name . ";\n"); + break; + } + } + + // Data + if ($schema_data) + { + $extractor->write_data($table_name); + } + } + + $extractor->write_end(); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_BACKUP'); + + trigger_error($user->lang['BACKUP_SUCCESS'] . adm_back_link($this->u_action)); + break; + + default: + $tables = $this->db_tools->sql_list_tables(); + asort($tables); + foreach ($tables as $table_name) + { + if (strlen($table_prefix) === 0 || stripos($table_name, $table_prefix) === 0) + { + $template->assign_block_vars('tables', array( + 'TABLE' => $table_name + )); + } + } + unset($tables); + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action . '&action=download' + )); + + $available_methods = array('gzip' => 'zlib', 'bzip2' => 'bz2'); + + foreach ($available_methods as $type => $module) + { + if (!@extension_loaded($module)) + { + continue; + } + + $template->assign_block_vars('methods', array( + 'TYPE' => $type + )); + } + + $template->assign_block_vars('methods', array( + 'TYPE' => 'text' + )); + break; + } + break; + + case 'restore': + + $this->page_title = 'ACP_RESTORE'; + + switch ($action) + { + case 'submit': + $delete = $request->variable('delete', ''); + $file = $request->variable('file', ''); + + $backup_info = $this->get_backup_file($phpbb_root_path . 'store/', $file); + + if (empty($backup_info) || !is_readable($backup_info['file_name'])) + { + trigger_error($user->lang['BACKUP_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if ($delete) + { + if (confirm_box(true)) + { + unlink($backup_info['file_name']); + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_DELETE'); + trigger_error($user->lang['BACKUP_DELETE'] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, $user->lang['DELETE_SELECTED_BACKUP'], build_hidden_fields(array('delete' => $delete, 'file' => $file))); + } + } + else if (confirm_box(true)) + { + switch ($backup_info['extensions']) + { + case 'sql': + $fp = fopen($backup_info['file_name'], 'rb'); + $read = 'fread'; + $seek = 'fseek'; + $eof = 'feof'; + $close = 'fclose'; + $fgetd = 'fgetd'; + break; + + case 'sql.bz2': + $fp = bzopen($backup_info['file_name'], 'r'); + $read = 'bzread'; + $seek = ''; + $eof = 'feof'; + $close = 'bzclose'; + $fgetd = 'fgetd_seekless'; + break; + + case 'sql.gz': + $fp = gzopen($backup_info['file_name'], 'rb'); + $read = 'gzread'; + $seek = 'gzseek'; + $eof = 'gzeof'; + $close = 'gzclose'; + $fgetd = 'fgetd'; + break; + + default: + trigger_error($user->lang['BACKUP_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + return; + } + + switch ($db->get_sql_layer()) + { + case 'mysql': + case 'mysql4': + case 'mysqli': + case 'sqlite3': + while (($sql = $fgetd($fp, ";\n", $read, $seek, $eof)) !== false) + { + $db->sql_query($sql); + } + break; + + case 'postgres': + $delim = ";\n"; + while (($sql = $fgetd($fp, $delim, $read, $seek, $eof)) !== false) + { + $query = trim($sql); + + if (substr($query, 0, 13) == 'CREATE DOMAIN') + { + list(, , $domain) = explode(' ', $query); + $sql = "SELECT domain_name + FROM information_schema.domains + WHERE domain_name = '$domain';"; + $result = $db->sql_query($sql); + if (!$db->sql_fetchrow($result)) + { + $db->sql_query($query); + } + $db->sql_freeresult($result); + } + else + { + $db->sql_query($query); + } + + if (substr($query, 0, 4) == 'COPY') + { + while (($sub = $fgetd($fp, "\n", $read, $seek, $eof)) !== '\.') + { + if ($sub === false) + { + trigger_error($user->lang['RESTORE_FAILURE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + pg_put_line($db->get_db_connect_id(), $sub . "\n"); + } + pg_put_line($db->get_db_connect_id(), "\\.\n"); + pg_end_copy($db->get_db_connect_id()); + } + } + break; + + case 'oracle': + while (($sql = $fgetd($fp, "/\n", $read, $seek, $eof)) !== false) + { + $db->sql_query($sql); + } + break; + + case 'mssql_odbc': + case 'mssqlnative': + while (($sql = $fgetd($fp, "GO\n", $read, $seek, $eof)) !== false) + { + $db->sql_query($sql); + } + break; + } + + $close($fp); + + // Purge the cache due to updated data + $cache->purge(); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DB_RESTORE'); + trigger_error($user->lang['RESTORE_SUCCESS'] . adm_back_link($this->u_action)); + break; + } + else + { + confirm_box(false, $user->lang['RESTORE_SELECTED_BACKUP'], build_hidden_fields(array('file' => $file))); + } + + default: + $backup_files = $this->get_file_list($phpbb_root_path . 'store/'); + + if (!empty($backup_files)) + { + krsort($backup_files); + + foreach ($backup_files as $name => $file) + { + $template->assign_block_vars('files', array( + 'FILE' => sha1($file), + 'NAME' => $user->format_date($name, 'd-m-Y H:i', true), + 'SUPPORTED' => true, + )); + } + } + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action . '&action=submit' + )); + break; + } + break; + } + } + + /** + * Get backup file from file hash + * + * @param string $directory Relative path to directory + * @param string $file_hash Hash of selected file + * + * @return array Backup file data or empty array if unable to find file + */ + protected function get_backup_file($directory, $file_hash) + { + $backup_data = []; + + $file_list = $this->get_file_list($directory); + $supported_extensions = $this->get_supported_extensions(); + + foreach ($file_list as $file) + { + preg_match('#^backup_(\d{10,})_(?:[a-z\d]{16}|[a-z\d]{32})\.(sql(?:\.(?:gz|bz2))?)$#i', $file, $matches); + if (sha1($file) === $file_hash && in_array($matches[2], $supported_extensions)) + { + $backup_data = [ + 'file_name' => $directory . $file, + 'extension' => $matches[2], + ]; + break; + } + } + + return $backup_data; + } + + /** + * Get backup file list for directory + * + * @param string $directory Relative path to backup directory + * + * @return array List of backup files in specified directory + */ + protected function get_file_list($directory) + { + $supported_extensions = $this->get_supported_extensions(); + + $dh = @opendir($directory); + + $backup_files = []; + + if ($dh) + { + while (($file = readdir($dh)) !== false) + { + if (preg_match('#^backup_(\d{10,})_(?:[a-z\d]{16}|[a-z\d]{32})\.(sql(?:\.(?:gz|bz2))?)$#i', $file, $matches)) + { + if (in_array($matches[2], $supported_extensions)) + { + $backup_files[(int) $matches[1]] = $file; + } + } + } + closedir($dh); + } + + return $backup_files; + } + + /** + * Get supported extensions for backup + * + * @return array List of supported extensions + */ + protected function get_supported_extensions() + { + $extensions = ['sql']; + $available_methods = ['sql.gz' => 'zlib', 'sql.bz2' => 'bz2']; + + foreach ($available_methods as $type => $module) + { + if (!@extension_loaded($module)) + { + continue; + } + $extensions[] = $type; + } + + return $extensions; + } +} + +// get how much space we allow for a chunk of data, very similar to phpMyAdmin's way of doing things ;-) (hey, we only do this for MySQL anyway :P) +function get_usable_memory() +{ + $val = trim(@ini_get('memory_limit')); + + if (preg_match('/(\\d+)([mkg]?)/i', $val, $regs)) + { + $memory_limit = (int) $regs[1]; + switch ($regs[2]) + { + + case 'k': + case 'K': + $memory_limit *= 1024; + break; + + case 'm': + case 'M': + $memory_limit *= 1048576; + break; + + case 'g': + case 'G': + $memory_limit *= 1073741824; + break; + } + + // how much memory PHP requires at the start of export (it is really a little less) + if ($memory_limit > 6100000) + { + $memory_limit -= 6100000; + } + + // allow us to consume half of the total memory available + $memory_limit /= 2; + } + else + { + // set the buffer to 1M if we have no clue how much memory PHP will give us :P + $memory_limit = 1048576; + } + + return $memory_limit; +} + +function sanitize_data_mssql($text) +{ + $data = preg_split('/[\n\t\r\b\f]/', $text); + preg_match_all('/[\n\t\r\b\f]/', $text, $matches); + + $val = array(); + + foreach ($data as $value) + { + if (strlen($value)) + { + $val[] = "'" . $value . "'"; + } + if (count($matches[0])) + { + $val[] = 'char(' . ord(array_shift($matches[0])) . ')'; + } + } + + return implode('+', $val); +} + +function sanitize_data_oracle($text) +{ +// $data = preg_split('/[\0\n\t\r\b\f\'"\/\\\]/', $text); +// preg_match_all('/[\0\n\t\r\b\f\'"\/\\\]/', $text, $matches); + $data = preg_split('/[\0\b\f\'\/]/', $text); + preg_match_all('/[\0\r\b\f\'\/]/', $text, $matches); + + $val = array(); + + foreach ($data as $value) + { + if (strlen($value)) + { + $val[] = "'" . $value . "'"; + } + if (count($matches[0])) + { + $val[] = 'chr(' . ord(array_shift($matches[0])) . ')'; + } + } + + return implode('||', $val); +} + +function sanitize_data_generic($text) +{ + $data = preg_split('/[\n\t\r\b\f]/', $text); + preg_match_all('/[\n\t\r\b\f]/', $text, $matches); + + $val = array(); + + foreach ($data as $value) + { + if (strlen($value)) + { + $val[] = "'" . $value . "'"; + } + if (count($matches[0])) + { + $val[] = "'" . array_shift($matches[0]) . "'"; + } + } + + return implode('||', $val); +} + +// modified from PHP.net +function fgetd(&$fp, $delim, $read, $seek, $eof, $buffer = 8192) +{ + $record = ''; + $delim_len = strlen($delim); + + while (!$eof($fp)) + { + $pos = strpos($record, $delim); + if ($pos === false) + { + $record .= $read($fp, $buffer); + if ($eof($fp) && ($pos = strpos($record, $delim)) !== false) + { + $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR); + return substr($record, 0, $pos); + } + } + else + { + $seek($fp, $pos + $delim_len - strlen($record), SEEK_CUR); + return substr($record, 0, $pos); + } + } + + return false; +} + +function fgetd_seekless(&$fp, $delim, $read, $seek, $eof, $buffer = 8192) +{ + static $array = array(); + static $record = ''; + + if (!count($array)) + { + while (!$eof($fp)) + { + if (strpos($record, $delim) !== false) + { + $array = explode($delim, $record); + $record = array_pop($array); + break; + } + else + { + $record .= $read($fp, $buffer); + } + } + if ($eof($fp) && strpos($record, $delim) !== false) + { + $array = explode($delim, $record); + $record = array_pop($array); + } + } + + if (count($array)) + { + return array_shift($array); + } + + return false; +} diff --git a/includes/acp/acp_disallow.php b/includes/acp/acp_disallow.php new file mode 100644 index 0000000..70eb398 --- /dev/null +++ b/includes/acp/acp_disallow.php @@ -0,0 +1,115 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_disallow +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $template, $cache, $phpbb_log, $request; + + $user->add_lang('acp/posting'); + + // Set up general vars + $this->tpl_name = 'acp_disallow'; + $this->page_title = 'ACP_DISALLOW_USERNAMES'; + + $form_key = 'acp_disallow'; + add_form_key($form_key); + + $disallow = (isset($_POST['disallow'])) ? true : false; + $allow = (isset($_POST['allow'])) ? true : false; + + if (($allow || $disallow) && !check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if ($disallow) + { + $disallowed_user = str_replace('*', '%', $request->variable('disallowed_user', '', true)); + + if (!$disallowed_user) + { + trigger_error($user->lang['NO_USERNAME_SPECIFIED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT disallow_id + FROM ' . DISALLOW_TABLE . " + WHERE disallow_username = '" . $db->sql_escape($disallowed_user) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + trigger_error($user->lang['DISALLOWED_ALREADY'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'INSERT INTO ' . DISALLOW_TABLE . ' ' . $db->sql_build_array('INSERT', array('disallow_username' => $disallowed_user)); + $db->sql_query($sql); + + $cache->destroy('_disallowed_usernames'); + + $message = $user->lang['DISALLOW_SUCCESSFUL']; + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DISALLOW_ADD', false, array(str_replace('%', '*', $disallowed_user))); + + trigger_error($message . adm_back_link($this->u_action)); + } + else if ($allow) + { + $disallowed_id = $request->variable('disallowed_id', 0); + + if (!$disallowed_id) + { + trigger_error($user->lang['NO_USERNAME_SPECIFIED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'DELETE FROM ' . DISALLOW_TABLE . ' + WHERE disallow_id = ' . $disallowed_id; + $db->sql_query($sql); + + $cache->destroy('_disallowed_usernames'); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_DISALLOW_DELETE'); + + trigger_error($user->lang['DISALLOWED_DELETED'] . adm_back_link($this->u_action)); + } + + // Grab the current list of disallowed usernames... + $sql = 'SELECT * + FROM ' . DISALLOW_TABLE; + $result = $db->sql_query($sql); + + $disallow_select = ''; + while ($row = $db->sql_fetchrow($result)) + { + $disallow_select .= ''; + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'S_DISALLOWED_NAMES' => $disallow_select) + ); + } +} diff --git a/includes/acp/acp_email.php b/includes/acp/acp_email.php new file mode 100644 index 0000000..5a1fbac --- /dev/null +++ b/includes/acp/acp_email.php @@ -0,0 +1,351 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_email +{ + var $u_action; + + function main($id, $mode) + { + global $config, $db, $user, $template, $phpbb_log, $request; + global $phpbb_root_path, $phpbb_admin_path, $phpEx, $phpbb_dispatcher; + + $user->add_lang('acp/email'); + $this->tpl_name = 'acp_email'; + $this->page_title = 'ACP_MASS_EMAIL'; + + $form_key = 'acp_email'; + add_form_key($form_key); + + // Set some vars + $submit = (isset($_POST['submit'])) ? true : false; + $error = array(); + + $usernames = $request->variable('usernames', '', true); + $usernames = (!empty($usernames)) ? explode("\n", $usernames) : array(); + $group_id = $request->variable('g', 0); + $subject = $request->variable('subject', '', true); + $message = $request->variable('message', '', true); + + // Do the job ... + if ($submit) + { + // Error checking needs to go here ... if no subject and/or no message then skip + // over the send and return to the form + $use_queue = (isset($_POST['send_immediately'])) ? false : true; + $priority = $request->variable('mail_priority_flag', MAIL_NORMAL_PRIORITY); + + if (!check_form_key($form_key)) + { + $error[] = $user->lang['FORM_INVALID']; + } + + if (!$subject) + { + $error[] = $user->lang['NO_EMAIL_SUBJECT']; + } + + if (!$message) + { + $error[] = $user->lang['NO_EMAIL_MESSAGE']; + } + + if (!count($error)) + { + if (!empty($usernames)) + { + // If giving usernames the admin is able to email inactive users too... + $sql_ary = array( + 'SELECT' => 'username, user_email, user_jabber, user_notify_type, user_lang', + 'FROM' => array( + USERS_TABLE => '', + ), + 'WHERE' => $db->sql_in_set('username_clean', array_map('utf8_clean_string', $usernames)) . ' + AND user_allow_massemail = 1', + 'ORDER_BY' => 'user_lang, user_notify_type', + ); + } + else + { + if ($group_id) + { + $sql_ary = array( + 'SELECT' => 'u.user_email, u.username, u.username_clean, u.user_lang, u.user_jabber, u.user_notify_type', + 'FROM' => array( + USERS_TABLE => 'u', + USER_GROUP_TABLE => 'ug', + ), + 'WHERE' => 'ug.group_id = ' . $group_id . ' + AND ug.user_pending = 0 + AND u.user_id = ug.user_id + AND u.user_allow_massemail = 1 + AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')', + 'ORDER_BY' => 'u.user_lang, u.user_notify_type', + ); + } + else + { + $sql_ary = array( + 'SELECT' => 'u.username, u.username_clean, u.user_email, u.user_jabber, u.user_lang, u.user_notify_type', + 'FROM' => array( + USERS_TABLE => 'u', + ), + 'WHERE' => 'u.user_allow_massemail = 1 + AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')', + 'ORDER_BY' => 'u.user_lang, u.user_notify_type', + ); + } + + // Mail banned or not + if (!isset($_REQUEST['mail_banned_flag'])) + { + $sql_ary['WHERE'] .= ' AND (b.ban_id IS NULL + OR b.ban_exclude = 1)'; + $sql_ary['LEFT_JOIN'] = array( + array( + 'FROM' => array( + BANLIST_TABLE => 'b', + ), + 'ON' => 'u.user_id = b.ban_userid', + ), + ); + } + } + /** + * Modify sql query to change the list of users the email is sent to + * + * @event core.acp_email_modify_sql + * @var array sql_ary Array which is used to build the sql query + * @since 3.1.2-RC1 + */ + $vars = array('sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.acp_email_modify_sql', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + + if (!$row) + { + $db->sql_freeresult($result); + trigger_error($user->lang['NO_USER'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $i = $j = 0; + + // Send with BCC + // Maximum number of bcc recipients + $max_chunk_size = (int) $config['email_max_chunk_size']; + $email_list = array(); + $old_lang = $row['user_lang']; + $old_notify_type = $row['user_notify_type']; + + do + { + if (($row['user_notify_type'] == NOTIFY_EMAIL && $row['user_email']) || + ($row['user_notify_type'] == NOTIFY_IM && $row['user_jabber']) || + ($row['user_notify_type'] == NOTIFY_BOTH && ($row['user_email'] || $row['user_jabber']))) + { + if ($i == $max_chunk_size || $row['user_lang'] != $old_lang || $row['user_notify_type'] != $old_notify_type) + { + $i = 0; + + if (count($email_list)) + { + $j++; + } + + $old_lang = $row['user_lang']; + $old_notify_type = $row['user_notify_type']; + } + + $email_list[$j][$i]['lang'] = $row['user_lang']; + $email_list[$j][$i]['method'] = $row['user_notify_type']; + $email_list[$j][$i]['email'] = $row['user_email']; + $email_list[$j][$i]['name'] = $row['username']; + $email_list[$j][$i]['jabber'] = $row['user_jabber']; + $i++; + } + } + while ($row = $db->sql_fetchrow($result)); + $db->sql_freeresult($result); + + // Send the messages + if (!class_exists('messenger')) + { + include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + } + + if (!function_exists('get_group_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + $messenger = new messenger($use_queue); + + $errored = false; + + $email_template = 'admin_send_email'; + $template_data = array( + 'CONTACT_EMAIL' => phpbb_get_board_contact($config, $phpEx), + 'MESSAGE' => htmlspecialchars_decode($message), + ); + $generate_log_entry = true; + + /** + * Modify email template data before the emails are sent + * + * @event core.acp_email_send_before + * @var string email_template The template to be used for sending the email + * @var string subject The subject of the email + * @var array template_data Array with template data assigned to email template + * @var bool generate_log_entry If false, no log entry will be created + * @var array usernames Usernames which will be displayed in log entry, if it will be created + * @var int group_id The group this email will be sent to + * @var bool use_queue If true, email queue will be used for sending + * @var int priority Priority of sent emails + * @since 3.1.3-RC1 + */ + $vars = array( + 'email_template', + 'subject', + 'template_data', + 'generate_log_entry', + 'usernames', + 'group_id', + 'use_queue', + 'priority', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_email_send_before', compact($vars))); + + for ($i = 0, $size = count($email_list); $i < $size; $i++) + { + $used_lang = $email_list[$i][0]['lang']; + $used_method = $email_list[$i][0]['method']; + + for ($j = 0, $list_size = count($email_list[$i]); $j < $list_size; $j++) + { + $email_row = $email_list[$i][$j]; + + $messenger->{((count($email_list[$i]) == 1) ? 'to' : 'bcc')}($email_row['email'], $email_row['name']); + $messenger->im($email_row['jabber'], $email_row['name']); + } + + $messenger->template($email_template, $used_lang); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->subject(htmlspecialchars_decode($subject)); + $messenger->set_mail_priority($priority); + + $messenger->assign_vars($template_data); + + if (!($messenger->send($used_method))) + { + $errored = true; + } + } + unset($email_list); + + $messenger->save_queue(); + + if ($generate_log_entry) + { + if (!empty($usernames)) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_MASS_EMAIL', false, array(implode(', ', utf8_normalize_nfc($usernames)))); + } + else + { + if ($group_id) + { + $group_name = get_group_name($group_id); + } + else + { + // Not great but the logging routine doesn't cope well with localising on the fly + $group_name = $user->lang['ALL_USERS']; + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_MASS_EMAIL', false, array($group_name)); + } + } + + if (!$errored) + { + $message = ($use_queue) ? $user->lang['EMAIL_SENT_QUEUE'] : $user->lang['EMAIL_SENT']; + trigger_error($message . adm_back_link($this->u_action)); + } + else + { + $message = sprintf($user->lang['EMAIL_SEND_ERROR'], '', ''); + trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING); + } + } + } + + // Exclude bots and guests... + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name IN ('BOTS', 'GUESTS')"; + $result = $db->sql_query($sql); + + $exclude = array(); + while ($row = $db->sql_fetchrow($result)) + { + $exclude[] = $row['group_id']; + } + $db->sql_freeresult($result); + + $select_list = ''; + $select_list .= group_select_options($group_id, $exclude); + + $s_priority_options = ''; + $s_priority_options .= ''; + $s_priority_options .= ''; + + $template_data = array( + 'S_WARNING' => (count($error)) ? true : false, + 'WARNING_MSG' => (count($error)) ? implode('
', $error) : '', + 'U_ACTION' => $this->u_action, + 'S_GROUP_OPTIONS' => $select_list, + 'USERNAMES' => implode("\n", $usernames), + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=acp_email&field=usernames'), + 'SUBJECT' => $subject, + 'MESSAGE' => $message, + 'S_PRIORITY_OPTIONS' => $s_priority_options, + ); + + /** + * Modify custom email template data before we display the form + * + * @event core.acp_email_display + * @var array template_data Array with template data assigned to email template + * @var array exclude Array with groups which are excluded from group selection + * @var array usernames Usernames which will be displayed in form + * + * @since 3.1.4-RC1 + */ + $vars = array('template_data', 'exclude', 'usernames'); + extract($phpbb_dispatcher->trigger_event('core.acp_email_display', compact($vars))); + + $template->assign_vars($template_data); + } +} diff --git a/includes/acp/acp_extensions.php b/includes/acp/acp_extensions.php new file mode 100644 index 0000000..a1cb210 --- /dev/null +++ b/includes/acp/acp_extensions.php @@ -0,0 +1,665 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +use phpbb\exception\exception_interface; +use phpbb\exception\version_check_exception; + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_extensions +{ + var $u_action; + var $tpl_name; + var $page_title; + + private $config; + private $template; + private $user; + private $log; + private $request; + private $phpbb_dispatcher; + private $ext_manager; + private $phpbb_container; + private $php_ini; + + function main() + { + // Start the page + global $config, $user, $template, $request, $phpbb_extension_manager, $phpbb_root_path, $phpbb_log, $phpbb_dispatcher, $phpbb_container; + + $this->config = $config; + $this->template = $template; + $this->user = $user; + $this->request = $request; + $this->log = $phpbb_log; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->ext_manager = $phpbb_extension_manager; + $this->phpbb_container = $phpbb_container; + $this->php_ini = $this->phpbb_container->get('php_ini'); + + $this->user->add_lang(array('install', 'acp/extensions', 'migrator')); + + $this->page_title = 'ACP_EXTENSIONS'; + + $action = $this->request->variable('action', 'list'); + $ext_name = $this->request->variable('ext_name', ''); + + // What is a safe limit of execution time? Half the max execution time should be safe. + $safe_time_limit = ($this->php_ini->getNumeric('max_execution_time') / 2); + $start_time = time(); + + // Cancel action + if ($this->request->is_set_post('cancel')) + { + $action = 'list'; + $ext_name = ''; + } + + if (in_array($action, array('enable', 'disable', 'delete_data')) && !check_link_hash($this->request->variable('hash', ''), $action . '.' . $ext_name)) + { + trigger_error('FORM_INVALID', E_USER_WARNING); + } + + /** + * Event to run a specific action on extension + * + * @event core.acp_extensions_run_action_before + * @var string action Action to run; if the event completes execution of the action, should be set to 'none' + * @var string u_action Url we are at + * @var string ext_name Extension name from request + * @var int safe_time_limit Safe limit of execution time + * @var int start_time Start time + * @var string tpl_name Template file to load + * @since 3.1.11-RC1 + * @changed 3.2.1-RC1 Renamed to core.acp_extensions_run_action_before, added tpl_name, added action 'none' + */ + $u_action = $this->u_action; + $tpl_name = ''; + $vars = array('action', 'u_action', 'ext_name', 'safe_time_limit', 'start_time', 'tpl_name'); + extract($this->phpbb_dispatcher->trigger_event('core.acp_extensions_run_action_before', compact($vars))); + + // In case they have been updated by the event + $this->u_action = $u_action; + $this->tpl_name = $tpl_name; + + // If they've specified an extension, let's load the metadata manager and validate it. + if ($ext_name) + { + $md_manager = $this->ext_manager->create_extension_metadata_manager($ext_name); + + try + { + $md_manager->get_metadata('all'); + } + catch (exception_interface $e) + { + $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + // What are we doing? + switch ($action) + { + case 'none': + // Intentionally empty, used by extensions that execute additional actions in the prior event + break; + + case 'set_config_version_check_force_unstable': + $force_unstable = $this->request->variable('force_unstable', false); + + if ($force_unstable) + { + $s_hidden_fields = build_hidden_fields(array( + 'force_unstable' => $force_unstable, + )); + + confirm_box(false, $this->user->lang('EXTENSION_FORCE_UNSTABLE_CONFIRM'), $s_hidden_fields); + } + else + { + $this->config->set('extension_force_unstable', false); + trigger_error($this->user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action)); + } + break; + + case 'list': + default: + if (confirm_box(true)) + { + $this->config->set('extension_force_unstable', true); + trigger_error($this->user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action)); + } + + $this->list_enabled_exts(); + $this->list_disabled_exts(); + $this->list_available_exts(); + + $this->template->assign_vars(array( + 'U_VERSIONCHECK_FORCE' => $this->u_action . '&action=list&versioncheck_force=1', + 'FORCE_UNSTABLE' => $this->config['extension_force_unstable'], + 'U_ACTION' => $this->u_action, + )); + + $this->tpl_name = 'acp_ext_list'; + break; + + case 'enable_pre': + try + { + $md_manager->validate_enable(); + } + catch (exception_interface $e) + { + $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING); + } + + $extension = $this->ext_manager->get_extension($ext_name); + if (!$extension->is_enableable()) + { + trigger_error($this->user->lang['EXTENSION_NOT_ENABLEABLE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if ($this->ext_manager->is_enabled($ext_name)) + { + redirect($this->u_action); + } + + $this->tpl_name = 'acp_ext_enable'; + + $this->template->assign_vars(array( + 'PRE' => true, + 'L_CONFIRM_MESSAGE' => $this->user->lang('EXTENSION_ENABLE_CONFIRM', $md_manager->get_metadata('display-name')), + 'U_ENABLE' => $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name) . '&hash=' . generate_link_hash('enable.' . $ext_name), + )); + break; + + case 'enable': + try + { + $md_manager->validate_enable(); + } + catch (exception_interface $e) + { + $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + trigger_error($message . adm_back_link($this->u_action), E_USER_WARNING); + } + + $extension = $this->ext_manager->get_extension($ext_name); + if (!$extension->is_enableable()) + { + trigger_error($this->user->lang['EXTENSION_NOT_ENABLEABLE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + try + { + while ($this->ext_manager->enable_step($ext_name)) + { + // Are we approaching the time limit? If so we want to pause the update and continue after refreshing + if ((time() - $start_time) >= $safe_time_limit) + { + $this->template->assign_var('S_NEXT_STEP', true); + + meta_refresh(0, $this->u_action . '&action=enable&ext_name=' . urlencode($ext_name) . '&hash=' . generate_link_hash('enable.' . $ext_name)); + } + } + + // Update custom style for admin area + $this->template->set_custom_style(array( + array( + 'name' => 'adm', + 'ext_path' => 'adm/style/', + ), + ), array($phpbb_root_path . 'adm/style')); + + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_EXT_ENABLE', time(), array($ext_name)); + } + catch (\phpbb\db\migration\exception $e) + { + $this->template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($this->user)); + } + + $this->tpl_name = 'acp_ext_enable'; + + $this->template->assign_vars(array( + 'U_RETURN' => $this->u_action . '&action=list', + )); + break; + + case 'disable_pre': + if (!$this->ext_manager->is_enabled($ext_name)) + { + redirect($this->u_action); + } + + $this->tpl_name = 'acp_ext_disable'; + + $this->template->assign_vars(array( + 'PRE' => true, + 'L_CONFIRM_MESSAGE' => $this->user->lang('EXTENSION_DISABLE_CONFIRM', $md_manager->get_metadata('display-name')), + 'U_DISABLE' => $this->u_action . '&action=disable&ext_name=' . urlencode($ext_name) . '&hash=' . generate_link_hash('disable.' . $ext_name), + )); + break; + + case 'disable': + if (!$this->ext_manager->is_enabled($ext_name)) + { + redirect($this->u_action); + } + + while ($this->ext_manager->disable_step($ext_name)) + { + // Are we approaching the time limit? If so we want to pause the update and continue after refreshing + if ((time() - $start_time) >= $safe_time_limit) + { + $this->template->assign_var('S_NEXT_STEP', true); + + meta_refresh(0, $this->u_action . '&action=disable&ext_name=' . urlencode($ext_name) . '&hash=' . generate_link_hash('disable.' . $ext_name)); + } + } + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_EXT_DISABLE', time(), array($ext_name)); + + $this->tpl_name = 'acp_ext_disable'; + + $this->template->assign_vars(array( + 'U_RETURN' => $this->u_action . '&action=list', + )); + break; + + case 'delete_data_pre': + if ($this->ext_manager->is_enabled($ext_name)) + { + redirect($this->u_action); + } + $this->tpl_name = 'acp_ext_delete_data'; + + $this->template->assign_vars(array( + 'PRE' => true, + 'L_CONFIRM_MESSAGE' => $this->user->lang('EXTENSION_DELETE_DATA_CONFIRM', $md_manager->get_metadata('display-name')), + 'U_PURGE' => $this->u_action . '&action=delete_data&ext_name=' . urlencode($ext_name) . '&hash=' . generate_link_hash('delete_data.' . $ext_name), + )); + break; + + case 'delete_data': + if ($this->ext_manager->is_enabled($ext_name)) + { + redirect($this->u_action); + } + + try + { + while ($this->ext_manager->purge_step($ext_name)) + { + // Are we approaching the time limit? If so we want to pause the update and continue after refreshing + if ((time() - $start_time) >= $safe_time_limit) + { + $this->template->assign_var('S_NEXT_STEP', true); + + meta_refresh(0, $this->u_action . '&action=delete_data&ext_name=' . urlencode($ext_name) . '&hash=' . generate_link_hash('delete_data.' . $ext_name)); + } + } + $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_EXT_PURGE', time(), array($ext_name)); + } + catch (\phpbb\db\migration\exception $e) + { + $this->template->assign_var('MIGRATOR_ERROR', $e->getLocalisedMessage($this->user)); + } + + $this->tpl_name = 'acp_ext_delete_data'; + + $this->template->assign_vars(array( + 'U_RETURN' => $this->u_action . '&action=list', + )); + break; + + case 'details': + // Output it to the template + $meta = $md_manager->get_metadata('all'); + $this->output_metadata_to_template($meta); + + if (isset($meta['extra']['version-check'])) + { + try + { + $updates_available = $this->ext_manager->version_check($md_manager, $this->request->variable('versioncheck_force', false), false, $this->config['extension_force_unstable'] ? 'unstable' : null); + + $this->template->assign_vars(array( + 'S_UP_TO_DATE' => empty($updates_available), + 'UP_TO_DATE_MSG' => $this->user->lang(empty($updates_available) ? 'UP_TO_DATE' : 'NOT_UP_TO_DATE', $md_manager->get_metadata('display-name')), + )); + + $this->template->assign_block_vars('updates_available', $updates_available); + } + catch (exception_interface $e) + { + $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + + $this->template->assign_vars(array( + 'S_VERSIONCHECK_FAIL' => true, + 'VERSIONCHECK_FAIL_REASON' => ($e->getMessage() !== 'VERSIONCHECK_FAIL') ? $message : '', + )); + } + $this->template->assign_var('S_VERSIONCHECK', true); + } + else + { + $this->template->assign_var('S_VERSIONCHECK', false); + } + + $this->template->assign_vars(array( + 'U_BACK' => $this->u_action . '&action=list', + 'U_VERSIONCHECK_FORCE' => $this->u_action . '&action=details&versioncheck_force=1&ext_name=' . urlencode($md_manager->get_metadata('name')), + )); + + $this->tpl_name = 'acp_ext_details'; + break; + } + + /** + * Event to run after a specific action on extension has completed + * + * @event core.acp_extensions_run_action_after + * @var string action Action that has run + * @var string u_action Url we are at + * @var string ext_name Extension name from request + * @var int safe_time_limit Safe limit of execution time + * @var int start_time Start time + * @var string tpl_name Template file to load + * @since 3.1.11-RC1 + */ + $u_action = $this->u_action; + $tpl_name = $this->tpl_name; + $vars = array('action', 'u_action', 'ext_name', 'safe_time_limit', 'start_time', 'tpl_name'); + extract($this->phpbb_dispatcher->trigger_event('core.acp_extensions_run_action_after', compact($vars))); + + // In case they have been updated by the event + $this->u_action = $u_action; + $this->tpl_name = $tpl_name; + } + + /** + * Lists all the enabled extensions and dumps to the template + * + * @return null + */ + public function list_enabled_exts() + { + $enabled_extension_meta_data = array(); + + foreach ($this->ext_manager->all_enabled() as $name => $location) + { + $md_manager = $this->ext_manager->create_extension_metadata_manager($name); + + try + { + $meta = $md_manager->get_metadata('all'); + $enabled_extension_meta_data[$name] = array( + 'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'), + 'META_VERSION' => $meta['version'], + ); + + if (isset($meta['extra']['version-check'])) + { + try + { + $force_update = $this->request->variable('versioncheck_force', false); + $updates = $this->ext_manager->version_check($md_manager, $force_update, !$force_update); + + $enabled_extension_meta_data[$name]['S_UP_TO_DATE'] = empty($updates); + $enabled_extension_meta_data[$name]['S_VERSIONCHECK'] = true; + $enabled_extension_meta_data[$name]['U_VERSIONCHECK_FORCE'] = $this->u_action . '&action=details&versioncheck_force=1&ext_name=' . urlencode($md_manager->get_metadata('name')); + } + catch (exception_interface $e) + { + // Ignore exceptions due to the version check + } + } + else + { + $enabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false; + } + } + catch (exception_interface $e) + { + $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + $this->template->assign_block_vars('disabled', array( + 'META_DISPLAY_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $message), + 'S_VERSIONCHECK' => false, + )); + } + catch (\RuntimeException $e) + { + $enabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false; + } + } + + uasort($enabled_extension_meta_data, array($this, 'sort_extension_meta_data_table')); + + foreach ($enabled_extension_meta_data as $name => $block_vars) + { + $block_vars['NAME'] = $name; + $block_vars['U_DETAILS'] = $this->u_action . '&action=details&ext_name=' . urlencode($name); + + $this->template->assign_block_vars('enabled', $block_vars); + + $this->output_actions('enabled', array( + 'DISABLE' => $this->u_action . '&action=disable_pre&ext_name=' . urlencode($name), + )); + } + } + + /** + * Lists all the disabled extensions and dumps to the template + * + * @return null + */ + public function list_disabled_exts() + { + $disabled_extension_meta_data = array(); + + foreach ($this->ext_manager->all_disabled() as $name => $location) + { + $md_manager = $this->ext_manager->create_extension_metadata_manager($name); + + try + { + $meta = $md_manager->get_metadata('all'); + $disabled_extension_meta_data[$name] = array( + 'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'), + 'META_VERSION' => $meta['version'], + ); + + if (isset($meta['extra']['version-check'])) + { + $force_update = $this->request->variable('versioncheck_force', false); + $updates = $this->ext_manager->version_check($md_manager, $force_update, !$force_update); + + $disabled_extension_meta_data[$name]['S_UP_TO_DATE'] = empty($updates); + $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = true; + $disabled_extension_meta_data[$name]['U_VERSIONCHECK_FORCE'] = $this->u_action . '&action=details&versioncheck_force=1&ext_name=' . urlencode($md_manager->get_metadata('name')); + } + else + { + $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false; + } + } + catch (version_check_exception $e) + { + $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false; + } + catch (exception_interface $e) + { + $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + $this->template->assign_block_vars('disabled', array( + 'META_DISPLAY_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $message), + 'S_VERSIONCHECK' => false, + )); + } + catch (\RuntimeException $e) + { + $disabled_extension_meta_data[$name]['S_VERSIONCHECK'] = false; + } + } + + uasort($disabled_extension_meta_data, array($this, 'sort_extension_meta_data_table')); + + foreach ($disabled_extension_meta_data as $name => $block_vars) + { + $block_vars['NAME'] = $name; + $block_vars['U_DETAILS'] = $this->u_action . '&action=details&ext_name=' . urlencode($name); + + $this->template->assign_block_vars('disabled', $block_vars); + + $this->output_actions('disabled', array( + 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . urlencode($name), + 'DELETE_DATA' => $this->u_action . '&action=delete_data_pre&ext_name=' . urlencode($name), + )); + } + } + + /** + * Lists all the available extensions and dumps to the template + * + * @return null + */ + public function list_available_exts() + { + $uninstalled = array_diff_key($this->ext_manager->all_available(), $this->ext_manager->all_configured()); + + $available_extension_meta_data = array(); + + foreach ($uninstalled as $name => $location) + { + $md_manager = $this->ext_manager->create_extension_metadata_manager($name); + + try + { + $meta = $md_manager->get_metadata('all'); + $available_extension_meta_data[$name] = array( + 'META_DISPLAY_NAME' => $md_manager->get_metadata('display-name'), + 'META_VERSION' => $meta['version'], + ); + + if (isset($meta['extra']['version-check'])) + { + $force_update = $this->request->variable('versioncheck_force', false); + $updates = $this->ext_manager->version_check($md_manager, $force_update, !$force_update); + + $available_extension_meta_data[$name]['S_UP_TO_DATE'] = empty($updates); + $available_extension_meta_data[$name]['S_VERSIONCHECK'] = true; + $available_extension_meta_data[$name]['U_VERSIONCHECK_FORCE'] = $this->u_action . '&action=details&versioncheck_force=1&ext_name=' . urlencode($md_manager->get_metadata('name')); + } + else + { + $available_extension_meta_data[$name]['S_VERSIONCHECK'] = false; + } + } + catch (version_check_exception $e) + { + $available_extension_meta_data[$name]['S_VERSIONCHECK'] = false; + } + catch (exception_interface $e) + { + $message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + $this->template->assign_block_vars('disabled', array( + 'META_DISPLAY_NAME' => $this->user->lang('EXTENSION_INVALID_LIST', $name, $message), + 'S_VERSIONCHECK' => false, + )); + } + } + + uasort($available_extension_meta_data, array($this, 'sort_extension_meta_data_table')); + + foreach ($available_extension_meta_data as $name => $block_vars) + { + $block_vars['NAME'] = $name; + $block_vars['U_DETAILS'] = $this->u_action . '&action=details&ext_name=' . urlencode($name); + + $this->template->assign_block_vars('disabled', $block_vars); + + $this->output_actions('disabled', array( + 'ENABLE' => $this->u_action . '&action=enable_pre&ext_name=' . urlencode($name), + )); + } + } + + /** + * Output actions to a block + * + * @param string $block + * @param array $actions + */ + private function output_actions($block, $actions) + { + foreach ($actions as $lang => $url) + { + $this->template->assign_block_vars($block . '.actions', array( + 'L_ACTION' => $this->user->lang('EXTENSION_' . $lang), + 'L_ACTION_EXPLAIN' => (isset($this->user->lang['EXTENSION_' . $lang . '_EXPLAIN'])) ? $this->user->lang('EXTENSION_' . $lang . '_EXPLAIN') : '', + 'U_ACTION' => $url, + )); + } + } + + /** + * Sort helper for the table containing the metadata about the extensions. + */ + protected function sort_extension_meta_data_table($val1, $val2) + { + return strnatcasecmp($val1['META_DISPLAY_NAME'], $val2['META_DISPLAY_NAME']); + } + + /** + * Outputs extension metadata into the template + * + * @param array $metadata Array with all metadata for the extension + * @return null + */ + public function output_metadata_to_template($metadata) + { + $this->template->assign_vars(array( + 'META_NAME' => $metadata['name'], + 'META_TYPE' => $metadata['type'], + 'META_DESCRIPTION' => (isset($metadata['description'])) ? $metadata['description'] : '', + 'META_HOMEPAGE' => (isset($metadata['homepage'])) ? $metadata['homepage'] : '', + 'META_VERSION' => $metadata['version'], + 'META_TIME' => (isset($metadata['time'])) ? $metadata['time'] : '', + 'META_LICENSE' => $metadata['license'], + + 'META_REQUIRE_PHP' => (isset($metadata['require']['php'])) ? $metadata['require']['php'] : '', + 'META_REQUIRE_PHP_FAIL' => (isset($metadata['require']['php'])) ? false : true, + + 'META_REQUIRE_PHPBB' => (isset($metadata['extra']['soft-require']['phpbb/phpbb'])) ? $metadata['extra']['soft-require']['phpbb/phpbb'] : '', + 'META_REQUIRE_PHPBB_FAIL' => (isset($metadata['extra']['soft-require']['phpbb/phpbb'])) ? false : true, + + 'META_DISPLAY_NAME' => (isset($metadata['extra']['display-name'])) ? $metadata['extra']['display-name'] : '', + )); + + foreach ($metadata['authors'] as $author) + { + $this->template->assign_block_vars('meta_authors', array( + 'AUTHOR_NAME' => $author['name'], + 'AUTHOR_EMAIL' => (isset($author['email'])) ? $author['email'] : '', + 'AUTHOR_HOMEPAGE' => (isset($author['homepage'])) ? $author['homepage'] : '', + 'AUTHOR_ROLE' => (isset($author['role'])) ? $author['role'] : '', + )); + } + } +} diff --git a/includes/acp/acp_forums.php b/includes/acp/acp_forums.php new file mode 100644 index 0000000..be5a7a2 --- /dev/null +++ b/includes/acp/acp_forums.php @@ -0,0 +1,2179 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_forums +{ + var $u_action; + var $parent_id = 0; + + function main($id, $mode) + { + global $db, $user, $auth, $template, $cache, $request, $phpbb_dispatcher; + global $phpbb_admin_path, $phpbb_root_path, $phpEx, $phpbb_log; + + $user->add_lang('acp/forums'); + $this->tpl_name = 'acp_forums'; + $this->page_title = 'ACP_MANAGE_FORUMS'; + + $form_key = 'acp_forums'; + add_form_key($form_key); + + $action = $request->variable('action', ''); + $update = (isset($_POST['update'])) ? true : false; + $forum_id = $request->variable('f', 0); + + $this->parent_id = $request->variable('parent_id', 0); + $forum_data = $errors = array(); + if ($update && !check_form_key($form_key)) + { + $update = false; + $errors[] = $user->lang['FORM_INVALID']; + } + + // Check additional permissions + switch ($action) + { + case 'progress_bar': + $start = $request->variable('start', 0); + $total = $request->variable('total', 0); + + $this->display_progress_bar($start, $total); + break; + + case 'delete': + + if (!$auth->acl_get('a_forumdel')) + { + trigger_error($user->lang['NO_PERMISSION_FORUM_DELETE'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + break; + + case 'add': + + if (!$auth->acl_get('a_forumadd')) + { + trigger_error($user->lang['NO_PERMISSION_FORUM_ADD'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + break; + } + + // Major routines + if ($update) + { + switch ($action) + { + case 'delete': + $action_subforums = $request->variable('action_subforums', ''); + $subforums_to_id = $request->variable('subforums_to_id', 0); + $action_posts = $request->variable('action_posts', ''); + $posts_to_id = $request->variable('posts_to_id', 0); + + $errors = $this->delete_forum($forum_id, $action_posts, $action_subforums, $posts_to_id, $subforums_to_id); + + if (count($errors)) + { + break; + } + + $auth->acl_clear_prefetch(); + $cache->destroy('sql', FORUMS_TABLE); + + trigger_error($user->lang['FORUM_DELETED'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id)); + + break; + + case 'edit': + $forum_data = array( + 'forum_id' => $forum_id + ); + + // No break here + + case 'add': + + $forum_data += array( + 'parent_id' => $request->variable('forum_parent_id', $this->parent_id), + 'forum_type' => $request->variable('forum_type', FORUM_POST), + 'type_action' => $request->variable('type_action', ''), + 'forum_status' => $request->variable('forum_status', ITEM_UNLOCKED), + 'forum_parents' => '', + 'forum_name' => $request->variable('forum_name', '', true), + 'forum_link' => $request->variable('forum_link', ''), + 'forum_link_track' => $request->variable('forum_link_track', false), + 'forum_desc' => $request->variable('forum_desc', '', true), + 'forum_desc_uid' => '', + 'forum_desc_options' => 7, + 'forum_desc_bitfield' => '', + 'forum_rules' => $request->variable('forum_rules', '', true), + 'forum_rules_uid' => '', + 'forum_rules_options' => 7, + 'forum_rules_bitfield' => '', + 'forum_rules_link' => $request->variable('forum_rules_link', ''), + 'forum_image' => $request->variable('forum_image', ''), + 'forum_style' => $request->variable('forum_style', 0), + 'display_subforum_list' => $request->variable('display_subforum_list', false), + 'display_on_index' => $request->variable('display_on_index', false), + 'forum_topics_per_page' => $request->variable('topics_per_page', 0), + 'enable_indexing' => $request->variable('enable_indexing', true), + 'enable_icons' => $request->variable('enable_icons', false), + 'enable_prune' => $request->variable('enable_prune', false), + 'enable_post_review' => $request->variable('enable_post_review', true), + 'enable_quick_reply' => $request->variable('enable_quick_reply', false), + 'enable_shadow_prune' => $request->variable('enable_shadow_prune', false), + 'prune_days' => $request->variable('prune_days', 7), + 'prune_viewed' => $request->variable('prune_viewed', 7), + 'prune_freq' => $request->variable('prune_freq', 1), + 'prune_old_polls' => $request->variable('prune_old_polls', false), + 'prune_announce' => $request->variable('prune_announce', false), + 'prune_sticky' => $request->variable('prune_sticky', false), + 'prune_shadow_days' => $request->variable('prune_shadow_days', 7), + 'prune_shadow_freq' => $request->variable('prune_shadow_freq', 1), + 'forum_password' => $request->variable('forum_password', '', true), + 'forum_password_confirm'=> $request->variable('forum_password_confirm', '', true), + 'forum_password_unset' => $request->variable('forum_password_unset', false), + ); + + /** + * Request forum data and operate on it (parse texts, etc.) + * + * @event core.acp_manage_forums_request_data + * @var string action Type of the action: add|edit + * @var array forum_data Array with new forum data + * @since 3.1.0-a1 + */ + $vars = array('action', 'forum_data'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_request_data', compact($vars))); + + // On add, add empty forum_options... else do not consider it (not updating it) + if ($action == 'add') + { + $forum_data['forum_options'] = 0; + } + + // Use link_display_on_index setting if forum type is link + if ($forum_data['forum_type'] == FORUM_LINK) + { + $forum_data['display_on_index'] = $request->variable('link_display_on_index', false); + } + + // Linked forums and categories are not able to be locked... + if ($forum_data['forum_type'] == FORUM_LINK || $forum_data['forum_type'] == FORUM_CAT) + { + $forum_data['forum_status'] = ITEM_UNLOCKED; + } + + $forum_data['show_active'] = ($forum_data['forum_type'] == FORUM_POST) ? $request->variable('display_recent', true) : $request->variable('display_active', false); + + // Get data for forum rules if specified... + if ($forum_data['forum_rules']) + { + generate_text_for_storage($forum_data['forum_rules'], $forum_data['forum_rules_uid'], $forum_data['forum_rules_bitfield'], $forum_data['forum_rules_options'], $request->variable('rules_parse_bbcode', false), $request->variable('rules_parse_urls', false), $request->variable('rules_parse_smilies', false)); + } + + // Get data for forum description if specified + if ($forum_data['forum_desc']) + { + generate_text_for_storage($forum_data['forum_desc'], $forum_data['forum_desc_uid'], $forum_data['forum_desc_bitfield'], $forum_data['forum_desc_options'], $request->variable('desc_parse_bbcode', false), $request->variable('desc_parse_urls', false), $request->variable('desc_parse_smilies', false)); + } + + $errors = $this->update_forum_data($forum_data); + + if (!count($errors)) + { + $forum_perm_from = $request->variable('forum_perm_from', 0); + $cache->destroy('sql', FORUMS_TABLE); + + $copied_permissions = false; + // Copy permissions? + if ($forum_perm_from && $forum_perm_from != $forum_data['forum_id'] && + ($action != 'edit' || empty($forum_id) || ($auth->acl_get('a_fauth') && $auth->acl_get('a_authusers') && $auth->acl_get('a_authgroups') && $auth->acl_get('a_mauth')))) + { + copy_forum_permissions($forum_perm_from, $forum_data['forum_id'], ($action == 'edit') ? true : false); + phpbb_cache_moderators($db, $cache, $auth); + $copied_permissions = true; + } +/* Commented out because of questionable UI workflow - re-visit for 3.0.7 + else if (!$this->parent_id && $action != 'edit' && $auth->acl_get('a_fauth') && $auth->acl_get('a_authusers') && $auth->acl_get('a_authgroups') && $auth->acl_get('a_mauth')) + { + $this->copy_permission_page($forum_data); + return; + } +*/ + $auth->acl_clear_prefetch(); + + $acl_url = '&mode=setting_forum_local&forum_id[]=' . $forum_data['forum_id']; + + $message = ($action == 'add') ? $user->lang['FORUM_CREATED'] : $user->lang['FORUM_UPDATED']; + + // redirect directly to permission settings screen if authed + if ($action == 'add' && !$copied_permissions && $auth->acl_get('a_fauth')) + { + $message .= '

' . sprintf($user->lang['REDIRECT_ACL'], '', ''); + + meta_refresh(4, append_sid("{$phpbb_admin_path}index.$phpEx", 'i=permissions' . $acl_url)); + } + + trigger_error($message . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id)); + } + + break; + } + } + + switch ($action) + { + case 'move_up': + case 'move_down': + + if (!$forum_id) + { + trigger_error($user->lang['NO_FORUM'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $sql = 'SELECT * + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['NO_FORUM'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $move_forum_name = $this->move_forum_by($row, $action, 1); + + if ($move_forum_name !== false) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_' . strtoupper($action), false, array($row['forum_name'], $move_forum_name)); + $cache->destroy('sql', FORUMS_TABLE); + } + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array('success' => ($move_forum_name !== false))); + } + + break; + + case 'sync': + if (!$forum_id) + { + trigger_error($user->lang['NO_FORUM'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + @set_time_limit(0); + + $sql = 'SELECT forum_name, (forum_topics_approved + forum_topics_unapproved + forum_topics_softdeleted) AS total_topics + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['NO_FORUM'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + if ($row['total_topics']) + { + $sql = 'SELECT MIN(topic_id) as min_topic_id, MAX(topic_id) as max_topic_id + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = ' . $forum_id; + $result = $db->sql_query($sql); + $row2 = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Typecast to int if there is no data available + $row2['min_topic_id'] = (int) $row2['min_topic_id']; + $row2['max_topic_id'] = (int) $row2['max_topic_id']; + + $start = $request->variable('start', $row2['min_topic_id']); + + $batch_size = 2000; + $end = $start + $batch_size; + + // Sync all topics in batch mode... + sync('topic', 'range', 'topic_id BETWEEN ' . $start . ' AND ' . $end, true, true); + + if ($end < $row2['max_topic_id']) + { + // We really need to find a way of showing statistics... no progress here + $sql = 'SELECT COUNT(topic_id) as num_topics + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = ' . $forum_id . ' + AND topic_id BETWEEN ' . $start . ' AND ' . $end; + $result = $db->sql_query($sql); + $topics_done = $request->variable('topics_done', 0) + (int) $db->sql_fetchfield('num_topics'); + $db->sql_freeresult($result); + + $start += $batch_size; + + $url = $this->u_action . "&parent_id={$this->parent_id}&f=$forum_id&action=sync&start=$start&topics_done=$topics_done&total={$row['total_topics']}"; + + meta_refresh(0, $url); + + $template->assign_vars(array( + 'U_PROGRESS_BAR' => $this->u_action . "&action=progress_bar&start=$topics_done&total={$row['total_topics']}", + 'UA_PROGRESS_BAR' => addslashes($this->u_action . "&action=progress_bar&start=$topics_done&total={$row['total_topics']}"), + 'S_CONTINUE_SYNC' => true, + 'L_PROGRESS_EXPLAIN' => sprintf($user->lang['SYNC_IN_PROGRESS_EXPLAIN'], $topics_done, $row['total_topics'])) + ); + + return; + } + } + + $url = $this->u_action . "&parent_id={$this->parent_id}&f=$forum_id&action=sync_forum"; + meta_refresh(0, $url); + + $template->assign_vars(array( + 'U_PROGRESS_BAR' => $this->u_action . '&action=progress_bar', + 'UA_PROGRESS_BAR' => addslashes($this->u_action . '&action=progress_bar'), + 'S_CONTINUE_SYNC' => true, + 'L_PROGRESS_EXPLAIN' => sprintf($user->lang['SYNC_IN_PROGRESS_EXPLAIN'], 0, $row['total_topics'])) + ); + + return; + + break; + + case 'sync_forum': + + $sql = 'SELECT forum_name, forum_type + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['NO_FORUM'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + sync('forum', 'forum_id', $forum_id, false, true); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_SYNC', false, array($row['forum_name'])); + + $cache->destroy('sql', FORUMS_TABLE); + + $template->assign_var('L_FORUM_RESYNCED', sprintf($user->lang['FORUM_RESYNCED'], $row['forum_name'])); + + break; + + case 'add': + case 'edit': + + if ($update) + { + $forum_data['forum_flags'] = 0; + $forum_data['forum_flags'] += ($request->variable('forum_link_track', false)) ? FORUM_FLAG_LINK_TRACK : 0; + $forum_data['forum_flags'] += ($request->variable('prune_old_polls', false)) ? FORUM_FLAG_PRUNE_POLL : 0; + $forum_data['forum_flags'] += ($request->variable('prune_announce', false)) ? FORUM_FLAG_PRUNE_ANNOUNCE : 0; + $forum_data['forum_flags'] += ($request->variable('prune_sticky', false)) ? FORUM_FLAG_PRUNE_STICKY : 0; + $forum_data['forum_flags'] += ($forum_data['show_active']) ? FORUM_FLAG_ACTIVE_TOPICS : 0; + $forum_data['forum_flags'] += ($request->variable('enable_post_review', true)) ? FORUM_FLAG_POST_REVIEW : 0; + $forum_data['forum_flags'] += ($request->variable('enable_quick_reply', false)) ? FORUM_FLAG_QUICK_REPLY : 0; + } + + // Initialise $row, so we always have it in the event + $row = array(); + + // Show form to create/modify a forum + if ($action == 'edit') + { + $this->page_title = 'EDIT_FORUM'; + $row = $this->get_forum_info($forum_id); + $old_forum_type = $row['forum_type']; + + if (!$update) + { + $forum_data = $row; + } + else + { + $forum_data['left_id'] = $row['left_id']; + $forum_data['right_id'] = $row['right_id']; + } + + // Make sure no direct child forums are able to be selected as parents. + $exclude_forums = array(); + foreach (get_forum_branch($forum_id, 'children') as $row) + { + $exclude_forums[] = $row['forum_id']; + } + + $parents_list = make_forum_select($forum_data['parent_id'], $exclude_forums, false, false, false); + + $forum_data['forum_password_confirm'] = $forum_data['forum_password']; + } + else + { + $this->page_title = 'CREATE_FORUM'; + + $forum_id = $this->parent_id; + $parents_list = make_forum_select($this->parent_id, false, false, false, false); + + // Fill forum data with default values + if (!$update) + { + $forum_data = array( + 'parent_id' => $this->parent_id, + 'forum_type' => FORUM_POST, + 'forum_status' => ITEM_UNLOCKED, + 'forum_name' => $request->variable('forum_name', '', true), + 'forum_link' => '', + 'forum_link_track' => false, + 'forum_desc' => '', + 'forum_rules' => '', + 'forum_rules_link' => '', + 'forum_image' => '', + 'forum_style' => 0, + 'display_subforum_list' => true, + 'display_on_index' => false, + 'forum_topics_per_page' => 0, + 'enable_indexing' => true, + 'enable_icons' => false, + 'enable_prune' => false, + 'prune_days' => 7, + 'prune_viewed' => 7, + 'prune_freq' => 1, + 'enable_shadow_prune' => false, + 'prune_shadow_days' => 7, + 'prune_shadow_freq' => 1, + 'forum_flags' => FORUM_FLAG_POST_REVIEW + FORUM_FLAG_ACTIVE_TOPICS, + 'forum_options' => 0, + 'forum_password' => '', + 'forum_password_confirm'=> '', + ); + } + } + + /** + * Initialise data before we display the add/edit form + * + * @event core.acp_manage_forums_initialise_data + * @var string action Type of the action: add|edit + * @var bool update Do we display the form only + * or did the user press submit + * @var int forum_id When editing: the forum id, + * when creating: the parent forum id + * @var array row Array with current forum data + * empty when creating new forum + * @var array forum_data Array with new forum data + * @var string parents_list List of parent options + * @since 3.1.0-a1 + */ + $vars = array('action', 'update', 'forum_id', 'row', 'forum_data', 'parents_list'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_initialise_data', compact($vars))); + + $forum_rules_data = array( + 'text' => $forum_data['forum_rules'], + 'allow_bbcode' => true, + 'allow_smilies' => true, + 'allow_urls' => true + ); + + $forum_desc_data = array( + 'text' => $forum_data['forum_desc'], + 'allow_bbcode' => true, + 'allow_smilies' => true, + 'allow_urls' => true + ); + + $forum_rules_preview = ''; + + // Parse rules if specified + if ($forum_data['forum_rules']) + { + if (!isset($forum_data['forum_rules_uid'])) + { + // Before we are able to display the preview and plane text, we need to parse our $request->variable()'d value... + $forum_data['forum_rules_uid'] = ''; + $forum_data['forum_rules_bitfield'] = ''; + $forum_data['forum_rules_options'] = 0; + + generate_text_for_storage($forum_data['forum_rules'], $forum_data['forum_rules_uid'], $forum_data['forum_rules_bitfield'], $forum_data['forum_rules_options'], $request->variable('rules_allow_bbcode', false), $request->variable('rules_allow_urls', false), $request->variable('rules_allow_smilies', false)); + } + + // Generate preview content + $forum_rules_preview = generate_text_for_display($forum_data['forum_rules'], $forum_data['forum_rules_uid'], $forum_data['forum_rules_bitfield'], $forum_data['forum_rules_options']); + + // decode... + $forum_rules_data = generate_text_for_edit($forum_data['forum_rules'], $forum_data['forum_rules_uid'], $forum_data['forum_rules_options']); + } + + // Parse desciption if specified + if ($forum_data['forum_desc']) + { + if (!isset($forum_data['forum_desc_uid'])) + { + // Before we are able to display the preview and plane text, we need to parse our $request->variable()'d value... + $forum_data['forum_desc_uid'] = ''; + $forum_data['forum_desc_bitfield'] = ''; + $forum_data['forum_desc_options'] = 0; + + generate_text_for_storage($forum_data['forum_desc'], $forum_data['forum_desc_uid'], $forum_data['forum_desc_bitfield'], $forum_data['forum_desc_options'], $request->variable('desc_allow_bbcode', false), $request->variable('desc_allow_urls', false), $request->variable('desc_allow_smilies', false)); + } + + // decode... + $forum_desc_data = generate_text_for_edit($forum_data['forum_desc'], $forum_data['forum_desc_uid'], $forum_data['forum_desc_options']); + } + + $forum_type_options = ''; + $forum_type_ary = array(FORUM_CAT => 'CAT', FORUM_POST => 'FORUM', FORUM_LINK => 'LINK'); + + foreach ($forum_type_ary as $value => $lang) + { + $forum_type_options .= ''; + } + + $styles_list = style_select($forum_data['forum_style'], true); + + $statuslist = ''; + + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type = ' . FORUM_POST . " + AND forum_id <> $forum_id"; + $result = $db->sql_query_limit($sql, 1); + + $postable_forum_exists = false; + if ($db->sql_fetchrow($result)) + { + $postable_forum_exists = true; + } + $db->sql_freeresult($result); + + // Subforum move options + if ($action == 'edit' && $forum_data['forum_type'] == FORUM_CAT) + { + $subforums_id = array(); + $subforums = get_forum_branch($forum_id, 'children'); + + foreach ($subforums as $row) + { + $subforums_id[] = $row['forum_id']; + } + + $forums_list = make_forum_select($forum_data['parent_id'], $subforums_id); + + if ($postable_forum_exists) + { + $template->assign_vars(array( + 'S_MOVE_FORUM_OPTIONS' => make_forum_select($forum_data['parent_id'], $subforums_id)) // , false, true, false??? + ); + } + + $template->assign_vars(array( + 'S_HAS_SUBFORUMS' => ($forum_data['right_id'] - $forum_data['left_id'] > 1) ? true : false, + 'S_FORUMS_LIST' => $forums_list) + ); + } + else if ($postable_forum_exists) + { + $template->assign_vars(array( + 'S_MOVE_FORUM_OPTIONS' => make_forum_select($forum_data['parent_id'], $forum_id, false, true, false)) + ); + } + + $s_show_display_on_index = false; + + if ($forum_data['parent_id'] > 0) + { + // if this forum is a subforum put the "display on index" checkbox + if ($parent_info = $this->get_forum_info($forum_data['parent_id'])) + { + if ($parent_info['parent_id'] > 0 || $parent_info['forum_type'] == FORUM_CAT) + { + $s_show_display_on_index = true; + } + } + } + + if (strlen($forum_data['forum_password']) == 32) + { + $errors[] = $user->lang['FORUM_PASSWORD_OLD']; + } + + $template_data = array( + 'S_EDIT_FORUM' => true, + 'S_ERROR' => (count($errors)) ? true : false, + 'S_PARENT_ID' => $this->parent_id, + 'S_FORUM_PARENT_ID' => $forum_data['parent_id'], + 'S_ADD_ACTION' => ($action == 'add') ? true : false, + + 'U_BACK' => $this->u_action . '&parent_id=' . $this->parent_id, + 'U_EDIT_ACTION' => $this->u_action . "&parent_id={$this->parent_id}&action=$action&f=$forum_id", + + 'L_COPY_PERMISSIONS_EXPLAIN' => $user->lang['COPY_PERMISSIONS_' . strtoupper($action) . '_EXPLAIN'], + 'L_TITLE' => $user->lang[$this->page_title], + 'ERROR_MSG' => (count($errors)) ? implode('
', $errors) : '', + + 'FORUM_NAME' => $forum_data['forum_name'], + 'FORUM_DATA_LINK' => $forum_data['forum_link'], + 'FORUM_IMAGE' => $forum_data['forum_image'], + 'FORUM_IMAGE_SRC' => ($forum_data['forum_image']) ? $phpbb_root_path . $forum_data['forum_image'] : '', + 'FORUM_POST' => FORUM_POST, + 'FORUM_LINK' => FORUM_LINK, + 'FORUM_CAT' => FORUM_CAT, + 'PRUNE_FREQ' => $forum_data['prune_freq'], + 'PRUNE_DAYS' => $forum_data['prune_days'], + 'PRUNE_VIEWED' => $forum_data['prune_viewed'], + 'PRUNE_SHADOW_FREQ' => $forum_data['prune_shadow_freq'], + 'PRUNE_SHADOW_DAYS' => $forum_data['prune_shadow_days'], + 'TOPICS_PER_PAGE' => $forum_data['forum_topics_per_page'], + 'FORUM_RULES_LINK' => $forum_data['forum_rules_link'], + 'FORUM_RULES' => $forum_data['forum_rules'], + 'FORUM_RULES_PREVIEW' => $forum_rules_preview, + 'FORUM_RULES_PLAIN' => $forum_rules_data['text'], + 'S_BBCODE_CHECKED' => ($forum_rules_data['allow_bbcode']) ? true : false, + 'S_SMILIES_CHECKED' => ($forum_rules_data['allow_smilies']) ? true : false, + 'S_URLS_CHECKED' => ($forum_rules_data['allow_urls']) ? true : false, + 'S_FORUM_PASSWORD_SET' => (empty($forum_data['forum_password'])) ? false : true, + + 'FORUM_DESC' => $forum_desc_data['text'], + 'S_DESC_BBCODE_CHECKED' => ($forum_desc_data['allow_bbcode']) ? true : false, + 'S_DESC_SMILIES_CHECKED' => ($forum_desc_data['allow_smilies']) ? true : false, + 'S_DESC_URLS_CHECKED' => ($forum_desc_data['allow_urls']) ? true : false, + + 'S_FORUM_TYPE_OPTIONS' => $forum_type_options, + 'S_STATUS_OPTIONS' => $statuslist, + 'S_PARENT_OPTIONS' => $parents_list, + 'S_STYLES_OPTIONS' => $styles_list, + 'S_FORUM_OPTIONS' => make_forum_select(($action == 'add') ? $forum_data['parent_id'] : false, ($action == 'edit') ? $forum_data['forum_id'] : false, false, false, false), + 'S_SHOW_DISPLAY_ON_INDEX' => $s_show_display_on_index, + 'S_FORUM_POST' => ($forum_data['forum_type'] == FORUM_POST) ? true : false, + 'S_FORUM_ORIG_POST' => (isset($old_forum_type) && $old_forum_type == FORUM_POST) ? true : false, + 'S_FORUM_ORIG_CAT' => (isset($old_forum_type) && $old_forum_type == FORUM_CAT) ? true : false, + 'S_FORUM_ORIG_LINK' => (isset($old_forum_type) && $old_forum_type == FORUM_LINK) ? true : false, + 'S_FORUM_LINK' => ($forum_data['forum_type'] == FORUM_LINK) ? true : false, + 'S_FORUM_CAT' => ($forum_data['forum_type'] == FORUM_CAT) ? true : false, + 'S_ENABLE_INDEXING' => ($forum_data['enable_indexing']) ? true : false, + 'S_TOPIC_ICONS' => ($forum_data['enable_icons']) ? true : false, + 'S_DISPLAY_SUBFORUM_LIST' => ($forum_data['display_subforum_list']) ? true : false, + 'S_DISPLAY_ON_INDEX' => ($forum_data['display_on_index']) ? true : false, + 'S_PRUNE_ENABLE' => ($forum_data['enable_prune']) ? true : false, + 'S_PRUNE_SHADOW_ENABLE' => ($forum_data['enable_shadow_prune']) ? true : false, + 'S_FORUM_LINK_TRACK' => ($forum_data['forum_flags'] & FORUM_FLAG_LINK_TRACK) ? true : false, + 'S_PRUNE_OLD_POLLS' => ($forum_data['forum_flags'] & FORUM_FLAG_PRUNE_POLL) ? true : false, + 'S_PRUNE_ANNOUNCE' => ($forum_data['forum_flags'] & FORUM_FLAG_PRUNE_ANNOUNCE) ? true : false, + 'S_PRUNE_STICKY' => ($forum_data['forum_flags'] & FORUM_FLAG_PRUNE_STICKY) ? true : false, + 'S_DISPLAY_ACTIVE_TOPICS' => ($forum_data['forum_type'] == FORUM_POST) ? ($forum_data['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS) : true, + 'S_ENABLE_ACTIVE_TOPICS' => ($forum_data['forum_type'] == FORUM_CAT) ? ($forum_data['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS) : false, + 'S_ENABLE_POST_REVIEW' => ($forum_data['forum_flags'] & FORUM_FLAG_POST_REVIEW) ? true : false, + 'S_ENABLE_QUICK_REPLY' => ($forum_data['forum_flags'] & FORUM_FLAG_QUICK_REPLY) ? true : false, + 'S_CAN_COPY_PERMISSIONS' => ($action != 'edit' || empty($forum_id) || ($auth->acl_get('a_fauth') && $auth->acl_get('a_authusers') && $auth->acl_get('a_authgroups') && $auth->acl_get('a_mauth'))) ? true : false, + ); + + /** + * Modify forum template data before we display the form + * + * @event core.acp_manage_forums_display_form + * @var string action Type of the action: add|edit + * @var bool update Do we display the form only + * or did the user press submit + * @var int forum_id When editing: the forum id, + * when creating: the parent forum id + * @var array row Array with current forum data + * empty when creating new forum + * @var array forum_data Array with new forum data + * @var string parents_list List of parent options + * @var array errors Array of errors, if you add errors + * ensure to update the template variables + * S_ERROR and ERROR_MSG to display it + * @var array template_data Array with new forum data + * @since 3.1.0-a1 + */ + $vars = array( + 'action', + 'update', + 'forum_id', + 'row', + 'forum_data', + 'parents_list', + 'errors', + 'template_data', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_display_form', compact($vars))); + + $template->assign_vars($template_data); + + return; + + break; + + case 'delete': + + if (!$forum_id) + { + trigger_error($user->lang['NO_FORUM'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $forum_data = $this->get_forum_info($forum_id); + + $subforums_id = array(); + $subforums = get_forum_branch($forum_id, 'children'); + + foreach ($subforums as $row) + { + $subforums_id[] = $row['forum_id']; + } + + $forums_list = make_forum_select($forum_data['parent_id'], $subforums_id); + + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type = ' . FORUM_POST . " + AND forum_id <> $forum_id"; + $result = $db->sql_query_limit($sql, 1); + + if ($db->sql_fetchrow($result)) + { + $template->assign_vars(array( + 'S_MOVE_FORUM_OPTIONS' => make_forum_select($forum_data['parent_id'], $subforums_id, false, true)) // , false, true, false??? + ); + } + $db->sql_freeresult($result); + + $parent_id = ($this->parent_id == $forum_id) ? 0 : $this->parent_id; + + $template->assign_vars(array( + 'S_DELETE_FORUM' => true, + 'U_ACTION' => $this->u_action . "&parent_id={$parent_id}&action=delete&f=$forum_id", + 'U_BACK' => $this->u_action . '&parent_id=' . $this->parent_id, + + 'FORUM_NAME' => $forum_data['forum_name'], + 'S_FORUM_POST' => ($forum_data['forum_type'] == FORUM_POST) ? true : false, + 'S_FORUM_LINK' => ($forum_data['forum_type'] == FORUM_LINK) ? true : false, + 'S_HAS_SUBFORUMS' => ($forum_data['right_id'] - $forum_data['left_id'] > 1) ? true : false, + 'S_FORUMS_LIST' => $forums_list, + 'S_ERROR' => (count($errors)) ? true : false, + 'ERROR_MSG' => (count($errors)) ? implode('
', $errors) : '') + ); + + return; + break; + + case 'copy_perm': + $forum_perm_from = $request->variable('forum_perm_from', 0); + + // Copy permissions? + if (!empty($forum_perm_from) && $forum_perm_from != $forum_id) + { + copy_forum_permissions($forum_perm_from, $forum_id, true); + phpbb_cache_moderators($db, $cache, $auth); + $auth->acl_clear_prefetch(); + $cache->destroy('sql', FORUMS_TABLE); + + $acl_url = '&mode=setting_forum_local&forum_id[]=' . $forum_id; + + $message = $user->lang['FORUM_UPDATED']; + + // Redirect to permissions + if ($auth->acl_get('a_fauth')) + { + $message .= '

' . sprintf($user->lang['REDIRECT_ACL'], '', ''); + } + + trigger_error($message . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id)); + } + + break; + } + + // Default management page + if (!$this->parent_id) + { + $navigation = $user->lang['FORUM_INDEX']; + } + else + { + $navigation = '' . $user->lang['FORUM_INDEX'] . ''; + + $forums_nav = get_forum_branch($this->parent_id, 'parents', 'descending'); + foreach ($forums_nav as $row) + { + if ($row['forum_id'] == $this->parent_id) + { + $navigation .= ' -> ' . $row['forum_name']; + } + else + { + $navigation .= ' -> ' . $row['forum_name'] . ''; + } + } + } + + // Jumpbox + $forum_box = make_forum_select($this->parent_id, false, false, false, false); //make_forum_select($this->parent_id); + + if ($action == 'sync' || $action == 'sync_forum') + { + $template->assign_var('S_RESYNCED', true); + } + + $sql = 'SELECT * + FROM ' . FORUMS_TABLE . " + WHERE parent_id = $this->parent_id + ORDER BY left_id"; + $result = $db->sql_query($sql); + + $rowset = array(); + while ($row = $db->sql_fetchrow($result)) + { + $rowset[(int) $row['forum_id']] = $row; + } + $db->sql_freeresult($result); + + /** + * Modify the forum list data + * + * @event core.acp_manage_forums_modify_forum_list + * @var array rowset Array with the forums list data + * @since 3.1.10-RC1 + */ + $vars = array('rowset'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_modify_forum_list', compact($vars))); + + if (!empty($rowset)) + { + foreach ($rowset as $row) + { + $forum_type = $row['forum_type']; + + if ($row['forum_status'] == ITEM_LOCKED) + { + $folder_image = '' . $user->lang['LOCKED'] . ''; + } + else + { + switch ($forum_type) + { + case FORUM_LINK: + $folder_image = '' . $user->lang['LINK'] . ''; + break; + + default: + $folder_image = ($row['left_id'] + 1 != $row['right_id']) ? '' . $user->lang['SUBFORUM'] . '' : '' . $user->lang['FOLDER'] . ''; + break; + } + } + + $url = $this->u_action . "&parent_id=$this->parent_id&f={$row['forum_id']}"; + + $template->assign_block_vars('forums', array( + 'FOLDER_IMAGE' => $folder_image, + 'FORUM_IMAGE' => ($row['forum_image']) ? '' : '', + 'FORUM_IMAGE_SRC' => ($row['forum_image']) ? $phpbb_root_path . $row['forum_image'] : '', + 'FORUM_NAME' => $row['forum_name'], + 'FORUM_DESCRIPTION' => generate_text_for_display($row['forum_desc'], $row['forum_desc_uid'], $row['forum_desc_bitfield'], $row['forum_desc_options']), + 'FORUM_TOPICS' => $row['forum_topics_approved'], + 'FORUM_POSTS' => $row['forum_posts_approved'], + + 'S_FORUM_LINK' => ($forum_type == FORUM_LINK) ? true : false, + 'S_FORUM_POST' => ($forum_type == FORUM_POST) ? true : false, + + 'U_FORUM' => $this->u_action . '&parent_id=' . $row['forum_id'], + 'U_MOVE_UP' => $url . '&action=move_up', + 'U_MOVE_DOWN' => $url . '&action=move_down', + 'U_EDIT' => $url . '&action=edit', + 'U_DELETE' => $url . '&action=delete', + 'U_SYNC' => $url . '&action=sync') + ); + } + } + else if ($this->parent_id) + { + $row = $this->get_forum_info($this->parent_id); + + $url = $this->u_action . '&parent_id=' . $this->parent_id . '&f=' . $row['forum_id']; + + $template->assign_vars(array( + 'S_NO_FORUMS' => true, + + 'U_EDIT' => $url . '&action=edit', + 'U_DELETE' => $url . '&action=delete', + 'U_SYNC' => $url . '&action=sync') + ); + } + unset($rowset); + + $template->assign_vars(array( + 'ERROR_MSG' => (count($errors)) ? implode('
', $errors) : '', + 'NAVIGATION' => $navigation, + 'FORUM_BOX' => $forum_box, + 'U_SEL_ACTION' => $this->u_action, + 'U_ACTION' => $this->u_action . '&parent_id=' . $this->parent_id, + + 'U_PROGRESS_BAR' => $this->u_action . '&action=progress_bar', + 'UA_PROGRESS_BAR' => addslashes($this->u_action . '&action=progress_bar'), + )); + } + + /** + * Get forum details + */ + function get_forum_info($forum_id) + { + global $db; + + $sql = 'SELECT * + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error("Forum #$forum_id does not exist", E_USER_ERROR); + } + + return $row; + } + + /** + * Update forum data + */ + function update_forum_data(&$forum_data_ary) + { + global $db, $user, $cache, $phpbb_root_path, $phpbb_container, $phpbb_dispatcher, $phpbb_log, $request; + + $errors = array(); + + $forum_data = $forum_data_ary; + /** + * Validate the forum data before we create/update the forum + * + * @event core.acp_manage_forums_validate_data + * @var array forum_data Array with new forum data + * @var array errors Array of errors, should be strings and not + * language key. + * @since 3.1.0-a1 + */ + $vars = array('forum_data', 'errors'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_validate_data', compact($vars))); + $forum_data_ary = $forum_data; + unset($forum_data); + + if ($forum_data_ary['forum_name'] == '') + { + $errors[] = $user->lang['FORUM_NAME_EMPTY']; + } + + if (utf8_strlen($forum_data_ary['forum_desc']) > 4000) + { + $errors[] = $user->lang['FORUM_DESC_TOO_LONG']; + } + + if (utf8_strlen($forum_data_ary['forum_rules']) > 4000) + { + $errors[] = $user->lang['FORUM_RULES_TOO_LONG']; + } + + if ($forum_data_ary['forum_password'] || $forum_data_ary['forum_password_confirm']) + { + if ($forum_data_ary['forum_password'] != $forum_data_ary['forum_password_confirm']) + { + $forum_data_ary['forum_password'] = $forum_data_ary['forum_password_confirm'] = ''; + $errors[] = $user->lang['FORUM_PASSWORD_MISMATCH']; + } + } + + if ($forum_data_ary['prune_days'] < 0 || $forum_data_ary['prune_viewed'] < 0 || $forum_data_ary['prune_freq'] < 0) + { + $forum_data_ary['prune_days'] = $forum_data_ary['prune_viewed'] = $forum_data_ary['prune_freq'] = 0; + $errors[] = $user->lang['FORUM_DATA_NEGATIVE']; + } + + $range_test_ary = array( + array('lang' => 'FORUM_TOPICS_PAGE', 'value' => $forum_data_ary['forum_topics_per_page'], 'column_type' => 'USINT:0'), + ); + + if (!empty($forum_data_ary['forum_image']) && !file_exists($phpbb_root_path . $forum_data_ary['forum_image'])) + { + $errors[] = $user->lang['FORUM_IMAGE_NO_EXIST']; + } + + validate_range($range_test_ary, $errors); + + // Set forum flags + // 1 = link tracking + // 2 = prune old polls + // 4 = prune announcements + // 8 = prune stickies + // 16 = show active topics + // 32 = enable post review + $forum_data_ary['forum_flags'] = 0; + $forum_data_ary['forum_flags'] += ($forum_data_ary['forum_link_track']) ? FORUM_FLAG_LINK_TRACK : 0; + $forum_data_ary['forum_flags'] += ($forum_data_ary['prune_old_polls']) ? FORUM_FLAG_PRUNE_POLL : 0; + $forum_data_ary['forum_flags'] += ($forum_data_ary['prune_announce']) ? FORUM_FLAG_PRUNE_ANNOUNCE : 0; + $forum_data_ary['forum_flags'] += ($forum_data_ary['prune_sticky']) ? FORUM_FLAG_PRUNE_STICKY : 0; + $forum_data_ary['forum_flags'] += ($forum_data_ary['show_active']) ? FORUM_FLAG_ACTIVE_TOPICS : 0; + $forum_data_ary['forum_flags'] += ($forum_data_ary['enable_post_review']) ? FORUM_FLAG_POST_REVIEW : 0; + $forum_data_ary['forum_flags'] += ($forum_data_ary['enable_quick_reply']) ? FORUM_FLAG_QUICK_REPLY : 0; + + // Unset data that are not database fields + $forum_data_sql = $forum_data_ary; + + unset($forum_data_sql['forum_link_track']); + unset($forum_data_sql['prune_old_polls']); + unset($forum_data_sql['prune_announce']); + unset($forum_data_sql['prune_sticky']); + unset($forum_data_sql['show_active']); + unset($forum_data_sql['enable_post_review']); + unset($forum_data_sql['enable_quick_reply']); + unset($forum_data_sql['forum_password_confirm']); + + // What are we going to do tonight Brain? The same thing we do everynight, + // try to take over the world ... or decide whether to continue update + // and if so, whether it's a new forum/cat/link or an existing one + if (count($errors)) + { + return $errors; + } + + // As we don't know the old password, it's kinda tricky to detect changes + if ($forum_data_sql['forum_password_unset']) + { + $forum_data_sql['forum_password'] = ''; + } + else if (empty($forum_data_sql['forum_password'])) + { + unset($forum_data_sql['forum_password']); + } + else + { + // Instantiate passwords manager + /* @var $passwords_manager \phpbb\passwords\manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + + $forum_data_sql['forum_password'] = $passwords_manager->hash($forum_data_sql['forum_password']); + } + unset($forum_data_sql['forum_password_unset']); + + $forum_data = $forum_data_ary; + /** + * Remove invalid values from forum_data_sql that should not be updated + * + * @event core.acp_manage_forums_update_data_before + * @var array forum_data Array with forum data + * @var array forum_data_sql Array with data we are going to update + * If forum_data_sql[forum_id] is set, we update + * that forum, otherwise a new one is created. + * @since 3.1.0-a1 + */ + $vars = array('forum_data', 'forum_data_sql'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_update_data_before', compact($vars))); + $forum_data_ary = $forum_data; + unset($forum_data); + + $is_new_forum = !isset($forum_data_sql['forum_id']); + + if ($is_new_forum) + { + // no forum_id means we're creating a new forum + unset($forum_data_sql['type_action']); + + if ($forum_data_sql['parent_id']) + { + $sql = 'SELECT left_id, right_id, forum_type + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $forum_data_sql['parent_id']; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['PARENT_NOT_EXIST'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + if ($row['forum_type'] == FORUM_LINK) + { + $errors[] = $user->lang['PARENT_IS_LINK_FORUM']; + return $errors; + } + + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE left_id > ' . $row['right_id']; + $db->sql_query($sql); + + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET right_id = right_id + 2 + WHERE ' . $row['left_id'] . ' BETWEEN left_id AND right_id'; + $db->sql_query($sql); + + $forum_data_sql['left_id'] = $row['right_id']; + $forum_data_sql['right_id'] = $row['right_id'] + 1; + } + else + { + $sql = 'SELECT MAX(right_id) AS right_id + FROM ' . FORUMS_TABLE; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $forum_data_sql['left_id'] = $row['right_id'] + 1; + $forum_data_sql['right_id'] = $row['right_id'] + 2; + } + + $sql = 'INSERT INTO ' . FORUMS_TABLE . ' ' . $db->sql_build_array('INSERT', $forum_data_sql); + $db->sql_query($sql); + + $forum_data_ary['forum_id'] = $db->sql_nextid(); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_ADD', false, array($forum_data_ary['forum_name'])); + } + else + { + $row = $this->get_forum_info($forum_data_sql['forum_id']); + + if ($row['forum_type'] == FORUM_POST && $row['forum_type'] != $forum_data_sql['forum_type']) + { + // Has subforums and want to change into a link? + if ($row['right_id'] - $row['left_id'] > 1 && $forum_data_sql['forum_type'] == FORUM_LINK) + { + $errors[] = $user->lang['FORUM_WITH_SUBFORUMS_NOT_TO_LINK']; + return $errors; + } + + // we're turning a postable forum into a non-postable forum + if ($forum_data_sql['type_action'] == 'move') + { + $to_forum_id = $request->variable('to_forum_id', 0); + + if ($to_forum_id) + { + $errors = $this->move_forum_content($forum_data_sql['forum_id'], $to_forum_id); + } + else + { + return array($user->lang['NO_DESTINATION_FORUM']); + } + } + else if ($forum_data_sql['type_action'] == 'delete') + { + $errors = $this->delete_forum_content($forum_data_sql['forum_id']); + } + else + { + return array($user->lang['NO_FORUM_ACTION']); + } + + $forum_data_sql['forum_posts_approved'] = $forum_data_sql['forum_posts_unapproved'] = $forum_data_sql['forum_posts_softdeleted'] = $forum_data_sql['forum_topics_approved'] = $forum_data_sql['forum_topics_unapproved'] = $forum_data_sql['forum_topics_softdeleted'] = 0; + $forum_data_sql['forum_last_post_id'] = $forum_data_sql['forum_last_poster_id'] = $forum_data_sql['forum_last_post_time'] = 0; + $forum_data_sql['forum_last_poster_name'] = $forum_data_sql['forum_last_poster_colour'] = ''; + } + else if ($row['forum_type'] == FORUM_CAT && $forum_data_sql['forum_type'] == FORUM_LINK) + { + // Has subforums? + if ($row['right_id'] - $row['left_id'] > 1) + { + // We are turning a category into a link - but need to decide what to do with the subforums. + $action_subforums = $request->variable('action_subforums', ''); + $subforums_to_id = $request->variable('subforums_to_id', 0); + + if ($action_subforums == 'delete') + { + $rows = get_forum_branch($row['forum_id'], 'children', 'descending', false); + + foreach ($rows as $_row) + { + // Do not remove the forum id we are about to change. ;) + if ($_row['forum_id'] == $row['forum_id']) + { + continue; + } + + $forum_ids[] = $_row['forum_id']; + $errors = array_merge($errors, $this->delete_forum_content($_row['forum_id'])); + } + + if (count($errors)) + { + return $errors; + } + + if (count($forum_ids)) + { + $sql = 'DELETE FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids); + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids); + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_USERS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids); + $db->sql_query($sql); + + // Delete forum ids from extension groups table + $sql = 'SELECT group_id, allowed_forums + FROM ' . EXTENSION_GROUPS_TABLE; + $result = $db->sql_query($sql); + + while ($_row = $db->sql_fetchrow($result)) + { + if (!$_row['allowed_forums']) + { + continue; + } + + $allowed_forums = unserialize(trim($_row['allowed_forums'])); + $allowed_forums = array_diff($allowed_forums, $forum_ids); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . " + SET allowed_forums = '" . ((count($allowed_forums)) ? serialize($allowed_forums) : '') . "' + WHERE group_id = {$_row['group_id']}"; + $db->sql_query($sql); + } + $db->sql_freeresult($result); + + $cache->destroy('_extensions'); + } + } + else if ($action_subforums == 'move') + { + if (!$subforums_to_id) + { + return array($user->lang['NO_DESTINATION_FORUM']); + } + + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $subforums_to_id; + $result = $db->sql_query($sql); + $_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$_row) + { + return array($user->lang['NO_FORUM']); + } + + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . " + WHERE parent_id = {$row['forum_id']}"; + $result = $db->sql_query($sql); + + while ($_row = $db->sql_fetchrow($result)) + { + $this->move_forum($_row['forum_id'], $subforums_to_id); + } + $db->sql_freeresult($result); + + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET parent_id = $subforums_to_id + WHERE parent_id = {$row['forum_id']}"; + $db->sql_query($sql); + } + + // Adjust the left/right id + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET right_id = left_id + 1 + WHERE forum_id = ' . $row['forum_id']; + $db->sql_query($sql); + } + } + else if ($row['forum_type'] == FORUM_CAT && $forum_data_sql['forum_type'] == FORUM_POST) + { + // Changing a category to a forum? Reset the data (you can't post directly in a cat, you must use a forum) + $forum_data_sql['forum_posts_approved'] = 0; + $forum_data_sql['forum_posts_unapproved'] = 0; + $forum_data_sql['forum_posts_softdeleted'] = 0; + $forum_data_sql['forum_topics_approved'] = 0; + $forum_data_sql['forum_topics_unapproved'] = 0; + $forum_data_sql['forum_topics_softdeleted'] = 0; + $forum_data_sql['forum_last_post_id'] = 0; + $forum_data_sql['forum_last_post_subject'] = ''; + $forum_data_sql['forum_last_post_time'] = 0; + $forum_data_sql['forum_last_poster_id'] = 0; + $forum_data_sql['forum_last_poster_name'] = ''; + $forum_data_sql['forum_last_poster_colour'] = ''; + } + + if (count($errors)) + { + return $errors; + } + + if ($row['parent_id'] != $forum_data_sql['parent_id']) + { + if ($row['forum_id'] != $forum_data_sql['parent_id']) + { + $errors = $this->move_forum($forum_data_sql['forum_id'], $forum_data_sql['parent_id']); + } + else + { + $forum_data_sql['parent_id'] = $row['parent_id']; + } + } + + if (count($errors)) + { + return $errors; + } + + unset($forum_data_sql['type_action']); + + if ($row['forum_name'] != $forum_data_sql['forum_name']) + { + // the forum name has changed, clear the parents list of all forums (for safety) + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET forum_parents = ''"; + $db->sql_query($sql); + } + + // Setting the forum id to the forum id is not really received well by some dbs. ;) + $forum_id = $forum_data_sql['forum_id']; + unset($forum_data_sql['forum_id']); + + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $forum_data_sql) . ' + WHERE forum_id = ' . $forum_id; + $db->sql_query($sql); + + // Add it back + $forum_data_ary['forum_id'] = $forum_id; + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_EDIT', false, array($forum_data_ary['forum_name'])); + } + + $forum_data = $forum_data_ary; + /** + * Event after a forum was updated or created + * + * @event core.acp_manage_forums_update_data_after + * @var array forum_data Array with forum data + * @var array forum_data_sql Array with data we updated + * @var bool is_new_forum Did we create a forum or update one + * If you want to overwrite this value, + * ensure to set forum_data_sql[forum_id] + * @var array errors Array of errors, should be strings and not + * language key. + * @since 3.1.0-a1 + */ + $vars = array('forum_data', 'forum_data_sql', 'is_new_forum', 'errors'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_update_data_after', compact($vars))); + $forum_data_ary = $forum_data; + unset($forum_data); + + return $errors; + } + + /** + * Move forum + */ + function move_forum($from_id, $to_id) + { + global $db, $user, $phpbb_dispatcher; + + $errors = array(); + + // Check if we want to move to a parent with link type + if ($to_id > 0) + { + $to_data = $this->get_forum_info($to_id); + + if ($to_data['forum_type'] == FORUM_LINK) + { + $errors[] = $user->lang['PARENT_IS_LINK_FORUM']; + } + } + + /** + * Event when we move all children of one forum to another + * + * This event may be triggered, when a forum is deleted + * + * @event core.acp_manage_forums_move_children + * @var int from_id If of the current parent forum + * @var int to_id If of the new parent forum + * @var array errors Array of errors, should be strings and not + * language key. + * @since 3.1.0-a1 + */ + $vars = array('from_id', 'to_id', 'errors'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_move_children', compact($vars))); + + // Return if there were errors + if (!empty($errors)) + { + return $errors; + } + + $db->sql_transaction('begin'); + + $moved_forums = get_forum_branch($from_id, 'children', 'descending'); + $from_data = $moved_forums[0]; + $diff = count($moved_forums) * 2; + + $moved_ids = array(); + for ($i = 0, $size = count($moved_forums); $i < $size; ++$i) + { + $moved_ids[] = $moved_forums[$i]['forum_id']; + } + + // Resync parents + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET right_id = right_id - $diff, forum_parents = '' + WHERE left_id < " . $from_data['right_id'] . " + AND right_id > " . $from_data['right_id']; + $db->sql_query($sql); + + // Resync righthand side of tree + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET left_id = left_id - $diff, right_id = right_id - $diff, forum_parents = '' + WHERE left_id > " . $from_data['right_id']; + $db->sql_query($sql); + + if ($to_id > 0) + { + // Retrieve $to_data again, it may have been changed... + $to_data = $this->get_forum_info($to_id); + + // Resync new parents + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET right_id = right_id + $diff, forum_parents = '' + WHERE " . $to_data['right_id'] . ' BETWEEN left_id AND right_id + AND ' . $db->sql_in_set('forum_id', $moved_ids, true); + $db->sql_query($sql); + + // Resync the righthand side of the tree + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET left_id = left_id + $diff, right_id = right_id + $diff, forum_parents = '' + WHERE left_id > " . $to_data['right_id'] . ' + AND ' . $db->sql_in_set('forum_id', $moved_ids, true); + $db->sql_query($sql); + + // Resync moved branch + $to_data['right_id'] += $diff; + + if ($to_data['right_id'] > $from_data['right_id']) + { + $diff = '+ ' . ($to_data['right_id'] - $from_data['right_id'] - 1); + } + else + { + $diff = '- ' . abs($to_data['right_id'] - $from_data['right_id'] - 1); + } + } + else + { + $sql = 'SELECT MAX(right_id) AS right_id + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $moved_ids, true); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $diff = '+ ' . ($row['right_id'] - $from_data['left_id'] + 1); + } + + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET left_id = left_id $diff, right_id = right_id $diff, forum_parents = '' + WHERE " . $db->sql_in_set('forum_id', $moved_ids); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + + return $errors; + } + + /** + * Move forum content from one to another forum + */ + function move_forum_content($from_id, $to_id, $sync = true) + { + global $db, $phpbb_dispatcher; + + $errors = array(); + + /** + * Event when we move content from one forum to another + * + * @event core.acp_manage_forums_move_content + * @var int from_id If of the current parent forum + * @var int to_id If of the new parent forum + * @var bool sync Shall we sync the "to"-forum's data + * @var array errors Array of errors, should be strings and not + * language key. If this array is not empty, + * The content will not be moved. + * @since 3.1.0-a1 + */ + $vars = array('from_id', 'to_id', 'sync', 'errors'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_move_content', compact($vars))); + + // Return if there were errors + if (!empty($errors)) + { + return $errors; + } + + $table_ary = array(LOG_TABLE, POSTS_TABLE, TOPICS_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); + + /** + * Perform additional actions before move forum content + * + * @event core.acp_manage_forums_move_content_sql_before + * @var array table_ary Array of tables from which forum_id will be updated + * @since 3.2.4-RC1 + */ + $vars = array('table_ary'); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_forums_move_content_sql_before', compact($vars))); + + foreach ($table_ary as $table) + { + $sql = "UPDATE $table + SET forum_id = $to_id + WHERE forum_id = $from_id"; + $db->sql_query($sql); + } + unset($table_ary); + + $table_ary = array(FORUMS_ACCESS_TABLE, FORUMS_TRACK_TABLE, FORUMS_WATCH_TABLE, MODERATOR_CACHE_TABLE); + + foreach ($table_ary as $table) + { + $sql = "DELETE FROM $table + WHERE forum_id = $from_id"; + $db->sql_query($sql); + } + + if ($sync) + { + // Delete ghost topics that link back to the same forum then resync counters + sync('topic_moved'); + sync('forum', 'forum_id', $to_id, false, true); + } + + return array(); + } + + /** + * Remove complete forum + */ + function delete_forum($forum_id, $action_posts = 'delete', $action_subforums = 'delete', $posts_to_id = 0, $subforums_to_id = 0) + { + global $db, $user, $cache, $phpbb_log; + + $forum_data = $this->get_forum_info($forum_id); + + $errors = array(); + $log_action_posts = $log_action_forums = $posts_to_name = $subforums_to_name = ''; + $forum_ids = array($forum_id); + + if ($action_posts == 'delete') + { + $log_action_posts = 'POSTS'; + $errors = array_merge($errors, $this->delete_forum_content($forum_id)); + } + else if ($action_posts == 'move') + { + if (!$posts_to_id) + { + $errors[] = $user->lang['NO_DESTINATION_FORUM']; + } + else + { + $log_action_posts = 'MOVE_POSTS'; + + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $posts_to_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $errors[] = $user->lang['NO_FORUM']; + } + else + { + $posts_to_name = $row['forum_name']; + $errors = array_merge($errors, $this->move_forum_content($forum_id, $posts_to_id)); + } + } + } + + if (count($errors)) + { + return $errors; + } + + if ($action_subforums == 'delete') + { + $log_action_forums = 'FORUMS'; + $rows = get_forum_branch($forum_id, 'children', 'descending', false); + + foreach ($rows as $row) + { + $forum_ids[] = $row['forum_id']; + $errors = array_merge($errors, $this->delete_forum_content($row['forum_id'])); + } + + if (count($errors)) + { + return $errors; + } + + $diff = count($forum_ids) * 2; + + $sql = 'DELETE FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids); + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids); + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_USERS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids); + $db->sql_query($sql); + } + else if ($action_subforums == 'move') + { + if (!$subforums_to_id) + { + $errors[] = $user->lang['NO_DESTINATION_FORUM']; + } + else + { + $log_action_forums = 'MOVE_FORUMS'; + + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $subforums_to_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $errors[] = $user->lang['NO_FORUM']; + } + else + { + $subforums_to_name = $row['forum_name']; + + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . " + WHERE parent_id = $forum_id"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $this->move_forum($row['forum_id'], $subforums_to_id); + } + $db->sql_freeresult($result); + + // Grab new forum data for correct tree updating later + $forum_data = $this->get_forum_info($forum_id); + + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET parent_id = $subforums_to_id + WHERE parent_id = $forum_id"; + $db->sql_query($sql); + + $diff = 2; + $sql = 'DELETE FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . " + WHERE forum_id = $forum_id"; + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_USERS_TABLE . " + WHERE forum_id = $forum_id"; + $db->sql_query($sql); + } + } + + if (count($errors)) + { + return $errors; + } + } + else + { + $diff = 2; + $sql = 'DELETE FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . " + WHERE forum_id = $forum_id"; + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_USERS_TABLE . " + WHERE forum_id = $forum_id"; + $db->sql_query($sql); + } + + // Resync tree + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET right_id = right_id - $diff + WHERE left_id < {$forum_data['right_id']} AND right_id > {$forum_data['right_id']}"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET left_id = left_id - $diff, right_id = right_id - $diff + WHERE left_id > {$forum_data['right_id']}"; + $db->sql_query($sql); + + // Delete forum ids from extension groups table + $sql = 'SELECT group_id, allowed_forums + FROM ' . EXTENSION_GROUPS_TABLE; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (!$row['allowed_forums']) + { + continue; + } + + $allowed_forums = unserialize(trim($row['allowed_forums'])); + $allowed_forums = array_diff($allowed_forums, $forum_ids); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . " + SET allowed_forums = '" . ((count($allowed_forums)) ? serialize($allowed_forums) : '') . "' + WHERE group_id = {$row['group_id']}"; + $db->sql_query($sql); + } + $db->sql_freeresult($result); + + $cache->destroy('_extensions'); + + $log_action = implode('_', array($log_action_posts, $log_action_forums)); + + switch ($log_action) + { + case 'MOVE_POSTS_MOVE_FORUMS': + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_MOVE_POSTS_MOVE_FORUMS', false, array($posts_to_name, $subforums_to_name, $forum_data['forum_name'])); + break; + + case 'MOVE_POSTS_FORUMS': + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_MOVE_POSTS_FORUMS', false, array($posts_to_name, $forum_data['forum_name'])); + break; + + case 'POSTS_MOVE_FORUMS': + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_POSTS_MOVE_FORUMS', false, array($subforums_to_name, $forum_data['forum_name'])); + break; + + case '_MOVE_FORUMS': + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_MOVE_FORUMS', false, array($subforums_to_name, $forum_data['forum_name'])); + break; + + case 'MOVE_POSTS_': + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_MOVE_POSTS', false, array($posts_to_name, $forum_data['forum_name'])); + break; + + case 'POSTS_FORUMS': + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_POSTS_FORUMS', false, array($forum_data['forum_name'])); + break; + + case '_FORUMS': + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_FORUMS', false, array($forum_data['forum_name'])); + break; + + case 'POSTS_': + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_POSTS', false, array($forum_data['forum_name'])); + break; + + default: + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_DEL_FORUM', false, array($forum_data['forum_name'])); + break; + } + + return $errors; + } + + /** + * Delete forum content + */ + function delete_forum_content($forum_id) + { + global $db, $config, $phpbb_root_path, $phpEx, $phpbb_container, $phpbb_dispatcher; + + include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + + $db->sql_transaction('begin'); + + // Select then delete all attachments + $sql = 'SELECT a.topic_id + FROM ' . POSTS_TABLE . ' p, ' . ATTACHMENTS_TABLE . " a + WHERE p.forum_id = $forum_id + AND a.in_message = 0 + AND a.topic_id = p.topic_id"; + $result = $db->sql_query($sql); + + $topic_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topic_ids[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $attachment_manager->delete('topic', $topic_ids, false); + unset($attachment_manager); + + // Delete shadow topics pointing to topics in this forum + delete_topic_shadows($forum_id); + + // Before we remove anything we make sure we are able to adjust the post counts later. ;) + $sql = 'SELECT poster_id + FROM ' . POSTS_TABLE . ' + WHERE forum_id = ' . $forum_id . ' + AND post_postcount = 1 + AND post_visibility = ' . ITEM_APPROVED; + $result = $db->sql_query($sql); + + $post_counts = array(); + while ($row = $db->sql_fetchrow($result)) + { + $post_counts[$row['poster_id']] = (!empty($post_counts[$row['poster_id']])) ? $post_counts[$row['poster_id']] + 1 : 1; + } + $db->sql_freeresult($result); + + switch ($db->get_sql_layer()) + { + case 'mysql4': + case 'mysqli': + + // Delete everything else and thank MySQL for offering multi-table deletion + $tables_ary = array( + SEARCH_WORDMATCH_TABLE => 'post_id', + REPORTS_TABLE => 'post_id', + WARNINGS_TABLE => 'post_id', + BOOKMARKS_TABLE => 'topic_id', + TOPICS_WATCH_TABLE => 'topic_id', + TOPICS_POSTED_TABLE => 'topic_id', + POLL_OPTIONS_TABLE => 'topic_id', + POLL_VOTES_TABLE => 'topic_id', + ); + + $sql = 'DELETE ' . POSTS_TABLE; + $sql_using = "\nFROM " . POSTS_TABLE; + $sql_where = "\nWHERE " . POSTS_TABLE . ".forum_id = $forum_id\n"; + + foreach ($tables_ary as $table => $field) + { + $sql .= ", $table "; + $sql_using .= ", $table "; + $sql_where .= "\nAND $table.$field = " . POSTS_TABLE . ".$field"; + } + + $db->sql_query($sql . $sql_using . $sql_where); + + break; + + default: + + // Delete everything else and curse your DB for not offering multi-table deletion + $tables_ary = array( + 'post_id' => array( + SEARCH_WORDMATCH_TABLE, + REPORTS_TABLE, + WARNINGS_TABLE, + ), + + 'topic_id' => array( + BOOKMARKS_TABLE, + TOPICS_WATCH_TABLE, + TOPICS_POSTED_TABLE, + POLL_OPTIONS_TABLE, + POLL_VOTES_TABLE, + ) + ); + + // Amount of rows we select and delete in one iteration. + $batch_size = 500; + + foreach ($tables_ary as $field => $tables) + { + $start = 0; + + do + { + $sql = "SELECT $field + FROM " . POSTS_TABLE . ' + WHERE forum_id = ' . $forum_id; + $result = $db->sql_query_limit($sql, $batch_size, $start); + + $ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $ids[] = $row[$field]; + } + $db->sql_freeresult($result); + + if (count($ids)) + { + $start += count($ids); + + foreach ($tables as $table) + { + $db->sql_query("DELETE FROM $table WHERE " . $db->sql_in_set($field, $ids)); + } + } + } + while (count($ids) == $batch_size); + } + unset($ids); + + break; + } + + $table_ary = array(FORUMS_ACCESS_TABLE, FORUMS_TRACK_TABLE, FORUMS_WATCH_TABLE, LOG_TABLE, MODERATOR_CACHE_TABLE, POSTS_TABLE, TOPICS_TABLE, TOPICS_TRACK_TABLE); + + /** + * Perform additional actions before forum content deletion + * + * @event core.delete_forum_content_before_query + * @var array table_ary Array of tables from which all rows will be deleted that hold the forum_id + * @var int forum_id the forum id + * @var array topic_ids Array of the topic ids from the forum to be deleted + * @var array post_counts Array of counts of posts in the forum, by poster_id + * @since 3.1.6-RC1 + */ + $vars = array( + 'table_ary', + 'forum_id', + 'topic_ids', + 'post_counts', + ); + extract($phpbb_dispatcher->trigger_event('core.delete_forum_content_before_query', compact($vars))); + + foreach ($table_ary as $table) + { + $db->sql_query("DELETE FROM $table WHERE forum_id = $forum_id"); + } + + // Set forum ids to 0 + $table_ary = array(DRAFTS_TABLE); + + foreach ($table_ary as $table) + { + $db->sql_query("UPDATE $table SET forum_id = 0 WHERE forum_id = $forum_id"); + } + + // Adjust users post counts + if (count($post_counts)) + { + foreach ($post_counts as $poster_id => $substract) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = 0 + WHERE user_id = ' . $poster_id . ' + AND user_posts < ' . $substract; + $db->sql_query($sql); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = user_posts - ' . $substract . ' + WHERE user_id = ' . $poster_id . ' + AND user_posts >= ' . $substract; + $db->sql_query($sql); + } + } + + $db->sql_transaction('commit'); + + // Make sure the overall post/topic count is correct... + $sql = 'SELECT COUNT(post_id) AS stat + FROM ' . POSTS_TABLE . ' + WHERE post_visibility = ' . ITEM_APPROVED; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $config->set('num_posts', (int) $row['stat'], false); + + $sql = 'SELECT COUNT(topic_id) AS stat + FROM ' . TOPICS_TABLE . ' + WHERE topic_visibility = ' . ITEM_APPROVED; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $config->set('num_topics', (int) $row['stat'], false); + + $sql = 'SELECT COUNT(attach_id) as stat + FROM ' . ATTACHMENTS_TABLE; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $config->set('num_files', (int) $row['stat'], false); + + $sql = 'SELECT SUM(filesize) as stat + FROM ' . ATTACHMENTS_TABLE; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $config->set('upload_dir_size', (float) $row['stat'], false); + + return array(); + } + + /** + * Move forum position by $steps up/down + */ + function move_forum_by($forum_row, $action = 'move_up', $steps = 1) + { + global $db; + + /** + * Fetch all the siblings between the module's current spot + * and where we want to move it to. If there are less than $steps + * siblings between the current spot and the target then the + * module will move as far as possible + */ + $sql = 'SELECT forum_id, forum_name, left_id, right_id + FROM ' . FORUMS_TABLE . " + WHERE parent_id = {$forum_row['parent_id']} + AND " . (($action == 'move_up') ? "right_id < {$forum_row['right_id']} ORDER BY right_id DESC" : "left_id > {$forum_row['left_id']} ORDER BY left_id ASC"); + $result = $db->sql_query_limit($sql, $steps); + + $target = array(); + while ($row = $db->sql_fetchrow($result)) + { + $target = $row; + } + $db->sql_freeresult($result); + + if (!count($target)) + { + // The forum is already on top or bottom + return false; + } + + /** + * $left_id and $right_id define the scope of the nodes that are affected by the move. + * $diff_up and $diff_down are the values to substract or add to each node's left_id + * and right_id in order to move them up or down. + * $move_up_left and $move_up_right define the scope of the nodes that are moving + * up. Other nodes in the scope of ($left_id, $right_id) are considered to move down. + */ + if ($action == 'move_up') + { + $left_id = $target['left_id']; + $right_id = $forum_row['right_id']; + + $diff_up = $forum_row['left_id'] - $target['left_id']; + $diff_down = $forum_row['right_id'] + 1 - $forum_row['left_id']; + + $move_up_left = $forum_row['left_id']; + $move_up_right = $forum_row['right_id']; + } + else + { + $left_id = $forum_row['left_id']; + $right_id = $target['right_id']; + + $diff_up = $forum_row['right_id'] + 1 - $forum_row['left_id']; + $diff_down = $target['right_id'] - $forum_row['right_id']; + + $move_up_left = $forum_row['right_id'] + 1; + $move_up_right = $target['right_id']; + } + + // Now do the dirty job + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET left_id = left_id + CASE + WHEN left_id BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} + ELSE {$diff_down} + END, + right_id = right_id + CASE + WHEN right_id BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} + ELSE {$diff_down} + END, + forum_parents = '' + WHERE + left_id BETWEEN {$left_id} AND {$right_id} + AND right_id BETWEEN {$left_id} AND {$right_id}"; + $db->sql_query($sql); + + return $target['forum_name']; + } + + /** + * Display progress bar for syncinc forums + */ + function display_progress_bar($start, $total) + { + global $template, $user; + + adm_page_header($user->lang['SYNC_IN_PROGRESS']); + + $template->set_filenames(array( + 'body' => 'progress_bar.html') + ); + + $template->assign_vars(array( + 'L_PROGRESS' => $user->lang['SYNC_IN_PROGRESS'], + 'L_PROGRESS_EXPLAIN' => ($start && $total) ? sprintf($user->lang['SYNC_IN_PROGRESS_EXPLAIN'], $start, $total) : $user->lang['SYNC_IN_PROGRESS']) + ); + + adm_page_footer(); + } + + /** + * Display copy permission page + * Not used at the moment - we will have a look at it for 3.0.7 + */ + function copy_permission_page($forum_data) + { + global $phpEx, $phpbb_admin_path, $template, $user; + + $acl_url = '&mode=setting_forum_local&forum_id[]=' . $forum_data['forum_id']; + $action = append_sid($this->u_action . "&parent_id={$this->parent_id}&f={$forum_data['forum_id']}&action=copy_perm"); + + $l_acl = sprintf($user->lang['COPY_TO_ACL'], '', ''); + + $this->tpl_name = 'acp_forums_copy_perm'; + + $template->assign_vars(array( + 'U_ACL' => append_sid("{$phpbb_admin_path}index.$phpEx", 'i=permissions' . $acl_url), + 'L_ACL_LINK' => $l_acl, + 'L_BACK_LINK' => adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), + 'S_COPY_ACTION' => $action, + 'S_FORUM_OPTIONS' => make_forum_select($forum_data['parent_id'], $forum_data['forum_id'], false, false, false), + )); + } + +} diff --git a/includes/acp/acp_groups.php b/includes/acp/acp_groups.php new file mode 100644 index 0000000..7b1dc70 --- /dev/null +++ b/includes/acp/acp_groups.php @@ -0,0 +1,1228 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_groups +{ + var $u_action; + + function main($id, $mode) + { + global $config, $db, $user, $auth, $template, $cache; + global $phpbb_root_path, $phpbb_admin_path, $phpEx; + global $request, $phpbb_container, $phpbb_dispatcher; + + /** @var \phpbb\language\language $language Language object */ + $language = $phpbb_container->get('language'); + + $user->add_lang('acp/groups'); + $this->tpl_name = 'acp_groups'; + $this->page_title = 'ACP_GROUPS_MANAGE'; + + $form_key = 'acp_groups'; + add_form_key($form_key); + + if ($mode == 'position') + { + $this->manage_position(); + return; + } + + if (!function_exists('group_user_attributes')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + // Check and set some common vars + $action = (isset($_POST['add'])) ? 'add' : ((isset($_POST['addusers'])) ? 'addusers' : $request->variable('action', '')); + $group_id = $request->variable('g', 0); + $mark_ary = $request->variable('mark', array(0)); + $name_ary = $request->variable('usernames', '', true); + $leader = $request->variable('leader', 0); + $default = $request->variable('default', 0); + $start = $request->variable('start', 0); + $update = (isset($_POST['update'])) ? true : false; + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + // Clear some vars + $group_row = array(); + + // Grab basic data for group, if group_id is set and exists + if ($group_id) + { + $sql = 'SELECT g.*, t.teampage_position AS group_teampage + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . TEAMPAGE_TABLE . ' t + ON (t.group_id = g.group_id) + WHERE g.group_id = ' . $group_id; + $result = $db->sql_query($sql); + $group_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$group_row) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Check if the user is allowed to manage this group if set to founder only. + if ($user->data['user_type'] != USER_FOUNDER && $group_row['group_founder_manage']) + { + trigger_error($user->lang['NOT_ALLOWED_MANAGE_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + // Which page? + switch ($action) + { + case 'approve': + case 'demote': + case 'promote': + if (!check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Approve, demote or promote + $group_name = $group_helper->get_name($group_row['group_name']); + $error = group_user_attributes($action, $group_id, $mark_ary, false, $group_name); + + if (!$error) + { + switch ($action) + { + case 'demote': + $message = 'GROUP_MODS_DEMOTED'; + break; + + case 'promote': + $message = 'GROUP_MODS_PROMOTED'; + break; + + case 'approve': + $message = 'USERS_APPROVED'; + break; + } + + trigger_error($user->lang[$message] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); + } + else + { + trigger_error($user->lang[$error] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } + + break; + + case 'default': + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); + } + else if (empty($mark_ary)) + { + trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } + + if (confirm_box(true)) + { + $group_name = $group_helper->get_name($group_row['group_name']); + group_user_attributes('default', $group_id, $mark_ary, false, $group_name, $group_row); + trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } + break; + + case 'set_default_on_all': + if (confirm_box(true)) + { + $group_name = $group_helper->get_name($group_row['group_name']); + + $start = 0; + + do + { + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . " + WHERE group_id = $group_id + ORDER BY user_id"; + $result = $db->sql_query_limit($sql, 200, $start); + + $mark_ary = array(); + if ($row = $db->sql_fetchrow($result)) + { + do + { + $mark_ary[] = $row['user_id']; + } + while ($row = $db->sql_fetchrow($result)); + + group_user_attributes('default', $group_id, $mark_ary, false, $group_name, $group_row); + + $start = (count($mark_ary) < 200) ? 0 : $start + 200; + } + else + { + $start = 0; + } + $db->sql_freeresult($result); + } + while ($start); + + trigger_error($user->lang['GROUP_DEFS_UPDATED'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } + break; + + case 'deleteusers': + if (empty($mark_ary)) + { + trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } + case 'delete': + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); + } + else if ($action === 'delete' && $group_row['group_type'] == GROUP_SPECIAL) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (confirm_box(true)) + { + $error = ''; + + switch ($action) + { + case 'delete': + if (!$auth->acl_get('a_groupdel')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $error = group_delete($group_id, $group_row['group_name']); + break; + + case 'deleteusers': + $group_name = $group_helper->get_name($group_row['group_name']); + $error = group_user_del($group_id, $mark_ary, false, $group_name); + break; + } + + $back_link = ($action == 'delete') ? $this->u_action : $this->u_action . '&action=list&g=' . $group_id; + + if ($error) + { + trigger_error($user->lang[$error] . adm_back_link($back_link), E_USER_WARNING); + } + + $message = ($action == 'delete') ? 'GROUP_DELETED' : 'GROUP_USERS_REMOVE'; + trigger_error($user->lang[$message] . adm_back_link($back_link)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } + break; + + case 'addusers': + if (!check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (!$name_ary) + { + trigger_error($user->lang['NO_USERS'] . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } + + $name_ary = array_unique(explode("\n", $name_ary)); + $group_name = $group_helper->get_name($group_row['group_name']); + + // Add user/s to group + if ($error = group_user_add($group_id, false, $name_ary, $group_name, $default, $leader, 0, $group_row)) + { + $display_message = $language->lang($error); + + if ($error == 'GROUP_USERS_INVALID') + { + // Find which users don't exist + $actual_name_ary = $name_ary; + $actual_user_id_ary = []; + user_get_id_name($actual_user_id_ary, $actual_name_ary, false, true); + + $display_message = $language->lang('GROUP_USERS_INVALID', implode($language->lang('COMMA_SEPARATOR'), array_udiff($name_ary, $actual_name_ary, 'strcasecmp'))); + } + + trigger_error($display_message . adm_back_link($this->u_action . '&action=list&g=' . $group_id), E_USER_WARNING); + } + + $message = ($leader) ? 'GROUP_MODS_ADDED' : 'GROUP_USERS_ADDED'; + trigger_error($user->lang[$message] . adm_back_link($this->u_action . '&action=list&g=' . $group_id)); + break; + + case 'edit': + case 'add': + + if (!function_exists('display_forums')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + if ($action == 'edit' && !$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if ($action == 'add' && !$auth->acl_get('a_groupadd')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $error = array(); + $user->add_lang('ucp'); + + // Setup avatar data for later + $avatars_enabled = false; + $avatar_drivers = null; + $avatar_data = null; + $avatar_error = array(); + + /** @var \phpbb\avatar\manager $phpbb_avatar_manager */ + $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); + + if ($config['allow_avatar']) + { + $avatar_drivers = $phpbb_avatar_manager->get_enabled_drivers(); + + // This is normalised data, without the group_ prefix + $avatar_data = \phpbb\avatar\manager::clean_row($group_row, 'group'); + if (!isset($avatar_data['id'])) + { + $avatar_data['id'] = 'g' . $group_id; + } + } + + if ($request->is_set_post('avatar_delete')) + { + if (confirm_box(true)) + { + $avatar_data['id'] = substr($avatar_data['id'], 1); + $phpbb_avatar_manager->handle_avatar_delete($db, $user, $avatar_data, GROUPS_TABLE, 'group_'); + + $message = ($action == 'edit') ? 'GROUP_UPDATED' : 'GROUP_CREATED'; + trigger_error($user->lang[$message] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, $user->lang('CONFIRM_AVATAR_DELETE'), build_hidden_fields(array( + 'avatar_delete' => true, + 'i' => $id, + 'mode' => $mode, + 'g' => $group_id, + 'action' => $action)) + ); + } + } + + // Did we submit? + if ($update) + { + if (!check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $group_name = $request->variable('group_name', '', true); + $group_desc = $request->variable('group_desc', '', true); + $group_type = $request->variable('group_type', GROUP_FREE); + + $allow_desc_bbcode = $request->variable('desc_parse_bbcode', false); + $allow_desc_urls = $request->variable('desc_parse_urls', false); + $allow_desc_smilies = $request->variable('desc_parse_smilies', false); + + $submit_ary = array( + 'colour' => $request->variable('group_colour', ''), + 'rank' => $request->variable('group_rank', 0), + 'receive_pm' => isset($_REQUEST['group_receive_pm']) ? 1 : 0, + 'legend' => isset($_REQUEST['group_legend']) ? 1 : 0, + 'teampage' => isset($_REQUEST['group_teampage']) ? 1 : 0, + 'message_limit' => $request->variable('group_message_limit', 0), + 'max_recipients' => $request->variable('group_max_recipients', 0), + 'founder_manage' => 0, + 'skip_auth' => $request->variable('group_skip_auth', 0), + ); + + if ($user->data['user_type'] == USER_FOUNDER) + { + $submit_ary['founder_manage'] = isset($_REQUEST['group_founder_manage']) ? 1 : 0; + } + + if ($config['allow_avatar']) + { + // Handle avatar + $driver_name = $phpbb_avatar_manager->clean_driver_name($request->variable('avatar_driver', '')); + + if (in_array($driver_name, $avatar_drivers) && !$request->is_set_post('avatar_delete')) + { + $driver = $phpbb_avatar_manager->get_driver($driver_name); + $result = $driver->process_form($request, $template, $user, $avatar_data, $avatar_error); + + if ($result && empty($avatar_error)) + { + $result['avatar_type'] = $driver_name; + $submit_ary = array_merge($submit_ary, $result); + } + } + else + { + $driver = $phpbb_avatar_manager->get_driver($avatar_data['avatar_type']); + if ($driver) + { + $driver->delete($avatar_data); + } + + // Removing the avatar + $submit_ary['avatar_type'] = ''; + $submit_ary['avatar'] = ''; + $submit_ary['avatar_width'] = 0; + $submit_ary['avatar_height'] = 0; + } + + // Merge any avatar errors into the primary error array + $error = array_merge($error, $phpbb_avatar_manager->localize_errors($user, $avatar_error)); + } + + /* + * Validate the length of "Maximum number of allowed recipients per + * private message" setting. We use 16777215 as a maximum because it matches + * MySQL unsigned mediumint maximum value which is the lowest amongst DBMSes + * supported by phpBB3. Also validate the submitted colour value. + */ + $validation_checks = array( + 'max_recipients' => array('num', false, 0, 16777215), + 'colour' => array('hex_colour', true), + ); + + /** + * Request group data and operate on it + * + * @event core.acp_manage_group_request_data + * @var string action Type of the action: add|edit + * @var int group_id The group id + * @var array group_row Array with new group data + * @var array error Array of errors, if you add errors + * ensure to update the template variables + * S_ERROR and ERROR_MSG to display it + * @var string group_name The group name + * @var string group_desc The group description + * @var int group_type The group type + * @var bool allow_desc_bbcode Allow bbcode in group description: true|false + * @var bool allow_desc_urls Allow urls in group description: true|false + * @var bool allow_desc_smilies Allow smiles in group description: true|false + * @var array submit_ary Array with new group data + * @var array validation_checks Array with validation data + * @since 3.1.0-b5 + */ + $vars = array( + 'action', + 'group_id', + 'group_row', + 'error', + 'group_name', + 'group_desc', + 'group_type', + 'allow_desc_bbcode', + 'allow_desc_urls', + 'allow_desc_smilies', + 'submit_ary', + 'validation_checks', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_group_request_data', compact($vars))); + + if ($validation_error = validate_data($submit_ary, $validation_checks)) + { + // Replace "error" string with its real, localised form + $error = array_merge($error, $validation_error); + } + + if (!count($error)) + { + // Only set the rank, colour, etc. if it's changed or if we're adding a new + // group. This prevents existing group members being updated if no changes + // were made. + // However there are some attributes that need to be set everytime, + // otherwise the group gets removed from the feature. + $set_attributes = array('legend', 'teampage'); + + $group_attributes = array(); + $test_variables = array( + 'rank' => 'int', + 'colour' => 'string', + 'avatar' => 'string', + 'avatar_type' => 'string', + 'avatar_width' => 'int', + 'avatar_height' => 'int', + 'receive_pm' => 'int', + 'legend' => 'int', + 'teampage' => 'int', + 'message_limit' => 'int', + 'max_recipients'=> 'int', + 'founder_manage'=> 'int', + 'skip_auth' => 'int', + ); + + /** + * Initialise data before we display the add/edit form + * + * @event core.acp_manage_group_initialise_data + * @var string action Type of the action: add|edit + * @var int group_id The group id + * @var array group_row Array with new group data + * @var array error Array of errors, if you add errors + * ensure to update the template variables + * S_ERROR and ERROR_MSG to display it + * @var string group_name The group name + * @var string group_desc The group description + * @var int group_type The group type + * @var bool allow_desc_bbcode Allow bbcode in group description: true|false + * @var bool allow_desc_urls Allow urls in group description: true|false + * @var bool allow_desc_smilies Allow smiles in group description: true|false + * @var array submit_ary Array with new group data + * @var array test_variables Array with variables for test + * @since 3.1.0-b5 + */ + $vars = array( + 'action', + 'group_id', + 'group_row', + 'error', + 'group_name', + 'group_desc', + 'group_type', + 'allow_desc_bbcode', + 'allow_desc_urls', + 'allow_desc_smilies', + 'submit_ary', + 'test_variables', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_group_initialise_data', compact($vars))); + + foreach ($test_variables as $test => $type) + { + if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || isset($group_attributes['group_avatar']) && strpos($test, 'avatar') === 0 || in_array($test, $set_attributes))) + { + settype($submit_ary[$test], $type); + $group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test]; + } + } + + if (!($error = group_create($group_id, $group_type, $group_name, $group_desc, $group_attributes, $allow_desc_bbcode, $allow_desc_urls, $allow_desc_smilies))) + { + $group_perm_from = $request->variable('group_perm_from', 0); + + // Copy permissions? + // If the user has the a_authgroups permission and at least one additional permission ability set the permissions are fully transferred. + // We do not limit on one auth category because this can lead to incomplete permissions being tricky to fix for the admin, roles being assigned or added non-default permissions. + // Since the user only has the option to copy permissions from non leader managed groups this seems to be a good compromise. + if ($group_perm_from && $action == 'add' && $auth->acl_get('a_authgroups') && $auth->acl_gets('a_aauth', 'a_fauth', 'a_mauth', 'a_uauth')) + { + $sql = 'SELECT group_founder_manage + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . $group_perm_from; + $result = $db->sql_query($sql); + $check_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Check the group if non-founder + if ($check_row && ($user->data['user_type'] == USER_FOUNDER || $check_row['group_founder_manage'] == 0)) + { + // From the mysql documentation: + // Prior to MySQL 4.0.14, the target table of the INSERT statement cannot appear in the FROM clause of the SELECT part of the query. This limitation is lifted in 4.0.14. + // Due to this we stay on the safe side if we do the insertion "the manual way" + + // Copy permisisons from/to the acl groups table (only group_id gets changed) + $sql = 'SELECT forum_id, auth_option_id, auth_role_id, auth_setting + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_perm_from; + $result = $db->sql_query($sql); + + $groups_sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $groups_sql_ary[] = array( + 'group_id' => (int) $group_id, + 'forum_id' => (int) $row['forum_id'], + 'auth_option_id' => (int) $row['auth_option_id'], + 'auth_role_id' => (int) $row['auth_role_id'], + 'auth_setting' => (int) $row['auth_setting'] + ); + } + $db->sql_freeresult($result); + + // Now insert the data + $db->sql_multi_insert(ACL_GROUPS_TABLE, $groups_sql_ary); + + $auth->acl_clear_prefetch(); + } + } + + $cache->destroy('sql', array(GROUPS_TABLE, TEAMPAGE_TABLE)); + + $message = ($action == 'edit') ? 'GROUP_UPDATED' : 'GROUP_CREATED'; + trigger_error($user->lang[$message] . adm_back_link($this->u_action)); + } + } + + if (count($error)) + { + $error = array_map(array(&$user, 'lang'), $error); + $group_rank = $submit_ary['rank']; + + $group_desc_data = array( + 'text' => $group_desc, + 'allow_bbcode' => $allow_desc_bbcode, + 'allow_smilies' => $allow_desc_smilies, + 'allow_urls' => $allow_desc_urls + ); + } + } + else if (!$group_id) + { + $group_name = $request->variable('group_name', '', true); + $group_desc_data = array( + 'text' => '', + 'allow_bbcode' => true, + 'allow_smilies' => true, + 'allow_urls' => true + ); + $group_rank = 0; + $group_type = GROUP_OPEN; + } + else + { + $group_name = $group_row['group_name']; + $group_desc_data = generate_text_for_edit($group_row['group_desc'], $group_row['group_desc_uid'], $group_row['group_desc_options']); + $group_type = $group_row['group_type']; + $group_rank = $group_row['group_rank']; + } + + $sql = 'SELECT * + FROM ' . RANKS_TABLE . ' + WHERE rank_special = 1 + ORDER BY rank_title'; + $result = $db->sql_query($sql); + + $rank_options = ''; + + while ($row = $db->sql_fetchrow($result)) + { + $selected = ($group_rank && $row['rank_id'] == $group_rank) ? ' selected="selected"' : ''; + $rank_options .= ''; + } + $db->sql_freeresult($result); + + $type_free = ($group_type == GROUP_FREE) ? ' checked="checked"' : ''; + $type_open = ($group_type == GROUP_OPEN) ? ' checked="checked"' : ''; + $type_closed = ($group_type == GROUP_CLOSED) ? ' checked="checked"' : ''; + $type_hidden = ($group_type == GROUP_HIDDEN) ? ' checked="checked"' : ''; + + // Load up stuff for avatars + if ($config['allow_avatar']) + { + $avatars_enabled = false; + $selected_driver = $phpbb_avatar_manager->clean_driver_name($request->variable('avatar_driver', $avatar_data['avatar_type'])); + + // Assign min and max values before generating avatar driver html + $template->assign_vars(array( + 'AVATAR_MIN_WIDTH' => $config['avatar_min_width'], + 'AVATAR_MAX_WIDTH' => $config['avatar_max_width'], + 'AVATAR_MIN_HEIGHT' => $config['avatar_min_height'], + 'AVATAR_MAX_HEIGHT' => $config['avatar_max_height'], + )); + + foreach ($avatar_drivers as $current_driver) + { + $driver = $phpbb_avatar_manager->get_driver($current_driver); + + $avatars_enabled = true; + $template->set_filenames(array( + 'avatar' => $driver->get_acp_template_name(), + )); + + if ($driver->prepare_form($request, $template, $user, $avatar_data, $avatar_error)) + { + $driver_name = $phpbb_avatar_manager->prepare_driver_name($current_driver); + $driver_upper = strtoupper($driver_name); + $template->assign_block_vars('avatar_drivers', array( + 'L_TITLE' => $user->lang($driver_upper . '_TITLE'), + 'L_EXPLAIN' => $user->lang($driver_upper . '_EXPLAIN'), + + 'DRIVER' => $driver_name, + 'SELECTED' => $current_driver == $selected_driver, + 'OUTPUT' => $template->assign_display('avatar'), + )); + } + } + } + + $avatar = phpbb_get_group_avatar($group_row, 'GROUP_AVATAR', true); + + if (isset($phpbb_avatar_manager) && !$update) + { + // Merge any avatar errors into the primary error array + $error = array_merge($error, $phpbb_avatar_manager->localize_errors($user, $avatar_error)); + } + + $back_link = $request->variable('back_link', ''); + + switch ($back_link) + { + case 'acp_users_groups': + $u_back = append_sid("{$phpbb_admin_path}index.$phpEx", 'i=users&mode=groups&u=' . $request->variable('u', 0)); + break; + + default: + $u_back = $this->u_action; + break; + } + + $template->assign_vars(array( + 'S_EDIT' => true, + 'S_ADD_GROUP' => ($action == 'add') ? true : false, + 'S_GROUP_PERM' => ($action == 'add' && $auth->acl_get('a_authgroups') && $auth->acl_gets('a_aauth', 'a_fauth', 'a_mauth', 'a_uauth')) ? true : false, + 'S_INCLUDE_SWATCH' => true, + 'S_ERROR' => (count($error)) ? true : false, + 'S_SPECIAL_GROUP' => ($group_type == GROUP_SPECIAL) ? true : false, + 'S_USER_FOUNDER' => ($user->data['user_type'] == USER_FOUNDER) ? true : false, + 'S_AVATARS_ENABLED' => ($config['allow_avatar'] && $avatars_enabled), + + 'ERROR_MSG' => (count($error)) ? implode('
', $error) : '', + 'GROUP_NAME' => $group_helper->get_name($group_name), + 'GROUP_INTERNAL_NAME' => $group_name, + 'GROUP_DESC' => $group_desc_data['text'], + 'GROUP_RECEIVE_PM' => (isset($group_row['group_receive_pm']) && $group_row['group_receive_pm']) ? ' checked="checked"' : '', + 'GROUP_FOUNDER_MANAGE' => (isset($group_row['group_founder_manage']) && $group_row['group_founder_manage']) ? ' checked="checked"' : '', + 'GROUP_LEGEND' => (isset($group_row['group_legend']) && $group_row['group_legend']) ? ' checked="checked"' : '', + 'GROUP_TEAMPAGE' => (isset($group_row['group_teampage']) && $group_row['group_teampage']) ? ' checked="checked"' : '', + 'GROUP_MESSAGE_LIMIT' => (isset($group_row['group_message_limit'])) ? $group_row['group_message_limit'] : 0, + 'GROUP_MAX_RECIPIENTS' => (isset($group_row['group_max_recipients'])) ? $group_row['group_max_recipients'] : 0, + 'GROUP_COLOUR' => (isset($group_row['group_colour'])) ? $group_row['group_colour'] : '', + 'GROUP_SKIP_AUTH' => (!empty($group_row['group_skip_auth'])) ? ' checked="checked"' : '', + + 'S_DESC_BBCODE_CHECKED' => $group_desc_data['allow_bbcode'], + 'S_DESC_URLS_CHECKED' => $group_desc_data['allow_urls'], + 'S_DESC_SMILIES_CHECKED'=> $group_desc_data['allow_smilies'], + + 'S_RANK_OPTIONS' => $rank_options, + 'S_GROUP_OPTIONS' => group_select_options(false, false, (($user->data['user_type'] == USER_FOUNDER) ? false : 0)), + 'AVATAR' => empty($avatar) ? '' : $avatar, + 'AVATAR_MAX_FILESIZE' => $config['avatar_filesize'], + 'AVATAR_WIDTH' => (isset($group_row['group_avatar_width'])) ? $group_row['group_avatar_width'] : '', + 'AVATAR_HEIGHT' => (isset($group_row['group_avatar_height'])) ? $group_row['group_avatar_height'] : '', + + 'GROUP_TYPE_FREE' => GROUP_FREE, + 'GROUP_TYPE_OPEN' => GROUP_OPEN, + 'GROUP_TYPE_CLOSED' => GROUP_CLOSED, + 'GROUP_TYPE_HIDDEN' => GROUP_HIDDEN, + 'GROUP_TYPE_SPECIAL' => GROUP_SPECIAL, + + 'GROUP_FREE' => $type_free, + 'GROUP_OPEN' => $type_open, + 'GROUP_CLOSED' => $type_closed, + 'GROUP_HIDDEN' => $type_hidden, + + 'U_BACK' => $u_back, + 'U_ACTION' => "{$this->u_action}&action=$action&g=$group_id", + 'L_AVATAR_EXPLAIN' => phpbb_avatar_explanation_string(), + )); + + /** + * Modify group template data before we display the form + * + * @event core.acp_manage_group_display_form + * @var string action Type of the action: add|edit + * @var bool update Do we display the form only + * or did the user press submit + * @var int group_id The group id + * @var array group_row Array with new group data + * @var string group_name The group name + * @var int group_type The group type + * @var array group_desc_data The group description data + * @var string group_rank The group rank + * @var string rank_options The rank options + * @var array error Array of errors, if you add errors + * ensure to update the template variables + * S_ERROR and ERROR_MSG to display it + * @since 3.1.0-b5 + */ + $vars = array( + 'action', + 'update', + 'group_id', + 'group_row', + 'group_desc_data', + 'group_name', + 'group_type', + 'group_rank', + 'rank_options', + 'error', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_manage_group_display_form', compact($vars))); + + return; + break; + + case 'list': + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $this->page_title = 'GROUP_MEMBERS'; + + // Grab the leaders - always, on every page... + $sql = 'SELECT u.user_id, u.username, u.username_clean, u.user_regdate, u.user_colour, u.user_posts, u.group_id, ug.group_leader, ug.user_pending + FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . " ug + WHERE ug.group_id = $group_id + AND u.user_id = ug.user_id + AND ug.group_leader = 1 + ORDER BY ug.group_leader DESC, ug.user_pending ASC, u.username_clean"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('leader', array( + 'U_USER_EDIT' => append_sid("{$phpbb_admin_path}index.$phpEx", "i=users&action=edit&u={$row['user_id']}"), + + 'USERNAME' => $row['username'], + 'USERNAME_COLOUR' => $row['user_colour'], + 'S_GROUP_DEFAULT' => ($row['group_id'] == $group_id) ? true : false, + 'JOINED' => ($row['user_regdate']) ? $user->format_date($row['user_regdate']) : ' - ', + 'USER_POSTS' => $row['user_posts'], + 'USER_ID' => $row['user_id'], + )); + } + $db->sql_freeresult($result); + + // Total number of group members (non-leaders) + $sql = 'SELECT COUNT(user_id) AS total_members + FROM ' . USER_GROUP_TABLE . " + WHERE group_id = $group_id + AND group_leader = 0"; + $result = $db->sql_query($sql); + $total_members = (int) $db->sql_fetchfield('total_members'); + $db->sql_freeresult($result); + + $s_action_options = ''; + $options = array('default' => 'DEFAULT', 'approve' => 'APPROVE', 'demote' => 'DEMOTE', 'promote' => 'PROMOTE', 'deleteusers' => 'DELETE'); + + foreach ($options as $option => $lang) + { + $s_action_options .= ''; + } + + $base_url = $this->u_action . "&action=$action&g=$group_id"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $total_members, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'S_LIST' => true, + 'S_GROUP_SPECIAL' => ($group_row['group_type'] == GROUP_SPECIAL) ? true : false, + 'S_ACTION_OPTIONS' => $s_action_options, + + 'GROUP_NAME' => $group_helper->get_name($group_row['group_name']), + + 'U_ACTION' => $this->u_action . "&g=$group_id", + 'U_BACK' => $this->u_action, + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=list&field=usernames'), + 'U_DEFAULT_ALL' => "{$this->u_action}&action=set_default_on_all&g=$group_id", + )); + + // Grab the members + $sql = 'SELECT u.user_id, u.username, u.username_clean, u.user_colour, u.user_regdate, u.user_posts, u.group_id, ug.group_leader, ug.user_pending + FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . " ug + WHERE ug.group_id = $group_id + AND u.user_id = ug.user_id + AND ug.group_leader = 0 + ORDER BY ug.group_leader DESC, ug.user_pending ASC, u.username_clean"; + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $pending = false; + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['user_pending'] && !$pending) + { + $template->assign_block_vars('member', array( + 'S_PENDING' => true) + ); + + $pending = true; + } + + $template->assign_block_vars('member', array( + 'U_USER_EDIT' => append_sid("{$phpbb_admin_path}index.$phpEx", "i=users&action=edit&u={$row['user_id']}"), + + 'USERNAME' => $row['username'], + 'USERNAME_COLOUR' => $row['user_colour'], + 'S_GROUP_DEFAULT' => ($row['group_id'] == $group_id) ? true : false, + 'JOINED' => ($row['user_regdate']) ? $user->format_date($row['user_regdate']) : ' - ', + 'USER_POSTS' => $row['user_posts'], + 'USER_ID' => $row['user_id']) + ); + } + $db->sql_freeresult($result); + + return; + break; + } + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'S_GROUP_ADD' => ($auth->acl_get('a_groupadd')) ? true : false) + ); + + // Get us all the groups + $sql = 'SELECT g.group_id, g.group_name, g.group_type, g.group_colour + FROM ' . GROUPS_TABLE . ' g + ORDER BY g.group_type ASC, g.group_name'; + $result = $db->sql_query($sql); + + $lookup = $cached_group_data = array(); + while ($row = $db->sql_fetchrow($result)) + { + $type = ($row['group_type'] == GROUP_SPECIAL) ? 'special' : 'normal'; + + // used to determine what type a group is + $lookup[$row['group_id']] = $type; + + // used for easy access to the data within a group + $cached_group_data[$type][$row['group_id']] = $row; + $cached_group_data[$type][$row['group_id']]['total_members'] = 0; + $cached_group_data[$type][$row['group_id']]['pending_members'] = 0; + } + $db->sql_freeresult($result); + + // How many people are in which group? + $sql = 'SELECT COUNT(ug.user_id) AS total_members, SUM(ug.user_pending) AS pending_members, ug.group_id + FROM ' . USER_GROUP_TABLE . ' ug + WHERE ' . $db->sql_in_set('ug.group_id', array_keys($lookup)) . ' + GROUP BY ug.group_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $type = $lookup[$row['group_id']]; + $cached_group_data[$type][$row['group_id']]['total_members'] = $row['total_members']; + $cached_group_data[$type][$row['group_id']]['pending_members'] = $row['pending_members']; + } + $db->sql_freeresult($result); + + // The order is... normal, then special + ksort($cached_group_data); + + foreach ($cached_group_data as $type => $row_ary) + { + if ($type == 'special') + { + $template->assign_block_vars('groups', array( + 'S_SPECIAL' => true) + ); + } + + foreach ($row_ary as $group_id => $row) + { + $group_name = (!empty($user->lang['G_' . $row['group_name']]))? $user->lang['G_' . $row['group_name']] : $row['group_name']; + + $template->assign_block_vars('groups', array( + 'U_LIST' => "{$this->u_action}&action=list&g=$group_id", + 'U_EDIT' => "{$this->u_action}&action=edit&g=$group_id", + 'U_DELETE' => ($auth->acl_get('a_groupdel')) ? "{$this->u_action}&action=delete&g=$group_id" : '', + + 'S_GROUP_SPECIAL' => ($row['group_type'] == GROUP_SPECIAL) ? true : false, + + 'GROUP_NAME' => $group_name, + 'GROUP_COLOR' => $row['group_colour'], + 'TOTAL_MEMBERS' => $row['total_members'], + 'PENDING_MEMBERS' => $row['pending_members'] + )); + } + } + } + + public function manage_position() + { + global $config, $db, $template, $user, $request, $phpbb_container; + + $this->tpl_name = 'acp_groups_position'; + $this->page_title = 'ACP_GROUPS_POSITION'; + + $field = $request->variable('field', ''); + $action = $request->variable('action', ''); + $group_id = $request->variable('g', 0); + $teampage_id = $request->variable('t', 0); + $category_id = $request->variable('c', 0); + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + if ($field && !in_array($field, array('legend', 'teampage'))) + { + // Invalid mode + trigger_error($user->lang['NO_MODE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + else if ($field && in_array($field, array('legend', 'teampage'))) + { + /* @var $group_position \phpbb\groupposition\groupposition_interface */ + $group_position = $phpbb_container->get('groupposition.' . $field); + } + + if ($field == 'teampage') + { + try + { + switch ($action) + { + case 'add': + $group_position->add_group_teampage($group_id, $category_id); + break; + + case 'add_category': + $group_position->add_category_teampage($request->variable('category_name', '', true)); + break; + + case 'delete': + $group_position->delete_teampage($teampage_id); + break; + + case 'move_up': + $group_position->move_up_teampage($teampage_id); + break; + + case 'move_down': + $group_position->move_down_teampage($teampage_id); + break; + } + } + catch (\phpbb\groupposition\exception $exception) + { + trigger_error($user->lang($exception->getMessage()) . adm_back_link($this->u_action), E_USER_WARNING); + } + } + else if ($field == 'legend') + { + try + { + switch ($action) + { + case 'add': + $group_position->add_group($group_id); + break; + + case 'delete': + $group_position->delete_group($group_id); + break; + + case 'move_up': + $group_position->move_up($group_id); + break; + + case 'move_down': + $group_position->move_down($group_id); + break; + } + } + catch (\phpbb\groupposition\exception $exception) + { + trigger_error($user->lang($exception->getMessage()) . adm_back_link($this->u_action), E_USER_WARNING); + } + } + else + { + switch ($action) + { + case 'set_config_teampage': + $config->set('teampage_forums', $request->variable('teampage_forums', 0)); + $config->set('teampage_memberships', $request->variable('teampage_memberships', 0)); + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action)); + break; + + case 'set_config_legend': + $config->set('legend_sort_groupname', $request->variable('legend_sort_groupname', 0)); + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($this->u_action)); + break; + } + } + + if (($action == 'move_up' || $action == 'move_down') && $request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array('success' => true)); + } + + $sql = 'SELECT group_id, group_name, group_colour, group_type, group_legend + FROM ' . GROUPS_TABLE . ' + ORDER BY group_legend ASC, group_type DESC, group_name ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $group_name = $group_helper->get_name($row['group_name']); + if ($row['group_legend']) + { + $template->assign_block_vars('legend', array( + 'GROUP_NAME' => $group_name, + 'GROUP_COLOUR' => ($row['group_colour']) ? '#' . $row['group_colour'] : '', + 'GROUP_TYPE' => $user->lang[\phpbb\groupposition\legend::group_type_language($row['group_type'])], + + 'U_MOVE_DOWN' => "{$this->u_action}&field=legend&action=move_down&g=" . $row['group_id'], + 'U_MOVE_UP' => "{$this->u_action}&field=legend&action=move_up&g=" . $row['group_id'], + 'U_DELETE' => "{$this->u_action}&field=legend&action=delete&g=" . $row['group_id'], + )); + } + else + { + $template->assign_block_vars('add_legend', array( + 'GROUP_ID' => (int) $row['group_id'], + 'GROUP_NAME' => $group_name, + 'GROUP_SPECIAL' => ($row['group_type'] == GROUP_SPECIAL), + )); + } + } + $db->sql_freeresult($result); + + $category_url_param = (($category_id) ? '&c=' . $category_id : ''); + + $sql = 'SELECT t.*, g.group_name, g.group_colour, g.group_type + FROM ' . TEAMPAGE_TABLE . ' t + LEFT JOIN ' . GROUPS_TABLE . ' g + ON (t.group_id = g.group_id) + WHERE t.teampage_parent = ' . $category_id . ' + OR t.teampage_id = ' . $category_id . ' + ORDER BY t.teampage_position ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['teampage_id'] == $category_id) + { + $template->assign_vars(array( + 'CURRENT_CATEGORY_NAME' => $row['teampage_name'], + )); + continue; + } + + if ($row['group_id']) + { + $group_name = $group_helper->get_name($row['group_name']); + $group_type = $user->lang[\phpbb\groupposition\teampage::group_type_language($row['group_type'])]; + } + else + { + $group_name = $row['teampage_name']; + $group_type = ''; + } + + $template->assign_block_vars('teampage', array( + 'GROUP_NAME' => $group_name, + 'GROUP_COLOUR' => ($row['group_colour']) ? '#' . $row['group_colour'] : '', + 'GROUP_TYPE' => $group_type, + + 'U_CATEGORY' => (!$row['group_id']) ? "{$this->u_action}&c=" . $row['teampage_id'] : '', + 'U_MOVE_DOWN' => "{$this->u_action}&field=teampage&action=move_down{$category_url_param}&t=" . $row['teampage_id'], + 'U_MOVE_UP' => "{$this->u_action}&field=teampage&action=move_up{$category_url_param}&t=" . $row['teampage_id'], + 'U_DELETE' => "{$this->u_action}&field=teampage&action=delete{$category_url_param}&t=" . $row['teampage_id'], + )); + } + $db->sql_freeresult($result); + + $sql = 'SELECT g.group_id, g.group_name, g.group_colour, g.group_type + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . TEAMPAGE_TABLE . ' t + ON (t.group_id = g.group_id) + WHERE t.teampage_id IS NULL + ORDER BY g.group_type DESC, g.group_name ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $group_name = $group_helper->get_name($row['group_name']); + $template->assign_block_vars('add_teampage', array( + 'GROUP_ID' => (int) $row['group_id'], + 'GROUP_NAME' => $group_name, + 'GROUP_SPECIAL' => ($row['group_type'] == GROUP_SPECIAL), + )); + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'U_ACTION_LEGEND' => $this->u_action . '&field=legend', + 'U_ACTION_TEAMPAGE' => $this->u_action . '&field=teampage' . $category_url_param, + 'U_ACTION_TEAMPAGE_CAT' => $this->u_action . '&field=teampage_cat', + + 'S_TEAMPAGE_CATEGORY' => $category_id, + 'DISPLAY_FORUMS' => ($config['teampage_forums']) ? true : false, + 'DISPLAY_MEMBERSHIPS' => $config['teampage_memberships'], + 'LEGEND_SORT_GROUPNAME' => ($config['legend_sort_groupname']) ? true : false, + )); + } +} diff --git a/includes/acp/acp_help_phpbb.php b/includes/acp/acp_help_phpbb.php new file mode 100644 index 0000000..a36b36e --- /dev/null +++ b/includes/acp/acp_help_phpbb.php @@ -0,0 +1,143 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_help_phpbb +{ + var $u_action; + + function main($id, $mode) + { + global $config, $request, $template, $user, $phpbb_dispatcher, $phpbb_admin_path, $phpbb_root_path, $phpEx; + + if (!class_exists('phpbb_questionnaire_data_collector')) + { + include($phpbb_root_path . 'includes/questionnaire/questionnaire.' . $phpEx); + } + + $collect_url = "https://www.phpbb.com/stats/receive_stats.php"; + + $this->tpl_name = 'acp_help_phpbb'; + $this->page_title = 'ACP_HELP_PHPBB'; + + $submit = ($request->is_set_post('submit')) ? true : false; + + $form_key = 'acp_help_phpbb'; + add_form_key($form_key); + $error = array(); + + if ($submit && !check_form_key($form_key)) + { + $error[] = $user->lang['FORM_INVALID']; + } + // Do not write values if there is an error + if (count($error)) + { + $submit = false; + } + + // generate a unique id if necessary + if (!isset($config['questionnaire_unique_id'])) + { + $install_id = unique_id(); + $config->set('questionnaire_unique_id', $install_id); + } + else + { + $install_id = $config['questionnaire_unique_id']; + } + + $collector = new phpbb_questionnaire_data_collector($install_id); + + // Add data provider + $collector->add_data_provider(new phpbb_questionnaire_php_data_provider()); + $collector->add_data_provider(new phpbb_questionnaire_system_data_provider()); + $collector->add_data_provider(new phpbb_questionnaire_phpbb_data_provider($config)); + + /** + * Event to modify ACP help phpBB page and/or listen to submit + * + * @event core.acp_help_phpbb_submit_before + * @var boolean submit Do we display the form or process the submission + * @since 3.2.0-RC2 + */ + $vars = array('submit'); + extract($phpbb_dispatcher->trigger_event('core.acp_help_phpbb_submit_before', compact($vars))); + + if ($submit) + { + $config->set('help_send_statistics', $request->variable('help_send_statistics', false)); + $response = $request->variable('send_statistics_response', ''); + + $config->set('help_send_statistics_time', time()); + + if (!empty($response)) + { + if ((strpos($response, 'Thank you') !== false || strpos($response, 'Flood protection') !== false)) + { + trigger_error($user->lang('THANKS_SEND_STATISTICS') . adm_back_link($this->u_action)); + } + else + { + trigger_error($user->lang('FAIL_SEND_STATISTICS') . adm_back_link($this->u_action)); + } + } + + trigger_error($user->lang('CONFIG_UPDATED') . adm_back_link($this->u_action)); + } + + $template->assign_vars(array( + 'U_COLLECT_STATS' => $collect_url, + 'S_COLLECT_STATS' => (!empty($config['help_send_statistics'])) ? true : false, + 'RAW_DATA' => $collector->get_data_for_form(), + 'U_ACP_MAIN' => append_sid("{$phpbb_admin_path}index.$phpEx"), + 'U_ACTION' => $this->u_action, + // Pass earliest time we should try to send stats again + 'COLLECT_STATS_TIME' => intval($config['help_send_statistics_time']) + 86400, + )); + + $raw = $collector->get_data_raw(); + + foreach ($raw as $provider => $data) + { + if ($provider == 'install_id') + { + $data = array($provider => $data); + } + + $template->assign_block_vars('providers', array( + 'NAME' => htmlspecialchars($provider), + )); + + foreach ($data as $key => $value) + { + if (is_array($value)) + { + $value = utf8_wordwrap(serialize($value), 75, "\n", true); + } + + $template->assign_block_vars('providers.values', array( + 'KEY' => utf8_htmlspecialchars($key), + 'VALUE' => utf8_htmlspecialchars($value), + )); + } + } + } +} diff --git a/includes/acp/acp_icons.php b/includes/acp/acp_icons.php new file mode 100644 index 0000000..2c3948f --- /dev/null +++ b/includes/acp/acp_icons.php @@ -0,0 +1,999 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* @todo [smilies] check regular expressions for special char replacements (stored specialchared in db) +*/ +class acp_icons +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $template, $cache; + global $config, $phpbb_root_path; + global $request, $phpbb_container; + + $user->add_lang('acp/posting'); + + // Set up general vars + $action = $request->variable('action', ''); + $action = (isset($_POST['add'])) ? 'add' : $action; + $action = (isset($_POST['edit'])) ? 'edit' : $action; + $action = (isset($_POST['import'])) ? 'import' : $action; + $icon_id = $request->variable('id', 0); + $submit = $request->is_set_post('submit', false); + + $form_key = 'acp_icons'; + add_form_key($form_key); + + $mode = ($mode == 'smilies') ? 'smilies' : 'icons'; + + $this->tpl_name = 'acp_icons'; + + // What are we working on? + switch ($mode) + { + case 'smilies': + $table = SMILIES_TABLE; + $lang = 'SMILIES'; + $fields = 'smiley'; + $img_path = $config['smilies_path']; + break; + + case 'icons': + $table = ICONS_TABLE; + $lang = 'ICONS'; + $fields = 'icons'; + $img_path = $config['icons_path']; + break; + } + + $this->page_title = 'ACP_' . $lang; + + // Clear some arrays + $_images = $_paks = array(); + $notice = ''; + + // Grab file list of paks and images + if ($action == 'edit' || $action == 'add' || $action == 'import') + { + $imglist = filelist($phpbb_root_path . $img_path, ''); + + foreach ($imglist as $path => $img_ary) + { + if (empty($img_ary)) + { + continue; + } + + asort($img_ary, SORT_STRING); + + foreach ($img_ary as $img) + { + $img_size = getimagesize($phpbb_root_path . $img_path . '/' . $path . $img); + + if (!$img_size[0] || !$img_size[1] || strlen($img) > 255) + { + continue; + } + + // adjust the width and height to be lower than 128px while perserving the aspect ratio (for icons) + if ($mode == 'icons') + { + if ($img_size[0] > 127 && $img_size[0] > $img_size[1]) + { + $img_size[1] = (int) ($img_size[1] * (127 / $img_size[0])); + $img_size[0] = 127; + } + else if ($img_size[1] > 127) + { + $img_size[0] = (int) ($img_size[0] * (127 / $img_size[1])); + $img_size[1] = 127; + } + } + + $_images[$path . $img]['file'] = $path . $img; + $_images[$path . $img]['width'] = $img_size[0]; + $_images[$path . $img]['height'] = $img_size[1]; + } + } + unset($imglist); + + if ($dir = @opendir($phpbb_root_path . $img_path)) + { + while (($file = readdir($dir)) !== false) + { + if (is_file($phpbb_root_path . $img_path . '/' . $file) && preg_match('#\.pak$#i', $file)) + { + $_paks[] = $file; + } + } + closedir($dir); + + if (!empty($_paks)) + { + asort($_paks, SORT_STRING); + } + } + } + + // What shall we do today? Oops, I believe that's trademarked ... + switch ($action) + { + case 'edit': + unset($_images); + $_images = array(); + + // no break; + + case 'add': + + $smilies = $default_row = array(); + $smiley_options = $order_list = $add_order_list = ''; + + if ($action == 'add' && $mode == 'smilies') + { + $sql = 'SELECT * + FROM ' . SMILIES_TABLE . ' + ORDER BY smiley_order'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (empty($smilies[$row['smiley_url']])) + { + $smilies[$row['smiley_url']] = $row; + } + } + $db->sql_freeresult($result); + + if (count($smilies)) + { + foreach ($smilies as $row) + { + $selected = false; + + if (!$smiley_options) + { + $selected = true; + $default_row = $row; + } + $smiley_options .= ''; + + $template->assign_block_vars('smile', array( + 'SMILEY_URL' => addslashes($row['smiley_url']), + 'CODE' => addslashes($row['code']), + 'EMOTION' => addslashes($row['emotion']), + 'WIDTH' => $row['smiley_width'], + 'HEIGHT' => $row['smiley_height'], + 'ORDER' => $row['smiley_order'] + 1, + )); + } + } + } + + $sql = "SELECT * + FROM $table + ORDER BY {$fields}_order " . (($icon_id || $action == 'add') ? 'DESC' : 'ASC'); + $result = $db->sql_query($sql); + + $data = array(); + $after = false; + $order_lists = array('', ''); + $add_order_lists = array('', ''); + $display_count = 0; + + while ($row = $db->sql_fetchrow($result)) + { + if ($action == 'add') + { + unset($_images[$row[$fields . '_url']]); + } + + if ($row[$fields . '_id'] == $icon_id) + { + $after = true; + $data[$row[$fields . '_url']] = $row; + } + else + { + if ($action == 'edit' && !$icon_id) + { + $data[$row[$fields . '_url']] = $row; + } + + $selected = ''; + if (!empty($after)) + { + $selected = ' selected="selected"'; + $after = false; + } + if ($row['display_on_posting']) + { + $display_count++; + } + $after_txt = ($mode == 'smilies') ? $row['code'] : $row['icons_url']; + $order_lists[$row['display_on_posting']] = '' . $order_lists[$row['display_on_posting']]; + + if (!empty($default_row)) + { + $add_order_lists[$row['display_on_posting']] = '' . $add_order_lists[$row['display_on_posting']]; + } + } + } + $db->sql_freeresult($result); + + $order_list = ''; + $add_order_list = ''; + + if ($action == 'add') + { + $data = $_images; + } + + $colspan = (($mode == 'smilies') ? 7 : 6); + $colspan += ($icon_id) ? 1 : 0; + $colspan += ($action == 'add') ? 2 : 0; + + $template->assign_vars(array( + 'S_EDIT' => true, + 'S_SMILIES' => ($mode == 'smilies') ? true : false, + 'S_ADD' => ($action == 'add') ? true : false, + + 'S_ORDER_LIST_DISPLAY' => $order_list . $order_lists[1], + 'S_ORDER_LIST_UNDISPLAY' => $order_list . $order_lists[0], + 'S_ORDER_LIST_DISPLAY_COUNT' => $display_count + 1, + + 'L_TITLE' => $user->lang['ACP_' . $lang], + 'L_EXPLAIN' => $user->lang['ACP_' . $lang . '_EXPLAIN'], + 'L_CONFIG' => $user->lang[$lang . '_CONFIG'], + 'L_URL' => $user->lang[$lang . '_URL'], + 'L_LOCATION' => $user->lang[$lang . '_LOCATION'], + 'L_WIDTH' => $user->lang[$lang . '_WIDTH'], + 'L_HEIGHT' => $user->lang[$lang . '_HEIGHT'], + 'L_ORDER' => $user->lang[$lang . '_ORDER'], + 'L_NO_ICONS' => $user->lang['NO_' . $lang . '_' . strtoupper($action)], + + 'COLSPAN' => $colspan, + 'ID' => $icon_id, + + 'U_BACK' => $this->u_action, + 'U_ACTION' => $this->u_action . '&action=' . (($action == 'add') ? 'create' : 'modify'), + )); + + foreach ($data as $img => $img_row) + { + $template->assign_block_vars('items', array( + 'IMG' => $img, + 'A_IMG' => addslashes($img), + 'IMG_SRC' => $phpbb_root_path . $img_path . '/' . $img, + + 'CODE' => ($mode == 'smilies' && isset($img_row['code'])) ? $img_row['code'] : '', + 'EMOTION' => ($mode == 'smilies' && isset($img_row['emotion'])) ? $img_row['emotion'] : '', + + 'S_ID' => (isset($img_row[$fields . '_id'])) ? true : false, + 'ID' => (isset($img_row[$fields . '_id'])) ? $img_row[$fields . '_id'] : 0, + 'WIDTH' => (!empty($img_row[$fields .'_width'])) ? $img_row[$fields .'_width'] : $img_row['width'], + 'HEIGHT' => (!empty($img_row[$fields .'_height'])) ? $img_row[$fields .'_height'] : $img_row['height'], + 'TEXT_ALT' => ($mode == 'icons' && !empty($img_row['icons_alt'])) ? $img_row['icons_alt'] : $img, + 'ALT' => ($mode == 'icons' && !empty($img_row['icons_alt'])) ? $img_row['icons_alt'] : '', + 'POSTING_CHECKED' => (!empty($img_row['display_on_posting']) || $action == 'add') ? ' checked="checked"' : '', + )); + } + + // Ok, another row for adding an addition code for a pre-existing image... + if ($action == 'add' && $mode == 'smilies' && count($smilies)) + { + $template->assign_vars(array( + 'S_ADD_CODE' => true, + + 'S_IMG_OPTIONS' => $smiley_options, + + 'S_ADD_ORDER_LIST_DISPLAY' => $add_order_list . $add_order_lists[1], + 'S_ADD_ORDER_LIST_UNDISPLAY' => $add_order_list . $add_order_lists[0], + + 'IMG_SRC' => $phpbb_root_path . $img_path . '/' . $default_row['smiley_url'], + 'IMG_PATH' => $img_path, + + 'CODE' => $default_row['code'], + 'EMOTION' => $default_row['emotion'], + + 'WIDTH' => $default_row['smiley_width'], + 'HEIGHT' => $default_row['smiley_height'], + )); + } + + return; + + break; + + case 'create': + case 'modify': + + if (!check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Get items to create/modify + $images = (isset($_POST['image'])) ? array_keys($request->variable('image', array('' => 0))) : array(); + + // Now really get the items + $image_id = (isset($_POST['id'])) ? $request->variable('id', array('' => 0)) : array(); + $image_order = (isset($_POST['order'])) ? $request->variable('order', array('' => 0)) : array(); + $image_width = (isset($_POST['width'])) ? $request->variable('width', array('' => 0)) : array(); + $image_height = (isset($_POST['height'])) ? $request->variable('height', array('' => 0)) : array(); + $image_add = (isset($_POST['add_img'])) ? $request->variable('add_img', array('' => 0)) : array(); + $image_emotion = $request->variable('emotion', array('' => ''), true); + $image_code = $request->variable('code', array('' => ''), true); + $image_alt = ($request->is_set_post('alt')) ? $request->variable('alt', array('' => ''), true) : array(); + $image_display_on_posting = (isset($_POST['display_on_posting'])) ? $request->variable('display_on_posting', array('' => 0)) : array(); + + // Ok, add the relevant bits if we are adding new codes to existing emoticons... + if ($request->variable('add_additional_code', false, false, \phpbb\request\request_interface::POST)) + { + $add_image = $request->variable('add_image', ''); + $add_code = $request->variable('add_code', '', true); + $add_emotion = $request->variable('add_emotion', '', true); + + if ($add_image && $add_emotion && $add_code) + { + $images[] = $add_image; + $image_add[$add_image] = true; + + $image_code[$add_image] = $add_code; + $image_emotion[$add_image] = $add_emotion; + $image_width[$add_image] = $request->variable('add_width', 0); + $image_height[$add_image] = $request->variable('add_height', 0); + + if ($request->variable('add_display_on_posting', false, false, \phpbb\request\request_interface::POST)) + { + $image_display_on_posting[$add_image] = 1; + } + + $image_order[$add_image] = $request->variable('add_order', 0); + } + } + + if ($mode == 'smilies' && $action == 'create') + { + $smiley_count = $this->item_count($table); + + $addable_smileys_count = count($images); + foreach ($images as $image) + { + if (!isset($image_add[$image])) + { + --$addable_smileys_count; + } + } + + if ($smiley_count + $addable_smileys_count > SMILEY_LIMIT) + { + trigger_error($user->lang('TOO_MANY_SMILIES', SMILEY_LIMIT) . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + $icons_updated = 0; + $errors = array(); + foreach ($images as $image) + { + if ($mode == 'smilies' && ($image_emotion[$image] == '' || $image_code[$image] == '')) + { + $errors[$image] = 'SMILIE_NO_' . (($image_emotion[$image] == '') ? 'EMOTION' : 'CODE'); + } + else if ($action == 'create' && !isset($image_add[$image])) + { + // skip images where add wasn't checked + } + else if (!file_exists($phpbb_root_path . $img_path . '/' . $image)) + { + $errors[$image] = 'SMILIE_NO_FILE'; + } + else + { + if ($image_width[$image] == 0 || $image_height[$image] == 0) + { + $img_size = getimagesize($phpbb_root_path . $img_path . '/' . $image); + $image_width[$image] = $img_size[0]; + $image_height[$image] = $img_size[1]; + } + + // Adjust image width/height for icons + if ($mode == 'icons') + { + if ($image_width[$image] > 127 && $image_width[$image] > $image_height[$image]) + { + $image_height[$image] = (int) ($image_height[$image] * (127 / $image_width[$image])); + $image_width[$image] = 127; + } + else if ($image_height[$image] > 127) + { + $image_width[$image] = (int) ($image_width[$image] * (127 / $image_height[$image])); + $image_height[$image] = 127; + } + } + + $img_sql = array( + $fields . '_url' => $image, + $fields . '_width' => $image_width[$image], + $fields . '_height' => $image_height[$image], + 'display_on_posting' => (isset($image_display_on_posting[$image])) ? 1 : 0, + ); + + if ($mode == 'smilies') + { + $img_sql = array_merge($img_sql, array( + 'emotion' => $image_emotion[$image], + 'code' => $image_code[$image]) + ); + } + + if ($mode == 'icons') + { + $img_sql = array_merge($img_sql, array( + 'icons_alt' => $image_alt[$image]) + ); + } + + // Image_order holds the 'new' order value + if (!empty($image_order[$image])) + { + $img_sql = array_merge($img_sql, array( + $fields . '_order' => $image_order[$image]) + ); + + // Since we always add 'after' an item, we just need to increase all following + the current by one + $sql = "UPDATE $table + SET {$fields}_order = {$fields}_order + 1 + WHERE {$fields}_order >= {$image_order[$image]}"; + $db->sql_query($sql); + + // If we adjust the order, we need to adjust all other orders too - they became inaccurate... + foreach ($image_order as $_image => $_order) + { + if ($_image == $image) + { + continue; + } + + if ($_order >= $image_order[$image]) + { + $image_order[$_image]++; + } + } + } + + if ($action == 'modify' && !empty($image_id[$image])) + { + $sql = "UPDATE $table + SET " . $db->sql_build_array('UPDATE', $img_sql) . " + WHERE {$fields}_id = " . $image_id[$image]; + $db->sql_query($sql); + $icons_updated++; + } + else if ($action !== 'modify') + { + $sql = "INSERT INTO $table " . $db->sql_build_array('INSERT', $img_sql); + $db->sql_query($sql); + $icons_updated++; + } + + } + } + + $cache->destroy('_icons'); + $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); + + $level = ($icons_updated) ? E_USER_NOTICE : E_USER_WARNING; + $errormsgs = ''; + foreach ($errors as $img => $error) + { + $errormsgs .= '
' . sprintf($user->lang[$error], $img); + } + if ($action == 'modify') + { + trigger_error($user->lang($lang . '_EDITED', $icons_updated) . $errormsgs . adm_back_link($this->u_action), $level); + } + else + { + trigger_error($user->lang($lang . '_ADDED', $icons_updated) . $errormsgs . adm_back_link($this->u_action), $level); + } + + break; + + case 'import': + + $pak = $request->variable('pak', ''); + $current = $request->variable('current', ''); + + if ($pak != '') + { + $order = 0; + + if (!check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (!($pak_ary = @file($phpbb_root_path . $img_path . '/' . $pak))) + { + trigger_error($user->lang['PAK_FILE_NOT_READABLE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Make sure the pak_ary is valid + foreach ($pak_ary as $pak_entry) + { + if (preg_match_all("#'(.*?)', ?#", $pak_entry, $data)) + { + if ((count($data[1]) != 4 && $mode == 'icons') || + ((count($data[1]) != 6 || (empty($data[1][4]) || empty($data[1][5]))) && $mode == 'smilies' )) + { + trigger_error($user->lang['WRONG_PAK_TYPE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + else + { + trigger_error($user->lang['WRONG_PAK_TYPE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + // The user has already selected a smilies_pak file + if ($current == 'delete') + { + switch ($db->get_sql_layer()) + { + case 'sqlite3': + $db->sql_query('DELETE FROM ' . $table); + break; + + default: + $db->sql_query('TRUNCATE TABLE ' . $table); + break; + } + + switch ($mode) + { + case 'smilies': + break; + + case 'icons': + // Reset all icon_ids + $db->sql_query('UPDATE ' . TOPICS_TABLE . ' SET icon_id = 0'); + $db->sql_query('UPDATE ' . POSTS_TABLE . ' SET icon_id = 0'); + break; + } + } + else + { + $cur_img = array(); + + $field_sql = ($mode == 'smilies') ? 'code' : 'icons_url'; + + $sql = "SELECT $field_sql + FROM $table"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + ++$order; + $cur_img[$row[$field_sql]] = 1; + } + $db->sql_freeresult($result); + } + + if ($mode == 'smilies') + { + $smiley_count = $this->item_count($table); + if ($smiley_count + count($pak_ary) > SMILEY_LIMIT) + { + trigger_error($user->lang('TOO_MANY_SMILIES', SMILEY_LIMIT) . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + foreach ($pak_ary as $pak_entry) + { + $data = array(); + if (preg_match_all("#'(.*?)', ?#", $pak_entry, $data)) + { + if ((count($data[1]) != 4 && $mode == 'icons') || + (count($data[1]) != 6 && $mode == 'smilies')) + { + trigger_error($user->lang['WRONG_PAK_TYPE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Stripslash here because it got addslashed before... (on export) + $img = stripslashes($data[1][0]); + $width = stripslashes($data[1][1]); + $height = stripslashes($data[1][2]); + $display_on_posting = stripslashes($data[1][3]); + + if (isset($data[1][4]) && isset($data[1][5])) + { + $emotion = stripslashes($data[1][4]); + $code = stripslashes($data[1][5]); + } + + if ($current == 'replace' && + (($mode == 'smilies' && !empty($cur_img[$code])) || + ($mode == 'icons' && !empty($cur_img[$img])))) + { + $replace_sql = ($mode == 'smilies') ? $code : $img; + $sql = array( + $fields . '_url' => $img, + $fields . '_height' => (int) $height, + $fields . '_width' => (int) $width, + 'display_on_posting' => (int) $display_on_posting, + ); + + if ($mode == 'smilies') + { + $sql = array_merge($sql, array( + 'emotion' => $emotion, + )); + } + + $sql = "UPDATE $table SET " . $db->sql_build_array('UPDATE', $sql) . " + WHERE $field_sql = '" . $db->sql_escape($replace_sql) . "'"; + $db->sql_query($sql); + } + else + { + ++$order; + + $sql = array( + $fields . '_url' => $img, + $fields . '_height' => (int) $height, + $fields . '_width' => (int) $width, + $fields . '_order' => (int) $order, + 'display_on_posting'=> (int) $display_on_posting, + ); + + if ($mode == 'smilies') + { + $sql = array_merge($sql, array( + 'code' => $code, + 'emotion' => $emotion, + )); + } + $db->sql_query("INSERT INTO $table " . $db->sql_build_array('INSERT', $sql)); + } + } + } + + $cache->destroy('_icons'); + $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); + + trigger_error($user->lang[$lang . '_IMPORT_SUCCESS'] . adm_back_link($this->u_action)); + } + else + { + $pak_options = ''; + + foreach ($_paks as $pak) + { + $pak_options .= ''; + } + + $template->assign_vars(array( + 'S_CHOOSE_PAK' => true, + 'S_PAK_OPTIONS' => $pak_options, + + 'L_TITLE' => $user->lang['ACP_' . $lang], + 'L_EXPLAIN' => $user->lang['ACP_' . $lang . '_EXPLAIN'], + 'L_NO_PAK_OPTIONS' => $user->lang['NO_' . $lang . '_PAK'], + 'L_CURRENT' => $user->lang['CURRENT_' . $lang], + 'L_CURRENT_EXPLAIN' => $user->lang['CURRENT_' . $lang . '_EXPLAIN'], + 'L_IMPORT_SUBMIT' => $user->lang['IMPORT_' . $lang], + + 'U_BACK' => $this->u_action, + 'U_ACTION' => $this->u_action . '&action=import', + ) + ); + } + break; + + case 'export': + + $this->page_title = 'EXPORT_' . $lang; + $this->tpl_name = 'message_body'; + + $template->assign_vars(array( + 'MESSAGE_TITLE' => $user->lang['EXPORT_' . $lang], + 'MESSAGE_TEXT' => sprintf($user->lang['EXPORT_' . $lang . '_EXPLAIN'], '', ''), + + 'S_USER_NOTICE' => true, + ) + ); + + return; + + break; + + case 'send': + + if (!check_link_hash($request->variable('hash', ''), 'acp_icons')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = "SELECT * + FROM $table + ORDER BY {$fields}_order"; + $result = $db->sql_query($sql); + + $pak = ''; + while ($row = $db->sql_fetchrow($result)) + { + $pak .= "'" . addslashes($row[$fields . '_url']) . "', "; + $pak .= "'" . addslashes($row[$fields . '_width']) . "', "; + $pak .= "'" . addslashes($row[$fields . '_height']) . "', "; + $pak .= "'" . addslashes($row['display_on_posting']) . "', "; + + if ($mode == 'smilies') + { + $pak .= "'" . addslashes($row['emotion']) . "', "; + $pak .= "'" . addslashes($row['code']) . "', "; + } + + $pak .= "\n"; + } + $db->sql_freeresult($result); + + if ($pak != '') + { + garbage_collection(); + + header('Cache-Control: public'); + + // Send out the Headers + header('Content-Type: text/x-delimtext; name="' . $mode . '.pak"'); + header('Content-Disposition: inline; filename="' . $mode . '.pak"'); + echo $pak; + + flush(); + exit; + } + else + { + trigger_error($user->lang['NO_' . strtoupper($fields) . '_EXPORT'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + break; + + case 'delete': + + if (confirm_box(true)) + { + $sql = "DELETE FROM $table + WHERE {$fields}_id = $icon_id"; + $db->sql_query($sql); + + switch ($mode) + { + case 'smilies': + break; + + case 'icons': + // Reset appropriate icon_ids + $db->sql_query('UPDATE ' . TOPICS_TABLE . " + SET icon_id = 0 + WHERE icon_id = $icon_id"); + + $db->sql_query('UPDATE ' . POSTS_TABLE . " + SET icon_id = 0 + WHERE icon_id = $icon_id"); + break; + } + + $notice = $user->lang[$lang . '_DELETED']; + + $cache->destroy('_icons'); + $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $notice, + 'REFRESH_DATA' => array( + 'time' => 3 + ) + )); + } + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'id' => $icon_id, + 'action' => 'delete', + ))); + } + + break; + + case 'move_up': + case 'move_down': + + if (!check_link_hash($request->variable('hash', ''), 'acp_icons')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Get current order id... + $sql = "SELECT {$fields}_order as current_order + FROM $table + WHERE {$fields}_id = $icon_id"; + $result = $db->sql_query($sql); + $current_order = (int) $db->sql_fetchfield('current_order'); + $db->sql_freeresult($result); + + if ($current_order == 0 && $action == 'move_up') + { + break; + } + + // on move_down, switch position with next order_id... + // on move_up, switch position with previous order_id... + $switch_order_id = ($action == 'move_down') ? $current_order + 1 : $current_order - 1; + + // + $sql = "UPDATE $table + SET {$fields}_order = $current_order + WHERE {$fields}_order = $switch_order_id + AND {$fields}_id <> $icon_id"; + $db->sql_query($sql); + $move_executed = (bool) $db->sql_affectedrows(); + + // Only update the other entry too if the previous entry got updated + if ($move_executed) + { + $sql = "UPDATE $table + SET {$fields}_order = $switch_order_id + WHERE {$fields}_order = $current_order + AND {$fields}_id = $icon_id"; + $db->sql_query($sql); + } + + $cache->destroy('_icons'); + $cache->destroy('sql', $table); + $phpbb_container->get('text_formatter.cache')->invalidate(); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'success' => $move_executed, + )); + } + + break; + } + + // By default, check that image_order is valid and fix it if necessary + $sql = "SELECT {$fields}_id AS order_id, {$fields}_order AS fields_order + FROM $table + ORDER BY display_on_posting DESC, {$fields}_order"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $order = 0; + do + { + ++$order; + if ($row['fields_order'] != $order) + { + $db->sql_query("UPDATE $table + SET {$fields}_order = $order + WHERE {$fields}_id = " . $row['order_id']); + } + } + while ($row = $db->sql_fetchrow($result)); + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'L_TITLE' => $user->lang['ACP_' . $lang], + 'L_EXPLAIN' => $user->lang['ACP_' . $lang . '_EXPLAIN'], + 'L_IMPORT' => $user->lang['IMPORT_' . $lang], + 'L_EXPORT' => $user->lang['EXPORT_' . $lang], + 'L_NOT_DISPLAYED' => $user->lang[$lang . '_NOT_DISPLAYED'], + 'L_ICON_ADD' => $user->lang['ADD_' . $lang], + 'L_ICON_EDIT' => $user->lang['EDIT_' . $lang], + + 'NOTICE' => $notice, + 'COLSPAN' => ($mode == 'smilies') ? 5 : 3, + + 'S_SMILIES' => ($mode == 'smilies') ? true : false, + + 'U_ACTION' => $this->u_action, + 'U_IMPORT' => $this->u_action . '&action=import', + 'U_EXPORT' => $this->u_action . '&action=export', + ) + ); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $pagination_start = $request->variable('start', 0); + $spacer = false; + + $item_count = $this->item_count($table); + + $sql = "SELECT * + FROM $table + ORDER BY {$fields}_order ASC"; + $result = $db->sql_query_limit($sql, $config['smilies_per_page'], $pagination_start); + + while ($row = $db->sql_fetchrow($result)) + { + $alt_text = ($mode == 'smilies') ? $row['code'] : (($mode == 'icons' && !empty($row['icons_alt'])) ? $row['icons_alt'] : $row['icons_url']); + + $template->assign_block_vars('items', array( + 'S_SPACER' => (!$spacer && !$row['display_on_posting']) ? true : false, + 'ALT_TEXT' => $alt_text, + 'IMG_SRC' => $phpbb_root_path . $img_path . '/' . $row[$fields . '_url'], + 'WIDTH' => $row[$fields . '_width'], + 'HEIGHT' => $row[$fields . '_height'], + 'CODE' => (isset($row['code'])) ? $row['code'] : '', + 'EMOTION' => (isset($row['emotion'])) ? $row['emotion'] : '', + 'U_EDIT' => $this->u_action . '&action=edit&id=' . $row[$fields . '_id'], + 'U_DELETE' => $this->u_action . '&action=delete&id=' . $row[$fields . '_id'], + 'U_MOVE_UP' => $this->u_action . '&action=move_up&id=' . $row[$fields . '_id'] . '&start=' . $pagination_start . '&hash=' . generate_link_hash('acp_icons'), + 'U_MOVE_DOWN' => $this->u_action . '&action=move_down&id=' . $row[$fields . '_id'] . '&start=' . $pagination_start . '&hash=' . generate_link_hash('acp_icons'), + )); + + if (!$spacer && !$row['display_on_posting']) + { + $spacer = true; + } + } + $db->sql_freeresult($result); + + $pagination->generate_template_pagination($this->u_action, 'pagination', 'start', $item_count, $config['smilies_per_page'], $pagination_start); + } + + /** + * Returns the count of smilies or icons in the database + * + * @param string $table The table of items to count. + * @return int number of items + */ + /* private */ function item_count($table) + { + global $db; + + $sql = "SELECT COUNT(*) AS item_count + FROM $table"; + $result = $db->sql_query($sql); + $item_count = (int) $db->sql_fetchfield('item_count'); + $db->sql_freeresult($result); + + return $item_count; + } +} diff --git a/includes/acp/acp_inactive.php b/includes/acp/acp_inactive.php new file mode 100644 index 0000000..4ee4cd4 --- /dev/null +++ b/includes/acp/acp_inactive.php @@ -0,0 +1,322 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_inactive +{ + var $u_action; + var $p_master; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $config, $db, $user, $auth, $template, $phpbb_container, $phpbb_log, $request; + global $phpbb_root_path, $phpbb_admin_path, $phpEx; + + if (!function_exists('user_active_flip')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $user->add_lang('memberlist'); + + $action = $request->variable('action', ''); + $mark = (isset($_REQUEST['mark'])) ? $request->variable('mark', array(0)) : array(); + $start = $request->variable('start', 0); + $submit = isset($_POST['submit']); + + // Sort keys + $sort_days = $request->variable('st', 0); + $sort_key = $request->variable('sk', 'i'); + $sort_dir = $request->variable('sd', 'd'); + + $form_key = 'acp_inactive'; + add_form_key($form_key); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + // We build the sort key and per page settings here, because they may be needed later + + // Number of entries to display + $per_page = $request->variable('users_per_page', (int) $config['topics_per_page']); + + // Sorting + $limit_days = array(0 => $user->lang['ALL_ENTRIES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('i' => $user->lang['SORT_INACTIVE'], 'j' => $user->lang['SORT_REG_DATE'], 'l' => $user->lang['SORT_LAST_VISIT'], 'd' => $user->lang['SORT_LAST_REMINDER'], 'r' => $user->lang['SORT_REASON'], 'u' => $user->lang['SORT_USERNAME'], 'p' => $user->lang['SORT_POSTS'], 'e' => $user->lang['SORT_REMINDER']); + $sort_by_sql = array('i' => 'user_inactive_time', 'j' => 'user_regdate', 'l' => 'user_lastvisit', 'd' => 'user_reminded_time', 'r' => 'user_inactive_reason', 'u' => 'username_clean', 'p' => 'user_posts', 'e' => 'user_reminded'); + + $s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param); + + if ($submit && count($mark)) + { + if ($action !== 'delete' && !check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + switch ($action) + { + case 'activate': + case 'delete': + + $sql = 'SELECT user_id, username + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $mark); + $result = $db->sql_query($sql); + + $user_affected = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_affected[$row['user_id']] = $row['username']; + } + $db->sql_freeresult($result); + + if ($action == 'activate') + { + // Get those 'being activated'... + $sql = 'SELECT user_id, username' . (($config['require_activation'] == USER_ACTIVATION_ADMIN) ? ', user_email, user_lang' : '') . ' + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $mark) . ' + AND user_type = ' . USER_INACTIVE; + $result = $db->sql_query($sql); + + $inactive_users = array(); + while ($row = $db->sql_fetchrow($result)) + { + $inactive_users[] = $row; + } + $db->sql_freeresult($result); + + user_active_flip('activate', $mark); + + if ($config['require_activation'] == USER_ACTIVATION_ADMIN && !empty($inactive_users)) + { + if (!class_exists('messenger')) + { + include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + } + + $messenger = new messenger(false); + + foreach ($inactive_users as $row) + { + $messenger->template('admin_welcome_activated', $row['user_lang']); + + $messenger->set_addresses($row); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($row['username'])) + ); + + $messenger->send(NOTIFY_EMAIL); + } + + $messenger->save_queue(); + } + + if (!empty($inactive_users)) + { + foreach ($inactive_users as $row) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_ACTIVE', false, array($row['username'])); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_ACTIVE_USER', false, array( + 'reportee_id' => $row['user_id'] + )); + } + + trigger_error(sprintf($user->lang['LOG_INACTIVE_ACTIVATE'], implode($user->lang['COMMA_SEPARATOR'], $user_affected) . ' ' . adm_back_link($this->u_action))); + } + + // For activate we really need to redirect, else a refresh can result in users being deactivated again + $u_action = $this->u_action . "&$u_sort_param&start=$start"; + $u_action .= ($per_page != $config['topics_per_page']) ? "&users_per_page=$per_page" : ''; + + redirect($u_action); + } + else if ($action == 'delete') + { + if (confirm_box(true)) + { + if (!$auth->acl_get('a_userdel')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + user_delete('retain', $mark, true); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_INACTIVE_' . strtoupper($action), false, array(implode(', ', $user_affected))); + + trigger_error(sprintf($user->lang['LOG_INACTIVE_DELETE'], implode($user->lang['COMMA_SEPARATOR'], $user_affected) . ' ' . adm_back_link($this->u_action))); + } + else + { + $s_hidden_fields = array( + 'mode' => $mode, + 'action' => $action, + 'mark' => $mark, + 'submit' => 1, + 'start' => $start, + ); + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields($s_hidden_fields)); + } + } + + break; + + case 'remind': + if (empty($config['email_enable'])) + { + trigger_error($user->lang['EMAIL_DISABLED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT user_id, username, user_email, user_lang, user_jabber, user_notify_type, user_regdate, user_actkey + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $mark) . ' + AND user_inactive_reason'; + + $sql .= ($config['require_activation'] == USER_ACTIVATION_ADMIN) ? ' = ' . INACTIVE_REMIND : ' <> ' . INACTIVE_MANUAL; + + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + // Send the messages + if (!class_exists('messenger')) + { + include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + } + + $messenger = new messenger(); + $usernames = $user_ids = array(); + + do + { + $messenger->template('user_remind_inactive', $row['user_lang']); + + $messenger->set_addresses($row); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($row['username']), + 'REGISTER_DATE' => $user->format_date($row['user_regdate'], false, true), + 'U_ACTIVATE' => generate_board_url() . "/ucp.$phpEx?mode=activate&u=" . $row['user_id'] . '&k=' . $row['user_actkey']) + ); + + $messenger->send($row['user_notify_type']); + + $usernames[] = $row['username']; + $user_ids[] = (int) $row['user_id']; + } + while ($row = $db->sql_fetchrow($result)); + + $messenger->save_queue(); + + // Add the remind state to the database + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_reminded = user_reminded + 1, + user_reminded_time = ' . time() . ' + WHERE ' . $db->sql_in_set('user_id', $user_ids); + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_INACTIVE_REMIND', false, array(implode(', ', $usernames))); + + trigger_error(sprintf($user->lang['LOG_INACTIVE_REMIND'], implode($user->lang['COMMA_SEPARATOR'], $usernames) . ' ' . adm_back_link($this->u_action))); + } + $db->sql_freeresult($result); + + // For remind we really need to redirect, else a refresh can result in more than one reminder + $u_action = $this->u_action . "&$u_sort_param&start=$start"; + $u_action .= ($per_page != $config['topics_per_page']) ? "&users_per_page=$per_page" : ''; + + redirect($u_action); + + break; + } + } + + // Define where and sort sql for use in displaying logs + $sql_where = ($sort_days) ? (time() - ($sort_days * 86400)) : 0; + $sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC'); + + $inactive = array(); + $inactive_count = 0; + + $start = view_inactive_users($inactive, $inactive_count, $per_page, $start, $sql_where, $sql_sort); + + foreach ($inactive as $row) + { + $template->assign_block_vars('inactive', array( + 'INACTIVE_DATE' => $user->format_date($row['user_inactive_time']), + 'REMINDED_DATE' => $user->format_date($row['user_reminded_time']), + 'JOINED' => $user->format_date($row['user_regdate']), + 'LAST_VISIT' => (!$row['user_lastvisit']) ? ' - ' : $user->format_date($row['user_lastvisit']), + + 'REASON' => $row['inactive_reason'], + 'USER_ID' => $row['user_id'], + 'POSTS' => ($row['user_posts']) ? $row['user_posts'] : 0, + 'REMINDED' => $row['user_reminded'], + + 'REMINDED_EXPLAIN' => $user->lang('USER_LAST_REMINDED', (int) $row['user_reminded'], $user->format_date($row['user_reminded_time'])), + + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, append_sid("{$phpbb_admin_path}index.$phpEx", 'i=users&mode=overview&redirect=acp_inactive')), + 'USERNAME' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), + 'USER_COLOR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), + 'USER_EMAIL' => $row['user_email'], + + 'U_USER_ADMIN' => append_sid("{$phpbb_admin_path}index.$phpEx", "i=users&mode=overview&u={$row['user_id']}"), + 'U_SEARCH_USER' => ($auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id={$row['user_id']}&sr=posts") : '', + )); + } + + $option_ary = array('activate' => 'ACTIVATE', 'delete' => 'DELETE'); + if ($config['email_enable']) + { + $option_ary += array('remind' => 'REMIND'); + } + + $base_url = $this->u_action . "&$u_sort_param&users_per_page=$per_page"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $inactive_count, $per_page, $start); + + $template->assign_vars(array( + 'S_INACTIVE_USERS' => true, + 'S_INACTIVE_OPTIONS' => build_select($option_ary), + + 'S_LIMIT_DAYS' => $s_limit_days, + 'S_SORT_KEY' => $s_sort_key, + 'S_SORT_DIR' => $s_sort_dir, + 'USERS_PER_PAGE' => $per_page, + + 'U_ACTION' => $this->u_action . "&$u_sort_param&users_per_page=$per_page&start=$start", + )); + + $this->tpl_name = 'acp_inactive'; + $this->page_title = 'ACP_INACTIVE_USERS'; + } +} diff --git a/includes/acp/acp_jabber.php b/includes/acp/acp_jabber.php new file mode 100644 index 0000000..07f5dad --- /dev/null +++ b/includes/acp/acp_jabber.php @@ -0,0 +1,144 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @todo Check/enter/update transport info +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_jabber +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $template, $phpbb_log, $request; + global $config, $phpbb_root_path, $phpEx; + + $user->add_lang('acp/board'); + + if (!class_exists('jabber')) + { + include($phpbb_root_path . 'includes/functions_jabber.' . $phpEx); + } + + $submit = (isset($_POST['submit'])) ? true : false; + + if ($mode != 'settings') + { + return; + } + + $this->tpl_name = 'acp_jabber'; + $this->page_title = 'ACP_JABBER_SETTINGS'; + + $jab_enable = $request->variable('jab_enable', (bool) $config['jab_enable']); + $jab_host = $request->variable('jab_host', (string) $config['jab_host']); + $jab_port = $request->variable('jab_port', (int) $config['jab_port']); + $jab_username = $request->variable('jab_username', (string) $config['jab_username']); + $jab_password = $request->variable('jab_password', (string) $config['jab_password']); + $jab_package_size = $request->variable('jab_package_size', (int) $config['jab_package_size']); + $jab_use_ssl = $request->variable('jab_use_ssl', (bool) $config['jab_use_ssl']); + $jab_verify_peer = $request->variable('jab_verify_peer', (bool) $config['jab_verify_peer']); + $jab_verify_peer_name = $request->variable('jab_verify_peer_name', (bool) $config['jab_verify_peer_name']); + $jab_allow_self_signed = $request->variable('jab_allow_self_signed', (bool) $config['jab_allow_self_signed']); + + $form_name = 'acp_jabber'; + add_form_key($form_name); + + if ($submit) + { + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); + } + + $message = $user->lang['JAB_SETTINGS_CHANGED']; + $log = 'JAB_SETTINGS_CHANGED'; + + // Is this feature enabled? Then try to establish a connection + if ($jab_enable) + { + $jabber = new jabber($jab_host, $jab_port, $jab_username, $jab_password, $jab_use_ssl, $jab_verify_peer, $jab_verify_peer_name, $jab_allow_self_signed); + + if (!$jabber->connect()) + { + trigger_error($user->lang['ERR_JAB_CONNECT'] . '

' . $jabber->get_log() . adm_back_link($this->u_action), E_USER_WARNING); + } + + // We'll try to authorise using this account + if (!$jabber->login()) + { + trigger_error($user->lang['ERR_JAB_AUTH'] . '

' . $jabber->get_log() . adm_back_link($this->u_action), E_USER_WARNING); + } + + $jabber->disconnect(); + } + else + { + // This feature is disabled. + // We update the user table to be sure all users that have IM as notify type are set to both as notify type + // We set this to both because users still have their jabber address entered and may want to receive jabber notifications again once it is re-enabled. + $sql_ary = array( + 'user_notify_type' => NOTIFY_BOTH, + ); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_notify_type = ' . NOTIFY_IM; + $db->sql_query($sql); + } + + $config->set('jab_enable', $jab_enable); + $config->set('jab_host', $jab_host); + $config->set('jab_port', $jab_port); + $config->set('jab_username', $jab_username); + if ($jab_password !== '********') + { + $config->set('jab_password', $jab_password); + } + $config->set('jab_package_size', $jab_package_size); + $config->set('jab_use_ssl', $jab_use_ssl); + $config->set('jab_verify_peer', $jab_verify_peer); + $config->set('jab_verify_peer_name', $jab_verify_peer_name); + $config->set('jab_allow_self_signed', $jab_allow_self_signed); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_' . $log); + trigger_error($message . adm_back_link($this->u_action)); + } + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'JAB_ENABLE' => $jab_enable, + 'L_JAB_SERVER_EXPLAIN' => sprintf($user->lang['JAB_SERVER_EXPLAIN'], '', ''), + 'JAB_HOST' => $jab_host, + 'JAB_PORT' => ($jab_port) ? $jab_port : '', + 'JAB_USERNAME' => $jab_username, + 'JAB_PASSWORD' => $jab_password !== '' ? '********' : '', + 'JAB_PACKAGE_SIZE' => $jab_package_size, + 'JAB_USE_SSL' => $jab_use_ssl, + 'JAB_VERIFY_PEER' => $jab_verify_peer, + 'JAB_VERIFY_PEER_NAME' => $jab_verify_peer_name, + 'JAB_ALLOW_SELF_SIGNED' => $jab_allow_self_signed, + 'S_CAN_USE_SSL' => jabber::can_use_ssl(), + 'S_GTALK_NOTE' => (!@function_exists('dns_get_record')) ? true : false, + )); + } +} diff --git a/includes/acp/acp_language.php b/includes/acp/acp_language.php new file mode 100644 index 0000000..8881f62 --- /dev/null +++ b/includes/acp/acp_language.php @@ -0,0 +1,461 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_language +{ + var $u_action; + var $main_files; + var $language_header = ''; + var $lang_header = ''; + + var $language_file = ''; + var $language_directory = ''; + + function main($id, $mode) + { + global $config, $db, $user, $template, $phpbb_log, $phpbb_container; + global $phpbb_root_path, $phpEx, $request, $phpbb_dispatcher; + + if (!function_exists('validate_language_iso_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + // Check and set some common vars + $action = (isset($_POST['update_details'])) ? 'update_details' : ''; + $action = (isset($_POST['remove_store'])) ? 'details' : $action; + + $submit = (empty($action) && !isset($_POST['update']) && !isset($_POST['test_connection'])) ? false : true; + $action = (empty($action)) ? $request->variable('action', '') : $action; + + $form_name = 'acp_lang'; + add_form_key('acp_lang'); + + $lang_id = $request->variable('id', 0); + + $selected_lang_file = $request->variable('language_file', '|common.' . $phpEx); + + list($this->language_directory, $this->language_file) = explode('|', $selected_lang_file); + + $this->language_directory = basename($this->language_directory); + $this->language_file = basename($this->language_file); + + $user->add_lang('acp/language'); + $this->tpl_name = 'acp_language'; + $this->page_title = 'ACP_LANGUAGE_PACKS'; + + switch ($action) + { + case 'update_details': + + if (!$submit || !check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); + } + + if (!$lang_id) + { + trigger_error($user->lang['NO_LANG_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT * + FROM ' . LANG_TABLE . " + WHERE lang_id = $lang_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $sql_ary = array( + 'lang_english_name' => $request->variable('lang_english_name', $row['lang_english_name']), + 'lang_local_name' => $request->variable('lang_local_name', $row['lang_local_name'], true), + 'lang_author' => $request->variable('lang_author', $row['lang_author'], true), + ); + + $db->sql_query('UPDATE ' . LANG_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE lang_id = ' . $lang_id); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_LANGUAGE_PACK_UPDATED', false, array($sql_ary['lang_english_name'])); + + trigger_error($user->lang['LANGUAGE_DETAILS_UPDATED'] . adm_back_link($this->u_action)); + break; + + case 'details': + + if (!$lang_id) + { + trigger_error($user->lang['NO_LANG_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $this->page_title = 'LANGUAGE_PACK_DETAILS'; + + $sql = 'SELECT * + FROM ' . LANG_TABLE . ' + WHERE lang_id = ' . $lang_id; + $result = $db->sql_query($sql); + $lang_entries = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$lang_entries) + { + trigger_error($user->lang['LANGUAGE_PACK_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $lang_iso = $lang_entries['lang_iso']; + + $template->assign_vars(array( + 'S_DETAILS' => true, + 'U_ACTION' => $this->u_action . "&action=details&id=$lang_id", + 'U_BACK' => $this->u_action, + + 'LANG_LOCAL_NAME' => $lang_entries['lang_local_name'], + 'LANG_ENGLISH_NAME' => $lang_entries['lang_english_name'], + 'LANG_ISO' => $lang_iso, + 'LANG_AUTHOR' => $lang_entries['lang_author'], + 'L_MISSING_FILES' => $user->lang('THOSE_MISSING_LANG_FILES', $lang_entries['lang_local_name']), + 'L_MISSING_VARS_EXPLAIN' => $user->lang('THOSE_MISSING_LANG_VARIABLES', $lang_entries['lang_local_name']), + )); + + // If current lang is different from the default lang, then highlight missing files and variables + if ($lang_iso != $config['default_lang']) + { + try + { + $iterator = new \RecursiveIteratorIterator( + new \phpbb\recursive_dot_prefix_filter_iterator( + new \RecursiveDirectoryIterator( + $phpbb_root_path . 'language/' . $config['default_lang'] . '/', + \FilesystemIterator::SKIP_DOTS + ) + ), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + } + catch (\Exception $e) + { + return array(); + } + + foreach ($iterator as $file_info) + { + /** @var \RecursiveDirectoryIterator $file_info */ + $relative_path = $iterator->getInnerIterator()->getSubPathname(); + $relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path); + + if (file_exists($phpbb_root_path . 'language/' . $lang_iso . '/' . $relative_path)) + { + if (substr($relative_path, 0 - strlen($phpEx)) === $phpEx) + { + $missing_vars = $this->compare_language_files($config['default_lang'], $lang_iso, $relative_path); + + if (!empty($missing_vars)) + { + $template->assign_block_vars('missing_varfile', array( + 'FILE_NAME' => $relative_path, + )); + + foreach ($missing_vars as $var) + { + $template->assign_block_vars('missing_varfile.variable', array( + 'VAR_NAME' => $var, + )); + } + } + } + } + else + { + $template->assign_block_vars('missing_files', array( + 'FILE_NAME' => $relative_path, + )); + } + } + } + return; + break; + + case 'delete': + + if (!$lang_id) + { + trigger_error($user->lang['NO_LANG_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT * + FROM ' . LANG_TABLE . ' + WHERE lang_id = ' . $lang_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row['lang_iso'] == $config['default_lang']) + { + trigger_error($user->lang['NO_REMOVE_DEFAULT_LANG'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (confirm_box(true)) + { + $db->sql_query('DELETE FROM ' . LANG_TABLE . ' WHERE lang_id = ' . $lang_id); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lang = '" . $db->sql_escape($config['default_lang']) . "' + WHERE user_lang = '" . $db->sql_escape($row['lang_iso']) . "'"; + $db->sql_query($sql); + + // We also need to remove the translated entries for custom profile fields - we want clean tables, don't we? + $sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . ' WHERE lang_id = ' . $lang_id; + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' WHERE lang_id = ' . $lang_id; + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_LANGUAGE_PACK_DELETED', false, array($row['lang_english_name'])); + + $delete_message = sprintf($user->lang['LANGUAGE_PACK_DELETED'], $row['lang_english_name']); + $lang_iso = $row['lang_iso']; + /** + * Run code after language deleted + * + * @event core.acp_language_after_delete + * @var string lang_iso Language ISO code + * @var string delete_message Delete message appear to user + * @since 3.2.2-RC1 + */ + $vars = array('lang_iso', 'delete_message'); + extract($phpbb_dispatcher->trigger_event('core.acp_language_after_delete', compact($vars))); + + trigger_error($delete_message . adm_back_link($this->u_action)); + } + else + { + $s_hidden_fields = array( + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'id' => $lang_id, + ); + confirm_box(false, $user->lang('DELETE_LANGUAGE_CONFIRM', $row['lang_english_name']), build_hidden_fields($s_hidden_fields)); + } + break; + + case 'install': + if (!check_link_hash($request->variable('hash', ''), 'acp_language')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $lang_iso = $request->variable('iso', ''); + $lang_iso = basename($lang_iso); + + if (!$lang_iso || !file_exists("{$phpbb_root_path}language/$lang_iso/iso.txt")) + { + trigger_error($user->lang['LANGUAGE_PACK_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $file = file("{$phpbb_root_path}language/$lang_iso/iso.txt"); + + $lang_pack = array( + 'iso' => $lang_iso, + 'name' => trim(htmlspecialchars($file[0])), + 'local_name'=> trim(htmlspecialchars($file[1], ENT_COMPAT, 'UTF-8')), + 'author' => trim(htmlspecialchars($file[2], ENT_COMPAT, 'UTF-8')) + ); + unset($file); + + $sql = 'SELECT lang_iso + FROM ' . LANG_TABLE . " + WHERE lang_iso = '" . $db->sql_escape($lang_iso) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + trigger_error($user->lang['LANGUAGE_PACK_ALREADY_INSTALLED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (!$lang_pack['name'] || !$lang_pack['local_name']) + { + trigger_error($user->lang['INVALID_LANGUAGE_PACK'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Add language pack + $sql_ary = array( + 'lang_iso' => $lang_pack['iso'], + 'lang_dir' => $lang_pack['iso'], + 'lang_english_name' => $lang_pack['name'], + 'lang_local_name' => $lang_pack['local_name'], + 'lang_author' => $lang_pack['author'] + ); + + $db->sql_query('INSERT INTO ' . LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + $lang_id = $db->sql_nextid(); + + // Now let's copy the default language entries for custom profile fields for this new language - makes admin's life easier. + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE . " + WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; + $result = $db->sql_query($sql); + $default_lang_id = (int) $db->sql_fetchfield('lang_id'); + $db->sql_freeresult($result); + + // We want to notify the admin that custom profile fields need to be updated for the new language. + $notify_cpf_update = false; + + // From the mysql documentation: + // Prior to MySQL 4.0.14, the target table of the INSERT statement cannot appear in the FROM clause of the SELECT part of the query. This limitation is lifted in 4.0.14. + // Due to this we stay on the safe side if we do the insertion "the manual way" + + $sql = 'SELECT field_id, lang_name, lang_explain, lang_default_value + FROM ' . PROFILE_LANG_TABLE . ' + WHERE lang_id = ' . $default_lang_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $row['lang_id'] = $lang_id; + $db->sql_query('INSERT INTO ' . PROFILE_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $row)); + $notify_cpf_update = true; + } + $db->sql_freeresult($result); + + $sql = 'SELECT field_id, option_id, field_type, lang_value + FROM ' . PROFILE_FIELDS_LANG_TABLE . ' + WHERE lang_id = ' . $default_lang_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $row['lang_id'] = $lang_id; + $db->sql_query('INSERT INTO ' . PROFILE_FIELDS_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $row)); + $notify_cpf_update = true; + } + $db->sql_freeresult($result); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_LANGUAGE_PACK_INSTALLED', false, array($lang_pack['name'])); + + $message = sprintf($user->lang['LANGUAGE_PACK_INSTALLED'], $lang_pack['name']); + $message .= ($notify_cpf_update) ? '

' . $user->lang['LANGUAGE_PACK_CPF_UPDATE'] : ''; + trigger_error($message . adm_back_link($this->u_action)); + + break; + } + + $sql = 'SELECT user_lang, COUNT(user_lang) AS lang_count + FROM ' . USERS_TABLE . ' + GROUP BY user_lang'; + $result = $db->sql_query($sql); + + $lang_count = array(); + while ($row = $db->sql_fetchrow($result)) + { + $lang_count[$row['user_lang']] = $row['lang_count']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT * + FROM ' . LANG_TABLE . ' + ORDER BY lang_english_name'; + $result = $db->sql_query($sql); + + $installed = array(); + + while ($row = $db->sql_fetchrow($result)) + { + $installed[] = $row['lang_iso']; + $tagstyle = ($row['lang_iso'] == $config['default_lang']) ? '*' : ''; + + $template->assign_block_vars('lang', array( + 'U_DETAILS' => $this->u_action . "&action=details&id={$row['lang_id']}", + 'U_DOWNLOAD' => $this->u_action . "&action=download&id={$row['lang_id']}", + 'U_DELETE' => $this->u_action . "&action=delete&id={$row['lang_id']}", + + 'ENGLISH_NAME' => $row['lang_english_name'], + 'TAG' => $tagstyle, + 'LOCAL_NAME' => $row['lang_local_name'], + 'ISO' => $row['lang_iso'], + 'USED_BY' => (isset($lang_count[$row['lang_iso']])) ? $lang_count[$row['lang_iso']] : 0, + )); + } + $db->sql_freeresult($result); + + $new_ary = $iso = array(); + + /** @var \phpbb\language\language_file_helper $language_helper */ + $language_helper = $phpbb_container->get('language.helper.language_file'); + $iso = $language_helper->get_available_languages(); + + foreach ($iso as $lang_array) + { + $lang_iso = $lang_array['iso']; + + if (!in_array($lang_iso, $installed)) + { + $new_ary[$lang_iso] = $lang_array; + } + } + + unset($installed); + + if (count($new_ary)) + { + foreach ($new_ary as $iso => $lang_ary) + { + $template->assign_block_vars('notinst', array( + 'ISO' => htmlspecialchars($lang_ary['iso']), + 'LOCAL_NAME' => htmlspecialchars($lang_ary['local_name'], ENT_COMPAT, 'UTF-8'), + 'NAME' => htmlspecialchars($lang_ary['name'], ENT_COMPAT, 'UTF-8'), + 'U_INSTALL' => $this->u_action . '&action=install&iso=' . urlencode($lang_ary['iso']) . '&hash=' . generate_link_hash('acp_language')) + ); + } + } + + unset($new_ary); + } + + /** + * Compare two language files + */ + function compare_language_files($source_lang, $dest_lang, $file) + { + global $phpbb_root_path; + + $source_file = $phpbb_root_path . 'language/' . $source_lang . '/' . $file; + $dest_file = $phpbb_root_path . 'language/' . $dest_lang . '/' . $file; + + if (!file_exists($dest_file)) + { + return array(); + } + + $lang = array(); + include($source_file); + $lang_entry_src = $lang; + + $lang = array(); + include($dest_file); + $lang_entry_dst = $lang; + + unset($lang); + + return array_diff(array_keys($lang_entry_src), array_keys($lang_entry_dst)); + } +} diff --git a/includes/acp/acp_logs.php b/includes/acp/acp_logs.php new file mode 100644 index 0000000..f9bb357 --- /dev/null +++ b/includes/acp/acp_logs.php @@ -0,0 +1,176 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_logs +{ + var $u_action; + + function main($id, $mode) + { + global $user, $auth, $template, $phpbb_container; + global $config; + global $request; + + $user->add_lang('mcp'); + + // Set up general vars + $action = $request->variable('action', ''); + $forum_id = $request->variable('f', 0); + $start = $request->variable('start', 0); + $deletemark = $request->variable('delmarked', false, false, \phpbb\request\request_interface::POST); + $deleteall = $request->variable('delall', false, false, \phpbb\request\request_interface::POST); + $marked = $request->variable('mark', array(0)); + + // Sort keys + $sort_days = $request->variable('st', 0); + $sort_key = $request->variable('sk', 't'); + $sort_dir = $request->variable('sd', 'd'); + + $this->tpl_name = 'acp_logs'; + $this->log_type = constant('LOG_' . strtoupper($mode)); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + // Delete entries if requested and able + if (($deletemark || $deleteall) && $auth->acl_get('a_clearlogs')) + { + if (confirm_box(true)) + { + $conditions = array(); + + if ($deletemark && count($marked)) + { + $conditions['log_id'] = array('IN' => $marked); + } + + if ($deleteall) + { + if ($sort_days) + { + $conditions['log_time'] = array('>=', time() - ($sort_days * 86400)); + } + + $keywords = $request->variable('keywords', '', true); + $conditions['keywords'] = $keywords; + } + + /* @var $phpbb_log \phpbb\log\log_interface */ + $phpbb_log = $phpbb_container->get('log'); + $phpbb_log->delete($mode, $conditions); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'f' => $forum_id, + 'start' => $start, + 'delmarked' => $deletemark, + 'delall' => $deleteall, + 'mark' => $marked, + 'st' => $sort_days, + 'sk' => $sort_key, + 'sd' => $sort_dir, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } + } + + // Sorting + $limit_days = array(0 => $user->lang['ALL_ENTRIES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('u' => $user->lang['SORT_USERNAME'], 't' => $user->lang['SORT_DATE'], 'i' => $user->lang['SORT_IP'], 'o' => $user->lang['SORT_ACTION']); + $sort_by_sql = array('u' => 'u.username_clean', 't' => 'l.log_time', 'i' => 'l.log_ip', 'o' => 'l.log_operation'); + + $s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param); + + // Define where and sort sql for use in displaying logs + $sql_where = ($sort_days) ? (time() - ($sort_days * 86400)) : 0; + $sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC'); + + $keywords = $request->variable('keywords', '', true); + $keywords_param = !empty($keywords) ? '&keywords=' . urlencode(htmlspecialchars_decode($keywords)) : ''; + + $l_title = $user->lang['ACP_' . strtoupper($mode) . '_LOGS']; + $l_title_explain = $user->lang['ACP_' . strtoupper($mode) . '_LOGS_EXPLAIN']; + + $this->page_title = $l_title; + + // Define forum list if we're looking @ mod logs + if ($mode == 'mod') + { + $forum_box = '' . make_forum_select($forum_id); + + $template->assign_vars(array( + 'S_SHOW_FORUMS' => true, + 'S_FORUM_BOX' => $forum_box) + ); + } + + // Grab log data + $log_data = array(); + $log_count = 0; + $start = view_log($mode, $log_data, $log_count, $config['topics_per_page'], $start, $forum_id, 0, 0, $sql_where, $sql_sort, $keywords); + + $base_url = $this->u_action . "&$u_sort_param$keywords_param"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'L_TITLE' => $l_title, + 'L_EXPLAIN' => $l_title_explain, + 'U_ACTION' => $this->u_action . "&$u_sort_param$keywords_param&start=$start", + + 'S_LIMIT_DAYS' => $s_limit_days, + 'S_SORT_KEY' => $s_sort_key, + 'S_SORT_DIR' => $s_sort_dir, + 'S_CLEARLOGS' => $auth->acl_get('a_clearlogs'), + 'S_KEYWORDS' => $keywords, + ) + ); + + foreach ($log_data as $row) + { + $data = array(); + + $checks = array('viewpost', 'viewtopic', 'viewlogs', 'viewforum'); + foreach ($checks as $check) + { + if (isset($row[$check]) && $row[$check]) + { + $data[] = '' . $user->lang['LOGVIEW_' . strtoupper($check)] . ''; + } + } + + $template->assign_block_vars('log', array( + 'USERNAME' => $row['username_full'], + 'REPORTEE_USERNAME' => ($row['reportee_username'] && $row['user_id'] != $row['reportee_id']) ? $row['reportee_username_full'] : '', + + 'IP' => $row['ip'], + 'DATE' => $user->format_date($row['time']), + 'ACTION' => $row['action'], + 'DATA' => (count($data)) ? implode(' | ', $data) : '', + 'ID' => $row['id'], + ) + ); + } + } +} diff --git a/includes/acp/acp_main.php b/includes/acp/acp_main.php new file mode 100644 index 0000000..8f169d1 --- /dev/null +++ b/includes/acp/acp_main.php @@ -0,0 +1,707 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_main +{ + var $u_action; + + function main($id, $mode) + { + global $config, $db, $cache, $user, $auth, $template, $request, $phpbb_log; + global $phpbb_root_path, $phpbb_admin_path, $phpEx, $phpbb_container, $phpbb_dispatcher, $phpbb_filesystem; + + // Show restore permissions notice + if ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) + { + $this->tpl_name = 'acp_main'; + $this->page_title = 'ACP_MAIN'; + + $sql = 'SELECT user_id, username, user_colour + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . $user->data['user_perm_from']; + $result = $db->sql_query($sql); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $perm_from = get_username_string('full', $user_row['user_id'], $user_row['username'], $user_row['user_colour']); + + $template->assign_vars(array( + 'S_RESTORE_PERMISSIONS' => true, + 'U_RESTORE_PERMISSIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm'), + 'PERM_FROM' => $perm_from, + 'L_PERMISSIONS_TRANSFERRED_EXPLAIN' => sprintf($user->lang['PERMISSIONS_TRANSFERRED_EXPLAIN'], $perm_from, append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm')), + )); + + return; + } + + $action = $request->variable('action', ''); + + if ($action) + { + if ($action === 'admlogout') + { + $user->unset_admin(); + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + if (!confirm_box(true)) + { + switch ($action) + { + case 'online': + $confirm = true; + $confirm_lang = 'RESET_ONLINE_CONFIRM'; + break; + case 'stats': + $confirm = true; + $confirm_lang = 'RESYNC_STATS_CONFIRM'; + break; + case 'user': + $confirm = true; + $confirm_lang = 'RESYNC_POSTCOUNTS_CONFIRM'; + break; + case 'date': + $confirm = true; + $confirm_lang = 'RESET_DATE_CONFIRM'; + break; + case 'db_track': + $confirm = true; + $confirm_lang = 'RESYNC_POST_MARKING_CONFIRM'; + break; + case 'purge_cache': + $confirm = true; + $confirm_lang = 'PURGE_CACHE_CONFIRM'; + break; + case 'purge_sessions': + $confirm = true; + $confirm_lang = 'PURGE_SESSIONS_CONFIRM'; + break; + + default: + $confirm = true; + $confirm_lang = 'CONFIRM_OPERATION'; + } + + if ($confirm) + { + confirm_box(false, $user->lang[$confirm_lang], build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + ))); + } + } + else + { + switch ($action) + { + + case 'online': + if (!$auth->acl_get('a_board')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $config->set('record_online_users', 1, false); + $config->set('record_online_date', time(), false); + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RESET_ONLINE'); + + if ($request->is_ajax()) + { + trigger_error('RESET_ONLINE_SUCCESS'); + } + break; + + case 'stats': + if (!$auth->acl_get('a_board')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT COUNT(post_id) AS stat + FROM ' . POSTS_TABLE . ' + WHERE post_visibility = ' . ITEM_APPROVED; + $result = $db->sql_query($sql); + $config->set('num_posts', (int) $db->sql_fetchfield('stat'), false); + $db->sql_freeresult($result); + + $sql = 'SELECT COUNT(topic_id) AS stat + FROM ' . TOPICS_TABLE . ' + WHERE topic_visibility = ' . ITEM_APPROVED; + $result = $db->sql_query($sql); + $config->set('num_topics', (int) $db->sql_fetchfield('stat'), false); + $db->sql_freeresult($result); + + $sql = 'SELECT COUNT(user_id) AS stat + FROM ' . USERS_TABLE . ' + WHERE user_type IN (' . USER_NORMAL . ',' . USER_FOUNDER . ')'; + $result = $db->sql_query($sql); + $config->set('num_users', (int) $db->sql_fetchfield('stat'), false); + $db->sql_freeresult($result); + + $sql = 'SELECT COUNT(attach_id) as stat + FROM ' . ATTACHMENTS_TABLE . ' + WHERE is_orphan = 0'; + $result = $db->sql_query($sql); + $config->set('num_files', (int) $db->sql_fetchfield('stat'), false); + $db->sql_freeresult($result); + + $sql = 'SELECT SUM(filesize) as stat + FROM ' . ATTACHMENTS_TABLE . ' + WHERE is_orphan = 0'; + $result = $db->sql_query($sql); + $config->set('upload_dir_size', (float) $db->sql_fetchfield('stat'), false); + $db->sql_freeresult($result); + + if (!function_exists('update_last_username')) + { + include($phpbb_root_path . "includes/functions_user.$phpEx"); + } + update_last_username(); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RESYNC_STATS'); + + if ($request->is_ajax()) + { + trigger_error('RESYNC_STATS_SUCCESS'); + } + break; + + case 'user': + if (!$auth->acl_get('a_board')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Resync post counts + $start = $max_post_id = 0; + + // Find the maximum post ID, we can only stop the cycle when we've reached it + $sql = 'SELECT MAX(forum_last_post_id) as max_post_id + FROM ' . FORUMS_TABLE; + $result = $db->sql_query($sql); + $max_post_id = (int) $db->sql_fetchfield('max_post_id'); + $db->sql_freeresult($result); + + // No maximum post id? :o + if (!$max_post_id) + { + $sql = 'SELECT MAX(post_id) as max_post_id + FROM ' . POSTS_TABLE; + $result = $db->sql_query($sql); + $max_post_id = (int) $db->sql_fetchfield('max_post_id'); + $db->sql_freeresult($result); + } + + // Still no maximum post id? Then we are finished + if (!$max_post_id) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RESYNC_POSTCOUNTS'); + break; + } + + $step = ($config['num_posts']) ? (max((int) ($config['num_posts'] / 5), 20000)) : 20000; + $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_posts = 0'); + + while ($start < $max_post_id) + { + $sql = 'SELECT COUNT(post_id) AS num_posts, poster_id + FROM ' . POSTS_TABLE . ' + WHERE post_id BETWEEN ' . ($start + 1) . ' AND ' . ($start + $step) . ' + AND post_postcount = 1 AND post_visibility = ' . ITEM_APPROVED . ' + GROUP BY poster_id'; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + do + { + $sql = 'UPDATE ' . USERS_TABLE . " SET user_posts = user_posts + {$row['num_posts']} WHERE user_id = {$row['poster_id']}"; + $db->sql_query($sql); + } + while ($row = $db->sql_fetchrow($result)); + } + $db->sql_freeresult($result); + + $start += $step; + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RESYNC_POSTCOUNTS'); + + if ($request->is_ajax()) + { + trigger_error('RESYNC_POSTCOUNTS_SUCCESS'); + } + break; + + case 'date': + if (!$auth->acl_get('a_board')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $config->set('board_startdate', time() - 1); + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RESET_DATE'); + + if ($request->is_ajax()) + { + trigger_error('RESET_DATE_SUCCESS'); + } + break; + + case 'db_track': + switch ($db->get_sql_layer()) + { + case 'sqlite3': + $db->sql_query('DELETE FROM ' . TOPICS_POSTED_TABLE); + break; + + default: + $db->sql_query('TRUNCATE TABLE ' . TOPICS_POSTED_TABLE); + break; + } + + // This can get really nasty... therefore we only do the last six months + $get_from_time = time() - (6 * 4 * 7 * 24 * 60 * 60); + + // Select forum ids, do not include categories + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type <> ' . FORUM_CAT; + $result = $db->sql_query($sql); + + $forum_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_ids[] = $row['forum_id']; + } + $db->sql_freeresult($result); + + // Any global announcements? ;) + $forum_ids[] = 0; + + // Now go through the forums and get us some topics... + foreach ($forum_ids as $forum_id) + { + $sql = 'SELECT p.poster_id, p.topic_id + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t + WHERE t.forum_id = ' . $forum_id . ' + AND t.topic_moved_id = 0 + AND t.topic_last_post_time > ' . $get_from_time . ' + AND t.topic_id = p.topic_id + AND p.poster_id <> ' . ANONYMOUS . ' + GROUP BY p.poster_id, p.topic_id'; + $result = $db->sql_query($sql); + + $posted = array(); + while ($row = $db->sql_fetchrow($result)) + { + $posted[$row['poster_id']][] = $row['topic_id']; + } + $db->sql_freeresult($result); + + $sql_ary = array(); + foreach ($posted as $user_id => $topic_row) + { + foreach ($topic_row as $topic_id) + { + $sql_ary[] = array( + 'user_id' => (int) $user_id, + 'topic_id' => (int) $topic_id, + 'topic_posted' => 1, + ); + } + } + unset($posted); + + if (count($sql_ary)) + { + $db->sql_multi_insert(TOPICS_POSTED_TABLE, $sql_ary); + } + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RESYNC_POST_MARKING'); + + if ($request->is_ajax()) + { + trigger_error('RESYNC_POST_MARKING_SUCCESS'); + } + break; + + case 'purge_cache': + $config->increment('assets_version', 1); + $cache->purge(); + + // Remove old renderers from the text_formatter service. Since this + // operation is performed after the cache is purged, there is not "current" + // renderer and in effect all renderers will be purged + $phpbb_container->get('text_formatter.cache')->tidy(); + + // Clear permissions + $auth->acl_clear_prefetch(); + phpbb_cache_moderators($db, $cache, $auth); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_PURGE_CACHE'); + + if ($request->is_ajax()) + { + trigger_error('PURGE_CACHE_SUCCESS'); + } + break; + + case 'purge_sessions': + if ((int) $user->data['user_type'] !== USER_FOUNDER) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $tables = array(CONFIRM_TABLE, SESSIONS_TABLE); + + foreach ($tables as $table) + { + switch ($db->get_sql_layer()) + { + case 'sqlite3': + $db->sql_query("DELETE FROM $table"); + break; + + default: + $db->sql_query("TRUNCATE TABLE $table"); + break; + } + } + + // let's restore the admin session + $reinsert_ary = array( + 'session_id' => (string) $user->session_id, + 'session_page' => (string) substr($user->page['page'], 0, 199), + 'session_forum_id' => $user->page['forum'], + 'session_user_id' => (int) $user->data['user_id'], + 'session_start' => (int) $user->data['session_start'], + 'session_last_visit' => (int) $user->data['session_last_visit'], + 'session_time' => (int) $user->time_now, + 'session_browser' => (string) trim(substr($user->browser, 0, 149)), + 'session_forwarded_for' => (string) $user->forwarded_for, + 'session_ip' => (string) $user->ip, + 'session_autologin' => (int) $user->data['session_autologin'], + 'session_admin' => 1, + 'session_viewonline' => (int) $user->data['session_viewonline'], + ); + + $sql = 'INSERT INTO ' . SESSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $reinsert_ary); + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_PURGE_SESSIONS'); + + if ($request->is_ajax()) + { + trigger_error('PURGE_SESSIONS_SUCCESS'); + } + break; + } + } + } + + // Version check + $user->add_lang('install'); + + if ($auth->acl_get('a_server') && version_compare(PHP_VERSION, '5.4.0', '<')) + { + $template->assign_vars(array( + 'S_PHP_VERSION_OLD' => true, + 'L_PHP_VERSION_OLD' => sprintf($user->lang['PHP_VERSION_OLD'], PHP_VERSION, '5.4.0', '', ''), + )); + } + + if ($auth->acl_get('a_board')) + { + $version_helper = $phpbb_container->get('version_helper'); + try + { + $recheck = $request->variable('versioncheck_force', false); + $updates_available = $version_helper->get_update_on_branch($recheck); + $upgrades_available = $version_helper->get_suggested_updates(); + if (!empty($upgrades_available)) + { + $upgrades_available = array_pop($upgrades_available); + } + + $template->assign_vars(array( + 'S_VERSION_UP_TO_DATE' => empty($updates_available), + 'S_VERSION_UPGRADEABLE' => !empty($upgrades_available), + 'UPGRADE_INSTRUCTIONS' => !empty($upgrades_available) ? $user->lang('UPGRADE_INSTRUCTIONS', $upgrades_available['current'], $upgrades_available['announcement']) : false, + )); + } + catch (\RuntimeException $e) + { + $message = call_user_func_array(array($user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + $template->assign_vars(array( + 'S_VERSIONCHECK_FAIL' => true, + 'VERSIONCHECK_FAIL_REASON' => ($e->getMessage() !== 'VERSIONCHECK_FAIL') ? $message : '', + )); + } + } + else + { + // We set this template var to true, to not display an outdated version notice. + $template->assign_var('S_VERSION_UP_TO_DATE', true); + } + + // Incomplete update? + if (phpbb_version_compare($config['version'], PHPBB_VERSION, '<')) + { + $template->assign_var('S_UPDATE_INCOMPLETE', true); + } + + /** + * Notice admin + * + * @event core.acp_main_notice + * @since 3.1.0-RC3 + */ + $phpbb_dispatcher->dispatch('core.acp_main_notice'); + + // Get forum statistics + $total_posts = $config['num_posts']; + $total_topics = $config['num_topics']; + $total_users = $config['num_users']; + $total_files = $config['num_files']; + + $start_date = $user->format_date($config['board_startdate']); + + $boarddays = (time() - $config['board_startdate']) / 86400; + + $posts_per_day = sprintf('%.2f', $total_posts / $boarddays); + $topics_per_day = sprintf('%.2f', $total_topics / $boarddays); + $users_per_day = sprintf('%.2f', $total_users / $boarddays); + $files_per_day = sprintf('%.2f', $total_files / $boarddays); + + $upload_dir_size = get_formatted_filesize($config['upload_dir_size']); + + $avatar_dir_size = 0; + + if ($avatar_dir = @opendir($phpbb_root_path . $config['avatar_path'])) + { + while (($file = readdir($avatar_dir)) !== false) + { + if ($file[0] != '.' && $file != 'CVS' && strpos($file, 'index.') === false) + { + $avatar_dir_size += filesize($phpbb_root_path . $config['avatar_path'] . '/' . $file); + } + } + closedir($avatar_dir); + + $avatar_dir_size = get_formatted_filesize($avatar_dir_size); + } + else + { + // Couldn't open Avatar dir. + $avatar_dir_size = $user->lang['NOT_AVAILABLE']; + } + + if ($posts_per_day > $total_posts) + { + $posts_per_day = $total_posts; + } + + if ($topics_per_day > $total_topics) + { + $topics_per_day = $total_topics; + } + + if ($users_per_day > $total_users) + { + $users_per_day = $total_users; + } + + if ($files_per_day > $total_files) + { + $files_per_day = $total_files; + } + + if ($config['allow_attachments'] || $config['allow_pm_attach']) + { + $sql = 'SELECT COUNT(attach_id) AS total_orphan + FROM ' . ATTACHMENTS_TABLE . ' + WHERE is_orphan = 1 + AND filetime < ' . (time() - 3*60*60); + $result = $db->sql_query($sql); + $total_orphan = (int) $db->sql_fetchfield('total_orphan'); + $db->sql_freeresult($result); + } + else + { + $total_orphan = false; + } + + $dbsize = get_database_size(); + + $template->assign_vars(array( + 'TOTAL_POSTS' => $total_posts, + 'POSTS_PER_DAY' => $posts_per_day, + 'TOTAL_TOPICS' => $total_topics, + 'TOPICS_PER_DAY' => $topics_per_day, + 'TOTAL_USERS' => $total_users, + 'USERS_PER_DAY' => $users_per_day, + 'TOTAL_FILES' => $total_files, + 'FILES_PER_DAY' => $files_per_day, + 'START_DATE' => $start_date, + 'AVATAR_DIR_SIZE' => $avatar_dir_size, + 'DBSIZE' => $dbsize, + 'UPLOAD_DIR_SIZE' => $upload_dir_size, + 'TOTAL_ORPHAN' => $total_orphan, + 'S_TOTAL_ORPHAN' => ($total_orphan === false) ? false : true, + 'GZIP_COMPRESSION' => ($config['gzip_compress'] && @extension_loaded('zlib')) ? $user->lang['ON'] : $user->lang['OFF'], + 'DATABASE_INFO' => $db->sql_server_info(), + 'PHP_VERSION_INFO' => PHP_VERSION, + 'BOARD_VERSION' => $config['version'], + + 'U_ACTION' => $this->u_action, + 'U_ADMIN_LOG' => append_sid("{$phpbb_admin_path}index.$phpEx", 'i=logs&mode=admin'), + 'U_INACTIVE_USERS' => append_sid("{$phpbb_admin_path}index.$phpEx", 'i=inactive&mode=list'), + 'U_VERSIONCHECK' => append_sid("{$phpbb_admin_path}index.$phpEx", 'i=update&mode=version_check'), + 'U_VERSIONCHECK_FORCE' => append_sid("{$phpbb_admin_path}index.$phpEx", 'versioncheck_force=1'), + 'U_ATTACH_ORPHAN' => append_sid("{$phpbb_admin_path}index.$phpEx", 'i=acp_attachments&mode=orphan'), + + 'S_VERSIONCHECK' => ($auth->acl_get('a_board')) ? true : false, + 'S_ACTION_OPTIONS' => ($auth->acl_get('a_board')) ? true : false, + 'S_FOUNDER' => ($user->data['user_type'] == USER_FOUNDER) ? true : false, + ) + ); + + $log_data = array(); + $log_count = false; + + if ($auth->acl_get('a_viewlogs')) + { + view_log('admin', $log_data, $log_count, 5); + + foreach ($log_data as $row) + { + $template->assign_block_vars('log', array( + 'USERNAME' => $row['username_full'], + 'IP' => $row['ip'], + 'DATE' => $user->format_date($row['time']), + 'ACTION' => $row['action']) + ); + } + } + + if ($auth->acl_get('a_user')) + { + $user->add_lang('memberlist'); + + $inactive = array(); + $inactive_count = 0; + + view_inactive_users($inactive, $inactive_count, 10); + + foreach ($inactive as $row) + { + $template->assign_block_vars('inactive', array( + 'INACTIVE_DATE' => $user->format_date($row['user_inactive_time']), + 'REMINDED_DATE' => $user->format_date($row['user_reminded_time']), + 'JOINED' => $user->format_date($row['user_regdate']), + 'LAST_VISIT' => (!$row['user_lastvisit']) ? ' - ' : $user->format_date($row['user_lastvisit']), + + 'REASON' => $row['inactive_reason'], + 'USER_ID' => $row['user_id'], + 'POSTS' => ($row['user_posts']) ? $row['user_posts'] : 0, + 'REMINDED' => $row['user_reminded'], + + 'REMINDED_EXPLAIN' => $user->lang('USER_LAST_REMINDED', (int) $row['user_reminded'], $user->format_date($row['user_reminded_time'])), + + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, append_sid("{$phpbb_admin_path}index.$phpEx", 'i=users&mode=overview')), + 'USERNAME' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), + 'USER_COLOR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), + + 'U_USER_ADMIN' => append_sid("{$phpbb_admin_path}index.$phpEx", "i=users&mode=overview&u={$row['user_id']}"), + 'U_SEARCH_USER' => ($auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id={$row['user_id']}&sr=posts") : '', + )); + } + + $option_ary = array('activate' => 'ACTIVATE', 'delete' => 'DELETE'); + if ($config['email_enable']) + { + $option_ary += array('remind' => 'REMIND'); + } + + $template->assign_vars(array( + 'S_INACTIVE_USERS' => true, + 'S_INACTIVE_OPTIONS' => build_select($option_ary)) + ); + } + + // Warn if install is still present + if (file_exists($phpbb_root_path . 'install') && !is_file($phpbb_root_path . 'install')) + { + $template->assign_var('S_REMOVE_INSTALL', true); + } + + // Warn if no search index is created + if ($config['num_posts'] && class_exists($config['search_type'])) + { + $error = false; + $search_type = $config['search_type']; + $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher); + + if (!$search->index_created()) + { + $template->assign_vars(array( + 'S_SEARCH_INDEX_MISSING' => true, + 'L_NO_SEARCH_INDEX' => $user->lang('NO_SEARCH_INDEX', $search->get_name(), '', ''), + )); + } + } + + if (!defined('PHPBB_DISABLE_CONFIG_CHECK') && file_exists($phpbb_root_path . 'config.' . $phpEx) && $phpbb_filesystem->is_writable($phpbb_root_path . 'config.' . $phpEx)) + { + // World-Writable? (000x) + $template->assign_var('S_WRITABLE_CONFIG', (bool) (@fileperms($phpbb_root_path . 'config.' . $phpEx) & 0x0002)); + } + + if (extension_loaded('mbstring')) + { + $template->assign_vars(array( + 'S_MBSTRING_LOADED' => true, + 'S_MBSTRING_FUNC_OVERLOAD_FAIL' => (intval(@ini_get('mbstring.func_overload')) & (MB_OVERLOAD_MAIL | MB_OVERLOAD_STRING)), + 'S_MBSTRING_ENCODING_TRANSLATION_FAIL' => (@ini_get('mbstring.encoding_translation') != 0), + 'S_MBSTRING_HTTP_INPUT_FAIL' => !in_array(@ini_get('mbstring.http_input'), array('pass', '')), + 'S_MBSTRING_HTTP_OUTPUT_FAIL' => !in_array(@ini_get('mbstring.http_output'), array('pass', '')), + )); + } + + // Fill dbms version if not yet filled + if (empty($config['dbms_version'])) + { + $config->set('dbms_version', $db->sql_server_info(true)); + } + + $this->tpl_name = 'acp_main'; + $this->page_title = 'ACP_MAIN'; + } +} diff --git a/includes/acp/acp_modules.php b/includes/acp/acp_modules.php new file mode 100644 index 0000000..fb0c090 --- /dev/null +++ b/includes/acp/acp_modules.php @@ -0,0 +1,666 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +use phpbb\module\exception\module_exception; + +/** +* - Able to check for new module versions (modes changed/adjusted/added/removed) +* Icons for: +* - module enabled and displayed (common) +* - module enabled and not displayed +* - module deactivated +* - category (enabled) +* - category disabled +*/ + +class acp_modules +{ + var $module_class = ''; + var $parent_id; + var $u_action; + + function main($id, $mode) + { + global $db, $user, $template, $module, $request, $phpbb_log, $phpbb_container; + + /** @var \phpbb\module\module_manager $module_manager */ + $module_manager = $phpbb_container->get('module.manager'); + + // Set a global define for modules we might include (the author is able to prevent execution of code by checking this constant) + define('MODULE_INCLUDE', true); + + $user->add_lang('acp/modules'); + $this->tpl_name = 'acp_modules'; + + $form_key = 'acp_modules'; + add_form_key($form_key); + + // module class + $this->module_class = $mode; + + if ($this->module_class == 'ucp') + { + $user->add_lang('ucp'); + } + else if ($this->module_class == 'mcp') + { + $user->add_lang('mcp'); + } + + if ($module->p_class != $this->module_class) + { + $module->add_mod_info($this->module_class); + } + + $this->page_title = strtoupper($this->module_class); + + $this->parent_id = $request->variable('parent_id', 0); + $module_id = $request->variable('m', 0); + $action = $request->variable('action', ''); + $errors = array(); + + switch ($action) + { + case 'delete': + if (!$module_id) + { + trigger_error($user->lang['NO_MODULE_ID'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + if (confirm_box(true)) + { + // Make sure we are not directly within a module + if ($module_id == $this->parent_id) + { + $sql = 'SELECT parent_id + FROM ' . MODULES_TABLE . ' + WHERE module_id = ' . $module_id; + $result = $db->sql_query($sql); + $this->parent_id = (int) $db->sql_fetchfield('parent_id'); + $db->sql_freeresult($result); + } + + try + { + $row = $module_manager->get_module_row($module_id, $this->module_class); + $module_manager->delete_module($module_id, $this->module_class); + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_MODULE_REMOVED', false, array($user->lang($row['module_langname']))); + } + catch (module_exception $e) + { + $msg = $user->lang($e->getMessage()); + trigger_error($msg . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $module_manager->remove_cache_file($this->module_class); + trigger_error($user->lang['MODULE_DELETED'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id)); + } + else + { + confirm_box(false, 'DELETE_MODULE', build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'parent_id' => $this->parent_id, + 'module_id' => $module_id, + 'action' => $action, + ))); + } + + break; + + case 'enable': + case 'disable': + if (!$module_id) + { + trigger_error($user->lang['NO_MODULE_ID'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + if (!check_link_hash($request->variable('hash', ''), 'acp_modules')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_class = '" . $db->sql_escape($this->module_class) . "' + AND module_id = $module_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['NO_MODULE'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_enabled = ' . (($action == 'enable') ? 1 : 0) . " + WHERE module_class = '" . $db->sql_escape($this->module_class) . "' + AND module_id = $module_id"; + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_MODULE_' . strtoupper($action), false, array($user->lang($row['module_langname']))); + $module_manager->remove_cache_file($this->module_class); + + break; + + case 'move_up': + case 'move_down': + if (!$module_id) + { + trigger_error($user->lang['NO_MODULE_ID'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + if (!check_link_hash($request->variable('hash', ''), 'acp_modules')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_class = '" . $db->sql_escape($this->module_class) . "' + AND module_id = $module_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error($user->lang['NO_MODULE'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + try + { + $move_module_name = $module_manager->move_module_by($row, $this->module_class, $action, 1); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_MODULE_' . strtoupper($action), false, array($user->lang($row['module_langname']), $move_module_name)); + $module_manager->remove_cache_file($this->module_class); + } + catch (module_exception $e) + { + // Do nothing + } + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'success' => ($move_module_name !== false), + )); + } + + break; + + case 'quickadd': + $quick_install = $request->variable('quick_install', ''); + + if (confirm_box(true)) + { + if (!$quick_install || strpos($quick_install, '::') === false) + { + break; + } + + list($module_basename, $module_mode) = explode('::', $quick_install); + + // Check if module name and mode exist... + $fileinfo = $module_manager->get_module_infos($this->module_class, $module_basename); + $fileinfo = $fileinfo[$module_basename]; + + if (isset($fileinfo['modes'][$module_mode])) + { + $module_data = array( + 'module_basename' => $module_basename, + 'module_enabled' => 0, + 'module_display' => (isset($fileinfo['modes'][$module_mode]['display'])) ? $fileinfo['modes'][$module_mode]['display'] : 1, + 'parent_id' => $this->parent_id, + 'module_class' => $this->module_class, + 'module_langname' => $fileinfo['modes'][$module_mode]['title'], + 'module_mode' => $module_mode, + 'module_auth' => $fileinfo['modes'][$module_mode]['auth'], + ); + + try + { + $module_manager->update_module_data($module_data); + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_MODULE_ADD', false, array($user->lang($module_data['module_langname']))); + } + catch (\phpbb\module\exception\module_exception $e) + { + $msg = $user->lang($e->getMessage()); + trigger_error($msg . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + if (!count($errors)) + { + $module_manager->remove_cache_file($this->module_class); + + trigger_error($user->lang['MODULE_ADDED'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id)); + } + } + } + else + { + confirm_box(false, 'ADD_MODULE', build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'parent_id' => $this->parent_id, + 'action' => 'quickadd', + 'quick_install' => $quick_install, + ))); + } + + break; + + case 'edit': + + if (!$module_id) + { + trigger_error($user->lang['NO_MODULE_ID'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + try + { + $module_row = $module_manager->get_module_row($module_id, $this->module_class); + } + catch (\phpbb\module\exception\module_not_found_exception $e) + { + $msg = $user->lang($e->getMessage()); + trigger_error($msg . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + // no break + + case 'add': + + if ($action == 'add') + { + $module_row = array( + 'module_basename' => '', + 'module_enabled' => 0, + 'module_display' => 1, + 'parent_id' => 0, + 'module_langname' => $request->variable('module_langname', '', true), + 'module_mode' => '', + 'module_auth' => '', + ); + } + + $module_data = array(); + + $module_data['module_basename'] = $request->variable('module_basename', (string) $module_row['module_basename']); + $module_data['module_enabled'] = $request->variable('module_enabled', (int) $module_row['module_enabled']); + $module_data['module_display'] = $request->variable('module_display', (int) $module_row['module_display']); + $module_data['parent_id'] = $request->variable('module_parent_id', (int) $module_row['parent_id']); + $module_data['module_class'] = $this->module_class; + $module_data['module_langname'] = $request->variable('module_langname', (string) $module_row['module_langname'], true); + $module_data['module_mode'] = $request->variable('module_mode', (string) $module_row['module_mode']); + + $submit = (isset($_POST['submit'])) ? true : false; + + if ($submit) + { + if (!check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + if (!$module_data['module_langname']) + { + trigger_error($user->lang['NO_MODULE_LANGNAME'] . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $module_type = $request->variable('module_type', 'category'); + + if ($module_type == 'category') + { + $module_data['module_basename'] = $module_data['module_mode'] = $module_data['module_auth'] = ''; + $module_data['module_display'] = 1; + } + + if ($action == 'edit') + { + $module_data['module_id'] = $module_id; + } + + // Adjust auth row + if ($module_data['module_basename'] && $module_data['module_mode']) + { + $fileinfo = $module_manager->get_module_infos($this->module_class, $module_data['module_basename']); + $module_data['module_auth'] = $fileinfo[$module_data['module_basename']]['modes'][$module_data['module_mode']]['auth']; + } + + try + { + $module_manager->update_module_data($module_data); + $phpbb_log->add('admin', + $user->data['user_id'], + $user->ip, + ($action === 'edit') ? 'LOG_MODULE_EDIT' : 'LOG_MODULE_ADD', + false, + array($user->lang($module_data['module_langname'])) + ); } + catch (\phpbb\module\exception\module_exception $e) + { + $msg = $user->lang($e->getMessage()); + trigger_error($msg . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + if (!count($errors)) + { + $module_manager->remove_cache_file($this->module_class); + + trigger_error((($action == 'add') ? $user->lang['MODULE_ADDED'] : $user->lang['MODULE_EDITED']) . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id)); + } + } + + // Category/not category? + $is_cat = (!$module_data['module_basename']) ? true : false; + + // Get module information + $module_infos = $module_manager->get_module_infos($this->module_class); + + // Build name options + $s_name_options = $s_mode_options = ''; + foreach ($module_infos as $option => $values) + { + if (!$module_data['module_basename']) + { + $module_data['module_basename'] = $option; + } + + // Name options + $s_name_options .= ''; + + $template->assign_block_vars('m_names', array('NAME' => $option, 'A_NAME' => addslashes($option))); + + // Build module modes + foreach ($values['modes'] as $m_mode => $m_values) + { + if ($option == $module_data['module_basename']) + { + $s_mode_options .= ''; + } + + $template->assign_block_vars('m_names.modes', array( + 'OPTION' => $m_mode, + 'VALUE' => $user->lang($m_values['title']), + 'A_OPTION' => addslashes($m_mode), + 'A_VALUE' => addslashes($user->lang($m_values['title']))) + ); + } + } + + $s_cat_option = ''; + + $template->assign_vars(array_merge(array( + 'S_EDIT_MODULE' => true, + 'S_IS_CAT' => $is_cat, + 'S_CAT_OPTIONS' => $s_cat_option . $this->make_module_select($module_data['parent_id'], ($action == 'edit') ? $module_row['module_id'] : false, false, false, false, true), + 'S_MODULE_NAMES' => $s_name_options, + 'S_MODULE_MODES' => $s_mode_options, + 'U_BACK' => $this->u_action . '&parent_id=' . $this->parent_id, + 'U_EDIT_ACTION' => $this->u_action . '&parent_id=' . $this->parent_id, + + 'L_TITLE' => $user->lang[strtoupper($action) . '_MODULE'], + + 'MODULENAME' => $user->lang($module_data['module_langname']), + 'ACTION' => $action, + 'MODULE_ID' => $module_id, + + ), + array_change_key_case($module_data, CASE_UPPER)) + ); + + if (count($errors)) + { + $template->assign_vars(array( + 'S_ERROR' => true, + 'ERROR_MSG' => implode('
', $errors)) + ); + } + + return; + + break; + } + + // Default management page + if (count($errors)) + { + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang('ERROR'), + 'MESSAGE_TEXT' => implode('
', $errors), + 'SUCCESS' => false, + )); + } + + $template->assign_vars(array( + 'S_ERROR' => true, + 'ERROR_MSG' => implode('
', $errors)) + ); + } + + if (!$this->parent_id) + { + $navigation = strtoupper($this->module_class); + } + else + { + $navigation = '' . strtoupper($this->module_class) . ''; + + $modules_nav = $module_manager->get_module_branch($this->parent_id, $this->module_class, 'parents'); + + foreach ($modules_nav as $row) + { + $langname = $user->lang($row['module_langname']); + + if ($row['module_id'] == $this->parent_id) + { + $navigation .= ' -> ' . $langname; + } + else + { + $navigation .= ' -> ' . $langname . ''; + } + } + } + + // Jumpbox + $module_box = $this->make_module_select($this->parent_id, false, false, false, false); + + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE parent_id = {$this->parent_id} + AND module_class = '" . $db->sql_escape($this->module_class) . "' + ORDER BY left_id"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + do + { + $langname = $user->lang($row['module_langname']); + + if (!$row['module_enabled']) + { + $module_image = '' . $user->lang['DEACTIVATED_MODULE'] .''; + } + else + { + $module_image = (!$row['module_basename'] || $row['left_id'] + 1 != $row['right_id']) ? '' . $user->lang['CATEGORY'] . '' : '' . $user->lang['MODULE'] . ''; + } + + $url = $this->u_action . '&parent_id=' . $this->parent_id . '&m=' . $row['module_id']; + + $template->assign_block_vars('modules', array( + 'MODULE_IMAGE' => $module_image, + 'MODULE_TITLE' => $langname, + 'MODULE_ENABLED' => ($row['module_enabled']) ? true : false, + 'MODULE_DISPLAYED' => ($row['module_display']) ? true : false, + + 'S_ACP_CAT_SYSTEM' => ($this->module_class == 'acp' && $row['module_langname'] == 'ACP_CAT_SYSTEM') ? true : false, + 'S_ACP_MODULE_MANAGEMENT' => ($this->module_class == 'acp' && ($row['module_basename'] == 'modules' || $row['module_langname'] == 'ACP_MODULE_MANAGEMENT')) ? true : false, + + 'U_MODULE' => $this->u_action . '&parent_id=' . $row['module_id'], + 'U_MOVE_UP' => $url . '&action=move_up&hash=' . generate_link_hash('acp_modules'), + 'U_MOVE_DOWN' => $url . '&action=move_down&hash=' . generate_link_hash('acp_modules'), + 'U_EDIT' => $url . '&action=edit', + 'U_DELETE' => $url . '&action=delete', + 'U_ENABLE' => $url . '&action=enable&hash=' . generate_link_hash('acp_modules'), + 'U_DISABLE' => $url . '&action=disable&hash=' . generate_link_hash('acp_modules')) + ); + } + while ($row = $db->sql_fetchrow($result)); + } + else if ($this->parent_id) + { + try + { + $row = $module_manager->get_module_row($this->parent_id, $this->module_class); + } + catch (\phpbb\module\exception\module_not_found_exception $e) + { + $msg = $user->lang($e->getMessage()); + trigger_error($msg . adm_back_link($this->u_action . '&parent_id=' . $this->parent_id), E_USER_WARNING); + } + + $url = $this->u_action . '&parent_id=' . $this->parent_id . '&m=' . $row['module_id']; + + $template->assign_vars(array( + 'S_NO_MODULES' => true, + 'MODULE_TITLE' => $langname, + 'MODULE_ENABLED' => ($row['module_enabled']) ? true : false, + 'MODULE_DISPLAYED' => ($row['module_display']) ? true : false, + + 'U_EDIT' => $url . '&action=edit', + 'U_DELETE' => $url . '&action=delete', + 'U_ENABLE' => $url . '&action=enable&hash=' . generate_link_hash('acp_modules'), + 'U_DISABLE' => $url . '&action=disable&hash=' . generate_link_hash('acp_modules')) + ); + } + $db->sql_freeresult($result); + + // Quick adding module + $module_infos = $module_manager->get_module_infos($this->module_class); + + // Build quick options + $s_install_options = ''; + foreach ($module_infos as $option => $values) + { + // Name options + $s_install_options .= ''; + + // Build module modes + foreach ($values['modes'] as $m_mode => $m_values) + { + $s_install_options .= ''; + } + + $s_install_options .= ''; + } + + $template->assign_vars(array( + 'U_SEL_ACTION' => $this->u_action, + 'U_ACTION' => $this->u_action . '&parent_id=' . $this->parent_id, + 'NAVIGATION' => $navigation, + 'MODULE_BOX' => $module_box, + 'PARENT_ID' => $this->parent_id, + 'S_INSTALL_OPTIONS' => $s_install_options, + ) + ); + } + + /** + * Simple version of jumpbox, just lists modules + */ + function make_module_select($select_id = false, $ignore_id = false, $ignore_acl = false, $ignore_nonpost = false, $ignore_emptycat = true, $ignore_noncat = false) + { + global $db, $user; + + $sql = 'SELECT module_id, module_enabled, module_basename, parent_id, module_langname, left_id, right_id, module_auth + FROM ' . MODULES_TABLE . " + WHERE module_class = '" . $db->sql_escape($this->module_class) . "' + ORDER BY left_id ASC"; + $result = $db->sql_query($sql); + + $right = $iteration = 0; + $padding_store = array('0' => ''); + $module_list = $padding = ''; + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['left_id'] < $right) + { + $padding .= '   '; + $padding_store[$row['parent_id']] = $padding; + } + else if ($row['left_id'] > $right + 1) + { + $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : ''; + } + + $right = $row['right_id']; + + if (!$ignore_acl && $row['module_auth']) + { + // We use zero as the forum id to check - global setting. + if (!p_master::module_auth($row['module_auth'], 0)) + { + continue; + } + } + + // ignore this module? + if ((is_array($ignore_id) && in_array($row['module_id'], $ignore_id)) || $row['module_id'] == $ignore_id) + { + continue; + } + + // empty category + if (!$row['module_basename'] && ($row['left_id'] + 1 == $row['right_id']) && $ignore_emptycat) + { + continue; + } + + // ignore non-category? + if ($row['module_basename'] && $ignore_noncat) + { + continue; + } + + $selected = (is_array($select_id)) ? ((in_array($row['module_id'], $select_id)) ? ' selected="selected"' : '') : (($row['module_id'] == $select_id) ? ' selected="selected"' : ''); + + $langname = $user->lang($row['module_langname']); + $module_list .= ''; + + $iteration++; + } + $db->sql_freeresult($result); + + unset($padding_store); + + return $module_list; + } +} diff --git a/includes/acp/acp_permission_roles.php b/includes/acp/acp_permission_roles.php new file mode 100644 index 0000000..80cad99 --- /dev/null +++ b/includes/acp/acp_permission_roles.php @@ -0,0 +1,596 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_permission_roles +{ + var $u_action; + protected $auth_admin; + + function main($id, $mode) + { + global $db, $user, $template, $phpbb_container; + global $phpbb_root_path, $phpEx; + global $request, $phpbb_log; + + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + if (!class_exists('auth_admin')) + { + include($phpbb_root_path . 'includes/acp/auth.' . $phpEx); + } + + $this->auth_admin = new auth_admin(); + + $user->add_lang('acp/permissions'); + add_permission_language(); + + $this->tpl_name = 'acp_permission_roles'; + + $submit = (isset($_POST['submit'])) ? true : false; + $role_id = $request->variable('role_id', 0); + $action = $request->variable('action', ''); + $action = (isset($_POST['add'])) ? 'add' : $action; + + $form_name = 'acp_permissions'; + add_form_key($form_name); + + if (!$role_id && in_array($action, array('remove', 'edit', 'move_up', 'move_down'))) + { + trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + switch ($mode) + { + case 'admin_roles': + $permission_type = 'a_'; + $this->page_title = 'ACP_ADMIN_ROLES'; + break; + + case 'user_roles': + $permission_type = 'u_'; + $this->page_title = 'ACP_USER_ROLES'; + break; + + case 'mod_roles': + $permission_type = 'm_'; + $this->page_title = 'ACP_MOD_ROLES'; + break; + + case 'forum_roles': + $permission_type = 'f_'; + $this->page_title = 'ACP_FORUM_ROLES'; + break; + + default: + trigger_error('NO_MODE', E_USER_ERROR); + break; + } + + $template->assign_vars(array( + 'L_TITLE' => $user->lang[$this->page_title], + 'L_EXPLAIN' => $user->lang[$this->page_title . '_EXPLAIN']) + ); + + // Take action... admin submitted something + if ($submit || $action == 'remove') + { + switch ($action) + { + case 'remove': + + $sql = 'SELECT * + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $result = $db->sql_query($sql); + $role_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$role_row) + { + trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (confirm_box(true)) + { + $this->remove_role($role_id, $permission_type); + + $role_name = (!empty($user->lang[$role_row['role_name']])) ? $user->lang[$role_row['role_name']] : $role_row['role_name']; + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_' . strtoupper($permission_type) . 'ROLE_REMOVED', false, array($role_name)); + trigger_error($user->lang['ROLE_DELETED'] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, 'DELETE_ROLE', build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'role_id' => $role_id, + 'action' => $action, + ))); + } + + break; + + case 'edit': + + // Get role we edit + $sql = 'SELECT * + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $result = $db->sql_query($sql); + $role_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$role_row) + { + trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // no break; + + case 'add': + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); + } + + $role_name = $request->variable('role_name', '', true); + $role_description = $request->variable('role_description', '', true); + $auth_settings = $request->variable('setting', array('' => 0)); + + if (!$role_name) + { + trigger_error($user->lang['NO_ROLE_NAME_SPECIFIED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (utf8_strlen($role_description) > 4000) + { + trigger_error($user->lang['ROLE_DESCRIPTION_LONG'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // if we add/edit a role we check the name to be unique among the settings... + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $db->sql_escape($permission_type) . "' + AND role_name = '" . $db->sql_escape($role_name) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Make sure we only print out the error if we add the role or change it's name + if ($row && ($mode == 'add' || ($mode == 'edit' && $role_row['role_name'] != $role_name))) + { + trigger_error(sprintf($user->lang['ROLE_NAME_ALREADY_EXIST'], $role_name) . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql_ary = array( + 'role_name' => (string) $role_name, + 'role_description' => (string) $role_description, + 'role_type' => (string) $permission_type, + ); + + if ($action == 'edit') + { + $sql = 'UPDATE ' . ACL_ROLES_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE role_id = ' . $role_id; + $db->sql_query($sql); + } + else + { + // Get maximum role order for inserting a new role... + $sql = 'SELECT MAX(role_order) as max_order + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $db->sql_escape($permission_type) . "'"; + $result = $db->sql_query($sql); + $max_order = (int) $db->sql_fetchfield('max_order'); + $db->sql_freeresult($result); + + $sql_ary['role_order'] = $max_order + 1; + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + + $role_id = $db->sql_nextid(); + } + + // Now add the auth settings + $this->auth_admin->acl_set_role($role_id, $auth_settings); + + $role_name = (!empty($user->lang[$role_name])) ? $user->lang[$role_name] : $role_name; + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_' . strtoupper($permission_type) . 'ROLE_' . strtoupper($action), false, array($role_name)); + + trigger_error($user->lang['ROLE_' . strtoupper($action) . '_SUCCESS'] . adm_back_link($this->u_action)); + + break; + } + } + + // Display screens + switch ($action) + { + case 'add': + + $options_from = $request->variable('options_from', 0); + + $role_row = array( + 'role_name' => $request->variable('role_name', '', true), + 'role_description' => $request->variable('role_description', '', true), + 'role_type' => $permission_type, + ); + + if ($options_from) + { + $sql = 'SELECT p.auth_option_id, p.auth_setting, o.auth_option + FROM ' . ACL_ROLES_DATA_TABLE . ' p, ' . ACL_OPTIONS_TABLE . ' o + WHERE o.auth_option_id = p.auth_option_id + AND p.role_id = ' . $options_from . ' + ORDER BY p.auth_option_id'; + $result = $db->sql_query($sql); + + $auth_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + $auth_options[$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + } + else + { + $sql = 'SELECT auth_option_id, auth_option + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option " . $db->sql_like_expression($permission_type . $db->get_any_char()) . " + AND auth_option <> '{$permission_type}' + ORDER BY auth_option_id"; + $result = $db->sql_query($sql); + + $auth_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + $auth_options[$row['auth_option']] = ACL_NO; + } + $db->sql_freeresult($result); + } + + // no break; + + case 'edit': + + if ($action == 'edit') + { + $sql = 'SELECT * + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $result = $db->sql_query($sql); + $role_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $sql = 'SELECT p.auth_option_id, p.auth_setting, o.auth_option + FROM ' . ACL_ROLES_DATA_TABLE . ' p, ' . ACL_OPTIONS_TABLE . ' o + WHERE o.auth_option_id = p.auth_option_id + AND p.role_id = ' . $role_id . ' + ORDER BY p.auth_option_id'; + $result = $db->sql_query($sql); + + $auth_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + $auth_options[$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + } + + if (!$role_row) + { + trigger_error($user->lang['NO_ROLE_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + /* @var $phpbb_permissions \phpbb\permissions */ + $phpbb_permissions = $phpbb_container->get('acl.permissions'); + + $template->assign_vars(array( + 'S_EDIT' => true, + + 'U_ACTION' => $this->u_action . "&action={$action}&role_id={$role_id}", + 'U_BACK' => $this->u_action, + + 'ROLE_NAME' => $role_row['role_name'], + 'ROLE_DESCRIPTION' => $role_row['role_description'], + 'L_ACL_TYPE' => $phpbb_permissions->get_type_lang($permission_type), + )); + + // We need to fill the auth options array with ACL_NO options ;) + $sql = 'SELECT auth_option_id, auth_option + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option " . $db->sql_like_expression($permission_type . $db->get_any_char()) . " + AND auth_option <> '{$permission_type}' + ORDER BY auth_option_id"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (!isset($auth_options[$row['auth_option']])) + { + $auth_options[$row['auth_option']] = ACL_NO; + } + } + $db->sql_freeresult($result); + + // Unset global permission option + unset($auth_options[$permission_type]); + + // Display auth options + $this->display_auth_options($auth_options); + + // Get users/groups/forums using this preset... + if ($action == 'edit') + { + $hold_ary = $this->auth_admin->get_role_mask($role_id); + + if (count($hold_ary)) + { + $role_name = (!empty($user->lang[$role_row['role_name']])) ? $user->lang[$role_row['role_name']] : $role_row['role_name']; + + $template->assign_vars(array( + 'S_DISPLAY_ROLE_MASK' => true, + 'L_ROLE_ASSIGNED_TO' => sprintf($user->lang['ROLE_ASSIGNED_TO'], $role_name)) + ); + + $this->auth_admin->display_role_mask($hold_ary); + } + } + + return; + break; + + case 'move_up': + case 'move_down': + + if (!check_link_hash($request->variable('hash', ''), 'acp_permission_roles')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT role_order + FROM ' . ACL_ROLES_TABLE . " + WHERE role_id = $role_id"; + $result = $db->sql_query($sql); + $order = $db->sql_fetchfield('role_order'); + $db->sql_freeresult($result); + + if ($order === false || ($order == 0 && $action == 'move_up')) + { + break; + } + $order = (int) $order; + $order_total = $order * 2 + (($action == 'move_up') ? -1 : 1); + + $sql = 'UPDATE ' . ACL_ROLES_TABLE . ' + SET role_order = ' . $order_total . " - role_order + WHERE role_type = '" . $db->sql_escape($permission_type) . "' + AND role_order IN ($order, " . (($action == 'move_up') ? $order - 1 : $order + 1) . ')'; + $db->sql_query($sql); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'success' => (bool) $db->sql_affectedrows(), + )); + } + + break; + } + + // By default, check that role_order is valid and fix it if necessary + $sql = 'SELECT role_id, role_order + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $db->sql_escape($permission_type) . "' + ORDER BY role_order ASC"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $order = 0; + do + { + $order++; + if ($row['role_order'] != $order) + { + $db->sql_query('UPDATE ' . ACL_ROLES_TABLE . " SET role_order = $order WHERE role_id = {$row['role_id']}"); + } + } + while ($row = $db->sql_fetchrow($result)); + } + $db->sql_freeresult($result); + + // Display assigned items? + $display_item = $request->variable('display_item', 0); + + // Select existing roles + $sql = 'SELECT * + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $db->sql_escape($permission_type) . "' + ORDER BY role_order ASC"; + $result = $db->sql_query($sql); + + $s_role_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + $role_name = (!empty($user->lang[$row['role_name']])) ? $user->lang[$row['role_name']] : $row['role_name']; + + $template->assign_block_vars('roles', array( + 'ROLE_NAME' => $role_name, + 'ROLE_DESCRIPTION' => (!empty($user->lang[$row['role_description']])) ? $user->lang[$row['role_description']] : nl2br($row['role_description']), + + 'U_EDIT' => $this->u_action . '&action=edit&role_id=' . $row['role_id'], + 'U_REMOVE' => $this->u_action . '&action=remove&role_id=' . $row['role_id'], + 'U_MOVE_UP' => $this->u_action . '&action=move_up&role_id=' . $row['role_id'] . '&hash=' . generate_link_hash('acp_permission_roles'), + 'U_MOVE_DOWN' => $this->u_action . '&action=move_down&role_id=' . $row['role_id'] . '&hash=' . generate_link_hash('acp_permission_roles'), + 'U_DISPLAY_ITEMS' => ($row['role_id'] == $display_item) ? '' : $this->u_action . '&display_item=' . $row['role_id'] . '#assigned_to') + ); + + $s_role_options .= ''; + + if ($display_item == $row['role_id']) + { + $template->assign_vars(array( + 'L_ROLE_ASSIGNED_TO' => sprintf($user->lang['ROLE_ASSIGNED_TO'], $role_name)) + ); + } + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'S_ROLE_OPTIONS' => $s_role_options) + ); + + if ($display_item) + { + $template->assign_vars(array( + 'S_DISPLAY_ROLE_MASK' => true) + ); + + $hold_ary = $this->auth_admin->get_role_mask($display_item); + $this->auth_admin->display_role_mask($hold_ary); + } + } + + /** + * Display permission settings able to be set + */ + function display_auth_options($auth_options) + { + global $template, $phpbb_container; + + /* @var $phpbb_permissions \phpbb\permissions */ + $phpbb_permissions = $phpbb_container->get('acl.permissions'); + + $content_array = $categories = array(); + $key_sort_array = array(0); + $auth_options = array(0 => $auth_options); + + // Making use of auth_admin method here (we do not really want to change two similar code fragments) + $this->auth_admin->build_permission_array($auth_options, $content_array, $categories, $key_sort_array); + + $content_array = $content_array[0]; + + $template->assign_var('S_NUM_PERM_COLS', count($categories)); + + // Assign to template + foreach ($content_array as $cat => $cat_array) + { + $template->assign_block_vars('auth', array( + 'CAT_NAME' => $phpbb_permissions->get_category_lang($cat), + + 'S_YES' => ($cat_array['S_YES'] && !$cat_array['S_NEVER'] && !$cat_array['S_NO']) ? true : false, + 'S_NEVER' => ($cat_array['S_NEVER'] && !$cat_array['S_YES'] && !$cat_array['S_NO']) ? true : false, + 'S_NO' => ($cat_array['S_NO'] && !$cat_array['S_NEVER'] && !$cat_array['S_YES']) ? true : false) + ); + + foreach ($cat_array['permissions'] as $permission => $allowed) + { + $template->assign_block_vars('auth.mask', array( + 'S_YES' => ($allowed == ACL_YES) ? true : false, + 'S_NEVER' => ($allowed == ACL_NEVER) ? true : false, + 'S_NO' => ($allowed == ACL_NO) ? true : false, + + 'FIELD_NAME' => $permission, + 'PERMISSION' => $phpbb_permissions->get_permission_lang($permission), + )); + } + } + } + + /** + * Remove role + */ + function remove_role($role_id, $permission_type) + { + global $db; + + // Get complete auth array + $sql = 'SELECT auth_option, auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option " . $db->sql_like_expression($permission_type . $db->get_any_char()); + $result = $db->sql_query($sql); + + $auth_settings = array(); + while ($row = $db->sql_fetchrow($result)) + { + $auth_settings[$row['auth_option']] = ACL_NO; + } + $db->sql_freeresult($result); + + // Get the role auth settings we need to re-set... + $sql = 'SELECT o.auth_option, r.auth_setting + FROM ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' o + WHERE o.auth_option_id = r.auth_option_id + AND r.role_id = ' . $role_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $auth_settings[$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + + // Get role assignments + $hold_ary = $this->auth_admin->get_role_mask($role_id); + + // Re-assign permissions + foreach ($hold_ary as $forum_id => $forum_ary) + { + if (isset($forum_ary['users'])) + { + $this->auth_admin->acl_set('user', $forum_id, $forum_ary['users'], $auth_settings, 0, false); + } + + if (isset($forum_ary['groups'])) + { + $this->auth_admin->acl_set('group', $forum_id, $forum_ary['groups'], $auth_settings, 0, false); + } + } + + // Remove role from users and groups just to be sure (happens through acl_set) + $sql = 'DELETE FROM ' . ACL_USERS_TABLE . ' + WHERE auth_role_id = ' . $role_id; + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE auth_role_id = ' . $role_id; + $db->sql_query($sql); + + // Remove role data and role + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $db->sql_query($sql); + + $this->auth_admin->acl_clear_prefetch(); + } +} diff --git a/includes/acp/acp_permissions.php b/includes/acp/acp_permissions.php new file mode 100644 index 0000000..e683b19 --- /dev/null +++ b/includes/acp/acp_permissions.php @@ -0,0 +1,1346 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_permissions +{ + var $u_action; + var $permission_dropdown; + + /** + * @var $phpbb_permissions \phpbb\permissions + */ + protected $permissions; + + function main($id, $mode) + { + global $db, $user, $auth, $template, $phpbb_container, $request; + global $config, $phpbb_root_path, $phpEx; + + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + if (!class_exists('auth_admin')) + { + include($phpbb_root_path . 'includes/acp/auth.' . $phpEx); + } + + $this->permissions = $phpbb_container->get('acl.permissions'); + + $auth_admin = new auth_admin(); + + $user->add_lang('acp/permissions'); + add_permission_language(); + + $this->tpl_name = 'acp_permissions'; + + // Trace has other vars + if ($mode == 'trace') + { + $user_id = $request->variable('u', 0); + $forum_id = $request->variable('f', 0); + $permission = $request->variable('auth', ''); + + $this->tpl_name = 'permission_trace'; + + if ($user_id && isset($auth_admin->acl_options['id'][$permission]) && $auth->acl_get('a_viewauth')) + { + $this->page_title = sprintf($user->lang['TRACE_PERMISSION'], $this->permissions->get_permission_lang($permission)); + $this->permission_trace($user_id, $forum_id, $permission); + return; + } + trigger_error('NO_MODE', E_USER_ERROR); + } + + // Copy forum permissions + if ($mode == 'setting_forum_copy') + { + $this->tpl_name = 'permission_forum_copy'; + + if ($auth->acl_get('a_fauth') && $auth->acl_get('a_authusers') && $auth->acl_get('a_authgroups') && $auth->acl_get('a_mauth')) + { + $this->page_title = 'ACP_FORUM_PERMISSIONS_COPY'; + $this->copy_forum_permissions(); + return; + } + + trigger_error('NO_MODE', E_USER_ERROR); + } + + // Set some vars + $action = $request->variable('action', array('' => 0)); + $action = key($action); + $action = (isset($_POST['psubmit'])) ? 'apply_permissions' : $action; + + $all_forums = $request->variable('all_forums', 0); + $subforum_id = $request->variable('subforum_id', 0); + $forum_id = $request->variable('forum_id', array(0)); + + $username = $request->variable('username', array(''), true); + $usernames = $request->variable('usernames', '', true); + $user_id = $request->variable('user_id', array(0)); + + $group_id = $request->variable('group_id', array(0)); + $select_all_groups = $request->variable('select_all_groups', 0); + + $form_name = 'acp_permissions'; + add_form_key($form_name); + + // If select all groups is set, we pre-build the group id array (this option is used for other screens to link to the permission settings screen) + if ($select_all_groups) + { + // Add default groups to selection + $sql_and = (!$config['coppa_enable']) ? " AND group_name <> 'REGISTERED_COPPA'" : ''; + + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . ' + WHERE group_type = ' . GROUP_SPECIAL . " + $sql_and"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $group_id[] = $row['group_id']; + } + $db->sql_freeresult($result); + } + + // Map usernames to ids and vice versa + if ($usernames) + { + $username = explode("\n", $usernames); + } + unset($usernames); + + if (count($username) && !count($user_id)) + { + user_get_id_name($user_id, $username); + + if (!count($user_id)) + { + trigger_error($user->lang['SELECTED_USER_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + unset($username); + + // Build forum ids (of all forums are checked or subforum listing used) + if ($all_forums) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + ORDER BY left_id'; + $result = $db->sql_query($sql); + + $forum_id = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_id[] = (int) $row['forum_id']; + } + $db->sql_freeresult($result); + } + else if ($subforum_id) + { + $forum_id = array(); + foreach (get_forum_branch($subforum_id, 'children') as $row) + { + $forum_id[] = (int) $row['forum_id']; + } + } + + // Define some common variables for every mode + $permission_scope = (strpos($mode, '_global') !== false) ? 'global' : 'local'; + + // Showing introductionary page? + if ($mode == 'intro') + { + $this->page_title = 'ACP_PERMISSIONS'; + + $template->assign_vars(array( + 'S_INTRO' => true) + ); + + return; + } + + switch ($mode) + { + case 'setting_user_global': + case 'setting_group_global': + $this->permission_dropdown = array('u_', 'm_', 'a_'); + $permission_victim = ($mode == 'setting_user_global') ? array('user') : array('group'); + $this->page_title = ($mode == 'setting_user_global') ? 'ACP_USERS_PERMISSIONS' : 'ACP_GROUPS_PERMISSIONS'; + break; + + case 'setting_user_local': + case 'setting_group_local': + $this->permission_dropdown = array('f_', 'm_'); + $permission_victim = ($mode == 'setting_user_local') ? array('user', 'forums') : array('group', 'forums'); + $this->page_title = ($mode == 'setting_user_local') ? 'ACP_USERS_FORUM_PERMISSIONS' : 'ACP_GROUPS_FORUM_PERMISSIONS'; + break; + + case 'setting_admin_global': + case 'setting_mod_global': + $this->permission_dropdown = (strpos($mode, '_admin_') !== false) ? array('a_') : array('m_'); + $permission_victim = array('usergroup'); + $this->page_title = ($mode == 'setting_admin_global') ? 'ACP_ADMINISTRATORS' : 'ACP_GLOBAL_MODERATORS'; + break; + + case 'setting_mod_local': + case 'setting_forum_local': + $this->permission_dropdown = ($mode == 'setting_mod_local') ? array('m_') : array('f_'); + $permission_victim = array('forums', 'usergroup'); + $this->page_title = ($mode == 'setting_mod_local') ? 'ACP_FORUM_MODERATORS' : 'ACP_FORUM_PERMISSIONS'; + break; + + case 'view_admin_global': + case 'view_user_global': + case 'view_mod_global': + $this->permission_dropdown = ($mode == 'view_admin_global') ? array('a_') : (($mode == 'view_user_global') ? array('u_') : array('m_')); + $permission_victim = array('usergroup_view'); + $this->page_title = ($mode == 'view_admin_global') ? 'ACP_VIEW_ADMIN_PERMISSIONS' : (($mode == 'view_user_global') ? 'ACP_VIEW_USER_PERMISSIONS' : 'ACP_VIEW_GLOBAL_MOD_PERMISSIONS'); + break; + + case 'view_mod_local': + case 'view_forum_local': + $this->permission_dropdown = ($mode == 'view_mod_local') ? array('m_') : array('f_'); + $permission_victim = array('forums', 'usergroup_view'); + $this->page_title = ($mode == 'view_mod_local') ? 'ACP_VIEW_FORUM_MOD_PERMISSIONS' : 'ACP_VIEW_FORUM_PERMISSIONS'; + break; + + default: + trigger_error('NO_MODE', E_USER_ERROR); + break; + } + + $template->assign_vars(array( + 'L_TITLE' => $user->lang[$this->page_title], + 'L_EXPLAIN' => $user->lang[$this->page_title . '_EXPLAIN']) + ); + + // Get permission type + $permission_type = $request->variable('type', $this->permission_dropdown[0]); + + if (!in_array($permission_type, $this->permission_dropdown)) + { + trigger_error($user->lang['WRONG_PERMISSION_TYPE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Handle actions + if (strpos($mode, 'setting_') === 0 && $action) + { + switch ($action) + { + case 'delete': + if (confirm_box(true)) + { + // All users/groups selected? + $all_users = (isset($_POST['all_users'])) ? true : false; + $all_groups = (isset($_POST['all_groups'])) ? true : false; + + if ($all_users || $all_groups) + { + $items = $this->retrieve_defined_user_groups($permission_scope, $forum_id, $permission_type); + + if ($all_users && count($items['user_ids'])) + { + $user_id = $items['user_ids']; + } + else if ($all_groups && count($items['group_ids'])) + { + $group_id = $items['group_ids']; + } + } + + if (count($user_id) || count($group_id)) + { + $this->remove_permissions($mode, $permission_type, $auth_admin, $user_id, $group_id, $forum_id); + } + else + { + trigger_error($user->lang['NO_USER_GROUP_SELECTED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + else + { + if (isset($_POST['cancel'])) + { + $u_redirect = $this->u_action . '&type=' . $permission_type; + foreach ($forum_id as $fid) + { + $u_redirect .= '&forum_id[]=' . $fid; + } + redirect($u_redirect); + } + + $s_hidden_fields = array( + 'i' => $id, + 'mode' => $mode, + 'action' => array($action => 1), + 'user_id' => $user_id, + 'group_id' => $group_id, + 'forum_id' => $forum_id, + 'type' => $permission_type, + ); + if (isset($_POST['all_users'])) + { + $s_hidden_fields['all_users'] = 1; + } + if (isset($_POST['all_groups'])) + { + $s_hidden_fields['all_groups'] = 1; + } + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields($s_hidden_fields)); + } + break; + + case 'apply_permissions': + if (!isset($_POST['setting'])) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_SETTING_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); + } + + $this->set_permissions($mode, $permission_type, $auth_admin, $user_id, $group_id); + break; + + case 'apply_all_permissions': + if (!isset($_POST['setting'])) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_SETTING_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); + } + + $this->set_all_permissions($mode, $permission_type, $auth_admin, $user_id, $group_id); + break; + } + } + + // Go through the screens/options needed and present them in correct order + foreach ($permission_victim as $victim) + { + switch ($victim) + { + case 'forum_dropdown': + + if (count($forum_id)) + { + $this->check_existence('forum', $forum_id); + continue 2; + } + + $template->assign_vars(array( + 'S_SELECT_FORUM' => true, + 'S_FORUM_OPTIONS' => make_forum_select(false, false, true, false, false)) + ); + + break; + + case 'forums': + + if (count($forum_id)) + { + $this->check_existence('forum', $forum_id); + continue 2; + } + + $forum_list = make_forum_select(false, false, true, false, false, false, true); + + // Build forum options + $s_forum_options = ''; + foreach ($forum_list as $f_id => $f_row) + { + $s_forum_options .= ''; + } + + // Build subforum options + $s_subforum_options = $this->build_subforum_options($forum_list); + + $template->assign_vars(array( + 'S_SELECT_FORUM' => true, + 'S_FORUM_OPTIONS' => $s_forum_options, + 'S_SUBFORUM_OPTIONS' => $s_subforum_options, + 'S_FORUM_ALL' => true, + 'S_FORUM_MULTIPLE' => true) + ); + + break; + + case 'user': + + if (count($user_id)) + { + $this->check_existence('user', $user_id); + continue 2; + } + + $template->assign_vars(array( + 'S_SELECT_USER' => true, + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=select_victim&field=username&select_single=true'), + )); + + break; + + case 'group': + + if (count($group_id)) + { + $this->check_existence('group', $group_id); + continue 2; + } + + $template->assign_vars(array( + 'S_SELECT_GROUP' => true, + 'S_GROUP_OPTIONS' => group_select_options(false, false, false), // Show all groups + )); + + break; + + case 'usergroup': + case 'usergroup_view': + + $all_users = (isset($_POST['all_users'])) ? true : false; + $all_groups = (isset($_POST['all_groups'])) ? true : false; + + if ((count($user_id) && !$all_users) || (count($group_id) && !$all_groups)) + { + if (count($user_id)) + { + $this->check_existence('user', $user_id); + } + + if (count($group_id)) + { + $this->check_existence('group', $group_id); + } + + continue 2; + } + + // Now we check the users... because the "all"-selection is different here (all defined users/groups) + $items = $this->retrieve_defined_user_groups($permission_scope, $forum_id, $permission_type); + + if ($all_users && count($items['user_ids'])) + { + $user_id = $items['user_ids']; + continue 2; + } + + if ($all_groups && count($items['group_ids'])) + { + $group_id = $items['group_ids']; + continue 2; + } + + $template->assign_vars(array( + 'S_SELECT_USERGROUP' => ($victim == 'usergroup') ? true : false, + 'S_SELECT_USERGROUP_VIEW' => ($victim == 'usergroup_view') ? true : false, + 'S_DEFINED_USER_OPTIONS' => $items['user_ids_options'], + 'S_DEFINED_GROUP_OPTIONS' => $items['group_ids_options'], + 'S_ADD_GROUP_OPTIONS' => group_select_options(false, $items['group_ids'], false), // Show all groups + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=add_user&field=username&select_single=true'), + )); + + break; + } + + // The S_ALLOW_SELECT parameter below is a measure to lower memory usage. + // If there are more than 5 forums selected the admin is not able to select all users/groups too. + // We need to see if the number of forums can be increased or need to be decreased. + + // Setting permissions screen + $s_hidden_fields = build_hidden_fields(array( + 'user_id' => $user_id, + 'group_id' => $group_id, + 'forum_id' => $forum_id, + 'type' => $permission_type, + )); + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'ANONYMOUS_USER_ID' => ANONYMOUS, + + 'S_SELECT_VICTIM' => true, + 'S_ALLOW_ALL_SELECT' => (count($forum_id) > 5) ? false : true, + 'S_CAN_SELECT_USER' => ($auth->acl_get('a_authusers')) ? true : false, + 'S_CAN_SELECT_GROUP' => ($auth->acl_get('a_authgroups')) ? true : false, + 'S_HIDDEN_FIELDS' => $s_hidden_fields) + ); + + // Let the forum names being displayed + if (count($forum_id)) + { + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_id) . ' + ORDER BY left_id ASC'; + $result = $db->sql_query($sql); + + $forum_names = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_names[] = $row['forum_name']; + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'S_FORUM_NAMES' => (count($forum_names)) ? true : false, + 'FORUM_NAMES' => implode($user->lang['COMMA_SEPARATOR'], $forum_names)) + ); + } + + return; + } + + // Setting permissions screen + $s_hidden_fields = build_hidden_fields(array( + 'user_id' => $user_id, + 'group_id' => $group_id, + 'forum_id' => $forum_id, + 'type' => $permission_type, + )); + + // Do not allow forum_ids being set and no other setting defined (will bog down the server too much) + if (count($forum_id) && !count($user_id) && !count($group_id)) + { + trigger_error($user->lang['ONLY_FORUM_DEFINED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $template->assign_vars(array( + 'S_PERMISSION_DROPDOWN' => (count($this->permission_dropdown) > 1) ? $this->build_permission_dropdown($this->permission_dropdown, $permission_type, $permission_scope) : false, + 'L_PERMISSION_TYPE' => $this->permissions->get_type_lang($permission_type), + + 'U_ACTION' => $this->u_action, + 'S_HIDDEN_FIELDS' => $s_hidden_fields) + ); + + if (strpos($mode, 'setting_') === 0) + { + $template->assign_vars(array( + 'S_SETTING_PERMISSIONS' => true) + ); + + $hold_ary = $auth_admin->get_mask('set', (count($user_id)) ? $user_id : false, (count($group_id)) ? $group_id : false, (count($forum_id)) ? $forum_id : false, $permission_type, $permission_scope, ACL_NO); + $auth_admin->display_mask('set', $permission_type, $hold_ary, ((count($user_id)) ? 'user' : 'group'), (($permission_scope == 'local') ? true : false)); + } + else + { + $template->assign_vars(array( + 'S_VIEWING_PERMISSIONS' => true) + ); + + $hold_ary = $auth_admin->get_mask('view', (count($user_id)) ? $user_id : false, (count($group_id)) ? $group_id : false, (count($forum_id)) ? $forum_id : false, $permission_type, $permission_scope, ACL_NEVER); + $auth_admin->display_mask('view', $permission_type, $hold_ary, ((count($user_id)) ? 'user' : 'group'), (($permission_scope == 'local') ? true : false)); + } + } + + /** + * Build +subforum options + */ + function build_subforum_options($forum_list) + { + global $user; + + $s_options = ''; + + $forum_list = array_merge($forum_list); + + foreach ($forum_list as $key => $row) + { + if ($row['disabled']) + { + continue; + } + + $s_options .= ''; + } + + return $s_options; + } + + /** + * Build dropdown field for changing permission types + */ + function build_permission_dropdown($options, $default_option, $permission_scope) + { + global $auth; + + $s_dropdown_options = ''; + foreach ($options as $setting) + { + if (!$auth->acl_get('a_' . str_replace('_', '', $setting) . 'auth')) + { + continue; + } + + $selected = ($setting == $default_option) ? ' selected="selected"' : ''; + $l_setting = $this->permissions->get_type_lang($setting, $permission_scope); + $s_dropdown_options .= ''; + } + + return $s_dropdown_options; + } + + /** + * Check if selected items exist. Remove not found ids and if empty return error. + */ + function check_existence($mode, &$ids) + { + global $db, $user; + + switch ($mode) + { + case 'user': + $table = USERS_TABLE; + $sql_id = 'user_id'; + break; + + case 'group': + $table = GROUPS_TABLE; + $sql_id = 'group_id'; + break; + + case 'forum': + $table = FORUMS_TABLE; + $sql_id = 'forum_id'; + break; + } + + if (count($ids)) + { + $sql = "SELECT $sql_id + FROM $table + WHERE " . $db->sql_in_set($sql_id, $ids); + $result = $db->sql_query($sql); + + $ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $ids[] = (int) $row[$sql_id]; + } + $db->sql_freeresult($result); + } + + if (!count($ids)) + { + trigger_error($user->lang['SELECTED_' . strtoupper($mode) . '_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + /** + * Apply permissions + */ + function set_permissions($mode, $permission_type, $auth_admin, &$user_id, &$group_id) + { + global $db, $cache, $user, $auth; + global $request; + + $psubmit = $request->variable('psubmit', array(0 => array(0 => 0))); + + // User or group to be set? + $ug_type = (count($user_id)) ? 'user' : 'group'; + + // Check the permission setting again + if (!$auth->acl_get('a_' . str_replace('_', '', $permission_type) . 'auth') || !$auth->acl_get('a_auth' . $ug_type . 's')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // We loop through the auth settings defined in our submit + list($ug_id, ) = each($psubmit); + list($forum_id, ) = each($psubmit[$ug_id]); + + $settings = $request->variable('setting', array(0 => array(0 => array('' => 0))), false, \phpbb\request\request_interface::POST); + if (empty($settings) || empty($settings[$ug_id]) || empty($settings[$ug_id][$forum_id])) + { + trigger_error('WRONG_PERMISSION_SETTING_FORMAT', E_USER_WARNING); + } + + $auth_settings = $settings[$ug_id][$forum_id]; + + // Do we have a role we want to set? + $roles = $request->variable('role', array(0 => array(0 => 0)), false, \phpbb\request\request_interface::POST); + $assigned_role = (isset($roles[$ug_id][$forum_id])) ? (int) $roles[$ug_id][$forum_id] : 0; + + // Do the admin want to set these permissions to other items too? + $inherit = $request->variable('inherit', array(0 => array(0))); + + $ug_id = array($ug_id); + $forum_id = array($forum_id); + + if (count($inherit)) + { + foreach ($inherit as $_ug_id => $forum_id_ary) + { + // Inherit users/groups? + if (!in_array($_ug_id, $ug_id)) + { + $ug_id[] = $_ug_id; + } + + // Inherit forums? + $forum_id = array_merge($forum_id, array_keys($forum_id_ary)); + } + } + + $forum_id = array_unique($forum_id); + + // If the auth settings differ from the assigned role, then do not set a role... + if ($assigned_role) + { + if (!$this->check_assigned_role($assigned_role, $auth_settings)) + { + $assigned_role = 0; + } + } + + // Update the permission set... + $auth_admin->acl_set($ug_type, $forum_id, $ug_id, $auth_settings, $assigned_role); + + // Do we need to recache the moderator lists? + if ($permission_type == 'm_') + { + phpbb_cache_moderators($db, $cache, $auth); + } + + // Remove users who are now moderators or admins from everyones foes list + if ($permission_type == 'm_' || $permission_type == 'a_') + { + phpbb_update_foes($db, $auth, $group_id, $user_id); + } + + $this->log_action($mode, 'add', $permission_type, $ug_type, $ug_id, $forum_id); + + meta_refresh(5, $this->u_action); + trigger_error($user->lang['AUTH_UPDATED'] . adm_back_link($this->u_action)); + } + + /** + * Apply all permissions + */ + function set_all_permissions($mode, $permission_type, $auth_admin, &$user_id, &$group_id) + { + global $db, $cache, $user, $auth; + global $request; + + // User or group to be set? + $ug_type = (count($user_id)) ? 'user' : 'group'; + + // Check the permission setting again + if (!$auth->acl_get('a_' . str_replace('_', '', $permission_type) . 'auth') || !$auth->acl_get('a_auth' . $ug_type . 's')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $auth_settings = $request->variable('setting', array(0 => array(0 => array('' => 0))), false, \phpbb\request\request_interface::POST); + $auth_roles = $request->variable('role', array(0 => array(0 => 0)), false, \phpbb\request\request_interface::POST); + $ug_ids = $forum_ids = array(); + + // We need to go through the auth settings + foreach ($auth_settings as $ug_id => $forum_auth_row) + { + $ug_id = (int) $ug_id; + $ug_ids[] = $ug_id; + + foreach ($forum_auth_row as $forum_id => $auth_options) + { + $forum_id = (int) $forum_id; + $forum_ids[] = $forum_id; + + // Check role... + $assigned_role = (isset($auth_roles[$ug_id][$forum_id])) ? (int) $auth_roles[$ug_id][$forum_id] : 0; + + // If the auth settings differ from the assigned role, then do not set a role... + if ($assigned_role) + { + if (!$this->check_assigned_role($assigned_role, $auth_options)) + { + $assigned_role = 0; + } + } + + // Update the permission set... + $auth_admin->acl_set($ug_type, $forum_id, $ug_id, $auth_options, $assigned_role, false); + } + } + + $auth_admin->acl_clear_prefetch(); + + // Do we need to recache the moderator lists? + if ($permission_type == 'm_') + { + phpbb_cache_moderators($db, $cache, $auth); + } + + // Remove users who are now moderators or admins from everyones foes list + if ($permission_type == 'm_' || $permission_type == 'a_') + { + phpbb_update_foes($db, $auth, $group_id, $user_id); + } + + $this->log_action($mode, 'add', $permission_type, $ug_type, $ug_ids, $forum_ids); + + if ($mode == 'setting_forum_local' || $mode == 'setting_mod_local') + { + meta_refresh(5, $this->u_action . '&forum_id[]=' . implode('&forum_id[]=', $forum_ids)); + trigger_error($user->lang['AUTH_UPDATED'] . adm_back_link($this->u_action . '&forum_id[]=' . implode('&forum_id[]=', $forum_ids))); + } + else + { + meta_refresh(5, $this->u_action); + trigger_error($user->lang['AUTH_UPDATED'] . adm_back_link($this->u_action)); + } + } + + /** + * Compare auth settings with auth settings from role + * returns false if they differ, true if they are equal + */ + function check_assigned_role($role_id, &$auth_settings) + { + global $db; + + $sql = 'SELECT o.auth_option, r.auth_setting + FROM ' . ACL_OPTIONS_TABLE . ' o, ' . ACL_ROLES_DATA_TABLE . ' r + WHERE o.auth_option_id = r.auth_option_id + AND r.role_id = ' . $role_id; + $result = $db->sql_query($sql); + + $test_auth_settings = array(); + while ($row = $db->sql_fetchrow($result)) + { + $test_auth_settings[$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + + // We need to add any ACL_NO setting from auth_settings to compare correctly + foreach ($auth_settings as $option => $setting) + { + if ($setting == ACL_NO) + { + $test_auth_settings[$option] = $setting; + } + } + + if (count(array_diff_assoc($auth_settings, $test_auth_settings))) + { + return false; + } + + return true; + } + + /** + * Remove permissions + */ + function remove_permissions($mode, $permission_type, $auth_admin, &$user_id, &$group_id, &$forum_id) + { + global $user, $db, $cache, $auth; + + // User or group to be set? + $ug_type = (count($user_id)) ? 'user' : 'group'; + + // Check the permission setting again + if (!$auth->acl_get('a_' . str_replace('_', '', $permission_type) . 'auth') || !$auth->acl_get('a_auth' . $ug_type . 's')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $auth_admin->acl_delete($ug_type, (($ug_type == 'user') ? $user_id : $group_id), (count($forum_id) ? $forum_id : false), $permission_type); + + // Do we need to recache the moderator lists? + if ($permission_type == 'm_') + { + phpbb_cache_moderators($db, $cache, $auth); + } + + $this->log_action($mode, 'del', $permission_type, $ug_type, (($ug_type == 'user') ? $user_id : $group_id), (count($forum_id) ? $forum_id : array(0 => 0))); + + if ($mode == 'setting_forum_local' || $mode == 'setting_mod_local') + { + meta_refresh(5, $this->u_action . '&forum_id[]=' . implode('&forum_id[]=', $forum_id)); + trigger_error($user->lang['AUTH_UPDATED'] . adm_back_link($this->u_action . '&forum_id[]=' . implode('&forum_id[]=', $forum_id))); + } + else + { + meta_refresh(5, $this->u_action); + trigger_error($user->lang['AUTH_UPDATED'] . adm_back_link($this->u_action)); + } + } + + /** + * Log permission changes + */ + function log_action($mode, $action, $permission_type, $ug_type, $ug_id, $forum_id) + { + global $db, $user, $phpbb_log, $phpbb_container; + + if (!is_array($ug_id)) + { + $ug_id = array($ug_id); + } + + if (!is_array($forum_id)) + { + $forum_id = array($forum_id); + } + + // Logging ... first grab user or groupnames ... + $sql = ($ug_type == 'group') ? 'SELECT group_name as name, group_type FROM ' . GROUPS_TABLE . ' WHERE ' : 'SELECT username as name FROM ' . USERS_TABLE . ' WHERE '; + $sql .= $db->sql_in_set(($ug_type == 'group') ? 'group_id' : 'user_id', array_map('intval', $ug_id)); + $result = $db->sql_query($sql); + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $l_ug_list = ''; + while ($row = $db->sql_fetchrow($result)) + { + $group_name = $group_helper->get_name($row['name']); + $l_ug_list .= (($l_ug_list != '') ? ', ' : '') . ((isset($row['group_type']) && $row['group_type'] == GROUP_SPECIAL) ? '' . $group_name . '' : $group_name); + } + $db->sql_freeresult($result); + + $mode = str_replace('setting_', '', $mode); + + if ($forum_id[0] == 0) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ACL_' . strtoupper($action) . '_' . strtoupper($mode) . '_' . strtoupper($permission_type), false, array($l_ug_list)); + } + else + { + // Grab the forum details if non-zero forum_id + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_id); + $result = $db->sql_query($sql); + + $l_forum_list = ''; + while ($row = $db->sql_fetchrow($result)) + { + $l_forum_list .= (($l_forum_list != '') ? ', ' : '') . $row['forum_name']; + } + $db->sql_freeresult($result); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ACL_' . strtoupper($action) . '_' . strtoupper($mode) . '_' . strtoupper($permission_type), false, array($l_forum_list, $l_ug_list)); + } + } + + /** + * Display a complete trace tree for the selected permission to determine where settings are set/unset + */ + function permission_trace($user_id, $forum_id, $permission) + { + global $db, $template, $user, $auth, $request, $phpbb_container; + + if ($user_id != $user->data['user_id']) + { + $userdata = $auth->obtain_user_data($user_id); + } + else + { + $userdata = $user->data; + } + + if (!$userdata) + { + trigger_error('NO_USERS', E_USER_ERROR); + } + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $forum_name = false; + + if ($forum_id) + { + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query($sql, 3600); + $forum_name = $db->sql_fetchfield('forum_name'); + $db->sql_freeresult($result); + } + + $back = $request->variable('back', 0); + + $template->assign_vars(array( + 'PERMISSION' => $this->permissions->get_permission_lang($permission), + 'PERMISSION_USERNAME' => $userdata['username'], + 'FORUM_NAME' => $forum_name, + + 'S_GLOBAL_TRACE' => ($forum_id) ? false : true, + + 'U_BACK' => ($back) ? build_url(array('f', 'back')) . "&f=$back" : '') + ); + + $template->assign_block_vars('trace', array( + 'WHO' => $user->lang['DEFAULT'], + 'INFORMATION' => $user->lang['TRACE_DEFAULT'], + + 'S_SETTING_NO' => true, + 'S_TOTAL_NO' => true) + ); + + $sql = 'SELECT DISTINCT g.group_name, g.group_id, g.group_type + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . USER_GROUP_TABLE . ' ug ON (ug.group_id = g.group_id) + WHERE ug.user_id = ' . $user_id . ' + AND ug.user_pending = 0 + AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) + ORDER BY g.group_type DESC, g.group_id DESC'; + $result = $db->sql_query($sql); + + $groups = array(); + while ($row = $db->sql_fetchrow($result)) + { + $groups[$row['group_id']] = array( + 'auth_setting' => ACL_NO, + 'group_name' => $group_helper->get_name($row['group_name']), + ); + } + $db->sql_freeresult($result); + + $total = ACL_NO; + $add_key = (($forum_id) ? '_LOCAL' : ''); + + if (count($groups)) + { + // Get group auth settings + $hold_ary = $auth->acl_group_raw_data(array_keys($groups), $permission, $forum_id); + + foreach ($hold_ary as $group_id => $forum_ary) + { + $groups[$group_id]['auth_setting'] = $hold_ary[$group_id][$forum_id][$permission]; + } + unset($hold_ary); + + foreach ($groups as $id => $row) + { + switch ($row['auth_setting']) + { + case ACL_NO: + $information = $user->lang['TRACE_GROUP_NO' . $add_key]; + break; + + case ACL_YES: + $information = ($total == ACL_YES) ? $user->lang['TRACE_GROUP_YES_TOTAL_YES' . $add_key] : (($total == ACL_NEVER) ? $user->lang['TRACE_GROUP_YES_TOTAL_NEVER' . $add_key] : $user->lang['TRACE_GROUP_YES_TOTAL_NO' . $add_key]); + $total = ($total == ACL_NO) ? ACL_YES : $total; + break; + + case ACL_NEVER: + $information = ($total == ACL_YES) ? $user->lang['TRACE_GROUP_NEVER_TOTAL_YES' . $add_key] : (($total == ACL_NEVER) ? $user->lang['TRACE_GROUP_NEVER_TOTAL_NEVER' . $add_key] : $user->lang['TRACE_GROUP_NEVER_TOTAL_NO' . $add_key]); + $total = ACL_NEVER; + break; + } + + $template->assign_block_vars('trace', array( + 'WHO' => $row['group_name'], + 'INFORMATION' => $information, + + 'S_SETTING_NO' => ($row['auth_setting'] == ACL_NO) ? true : false, + 'S_SETTING_YES' => ($row['auth_setting'] == ACL_YES) ? true : false, + 'S_SETTING_NEVER' => ($row['auth_setting'] == ACL_NEVER) ? true : false, + 'S_TOTAL_NO' => ($total == ACL_NO) ? true : false, + 'S_TOTAL_YES' => ($total == ACL_YES) ? true : false, + 'S_TOTAL_NEVER' => ($total == ACL_NEVER) ? true : false) + ); + } + } + + // Get user specific permission... globally or for this forum + $hold_ary = $auth->acl_user_raw_data($user_id, $permission, $forum_id); + $auth_setting = (!count($hold_ary)) ? ACL_NO : $hold_ary[$user_id][$forum_id][$permission]; + + switch ($auth_setting) + { + case ACL_NO: + $information = ($total == ACL_NO) ? $user->lang['TRACE_USER_NO_TOTAL_NO' . $add_key] : $user->lang['TRACE_USER_KEPT' . $add_key]; + $total = ($total == ACL_NO) ? ACL_NEVER : $total; + break; + + case ACL_YES: + $information = ($total == ACL_YES) ? $user->lang['TRACE_USER_YES_TOTAL_YES' . $add_key] : (($total == ACL_NEVER) ? $user->lang['TRACE_USER_YES_TOTAL_NEVER' . $add_key] : $user->lang['TRACE_USER_YES_TOTAL_NO' . $add_key]); + $total = ($total == ACL_NO) ? ACL_YES : $total; + break; + + case ACL_NEVER: + $information = ($total == ACL_YES) ? $user->lang['TRACE_USER_NEVER_TOTAL_YES' . $add_key] : (($total == ACL_NEVER) ? $user->lang['TRACE_USER_NEVER_TOTAL_NEVER' . $add_key] : $user->lang['TRACE_USER_NEVER_TOTAL_NO' . $add_key]); + $total = ACL_NEVER; + break; + } + + $template->assign_block_vars('trace', array( + 'WHO' => $userdata['username'], + 'INFORMATION' => $information, + + 'S_SETTING_NO' => ($auth_setting == ACL_NO) ? true : false, + 'S_SETTING_YES' => ($auth_setting == ACL_YES) ? true : false, + 'S_SETTING_NEVER' => ($auth_setting == ACL_NEVER) ? true : false, + 'S_TOTAL_NO' => false, + 'S_TOTAL_YES' => ($total == ACL_YES) ? true : false, + 'S_TOTAL_NEVER' => ($total == ACL_NEVER) ? true : false) + ); + + if ($forum_id != 0 && isset($auth->acl_options['global'][$permission])) + { + if ($user_id != $user->data['user_id']) + { + $auth2 = new \phpbb\auth\auth(); + $auth2->acl($userdata); + $auth_setting = $auth2->acl_get($permission); + } + else + { + $auth_setting = $auth->acl_get($permission); + } + + if ($auth_setting) + { + $information = ($total == ACL_YES) ? $user->lang['TRACE_USER_GLOBAL_YES_TOTAL_YES'] : $user->lang['TRACE_USER_GLOBAL_YES_TOTAL_NEVER']; + $total = ACL_YES; + } + else + { + $information = $user->lang['TRACE_USER_GLOBAL_NEVER_TOTAL_KEPT']; + } + + // If there is no auth information we do not need to worry the user by showing non-relevant data. + if ($auth_setting) + { + $template->assign_block_vars('trace', array( + 'WHO' => sprintf($user->lang['TRACE_GLOBAL_SETTING'], $userdata['username']), + 'INFORMATION' => sprintf($information, '", ''), + + 'S_SETTING_NO' => false, + 'S_SETTING_YES' => $auth_setting, + 'S_SETTING_NEVER' => !$auth_setting, + 'S_TOTAL_NO' => false, + 'S_TOTAL_YES' => ($total == ACL_YES) ? true : false, + 'S_TOTAL_NEVER' => ($total == ACL_NEVER) ? true : false) + ); + } + } + + // Take founder status into account, overwriting the default values + if ($userdata['user_type'] == USER_FOUNDER && strpos($permission, 'a_') === 0) + { + $template->assign_block_vars('trace', array( + 'WHO' => $userdata['username'], + 'INFORMATION' => $user->lang['TRACE_USER_FOUNDER'], + + 'S_SETTING_NO' => ($auth_setting == ACL_NO) ? true : false, + 'S_SETTING_YES' => ($auth_setting == ACL_YES) ? true : false, + 'S_SETTING_NEVER' => ($auth_setting == ACL_NEVER) ? true : false, + 'S_TOTAL_NO' => false, + 'S_TOTAL_YES' => true, + 'S_TOTAL_NEVER' => false) + ); + + $total = ACL_YES; + } + + // Total value... + $template->assign_vars(array( + 'S_RESULT_NO' => ($total == ACL_NO) ? true : false, + 'S_RESULT_YES' => ($total == ACL_YES) ? true : false, + 'S_RESULT_NEVER' => ($total == ACL_NEVER) ? true : false, + )); + } + + /** + * Handles copying permissions from one forum to others + */ + function copy_forum_permissions() + { + global $db, $auth, $cache, $template, $user, $request; + + $user->add_lang('acp/forums'); + + $submit = isset($_POST['submit']) ? true : false; + + if ($submit) + { + $src = $request->variable('src_forum_id', 0); + $dest = $request->variable('dest_forum_ids', array(0)); + + if (confirm_box(true)) + { + if (copy_forum_permissions($src, $dest)) + { + phpbb_cache_moderators($db, $cache, $auth); + + $auth->acl_clear_prefetch(); + $cache->destroy('sql', FORUMS_TABLE); + + trigger_error($user->lang['AUTH_UPDATED'] . adm_back_link($this->u_action)); + } + else + { + trigger_error($user->lang['SELECTED_FORUM_NOT_EXIST'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + else + { + $s_hidden_fields = array( + 'submit' => $submit, + 'src_forum_id' => $src, + 'dest_forum_ids' => $dest, + ); + + $s_hidden_fields = build_hidden_fields($s_hidden_fields); + + confirm_box(false, $user->lang['COPY_PERMISSIONS_CONFIRM'], $s_hidden_fields); + } + } + + $template->assign_vars(array( + 'S_FORUM_OPTIONS' => make_forum_select(false, false, false, false, false), + )); + } + + /** + * Get already assigned users/groups + */ + function retrieve_defined_user_groups($permission_scope, $forum_id, $permission_type) + { + global $db, $phpbb_container; + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $sql_forum_id = ($permission_scope == 'global') ? 'AND a.forum_id = 0' : ((count($forum_id)) ? 'AND ' . $db->sql_in_set('a.forum_id', $forum_id) : 'AND a.forum_id <> 0'); + + // Permission options are only able to be a permission set... therefore we will pre-fetch the possible options and also the possible roles + $option_ids = $role_ids = array(); + + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE auth_option ' . $db->sql_like_expression($permission_type . $db->get_any_char()); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $option_ids[] = (int) $row['auth_option_id']; + } + $db->sql_freeresult($result); + + if (count($option_ids)) + { + $sql = 'SELECT DISTINCT role_id + FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE ' . $db->sql_in_set('auth_option_id', $option_ids); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $role_ids[] = (int) $row['role_id']; + } + $db->sql_freeresult($result); + } + + if (count($option_ids) && count($role_ids)) + { + $sql_where = 'AND (' . $db->sql_in_set('a.auth_option_id', $option_ids) . ' OR ' . $db->sql_in_set('a.auth_role_id', $role_ids) . ')'; + } + else if (count($role_ids)) + { + $sql_where = 'AND ' . $db->sql_in_set('a.auth_role_id', $role_ids); + } + else if (count($option_ids)) + { + $sql_where = 'AND ' . $db->sql_in_set('a.auth_option_id', $option_ids); + } + + // Not ideal, due to the filesort, non-use of indexes, etc. + $sql = 'SELECT DISTINCT u.user_id, u.username, u.username_clean, u.user_regdate + FROM ' . USERS_TABLE . ' u, ' . ACL_USERS_TABLE . " a + WHERE u.user_id = a.user_id + $sql_forum_id + $sql_where + ORDER BY u.username_clean, u.user_regdate ASC"; + $result = $db->sql_query($sql); + + $s_defined_user_options = ''; + $defined_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $s_defined_user_options .= ''; + $defined_user_ids[] = $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT DISTINCT g.group_type, g.group_name, g.group_id + FROM ' . GROUPS_TABLE . ' g, ' . ACL_GROUPS_TABLE . " a + WHERE g.group_id = a.group_id + $sql_forum_id + $sql_where + ORDER BY g.group_type DESC, g.group_name ASC"; + $result = $db->sql_query($sql); + + $s_defined_group_options = ''; + $defined_group_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $s_defined_group_options .= '' . $group_helper->get_name($row['group_name']) . ''; + $defined_group_ids[] = $row['group_id']; + } + $db->sql_freeresult($result); + + return array( + 'group_ids' => $defined_group_ids, + 'group_ids_options' => $s_defined_group_options, + 'user_ids' => $defined_user_ids, + 'user_ids_options' => $s_defined_user_options + ); + } +} diff --git a/includes/acp/acp_php_info.php b/includes/acp/acp_php_info.php new file mode 100644 index 0000000..2a1afe8 --- /dev/null +++ b/includes/acp/acp_php_info.php @@ -0,0 +1,89 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_php_info +{ + var $u_action; + + function main($id, $mode) + { + global $template; + + if ($mode != 'info') + { + trigger_error('NO_MODE', E_USER_ERROR); + } + + $this->tpl_name = 'acp_php_info'; + $this->page_title = 'ACP_PHP_INFO'; + + ob_start(); + phpinfo(INFO_GENERAL | INFO_CONFIGURATION | INFO_MODULES | INFO_VARIABLES); + $phpinfo = ob_get_clean(); + + $phpinfo = trim($phpinfo); + + // Here we play around a little with the PHP Info HTML to try and stylise + // it along phpBB's lines ... hopefully without breaking anything. The idea + // for this was nabbed from the PHP annotated manual + preg_match_all('#]*>(.*)#si', $phpinfo, $output); + + if (empty($phpinfo) || empty($output[1][0])) + { + trigger_error('NO_PHPINFO_AVAILABLE', E_USER_WARNING); + } + + $output = $output[1][0]; + + // expose_php can make the image not exist + if (preg_match('#]*>]*>#', $output)) + { + $output = preg_replace('#(.*?]*>]*>)(.*?)#s', '
\2\1
', $output); + } + else + { + $output = preg_replace('#(.*?)#s', '
\1
', $output); + } + $output = preg_replace('#]+>#i', '', $output); + $output = preg_replace('#', ''), array('class="row1"', 'class="row2"', '', '', ''), $output); + + // Fix invalid anchor names (eg "module_Zend Optimizer") + $output = preg_replace_callback('##', array($this, 'remove_spaces'), $output); + + if (empty($output)) + { + trigger_error('NO_PHPINFO_AVAILABLE', E_USER_WARNING); + } + + $orig_output = $output; + + preg_match_all('#
(.*)
#siU', $output, $output); + $output = (!empty($output[1][0])) ? $output[1][0] : $orig_output; + + $template->assign_var('PHPINFO', $output); + } + + function remove_spaces($matches) + { + return '
'; + } +} diff --git a/includes/acp/acp_profile.php b/includes/acp/acp_profile.php new file mode 100644 index 0000000..49da7d8 --- /dev/null +++ b/includes/acp/acp_profile.php @@ -0,0 +1,1292 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_profile +{ + var $u_action; + + var $edit_lang_id; + var $lang_defs; + + /** + * @var \phpbb\di\service_collection + */ + protected $type_collection; + + function main($id, $mode) + { + global $config, $db, $user, $template; + global $phpbb_root_path, $phpEx; + global $request, $phpbb_container, $phpbb_log, $phpbb_dispatcher; + + if (!function_exists('generate_smilies')) + { + include($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + } + + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $user->add_lang(array('ucp', 'acp/profile')); + $this->tpl_name = 'acp_profile'; + $this->page_title = 'ACP_CUSTOM_PROFILE_FIELDS'; + + $field_id = $request->variable('field_id', 0); + $action = (isset($_POST['create'])) ? 'create' : $request->variable('action', ''); + + $error = array(); + + $form_key = 'acp_profile'; + add_form_key($form_key); + + if (!$field_id && in_array($action, array('delete','activate', 'deactivate', 'move_up', 'move_down', 'edit'))) + { + trigger_error($user->lang['NO_FIELD_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + $this->type_collection = $phpbb_container->get('profilefields.type_collection'); + + // Build Language array + // Based on this, we decide which elements need to be edited later and which language items are missing + $this->lang_defs = array(); + + $sql = 'SELECT lang_id, lang_iso + FROM ' . LANG_TABLE . ' + ORDER BY lang_english_name'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + // Make some arrays with all available languages + $this->lang_defs['id'][$row['lang_id']] = $row['lang_iso']; + $this->lang_defs['iso'][$row['lang_iso']] = $row['lang_id']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT field_id, lang_id + FROM ' . PROFILE_LANG_TABLE . ' + ORDER BY lang_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + // Which languages are available for each item + $this->lang_defs['entry'][$row['field_id']][] = $row['lang_id']; + } + $db->sql_freeresult($result); + + // Have some fields been defined? + if (isset($this->lang_defs['entry'])) + { + foreach ($this->lang_defs['entry'] as $field_ident => $field_ary) + { + // Fill an array with the languages that are missing for each field + $this->lang_defs['diff'][$field_ident] = array_diff(array_values($this->lang_defs['iso']), $field_ary); + } + } + + switch ($action) + { + case 'delete': + + if (confirm_box(true)) + { + $sql = 'SELECT field_ident + FROM ' . PROFILE_FIELDS_TABLE . " + WHERE field_id = $field_id"; + $result = $db->sql_query($sql); + $field_ident = (string) $db->sql_fetchfield('field_ident'); + $db->sql_freeresult($result); + + $db->sql_transaction('begin'); + + $db->sql_query('DELETE FROM ' . PROFILE_FIELDS_TABLE . " WHERE field_id = $field_id"); + $db->sql_query('DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . " WHERE field_id = $field_id"); + $db->sql_query('DELETE FROM ' . PROFILE_LANG_TABLE . " WHERE field_id = $field_id"); + + /* @var $db_tools \phpbb\db\tools\tools_interface */ + $db_tools = $phpbb_container->get('dbal.tools'); + $db_tools->sql_column_remove(PROFILE_FIELDS_DATA_TABLE, 'pf_' . $field_ident); + + $order = 0; + + $sql = 'SELECT * + FROM ' . PROFILE_FIELDS_TABLE . ' + ORDER BY field_order'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $order++; + if ($row['field_order'] != $order) + { + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . " + SET field_order = $order + WHERE field_id = {$row['field_id']}"; + $db->sql_query($sql); + } + } + $db->sql_freeresult($result); + + $db->sql_transaction('commit'); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_PROFILE_FIELD_REMOVED', false, array($field_ident)); + trigger_error($user->lang['REMOVED_PROFILE_FIELD'] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, 'DELETE_PROFILE_FIELD', build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'field_id' => $field_id, + ))); + } + + break; + + case 'activate': + + if (!check_link_hash($request->variable('hash', ''), 'acp_profile')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE . " + WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; + $result = $db->sql_query($sql); + $default_lang_id = (int) $db->sql_fetchfield('lang_id'); + $db->sql_freeresult($result); + + if (!in_array($default_lang_id, $this->lang_defs['entry'][$field_id])) + { + trigger_error($user->lang['DEFAULT_LANGUAGE_NOT_FILLED'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . " + SET field_active = 1 + WHERE field_id = $field_id"; + $db->sql_query($sql); + + $sql = 'SELECT field_ident + FROM ' . PROFILE_FIELDS_TABLE . " + WHERE field_id = $field_id"; + $result = $db->sql_query($sql); + $field_ident = (string) $db->sql_fetchfield('field_ident'); + $db->sql_freeresult($result); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_PROFILE_FIELD_ACTIVATE', false, array($field_ident)); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'text' => $user->lang('DEACTIVATE'), + )); + } + + trigger_error($user->lang['PROFILE_FIELD_ACTIVATED'] . adm_back_link($this->u_action)); + + break; + + case 'deactivate': + + if (!check_link_hash($request->variable('hash', ''), 'acp_profile')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . " + SET field_active = 0 + WHERE field_id = $field_id"; + $db->sql_query($sql); + + $sql = 'SELECT field_ident + FROM ' . PROFILE_FIELDS_TABLE . " + WHERE field_id = $field_id"; + $result = $db->sql_query($sql); + $field_ident = (string) $db->sql_fetchfield('field_ident'); + $db->sql_freeresult($result); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'text' => $user->lang('ACTIVATE'), + )); + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_PROFILE_FIELD_DEACTIVATE', false, array($field_ident)); + + trigger_error($user->lang['PROFILE_FIELD_DEACTIVATED'] . adm_back_link($this->u_action)); + + break; + + case 'move_up': + case 'move_down': + + if (!check_link_hash($request->variable('hash', ''), 'acp_profile')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT field_order + FROM ' . PROFILE_FIELDS_TABLE . " + WHERE field_id = $field_id"; + $result = $db->sql_query($sql); + $field_order = $db->sql_fetchfield('field_order'); + $db->sql_freeresult($result); + + if ($field_order === false || ($field_order == 0 && $action == 'move_up')) + { + break; + } + $field_order = (int) $field_order; + $order_total = $field_order * 2 + (($action == 'move_up') ? -1 : 1); + + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . " + SET field_order = $order_total - field_order + WHERE field_order IN ($field_order, " . (($action == 'move_up') ? $field_order - 1 : $field_order + 1) . ')'; + $db->sql_query($sql); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'success' => (bool) $db->sql_affectedrows(), + )); + } + + break; + + case 'create': + case 'edit': + + $step = $request->variable('step', 1); + + $submit = (isset($_REQUEST['next']) || isset($_REQUEST['prev'])) ? true : false; + $save = (isset($_REQUEST['save'])) ? true : false; + + // The language id of default language + $this->edit_lang_id = $this->lang_defs['iso'][$config['default_lang']]; + + // We are editing... we need to grab basic things + if ($action == 'edit') + { + $sql = 'SELECT l.*, f.* + FROM ' . PROFILE_LANG_TABLE . ' l, ' . PROFILE_FIELDS_TABLE . ' f + WHERE l.lang_id = ' . $this->edit_lang_id . " + AND f.field_id = $field_id + AND l.field_id = f.field_id"; + $result = $db->sql_query($sql); + $field_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$field_row) + { + // Some admin changed the default language? + $sql = 'SELECT l.*, f.* + FROM ' . PROFILE_LANG_TABLE . ' l, ' . PROFILE_FIELDS_TABLE . ' f + WHERE l.lang_id <> ' . $this->edit_lang_id . " + AND f.field_id = $field_id + AND l.field_id = f.field_id"; + $result = $db->sql_query($sql); + $field_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$field_row) + { + trigger_error($user->lang['FIELD_NOT_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $this->edit_lang_id = $field_row['lang_id']; + } + $field_type = $field_row['field_type']; + $profile_field = $this->type_collection[$field_type]; + + // Get language entries + $sql = 'SELECT * + FROM ' . PROFILE_FIELDS_LANG_TABLE . ' + WHERE lang_id = ' . $this->edit_lang_id . " + AND field_id = $field_id + ORDER BY option_id ASC"; + $result = $db->sql_query($sql); + + $lang_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + $lang_options[$row['option_id']] = $row['lang_value']; + } + $db->sql_freeresult($result); + + $s_hidden_fields = ''; + } + else + { + // We are adding a new field, define basic params + $lang_options = $field_row = array(); + + $field_type = $request->variable('field_type', ''); + + if (!isset($this->type_collection[$field_type])) + { + trigger_error($user->lang['NO_FIELD_TYPE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $profile_field = $this->type_collection[$field_type]; + $field_row = array_merge($profile_field->get_default_option_values(), array( + 'field_ident' => str_replace(' ', '_', utf8_clean_string($request->variable('field_ident', '', true))), + 'field_required' => 0, + 'field_show_novalue'=> 0, + 'field_hide' => 0, + 'field_show_profile'=> 0, + 'field_no_view' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 0, + 'field_show_on_vt' => 0, + 'field_show_on_ml' => 0, + 'field_is_contact' => 0, + 'field_contact_desc'=> '', + 'field_contact_url' => '', + 'lang_name' => $request->variable('field_ident', '', true), + 'lang_explain' => '', + 'lang_default_value'=> '') + ); + + $s_hidden_fields = ''; + } + + // $exclude contains the data we gather in each step + $exclude = array( + 1 => array('field_ident', 'lang_name', 'lang_explain', 'field_option_none', 'field_show_on_reg', 'field_show_on_pm', 'field_show_on_vt', 'field_show_on_ml', 'field_required', 'field_show_novalue', 'field_hide', 'field_show_profile', 'field_no_view', 'field_is_contact', 'field_contact_desc', 'field_contact_url'), + 2 => array('field_length', 'field_maxlen', 'field_minlen', 'field_validation', 'field_novalue', 'field_default_value'), + 3 => array('l_lang_name', 'l_lang_explain', 'l_lang_default_value', 'l_lang_options') + ); + + // Visibility Options... + $visibility_ary = array( + 'field_required', + 'field_show_novalue', + 'field_show_on_reg', + 'field_show_on_pm', + 'field_show_on_vt', + 'field_show_on_ml', + 'field_show_profile', + 'field_hide', + 'field_is_contact', + ); + + /** + * Event to add initialization for new profile field table fields + * + * @event core.acp_profile_create_edit_init + * @var string action create|edit + * @var int step Configuration step (1|2|3) + * @var bool submit Form has been submitted + * @var bool save Configuration should be saved + * @var string field_type Type of the field we are dealing with + * @var array field_row Array of data about the field + * @var array exclude Array of excluded fields by step + * @var array visibility_ary Array of fields that are visibility related + * @since 3.1.6-RC1 + */ + $vars = array( + 'action', + 'step', + 'submit', + 'save', + 'field_type', + 'field_row', + 'exclude', + 'visibility_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_profile_create_edit_init', compact($vars))); + + $options = $profile_field->prepare_options_form($exclude, $visibility_ary); + + $cp->vars['field_ident'] = ($action == 'create' && $step == 1) ? utf8_clean_string($request->variable('field_ident', $field_row['field_ident'], true)) : $request->variable('field_ident', $field_row['field_ident']); + $cp->vars['lang_name'] = $request->variable('lang_name', $field_row['lang_name'], true); + $cp->vars['lang_explain'] = $request->variable('lang_explain', $field_row['lang_explain'], true); + $cp->vars['lang_default_value'] = $request->variable('lang_default_value', $field_row['lang_default_value'], true); + $cp->vars['field_contact_desc'] = $request->variable('field_contact_desc', $field_row['field_contact_desc'], true); + $cp->vars['field_contact_url'] = $request->variable('field_contact_url', $field_row['field_contact_url'], true); + + foreach ($visibility_ary as $val) + { + $cp->vars[$val] = ($submit || $save) ? $request->variable($val, 0) : $field_row[$val]; + } + + $cp->vars['field_no_view'] = $request->variable('field_no_view', (int) $field_row['field_no_view']); + + // If the user has submitted a form with options (i.e. dropdown field) + if ($options) + { + $exploded_options = (is_array($options)) ? $options : explode("\n", $options); + + if (count($exploded_options) == count($lang_options) || $action == 'create') + { + // The number of options in the field is equal to the number of options already in the database + // Or we are creating a new dropdown list. + $cp->vars['lang_options'] = $exploded_options; + } + else if ($action == 'edit') + { + // Changing the number of options? (We remove and re-create the option fields) + $cp->vars['lang_options'] = $exploded_options; + } + } + else + { + $cp->vars['lang_options'] = $lang_options; + } + + // step 2 + foreach ($exclude[2] as $key) + { + $var = $request->variable($key, $field_row[$key], true); + + $field_data = $cp->vars; + $var = $profile_field->get_excluded_options($key, $action, $var, $field_data, 2); + $cp->vars = $field_data; + + $cp->vars[$key] = $var; + } + + // step 3 - all arrays + if ($action == 'edit') + { + // Get language entries + $sql = 'SELECT * + FROM ' . PROFILE_FIELDS_LANG_TABLE . ' + WHERE lang_id <> ' . $this->edit_lang_id . " + AND field_id = $field_id + ORDER BY option_id ASC"; + $result = $db->sql_query($sql); + + $l_lang_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + $l_lang_options[$row['lang_id']][$row['option_id']] = $row['lang_value']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT lang_id, lang_name, lang_explain, lang_default_value + FROM ' . PROFILE_LANG_TABLE . ' + WHERE lang_id <> ' . $this->edit_lang_id . " + AND field_id = $field_id + ORDER BY lang_id ASC"; + $result = $db->sql_query($sql); + + $l_lang_name = $l_lang_explain = $l_lang_default_value = array(); + while ($row = $db->sql_fetchrow($result)) + { + $l_lang_name[$row['lang_id']] = $row['lang_name']; + $l_lang_explain[$row['lang_id']] = $row['lang_explain']; + $l_lang_default_value[$row['lang_id']] = $row['lang_default_value']; + } + $db->sql_freeresult($result); + } + + foreach ($exclude[3] as $key) + { + $cp->vars[$key] = $request->variable($key, array(0 => ''), true); + + if (!$cp->vars[$key] && $action == 'edit') + { + $cp->vars[$key] = ${$key}; + } + + $field_data = $cp->vars; + $var = $profile_field->get_excluded_options($key, $action, $var, $field_data, 3); + $cp->vars = $field_data; + } + + // Check for general issues in every step + if ($submit) // && $step == 1 + { + // Check values for step 1 + if ($cp->vars['field_ident'] == '') + { + $error[] = $user->lang['EMPTY_FIELD_IDENT']; + } + + if (!preg_match('/^[a-z_]+$/', $cp->vars['field_ident'])) + { + $error[] = $user->lang['INVALID_CHARS_FIELD_IDENT']; + } + + if (strlen($cp->vars['field_ident']) > 17) + { + $error[] = $user->lang['INVALID_FIELD_IDENT_LEN']; + } + + if ($cp->vars['lang_name'] == '') + { + $error[] = $user->lang['EMPTY_USER_FIELD_NAME']; + } + + $error = $profile_field->validate_options_on_submit($error, $cp->vars); + + // Check for already existing field ident + if ($action != 'edit') + { + $sql = 'SELECT field_ident + FROM ' . PROFILE_FIELDS_TABLE . " + WHERE field_ident = '" . $db->sql_escape($cp->vars['field_ident']) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $error[] = $user->lang['FIELD_IDENT_ALREADY_EXIST']; + } + } + } + + if (count($error)) + { + $submit = false; + } + else + { + $step = (isset($_REQUEST['next'])) ? $step + 1 : ((isset($_REQUEST['prev'])) ? $step - 1 : $step); + } + + // Build up the specific hidden fields + foreach ($exclude as $num => $key_ary) + { + if ($num == $step) + { + continue; + } + + $_new_key_ary = array(); + + $field_data = $cp->vars; + foreach ($key_ary as $key) + { + $var = $profile_field->prepare_hidden_fields($step, $key, $action, $field_data); + if ($var !== null) + { + $_new_key_ary[$key] = $var; + } + } + $cp->vars = $field_data; + + $s_hidden_fields .= build_hidden_fields($_new_key_ary); + } + + if (!count($error)) + { + if (($step == 3 && (count($this->lang_defs['iso']) == 1 || $save)) || ($action == 'edit' && $save)) + { + if (!check_form_key($form_key)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $this->save_profile_field($cp, $field_type, $action); + } + } + + $template->assign_vars(array( + 'S_EDIT' => true, + 'S_EDIT_MODE' => ($action == 'edit') ? true : false, + 'ERROR_MSG' => (count($error)) ? implode('
', $error) : '', + + 'L_TITLE' => $user->lang['STEP_' . $step . '_TITLE_' . strtoupper($action)], + 'L_EXPLAIN' => $user->lang['STEP_' . $step . '_EXPLAIN_' . strtoupper($action)], + + 'U_ACTION' => $this->u_action . "&action=$action&step=$step", + 'U_BACK' => $this->u_action) + ); + + // Now go through the steps + switch ($step) + { + // Create basic options - only small differences between field types + case 1: + $template_vars = array( + 'S_STEP_ONE' => true, + 'S_FIELD_REQUIRED' => ($cp->vars['field_required']) ? true : false, + 'S_FIELD_SHOW_NOVALUE'=> ($cp->vars['field_show_novalue']) ? true : false, + 'S_SHOW_ON_REG' => ($cp->vars['field_show_on_reg']) ? true : false, + 'S_SHOW_ON_PM' => ($cp->vars['field_show_on_pm']) ? true : false, + 'S_SHOW_ON_VT' => ($cp->vars['field_show_on_vt']) ? true : false, + 'S_SHOW_ON_MEMBERLIST'=> ($cp->vars['field_show_on_ml']) ? true : false, + 'S_FIELD_HIDE' => ($cp->vars['field_hide']) ? true : false, + 'S_SHOW_PROFILE' => ($cp->vars['field_show_profile']) ? true : false, + 'S_FIELD_NO_VIEW' => ($cp->vars['field_no_view']) ? true : false, + 'S_FIELD_CONTACT' => $cp->vars['field_is_contact'], + 'FIELD_CONTACT_DESC'=> $cp->vars['field_contact_desc'], + 'FIELD_CONTACT_URL' => $cp->vars['field_contact_url'], + + 'L_LANG_SPECIFIC' => sprintf($user->lang['LANG_SPECIFIC_OPTIONS'], $config['default_lang']), + 'FIELD_TYPE' => $profile_field->get_name(), + 'FIELD_IDENT' => $cp->vars['field_ident'], + 'LANG_NAME' => $cp->vars['lang_name'], + 'LANG_EXPLAIN' => $cp->vars['lang_explain'], + ); + + $field_data = $cp->vars; + $profile_field->display_options($template_vars, $field_data); + $cp->vars = $field_data; + + // Build common create options + $template->assign_vars($template_vars); + break; + + case 2: + + $template->assign_vars(array( + 'S_STEP_TWO' => true, + 'L_NEXT_STEP' => (count($this->lang_defs['iso']) == 1) ? $user->lang['SAVE'] : $user->lang['PROFILE_LANG_OPTIONS']) + ); + + // Build options based on profile type + $options = $profile_field->get_options($this->lang_defs['iso'][$config['default_lang']], $cp->vars); + + foreach ($options as $num => $option_ary) + { + $template->assign_block_vars('option', $option_ary); + } + + break; + + // Define remaining language variables + case 3: + + $template->assign_var('S_STEP_THREE', true); + $options = $this->build_language_options($cp, $field_type, $action); + + foreach ($options as $lang_id => $lang_ary) + { + $template->assign_block_vars('options', array( + 'LANGUAGE' => sprintf($user->lang[(($lang_id == $this->edit_lang_id) ? 'DEFAULT_' : '') . 'ISO_LANGUAGE'], $lang_ary['lang_iso'])) + ); + + foreach ($lang_ary['fields'] as $field_ident => $field_ary) + { + $template->assign_block_vars('options.field', array( + 'L_TITLE' => $field_ary['TITLE'], + 'L_EXPLAIN' => (isset($field_ary['EXPLAIN'])) ? $field_ary['EXPLAIN'] : '', + 'FIELD' => $field_ary['FIELD']) + ); + } + } + + break; + } + + $field_data = $cp->vars; + /** + * Event to add template variables for new profile field table fields + * + * @event core.acp_profile_create_edit_after + * @var string action create|edit + * @var int step Configuration step (1|2|3) + * @var bool submit Form has been submitted + * @var bool save Configuration should be saved + * @var string field_type Type of the field we are dealing with + * @var array field_data Array of data about the field + * @var array s_hidden_fields Array of hidden fields in case this needs modification + * @var array options Array of options specific to this step + * @since 3.1.6-RC1 + */ + $vars = array( + 'action', + 'step', + 'submit', + 'save', + 'field_type', + 'field_data', + 's_hidden_fields', + 'options', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_profile_create_edit_after', compact($vars))); + + $template->assign_vars(array( + 'S_HIDDEN_FIELDS' => $s_hidden_fields) + ); + + return; + + break; + } + + $tpl_name = $this->tpl_name; + $page_title = $this->page_title; + $u_action = $this->u_action; + + /** + * Event to handle actions on the ACP profile fields page + * + * @event core.acp_profile_action + * @var string action Action that is being performed + * @var string tpl_name Template file to load + * @var string page_title Page title + * @var string u_action The URL we are at, read only + * @since 3.2.2-RC1 + */ + $vars = array( + 'action', + 'tpl_name', + 'page_title', + 'u_action', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_profile_action', compact($vars))); + + $this->tpl_name = $tpl_name; + $this->page_title = $page_title; + unset($u_action); + + $sql = 'SELECT * + FROM ' . PROFILE_FIELDS_TABLE . ' + ORDER BY field_order'; + $result = $db->sql_query($sql); + + $s_one_need_edit = false; + while ($row = $db->sql_fetchrow($result)) + { + $active_lang = (!$row['field_active']) ? 'ACTIVATE' : 'DEACTIVATE'; + $active_value = (!$row['field_active']) ? 'activate' : 'deactivate'; + $id = $row['field_id']; + + $s_need_edit = (count($this->lang_defs['diff'][$row['field_id']])) ? true : false; + + if ($s_need_edit) + { + $s_one_need_edit = true; + } + + if (!isset($this->type_collection[$row['field_type']])) + { + continue; + } + $profile_field = $this->type_collection[$row['field_type']]; + + $field_block = array( + 'FIELD_IDENT' => $row['field_ident'], + 'FIELD_TYPE' => $profile_field->get_name(), + + 'L_ACTIVATE_DEACTIVATE' => $user->lang[$active_lang], + 'U_ACTIVATE_DEACTIVATE' => $this->u_action . "&action=$active_value&field_id=$id" . '&hash=' . generate_link_hash('acp_profile'), + 'U_EDIT' => $this->u_action . "&action=edit&field_id=$id", + 'U_TRANSLATE' => $this->u_action . "&action=edit&field_id=$id&step=3", + 'U_DELETE' => $this->u_action . "&action=delete&field_id=$id", + 'U_MOVE_UP' => $this->u_action . "&action=move_up&field_id=$id" . '&hash=' . generate_link_hash('acp_profile'), + 'U_MOVE_DOWN' => $this->u_action . "&action=move_down&field_id=$id" . '&hash=' . generate_link_hash('acp_profile'), + + 'S_NEED_EDIT' => $s_need_edit, + ); + + /** + * Event to modify profile field data before it is assigned to the template + * + * @event core.acp_profile_modify_profile_row + * @var array row Array with data for the current profile field + * @var array field_block Template data that is being assigned to the 'fields' block + * @var object profile_field A profile field instance, implements \phpbb\profilefields\type\type_base + * @since 3.2.2-RC1 + */ + $vars = array( + 'row', + 'field_block', + 'profile_field', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_profile_modify_profile_row', compact($vars))); + + $template->assign_block_vars('fields', $field_block); + } + $db->sql_freeresult($result); + + // At least one option field needs editing? + if ($s_one_need_edit) + { + $template->assign_var('S_NEED_EDIT', true); + } + + $s_select_type = ''; + foreach ($this->type_collection as $key => $profile_field) + { + $s_select_type .= ''; + } + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'S_TYPE_OPTIONS' => $s_select_type, + )); + } + + /** + * Build all Language specific options + */ + function build_language_options($cp, $field_type, $action = 'create') + { + global $user, $config, $db, $request; + + $default_lang_id = (!empty($this->edit_lang_id)) ? $this->edit_lang_id : $this->lang_defs['iso'][$config['default_lang']]; + + $sql = 'SELECT lang_id, lang_iso + FROM ' . LANG_TABLE . ' + WHERE lang_id <> ' . (int) $default_lang_id . ' + ORDER BY lang_english_name'; + $result = $db->sql_query($sql); + + $languages = array(); + while ($row = $db->sql_fetchrow($result)) + { + $languages[$row['lang_id']] = $row['lang_iso']; + } + $db->sql_freeresult($result); + + $profile_field = $this->type_collection[$field_type]; + $options = $profile_field->get_language_options($cp->vars); + + $lang_options = array(); + + foreach ($options as $field => $field_type) + { + $lang_options[1]['lang_iso'] = $this->lang_defs['id'][$default_lang_id]; + $lang_options[1]['fields'][$field] = array( + 'TITLE' => $user->lang['CP_' . strtoupper($field)], + 'FIELD' => '
' . ((is_array($cp->vars[$field])) ? implode('
', $cp->vars[$field]) : bbcode_nl2br($cp->vars[$field])) . '
' + ); + + if (isset($user->lang['CP_' . strtoupper($field) . '_EXPLAIN'])) + { + $lang_options[1]['fields'][$field]['EXPLAIN'] = $user->lang['CP_' . strtoupper($field) . '_EXPLAIN']; + } + } + + foreach ($languages as $lang_id => $lang_iso) + { + $lang_options[$lang_id]['lang_iso'] = $lang_iso; + foreach ($options as $field => $field_type) + { + $value = ($action == 'create') ? $request->variable('l_' . $field, array(0 => ''), true) : $cp->vars['l_' . $field]; + if ($field == 'lang_options') + { + $var = (!isset($cp->vars['l_lang_options'][$lang_id]) || !is_array($cp->vars['l_lang_options'][$lang_id])) ? $cp->vars['lang_options'] : $cp->vars['l_lang_options'][$lang_id]; + + switch ($field_type) + { + case 'two_options': + + $lang_options[$lang_id]['fields'][$field] = array( + 'TITLE' => $user->lang['CP_' . strtoupper($field)], + 'FIELD' => ' +
' . $user->lang['FIRST_OPTION'] . '
+
' . $user->lang['SECOND_OPTION'] . '
' + ); + break; + + case 'optionfield': + $value = ((isset($value[$lang_id])) ? ((is_array($value[$lang_id])) ? implode("\n", $value[$lang_id]) : $value[$lang_id]) : implode("\n", $var)); + $lang_options[$lang_id]['fields'][$field] = array( + 'TITLE' => $user->lang['CP_' . strtoupper($field)], + 'FIELD' => '
' + ); + break; + } + + if (isset($user->lang['CP_' . strtoupper($field) . '_EXPLAIN'])) + { + $lang_options[$lang_id]['fields'][$field]['EXPLAIN'] = $user->lang['CP_' . strtoupper($field) . '_EXPLAIN']; + } + } + else + { + $var = ($action == 'create' || !is_array($cp->vars[$field])) ? $cp->vars[$field] : $cp->vars[$field][$lang_id]; + + $lang_options[$lang_id]['fields'][$field] = array( + 'TITLE' => $user->lang['CP_' . strtoupper($field)], + 'FIELD' => ($field_type == 'string') ? '
' : '
' + ); + + if (isset($user->lang['CP_' . strtoupper($field) . '_EXPLAIN'])) + { + $lang_options[$lang_id]['fields'][$field]['EXPLAIN'] = $user->lang['CP_' . strtoupper($field) . '_EXPLAIN']; + } + } + } + } + + return $lang_options; + } + + /** + * Save Profile Field + */ + function save_profile_field($cp, $field_type, $action = 'create') + { + global $db, $config, $user, $phpbb_container, $phpbb_log, $request, $phpbb_dispatcher; + + $field_id = $request->variable('field_id', 0); + + // Collect all information, if something is going wrong, abort the operation + $profile_sql = $profile_lang = $empty_lang = $profile_lang_fields = array(); + + $default_lang_id = (!empty($this->edit_lang_id)) ? $this->edit_lang_id : $this->lang_defs['iso'][$config['default_lang']]; + + if ($action == 'create') + { + $sql = 'SELECT MAX(field_order) as max_field_order + FROM ' . PROFILE_FIELDS_TABLE; + $result = $db->sql_query($sql); + $new_field_order = (int) $db->sql_fetchfield('max_field_order'); + $db->sql_freeresult($result); + + $field_ident = $cp->vars['field_ident']; + } + + // Save the field + $profile_fields = array( + 'field_length' => $cp->vars['field_length'], + 'field_minlen' => $cp->vars['field_minlen'], + 'field_maxlen' => $cp->vars['field_maxlen'], + 'field_novalue' => $cp->vars['field_novalue'], + 'field_default_value' => $cp->vars['field_default_value'], + 'field_validation' => $cp->vars['field_validation'], + 'field_required' => $cp->vars['field_required'], + 'field_show_novalue' => $cp->vars['field_show_novalue'], + 'field_show_on_reg' => $cp->vars['field_show_on_reg'], + 'field_show_on_pm' => $cp->vars['field_show_on_pm'], + 'field_show_on_vt' => $cp->vars['field_show_on_vt'], + 'field_show_on_ml' => $cp->vars['field_show_on_ml'], + 'field_hide' => $cp->vars['field_hide'], + 'field_show_profile' => $cp->vars['field_show_profile'], + 'field_no_view' => $cp->vars['field_no_view'], + 'field_is_contact' => $cp->vars['field_is_contact'], + 'field_contact_desc' => $cp->vars['field_contact_desc'], + 'field_contact_url' => $cp->vars['field_contact_url'], + ); + + $field_data = $cp->vars; + /** + * Event to modify profile field configuration data before saving to database + * + * @event core.acp_profile_create_edit_save_before + * @var string action create|edit + * @var string field_type Type of the field we are dealing with + * @var array field_data Array of data about the field + * @var array profile_fields Array of fields to be sent to the database + * @since 3.1.6-RC1 + */ + $vars = array( + 'action', + 'field_type', + 'field_data', + 'profile_fields', + ); + extract($phpbb_dispatcher->trigger_event('core.acp_profile_create_edit_save_before', compact($vars))); + + if ($action == 'create') + { + $profile_fields += array( + 'field_type' => $field_type, + 'field_ident' => $field_ident, + 'field_name' => $field_ident, + 'field_order' => $new_field_order + 1, + 'field_active' => 1 + ); + + $sql = 'INSERT INTO ' . PROFILE_FIELDS_TABLE . ' ' . $db->sql_build_array('INSERT', $profile_fields); + $db->sql_query($sql); + + $field_id = $db->sql_nextid(); + } + else + { + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $profile_fields) . " + WHERE field_id = $field_id"; + $db->sql_query($sql); + } + + $profile_field = $this->type_collection[$field_type]; + + if ($action == 'create') + { + $field_ident = 'pf_' . $field_ident; + /* @var $db_tools \phpbb\db\tools\tools_interface */ + $db_tools = $phpbb_container->get('dbal.tools'); + $db_tools->sql_column_add(PROFILE_FIELDS_DATA_TABLE, $field_ident, array($profile_field->get_database_column_type(), null)); + } + + $sql_ary = array( + 'lang_name' => $cp->vars['lang_name'], + 'lang_explain' => $cp->vars['lang_explain'], + 'lang_default_value' => $cp->vars['lang_default_value'] + ); + + if ($action == 'create') + { + $sql_ary['field_id'] = $field_id; + $sql_ary['lang_id'] = $default_lang_id; + + $profile_sql[] = 'INSERT INTO ' . PROFILE_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + } + else + { + $this->update_insert(PROFILE_LANG_TABLE, $sql_ary, array('field_id' => $field_id, 'lang_id' => $default_lang_id)); + } + + if (is_array($cp->vars['l_lang_name']) && count($cp->vars['l_lang_name'])) + { + foreach ($cp->vars['l_lang_name'] as $lang_id => $data) + { + if (($cp->vars['lang_name'] != '' && $cp->vars['l_lang_name'][$lang_id] == '') + || ($cp->vars['lang_explain'] != '' && $cp->vars['l_lang_explain'][$lang_id] == '') + || ($cp->vars['lang_default_value'] != '' && $cp->vars['l_lang_default_value'][$lang_id] == '')) + { + $empty_lang[$lang_id] = true; + break; + } + + if (!isset($empty_lang[$lang_id])) + { + $profile_lang[] = array( + 'field_id' => $field_id, + 'lang_id' => $lang_id, + 'lang_name' => $cp->vars['l_lang_name'][$lang_id], + 'lang_explain' => (isset($cp->vars['l_lang_explain'][$lang_id])) ? $cp->vars['l_lang_explain'][$lang_id] : '', + 'lang_default_value' => (isset($cp->vars['l_lang_default_value'][$lang_id])) ? $cp->vars['l_lang_default_value'][$lang_id] : '' + ); + } + } + + foreach ($empty_lang as $lang_id => $NULL) + { + $sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . " + WHERE field_id = $field_id + AND lang_id = " . (int) $lang_id; + $db->sql_query($sql); + } + } + + $cp->vars = $profile_field->get_language_options_input($cp->vars); + + if ($cp->vars['lang_options']) + { + if (!is_array($cp->vars['lang_options'])) + { + $cp->vars['lang_options'] = explode("\n", $cp->vars['lang_options']); + } + + if ($action != 'create') + { + $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . " + WHERE field_id = $field_id + AND lang_id = " . (int) $default_lang_id; + $db->sql_query($sql); + } + + foreach ($cp->vars['lang_options'] as $option_id => $value) + { + $sql_ary = array( + 'field_type' => $field_type, + 'lang_value' => $value + ); + + if ($action == 'create') + { + $sql_ary['field_id'] = $field_id; + $sql_ary['lang_id'] = $default_lang_id; + $sql_ary['option_id'] = (int) $option_id; + + $profile_sql[] = 'INSERT INTO ' . PROFILE_FIELDS_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + } + else + { + $this->update_insert(PROFILE_FIELDS_LANG_TABLE, $sql_ary, array( + 'field_id' => $field_id, + 'lang_id' => (int) $default_lang_id, + 'option_id' => (int) $option_id) + ); + } + } + } + + if (is_array($cp->vars['l_lang_options']) && count($cp->vars['l_lang_options'])) + { + $empty_lang = array(); + + foreach ($cp->vars['l_lang_options'] as $lang_id => $lang_ary) + { + if (!is_array($lang_ary)) + { + $lang_ary = explode("\n", $lang_ary); + } + + if (count($lang_ary) != count($cp->vars['lang_options'])) + { + $empty_lang[$lang_id] = true; + } + + if (!isset($empty_lang[$lang_id])) + { + if ($action != 'create') + { + $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . " + WHERE field_id = $field_id + AND lang_id = " . (int) $lang_id; + $db->sql_query($sql); + } + + foreach ($lang_ary as $option_id => $value) + { + $profile_lang_fields[] = array( + 'field_id' => (int) $field_id, + 'lang_id' => (int) $lang_id, + 'option_id' => (int) $option_id, + 'field_type' => $field_type, + 'lang_value' => $value + ); + } + } + } + + foreach ($empty_lang as $lang_id => $NULL) + { + $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . " + WHERE field_id = $field_id + AND lang_id = " . (int) $lang_id; + $db->sql_query($sql); + } + } + + foreach ($profile_lang as $sql) + { + if ($action == 'create') + { + $profile_sql[] = 'INSERT INTO ' . PROFILE_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql); + } + else + { + $lang_id = $sql['lang_id']; + unset($sql['lang_id'], $sql['field_id']); + + $this->update_insert(PROFILE_LANG_TABLE, $sql, array('lang_id' => (int) $lang_id, 'field_id' => $field_id)); + } + } + + if (count($profile_lang_fields)) + { + foreach ($profile_lang_fields as $sql) + { + if ($action == 'create') + { + $profile_sql[] = 'INSERT INTO ' . PROFILE_FIELDS_LANG_TABLE . ' ' . $db->sql_build_array('INSERT', $sql); + } + else + { + $lang_id = $sql['lang_id']; + $option_id = $sql['option_id']; + unset($sql['lang_id'], $sql['field_id'], $sql['option_id']); + + $this->update_insert(PROFILE_FIELDS_LANG_TABLE, $sql, array( + 'lang_id' => $lang_id, + 'field_id' => $field_id, + 'option_id' => $option_id) + ); + } + } + } + + $db->sql_transaction('begin'); + + if ($action == 'create') + { + foreach ($profile_sql as $sql) + { + $db->sql_query($sql); + } + } + + $db->sql_transaction('commit'); + + if ($action == 'edit') + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_PROFILE_FIELD_EDIT', false, array($cp->vars['field_ident'] . ':' . $cp->vars['lang_name'])); + trigger_error($user->lang['CHANGED_PROFILE_FIELD'] . adm_back_link($this->u_action)); + } + else + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_PROFILE_FIELD_CREATE', false, array(substr($field_ident, 3) . ':' . $cp->vars['lang_name'])); + trigger_error($user->lang['ADDED_PROFILE_FIELD'] . adm_back_link($this->u_action)); + } + } + + /** + * Update, then insert if not successfull + */ + function update_insert($table, $sql_ary, $where_fields) + { + global $db; + + $where_sql = array(); + $check_key = ''; + + foreach ($where_fields as $key => $value) + { + $check_key = (!$check_key) ? $key : $check_key; + $where_sql[] = $key . ' = ' . ((is_string($value)) ? "'" . $db->sql_escape($value) . "'" : (int) $value); + } + + if (!count($where_sql)) + { + return; + } + + $sql = "SELECT $check_key + FROM $table + WHERE " . implode(' AND ', $where_sql); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $sql_ary = array_merge($where_fields, $sql_ary); + + if (count($sql_ary)) + { + $db->sql_query("INSERT INTO $table " . $db->sql_build_array('INSERT', $sql_ary)); + } + } + else + { + if (count($sql_ary)) + { + $sql = "UPDATE $table SET " . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE ' . implode(' AND ', $where_sql); + $db->sql_query($sql); + } + } + } +} diff --git a/includes/acp/acp_prune.php b/includes/acp/acp_prune.php new file mode 100644 index 0000000..3eee4f7 --- /dev/null +++ b/includes/acp/acp_prune.php @@ -0,0 +1,587 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_prune +{ + var $u_action; + + function main($id, $mode) + { + global $user, $phpEx, $phpbb_root_path; + + $user->add_lang('acp/prune'); + + if (!function_exists('user_active_flip')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + switch ($mode) + { + case 'forums': + $this->tpl_name = 'acp_prune_forums'; + $this->page_title = 'ACP_PRUNE_FORUMS'; + $this->prune_forums($id, $mode); + break; + + case 'users': + $this->tpl_name = 'acp_prune_users'; + $this->page_title = 'ACP_PRUNE_USERS'; + $this->prune_users($id, $mode); + break; + } + } + + /** + * Prune forums + */ + function prune_forums($id, $mode) + { + global $db, $user, $auth, $template, $phpbb_log, $request, $phpbb_dispatcher; + + $all_forums = $request->variable('all_forums', 0); + $forum_id = $request->variable('f', array(0)); + $submit = (isset($_POST['submit'])) ? true : false; + + if ($all_forums) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + ORDER BY left_id'; + $result = $db->sql_query($sql); + + $forum_id = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_id[] = $row['forum_id']; + } + $db->sql_freeresult($result); + } + + if ($submit) + { + if (confirm_box(true)) + { + $prune_posted = $request->variable('prune_days', 0); + $prune_viewed = $request->variable('prune_vieweddays', 0); + $prune_all = (!$prune_posted && !$prune_viewed) ? true : false; + + $prune_flags = 0; + $prune_flags += ($request->variable('prune_old_polls', 0)) ? 2 : 0; + $prune_flags += ($request->variable('prune_announce', 0)) ? 4 : 0; + $prune_flags += ($request->variable('prune_sticky', 0)) ? 8 : 0; + + // Convert days to seconds for timestamp functions... + $prunedate_posted = time() - ($prune_posted * 86400); + $prunedate_viewed = time() - ($prune_viewed * 86400); + + $template->assign_vars(array( + 'S_PRUNED' => true) + ); + + $sql_forum = (count($forum_id)) ? ' AND ' . $db->sql_in_set('forum_id', $forum_id) : ''; + + // Get a list of forum's or the data for the forum that we are pruning. + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE . ' + WHERE forum_type = ' . FORUM_POST . " + $sql_forum + ORDER BY left_id ASC"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $prune_ids = array(); + $p_result['topics'] = 0; + $p_result['posts'] = 0; + $log_data = ''; + + do + { + if (!$auth->acl_get('f_list', $row['forum_id'])) + { + continue; + } + + if ($prune_all) + { + $p_result = prune($row['forum_id'], 'posted', time(), $prune_flags, false); + } + else + { + if ($prune_posted) + { + $return = prune($row['forum_id'], 'posted', $prunedate_posted, $prune_flags, false); + $p_result['topics'] += $return['topics']; + $p_result['posts'] += $return['posts']; + } + + if ($prune_viewed) + { + $return = prune($row['forum_id'], 'viewed', $prunedate_viewed, $prune_flags, false); + $p_result['topics'] += $return['topics']; + $p_result['posts'] += $return['posts']; + } + } + + $prune_ids[] = $row['forum_id']; + + $template->assign_block_vars('pruned', array( + 'FORUM_NAME' => $row['forum_name'], + 'NUM_TOPICS' => $p_result['topics'], + 'NUM_POSTS' => $p_result['posts']) + ); + + $log_data .= (($log_data != '') ? ', ' : '') . $row['forum_name']; + } + while ($row = $db->sql_fetchrow($result)); + + // Sync all pruned forums at once + sync('forum', 'forum_id', $prune_ids, true, true); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_PRUNE', false, array($log_data)); + } + $db->sql_freeresult($result); + + return; + } + else + { + $hidden_fields = array( + 'i' => $id, + 'mode' => $mode, + 'submit' => 1, + 'all_forums' => $all_forums, + 'f' => $forum_id, + + 'prune_days' => $request->variable('prune_days', 0), + 'prune_vieweddays' => $request->variable('prune_vieweddays', 0), + 'prune_old_polls' => $request->variable('prune_old_polls', 0), + 'prune_announce' => $request->variable('prune_announce', 0), + 'prune_sticky' => $request->variable('prune_sticky', 0), + ); + + /** + * Use this event to pass data from the prune form to the confirmation screen + * + * @event core.prune_forums_settings_confirm + * @var array hidden_fields Hidden fields that are passed through the confirm screen + * @since 3.2.2-RC1 + */ + $vars = array('hidden_fields'); + extract($phpbb_dispatcher->trigger_event('core.prune_forums_settings_confirm', compact($vars))); + + confirm_box(false, $user->lang['PRUNE_FORUM_CONFIRM'], build_hidden_fields($hidden_fields)); + } + } + + // If they haven't selected a forum for pruning yet then + // display a select box to use for pruning. + if (!count($forum_id)) + { + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'S_SELECT_FORUM' => true, + 'S_FORUM_OPTIONS' => make_forum_select(false, false, false)) + ); + } + else + { + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_id); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + + if (!$row) + { + $db->sql_freeresult($result); + trigger_error($user->lang['NO_FORUM'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $forum_list = $s_hidden_fields = ''; + do + { + $forum_list .= (($forum_list != '') ? ', ' : '') . '' . $row['forum_name'] . ''; + $s_hidden_fields .= ''; + } + while ($row = $db->sql_fetchrow($result)); + + $db->sql_freeresult($result); + + $l_selected_forums = (count($forum_id) == 1) ? 'SELECTED_FORUM' : 'SELECTED_FORUMS'; + + $template_data = array( + 'L_SELECTED_FORUMS' => $user->lang[$l_selected_forums], + 'U_ACTION' => $this->u_action, + 'U_BACK' => $this->u_action, + 'FORUM_LIST' => $forum_list, + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + ); + + /** + * Event to add/modify prune forums settings template data + * + * @event core.prune_forums_settings_template_data + * @var array template_data Array with form template data + * @since 3.2.2-RC1 + */ + $vars = array('template_data'); + extract($phpbb_dispatcher->trigger_event('core.prune_forums_settings_template_data', compact($vars))); + + $template->assign_vars($template_data); + } + } + + /** + * Prune users + */ + function prune_users($id, $mode) + { + global $db, $user, $auth, $template, $phpbb_log, $request; + global $phpbb_root_path, $phpbb_admin_path, $phpEx, $phpbb_container; + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $user->add_lang('memberlist'); + + $prune = (isset($_POST['prune'])) ? true : false; + + if ($prune) + { + $action = $request->variable('action', 'deactivate'); + $deleteposts = $request->variable('deleteposts', 0); + + if (confirm_box(true)) + { + $user_ids = $usernames = array(); + + $this->get_prune_users($user_ids, $usernames); + if (count($user_ids)) + { + if ($action == 'deactivate') + { + user_active_flip('deactivate', $user_ids); + $l_log = 'LOG_PRUNE_USER_DEAC'; + } + else if ($action == 'delete') + { + if ($deleteposts) + { + user_delete('remove', $user_ids); + + $l_log = 'LOG_PRUNE_USER_DEL_DEL'; + } + else + { + user_delete('retain', $user_ids, true); + + $l_log = 'LOG_PRUNE_USER_DEL_ANON'; + } + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $l_log, false, array(implode(', ', $usernames))); + $msg = $user->lang['USER_' . strtoupper($action) . '_SUCCESS']; + } + else + { + $msg = $user->lang['USER_PRUNE_FAILURE']; + } + + trigger_error($msg . adm_back_link($this->u_action)); + } + else + { + // We list the users which will be pruned... + $user_ids = $usernames = array(); + $this->get_prune_users($user_ids, $usernames); + + if (!count($user_ids)) + { + trigger_error($user->lang['USER_PRUNE_FAILURE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Assign to template + foreach ($user_ids as $user_id) + { + $template->assign_block_vars('users', array( + 'USERNAME' => $usernames[$user_id], + 'USER_ID' => $user_id, + 'U_PROFILE' => get_username_string('profile', $user_id, $usernames[$user_id]), + 'U_USER_ADMIN' => ($auth->acl_get('a_user')) ? append_sid("{$phpbb_admin_path}index.$phpEx", 'i=users&mode=overview&u=' . $user_id, true, $user->session_id) : '', + )); + } + + $template->assign_vars(array( + 'S_DEACTIVATE' => ($action == 'deactivate') ? true : false, + 'S_DELETE' => ($action == 'delete') ? true : false, + )); + + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'prune' => 1, + + 'deleteposts' => $request->variable('deleteposts', 0), + 'action' => $request->variable('action', ''), + )), 'confirm_body_prune.html'); + } + } + + $find_count = array('lt' => $user->lang['LESS_THAN'], 'eq' => $user->lang['EQUAL_TO'], 'gt' => $user->lang['MORE_THAN']); + $s_find_count = ''; + + foreach ($find_count as $key => $value) + { + $selected = ($key == 'eq') ? ' selected="selected"' : ''; + $s_find_count .= ''; + } + + $find_time = array('lt' => $user->lang['BEFORE'], 'gt' => $user->lang['AFTER']); + $s_find_active_time = ''; + foreach ($find_time as $key => $value) + { + $s_find_active_time .= ''; + } + + $sql = 'SELECT group_id, group_name + FROM ' . GROUPS_TABLE . ' + WHERE group_type <> ' . GROUP_SPECIAL . ' + ORDER BY group_name ASC'; + $result = $db->sql_query($sql); + + $s_group_list = ''; + while ($row = $db->sql_fetchrow($result)) + { + $s_group_list .= ''; + } + $db->sql_freeresult($result); + + if ($s_group_list) + { + // Only prepend the "All groups" option if there are groups, + // otherwise we don't want to display this option at all. + $s_group_list = '' . $s_group_list; + } + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'S_ACTIVE_OPTIONS' => $s_find_active_time, + 'S_GROUP_LIST' => $s_group_list, + 'S_COUNT_OPTIONS' => $s_find_count, + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=acp_prune&field=users'), + )); + } + + /** + * Get user_ids/usernames from those being pruned + */ + function get_prune_users(&$user_ids, &$usernames) + { + global $user, $db, $request; + + $users_by_name = $request->variable('users', '', true); + $users_by_id = $request->variable('user_ids', array(0)); + $group_id = $request->variable('group_id', 0); + $posts_on_queue = (trim($request->variable('posts_on_queue', '')) === '') ? false : $request->variable('posts_on_queue', 0); + + if ($users_by_name) + { + $users = explode("\n", $users_by_name); + $where_sql = ' AND ' . $db->sql_in_set('username_clean', array_map('utf8_clean_string', $users)); + } + else if (!empty($users_by_id)) + { + $user_ids = $users_by_id; + user_get_id_name($user_ids, $usernames); + + $where_sql = ' AND ' . $db->sql_in_set('user_id', $user_ids); + } + else + { + $username = $request->variable('username', '', true); + $email = $request->variable('email', ''); + + $active_select = $request->variable('active_select', 'lt'); + $count_select = $request->variable('count_select', 'eq'); + $queue_select = $request->variable('queue_select', 'gt'); + $joined_before = $request->variable('joined_before', ''); + $joined_after = $request->variable('joined_after', ''); + $active = $request->variable('active', ''); + + $count = ($request->variable('count', '') === '') ? false : $request->variable('count', 0); + + $active = ($active) ? explode('-', $active) : array(); + $joined_before = ($joined_before) ? explode('-', $joined_before) : array(); + $joined_after = ($joined_after) ? explode('-', $joined_after) : array(); + + // calculate the conditions required by the join time criteria + $joined_sql = ''; + if (!empty($joined_before) && !empty($joined_after)) + { + // if the two entered dates are equal, we need to adjust + // so that our time range is a full day instead of 1 second + if ($joined_after == $joined_before) + { + $joined_after[2] += 1; + } + + $joined_sql = ' AND user_regdate BETWEEN ' . gmmktime(0, 0, 0, (int) $joined_after[1], (int) $joined_after[2], (int) $joined_after[0]) . + ' AND ' . gmmktime(0, 0, 0, (int) $joined_before[1], (int) $joined_before[2], (int) $joined_before[0]); + } + else if (empty($joined_before) && !empty($joined_after)) + { + $joined_sql = ' AND user_regdate > ' . gmmktime(0, 0, 0, (int) $joined_after[1], (int) $joined_after[2], (int) $joined_after[0]); + } + else if (empty($joined_after) && !empty($joined_before)) + { + $joined_sql = ' AND user_regdate < ' . gmmktime(0, 0, 0, (int) $joined_before[1], (int) $joined_before[2], (int) $joined_before[0]); + } + // implicit else when both arrays are empty do nothing + + if ((count($active) && count($active) != 3) || (count($joined_before) && count($joined_before) != 3) || (count($joined_after) && count($joined_after) != 3)) + { + trigger_error($user->lang['WRONG_ACTIVE_JOINED_DATE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $key_match = array('lt' => '<', 'gt' => '>', 'eq' => '='); + + $where_sql = ''; + $where_sql .= ($username) ? ' AND username_clean ' . $db->sql_like_expression(str_replace('*', $db->get_any_char(), utf8_clean_string($username))) : ''; + $where_sql .= ($email) ? ' AND user_email ' . $db->sql_like_expression(str_replace('*', $db->get_any_char(), $email)) . ' ' : ''; + $where_sql .= $joined_sql; + $where_sql .= ($count !== false) ? " AND user_posts " . $key_match[$count_select] . ' ' . (int) $count . ' ' : ''; + + // First handle pruning of users who never logged in, last active date is 0000-00-00 + if (count($active) && (int) $active[0] == 0 && (int) $active[1] == 0 && (int) $active[2] == 0) + { + $where_sql .= ' AND user_lastvisit = 0'; + } + else if (count($active) && $active_select != 'lt') + { + $where_sql .= ' AND user_lastvisit ' . $key_match[$active_select] . ' ' . gmmktime(0, 0, 0, (int) $active[1], (int) $active[2], (int) $active[0]); + } + else if (count($active)) + { + $where_sql .= ' AND (user_lastvisit > 0 AND user_lastvisit < ' . gmmktime(0, 0, 0, (int) $active[1], (int) $active[2], (int) $active[0]) . ')'; + } + } + + // If no search criteria were provided, go no further. + if (!$where_sql && !$group_id && $posts_on_queue === false) + { + return; + } + + // Get bot ids + $sql = 'SELECT user_id + FROM ' . BOTS_TABLE; + $result = $db->sql_query($sql); + + $bot_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $bot_ids[] = $row['user_id']; + } + $db->sql_freeresult($result); + + // Protect the admin, do not prune if no options are given... + if ($where_sql) + { + // Do not prune founder members + $sql = 'SELECT user_id, username + FROM ' . USERS_TABLE . ' + WHERE user_id <> ' . ANONYMOUS . ' + AND user_type <> ' . USER_FOUNDER . " + $where_sql"; + $result = $db->sql_query($sql); + + $user_ids = $usernames = array(); + + while ($row = $db->sql_fetchrow($result)) + { + // Do not prune bots and the user currently pruning. + if ($row['user_id'] != $user->data['user_id'] && !in_array($row['user_id'], $bot_ids)) + { + $user_ids[] = $row['user_id']; + $usernames[$row['user_id']] = $row['username']; + } + } + $db->sql_freeresult($result); + } + + if ($group_id) + { + $sql = 'SELECT u.user_id, u.username + FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . ' u + WHERE ug.group_id = ' . (int) $group_id . ' + AND ug.user_id <> ' . ANONYMOUS . ' + AND u.user_type <> ' . USER_FOUNDER . ' + AND ug.user_pending = 0 + AND u.user_id = ug.user_id + ' . (!empty($user_ids) ? ' AND ' . $db->sql_in_set('ug.user_id', $user_ids) : ''); + $result = $db->sql_query($sql); + + // we're performing an intersection operation, so all the relevant users + // come from this most recent query (which was limited to the results of the + // previous query) + $user_ids = $usernames = array(); + while ($row = $db->sql_fetchrow($result)) + { + // Do not prune bots and the user currently pruning. + if ($row['user_id'] != $user->data['user_id'] && !in_array($row['user_id'], $bot_ids)) + { + $user_ids[] = $row['user_id']; + $usernames[$row['user_id']] = $row['username']; + } + } + $db->sql_freeresult($result); + } + + if ($posts_on_queue !== false) + { + $sql = 'SELECT u.user_id, u.username, COUNT(p.post_id) AS queue_posts + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE u.user_id <> ' . ANONYMOUS . ' + AND u.user_type <> ' . USER_FOUNDER . ' + AND ' . $db->sql_in_set('p.post_visibility', array(ITEM_UNAPPROVED, ITEM_REAPPROVE)) . ' + AND u.user_id = p.poster_id + ' . (!empty($user_ids) ? ' AND ' . $db->sql_in_set('p.poster_id', $user_ids) : '') . ' + GROUP BY p.poster_id + HAVING queue_posts ' . $key_match[$queue_select] . ' ' . $posts_on_queue; + $result = $db->sql_query($sql); + + // same intersection logic as the above group ID portion + $user_ids = $usernames = array(); + while ($row = $db->sql_fetchrow($result)) + { + // Do not prune bots and the user currently pruning. + if ($row['user_id'] != $user->data['user_id'] && !in_array($row['user_id'], $bot_ids)) + { + $user_ids[] = $row['user_id']; + $usernames[$row['user_id']] = $row['username']; + } + } + $db->sql_freeresult($result); + } + } +} diff --git a/includes/acp/acp_ranks.php b/includes/acp/acp_ranks.php new file mode 100644 index 0000000..4d2b64d --- /dev/null +++ b/includes/acp/acp_ranks.php @@ -0,0 +1,285 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_ranks +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $template, $cache, $request, $phpbb_dispatcher; + global $config, $phpbb_root_path, $phpbb_admin_path, $phpbb_log; + + $user->add_lang('acp/posting'); + + // Set up general vars + $action = $request->variable('action', ''); + $action = (isset($_POST['add'])) ? 'add' : $action; + $action = (isset($_POST['save'])) ? 'save' : $action; + $rank_id = $request->variable('id', 0); + + $this->tpl_name = 'acp_ranks'; + $this->page_title = 'ACP_MANAGE_RANKS'; + + $form_name = 'acp_ranks'; + add_form_key($form_name); + + switch ($action) + { + case 'save': + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); + } + $rank_title = $request->variable('title', '', true); + $special_rank = $request->variable('special_rank', 0); + $min_posts = ($special_rank) ? 0 : max(0, $request->variable('min_posts', 0)); + $rank_image = $request->variable('rank_image', ''); + + // The rank image has to be a jpg, gif or png + if ($rank_image != '' && !preg_match('#(\.gif|\.png|\.jpg|\.jpeg)$#i', $rank_image)) + { + $rank_image = ''; + } + + if (!$rank_title) + { + trigger_error($user->lang['NO_RANK_TITLE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql_ary = array( + 'rank_title' => $rank_title, + 'rank_special' => $special_rank, + 'rank_min' => $min_posts, + 'rank_image' => htmlspecialchars_decode($rank_image) + ); + + /** + * Modify the SQL array when saving a rank + * + * @event core.acp_ranks_save_modify_sql_ary + * @var int rank_id The ID of the rank (if available) + * @var array sql_ary Array with the rank's data + * @since 3.1.0-RC3 + */ + $vars = array('rank_id', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.acp_ranks_save_modify_sql_ary', compact($vars))); + + if ($rank_id) + { + $sql = 'UPDATE ' . RANKS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " WHERE rank_id = $rank_id"; + $message = $user->lang['RANK_UPDATED']; + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RANK_UPDATED', false, array($rank_title)); + } + else + { + $sql = 'INSERT INTO ' . RANKS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $message = $user->lang['RANK_ADDED']; + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RANK_ADDED', false, array($rank_title)); + } + $db->sql_query($sql); + + $cache->destroy('_ranks'); + + trigger_error($message . adm_back_link($this->u_action)); + + break; + + case 'delete': + + if (!$rank_id) + { + trigger_error($user->lang['MUST_SELECT_RANK'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (confirm_box(true)) + { + $sql = 'SELECT rank_title + FROM ' . RANKS_TABLE . ' + WHERE rank_id = ' . $rank_id; + $result = $db->sql_query($sql); + $rank_title = (string) $db->sql_fetchfield('rank_title'); + $db->sql_freeresult($result); + + $sql = 'DELETE FROM ' . RANKS_TABLE . " + WHERE rank_id = $rank_id"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_rank = 0 + WHERE user_rank = $rank_id"; + $db->sql_query($sql); + + $cache->destroy('_ranks'); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_RANK_REMOVED', false, array($rank_title)); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $user->lang['RANK_REMOVED'], + 'REFRESH_DATA' => array( + 'time' => 3 + ) + )); + } + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'rank_id' => $rank_id, + 'action' => 'delete', + ))); + } + + break; + + case 'edit': + case 'add': + + $ranks = $existing_imgs = array(); + + $sql = 'SELECT * + FROM ' . RANKS_TABLE . ' + ORDER BY rank_min ASC, rank_special ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $existing_imgs[] = $row['rank_image']; + + if ($action == 'edit' && $rank_id == $row['rank_id']) + { + $ranks = $row; + } + } + $db->sql_freeresult($result); + + $imglist = filelist($phpbb_root_path . $config['ranks_path'], ''); + $edit_img = $filename_list = ''; + + foreach ($imglist as $path => $img_ary) + { + sort($img_ary); + + foreach ($img_ary as $img) + { + $img = $path . $img; + + if ($ranks && $img == $ranks['rank_image']) + { + $selected = ' selected="selected"'; + $edit_img = $img; + } + else + { + $selected = ''; + } + + if (strlen($img) > 255) + { + continue; + } + + $filename_list .= ''; + } + } + + $filename_list = '' . $filename_list; + unset($existing_imgs, $imglist); + + $tpl_ary = array( + 'S_EDIT' => true, + 'U_BACK' => $this->u_action, + 'RANKS_PATH' => $phpbb_root_path . $config['ranks_path'], + 'U_ACTION' => $this->u_action . '&id=' . $rank_id, + + 'RANK_TITLE' => (isset($ranks['rank_title'])) ? $ranks['rank_title'] : '', + 'S_FILENAME_LIST' => $filename_list, + 'RANK_IMAGE' => ($edit_img) ? $phpbb_root_path . $config['ranks_path'] . '/' . $edit_img : htmlspecialchars($phpbb_admin_path) . 'images/spacer.gif', + 'S_SPECIAL_RANK' => (isset($ranks['rank_special']) && $ranks['rank_special']) ? true : false, + 'MIN_POSTS' => (isset($ranks['rank_min']) && !$ranks['rank_special']) ? $ranks['rank_min'] : 0, + ); + + /** + * Modify the template output array for editing/adding ranks + * + * @event core.acp_ranks_edit_modify_tpl_ary + * @var array ranks Array with the rank's data + * @var array tpl_ary Array with the rank's template data + * @since 3.1.0-RC3 + */ + $vars = array('ranks', 'tpl_ary'); + extract($phpbb_dispatcher->trigger_event('core.acp_ranks_edit_modify_tpl_ary', compact($vars))); + + $template->assign_vars($tpl_ary); + return; + + break; + } + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action) + ); + + $sql = 'SELECT * + FROM ' . RANKS_TABLE . ' + ORDER BY rank_special DESC, rank_min ASC, rank_title ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $rank_row = array( + 'S_RANK_IMAGE' => ($row['rank_image']) ? true : false, + 'S_SPECIAL_RANK' => ($row['rank_special']) ? true : false, + + 'RANK_IMAGE' => $phpbb_root_path . $config['ranks_path'] . '/' . $row['rank_image'], + 'RANK_TITLE' => $row['rank_title'], + 'MIN_POSTS' => $row['rank_min'], + + 'U_EDIT' => $this->u_action . '&action=edit&id=' . $row['rank_id'], + 'U_DELETE' => $this->u_action . '&action=delete&id=' . $row['rank_id'], + ); + + /** + * Modify the template output array for each listed rank + * + * @event core.acp_ranks_list_modify_rank_row + * @var array row Array with the rank's data + * @var array rank_row Array with the rank's template data + * @since 3.1.0-RC3 + */ + $vars = array('row', 'rank_row'); + extract($phpbb_dispatcher->trigger_event('core.acp_ranks_list_modify_rank_row', compact($vars))); + + $template->assign_block_vars('ranks', $rank_row); + } + $db->sql_freeresult($result); + + } +} diff --git a/includes/acp/acp_reasons.php b/includes/acp/acp_reasons.php new file mode 100644 index 0000000..dfb2ccb --- /dev/null +++ b/includes/acp/acp_reasons.php @@ -0,0 +1,394 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_reasons +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $template; + global $request, $phpbb_log; + + $user->add_lang(array('mcp', 'acp/posting')); + + // Set up general vars + $action = $request->variable('action', ''); + $submit = (isset($_POST['submit'])) ? true : false; + $reason_id = $request->variable('id', 0); + + $this->tpl_name = 'acp_reasons'; + $this->page_title = 'ACP_REASONS'; + + $form_name = 'acp_reason'; + add_form_key('acp_reason'); + + $error = array(); + + switch ($action) + { + case 'add': + case 'edit': + + $reason_row = array( + 'reason_title' => $request->variable('reason_title', '', true), + 'reason_description' => $request->variable('reason_description', '', true), + ); + + if ($submit) + { + if (!check_form_key($form_name)) + { + $error[] = $user->lang['FORM_INVALID']; + } + // Reason specified? + if (!$reason_row['reason_title'] || !$reason_row['reason_description']) + { + $error[] = $user->lang['NO_REASON_INFO']; + } + + $check_double = ($action == 'add') ? true : false; + + if ($action == 'edit') + { + $sql = 'SELECT reason_title + FROM ' . REPORTS_REASONS_TABLE . " + WHERE reason_id = $reason_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (strtolower($row['reason_title']) == 'other' || strtolower($reason_row['reason_title']) == 'other') + { + $reason_row['reason_title'] = 'other'; + } + + if ($row['reason_title'] != $reason_row['reason_title']) + { + $check_double = true; + } + } + + // Check for same reason if adding it... + if ($check_double) + { + $sql = 'SELECT reason_id + FROM ' . REPORTS_REASONS_TABLE . " + WHERE reason_title = '" . $db->sql_escape($reason_row['reason_title']) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row || ($action == 'add' && strtolower($reason_row['reason_title']) == 'other')) + { + $error[] = $user->lang['REASON_ALREADY_EXIST']; + } + } + + if (!count($error)) + { + // New reason? + if ($action == 'add') + { + // Get new order... + $sql = 'SELECT MAX(reason_order) as max_reason_order + FROM ' . REPORTS_REASONS_TABLE; + $result = $db->sql_query($sql); + $max_order = (int) $db->sql_fetchfield('max_reason_order'); + $db->sql_freeresult($result); + + $sql_ary = array( + 'reason_title' => (string) $reason_row['reason_title'], + 'reason_description' => (string) $reason_row['reason_description'], + 'reason_order' => $max_order + 1 + ); + + $db->sql_query('INSERT INTO ' . REPORTS_REASONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + + $log = 'ADDED'; + } + else if ($reason_id) + { + $sql_ary = array( + 'reason_title' => (string) $reason_row['reason_title'], + 'reason_description' => (string) $reason_row['reason_description'], + ); + + $db->sql_query('UPDATE ' . REPORTS_REASONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE reason_id = ' . $reason_id); + + $log = 'UPDATED'; + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_REASON_' . $log, false, array($reason_row['reason_title'])); + trigger_error($user->lang['REASON_' . $log] . adm_back_link($this->u_action)); + } + } + else if ($reason_id) + { + $sql = 'SELECT * + FROM ' . REPORTS_REASONS_TABLE . ' + WHERE reason_id = ' . $reason_id; + $result = $db->sql_query($sql); + $reason_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$reason_row) + { + trigger_error($user->lang['NO_REASON'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + $l_title = ($action == 'edit') ? 'EDIT' : 'ADD'; + + $translated = false; + + // If the reason is defined within the language file, we will use the localized version, else just use the database entry... + if (isset($user->lang['report_reasons']['TITLE'][strtoupper($reason_row['reason_title'])]) && isset($user->lang['report_reasons']['DESCRIPTION'][strtoupper($reason_row['reason_title'])])) + { + $translated = true; + } + + $template->assign_vars(array( + 'L_TITLE' => $user->lang['REASON_' . $l_title], + 'U_ACTION' => $this->u_action . "&id=$reason_id&action=$action", + 'U_BACK' => $this->u_action, + 'ERROR_MSG' => (count($error)) ? implode('
', $error) : '', + + 'REASON_TITLE' => $reason_row['reason_title'], + 'REASON_DESCRIPTION' => $reason_row['reason_description'], + + 'TRANSLATED_TITLE' => ($translated) ? $user->lang['report_reasons']['TITLE'][strtoupper($reason_row['reason_title'])] : '', + 'TRANSLATED_DESCRIPTION'=> ($translated) ? $user->lang['report_reasons']['DESCRIPTION'][strtoupper($reason_row['reason_title'])] : '', + + 'S_AVAILABLE_TITLES' => implode($user->lang['COMMA_SEPARATOR'], array_map('htmlspecialchars', array_keys($user->lang['report_reasons']['TITLE']))), + 'S_EDIT_REASON' => true, + 'S_TRANSLATED' => $translated, + 'S_ERROR' => (count($error)) ? true : false, + ) + ); + + return; + break; + + case 'delete': + + $sql = 'SELECT * + FROM ' . REPORTS_REASONS_TABLE . ' + WHERE reason_id = ' . $reason_id; + $result = $db->sql_query($sql); + $reason_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$reason_row) + { + trigger_error($user->lang['NO_REASON'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (strtolower($reason_row['reason_title']) == 'other') + { + trigger_error($user->lang['NO_REMOVE_DEFAULT_REASON'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Let the deletion be confirmed... + if (confirm_box(true)) + { + $sql = 'SELECT reason_id + FROM ' . REPORTS_REASONS_TABLE . " + WHERE LOWER(reason_title) = 'other'"; + $result = $db->sql_query($sql); + $other_reason_id = (int) $db->sql_fetchfield('reason_id'); + $db->sql_freeresult($result); + + switch ($db->get_sql_layer()) + { + // The ugly one! + case 'mysqli': + case 'mysql4': + case 'mysql': + // Change the reports using this reason to 'other' + $sql = 'UPDATE ' . REPORTS_TABLE . ' + SET reason_id = ' . $other_reason_id . ", report_text = CONCAT('" . $db->sql_escape($reason_row['reason_description']) . "\n\n', report_text) + WHERE reason_id = $reason_id"; + break; + + // Standard? What's that? + case 'mssql_odbc': + case 'mssqlnative': + // Change the reports using this reason to 'other' + $sql = "DECLARE @ptrval binary(16) + + SELECT @ptrval = TEXTPTR(report_text) + FROM " . REPORTS_TABLE . " + WHERE reason_id = " . $reason_id . " + + UPDATETEXT " . REPORTS_TABLE . ".report_text @ptrval 0 0 '" . $db->sql_escape($reason_row['reason_description']) . "\n\n' + + UPDATE " . REPORTS_TABLE . ' + SET reason_id = ' . $other_reason_id . " + WHERE reason_id = $reason_id"; + break; + + // Teh standard + case 'postgres': + case 'oracle': + case 'sqlite3': + // Change the reports using this reason to 'other' + $sql = 'UPDATE ' . REPORTS_TABLE . ' + SET reason_id = ' . $other_reason_id . ", report_text = '" . $db->sql_escape($reason_row['reason_description']) . "\n\n' || report_text + WHERE reason_id = $reason_id"; + break; + } + $db->sql_query($sql); + + $db->sql_query('DELETE FROM ' . REPORTS_REASONS_TABLE . ' WHERE reason_id = ' . $reason_id); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_REASON_REMOVED', false, array($reason_row['reason_title'])); + trigger_error($user->lang['REASON_REMOVED'] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'id' => $reason_id)) + ); + } + + break; + + case 'move_up': + case 'move_down': + + if (!check_link_hash($request->variable('hash', ''), 'acp_reasons')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT reason_order + FROM ' . REPORTS_REASONS_TABLE . " + WHERE reason_id = $reason_id"; + $result = $db->sql_query($sql); + $order = $db->sql_fetchfield('reason_order'); + $db->sql_freeresult($result); + + if ($order === false || ($order == 0 && $action == 'move_up')) + { + break; + } + $order = (int) $order; + $order_total = $order * 2 + (($action == 'move_up') ? -1 : 1); + + $sql = 'UPDATE ' . REPORTS_REASONS_TABLE . ' + SET reason_order = ' . $order_total . ' - reason_order + WHERE reason_order IN (' . $order . ', ' . (($action == 'move_up') ? $order - 1 : $order + 1) . ')'; + $db->sql_query($sql); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'success' => (bool) $db->sql_affectedrows(), + )); + } + break; + } + + // By default, check that order is valid and fix it if necessary + $sql = 'SELECT reason_id, reason_order + FROM ' . REPORTS_REASONS_TABLE . ' + ORDER BY reason_order'; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $order = 0; + do + { + ++$order; + + if ($row['reason_order'] != $order) + { + $sql = 'UPDATE ' . REPORTS_REASONS_TABLE . " + SET reason_order = $order + WHERE reason_id = {$row['reason_id']}"; + $db->sql_query($sql); + } + } + while ($row = $db->sql_fetchrow($result)); + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + ) + ); + + // Reason count + $sql = 'SELECT reason_id, COUNT(reason_id) AS reason_count + FROM ' . REPORTS_TABLE . ' + GROUP BY reason_id'; + $result = $db->sql_query($sql); + + $reason_count = array(); + while ($row = $db->sql_fetchrow($result)) + { + $reason_count[$row['reason_id']] = $row['reason_count']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT * + FROM ' . REPORTS_REASONS_TABLE . ' + ORDER BY reason_order ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $translated = false; + $other_reason = ($row['reason_title'] == 'other') ? true : false; + + // If the reason is defined within the language file, we will use the localized version, else just use the database entry... + if (isset($user->lang['report_reasons']['TITLE'][strtoupper($row['reason_title'])]) && isset($user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])])) + { + $row['reason_description'] = $user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])]; + $row['reason_title'] = $user->lang['report_reasons']['TITLE'][strtoupper($row['reason_title'])]; + + $translated = true; + } + + $template->assign_block_vars('reasons', array( + 'REASON_TITLE' => $row['reason_title'], + 'REASON_DESCRIPTION' => $row['reason_description'], + 'REASON_COUNT' => (isset($reason_count[$row['reason_id']])) ? $reason_count[$row['reason_id']] : 0, + + 'S_TRANSLATED' => $translated, + 'S_OTHER_REASON' => $other_reason, + + 'U_EDIT' => $this->u_action . '&action=edit&id=' . $row['reason_id'], + 'U_DELETE' => (!$other_reason) ? $this->u_action . '&action=delete&id=' . $row['reason_id'] : '', + 'U_MOVE_UP' => $this->u_action . '&action=move_up&id=' . $row['reason_id'] . '&hash=' . generate_link_hash('acp_reasons'), + 'U_MOVE_DOWN' => $this->u_action . '&action=move_down&id=' . $row['reason_id'] . '&hash=' . generate_link_hash('acp_reasons')) + ); + } + $db->sql_freeresult($result); + } +} diff --git a/includes/acp/acp_search.php b/includes/acp/acp_search.php new file mode 100644 index 0000000..538a28a --- /dev/null +++ b/includes/acp/acp_search.php @@ -0,0 +1,623 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_search +{ + var $u_action; + var $state; + var $search; + var $max_post_id; + var $batch_size = 100; + + function main($id, $mode) + { + global $user; + + $user->add_lang('acp/search'); + + // For some this may be of help... + @ini_set('memory_limit', '128M'); + + switch ($mode) + { + case 'settings': + $this->settings($id, $mode); + break; + + case 'index': + $this->index($id, $mode); + break; + } + } + + function settings($id, $mode) + { + global $user, $template, $phpbb_log, $request; + global $config, $phpbb_admin_path, $phpEx; + + $submit = (isset($_POST['submit'])) ? true : false; + + if ($submit && !check_link_hash($request->variable('hash', ''), 'acp_search')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $search_types = $this->get_search_types(); + + $settings = array( + 'search_interval' => 'float', + 'search_anonymous_interval' => 'float', + 'load_search' => 'bool', + 'limit_search_load' => 'float', + 'min_search_author_chars' => 'integer', + 'max_num_search_keywords' => 'integer', + 'search_store_results' => 'integer', + ); + + $search = null; + $error = false; + $search_options = ''; + foreach ($search_types as $type) + { + if ($this->init_search($type, $search, $error)) + { + continue; + } + + $name = $search->get_name(); + + $selected = ($config['search_type'] == $type) ? ' selected="selected"' : ''; + $identifier = substr($type, strrpos($type, '\\') + 1); + $search_options .= ""; + + if (method_exists($search, 'acp')) + { + $vars = $search->acp(); + + if (!$submit) + { + $template->assign_block_vars('backend', array( + 'NAME' => $name, + 'SETTINGS' => $vars['tpl'], + 'IDENTIFIER' => $identifier, + )); + } + else if (is_array($vars['config'])) + { + $settings = array_merge($settings, $vars['config']); + } + } + } + unset($search); + unset($error); + + $cfg_array = (isset($_REQUEST['config'])) ? $request->variable('config', array('' => ''), true) : array(); + $updated = $request->variable('updated', false); + + foreach ($settings as $config_name => $var_type) + { + if (!isset($cfg_array[$config_name])) + { + continue; + } + + // e.g. integer:4:12 (min 4, max 12) + $var_type = explode(':', $var_type); + + $config_value = $cfg_array[$config_name]; + settype($config_value, $var_type[0]); + + if (isset($var_type[1])) + { + $config_value = max($var_type[1], $config_value); + } + + if (isset($var_type[2])) + { + $config_value = min($var_type[2], $config_value); + } + + // only change config if anything was actually changed + if ($submit && ($config[$config_name] != $config_value)) + { + $config->set($config_name, $config_value); + $updated = true; + } + } + + if ($submit) + { + $extra_message = ''; + if ($updated) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_SEARCH'); + } + + if (isset($cfg_array['search_type']) && in_array($cfg_array['search_type'], $search_types, true) && ($cfg_array['search_type'] != $config['search_type'])) + { + $search = null; + $error = false; + + if (!$this->init_search($cfg_array['search_type'], $search, $error)) + { + if (confirm_box(true)) + { + if (!method_exists($search, 'init') || !($error = $search->init())) + { + $config->set('search_type', $cfg_array['search_type']); + + if (!$updated) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_SEARCH'); + } + $extra_message = '
' . $user->lang['SWITCHED_SEARCH_BACKEND'] . '
» ' . $user->lang['GO_TO_SEARCH_INDEX'] . ''; + } + else + { + trigger_error($error . adm_back_link($this->u_action), E_USER_WARNING); + } + } + else + { + confirm_box(false, $user->lang['CONFIRM_SEARCH_BACKEND'], build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'submit' => true, + 'updated' => $updated, + 'config' => array('search_type' => $cfg_array['search_type']), + ))); + } + } + else + { + trigger_error($error . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + $search = null; + $error = false; + if (!$this->init_search($config['search_type'], $search, $error)) + { + if ($updated) + { + if (method_exists($search, 'config_updated')) + { + if ($search->config_updated()) + { + trigger_error($error . adm_back_link($this->u_action), E_USER_WARNING); + } + } + } + } + else + { + trigger_error($error . adm_back_link($this->u_action), E_USER_WARNING); + } + + trigger_error($user->lang['CONFIG_UPDATED'] . $extra_message . adm_back_link($this->u_action)); + } + unset($cfg_array); + + $this->tpl_name = 'acp_search'; + $this->page_title = 'ACP_SEARCH_SETTINGS'; + + $template->assign_vars(array( + 'LIMIT_SEARCH_LOAD' => (float) $config['limit_search_load'], + 'MIN_SEARCH_AUTHOR_CHARS' => (int) $config['min_search_author_chars'], + 'SEARCH_INTERVAL' => (float) $config['search_interval'], + 'SEARCH_GUEST_INTERVAL' => (float) $config['search_anonymous_interval'], + 'SEARCH_STORE_RESULTS' => (int) $config['search_store_results'], + 'MAX_NUM_SEARCH_KEYWORDS' => (int) $config['max_num_search_keywords'], + + 'S_SEARCH_TYPES' => $search_options, + 'S_YES_SEARCH' => (bool) $config['load_search'], + 'S_SETTINGS' => true, + + 'U_ACTION' => $this->u_action . '&hash=' . generate_link_hash('acp_search')) + ); + } + + function index($id, $mode) + { + global $db, $user, $template, $phpbb_log, $request; + global $config, $phpbb_admin_path, $phpEx; + + $action = $request->variable('action', ''); + $this->state = explode(',', $config['search_indexing_state']); + + if (isset($_POST['cancel'])) + { + $action = ''; + $this->state = array(); + $this->save_state(); + } + $submit = $request->is_set_post('submit', false); + + if (!check_link_hash($request->variable('hash', ''), 'acp_search') && in_array($action, array('create', 'delete'))) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if ($action) + { + switch ($action) + { + case 'progress_bar': + $type = $request->variable('type', ''); + $this->display_progress_bar($type); + break; + + case 'delete': + $this->state[1] = 'delete'; + break; + + case 'create': + $this->state[1] = 'create'; + break; + + default: + trigger_error('NO_ACTION', E_USER_ERROR); + break; + } + + if (empty($this->state[0])) + { + $this->state[0] = $request->variable('search_type', ''); + } + + $this->search = null; + $error = false; + if ($this->init_search($this->state[0], $this->search, $error)) + { + trigger_error($error . adm_back_link($this->u_action), E_USER_WARNING); + } + $name = $this->search->get_name(); + + $action = &$this->state[1]; + + $this->max_post_id = $this->get_max_post_id(); + + $post_counter = (isset($this->state[2])) ? $this->state[2] : 0; + $this->state[2] = &$post_counter; + $this->save_state(); + + switch ($action) + { + case 'delete': + if (method_exists($this->search, 'delete_index')) + { + // pass a reference to myself so the $search object can make use of save_state() and attributes + if ($error = $this->search->delete_index($this, append_sid("{$phpbb_admin_path}index.$phpEx", "i=$id&mode=$mode&action=delete&hash=" . generate_link_hash('acp_search'), false))) + { + $this->state = array(''); + $this->save_state(); + trigger_error($error . adm_back_link($this->u_action) . $this->close_popup_js(), E_USER_WARNING); + } + } + else + { + $starttime = microtime(true); + $row_count = 0; + while (still_on_time() && $post_counter <= $this->max_post_id) + { + $sql = 'SELECT post_id, poster_id, forum_id + FROM ' . POSTS_TABLE . ' + WHERE post_id >= ' . (int) ($post_counter + 1) . ' + AND post_id <= ' . (int) ($post_counter + $this->batch_size); + $result = $db->sql_query($sql); + + $ids = $posters = $forum_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $ids[] = $row['post_id']; + $posters[] = $row['poster_id']; + $forum_ids[] = $row['forum_id']; + } + $db->sql_freeresult($result); + $row_count += count($ids); + + if (count($ids)) + { + $this->search->index_remove($ids, $posters, $forum_ids); + } + + $post_counter += $this->batch_size; + } + // save the current state + $this->save_state(); + + if ($post_counter <= $this->max_post_id) + { + $totaltime = microtime(true) - $starttime; + $rows_per_second = $row_count / $totaltime; + meta_refresh(1, append_sid($this->u_action . '&action=delete&skip_rows=' . $post_counter . '&hash=' . generate_link_hash('acp_search'))); + trigger_error($user->lang('SEARCH_INDEX_DELETE_REDIRECT', (int) $row_count, $post_counter) . $user->lang('SEARCH_INDEX_DELETE_REDIRECT_RATE', $rows_per_second)); + } + } + + $this->search->tidy(); + + $this->state = array(''); + $this->save_state(); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_SEARCH_INDEX_REMOVED', false, array($name)); + trigger_error($user->lang['SEARCH_INDEX_REMOVED'] . adm_back_link($this->u_action) . $this->close_popup_js()); + break; + + case 'create': + if (method_exists($this->search, 'create_index')) + { + // pass a reference to acp_search so the $search object can make use of save_state() and attributes + if ($error = $this->search->create_index($this, append_sid("{$phpbb_admin_path}index.$phpEx", "i=$id&mode=$mode&action=create", false))) + { + $this->state = array(''); + $this->save_state(); + trigger_error($error . adm_back_link($this->u_action) . $this->close_popup_js(), E_USER_WARNING); + } + } + else + { + $sql = 'SELECT forum_id, enable_indexing + FROM ' . FORUMS_TABLE; + $result = $db->sql_query($sql, 3600); + + while ($row = $db->sql_fetchrow($result)) + { + $forums[$row['forum_id']] = (bool) $row['enable_indexing']; + } + $db->sql_freeresult($result); + + $starttime = microtime(true); + $row_count = 0; + while (still_on_time() && $post_counter <= $this->max_post_id) + { + $sql = 'SELECT post_id, post_subject, post_text, poster_id, forum_id + FROM ' . POSTS_TABLE . ' + WHERE post_id >= ' . (int) ($post_counter + 1) . ' + AND post_id <= ' . (int) ($post_counter + $this->batch_size); + $result = $db->sql_query($sql); + + $buffer = $db->sql_buffer_nested_transactions(); + + if ($buffer) + { + $rows = $db->sql_fetchrowset($result); + $rows[] = false; // indicate end of array for while loop below + + $db->sql_freeresult($result); + } + + $i = 0; + while ($row = ($buffer ? $rows[$i++] : $db->sql_fetchrow($result))) + { + // Indexing enabled for this forum + if (isset($forums[$row['forum_id']]) && $forums[$row['forum_id']]) + { + $this->search->index('post', $row['post_id'], $row['post_text'], $row['post_subject'], $row['poster_id'], $row['forum_id']); + } + $row_count++; + } + if (!$buffer) + { + $db->sql_freeresult($result); + } + + $post_counter += $this->batch_size; + } + // save the current state + $this->save_state(); + + // pretend the number of posts was as big as the number of ids we indexed so far + // just an estimation as it includes deleted posts + $num_posts = $config['num_posts']; + $config['num_posts'] = min($config['num_posts'], $post_counter); + $this->search->tidy(); + $config['num_posts'] = $num_posts; + + if ($post_counter <= $this->max_post_id) + { + $totaltime = microtime(true) - $starttime; + $rows_per_second = $row_count / $totaltime; + meta_refresh(1, append_sid($this->u_action . '&action=create&skip_rows=' . $post_counter . '&hash=' . generate_link_hash('acp_search'))); + trigger_error($user->lang('SEARCH_INDEX_CREATE_REDIRECT', (int) $row_count, $post_counter) . $user->lang('SEARCH_INDEX_CREATE_REDIRECT_RATE', $rows_per_second)); + } + } + + $this->search->tidy(); + + $this->state = array(''); + $this->save_state(); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_SEARCH_INDEX_CREATED', false, array($name)); + trigger_error($user->lang['SEARCH_INDEX_CREATED'] . adm_back_link($this->u_action) . $this->close_popup_js()); + break; + } + } + + $search_types = $this->get_search_types(); + + $search = null; + $error = false; + foreach ($search_types as $type) + { + if ($this->init_search($type, $search, $error) || !method_exists($search, 'index_created')) + { + continue; + } + + $name = $search->get_name(); + + $data = array(); + if (method_exists($search, 'index_stats')) + { + $data = $search->index_stats(); + } + + $statistics = array(); + foreach ($data as $statistic => $value) + { + $n = count($statistics); + if ($n && count($statistics[$n - 1]) < 3) + { + $statistics[$n - 1] += array('statistic_2' => $statistic, 'value_2' => $value); + } + else + { + $statistics[] = array('statistic_1' => $statistic, 'value_1' => $value); + } + } + + $template->assign_block_vars('backend', array( + 'L_NAME' => $name, + 'NAME' => $type, + + 'S_ACTIVE' => ($type == $config['search_type']) ? true : false, + 'S_HIDDEN_FIELDS' => build_hidden_fields(array('search_type' => $type)), + 'S_INDEXED' => (bool) $search->index_created(), + 'S_STATS' => (bool) count($statistics)) + ); + + foreach ($statistics as $statistic) + { + $template->assign_block_vars('backend.data', array( + 'STATISTIC_1' => $statistic['statistic_1'], + 'VALUE_1' => $statistic['value_1'], + 'STATISTIC_2' => (isset($statistic['statistic_2'])) ? $statistic['statistic_2'] : '', + 'VALUE_2' => (isset($statistic['value_2'])) ? $statistic['value_2'] : '') + ); + } + } + unset($search); + unset($error); + unset($statistics); + unset($data); + + $this->tpl_name = 'acp_search'; + $this->page_title = 'ACP_SEARCH_INDEX'; + + $template->assign_vars(array( + 'S_INDEX' => true, + 'U_ACTION' => $this->u_action . '&hash=' . generate_link_hash('acp_search'), + 'U_PROGRESS_BAR' => append_sid("{$phpbb_admin_path}index.$phpEx", "i=$id&mode=$mode&action=progress_bar"), + 'UA_PROGRESS_BAR' => addslashes(append_sid("{$phpbb_admin_path}index.$phpEx", "i=$id&mode=$mode&action=progress_bar")), + )); + + if (isset($this->state[1])) + { + $template->assign_vars(array( + 'S_CONTINUE_INDEXING' => $this->state[1], + 'U_CONTINUE_INDEXING' => $this->u_action . '&action=' . $this->state[1] . '&hash=' . generate_link_hash('acp_search'), + 'L_CONTINUE' => ($this->state[1] == 'create') ? $user->lang['CONTINUE_INDEXING'] : $user->lang['CONTINUE_DELETING_INDEX'], + 'L_CONTINUE_EXPLAIN' => ($this->state[1] == 'create') ? $user->lang['CONTINUE_INDEXING_EXPLAIN'] : $user->lang['CONTINUE_DELETING_INDEX_EXPLAIN']) + ); + } + } + + function display_progress_bar($type) + { + global $template, $user; + + $l_type = ($type == 'create') ? 'INDEXING_IN_PROGRESS' : 'DELETING_INDEX_IN_PROGRESS'; + + adm_page_header($user->lang[$l_type]); + + $template->set_filenames(array( + 'body' => 'progress_bar.html') + ); + + $template->assign_vars(array( + 'L_PROGRESS' => $user->lang[$l_type], + 'L_PROGRESS_EXPLAIN' => $user->lang[$l_type . '_EXPLAIN']) + ); + + adm_page_footer(); + } + + function close_popup_js() + { + return "\n"; + } + + function get_search_types() + { + global $phpbb_extension_manager; + + $finder = $phpbb_extension_manager->get_finder(); + + return $finder + ->extension_suffix('_backend') + ->extension_directory('/search') + ->core_path('phpbb/search/') + ->get_classes(); + } + + function get_max_post_id() + { + global $db; + + $sql = 'SELECT MAX(post_id) as max_post_id + FROM '. POSTS_TABLE; + $result = $db->sql_query($sql); + $max_post_id = (int) $db->sql_fetchfield('max_post_id'); + $db->sql_freeresult($result); + + return $max_post_id; + } + + function save_state($state = false) + { + global $config; + + if ($state) + { + $this->state = $state; + } + + ksort($this->state); + + $config->set('search_indexing_state', implode(',', $this->state), true); + } + + /** + * Initialises a search backend object + * + * @return false if no error occurred else an error message + */ + function init_search($type, &$search, &$error) + { + global $phpbb_root_path, $phpEx, $user, $auth, $config, $db, $phpbb_dispatcher; + + if (!class_exists($type) || !method_exists($type, 'keyword_search')) + { + $error = $user->lang['NO_SUCH_SEARCH_MODULE']; + return $error; + } + + $error = false; + $search = new $type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher); + + return $error; + } +} diff --git a/includes/acp/acp_styles.php b/includes/acp/acp_styles.php new file mode 100644 index 0000000..1bf5a3c --- /dev/null +++ b/includes/acp/acp_styles.php @@ -0,0 +1,1373 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_styles +{ + public $u_action; + + protected $u_base_action; + protected $s_hidden_fields; + protected $mode; + protected $styles_path; + protected $styles_path_absolute = 'styles'; + protected $default_style = 0; + protected $styles_list_cols = 0; + protected $reserved_style_names = array('adm', 'admin', 'all'); + + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\template\template */ + protected $template; + + /** @var \phpbb\request\request_interface */ + protected $request; + + /** @var \phpbb\cache\driver\driver_interface */ + protected $cache; + + /** @var \phpbb\auth\auth */ + protected $auth; + + /** @var \phpbb\textformatter\cache_interface */ + protected $text_formatter_cache; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var \phpbb\event\dispatcher_interface */ + protected $dispatcher; + + public function main($id, $mode) + { + global $db, $user, $phpbb_admin_path, $phpbb_root_path, $phpEx, $template, $request, $cache, $auth, $config, $phpbb_dispatcher, $phpbb_container; + + $this->db = $db; + $this->user = $user; + $this->template = $template; + $this->request = $request; + $this->cache = $cache; + $this->auth = $auth; + $this->text_formatter_cache = $phpbb_container->get('text_formatter.cache'); + $this->config = $config; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $phpEx; + $this->dispatcher = $phpbb_dispatcher; + + $this->default_style = $config['default_style']; + $this->styles_path = $this->phpbb_root_path . $this->styles_path_absolute . '/'; + + $this->u_base_action = append_sid("{$phpbb_admin_path}index.{$this->php_ext}", "i={$id}"); + $this->s_hidden_fields = array( + 'mode' => $mode, + ); + + $this->user->add_lang('acp/styles'); + + $this->tpl_name = 'acp_styles'; + $this->page_title = 'ACP_CAT_STYLES'; + $this->mode = $mode; + + $action = $this->request->variable('action', ''); + $post_actions = array('install', 'activate', 'deactivate', 'uninstall'); + + foreach ($post_actions as $key) + { + if ($this->request->is_set_post($key)) + { + $action = $key; + } + } + + // The uninstall action uses confirm_box() to verify the validity of the request, + // so there is no need to check for a valid token here. + if (in_array($action, $post_actions) && $action != 'uninstall') + { + $is_valid_request = check_link_hash($request->variable('hash', ''), $action) || check_form_key('styles_management'); + + if (!$is_valid_request) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + if ($action != '') + { + $this->s_hidden_fields['action'] = $action; + } + + $this->template->assign_vars(array( + 'U_ACTION' => $this->u_base_action, + 'S_HIDDEN_FIELDS' => build_hidden_fields($this->s_hidden_fields) + ) + ); + + /** + * Run code before ACP styles action execution + * + * @event core.acp_styles_action_before + * @var int id Module ID + * @var string mode Active module + * @var string action Module that should be run + * @since 3.1.7-RC1 + */ + $vars = array('id', 'mode', 'action'); + extract($this->dispatcher->trigger_event('core.acp_styles_action_before', compact($vars))); + + // Execute actions + switch ($action) + { + case 'install': + $this->action_install(); + return; + case 'uninstall': + $this->action_uninstall(); + return; + case 'activate': + $this->action_activate(); + return; + case 'deactivate': + $this->action_deactivate(); + return; + case 'details': + $this->action_details(); + return; + default: + $this->frontend(); + } + } + + /** + * Main page + */ + protected function frontend() + { + add_form_key('styles_management'); + + // Check mode + switch ($this->mode) + { + case 'style': + $this->welcome_message('ACP_STYLES', 'ACP_STYLES_EXPLAIN'); + $this->show_installed(); + return; + case 'install': + $this->welcome_message('INSTALL_STYLES', 'INSTALL_STYLES_EXPLAIN'); + $this->show_available(); + return; + } + trigger_error($this->user->lang['NO_MODE'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + /** + * Install style(s) + */ + protected function action_install() + { + // Get list of styles to install + $dirs = $this->request_vars('dir', '', true); + + // Get list of styles that can be installed + $styles = $this->find_available(false); + + // Install each style + $messages = array(); + $installed_names = array(); + $installed_dirs = array(); + foreach ($dirs as $dir) + { + if (in_array($dir, $this->reserved_style_names)) + { + $messages[] = $this->user->lang('STYLE_NAME_RESERVED', htmlspecialchars($dir)); + continue; + } + + $found = false; + foreach ($styles as &$style) + { + // Check if: + // 1. Directory matches directory we are looking for + // 2. Style is not installed yet + // 3. Style with same name or directory hasn't been installed already within this function + if ($style['style_path'] == $dir && empty($style['_installed']) && !in_array($style['style_path'], $installed_dirs) && !in_array($style['style_name'], $installed_names)) + { + // Install style + $style['style_active'] = 1; + $style['style_id'] = $this->install_style($style); + $style['_installed'] = true; + $found = true; + $installed_names[] = $style['style_name']; + $installed_dirs[] = $style['style_path']; + $messages[] = sprintf($this->user->lang['STYLE_INSTALLED'], htmlspecialchars($style['style_name'])); + } + } + if (!$found) + { + $messages[] = sprintf($this->user->lang['STYLE_NOT_INSTALLED'], htmlspecialchars($dir)); + } + } + + // Invalidate the text formatter's cache for the new styles to take effect + if (!empty($installed_names)) + { + $this->text_formatter_cache->invalidate(); + } + + // Show message + if (!count($messages)) + { + trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + $message = implode('
', $messages); + $message .= '

« ' . $this->user->lang('STYLE_INSTALLED_RETURN_INSTALLED_STYLES') . ''; + $message .= '

» ' . $this->user->lang('STYLE_INSTALLED_RETURN_UNINSTALLED_STYLES') . ''; + trigger_error($message, E_USER_NOTICE); + } + + /** + * Confirm styles removal + */ + protected function action_uninstall() + { + // Get list of styles to uninstall + $ids = $this->request_vars('id', 0, true); + + // Check if confirmation box was submitted + if (confirm_box(true)) + { + // Uninstall + $this->action_uninstall_confirmed($ids, $this->request->variable('confirm_delete_files', false)); + return; + } + + // Confirm box + $s_hidden = build_hidden_fields(array( + 'action' => 'uninstall', + 'ids' => $ids + )); + $this->template->assign_var('S_CONFIRM_DELETE', true); + confirm_box(false, $this->user->lang['CONFIRM_UNINSTALL_STYLES'], $s_hidden, 'acp_styles.html'); + + // Canceled - show styles list + $this->frontend(); + } + + /** + * Uninstall styles(s) + * + * @param array $ids List of style IDs + * @param bool $delete_files If true, script will attempt to remove files for selected styles + */ + protected function action_uninstall_confirmed($ids, $delete_files) + { + global $user, $phpbb_log; + + $default = $this->default_style; + $uninstalled = array(); + $messages = array(); + + // Check styles list + foreach ($ids as $id) + { + if (!$id) + { + trigger_error($this->user->lang['INVALID_STYLE_ID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + if ($id == $default) + { + trigger_error($this->user->lang['UNINSTALL_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING); + } + $uninstalled[$id] = false; + } + + // Order by reversed style_id, so parent styles would be removed after child styles + // This way parent and child styles can be removed in same function call + $sql = 'SELECT * + FROM ' . STYLES_TABLE . ' + WHERE style_id IN (' . implode(', ', $ids) . ') + ORDER BY style_id DESC'; + $result = $this->db->sql_query($sql); + + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + // Uinstall each style + $uninstalled = array(); + foreach ($rows as $style) + { + $result = $this->uninstall_style($style, $delete_files); + + if (is_string($result)) + { + $messages[] = $result; + continue; + } + $messages[] = sprintf($this->user->lang['STYLE_UNINSTALLED'], $style['style_name']); + $uninstalled[] = $style['style_name']; + + // Attempt to delete files + if ($delete_files) + { + $messages[] = sprintf($this->user->lang[$this->delete_style_files($style['style_path']) ? 'DELETE_STYLE_FILES_SUCCESS' : 'DELETE_STYLE_FILES_FAILED'], $style['style_name']); + } + } + + if (empty($messages)) + { + // Nothing to uninstall? + trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Log action + if (count($uninstalled)) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_DELETE', false, array(implode(', ', $uninstalled))); + } + + // Clear cache + $this->cache->purge(); + + // Show message + trigger_error(implode('
', $messages) . adm_back_link($this->u_action), E_USER_NOTICE); + } + + /** + * Activate styles + */ + protected function action_activate() + { + // Get list of styles to activate + $ids = $this->request_vars('id', 0, true); + + // Activate styles + $sql = 'UPDATE ' . STYLES_TABLE . ' + SET style_active = 1 + WHERE style_id IN (' . implode(', ', $ids) . ')'; + $this->db->sql_query($sql); + + // Purge cache + $this->cache->destroy('sql', STYLES_TABLE); + + // Show styles list + $this->frontend(); + } + + /** + * Deactivate styles + */ + protected function action_deactivate() + { + // Get list of styles to deactivate + $ids = $this->request_vars('id', 0, true); + + // Check for default style + foreach ($ids as $id) + { + if ($id == $this->default_style) + { + trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + // Reset default style for users who use selected styles + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_style = ' . (int) $this->default_style . ' + WHERE user_style IN (' . implode(', ', $ids) . ')'; + $this->db->sql_query($sql); + + // Deactivate styles + $sql = 'UPDATE ' . STYLES_TABLE . ' + SET style_active = 0 + WHERE style_id IN (' . implode(', ', $ids) . ')'; + $this->db->sql_query($sql); + + // Purge cache + $this->cache->destroy('sql', STYLES_TABLE); + + // Show styles list + $this->frontend(); + } + + /** + * Show style details + */ + protected function action_details() + { + global $user, $phpbb_log; + + $id = $this->request->variable('id', 0); + if (!$id) + { + trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Get all styles + $styles = $this->get_styles(); + usort($styles, array($this, 'sort_styles')); + + // Find current style + $style = false; + foreach ($styles as $row) + { + if ($row['style_id'] == $id) + { + $style = $row; + break; + } + } + + if ($style === false) + { + trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Read style configuration file + $style_cfg = $this->read_style_cfg($style['style_path']); + + // Find all available parent styles + $list = $this->find_possible_parents($styles, $id); + + // Add form key + $form_key = 'acp_styles'; + add_form_key($form_key); + + // Change data + if ($this->request->variable('update', false)) + { + if (!check_form_key($form_key)) + { + trigger_error($this->user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $update = array( + 'style_name' => trim($this->request->variable('style_name', $style['style_name'])), + 'style_parent_id' => $this->request->variable('style_parent', (int) $style['style_parent_id']), + 'style_active' => $this->request->variable('style_active', (int) $style['style_active']), + ); + $update_action = $this->u_action . '&action=details&id=' . $id; + + // Check style name + if ($update['style_name'] != $style['style_name']) + { + if (!strlen($update['style_name'])) + { + trigger_error($this->user->lang['STYLE_ERR_STYLE_NAME'] . adm_back_link($update_action), E_USER_WARNING); + } + foreach ($styles as $row) + { + if ($row['style_name'] == $update['style_name']) + { + trigger_error($this->user->lang['STYLE_ERR_NAME_EXIST'] . adm_back_link($update_action), E_USER_WARNING); + } + } + } + else + { + unset($update['style_name']); + } + + // Check parent style id + if ($update['style_parent_id'] != $style['style_parent_id']) + { + if ($update['style_parent_id'] != 0) + { + $found = false; + foreach ($list as $row) + { + if ($row['style_id'] == $update['style_parent_id']) + { + $found = true; + $update['style_parent_tree'] = ($row['style_parent_tree'] != '' ? $row['style_parent_tree'] . '/' : '') . $row['style_path']; + break; + } + } + if (!$found) + { + trigger_error($this->user->lang['STYLE_ERR_INVALID_PARENT'] . adm_back_link($update_action), E_USER_WARNING); + } + } + else + { + $update['style_parent_tree'] = ''; + } + } + else + { + unset($update['style_parent_id']); + } + + // Check style_active + if ($update['style_active'] != $style['style_active']) + { + if (!$update['style_active'] && $this->default_style == $style['style_id']) + { + trigger_error($this->user->lang['DEACTIVATE_DEFAULT'] . adm_back_link($update_action), E_USER_WARNING); + } + } + else + { + unset($update['style_active']); + } + + // Update data + if (count($update)) + { + $sql = 'UPDATE ' . STYLES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $update) . " + WHERE style_id = $id"; + $this->db->sql_query($sql); + + $style = array_merge($style, $update); + + if (isset($update['style_parent_id'])) + { + // Update styles tree + $styles = $this->get_styles(); + if ($this->update_styles_tree($styles, $style)) + { + // Something was changed in styles tree, purge all cache + $this->cache->purge(); + } + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_EDIT_DETAILS', false, array($style['style_name'])); + } + + // Update default style + $default = $this->request->variable('style_default', 0); + if ($default) + { + if (!$style['style_active']) + { + trigger_error($this->user->lang['STYLE_DEFAULT_CHANGE_INACTIVE'] . adm_back_link($update_action), E_USER_WARNING); + } + $this->config->set('default_style', $id); + $this->cache->purge(); + } + + // Show styles list + $this->frontend(); + return; + } + + // Show page title + $this->welcome_message('ACP_STYLES', null); + + // Show parent styles + foreach ($list as $row) + { + $this->template->assign_block_vars('parent_styles', array( + 'STYLE_ID' => $row['style_id'], + 'STYLE_NAME' => htmlspecialchars($row['style_name']), + 'LEVEL' => $row['level'], + 'SPACER' => str_repeat('  ', $row['level']), + ) + ); + } + + // Show style details + $this->template->assign_vars(array( + 'S_STYLE_DETAILS' => true, + 'STYLE_ID' => $style['style_id'], + 'STYLE_NAME' => htmlspecialchars($style['style_name']), + 'STYLE_PATH' => htmlspecialchars($style['style_path']), + 'STYLE_VERSION' => htmlspecialchars($style_cfg['style_version']), + 'STYLE_COPYRIGHT' => strip_tags($style['style_copyright']), + 'STYLE_PARENT' => $style['style_parent_id'], + 'S_STYLE_ACTIVE' => $style['style_active'], + 'S_STYLE_DEFAULT' => ($style['style_id'] == $this->default_style) + ) + ); + } + + /** + * List installed styles + */ + protected function show_installed() + { + // Get all installed styles + $styles = $this->get_styles(); + + if (!count($styles)) + { + trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + usort($styles, array($this, 'sort_styles')); + + // Get users + $users = $this->get_users(); + + // Add users counter to rows + foreach ($styles as &$style) + { + $style['_users'] = isset($users[$style['style_id']]) ? $users[$style['style_id']] : 0; + } + + // Set up styles list variables + // Addons should increase this number and update template variable + $this->styles_list_cols = 4; + $this->template->assign_var('STYLES_LIST_COLS', $this->styles_list_cols); + + // Show styles list + $this->show_styles_list($styles, 0, 0); + + // Show styles with invalid inherits_id + foreach ($styles as $style) + { + if (empty($style['_shown'])) + { + $style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($style['style_parent_tree'])); + $this->list_style($style, 0); + } + } + + // Add buttons + $this->template->assign_block_vars('extra_actions', array( + 'ACTION_NAME' => 'activate', + 'L_ACTION' => $this->user->lang['STYLE_ACTIVATE'], + ) + ); + + $this->template->assign_block_vars('extra_actions', array( + 'ACTION_NAME' => 'deactivate', + 'L_ACTION' => $this->user->lang['STYLE_DEACTIVATE'], + ) + ); + + if (isset($this->style_counters) && $this->style_counters['total'] > 1) + { + $this->template->assign_block_vars('extra_actions', array( + 'ACTION_NAME' => 'uninstall', + 'L_ACTION' => $this->user->lang['STYLE_UNINSTALL'], + ) + ); + } + } + + /** + * Show list of styles that can be installed + */ + protected function show_available() + { + // Get list of styles + $styles = $this->find_available(true); + + // Show styles + if (empty($styles)) + { + trigger_error($this->user->lang['NO_UNINSTALLED_STYLE'] . adm_back_link($this->u_base_action), E_USER_NOTICE); + } + + usort($styles, array($this, 'sort_styles')); + + $this->styles_list_cols = 3; + $this->template->assign_vars(array( + 'STYLES_LIST_COLS' => $this->styles_list_cols, + 'STYLES_LIST_HIDE_COUNT' => true + ) + ); + + // Show styles + foreach ($styles as &$style) + { + // Check if style has a parent style in styles list + $has_parent = false; + if ($style['_inherit_name'] != '') + { + foreach ($styles as $parent_style) + { + if ($parent_style['style_name'] == $style['_inherit_name'] && empty($parent_style['_shown'])) + { + // Show parent style first + $has_parent = true; + } + } + } + if (!$has_parent) + { + $this->list_style($style, 0); + $this->show_available_child_styles($styles, $style['style_name'], 1); + } + } + + // Show styles that do not have parent style in styles list + foreach ($styles as $style) + { + if (empty($style['_shown'])) + { + $this->list_style($style, 0); + } + } + + // Add button + if (isset($this->style_counters) && $this->style_counters['caninstall'] > 0) + { + $this->template->assign_block_vars('extra_actions', array( + 'ACTION_NAME' => 'install', + 'L_ACTION' => $this->user->lang['INSTALL_STYLES'], + ) + ); + } + } + + /** + * Find styles available for installation + * + * @param bool $all if true, function will return all installable styles. if false, function will return only styles that can be installed + * @return array List of styles + */ + protected function find_available($all) + { + // Get list of installed styles + $installed = $this->get_styles(); + + $installed_dirs = array(); + $installed_names = array(); + foreach ($installed as $style) + { + $installed_dirs[] = $style['style_path']; + $installed_names[$style['style_name']] = array( + 'path' => $style['style_path'], + 'id' => $style['style_id'], + 'parent' => $style['style_parent_id'], + 'tree' => (strlen($style['style_parent_tree']) ? $style['style_parent_tree'] . '/' : '') . $style['style_path'], + ); + } + + // Get list of directories + $dirs = $this->find_style_dirs(); + + // Find styles that can be installed + $styles = array(); + foreach ($dirs as $dir) + { + if (in_array($dir, $installed_dirs)) + { + // Style is already installed + continue; + } + $cfg = $this->read_style_cfg($dir); + if ($cfg === false) + { + // Invalid style.cfg + continue; + } + + // Style should be available for installation + $parent = $cfg['parent']; + $style = array( + 'style_id' => 0, + 'style_name' => $cfg['name'], + 'style_copyright' => $cfg['copyright'], + 'style_active' => 0, + 'style_path' => $dir, + 'bbcode_bitfield' => $cfg['template_bitfield'], + 'style_parent_id' => 0, + 'style_parent_tree' => '', + // Extra values for styles list + // All extra variable start with _ so they won't be confused with data that can be added to styles table + '_inherit_name' => $parent, + '_available' => true, + '_note' => '', + ); + + // Check style inheritance + if ($parent != '') + { + if (isset($installed_names[$parent])) + { + // Parent style is installed + $row = $installed_names[$parent]; + $style['style_parent_id'] = $row['id']; + $style['style_parent_tree'] = $row['tree']; + } + else + { + // Parent style is not installed yet + $style['_available'] = false; + $style['_note'] = sprintf($this->user->lang['REQUIRES_STYLE'], htmlspecialchars($parent)); + } + } + + if ($all || $style['_available']) + { + $styles[] = $style; + } + } + + return $styles; + } + + /** + * Show styles list + * + * @param array $styles styles list + * @param int $parent parent style id + * @param int $level style inheritance level + */ + protected function show_styles_list(&$styles, $parent, $level) + { + foreach ($styles as &$style) + { + if (empty($style['_shown']) && $style['style_parent_id'] == $parent) + { + $this->list_style($style, $level); + $this->show_styles_list($styles, $style['style_id'], $level + 1); + } + } + } + + /** + * Show available styles tree + * + * @param array $styles Styles list, passed as reference + * @param string $name Name of parent style + * @param int $level Styles tree level + */ + protected function show_available_child_styles(&$styles, $name, $level) + { + foreach ($styles as &$style) + { + if (empty($style['_shown']) && $style['_inherit_name'] == $name) + { + $this->list_style($style, $level); + $this->show_available_child_styles($styles, $style['style_name'], $level + 1); + } + } + } + + /** + * Update styles tree + * + * @param array $styles Styles list, passed as reference + * @param array|false $style Current style, false if root + * @return bool True if something was updated, false if not + */ + protected function update_styles_tree(&$styles, $style = false) + { + $parent_id = ($style === false) ? 0 : $style['style_id']; + $parent_tree = ($style === false) ? '' : ($style['style_parent_tree'] == '' ? '' : $style['style_parent_tree']) . $style['style_path']; + $update = false; + $updated = false; + foreach ($styles as &$row) + { + if ($row['style_parent_id'] == $parent_id) + { + if ($row['style_parent_tree'] != $parent_tree) + { + $row['style_parent_tree'] = $parent_tree; + $update = true; + } + $updated |= $this->update_styles_tree($styles, $row); + } + } + if ($update) + { + $sql = 'UPDATE ' . STYLES_TABLE . " + SET style_parent_tree = '" . $this->db->sql_escape($parent_tree) . "' + WHERE style_parent_id = {$parent_id}"; + $this->db->sql_query($sql); + $updated = true; + } + return $updated; + } + + /** + * Find all possible parent styles for style + * + * @param array $styles list of styles + * @param int $id id of style + * @param int $parent current parent style id + * @param int $level current tree level + * @return array Style ids, names and levels + */ + protected function find_possible_parents($styles, $id = -1, $parent = 0, $level = 0) + { + $results = array(); + foreach ($styles as $style) + { + if ($style['style_id'] != $id && $style['style_parent_id'] == $parent) + { + $results[] = array( + 'style_id' => $style['style_id'], + 'style_name' => $style['style_name'], + 'style_path' => $style['style_path'], + 'style_parent_id' => $style['style_parent_id'], + 'style_parent_tree' => $style['style_parent_tree'], + 'level' => $level + ); + $results = array_merge($results, $this->find_possible_parents($styles, $id, $style['style_id'], $level + 1)); + } + } + return $results; + } + + /** + * Show item in styles list + * + * @param array $style style row + * @param int $level style inheritance level + */ + protected function list_style(&$style, $level) + { + // Mark row as shown + if (!empty($style['_shown'])) + { + return; + } + + $style['_shown'] = true; + + // Generate template variables + $actions = array(); + $row = array( + // Style data + 'STYLE_ID' => $style['style_id'], + 'STYLE_NAME' => htmlspecialchars($style['style_name']), + 'STYLE_PHPBB_VERSION' => $this->read_style_cfg($style['style_path'])['phpbb_version'], + 'STYLE_PATH' => htmlspecialchars($style['style_path']), + 'STYLE_COPYRIGHT' => strip_tags($style['style_copyright']), + 'STYLE_ACTIVE' => $style['style_active'], + + // Additional data + 'DEFAULT' => ($style['style_id'] && $style['style_id'] == $this->default_style), + 'USERS' => (isset($style['_users'])) ? $style['_users'] : '', + 'LEVEL' => $level, + 'PADDING' => (4 + 16 * $level), + 'SHOW_COPYRIGHT' => ($style['style_id']) ? false : true, + 'STYLE_PATH_FULL' => htmlspecialchars($this->styles_path_absolute . '/' . $style['style_path']) . '/', + + // Comment to show below style + 'COMMENT' => (isset($style['_note'])) ? $style['_note'] : '', + + // The following variables should be used by hooks to add custom HTML code + 'EXTRA' => '', + 'EXTRA_OPTIONS' => '' + ); + + // Status specific data + if ($style['style_id']) + { + // Style is installed + + // Details + $actions[] = array( + 'U_ACTION' => $this->u_action . '&action=details&id=' . $style['style_id'], + 'L_ACTION' => $this->user->lang['DETAILS'] + ); + + // Activate/Deactive + $action_name = ($style['style_active'] ? 'de' : '') . 'activate'; + + $actions[] = array( + 'U_ACTION' => $this->u_action . '&action=' . $action_name . '&hash=' . generate_link_hash($action_name) . '&id=' . $style['style_id'], + 'L_ACTION' => $this->user->lang['STYLE_' . ($style['style_active'] ? 'DE' : '') . 'ACTIVATE'] + ); + +/* // Export + $actions[] = array( + 'U_ACTION' => $this->u_action . '&action=export&hash=' . generate_link_hash('export') . '&id=' . $style['style_id'], + 'L_ACTION' => $this->user->lang['EXPORT'] + ); */ + + // Uninstall + $actions[] = array( + 'U_ACTION' => $this->u_action . '&action=uninstall&hash=' . generate_link_hash('uninstall') . '&id=' . $style['style_id'], + 'L_ACTION' => $this->user->lang['STYLE_UNINSTALL'] + ); + + // Preview + $actions[] = array( + 'U_ACTION' => append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'style=' . $style['style_id']), + 'L_ACTION' => $this->user->lang['PREVIEW'] + ); + } + else + { + // Style is not installed + if (empty($style['_available'])) + { + $actions[] = array( + 'HTML' => $this->user->lang['CANNOT_BE_INSTALLED'] + ); + } + else + { + $actions[] = array( + 'U_ACTION' => $this->u_action . '&action=install&hash=' . generate_link_hash('install') . '&dir=' . urlencode($style['style_path']), + 'L_ACTION' => $this->user->lang['INSTALL_STYLE'] + ); + } + } + + // todo: add hook + + // Assign template variables + $this->template->assign_block_vars('styles_list', $row); + foreach ($actions as $action) + { + $this->template->assign_block_vars('styles_list.actions', $action); + } + + // Increase counters + $counter = ($style['style_id']) ? ($style['style_active'] ? 'active' : 'inactive') : (empty($style['_available']) ? 'cannotinstall' : 'caninstall'); + if (!isset($this->style_counters)) + { + $this->style_counters = array( + 'total' => 0, + 'active' => 0, + 'inactive' => 0, + 'caninstall' => 0, + 'cannotinstall' => 0 + ); + } + $this->style_counters[$counter]++; + $this->style_counters['total']++; + } + + /** + * Show welcome message + * + * @param string $title main title + * @param string $description page description + */ + protected function welcome_message($title, $description) + { + $this->template->assign_vars(array( + 'L_TITLE' => $this->user->lang[$title], + 'L_EXPLAIN' => (isset($this->user->lang[$description])) ? $this->user->lang[$description] : '' + ) + ); + } + + /** + * Find all directories that have styles + * + * @return array Directory names + */ + protected function find_style_dirs() + { + $styles = array(); + + $dp = @opendir($this->styles_path); + if ($dp) + { + while (($file = readdir($dp)) !== false) + { + $dir = $this->styles_path . $file; + if ($file[0] == '.' || !is_dir($dir)) + { + continue; + } + + if (file_exists("{$dir}/style.cfg")) + { + $styles[] = $file; + } + } + closedir($dp); + } + + return $styles; + } + + /** + * Sort styles + */ + public function sort_styles($style1, $style2) + { + if ($style1['style_active'] != $style2['style_active']) + { + return ($style1['style_active']) ? -1 : 1; + } + if (isset($style1['_available']) && $style1['_available'] != $style2['_available']) + { + return ($style1['_available']) ? -1 : 1; + } + return strcasecmp(isset($style1['style_name']) ? $style1['style_name'] : $style1['name'], isset($style2['style_name']) ? $style2['style_name'] : $style2['name']); + } + + /** + * Read style configuration file + * + * @param string $dir style directory + * @return array|bool Style data, false on error + */ + protected function read_style_cfg($dir) + { + static $required = array('name', 'phpbb_version', 'copyright'); + $cfg = parse_cfg_file($this->styles_path . $dir . '/style.cfg'); + + // Check if it is a valid file + foreach ($required as $key) + { + if (!isset($cfg[$key])) + { + return false; + } + } + + // Check data + if (!isset($cfg['parent']) || !is_string($cfg['parent']) || $cfg['parent'] == $cfg['name']) + { + $cfg['parent'] = ''; + } + if (!isset($cfg['template_bitfield'])) + { + $cfg['template_bitfield'] = $this->default_bitfield(); + } + + return $cfg; + } + + /** + * Install style + * + * @param array $style style data + * @return int Style id + */ + protected function install_style($style) + { + global $user, $phpbb_log; + + // Generate row + $sql_ary = array(); + foreach ($style as $key => $value) + { + if ($key != 'style_id' && substr($key, 0, 1) != '_') + { + $sql_ary[$key] = $value; + } + } + + // Add to database + $this->db->sql_transaction('begin'); + + $sql = 'INSERT INTO ' . STYLES_TABLE . ' + ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + + $id = $this->db->sql_nextid(); + + $this->db->sql_transaction('commit'); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_STYLE_ADD', false, array($sql_ary['style_name'])); + + return $id; + } + + /** + * Lists all styles + * + * @return array Rows with styles data + */ + protected function get_styles() + { + $sql = 'SELECT * + FROM ' . STYLES_TABLE; + $result = $this->db->sql_query($sql); + + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Count users for each style + * + * @return array Styles in following format: [style_id] = number of users + */ + protected function get_users() + { + $sql = 'SELECT user_style, COUNT(user_style) AS style_count + FROM ' . USERS_TABLE . ' + GROUP BY user_style'; + $result = $this->db->sql_query($sql); + + $style_count = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $style_count[$row['user_style']] = $row['style_count']; + } + $this->db->sql_freeresult($result); + + return $style_count; + } + + /** + * Uninstall style + * + * @param array $style Style data + * @return bool|string True on success, error message on error + */ + protected function uninstall_style($style) + { + $id = $style['style_id']; + $path = $style['style_path']; + + // Check if style has child styles + $sql = 'SELECT style_id + FROM ' . STYLES_TABLE . ' + WHERE style_parent_id = ' . (int) $id . " OR style_parent_tree = '" . $this->db->sql_escape($path) . "'"; + $result = $this->db->sql_query($sql); + + $conflict = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($conflict !== false) + { + return sprintf($this->user->lang['STYLE_UNINSTALL_DEPENDENT'], $style['style_name']); + } + + // Change default style for users + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_style = ' . (int) $this->default_style . ' + WHERE user_style = ' . $id; + $this->db->sql_query($sql); + + // Uninstall style + $sql = 'DELETE FROM ' . STYLES_TABLE . ' + WHERE style_id = ' . $id; + $this->db->sql_query($sql); + return true; + } + + /** + * Delete all files in style directory + * + * @param string $path Style directory + * @param string $dir Directory to remove inside style's directory + * @return bool True on success, false on error + */ + protected function delete_style_files($path, $dir = '') + { + $dirname = $this->styles_path . $path . $dir; + $result = true; + + $dp = @opendir($dirname); + + if ($dp) + { + while (($file = readdir($dp)) !== false) + { + if ($file == '.' || $file == '..') + { + continue; + } + $filename = $dirname . '/' . $file; + if (is_dir($filename)) + { + if (!$this->delete_style_files($path, $dir . '/' . $file)) + { + $result = false; + } + } + else + { + if (!@unlink($filename)) + { + $result = false; + } + } + } + closedir($dp); + } + if (!@rmdir($dirname)) + { + return false; + } + + return $result; + } + + /** + * Get list of items from posted data + * + * @param string $name Variable name + * @param string|int $default Default value for array + * @param bool $error If true, error will be triggered if list is empty + * @return array Items + */ + protected function request_vars($name, $default, $error = false) + { + $item = $this->request->variable($name, $default); + $items = $this->request->variable($name . 's', array($default)); + + if (count($items) == 1 && $items[0] == $default) + { + $items = array(); + } + + if ($item != $default && !count($items)) + { + $items[] = $item; + } + + if ($error && !count($items)) + { + trigger_error($this->user->lang['NO_MATCHING_STYLES_FOUND'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + return $items; + } + + /** + * Generates default bitfield + * + * This bitfield decides which bbcodes are defined in a template. + * + * @return string Bitfield + */ + protected function default_bitfield() + { + static $value; + if (isset($value)) + { + return $value; + } + + // Hardcoded template bitfield to add for new templates + $default_bitfield = '1111111111111'; + + $bitfield = new bitfield(); + for ($i = 0; $i < strlen($default_bitfield); $i++) + { + if ($default_bitfield[$i] == '1') + { + $bitfield->set($i); + } + } + + return $bitfield->get_base64(); + } + +} diff --git a/includes/acp/acp_update.php b/includes/acp/acp_update.php new file mode 100644 index 0000000..9124a59 --- /dev/null +++ b/includes/acp/acp_update.php @@ -0,0 +1,86 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_update +{ + var $u_action; + + function main($id, $mode) + { + global $config, $user, $template, $request; + global $phpbb_root_path, $phpEx, $phpbb_container; + + $user->add_lang('install'); + + $this->tpl_name = 'acp_update'; + $this->page_title = 'ACP_VERSION_CHECK'; + + /* @var $version_helper \phpbb\version_helper */ + $version_helper = $phpbb_container->get('version_helper'); + try + { + $recheck = $request->variable('versioncheck_force', false); + $updates_available = $version_helper->get_update_on_branch($recheck); + $upgrades_available = $version_helper->get_suggested_updates(); + if (!empty($upgrades_available)) + { + $upgrades_available = array_pop($upgrades_available); + } + } + catch (\RuntimeException $e) + { + $template->assign_var('S_VERSIONCHECK_FAIL', true); + + $updates_available = array(); + } + + if (!empty($updates_available)) + { + $template->assign_block_vars('updates_available', $updates_available); + } + + $update_link = $phpbb_root_path . 'install/app.' . $phpEx; + + $template->assign_vars(array( + 'S_UP_TO_DATE' => empty($updates_available), + 'U_ACTION' => $this->u_action, + 'U_VERSIONCHECK_FORCE' => append_sid($this->u_action . '&versioncheck_force=1'), + + 'CURRENT_VERSION' => $config['version'], + + 'UPDATE_INSTRUCTIONS' => sprintf($user->lang['UPDATE_INSTRUCTIONS'], $update_link), + 'S_VERSION_UPGRADEABLE' => !empty($upgrades_available), + 'UPGRADE_INSTRUCTIONS' => !empty($upgrades_available) ? $user->lang('UPGRADE_INSTRUCTIONS', $upgrades_available['current'], $upgrades_available['announcement']) : false, + )); + + // Incomplete update? + if (phpbb_version_compare($config['version'], PHPBB_VERSION, '<')) + { + $database_update_link = $phpbb_root_path . 'install/app.php/update'; + + $template->assign_vars(array( + 'S_UPDATE_INCOMPLETE' => true, + 'FILES_VERSION' => PHPBB_VERSION, + 'INCOMPLETE_INSTRUCTIONS' => $user->lang('UPDATE_INCOMPLETE_EXPLAIN', $database_update_link), + )); + } + } +} diff --git a/includes/acp/acp_users.php b/includes/acp/acp_users.php new file mode 100644 index 0000000..2d1eaad --- /dev/null +++ b/includes/acp/acp_users.php @@ -0,0 +1,2692 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class acp_users +{ + var $u_action; + var $p_master; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $config, $db, $user, $auth, $template; + global $phpbb_root_path, $phpbb_admin_path, $phpEx; + global $phpbb_dispatcher, $request; + global $phpbb_container, $phpbb_log; + + $user->add_lang(array('posting', 'ucp', 'acp/users')); + $this->tpl_name = 'acp_users'; + + $error = array(); + $username = $request->variable('username', '', true); + $user_id = $request->variable('u', 0); + $action = $request->variable('action', ''); + + // Get referer to redirect user to the appropriate page after delete action + $redirect = $request->variable('redirect', ''); + $redirect_tag = "redirect=$redirect"; + $redirect_url = append_sid("{$phpbb_admin_path}index.$phpEx", "i=$redirect"); + + $submit = (isset($_POST['update']) && !isset($_POST['cancel'])) ? true : false; + + $form_name = 'acp_users'; + add_form_key($form_name); + + // Whois (special case) + if ($action == 'whois') + { + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $this->page_title = 'WHOIS'; + $this->tpl_name = 'simple_body'; + + $user_ip = phpbb_ip_normalise($request->variable('user_ip', '')); + $domain = gethostbyaddr($user_ip); + $ipwhois = user_ipwhois($user_ip); + + $template->assign_vars(array( + 'MESSAGE_TITLE' => sprintf($user->lang['IP_WHOIS_FOR'], $domain), + 'MESSAGE_TEXT' => nl2br($ipwhois)) + ); + + return; + } + + // Show user selection mask + if (!$username && !$user_id) + { + $this->page_title = 'SELECT_USER'; + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'ANONYMOUS_USER_ID' => ANONYMOUS, + + 'S_SELECT_USER' => true, + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=select_user&field=username&select_single=true'), + )); + + return; + } + + if (!$user_id) + { + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; + $result = $db->sql_query($sql); + $user_id = (int) $db->sql_fetchfield('user_id'); + $db->sql_freeresult($result); + + if (!$user_id) + { + trigger_error($user->lang['NO_USER'] . adm_back_link($this->u_action), E_USER_WARNING); + } + } + + // Generate content for all modes + $sql = 'SELECT u.*, s.* + FROM ' . USERS_TABLE . ' u + LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id) + WHERE u.user_id = ' . $user_id . ' + ORDER BY s.session_time DESC'; + $result = $db->sql_query_limit($sql, 1); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$user_row) + { + trigger_error($user->lang['NO_USER'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Generate overall "header" for user admin + $s_form_options = ''; + + // Build modes dropdown list + $sql = 'SELECT module_mode, module_auth + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'acp_users' + AND module_enabled = 1 + AND module_class = 'acp' + ORDER BY left_id, module_mode"; + $result = $db->sql_query($sql); + + $dropdown_modes = array(); + while ($row = $db->sql_fetchrow($result)) + { + if (!$this->p_master->module_auth_self($row['module_auth'])) + { + continue; + } + + $dropdown_modes[$row['module_mode']] = true; + } + $db->sql_freeresult($result); + + foreach ($dropdown_modes as $module_mode => $null) + { + $selected = ($mode == $module_mode) ? ' selected="selected"' : ''; + $s_form_options .= ''; + } + + $template->assign_vars(array( + 'U_BACK' => (empty($redirect)) ? $this->u_action : $redirect_url, + 'U_MODE_SELECT' => append_sid("{$phpbb_admin_path}index.$phpEx", "i=$id&u=$user_id"), + 'U_ACTION' => $this->u_action . '&u=' . $user_id . ((empty($redirect)) ? '' : '&' . $redirect_tag), + 'S_FORM_OPTIONS' => $s_form_options, + 'MANAGED_USERNAME' => $user_row['username']) + ); + + // Prevent normal users/admins change/view founders if they are not a founder by themselves + if ($user->data['user_type'] != USER_FOUNDER && $user_row['user_type'] == USER_FOUNDER) + { + trigger_error($user->lang['NOT_MANAGE_FOUNDER'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $this->page_title = $user_row['username'] . ' :: ' . $user->lang('ACP_USER_' . strtoupper($mode)); + + switch ($mode) + { + case 'overview': + + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $user->add_lang('acp/ban'); + + $delete = $request->variable('delete', 0); + $delete_type = $request->variable('delete_type', ''); + $ip = $request->variable('ip', 'ip'); + + /** + * Run code at beginning of ACP users overview + * + * @event core.acp_users_overview_before + * @var array user_row Current user data + * @var string mode Active module + * @var string action Module that should be run + * @var bool submit Do we display the form only + * or did the user press submit + * @var array error Array holding error messages + * @since 3.1.3-RC1 + */ + $vars = array('user_row', 'mode', 'action', 'submit', 'error'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_overview_before', compact($vars))); + + if ($submit) + { + if ($delete) + { + if (!$auth->acl_get('a_userdel')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NO_AUTH_OPERATION'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + // Check if the user wants to remove himself or the guest user account + if ($user_id == ANONYMOUS) + { + trigger_error($user->lang['CANNOT_REMOVE_ANONYMOUS'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + // Founders can not be deleted. + if ($user_row['user_type'] == USER_FOUNDER) + { + trigger_error($user->lang['CANNOT_REMOVE_FOUNDER'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($user_id == $user->data['user_id']) + { + trigger_error($user->lang['CANNOT_REMOVE_YOURSELF'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($delete_type) + { + if (confirm_box(true)) + { + user_delete($delete_type, $user_id, $user_row['username']); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_DELETED', false, array($user_row['username'])); + trigger_error($user->lang['USER_DELETED'] . adm_back_link( + (empty($redirect)) ? $this->u_action : $redirect_url + ) + ); + } + else + { + $delete_confirm_hidden_fields = array( + 'u' => $user_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'update' => true, + 'delete' => 1, + 'delete_type' => $delete_type, + ); + + // Checks if the redirection page is specified + if (!empty($redirect)) + { + $delete_confirm_hidden_fields['redirect'] = $redirect; + } + + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields($delete_confirm_hidden_fields)); + } + } + else + { + trigger_error($user->lang['NO_MODE'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + } + + // Handle quicktool actions + switch ($action) + { + case 'banuser': + case 'banemail': + case 'banip': + + if ($user_id == $user->data['user_id']) + { + trigger_error($user->lang['CANNOT_BAN_YOURSELF'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($user_id == ANONYMOUS) + { + trigger_error($user->lang['CANNOT_BAN_ANONYMOUS'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($user_row['user_type'] == USER_FOUNDER) + { + trigger_error($user->lang['CANNOT_BAN_FOUNDER'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $ban = array(); + + switch ($action) + { + case 'banuser': + $ban[] = $user_row['username']; + $reason = 'USER_ADMIN_BAN_NAME_REASON'; + break; + + case 'banemail': + $ban[] = $user_row['user_email']; + $reason = 'USER_ADMIN_BAN_EMAIL_REASON'; + break; + + case 'banip': + $ban[] = $user_row['user_ip']; + + $sql = 'SELECT DISTINCT poster_ip + FROM ' . POSTS_TABLE . " + WHERE poster_id = $user_id"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $ban[] = $row['poster_ip']; + } + $db->sql_freeresult($result); + + $reason = 'USER_ADMIN_BAN_IP_REASON'; + break; + } + + $ban_reason = $request->variable('ban_reason', $user->lang[$reason], true); + $ban_give_reason = $request->variable('ban_give_reason', '', true); + + // Log not used at the moment, we simply utilize the ban function. + $result = user_ban(substr($action, 3), $ban, 0, 0, 0, $ban_reason, $ban_give_reason); + + trigger_error((($result === false) ? $user->lang['BAN_ALREADY_ENTERED'] : $user->lang['BAN_SUCCESSFUL']) . adm_back_link($this->u_action . '&u=' . $user_id)); + + break; + + case 'reactivate': + + if ($user_id == $user->data['user_id']) + { + trigger_error($user->lang['CANNOT_FORCE_REACT_YOURSELF'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($user_row['user_type'] == USER_FOUNDER) + { + trigger_error($user->lang['CANNOT_FORCE_REACT_FOUNDER'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($user_row['user_type'] == USER_IGNORE) + { + trigger_error($user->lang['CANNOT_FORCE_REACT_BOT'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($config['email_enable']) + { + if (!class_exists('messenger')) + { + include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + } + + $server_url = generate_board_url(); + + $user_actkey = gen_rand_string(mt_rand(6, 10)); + $email_template = ($user_row['user_type'] == USER_NORMAL) ? 'user_reactivate_account' : 'user_resend_inactive'; + + if ($user_row['user_type'] == USER_NORMAL) + { + user_active_flip('deactivate', $user_id, INACTIVE_REMIND); + } + else + { + // Grabbing the last confirm key - we only send a reminder + $sql = 'SELECT user_actkey + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query($sql); + $user_activation_key = (string) $db->sql_fetchfield('user_actkey'); + $db->sql_freeresult($result); + + $user_actkey = empty($user_activation_key) ? $user_actkey : $user_activation_key; + } + + if ($user_row['user_type'] == USER_NORMAL || empty($user_activation_key)) + { + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_actkey = '" . $db->sql_escape($user_actkey) . "' + WHERE user_id = $user_id"; + $db->sql_query($sql); + } + + $messenger = new messenger(false); + + $messenger->template($email_template, $user_row['user_lang']); + + $messenger->set_addresses($user_row); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'WELCOME_MSG' => htmlspecialchars_decode(sprintf($user->lang['WELCOME_SUBJECT'], $config['sitename'])), + 'USERNAME' => htmlspecialchars_decode($user_row['username']), + 'U_ACTIVATE' => "$server_url/ucp.$phpEx?mode=activate&u={$user_row['user_id']}&k=$user_actkey") + ); + + $messenger->send(NOTIFY_EMAIL); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_REACTIVATE', false, array($user_row['username'])); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_REACTIVATE_USER', false, array( + 'reportee_id' => $user_id + )); + + trigger_error($user->lang['FORCE_REACTIVATION_SUCCESS'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + + break; + + case 'active': + + if ($user_id == $user->data['user_id']) + { + // It is only deactivation since the user is already activated (else he would not have reached this page) + trigger_error($user->lang['CANNOT_DEACTIVATE_YOURSELF'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($user_row['user_type'] == USER_FOUNDER) + { + trigger_error($user->lang['CANNOT_DEACTIVATE_FOUNDER'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($user_row['user_type'] == USER_IGNORE) + { + trigger_error($user->lang['CANNOT_DEACTIVATE_BOT'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + user_active_flip('flip', $user_id); + + if ($user_row['user_type'] == USER_INACTIVE) + { + if ($config['require_activation'] == USER_ACTIVATION_ADMIN) + { + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + $phpbb_notifications->delete_notifications('notification.type.admin_activate_user', $user_row['user_id']); + + if (!class_exists('messenger')) + { + include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + } + + $messenger = new messenger(false); + + $messenger->template('admin_welcome_activated', $user_row['user_lang']); + + $messenger->set_addresses($user_row); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($user_row['username'])) + ); + + $messenger->send(NOTIFY_EMAIL); + } + } + + $message = ($user_row['user_type'] == USER_INACTIVE) ? 'USER_ADMIN_ACTIVATED' : 'USER_ADMIN_DEACTIVED'; + $log = ($user_row['user_type'] == USER_INACTIVE) ? 'LOG_USER_ACTIVE' : 'LOG_USER_INACTIVE'; + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($user_row['username'])); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, $log . '_USER', false, array( + 'reportee_id' => $user_id + )); + + trigger_error($user->lang[$message] . adm_back_link($this->u_action . '&u=' . $user_id)); + + break; + + case 'delsig': + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $sql_ary = array( + 'user_sig' => '', + 'user_sig_bbcode_uid' => '', + 'user_sig_bbcode_bitfield' => '' + ); + + $sql = 'UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " + WHERE user_id = $user_id"; + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_DEL_SIG', false, array($user_row['username'])); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_DEL_SIG_USER', false, array( + 'reportee_id' => $user_id + )); + + trigger_error($user->lang['USER_ADMIN_SIG_REMOVED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + + break; + + case 'delavatar': + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + // Delete old avatar if present + /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ + $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); + $phpbb_avatar_manager->handle_avatar_delete($db, $user, $phpbb_avatar_manager->clean_row($user_row, 'user'), USERS_TABLE, 'user_'); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_DEL_AVATAR', false, array($user_row['username'])); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_DEL_AVATAR_USER', false, array( + 'reportee_id' => $user_id + )); + + trigger_error($user->lang['USER_ADMIN_AVATAR_REMOVED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + break; + + case 'delposts': + + if (confirm_box(true)) + { + // Delete posts, attachments, etc. + delete_posts('poster_id', $user_id); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_DEL_POSTS', false, array($user_row['username'])); + trigger_error($user->lang['USER_POSTS_DELETED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'u' => $user_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'update' => true)) + ); + } + + break; + + case 'delattach': + + if (confirm_box(true)) + { + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $attachment_manager->delete('user', $user_id); + unset($attachment_manager); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_DEL_ATTACH', false, array($user_row['username'])); + trigger_error($user->lang['USER_ATTACHMENTS_REMOVED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'u' => $user_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'update' => true)) + ); + } + + break; + + case 'deloutbox': + + if (confirm_box(true)) + { + $msg_ids = array(); + $lang = 'EMPTY'; + + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . " + WHERE author_id = $user_id + AND folder_id = " . PRIVMSGS_OUTBOX; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + if (!function_exists('delete_pm')) + { + include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); + } + + do + { + $msg_ids[] = (int) $row['msg_id']; + } + while ($row = $db->sql_fetchrow($result)); + + $db->sql_freeresult($result); + + delete_pm($user_id, $msg_ids, PRIVMSGS_OUTBOX); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_DEL_OUTBOX', false, array($user_row['username'])); + + $lang = 'EMPTIED'; + } + $db->sql_freeresult($result); + + trigger_error($user->lang['USER_OUTBOX_' . $lang] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'u' => $user_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'update' => true)) + ); + } + break; + + case 'moveposts': + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $user->add_lang('acp/forums'); + + $new_forum_id = $request->variable('new_f', 0); + + if (!$new_forum_id) + { + $this->page_title = 'USER_ADMIN_MOVE_POSTS'; + + $template->assign_vars(array( + 'S_SELECT_FORUM' => true, + 'U_ACTION' => $this->u_action . "&action=$action&u=$user_id", + 'U_BACK' => $this->u_action . "&u=$user_id", + 'S_FORUM_OPTIONS' => make_forum_select(false, false, false, true)) + ); + + return; + } + + // Is the new forum postable to? + $sql = 'SELECT forum_name, forum_type + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $new_forum_id"; + $result = $db->sql_query($sql); + $forum_info = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$forum_info) + { + trigger_error($user->lang['NO_FORUM'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($forum_info['forum_type'] != FORUM_POST) + { + trigger_error($user->lang['MOVE_POSTS_NO_POSTABLE_FORUM'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + // Two stage? + // Move topics comprising only posts from this user + $topic_id_ary = $move_topic_ary = $move_post_ary = $new_topic_id_ary = array(); + $forum_id_ary = array($new_forum_id); + + $sql = 'SELECT topic_id, post_visibility, COUNT(post_id) AS total_posts + FROM ' . POSTS_TABLE . " + WHERE poster_id = $user_id + AND forum_id <> $new_forum_id + GROUP BY topic_id, post_visibility"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_id_ary[$row['topic_id']][$row['post_visibility']] = $row['total_posts']; + } + $db->sql_freeresult($result); + + if (count($topic_id_ary)) + { + $sql = 'SELECT topic_id, forum_id, topic_title, topic_posts_approved, topic_posts_unapproved, topic_posts_softdeleted, topic_attachment + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', array_keys($topic_id_ary)); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($topic_id_ary[$row['topic_id']][ITEM_APPROVED] == $row['topic_posts_approved'] + && $topic_id_ary[$row['topic_id']][ITEM_UNAPPROVED] == $row['topic_posts_unapproved'] + && $topic_id_ary[$row['topic_id']][ITEM_REAPPROVE] == $row['topic_posts_unapproved'] + && $topic_id_ary[$row['topic_id']][ITEM_DELETED] == $row['topic_posts_softdeleted']) + { + $move_topic_ary[] = $row['topic_id']; + } + else + { + $move_post_ary[$row['topic_id']]['title'] = $row['topic_title']; + $move_post_ary[$row['topic_id']]['attach'] = ($row['topic_attachment']) ? 1 : 0; + } + + $forum_id_ary[] = $row['forum_id']; + } + $db->sql_freeresult($result); + } + + // Entire topic comprises posts by this user, move these topics + if (count($move_topic_ary)) + { + move_topics($move_topic_ary, $new_forum_id, false); + } + + if (count($move_post_ary)) + { + // Create new topic + // Update post_ids, report_ids, attachment_ids + foreach ($move_post_ary as $topic_id => $post_ary) + { + // Create new topic + $sql = 'INSERT INTO ' . TOPICS_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'topic_poster' => $user_id, + 'topic_time' => time(), + 'forum_id' => $new_forum_id, + 'icon_id' => 0, + 'topic_visibility' => ITEM_APPROVED, + 'topic_title' => $post_ary['title'], + 'topic_first_poster_name' => $user_row['username'], + 'topic_type' => POST_NORMAL, + 'topic_time_limit' => 0, + 'topic_attachment' => $post_ary['attach']) + ); + $db->sql_query($sql); + + $new_topic_id = $db->sql_nextid(); + + // Move posts + $sql = 'UPDATE ' . POSTS_TABLE . " + SET forum_id = $new_forum_id, topic_id = $new_topic_id + WHERE topic_id = $topic_id + AND poster_id = $user_id"; + $db->sql_query($sql); + + if ($post_ary['attach']) + { + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . " + SET topic_id = $new_topic_id + WHERE topic_id = $topic_id + AND poster_id = $user_id"; + $db->sql_query($sql); + } + + $new_topic_id_ary[] = $new_topic_id; + } + } + + $forum_id_ary = array_unique($forum_id_ary); + $topic_id_ary = array_unique(array_merge(array_keys($topic_id_ary), $new_topic_id_ary)); + + if (count($topic_id_ary)) + { + sync('topic_reported', 'topic_id', $topic_id_ary); + sync('topic', 'topic_id', $topic_id_ary); + } + + if (count($forum_id_ary)) + { + sync('forum', 'forum_id', $forum_id_ary, false, true); + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_MOVE_POSTS', false, array($user_row['username'], $forum_info['forum_name'])); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_MOVE_POSTS_USER', false, array( + 'reportee_id' => $user_id, + $forum_info['forum_name'] + )); + + trigger_error($user->lang['USER_POSTS_MOVED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + + break; + + case 'leave_nr': + + if (confirm_box(true)) + { + remove_newly_registered($user_id, $user_row); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_REMOVED_NR', false, array($user_row['username'])); + trigger_error($user->lang['USER_LIFTED_NR'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'u' => $user_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'update' => true)) + ); + } + + break; + + default: + $u_action = $this->u_action; + + /** + * Run custom quicktool code + * + * @event core.acp_users_overview_run_quicktool + * @var string action Quick tool that should be run + * @var array user_row Current user data + * @var string u_action The u_action link + * @since 3.1.0-a1 + * @changed 3.2.2-RC1 Added u_action + */ + $vars = array('action', 'user_row', 'u_action'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_overview_run_quicktool', compact($vars))); + + unset($u_action); + break; + } + + // Handle registration info updates + $data = array( + 'username' => $request->variable('user', $user_row['username'], true), + 'user_founder' => $request->variable('user_founder', ($user_row['user_type'] == USER_FOUNDER) ? 1 : 0), + 'email' => strtolower($request->variable('user_email', $user_row['user_email'])), + 'new_password' => $request->variable('new_password', '', true), + 'password_confirm' => $request->variable('password_confirm', '', true), + ); + + // Validation data - we do not check the password complexity setting here + $check_ary = array( + 'new_password' => array( + array('string', true, $config['min_pass_chars'], $config['max_pass_chars']), + array('password')), + 'password_confirm' => array('string', true, $config['min_pass_chars'], $config['max_pass_chars']), + ); + + // Check username if altered + if ($data['username'] != $user_row['username']) + { + $check_ary += array( + 'username' => array( + array('string', false, $config['min_name_chars'], $config['max_name_chars']), + array('username', $user_row['username']) + ), + ); + } + + // Check email if altered + if ($data['email'] != $user_row['user_email']) + { + $check_ary += array( + 'email' => array( + array('string', false, 6, 60), + array('user_email', $user_row['user_email']), + ), + ); + } + + $error = validate_data($data, $check_ary); + + if ($data['new_password'] && $data['password_confirm'] != $data['new_password']) + { + $error[] = 'NEW_PASSWORD_ERROR'; + } + + if (!check_form_key($form_name)) + { + $error[] = 'FORM_INVALID'; + } + + // Instantiate passwords manager + /* @var $passwords_manager \phpbb\passwords\manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + + // Which updates do we need to do? + $update_username = ($user_row['username'] != $data['username']) ? $data['username'] : false; + $update_password = $data['new_password'] && !$passwords_manager->check($data['new_password'], $user_row['user_password']); + $update_email = ($data['email'] != $user_row['user_email']) ? $data['email'] : false; + + if (!count($error)) + { + $sql_ary = array(); + + if ($user_row['user_type'] != USER_FOUNDER || $user->data['user_type'] == USER_FOUNDER) + { + // Only allow founders updating the founder status... + if ($user->data['user_type'] == USER_FOUNDER) + { + // Setting a normal member to be a founder + if ($data['user_founder'] && $user_row['user_type'] != USER_FOUNDER) + { + // Make sure the user is not setting an Inactive or ignored user to be a founder + if ($user_row['user_type'] == USER_IGNORE) + { + trigger_error($user->lang['CANNOT_SET_FOUNDER_IGNORED'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($user_row['user_type'] == USER_INACTIVE) + { + trigger_error($user->lang['CANNOT_SET_FOUNDER_INACTIVE'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $sql_ary['user_type'] = USER_FOUNDER; + } + else if (!$data['user_founder'] && $user_row['user_type'] == USER_FOUNDER) + { + // Check if at least one founder is present + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE user_type = ' . USER_FOUNDER . ' + AND user_id <> ' . $user_id; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $sql_ary['user_type'] = USER_NORMAL; + } + else + { + trigger_error($user->lang['AT_LEAST_ONE_FOUNDER'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + } + } + } + + /** + * Modify user data before we update it + * + * @event core.acp_users_overview_modify_data + * @var array user_row Current user data + * @var array data Submitted user data + * @var array sql_ary User data we udpate + * @since 3.1.0-a1 + */ + $vars = array('user_row', 'data', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_overview_modify_data', compact($vars))); + + if ($update_username !== false) + { + $sql_ary['username'] = $update_username; + $sql_ary['username_clean'] = utf8_clean_string($update_username); + + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_UPDATE_NAME', false, array( + 'reportee_id' => $user_id, + $user_row['username'], + $update_username + )); + } + + if ($update_email !== false) + { + $sql_ary += array( + 'user_email' => $update_email, + 'user_email_hash' => phpbb_email_hash($update_email), + ); + + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_UPDATE_EMAIL', false, array( + 'reportee_id' => $user_id, + $user_row['username'], + $user_row['user_email'], + $update_email + )); + } + + if ($update_password) + { + $sql_ary += array( + 'user_password' => $passwords_manager->hash($data['new_password']), + 'user_passchg' => time(), + ); + + $user->reset_login_keys($user_id); + + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_NEW_PASSWORD', false, array( + 'reportee_id' => $user_id, + $user_row['username'] + )); + } + + if (count($sql_ary)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user_id; + $db->sql_query($sql); + } + + if ($update_username) + { + user_update_name($user_row['username'], $update_username); + } + + // Let the users permissions being updated + $auth->acl_clear_prefetch($user_id); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_USER_UPDATE', false, array($data['username'])); + + trigger_error($user->lang['USER_OVERVIEW_UPDATED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + } + + if ($user_id == $user->data['user_id']) + { + $quick_tool_ary = array('delsig' => 'DEL_SIG', 'delavatar' => 'DEL_AVATAR', 'moveposts' => 'MOVE_POSTS', 'delposts' => 'DEL_POSTS', 'delattach' => 'DEL_ATTACH', 'deloutbox' => 'DEL_OUTBOX'); + if ($user_row['user_new']) + { + $quick_tool_ary['leave_nr'] = 'LEAVE_NR'; + } + } + else + { + $quick_tool_ary = array(); + + if ($user_row['user_type'] != USER_FOUNDER) + { + $quick_tool_ary += array('banuser' => 'BAN_USER', 'banemail' => 'BAN_EMAIL', 'banip' => 'BAN_IP'); + } + + if ($user_row['user_type'] != USER_FOUNDER && $user_row['user_type'] != USER_IGNORE) + { + $quick_tool_ary += array('active' => (($user_row['user_type'] == USER_INACTIVE) ? 'ACTIVATE' : 'DEACTIVATE')); + } + + $quick_tool_ary += array('delsig' => 'DEL_SIG', 'delavatar' => 'DEL_AVATAR', 'moveposts' => 'MOVE_POSTS', 'delposts' => 'DEL_POSTS', 'delattach' => 'DEL_ATTACH', 'deloutbox' => 'DEL_OUTBOX'); + + if ($config['email_enable'] && ($user_row['user_type'] == USER_NORMAL || $user_row['user_type'] == USER_INACTIVE)) + { + $quick_tool_ary['reactivate'] = 'FORCE'; + } + + if ($user_row['user_new']) + { + $quick_tool_ary['leave_nr'] = 'LEAVE_NR'; + } + } + + if ($config['load_onlinetrack']) + { + $sql = 'SELECT MAX(session_time) AS session_time, MIN(session_viewonline) AS session_viewonline + FROM ' . SESSIONS_TABLE . " + WHERE session_user_id = $user_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $user_row['session_time'] = (isset($row['session_time'])) ? $row['session_time'] : 0; + $user_row['session_viewonline'] = (isset($row['session_viewonline'])) ? $row['session_viewonline'] : 0; + unset($row); + } + + /** + * Add additional quick tool options and overwrite user data + * + * @event core.acp_users_display_overview + * @var array user_row Array with user data + * @var array quick_tool_ary Ouick tool options + * @since 3.1.0-a1 + */ + $vars = array('user_row', 'quick_tool_ary'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_display_overview', compact($vars))); + + $s_action_options = ''; + foreach ($quick_tool_ary as $value => $lang) + { + $s_action_options .= ''; + } + + $last_active = (!empty($user_row['session_time'])) ? $user_row['session_time'] : $user_row['user_lastvisit']; + + $inactive_reason = ''; + if ($user_row['user_type'] == USER_INACTIVE) + { + $inactive_reason = $user->lang['INACTIVE_REASON_UNKNOWN']; + + switch ($user_row['user_inactive_reason']) + { + case INACTIVE_REGISTER: + $inactive_reason = $user->lang['INACTIVE_REASON_REGISTER']; + break; + + case INACTIVE_PROFILE: + $inactive_reason = $user->lang['INACTIVE_REASON_PROFILE']; + break; + + case INACTIVE_MANUAL: + $inactive_reason = $user->lang['INACTIVE_REASON_MANUAL']; + break; + + case INACTIVE_REMIND: + $inactive_reason = $user->lang['INACTIVE_REASON_REMIND']; + break; + } + } + + // Posts in Queue + $sql = 'SELECT COUNT(post_id) as posts_in_queue + FROM ' . POSTS_TABLE . ' + WHERE poster_id = ' . $user_id . ' + AND ' . $db->sql_in_set('post_visibility', array(ITEM_UNAPPROVED, ITEM_REAPPROVE)); + $result = $db->sql_query($sql); + $user_row['posts_in_queue'] = (int) $db->sql_fetchfield('posts_in_queue'); + $db->sql_freeresult($result); + + $sql = 'SELECT post_id + FROM ' . POSTS_TABLE . ' + WHERE poster_id = '. $user_id; + $result = $db->sql_query_limit($sql, 1); + $user_row['user_has_posts'] = (bool) $db->sql_fetchfield('post_id'); + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'L_NAME_CHARS_EXPLAIN' => $user->lang($config['allow_name_chars'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_name_chars']), $user->lang('CHARACTERS', (int) $config['max_name_chars'])), + 'L_CHANGE_PASSWORD_EXPLAIN' => $user->lang($config['pass_complex'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_pass_chars']), $user->lang('CHARACTERS', (int) $config['max_pass_chars'])), + 'L_POSTS_IN_QUEUE' => $user->lang('NUM_POSTS_IN_QUEUE', $user_row['posts_in_queue']), + 'S_FOUNDER' => ($user->data['user_type'] == USER_FOUNDER) ? true : false, + + 'S_OVERVIEW' => true, + 'S_USER_IP' => ($user_row['user_ip']) ? true : false, + 'S_USER_FOUNDER' => ($user_row['user_type'] == USER_FOUNDER) ? true : false, + 'S_ACTION_OPTIONS' => $s_action_options, + 'S_OWN_ACCOUNT' => ($user_id == $user->data['user_id']) ? true : false, + 'S_USER_INACTIVE' => ($user_row['user_type'] == USER_INACTIVE) ? true : false, + + 'U_SHOW_IP' => $this->u_action . "&u=$user_id&ip=" . (($ip == 'ip') ? 'hostname' : 'ip'), + 'U_WHOIS' => $this->u_action . "&action=whois&user_ip={$user_row['user_ip']}", + 'U_MCP_QUEUE' => ($auth->acl_getf_global('m_approve')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue', true, $user->session_id) : '', + 'U_SEARCH_USER' => ($config['load_search'] && $auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id={$user_row['user_id']}&sr=posts") : '', + + 'U_SWITCH_PERMISSIONS' => ($auth->acl_get('a_switchperm') && $user->data['user_id'] != $user_row['user_id']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", "mode=switch_perm&u={$user_row['user_id']}&hash=" . generate_link_hash('switchperm')) : '', + + 'POSTS_IN_QUEUE' => $user_row['posts_in_queue'], + 'USER' => $user_row['username'], + 'USER_REGISTERED' => $user->format_date($user_row['user_regdate']), + 'REGISTERED_IP' => ($ip == 'hostname') ? gethostbyaddr($user_row['user_ip']) : $user_row['user_ip'], + 'USER_LASTACTIVE' => ($last_active) ? $user->format_date($last_active) : ' - ', + 'USER_EMAIL' => $user_row['user_email'], + 'USER_WARNINGS' => $user_row['user_warnings'], + 'USER_POSTS' => $user_row['user_posts'], + 'USER_HAS_POSTS' => $user_row['user_has_posts'], + 'USER_INACTIVE_REASON' => $inactive_reason, + )); + + break; + + case 'feedback': + + $user->add_lang('mcp'); + + // Set up general vars + $start = $request->variable('start', 0); + $deletemark = (isset($_POST['delmarked'])) ? true : false; + $deleteall = (isset($_POST['delall'])) ? true : false; + $marked = $request->variable('mark', array(0)); + $message = $request->variable('message', '', true); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + // Sort keys + $sort_days = $request->variable('st', 0); + $sort_key = $request->variable('sk', 't'); + $sort_dir = $request->variable('sd', 'd'); + + // Delete entries if requested and able + if (($deletemark || $deleteall) && $auth->acl_get('a_clearlogs')) + { + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $where_sql = ''; + if ($deletemark && $marked) + { + $sql_in = array(); + foreach ($marked as $mark) + { + $sql_in[] = $mark; + } + $where_sql = ' AND ' . $db->sql_in_set('log_id', $sql_in); + unset($sql_in); + } + + if ($where_sql || $deleteall) + { + $sql = 'DELETE FROM ' . LOG_TABLE . ' + WHERE log_type = ' . LOG_USERS . " + AND reportee_id = $user_id + $where_sql"; + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CLEAR_USER', false, array($user_row['username'])); + } + } + + if ($submit && $message) + { + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_FEEDBACK', false, array($user_row['username'])); + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_USER_FEEDBACK', false, array( + 'forum_id' => 0, + 'topic_id' => 0, + $user_row['username'] + )); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_GENERAL', false, array( + 'reportee_id' => $user_id, + $message + )); + + trigger_error($user->lang['USER_FEEDBACK_ADDED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + + // Sorting + $limit_days = array(0 => $user->lang['ALL_ENTRIES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('u' => $user->lang['SORT_USERNAME'], 't' => $user->lang['SORT_DATE'], 'i' => $user->lang['SORT_IP'], 'o' => $user->lang['SORT_ACTION']); + $sort_by_sql = array('u' => 'u.username_clean', 't' => 'l.log_time', 'i' => 'l.log_ip', 'o' => 'l.log_operation'); + + $s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param); + + // Define where and sort sql for use in displaying logs + $sql_where = ($sort_days) ? (time() - ($sort_days * 86400)) : 0; + $sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC'); + + // Grab log data + $log_data = array(); + $log_count = 0; + $start = view_log('user', $log_data, $log_count, $config['topics_per_page'], $start, 0, 0, $user_id, $sql_where, $sql_sort); + + $base_url = $this->u_action . "&u=$user_id&$u_sort_param"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'S_FEEDBACK' => true, + + 'S_LIMIT_DAYS' => $s_limit_days, + 'S_SORT_KEY' => $s_sort_key, + 'S_SORT_DIR' => $s_sort_dir, + 'S_CLEARLOGS' => $auth->acl_get('a_clearlogs')) + ); + + foreach ($log_data as $row) + { + $template->assign_block_vars('log', array( + 'USERNAME' => $row['username_full'], + 'IP' => $row['ip'], + 'DATE' => $user->format_date($row['time']), + 'ACTION' => nl2br($row['action']), + 'ID' => $row['id']) + ); + } + + break; + + case 'warnings': + $user->add_lang('mcp'); + + // Set up general vars + $deletemark = (isset($_POST['delmarked'])) ? true : false; + $deleteall = (isset($_POST['delall'])) ? true : false; + $confirm = (isset($_POST['confirm'])) ? true : false; + $marked = $request->variable('mark', array(0)); + + // Delete entries if requested and able + if ($deletemark || $deleteall || $confirm) + { + if (confirm_box(true)) + { + $where_sql = ''; + $deletemark = $request->variable('delmarked', 0); + $deleteall = $request->variable('delall', 0); + if ($deletemark && $marked) + { + $where_sql = ' AND ' . $db->sql_in_set('warning_id', array_values($marked)); + } + + if ($where_sql || $deleteall) + { + $sql = 'DELETE FROM ' . WARNINGS_TABLE . " + WHERE user_id = $user_id + $where_sql"; + $db->sql_query($sql); + + if ($deleteall) + { + $log_warnings = $deleted_warnings = 0; + } + else + { + $num_warnings = (int) $db->sql_affectedrows(); + $deleted_warnings = ' user_warnings - ' . $num_warnings; + $log_warnings = ($num_warnings > 2) ? 2 : $num_warnings; + } + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_warnings = $deleted_warnings + WHERE user_id = $user_id"; + $db->sql_query($sql); + + if ($log_warnings) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_WARNINGS_DELETED', false, array($user_row['username'], $num_warnings)); + } + else + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_WARNINGS_DELETED_ALL', false, array($user_row['username'])); + } + } + } + else + { + $s_hidden_fields = array( + 'i' => $id, + 'mode' => $mode, + 'u' => $user_id, + 'mark' => $marked, + ); + if (isset($_POST['delmarked'])) + { + $s_hidden_fields['delmarked'] = 1; + } + if (isset($_POST['delall'])) + { + $s_hidden_fields['delall'] = 1; + } + if (isset($_POST['delall']) || (isset($_POST['delmarked']) && count($marked))) + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields($s_hidden_fields)); + } + } + } + + $sql = 'SELECT w.warning_id, w.warning_time, w.post_id, l.log_operation, l.log_data, l.user_id AS mod_user_id, m.username AS mod_username, m.user_colour AS mod_user_colour + FROM ' . WARNINGS_TABLE . ' w + LEFT JOIN ' . LOG_TABLE . ' l + ON (w.log_id = l.log_id) + LEFT JOIN ' . USERS_TABLE . ' m + ON (l.user_id = m.user_id) + WHERE w.user_id = ' . $user_id . ' + ORDER BY w.warning_time DESC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (!$row['log_operation']) + { + // We do not have a log-entry anymore, so there is no data available + $row['action'] = $user->lang['USER_WARNING_LOG_DELETED']; + } + else + { + $row['action'] = (isset($user->lang[$row['log_operation']])) ? $user->lang[$row['log_operation']] : '{' . ucfirst(str_replace('_', ' ', $row['log_operation'])) . '}'; + if (!empty($row['log_data'])) + { + $log_data_ary = @unserialize($row['log_data']); + $log_data_ary = ($log_data_ary === false) ? array() : $log_data_ary; + + if (isset($user->lang[$row['log_operation']])) + { + // Check if there are more occurrences of % than arguments, if there are we fill out the arguments array + // It doesn't matter if we add more arguments than placeholders + if ((substr_count($row['action'], '%') - count($log_data_ary)) > 0) + { + $log_data_ary = array_merge($log_data_ary, array_fill(0, substr_count($row['action'], '%') - count($log_data_ary), '')); + } + $row['action'] = vsprintf($row['action'], $log_data_ary); + $row['action'] = bbcode_nl2br(censor_text($row['action'])); + } + else if (!empty($log_data_ary)) + { + $row['action'] .= '
' . implode('', $log_data_ary); + } + } + } + + $template->assign_block_vars('warn', array( + 'ID' => $row['warning_id'], + 'USERNAME' => ($row['log_operation']) ? get_username_string('full', $row['mod_user_id'], $row['mod_username'], $row['mod_user_colour']) : '-', + 'ACTION' => make_clickable($row['action']), + 'DATE' => $user->format_date($row['warning_time']), + )); + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'S_WARNINGS' => true, + )); + + break; + + case 'profile': + + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + + $cp_data = $cp_error = array(); + + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE . " + WHERE lang_iso = '" . $db->sql_escape($user->data['user_lang']) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $user_row['iso_lang_id'] = $row['lang_id']; + + $data = array( + 'jabber' => $request->variable('jabber', $user_row['user_jabber'], true), + 'bday_day' => 0, + 'bday_month' => 0, + 'bday_year' => 0, + ); + + if ($user_row['user_birthday']) + { + list($data['bday_day'], $data['bday_month'], $data['bday_year']) = explode('-', $user_row['user_birthday']); + } + + $data['bday_day'] = $request->variable('bday_day', $data['bday_day']); + $data['bday_month'] = $request->variable('bday_month', $data['bday_month']); + $data['bday_year'] = $request->variable('bday_year', $data['bday_year']); + $data['user_birthday'] = sprintf('%2d-%2d-%4d', $data['bday_day'], $data['bday_month'], $data['bday_year']); + + /** + * Modify user data on editing profile in ACP + * + * @event core.acp_users_modify_profile + * @var array data Array with user profile data + * @var bool submit Flag indicating if submit button has been pressed + * @var int user_id The user id + * @var array user_row Array with the full user data + * @since 3.1.4-RC1 + */ + $vars = array('data', 'submit', 'user_id', 'user_row'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_modify_profile', compact($vars))); + + if ($submit) + { + $error = validate_data($data, array( + 'jabber' => array( + array('string', true, 5, 255), + array('jabber')), + 'bday_day' => array('num', true, 1, 31), + 'bday_month' => array('num', true, 1, 12), + 'bday_year' => array('num', true, 1901, gmdate('Y', time())), + 'user_birthday' => array('date', true), + )); + + // validate custom profile fields + $cp->submit_cp_field('profile', $user_row['iso_lang_id'], $cp_data, $cp_error); + + if (count($cp_error)) + { + $error = array_merge($error, $cp_error); + } + if (!check_form_key($form_name)) + { + $error[] = 'FORM_INVALID'; + } + + /** + * Validate profile data in ACP before submitting to the database + * + * @event core.acp_users_profile_validate + * @var array data Array with user profile data + * @var int user_id The user id + * @var array user_row Array with the full user data + * @var array error Array with the form errors + * @since 3.1.4-RC1 + * @changed 3.1.12-RC1 Removed submit, added user_id, user_row + */ + $vars = array('data', 'user_id', 'user_row', 'error'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_profile_validate', compact($vars))); + + if (!count($error)) + { + $sql_ary = array( + 'user_jabber' => $data['jabber'], + 'user_birthday' => $data['user_birthday'], + ); + + /** + * Modify profile data in ACP before submitting to the database + * + * @event core.acp_users_profile_modify_sql_ary + * @var array cp_data Array with the user custom profile fields data + * @var array data Array with user profile data + * @var int user_id The user id + * @var array user_row Array with the full user data + * @var array sql_ary Array with sql data + * @since 3.1.4-RC1 + */ + $vars = array('cp_data', 'data', 'user_id', 'user_row', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_profile_modify_sql_ary', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " + WHERE user_id = $user_id"; + $db->sql_query($sql); + + // Update Custom Fields + $cp->update_profile_field_data($user_id, $cp_data); + + trigger_error($user->lang['USER_PROFILE_UPDATED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + } + + $s_birthday_day_options = ''; + for ($i = 1; $i < 32; $i++) + { + $selected = ($i == $data['bday_day']) ? ' selected="selected"' : ''; + $s_birthday_day_options .= ""; + } + + $s_birthday_month_options = ''; + for ($i = 1; $i < 13; $i++) + { + $selected = ($i == $data['bday_month']) ? ' selected="selected"' : ''; + $s_birthday_month_options .= ""; + } + + $now = getdate(); + $s_birthday_year_options = ''; + for ($i = $now['year'] - 100; $i <= $now['year']; $i++) + { + $selected = ($i == $data['bday_year']) ? ' selected="selected"' : ''; + $s_birthday_year_options .= ""; + } + unset($now); + + $template->assign_vars(array( + 'JABBER' => $data['jabber'], + 'S_BIRTHDAY_DAY_OPTIONS' => $s_birthday_day_options, + 'S_BIRTHDAY_MONTH_OPTIONS' => $s_birthday_month_options, + 'S_BIRTHDAY_YEAR_OPTIONS' => $s_birthday_year_options, + + 'S_PROFILE' => true) + ); + + // Get additional profile fields and assign them to the template block var 'profile_fields' + $user->get_profile_fields($user_id); + + $cp->generate_profile_fields('profile', $user_row['iso_lang_id']); + + break; + + case 'prefs': + + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $data = array( + 'dateformat' => $request->variable('dateformat', $user_row['user_dateformat'], true), + 'lang' => basename($request->variable('lang', $user_row['user_lang'])), + 'tz' => $request->variable('tz', $user_row['user_timezone']), + 'style' => $request->variable('style', $user_row['user_style']), + 'viewemail' => $request->variable('viewemail', $user_row['user_allow_viewemail']), + 'massemail' => $request->variable('massemail', $user_row['user_allow_massemail']), + 'hideonline' => $request->variable('hideonline', !$user_row['user_allow_viewonline']), + 'notifymethod' => $request->variable('notifymethod', $user_row['user_notify_type']), + 'notifypm' => $request->variable('notifypm', $user_row['user_notify_pm']), + 'allowpm' => $request->variable('allowpm', $user_row['user_allow_pm']), + + 'topic_sk' => $request->variable('topic_sk', ($user_row['user_topic_sortby_type']) ? $user_row['user_topic_sortby_type'] : 't'), + 'topic_sd' => $request->variable('topic_sd', ($user_row['user_topic_sortby_dir']) ? $user_row['user_topic_sortby_dir'] : 'd'), + 'topic_st' => $request->variable('topic_st', ($user_row['user_topic_show_days']) ? $user_row['user_topic_show_days'] : 0), + + 'post_sk' => $request->variable('post_sk', ($user_row['user_post_sortby_type']) ? $user_row['user_post_sortby_type'] : 't'), + 'post_sd' => $request->variable('post_sd', ($user_row['user_post_sortby_dir']) ? $user_row['user_post_sortby_dir'] : 'a'), + 'post_st' => $request->variable('post_st', ($user_row['user_post_show_days']) ? $user_row['user_post_show_days'] : 0), + + 'view_images' => $request->variable('view_images', $this->optionget($user_row, 'viewimg')), + 'view_flash' => $request->variable('view_flash', $this->optionget($user_row, 'viewflash')), + 'view_smilies' => $request->variable('view_smilies', $this->optionget($user_row, 'viewsmilies')), + 'view_sigs' => $request->variable('view_sigs', $this->optionget($user_row, 'viewsigs')), + 'view_avatars' => $request->variable('view_avatars', $this->optionget($user_row, 'viewavatars')), + 'view_wordcensor' => $request->variable('view_wordcensor', $this->optionget($user_row, 'viewcensors')), + + 'bbcode' => $request->variable('bbcode', $this->optionget($user_row, 'bbcode')), + 'smilies' => $request->variable('smilies', $this->optionget($user_row, 'smilies')), + 'sig' => $request->variable('sig', $this->optionget($user_row, 'attachsig')), + 'notify' => $request->variable('notify', $user_row['user_notify']), + ); + + /** + * Modify users preferences data + * + * @event core.acp_users_prefs_modify_data + * @var array data Array with users preferences data + * @var array user_row Array with user data + * @since 3.1.0-b3 + */ + $vars = array('data', 'user_row'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_prefs_modify_data', compact($vars))); + + if ($submit) + { + $error = validate_data($data, array( + 'dateformat' => array('string', false, 1, 64), + 'lang' => array('match', false, '#^[a-z_\-]{2,}$#i'), + 'tz' => array('timezone'), + + 'topic_sk' => array('string', false, 1, 1), + 'topic_sd' => array('string', false, 1, 1), + 'post_sk' => array('string', false, 1, 1), + 'post_sd' => array('string', false, 1, 1), + )); + + if (!check_form_key($form_name)) + { + $error[] = 'FORM_INVALID'; + } + + if (!count($error)) + { + $this->optionset($user_row, 'viewimg', $data['view_images']); + $this->optionset($user_row, 'viewflash', $data['view_flash']); + $this->optionset($user_row, 'viewsmilies', $data['view_smilies']); + $this->optionset($user_row, 'viewsigs', $data['view_sigs']); + $this->optionset($user_row, 'viewavatars', $data['view_avatars']); + $this->optionset($user_row, 'viewcensors', $data['view_wordcensor']); + $this->optionset($user_row, 'bbcode', $data['bbcode']); + $this->optionset($user_row, 'smilies', $data['smilies']); + $this->optionset($user_row, 'attachsig', $data['sig']); + + $sql_ary = array( + 'user_options' => $user_row['user_options'], + + 'user_allow_pm' => $data['allowpm'], + 'user_allow_viewemail' => $data['viewemail'], + 'user_allow_massemail' => $data['massemail'], + 'user_allow_viewonline' => !$data['hideonline'], + 'user_notify_type' => $data['notifymethod'], + 'user_notify_pm' => $data['notifypm'], + + 'user_dateformat' => $data['dateformat'], + 'user_lang' => $data['lang'], + 'user_timezone' => $data['tz'], + 'user_style' => $data['style'], + + 'user_topic_sortby_type' => $data['topic_sk'], + 'user_post_sortby_type' => $data['post_sk'], + 'user_topic_sortby_dir' => $data['topic_sd'], + 'user_post_sortby_dir' => $data['post_sd'], + + 'user_topic_show_days' => $data['topic_st'], + 'user_post_show_days' => $data['post_st'], + + 'user_notify' => $data['notify'], + ); + + /** + * Modify SQL query before users preferences are updated + * + * @event core.acp_users_prefs_modify_sql + * @var array data Array with users preferences data + * @var array user_row Array with user data + * @var array sql_ary SQL array with users preferences data to update + * @var array error Array with errors data + * @since 3.1.0-b3 + */ + $vars = array('data', 'user_row', 'sql_ary', 'error'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_prefs_modify_sql', compact($vars))); + + if (!count($error)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " + WHERE user_id = $user_id"; + $db->sql_query($sql); + + // Check if user has an active session + if ($user_row['session_id']) + { + // We'll update the session if user_allow_viewonline has changed and the user is a bot + // Or if it's a regular user and the admin set it to hide the session + if ($user_row['user_allow_viewonline'] != $sql_ary['user_allow_viewonline'] && $user_row['user_type'] == USER_IGNORE + || $user_row['user_allow_viewonline'] && !$sql_ary['user_allow_viewonline']) + { + // We also need to check if the user has the permission to cloak. + $user_auth = new \phpbb\auth\auth(); + $user_auth->acl($user_row); + + $session_sql_ary = array( + 'session_viewonline' => ($user_auth->acl_get('u_hideonline')) ? $sql_ary['user_allow_viewonline'] : true, + ); + + $sql = 'UPDATE ' . SESSIONS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $session_sql_ary) . " + WHERE session_user_id = $user_id"; + $db->sql_query($sql); + + unset($user_auth); + } + } + + trigger_error($user->lang['USER_PREFS_UPDATED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + } + + $dateformat_options = ''; + foreach ($user->lang['dateformats'] as $format => $null) + { + $dateformat_options .= ''; + } + + $s_custom = false; + + $dateformat_options .= ''; + + $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']); + + // Topic ordering options + $limit_topic_days = array(0 => $user->lang['ALL_TOPICS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_topic_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 'r' => $user->lang['REPLIES'], 's' => $user->lang['SUBJECT'], 'v' => $user->lang['VIEWS']); + + // Post ordering options + $limit_post_days = array(0 => $user->lang['ALL_POSTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_post_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 's' => $user->lang['SUBJECT']); + + $_options = array('topic', 'post'); + foreach ($_options as $sort_option) + { + ${'s_limit_' . $sort_option . '_days'} = ''; + + ${'s_sort_' . $sort_option . '_key'} = ''; + + ${'s_sort_' . $sort_option . '_dir'} = ''; + } + + phpbb_timezone_select($template, $user, $data['tz'], true); + $user_prefs_data = array( + 'S_PREFS' => true, + 'S_JABBER_DISABLED' => ($config['jab_enable'] && $user_row['user_jabber'] && @extension_loaded('xml')) ? false : true, + + 'VIEW_EMAIL' => $data['viewemail'], + 'MASS_EMAIL' => $data['massemail'], + 'ALLOW_PM' => $data['allowpm'], + 'HIDE_ONLINE' => $data['hideonline'], + 'NOTIFY_EMAIL' => ($data['notifymethod'] == NOTIFY_EMAIL) ? true : false, + 'NOTIFY_IM' => ($data['notifymethod'] == NOTIFY_IM) ? true : false, + 'NOTIFY_BOTH' => ($data['notifymethod'] == NOTIFY_BOTH) ? true : false, + 'NOTIFY_PM' => $data['notifypm'], + 'BBCODE' => $data['bbcode'], + 'SMILIES' => $data['smilies'], + 'ATTACH_SIG' => $data['sig'], + 'NOTIFY' => $data['notify'], + 'VIEW_IMAGES' => $data['view_images'], + 'VIEW_FLASH' => $data['view_flash'], + 'VIEW_SMILIES' => $data['view_smilies'], + 'VIEW_SIGS' => $data['view_sigs'], + 'VIEW_AVATARS' => $data['view_avatars'], + 'VIEW_WORDCENSOR' => $data['view_wordcensor'], + + 'S_TOPIC_SORT_DAYS' => $s_limit_topic_days, + 'S_TOPIC_SORT_KEY' => $s_sort_topic_key, + 'S_TOPIC_SORT_DIR' => $s_sort_topic_dir, + 'S_POST_SORT_DAYS' => $s_limit_post_days, + 'S_POST_SORT_KEY' => $s_sort_post_key, + 'S_POST_SORT_DIR' => $s_sort_post_dir, + + 'DATE_FORMAT' => $data['dateformat'], + 'S_DATEFORMAT_OPTIONS' => $dateformat_options, + 'S_CUSTOM_DATEFORMAT' => $s_custom, + 'DEFAULT_DATEFORMAT' => $config['default_dateformat'], + 'A_DEFAULT_DATEFORMAT' => addslashes($config['default_dateformat']), + + 'S_LANG_OPTIONS' => language_select($data['lang']), + 'S_STYLE_OPTIONS' => style_select($data['style']), + ); + + /** + * Modify users preferences data before assigning it to the template + * + * @event core.acp_users_prefs_modify_template_data + * @var array data Array with users preferences data + * @var array user_row Array with user data + * @var array user_prefs_data Array with users preferences data to be assigned to the template + * @since 3.1.0-b3 + */ + $vars = array('data', 'user_row', 'user_prefs_data'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_prefs_modify_template_data', compact($vars))); + + $template->assign_vars($user_prefs_data); + + break; + + case 'avatar': + + $avatars_enabled = false; + /** @var \phpbb\avatar\manager $phpbb_avatar_manager */ + $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); + + if ($config['allow_avatar']) + { + $avatar_drivers = $phpbb_avatar_manager->get_enabled_drivers(); + + // This is normalised data, without the user_ prefix + $avatar_data = \phpbb\avatar\manager::clean_row($user_row, 'user'); + + if ($submit) + { + if (check_form_key($form_name)) + { + $driver_name = $phpbb_avatar_manager->clean_driver_name($request->variable('avatar_driver', '')); + + if (in_array($driver_name, $avatar_drivers) && !$request->is_set_post('avatar_delete')) + { + $driver = $phpbb_avatar_manager->get_driver($driver_name); + $result = $driver->process_form($request, $template, $user, $avatar_data, $error); + + if ($result && empty($error)) + { + // Success! Lets save the result in the database + $result = array( + 'user_avatar_type' => $driver_name, + 'user_avatar' => $result['avatar'], + 'user_avatar_width' => $result['avatar_width'], + 'user_avatar_height' => $result['avatar_height'], + ); + + /** + * Modify users preferences data before assigning it to the template + * + * @event core.acp_users_avatar_sql + * @var array user_row Array with user data + * @var array result Array with user avatar data to be updated in the DB + * @since 3.2.4-RC1 + */ + $vars = array('user_row', 'result'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_avatar_sql', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $result) . ' + WHERE user_id = ' . (int) $user_id; + + $db->sql_query($sql); + trigger_error($user->lang['USER_AVATAR_UPDATED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + } + } + else + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + } + + // Handle deletion of avatars + if ($request->is_set_post('avatar_delete')) + { + if (!confirm_box(true)) + { + confirm_box(false, $user->lang('CONFIRM_AVATAR_DELETE'), build_hidden_fields(array( + 'avatar_delete' => true)) + ); + } + else + { + $phpbb_avatar_manager->handle_avatar_delete($db, $user, $avatar_data, USERS_TABLE, 'user_'); + + trigger_error($user->lang['USER_AVATAR_UPDATED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + } + + $selected_driver = $phpbb_avatar_manager->clean_driver_name($request->variable('avatar_driver', $user_row['user_avatar_type'])); + + // Assign min and max values before generating avatar driver html + $template->assign_vars(array( + 'AVATAR_MIN_WIDTH' => $config['avatar_min_width'], + 'AVATAR_MAX_WIDTH' => $config['avatar_max_width'], + 'AVATAR_MIN_HEIGHT' => $config['avatar_min_height'], + 'AVATAR_MAX_HEIGHT' => $config['avatar_max_height'], + )); + + foreach ($avatar_drivers as $current_driver) + { + $driver = $phpbb_avatar_manager->get_driver($current_driver); + + $avatars_enabled = true; + $template->set_filenames(array( + 'avatar' => $driver->get_acp_template_name(), + )); + + if ($driver->prepare_form($request, $template, $user, $avatar_data, $error)) + { + $driver_name = $phpbb_avatar_manager->prepare_driver_name($current_driver); + $driver_upper = strtoupper($driver_name); + + $template->assign_block_vars('avatar_drivers', array( + 'L_TITLE' => $user->lang($driver_upper . '_TITLE'), + 'L_EXPLAIN' => $user->lang($driver_upper . '_EXPLAIN'), + + 'DRIVER' => $driver_name, + 'SELECTED' => $current_driver == $selected_driver, + 'OUTPUT' => $template->assign_display('avatar'), + )); + } + } + } + + // Avatar manager is not initialized if avatars are disabled + if (isset($phpbb_avatar_manager)) + { + // Replace "error" strings with their real, localised form + $error = $phpbb_avatar_manager->localize_errors($user, $error); + } + + $avatar = phpbb_get_user_avatar($user_row, 'USER_AVATAR', true); + + $template->assign_vars(array( + 'S_AVATAR' => true, + 'ERROR' => (!empty($error)) ? implode('
', $error) : '', + 'AVATAR' => (empty($avatar) ? '' : $avatar), + + 'S_FORM_ENCTYPE' => ' enctype="multipart/form-data"', + + 'L_AVATAR_EXPLAIN' => $user->lang(($config['avatar_filesize'] == 0) ? 'AVATAR_EXPLAIN_NO_FILESIZE' : 'AVATAR_EXPLAIN', $config['avatar_max_width'], $config['avatar_max_height'], $config['avatar_filesize'] / 1024), + + 'S_AVATARS_ENABLED' => ($config['allow_avatar'] && $avatars_enabled), + )); + + break; + + case 'rank': + + if ($submit) + { + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $rank_id = $request->variable('user_rank', 0); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_rank = $rank_id + WHERE user_id = $user_id"; + $db->sql_query($sql); + + trigger_error($user->lang['USER_RANK_UPDATED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + + $sql = 'SELECT * + FROM ' . RANKS_TABLE . ' + WHERE rank_special = 1 + ORDER BY rank_title'; + $result = $db->sql_query($sql); + + $s_rank_options = ''; + + while ($row = $db->sql_fetchrow($result)) + { + $selected = ($user_row['user_rank'] && $row['rank_id'] == $user_row['user_rank']) ? ' selected="selected"' : ''; + $s_rank_options .= ''; + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'S_RANK' => true, + 'S_RANK_OPTIONS' => $s_rank_options) + ); + + break; + + case 'sig': + + if (!function_exists('display_custom_bbcodes')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + $enable_bbcode = ($config['allow_sig_bbcode']) ? $this->optionget($user_row, 'sig_bbcode') : false; + $enable_smilies = ($config['allow_sig_smilies']) ? $this->optionget($user_row, 'sig_smilies') : false; + $enable_urls = ($config['allow_sig_links']) ? $this->optionget($user_row, 'sig_links') : false; + + $bbcode_flags = ($enable_bbcode ? OPTION_FLAG_BBCODE : 0) + ($enable_smilies ? OPTION_FLAG_SMILIES : 0) + ($enable_urls ? OPTION_FLAG_LINKS : 0); + + $decoded_message = generate_text_for_edit($user_row['user_sig'], $user_row['user_sig_bbcode_uid'], $bbcode_flags); + $signature = $request->variable('signature', $decoded_message['text'], true); + $signature_preview = ''; + + if ($submit || $request->is_set_post('preview')) + { + $enable_bbcode = ($config['allow_sig_bbcode']) ? !$request->variable('disable_bbcode', false) : false; + $enable_smilies = ($config['allow_sig_smilies']) ? !$request->variable('disable_smilies', false) : false; + $enable_urls = ($config['allow_sig_links']) ? !$request->variable('disable_magic_url', false) : false; + + if (!check_form_key($form_name)) + { + $error[] = 'FORM_INVALID'; + } + } + + $bbcode_uid = $bbcode_bitfield = $bbcode_flags = ''; + $warn_msg = generate_text_for_storage( + $signature, + $bbcode_uid, + $bbcode_bitfield, + $bbcode_flags, + $enable_bbcode, + $enable_urls, + $enable_smilies, + $config['allow_sig_img'], + $config['allow_sig_flash'], + true, + $config['allow_sig_links'], + 'sig' + ); + + if (count($warn_msg)) + { + $error += $warn_msg; + } + + if (!$submit) + { + // Parse it for displaying + $signature_preview = generate_text_for_display($signature, $bbcode_uid, $bbcode_bitfield, $bbcode_flags); + } + else + { + if (!count($error)) + { + $this->optionset($user_row, 'sig_bbcode', $enable_bbcode); + $this->optionset($user_row, 'sig_smilies', $enable_smilies); + $this->optionset($user_row, 'sig_links', $enable_urls); + + $sql_ary = array( + 'user_sig' => $signature, + 'user_options' => $user_row['user_options'], + 'user_sig_bbcode_uid' => $bbcode_uid, + 'user_sig_bbcode_bitfield' => $bbcode_bitfield, + ); + + /** + * Modify user signature before it is stored in the DB + * + * @event core.acp_users_modify_signature_sql_ary + * @var array user_row Array with user data + * @var array sql_ary Array with user signature data to be updated in the DB + * @since 3.2.4-RC1 + */ + $vars = array('user_row', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_modify_signature_sql_ary', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user_id; + $db->sql_query($sql); + + trigger_error($user->lang['USER_SIG_UPDATED'] . adm_back_link($this->u_action . '&u=' . $user_id)); + } + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + + if ($request->is_set_post('preview')) + { + $decoded_message = generate_text_for_edit($signature, $bbcode_uid, $bbcode_flags); + } + + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + + $template->assign_vars(array( + 'S_SIGNATURE' => true, + + 'SIGNATURE' => $decoded_message['text'], + 'SIGNATURE_PREVIEW' => $signature_preview, + + 'S_BBCODE_CHECKED' => (!$enable_bbcode) ? ' checked="checked"' : '', + 'S_SMILIES_CHECKED' => (!$enable_smilies) ? ' checked="checked"' : '', + 'S_MAGIC_URL_CHECKED' => (!$enable_urls) ? ' checked="checked"' : '', + + 'BBCODE_STATUS' => $user->lang(($config['allow_sig_bbcode'] ? 'BBCODE_IS_ON' : 'BBCODE_IS_OFF'), '', ''), + 'SMILIES_STATUS' => ($config['allow_sig_smilies']) ? $user->lang['SMILIES_ARE_ON'] : $user->lang['SMILIES_ARE_OFF'], + 'IMG_STATUS' => ($config['allow_sig_img']) ? $user->lang['IMAGES_ARE_ON'] : $user->lang['IMAGES_ARE_OFF'], + 'FLASH_STATUS' => ($config['allow_sig_flash']) ? $user->lang['FLASH_IS_ON'] : $user->lang['FLASH_IS_OFF'], + 'URL_STATUS' => ($config['allow_sig_links']) ? $user->lang['URL_IS_ON'] : $user->lang['URL_IS_OFF'], + + 'L_SIGNATURE_EXPLAIN' => $user->lang('SIGNATURE_EXPLAIN', (int) $config['max_sig_chars']), + + 'S_BBCODE_ALLOWED' => $config['allow_sig_bbcode'], + 'S_SMILIES_ALLOWED' => $config['allow_sig_smilies'], + 'S_BBCODE_IMG' => ($config['allow_sig_img']) ? true : false, + 'S_BBCODE_FLASH' => ($config['allow_sig_flash']) ? true : false, + 'S_LINKS_ALLOWED' => ($config['allow_sig_links']) ? true : false) + ); + + // Assigning custom bbcodes + display_custom_bbcodes(); + + break; + + case 'attach': + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + $start = $request->variable('start', 0); + $deletemark = (isset($_POST['delmarked'])) ? true : false; + $marked = $request->variable('mark', array(0)); + + // Sort keys + $sort_key = $request->variable('sk', 'a'); + $sort_dir = $request->variable('sd', 'd'); + + if ($deletemark && count($marked)) + { + $sql = 'SELECT attach_id + FROM ' . ATTACHMENTS_TABLE . ' + WHERE poster_id = ' . $user_id . ' + AND is_orphan = 0 + AND ' . $db->sql_in_set('attach_id', $marked); + $result = $db->sql_query($sql); + + $marked = array(); + while ($row = $db->sql_fetchrow($result)) + { + $marked[] = $row['attach_id']; + } + $db->sql_freeresult($result); + } + + if ($deletemark && count($marked)) + { + if (confirm_box(true)) + { + $sql = 'SELECT real_filename + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', $marked); + $result = $db->sql_query($sql); + + $log_attachments = array(); + while ($row = $db->sql_fetchrow($result)) + { + $log_attachments[] = $row['real_filename']; + } + $db->sql_freeresult($result); + + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $attachment_manager->delete('attach', $marked); + unset($attachment_manager); + + $message = (count($log_attachments) == 1) ? $user->lang['ATTACHMENT_DELETED'] : $user->lang['ATTACHMENTS_DELETED']; + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ATTACHMENTS_DELETED', false, array(implode($user->lang['COMMA_SEPARATOR'], $log_attachments))); + trigger_error($message . adm_back_link($this->u_action . '&u=' . $user_id)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'u' => $user_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'delmarked' => true, + 'mark' => $marked)) + ); + } + } + + $sk_text = array('a' => $user->lang['SORT_FILENAME'], 'c' => $user->lang['SORT_EXTENSION'], 'd' => $user->lang['SORT_SIZE'], 'e' => $user->lang['SORT_DOWNLOADS'], 'f' => $user->lang['SORT_POST_TIME'], 'g' => $user->lang['SORT_TOPIC_TITLE']); + $sk_sql = array('a' => 'a.real_filename', 'c' => 'a.extension', 'd' => 'a.filesize', 'e' => 'a.download_count', 'f' => 'a.filetime', 'g' => 't.topic_title'); + + $sd_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']); + + $s_sort_key = ''; + foreach ($sk_text as $key => $value) + { + $selected = ($sort_key == $key) ? ' selected="selected"' : ''; + $s_sort_key .= ''; + } + + $s_sort_dir = ''; + foreach ($sd_text as $key => $value) + { + $selected = ($sort_dir == $key) ? ' selected="selected"' : ''; + $s_sort_dir .= ''; + } + + if (!isset($sk_sql[$sort_key])) + { + $sort_key = 'a'; + } + + $order_by = $sk_sql[$sort_key] . ' ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'); + + $sql = 'SELECT COUNT(attach_id) as num_attachments + FROM ' . ATTACHMENTS_TABLE . " + WHERE poster_id = $user_id + AND is_orphan = 0"; + $result = $db->sql_query_limit($sql, 1); + $num_attachments = (int) $db->sql_fetchfield('num_attachments'); + $db->sql_freeresult($result); + + $sql = 'SELECT a.*, t.topic_title, p.message_subject as message_title + FROM ' . ATTACHMENTS_TABLE . ' a + LEFT JOIN ' . TOPICS_TABLE . ' t ON (a.topic_id = t.topic_id + AND a.in_message = 0) + LEFT JOIN ' . PRIVMSGS_TABLE . ' p ON (a.post_msg_id = p.msg_id + AND a.in_message = 1) + WHERE a.poster_id = ' . $user_id . " + AND a.is_orphan = 0 + ORDER BY $order_by"; + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['in_message']) + { + $view_topic = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&p={$row['post_msg_id']}"); + } + else + { + $view_topic = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t={$row['topic_id']}&p={$row['post_msg_id']}") . '#p' . $row['post_msg_id']; + } + + $template->assign_block_vars('attach', array( + 'REAL_FILENAME' => $row['real_filename'], + 'COMMENT' => nl2br($row['attach_comment']), + 'EXTENSION' => $row['extension'], + 'SIZE' => get_formatted_filesize($row['filesize']), + 'DOWNLOAD_COUNT' => $row['download_count'], + 'POST_TIME' => $user->format_date($row['filetime']), + 'TOPIC_TITLE' => ($row['in_message']) ? $row['message_title'] : $row['topic_title'], + + 'ATTACH_ID' => $row['attach_id'], + 'POST_ID' => $row['post_msg_id'], + 'TOPIC_ID' => $row['topic_id'], + + 'S_IN_MESSAGE' => $row['in_message'], + + 'U_DOWNLOAD' => append_sid("{$phpbb_root_path}download/file.$phpEx", 'mode=view&id=' . $row['attach_id']), + 'U_VIEW_TOPIC' => $view_topic) + ); + } + $db->sql_freeresult($result); + + $base_url = $this->u_action . "&u=$user_id&sk=$sort_key&sd=$sort_dir"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $num_attachments, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'S_ATTACHMENTS' => true, + 'S_SORT_KEY' => $s_sort_key, + 'S_SORT_DIR' => $s_sort_dir, + )); + + break; + + case 'groups': + + if (!function_exists('group_user_attributes')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $user->add_lang(array('groups', 'acp/groups')); + $group_id = $request->variable('g', 0); + + if ($group_id) + { + // Check the founder only entry for this group to make sure everything is well + $sql = 'SELECT group_founder_manage + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . $group_id; + $result = $db->sql_query($sql); + $founder_manage = (int) $db->sql_fetchfield('group_founder_manage'); + $db->sql_freeresult($result); + + if ($user->data['user_type'] != USER_FOUNDER && $founder_manage) + { + trigger_error($user->lang['NOT_ALLOWED_MANAGE_GROUP'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + } + + switch ($action) + { + case 'demote': + case 'promote': + case 'default': + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if (!check_link_hash($request->variable('hash', ''), 'acp_users')) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + group_user_attributes($action, $group_id, $user_id); + + if ($action == 'default') + { + $user_row['group_id'] = $group_id; + } + break; + + case 'delete': + + if (confirm_box(true)) + { + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if ($error = group_user_del($group_id, $user_id)) + { + trigger_error($user->lang[$error] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $error = array(); + + // The delete action was successful - therefore update the user row... + $sql = 'SELECT u.*, s.* + FROM ' . USERS_TABLE . ' u + LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id) + WHERE u.user_id = ' . $user_id . ' + ORDER BY s.session_time DESC'; + $result = $db->sql_query_limit($sql, 1); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'u' => $user_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'g' => $group_id)) + ); + } + + break; + + case 'approve': + + if (confirm_box(true)) + { + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + group_user_attributes($action, $group_id, $user_id); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'u' => $user_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action, + 'g' => $group_id)) + ); + } + + break; + } + + // Add user to group? + if ($submit) + { + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + // Add user/s to group + if ($error = group_user_add($group_id, $user_id)) + { + trigger_error($user->lang[$error] . adm_back_link($this->u_action . '&u=' . $user_id), E_USER_WARNING); + } + + $error = array(); + } + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $sql = 'SELECT ug.*, g.* + FROM ' . GROUPS_TABLE . ' g, ' . USER_GROUP_TABLE . " ug + WHERE ug.user_id = $user_id + AND g.group_id = ug.group_id + ORDER BY g.group_type DESC, ug.user_pending ASC, g.group_name"; + $result = $db->sql_query($sql); + + $i = 0; + $group_data = $id_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $type = ($row['group_type'] == GROUP_SPECIAL) ? 'special' : (($row['user_pending']) ? 'pending' : 'normal'); + + $group_data[$type][$i]['group_id'] = $row['group_id']; + $group_data[$type][$i]['group_name'] = $row['group_name']; + $group_data[$type][$i]['group_leader'] = ($row['group_leader']) ? 1 : 0; + + $id_ary[] = $row['group_id']; + + $i++; + } + $db->sql_freeresult($result); + + // Select box for other groups + $sql = 'SELECT group_id, group_name, group_type, group_founder_manage + FROM ' . GROUPS_TABLE . ' + ' . ((count($id_ary)) ? 'WHERE ' . $db->sql_in_set('group_id', $id_ary, true) : '') . ' + ORDER BY group_type DESC, group_name ASC'; + $result = $db->sql_query($sql); + + $s_group_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + if (!$config['coppa_enable'] && $row['group_name'] == 'REGISTERED_COPPA') + { + continue; + } + + // Do not display those groups not allowed to be managed + if ($user->data['user_type'] != USER_FOUNDER && $row['group_founder_manage']) + { + continue; + } + + $s_group_options .= '' . $group_helper->get_name($row['group_name']) . ''; + } + $db->sql_freeresult($result); + + $current_type = ''; + foreach ($group_data as $group_type => $data_ary) + { + if ($current_type != $group_type) + { + $template->assign_block_vars('group', array( + 'S_NEW_GROUP_TYPE' => true, + 'GROUP_TYPE' => $user->lang['USER_GROUP_' . strtoupper($group_type)]) + ); + } + + foreach ($data_ary as $data) + { + $template->assign_block_vars('group', array( + 'U_EDIT_GROUP' => append_sid("{$phpbb_admin_path}index.$phpEx", "i=groups&mode=manage&action=edit&u=$user_id&g={$data['group_id']}&back_link=acp_users_groups"), + 'U_DEFAULT' => $this->u_action . "&action=default&u=$user_id&g=" . $data['group_id'] . '&hash=' . generate_link_hash('acp_users'), + 'U_DEMOTE_PROMOTE' => $this->u_action . '&action=' . (($data['group_leader']) ? 'demote' : 'promote') . "&u=$user_id&g=" . $data['group_id'] . '&hash=' . generate_link_hash('acp_users'), + 'U_DELETE' => $this->u_action . "&action=delete&u=$user_id&g=" . $data['group_id'], + 'U_APPROVE' => ($group_type == 'pending') ? $this->u_action . "&action=approve&u=$user_id&g=" . $data['group_id'] : '', + + 'GROUP_NAME' => $group_helper->get_name($data['group_name']), + 'L_DEMOTE_PROMOTE' => ($data['group_leader']) ? $user->lang['GROUP_DEMOTE'] : $user->lang['GROUP_PROMOTE'], + + 'S_IS_MEMBER' => ($group_type != 'pending') ? true : false, + 'S_NO_DEFAULT' => ($user_row['group_id'] != $data['group_id']) ? true : false, + 'S_SPECIAL_GROUP' => ($group_type == 'special') ? true : false, + ) + ); + } + } + + $template->assign_vars(array( + 'S_GROUPS' => true, + 'S_GROUP_OPTIONS' => $s_group_options) + ); + + break; + + case 'perm': + + if (!class_exists('auth_admin')) + { + include($phpbb_root_path . 'includes/acp/auth.' . $phpEx); + } + + $auth_admin = new auth_admin(); + + $user->add_lang('acp/permissions'); + add_permission_language(); + + $forum_id = $request->variable('f', 0); + + // Global Permissions + if (!$forum_id) + { + // Select auth options + $sql = 'SELECT auth_option, is_local, is_global + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE auth_option ' . $db->sql_like_expression($db->get_any_char() . '_') . ' + AND is_global = 1 + ORDER BY auth_option'; + $result = $db->sql_query($sql); + + $hold_ary = array(); + + while ($row = $db->sql_fetchrow($result)) + { + $hold_ary = $auth_admin->get_mask('view', $user_id, false, false, $row['auth_option'], 'global', ACL_NEVER); + $auth_admin->display_mask('view', $row['auth_option'], $hold_ary, 'user', false, false); + } + $db->sql_freeresult($result); + + unset($hold_ary); + } + else + { + $sql = 'SELECT auth_option, is_local, is_global + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option " . $db->sql_like_expression($db->get_any_char() . '_') . " + AND is_local = 1 + ORDER BY is_global DESC, auth_option"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $hold_ary = $auth_admin->get_mask('view', $user_id, false, $forum_id, $row['auth_option'], 'local', ACL_NEVER); + $auth_admin->display_mask('view', $row['auth_option'], $hold_ary, 'user', true, false); + } + $db->sql_freeresult($result); + } + + $s_forum_options = ''; + $s_forum_options .= make_forum_select($forum_id, false, true, false, false, false); + + $template->assign_vars(array( + 'S_PERMISSIONS' => true, + + 'S_GLOBAL' => (!$forum_id) ? true : false, + 'S_FORUM_OPTIONS' => $s_forum_options, + + 'U_ACTION' => $this->u_action . '&u=' . $user_id, + 'U_USER_PERMISSIONS' => append_sid("{$phpbb_admin_path}index.$phpEx" ,'i=permissions&mode=setting_user_global&user_id[]=' . $user_id), + 'U_USER_FORUM_PERMISSIONS' => append_sid("{$phpbb_admin_path}index.$phpEx", 'i=permissions&mode=setting_user_local&user_id[]=' . $user_id)) + ); + + break; + + default: + + /** + * Additional modes provided by extensions + * + * @event core.acp_users_mode_add + * @var string mode New mode + * @var int user_id User id of the user to manage + * @var array user_row Array with user data + * @var array error Array with errors data + * @since 3.2.2-RC1 + */ + $vars = array('mode', 'user_id', 'user_row', 'error'); + extract($phpbb_dispatcher->trigger_event('core.acp_users_mode_add', compact($vars))); + + break; + } + + // Assign general variables + $template->assign_vars(array( + 'S_ERROR' => (count($error)) ? true : false, + 'ERROR_MSG' => (count($error)) ? implode('
', $error) : '') + ); + } + + /** + * Set option bit field for user options in a user row array. + * + * Optionset replacement for this module based on $user->optionset. + * + * @param array $user_row Row from the users table. + * @param int $key Option key, as defined in $user->keyoptions property. + * @param bool $value True to set the option, false to clear the option. + * @param int $data Current bit field value, or false to use $user_row['user_options'] + * @return int|bool If $data is false, the bit field is modified and + * written back to $user_row['user_options'], and + * return value is true if the bit field changed and + * false otherwise. If $data is not false, the new + * bitfield value is returned. + */ + function optionset(&$user_row, $key, $value, $data = false) + { + global $user; + + $var = ($data !== false) ? $data : $user_row['user_options']; + + $new_var = phpbb_optionset($user->keyoptions[$key], $value, $var); + + if ($data === false) + { + if ($new_var != $var) + { + $user_row['user_options'] = $new_var; + return true; + } + else + { + return false; + } + } + else + { + return $new_var; + } + } + + /** + * Get option bit field from user options in a user row array. + * + * Optionget replacement for this module based on $user->optionget. + * + * @param array $user_row Row from the users table. + * @param int $key option key, as defined in $user->keyoptions property. + * @param int $data bit field value to use, or false to use $user_row['user_options'] + * @return bool true if the option is set in the bit field, false otherwise + */ + function optionget(&$user_row, $key, $data = false) + { + global $user; + + $var = ($data !== false) ? $data : $user_row['user_options']; + return phpbb_optionget($user->keyoptions[$key], $var); + } +} diff --git a/includes/acp/acp_words.php b/includes/acp/acp_words.php new file mode 100644 index 0000000..e5eeb7a --- /dev/null +++ b/includes/acp/acp_words.php @@ -0,0 +1,191 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* @todo [words] check regular expressions for special char replacements (stored specialchared in db) +*/ +class acp_words +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $template, $cache, $phpbb_log, $request, $phpbb_container; + + $user->add_lang('acp/posting'); + + // Set up general vars + $action = $request->variable('action', ''); + $action = (isset($_POST['add'])) ? 'add' : ((isset($_POST['save'])) ? 'save' : $action); + + $s_hidden_fields = ''; + $word_info = array(); + + $this->tpl_name = 'acp_words'; + $this->page_title = 'ACP_WORDS'; + + $form_name = 'acp_words'; + add_form_key($form_name); + + switch ($action) + { + case 'edit': + + $word_id = $request->variable('id', 0); + + if (!$word_id) + { + trigger_error($user->lang['NO_WORD'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + $sql = 'SELECT * + FROM ' . WORDS_TABLE . " + WHERE word_id = $word_id"; + $result = $db->sql_query($sql); + $word_info = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $s_hidden_fields .= ''; + + case 'add': + + $template->assign_vars(array( + 'S_EDIT_WORD' => true, + 'U_ACTION' => $this->u_action, + 'U_BACK' => $this->u_action, + 'WORD' => (isset($word_info['word'])) ? $word_info['word'] : '', + 'REPLACEMENT' => (isset($word_info['replacement'])) ? $word_info['replacement'] : '', + 'S_HIDDEN_FIELDS' => $s_hidden_fields) + ); + + return; + + break; + + case 'save': + + if (!check_form_key($form_name)) + { + trigger_error($user->lang['FORM_INVALID']. adm_back_link($this->u_action), E_USER_WARNING); + } + + $word_id = $request->variable('id', 0); + $word = $request->variable('word', '', true); + $replacement = $request->variable('replacement', '', true); + + if ($word === '' || $replacement === '') + { + trigger_error($user->lang['ENTER_WORD'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + // Replace multiple consecutive asterisks with single one as those are not needed + $word = preg_replace('#\*{2,}#', '*', $word); + + $sql_ary = array( + 'word' => $word, + 'replacement' => $replacement + ); + + if ($word_id) + { + $db->sql_query('UPDATE ' . WORDS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' WHERE word_id = ' . $word_id); + } + else + { + $db->sql_query('INSERT INTO ' . WORDS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + } + + $cache->destroy('_word_censors'); + $phpbb_container->get('text_formatter.cache')->invalidate(); + + $log_action = ($word_id) ? 'LOG_WORD_EDIT' : 'LOG_WORD_ADD'; + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log_action, false, array($word)); + + $message = ($word_id) ? $user->lang['WORD_UPDATED'] : $user->lang['WORD_ADDED']; + trigger_error($message . adm_back_link($this->u_action)); + + break; + + case 'delete': + + $word_id = $request->variable('id', 0); + + if (!$word_id) + { + trigger_error($user->lang['NO_WORD'] . adm_back_link($this->u_action), E_USER_WARNING); + } + + if (confirm_box(true)) + { + $sql = 'SELECT word + FROM ' . WORDS_TABLE . " + WHERE word_id = $word_id"; + $result = $db->sql_query($sql); + $deleted_word = $db->sql_fetchfield('word'); + $db->sql_freeresult($result); + + $sql = 'DELETE FROM ' . WORDS_TABLE . " + WHERE word_id = $word_id"; + $db->sql_query($sql); + + $cache->destroy('_word_censors'); + $phpbb_container->get('text_formatter.cache')->invalidate(); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_WORD_DELETE', false, array($deleted_word)); + + trigger_error($user->lang['WORD_REMOVED'] . adm_back_link($this->u_action)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'id' => $word_id, + 'action' => 'delete', + ))); + } + + break; + } + + $template->assign_vars(array( + 'U_ACTION' => $this->u_action, + 'S_HIDDEN_FIELDS' => $s_hidden_fields) + ); + + $sql = 'SELECT * + FROM ' . WORDS_TABLE . ' + ORDER BY word'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('words', array( + 'WORD' => $row['word'], + 'REPLACEMENT' => $row['replacement'], + 'U_EDIT' => $this->u_action . '&action=edit&id=' . $row['word_id'], + 'U_DELETE' => $this->u_action . '&action=delete&id=' . $row['word_id']) + ); + } + $db->sql_freeresult($result); + } +} diff --git a/includes/acp/auth.php b/includes/acp/auth.php new file mode 100644 index 0000000..b414a31 --- /dev/null +++ b/includes/acp/auth.php @@ -0,0 +1,1323 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ACP Permission/Auth class +*/ +class auth_admin extends \phpbb\auth\auth +{ + /** + * Init auth settings + */ + function __construct() + { + global $db, $cache; + + if (($this->acl_options = $cache->get('_acl_options')) === false) + { + $sql = 'SELECT auth_option_id, auth_option, is_global, is_local + FROM ' . ACL_OPTIONS_TABLE . ' + ORDER BY auth_option_id'; + $result = $db->sql_query($sql); + + $global = $local = 0; + $this->acl_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['is_global']) + { + $this->acl_options['global'][$row['auth_option']] = $global++; + } + + if ($row['is_local']) + { + $this->acl_options['local'][$row['auth_option']] = $local++; + } + + $this->acl_options['id'][$row['auth_option']] = (int) $row['auth_option_id']; + $this->acl_options['option'][(int) $row['auth_option_id']] = $row['auth_option']; + } + $db->sql_freeresult($result); + + $cache->put('_acl_options', $this->acl_options); + } + } + + /** + * Get permission mask + * This function only supports getting permissions of one type (for example a_) + * + * @param set|view $mode defines the permissions we get, view gets effective permissions (checking user AND group permissions), set only gets the user or group permission set alone + * @param mixed $user_id user ids to search for (a user_id or a group_id has to be specified at least) + * @param mixed $group_id group ids to search for, return group related settings (a user_id or a group_id has to be specified at least) + * @param mixed $forum_id forum_ids to search for. Defining a forum id also means getting local settings + * @param string $auth_option the auth_option defines the permission setting to look for (a_ for example) + * @param local|global $scope the scope defines the permission scope. If local, a forum_id is additionally required + * @param ACL_NEVER|ACL_NO|ACL_YES $acl_fill defines the mode those permissions not set are getting filled with + */ + function get_mask($mode, $user_id = false, $group_id = false, $forum_id = false, $auth_option = false, $scope = false, $acl_fill = ACL_NEVER) + { + global $db, $user; + + $hold_ary = array(); + $view_user_mask = ($mode == 'view' && $group_id === false) ? true : false; + + if ($auth_option === false || $scope === false) + { + return array(); + } + + $acl_user_function = ($mode == 'set') ? 'acl_user_raw_data' : 'acl_raw_data'; + + if (!$view_user_mask) + { + if ($forum_id !== false) + { + $hold_ary = ($group_id !== false) ? $this->acl_group_raw_data($group_id, $auth_option . '%', $forum_id) : $this->$acl_user_function($user_id, $auth_option . '%', $forum_id); + } + else + { + $hold_ary = ($group_id !== false) ? $this->acl_group_raw_data($group_id, $auth_option . '%', ($scope == 'global') ? 0 : false) : $this->$acl_user_function($user_id, $auth_option . '%', ($scope == 'global') ? 0 : false); + } + } + + // Make sure hold_ary is filled with every setting (prevents missing forums/users/groups) + $ug_id = ($group_id !== false) ? ((!is_array($group_id)) ? array($group_id) : $group_id) : ((!is_array($user_id)) ? array($user_id) : $user_id); + $forum_ids = ($forum_id !== false) ? ((!is_array($forum_id)) ? array($forum_id) : $forum_id) : (($scope == 'global') ? array(0) : array()); + + // Only those options we need + $compare_options = array_diff(preg_replace('/^((?!' . $auth_option . ').+)|(' . $auth_option . ')$/', '', array_keys($this->acl_options[$scope])), array('')); + + // If forum_ids is false and the scope is local we actually want to have all forums within the array + if ($scope == 'local' && !count($forum_ids)) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE; + $result = $db->sql_query($sql, 120); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_ids[] = (int) $row['forum_id']; + } + $db->sql_freeresult($result); + } + + if ($view_user_mask) + { + $auth2 = null; + + $sql = 'SELECT user_id, user_permissions, user_type + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $ug_id); + $result = $db->sql_query($sql); + + while ($userdata = $db->sql_fetchrow($result)) + { + if ($user->data['user_id'] != $userdata['user_id']) + { + $auth2 = new \phpbb\auth\auth(); + $auth2->acl($userdata); + } + else + { + global $auth; + $auth2 = &$auth; + } + + $hold_ary[$userdata['user_id']] = array(); + foreach ($forum_ids as $f_id) + { + $hold_ary[$userdata['user_id']][$f_id] = array(); + foreach ($compare_options as $option) + { + $hold_ary[$userdata['user_id']][$f_id][$option] = $auth2->acl_get($option, $f_id); + } + } + } + $db->sql_freeresult($result); + + unset($userdata); + unset($auth2); + } + + foreach ($ug_id as $_id) + { + if (!isset($hold_ary[$_id])) + { + $hold_ary[$_id] = array(); + } + + foreach ($forum_ids as $f_id) + { + if (!isset($hold_ary[$_id][$f_id])) + { + $hold_ary[$_id][$f_id] = array(); + } + } + } + + // Now, we need to fill the gaps with $acl_fill. ;) + + // Now switch back to keys + if (count($compare_options)) + { + $compare_options = array_combine($compare_options, array_fill(1, count($compare_options), $acl_fill)); + } + + // Defining the user-function here to save some memory + $return_acl_fill = function () use ($acl_fill) + { + return $acl_fill; + }; + + // Actually fill the gaps + if (count($hold_ary)) + { + foreach ($hold_ary as $ug_id => $row) + { + foreach ($row as $id => $options) + { + // Do not include the global auth_option + unset($options[$auth_option]); + + // Not a "fine" solution, but at all it's a 1-dimensional + // array_diff_key function filling the resulting array values with zeros + // The differences get merged into $hold_ary (all permissions having $acl_fill set) + $hold_ary[$ug_id][$id] = array_merge($options, + + array_map($return_acl_fill, + array_flip( + array_diff( + array_keys($compare_options), array_keys($options) + ) + ) + ) + ); + } + } + } + else + { + $hold_ary[($group_id !== false) ? $group_id : $user_id][(int) $forum_id] = $compare_options; + } + + return $hold_ary; + } + + /** + * Get permission mask for roles + * This function only supports getting masks for one role + */ + function get_role_mask($role_id) + { + global $db; + + $hold_ary = array(); + + // Get users having this role set... + $sql = 'SELECT user_id, forum_id + FROM ' . ACL_USERS_TABLE . ' + WHERE auth_role_id = ' . $role_id . ' + ORDER BY forum_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $hold_ary[$row['forum_id']]['users'][] = $row['user_id']; + } + $db->sql_freeresult($result); + + // Now grab groups... + $sql = 'SELECT group_id, forum_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE auth_role_id = ' . $role_id . ' + ORDER BY forum_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $hold_ary[$row['forum_id']]['groups'][] = $row['group_id']; + } + $db->sql_freeresult($result); + + return $hold_ary; + } + + /** + * Display permission mask (assign to template) + */ + function display_mask($mode, $permission_type, &$hold_ary, $user_mode = 'user', $local = false, $group_display = true) + { + global $template, $user, $db, $phpbb_container; + + /* @var $phpbb_permissions \phpbb\permissions */ + $phpbb_permissions = $phpbb_container->get('acl.permissions'); + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + // Define names for template loops, might be able to be set + $tpl_pmask = 'p_mask'; + $tpl_fmask = 'f_mask'; + $tpl_category = 'category'; + $tpl_mask = 'mask'; + + $l_acl_type = $phpbb_permissions->get_type_lang($permission_type, (($local) ? 'local' : 'global')); + + // Allow trace for viewing permissions and in user mode + $show_trace = ($mode == 'view' && $user_mode == 'user') ? true : false; + + // Get names + if ($user_mode == 'user') + { + $sql = 'SELECT user_id as ug_id, username as ug_name + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', array_keys($hold_ary)) . ' + ORDER BY username_clean ASC'; + } + else + { + $sql = 'SELECT group_id as ug_id, group_name as ug_name, group_type + FROM ' . GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('group_id', array_keys($hold_ary)) . ' + ORDER BY group_type DESC, group_name ASC'; + } + $result = $db->sql_query($sql); + + $ug_names_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $ug_names_ary[$row['ug_id']] = ($user_mode == 'user') ? $row['ug_name'] : $group_helper->get_name($row['ug_name']); + } + $db->sql_freeresult($result); + + // Get used forums + $forum_ids = array(); + foreach ($hold_ary as $ug_id => $row) + { + $forum_ids = array_merge($forum_ids, array_keys($row)); + } + $forum_ids = array_unique($forum_ids); + + $forum_names_ary = array(); + if ($local) + { + $forum_names_ary = make_forum_select(false, false, true, false, false, false, true); + + // Remove the disabled ones, since we do not create an option field here... + foreach ($forum_names_ary as $key => $value) + { + if (!$value['disabled']) + { + continue; + } + unset($forum_names_ary[$key]); + } + } + else + { + $forum_names_ary[0] = $l_acl_type; + } + + // Get available roles + $sql = 'SELECT * + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $db->sql_escape($permission_type) . "' + ORDER BY role_order ASC"; + $result = $db->sql_query($sql); + + $roles = array(); + while ($row = $db->sql_fetchrow($result)) + { + $roles[$row['role_id']] = $row; + } + $db->sql_freeresult($result); + + $cur_roles = $this->acl_role_data($user_mode, $permission_type, array_keys($hold_ary)); + + // Build js roles array (role data assignments) + $s_role_js_array = ''; + + if (count($roles)) + { + $s_role_js_array = array(); + + // Make sure every role (even if empty) has its array defined + foreach ($roles as $_role_id => $null) + { + $s_role_js_array[$_role_id] = "\n" . 'role_options[' . $_role_id . '] = new Array();' . "\n"; + } + + $sql = 'SELECT r.role_id, o.auth_option, r.auth_setting + FROM ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' o + WHERE o.auth_option_id = r.auth_option_id + AND ' . $db->sql_in_set('r.role_id', array_keys($roles)); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $flag = substr($row['auth_option'], 0, strpos($row['auth_option'], '_') + 1); + if ($flag == $row['auth_option']) + { + continue; + } + + $s_role_js_array[$row['role_id']] .= 'role_options[' . $row['role_id'] . '][\'' . addslashes($row['auth_option']) . '\'] = ' . $row['auth_setting'] . '; '; + } + $db->sql_freeresult($result); + + $s_role_js_array = implode('', $s_role_js_array); + } + + $template->assign_var('S_ROLE_JS_ARRAY', $s_role_js_array); + unset($s_role_js_array); + + // Now obtain memberships + $user_groups_default = $user_groups_custom = array(); + if ($user_mode == 'user' && $group_display) + { + $sql = 'SELECT group_id, group_name, group_type + FROM ' . GROUPS_TABLE . ' + ORDER BY group_type DESC, group_name ASC'; + $result = $db->sql_query($sql); + + $groups = array(); + while ($row = $db->sql_fetchrow($result)) + { + $groups[$row['group_id']] = $row; + } + $db->sql_freeresult($result); + + $memberships = group_memberships(false, array_keys($hold_ary), false); + + // User is not a member of any group? Bad admin, bad bad admin... + if ($memberships) + { + foreach ($memberships as $row) + { + $user_groups_default[$row['user_id']][] = $group_helper->get_name($groups[$row['group_id']]['group_name']); + } + } + unset($memberships, $groups); + } + + // If we only have one forum id to display or being in local mode and more than one user/group to display, + // we switch the complete interface to group by user/usergroup instead of grouping by forum + // To achieve this, we need to switch the array a bit + if (count($forum_ids) == 1 || ($local && count($ug_names_ary) > 1)) + { + $hold_ary_temp = $hold_ary; + $hold_ary = array(); + foreach ($hold_ary_temp as $ug_id => $row) + { + foreach ($forum_names_ary as $forum_id => $forum_row) + { + if (isset($row[$forum_id])) + { + $hold_ary[$forum_id][$ug_id] = $row[$forum_id]; + } + } + } + unset($hold_ary_temp); + + foreach ($hold_ary as $forum_id => $forum_array) + { + $content_array = $categories = array(); + $this->build_permission_array($hold_ary[$forum_id], $content_array, $categories, array_keys($ug_names_ary)); + + $template->assign_block_vars($tpl_pmask, array( + 'NAME' => ($forum_id == 0) ? $forum_names_ary[0] : $forum_names_ary[$forum_id]['forum_name'], + 'PADDING' => ($forum_id == 0) ? '' : $forum_names_ary[$forum_id]['padding'], + + 'CATEGORIES' => implode('
', $categories), + + 'L_ACL_TYPE' => $l_acl_type, + + 'S_LOCAL' => ($local) ? true : false, + 'S_GLOBAL' => (!$local) ? true : false, + 'S_NUM_CATS' => count($categories), + 'S_VIEW' => ($mode == 'view') ? true : false, + 'S_NUM_OBJECTS' => count($content_array), + 'S_USER_MODE' => ($user_mode == 'user') ? true : false, + 'S_GROUP_MODE' => ($user_mode == 'group') ? true : false) + ); + + @reset($content_array); + while (list($ug_id, $ug_array) = each($content_array)) + { + // Build role dropdown options + $current_role_id = (isset($cur_roles[$ug_id][$forum_id])) ? $cur_roles[$ug_id][$forum_id] : 0; + + $role_options = array(); + + $s_role_options = ''; + $current_role_id = (isset($cur_roles[$ug_id][$forum_id])) ? $cur_roles[$ug_id][$forum_id] : 0; + + @reset($roles); + while (list($role_id, $role_row) = each($roles)) + { + $role_description = (!empty($user->lang[$role_row['role_description']])) ? $user->lang[$role_row['role_description']] : nl2br($role_row['role_description']); + $role_name = (!empty($user->lang[$role_row['role_name']])) ? $user->lang[$role_row['role_name']] : $role_row['role_name']; + + $title = ($role_description) ? ' title="' . $role_description . '"' : ''; + $s_role_options .= ''; + + $role_options[] = array( + 'ID' => $role_id, + 'ROLE_NAME' => $role_name, + 'TITLE' => $role_description, + 'SELECTED' => $role_id == $current_role_id, + ); + } + + if ($s_role_options) + { + $s_role_options = '' . $s_role_options; + } + + if (!$current_role_id && $mode != 'view') + { + $s_custom_permissions = false; + + foreach ($ug_array as $key => $value) + { + if ($value['S_NEVER'] || $value['S_YES']) + { + $s_custom_permissions = true; + break; + } + } + } + else + { + $s_custom_permissions = false; + } + + $template->assign_block_vars($tpl_pmask . '.' . $tpl_fmask, array( + 'NAME' => $ug_names_ary[$ug_id], + 'UG_ID' => $ug_id, + 'S_ROLE_OPTIONS' => $s_role_options, + 'S_CUSTOM' => $s_custom_permissions, + 'FORUM_ID' => $forum_id, + 'S_ROLE_ID' => $current_role_id, + )); + + $template->assign_block_vars_array($tpl_pmask . '.' . $tpl_fmask . '.role_options', $role_options); + + $this->assign_cat_array($ug_array, $tpl_pmask . '.' . $tpl_fmask . '.' . $tpl_category, $tpl_mask, $ug_id, $forum_id, ($mode == 'view'), $show_trace); + + unset($content_array[$ug_id]); + } + + unset($hold_ary[$forum_id]); + } + } + else + { + foreach ($ug_names_ary as $ug_id => $ug_name) + { + if (!isset($hold_ary[$ug_id])) + { + continue; + } + + $content_array = $categories = array(); + $this->build_permission_array($hold_ary[$ug_id], $content_array, $categories, array_keys($forum_names_ary)); + + $template->assign_block_vars($tpl_pmask, array( + 'NAME' => $ug_name, + 'CATEGORIES' => implode('', $categories), + + 'USER_GROUPS_DEFAULT' => ($user_mode == 'user' && isset($user_groups_default[$ug_id]) && count($user_groups_default[$ug_id])) ? implode($user->lang['COMMA_SEPARATOR'], $user_groups_default[$ug_id]) : '', + 'USER_GROUPS_CUSTOM' => ($user_mode == 'user' && isset($user_groups_custom[$ug_id]) && count($user_groups_custom[$ug_id])) ? implode($user->lang['COMMA_SEPARATOR'], $user_groups_custom[$ug_id]) : '', + 'L_ACL_TYPE' => $l_acl_type, + + 'S_LOCAL' => ($local) ? true : false, + 'S_GLOBAL' => (!$local) ? true : false, + 'S_NUM_CATS' => count($categories), + 'S_VIEW' => ($mode == 'view') ? true : false, + 'S_NUM_OBJECTS' => count($content_array), + 'S_USER_MODE' => ($user_mode == 'user') ? true : false, + 'S_GROUP_MODE' => ($user_mode == 'group') ? true : false) + ); + + @reset($content_array); + while (list($forum_id, $forum_array) = each($content_array)) + { + // Build role dropdown options + $current_role_id = (isset($cur_roles[$ug_id][$forum_id])) ? $cur_roles[$ug_id][$forum_id] : 0; + + $role_options = array(); + + $current_role_id = (isset($cur_roles[$ug_id][$forum_id])) ? $cur_roles[$ug_id][$forum_id] : 0; + $s_role_options = ''; + + @reset($roles); + while (list($role_id, $role_row) = each($roles)) + { + $role_description = (!empty($user->lang[$role_row['role_description']])) ? $user->lang[$role_row['role_description']] : nl2br($role_row['role_description']); + $role_name = (!empty($user->lang[$role_row['role_name']])) ? $user->lang[$role_row['role_name']] : $role_row['role_name']; + + $title = ($role_description) ? ' title="' . $role_description . '"' : ''; + $s_role_options .= ''; + + $role_options[] = array( + 'ID' => $role_id, + 'ROLE_NAME' => $role_name, + 'TITLE' => $role_description, + 'SELECTED' => $role_id == $current_role_id, + ); + } + + if ($s_role_options) + { + $s_role_options = '' . $s_role_options; + } + + if (!$current_role_id && $mode != 'view') + { + $s_custom_permissions = false; + + foreach ($forum_array as $key => $value) + { + if ($value['S_NEVER'] || $value['S_YES']) + { + $s_custom_permissions = true; + break; + } + } + } + else + { + $s_custom_permissions = false; + } + + $template->assign_block_vars($tpl_pmask . '.' . $tpl_fmask, array( + 'NAME' => ($forum_id == 0) ? $forum_names_ary[0] : $forum_names_ary[$forum_id]['forum_name'], + 'PADDING' => ($forum_id == 0) ? '' : $forum_names_ary[$forum_id]['padding'], + 'S_CUSTOM' => $s_custom_permissions, + 'UG_ID' => $ug_id, + 'S_ROLE_OPTIONS' => $s_role_options, + 'FORUM_ID' => $forum_id) + ); + + $template->assign_block_vars_array($tpl_pmask . '.' . $tpl_fmask . '.role_options', $role_options); + + $this->assign_cat_array($forum_array, $tpl_pmask . '.' . $tpl_fmask . '.' . $tpl_category, $tpl_mask, $ug_id, $forum_id, ($mode == 'view'), $show_trace); + } + + unset($hold_ary[$ug_id], $ug_names_ary[$ug_id]); + } + } + } + + /** + * Display permission mask for roles + */ + function display_role_mask(&$hold_ary) + { + global $db, $template, $user, $phpbb_root_path, $phpEx; + global $phpbb_container; + + if (!count($hold_ary)) + { + return; + } + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + // Get forum names + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', array_keys($hold_ary)) . ' + ORDER BY left_id'; + $result = $db->sql_query($sql); + + // If the role is used globally, then reflect that + $forum_names = (isset($hold_ary[0])) ? array(0 => '') : array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_names[$row['forum_id']] = $row['forum_name']; + } + $db->sql_freeresult($result); + + foreach ($forum_names as $forum_id => $forum_name) + { + $auth_ary = $hold_ary[$forum_id]; + + $template->assign_block_vars('role_mask', array( + 'NAME' => ($forum_id == 0) ? $user->lang['GLOBAL_MASK'] : $forum_name, + 'FORUM_ID' => $forum_id) + ); + + if (isset($auth_ary['users']) && count($auth_ary['users'])) + { + $sql = 'SELECT user_id, username + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $auth_ary['users']) . ' + ORDER BY username_clean ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('role_mask.users', array( + 'USER_ID' => $row['user_id'], + 'USERNAME' => get_username_string('username', $row['user_id'], $row['username']), + 'U_PROFILE' => get_username_string('profile', $row['user_id'], $row['username']), + )); + } + $db->sql_freeresult($result); + } + + if (isset($auth_ary['groups']) && count($auth_ary['groups'])) + { + $sql = 'SELECT group_id, group_name, group_type + FROM ' . GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('group_id', $auth_ary['groups']) . ' + ORDER BY group_type ASC, group_name'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('role_mask.groups', array( + 'GROUP_ID' => $row['group_id'], + 'GROUP_NAME' => $group_helper->get_name($row['group_name']), + 'U_PROFILE' => append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=group&g={$row['group_id']}")) + ); + } + $db->sql_freeresult($result); + } + } + } + + /** + * NOTE: this function is not in use atm + * Add a new option to the list ... $options is a hash of form -> + * $options = array( + * 'local' => array('option1', 'option2', ...), + * 'global' => array('optionA', 'optionB', ...) + * ); + */ + function acl_add_option($options) + { + global $db, $cache; + + if (!is_array($options)) + { + return false; + } + + $cur_options = array(); + + // Determine current options + $sql = 'SELECT auth_option, is_global, is_local + FROM ' . ACL_OPTIONS_TABLE . ' + ORDER BY auth_option_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $cur_options[$row['auth_option']] = ($row['is_global'] && $row['is_local']) ? 'both' : (($row['is_global']) ? 'global' : 'local'); + } + $db->sql_freeresult($result); + + // Here we need to insert new options ... this requires discovering whether + // an options is global, local or both and whether we need to add an permission + // set flag (x_) + $new_options = array('local' => array(), 'global' => array()); + + foreach ($options as $type => $option_ary) + { + $option_ary = array_unique($option_ary); + + foreach ($option_ary as $option_value) + { + $new_options[$type][] = $option_value; + + $flag = substr($option_value, 0, strpos($option_value, '_') + 1); + + if (!in_array($flag, $new_options[$type])) + { + $new_options[$type][] = $flag; + } + } + } + unset($options); + + $options = array(); + $options['local'] = array_diff($new_options['local'], $new_options['global']); + $options['global'] = array_diff($new_options['global'], $new_options['local']); + $options['both'] = array_intersect($new_options['local'], $new_options['global']); + + // Now check which options to add/update + $add_options = $update_options = array(); + + // First local ones... + foreach ($options as $type => $option_ary) + { + foreach ($option_ary as $option) + { + if (!isset($cur_options[$option])) + { + $add_options[] = array( + 'auth_option' => (string) $option, + 'is_global' => ($type == 'global' || $type == 'both') ? 1 : 0, + 'is_local' => ($type == 'local' || $type == 'both') ? 1 : 0 + ); + + continue; + } + + // Else, update existing entry if it is changed... + if ($type === $cur_options[$option]) + { + continue; + } + + // New type is always both: + // If is now both, we set both. + // If it was global the new one is local and we need to set it to both + // If it was local the new one is global and we need to set it to both + $update_options[] = $option; + } + } + + if (!empty($add_options)) + { + $db->sql_multi_insert(ACL_OPTIONS_TABLE, $add_options); + } + + if (!empty($update_options)) + { + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET is_global = 1, is_local = 1 + WHERE ' . $db->sql_in_set('auth_option', $update_options); + $db->sql_query($sql); + } + + $cache->destroy('_acl_options'); + $this->acl_clear_prefetch(); + + // Because we just changed the options and also purged the options cache, we instantly update/regenerate it for later calls to succeed. + $this->acl_options = array(); + $this->__construct(); + + return true; + } + + /** + * Set a user or group ACL record + */ + function acl_set($ug_type, $forum_id, $ug_id, $auth, $role_id = 0, $clear_prefetch = true) + { + global $db; + + // One or more forums + if (!is_array($forum_id)) + { + $forum_id = array($forum_id); + } + + // One or more users + if (!is_array($ug_id)) + { + $ug_id = array($ug_id); + } + + $ug_id_sql = $db->sql_in_set($ug_type . '_id', array_map('intval', $ug_id)); + $forum_sql = $db->sql_in_set('forum_id', array_map('intval', $forum_id)); + + // Instead of updating, inserting, removing we just remove all current settings and re-set everything... + $table = ($ug_type == 'user') ? ACL_USERS_TABLE : ACL_GROUPS_TABLE; + $id_field = $ug_type . '_id'; + + // Get any flags as required + reset($auth); + $flag = key($auth); + $flag = substr($flag, 0, strpos($flag, '_') + 1); + + // This ID (the any-flag) is set if one or more permissions are true... + $any_option_id = (int) $this->acl_options['id'][$flag]; + + // Remove any-flag from auth ary + if (isset($auth[$flag])) + { + unset($auth[$flag]); + } + + // Remove current auth options... + $auth_option_ids = array((int) $any_option_id); + foreach ($auth as $auth_option => $auth_setting) + { + $auth_option_ids[] = (int) $this->acl_options['id'][$auth_option]; + } + + $sql = "DELETE FROM $table + WHERE $forum_sql + AND $ug_id_sql + AND " . $db->sql_in_set('auth_option_id', $auth_option_ids); + $db->sql_query($sql); + + // Remove those having a role assigned... the correct type of course... + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $db->sql_escape($flag) . "'"; + $result = $db->sql_query($sql); + + $role_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $role_ids[] = $row['role_id']; + } + $db->sql_freeresult($result); + + if (count($role_ids)) + { + $sql = "DELETE FROM $table + WHERE $forum_sql + AND $ug_id_sql + AND auth_option_id = 0 + AND " . $db->sql_in_set('auth_role_id', $role_ids); + $db->sql_query($sql); + } + + // Ok, include the any-flag if one or more auth options are set to yes... + foreach ($auth as $auth_option => $setting) + { + if ($setting == ACL_YES && (!isset($auth[$flag]) || $auth[$flag] == ACL_NEVER)) + { + $auth[$flag] = ACL_YES; + } + } + + $sql_ary = array(); + foreach ($forum_id as $forum) + { + $forum = (int) $forum; + + if ($role_id) + { + foreach ($ug_id as $id) + { + $sql_ary[] = array( + $id_field => (int) $id, + 'forum_id' => (int) $forum, + 'auth_option_id' => 0, + 'auth_setting' => 0, + 'auth_role_id' => (int) $role_id, + ); + } + } + else + { + foreach ($auth as $auth_option => $setting) + { + $auth_option_id = (int) $this->acl_options['id'][$auth_option]; + + if ($setting != ACL_NO) + { + foreach ($ug_id as $id) + { + $sql_ary[] = array( + $id_field => (int) $id, + 'forum_id' => (int) $forum, + 'auth_option_id' => (int) $auth_option_id, + 'auth_setting' => (int) $setting + ); + } + } + } + } + } + + $db->sql_multi_insert($table, $sql_ary); + + if ($clear_prefetch) + { + $this->acl_clear_prefetch(); + } + } + + /** + * Set a role-specific ACL record + */ + function acl_set_role($role_id, $auth) + { + global $db; + + // Get any-flag as required + reset($auth); + $flag = key($auth); + $flag = substr($flag, 0, strpos($flag, '_') + 1); + + // Remove any-flag from auth ary + if (isset($auth[$flag])) + { + unset($auth[$flag]); + } + + // Re-set any flag... + foreach ($auth as $auth_option => $setting) + { + if ($setting == ACL_YES && (!isset($auth[$flag]) || $auth[$flag] == ACL_NEVER)) + { + $auth[$flag] = ACL_YES; + } + } + + $sql_ary = array(); + foreach ($auth as $auth_option => $setting) + { + $auth_option_id = (int) $this->acl_options['id'][$auth_option]; + + if ($setting != ACL_NO) + { + $sql_ary[] = array( + 'role_id' => (int) $role_id, + 'auth_option_id' => (int) $auth_option_id, + 'auth_setting' => (int) $setting + ); + } + } + + // If no data is there, we set the any-flag to ACL_NEVER... + if (!count($sql_ary)) + { + $sql_ary[] = array( + 'role_id' => (int) $role_id, + 'auth_option_id' => (int) $this->acl_options['id'][$flag], + 'auth_setting' => ACL_NEVER + ); + } + + // Remove current auth options... + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $db->sql_query($sql); + + // Now insert the new values + $db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); + + $this->acl_clear_prefetch(); + } + + /** + * Remove local permission + */ + function acl_delete($mode, $ug_id = false, $forum_id = false, $permission_type = false) + { + global $db; + + if ($ug_id === false && $forum_id === false) + { + return; + } + + $option_id_ary = array(); + $table = ($mode == 'user') ? ACL_USERS_TABLE : ACL_GROUPS_TABLE; + $id_field = $mode . '_id'; + + $where_sql = array(); + + if ($forum_id !== false) + { + $where_sql[] = (!is_array($forum_id)) ? 'forum_id = ' . (int) $forum_id : $db->sql_in_set('forum_id', array_map('intval', $forum_id)); + } + + if ($ug_id !== false) + { + $where_sql[] = (!is_array($ug_id)) ? $id_field . ' = ' . (int) $ug_id : $db->sql_in_set($id_field, array_map('intval', $ug_id)); + } + + // There seem to be auth options involved, therefore we need to go through the list and make sure we capture roles correctly + if ($permission_type !== false) + { + // Get permission type + $sql = 'SELECT auth_option, auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option " . $db->sql_like_expression($permission_type . $db->get_any_char()); + $result = $db->sql_query($sql); + + $auth_id_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $option_id_ary[] = $row['auth_option_id']; + $auth_id_ary[$row['auth_option']] = ACL_NO; + } + $db->sql_freeresult($result); + + // First of all, lets grab the items having roles with the specified auth options assigned + $sql = "SELECT auth_role_id, $id_field, forum_id + FROM $table, " . ACL_ROLES_TABLE . " r + WHERE auth_role_id <> 0 + AND auth_role_id = r.role_id + AND r.role_type = '{$permission_type}' + AND " . implode(' AND ', $where_sql) . ' + ORDER BY auth_role_id'; + $result = $db->sql_query($sql); + + $cur_role_auth = array(); + while ($row = $db->sql_fetchrow($result)) + { + $cur_role_auth[$row['auth_role_id']][$row['forum_id']][] = $row[$id_field]; + } + $db->sql_freeresult($result); + + // Get role data for resetting data + if (count($cur_role_auth)) + { + $sql = 'SELECT ao.auth_option, rd.role_id, rd.auth_setting + FROM ' . ACL_OPTIONS_TABLE . ' ao, ' . ACL_ROLES_DATA_TABLE . ' rd + WHERE ao.auth_option_id = rd.auth_option_id + AND ' . $db->sql_in_set('rd.role_id', array_keys($cur_role_auth)); + $result = $db->sql_query($sql); + + $auth_settings = array(); + while ($row = $db->sql_fetchrow($result)) + { + // We need to fill all auth_options, else setting it will fail... + if (!isset($auth_settings[$row['role_id']])) + { + $auth_settings[$row['role_id']] = $auth_id_ary; + } + $auth_settings[$row['role_id']][$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + + // Set the options + foreach ($cur_role_auth as $role_id => $auth_row) + { + foreach ($auth_row as $f_id => $ug_row) + { + $this->acl_set($mode, $f_id, $ug_row, $auth_settings[$role_id], 0, false); + } + } + } + } + + // Now, normally remove permissions... + if ($permission_type !== false) + { + $where_sql[] = $db->sql_in_set('auth_option_id', array_map('intval', $option_id_ary)); + } + + $sql = "DELETE FROM $table + WHERE " . implode(' AND ', $where_sql); + $db->sql_query($sql); + + $this->acl_clear_prefetch(); + } + + /** + * Assign category to template + * used by display_mask() + */ + function assign_cat_array(&$category_array, $tpl_cat, $tpl_mask, $ug_id, $forum_id, $s_view, $show_trace = false) + { + global $template, $phpbb_admin_path, $phpEx, $phpbb_container; + + /* @var $phpbb_permissions \phpbb\permissions */ + $phpbb_permissions = $phpbb_container->get('acl.permissions'); + + @reset($category_array); + while (list($cat, $cat_array) = each($category_array)) + { + if (!$phpbb_permissions->category_defined($cat)) + { + continue; + } + + $template->assign_block_vars($tpl_cat, array( + 'S_YES' => ($cat_array['S_YES'] && !$cat_array['S_NEVER'] && !$cat_array['S_NO']) ? true : false, + 'S_NEVER' => ($cat_array['S_NEVER'] && !$cat_array['S_YES'] && !$cat_array['S_NO']) ? true : false, + 'S_NO' => ($cat_array['S_NO'] && !$cat_array['S_NEVER'] && !$cat_array['S_YES']) ? true : false, + + 'CAT_NAME' => $phpbb_permissions->get_category_lang($cat), + )); + + /* Sort permissions by name (more naturaly and user friendly than sorting by a primary key) + * Commented out due to it's memory consumption and time needed + * + $key_array = array_intersect(array_keys($user->lang), array_map(create_function('$a', 'return "acl_" . $a;'), array_keys($cat_array['permissions']))); + $values_array = $cat_array['permissions']; + + $cat_array['permissions'] = array(); + + foreach ($key_array as $key) + { + $key = str_replace('acl_', '', $key); + $cat_array['permissions'][$key] = $values_array[$key]; + } + unset($key_array, $values_array); +*/ + @reset($cat_array['permissions']); + while (list($permission, $allowed) = each($cat_array['permissions'])) + { + if (!$phpbb_permissions->permission_defined($permission)) + { + continue; + } + + if ($s_view) + { + $template->assign_block_vars($tpl_cat . '.' . $tpl_mask, array( + 'S_YES' => ($allowed == ACL_YES) ? true : false, + 'S_NEVER' => ($allowed == ACL_NEVER) ? true : false, + + 'UG_ID' => $ug_id, + 'FORUM_ID' => $forum_id, + 'FIELD_NAME' => $permission, + 'S_FIELD_NAME' => 'setting[' . $ug_id . '][' . $forum_id . '][' . $permission . ']', + + 'U_TRACE' => ($show_trace) ? append_sid("{$phpbb_admin_path}index.$phpEx", "i=permissions&mode=trace&u=$ug_id&f=$forum_id&auth=$permission") : '', + 'UA_TRACE' => ($show_trace) ? append_sid("{$phpbb_admin_path}index.$phpEx", "i=permissions&mode=trace&u=$ug_id&f=$forum_id&auth=$permission", false) : '', + + 'PERMISSION' => $phpbb_permissions->get_permission_lang($permission), + )); + } + else + { + $template->assign_block_vars($tpl_cat . '.' . $tpl_mask, array( + 'S_YES' => ($allowed == ACL_YES) ? true : false, + 'S_NEVER' => ($allowed == ACL_NEVER) ? true : false, + 'S_NO' => ($allowed == ACL_NO) ? true : false, + + 'UG_ID' => $ug_id, + 'FORUM_ID' => $forum_id, + 'FIELD_NAME' => $permission, + 'S_FIELD_NAME' => 'setting[' . $ug_id . '][' . $forum_id . '][' . $permission . ']', + + 'U_TRACE' => ($show_trace) ? append_sid("{$phpbb_admin_path}index.$phpEx", "i=permissions&mode=trace&u=$ug_id&f=$forum_id&auth=$permission") : '', + 'UA_TRACE' => ($show_trace) ? append_sid("{$phpbb_admin_path}index.$phpEx", "i=permissions&mode=trace&u=$ug_id&f=$forum_id&auth=$permission", false) : '', + + 'PERMISSION' => $phpbb_permissions->get_permission_lang($permission), + )); + } + } + } + } + + /** + * Building content array from permission rows with explicit key ordering + * used by display_mask() + */ + function build_permission_array(&$permission_row, &$content_array, &$categories, $key_sort_array) + { + global $phpbb_container; + + /* @var $phpbb_permissions \phpbb\permissions */ + $phpbb_permissions = $phpbb_container->get('acl.permissions'); + + foreach ($key_sort_array as $forum_id) + { + if (!isset($permission_row[$forum_id])) + { + continue; + } + + $permissions = $permission_row[$forum_id]; + ksort($permissions); + + @reset($permissions); + while (list($permission, $auth_setting) = each($permissions)) + { + $cat = $phpbb_permissions->get_permission_category($permission); + + // Build our categories array + if (!isset($categories[$cat])) + { + $categories[$cat] = $phpbb_permissions->get_category_lang($cat); + } + + // Build our content array + if (!isset($content_array[$forum_id])) + { + $content_array[$forum_id] = array(); + } + + if (!isset($content_array[$forum_id][$cat])) + { + $content_array[$forum_id][$cat] = array( + 'S_YES' => false, + 'S_NEVER' => false, + 'S_NO' => false, + 'permissions' => array(), + ); + } + + $content_array[$forum_id][$cat]['S_YES'] |= ($auth_setting == ACL_YES) ? true : false; + $content_array[$forum_id][$cat]['S_NEVER'] |= ($auth_setting == ACL_NEVER) ? true : false; + $content_array[$forum_id][$cat]['S_NO'] |= ($auth_setting == ACL_NO) ? true : false; + + $content_array[$forum_id][$cat]['permissions'][$permission] = $auth_setting; + } + } + } + + /** + * Use permissions from another user. This transferes a permission set from one user to another. + * The other user is always able to revert back to his permission set. + * This function does not check for lower/higher permissions, it is possible for the user to gain + * "more" permissions by this. + * Admin permissions will not be copied. + */ + function ghost_permissions($from_user_id, $to_user_id) + { + global $db; + + if ($to_user_id == ANONYMOUS) + { + return false; + } + + $hold_ary = $this->acl_raw_data_single_user($from_user_id); + + // Key 0 in $hold_ary are global options, all others are forum_ids + + // We disallow copying admin permissions + foreach ($this->acl_options['global'] as $opt => $id) + { + if (strpos($opt, 'a_') === 0) + { + $hold_ary[0][$this->acl_options['id'][$opt]] = ACL_NEVER; + } + } + + // Force a_switchperm to be allowed + $hold_ary[0][$this->acl_options['id']['a_switchperm']] = ACL_YES; + + $user_permissions = $this->build_bitstring($hold_ary); + + if (!$user_permissions) + { + return false; + } + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_permissions = '" . $db->sql_escape($user_permissions) . "', + user_perm_from = $from_user_id + WHERE user_id = " . $to_user_id; + $db->sql_query($sql); + + return true; + } +} diff --git a/includes/acp/info/acp_attachments.php b/includes/acp/info/acp_attachments.php new file mode 100644 index 0000000..057f082 --- /dev/null +++ b/includes/acp/info/acp_attachments.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_attachments_info +{ + function module() + { + return array( + 'filename' => 'acp_attachments', + 'title' => 'ACP_ATTACHMENTS', + 'modes' => array( + 'attach' => array('title' => 'ACP_ATTACHMENT_SETTINGS', 'auth' => 'acl_a_attach', 'cat' => array('ACP_BOARD_CONFIGURATION', 'ACP_ATTACHMENTS')), + 'extensions' => array('title' => 'ACP_MANAGE_EXTENSIONS', 'auth' => 'acl_a_attach', 'cat' => array('ACP_ATTACHMENTS')), + 'ext_groups' => array('title' => 'ACP_EXTENSION_GROUPS', 'auth' => 'acl_a_attach', 'cat' => array('ACP_ATTACHMENTS')), + 'orphan' => array('title' => 'ACP_ORPHAN_ATTACHMENTS', 'auth' => 'acl_a_attach', 'cat' => array('ACP_ATTACHMENTS')), + 'manage' => array('title' => 'ACP_MANAGE_ATTACHMENTS', 'auth' => 'acl_a_attach', 'cat' => array('ACP_ATTACHMENTS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_ban.php b/includes/acp/info/acp_ban.php new file mode 100644 index 0000000..c88f4c2 --- /dev/null +++ b/includes/acp/info/acp_ban.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_ban_info +{ + function module() + { + return array( + 'filename' => 'acp_ban', + 'title' => 'ACP_BAN', + 'modes' => array( + 'email' => array('title' => 'ACP_BAN_EMAILS', 'auth' => 'acl_a_ban', 'cat' => array('ACP_USER_SECURITY')), + 'ip' => array('title' => 'ACP_BAN_IPS', 'auth' => 'acl_a_ban', 'cat' => array('ACP_USER_SECURITY')), + 'user' => array('title' => 'ACP_BAN_USERNAMES', 'auth' => 'acl_a_ban', 'cat' => array('ACP_USER_SECURITY')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_bbcodes.php b/includes/acp/info/acp_bbcodes.php new file mode 100644 index 0000000..dfcd43a --- /dev/null +++ b/includes/acp/info/acp_bbcodes.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_bbcodes_info +{ + function module() + { + return array( + 'filename' => 'acp_bbcodes', + 'title' => 'ACP_BBCODES', + 'modes' => array( + 'bbcodes' => array('title' => 'ACP_BBCODES', 'auth' => 'acl_a_bbcode', 'cat' => array('ACP_MESSAGES')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_board.php b/includes/acp/info/acp_board.php new file mode 100644 index 0000000..1a3ee7b --- /dev/null +++ b/includes/acp/info/acp_board.php @@ -0,0 +1,49 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_board_info +{ + function module() + { + return array( + 'filename' => 'acp_board', + 'title' => 'ACP_BOARD_MANAGEMENT', + 'modes' => array( + 'settings' => array('title' => 'ACP_BOARD_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION')), + 'features' => array('title' => 'ACP_BOARD_FEATURES', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION')), + 'avatar' => array('title' => 'ACP_AVATAR_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION')), + 'message' => array('title' => 'ACP_MESSAGE_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION', 'ACP_MESSAGES')), + 'post' => array('title' => 'ACP_POST_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION', 'ACP_MESSAGES')), + 'signature' => array('title' => 'ACP_SIGNATURE_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION')), + 'feed' => array('title' => 'ACP_FEED_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION')), + 'registration' => array('title' => 'ACP_REGISTER_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION')), + + 'auth' => array('title' => 'ACP_AUTH_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_CLIENT_COMMUNICATION')), + 'email' => array('title' => 'ACP_EMAIL_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_CLIENT_COMMUNICATION')), + + 'cookie' => array('title' => 'ACP_COOKIE_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_SERVER_CONFIGURATION')), + 'server' => array('title' => 'ACP_SERVER_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_SERVER_CONFIGURATION')), + 'security' => array('title' => 'ACP_SECURITY_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_SERVER_CONFIGURATION')), + 'load' => array('title' => 'ACP_LOAD_SETTINGS', 'auth' => 'acl_a_server', 'cat' => array('ACP_SERVER_CONFIGURATION')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_bots.php b/includes/acp/info/acp_bots.php new file mode 100644 index 0000000..26782d8 --- /dev/null +++ b/includes/acp/info/acp_bots.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_bots_info +{ + function module() + { + return array( + 'filename' => 'acp_bots', + 'title' => 'ACP_BOTS', + 'modes' => array( + 'bots' => array('title' => 'ACP_BOTS', 'auth' => 'acl_a_bots', 'cat' => array('ACP_GENERAL_TASKS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_captcha.php b/includes/acp/info/acp_captcha.php new file mode 100644 index 0000000..3f7bf03 --- /dev/null +++ b/includes/acp/info/acp_captcha.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_captcha_info +{ + function module() + { + return array( + 'filename' => 'acp_captcha', + 'title' => 'ACP_CAPTCHA', + 'modes' => array( + 'visual' => array('title' => 'ACP_VC_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION')), + 'img' => array('title' => 'ACP_VC_CAPTCHA_DISPLAY', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION'), 'display' => false) + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_contact.php b/includes/acp/info/acp_contact.php new file mode 100644 index 0000000..548eb52 --- /dev/null +++ b/includes/acp/info/acp_contact.php @@ -0,0 +1,30 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @package module_install +*/ +class acp_contact_info +{ + public function module() + { + return array( + 'filename' => 'acp_contact', + 'title' => 'ACP_CONTACT', + 'version' => '1.0.0', + 'modes' => array( + 'contact' => array('title' => 'ACP_CONTACT_SETTINGS', 'auth' => 'acl_a_board', 'cat' => array('ACP_BOARD_CONFIGURATION')), + ), + ); + } +} diff --git a/includes/acp/info/acp_database.php b/includes/acp/info/acp_database.php new file mode 100644 index 0000000..815db53 --- /dev/null +++ b/includes/acp/info/acp_database.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_database_info +{ + function module() + { + return array( + 'filename' => 'acp_database', + 'title' => 'ACP_DATABASE', + 'modes' => array( + 'backup' => array('title' => 'ACP_BACKUP', 'auth' => 'acl_a_backup', 'cat' => array('ACP_CAT_DATABASE')), + 'restore' => array('title' => 'ACP_RESTORE', 'auth' => 'acl_a_backup', 'cat' => array('ACP_CAT_DATABASE')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_disallow.php b/includes/acp/info/acp_disallow.php new file mode 100644 index 0000000..df4765b --- /dev/null +++ b/includes/acp/info/acp_disallow.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_disallow_info +{ + function module() + { + return array( + 'filename' => 'acp_disallow', + 'title' => 'ACP_DISALLOW', + 'modes' => array( + 'usernames' => array('title' => 'ACP_DISALLOW_USERNAMES', 'auth' => 'acl_a_names', 'cat' => array('ACP_USER_SECURITY')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_email.php b/includes/acp/info/acp_email.php new file mode 100644 index 0000000..e85ef09 --- /dev/null +++ b/includes/acp/info/acp_email.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_email_info +{ + function module() + { + return array( + 'filename' => 'acp_email', + 'title' => 'ACP_MASS_EMAIL', + 'modes' => array( + 'email' => array('title' => 'ACP_MASS_EMAIL', 'auth' => 'acl_a_email && cfg_email_enable', 'cat' => array('ACP_GENERAL_TASKS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_extensions.php b/includes/acp/info/acp_extensions.php new file mode 100644 index 0000000..9adcd54 --- /dev/null +++ b/includes/acp/info/acp_extensions.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_extensions_info +{ + function module() + { + return array( + 'filename' => 'acp_extensions', + 'title' => 'ACP_EXTENSION_MANAGEMENT', + 'modes' => array( + 'main' => array('title' => 'ACP_EXTENSIONS', 'auth' => 'acl_a_extensions', 'cat' => array('ACP_EXTENSION_MANAGEMENT')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_forums.php b/includes/acp/info/acp_forums.php new file mode 100644 index 0000000..8b5ce7e --- /dev/null +++ b/includes/acp/info/acp_forums.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_forums_info +{ + function module() + { + return array( + 'filename' => 'acp_forums', + 'title' => 'ACP_FORUM_MANAGEMENT', + 'modes' => array( + 'manage' => array('title' => 'ACP_MANAGE_FORUMS', 'auth' => 'acl_a_forum', 'cat' => array('ACP_MANAGE_FORUMS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_groups.php b/includes/acp/info/acp_groups.php new file mode 100644 index 0000000..e0aafec --- /dev/null +++ b/includes/acp/info/acp_groups.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_groups_info +{ + function module() + { + return array( + 'filename' => 'acp_groups', + 'title' => 'ACP_GROUPS_MANAGEMENT', + 'modes' => array( + 'manage' => array('title' => 'ACP_GROUPS_MANAGE', 'auth' => 'acl_a_group', 'cat' => array('ACP_GROUPS')), + 'position' => array('title' => 'ACP_GROUPS_POSITION', 'auth' => 'acl_a_group', 'cat' => array('ACP_GROUPS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_help_phpbb.php b/includes/acp/info/acp_help_phpbb.php new file mode 100644 index 0000000..dee8ef4 --- /dev/null +++ b/includes/acp/info/acp_help_phpbb.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_help_phpbb_info +{ + function module() + { + return array( + 'filename' => 'acp_help_phpbb', + 'title' => 'ACP_HELP_PHPBB', + 'modes' => array( + 'help_phpbb' => array('title' => 'ACP_HELP_PHPBB', 'auth' => 'acl_a_server', 'cat' => array('ACP_SERVER_CONFIGURATION')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_icons.php b/includes/acp/info/acp_icons.php new file mode 100644 index 0000000..87eaddd --- /dev/null +++ b/includes/acp/info/acp_icons.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_icons_info +{ + function module() + { + return array( + 'filename' => 'acp_icons', + 'title' => 'ACP_ICONS_SMILIES', + 'modes' => array( + 'icons' => array('title' => 'ACP_ICONS', 'auth' => 'acl_a_icons', 'cat' => array('ACP_MESSAGES')), + 'smilies' => array('title' => 'ACP_SMILIES', 'auth' => 'acl_a_icons', 'cat' => array('ACP_MESSAGES')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_inactive.php b/includes/acp/info/acp_inactive.php new file mode 100644 index 0000000..38cb964 --- /dev/null +++ b/includes/acp/info/acp_inactive.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_inactive_info +{ + function module() + { + return array( + 'filename' => 'acp_inactive', + 'title' => 'ACP_INACTIVE_USERS', + 'modes' => array( + 'list' => array('title' => 'ACP_INACTIVE_USERS', 'auth' => 'acl_a_user', 'cat' => array('ACP_CAT_USERS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_jabber.php b/includes/acp/info/acp_jabber.php new file mode 100644 index 0000000..660299a --- /dev/null +++ b/includes/acp/info/acp_jabber.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_jabber_info +{ + function module() + { + return array( + 'filename' => 'acp_jabber', + 'title' => 'ACP_JABBER_SETTINGS', + 'modes' => array( + 'settings' => array('title' => 'ACP_JABBER_SETTINGS', 'auth' => 'acl_a_jabber', 'cat' => array('ACP_CLIENT_COMMUNICATION')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_language.php b/includes/acp/info/acp_language.php new file mode 100644 index 0000000..1a5a2b6 --- /dev/null +++ b/includes/acp/info/acp_language.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_language_info +{ + function module() + { + return array( + 'filename' => 'acp_language', + 'title' => 'ACP_LANGUAGE', + 'modes' => array( + 'lang_packs' => array('title' => 'ACP_LANGUAGE_PACKS', 'auth' => 'acl_a_language', 'cat' => array('ACP_LANGUAGE')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_logs.php b/includes/acp/info/acp_logs.php new file mode 100644 index 0000000..1be7b28 --- /dev/null +++ b/includes/acp/info/acp_logs.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_logs_info +{ + function module() + { + global $phpbb_dispatcher; + + $modes = array( + 'admin' => array('title' => 'ACP_ADMIN_LOGS', 'auth' => 'acl_a_viewlogs', 'cat' => array('ACP_FORUM_LOGS')), + 'mod' => array('title' => 'ACP_MOD_LOGS', 'auth' => 'acl_a_viewlogs', 'cat' => array('ACP_FORUM_LOGS')), + 'users' => array('title' => 'ACP_USERS_LOGS', 'auth' => 'acl_a_viewlogs', 'cat' => array('ACP_FORUM_LOGS')), + 'critical' => array('title' => 'ACP_CRITICAL_LOGS', 'auth' => 'acl_a_viewlogs', 'cat' => array('ACP_FORUM_LOGS')), + ); + + /** + * Event to add or modify ACP log modulemodes + * + * @event core.acp_logs_info_modify_modes + * @var array modes Array with modes info + * @since 3.1.11-RC1 + * @since 3.2.1-RC1 + */ + $vars = array('modes'); + extract($phpbb_dispatcher->trigger_event('core.acp_logs_info_modify_modes', compact($vars))); + + return array( + 'filename' => 'acp_logs', + 'title' => 'ACP_LOGGING', + 'modes' => $modes, + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_main.php b/includes/acp/info/acp_main.php new file mode 100644 index 0000000..48d35da --- /dev/null +++ b/includes/acp/info/acp_main.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_main_info +{ + function module() + { + return array( + 'filename' => 'acp_main', + 'title' => 'ACP_INDEX', + 'modes' => array( + 'main' => array('title' => 'ACP_INDEX', 'auth' => '', 'cat' => array('ACP_CAT_GENERAL')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_modules.php b/includes/acp/info/acp_modules.php new file mode 100644 index 0000000..073e69c --- /dev/null +++ b/includes/acp/info/acp_modules.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_modules_info +{ + function module() + { + return array( + 'filename' => 'acp_modules', + 'title' => 'ACP_MODULE_MANAGEMENT', + 'modes' => array( + 'acp' => array('title' => 'ACP', 'auth' => 'acl_a_modules', 'cat' => array('ACP_MODULE_MANAGEMENT')), + 'ucp' => array('title' => 'UCP', 'auth' => 'acl_a_modules', 'cat' => array('ACP_MODULE_MANAGEMENT')), + 'mcp' => array('title' => 'MCP', 'auth' => 'acl_a_modules', 'cat' => array('ACP_MODULE_MANAGEMENT')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_permission_roles.php b/includes/acp/info/acp_permission_roles.php new file mode 100644 index 0000000..34af693 --- /dev/null +++ b/includes/acp/info/acp_permission_roles.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_permission_roles_info +{ + function module() + { + return array( + 'filename' => 'acp_permission_roles', + 'title' => 'ACP_PERMISSION_ROLES', + 'modes' => array( + 'admin_roles' => array('title' => 'ACP_ADMIN_ROLES', 'auth' => 'acl_a_roles && acl_a_aauth', 'cat' => array('ACP_PERMISSION_ROLES')), + 'user_roles' => array('title' => 'ACP_USER_ROLES', 'auth' => 'acl_a_roles && acl_a_uauth', 'cat' => array('ACP_PERMISSION_ROLES')), + 'mod_roles' => array('title' => 'ACP_MOD_ROLES', 'auth' => 'acl_a_roles && acl_a_mauth', 'cat' => array('ACP_PERMISSION_ROLES')), + 'forum_roles' => array('title' => 'ACP_FORUM_ROLES', 'auth' => 'acl_a_roles && acl_a_fauth', 'cat' => array('ACP_PERMISSION_ROLES')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_permissions.php b/includes/acp/info/acp_permissions.php new file mode 100644 index 0000000..3d415f2 --- /dev/null +++ b/includes/acp/info/acp_permissions.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_permissions_info +{ + function module() + { + return array( + 'filename' => 'acp_permissions', + 'title' => 'ACP_PERMISSIONS', + 'modes' => array( + 'intro' => array('title' => 'ACP_PERMISSIONS', 'auth' => 'acl_a_authusers || acl_a_authgroups || acl_a_viewauth', 'cat' => array('ACP_CAT_PERMISSIONS')), + 'trace' => array('title' => 'ACP_PERMISSION_TRACE', 'auth' => 'acl_a_viewauth', 'display' => false, 'cat' => array('ACP_PERMISSION_MASKS')), + + 'setting_forum_local' => array('title' => 'ACP_FORUM_PERMISSIONS', 'auth' => 'acl_a_fauth && (acl_a_authusers || acl_a_authgroups)', 'cat' => array('ACP_FORUM_BASED_PERMISSIONS')), + 'setting_forum_copy' => array('title' => 'ACP_FORUM_PERMISSIONS_COPY', 'auth' => 'acl_a_fauth && acl_a_authusers && acl_a_authgroups && acl_a_mauth', 'cat' => array('ACP_FORUM_BASED_PERMISSIONS')), + 'setting_mod_local' => array('title' => 'ACP_FORUM_MODERATORS', 'auth' => 'acl_a_mauth && (acl_a_authusers || acl_a_authgroups)', 'cat' => array('ACP_FORUM_BASED_PERMISSIONS')), + 'setting_user_global' => array('title' => 'ACP_USERS_PERMISSIONS', 'auth' => 'acl_a_authusers && (acl_a_aauth || acl_a_mauth || acl_a_uauth)', 'cat' => array('ACP_GLOBAL_PERMISSIONS', 'ACP_CAT_USERS')), + 'setting_user_local' => array('title' => 'ACP_USERS_FORUM_PERMISSIONS', 'auth' => 'acl_a_authusers && (acl_a_mauth || acl_a_fauth)', 'cat' => array('ACP_FORUM_BASED_PERMISSIONS', 'ACP_CAT_USERS')), + 'setting_group_global' => array('title' => 'ACP_GROUPS_PERMISSIONS', 'auth' => 'acl_a_authgroups && (acl_a_aauth || acl_a_mauth || acl_a_uauth)', 'cat' => array('ACP_GLOBAL_PERMISSIONS', 'ACP_GROUPS')), + 'setting_group_local' => array('title' => 'ACP_GROUPS_FORUM_PERMISSIONS', 'auth' => 'acl_a_authgroups && (acl_a_mauth || acl_a_fauth)', 'cat' => array('ACP_FORUM_BASED_PERMISSIONS', 'ACP_GROUPS')), + 'setting_admin_global' => array('title' => 'ACP_ADMINISTRATORS', 'auth' => 'acl_a_aauth && (acl_a_authusers || acl_a_authgroups)', 'cat' => array('ACP_GLOBAL_PERMISSIONS')), + 'setting_mod_global' => array('title' => 'ACP_GLOBAL_MODERATORS', 'auth' => 'acl_a_mauth && (acl_a_authusers || acl_a_authgroups)', 'cat' => array('ACP_GLOBAL_PERMISSIONS')), + + 'view_admin_global' => array('title' => 'ACP_VIEW_ADMIN_PERMISSIONS', 'auth' => 'acl_a_viewauth', 'cat' => array('ACP_PERMISSION_MASKS')), + 'view_user_global' => array('title' => 'ACP_VIEW_USER_PERMISSIONS', 'auth' => 'acl_a_viewauth', 'cat' => array('ACP_PERMISSION_MASKS')), + 'view_mod_global' => array('title' => 'ACP_VIEW_GLOBAL_MOD_PERMISSIONS', 'auth' => 'acl_a_viewauth', 'cat' => array('ACP_PERMISSION_MASKS')), + 'view_mod_local' => array('title' => 'ACP_VIEW_FORUM_MOD_PERMISSIONS', 'auth' => 'acl_a_viewauth', 'cat' => array('ACP_PERMISSION_MASKS')), + 'view_forum_local' => array('title' => 'ACP_VIEW_FORUM_PERMISSIONS', 'auth' => 'acl_a_viewauth', 'cat' => array('ACP_PERMISSION_MASKS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_php_info.php b/includes/acp/info/acp_php_info.php new file mode 100644 index 0000000..c5e60c7 --- /dev/null +++ b/includes/acp/info/acp_php_info.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_php_info_info +{ + function module() + { + return array( + 'filename' => 'acp_php_info', + 'title' => 'ACP_PHP_INFO', + 'modes' => array( + 'info' => array('title' => 'ACP_PHP_INFO', 'auth' => 'acl_a_phpinfo', 'cat' => array('ACP_GENERAL_TASKS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_profile.php b/includes/acp/info/acp_profile.php new file mode 100644 index 0000000..ede3420 --- /dev/null +++ b/includes/acp/info/acp_profile.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_profile_info +{ + function module() + { + return array( + 'filename' => 'acp_profile', + 'title' => 'ACP_CUSTOM_PROFILE_FIELDS', + 'modes' => array( + 'profile' => array('title' => 'ACP_CUSTOM_PROFILE_FIELDS', 'auth' => 'acl_a_profile', 'cat' => array('ACP_CAT_USERS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_prune.php b/includes/acp/info/acp_prune.php new file mode 100644 index 0000000..74e5248 --- /dev/null +++ b/includes/acp/info/acp_prune.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_prune_info +{ + function module() + { + return array( + 'filename' => 'acp_prune', + 'title' => 'ACP_PRUNING', + 'modes' => array( + 'forums' => array('title' => 'ACP_PRUNE_FORUMS', 'auth' => 'acl_a_prune', 'cat' => array('ACP_MANAGE_FORUMS')), + 'users' => array('title' => 'ACP_PRUNE_USERS', 'auth' => 'acl_a_userdel', 'cat' => array('ACP_CAT_USERS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_ranks.php b/includes/acp/info/acp_ranks.php new file mode 100644 index 0000000..9bf51eb --- /dev/null +++ b/includes/acp/info/acp_ranks.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_ranks_info +{ + function module() + { + return array( + 'filename' => 'acp_ranks', + 'title' => 'ACP_RANKS', + 'modes' => array( + 'ranks' => array('title' => 'ACP_MANAGE_RANKS', 'auth' => 'acl_a_ranks', 'cat' => array('ACP_CAT_USERS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_reasons.php b/includes/acp/info/acp_reasons.php new file mode 100644 index 0000000..55a0495 --- /dev/null +++ b/includes/acp/info/acp_reasons.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_reasons_info +{ + function module() + { + return array( + 'filename' => 'acp_reasons', + 'title' => 'ACP_REASONS', + 'modes' => array( + 'main' => array('title' => 'ACP_MANAGE_REASONS', 'auth' => 'acl_a_reasons', 'cat' => array('ACP_GENERAL_TASKS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_search.php b/includes/acp/info/acp_search.php new file mode 100644 index 0000000..0635dd9 --- /dev/null +++ b/includes/acp/info/acp_search.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_search_info +{ + function module() + { + return array( + 'filename' => 'acp_search', + 'title' => 'ACP_SEARCH', + 'modes' => array( + 'settings' => array('title' => 'ACP_SEARCH_SETTINGS', 'auth' => 'acl_a_search', 'cat' => array('ACP_SERVER_CONFIGURATION')), + 'index' => array('title' => 'ACP_SEARCH_INDEX', 'auth' => 'acl_a_search', 'cat' => array('ACP_CAT_DATABASE')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_styles.php b/includes/acp/info/acp_styles.php new file mode 100644 index 0000000..59b0a64 --- /dev/null +++ b/includes/acp/info/acp_styles.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_styles_info +{ + function module() + { + return array( + 'filename' => 'acp_styles', + 'title' => 'ACP_CAT_STYLES', + 'modes' => array( + 'style' => array('title' => 'ACP_STYLES', 'auth' => 'acl_a_styles', 'cat' => array('ACP_STYLE_MANAGEMENT')), + 'install' => array('title' => 'ACP_STYLES_INSTALL', 'auth' => 'acl_a_styles', 'cat' => array('ACP_STYLE_MANAGEMENT')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_update.php b/includes/acp/info/acp_update.php new file mode 100644 index 0000000..7806fb4 --- /dev/null +++ b/includes/acp/info/acp_update.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_update_info +{ + function module() + { + return array( + 'filename' => 'acp_update', + 'title' => 'ACP_UPDATE', + 'modes' => array( + 'version_check' => array('title' => 'ACP_VERSION_CHECK', 'auth' => 'acl_a_board', 'cat' => array('ACP_AUTOMATION')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_users.php b/includes/acp/info/acp_users.php new file mode 100644 index 0000000..cb59d24 --- /dev/null +++ b/includes/acp/info/acp_users.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_users_info +{ + function module() + { + return array( + 'filename' => 'acp_users', + 'title' => 'ACP_USER_MANAGEMENT', + 'modes' => array( + 'overview' => array('title' => 'ACP_MANAGE_USERS', 'auth' => 'acl_a_user', 'cat' => array('ACP_CAT_USERS')), + 'feedback' => array('title' => 'ACP_USER_FEEDBACK', 'auth' => 'acl_a_user', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'warnings' => array('title' => 'ACP_USER_WARNINGS', 'auth' => 'acl_a_user', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'profile' => array('title' => 'ACP_USER_PROFILE', 'auth' => 'acl_a_user', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'prefs' => array('title' => 'ACP_USER_PREFS', 'auth' => 'acl_a_user', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'avatar' => array('title' => 'ACP_USER_AVATAR', 'auth' => 'acl_a_user', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'rank' => array('title' => 'ACP_USER_RANK', 'auth' => 'acl_a_user', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'sig' => array('title' => 'ACP_USER_SIG', 'auth' => 'acl_a_user', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'groups' => array('title' => 'ACP_USER_GROUPS', 'auth' => 'acl_a_user && acl_a_group', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'perm' => array('title' => 'ACP_USER_PERM', 'auth' => 'acl_a_user && acl_a_viewauth', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + 'attach' => array('title' => 'ACP_USER_ATTACH', 'auth' => 'acl_a_user', 'display' => false, 'cat' => array('ACP_CAT_USERS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/acp/info/acp_words.php b/includes/acp/info/acp_words.php new file mode 100644 index 0000000..8a6d0d7 --- /dev/null +++ b/includes/acp/info/acp_words.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class acp_words_info +{ + function module() + { + return array( + 'filename' => 'acp_words', + 'title' => 'ACP_WORDS', + 'modes' => array( + 'words' => array('title' => 'ACP_WORDS', 'auth' => 'acl_a_words', 'cat' => array('ACP_MESSAGES')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/bbcode.php b/includes/bbcode.php new file mode 100644 index 0000000..c31b63a --- /dev/null +++ b/includes/bbcode.php @@ -0,0 +1,707 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* BBCode class +*/ +class bbcode +{ + var $bbcode_uid = ''; + var $bbcode_bitfield = ''; + var $bbcode_cache = array(); + var $bbcode_template = array(); + + var $bbcodes = array(); + + var $template_bitfield; + + /** + * Constructor + */ + function __construct($bitfield = '') + { + $this->bbcode_set_bitfield($bitfield); + } + + /** + * Init bbcode cache entries if bitfield is specified + * + * @param string $bbcode_bitfield The bbcode bitfield + */ + function bbcode_set_bitfield($bitfield = '') + { + if ($bitfield) + { + $this->bbcode_bitfield = $bitfield; + $this->bbcode_cache_init(); + } + } + + /** + * Second pass bbcodes + */ + function bbcode_second_pass(&$message, $bbcode_uid = '', $bbcode_bitfield = false) + { + if ($bbcode_uid) + { + $this->bbcode_uid = $bbcode_uid; + } + + if ($bbcode_bitfield !== false) + { + $this->bbcode_bitfield = $bbcode_bitfield; + + // Init those added with a new bbcode_bitfield (already stored codes will not get parsed again) + $this->bbcode_cache_init(); + } + + if (!$this->bbcode_bitfield) + { + // Remove the uid from tags that have not been transformed into HTML + if ($this->bbcode_uid) + { + $message = str_replace(':' . $this->bbcode_uid, '', $message); + } + + return; + } + + $str = array('search' => array(), 'replace' => array()); + $preg = array('search' => array(), 'replace' => array()); + + $bitfield = new bitfield($this->bbcode_bitfield); + $bbcodes_set = $bitfield->get_all_set(); + + $undid_bbcode_specialchars = false; + foreach ($bbcodes_set as $bbcode_id) + { + if (!empty($this->bbcode_cache[$bbcode_id])) + { + foreach ($this->bbcode_cache[$bbcode_id] as $type => $array) + { + foreach ($array as $search => $replace) + { + ${$type}['search'][] = str_replace('$uid', $this->bbcode_uid, $search); + ${$type}['replace'][] = $replace; + } + + if (count($str['search'])) + { + $message = str_replace($str['search'], $str['replace'], $message); + $str = array('search' => array(), 'replace' => array()); + } + + if (count($preg['search'])) + { + // we need to turn the entities back into their original form to allow the + // search patterns to work properly + if (!$undid_bbcode_specialchars) + { + $message = str_replace(array(':', '.'), array(':', '.'), $message); + $undid_bbcode_specialchars = true; + } + + foreach ($preg['search'] as $key => $search) + { + if (is_callable($preg['replace'][$key])) + { + $message = preg_replace_callback($search, $preg['replace'][$key], $message); + } + else + { + $message = preg_replace($search, $preg['replace'][$key], $message); + } + } + + $preg = array('search' => array(), 'replace' => array()); + } + } + } + } + + // Remove the uid from tags that have not been transformed into HTML + $message = str_replace(':' . $this->bbcode_uid, '', $message); + } + + /** + * Init bbcode cache + * + * requires: $this->bbcode_bitfield + * sets: $this->bbcode_cache with bbcode templates needed for bbcode_bitfield + */ + function bbcode_cache_init() + { + global $user, $phpbb_dispatcher, $phpbb_extension_manager, $phpbb_container, $phpbb_filesystem; + + if (empty($this->template_filename)) + { + $this->template_bitfield = new bitfield($user->style['bbcode_bitfield']); + + $template = new \phpbb\template\twig\twig( + $phpbb_container->get('path_helper'), + $phpbb_container->get('config'), + new \phpbb\template\context(), + new \phpbb\template\twig\environment( + $phpbb_container->get('config'), + $phpbb_container->get('filesystem'), + $phpbb_container->get('path_helper'), + $phpbb_container->getParameter('core.cache_dir'), + $phpbb_container->get('ext.manager'), + new \phpbb\template\twig\loader( + $phpbb_filesystem + ) + ), + $phpbb_container->getParameter('core.cache_dir'), + $phpbb_container->get('user'), + $phpbb_container->get('template.twig.extensions.collection'), + $phpbb_extension_manager + ); + + $template->set_style(); + $template->set_filenames(array('bbcode.html' => 'bbcode.html')); + $this->template_filename = $template->get_source_file_for_handle('bbcode.html'); + } + + $bbcode_ids = $rowset = $sql = array(); + + $bitfield = new bitfield($this->bbcode_bitfield); + $bbcodes_set = $bitfield->get_all_set(); + + foreach ($bbcodes_set as $bbcode_id) + { + if (isset($this->bbcode_cache[$bbcode_id])) + { + // do not try to re-cache it if it's already in + continue; + } + $bbcode_ids[] = $bbcode_id; + + if ($bbcode_id > NUM_CORE_BBCODES) + { + $sql[] = $bbcode_id; + } + } + + if (count($sql)) + { + global $db; + + $sql = 'SELECT * + FROM ' . BBCODES_TABLE . ' + WHERE ' . $db->sql_in_set('bbcode_id', $sql); + $result = $db->sql_query($sql, 3600); + + while ($row = $db->sql_fetchrow($result)) + { + // To circumvent replacing newlines with
for the generated html, + // we use carriage returns here. They are later changed back to newlines + $row['bbcode_tpl'] = str_replace("\n", "\r", $row['bbcode_tpl']); + $row['second_pass_replace'] = str_replace("\n", "\r", $row['second_pass_replace']); + + $rowset[$row['bbcode_id']] = $row; + } + $db->sql_freeresult($result); + } + + // To perform custom second pass in extension, use $this->bbcode_second_pass_by_extension() + // method which accepts variable number of parameters + foreach ($bbcode_ids as $bbcode_id) + { + switch ($bbcode_id) + { + case BBCODE_ID_QUOTE: + $this->bbcode_cache[$bbcode_id] = array( + 'str' => array( + '[/quote:$uid]' => $this->bbcode_tpl('quote_close', $bbcode_id) + ), + 'preg' => array( + '#\[quote(?:="(.*?)")?:$uid\]((?!\[quote(?:=".*?")?:$uid\]).)?#is' => function ($match) { + if (!isset($match[2])) + { + $match[2] = ''; + } + + return $this->bbcode_second_pass_quote($match[1], $match[2]); + }, + ) + ); + break; + + case BBCODE_ID_B: + $this->bbcode_cache[$bbcode_id] = array( + 'str' => array( + '[b:$uid]' => $this->bbcode_tpl('b_open', $bbcode_id), + '[/b:$uid]' => $this->bbcode_tpl('b_close', $bbcode_id), + ) + ); + break; + + case BBCODE_ID_I: + $this->bbcode_cache[$bbcode_id] = array( + 'str' => array( + '[i:$uid]' => $this->bbcode_tpl('i_open', $bbcode_id), + '[/i:$uid]' => $this->bbcode_tpl('i_close', $bbcode_id), + ) + ); + break; + + case BBCODE_ID_URL: + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#\[url:$uid\]((.*?))\[/url:$uid\]#s' => $this->bbcode_tpl('url', $bbcode_id), + '#\[url=([^\[]+?):$uid\](.*?)\[/url:$uid\]#s' => $this->bbcode_tpl('url', $bbcode_id), + ) + ); + break; + + case BBCODE_ID_IMG: + if ($user->optionget('viewimg')) + { + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#\[img:$uid\](.*?)\[/img:$uid\]#s' => $this->bbcode_tpl('img', $bbcode_id), + ) + ); + } + else + { + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#\[img:$uid\](.*?)\[/img:$uid\]#s' => str_replace('$2', '[ img ]', $this->bbcode_tpl('url', $bbcode_id, true)), + ) + ); + } + break; + + case BBCODE_ID_SIZE: + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#\[size=([\-\+]?\d+):$uid\](.*?)\[/size:$uid\]#s' => $this->bbcode_tpl('size', $bbcode_id), + ) + ); + break; + + case BBCODE_ID_COLOR: + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '!\[color=(#[0-9a-f]{3}|#[0-9a-f]{6}|[a-z\-]+):$uid\](.*?)\[/color:$uid\]!is' => $this->bbcode_tpl('color', $bbcode_id), + ) + ); + break; + + case BBCODE_ID_U: + $this->bbcode_cache[$bbcode_id] = array( + 'str' => array( + '[u:$uid]' => $this->bbcode_tpl('u_open', $bbcode_id), + '[/u:$uid]' => $this->bbcode_tpl('u_close', $bbcode_id), + ) + ); + break; + + case BBCODE_ID_CODE: + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#\[code(?:=([a-z]+))?:$uid\](.*?)\[/code:$uid\]#is' => function ($match) { + return $this->bbcode_second_pass_code($match[1], $match[2]); + }, + ) + ); + break; + + case BBCODE_ID_LIST: + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#(\[\/?(list|\*):[mou]?:?$uid\])[\n]{1}#' => "\$1", + '#(\[list=([^\[]+):$uid\])[\n]{1}#' => "\$1", + '#\[list=([^\[]+):$uid\]#' => function ($match) { + return $this->bbcode_list($match[1]); + }, + ), + 'str' => array( + '[list:$uid]' => $this->bbcode_tpl('ulist_open_default', $bbcode_id), + '[/list:u:$uid]' => $this->bbcode_tpl('ulist_close', $bbcode_id), + '[/list:o:$uid]' => $this->bbcode_tpl('olist_close', $bbcode_id), + '[*:$uid]' => $this->bbcode_tpl('listitem', $bbcode_id), + '[/*:$uid]' => $this->bbcode_tpl('listitem_close', $bbcode_id), + '[/*:m:$uid]' => $this->bbcode_tpl('listitem_close', $bbcode_id) + ), + ); + break; + + case BBCODE_ID_EMAIL: + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#\[email:$uid\]((.*?))\[/email:$uid\]#is' => $this->bbcode_tpl('email', $bbcode_id), + '#\[email=([^\[]+):$uid\](.*?)\[/email:$uid\]#is' => $this->bbcode_tpl('email', $bbcode_id) + ) + ); + break; + + case BBCODE_ID_FLASH: + if ($user->optionget('viewflash')) + { + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#\[flash=([0-9]+),([0-9]+):$uid\](.*?)\[/flash:$uid\]#' => $this->bbcode_tpl('flash', $bbcode_id), + ) + ); + } + else + { + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array( + '#\[flash=([0-9]+),([0-9]+):$uid\](.*?)\[/flash:$uid\]#' => str_replace('$1', '$3', str_replace('$2', '[ flash ]', $this->bbcode_tpl('url', $bbcode_id, true))) + ) + ); + } + break; + + case BBCODE_ID_ATTACH: + $this->bbcode_cache[$bbcode_id] = array( + 'str' => array( + '[/attachment:$uid]' => $this->bbcode_tpl('inline_attachment_close', $bbcode_id) + ), + 'preg' => array( + '#\[attachment=([0-9]+):$uid\]#' => $this->bbcode_tpl('inline_attachment_open', $bbcode_id) + ) + ); + break; + + default: + if (isset($rowset[$bbcode_id])) + { + if ($this->template_bitfield->get($bbcode_id)) + { + // The bbcode requires a custom template to be loaded + if (!$bbcode_tpl = $this->bbcode_tpl($rowset[$bbcode_id]['bbcode_tag'], $bbcode_id)) + { + // For some reason, the required template seems not to be available, use the default template + $bbcode_tpl = (!empty($rowset[$bbcode_id]['second_pass_replace'])) ? $rowset[$bbcode_id]['second_pass_replace'] : $rowset[$bbcode_id]['bbcode_tpl']; + } + else + { + // In order to use templates with custom bbcodes we need + // to replace all {VARS} to corresponding backreferences + // Note that backreferences are numbered from bbcode_match + if (preg_match_all('/\{(URL|LOCAL_URL|EMAIL|TEXT|SIMPLETEXT|INTTEXT|IDENTIFIER|COLOR|NUMBER)[0-9]*\}/', $rowset[$bbcode_id]['bbcode_match'], $m)) + { + foreach ($m[0] as $i => $tok) + { + $bbcode_tpl = str_replace($tok, '$' . ($i + 1), $bbcode_tpl); + } + } + } + } + else + { + // Default template + $bbcode_tpl = (!empty($rowset[$bbcode_id]['second_pass_replace'])) ? $rowset[$bbcode_id]['second_pass_replace'] : $rowset[$bbcode_id]['bbcode_tpl']; + } + + // Replace {L_*} lang strings + $bbcode_tpl = preg_replace_callback('/{L_([A-Z0-9_]+)}/', function ($match) use ($user) { + return (!empty($user->lang[$match[1]])) ? $user->lang($match[1]) : ucwords(strtolower(str_replace('_', ' ', $match[1]))); + }, $bbcode_tpl); + + if (!empty($rowset[$bbcode_id]['second_pass_replace'])) + { + // The custom BBCode requires second-pass pattern replacements + $this->bbcode_cache[$bbcode_id] = array( + 'preg' => array($rowset[$bbcode_id]['second_pass_match'] => $bbcode_tpl) + ); + } + else + { + $this->bbcode_cache[$bbcode_id] = array( + 'str' => array($rowset[$bbcode_id]['second_pass_match'] => $bbcode_tpl) + ); + } + } + else + { + $this->bbcode_cache[$bbcode_id] = false; + } + break; + } + } + + $bbcode_cache = $this->bbcode_cache; + $bbcode_bitfield = $this->bbcode_bitfield; + $bbcode_uid = $this->bbcode_uid; + + /** + * Use this event to modify the bbcode_cache + * + * @event core.bbcode_cache_init_end + * @var array bbcode_cache The array of cached search and replace patterns of bbcodes + * @var string bbcode_bitfield The bbcode bitfield + * @var string bbcode_uid The bbcode uid + * @since 3.1.3-RC1 + */ + $vars = array('bbcode_cache', 'bbcode_bitfield', 'bbcode_uid'); + extract($phpbb_dispatcher->trigger_event('core.bbcode_cache_init_end', compact($vars))); + + $this->bbcode_cache = $bbcode_cache; + $this->bbcode_bitfield = $bbcode_bitfield; + $this->bbcode_uid = $bbcode_uid; + } + + /** + * Return bbcode template + */ + function bbcode_tpl($tpl_name, $bbcode_id = -1, $skip_bitfield_check = false) + { + static $bbcode_hardtpl = array(); + if (empty($bbcode_hardtpl)) + { + global $user; + + $bbcode_hardtpl = array( + 'b_open' => '', + 'b_close' => '', + 'i_open' => '', + 'i_close' => '', + 'u_open' => '', + 'u_close' => '', + 'img' => '' . $user->lang['IMAGE'] . '', + 'size' => '$2', + 'color' => '$2', + 'email' => '$2' + ); + } + + if ($bbcode_id != -1 && !$skip_bitfield_check && !$this->template_bitfield->get($bbcode_id)) + { + return (isset($bbcode_hardtpl[$tpl_name])) ? $bbcode_hardtpl[$tpl_name] : false; + } + + if (empty($this->bbcode_template)) + { + if (($tpl = file_get_contents($this->template_filename)) === false) + { + trigger_error('Could not load bbcode template', E_USER_ERROR); + } + + // replace \ with \\ and then ' with \'. + $tpl = str_replace('\\', '\\\\', $tpl); + $tpl = str_replace("'", "\'", $tpl); + + // strip newlines and indent + $tpl = preg_replace("/\n[\n\r\s\t]*/", '', $tpl); + + // Turn template blocks into PHP assignment statements for the values of $bbcode_tpl.. + $this->bbcode_template = array(); + + // Capture the BBCode template matches + // Allow phpBB template or the Twig syntax + $matches = (preg_match_all('#(.*?)#', $tpl, $match)) ?: + preg_match_all('#{% for (.*?) in .*? %}(.*?){% endfor %}#s', $tpl, $match); + + for ($i = 0; $i < $matches; $i++) + { + if (empty($match[1][$i])) + { + continue; + } + + $this->bbcode_template[$match[1][$i]] = $this->bbcode_tpl_replace($match[1][$i], $match[2][$i]); + } + } + + return (isset($this->bbcode_template[$tpl_name])) ? $this->bbcode_template[$tpl_name] : ((isset($bbcode_hardtpl[$tpl_name])) ? $bbcode_hardtpl[$tpl_name] : false); + } + + /** + * Return bbcode template replacement + */ + function bbcode_tpl_replace($tpl_name, $tpl) + { + global $user; + + static $replacements = array( + 'quote_username_open' => array('{USERNAME}' => '$1'), + 'color' => array('{COLOR}' => '$1', '{TEXT}' => '$2'), + 'size' => array('{SIZE}' => '$1', '{TEXT}' => '$2'), + 'img' => array('{URL}' => '$1'), + 'flash' => array('{WIDTH}' => '$1', '{HEIGHT}' => '$2', '{URL}' => '$3'), + 'url' => array('{URL}' => '$1', '{DESCRIPTION}' => '$2'), + 'email' => array('{EMAIL}' => '$1', '{DESCRIPTION}' => '$2') + ); + + $tpl = preg_replace_callback('/{L_([A-Z0-9_]+)}/', function ($match) use ($user) { + return (!empty($user->lang[$match[1]])) ? $user->lang($match[1]) : ucwords(strtolower(str_replace('_', ' ', $match[1]))); + }, $tpl); + + if (!empty($replacements[$tpl_name])) + { + $tpl = strtr($tpl, $replacements[$tpl_name]); + } + + return trim($tpl); + } + + /** + * Second parse list bbcode + */ + function bbcode_list($type) + { + if ($type == '') + { + $tpl = 'ulist_open_default'; + $type = 'default'; + } + else if ($type == 'i') + { + $tpl = 'olist_open'; + $type = 'lower-roman'; + } + else if ($type == 'I') + { + $tpl = 'olist_open'; + $type = 'upper-roman'; + } + else if (preg_match('#^(disc|circle|square)$#i', $type)) + { + $tpl = 'ulist_open'; + $type = strtolower($type); + } + else if (preg_match('#^[a-z]$#', $type)) + { + $tpl = 'olist_open'; + $type = 'lower-alpha'; + } + else if (preg_match('#[A-Z]#', $type)) + { + $tpl = 'olist_open'; + $type = 'upper-alpha'; + } + else if (is_numeric($type)) + { + $tpl = 'olist_open'; + $type = 'decimal'; + } + else + { + $tpl = 'olist_open'; + $type = 'decimal'; + } + + return str_replace('{LIST_TYPE}', $type, $this->bbcode_tpl($tpl)); + } + + /** + * Second parse quote tag + */ + function bbcode_second_pass_quote($username, $quote) + { + // when using the /e modifier, preg_replace slashes double-quotes but does not + // seem to slash anything else + $quote = str_replace('\"', '"', $quote); + $username = str_replace('\"', '"', $username); + + // remove newline at the beginning + if ($quote == "\n") + { + $quote = ''; + } + + $quote = (($username) ? str_replace('$1', $username, $this->bbcode_tpl('quote_username_open')) : $this->bbcode_tpl('quote_open')) . $quote; + + return $quote; + } + + /** + * Second parse code tag + */ + function bbcode_second_pass_code($type, $code) + { + // when using the /e modifier, preg_replace slashes double-quotes but does not + // seem to slash anything else + $code = str_replace('\"', '"', $code); + + switch ($type) + { + case 'php': + // Not the english way, but valid because of hardcoded syntax highlighting + if (strpos($code, '
') === 0) + { + $code = substr($code, 41); + } + + // no break; + + default: + $code = str_replace("\t", '   ', $code); + $code = str_replace(' ', '  ', $code); + $code = str_replace(' ', '  ', $code); + $code = str_replace("\n ", "\n ", $code); + + // keep space at the beginning + if (!empty($code) && $code[0] == ' ') + { + $code = ' ' . substr($code, 1); + } + + // remove newline at the beginning + if (!empty($code) && $code[0] == "\n") + { + $code = substr($code, 1); + } + break; + } + + $code = $this->bbcode_tpl('code_open') . $code . $this->bbcode_tpl('code_close'); + + return $code; + } + + /** + * Function to perform custom bbcode second pass by extensions + * can be used to assign bbcode pattern replacement + * Example: '#\[list=([^\[]+):$uid\]#e' => "\$this->bbcode_second_pass_by_extension('\$1')" + * + * Accepts variable number of parameters + * + * @return mixed Second pass result + */ + function bbcode_second_pass_by_extension() + { + global $phpbb_dispatcher; + + $return = false; + $params_array = func_get_args(); + + /** + * Event to perform bbcode second pass with + * the custom validating methods provided by extensions + * + * @event core.bbcode_second_pass_by_extension + * @var array params_array Array with the function parameters + * @var mixed return Second pass result to return + * + * @since 3.1.5-RC1 + */ + $vars = array('params_array', 'return'); + extract($phpbb_dispatcher->trigger_event('core.bbcode_second_pass_by_extension', compact($vars))); + + return $return; + } +} diff --git a/includes/compatibility_globals.php b/includes/compatibility_globals.php new file mode 100644 index 0000000..ad394e3 --- /dev/null +++ b/includes/compatibility_globals.php @@ -0,0 +1,84 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** + * Sets compatibility globals in the global scope + * + * This function registers compatibility variables to the global + * variable scope. This is required to make it possible to include this file + * in a service. + */ +function register_compatibility_globals() +{ + global $phpbb_container; + + global $cache, $phpbb_dispatcher, $request, $user, $auth, $db, $config, $language, $phpbb_log; + global $symfony_request, $phpbb_filesystem, $phpbb_path_helper, $phpbb_extension_manager, $template; + + // set up caching + /* @var $cache \phpbb\cache\service */ + $cache = $phpbb_container->get('cache'); + + // Instantiate some basic classes + /* @var $phpbb_dispatcher \phpbb\event\dispatcher */ + $phpbb_dispatcher = $phpbb_container->get('dispatcher'); + + /* @var $request \phpbb\request\request_interface */ + $request = $phpbb_container->get('request'); + // Inject request instance, so only this instance is used with request_var + request_var('', 0, false, false, $request); + + /* @var $user \phpbb\user */ + $user = $phpbb_container->get('user'); + + /* @var \phpbb\language\language $language */ + $language = $phpbb_container->get('language'); + + /* @var $auth \phpbb\auth\auth */ + $auth = $phpbb_container->get('auth'); + + /* @var $db \phpbb\db\driver\driver_interface */ + $db = $phpbb_container->get('dbal.conn'); + + // Grab global variables, re-cache if necessary + /* @var $config phpbb\config\db */ + $config = $phpbb_container->get('config'); + set_config('', '', false, $config); + set_config_count('', 0, false, $config); + + /* @var $phpbb_log \phpbb\log\log_interface */ + $phpbb_log = $phpbb_container->get('log'); + + /* @var $symfony_request \phpbb\symfony_request */ + $symfony_request = $phpbb_container->get('symfony_request'); + + /* @var $phpbb_filesystem \phpbb\filesystem\filesystem_interface */ + $phpbb_filesystem = $phpbb_container->get('filesystem'); + + /* @var $phpbb_path_helper \phpbb\path_helper */ + $phpbb_path_helper = $phpbb_container->get('path_helper'); + + // load extensions + /* @var $phpbb_extension_manager \phpbb\extension\manager */ + $phpbb_extension_manager = $phpbb_container->get('ext.manager'); + + /* @var $template \phpbb\template\template */ + $template = $phpbb_container->get('template'); +} diff --git a/includes/constants.php b/includes/constants.php new file mode 100644 index 0000000..3effe6c --- /dev/null +++ b/includes/constants.php @@ -0,0 +1,316 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* valid external constants: +* PHPBB_MSG_HANDLER +* PHPBB_DB_NEW_LINK +* PHPBB_ROOT_PATH +* PHPBB_ADMIN_PATH +*/ + +// phpBB Version +@define('PHPBB_VERSION', '3.2.7'); + +// QA-related +// define('PHPBB_QA', 1); + +// User related +define('ANONYMOUS', 1); + +define('USER_ACTIVATION_NONE', 0); +define('USER_ACTIVATION_SELF', 1); +define('USER_ACTIVATION_ADMIN', 2); +define('USER_ACTIVATION_DISABLE', 3); + +define('AVATAR_UPLOAD', 1); +define('AVATAR_REMOTE', 2); +define('AVATAR_GALLERY', 3); + +define('USER_NORMAL', 0); +define('USER_INACTIVE', 1); +define('USER_IGNORE', 2); +define('USER_FOUNDER', 3); + +define('INACTIVE_REGISTER', 1); // Newly registered account +define('INACTIVE_PROFILE', 2); // Profile details changed +define('INACTIVE_MANUAL', 3); // Account deactivated by administrator +define('INACTIVE_REMIND', 4); // Forced user account reactivation + +// ACL +define('ACL_NEVER', 0); +define('ACL_YES', 1); +define('ACL_NO', -1); + +// Login error codes +define('LOGIN_CONTINUE', 1); +define('LOGIN_BREAK', 2); +define('LOGIN_SUCCESS', 3); +define('LOGIN_SUCCESS_CREATE_PROFILE', 20); +define('LOGIN_SUCCESS_LINK_PROFILE', 21); +define('LOGIN_ERROR_USERNAME', 10); +define('LOGIN_ERROR_PASSWORD', 11); +define('LOGIN_ERROR_ACTIVE', 12); +define('LOGIN_ERROR_ATTEMPTS', 13); +define('LOGIN_ERROR_EXTERNAL_AUTH', 14); +define('LOGIN_ERROR_PASSWORD_CONVERT', 15); + +// Maximum login attempts +// The value is arbitrary, but it has to fit into the user_login_attempts field. +define('LOGIN_ATTEMPTS_MAX', 100); + +// Group settings +define('GROUP_OPEN', 0); +define('GROUP_CLOSED', 1); +define('GROUP_HIDDEN', 2); +define('GROUP_SPECIAL', 3); +define('GROUP_FREE', 4); + +// Forum/Topic states +define('FORUM_CAT', 0); +define('FORUM_POST', 1); +define('FORUM_LINK', 2); +define('ITEM_UNLOCKED', 0); +define('ITEM_LOCKED', 1); +define('ITEM_MOVED', 2); + +define('ITEM_UNAPPROVED', 0); // => has not yet been approved +define('ITEM_APPROVED', 1); // => has been approved, and has not been soft deleted +define('ITEM_DELETED', 2); // => has been soft deleted +define('ITEM_REAPPROVE', 3); // => has been edited and needs to be re-approved + +// Forum Flags +define('FORUM_FLAG_LINK_TRACK', 1); +define('FORUM_FLAG_PRUNE_POLL', 2); +define('FORUM_FLAG_PRUNE_ANNOUNCE', 4); +define('FORUM_FLAG_PRUNE_STICKY', 8); +define('FORUM_FLAG_ACTIVE_TOPICS', 16); +define('FORUM_FLAG_POST_REVIEW', 32); +define('FORUM_FLAG_QUICK_REPLY', 64); + +// Forum Options... sequential order. Modifications should begin at number 10 (number 29 is maximum) +define('FORUM_OPTION_FEED_NEWS', 1); +define('FORUM_OPTION_FEED_EXCLUDE', 2); + +// Optional text flags +define('OPTION_FLAG_BBCODE', 1); +define('OPTION_FLAG_SMILIES', 2); +define('OPTION_FLAG_LINKS', 4); + +// Topic types +define('POST_NORMAL', 0); +define('POST_STICKY', 1); +define('POST_ANNOUNCE', 2); +define('POST_GLOBAL', 3); + +// Lastread types +define('TRACK_NORMAL', 0); +define('TRACK_POSTED', 1); + +// Notify methods +define('NOTIFY_EMAIL', 0); +define('NOTIFY_IM', 1); +define('NOTIFY_BOTH', 2); + +// Notify status +define('NOTIFY_YES', 0); +define('NOTIFY_NO', 1); + +// Email Priority Settings +define('MAIL_LOW_PRIORITY', 4); +define('MAIL_NORMAL_PRIORITY', 3); +define('MAIL_HIGH_PRIORITY', 2); + +// Log types +define('LOG_ADMIN', 0); +define('LOG_MOD', 1); +define('LOG_CRITICAL', 2); +define('LOG_USERS', 3); + +// Private messaging - Do NOT change these values +define('PRIVMSGS_HOLD_BOX', -4); +define('PRIVMSGS_NO_BOX', -3); +define('PRIVMSGS_OUTBOX', -2); +define('PRIVMSGS_SENTBOX', -1); +define('PRIVMSGS_INBOX', 0); + +// Full Folder Actions +define('FULL_FOLDER_NONE', -3); +define('FULL_FOLDER_DELETE', -2); +define('FULL_FOLDER_HOLD', -1); + +// Download Modes - Attachments +define('INLINE_LINK', 1); +// This mode is only used internally to allow modders extending the attachment functionality +define('PHYSICAL_LINK', 2); + +// Confirm types +define('CONFIRM_REG', 1); +define('CONFIRM_LOGIN', 2); +define('CONFIRM_POST', 3); +define('CONFIRM_REPORT', 4); + +// Categories - Attachments +define('ATTACHMENT_CATEGORY_NONE', 0); +define('ATTACHMENT_CATEGORY_IMAGE', 1); // Inline Images +define('ATTACHMENT_CATEGORY_WM', 2); // Windows Media Files - Streaming - @deprecated 3.2 +define('ATTACHMENT_CATEGORY_RM', 3); // Real Media Files - Streaming - @deprecated 3.2 +define('ATTACHMENT_CATEGORY_THUMB', 4); // Not used within the database, only while displaying posts +define('ATTACHMENT_CATEGORY_FLASH', 5); // Flash/SWF files +define('ATTACHMENT_CATEGORY_QUICKTIME', 6); // Quicktime/Mov files - @deprecated 3.2 + +// BBCode UID length +define('BBCODE_UID_LEN', 8); + +// Number of core BBCodes +define('NUM_CORE_BBCODES', 12); +define('NUM_PREDEFINED_BBCODES', 22); + +// BBCode IDs +define('BBCODE_ID_QUOTE', 0); +define('BBCODE_ID_B', 1); +define('BBCODE_ID_I', 2); +define('BBCODE_ID_URL', 3); +define('BBCODE_ID_IMG', 4); +define('BBCODE_ID_SIZE', 5); +define('BBCODE_ID_COLOR', 6); +define('BBCODE_ID_U', 7); +define('BBCODE_ID_CODE', 8); +define('BBCODE_ID_LIST', 9); +define('BBCODE_ID_EMAIL', 10); +define('BBCODE_ID_FLASH', 11); +define('BBCODE_ID_ATTACH', 12); + +// BBCode hard limit +define('BBCODE_LIMIT', 1511); + +// Smiley hard limit +define('SMILEY_LIMIT', 1000); + +// Magic url types +define('MAGIC_URL_EMAIL', 1); +define('MAGIC_URL_FULL', 2); +define('MAGIC_URL_LOCAL', 3); +define('MAGIC_URL_WWW', 4); + +// Profile Field Types +define('FIELD_INT', 1); +define('FIELD_STRING', 2); +define('FIELD_TEXT', 3); +define('FIELD_BOOL', 4); +define('FIELD_DROPDOWN', 5); +define('FIELD_DATE', 6); + +// referer validation +define('REFERER_VALIDATE_NONE', 0); +define('REFERER_VALIDATE_HOST', 1); +define('REFERER_VALIDATE_PATH', 2); + +// phpbb_chmod() permissions +@define('CHMOD_ALL', 7); +@define('CHMOD_READ', 4); +@define('CHMOD_WRITE', 2); +@define('CHMOD_EXECUTE', 1); + +// Captcha code length +define('CAPTCHA_MIN_CHARS', 4); +define('CAPTCHA_MAX_CHARS', 7); + +// Additional constants +define('VOTE_CONVERTED', 127); + +// BC global FTW +global $table_prefix; + +// Table names +define('ACL_GROUPS_TABLE', $table_prefix . 'acl_groups'); +define('ACL_OPTIONS_TABLE', $table_prefix . 'acl_options'); +define('ACL_ROLES_DATA_TABLE', $table_prefix . 'acl_roles_data'); +define('ACL_ROLES_TABLE', $table_prefix . 'acl_roles'); +define('ACL_USERS_TABLE', $table_prefix . 'acl_users'); +define('ATTACHMENTS_TABLE', $table_prefix . 'attachments'); +define('BANLIST_TABLE', $table_prefix . 'banlist'); +define('BBCODES_TABLE', $table_prefix . 'bbcodes'); +define('BOOKMARKS_TABLE', $table_prefix . 'bookmarks'); +define('BOTS_TABLE', $table_prefix . 'bots'); +@define('CONFIG_TABLE', $table_prefix . 'config'); +define('CONFIG_TEXT_TABLE', $table_prefix . 'config_text'); +define('CONFIRM_TABLE', $table_prefix . 'confirm'); +define('DISALLOW_TABLE', $table_prefix . 'disallow'); +define('DRAFTS_TABLE', $table_prefix . 'drafts'); +define('EXT_TABLE', $table_prefix . 'ext'); +define('EXTENSIONS_TABLE', $table_prefix . 'extensions'); +define('EXTENSION_GROUPS_TABLE', $table_prefix . 'extension_groups'); +define('FORUMS_TABLE', $table_prefix . 'forums'); +define('FORUMS_ACCESS_TABLE', $table_prefix . 'forums_access'); +define('FORUMS_TRACK_TABLE', $table_prefix . 'forums_track'); +define('FORUMS_WATCH_TABLE', $table_prefix . 'forums_watch'); +define('GROUPS_TABLE', $table_prefix . 'groups'); +define('ICONS_TABLE', $table_prefix . 'icons'); +define('LANG_TABLE', $table_prefix . 'lang'); +define('LOG_TABLE', $table_prefix . 'log'); +define('LOGIN_ATTEMPT_TABLE', $table_prefix . 'login_attempts'); +define('MIGRATIONS_TABLE', $table_prefix . 'migrations'); +define('MODERATOR_CACHE_TABLE', $table_prefix . 'moderator_cache'); +define('MODULES_TABLE', $table_prefix . 'modules'); +define('NOTIFICATION_TYPES_TABLE', $table_prefix . 'notification_types'); +define('NOTIFICATIONS_TABLE', $table_prefix . 'notifications'); +define('POLL_OPTIONS_TABLE', $table_prefix . 'poll_options'); +define('POLL_VOTES_TABLE', $table_prefix . 'poll_votes'); +define('POSTS_TABLE', $table_prefix . 'posts'); +define('PRIVMSGS_TABLE', $table_prefix . 'privmsgs'); +define('PRIVMSGS_FOLDER_TABLE', $table_prefix . 'privmsgs_folder'); +define('PRIVMSGS_RULES_TABLE', $table_prefix . 'privmsgs_rules'); +define('PRIVMSGS_TO_TABLE', $table_prefix . 'privmsgs_to'); +define('PROFILE_FIELDS_TABLE', $table_prefix . 'profile_fields'); +define('PROFILE_FIELDS_DATA_TABLE', $table_prefix . 'profile_fields_data'); +define('PROFILE_FIELDS_LANG_TABLE', $table_prefix . 'profile_fields_lang'); +define('PROFILE_LANG_TABLE', $table_prefix . 'profile_lang'); +define('RANKS_TABLE', $table_prefix . 'ranks'); +define('REPORTS_TABLE', $table_prefix . 'reports'); +define('REPORTS_REASONS_TABLE', $table_prefix . 'reports_reasons'); +define('SEARCH_RESULTS_TABLE', $table_prefix . 'search_results'); +define('SEARCH_WORDLIST_TABLE', $table_prefix . 'search_wordlist'); +define('SEARCH_WORDMATCH_TABLE', $table_prefix . 'search_wordmatch'); +define('SESSIONS_TABLE', $table_prefix . 'sessions'); +define('SESSIONS_KEYS_TABLE', $table_prefix . 'sessions_keys'); +define('SITELIST_TABLE', $table_prefix . 'sitelist'); +define('SMILIES_TABLE', $table_prefix . 'smilies'); +define('SPHINX_TABLE', $table_prefix . 'sphinx'); +define('STYLES_TABLE', $table_prefix . 'styles'); +define('STYLES_TEMPLATE_TABLE', $table_prefix . 'styles_template'); +define('STYLES_TEMPLATE_DATA_TABLE',$table_prefix . 'styles_template_data'); +define('STYLES_THEME_TABLE', $table_prefix . 'styles_theme'); +define('STYLES_IMAGESET_TABLE', $table_prefix . 'styles_imageset'); +define('STYLES_IMAGESET_DATA_TABLE',$table_prefix . 'styles_imageset_data'); +define('TEAMPAGE_TABLE', $table_prefix . 'teampage'); +define('TOPICS_TABLE', $table_prefix . 'topics'); +define('TOPICS_POSTED_TABLE', $table_prefix . 'topics_posted'); +define('TOPICS_TRACK_TABLE', $table_prefix . 'topics_track'); +define('TOPICS_WATCH_TABLE', $table_prefix . 'topics_watch'); +define('USER_GROUP_TABLE', $table_prefix . 'user_group'); +define('USER_NOTIFICATIONS_TABLE', $table_prefix . 'user_notifications'); +define('USERS_TABLE', $table_prefix . 'users'); +define('WARNINGS_TABLE', $table_prefix . 'warnings'); +define('WORDS_TABLE', $table_prefix . 'words'); +define('ZEBRA_TABLE', $table_prefix . 'zebra'); + +// Additional tables diff --git a/includes/diff/diff.php b/includes/diff/diff.php new file mode 100644 index 0000000..d8ae9d7 --- /dev/null +++ b/includes/diff/diff.php @@ -0,0 +1,1152 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Code from pear.php.net, Text_Diff-1.1.0 package +* http://pear.php.net/package/Text_Diff/ +* +* Modified by phpBB Limited to meet our coding standards +* and being able to integrate into phpBB +* +* General API for generating and formatting diffs - the differences between +* two sequences of strings. +* +* Copyright 2004 Geoffrey T. Dairiki +* Copyright 2004-2008 The Horde Project (http://www.horde.org/) +* +* @package diff +* @author Geoffrey T. Dairiki +*/ +class diff +{ + /** + * Array of changes. + * @var array + */ + var $_edits; + + /** + * Computes diffs between sequences of strings. + * + * @param array &$from_content An array of strings. Typically these are lines from a file. + * @param array &$to_content An array of strings. + * @param bool $preserve_cr If true, \r is replaced by a new line in the diff output + */ + function __construct(&$from_content, &$to_content, $preserve_cr = true) + { + $diff_engine = new diff_engine(); + $this->_edits = $diff_engine->diff($from_content, $to_content, $preserve_cr); + } + + /** + * Returns the array of differences. + */ + function get_diff() + { + return $this->_edits; + } + + /** + * returns the number of new (added) lines in a given diff. + * + * @since Text_Diff 1.1.0 + * + * @return integer The number of new lines + */ + function count_added_lines() + { + $count = 0; + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if (is_a($edit, 'diff_op_add') || is_a($edit, 'diff_op_change')) + { + $count += $edit->nfinal(); + } + } + return $count; + } + + /** + * Returns the number of deleted (removed) lines in a given diff. + * + * @since Text_Diff 1.1.0 + * + * @return integer The number of deleted lines + */ + function count_deleted_lines() + { + $count = 0; + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if (is_a($edit, 'diff_op_delete') || is_a($edit, 'diff_op_change')) + { + $count += $edit->norig(); + } + } + return $count; + } + + /** + * Computes a reversed diff. + * + * Example: + * + * $diff = new diff($lines1, $lines2); + * $rev = $diff->reverse(); + * + * + * @return diff A Diff object representing the inverse of the original diff. + * Note that we purposely don't return a reference here, since + * this essentially is a clone() method. + */ + function reverse() + { + if (version_compare(zend_version(), '2', '>')) + { + $rev = clone($this); + } + else + { + $rev = $this; + } + + $rev->_edits = array(); + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + $rev->_edits[] = $edit->reverse(); + } + + return $rev; + } + + /** + * Checks for an empty diff. + * + * @return boolean True if two sequences were identical. + */ + function is_empty() + { + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + // skip diff_op_copy + if (is_a($edit, 'diff_op_copy')) + { + continue; + } + + if (is_a($edit, 'diff_op_delete') || is_a($edit, 'diff_op_add')) + { + $orig = $edit->orig; + $final = $edit->final; + + // We can simplify one case where the array is usually supposed to be empty... + if (count($orig) == 1 && trim($orig[0]) === '') $orig = array(); + if (count($final) == 1 && trim($final[0]) === '') $final = array(); + + if (!$orig && !$final) + { + continue; + } + + return false; + } + + return false; + } + + return true; + } + + /** + * Computes the length of the Longest Common Subsequence (LCS). + * + * This is mostly for diagnostic purposes. + * + * @return integer The length of the LCS. + */ + function lcs() + { + $lcs = 0; + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if (is_a($edit, 'diff_op_copy')) + { + $lcs += count($edit->orig); + } + } + return $lcs; + } + + /** + * Gets the original set of lines. + * + * This reconstructs the $from_lines parameter passed to the constructor. + * + * @return array The original sequence of strings. + */ + function get_original() + { + $lines = array(); + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if ($edit->orig) + { + array_splice($lines, count($lines), 0, $edit->orig); + } + } + return $lines; + } + + /** + * Gets the final set of lines. + * + * This reconstructs the $to_lines parameter passed to the constructor. + * + * @return array The sequence of strings. + */ + function get_final() + { + $lines = array(); + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if ($edit->final) + { + array_splice($lines, count($lines), 0, $edit->final); + } + } + return $lines; + } + + /** + * Removes trailing newlines from a line of text. This is meant to be used with array_walk(). + * + * @param string &$line The line to trim. + * @param integer $key The index of the line in the array. Not used. + */ + function trim_newlines(&$line, $key) + { + $line = str_replace(array("\n", "\r"), '', $line); + } + + /** + * Checks a diff for validity. + * + * This is here only for debugging purposes. + */ + function _check($from_lines, $to_lines) + { + if (serialize($from_lines) != serialize($this->get_original())) + { + trigger_error("[diff] Reconstructed original doesn't match", E_USER_ERROR); + } + + if (serialize($to_lines) != serialize($this->get_final())) + { + trigger_error("[diff] Reconstructed final doesn't match", E_USER_ERROR); + } + + $rev = $this->reverse(); + + if (serialize($to_lines) != serialize($rev->get_original())) + { + trigger_error("[diff] Reversed original doesn't match", E_USER_ERROR); + } + + if (serialize($from_lines) != serialize($rev->get_final())) + { + trigger_error("[diff] Reversed final doesn't match", E_USER_ERROR); + } + + $prevtype = null; + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if ($prevtype == get_class($edit)) + { + trigger_error("[diff] Edit sequence is non-optimal", E_USER_ERROR); + } + $prevtype = get_class($edit); + } + + return true; + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +*/ +class mapped_diff extends diff +{ + /** + * Computes a diff between sequences of strings. + * + * This can be used to compute things like case-insensitve diffs, or diffs + * which ignore changes in white-space. + * + * @param array $from_lines An array of strings. + * @param array $to_lines An array of strings. + * @param array $mapped_from_lines This array should have the same size number of elements as $from_lines. + * The elements in $mapped_from_lines and $mapped_to_lines are what is actually + * compared when computing the diff. + * @param array $mapped_to_lines This array should have the same number of elements as $to_lines. + */ + function __construct(&$from_lines, &$to_lines, &$mapped_from_lines, &$mapped_to_lines) + { + if (count($from_lines) != count($mapped_from_lines) || count($to_lines) != count($mapped_to_lines)) + { + return false; + } + + parent::__construct($mapped_from_lines, $mapped_to_lines); + + $xi = $yi = 0; + for ($i = 0; $i < count($this->_edits); $i++) + { + $orig = &$this->_edits[$i]->orig; + if (is_array($orig)) + { + $orig = array_slice($from_lines, $xi, count($orig)); + $xi += count($orig); + } + + $final = &$this->_edits[$i]->final; + if (is_array($final)) + { + $final = array_slice($to_lines, $yi, count($final)); + $yi += count($final); + } + } + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +* +* @access private +*/ +class diff_op +{ + var $orig; + var $final; + + function &reverse() + { + trigger_error('[diff] Abstract method', E_USER_ERROR); + } + + function norig() + { + return ($this->orig) ? count($this->orig) : 0; + } + + function nfinal() + { + return ($this->final) ? count($this->final) : 0; + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +* +* @access private +*/ +class diff_op_copy extends diff_op +{ + function __construct($orig, $final = false) + { + if (!is_array($final)) + { + $final = $orig; + } + $this->orig = $orig; + $this->final = $final; + } + + function &reverse() + { + $reverse = new diff_op_copy($this->final, $this->orig); + return $reverse; + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +* +* @access private +*/ +class diff_op_delete extends diff_op +{ + function __construct($lines) + { + $this->orig = $lines; + $this->final = false; + } + + function &reverse() + { + $reverse = new diff_op_add($this->orig); + return $reverse; + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +* +* @access private +*/ +class diff_op_add extends diff_op +{ + function __construct($lines) + { + $this->final = $lines; + $this->orig = false; + } + + function &reverse() + { + $reverse = new diff_op_delete($this->final); + return $reverse; + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +* +* @access private +*/ +class diff_op_change extends diff_op +{ + function __construct($orig, $final) + { + $this->orig = $orig; + $this->final = $final; + } + + function &reverse() + { + $reverse = new diff_op_change($this->final, $this->orig); + return $reverse; + } +} + + +/** +* A class for computing three way diffs. +* +* @package diff +* @author Geoffrey T. Dairiki +*/ +class diff3 extends diff +{ + /** + * Conflict counter. + * @var integer + */ + var $_conflicting_blocks = 0; + + /** + * Computes diff between 3 sequences of strings. + * + * @param array &$orig The original lines to use. + * @param array &$final1 The first version to compare to. + * @param array &$final2 The second version to compare to. + * @param bool $preserve_cr If true, \r\n and bare \r are replaced by a new line + * in the diff output + */ + function __construct(&$orig, &$final1, &$final2, $preserve_cr = true) + { + $diff_engine = new diff_engine(); + + $diff_1 = $diff_engine->diff($orig, $final1, $preserve_cr); + $diff_2 = $diff_engine->diff($orig, $final2, $preserve_cr); + + unset($diff_engine); + + $this->_edits = $this->_diff3($diff_1, $diff_2); + } + + /** + * Return number of conflicts + */ + function get_num_conflicts() + { + $conflicts = 0; + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if ($edit->is_conflict()) + { + $conflicts++; + } + } + + return $conflicts; + } + + /** + * Get conflicts content for download. This is generally a merged file, but preserving conflicts and adding explanations to it. + * A user could then go through this file, search for the conflicts and changes the code accordingly. + * + * @param string $label1 the cvs file version/label from the original set of lines + * @param string $label2 the cvs file version/label from the new set of lines + * @param string $label_sep the explanation between label1 and label2 - more of a helper for the user + * + * @return mixed the merged output + */ + function get_conflicts_content($label1 = 'CURRENT_FILE', $label2 = 'NEW_FILE', $label_sep = 'DIFF_SEP_EXPLAIN') + { + global $user; + + $label1 = (!empty($user->lang[$label1])) ? $user->lang[$label1] : $label1; + $label2 = (!empty($user->lang[$label2])) ? $user->lang[$label2] : $label2; + $label_sep = (!empty($user->lang[$label_sep])) ? $user->lang[$label_sep] : $label_sep; + + $lines = array(); + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if ($edit->is_conflict()) + { + // Start conflict label + $label_start = array('<<<<<<< ' . $label1); + $label_mid = array('======= ' . $label_sep); + $label_end = array('>>>>>>> ' . $label2); + + $lines = array_merge($lines, $label_start, $edit->final1, $label_mid, $edit->final2, $label_end); + $this->_conflicting_blocks++; + } + else + { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * Return merged output (used by the renderer) + * + * @return mixed the merged output + */ + function merged_output() + { + return $this->get_conflicts_content(); + } + + /** + * Merge the output and use the new file code for conflicts + */ + function merged_new_output() + { + $lines = array(); + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if ($edit->is_conflict()) + { + $lines = array_merge($lines, $edit->final2); + } + else + { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * Merge the output and use the original file code for conflicts + */ + function merged_orig_output() + { + $lines = array(); + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if ($edit->is_conflict()) + { + $lines = array_merge($lines, $edit->final1); + } + else + { + $lines = array_merge($lines, $edit->merged()); + } + } + + return $lines; + } + + /** + * Get conflicting block(s) + */ + function get_conflicts() + { + $conflicts = array(); + + for ($i = 0, $size = count($this->_edits); $i < $size; $i++) + { + $edit = $this->_edits[$i]; + + if ($edit->is_conflict()) + { + $conflicts[] = array($edit->final1, $edit->final2); + } + } + + return $conflicts; + } + + /** + * @access private + */ + function _diff3(&$edits1, &$edits2) + { + $edits = array(); + $bb = new diff3_block_builder(); + + $e1 = current($edits1); + $e2 = current($edits2); + + while ($e1 || $e2) + { + if ($e1 && $e2 && is_a($e1, 'diff_op_copy') && is_a($e2, 'diff_op_copy')) + { + // We have copy blocks from both diffs. This is the (only) time we want to emit a diff3 copy block. + // Flush current diff3 diff block, if any. + if ($edit = $bb->finish()) + { + $edits[] = $edit; + } + + $ncopy = min($e1->norig(), $e2->norig()); + $edits[] = new diff3_op_copy(array_slice($e1->orig, 0, $ncopy)); + + if ($e1->norig() > $ncopy) + { + array_splice($e1->orig, 0, $ncopy); + array_splice($e1->final, 0, $ncopy); + } + else + { + $e1 = next($edits1); + } + + if ($e2->norig() > $ncopy) + { + array_splice($e2->orig, 0, $ncopy); + array_splice($e2->final, 0, $ncopy); + } + else + { + $e2 = next($edits2); + } + } + else + { + if ($e1 && $e2) + { + if ($e1->orig && $e2->orig) + { + $norig = min($e1->norig(), $e2->norig()); + $orig = array_splice($e1->orig, 0, $norig); + array_splice($e2->orig, 0, $norig); + $bb->input($orig); + } + else + { + $norig = 0; + } + + if (is_a($e1, 'diff_op_copy')) + { + $bb->out1(array_splice($e1->final, 0, $norig)); + } + + if (is_a($e2, 'diff_op_copy')) + { + $bb->out2(array_splice($e2->final, 0, $norig)); + } + } + + if ($e1 && ! $e1->orig) + { + $bb->out1($e1->final); + $e1 = next($edits1); + } + + if ($e2 && ! $e2->orig) + { + $bb->out2($e2->final); + $e2 = next($edits2); + } + } + } + + if ($edit = $bb->finish()) + { + $edits[] = $edit; + } + + return $edits; + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +* +* @access private +*/ +class diff3_op +{ + function __construct($orig = false, $final1 = false, $final2 = false) + { + $this->orig = $orig ? $orig : array(); + $this->final1 = $final1 ? $final1 : array(); + $this->final2 = $final2 ? $final2 : array(); + } + + function merged() + { + if (!isset($this->_merged)) + { + // Prepare the arrays before we compare them. ;) + $this->solve_prepare(); + + if ($this->final1 === $this->final2) + { + $this->_merged = &$this->final1; + } + else if ($this->final1 === $this->orig) + { + $this->_merged = &$this->final2; + } + else if ($this->final2 === $this->orig) + { + $this->_merged = &$this->final1; + } + else + { + // The following tries to aggressively solve conflicts... + $this->_merged = false; + $this->solve_conflict(); + } + } + + return $this->_merged; + } + + function is_conflict() + { + return ($this->merged() === false) ? true : false; + } + + /** + * Function to prepare the arrays for comparing - we want to skip over newline changes + * @author acydburn + */ + function solve_prepare() + { + // We can simplify one case where the array is usually supposed to be empty... + if (count($this->orig) == 1 && trim($this->orig[0]) === '') $this->orig = array(); + if (count($this->final1) == 1 && trim($this->final1[0]) === '') $this->final1 = array(); + if (count($this->final2) == 1 && trim($this->final2[0]) === '') $this->final2 = array(); + + // Now we only can have the case where the only difference between arrays are newlines, so compare all cases + + // First, some strings we can compare... + $orig = $final1 = $final2 = ''; + + foreach ($this->orig as $null => $line) $orig .= trim($line); + foreach ($this->final1 as $null => $line) $final1 .= trim($line); + foreach ($this->final2 as $null => $line) $final2 .= trim($line); + + // final1 === final2 + if ($final1 === $final2) + { + // We preserve the part which will be used in the merge later + $this->final2 = $this->final1; + } + // final1 === orig + else if ($final1 === $orig) + { + // Here it does not really matter what we choose, but we will use the new code + $this->orig = $this->final1; + } + // final2 === orig + else if ($final2 === $orig) + { + // Here it does not really matter too (final1 will be used), but we will use the new code + $this->orig = $this->final2; + } + } + + /** + * Find code portions from $orig in $final1 and use $final2 as merged instance if provided + * @author acydburn + */ + function _compare_conflict_seq($orig, $final1, $final2 = false) + { + $result = array('merge_found' => false, 'merge' => array()); + + $_orig = &$this->$orig; + $_final1 = &$this->$final1; + + // Ok, we basically search for $orig in $final1 + $compare_seq = count($_orig); + + // Go through the conflict code + for ($i = 0, $j = 0, $size = count($_final1); $i < $size; $i++, $j = $i) + { + $line = $_final1[$i]; + $skip = 0; + + for ($x = 0; $x < $compare_seq; $x++) + { + // Try to skip all matching lines + if (trim($line) === trim($_orig[$x])) + { + $line = (++$j < $size) ? $_final1[$j] : $line; + $skip++; + } + } + + if ($skip === $compare_seq) + { + $result['merge_found'] = true; + + if ($final2 !== false) + { + $result['merge'] = array_merge($result['merge'], $this->$final2); + } + $i += ($skip - 1); + } + else if ($final2 !== false) + { + $result['merge'][] = $line; + } + } + + return $result; + } + + /** + * Tries to solve conflicts aggressively based on typical "assumptions" + * @author acydburn + */ + function solve_conflict() + { + $this->_merged = false; + + // CASE ONE: orig changed into final2, but modified/unknown code in final1. + // IF orig is found "as is" in final1 we replace the code directly in final1 and populate this as final2/merge + if (count($this->orig) && count($this->final2)) + { + $result = $this->_compare_conflict_seq('orig', 'final1', 'final2'); + + if ($result['merge_found']) + { + $this->final2 = $result['merge']; + $this->_merged = &$this->final2; + return; + } + + $result = $this->_compare_conflict_seq('final2', 'final1'); + + if ($result['merge_found']) + { + $this->_merged = &$this->final1; + return; + } + + // Try to solve $Id$ issues. ;) + if (count($this->orig) == 1 && count($this->final1) == 1 && count($this->final2) == 1) + { + $match = '#^' . preg_quote('* @version $Id: ', '#') . '[a-z\._\- ]+[0-9]+ [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9\:Z]+ [a-z0-9_\- ]+\$$#'; + + if (preg_match($match, $this->orig[0]) && preg_match($match, $this->final1[0]) && preg_match($match, $this->final2[0])) + { + $this->_merged = &$this->final2; + return; + } + } + + $second_run = false; + + // Try to solve issues where the only reason why the above did not work is a newline being removed in the final1 code but exist in the orig/final2 code + if (trim($this->orig[0]) === '' && trim($this->final2[0]) === '') + { + unset($this->orig[0], $this->final2[0]); + $this->orig = array_values($this->orig); + $this->final2 = array_values($this->final2); + + $second_run = true; + } + + // The same is true for a line at the end. ;) + if (count($this->orig) && count($this->final2) && count($this->orig) === count($this->final2) && trim($this->orig[count($this->orig)-1]) === '' && trim($this->final2[count($this->final2)-1]) === '') + { + unset($this->orig[count($this->orig)-1], $this->final2[count($this->final2)-1]); + $this->orig = array_values($this->orig); + $this->final2 = array_values($this->final2); + + $second_run = true; + } + + if ($second_run) + { + $result = $this->_compare_conflict_seq('orig', 'final1', 'final2'); + + if ($result['merge_found']) + { + $this->final2 = $result['merge']; + $this->_merged = &$this->final2; + return; + } + + $result = $this->_compare_conflict_seq('final2', 'final1'); + + if ($result['merge_found']) + { + $this->_merged = &$this->final1; + return; + } + } + + return; + } + + // CASE TWO: Added lines from orig to final2 but final1 had added lines too. Just merge them. + if (!count($this->orig) && $this->final1 !== $this->final2 && count($this->final1) && count($this->final2)) + { + $result = $this->_compare_conflict_seq('final2', 'final1'); + + if ($result['merge_found']) + { + $this->final2 = $this->final1; + $this->_merged = &$this->final1; + } + else + { + $result = $this->_compare_conflict_seq('final1', 'final2'); + + if (!$result['merge_found']) + { + $this->final2 = array_merge($this->final1, $this->final2); + $this->_merged = &$this->final2; + } + else + { + $this->final2 = $this->final1; + $this->_merged = &$this->final1; + } + } + + return; + } + + // CASE THREE: Removed lines (orig has the to-remove line(s), but final1 has additional lines which does not need to be removed). Just remove orig from final1 and then use final1 as final2/merge + if (!count($this->final2) && count($this->orig) && count($this->final1) && $this->orig !== $this->final1) + { + $result = $this->_compare_conflict_seq('orig', 'final1'); + + if (!$result['merge_found']) + { + return; + } + + // First of all, try to find the code in orig in final1. ;) + $compare_seq = count($this->orig); + $begin = $end = -1; + $j = 0; + + for ($i = 0, $size = count($this->final1); $i < $size; $i++) + { + $line = $this->final1[$i]; + + if (trim($line) === trim($this->orig[$j])) + { + // Mark begin + if ($begin === -1) + { + $begin = $i; + } + + // End is always $i, the last found line + $end = $i; + + if (isset($this->orig[$j+1])) + { + $j++; + } + } + } + + if ($begin !== -1 && $begin + ($compare_seq - 1) == $end) + { + foreach ($this->final1 as $i => $line) + { + if ($i < $begin || $i > $end) + { + $merged[] = $line; + } + } + + $this->final2 = $merged; + $this->_merged = &$this->final2; + } + + return; + } + + return; + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +* +* @access private +*/ +class diff3_op_copy extends diff3_op +{ + function __construct($lines = false) + { + $this->orig = $lines ? $lines : array(); + $this->final1 = &$this->orig; + $this->final2 = &$this->orig; + } + + function merged() + { + return $this->orig; + } + + function is_conflict() + { + return false; + } +} + +/** +* @package diff +* @author Geoffrey T. Dairiki +* +* @access private +*/ +class diff3_block_builder +{ + function __construct() + { + $this->_init(); + } + + function input($lines) + { + if ($lines) + { + $this->_append($this->orig, $lines); + } + } + + function out1($lines) + { + if ($lines) + { + $this->_append($this->final1, $lines); + } + } + + function out2($lines) + { + if ($lines) + { + $this->_append($this->final2, $lines); + } + } + + function is_empty() + { + return !$this->orig && !$this->final1 && !$this->final2; + } + + function finish() + { + if ($this->is_empty()) + { + return false; + } + else + { + $edit = new diff3_op($this->orig, $this->final1, $this->final2); + $this->_init(); + return $edit; + } + } + + function _init() + { + $this->orig = $this->final1 = $this->final2 = array(); + } + + function _append(&$array, $lines) + { + array_splice($array, count($array), 0, $lines); + } +} diff --git a/includes/diff/engine.php b/includes/diff/engine.php new file mode 100644 index 0000000..757fdad --- /dev/null +++ b/includes/diff/engine.php @@ -0,0 +1,555 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Code from pear.php.net, Text_Diff-1.1.0 package +* http://pear.php.net/package/Text_Diff/ (native engine) +* +* Modified by phpBB Limited to meet our coding standards +* and being able to integrate into phpBB +* +* Class used internally by Text_Diff to actually compute the diffs. This +* class is implemented using native PHP code. +* +* The algorithm used here is mostly lifted from the perl module +* Algorithm::Diff (version 1.06) by Ned Konz, which is available at: +* http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip +* +* More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html +* +* Some ideas (and a bit of code) are taken from analyze.c, of GNU +* diffutils-2.7, which can be found at: +* ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz +* +* Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from +* Geoffrey T. Dairiki . The original PHP version of this +* code was written by him, and is used/adapted with his permission. +* +* Copyright 2004-2008 The Horde Project (http://www.horde.org/) +* +* @author Geoffrey T. Dairiki +* @package diff +* +* @access private +*/ +class diff_engine +{ + /** + * If set to true we trim all lines before we compare them. This ensures that sole space/tab changes do not trigger diffs. + */ + var $skip_whitespace_changes = true; + + function diff(&$from_lines, &$to_lines, $preserve_cr = true) + { + // Remove empty lines... + // If preserve_cr is true, we basically only change \r\n and bare \r to \n to get the same carriage returns for both files + // If it is false, we try to only use \n once per line and ommit all empty lines to be able to get a proper data diff + + if (is_array($from_lines)) + { + $from_lines = implode("\n", $from_lines); + } + + if (is_array($to_lines)) + { + $to_lines = implode("\n", $to_lines); + } + + if ($preserve_cr) + { + $from_lines = explode("\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $from_lines))); + $to_lines = explode("\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $to_lines))); + } + else + { + $from_lines = explode("\n", preg_replace('#[\n\r]+#', "\n", $from_lines)); + $to_lines = explode("\n", preg_replace('#[\n\r]+#', "\n", $to_lines)); + } + + $n_from = count($from_lines); + $n_to = count($to_lines); + + $this->xchanged = $this->ychanged = $this->xv = $this->yv = $this->xind = $this->yind = array(); + unset($this->seq, $this->in_seq, $this->lcs); + + // Skip leading common lines. + for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) + { + if (trim($from_lines[$skip]) !== trim($to_lines[$skip])) + { + break; + } + $this->xchanged[$skip] = $this->ychanged[$skip] = false; + } + + // Skip trailing common lines. + $xi = $n_from; + $yi = $n_to; + + for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) + { + if (trim($from_lines[$xi]) !== trim($to_lines[$yi])) + { + break; + } + $this->xchanged[$xi] = $this->ychanged[$yi] = false; + } + + // Ignore lines which do not exist in both files. + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) + { + if ($this->skip_whitespace_changes) $xhash[trim($from_lines[$xi])] = 1; else $xhash[$from_lines[$xi]] = 1; + } + + for ($yi = $skip; $yi < $n_to - $endskip; $yi++) + { + $line = ($this->skip_whitespace_changes) ? trim($to_lines[$yi]) : $to_lines[$yi]; + + if (($this->ychanged[$yi] = empty($xhash[$line]))) + { + continue; + } + $yhash[$line] = 1; + $this->yv[] = $line; + $this->yind[] = $yi; + } + + for ($xi = $skip; $xi < $n_from - $endskip; $xi++) + { + $line = ($this->skip_whitespace_changes) ? trim($from_lines[$xi]) : $from_lines[$xi]; + + if (($this->xchanged[$xi] = empty($yhash[$line]))) + { + continue; + } + $this->xv[] = $line; + $this->xind[] = $xi; + } + + // Find the LCS. + $this->_compareseq(0, count($this->xv), 0, count($this->yv)); + + // Merge edits when possible. + if ($this->skip_whitespace_changes) + { + $from_lines_clean = array_map('trim', $from_lines); + $to_lines_clean = array_map('trim', $to_lines); + + $this->_shift_boundaries($from_lines_clean, $this->xchanged, $this->ychanged); + $this->_shift_boundaries($to_lines_clean, $this->ychanged, $this->xchanged); + + unset($from_lines_clean, $to_lines_clean); + } + else + { + $this->_shift_boundaries($from_lines, $this->xchanged, $this->ychanged); + $this->_shift_boundaries($to_lines, $this->ychanged, $this->xchanged); + } + + // Compute the edit operations. + $edits = array(); + $xi = $yi = 0; + + while ($xi < $n_from || $yi < $n_to) + { + // Skip matching "snake". + $copy = array(); + + while ($xi < $n_from && $yi < $n_to && !$this->xchanged[$xi] && !$this->ychanged[$yi]) + { + $copy[] = $from_lines[$xi++]; + $yi++; + } + + if ($copy) + { + $edits[] = new diff_op_copy($copy); + } + + // Find deletes & adds. + $delete = array(); + while ($xi < $n_from && $this->xchanged[$xi]) + { + $delete[] = $from_lines[$xi++]; + } + + $add = array(); + while ($yi < $n_to && $this->ychanged[$yi]) + { + $add[] = $to_lines[$yi++]; + } + + if ($delete && $add) + { + $edits[] = new diff_op_change($delete, $add); + } + else if ($delete) + { + $edits[] = new diff_op_delete($delete); + } + else if ($add) + { + $edits[] = new diff_op_add($add); + } + } + + return $edits; + } + + /** + * Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, + * XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized segments. + * + * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of + * NCHUNKS+1 (X, Y) indexes giving the diving points between sub + * sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1), + * the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) == + * (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). + * + * This function assumes that the first lines of the specified portions of + * the two files do not match, and likewise that the last lines do not + * match. The caller must trim matching lines from the beginning and end + * of the portions it is going to specify. + */ + function _diag($xoff, $xlim, $yoff, $ylim, $nchunks) + { + $flip = false; + + if ($xlim - $xoff > $ylim - $yoff) + { + // Things seems faster (I'm not sure I understand why) when the shortest sequence is in X. + $flip = true; + list($xoff, $xlim, $yoff, $ylim) = array($yoff, $ylim, $xoff, $xlim); + } + + if ($flip) + { + for ($i = $ylim - 1; $i >= $yoff; $i--) + { + $ymatches[$this->xv[$i]][] = $i; + } + } + else + { + for ($i = $ylim - 1; $i >= $yoff; $i--) + { + $ymatches[$this->yv[$i]][] = $i; + } + } + + $this->lcs = 0; + $this->seq[0]= $yoff - 1; + $this->in_seq = array(); + $ymids[0] = array(); + + $numer = $xlim - $xoff + $nchunks - 1; + $x = $xoff; + + for ($chunk = 0; $chunk < $nchunks; $chunk++) + { + if ($chunk > 0) + { + for ($i = 0; $i <= $this->lcs; $i++) + { + $ymids[$i][$chunk - 1] = $this->seq[$i]; + } + } + + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks); + + for (; $x < $x1; $x++) + { + $line = $flip ? $this->yv[$x] : $this->xv[$x]; + if (empty($ymatches[$line])) + { + continue; + } + $matches = $ymatches[$line]; + + reset($matches); + while (list(, $y) = each($matches)) + { + if (empty($this->in_seq[$y])) + { + $k = $this->_lcs_pos($y); + $ymids[$k] = $ymids[$k - 1]; + break; + } + } + + // no reset() here + while (list(, $y) = each($matches)) + { + if ($y > $this->seq[$k - 1]) + { + // Optimization: this is a common case: next match is just replacing previous match. + $this->in_seq[$this->seq[$k]] = false; + $this->seq[$k] = $y; + $this->in_seq[$y] = 1; + } + else if (empty($this->in_seq[$y])) + { + $k = $this->_lcs_pos($y); + $ymids[$k] = $ymids[$k - 1]; + } + } + } + } + + $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); + $ymid = $ymids[$this->lcs]; + + for ($n = 0; $n < $nchunks - 1; $n++) + { + $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); + $y1 = $ymid[$n] + 1; + $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); + } + $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); + + return array($this->lcs, $seps); + } + + function _lcs_pos($ypos) + { + $end = $this->lcs; + + if ($end == 0 || $ypos > $this->seq[$end]) + { + $this->seq[++$this->lcs] = $ypos; + $this->in_seq[$ypos] = 1; + return $this->lcs; + } + + $beg = 1; + while ($beg < $end) + { + $mid = (int)(($beg + $end) / 2); + if ($ypos > $this->seq[$mid]) + { + $beg = $mid + 1; + } + else + { + $end = $mid; + } + } + + $this->in_seq[$this->seq[$end]] = false; + $this->seq[$end] = $ypos; + $this->in_seq[$ypos] = 1; + + return $end; + } + + /** + * Finds LCS of two sequences. + * + * The results are recorded in the vectors $this->{x,y}changed[], by + * storing a 1 in the element for each line that is an insertion or + * deletion (ie. is not in the LCS). + * + * The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1. + * + * Note that XLIM, YLIM are exclusive bounds. All line numbers are + * origin-0 and discarded lines are not counted. + */ + function _compareseq($xoff, $xlim, $yoff, $ylim) + { + // Slide down the bottom initial diagonal. + while ($xoff < $xlim && $yoff < $ylim && $this->xv[$xoff] == $this->yv[$yoff]) + { + ++$xoff; + ++$yoff; + } + + // Slide up the top initial diagonal. + while ($xlim > $xoff && $ylim > $yoff && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) + { + --$xlim; + --$ylim; + } + + if ($xoff == $xlim || $yoff == $ylim) + { + $lcs = 0; + } + else + { + // This is ad hoc but seems to work well. + // $nchunks = sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); + // $nchunks = max(2,min(8,(int)$nchunks)); + $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; + list($lcs, $seps) = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); + } + + if ($lcs == 0) + { + // X and Y sequences have no common subsequence: mark all changed. + while ($yoff < $ylim) + { + $this->ychanged[$this->yind[$yoff++]] = 1; + } + + while ($xoff < $xlim) + { + $this->xchanged[$this->xind[$xoff++]] = 1; + } + } + else + { + // Use the partitions to split this problem into subproblems. + reset($seps); + $pt1 = $seps[0]; + + while ($pt2 = next($seps)) + { + $this->_compareseq($pt1[0], $pt2[0], $pt1[1], $pt2[1]); + $pt1 = $pt2; + } + } + } + + /** + * Adjusts inserts/deletes of identical lines to join changes as much as possible. + * + * We do something when a run of changed lines include a line at one end + * and has an excluded, identical line at the other. We are free to + * choose which identical line is included. 'compareseq' usually chooses + * the one at the beginning, but usually it is cleaner to consider the + * following identical line to be the "change". + * + * This is extracted verbatim from analyze.c (GNU diffutils-2.7). + */ + function _shift_boundaries($lines, &$changed, $other_changed) + { + $i = 0; + $j = 0; + + $len = count($lines); + $other_len = count($other_changed); + + while (1) + { + // Scan forward to find the beginning of another run of + // changes. Also keep track of the corresponding point in the other file. + // + // Throughout this code, $i and $j are adjusted together so that + // the first $i elements of $changed and the first $j elements of + // $other_changed both contain the same number of zeros (unchanged lines). + // + // Furthermore, $j is always kept so that $j == $other_len or $other_changed[$j] == false. + while ($j < $other_len && $other_changed[$j]) + { + $j++; + } + + while ($i < $len && ! $changed[$i]) + { + $i++; + $j++; + + while ($j < $other_len && $other_changed[$j]) + { + $j++; + } + } + + if ($i == $len) + { + break; + } + + $start = $i; + + // Find the end of this run of changes. + while (++$i < $len && $changed[$i]) + { + continue; + } + + do + { + // Record the length of this run of changes, so that we can later determine whether the run has grown. + $runlength = $i - $start; + + // Move the changed region back, so long as the previous unchanged line matches the last changed one. + // This merges with previous changed regions. + while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) + { + $changed[--$start] = 1; + $changed[--$i] = false; + + while ($start > 0 && $changed[$start - 1]) + { + $start--; + } + + while ($other_changed[--$j]) + { + continue; + } + } + + // Set CORRESPONDING to the end of the changed run, at the last point where it corresponds to a changed run in the + // other file. CORRESPONDING == LEN means no such point has been found. + $corresponding = $j < $other_len ? $i : $len; + + // Move the changed region forward, so long as the first changed line matches the following unchanged one. + // This merges with following changed regions. + // Do this second, so that if there are no merges, the changed region is moved forward as far as possible. + while ($i < $len && $lines[$start] == $lines[$i]) + { + $changed[$start++] = false; + $changed[$i++] = 1; + + while ($i < $len && $changed[$i]) + { + $i++; + } + + $j++; + if ($j < $other_len && $other_changed[$j]) + { + $corresponding = $i; + while ($j < $other_len && $other_changed[$j]) + { + $j++; + } + } + } + } + while ($runlength != $i - $start); + + // If possible, move the fully-merged run of changes back to a corresponding run in the other file. + while ($corresponding < $i) + { + $changed[--$start] = 1; + $changed[--$i] = 0; + + while ($other_changed[--$j]) + { + continue; + } + } + } + } +} diff --git a/includes/diff/renderer.php b/includes/diff/renderer.php new file mode 100644 index 0000000..8a8b0c2 --- /dev/null +++ b/includes/diff/renderer.php @@ -0,0 +1,861 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Code from pear.php.net, Text_Diff-1.1.0 package +* http://pear.php.net/package/Text_Diff/ +* +* Modified by phpBB Limited to meet our coding standards +* and being able to integrate into phpBB +* +* A class to render Diffs in different formats. +* +* This class renders the diff in classic diff format. It is intended that +* this class be customized via inheritance, to obtain fancier outputs. +* +* Copyright 2004-2008 The Horde Project (http://www.horde.org/) +* +* @package diff +*/ +class diff_renderer +{ + /** + * Number of leading context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses may want to + * set this to other values. + */ + var $_leading_context_lines = 0; + + /** + * Number of trailing context "lines" to preserve. + * + * This should be left at zero for this class, but subclasses may want to + * set this to other values. + */ + var $_trailing_context_lines = 0; + + /** + * Constructor. + */ + function __construct($params = array()) + { + foreach ($params as $param => $value) + { + $v = '_' . $param; + if (isset($this->$v)) + { + $this->$v = $value; + } + } + } + + /** + * Get any renderer parameters. + * + * @return array All parameters of this renderer object. + */ + function get_params() + { + $params = array(); + foreach (get_object_vars($this) as $k => $v) + { + if ($k[0] == '_') + { + $params[substr($k, 1)] = $v; + } + } + + return $params; + } + + /** + * Renders a diff. + * + * @param diff &$diff A diff object. + * + * @return string The formatted output. + */ + function render(&$diff) + { + $xi = $yi = 1; + $block = false; + $context = array(); + + // Create a new diff object if it is a 3-way diff + if (is_a($diff, 'diff3')) + { + $diff3 = &$diff; + + $diff_1 = $diff3->get_original(); + $diff_2 = $diff3->merged_output(); + + unset($diff3); + + $diff = new diff($diff_1, $diff_2); + } + + $nlead = $this->_leading_context_lines; + $ntrail = $this->_trailing_context_lines; + + $output = $this->_start_diff(); + $diffs = $diff->get_diff(); + + foreach ($diffs as $i => $edit) + { + // If these are unchanged (copied) lines, and we want to keep leading or trailing context lines, extract them from the copy block. + if (is_a($edit, 'diff_op_copy')) + { + // Do we have any diff blocks yet? + if (is_array($block)) + { + // How many lines to keep as context from the copy block. + $keep = ($i == count($diffs) - 1) ? $ntrail : $nlead + $ntrail; + if (count($edit->orig) <= $keep) + { + // We have less lines in the block than we want for context => keep the whole block. + $block[] = $edit; + } + else + { + if ($ntrail) + { + // Create a new block with as many lines as we need for the trailing context. + $context = array_slice($edit->orig, 0, $ntrail); + $block[] = new diff_op_copy($context); + } + + $output .= $this->_block($x0, $ntrail + $xi - $x0, $y0, $ntrail + $yi - $y0, $block); + $block = false; + } + } + // Keep the copy block as the context for the next block. + $context = $edit->orig; + } + else + { + // Don't we have any diff blocks yet? + if (!is_array($block)) + { + // Extract context lines from the preceding copy block. + $context = array_slice($context, count($context) - $nlead); + $x0 = $xi - count($context); + $y0 = $yi - count($context); + $block = array(); + + if ($context) + { + $block[] = new diff_op_copy($context); + } + } + $block[] = $edit; + } + + $xi += ($edit->orig) ? count($edit->orig) : 0; + $yi += ($edit->final) ? count($edit->final) : 0; + } + + if (is_array($block)) + { + $output .= $this->_block($x0, $xi - $x0, $y0, $yi - $y0, $block); + } + + return $output . $this->_end_diff(); + } + + function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) + { + $output = $this->_start_block($this->_block_header($xbeg, $xlen, $ybeg, $ylen)); + + foreach ($edits as $edit) + { + switch (get_class($edit)) + { + case 'diff_op_copy': + $output .= $this->_context($edit->orig); + break; + + case 'diff_op_add': + $output .= $this->_added($edit->final); + break; + + case 'diff_op_delete': + $output .= $this->_deleted($edit->orig); + break; + + case 'diff_op_change': + $output .= $this->_changed($edit->orig, $edit->final); + break; + } + } + + return $output . $this->_end_block(); + } + + function _start_diff() + { + return ''; + } + + function _end_diff() + { + return ''; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen > 1) + { + $xbeg .= ',' . ($xbeg + $xlen - 1); + } + + if ($ylen > 1) + { + $ybeg .= ',' . ($ybeg + $ylen - 1); + } + + // this matches the GNU Diff behaviour + if ($xlen && !$ylen) + { + $ybeg--; + } + else if (!$xlen) + { + $xbeg--; + } + + return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; + } + + function _start_block($header) + { + return $header . "\n"; + } + + function _end_block() + { + return ''; + } + + function _lines($lines, $prefix = ' ') + { + return $prefix . implode("\n$prefix", $lines) . "\n"; + } + + function _context($lines) + { + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '> '); + } + + function _deleted($lines) + { + return $this->_lines($lines, '< '); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . "---\n" . $this->_added($final); + } + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + return $this->render($diff); + } +} + +/** +* Renders a unified diff +* @package diff +*/ +class diff_renderer_unified extends diff_renderer +{ + var $_leading_context_lines = 4; + var $_trailing_context_lines = 4; + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + return nl2br($this->render($diff)); + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen != 1) + { + $xbeg .= ',' . $xlen; + } + + if ($ylen != 1) + { + $ybeg .= ',' . $ylen; + } + return '
@@ -' . $xbeg . ' +' . $ybeg . ' @@
'; + } + + function _context($lines) + { + return '
' . htmlspecialchars($this->_lines($lines, ' ')) . '
'; + } + + function _added($lines) + { + return '
' . htmlspecialchars($this->_lines($lines, '+')) . '
'; + } + + function _deleted($lines) + { + return '
' . htmlspecialchars($this->_lines($lines, '-')) . '
'; + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } + + function _start_diff() + { + $start = '
'; + + return $start; + } + + function _end_diff() + { + return '
'; + } + + function _end_block() + { + return ''; + } +} + +/** +* "Inline" diff renderer. +* +* This class renders diffs in the Wiki-style "inline" format. +* +* @author Ciprian Popovici +* @package diff +*/ +class diff_renderer_inline extends diff_renderer +{ + var $_leading_context_lines = 10000; + var $_trailing_context_lines = 10000; + + // Prefix and suffix for inserted text + var $_ins_prefix = ''; + var $_ins_suffix = ''; + + // Prefix and suffix for deleted text + var $_del_prefix = ''; + var $_del_suffix = ''; + + var $_block_head = ''; + + // What are we currently splitting on? Used to recurse to show word-level + var $_split_level = 'lines'; + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + return '
' . nl2br($this->render($diff)) . '
'; + } + + function _start_diff() + { + return ''; + } + + function _end_diff() + { + return ''; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + return $this->_block_head; + } + + function _start_block($header) + { + return $header; + } + + function _lines($lines, $prefix = ' ', $encode = true) + { + if ($encode) + { + array_walk($lines, array(&$this, '_encode')); + } + + if ($this->_split_level == 'words') + { + return implode('', $lines); + } + else + { + return implode("\n", $lines) . "\n"; + } + } + + function _added($lines) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_ins_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_ins_suffix; + return $this->_lines($lines, ' ', false); + } + + function _deleted($lines, $words = false) + { + array_walk($lines, array(&$this, '_encode')); + $lines[0] = $this->_del_prefix . $lines[0]; + $lines[count($lines) - 1] .= $this->_del_suffix; + return $this->_lines($lines, ' ', false); + } + + function _changed($orig, $final) + { + // If we've already split on words, don't try to do so again - just display. + if ($this->_split_level == 'words') + { + $prefix = ''; + while ($orig[0] !== false && $final[0] !== false && substr($orig[0], 0, 1) == ' ' && substr($final[0], 0, 1) == ' ') + { + $prefix .= substr($orig[0], 0, 1); + $orig[0] = substr($orig[0], 1); + $final[0] = substr($final[0], 1); + } + + return $prefix . $this->_deleted($orig) . $this->_added($final); + } + + $text1 = implode("\n", $orig); + $text2 = implode("\n", $final); + + // Non-printing newline marker. + $nl = "\0"; + + // We want to split on word boundaries, but we need to preserve whitespace as well. + // Therefore we split on words, but include all blocks of whitespace in the wordlist. + $splitted_text_1 = $this->_split_on_words($text1, $nl); + $splitted_text_2 = $this->_split_on_words($text2, $nl); + + $diff = new diff($splitted_text_1, $splitted_text_2); + unset($splitted_text_1, $splitted_text_2); + + // Get the diff in inline format. + $renderer = new diff_renderer_inline(array_merge($this->get_params(), array('split_level' => 'words'))); + + // Run the diff and get the output. + return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; + } + + function _split_on_words($string, $newline_escape = "\n") + { + // Ignore \0; otherwise the while loop will never finish. + $string = str_replace("\0", '', $string); + + $words = array(); + $length = strlen($string); + $pos = 0; + + $tab_there = true; + while ($pos < $length) + { + // Check for tabs... do not include them + if ($tab_there && substr($string, $pos, 1) === "\t") + { + $words[] = "\t"; + $pos++; + + continue; + } + else + { + $tab_there = false; + } + + // Eat a word with any preceding whitespace. + $spaces = strspn(substr($string, $pos), " \n"); + $nextpos = strcspn(substr($string, $pos + $spaces), " \n"); + $words[] = str_replace("\n", $newline_escape, substr($string, $pos, $spaces + $nextpos)); + $pos += $spaces + $nextpos; + } + + return $words; + } + + function _encode(&$string) + { + $string = htmlspecialchars($string); + } +} + +/** +* "raw" diff renderer. +* This class could be used to output a raw unified patch file +* +* @package diff +*/ +class diff_renderer_raw extends diff_renderer +{ + var $_leading_context_lines = 4; + var $_trailing_context_lines = 4; + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + return ''; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + if ($xlen != 1) + { + $xbeg .= ',' . $xlen; + } + + if ($ylen != 1) + { + $ybeg .= ',' . $ylen; + } + return '@@ -' . $xbeg . ' +' . $ybeg . ' @@'; + } + + function _context($lines) + { + return $this->_lines($lines, ' '); + } + + function _added($lines) + { + return $this->_lines($lines, '+'); + } + + function _deleted($lines) + { + return $this->_lines($lines, '-'); + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } +} + +/** +* "chora (Horde)" diff renderer - similar style. +* This renderer class is a modified human_readable function from the Horde Framework. +* +* @package diff +*/ +class diff_renderer_side_by_side extends diff_renderer +{ + var $_leading_context_lines = 3; + var $_trailing_context_lines = 3; + + var $lines = array(); + + // Hold the left and right columns of lines for change blocks. + var $cols; + var $state; + + var $data = false; + + /** + * Our function to get the diff + */ + function get_diff_content($diff) + { + global $user; + + $output = ''; + $output .= ' + + +'; + + $this->render($diff); + + // Is the diff empty? + if (!count($this->lines)) + { + $output .= ''; + } + else + { + // Iterate through every header block of changes + foreach ($this->lines as $header) + { + $output .= ''; + + // Each header block consists of a number of changes (add, remove, change). + $current_context = ''; + + foreach ($header['contents'] as $change) + { + if (!empty($current_context) && $change['type'] != 'empty') + { + $line = $current_context; + $current_context = ''; + + $output .= ' + '; + } + + switch ($change['type']) + { + case 'add': + $line = ''; + + foreach ($change['lines'] as $_line) + { + $line .= htmlspecialchars($_line) . '
'; + } + + $output .= ''; + break; + + case 'remove': + $line = ''; + + foreach ($change['lines'] as $_line) + { + $line .= htmlspecialchars($_line) . '
'; + } + + $output .= ''; + break; + + case 'empty': + $current_context .= htmlspecialchars($change['line']) . '
'; + break; + + case 'change': + // Pop the old/new stacks one by one, until both are empty. + $oldsize = count($change['old']); + $newsize = count($change['new']); + $left = $right = ''; + + for ($row = 0, $row_max = max($oldsize, $newsize); $row < $row_max; ++$row) + { + $left .= isset($change['old'][$row]) ? htmlspecialchars($change['old'][$row]) : ''; + $left .= '
'; + $right .= isset($change['new'][$row]) ? htmlspecialchars($change['new'][$row]) : ''; + $right .= '
'; + } + + $output .= ''; + + if (!empty($left)) + { + $output .= ''; + } + else if ($row < $oldsize) + { + $output .= ''; + } + else + { + $output .= ''; + } + + if (!empty($right)) + { + $output .= ''; + } + else if ($row < $newsize) + { + $output .= ''; + } + else + { + $output .= ''; + } + + $output .= ''; + break; + } + } + + if (!empty($current_context)) + { + $line = $current_context; + $current_context = ''; + + $output .= ''; + $output .= ''; + } + } + } + + $output .= '
+   ' . $user->lang['LINE_UNMODIFIED'] . ' +   ' . $user->lang['LINE_ADDED'] . ' +   ' . $user->lang['LINE_MODIFIED'] . ' +   ' . $user->lang['LINE_REMOVED'] . ' +
' . $user->lang['NO_VISIBLE_CHANGES'] . '
' . $user->lang['LINE'] . ' ' . $header['oldline'] . '' . $user->lang['LINE'] . ' ' . $header['newline'] . '
' . ((strlen($line)) ? $line : ' ') . '
' . ((strlen($line)) ? $line : ' ') . '
 
' . ((strlen($line)) ? $line : ' ') . '
' . ((strlen($line)) ? $line : ' ') . '
 
' . $left . '
  
' . $right . '
  
' . ((strlen($line)) ? $line : ' ') . '
' . ((strlen($line)) ? $line : ' ') . '
'; + + return $output; + } + + function _start_diff() + { + $this->lines = array(); + + $this->data = false; + $this->cols = array(array(), array()); + $this->state = 'empty'; + + return ''; + } + + function _end_diff() + { + // Just flush any remaining entries in the columns stack. + switch ($this->state) + { + case 'add': + $this->data['contents'][] = array('type' => 'add', 'lines' => $this->cols[0]); + break; + + case 'remove': + // We have some removal lines pending in our stack, so flush them. + $this->data['contents'][] = array('type' => 'remove', 'lines' => $this->cols[0]); + break; + + case 'change': + // We have both remove and addition lines, so this is a change block. + $this->data['contents'][] = array('type' => 'change', 'old' => $this->cols[0], 'new' => $this->cols[1]); + break; + } + + if ($this->data !== false) + { + $this->lines[] = $this->data; + } + + return ''; + } + + function _block_header($xbeg, $xlen, $ybeg, $ylen) + { + // Push any previous header information to the return stack. + if ($this->data !== false) + { + $this->lines[] = $this->data; + } + + $this->data = array('type' => 'header', 'oldline' => $xbeg, 'newline' => $ybeg, 'contents' => array()); + $this->state = 'dump'; + } + + function _added($lines) + { + array_walk($lines, array(&$this, '_perform_add')); + } + + function _perform_add($line) + { + if ($this->state == 'empty') + { + return ''; + } + + // This is just an addition line. + if ($this->state == 'dump' || $this->state == 'add') + { + // Start adding to the addition stack. + $this->cols[0][] = $line; + $this->state = 'add'; + } + else + { + // This is inside a change block, so start accumulating lines. + $this->state = 'change'; + $this->cols[1][] = $line; + } + } + + function _deleted($lines) + { + array_walk($lines, array(&$this, '_perform_delete')); + } + + function _perform_delete($line) + { + // This is a removal line. + $this->state = 'remove'; + $this->cols[0][] = $line; + } + + function _context($lines) + { + array_walk($lines, array(&$this, '_perform_context')); + } + + function _perform_context($line) + { + // An empty block with no action. + switch ($this->state) + { + case 'add': + $this->data['contents'][] = array('type' => 'add', 'lines' => $this->cols[0]); + break; + + case 'remove': + // We have some removal lines pending in our stack, so flush them. + $this->data['contents'][] = array('type' => 'remove', 'lines' => $this->cols[0]); + break; + + case 'change': + // We have both remove and addition lines, so this is a change block. + $this->data['contents'][] = array('type' => 'change', 'old' => $this->cols[0], 'new' => $this->cols[1]); + break; + } + + $this->cols = array(array(), array()); + $this->data['contents'][] = array('type' => 'empty', 'line' => $line); + $this->state = 'dump'; + } + + function _changed($orig, $final) + { + return $this->_deleted($orig) . $this->_added($final); + } + +} diff --git a/includes/functions.php b/includes/functions.php new file mode 100644 index 0000000..e1849ec --- /dev/null +++ b/includes/functions.php @@ -0,0 +1,4947 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +// Common global functions +/** +* Load the autoloaders added by the extensions. +* +* @param string $phpbb_root_path Path to the phpbb root directory. +*/ +function phpbb_load_extensions_autoloaders($phpbb_root_path) +{ + $iterator = new \RecursiveIteratorIterator( + new \phpbb\recursive_dot_prefix_filter_iterator( + new \RecursiveDirectoryIterator( + $phpbb_root_path . 'ext/', + \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS + ) + ), + \RecursiveIteratorIterator::SELF_FIRST + ); + $iterator->setMaxDepth(2); + + foreach ($iterator as $file_info) + { + if ($file_info->getFilename() === 'vendor' && $iterator->getDepth() === 2) + { + $filename = $file_info->getRealPath() . '/autoload.php'; + if (file_exists($filename)) + { + require $filename; + } + } + } +} + +/** +* Casts a variable to the given type. +* +* @deprecated +*/ +function set_var(&$result, $var, $type, $multibyte = false) +{ + // no need for dependency injection here, if you have the object, call the method yourself! + $type_cast_helper = new \phpbb\request\type_cast_helper(); + $type_cast_helper->set_var($result, $var, $type, $multibyte); +} + +/** +* Generates an alphanumeric random string of given length +* +* @param int $num_chars Length of random string, defaults to 8. +* This number should be less or equal than 64. +* +* @return string +*/ +function gen_rand_string($num_chars = 8) +{ + $range = array_merge(range('A', 'Z'), range(0, 9)); + $size = count($range); + + $output = ''; + for ($i = 0; $i < $num_chars; $i++) + { + $rand = random_int(0, $size-1); + $output .= $range[$rand]; + } + + return $output; +} + +/** +* Generates a user-friendly alphanumeric random string of given length +* We remove 0 and O so users cannot confuse those in passwords etc. +* +* @param int $num_chars Length of random string, defaults to 8. +* This number should be less or equal than 64. +* +* @return string +*/ +function gen_rand_string_friendly($num_chars = 8) +{ + $range = array_merge(range('A', 'N'), range('P', 'Z'), range(1, 9)); + $size = count($range); + + $output = ''; + for ($i = 0; $i < $num_chars; $i++) + { + $rand = random_int(0, $size-1); + $output .= $range[$rand]; + } + + return $output; +} + +/** +* Return unique id +*/ +function unique_id() +{ + return strtolower(gen_rand_string(16)); +} + +/** +* Wrapper for mt_rand() which allows swapping $min and $max parameters. +* +* PHP does not allow us to swap the order of the arguments for mt_rand() anymore. +* (since PHP 5.3.4, see http://bugs.php.net/46587) +* +* @param int $min Lowest value to be returned +* @param int $max Highest value to be returned +* +* @return int Random integer between $min and $max (or $max and $min) +*/ +function phpbb_mt_rand($min, $max) +{ + return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max); +} + +/** +* Wrapper for getdate() which returns the equivalent array for UTC timestamps. +* +* @param int $time Unix timestamp (optional) +* +* @return array Returns an associative array of information related to the timestamp. +* See http://www.php.net/manual/en/function.getdate.php +*/ +function phpbb_gmgetdate($time = false) +{ + if ($time === false) + { + $time = time(); + } + + // getdate() interprets timestamps in local time. + // What follows uses the fact that getdate() and + // date('Z') balance each other out. + return getdate($time - date('Z')); +} + +/** +* Return formatted string for filesizes +* +* @param mixed $value filesize in bytes +* (non-negative number; int, float or string) +* @param bool $string_only true if language string should be returned +* @param array $allowed_units only allow these units (data array indexes) +* +* @return mixed data array if $string_only is false +*/ +function get_formatted_filesize($value, $string_only = true, $allowed_units = false) +{ + global $user; + + $available_units = array( + 'tb' => array( + 'min' => 1099511627776, // pow(2, 40) + 'index' => 4, + 'si_unit' => 'TB', + 'iec_unit' => 'TIB', + ), + 'gb' => array( + 'min' => 1073741824, // pow(2, 30) + 'index' => 3, + 'si_unit' => 'GB', + 'iec_unit' => 'GIB', + ), + 'mb' => array( + 'min' => 1048576, // pow(2, 20) + 'index' => 2, + 'si_unit' => 'MB', + 'iec_unit' => 'MIB', + ), + 'kb' => array( + 'min' => 1024, // pow(2, 10) + 'index' => 1, + 'si_unit' => 'KB', + 'iec_unit' => 'KIB', + ), + 'b' => array( + 'min' => 0, + 'index' => 0, + 'si_unit' => 'BYTES', // Language index + 'iec_unit' => 'BYTES', // Language index + ), + ); + + foreach ($available_units as $si_identifier => $unit_info) + { + if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units)) + { + continue; + } + + if ($value >= $unit_info['min']) + { + $unit_info['si_identifier'] = $si_identifier; + + break; + } + } + unset($available_units); + + for ($i = 0; $i < $unit_info['index']; $i++) + { + $value /= 1024; + } + $value = round($value, 2); + + // Lookup units in language dictionary + $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit']; + $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit']; + + // Default to IEC + $unit_info['unit'] = $unit_info['iec_unit']; + + if (!$string_only) + { + $unit_info['value'] = $value; + + return $unit_info; + } + + return $value . ' ' . $unit_info['unit']; +} + +/** +* Determine whether we are approaching the maximum execution time. Should be called once +* at the beginning of the script in which it's used. +* @return bool Either true if the maximum execution time is nearly reached, or false +* if some time is still left. +*/ +function still_on_time($extra_time = 15) +{ + static $max_execution_time, $start_time; + + $current_time = microtime(true); + + if (empty($max_execution_time)) + { + $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time'); + + // If zero, then set to something higher to not let the user catch the ten seconds barrier. + if ($max_execution_time === 0) + { + $max_execution_time = 50 + $extra_time; + } + + $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50); + + // For debugging purposes + // $max_execution_time = 10; + + global $starttime; + $start_time = (empty($starttime)) ? $current_time : $starttime; + } + + return (ceil($current_time - $start_time) < $max_execution_time) ? true : false; +} + +/** +* Hashes an email address to a big integer +* +* @param string $email Email address +* +* @return string Unsigned Big Integer +*/ +function phpbb_email_hash($email) +{ + return sprintf('%u', crc32(strtolower($email))) . strlen($email); +} + +/** +* Wrapper for version_compare() that allows using uppercase A and B +* for alpha and beta releases. +* +* See http://www.php.net/manual/en/function.version-compare.php +* +* @param string $version1 First version number +* @param string $version2 Second version number +* @param string $operator Comparison operator (optional) +* +* @return mixed Boolean (true, false) if comparison operator is specified. +* Integer (-1, 0, 1) otherwise. +*/ +function phpbb_version_compare($version1, $version2, $operator = null) +{ + $version1 = strtolower($version1); + $version2 = strtolower($version2); + + if (is_null($operator)) + { + return version_compare($version1, $version2); + } + else + { + return version_compare($version1, $version2, $operator); + } +} + +// functions used for building option fields + +/** +* Pick a language, any language ... +*/ +function language_select($default = '') +{ + global $db; + + $sql = 'SELECT lang_iso, lang_local_name + FROM ' . LANG_TABLE . ' + ORDER BY lang_english_name'; + $result = $db->sql_query($sql); + + $lang_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : ''; + $lang_options .= ''; + } + $db->sql_freeresult($result); + + return $lang_options; +} + +/** +* Pick a template/theme combo, +*/ +function style_select($default = '', $all = false) +{ + global $db; + + $sql_where = (!$all) ? 'WHERE style_active = 1 ' : ''; + $sql = 'SELECT style_id, style_name + FROM ' . STYLES_TABLE . " + $sql_where + ORDER BY style_name"; + $result = $db->sql_query($sql); + + $style_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + $selected = ($row['style_id'] == $default) ? ' selected="selected"' : ''; + $style_options .= ''; + } + $db->sql_freeresult($result); + + return $style_options; +} + +/** +* Format the timezone offset with hours and minutes +* +* @param int $tz_offset Timezone offset in seconds +* @param bool $show_null Whether null offsets should be shown +* @return string Normalized offset string: -7200 => -02:00 +* 16200 => +04:30 +*/ +function phpbb_format_timezone_offset($tz_offset, $show_null = false) +{ + $sign = ($tz_offset < 0) ? '-' : '+'; + $time_offset = abs($tz_offset); + + if ($time_offset == 0 && $show_null == false) + { + return ''; + } + + $offset_seconds = $time_offset % 3600; + $offset_minutes = $offset_seconds / 60; + $offset_hours = ($time_offset - $offset_seconds) / 3600; + + $offset_string = sprintf("%s%02d:%02d", $sign, $offset_hours, $offset_minutes); + return $offset_string; +} + +/** +* Compares two time zone labels. +* Arranges them in increasing order by timezone offset. +* Places UTC before other timezones in the same offset. +*/ +function phpbb_tz_select_compare($a, $b) +{ + $a_sign = $a[3]; + $b_sign = $b[3]; + if ($a_sign != $b_sign) + { + return $a_sign == '-' ? -1 : 1; + } + + $a_offset = substr($a, 4, 5); + $b_offset = substr($b, 4, 5); + if ($a_offset == $b_offset) + { + $a_name = substr($a, 12); + $b_name = substr($b, 12); + if ($a_name == $b_name) + { + return 0; + } + else if ($a_name == 'UTC') + { + return -1; + } + else if ($b_name == 'UTC') + { + return 1; + } + else + { + return $a_name < $b_name ? -1 : 1; + } + } + else + { + if ($a_sign == '-') + { + return $a_offset > $b_offset ? -1 : 1; + } + else + { + return $a_offset < $b_offset ? -1 : 1; + } + } +} + +/** +* Return list of timezone identifiers +* We also add the selected timezone if we can create an object with it. +* DateTimeZone::listIdentifiers seems to not add all identifiers to the list, +* because some are only kept for backward compatible reasons. If the user has +* a deprecated value, we add it here, so it can still be kept. Once the user +* changed his value, there is no way back to deprecated values. +* +* @param string $selected_timezone Additional timezone that shall +* be added to the list of identiers +* @return array DateTimeZone::listIdentifiers and additional +* selected_timezone if it is a valid timezone. +*/ +function phpbb_get_timezone_identifiers($selected_timezone) +{ + $timezones = DateTimeZone::listIdentifiers(); + + if (!in_array($selected_timezone, $timezones)) + { + try + { + // Add valid timezones that are currently selected but not returned + // by DateTimeZone::listIdentifiers + $validate_timezone = new DateTimeZone($selected_timezone); + $timezones[] = $selected_timezone; + } + catch (\Exception $e) + { + } + } + + return $timezones; +} + +/** +* Options to pick a timezone and date/time +* +* @param \phpbb\template\template $template phpBB template object +* @param \phpbb\user $user Object of the current user +* @param string $default A timezone to select +* @param boolean $truncate Shall we truncate the options text +* +* @return array Returns an array containing the options for the time selector. +*/ +function phpbb_timezone_select($template, $user, $default = '', $truncate = false) +{ + static $timezones; + + $default_offset = ''; + if (!isset($timezones)) + { + $unsorted_timezones = phpbb_get_timezone_identifiers($default); + + $timezones = array(); + foreach ($unsorted_timezones as $timezone) + { + $tz = new DateTimeZone($timezone); + $dt = $user->create_datetime('now', $tz); + $offset = $dt->getOffset(); + $current_time = $dt->format($user->lang['DATETIME_FORMAT'], true); + $offset_string = phpbb_format_timezone_offset($offset, true); + $timezones['UTC' . $offset_string . ' - ' . $timezone] = array( + 'tz' => $timezone, + 'offset' => $offset_string, + 'current' => $current_time, + ); + if ($timezone === $default) + { + $default_offset = 'UTC' . $offset_string; + } + } + unset($unsorted_timezones); + + uksort($timezones, 'phpbb_tz_select_compare'); + } + + $tz_select = $opt_group = ''; + + foreach ($timezones as $key => $timezone) + { + if ($opt_group != $timezone['offset']) + { + // Generate tz_select for backwards compatibility + $tz_select .= ($opt_group) ? '' : ''; + $tz_select .= ''; + $opt_group = $timezone['offset']; + $template->assign_block_vars('timezone_select', array( + 'LABEL' => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']), + 'VALUE' => $key . ' - ' . $timezone['current'], + )); + + $selected = (!empty($default_offset) && strpos($key, $default_offset) !== false) ? ' selected="selected"' : ''; + $template->assign_block_vars('timezone_date', array( + 'VALUE' => $key . ' - ' . $timezone['current'], + 'SELECTED' => !empty($selected), + 'TITLE' => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']), + )); + } + + $label = $timezone['tz']; + if (isset($user->lang['timezones'][$label])) + { + $label = $user->lang['timezones'][$label]; + } + $title = $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $label); + + if ($truncate) + { + $label = truncate_string($label, 50, 255, false, '...'); + } + + // Also generate timezone_select for backwards compatibility + $selected = ($timezone['tz'] === $default) ? ' selected="selected"' : ''; + $tz_select .= ''; + $template->assign_block_vars('timezone_select.timezone_options', array( + 'TITLE' => $title, + 'VALUE' => $timezone['tz'], + 'SELECTED' => !empty($selected), + 'LABEL' => $label, + )); + } + $tz_select .= ''; + + return $tz_select; +} + +// Functions handling topic/post tracking/marking + +/** +* Marks a topic/forum as read +* Marks a topic as posted to +* +* @param string $mode (all, topics, topic, post) +* @param int|bool $forum_id Used in all, topics, and topic mode +* @param int|bool $topic_id Used in topic and post mode +* @param int $post_time 0 means current time(), otherwise to set a specific mark time +* @param int $user_id can only be used with $mode == 'post' +*/ +function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0) +{ + global $db, $user, $config; + global $request, $phpbb_container, $phpbb_dispatcher; + + $post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time; + + $should_markread = true; + + /** + * This event is used for performing actions directly before marking forums, + * topics or posts as read. + * + * It is also possible to prevent the marking. For that, the $should_markread parameter + * should be set to FALSE. + * + * @event core.markread_before + * @var string mode Variable containing marking mode value + * @var mixed forum_id Variable containing forum id, or false + * @var mixed topic_id Variable containing topic id, or false + * @var int post_time Variable containing post time + * @var int user_id Variable containing the user id + * @var bool should_markread Flag indicating if the markread should be done or not. + * @since 3.1.4-RC1 + */ + $vars = array( + 'mode', + 'forum_id', + 'topic_id', + 'post_time', + 'user_id', + 'should_markread', + ); + extract($phpbb_dispatcher->trigger_event('core.markread_before', compact($vars))); + + if (!$should_markread) + { + return; + } + + if ($mode == 'all') + { + if (empty($forum_id)) + { + // Mark all forums read (index page) + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + // Mark all topic notifications read for this user + $phpbb_notifications->mark_notifications(array( + 'notification.type.topic', + 'notification.type.quote', + 'notification.type.bookmark', + 'notification.type.post', + 'notification.type.approve_topic', + 'notification.type.approve_post', + ), false, $user->data['user_id'], $post_time); + + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + // Mark all forums read (index page) + $tables = array(TOPICS_TRACK_TABLE, FORUMS_TRACK_TABLE); + foreach ($tables as $table) + { + $sql = 'DELETE FROM ' . $table . " + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time"; + $db->sql_query($sql); + } + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND user_lastmark < $post_time"; + $db->sql_query($sql); + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); + $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); + + unset($tracking_topics['tf']); + unset($tracking_topics['t']); + unset($tracking_topics['f']); + $tracking_topics['l'] = base_convert($post_time - $config['board_startdate'], 10, 36); + + $user->set_cookie('track', tracking_serialize($tracking_topics), $post_time + 31536000); + $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking_topics), \phpbb\request\request_interface::COOKIE); + + unset($tracking_topics); + + if ($user->data['is_registered']) + { + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND user_lastmark < $post_time"; + $db->sql_query($sql); + } + } + } + } + else if ($mode == 'topics') + { + // Mark all topics in forums read + if (!is_array($forum_id)) + { + $forum_id = array($forum_id); + } + else + { + $forum_id = array_unique($forum_id); + } + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications_by_parent(array( + 'notification.type.topic', + 'notification.type.approve_topic', + ), $forum_id, $user->data['user_id'], $post_time); + + // Mark all post/quote notifications read for this user in this forum + $topic_ids = array(); + $sql = 'SELECT topic_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_id); + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) + { + $topic_ids[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + $phpbb_notifications->mark_notifications_by_parent(array( + 'notification.type.quote', + 'notification.type.bookmark', + 'notification.type.post', + 'notification.type.approve_post', + ), $topic_ids, $user->data['user_id'], $post_time); + + // Add 0 to forums array to mark global announcements correctly + // $forum_id[] = 0; + + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time + AND " . $db->sql_in_set('forum_id', $forum_id); + $db->sql_query($sql); + + $sql = 'SELECT forum_id + FROM ' . FORUMS_TRACK_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND " . $db->sql_in_set('forum_id', $forum_id); + $result = $db->sql_query($sql); + + $sql_update = array(); + while ($row = $db->sql_fetchrow($result)) + { + $sql_update[] = (int) $row['forum_id']; + } + $db->sql_freeresult($result); + + if (count($sql_update)) + { + $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . " + SET mark_time = $post_time + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time + AND " . $db->sql_in_set('forum_id', $sql_update); + $db->sql_query($sql); + } + + if ($sql_insert = array_diff($forum_id, $sql_update)) + { + $sql_ary = array(); + foreach ($sql_insert as $f_id) + { + $sql_ary[] = array( + 'user_id' => (int) $user->data['user_id'], + 'forum_id' => (int) $f_id, + 'mark_time' => $post_time, + ); + } + + $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary); + } + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); + $tracking = ($tracking) ? tracking_unserialize($tracking) : array(); + + foreach ($forum_id as $f_id) + { + $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array(); + + if (isset($tracking['tf'][$f_id])) + { + unset($tracking['tf'][$f_id]); + } + + foreach ($topic_ids36 as $topic_id36) + { + unset($tracking['t'][$topic_id36]); + } + + if (isset($tracking['f'][$f_id])) + { + unset($tracking['f'][$f_id]); + } + + $tracking['f'][$f_id] = base_convert($post_time - $config['board_startdate'], 10, 36); + } + + if (isset($tracking['tf']) && empty($tracking['tf'])) + { + unset($tracking['tf']); + } + + $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000); + $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE); + + unset($tracking); + } + } + else if ($mode == 'topic') + { + if ($topic_id === false || $forum_id === false) + { + return; + } + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + // Mark post notifications read for this user in this topic + $phpbb_notifications->mark_notifications(array( + 'notification.type.topic', + 'notification.type.approve_topic', + ), $topic_id, $user->data['user_id'], $post_time); + + $phpbb_notifications->mark_notifications_by_parent(array( + 'notification.type.quote', + 'notification.type.bookmark', + 'notification.type.post', + 'notification.type.approve_post', + ), $topic_id, $user->data['user_id'], $post_time); + + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . " + SET mark_time = $post_time + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time + AND topic_id = $topic_id"; + $db->sql_query($sql); + + // insert row + if (!$db->sql_affectedrows()) + { + $db->sql_return_on_error(true); + + $sql_ary = array( + 'user_id' => (int) $user->data['user_id'], + 'topic_id' => (int) $topic_id, + 'forum_id' => (int) $forum_id, + 'mark_time' => $post_time, + ); + + $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + + $db->sql_return_on_error(false); + } + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); + $tracking = ($tracking) ? tracking_unserialize($tracking) : array(); + + $topic_id36 = base_convert($topic_id, 10, 36); + + if (!isset($tracking['t'][$topic_id36])) + { + $tracking['tf'][$forum_id][$topic_id36] = true; + } + + $tracking['t'][$topic_id36] = base_convert($post_time - (int) $config['board_startdate'], 10, 36); + + // If the cookie grows larger than 10000 characters we will remove the smallest value + // This can result in old topics being unread - but most of the time it should be accurate... + if (strlen($request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE)) > 10000) + { + //echo 'Cookie grown too large' . print_r($tracking, true); + + // We get the ten most minimum stored time offsets and its associated topic ids + $time_keys = array(); + for ($i = 0; $i < 10 && count($tracking['t']); $i++) + { + $min_value = min($tracking['t']); + $m_tkey = array_search($min_value, $tracking['t']); + unset($tracking['t'][$m_tkey]); + + $time_keys[$m_tkey] = $min_value; + } + + // Now remove the topic ids from the array... + foreach ($tracking['tf'] as $f_id => $topic_id_ary) + { + foreach ($time_keys as $m_tkey => $min_value) + { + if (isset($topic_id_ary[$m_tkey])) + { + $tracking['f'][$f_id] = $min_value; + unset($tracking['tf'][$f_id][$m_tkey]); + } + } + } + + if ($user->data['is_registered']) + { + $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10)); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastmark = $post_time + WHERE user_id = {$user->data['user_id']} + AND mark_time < $post_time"; + $db->sql_query($sql); + } + else + { + $tracking['l'] = max($time_keys); + } + } + + $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000); + $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE); + } + } + else if ($mode == 'post') + { + if ($topic_id === false) + { + return; + } + + $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id; + + if ($config['load_db_track'] && $use_user_id != ANONYMOUS) + { + $db->sql_return_on_error(true); + + $sql_ary = array( + 'user_id' => (int) $use_user_id, + 'topic_id' => (int) $topic_id, + 'topic_posted' => 1, + ); + + $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + + $db->sql_return_on_error(false); + } + } + + /** + * This event is used for performing actions directly after forums, + * topics or posts have been marked as read. + * + * @event core.markread_after + * @var string mode Variable containing marking mode value + * @var mixed forum_id Variable containing forum id, or false + * @var mixed topic_id Variable containing topic id, or false + * @var int post_time Variable containing post time + * @var int user_id Variable containing the user id + * @since 3.2.6-RC1 + */ + $vars = array( + 'mode', + 'forum_id', + 'topic_id', + 'post_time', + 'user_id', + ); + extract($phpbb_dispatcher->trigger_event('core.markread_after', compact($vars))); +} + +/** +* Get topic tracking info by using already fetched info +*/ +function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false) +{ + global $user; + + $last_read = array(); + + if (!is_array($topic_ids)) + { + $topic_ids = array($topic_ids); + } + + foreach ($topic_ids as $topic_id) + { + if (!empty($rowset[$topic_id]['mark_time'])) + { + $last_read[$topic_id] = $rowset[$topic_id]['mark_time']; + } + } + + $topic_ids = array_diff($topic_ids, array_keys($last_read)); + + if (count($topic_ids)) + { + $mark_time = array(); + + if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false) + { + $mark_time[$forum_id] = $forum_mark_time[$forum_id]; + } + + $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark']; + + foreach ($topic_ids as $topic_id) + { + $last_read[$topic_id] = $user_lastmark; + } + } + + return $last_read; +} + +/** +* Get topic tracking info from db (for cookie based tracking only this function is used) +*/ +function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false) +{ + global $config, $user, $request; + + $last_read = array(); + + if (!is_array($topic_ids)) + { + $topic_ids = array($topic_ids); + } + + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + global $db; + + $sql = 'SELECT topic_id, mark_time + FROM ' . TOPICS_TRACK_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND " . $db->sql_in_set('topic_id', $topic_ids); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $last_read[$row['topic_id']] = $row['mark_time']; + } + $db->sql_freeresult($result); + + $topic_ids = array_diff($topic_ids, array_keys($last_read)); + + if (count($topic_ids)) + { + $sql = 'SELECT forum_id, mark_time + FROM ' . FORUMS_TRACK_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND forum_id = $forum_id"; + $result = $db->sql_query($sql); + + $mark_time = array(); + while ($row = $db->sql_fetchrow($result)) + { + $mark_time[$row['forum_id']] = $row['mark_time']; + } + $db->sql_freeresult($result); + + $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark']; + + foreach ($topic_ids as $topic_id) + { + $last_read[$topic_id] = $user_lastmark; + } + } + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + global $tracking_topics; + + if (!isset($tracking_topics) || !count($tracking_topics)) + { + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); + $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); + } + + if (!$user->data['is_registered']) + { + $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0; + } + else + { + $user_lastmark = $user->data['user_lastmark']; + } + + foreach ($topic_ids as $topic_id) + { + $topic_id36 = base_convert($topic_id, 10, 36); + + if (isset($tracking_topics['t'][$topic_id36])) + { + $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate']; + } + } + + $topic_ids = array_diff($topic_ids, array_keys($last_read)); + + if (count($topic_ids)) + { + $mark_time = array(); + + if (isset($tracking_topics['f'][$forum_id])) + { + $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']; + } + + $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark; + + foreach ($topic_ids as $topic_id) + { + $last_read[$topic_id] = $user_lastmark; + } + } + } + + return $last_read; +} + +/** +* Get list of unread topics +* +* @param int $user_id User ID (or false for current user) +* @param string $sql_extra Extra WHERE SQL statement +* @param string $sql_sort ORDER BY SQL sorting statement +* @param string $sql_limit Limits the size of unread topics list, 0 for unlimited query +* @param string $sql_limit_offset Sets the offset of the first row to search, 0 to search from the start +* +* @return array[int][int] Topic ids as keys, mark_time of topic as value +*/ +function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0) +{ + global $config, $db, $user, $request; + global $phpbb_dispatcher; + + $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id; + + // Data array we're going to return + $unread_topics = array(); + + if (empty($sql_sort)) + { + $sql_sort = 'ORDER BY t.topic_last_post_time DESC, t.topic_last_post_id DESC'; + } + + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + // Get list of the unread topics + $last_mark = (int) $user->data['user_lastmark']; + + $sql_array = array( + 'SELECT' => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time', + + 'FROM' => array(TOPICS_TABLE => 't'), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'), + 'ON' => "tt.user_id = $user_id AND t.topic_id = tt.topic_id", + ), + array( + 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'), + 'ON' => "ft.user_id = $user_id AND t.forum_id = ft.forum_id", + ), + ), + + 'WHERE' => " + t.topic_last_post_time > $last_mark AND + ( + (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR + (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR + (tt.mark_time IS NULL AND ft.mark_time IS NULL) + ) + $sql_extra + $sql_sort", + ); + + /** + * Change SQL query for fetching unread topics data + * + * @event core.get_unread_topics_modify_sql + * @var array sql_array Fully assembled SQL query with keys SELECT, FROM, LEFT_JOIN, WHERE + * @var int last_mark User's last_mark time + * @var string sql_extra Extra WHERE SQL statement + * @var string sql_sort ORDER BY SQL sorting statement + * @since 3.1.4-RC1 + */ + $vars = array( + 'sql_array', + 'last_mark', + 'sql_extra', + 'sql_sort', + ); + extract($phpbb_dispatcher->trigger_event('core.get_unread_topics_modify_sql', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_id = (int) $row['topic_id']; + $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark); + } + $db->sql_freeresult($result); + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + global $tracking_topics; + + if (empty($tracking_topics)) + { + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', false, \phpbb\request\request_interface::COOKIE); + $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); + } + + if (!$user->data['is_registered']) + { + $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0; + } + else + { + $user_lastmark = (int) $user->data['user_lastmark']; + } + + $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time + FROM ' . TOPICS_TABLE . ' t + WHERE t.topic_last_post_time > ' . $user_lastmark . " + $sql_extra + $sql_sort"; + $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_id = (int) $row['forum_id']; + $topic_id = (int) $row['topic_id']; + $topic_id36 = base_convert($topic_id, 10, 36); + + if (isset($tracking_topics['t'][$topic_id36])) + { + $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate']; + + if ($row['topic_last_post_time'] > $last_read) + { + $unread_topics[$topic_id] = $last_read; + } + } + else if (isset($tracking_topics['f'][$forum_id])) + { + $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']; + + if ($row['topic_last_post_time'] > $mark_time) + { + $unread_topics[$topic_id] = $mark_time; + } + } + else + { + $unread_topics[$topic_id] = $user_lastmark; + } + } + $db->sql_freeresult($result); + } + + return $unread_topics; +} + +/** +* Check for read forums and update topic tracking info accordingly +* +* @param int $forum_id the forum id to check +* @param int $forum_last_post_time the forums last post time +* @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled +* @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time +* +* @return true if complete forum got marked read, else false. +*/ +function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false) +{ + global $db, $tracking_topics, $user, $config, $request, $phpbb_container; + + // Determine the users last forum mark time if not given. + if ($mark_time_forum === false) + { + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark']; + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); + $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); + + if (!$user->data['is_registered']) + { + $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0; + } + + $mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark']; + } + } + + // Handle update of unapproved topics info. + // Only update for moderators having m_approve permission for the forum. + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + // Check the forum for any left unread topics. + // If there are none, we mark the forum as read. + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + if ($mark_time_forum >= $forum_last_post_time) + { + // We do not need to mark read, this happened before. Therefore setting this to true + $row = true; + } + else + { + $sql = 'SELECT t.forum_id + FROM ' . TOPICS_TABLE . ' t + LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt + ON (tt.topic_id = t.topic_id + AND tt.user_id = ' . $user->data['user_id'] . ') + WHERE t.forum_id = ' . $forum_id . ' + AND t.topic_last_post_time > ' . $mark_time_forum . ' + AND t.topic_moved_id = 0 + AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.') . ' + AND (tt.topic_id IS NULL + OR tt.mark_time < t.topic_last_post_time)'; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + // Get information from cookie + if (!isset($tracking_topics['tf'][$forum_id])) + { + // We do not need to mark read, this happened before. Therefore setting this to true + $row = true; + } + else + { + $sql = 'SELECT t.topic_id + FROM ' . TOPICS_TABLE . ' t + WHERE t.forum_id = ' . $forum_id . ' + AND t.topic_last_post_time > ' . $mark_time_forum . ' + AND t.topic_moved_id = 0 + AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.'); + $result = $db->sql_query($sql); + + $check_forum = $tracking_topics['tf'][$forum_id]; + $unread = false; + + while ($row = $db->sql_fetchrow($result)) + { + if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)])) + { + $unread = true; + break; + } + } + $db->sql_freeresult($result); + + $row = $unread; + } + } + else + { + $row = true; + } + + if (!$row) + { + markread('topics', $forum_id); + return true; + } + + return false; +} + +/** +* Transform an array into a serialized format +*/ +function tracking_serialize($input) +{ + $out = ''; + foreach ($input as $key => $value) + { + if (is_array($value)) + { + $out .= $key . ':(' . tracking_serialize($value) . ');'; + } + else + { + $out .= $key . ':' . $value . ';'; + } + } + return $out; +} + +/** +* Transform a serialized array into an actual array +*/ +function tracking_unserialize($string, $max_depth = 3) +{ + $n = strlen($string); + if ($n > 10010) + { + die('Invalid data supplied'); + } + $data = $stack = array(); + $key = ''; + $mode = 0; + $level = &$data; + for ($i = 0; $i < $n; ++$i) + { + switch ($mode) + { + case 0: + switch ($string[$i]) + { + case ':': + $level[$key] = 0; + $mode = 1; + break; + case ')': + unset($level); + $level = array_pop($stack); + $mode = 3; + break; + default: + $key .= $string[$i]; + } + break; + + case 1: + switch ($string[$i]) + { + case '(': + if (count($stack) >= $max_depth) + { + die('Invalid data supplied'); + } + $stack[] = &$level; + $level[$key] = array(); + $level = &$level[$key]; + $key = ''; + $mode = 0; + break; + default: + $level[$key] = $string[$i]; + $mode = 2; + break; + } + break; + + case 2: + switch ($string[$i]) + { + case ')': + unset($level); + $level = array_pop($stack); + $mode = 3; + break; + case ';': + $key = ''; + $mode = 0; + break; + default: + $level[$key] .= $string[$i]; + break; + } + break; + + case 3: + switch ($string[$i]) + { + case ')': + unset($level); + $level = array_pop($stack); + break; + case ';': + $key = ''; + $mode = 0; + break; + default: + die('Invalid data supplied'); + break; + } + break; + } + } + + if (count($stack) != 0 || ($mode != 0 && $mode != 3)) + { + die('Invalid data supplied'); + } + + return $level; +} + +// Server functions (building urls, redirecting...) + +/** +* Append session id to url. +* This function supports hooks. +* +* @param string $url The url the session id needs to be appended to (can have params) +* @param mixed $params String or array of additional url parameters +* @param bool $is_amp Is url using & (true) or & (false) +* @param string $session_id Possibility to use a custom session id instead of the global one +* @param bool $is_route Is url generated by a route. +* +* @return string The corrected url. +* +* Examples: +* +* append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&f=2"); +* append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2'); +* append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false); +* append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2)); +* +* +*/ +function append_sid($url, $params = false, $is_amp = true, $session_id = false, $is_route = false) +{ + global $_SID, $_EXTRA_URL, $phpbb_hook, $phpbb_path_helper; + global $phpbb_dispatcher; + + if ($params === '' || (is_array($params) && empty($params))) + { + // Do not append the ? if the param-list is empty anyway. + $params = false; + } + + // Update the root path with the correct relative web path + if (!$is_route && $phpbb_path_helper instanceof \phpbb\path_helper) + { + $url = $phpbb_path_helper->update_web_root_path($url); + } + + $append_sid_overwrite = false; + + /** + * This event can either supplement or override the append_sid() function + * + * To override this function, the event must set $append_sid_overwrite to + * the new URL value, which will be returned following the event + * + * @event core.append_sid + * @var string url The url the session id needs + * to be appended to (can have + * params) + * @var mixed params String or array of additional + * url parameters + * @var bool is_amp Is url using & (true) or + * & (false) + * @var bool|string session_id Possibility to use a custom + * session id (string) instead of + * the global one (false) + * @var bool|string append_sid_overwrite Overwrite function (string + * URL) or not (false) + * @var bool is_route Is url generated by a route. + * @since 3.1.0-a1 + */ + $vars = array('url', 'params', 'is_amp', 'session_id', 'append_sid_overwrite', 'is_route'); + extract($phpbb_dispatcher->trigger_event('core.append_sid', compact($vars))); + + if ($append_sid_overwrite) + { + return $append_sid_overwrite; + } + + // The following hook remains for backwards compatibility, though use of + // the event above is preferred. + // Developers using the hook function need to globalise the $_SID and $_EXTRA_URL on their own and also handle it appropriately. + // They could mimic most of what is within this function + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__, $url, $params, $is_amp, $session_id)) + { + if ($phpbb_hook->hook_return(__FUNCTION__)) + { + return $phpbb_hook->hook_return_result(__FUNCTION__); + } + } + + $params_is_array = is_array($params); + + // Get anchor + $anchor = ''; + if (strpos($url, '#') !== false) + { + list($url, $anchor) = explode('#', $url, 2); + $anchor = '#' . $anchor; + } + else if (!$params_is_array && strpos($params, '#') !== false) + { + list($params, $anchor) = explode('#', $params, 2); + $anchor = '#' . $anchor; + } + + // Handle really simple cases quickly + if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor) + { + if ($params === false) + { + return $url; + } + + $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&' : '&'); + return $url . ($params !== false ? $url_delim. $params : ''); + } + + // Assign sid if session id is not specified + if ($session_id === false) + { + $session_id = $_SID; + } + + $amp_delim = ($is_amp) ? '&' : '&'; + $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim; + + // Appending custom url parameter? + $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : ''; + + // Use the short variant if possible ;) + if ($params === false) + { + // Append session id + if (!$session_id) + { + return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor; + } + else + { + return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor; + } + } + + // Build string if parameters are specified as array + if (is_array($params)) + { + $output = array(); + + foreach ($params as $key => $item) + { + if ($item === NULL) + { + continue; + } + + if ($key == '#') + { + $anchor = '#' . $item; + continue; + } + + $output[] = $key . '=' . $item; + } + + $params = implode($amp_delim, $output); + } + + // Append session id and parameters (even if they are empty) + // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter + return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor; +} + +/** +* Generate board url (example: http://www.example.com/phpBB) +* +* @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com) +* +* @return string the generated board url +*/ +function generate_board_url($without_script_path = false) +{ + global $config, $user, $request, $symfony_request; + + $server_name = $user->host; + + // Forcing server vars is the only way to specify/override the protocol + if ($config['force_server_vars'] || !$server_name) + { + $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://'); + $server_name = $config['server_name']; + $server_port = (int) $config['server_port']; + $script_path = $config['script_path']; + + $url = $server_protocol . $server_name; + $cookie_secure = $config['cookie_secure']; + } + else + { + $server_port = (int) $symfony_request->getPort(); + + $forwarded_proto = $request->server('HTTP_X_FORWARDED_PROTO'); + + if (!empty($forwarded_proto) && $forwarded_proto === 'https') + { + $server_port = 443; + } + // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection + $cookie_secure = $request->is_secure() ? 1 : 0; + $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name; + + $script_path = $user->page['root_script_path']; + } + + if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80))) + { + // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true) + if (strpos($server_name, ':') === false) + { + $url .= ':' . $server_port; + } + } + + if (!$without_script_path) + { + $url .= $script_path; + } + + // Strip / from the end + if (substr($url, -1, 1) == '/') + { + $url = substr($url, 0, -1); + } + + return $url; +} + +/** +* Redirects the user to another page then exits the script nicely +* This function is intended for urls within the board. It's not meant to redirect to cross-domains. +* +* @param string $url The url to redirect to +* @param bool $return If true, do not redirect but return the sanitized URL. Default is no return. +* @param bool $disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false. +*/ +function redirect($url, $return = false, $disable_cd_check = false) +{ + global $user, $phpbb_path_helper, $phpbb_dispatcher; + + if (!$user->is_setup()) + { + $user->add_lang('common'); + } + + // Make sure no &'s are in, this will break the redirect + $url = str_replace('&', '&', $url); + + // Determine which type of redirect we need to handle... + $url_parts = @parse_url($url); + + if ($url_parts === false) + { + // Malformed url + trigger_error('INSECURE_REDIRECT', E_USER_WARNING); + } + else if (!empty($url_parts['scheme']) && !empty($url_parts['host'])) + { + // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work) + if (!$disable_cd_check && $url_parts['host'] !== $user->host) + { + trigger_error('INSECURE_REDIRECT', E_USER_WARNING); + } + } + else if ($url[0] == '/') + { + // Absolute uri, prepend direct url... + $url = generate_board_url(true) . $url; + } + else + { + // Relative uri + $pathinfo = pathinfo($url); + + // Is the uri pointing to the current directory? + if ($pathinfo['dirname'] == '.') + { + $url = str_replace('./', '', $url); + + // Strip / from the beginning + if ($url && substr($url, 0, 1) == '/') + { + $url = substr($url, 1); + } + } + + $url = $phpbb_path_helper->remove_web_root_path($url); + + if ($user->page['page_dir']) + { + $url = $user->page['page_dir'] . '/' . $url; + } + + $url = generate_board_url() . '/' . $url; + } + + // Clean URL and check if we go outside the forum directory + $url = $phpbb_path_helper->clean_url($url); + + if (!$disable_cd_check && strpos($url, generate_board_url(true) . '/') !== 0) + { + trigger_error('INSECURE_REDIRECT', E_USER_WARNING); + } + + // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2 + if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false) + { + trigger_error('INSECURE_REDIRECT', E_USER_WARNING); + } + + // Now, also check the protocol and for a valid url the last time... + $allowed_protocols = array('http', 'https', 'ftp', 'ftps'); + $url_parts = parse_url($url); + + if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols)) + { + trigger_error('INSECURE_REDIRECT', E_USER_WARNING); + } + + /** + * Execute code and/or overwrite redirect() + * + * @event core.functions.redirect + * @var string url The url + * @var bool return If true, do not redirect but return the sanitized URL. + * @var bool disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. + * @since 3.1.0-RC3 + */ + $vars = array('url', 'return', 'disable_cd_check'); + extract($phpbb_dispatcher->trigger_event('core.functions.redirect', compact($vars))); + + if ($return) + { + return $url; + } + else + { + garbage_collection(); + } + + // Redirect via an HTML form for PITA webservers + if (@preg_match('#WebSTAR|Xitami#', getenv('SERVER_SOFTWARE'))) + { + header('Refresh: 0; URL=' . $url); + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo '' . $user->lang['REDIRECT'] . ''; + echo ''; + echo ''; + echo '
' . sprintf($user->lang['URL_REDIRECT'], '', '') . '
'; + echo ''; + echo ''; + + exit; + } + + // Behave as per HTTP/1.1 spec for others + header('Location: ' . $url); + exit; +} + +/** +* Re-Apply session id after page reloads +*/ +function reapply_sid($url, $is_route = false) +{ + global $phpEx, $phpbb_root_path; + + if ($url === "index.$phpEx") + { + return append_sid("index.$phpEx"); + } + else if ($url === "{$phpbb_root_path}index.$phpEx") + { + return append_sid("{$phpbb_root_path}index.$phpEx"); + } + + // Remove previously added sid + if (strpos($url, 'sid=') !== false) + { + // All kind of links + $url = preg_replace('/(\?)?(&|&)?sid=[a-z0-9]+/', '', $url); + // if the sid was the first param, make the old second as first ones + $url = preg_replace("/$phpEx(&|&)+?/", "$phpEx?", $url); + } + + return append_sid($url, false, true, false, $is_route); +} + +/** +* Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url +*/ +function build_url($strip_vars = false) +{ + global $config, $user, $phpbb_path_helper; + + $page = $phpbb_path_helper->get_valid_page($user->page['page'], $config['enable_mod_rewrite']); + + // Append SID + $redirect = append_sid($page, false, false); + + if ($strip_vars !== false) + { + $redirect = $phpbb_path_helper->strip_url_params($redirect, $strip_vars, false); + } + else + { + $redirect = str_replace('&', '&', $redirect); + } + + return $redirect . ((strpos($redirect, '?') === false) ? '?' : ''); +} + +/** +* Meta refresh assignment +* Adds META template variable with meta http tag. +* +* @param int $time Time in seconds for meta refresh tag +* @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned +* @param bool $disable_cd_check If true, meta_refresh() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false. +*/ +function meta_refresh($time, $url, $disable_cd_check = false) +{ + global $template, $refresh_data, $request; + + $url = redirect($url, true, $disable_cd_check); + if ($request->is_ajax()) + { + $refresh_data = array( + 'time' => $time, + 'url' => $url, + ); + } + else + { + // For XHTML compatibility we change back & to & + $url = str_replace('&', '&', $url); + + $current_hour = date('H'); + $day_state = $current_hour >= 6 && $current_hour <= 18 ? 'jour' : 'nuit'; + $template->assign_vars(array( + 'META' => '') + ); + } + + return $url; +} + +/** +* Outputs correct status line header. +* +* Depending on php sapi one of the two following forms is used: +* +* Status: 404 Not Found +* +* HTTP/1.x 404 Not Found +* +* HTTP version is taken from HTTP_VERSION environment variable, +* and defaults to 1.0. +* +* Sample usage: +* +* send_status_line(404, 'Not Found'); +* +* @param int $code HTTP status code +* @param string $message Message for the status code +* @return null +*/ +function send_status_line($code, $message) +{ + if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi') + { + // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though + header("Status: $code $message", true, $code); + } + else + { + $version = phpbb_request_http_version(); + header("$version $code $message", true, $code); + } +} + +/** +* Returns the HTTP version used in the current request. +* +* Handles the case of being called before $request is present, +* in which case it falls back to the $_SERVER superglobal. +* +* @return string HTTP version +*/ +function phpbb_request_http_version() +{ + global $request; + + $version = ''; + if ($request && $request->server('SERVER_PROTOCOL')) + { + $version = $request->server('SERVER_PROTOCOL'); + } + else if (isset($_SERVER['SERVER_PROTOCOL'])) + { + $version = $_SERVER['SERVER_PROTOCOL']; + } + + if (!empty($version) && is_string($version) && preg_match('#^HTTP/[0-9]\.[0-9]$#', $version)) + { + return $version; + } + + return 'HTTP/1.0'; +} + +//Form validation + + +/** +* Add a secret hash for use in links/GET requests +* @param string $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply +* @return string the hash + +*/ +function generate_link_hash($link_name) +{ + global $user; + + if (!isset($user->data["hash_$link_name"])) + { + $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8); + } + + return $user->data["hash_$link_name"]; +} + + +/** +* checks a link hash - for GET requests +* @param string $token the submitted token +* @param string $link_name The name of the link +* @return boolean true if all is fine +*/ +function check_link_hash($token, $link_name) +{ + return $token === generate_link_hash($link_name); +} + +/** +* Add a secret token to the form (requires the S_FORM_TOKEN template variable) +* @param string $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply +* @param string $template_variable_suffix A string that is appended to the name of the template variable to which the form elements are assigned +*/ +function add_form_key($form_name, $template_variable_suffix = '') +{ + global $config, $template, $user, $phpbb_dispatcher; + + $now = time(); + $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : ''; + $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid); + + $s_fields = build_hidden_fields(array( + 'creation_time' => $now, + 'form_token' => $token, + )); + + /** + * Perform additional actions on creation of the form token + * + * @event core.add_form_key + * @var string form_name The form name + * @var int now Current time timestamp + * @var string s_fields Generated hidden fields + * @var string token Form token + * @var string token_sid User session ID + * @var string template_variable_suffix The string that is appended to template variable name + * + * @since 3.1.0-RC3 + * @changed 3.1.11-RC1 Added template_variable_suffix + */ + $vars = array( + 'form_name', + 'now', + 's_fields', + 'token', + 'token_sid', + 'template_variable_suffix', + ); + extract($phpbb_dispatcher->trigger_event('core.add_form_key', compact($vars))); + + $template->assign_var('S_FORM_TOKEN' . $template_variable_suffix, $s_fields); +} + +/** + * Check the form key. Required for all altering actions not secured by confirm_box + * + * @param string $form_name The name of the form; has to match the name used + * in add_form_key, otherwise no restrictions apply + * @param int $timespan The maximum acceptable age for a submitted form + * in seconds. Defaults to the config setting. + * @return bool True, if the form key was valid, false otherwise + */ +function check_form_key($form_name, $timespan = false) +{ + global $config, $request, $user; + + if ($timespan === false) + { + // we enforce a minimum value of half a minute here. + $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']); + } + + if ($request->is_set_post('creation_time') && $request->is_set_post('form_token')) + { + $creation_time = abs($request->variable('creation_time', 0)); + $token = $request->variable('form_token', ''); + + $diff = time() - $creation_time; + + // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)... + if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1)) + { + $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : ''; + $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid); + + if ($key === $token) + { + return true; + } + } + } + + return false; +} + +// Message/Login boxes + +/** +* Build Confirm box +* @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box +* @param string|array $title Title/Message used for confirm box. +* message text is _CONFIRM appended to title. +* If title cannot be found in user->lang a default one is displayed +* If title_CONFIRM cannot be found in user->lang the text given is used. +* If title is an array, the first array value is used as explained per above, +* all other array values are sent as parameters to the language function. +* @param string $hidden Hidden variables +* @param string $html_body Template used for confirm box +* @param string $u_action Custom form action +* +* @return bool True if confirmation was successful, false if not +*/ +function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '') +{ + global $user, $template, $db, $request; + global $config, $language, $phpbb_path_helper; + + if (isset($_POST['cancel'])) + { + return false; + } + + $confirm = ($language->lang('YES') === $request->variable('confirm', '', true, \phpbb\request\request_interface::POST)); + + if ($check && $confirm) + { + $user_id = $request->variable('confirm_uid', 0); + $session_id = $request->variable('sess', ''); + $confirm_key = $request->variable('confirm_key', ''); + + if ($user_id != $user->data['user_id'] || $session_id != $user->session_id || !$confirm_key || !$user->data['user_last_confirm_key'] || $confirm_key != $user->data['user_last_confirm_key']) + { + return false; + } + + // Reset user_last_confirm_key + $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '' + WHERE user_id = " . $user->data['user_id']; + $db->sql_query($sql); + + return true; + } + else if ($check) + { + return false; + } + + $s_hidden_fields = build_hidden_fields(array( + 'confirm_uid' => $user->data['user_id'], + 'sess' => $user->session_id, + 'sid' => $user->session_id, + )); + + // generate activation key + $confirm_key = gen_rand_string(10); + + // generate language strings + if (is_array($title)) + { + $key = array_shift($title); + $count = array_shift($title); + $confirm_title = $language->is_set($key) ? $language->lang($key, $count, $title) : $language->lang('CONFIRM'); + $confirm_text = $language->is_set($key . '_CONFIRM') ? $language->lang($key . '_CONFIRM', $count, $title) : $key; + } + else + { + $confirm_title = $language->is_set($title) ? $language->lang($title) : $language->lang('CONFIRM'); + $confirm_text = $language->is_set($title . '_CONFIRM') ? $language->lang($title . '_CONFIRM') : $title; + } + + if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) + { + adm_page_header($confirm_title); + } + else + { + page_header($confirm_title); + } + + $template->set_filenames(array( + 'body' => $html_body) + ); + + // If activation key already exist, we better do not re-use the key (something very strange is going on...) + if ($request->variable('confirm_key', '')) + { + // This should not occur, therefore we cancel the operation to safe the user + return false; + } + + // re-add sid / transform & to & for user->page (user->page is always using &) + $use_page = ($u_action) ? $u_action : str_replace('&', '&', $user->page['page']); + $u_action = reapply_sid($phpbb_path_helper->get_valid_page($use_page, $config['enable_mod_rewrite'])); + $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&') . 'confirm_key=' . $confirm_key; + + $template->assign_vars(array( + 'MESSAGE_TITLE' => $confirm_title, + 'MESSAGE_TEXT' => $confirm_text, + + 'YES_VALUE' => $language->lang('YES'), + 'S_CONFIRM_ACTION' => $u_action, + 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields, + 'S_AJAX_REQUEST' => $request->is_ajax(), + )); + + $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "' + WHERE user_id = " . $user->data['user_id']; + $db->sql_query($sql); + + if ($request->is_ajax()) + { + $u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id; + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_BODY' => $template->assign_display('body'), + 'MESSAGE_TITLE' => $confirm_title, + 'MESSAGE_TEXT' => $confirm_text, + + 'YES_VALUE' => $language->lang('YES'), + 'S_CONFIRM_ACTION' => str_replace('&', '&', $u_action), //inefficient, rewrite whole function + 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields + )); + } + + if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) + { + adm_page_footer(); + } + else + { + page_footer(); + } + + exit; // unreachable, page_footer() above will call exit() +} + +/** +* Generate login box or verify password +*/ +function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true) +{ + global $user, $template, $auth, $phpEx, $phpbb_root_path, $config; + global $request, $phpbb_container, $phpbb_dispatcher, $phpbb_log; + + $err = ''; + $form_name = 'login'; + + // Make sure user->setup() has been called + if (!$user->is_setup()) + { + $user->setup(); + } + + /** + * This event allows an extension to modify the login process + * + * @event core.login_box_before + * @var string redirect Redirect string + * @var string l_explain Explain language string + * @var string l_success Success language string + * @var bool admin Is admin? + * @var bool s_display Display full login form? + * @var string err Error string + * @since 3.1.9-RC1 + */ + $vars = array('redirect', 'l_explain', 'l_success', 'admin', 's_display', 'err'); + extract($phpbb_dispatcher->trigger_event('core.login_box_before', compact($vars))); + + // Print out error if user tries to authenticate as an administrator without having the privileges... + if ($admin && !$auth->acl_get('a_')) + { + // Not authd + // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions + if ($user->data['is_registered']) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL'); + } + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_ADMIN'); + } + + if (empty($err) && ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external'))) + { + // Get credential + if ($admin) + { + $credential = $request->variable('credential', ''); + + if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32) + { + if ($user->data['is_registered']) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL'); + } + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_ADMIN'); + } + + $password = $request->untrimmed_variable('password_' . $credential, '', true); + } + else + { + $password = $request->untrimmed_variable('password', '', true); + } + + $username = $request->variable('username', '', true); + $autologin = $request->is_set_post('autologin'); + $viewonline = (int) !$request->is_set_post('viewonline'); + $admin = ($admin) ? 1 : 0; + $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline; + + // Check if the supplied username is equal to the one stored within the database if re-authenticating + if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username'])) + { + // We log the attempt to use a different username... + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL'); + + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_ADMIN_USER_DIFFER'); + } + + // Check form key + if ($password && !check_form_key($form_name)) + { + $result = array( + 'status' => false, + 'error_msg' => 'FORM_INVALID', + ); + } + else + { + // If authentication is successful we redirect user to previous page + $result = $auth->login($username, $password, $autologin, $viewonline, $admin); + } + + // If admin authentication and login, we will log if it was a success or not... + // We also break the operation on the first non-success login - it could be argued that the user already knows + if ($admin) + { + if ($result['status'] == LOGIN_SUCCESS) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_SUCCESS'); + } + else + { + // Only log the failed attempt if a real user tried to. + // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions + if ($user->data['is_registered']) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL'); + } + } + } + + // The result parameter is always an array, holding the relevant information... + if ($result['status'] == LOGIN_SUCCESS) + { + $redirect = $request->variable('redirect', "{$phpbb_root_path}index.$phpEx"); + + /** + * This event allows an extension to modify the redirection when a user successfully logs in + * + * @event core.login_box_redirect + * @var string redirect Redirect string + * @var bool admin Is admin? + * @var array result Result from auth provider + * @since 3.1.0-RC5 + * @changed 3.1.9-RC1 Removed undefined return variable + * @changed 3.2.4-RC1 Added result + */ + $vars = array('redirect', 'admin', 'result'); + extract($phpbb_dispatcher->trigger_event('core.login_box_redirect', compact($vars))); + + // append/replace SID (may change during the session for AOL users) + $redirect = reapply_sid($redirect); + + // Special case... the user is effectively banned, but we allow founders to login + if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER) + { + return; + } + + redirect($redirect); + } + + // Something failed, determine what... + if ($result['status'] == LOGIN_BREAK) + { + trigger_error($result['error_msg']); + } + + // Special cases... determine + switch ($result['status']) + { + case LOGIN_ERROR_PASSWORD_CONVERT: + $err = sprintf( + $user->lang[$result['error_msg']], + ($config['email_enable']) ? '' : '', + ($config['email_enable']) ? '' : '', + '', + '' + ); + break; + + case LOGIN_ERROR_ATTEMPTS: + + $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_LOGIN); + // $captcha->reset(); + + $template->assign_vars(array( + 'CAPTCHA_TEMPLATE' => $captcha->get_template(), + )); + // no break; + + // Username, password, etc... + default: + $err = $user->lang[$result['error_msg']]; + + // Assign admin contact to some error messages + if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD') + { + $err = sprintf($user->lang[$result['error_msg']], '', ''); + } + + break; + } + + /** + * This event allows an extension to process when a user fails a login attempt + * + * @event core.login_box_failed + * @var array result Login result data + * @var string username User name used to login + * @var string password Password used to login + * @var string err Error message + * @since 3.1.3-RC1 + */ + $vars = array('result', 'username', 'password', 'err'); + extract($phpbb_dispatcher->trigger_event('core.login_box_failed', compact($vars))); + } + + // Assign credential for username/password pair + $credential = ($admin) ? md5(unique_id()) : false; + + $s_hidden_fields = array( + 'sid' => $user->session_id, + ); + + if ($redirect) + { + $s_hidden_fields['redirect'] = $redirect; + } + + if ($admin) + { + $s_hidden_fields['credential'] = $credential; + } + + /* @var $provider_collection \phpbb\auth\provider_collection */ + $provider_collection = $phpbb_container->get('auth.provider_collection'); + $auth_provider = $provider_collection->get_provider(); + + $auth_provider_data = $auth_provider->get_login_data(); + if ($auth_provider_data) + { + if (isset($auth_provider_data['VARS'])) + { + $template->assign_vars($auth_provider_data['VARS']); + } + + if (isset($auth_provider_data['BLOCK_VAR_NAME'])) + { + foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars) + { + $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars); + } + } + + $template->assign_vars(array( + 'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'], + )); + } + + // Add form token for login box + add_form_key($form_name, '_LOGIN'); + + $s_hidden_fields = build_hidden_fields($s_hidden_fields); + + $login_box_template_data = array( + 'LOGIN_ERROR' => $err, + 'LOGIN_EXPLAIN' => $l_explain, + + 'U_SEND_PASSWORD' => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '', + 'U_RESEND_ACTIVATION' => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '', + 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'), + 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'), + 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')), + + 'S_DISPLAY_FULL_LOGIN' => ($s_display) ? true : false, + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + + 'S_ADMIN_AUTH' => $admin, + 'USERNAME' => ($admin) ? $user->data['username'] : '', + + 'USERNAME_CREDENTIAL' => 'username', + 'PASSWORD_CREDENTIAL' => ($admin) ? 'password_' . $credential : 'password', + ); + + /** + * Event to add/modify login box template data + * + * @event core.login_box_modify_template_data + * @var int admin Flag whether user is admin + * @var string username User name + * @var int autologin Flag whether autologin is enabled + * @var string redirect Redirect URL + * @var array login_box_template_data Array with the login box template data + * @since 3.2.3-RC2 + */ + $vars = array( + 'admin', + 'username', + 'autologin', + 'redirect', + 'login_box_template_data', + ); + extract($phpbb_dispatcher->trigger_event('core.login_box_modify_template_data', compact($vars))); + + $template->assign_vars($login_box_template_data); + + page_header($user->lang['LOGIN']); + + $template->set_filenames(array( + 'body' => 'login_body.html') + ); + make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx")); + + page_footer(); +} + +/** +* Generate forum login box +*/ +function login_forum_box($forum_data) +{ + global $db, $phpbb_container, $request, $template, $user, $phpbb_dispatcher, $phpbb_root_path, $phpEx; + + $password = $request->variable('password', '', true); + + $sql = 'SELECT forum_id + FROM ' . FORUMS_ACCESS_TABLE . ' + WHERE forum_id = ' . $forum_data['forum_id'] . ' + AND user_id = ' . $user->data['user_id'] . " + AND session_id = '" . $db->sql_escape($user->session_id) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + return true; + } + + if ($password) + { + // Remove expired authorised sessions + $sql = 'SELECT f.session_id + FROM ' . FORUMS_ACCESS_TABLE . ' f + LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id) + WHERE s.session_id IS NULL'; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $sql_in = array(); + do + { + $sql_in[] = (string) $row['session_id']; + } + while ($row = $db->sql_fetchrow($result)); + + // Remove expired sessions + $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . ' + WHERE ' . $db->sql_in_set('session_id', $sql_in); + $db->sql_query($sql); + } + $db->sql_freeresult($result); + + /* @var $passwords_manager \phpbb\passwords\manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + + if ($passwords_manager->check($password, $forum_data['forum_password'])) + { + $sql_ary = array( + 'forum_id' => (int) $forum_data['forum_id'], + 'user_id' => (int) $user->data['user_id'], + 'session_id' => (string) $user->session_id, + ); + + $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + + return true; + } + + $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']); + } + + /** + * Performing additional actions, load additional data on forum login + * + * @event core.login_forum_box + * @var array forum_data Array with forum data + * @var string password Password entered + * @since 3.1.0-RC3 + */ + $vars = array('forum_data', 'password'); + extract($phpbb_dispatcher->trigger_event('core.login_forum_box', compact($vars))); + + page_header($user->lang['LOGIN']); + + // Add form token for login box + add_form_key('login', '_LOGIN'); + + $template->assign_vars(array( + 'FORUM_NAME' => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '', + 'S_LOGIN_ACTION' => build_url(array('f')), + 'S_HIDDEN_FIELDS' => build_hidden_fields(array('f' => $forum_data['forum_id']))) + ); + + $template->set_filenames(array( + 'body' => 'login_forum.html') + ); + + make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_data['forum_id']); + + page_footer(); +} + +// Little helpers + +/** +* Little helper for the build_hidden_fields function +*/ +function _build_hidden_fields($key, $value, $specialchar, $stripslashes) +{ + $hidden_fields = ''; + + if (!is_array($value)) + { + $value = ($stripslashes) ? stripslashes($value) : $value; + $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value; + + $hidden_fields .= '' . "\n"; + } + else + { + foreach ($value as $_key => $_value) + { + $_key = ($stripslashes) ? stripslashes($_key) : $_key; + $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key; + + $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes); + } + } + + return $hidden_fields; +} + +/** +* Build simple hidden fields from array +* +* @param array $field_ary an array of values to build the hidden field from +* @param bool $specialchar if true, keys and values get specialchared +* @param bool $stripslashes if true, keys and values get stripslashed +* +* @return string the hidden fields +*/ +function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false) +{ + $s_hidden_fields = ''; + + foreach ($field_ary as $name => $vars) + { + $name = ($stripslashes) ? stripslashes($name) : $name; + $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name; + + $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes); + } + + return $s_hidden_fields; +} + +/** +* Parse cfg file +*/ +function parse_cfg_file($filename, $lines = false) +{ + $parsed_items = array(); + + if ($lines === false) + { + $lines = file($filename); + } + + foreach ($lines as $line) + { + $line = trim($line); + + if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false) + { + continue; + } + + // Determine first occurrence, since in values the equal sign is allowed + $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos)))); + $value = trim(substr($line, $delim_pos + 1)); + + if (in_array($value, array('off', 'false', '0'))) + { + $value = false; + } + else if (in_array($value, array('on', 'true', '1'))) + { + $value = true; + } + else if (!trim($value)) + { + $value = ''; + } + else if (($value[0] == "'" && $value[strlen($value) - 1] == "'") || ($value[0] == '"' && $value[strlen($value) - 1] == '"')) + { + $value = htmlspecialchars(substr($value, 1, strlen($value)-2)); + } + else + { + $value = htmlspecialchars($value); + } + + $parsed_items[$key] = $value; + } + + if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name']) + { + unset($parsed_items['parent']); + } + + return $parsed_items; +} + +/** +* Return a nicely formatted backtrace. +* +* Turns the array returned by debug_backtrace() into HTML markup. +* Also filters out absolute paths to phpBB root. +* +* @return string HTML markup +*/ +function get_backtrace() +{ + $output = '
'; + $backtrace = debug_backtrace(); + + // We skip the first one, because it only shows this file/function + unset($backtrace[0]); + + foreach ($backtrace as $trace) + { + // Strip the current directory from path + $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file'])); + $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line']; + + // Only show function arguments for include etc. + // Other parameters may contain sensible information + $argument = ''; + if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once'))) + { + $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0])); + } + + $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class']; + $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type']; + + $output .= '
'; + $output .= 'FILE: ' . $trace['file'] . '
'; + $output .= 'LINE: ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '
'; + + $output .= 'CALL: ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']); + $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')
'; + } + $output .= '
'; + return $output; +} + +/** +* This function returns a regular expression pattern for commonly used expressions +* Use with / as delimiter for email mode and # for url modes +* mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6 +*/ +function get_preg_expression($mode) +{ + switch ($mode) + { + case 'email': + // Regex written by James Watts and Francisco Jose Martin Moreno + // http://fightingforalostcause.net/misc/2006/compare-email-regex.php + return '((?:[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&)+)@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)'; + break; + + case 'bbcode_htm': + return array( + '#.*?#', + '#.*?#', + '#\2#', + '#.*?#', + '#(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*)))*))?(?:[+](?(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+)))*))?)$/'; + break; + } + + return ''; +} + +/** +* Generate regexp for naughty words censoring +* Depends on whether installed PHP version supports unicode properties +* +* @param string $word word template to be replaced +* +* @return string $preg_expr regex to use with word censor +*/ +function get_censor_preg_expression($word) +{ + // Unescape the asterisk to simplify further conversions + $word = str_replace('\*', '*', preg_quote($word, '#')); + + // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes + $word = preg_replace(array('#(?<=[\p{Nd}\p{L}_])\*+(?=[\p{Nd}\p{L}_])#iu', '#^\*+#', '#\*+$#'), array('([\x20]*?|[\p{Nd}\p{L}_-]*?)', '[\p{Nd}\p{L}_-]*?', '[\p{Nd}\p{L}_-]*?'), $word); + + // Generate the final substitution + $preg_expr = '#(? strlen($longest_match)) + { + $longest_match = $match[0]; + $longest_match_offset = $match[1]; + } + } + + $ret = substr_replace($ret, '', $longest_match_offset, strlen($longest_match)); + + if ($longest_match_offset == strlen($ret)) + { + $ret .= ':'; + } + + if ($longest_match_offset == 0) + { + $ret = ':' . $ret; + } + + return $ret; + + default: + return false; + } +} + +/** +* Wrapper for inet_pton() +* +* Converts a human readable IP address to its packed in_addr representation +* inet_pton() is supported by PHP since 5.1.0, since 5.3.0 also on Windows. +* +* @param string $address A human readable IPv4 or IPv6 address. +* +* @return mixed false if address is invalid, +* in_addr representation of the given address otherwise (string) +*/ +function phpbb_inet_pton($address) +{ + $ret = ''; + if (preg_match(get_preg_expression('ipv4'), $address)) + { + foreach (explode('.', $address) as $part) + { + $ret .= ($part <= 0xF ? '0' : '') . dechex($part); + } + + return pack('H*', $ret); + } + + if (preg_match(get_preg_expression('ipv6'), $address)) + { + $parts = explode(':', $address); + $missing_parts = 8 - count($parts) + 1; + + if (substr($address, 0, 2) === '::') + { + ++$missing_parts; + } + + if (substr($address, -2) === '::') + { + ++$missing_parts; + } + + $embedded_ipv4 = false; + $last_part = end($parts); + + if (preg_match(get_preg_expression('ipv4'), $last_part)) + { + $parts[count($parts) - 1] = ''; + $last_part = phpbb_inet_pton($last_part); + $embedded_ipv4 = true; + --$missing_parts; + } + + foreach ($parts as $i => $part) + { + if (strlen($part)) + { + $ret .= str_pad($part, 4, '0', STR_PAD_LEFT); + } + else if ($i && $i < count($parts) - 1) + { + $ret .= str_repeat('0000', $missing_parts); + } + } + + $ret = pack('H*', $ret); + + if ($embedded_ipv4) + { + $ret .= $last_part; + } + + return $ret; + } + + return false; +} + +/** +* Wrapper for php's checkdnsrr function. +* +* @param string $host Fully-Qualified Domain Name +* @param string $type Resource record type to lookup +* Supported types are: MX (default), A, AAAA, NS, TXT, CNAME +* Other types may work or may not work +* +* @return mixed true if entry found, +* false if entry not found, +* null if this function is not supported by this environment +* +* Since null can also be returned, you probably want to compare the result +* with === true or === false, +*/ +function phpbb_checkdnsrr($host, $type = 'MX') +{ + // The dot indicates to search the DNS root (helps those having DNS prefixes on the same domain) + if (substr($host, -1) == '.') + { + $host_fqdn = $host; + $host = substr($host, 0, -1); + } + else + { + $host_fqdn = $host . '.'; + } + // $host has format some.host.example.com + // $host_fqdn has format some.host.example.com. + + // If we're looking for an A record we can use gethostbyname() + if ($type == 'A' && function_exists('gethostbyname')) + { + return (@gethostbyname($host_fqdn) == $host_fqdn) ? false : true; + } + + if (function_exists('checkdnsrr')) + { + return checkdnsrr($host_fqdn, $type); + } + + if (function_exists('dns_get_record')) + { + // dns_get_record() expects an integer as second parameter + // We have to convert the string $type to the corresponding integer constant. + $type_constant = 'DNS_' . $type; + $type_param = (defined($type_constant)) ? constant($type_constant) : DNS_ANY; + + // dns_get_record() might throw E_WARNING and return false for records that do not exist + $resultset = @dns_get_record($host_fqdn, $type_param); + + if (empty($resultset) || !is_array($resultset)) + { + return false; + } + else if ($type_param == DNS_ANY) + { + // $resultset is a non-empty array + return true; + } + + foreach ($resultset as $result) + { + if ( + isset($result['host']) && $result['host'] == $host && + isset($result['type']) && $result['type'] == $type + ) + { + return true; + } + } + + return false; + } + + // If we're on Windows we can still try to call nslookup via exec() as a last resort + if (DIRECTORY_SEPARATOR == '\\' && function_exists('exec')) + { + @exec('nslookup -type=' . escapeshellarg($type) . ' ' . escapeshellarg($host_fqdn), $output); + + // If output is empty, the nslookup failed + if (empty($output)) + { + return NULL; + } + + foreach ($output as $line) + { + $line = trim($line); + + if (empty($line)) + { + continue; + } + + // Squash tabs and multiple whitespaces to a single whitespace. + $line = preg_replace('/\s+/', ' ', $line); + + switch ($type) + { + case 'MX': + if (stripos($line, "$host MX") === 0) + { + return true; + } + break; + + case 'NS': + if (stripos($line, "$host nameserver") === 0) + { + return true; + } + break; + + case 'TXT': + if (stripos($line, "$host text") === 0) + { + return true; + } + break; + + case 'CNAME': + if (stripos($line, "$host canonical name") === 0) + { + return true; + } + break; + + default: + case 'AAAA': + // AAAA records returned by nslookup on Windows XP/2003 have this format. + // Later Windows versions use the A record format below for AAAA records. + if (stripos($line, "$host AAAA IPv6 address") === 0) + { + return true; + } + // No break + + case 'A': + if (!empty($host_matches)) + { + // Second line + if (stripos($line, "Address: ") === 0) + { + return true; + } + else + { + $host_matches = false; + } + } + else if (stripos($line, "Name: $host") === 0) + { + // First line + $host_matches = true; + } + break; + } + } + + return false; + } + + return NULL; +} + +// Handler, header and footer + +/** +* Error and message handler, call with trigger_error if read +*/ +function msg_handler($errno, $msg_text, $errfile, $errline) +{ + global $cache, $db, $auth, $template, $config, $user, $request; + global $phpbb_root_path, $msg_title, $msg_long_text, $phpbb_log; + + // Do not display notices if we suppress them via @ + if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE) + { + return; + } + + // Message handler is stripping text. In case we need it, we are possible to define long text... + if (isset($msg_long_text) && $msg_long_text && !$msg_text) + { + $msg_text = $msg_long_text; + } + + switch ($errno) + { + case E_NOTICE: + case E_WARNING: + + // Check the error reporting level and return if the error level does not match + // If DEBUG is defined the default level is E_ALL + if (($errno & ((defined('DEBUG')) ? E_ALL : error_reporting())) == 0) + { + return; + } + + if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false) + { + $errfile = phpbb_filter_root_path($errfile); + $msg_text = phpbb_filter_root_path($msg_text); + $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice'; + echo '[phpBB Debug] ' . $error_name . ': in file ' . $errfile . ' on line ' . $errline . ': ' . $msg_text . '
' . "\n"; + + // we are writing an image - the user won't see the debug, so let's place it in the log + if (defined('IMAGE_OUTPUT') || defined('IN_CRON')) + { + $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IMAGE_GENERATION_ERROR', false, array($errfile, $errline, $msg_text)); + } + // echo '

BACKTRACE
' . get_backtrace() . '
' . "\n"; + } + + return; + + break; + + case E_USER_ERROR: + + if (!empty($user) && $user->is_setup()) + { + $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text; + $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title); + + $l_return_index = sprintf($user->lang['RETURN_INDEX'], '', ''); + $l_notify = ''; + + if (!empty($config['board_contact'])) + { + $l_notify = '

' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '

'; + } + } + else + { + $msg_title = 'General Error'; + $l_return_index = 'Return to index page'; + $l_notify = ''; + + if (!empty($config['board_contact'])) + { + $l_notify = '

Please notify the board administrator or webmaster: ' . $config['board_contact'] . '

'; + } + } + + $log_text = $msg_text; + $backtrace = get_backtrace(); + if ($backtrace) + { + $log_text .= '

BACKTRACE
' . $backtrace; + } + + if (defined('IN_INSTALL') || defined('DEBUG') || isset($auth) && $auth->acl_get('a_')) + { + $msg_text = $log_text; + + // If this is defined there already was some output + // So let's not break it + if (defined('IN_DB_UPDATE')) + { + echo '
' . $msg_text . '
'; + + $db->sql_return_on_error(true); + phpbb_end_update($cache, $config); + } + } + + if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db)) + { + // let's avoid loops + $db->sql_return_on_error(true); + $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_GENERAL_ERROR', false, array($msg_title, $log_text)); + $db->sql_return_on_error(false); + } + + // Do not send 200 OK, but service unavailable on errors + send_status_line(503, 'Service Unavailable'); + + garbage_collection(); + + // Try to not call the adm page data... + + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo '' . $msg_title . ''; + echo ''; + echo ''; + echo ''; + echo '
'; + echo ' '; + echo '
'; + echo '
'; + echo '
'; + echo '

' . $msg_title . '

'; + + echo '
' . $msg_text . '
'; + + echo $l_notify; + + echo '
'; + echo '
'; + echo '
'; + echo ' '; + echo '
'; + echo ''; + echo ''; + + exit_handler(); + + // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here. + exit; + break; + + case E_USER_WARNING: + case E_USER_NOTICE: + + define('IN_ERROR_HANDLER', true); + + if (empty($user->data)) + { + $user->session_begin(); + } + + // We re-init the auth array to get correct results on login/logout + $auth->acl($user->data); + + if (!$user->is_setup()) + { + $user->setup(); + } + + if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER') + { + send_status_line(404, 'Not Found'); + } + + $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text; + $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title); + + if (!defined('HEADER_INC')) + { + if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) + { + adm_page_header($msg_title); + } + else + { + page_header($msg_title); + } + } + + $template->set_filenames(array( + 'body' => 'message_body.html') + ); + + $template->assign_vars(array( + 'MESSAGE_TITLE' => $msg_title, + 'MESSAGE_TEXT' => $msg_text, + 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false, + 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false) + ); + + if ($request->is_ajax()) + { + global $refresh_data; + + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $msg_title, + 'MESSAGE_TEXT' => $msg_text, + 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false, + 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false, + 'REFRESH_DATA' => (!empty($refresh_data)) ? $refresh_data : null + )); + } + + // We do not want the cron script to be called on error messages + define('IN_CRON', true); + + if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) + { + adm_page_footer(); + } + else + { + page_footer(); + } + + exit_handler(); + break; + + // PHP4 compatibility + case E_DEPRECATED: + return true; + break; + } + + // If we notice an error not handled here we pass this back to PHP by returning false + // This may not work for all php versions + return false; +} + +/** +* Removes absolute path to phpBB root directory from error messages +* and converts backslashes to forward slashes. +* +* @param string $errfile Absolute file path +* (e.g. /var/www/phpbb3/phpBB/includes/functions.php) +* Please note that if $errfile is outside of the phpBB root, +* the root path will not be found and can not be filtered. +* @return string Relative file path +* (e.g. /includes/functions.php) +*/ +function phpbb_filter_root_path($errfile) +{ + global $phpbb_filesystem; + + static $root_path; + + if (empty($root_path)) + { + if ($phpbb_filesystem) + { + $root_path = $phpbb_filesystem->realpath(dirname(__FILE__) . '/../'); + } + else + { + $filesystem = new \phpbb\filesystem\filesystem(); + $root_path = $filesystem->realpath(dirname(__FILE__) . '/../'); + } + } + + return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile); +} + +/** +* Queries the session table to get information about online guests +* @param int $item_id Limits the search to the item with this id +* @param string $item The name of the item which is stored in the session table as session_{$item}_id +* @return int The number of active distinct guest sessions +*/ +function obtain_guest_count($item_id = 0, $item = 'forum') +{ + global $db, $config; + + if ($item_id) + { + $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id; + } + else + { + $reading_sql = ''; + } + $time = (time() - (intval($config['load_online_time']) * 60)); + + // Get number of online guests + + if ($db->get_sql_layer() === 'sqlite3') + { + $sql = 'SELECT COUNT(session_ip) as num_guests + FROM ( + SELECT DISTINCT s.session_ip + FROM ' . SESSIONS_TABLE . ' s + WHERE s.session_user_id = ' . ANONYMOUS . ' + AND s.session_time >= ' . ($time - ((int) ($time % 60))) . + $reading_sql . + ')'; + } + else + { + $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests + FROM ' . SESSIONS_TABLE . ' s + WHERE s.session_user_id = ' . ANONYMOUS . ' + AND s.session_time >= ' . ($time - ((int) ($time % 60))) . + $reading_sql; + } + $result = $db->sql_query($sql); + $guests_online = (int) $db->sql_fetchfield('num_guests'); + $db->sql_freeresult($result); + + return $guests_online; +} + +/** +* Queries the session table to get information about online users +* @param int $item_id Limits the search to the item with this id +* @param string $item The name of the item which is stored in the session table as session_{$item}_id +* @return array An array containing the ids of online, hidden and visible users, as well as statistical info +*/ +function obtain_users_online($item_id = 0, $item = 'forum') +{ + global $db, $config; + + $reading_sql = ''; + if ($item_id !== 0) + { + $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id; + } + + $online_users = array( + 'online_users' => array(), + 'hidden_users' => array(), + 'total_online' => 0, + 'visible_online' => 0, + 'hidden_online' => 0, + 'guests_online' => 0, + ); + + if ($config['load_online_guests']) + { + $online_users['guests_online'] = obtain_guest_count($item_id, $item); + } + + // a little discrete magic to cache this for 30 seconds + $time = (time() - (intval($config['load_online_time']) * 60)); + + $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline + FROM ' . SESSIONS_TABLE . ' s + WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) . + $reading_sql . + ' AND s.session_user_id <> ' . ANONYMOUS; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + // Skip multiple sessions for one user + if (!isset($online_users['online_users'][$row['session_user_id']])) + { + $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id']; + if ($row['session_viewonline']) + { + $online_users['visible_online']++; + } + else + { + $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id']; + $online_users['hidden_online']++; + } + } + } + $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online']; + $db->sql_freeresult($result); + + return $online_users; +} + +/** +* Uses the result of obtain_users_online to generate a localized, readable representation. +* @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics +* @param int $item_id Indicate that the data is limited to one item and not global +* @param string $item The name of the item which is stored in the session table as session_{$item}_id +* @return array An array containing the string for output to the template +*/ +function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum') +{ + global $config, $db, $user, $auth, $phpbb_dispatcher; + + $user_online_link = $rowset = array(); + // Need caps version of $item for language-strings + $item_caps = strtoupper($item); + + if (count($online_users['online_users'])) + { + $sql_ary = array( + 'SELECT' => 'u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour', + 'FROM' => array( + USERS_TABLE => 'u', + ), + 'WHERE' => $db->sql_in_set('u.user_id', $online_users['online_users']), + 'ORDER_BY' => 'u.username_clean ASC', + ); + + /** + * Modify SQL query to obtain online users data + * + * @event core.obtain_users_online_string_sql + * @var array online_users Array with online users data + * from obtain_users_online() + * @var int item_id Restrict online users to item id + * @var string item Restrict online users to a certain + * session item, e.g. forum for + * session_forum_id + * @var array sql_ary SQL query array to obtain users online data + * @since 3.1.4-RC1 + * @changed 3.1.7-RC1 Change sql query into array and adjust var accordingly. Allows extension authors the ability to adjust the sql_ary. + */ + $vars = array('online_users', 'item_id', 'item', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_sql', compact($vars))); + + $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); + $rowset = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + foreach ($rowset as $row) + { + // User is logged in and therefore not a guest + if ($row['user_id'] != ANONYMOUS) + { + if (isset($online_users['hidden_users'][$row['user_id']])) + { + $row['username'] = '' . $row['username'] . ''; + } + + if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline') || $row['user_id'] === $user->data['user_id']) + { + $user_online_link[$row['user_id']] = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']); + } + } + } + } + + /** + * Modify online userlist data + * + * @event core.obtain_users_online_string_before_modify + * @var array online_users Array with online users data + * from obtain_users_online() + * @var int item_id Restrict online users to item id + * @var string item Restrict online users to a certain + * session item, e.g. forum for + * session_forum_id + * @var array rowset Array with online users data + * @var array user_online_link Array with online users items (usernames) + * @since 3.1.10-RC1 + */ + $vars = array( + 'online_users', + 'item_id', + 'item', + 'rowset', + 'user_online_link', + ); + extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_before_modify', compact($vars))); + + $online_userlist = implode(', ', $user_online_link); + + if (!$online_userlist) + { + $online_userlist = $user->lang['NO_ONLINE_USERS']; + } + + if ($item_id === 0) + { + $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist; + } + else if ($config['load_online_guests']) + { + $online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist); + } + else + { + $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist); + } + // Build online listing + $visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']); + $hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']); + + if ($config['load_online_guests']) + { + $guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']); + $l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online); + } + else + { + $l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online); + } + + /** + * Modify online userlist data + * + * @event core.obtain_users_online_string_modify + * @var array online_users Array with online users data + * from obtain_users_online() + * @var int item_id Restrict online users to item id + * @var string item Restrict online users to a certain + * session item, e.g. forum for + * session_forum_id + * @var array rowset Array with online users data + * @var array user_online_link Array with online users items (usernames) + * @var string online_userlist String containing users online list + * @var string l_online_users String with total online users count info + * @since 3.1.4-RC1 + */ + $vars = array( + 'online_users', + 'item_id', + 'item', + 'rowset', + 'user_online_link', + 'online_userlist', + 'l_online_users', + ); + extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_modify', compact($vars))); + + return array( + 'online_userlist' => $online_userlist, + 'l_online_users' => $l_online_users, + ); +} + +/** +* Get option bitfield from custom data +* +* @param int $bit The bit/value to get +* @param int $data Current bitfield to check +* @return bool Returns true if value of constant is set in bitfield, else false +*/ +function phpbb_optionget($bit, $data) +{ + return ($data & 1 << (int) $bit) ? true : false; +} + +/** +* Set option bitfield +* +* @param int $bit The bit/value to set/unset +* @param bool $set True if option should be set, false if option should be unset. +* @param int $data Current bitfield to change +* +* @return int The new bitfield +*/ +function phpbb_optionset($bit, $set, $data) +{ + if ($set && !($data & 1 << $bit)) + { + $data += 1 << $bit; + } + else if (!$set && ($data & 1 << $bit)) + { + $data -= 1 << $bit; + } + + return $data; +} + +/** +* Login using http authenticate. +* +* @param array $param Parameter array, see $param_defaults array. +* +* @return null +*/ +function phpbb_http_login($param) +{ + global $auth, $user, $request; + global $config; + + $param_defaults = array( + 'auth_message' => '', + + 'autologin' => false, + 'viewonline' => true, + 'admin' => false, + ); + + // Overwrite default values with passed values + $param = array_merge($param_defaults, $param); + + // User is already logged in + // We will not overwrite his session + if (!empty($user->data['is_registered'])) + { + return; + } + + // $_SERVER keys to check + $username_keys = array( + 'PHP_AUTH_USER', + 'Authorization', + 'REMOTE_USER', 'REDIRECT_REMOTE_USER', + 'HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION', + 'REMOTE_AUTHORIZATION', 'REDIRECT_REMOTE_AUTHORIZATION', + 'AUTH_USER', + ); + + $password_keys = array( + 'PHP_AUTH_PW', + 'REMOTE_PASSWORD', + 'AUTH_PASSWORD', + ); + + $username = null; + foreach ($username_keys as $k) + { + if ($request->is_set($k, \phpbb\request\request_interface::SERVER)) + { + $username = htmlspecialchars_decode($request->server($k)); + break; + } + } + + $password = null; + foreach ($password_keys as $k) + { + if ($request->is_set($k, \phpbb\request\request_interface::SERVER)) + { + $password = htmlspecialchars_decode($request->server($k)); + break; + } + } + + // Decode encoded information (IIS, CGI, FastCGI etc.) + if (!is_null($username) && is_null($password) && strpos($username, 'Basic ') === 0) + { + list($username, $password) = explode(':', base64_decode(substr($username, 6)), 2); + } + + if (!is_null($username) && !is_null($password)) + { + set_var($username, $username, 'string', true); + set_var($password, $password, 'string', true); + + $auth_result = $auth->login($username, $password, $param['autologin'], $param['viewonline'], $param['admin']); + + if ($auth_result['status'] == LOGIN_SUCCESS) + { + return; + } + else if ($auth_result['status'] == LOGIN_ERROR_ATTEMPTS) + { + send_status_line(401, 'Unauthorized'); + + trigger_error('NOT_AUTHORISED'); + } + } + + // Prepend sitename to auth_message + $param['auth_message'] = ($param['auth_message'] === '') ? $config['sitename'] : $config['sitename'] . ' - ' . $param['auth_message']; + + // We should probably filter out non-ASCII characters - RFC2616 + $param['auth_message'] = preg_replace('/[\x80-\xFF]/', '?', $param['auth_message']); + + header('WWW-Authenticate: Basic realm="' . $param['auth_message'] . '"'); + send_status_line(401, 'Unauthorized'); + + trigger_error('NOT_AUTHORISED'); +} + +/** +* Escapes and quotes a string for use as an HTML/XML attribute value. +* +* This is a port of Python xml.sax.saxutils quoteattr. +* +* The function will attempt to choose a quote character in such a way as to +* avoid escaping quotes in the string. If this is not possible the string will +* be wrapped in double quotes and double quotes will be escaped. +* +* @param string $data The string to be escaped +* @param array $entities Associative array of additional entities to be escaped +* @return string Escaped and quoted string +*/ +function phpbb_quoteattr($data, $entities = null) +{ + $data = str_replace('&', '&', $data); + $data = str_replace('>', '>', $data); + $data = str_replace('<', '<', $data); + + $data = str_replace("\n", ' ', $data); + $data = str_replace("\r", ' ', $data); + $data = str_replace("\t", ' ', $data); + + if (!empty($entities)) + { + $data = str_replace(array_keys($entities), array_values($entities), $data); + } + + if (strpos($data, '"') !== false) + { + if (strpos($data, "'") !== false) + { + $data = '"' . str_replace('"', '"', $data) . '"'; + } + else + { + $data = "'" . $data . "'"; + } + } + else + { + $data = '"' . $data . '"'; + } + + return $data; +} + +/** +* Converts query string (GET) parameters in request into hidden fields. +* +* Useful for forwarding GET parameters when submitting forms with GET method. +* +* It is possible to omit some of the GET parameters, which is useful if +* they are specified in the form being submitted. +* +* sid is always omitted. +* +* @param \phpbb\request\request $request Request object +* @param array $exclude A list of variable names that should not be forwarded +* @return string HTML with hidden fields +*/ +function phpbb_build_hidden_fields_for_query_params($request, $exclude = null) +{ + $names = $request->variable_names(\phpbb\request\request_interface::GET); + $hidden = ''; + foreach ($names as $name) + { + // Sessions are dealt with elsewhere, omit sid always + if ($name == 'sid') + { + continue; + } + + // Omit any additional parameters requested + if (!empty($exclude) && in_array($name, $exclude)) + { + continue; + } + + $escaped_name = phpbb_quoteattr($name); + + // Note: we might retrieve the variable from POST or cookies + // here. To avoid exposing cookies, skip variables that are + // overwritten somewhere other than GET entirely. + $value = $request->variable($name, '', true); + $get_value = $request->variable($name, '', true, \phpbb\request\request_interface::GET); + if ($value === $get_value) + { + $escaped_value = phpbb_quoteattr($value); + $hidden .= ""; + } + } + return $hidden; +} + +/** +* Get user avatar +* +* @param array $user_row Row from the users table +* @param string $alt Optional language string for alt tag within image, can be a language key or text +* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP +* @param bool $lazy If true, will be lazy loaded (requires JS) +* +* @return string Avatar html +*/ +function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false) +{ + $row = \phpbb\avatar\manager::clean_row($user_row, 'user'); + return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); +} + +/** +* Get group avatar +* +* @param array $group_row Row from the groups table +* @param string $alt Optional language string for alt tag within image, can be a language key or text +* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP +* @param bool $lazy If true, will be lazy loaded (requires JS) +* +* @return string Avatar html +*/ +function phpbb_get_group_avatar($user_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false) +{ + $row = \phpbb\avatar\manager::clean_row($user_row, 'group'); + return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); +} + +/** +* Get avatar +* +* @param array $row Row cleaned by \phpbb\avatar\manager::clean_row +* @param string $alt Optional language string for alt tag within image, can be a language key or text +* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP +* @param bool $lazy If true, will be lazy loaded (requires JS) +* +* @return string Avatar html +*/ +function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false) +{ + global $user, $config; + global $phpbb_container, $phpbb_dispatcher; + + if (!$config['allow_avatar'] && !$ignore_config) + { + return ''; + } + + $avatar_data = array( + 'src' => $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + + /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ + $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); + $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], !$ignore_config); + $html = ''; + + if ($driver) + { + $html = $driver->get_custom_html($user, $row, $alt); + $avatar_data = $driver->get_data($row); + } + else + { + $avatar_data['src'] = ''; + } + + if (empty($html) && !empty($avatar_data['src'])) + { + if ($lazy) + { + // Determine board url - we may need it later + $board_url = generate_board_url() . '/'; + // This path is sent with the base template paths in the assign_vars() + // call below. We need to correct it in case we are accessing from a + // controller because the web paths will be incorrect otherwise. + $phpbb_path_helper = $phpbb_container->get('path_helper'); + $corrected_path = $phpbb_path_helper->get_web_root_path(); + + $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path; + + $theme = "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme'; + + $src = 'src="' . $theme . '/images/no_avatar.gif" data-src="' . $avatar_data['src'] . '"'; + } + else + { + $src = 'src="' . $avatar_data['src'] . '"'; + } + + $html = ''; + } + + /** + * Event to modify HTML tag of avatar + * + * @event core.get_avatar_after + * @var array row Row cleaned by \phpbb\avatar\manager::clean_row + * @var string alt Optional language string for alt tag within image, can be a language key or text + * @var bool ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP + * @var array avatar_data The HTML attributes for avatar tag + * @var string html The HTML tag of generated avatar + * @since 3.1.6-RC1 + */ + $vars = array('row', 'alt', 'ignore_config', 'avatar_data', 'html'); + extract($phpbb_dispatcher->trigger_event('core.get_avatar_after', compact($vars))); + + return $html; +} + +/** +* Generate page header +*/ +function page_header($page_title = '', $display_online_list = false, $item_id = 0, $item = 'forum', $send_headers = true) +{ + global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path; + global $phpbb_dispatcher, $request, $phpbb_container, $phpbb_admin_path; + + if (defined('HEADER_INC')) + { + return; + } + + define('HEADER_INC', true); + + // A listener can set this variable to `true` when it overrides this function + $page_header_override = false; + + /** + * Execute code and/or overwrite page_header() + * + * @event core.page_header + * @var string page_title Page title + * @var bool display_online_list Do we display online users list + * @var string item Restrict online users to a certain + * session item, e.g. forum for + * session_forum_id + * @var int item_id Restrict online users to item id + * @var bool page_header_override Shall we return instead of running + * the rest of page_header() + * @since 3.1.0-a1 + */ + $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override'); + extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars))); + + if ($page_header_override) + { + return; + } + + // gzip_compression + if ($config['gzip_compress']) + { + // to avoid partially compressed output resulting in blank pages in + // the browser or error messages, compression is disabled in a few cases: + // + // 1) if headers have already been sent, this indicates plaintext output + // has been started so further content must not be compressed + // 2) the length of the current output buffer is non-zero. This means + // there is already some uncompressed content in this output buffer + // so further output must not be compressed + // 3) if more than one level of output buffering is used because we + // cannot test all output buffer level content lengths. One level + // could be caused by php.ini output_buffering. Anything + // beyond that is manual, so the code wrapping phpBB in output buffering + // can easily compress the output itself. + // + if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0) + { + ob_start('ob_gzhandler'); + } + } + + $user->update_session_infos(); + + // Generate logged in/logged out status + if ($user->data['user_id'] != ANONYMOUS) + { + $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id); + $l_login_logout = $user->lang['LOGOUT']; + } + else + { + $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'); + $l_login_logout = $user->lang['LOGIN']; + } + + // Last visit date/time + $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : ''; + + // Get users online list ... if required + $l_online_users = $online_userlist = $l_online_record = $l_online_time = ''; + + if ($config['load_online'] && $config['load_online_time'] && $display_online_list) + { + /** + * Load online data: + * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id. + */ + $item_id = max($item_id, 0); + + $online_users = obtain_users_online($item_id, $item); + $user_online_strings = obtain_users_online_string($online_users, $item_id, $item); + + $l_online_users = $user_online_strings['l_online_users']; + $online_userlist = $user_online_strings['online_userlist']; + $total_online_users = $online_users['total_online']; + + if ($total_online_users > $config['record_online_users']) + { + $config->set('record_online_users', $total_online_users, false); + $config->set('record_online_date', time(), false); + } + + $l_online_record = $user->lang('RECORD_ONLINE_USERS', (int) $config['record_online_users'], $user->format_date($config['record_online_date'], false, true)); + + $l_online_time = $user->lang('VIEW_ONLINE_TIMES', (int) $config['load_online_time']); + } + + $s_privmsg_new = false; + + // Check for new private messages if user is logged in + if (!empty($user->data['is_registered'])) + { + if ($user->data['user_new_privmsg']) + { + if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit']) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_last_privmsg = ' . $user->data['session_last_visit'] . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $s_privmsg_new = true; + } + else + { + $s_privmsg_new = false; + } + } + else + { + $s_privmsg_new = false; + } + } + + $forum_id = $request->variable('f', 0); + $topic_id = $request->variable('t', 0); + + $s_feed_news = false; + + // Get option for news + if ($config['feed_enable']) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); + $result = $db->sql_query_limit($sql, 1, 0, 600); + $s_feed_news = (int) $db->sql_fetchfield('forum_id'); + $db->sql_freeresult($result); + } + + // Determine board url - we may need it later + $board_url = generate_board_url() . '/'; + // This path is sent with the base template paths in the assign_vars() + // call below. We need to correct it in case we are accessing from a + // controller because the web paths will be incorrect otherwise. + /* @var $phpbb_path_helper \phpbb\path_helper */ + $phpbb_path_helper = $phpbb_container->get('path_helper'); + $corrected_path = $phpbb_path_helper->get_web_root_path(); + $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path; + + // Send a proper content-language to the output + $user_lang = $user->lang['USER_LANG']; + if (strpos($user_lang, '-x-') !== false) + { + $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-')); + } + + $s_search_hidden_fields = array(); + if ($_SID) + { + $s_search_hidden_fields['sid'] = $_SID; + } + + if (!empty($_EXTRA_URL)) + { + foreach ($_EXTRA_URL as $url_param) + { + $url_param = explode('=', $url_param, 2); + $s_search_hidden_fields[$url_param[0]] = $url_param[1]; + } + } + + $dt = $user->create_datetime(); + $timezone_offset = $user->lang(array('timezones', 'UTC_OFFSET'), phpbb_format_timezone_offset($dt->getOffset())); + $timezone_name = $user->timezone->getName(); + if (isset($user->lang['timezones'][$timezone_name])) + { + $timezone_name = $user->lang['timezones'][$timezone_name]; + } + + // Output the notifications + $notifications = false; + if ($config['load_notifications'] && $config['allow_board_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE) + { + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $notifications = $phpbb_notifications->load_notifications('notification.method.board', array( + 'all_unread' => true, + 'limit' => 5, + )); + + foreach ($notifications['notifications'] as $notification) + { + $template->assign_block_vars('notifications', $notification->prepare_for_display()); + } + } + + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + $notification_mark_hash = generate_link_hash('mark_all_notifications_read'); + + $s_login_redirect = build_hidden_fields(array('redirect' => $phpbb_path_helper->remove_web_root_path(build_url()))); + /** + * Workaround for missing template variable in pre phpBB 3.2.6 styles. + * @deprecated 3.2.7 (To be removed: 3.3.0-a1) + */ + $form_token_login = $template->retrieve_var('S_FORM_TOKEN_LOGIN'); + if (!empty($form_token_login)) + { + $s_login_redirect .= $form_token_login; + // Remove S_FORM_TOKEN_LOGIN as it's already appended to S_LOGIN_REDIRECT + $template->assign_var('S_FORM_TOKEN_LOGIN', ''); + } + + // The following assigns all _common_ variables that may be used at any point in a template. + $template->assign_vars(array( + 'SITENAME' => $config['sitename'], + 'SITE_DESCRIPTION' => $config['site_desc'], + 'PAGE_TITLE' => $page_title, + 'SCRIPT_NAME' => str_replace('.' . $phpEx, '', $user->page['page_name']), + 'LAST_VISIT_DATE' => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit), + 'LAST_VISIT_YOU' => $s_last_visit, + 'CURRENT_TIME' => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)), + 'TOTAL_USERS_ONLINE' => $l_online_users, + 'LOGGED_IN_USER_LIST' => $online_userlist, + 'RECORD_USERS' => $l_online_record, + + 'PRIVATE_MESSAGE_COUNT' => (!empty($user->data['user_unread_privmsg'])) ? $user->data['user_unread_privmsg'] : 0, + 'CURRENT_USER_AVATAR' => phpbb_get_user_avatar($user->data), + 'CURRENT_USERNAME_SIMPLE' => get_username_string('no_profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']), + 'CURRENT_USERNAME_FULL' => get_username_string('full', $user->data['user_id'], $user->data['username'], $user->data['user_colour']), + 'UNREAD_NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '', + 'NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '', + 'U_VIEW_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'), + 'U_MARK_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_list&mark=all&token=' . $notification_mark_hash), + 'U_NOTIFICATION_SETTINGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&mode=notification_options'), + 'S_NOTIFICATIONS_DISPLAY' => $config['load_notifications'] && $config['allow_board_notifications'], + + 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'], + 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'], + 'S_USER_NEW' => $user->data['user_new'], + + 'SID' => $SID, + '_SID' => $_SID, + 'SESSION_ID' => $user->session_id, + 'ROOT_PATH' => $web_path, + 'BOARD_URL' => $board_url, + + 'L_LOGIN_LOGOUT' => $l_login_logout, + 'L_INDEX' => ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['FORUM_INDEX'], + 'L_SITE_HOME' => ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'], + 'L_ONLINE_EXPLAIN' => $l_online_time, + + 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), + 'U_RETURN_INBOX' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'), + 'U_MEMBERLIST' => append_sid("{$phpbb_root_path}memberlist.$phpEx"), + 'U_VIEWONLINE' => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '', + 'U_LOGIN_LOGOUT' => $u_login_logout, + 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"), + 'U_SEARCH' => append_sid("{$phpbb_root_path}search.$phpEx"), + 'U_SITE_HOME' => $config['site_home_url'], + 'U_REGISTER' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'), + 'U_PROFILE' => append_sid("{$phpbb_root_path}ucp.$phpEx"), + 'U_USER_PROFILE' => get_username_string('profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']), + 'U_MODCP' => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id), + 'U_FAQ' => $controller_helper->route('phpbb_help_faq_controller'), + 'U_SEARCH_SELF' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'), + 'U_SEARCH_NEW' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'), + 'U_SEARCH_UNANSWERED' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'), + 'U_SEARCH_UNREAD' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'), + 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'), + 'U_DELETE_COOKIES' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'), + 'U_CONTACT_US' => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '', + 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'), + 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'), + 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'), + 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')), + 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '', + 'U_FEED' => $controller_helper->route('phpbb_feed_index'), + + 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false, + 'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false, + 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false, + 'S_REGISTERED_USER' => (!empty($user->data['is_registered'])) ? true : false, + 'S_IS_BOT' => (!empty($user->data['is_bot'])) ? true : false, + 'S_USER_LANG' => $user_lang, + 'S_USER_BROWSER' => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'], + 'S_USERNAME' => $user->data['username'], + 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'], + 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right', + 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left', + 'S_CONTENT_ENCODING' => 'UTF-8', + 'S_TIMEZONE' => sprintf($user->lang['ALL_TIMES'], $timezone_offset, $timezone_name), + 'S_DISPLAY_ONLINE_LIST' => ($l_online_time) ? 1 : 0, + 'S_DISPLAY_SEARCH' => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1), + 'S_DISPLAY_PM' => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false, + 'S_DISPLAY_MEMBERLIST' => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0, + 'S_NEW_PM' => ($s_privmsg_new) ? 1 : 0, + 'S_REGISTER_ENABLED' => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false, + 'S_FORUM_ID' => $forum_id, + 'S_TOPIC_ID' => $topic_id, + + 'S_LOGIN_ACTION' => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id)), + 'S_LOGIN_REDIRECT' => $s_login_redirect, + + 'S_ENABLE_FEEDS' => ($config['feed_enable']) ? true : false, + 'S_ENABLE_FEEDS_OVERALL' => ($config['feed_overall']) ? true : false, + 'S_ENABLE_FEEDS_FORUMS' => ($config['feed_overall_forums']) ? true : false, + 'S_ENABLE_FEEDS_TOPICS' => ($config['feed_topics_new']) ? true : false, + 'S_ENABLE_FEEDS_TOPICS_ACTIVE' => ($config['feed_topics_active']) ? true : false, + 'S_ENABLE_FEEDS_NEWS' => ($s_feed_news) ? true : false, + + 'S_LOAD_UNREADS' => ($config['load_unreads_search'] && ($config['load_anon_lastread'] || $user->data['is_registered'])) ? true : false, + + 'S_SEARCH_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields), + + 'T_ASSETS_VERSION' => $config['assets_version'], + 'T_ASSETS_PATH' => "{$web_path}assets", + 'T_THEME_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme', + 'T_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template', + 'T_SUPER_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template', + 'T_IMAGES_PATH' => "{$web_path}images/", + 'T_SMILIES_PATH' => "{$web_path}{$config['smilies_path']}/", + 'T_AVATAR_PATH' => "{$web_path}{$config['avatar_path']}/", + 'T_AVATAR_GALLERY_PATH' => "{$web_path}{$config['avatar_gallery_path']}/", + 'T_ICONS_PATH' => "{$web_path}{$config['icons_path']}/", + 'T_RANKS_PATH' => "{$web_path}{$config['ranks_path']}/", + 'T_UPLOAD_PATH' => "{$web_path}{$config['upload_path']}/", + 'T_STYLESHEET_LINK' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/stylesheet_' . $day_state . '.css?assets_version=' . $config['assets_version'], + 'T_STYLESHEET_LANG_LINK'=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/' . $user->lang_name . '/stylesheet.css?assets_version=' . $config['assets_version'], + 'T_FONT_AWESOME_LINK' => !empty($config['allow_cdn']) && !empty($config['load_font_awesome_url']) ? $config['load_font_awesome_url'] : "{$web_path}assets/css/font-awesome.min.css?assets_version=" . $config['assets_version'], + 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery.min.js?assets_version=" . $config['assets_version'], + 'S_ALLOW_CDN' => !empty($config['allow_cdn']), + 'S_COOKIE_NOTICE' => !empty($config['cookie_notice']), + + 'T_THEME_NAME' => rawurlencode($user->style['style_path']), + 'T_THEME_LANG_NAME' => $user->lang_name, + 'T_TEMPLATE_NAME' => $user->style['style_path'], + 'T_SUPER_TEMPLATE_NAME' => rawurlencode((isset($user->style['style_parent_tree']) && $user->style['style_parent_tree']) ? $user->style['style_parent_tree'] : $user->style['style_path']), + 'T_IMAGES' => 'images', + 'T_SMILIES' => $config['smilies_path'], + 'T_AVATAR' => $config['avatar_path'], + 'T_AVATAR_GALLERY' => $config['avatar_gallery_path'], + 'T_ICONS' => $config['icons_path'], + 'T_RANKS' => $config['ranks_path'], + 'T_UPLOAD' => $config['upload_path'], + + 'SITE_LOGO_IMG' => $user->img('site_logo'), + )); + + $http_headers = array(); + + if ($send_headers) + { + // An array of http headers that phpbb will set. The following event may override these. + $http_headers += array( + // application/xhtml+xml not used because of IE + 'Content-type' => 'text/html; charset=UTF-8', + 'Cache-Control' => 'private, no-cache="set-cookie"', + 'Expires' => gmdate('D, d M Y H:i:s', time()) . ' GMT', + ); + if (!empty($user->data['is_bot'])) + { + // Let reverse proxies know we detected a bot. + $http_headers['X-PHPBB-IS-BOT'] = 'yes'; + } + } + + /** + * Execute code and/or overwrite _common_ template variables after they have been assigned. + * + * @event core.page_header_after + * @var string page_title Page title + * @var bool display_online_list Do we display online users list + * @var string item Restrict online users to a certain + * session item, e.g. forum for + * session_forum_id + * @var int item_id Restrict online users to item id + * @var array http_headers HTTP headers that should be set by phpbb + * + * @since 3.1.0-b3 + */ + $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'http_headers'); + extract($phpbb_dispatcher->trigger_event('core.page_header_after', compact($vars))); + + foreach ($http_headers as $hname => $hval) + { + header((string) $hname . ': ' . (string) $hval); + } + + return; +} + +/** +* Check and display the SQL report if requested. +* +* @param \phpbb\request\request_interface $request Request object +* @param \phpbb\auth\auth $auth Auth object +* @param \phpbb\db\driver\driver_interface $db Database connection +*/ +function phpbb_check_and_display_sql_report(\phpbb\request\request_interface $request, \phpbb\auth\auth $auth, \phpbb\db\driver\driver_interface $db) +{ + if ($request->variable('explain', false) && $auth->acl_get('a_') && defined('DEBUG')) + { + $db->sql_report('display'); + } +} + +/** +* Generate the debug output string +* +* @param \phpbb\db\driver\driver_interface $db Database connection +* @param \phpbb\config\config $config Config object +* @param \phpbb\auth\auth $auth Auth object +* @param \phpbb\user $user User object +* @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher +* @return string +*/ +function phpbb_generate_debug_output(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\auth\auth $auth, \phpbb\user $user, \phpbb\event\dispatcher_interface $phpbb_dispatcher) +{ + $debug_info = array(); + + // Output page creation time + if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + if (isset($GLOBALS['starttime'])) + { + $totaltime = microtime(true) - $GLOBALS['starttime']; + $debug_info[] = sprintf('Time: %.3fs', $db->get_sql_time(), ($totaltime - $db->get_sql_time()), $totaltime); + } + + $debug_info[] = sprintf('Queries: %d', $db->sql_num_queries(true), $db->sql_num_queries()); + + $memory_usage = memory_get_peak_usage(); + if ($memory_usage) + { + $memory_usage = get_formatted_filesize($memory_usage); + + $debug_info[] = 'Peak Memory Usage: ' . $memory_usage; + } + } + + if (defined('DEBUG')) + { + $debug_info[] = 'GZIP: ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off'); + + if ($user->load) + { + $debug_info[] = 'Load: ' . $user->load; + } + + if ($auth->acl_get('a_')) + { + $debug_info[] = 'SQL Explain'; + } + } + + /** + * Modify debug output information + * + * @event core.phpbb_generate_debug_output + * @var array debug_info Array of strings with debug information + * + * @since 3.1.0-RC3 + */ + $vars = array('debug_info'); + extract($phpbb_dispatcher->trigger_event('core.phpbb_generate_debug_output', compact($vars))); + + return implode(' | ', $debug_info); +} + +/** +* Generate page footer +* +* @param bool $run_cron Whether or not to run the cron +* @param bool $display_template Whether or not to display the template +* @param bool $exit_handler Whether or not to run the exit_handler() +*/ +function page_footer($run_cron = true, $display_template = true, $exit_handler = true) +{ + global $db, $config, $template, $user, $auth, $cache, $phpEx; + global $request, $phpbb_dispatcher, $phpbb_admin_path; + + // A listener can set this variable to `true` when it overrides this function + $page_footer_override = false; + + /** + * Execute code and/or overwrite page_footer() + * + * @event core.page_footer + * @var bool run_cron Shall we run cron tasks + * @var bool page_footer_override Shall we return instead of running + * the rest of page_footer() + * @since 3.1.0-a1 + */ + $vars = array('run_cron', 'page_footer_override'); + extract($phpbb_dispatcher->trigger_event('core.page_footer', compact($vars))); + + if ($page_footer_override) + { + return; + } + + phpbb_check_and_display_sql_report($request, $auth, $db); + + $template->assign_vars(array( + 'DEBUG_OUTPUT' => phpbb_generate_debug_output($db, $config, $auth, $user, $phpbb_dispatcher), + 'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '', + 'CREDIT_LINE' => $user->lang('POWERED_BY', 'phpBB® Forum Software © phpBB Limited'), + + 'U_ACP' => ($auth->acl_get('a_') && !empty($user->data['is_registered'])) ? append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id) : '') + ); + + // Call cron-type script + $call_cron = false; + if (!defined('IN_CRON') && !$config['use_system_cron'] && $run_cron && !$config['board_disable'] && !$user->data['is_bot'] && !$cache->get('_cron.lock_check')) + { + $call_cron = true; + $time_now = (!empty($user->time_now) && is_int($user->time_now)) ? $user->time_now : time(); + + // Any old lock present? + if (!empty($config['cron_lock'])) + { + $cron_time = explode(' ', $config['cron_lock']); + + // If 1 hour lock is present we do not call cron.php + if ($cron_time[0] + 3600 >= $time_now) + { + $call_cron = false; + } + } + } + + // Call cron job? + if ($call_cron) + { + global $phpbb_container; + + /* @var $cron \phpbb\cron\manager */ + $cron = $phpbb_container->get('cron.manager'); + $task = $cron->find_one_ready_task(); + + if ($task) + { + $url = $task->get_url(); + $template->assign_var('RUN_CRON_TASK', 'cron'); + } + else + { + $cache->put('_cron.lock_check', true, 60); + } + } + + /** + * Execute code and/or modify output before displaying the template. + * + * @event core.page_footer_after + * @var bool display_template Whether or not to display the template + * @var bool exit_handler Whether or not to run the exit_handler() + * + * @since 3.1.0-RC5 + */ + $vars = array('display_template', 'exit_handler'); + extract($phpbb_dispatcher->trigger_event('core.page_footer_after', compact($vars))); + + if ($display_template) + { + $template->display('body'); + } + + garbage_collection(); + + if ($exit_handler) + { + exit_handler(); + } +} + +/** +* Closing the cache object and the database +* Cool function name, eh? We might want to add operations to it later +*/ +function garbage_collection() +{ + global $cache, $db; + global $phpbb_dispatcher; + + if (!empty($phpbb_dispatcher)) + { + /** + * Unload some objects, to free some memory, before we finish our task + * + * @event core.garbage_collection + * @since 3.1.0-a1 + */ + $phpbb_dispatcher->dispatch('core.garbage_collection'); + } + + // Unload cache, must be done before the DB connection if closed + if (!empty($cache)) + { + $cache->unload(); + } + + // Close our DB connection. + if (!empty($db)) + { + $db->sql_close(); + } +} + +/** +* Handler for exit calls in phpBB. +* This function supports hooks. +* +* Note: This function is called after the template has been outputted. +*/ +function exit_handler() +{ + global $phpbb_hook; + + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__)) + { + if ($phpbb_hook->hook_return(__FUNCTION__)) + { + return $phpbb_hook->hook_return_result(__FUNCTION__); + } + } + + // As a pre-caution... some setups display a blank page if the flush() is not there. + (ob_get_level() > 0) ? @ob_flush() : @flush(); + + exit; +} + +/** +* Handler for init calls in phpBB. This function is called in \phpbb\user::setup(); +* This function supports hooks. +*/ +function phpbb_user_session_handler() +{ + global $phpbb_hook; + + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(__FUNCTION__)) + { + if ($phpbb_hook->hook_return(__FUNCTION__)) + { + return $phpbb_hook->hook_return_result(__FUNCTION__); + } + } + + return; +} + +/** +* Casts a numeric string $input to an appropriate numeric type (i.e. integer or float) +* +* @param string $input A numeric string. +* +* @return int|float Integer $input if $input fits integer, +* float $input otherwise. +*/ +function phpbb_to_numeric($input) +{ + return ($input > PHP_INT_MAX) ? (float) $input : (int) $input; +} + +/** +* Get the board contact details (e.g. for emails) +* +* @param \phpbb\config\config $config +* @param string $phpEx +* @return string +*/ +function phpbb_get_board_contact(\phpbb\config\config $config, $phpEx) +{ + if ($config['contact_admin_form_enable']) + { + return generate_board_url() . '/memberlist.' . $phpEx . '?mode=contactadmin'; + } + else + { + return $config['board_contact']; + } +} + +/** +* Get a clickable board contact details link +* +* @param \phpbb\config\config $config +* @param string $phpbb_root_path +* @param string $phpEx +* @return string +*/ +function phpbb_get_board_contact_link(\phpbb\config\config $config, $phpbb_root_path, $phpEx) +{ + if ($config['contact_admin_form_enable'] && $config['email_enable']) + { + return append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin'); + } + else + { + return 'mailto:' . htmlspecialchars($config['board_contact']); + } +} diff --git a/includes/functions_acp.php b/includes/functions_acp.php new file mode 100644 index 0000000..dd326c3 --- /dev/null +++ b/includes/functions_acp.php @@ -0,0 +1,726 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Header for acp pages +*/ +function adm_page_header($page_title) +{ + global $config, $user, $template; + global $phpbb_root_path, $phpbb_admin_path, $phpEx, $SID, $_SID; + global $phpbb_dispatcher, $phpbb_container; + + if (defined('HEADER_INC')) + { + return; + } + + define('HEADER_INC', true); + + // A listener can set this variable to `true` when it overrides this function + $adm_page_header_override = false; + + /** + * Execute code and/or overwrite adm_page_header() + * + * @event core.adm_page_header + * @var string page_title Page title + * @var bool adm_page_header_override Shall we return instead of + * running the rest of adm_page_header() + * @since 3.1.0-a1 + */ + $vars = array('page_title', 'adm_page_header_override'); + extract($phpbb_dispatcher->trigger_event('core.adm_page_header', compact($vars))); + + if ($adm_page_header_override) + { + return; + } + + $user->update_session_infos(); + + // gzip_compression + if ($config['gzip_compress']) + { + if (@extension_loaded('zlib') && !headers_sent()) + { + ob_start('ob_gzhandler'); + } + } + + $template->assign_vars(array( + 'PAGE_TITLE' => $page_title, + 'USERNAME' => $user->data['username'], + + 'SID' => $SID, + '_SID' => $_SID, + 'SESSION_ID' => $user->session_id, + 'ROOT_PATH' => $phpbb_root_path, + 'ADMIN_ROOT_PATH' => $phpbb_admin_path, + + 'U_LOGOUT' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout'), + 'U_ADM_LOGOUT' => append_sid("{$phpbb_admin_path}index.$phpEx", 'action=admlogout'), + 'U_ADM_INDEX' => append_sid("{$phpbb_admin_path}index.$phpEx"), + 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"), + + 'T_IMAGES_PATH' => "{$phpbb_root_path}images/", + 'T_SMILIES_PATH' => "{$phpbb_root_path}{$config['smilies_path']}/", + 'T_AVATAR_PATH' => "{$phpbb_root_path}{$config['avatar_path']}/", + 'T_AVATAR_GALLERY_PATH' => "{$phpbb_root_path}{$config['avatar_gallery_path']}/", + 'T_ICONS_PATH' => "{$phpbb_root_path}{$config['icons_path']}/", + 'T_RANKS_PATH' => "{$phpbb_root_path}{$config['ranks_path']}/", + 'T_UPLOAD_PATH' => "{$phpbb_root_path}{$config['upload_path']}/", + 'T_FONT_AWESOME_LINK' => !empty($config['allow_cdn']) && !empty($config['load_font_awesome_url']) ? $config['load_font_awesome_url'] : "{$phpbb_root_path}assets/css/font-awesome.min.css?assets_version=" . $config['assets_version'], + + 'T_ASSETS_VERSION' => $config['assets_version'], + + 'ICON_MOVE_UP' => '' . $user->lang['MOVE_UP'] . '', + 'ICON_MOVE_UP_DISABLED' => '' . $user->lang['MOVE_UP'] . '', + 'ICON_MOVE_DOWN' => '' . $user->lang['MOVE_DOWN'] . '', + 'ICON_MOVE_DOWN_DISABLED' => '' . $user->lang['MOVE_DOWN'] . '', + 'ICON_EDIT' => '' . $user->lang['EDIT'] . '', + 'ICON_EDIT_DISABLED' => '' . $user->lang['EDIT'] . '', + 'ICON_DELETE' => '' . $user->lang['DELETE'] . '', + 'ICON_DELETE_DISABLED' => '' . $user->lang['DELETE'] . '', + 'ICON_SYNC' => '' . $user->lang['RESYNC'] . '', + 'ICON_SYNC_DISABLED' => '' . $user->lang['RESYNC'] . '', + + 'S_USER_LANG' => $user->lang['USER_LANG'], + 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'], + 'S_CONTENT_ENCODING' => 'UTF-8', + 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right', + 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left', + + 'CONTAINER_EXCEPTION' => $phpbb_container->hasParameter('container_exception') ? $phpbb_container->getParameter('container_exception') : false, + )); + + // An array of http headers that phpbb will set. The following event may override these. + $http_headers = array( + // application/xhtml+xml not used because of IE + 'Content-type' => 'text/html; charset=UTF-8', + 'Cache-Control' => 'private, no-cache="set-cookie"', + 'Expires' => gmdate('D, d M Y H:i:s', time()) . ' GMT', + ); + + /** + * Execute code and/or overwrite _common_ template variables after they have been assigned. + * + * @event core.adm_page_header_after + * @var string page_title Page title + * @var array http_headers HTTP headers that should be set by phpbb + * + * @since 3.1.0-RC3 + */ + $vars = array('page_title', 'http_headers'); + extract($phpbb_dispatcher->trigger_event('core.adm_page_header_after', compact($vars))); + + foreach ($http_headers as $hname => $hval) + { + header((string) $hname . ': ' . (string) $hval); + } + + return; +} + +/** +* Page footer for acp pages +*/ +function adm_page_footer($copyright_html = true) +{ + global $db, $config, $template, $user, $auth; + global $phpbb_root_path; + global $request, $phpbb_dispatcher; + + // A listener can set this variable to `true` when it overrides this function + $adm_page_footer_override = false; + + /** + * Execute code and/or overwrite adm_page_footer() + * + * @event core.adm_page_footer + * @var bool copyright_html Shall we display the copyright? + * @var bool adm_page_footer_override Shall we return instead of + * running the rest of adm_page_footer() + * @since 3.1.0-a1 + */ + $vars = array('copyright_html', 'adm_page_footer_override'); + extract($phpbb_dispatcher->trigger_event('core.adm_page_footer', compact($vars))); + + if ($adm_page_footer_override) + { + return; + } + + phpbb_check_and_display_sql_report($request, $auth, $db); + + $template->assign_vars(array( + 'DEBUG_OUTPUT' => phpbb_generate_debug_output($db, $config, $auth, $user, $phpbb_dispatcher), + 'TRANSLATION_INFO' => (!empty($user->lang['TRANSLATION_INFO'])) ? $user->lang['TRANSLATION_INFO'] : '', + 'S_COPYRIGHT_HTML' => $copyright_html, + 'CREDIT_LINE' => $user->lang('POWERED_BY', 'phpBB® Forum Software © phpBB Limited'), + 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$phpbb_root_path}assets/javascript/jquery.min.js", + 'S_ALLOW_CDN' => !empty($config['allow_cdn']), + 'VERSION' => $config['version']) + ); + + $template->display('body'); + + garbage_collection(); + exit_handler(); +} + +/** +* Generate back link for acp pages +*/ +function adm_back_link($u_action) +{ + global $user; + return '

« ' . $user->lang['BACK_TO_PREV'] . ''; +} + +/** +* Build select field options in acp pages +*/ +function build_select($option_ary, $option_default = false) +{ + global $user; + + $html = ''; + foreach ($option_ary as $value => $title) + { + $selected = ($option_default !== false && $value == $option_default) ? ' selected="selected"' : ''; + $html .= ''; + } + + return $html; +} + +/** +* Build radio fields in acp pages +*/ +function h_radio($name, $input_ary, $input_default = false, $id = false, $key = false, $separator = '') +{ + global $user; + + $html = ''; + $id_assigned = false; + foreach ($input_ary as $value => $title) + { + $selected = ($input_default !== false && $value == $input_default) ? ' checked="checked"' : ''; + $html .= '' . $separator; + $id_assigned = true; + } + + return $html; +} + +/** +* Build configuration template for acp configuration pages +*/ +function build_cfg_template($tpl_type, $key, &$new_ary, $config_key, $vars) +{ + global $user, $module, $phpbb_dispatcher; + + $tpl = ''; + $name = 'config[' . $config_key . ']'; + + // Make sure there is no notice printed out for non-existent config options (we simply set them) + if (!isset($new_ary[$config_key])) + { + $new_ary[$config_key] = ''; + } + + switch ($tpl_type[0]) + { + case 'password': + if ($new_ary[$config_key] !== '') + { + // replace passwords with asterixes + $new_ary[$config_key] = '********'; + } + case 'text': + case 'url': + case 'email': + case 'tel': + case 'search': + // maxlength and size are only valid for these types and will be + // ignored for other input types. + $size = (int) $tpl_type[1]; + $maxlength = (int) $tpl_type[2]; + + $tpl = ''; + break; + + case 'color': + case 'datetime': + case 'datetime-local': + case 'month': + case 'week': + $tpl = ''; + break; + + case 'date': + case 'time': + case 'number': + case 'range': + $max = ''; + $min = ( isset($tpl_type[1]) ) ? (int) $tpl_type[1] : false; + if ( isset($tpl_type[2]) ) + { + $max = (int) $tpl_type[2]; + } + + $tpl = ''; + break; + + case 'dimension': + $max = ''; + + $min = (int) $tpl_type[1]; + + if ( isset($tpl_type[2]) ) + { + $max = (int) $tpl_type[2]; + } + + $tpl = ' x '; + break; + + case 'textarea': + $rows = (int) $tpl_type[1]; + $cols = (int) $tpl_type[2]; + + $tpl = ''; + break; + + case 'radio': + $key_yes = ($new_ary[$config_key]) ? ' checked="checked"' : ''; + $key_no = (!$new_ary[$config_key]) ? ' checked="checked"' : ''; + + $tpl_type_cond = explode('_', $tpl_type[1]); + $type_no = ($tpl_type_cond[0] == 'disabled' || $tpl_type_cond[0] == 'enabled') ? false : true; + + $tpl_no = ''; + $tpl_yes = ''; + + $tpl = ($tpl_type_cond[0] == 'yes' || $tpl_type_cond[0] == 'enabled') ? $tpl_yes . $tpl_no : $tpl_no . $tpl_yes; + break; + + case 'select': + case 'custom': + + if (isset($vars['method'])) + { + $call = array($module->module, $vars['method']); + } + else if (isset($vars['function'])) + { + $call = $vars['function']; + } + else + { + break; + } + + if (isset($vars['params'])) + { + $args = array(); + foreach ($vars['params'] as $value) + { + switch ($value) + { + case '{CONFIG_VALUE}': + $value = $new_ary[$config_key]; + break; + + case '{KEY}': + $value = $key; + break; + } + + $args[] = $value; + } + } + else + { + $args = array($new_ary[$config_key], $key); + } + + $return = call_user_func_array($call, $args); + + if ($tpl_type[0] == 'select') + { + $size = (isset($tpl_type[1])) ? (int) $tpl_type[1] : 1; + $data_toggle = (!empty($tpl_type[2])) ? ' data-togglable-settings="true"' : ''; + + $tpl = ''; + } + else + { + $tpl = $return; + } + + break; + + default: + break; + } + + if (isset($vars['append'])) + { + $tpl .= $vars['append']; + } + + $new = $new_ary; + /** + * Overwrite the html code we display for the config value + * + * @event core.build_config_template + * @var array tpl_type Config type array: + * 0 => data type + * 1 [optional] => string: size, int: minimum + * 2 [optional] => string: max. length, int: maximum + * @var string key Should be used for the id attribute in html + * @var array new Array with the config values we display + * @var string name Should be used for the name attribute + * @var array vars Array with the options for the config + * @var string tpl The resulting html code we display + * @since 3.1.0-a1 + */ + $vars = array('tpl_type', 'key', 'new', 'name', 'vars', 'tpl'); + extract($phpbb_dispatcher->trigger_event('core.build_config_template', compact($vars))); + $new_ary = $new; + unset($new); + + return $tpl; +} + +/** +* Going through a config array and validate values, writing errors to $error. The validation method accepts parameters separated by ':' for string and int. +* The first parameter defines the type to be used, the second the lower bound and the third the upper bound. Only the type is required. +*/ +function validate_config_vars($config_vars, &$cfg_array, &$error) +{ + global $phpbb_root_path, $user, $phpbb_dispatcher, $phpbb_filesystem, $language; + + $type = 0; + $min = 1; + $max = 2; + + foreach ($config_vars as $config_name => $config_definition) + { + if (!isset($cfg_array[$config_name]) || strpos($config_name, 'legend') !== false) + { + continue; + } + + if (!isset($config_definition['validate'])) + { + continue; + } + + $validator = explode(':', $config_definition['validate']); + + // Validate a bit. ;) (0 = type, 1 = min, 2= max) + switch ($validator[$type]) + { + case 'url': + $cfg_array[$config_name] = trim($cfg_array[$config_name]); + + if (!empty($cfg_array[$config_name]) && !preg_match('#^' . get_preg_expression('url') . '$#iu', $cfg_array[$config_name])) + { + $error[] = $language->lang('URL_INVALID', $language->lang($config_definition['lang'])); + } + + // no break here + + case 'string': + $length = utf8_strlen($cfg_array[$config_name]); + + // the column is a VARCHAR + $validator[$max] = (isset($validator[$max])) ? min(255, $validator[$max]) : 255; + + if (isset($validator[$min]) && $length < $validator[$min]) + { + $error[] = sprintf($user->lang['SETTING_TOO_SHORT'], $user->lang[$config_definition['lang']], $validator[$min]); + } + else if (isset($validator[$max]) && $length > $validator[2]) + { + $error[] = sprintf($user->lang['SETTING_TOO_LONG'], $user->lang[$config_definition['lang']], $validator[$max]); + } + break; + + case 'bool': + $cfg_array[$config_name] = ($cfg_array[$config_name]) ? 1 : 0; + break; + + case 'int': + $cfg_array[$config_name] = (int) $cfg_array[$config_name]; + + if (isset($validator[$min]) && $cfg_array[$config_name] < $validator[$min]) + { + $error[] = sprintf($user->lang['SETTING_TOO_LOW'], $user->lang[$config_definition['lang']], $validator[$min]); + } + else if (isset($validator[$max]) && $cfg_array[$config_name] > $validator[$max]) + { + $error[] = sprintf($user->lang['SETTING_TOO_BIG'], $user->lang[$config_definition['lang']], $validator[$max]); + } + + if (strpos($config_name, '_max') !== false) + { + // Min/max pairs of settings should ensure that min <= max + // Replace _max with _min to find the name of the minimum + // corresponding configuration variable + $min_name = str_replace('_max', '_min', $config_name); + + if (isset($cfg_array[$min_name]) && is_numeric($cfg_array[$min_name]) && $cfg_array[$config_name] < $cfg_array[$min_name]) + { + // A minimum value exists and the maximum value is less than it + $error[] = sprintf($user->lang['SETTING_TOO_LOW'], $user->lang[$config_definition['lang']], (int) $cfg_array[$min_name]); + } + } + break; + + case 'email': + if (!preg_match('/^' . get_preg_expression('email') . '$/i', $cfg_array[$config_name])) + { + $error[] = $user->lang['EMAIL_INVALID_EMAIL']; + } + break; + + // Absolute path + case 'script_path': + if (!$cfg_array[$config_name]) + { + break; + } + + $destination = str_replace('\\', '/', $cfg_array[$config_name]); + + if ($destination !== '/') + { + // Adjust destination path (no trailing slash) + if (substr($destination, -1, 1) == '/') + { + $destination = substr($destination, 0, -1); + } + + $destination = str_replace(array('../', './'), '', $destination); + + if ($destination[0] != '/') + { + $destination = '/' . $destination; + } + } + + $cfg_array[$config_name] = trim($destination); + + break; + + // Absolute path + case 'lang': + if (!$cfg_array[$config_name]) + { + break; + } + + $cfg_array[$config_name] = basename($cfg_array[$config_name]); + + if (!file_exists($phpbb_root_path . 'language/' . $cfg_array[$config_name] . '/')) + { + $error[] = $user->lang['WRONG_DATA_LANG']; + } + break; + + // Relative path (appended $phpbb_root_path) + case 'rpath': + case 'rwpath': + if (!$cfg_array[$config_name]) + { + break; + } + + $destination = $cfg_array[$config_name]; + + // Adjust destination path (no trailing slash) + if (substr($destination, -1, 1) == '/' || substr($destination, -1, 1) == '\\') + { + $destination = substr($destination, 0, -1); + } + + $destination = str_replace(array('../', '..\\', './', '.\\'), '', $destination); + if ($destination && ($destination[0] == '/' || $destination[0] == "\\")) + { + $destination = ''; + } + + $cfg_array[$config_name] = trim($destination); + + // Path being relative (still prefixed by phpbb_root_path), but with the ability to escape the root dir... + case 'path': + case 'wpath': + + if (!$cfg_array[$config_name]) + { + break; + } + + $cfg_array[$config_name] = trim($cfg_array[$config_name]); + + // Make sure no NUL byte is present... + if (strpos($cfg_array[$config_name], "\0") !== false || strpos($cfg_array[$config_name], '%00') !== false) + { + $cfg_array[$config_name] = ''; + break; + } + + $path = $phpbb_root_path . $cfg_array[$config_name]; + + if (!file_exists($path)) + { + $error[] = sprintf($user->lang['DIRECTORY_DOES_NOT_EXIST'], $cfg_array[$config_name]); + } + + if (file_exists($path) && !is_dir($path)) + { + $error[] = sprintf($user->lang['DIRECTORY_NOT_DIR'], $cfg_array[$config_name]); + } + + // Check if the path is writable + if ($config_definition['validate'] == 'wpath' || $config_definition['validate'] == 'rwpath') + { + if (file_exists($path) && !$phpbb_filesystem->is_writable($path)) + { + $error[] = sprintf($user->lang['DIRECTORY_NOT_WRITABLE'], $cfg_array[$config_name]); + } + } + + break; + + default: + /** + * Validate a config value + * + * @event core.validate_config_variable + * @var array cfg_array Array with config values + * @var string config_name Name of the config we validate + * @var array config_definition Array with the options for + * this config + * @var array error Array of errors, the errors should + * be strings only, language keys are + * not replaced afterwards + * @since 3.1.0-a1 + */ + $vars = array('cfg_array', 'config_name', 'config_definition', 'error'); + extract($phpbb_dispatcher->trigger_event('core.validate_config_variable', compact($vars))); + break; + } + } + + return; +} + +/** +* Checks whatever or not a variable is OK for use in the Database +* param mixed $value_ary An array of the form array(array('lang' => ..., 'value' => ..., 'column_type' =>))' +* param mixed $error The error array +*/ +function validate_range($value_ary, &$error) +{ + global $user; + + $column_types = array( + 'BOOL' => array('php_type' => 'int', 'min' => 0, 'max' => 1), + 'USINT' => array('php_type' => 'int', 'min' => 0, 'max' => 65535), + 'UINT' => array('php_type' => 'int', 'min' => 0, 'max' => (int) 0x7fffffff), + // Do not use (int) 0x80000000 - it evaluates to different + // values on 32-bit and 64-bit systems. + // Apparently -2147483648 is a float on 32-bit systems, + // despite fitting in an int, thus explicit cast is needed. + 'INT' => array('php_type' => 'int', 'min' => (int) -2147483648, 'max' => (int) 0x7fffffff), + 'TINT' => array('php_type' => 'int', 'min' => -128, 'max' => 127), + + 'VCHAR' => array('php_type' => 'string', 'min' => 0, 'max' => 255), + ); + foreach ($value_ary as $value) + { + $column = explode(':', $value['column_type']); + if (!isset($column_types[$column[0]])) + { + continue; + } + else + { + $type = $column_types[$column[0]]; + } + + switch ($type['php_type']) + { + case 'string' : + $max = (isset($column[1])) ? min($column[1],$type['max']) : $type['max']; + if (utf8_strlen($value['value']) > $max) + { + $error[] = sprintf($user->lang['SETTING_TOO_LONG'], $user->lang[$value['lang']], $max); + } + break; + + case 'int': + $min = (isset($column[1])) ? max($column[1],$type['min']) : $type['min']; + $max = (isset($column[2])) ? min($column[2],$type['max']) : $type['max']; + if ($value['value'] < $min) + { + $error[] = sprintf($user->lang['SETTING_TOO_LOW'], $user->lang[$value['lang']], $min); + } + else if ($value['value'] > $max) + { + $error[] = sprintf($user->lang['SETTING_TOO_BIG'], $user->lang[$value['lang']], $max); + } + break; + } + } +} + +/** +* Inserts new config display_vars into an exisiting display_vars array +* at the given position. +* +* @param array $display_vars An array of existing config display vars +* @param array $add_config_vars An array of new config display vars +* @param array $where Where to place the new config vars, +* before or after an exisiting config, as an array +* of the form: array('after' => 'config_name') or +* array('before' => 'config_name'). +* @return array The array of config display vars +*/ +function phpbb_insert_config_array($display_vars, $add_config_vars, $where) +{ + if (is_array($where) && array_key_exists(current($where), $display_vars)) + { + $position = array_search(current($where), array_keys($display_vars)) + ((key($where) == 'before') ? 0 : 1); + $display_vars = array_merge( + array_slice($display_vars, 0, $position), + $add_config_vars, + array_slice($display_vars, $position) + ); + } + + return $display_vars; +} diff --git a/includes/functions_admin.php b/includes/functions_admin.php new file mode 100644 index 0000000..c19d48b --- /dev/null +++ b/includes/functions_admin.php @@ -0,0 +1,3109 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Recalculate Nested Sets +* +* @param int $new_id first left_id (should start with 1) +* @param string $pkey primary key-column (containing the id for the parent_id of the children) +* @param string $table constant or fullname of the table +* @param int $parent_id parent_id of the current set (default = 0) +* @param array $where contains strings to compare closer on the where statement (additional) +*/ +function recalc_nested_sets(&$new_id, $pkey, $table, $parent_id = 0, $where = array()) +{ + global $db; + + $sql = 'SELECT * + FROM ' . $table . ' + WHERE parent_id = ' . (int) $parent_id . + ((!empty($where)) ? ' AND ' . implode(' AND ', $where) : '') . ' + ORDER BY left_id ASC'; + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) + { + // First we update the left_id for this module + if ($row['left_id'] != $new_id) + { + $db->sql_query('UPDATE ' . $table . ' SET ' . $db->sql_build_array('UPDATE', array('left_id' => $new_id)) . " WHERE $pkey = {$row[$pkey]}"); + } + $new_id++; + + // Then we go through any children and update their left/right id's + recalc_nested_sets($new_id, $pkey, $table, $row[$pkey], $where); + + // Then we come back and update the right_id for this module + if ($row['right_id'] != $new_id) + { + $db->sql_query('UPDATE ' . $table . ' SET ' . $db->sql_build_array('UPDATE', array('right_id' => $new_id)) . " WHERE $pkey = {$row[$pkey]}"); + } + $new_id++; + } + $db->sql_freeresult($result); +} + +/** +* Simple version of jumpbox, just lists authed forums +*/ +function make_forum_select($select_id = false, $ignore_id = false, $ignore_acl = false, $ignore_nonpost = false, $ignore_emptycat = true, $only_acl_post = false, $return_array = false) +{ + global $db, $auth, $phpbb_dispatcher; + + // This query is identical to the jumpbox one + $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, forum_flags, forum_options, left_id, right_id + FROM ' . FORUMS_TABLE . ' + ORDER BY left_id ASC'; + $result = $db->sql_query($sql, 600); + + $rowset = array(); + while ($row = $db->sql_fetchrow($result)) + { + $rowset[(int) $row['forum_id']] = $row; + } + $db->sql_freeresult($result); + + $right = 0; + $padding_store = array('0' => ''); + $padding = ''; + $forum_list = ($return_array) ? array() : ''; + + /** + * Modify the forum list data + * + * @event core.make_forum_select_modify_forum_list + * @var array rowset Array with the forums list data + * @since 3.1.10-RC1 + */ + $vars = array('rowset'); + extract($phpbb_dispatcher->trigger_event('core.make_forum_select_modify_forum_list', compact($vars))); + + // Sometimes it could happen that forums will be displayed here not be displayed within the index page + // This is the result of forums not displayed at index, having list permissions and a parent of a forum with no permissions. + // If this happens, the padding could be "broken" + + foreach ($rowset as $row) + { + if ($row['left_id'] < $right) + { + $padding .= '   '; + $padding_store[$row['parent_id']] = $padding; + } + else if ($row['left_id'] > $right + 1) + { + $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : ''; + } + + $right = $row['right_id']; + $disabled = false; + + if (!$ignore_acl && $auth->acl_gets(array('f_list', 'a_forum', 'a_forumadd', 'a_forumdel'), $row['forum_id'])) + { + if ($only_acl_post && !$auth->acl_get('f_post', $row['forum_id']) || (!$auth->acl_get('m_approve', $row['forum_id']) && !$auth->acl_get('f_noapprove', $row['forum_id']))) + { + $disabled = true; + } + } + else if (!$ignore_acl) + { + continue; + } + + if ( + ((is_array($ignore_id) && in_array($row['forum_id'], $ignore_id)) || $row['forum_id'] == $ignore_id) + || + // Non-postable forum with no subforums, don't display + ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id']) && $ignore_emptycat) + || + ($row['forum_type'] != FORUM_POST && $ignore_nonpost) + ) + { + $disabled = true; + } + + if ($return_array) + { + // Include some more information... + $selected = (is_array($select_id)) ? ((in_array($row['forum_id'], $select_id)) ? true : false) : (($row['forum_id'] == $select_id) ? true : false); + $forum_list[$row['forum_id']] = array_merge(array('padding' => $padding, 'selected' => ($selected && !$disabled), 'disabled' => $disabled), $row); + } + else + { + $selected = (is_array($select_id)) ? ((in_array($row['forum_id'], $select_id)) ? ' selected="selected"' : '') : (($row['forum_id'] == $select_id) ? ' selected="selected"' : ''); + $forum_list .= ''; + } + } + unset($padding_store, $rowset); + + return $forum_list; +} + +/** +* Generate size select options +*/ +function size_select_options($size_compare) +{ + global $user; + + $size_types_text = array($user->lang['BYTES'], $user->lang['KIB'], $user->lang['MIB']); + $size_types = array('b', 'kb', 'mb'); + + $s_size_options = ''; + + for ($i = 0, $size = count($size_types_text); $i < $size; $i++) + { + $selected = ($size_compare == $size_types[$i]) ? ' selected="selected"' : ''; + $s_size_options .= ''; + } + + return $s_size_options; +} + +/** +* Generate list of groups (option fields without select) +* +* @param int $group_id The default group id to mark as selected +* @param array $exclude_ids The group ids to exclude from the list, false (default) if you whish to exclude no id +* @param int $manage_founder If set to false (default) all groups are returned, if 0 only those groups returned not being managed by founders only, if 1 only those groups returned managed by founders only. +* +* @return string The list of options. +*/ +function group_select_options($group_id, $exclude_ids = false, $manage_founder = false) +{ + global $db, $config, $phpbb_container; + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $exclude_sql = ($exclude_ids !== false && count($exclude_ids)) ? 'WHERE ' . $db->sql_in_set('group_id', array_map('intval', $exclude_ids), true) : ''; + $sql_and = (!$config['coppa_enable']) ? (($exclude_sql) ? ' AND ' : ' WHERE ') . "group_name <> 'REGISTERED_COPPA'" : ''; + $sql_founder = ($manage_founder !== false) ? (($exclude_sql || $sql_and) ? ' AND ' : ' WHERE ') . 'group_founder_manage = ' . (int) $manage_founder : ''; + + $sql = 'SELECT group_id, group_name, group_type + FROM ' . GROUPS_TABLE . " + $exclude_sql + $sql_and + $sql_founder + ORDER BY group_type DESC, group_name ASC"; + $result = $db->sql_query($sql); + + $s_group_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + $selected = ($row['group_id'] == $group_id) ? ' selected="selected"' : ''; + $s_group_options .= '' . $group_helper->get_name($row['group_name']) . ''; + } + $db->sql_freeresult($result); + + return $s_group_options; +} + +/** +* Obtain authed forums list +*/ +function get_forum_list($acl_list = 'f_list', $id_only = true, $postable_only = false, $no_cache = false) +{ + global $db, $auth, $phpbb_dispatcher; + static $forum_rows; + + if (!isset($forum_rows)) + { + // This query is identical to the jumpbox one + $expire_time = ($no_cache) ? 0 : 600; + + $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id + FROM ' . FORUMS_TABLE . ' + ORDER BY left_id ASC'; + $result = $db->sql_query($sql, $expire_time); + + $forum_rows = array(); + + $right = $padding = 0; + $padding_store = array('0' => 0); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['left_id'] < $right) + { + $padding++; + $padding_store[$row['parent_id']] = $padding; + } + else if ($row['left_id'] > $right + 1) + { + // Ok, if the $padding_store for this parent is empty there is something wrong. For now we will skip over it. + // @todo digging deep to find out "how" this can happen. + $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : $padding; + } + + $right = $row['right_id']; + $row['padding'] = $padding; + + $forum_rows[] = $row; + } + $db->sql_freeresult($result); + unset($padding_store); + } + + $rowset = array(); + foreach ($forum_rows as $row) + { + if ($postable_only && $row['forum_type'] != FORUM_POST) + { + continue; + } + + if ($acl_list == '' || ($acl_list != '' && $auth->acl_gets($acl_list, $row['forum_id']))) + { + $rowset[] = ($id_only) ? (int) $row['forum_id'] : $row; + } + } + + /** + * Modify the forum list data + * + * @event core.get_forum_list_modify_data + * @var array rowset Array with the forum list data + * @since 3.1.10-RC1 + */ + $vars = array('rowset'); + extract($phpbb_dispatcher->trigger_event('core.get_forum_list_modify_data', compact($vars))); + + return $rowset; +} + +/** +* Get forum branch +*/ +function get_forum_branch($forum_id, $type = 'all', $order = 'descending', $include_forum = true) +{ + global $db; + + switch ($type) + { + case 'parents': + $condition = 'f1.left_id BETWEEN f2.left_id AND f2.right_id'; + break; + + case 'children': + $condition = 'f2.left_id BETWEEN f1.left_id AND f1.right_id'; + break; + + default: + $condition = 'f2.left_id BETWEEN f1.left_id AND f1.right_id OR f1.left_id BETWEEN f2.left_id AND f2.right_id'; + break; + } + + $rows = array(); + + $sql = 'SELECT f2.* + FROM ' . FORUMS_TABLE . ' f1 + LEFT JOIN ' . FORUMS_TABLE . " f2 ON ($condition) + WHERE f1.forum_id = $forum_id + ORDER BY f2.left_id " . (($order == 'descending') ? 'ASC' : 'DESC'); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (!$include_forum && $row['forum_id'] == $forum_id) + { + continue; + } + + $rows[] = $row; + } + $db->sql_freeresult($result); + + return $rows; +} + +/** +* Copies permissions from one forum to others +* +* @param int $src_forum_id The source forum we want to copy permissions from +* @param array $dest_forum_ids The destination forum(s) we want to copy to +* @param bool $clear_dest_perms True if destination permissions should be deleted +* @param bool $add_log True if log entry should be added +* +* @return bool False on error +*/ +function copy_forum_permissions($src_forum_id, $dest_forum_ids, $clear_dest_perms = true, $add_log = true) +{ + global $db, $user, $phpbb_log; + + // Only one forum id specified + if (!is_array($dest_forum_ids)) + { + $dest_forum_ids = array($dest_forum_ids); + } + + // Make sure forum ids are integers + $src_forum_id = (int) $src_forum_id; + $dest_forum_ids = array_map('intval', $dest_forum_ids); + + // No source forum or no destination forums specified + if (empty($src_forum_id) || empty($dest_forum_ids)) + { + return false; + } + + // Check if source forum exists + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $src_forum_id; + $result = $db->sql_query($sql); + $src_forum_name = $db->sql_fetchfield('forum_name'); + $db->sql_freeresult($result); + + // Source forum doesn't exist + if (empty($src_forum_name)) + { + return false; + } + + // Check if destination forums exists + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids); + $result = $db->sql_query($sql); + + $dest_forum_ids = $dest_forum_names = array(); + while ($row = $db->sql_fetchrow($result)) + { + $dest_forum_ids[] = (int) $row['forum_id']; + $dest_forum_names[] = $row['forum_name']; + } + $db->sql_freeresult($result); + + // No destination forum exists + if (empty($dest_forum_ids)) + { + return false; + } + + // From the mysql documentation: + // Prior to MySQL 4.0.14, the target table of the INSERT statement cannot appear + // in the FROM clause of the SELECT part of the query. This limitation is lifted in 4.0.14. + // Due to this we stay on the safe side if we do the insertion "the manual way" + + // Rowsets we're going to insert + $users_sql_ary = $groups_sql_ary = array(); + + // Query acl users table for source forum data + $sql = 'SELECT user_id, auth_option_id, auth_role_id, auth_setting + FROM ' . ACL_USERS_TABLE . ' + WHERE forum_id = ' . $src_forum_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $row = array( + 'user_id' => (int) $row['user_id'], + 'auth_option_id' => (int) $row['auth_option_id'], + 'auth_role_id' => (int) $row['auth_role_id'], + 'auth_setting' => (int) $row['auth_setting'], + ); + + foreach ($dest_forum_ids as $dest_forum_id) + { + $users_sql_ary[] = $row + array('forum_id' => $dest_forum_id); + } + } + $db->sql_freeresult($result); + + // Query acl groups table for source forum data + $sql = 'SELECT group_id, auth_option_id, auth_role_id, auth_setting + FROM ' . ACL_GROUPS_TABLE . ' + WHERE forum_id = ' . $src_forum_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $row = array( + 'group_id' => (int) $row['group_id'], + 'auth_option_id' => (int) $row['auth_option_id'], + 'auth_role_id' => (int) $row['auth_role_id'], + 'auth_setting' => (int) $row['auth_setting'], + ); + + foreach ($dest_forum_ids as $dest_forum_id) + { + $groups_sql_ary[] = $row + array('forum_id' => $dest_forum_id); + } + } + $db->sql_freeresult($result); + + $db->sql_transaction('begin'); + + // Clear current permissions of destination forums + if ($clear_dest_perms) + { + $sql = 'DELETE FROM ' . ACL_USERS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids); + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $dest_forum_ids); + $db->sql_query($sql); + } + + $db->sql_multi_insert(ACL_USERS_TABLE, $users_sql_ary); + $db->sql_multi_insert(ACL_GROUPS_TABLE, $groups_sql_ary); + + if ($add_log) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_FORUM_COPIED_PERMISSIONS', false, array($src_forum_name, implode(', ', $dest_forum_names))); + } + + $db->sql_transaction('commit'); + + return true; +} + +/** +* Get physical file listing +*/ +function filelist($rootdir, $dir = '', $type = 'gif|jpg|jpeg|png') +{ + $matches = array($dir => array()); + + // Remove initial / if present + $rootdir = (substr($rootdir, 0, 1) == '/') ? substr($rootdir, 1) : $rootdir; + // Add closing / if not present + $rootdir = ($rootdir && substr($rootdir, -1) != '/') ? $rootdir . '/' : $rootdir; + + // Remove initial / if present + $dir = (substr($dir, 0, 1) == '/') ? substr($dir, 1) : $dir; + // Add closing / if not present + $dir = ($dir && substr($dir, -1) != '/') ? $dir . '/' : $dir; + + if (!is_dir($rootdir . $dir)) + { + return $matches; + } + + $dh = @opendir($rootdir . $dir); + + if (!$dh) + { + return $matches; + } + + while (($fname = readdir($dh)) !== false) + { + if (is_file("$rootdir$dir$fname")) + { + if (filesize("$rootdir$dir$fname") && preg_match('#\.' . $type . '$#i', $fname)) + { + $matches[$dir][] = $fname; + } + } + else if ($fname[0] != '.' && is_dir("$rootdir$dir$fname")) + { + $matches += filelist($rootdir, "$dir$fname", $type); + } + } + closedir($dh); + + return $matches; +} + +/** +* Move topic(s) +*/ +function move_topics($topic_ids, $forum_id, $auto_sync = true) +{ + global $db, $phpbb_dispatcher; + + if (empty($topic_ids)) + { + return; + } + + $forum_ids = array($forum_id); + + if (!is_array($topic_ids)) + { + $topic_ids = array($topic_ids); + } + + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids) . ' + AND forum_id = ' . $forum_id; + $db->sql_query($sql); + + if ($auto_sync) + { + $sql = 'SELECT DISTINCT forum_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_ids[] = $row['forum_id']; + } + $db->sql_freeresult($result); + } + + $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); + + /** + * Perform additional actions before topics move + * + * @event core.move_topics_before_query + * @var array table_ary Array of tables from which forum_id will be updated for all rows that hold the moved topics + * @var array topic_ids Array of the moved topic ids + * @var string forum_id The forum id from where the topics are moved + * @var array forum_ids Array of the forums where the topics are moving (includes also forum_id) + * @var bool auto_sync Whether or not to perform auto sync + * @since 3.1.5-RC1 + */ + $vars = array( + 'table_ary', + 'topic_ids', + 'forum_id', + 'forum_ids', + 'auto_sync', + ); + extract($phpbb_dispatcher->trigger_event('core.move_topics_before_query', compact($vars))); + + foreach ($table_ary as $table) + { + $sql = "UPDATE $table + SET forum_id = $forum_id + WHERE " . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); + } + unset($table_ary); + + if ($auto_sync) + { + sync('forum', 'forum_id', $forum_ids, true, true); + unset($forum_ids); + } +} + +/** +* Move post(s) +*/ +function move_posts($post_ids, $topic_id, $auto_sync = true) +{ + global $db, $phpbb_dispatcher; + + if (!is_array($post_ids)) + { + $post_ids = array($post_ids); + } + + $forum_ids = array(); + $topic_ids = array($topic_id); + + $sql = 'SELECT DISTINCT topic_id, forum_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_id', $post_ids); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_ids[] = (int) $row['forum_id']; + $topic_ids[] = (int) $row['topic_id']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT forum_id + FROM ' . TOPICS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + $forum_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$forum_row) + { + trigger_error('NO_TOPIC'); + } + + /** + * Perform additional actions before moving posts + * + * @event core.move_posts_before + * @var array post_ids Array of post ids to move + * @var int topic_id The topic id the posts are moved to + * @var bool auto_sync Whether or not to perform auto sync + * @var array forum_ids Array of the forum ids the posts are moved from + * @var array topic_ids Array of the topic ids the posts are moved from + * @var array forum_row Array with the forum id of the topic the posts are moved to + * @since 3.1.7-RC1 + */ + $vars = array( + 'post_ids', + 'topic_id', + 'auto_sync', + 'forum_ids', + 'topic_ids', + 'forum_row', + ); + extract($phpbb_dispatcher->trigger_event('core.move_posts_before', compact($vars))); + + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET forum_id = ' . (int) $forum_row['forum_id'] . ", topic_id = $topic_id + WHERE " . $db->sql_in_set('post_id', $post_ids); + $db->sql_query($sql); + + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . " + SET topic_id = $topic_id, in_message = 0 + WHERE " . $db->sql_in_set('post_msg_id', $post_ids); + $db->sql_query($sql); + + /** + * Perform additional actions after moving posts + * + * @event core.move_posts_after + * @var array post_ids Array of the moved post ids + * @var int topic_id The topic id the posts are moved to + * @var bool auto_sync Whether or not to perform auto sync + * @var array forum_ids Array of the forum ids the posts are moved from + * @var array topic_ids Array of the topic ids the posts are moved from + * @var array forum_row Array with the forum id of the topic the posts are moved to + * @since 3.1.7-RC1 + */ + $vars = array( + 'post_ids', + 'topic_id', + 'auto_sync', + 'forum_ids', + 'topic_ids', + 'forum_row', + ); + extract($phpbb_dispatcher->trigger_event('core.move_posts_after', compact($vars))); + + if ($auto_sync) + { + $forum_ids[] = (int) $forum_row['forum_id']; + + sync('topic_reported', 'topic_id', $topic_ids); + sync('topic_attachment', 'topic_id', $topic_ids); + sync('topic', 'topic_id', $topic_ids, true); + sync('forum', 'forum_id', $forum_ids, true, true); + + /** + * Perform additional actions after move post sync + * + * @event core.move_posts_sync_after + * @var array post_ids Array of the moved post ids + * @var int topic_id The topic id the posts are moved to + * @var bool auto_sync Whether or not to perform auto sync + * @var array forum_ids Array of the forum ids the posts are moved from + * @var array topic_ids Array of the topic ids the posts are moved from + * @var array forum_row Array with the forum id of the topic the posts are moved to + * @since 3.1.11-RC1 + */ + $vars = array( + 'post_ids', + 'topic_id', + 'auto_sync', + 'forum_ids', + 'topic_ids', + 'forum_row', + ); + extract($phpbb_dispatcher->trigger_event('core.move_posts_sync_after', compact($vars))); + } + + // Update posted information + update_posted_info($topic_ids); +} + +/** +* Remove topic(s) +*/ +function delete_topics($where_type, $where_ids, $auto_sync = true, $post_count_sync = true, $call_delete_posts = true) +{ + global $db, $config, $phpbb_container, $phpbb_dispatcher; + + $approved_topics = 0; + $forum_ids = $topic_ids = array(); + + if ($where_type === 'range') + { + $where_clause = $where_ids; + } + else + { + $where_ids = (is_array($where_ids)) ? array_unique($where_ids) : array($where_ids); + + if (!count($where_ids)) + { + return array('topics' => 0, 'posts' => 0); + } + + $where_clause = $db->sql_in_set($where_type, $where_ids); + } + + // Making sure that delete_posts does not call delete_topics again... + $return = array( + 'posts' => ($call_delete_posts) ? delete_posts($where_type, $where_ids, false, true, $post_count_sync, false) : 0, + ); + + $sql = 'SELECT topic_id, forum_id, topic_visibility, topic_moved_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $where_clause; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_ids[] = $row['forum_id']; + $topic_ids[] = $row['topic_id']; + + if ($row['topic_visibility'] == ITEM_APPROVED && !$row['topic_moved_id']) + { + $approved_topics++; + } + } + $db->sql_freeresult($result); + + $return['topics'] = count($topic_ids); + + if (!count($topic_ids)) + { + return $return; + } + + $db->sql_transaction('begin'); + + $table_ary = array(BOOKMARKS_TABLE, TOPICS_TRACK_TABLE, TOPICS_POSTED_TABLE, POLL_VOTES_TABLE, POLL_OPTIONS_TABLE, TOPICS_WATCH_TABLE, TOPICS_TABLE); + + /** + * Perform additional actions before topic(s) deletion + * + * @event core.delete_topics_before_query + * @var array table_ary Array of tables from which all rows will be deleted that hold a topic_id occuring in topic_ids + * @var array topic_ids Array of topic ids to delete + * @since 3.1.4-RC1 + */ + $vars = array( + 'table_ary', + 'topic_ids', + ); + extract($phpbb_dispatcher->trigger_event('core.delete_topics_before_query', compact($vars))); + + foreach ($table_ary as $table) + { + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); + } + unset($table_ary); + + /** + * Perform additional actions after topic(s) deletion + * + * @event core.delete_topics_after_query + * @var array topic_ids Array of topic ids that were deleted + * @since 3.1.4-RC1 + */ + $vars = array( + 'topic_ids', + ); + extract($phpbb_dispatcher->trigger_event('core.delete_topics_after_query', compact($vars))); + + $moved_topic_ids = array(); + + // update the other forums + $sql = 'SELECT topic_id, forum_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_ids[] = $row['forum_id']; + $moved_topic_ids[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + if (count($moved_topic_ids)) + { + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $moved_topic_ids); + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + + if ($auto_sync) + { + sync('forum', 'forum_id', array_unique($forum_ids), true, true); + sync('topic_reported', $where_type, $where_ids); + } + + if ($approved_topics) + { + $config->increment('num_topics', $approved_topics * (-1), false); + } + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->delete_notifications(array( + 'notification.type.topic', + 'notification.type.approve_topic', + 'notification.type.topic_in_queue', + ), $topic_ids); + + return $return; +} + +/** +* Remove post(s) +*/ +function delete_posts($where_type, $where_ids, $auto_sync = true, $posted_sync = true, $post_count_sync = true, $call_delete_topics = true) +{ + global $db, $config, $phpbb_root_path, $phpEx, $auth, $user, $phpbb_container, $phpbb_dispatcher; + + // Notifications types to delete + $delete_notifications_types = array( + 'notification.type.quote', + 'notification.type.approve_post', + 'notification.type.post_in_queue', + 'notification.type.report_post', + ); + + /** + * Perform additional actions before post(s) deletion + * + * @event core.delete_posts_before + * @var string where_type Variable containing posts deletion mode + * @var mixed where_ids Array or comma separated list of posts ids to delete + * @var bool auto_sync Flag indicating if topics/forums should be synchronized + * @var bool posted_sync Flag indicating if topics_posted table should be resynchronized + * @var bool post_count_sync Flag indicating if posts count should be resynchronized + * @var bool call_delete_topics Flag indicating if topics having no posts should be deleted + * @var array delete_notifications_types Array with notifications types to delete + * @since 3.1.0-a4 + */ + $vars = array( + 'where_type', + 'where_ids', + 'auto_sync', + 'posted_sync', + 'post_count_sync', + 'call_delete_topics', + 'delete_notifications_types', + ); + extract($phpbb_dispatcher->trigger_event('core.delete_posts_before', compact($vars))); + + if ($where_type === 'range') + { + $where_clause = $where_ids; + } + else + { + if (is_array($where_ids)) + { + $where_ids = array_unique($where_ids); + } + else + { + $where_ids = array($where_ids); + } + + if (!count($where_ids)) + { + return false; + } + + $where_ids = array_map('intval', $where_ids); + +/* Possible code for splitting post deletion + if (count($where_ids) >= 1001) + { + // Split into chunks of 1000 + $chunks = array_chunk($where_ids, 1000); + + foreach ($chunks as $_where_ids) + { + delete_posts($where_type, $_where_ids, $auto_sync, $posted_sync, $post_count_sync, $call_delete_topics); + } + + return; + }*/ + + $where_clause = $db->sql_in_set($where_type, $where_ids); + } + + $approved_posts = 0; + $post_ids = $topic_ids = $forum_ids = $post_counts = $remove_topics = array(); + + $sql = 'SELECT post_id, poster_id, post_visibility, post_postcount, topic_id, forum_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $where_clause; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['post_id']; + $poster_ids[] = (int) $row['poster_id']; + $topic_ids[] = (int) $row['topic_id']; + $forum_ids[] = (int) $row['forum_id']; + + if ($row['post_postcount'] && $post_count_sync && $row['post_visibility'] == ITEM_APPROVED) + { + $post_counts[$row['poster_id']] = (!empty($post_counts[$row['poster_id']])) ? $post_counts[$row['poster_id']] + 1 : 1; + } + + if ($row['post_visibility'] == ITEM_APPROVED) + { + $approved_posts++; + } + } + $db->sql_freeresult($result); + + if (!count($post_ids)) + { + return false; + } + + $db->sql_transaction('begin'); + + $table_ary = array(POSTS_TABLE, REPORTS_TABLE); + + /** + * Perform additional actions during post(s) deletion before running the queries + * + * @event core.delete_posts_in_transaction_before + * @var array post_ids Array with deleted posts' ids + * @var array poster_ids Array with deleted posts' author ids + * @var array topic_ids Array with deleted posts' topic ids + * @var array forum_ids Array with deleted posts' forum ids + * @var string where_type Variable containing posts deletion mode + * @var mixed where_ids Array or comma separated list of post ids to delete + * @var array delete_notifications_types Array with notifications types to delete + * @var array table_ary Array with table names to delete data from + * @since 3.1.7-RC1 + */ + $vars = array( + 'post_ids', + 'poster_ids', + 'topic_ids', + 'forum_ids', + 'where_type', + 'where_ids', + 'delete_notifications_types', + 'table_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.delete_posts_in_transaction_before', compact($vars))); + + foreach ($table_ary as $table) + { + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set('post_id', $post_ids); + $db->sql_query($sql); + } + unset($table_ary); + + // Adjust users post counts + if (count($post_counts) && $post_count_sync) + { + foreach ($post_counts as $poster_id => $substract) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = 0 + WHERE user_id = ' . $poster_id . ' + AND user_posts < ' . $substract; + $db->sql_query($sql); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = user_posts - ' . $substract . ' + WHERE user_id = ' . $poster_id . ' + AND user_posts >= ' . $substract; + $db->sql_query($sql); + } + } + + // Remove topics now having no posts? + if (count($topic_ids)) + { + $sql = 'SELECT topic_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . ' + GROUP BY topic_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $remove_topics[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + // Actually, those not within remove_topics should be removed. ;) + $remove_topics = array_diff($topic_ids, $remove_topics); + } + + // Remove the message from the search index + $search_type = $config['search_type']; + + if (!class_exists($search_type)) + { + trigger_error('NO_SUCH_SEARCH_MODULE'); + } + + $error = false; + $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher); + + if ($error) + { + trigger_error($error); + } + + $search->index_remove($post_ids, $poster_ids, $forum_ids); + + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $attachment_manager->delete('post', $post_ids, false); + unset($attachment_manager); + + /** + * Perform additional actions during post(s) deletion + * + * @event core.delete_posts_in_transaction + * @var array post_ids Array with deleted posts' ids + * @var array poster_ids Array with deleted posts' author ids + * @var array topic_ids Array with deleted posts' topic ids + * @var array forum_ids Array with deleted posts' forum ids + * @var string where_type Variable containing posts deletion mode + * @var mixed where_ids Array or comma separated list of posts ids to delete + * @var array delete_notifications_types Array with notifications types to delete + * @since 3.1.0-a4 + */ + $vars = array( + 'post_ids', + 'poster_ids', + 'topic_ids', + 'forum_ids', + 'where_type', + 'where_ids', + 'delete_notifications_types', + ); + extract($phpbb_dispatcher->trigger_event('core.delete_posts_in_transaction', compact($vars))); + + $db->sql_transaction('commit'); + + /** + * Perform additional actions after post(s) deletion + * + * @event core.delete_posts_after + * @var array post_ids Array with deleted posts' ids + * @var array poster_ids Array with deleted posts' author ids + * @var array topic_ids Array with deleted posts' topic ids + * @var array forum_ids Array with deleted posts' forum ids + * @var string where_type Variable containing posts deletion mode + * @var mixed where_ids Array or comma separated list of posts ids to delete + * @var array delete_notifications_types Array with notifications types to delete + * @since 3.1.0-a4 + */ + $vars = array( + 'post_ids', + 'poster_ids', + 'topic_ids', + 'forum_ids', + 'where_type', + 'where_ids', + 'delete_notifications_types', + ); + extract($phpbb_dispatcher->trigger_event('core.delete_posts_after', compact($vars))); + + // Resync topics_posted table + if ($posted_sync) + { + update_posted_info($topic_ids); + } + + if ($auto_sync) + { + sync('topic_reported', 'topic_id', $topic_ids); + sync('topic', 'topic_id', $topic_ids, true); + sync('forum', 'forum_id', $forum_ids, true, true); + } + + if ($approved_posts && $post_count_sync) + { + $config->increment('num_posts', $approved_posts * (-1), false); + } + + // We actually remove topics now to not be inconsistent (the delete_topics function calls this function too) + if (count($remove_topics) && $call_delete_topics) + { + delete_topics('topic_id', $remove_topics, $auto_sync, $post_count_sync, false); + } + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->delete_notifications($delete_notifications_types, $post_ids); + + return count($post_ids); +} + +/** +* Delete Attachments +* +* @deprecated 3.2.0-a1 (To be removed: 3.4.0) +* +* @param string $mode can be: post|message|topic|attach|user +* @param mixed $ids can be: post_ids, message_ids, topic_ids, attach_ids, user_ids +* @param bool $resync set this to false if you are deleting posts or topics +*/ +function delete_attachments($mode, $ids, $resync = true) +{ + global $phpbb_container; + + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $num_deleted = $attachment_manager->delete($mode, $ids, $resync); + + unset($attachment_manager); + + return $num_deleted; +} + +/** +* Deletes shadow topics pointing to a specified forum. +* +* @param int $forum_id The forum id +* @param string $sql_more Additional WHERE statement, e.g. t.topic_time < (time() - 1234) +* @param bool $auto_sync Will call sync() if this is true +* +* @return array Array with affected forums +*/ +function delete_topic_shadows($forum_id, $sql_more = '', $auto_sync = true) +{ + global $db; + + if (!$forum_id) + { + // Nothing to do. + return; + } + + // Set of affected forums we have to resync + $sync_forum_ids = array(); + + // Amount of topics we select and delete at once. + $batch_size = 500; + + do + { + $sql = 'SELECT t2.forum_id, t2.topic_id + FROM ' . TOPICS_TABLE . ' t2, ' . TOPICS_TABLE . ' t + WHERE t2.topic_moved_id = t.topic_id + AND t.forum_id = ' . (int) $forum_id . ' + ' . (($sql_more) ? 'AND ' . $sql_more : ''); + $result = $db->sql_query_limit($sql, $batch_size); + + $topic_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topic_ids[] = (int) $row['topic_id']; + + $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id']; + } + $db->sql_freeresult($result); + + if (!empty($topic_ids)) + { + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); + } + } + while (count($topic_ids) == $batch_size); + + if ($auto_sync) + { + sync('forum', 'forum_id', $sync_forum_ids, true, true); + } + + return $sync_forum_ids; +} + +/** +* Update/Sync posted information for topics +*/ +function update_posted_info(&$topic_ids) +{ + global $db, $config; + + if (empty($topic_ids) || !$config['load_db_track']) + { + return; + } + + // First of all, let us remove any posted information for these topics + $sql = 'DELETE FROM ' . TOPICS_POSTED_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); + + // Now, let us collect the user/topic combos for rebuilding the information + $sql = 'SELECT poster_id, topic_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . ' + AND poster_id <> ' . ANONYMOUS . ' + GROUP BY poster_id, topic_id'; + $result = $db->sql_query($sql); + + $posted = array(); + while ($row = $db->sql_fetchrow($result)) + { + // Add as key to make them unique (grouping by) and circumvent empty keys on array_unique + $posted[$row['poster_id']][] = $row['topic_id']; + } + $db->sql_freeresult($result); + + // Now add the information... + $sql_ary = array(); + foreach ($posted as $user_id => $topic_row) + { + foreach ($topic_row as $topic_id) + { + $sql_ary[] = array( + 'user_id' => (int) $user_id, + 'topic_id' => (int) $topic_id, + 'topic_posted' => 1, + ); + } + } + unset($posted); + + $db->sql_multi_insert(TOPICS_POSTED_TABLE, $sql_ary); +} + +/** +* Delete attached file +* +* @deprecated 3.2.0-a1 (To be removed: 3.4.0) +*/ +function phpbb_unlink($filename, $mode = 'file', $entry_removed = false) +{ + global $phpbb_container; + + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $unlink = $attachment_manager->unlink($filename, $mode, $entry_removed); + unset($attachment_manager); + + return $unlink; +} + +/** +* All-encompasing sync function +* +* Exaples: +* +* sync('topic', 'topic_id', 123); // resync topic #123 +* sync('topic', 'forum_id', array(2, 3)); // resync topics from forum #2 and #3 +* sync('topic'); // resync all topics +* sync('topic', 'range', 'topic_id BETWEEN 1 AND 60'); // resync a range of topics/forums (only available for 'topic' and 'forum' modes) +* +* +* Modes: +* - forum Resync complete forum +* - topic Resync topics +* - topic_moved Removes topic shadows that would be in the same forum as the topic they link to +* - topic_visibility Resyncs the topic_visibility flag according to the status of the first post +* - post_reported Resyncs the post_reported flag, relying on actual reports +* - topic_reported Resyncs the topic_reported flag, relying on post_reported flags +* - post_attachement Same as post_reported, but with attachment flags +* - topic_attachement Same as topic_reported, but with attachment flags +*/ +function sync($mode, $where_type = '', $where_ids = '', $resync_parents = false, $sync_extra = false) +{ + global $db; + + if (is_array($where_ids)) + { + $where_ids = array_unique($where_ids); + $where_ids = array_map('intval', $where_ids); + } + else if ($where_type != 'range') + { + $where_ids = ($where_ids) ? array((int) $where_ids) : array(); + } + + if ($mode == 'forum' || $mode == 'topic' || $mode == 'topic_visibility' || $mode == 'topic_reported' || $mode == 'post_reported') + { + if (!$where_type) + { + $where_sql = ''; + $where_sql_and = 'WHERE'; + } + else if ($where_type == 'range') + { + // Only check a range of topics/forums. For instance: 'topic_id BETWEEN 1 AND 60' + $where_sql = 'WHERE (' . $mode[0] . ".$where_ids)"; + $where_sql_and = $where_sql . "\n\tAND"; + } + else + { + // Do not sync the "global forum" + $where_ids = array_diff($where_ids, array(0)); + + if (!count($where_ids)) + { + // Empty array with IDs. This means that we don't have any work to do. Just return. + return; + } + + // Limit the topics/forums we are syncing, use specific topic/forum IDs. + // $where_type contains the field for the where clause (forum_id, topic_id) + $where_sql = 'WHERE ' . $db->sql_in_set($mode[0] . '.' . $where_type, $where_ids); + $where_sql_and = $where_sql . "\n\tAND"; + } + } + else + { + if (!count($where_ids)) + { + return; + } + + // $where_type contains the field for the where clause (forum_id, topic_id) + $where_sql = 'WHERE ' . $db->sql_in_set($mode[0] . '.' . $where_type, $where_ids); + $where_sql_and = $where_sql . "\n\tAND"; + } + + switch ($mode) + { + case 'topic_moved': + $db->sql_transaction('begin'); + switch ($db->get_sql_layer()) + { + case 'mysql4': + case 'mysqli': + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + USING ' . TOPICS_TABLE . ' t1, ' . TOPICS_TABLE . " t2 + WHERE t1.topic_moved_id = t2.topic_id + AND t1.forum_id = t2.forum_id"; + $db->sql_query($sql); + break; + + default: + $sql = 'SELECT t1.topic_id + FROM ' .TOPICS_TABLE . ' t1, ' . TOPICS_TABLE . " t2 + WHERE t1.topic_moved_id = t2.topic_id + AND t1.forum_id = t2.forum_id"; + $result = $db->sql_query($sql); + + $topic_id_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topic_id_ary[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + if (!count($topic_id_ary)) + { + return; + } + + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_id_ary); + $db->sql_query($sql); + + break; + } + + $db->sql_transaction('commit'); + break; + + case 'topic_visibility': + + $db->sql_transaction('begin'); + + $sql = 'SELECT t.topic_id, p.post_visibility + FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + $where_sql_and p.topic_id = t.topic_id + AND p.post_visibility = " . ITEM_APPROVED; + $result = $db->sql_query($sql); + + $topics_approved = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topics_approved[] = (int) $row['topic_id']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT t.topic_id, p.post_visibility + FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + $where_sql_and " . $db->sql_in_set('t.topic_id', $topics_approved, true, true) . ' + AND p.topic_id = t.topic_id + AND p.post_visibility = ' . ITEM_DELETED; + $result = $db->sql_query($sql); + + $topics_softdeleted = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topics_softdeleted[] = (int) $row['topic_id']; + } + $db->sql_freeresult($result); + + $topics_softdeleted = array_diff($topics_softdeleted, $topics_approved); + $topics_not_unapproved = array_merge($topics_softdeleted, $topics_approved); + + $update_ary = array( + ITEM_UNAPPROVED => (!empty($topics_not_unapproved)) ? $where_sql_and . ' ' . $db->sql_in_set('topic_id', $topics_not_unapproved, true) : '', + ITEM_APPROVED => (!empty($topics_approved)) ? ' WHERE ' . $db->sql_in_set('topic_id', $topics_approved) : '', + ITEM_DELETED => (!empty($topics_softdeleted)) ? ' WHERE ' . $db->sql_in_set('topic_id', $topics_softdeleted) : '', + ); + + foreach ($update_ary as $visibility => $sql_where) + { + if ($sql_where) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_visibility = ' . $visibility . ' + ' . $sql_where; + $db->sql_query($sql); + } + } + + $db->sql_transaction('commit'); + break; + + case 'post_reported': + $post_ids = $post_reported = array(); + + $db->sql_transaction('begin'); + + $sql = 'SELECT p.post_id, p.post_reported + FROM ' . POSTS_TABLE . " p + $where_sql + GROUP BY p.post_id, p.post_reported"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $post_ids[$row['post_id']] = $row['post_id']; + if ($row['post_reported']) + { + $post_reported[$row['post_id']] = 1; + } + } + $db->sql_freeresult($result); + + $sql = 'SELECT DISTINCT(post_id) + FROM ' . REPORTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_id', $post_ids) . ' + AND report_closed = 0'; + $result = $db->sql_query($sql); + + $post_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + if (!isset($post_reported[$row['post_id']])) + { + $post_ids[] = $row['post_id']; + } + else + { + unset($post_reported[$row['post_id']]); + } + } + $db->sql_freeresult($result); + + // $post_reported should be empty by now, if it's not it contains + // posts that are falsely flagged as reported + foreach ($post_reported as $post_id => $void) + { + $post_ids[] = $post_id; + } + + if (count($post_ids)) + { + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_reported = 1 - post_reported + WHERE ' . $db->sql_in_set('post_id', $post_ids); + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + break; + + case 'topic_reported': + if ($sync_extra) + { + sync('post_reported', $where_type, $where_ids); + } + + $topic_ids = $topic_reported = array(); + + $db->sql_transaction('begin'); + + $sql = 'SELECT DISTINCT(t.topic_id) + FROM ' . POSTS_TABLE . " t + $where_sql_and t.post_reported = 1"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_reported[$row['topic_id']] = 1; + } + $db->sql_freeresult($result); + + $sql = 'SELECT t.topic_id, t.topic_reported + FROM ' . TOPICS_TABLE . " t + $where_sql"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['topic_reported'] ^ isset($topic_reported[$row['topic_id']])) + { + $topic_ids[] = $row['topic_id']; + } + } + $db->sql_freeresult($result); + + if (count($topic_ids)) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_reported = 1 - topic_reported + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + break; + + case 'post_attachment': + $post_ids = $post_attachment = array(); + + $db->sql_transaction('begin'); + + $sql = 'SELECT p.post_id, p.post_attachment + FROM ' . POSTS_TABLE . " p + $where_sql + GROUP BY p.post_id, p.post_attachment"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $post_ids[$row['post_id']] = $row['post_id']; + if ($row['post_attachment']) + { + $post_attachment[$row['post_id']] = 1; + } + } + $db->sql_freeresult($result); + + $sql = 'SELECT DISTINCT(post_msg_id) + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_msg_id', $post_ids) . ' + AND in_message = 0'; + $result = $db->sql_query($sql); + + $post_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + if (!isset($post_attachment[$row['post_msg_id']])) + { + $post_ids[] = $row['post_msg_id']; + } + else + { + unset($post_attachment[$row['post_msg_id']]); + } + } + $db->sql_freeresult($result); + + // $post_attachment should be empty by now, if it's not it contains + // posts that are falsely flagged as having attachments + foreach ($post_attachment as $post_id => $void) + { + $post_ids[] = $post_id; + } + + if (count($post_ids)) + { + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_attachment = 1 - post_attachment + WHERE ' . $db->sql_in_set('post_id', $post_ids); + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + break; + + case 'topic_attachment': + if ($sync_extra) + { + sync('post_attachment', $where_type, $where_ids); + } + + $topic_ids = $topic_attachment = array(); + + $db->sql_transaction('begin'); + + $sql = 'SELECT DISTINCT(t.topic_id) + FROM ' . POSTS_TABLE . " t + $where_sql_and t.post_attachment = 1"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_attachment[$row['topic_id']] = 1; + } + $db->sql_freeresult($result); + + $sql = 'SELECT t.topic_id, t.topic_attachment + FROM ' . TOPICS_TABLE . " t + $where_sql"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['topic_attachment'] ^ isset($topic_attachment[$row['topic_id']])) + { + $topic_ids[] = $row['topic_id']; + } + } + $db->sql_freeresult($result); + + if (count($topic_ids)) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_attachment = 1 - topic_attachment + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + + break; + + case 'forum': + + $db->sql_transaction('begin'); + + // 1: Get the list of all forums + $sql = 'SELECT f.* + FROM ' . FORUMS_TABLE . " f + $where_sql"; + $result = $db->sql_query($sql); + + $forum_data = $forum_ids = $post_ids = $last_post_id = $post_info = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['forum_type'] == FORUM_LINK) + { + continue; + } + + $forum_id = (int) $row['forum_id']; + $forum_ids[$forum_id] = $forum_id; + + $forum_data[$forum_id] = $row; + if ($sync_extra) + { + $forum_data[$forum_id]['posts_approved'] = 0; + $forum_data[$forum_id]['posts_unapproved'] = 0; + $forum_data[$forum_id]['posts_softdeleted'] = 0; + $forum_data[$forum_id]['topics_approved'] = 0; + $forum_data[$forum_id]['topics_unapproved'] = 0; + $forum_data[$forum_id]['topics_softdeleted'] = 0; + } + $forum_data[$forum_id]['last_post_id'] = 0; + $forum_data[$forum_id]['last_post_subject'] = ''; + $forum_data[$forum_id]['last_post_time'] = 0; + $forum_data[$forum_id]['last_poster_id'] = 0; + $forum_data[$forum_id]['last_poster_name'] = ''; + $forum_data[$forum_id]['last_poster_colour'] = ''; + } + $db->sql_freeresult($result); + + if (!count($forum_ids)) + { + break; + } + + $forum_ids = array_values($forum_ids); + + // 2: Get topic counts for each forum (optional) + if ($sync_extra) + { + $sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS total_topics + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids) . ' + GROUP BY forum_id, topic_visibility'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_id = (int) $row['forum_id']; + + if ($row['topic_visibility'] == ITEM_APPROVED) + { + $forum_data[$forum_id]['topics_approved'] = $row['total_topics']; + } + else if ($row['topic_visibility'] == ITEM_UNAPPROVED || $row['topic_visibility'] == ITEM_REAPPROVE) + { + $forum_data[$forum_id]['topics_unapproved'] = $row['total_topics']; + } + else if ($row['topic_visibility'] == ITEM_DELETED) + { + $forum_data[$forum_id]['topics_softdeleted'] = $row['total_topics']; + } + } + $db->sql_freeresult($result); + } + + // 3: Get post count for each forum (optional) + if ($sync_extra) + { + if (count($forum_ids) == 1) + { + $sql = 'SELECT SUM(t.topic_posts_approved) AS forum_posts_approved, SUM(t.topic_posts_unapproved) AS forum_posts_unapproved, SUM(t.topic_posts_softdeleted) AS forum_posts_softdeleted + FROM ' . TOPICS_TABLE . ' t + WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . ' + AND t.topic_status <> ' . ITEM_MOVED; + } + else + { + $sql = 'SELECT t.forum_id, SUM(t.topic_posts_approved) AS forum_posts_approved, SUM(t.topic_posts_unapproved) AS forum_posts_unapproved, SUM(t.topic_posts_softdeleted) AS forum_posts_softdeleted + FROM ' . TOPICS_TABLE . ' t + WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . ' + AND t.topic_status <> ' . ITEM_MOVED . ' + GROUP BY t.forum_id'; + } + + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_id = (count($forum_ids) == 1) ? (int) $forum_ids[0] : (int) $row['forum_id']; + + $forum_data[$forum_id]['posts_approved'] = (int) $row['forum_posts_approved']; + $forum_data[$forum_id]['posts_unapproved'] = (int) $row['forum_posts_unapproved']; + $forum_data[$forum_id]['posts_softdeleted'] = (int) $row['forum_posts_softdeleted']; + } + $db->sql_freeresult($result); + } + + // 4: Get last_post_id for each forum + if (count($forum_ids) == 1) + { + $sql = 'SELECT MAX(t.topic_last_post_id) as last_post_id + FROM ' . TOPICS_TABLE . ' t + WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . ' + AND t.topic_visibility = ' . ITEM_APPROVED; + } + else + { + $sql = 'SELECT t.forum_id, MAX(t.topic_last_post_id) as last_post_id + FROM ' . TOPICS_TABLE . ' t + WHERE ' . $db->sql_in_set('t.forum_id', $forum_ids) . ' + AND t.topic_visibility = ' . ITEM_APPROVED . ' + GROUP BY t.forum_id'; + } + + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_id = (count($forum_ids) == 1) ? (int) $forum_ids[0] : (int) $row['forum_id']; + + $forum_data[$forum_id]['last_post_id'] = (int) $row['last_post_id']; + + $post_ids[] = $row['last_post_id']; + } + $db->sql_freeresult($result); + + // 5: Retrieve last_post infos + if (count($post_ids)) + { + $sql = 'SELECT p.post_id, p.poster_id, p.post_subject, p.post_time, p.post_username, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . ' + AND p.poster_id = u.user_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $post_info[$row['post_id']] = $row; + } + $db->sql_freeresult($result); + + foreach ($forum_data as $forum_id => $data) + { + if ($data['last_post_id']) + { + if (isset($post_info[$data['last_post_id']])) + { + $forum_data[$forum_id]['last_post_subject'] = $post_info[$data['last_post_id']]['post_subject']; + $forum_data[$forum_id]['last_post_time'] = $post_info[$data['last_post_id']]['post_time']; + $forum_data[$forum_id]['last_poster_id'] = $post_info[$data['last_post_id']]['poster_id']; + $forum_data[$forum_id]['last_poster_name'] = ($post_info[$data['last_post_id']]['poster_id'] != ANONYMOUS) ? $post_info[$data['last_post_id']]['username'] : $post_info[$data['last_post_id']]['post_username']; + $forum_data[$forum_id]['last_poster_colour'] = $post_info[$data['last_post_id']]['user_colour']; + } + else + { + // For some reason we did not find the post in the db + $forum_data[$forum_id]['last_post_id'] = 0; + $forum_data[$forum_id]['last_post_subject'] = ''; + $forum_data[$forum_id]['last_post_time'] = 0; + $forum_data[$forum_id]['last_poster_id'] = 0; + $forum_data[$forum_id]['last_poster_name'] = ''; + $forum_data[$forum_id]['last_poster_colour'] = ''; + } + } + } + unset($post_info); + } + + // 6: Now do that thing + $fieldnames = array('last_post_id', 'last_post_subject', 'last_post_time', 'last_poster_id', 'last_poster_name', 'last_poster_colour'); + + if ($sync_extra) + { + array_push($fieldnames, 'posts_approved', 'posts_unapproved', 'posts_softdeleted', 'topics_approved', 'topics_unapproved', 'topics_softdeleted'); + } + + foreach ($forum_data as $forum_id => $row) + { + $sql_ary = array(); + + foreach ($fieldnames as $fieldname) + { + if ($row['forum_' . $fieldname] != $row[$fieldname]) + { + if (preg_match('#(name|colour|subject)$#', $fieldname)) + { + $sql_ary['forum_' . $fieldname] = (string) $row[$fieldname]; + } + else + { + $sql_ary['forum_' . $fieldname] = (int) $row[$fieldname]; + } + } + } + + if (count($sql_ary)) + { + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE forum_id = ' . $forum_id; + $db->sql_query($sql); + } + } + + $db->sql_transaction('commit'); + break; + + case 'topic': + $topic_data = $post_ids = $resync_forums = $delete_topics = $delete_posts = $moved_topics = array(); + + $db->sql_transaction('begin'); + + $sql = 'SELECT t.topic_id, t.forum_id, t.topic_moved_id, t.topic_visibility, ' . (($sync_extra) ? 't.topic_attachment, t.topic_reported, ' : '') . 't.topic_poster, t.topic_time, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_first_post_id, t.topic_first_poster_name, t.topic_first_poster_colour, t.topic_last_post_id, t.topic_last_post_subject, t.topic_last_poster_id, t.topic_last_poster_name, t.topic_last_poster_colour, t.topic_last_post_time + FROM ' . TOPICS_TABLE . " t + $where_sql"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['topic_moved_id']) + { + $moved_topics[] = $row['topic_id']; + continue; + } + + $topic_id = (int) $row['topic_id']; + $topic_data[$topic_id] = $row; + $topic_data[$topic_id]['visibility'] = ITEM_UNAPPROVED; + $topic_data[$topic_id]['posts_approved'] = 0; + $topic_data[$topic_id]['posts_unapproved'] = 0; + $topic_data[$topic_id]['posts_softdeleted'] = 0; + $topic_data[$topic_id]['first_post_id'] = 0; + $topic_data[$topic_id]['last_post_id'] = 0; + unset($topic_data[$topic_id]['topic_id']); + + // This array holds all topic_ids + $delete_topics[$topic_id] = ''; + + if ($sync_extra) + { + $topic_data[$topic_id]['reported'] = 0; + $topic_data[$topic_id]['attachment'] = 0; + } + } + $db->sql_freeresult($result); + + // Use "t" as table alias because of the $where_sql clause + // NOTE: 't.post_visibility' in the GROUP BY is causing a major slowdown. + $sql = 'SELECT t.topic_id, t.post_visibility, COUNT(t.post_id) AS total_posts, MIN(t.post_id) AS first_post_id, MAX(t.post_id) AS last_post_id + FROM ' . POSTS_TABLE . " t + $where_sql + GROUP BY t.topic_id, t.post_visibility"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_id = (int) $row['topic_id']; + + $row['first_post_id'] = (int) $row['first_post_id']; + $row['last_post_id'] = (int) $row['last_post_id']; + + if (!isset($topic_data[$topic_id])) + { + // Hey, these posts come from a topic that does not exist + $delete_posts[$topic_id] = ''; + } + else + { + // Unset the corresponding entry in $delete_topics + // When we'll be done, only topics with no posts will remain + unset($delete_topics[$topic_id]); + + if ($row['post_visibility'] == ITEM_APPROVED) + { + $topic_data[$topic_id]['posts_approved'] = $row['total_posts']; + } + else if ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) + { + $topic_data[$topic_id]['posts_unapproved'] = $row['total_posts']; + } + else if ($row['post_visibility'] == ITEM_DELETED) + { + $topic_data[$topic_id]['posts_softdeleted'] = $row['total_posts']; + } + + if ($row['post_visibility'] == ITEM_APPROVED) + { + $topic_data[$topic_id]['visibility'] = ITEM_APPROVED; + $topic_data[$topic_id]['first_post_id'] = $row['first_post_id']; + $topic_data[$topic_id]['last_post_id'] = $row['last_post_id']; + } + else if ($topic_data[$topic_id]['visibility'] != ITEM_APPROVED) + { + // If there is no approved post, we take the min/max of the other visibilities + // for the last and first post info, because it is only visible to moderators anyway + $topic_data[$topic_id]['first_post_id'] = (!empty($topic_data[$topic_id]['first_post_id'])) ? min($topic_data[$topic_id]['first_post_id'], $row['first_post_id']) : $row['first_post_id']; + $topic_data[$topic_id]['last_post_id'] = max($topic_data[$topic_id]['last_post_id'], $row['last_post_id']); + + if ($topic_data[$topic_id]['visibility'] == ITEM_UNAPPROVED || $topic_data[$topic_id]['visibility'] == ITEM_REAPPROVE) + { + // Soft delete status is stronger than unapproved. + $topic_data[$topic_id]['visibility'] = $row['post_visibility']; + } + } + } + } + $db->sql_freeresult($result); + + foreach ($topic_data as $topic_id => $row) + { + $post_ids[] = $row['first_post_id']; + if ($row['first_post_id'] != $row['last_post_id']) + { + $post_ids[] = $row['last_post_id']; + } + } + + // Now we delete empty topics and orphan posts + if (count($delete_posts)) + { + delete_posts('topic_id', array_keys($delete_posts), false); + unset($delete_posts); + } + + if (!count($topic_data)) + { + // If we get there, topic ids were invalid or topics did not contain any posts + delete_topics($where_type, $where_ids, true); + return; + } + + if (count($delete_topics)) + { + $delete_topic_ids = array(); + foreach ($delete_topics as $topic_id => $void) + { + unset($topic_data[$topic_id]); + $delete_topic_ids[] = $topic_id; + } + + delete_topics('topic_id', $delete_topic_ids, false); + unset($delete_topics, $delete_topic_ids); + } + + $sql = 'SELECT p.post_id, p.topic_id, p.post_visibility, p.poster_id, p.post_subject, p.post_username, p.post_time, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . ' + AND u.user_id = p.poster_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_id = intval($row['topic_id']); + + if ($row['post_id'] == $topic_data[$topic_id]['first_post_id']) + { + $topic_data[$topic_id]['time'] = $row['post_time']; + $topic_data[$topic_id]['poster'] = $row['poster_id']; + $topic_data[$topic_id]['first_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username']; + $topic_data[$topic_id]['first_poster_colour'] = $row['user_colour']; + } + + if ($row['post_id'] == $topic_data[$topic_id]['last_post_id']) + { + $topic_data[$topic_id]['last_poster_id'] = $row['poster_id']; + $topic_data[$topic_id]['last_post_subject'] = $row['post_subject']; + $topic_data[$topic_id]['last_post_time'] = $row['post_time']; + $topic_data[$topic_id]['last_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username']; + $topic_data[$topic_id]['last_poster_colour'] = $row['user_colour']; + } + } + $db->sql_freeresult($result); + + // Make sure shadow topics do link to existing topics + if (count($moved_topics)) + { + $delete_topics = array(); + + $sql = 'SELECT t1.topic_id, t1.topic_moved_id + FROM ' . TOPICS_TABLE . ' t1 + LEFT JOIN ' . TOPICS_TABLE . ' t2 ON (t2.topic_id = t1.topic_moved_id) + WHERE ' . $db->sql_in_set('t1.topic_id', $moved_topics) . ' + AND t2.topic_id IS NULL'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $delete_topics[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + if (count($delete_topics)) + { + delete_topics('topic_id', $delete_topics, false); + } + unset($delete_topics); + + // Make sure shadow topics having no last post data being updated (this only rarely happens...) + $sql = 'SELECT topic_id, topic_moved_id, topic_last_post_id, topic_first_post_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $moved_topics) . ' + AND topic_last_post_time = 0'; + $result = $db->sql_query($sql); + + $shadow_topic_data = $post_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $shadow_topic_data[$row['topic_moved_id']] = $row; + $post_ids[] = $row['topic_last_post_id']; + $post_ids[] = $row['topic_first_post_id']; + } + $db->sql_freeresult($result); + + $sync_shadow_topics = array(); + if (count($post_ids)) + { + $sql = 'SELECT p.post_id, p.topic_id, p.post_visibility, p.poster_id, p.post_subject, p.post_username, p.post_time, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . ' + AND u.user_id = p.poster_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_id = (int) $row['topic_id']; + + // Ok, there should be a shadow topic. If there isn't, then there's something wrong with the db. + // However, there's not much we can do about it. + if (!empty($shadow_topic_data[$topic_id])) + { + if ($row['post_id'] == $shadow_topic_data[$topic_id]['topic_first_post_id']) + { + $orig_topic_id = $shadow_topic_data[$topic_id]['topic_id']; + + if (!isset($sync_shadow_topics[$orig_topic_id])) + { + $sync_shadow_topics[$orig_topic_id] = array(); + } + + $sync_shadow_topics[$orig_topic_id]['topic_time'] = $row['post_time']; + $sync_shadow_topics[$orig_topic_id]['topic_poster'] = $row['poster_id']; + $sync_shadow_topics[$orig_topic_id]['topic_first_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username']; + $sync_shadow_topics[$orig_topic_id]['topic_first_poster_colour'] = $row['user_colour']; + } + + if ($row['post_id'] == $shadow_topic_data[$topic_id]['topic_last_post_id']) + { + $orig_topic_id = $shadow_topic_data[$topic_id]['topic_id']; + + if (!isset($sync_shadow_topics[$orig_topic_id])) + { + $sync_shadow_topics[$orig_topic_id] = array(); + } + + $sync_shadow_topics[$orig_topic_id]['topic_last_poster_id'] = $row['poster_id']; + $sync_shadow_topics[$orig_topic_id]['topic_last_post_subject'] = $row['post_subject']; + $sync_shadow_topics[$orig_topic_id]['topic_last_post_time'] = $row['post_time']; + $sync_shadow_topics[$orig_topic_id]['topic_last_poster_name'] = ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username']; + $sync_shadow_topics[$orig_topic_id]['topic_last_poster_colour'] = $row['user_colour']; + } + } + } + $db->sql_freeresult($result); + + $shadow_topic_data = array(); + + // Update the information we collected + if (count($sync_shadow_topics)) + { + foreach ($sync_shadow_topics as $sync_topic_id => $sql_ary) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE topic_id = ' . $sync_topic_id; + $db->sql_query($sql); + } + } + } + + unset($sync_shadow_topics, $shadow_topic_data); + } + + // These are fields that will be synchronised + $fieldnames = array('time', 'visibility', 'posts_approved', 'posts_unapproved', 'posts_softdeleted', 'poster', 'first_post_id', 'first_poster_name', 'first_poster_colour', 'last_post_id', 'last_post_subject', 'last_post_time', 'last_poster_id', 'last_poster_name', 'last_poster_colour'); + + if ($sync_extra) + { + // This routine assumes that post_reported values are correct + // if they are not, use sync('post_reported') first + $sql = 'SELECT t.topic_id, p.post_id + FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + $where_sql_and p.topic_id = t.topic_id + AND p.post_reported = 1 + GROUP BY t.topic_id, p.post_id"; + $result = $db->sql_query($sql); + + $fieldnames[] = 'reported'; + while ($row = $db->sql_fetchrow($result)) + { + $topic_data[intval($row['topic_id'])]['reported'] = 1; + } + $db->sql_freeresult($result); + + // This routine assumes that post_attachment values are correct + // if they are not, use sync('post_attachment') first + $sql = 'SELECT t.topic_id, p.post_id + FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + $where_sql_and p.topic_id = t.topic_id + AND p.post_attachment = 1 + GROUP BY t.topic_id, p.post_id"; + $result = $db->sql_query($sql); + + $fieldnames[] = 'attachment'; + while ($row = $db->sql_fetchrow($result)) + { + $topic_data[intval($row['topic_id'])]['attachment'] = 1; + } + $db->sql_freeresult($result); + } + + foreach ($topic_data as $topic_id => $row) + { + $sql_ary = array(); + + foreach ($fieldnames as $fieldname) + { + if (isset($row[$fieldname]) && isset($row['topic_' . $fieldname]) && $row['topic_' . $fieldname] != $row[$fieldname]) + { + $sql_ary['topic_' . $fieldname] = $row[$fieldname]; + } + } + + if (count($sql_ary)) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE topic_id = ' . $topic_id; + $db->sql_query($sql); + + $resync_forums[$row['forum_id']] = $row['forum_id']; + } + } + unset($topic_data); + + $db->sql_transaction('commit'); + + // if some topics have been resync'ed then resync parent forums + // except when we're only syncing a range, we don't want to sync forums during + // batch processing. + if ($resync_parents && count($resync_forums) && $where_type != 'range') + { + sync('forum', 'forum_id', array_values($resync_forums), true, true); + } + break; + } + + return; +} + +/** +* Prune function +*/ +function prune($forum_id, $prune_mode, $prune_date, $prune_flags = 0, $auto_sync = true, $prune_limit = 0) +{ + global $db, $phpbb_dispatcher; + + if (!is_array($forum_id)) + { + $forum_id = array($forum_id); + } + + if (!count($forum_id)) + { + return; + } + + $sql_and = ''; + + if (!($prune_flags & FORUM_FLAG_PRUNE_ANNOUNCE)) + { + $sql_and .= ' AND topic_type <> ' . POST_ANNOUNCE; + $sql_and .= ' AND topic_type <> ' . POST_GLOBAL; + } + + if (!($prune_flags & FORUM_FLAG_PRUNE_STICKY)) + { + $sql_and .= ' AND topic_type <> ' . POST_STICKY; + } + + if ($prune_mode == 'posted') + { + $sql_and .= " AND topic_last_post_time < $prune_date"; + } + + if ($prune_mode == 'viewed') + { + $sql_and .= " AND topic_last_view_time < $prune_date"; + } + + if ($prune_mode == 'shadow') + { + $sql_and .= ' AND topic_status = ' . ITEM_MOVED . " AND topic_last_post_time < $prune_date"; + } + + /** + * Use this event to modify the SQL that selects topics to be pruned + * + * @event core.prune_sql + * @var string forum_id The forum id + * @var string prune_mode The prune mode + * @var string prune_date The prune date + * @var int prune_flags The prune flags + * @var bool auto_sync Whether or not to perform auto sync + * @var string sql_and SQL text appended to where clause + * @var int prune_limit The prune limit + * @since 3.1.3-RC1 + * @changed 3.1.10-RC1 Added prune_limit + */ + $vars = array( + 'forum_id', + 'prune_mode', + 'prune_date', + 'prune_flags', + 'auto_sync', + 'sql_and', + 'prune_limit', + ); + extract($phpbb_dispatcher->trigger_event('core.prune_sql', compact($vars))); + + $sql = 'SELECT topic_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_id) . " + AND poll_start = 0 + $sql_and"; + $result = $db->sql_query_limit($sql, $prune_limit); + + $topic_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topic_list[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + if ($prune_flags & FORUM_FLAG_PRUNE_POLL) + { + $sql = 'SELECT topic_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_id) . " + AND poll_start > 0 + AND poll_last_vote < $prune_date + $sql_and"; + $result = $db->sql_query_limit($sql, $prune_limit); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_list[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + $topic_list = array_unique($topic_list); + } + + /** + * Perform additional actions before topic deletion via pruning + * + * @event core.prune_delete_before + * @var int[] topic_list The IDs of the topics to be deleted + * @since 3.2.2-RC1 + */ + $vars = array('topic_list'); + extract($phpbb_dispatcher->trigger_event('core.prune_delete_before', compact($vars))); + + return delete_topics('topic_id', $topic_list, $auto_sync, false); +} + +/** +* Function auto_prune(), this function now relies on passed vars +*/ +function auto_prune($forum_id, $prune_mode, $prune_flags, $prune_days, $prune_freq) +{ + global $db, $user, $phpbb_log; + + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query($sql, 3600); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $prune_date = time() - ($prune_days * 86400); + $next_prune = time() + ($prune_freq * 86400); + + $result = prune($forum_id, $prune_mode, $prune_date, $prune_flags, true, 300); + + if ($result['topics'] == 0 && $result['posts'] == 0) + { + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET prune_next = $next_prune + WHERE forum_id = $forum_id"; + $db->sql_query($sql); + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_AUTO_PRUNE', false, array($row['forum_name'])); + } + + return; +} + +/** +* Cache moderators. Called whenever permissions are changed +* via admin_permissions. Changes of usernames and group names +* must be carried through for the moderators table. +* +* @param \phpbb\db\driver\driver_interface $db Database connection +* @param \phpbb\cache\driver\driver_interface Cache driver +* @param \phpbb\auth\auth $auth Authentication object +* @return null +*/ +function phpbb_cache_moderators($db, $cache, $auth) +{ + // Remove cached sql results + $cache->destroy('sql', MODERATOR_CACHE_TABLE); + + // Clear table + switch ($db->get_sql_layer()) + { + case 'sqlite3': + $db->sql_query('DELETE FROM ' . MODERATOR_CACHE_TABLE); + break; + + default: + $db->sql_query('TRUNCATE TABLE ' . MODERATOR_CACHE_TABLE); + break; + } + + // We add moderators who have forum moderator permissions without an explicit ACL_NEVER setting + $sql_ary = array(); + + // Grab all users having moderative options... + $hold_ary = $auth->acl_user_raw_data(false, 'm_%', false); + + // Add users? + if (!empty($hold_ary)) + { + // At least one moderative option warrants a display + $ug_id_ary = array_keys($hold_ary); + + // Remove users who have group memberships with DENY moderator permissions + $sql_ary_deny = array( + 'SELECT' => 'a.forum_id, ug.user_id, g.group_id', + + 'FROM' => array( + ACL_OPTIONS_TABLE => 'o', + USER_GROUP_TABLE => 'ug', + GROUPS_TABLE => 'g', + ACL_GROUPS_TABLE => 'a', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(ACL_ROLES_DATA_TABLE => 'r'), + 'ON' => 'a.auth_role_id = r.role_id', + ), + ), + + 'WHERE' => '(o.auth_option_id = a.auth_option_id OR o.auth_option_id = r.auth_option_id) + AND ((a.auth_setting = ' . ACL_NEVER . ' AND r.auth_setting IS NULL) + OR r.auth_setting = ' . ACL_NEVER . ') + AND a.group_id = ug.group_id + AND g.group_id = ug.group_id + AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) + AND ' . $db->sql_in_set('ug.user_id', $ug_id_ary) . " + AND ug.user_pending = 0 + AND o.auth_option " . $db->sql_like_expression('m_' . $db->get_any_char()), + ); + $sql = $db->sql_build_query('SELECT', $sql_ary_deny); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (isset($hold_ary[$row['user_id']][$row['forum_id']])) + { + unset($hold_ary[$row['user_id']][$row['forum_id']]); + } + } + $db->sql_freeresult($result); + + if (count($hold_ary)) + { + // Get usernames... + $sql = 'SELECT user_id, username + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', array_keys($hold_ary)); + $result = $db->sql_query($sql); + + $usernames_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $usernames_ary[$row['user_id']] = $row['username']; + } + $db->sql_freeresult($result); + + foreach ($hold_ary as $user_id => $forum_id_ary) + { + // Do not continue if user does not exist + if (!isset($usernames_ary[$user_id])) + { + continue; + } + + foreach ($forum_id_ary as $forum_id => $auth_ary) + { + $sql_ary[] = array( + 'forum_id' => (int) $forum_id, + 'user_id' => (int) $user_id, + 'username' => (string) $usernames_ary[$user_id], + 'group_id' => 0, + 'group_name' => '' + ); + } + } + } + } + + // Now to the groups... + $hold_ary = $auth->acl_group_raw_data(false, 'm_%', false); + + if (!empty($hold_ary)) + { + $ug_id_ary = array_keys($hold_ary); + + // Make sure not hidden or special groups are involved... + $sql = 'SELECT group_name, group_id, group_type + FROM ' . GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('group_id', $ug_id_ary); + $result = $db->sql_query($sql); + + $groupnames_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['group_type'] == GROUP_HIDDEN || $row['group_type'] == GROUP_SPECIAL) + { + unset($hold_ary[$row['group_id']]); + } + + $groupnames_ary[$row['group_id']] = $row['group_name']; + } + $db->sql_freeresult($result); + + foreach ($hold_ary as $group_id => $forum_id_ary) + { + // If there is no group, we do not assign it... + if (!isset($groupnames_ary[$group_id])) + { + continue; + } + + foreach ($forum_id_ary as $forum_id => $auth_ary) + { + $flag = false; + foreach ($auth_ary as $auth_option => $setting) + { + // Make sure at least one ACL_YES option is set... + if ($setting == ACL_YES) + { + $flag = true; + break; + } + } + + if (!$flag) + { + continue; + } + + $sql_ary[] = array( + 'forum_id' => (int) $forum_id, + 'user_id' => 0, + 'username' => '', + 'group_id' => (int) $group_id, + 'group_name' => (string) $groupnames_ary[$group_id] + ); + } + } + } + + $db->sql_multi_insert(MODERATOR_CACHE_TABLE, $sql_ary); +} + +/** +* View log +* +* @param string $mode The mode defines which log_type is used and from which log the entry is retrieved +* @param array &$log The result array with the logs +* @param mixed &$log_count If $log_count is set to false, we will skip counting all entries in the database. +* Otherwise an integer with the number of total matching entries is returned. +* @param int $limit Limit the number of entries that are returned +* @param int $offset Offset when fetching the log entries, f.e. when paginating +* @param mixed $forum_id Restrict the log entries to the given forum_id (can also be an array of forum_ids) +* @param int $topic_id Restrict the log entries to the given topic_id +* @param int $user_id Restrict the log entries to the given user_id +* @param int $log_time Only get log entries newer than the given timestamp +* @param string $sort_by SQL order option, e.g. 'l.log_time DESC' +* @param string $keywords Will only return log entries that have the keywords in log_operation or log_data +* +* @return int Returns the offset of the last valid page, if the specified offset was invalid (too high) +*/ +function view_log($mode, &$log, &$log_count, $limit = 0, $offset = 0, $forum_id = 0, $topic_id = 0, $user_id = 0, $limit_days = 0, $sort_by = 'l.log_time DESC', $keywords = '') +{ + global $phpbb_log; + + $count_logs = ($log_count !== false); + + $log = $phpbb_log->get_logs($mode, $count_logs, $limit, $offset, $forum_id, $topic_id, $user_id, $limit_days, $sort_by, $keywords); + $log_count = $phpbb_log->get_log_count(); + + return $phpbb_log->get_valid_offset(); +} + +/** +* Removes moderators and administrators from foe lists. +* +* @param \phpbb\db\driver\driver_interface $db Database connection +* @param \phpbb\auth\auth $auth Authentication object +* @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore +* @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore +* @return null +*/ +function phpbb_update_foes($db, $auth, $group_id = false, $user_id = false) +{ + // update foes for some user + if (is_array($user_id) && count($user_id)) + { + $sql = 'DELETE FROM ' . ZEBRA_TABLE . ' + WHERE ' . $db->sql_in_set('zebra_id', $user_id) . ' + AND foe = 1'; + $db->sql_query($sql); + return; + } + + // update foes for some group + if (is_array($group_id) && count($group_id)) + { + // Grab group settings... + $sql_ary = array( + 'SELECT' => 'a.group_id', + + 'FROM' => array( + ACL_OPTIONS_TABLE => 'ao', + ACL_GROUPS_TABLE => 'a', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(ACL_ROLES_DATA_TABLE => 'r'), + 'ON' => 'a.auth_role_id = r.role_id', + ), + ), + + 'WHERE' => '(ao.auth_option_id = a.auth_option_id OR ao.auth_option_id = r.auth_option_id) + AND ' . $db->sql_in_set('a.group_id', $group_id) . " + AND ao.auth_option IN ('a_', 'm_')", + + 'GROUP_BY' => 'a.group_id', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $groups = array(); + while ($row = $db->sql_fetchrow($result)) + { + $groups[] = (int) $row['group_id']; + } + $db->sql_freeresult($result); + + if (!count($groups)) + { + return; + } + + switch ($db->get_sql_layer()) + { + case 'mysqli': + case 'mysql4': + $sql = 'DELETE ' . (($db->get_sql_layer() === 'mysqli' || version_compare($db->sql_server_info(true), '4.1', '>=')) ? 'z.*' : ZEBRA_TABLE) . ' + FROM ' . ZEBRA_TABLE . ' z, ' . USER_GROUP_TABLE . ' ug + WHERE z.zebra_id = ug.user_id + AND z.foe = 1 + AND ' . $db->sql_in_set('ug.group_id', $groups); + $db->sql_query($sql); + break; + + default: + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . ' + WHERE ' . $db->sql_in_set('group_id', $groups); + $result = $db->sql_query($sql); + + $users = array(); + while ($row = $db->sql_fetchrow($result)) + { + $users[] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + if (count($users)) + { + $sql = 'DELETE FROM ' . ZEBRA_TABLE . ' + WHERE ' . $db->sql_in_set('zebra_id', $users) . ' + AND foe = 1'; + $db->sql_query($sql); + } + break; + } + + return; + } + + // update foes for everyone + $perms = array(); + foreach ($auth->acl_get_list(false, array('a_', 'm_'), false) as $forum_id => $forum_ary) + { + foreach ($forum_ary as $auth_option => $user_ary) + { + $perms = array_merge($perms, $user_ary); + } + } + + if (count($perms)) + { + $sql = 'DELETE FROM ' . ZEBRA_TABLE . ' + WHERE ' . $db->sql_in_set('zebra_id', array_unique($perms)) . ' + AND foe = 1'; + $db->sql_query($sql); + } + unset($perms); +} + +/** +* Lists inactive users +*/ +function view_inactive_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_inactive_time DESC') +{ + global $db, $user; + + $sql = 'SELECT COUNT(user_id) AS user_count + FROM ' . USERS_TABLE . ' + WHERE user_type = ' . USER_INACTIVE . + (($limit_days) ? " AND user_inactive_time >= $limit_days" : ''); + $result = $db->sql_query($sql); + $user_count = (int) $db->sql_fetchfield('user_count'); + $db->sql_freeresult($result); + + if ($user_count == 0) + { + // Save the queries, because there are no users to display + return 0; + } + + if ($offset >= $user_count) + { + $offset = ($offset - $limit < 0) ? 0 : $offset - $limit; + } + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_type = ' . USER_INACTIVE . + (($limit_days) ? " AND user_inactive_time >= $limit_days" : '') . " + ORDER BY $sort_by"; + $result = $db->sql_query_limit($sql, $limit, $offset); + + while ($row = $db->sql_fetchrow($result)) + { + $row['inactive_reason'] = $user->lang['INACTIVE_REASON_UNKNOWN']; + switch ($row['user_inactive_reason']) + { + case INACTIVE_REGISTER: + $row['inactive_reason'] = $user->lang['INACTIVE_REASON_REGISTER']; + break; + + case INACTIVE_PROFILE: + $row['inactive_reason'] = $user->lang['INACTIVE_REASON_PROFILE']; + break; + + case INACTIVE_MANUAL: + $row['inactive_reason'] = $user->lang['INACTIVE_REASON_MANUAL']; + break; + + case INACTIVE_REMIND: + $row['inactive_reason'] = $user->lang['INACTIVE_REASON_REMIND']; + break; + } + + $users[] = $row; + } + $db->sql_freeresult($result); + + return $offset; +} + +/** +* Lists warned users +*/ +function view_warned_users(&$users, &$user_count, $limit = 0, $offset = 0, $limit_days = 0, $sort_by = 'user_warnings DESC') +{ + global $db; + + $sql = 'SELECT user_id, username, user_colour, user_warnings, user_last_warning + FROM ' . USERS_TABLE . ' + WHERE user_warnings > 0 + ' . (($limit_days) ? "AND user_last_warning >= $limit_days" : '') . " + ORDER BY $sort_by"; + $result = $db->sql_query_limit($sql, $limit, $offset); + $users = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + $sql = 'SELECT count(user_id) AS user_count + FROM ' . USERS_TABLE . ' + WHERE user_warnings > 0 + ' . (($limit_days) ? "AND user_last_warning >= $limit_days" : ''); + $result = $db->sql_query($sql); + $user_count = (int) $db->sql_fetchfield('user_count'); + $db->sql_freeresult($result); + + return; +} + +/** +* Get database size +* Currently only mysql and mssql are supported +*/ +function get_database_size() +{ + global $db, $user, $table_prefix; + + $database_size = false; + + // This code is heavily influenced by a similar routine in phpMyAdmin 2.2.0 + switch ($db->get_sql_layer()) + { + case 'mysql': + case 'mysql4': + case 'mysqli': + $sql = 'SELECT VERSION() AS mysql_version'; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $version = $row['mysql_version']; + + if (preg_match('#(3\.23|[45]\.|10\.[0-9]\.[0-9]{1,2}-+Maria)#', $version)) + { + $db_name = (preg_match('#^(?:3\.23\.(?:[6-9]|[1-9]{2}))|[45]\.|10\.[0-9]\.[0-9]{1,2}-+Maria#', $version)) ? "`{$db->get_db_name()}`" : $db->get_db_name(); + + $sql = 'SHOW TABLE STATUS + FROM ' . $db_name; + $result = $db->sql_query($sql, 7200); + + $database_size = 0; + while ($row = $db->sql_fetchrow($result)) + { + if ((isset($row['Type']) && $row['Type'] != 'MRG_MyISAM') || (isset($row['Engine']) && ($row['Engine'] == 'MyISAM' || $row['Engine'] == 'InnoDB' || $row['Engine'] == 'Aria'))) + { + if ($table_prefix != '') + { + if (strpos($row['Name'], $table_prefix) !== false) + { + $database_size += $row['Data_length'] + $row['Index_length']; + } + } + else + { + $database_size += $row['Data_length'] + $row['Index_length']; + } + } + } + $db->sql_freeresult($result); + } + } + break; + + case 'sqlite3': + global $dbhost; + + if (file_exists($dbhost)) + { + $database_size = filesize($dbhost); + } + + break; + + case 'mssql_odbc': + case 'mssqlnative': + $sql = 'SELECT @@VERSION AS mssql_version'; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $sql = 'SELECT ((SUM(size) * 8.0) * 1024.0) as dbsize + FROM sysfiles'; + + if ($row) + { + // Azure stats are stored elsewhere + if (strpos($row['mssql_version'], 'SQL Azure') !== false) + { + $sql = 'SELECT ((SUM(reserved_page_count) * 8.0) * 1024.0) as dbsize + FROM sys.dm_db_partition_stats'; + } + } + + $result = $db->sql_query($sql, 7200); + $database_size = ($row = $db->sql_fetchrow($result)) ? $row['dbsize'] : false; + $db->sql_freeresult($result); + break; + + case 'postgres': + $sql = "SELECT proname + FROM pg_proc + WHERE proname = 'pg_database_size'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row['proname'] == 'pg_database_size') + { + $database = $db->get_db_name(); + if (strpos($database, '.') !== false) + { + list($database, ) = explode('.', $database); + } + + $sql = "SELECT oid + FROM pg_database + WHERE datname = '$database'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $oid = $row['oid']; + + $sql = 'SELECT pg_database_size(' . $oid . ') as size'; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $database_size = $row['size']; + } + break; + + case 'oracle': + $sql = 'SELECT SUM(bytes) as dbsize + FROM user_segments'; + $result = $db->sql_query($sql, 7200); + $database_size = ($row = $db->sql_fetchrow($result)) ? $row['dbsize'] : false; + $db->sql_freeresult($result); + break; + } + + $database_size = ($database_size !== false) ? get_formatted_filesize($database_size) : $user->lang['NOT_AVAILABLE']; + + return $database_size; +} + +/* +* Tidy Warnings +* Remove all warnings which have now expired from the database +* The duration of a warning can be defined by the administrator +* This only removes the warning and reduces the associated count, +* it does not remove the user note recording the contents of the warning +*/ +function tidy_warnings() +{ + global $db, $config; + + $expire_date = time() - ($config['warnings_expire_days'] * 86400); + $warning_list = $user_list = array(); + + $sql = 'SELECT * FROM ' . WARNINGS_TABLE . " + WHERE warning_time < $expire_date"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $warning_list[] = $row['warning_id']; + $user_list[$row['user_id']] = isset($user_list[$row['user_id']]) ? ++$user_list[$row['user_id']] : 1; + } + $db->sql_freeresult($result); + + if (count($warning_list)) + { + $db->sql_transaction('begin'); + + $sql = 'DELETE FROM ' . WARNINGS_TABLE . ' + WHERE ' . $db->sql_in_set('warning_id', $warning_list); + $db->sql_query($sql); + + foreach ($user_list as $user_id => $value) + { + $sql = 'UPDATE ' . USERS_TABLE . " SET user_warnings = user_warnings - $value + WHERE user_id = $user_id"; + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + } + + $config->set('warnings_last_gc', time(), false); +} + +/** +* Tidy database, doing some maintanance tasks +*/ +function tidy_database() +{ + global $config, $db; + + // Here we check permission consistency + + // Sometimes, it can happen permission tables having forums listed which do not exist + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE; + $result = $db->sql_query($sql); + + $forum_ids = array(0); + while ($row = $db->sql_fetchrow($result)) + { + $forum_ids[] = $row['forum_id']; + } + $db->sql_freeresult($result); + + $db->sql_transaction('begin'); + + // Delete those rows from the acl tables not having listed the forums above + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true); + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_USERS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_ids, true); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + + $config->set('database_last_gc', time(), false); +} + +/** +* Add permission language - this will make sure custom files will be included +*/ +function add_permission_language() +{ + global $user, $phpEx, $phpbb_extension_manager; + + // add permission language files from extensions + $finder = $phpbb_extension_manager->get_finder(); + + $lang_files = $finder + ->prefix('permissions_') + ->suffix(".$phpEx") + ->core_path('language/') + ->extension_directory('/language') + ->find(); + + foreach ($lang_files as $lang_file => $ext_name) + { + if ($ext_name === '/') + { + $user->add_lang($lang_file); + } + else + { + $user->add_lang_ext($ext_name, $lang_file); + } + } +} + +/** + * Enables a particular flag in a bitfield column of a given table. + * + * @param string $table_name The table to update + * @param string $column_name The column containing a bitfield to update + * @param int $flag The binary flag which is OR-ed with the current column value + * @param string $sql_more This string is attached to the sql query generated to update the table. + * + * @return null + */ +function enable_bitfield_column_flag($table_name, $column_name, $flag, $sql_more = '') +{ + global $db; + + $sql = 'UPDATE ' . $table_name . ' + SET ' . $column_name . ' = ' . $db->sql_bit_or($column_name, $flag) . ' + ' . $sql_more; + $db->sql_query($sql); +} diff --git a/includes/functions_compatibility.php b/includes/functions_compatibility.php new file mode 100644 index 0000000..e95fa40 --- /dev/null +++ b/includes/functions_compatibility.php @@ -0,0 +1,513 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Get user avatar +* +* @deprecated 3.1.0-a1 (To be removed: 3.3.0) +* +* @param string $avatar Users assigned avatar name +* @param int $avatar_type Type of avatar +* @param string $avatar_width Width of users avatar +* @param string $avatar_height Height of users avatar +* @param string $alt Optional language string for alt tag within image, can be a language key or text +* @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP +* @param bool $lazy If true, will be lazy loaded (requires JS) +* +* @return string Avatar image +*/ +function get_user_avatar($avatar, $avatar_type, $avatar_width, $avatar_height, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false) +{ + // map arguments to new function phpbb_get_avatar() + $row = array( + 'avatar' => $avatar, + 'avatar_type' => $avatar_type, + 'avatar_width' => $avatar_width, + 'avatar_height' => $avatar_height, + ); + + return phpbb_get_avatar($row, $alt, $ignore_config, $lazy); +} + +/** +* Hash the password +* +* @deprecated 3.1.0-a2 (To be removed: 3.3.0) +* +* @param string $password Password to be hashed +* +* @return string|bool Password hash or false if something went wrong during hashing +*/ +function phpbb_hash($password) +{ + global $phpbb_container; + + /* @var $passwords_manager \phpbb\passwords\manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + return $passwords_manager->hash($password); +} + +/** +* Check for correct password +* +* @deprecated 3.1.0-a2 (To be removed: 3.3.0) +* +* @param string $password The password in plain text +* @param string $hash The stored password hash +* +* @return bool Returns true if the password is correct, false if not. +*/ +function phpbb_check_hash($password, $hash) +{ + global $phpbb_container; + + /* @var $passwords_manager \phpbb\passwords\manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + return $passwords_manager->check($password, $hash); +} + +/** +* Eliminates useless . and .. components from specified path. +* +* Deprecated, use filesystem class instead +* +* @param string $path Path to clean +* @return string Cleaned path +* +* @deprecated 3.1.0 (To be removed: 3.3.0) +*/ +function phpbb_clean_path($path) +{ + global $phpbb_path_helper, $phpbb_container; + + if (!$phpbb_path_helper && $phpbb_container) + { + /* @var $phpbb_path_helper \phpbb\path_helper */ + $phpbb_path_helper = $phpbb_container->get('path_helper'); + } + else if (!$phpbb_path_helper) + { + global $phpbb_root_path, $phpEx; + + // The container is not yet loaded, use a new instance + if (!class_exists('\phpbb\path_helper')) + { + require($phpbb_root_path . 'phpbb/path_helper.' . $phpEx); + } + + $request = new phpbb\request\request(); + $phpbb_path_helper = new phpbb\path_helper( + new phpbb\symfony_request( + $request + ), + new phpbb\filesystem\filesystem(), + $request, + $phpbb_root_path, + $phpEx + ); + } + + return $phpbb_path_helper->clean_path($path); +} + +/** +* Pick a timezone +* +* @param string $default A timezone to select +* @param boolean $truncate Shall we truncate the options text +* +* @return string Returns the options for timezone selector only +* +* @deprecated 3.1.0 (To be removed: 3.3.0) +*/ +function tz_select($default = '', $truncate = false) +{ + global $template, $user; + + return phpbb_timezone_select($template, $user, $default, $truncate); +} + +/** +* Cache moderators. Called whenever permissions are changed +* via admin_permissions. Changes of usernames and group names +* must be carried through for the moderators table. +* +* @deprecated 3.1.0 (To be removed: 3.3.0) +* @return null +*/ +function cache_moderators() +{ + global $db, $cache, $auth; + return phpbb_cache_moderators($db, $cache, $auth); +} + +/** +* Removes moderators and administrators from foe lists. +* +* @deprecated 3.1.0 (To be removed: 3.3.0) +* @param array|bool $group_id If an array, remove all members of this group from foe lists, or false to ignore +* @param array|bool $user_id If an array, remove this user from foe lists, or false to ignore +* @return null +*/ +function update_foes($group_id = false, $user_id = false) +{ + global $db, $auth; + return phpbb_update_foes($db, $auth, $group_id, $user_id); +} + +/** +* Get user rank title and image +* +* @param int $user_rank the current stored users rank id +* @param int $user_posts the users number of posts +* @param string &$rank_title the rank title will be stored here after execution +* @param string &$rank_img the rank image as full img tag is stored here after execution +* @param string &$rank_img_src the rank image source is stored here after execution +* +* @deprecated 3.1.0-RC5 (To be removed: 3.3.0) +* +* Note: since we do not want to break backwards-compatibility, this function will only properly assign ranks to guests if you call it for them with user_posts == false +*/ +function get_user_rank($user_rank, $user_posts, &$rank_title, &$rank_img, &$rank_img_src) +{ + global $phpbb_root_path, $phpEx; + if (!function_exists('phpbb_get_user_rank')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + $rank_data = phpbb_get_user_rank(array('user_rank' => $user_rank), $user_posts); + $rank_title = $rank_data['title']; + $rank_img = $rank_data['img']; + $rank_img_src = $rank_data['img_src']; +} + +/** + * Retrieve contents from remotely stored file + * + * @deprecated 3.1.2 Use file_downloader instead + */ +function get_remote_file($host, $directory, $filename, &$errstr, &$errno, $port = 80, $timeout = 6) +{ + global $phpbb_container; + + // Get file downloader and assign $errstr and $errno + /* @var $file_downloader \phpbb\file_downloader */ + $file_downloader = $phpbb_container->get('file_downloader'); + + $file_data = $file_downloader->get($host, $directory, $filename, $port, $timeout); + $errstr = $file_downloader->get_error_string(); + $errno = $file_downloader->get_error_number(); + + return $file_data; +} + +/** + * Add log entry + * + * @param string $mode The mode defines which log_type is used and from which log the entry is retrieved + * @param int $forum_id Mode 'mod' ONLY: forum id of the related item, NOT INCLUDED otherwise + * @param int $topic_id Mode 'mod' ONLY: topic id of the related item, NOT INCLUDED otherwise + * @param int $reportee_id Mode 'user' ONLY: user id of the reportee, NOT INCLUDED otherwise + * @param string $log_operation Name of the operation + * @param array $additional_data More arguments can be added, depending on the log_type + * + * @return int|bool Returns the log_id, if the entry was added to the database, false otherwise. + * + * @deprecated 3.1.0 (To be removed: 3.3.0) + */ +function add_log() +{ + global $phpbb_log, $user; + + $args = func_get_args(); + $mode = array_shift($args); + + // This looks kind of dirty, but add_log has some additional data before the log_operation + $additional_data = array(); + switch ($mode) + { + case 'admin': + case 'critical': + break; + case 'mod': + $additional_data['forum_id'] = array_shift($args); + $additional_data['topic_id'] = array_shift($args); + break; + case 'user': + $additional_data['reportee_id'] = array_shift($args); + break; + } + + $log_operation = array_shift($args); + $additional_data = array_merge($additional_data, $args); + + $user_id = (empty($user->data)) ? ANONYMOUS : $user->data['user_id']; + $user_ip = (empty($user->ip)) ? '' : $user->ip; + + return $phpbb_log->add($mode, $user_id, $user_ip, $log_operation, time(), $additional_data); +} + +/** + * Sets a configuration option's value. + * + * Please note that this function does not update the is_dynamic value for + * an already existing config option. + * + * @param string $config_name The configuration option's name + * @param string $config_value New configuration value + * @param bool $is_dynamic Whether this variable should be cached (false) or + * if it changes too frequently (true) to be + * efficiently cached. + * + * @return null + * + * @deprecated 3.1.0 (To be removed: 3.3.0) + */ +function set_config($config_name, $config_value, $is_dynamic = false, \phpbb\config\config $set_config = null) +{ + static $config = null; + + if ($set_config !== null) + { + $config = $set_config; + + if (empty($config_name)) + { + return; + } + } + + $config->set($config_name, $config_value, !$is_dynamic); +} + +/** + * Increments an integer config value directly in the database. + * + * @param string $config_name The configuration option's name + * @param int $increment Amount to increment by + * @param bool $is_dynamic Whether this variable should be cached (false) or + * if it changes too frequently (true) to be + * efficiently cached. + * + * @return null + * + * @deprecated 3.1.0 (To be removed: 3.3.0) + */ +function set_config_count($config_name, $increment, $is_dynamic = false, \phpbb\config\config $set_config = null) +{ + static $config = null; + if ($set_config !== null) + { + $config = $set_config; + if (empty($config_name)) + { + return; + } + } + $config->increment($config_name, $increment, !$is_dynamic); +} + +/** + * Wrapper function of \phpbb\request\request::variable which exists for backwards compatability. + * See {@link \phpbb\request\request_interface::variable \phpbb\request\request_interface::variable} for + * documentation of this function's use. + * + * @deprecated 3.1.0 (To be removed: 3.3.0) + * @param mixed $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * If you pass an instance of {@link \phpbb\request\request_interface phpbb_request_interface} + * as this parameter it will overwrite the current request class instance. If you do + * not do so, it will create its own instance (but leave superglobals enabled). + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param bool $cookie This param is mapped to \phpbb\request\request_interface::COOKIE as the last param for + * \phpbb\request\request_interface::variable for backwards compatability reasons. + * @param \phpbb\request\request_interface|null|false If an instance of \phpbb\request\request_interface is given the instance is stored in + * a static variable and used for all further calls where this parameters is null. Until + * the function is called with an instance it automatically creates a new \phpbb\request\request + * instance on every call. By passing false this per-call instantiation can be restored + * after having passed in a \phpbb\request\request_interface instance. + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ +function request_var($var_name, $default, $multibyte = false, $cookie = false, $request = null) +{ + // This is all just an ugly hack to add "Dependency Injection" to a function + // the only real code is the function call which maps this function to a method. + static $static_request = null; + if ($request instanceof \phpbb\request\request_interface) + { + $static_request = $request; + if (empty($var_name)) + { + return; + } + } + else if ($request === false) + { + $static_request = null; + if (empty($var_name)) + { + return; + } + } + $tmp_request = $static_request; + // no request class set, create a temporary one ourselves to keep backwards compatibility + if ($tmp_request === null) + { + // false param: enable super globals, so the created request class does not + // make super globals inaccessible everywhere outside this function. + $tmp_request = new \phpbb\request\request(new \phpbb\request\type_cast_helper(), false); + } + return $tmp_request->variable($var_name, $default, $multibyte, ($cookie) ? \phpbb\request\request_interface::COOKIE : \phpbb\request\request_interface::REQUEST); +} + +/** + * Get tables of a database + * + * @deprecated 3.1.0 (To be removed: 3.3.0) + */ +function get_tables($db) +{ + $db_tools_factory = new \phpbb\db\tools\factory(); + $db_tools = $db_tools_factory->get($db); + + return $db_tools->sql_list_tables(); +} + +/** + * Global function for chmodding directories and files for internal use + * + * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions. + * The function determines owner and group from common.php file and sets the same to the provided file. + * The function uses bit fields to build the permissions. + * The function sets the appropiate execute bit on directories. + * + * Supported constants representing bit fields are: + * + * CHMOD_ALL - all permissions (7) + * CHMOD_READ - read permission (4) + * CHMOD_WRITE - write permission (2) + * CHMOD_EXECUTE - execute permission (1) + * + * NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions. + * + * @param string $filename The file/directory to be chmodded + * @param int $perms Permissions to set + * + * @return bool true on success, otherwise false + * + * @deprecated 3.2.0-dev use \phpbb\filesystem\filesystem::phpbb_chmod() instead + */ +function phpbb_chmod($filename, $perms = CHMOD_READ) +{ + global $phpbb_filesystem; + + try + { + $phpbb_filesystem->phpbb_chmod($filename, $perms); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + return false; + } + + return true; +} + +/** + * Test if a file/directory is writable + * + * This function calls the native is_writable() when not running under + * Windows and it is not disabled. + * + * @param string $file Path to perform write test on + * @return bool True when the path is writable, otherwise false. + * + * @deprecated 3.2.0-dev use \phpbb\filesystem\filesystem::is_writable() instead + */ +function phpbb_is_writable($file) +{ + global $phpbb_filesystem; + + return $phpbb_filesystem->is_writable($file); +} + +/** + * Checks if a path ($path) is absolute or relative + * + * @param string $path Path to check absoluteness of + * @return boolean + * + * @deprecated 3.2.0-dev use \phpbb\filesystem\filesystem::is_absolute_path() instead + */ +function phpbb_is_absolute($path) +{ + global $phpbb_filesystem; + + return $phpbb_filesystem->is_absolute_path($path); +} + +/** + * A wrapper for realpath + * + * @deprecated 3.2.0-dev use \phpbb\filesystem\filesystem::realpath() instead + */ +function phpbb_realpath($path) +{ + global $phpbb_filesystem; + + return $phpbb_filesystem->realpath($path); +} + +/** + * Determine which plural form we should use. + * For some languages this is not as simple as for English. + * + * @param $rule int ID of the plural rule we want to use, see https://area51.phpbb.com/docs/dev/32x/language/plurals.html + * @param $number int|float The number we want to get the plural case for. Float numbers are floored. + * @return int The plural-case we need to use for the number plural-rule combination + * + * @deprecated 3.2.0-dev (To be removed: 3.3.0) + */ +function phpbb_get_plural_form($rule, $number) +{ + global $phpbb_container; + + /** @var \phpbb\language\language $language */ + $language = $phpbb_container->get('language'); + return $language->get_plural_form($number, $rule); +} + +/** +* @return bool Always true +* @deprecated 3.2.0-dev +*/ +function phpbb_pcre_utf8_support() +{ + return true; +} diff --git a/includes/functions_compress.php b/includes/functions_compress.php new file mode 100644 index 0000000..e86da77 --- /dev/null +++ b/includes/functions_compress.php @@ -0,0 +1,830 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Class for handling archives (compression/decompression) +*/ +class compress +{ + var $fp = 0; + + /** + * @var array + */ + protected $filelist = array(); + + /** + * Add file to archive + */ + function add_file($src, $src_rm_prefix = '', $src_add_prefix = '', $skip_files = '') + { + global $phpbb_root_path; + + $skip_files = explode(',', $skip_files); + + // Remove rm prefix from src path + $src_path = ($src_rm_prefix) ? preg_replace('#^(' . preg_quote($src_rm_prefix, '#') . ')#', '', $src) : $src; + // Add src prefix + $src_path = ($src_add_prefix) ? ($src_add_prefix . ((substr($src_add_prefix, -1) != '/') ? '/' : '') . $src_path) : $src_path; + // Remove initial "/" if present + $src_path = (substr($src_path, 0, 1) == '/') ? substr($src_path, 1) : $src_path; + + if (is_file($phpbb_root_path . $src)) + { + $this->data($src_path, file_get_contents("$phpbb_root_path$src"), stat("$phpbb_root_path$src"), false); + } + else if (is_dir($phpbb_root_path . $src)) + { + // Clean up path, add closing / if not present + $src_path = ($src_path && substr($src_path, -1) != '/') ? $src_path . '/' : $src_path; + + $filelist = filelist("$phpbb_root_path$src", '', '*'); + krsort($filelist); + + /** + * Commented out, as adding the folders produces corrupted archives + if ($src_path) + { + $this->data($src_path, '', true, stat("$phpbb_root_path$src")); + } + */ + + foreach ($filelist as $path => $file_ary) + { + /** + * Commented out, as adding the folders produces corrupted archives + if ($path) + { + // Same as for src_path + $path = (substr($path, 0, 1) == '/') ? substr($path, 1) : $path; + $path = ($path && substr($path, -1) != '/') ? $path . '/' : $path; + + $this->data("$src_path$path", '', true, stat("$phpbb_root_path$src$path")); + } + */ + + foreach ($file_ary as $file) + { + if (in_array($path . $file, $skip_files)) + { + continue; + } + + $this->data("$src_path$path$file", file_get_contents("$phpbb_root_path$src$path$file"), stat("$phpbb_root_path$src$path$file"), false); + } + } + } + else + { + // $src does not exist + return false; + } + + return true; + } + + /** + * Add custom file (the filepath will not be adjusted) + */ + function add_custom_file($src, $filename) + { + if (!file_exists($src)) + { + return false; + } + + $this->data($filename, file_get_contents($src), stat($src), false); + return true; + } + + /** + * Add file data + */ + function add_data($src, $name) + { + $stat = array(); + $stat[2] = 436; //384 + $stat[4] = $stat[5] = 0; + $stat[7] = strlen($src); + $stat[9] = time(); + $this->data($name, $src, $stat, false); + return true; + } + + /** + * Checks if a file by that name as already been added and, if it has, + * returns a new, unique name. + * + * @param string $name The filename + * @return string A unique filename + */ + protected function unique_filename($name) + { + if (isset($this->filelist[$name])) + { + $start = $name; + $ext = ''; + $this->filelist[$name]++; + + // Separate the extension off the end of the filename to preserve it + $pos = strrpos($name, '.'); + if ($pos !== false) + { + $start = substr($name, 0, $pos); + $ext = substr($name, $pos); + } + + return $start . '_' . $this->filelist[$name] . $ext; + } + + $this->filelist[$name] = 0; + return $name; + } + + /** + * Return available methods + * + * @return array Array of strings of available compression methods (.tar, .tar.gz, .zip, etc.) + */ + static public function methods() + { + $methods = array('.tar'); + $available_methods = array('.tar.gz' => 'zlib', '.tar.bz2' => 'bz2', '.zip' => 'zlib'); + + foreach ($available_methods as $type => $module) + { + if (!@extension_loaded($module)) + { + continue; + } + $methods[] = $type; + } + + return $methods; + } +} + +/** +* Zip creation class from phpMyAdmin 2.3.0 (c) Tobias Ratschiller, Olivier Müller, Loïc Chapeaux, +* Marc Delisle, http://www.phpmyadmin.net/ +* +* Zip extraction function by Alexandre Tedeschi, alexandrebr at gmail dot com +* +* Modified extensively by psoTFX and DavidMJ, (c) phpBB Limited, 2003 +* +* Based on work by Eric Mueller and Denis125 +* Official ZIP file format: http://www.pkware.com/appnote.txt +*/ +class compress_zip extends compress +{ + var $datasec = array(); + var $ctrl_dir = array(); + var $eof_cdh = "\x50\x4b\x05\x06\x00\x00\x00\x00"; + + var $old_offset = 0; + var $datasec_len = 0; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * Constructor + */ + function __construct($mode, $file) + { + global $phpbb_filesystem; + + $this->fp = @fopen($file, $mode . 'b'); + $this->filesystem = ($phpbb_filesystem instanceof \phpbb\filesystem\filesystem_interface) ? $phpbb_filesystem : new \phpbb\filesystem\filesystem(); + + if (!$this->fp) + { + trigger_error('Unable to open file ' . $file . ' [' . $mode . 'b]'); + } + } + + /** + * Convert unix to dos time + */ + function unix_to_dos_time($time) + { + $timearray = (!$time) ? getdate() : getdate($time); + + if ($timearray['year'] < 1980) + { + $timearray['year'] = 1980; + $timearray['mon'] = $timearray['mday'] = 1; + $timearray['hours'] = $timearray['minutes'] = $timearray['seconds'] = 0; + } + + return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1); + } + + /** + * Extract archive + */ + function extract($dst) + { + // Loop the file, looking for files and folders + $dd_try = false; + rewind($this->fp); + + while (!feof($this->fp)) + { + // Check if the signature is valid... + $signature = fread($this->fp, 4); + + switch ($signature) + { + // 'Local File Header' + case "\x50\x4b\x03\x04": + // Lets get everything we need. + // We don't store the version needed to extract, the general purpose bit flag or the date and time fields + $data = unpack("@4/vc_method/@10/Vcrc/Vc_size/Vuc_size/vname_len/vextra_field", fread($this->fp, 26)); + $file_name = fread($this->fp, $data['name_len']); // filename + + if ($data['extra_field']) + { + fread($this->fp, $data['extra_field']); // extra field + } + + $target_filename = "$dst$file_name"; + + if (!$data['uc_size'] && !$data['crc'] && substr($file_name, -1, 1) == '/') + { + if (!is_dir($target_filename)) + { + $str = ''; + $folders = explode('/', $target_filename); + + // Create and folders and subfolders if they do not exist + foreach ($folders as $folder) + { + $folder = trim($folder); + if (!$folder) + { + continue; + } + + $str = (!empty($str)) ? $str . '/' . $folder : $folder; + if (!is_dir($str)) + { + if (!@mkdir($str, 0777)) + { + trigger_error("Could not create directory $folder"); + } + + try + { + $this->filesystem->phpbb_chmod($str, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + } + } + // This is a directory, we are not writting files + continue; + } + else + { + // Some archivers are punks, they don't include folders in their archives! + $str = ''; + $folders = explode('/', pathinfo($target_filename, PATHINFO_DIRNAME)); + + // Create and folders and subfolders if they do not exist + foreach ($folders as $folder) + { + $folder = trim($folder); + if (!$folder) + { + continue; + } + + $str = (!empty($str)) ? $str . '/' . $folder : $folder; + if (!is_dir($str)) + { + if (!@mkdir($str, 0777)) + { + trigger_error("Could not create directory $folder"); + } + + try + { + $this->filesystem->phpbb_chmod($str, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + } + } + + if (!$data['uc_size']) + { + $content = ''; + } + else + { + $content = fread($this->fp, $data['c_size']); + } + + $fp = fopen($target_filename, "w"); + + switch ($data['c_method']) + { + case 0: + // Not compressed + fwrite($fp, $content); + break; + + case 8: + // Deflate + fwrite($fp, gzinflate($content, $data['uc_size'])); + break; + + case 12: + // Bzip2 + fwrite($fp, bzdecompress($content)); + break; + } + + fclose($fp); + break; + + // We hit the 'Central Directory Header', we can stop because nothing else in here requires our attention + // or we hit the end of the central directory record, we can safely end the loop as we are totally finished with looking for files and folders + case "\x50\x4b\x01\x02": + // This case should simply never happen.. but it does exist.. + case "\x50\x4b\x05\x06": + break 2; + + // 'Packed to Removable Disk', ignore it and look for the next signature... + case 'PK00': + continue 2; + + // We have encountered a header that is weird. Lets look for better data... + default: + if (!$dd_try) + { + // Unexpected header. Trying to detect wrong placed 'Data Descriptor'; + $dd_try = true; + fseek($this->fp, 8, SEEK_CUR); // Jump over 'crc-32'(4) 'compressed-size'(4), 'uncompressed-size'(4) + continue 2; + } + trigger_error("Unexpected header, ending loop"); + break 2; + } + + $dd_try = false; + } + } + + /** + * Close archive + */ + function close() + { + // Write out central file directory and footer ... if it exists + if (count($this->ctrl_dir)) + { + fwrite($this->fp, $this->file()); + } + fclose($this->fp); + } + + /** + * Create the structures ... note we assume version made by is MSDOS + */ + function data($name, $data, $stat, $is_dir = false) + { + $name = str_replace('\\', '/', $name); + $name = $this->unique_filename($name); + + $hexdtime = pack('V', $this->unix_to_dos_time($stat[9])); + + if ($is_dir) + { + $unc_len = $c_len = $crc = 0; + $zdata = ''; + $var_ext = 10; + } + else + { + $unc_len = strlen($data); + $crc = crc32($data); + $zdata = gzdeflate($data); + $c_len = strlen($zdata); + $var_ext = 20; + + // Did we compress? No, then use data as is + if ($c_len >= $unc_len) + { + $zdata = $data; + $c_len = $unc_len; + $var_ext = 10; + } + } + unset($data); + + // If we didn't compress set method to store, else deflate + $c_method = ($c_len == $unc_len) ? "\x00\x00" : "\x08\x00"; + + // Are we a file or a directory? Set archive for file + $attrib = ($is_dir) ? 16 : 32; + + // File Record Header + $fr = "\x50\x4b\x03\x04"; // Local file header 4bytes + $fr .= pack('v', $var_ext); // ver needed to extract 2bytes + $fr .= "\x00\x00"; // gen purpose bit flag 2bytes + $fr .= $c_method; // compression method 2bytes + $fr .= $hexdtime; // last mod time and date 2+2bytes + $fr .= pack('V', $crc); // crc32 4bytes + $fr .= pack('V', $c_len); // compressed filesize 4bytes + $fr .= pack('V', $unc_len); // uncompressed filesize 4bytes + $fr .= pack('v', strlen($name));// length of filename 2bytes + + $fr .= pack('v', 0); // extra field length 2bytes + $fr .= $name; + $fr .= $zdata; + unset($zdata); + + $this->datasec_len += strlen($fr); + + // Add data to file ... by writing data out incrementally we save some memory + fwrite($this->fp, $fr); + unset($fr); + + // Central Directory Header + $cdrec = "\x50\x4b\x01\x02"; // header 4bytes + $cdrec .= "\x00\x00"; // version made by + $cdrec .= pack('v', $var_ext); // version needed to extract + $cdrec .= "\x00\x00"; // gen purpose bit flag + $cdrec .= $c_method; // compression method + $cdrec .= $hexdtime; // last mod time & date + $cdrec .= pack('V', $crc); // crc32 + $cdrec .= pack('V', $c_len); // compressed filesize + $cdrec .= pack('V', $unc_len); // uncompressed filesize + $cdrec .= pack('v', strlen($name)); // length of filename + $cdrec .= pack('v', 0); // extra field length + $cdrec .= pack('v', 0); // file comment length + $cdrec .= pack('v', 0); // disk number start + $cdrec .= pack('v', 0); // internal file attributes + $cdrec .= pack('V', $attrib); // external file attributes + $cdrec .= pack('V', $this->old_offset); // relative offset of local header + $cdrec .= $name; + + // Save to central directory + $this->ctrl_dir[] = $cdrec; + + $this->old_offset = $this->datasec_len; + } + + /** + * file + */ + function file() + { + $ctrldir = implode('', $this->ctrl_dir); + + return $ctrldir . $this->eof_cdh . + pack('v', count($this->ctrl_dir)) . // total # of entries "on this disk" + pack('v', count($this->ctrl_dir)) . // total # of entries overall + pack('V', strlen($ctrldir)) . // size of central dir + pack('V', $this->datasec_len) . // offset to start of central dir + "\x00\x00"; // .zip file comment length + } + + /** + * Download archive + */ + function download($filename, $download_name = false) + { + global $phpbb_root_path; + + if ($download_name === false) + { + $download_name = $filename; + } + + $mimetype = 'application/zip'; + + header('Cache-Control: private, no-cache'); + header("Content-Type: $mimetype; name=\"$download_name.zip\""); + header("Content-disposition: attachment; filename=$download_name.zip"); + + $fp = @fopen("{$phpbb_root_path}store/$filename.zip", 'rb'); + if ($fp) + { + while ($buffer = fread($fp, 1024)) + { + echo $buffer; + } + fclose($fp); + } + } +} + +/** +* Tar/tar.gz compression routine +* Header/checksum creation derived from tarfile.pl, (c) Tom Horsley, 1994 +*/ +class compress_tar extends compress +{ + var $isgz = false; + var $isbz = false; + var $filename = ''; + var $mode = ''; + var $type = ''; + var $wrote = false; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * Constructor + */ + function __construct($mode, $file, $type = '') + { + global $phpbb_filesystem; + + $type = (!$type) ? $file : $type; + $this->isgz = preg_match('#(\.tar\.gz|\.tgz)$#', $type); + $this->isbz = preg_match('#\.tar\.bz2$#', $type); + + $this->mode = &$mode; + $this->file = &$file; + $this->type = &$type; + $this->open(); + + $this->filesystem = ($phpbb_filesystem instanceof \phpbb\filesystem\filesystem_interface) ? $phpbb_filesystem : new \phpbb\filesystem\filesystem(); + } + + /** + * Extract archive + */ + function extract($dst) + { + $fzread = ($this->isbz && function_exists('bzread')) ? 'bzread' : (($this->isgz && @extension_loaded('zlib')) ? 'gzread' : 'fread'); + + // Run through the file and grab directory entries + while ($buffer = $fzread($this->fp, 512)) + { + $tmp = unpack('A6magic', substr($buffer, 257, 6)); + + if (trim($tmp['magic']) == 'ustar') + { + $tmp = unpack('A100name', $buffer); + $filename = trim($tmp['name']); + + $tmp = unpack('Atype', substr($buffer, 156, 1)); + $filetype = (int) trim($tmp['type']); + + $tmp = unpack('A12size', substr($buffer, 124, 12)); + $filesize = octdec((int) trim($tmp['size'])); + + $target_filename = "$dst$filename"; + + if ($filetype == 5) + { + if (!is_dir($target_filename)) + { + $str = ''; + $folders = explode('/', $target_filename); + + // Create and folders and subfolders if they do not exist + foreach ($folders as $folder) + { + $folder = trim($folder); + if (!$folder) + { + continue; + } + + $str = (!empty($str)) ? $str . '/' . $folder : $folder; + if (!is_dir($str)) + { + if (!@mkdir($str, 0777)) + { + trigger_error("Could not create directory $folder"); + } + + try + { + $this->filesystem->phpbb_chmod($str, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + } + } + } + else if ($filesize >= 0 && ($filetype == 0 || $filetype == "\0")) + { + // Some archivers are punks, they don't properly order the folders in their archives! + $str = ''; + $folders = explode('/', pathinfo($target_filename, PATHINFO_DIRNAME)); + + // Create and folders and subfolders if they do not exist + foreach ($folders as $folder) + { + $folder = trim($folder); + if (!$folder) + { + continue; + } + + $str = (!empty($str)) ? $str . '/' . $folder : $folder; + if (!is_dir($str)) + { + if (!@mkdir($str, 0777)) + { + trigger_error("Could not create directory $folder"); + } + + try + { + $this->filesystem->phpbb_chmod($str, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + } + + // Write out the files + if (!($fp = fopen($target_filename, 'wb'))) + { + trigger_error("Couldn't create file $filename"); + } + + try + { + $this->filesystem->phpbb_chmod($target_filename, CHMOD_READ); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + + // Grab the file contents + fwrite($fp, ($filesize) ? $fzread($this->fp, ($filesize + 511) &~ 511) : '', $filesize); + fclose($fp); + } + } + } + } + + /** + * Close archive + */ + function close() + { + $fzclose = ($this->isbz && function_exists('bzclose')) ? 'bzclose' : (($this->isgz && @extension_loaded('zlib')) ? 'gzclose' : 'fclose'); + + if ($this->wrote) + { + $fzwrite = ($this->isbz && function_exists('bzwrite')) ? 'bzwrite' : (($this->isgz && @extension_loaded('zlib')) ? 'gzwrite' : 'fwrite'); + + // The end of a tar archive ends in two records of all NULLs (1024 bytes of \0) + $fzwrite($this->fp, str_repeat("\0", 1024)); + } + + $fzclose($this->fp); + } + + /** + * Create the structures + */ + function data($name, $data, $stat, $is_dir = false) + { + $name = $this->unique_filename($name); + $this->wrote = true; + $fzwrite = ($this->isbz && function_exists('bzwrite')) ? 'bzwrite' : (($this->isgz && @extension_loaded('zlib')) ? 'gzwrite' : 'fwrite'); + + $typeflag = ($is_dir) ? '5' : ''; + + // This is the header data, it contains all the info we know about the file or folder that we are about to archive + $header = ''; + $header .= pack('a100', $name); // file name + $header .= pack('a8', sprintf("%07o", $stat[2])); // file mode + $header .= pack('a8', sprintf("%07o", $stat[4])); // owner id + $header .= pack('a8', sprintf("%07o", $stat[5])); // group id + $header .= pack('a12', sprintf("%011o", $stat[7])); // file size + $header .= pack('a12', sprintf("%011o", $stat[9])); // last mod time + + // Checksum + $checksum = 0; + for ($i = 0; $i < 148; $i++) + { + $checksum += ord($header[$i]); + } + + // We precompute the rest of the hash, this saves us time in the loop and allows us to insert our hash without resorting to string functions + $checksum += 2415 + (($is_dir) ? 53 : 0); + + $header .= pack('a8', sprintf("%07o", $checksum)); // checksum + $header .= pack('a1', $typeflag); // link indicator + $header .= pack('a100', ''); // name of linked file + $header .= pack('a6', 'ustar'); // ustar indicator + $header .= pack('a2', '00'); // ustar version + $header .= pack('a32', 'Unknown'); // owner name + $header .= pack('a32', 'Unknown'); // group name + $header .= pack('a8', ''); // device major number + $header .= pack('a8', ''); // device minor number + $header .= pack('a155', ''); // filename prefix + $header .= pack('a12', ''); // end + + // This writes the entire file in one shot. Header, followed by data and then null padded to a multiple of 512 + $fzwrite($this->fp, $header . (($stat[7] !== 0 && !$is_dir) ? $data . str_repeat("\0", (($stat[7] + 511) &~ 511) - $stat[7]) : '')); + unset($data); + } + + /** + * Open archive + */ + function open() + { + $fzopen = ($this->isbz && function_exists('bzopen')) ? 'bzopen' : (($this->isgz && @extension_loaded('zlib')) ? 'gzopen' : 'fopen'); + $this->fp = @$fzopen($this->file, $this->mode . (($fzopen == 'bzopen') ? '' : 'b') . (($fzopen == 'gzopen') ? '9' : '')); + + if (!$this->fp) + { + trigger_error('Unable to open file ' . $this->file . ' [' . $fzopen . ' - ' . $this->mode . 'b]'); + } + } + + /** + * Download archive + */ + function download($filename, $download_name = false) + { + global $phpbb_root_path; + + if ($download_name === false) + { + $download_name = $filename; + } + + switch ($this->type) + { + case '.tar': + $mimetype = 'application/x-tar'; + break; + + case '.tar.gz': + $mimetype = 'application/x-gzip'; + break; + + case '.tar.bz2': + $mimetype = 'application/x-bzip2'; + break; + + default: + $mimetype = 'application/octet-stream'; + break; + } + + header('Cache-Control: private, no-cache'); + header("Content-Type: $mimetype; name=\"$download_name$this->type\""); + header("Content-disposition: attachment; filename=$download_name$this->type"); + + $fp = @fopen("{$phpbb_root_path}store/$filename$this->type", 'rb'); + if ($fp) + { + while ($buffer = fread($fp, 1024)) + { + echo $buffer; + } + fclose($fp); + } + } +} diff --git a/includes/functions_content.php b/includes/functions_content.php new file mode 100644 index 0000000..a15a03f --- /dev/null +++ b/includes/functions_content.php @@ -0,0 +1,1805 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* gen_sort_selects() +* make_jumpbox() +* bump_topic_allowed() +* get_context() +* phpbb_clean_search_string() +* decode_message() +* strip_bbcode() +* generate_text_for_display() +* generate_text_for_storage() +* generate_text_for_edit() +* make_clickable_callback() +* make_clickable() +* censor_text() +* bbcode_nl2br() +* smiley_text() +* parse_attachments() +* extension_allowed() +* truncate_string() +* get_username_string() +* class bitfield +*/ + +/** +* Generate sort selection fields +*/ +function gen_sort_selects(&$limit_days, &$sort_by_text, &$sort_days, &$sort_key, &$sort_dir, &$s_limit_days, &$s_sort_key, &$s_sort_dir, &$u_sort_param, $def_st = false, $def_sk = false, $def_sd = false) +{ + global $user, $phpbb_dispatcher; + + $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']); + + $sorts = array( + 'st' => array( + 'key' => 'sort_days', + 'default' => $def_st, + 'options' => $limit_days, + 'output' => &$s_limit_days, + ), + + 'sk' => array( + 'key' => 'sort_key', + 'default' => $def_sk, + 'options' => $sort_by_text, + 'output' => &$s_sort_key, + ), + + 'sd' => array( + 'key' => 'sort_dir', + 'default' => $def_sd, + 'options' => $sort_dir_text, + 'output' => &$s_sort_dir, + ), + ); + $u_sort_param = ''; + + foreach ($sorts as $name => $sort_ary) + { + $key = $sort_ary['key']; + $selected = ${$sort_ary['key']}; + + // Check if the key is selectable. If not, we reset to the default or first key found. + // This ensures the values are always valid. We also set $sort_dir/sort_key/etc. to the + // correct value, else the protection is void. ;) + if (!isset($sort_ary['options'][$selected])) + { + if ($sort_ary['default'] !== false) + { + $selected = ${$key} = $sort_ary['default']; + } + else + { + @reset($sort_ary['options']); + $selected = ${$key} = key($sort_ary['options']); + } + } + + $sort_ary['output'] = ''; + + $u_sort_param .= ($selected !== $sort_ary['default']) ? ((strlen($u_sort_param)) ? '&' : '') . "{$name}={$selected}" : ''; + } + + /** + * Run code before generated sort selects are returned + * + * @event core.gen_sort_selects_after + * @var int limit_days Days limit + * @var array sort_by_text Sort by text options + * @var int sort_days Sort by days flag + * @var string sort_key Sort key + * @var string sort_dir Sort dir + * @var string s_limit_days String of days limit + * @var string s_sort_key String of sort key + * @var string s_sort_dir String of sort dir + * @var string u_sort_param Sort URL params + * @var bool def_st Default sort days + * @var bool def_sk Default sort key + * @var bool def_sd Default sort dir + * @var array sorts Sorts + * @since 3.1.9-RC1 + */ + $vars = array( + 'limit_days', + 'sort_by_text', + 'sort_days', + 'sort_key', + 'sort_dir', + 's_limit_days', + 's_sort_key', + 's_sort_dir', + 'u_sort_param', + 'def_st', + 'def_sk', + 'def_sd', + 'sorts', + ); + extract($phpbb_dispatcher->trigger_event('core.gen_sort_selects_after', compact($vars))); + + return; +} + +/** +* Generate Jumpbox +*/ +function make_jumpbox($action, $forum_id = false, $select_all = false, $acl_list = false, $force_display = false) +{ + global $config, $auth, $template, $user, $db, $phpbb_path_helper, $phpbb_dispatcher; + + // We only return if the jumpbox is not forced to be displayed (in case it is needed for functionality) + if (!$config['load_jumpbox'] && $force_display === false) + { + return; + } + + $sql = 'SELECT forum_id, forum_name, parent_id, forum_type, left_id, right_id + FROM ' . FORUMS_TABLE . ' + ORDER BY left_id ASC'; + $result = $db->sql_query($sql, 600); + + $rowset = array(); + while ($row = $db->sql_fetchrow($result)) + { + $rowset[(int) $row['forum_id']] = $row; + } + $db->sql_freeresult($result); + + $right = $padding = 0; + $padding_store = array('0' => 0); + $display_jumpbox = false; + $iteration = 0; + + /** + * Modify the jumpbox forum list data + * + * @event core.make_jumpbox_modify_forum_list + * @var array rowset Array with the forums list data + * @since 3.1.10-RC1 + */ + $vars = array('rowset'); + extract($phpbb_dispatcher->trigger_event('core.make_jumpbox_modify_forum_list', compact($vars))); + + // Sometimes it could happen that forums will be displayed here not be displayed within the index page + // This is the result of forums not displayed at index, having list permissions and a parent of a forum with no permissions. + // If this happens, the padding could be "broken" + + foreach ($rowset as $row) + { + if ($row['left_id'] < $right) + { + $padding++; + $padding_store[$row['parent_id']] = $padding; + } + else if ($row['left_id'] > $right + 1) + { + // Ok, if the $padding_store for this parent is empty there is something wrong. For now we will skip over it. + // @todo digging deep to find out "how" this can happen. + $padding = (isset($padding_store[$row['parent_id']])) ? $padding_store[$row['parent_id']] : $padding; + } + + $right = $row['right_id']; + + if ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id'])) + { + // Non-postable forum with no subforums, don't display + continue; + } + + if (!$auth->acl_get('f_list', $row['forum_id'])) + { + // if the user does not have permissions to list this forum skip + continue; + } + + if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id'])) + { + continue; + } + + $tpl_ary = array(); + if (!$display_jumpbox) + { + $tpl_ary[] = array( + 'FORUM_ID' => ($select_all) ? 0 : -1, + 'FORUM_NAME' => ($select_all) ? $user->lang['ALL_FORUMS'] : $user->lang['SELECT_FORUM'], + 'S_FORUM_COUNT' => $iteration, + 'LINK' => $phpbb_path_helper->append_url_params($action, array('f' => $forum_id)), + ); + + $iteration++; + $display_jumpbox = true; + } + + $tpl_ary[] = array( + 'FORUM_ID' => $row['forum_id'], + 'FORUM_NAME' => $row['forum_name'], + 'SELECTED' => ($row['forum_id'] == $forum_id) ? ' selected="selected"' : '', + 'S_FORUM_COUNT' => $iteration, + 'S_IS_CAT' => ($row['forum_type'] == FORUM_CAT) ? true : false, + 'S_IS_LINK' => ($row['forum_type'] == FORUM_LINK) ? true : false, + 'S_IS_POST' => ($row['forum_type'] == FORUM_POST) ? true : false, + 'LINK' => $phpbb_path_helper->append_url_params($action, array('f' => $row['forum_id'])), + ); + + /** + * Modify the jumpbox before it is assigned to the template + * + * @event core.make_jumpbox_modify_tpl_ary + * @var array row The data of the forum + * @var array tpl_ary Template data of the forum + * @since 3.1.10-RC1 + */ + $vars = array( + 'row', + 'tpl_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.make_jumpbox_modify_tpl_ary', compact($vars))); + + $template->assign_block_vars_array('jumpbox_forums', $tpl_ary); + + unset($tpl_ary); + + for ($i = 0; $i < $padding; $i++) + { + $template->assign_block_vars('jumpbox_forums.level', array()); + } + $iteration++; + } + unset($padding_store, $rowset); + + $url_parts = $phpbb_path_helper->get_url_parts($action); + + $template->assign_vars(array( + 'S_DISPLAY_JUMPBOX' => $display_jumpbox, + 'S_JUMPBOX_ACTION' => $action, + 'HIDDEN_FIELDS_FOR_JUMPBOX' => build_hidden_fields($url_parts['params']), + )); + + return; +} + +/** +* Bump Topic Check - used by posting and viewtopic +*/ +function bump_topic_allowed($forum_id, $topic_bumped, $last_post_time, $topic_poster, $last_topic_poster) +{ + global $config, $auth, $user; + + // Check permission and make sure the last post was not already bumped + if (!$auth->acl_get('f_bump', $forum_id) || $topic_bumped) + { + return false; + } + + // Check bump time range, is the user really allowed to bump the topic at this time? + $bump_time = ($config['bump_type'] == 'm') ? $config['bump_interval'] * 60 : (($config['bump_type'] == 'h') ? $config['bump_interval'] * 3600 : $config['bump_interval'] * 86400); + + // Check bump time + if ($last_post_time + $bump_time > time()) + { + return false; + } + + // Check bumper, only topic poster and last poster are allowed to bump + if ($topic_poster != $user->data['user_id'] && $last_topic_poster != $user->data['user_id']) + { + return false; + } + + // A bump time of 0 will completely disable the bump feature... not intended but might be useful. + return $bump_time; +} + +/** +* Generates a text with approx. the specified length which contains the specified words and their context +* +* @param string $text The full text from which context shall be extracted +* @param string $words An array of words which should be contained in the result, has to be a valid part of a PCRE pattern (escape with preg_quote!) +* @param int $length The desired length of the resulting text, however the result might be shorter or longer than this value +* +* @return string Context of the specified words separated by "..." +*/ +function get_context($text, $words, $length = 400) +{ + // first replace all whitespaces with single spaces + $text = preg_replace('/ +/', ' ', strtr($text, "\t\n\r\x0C ", ' ')); + + // we need to turn the entities back into their original form, to not cut the message in between them + $entities = array('<', '>', '[', ']', '.', ':', ':'); + $characters = array('<', '>', '[', ']', '.', ':', ':'); + $text = str_replace($entities, $characters, $text); + + $word_indizes = array(); + if (count($words)) + { + $match = ''; + // find the starting indizes of all words + foreach ($words as $word) + { + if ($word) + { + if (preg_match('#(?:[^\w]|^)(' . $word . ')(?:[^\w]|$)#i', $text, $match)) + { + if (empty($match[1])) + { + continue; + } + + $pos = utf8_strpos($text, $match[1]); + if ($pos !== false) + { + $word_indizes[] = $pos; + } + } + } + } + unset($match); + + if (count($word_indizes)) + { + $word_indizes = array_unique($word_indizes); + sort($word_indizes); + + $wordnum = count($word_indizes); + // number of characters on the right and left side of each word + $sequence_length = (int) ($length / (2 * $wordnum)) - 2; + $final_text = ''; + $word = $j = 0; + $final_text_index = -1; + + // cycle through every character in the original text + for ($i = $word_indizes[$word], $n = utf8_strlen($text); $i < $n; $i++) + { + // if the current position is the start of one of the words then append $sequence_length characters to the final text + if (isset($word_indizes[$word]) && ($i == $word_indizes[$word])) + { + if ($final_text_index < $i - $sequence_length - 1) + { + $final_text .= '... ' . preg_replace('#^([^ ]*)#', '', utf8_substr($text, $i - $sequence_length, $sequence_length)); + } + else + { + // if the final text is already nearer to the current word than $sequence_length we only append the text + // from its current index on and distribute the unused length to all other sequenes + $sequence_length += (int) (($final_text_index - $i + $sequence_length + 1) / (2 * $wordnum)); + $final_text .= utf8_substr($text, $final_text_index + 1, $i - $final_text_index - 1); + } + $final_text_index = $i - 1; + + // add the following characters to the final text (see below) + $word++; + $j = 1; + } + + if ($j > 0) + { + // add the character to the final text and increment the sequence counter + $final_text .= utf8_substr($text, $i, 1); + $final_text_index++; + $j++; + + // if this is a whitespace then check whether we are done with this sequence + if (utf8_substr($text, $i, 1) == ' ') + { + // only check whether we have to exit the context generation completely if we haven't already reached the end anyway + if ($i + 4 < $n) + { + if (($j > $sequence_length && $word >= $wordnum) || utf8_strlen($final_text) > $length) + { + $final_text .= ' ...'; + break; + } + } + else + { + // make sure the text really reaches the end + $j -= 4; + } + + // stop context generation and wait for the next word + if ($j > $sequence_length) + { + $j = 0; + } + } + } + } + return str_replace($characters, $entities, $final_text); + } + } + + if (!count($words) || !count($word_indizes)) + { + return str_replace($characters, $entities, ((utf8_strlen($text) >= $length + 3) ? utf8_substr($text, 0, $length) . '...' : $text)); + } +} + +/** +* Cleans a search string by removing single wildcards from it and replacing multiple spaces with a single one. +* +* @param string $search_string The full search string which should be cleaned. +* +* @return string The cleaned search string without any wildcards and multiple spaces. +*/ +function phpbb_clean_search_string($search_string) +{ + // This regular expressions matches every single wildcard. + // That means one after a whitespace or the beginning of the string or one before a whitespace or the end of the string. + $search_string = preg_replace('#(?<=^|\s)\*+(?=\s|$)#', '', $search_string); + $search_string = trim($search_string); + $search_string = preg_replace(array('#\s+#u', '#\*+#u'), array(' ', '*'), $search_string); + return $search_string; +} + +/** +* Decode text whereby text is coming from the db and expected to be pre-parsed content +* We are placing this outside of the message parser because we are often in need of it... +* +* NOTE: special chars are kept encoded +* +* @param string &$message Original message, passed by reference +* @param string $bbcode_uid BBCode UID +* @return null +*/ +function decode_message(&$message, $bbcode_uid = '') +{ + global $phpbb_container, $phpbb_dispatcher; + + /** + * Use this event to modify the message before it is decoded + * + * @event core.decode_message_before + * @var string message_text The message content + * @var string bbcode_uid The message BBCode UID + * @since 3.1.9-RC1 + */ + $message_text = $message; + $vars = array('message_text', 'bbcode_uid'); + extract($phpbb_dispatcher->trigger_event('core.decode_message_before', compact($vars))); + $message = $message_text; + + if (preg_match('#^<[rt][ >]#', $message)) + { + $message = htmlspecialchars($phpbb_container->get('text_formatter.utils')->unparse($message), ENT_COMPAT); + } + else + { + if ($bbcode_uid) + { + $match = array('
', "[/*:m:$bbcode_uid]", ":u:$bbcode_uid", ":o:$bbcode_uid", ":$bbcode_uid"); + $replace = array("\n", '', '', '', ''); + } + else + { + $match = array('
'); + $replace = array("\n"); + } + + $message = str_replace($match, $replace, $message); + + $match = get_preg_expression('bbcode_htm'); + $replace = array('\1', '\1', '\2', '\2', '\1', '', ''); + + $message = preg_replace($match, $replace, $message); + } + + /** + * Use this event to modify the message after it is decoded + * + * @event core.decode_message_after + * @var string message_text The message content + * @var string bbcode_uid The message BBCode UID + * @since 3.1.9-RC1 + */ + $message_text = $message; + $vars = array('message_text', 'bbcode_uid'); + extract($phpbb_dispatcher->trigger_event('core.decode_message_after', compact($vars))); + $message = $message_text; +} + +/** +* Strips all bbcode from a text in place +*/ +function strip_bbcode(&$text, $uid = '') +{ + global $phpbb_container; + + if (preg_match('#^<[rt][ >]#', $text)) + { + $text = $phpbb_container->get('text_formatter.utils')->clean_formatting($text); + } + else + { + if (!$uid) + { + $uid = '[0-9a-z]{5,}'; + } + + $text = preg_replace("#\[\/?[a-z0-9\*\+\-]+(?:=(?:".*"|[^\]]*))?(?::[a-z])?(\:$uid)\]#", ' ', $text); + + $match = get_preg_expression('bbcode_htm'); + $replace = array('\1', '\1', '\2', '\1', '', ''); + + $text = preg_replace($match, $replace, $text); + } +} + +/** +* For display of custom parsed text on user-facing pages +* Expects $text to be the value directly from the database (stored value) +*/ +function generate_text_for_display($text, $uid, $bitfield, $flags, $censor_text = true) +{ + static $bbcode; + global $auth, $config, $user; + global $phpbb_dispatcher, $phpbb_container; + + if ($text === '') + { + return ''; + } + + /** + * Use this event to modify the text before it is parsed + * + * @event core.modify_text_for_display_before + * @var string text The text to parse + * @var string uid The BBCode UID + * @var string bitfield The BBCode Bitfield + * @var int flags The BBCode Flags + * @var bool censor_text Whether or not to apply word censors + * @since 3.1.0-a1 + */ + $vars = array('text', 'uid', 'bitfield', 'flags', 'censor_text'); + extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_before', compact($vars))); + + if (preg_match('#^<[rt][ >]#', $text)) + { + $renderer = $phpbb_container->get('text_formatter.renderer'); + + // Temporarily switch off viewcensors if applicable + $old_censor = $renderer->get_viewcensors(); + + // Check here if the user is having viewing censors disabled (and also allowed to do so). + if (!$user->optionget('viewcensors') && $config['allow_nocensors'] && $auth->acl_get('u_chgcensors')) + { + $censor_text = false; + } + + if ($old_censor !== $censor_text) + { + $renderer->set_viewcensors($censor_text); + } + + $text = $renderer->render($text); + + // Restore the previous value + if ($old_censor !== $censor_text) + { + $renderer->set_viewcensors($old_censor); + } + } + else + { + if ($censor_text) + { + $text = censor_text($text); + } + + // Parse bbcode if bbcode uid stored and bbcode enabled + if ($uid && ($flags & OPTION_FLAG_BBCODE)) + { + if (!class_exists('bbcode')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/bbcode.' . $phpEx); + } + + if (empty($bbcode)) + { + $bbcode = new bbcode($bitfield); + } + else + { + $bbcode->bbcode_set_bitfield($bitfield); + } + + $bbcode->bbcode_second_pass($text, $uid); + } + + $text = bbcode_nl2br($text); + $text = smiley_text($text, !($flags & OPTION_FLAG_SMILIES)); + } + + /** + * Use this event to modify the text after it is parsed + * + * @event core.modify_text_for_display_after + * @var string text The text to parse + * @var string uid The BBCode UID + * @var string bitfield The BBCode Bitfield + * @var int flags The BBCode Flags + * @since 3.1.0-a1 + */ + $vars = array('text', 'uid', 'bitfield', 'flags'); + extract($phpbb_dispatcher->trigger_event('core.modify_text_for_display_after', compact($vars))); + + return $text; +} + +/** +* For parsing custom parsed text to be stored within the database. +* This function additionally returns the uid and bitfield that needs to be stored. +* Expects $text to be the value directly from $request->variable() and in it's non-parsed form +* +* @param string $text The text to be replaced with the parsed one +* @param string $uid The BBCode uid for this parse +* @param string $bitfield The BBCode bitfield for this parse +* @param int $flags The allow_bbcode, allow_urls and allow_smilies compiled into a single integer. +* @param bool $allow_bbcode If BBCode is allowed (i.e. if BBCode is parsed) +* @param bool $allow_urls If urls is allowed +* @param bool $allow_smilies If smilies are allowed +* @param bool $allow_img_bbcode +* @param bool $allow_flash_bbcode +* @param bool $allow_quote_bbcode +* @param bool $allow_url_bbcode +* @param string $mode Mode to parse text as, e.g. post or sig +* +* @return array An array of string with the errors that occurred while parsing +*/ +function generate_text_for_storage(&$text, &$uid, &$bitfield, &$flags, $allow_bbcode = false, $allow_urls = false, $allow_smilies = false, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $mode = 'post') +{ + global $phpbb_root_path, $phpEx, $phpbb_dispatcher; + + /** + * Use this event to modify the text before it is prepared for storage + * + * @event core.modify_text_for_storage_before + * @var string text The text to parse + * @var string uid The BBCode UID + * @var string bitfield The BBCode Bitfield + * @var int flags The BBCode Flags + * @var bool allow_bbcode Whether or not to parse BBCode + * @var bool allow_urls Whether or not to parse URLs + * @var bool allow_smilies Whether or not to parse Smilies + * @var bool allow_img_bbcode Whether or not to parse the [img] BBCode + * @var bool allow_flash_bbcode Whether or not to parse the [flash] BBCode + * @var bool allow_quote_bbcode Whether or not to parse the [quote] BBCode + * @var bool allow_url_bbcode Whether or not to parse the [url] BBCode + * @var string mode Mode to parse text as, e.g. post or sig + * @since 3.1.0-a1 + * @changed 3.2.0-a1 Added mode + */ + $vars = array( + 'text', + 'uid', + 'bitfield', + 'flags', + 'allow_bbcode', + 'allow_urls', + 'allow_smilies', + 'allow_img_bbcode', + 'allow_flash_bbcode', + 'allow_quote_bbcode', + 'allow_url_bbcode', + 'mode', + ); + extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_before', compact($vars))); + + $uid = $bitfield = ''; + $flags = (($allow_bbcode) ? OPTION_FLAG_BBCODE : 0) + (($allow_smilies) ? OPTION_FLAG_SMILIES : 0) + (($allow_urls) ? OPTION_FLAG_LINKS : 0); + + if (!class_exists('parse_message')) + { + include($phpbb_root_path . 'includes/message_parser.' . $phpEx); + } + + $message_parser = new parse_message($text); + $message_parser->parse($allow_bbcode, $allow_urls, $allow_smilies, $allow_img_bbcode, $allow_flash_bbcode, $allow_quote_bbcode, $allow_url_bbcode, true, $mode); + + $text = $message_parser->message; + $uid = $message_parser->bbcode_uid; + + // If the bbcode_bitfield is empty, there is no need for the uid to be stored. + if (!$message_parser->bbcode_bitfield) + { + $uid = ''; + } + + $bitfield = $message_parser->bbcode_bitfield; + + /** + * Use this event to modify the text after it is prepared for storage + * + * @event core.modify_text_for_storage_after + * @var string text The text to parse + * @var string uid The BBCode UID + * @var string bitfield The BBCode Bitfield + * @var int flags The BBCode Flags + * @var string message_parser The message_parser object + * @since 3.1.0-a1 + * @changed 3.1.11-RC1 Added message_parser to vars + */ + $vars = array('text', 'uid', 'bitfield', 'flags', 'message_parser'); + extract($phpbb_dispatcher->trigger_event('core.modify_text_for_storage_after', compact($vars))); + + return $message_parser->warn_msg; +} + +/** +* For decoding custom parsed text for edits as well as extracting the flags +* Expects $text to be the value directly from the database (pre-parsed content) +*/ +function generate_text_for_edit($text, $uid, $flags) +{ + global $phpbb_dispatcher; + + /** + * Use this event to modify the text before it is decoded for editing + * + * @event core.modify_text_for_edit_before + * @var string text The text to parse + * @var string uid The BBCode UID + * @var int flags The BBCode Flags + * @since 3.1.0-a1 + */ + $vars = array('text', 'uid', 'flags'); + extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_before', compact($vars))); + + decode_message($text, $uid); + + /** + * Use this event to modify the text after it is decoded for editing + * + * @event core.modify_text_for_edit_after + * @var string text The text to parse + * @var int flags The BBCode Flags + * @since 3.1.0-a1 + */ + $vars = array('text', 'flags'); + extract($phpbb_dispatcher->trigger_event('core.modify_text_for_edit_after', compact($vars))); + + return array( + 'allow_bbcode' => ($flags & OPTION_FLAG_BBCODE) ? 1 : 0, + 'allow_smilies' => ($flags & OPTION_FLAG_SMILIES) ? 1 : 0, + 'allow_urls' => ($flags & OPTION_FLAG_LINKS) ? 1 : 0, + 'text' => $text + ); +} + +/** +* A subroutine of make_clickable used with preg_replace +* It places correct HTML around an url, shortens the displayed text +* and makes sure no entities are inside URLs +*/ +function make_clickable_callback($type, $whitespace, $url, $relative_url, $class) +{ + $orig_url = $url; + $orig_relative = $relative_url; + $append = ''; + $url = htmlspecialchars_decode($url); + $relative_url = htmlspecialchars_decode($relative_url); + + // make sure no HTML entities were matched + $chars = array('<', '>', '"'); + $split = false; + + foreach ($chars as $char) + { + $next_split = strpos($url, $char); + if ($next_split !== false) + { + $split = ($split !== false) ? min($split, $next_split) : $next_split; + } + } + + if ($split !== false) + { + // an HTML entity was found, so the URL has to end before it + $append = substr($url, $split) . $relative_url; + $url = substr($url, 0, $split); + $relative_url = ''; + } + else if ($relative_url) + { + // same for $relative_url + $split = false; + foreach ($chars as $char) + { + $next_split = strpos($relative_url, $char); + if ($next_split !== false) + { + $split = ($split !== false) ? min($split, $next_split) : $next_split; + } + } + + if ($split !== false) + { + $append = substr($relative_url, $split); + $relative_url = substr($relative_url, 0, $split); + } + } + + // if the last character of the url is a punctuation mark, exclude it from the url + $last_char = ($relative_url) ? $relative_url[strlen($relative_url) - 1] : $url[strlen($url) - 1]; + + switch ($last_char) + { + case '.': + case '?': + case '!': + case ':': + case ',': + $append = $last_char; + if ($relative_url) + { + $relative_url = substr($relative_url, 0, -1); + } + else + { + $url = substr($url, 0, -1); + } + break; + + // set last_char to empty here, so the variable can be used later to + // check whether a character was removed + default: + $last_char = ''; + break; + } + + $short_url = (utf8_strlen($url) > 55) ? utf8_substr($url, 0, 39) . ' ... ' . utf8_substr($url, -10) : $url; + + switch ($type) + { + case MAGIC_URL_LOCAL: + $tag = 'l'; + $relative_url = preg_replace('/[&?]sid=[0-9a-f]{32}$/', '', preg_replace('/([&?])sid=[0-9a-f]{32}&/', '$1', $relative_url)); + $url = $url . '/' . $relative_url; + $text = $relative_url; + + // this url goes to http://domain.tld/path/to/board/ which + // would result in an empty link if treated as local so + // don't touch it and let MAGIC_URL_FULL take care of it. + if (!$relative_url) + { + return $whitespace . $orig_url . '/' . $orig_relative; // slash is taken away by relative url pattern + } + break; + + case MAGIC_URL_FULL: + $tag = 'm'; + $text = $short_url; + break; + + case MAGIC_URL_WWW: + $tag = 'w'; + $url = 'http://' . $url; + $text = $short_url; + break; + + case MAGIC_URL_EMAIL: + $tag = 'e'; + $text = $short_url; + $url = 'mailto:' . $url; + break; + } + + $url = htmlspecialchars($url); + $text = htmlspecialchars($text); + $append = htmlspecialchars($append); + + $html = "$whitespace$text$append"; + + return $html; +} + +/** +* make_clickable function +* +* Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx. +* Cuts down displayed size of link if over 50 chars, turns absolute links +* into relative versions when the server/script path matches the link +*/ +function make_clickable($text, $server_url = false, $class = 'postlink') +{ + if ($server_url === false) + { + $server_url = generate_board_url(); + } + + static $static_class; + static $magic_url_match_args; + + if (!isset($magic_url_match_args[$server_url]) || $static_class != $class) + { + $static_class = $class; + $class = ($static_class) ? ' class="' . $static_class . '"' : ''; + $local_class = ($static_class) ? ' class="' . $static_class . '-local"' : ''; + + if (!is_array($magic_url_match_args)) + { + $magic_url_match_args = array(); + } + + // relative urls for this board + $magic_url_match_args[$server_url][] = array( + '#(^|[\n\t (>.])(' . preg_quote($server_url, '#') . ')/(' . get_preg_expression('relative_url_inline') . ')#iu', + MAGIC_URL_LOCAL, + $local_class, + ); + + // matches a xxxx://aaaaa.bbb.cccc. ... + $magic_url_match_args[$server_url][] = array( + '#(^|[\n\t (>.])(' . get_preg_expression('url_inline') . ')#iu', + MAGIC_URL_FULL, + $class, + ); + + // matches a "www.xxxx.yyyy[/zzzz]" kinda lazy URL thing + $magic_url_match_args[$server_url][] = array( + '#(^|[\n\t (>])(' . get_preg_expression('www_url_inline') . ')#iu', + MAGIC_URL_WWW, + $class, + ); + + // matches an email@domain type address at the start of a line, or after a space or after what might be a BBCode. + $magic_url_match_args[$server_url][] = array( + '/(^|[\n\t (>])(' . get_preg_expression('email') . ')/iu', + MAGIC_URL_EMAIL, + '', + ); + } + + foreach ($magic_url_match_args[$server_url] as $magic_args) + { + if (preg_match($magic_args[0], $text, $matches)) + { + $text = preg_replace_callback($magic_args[0], function($matches) use ($magic_args) + { + $relative_url = isset($matches[3]) ? $matches[3] : ''; + return make_clickable_callback($magic_args[1], $matches[1], $matches[2], $relative_url, $magic_args[2]); + }, $text); + } + } + + return $text; +} + +/** +* Censoring +*/ +function censor_text($text) +{ + static $censors; + + // Nothing to do? + if ($text === '') + { + return ''; + } + + // We moved the word censor checks in here because we call this function quite often - and then only need to do the check once + if (!isset($censors) || !is_array($censors)) + { + global $config, $user, $auth, $cache; + + // We check here if the user is having viewing censors disabled (and also allowed to do so). + if (!$user->optionget('viewcensors') && $config['allow_nocensors'] && $auth->acl_get('u_chgcensors')) + { + $censors = array(); + } + else + { + $censors = $cache->obtain_word_list(); + } + } + + if (count($censors)) + { + return preg_replace($censors['match'], $censors['replace'], $text); + } + + return $text; +} + +/** +* custom version of nl2br which takes custom BBCodes into account +*/ +function bbcode_nl2br($text) +{ + // custom BBCodes might contain carriage returns so they + // are not converted into
so now revert that + $text = str_replace(array("\n", "\r"), array('
', "\n"), $text); + return $text; +} + +/** +* Smiley processing +*/ +function smiley_text($text, $force_option = false) +{ + global $config, $user, $phpbb_path_helper, $phpbb_dispatcher; + + if ($force_option || !$config['allow_smilies'] || !$user->optionget('viewsmilies')) + { + return preg_replace('##', ''; + } + } + + $filesize = get_formatted_filesize($attachment['filesize'], false); + + $comment = bbcode_nl2br(censor_text($attachment['attach_comment'])); + + $block_array += array( + 'UPLOAD_ICON' => $upload_icon, + 'FILESIZE' => $filesize['value'], + 'SIZE_LANG' => $filesize['unit'], + 'DOWNLOAD_NAME' => utf8_basename($attachment['real_filename']), + 'COMMENT' => $comment, + ); + + $denied = false; + + if (!extension_allowed($forum_id, $attachment['extension'], $extensions)) + { + $denied = true; + + $block_array += array( + 'S_DENIED' => true, + 'DENIED_MESSAGE' => sprintf($user->lang['EXTENSION_DISABLED_AFTER_POSTING'], $attachment['extension']) + ); + } + + if (!$denied) + { + $display_cat = $extensions[$attachment['extension']]['display_cat']; + + if ($display_cat == ATTACHMENT_CATEGORY_IMAGE) + { + if ($attachment['thumbnail']) + { + $display_cat = ATTACHMENT_CATEGORY_THUMB; + } + else + { + if ($config['img_display_inlined']) + { + if ($config['img_link_width'] || $config['img_link_height']) + { + $dimension = @getimagesize($filename); + + // If the dimensions could not be determined or the image being 0x0 we display it as a link for safety purposes + if ($dimension === false || empty($dimension[0]) || empty($dimension[1])) + { + $display_cat = ATTACHMENT_CATEGORY_NONE; + } + else + { + $display_cat = ($dimension[0] <= $config['img_link_width'] && $dimension[1] <= $config['img_link_height']) ? ATTACHMENT_CATEGORY_IMAGE : ATTACHMENT_CATEGORY_NONE; + } + } + } + else + { + $display_cat = ATTACHMENT_CATEGORY_NONE; + } + } + } + + // Make some descisions based on user options being set. + if (($display_cat == ATTACHMENT_CATEGORY_IMAGE || $display_cat == ATTACHMENT_CATEGORY_THUMB) && !$user->optionget('viewimg')) + { + $display_cat = ATTACHMENT_CATEGORY_NONE; + } + + if ($display_cat == ATTACHMENT_CATEGORY_FLASH && !$user->optionget('viewflash')) + { + $display_cat = ATTACHMENT_CATEGORY_NONE; + } + + $download_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']); + $l_downloaded_viewed = 'VIEWED_COUNTS'; + + switch ($display_cat) + { + // Images + case ATTACHMENT_CATEGORY_IMAGE: + $inline_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id']); + $download_link .= '&mode=view'; + + $block_array += array( + 'S_IMAGE' => true, + 'U_INLINE_LINK' => $inline_link, + ); + + $update_count_ary[] = $attachment['attach_id']; + break; + + // Images, but display Thumbnail + case ATTACHMENT_CATEGORY_THUMB: + $thumbnail_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $attachment['attach_id'] . '&t=1'); + $download_link .= '&mode=view'; + + $block_array += array( + 'S_THUMBNAIL' => true, + 'THUMB_IMAGE' => $thumbnail_link, + ); + + $update_count_ary[] = $attachment['attach_id']; + break; + + // Macromedia Flash Files + case ATTACHMENT_CATEGORY_FLASH: + list($width, $height) = @getimagesize($filename); + + $block_array += array( + 'S_FLASH_FILE' => true, + 'WIDTH' => $width, + 'HEIGHT' => $height, + 'U_VIEW_LINK' => $download_link . '&view=1', + ); + + // Viewed/Heared File ... update the download count + $update_count_ary[] = $attachment['attach_id']; + break; + + default: + $l_downloaded_viewed = 'DOWNLOAD_COUNTS'; + + $block_array += array( + 'S_FILE' => true, + ); + break; + } + + if (!isset($attachment['download_count'])) + { + $attachment['download_count'] = 0; + } + + $block_array += array( + 'U_DOWNLOAD_LINK' => $download_link, + 'L_DOWNLOAD_COUNT' => $user->lang($l_downloaded_viewed, (int) $attachment['download_count']), + ); + } + + $update_count = $update_count_ary; + /** + * Use this event to modify the attachment template data. + * + * This event is triggered once per attachment. + * + * @event core.parse_attachments_modify_template_data + * @var array attachment Array with attachment data + * @var array block_array Template data of the attachment + * @var int display_cat Attachment category data + * @var string download_link Attachment download link + * @var array extensions Array with attachment extensions data + * @var mixed forum_id The forum id the attachments are displayed in (false if in private message) + * @var bool preview Flag indicating if we are in post preview mode + * @var array update_count Array with attachment ids to update download count + * @since 3.1.0-RC5 + */ + $vars = array( + 'attachment', + 'block_array', + 'display_cat', + 'download_link', + 'extensions', + 'forum_id', + 'preview', + 'update_count', + ); + extract($phpbb_dispatcher->trigger_event('core.parse_attachments_modify_template_data', compact($vars))); + $update_count_ary = $update_count; + unset($update_count); + + $template->assign_block_vars('_file', $block_array); + + $compiled_attachments[] = $template->assign_display('attachment_tpl'); + } + + $attachments = $compiled_attachments; + unset($compiled_attachments); + + $unset_tpl = array(); + + preg_match_all('#(.*?)#', $message, $matches, PREG_PATTERN_ORDER); + + $replace = array(); + foreach ($matches[0] as $num => $capture) + { + $index = $matches[1][$num]; + + $replace['from'][] = $matches[0][$num]; + $replace['to'][] = (isset($attachments[$index])) ? $attachments[$index] : sprintf($user->lang['MISSING_INLINE_ATTACHMENT'], $matches[2][array_search($index, $matches[1])]); + + $unset_tpl[] = $index; + } + + if (isset($replace['from'])) + { + $message = str_replace($replace['from'], $replace['to'], $message); + } + + $unset_tpl = array_unique($unset_tpl); + + // Sort correctly + if ($config['display_order']) + { + // Ascending sort + krsort($attachments); + } + else + { + // Descending sort + ksort($attachments); + } + + // Needed to let not display the inlined attachments at the end of the post again + foreach ($unset_tpl as $index) + { + unset($attachments[$index]); + } +} + +/** +* Check if extension is allowed to be posted. +* +* @param mixed $forum_id The forum id to check or false if private message +* @param string $extension The extension to check, for example zip. +* @param array &$extensions The extension array holding the information from the cache (will be obtained if empty) +* +* @return bool False if the extension is not allowed to be posted, else true. +*/ +function extension_allowed($forum_id, $extension, &$extensions) +{ + if (empty($extensions)) + { + global $cache; + $extensions = $cache->obtain_attach_extensions($forum_id); + } + + return (!isset($extensions['_allowed_'][$extension])) ? false : true; +} + +/** +* Truncates string while retaining special characters if going over the max length +* The default max length is 60 at the moment +* The maximum storage length is there to fit the string within the given length. The string may be further truncated due to html entities. +* For example: string given is 'a "quote"' (length: 9), would be a stored as 'a "quote"' (length: 19) +* +* @param string $string The text to truncate to the given length. String is specialchared. +* @param int $max_length Maximum length of string (multibyte character count as 1 char / Html entity count as 1 char) +* @param int $max_store_length Maximum character length of string (multibyte character count as 1 char / Html entity count as entity chars). +* @param bool $allow_reply Allow Re: in front of string +* NOTE: This parameter can cause undesired behavior (returning strings longer than $max_store_length) and is deprecated. +* @param string $append String to be appended +*/ +function truncate_string($string, $max_length = 60, $max_store_length = 255, $allow_reply = false, $append = '') +{ + $strip_reply = false; + $stripped = false; + if ($allow_reply && strpos($string, 'Re: ') === 0) + { + $strip_reply = true; + $string = substr($string, 4); + } + + $_chars = utf8_str_split(htmlspecialchars_decode($string)); + $chars = array_map('utf8_htmlspecialchars', $_chars); + + // Now check the length ;) + if (count($chars) > $max_length) + { + // Cut off the last elements from the array + $string = implode('', array_slice($chars, 0, $max_length - utf8_strlen($append))); + $stripped = true; + } + + // Due to specialchars, we may not be able to store the string... + if (utf8_strlen($string) > $max_store_length) + { + // let's split again, we do not want half-baked strings where entities are split + $_chars = utf8_str_split(htmlspecialchars_decode($string)); + $chars = array_map('utf8_htmlspecialchars', $_chars); + + do + { + array_pop($chars); + $string = implode('', $chars); + } + while (!empty($chars) && utf8_strlen($string) > $max_store_length); + } + + if ($strip_reply) + { + $string = 'Re: ' . $string; + } + + if ($append != '' && $stripped) + { + $string = $string . $append; + } + + return $string; +} + +/** +* Get username details for placing into templates. +* This function caches all modes on first call, except for no_profile and anonymous user - determined by $user_id. +* +* @param string $mode Can be profile (for getting an url to the profile), username (for obtaining the username), colour (for obtaining the user colour), full (for obtaining a html string representing a coloured link to the users profile) or no_profile (the same as full but forcing no profile link) +* @param int $user_id The users id +* @param string $username The users name +* @param string $username_colour The users colour +* @param string $guest_username optional parameter to specify the guest username. It will be used in favor of the GUEST language variable then. +* @param string $custom_profile_url optional parameter to specify a profile url. The user id get appended to this url as &u={user_id} +* +* @return string A string consisting of what is wanted based on $mode. +*/ +function get_username_string($mode, $user_id, $username, $username_colour = '', $guest_username = false, $custom_profile_url = false) +{ + static $_profile_cache; + global $phpbb_dispatcher; + + // We cache some common variables we need within this function + if (empty($_profile_cache)) + { + global $phpbb_root_path, $phpEx; + + $_profile_cache['base_url'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=viewprofile&u={USER_ID}'); + $_profile_cache['tpl_noprofile'] = '{USERNAME}'; + $_profile_cache['tpl_noprofile_colour'] = '{USERNAME}'; + $_profile_cache['tpl_profile'] = '{USERNAME}'; + $_profile_cache['tpl_profile_colour'] = '{USERNAME}'; + } + + global $user, $auth; + + // This switch makes sure we only run code required for the mode + switch ($mode) + { + case 'full': + case 'no_profile': + case 'colour': + + // Build correct username colour + $username_colour = ($username_colour) ? '#' . $username_colour : ''; + + // Return colour + if ($mode == 'colour') + { + $username_string = $username_colour; + break; + } + + // no break; + + case 'username': + + // Build correct username + if ($guest_username === false) + { + $username = ($username) ? $username : $user->lang['GUEST']; + } + else + { + $username = ($user_id && $user_id != ANONYMOUS) ? $username : ((!empty($guest_username)) ? $guest_username : $user->lang['GUEST']); + } + + // Return username + if ($mode == 'username') + { + $username_string = $username; + break; + } + + // no break; + + case 'profile': + + // Build correct profile url - only show if not anonymous and permission to view profile if registered user + // For anonymous the link leads to a login page. + if ($user_id && $user_id != ANONYMOUS && ($user->data['user_id'] == ANONYMOUS || $auth->acl_get('u_viewprofile'))) + { + $profile_url = ($custom_profile_url !== false) ? $custom_profile_url . '&u=' . (int) $user_id : str_replace(array('={USER_ID}', '=%7BUSER_ID%7D'), '=' . (int) $user_id, $_profile_cache['base_url']); + } + else + { + $profile_url = ''; + } + + // Return profile + if ($mode == 'profile') + { + $username_string = $profile_url; + break; + } + + // no break; + } + + if (!isset($username_string)) + { + if (($mode == 'full' && !$profile_url) || $mode == 'no_profile') + { + $username_string = str_replace(array('{USERNAME_COLOUR}', '{USERNAME}'), array($username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_noprofile'] : $_profile_cache['tpl_noprofile_colour']); + } + else + { + $username_string = str_replace(array('{PROFILE_URL}', '{USERNAME_COLOUR}', '{USERNAME}'), array($profile_url, $username_colour, $username), (!$username_colour) ? $_profile_cache['tpl_profile'] : $_profile_cache['tpl_profile_colour']); + } + } + + /** + * Use this event to change the output of get_username_string() + * + * @event core.modify_username_string + * @var string mode profile|username|colour|full|no_profile + * @var int user_id String or array of additional url + * parameters + * @var string username The user's username + * @var string username_colour The user's colour + * @var string guest_username Optional parameter to specify the + * guest username. + * @var string custom_profile_url Optional parameter to specify a + * profile url. + * @var string username_string The string that has been generated + * @var array _profile_cache Array of original return templates + * @since 3.1.0-a1 + */ + $vars = array( + 'mode', + 'user_id', + 'username', + 'username_colour', + 'guest_username', + 'custom_profile_url', + 'username_string', + '_profile_cache', + ); + extract($phpbb_dispatcher->trigger_event('core.modify_username_string', compact($vars))); + + return $username_string; +} + +/** + * Add an option to the quick-mod tools. + * + * @param string $url The recepting URL for the quickmod actions. + * @param string $option The language key for the value of the option. + * @param string $lang_string The language string to use. + */ +function phpbb_add_quickmod_option($url, $option, $lang_string) +{ + global $template, $user, $phpbb_path_helper; + + $lang_string = $user->lang($lang_string); + $template->assign_block_vars('quickmod', array( + 'VALUE' => $option, + 'TITLE' => $lang_string, + 'LINK' => $phpbb_path_helper->append_url_params($url, array('action' => $option)), + )); +} + +/** +* Concatenate an array into a string list. +* +* @param array $items Array of items to concatenate +* @param object $user The phpBB $user object. +* +* @return string String list. Examples: "A"; "A and B"; "A, B, and C" +*/ +function phpbb_generate_string_list($items, $user) +{ + if (empty($items)) + { + return ''; + } + + $count = count($items); + $last_item = array_pop($items); + $lang_key = 'STRING_LIST_MULTI'; + + if ($count == 1) + { + return $last_item; + } + else if ($count == 2) + { + $lang_key = 'STRING_LIST_SIMPLE'; + } + $list = implode($user->lang['COMMA_SEPARATOR'], $items); + + return $user->lang($lang_key, $list, $last_item); +} + +class bitfield +{ + var $data; + + function __construct($bitfield = '') + { + $this->data = base64_decode($bitfield); + } + + /** + */ + function get($n) + { + // Get the ($n / 8)th char + $byte = $n >> 3; + + if (strlen($this->data) >= $byte + 1) + { + $c = $this->data[$byte]; + + // Lookup the ($n % 8)th bit of the byte + $bit = 7 - ($n & 7); + return (bool) (ord($c) & (1 << $bit)); + } + else + { + return false; + } + } + + function set($n) + { + $byte = $n >> 3; + $bit = 7 - ($n & 7); + + if (strlen($this->data) >= $byte + 1) + { + $this->data[$byte] = $this->data[$byte] | chr(1 << $bit); + } + else + { + $this->data .= str_repeat("\0", $byte - strlen($this->data)); + $this->data .= chr(1 << $bit); + } + } + + function clear($n) + { + $byte = $n >> 3; + + if (strlen($this->data) >= $byte + 1) + { + $bit = 7 - ($n & 7); + $this->data[$byte] = $this->data[$byte] &~ chr(1 << $bit); + } + } + + function get_blob() + { + return $this->data; + } + + function get_base64() + { + return base64_encode($this->data); + } + + function get_bin() + { + $bin = ''; + $len = strlen($this->data); + + for ($i = 0; $i < $len; ++$i) + { + $bin .= str_pad(decbin(ord($this->data[$i])), 8, '0', STR_PAD_LEFT); + } + + return $bin; + } + + function get_all_set() + { + return array_keys(array_filter(str_split($this->get_bin()))); + } + + function merge($bitfield) + { + $this->data = $this->data | $bitfield->get_blob(); + } +} + +/** + * Formats the quote according to the given BBCode status setting + * + * @param phpbb\language\language $language Language class + * @param parse_message $message_parser Message parser class + * @param phpbb\textformatter\utils_interface $text_formatter_utils Text formatter utilities + * @param bool $bbcode_status The status of the BBCode setting + * @param array $quote_attributes The attributes of the quoted post + * @param string $message_link Link of the original quoted post + */ +function phpbb_format_quote($language, $message_parser, $text_formatter_utils, $bbcode_status, $quote_attributes, $message_link = '') +{ + if ($bbcode_status) + { + $quote_text = $text_formatter_utils->generate_quote( + censor_text($message_parser->message), + $quote_attributes + ); + + $message_parser->message = $quote_text . "\n\n"; + } + else + { + $offset = 0; + $quote_string = "> "; + $message = censor_text(trim($message_parser->message)); + // see if we are nesting. It's easily tricked but should work for one level of nesting + if (strpos($message, ">") !== false) + { + $offset = 10; + } + $message = utf8_wordwrap($message, 75 + $offset, "\n"); + + $message = $quote_string . $message; + $message = str_replace("\n", "\n" . $quote_string, $message); + + $message_parser->message = $quote_attributes['author'] . " " . $language->lang('WROTE') . ":\n" . $message . "\n"; + } + + if ($message_link) + { + $message_parser->message = $message_link . $message_parser->message; + } +} diff --git a/includes/functions_convert.php b/includes/functions_convert.php new file mode 100644 index 0000000..2cfbe95 --- /dev/null +++ b/includes/functions_convert.php @@ -0,0 +1,2514 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Default avatar width/height +* @ignore +*/ +define('DEFAULT_AVATAR_X', 80); +define('DEFAULT_AVATAR_Y', 80); + +// Global functions - all functions can be used by convertors + +// SIMPLE FUNCTIONS + +/** +* Return the preceding value +*/ +function dec($var) +{ + return --$var; +} + +/** +* Return the next value +*/ +function inc($var) +{ + return ++$var; +} + +/** +* Return whether the value is positive +*/ +function is_positive($n) +{ + return ($n > 0) ? 1 : 0; +} + +/** +* Boolean inverse of the value +*/ +function not($var) +{ + return ($var) ? 0 : 1; +} + +/** +* Convert a textual value to it's equivalent boolean value +* +* @param string $str String to convert (converts yes, on, y, 1 and true to boolean true) +* @return boolean The equivalent value +*/ +function str_to_bool($str) +{ + $str = strtolower($str); + return ($str == 'yes' || $str == 'on' || $str == 'y' || $str == 'true' || $str == '1') ? true : false; +} + +/** +* Function to mimic php's empty() function (it is the same) +*/ +function is_empty($mixed) +{ + return empty($mixed); +} + +/** +* Convert the name of a user's primary group to the appropriate equivalent phpBB group id +* +* @param string $status The name of the group +* @return int The group_id corresponding to the equivalent group +*/ +function str_to_primary_group($status) +{ + switch (ucfirst(strtolower($status))) + { + case 'Administrator': + return get_group_id('administrators'); + break; + + case 'Super moderator': + case 'Global moderator': + case 'Moderator': + return get_group_id('global_moderators'); + break; + + case 'Guest': + case 'Anonymous': + return get_group_id('guests'); + break; + + default: + return get_group_id('registered'); + break; + } +} + +/** +* Convert a boolean into the appropriate phpBB constant indicating whether the item is locked +*/ +function is_item_locked($bool) +{ + return ($bool) ? ITEM_LOCKED : ITEM_UNLOCKED; +} + +/** +* Convert a value from days to seconds +*/ +function days_to_seconds($days) +{ + return ($days * 86400); +} + +/** +* Determine whether a user is anonymous and return the appropriate new user_id +*/ +function is_user_anonymous($user_id) +{ + return ($user_id > ANONYMOUS) ? $user_id : ANONYMOUS; +} + +/** +* Generate a key value based on existing values +* +* @param int $pad Amount to add to the maximum value +* @return int Key value +*/ +function auto_id($pad = 0) +{ + global $auto_id, $convert_row; + + if (!empty($convert_row['max_id'])) + { + return $convert_row['max_id'] + $pad; + } + + return $auto_id + $pad; +} + +/** +* Convert a boolean into the appropriate phpBB constant indicating whether the user is active +*/ +function set_user_type($user_active) +{ + return ($user_active) ? USER_NORMAL : USER_INACTIVE; +} + +/** +* Convert a value from minutes to hours +*/ +function minutes_to_hours($minutes) +{ + return ($minutes / 3600); +} + +/** +* Return the group_id for a given group name +*/ +function get_group_id($group_name) +{ + global $db, $group_mapping; + + if (empty($group_mapping)) + { + $sql = 'SELECT group_name, group_id + FROM ' . GROUPS_TABLE; + $result = $db->sql_query($sql); + + $group_mapping = array(); + while ($row = $db->sql_fetchrow($result)) + { + $group_mapping[strtoupper($row['group_name'])] = (int) $row['group_id']; + } + $db->sql_freeresult($result); + } + + if (!count($group_mapping)) + { + add_default_groups(); + return get_group_id($group_name); + } + + if (isset($group_mapping[strtoupper($group_name)])) + { + return $group_mapping[strtoupper($group_name)]; + } + + return $group_mapping['REGISTERED']; +} + +/** +* Generate the email hash stored in the users table +* +* Note: Deprecated, calls should directly go to phpbb_email_hash() +*/ +function gen_email_hash($email) +{ + return phpbb_email_hash($email); +} + +/** +* Convert a boolean into the appropriate phpBB constant indicating whether the topic is locked +*/ +function is_topic_locked($bool) +{ + return (!empty($bool)) ? ITEM_LOCKED : ITEM_UNLOCKED; +} + +/** +* Generate a bbcode_uid value +*/ +function make_uid($timestamp) +{ + static $last_timestamp, $last_uid; + + if (empty($last_timestamp) || $timestamp != $last_timestamp) + { + $last_uid = substr(base_convert(unique_id(), 16, 36), 0, BBCODE_UID_LEN); + } + $last_timestamp = $timestamp; + return $last_uid; +} + + +/** +* Validate a website address +*/ +function validate_website($url) +{ + if ($url === 'http://') + { + return ''; + } + else if (!preg_match('#^http[s]?://#i', $url) && strlen($url) > 0) + { + return 'http://' . $url; + } + return $url; +} + +/** +* Convert nulls to zeros for fields which allowed a NULL value in the source but not the destination +*/ +function null_to_zero($value) +{ + return ($value === NULL) ? 0 : $value; +} + +/** +* Convert nulls to empty strings for fields which allowed a NULL value in the source but not the destination +*/ +function null_to_str($value) +{ + return ($value === NULL) ? '' : $value; +} + +// EXTENDED FUNCTIONS + +/** +* Get old config value +*/ +function get_config_value($config_name) +{ + static $convert_config; + + if (!isset($convert_config)) + { + $convert_config = get_config(); + } + + if (!isset($convert_config[$config_name])) + { + return false; + } + + return (empty($convert_config[$config_name])) ? '' : $convert_config[$config_name]; +} + +/** +* Convert an IP address from the hexadecimal notation to normal dotted-quad notation +*/ +function decode_ip($int_ip) +{ + if (!$int_ip) + { + return $int_ip; + } + + $hexipbang = explode('.', chunk_split($int_ip, 2, '.')); + + // Any mod changing the way ips are stored? Then we are not able to convert and enter the ip "as is" to not "destroy" anything... + if (count($hexipbang) < 4) + { + return $int_ip; + } + + return hexdec($hexipbang[0]) . '.' . hexdec($hexipbang[1]) . '.' . hexdec($hexipbang[2]) . '.' . hexdec($hexipbang[3]); +} + +/** +* Reverse the encoding of wild-carded bans +*/ +function decode_ban_ip($int_ip) +{ + return str_replace('255', '*', decode_ip($int_ip)); +} + +/** +* Determine the MIME-type of a specified filename +* This does not actually inspect the file, but simply uses the file extension +*/ +function mimetype($filename) +{ + if (!preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) + { + return 'application/octet-stream'; + } + + switch (strtolower($m[1])) + { + case 'zip': return 'application/zip'; + case 'jpeg': return 'image/jpeg'; + case 'jpg': return 'image/jpeg'; + case 'jpe': return 'image/jpeg'; + case 'png': return 'image/png'; + case 'gif': return 'image/gif'; + case 'htm': + case 'html': return 'text/html'; + case 'tif': return 'image/tiff'; + case 'tiff': return 'image/tiff'; + case 'ras': return 'image/x-cmu-raster'; + case 'pnm': return 'image/x-portable-anymap'; + case 'pbm': return 'image/x-portable-bitmap'; + case 'pgm': return 'image/x-portable-graymap'; + case 'ppm': return 'image/x-portable-pixmap'; + case 'rgb': return 'image/x-rgb'; + case 'xbm': return 'image/x-xbitmap'; + case 'xpm': return 'image/x-xpixmap'; + case 'xwd': return 'image/x-xwindowdump'; + case 'z': return 'application/x-compress'; + case 'gtar': return 'application/x-gtar'; + case 'tgz': return 'application/x-gtar'; + case 'gz': return 'application/x-gzip'; + case 'tar': return 'application/x-tar'; + case 'xls': return 'application/excel'; + case 'pdf': return 'application/pdf'; + case 'ppt': return 'application/powerpoint'; + case 'rm': return 'application/vnd.rn-realmedia'; + case 'wma': return 'audio/x-ms-wma'; + case 'swf': return 'application/x-shockwave-flash'; + case 'ief': return 'image/ief'; + case 'doc': + case 'dot': + case 'wrd': return 'application/msword'; + case 'ai': + case 'eps': + case 'ps': return 'application/postscript'; + case 'asc': + case 'txt': + case 'c': + case 'cc': + case 'h': + case 'hh': + case 'cpp': + case 'hpp': + case 'php': + case 'php3': return 'text/plain'; + default: return 'application/octet-stream'; + } +} + +/** +* Obtain the dimensions of all remotely hosted avatars +* This should only be called from execute_last +* There can be significant network overhead if there are a large number of remote avatars +* @todo Look at the option of allowing the user to decide whether this is called or to force the dimensions +*/ +function remote_avatar_dims() +{ + global $db; + + $sql = 'SELECT user_id, user_avatar + FROM ' . USERS_TABLE . ' + WHERE user_avatar_type = ' . AVATAR_REMOTE; + $result = $db->sql_query($sql); + + $remote_avatars = array(); + while ($row = $db->sql_fetchrow($result)) + { + $remote_avatars[(int) $row['user_id']] = $row['user_avatar']; + } + $db->sql_freeresult($result); + + foreach ($remote_avatars as $user_id => $avatar) + { + $width = (int) get_remote_avatar_dim($avatar, 0); + $height = (int) get_remote_avatar_dim($avatar, 1); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_avatar_width = ' . (int) $width . ', user_avatar_height = ' . (int) $height . ' + WHERE user_id = ' . $user_id; + $db->sql_query($sql); + } +} + +function import_avatar_gallery($gallery_name = '', $subdirs_as_galleries = false) +{ + global $config, $convert, $user; + + $relative_path = empty($convert->convertor['source_path_absolute']); + + // check for trailing slash + if (rtrim($convert->convertor['avatar_gallery_path'], '/') === '') + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_GALLERY_PATH'], 'import_avatar_gallery()'), __LINE__, __FILE__); + } + + $src_path = relative_base(path($convert->convertor['avatar_gallery_path'], $relative_path), $relative_path); + + if (is_dir($src_path)) + { + // Do not die on failure... safe mode restrictions may be in effect. + copy_dir($convert->convertor['avatar_gallery_path'], path($config['avatar_gallery_path']) . $gallery_name, !$subdirs_as_galleries, false, false, $relative_path); + + // only doing 1 level deep. (ibf 1.x) + // notes: ibf has 2 tiers: directly in the avatar directory for base gallery (handled in the above statement), plus subdirs(handled below). + // recursive subdirs ignored. -- i don't know if other forums support recursive galleries. if they do, this following code could be upgraded to be recursive. + if ($subdirs_as_galleries) + { + $dirlist = array(); + if ($handle = @opendir($src_path)) + { + while ($entry = readdir($handle)) + { + if ($entry[0] == '.' || $entry == 'CVS' || $entry == 'index.htm') + { + continue; + } + + if (is_dir($src_path . $entry)) + { + $dirlist[] = $entry; + } + } + closedir($handle); + } + else if ($dir = @dir($src_path)) + { + while ($entry = $dir->read()) + { + if ($entry[0] == '.' || $entry == 'CVS' || $entry == 'index.htm') + { + continue; + } + + if (is_dir($src_path . $entry)) + { + $dirlist[] = $entry; + } + } + $dir->close(); + } + + for ($i = 0, $end = count($dirlist); $i < $end; ++$i) + { + $dir = $dirlist[$i]; + + // Do not die on failure... safe mode restrictions may be in effect. + copy_dir(path($convert->convertor['avatar_gallery_path'], $relative_path) . $dir, path($config['avatar_gallery_path']) . $dir, true, false, false, $relative_path); + } + } + } +} + +function import_attachment_files($category_name = '') +{ + global $config, $convert, $db, $user; + + $sql = 'SELECT config_value AS upload_path + FROM ' . CONFIG_TABLE . " + WHERE config_name = 'upload_path'"; + $result = $db->sql_query($sql); + $config['upload_path'] = $db->sql_fetchfield('upload_path'); + $db->sql_freeresult($result); + + $relative_path = empty($convert->convertor['source_path_absolute']); + + if (empty($convert->convertor['upload_path'])) + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_UPLOAD_DIR'], 'import_attachment_files()'), __LINE__, __FILE__); + } + + if (is_dir(relative_base(path($convert->convertor['upload_path'], $relative_path), $relative_path))) + { + copy_dir($convert->convertor['upload_path'], path($config['upload_path']) . $category_name, true, false, true, $relative_path); + } +} + +function attachment_forum_perms($forum_id) +{ + if (!is_array($forum_id)) + { + $forum_id = array($forum_id); + } + + return serialize($forum_id); +} + +// base64todec function +// -> from php manual? +function base64_unpack($string) +{ + $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-'; + $base = strlen($chars); + + $length = strlen($string); + $number = 0; + + for ($i = 1; $i <= $length; $i++) + { + $pos = $length - $i; + $operand = strpos($chars, substr($string, $pos, 1)); + $exponent = pow($base, $i-1); + $dec_value = $operand * $exponent; + $number += $dec_value; + } + + return $number; +} + +function _import_check($config_var, $source, $use_target) +{ + global $convert, $config; + + $result = array( + 'orig_source' => $source, + 'copied' => false, + 'relative_path' => (empty($convert->convertor['source_path_absolute'])) ? true : false, + ); + + // copy file will prepend $phpBB_root_path + $target = $config[$config_var] . '/' . utf8_basename(($use_target === false) ? $source : $use_target); + + if (!empty($convert->convertor[$config_var]) && strpos($source, $convert->convertor[$config_var]) !== 0) + { + $source = $convert->convertor[$config_var] . $source; + } + + $result['source'] = $source; + + if (file_exists(relative_base($source, $result['relative_path'], __LINE__, __FILE__))) + { + $result['copied'] = copy_file($source, $target, false, false, $result['relative_path']); + } + + if ($result['copied']) + { + $result['target'] = utf8_basename($target); + } + else + { + $result['target'] = ($use_target !== false) ? $result['orig_source'] : utf8_basename($target); + } + + return $result; +} + +function import_attachment($source, $use_target = false) +{ + if (empty($source)) + { + return ''; + } + + global $convert, $config, $user; + + // check for trailing slash + if (rtrim($convert->convertor['upload_path'], '/') === '') + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_UPLOAD_DIR'], 'import_attachment()'), __LINE__, __FILE__); + } + + $result = _import_check('upload_path', $source, $use_target); + + if ($result['copied']) + { + // Thumbnails? + if (is_array($convert->convertor['thumbnails'])) + { + $thumb_dir = $convert->convertor['thumbnails'][0]; + $thumb_prefix = $convert->convertor['thumbnails'][1]; + $thumb_source = $thumb_dir . $thumb_prefix . utf8_basename($result['source']); + + if (strpos($thumb_source, $convert->convertor['upload_path']) !== 0) + { + $thumb_source = $convert->convertor['upload_path'] . $thumb_source; + } + $thumb_target = $config['upload_path'] . '/thumb_' . $result['target']; + + if (file_exists(relative_base($thumb_source, $result['relative_path'], __LINE__, __FILE__))) + { + copy_file($thumb_source, $thumb_target, false, false, $result['relative_path']); + } + } + } + + return $result['target']; +} + +function import_rank($source, $use_target = false) +{ + if (empty($source)) + { + return ''; + } + + global $convert, $user; + + if (!isset($convert->convertor['ranks_path'])) + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_RANKS_PATH'], 'import_rank()'), __LINE__, __FILE__); + } + + $result = _import_check('ranks_path', $source, $use_target); + return $result['target']; +} + +function import_smiley($source, $use_target = false) +{ + if (empty($source)) + { + return ''; + } + + global $convert, $user; + + // check for trailing slash + if (rtrim($convert->convertor['smilies_path'], '/') === '') + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_SMILIES_PATH'], 'import_smiley()'), __LINE__, __FILE__); + } + + $result = _import_check('smilies_path', $source, $use_target); + return $result['target']; +} + +/* +*/ +function import_avatar($source, $use_target = false, $user_id = false) +{ + if (empty($source) || preg_match('#^https?:#i', $source) || preg_match('#blank\.(gif|png)$#i', $source)) + { + return; + } + + global $convert, $config, $user; + + // check for trailing slash + if (rtrim($convert->convertor['avatar_path'], '/') === '') + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_AVATAR_PATH'], 'import_avatar()'), __LINE__, __FILE__); + } + + if ($use_target === false && $user_id !== false) + { + $use_target = $config['avatar_salt'] . '_' . $user_id . '.' . substr(strrchr($source, '.'), 1); + } + + _import_check('avatar_path', $source, $use_target); + + return ((!empty($user_id)) ? $user_id : $use_target) . '.' . substr(strrchr($source, '.'), 1); +} + +/** +* @todo all image dimension functions below (there are a *lot*) should get revisited and converted to one or two functions (no more needed, really). +*/ + +/** +* Calculate the size of the specified image +* Called from the following functions for calculating the size of specific image types +*/ +function get_image_dim($source) +{ + if (empty($source)) + { + return array(0, 0); + } + + global $convert; + + $relative_path = empty($convert->convertor['source_path_absolute']); + + if (file_exists(relative_base($source, $relative_path))) + { + $image = relative_base($source, $relative_path); + return @getimagesize($image); + } + + return false; +} + +/** +* Obtain the width of the specified smilie +*/ +function get_smiley_width($src) +{ + return get_smiley_dim($src, 0); +} + +/** +* Obtain the height of the specified smilie +*/ +function get_smiley_height($src) +{ + return get_smiley_dim($src, 1); +} + +/** +* Obtain the size of the specified smilie (using the cache if possible) and cache the value +*/ +function get_smiley_dim($source, $axis) +{ + if (empty($source)) + { + return 15; + } + + static $smiley_cache = array(); + + if (isset($smiley_cache[$source])) + { + return $smiley_cache[$source][$axis]; + } + + global $convert, $user; + + $orig_source = $source; + + if (!isset($convert->convertor['smilies_path'])) + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_SMILIES_PATH'], 'get_smiley_dim()'), __LINE__, __FILE__); + } + + if (!empty($convert->convertor['smilies_path']) && strpos($source, $convert->convertor['smilies_path']) !== 0) + { + $source = $convert->convertor['smilies_path'] . $source; + } + + $smiley_cache[$orig_source] = get_image_dim($source); + + if (empty($smiley_cache[$orig_source]) || empty($smiley_cache[$orig_source][0]) || empty($smiley_cache[$orig_source][1])) + { + $smiley_cache[$orig_source] = array(15, 15); + return 15; + } + + return $smiley_cache[$orig_source][$axis]; +} + +/** +* Obtain the width of the specified avatar +*/ +function get_avatar_width($src, $func = false, $arg1 = false, $arg2 = false) +{ + return get_avatar_dim($src, 0, $func, $arg1, $arg2); +} + +/** +* Obtain the height of the specified avatar +*/ +function get_avatar_height($src, $func = false, $arg1 = false, $arg2 = false) +{ + return get_avatar_dim($src, 1, $func, $arg1, $arg2); +} + +/** +*/ +function get_avatar_dim($src, $axis, $func = false, $arg1 = false, $arg2 = false) +{ + $avatar_type = AVATAR_UPLOAD; + + if ($func) + { + if ($arg1 || $arg2) + { + $ary = array($arg1); + + if ($arg2) + { + $ary[] = $arg2; + } + + $avatar_type = call_user_func_array($func, $ary); + } + else + { + $avatar_type = call_user_func($func); + } + } + + switch ($avatar_type) + { + case AVATAR_UPLOAD: + return get_upload_avatar_dim($src, $axis); + break; + + case AVATAR_GALLERY: + return get_gallery_avatar_dim($src, $axis); + break; + + case AVATAR_REMOTE: + // see notes on this functions usage and (hopefully) model $func to avoid this accordingly + return get_remote_avatar_dim($src, $axis); + break; + + default: + $default_x = (defined('DEFAULT_AVATAR_X_CUSTOM')) ? DEFAULT_AVATAR_X_CUSTOM : DEFAULT_AVATAR_X; + $default_y = (defined('DEFAULT_AVATAR_Y_CUSTOM')) ? DEFAULT_AVATAR_Y_CUSTOM : DEFAULT_AVATAR_Y; + + return $axis ? $default_y : $default_x; + break; + } +} + +/** +* Obtain the size of the specified uploaded avatar (using the cache if possible) and cache the value +*/ +function get_upload_avatar_dim($source, $axis) +{ + static $cachedims = false; + static $cachekey = false; + + if (empty($source)) + { + return 0; + } + + if ($cachekey == $source) + { + return $cachedims[$axis]; + } + + if (substr($source, 0, 7) == 'upload:') + { + $source = substr($source, 7); + } + + global $convert, $user; + + if (!isset($convert->convertor['avatar_path'])) + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_AVATAR_PATH'], 'get_upload_avatar_dim()'), __LINE__, __FILE__); + } + + if (!empty($convert->convertor['avatar_path']) && strpos($source, $convert->convertor['avatar_path']) !== 0) + { + $source = path($convert->convertor['avatar_path'], empty($convert->convertor['source_path_absolute'])) . $source; + } + + $cachedims = get_image_dim($source); + + if (empty($cachedims) || empty($cachedims[0]) || empty($cachedims[1])) + { + $default_x = (defined('DEFAULT_AVATAR_X_CUSTOM')) ? DEFAULT_AVATAR_X_CUSTOM : DEFAULT_AVATAR_X; + $default_y = (defined('DEFAULT_AVATAR_Y_CUSTOM')) ? DEFAULT_AVATAR_Y_CUSTOM : DEFAULT_AVATAR_Y; + + $cachedims = array($default_x, $default_y); + } + + return $cachedims[$axis]; +} + +/** +* Obtain the size of the specified gallery avatar (using the cache if possible) and cache the value +*/ +function get_gallery_avatar_dim($source, $axis) +{ + if (empty($source)) + { + return 0; + } + + static $avatar_cache = array(); + + if (isset($avatar_cache[$source])) + { + return $avatar_cache[$source][$axis]; + } + + global $convert, $user; + + $orig_source = $source; + + if (!isset($convert->convertor['avatar_gallery_path'])) + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_GALLERY_PATH'], 'get_gallery_avatar_dim()'), __LINE__, __FILE__); + } + + if (!empty($convert->convertor['avatar_gallery_path']) && strpos($source, $convert->convertor['avatar_gallery_path']) !== 0) + { + $source = path($convert->convertor['avatar_gallery_path'], empty($convert->convertor['source_path_absolute'])) . $source; + } + + $avatar_cache[$orig_source] = get_image_dim($source); + + if (empty($avatar_cache[$orig_source]) || empty($avatar_cache[$orig_source][0]) || empty($avatar_cache[$orig_source][1])) + { + $default_x = (defined('DEFAULT_AVATAR_X_CUSTOM')) ? DEFAULT_AVATAR_X_CUSTOM : DEFAULT_AVATAR_X; + $default_y = (defined('DEFAULT_AVATAR_Y_CUSTOM')) ? DEFAULT_AVATAR_Y_CUSTOM : DEFAULT_AVATAR_Y; + + $avatar_cache[$orig_source] = array($default_x, $default_y); + } + + return $avatar_cache[$orig_source][$axis]; +} + +/** +* Obtain the size of the specified remote avatar (using the cache if possible) and cache the value +* Whilst it's unlikely that remote avatars will be duplicated, it is possible so caching seems the best option +* This should only be called from a post processing step due to the possibility of network timeouts +*/ +function get_remote_avatar_dim($src, $axis) +{ + if (empty($src)) + { + return 0; + } + + static $remote_avatar_cache = array(); + + // an ugly hack: we assume that the dimensions of each remote avatar are accessed exactly twice (x and y) + if (isset($remote_avatar_cache[$src])) + { + $retval = $remote_avatar_cache[$src][$axis]; + unset($remote_avatar_cache); + return $retval; + } + + $url_info = @parse_url($src); + if (empty($url_info['host'])) + { + return 0; + } + $host = $url_info['host']; + $port = (isset($url_info['port'])) ? $url_info['port'] : 0; + $protocol = (isset($url_info['scheme'])) ? $url_info['scheme'] : 'http'; + if (empty($port)) + { + switch (strtolower($protocol)) + { + case 'ftp': + $port = 21; + break; + + case 'https': + $port = 443; + break; + + default: + $port = 80; + } + } + + $timeout = @ini_get('default_socket_timeout'); + @ini_set('default_socket_timeout', 2); + + // We're just trying to reach the server to avoid timeouts + $fp = @fsockopen($host, $port, $errno, $errstr, 1); + if ($fp) + { + $remote_avatar_cache[$src] = @getimagesize($src); + fclose($fp); + } + + $default_x = (defined('DEFAULT_AVATAR_X_CUSTOM')) ? DEFAULT_AVATAR_X_CUSTOM : DEFAULT_AVATAR_X; + $default_y = (defined('DEFAULT_AVATAR_Y_CUSTOM')) ? DEFAULT_AVATAR_Y_CUSTOM : DEFAULT_AVATAR_Y; + $default = array($default_x, $default_y); + + if (empty($remote_avatar_cache[$src]) || empty($remote_avatar_cache[$src][0]) || empty($remote_avatar_cache[$src][1])) + { + $remote_avatar_cache[$src] = $default; + } + else + { + // We trust gallery and uploaded avatars to conform to the size settings; we might have to adjust here + if ($remote_avatar_cache[$src][0] > $default_x || $remote_avatar_cache[$src][1] > $default_y) + { + $bigger = ($remote_avatar_cache[$src][0] > $remote_avatar_cache[$src][1]) ? 0 : 1; + $ratio = $default[$bigger] / $remote_avatar_cache[$src][$bigger]; + $remote_avatar_cache[$src][0] = (int) ($remote_avatar_cache[$src][0] * $ratio); + $remote_avatar_cache[$src][1] = (int) ($remote_avatar_cache[$src][1] * $ratio); + } + } + + @ini_set('default_socket_timeout', $timeout); + return $remote_avatar_cache[$src][$axis]; +} + +function set_user_options() +{ + global $convert_row; + + // Key need to be set in row, else default value is chosen + $keyoptions = array( + 'viewimg' => array('bit' => 0, 'default' => 1), + 'viewflash' => array('bit' => 1, 'default' => 1), + 'viewsmilies' => array('bit' => 2, 'default' => 1), + 'viewsigs' => array('bit' => 3, 'default' => 1), + 'viewavatars' => array('bit' => 4, 'default' => 1), + 'viewcensors' => array('bit' => 5, 'default' => 1), + 'attachsig' => array('bit' => 6, 'default' => 0), + 'bbcode' => array('bit' => 8, 'default' => 1), + 'smilies' => array('bit' => 9, 'default' => 1), + 'sig_bbcode' => array('bit' => 15, 'default' => 1), + 'sig_smilies' => array('bit' => 16, 'default' => 1), + 'sig_links' => array('bit' => 17, 'default' => 1), + ); + + $option_field = 0; + + foreach ($keyoptions as $key => $key_ary) + { + $value = (isset($convert_row[$key])) ? (int) $convert_row[$key] : $key_ary['default']; + + if ($value && !($option_field & 1 << $key_ary['bit'])) + { + $option_field += 1 << $key_ary['bit']; + } + } + + return $option_field; +} + +/** +* Index messages on the fly as we convert them +* @todo naderman, can you check that this works with the new search plugins as it's use is currently disabled (and thus untested) +function search_indexing($message = '') +{ + global $fulltext_search, $convert_row; + + if (!isset($convert_row['post_id'])) + { + return; + } + + if (!$message) + { + if (!isset($convert_row['message'])) + { + return; + } + + $message = $convert_row['message']; + } + + $title = (isset($convert_row['title'])) ? $convert_row['title'] : ''; + + $fulltext_search->index('post', $convert_row['post_id'], $message, $title, $convert_row['poster_id'], $convert_row['forum_id']); +} +*/ + +function make_unique_filename($filename) +{ + if (!strlen($filename)) + { + $filename = md5(unique_id()) . '.dat'; + } + else if ($filename[0] == '.') + { + $filename = md5(unique_id()) . $filename; + } + else if (preg_match('/\.([a-z]+)$/i', $filename, $m)) + { + $filename = preg_replace('/\.([a-z]+)$/i', '_' . md5(unique_id()) . '.\1', $filename); + } + else + { + $filename .= '_' . md5(unique_id()) . '.dat'; + } + + return $filename; +} + +function words_unique(&$words) +{ + reset($words); + $return_array = array(); + + $word = current($words); + do + { + $return_array[$word] = $word; + } + while ($word = next($words)); + + return $return_array; +} + +/** +* Adds a user to the specified group and optionally makes them a group leader +* This function does not create the group if it does not exist and so should only be called after the groups have been created +*/ +function add_user_group($group_id, $user_id, $group_leader = false) +{ + global $db; + + $sql = 'INSERT INTO ' . USER_GROUP_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'group_id' => $group_id, + 'user_id' => $user_id, + 'group_leader' => ($group_leader) ? 1 : 0, + 'user_pending' => 0)); + $db->sql_query($sql); +} + +// STANDALONE FUNCTIONS + +/** +* Add users to the pre-defined "special" groups +* +* @param string $group The name of the special group to add to +* @param string $select_query An SQL query to retrieve the user(s) to add to the group +*/ +function user_group_auth($group, $select_query, $use_src_db) +{ + global $convert, $user, $db, $src_db, $same_db; + + if (!in_array($group, array('guests', 'registered', 'registered_coppa', 'global_moderators', 'administrators', 'bots'))) + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_WRONG_GROUP'], $group, 'user_group_auth()'), __LINE__, __FILE__, true); + return; + } + + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $db->sql_escape(strtoupper($group)) . "'"; + $result = $db->sql_query($sql); + $group_id = (int) $db->sql_fetchfield('group_id'); + $db->sql_freeresult($result); + + if (!$group_id) + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_NO_GROUP'], $group, 'user_group_auth()'), __LINE__, __FILE__, true); + return; + } + + if ($same_db || !$use_src_db) + { + $sql = 'INSERT INTO ' . USER_GROUP_TABLE . ' (user_id, group_id, user_pending) + ' . str_replace('{' . strtoupper($group) . '}', $group_id . ', 0', $select_query); + $db->sql_query($sql); + } + else + { + $result = $src_db->sql_query(str_replace('{' . strtoupper($group) . '}', $group_id . ' ', $select_query)); + while ($row = $src_db->sql_fetchrow($result)) + { + // this might become quite a lot of INSERTS unfortunately + $sql = 'INSERT INTO ' . USER_GROUP_TABLE . " (user_id, group_id, user_pending) + VALUES ({$row['user_id']}, $group_id, 0)"; + $db->sql_query($sql); + } + $src_db->sql_freeresult($result); + } +} + +/** +* Retrieves configuration information from the source forum and caches it as an array +* Both database and file driven configuration formats can be handled +* (the type used is specified in $config_schema, see convert_phpbb20.php for more details) +*/ +function get_config() +{ + static $convert_config; + global $user; + + if (isset($convert_config)) + { + return $convert_config; + } + + global $src_db, $same_db; + global $convert; + + if ($convert->config_schema['table_format'] != 'file') + { + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + $sql = 'SELECT * FROM ' . $convert->src_table_prefix . $convert->config_schema['table_name']; + $result = $src_db->sql_query($sql); + $row = $src_db->sql_fetchrow($result); + + if (!$row) + { + $convert->p_master->error($user->lang['CONV_ERROR_GET_CONFIG'], __LINE__, __FILE__); + } + } + + if (is_array($convert->config_schema['table_format'])) + { + $convert_config = array(); + list($key, $val) = each($convert->config_schema['table_format']); + + do + { + $convert_config[$row[$key]] = $row[$val]; + } + while ($row = $src_db->sql_fetchrow($result)); + $src_db->sql_freeresult($result); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + } + else if ($convert->config_schema['table_format'] == 'file') + { + $filename = $convert->options['forum_path'] . '/' . $convert->config_schema['filename']; + if (!file_exists($filename)) + { + $convert->p_master->error($user->lang('FILE_NOT_FOUND', $filename), __LINE__, __FILE__); + } + + if (isset($convert->config_schema['array_name'])) + { + unset($convert->config_schema['array_name']); + } + + $convert_config = extract_variables_from_file($filename); + if (!empty($convert->config_schema['array_name'])) + { + $convert_config = $convert_config[$convert->config_schema['array_name']]; + } + } + else + { + $convert_config = $row; + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + } + + if (!count($convert_config)) + { + $convert->p_master->error($user->lang['CONV_ERROR_CONFIG_EMPTY'], __LINE__, __FILE__); + } + + return $convert_config; +} + +/** +* Transfers the relevant configuration information from the source forum +* The mapping of fields is specified in $config_schema, see convert_phpbb20.php for more details +*/ +function restore_config($schema) +{ + global $config; + + $convert_config = get_config(); + + foreach ($schema['settings'] as $config_name => $src) + { + if (preg_match('/(.*)\((.*)\)/', $src, $m)) + { + $var = (empty($m[2]) || empty($convert_config[$m[2]])) ? "''" : "'" . addslashes($convert_config[$m[2]]) . "'"; + $exec = '$config_value = ' . $m[1] . '(' . $var . ');'; + // @codingStandardsIgnoreStart + eval($exec); + // @codingStandardsIgnoreEnd + } + else + { + if ($schema['table_format'] != 'file' || empty($schema['array_name'])) + { + $config_value = (isset($convert_config[$src])) ? $convert_config[$src] : ''; + } + else if (!empty($schema['array_name'])) + { + $src_ary = $schema['array_name']; + $config_value = (isset($convert_config[$src_ary][$src])) ? $convert_config[$src_ary][$src] : ''; + } + } + + if ($config_value !== '') + { + // Most are... + if (is_string($config_value)) + { + $config_value = truncate_string(utf8_htmlspecialchars($config_value), 255, 255, false); + } + + $config->set($config_name, $config_value); + } + } +} + +/** +* Update the count of PM's in custom folders for all users +*/ +function update_folder_pm_count() +{ + global $db; + + $sql = 'SELECT user_id, folder_id, COUNT(msg_id) as num_messages + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE folder_id NOT IN (' . PRIVMSGS_NO_BOX . ', ' . PRIVMSGS_HOLD_BOX . ', ' . PRIVMSGS_INBOX . ', ' . PRIVMSGS_OUTBOX . ', ' . PRIVMSGS_SENTBOX . ') + GROUP BY folder_id, user_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $db->sql_query('UPDATE ' . PRIVMSGS_FOLDER_TABLE . ' SET pm_count = ' . $row['num_messages'] . ' + WHERE user_id = ' . $row['user_id'] . ' AND folder_id = ' . $row['folder_id']); + } + $db->sql_freeresult($result); +} + +// Functions mainly used by the main convertor script + +function path($path, $path_relative = true) +{ + if ($path === false) + { + return ''; + } + + if (substr($path, -1) != '/') + { + $path .= '/'; + } + + if (!$path_relative) + { + return $path; + } + + if (substr($path, 0, 1) == '/') + { + $path = substr($path, 1); + } + + return $path; +} + +/** +* Extract the variables defined in a configuration file +* @todo As noted by Xore we need to look at this from a security perspective +*/ +function extract_variables_from_file($_filename) +{ + include($_filename); + + $vars = get_defined_vars(); + unset($vars['_filename']); + + return $vars; +} + +function get_path($src_path, $src_url, $test_file) +{ + global $phpbb_root_path, $phpEx; + + $board_config = get_config(); + + $test_file = preg_replace('/\.php$/i', ".$phpEx", $test_file); + $src_path = path($src_path); + + if (@file_exists($phpbb_root_path . $src_path . $test_file)) + { + return $src_path; + } + + if (!empty($src_url) && !empty($board_config['server_name'])) + { + if (!preg_match('#https?://([^/]+)(.*)#i', $src_url, $m)) + { + return false; + } + + if ($m[1] != $board_config['server_name']) + { + return false; + } + + $url_parts = explode('/', $m[2]); + if (substr($src_url, -1) != '/') + { + if (preg_match('/.*\.([a-z0-9]{3,4})$/i', $url_parts[count($url_parts) - 1])) + { + $url_parts[count($url_parts) - 1] = ''; + } + else + { + $url_parts[] = ''; + } + } + + $script_path = $board_config['script_path']; + if (substr($script_path, -1) == '/') + { + $script_path = substr($script_path, 0, -1); + } + + $path_array = array(); + + $phpbb_parts = explode('/', $script_path); + for ($i = 0, $end = count($url_parts); $i < $end; ++$i) + { + if ($i < count($phpbb_parts[$i]) && $url_parts[$i] == $phpbb_parts[$i]) + { + $path_array[] = $url_parts[$i]; + unset($url_parts[$i]); + } + else + { + $path = ''; + for ($j = $i, $end2 = count($phpbb_parts); $j < $end2; ++$j) + { + $path .= '../'; + } + $path .= implode('/', $url_parts); + break; + } + } + + if (!empty($path)) + { + if (@file_exists($phpbb_root_path . $path . $test_file)) + { + return $path; + } + } + } + + return false; +} + +function compare_table($tables, $tablename, &$prefixes) +{ + for ($i = 0, $table_size = count($tables); $i < $table_size; ++$i) + { + if (preg_match('/(.*)' . $tables[$i] . '$/', $tablename, $m)) + { + if (empty($m[1])) + { + $m[1] = '*'; + } + + if (isset($prefixes[$m[1]])) + { + $prefixes[$m[1]]++; + } + else + { + $prefixes[$m[1]] = 1; + } + } + } +} + +/** +* Grant permissions to a specified user or group +* +* @param string $ug_type user|group|user_role|group_role +* @param mixed $forum_id forum ids (array|int|0) -> 0 == all forums +* @param mixed $ug_id [int] user_id|group_id : [string] usergroup name +* @param mixed $acl_list [string] acl entry : [array] acl entries : [string] role entry +* @param int $setting ACL_YES|ACL_NO|ACL_NEVER +*/ +function mass_auth($ug_type, $forum_id, $ug_id, $acl_list, $setting = ACL_NO) +{ + global $db; + static $acl_option_ids, $group_ids; + + if (($ug_type == 'group' || $ug_type == 'group_role') && is_string($ug_id)) + { + if (!isset($group_ids[$ug_id])) + { + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $db->sql_escape(strtoupper($ug_id)) . "'"; + $result = $db->sql_query_limit($sql, 1); + $id = (int) $db->sql_fetchfield('group_id'); + $db->sql_freeresult($result); + + if (!$id) + { + return; + } + + $group_ids[$ug_id] = $id; + } + + $ug_id = (int) $group_ids[$ug_id]; + } + + $table = ($ug_type == 'user' || $ug_type == 'user_role') ? ACL_USERS_TABLE : ACL_GROUPS_TABLE; + $id_field = ($ug_type == 'user' || $ug_type == 'user_role') ? 'user_id' : 'group_id'; + + // Role based permissions are the simplest to handle so check for them first + if ($ug_type == 'user_role' || $ug_type == 'group_role') + { + if (is_numeric($forum_id)) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = 'ROLE_" . $db->sql_escape($acl_list) . "'"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // If we have no role id there is something wrong here + if ($row) + { + $sql = "INSERT INTO $table ($id_field, forum_id, auth_role_id) VALUES ($ug_id, $forum_id, " . $row['role_id'] . ')'; + $db->sql_query($sql); + } + } + + return; + } + + // Build correct parameters + $auth = array(); + + if (!is_array($acl_list)) + { + $auth = array($acl_list => $setting); + } + else + { + foreach ($acl_list as $auth_option) + { + $auth[$auth_option] = $setting; + } + } + unset($acl_list); + + if (!is_array($forum_id)) + { + $forum_id = array($forum_id); + } + + // Set any flags as required + foreach ($auth as $auth_option => $acl_setting) + { + $flag = substr($auth_option, 0, strpos($auth_option, '_') + 1); + if (empty($auth[$flag])) + { + $auth[$flag] = $acl_setting; + } + } + + if (!is_array($acl_option_ids) || empty($acl_option_ids)) + { + $sql = 'SELECT auth_option_id, auth_option + FROM ' . ACL_OPTIONS_TABLE; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $acl_option_ids[$row['auth_option']] = $row['auth_option_id']; + } + $db->sql_freeresult($result); + } + + $sql_forum = 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id), false, true); + + $sql = ($ug_type == 'user') ? 'SELECT o.auth_option_id, o.auth_option, a.forum_id, a.auth_setting FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . " o WHERE a.auth_option_id = o.auth_option_id $sql_forum AND a.user_id = $ug_id" : 'SELECT o.auth_option_id, o.auth_option, a.forum_id, a.auth_setting FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . " o WHERE a.auth_option_id = o.auth_option_id $sql_forum AND a.group_id = $ug_id"; + $result = $db->sql_query($sql); + + $cur_auth = array(); + while ($row = $db->sql_fetchrow($result)) + { + $cur_auth[$row['forum_id']][$row['auth_option_id']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + + $sql_ary = array(); + foreach ($forum_id as $forum) + { + foreach ($auth as $auth_option => $setting) + { + $auth_option_id = $acl_option_ids[$auth_option]; + + if (!$auth_option_id) + { + continue; + } + + switch ($setting) + { + case ACL_NO: + if (isset($cur_auth[$forum][$auth_option_id])) + { + $sql_ary['delete'][] = "DELETE FROM $table + WHERE forum_id = $forum + AND auth_option_id = $auth_option_id + AND $id_field = $ug_id"; + } + break; + + default: + if (!isset($cur_auth[$forum][$auth_option_id])) + { + $sql_ary['insert'][] = "$ug_id, $forum, $auth_option_id, $setting"; + } + else if ($cur_auth[$forum][$auth_option_id] != $setting) + { + $sql_ary['update'][] = "UPDATE " . $table . " + SET auth_setting = $setting + WHERE $id_field = $ug_id + AND forum_id = $forum + AND auth_option_id = $auth_option_id"; + } + } + } + } + unset($cur_auth); + + $sql = ''; + foreach ($sql_ary as $sql_type => $sql_subary) + { + switch ($sql_type) + { + case 'insert': + switch ($db->get_sql_layer()) + { + case 'mysql': + case 'mysql4': + $sql = 'VALUES ' . implode(', ', preg_replace('#^(.*?)$#', '(\1)', $sql_subary)); + break; + + case 'sqlite3': + case 'mssqlnative': + $sql = implode(' UNION ALL ', preg_replace('#^(.*?)$#', 'SELECT \1', $sql_subary)); + break; + + default: + foreach ($sql_subary as $sql) + { + $sql = "INSERT INTO $table ($id_field, forum_id, auth_option_id, auth_setting) VALUES ($sql)"; + $db->sql_query($sql); + $sql = ''; + } + } + + if ($sql != '') + { + $sql = "INSERT INTO $table ($id_field, forum_id, auth_option_id, auth_setting) $sql"; + $db->sql_query($sql); + } + break; + + case 'update': + case 'delete': + foreach ($sql_subary as $sql) + { + $db->sql_query($sql); + $sql = ''; + } + break; + } + unset($sql_ary[$sql_type]); + } + unset($sql_ary); + +} + +/** +* Update the count of unread private messages for all users +*/ +function update_unread_count() +{ + global $db; + + $sql = 'SELECT user_id, COUNT(msg_id) as num_messages + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE pm_unread = 1 + AND folder_id <> ' . PRIVMSGS_OUTBOX . ' + GROUP BY user_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_unread_privmsg = ' . $row['num_messages'] . ' + WHERE user_id = ' . $row['user_id']); + } + $db->sql_freeresult($result); +} + +/** +* Add any of the pre-defined "special" groups which are missing from the database +*/ +function add_default_groups() +{ + global $db; + + $default_groups = array( + 'GUESTS' => array('', 0, 0), + 'REGISTERED' => array('', 0, 0), + 'REGISTERED_COPPA' => array('', 0, 0), + 'GLOBAL_MODERATORS' => array('00AA00', 2, 0), + 'ADMINISTRATORS' => array('AA0000', 1, 1), + 'BOTS' => array('9E8DA7', 0, 0), + 'NEWLY_REGISTERED' => array('', 0, 0), + ); + + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('group_name', array_keys($default_groups)); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + unset($default_groups[strtoupper($row['group_name'])]); + } + $db->sql_freeresult($result); + + $sql_ary = array(); + + foreach ($default_groups as $name => $data) + { + $sql_ary[] = array( + 'group_name' => (string) $name, + 'group_desc' => '', + 'group_desc_uid' => '', + 'group_desc_bitfield' => '', + 'group_type' => GROUP_SPECIAL, + 'group_colour' => (string) $data[0], + 'group_legend' => (int) $data[1], + 'group_founder_manage' => (int) $data[2], + ); + } + + if (count($sql_ary)) + { + $db->sql_multi_insert(GROUPS_TABLE, $sql_ary); + } +} + +function add_groups_to_teampage() +{ + global $db; + + $teampage_groups = array( + 'ADMINISTRATORS' => 1, + 'GLOBAL_MODERATORS' => 2, + ); + + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('group_name', array_keys($teampage_groups)); + $result = $db->sql_query($sql); + + $teampage_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $teampage_ary[] = array( + 'group_id' => (int) $row['group_id'], + 'teampage_name' => '', + 'teampage_position' => (int) $teampage_groups[$row['group_name']], + 'teampage_parent' => 0, + ); + } + $db->sql_freeresult($result); + + if (count($teampage_ary)) + { + $db->sql_multi_insert(TEAMPAGE_TABLE, $teampage_ary); + } +} + + +/** +* Sync post count. We might need to do this in batches. +*/ +function sync_post_count($offset, $limit) +{ + global $db; + $sql = 'SELECT COUNT(post_id) AS num_posts, poster_id + FROM ' . POSTS_TABLE . ' + WHERE post_postcount = 1 + AND post_visibility = ' . ITEM_APPROVED . ' + GROUP BY poster_id + ORDER BY poster_id'; + $result = $db->sql_query_limit($sql, $limit, $offset); + + while ($row = $db->sql_fetchrow($result)) + { + $db->sql_query('UPDATE ' . USERS_TABLE . " SET user_posts = {$row['num_posts']} WHERE user_id = {$row['poster_id']}"); + } + $db->sql_freeresult($result); +} + +/** +* Add the search bots into the database +* This code should be used in execute_last if the source database did not have bots +* If you are converting bots this function should not be called +* @todo We might want to look at sharing the bot list between the install code and this code for consistancy +*/ +function add_bots() +{ + global $db, $convert, $user, $config, $phpbb_root_path, $phpEx; + + $db->sql_query($convert->truncate_statement . BOTS_TABLE); + + $sql = 'SELECT group_id FROM ' . GROUPS_TABLE . " WHERE group_name = 'BOTS'"; + $result = $db->sql_query($sql); + $group_id = (int) $db->sql_fetchfield('group_id', false, $result); + $db->sql_freeresult($result); + + if (!$group_id) + { + add_default_groups(); + + $sql = 'SELECT group_id FROM ' . GROUPS_TABLE . " WHERE group_name = 'BOTS'"; + $result = $db->sql_query($sql); + $group_id = (int) $db->sql_fetchfield('group_id', false, $result); + $db->sql_freeresult($result); + + if (!$group_id) + { + global $install; + $install->error($user->lang['CONV_ERROR_INCONSISTENT_GROUPS'], __LINE__, __FILE__); + } + } + + $bots = array( + 'AdsBot [Google]' => array('AdsBot-Google', ''), + 'Alexa [Bot]' => array('ia_archiver', ''), + 'Alta Vista [Bot]' => array('Scooter/', ''), + 'Ask Jeeves [Bot]' => array('Ask Jeeves', ''), + 'Baidu [Spider]' => array('Baiduspider+(', ''), + 'Bing [Bot]' => array('bingbot/', ''), + 'Exabot [Bot]' => array('Exabot/', ''), + 'FAST Enterprise [Crawler]' => array('FAST Enterprise Crawler', ''), + 'FAST WebCrawler [Crawler]' => array('FAST-WebCrawler/', ''), + 'Francis [Bot]' => array('http://www.neomo.de/', ''), + 'Gigabot [Bot]' => array('Gigabot/', ''), + 'Google Adsense [Bot]' => array('Mediapartners-Google', ''), + 'Google Desktop' => array('Google Desktop', ''), + 'Google Feedfetcher' => array('Feedfetcher-Google', ''), + 'Google [Bot]' => array('Googlebot', ''), + 'Heise IT-Markt [Crawler]' => array('heise-IT-Markt-Crawler', ''), + 'Heritrix [Crawler]' => array('heritrix/1.', ''), + 'IBM Research [Bot]' => array('ibm.com/cs/crawler', ''), + 'ICCrawler - ICjobs' => array('ICCrawler - ICjobs', ''), + 'ichiro [Crawler]' => array('ichiro/2', ''), + 'Majestic-12 [Bot]' => array('MJ12bot/', ''), + 'Metager [Bot]' => array('MetagerBot/', ''), + 'MSN NewsBlogs' => array('msnbot-NewsBlogs/', ''), + 'MSN [Bot]' => array('msnbot/', ''), + 'MSNbot Media' => array('msnbot-media/', ''), + 'NG-Search [Bot]' => array('NG-Search/', ''), + 'Nutch [Bot]' => array('http://lucene.apache.org/nutch/', ''), + 'Nutch/CVS [Bot]' => array('NutchCVS/', ''), + 'OmniExplorer [Bot]' => array('OmniExplorer_Bot/', ''), + 'Online link [Validator]' => array('online link validator', ''), + 'psbot [Picsearch]' => array('psbot/0', ''), + 'Seekport [Bot]' => array('Seekbot/', ''), + 'Sensis [Crawler]' => array('Sensis Web Crawler', ''), + 'SEO Crawler' => array('SEO search Crawler/', ''), + 'Seoma [Crawler]' => array('Seoma [SEO Crawler]', ''), + 'SEOSearch [Crawler]' => array('SEOsearch/', ''), + 'Snappy [Bot]' => array('Snappy/1.1 ( http://www.urltrends.com/ )', ''), + 'Steeler [Crawler]' => array('http://www.tkl.iis.u-tokyo.ac.jp/~crawler/', ''), + 'Synoo [Bot]' => array('SynooBot/', ''), + 'Telekom [Bot]' => array('crawleradmin.t-info@telekom.de', ''), + 'TurnitinBot [Bot]' => array('TurnitinBot/', ''), + 'Voyager [Bot]' => array('voyager/1.0', ''), + 'W3 [Sitesearch]' => array('W3 SiteSearch Crawler', ''), + 'W3C [Linkcheck]' => array('W3C-checklink/', ''), + 'W3C [Validator]' => array('W3C_*Validator', ''), + 'WiseNut [Bot]' => array('http://www.WISEnutbot.com', ''), + 'YaCy [Bot]' => array('yacybot', ''), + 'Yahoo MMCrawler [Bot]' => array('Yahoo-MMCrawler/', ''), + 'Yahoo Slurp [Bot]' => array('Yahoo! DE Slurp', ''), + 'Yahoo [Bot]' => array('Yahoo! Slurp', ''), + 'YahooSeeker [Bot]' => array('YahooSeeker/', ''), + ); + + if (!function_exists('user_add')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + foreach ($bots as $bot_name => $bot_ary) + { + $user_row = array( + 'user_type' => USER_IGNORE, + 'group_id' => $group_id, + 'username' => $bot_name, + 'user_regdate' => time(), + 'user_password' => '', + 'user_colour' => '9E8DA7', + 'user_email' => '', + 'user_lang' => $config['default_lang'], + 'user_style' => 1, + 'user_timezone' => 'UTC', + 'user_allow_massemail' => 0, + ); + + $user_id = user_add($user_row); + + if ($user_id) + { + $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'bot_active' => 1, + 'bot_name' => $bot_name, + 'user_id' => $user_id, + 'bot_agent' => $bot_ary[0], + 'bot_ip' => $bot_ary[1]) + ); + $db->sql_query($sql); + } + } +} + +/** +* Update any dynamic configuration variables after the conversion is finished +* @todo Confirm that this updates all relevant values since it has not necessarily been kept in sync with all changes +*/ +function update_dynamic_config() +{ + global $db, $config; + + // Get latest username + $sql = 'SELECT user_id, username, user_colour + FROM ' . USERS_TABLE . ' + WHERE user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; + + if (!empty($config['increment_user_id'])) + { + $sql .= ' AND user_id <> ' . $config['increment_user_id']; + } + + $sql .= ' ORDER BY user_id DESC'; + + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $config->set('newest_user_id', $row['user_id'], false); + $config->set('newest_username', $row['username'], false); + $config->set('newest_user_colour', $row['user_colour'], false); + } + +// Also do not reset record online user/date. There will be old data or the fresh data from the schema. +// set_config('record_online_users', 1, true); +// set_config('record_online_date', time(), true); + + $sql = 'SELECT COUNT(post_id) AS stat + FROM ' . POSTS_TABLE . ' + WHERE post_visibility = ' . ITEM_APPROVED; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $config->set('num_posts', (int) $row['stat'], false); + + $sql = 'SELECT COUNT(topic_id) AS stat + FROM ' . TOPICS_TABLE . ' + WHERE topic_visibility = ' . ITEM_APPROVED; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $config->set('num_topics', (int) $row['stat'], false); + + $sql = 'SELECT COUNT(user_id) AS stat + FROM ' . USERS_TABLE . ' + WHERE user_type IN (' . USER_NORMAL . ',' . USER_FOUNDER . ')'; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $config->set('num_users', (int) $row['stat'], false); + + $sql = 'SELECT COUNT(attach_id) as stat + FROM ' . ATTACHMENTS_TABLE . ' + WHERE is_orphan = 0'; + $result = $db->sql_query($sql); + $config->set('num_files', (int) $db->sql_fetchfield('stat'), false); + $db->sql_freeresult($result); + + $sql = 'SELECT SUM(filesize) as stat + FROM ' . ATTACHMENTS_TABLE . ' + WHERE is_orphan = 0'; + $result = $db->sql_query($sql); + $config->set('upload_dir_size', (float) $db->sql_fetchfield('stat'), false); + $db->sql_freeresult($result); + + /** + * We do not resync users post counts - this can be done by the admin after conversion if wanted. + $sql = 'SELECT COUNT(post_id) AS num_posts, poster_id + FROM ' . POSTS_TABLE . ' + WHERE post_postcount = 1 + GROUP BY poster_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $db->sql_query('UPDATE ' . USERS_TABLE . " SET user_posts = {$row['num_posts']} WHERE user_id = {$row['poster_id']}"); + } + $db->sql_freeresult($result); + */ +} + +/** +* Updates topics_posted entries +*/ +function update_topics_posted() +{ + global $db; + + switch ($db->get_sql_layer()) + { + case 'sqlite3': + $db->sql_query('DELETE FROM ' . TOPICS_POSTED_TABLE); + break; + + default: + $db->sql_query('TRUNCATE TABLE ' . TOPICS_POSTED_TABLE); + break; + } + + // This can get really nasty... therefore we only do the last six months + $get_from_time = time() - (6 * 4 * 7 * 24 * 60 * 60); + + // Select forum ids, do not include categories + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type <> ' . FORUM_CAT; + $result = $db->sql_query($sql); + + $forum_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_ids[] = $row['forum_id']; + } + $db->sql_freeresult($result); + + // Any global announcements? ;) + $forum_ids[] = 0; + + // Now go through the forums and get us some topics... + foreach ($forum_ids as $forum_id) + { + $sql = 'SELECT p.poster_id, p.topic_id + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t + WHERE t.forum_id = ' . $forum_id . ' + AND t.topic_moved_id = 0 + AND t.topic_last_post_time > ' . $get_from_time . ' + AND t.topic_id = p.topic_id + AND p.poster_id <> ' . ANONYMOUS . ' + GROUP BY p.poster_id, p.topic_id'; + $result = $db->sql_query($sql); + + $posted = array(); + while ($row = $db->sql_fetchrow($result)) + { + $posted[$row['poster_id']][] = $row['topic_id']; + } + $db->sql_freeresult($result); + + $sql_ary = array(); + foreach ($posted as $user_id => $topic_row) + { + foreach ($topic_row as $topic_id) + { + $sql_ary[] = array( + 'user_id' => (int) $user_id, + 'topic_id' => (int) $topic_id, + 'topic_posted' => 1, + ); + } + } + unset($posted); + + if (count($sql_ary)) + { + $db->sql_multi_insert(TOPICS_POSTED_TABLE, $sql_ary); + } + } +} + +/** +* Ensure that all users have a default group specified and update related information such as their colour +*/ +function fix_empty_primary_groups() +{ + global $db; + + // Set group ids for users not already having it + $sql = 'UPDATE ' . USERS_TABLE . ' SET group_id = ' . get_group_id('registered') . ' + WHERE group_id = 0 AND user_type = ' . USER_INACTIVE; + $db->sql_query($sql); + + $sql = 'UPDATE ' . USERS_TABLE . ' SET group_id = ' . get_group_id('registered') . ' + WHERE group_id = 0 AND user_type = ' . USER_NORMAL; + $db->sql_query($sql); + + $db->sql_query('UPDATE ' . USERS_TABLE . ' SET group_id = ' . get_group_id('guests') . ' WHERE user_id = ' . ANONYMOUS); + + $sql = 'SELECT user_id FROM ' . USER_GROUP_TABLE . ' WHERE group_id = ' . get_group_id('administrators'); + $result = $db->sql_query($sql); + + $user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_ids[] = $row['user_id']; + } + $db->sql_freeresult($result); + + if (count($user_ids)) + { + $db->sql_query('UPDATE ' . USERS_TABLE . ' SET group_id = ' . get_group_id('administrators') . ' + WHERE group_id = 0 AND ' . $db->sql_in_set('user_id', $user_ids)); + } + + $sql = 'SELECT user_id FROM ' . USER_GROUP_TABLE . ' WHERE group_id = ' . get_group_id('global_moderators'); + $result = $db->sql_query($sql); + + $user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_ids[] = $row['user_id']; + } + $db->sql_freeresult($result); + + if (count($user_ids)) + { + $db->sql_query('UPDATE ' . USERS_TABLE . ' SET group_id = ' . get_group_id('global_moderators') . ' + WHERE group_id = 0 AND ' . $db->sql_in_set('user_id', $user_ids)); + } + + // Set user colour + $sql = 'SELECT group_id, group_colour FROM ' . GROUPS_TABLE . " + WHERE group_colour <> ''"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $db->sql_query('UPDATE ' . USERS_TABLE . " SET user_colour = '{$row['group_colour']}' WHERE group_id = {$row['group_id']}"); + } + $db->sql_freeresult($result); +} + +/** +* Cleanly remove invalid user entries after converting the users table... +*/ +function remove_invalid_users() +{ + global $db, $phpEx, $phpbb_root_path; + + // username_clean is UNIQUE + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . " + WHERE username_clean = ''"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + if (!function_exists('user_delete')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + user_delete('remove', $row['user_id']); + } +} + +function convert_bbcode($message, $convert_size = true, $extended_bbcodes = false) +{ + static $orig, $repl, $origx, $replx, $str_from, $str_to; + + if (empty($orig)) + { + $orig = $repl = array(); + + $orig[] = '#\[(php|sql)\](.*?)\[/(php|sql)\]#is'; + $repl[] = '[code]\2[/code]'; + + $orig[] = '#\[font=[^\]]+\](.*?)\[/font\]#is'; + $repl[] = '\1'; + + $orig[] = '#\[align=[a-z]+\](.*?)\[/align\]#is'; + $repl[] = '\1'; + + $orig[] = '#\[/list=.*?\]#is'; + $repl[] = '[/list]'; + + $origx = array( + '#\[glow[^\]]+\](.*?)\[/glow\]#is', + '#\[shadow[^\]]+\](.*?)\[/shadow\]#is', + '#\[flash[^\]]+\](.*?)\[/flash\]#is' + ); + + $replx = array( + '\1', + '\1', + '[url=\1]Flash[/url]' + ); + + $str_from = array( + '[ftp]', '[/ftp]', + '[ftp=', '[/ftp]', + '[pre]', '[/pre]', + '[table]', '[/table]', + '[td]', '[/td]', + '[tr]', '[/tr]', + '[s]', '[/s]', + '[left]', '[/left]', + '[right]', '[/right]', + '[center]', '[/center]', + '[sub]', '[/sub]', + '[sup]', '[/sup]', + '[tt]', '[/tt]', + '[move]', '[/move]', + '[hr]' + ); + + $str_to = array( + '[url]', '[/url]', + '[url=', '[/url]', + '[code]', '[/code]', + "\n", '', + '', '', + "\n", '', + '', '', + '', '', + '', '', + '', '', + '', '', + '', '', + '', '', + '', '', + "\n\n" + ); + + for ($i = 0, $end = count($str_from); $i < $end; ++$i) + { + $origx[] = '#\\' . str_replace(']', '\\]', $str_from[$i]) . '#is'; + $replx[] = $str_to[$i]; + } + } + + if (preg_match_all('#\[email=([^\]]+)\](.*?)\[/email\]#i', $message, $m)) + { + for ($i = 0, $end = count($m[1]); $i < $end; ++$i) + { + if ($m[1][$i] == $m[2][$i]) + { + $message = str_replace($m[0][$i], '[email]' . $m[1][$i] . '[/email]', $message); + } + else + { + $message = str_replace($m[0][$i], $m[2][$i] . ' ([email]' . $m[1][$i] . '[/email])', $message); + } + } + } + + if ($convert_size && preg_match('#\[size=[0-9]+\].*?\[/size\]#i', $message)) + { + $size = array(9, 9, 12, 15, 18, 24, 29, 29, 29, 29); + $message = preg_replace('#\[size=([0-9]+)\](.*?)\[/size\]#i', '[size=\1]\2[/size]', $message); + $message = preg_replace('#\[size=[0-9]{2,}\](.*?)\[/size\]#i', '[size=29]\1[/size]', $message); + + for ($i = count($size); $i;) + { + $i--; + $message = str_replace('[size=' . $i . ']', '[size=' . $size[$i] . ']', $message); + } + } + + if ($extended_bbcodes) + { + $message = preg_replace($origx, $replx, $message); + } + + $message = preg_replace($orig, $repl, $message); + return $message; +} + + +function copy_file($src, $trg, $overwrite = false, $die_on_failure = true, $source_relative_path = true) +{ + global $convert, $phpbb_root_path, $user, $phpbb_filesystem; + + /** @var \phpbb\filesystem\filesystem_interface $filesystem */ + $filesystem = $phpbb_filesystem; + + if (substr($trg, -1) == '/') + { + $trg .= utf8_basename($src); + } + $src_path = relative_base($src, $source_relative_path, __LINE__, __FILE__); + $trg_path = $trg; + + if (!$overwrite && @file_exists($trg_path)) + { + return true; + } + + if (!@file_exists($src_path)) + { + return; + } + + $path = $phpbb_root_path; + $parts = explode('/', $trg); + unset($parts[count($parts) - 1]); + + for ($i = 0, $end = count($parts); $i < $end; ++$i) + { + $path .= $parts[$i] . '/'; + + if (!is_dir($path)) + { + @mkdir($path, 0777); + } + } + + if (!$filesystem->is_writable($path)) + { + @chmod($path, 0777); + } + + if (!@copy($src_path, $phpbb_root_path . $trg_path)) + { + $convert->p_master->error(sprintf($user->lang['COULD_NOT_COPY'], $src_path, $phpbb_root_path . $trg_path), __LINE__, __FILE__, !$die_on_failure); + return; + } + + if ($perm = @fileperms($src_path)) + { + @chmod($phpbb_root_path . $trg_path, $perm); + } + + return true; +} + +function copy_dir($src, $trg, $copy_subdirs = true, $overwrite = false, $die_on_failure = true, $source_relative_path = true) +{ + global $convert, $phpbb_root_path, $config, $user, $phpbb_filesystem; + + /** @var \phpbb\filesystem\filesystem_interface $filesystem */ + $filesystem = $phpbb_filesystem; + + $dirlist = $filelist = $bad_dirs = array(); + $src = path($src, $source_relative_path); + $trg = path($trg); + $src_path = relative_base($src, $source_relative_path, __LINE__, __FILE__); + $trg_path = $phpbb_root_path . $trg; + + if (!is_dir($trg_path)) + { + @mkdir($trg_path, 0777); + @chmod($trg_path, 0777); + } + + if (!$filesystem->is_writable($trg_path)) + { + $bad_dirs[] = path($config['script_path']) . $trg; + } + + if ($handle = @opendir($src_path)) + { + while ($entry = readdir($handle)) + { + if ($entry[0] == '.' || $entry == 'CVS' || $entry == 'index.htm') + { + continue; + } + + if (is_dir($src_path . $entry)) + { + $dirlist[] = $entry; + } + else + { + $filelist[] = $entry; + } + } + closedir($handle); + } + else if ($dir = @dir($src_path)) + { + while ($entry = $dir->read()) + { + if ($entry[0] == '.' || $entry == 'CVS' || $entry == 'index.htm') + { + continue; + } + + if (is_dir($src_path . $entry)) + { + $dirlist[] = $entry; + } + else + { + $filelist[] = $entry; + } + } + $dir->close(); + } + else + { + $convert->p_master->error(sprintf($user->lang['CONV_ERROR_COULD_NOT_READ'], relative_base($src, $source_relative_path)), __LINE__, __FILE__); + } + + if ($copy_subdirs) + { + for ($i = 0, $end = count($dirlist); $i < $end; ++$i) + { + $dir = $dirlist[$i]; + + if ($dir == 'CVS') + { + continue; + } + + if (!is_dir($trg_path . $dir)) + { + @mkdir($trg_path . $dir, 0777); + @chmod($trg_path . $dir, 0777); + } + + if (!$filesystem->is_writable($trg_path . $dir)) + { + $bad_dirs[] = $trg . $dir; + $bad_dirs[] = $trg_path . $dir; + } + + if (!count($bad_dirs)) + { + copy_dir($src . $dir, $trg . $dir, true, $overwrite, $die_on_failure, $source_relative_path); + } + } + } + + if (count($bad_dirs)) + { + $str = (count($bad_dirs) == 1) ? $user->lang['MAKE_FOLDER_WRITABLE'] : $user->lang['MAKE_FOLDERS_WRITABLE']; + sort($bad_dirs); + $convert->p_master->error(sprintf($str, implode('
', $bad_dirs)), __LINE__, __FILE__); + } + + for ($i = 0, $end = count($filelist); $i < $end; ++$i) + { + copy_file($src . $filelist[$i], $trg . $filelist[$i], $overwrite, $die_on_failure, $source_relative_path); + } +} + +function relative_base($path, $is_relative = true, $line = false, $file = false) +{ + global $convert, $user; + + if (!$is_relative) + { + return $path; + } + + if (empty($convert->options['forum_path']) && $is_relative) + { + $line = $line ? $line : __LINE__; + $file = $file ? $file : __FILE__; + + $convert->p_master->error($user->lang['CONV_ERROR_NO_FORUM_PATH'], $line, $file); + } + + return $convert->options['forum_path'] . '/' . $path; +} + +function get_smiley_display() +{ + static $smiley_count = 0; + $smiley_count++; + return ($smiley_count < 50) ? 1 : 0; +} + + +function fill_dateformat($user_dateformat) +{ + global $config; + + return ((empty($user_dateformat)) ? $config['default_dateformat'] : $user_dateformat); +} diff --git a/includes/functions_database_helper.php b/includes/functions_database_helper.php new file mode 100644 index 0000000..8f363d5 --- /dev/null +++ b/includes/functions_database_helper.php @@ -0,0 +1,210 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Updates rows in given table from a set of values to a new value. +* If this results in rows violating uniqueness constraints, the duplicate +* rows are eliminated. +* +* The only supported table is bookmarks. +* +* @param \phpbb\db\driver\driver_interface $db Database object +* @param string $table Table on which to perform the update +* @param string $column Column whose values to change +* @param array $from_values An array of values that should be changed +* @param int $to_value The new value +* @return null +*/ +function phpbb_update_rows_avoiding_duplicates(\phpbb\db\driver\driver_interface $db, $table, $column, $from_values, $to_value) +{ + $sql = "SELECT $column, user_id + FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[$row[$column]][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = " . (int) $to_value; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $queries = array(); + foreach ($from_values as $from_value) + { + if (!isset($old_user_ids[$from_value])) + { + continue; + } + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + } + } + + if (!empty($queries)) + { + $db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + } +} + +/** +* Updates rows in given table from a set of values to a new value. +* If this results in rows violating uniqueness constraints, the duplicate +* rows are merged respecting notify_status (0 takes precedence over 1). +* +* The only supported table is topics_watch. +* +* @param \phpbb\db\driver\driver_interface $db Database object +* @param string $table Table on which to perform the update +* @param string $column Column whose values to change +* @param array $from_values An array of values that should be changed +* @param int $to_value The new value +* @return null +*/ +function phpbb_update_rows_avoiding_duplicates_notify_status(\phpbb\db\driver\driver_interface $db, $table, $column, $from_values, $to_value) +{ + $sql = "SELECT $column, user_id, notify_status + FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $result = $db->sql_query($sql); + + $old_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $old_user_ids[(int) $row['notify_status']][$row[$column]][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT $column, user_id + FROM $table + WHERE $column = " . (int) $to_value; + $result = $db->sql_query($sql); + + $new_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $new_user_ids[$row[$column]][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $queries = array(); + $extra_updates = array( + 0 => 'notify_status = 0', + 1 => '', + ); + foreach ($from_values as $from_value) + { + foreach ($extra_updates as $notify_status => $extra_update) + { + if (!isset($old_user_ids[$notify_status][$from_value])) + { + continue; + } + if (empty($new_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "'"; + $queries[] = $sql; + } + else + { + $different_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $new_user_ids[$to_value]); + if (!empty($different_user_ids)) + { + $sql = "UPDATE $table + SET $column = " . (int) $to_value . " + WHERE $column = '" . $db->sql_escape($from_value) . "' + AND " . $db->sql_in_set('user_id', $different_user_ids); + $queries[] = $sql; + } + + if ($extra_update) + { + $same_user_ids = array_diff($old_user_ids[$notify_status][$from_value], $different_user_ids); + if (!empty($same_user_ids)) + { + $sql = "UPDATE $table + SET $extra_update + WHERE $column = '" . (int) $to_value . "' + AND " . $db->sql_in_set('user_id', $same_user_ids); + $queries[] = $sql; + } + } + } + } + } + + if (!empty($queries)) + { + $db->sql_transaction('begin'); + + foreach ($queries as $sql) + { + $db->sql_query($sql); + } + + $sql = "DELETE FROM $table + WHERE " . $db->sql_in_set($column, $from_values); + $db->sql_query($sql); + + $db->sql_transaction('commit'); + } +} diff --git a/includes/functions_display.php b/includes/functions_display.php new file mode 100644 index 0000000..7924670 --- /dev/null +++ b/includes/functions_display.php @@ -0,0 +1,1733 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Display Forums +*/ +function display_forums($root_data = '', $display_moderators = true, $return_moderators = false) +{ + global $db, $auth, $user, $template; + global $phpbb_root_path, $phpEx, $config; + global $request, $phpbb_dispatcher, $phpbb_container; + + $forum_rows = $subforums = $forum_ids = $forum_ids_moderator = $forum_moderators = $active_forum_ary = array(); + $parent_id = $visible_forums = 0; + + // Mark forums read? + $mark_read = $request->variable('mark', ''); + + if ($mark_read == 'all') + { + $mark_read = ''; + } + + if (!$root_data) + { + if ($mark_read == 'forums') + { + $mark_read = 'all'; + } + + $root_data = array('forum_id' => 0); + $sql_where = ''; + } + else + { + $sql_where = 'left_id > ' . $root_data['left_id'] . ' AND left_id < ' . $root_data['right_id']; + } + + // Handle marking everything read + if ($mark_read == 'all') + { + $redirect = build_url(array('mark', 'hash', 'mark_time')); + meta_refresh(3, $redirect); + + if (check_link_hash($request->variable('hash', ''), 'global')) + { + markread('all', false, false, $request->variable('mark_time', 0)); + + if ($request->is_ajax()) + { + // Tell the ajax script what language vars and URL need to be replaced + $data = array( + 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'], + 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'], + 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&mark=forums&mark_time=' . time()) : '', + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $user->lang['FORUMS_MARKED'] + ); + $json_response = new \phpbb\json_response(); + $json_response->send($data); + } + + trigger_error( + $user->lang['FORUMS_MARKED'] . '

' . + sprintf($user->lang['RETURN_INDEX'], '', '') + ); + } + else + { + trigger_error(sprintf($user->lang['RETURN_PAGE'], '', '')); + } + } + + // Display list of active topics for this category? + $show_active = (isset($root_data['forum_flags']) && ($root_data['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS)) ? true : false; + + $sql_array = array( + 'SELECT' => 'f.*', + 'FROM' => array( + FORUMS_TABLE => 'f' + ), + 'LEFT_JOIN' => array(), + ); + + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $sql_array['LEFT_JOIN'][] = array('FROM' => array(FORUMS_TRACK_TABLE => 'ft'), 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id'); + $sql_array['SELECT'] .= ', ft.mark_time'; + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); + $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); + + if (!$user->data['is_registered']) + { + $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0; + } + } + + if ($show_active) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(FORUMS_ACCESS_TABLE => 'fa'), + 'ON' => "fa.forum_id = f.forum_id AND fa.session_id = '" . $db->sql_escape($user->session_id) . "'" + ); + + $sql_array['SELECT'] .= ', fa.user_id'; + } + + $sql_ary = array( + 'SELECT' => $sql_array['SELECT'], + 'FROM' => $sql_array['FROM'], + 'LEFT_JOIN' => $sql_array['LEFT_JOIN'], + + 'WHERE' => $sql_where, + + 'ORDER_BY' => 'f.left_id', + ); + + /** + * Event to modify the SQL query before the forum data is queried + * + * @event core.display_forums_modify_sql + * @var array sql_ary The SQL array to get the data of the forums + * @since 3.1.0-a1 + */ + $vars = array('sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_sql', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $forum_tracking_info = $valid_categories = array(); + $branch_root_id = $root_data['forum_id']; + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + while ($row = $db->sql_fetchrow($result)) + { + /** + * Event to modify the data set of a forum + * + * This event is triggered once per forum + * + * @event core.display_forums_modify_row + * @var int branch_root_id Last top-level forum + * @var array row The data of the forum + * @since 3.1.0-a1 + */ + $vars = array('branch_root_id', 'row'); + extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_row', compact($vars))); + + $forum_id = $row['forum_id']; + + // Mark forums read? + if ($mark_read == 'forums') + { + if ($auth->acl_get('f_list', $forum_id)) + { + $forum_ids[] = $forum_id; + } + + continue; + } + + // Category with no members + if ($row['forum_type'] == FORUM_CAT && ($row['left_id'] + 1 == $row['right_id'])) + { + continue; + } + + // Skip branch + if (isset($right_id)) + { + if ($row['left_id'] < $right_id) + { + continue; + } + unset($right_id); + } + + if (!$auth->acl_get('f_list', $forum_id)) + { + // if the user does not have permissions to list this forum, skip everything until next branch + $right_id = $row['right_id']; + continue; + } + + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $forum_tracking_info[$forum_id] = (!empty($row['mark_time'])) ? $row['mark_time'] : $user->data['user_lastmark']; + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + if (!$user->data['is_registered']) + { + $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0; + } + $forum_tracking_info[$forum_id] = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark']; + } + + // Lets check whether there are unapproved topics/posts, so we can display an information to moderators + $row['forum_id_unapproved_topics'] = ($auth->acl_get('m_approve', $forum_id) && $row['forum_topics_unapproved']) ? $forum_id : 0; + $row['forum_id_unapproved_posts'] = ($auth->acl_get('m_approve', $forum_id) && $row['forum_posts_unapproved']) ? $forum_id : 0; + $row['forum_posts'] = $phpbb_content_visibility->get_count('forum_posts', $row, $forum_id); + $row['forum_topics'] = $phpbb_content_visibility->get_count('forum_topics', $row, $forum_id); + + // Display active topics from this forum? + if ($show_active && $row['forum_type'] == FORUM_POST && $auth->acl_get('f_read', $forum_id) && ($row['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS)) + { + if (!isset($active_forum_ary['forum_topics'])) + { + $active_forum_ary['forum_topics'] = 0; + } + + if (!isset($active_forum_ary['forum_posts'])) + { + $active_forum_ary['forum_posts'] = 0; + } + + $active_forum_ary['forum_id'][] = $forum_id; + $active_forum_ary['enable_icons'][] = $row['enable_icons']; + $active_forum_ary['forum_topics'] += $row['forum_topics']; + $active_forum_ary['forum_posts'] += $row['forum_posts']; + + // If this is a passworded forum we do not show active topics from it if the user is not authorised to view it... + if ($row['forum_password'] && $row['user_id'] != $user->data['user_id']) + { + $active_forum_ary['exclude_forum_id'][] = $forum_id; + } + } + + // Fill list of categories with forums + if (isset($forum_rows[$row['parent_id']])) + { + $valid_categories[$row['parent_id']] = true; + } + + // + if ($row['parent_id'] == $root_data['forum_id'] || $row['parent_id'] == $branch_root_id) + { + if ($row['forum_type'] != FORUM_CAT) + { + $forum_ids_moderator[] = (int) $forum_id; + } + + // Direct child of current branch + $parent_id = $forum_id; + $forum_rows[$forum_id] = $row; + + if ($row['forum_type'] == FORUM_CAT && $row['parent_id'] == $root_data['forum_id']) + { + $branch_root_id = $forum_id; + } + $forum_rows[$parent_id]['forum_id_last_post'] = $row['forum_id']; + $forum_rows[$parent_id]['forum_password_last_post'] = $row['forum_password']; + $forum_rows[$parent_id]['orig_forum_last_post_time'] = $row['forum_last_post_time']; + } + else if ($row['forum_type'] != FORUM_CAT) + { + $subforums[$parent_id][$forum_id]['display'] = ($row['display_on_index']) ? true : false; + $subforums[$parent_id][$forum_id]['name'] = $row['forum_name']; + $subforums[$parent_id][$forum_id]['orig_forum_last_post_time'] = $row['forum_last_post_time']; + $subforums[$parent_id][$forum_id]['children'] = array(); + $subforums[$parent_id][$forum_id]['type'] = $row['forum_type']; + + if (isset($subforums[$parent_id][$row['parent_id']]) && !$row['display_on_index']) + { + $subforums[$parent_id][$row['parent_id']]['children'][] = $forum_id; + } + + if (!$forum_rows[$parent_id]['forum_id_unapproved_topics'] && $row['forum_id_unapproved_topics']) + { + $forum_rows[$parent_id]['forum_id_unapproved_topics'] = $forum_id; + } + + if (!$forum_rows[$parent_id]['forum_id_unapproved_posts'] && $row['forum_id_unapproved_posts']) + { + $forum_rows[$parent_id]['forum_id_unapproved_posts'] = $forum_id; + } + + $forum_rows[$parent_id]['forum_topics'] += $row['forum_topics']; + + // Do not list redirects in LINK Forums as Posts. + if ($row['forum_type'] != FORUM_LINK) + { + $forum_rows[$parent_id]['forum_posts'] += $row['forum_posts']; + } + + if ($row['forum_last_post_time'] > $forum_rows[$parent_id]['forum_last_post_time']) + { + $forum_rows[$parent_id]['forum_last_post_id'] = $row['forum_last_post_id']; + $forum_rows[$parent_id]['forum_last_post_subject'] = $row['forum_last_post_subject']; + $forum_rows[$parent_id]['forum_last_post_time'] = $row['forum_last_post_time']; + $forum_rows[$parent_id]['forum_last_poster_id'] = $row['forum_last_poster_id']; + $forum_rows[$parent_id]['forum_last_poster_name'] = $row['forum_last_poster_name']; + $forum_rows[$parent_id]['forum_last_poster_colour'] = $row['forum_last_poster_colour']; + $forum_rows[$parent_id]['forum_id_last_post'] = $forum_id; + $forum_rows[$parent_id]['forum_password_last_post'] = $row['forum_password']; + } + } + + /** + * Event to modify the forum rows data set + * + * This event is triggered once per forum + * + * @event core.display_forums_modify_forum_rows + * @var array forum_rows Data array of all forums we display + * @var array subforums Data array of all subforums we display + * @var int branch_root_id Current top-level forum + * @var int parent_id Current parent forum + * @var array row The data of the forum + * @since 3.1.0-a1 + */ + $vars = array('forum_rows', 'subforums', 'branch_root_id', 'parent_id', 'row'); + extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_forum_rows', compact($vars))); + } + $db->sql_freeresult($result); + + // Handle marking posts + if ($mark_read == 'forums') + { + $redirect = build_url(array('mark', 'hash', 'mark_time')); + $token = $request->variable('hash', ''); + if (check_link_hash($token, 'global')) + { + markread('topics', $forum_ids, false, $request->variable('mark_time', 0)); + $message = sprintf($user->lang['RETURN_FORUM'], '', ''); + meta_refresh(3, $redirect); + + if ($request->is_ajax()) + { + // Tell the ajax script what language vars and URL need to be replaced + $data = array( + 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'], + 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'], + 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . '&f=' . $root_data['forum_id'] . '&mark=forums&mark_time=' . time()) : '', + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $user->lang['FORUMS_MARKED'] + ); + $json_response = new \phpbb\json_response(); + $json_response->send($data); + } + + trigger_error($user->lang['FORUMS_MARKED'] . '

' . $message); + } + else + { + $message = sprintf($user->lang['RETURN_PAGE'], '', ''); + meta_refresh(3, $redirect); + trigger_error($message); + } + + } + + // Grab moderators ... if necessary + if ($display_moderators) + { + if ($return_moderators) + { + $forum_ids_moderator[] = $root_data['forum_id']; + } + get_moderators($forum_moderators, $forum_ids_moderator); + } + + /** + * Event to perform additional actions before the forum list is being generated + * + * @event core.display_forums_before + * @var array active_forum_ary Array with forum data to display active topics + * @var bool display_moderators Flag indicating if we display forum moderators + * @var array forum_moderators Array with forum moderators list + * @var array forum_rows Data array of all forums we display + * @var bool return_moderators Flag indicating if moderators list should be returned + * @var array root_data Array with the root forum data + * @since 3.1.4-RC1 + */ + $vars = array( + 'active_forum_ary', + 'display_moderators', + 'forum_moderators', + 'forum_rows', + 'return_moderators', + 'root_data', + ); + extract($phpbb_dispatcher->trigger_event('core.display_forums_before', compact($vars))); + + // Used to tell whatever we have to create a dummy category or not. + $last_catless = true; + foreach ($forum_rows as $row) + { + // Category + if ($row['parent_id'] == $root_data['forum_id'] && $row['forum_type'] == FORUM_CAT) + { + // Do not display categories without any forums to display + if (!isset($valid_categories[$row['forum_id']])) + { + continue; + } + + $cat_row = array( + 'S_IS_CAT' => true, + 'FORUM_ID' => $row['forum_id'], + 'FORUM_NAME' => $row['forum_name'], + 'FORUM_DESC' => generate_text_for_display($row['forum_desc'], $row['forum_desc_uid'], $row['forum_desc_bitfield'], $row['forum_desc_options']), + 'FORUM_FOLDER_IMG' => '', + 'FORUM_FOLDER_IMG_SRC' => '', + 'FORUM_IMAGE' => ($row['forum_image']) ? '' . $user->lang['FORUM_CAT'] . '' : '', + 'FORUM_IMAGE_SRC' => ($row['forum_image']) ? $phpbb_root_path . $row['forum_image'] : '', + 'U_VIEWFORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']), + ); + + /** + * Modify the template data block of the 'category' + * + * This event is triggered once per 'category' + * + * @event core.display_forums_modify_category_template_vars + * @var array cat_row Template data of the 'category' + * @var bool last_catless The flag indicating whether the last forum had a parent category + * @var array root_data Array with the root forum data + * @var array row The data of the 'category' + * @since 3.1.0-RC4 + * @changed 3.1.7-RC1 Removed undefined catless variable + */ + $vars = array( + 'cat_row', + 'last_catless', + 'root_data', + 'row', + ); + extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_category_template_vars', compact($vars))); + + $template->assign_block_vars('forumrow', $cat_row); + + continue; + } + + $visible_forums++; + $forum_id = $row['forum_id']; + + $forum_unread = (isset($forum_tracking_info[$forum_id]) && $row['orig_forum_last_post_time'] > $forum_tracking_info[$forum_id]) ? true : false; + + $folder_image = $folder_alt = $l_subforums = ''; + $subforums_list = array(); + + // Generate list of subforums if we need to + if (isset($subforums[$forum_id])) + { + foreach ($subforums[$forum_id] as $subforum_id => $subforum_row) + { + $subforum_unread = (isset($forum_tracking_info[$subforum_id]) && $subforum_row['orig_forum_last_post_time'] > $forum_tracking_info[$subforum_id]) ? true : false; + + if (!$subforum_unread && !empty($subforum_row['children'])) + { + foreach ($subforum_row['children'] as $child_id) + { + if (isset($forum_tracking_info[$child_id]) && $subforums[$forum_id][$child_id]['orig_forum_last_post_time'] > $forum_tracking_info[$child_id]) + { + // Once we found an unread child forum, we can drop out of this loop + $subforum_unread = true; + break; + } + } + } + + if ($subforum_row['display'] && $subforum_row['name']) + { + $subforums_list[] = array( + 'link' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $subforum_id), + 'name' => $subforum_row['name'], + 'unread' => $subforum_unread, + 'type' => $subforum_row['type'], + ); + } + else + { + unset($subforums[$forum_id][$subforum_id]); + } + + // If one subforum is unread the forum gets unread too... + if ($subforum_unread) + { + $forum_unread = true; + } + } + + $l_subforums = (count($subforums[$forum_id]) == 1) ? $user->lang['SUBFORUM'] : $user->lang['SUBFORUMS']; + $folder_image = ($forum_unread) ? 'forum_unread_subforum' : 'forum_read_subforum'; + } + else + { + switch ($row['forum_type']) + { + case FORUM_POST: + $folder_image = ($forum_unread) ? 'forum_unread' : 'forum_read'; + break; + + case FORUM_LINK: + $folder_image = 'forum_link'; + break; + } + } + + // Which folder should we display? + if ($row['forum_status'] == ITEM_LOCKED) + { + $folder_image = ($forum_unread) ? 'forum_unread_locked' : 'forum_read_locked'; + $folder_alt = 'FORUM_LOCKED'; + } + else + { + $folder_alt = ($forum_unread) ? 'UNREAD_POSTS' : 'NO_UNREAD_POSTS'; + } + + // Create last post link information, if appropriate + if ($row['forum_last_post_id']) + { + if ($row['forum_password_last_post'] === '' && $auth->acl_gets('f_read', 'f_list_topics', $row['forum_id_last_post'])) + { + $last_post_subject = censor_text($row['forum_last_post_subject']); + $last_post_subject_truncated = truncate_string($last_post_subject, 30, 255, false, $user->lang['ELLIPSIS']); + } + else + { + $last_post_subject = $last_post_subject_truncated = ''; + } + $last_post_time = $user->format_date($row['forum_last_post_time']); + $last_post_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id_last_post'] . '&p=' . $row['forum_last_post_id']) . '#p' . $row['forum_last_post_id']; + } + else + { + $last_post_subject = $last_post_time = $last_post_url = $last_post_subject_truncated = ''; + } + + // Output moderator listing ... if applicable + $l_moderator = $moderators_list = ''; + if ($display_moderators && !empty($forum_moderators[$forum_id])) + { + $l_moderator = (count($forum_moderators[$forum_id]) == 1) ? $user->lang['MODERATOR'] : $user->lang['MODERATORS']; + $moderators_list = implode($user->lang['COMMA_SEPARATOR'], $forum_moderators[$forum_id]); + } + + $l_post_click_count = ($row['forum_type'] == FORUM_LINK) ? 'CLICKS' : 'POSTS'; + $post_click_count = ($row['forum_type'] != FORUM_LINK || $row['forum_flags'] & FORUM_FLAG_LINK_TRACK) ? $row['forum_posts'] : ''; + + $s_subforums_list = $subforums_row = array(); + foreach ($subforums_list as $subforum) + { + $s_subforums_list[] = '' . $subforum['name'] . ''; + $subforums_row[] = array( + 'U_SUBFORUM' => $subforum['link'], + 'SUBFORUM_NAME' => $subforum['name'], + 'S_UNREAD' => $subforum['unread'], + 'IS_LINK' => $subforum['type'] == FORUM_LINK, + ); + } + $s_subforums_list = (string) implode($user->lang['COMMA_SEPARATOR'], $s_subforums_list); + $catless = ($row['parent_id'] == $root_data['forum_id']) ? true : false; + + if ($row['forum_type'] != FORUM_LINK) + { + $u_viewforum = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']); + } + else + { + // If the forum is a link and we count redirects we need to visit it + // If the forum is having a password or no read access we do not expose the link, but instead handle it in viewforum + if (($row['forum_flags'] & FORUM_FLAG_LINK_TRACK) || $row['forum_password'] || !$auth->acl_get('f_read', $forum_id)) + { + $u_viewforum = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']); + } + else + { + $u_viewforum = $row['forum_link']; + } + } + + $forum_row = array( + 'S_IS_CAT' => false, + 'S_NO_CAT' => $catless && !$last_catless, + 'S_IS_LINK' => ($row['forum_type'] == FORUM_LINK) ? true : false, + 'S_UNREAD_FORUM' => $forum_unread, + 'S_AUTH_READ' => $auth->acl_get('f_read', $row['forum_id']), + 'S_LOCKED_FORUM' => ($row['forum_status'] == ITEM_LOCKED) ? true : false, + 'S_LIST_SUBFORUMS' => ($row['display_subforum_list']) ? true : false, + 'S_SUBFORUMS' => (count($subforums_list)) ? true : false, + 'S_DISPLAY_SUBJECT' => ($last_post_subject !== '' && $config['display_last_subject']) ? true : false, + 'S_FEED_ENABLED' => ($config['feed_forum'] && !phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $row['forum_options']) && $row['forum_type'] == FORUM_POST) ? true : false, + + 'FORUM_ID' => $row['forum_id'], + 'FORUM_NAME' => $row['forum_name'], + 'FORUM_DESC' => generate_text_for_display($row['forum_desc'], $row['forum_desc_uid'], $row['forum_desc_bitfield'], $row['forum_desc_options']), + 'TOPICS' => $row['forum_topics'], + $l_post_click_count => $post_click_count, + 'FORUM_IMG_STYLE' => $folder_image, + 'FORUM_FOLDER_IMG' => $user->img($folder_image, $folder_alt), + 'FORUM_FOLDER_IMG_ALT' => isset($user->lang[$folder_alt]) ? $user->lang[$folder_alt] : '', + 'FORUM_IMAGE' => ($row['forum_image']) ? '' . $user->lang[$folder_alt] . '' : '', + 'FORUM_IMAGE_SRC' => ($row['forum_image']) ? $phpbb_root_path . $row['forum_image'] : '', + 'LAST_POST_SUBJECT' => $last_post_subject, + 'LAST_POST_SUBJECT_TRUNCATED' => $last_post_subject_truncated, + 'LAST_POST_TIME' => $last_post_time, + 'LAST_POSTER' => get_username_string('username', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), + 'LAST_POSTER_COLOUR' => get_username_string('colour', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), + 'LAST_POSTER_FULL' => get_username_string('full', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), + 'MODERATORS' => $moderators_list, + 'SUBFORUMS' => $s_subforums_list, + + 'L_SUBFORUM_STR' => $l_subforums, + 'L_MODERATOR_STR' => $l_moderator, + + 'U_UNAPPROVED_TOPICS' => ($row['forum_id_unapproved_topics']) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=unapproved_topics&f=' . $row['forum_id_unapproved_topics']) : '', + 'U_UNAPPROVED_POSTS' => ($row['forum_id_unapproved_posts']) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=unapproved_posts&f=' . $row['forum_id_unapproved_posts']) : '', + 'U_VIEWFORUM' => $u_viewforum, + 'U_LAST_POSTER' => get_username_string('profile', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), + 'U_LAST_POST' => $last_post_url, + ); + + /** + * Modify the template data block of the forum + * + * This event is triggered once per forum + * + * @event core.display_forums_modify_template_vars + * @var array forum_row Template data of the forum + * @var array row The data of the forum + * @var array subforums_row Template data of subforums + * @since 3.1.0-a1 + * @changed 3.1.0-b5 Added var subforums_row + */ + $vars = array('forum_row', 'row', 'subforums_row'); + extract($phpbb_dispatcher->trigger_event('core.display_forums_modify_template_vars', compact($vars))); + + $template->assign_block_vars('forumrow', $forum_row); + + // Assign subforums loop for style authors + $template->assign_block_vars_array('forumrow.subforum', $subforums_row); + + /** + * Modify and/or assign additional template data for the forum + * after forumrow loop has been assigned. This can be used + * to create additional forumrow subloops in extensions. + * + * This event is triggered once per forum + * + * @event core.display_forums_add_template_data + * @var array forum_row Template data of the forum + * @var array row The data of the forum + * @var array subforums_list The data of subforums + * @var array subforums_row Template data of subforums + * @var bool catless The flag indicating whether a forum has a parent category + * @since 3.1.0-b5 + */ + $vars = array( + 'forum_row', + 'row', + 'subforums_list', + 'subforums_row', + 'catless', + ); + extract($phpbb_dispatcher->trigger_event('core.display_forums_add_template_data', compact($vars))); + + $last_catless = $catless; + } + + $template->assign_vars(array( + 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . '&f=' . $root_data['forum_id'] . '&mark=forums&mark_time=' . time()) : '', + 'S_HAS_SUBFORUM' => ($visible_forums) ? true : false, + 'L_SUBFORUM' => ($visible_forums == 1) ? $user->lang['SUBFORUM'] : $user->lang['SUBFORUMS'], + 'LAST_POST_IMG' => $user->img('icon_topic_latest', 'VIEW_LATEST_POST'), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', 'TOPICS_UNAPPROVED'), + 'UNAPPROVED_POST_IMG' => $user->img('icon_topic_unapproved', 'POSTS_UNAPPROVED_FORUM'), + )); + + /** + * Event to perform additional actions after the forum list has been generated + * + * @event core.display_forums_after + * @var array active_forum_ary Array with forum data to display active topics + * @var bool display_moderators Flag indicating if we display forum moderators + * @var array forum_moderators Array with forum moderators list + * @var array forum_rows Data array of all forums we display + * @var bool return_moderators Flag indicating if moderators list should be returned + * @var array root_data Array with the root forum data + * @since 3.1.0-RC5 + */ + $vars = array( + 'active_forum_ary', + 'display_moderators', + 'forum_moderators', + 'forum_rows', + 'return_moderators', + 'root_data', + ); + extract($phpbb_dispatcher->trigger_event('core.display_forums_after', compact($vars))); + + if ($return_moderators) + { + return array($active_forum_ary, $forum_moderators); + } + + return array($active_forum_ary, array()); +} + +/** +* Create forum rules for given forum +*/ +function generate_forum_rules(&$forum_data) +{ + if ($forum_data['forum_rules']) + { + $forum_data['forum_rules'] = generate_text_for_display($forum_data['forum_rules'], $forum_data['forum_rules_uid'], $forum_data['forum_rules_bitfield'], $forum_data['forum_rules_options']); + } + + if (!$forum_data['forum_rules'] && !$forum_data['forum_rules_link']) + { + return; + } + + global $template; + + $template->assign_vars(array( + 'S_FORUM_RULES' => true, + 'U_FORUM_RULES' => $forum_data['forum_rules_link'], + 'FORUM_RULES' => $forum_data['forum_rules']) + ); +} + +/** +* Create forum navigation links for given forum, create parent +* list if currently null, assign basic forum info to template +*/ +function generate_forum_nav(&$forum_data_ary) +{ + global $template, $auth, $config; + global $phpEx, $phpbb_root_path, $phpbb_dispatcher; + + if (!$auth->acl_get('f_list', $forum_data_ary['forum_id'])) + { + return; + } + + $navlinks_parents = $forum_template_data = array(); + + // Get forum parents + $forum_parents = get_forum_parents($forum_data_ary); + + $microdata_attr = 'data-forum-id'; + + // Build navigation links + if (!empty($forum_parents)) + { + foreach ($forum_parents as $parent_forum_id => $parent_data) + { + list($parent_name, $parent_type) = array_values($parent_data); + + // Skip this parent if the user does not have the permission to view it + if (!$auth->acl_get('f_list', $parent_forum_id)) + { + continue; + } + + $navlinks_parents[] = array( + 'S_IS_CAT' => ($parent_type == FORUM_CAT) ? true : false, + 'S_IS_LINK' => ($parent_type == FORUM_LINK) ? true : false, + 'S_IS_POST' => ($parent_type == FORUM_POST) ? true : false, + 'FORUM_NAME' => $parent_name, + 'FORUM_ID' => $parent_forum_id, + 'MICRODATA' => $microdata_attr . '="' . $parent_forum_id . '"', + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $parent_forum_id), + ); + } + } + + $navlinks = array( + 'S_IS_CAT' => ($forum_data_ary['forum_type'] == FORUM_CAT) ? true : false, + 'S_IS_LINK' => ($forum_data_ary['forum_type'] == FORUM_LINK) ? true : false, + 'S_IS_POST' => ($forum_data_ary['forum_type'] == FORUM_POST) ? true : false, + 'FORUM_NAME' => $forum_data_ary['forum_name'], + 'FORUM_ID' => $forum_data_ary['forum_id'], + 'MICRODATA' => $microdata_attr . '="' . $forum_data_ary['forum_id'] . '"', + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_data_ary['forum_id']), + ); + + $forum_template_data = array( + 'FORUM_ID' => $forum_data_ary['forum_id'], + 'FORUM_NAME' => $forum_data_ary['forum_name'], + 'FORUM_DESC' => generate_text_for_display($forum_data_ary['forum_desc'], $forum_data_ary['forum_desc_uid'], $forum_data_ary['forum_desc_bitfield'], $forum_data_ary['forum_desc_options']), + + 'S_ENABLE_FEEDS_FORUM' => ($config['feed_forum'] && $forum_data_ary['forum_type'] == FORUM_POST && !phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $forum_data_ary['forum_options'])) ? true : false, + ); + + $forum_data = $forum_data_ary; + /** + * Event to modify the navlinks text + * + * @event core.generate_forum_nav + * @var array forum_data Array with the forum data + * @var array forum_template_data Array with generic forum template data + * @var string microdata_attr The microdata attribute + * @var array navlinks_parents Array with the forum parents navlinks data + * @var array navlinks Array with the forum navlinks data + * @since 3.1.5-RC1 + */ + $vars = array( + 'forum_data', + 'forum_template_data', + 'microdata_attr', + 'navlinks_parents', + 'navlinks', + ); + extract($phpbb_dispatcher->trigger_event('core.generate_forum_nav', compact($vars))); + $forum_data_ary = $forum_data; + unset($forum_data); + + $template->assign_block_vars_array('navlinks', $navlinks_parents); + $template->assign_block_vars('navlinks', $navlinks); + $template->assign_vars($forum_template_data); + + return; +} + +/** +* Returns forum parents as an array. Get them from forum_data if available, or update the database otherwise +*/ +function get_forum_parents(&$forum_data) +{ + global $db; + + $forum_parents = array(); + + if ($forum_data['parent_id'] > 0) + { + if ($forum_data['forum_parents'] == '') + { + $sql = 'SELECT forum_id, forum_name, forum_type + FROM ' . FORUMS_TABLE . ' + WHERE left_id < ' . $forum_data['left_id'] . ' + AND right_id > ' . $forum_data['right_id'] . ' + ORDER BY left_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_parents[$row['forum_id']] = array($row['forum_name'], (int) $row['forum_type']); + } + $db->sql_freeresult($result); + + $forum_data['forum_parents'] = serialize($forum_parents); + + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET forum_parents = '" . $db->sql_escape($forum_data['forum_parents']) . "' + WHERE parent_id = " . $forum_data['parent_id']; + $db->sql_query($sql); + } + else + { + $forum_parents = unserialize($forum_data['forum_parents']); + } + } + + return $forum_parents; +} + +/** +* Obtain list of moderators of each forum +*/ +function get_moderators(&$forum_moderators, $forum_id = false) +{ + global $db, $phpbb_root_path, $phpEx, $user, $auth; + global $phpbb_container; + + $forum_id_ary = array(); + + if ($forum_id !== false) + { + if (!is_array($forum_id)) + { + $forum_id = array($forum_id); + } + + // Exchange key/value pair to be able to faster check for the forum id existence + $forum_id_ary = array_flip($forum_id); + } + + $sql_array = array( + 'SELECT' => 'm.*, u.user_colour, g.group_colour, g.group_type', + + 'FROM' => array( + MODERATOR_CACHE_TABLE => 'm', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(USERS_TABLE => 'u'), + 'ON' => 'm.user_id = u.user_id', + ), + array( + 'FROM' => array(GROUPS_TABLE => 'g'), + 'ON' => 'm.group_id = g.group_id', + ), + ), + + 'WHERE' => 'm.display_on_index = 1', + ); + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + // We query every forum here because for caching we should not have any parameter. + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql, 3600); + + while ($row = $db->sql_fetchrow($result)) + { + $f_id = (int) $row['forum_id']; + + if (!isset($forum_id_ary[$f_id])) + { + continue; + } + + if (!empty($row['user_id'])) + { + $forum_moderators[$f_id][] = get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']); + } + else + { + $group_name = $group_helper->get_name($row['group_name']); + + if ($user->data['user_id'] != ANONYMOUS && !$auth->acl_get('u_viewprofile')) + { + $forum_moderators[$f_id][] = '' . $group_name . ''; + } + else + { + $forum_moderators[$f_id][] = '' . $group_name . ''; + } + } + } + $db->sql_freeresult($result); + + return; +} + +/** +* User authorisation levels output +* +* @param string $mode Can be forum or topic. Not in use at the moment. +* @param int $forum_id The current forum the user is in. +* @param int $forum_status The forums status bit. +*/ +function gen_forum_auth_level($mode, $forum_id, $forum_status) +{ + global $template, $auth, $user, $config; + + $locked = ($forum_status == ITEM_LOCKED && !$auth->acl_get('m_edit', $forum_id)) ? true : false; + + $rules = array( + ($auth->acl_get('f_post', $forum_id) && !$locked) ? $user->lang['RULES_POST_CAN'] : $user->lang['RULES_POST_CANNOT'], + ($auth->acl_get('f_reply', $forum_id) && !$locked) ? $user->lang['RULES_REPLY_CAN'] : $user->lang['RULES_REPLY_CANNOT'], + ($user->data['is_registered'] && $auth->acl_gets('f_edit', 'm_edit', $forum_id) && !$locked) ? $user->lang['RULES_EDIT_CAN'] : $user->lang['RULES_EDIT_CANNOT'], + ($user->data['is_registered'] && ($auth->acl_gets('f_delete', 'm_delete', $forum_id) || $auth->acl_gets('f_softdelete', 'm_softdelete', $forum_id)) && !$locked) ? $user->lang['RULES_DELETE_CAN'] : $user->lang['RULES_DELETE_CANNOT'], + ); + + if ($config['allow_attachments']) + { + $rules[] = ($auth->acl_get('f_attach', $forum_id) && $auth->acl_get('u_attach') && !$locked) ? $user->lang['RULES_ATTACH_CAN'] : $user->lang['RULES_ATTACH_CANNOT']; + } + + foreach ($rules as $rule) + { + $template->assign_block_vars('rules', array('RULE' => $rule)); + } + + return; +} + +/** +* Generate topic status +*/ +function topic_status(&$topic_row, $replies, $unread_topic, &$folder_img, &$folder_alt, &$topic_type) +{ + global $user, $config; + + if ($topic_row['topic_status'] == ITEM_MOVED) + { + $topic_type = $user->lang['VIEW_TOPIC_MOVED']; + $folder_img = 'topic_moved'; + $folder_alt = 'TOPIC_MOVED'; + } + else + { + switch ($topic_row['topic_type']) + { + case POST_GLOBAL: + $topic_type = $user->lang['VIEW_TOPIC_GLOBAL']; + $folder = 'global_read'; + $folder_new = 'global_unread'; + break; + + case POST_ANNOUNCE: + $topic_type = $user->lang['VIEW_TOPIC_ANNOUNCEMENT']; + $folder = 'announce_read'; + $folder_new = 'announce_unread'; + break; + + case POST_STICKY: + $topic_type = $user->lang['VIEW_TOPIC_STICKY']; + $folder = 'sticky_read'; + $folder_new = 'sticky_unread'; + break; + + default: + $topic_type = ''; + $folder = 'topic_read'; + $folder_new = 'topic_unread'; + + // Hot topic threshold is for posts in a topic, which is replies + the first post. ;) + if ($config['hot_threshold'] && ($replies + 1) >= $config['hot_threshold'] && $topic_row['topic_status'] != ITEM_LOCKED) + { + $folder .= '_hot'; + $folder_new .= '_hot'; + } + break; + } + + if ($topic_row['topic_status'] == ITEM_LOCKED) + { + $topic_type = $user->lang['VIEW_TOPIC_LOCKED']; + $folder .= '_locked'; + $folder_new .= '_locked'; + } + + $folder_img = ($unread_topic) ? $folder_new : $folder; + $folder_alt = ($unread_topic) ? 'UNREAD_POSTS' : (($topic_row['topic_status'] == ITEM_LOCKED) ? 'TOPIC_LOCKED' : 'NO_UNREAD_POSTS'); + + // Posted image? + if (!empty($topic_row['topic_posted']) && $topic_row['topic_posted']) + { + $folder_img .= '_mine'; + } + } + + if ($topic_row['poll_start'] && $topic_row['topic_status'] != ITEM_MOVED) + { + $topic_type = $user->lang['VIEW_TOPIC_POLL']; + } +} + +/** +* Assign/Build custom bbcodes for display in screens supporting using of bbcodes +* The custom bbcodes buttons will be placed within the template block 'custom_tags' +*/ +function display_custom_bbcodes() +{ + global $db, $template, $user, $phpbb_dispatcher; + + // Start counting from 22 for the bbcode ids (every bbcode takes two ids - opening/closing) + $num_predefined_bbcodes = NUM_PREDEFINED_BBCODES; + + $sql_ary = array( + 'SELECT' => 'b.bbcode_id, b.bbcode_tag, b.bbcode_helpline', + 'FROM' => array(BBCODES_TABLE => 'b'), + 'WHERE' => 'b.display_on_posting = 1', + 'ORDER_BY' => 'b.bbcode_tag', + ); + + /** + * Event to modify the SQL query before custom bbcode data is queried + * + * @event core.display_custom_bbcodes_modify_sql + * @var array sql_ary The SQL array to get the bbcode data + * @var int num_predefined_bbcodes The number of predefined core bbcodes + * (multiplied by factor of 2) + * @since 3.1.0-a3 + */ + $vars = array('sql_ary', 'num_predefined_bbcodes'); + extract($phpbb_dispatcher->trigger_event('core.display_custom_bbcodes_modify_sql', compact($vars))); + + $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); + + $i = 0; + while ($row = $db->sql_fetchrow($result)) + { + // If the helpline is defined within the language file, we will use the localised version, else just use the database entry... + if (isset($user->lang[strtoupper($row['bbcode_helpline'])])) + { + $row['bbcode_helpline'] = $user->lang[strtoupper($row['bbcode_helpline'])]; + } + + $custom_tags = array( + 'BBCODE_NAME' => "'[{$row['bbcode_tag']}]', '[/" . str_replace('=', '', $row['bbcode_tag']) . "]'", + 'BBCODE_ID' => $num_predefined_bbcodes + ($i * 2), + 'BBCODE_TAG' => $row['bbcode_tag'], + 'BBCODE_TAG_CLEAN' => str_replace('=', '-', $row['bbcode_tag']), + 'BBCODE_HELPLINE' => $row['bbcode_helpline'], + 'A_BBCODE_HELPLINE' => str_replace(array('&', '"', "'", '<', '>'), array('&', '"', "\'", '<', '>'), $row['bbcode_helpline']), + ); + + /** + * Event to modify the template data block of a custom bbcode + * + * This event is triggered once per bbcode + * + * @event core.display_custom_bbcodes_modify_row + * @var array custom_tags Template data of the bbcode + * @var array row The data of the bbcode + * @since 3.1.0-a1 + */ + $vars = array('custom_tags', 'row'); + extract($phpbb_dispatcher->trigger_event('core.display_custom_bbcodes_modify_row', compact($vars))); + + $template->assign_block_vars('custom_tags', $custom_tags); + + $i++; + } + $db->sql_freeresult($result); + + /** + * Display custom bbcodes + * + * @event core.display_custom_bbcodes + * @since 3.1.0-a1 + */ + $phpbb_dispatcher->dispatch('core.display_custom_bbcodes'); +} + +/** +* Display reasons +* +* @deprecated 3.2.0-dev +*/ +function display_reasons($reason_id = 0) +{ + global $phpbb_container; + + $phpbb_container->get('phpbb.report.report_reason_list_provider')->display_reasons($reason_id); +} + +/** +* Display user activity (action forum/topic) +*/ +function display_user_activity(&$userdata_ary) +{ + global $auth, $template, $db, $user, $config; + global $phpbb_root_path, $phpEx; + global $phpbb_container, $phpbb_dispatcher; + + // Do not display user activity for users having too many posts... + $limit = $config['load_user_activity_limit']; + if ($userdata_ary['user_posts'] > $limit && $limit != 0) + { + return; + } + + $forum_ary = array(); + + $forum_read_ary = $auth->acl_getf('f_read'); + foreach ($forum_read_ary as $forum_id => $allowed) + { + if ($allowed['f_read']) + { + $forum_ary[] = (int) $forum_id; + } + } + + $forum_ary = array_diff($forum_ary, $user->get_passworded_forums()); + + $active_f_row = $active_t_row = array(); + if (!empty($forum_ary)) + { + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + // Obtain active forum + $sql = 'SELECT forum_id, COUNT(post_id) AS num_posts + FROM ' . POSTS_TABLE . ' + WHERE poster_id = ' . $userdata_ary['user_id'] . ' + AND post_postcount = 1 + AND ' . $phpbb_content_visibility->get_forums_visibility_sql('post', $forum_ary) . ' + GROUP BY forum_id + ORDER BY num_posts DESC'; + $result = $db->sql_query_limit($sql, 1); + $active_f_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!empty($active_f_row)) + { + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $active_f_row['forum_id']; + $result = $db->sql_query($sql, 3600); + $active_f_row['forum_name'] = (string) $db->sql_fetchfield('forum_name'); + $db->sql_freeresult($result); + } + + // Obtain active topic + $sql = 'SELECT topic_id, COUNT(post_id) AS num_posts + FROM ' . POSTS_TABLE . ' + WHERE poster_id = ' . $userdata_ary['user_id'] . ' + AND post_postcount = 1 + AND ' . $phpbb_content_visibility->get_forums_visibility_sql('post', $forum_ary) . ' + GROUP BY topic_id + ORDER BY num_posts DESC'; + $result = $db->sql_query_limit($sql, 1); + $active_t_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!empty($active_t_row)) + { + $sql = 'SELECT topic_title + FROM ' . TOPICS_TABLE . ' + WHERE topic_id = ' . $active_t_row['topic_id']; + $result = $db->sql_query($sql); + $active_t_row['topic_title'] = (string) $db->sql_fetchfield('topic_title'); + $db->sql_freeresult($result); + } + } + + $userdata = $userdata_ary; + $show_user_activity = true; + /** + * Alter list of forums and topics to display as active + * + * @event core.display_user_activity_modify_actives + * @var array userdata User's data + * @var array active_f_row List of active forums + * @var array active_t_row List of active posts + * @var bool show_user_activity Show user forum and topic activity + * @since 3.1.0-RC3 + * @changed 3.2.5-RC1 Added show_user_activity into event + */ + $vars = array('userdata', 'active_f_row', 'active_t_row', 'show_user_activity'); + extract($phpbb_dispatcher->trigger_event('core.display_user_activity_modify_actives', compact($vars))); + $userdata_ary = $userdata; + unset($userdata); + + $userdata_ary['active_t_row'] = $active_t_row; + $userdata_ary['active_f_row'] = $active_f_row; + + $active_f_name = $active_f_id = $active_f_count = $active_f_pct = ''; + if (!empty($active_f_row['num_posts'])) + { + $active_f_name = $active_f_row['forum_name']; + $active_f_id = $active_f_row['forum_id']; + $active_f_count = $active_f_row['num_posts']; + $active_f_pct = ($userdata_ary['user_posts']) ? ($active_f_count / $userdata_ary['user_posts']) * 100 : 0; + } + + $active_t_name = $active_t_id = $active_t_count = $active_t_pct = ''; + if (!empty($active_t_row['num_posts'])) + { + $active_t_name = $active_t_row['topic_title']; + $active_t_id = $active_t_row['topic_id']; + $active_t_count = $active_t_row['num_posts']; + $active_t_pct = ($userdata_ary['user_posts']) ? ($active_t_count / $userdata_ary['user_posts']) * 100 : 0; + } + + $l_active_pct = ($userdata_ary['user_id'] != ANONYMOUS && $userdata_ary['user_id'] == $user->data['user_id']) ? $user->lang['POST_PCT_ACTIVE_OWN'] : $user->lang['POST_PCT_ACTIVE']; + + $template->assign_vars(array( + 'ACTIVE_FORUM' => $active_f_name, + 'ACTIVE_FORUM_POSTS' => $user->lang('USER_POSTS', (int) $active_f_count), + 'ACTIVE_FORUM_PCT' => sprintf($l_active_pct, $active_f_pct), + 'ACTIVE_TOPIC' => censor_text($active_t_name), + 'ACTIVE_TOPIC_POSTS' => $user->lang('USER_POSTS', (int) $active_t_count), + 'ACTIVE_TOPIC_PCT' => sprintf($l_active_pct, $active_t_pct), + 'U_ACTIVE_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $active_f_id), + 'U_ACTIVE_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=' . $active_t_id), + 'S_SHOW_ACTIVITY' => $show_user_activity) + ); +} + +/** +* Topic and forum watching common code +*/ +function watch_topic_forum($mode, &$s_watching, $user_id, $forum_id, $topic_id, $notify_status = 'unset', $start = 0, $item_title = '') +{ + global $db, $user, $phpEx, $start, $phpbb_root_path; + global $request; + + $table_sql = ($mode == 'forum') ? FORUMS_WATCH_TABLE : TOPICS_WATCH_TABLE; + $where_sql = ($mode == 'forum') ? 'forum_id' : 'topic_id'; + $match_id = ($mode == 'forum') ? $forum_id : $topic_id; + $u_url = "uid={$user->data['user_id']}"; + $u_url .= ($mode == 'forum') ? '&f' : '&f=' . $forum_id . '&t'; + $is_watching = 0; + + // Is user watching this topic? + if ($user_id != ANONYMOUS) + { + $can_watch = true; + + if ($notify_status == 'unset') + { + $sql = "SELECT notify_status + FROM $table_sql + WHERE $where_sql = $match_id + AND user_id = $user_id"; + $result = $db->sql_query($sql); + + $notify_status = ($row = $db->sql_fetchrow($result)) ? $row['notify_status'] : NULL; + $db->sql_freeresult($result); + } + + if (!is_null($notify_status) && $notify_status !== '') + { + if (isset($_GET['unwatch'])) + { + $uid = $request->variable('uid', 0); + $token = $request->variable('hash', ''); + + if ($token && check_link_hash($token, "{$mode}_$match_id") || confirm_box(true)) + { + if ($uid != $user_id || $request->variable('unwatch', '', false, \phpbb\request\request_interface::GET) != $mode) + { + $redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); + $message = $user->lang['ERR_UNWATCHING']; + + if (!$request->is_ajax()) + { + $message .= '

' . $user->lang('RETURN_' . strtoupper($mode), '', ''); + } + trigger_error($message); + } + + $sql = 'DELETE FROM ' . $table_sql . " + WHERE $where_sql = $match_id + AND user_id = $user_id"; + $db->sql_query($sql); + + $redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); + $message = $user->lang['NOT_WATCHING_' . strtoupper($mode)]; + + if (!$request->is_ajax()) + { + $message .= '

' . $user->lang('RETURN_' . strtoupper($mode), '', ''); + } + meta_refresh(3, $redirect_url); + trigger_error($message); + } + else + { + $s_hidden_fields = array( + 'uid' => $user->data['user_id'], + 'unwatch' => $mode, + 'start' => $start, + 'f' => $forum_id, + ); + if ($mode != 'forum') + { + $s_hidden_fields['t'] = $topic_id; + } + + if ($item_title == '') + { + $confirm_box_message = 'UNWATCH_' . strtoupper($mode); + } + else + { + $confirm_box_message = $user->lang('UNWATCH_' . strtoupper($mode) . '_DETAILED', $item_title); + } + confirm_box(false, $confirm_box_message, build_hidden_fields($s_hidden_fields)); + } + } + else + { + $is_watching = true; + + if ($notify_status != NOTIFY_YES) + { + $sql = 'UPDATE ' . $table_sql . " + SET notify_status = " . NOTIFY_YES . " + WHERE $where_sql = $match_id + AND user_id = $user_id"; + $db->sql_query($sql); + } + } + } + else + { + if (isset($_GET['watch'])) + { + $uid = $request->variable('uid', 0); + $token = $request->variable('hash', ''); + + if ($token && check_link_hash($token, "{$mode}_$match_id") || confirm_box(true)) + { + if ($uid != $user_id || $request->variable('watch', '', false, \phpbb\request\request_interface::GET) != $mode) + { + $redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); + $message = $user->lang['ERR_WATCHING']; + + if (!$request->is_ajax()) + { + $message .= '

' . $user->lang('RETURN_' . strtoupper($mode), '', ''); + } + trigger_error($message); + } + + $is_watching = true; + + $sql = 'INSERT INTO ' . $table_sql . " (user_id, $where_sql, notify_status) + VALUES ($user_id, $match_id, " . NOTIFY_YES . ')'; + $db->sql_query($sql); + + $redirect_url = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&start=$start"); + $message = $user->lang['ARE_WATCHING_' . strtoupper($mode)]; + + if (!$request->is_ajax()) + { + $message .= '

' . $user->lang('RETURN_' . strtoupper($mode), '', ''); + } + meta_refresh(3, $redirect_url); + trigger_error($message); + } + else + { + $s_hidden_fields = array( + 'uid' => $user->data['user_id'], + 'watch' => $mode, + 'start' => $start, + 'f' => $forum_id, + ); + if ($mode != 'forum') + { + $s_hidden_fields['t'] = $topic_id; + } + + $confirm_box_message = (($item_title == '') ? 'WATCH_' . strtoupper($mode) : $user->lang('WATCH_' . strtoupper($mode) . '_DETAILED', $item_title)); + confirm_box(false, $confirm_box_message, build_hidden_fields($s_hidden_fields)); + } + } + else + { + $is_watching = 0; + } + } + } + else + { + if ((isset($_GET['unwatch']) && $request->variable('unwatch', '', false, \phpbb\request\request_interface::GET) == $mode) || + (isset($_GET['watch']) && $request->variable('watch', '', false, \phpbb\request\request_interface::GET) == $mode)) + { + login_box(); + } + else + { + $can_watch = 0; + $is_watching = 0; + } + } + + if ($can_watch) + { + $s_watching['link'] = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&" . (($is_watching) ? 'unwatch' : 'watch') . "=$mode&start=$start&hash=" . generate_link_hash("{$mode}_$match_id")); + $s_watching['link_toggle'] = append_sid("{$phpbb_root_path}view$mode.$phpEx", "$u_url=$match_id&" . ((!$is_watching) ? 'unwatch' : 'watch') . "=$mode&start=$start&hash=" . generate_link_hash("{$mode}_$match_id")); + $s_watching['title'] = $user->lang[(($is_watching) ? 'STOP' : 'START') . '_WATCHING_' . strtoupper($mode)]; + $s_watching['title_toggle'] = $user->lang[((!$is_watching) ? 'STOP' : 'START') . '_WATCHING_' . strtoupper($mode)]; + $s_watching['is_watching'] = $is_watching; + } + + return; +} + +/** +* Get user rank title and image +* +* @param array $user_data the current stored users data +* @param int $user_posts the users number of posts +* +* @return array An associative array containing the rank title (title), the rank image as full img tag (img) and the rank image source (img_src) +* +* Note: since we do not want to break backwards-compatibility, this function will only properly assign ranks to guests if you call it for them with user_posts == false +*/ +function phpbb_get_user_rank($user_data, $user_posts) +{ + global $ranks, $config, $phpbb_root_path, $phpbb_path_helper, $phpbb_dispatcher; + + $user_rank_data = array( + 'title' => null, + 'img' => null, + 'img_src' => null, + ); + + /** + * Preparing a user's rank before displaying + * + * @event core.modify_user_rank + * @var array user_data Array with user's data + * @var int user_posts User_posts to change + * @since 3.1.0-RC4 + */ + + $vars = array('user_data', 'user_posts'); + extract($phpbb_dispatcher->trigger_event('core.modify_user_rank', compact($vars))); + + if (empty($ranks)) + { + global $cache; + $ranks = $cache->obtain_ranks(); + } + + if (!empty($user_data['user_rank'])) + { + + $user_rank_data['title'] = (isset($ranks['special'][$user_data['user_rank']]['rank_title'])) ? $ranks['special'][$user_data['user_rank']]['rank_title'] : ''; + + $user_rank_data['img_src'] = (!empty($ranks['special'][$user_data['user_rank']]['rank_image'])) ? $phpbb_path_helper->update_web_root_path($phpbb_root_path . $config['ranks_path'] . '/' . $ranks['special'][$user_data['user_rank']]['rank_image']) : ''; + + $user_rank_data['img'] = (!empty($ranks['special'][$user_data['user_rank']]['rank_image'])) ? '' . $ranks['special'][$user_data['user_rank']]['rank_title'] . '' : ''; + } + else if ($user_posts !== false) + { + if (!empty($ranks['normal'])) + { + foreach ($ranks['normal'] as $rank) + { + if ($user_posts >= $rank['rank_min']) + { + $user_rank_data['title'] = $rank['rank_title']; + $user_rank_data['img_src'] = (!empty($rank['rank_image'])) ? $phpbb_path_helper->update_web_root_path($phpbb_root_path . $config['ranks_path'] . '/' . $rank['rank_image']) : ''; + $user_rank_data['img'] = (!empty($rank['rank_image'])) ? '' . $rank['rank_title'] . '' : ''; + break; + } + } + } + } + + /** + * Modify a user's rank before displaying + * + * @event core.get_user_rank_after + * @var array user_data Array with user's data + * @var int user_posts User_posts to change + * @var array user_rank_data User rank data + * @since 3.1.11-RC1 + */ + + $vars = array( + 'user_data', + 'user_posts', + 'user_rank_data', + ); + extract($phpbb_dispatcher->trigger_event('core.get_user_rank_after', compact($vars))); + + return $user_rank_data; +} + +/** +* Prepare profile data +*/ +function phpbb_show_profile($data, $user_notes_enabled = false, $warn_user_enabled = false, $check_can_receive_pm = true) +{ + global $config, $auth, $user, $phpEx, $phpbb_root_path, $phpbb_dispatcher; + + $username = $data['username']; + $user_id = $data['user_id']; + + $user_rank_data = phpbb_get_user_rank($data, (($user_id == ANONYMOUS) ? false : $data['user_posts'])); + + if ((!empty($data['user_allow_viewemail']) && $auth->acl_get('u_sendemail')) || $auth->acl_get('a_user')) + { + $email = ($config['board_email_form'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=email&u=' . $user_id) : (($config['board_hide_emails'] && !$auth->acl_get('a_user')) ? '' : 'mailto:' . $data['user_email']); + } + else + { + $email = ''; + } + + if ($config['load_onlinetrack']) + { + $update_time = $config['load_online_time'] * 60; + $online = (time() - $update_time < $data['session_time'] && ((isset($data['session_viewonline']) && $data['session_viewonline']) || $auth->acl_get('u_viewonline'))) ? true : false; + } + else + { + $online = false; + } + + if ($data['user_allow_viewonline'] || $auth->acl_get('u_viewonline')) + { + $last_active = (!empty($data['session_time'])) ? $data['session_time'] : $data['user_lastvisit']; + } + else + { + $last_active = ''; + } + + $age = ''; + + if ($config['allow_birthdays'] && $data['user_birthday']) + { + list($bday_day, $bday_month, $bday_year) = array_map('intval', explode('-', $data['user_birthday'])); + + if ($bday_year) + { + $now = $user->create_datetime(); + $now = phpbb_gmgetdate($now->getTimestamp() + $now->getOffset()); + + $diff = $now['mon'] - $bday_month; + if ($diff == 0) + { + $diff = ($now['mday'] - $bday_day < 0) ? 1 : 0; + } + else + { + $diff = ($diff < 0) ? 1 : 0; + } + + $age = max(0, (int) ($now['year'] - $bday_year - $diff)); + } + } + + if (!function_exists('phpbb_get_banned_user_ids')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + // Can this user receive a Private Message? + $can_receive_pm = $check_can_receive_pm && ( + // They must be a "normal" user + $data['user_type'] != USER_IGNORE && + + // They must not be deactivated by the administrator + ($data['user_type'] != USER_INACTIVE || $data['user_inactive_reason'] != INACTIVE_MANUAL) && + + // They must be able to read PMs + count($auth->acl_get_list($user_id, 'u_readpm')) && + + // They must not be permanently banned + !count(phpbb_get_banned_user_ids($user_id, false)) && + + // They must allow users to contact via PM + (($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) || $data['user_allow_pm']) + ); + + // Dump it out to the template + $template_data = array( + 'AGE' => $age, + 'RANK_TITLE' => $user_rank_data['title'], + 'JOINED' => $user->format_date($data['user_regdate']), + 'LAST_ACTIVE' => (empty($last_active)) ? ' - ' : $user->format_date($last_active), + 'POSTS' => ($data['user_posts']) ? $data['user_posts'] : 0, + 'WARNINGS' => isset($data['user_warnings']) ? $data['user_warnings'] : 0, + + 'USERNAME_FULL' => get_username_string('full', $user_id, $username, $data['user_colour']), + 'USERNAME' => get_username_string('username', $user_id, $username, $data['user_colour']), + 'USER_COLOR' => get_username_string('colour', $user_id, $username, $data['user_colour']), + 'U_VIEW_PROFILE' => get_username_string('profile', $user_id, $username, $data['user_colour']), + + 'A_USERNAME' => addslashes(get_username_string('username', $user_id, $username, $data['user_colour'])), + + 'AVATAR_IMG' => phpbb_get_user_avatar($data), + 'ONLINE_IMG' => (!$config['load_onlinetrack']) ? '' : (($online) ? $user->img('icon_user_online', 'ONLINE') : $user->img('icon_user_offline', 'OFFLINE')), + 'S_ONLINE' => ($config['load_onlinetrack'] && $online) ? true : false, + 'RANK_IMG' => $user_rank_data['img'], + 'RANK_IMG_SRC' => $user_rank_data['img_src'], + 'S_JABBER_ENABLED' => ($config['jab_enable']) ? true : false, + + 'S_WARNINGS' => ($auth->acl_getf_global('m_') || $auth->acl_get('m_warn')) ? true : false, + + 'U_SEARCH_USER' => ($auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id=$user_id&sr=posts") : '', + 'U_NOTES' => ($user_notes_enabled && $auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $user_id, true, $user->session_id) : '', + 'U_WARN' => ($warn_user_enabled && $auth->acl_get('m_warn')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user&u=' . $user_id, true, $user->session_id) : '', + 'U_PM' => ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && $can_receive_pm) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $user_id) : '', + 'U_EMAIL' => $email, + 'U_JABBER' => ($data['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=jabber&u=' . $user_id) : '', + + 'USER_JABBER' => ($config['jab_enable']) ? $data['user_jabber'] : '', + 'USER_JABBER_IMG' => ($config['jab_enable'] && $data['user_jabber']) ? $user->img('icon_contact_jabber', $data['user_jabber']) : '', + + 'L_SEND_EMAIL_USER' => $user->lang('SEND_EMAIL_USER', $username), + 'L_CONTACT_USER' => $user->lang('CONTACT_USER', $username), + 'L_VIEWING_PROFILE' => $user->lang('VIEWING_PROFILE', $username), + ); + + /** + * Preparing a user's data before displaying it in profile and memberlist + * + * @event core.memberlist_prepare_profile_data + * @var array data Array with user's data + * @var array template_data Template array with user's data + * @since 3.1.0-a1 + */ + $vars = array('data', 'template_data'); + extract($phpbb_dispatcher->trigger_event('core.memberlist_prepare_profile_data', compact($vars))); + + return $template_data; +} + +function phpbb_sort_last_active($first, $second) +{ + global $id_cache, $sort_dir; + + $lesser_than = ($sort_dir === 'd') ? -1 : 1; + + if (isset($id_cache[$first]['group_leader']) && $id_cache[$first]['group_leader'] && (!isset($id_cache[$second]['group_leader']) || !$id_cache[$second]['group_leader'])) + { + return -1; + } + else if (isset($id_cache[$second]['group_leader']) && (!isset($id_cache[$first]['group_leader']) || !$id_cache[$first]['group_leader']) && $id_cache[$second]['group_leader']) + { + return 1; + } + else + { + return $lesser_than * (int) ($id_cache[$first]['last_visit'] - $id_cache[$second]['last_visit']); + } +} diff --git a/includes/functions_download.php b/includes/functions_download.php new file mode 100644 index 0000000..7be12ba --- /dev/null +++ b/includes/functions_download.php @@ -0,0 +1,790 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* A simplified function to deliver avatars +* The argument needs to be checked before calling this function. +*/ +function send_avatar_to_browser($file, $browser) +{ + global $config, $phpbb_root_path; + + $prefix = $config['avatar_salt'] . '_'; + $image_dir = $config['avatar_path']; + + // Adjust image_dir path (no trailing slash) + if (substr($image_dir, -1, 1) == '/' || substr($image_dir, -1, 1) == '\\') + { + $image_dir = substr($image_dir, 0, -1) . '/'; + } + $image_dir = str_replace(array('../', '..\\', './', '.\\'), '', $image_dir); + + if ($image_dir && ($image_dir[0] == '/' || $image_dir[0] == '\\')) + { + $image_dir = ''; + } + $file_path = $phpbb_root_path . $image_dir . '/' . $prefix . $file; + + if ((@file_exists($file_path) && @is_readable($file_path)) && !headers_sent()) + { + header('Cache-Control: public'); + + $image_data = @getimagesize($file_path); + header('Content-Type: ' . image_type_to_mime_type($image_data[2])); + + if ((strpos(strtolower($browser), 'msie') !== false) && !phpbb_is_greater_ie_version($browser, 7)) + { + header('Content-Disposition: attachment; ' . header_filename($file)); + + if (strpos(strtolower($browser), 'msie 6.0') !== false) + { + header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); + } + else + { + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT'); + } + } + else + { + header('Content-Disposition: inline; ' . header_filename($file)); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT'); + } + + $size = @filesize($file_path); + if ($size) + { + header("Content-Length: $size"); + } + + if (@readfile($file_path) == false) + { + $fp = @fopen($file_path, 'rb'); + + if ($fp !== false) + { + while (!feof($fp)) + { + echo fread($fp, 8192); + } + fclose($fp); + } + } + + flush(); + } + else + { + header('HTTP/1.0 404 Not Found'); + } +} + +/** +* Wraps an url into a simple html page. Used to display attachments in IE. +* this is a workaround for now; might be moved to template system later +* direct any complaints to 1 Microsoft Way, Redmond +*/ +function wrap_img_in_html($src, $title) +{ + echo ''; + echo ''; + echo ''; + echo ''; + echo ''; + echo '' . $title . ''; + echo ''; + echo ''; + echo '
'; + echo '' . $title . ''; + echo '
'; + echo ''; + echo ''; +} + +/** +* Send file to browser +*/ +function send_file_to_browser($attachment, $upload_dir, $category) +{ + global $user, $db, $phpbb_dispatcher, $phpbb_root_path, $request; + + $filename = $phpbb_root_path . $upload_dir . '/' . $attachment['physical_filename']; + + if (!@file_exists($filename)) + { + send_status_line(404, 'Not Found'); + trigger_error('ERROR_NO_ATTACHMENT'); + } + + // Correct the mime type - we force application/octetstream for all files, except images + // Please do not change this, it is a security precaution + if ($category != ATTACHMENT_CATEGORY_IMAGE || strpos($attachment['mimetype'], 'image') !== 0) + { + $attachment['mimetype'] = (strpos(strtolower($user->browser), 'msie') !== false || strpos(strtolower($user->browser), 'opera') !== false) ? 'application/octetstream' : 'application/octet-stream'; + } + + if (@ob_get_length()) + { + @ob_end_clean(); + } + + // Now send the File Contents to the Browser + $size = @filesize($filename); + + /** + * Event to alter attachment before it is sent to browser. + * + * @event core.send_file_to_browser_before + * @var array attachment Attachment data + * @var string upload_dir Relative path of upload directory + * @var int category Attachment category + * @var string filename Path to file, including filename + * @var int size File size + * @since 3.1.11-RC1 + */ + $vars = array( + 'attachment', + 'upload_dir', + 'category', + 'filename', + 'size', + ); + extract($phpbb_dispatcher->trigger_event('core.send_file_to_browser_before', compact($vars))); + + // To correctly display further errors we need to make sure we are using the correct headers for both (unsetting content-length may not work) + + // Check if headers already sent or not able to get the file contents. + if (headers_sent() || !@file_exists($filename) || !@is_readable($filename)) + { + // PHP track_errors setting On? + if (!empty($php_errormsg)) + { + send_status_line(500, 'Internal Server Error'); + trigger_error($user->lang['UNABLE_TO_DELIVER_FILE'] . '
' . sprintf($user->lang['TRACKED_PHP_ERROR'], $php_errormsg)); + } + + send_status_line(500, 'Internal Server Error'); + trigger_error('UNABLE_TO_DELIVER_FILE'); + } + + // Make sure the database record for the filesize is correct + if ($size > 0 && $size != $attachment['filesize'] && strpos($attachment['physical_filename'], 'thumb_') === false) + { + // Update database record + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET filesize = ' . (int) $size . ' + WHERE attach_id = ' . (int) $attachment['attach_id']; + $db->sql_query($sql); + } + + // Now the tricky part... let's dance + header('Cache-Control: public'); + + // Send out the Headers. Do not set Content-Disposition to inline please, it is a security measure for users using the Internet Explorer. + header('Content-Type: ' . $attachment['mimetype']); + + if (phpbb_is_greater_ie_version($user->browser, 7)) + { + header('X-Content-Type-Options: nosniff'); + } + + if ($category == ATTACHMENT_CATEGORY_FLASH && $request->variable('view', 0) === 1) + { + // We use content-disposition: inline for flash files and view=1 to let it correctly play with flash player 10 - any other disposition will fail to play inline + header('Content-Disposition: inline'); + } + else + { + if (empty($user->browser) || ((strpos(strtolower($user->browser), 'msie') !== false) && !phpbb_is_greater_ie_version($user->browser, 7))) + { + header('Content-Disposition: attachment; ' . header_filename(htmlspecialchars_decode($attachment['real_filename']))); + if (empty($user->browser) || (strpos(strtolower($user->browser), 'msie 6.0') !== false)) + { + header('Expires: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT'); + } + } + else + { + header('Content-Disposition: ' . ((strpos($attachment['mimetype'], 'image') === 0) ? 'inline' : 'attachment') . '; ' . header_filename(htmlspecialchars_decode($attachment['real_filename']))); + if (phpbb_is_greater_ie_version($user->browser, 7) && (strpos($attachment['mimetype'], 'image') !== 0)) + { + header('X-Download-Options: noopen'); + } + } + } + + // Close the db connection before sending the file etc. + file_gc(false); + + if (!set_modified_headers($attachment['filetime'], $user->browser)) + { + // We make sure those have to be enabled manually by defining a constant + // because of the potential disclosure of full attachment path + // in case support for features is absent in the webserver software. + if (defined('PHPBB_ENABLE_X_ACCEL_REDIRECT') && PHPBB_ENABLE_X_ACCEL_REDIRECT) + { + // X-Accel-Redirect - http://wiki.nginx.org/XSendfile + header('X-Accel-Redirect: ' . $user->page['root_script_path'] . $upload_dir . '/' . $attachment['physical_filename']); + exit; + } + else if (defined('PHPBB_ENABLE_X_SENDFILE') && PHPBB_ENABLE_X_SENDFILE && !phpbb_http_byte_range($size)) + { + // X-Sendfile - http://blog.lighttpd.net/articles/2006/07/02/x-sendfile + // Lighttpd's X-Sendfile does not support range requests as of 1.4.26 + // and always requires an absolute path. + header('X-Sendfile: ' . dirname(__FILE__) . "/../$upload_dir/{$attachment['physical_filename']}"); + exit; + } + + if ($size) + { + header("Content-Length: $size"); + } + + // Try to deliver in chunks + @set_time_limit(0); + + $fp = @fopen($filename, 'rb'); + + if ($fp !== false) + { + // Deliver file partially if requested + if ($range = phpbb_http_byte_range($size)) + { + fseek($fp, $range['byte_pos_start']); + + send_status_line(206, 'Partial Content'); + header('Content-Range: bytes ' . $range['byte_pos_start'] . '-' . $range['byte_pos_end'] . '/' . $range['bytes_total']); + header('Content-Length: ' . $range['bytes_requested']); + + // First read chunks + while (!feof($fp) && ftell($fp) < $range['byte_pos_end'] - 8192) + { + echo fread($fp, 8192); + } + // Then, read the remainder + echo fread($fp, $range['bytes_requested'] % 8192); + } + else + { + while (!feof($fp)) + { + echo fread($fp, 8192); + } + } + fclose($fp); + } + else + { + @readfile($filename); + } + + flush(); + } + + exit; +} + +/** +* Get a browser friendly UTF-8 encoded filename +*/ +function header_filename($file) +{ + global $request; + + $user_agent = $request->header('User-Agent'); + + // There be dragons here. + // Not many follows the RFC... + if (strpos($user_agent, 'MSIE') !== false || strpos($user_agent, 'Konqueror') !== false) + { + return "filename=" . rawurlencode($file); + } + + // follow the RFC for extended filename for the rest + return "filename*=UTF-8''" . rawurlencode($file); +} + +/** +* Check if downloading item is allowed +*/ +function download_allowed() +{ + global $config, $user, $db, $request; + + if (!$config['secure_downloads']) + { + return true; + } + + $url = htmlspecialchars_decode($request->header('Referer')); + + if (!$url) + { + return ($config['secure_allow_empty_referer']) ? true : false; + } + + // Split URL into domain and script part + $url = @parse_url($url); + + if ($url === false) + { + return ($config['secure_allow_empty_referer']) ? true : false; + } + + $hostname = $url['host']; + unset($url); + + $allowed = ($config['secure_allow_deny']) ? false : true; + $iplist = array(); + + if (($ip_ary = @gethostbynamel($hostname)) !== false) + { + foreach ($ip_ary as $ip) + { + if ($ip) + { + $iplist[] = $ip; + } + } + } + + // Check for own server... + $server_name = $user->host; + + // Forcing server vars is the only way to specify/override the protocol + if ($config['force_server_vars'] || !$server_name) + { + $server_name = $config['server_name']; + } + + if (preg_match('#^.*?' . preg_quote($server_name, '#') . '.*?$#i', $hostname)) + { + $allowed = true; + } + + // Get IP's and Hostnames + if (!$allowed) + { + $sql = 'SELECT site_ip, site_hostname, ip_exclude + FROM ' . SITELIST_TABLE; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $site_ip = trim($row['site_ip']); + $site_hostname = trim($row['site_hostname']); + + if ($site_ip) + { + foreach ($iplist as $ip) + { + if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_ip, '#')) . '$#i', $ip)) + { + if ($row['ip_exclude']) + { + $allowed = ($config['secure_allow_deny']) ? false : true; + break 2; + } + else + { + $allowed = ($config['secure_allow_deny']) ? true : false; + } + } + } + } + + if ($site_hostname) + { + if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($site_hostname, '#')) . '$#i', $hostname)) + { + if ($row['ip_exclude']) + { + $allowed = ($config['secure_allow_deny']) ? false : true; + break; + } + else + { + $allowed = ($config['secure_allow_deny']) ? true : false; + } + } + } + } + $db->sql_freeresult($result); + } + + return $allowed; +} + +/** +* Check if the browser has the file already and set the appropriate headers- +* @returns false if a resend is in order. +*/ +function set_modified_headers($stamp, $browser) +{ + global $request; + + // let's see if we have to send the file at all + $last_load = $request->header('If-Modified-Since') ? strtotime(trim($request->header('If-Modified-Since'))) : false; + + if (strpos(strtolower($browser), 'msie 6.0') === false && !phpbb_is_greater_ie_version($browser, 7)) + { + if ($last_load !== false && $last_load >= $stamp) + { + send_status_line(304, 'Not Modified'); + // seems that we need those too ... browsers + header('Cache-Control: public'); + header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT'); + return true; + } + else + { + header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $stamp) . ' GMT'); + } + } + return false; +} + +/** +* Garbage Collection +* +* @param bool $exit Whether to die or not. +* +* @return null +*/ +function file_gc($exit = true) +{ + global $cache, $db; + + if (!empty($cache)) + { + $cache->unload(); + } + + $db->sql_close(); + + if ($exit) + { + exit; + } +} + +/** +* HTTP range support (RFC 2616 Section 14.35) +* +* Allows browsers to request partial file content +* in case a download has been interrupted. +* +* @param int $filesize the size of the file in bytes we are about to deliver +* +* @return mixed false if the whole file has to be delivered +* associative array on success +*/ +function phpbb_http_byte_range($filesize) +{ + // Only call find_range_request() once. + static $request_array; + + if (!$filesize) + { + return false; + } + + if (!isset($request_array)) + { + $request_array = phpbb_find_range_request(); + } + + return (empty($request_array)) ? false : phpbb_parse_range_request($request_array, $filesize); +} + +/** +* Searches for HTTP range request in request headers. +* +* @return mixed false if no request found +* array of strings containing the requested ranges otherwise +* e.g. array(0 => '0-0', 1 => '123-125') +*/ +function phpbb_find_range_request() +{ + global $request; + + $value = $request->header('Range'); + + // Make sure range request starts with "bytes=" + if (strpos($value, 'bytes=') === 0) + { + // Strip leading 'bytes=' + // Multiple ranges can be separated by a comma + return explode(',', substr($value, 6)); + } + + return false; +} + +/** +* Analyses a range request array. +* +* A range request can contain multiple ranges, +* we however only handle the first request and +* only support requests from a given byte to the end of the file. +* +* @param array $request_array array of strings containing the requested ranges +* @param int $filesize the full size of the file in bytes that has been requested +* +* @return mixed false if the whole file has to be delivered +* associative array on success +* byte_pos_start the first byte position, can be passed to fseek() +* byte_pos_end the last byte position +* bytes_requested the number of bytes requested +* bytes_total the full size of the file +*/ +function phpbb_parse_range_request($request_array, $filesize) +{ + $first_byte_pos = -1; + $last_byte_pos = -1; + + // Go through all ranges + foreach ($request_array as $range_string) + { + $range = explode('-', trim($range_string)); + + // "-" is invalid, "0-0" however is valid and means the very first byte. + if (count($range) != 2 || $range[0] === '' && $range[1] === '') + { + continue; + } + + // Substitute defaults + if ($range[0] === '') + { + $range[0] = 0; + } + + if ($range[1] === '') + { + $range[1] = $filesize - 1; + } + + if ($last_byte_pos >= 0 && $last_byte_pos + 1 != $range[0]) + { + // We only support contiguous ranges, no multipart stuff :( + return false; + } + + if ($range[1] && $range[1] < $range[0]) + { + // The requested range contains 0 bytes. + continue; + } + + // Return bytes from $range[0] to $range[1] + if ($first_byte_pos < 0) + { + $first_byte_pos = (int) $range[0]; + } + + $last_byte_pos = (int) $range[1]; + + if ($first_byte_pos >= $filesize) + { + // Requested range not satisfiable + return false; + } + + // Adjust last-byte-pos if it is absent or greater than the content. + if ($range[1] === '' || $last_byte_pos >= $filesize) + { + $last_byte_pos = $filesize - 1; + } + } + + if ($first_byte_pos < 0 || $last_byte_pos < 0) + { + return false; + } + + return array( + 'byte_pos_start' => $first_byte_pos, + 'byte_pos_end' => $last_byte_pos, + 'bytes_requested' => $last_byte_pos - $first_byte_pos + 1, + 'bytes_total' => $filesize, + ); +} + +/** +* Increments the download count of all provided attachments +* +* @param \phpbb\db\driver\driver_interface $db The database object +* @param array|int $ids The attach_id of each attachment +* +* @return null +*/ +function phpbb_increment_downloads($db, $ids) +{ + if (!is_array($ids)) + { + $ids = array($ids); + } + + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET download_count = download_count + 1 + WHERE ' . $db->sql_in_set('attach_id', $ids); + $db->sql_query($sql); +} + +/** +* Handles authentication when downloading attachments from a post or topic +* +* @param \phpbb\db\driver\driver_interface $db The database object +* @param \phpbb\auth\auth $auth The authentication object +* @param int $topic_id The id of the topic that we are downloading from +* +* @return null +*/ +function phpbb_download_handle_forum_auth($db, $auth, $topic_id) +{ + global $phpbb_container; + + $sql_array = array( + 'SELECT' => 't.topic_visibility, t.forum_id, f.forum_name, f.forum_password, f.parent_id', + 'FROM' => array( + TOPICS_TABLE => 't', + FORUMS_TABLE => 'f', + ), + 'WHERE' => 't.topic_id = ' . (int) $topic_id . ' + AND t.forum_id = f.forum_id', + ); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + if ($row && !$phpbb_content_visibility->is_visible('topic', $row['forum_id'], $row)) + { + send_status_line(404, 'Not Found'); + trigger_error('ERROR_NO_ATTACHMENT'); + } + else if ($row && $auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id'])) + { + if ($row['forum_password']) + { + // Do something else ... ? + login_forum_box($row); + } + } + else + { + send_status_line(403, 'Forbidden'); + trigger_error('SORRY_AUTH_VIEW_ATTACH'); + } +} + +/** +* Handles authentication when downloading attachments from PMs +* +* @param \phpbb\db\driver\driver_interface $db The database object +* @param \phpbb\auth\auth $auth The authentication object +* @param int $user_id The user id +* @param int $msg_id The id of the PM that we are downloading from +* +* @return null +*/ +function phpbb_download_handle_pm_auth($db, $auth, $user_id, $msg_id) +{ + global $phpbb_dispatcher; + + if (!$auth->acl_get('u_pm_download')) + { + send_status_line(403, 'Forbidden'); + trigger_error('SORRY_AUTH_VIEW_ATTACH'); + } + + $allowed = phpbb_download_check_pm_auth($db, $user_id, $msg_id); + + /** + * Event to modify PM attachments download auth + * + * @event core.modify_pm_attach_download_auth + * @var bool allowed Whether the user is allowed to download from that PM or not + * @var int msg_id The id of the PM to download from + * @var int user_id The user id for auth check + * @since 3.1.11-RC1 + */ + $vars = array('allowed', 'msg_id', 'user_id'); + extract($phpbb_dispatcher->trigger_event('core.modify_pm_attach_download_auth', compact($vars))); + + if (!$allowed) + { + send_status_line(403, 'Forbidden'); + trigger_error('ERROR_NO_ATTACHMENT'); + } +} + +/** +* Checks whether a user can download from a particular PM +* +* @param \phpbb\db\driver\driver_interface $db The database object +* @param int $user_id The user id +* @param int $msg_id The id of the PM that we are downloading from +* +* @return bool Whether the user is allowed to download from that PM or not +*/ +function phpbb_download_check_pm_auth($db, $user_id, $msg_id) +{ + // Check if the attachment is within the users scope... + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE msg_id = ' . (int) $msg_id . ' + AND ( + user_id = ' . (int) $user_id . ' + OR author_id = ' . (int) $user_id . ' + )'; + $result = $db->sql_query_limit($sql, 1); + $allowed = (bool) $db->sql_fetchfield('msg_id'); + $db->sql_freeresult($result); + + return $allowed; +} + +/** +* Check if the browser is internet explorer version 7+ +* +* @param string $user_agent User agent HTTP header +* @param int $version IE version to check against +* +* @return bool true if internet explorer version is greater than $version +*/ +function phpbb_is_greater_ie_version($user_agent, $version) +{ + if (preg_match('/msie (\d+)/', strtolower($user_agent), $matches)) + { + $ie_version = (int) $matches[1]; + return ($ie_version > $version); + } + else + { + return false; + } +} diff --git a/includes/functions_jabber.php b/includes/functions_jabber.php new file mode 100644 index 0000000..cf0865e --- /dev/null +++ b/includes/functions_jabber.php @@ -0,0 +1,904 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* +* Jabber class from Flyspray project +* +* @version class.jabber2.php 1595 2008-09-19 (0.9.9) +* @copyright 2006 Flyspray.org +* @author Florian Schmitz (floele) +* +* Only slightly modified by Acyd Burn +*/ +class jabber +{ + var $connection = null; + var $session = array(); + var $timeout = 10; + + var $server; + var $connect_server; + var $port; + var $username; + var $password; + var $use_ssl; + var $verify_peer; + var $verify_peer_name; + var $allow_self_signed; + var $resource = 'functions_jabber.phpbb.php'; + + var $enable_logging; + var $log_array; + + var $features = array(); + + /** + * Constructor + * + * @param string $server Jabber server + * @param int $port Jabber server port + * @param string $username Jabber username or JID + * @param string $password Jabber password + * @param boold $use_ssl Use ssl + * @param bool $verify_peer Verify SSL certificate + * @param bool $verify_peer_name Verify Jabber peer name + * @param bool $allow_self_signed Allow self signed certificates + */ + function __construct($server, $port, $username, $password, $use_ssl = false, $verify_peer = true, $verify_peer_name = true, $allow_self_signed = false) + { + $this->connect_server = ($server) ? $server : 'localhost'; + $this->port = ($port) ? $port : 5222; + + // Get the server and the username + if (strpos($username, '@') === false) + { + $this->server = $this->connect_server; + $this->username = $username; + } + else + { + $jid = explode('@', $username, 2); + + $this->username = $jid[0]; + $this->server = $jid[1]; + } + + $this->password = $password; + $this->use_ssl = ($use_ssl && self::can_use_ssl()) ? true : false; + $this->verify_peer = $verify_peer; + $this->verify_peer_name = $verify_peer_name; + $this->allow_self_signed = $allow_self_signed; + + // Change port if we use SSL + if ($this->port == 5222 && $this->use_ssl) + { + $this->port = 5223; + } + + $this->enable_logging = true; + $this->log_array = array(); + } + + /** + * Able to use the SSL functionality? + */ + static public function can_use_ssl() + { + return @extension_loaded('openssl'); + } + + /** + * Able to use TLS? + */ + static public function can_use_tls() + { + if (!@extension_loaded('openssl') || !function_exists('stream_socket_enable_crypto') || !function_exists('stream_get_meta_data') || !function_exists('stream_set_blocking') || !function_exists('stream_get_wrappers')) + { + return false; + } + + /** + * Make sure the encryption stream is supported + * Also seem to work without the crypto stream if correctly compiled + + $streams = stream_get_wrappers(); + + if (!in_array('streams.crypto', $streams)) + { + return false; + } + */ + + return true; + } + + /** + * Sets the resource which is used. No validation is done here, only escaping. + * @param string $name + * @access public + */ + function set_resource($name) + { + $this->resource = $name; + } + + /** + * Connect + */ + function connect() + { +/* if (!$this->check_jid($this->username . '@' . $this->server)) + { + $this->add_to_log('Error: Jabber ID is not valid: ' . $this->username . '@' . $this->server); + return false; + }*/ + + $this->session['ssl'] = $this->use_ssl; + + if ($this->open_socket($this->connect_server, $this->port, $this->use_ssl, $this->verify_peer, $this->verify_peer_name, $this->allow_self_signed)) + { + $this->send("\n"); + $this->send("\n"); + } + else + { + $this->add_to_log('Error: connect() #2'); + return false; + } + + // Now we listen what the server has to say...and give appropriate responses + $this->response($this->listen()); + return true; + } + + /** + * Disconnect + */ + function disconnect() + { + if ($this->connected()) + { + // disconnect gracefully + if (isset($this->session['sent_presence'])) + { + $this->send_presence('offline', '', true); + } + + $this->send(''); + $this->session = array(); + return fclose($this->connection); + } + + return false; + } + + /** + * Connected? + */ + function connected() + { + return (is_resource($this->connection) && !feof($this->connection)) ? true : false; + } + + + /** + * Initiates login (using data from contructor, after calling connect()) + * @access public + * @return bool + */ + function login() + { + if (!count($this->features)) + { + $this->add_to_log('Error: No feature information from server available.'); + return false; + } + + return $this->response($this->features); + } + + /** + * Send data to the Jabber server + * @param string $xml + * @access public + * @return bool + */ + function send($xml) + { + if ($this->connected()) + { + $xml = trim($xml); + $this->add_to_log('SEND: '. $xml); + return fwrite($this->connection, $xml); + } + else + { + $this->add_to_log('Error: Could not send, connection lost (flood?).'); + return false; + } + } + + /** + * OpenSocket + * @param string $server host to connect to + * @param int $port port number + * @param bool $use_ssl use ssl or not + * @param bool $verify_peer verify ssl certificate + * @param bool $verify_peer_name verify peer name + * @param bool $allow_self_signed allow self-signed ssl certificates + * @access public + * @return bool + */ + function open_socket($server, $port, $use_ssl, $verify_peer, $verify_peer_name, $allow_self_signed) + { + if (@function_exists('dns_get_record')) + { + $record = @dns_get_record("_xmpp-client._tcp.$server", DNS_SRV); + if (!empty($record) && !empty($record[0]['target'])) + { + $server = $record[0]['target']; + } + } + + $options = array(); + + if ($use_ssl) + { + $remote_socket = 'ssl://' . $server . ':' . $port; + + // Set ssl context options, see http://php.net/manual/en/context.ssl.php + $options['ssl'] = array('verify_peer' => $verify_peer, 'verify_peer_name' => $verify_peer_name, 'allow_self_signed' => $allow_self_signed); + } + else + { + $remote_socket = $server . ':' . $port; + } + + $socket_context = stream_context_create($options); + + if ($this->connection = @stream_socket_client($remote_socket, $errorno, $errorstr, $this->timeout, STREAM_CLIENT_CONNECT, $socket_context)) + { + stream_set_blocking($this->connection, 0); + stream_set_timeout($this->connection, 60); + + return true; + } + + // Apparently an error occurred... + $this->add_to_log('Error: open_socket() - ' . $errorstr); + return false; + } + + /** + * Return log + */ + function get_log() + { + if ($this->enable_logging && count($this->log_array)) + { + return implode("

", $this->log_array); + } + + return ''; + } + + /** + * Add information to log + */ + function add_to_log($string) + { + if ($this->enable_logging) + { + $this->log_array[] = utf8_htmlspecialchars($string); + } + } + + /** + * Listens to the connection until it gets data or the timeout is reached. + * Thus, it should only be called if data is expected to be received. + * @access public + * @return mixed either false for timeout or an array with the received data + */ + function listen($timeout = 10, $wait = false) + { + if (!$this->connected()) + { + return false; + } + + // Wait for a response until timeout is reached + $start = time(); + $data = ''; + + do + { + $read = trim(fread($this->connection, 4096)); + $data .= $read; + } + while (time() <= $start + $timeout && !feof($this->connection) && ($wait || $data == '' || $read != '' || (substr(rtrim($data), -1) != '>'))); + + if ($data != '') + { + $this->add_to_log('RECV: '. $data); + return $this->xmlize($data); + } + else + { + $this->add_to_log('Timeout, no response from server.'); + return false; + } + } + + /** + * Initiates account registration (based on data used for contructor) + * @access public + * @return bool + */ + function register() + { + if (!isset($this->session['id']) || isset($this->session['jid'])) + { + $this->add_to_log('Error: Cannot initiate registration.'); + return false; + } + + $this->send(""); + return $this->response($this->listen()); + } + + /** + * Sets account presence. No additional info required (default is "online" status) + * @param $message online, offline... + * @param $type dnd, away, chat, xa or nothing + * @param $unavailable set this to true if you want to become unavailable + * @access public + * @return bool + */ + function send_presence($message = '', $type = '', $unavailable = false) + { + if (!isset($this->session['jid'])) + { + $this->add_to_log('ERROR: send_presence() - Cannot set presence at this point, no jid given.'); + return false; + } + + $type = strtolower($type); + $type = (in_array($type, array('dnd', 'away', 'chat', 'xa'))) ? ''. $type .'' : ''; + + $unavailable = ($unavailable) ? " type='unavailable'" : ''; + $message = ($message) ? '' . utf8_htmlspecialchars($message) .'' : ''; + + $this->session['sent_presence'] = !$unavailable; + + return $this->send("" . $type . $message . ''); + } + + /** + * This handles all the different XML elements + * @param array $xml + * @access public + * @return bool + */ + function response($xml) + { + if (!is_array($xml) || !count($xml)) + { + return false; + } + + // did we get multiple elements? do one after another + // array('message' => ..., 'presence' => ...) + if (count($xml) > 1) + { + foreach ($xml as $key => $value) + { + $this->response(array($key => $value)); + } + return; + } + else + { + // or even multiple elements of the same type? + // array('message' => array(0 => ..., 1 => ...)) + if (count(reset($xml)) > 1) + { + foreach (reset($xml) as $value) + { + $this->response(array(key($xml) => array(0 => $value))); + } + return; + } + } + + switch (key($xml)) + { + case 'stream:stream': + // Connection initialised (or after authentication). Not much to do here... + + if (isset($xml['stream:stream'][0]['#']['stream:features'])) + { + // we already got all info we need + $this->features = $xml['stream:stream'][0]['#']; + } + else + { + $this->features = $this->listen(); + } + + $second_time = isset($this->session['id']); + $this->session['id'] = $xml['stream:stream'][0]['@']['id']; + + if ($second_time) + { + // If we are here for the second time after TLS, we need to continue logging in + return $this->login(); + } + + // go on with authentication? + if (isset($this->features['stream:features'][0]['#']['bind']) || !empty($this->session['tls'])) + { + return $this->response($this->features); + } + break; + + case 'stream:features': + // Resource binding after successful authentication + if (isset($this->session['authenticated'])) + { + // session required? + $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']); + + $this->send(" + + " . utf8_htmlspecialchars($this->resource) . ' + + '); + return $this->response($this->listen()); + } + + // Let's use TLS if SSL is not enabled and we can actually use it + if (!$this->session['ssl'] && self::can_use_tls() && self::can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls'])) + { + $this->add_to_log('Switching to TLS.'); + $this->send("\n"); + return $this->response($this->listen()); + } + + // Does the server support SASL authentication? + + // I hope so, because we do (and no other method). + if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) && $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl') + { + // Now decide on method + $methods = array(); + + foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value) + { + $methods[] = $value['#']; + } + + // we prefer DIGEST-MD5 + // we don't want to use plain authentication (neither does the server usually) if no encryption is in place + + // http://www.xmpp.org/extensions/attic/jep-0078-1.7.html + // The plaintext mechanism SHOULD NOT be used unless the underlying stream is encrypted (using SSL or TLS) + // and the client has verified that the server certificate is signed by a trusted certificate authority. + + if (in_array('DIGEST-MD5', $methods)) + { + $this->send(""); + } + else if (in_array('PLAIN', $methods) && ($this->session['ssl'] || !empty($this->session['tls']))) + { + // http://www.ietf.org/rfc/rfc4616.txt (PLAIN SASL Mechanism) + $this->send("" + . base64_encode($this->username . '@' . $this->server . chr(0) . $this->username . chr(0) . $this->password) . + ''); + } + else if (in_array('ANONYMOUS', $methods)) + { + $this->send(""); + } + else + { + // not good... + $this->add_to_log('Error: No authentication method supported.'); + $this->disconnect(); + return false; + } + + return $this->response($this->listen()); + } + else + { + // ok, this is it. bye. + $this->add_to_log('Error: Server does not offer SASL authentication.'); + $this->disconnect(); + return false; + } + break; + + case 'challenge': + // continue with authentication...a challenge literally -_- + $decoded = base64_decode($xml['challenge'][0]['#']); + $decoded = $this->parse_data($decoded); + + if (!isset($decoded['digest-uri'])) + { + $decoded['digest-uri'] = 'xmpp/'. $this->server; + } + + // better generate a cnonce, maybe it's needed + $decoded['cnonce'] = base64_encode(md5(uniqid(mt_rand(), true))); + + // second challenge? + if (isset($decoded['rspauth'])) + { + $this->send(""); + } + else + { + // Make sure we only use 'auth' for qop (relevant for $this->encrypt_password()) + // If the is choking up on the changed parameter we may need to adjust encrypt_password() directly + if (isset($decoded['qop']) && $decoded['qop'] != 'auth' && strpos($decoded['qop'], 'auth') !== false) + { + $decoded['qop'] = 'auth'; + } + + $response = array( + 'username' => $this->username, + 'response' => $this->encrypt_password(array_merge($decoded, array('nc' => '00000001'))), + 'charset' => 'utf-8', + 'nc' => '00000001', + 'qop' => 'auth', // only auth being supported + ); + + foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key) + { + if (isset($decoded[$key])) + { + $response[$key] = $decoded[$key]; + } + } + + $this->send("" . base64_encode($this->implode_data($response)) . ''); + } + + return $this->response($this->listen()); + break; + + case 'failure': + $this->add_to_log('Error: Server sent "failure".'); + $this->disconnect(); + return false; + break; + + case 'proceed': + // continue switching to TLS + $meta = stream_get_meta_data($this->connection); + stream_set_blocking($this->connection, 1); + + if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) + { + $this->add_to_log('Error: TLS mode change failed.'); + return false; + } + + stream_set_blocking($this->connection, $meta['blocked']); + $this->session['tls'] = true; + + // new stream + $this->send("\n"); + $this->send("\n"); + + return $this->response($this->listen()); + break; + + case 'success': + // Yay, authentication successful. + $this->send("\n"); + $this->session['authenticated'] = true; + + // we have to wait for another response + return $this->response($this->listen()); + break; + + case 'iq': + // we are not interested in IQs we did not expect + if (!isset($xml['iq'][0]['@']['id'])) + { + return false; + } + + // multiple possibilities here + switch ($xml['iq'][0]['@']['id']) + { + case 'bind_1': + $this->session['jid'] = $xml['iq'][0]['#']['bind'][0]['#']['jid'][0]['#']; + + // and (maybe) yet another request to be able to send messages *finally* + if ($this->session['sess_required']) + { + $this->send(" + + "); + return $this->response($this->listen()); + } + + return true; + break; + + case 'sess_1': + return true; + break; + + case 'reg_1': + $this->send(" + + " . utf8_htmlspecialchars($this->username) . " + " . utf8_htmlspecialchars($this->password) . " + + "); + return $this->response($this->listen()); + break; + + case 'reg_2': + // registration end + if (isset($xml['iq'][0]['#']['error'])) + { + $this->add_to_log('Warning: Registration failed.'); + return false; + } + return true; + break; + + case 'unreg_1': + return true; + break; + + default: + $this->add_to_log('Notice: Received unexpected IQ.'); + return false; + break; + } + break; + + case 'message': + // we are only interested in content... + if (!isset($xml['message'][0]['#']['body'])) + { + return false; + } + + $message['body'] = $xml['message'][0]['#']['body'][0]['#']; + $message['from'] = $xml['message'][0]['@']['from']; + + if (isset($xml['message'][0]['#']['subject'])) + { + $message['subject'] = $xml['message'][0]['#']['subject'][0]['#']; + } + $this->session['messages'][] = $message; + break; + + default: + // hm...don't know this response + $this->add_to_log('Notice: Unknown server response (' . key($xml) . ')'); + return false; + break; + } + } + + function send_message($to, $text, $subject = '', $type = 'normal') + { + if (!isset($this->session['jid'])) + { + return false; + } + + if (!in_array($type, array('chat', 'normal', 'error', 'groupchat', 'headline'))) + { + $type = 'normal'; + } + + return $this->send(" + " . utf8_htmlspecialchars($subject) . " + " . utf8_htmlspecialchars($text) . " + " + ); + } + + /** + * Encrypts a password as in RFC 2831 + * @param array $data Needs data from the client-server connection + * @access public + * @return string + */ + function encrypt_password($data) + { + // let's me think about again... + foreach (array('realm', 'cnonce', 'digest-uri') as $key) + { + if (!isset($data[$key])) + { + $data[$key] = ''; + } + } + + $pack = md5($this->username . ':' . $data['realm'] . ':' . $this->password); + + if (isset($data['authzid'])) + { + $a1 = pack('H32', $pack) . sprintf(':%s:%s:%s', $data['nonce'], $data['cnonce'], $data['authzid']); + } + else + { + $a1 = pack('H32', $pack) . sprintf(':%s:%s', $data['nonce'], $data['cnonce']); + } + + // should be: qop = auth + $a2 = 'AUTHENTICATE:'. $data['digest-uri']; + + return md5(sprintf('%s:%s:%s:%s:%s:%s', md5($a1), $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], md5($a2))); + } + + /** + * parse_data like a="b",c="d",... or like a="a, b", c, d="e", f=g,... + * @param string $data + * @access public + * @return array a => b ... + */ + function parse_data($data) + { + $data = explode(',', $data); + $pairs = array(); + $key = false; + + foreach ($data as $pair) + { + $dd = strpos($pair, '='); + + if ($dd) + { + $key = trim(substr($pair, 0, $dd)); + $pairs[$key] = trim(trim(substr($pair, $dd + 1)), '"'); + } + else if (strpos(strrev(trim($pair)), '"') === 0 && $key) + { + // We are actually having something left from "a, b" values, add it to the last one we handled. + $pairs[$key] .= ',' . trim(trim($pair), '"'); + continue; + } + } + + return $pairs; + } + + /** + * opposite of jabber::parse_data() + * @param array $data + * @access public + * @return string + */ + function implode_data($data) + { + $return = array(); + foreach ($data as $key => $value) + { + $return[] = $key . '="' . $value . '"'; + } + return implode(',', $return); + } + + /** + * xmlize() + * @author Hans Anderson + * @copyright Hans Anderson / http://www.hansanderson.com/php/xml/ + */ + function xmlize($data, $skip_white = 1, $encoding = 'UTF-8') + { + $data = trim($data); + + if (substr($data, 0, 5) != ''. $data . ''; + } + + $vals = $index = $array = array(); + $parser = xml_parser_create($encoding); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $skip_white); + xml_parse_into_struct($parser, $data, $vals, $index); + xml_parser_free($parser); + + $i = 0; + $tagname = $vals[$i]['tag']; + + $array[$tagname][0]['@'] = (isset($vals[$i]['attributes'])) ? $vals[$i]['attributes'] : array(); + $array[$tagname][0]['#'] = $this->_xml_depth($vals, $i); + + if (substr($data, 0, 5) != '_xml_depth($vals, $i); + + break; + + case 'cdata': + array_push($children, $vals[$i]['value']); + break; + + case 'complete': + + $tagname = $vals[$i]['tag']; + $size = (isset($children[$tagname])) ? count($children[$tagname]) : 0; + $children[$tagname][$size]['#'] = (isset($vals[$i]['value'])) ? $vals[$i]['value'] : array(); + + if (isset($vals[$i]['attributes'])) + { + $children[$tagname][$size]['@'] = $vals[$i]['attributes']; + } + + break; + + case 'close': + return $children; + break; + } + } + + return $children; + } +} diff --git a/includes/functions_mcp.php b/includes/functions_mcp.php new file mode 100644 index 0000000..75e2461 --- /dev/null +++ b/includes/functions_mcp.php @@ -0,0 +1,743 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Functions used to generate additional URL paramters +*/ +function phpbb_module__url($mode, $module_row) +{ + return phpbb_extra_url(); +} + +function phpbb_module_notes_url($mode, $module_row) +{ + if ($mode == 'front') + { + return ''; + } + + global $user_id; + return ($user_id) ? "&u=$user_id" : ''; +} + +function phpbb_module_warn_url($mode, $module_row) +{ + if ($mode == 'front' || $mode == 'list') + { + global $forum_id; + + return ($forum_id) ? "&f=$forum_id" : ''; + } + + if ($mode == 'warn_post') + { + global $forum_id, $post_id; + + $url_extra = ($forum_id) ? "&f=$forum_id" : ''; + $url_extra .= ($post_id) ? "&p=$post_id" : ''; + + return $url_extra; + } + else + { + global $user_id; + + return ($user_id) ? "&u=$user_id" : ''; + } +} + +function phpbb_module_main_url($mode, $module_row) +{ + return phpbb_extra_url(); +} + +function phpbb_module_logs_url($mode, $module_row) +{ + return phpbb_extra_url(); +} + +function phpbb_module_ban_url($mode, $module_row) +{ + return phpbb_extra_url(); +} + +function phpbb_module_queue_url($mode, $module_row) +{ + return phpbb_extra_url(); +} + +function phpbb_module_reports_url($mode, $module_row) +{ + return phpbb_extra_url(); +} + +function phpbb_extra_url() +{ + global $forum_id, $topic_id, $post_id, $report_id, $user_id; + + $url_extra = ''; + $url_extra .= ($forum_id) ? "&f=$forum_id" : ''; + $url_extra .= ($topic_id) ? "&t=$topic_id" : ''; + $url_extra .= ($post_id) ? "&p=$post_id" : ''; + $url_extra .= ($user_id) ? "&u=$user_id" : ''; + $url_extra .= ($report_id) ? "&r=$report_id" : ''; + + return $url_extra; +} + +/** +* Get simple topic data +*/ +function phpbb_get_topic_data($topic_ids, $acl_list = false, $read_tracking = false) +{ + global $auth, $db, $config, $user; + static $rowset = array(); + + $topics = array(); + + if (!count($topic_ids)) + { + return array(); + } + + // cache might not contain read tracking info, so we can't use it if read + // tracking information is requested + if (!$read_tracking) + { + $cache_topic_ids = array_intersect($topic_ids, array_keys($rowset)); + $topic_ids = array_diff($topic_ids, array_keys($rowset)); + } + else + { + $cache_topic_ids = array(); + } + + if (count($topic_ids)) + { + $sql_array = array( + 'SELECT' => 't.*, f.*', + + 'FROM' => array( + TOPICS_TABLE => 't', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'f.forum_id = t.forum_id' + ) + ), + + 'WHERE' => $db->sql_in_set('t.topic_id', $topic_ids) + ); + + if ($read_tracking && $config['load_db_lastread']) + { + $sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time as forum_mark_time'; + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'), + 'ON' => 'tt.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = tt.topic_id' + ); + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'), + 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND t.forum_id = ft.forum_id' + ); + } + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $rowset[$row['topic_id']] = $row; + + if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id'])) + { + continue; + } + + $topics[$row['topic_id']] = $row; + } + $db->sql_freeresult($result); + } + + foreach ($cache_topic_ids as $id) + { + if (!$acl_list || $auth->acl_gets($acl_list, $rowset[$id]['forum_id'])) + { + $topics[$id] = $rowset[$id]; + } + } + + return $topics; +} + +/** +* Get simple post data +*/ +function phpbb_get_post_data($post_ids, $acl_list = false, $read_tracking = false) +{ + global $db, $auth, $config, $user, $phpbb_container; + + $rowset = array(); + + if (!count($post_ids)) + { + return array(); + } + + $sql_array = array( + 'SELECT' => 'p.*, u.*, t.*, f.*', + + 'FROM' => array( + USERS_TABLE => 'u', + POSTS_TABLE => 'p', + TOPICS_TABLE => 't', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'f.forum_id = t.forum_id' + ) + ), + + 'WHERE' => $db->sql_in_set('p.post_id', $post_ids) . ' + AND u.user_id = p.poster_id + AND t.topic_id = p.topic_id', + ); + + if ($read_tracking && $config['load_db_lastread']) + { + $sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time as forum_mark_time'; + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'), + 'ON' => 'tt.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = tt.topic_id' + ); + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'), + 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND t.forum_id = ft.forum_id' + ); + } + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + unset($sql_array); + + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + while ($row = $db->sql_fetchrow($result)) + { + if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id'])) + { + continue; + } + + if (!$phpbb_content_visibility->is_visible('post', $row['forum_id'], $row)) + { + // Moderators without the permission to approve post should at least not see them. ;) + continue; + } + + $rowset[$row['post_id']] = $row; + } + $db->sql_freeresult($result); + + return $rowset; +} + +/** +* Get simple forum data +*/ +function phpbb_get_forum_data($forum_id, $acl_list = 'f_list', $read_tracking = false) +{ + global $auth, $db, $user, $config, $phpbb_container; + + $rowset = array(); + + if (!is_array($forum_id)) + { + $forum_id = array($forum_id); + } + + if (!count($forum_id)) + { + return array(); + } + + if ($read_tracking && $config['load_db_lastread']) + { + $read_tracking_join = ' LEFT JOIN ' . FORUMS_TRACK_TABLE . ' ft ON (ft.user_id = ' . $user->data['user_id'] . ' + AND ft.forum_id = f.forum_id)'; + $read_tracking_select = ', ft.mark_time'; + } + else + { + $read_tracking_join = $read_tracking_select = ''; + } + + $sql = "SELECT f.* $read_tracking_select + FROM " . FORUMS_TABLE . " f$read_tracking_join + WHERE " . $db->sql_in_set('f.forum_id', $forum_id); + $result = $db->sql_query($sql); + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + while ($row = $db->sql_fetchrow($result)) + { + if ($acl_list && !$auth->acl_gets($acl_list, $row['forum_id'])) + { + continue; + } + + $row['forum_topics_approved'] = $phpbb_content_visibility->get_count('forum_topics', $row, $row['forum_id']); + + $rowset[$row['forum_id']] = $row; + } + $db->sql_freeresult($result); + + return $rowset; +} + +/** +* Get simple pm data +*/ +function phpbb_get_pm_data($pm_ids) +{ + global $db; + + $rowset = array(); + + if (!count($pm_ids)) + { + return array(); + } + + $sql_array = array( + 'SELECT' => 'p.*, u.*', + + 'FROM' => array( + USERS_TABLE => 'u', + PRIVMSGS_TABLE => 'p', + ), + + 'WHERE' => $db->sql_in_set('p.msg_id', $pm_ids) . ' + AND u.user_id = p.author_id', + ); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + unset($sql_array); + + while ($row = $db->sql_fetchrow($result)) + { + $rowset[$row['msg_id']] = $row; + } + $db->sql_freeresult($result); + + return $rowset; +} + +/** +* sorting in mcp +* +* @param string $where_sql should either be WHERE (default if ommited) or end with AND or OR +* +* $mode reports and reports_closed: the $where parameters uses aliases p for posts table and r for report table +* $mode unapproved_posts: the $where parameters uses aliases p for posts table and t for topic table +*/ +function phpbb_mcp_sorting($mode, &$sort_days_val, &$sort_key_val, &$sort_dir_val, &$sort_by_sql_ary, &$sort_order_sql, &$total_val, $forum_id = 0, $topic_id = 0, $where_sql = 'WHERE') +{ + global $db, $user, $auth, $template, $request, $phpbb_dispatcher; + + $sort_days_val = $request->variable('st', 0); + $min_time = ($sort_days_val) ? time() - ($sort_days_val * 86400) : 0; + + switch ($mode) + { + case 'viewforum': + $type = 'topics'; + $default_key = 't'; + $default_dir = 'd'; + + $sql = 'SELECT COUNT(topic_id) AS total + FROM ' . TOPICS_TABLE . " + $where_sql forum_id = $forum_id + AND topic_type NOT IN (" . POST_ANNOUNCE . ', ' . POST_GLOBAL . ") + AND topic_last_post_time >= $min_time"; + + if (!$auth->acl_get('m_approve', $forum_id)) + { + $sql .= ' AND topic_visibility = ' . ITEM_APPROVED; + } + break; + + case 'viewtopic': + $type = 'posts'; + $default_key = 't'; + $default_dir = 'a'; + + $sql = 'SELECT COUNT(post_id) AS total + FROM ' . POSTS_TABLE . " + $where_sql topic_id = $topic_id + AND post_time >= $min_time"; + + if (!$auth->acl_get('m_approve', $forum_id)) + { + $sql .= ' AND post_visibility = ' . ITEM_APPROVED; + } + break; + + case 'unapproved_posts': + case 'deleted_posts': + $visibility_const = ($mode == 'unapproved_posts') ? array(ITEM_UNAPPROVED, ITEM_REAPPROVE) : ITEM_DELETED; + $type = 'posts'; + $default_key = 't'; + $default_dir = 'd'; + $where_sql .= ($topic_id) ? ' p.topic_id = ' . $topic_id . ' AND' : ''; + + $sql = 'SELECT COUNT(p.post_id) AS total + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . " t + $where_sql " . $db->sql_in_set('p.forum_id', ($forum_id) ? array($forum_id) : array_intersect(get_forum_list('f_read'), get_forum_list('m_approve'))) . ' + AND ' . $db->sql_in_set('p.post_visibility', $visibility_const) .' + AND t.topic_id = p.topic_id + AND t.topic_visibility <> p.post_visibility'; + + if ($min_time) + { + $sql .= ' AND post_time >= ' . $min_time; + } + break; + + case 'unapproved_topics': + case 'deleted_topics': + $visibility_const = ($mode == 'unapproved_topics') ? array(ITEM_UNAPPROVED, ITEM_REAPPROVE) : ITEM_DELETED; + $type = 'topics'; + $default_key = 't'; + $default_dir = 'd'; + + $sql = 'SELECT COUNT(topic_id) AS total + FROM ' . TOPICS_TABLE . " + $where_sql " . $db->sql_in_set('forum_id', ($forum_id) ? array($forum_id) : array_intersect(get_forum_list('f_read'), get_forum_list('m_approve'))) . ' + AND ' . $db->sql_in_set('topic_visibility', $visibility_const); + + if ($min_time) + { + $sql .= ' AND topic_time >= ' . $min_time; + } + break; + + case 'pm_reports': + case 'pm_reports_closed': + case 'reports': + case 'reports_closed': + $pm = (strpos($mode, 'pm_') === 0) ? true : false; + + $type = ($pm) ? 'pm_reports' : 'reports'; + $default_key = 't'; + $default_dir = 'd'; + $limit_time_sql = ($min_time) ? "AND r.report_time >= $min_time" : ''; + + if ($topic_id) + { + $where_sql .= ' p.topic_id = ' . $topic_id . ' AND '; + } + else if ($forum_id) + { + $where_sql .= ' p.forum_id = ' . $forum_id . ' AND '; + } + else if (!$pm) + { + $where_sql .= ' ' . $db->sql_in_set('p.forum_id', get_forum_list(array('!f_read', '!m_report')), true, true) . ' AND '; + } + + if ($mode == 'reports' || $mode == 'pm_reports') + { + $where_sql .= ' r.report_closed = 0 AND '; + } + else + { + $where_sql .= ' r.report_closed = 1 AND '; + } + + if ($pm) + { + $sql = 'SELECT COUNT(r.report_id) AS total + FROM ' . REPORTS_TABLE . ' r, ' . PRIVMSGS_TABLE . " p + $where_sql r.post_id = 0 + AND p.msg_id = r.pm_id + $limit_time_sql"; + } + else + { + $sql = 'SELECT COUNT(r.report_id) AS total + FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . " p + $where_sql r.pm_id = 0 + AND p.post_id = r.post_id + $limit_time_sql"; + } + break; + + case 'viewlogs': + $type = 'logs'; + $default_key = 't'; + $default_dir = 'd'; + + $sql = 'SELECT COUNT(log_id) AS total + FROM ' . LOG_TABLE . " + $where_sql " . $db->sql_in_set('forum_id', ($forum_id) ? array($forum_id) : array_intersect(get_forum_list('f_read'), get_forum_list('m_'))) . ' + AND log_time >= ' . $min_time . ' + AND log_type = ' . LOG_MOD; + break; + } + + $sort_key_val = $request->variable('sk', $default_key); + $sort_dir_val = $request->variable('sd', $default_dir); + $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']); + + switch ($type) + { + case 'topics': + $limit_days = array(0 => $user->lang['ALL_TOPICS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 'tt' => $user->lang['TOPIC_TIME'], 'r' => $user->lang['REPLIES'], 's' => $user->lang['SUBJECT'], 'v' => $user->lang['VIEWS']); + + $sort_by_sql_ary = array('a' => 't.topic_first_poster_name', 't' => array('t.topic_last_post_time', 't.topic_last_post_id'), 'tt' => 't.topic_time', 'r' => (($auth->acl_get('m_approve', $forum_id)) ? 't.topic_posts_approved + t.topic_posts_unapproved + t.topic_posts_softdeleted' : 't.topic_posts_approved'), 's' => 't.topic_title', 'v' => 't.topic_views'); + $limit_time_sql = ($min_time) ? "AND t.topic_last_post_time >= $min_time" : ''; + break; + + case 'posts': + $limit_days = array(0 => $user->lang['ALL_POSTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 's' => $user->lang['SUBJECT']); + $sort_by_sql_ary = array('a' => 'u.username_clean', 't' => array('p.post_time', 'p.post_id'), 's' => 'p.post_subject'); + $limit_time_sql = ($min_time) ? "AND p.post_time >= $min_time" : ''; + break; + + case 'reports': + $limit_days = array(0 => $user->lang['ALL_REPORTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('a' => $user->lang['AUTHOR'], 'r' => $user->lang['REPORTER'], 'p' => $user->lang['POST_TIME'], 't' => $user->lang['REPORT_TIME'], 's' => $user->lang['SUBJECT']); + $sort_by_sql_ary = array('a' => 'u.username_clean', 'r' => 'ru.username', 'p' => array('p.post_time', 'p.post_id'), 't' => 'r.report_time', 's' => 'p.post_subject'); + break; + + case 'pm_reports': + $limit_days = array(0 => $user->lang['ALL_REPORTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('a' => $user->lang['AUTHOR'], 'r' => $user->lang['REPORTER'], 'p' => $user->lang['POST_TIME'], 't' => $user->lang['REPORT_TIME'], 's' => $user->lang['SUBJECT']); + $sort_by_sql_ary = array('a' => 'u.username_clean', 'r' => 'ru.username', 'p' => 'p.message_time', 't' => 'r.report_time', 's' => 'p.message_subject'); + break; + + case 'logs': + $limit_days = array(0 => $user->lang['ALL_ENTRIES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('u' => $user->lang['SORT_USERNAME'], 't' => $user->lang['SORT_DATE'], 'i' => $user->lang['SORT_IP'], 'o' => $user->lang['SORT_ACTION']); + + $sort_by_sql_ary = array('u' => 'u.username_clean', 't' => 'l.log_time', 'i' => 'l.log_ip', 'o' => 'l.log_operation'); + $limit_time_sql = ($min_time) ? "AND l.log_time >= $min_time" : ''; + break; + } + + // Default total to -1 to allow editing by the event + $total_val = -1; + + $sort_by_sql = $sort_by_sql_ary; + $sort_days = $sort_days_val; + $sort_dir = $sort_dir_val; + $sort_key = $sort_key_val; + $total = $total_val; + /** + * This event allows you to control the SQL query used to get the total number + * of reports the user can access. + * + * This total is used for the pagination and for displaying the total number + * of reports to the user + * + * + * @event core.mcp_sorting_query_before + * @var string sql The current SQL search string + * @var string mode An id related to the module(s) the user is viewing + * @var string type Which kind of information is this being used for displaying. Posts, topics, etc... + * @var int forum_id The forum id of the posts the user is trying to access, if not 0 + * @var int topic_id The topic id of the posts the user is trying to access, if not 0 + * @var int sort_days The max age of the oldest report to be shown, in days + * @var string sort_key The way the user has decided to sort the data. + * The valid values must be in the keys of the sort_by_* variables + * @var string sort_dir Either 'd' for "DESC" or 'a' for 'ASC' in the SQL query + * @var int limit_days The possible max ages of the oldest report for the user to choose, in days. + * @var array sort_by_sql SQL text (values) for the possible names of the ways of sorting data (keys). + * @var array sort_by_text Language text (values) for the possible names of the ways of sorting data (keys). + * @var int min_time Integer with the minimum post time that the user is searching for + * @var int limit_time_sql Time limiting options used in the SQL query. + * @var int total The total number of reports that exist. Only set if you want to override the result + * @var string where_sql Extra information included in the WHERE clause. It must end with "WHERE" or "AND" or "OR". + * Set to "WHERE" and set total above -1 to override the total value + * @since 3.1.4-RC1 + */ + $vars = array( + 'sql', + 'mode', + 'type', + 'forum_id', + 'topic_id', + 'sort_days', + 'sort_key', + 'sort_dir', + 'limit_days', + 'sort_by_sql', + 'sort_by_text', + 'min_time', + 'limit_time_sql', + 'total', + 'where_sql', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_sorting_query_before', compact($vars))); + $sort_by_sql_ary = $sort_by_sql; + $sort_days_val = $sort_days; + $sort_key_val = $sort_key; + $sort_dir_val = $sort_dir; + $total_val = $total; + unset($sort_by_sql); + unset($sort_days); + unset($sort_key); + unset($sort_dir); + unset($total); + + if (!isset($sort_by_sql_ary[$sort_key_val])) + { + $sort_key_val = $default_key; + } + + $direction = ($sort_dir_val == 'd') ? 'DESC' : 'ASC'; + + if (is_array($sort_by_sql_ary[$sort_key_val])) + { + $sort_order_sql = implode(' ' . $direction . ', ', $sort_by_sql_ary[$sort_key_val]) . ' ' . $direction; + } + else + { + $sort_order_sql = $sort_by_sql_ary[$sort_key_val] . ' ' . $direction; + } + + $s_limit_days = $s_sort_key = $s_sort_dir = $sort_url = ''; + gen_sort_selects($limit_days, $sort_by_text, $sort_days_val, $sort_key_val, $sort_dir_val, $s_limit_days, $s_sort_key, $s_sort_dir, $sort_url); + + $template->assign_vars(array( + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DAYS' => $s_limit_days) + ); + + if (($sort_days_val && $mode != 'viewlogs') || in_array($mode, array('reports', 'unapproved_topics', 'unapproved_posts', 'deleted_topics', 'deleted_posts')) || $where_sql != 'WHERE') + { + $result = $db->sql_query($sql); + $total_val = (int) $db->sql_fetchfield('total'); + $db->sql_freeresult($result); + } + else if ($total_val < -1) + { + $total_val = -1; + } +} + +/** +* Validate ids +* +* @param array &$ids The relevant ids to check +* @param string $table The table to find the ids in +* @param string $sql_id The ids relevant column name +* @param array $acl_list A list of permissions the user need to have +* @param mixed $singe_forum Limit to one forum id (int) or the first forum found (true) +* +* @return mixed False if no ids were able to be retrieved, true if at least one id left. +* Additionally, this value can be the forum_id assigned if $single_forum was set. +* Therefore checking the result for with !== false is the best method. +*/ +function phpbb_check_ids(&$ids, $table, $sql_id, $acl_list = false, $single_forum = false) +{ + global $db, $auth; + + if (!is_array($ids) || empty($ids)) + { + return false; + } + + $sql = "SELECT $sql_id, forum_id FROM $table + WHERE " . $db->sql_in_set($sql_id, $ids); + $result = $db->sql_query($sql); + + $ids = array(); + $forum_id = false; + + while ($row = $db->sql_fetchrow($result)) + { + if ($acl_list && $row['forum_id'] && !$auth->acl_gets($acl_list, $row['forum_id'])) + { + continue; + } + + if ($acl_list && !$row['forum_id'] && !$auth->acl_getf_global($acl_list)) + { + continue; + } + + // Limit forum? If not, just assign the id. + if ($single_forum === false) + { + $ids[] = $row[$sql_id]; + continue; + } + + // Limit forum to a specific forum id? + // This can get really tricky, because we do not want to create a failure on global topics. :) + if ($row['forum_id']) + { + if ($single_forum !== true && $row['forum_id'] == (int) $single_forum) + { + $forum_id = (int) $single_forum; + } + else if ($forum_id === false) + { + $forum_id = $row['forum_id']; + } + + if ($row['forum_id'] == $forum_id) + { + $ids[] = $row[$sql_id]; + } + } + else + { + // Always add a global topic + $ids[] = $row[$sql_id]; + } + } + $db->sql_freeresult($result); + + if (!count($ids)) + { + return false; + } + + // If forum id is false and ids populated we may have only global announcements selected (returning 0 because of (int) $forum_id) + + return ($single_forum === false) ? true : (int) $forum_id; +} diff --git a/includes/functions_messenger.php b/includes/functions_messenger.php new file mode 100644 index 0000000..4f0d400 --- /dev/null +++ b/includes/functions_messenger.php @@ -0,0 +1,1915 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Messenger +*/ +class messenger +{ + var $msg, $replyto, $from, $subject; + var $addresses = array(); + var $extra_headers = array(); + + var $mail_priority = MAIL_NORMAL_PRIORITY; + var $use_queue = true; + + /** @var \phpbb\template\template */ + protected $template; + + /** + * Constructor + */ + function __construct($use_queue = true) + { + global $config; + + $this->use_queue = (!$config['email_package_size']) ? false : $use_queue; + $this->subject = ''; + } + + /** + * Resets all the data (address, template file, etc etc) to default + */ + function reset() + { + $this->addresses = $this->extra_headers = array(); + $this->msg = $this->replyto = $this->from = ''; + $this->mail_priority = MAIL_NORMAL_PRIORITY; + } + + /** + * Set addresses for to/im as available + * + * @param array $user User row + */ + function set_addresses($user) + { + if (isset($user['user_email']) && $user['user_email']) + { + $this->to($user['user_email'], (isset($user['username']) ? $user['username'] : '')); + } + + if (isset($user['user_jabber']) && $user['user_jabber']) + { + $this->im($user['user_jabber'], (isset($user['username']) ? $user['username'] : '')); + } + } + + /** + * Sets an email address to send to + */ + function to($address, $realname = '') + { + global $config; + + if (!trim($address)) + { + return; + } + + $pos = isset($this->addresses['to']) ? count($this->addresses['to']) : 0; + + $this->addresses['to'][$pos]['email'] = trim($address); + + // If empty sendmail_path on windows, PHP changes the to line + if (!$config['smtp_delivery'] && DIRECTORY_SEPARATOR == '\\') + { + $this->addresses['to'][$pos]['name'] = ''; + } + else + { + $this->addresses['to'][$pos]['name'] = trim($realname); + } + } + + /** + * Sets an cc address to send to + */ + function cc($address, $realname = '') + { + if (!trim($address)) + { + return; + } + + $pos = isset($this->addresses['cc']) ? count($this->addresses['cc']) : 0; + $this->addresses['cc'][$pos]['email'] = trim($address); + $this->addresses['cc'][$pos]['name'] = trim($realname); + } + + /** + * Sets an bcc address to send to + */ + function bcc($address, $realname = '') + { + if (!trim($address)) + { + return; + } + + $pos = isset($this->addresses['bcc']) ? count($this->addresses['bcc']) : 0; + $this->addresses['bcc'][$pos]['email'] = trim($address); + $this->addresses['bcc'][$pos]['name'] = trim($realname); + } + + /** + * Sets a im contact to send to + */ + function im($address, $realname = '') + { + // IM-Addresses could be empty + if (!trim($address)) + { + return; + } + + $pos = isset($this->addresses['im']) ? count($this->addresses['im']) : 0; + $this->addresses['im'][$pos]['uid'] = trim($address); + $this->addresses['im'][$pos]['name'] = trim($realname); + } + + /** + * Set the reply to address + */ + function replyto($address) + { + $this->replyto = trim($address); + } + + /** + * Set the from address + */ + function from($address) + { + $this->from = trim($address); + } + + /** + * set up subject for mail + */ + function subject($subject = '') + { + $this->subject = trim($subject); + } + + /** + * set up extra mail headers + */ + function headers($headers) + { + $this->extra_headers[] = trim($headers); + } + + /** + * Adds X-AntiAbuse headers + * + * @param array $config Configuration array + * @param user $user A user object + * + * @return null + */ + function anti_abuse_headers($config, $user) + { + $this->headers('X-AntiAbuse: Board servername - ' . mail_encode($config['server_name'])); + $this->headers('X-AntiAbuse: User_id - ' . $user->data['user_id']); + $this->headers('X-AntiAbuse: Username - ' . mail_encode($user->data['username'])); + $this->headers('X-AntiAbuse: User IP - ' . $user->ip); + } + + /** + * Set the email priority + */ + function set_mail_priority($priority = MAIL_NORMAL_PRIORITY) + { + $this->mail_priority = $priority; + } + + /** + * Set email template to use + */ + function template($template_file, $template_lang = '', $template_path = '', $template_dir_prefix = '') + { + global $config, $phpbb_root_path, $user; + + $template_dir_prefix = (!$template_dir_prefix || $template_dir_prefix[0] === '/') ? $template_dir_prefix : '/' . $template_dir_prefix; + + $this->setup_template(); + + if (!trim($template_file)) + { + trigger_error('No template file for emailing set.', E_USER_ERROR); + } + + if (!trim($template_lang)) + { + // fall back to board default language if the user's language is + // missing $template_file. If this does not exist either, + // $this->template->set_filenames will do a trigger_error + $template_lang = basename($config['default_lang']); + } + + $ext_template_paths = array( + array( + 'name' => $template_lang . '_email', + 'ext_path' => 'language/' . $template_lang . '/email' . $template_dir_prefix, + ), + ); + + if ($template_path) + { + $template_paths = array( + $template_path . $template_dir_prefix, + ); + } + else + { + $template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/'; + $template_path .= $template_lang . '/email'; + + $template_paths = array( + $template_path . $template_dir_prefix, + ); + + $board_language = basename($config['default_lang']); + + // we can only specify default language fallback when the path is not a custom one for which we + // do not know the default language alternative + if ($template_lang !== $board_language) + { + $fallback_template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/'; + $fallback_template_path .= $board_language . '/email'; + + $template_paths[] = $fallback_template_path . $template_dir_prefix; + + $ext_template_paths[] = array( + 'name' => $board_language . '_email', + 'ext_path' => 'language/' . $board_language . '/email' . $template_dir_prefix, + ); + } + // If everything fails just fall back to en template + if ($template_lang !== 'en' && $board_language !== 'en') + { + $fallback_template_path = (!empty($user->lang_path)) ? $user->lang_path : $phpbb_root_path . 'language/'; + $fallback_template_path .= 'en/email'; + + $template_paths[] = $fallback_template_path . $template_dir_prefix; + + $ext_template_paths[] = array( + 'name' => 'en_email', + 'ext_path' => 'language/en/email' . $template_dir_prefix, + ); + } + } + + $this->set_template_paths($ext_template_paths, $template_paths); + + $this->template->set_filenames(array( + 'body' => $template_file . '.txt', + )); + + return true; + } + + /** + * assign variables to email template + */ + function assign_vars($vars) + { + $this->setup_template(); + + $this->template->assign_vars($vars); + } + + function assign_block_vars($blockname, $vars) + { + $this->setup_template(); + + $this->template->assign_block_vars($blockname, $vars); + } + + /** + * Send the mail out to the recipients set previously in var $this->addresses + * + * @param int $method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH + * @param bool $break Flag indicating if the function only formats the subject + * and the message without sending it + * + * @return bool + */ + function send($method = NOTIFY_EMAIL, $break = false) + { + global $config, $user, $phpbb_dispatcher; + + // We add some standard variables we always use, no need to specify them always + $this->assign_vars(array( + 'U_BOARD' => generate_board_url(), + 'EMAIL_SIG' => str_replace('
', "\n", "-- \n" . htmlspecialchars_decode($config['board_email_sig'])), + 'SITENAME' => htmlspecialchars_decode($config['sitename']), + )); + + $subject = $this->subject; + $template = $this->template; + /** + * Event to modify the template before parsing + * + * @event core.modify_notification_template + * @var int method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH + * @var bool break Flag indicating if the function only formats the subject + * and the message without sending it + * @var string subject The message subject + * @var \phpbb\template\template template The (readonly) template object + * @since 3.2.4-RC1 + */ + $vars = array('method', 'break', 'subject', 'template'); + extract($phpbb_dispatcher->trigger_event('core.modify_notification_template', compact($vars))); + + // Parse message through template + $message = trim($this->template->assign_display('body')); + + /** + * Event to modify notification message text after parsing + * + * @event core.modify_notification_message + * @var int method User notification method NOTIFY_EMAIL|NOTIFY_IM|NOTIFY_BOTH + * @var bool break Flag indicating if the function only formats the subject + * and the message without sending it + * @var string subject The message subject + * @var string message The message text + * @since 3.1.11-RC1 + */ + $vars = array('method', 'break', 'subject', 'message'); + extract($phpbb_dispatcher->trigger_event('core.modify_notification_message', compact($vars))); + + $this->subject = $subject; + $this->msg = $message; + unset($subject, $message, $template); + + // Because we use \n for newlines in the body message we need to fix line encoding errors for those admins who uploaded email template files in the wrong encoding + $this->msg = str_replace("\r\n", "\n", $this->msg); + + // We now try and pull a subject from the email body ... if it exists, + // do this here because the subject may contain a variable + $drop_header = ''; + $match = array(); + if (preg_match('#^(Subject:(.*?))$#m', $this->msg, $match)) + { + $this->subject = (trim($match[2]) != '') ? trim($match[2]) : (($this->subject != '') ? $this->subject : $user->lang['NO_EMAIL_SUBJECT']); + $drop_header .= '[\r\n]*?' . preg_quote($match[1], '#'); + } + else + { + $this->subject = (($this->subject != '') ? $this->subject : $user->lang['NO_EMAIL_SUBJECT']); + } + + if (preg_match('#^(List-Unsubscribe:(.*?))$#m', $this->msg, $match)) + { + $this->extra_headers[] = $match[1]; + $drop_header .= '[\r\n]*?' . preg_quote($match[1], '#'); + } + + if ($drop_header) + { + $this->msg = trim(preg_replace('#' . $drop_header . '#s', '', $this->msg)); + } + + if ($break) + { + return true; + } + + switch ($method) + { + case NOTIFY_EMAIL: + $result = $this->msg_email(); + break; + + case NOTIFY_IM: + $result = $this->msg_jabber(); + break; + + case NOTIFY_BOTH: + $result = $this->msg_email(); + $this->msg_jabber(); + break; + } + + $this->reset(); + return $result; + } + + /** + * Add error message to log + */ + function error($type, $msg) + { + global $user, $config, $request, $phpbb_log; + + // Session doesn't exist, create it + if (!isset($user->session_id) || $user->session_id === '') + { + $user->session_begin(); + } + + $calling_page = htmlspecialchars_decode($request->server('PHP_SELF')); + + switch ($type) + { + case 'EMAIL': + $message = 'EMAIL/' . (($config['smtp_delivery']) ? 'SMTP' : 'PHP/mail()') . ''; + break; + + default: + $message = "$type"; + break; + } + + $message .= '
' . htmlspecialchars($calling_page) . '

' . $msg . '
'; + $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_ERROR_' . $type, false, array($message)); + } + + /** + * Save to queue + */ + function save_queue() + { + global $config; + + if ($config['email_package_size'] && $this->use_queue && !empty($this->queue)) + { + $this->queue->save(); + return; + } + } + + /** + * Generates a valid message id to be used in emails + * + * @return string message id + */ + function generate_message_id() + { + global $config, $request; + + $domain = ($config['server_name']) ?: $request->server('SERVER_NAME', 'phpbb.generated'); + + return md5(unique_id(time())) . '@' . $domain; + } + + /** + * Return email header + */ + function build_header($to, $cc, $bcc) + { + global $config, $phpbb_dispatcher; + + // We could use keys here, but we won't do this for 3.0.x to retain backwards compatibility + $headers = array(); + + $headers[] = 'From: ' . $this->from; + + if ($cc) + { + $headers[] = 'Cc: ' . $cc; + } + + if ($bcc) + { + $headers[] = 'Bcc: ' . $bcc; + } + + $headers[] = 'Reply-To: ' . $this->replyto; + $headers[] = 'Return-Path: <' . $config['board_email'] . '>'; + $headers[] = 'Sender: <' . $config['board_email'] . '>'; + $headers[] = 'MIME-Version: 1.0'; + $headers[] = 'Message-ID: <' . $this->generate_message_id() . '>'; + $headers[] = 'Date: ' . date('r', time()); + $headers[] = 'Content-Type: text/plain; charset=UTF-8'; // format=flowed + $headers[] = 'Content-Transfer-Encoding: 8bit'; // 7bit + + $headers[] = 'X-Priority: ' . $this->mail_priority; + $headers[] = 'X-MSMail-Priority: ' . (($this->mail_priority == MAIL_LOW_PRIORITY) ? 'Low' : (($this->mail_priority == MAIL_NORMAL_PRIORITY) ? 'Normal' : 'High')); + $headers[] = 'X-Mailer: phpBB3'; + $headers[] = 'X-MimeOLE: phpBB3'; + $headers[] = 'X-phpBB-Origin: phpbb://' . str_replace(array('http://', 'https://'), array('', ''), generate_board_url()); + + /** + * Event to modify email header entries + * + * @event core.modify_email_headers + * @var array headers Array containing email header entries + * @since 3.1.11-RC1 + */ + $vars = array('headers'); + extract($phpbb_dispatcher->trigger_event('core.modify_email_headers', compact($vars))); + + if (count($this->extra_headers)) + { + $headers = array_merge($headers, $this->extra_headers); + } + + return $headers; + } + + /** + * Send out emails + */ + function msg_email() + { + global $config, $phpbb_dispatcher; + + if (empty($config['email_enable'])) + { + return false; + } + + // Addresses to send to? + if (empty($this->addresses) || (empty($this->addresses['to']) && empty($this->addresses['cc']) && empty($this->addresses['bcc']))) + { + // Send was successful. ;) + return true; + } + + $use_queue = false; + if ($config['email_package_size'] && $this->use_queue) + { + if (empty($this->queue)) + { + $this->queue = new queue(); + $this->queue->init('email', $config['email_package_size']); + } + $use_queue = true; + } + + $contact_name = htmlspecialchars_decode($config['board_contact_name']); + $board_contact = (($contact_name !== '') ? '"' . mail_encode($contact_name) . '" ' : '') . '<' . $config['board_contact'] . '>'; + + $break = false; + $addresses = $this->addresses; + $subject = $this->subject; + $msg = $this->msg; + /** + * Event to send message via external transport + * + * @event core.notification_message_email + * @var bool break Flag indicating if the function return after hook + * @var array addresses The message recipients + * @var string subject The message subject + * @var string msg The message text + * @since 3.2.4-RC1 + */ + $vars = array( + 'break', + 'addresses', + 'subject', + 'msg', + ); + extract($phpbb_dispatcher->trigger_event('core.notification_message_email', compact($vars))); + + if ($break) + { + return true; + } + + if (empty($this->replyto)) + { + $this->replyto = $board_contact; + } + + if (empty($this->from)) + { + $this->from = $board_contact; + } + + $encode_eol = ($config['smtp_delivery']) ? "\r\n" : PHP_EOL; + + // Build to, cc and bcc strings + $to = $cc = $bcc = ''; + foreach ($this->addresses as $type => $address_ary) + { + if ($type == 'im') + { + continue; + } + + foreach ($address_ary as $which_ary) + { + ${$type} .= ((${$type} != '') ? ', ' : '') . (($which_ary['name'] != '') ? mail_encode($which_ary['name'], $encode_eol) . ' <' . $which_ary['email'] . '>' : $which_ary['email']); + } + } + + // Build header + $headers = $this->build_header($to, $cc, $bcc); + + // Send message ... + if (!$use_queue) + { + $mail_to = ($to == '') ? 'undisclosed-recipients:;' : $to; + $err_msg = ''; + + if ($config['smtp_delivery']) + { + $result = smtpmail($this->addresses, mail_encode($this->subject), wordwrap(utf8_wordwrap($this->msg), 997, "\n", true), $err_msg, $headers); + } + else + { + $result = phpbb_mail($mail_to, $this->subject, $this->msg, $headers, PHP_EOL, $err_msg); + } + + if (!$result) + { + $this->error('EMAIL', $err_msg); + return false; + } + } + else + { + $this->queue->put('email', array( + 'to' => $to, + 'addresses' => $this->addresses, + 'subject' => $this->subject, + 'msg' => $this->msg, + 'headers' => $headers) + ); + } + + return true; + } + + /** + * Send jabber message out + */ + function msg_jabber() + { + global $config, $user, $phpbb_root_path, $phpEx; + + if (empty($config['jab_enable']) || empty($config['jab_host']) || empty($config['jab_username']) || empty($config['jab_password'])) + { + return false; + } + + if (empty($this->addresses['im'])) + { + // Send was successful. ;) + return true; + } + + $use_queue = false; + if ($config['jab_package_size'] && $this->use_queue) + { + if (empty($this->queue)) + { + $this->queue = new queue(); + $this->queue->init('jabber', $config['jab_package_size']); + } + $use_queue = true; + } + + $addresses = array(); + foreach ($this->addresses['im'] as $type => $uid_ary) + { + $addresses[] = $uid_ary['uid']; + } + $addresses = array_unique($addresses); + + if (!$use_queue) + { + include_once($phpbb_root_path . 'includes/functions_jabber.' . $phpEx); + $this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], htmlspecialchars_decode($config['jab_password']), $config['jab_use_ssl'], $config['jab_verify_peer'], $config['jab_verify_peer_name'], $config['jab_allow_self_signed']); + + if (!$this->jabber->connect()) + { + $this->error('JABBER', $user->lang['ERR_JAB_CONNECT'] . '
' . $this->jabber->get_log()); + return false; + } + + if (!$this->jabber->login()) + { + $this->error('JABBER', $user->lang['ERR_JAB_AUTH'] . '
' . $this->jabber->get_log()); + return false; + } + + foreach ($addresses as $address) + { + $this->jabber->send_message($address, $this->msg, $this->subject); + } + + $this->jabber->disconnect(); + } + else + { + $this->queue->put('jabber', array( + 'addresses' => $addresses, + 'subject' => $this->subject, + 'msg' => $this->msg) + ); + } + unset($addresses); + return true; + } + + /** + * Setup template engine + */ + protected function setup_template() + { + global $phpbb_container, $phpbb_dispatcher; + + if ($this->template instanceof \phpbb\template\template) + { + return; + } + + $template_environment = new \phpbb\template\twig\environment( + $phpbb_container->get('config'), + $phpbb_container->get('filesystem'), + $phpbb_container->get('path_helper'), + $phpbb_container->getParameter('core.template.cache_path'), + $phpbb_container->get('ext.manager'), + new \phpbb\template\twig\loader( + $phpbb_container->get('filesystem') + ), + $phpbb_dispatcher, + array() + ); + $template_environment->setLexer($phpbb_container->get('template.twig.lexer')); + + $this->template = new \phpbb\template\twig\twig( + $phpbb_container->get('path_helper'), + $phpbb_container->get('config'), + new \phpbb\template\context(), + $template_environment, + $phpbb_container->getParameter('core.template.cache_path'), + $phpbb_container->get('user'), + $phpbb_container->get('template.twig.extensions.collection'), + $phpbb_container->get('ext.manager') + ); + } + + /** + * Set template paths to load + */ + protected function set_template_paths($path_name, $paths) + { + $this->setup_template(); + + $this->template->set_custom_style($path_name, $paths); + } +} + +/** +* handling email and jabber queue +*/ +class queue +{ + var $data = array(); + var $queue_data = array(); + var $package_size = 0; + var $cache_file = ''; + var $eol = "\n"; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * constructor + */ + function __construct() + { + global $phpEx, $phpbb_root_path, $phpbb_filesystem, $phpbb_container; + + $this->data = array(); + $this->cache_file = $phpbb_container->getParameter('core.cache_dir') . "queue.$phpEx"; + $this->filesystem = $phpbb_filesystem; + } + + /** + * Init a queue object + */ + function init($object, $package_size) + { + $this->data[$object] = array(); + $this->data[$object]['package_size'] = $package_size; + $this->data[$object]['data'] = array(); + } + + /** + * Put object in queue + */ + function put($object, $scope) + { + $this->data[$object]['data'][] = $scope; + } + + /** + * Process queue + * Using lock file + */ + function process() + { + global $config, $phpEx, $phpbb_root_path, $user, $phpbb_dispatcher; + + $lock = new \phpbb\lock\flock($this->cache_file); + $lock->acquire(); + + // avoid races, check file existence once + $have_cache_file = file_exists($this->cache_file); + if (!$have_cache_file || $config['last_queue_run'] > time() - $config['queue_interval']) + { + if (!$have_cache_file) + { + $config->set('last_queue_run', time(), false); + } + + $lock->release(); + return; + } + + $config->set('last_queue_run', time(), false); + + include($this->cache_file); + + foreach ($this->queue_data as $object => $data_ary) + { + @set_time_limit(0); + + if (!isset($data_ary['package_size'])) + { + $data_ary['package_size'] = 0; + } + + $package_size = $data_ary['package_size']; + $num_items = (!$package_size || count($data_ary['data']) < $package_size) ? count($data_ary['data']) : $package_size; + + /* + * This code is commented out because it causes problems on some web hosts. + * The core problem is rather restrictive email sending limits. + * This code is nly useful if you have no such restrictions from the + * web host and the package size setting is wrong. + + // If the amount of emails to be sent is way more than package_size than we need to increase it to prevent backlogs... + if (count($data_ary['data']) > $package_size * 2.5) + { + $num_items = count($data_ary['data']); + } + */ + + switch ($object) + { + case 'email': + // Delete the email queued objects if mailing is disabled + if (!$config['email_enable']) + { + unset($this->queue_data['email']); + continue 2; + } + break; + + case 'jabber': + if (!$config['jab_enable']) + { + unset($this->queue_data['jabber']); + continue 2; + } + + include_once($phpbb_root_path . 'includes/functions_jabber.' . $phpEx); + $this->jabber = new jabber($config['jab_host'], $config['jab_port'], $config['jab_username'], htmlspecialchars_decode($config['jab_password']), $config['jab_use_ssl'], $config['jab_verify_peer'], $config['jab_verify_peer_name'], $config['jab_allow_self_signed']); + + if (!$this->jabber->connect()) + { + $messenger = new messenger(); + $messenger->error('JABBER', $user->lang['ERR_JAB_CONNECT']); + continue 2; + } + + if (!$this->jabber->login()) + { + $messenger = new messenger(); + $messenger->error('JABBER', $user->lang['ERR_JAB_AUTH']); + continue 2; + } + + break; + + default: + $lock->release(); + return; + } + + for ($i = 0; $i < $num_items; $i++) + { + // Make variables available... + extract(array_shift($this->queue_data[$object]['data'])); + + switch ($object) + { + case 'email': + $break = false; + /** + * Event to send message via external transport + * + * @event core.notification_message_process + * @var bool break Flag indicating if the function return after hook + * @var array addresses The message recipients + * @var string subject The message subject + * @var string msg The message text + * @since 3.2.4-RC1 + */ + $vars = array( + 'break', + 'addresses', + 'subject', + 'msg', + ); + extract($phpbb_dispatcher->trigger_event('core.notification_message_process', compact($vars))); + + if (!$break) + { + $err_msg = ''; + $to = (!$to) ? 'undisclosed-recipients:;' : $to; + + if ($config['smtp_delivery']) + { + $result = smtpmail($addresses, mail_encode($subject), wordwrap(utf8_wordwrap($msg), 997, "\n", true), $err_msg, $headers); + } + else + { + $result = phpbb_mail($to, $subject, $msg, $headers, PHP_EOL, $err_msg); + } + + if (!$result) + { + $messenger = new messenger(); + $messenger->error('EMAIL', $err_msg); + continue 2; + } + } + break; + + case 'jabber': + foreach ($addresses as $address) + { + if ($this->jabber->send_message($address, $msg, $subject) === false) + { + $messenger = new messenger(); + $messenger->error('JABBER', $this->jabber->get_log()); + continue 3; + } + } + break; + } + } + + // No more data for this object? Unset it + if (!count($this->queue_data[$object]['data'])) + { + unset($this->queue_data[$object]); + } + + // Post-object processing + switch ($object) + { + case 'jabber': + // Hang about a couple of secs to ensure the messages are + // handled, then disconnect + $this->jabber->disconnect(); + break; + } + } + + if (!count($this->queue_data)) + { + @unlink($this->cache_file); + } + else + { + if ($fp = @fopen($this->cache_file, 'wb')) + { + fwrite($fp, "queue_data = unserialize(" . var_export(serialize($this->queue_data), true) . ");\n\n?>"); + fclose($fp); + + if (function_exists('opcache_invalidate')) + { + @opcache_invalidate($this->cache_file); + } + + try + { + $this->filesystem->phpbb_chmod($this->cache_file, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + } + + $lock->release(); + } + + /** + * Save queue + */ + function save() + { + if (!count($this->data)) + { + return; + } + + $lock = new \phpbb\lock\flock($this->cache_file); + $lock->acquire(); + + if (file_exists($this->cache_file)) + { + include($this->cache_file); + + foreach ($this->queue_data as $object => $data_ary) + { + if (isset($this->data[$object]) && count($this->data[$object])) + { + $this->data[$object]['data'] = array_merge($data_ary['data'], $this->data[$object]['data']); + } + else + { + $this->data[$object]['data'] = $data_ary['data']; + } + } + } + + if ($fp = @fopen($this->cache_file, 'w')) + { + fwrite($fp, "queue_data = unserialize(" . var_export(serialize($this->data), true) . ");\n\n?>"); + fclose($fp); + + if (function_exists('opcache_invalidate')) + { + @opcache_invalidate($this->cache_file); + } + + try + { + $this->filesystem->phpbb_chmod($this->cache_file, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + + $this->data = array(); + } + + $lock->release(); + } +} + +/** +* Replacement or substitute for PHP's mail command +*/ +function smtpmail($addresses, $subject, $message, &$err_msg, $headers = false) +{ + global $config, $user; + + // Fix any bare linefeeds in the message to make it RFC821 Compliant. + $message = preg_replace("#(?lang['NO_EMAIL_SUBJECT'])) ? $user->lang['NO_EMAIL_SUBJECT'] : 'No email subject specified'; + return false; + } + + if (trim($message) == '') + { + $err_msg = (isset($user->lang['NO_EMAIL_MESSAGE'])) ? $user->lang['NO_EMAIL_MESSAGE'] : 'Email message was blank'; + return false; + } + + $mail_rcpt = $mail_to = $mail_cc = array(); + + // Build correct addresses for RCPT TO command and the client side display (TO, CC) + if (isset($addresses['to']) && count($addresses['to'])) + { + foreach ($addresses['to'] as $which_ary) + { + $mail_to[] = ($which_ary['name'] != '') ? mail_encode(trim($which_ary['name'])) . ' <' . trim($which_ary['email']) . '>' : '<' . trim($which_ary['email']) . '>'; + $mail_rcpt['to'][] = '<' . trim($which_ary['email']) . '>'; + } + } + + if (isset($addresses['bcc']) && count($addresses['bcc'])) + { + foreach ($addresses['bcc'] as $which_ary) + { + $mail_rcpt['bcc'][] = '<' . trim($which_ary['email']) . '>'; + } + } + + if (isset($addresses['cc']) && count($addresses['cc'])) + { + foreach ($addresses['cc'] as $which_ary) + { + $mail_cc[] = ($which_ary['name'] != '') ? mail_encode(trim($which_ary['name'])) . ' <' . trim($which_ary['email']) . '>' : '<' . trim($which_ary['email']) . '>'; + $mail_rcpt['cc'][] = '<' . trim($which_ary['email']) . '>'; + } + } + + $smtp = new smtp_class(); + + $errno = 0; + $errstr = ''; + + $smtp->add_backtrace('Connecting to ' . $config['smtp_host'] . ':' . $config['smtp_port']); + + // Ok we have error checked as much as we can to this point let's get on it already. + if (!class_exists('\phpbb\error_collector')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/error_collector.' . $phpEx); + } + $collector = new \phpbb\error_collector; + $collector->install(); + + $options = array(); + $verify_peer = (bool) $config['smtp_verify_peer']; + $verify_peer_name = (bool) $config['smtp_verify_peer_name']; + $allow_self_signed = (bool) $config['smtp_allow_self_signed']; + $remote_socket = $config['smtp_host'] . ':' . $config['smtp_port']; + + // Set ssl context options, see http://php.net/manual/en/context.ssl.php + $options['ssl'] = array('verify_peer' => $verify_peer, 'verify_peer_name' => $verify_peer_name, 'allow_self_signed' => $allow_self_signed); + $socket_context = stream_context_create($options); + + $smtp->socket = @stream_socket_client($remote_socket, $errno, $errstr, 20, STREAM_CLIENT_CONNECT, $socket_context); + $collector->uninstall(); + $error_contents = $collector->format_errors(); + + if (!$smtp->socket) + { + if ($errstr) + { + $errstr = utf8_convert_message($errstr); + } + + $err_msg = (isset($user->lang['NO_CONNECT_TO_SMTP_HOST'])) ? sprintf($user->lang['NO_CONNECT_TO_SMTP_HOST'], $errno, $errstr) : "Could not connect to smtp host : $errno : $errstr"; + $err_msg .= ($error_contents) ? '

' . htmlspecialchars($error_contents) : ''; + return false; + } + + // Wait for reply + if ($err_msg = $smtp->server_parse('220', __LINE__)) + { + $smtp->close_session($err_msg); + return false; + } + + // Let me in. This function handles the complete authentication process + if ($err_msg = $smtp->log_into_server($config['smtp_host'], $config['smtp_username'], htmlspecialchars_decode($config['smtp_password']), $config['smtp_auth_method'])) + { + $smtp->close_session($err_msg); + return false; + } + + // From this point onward most server response codes should be 250 + // Specify who the mail is from.... + $smtp->server_send('MAIL FROM:<' . $config['board_email'] . '>'); + if ($err_msg = $smtp->server_parse('250', __LINE__)) + { + $smtp->close_session($err_msg); + return false; + } + + // Specify each user to send to and build to header. + $to_header = implode(', ', $mail_to); + $cc_header = implode(', ', $mail_cc); + + // Now tell the MTA to send the Message to the following people... [TO, BCC, CC] + $rcpt = false; + foreach ($mail_rcpt as $type => $mail_to_addresses) + { + foreach ($mail_to_addresses as $mail_to_address) + { + // Add an additional bit of error checking to the To field. + if (preg_match('#[^ ]+\@[^ ]+#', $mail_to_address)) + { + $smtp->server_send("RCPT TO:$mail_to_address"); + if ($err_msg = $smtp->server_parse('250', __LINE__)) + { + // We continue... if users are not resolved we do not care + if ($smtp->numeric_response_code != 550) + { + $smtp->close_session($err_msg); + return false; + } + } + else + { + $rcpt = true; + } + } + } + } + + // We try to send messages even if a few people do not seem to have valid email addresses, but if no one has, we have to exit here. + if (!$rcpt) + { + $user->session_begin(); + $err_msg .= '

'; + $err_msg .= (isset($user->lang['INVALID_EMAIL_LOG'])) ? sprintf($user->lang['INVALID_EMAIL_LOG'], htmlspecialchars($mail_to_address)) : '' . htmlspecialchars($mail_to_address) . ' possibly an invalid email address?'; + $smtp->close_session($err_msg); + return false; + } + + // Ok now we tell the server we are ready to start sending data + $smtp->server_send('DATA'); + + // This is the last response code we look for until the end of the message. + if ($err_msg = $smtp->server_parse('354', __LINE__)) + { + $smtp->close_session($err_msg); + return false; + } + + // Send the Subject Line... + $smtp->server_send("Subject: $subject"); + + // Now the To Header. + $to_header = ($to_header == '') ? 'undisclosed-recipients:;' : $to_header; + $smtp->server_send("To: $to_header"); + + // Now the CC Header. + if ($cc_header != '') + { + $smtp->server_send("CC: $cc_header"); + } + + // Now any custom headers.... + if ($headers !== false) + { + $smtp->server_send("$headers\r\n"); + } + + // Ok now we are ready for the message... + $smtp->server_send($message); + + // Ok the all the ingredients are mixed in let's cook this puppy... + $smtp->server_send('.'); + if ($err_msg = $smtp->server_parse('250', __LINE__)) + { + $smtp->close_session($err_msg); + return false; + } + + // Now tell the server we are done and close the socket... + $smtp->server_send('QUIT'); + $smtp->close_session($err_msg); + + return true; +} + +/** +* SMTP Class +* Auth Mechanisms originally taken from the AUTH Modules found within the PHP Extension and Application Repository (PEAR) +* See docs/AUTHORS for more details +*/ +class smtp_class +{ + var $server_response = ''; + var $socket = 0; + protected $socket_tls = false; + var $responses = array(); + var $commands = array(); + var $numeric_response_code = 0; + + var $backtrace = false; + var $backtrace_log = array(); + + function __construct() + { + // Always create a backtrace for admins to identify SMTP problems + $this->backtrace = true; + $this->backtrace_log = array(); + } + + /** + * Add backtrace message for debugging + */ + function add_backtrace($message) + { + if ($this->backtrace) + { + $this->backtrace_log[] = utf8_htmlspecialchars($message); + } + } + + /** + * Send command to smtp server + */ + function server_send($command, $private_info = false) + { + fputs($this->socket, $command . "\r\n"); + + (!$private_info) ? $this->add_backtrace("# $command") : $this->add_backtrace('# Omitting sensitive information'); + + // We could put additional code here + } + + /** + * We use the line to give the support people an indication at which command the error occurred + */ + function server_parse($response, $line) + { + global $user; + + $this->server_response = ''; + $this->responses = array(); + $this->numeric_response_code = 0; + + while (substr($this->server_response, 3, 1) != ' ') + { + if (!($this->server_response = fgets($this->socket, 256))) + { + return (isset($user->lang['NO_EMAIL_RESPONSE_CODE'])) ? $user->lang['NO_EMAIL_RESPONSE_CODE'] : 'Could not get mail server response codes'; + } + $this->responses[] = substr(rtrim($this->server_response), 4); + $this->numeric_response_code = (int) substr($this->server_response, 0, 3); + + $this->add_backtrace("LINE: $line <- {$this->server_response}"); + } + + if (!(substr($this->server_response, 0, 3) == $response)) + { + $this->numeric_response_code = (int) substr($this->server_response, 0, 3); + return (isset($user->lang['EMAIL_SMTP_ERROR_RESPONSE'])) ? sprintf($user->lang['EMAIL_SMTP_ERROR_RESPONSE'], $line, $this->server_response) : "Ran into problems sending Mail at Line $line. Response: $this->server_response"; + } + + return 0; + } + + /** + * Close session + */ + function close_session(&$err_msg) + { + fclose($this->socket); + + if ($this->backtrace) + { + $message = '

Backtrace

' . implode('
', $this->backtrace_log) . '

'; + $err_msg .= $message; + } + } + + /** + * Log into server and get possible auth codes if neccessary + */ + function log_into_server($hostname, $username, $password, $default_auth_method) + { + global $user; + + // Here we try to determine the *real* hostname (reverse DNS entry preferrably) + $local_host = $user->host; + + if (function_exists('php_uname')) + { + $local_host = php_uname('n'); + + // Able to resolve name to IP + if (($addr = @gethostbyname($local_host)) !== $local_host) + { + // Able to resolve IP back to name + if (($name = @gethostbyaddr($addr)) !== $addr) + { + $local_host = $name; + } + } + } + + // If we are authenticating through pop-before-smtp, we + // have to login ones before we get authenticated + // NOTE: on some configurations the time between an update of the auth database takes so + // long that the first email send does not work. This is not a biggie on a live board (only + // the install mail will most likely fail) - but on a dynamic ip connection this might produce + // severe problems and is not fixable! + if ($default_auth_method == 'POP-BEFORE-SMTP' && $username && $password) + { + global $config; + + $errno = 0; + $errstr = ''; + + $this->server_send("QUIT"); + fclose($this->socket); + + $this->pop_before_smtp($hostname, $username, $password); + $username = $password = $default_auth_method = ''; + + // We need to close the previous session, else the server is not + // able to get our ip for matching... + if (!$this->socket = @fsockopen($config['smtp_host'], $config['smtp_port'], $errno, $errstr, 10)) + { + if ($errstr) + { + $errstr = utf8_convert_message($errstr); + } + + $err_msg = (isset($user->lang['NO_CONNECT_TO_SMTP_HOST'])) ? sprintf($user->lang['NO_CONNECT_TO_SMTP_HOST'], $errno, $errstr) : "Could not connect to smtp host : $errno : $errstr"; + return $err_msg; + } + + // Wait for reply + if ($err_msg = $this->server_parse('220', __LINE__)) + { + $this->close_session($err_msg); + return $err_msg; + } + } + + $hello_result = $this->hello($local_host); + if (!is_null($hello_result)) + { + return $hello_result; + } + + // SMTP STARTTLS (RFC 3207) + if (!$this->socket_tls) + { + $this->socket_tls = $this->starttls(); + + if ($this->socket_tls) + { + // Switched to TLS + // RFC 3207: "The client MUST discard any knowledge obtained from the server, [...]" + // So say hello again + $hello_result = $this->hello($local_host); + + if (!is_null($hello_result)) + { + return $hello_result; + } + } + } + + // If we are not authenticated yet, something might be wrong if no username and passwd passed + if (!$username || !$password) + { + return false; + } + + if (!isset($this->commands['AUTH'])) + { + return (isset($user->lang['SMTP_NO_AUTH_SUPPORT'])) ? $user->lang['SMTP_NO_AUTH_SUPPORT'] : 'SMTP server does not support authentication'; + } + + // Get best authentication method + $available_methods = explode(' ', $this->commands['AUTH']); + + // Define the auth ordering if the default auth method was not found + $auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5'); + $method = ''; + + if (in_array($default_auth_method, $available_methods)) + { + $method = $default_auth_method; + } + else + { + foreach ($auth_methods as $_method) + { + if (in_array($_method, $available_methods)) + { + $method = $_method; + break; + } + } + } + + if (!$method) + { + return (isset($user->lang['NO_SUPPORTED_AUTH_METHODS'])) ? $user->lang['NO_SUPPORTED_AUTH_METHODS'] : 'No supported authentication methods'; + } + + $method = strtolower(str_replace('-', '_', $method)); + return $this->$method($username, $password); + } + + /** + * SMTP EHLO/HELO + * + * @return mixed Null if the authentication process is supposed to continue + * False if already authenticated + * Error message (string) otherwise + */ + protected function hello($hostname) + { + // Try EHLO first + $this->server_send("EHLO $hostname"); + if ($err_msg = $this->server_parse('250', __LINE__)) + { + // a 503 response code means that we're already authenticated + if ($this->numeric_response_code == 503) + { + return false; + } + + // If EHLO fails, we try HELO + $this->server_send("HELO $hostname"); + if ($err_msg = $this->server_parse('250', __LINE__)) + { + return ($this->numeric_response_code == 503) ? false : $err_msg; + } + } + + foreach ($this->responses as $response) + { + $response = explode(' ', $response); + $response_code = $response[0]; + unset($response[0]); + $this->commands[$response_code] = implode(' ', $response); + } + } + + /** + * SMTP STARTTLS (RFC 3207) + * + * @return bool Returns true if TLS was started + * Otherwise false + */ + protected function starttls() + { + if (!function_exists('stream_socket_enable_crypto')) + { + return false; + } + + if (!isset($this->commands['STARTTLS'])) + { + return false; + } + + $this->server_send('STARTTLS'); + + if ($err_msg = $this->server_parse('220', __LINE__)) + { + return false; + } + + $result = false; + $stream_meta = stream_get_meta_data($this->socket); + + if (socket_set_blocking($this->socket, 1)) + { + $result = stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + socket_set_blocking($this->socket, (int) $stream_meta['blocked']); + } + + return $result; + } + + /** + * Pop before smtp authentication + */ + function pop_before_smtp($hostname, $username, $password) + { + global $user; + + if (!$this->socket = @fsockopen($hostname, 110, $errno, $errstr, 10)) + { + if ($errstr) + { + $errstr = utf8_convert_message($errstr); + } + + return (isset($user->lang['NO_CONNECT_TO_SMTP_HOST'])) ? sprintf($user->lang['NO_CONNECT_TO_SMTP_HOST'], $errno, $errstr) : "Could not connect to smtp host : $errno : $errstr"; + } + + $this->server_send("USER $username", true); + if ($err_msg = $this->server_parse('+OK', __LINE__)) + { + return $err_msg; + } + + $this->server_send("PASS $password", true); + if ($err_msg = $this->server_parse('+OK', __LINE__)) + { + return $err_msg; + } + + $this->server_send('QUIT'); + fclose($this->socket); + + return false; + } + + /** + * Plain authentication method + */ + function plain($username, $password) + { + $this->server_send('AUTH PLAIN'); + if ($err_msg = $this->server_parse('334', __LINE__)) + { + return ($this->numeric_response_code == 503) ? false : $err_msg; + } + + $base64_method_plain = base64_encode("\0" . $username . "\0" . $password); + $this->server_send($base64_method_plain, true); + if ($err_msg = $this->server_parse('235', __LINE__)) + { + return $err_msg; + } + + return false; + } + + /** + * Login authentication method + */ + function login($username, $password) + { + $this->server_send('AUTH LOGIN'); + if ($err_msg = $this->server_parse('334', __LINE__)) + { + return ($this->numeric_response_code == 503) ? false : $err_msg; + } + + $this->server_send(base64_encode($username), true); + if ($err_msg = $this->server_parse('334', __LINE__)) + { + return $err_msg; + } + + $this->server_send(base64_encode($password), true); + if ($err_msg = $this->server_parse('235', __LINE__)) + { + return $err_msg; + } + + return false; + } + + /** + * cram_md5 authentication method + */ + function cram_md5($username, $password) + { + $this->server_send('AUTH CRAM-MD5'); + if ($err_msg = $this->server_parse('334', __LINE__)) + { + return ($this->numeric_response_code == 503) ? false : $err_msg; + } + + $md5_challenge = base64_decode($this->responses[0]); + $password = (strlen($password) > 64) ? pack('H32', md5($password)) : ((strlen($password) < 64) ? str_pad($password, 64, chr(0)) : $password); + $md5_digest = md5((substr($password, 0, 64) ^ str_repeat(chr(0x5C), 64)) . (pack('H32', md5((substr($password, 0, 64) ^ str_repeat(chr(0x36), 64)) . $md5_challenge)))); + + $base64_method_cram_md5 = base64_encode($username . ' ' . $md5_digest); + + $this->server_send($base64_method_cram_md5, true); + if ($err_msg = $this->server_parse('235', __LINE__)) + { + return $err_msg; + } + + return false; + } + + /** + * digest_md5 authentication method + * A real pain in the *** + */ + function digest_md5($username, $password) + { + global $config, $user; + + $this->server_send('AUTH DIGEST-MD5'); + if ($err_msg = $this->server_parse('334', __LINE__)) + { + return ($this->numeric_response_code == 503) ? false : $err_msg; + } + + $md5_challenge = base64_decode($this->responses[0]); + + // Parse the md5 challenge - from AUTH_SASL (PEAR) + $tokens = array(); + while (preg_match('/^([a-z-]+)=("[^"]+(?host; + } + + // Maxbuf + if (empty($tokens['maxbuf'])) + { + $tokens['maxbuf'] = 65536; + } + + // Required: nonce, algorithm + if (empty($tokens['nonce']) || empty($tokens['algorithm'])) + { + $tokens = array(); + } + $md5_challenge = $tokens; + + if (!empty($md5_challenge)) + { + $str = ''; + for ($i = 0; $i < 32; $i++) + { + $str .= chr(mt_rand(0, 255)); + } + $cnonce = base64_encode($str); + + $digest_uri = 'smtp/' . $config['smtp_host']; + + $auth_1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $username, $md5_challenge['realm'], $password))), $md5_challenge['nonce'], $cnonce); + $auth_2 = 'AUTHENTICATE:' . $digest_uri; + $response_value = md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($auth_1), $md5_challenge['nonce'], $cnonce, md5($auth_2))); + + $input_string = sprintf('username="%s",realm="%s",nonce="%s",cnonce="%s",nc="00000001",qop=auth,digest-uri="%s",response=%s,%d', $username, $md5_challenge['realm'], $md5_challenge['nonce'], $cnonce, $digest_uri, $response_value, $md5_challenge['maxbuf']); + } + else + { + return (isset($user->lang['INVALID_DIGEST_CHALLENGE'])) ? $user->lang['INVALID_DIGEST_CHALLENGE'] : 'Invalid digest challenge'; + } + + $base64_method_digest_md5 = base64_encode($input_string); + $this->server_send($base64_method_digest_md5, true); + if ($err_msg = $this->server_parse('334', __LINE__)) + { + return $err_msg; + } + + $this->server_send(' '); + if ($err_msg = $this->server_parse('235', __LINE__)) + { + return $err_msg; + } + + return false; + } +} + +/** +* Encodes the given string for proper display in UTF-8. +* +* This version is using base64 encoded data. The downside of this +* is if the mail client does not understand this encoding the user +* is basically doomed with an unreadable subject. +* +* Please note that this version fully supports RFC 2045 section 6.8. +* +* @param string $eol End of line we are using (optional to be backwards compatible) +*/ +function mail_encode($str, $eol = "\r\n") +{ + // define start delimimter, end delimiter and spacer + $start = "=?UTF-8?B?"; + $end = "?="; + $delimiter = "$eol "; + + // Maximum length is 75. $split_length *must* be a multiple of 4, but <= 75 - strlen($start . $delimiter . $end)!!! + $split_length = 60; + $encoded_str = base64_encode($str); + + // If encoded string meets the limits, we just return with the correct data. + if (strlen($encoded_str) <= $split_length) + { + return $start . $encoded_str . $end; + } + + // If there is only ASCII data, we just return what we want, correctly splitting the lines. + if (strlen($str) === utf8_strlen($str)) + { + return $start . implode($end . $delimiter . $start, str_split($encoded_str, $split_length)) . $end; + } + + // UTF-8 data, compose encoded lines + $array = utf8_str_split($str); + $str = ''; + + while (count($array)) + { + $text = ''; + + while (count($array) && intval((strlen($text . $array[0]) + 2) / 3) << 2 <= $split_length) + { + $text .= array_shift($array); + } + + $str .= $start . base64_encode($text) . $end . $delimiter; + } + + return substr($str, 0, -strlen($delimiter)); +} + +/** +* Wrapper for sending out emails with the PHP's mail function +*/ +function phpbb_mail($to, $subject, $msg, $headers, $eol, &$err_msg) +{ + global $config, $phpbb_root_path, $phpEx; + + // We use the EOL character for the OS here because the PHP mail function does not correctly transform line endings. On Windows SMTP is used (SMTP is \r\n), on UNIX a command is used... + // Reference: http://bugs.php.net/bug.php?id=15841 + $headers = implode($eol, $headers); + + if (!class_exists('\phpbb\error_collector')) + { + include($phpbb_root_path . 'includes/error_collector.' . $phpEx); + } + + $collector = new \phpbb\error_collector; + $collector->install(); + + // On some PHP Versions mail() *may* fail if there are newlines within the subject. + // Newlines are used as a delimiter for lines in mail_encode() according to RFC 2045 section 6.8. + // Because PHP can't decide what is wanted we revert back to the non-RFC-compliant way of separating by one space (Use '' as parameter to mail_encode() results in SPACE used) + $additional_parameters = $config['email_force_sender'] ? '-f' . $config['board_email'] : ''; + $result = mail($to, mail_encode($subject, ''), wordwrap(utf8_wordwrap($msg), 997, "\n", true), $headers, $additional_parameters); + + $collector->uninstall(); + $err_msg = $collector->format_errors(); + + return $result; +} diff --git a/includes/functions_module.php b/includes/functions_module.php new file mode 100644 index 0000000..88dafc4 --- /dev/null +++ b/includes/functions_module.php @@ -0,0 +1,1145 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Class handling all types of 'plugins' (a future term) +*/ +class p_master +{ + var $p_id; + var $p_class; + var $p_name; + var $p_mode; + var $p_parent; + + var $include_path = false; + var $active_module = false; + var $active_module_row_id = false; + var $acl_forum_id = false; + var $module_ary = array(); + + /** + * Constuctor + * Set module include path + */ + function __construct($include_path = false) + { + global $phpbb_root_path; + + $this->include_path = ($include_path !== false) ? $include_path : $phpbb_root_path . 'includes/'; + + // Make sure the path ends with / + if (substr($this->include_path, -1) !== '/') + { + $this->include_path .= '/'; + } + } + + /** + * Set custom include path for modules + * Schema for inclusion is include_path . modulebase + * + * @param string $include_path include path to be used. + * @access public + */ + function set_custom_include_path($include_path) + { + $this->include_path = $include_path; + + // Make sure the path ends with / + if (substr($this->include_path, -1) !== '/') + { + $this->include_path .= '/'; + } + } + + /** + * List modules + * + * This creates a list, stored in $this->module_ary of all available + * modules for the given class (ucp, mcp and acp). Additionally + * $this->module_y_ary is created with indentation information for + * displaying the module list appropriately. Only modules for which + * the user has access rights are included in these lists. + */ + function list_modules($p_class) + { + global $db, $user, $cache; + global $phpbb_dispatcher; + + // Sanitise for future path use, it's escaped as appropriate for queries + $this->p_class = str_replace(array('.', '/', '\\'), '', basename($p_class)); + + // Get cached modules + if (($this->module_cache = $cache->get('_modules_' . $this->p_class)) === false) + { + // Get modules + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_class = '" . $db->sql_escape($this->p_class) . "' + ORDER BY left_id ASC"; + $result = $db->sql_query($sql); + + $rows = array(); + while ($row = $db->sql_fetchrow($result)) + { + $rows[$row['module_id']] = $row; + } + $db->sql_freeresult($result); + + $this->module_cache = array(); + foreach ($rows as $module_id => $row) + { + $this->module_cache['modules'][] = $row; + $this->module_cache['parents'][$row['module_id']] = $this->get_parents($row['parent_id'], $row['left_id'], $row['right_id'], $rows); + } + unset($rows); + + $cache->put('_modules_' . $this->p_class, $this->module_cache); + } + + if (empty($this->module_cache)) + { + $this->module_cache = array('modules' => array(), 'parents' => array()); + } + + // We "could" build a true tree with this function - maybe mod authors want to use this... + // Functions for traversing and manipulating the tree are not available though + // We might re-structure the module system to use true trees in 3.2.x... + // $tree = $this->build_tree($this->module_cache['modules'], $this->module_cache['parents']); + + // Clean up module cache array to only let survive modules the user can access + $right_id = false; + + $hide_categories = array(); + foreach ($this->module_cache['modules'] as $key => $row) + { + // When the module has no mode (category) we check whether it has visible children + // before listing it as well. + if (!$row['module_mode']) + { + $hide_categories[(int) $row['module_id']] = $key; + } + + // Not allowed to view module? + if (!$this->module_auth_self($row['module_auth'])) + { + unset($this->module_cache['modules'][$key]); + continue; + } + + // Category with no members, ignore + if (!$row['module_basename'] && ($row['left_id'] + 1 == $row['right_id'])) + { + unset($this->module_cache['modules'][$key]); + continue; + } + + // Skip branch + if ($right_id !== false) + { + if ($row['left_id'] < $right_id) + { + unset($this->module_cache['modules'][$key]); + continue; + } + + $right_id = false; + } + + // Not enabled? + if (!$row['module_enabled']) + { + // If category is disabled then disable every child too + unset($this->module_cache['modules'][$key]); + $right_id = $row['right_id']; + continue; + } + + if ($row['module_mode']) + { + // The parent category has a visible child + // So remove it and all its parents from the hide array + unset($hide_categories[(int) $row['parent_id']]); + foreach ($this->module_cache['parents'][$row['module_id']] as $module_id => $row_id) + { + unset($hide_categories[$module_id]); + } + } + } + + foreach ($hide_categories as $module_id => $row_id) + { + unset($this->module_cache['modules'][$row_id]); + } + + // Re-index (this is needed, else we are not able to array_slice later) + $this->module_cache['modules'] = array_merge($this->module_cache['modules']); + + // Include MOD _info files for populating language entries within the menus + $this->add_mod_info($this->p_class); + + // Now build the module array, but exclude completely empty categories... + $right_id = false; + $names = array(); + + foreach ($this->module_cache['modules'] as $key => $row) + { + // Skip branch + if ($right_id !== false) + { + if ($row['left_id'] < $right_id) + { + continue; + } + + $right_id = false; + } + + // Category with no members on their way down (we have to check every level) + if (!$row['module_basename']) + { + $empty_category = true; + + // We go through the branch and look for an activated module + foreach (array_slice($this->module_cache['modules'], $key + 1) as $temp_row) + { + if ($temp_row['left_id'] > $row['left_id'] && $temp_row['left_id'] < $row['right_id']) + { + // Module there + if ($temp_row['module_basename'] && $temp_row['module_enabled']) + { + $empty_category = false; + break; + } + continue; + } + break; + } + + // Skip the branch + if ($empty_category) + { + $right_id = $row['right_id']; + continue; + } + } + + $depth = count($this->module_cache['parents'][$row['module_id']]); + + // We need to prefix the functions to not create a naming conflict + + // Function for building 'url_extra' + $short_name = $this->get_short_name($row['module_basename']); + + $url_func = 'phpbb_module_' . $short_name . '_url'; + if (!function_exists($url_func)) + { + $url_func = '_module_' . $short_name . '_url'; + } + + // Function for building the language name + $lang_func = 'phpbb_module_' . $short_name . '_lang'; + if (!function_exists($lang_func)) + { + $lang_func = '_module_' . $short_name . '_lang'; + } + + // Custom function for calling parameters on module init (for example assigning template variables) + $custom_func = 'phpbb_module_' . $short_name; + if (!function_exists($custom_func)) + { + $custom_func = '_module_' . $short_name; + } + + $names[$row['module_basename'] . '_' . $row['module_mode']][] = true; + + $module_row = array( + 'depth' => $depth, + + 'id' => (int) $row['module_id'], + 'parent' => (int) $row['parent_id'], + 'cat' => ($row['right_id'] > $row['left_id'] + 1) ? true : false, + + 'is_duplicate' => ($row['module_basename'] && count($names[$row['module_basename'] . '_' . $row['module_mode']]) > 1) ? true : false, + + 'name' => (string) $row['module_basename'], + 'mode' => (string) $row['module_mode'], + 'display' => (int) $row['module_display'], + + 'url_extra' => (function_exists($url_func)) ? $url_func($row['module_mode'], $row) : '', + + 'lang' => ($row['module_basename'] && function_exists($lang_func)) ? $lang_func($row['module_mode'], $row['module_langname']) : ((!empty($user->lang[$row['module_langname']])) ? $user->lang[$row['module_langname']] : $row['module_langname']), + 'langname' => $row['module_langname'], + + 'left' => $row['left_id'], + 'right' => $row['right_id'], + ); + + if (function_exists($custom_func)) + { + $custom_func($row['module_mode'], $module_row); + } + + /** + * This event allows to modify parameters for building modules list + * + * @event core.modify_module_row + * @var string url_func Function for building 'url_extra' + * @var string lang_func Function for building the language name + * @var string custom_func Custom function for calling parameters on module init + * @var array row Array holding the basic module data + * @var array module_row Array holding the module display parameters + * @since 3.1.0-b3 + */ + $vars = array('url_func', 'lang_func', 'custom_func', 'row', 'module_row'); + extract($phpbb_dispatcher->trigger_event('core.modify_module_row', compact($vars))); + + $this->module_ary[] = $module_row; + } + + unset($this->module_cache['modules'], $names); + } + + /** + * Check if a certain main module is accessible/loaded + * By giving the module mode you are able to additionally check for only one mode within the main module + * + * @param string $module_basename The module base name, for example logs, reports, main (for the mcp). + * @param mixed $module_mode The module mode to check. If provided the mode will be checked in addition for presence. + * + * @return bool Returns true if module is loaded and accessible, else returns false + */ + function loaded($module_basename, $module_mode = false) + { + if (!$this->is_full_class($module_basename)) + { + $module_basename = $this->p_class . '_' . $module_basename; + } + + if (empty($this->loaded_cache)) + { + $this->loaded_cache = array(); + + foreach ($this->module_ary as $row) + { + if (!$row['name']) + { + continue; + } + + if (!isset($this->loaded_cache[$row['name']])) + { + $this->loaded_cache[$row['name']] = array(); + } + + if (!$row['mode']) + { + continue; + } + + $this->loaded_cache[$row['name']][$row['mode']] = true; + } + } + + if ($module_mode === false) + { + return (isset($this->loaded_cache[$module_basename])) ? true : false; + } + + return (!empty($this->loaded_cache[$module_basename][$module_mode])) ? true : false; + } + + /** + * Check module authorisation. + * + * This is a non-static version that uses $this->acl_forum_id + * for the forum id. + */ + function module_auth_self($module_auth) + { + return self::module_auth($module_auth, $this->acl_forum_id); + } + + /** + * Check module authorisation. + * + * This is a static version, it must be given $forum_id. + * See also module_auth_self. + */ + static function module_auth($module_auth, $forum_id) + { + global $auth, $config; + global $request, $phpbb_extension_manager, $phpbb_dispatcher; + + $module_auth = trim($module_auth); + + // Generally allowed to access module if module_auth is empty + if (!$module_auth) + { + return true; + } + + // With the code below we make sure only those elements get eval'd we really want to be checked + preg_match_all('/(?: + "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" | + \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' | + [(),] | + [^\s(),]+)/x', $module_auth, $match); + + // Valid tokens for auth and their replacements + $valid_tokens = array( + 'acl_([a-z0-9_]+)(,\$id)?' => '(int) $auth->acl_get(\'\\1\'\\2)', + '\$id' => '(int) $forum_id', + 'aclf_([a-z0-9_]+)' => '(int) $auth->acl_getf_global(\'\\1\')', + 'cfg_([a-z0-9_]+)' => '(int) $config[\'\\1\']', + 'request_([a-zA-Z0-9_]+)' => '$request->variable(\'\\1\', false)', + 'ext_([a-zA-Z0-9_/]+)' => 'array_key_exists(\'\\1\', $phpbb_extension_manager->all_enabled())', + 'authmethod_([a-z0-9_\\\\]+)' => '($config[\'auth_method\'] === \'\\1\')', + ); + + /** + * Alter tokens for module authorisation check + * + * @event core.module_auth + * @var array valid_tokens Valid tokens and their auth check + * replacements + * @var string module_auth The module_auth of the current + * module + * @var int forum_id The current forum_id + * @since 3.1.0-a3 + */ + $vars = array('valid_tokens', 'module_auth', 'forum_id'); + extract($phpbb_dispatcher->trigger_event('core.module_auth', compact($vars))); + + $tokens = $match[0]; + for ($i = 0, $size = count($tokens); $i < $size; $i++) + { + $token = &$tokens[$i]; + + switch ($token) + { + case ')': + case '(': + case '&&': + case '||': + case ',': + break; + + default: + if (!preg_match('#(?:' . implode(array_keys($valid_tokens), ')|(?:') . ')#', $token)) + { + $token = ''; + } + break; + } + } + + $module_auth = implode(' ', $tokens); + + // Make sure $id separation is working fine + $module_auth = str_replace(' , ', ',', $module_auth); + + $module_auth = preg_replace( + // Array keys with # prepended/appended + array_map(function($value) { + return '#' . $value . '#'; + }, array_keys($valid_tokens)), + array_values($valid_tokens), + $module_auth + ); + + $is_auth = false; + // @codingStandardsIgnoreStart + eval('$is_auth = (int) (' . $module_auth . ');'); + // @codingStandardsIgnoreEnd + + return $is_auth; + } + + /** + * Set active module + */ + function set_active($id = false, $mode = false) + { + global $request; + + $icat = false; + $this->active_module = false; + + if ($request->variable('icat', '')) + { + $icat = $id; + $id = $request->variable('icat', ''); + } + + // Restore the backslashes in class names + if (strpos($id, '-') !== false) + { + $id = str_replace('-', '\\', $id); + } + + if ($id && !is_numeric($id) && !$this->is_full_class($id)) + { + $id = $this->p_class . '_' . $id; + } + + $category = false; + foreach ($this->module_ary as $row_id => $item_ary) + { + // If this is a module and it's selected, active + // If this is a category and the module is the first within it, active + // If this is a module and no mode selected, select first mode + // If no category or module selected, go active for first module in first category + if ( + (($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && (($item_ary['mode'] == $mode && !$item_ary['cat']) || ($icat && $item_ary['cat']))) || + ($item_ary['parent'] === $category && !$item_ary['cat'] && !$icat && $item_ary['display']) || + (($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && !$mode && !$item_ary['cat']) || + (!$id && !$mode && !$item_ary['cat'] && $item_ary['display']) + ) + { + if ($item_ary['cat']) + { + $id = $icat; + $icat = false; + + continue; + } + + $this->p_id = $item_ary['id']; + $this->p_parent = $item_ary['parent']; + $this->p_name = $item_ary['name']; + $this->p_mode = $item_ary['mode']; + $this->p_left = $item_ary['left']; + $this->p_right = $item_ary['right']; + + $this->module_cache['parents'] = $this->module_cache['parents'][$this->p_id]; + $this->active_module = $item_ary['id']; + $this->active_module_row_id = $row_id; + + break; + } + else if (($item_ary['cat'] && $item_ary['id'] === (int) $id) || ($item_ary['parent'] === $category && $item_ary['cat'])) + { + $category = $item_ary['id']; + } + } + } + + /** + * Loads currently active module + * + * This method loads a given module, passing it the relevant id and mode. + * + * @param string|false $mode mode, as passed through to the module + * @param string|false $module_url If supplied, we use this module url + * @param bool $execute_module If true, at the end we execute the main method for the new instance + */ + function load_active($mode = false, $module_url = false, $execute_module = true) + { + global $phpbb_root_path, $phpbb_admin_path, $phpEx, $user, $template, $request; + + $module_path = $this->include_path . $this->p_class; + $icat = $request->variable('icat', ''); + + if ($this->active_module === false) + { + trigger_error('MODULE_NOT_ACCESS', E_USER_ERROR); + } + + // new modules use the full class names, old ones are always called _, e.g. acp_board + if (!class_exists($this->p_name)) + { + if (!file_exists("$module_path/{$this->p_name}.$phpEx")) + { + trigger_error($user->lang('MODULE_NOT_FIND', "$module_path/{$this->p_name}.$phpEx"), E_USER_ERROR); + } + + include("$module_path/{$this->p_name}.$phpEx"); + + if (!class_exists($this->p_name)) + { + trigger_error($user->lang('MODULE_FILE_INCORRECT_CLASS', "$module_path/{$this->p_name}.$phpEx", $this->p_name), E_USER_ERROR); + } + } + + if (!empty($mode)) + { + $this->p_mode = $mode; + } + + // Create a new instance of the desired module ... + $class_name = $this->p_name; + + $this->module = new $class_name($this); + + // We pre-define the action parameter we are using all over the place + if (defined('IN_ADMIN')) + { + /* + * If this is an extension module, we'll try to automatically set + * the style paths for the extension (the ext author can change them + * if necessary). + */ + $module_dir = explode('\\', get_class($this->module)); + + // 0 vendor, 1 extension name, ... + if (isset($module_dir[1])) + { + $module_style_dir = $phpbb_root_path . 'ext/' . $module_dir[0] . '/' . $module_dir[1] . '/adm/style'; + + if (is_dir($module_style_dir)) + { + $template->set_custom_style(array( + array( + 'name' => 'adm', + 'ext_path' => 'adm/style/', + ), + ), array($module_style_dir, $phpbb_admin_path . 'style')); + } + } + + // Is first module automatically enabled a duplicate and the category not passed yet? + if (!$icat && $this->module_ary[$this->active_module_row_id]['is_duplicate']) + { + $icat = $this->module_ary[$this->active_module_row_id]['parent']; + } + + // Not being able to overwrite ;) + $this->module->u_action = append_sid("{$phpbb_admin_path}index.$phpEx", 'i=' . $this->get_module_identifier($this->p_name)) . (($icat) ? '&icat=' . $icat : '') . "&mode={$this->p_mode}"; + } + else + { + /* + * If this is an extension module, we'll try to automatically set + * the style paths for the extension (the ext author can change them + * if necessary). + */ + $module_dir = explode('\\', get_class($this->module)); + + // 0 vendor, 1 extension name, ... + if (isset($module_dir[1])) + { + $module_style_dir = 'ext/' . $module_dir[0] . '/' . $module_dir[1] . '/styles'; + + if (is_dir($phpbb_root_path . $module_style_dir)) + { + $template->set_style(array($module_style_dir, 'styles')); + } + } + + // If user specified the module url we will use it... + if ($module_url !== false) + { + $this->module->u_action = $module_url; + } + else + { + $this->module->u_action = $phpbb_root_path . (($user->page['page_dir']) ? $user->page['page_dir'] . '/' : '') . $user->page['page_name']; + } + + $this->module->u_action = append_sid($this->module->u_action, 'i=' . $this->get_module_identifier($this->p_name)) . (($icat) ? '&icat=' . $icat : '') . "&mode={$this->p_mode}"; + } + + // Add url_extra parameter to u_action url + if (!empty($this->module_ary) && $this->active_module !== false && $this->module_ary[$this->active_module_row_id]['url_extra']) + { + $this->module->u_action .= $this->module_ary[$this->active_module_row_id]['url_extra']; + } + + // Assign the module path for re-usage + $this->module->module_path = $module_path . '/'; + + // Execute the main method for the new instance, we send the module id and mode as parameters + // Users are able to call the main method after this function to be able to assign additional parameters manually + if ($execute_module) + { + $short_name = preg_replace("#^{$this->p_class}_#", '', $this->p_name); + $this->module->main($short_name, $this->p_mode); + } + } + + /** + * Appending url parameter to the currently active module. + * + * This function is called for adding specific url parameters while executing the current module. + * It is doing the same as the _module_{name}_url() function, apart from being able to be called after + * having dynamically parsed specific parameters. This allows more freedom in choosing additional parameters. + * One example can be seen in /includes/mcp/mcp_notes.php - $this->p_master->adjust_url() call. + * + * @param string $url_extra Extra url parameters, e.g.: &u=$user_id + * + */ + function adjust_url($url_extra) + { + if (empty($this->module_ary[$this->active_module_row_id])) + { + return; + } + + $row = &$this->module_ary[$this->active_module_row_id]; + + // We check for the same url_extra in $row['url_extra'] to overcome doubled additions... + if (strpos($row['url_extra'], $url_extra) === false) + { + $row['url_extra'] .= $url_extra; + } + } + + /** + * Check if a module is active + */ + function is_active($id, $mode = false) + { + // If we find a name by this id and being enabled we have our active one... + foreach ($this->module_ary as $row_id => $item_ary) + { + if (($item_ary['name'] === $id || $item_ary['id'] === (int) $id) && $item_ary['display'] || $item_ary['name'] === $this->p_class . '_' . $id) + { + if ($mode === false || $mode === $item_ary['mode']) + { + return true; + } + } + } + + return false; + } + + /** + * Get parents + */ + function get_parents($parent_id, $left_id, $right_id, &$all_parents) + { + $parents = array(); + + if ($parent_id > 0) + { + foreach ($all_parents as $module_id => $row) + { + if ($row['left_id'] < $left_id && $row['right_id'] > $right_id) + { + $parents[$module_id] = $row['parent_id']; + } + + if ($row['left_id'] > $left_id) + { + break; + } + } + } + + return $parents; + } + + /** + * Get tree branch + */ + function get_branch($left_id, $right_id, $remaining) + { + $branch = array(); + + foreach ($remaining as $key => $row) + { + if ($row['left_id'] > $left_id && $row['left_id'] < $right_id) + { + $branch[] = $row; + continue; + } + break; + } + + return $branch; + } + + /** + * Build true binary tree from given array + * Not in use + */ + function build_tree(&$modules, &$parents) + { + $tree = array(); + + foreach ($modules as $row) + { + $branch = &$tree; + + if ($row['parent_id']) + { + // Go through the tree to find our branch + $parent_tree = $parents[$row['module_id']]; + + foreach ($parent_tree as $id => $value) + { + if (!isset($branch[$id]) && isset($branch['child'])) + { + $branch = &$branch['child']; + } + $branch = &$branch[$id]; + } + $branch = &$branch['child']; + } + + $branch[$row['module_id']] = $row; + if (!isset($branch[$row['module_id']]['child'])) + { + $branch[$row['module_id']]['child'] = array(); + } + } + + return $tree; + } + + /** + * Build navigation structure + */ + function assign_tpl_vars($module_url) + { + global $template; + + $current_id = $right_id = false; + + // Make sure the module_url has a question mark set, effectively determining the delimiter to use + $delim = (strpos($module_url, '?') === false) ? '?' : '&'; + + $current_depth = 0; + $linear_offset = 'l_block1'; + $tabular_offset = 't_block2'; + + // Generate the list of modules, we'll do this in two ways ... + // 1) In a linear fashion + // 2) In a combined tabbed + linear fashion ... tabs for the categories + // and a linear list for subcategories/items + foreach ($this->module_ary as $row_id => $item_ary) + { + // Skip hidden modules + if (!$item_ary['display']) + { + continue; + } + + // Skip branch + if ($right_id !== false) + { + if ($item_ary['left'] < $right_id) + { + continue; + } + + $right_id = false; + } + + // Category with no members on their way down (we have to check every level) + if (!$item_ary['name']) + { + $empty_category = true; + + // We go through the branch and look for an activated module + foreach (array_slice($this->module_ary, $row_id + 1) as $temp_row) + { + if ($temp_row['left'] > $item_ary['left'] && $temp_row['left'] < $item_ary['right']) + { + // Module there and displayed? + if ($temp_row['name'] && $temp_row['display']) + { + $empty_category = false; + break; + } + continue; + } + break; + } + + // Skip the branch + if ($empty_category) + { + $right_id = $item_ary['right']; + continue; + } + } + + // Select first id we can get + if (!$current_id && (isset($this->module_cache['parents'][$item_ary['id']]) || $item_ary['id'] == $this->p_id)) + { + $current_id = $item_ary['id']; + } + + $depth = $item_ary['depth']; + + if ($depth > $current_depth) + { + $linear_offset = $linear_offset . '.l_block' . ($depth + 1); + $tabular_offset = ($depth + 1 > 2) ? $tabular_offset . '.t_block' . ($depth + 1) : $tabular_offset; + } + else if ($depth < $current_depth) + { + for ($i = $current_depth - $depth; $i > 0; $i--) + { + $linear_offset = substr($linear_offset, 0, strrpos($linear_offset, '.')); + $tabular_offset = ($i + $depth > 1) ? substr($tabular_offset, 0, strrpos($tabular_offset, '.')) : $tabular_offset; + } + } + + $u_title = $module_url . $delim . 'i='; + // if the item has a name use it, else use its id + if (empty($item_ary['name'])) + { + $u_title .= $item_ary['id']; + } + else + { + // if the category has a name, then use it. + $u_title .= $this->get_module_identifier($item_ary['name']); + } + // If the item is not a category append the mode + if (!$item_ary['cat']) + { + if ($item_ary['is_duplicate']) + { + $u_title .= '&icat=' . $current_id; + } + $u_title .= '&mode=' . $item_ary['mode']; + } + + // Was not allowed in categories before - /*!$item_ary['cat'] && */ + $u_title .= (isset($item_ary['url_extra'])) ? $item_ary['url_extra'] : ''; + + // Only output a categories items if it's currently selected + if (!$depth || ($depth && (in_array($item_ary['parent'], array_values($this->module_cache['parents'])) || $item_ary['parent'] == $this->p_parent))) + { + $use_tabular_offset = (!$depth) ? 't_block1' : $tabular_offset; + + $tpl_ary = array( + 'L_TITLE' => $item_ary['lang'], + 'S_SELECTED' => (isset($this->module_cache['parents'][$item_ary['id']]) || $item_ary['id'] == $this->p_id) ? true : false, + 'U_TITLE' => $u_title + ); + + $template->assign_block_vars($use_tabular_offset, array_merge($tpl_ary, array_change_key_case($item_ary, CASE_UPPER))); + } + + $tpl_ary = array( + 'L_TITLE' => $item_ary['lang'], + 'S_SELECTED' => (isset($this->module_cache['parents'][$item_ary['id']]) || $item_ary['id'] == $this->p_id) ? true : false, + 'U_TITLE' => $u_title + ); + + $template->assign_block_vars($linear_offset, array_merge($tpl_ary, array_change_key_case($item_ary, CASE_UPPER))); + + $current_depth = $depth; + } + } + + /** + * Returns desired template name + */ + function get_tpl_name() + { + return $this->module->tpl_name . '.html'; + } + + /** + * Returns the desired page title + */ + function get_page_title() + { + global $user; + + if (!isset($this->module->page_title)) + { + return ''; + } + + return (isset($user->lang[$this->module->page_title])) ? $user->lang[$this->module->page_title] : $this->module->page_title; + } + + /** + * Load module as the current active one without the need for registering it + * + * @param string $class module class (acp/mcp/ucp) + * @param string $name module name (class name of the module, or its basename + * phpbb_ext_foo_acp_bar_module, ucp_zebra or zebra) + * @param string $mode mode, as passed through to the module + * + */ + function load($class, $name, $mode = false) + { + // new modules use the full class names, old ones are always called _, e.g. acp_board + // in the latter case this function may be called as load('acp', 'board') + if (!class_exists($name) && substr($name, 0, strlen($class) + 1) !== $class . '_') + { + $name = $class . '_' . $name; + } + + $this->p_class = $class; + $this->p_name = $name; + + // Set active module to true instead of using the id + $this->active_module = true; + + $this->load_active($mode); + } + + /** + * Display module + */ + function display($page_title, $display_online_list = false) + { + global $template, $user; + + // Generate the page + if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) + { + adm_page_header($page_title); + } + else + { + page_header($page_title, $display_online_list); + } + + $template->set_filenames(array( + 'body' => $this->get_tpl_name()) + ); + + if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin']) + { + adm_page_footer(); + } + else + { + page_footer(); + } + } + + /** + * Toggle whether this module will be displayed or not + */ + function set_display($id, $mode = false, $display = true) + { + foreach ($this->module_ary as $row_id => $item_ary) + { + if (($item_ary['name'] === $id || $item_ary['name'] === $this->p_class . '_' . $id || $item_ary['id'] === (int) $id) && (!$mode || $item_ary['mode'] === $mode)) + { + $this->module_ary[$row_id]['display'] = (int) $display; + } + } + } + + /** + * Add custom MOD info language file + */ + function add_mod_info($module_class) + { + global $config, $user, $phpEx, $phpbb_extension_manager; + + $finder = $phpbb_extension_manager->get_finder(); + + // We grab the language files from the default, English and user's language. + // So we can fall back to the other files like we do when using add_lang() + $default_lang_files = $english_lang_files = $user_lang_files = array(); + + // Search for board default language if it's not the user language + if ($config['default_lang'] != $user->lang_name) + { + $default_lang_files = $finder + ->prefix('info_' . strtolower($module_class) . '_') + ->suffix(".$phpEx") + ->extension_directory('/language/' . basename($config['default_lang'])) + ->core_path('language/' . basename($config['default_lang']) . '/mods/') + ->find(); + } + + // Search for english, if its not the default or user language + if ($config['default_lang'] != 'en' && $user->lang_name != 'en') + { + $english_lang_files = $finder + ->prefix('info_' . strtolower($module_class) . '_') + ->suffix(".$phpEx") + ->extension_directory('/language/en') + ->core_path('language/en/mods/') + ->find(); + } + + // Find files in the user's language + $user_lang_files = $finder + ->prefix('info_' . strtolower($module_class) . '_') + ->suffix(".$phpEx") + ->extension_directory('/language/' . $user->lang_name) + ->core_path('language/' . $user->lang_name . '/mods/') + ->find(); + + $lang_files = array_merge($english_lang_files, $default_lang_files, $user_lang_files); + foreach ($lang_files as $lang_file => $ext_name) + { + $user->add_lang_ext($ext_name, $lang_file); + } + } + + /** + * Retrieve shortened module basename for legacy basenames (with xcp_ prefix) + * + * @param string $basename A module basename + * @return string The basename if it starts with phpbb_ or the basename with + * the current p_class (e.g. acp_) stripped. + */ + protected function get_short_name($basename) + { + if (substr($basename, 0, 6) === 'phpbb\\' || strpos($basename, '\\') !== false) + { + return $basename; + } + + // strip xcp_ prefix from old classes + return substr($basename, strlen($this->p_class) + 1); + } + + /** + * If the basename contains a \ we don't use that for the URL. + * + * Firefox is currently unable to correctly copy a urlencoded \ + * so users will be unable to post links to modules. + * However we can replace them with dashes and re-replace them later + * + * @param string $basename Basename of the module + * @return string Identifier that should be used for + * module link creation + */ + protected function get_module_identifier($basename) + { + if (strpos($basename, '\\') === false) + { + return $basename; + } + + return str_replace('\\', '-', $basename); + } + + /** + * Checks whether the given module basename is a correct class name + * + * @param string $basename A module basename + * @return bool True if the basename starts with phpbb_ or (x)cp_, false otherwise + */ + protected function is_full_class($basename) + { + return (strpos($basename, '\\') !== false || preg_match('/^(ucp|mcp|acp)_/', $basename)); + } +} diff --git a/includes/functions_posting.php b/includes/functions_posting.php new file mode 100644 index 0000000..3640f54 --- /dev/null +++ b/includes/functions_posting.php @@ -0,0 +1,2772 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Fill smiley templates (or just the variables) with smilies, either in a window or inline +*/ +function generate_smilies($mode, $forum_id) +{ + global $db, $user, $config, $template, $phpbb_dispatcher, $request; + global $phpEx, $phpbb_root_path, $phpbb_container, $phpbb_path_helper; + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $base_url = append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=smilies&f=' . $forum_id); + $start = $request->variable('start', 0); + + if ($mode == 'window') + { + if ($forum_id) + { + $sql = 'SELECT forum_style + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $user->setup('posting', (int) $row['forum_style']); + } + else + { + $user->setup('posting'); + } + + page_header($user->lang['SMILIES']); + + $sql = 'SELECT COUNT(smiley_id) AS item_count + FROM ' . SMILIES_TABLE . ' + GROUP BY smiley_url'; + $result = $db->sql_query($sql, 3600); + + $smiley_count = 0; + while ($row = $db->sql_fetchrow($result)) + { + ++$smiley_count; + } + $db->sql_freeresult($result); + + $template->set_filenames(array( + 'body' => 'posting_smilies.html') + ); + + $start = $pagination->validate_start($start, $config['smilies_per_page'], $smiley_count); + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $smiley_count, $config['smilies_per_page'], $start); + } + + $display_link = false; + if ($mode == 'inline') + { + $sql = 'SELECT smiley_id + FROM ' . SMILIES_TABLE . ' + WHERE display_on_posting = 0'; + $result = $db->sql_query_limit($sql, 1, 0, 3600); + + if ($row = $db->sql_fetchrow($result)) + { + $display_link = true; + } + $db->sql_freeresult($result); + } + + if ($mode == 'window') + { + $sql = 'SELECT smiley_url, MIN(emotion) as emotion, MIN(code) AS code, smiley_width, smiley_height, MIN(smiley_order) AS min_smiley_order + FROM ' . SMILIES_TABLE . ' + GROUP BY smiley_url, smiley_width, smiley_height + ORDER BY min_smiley_order'; + $result = $db->sql_query_limit($sql, $config['smilies_per_page'], $start, 3600); + } + else + { + $sql = 'SELECT * + FROM ' . SMILIES_TABLE . ' + WHERE display_on_posting = 1 + ORDER BY smiley_order'; + $result = $db->sql_query($sql, 3600); + } + + $smilies = array(); + while ($row = $db->sql_fetchrow($result)) + { + if (empty($smilies[$row['smiley_url']])) + { + $smilies[$row['smiley_url']] = $row; + } + } + $db->sql_freeresult($result); + + if (count($smilies)) + { + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $phpbb_path_helper->get_web_root_path(); + + foreach ($smilies as $row) + { + /** + * Modify smiley root path before populating smiley list + * + * @event core.generate_smilies_before + * @var string root_path root_path for smilies + * @since 3.1.11-RC1 + */ + $vars = array('root_path'); + extract($phpbb_dispatcher->trigger_event('core.generate_smilies_before', compact($vars))); + $template->assign_block_vars('smiley', array( + 'SMILEY_CODE' => $row['code'], + 'A_SMILEY_CODE' => addslashes($row['code']), + 'SMILEY_IMG' => $root_path . $config['smilies_path'] . '/' . $row['smiley_url'], + 'SMILEY_WIDTH' => $row['smiley_width'], + 'SMILEY_HEIGHT' => $row['smiley_height'], + 'SMILEY_DESC' => $row['emotion']) + ); + } + } + + /** + * This event is called after the smilies are populated + * + * @event core.generate_smilies_after + * @var string mode Mode of the smilies: window|inline + * @var int forum_id The forum ID we are currently in + * @var bool display_link Shall we display the "more smilies" link? + * @since 3.1.0-a1 + */ + $vars = array('mode', 'forum_id', 'display_link'); + extract($phpbb_dispatcher->trigger_event('core.generate_smilies_after', compact($vars))); + + if ($mode == 'inline' && $display_link) + { + $template->assign_vars(array( + 'S_SHOW_SMILEY_LINK' => true, + 'U_MORE_SMILIES' => $base_url, + )); + } + + if ($mode == 'window') + { + page_footer(); + } +} + +/** +* Update last post information +* Should be used instead of sync() if only the last post information are out of sync... faster +* +* @param string $type Can be forum|topic +* @param mixed $ids topic/forum ids +* @param bool $return_update_sql true: SQL query shall be returned, false: execute SQL +*/ +function update_post_information($type, $ids, $return_update_sql = false) +{ + global $db; + + if (empty($ids)) + { + return; + } + if (!is_array($ids)) + { + $ids = array($ids); + } + + $update_sql = $empty_forums = $not_empty_forums = array(); + + if ($type != 'topic') + { + $topic_join = ', ' . TOPICS_TABLE . ' t'; + $topic_condition = 'AND t.topic_id = p.topic_id AND t.topic_visibility = ' . ITEM_APPROVED; + } + else + { + $topic_join = ''; + $topic_condition = ''; + } + + if (count($ids) == 1) + { + $sql = 'SELECT p.post_id as last_post_id + FROM ' . POSTS_TABLE . " p $topic_join + WHERE " . $db->sql_in_set('p.' . $type . '_id', $ids) . " + $topic_condition + AND p.post_visibility = " . ITEM_APPROVED . " + ORDER BY p.post_id DESC"; + $result = $db->sql_query_limit($sql, 1); + } + else + { + $sql = 'SELECT p.' . $type . '_id, MAX(p.post_id) as last_post_id + FROM ' . POSTS_TABLE . " p $topic_join + WHERE " . $db->sql_in_set('p.' . $type . '_id', $ids) . " + $topic_condition + AND p.post_visibility = " . ITEM_APPROVED . " + GROUP BY p.{$type}_id"; + $result = $db->sql_query($sql); + } + + $last_post_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + if (count($ids) == 1) + { + $row[$type . '_id'] = $ids[0]; + } + + if ($type == 'forum') + { + $not_empty_forums[] = $row['forum_id']; + + if (empty($row['last_post_id'])) + { + $empty_forums[] = $row['forum_id']; + } + } + + $last_post_ids[] = $row['last_post_id']; + } + $db->sql_freeresult($result); + + if ($type == 'forum') + { + $empty_forums = array_merge($empty_forums, array_diff($ids, $not_empty_forums)); + + foreach ($empty_forums as $void => $forum_id) + { + $update_sql[$forum_id][] = 'forum_last_post_id = 0'; + $update_sql[$forum_id][] = "forum_last_post_subject = ''"; + $update_sql[$forum_id][] = 'forum_last_post_time = 0'; + $update_sql[$forum_id][] = 'forum_last_poster_id = 0'; + $update_sql[$forum_id][] = "forum_last_poster_name = ''"; + $update_sql[$forum_id][] = "forum_last_poster_colour = ''"; + } + } + + if (count($last_post_ids)) + { + $sql = 'SELECT p.' . $type . '_id, p.post_id, p.post_subject, p.post_time, p.poster_id, p.post_username, u.user_id, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE p.poster_id = u.user_id + AND ' . $db->sql_in_set('p.post_id', $last_post_ids); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $update_sql[$row["{$type}_id"]][] = $type . '_last_post_id = ' . (int) $row['post_id']; + $update_sql[$row["{$type}_id"]][] = "{$type}_last_post_subject = '" . $db->sql_escape($row['post_subject']) . "'"; + $update_sql[$row["{$type}_id"]][] = $type . '_last_post_time = ' . (int) $row['post_time']; + $update_sql[$row["{$type}_id"]][] = $type . '_last_poster_id = ' . (int) $row['poster_id']; + $update_sql[$row["{$type}_id"]][] = "{$type}_last_poster_colour = '" . $db->sql_escape($row['user_colour']) . "'"; + $update_sql[$row["{$type}_id"]][] = "{$type}_last_poster_name = '" . (($row['poster_id'] == ANONYMOUS) ? $db->sql_escape($row['post_username']) : $db->sql_escape($row['username'])) . "'"; + } + $db->sql_freeresult($result); + } + unset($empty_forums, $ids, $last_post_ids); + + if ($return_update_sql || !count($update_sql)) + { + return $update_sql; + } + + $table = ($type == 'forum') ? FORUMS_TABLE : TOPICS_TABLE; + + foreach ($update_sql as $update_id => $update_sql_ary) + { + $sql = "UPDATE $table + SET " . implode(', ', $update_sql_ary) . " + WHERE {$type}_id = $update_id"; + $db->sql_query($sql); + } + + return; +} + +/** +* Generate Topic Icons for display +*/ +function posting_gen_topic_icons($mode, $icon_id) +{ + global $phpbb_root_path, $config, $template, $cache; + + // Grab icons + $icons = $cache->obtain_icons(); + + if (!$icon_id) + { + $template->assign_var('S_NO_ICON_CHECKED', ' checked="checked"'); + } + + if (count($icons)) + { + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $phpbb_root_path; + + foreach ($icons as $id => $data) + { + if ($data['display']) + { + $template->assign_block_vars('topic_icon', array( + 'ICON_ID' => $id, + 'ICON_IMG' => $root_path . $config['icons_path'] . '/' . $data['img'], + 'ICON_WIDTH' => $data['width'], + 'ICON_HEIGHT' => $data['height'], + 'ICON_ALT' => $data['alt'], + + 'S_CHECKED' => ($id == $icon_id) ? true : false, + 'S_ICON_CHECKED' => ($id == $icon_id) ? ' checked="checked"' : '') + ); + } + } + + return true; + } + + return false; +} + +/** +* Build topic types able to be selected +*/ +function posting_gen_topic_types($forum_id, $cur_topic_type = POST_NORMAL) +{ + global $auth, $user, $template; + + $toggle = false; + + $topic_types = array( + 'sticky' => array('const' => POST_STICKY, 'lang' => 'POST_STICKY'), + 'announce' => array('const' => POST_ANNOUNCE, 'lang' => 'POST_ANNOUNCEMENT'), + 'announce_global' => array('const' => POST_GLOBAL, 'lang' => 'POST_GLOBAL') + ); + + $topic_type_array = array(); + + foreach ($topic_types as $auth_key => $topic_value) + { + if ($auth->acl_get('f_' . $auth_key, $forum_id)) + { + $toggle = true; + + $topic_type_array[] = array( + 'VALUE' => $topic_value['const'], + 'S_CHECKED' => ($cur_topic_type == $topic_value['const']) ? ' checked="checked"' : '', + 'L_TOPIC_TYPE' => $user->lang[$topic_value['lang']] + ); + } + } + + if ($toggle) + { + $topic_type_array = array_merge(array(0 => array( + 'VALUE' => POST_NORMAL, + 'S_CHECKED' => ($cur_topic_type == POST_NORMAL) ? ' checked="checked"' : '', + 'L_TOPIC_TYPE' => $user->lang['POST_NORMAL'])), + + $topic_type_array + ); + + foreach ($topic_type_array as $array) + { + $template->assign_block_vars('topic_type', $array); + } + + $template->assign_vars(array( + 'S_TOPIC_TYPE_STICKY' => ($auth->acl_get('f_sticky', $forum_id)), + 'S_TOPIC_TYPE_ANNOUNCE' => ($auth->acl_gets('f_announce', 'f_announce_global', $forum_id)), + )); + } + + return $toggle; +} + +// +// Attachment related functions +// + +/** +* Upload Attachment - filedata is generated here +* Uses upload class +* +* @deprecated 3.2.0-a1 (To be removed: 3.4.0) +* +* @param string $form_name The form name of the file upload input +* @param int $forum_id The id of the forum +* @param bool $local Whether the file is local or not +* @param string $local_storage The path to the local file +* @param bool $is_message Whether it is a PM or not +* @param array $local_filedata A filespec object created for the local file +* +* @return array File data array +*/ +function upload_attachment($form_name, $forum_id, $local = false, $local_storage = '', $is_message = false, $local_filedata = false) +{ + global $phpbb_container; + + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $file = $attachment_manager->upload($form_name, $forum_id, $local, $local_storage, $is_message, $local_filedata); + unset($attachment_manager); + + return $file; +} + +/** +* Calculate the needed size for Thumbnail +*/ +function get_img_size_format($width, $height) +{ + global $config; + + // Maximum Width the Image can take + $max_width = ($config['img_max_thumb_width']) ? $config['img_max_thumb_width'] : 400; + + if ($width > $height) + { + return array( + round($width * ($max_width / $width)), + round($height * ($max_width / $width)) + ); + } + else + { + return array( + round($width * ($max_width / $height)), + round($height * ($max_width / $height)) + ); + } +} + +/** +* Return supported image types +*/ +function get_supported_image_types($type = false) +{ + if (@extension_loaded('gd')) + { + $format = imagetypes(); + $new_type = 0; + + if ($type !== false) + { + // Type is one of the IMAGETYPE constants - it is fetched from getimagesize() + switch ($type) + { + // GIF + case IMAGETYPE_GIF: + $new_type = ($format & IMG_GIF) ? IMG_GIF : false; + break; + + // JPG, JPC, JP2 + case IMAGETYPE_JPEG: + case IMAGETYPE_JPC: + case IMAGETYPE_JPEG2000: + case IMAGETYPE_JP2: + case IMAGETYPE_JPX: + case IMAGETYPE_JB2: + $new_type = ($format & IMG_JPG) ? IMG_JPG : false; + break; + + // PNG + case IMAGETYPE_PNG: + $new_type = ($format & IMG_PNG) ? IMG_PNG : false; + break; + + // WBMP + case IMAGETYPE_WBMP: + $new_type = ($format & IMG_WBMP) ? IMG_WBMP : false; + break; + } + } + else + { + $new_type = array(); + $go_through_types = array(IMG_GIF, IMG_JPG, IMG_PNG, IMG_WBMP); + + foreach ($go_through_types as $check_type) + { + if ($format & $check_type) + { + $new_type[] = $check_type; + } + } + } + + return array( + 'gd' => ($new_type) ? true : false, + 'format' => $new_type, + 'version' => (function_exists('imagecreatetruecolor')) ? 2 : 1 + ); + } + + return array('gd' => false); +} + +/** +* Create Thumbnail +*/ +function create_thumbnail($source, $destination, $mimetype) +{ + global $config, $phpbb_filesystem, $phpbb_dispatcher; + + $min_filesize = (int) $config['img_min_thumb_filesize']; + $img_filesize = (file_exists($source)) ? @filesize($source) : false; + + if (!$img_filesize || $img_filesize <= $min_filesize) + { + return false; + } + + $dimension = @getimagesize($source); + + if ($dimension === false) + { + return false; + } + + list($width, $height, $type, ) = $dimension; + + if (empty($width) || empty($height)) + { + return false; + } + + list($new_width, $new_height) = get_img_size_format($width, $height); + + // Do not create a thumbnail if the resulting width/height is bigger than the original one + if ($new_width >= $width && $new_height >= $height) + { + return false; + } + + $thumbnail_created = false; + + /** + * Create thumbnail event to replace GD thumbnail creation with for example ImageMagick + * + * @event core.thumbnail_create_before + * @var string source Image source path + * @var string destination Thumbnail destination path + * @var string mimetype Image mime type + * @var float new_width Calculated thumbnail width + * @var float new_height Calculated thumbnail height + * @var bool thumbnail_created Set to true to skip default GD thumbnail creation + * @since 3.2.4 + */ + $vars = array( + 'source', + 'destination', + 'mimetype', + 'new_width', + 'new_height', + 'thumbnail_created', + ); + extract($phpbb_dispatcher->trigger_event('core.thumbnail_create_before', compact($vars))); + + if (!$thumbnail_created) + { + $type = get_supported_image_types($type); + + if ($type['gd']) + { + // If the type is not supported, we are not able to create a thumbnail + if ($type['format'] === false) + { + return false; + } + + switch ($type['format']) + { + case IMG_GIF: + $image = @imagecreatefromgif($source); + break; + + case IMG_JPG: + @ini_set('gd.jpeg_ignore_warning', 1); + $image = @imagecreatefromjpeg($source); + break; + + case IMG_PNG: + $image = @imagecreatefrompng($source); + break; + + case IMG_WBMP: + $image = @imagecreatefromwbmp($source); + break; + } + + if (empty($image)) + { + return false; + } + + if ($type['version'] == 1) + { + $new_image = imagecreate($new_width, $new_height); + + if ($new_image === false) + { + return false; + } + + imagecopyresized($new_image, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height); + } + else + { + $new_image = imagecreatetruecolor($new_width, $new_height); + + if ($new_image === false) + { + return false; + } + + // Preserve alpha transparency (png for example) + @imagealphablending($new_image, false); + @imagesavealpha($new_image, true); + + imagecopyresampled($new_image, $image, 0, 0, 0, 0, $new_width, $new_height, $width, $height); + } + + // If we are in safe mode create the destination file prior to using the gd functions to circumvent a PHP bug + if (@ini_get('safe_mode') || @strtolower(ini_get('safe_mode')) == 'on') + { + @touch($destination); + } + + switch ($type['format']) + { + case IMG_GIF: + imagegif($new_image, $destination); + break; + + case IMG_JPG: + imagejpeg($new_image, $destination, 90); + break; + + case IMG_PNG: + imagepng($new_image, $destination); + break; + + case IMG_WBMP: + imagewbmp($new_image, $destination); + break; + } + + imagedestroy($new_image); + } + else + { + return false; + } + } + + if (!file_exists($destination)) + { + return false; + } + + try + { + $phpbb_filesystem->phpbb_chmod($destination, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + + return true; +} + +/** +* Assign Inline attachments (build option fields) +*/ +function posting_gen_inline_attachments(&$attachment_data) +{ + global $template; + + if (count($attachment_data)) + { + $s_inline_attachment_options = ''; + + foreach ($attachment_data as $i => $attachment) + { + $s_inline_attachment_options .= ''; + } + + $template->assign_var('S_INLINE_ATTACHMENT_OPTIONS', $s_inline_attachment_options); + + return true; + } + + return false; +} + +/** +* Generate inline attachment entry +*/ +function posting_gen_attachment_entry($attachment_data, &$filename_data, $show_attach_box = true) +{ + global $template, $config, $phpbb_root_path, $phpEx, $user, $phpbb_dispatcher; + + // Some default template variables + $template->assign_vars(array( + 'S_SHOW_ATTACH_BOX' => $show_attach_box, + 'S_HAS_ATTACHMENTS' => count($attachment_data), + 'FILESIZE' => $config['max_filesize'], + 'FILE_COMMENT' => (isset($filename_data['filecomment'])) ? $filename_data['filecomment'] : '', + )); + + if (count($attachment_data)) + { + // We display the posted attachments within the desired order. + ($config['display_order']) ? krsort($attachment_data) : ksort($attachment_data); + + $attachrow_template_vars = []; + + foreach ($attachment_data as $count => $attach_row) + { + $hidden = ''; + $attach_row['real_filename'] = utf8_basename($attach_row['real_filename']); + + foreach ($attach_row as $key => $value) + { + $hidden .= ''; + } + + $download_link = append_sid("{$phpbb_root_path}download/file.$phpEx", 'mode=view&id=' . (int) $attach_row['attach_id'], true, ($attach_row['is_orphan']) ? $user->session_id : false); + + $attachrow_template_vars[(int) $attach_row['attach_id']] = array( + 'FILENAME' => utf8_basename($attach_row['real_filename']), + 'A_FILENAME' => addslashes(utf8_basename($attach_row['real_filename'])), + 'FILE_COMMENT' => $attach_row['attach_comment'], + 'ATTACH_ID' => $attach_row['attach_id'], + 'S_IS_ORPHAN' => $attach_row['is_orphan'], + 'ASSOC_INDEX' => $count, + 'FILESIZE' => get_formatted_filesize($attach_row['filesize']), + + 'U_VIEW_ATTACHMENT' => $download_link, + 'S_HIDDEN' => $hidden, + ); + } + + /** + * Modify inline attachments template vars + * + * @event core.modify_inline_attachments_template_vars + * @var array attachment_data Array containing attachments data + * @var array attachrow_template_vars Array containing attachments template vars + * @since 3.2.2-RC1 + */ + $vars = array('attachment_data', 'attachrow_template_vars'); + extract($phpbb_dispatcher->trigger_event('core.modify_inline_attachments_template_vars', compact($vars))); + + $template->assign_block_vars_array('attach_row', $attachrow_template_vars); + } + + return count($attachment_data); +} + +// +// General Post functions +// + +/** +* Load Drafts +*/ +function load_drafts($topic_id = 0, $forum_id = 0, $id = 0, $pm_action = '', $msg_id = 0) +{ + global $user, $db, $template, $auth; + global $phpbb_root_path, $phpbb_dispatcher, $phpEx; + + $topic_ids = $forum_ids = $draft_rows = array(); + + // Load those drafts not connected to forums/topics + // If forum_id == 0 AND topic_id == 0 then this is a PM draft + if (!$topic_id && !$forum_id) + { + $sql_and = ' AND d.forum_id = 0 AND d.topic_id = 0'; + } + else + { + $sql_and = ''; + $sql_and .= ($forum_id) ? ' AND d.forum_id = ' . (int) $forum_id : ''; + $sql_and .= ($topic_id) ? ' AND d.topic_id = ' . (int) $topic_id : ''; + } + + $sql = 'SELECT d.*, f.forum_id, f.forum_name + FROM ' . DRAFTS_TABLE . ' d + LEFT JOIN ' . FORUMS_TABLE . ' f ON (f.forum_id = d.forum_id) + WHERE d.user_id = ' . $user->data['user_id'] . " + $sql_and + ORDER BY d.save_time DESC"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['topic_id']) + { + $topic_ids[] = (int) $row['topic_id']; + } + $draft_rows[] = $row; + } + $db->sql_freeresult($result); + + if (!count($draft_rows)) + { + return; + } + + $topic_rows = array(); + if (count($topic_ids)) + { + $sql = 'SELECT topic_id, forum_id, topic_title, topic_poster + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', array_unique($topic_ids)); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_rows[$row['topic_id']] = $row; + } + $db->sql_freeresult($result); + } + + /** + * Drafts found and their topics + * Edit $draft_rows in order to add or remove drafts loaded + * + * @event core.load_drafts_draft_list_result + * @var array draft_rows The drafts query result. Includes its forum id and everything about the draft + * @var array topic_ids The list of topics got from the topics table + * @var array topic_rows The topics that draft_rows references + * @since 3.1.0-RC3 + */ + $vars = array('draft_rows', 'topic_ids', 'topic_rows'); + extract($phpbb_dispatcher->trigger_event('core.load_drafts_draft_list_result', compact($vars))); + + unset($topic_ids); + + $template->assign_var('S_SHOW_DRAFTS', true); + + foreach ($draft_rows as $draft) + { + $link_topic = $link_forum = $link_pm = false; + $view_url = $title = ''; + + if (isset($topic_rows[$draft['topic_id']]) + && ( + ($topic_rows[$draft['topic_id']]['forum_id'] && $auth->acl_get('f_read', $topic_rows[$draft['topic_id']]['forum_id'])) + || + (!$topic_rows[$draft['topic_id']]['forum_id'] && $auth->acl_getf_global('f_read')) + )) + { + $topic_forum_id = ($topic_rows[$draft['topic_id']]['forum_id']) ? $topic_rows[$draft['topic_id']]['forum_id'] : $forum_id; + + $link_topic = true; + $view_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $topic_forum_id . '&t=' . $draft['topic_id']); + $title = $topic_rows[$draft['topic_id']]['topic_title']; + + $insert_url = append_sid("{$phpbb_root_path}posting.$phpEx", 'f=' . $topic_forum_id . '&t=' . $draft['topic_id'] . '&mode=reply&d=' . $draft['draft_id']); + } + else if ($draft['forum_id'] && $auth->acl_get('f_read', $draft['forum_id'])) + { + $link_forum = true; + $view_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $draft['forum_id']); + $title = $draft['forum_name']; + + $insert_url = append_sid("{$phpbb_root_path}posting.$phpEx", 'f=' . $draft['forum_id'] . '&mode=post&d=' . $draft['draft_id']); + } + else + { + // Either display as PM draft if forum_id and topic_id are empty or if access to the forums has been denied afterwards... + $link_pm = true; + $insert_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=$id&mode=compose&d={$draft['draft_id']}" . (($pm_action) ? "&action=$pm_action" : '') . (($msg_id) ? "&p=$msg_id" : '')); + } + + $template->assign_block_vars('draftrow', array( + 'DRAFT_ID' => $draft['draft_id'], + 'DATE' => $user->format_date($draft['save_time']), + 'DRAFT_SUBJECT' => $draft['draft_subject'], + + 'TITLE' => $title, + 'U_VIEW' => $view_url, + 'U_INSERT' => $insert_url, + + 'S_LINK_PM' => $link_pm, + 'S_LINK_TOPIC' => $link_topic, + 'S_LINK_FORUM' => $link_forum) + ); + } +} + +/** +* Topic Review +*/ +function topic_review($topic_id, $forum_id, $mode = 'topic_review', $cur_post_id = 0, $show_quote_button = true) +{ + global $user, $auth, $db, $template; + global $config, $phpbb_root_path, $phpEx, $phpbb_container, $phpbb_dispatcher; + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + $sql_sort = ($mode == 'post_review') ? 'ASC' : 'DESC'; + + // Go ahead and pull all data for this topic + $sql = 'SELECT p.post_id + FROM ' . POSTS_TABLE . ' p' . " + WHERE p.topic_id = $topic_id + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id, 'p.') . ' + ' . (($mode == 'post_review') ? " AND p.post_id > $cur_post_id" : '') . ' + ' . (($mode == 'post_review_edit') ? " AND p.post_id = $cur_post_id" : '') . ' + ORDER BY p.post_time ' . $sql_sort . ', p.post_id ' . $sql_sort; + $result = $db->sql_query_limit($sql, $config['posts_per_page']); + + $post_list = array(); + + while ($row = $db->sql_fetchrow($result)) + { + $post_list[] = $row['post_id']; + } + + $db->sql_freeresult($result); + + if (!count($post_list)) + { + return false; + } + + // Handle 'post_review_edit' like 'post_review' from now on + if ($mode == 'post_review_edit') + { + $mode = 'post_review'; + } + + $sql_ary = array( + 'SELECT' => 'u.username, u.user_id, u.user_colour, p.*, z.friend, z.foe, uu.username as post_delete_username, uu.user_colour as post_delete_user_colour', + + 'FROM' => array( + USERS_TABLE => 'u', + POSTS_TABLE => 'p', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(ZEBRA_TABLE => 'z'), + 'ON' => 'z.user_id = ' . $user->data['user_id'] . ' AND z.zebra_id = p.poster_id', + ), + array( + 'FROM' => array(USERS_TABLE => 'uu'), + 'ON' => 'uu.user_id = p.post_delete_user', + ), + ), + + 'WHERE' => $db->sql_in_set('p.post_id', $post_list) . ' + AND u.user_id = p.poster_id', + ); + + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + $rowset = array(); + $has_attachments = false; + while ($row = $db->sql_fetchrow($result)) + { + $rowset[$row['post_id']] = $row; + + if ($row['post_attachment']) + { + $has_attachments = true; + } + } + $db->sql_freeresult($result); + + // Grab extensions + $attachments = array(); + if ($has_attachments && $auth->acl_get('u_download') && $auth->acl_get('f_download', $forum_id)) + { + // Get attachments... + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_msg_id', $post_list) . ' + AND in_message = 0 + ORDER BY filetime DESC, post_msg_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[$row['post_msg_id']][] = $row; + } + $db->sql_freeresult($result); + } + + /** + * Event to modify the posts list for topic reviews + * + * @event core.topic_review_modify_post_list + * @var array attachments Array with the post attachments data + * @var int cur_post_id Post offset ID + * @var int forum_id The topic's forum ID + * @var string mode The topic review mode + * @var array post_list Array with the post IDs + * @var array rowset Array with the posts data + * @var bool show_quote_button Flag indicating if the quote button should be displayed + * @var int topic_id The topic ID that is being reviewed + * @since 3.1.9-RC1 + */ + $vars = array( + 'attachments', + 'cur_post_id', + 'forum_id', + 'mode', + 'post_list', + 'rowset', + 'show_quote_button', + 'topic_id', + ); + extract($phpbb_dispatcher->trigger_event('core.topic_review_modify_post_list', compact($vars))); + + for ($i = 0, $end = count($post_list); $i < $end; ++$i) + { + // A non-existing rowset only happens if there was no user present for the entered poster_id + // This could be a broken posts table. + if (!isset($rowset[$post_list[$i]])) + { + continue; + } + + $row = $rowset[$post_list[$i]]; + + $poster_id = $row['user_id']; + $post_subject = $row['post_subject']; + + $decoded_message = false; + + if ($show_quote_button && $auth->acl_get('f_reply', $forum_id)) + { + $decoded_message = censor_text($row['post_text']); + decode_message($decoded_message, $row['bbcode_uid']); + + $decoded_message = bbcode_nl2br($decoded_message); + } + + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); + $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); + $message = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, true); + + if (!empty($attachments[$row['post_id']])) + { + $update_count = array(); + parse_attachments($forum_id, $message, $attachments[$row['post_id']], $update_count); + } + + $post_subject = censor_text($post_subject); + + $post_anchor = ($mode == 'post_review') ? 'ppr' . $row['post_id'] : 'pr' . $row['post_id']; + $u_show_post = append_sid($phpbb_root_path . 'viewtopic.' . $phpEx, "f=$forum_id&t=$topic_id&p={$row['post_id']}&view=show#p{$row['post_id']}"); + + $l_deleted_message = ''; + if ($row['post_visibility'] == ITEM_DELETED) + { + $display_postername = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']); + + // User having deleted the post also being the post author? + if (!$row['post_delete_user'] || $row['post_delete_user'] == $poster_id) + { + $display_username = $display_postername; + } + else + { + $display_username = get_username_string('full', $row['post_delete_user'], $row['post_delete_username'], $row['post_delete_user_colour']); + } + + if ($row['post_delete_reason']) + { + $l_deleted_message = $user->lang('POST_DELETED_BY_REASON', $display_postername, $display_username, $user->format_date($row['post_delete_time'], false, true), $row['post_delete_reason']); + } + else + { + $l_deleted_message = $user->lang('POST_DELETED_BY', $display_postername, $display_username, $user->format_date($row['post_delete_time'], false, true)); + } + } + + $post_row = array( + 'POST_AUTHOR_FULL' => get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR_COLOUR' => get_username_string('colour', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR' => get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'U_POST_AUTHOR' => get_username_string('profile', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + + 'S_HAS_ATTACHMENTS' => (!empty($attachments[$row['post_id']])) ? true : false, + 'S_FRIEND' => ($row['friend']) ? true : false, + 'S_IGNORE_POST' => ($row['foe']) ? true : false, + 'L_IGNORE_POST' => ($row['foe']) ? sprintf($user->lang['POST_BY_FOE'], get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), "", '') : '', + 'S_POST_DELETED' => ($row['post_visibility'] == ITEM_DELETED) ? true : false, + 'L_DELETE_POST' => $l_deleted_message, + + 'POST_SUBJECT' => $post_subject, + 'MINI_POST_IMG' => $user->img('icon_post_target', $user->lang['POST']), + 'POST_DATE' => $user->format_date($row['post_time']), + 'MESSAGE' => $message, + 'DECODED_MESSAGE' => $decoded_message, + 'POST_ID' => $row['post_id'], + 'POST_TIME' => $row['post_time'], + 'USER_ID' => $row['user_id'], + 'U_MINI_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $row['post_id']) . '#p' . $row['post_id'], + 'U_MCP_DETAILS' => ($auth->acl_get('m_info', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&mode=post_details&f=' . $forum_id . '&p=' . $row['post_id'], true, $user->session_id) : '', + 'POSTER_QUOTE' => ($show_quote_button && $auth->acl_get('f_reply', $forum_id)) ? addslashes(get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['post_username'])) : '', + ); + + $current_row_number = $i; + + /** + * Event to modify the template data block for topic reviews + * + * @event core.topic_review_modify_row + * @var string mode The review mode + * @var int topic_id The topic that is being reviewed + * @var int forum_id The topic's forum + * @var int cur_post_id Post offset id + * @var int current_row_number Number of the current row being iterated + * @var array post_row Template block array of the current post + * @var array row Array with original post and user data + * @since 3.1.4-RC1 + */ + $vars = array( + 'mode', + 'topic_id', + 'forum_id', + 'cur_post_id', + 'current_row_number', + 'post_row', + 'row', + ); + extract($phpbb_dispatcher->trigger_event('core.topic_review_modify_row', compact($vars))); + + $template->assign_block_vars($mode . '_row', $post_row); + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (!empty($attachments[$row['post_id']])) + { + foreach ($attachments[$row['post_id']] as $attachment) + { + $template->assign_block_vars($mode . '_row.attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + } + + unset($rowset[$post_list[$i]]); + } + + if ($mode == 'topic_review') + { + $template->assign_var('QUOTE_IMG', $user->img('icon_post_quote', $user->lang['REPLY_WITH_QUOTE'])); + } + + return true; +} + +// +// Post handling functions +// + +/** +* Delete Post +*/ +function delete_post($forum_id, $topic_id, $post_id, &$data, $is_soft = false, $softdelete_reason = '') +{ + global $db, $user, $phpbb_container, $phpbb_dispatcher; + global $config, $phpEx, $phpbb_root_path; + + // Specify our post mode + $post_mode = 'delete'; + if (($data['topic_first_post_id'] === $data['topic_last_post_id']) && ($data['topic_posts_approved'] + $data['topic_posts_unapproved'] + $data['topic_posts_softdeleted'] == 1)) + { + $post_mode = 'delete_topic'; + } + else if ($data['topic_first_post_id'] == $post_id) + { + $post_mode = 'delete_first_post'; + } + else if ($data['topic_last_post_id'] == $post_id) + { + $post_mode = 'delete_last_post'; + } + $sql_data = array(); + $next_post_id = false; + + include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + + $db->sql_transaction('begin'); + + // we must make sure to update forums that contain the shadow'd topic + if ($post_mode == 'delete_topic') + { + $shadow_forum_ids = array(); + + $sql = 'SELECT forum_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_moved_id', $topic_id); + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) + { + if (!isset($shadow_forum_ids[(int) $row['forum_id']])) + { + $shadow_forum_ids[(int) $row['forum_id']] = 1; + } + else + { + $shadow_forum_ids[(int) $row['forum_id']]++; + } + } + $db->sql_freeresult($result); + } + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + // (Soft) delete the post + if ($is_soft && ($post_mode != 'delete_topic')) + { + $phpbb_content_visibility->set_post_visibility(ITEM_DELETED, $post_id, $topic_id, $forum_id, $user->data['user_id'], time(), $softdelete_reason, ($data['topic_first_post_id'] == $post_id), ($data['topic_last_post_id'] == $post_id)); + } + else if (!$is_soft) + { + if (!delete_posts('post_id', array($post_id), false, false, false)) + { + // Try to delete topic, we may had an previous error causing inconsistency + if ($post_mode == 'delete_topic') + { + delete_topics('topic_id', array($topic_id), false); + } + trigger_error('ALREADY_DELETED'); + } + } + + $db->sql_transaction('commit'); + + // Collect the necessary information for updating the tables + $sql_data[FORUMS_TABLE] = $sql_data[TOPICS_TABLE] = ''; + switch ($post_mode) + { + case 'delete_topic': + + foreach ($shadow_forum_ids as $updated_forum => $topic_count) + { + // counting is fun! we only have to do count($forum_ids) number of queries, + // even if the topic is moved back to where its shadow lives (we count how many times it is in a forum) + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET forum_topics_approved = forum_topics_approved - ' . $topic_count . ' + WHERE forum_id = ' . $updated_forum; + $db->sql_query($sql); + update_post_information('forum', $updated_forum); + } + + if ($is_soft) + { + $phpbb_content_visibility->set_topic_visibility(ITEM_DELETED, $topic_id, $forum_id, $user->data['user_id'], time(), $softdelete_reason); + } + else + { + delete_topics('topic_id', array($topic_id), false); + + $phpbb_content_visibility->remove_topic_from_statistic($data, $sql_data); + + $update_sql = update_post_information('forum', $forum_id, true); + if (count($update_sql)) + { + $sql_data[FORUMS_TABLE] .= ($sql_data[FORUMS_TABLE]) ? ', ' : ''; + $sql_data[FORUMS_TABLE] .= implode(', ', $update_sql[$forum_id]); + } + } + + break; + + case 'delete_first_post': + $sql = 'SELECT p.post_id, p.poster_id, p.post_time, p.post_username, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . " u + WHERE p.topic_id = $topic_id + AND p.poster_id = u.user_id + AND p.post_visibility = " . ITEM_APPROVED . ' + ORDER BY p.post_time ASC, p.post_id ASC'; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + // No approved post, so the first is a not-approved post (unapproved or soft deleted) + $sql = 'SELECT p.post_id, p.poster_id, p.post_time, p.post_username, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . " u + WHERE p.topic_id = $topic_id + AND p.poster_id = u.user_id + ORDER BY p.post_time ASC, p.post_id ASC"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + $next_post_id = (int) $row['post_id']; + + $sql_data[TOPICS_TABLE] = $db->sql_build_array('UPDATE', array( + 'topic_poster' => (int) $row['poster_id'], + 'topic_first_post_id' => (int) $row['post_id'], + 'topic_first_poster_colour' => $row['user_colour'], + 'topic_first_poster_name' => ($row['poster_id'] == ANONYMOUS) ? $row['post_username'] : $row['username'], + 'topic_time' => (int) $row['post_time'], + )); + break; + + case 'delete_last_post': + if (!$is_soft) + { + // Update last post information when hard deleting. Soft delete already did that by itself. + $update_sql = update_post_information('forum', $forum_id, true); + if (count($update_sql)) + { + $sql_data[FORUMS_TABLE] = (($sql_data[FORUMS_TABLE]) ? $sql_data[FORUMS_TABLE] . ', ' : '') . implode(', ', $update_sql[$forum_id]); + } + + $sql_data[TOPICS_TABLE] = (($sql_data[TOPICS_TABLE]) ? $sql_data[TOPICS_TABLE] . ', ' : '') . 'topic_bumped = 0, topic_bumper = 0'; + + $update_sql = update_post_information('topic', $topic_id, true); + if (!empty($update_sql)) + { + $sql_data[TOPICS_TABLE] .= ', ' . implode(', ', $update_sql[$topic_id]); + $next_post_id = (int) str_replace('topic_last_post_id = ', '', $update_sql[$topic_id][0]); + } + } + + if (!$next_post_id) + { + $sql = 'SELECT MAX(post_id) as last_post_id + FROM ' . POSTS_TABLE . " + WHERE topic_id = $topic_id + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id); + $result = $db->sql_query($sql); + $next_post_id = (int) $db->sql_fetchfield('last_post_id'); + $db->sql_freeresult($result); + } + break; + + case 'delete': + $sql = 'SELECT post_id + FROM ' . POSTS_TABLE . " + WHERE topic_id = $topic_id + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id) . ' + AND post_time > ' . $data['post_time'] . ' + ORDER BY post_time ASC, post_id ASC'; + $result = $db->sql_query_limit($sql, 1); + $next_post_id = (int) $db->sql_fetchfield('post_id'); + $db->sql_freeresult($result); + break; + } + + if (($post_mode == 'delete') || ($post_mode == 'delete_last_post') || ($post_mode == 'delete_first_post')) + { + if (!$is_soft) + { + $phpbb_content_visibility->remove_post_from_statistic($data, $sql_data); + } + + $sql = 'SELECT 1 AS has_attachments + FROM ' . ATTACHMENTS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query_limit($sql, 1); + $has_attachments = (int) $db->sql_fetchfield('has_attachments'); + $db->sql_freeresult($result); + + if (!$has_attachments) + { + $sql_data[TOPICS_TABLE] = (($sql_data[TOPICS_TABLE]) ? $sql_data[TOPICS_TABLE] . ', ' : '') . 'topic_attachment = 0'; + } + } + + $db->sql_transaction('begin'); + + $where_sql = array( + FORUMS_TABLE => "forum_id = $forum_id", + TOPICS_TABLE => "topic_id = $topic_id", + USERS_TABLE => 'user_id = ' . $data['poster_id'], + ); + + foreach ($sql_data as $table => $update_sql) + { + if ($update_sql) + { + $db->sql_query("UPDATE $table SET $update_sql WHERE " . $where_sql[$table]); + } + } + + // Adjust posted info for this user by looking for a post by him/her within this topic... + if ($post_mode != 'delete_topic' && $config['load_db_track'] && $data['poster_id'] != ANONYMOUS) + { + $sql = 'SELECT poster_id + FROM ' . POSTS_TABLE . ' + WHERE topic_id = ' . $topic_id . ' + AND poster_id = ' . $data['poster_id']; + $result = $db->sql_query_limit($sql, 1); + $poster_id = (int) $db->sql_fetchfield('poster_id'); + $db->sql_freeresult($result); + + // The user is not having any more posts within this topic + if (!$poster_id) + { + $sql = 'DELETE FROM ' . TOPICS_POSTED_TABLE . ' + WHERE topic_id = ' . $topic_id . ' + AND user_id = ' . $data['poster_id']; + $db->sql_query($sql); + } + } + + $db->sql_transaction('commit'); + + if ($data['post_reported'] && ($post_mode != 'delete_topic')) + { + sync('topic_reported', 'topic_id', array($topic_id)); + } + + /** + * This event is used for performing actions directly after a post or topic + * has been deleted. + * + * @event core.delete_post_after + * @var int forum_id Post forum ID + * @var int topic_id Post topic ID + * @var int post_id Post ID + * @var array data Post data + * @var bool is_soft Soft delete flag + * @var string softdelete_reason Soft delete reason + * @var string post_mode delete_topic, delete_first_post, delete_last_post or delete + * @var mixed next_post_id Next post ID in the topic (post ID or false) + * + * @since 3.1.11-RC1 + */ + $vars = array( + 'forum_id', + 'topic_id', + 'post_id', + 'data', + 'is_soft', + 'softdelete_reason', + 'post_mode', + 'next_post_id', + ); + extract($phpbb_dispatcher->trigger_event('core.delete_post_after', compact($vars))); + + return $next_post_id; +} + +/** +* Submit Post +* @todo Split up and create lightweight, simple API for this. +*/ +function submit_post($mode, $subject, $username, $topic_type, &$poll_ary, &$data_ary, $update_message = true, $update_search_index = true) +{ + global $db, $auth, $user, $config, $phpEx, $phpbb_root_path, $phpbb_container, $phpbb_dispatcher, $phpbb_log, $request; + + $poll = $poll_ary; + $data = $data_ary; + /** + * Modify the data for post submitting + * + * @event core.modify_submit_post_data + * @var string mode Variable containing posting mode value + * @var string subject Variable containing post subject value + * @var string username Variable containing post author name + * @var int topic_type Variable containing topic type value + * @var array poll Array with the poll data for the post + * @var array data Array with the data for the post + * @var bool update_message Flag indicating if the post will be updated + * @var bool update_search_index Flag indicating if the search index will be updated + * @since 3.1.0-a4 + */ + $vars = array( + 'mode', + 'subject', + 'username', + 'topic_type', + 'poll', + 'data', + 'update_message', + 'update_search_index', + ); + extract($phpbb_dispatcher->trigger_event('core.modify_submit_post_data', compact($vars))); + $poll_ary = $poll; + $data_ary = $data; + unset($poll); + unset($data); + + // We do not handle erasing posts here + if ($mode == 'delete') + { + return false; + } + + if (!empty($data_ary['post_time'])) + { + $current_time = $data_ary['post_time']; + } + else + { + $current_time = time(); + } + + if ($mode == 'post') + { + $post_mode = 'post'; + $update_message = true; + } + else if ($mode != 'edit') + { + $post_mode = 'reply'; + $update_message = true; + } + else if ($mode == 'edit') + { + $post_mode = ($data_ary['topic_posts_approved'] + $data_ary['topic_posts_unapproved'] + $data_ary['topic_posts_softdeleted'] == 1) ? 'edit_topic' : (($data_ary['topic_first_post_id'] == $data_ary['post_id']) ? 'edit_first_post' : (($data_ary['topic_last_post_id'] == $data_ary['post_id']) ? 'edit_last_post' : 'edit')); + } + + // First of all make sure the subject and topic title are having the correct length. + // To achieve this without cutting off between special chars we convert to an array and then count the elements. + $subject = truncate_string($subject, 120); + $data_ary['topic_title'] = truncate_string($data_ary['topic_title'], 120); + + // Collect some basic information about which tables and which rows to update/insert + $sql_data = $topic_row = array(); + $poster_id = ($mode == 'edit') ? $data_ary['poster_id'] : (int) $user->data['user_id']; + + // Retrieve some additional information if not present + if ($mode == 'edit' && (!isset($data_ary['post_visibility']) || !isset($data_ary['topic_visibility']) || $data_ary['post_visibility'] === false || $data_ary['topic_visibility'] === false)) + { + $sql = 'SELECT p.post_visibility, t.topic_type, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_visibility + FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . ' p + WHERE t.topic_id = p.topic_id + AND p.post_id = ' . $data_ary['post_id']; + $result = $db->sql_query($sql); + $topic_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $data_ary['topic_visibility'] = $topic_row['topic_visibility']; + $data_ary['post_visibility'] = $topic_row['post_visibility']; + } + + // This variable indicates if the user is able to post or put into the queue + $post_visibility = ITEM_APPROVED; + + // Check the permissions for post approval. + // Moderators must go through post approval like ordinary users. + if (!$auth->acl_get('f_noapprove', $data_ary['forum_id'])) + { + // Post not approved, but in queue + $post_visibility = ITEM_UNAPPROVED; + switch ($post_mode) + { + case 'edit_first_post': + case 'edit': + case 'edit_last_post': + case 'edit_topic': + $post_visibility = ITEM_REAPPROVE; + break; + } + } + else if (isset($data_ary['post_visibility']) && $data_ary['post_visibility'] !== false) + { + $post_visibility = $data_ary['post_visibility']; + } + + // MODs/Extensions are able to force any visibility on posts + if (isset($data_ary['force_approved_state'])) + { + $post_visibility = (in_array((int) $data_ary['force_approved_state'], array(ITEM_APPROVED, ITEM_UNAPPROVED, ITEM_DELETED, ITEM_REAPPROVE))) ? (int) $data_ary['force_approved_state'] : $post_visibility; + } + if (isset($data_ary['force_visibility'])) + { + $post_visibility = (in_array((int) $data_ary['force_visibility'], array(ITEM_APPROVED, ITEM_UNAPPROVED, ITEM_DELETED, ITEM_REAPPROVE))) ? (int) $data_ary['force_visibility'] : $post_visibility; + } + + // Start the transaction here + $db->sql_transaction('begin'); + + // Collect Information + switch ($post_mode) + { + case 'post': + case 'reply': + $sql_data[POSTS_TABLE]['sql'] = array( + 'forum_id' => $data_ary['forum_id'], + 'poster_id' => (int) $user->data['user_id'], + 'icon_id' => $data_ary['icon_id'], + 'poster_ip' => $user->ip, + 'post_time' => $current_time, + 'post_visibility' => $post_visibility, + 'enable_bbcode' => $data_ary['enable_bbcode'], + 'enable_smilies' => $data_ary['enable_smilies'], + 'enable_magic_url' => $data_ary['enable_urls'], + 'enable_sig' => $data_ary['enable_sig'], + 'post_username' => (!$user->data['is_registered']) ? $username : '', + 'post_subject' => $subject, + 'post_text' => $data_ary['message'], + 'post_checksum' => $data_ary['message_md5'], + 'post_attachment' => (!empty($data_ary['attachment_data'])) ? 1 : 0, + 'bbcode_bitfield' => $data_ary['bbcode_bitfield'], + 'bbcode_uid' => $data_ary['bbcode_uid'], + 'post_postcount' => ($auth->acl_get('f_postcount', $data_ary['forum_id'])) ? 1 : 0, + 'post_edit_locked' => $data_ary['post_edit_locked'] + ); + break; + + case 'edit_first_post': + case 'edit': + + case 'edit_last_post': + case 'edit_topic': + + // If edit reason is given always display edit info + + // If editing last post then display no edit info + // If m_edit permission then display no edit info + // If normal edit display edit info + + // Display edit info if edit reason given or user is editing his post, which is not the last within the topic. + if ($data_ary['post_edit_reason'] || (!$auth->acl_get('m_edit', $data_ary['forum_id']) && ($post_mode == 'edit' || $post_mode == 'edit_first_post'))) + { + $data_ary['post_edit_reason'] = truncate_string($data_ary['post_edit_reason'], 255, 255, false); + + $sql_data[POSTS_TABLE]['sql'] = array( + 'post_edit_time' => $current_time, + 'post_edit_reason' => $data_ary['post_edit_reason'], + 'post_edit_user' => (int) $data_ary['post_edit_user'], + ); + + $sql_data[POSTS_TABLE]['stat'][] = 'post_edit_count = post_edit_count + 1'; + } + else if (!$data_ary['post_edit_reason'] && $mode == 'edit' && $auth->acl_get('m_edit', $data_ary['forum_id'])) + { + $sql_data[POSTS_TABLE]['sql'] = array( + 'post_edit_reason' => '', + ); + } + + // If the person editing this post is different to the one having posted then we will add a log entry stating the edit + // Could be simplified by only adding to the log if the edit is not tracked - but this may confuse admins/mods + if ($user->data['user_id'] != $poster_id) + { + $log_subject = ($subject) ? $subject : $data_ary['topic_title']; + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_POST_EDITED', false, array( + 'forum_id' => $data_ary['forum_id'], + 'topic_id' => $data_ary['topic_id'], + 'post_id' => $data_ary['post_id'], + $log_subject, + (!empty($username)) ? $username : $user->lang['GUEST'], + $data_ary['post_edit_reason'] + )); + } + + if (!isset($sql_data[POSTS_TABLE]['sql'])) + { + $sql_data[POSTS_TABLE]['sql'] = array(); + } + + $sql_data[POSTS_TABLE]['sql'] = array_merge($sql_data[POSTS_TABLE]['sql'], array( + 'forum_id' => $data_ary['forum_id'], + 'poster_id' => $data_ary['poster_id'], + 'icon_id' => $data_ary['icon_id'], + // We will change the visibility later + //'post_visibility' => $post_visibility, + 'enable_bbcode' => $data_ary['enable_bbcode'], + 'enable_smilies' => $data_ary['enable_smilies'], + 'enable_magic_url' => $data_ary['enable_urls'], + 'enable_sig' => $data_ary['enable_sig'], + 'post_username' => ($username && $data_ary['poster_id'] == ANONYMOUS) ? $username : '', + 'post_subject' => $subject, + 'post_checksum' => $data_ary['message_md5'], + 'post_attachment' => (!empty($data_ary['attachment_data'])) ? 1 : 0, + 'bbcode_bitfield' => $data_ary['bbcode_bitfield'], + 'bbcode_uid' => $data_ary['bbcode_uid'], + 'post_edit_locked' => $data_ary['post_edit_locked']) + ); + + if ($update_message) + { + $sql_data[POSTS_TABLE]['sql']['post_text'] = $data_ary['message']; + } + + break; + } + + // And the topic ladies and gentlemen + switch ($post_mode) + { + case 'post': + $sql_data[TOPICS_TABLE]['sql'] = array( + 'topic_poster' => (int) $user->data['user_id'], + 'topic_time' => $current_time, + 'topic_last_view_time' => $current_time, + 'forum_id' => $data_ary['forum_id'], + 'icon_id' => $data_ary['icon_id'], + 'topic_posts_approved' => ($post_visibility == ITEM_APPROVED) ? 1 : 0, + 'topic_posts_softdeleted' => ($post_visibility == ITEM_DELETED) ? 1 : 0, + 'topic_posts_unapproved' => ($post_visibility == ITEM_UNAPPROVED) ? 1 : 0, + 'topic_visibility' => $post_visibility, + 'topic_delete_user' => ($post_visibility != ITEM_APPROVED) ? (int) $user->data['user_id'] : 0, + 'topic_title' => $subject, + 'topic_first_poster_name' => (!$user->data['is_registered'] && $username) ? $username : (($user->data['user_id'] != ANONYMOUS) ? $user->data['username'] : ''), + 'topic_first_poster_colour' => $user->data['user_colour'], + 'topic_type' => $topic_type, + 'topic_time_limit' => $topic_type != POST_NORMAL ? ($data_ary['topic_time_limit'] * 86400) : 0, + 'topic_attachment' => (!empty($data_ary['attachment_data'])) ? 1 : 0, + 'topic_status' => (isset($data_ary['topic_status'])) ? $data_ary['topic_status'] : ITEM_UNLOCKED, + ); + + if (isset($poll_ary['poll_options']) && !empty($poll_ary['poll_options'])) + { + $poll_start = ($poll_ary['poll_start']) ? $poll_ary['poll_start'] : $current_time; + $poll_length = $poll_ary['poll_length'] * 86400; + if ($poll_length < 0) + { + $poll_start = $poll_start + $poll_length; + if ($poll_start < 0) + { + $poll_start = 0; + } + $poll_length = 1; + } + + $sql_data[TOPICS_TABLE]['sql'] = array_merge($sql_data[TOPICS_TABLE]['sql'], array( + 'poll_title' => $poll_ary['poll_title'], + 'poll_start' => $poll_start, + 'poll_max_options' => $poll_ary['poll_max_options'], + 'poll_length' => $poll_length, + 'poll_vote_change' => $poll_ary['poll_vote_change']) + ); + } + + $sql_data[USERS_TABLE]['stat'][] = "user_lastpost_time = $current_time" . (($auth->acl_get('f_postcount', $data_ary['forum_id']) && $post_visibility == ITEM_APPROVED) ? ', user_posts = user_posts + 1' : ''); + + if ($post_visibility == ITEM_APPROVED) + { + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_topics_approved = forum_topics_approved + 1'; + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_posts_approved = forum_posts_approved + 1'; + } + else if ($post_visibility == ITEM_UNAPPROVED) + { + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_topics_unapproved = forum_topics_unapproved + 1'; + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_posts_unapproved = forum_posts_unapproved + 1'; + } + else if ($post_visibility == ITEM_DELETED) + { + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_topics_softdeleted = forum_topics_softdeleted + 1'; + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_posts_softdeleted = forum_posts_softdeleted + 1'; + } + break; + + case 'reply': + $sql_data[TOPICS_TABLE]['stat'][] = 'topic_last_view_time = ' . $current_time . ', + topic_bumped = 0, + topic_bumper = 0' . + (($post_visibility == ITEM_APPROVED) ? ', topic_posts_approved = topic_posts_approved + 1' : '') . + (($post_visibility == ITEM_UNAPPROVED) ? ', topic_posts_unapproved = topic_posts_unapproved + 1' : '') . + (($post_visibility == ITEM_DELETED) ? ', topic_posts_softdeleted = topic_posts_softdeleted + 1' : '') . + ((!empty($data_ary['attachment_data']) || (isset($data_ary['topic_attachment']) && $data_ary['topic_attachment'])) ? ', topic_attachment = 1' : ''); + + $sql_data[USERS_TABLE]['stat'][] = "user_lastpost_time = $current_time" . (($auth->acl_get('f_postcount', $data_ary['forum_id']) && $post_visibility == ITEM_APPROVED) ? ', user_posts = user_posts + 1' : ''); + + if ($post_visibility == ITEM_APPROVED) + { + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_posts_approved = forum_posts_approved + 1'; + } + else if ($post_visibility == ITEM_UNAPPROVED) + { + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_posts_unapproved = forum_posts_unapproved + 1'; + } + else if ($post_visibility == ITEM_DELETED) + { + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_posts_softdeleted = forum_posts_softdeleted + 1'; + } + break; + + case 'edit_topic': + case 'edit_first_post': + if (isset($poll_ary['poll_options'])) + { + $poll_start = ($poll_ary['poll_start'] || empty($poll_ary['poll_options'])) ? $poll_ary['poll_start'] : $current_time; + $poll_length = $poll_ary['poll_length'] * 86400; + if ($poll_length < 0) + { + $poll_start = $poll_start + $poll_length; + if ($poll_start < 0) + { + $poll_start = 0; + } + $poll_length = 1; + } + } + + $sql_data[TOPICS_TABLE]['sql'] = array( + 'forum_id' => $data_ary['forum_id'], + 'icon_id' => $data_ary['icon_id'], + 'topic_title' => $subject, + 'topic_first_poster_name' => $username, + 'topic_type' => $topic_type, + 'topic_time_limit' => $topic_type != POST_NORMAL ? ($data_ary['topic_time_limit'] * 86400) : 0, + 'poll_title' => (isset($poll_ary['poll_options'])) ? $poll_ary['poll_title'] : '', + 'poll_start' => (isset($poll_ary['poll_options'])) ? $poll_start : 0, + 'poll_max_options' => (isset($poll_ary['poll_options'])) ? $poll_ary['poll_max_options'] : 1, + 'poll_length' => (isset($poll_ary['poll_options'])) ? $poll_length : 0, + 'poll_vote_change' => (isset($poll_ary['poll_vote_change'])) ? $poll_ary['poll_vote_change'] : 0, + 'topic_last_view_time' => $current_time, + + 'topic_attachment' => (!empty($data_ary['attachment_data'])) ? 1 : (isset($data_ary['topic_attachment']) ? $data_ary['topic_attachment'] : 0), + ); + + break; + } + + $poll = $poll_ary; + $data = $data_ary; + /** + * Modify sql query data for post submitting + * + * @event core.submit_post_modify_sql_data + * @var array data Array with the data for the post + * @var array poll Array with the poll data for the post + * @var string post_mode Variable containing posting mode value + * @var bool sql_data Array with the data for the posting SQL query + * @var string subject Variable containing post subject value + * @var int topic_type Variable containing topic type value + * @var string username Variable containing post author name + * @since 3.1.3-RC1 + */ + $vars = array( + 'data', + 'poll', + 'post_mode', + 'sql_data', + 'subject', + 'topic_type', + 'username', + ); + extract($phpbb_dispatcher->trigger_event('core.submit_post_modify_sql_data', compact($vars))); + $poll_ary = $poll; + $data_ary = $data; + unset($poll); + unset($data); + + // Submit new topic + if ($post_mode == 'post') + { + $sql = 'INSERT INTO ' . TOPICS_TABLE . ' ' . + $db->sql_build_array('INSERT', $sql_data[TOPICS_TABLE]['sql']); + $db->sql_query($sql); + + $data_ary['topic_id'] = $db->sql_nextid(); + + $sql_data[POSTS_TABLE]['sql'] = array_merge($sql_data[POSTS_TABLE]['sql'], array( + 'topic_id' => $data_ary['topic_id']) + ); + unset($sql_data[TOPICS_TABLE]['sql']); + } + + // Submit new post + if ($post_mode == 'post' || $post_mode == 'reply') + { + if ($post_mode == 'reply') + { + $sql_data[POSTS_TABLE]['sql'] = array_merge($sql_data[POSTS_TABLE]['sql'], array( + 'topic_id' => $data_ary['topic_id'], + )); + } + + $sql = 'INSERT INTO ' . POSTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_data[POSTS_TABLE]['sql']); + $db->sql_query($sql); + $data_ary['post_id'] = $db->sql_nextid(); + + if ($post_mode == 'post' || $post_visibility == ITEM_APPROVED) + { + $sql_data[TOPICS_TABLE]['sql'] = array( + 'topic_last_post_id' => $data_ary['post_id'], + 'topic_last_post_time' => $current_time, + 'topic_last_poster_id' => $sql_data[POSTS_TABLE]['sql']['poster_id'], + 'topic_last_poster_name' => ($user->data['user_id'] == ANONYMOUS) ? $sql_data[POSTS_TABLE]['sql']['post_username'] : $user->data['username'], + 'topic_last_poster_colour' => $user->data['user_colour'], + 'topic_last_post_subject' => (string) $subject, + ); + } + + if ($post_mode == 'post') + { + $sql_data[TOPICS_TABLE]['sql']['topic_first_post_id'] = $data_ary['post_id']; + } + + // Update total post count and forum information + if ($post_visibility == ITEM_APPROVED) + { + if ($post_mode == 'post') + { + $config->increment('num_topics', 1, false); + } + $config->increment('num_posts', 1, false); + + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_last_post_id = ' . $data_ary['post_id']; + $sql_data[FORUMS_TABLE]['stat'][] = "forum_last_post_subject = '" . $db->sql_escape($subject) . "'"; + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_last_post_time = ' . $current_time; + $sql_data[FORUMS_TABLE]['stat'][] = 'forum_last_poster_id = ' . (int) $user->data['user_id']; + $sql_data[FORUMS_TABLE]['stat'][] = "forum_last_poster_name = '" . $db->sql_escape((!$user->data['is_registered'] && $username) ? $username : (($user->data['user_id'] != ANONYMOUS) ? $user->data['username'] : '')) . "'"; + $sql_data[FORUMS_TABLE]['stat'][] = "forum_last_poster_colour = '" . $db->sql_escape($user->data['user_colour']) . "'"; + } + + unset($sql_data[POSTS_TABLE]['sql']); + } + + // Update the topics table + if (isset($sql_data[TOPICS_TABLE]['sql'])) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_data[TOPICS_TABLE]['sql']) . ' + WHERE topic_id = ' . $data_ary['topic_id']; + $db->sql_query($sql); + + unset($sql_data[TOPICS_TABLE]['sql']); + } + + // Update the posts table + if (isset($sql_data[POSTS_TABLE]['sql'])) + { + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_data[POSTS_TABLE]['sql']) . ' + WHERE post_id = ' . $data_ary['post_id']; + $db->sql_query($sql); + + unset($sql_data[POSTS_TABLE]['sql']); + } + + // Update Poll Tables + if (isset($poll_ary['poll_options'])) + { + $cur_poll_options = array(); + + if ($mode == 'edit') + { + $sql = 'SELECT * + FROM ' . POLL_OPTIONS_TABLE . ' + WHERE topic_id = ' . $data_ary['topic_id'] . ' + ORDER BY poll_option_id'; + $result = $db->sql_query($sql); + + $cur_poll_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + $cur_poll_options[] = $row; + } + $db->sql_freeresult($result); + } + + $sql_insert_ary = array(); + + for ($i = 0, $size = count($poll_ary['poll_options']); $i < $size; $i++) + { + if (strlen(trim($poll_ary['poll_options'][$i]))) + { + if (empty($cur_poll_options[$i])) + { + // If we add options we need to put them to the end to be able to preserve votes... + $sql_insert_ary[] = array( + 'poll_option_id' => (int) count($cur_poll_options) + 1 + count($sql_insert_ary), + 'topic_id' => (int) $data_ary['topic_id'], + 'poll_option_text' => (string) $poll_ary['poll_options'][$i] + ); + } + else if ($poll_ary['poll_options'][$i] != $cur_poll_options[$i]) + { + $sql = 'UPDATE ' . POLL_OPTIONS_TABLE . " + SET poll_option_text = '" . $db->sql_escape($poll_ary['poll_options'][$i]) . "' + WHERE poll_option_id = " . $cur_poll_options[$i]['poll_option_id'] . ' + AND topic_id = ' . $data_ary['topic_id']; + $db->sql_query($sql); + } + } + } + + $db->sql_multi_insert(POLL_OPTIONS_TABLE, $sql_insert_ary); + + if (count($poll_ary['poll_options']) < count($cur_poll_options)) + { + $sql = 'DELETE FROM ' . POLL_OPTIONS_TABLE . ' + WHERE poll_option_id > ' . count($poll_ary['poll_options']) . ' + AND topic_id = ' . $data_ary['topic_id']; + $db->sql_query($sql); + } + + // If edited, we would need to reset votes (since options can be re-ordered above, you can't be sure if the change is for changing the text or adding an option + if ($mode == 'edit' && count($poll_ary['poll_options']) != count($cur_poll_options)) + { + $db->sql_query('DELETE FROM ' . POLL_VOTES_TABLE . ' WHERE topic_id = ' . $data_ary['topic_id']); + $db->sql_query('UPDATE ' . POLL_OPTIONS_TABLE . ' SET poll_option_total = 0 WHERE topic_id = ' . $data_ary['topic_id']); + } + } + + // Submit Attachments + if (!empty($data_ary['attachment_data']) && $data_ary['post_id'] && in_array($mode, array('post', 'reply', 'quote', 'edit'))) + { + $space_taken = $files_added = 0; + $orphan_rows = array(); + + foreach ($data_ary['attachment_data'] as $pos => $attach_row) + { + $orphan_rows[(int) $attach_row['attach_id']] = array(); + } + + if (count($orphan_rows)) + { + $sql = 'SELECT attach_id, filesize, physical_filename + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan_rows)) . ' + AND is_orphan = 1 + AND poster_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + + $orphan_rows = array(); + while ($row = $db->sql_fetchrow($result)) + { + $orphan_rows[$row['attach_id']] = $row; + } + $db->sql_freeresult($result); + } + + foreach ($data_ary['attachment_data'] as $pos => $attach_row) + { + if ($attach_row['is_orphan'] && !isset($orphan_rows[$attach_row['attach_id']])) + { + continue; + } + + if (!$attach_row['is_orphan']) + { + // update entry in db if attachment already stored in db and filespace + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . " + SET attach_comment = '" . $db->sql_escape($attach_row['attach_comment']) . "' + WHERE attach_id = " . (int) $attach_row['attach_id'] . ' + AND is_orphan = 0'; + $db->sql_query($sql); + } + else + { + // insert attachment into db + if (!@file_exists($phpbb_root_path . $config['upload_path'] . '/' . utf8_basename($orphan_rows[$attach_row['attach_id']]['physical_filename']))) + { + continue; + } + + $space_taken += $orphan_rows[$attach_row['attach_id']]['filesize']; + $files_added++; + + $attach_sql = array( + 'post_msg_id' => $data_ary['post_id'], + 'topic_id' => $data_ary['topic_id'], + 'is_orphan' => 0, + 'poster_id' => $poster_id, + 'attach_comment' => $attach_row['attach_comment'], + ); + + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $attach_sql) . ' + WHERE attach_id = ' . $attach_row['attach_id'] . ' + AND is_orphan = 1 + AND poster_id = ' . $user->data['user_id']; + $db->sql_query($sql); + } + } + + if ($space_taken && $files_added) + { + $config->increment('upload_dir_size', $space_taken, false); + $config->increment('num_files', $files_added, false); + } + } + + $first_post_has_topic_info = ($post_mode == 'edit_first_post' && + (($post_visibility == ITEM_DELETED && $data_ary['topic_posts_softdeleted'] == 1) || + ($post_visibility == ITEM_UNAPPROVED && $data_ary['topic_posts_unapproved'] == 1) || + ($post_visibility == ITEM_REAPPROVE && $data_ary['topic_posts_unapproved'] == 1) || + ($post_visibility == ITEM_APPROVED && $data_ary['topic_posts_approved'] == 1))); + // Fix the post's and topic's visibility and first/last post information, when the post is edited + if (($post_mode != 'post' && $post_mode != 'reply') && $data_ary['post_visibility'] != $post_visibility) + { + // If the post was not approved, it could also be the starter, + // so we sync the starter after approving/restoring, to ensure that the stats are correct + // Same applies for the last post + $is_starter = ($post_mode == 'edit_first_post' || $post_mode == 'edit_topic' || $data_ary['post_visibility'] != ITEM_APPROVED); + $is_latest = ($post_mode == 'edit_last_post' || $post_mode == 'edit_topic' || $data_ary['post_visibility'] != ITEM_APPROVED); + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + $phpbb_content_visibility->set_post_visibility($post_visibility, $data_ary['post_id'], $data_ary['topic_id'], $data_ary['forum_id'], $user->data['user_id'], time(), '', $is_starter, $is_latest); + } + else if ($post_mode == 'edit_last_post' || $post_mode == 'edit_topic' || $first_post_has_topic_info) + { + if ($post_visibility == ITEM_APPROVED || $data_ary['topic_visibility'] == $post_visibility) + { + // only the subject can be changed from edit + $sql_data[TOPICS_TABLE]['stat'][] = "topic_last_post_subject = '" . $db->sql_escape($subject) . "'"; + + // Maybe not only the subject, but also changing anonymous usernames. ;) + if ($data_ary['poster_id'] == ANONYMOUS) + { + $sql_data[TOPICS_TABLE]['stat'][] = "topic_last_poster_name = '" . $db->sql_escape($username) . "'"; + } + + if ($post_visibility == ITEM_APPROVED) + { + // this does not _necessarily_ mean that we must update the info again, + // it just means that we might have to + $sql = 'SELECT forum_last_post_id, forum_last_post_subject + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . (int) $data_ary['forum_id']; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // this post is the latest post in the forum, better update + if ($row['forum_last_post_id'] == $data_ary['post_id'] && ($row['forum_last_post_subject'] !== $subject || $data_ary['poster_id'] == ANONYMOUS)) + { + // the post's subject changed + if ($row['forum_last_post_subject'] !== $subject) + { + $sql_data[FORUMS_TABLE]['stat'][] = "forum_last_post_subject = '" . $db->sql_escape($subject) . "'"; + } + + // Update the user name if poster is anonymous... just in case a moderator changed it + if ($data_ary['poster_id'] == ANONYMOUS) + { + $sql_data[FORUMS_TABLE]['stat'][] = "forum_last_poster_name = '" . $db->sql_escape($username) . "'"; + } + } + } + } + } + + // Update forum stats + $where_sql = array( + POSTS_TABLE => 'post_id = ' . $data_ary['post_id'], + TOPICS_TABLE => 'topic_id = ' . $data_ary['topic_id'], + FORUMS_TABLE => 'forum_id = ' . $data_ary['forum_id'], + USERS_TABLE => 'user_id = ' . $poster_id + ); + + foreach ($sql_data as $table => $update_ary) + { + if (isset($update_ary['stat']) && implode('', $update_ary['stat'])) + { + $sql = "UPDATE $table SET " . implode(', ', $update_ary['stat']) . ' WHERE ' . $where_sql[$table]; + $db->sql_query($sql); + } + } + + // Delete topic shadows (if any exist). We do not need a shadow topic for an global announcement + if ($topic_type == POST_GLOBAL) + { + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE topic_moved_id = ' . $data_ary['topic_id']; + $db->sql_query($sql); + } + + // Committing the transaction before updating search index + $db->sql_transaction('commit'); + + // Delete draft if post was loaded... + $draft_id = $request->variable('draft_loaded', 0); + if ($draft_id) + { + $sql = 'DELETE FROM ' . DRAFTS_TABLE . " + WHERE draft_id = $draft_id + AND user_id = {$user->data['user_id']}"; + $db->sql_query($sql); + } + + // Index message contents + if ($update_search_index && $data_ary['enable_indexing']) + { + // Select the search method and do some additional checks to ensure it can actually be utilised + $search_type = $config['search_type']; + + if (!class_exists($search_type)) + { + trigger_error('NO_SUCH_SEARCH_MODULE'); + } + + $error = false; + $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher); + + if ($error) + { + trigger_error($error); + } + + $search->index($mode, $data_ary['post_id'], $data_ary['message'], $subject, $poster_id, $data_ary['forum_id']); + } + + // Topic Notification, do not change if moderator is changing other users posts... + if ($user->data['user_id'] == $poster_id) + { + if (!$data_ary['notify_set'] && $data_ary['notify']) + { + $sql = 'INSERT INTO ' . TOPICS_WATCH_TABLE . ' (user_id, topic_id) + VALUES (' . $user->data['user_id'] . ', ' . $data_ary['topic_id'] . ')'; + $db->sql_query($sql); + } + else if (($config['email_enable'] || $config['jab_enable']) && $data_ary['notify_set'] && !$data_ary['notify']) + { + $sql = 'DELETE FROM ' . TOPICS_WATCH_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . ' + AND topic_id = ' . $data_ary['topic_id']; + $db->sql_query($sql); + } + } + + if ($mode == 'post' || $mode == 'reply' || $mode == 'quote') + { + // Mark this topic as posted to + markread('post', $data_ary['forum_id'], $data_ary['topic_id']); + } + + // Mark this topic as read + // We do not use post_time here, this is intended (post_time can have a date in the past if editing a message) + markread('topic', $data_ary['forum_id'], $data_ary['topic_id'], time()); + + // + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $sql = 'SELECT mark_time + FROM ' . FORUMS_TRACK_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . ' + AND forum_id = ' . $data_ary['forum_id']; + $result = $db->sql_query($sql); + $f_mark_time = (int) $db->sql_fetchfield('mark_time'); + $db->sql_freeresult($result); + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $f_mark_time = false; + } + + if (($config['load_db_lastread'] && $user->data['is_registered']) || $config['load_anon_lastread'] || $user->data['is_registered']) + { + // Update forum info + $sql = 'SELECT forum_last_post_time + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $data_ary['forum_id']; + $result = $db->sql_query($sql); + $forum_last_post_time = (int) $db->sql_fetchfield('forum_last_post_time'); + $db->sql_freeresult($result); + + update_forum_tracking_info($data_ary['forum_id'], $forum_last_post_time, $f_mark_time, false); + } + + // If a username was supplied or the poster is a guest, we will use the supplied username. + // Doing it this way we can use "...post by guest-username..." in notifications when + // "guest-username" is supplied or ommit the username if it is not. + $username = ($username !== '' || !$user->data['is_registered']) ? $username : $user->data['username']; + + // Send Notifications + $notification_data = array_merge($data_ary, array( + 'topic_title' => (isset($data_ary['topic_title'])) ? $data_ary['topic_title'] : $subject, + 'post_username' => $username, + 'poster_id' => $poster_id, + 'post_text' => $data_ary['message'], + 'post_time' => $current_time, + 'post_subject' => $subject, + )); + + /** + * This event allows you to modify the notification data upon submission + * + * @event core.modify_submit_notification_data + * @var array notification_data The notification data to be inserted in to the database + * @var array data_ary The data array with a lot of the post submission data + * @var string mode The posting mode + * @var int poster_id The poster id + * @since 3.2.4-RC1 + */ + $vars = array('notification_data', 'data_ary', 'mode', 'poster_id'); + extract($phpbb_dispatcher->trigger_event('core.modify_submit_notification_data', compact($vars))); + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + if ($post_visibility == ITEM_APPROVED) + { + switch ($mode) + { + case 'post': + $phpbb_notifications->add_notifications(array( + 'notification.type.quote', + 'notification.type.topic', + ), $notification_data); + break; + + case 'reply': + case 'quote': + $phpbb_notifications->add_notifications(array( + 'notification.type.quote', + 'notification.type.bookmark', + 'notification.type.post', + ), $notification_data); + break; + + case 'edit_topic': + case 'edit_first_post': + case 'edit': + case 'edit_last_post': + if ($user->data['user_id'] == $poster_id) + { + $phpbb_notifications->update_notifications(array( + 'notification.type.quote', + ), $notification_data); + } + + $phpbb_notifications->update_notifications(array( + 'notification.type.bookmark', + 'notification.type.topic', + 'notification.type.post', + ), $notification_data); + break; + } + } + else if ($post_visibility == ITEM_UNAPPROVED) + { + switch ($mode) + { + case 'post': + $phpbb_notifications->add_notifications('notification.type.topic_in_queue', $notification_data); + break; + + case 'reply': + case 'quote': + $phpbb_notifications->add_notifications('notification.type.post_in_queue', $notification_data); + break; + + case 'edit_topic': + case 'edit_first_post': + case 'edit': + case 'edit_last_post': + // Nothing to do here + break; + } + } + else if ($post_visibility == ITEM_REAPPROVE) + { + switch ($mode) + { + case 'edit_topic': + case 'edit_first_post': + $phpbb_notifications->add_notifications('notification.type.topic_in_queue', $notification_data); + + // Delete the approve_post notification so we can notify the user again, + // when his post got reapproved + $phpbb_notifications->delete_notifications('notification.type.approve_post', $notification_data['post_id']); + break; + + case 'edit': + case 'edit_last_post': + $phpbb_notifications->add_notifications('notification.type.post_in_queue', $notification_data); + + // Delete the approve_post notification so we can notify the user again, + // when his post got reapproved + $phpbb_notifications->delete_notifications('notification.type.approve_post', $notification_data['post_id']); + break; + + case 'post': + case 'reply': + case 'quote': + // Nothing to do here + break; + } + } + else if ($post_visibility == ITEM_DELETED) + { + switch ($mode) + { + case 'post': + case 'reply': + case 'quote': + case 'edit_topic': + case 'edit_first_post': + case 'edit': + case 'edit_last_post': + // Nothing to do here + break; + } + } + + $params = $add_anchor = ''; + + if ($post_visibility == ITEM_APPROVED || + ($auth->acl_get('m_softdelete', $data_ary['forum_id']) && $post_visibility == ITEM_DELETED) || + ($auth->acl_get('m_approve', $data_ary['forum_id']) && in_array($post_visibility, array(ITEM_UNAPPROVED, ITEM_REAPPROVE)))) + { + $params .= '&t=' . $data_ary['topic_id']; + + if ($mode != 'post') + { + $params .= '&p=' . $data_ary['post_id']; + $add_anchor = '#p' . $data_ary['post_id']; + } + } + else if ($mode != 'post' && $post_mode != 'edit_first_post' && $post_mode != 'edit_topic') + { + $params .= '&t=' . $data_ary['topic_id']; + } + + $url = (!$params) ? "{$phpbb_root_path}viewforum.$phpEx" : "{$phpbb_root_path}viewtopic.$phpEx"; + $url = append_sid($url, 'f=' . $data_ary['forum_id'] . $params) . $add_anchor; + + $poll = $poll_ary; + $data = $data_ary; + /** + * This event is used for performing actions directly after a post or topic + * has been submitted. When a new topic is posted, the topic ID is + * available in the $data array. + * + * The only action that can be done by altering data made available to this + * event is to modify the return URL ($url). + * + * @event core.submit_post_end + * @var string mode Variable containing posting mode value + * @var string subject Variable containing post subject value + * @var string username Variable containing post author name + * @var int topic_type Variable containing topic type value + * @var array poll Array with the poll data for the post + * @var array data Array with the data for the post + * @var int post_visibility Variable containing up to date post visibility + * @var bool update_message Flag indicating if the post will be updated + * @var bool update_search_index Flag indicating if the search index will be updated + * @var string url The "Return to topic" URL + * + * @since 3.1.0-a3 + * @changed 3.1.0-RC3 Added vars mode, subject, username, topic_type, + * poll, update_message, update_search_index + */ + $vars = array( + 'mode', + 'subject', + 'username', + 'topic_type', + 'poll', + 'data', + 'post_visibility', + 'update_message', + 'update_search_index', + 'url', + ); + extract($phpbb_dispatcher->trigger_event('core.submit_post_end', compact($vars))); + $data_ary = $data; + $poll_ary = $poll; + unset($data); + unset($poll); + + return $url; +} + +/** +* Handle topic bumping +* @param int $forum_id The ID of the forum the topic is being bumped belongs to +* @param int $topic_id The ID of the topic is being bumping +* @param array $post_data Passes some topic parameters: +* - 'topic_title' +* - 'topic_last_post_id' +* - 'topic_last_poster_id' +* - 'topic_last_post_subject' +* - 'topic_last_poster_name' +* - 'topic_last_poster_colour' +* @param int $bump_time The time at which topic was bumped, usually it is a current time as obtained via time(). +* @return string An URL to the bumped topic, example: ./viewtopic.php?forum_id=1&topic_id=2&p=3#p3 +*/ +function phpbb_bump_topic($forum_id, $topic_id, $post_data, $bump_time = false) +{ + global $config, $db, $user, $phpEx, $phpbb_root_path, $phpbb_log; + + if ($bump_time === false) + { + $bump_time = time(); + } + + // Begin bumping + $db->sql_transaction('begin'); + + // Update the topic's last post post_time + $sql = 'UPDATE ' . POSTS_TABLE . " + SET post_time = $bump_time + WHERE post_id = {$post_data['topic_last_post_id']} + AND topic_id = $topic_id"; + $db->sql_query($sql); + + // Sync the topic's last post time, the rest of the topic's last post data isn't changed + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_last_post_time = $bump_time, + topic_bumped = 1, + topic_bumper = " . $user->data['user_id'] . " + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + + // Update the forum's last post info + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET forum_last_post_id = " . $post_data['topic_last_post_id'] . ", + forum_last_poster_id = " . $post_data['topic_last_poster_id'] . ", + forum_last_post_subject = '" . $db->sql_escape($post_data['topic_last_post_subject']) . "', + forum_last_post_time = $bump_time, + forum_last_poster_name = '" . $db->sql_escape($post_data['topic_last_poster_name']) . "', + forum_last_poster_colour = '" . $db->sql_escape($post_data['topic_last_poster_colour']) . "' + WHERE forum_id = $forum_id"; + $db->sql_query($sql); + + // Update bumper's time of the last posting to prevent flood + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastpost_time = $bump_time + WHERE user_id = " . $user->data['user_id']; + $db->sql_query($sql); + + $db->sql_transaction('commit'); + + // Mark this topic as posted to + markread('post', $forum_id, $topic_id, $bump_time); + + // Mark this topic as read + markread('topic', $forum_id, $topic_id, $bump_time); + + // Update forum tracking info + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $sql = 'SELECT mark_time + FROM ' . FORUMS_TRACK_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . ' + AND forum_id = ' . $forum_id; + $result = $db->sql_query($sql); + $f_mark_time = (int) $db->sql_fetchfield('mark_time'); + $db->sql_freeresult($result); + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $f_mark_time = false; + } + + if (($config['load_db_lastread'] && $user->data['is_registered']) || $config['load_anon_lastread'] || $user->data['is_registered']) + { + // Update forum info + $sql = 'SELECT forum_last_post_time + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $forum_id; + $result = $db->sql_query($sql); + $forum_last_post_time = (int) $db->sql_fetchfield('forum_last_post_time'); + $db->sql_freeresult($result); + + update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time, false); + } + + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_BUMP_TOPIC', false, array( + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + $post_data['topic_title'] + )); + + $url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&p={$post_data['topic_last_post_id']}") . "#p{$post_data['topic_last_post_id']}"; + + return $url; +} + +/** +* Show upload popup (progress bar) +*/ +function phpbb_upload_popup($forum_style = 0) +{ + global $template, $user; + + ($forum_style) ? $user->setup('posting', $forum_style) : $user->setup('posting'); + + page_header($user->lang['PROGRESS_BAR']); + + $template->set_filenames(array( + 'popup' => 'posting_progress_bar.html') + ); + + $template->assign_vars(array( + 'PROGRESS_BAR' => $user->img('upload_bar', $user->lang['UPLOAD_IN_PROGRESS'])) + ); + + $template->display('popup'); + + garbage_collection(); + exit_handler(); +} + +/** +* Do the various checks required for removing posts as well as removing it +* +* @param int $forum_id The id of the forum +* @param int $topic_id The id of the topic +* @param int $post_id The id of the post +* @param array $post_data Array with the post data +* @param bool $is_soft The flag indicating whether it is the soft delete mode +* @param string $delete_reason Description for the post deletion reason +* +* @return null +*/ +function phpbb_handle_post_delete($forum_id, $topic_id, $post_id, &$post_data, $is_soft = false, $delete_reason = '') +{ + global $user, $auth, $config, $request; + global $phpbb_root_path, $phpEx, $phpbb_log, $phpbb_dispatcher; + + $force_delete_allowed = $force_softdelete_allowed = false; + $perm_check = ($is_soft) ? 'softdelete' : 'delete'; + + /** + * This event allows to modify the conditions for the post deletion + * + * @event core.handle_post_delete_conditions + * @var int forum_id The id of the forum + * @var int topic_id The id of the topic + * @var int post_id The id of the post + * @var array post_data Array with the post data + * @var bool is_soft The flag indicating whether it is the soft delete mode + * @var string delete_reason Description for the post deletion reason + * @var bool force_delete_allowed Allow the user to delete the post (all permissions and conditions are ignored) + * @var bool force_softdelete_allowed Allow the user to softdelete the post (all permissions and conditions are ignored) + * @var string perm_check The deletion mode softdelete|delete + * @since 3.1.11-RC1 + */ + $vars = array( + 'forum_id', + 'topic_id', + 'post_id', + 'post_data', + 'is_soft', + 'delete_reason', + 'force_delete_allowed', + 'force_softdelete_allowed', + 'perm_check', + ); + extract($phpbb_dispatcher->trigger_event('core.handle_post_delete_conditions', compact($vars))); + + // If moderator removing post or user itself removing post, present a confirmation screen + if ($force_delete_allowed || ($is_soft && $force_softdelete_allowed) || $auth->acl_get("m_$perm_check", $forum_id) || ($post_data['poster_id'] == $user->data['user_id'] && $user->data['is_registered'] && $auth->acl_get("f_$perm_check", $forum_id) && $post_id == $post_data['topic_last_post_id'] && !$post_data['post_edit_locked'] && ($post_data['post_time'] > time() - ($config['delete_time'] * 60) || !$config['delete_time']))) + { + $s_hidden_fields = array( + 'p' => $post_id, + 'f' => $forum_id, + 'mode' => ($is_soft) ? 'soft_delete' : 'delete', + ); + + if (confirm_box(true)) + { + $data = array( + 'topic_first_post_id' => $post_data['topic_first_post_id'], + 'topic_last_post_id' => $post_data['topic_last_post_id'], + 'topic_posts_approved' => $post_data['topic_posts_approved'], + 'topic_posts_unapproved' => $post_data['topic_posts_unapproved'], + 'topic_posts_softdeleted' => $post_data['topic_posts_softdeleted'], + 'topic_visibility' => $post_data['topic_visibility'], + 'topic_type' => $post_data['topic_type'], + 'post_visibility' => $post_data['post_visibility'], + 'post_reported' => $post_data['post_reported'], + 'post_time' => $post_data['post_time'], + 'poster_id' => $post_data['poster_id'], + 'post_postcount' => $post_data['post_postcount'], + ); + + $next_post_id = delete_post($forum_id, $topic_id, $post_id, $data, $is_soft, $delete_reason); + $post_username = ($post_data['poster_id'] == ANONYMOUS && !empty($post_data['post_username'])) ? $post_data['post_username'] : $post_data['username']; + + if ($next_post_id === false) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, (($is_soft) ? 'LOG_SOFTDELETE_TOPIC' : 'LOG_DELETE_TOPIC'), false, array( + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + $post_data['topic_title'], + $post_username, + $delete_reason + )); + + $meta_info = append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id"); + $message = $user->lang['POST_DELETED']; + } + else + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, (($is_soft) ? 'LOG_SOFTDELETE_POST' : 'LOG_DELETE_POST'), false, array( + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + $post_data['post_subject'], + $post_username, + $delete_reason + )); + + $meta_info = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&p=$next_post_id") . "#p$next_post_id"; + $message = $user->lang['POST_DELETED']; + + if (!$request->is_ajax()) + { + $message .= '

' . $user->lang('RETURN_TOPIC', '', ''); + } + } + + meta_refresh(3, $meta_info); + if (!$request->is_ajax()) + { + $message .= '

' . $user->lang('RETURN_FORUM', '', ''); + } + trigger_error($message); + } + else + { + global $template; + + $can_delete = $force_delete_allowed || ($auth->acl_get('m_delete', $forum_id) || ($post_data['poster_id'] == $user->data['user_id'] && $user->data['is_registered'] && $auth->acl_get('f_delete', $forum_id))); + $can_softdelete = $force_softdelete_allowed || ($auth->acl_get('m_softdelete', $forum_id) || ($post_data['poster_id'] == $user->data['user_id'] && $user->data['is_registered'] && $auth->acl_get('f_softdelete', $forum_id))); + + $template->assign_vars(array( + 'S_SOFTDELETED' => $post_data['post_visibility'] == ITEM_DELETED, + 'S_CHECKED_PERMANENT' => $request->is_set_post('delete_permanent') ? ' checked="checked"' : '', + 'S_ALLOWED_DELETE' => $can_delete, + 'S_ALLOWED_SOFTDELETE' => $can_softdelete, + )); + + $l_confirm = 'DELETE_POST'; + if ($post_data['post_visibility'] == ITEM_DELETED) + { + $l_confirm .= '_PERMANENTLY'; + $s_hidden_fields['delete_permanent'] = '1'; + } + else if (!$can_softdelete) + { + $s_hidden_fields['delete_permanent'] = '1'; + } + + confirm_box(false, $l_confirm, build_hidden_fields($s_hidden_fields), 'confirm_delete_body.html'); + } + } + + // If we are here the user is not able to delete - present the correct error message + if ($post_data['poster_id'] != $user->data['user_id'] && $auth->acl_get('f_delete', $forum_id)) + { + trigger_error('DELETE_OWN_POSTS'); + } + + if ($post_data['poster_id'] == $user->data['user_id'] && $auth->acl_get('f_delete', $forum_id) && $post_id != $post_data['topic_last_post_id']) + { + trigger_error('CANNOT_DELETE_REPLIED'); + } + + trigger_error('USER_CANNOT_DELETE'); +} diff --git a/includes/functions_privmsgs.php b/includes/functions_privmsgs.php new file mode 100644 index 0000000..444bf2c --- /dev/null +++ b/includes/functions_privmsgs.php @@ -0,0 +1,2264 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/* + Ability to simply add own rules by doing three things: + 1) Add an appropriate constant + 2) Add a new check array to the global_privmsgs_rules variable and the condition array (if one is required) + 3) Implement the rule logic in the check_rule() function + 4) Add a new language variable to ucp.php + + The user is then able to select the new rule. It will be checked against and handled as specified. + To add new actions (yes, checks can be added here too) to the rule management, the core code has to be modified. +*/ + +define('RULE_IS_LIKE', 1); // Is Like +define('RULE_IS_NOT_LIKE', 2); // Is Not Like +define('RULE_IS', 3); // Is +define('RULE_IS_NOT', 4); // Is Not +define('RULE_BEGINS_WITH', 5); // Begins with +define('RULE_ENDS_WITH', 6); // Ends with +define('RULE_IS_FRIEND', 7); // Is Friend +define('RULE_IS_FOE', 8); // Is Foe +define('RULE_IS_USER', 9); // Is User +define('RULE_IS_GROUP', 10); // Is In Usergroup +define('RULE_ANSWERED', 11); // Answered +define('RULE_FORWARDED', 12); // Forwarded +define('RULE_TO_GROUP', 14); // Usergroup +define('RULE_TO_ME', 15); // Me + +define('ACTION_PLACE_INTO_FOLDER', 1); +define('ACTION_MARK_AS_READ', 2); +define('ACTION_MARK_AS_IMPORTANT', 3); +define('ACTION_DELETE_MESSAGE', 4); + +define('CHECK_SUBJECT', 1); +define('CHECK_SENDER', 2); +define('CHECK_MESSAGE', 3); +define('CHECK_STATUS', 4); +define('CHECK_TO', 5); + +/** +* Global private message rules +* These rules define what to do if a rule is hit +*/ +$global_privmsgs_rules = array( + CHECK_SUBJECT => array( + RULE_IS_LIKE => array('check0' => 'message_subject'), + RULE_IS_NOT_LIKE => array('check0' => 'message_subject'), + RULE_IS => array('check0' => 'message_subject'), + RULE_IS_NOT => array('check0' => 'message_subject'), + RULE_BEGINS_WITH => array('check0' => 'message_subject'), + RULE_ENDS_WITH => array('check0' => 'message_subject'), + ), + + CHECK_SENDER => array( + RULE_IS_LIKE => array('check0' => 'username'), + RULE_IS_NOT_LIKE => array('check0' => 'username'), + RULE_IS => array('check0' => 'username'), + RULE_IS_NOT => array('check0' => 'username'), + RULE_BEGINS_WITH => array('check0' => 'username'), + RULE_ENDS_WITH => array('check0' => 'username'), + RULE_IS_FRIEND => array('check0' => 'friend'), + RULE_IS_FOE => array('check0' => 'foe'), + RULE_IS_USER => array('check0' => 'author_id'), + RULE_IS_GROUP => array('check0' => 'author_in_group'), + ), + + CHECK_MESSAGE => array( + RULE_IS_LIKE => array('check0' => 'message_text'), + RULE_IS_NOT_LIKE => array('check0' => 'message_text'), + RULE_IS => array('check0' => 'message_text'), + RULE_IS_NOT => array('check0' => 'message_text'), + ), + + CHECK_STATUS => array( + RULE_ANSWERED => array('check0' => 'pm_replied'), + RULE_FORWARDED => array('check0' => 'pm_forwarded'), + ), + + CHECK_TO => array( + RULE_TO_GROUP => array('check0' => 'to', 'check1' => 'bcc', 'check2' => 'user_in_group'), + RULE_TO_ME => array('check0' => 'to', 'check1' => 'bcc'), + ) +); + +/** +* This is for defining which condition fields to show for which Rule +*/ +$global_rule_conditions = array( + RULE_IS_LIKE => 'text', + RULE_IS_NOT_LIKE => 'text', + RULE_IS => 'text', + RULE_IS_NOT => 'text', + RULE_BEGINS_WITH => 'text', + RULE_ENDS_WITH => 'text', + RULE_IS_USER => 'user', + RULE_IS_GROUP => 'group' +); + +/** +* Get all folder +*/ +function get_folder($user_id, $folder_id = false) +{ + global $db, $user, $template; + global $phpbb_root_path, $phpEx; + + $folder = array(); + + // Get folder information + $sql = 'SELECT folder_id, COUNT(msg_id) as num_messages, SUM(pm_unread) as num_unread + FROM ' . PRIVMSGS_TO_TABLE . " + WHERE user_id = $user_id + AND folder_id <> " . PRIVMSGS_NO_BOX . ' + GROUP BY folder_id'; + $result = $db->sql_query($sql); + + $num_messages = $num_unread = array(); + while ($row = $db->sql_fetchrow($result)) + { + $num_messages[(int) $row['folder_id']] = $row['num_messages']; + $num_unread[(int) $row['folder_id']] = $row['num_unread']; + } + $db->sql_freeresult($result); + + // Make sure the default boxes are defined + $available_folder = array(PRIVMSGS_INBOX, PRIVMSGS_OUTBOX, PRIVMSGS_SENTBOX); + + foreach ($available_folder as $default_folder) + { + if (!isset($num_messages[$default_folder])) + { + $num_messages[$default_folder] = 0; + } + + if (!isset($num_unread[$default_folder])) + { + $num_unread[$default_folder] = 0; + } + } + + // Adjust unread status for outbox + $num_unread[PRIVMSGS_OUTBOX] = $num_messages[PRIVMSGS_OUTBOX]; + + $folder[PRIVMSGS_INBOX] = array( + 'folder_name' => $user->lang['PM_INBOX'], + 'num_messages' => $num_messages[PRIVMSGS_INBOX], + 'unread_messages' => $num_unread[PRIVMSGS_INBOX] + ); + + // Custom Folder + $sql = 'SELECT folder_id, folder_name, pm_count + FROM ' . PRIVMSGS_FOLDER_TABLE . " + WHERE user_id = $user_id"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $folder[$row['folder_id']] = array( + 'folder_name' => $row['folder_name'], + 'num_messages' => $row['pm_count'], + 'unread_messages' => ((isset($num_unread[$row['folder_id']])) ? $num_unread[$row['folder_id']] : 0) + ); + } + $db->sql_freeresult($result); + + $folder[PRIVMSGS_OUTBOX] = array( + 'folder_name' => $user->lang['PM_OUTBOX'], + 'num_messages' => $num_messages[PRIVMSGS_OUTBOX], + 'unread_messages' => $num_unread[PRIVMSGS_OUTBOX] + ); + + $folder[PRIVMSGS_SENTBOX] = array( + 'folder_name' => $user->lang['PM_SENTBOX'], + 'num_messages' => $num_messages[PRIVMSGS_SENTBOX], + 'unread_messages' => $num_unread[PRIVMSGS_SENTBOX] + ); + + // Define Folder Array for template designers (and for making custom folders usable by the template too) + foreach ($folder as $f_id => $folder_ary) + { + $folder_id_name = ($f_id == PRIVMSGS_INBOX) ? 'inbox' : (($f_id == PRIVMSGS_OUTBOX) ? 'outbox' : 'sentbox'); + + $template->assign_block_vars('folder', array( + 'FOLDER_ID' => $f_id, + 'FOLDER_NAME' => $folder_ary['folder_name'], + 'NUM_MESSAGES' => $folder_ary['num_messages'], + 'UNREAD_MESSAGES' => $folder_ary['unread_messages'], + + 'U_FOLDER' => ($f_id > 0) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=' . $f_id) : append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=' . $folder_id_name), + + 'S_CUR_FOLDER' => ($f_id === $folder_id) ? true : false, + 'S_UNREAD_MESSAGES' => ($folder_ary['unread_messages']) ? true : false, + 'S_CUSTOM_FOLDER' => ($f_id > 0) ? true : false) + ); + } + + if ($folder_id !== false && $folder_id !== PRIVMSGS_HOLD_BOX && !isset($folder[$folder_id])) + { + trigger_error('UNKNOWN_FOLDER'); + } + + return $folder; +} + +/** +* Delete Messages From Sentbox +* we are doing this here because this saves us a bunch of checks and queries +*/ +function clean_sentbox($num_sentbox_messages) +{ + global $db, $user; + + // Check Message Limit + if ($user->data['message_limit'] && $num_sentbox_messages > $user->data['message_limit']) + { + // Delete old messages + $sql = 'SELECT t.msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p + WHERE t.msg_id = p.msg_id + AND t.user_id = ' . $user->data['user_id'] . ' + AND t.folder_id = ' . PRIVMSGS_SENTBOX . ' + ORDER BY p.message_time ASC'; + $result = $db->sql_query_limit($sql, ($num_sentbox_messages - $user->data['message_limit'])); + + $delete_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $delete_ids[] = $row['msg_id']; + } + $db->sql_freeresult($result); + delete_pm($user->data['user_id'], $delete_ids, PRIVMSGS_SENTBOX); + } +} + +/** +* Check Rule against Message Information +*/ +function check_rule(&$rules, &$rule_row, &$message_row, $user_id) +{ + if (!isset($rules[$rule_row['rule_check']][$rule_row['rule_connection']])) + { + return false; + } + + $check_ary = $rules[$rule_row['rule_check']][$rule_row['rule_connection']]; + + $result = false; + + $check0 = $message_row[$check_ary['check0']]; + + switch ($rule_row['rule_connection']) + { + case RULE_IS_LIKE: + $result = preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0); + break; + + case RULE_IS_NOT_LIKE: + $result = !preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0); + break; + + case RULE_IS: + $result = ($check0 == $rule_row['rule_string']); + break; + + case RULE_IS_NOT: + $result = ($check0 != $rule_row['rule_string']); + break; + + case RULE_BEGINS_WITH: + $result = preg_match("/^" . preg_quote($rule_row['rule_string'], '/') . '/i', $check0); + break; + + case RULE_ENDS_WITH: + $result = preg_match("/" . preg_quote($rule_row['rule_string'], '/') . '$/i', $check0); + break; + + case RULE_IS_FRIEND: + case RULE_IS_FOE: + case RULE_ANSWERED: + case RULE_FORWARDED: + $result = ($check0 == 1); + break; + + case RULE_IS_USER: + $result = ($check0 == $rule_row['rule_user_id']); + break; + + case RULE_IS_GROUP: + $result = in_array($rule_row['rule_group_id'], $check0); + break; + + case RULE_TO_GROUP: + $result = (in_array('g_' . $message_row[$check_ary['check2']], $check0) || in_array('g_' . $message_row[$check_ary['check2']], $message_row[$check_ary['check1']])); + break; + + case RULE_TO_ME: + $result = (in_array('u_' . $user_id, $check0) || in_array('u_' . $user_id, $message_row[$check_ary['check1']])); + break; + } + + if (!$result) + { + return false; + } + + switch ($rule_row['rule_action']) + { + case ACTION_PLACE_INTO_FOLDER: + return array('action' => $rule_row['rule_action'], 'folder_id' => $rule_row['rule_folder_id']); + break; + + case ACTION_MARK_AS_READ: + case ACTION_MARK_AS_IMPORTANT: + return array('action' => $rule_row['rule_action'], 'pm_unread' => $message_row['pm_unread'], 'pm_marked' => $message_row['pm_marked']); + break; + + case ACTION_DELETE_MESSAGE: + global $db; + + // Check for admins/mods - users are not allowed to remove those messages... + // We do the check here to make sure the data we use is consistent + $sql = 'SELECT user_id, user_type, user_permissions + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $message_row['author_id']; + $result = $db->sql_query($sql); + $userdata = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $auth2 = new \phpbb\auth\auth(); + $auth2->acl($userdata); + + if (!$auth2->acl_get('a_') && !$auth2->acl_get('m_') && !$auth2->acl_getf_global('m_')) + { + return array('action' => $rule_row['rule_action'], 'pm_unread' => $message_row['pm_unread'], 'pm_marked' => $message_row['pm_marked']); + } + + return false; + break; + + default: + return false; + } + + return false; +} + +/** +* Update user PM count +*/ +function update_pm_counts() +{ + global $user, $db; + + // Update unread count + $sql = 'SELECT COUNT(msg_id) as num_messages + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE pm_unread = 1 + AND folder_id <> ' . PRIVMSGS_OUTBOX . ' + AND user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + $user->data['user_unread_privmsg'] = (int) $db->sql_fetchfield('num_messages'); + $db->sql_freeresult($result); + + // Update new pm count + $sql = 'SELECT COUNT(msg_id) as num_messages + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE pm_new = 1 + AND folder_id IN (' . PRIVMSGS_NO_BOX . ', ' . PRIVMSGS_HOLD_BOX . ') + AND user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + $user->data['user_new_privmsg'] = (int) $db->sql_fetchfield('num_messages'); + $db->sql_freeresult($result); + + $db->sql_query('UPDATE ' . USERS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', array( + 'user_unread_privmsg' => (int) $user->data['user_unread_privmsg'], + 'user_new_privmsg' => (int) $user->data['user_new_privmsg'], + )) . ' WHERE user_id = ' . $user->data['user_id']); + + // Ok, here we need to repair something, other boxes than privmsgs_no_box and privmsgs_hold_box should not carry the pm_new flag. + if (!$user->data['user_new_privmsg']) + { + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET pm_new = 0 + WHERE pm_new = 1 + AND folder_id NOT IN (' . PRIVMSGS_NO_BOX . ', ' . PRIVMSGS_HOLD_BOX . ') + AND user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + } +} + +/** +* Place new messages into appropriate folder +*/ +function place_pm_into_folder(&$global_privmsgs_rules, $release = false) +{ + global $db, $user, $config; + + if (!$user->data['user_new_privmsg']) + { + return array('not_moved' => 0, 'removed' => 0); + } + + $user_message_rules = (int) $user->data['user_message_rules']; + $user_id = (int) $user->data['user_id']; + + $action_ary = $move_into_folder = array(); + $num_removed = 0; + + // Newly processing on-hold messages + if ($release) + { + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET folder_id = ' . PRIVMSGS_NO_BOX . ' + WHERE folder_id = ' . PRIVMSGS_HOLD_BOX . " + AND user_id = $user_id"; + $db->sql_query($sql); + } + + // Get those messages not yet placed into any box + $retrieve_sql = 'SELECT t.*, p.*, u.username, u.user_id, u.group_id + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p, ' . USERS_TABLE . " u + WHERE t.user_id = $user_id + AND p.author_id = u.user_id + AND t.folder_id = " . PRIVMSGS_NO_BOX . ' + AND t.msg_id = p.msg_id'; + + // Just place into the appropriate arrays if no rules need to be checked + if (!$user_message_rules) + { + $result = $db->sql_query($retrieve_sql); + + while ($row = $db->sql_fetchrow($result)) + { + $action_ary[$row['msg_id']][] = array('action' => false); + } + $db->sql_freeresult($result); + } + else + { + $user_rules = $zebra = $check_rows = array(); + $user_ids = $memberships = array(); + + // First of all, grab all rules and retrieve friends/foes + $sql = 'SELECT * + FROM ' . PRIVMSGS_RULES_TABLE . " + WHERE user_id = $user_id"; + $result = $db->sql_query($sql); + $user_rules = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + if (count($user_rules)) + { + $sql = 'SELECT zebra_id, friend, foe + FROM ' . ZEBRA_TABLE . " + WHERE user_id = $user_id"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $zebra[$row['zebra_id']] = $row; + } + $db->sql_freeresult($result); + } + + // Now build a bare-bone check_row array + $result = $db->sql_query($retrieve_sql); + + while ($row = $db->sql_fetchrow($result)) + { + $check_rows[] = array_merge($row, array( + 'to' => explode(':', $row['to_address']), + 'bcc' => explode(':', $row['bcc_address']), + 'friend' => (isset($zebra[$row['author_id']])) ? $zebra[$row['author_id']]['friend'] : 0, + 'foe' => (isset($zebra[$row['author_id']])) ? $zebra[$row['author_id']]['foe'] : 0, + 'user_in_group' => array($user->data['group_id']), + 'author_in_group' => array()) + ); + + $user_ids[] = $row['user_id']; + } + $db->sql_freeresult($result); + + // Retrieve user memberships + if (count($user_ids)) + { + $sql = 'SELECT * + FROM ' . USER_GROUP_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $user_ids) . ' + AND user_pending = 0'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $memberships[$row['user_id']][] = $row['group_id']; + } + $db->sql_freeresult($result); + } + + // Now place into the appropriate folder + foreach ($check_rows as $row) + { + // Add membership if set + if (isset($memberships[$row['author_id']])) + { + $row['author_in_group'] = $memberships[$row['user_id']]; + } + + // Check Rule - this should be very quick since we have all information we need + $is_match = false; + foreach ($user_rules as $rule_row) + { + if (($action = check_rule($global_privmsgs_rules, $rule_row, $row, $user_id)) !== false) + { + $is_match = true; + $action_ary[$row['msg_id']][] = $action; + } + } + + if (!$is_match) + { + $action_ary[$row['msg_id']][] = array('action' => false); + } + } + + unset($user_rules, $zebra, $check_rows, $user_ids, $memberships); + } + + // We place actions into arrays, to save queries. + $unread_ids = $delete_ids = $important_ids = array(); + + foreach ($action_ary as $msg_id => $msg_ary) + { + // It is allowed to execute actions more than once, except placing messages into folder + $folder_action = $message_removed = false; + + foreach ($msg_ary as $pos => $rule_ary) + { + if ($folder_action && $rule_ary['action'] == ACTION_PLACE_INTO_FOLDER) + { + continue; + } + + switch ($rule_ary['action']) + { + case ACTION_PLACE_INTO_FOLDER: + // Folder actions have precedence, so we will remove any other ones + $folder_action = true; + $move_into_folder[(int) $rule_ary['folder_id']][] = $msg_id; + break; + + case ACTION_MARK_AS_READ: + if ($rule_ary['pm_unread']) + { + $unread_ids[] = $msg_id; + } + break; + + case ACTION_DELETE_MESSAGE: + $delete_ids[] = $msg_id; + $message_removed = true; + break; + + case ACTION_MARK_AS_IMPORTANT: + if (!$rule_ary['pm_marked']) + { + $important_ids[] = $msg_id; + } + break; + } + } + + // We place this here because it could happen that the messages are doubled if a rule marks a message and then moves it into a specific + // folder. Here we simply move the message into the INBOX if it gets not removed and also not put into a custom folder. + if (!$folder_action && !$message_removed) + { + $move_into_folder[PRIVMSGS_INBOX][] = $msg_id; + } + } + + // Do not change the order of processing + // The number of queries needed to be executed here highly depends on the defined rules and are + // only gone through if new messages arrive. + + // Delete messages + if (count($delete_ids)) + { + $num_removed += count($delete_ids); + delete_pm($user_id, $delete_ids, PRIVMSGS_NO_BOX); + } + + // Set messages to Unread + if (count($unread_ids)) + { + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET pm_unread = 0 + WHERE ' . $db->sql_in_set('msg_id', $unread_ids) . " + AND user_id = $user_id + AND folder_id = " . PRIVMSGS_NO_BOX; + $db->sql_query($sql); + } + + // mark messages as important + if (count($important_ids)) + { + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET pm_marked = 1 - pm_marked + WHERE folder_id = ' . PRIVMSGS_NO_BOX . " + AND user_id = $user_id + AND " . $db->sql_in_set('msg_id', $important_ids); + $db->sql_query($sql); + } + + // Move into folder + $folder = array(); + + if (count($move_into_folder)) + { + // Determine Full Folder Action - we need the move to folder id later eventually + $full_folder_action = ($user->data['user_full_folder'] == FULL_FOLDER_NONE) ? ($config['full_folder_action'] - (FULL_FOLDER_NONE*(-1))) : $user->data['user_full_folder']; + + $sql_folder = array_keys($move_into_folder); + if ($full_folder_action >= 0) + { + $sql_folder[] = $full_folder_action; + } + + $sql = 'SELECT folder_id, pm_count + FROM ' . PRIVMSGS_FOLDER_TABLE . ' + WHERE ' . $db->sql_in_set('folder_id', $sql_folder) . " + AND user_id = $user_id"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $folder[(int) $row['folder_id']] = (int) $row['pm_count']; + } + $db->sql_freeresult($result); + + unset($sql_folder); + + if (isset($move_into_folder[PRIVMSGS_INBOX])) + { + $sql = 'SELECT COUNT(msg_id) as num_messages + FROM ' . PRIVMSGS_TO_TABLE . " + WHERE user_id = $user_id + AND folder_id = " . PRIVMSGS_INBOX; + $result = $db->sql_query($sql); + $folder[PRIVMSGS_INBOX] = (int) $db->sql_fetchfield('num_messages'); + $db->sql_freeresult($result); + } + } + + // Here we have ideally only one folder to move into + foreach ($move_into_folder as $folder_id => $msg_ary) + { + $dest_folder = $folder_id; + $full_folder_action = FULL_FOLDER_NONE; + + // Check Message Limit - we calculate with the complete array, most of the time it is one message + // But we are making sure that the other way around works too (more messages in queue than allowed to be stored) + if ($user->data['message_limit'] && $folder[$folder_id] && ($folder[$folder_id] + count($msg_ary)) > $user->data['message_limit']) + { + $full_folder_action = ($user->data['user_full_folder'] == FULL_FOLDER_NONE) ? ($config['full_folder_action'] - (FULL_FOLDER_NONE*(-1))) : $user->data['user_full_folder']; + + // If destination folder itself is full... + if ($full_folder_action >= 0 && ($folder[$full_folder_action] + count($msg_ary)) > $user->data['message_limit']) + { + $full_folder_action = $config['full_folder_action'] - (FULL_FOLDER_NONE*(-1)); + } + + // If Full Folder Action is to move to another folder, we simply adjust the destination folder + if ($full_folder_action >= 0) + { + $dest_folder = $full_folder_action; + } + else if ($full_folder_action == FULL_FOLDER_DELETE) + { + // Delete some messages. NOTE: Ordered by msg_id here instead of message_time! + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . " + WHERE user_id = $user_id + AND folder_id = $dest_folder + ORDER BY msg_id ASC"; + $result = $db->sql_query_limit($sql, (($folder[$dest_folder] + count($msg_ary)) - $user->data['message_limit'])); + + $delete_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $delete_ids[] = $row['msg_id']; + } + $db->sql_freeresult($result); + + $num_removed += count($delete_ids); + delete_pm($user_id, $delete_ids, $dest_folder); + } + } + + // + if ($full_folder_action == FULL_FOLDER_HOLD) + { + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET folder_id = ' . PRIVMSGS_HOLD_BOX . ' + WHERE folder_id = ' . PRIVMSGS_NO_BOX . " + AND user_id = $user_id + AND " . $db->sql_in_set('msg_id', $msg_ary); + $db->sql_query($sql); + } + else + { + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . " + SET folder_id = $dest_folder, pm_new = 0 + WHERE folder_id = " . PRIVMSGS_NO_BOX . " + AND user_id = $user_id + AND pm_new = 1 + AND " . $db->sql_in_set('msg_id', $msg_ary); + $db->sql_query($sql); + + if ($dest_folder != PRIVMSGS_INBOX) + { + $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . ' + SET pm_count = pm_count + ' . (int) $db->sql_affectedrows() . " + WHERE folder_id = $dest_folder + AND user_id = $user_id"; + $db->sql_query($sql); + } + } + } + + if (count($action_ary)) + { + // Move from OUTBOX to SENTBOX + // We are not checking any full folder status here... SENTBOX is a special treatment (old messages get deleted) + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET folder_id = ' . PRIVMSGS_SENTBOX . ' + WHERE folder_id = ' . PRIVMSGS_OUTBOX . ' + AND ' . $db->sql_in_set('msg_id', array_keys($action_ary)); + $db->sql_query($sql); + } + + // Update new/unread count + update_pm_counts(); + + // Now check how many messages got not moved... + $sql = 'SELECT COUNT(msg_id) as num_messages + FROM ' . PRIVMSGS_TO_TABLE . " + WHERE user_id = $user_id + AND folder_id = " . PRIVMSGS_HOLD_BOX; + $result = $db->sql_query($sql); + $num_not_moved = (int) $db->sql_fetchfield('num_messages'); + $db->sql_freeresult($result); + + return array('not_moved' => $num_not_moved, 'removed' => $num_removed); +} + +/** +* Move PM from one to another folder +*/ +function move_pm($user_id, $message_limit, $move_msg_ids, $dest_folder, $cur_folder_id) +{ + global $db, $user; + global $phpbb_root_path, $phpEx; + + $num_moved = 0; + + if (!is_array($move_msg_ids)) + { + $move_msg_ids = array($move_msg_ids); + } + + if (count($move_msg_ids) && !in_array($dest_folder, array(PRIVMSGS_NO_BOX, PRIVMSGS_OUTBOX, PRIVMSGS_SENTBOX)) && + !in_array($cur_folder_id, array(PRIVMSGS_NO_BOX, PRIVMSGS_OUTBOX)) && $cur_folder_id != $dest_folder) + { + // We have to check the destination folder ;) + if ($dest_folder != PRIVMSGS_INBOX) + { + $sql = 'SELECT folder_id, folder_name, pm_count + FROM ' . PRIVMSGS_FOLDER_TABLE . " + WHERE folder_id = $dest_folder + AND user_id = $user_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + + if ($message_limit && $row['pm_count'] + count($move_msg_ids) > $message_limit) + { + $message = sprintf($user->lang['NOT_ENOUGH_SPACE_FOLDER'], $row['folder_name']) . '

'; + $message .= sprintf($user->lang['CLICK_RETURN_FOLDER'], '', '', $row['folder_name']); + trigger_error($message); + } + } + else + { + $sql = 'SELECT COUNT(msg_id) as num_messages + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE folder_id = ' . PRIVMSGS_INBOX . " + AND user_id = $user_id"; + $result = $db->sql_query($sql); + $num_messages = (int) $db->sql_fetchfield('num_messages'); + $db->sql_freeresult($result); + + if ($message_limit && $num_messages + count($move_msg_ids) > $message_limit) + { + $message = sprintf($user->lang['NOT_ENOUGH_SPACE_FOLDER'], $user->lang['PM_INBOX']) . '

'; + $message .= sprintf($user->lang['CLICK_RETURN_FOLDER'], '', '', $user->lang['PM_INBOX']); + trigger_error($message); + } + } + + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . " + SET folder_id = $dest_folder + WHERE folder_id = $cur_folder_id + AND user_id = $user_id + AND " . $db->sql_in_set('msg_id', $move_msg_ids); + $db->sql_query($sql); + $num_moved = $db->sql_affectedrows(); + + // Update pm counts + if ($num_moved) + { + if (!in_array($cur_folder_id, array(PRIVMSGS_INBOX, PRIVMSGS_OUTBOX, PRIVMSGS_SENTBOX))) + { + $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . " + SET pm_count = pm_count - $num_moved + WHERE folder_id = $cur_folder_id + AND user_id = $user_id"; + $db->sql_query($sql); + } + + if ($dest_folder != PRIVMSGS_INBOX) + { + $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . " + SET pm_count = pm_count + $num_moved + WHERE folder_id = $dest_folder + AND user_id = $user_id"; + $db->sql_query($sql); + } + } + } + else if (in_array($cur_folder_id, array(PRIVMSGS_NO_BOX, PRIVMSGS_OUTBOX))) + { + trigger_error('CANNOT_MOVE_SPECIAL'); + } + + return $num_moved; +} + +/** +* Update unread message status +*/ +function update_unread_status($unread, $msg_id, $user_id, $folder_id) +{ + if (!$unread) + { + return; + } + + global $db, $user, $phpbb_container; + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications('notification.type.pm', $msg_id, $user_id); + + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . " + SET pm_unread = 0 + WHERE msg_id = $msg_id + AND user_id = $user_id + AND folder_id = $folder_id + AND pm_unread = 1"; + $db->sql_query($sql); + + // If the message is already marked as read, we just skip the rest to avoid negative PM count + if (!$db->sql_affectedrows()) + { + return; + } + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_unread_privmsg = user_unread_privmsg - 1 + WHERE user_id = $user_id"; + $db->sql_query($sql); + + if ($user->data['user_id'] == $user_id) + { + $user->data['user_unread_privmsg']--; + + // Try to cope with previous wrong conversions... + if ($user->data['user_unread_privmsg'] < 0) + { + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_unread_privmsg = 0 + WHERE user_id = $user_id"; + $db->sql_query($sql); + + $user->data['user_unread_privmsg'] = 0; + } + } +} + +function mark_folder_read($user_id, $folder_id) +{ + global $db; + + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE folder_id = ' . ((int) $folder_id) . ' + AND user_id = ' . ((int) $user_id) . ' + AND pm_unread = 1'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + update_unread_status(true, $row['msg_id'], $user_id, $folder_id); + } + $db->sql_freeresult($result); +} + +/** +* Handle all actions possible with marked messages +*/ +function handle_mark_actions($user_id, $mark_action) +{ + global $db, $user, $phpbb_root_path, $phpEx, $request; + + $msg_ids = $request->variable('marked_msg_id', array(0)); + $cur_folder_id = $request->variable('cur_folder_id', PRIVMSGS_NO_BOX); + + if (!count($msg_ids)) + { + return false; + } + + switch ($mark_action) + { + case 'mark_important': + + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . " + SET pm_marked = 1 - pm_marked + WHERE folder_id = $cur_folder_id + AND user_id = $user_id + AND " . $db->sql_in_set('msg_id', $msg_ids); + $db->sql_query($sql); + + break; + + case 'delete_marked': + + global $auth; + + if (!$auth->acl_get('u_pm_delete')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_DELETE_MESSAGE'); + } + + if (confirm_box(true)) + { + delete_pm($user_id, $msg_ids, $cur_folder_id); + + $success_msg = (count($msg_ids) == 1) ? 'MESSAGE_DELETED' : 'MESSAGES_DELETED'; + $redirect = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=' . $cur_folder_id); + + meta_refresh(3, $redirect); + trigger_error($user->lang[$success_msg] . '

' . sprintf($user->lang['RETURN_FOLDER'], '', '')); + } + else + { + $s_hidden_fields = array( + 'cur_folder_id' => $cur_folder_id, + 'mark_option' => 'delete_marked', + 'submit_mark' => true, + 'marked_msg_id' => $msg_ids + ); + + confirm_box(false, 'DELETE_MARKED_PM', build_hidden_fields($s_hidden_fields)); + } + + break; + + default: + return false; + } + + return true; +} + +/** +* Delete PM(s) +*/ +function delete_pm($user_id, $msg_ids, $folder_id) +{ + global $db, $user, $phpbb_container, $phpbb_dispatcher; + + $user_id = (int) $user_id; + $folder_id = (int) $folder_id; + + if (!$user_id) + { + return false; + } + + if (!is_array($msg_ids)) + { + if (!$msg_ids) + { + return false; + } + $msg_ids = array($msg_ids); + } + + if (!count($msg_ids)) + { + return false; + } + + /** + * Get all info for PM(s) before they are deleted + * + * @event core.delete_pm_before + * @var int user_id ID of the user requested the message delete + * @var array msg_ids array of all messages to be deleted + * @var int folder_id ID of the user folder where the messages are stored + * @since 3.1.0-b5 + */ + $vars = array('user_id', 'msg_ids', 'folder_id'); + extract($phpbb_dispatcher->trigger_event('core.delete_pm_before', compact($vars))); + + // Get PM Information for later deleting + $sql = 'SELECT msg_id, pm_unread, pm_new + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $db->sql_in_set('msg_id', array_map('intval', $msg_ids)) . " + AND folder_id = $folder_id + AND user_id = $user_id"; + $result = $db->sql_query($sql); + + $delete_rows = array(); + $num_unread = $num_new = $num_deleted = 0; + while ($row = $db->sql_fetchrow($result)) + { + $num_unread += (int) $row['pm_unread']; + $num_new += (int) $row['pm_new']; + + $delete_rows[$row['msg_id']] = 1; + } + $db->sql_freeresult($result); + unset($msg_ids); + + if (!count($delete_rows)) + { + return false; + } + + $db->sql_transaction('begin'); + + // if no one has read the message yet (meaning it is in users outbox) + // then mark the message as deleted... + if ($folder_id == PRIVMSGS_OUTBOX) + { + // Remove PM from Outbox + $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . " + WHERE user_id = $user_id AND folder_id = " . PRIVMSGS_OUTBOX . ' + AND ' . $db->sql_in_set('msg_id', array_keys($delete_rows)); + $db->sql_query($sql); + + // Update PM Information for safety + $sql = 'UPDATE ' . PRIVMSGS_TABLE . " SET message_text = '' + WHERE " . $db->sql_in_set('msg_id', array_keys($delete_rows)); + $db->sql_query($sql); + + // Set delete flag for those intended to receive the PM + // We do not remove the message actually, to retain some basic information (sent time for example) + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET pm_deleted = 1 + WHERE ' . $db->sql_in_set('msg_id', array_keys($delete_rows)); + $db->sql_query($sql); + + $num_deleted = $db->sql_affectedrows(); + } + else + { + // Delete private message data + $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . " + WHERE user_id = $user_id + AND folder_id = $folder_id + AND " . $db->sql_in_set('msg_id', array_keys($delete_rows)); + $db->sql_query($sql); + $num_deleted = $db->sql_affectedrows(); + } + + // if folder id is user defined folder then decrease pm_count + if (!in_array($folder_id, array(PRIVMSGS_INBOX, PRIVMSGS_OUTBOX, PRIVMSGS_SENTBOX, PRIVMSGS_NO_BOX))) + { + $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . " + SET pm_count = pm_count - $num_deleted + WHERE folder_id = $folder_id"; + $db->sql_query($sql); + } + + // Update unread and new status field + if ($num_unread || $num_new) + { + $set_sql = ($num_unread) ? 'user_unread_privmsg = user_unread_privmsg - ' . $num_unread : ''; + + if ($num_new) + { + $set_sql .= ($set_sql != '') ? ', ' : ''; + $set_sql .= 'user_new_privmsg = user_new_privmsg - ' . $num_new; + } + + $db->sql_query('UPDATE ' . USERS_TABLE . " SET $set_sql WHERE user_id = $user_id"); + + $user->data['user_new_privmsg'] -= $num_new; + $user->data['user_unread_privmsg'] -= $num_unread; + } + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->delete_notifications('notification.type.pm', array_keys($delete_rows)); + + // Now we have to check which messages we can delete completely + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $db->sql_in_set('msg_id', array_keys($delete_rows)); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + unset($delete_rows[$row['msg_id']]); + } + $db->sql_freeresult($result); + + $delete_ids = array_keys($delete_rows); + + if (count($delete_ids)) + { + // Check if there are any attachments we need to remove + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $attachment_manager->delete('message', $delete_ids, false); + unset($attachment_manager); + + $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' + WHERE ' . $db->sql_in_set('msg_id', $delete_ids); + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + + return true; +} + +/** +* Delete all PM(s) for a given user and delete the ones without references +* +* @param int $user_id ID of the user whose private messages we want to delete +* +* @return boolean False if there were no pms found, true otherwise. +*/ +function phpbb_delete_user_pms($user_id) +{ + $user_id = (int) $user_id; + + if (!$user_id) + { + return false; + } + + return phpbb_delete_users_pms(array($user_id)); +} + +/** +* Delete all PM(s) for given users and delete the ones without references +* +* @param array $user_ids IDs of the users whose private messages we want to delete +* +* @return boolean False if there were no pms found, true otherwise. +*/ +function phpbb_delete_users_pms($user_ids) +{ + global $db, $phpbb_container; + + $user_id_sql = $db->sql_in_set('user_id', $user_ids); + $author_id_sql = $db->sql_in_set('author_id', $user_ids); + + // Get PM Information for later deleting + // The two queries where split, so we can use our indexes + $undelivered_msg = $delete_ids = array(); + + // Part 1: get PMs the user received + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $user_id_sql; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $msg_id = (int) $row['msg_id']; + $delete_ids[$msg_id] = $msg_id; + } + $db->sql_freeresult($result); + + // Part 2: get PMs the users sent, but are yet to be received. + // We cannot simply delete them. First we have to check + // whether another user already received and read the message. + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $author_id_sql . ' + AND folder_id = ' . PRIVMSGS_NO_BOX; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $msg_id = (int) $row['msg_id']; + $undelivered_msg[$msg_id] = $msg_id; + } + $db->sql_freeresult($result); + + if (empty($delete_ids) && empty($undelivered_msg)) + { + return false; + } + + $db->sql_transaction('begin'); + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + if (!empty($undelivered_msg)) + { + // A pm is delivered, if for any recipient the message was moved + // from their NO_BOX to another folder. We do not delete such + // messages, but only delete them for users, who have not yet + // received them. + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $author_id_sql . ' + AND folder_id <> ' . PRIVMSGS_NO_BOX . ' + AND folder_id <> ' . PRIVMSGS_OUTBOX . ' + AND folder_id <> ' . PRIVMSGS_SENTBOX; + $result = $db->sql_query($sql); + + $delivered_msg = array(); + while ($row = $db->sql_fetchrow($result)) + { + $msg_id = (int) $row['msg_id']; + $delivered_msg[$msg_id] = $msg_id; + unset($undelivered_msg[$msg_id]); + } + $db->sql_freeresult($result); + + $undelivered_user = array(); + + // Count the messages we delete, so we can correct the user pm data + $sql = 'SELECT user_id, COUNT(msg_id) as num_undelivered_privmsgs + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $author_id_sql . ' + AND folder_id = ' . PRIVMSGS_NO_BOX . ' + AND ' . $db->sql_in_set('msg_id', array_merge($undelivered_msg, $delivered_msg)) . ' + GROUP BY user_id'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $num_pms = (int) $row['num_undelivered_privmsgs']; + $undelivered_user[$num_pms][] = (int) $row['user_id']; + + if (count($undelivered_user[$num_pms]) > 50) + { + // If there are too many users affected the query might get + // too long, so we update the value for the first bunch here. + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_new_privmsg = user_new_privmsg - ' . $num_pms . ', + user_unread_privmsg = user_unread_privmsg - ' . $num_pms . ' + WHERE ' . $db->sql_in_set('user_id', $undelivered_user[$num_pms]); + $db->sql_query($sql); + unset($undelivered_user[$num_pms]); + } + } + $db->sql_freeresult($result); + + foreach ($undelivered_user as $num_pms => $undelivered_user_set) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_new_privmsg = user_new_privmsg - ' . $num_pms . ', + user_unread_privmsg = user_unread_privmsg - ' . $num_pms . ' + WHERE ' . $db->sql_in_set('user_id', $undelivered_user_set); + $db->sql_query($sql); + } + + if (!empty($delivered_msg)) + { + $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE folder_id = ' . PRIVMSGS_NO_BOX . ' + AND ' . $db->sql_in_set('msg_id', $delivered_msg); + $db->sql_query($sql); + + $phpbb_notifications->delete_notifications('notification.type.pm', $delivered_msg); + } + + if (!empty($undelivered_msg)) + { + $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg); + $db->sql_query($sql); + + $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' + WHERE ' . $db->sql_in_set('msg_id', $undelivered_msg); + $db->sql_query($sql); + + $phpbb_notifications->delete_notifications('notification.type.pm', $undelivered_msg); + } + } + + // Reset the user's pm count to 0 + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_new_privmsg = 0, + user_unread_privmsg = 0 + WHERE ' . $user_id_sql; + $db->sql_query($sql); + + // Delete private message data of the user + $sql = 'DELETE FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $user_id_sql; + $db->sql_query($sql); + + if (!empty($delete_ids)) + { + // Now we have to check which messages we can delete completely + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE ' . $db->sql_in_set('msg_id', $delete_ids); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + unset($delete_ids[$row['msg_id']]); + } + $db->sql_freeresult($result); + + if (!empty($delete_ids)) + { + // Check if there are any attachments we need to remove + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $attachment_manager->delete('message', $delete_ids, false); + unset($attachment_manager); + + $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' + WHERE ' . $db->sql_in_set('msg_id', $delete_ids); + $db->sql_query($sql); + + $phpbb_notifications->delete_notifications('notification.type.pm', $delete_ids); + } + } + + // Set the remaining author id to anonymous + // This way users are still able to read messages from users being removed + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET author_id = ' . ANONYMOUS . ' + WHERE ' . $author_id_sql; + $db->sql_query($sql); + + $sql = 'UPDATE ' . PRIVMSGS_TABLE . ' + SET author_id = ' . ANONYMOUS . ' + WHERE ' . $author_id_sql; + $db->sql_query($sql); + + $db->sql_transaction('commit'); + + return true; +} + +/** +* Rebuild message header +*/ +function rebuild_header($check_ary) +{ + $address = array(); + + foreach ($check_ary as $check_type => $address_field) + { + // Split Addresses into users and groups + preg_match_all('/:?(u|g)_([0-9]+):?/', $address_field, $match); + + $u = $g = array(); + foreach ($match[1] as $id => $type) + { + ${$type}[] = (int) $match[2][$id]; + } + + $_types = array('u', 'g'); + foreach ($_types as $type) + { + if (count(${$type})) + { + foreach (${$type} as $id) + { + $address[$type][$id] = $check_type; + } + } + } + } + + return $address; +} + +/** +* Print out/assign recipient information +*/ +function write_pm_addresses($check_ary, $author_id, $plaintext = false) +{ + global $db, $user, $template, $phpbb_root_path, $phpEx, $phpbb_container; + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $addresses = array(); + + foreach ($check_ary as $check_type => $address_field) + { + if (!is_array($address_field)) + { + // Split Addresses into users and groups + preg_match_all('/:?(u|g)_([0-9]+):?/', $address_field, $match); + + $u = $g = array(); + foreach ($match[1] as $id => $type) + { + ${$type}[] = (int) $match[2][$id]; + } + } + else + { + $u = $address_field['u']; + $g = $address_field['g']; + } + + $address = array(); + if (count($u)) + { + $sql = 'SELECT user_id, username, user_colour + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $u); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($check_type == 'to' || $author_id == $user->data['user_id'] || $row['user_id'] == $user->data['user_id']) + { + if ($plaintext) + { + $address[] = $row['username']; + } + else + { + $address['user'][$row['user_id']] = array('name' => $row['username'], 'colour' => $row['user_colour']); + } + } + } + $db->sql_freeresult($result); + } + + if (count($g)) + { + if ($plaintext) + { + $sql = 'SELECT group_name, group_type + FROM ' . GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('group_id', $g); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($check_type == 'to' || $author_id == $user->data['user_id'] || $row['user_id'] == $user->data['user_id']) + { + $address[] = $group_helper->get_name($row['group_name']); + } + } + $db->sql_freeresult($result); + } + else + { + $sql = 'SELECT g.group_id, g.group_name, g.group_colour, g.group_type, ug.user_id + FROM ' . GROUPS_TABLE . ' g, ' . USER_GROUP_TABLE . ' ug + WHERE ' . $db->sql_in_set('g.group_id', $g) . ' + AND g.group_id = ug.group_id + AND ug.user_pending = 0'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (!isset($address['group'][$row['group_id']])) + { + if ($check_type == 'to' || $author_id == $user->data['user_id'] || $row['user_id'] == $user->data['user_id']) + { + $row['group_name'] = $group_helper->get_name($row['group_name']); + $address['group'][$row['group_id']] = array('name' => $row['group_name'], 'colour' => $row['group_colour']); + } + } + + if (isset($address['user'][$row['user_id']])) + { + $address['user'][$row['user_id']]['in_group'] = $row['group_id']; + } + } + $db->sql_freeresult($result); + } + } + + if (count($address) && !$plaintext) + { + $template->assign_var('S_' . strtoupper($check_type) . '_RECIPIENT', true); + + foreach ($address as $type => $adr_ary) + { + foreach ($adr_ary as $id => $row) + { + $tpl_ary = array( + 'IS_GROUP' => ($type == 'group') ? true : false, + 'IS_USER' => ($type == 'user') ? true : false, + 'UG_ID' => $id, + 'NAME' => $row['name'], + 'COLOUR' => ($row['colour']) ? '#' . $row['colour'] : '', + 'TYPE' => $type, + ); + + if ($type == 'user') + { + $tpl_ary = array_merge($tpl_ary, array( + 'U_VIEW' => get_username_string('profile', $id, $row['name'], $row['colour']), + 'NAME_FULL' => get_username_string('full', $id, $row['name'], $row['colour']), + )); + } + else + { + $tpl_ary = array_merge($tpl_ary, array( + 'U_VIEW' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&g=' . $id), + )); + } + + $template->assign_block_vars($check_type . '_recipient', $tpl_ary); + } + } + } + + $addresses[$check_type] = $address; + } + + return $addresses; +} + +/** +* Get folder status +*/ +function get_folder_status($folder_id, $folder) +{ + global $user; + + if (isset($folder[$folder_id])) + { + $folder = $folder[$folder_id]; + } + else + { + return false; + } + + $return = array( + 'folder_name' => $folder['folder_name'], + 'cur' => $folder['num_messages'], + 'remaining' => ($user->data['message_limit']) ? $user->data['message_limit'] - $folder['num_messages'] : 0, + 'max' => $user->data['message_limit'], + 'percent' => ($user->data['message_limit']) ? (($user->data['message_limit'] > 0) ? floor(($folder['num_messages'] / $user->data['message_limit']) * 100) : 100) : 0, + ); + + $return['message'] = $user->lang('FOLDER_STATUS_MSG', $user->lang('MESSAGES_COUNT', (int) $return['max']), (int) $return['cur'], $return['percent']); + + return $return; +} + +// +// COMPOSE MESSAGES +// + +/** +* Submit PM +*/ +function submit_pm($mode, $subject, &$data_ary, $put_in_outbox = true) +{ + global $db, $auth, $config, $user, $phpbb_root_path, $phpbb_container, $phpbb_dispatcher, $request; + + // We do not handle erasing pms here + if ($mode == 'delete') + { + return false; + } + + $current_time = time(); + + $data = $data_ary; + /** + * Get all parts of the PM that are to be submited to the DB. + * + * @event core.submit_pm_before + * @var string mode PM Post mode - post|reply|quote|quotepost|forward|edit + * @var string subject Subject of the private message + * @var array data The whole row data of the PM. + * @since 3.1.0-b3 + */ + $vars = array('mode', 'subject', 'data'); + extract($phpbb_dispatcher->trigger_event('core.submit_pm_before', compact($vars))); + $data_ary = $data; + unset($data); + + // Collect some basic information about which tables and which rows to update/insert + $sql_data = array(); + $root_level = 0; + + // Recipient Information + $recipients = $to = $bcc = array(); + + if ($mode != 'edit') + { + // Build Recipient List + // u|g => array($user_id => 'to'|'bcc') + $_types = array('u', 'g'); + foreach ($_types as $ug_type) + { + if (isset($data_ary['address_list'][$ug_type]) && count($data_ary['address_list'][$ug_type])) + { + foreach ($data_ary['address_list'][$ug_type] as $id => $field) + { + $id = (int) $id; + + // Do not rely on the address list being "valid" + if (!$id || ($ug_type == 'u' && $id == ANONYMOUS)) + { + continue; + } + + $field = ($field == 'to') ? 'to' : 'bcc'; + if ($ug_type == 'u') + { + $recipients[$id] = $field; + } + ${$field}[] = $ug_type . '_' . $id; + } + } + } + + if (isset($data_ary['address_list']['g']) && count($data_ary['address_list']['g'])) + { + // We need to check the PM status of group members (do they want to receive PM's?) + // Only check if not a moderator or admin, since they are allowed to override this user setting + $sql_allow_pm = (!$auth->acl_gets('a_', 'm_') && !$auth->acl_getf_global('m_')) ? ' AND u.user_allow_pm = 1' : ''; + + $sql = 'SELECT u.user_type, ug.group_id, ug.user_id + FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . ' ug + WHERE ' . $db->sql_in_set('ug.group_id', array_keys($data_ary['address_list']['g'])) . ' + AND ug.user_pending = 0 + AND u.user_id = ug.user_id + AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')' . + $sql_allow_pm; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $field = ($data_ary['address_list']['g'][$row['group_id']] == 'to') ? 'to' : 'bcc'; + $recipients[$row['user_id']] = $field; + } + $db->sql_freeresult($result); + } + + if (!count($recipients)) + { + trigger_error('NO_RECIPIENT'); + } + } + + // First of all make sure the subject are having the correct length. + $subject = truncate_string($subject); + + $db->sql_transaction('begin'); + + $sql = ''; + + switch ($mode) + { + case 'reply': + case 'quote': + $root_level = ($data_ary['reply_from_root_level']) ? $data_ary['reply_from_root_level'] : $data_ary['reply_from_msg_id']; + + // Set message_replied switch for this user + $sql = 'UPDATE ' . PRIVMSGS_TO_TABLE . ' + SET pm_replied = 1 + WHERE user_id = ' . $data_ary['from_user_id'] . ' + AND msg_id = ' . $data_ary['reply_from_msg_id']; + + // no break + + case 'forward': + case 'post': + case 'quotepost': + $sql_data = array( + 'root_level' => $root_level, + 'author_id' => $data_ary['from_user_id'], + 'icon_id' => $data_ary['icon_id'], + 'author_ip' => $data_ary['from_user_ip'], + 'message_time' => $current_time, + 'enable_bbcode' => $data_ary['enable_bbcode'], + 'enable_smilies' => $data_ary['enable_smilies'], + 'enable_magic_url' => $data_ary['enable_urls'], + 'enable_sig' => $data_ary['enable_sig'], + 'message_subject' => $subject, + 'message_text' => $data_ary['message'], + 'message_attachment'=> (!empty($data_ary['attachment_data'])) ? 1 : 0, + 'bbcode_bitfield' => $data_ary['bbcode_bitfield'], + 'bbcode_uid' => $data_ary['bbcode_uid'], + 'to_address' => implode(':', $to), + 'bcc_address' => implode(':', $bcc), + 'message_reported' => 0, + ); + break; + + case 'edit': + $sql_data = array( + 'icon_id' => $data_ary['icon_id'], + 'message_edit_time' => $current_time, + 'enable_bbcode' => $data_ary['enable_bbcode'], + 'enable_smilies' => $data_ary['enable_smilies'], + 'enable_magic_url' => $data_ary['enable_urls'], + 'enable_sig' => $data_ary['enable_sig'], + 'message_subject' => $subject, + 'message_text' => $data_ary['message'], + 'message_attachment'=> (!empty($data_ary['attachment_data'])) ? 1 : 0, + 'bbcode_bitfield' => $data_ary['bbcode_bitfield'], + 'bbcode_uid' => $data_ary['bbcode_uid'] + ); + break; + } + + if (count($sql_data)) + { + if ($mode == 'post' || $mode == 'reply' || $mode == 'quote' || $mode == 'quotepost' || $mode == 'forward') + { + $db->sql_query('INSERT INTO ' . PRIVMSGS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_data)); + $data_ary['msg_id'] = $db->sql_nextid(); + } + else if ($mode == 'edit') + { + $sql = 'UPDATE ' . PRIVMSGS_TABLE . ' + SET message_edit_count = message_edit_count + 1, ' . $db->sql_build_array('UPDATE', $sql_data) . ' + WHERE msg_id = ' . $data_ary['msg_id']; + $db->sql_query($sql); + } + } + + if ($mode != 'edit') + { + if ($sql) + { + $db->sql_query($sql); + } + unset($sql); + + $sql_ary = array(); + foreach ($recipients as $user_id => $type) + { + $sql_ary[] = array( + 'msg_id' => (int) $data_ary['msg_id'], + 'user_id' => (int) $user_id, + 'author_id' => (int) $data_ary['from_user_id'], + 'folder_id' => PRIVMSGS_NO_BOX, + 'pm_new' => 1, + 'pm_unread' => 1, + 'pm_forwarded' => ($mode == 'forward') ? 1 : 0 + ); + } + + $db->sql_multi_insert(PRIVMSGS_TO_TABLE, $sql_ary); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_new_privmsg = user_new_privmsg + 1, user_unread_privmsg = user_unread_privmsg + 1, user_last_privmsg = ' . time() . ' + WHERE ' . $db->sql_in_set('user_id', array_keys($recipients)); + $db->sql_query($sql); + + // Put PM into outbox + if ($put_in_outbox) + { + $db->sql_query('INSERT INTO ' . PRIVMSGS_TO_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'msg_id' => (int) $data_ary['msg_id'], + 'user_id' => (int) $data_ary['from_user_id'], + 'author_id' => (int) $data_ary['from_user_id'], + 'folder_id' => PRIVMSGS_OUTBOX, + 'pm_new' => 0, + 'pm_unread' => 0, + 'pm_forwarded' => ($mode == 'forward') ? 1 : 0)) + ); + } + } + + // Set user last post time + if ($mode == 'reply' || $mode == 'quote' || $mode == 'quotepost' || $mode == 'forward' || $mode == 'post') + { + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_lastpost_time = $current_time + WHERE user_id = " . $data_ary['from_user_id']; + $db->sql_query($sql); + } + + // Submit Attachments + if (!empty($data_ary['attachment_data']) && $data_ary['msg_id'] && in_array($mode, array('post', 'reply', 'quote', 'quotepost', 'edit', 'forward'))) + { + $space_taken = $files_added = 0; + $orphan_rows = array(); + + foreach ($data_ary['attachment_data'] as $pos => $attach_row) + { + $orphan_rows[(int) $attach_row['attach_id']] = array(); + } + + if (count($orphan_rows)) + { + $sql = 'SELECT attach_id, filesize, physical_filename + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan_rows)) . ' + AND in_message = 1 + AND is_orphan = 1 + AND poster_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + + $orphan_rows = array(); + while ($row = $db->sql_fetchrow($result)) + { + $orphan_rows[$row['attach_id']] = $row; + } + $db->sql_freeresult($result); + } + + foreach ($data_ary['attachment_data'] as $pos => $attach_row) + { + if ($attach_row['is_orphan'] && !isset($orphan_rows[$attach_row['attach_id']])) + { + continue; + } + + if (!$attach_row['is_orphan']) + { + // update entry in db if attachment already stored in db and filespace + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . " + SET attach_comment = '" . $db->sql_escape($attach_row['attach_comment']) . "' + WHERE attach_id = " . (int) $attach_row['attach_id'] . ' + AND is_orphan = 0'; + $db->sql_query($sql); + } + else + { + // insert attachment into db + if (!@file_exists($phpbb_root_path . $config['upload_path'] . '/' . utf8_basename($orphan_rows[$attach_row['attach_id']]['physical_filename']))) + { + continue; + } + + $space_taken += $orphan_rows[$attach_row['attach_id']]['filesize']; + $files_added++; + + $attach_sql = array( + 'post_msg_id' => $data_ary['msg_id'], + 'topic_id' => 0, + 'is_orphan' => 0, + 'poster_id' => $data_ary['from_user_id'], + 'attach_comment' => $attach_row['attach_comment'], + ); + + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $attach_sql) . ' + WHERE attach_id = ' . $attach_row['attach_id'] . ' + AND is_orphan = 1 + AND poster_id = ' . $user->data['user_id']; + $db->sql_query($sql); + } + } + + if ($space_taken && $files_added) + { + $config->increment('upload_dir_size', $space_taken, false); + $config->increment('num_files', $files_added, false); + } + } + + // Delete draft if post was loaded... + $draft_id = $request->variable('draft_loaded', 0); + if ($draft_id) + { + $sql = 'DELETE FROM ' . DRAFTS_TABLE . " + WHERE draft_id = $draft_id + AND user_id = " . $data_ary['from_user_id']; + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + + // Send Notifications + $pm_data = array_merge($data_ary, array( + 'message_subject' => $subject, + 'recipients' => $recipients, + )); + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + if ($mode == 'edit') + { + $phpbb_notifications->update_notifications('notification.type.pm', $pm_data); + } + else + { + $phpbb_notifications->add_notifications('notification.type.pm', $pm_data); + } + + $data = $data_ary; + /** + * Get PM message ID after submission to DB + * + * @event core.submit_pm_after + * @var string mode PM Post mode - post|reply|quote|quotepost|forward|edit + * @var string subject Subject of the private message + * @var array data The whole row data of the PM. + * @var array pm_data The data sent to notification class + * @since 3.1.0-b5 + */ + $vars = array('mode', 'subject', 'data', 'pm_data'); + extract($phpbb_dispatcher->trigger_event('core.submit_pm_after', compact($vars))); + $data_ary = $data; + unset($data); + + return $data_ary['msg_id']; +} + +/** +* Display Message History +*/ +function message_history($msg_id, $user_id, $message_row, $folder, $in_post_mode = false) +{ + global $db, $user, $template, $phpbb_root_path, $phpEx, $auth; + + // Select all receipts and the author from the pm we currently view, to only display their pm-history + $sql = 'SELECT author_id, user_id + FROM ' . PRIVMSGS_TO_TABLE . " + WHERE msg_id = $msg_id + AND folder_id <> " . PRIVMSGS_HOLD_BOX; + $result = $db->sql_query($sql); + + $recipients = array(); + while ($row = $db->sql_fetchrow($result)) + { + $recipients[] = (int) $row['user_id']; + $recipients[] = (int) $row['author_id']; + } + $db->sql_freeresult($result); + $recipients = array_unique($recipients); + + // Get History Messages (could be newer) + $sql = 'SELECT t.*, p.*, u.* + FROM ' . PRIVMSGS_TABLE . ' p, ' . PRIVMSGS_TO_TABLE . ' t, ' . USERS_TABLE . ' u + WHERE t.msg_id = p.msg_id + AND p.author_id = u.user_id + AND t.folder_id NOT IN (' . PRIVMSGS_NO_BOX . ', ' . PRIVMSGS_HOLD_BOX . ') + AND ' . $db->sql_in_set('t.author_id', $recipients, false, true) . " + AND t.user_id = $user_id"; + + // We no longer need those. + unset($recipients); + + if (!$message_row['root_level']) + { + $sql .= " AND (p.root_level = $msg_id OR (p.root_level = 0 AND p.msg_id = $msg_id))"; + } + else + { + $sql .= " AND (p.root_level = " . $message_row['root_level'] . ' OR p.msg_id = ' . $message_row['root_level'] . ')'; + } + $sql .= ' ORDER BY p.message_time DESC'; + + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + + if (!$row) + { + $db->sql_freeresult($result); + return false; + } + + $title = $row['message_subject']; + + $rowset = array(); + $folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm') . '&folder='; + + do + { + $folder_id = (int) $row['folder_id']; + + $row['folder'][] = (isset($folder[$folder_id])) ? '' . $folder[$folder_id]['folder_name'] . '' : $user->lang['UNKNOWN_FOLDER']; + + if (isset($rowset[$row['msg_id']])) + { + $rowset[$row['msg_id']]['folder'][] = (isset($folder[$folder_id])) ? '' . $folder[$folder_id]['folder_name'] . '' : $user->lang['UNKNOWN_FOLDER']; + } + else + { + $rowset[$row['msg_id']] = $row; + } + } + while ($row = $db->sql_fetchrow($result)); + $db->sql_freeresult($result); + + if (count($rowset) == 1 && !$in_post_mode) + { + return false; + } + + $title = censor_text($title); + + $url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm'); + $next_history_pm = $previous_history_pm = $prev_id = 0; + + // Re-order rowset to be able to get the next/prev message rows... + $rowset = array_values($rowset); + + for ($i = 0, $size = count($rowset); $i < $size; $i++) + { + $row = &$rowset[$i]; + $id = (int) $row['msg_id']; + + $author_id = $row['author_id']; + $folder_id = (int) $row['folder_id']; + + $subject = $row['message_subject']; + $message = $row['message_text']; + + $message = censor_text($message); + + $decoded_message = false; + + if ($in_post_mode && $auth->acl_get('u_sendpm') && $author_id != ANONYMOUS) + { + $decoded_message = $message; + decode_message($decoded_message, $row['bbcode_uid']); + + $decoded_message = bbcode_nl2br($decoded_message); + } + + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); + $parse_flags |= ($row['enable_smilies'] ? OPTION_FLAG_SMILIES : 0); + + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false); + + $subject = censor_text($subject); + + if ($id == $msg_id) + { + $next_history_pm = (isset($rowset[$i + 1])) ? (int) $rowset[$i + 1]['msg_id'] : 0; + $previous_history_pm = $prev_id; + } + + $template->assign_block_vars('history_row', array( + 'MESSAGE_AUTHOR_QUOTE' => (($decoded_message) ? addslashes(get_username_string('username', $author_id, $row['username'], $row['user_colour'], $row['username'])) : ''), + 'MESSAGE_AUTHOR_FULL' => get_username_string('full', $author_id, $row['username'], $row['user_colour'], $row['username']), + 'MESSAGE_AUTHOR_COLOUR' => get_username_string('colour', $author_id, $row['username'], $row['user_colour'], $row['username']), + 'MESSAGE_AUTHOR' => get_username_string('username', $author_id, $row['username'], $row['user_colour'], $row['username']), + 'U_MESSAGE_AUTHOR' => get_username_string('profile', $author_id, $row['username'], $row['user_colour'], $row['username']), + + 'SUBJECT' => $subject, + 'SENT_DATE' => $user->format_date($row['message_time']), + 'MESSAGE' => $message, + 'FOLDER' => implode($user->lang['COMMA_SEPARATOR'], $row['folder']), + 'DECODED_MESSAGE' => $decoded_message, + + 'S_CURRENT_MSG' => ($row['msg_id'] == $msg_id), + 'S_AUTHOR_DELETED' => ($author_id == ANONYMOUS) ? true : false, + 'S_IN_POST_MODE' => $in_post_mode, + + 'MSG_ID' => $row['msg_id'], + 'MESSAGE_TIME' => $row['message_time'], + 'USER_ID' => $row['user_id'], + 'U_VIEW_MESSAGE' => "$url&f=$folder_id&p=" . $row['msg_id'], + 'U_QUOTE' => (!$in_post_mode && $auth->acl_get('u_sendpm') && $author_id != ANONYMOUS) ? "$url&mode=compose&action=quote&f=" . $folder_id . "&p=" . $row['msg_id'] : '', + 'U_POST_REPLY_PM' => ($author_id != $user->data['user_id'] && $author_id != ANONYMOUS && $auth->acl_get('u_sendpm')) ? "$url&mode=compose&action=reply&f=$folder_id&p=" . $row['msg_id'] : '') + ); + unset($rowset[$i]); + $prev_id = $id; + } + + $template->assign_vars(array( + 'QUOTE_IMG' => $user->img('icon_post_quote', $user->lang['REPLY_WITH_QUOTE']), + 'HISTORY_TITLE' => $title, + + 'U_VIEW_NEXT_HISTORY' => ($next_history_pm) ? "$url&p=" . $next_history_pm : '', + 'U_VIEW_PREVIOUS_HISTORY' => ($previous_history_pm) ? "$url&p=" . $previous_history_pm : '', + )); + + return true; +} + +/** +* Set correct users max messages in PM folder. +* If several group memberships define different amount of messages, the highest will be chosen. +*/ +function set_user_message_limit() +{ + global $user, $db, $config; + + // Get maximum about from user memberships + $message_limit = phpbb_get_max_setting_from_group($db, $user->data['user_id'], 'message_limit'); + + // If it is 0, there is no limit set and we use the maximum value within the config. + $user->data['message_limit'] = (!$message_limit) ? $config['pm_max_msgs'] : $message_limit; +} + +/** + * Get the maximum PM setting for the groups of the user + * + * @param \phpbb\db\driver\driver_interface $db + * @param int $user_id + * @param string $setting Only 'max_recipients' and 'message_limit' are supported + * @return int The maximum setting for all groups of the user, unless one group has '0' + * @throws \InvalidArgumentException If selected group setting is not supported + */ +function phpbb_get_max_setting_from_group(\phpbb\db\driver\driver_interface $db, $user_id, $setting) +{ + if ($setting !== 'max_recipients' && $setting !== 'message_limit') + { + throw new InvalidArgumentException('Setting "' . $setting . '" is not supported'); + } + + // Get maximum number of allowed recipients + $sql = 'SELECT MAX(g.group_' . $setting . ') as max_setting + FROM ' . GROUPS_TABLE . ' g, ' . USER_GROUP_TABLE . ' ug + WHERE ug.user_id = ' . (int) $user_id . ' + AND ug.user_pending = 0 + AND ug.group_id = g.group_id'; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + $max_setting = (int) $row['max_setting']; + + return $max_setting; +} + +/** +* Generates an array of coloured recipient names from a list of PMs - (groups & users) +* +* @param array $pm_by_id An array of rows from PRIVMSGS_TABLE, keys are the msg_ids. +* +* @return array 2D Array: array(msg_id => array('username or group string', ...), ...) +* Usernames are generated with {@link get_username_string get_username_string} +* Groups are coloured and have a link to the membership page +*/ +function get_recipient_strings($pm_by_id) +{ + global $db, $phpbb_root_path, $phpEx, $user, $phpbb_container; + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $address_list = $recipient_list = $address = array(); + + $_types = array('u', 'g'); + + foreach ($pm_by_id as $message_id => $row) + { + $address[$message_id] = rebuild_header(array('to' => $row['to_address'], 'bcc' => $row['bcc_address'])); + + foreach ($_types as $ug_type) + { + if (isset($address[$message_id][$ug_type]) && count($address[$message_id][$ug_type])) + { + foreach ($address[$message_id][$ug_type] as $ug_id => $in_to) + { + $recipient_list[$ug_type][$ug_id] = array('name' => $user->lang['NA'], 'colour' => ''); + } + } + } + } + + foreach ($_types as $ug_type) + { + if (!empty($recipient_list[$ug_type])) + { + if ($ug_type == 'u') + { + $sql = 'SELECT user_id as id, username as name, user_colour as colour + FROM ' . USERS_TABLE . ' + WHERE '; + } + else + { + $sql = 'SELECT group_id as id, group_name as name, group_colour as colour, group_type + FROM ' . GROUPS_TABLE . ' + WHERE '; + } + $sql .= $db->sql_in_set(($ug_type == 'u') ? 'user_id' : 'group_id', array_map('intval', array_keys($recipient_list[$ug_type]))); + + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($ug_type == 'g') + { + $row['name'] = $group_helper->get_name($row['name']); + } + + $recipient_list[$ug_type][$row['id']] = array('name' => $row['name'], 'colour' => $row['colour']); + } + $db->sql_freeresult($result); + } + } + + foreach ($address as $message_id => $adr_ary) + { + foreach ($adr_ary as $type => $id_ary) + { + foreach ($id_ary as $ug_id => $_id) + { + if ($type == 'u') + { + $address_list[$message_id][] = get_username_string('full', $ug_id, $recipient_list[$type][$ug_id]['name'], $recipient_list[$type][$ug_id]['colour']); + } + else + { + $user_colour = ($recipient_list[$type][$ug_id]['colour']) ? ' style="font-weight: bold; color:#' . $recipient_list[$type][$ug_id]['colour'] . '"' : ''; + $link = ''; + $address_list[$message_id][] = $link . $recipient_list[$type][$ug_id]['name'] . (($link) ? '' : ''); + } + } + } + } + + return $address_list; +} diff --git a/includes/functions_transfer.php b/includes/functions_transfer.php new file mode 100644 index 0000000..7427b89 --- /dev/null +++ b/includes/functions_transfer.php @@ -0,0 +1,899 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Transfer class, wrapper for ftp/sftp/ssh +*/ +class transfer +{ + var $connection; + var $host; + var $port; + var $username; + var $password; + var $timeout; + var $root_path; + var $tmp_path; + var $file_perms; + var $dir_perms; + + /** + * Constructor - init some basic values + */ + function __construct() + { + global $phpbb_root_path; + + $this->file_perms = 0644; + $this->dir_perms = 0777; + + // We use the store directory as temporary path to circumvent open basedir restrictions + $this->tmp_path = $phpbb_root_path . 'store/'; + } + + /** + * Write file to location + */ + function write_file($destination_file = '', $contents = '') + { + global $phpbb_root_path; + + $destination_file = $this->root_path . str_replace($phpbb_root_path, '', $destination_file); + + // need to create a temp file and then move that temp file. + // ftp functions can only move files around and can't create. + // This means that the users will need to have access to write + // temporary files or have write access on a folder within phpBB + // like the cache folder. If the user can't do either, then + // he/she needs to use the fsock ftp method + $temp_name = tempnam($this->tmp_path, 'transfer_'); + @unlink($temp_name); + + $fp = @fopen($temp_name, 'w'); + + if (!$fp) + { + trigger_error('Unable to create temporary file ' . $temp_name, E_USER_ERROR); + } + + @fwrite($fp, $contents); + @fclose($fp); + + $result = $this->overwrite_file($temp_name, $destination_file); + + // remove temporary file now + @unlink($temp_name); + + return $result; + } + + /** + * Moving file into location. If the destination file already exists it gets overwritten + */ + function overwrite_file($source_file, $destination_file) + { + /** + * @todo generally think about overwriting files in another way, by creating a temporary file and then renaming it + * @todo check for the destination file existance too + */ + $this->_delete($destination_file); + $result = $this->_put($source_file, $destination_file); + $this->_chmod($destination_file, $this->file_perms); + + return $result; + } + + /** + * Create directory structure + */ + function make_dir($dir) + { + global $phpbb_root_path; + + $dir = str_replace($phpbb_root_path, '', $dir); + $dir = explode('/', $dir); + $dirs = ''; + + for ($i = 0, $total = count($dir); $i < $total; $i++) + { + $result = true; + + if (strpos($dir[$i], '.') === 0) + { + continue; + } + $cur_dir = $dir[$i] . '/'; + + if (!file_exists($phpbb_root_path . $dirs . $cur_dir)) + { + // create the directory + $result = $this->_mkdir($dir[$i]); + $this->_chmod($dir[$i], $this->dir_perms); + } + + $this->_chdir($this->root_path . $dirs . $dir[$i]); + $dirs .= $cur_dir; + } + + $this->_chdir($this->root_path); + + /** + * @todo stack result into array to make sure every path creation has been taken care of + */ + return $result; + } + + /** + * Copy file from source location to destination location + */ + function copy_file($from_loc, $to_loc) + { + global $phpbb_root_path; + + $from_loc = ((strpos($from_loc, $phpbb_root_path) !== 0) ? $phpbb_root_path : '') . $from_loc; + $to_loc = $this->root_path . str_replace($phpbb_root_path, '', $to_loc); + + if (!file_exists($from_loc)) + { + return false; + } + + $result = $this->overwrite_file($from_loc, $to_loc); + + return $result; + } + + /** + * Remove file + */ + function delete_file($file) + { + global $phpbb_root_path; + + $file = $this->root_path . str_replace($phpbb_root_path, '', $file); + + return $this->_delete($file); + } + + /** + * Remove directory + * @todo remove child directories? + */ + function remove_dir($dir) + { + global $phpbb_root_path; + + $dir = $this->root_path . str_replace($phpbb_root_path, '', $dir); + + return $this->_rmdir($dir); + } + + /** + * Rename a file or folder + */ + function rename($old_handle, $new_handle) + { + global $phpbb_root_path; + + $old_handle = $this->root_path . str_replace($phpbb_root_path, '', $old_handle); + + return $this->_rename($old_handle, $new_handle); + } + + /** + * Check if a specified file exist... + */ + function file_exists($directory, $filename) + { + global $phpbb_root_path; + + $directory = $this->root_path . str_replace($phpbb_root_path, '', $directory); + + $this->_chdir($directory); + $result = $this->_ls(); + + if ($result !== false && is_array($result)) + { + return (in_array($filename, $result)) ? true : false; + } + + return false; + } + + /** + * Open session + */ + function open_session() + { + return $this->_init(); + } + + /** + * Close current session + */ + function close_session() + { + return $this->_close(); + } + + /** + * Determine methods able to be used + */ + static public function methods() + { + $methods = array(); + $disabled_functions = explode(',', @ini_get('disable_functions')); + + if (@extension_loaded('ftp')) + { + $methods[] = 'ftp'; + } + + if (!in_array('fsockopen', $disabled_functions)) + { + $methods[] = 'ftp_fsock'; + } + + return $methods; + } +} + +/** +* FTP transfer class +*/ +class ftp extends transfer +{ + /** + * Standard parameters for FTP session + */ + function __construct($host, $username, $password, $root_path, $port = 21, $timeout = 10) + { + $this->host = $host; + $this->port = $port; + $this->username = $username; + $this->password = $password; + $this->timeout = $timeout; + + // Make sure $this->root_path is layed out the same way as the $user->page['root_script_path'] value (/ at the end) + $this->root_path = str_replace('\\', '/', $this->root_path); + + if (!empty($root_path)) + { + $this->root_path = (($root_path[0] != '/' ) ? '/' : '') . $root_path . ((substr($root_path, -1, 1) == '/') ? '' : '/'); + } + + // Init some needed values + $this->transfer(); + + return; + } + + /** + * Requests data + */ + static public function data() + { + global $user; + + return array( + 'host' => 'localhost', + 'username' => 'anonymous', + 'password' => '', + 'root_path' => $user->page['root_script_path'], + 'port' => 21, + 'timeout' => 10 + ); + } + + /** + * Init FTP Session + * @access private + */ + function _init() + { + // connect to the server + $this->connection = @ftp_connect($this->host, $this->port, $this->timeout); + + if (!$this->connection) + { + return 'ERR_CONNECTING_SERVER'; + } + + // login to the server + if (!@ftp_login($this->connection, $this->username, $this->password)) + { + return 'ERR_UNABLE_TO_LOGIN'; + } + + // attempt to turn pasv mode on + @ftp_pasv($this->connection, true); + + // change to the root directory + if (!$this->_chdir($this->root_path)) + { + return 'ERR_CHANGING_DIRECTORY'; + } + + return true; + } + + /** + * Create Directory (MKDIR) + * @access private + */ + function _mkdir($dir) + { + return @ftp_mkdir($this->connection, $dir); + } + + /** + * Remove directory (RMDIR) + * @access private + */ + function _rmdir($dir) + { + return @ftp_rmdir($this->connection, $dir); + } + + /** + * Rename file + * @access private + */ + function _rename($old_handle, $new_handle) + { + return @ftp_rename($this->connection, $old_handle, $new_handle); + } + + /** + * Change current working directory (CHDIR) + * @access private + */ + function _chdir($dir = '') + { + if ($dir && $dir !== '/') + { + if (substr($dir, -1, 1) == '/') + { + $dir = substr($dir, 0, -1); + } + } + + return @ftp_chdir($this->connection, $dir); + } + + /** + * change file permissions (CHMOD) + * @access private + */ + function _chmod($file, $perms) + { + if (function_exists('ftp_chmod')) + { + $err = @ftp_chmod($this->connection, $perms, $file); + } + else + { + // Unfortunatly CHMOD is not expecting an octal value... + // We need to transform the integer (which was an octal) to an octal representation (to get the int) and then pass as is. ;) + $chmod_cmd = 'CHMOD ' . base_convert($perms, 10, 8) . ' ' . $file; + $err = $this->_site($chmod_cmd); + } + + return $err; + } + + /** + * Upload file to location (PUT) + * @access private + */ + function _put($from_file, $to_file) + { + // We only use the BINARY file mode to cicumvent rewrite actions from ftp server (mostly linefeeds being replaced) + $mode = FTP_BINARY; + + $to_dir = dirname($to_file); + $to_file = basename($to_file); + $this->_chdir($to_dir); + + $result = @ftp_put($this->connection, $to_file, $from_file, $mode); + $this->_chdir($this->root_path); + + return $result; + } + + /** + * Delete file (DELETE) + * @access private + */ + function _delete($file) + { + return @ftp_delete($this->connection, $file); + } + + /** + * Close ftp session (CLOSE) + * @access private + */ + function _close() + { + if (!$this->connection) + { + return false; + } + + return @ftp_quit($this->connection); + } + + /** + * Return current working directory (CWD) + * At the moment not used by parent class + * @access private + */ + function _cwd() + { + return @ftp_pwd($this->connection); + } + + /** + * Return list of files in a given directory (LS) + * @access private + */ + function _ls($dir = './') + { + $list = @ftp_nlist($this->connection, $dir); + + // See bug #46295 - Some FTP daemons don't like './' + if ($dir === './') + { + // Let's try some alternatives + $list = (empty($list)) ? @ftp_nlist($this->connection, '.') : $list; + $list = (empty($list)) ? @ftp_nlist($this->connection, '') : $list; + } + + // Return on error + if ($list === false) + { + return false; + } + + // Remove path if prepended + foreach ($list as $key => $item) + { + // Use same separator for item and dir + $item = str_replace('\\', '/', $item); + $dir = str_replace('\\', '/', $dir); + + if (!empty($dir) && strpos($item, $dir) === 0) + { + $item = substr($item, strlen($dir)); + } + + $list[$key] = $item; + } + + return $list; + } + + /** + * FTP SITE command (ftp-only function) + * @access private + */ + function _site($command) + { + return @ftp_site($this->connection, $command); + } +} + +/** +* FTP fsock transfer class +*/ +class ftp_fsock extends transfer +{ + var $data_connection; + + /** + * Standard parameters for FTP session + */ + function __construct($host, $username, $password, $root_path, $port = 21, $timeout = 10) + { + $this->host = $host; + $this->port = $port; + $this->username = $username; + $this->password = $password; + $this->timeout = $timeout; + + // Make sure $this->root_path is layed out the same way as the $user->page['root_script_path'] value (/ at the end) + $this->root_path = str_replace('\\', '/', $this->root_path); + + if (!empty($root_path)) + { + $this->root_path = (($root_path[0] != '/' ) ? '/' : '') . $root_path . ((substr($root_path, -1, 1) == '/') ? '' : '/'); + } + + // Init some needed values + parent::__construct(); + + return; + } + + /** + * Requests data + */ + static public function data() + { + global $user; + + return array( + 'host' => 'localhost', + 'username' => 'anonymous', + 'password' => '', + 'root_path' => $user->page['root_script_path'], + 'port' => 21, + 'timeout' => 10 + ); + } + + /** + * Init FTP Session + * @access private + */ + function _init() + { + $errno = 0; + $errstr = ''; + + // connect to the server + $this->connection = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout); + + if (!$this->connection || !$this->_check_command()) + { + return 'ERR_CONNECTING_SERVER'; + } + + @stream_set_timeout($this->connection, $this->timeout); + + // login + if (!$this->_send_command('USER', $this->username)) + { + return 'ERR_UNABLE_TO_LOGIN'; + } + + if (!$this->_send_command('PASS', $this->password)) + { + return 'ERR_UNABLE_TO_LOGIN'; + } + + // change to the root directory + if (!$this->_chdir($this->root_path)) + { + return 'ERR_CHANGING_DIRECTORY'; + } + + return true; + } + + /** + * Create Directory (MKDIR) + * @access private + */ + function _mkdir($dir) + { + return $this->_send_command('MKD', $dir); + } + + /** + * Remove directory (RMDIR) + * @access private + */ + function _rmdir($dir) + { + return $this->_send_command('RMD', $dir); + } + + /** + * Rename File + * @access private + */ + function _rename($old_handle, $new_handle) + { + $this->_send_command('RNFR', $old_handle); + return $this->_send_command('RNTO', $new_handle); + } + + /** + * Change current working directory (CHDIR) + * @access private + */ + function _chdir($dir = '') + { + if ($dir && $dir !== '/') + { + if (substr($dir, -1, 1) == '/') + { + $dir = substr($dir, 0, -1); + } + } + + return $this->_send_command('CWD', $dir); + } + + /** + * change file permissions (CHMOD) + * @access private + */ + function _chmod($file, $perms) + { + // Unfortunatly CHMOD is not expecting an octal value... + // We need to transform the integer (which was an octal) to an octal representation (to get the int) and then pass as is. ;) + return $this->_send_command('SITE CHMOD', base_convert($perms, 10, 8) . ' ' . $file); + } + + /** + * Upload file to location (PUT) + * @access private + */ + function _put($from_file, $to_file) + { + // We only use the BINARY file mode to cicumvent rewrite actions from ftp server (mostly linefeeds being replaced) + // 'I' == BINARY + // 'A' == ASCII + if (!$this->_send_command('TYPE', 'I')) + { + return false; + } + + // open the connection to send file over + if (!$this->_open_data_connection()) + { + return false; + } + + $this->_send_command('STOR', $to_file, false); + + // send the file + $fp = @fopen($from_file, 'rb'); + while (!@feof($fp)) + { + @fwrite($this->data_connection, @fread($fp, 4096)); + } + @fclose($fp); + + // close connection + $this->_close_data_connection(); + + return $this->_check_command(); + } + + /** + * Delete file (DELETE) + * @access private + */ + function _delete($file) + { + return $this->_send_command('DELE', $file); + } + + /** + * Close ftp session (CLOSE) + * @access private + */ + function _close() + { + if (!$this->connection) + { + return false; + } + + return $this->_send_command('QUIT'); + } + + /** + * Return current working directory (CWD) + * At the moment not used by parent class + * @access private + */ + function _cwd() + { + $this->_send_command('PWD', '', false); + return preg_replace('#^[0-9]{3} "(.+)" .+\r\n#', '\\1', $this->_check_command(true)); + } + + /** + * Return list of files in a given directory (LS) + * @access private + */ + function _ls($dir = './') + { + if (!$this->_open_data_connection()) + { + return false; + } + + $this->_send_command('NLST', $dir); + + $list = array(); + while (!@feof($this->data_connection)) + { + $filename = preg_replace('#[\r\n]#', '', @fgets($this->data_connection, 512)); + + if ($filename !== '') + { + $list[] = $filename; + } + } + $this->_close_data_connection(); + + // Clear buffer + $this->_check_command(); + + // See bug #46295 - Some FTP daemons don't like './' + if ($dir === './' && empty($list)) + { + // Let's try some alternatives + $list = $this->_ls('.'); + + if (empty($list)) + { + $list = $this->_ls(''); + } + + return $list; + } + + // Remove path if prepended + foreach ($list as $key => $item) + { + // Use same separator for item and dir + $item = str_replace('\\', '/', $item); + $dir = str_replace('\\', '/', $dir); + + if (!empty($dir) && strpos($item, $dir) === 0) + { + $item = substr($item, strlen($dir)); + } + + $list[$key] = $item; + } + + return $list; + } + + /** + * Send a command to server (FTP fsock only function) + * @access private + */ + function _send_command($command, $args = '', $check = true) + { + if (!empty($args)) + { + $command = "$command $args"; + } + + fwrite($this->connection, $command . "\r\n"); + + if ($check === true && !$this->_check_command()) + { + return false; + } + + return true; + } + + /** + * Opens a connection to send data (FTP fosck only function) + * @access private + */ + function _open_data_connection() + { + // Try to find out whether we have a IPv4 or IPv6 (control) connection + if (function_exists('stream_socket_get_name')) + { + $socket_name = stream_socket_get_name($this->connection, true); + $server_ip = substr($socket_name, 0, strrpos($socket_name, ':')); + } + + if (!isset($server_ip) || preg_match(get_preg_expression('ipv4'), $server_ip)) + { + // Passive mode + $this->_send_command('PASV', '', false); + + if (!$ip_port = $this->_check_command(true)) + { + return false; + } + + // open the connection to start sending the file + if (!preg_match('#[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]{1,3},[0-9]+,[0-9]+#', $ip_port, $temp)) + { + // bad ip and port + return false; + } + + $temp = explode(',', $temp[0]); + $server_ip = $temp[0] . '.' . $temp[1] . '.' . $temp[2] . '.' . $temp[3]; + $server_port = $temp[4] * 256 + $temp[5]; + } + else + { + // Extended Passive Mode - RFC2428 + $this->_send_command('EPSV', '', false); + + if (!$epsv_response = $this->_check_command(true)) + { + return false; + } + + // Response looks like "229 Entering Extended Passive Mode (|||12345|)" + // where 12345 is the tcp port for the data connection + if (!preg_match('#\(\|\|\|([0-9]+)\|\)#', $epsv_response, $match)) + { + return false; + } + $server_port = (int) $match[1]; + + // fsockopen expects IPv6 address in square brackets + $server_ip = "[$server_ip]"; + } + + $errno = 0; + $errstr = ''; + + if (!$this->data_connection = @fsockopen($server_ip, $server_port, $errno, $errstr, $this->timeout)) + { + return false; + } + @stream_set_timeout($this->data_connection, $this->timeout); + + return true; + } + + /** + * Closes a connection used to send data + * @access private + */ + function _close_data_connection() + { + return @fclose($this->data_connection); + } + + /** + * Check to make sure command was successful (FTP fsock only function) + * @access private + */ + function _check_command($return = false) + { + $response = ''; + + do + { + $result = @fgets($this->connection, 512); + $response .= $result; + } + while (substr($result, 3, 1) !== ' '); + + if (!preg_match('#^[123]#', $response)) + { + return false; + } + + return ($return) ? $response : true; + } +} diff --git a/includes/functions_user.php b/includes/functions_user.php new file mode 100644 index 0000000..fb9241d --- /dev/null +++ b/includes/functions_user.php @@ -0,0 +1,3759 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Obtain user_ids from usernames or vice versa. Returns false on +* success else the error string +* +* @param array &$user_id_ary The user ids to check or empty if usernames used +* @param array &$username_ary The usernames to check or empty if user ids used +* @param mixed $user_type Array of user types to check, false if not restricting by user type +* @param boolean $update_references If false, the supplied array is unset and appears unchanged from where it was called +* @return boolean|string Returns false on success, error string on failure +*/ +function user_get_id_name(&$user_id_ary, &$username_ary, $user_type = false, $update_references = false) +{ + global $db; + + // Are both arrays already filled? Yep, return else + // are neither array filled? + if ($user_id_ary && $username_ary) + { + return false; + } + else if (!$user_id_ary && !$username_ary) + { + return 'NO_USERS'; + } + + $which_ary = ($user_id_ary) ? 'user_id_ary' : 'username_ary'; + + if (${$which_ary} && !is_array(${$which_ary})) + { + ${$which_ary} = array(${$which_ary}); + } + + $sql_in = ($which_ary == 'user_id_ary') ? array_map('intval', ${$which_ary}) : array_map('utf8_clean_string', ${$which_ary}); + + // By unsetting the array here, the values passed in at the point user_get_id_name() was called will be retained. + // Otherwise, if we don't unset (as the array was passed by reference) the original array will be updated below. + if ($update_references === false) + { + unset(${$which_ary}); + } + + $user_id_ary = $username_ary = array(); + + // Grab the user id/username records + $sql_where = ($which_ary == 'user_id_ary') ? 'user_id' : 'username_clean'; + $sql = 'SELECT user_id, username + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set($sql_where, $sql_in); + + if ($user_type !== false && !empty($user_type)) + { + $sql .= ' AND ' . $db->sql_in_set('user_type', $user_type); + } + + $result = $db->sql_query($sql); + + if (!($row = $db->sql_fetchrow($result))) + { + $db->sql_freeresult($result); + return 'NO_USERS'; + } + + do + { + $username_ary[$row['user_id']] = $row['username']; + $user_id_ary[] = $row['user_id']; + } + while ($row = $db->sql_fetchrow($result)); + $db->sql_freeresult($result); + + return false; +} + +/** +* Get latest registered username and update database to reflect it +*/ +function update_last_username() +{ + global $config, $db; + + // Get latest username + $sql = 'SELECT user_id, username, user_colour + FROM ' . USERS_TABLE . ' + WHERE user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ') + ORDER BY user_id DESC'; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $config->set('newest_user_id', $row['user_id'], false); + $config->set('newest_username', $row['username'], false); + $config->set('newest_user_colour', $row['user_colour'], false); + } +} + +/** +* Updates a username across all relevant tables/fields +* +* @param string $old_name the old/current username +* @param string $new_name the new username +*/ +function user_update_name($old_name, $new_name) +{ + global $config, $db, $cache, $phpbb_dispatcher; + + $update_ary = array( + FORUMS_TABLE => array( + 'forum_last_poster_id' => 'forum_last_poster_name', + ), + MODERATOR_CACHE_TABLE => array( + 'user_id' => 'username', + ), + POSTS_TABLE => array( + 'poster_id' => 'post_username', + ), + TOPICS_TABLE => array( + 'topic_poster' => 'topic_first_poster_name', + 'topic_last_poster_id' => 'topic_last_poster_name', + ), + ); + + foreach ($update_ary as $table => $field_ary) + { + foreach ($field_ary as $id_field => $name_field) + { + $sql = "UPDATE $table + SET $name_field = '" . $db->sql_escape($new_name) . "' + WHERE $name_field = '" . $db->sql_escape($old_name) . "' + AND $id_field <> " . ANONYMOUS; + $db->sql_query($sql); + } + } + + if ($config['newest_username'] == $old_name) + { + $config->set('newest_username', $new_name, false); + } + + /** + * Update a username when it is changed + * + * @event core.update_username + * @var string old_name The old username that is replaced + * @var string new_name The new username + * @since 3.1.0-a1 + */ + $vars = array('old_name', 'new_name'); + extract($phpbb_dispatcher->trigger_event('core.update_username', compact($vars))); + + // Because some tables/caches use username-specific data we need to purge this here. + $cache->destroy('sql', MODERATOR_CACHE_TABLE); +} + +/** +* Adds an user +* +* @param mixed $user_row An array containing the following keys (and the appropriate values): username, group_id (the group to place the user in), user_email and the user_type(usually 0). Additional entries not overridden by defaults will be forwarded. +* @param array $cp_data custom profile fields, see custom_profile::build_insert_sql_array +* @param array $notifications_data The notifications settings for the new user +* @return the new user's ID. +*/ +function user_add($user_row, $cp_data = false, $notifications_data = null) +{ + global $db, $config; + global $phpbb_dispatcher, $phpbb_container; + + if (empty($user_row['username']) || !isset($user_row['group_id']) || !isset($user_row['user_email']) || !isset($user_row['user_type'])) + { + return false; + } + + $username_clean = utf8_clean_string($user_row['username']); + + if (empty($username_clean)) + { + return false; + } + + $sql_ary = array( + 'username' => $user_row['username'], + 'username_clean' => $username_clean, + 'user_password' => (isset($user_row['user_password'])) ? $user_row['user_password'] : '', + 'user_email' => strtolower($user_row['user_email']), + 'user_email_hash' => phpbb_email_hash($user_row['user_email']), + 'group_id' => $user_row['group_id'], + 'user_type' => $user_row['user_type'], + ); + + // These are the additional vars able to be specified + $additional_vars = array( + 'user_permissions' => '', + 'user_timezone' => $config['board_timezone'], + 'user_dateformat' => $config['default_dateformat'], + 'user_lang' => $config['default_lang'], + 'user_style' => (int) $config['default_style'], + 'user_actkey' => '', + 'user_ip' => '', + 'user_regdate' => time(), + 'user_passchg' => time(), + 'user_options' => 230271, + // We do not set the new flag here - registration scripts need to specify it + 'user_new' => 0, + + 'user_inactive_reason' => 0, + 'user_inactive_time' => 0, + 'user_lastmark' => time(), + 'user_lastvisit' => 0, + 'user_lastpost_time' => 0, + 'user_lastpage' => '', + 'user_posts' => 0, + 'user_colour' => '', + 'user_avatar' => '', + 'user_avatar_type' => '', + 'user_avatar_width' => 0, + 'user_avatar_height' => 0, + 'user_new_privmsg' => 0, + 'user_unread_privmsg' => 0, + 'user_last_privmsg' => 0, + 'user_message_rules' => 0, + 'user_full_folder' => PRIVMSGS_NO_BOX, + 'user_emailtime' => 0, + + 'user_notify' => 0, + 'user_notify_pm' => 1, + 'user_notify_type' => NOTIFY_EMAIL, + 'user_allow_pm' => 1, + 'user_allow_viewonline' => 1, + 'user_allow_viewemail' => 1, + 'user_allow_massemail' => 1, + + 'user_sig' => '', + 'user_sig_bbcode_uid' => '', + 'user_sig_bbcode_bitfield' => '', + + 'user_form_salt' => unique_id(), + ); + + // Now fill the sql array with not required variables + foreach ($additional_vars as $key => $default_value) + { + $sql_ary[$key] = (isset($user_row[$key])) ? $user_row[$key] : $default_value; + } + + // Any additional variables in $user_row not covered above? + $remaining_vars = array_diff(array_keys($user_row), array_keys($sql_ary)); + + // Now fill our sql array with the remaining vars + if (count($remaining_vars)) + { + foreach ($remaining_vars as $key) + { + $sql_ary[$key] = $user_row[$key]; + } + } + + /** + * Use this event to modify the values to be inserted when a user is added + * + * @event core.user_add_modify_data + * @var array user_row Array of user details submitted to user_add + * @var array cp_data Array of Custom profile fields submitted to user_add + * @var array sql_ary Array of data to be inserted when a user is added + * @var array notifications_data Array of notification data to be inserted when a user is added + * @since 3.1.0-a1 + * @changed 3.1.0-b5 Added user_row and cp_data + * @changed 3.1.11-RC1 Added notifications_data + */ + $vars = array('user_row', 'cp_data', 'sql_ary', 'notifications_data'); + extract($phpbb_dispatcher->trigger_event('core.user_add_modify_data', compact($vars))); + + $sql = 'INSERT INTO ' . USERS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + + $user_id = $db->sql_nextid(); + + // Insert Custom Profile Fields + if ($cp_data !== false && count($cp_data)) + { + $cp_data['user_id'] = (int) $user_id; + + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + $sql = 'INSERT INTO ' . PROFILE_FIELDS_DATA_TABLE . ' ' . + $db->sql_build_array('INSERT', $cp->build_insert_sql_array($cp_data)); + $db->sql_query($sql); + } + + // Place into appropriate group... + $sql = 'INSERT INTO ' . USER_GROUP_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'user_id' => (int) $user_id, + 'group_id' => (int) $user_row['group_id'], + 'user_pending' => 0) + ); + $db->sql_query($sql); + + // Now make it the users default group... + group_set_user_default($user_row['group_id'], array($user_id), false); + + // Add to newly registered users group if user_new is 1 + if ($config['new_member_post_limit'] && $sql_ary['user_new']) + { + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'NEWLY_REGISTERED' + AND group_type = " . GROUP_SPECIAL; + $result = $db->sql_query($sql); + $add_group_id = (int) $db->sql_fetchfield('group_id'); + $db->sql_freeresult($result); + + if ($add_group_id) + { + global $phpbb_log; + + // Because these actions only fill the log unnecessarily, we disable it + $phpbb_log->disable('admin'); + + // Add user to "newly registered users" group and set to default group if admin specified so. + if ($config['new_member_group_default']) + { + group_user_add($add_group_id, $user_id, false, false, true); + $user_row['group_id'] = $add_group_id; + } + else + { + group_user_add($add_group_id, $user_id); + } + + $phpbb_log->enable('admin'); + } + } + + // set the newest user and adjust the user count if the user is a normal user and no activation mail is sent + if ($user_row['user_type'] == USER_NORMAL || $user_row['user_type'] == USER_FOUNDER) + { + $config->set('newest_user_id', $user_id, false); + $config->set('newest_username', $user_row['username'], false); + $config->increment('num_users', 1, false); + + $sql = 'SELECT group_colour + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . (int) $user_row['group_id']; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $config->set('newest_user_colour', $row['group_colour'], false); + } + + // Use default notifications settings if notifications_data is not set + if ($notifications_data === null) + { + $notifications_data = array( + array( + 'item_type' => 'notification.type.post', + 'method' => 'notification.method.email', + ), + array( + 'item_type' => 'notification.type.topic', + 'method' => 'notification.method.email', + ), + ); + } + + /** + * Modify the notifications data to be inserted in the database when a user is added + * + * @event core.user_add_modify_notifications_data + * @var array user_row Array of user details submitted to user_add + * @var array cp_data Array of Custom profile fields submitted to user_add + * @var array sql_ary Array of data to be inserted when a user is added + * @var array notifications_data Array of notification data to be inserted when a user is added + * @since 3.2.2-RC1 + */ + $vars = array('user_row', 'cp_data', 'sql_ary', 'notifications_data'); + extract($phpbb_dispatcher->trigger_event('core.user_add_modify_notifications_data', compact($vars))); + + // Subscribe user to notifications if necessary + if (!empty($notifications_data)) + { + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + foreach ($notifications_data as $subscription) + { + $phpbb_notifications->add_subscription($subscription['item_type'], 0, $subscription['method'], $user_id); + } + } + + /** + * Event that returns user id, user details and user CPF of newly registered user + * + * @event core.user_add_after + * @var int user_id User id of newly registered user + * @var array user_row Array of user details submitted to user_add + * @var array cp_data Array of Custom profile fields submitted to user_add + * @since 3.1.0-b5 + */ + $vars = array('user_id', 'user_row', 'cp_data'); + extract($phpbb_dispatcher->trigger_event('core.user_add_after', compact($vars))); + + return $user_id; +} + +/** + * Remove User + * + * @param string $mode Either 'retain' or 'remove' + * @param mixed $user_ids Either an array of integers or an integer + * @param bool $retain_username + * @return bool + */ +function user_delete($mode, $user_ids, $retain_username = true) +{ + global $cache, $config, $db, $user, $phpbb_dispatcher, $phpbb_container; + global $phpbb_root_path, $phpEx; + + $db->sql_transaction('begin'); + + $user_rows = array(); + if (!is_array($user_ids)) + { + $user_ids = array($user_ids); + } + + $user_id_sql = $db->sql_in_set('user_id', $user_ids); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE ' . $user_id_sql; + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) + { + $user_rows[(int) $row['user_id']] = $row; + } + $db->sql_freeresult($result); + + if (empty($user_rows)) + { + return false; + } + + /** + * Event before a user is deleted + * + * @event core.delete_user_before + * @var string mode Mode of deletion (retain/delete posts) + * @var array user_ids IDs of the deleted user + * @var mixed retain_username True if username should be retained + * or false if not + * @var array user_rows Array containing data of the deleted users + * @since 3.1.0-a1 + * @changed 3.2.4-RC1 Added user_rows + */ + $vars = array('mode', 'user_ids', 'retain_username', 'user_rows'); + extract($phpbb_dispatcher->trigger_event('core.delete_user_before', compact($vars))); + + // Before we begin, we will remove the reports the user issued. + $sql = 'SELECT r.post_id, p.topic_id + FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p + WHERE ' . $db->sql_in_set('r.user_id', $user_ids) . ' + AND p.post_id = r.post_id'; + $result = $db->sql_query($sql); + + $report_posts = $report_topics = array(); + while ($row = $db->sql_fetchrow($result)) + { + $report_posts[] = $row['post_id']; + $report_topics[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + if (count($report_posts)) + { + $report_posts = array_unique($report_posts); + $report_topics = array_unique($report_topics); + + // Get a list of topics that still contain reported posts + $sql = 'SELECT DISTINCT topic_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $report_topics) . ' + AND post_reported = 1 + AND ' . $db->sql_in_set('post_id', $report_posts, true); + $result = $db->sql_query($sql); + + $keep_report_topics = array(); + while ($row = $db->sql_fetchrow($result)) + { + $keep_report_topics[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + if (count($keep_report_topics)) + { + $report_topics = array_diff($report_topics, $keep_report_topics); + } + unset($keep_report_topics); + + // Now set the flags back + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_reported = 0 + WHERE ' . $db->sql_in_set('post_id', $report_posts); + $db->sql_query($sql); + + if (count($report_topics)) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_reported = 0 + WHERE ' . $db->sql_in_set('topic_id', $report_topics); + $db->sql_query($sql); + } + } + + // Remove reports + $db->sql_query('DELETE FROM ' . REPORTS_TABLE . ' WHERE ' . $user_id_sql); + + $num_users_delta = 0; + + // Get auth provider collection in case accounts might need to be unlinked + $provider_collection = $phpbb_container->get('auth.provider_collection'); + + // Some things need to be done in the loop (if the query changes based + // on which user is currently being deleted) + $added_guest_posts = 0; + foreach ($user_rows as $user_id => $user_row) + { + if ($user_row['user_avatar'] && $user_row['user_avatar_type'] == 'avatar.driver.upload') + { + avatar_delete('user', $user_row); + } + + // Unlink accounts + foreach ($provider_collection as $provider_name => $auth_provider) + { + $provider_data = $auth_provider->get_auth_link_data($user_id); + + if ($provider_data !== null) + { + $link_data = array( + 'user_id' => $user_id, + 'link_method' => 'user_delete', + ); + + // BLOCK_VARS might contain hidden fields necessary for unlinking accounts + if (isset($provider_data['BLOCK_VARS']) && is_array($provider_data['BLOCK_VARS'])) + { + foreach ($provider_data['BLOCK_VARS'] as $provider_service) + { + if (!array_key_exists('HIDDEN_FIELDS', $provider_service)) + { + $provider_service['HIDDEN_FIELDS'] = array(); + } + + $auth_provider->unlink_account(array_merge($link_data, $provider_service['HIDDEN_FIELDS'])); + } + } + else + { + $auth_provider->unlink_account($link_data); + } + } + } + + // Decrement number of users if this user is active + if ($user_row['user_type'] != USER_INACTIVE && $user_row['user_type'] != USER_IGNORE) + { + --$num_users_delta; + } + + switch ($mode) + { + case 'retain': + if ($retain_username === false) + { + $post_username = $user->lang['GUEST']; + } + else + { + $post_username = $user_row['username']; + } + + // If the user is inactive and newly registered + // we assume no posts from the user, and save + // the queries + if ($user_row['user_type'] != USER_INACTIVE || $user_row['user_inactive_reason'] != INACTIVE_REGISTER || $user_row['user_posts']) + { + // When we delete these users and retain the posts, we must assign all the data to the guest user + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET forum_last_poster_id = ' . ANONYMOUS . ", forum_last_poster_name = '" . $db->sql_escape($post_username) . "', forum_last_poster_colour = '' + WHERE forum_last_poster_id = $user_id"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET poster_id = ' . ANONYMOUS . ", post_username = '" . $db->sql_escape($post_username) . "' + WHERE poster_id = $user_id"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_poster = ' . ANONYMOUS . ", topic_first_poster_name = '" . $db->sql_escape($post_username) . "', topic_first_poster_colour = '' + WHERE topic_poster = $user_id"; + $db->sql_query($sql); + + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_last_poster_id = ' . ANONYMOUS . ", topic_last_poster_name = '" . $db->sql_escape($post_username) . "', topic_last_poster_colour = '' + WHERE topic_last_poster_id = $user_id"; + $db->sql_query($sql); + + // Since we change every post by this author, we need to count this amount towards the anonymous user + + if ($user_row['user_posts']) + { + $added_guest_posts += $user_row['user_posts']; + } + } + break; + + case 'remove': + // there is nothing variant specific to deleting posts + break; + } + } + + if ($num_users_delta != 0) + { + $config->increment('num_users', $num_users_delta, false); + } + + // Now do the invariant tasks + // all queries performed in one call of this function are in a single transaction + // so this is kosher + if ($mode == 'retain') + { + // Assign more data to the Anonymous user + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET poster_id = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('poster_id', $user_ids); + $db->sql_query($sql); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = user_posts + ' . $added_guest_posts . ' + WHERE user_id = ' . ANONYMOUS; + $db->sql_query($sql); + } + else if ($mode == 'remove') + { + if (!function_exists('delete_posts')) + { + include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + } + + // Delete posts, attachments, etc. + // delete_posts can handle any number of IDs in its second argument + delete_posts('poster_id', $user_ids); + } + + $table_ary = [ + USERS_TABLE, + USER_GROUP_TABLE, + TOPICS_WATCH_TABLE, + FORUMS_WATCH_TABLE, + ACL_USERS_TABLE, + TOPICS_TRACK_TABLE, + TOPICS_POSTED_TABLE, + FORUMS_TRACK_TABLE, + PROFILE_FIELDS_DATA_TABLE, + MODERATOR_CACHE_TABLE, + DRAFTS_TABLE, + BOOKMARKS_TABLE, + SESSIONS_KEYS_TABLE, + PRIVMSGS_FOLDER_TABLE, + PRIVMSGS_RULES_TABLE, + $phpbb_container->getParameter('tables.auth_provider_oauth_token_storage'), + $phpbb_container->getParameter('tables.auth_provider_oauth_states'), + $phpbb_container->getParameter('tables.auth_provider_oauth_account_assoc'), + $phpbb_container->getParameter('tables.user_notifications') + ]; + + // Ignore errors on deleting from non-existent tables, e.g. when migrating + $db->sql_return_on_error(true); + // Delete the miscellaneous (non-post) data for the user + foreach ($table_ary as $table) + { + $sql = "DELETE FROM $table + WHERE " . $user_id_sql; + $db->sql_query($sql); + } + $db->sql_return_on_error(); + + $cache->destroy('sql', MODERATOR_CACHE_TABLE); + + // Change user_id to anonymous for posts edited by this user + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_edit_user = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('post_edit_user', $user_ids); + $db->sql_query($sql); + + // Change user_id to anonymous for pms edited by this user + $sql = 'UPDATE ' . PRIVMSGS_TABLE . ' + SET message_edit_user = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('message_edit_user', $user_ids); + $db->sql_query($sql); + + // Change user_id to anonymous for posts deleted by this user + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_delete_user = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('post_delete_user', $user_ids); + $db->sql_query($sql); + + // Change user_id to anonymous for topics deleted by this user + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_delete_user = ' . ANONYMOUS . ' + WHERE ' . $db->sql_in_set('topic_delete_user', $user_ids); + $db->sql_query($sql); + + // Delete user log entries about this user + $sql = 'DELETE FROM ' . LOG_TABLE . ' + WHERE ' . $db->sql_in_set('reportee_id', $user_ids); + $db->sql_query($sql); + + // Change user_id to anonymous for this users triggered events + $sql = 'UPDATE ' . LOG_TABLE . ' + SET user_id = ' . ANONYMOUS . ' + WHERE ' . $user_id_sql; + $db->sql_query($sql); + + // Delete the user_id from the zebra table + $sql = 'DELETE FROM ' . ZEBRA_TABLE . ' + WHERE ' . $user_id_sql . ' + OR ' . $db->sql_in_set('zebra_id', $user_ids); + $db->sql_query($sql); + + // Delete the user_id from the banlist + $sql = 'DELETE FROM ' . BANLIST_TABLE . ' + WHERE ' . $db->sql_in_set('ban_userid', $user_ids); + $db->sql_query($sql); + + // Delete the user_id from the session table + $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' + WHERE ' . $db->sql_in_set('session_user_id', $user_ids); + $db->sql_query($sql); + + // Clean the private messages tables from the user + if (!function_exists('phpbb_delete_user_pms')) + { + include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); + } + phpbb_delete_users_pms($user_ids); + + $phpbb_notifications = $phpbb_container->get('notification_manager'); + $phpbb_notifications->delete_notifications('notification.type.admin_activate_user', $user_ids); + + $db->sql_transaction('commit'); + + /** + * Event after a user is deleted + * + * @event core.delete_user_after + * @var string mode Mode of deletion (retain/delete posts) + * @var array user_ids IDs of the deleted user + * @var mixed retain_username True if username should be retained + * or false if not + * @var array user_rows Array containing data of the deleted users + * @since 3.1.0-a1 + * @changed 3.2.2-RC1 Added user_rows + */ + $vars = array('mode', 'user_ids', 'retain_username', 'user_rows'); + extract($phpbb_dispatcher->trigger_event('core.delete_user_after', compact($vars))); + + // Reset newest user info if appropriate + if (in_array($config['newest_user_id'], $user_ids)) + { + update_last_username(); + } + + return false; +} + +/** +* Flips user_type from active to inactive and vice versa, handles group membership updates +* +* @param string $mode can be flip for flipping from active/inactive, activate or deactivate +*/ +function user_active_flip($mode, $user_id_ary, $reason = INACTIVE_MANUAL) +{ + global $config, $db, $user, $auth, $phpbb_dispatcher; + + $deactivated = $activated = 0; + $sql_statements = array(); + + if (!is_array($user_id_ary)) + { + $user_id_ary = array($user_id_ary); + } + + if (!count($user_id_ary)) + { + return; + } + + $sql = 'SELECT user_id, group_id, user_type, user_inactive_reason + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $user_id_ary); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary = array(); + + if ($row['user_type'] == USER_IGNORE || $row['user_type'] == USER_FOUNDER || + ($mode == 'activate' && $row['user_type'] != USER_INACTIVE) || + ($mode == 'deactivate' && $row['user_type'] == USER_INACTIVE)) + { + continue; + } + + if ($row['user_type'] == USER_INACTIVE) + { + $activated++; + } + else + { + $deactivated++; + + // Remove the users session key... + $user->reset_login_keys($row['user_id']); + } + + $sql_ary += array( + 'user_type' => ($row['user_type'] == USER_NORMAL) ? USER_INACTIVE : USER_NORMAL, + 'user_inactive_time' => ($row['user_type'] == USER_NORMAL) ? time() : 0, + 'user_inactive_reason' => ($row['user_type'] == USER_NORMAL) ? $reason : 0, + ); + + $sql_statements[$row['user_id']] = $sql_ary; + } + $db->sql_freeresult($result); + + /** + * Check or modify activated/deactivated users data before submitting it to the database + * + * @event core.user_active_flip_before + * @var string mode User type changing mode, can be: flip|activate|deactivate + * @var int reason Reason for changing user type, can be: INACTIVE_REGISTER|INACTIVE_PROFILE|INACTIVE_MANUAL|INACTIVE_REMIND + * @var int activated The number of users to be activated + * @var int deactivated The number of users to be deactivated + * @var array user_id_ary Array with user ids to change user type + * @var array sql_statements Array with users data to submit to the database, keys: user ids, values: arrays with user data + * @since 3.1.4-RC1 + */ + $vars = array('mode', 'reason', 'activated', 'deactivated', 'user_id_ary', 'sql_statements'); + extract($phpbb_dispatcher->trigger_event('core.user_active_flip_before', compact($vars))); + + if (count($sql_statements)) + { + foreach ($sql_statements as $user_id => $sql_ary) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user_id; + $db->sql_query($sql); + } + + $auth->acl_clear_prefetch(array_keys($sql_statements)); + } + + /** + * Perform additional actions after the users have been activated/deactivated + * + * @event core.user_active_flip_after + * @var string mode User type changing mode, can be: flip|activate|deactivate + * @var int reason Reason for changing user type, can be: INACTIVE_REGISTER|INACTIVE_PROFILE|INACTIVE_MANUAL|INACTIVE_REMIND + * @var int activated The number of users to be activated + * @var int deactivated The number of users to be deactivated + * @var array user_id_ary Array with user ids to change user type + * @var array sql_statements Array with users data to submit to the database, keys: user ids, values: arrays with user data + * @since 3.1.4-RC1 + */ + $vars = array('mode', 'reason', 'activated', 'deactivated', 'user_id_ary', 'sql_statements'); + extract($phpbb_dispatcher->trigger_event('core.user_active_flip_after', compact($vars))); + + if ($deactivated) + { + $config->increment('num_users', $deactivated * (-1), false); + } + + if ($activated) + { + $config->increment('num_users', $activated, false); + } + + // Update latest username + update_last_username(); +} + +/** +* Add a ban or ban exclusion to the banlist. Bans either a user, an IP or an email address +* +* @param string $mode Type of ban. One of the following: user, ip, email +* @param mixed $ban Banned entity. Either string or array with usernames, ips or email addresses +* @param int $ban_len Ban length in minutes +* @param string $ban_len_other Ban length as a date (YYYY-MM-DD) +* @param boolean $ban_exclude Exclude these entities from banning? +* @param string $ban_reason String describing the reason for this ban +* @return boolean +*/ +function user_ban($mode, $ban, $ban_len, $ban_len_other, $ban_exclude, $ban_reason, $ban_give_reason = '') +{ + global $db, $user, $cache, $phpbb_log; + + // Delete stale bans + $sql = 'DELETE FROM ' . BANLIST_TABLE . ' + WHERE ban_end < ' . time() . ' + AND ban_end <> 0'; + $db->sql_query($sql); + + $ban_list = (!is_array($ban)) ? array_unique(explode("\n", $ban)) : $ban; + $ban_list_log = implode(', ', $ban_list); + + $current_time = time(); + + // Set $ban_end to the unix time when the ban should end. 0 is a permanent ban. + if ($ban_len) + { + if ($ban_len != -1 || !$ban_len_other) + { + $ban_end = max($current_time, $current_time + ($ban_len) * 60); + } + else + { + $ban_other = explode('-', $ban_len_other); + if (count($ban_other) == 3 && ((int) $ban_other[0] < 9999) && + (strlen($ban_other[0]) == 4) && (strlen($ban_other[1]) == 2) && (strlen($ban_other[2]) == 2)) + { + $ban_end = max($current_time, $user->create_datetime() + ->setDate((int) $ban_other[0], (int) $ban_other[1], (int) $ban_other[2]) + ->setTime(0, 0, 0) + ->getTimestamp() + $user->timezone->getOffset(new DateTime('UTC'))); + } + else + { + trigger_error('LENGTH_BAN_INVALID', E_USER_WARNING); + } + } + } + else + { + $ban_end = 0; + } + + $founder = $founder_names = array(); + + if (!$ban_exclude) + { + // Create a list of founder... + $sql = 'SELECT user_id, user_email, username_clean + FROM ' . USERS_TABLE . ' + WHERE user_type = ' . USER_FOUNDER; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $founder[$row['user_id']] = $row['user_email']; + $founder_names[$row['user_id']] = $row['username_clean']; + } + $db->sql_freeresult($result); + } + + $banlist_ary = array(); + + switch ($mode) + { + case 'user': + $type = 'ban_userid'; + + // At the moment we do not support wildcard username banning + + // Select the relevant user_ids. + $sql_usernames = array(); + + foreach ($ban_list as $username) + { + $username = trim($username); + if ($username != '') + { + $clean_name = utf8_clean_string($username); + if ($clean_name == $user->data['username_clean']) + { + trigger_error('CANNOT_BAN_YOURSELF', E_USER_WARNING); + } + if (in_array($clean_name, $founder_names)) + { + trigger_error('CANNOT_BAN_FOUNDER', E_USER_WARNING); + } + $sql_usernames[] = $clean_name; + } + } + + // Make sure we have been given someone to ban + if (!count($sql_usernames)) + { + trigger_error('NO_USER_SPECIFIED', E_USER_WARNING); + } + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('username_clean', $sql_usernames); + + // Do not allow banning yourself, the guest account, or founders. + $non_bannable = array($user->data['user_id'], ANONYMOUS); + if (count($founder)) + { + $sql .= ' AND ' . $db->sql_in_set('user_id', array_merge(array_keys($founder), $non_bannable), true); + } + else + { + $sql .= ' AND ' . $db->sql_in_set('user_id', $non_bannable, true); + } + + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + do + { + $banlist_ary[] = (int) $row['user_id']; + } + while ($row = $db->sql_fetchrow($result)); + } + else + { + $db->sql_freeresult($result); + trigger_error('NO_USERS', E_USER_WARNING); + } + $db->sql_freeresult($result); + break; + + case 'ip': + $type = 'ban_ip'; + + foreach ($ban_list as $ban_item) + { + if (preg_match('#^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})[ ]*\-[ ]*([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$#', trim($ban_item), $ip_range_explode)) + { + // This is an IP range + // Don't ask about all this, just don't ask ... ! + $ip_1_counter = $ip_range_explode[1]; + $ip_1_end = $ip_range_explode[5]; + + while ($ip_1_counter <= $ip_1_end) + { + $ip_2_counter = ($ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[2] : 0; + $ip_2_end = ($ip_1_counter < $ip_1_end) ? 254 : $ip_range_explode[6]; + + if ($ip_2_counter == 0 && $ip_2_end == 254) + { + $ip_2_counter = 256; + + $banlist_ary[] = "$ip_1_counter.*"; + } + + while ($ip_2_counter <= $ip_2_end) + { + $ip_3_counter = ($ip_2_counter == $ip_range_explode[2] && $ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[3] : 0; + $ip_3_end = ($ip_2_counter < $ip_2_end || $ip_1_counter < $ip_1_end) ? 254 : $ip_range_explode[7]; + + if ($ip_3_counter == 0 && $ip_3_end == 254) + { + $ip_3_counter = 256; + + $banlist_ary[] = "$ip_1_counter.$ip_2_counter.*"; + } + + while ($ip_3_counter <= $ip_3_end) + { + $ip_4_counter = ($ip_3_counter == $ip_range_explode[3] && $ip_2_counter == $ip_range_explode[2] && $ip_1_counter == $ip_range_explode[1]) ? $ip_range_explode[4] : 0; + $ip_4_end = ($ip_3_counter < $ip_3_end || $ip_2_counter < $ip_2_end) ? 254 : $ip_range_explode[8]; + + if ($ip_4_counter == 0 && $ip_4_end == 254) + { + $ip_4_counter = 256; + + $banlist_ary[] = "$ip_1_counter.$ip_2_counter.$ip_3_counter.*"; + } + + while ($ip_4_counter <= $ip_4_end) + { + $banlist_ary[] = "$ip_1_counter.$ip_2_counter.$ip_3_counter.$ip_4_counter"; + $ip_4_counter++; + } + $ip_3_counter++; + } + $ip_2_counter++; + } + $ip_1_counter++; + } + } + else if (preg_match('#^([0-9]{1,3})\.([0-9\*]{1,3})\.([0-9\*]{1,3})\.([0-9\*]{1,3})$#', trim($ban_item)) || preg_match('#^[a-f0-9:]+\*?$#i', trim($ban_item))) + { + // Normal IP address + $banlist_ary[] = trim($ban_item); + } + else if (preg_match('#^\*$#', trim($ban_item))) + { + // Ban all IPs + $banlist_ary[] = '*'; + } + else if (preg_match('#^([\w\-_]\.?){2,}$#is', trim($ban_item))) + { + // hostname + $ip_ary = gethostbynamel(trim($ban_item)); + + if (!empty($ip_ary)) + { + foreach ($ip_ary as $ip) + { + if ($ip) + { + if (strlen($ip) > 40) + { + continue; + } + + $banlist_ary[] = $ip; + } + } + } + } + + if (empty($banlist_ary)) + { + trigger_error('NO_IPS_DEFINED', E_USER_WARNING); + } + } + break; + + case 'email': + $type = 'ban_email'; + + foreach ($ban_list as $ban_item) + { + $ban_item = trim($ban_item); + + if (preg_match('#^.*?@*|(([a-z0-9\-]+\.)+([a-z]{2,3}))$#i', $ban_item)) + { + if (strlen($ban_item) > 100) + { + continue; + } + + if (!count($founder) || !in_array($ban_item, $founder)) + { + $banlist_ary[] = $ban_item; + } + } + } + + if (count($ban_list) == 0) + { + trigger_error('NO_EMAILS_DEFINED', E_USER_WARNING); + } + break; + + default: + trigger_error('NO_MODE', E_USER_WARNING); + break; + } + + // Fetch currently set bans of the specified type and exclude state. Prevent duplicate bans. + $sql_where = ($type == 'ban_userid') ? 'ban_userid <> 0' : "$type <> ''"; + + $sql = "SELECT $type + FROM " . BANLIST_TABLE . " + WHERE $sql_where + AND ban_exclude = " . (int) $ban_exclude; + $result = $db->sql_query($sql); + + // Reset $sql_where, because we use it later... + $sql_where = ''; + + if ($row = $db->sql_fetchrow($result)) + { + $banlist_ary_tmp = array(); + do + { + switch ($mode) + { + case 'user': + $banlist_ary_tmp[] = $row['ban_userid']; + break; + + case 'ip': + $banlist_ary_tmp[] = $row['ban_ip']; + break; + + case 'email': + $banlist_ary_tmp[] = $row['ban_email']; + break; + } + } + while ($row = $db->sql_fetchrow($result)); + + $banlist_ary_tmp = array_intersect($banlist_ary, $banlist_ary_tmp); + + if (count($banlist_ary_tmp)) + { + // One or more entities are already banned/excluded, delete the existing bans, so they can be re-inserted with the given new length + $sql = 'DELETE FROM ' . BANLIST_TABLE . ' + WHERE ' . $db->sql_in_set($type, $banlist_ary_tmp) . ' + AND ban_exclude = ' . (int) $ban_exclude; + $db->sql_query($sql); + } + + unset($banlist_ary_tmp); + } + $db->sql_freeresult($result); + + // We have some entities to ban + if (count($banlist_ary)) + { + $sql_ary = array(); + + foreach ($banlist_ary as $ban_entry) + { + $sql_ary[] = array( + $type => $ban_entry, + 'ban_start' => (int) $current_time, + 'ban_end' => (int) $ban_end, + 'ban_exclude' => (int) $ban_exclude, + 'ban_reason' => (string) $ban_reason, + 'ban_give_reason' => (string) $ban_give_reason, + ); + } + + $db->sql_multi_insert(BANLIST_TABLE, $sql_ary); + + // If we are banning we want to logout anyone matching the ban + if (!$ban_exclude) + { + switch ($mode) + { + case 'user': + $sql_where = 'WHERE ' . $db->sql_in_set('session_user_id', $banlist_ary); + break; + + case 'ip': + $sql_where = 'WHERE ' . $db->sql_in_set('session_ip', $banlist_ary); + break; + + case 'email': + $banlist_ary_sql = array(); + + foreach ($banlist_ary as $ban_entry) + { + $banlist_ary_sql[] = (string) str_replace('*', '%', $ban_entry); + } + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_email', $banlist_ary_sql); + $result = $db->sql_query($sql); + + $sql_in = array(); + + if ($row = $db->sql_fetchrow($result)) + { + do + { + $sql_in[] = $row['user_id']; + } + while ($row = $db->sql_fetchrow($result)); + + $sql_where = 'WHERE ' . $db->sql_in_set('session_user_id', $sql_in); + } + $db->sql_freeresult($result); + break; + } + + if (isset($sql_where) && $sql_where) + { + $sql = 'DELETE FROM ' . SESSIONS_TABLE . " + $sql_where"; + $db->sql_query($sql); + + if ($mode == 'user') + { + $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' ' . ((in_array('*', $banlist_ary)) ? '' : 'WHERE ' . $db->sql_in_set('user_id', $banlist_ary)); + $db->sql_query($sql); + } + } + } + + // Update log + $log_entry = ($ban_exclude) ? 'LOG_BAN_EXCLUDE_' : 'LOG_BAN_'; + + // Add to admin log, moderator log and user notes + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log_entry . strtoupper($mode), false, array($ban_reason, $ban_list_log)); + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, $log_entry . strtoupper($mode), false, array( + 'forum_id' => 0, + 'topic_id' => 0, + $ban_reason, + $ban_list_log + )); + if ($mode == 'user') + { + foreach ($banlist_ary as $user_id) + { + $phpbb_log->add('user', $user->data['user_id'], $user->ip, $log_entry . strtoupper($mode), false, array( + 'reportee_id' => $user_id, + $ban_reason, + $ban_list_log + )); + } + } + + $cache->destroy('sql', BANLIST_TABLE); + + return true; + } + + // There was nothing to ban/exclude. But destroying the cache because of the removal of stale bans. + $cache->destroy('sql', BANLIST_TABLE); + + return false; +} + +/** +* Unban User +*/ +function user_unban($mode, $ban) +{ + global $db, $user, $cache, $phpbb_log, $phpbb_dispatcher; + + // Delete stale bans + $sql = 'DELETE FROM ' . BANLIST_TABLE . ' + WHERE ban_end < ' . time() . ' + AND ban_end <> 0'; + $db->sql_query($sql); + + if (!is_array($ban)) + { + $ban = array($ban); + } + + $unban_sql = array_map('intval', $ban); + + if (count($unban_sql)) + { + // Grab details of bans for logging information later + switch ($mode) + { + case 'user': + $sql = 'SELECT u.username AS unban_info, u.user_id + FROM ' . USERS_TABLE . ' u, ' . BANLIST_TABLE . ' b + WHERE ' . $db->sql_in_set('b.ban_id', $unban_sql) . ' + AND u.user_id = b.ban_userid'; + break; + + case 'email': + $sql = 'SELECT ban_email AS unban_info + FROM ' . BANLIST_TABLE . ' + WHERE ' . $db->sql_in_set('ban_id', $unban_sql); + break; + + case 'ip': + $sql = 'SELECT ban_ip AS unban_info + FROM ' . BANLIST_TABLE . ' + WHERE ' . $db->sql_in_set('ban_id', $unban_sql); + break; + } + $result = $db->sql_query($sql); + + $l_unban_list = ''; + $user_ids_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $l_unban_list .= (($l_unban_list != '') ? ', ' : '') . $row['unban_info']; + if ($mode == 'user') + { + $user_ids_ary[] = $row['user_id']; + } + } + $db->sql_freeresult($result); + + $sql = 'DELETE FROM ' . BANLIST_TABLE . ' + WHERE ' . $db->sql_in_set('ban_id', $unban_sql); + $db->sql_query($sql); + + // Add to moderator log, admin log and user notes + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_UNBAN_' . strtoupper($mode), false, array($l_unban_list)); + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_UNBAN_' . strtoupper($mode), false, array( + 'forum_id' => 0, + 'topic_id' => 0, + $l_unban_list + )); + if ($mode == 'user') + { + foreach ($user_ids_ary as $user_id) + { + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_UNBAN_' . strtoupper($mode), false, array( + 'reportee_id' => $user_id, + $l_unban_list + )); + } + } + + /** + * Use this event to perform actions after the unban has been performed + * + * @event core.user_unban + * @var string mode One of the following: user, ip, email + * @var array user_ids_ary Array with user_ids + * @since 3.1.11-RC1 + */ + $vars = array( + 'mode', + 'user_ids_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.user_unban', compact($vars))); + } + + $cache->destroy('sql', BANLIST_TABLE); + + return false; +} + +/** +* Internet Protocol Address Whois +* RFC3912: WHOIS Protocol Specification +* +* @param string $ip Ip address, either IPv4 or IPv6. +* +* @return string Empty string if not a valid ip address. +* Otherwise make_clickable()'ed whois result. +*/ +function user_ipwhois($ip) +{ + if (empty($ip)) + { + return ''; + } + + if (!preg_match(get_preg_expression('ipv4'), $ip) && !preg_match(get_preg_expression('ipv6'), $ip)) + { + return ''; + } + + // IPv4 & IPv6 addresses + $whois_host = 'whois.arin.net.'; + + $ipwhois = ''; + + if (($fsk = @fsockopen($whois_host, 43))) + { + // CRLF as per RFC3912 + fputs($fsk, "$ip\r\n"); + while (!feof($fsk)) + { + $ipwhois .= fgets($fsk, 1024); + } + @fclose($fsk); + } + + $match = array(); + + // Test for referrals from $whois_host to other whois databases, roll on rwhois + if (preg_match('#ReferralServer:[\x20]*whois://(.+)#im', $ipwhois, $match)) + { + if (strpos($match[1], ':') !== false) + { + $pos = strrpos($match[1], ':'); + $server = substr($match[1], 0, $pos); + $port = (int) substr($match[1], $pos + 1); + unset($pos); + } + else + { + $server = $match[1]; + $port = 43; + } + + $buffer = ''; + + if (($fsk = @fsockopen($server, $port))) + { + fputs($fsk, "$ip\r\n"); + while (!feof($fsk)) + { + $buffer .= fgets($fsk, 1024); + } + @fclose($fsk); + } + + // Use the result from $whois_host if we don't get any result here + $ipwhois = (empty($buffer)) ? $ipwhois : $buffer; + } + + $ipwhois = htmlspecialchars($ipwhois); + + // Magic URL ;) + return trim(make_clickable($ipwhois, false, '')); +} + +/** +* Data validation ... used primarily but not exclusively by ucp modules +* +* "Master" function for validating a range of data types +*/ +function validate_data($data, $val_ary) +{ + global $user; + + $error = array(); + + foreach ($val_ary as $var => $val_seq) + { + if (!is_array($val_seq[0])) + { + $val_seq = array($val_seq); + } + + foreach ($val_seq as $validate) + { + $function = array_shift($validate); + array_unshift($validate, $data[$var]); + + if (is_array($function)) + { + $result = call_user_func_array(array($function[0], 'validate_' . $function[1]), $validate); + } + else + { + $function_prefix = (function_exists('phpbb_validate_' . $function)) ? 'phpbb_validate_' : 'validate_'; + $result = call_user_func_array($function_prefix . $function, $validate); + } + + if ($result) + { + // Since errors are checked later for their language file existence, we need to make sure custom errors are not adjusted. + $error[] = (empty($user->lang[$result . '_' . strtoupper($var)])) ? $result : $result . '_' . strtoupper($var); + } + } + } + + return $error; +} + +/** +* Validate String +* +* @return boolean|string Either false if validation succeeded or a string which will be used as the error message (with the variable name appended) +*/ +function validate_string($string, $optional = false, $min = 0, $max = 0) +{ + if (empty($string) && $optional) + { + return false; + } + + if ($min && utf8_strlen(htmlspecialchars_decode($string)) < $min) + { + return 'TOO_SHORT'; + } + else if ($max && utf8_strlen(htmlspecialchars_decode($string)) > $max) + { + return 'TOO_LONG'; + } + + return false; +} + +/** +* Validate Number +* +* @return boolean|string Either false if validation succeeded or a string which will be used as the error message (with the variable name appended) +*/ +function validate_num($num, $optional = false, $min = 0, $max = 1E99) +{ + if (empty($num) && $optional) + { + return false; + } + + if ($num < $min) + { + return 'TOO_SMALL'; + } + else if ($num > $max) + { + return 'TOO_LARGE'; + } + + return false; +} + +/** +* Validate Date +* @param String $string a date in the dd-mm-yyyy format +* @return boolean +*/ +function validate_date($date_string, $optional = false) +{ + $date = explode('-', $date_string); + if ((empty($date) || count($date) != 3) && $optional) + { + return false; + } + else if ($optional) + { + for ($field = 0; $field <= 1; $field++) + { + $date[$field] = (int) $date[$field]; + if (empty($date[$field])) + { + $date[$field] = 1; + } + } + $date[2] = (int) $date[2]; + // assume an arbitrary leap year + if (empty($date[2])) + { + $date[2] = 1980; + } + } + + if (count($date) != 3 || !checkdate($date[1], $date[0], $date[2])) + { + return 'INVALID'; + } + + return false; +} + + +/** +* Validate Match +* +* @return boolean|string Either false if validation succeeded or a string which will be used as the error message (with the variable name appended) +*/ +function validate_match($string, $optional = false, $match = '') +{ + if (empty($string) && $optional) + { + return false; + } + + if (empty($match)) + { + return false; + } + + if (!preg_match($match, $string)) + { + return 'WRONG_DATA'; + } + + return false; +} + +/** +* Validate Language Pack ISO Name +* +* Tests whether a language name is valid and installed +* +* @param string $lang_iso The language string to test +* +* @return bool|string Either false if validation succeeded or +* a string which will be used as the error message +* (with the variable name appended) +*/ +function validate_language_iso_name($lang_iso) +{ + global $db; + + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE . " + WHERE lang_iso = '" . $db->sql_escape($lang_iso) . "'"; + $result = $db->sql_query($sql); + $lang_id = (int) $db->sql_fetchfield('lang_id'); + $db->sql_freeresult($result); + + return ($lang_id) ? false : 'WRONG_DATA'; +} + +/** +* Validate Timezone Name +* +* Tests whether a timezone name is valid +* +* @param string $timezone The timezone string to test +* +* @return bool|string Either false if validation succeeded or +* a string which will be used as the error message +* (with the variable name appended) +*/ +function phpbb_validate_timezone($timezone) +{ + return (in_array($timezone, phpbb_get_timezone_identifiers($timezone))) ? false : 'TIMEZONE_INVALID'; +} + +/*** + * Validate Username + * + * Check to see if the username has been taken, or if it is disallowed. + * Also checks if it includes the " character or the 4-bytes Unicode ones + * (aka emojis) which we don't allow in usernames. + * Used for registering, changing names, and posting anonymously with a username + * + * @param string $username The username to check + * @param string $allowed_username An allowed username, default being $user->data['username'] + * + * @return mixed Either false if validation succeeded or a string which will be + * used as the error message (with the variable name appended) + */ +function validate_username($username, $allowed_username = false) +{ + global $config, $db, $user, $cache; + + $clean_username = utf8_clean_string($username); + $allowed_username = ($allowed_username === false) ? $user->data['username_clean'] : utf8_clean_string($allowed_username); + + if ($allowed_username == $clean_username) + { + return false; + } + + // The very first check is for + // out-of-bounds characters that are currently + // not supported by utf8_bin in MySQL + if (preg_match('/[\x{10000}-\x{10FFFF}]/u', $username)) + { + return 'INVALID_EMOJIS'; + } + + // ... fast checks first. + if (strpos($username, '"') !== false || strpos($username, '"') !== false || empty($clean_username)) + { + return 'INVALID_CHARS'; + } + + switch ($config['allow_name_chars']) + { + case 'USERNAME_CHARS_ANY': + $regex = '.+'; + break; + + case 'USERNAME_ALPHA_ONLY': + $regex = '[A-Za-z0-9]+'; + break; + + case 'USERNAME_ALPHA_SPACERS': + $regex = '[A-Za-z0-9-[\]_+ ]+'; + break; + + case 'USERNAME_LETTER_NUM': + $regex = '[\p{Lu}\p{Ll}\p{N}]+'; + break; + + case 'USERNAME_LETTER_NUM_SPACERS': + $regex = '[-\]_+ [\p{Lu}\p{Ll}\p{N}]+'; + break; + + case 'USERNAME_ASCII': + default: + $regex = '[\x01-\x7F]+'; + break; + } + + if (!preg_match('#^' . $regex . '$#u', $username)) + { + return 'INVALID_CHARS'; + } + + $sql = 'SELECT username + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $db->sql_escape($clean_username) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + return 'USERNAME_TAKEN'; + } + + $sql = 'SELECT group_name + FROM ' . GROUPS_TABLE . " + WHERE LOWER(group_name) = '" . $db->sql_escape(utf8_strtolower($username)) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + return 'USERNAME_TAKEN'; + } + + $bad_usernames = $cache->obtain_disallowed_usernames(); + + foreach ($bad_usernames as $bad_username) + { + if (preg_match('#^' . $bad_username . '$#', $clean_username)) + { + return 'USERNAME_DISALLOWED'; + } + } + + return false; +} + +/** +* Check to see if the password meets the complexity settings +* +* @return boolean|string Either false if validation succeeded or a string which will be used as the error message (with the variable name appended) +*/ +function validate_password($password) +{ + global $config; + + if ($password === '' || $config['pass_complex'] === 'PASS_TYPE_ANY') + { + // Password empty or no password complexity required. + return false; + } + + $upp = '\p{Lu}'; + $low = '\p{Ll}'; + $num = '\p{N}'; + $sym = '[^\p{Lu}\p{Ll}\p{N}]'; + $chars = array(); + + switch ($config['pass_complex']) + { + // No break statements below ... + // We require strong passwords in case pass_complex is not set or is invalid + default: + + // Require mixed case letters, numbers and symbols + case 'PASS_TYPE_SYMBOL': + $chars[] = $sym; + + // Require mixed case letters and numbers + case 'PASS_TYPE_ALPHA': + $chars[] = $num; + + // Require mixed case letters + case 'PASS_TYPE_CASE': + $chars[] = $low; + $chars[] = $upp; + } + + foreach ($chars as $char) + { + if (!preg_match('#' . $char . '#u', $password)) + { + return 'INVALID_CHARS'; + } + } + + return false; +} + +/** +* Check to see if email address is a valid address and contains a MX record +* +* @param string $email The email to check +* +* @return mixed Either false if validation succeeded or a string which will be used as the error message (with the variable name appended) +*/ +function phpbb_validate_email($email, $config = null) +{ + if ($config === null) + { + global $config; + } + + $email = strtolower($email); + + if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email)) + { + return 'EMAIL_INVALID'; + } + + // Check MX record. + // The idea for this is from reading the UseBB blog/announcement. :) + if ($config['email_check_mx']) + { + list(, $domain) = explode('@', $email); + + if (phpbb_checkdnsrr($domain, 'A') === false && phpbb_checkdnsrr($domain, 'MX') === false) + { + return 'DOMAIN_NO_MX_RECORD'; + } + } + + return false; +} + +/** +* Check to see if email address is banned or already present in the DB +* +* @param string $email The email to check +* @param string $allowed_email An allowed email, default being $user->data['user_email'] +* +* @return mixed Either false if validation succeeded or a string which will be used as the error message (with the variable name appended) +*/ +function validate_user_email($email, $allowed_email = false) +{ + global $config, $db, $user; + + $email = strtolower($email); + $allowed_email = ($allowed_email === false) ? strtolower($user->data['user_email']) : strtolower($allowed_email); + + if ($allowed_email == $email) + { + return false; + } + + $validate_email = phpbb_validate_email($email, $config); + if ($validate_email) + { + return $validate_email; + } + + if (($ban = $user->check_ban(false, false, $email, true)) !== false) + { + return ($ban === true) ? 'EMAIL_BANNED' : (!empty($ban['ban_give_reason']) ? $ban['ban_give_reason'] : $ban); + } + + if (!$config['allow_emailreuse']) + { + $sql = 'SELECT user_email_hash + FROM ' . USERS_TABLE . " + WHERE user_email_hash = " . $db->sql_escape(phpbb_email_hash($email)); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + return 'EMAIL_TAKEN'; + } + } + + return false; +} + +/** +* Validate jabber address +* Taken from the jabber class within flyspray (see author notes) +* +* @author flyspray.org +*/ +function validate_jabber($jid) +{ + if (!$jid) + { + return false; + } + + $separator_pos = strpos($jid, '@'); + + if ($separator_pos === false) + { + return 'WRONG_DATA'; + } + + $username = substr($jid, 0, $separator_pos); + $realm = substr($jid, $separator_pos + 1); + + if (strlen($username) == 0 || strlen($realm) < 3) + { + return 'WRONG_DATA'; + } + + $arr = explode('.', $realm); + + if (count($arr) == 0) + { + return 'WRONG_DATA'; + } + + foreach ($arr as $part) + { + if (substr($part, 0, 1) == '-' || substr($part, -1, 1) == '-') + { + return 'WRONG_DATA'; + } + + if (!preg_match("@^[a-zA-Z0-9-.]+$@", $part)) + { + return 'WRONG_DATA'; + } + } + + $boundary = array(array(0, 127), array(192, 223), array(224, 239), array(240, 247), array(248, 251), array(252, 253)); + + // Prohibited Characters RFC3454 + RFC3920 + $prohibited = array( + // Table C.1.1 + array(0x0020, 0x0020), // SPACE + // Table C.1.2 + array(0x00A0, 0x00A0), // NO-BREAK SPACE + array(0x1680, 0x1680), // OGHAM SPACE MARK + array(0x2000, 0x2001), // EN QUAD + array(0x2001, 0x2001), // EM QUAD + array(0x2002, 0x2002), // EN SPACE + array(0x2003, 0x2003), // EM SPACE + array(0x2004, 0x2004), // THREE-PER-EM SPACE + array(0x2005, 0x2005), // FOUR-PER-EM SPACE + array(0x2006, 0x2006), // SIX-PER-EM SPACE + array(0x2007, 0x2007), // FIGURE SPACE + array(0x2008, 0x2008), // PUNCTUATION SPACE + array(0x2009, 0x2009), // THIN SPACE + array(0x200A, 0x200A), // HAIR SPACE + array(0x200B, 0x200B), // ZERO WIDTH SPACE + array(0x202F, 0x202F), // NARROW NO-BREAK SPACE + array(0x205F, 0x205F), // MEDIUM MATHEMATICAL SPACE + array(0x3000, 0x3000), // IDEOGRAPHIC SPACE + // Table C.2.1 + array(0x0000, 0x001F), // [CONTROL CHARACTERS] + array(0x007F, 0x007F), // DELETE + // Table C.2.2 + array(0x0080, 0x009F), // [CONTROL CHARACTERS] + array(0x06DD, 0x06DD), // ARABIC END OF AYAH + array(0x070F, 0x070F), // SYRIAC ABBREVIATION MARK + array(0x180E, 0x180E), // MONGOLIAN VOWEL SEPARATOR + array(0x200C, 0x200C), // ZERO WIDTH NON-JOINER + array(0x200D, 0x200D), // ZERO WIDTH JOINER + array(0x2028, 0x2028), // LINE SEPARATOR + array(0x2029, 0x2029), // PARAGRAPH SEPARATOR + array(0x2060, 0x2060), // WORD JOINER + array(0x2061, 0x2061), // FUNCTION APPLICATION + array(0x2062, 0x2062), // INVISIBLE TIMES + array(0x2063, 0x2063), // INVISIBLE SEPARATOR + array(0x206A, 0x206F), // [CONTROL CHARACTERS] + array(0xFEFF, 0xFEFF), // ZERO WIDTH NO-BREAK SPACE + array(0xFFF9, 0xFFFC), // [CONTROL CHARACTERS] + array(0x1D173, 0x1D17A), // [MUSICAL CONTROL CHARACTERS] + // Table C.3 + array(0xE000, 0xF8FF), // [PRIVATE USE, PLANE 0] + array(0xF0000, 0xFFFFD), // [PRIVATE USE, PLANE 15] + array(0x100000, 0x10FFFD), // [PRIVATE USE, PLANE 16] + // Table C.4 + array(0xFDD0, 0xFDEF), // [NONCHARACTER CODE POINTS] + array(0xFFFE, 0xFFFF), // [NONCHARACTER CODE POINTS] + array(0x1FFFE, 0x1FFFF), // [NONCHARACTER CODE POINTS] + array(0x2FFFE, 0x2FFFF), // [NONCHARACTER CODE POINTS] + array(0x3FFFE, 0x3FFFF), // [NONCHARACTER CODE POINTS] + array(0x4FFFE, 0x4FFFF), // [NONCHARACTER CODE POINTS] + array(0x5FFFE, 0x5FFFF), // [NONCHARACTER CODE POINTS] + array(0x6FFFE, 0x6FFFF), // [NONCHARACTER CODE POINTS] + array(0x7FFFE, 0x7FFFF), // [NONCHARACTER CODE POINTS] + array(0x8FFFE, 0x8FFFF), // [NONCHARACTER CODE POINTS] + array(0x9FFFE, 0x9FFFF), // [NONCHARACTER CODE POINTS] + array(0xAFFFE, 0xAFFFF), // [NONCHARACTER CODE POINTS] + array(0xBFFFE, 0xBFFFF), // [NONCHARACTER CODE POINTS] + array(0xCFFFE, 0xCFFFF), // [NONCHARACTER CODE POINTS] + array(0xDFFFE, 0xDFFFF), // [NONCHARACTER CODE POINTS] + array(0xEFFFE, 0xEFFFF), // [NONCHARACTER CODE POINTS] + array(0xFFFFE, 0xFFFFF), // [NONCHARACTER CODE POINTS] + array(0x10FFFE, 0x10FFFF), // [NONCHARACTER CODE POINTS] + // Table C.5 + array(0xD800, 0xDFFF), // [SURROGATE CODES] + // Table C.6 + array(0xFFF9, 0xFFF9), // INTERLINEAR ANNOTATION ANCHOR + array(0xFFFA, 0xFFFA), // INTERLINEAR ANNOTATION SEPARATOR + array(0xFFFB, 0xFFFB), // INTERLINEAR ANNOTATION TERMINATOR + array(0xFFFC, 0xFFFC), // OBJECT REPLACEMENT CHARACTER + array(0xFFFD, 0xFFFD), // REPLACEMENT CHARACTER + // Table C.7 + array(0x2FF0, 0x2FFB), // [IDEOGRAPHIC DESCRIPTION CHARACTERS] + // Table C.8 + array(0x0340, 0x0340), // COMBINING GRAVE TONE MARK + array(0x0341, 0x0341), // COMBINING ACUTE TONE MARK + array(0x200E, 0x200E), // LEFT-TO-RIGHT MARK + array(0x200F, 0x200F), // RIGHT-TO-LEFT MARK + array(0x202A, 0x202A), // LEFT-TO-RIGHT EMBEDDING + array(0x202B, 0x202B), // RIGHT-TO-LEFT EMBEDDING + array(0x202C, 0x202C), // POP DIRECTIONAL FORMATTING + array(0x202D, 0x202D), // LEFT-TO-RIGHT OVERRIDE + array(0x202E, 0x202E), // RIGHT-TO-LEFT OVERRIDE + array(0x206A, 0x206A), // INHIBIT SYMMETRIC SWAPPING + array(0x206B, 0x206B), // ACTIVATE SYMMETRIC SWAPPING + array(0x206C, 0x206C), // INHIBIT ARABIC FORM SHAPING + array(0x206D, 0x206D), // ACTIVATE ARABIC FORM SHAPING + array(0x206E, 0x206E), // NATIONAL DIGIT SHAPES + array(0x206F, 0x206F), // NOMINAL DIGIT SHAPES + // Table C.9 + array(0xE0001, 0xE0001), // LANGUAGE TAG + array(0xE0020, 0xE007F), // [TAGGING CHARACTERS] + // RFC3920 + array(0x22, 0x22), // " + array(0x26, 0x26), // & + array(0x27, 0x27), // ' + array(0x2F, 0x2F), // / + array(0x3A, 0x3A), // : + array(0x3C, 0x3C), // < + array(0x3E, 0x3E), // > + array(0x40, 0x40) // @ + ); + + $pos = 0; + $result = true; + + while ($pos < strlen($username)) + { + $len = $uni = 0; + for ($i = 0; $i <= 5; $i++) + { + if (ord($username[$pos]) >= $boundary[$i][0] && ord($username[$pos]) <= $boundary[$i][1]) + { + $len = $i + 1; + $uni = (ord($username[$pos]) - $boundary[$i][0]) * pow(2, $i * 6); + + for ($k = 1; $k < $len; $k++) + { + $uni += (ord($username[$pos + $k]) - 128) * pow(2, ($i - $k) * 6); + } + + break; + } + } + + if ($len == 0) + { + return 'WRONG_DATA'; + } + + foreach ($prohibited as $pval) + { + if ($uni >= $pval[0] && $uni <= $pval[1]) + { + $result = false; + break 2; + } + } + + $pos = $pos + $len; + } + + if (!$result) + { + return 'WRONG_DATA'; + } + + return false; +} + +/** +* Validate hex colour value +* +* @param string $colour The hex colour value +* @param bool $optional Whether the colour value is optional. True if an empty +* string will be accepted as correct input, false if not. +* @return bool|string Error message if colour value is incorrect, false if it +* fits the hex colour code +*/ +function phpbb_validate_hex_colour($colour, $optional = false) +{ + if ($colour === '') + { + return (($optional) ? false : 'WRONG_DATA'); + } + + if (!preg_match('/^([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/', $colour)) + { + return 'WRONG_DATA'; + } + + return false; +} + +/** +* Verifies whether a style ID corresponds to an active style. +* +* @param int $style_id The style_id of a style which should be checked if activated or not. +* @return boolean +*/ +function phpbb_style_is_active($style_id) +{ + global $db; + + $sql = 'SELECT style_active + FROM ' . STYLES_TABLE . ' + WHERE style_id = '. (int) $style_id; + $result = $db->sql_query($sql); + + $style_is_active = (bool) $db->sql_fetchfield('style_active'); + $db->sql_freeresult($result); + + return $style_is_active; +} + +/** +* Remove avatar +*/ +function avatar_delete($mode, $row, $clean_db = false) +{ + global $phpbb_root_path, $config; + + // Check if the users avatar is actually *not* a group avatar + if ($mode == 'user') + { + if (strpos($row['user_avatar'], 'g') === 0 || (((int) $row['user_avatar'] !== 0) && ((int) $row['user_avatar'] !== (int) $row['user_id']))) + { + return false; + } + } + + if ($clean_db) + { + avatar_remove_db($row[$mode . '_avatar']); + } + $filename = get_avatar_filename($row[$mode . '_avatar']); + + if (file_exists($phpbb_root_path . $config['avatar_path'] . '/' . $filename)) + { + @unlink($phpbb_root_path . $config['avatar_path'] . '/' . $filename); + return true; + } + + return false; +} + +/** +* Generates avatar filename from the database entry +*/ +function get_avatar_filename($avatar_entry) +{ + global $config; + + if ($avatar_entry[0] === 'g') + { + $avatar_group = true; + $avatar_entry = substr($avatar_entry, 1); + } + else + { + $avatar_group = false; + } + $ext = substr(strrchr($avatar_entry, '.'), 1); + $avatar_entry = intval($avatar_entry); + return $config['avatar_salt'] . '_' . (($avatar_group) ? 'g' : '') . $avatar_entry . '.' . $ext; +} + +/** +* Returns an explanation string with maximum avatar settings +* +* @return string +*/ +function phpbb_avatar_explanation_string() +{ + global $config, $user; + + return $user->lang(($config['avatar_filesize'] == 0) ? 'AVATAR_EXPLAIN_NO_FILESIZE' : 'AVATAR_EXPLAIN', + $user->lang('PIXELS', (int) $config['avatar_max_width']), + $user->lang('PIXELS', (int) $config['avatar_max_height']), + round($config['avatar_filesize'] / 1024)); +} + +// +// Usergroup functions +// + +/** +* Add or edit a group. If we're editing a group we only update user +* parameters such as rank, etc. if they are changed +*/ +function group_create(&$group_id, $type, $name, $desc, $group_attributes, $allow_desc_bbcode = false, $allow_desc_urls = false, $allow_desc_smilies = false) +{ + global $db, $user, $phpbb_container, $phpbb_log; + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $error = array(); + + // Attributes which also affect the users table + $user_attribute_ary = array('group_colour', 'group_rank', 'group_avatar', 'group_avatar_type', 'group_avatar_width', 'group_avatar_height'); + + // Check data. Limit group name length. + if (!utf8_strlen($name) || utf8_strlen($name) > 60) + { + $error[] = (!utf8_strlen($name)) ? $user->lang['GROUP_ERR_USERNAME'] : $user->lang['GROUP_ERR_USER_LONG']; + } + + $err = group_validate_groupname($group_id, $name); + if (!empty($err)) + { + $error[] = $user->lang[$err]; + } + + if (!in_array($type, array(GROUP_OPEN, GROUP_CLOSED, GROUP_HIDDEN, GROUP_SPECIAL, GROUP_FREE))) + { + $error[] = $user->lang['GROUP_ERR_TYPE']; + } + + $group_teampage = !empty($group_attributes['group_teampage']); + unset($group_attributes['group_teampage']); + + if (!count($error)) + { + $current_legend = \phpbb\groupposition\legend::GROUP_DISABLED; + $current_teampage = \phpbb\groupposition\teampage::GROUP_DISABLED; + + /* @var $legend \phpbb\groupposition\legend */ + $legend = $phpbb_container->get('groupposition.legend'); + + /* @var $teampage \phpbb\groupposition\teampage */ + $teampage = $phpbb_container->get('groupposition.teampage'); + + if ($group_id) + { + try + { + $current_legend = $legend->get_group_value($group_id); + $current_teampage = $teampage->get_group_value($group_id); + } + catch (\phpbb\groupposition\exception $exception) + { + trigger_error($user->lang($exception->getMessage())); + } + } + + if (!empty($group_attributes['group_legend'])) + { + if (($group_id && ($current_legend == \phpbb\groupposition\legend::GROUP_DISABLED)) || !$group_id) + { + // Old group currently not in the legend or new group, add at the end. + $group_attributes['group_legend'] = 1 + $legend->get_group_count(); + } + else + { + // Group stayes in the legend + $group_attributes['group_legend'] = $current_legend; + } + } + else if ($group_id && ($current_legend != \phpbb\groupposition\legend::GROUP_DISABLED)) + { + // Group is removed from the legend + try + { + $legend->delete_group($group_id, true); + } + catch (\phpbb\groupposition\exception $exception) + { + trigger_error($user->lang($exception->getMessage())); + } + $group_attributes['group_legend'] = \phpbb\groupposition\legend::GROUP_DISABLED; + } + else + { + $group_attributes['group_legend'] = \phpbb\groupposition\legend::GROUP_DISABLED; + } + + // Unset the objects, we don't need them anymore. + unset($legend); + + $user_ary = array(); + $sql_ary = array( + 'group_name' => (string) $name, + 'group_desc' => (string) $desc, + 'group_desc_uid' => '', + 'group_desc_bitfield' => '', + 'group_type' => (int) $type, + ); + + // Parse description + if ($desc) + { + generate_text_for_storage($sql_ary['group_desc'], $sql_ary['group_desc_uid'], $sql_ary['group_desc_bitfield'], $sql_ary['group_desc_options'], $allow_desc_bbcode, $allow_desc_urls, $allow_desc_smilies); + } + + if (count($group_attributes)) + { + // Merge them with $sql_ary to properly update the group + $sql_ary = array_merge($sql_ary, $group_attributes); + } + + // Setting the log message before we set the group id (if group gets added) + $log = ($group_id) ? 'LOG_GROUP_UPDATED' : 'LOG_GROUP_CREATED'; + + if ($group_id) + { + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE group_id = ' . $group_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $user_ary[] = $row['user_id']; + } + $db->sql_freeresult($result); + + if (isset($sql_ary['group_avatar'])) + { + remove_default_avatar($group_id, $user_ary); + } + + if (isset($sql_ary['group_rank'])) + { + remove_default_rank($group_id, $user_ary); + } + + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " + WHERE group_id = $group_id"; + $db->sql_query($sql); + + // Since we may update the name too, we need to do this on other tables too... + $sql = 'UPDATE ' . MODERATOR_CACHE_TABLE . " + SET group_name = '" . $db->sql_escape($sql_ary['group_name']) . "' + WHERE group_id = $group_id"; + $db->sql_query($sql); + + // One special case is the group skip auth setting. If this was changed we need to purge permissions for this group + if (isset($group_attributes['group_skip_auth'])) + { + // Get users within this group... + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND user_pending = 0'; + $result = $db->sql_query($sql); + + $user_id_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_id_ary[] = $row['user_id']; + } + $db->sql_freeresult($result); + + if (!empty($user_id_ary)) + { + global $auth; + + // Clear permissions cache of relevant users + $auth->acl_clear_prefetch($user_id_ary); + } + } + } + else + { + $sql = 'INSERT INTO ' . GROUPS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + } + + // Remove the group from the teampage, only if unselected and we are editing a group, + // which is currently displayed. + if (!$group_teampage && $group_id && $current_teampage != \phpbb\groupposition\teampage::GROUP_DISABLED) + { + try + { + $teampage->delete_group($group_id); + } + catch (\phpbb\groupposition\exception $exception) + { + trigger_error($user->lang($exception->getMessage())); + } + } + + if (!$group_id) + { + $group_id = $db->sql_nextid(); + + if (isset($sql_ary['group_avatar_type']) && $sql_ary['group_avatar_type'] == 'avatar.driver.upload') + { + group_correct_avatar($group_id, $sql_ary['group_avatar']); + } + } + + try + { + if ($group_teampage && $current_teampage == \phpbb\groupposition\teampage::GROUP_DISABLED) + { + $teampage->add_group($group_id); + } + + if ($group_teampage) + { + if ($current_teampage == \phpbb\groupposition\teampage::GROUP_DISABLED) + { + $teampage->add_group($group_id); + } + } + else if ($group_id && ($current_teampage != \phpbb\groupposition\teampage::GROUP_DISABLED)) + { + $teampage->delete_group($group_id); + } + } + catch (\phpbb\groupposition\exception $exception) + { + trigger_error($user->lang($exception->getMessage())); + } + unset($teampage); + + // Set user attributes + $sql_ary = array(); + if (count($group_attributes)) + { + // Go through the user attributes array, check if a group attribute matches it and then set it. ;) + foreach ($user_attribute_ary as $attribute) + { + if (!isset($group_attributes[$attribute])) + { + continue; + } + + // If we are about to set an avatar, we will not overwrite user avatars if no group avatar is set... + if (strpos($attribute, 'group_avatar') === 0 && !$group_attributes[$attribute]) + { + continue; + } + + $sql_ary[$attribute] = $group_attributes[$attribute]; + } + } + + if (count($sql_ary) && count($user_ary)) + { + group_set_user_default($group_id, $user_ary, $sql_ary); + } + + $name = $group_helper->get_name($name); + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($name)); + + group_update_listings($group_id); + } + + return (count($error)) ? $error : false; +} + + +/** +* Changes a group avatar's filename to conform to the naming scheme +*/ +function group_correct_avatar($group_id, $old_entry) +{ + global $config, $db, $phpbb_root_path; + + $group_id = (int) $group_id; + $ext = substr(strrchr($old_entry, '.'), 1); + $old_filename = get_avatar_filename($old_entry); + $new_filename = $config['avatar_salt'] . "_g$group_id.$ext"; + $new_entry = 'g' . $group_id . '_' . substr(time(), -5) . ".$ext"; + + $avatar_path = $phpbb_root_path . $config['avatar_path']; + if (@rename($avatar_path . '/'. $old_filename, $avatar_path . '/' . $new_filename)) + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_avatar = \'' . $db->sql_escape($new_entry) . "' + WHERE group_id = $group_id"; + $db->sql_query($sql); + } +} + + +/** +* Remove avatar also for users not having the group as default +*/ +function avatar_remove_db($avatar_name) +{ + global $db; + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_avatar = '', + user_avatar_type = '' + WHERE user_avatar = '" . $db->sql_escape($avatar_name) . '\''; + $db->sql_query($sql); +} + + +/** +* Group Delete +*/ +function group_delete($group_id, $group_name = false) +{ + global $db, $cache, $auth, $user, $phpbb_root_path, $phpEx, $phpbb_dispatcher, $phpbb_container, $phpbb_log; + + if (!$group_name) + { + $group_name = get_group_name($group_id); + } + + $start = 0; + + do + { + $user_id_ary = $username_ary = array(); + + // Batch query for group members, call group_user_del + $sql = 'SELECT u.user_id, u.username + FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . " u + WHERE ug.group_id = $group_id + AND u.user_id = ug.user_id"; + $result = $db->sql_query_limit($sql, 200, $start); + + if ($row = $db->sql_fetchrow($result)) + { + do + { + $user_id_ary[] = $row['user_id']; + $username_ary[] = $row['username']; + + $start++; + } + while ($row = $db->sql_fetchrow($result)); + + group_user_del($group_id, $user_id_ary, $username_ary, $group_name); + } + else + { + $start = 0; + } + $db->sql_freeresult($result); + } + while ($start); + + // Delete group from legend and teampage + try + { + /* @var $legend \phpbb\groupposition\legend */ + $legend = $phpbb_container->get('groupposition.legend'); + $legend->delete_group($group_id); + unset($legend); + } + catch (\phpbb\groupposition\exception $exception) + { + // The group we want to delete does not exist. + // No reason to worry, we just continue the deleting process. + //trigger_error($user->lang($exception->getMessage())); + } + + try + { + /* @var $teampage \phpbb\groupposition\teampage */ + $teampage = $phpbb_container->get('groupposition.teampage'); + $teampage->delete_group($group_id); + unset($teampage); + } + catch (\phpbb\groupposition\exception $exception) + { + // The group we want to delete does not exist. + // No reason to worry, we just continue the deleting process. + //trigger_error($user->lang($exception->getMessage())); + } + + // Delete group + $sql = 'DELETE FROM ' . GROUPS_TABLE . " + WHERE group_id = $group_id"; + $db->sql_query($sql); + + // Delete auth entries from the groups table + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . " + WHERE group_id = $group_id"; + $db->sql_query($sql); + + /** + * Event after a group is deleted + * + * @event core.delete_group_after + * @var int group_id ID of the deleted group + * @var string group_name Name of the deleted group + * @since 3.1.0-a1 + */ + $vars = array('group_id', 'group_name'); + extract($phpbb_dispatcher->trigger_event('core.delete_group_after', compact($vars))); + + // Re-cache moderators + if (!function_exists('phpbb_cache_moderators')) + { + include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + } + + phpbb_cache_moderators($db, $cache, $auth); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_GROUP_DELETE', false, array($group_name)); + + // Return false - no error + return false; +} + +/** +* Add user(s) to group +* +* @return mixed false if no errors occurred, else the user lang string for the relevant error, for example 'NO_USER' +*/ +function group_user_add($group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $default = false, $leader = 0, $pending = 0, $group_attributes = false) +{ + global $db, $auth, $user, $phpbb_container, $phpbb_log, $phpbb_dispatcher; + + // We need both username and user_id info + $result = user_get_id_name($user_id_ary, $username_ary); + + if (empty($user_id_ary) || $result !== false) + { + return 'NO_USER'; + } + + // Because the item that gets passed into the previous function is unset, the reference is lost and our original + // array is retained - so we know there's a problem if there's a different number of ids to usernames now. + if (count($user_id_ary) != count($username_ary)) + { + return 'GROUP_USERS_INVALID'; + } + + // Remove users who are already members of this group + $sql = 'SELECT user_id, group_leader + FROM ' . USER_GROUP_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $user_id_ary) . " + AND group_id = $group_id"; + $result = $db->sql_query($sql); + + $add_id_ary = $update_id_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $add_id_ary[] = (int) $row['user_id']; + + if ($leader && !$row['group_leader']) + { + $update_id_ary[] = (int) $row['user_id']; + } + } + $db->sql_freeresult($result); + + // Do all the users exist in this group? + $add_id_ary = array_diff($user_id_ary, $add_id_ary); + + // If we have no users + if (!count($add_id_ary) && !count($update_id_ary)) + { + return 'GROUP_USERS_EXIST'; + } + + $db->sql_transaction('begin'); + + // Insert the new users + if (count($add_id_ary)) + { + $sql_ary = array(); + + foreach ($add_id_ary as $user_id) + { + $sql_ary[] = array( + 'user_id' => (int) $user_id, + 'group_id' => (int) $group_id, + 'group_leader' => (int) $leader, + 'user_pending' => (int) $pending, + ); + } + + $db->sql_multi_insert(USER_GROUP_TABLE, $sql_ary); + } + + if (count($update_id_ary)) + { + $sql = 'UPDATE ' . USER_GROUP_TABLE . ' + SET group_leader = 1 + WHERE ' . $db->sql_in_set('user_id', $update_id_ary) . " + AND group_id = $group_id"; + $db->sql_query($sql); + } + + if ($default) + { + group_user_attributes('default', $group_id, $user_id_ary, false, $group_name, $group_attributes); + } + + $db->sql_transaction('commit'); + + // Clear permissions cache of relevant users + $auth->acl_clear_prefetch($user_id_ary); + + /** + * Event after users are added to a group + * + * @event core.group_add_user_after + * @var int group_id ID of the group to which users are added + * @var string group_name Name of the group + * @var array user_id_ary IDs of the users which are added + * @var array username_ary names of the users which are added + * @var int pending Pending setting, 1 if user(s) added are pending + * @since 3.1.7-RC1 + */ + $vars = array( + 'group_id', + 'group_name', + 'user_id_ary', + 'username_ary', + 'pending', + ); + extract($phpbb_dispatcher->trigger_event('core.group_add_user_after', compact($vars))); + + if (!$group_name) + { + $group_name = get_group_name($group_id); + } + + $log = ($leader) ? 'LOG_MODS_ADDED' : (($pending) ? 'LOG_USERS_PENDING' : 'LOG_USERS_ADDED'); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary))); + + group_update_listings($group_id); + + if ($pending) + { + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + foreach ($add_id_ary as $user_id) + { + $phpbb_notifications->add_notifications('notification.type.group_request', array( + 'group_id' => $group_id, + 'user_id' => $user_id, + 'group_name' => $group_name, + )); + } + } + + // Return false - no error + return false; +} + +/** +* Remove a user/s from a given group. When we remove users we update their +* default group_id. We do this by examining which "special" groups they belong +* to. The selection is made based on a reasonable priority system +* +* @return false if no errors occurred, else the user lang string for the relevant error, for example 'NO_USER' +*/ +function group_user_del($group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $log_action = true) +{ + global $db, $auth, $config, $user, $phpbb_dispatcher, $phpbb_container, $phpbb_log; + + if ($config['coppa_enable']) + { + $group_order = array('ADMINISTRATORS', 'GLOBAL_MODERATORS', 'NEWLY_REGISTERED', 'REGISTERED_COPPA', 'REGISTERED', 'BOTS', 'GUESTS'); + } + else + { + $group_order = array('ADMINISTRATORS', 'GLOBAL_MODERATORS', 'NEWLY_REGISTERED', 'REGISTERED', 'BOTS', 'GUESTS'); + } + + // We need both username and user_id info + $result = user_get_id_name($user_id_ary, $username_ary); + + if (empty($user_id_ary) || $result !== false) + { + return 'NO_USER'; + } + + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' + WHERE ' . $db->sql_in_set('group_name', $group_order); + $result = $db->sql_query($sql); + + $group_order_id = $special_group_data = array(); + while ($row = $db->sql_fetchrow($result)) + { + $group_order_id[$row['group_name']] = $row['group_id']; + + $special_group_data[$row['group_id']] = array( + 'group_colour' => $row['group_colour'], + 'group_rank' => $row['group_rank'], + ); + + // Only set the group avatar if one is defined... + if ($row['group_avatar']) + { + $special_group_data[$row['group_id']] = array_merge($special_group_data[$row['group_id']], array( + 'group_avatar' => $row['group_avatar'], + 'group_avatar_type' => $row['group_avatar_type'], + 'group_avatar_width' => $row['group_avatar_width'], + 'group_avatar_height' => $row['group_avatar_height']) + ); + } + } + $db->sql_freeresult($result); + + // Get users default groups - we only need to reset default group membership if the group from which the user gets removed is set as default + $sql = 'SELECT user_id, group_id + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $user_id_ary); + $result = $db->sql_query($sql); + + $default_groups = array(); + while ($row = $db->sql_fetchrow($result)) + { + $default_groups[$row['user_id']] = $row['group_id']; + } + $db->sql_freeresult($result); + + // What special group memberships exist for these users? + $sql = 'SELECT g.group_id, g.group_name, ug.user_id + FROM ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g + WHERE ' . $db->sql_in_set('ug.user_id', $user_id_ary) . " + AND g.group_id = ug.group_id + AND g.group_id <> $group_id + AND g.group_type = " . GROUP_SPECIAL . ' + ORDER BY ug.user_id, g.group_id'; + $result = $db->sql_query($sql); + + $temp_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($default_groups[$row['user_id']] == $group_id && (!isset($temp_ary[$row['user_id']]) || $group_order_id[$row['group_name']] < $temp_ary[$row['user_id']])) + { + $temp_ary[$row['user_id']] = $row['group_id']; + } + } + $db->sql_freeresult($result); + + // sql_where_ary holds the new default groups and their users + $sql_where_ary = array(); + foreach ($temp_ary as $uid => $gid) + { + $sql_where_ary[$gid][] = $uid; + } + unset($temp_ary); + + foreach ($special_group_data as $gid => $default_data_ary) + { + if (isset($sql_where_ary[$gid]) && count($sql_where_ary[$gid])) + { + remove_default_rank($group_id, $sql_where_ary[$gid]); + remove_default_avatar($group_id, $sql_where_ary[$gid]); + group_set_user_default($gid, $sql_where_ary[$gid], $default_data_ary); + } + } + unset($special_group_data); + + /** + * Event before users are removed from a group + * + * @event core.group_delete_user_before + * @var int group_id ID of the group from which users are deleted + * @var string group_name Name of the group + * @var array user_id_ary IDs of the users which are removed + * @var array username_ary names of the users which are removed + * @since 3.1.0-a1 + */ + $vars = array('group_id', 'group_name', 'user_id_ary', 'username_ary'); + extract($phpbb_dispatcher->trigger_event('core.group_delete_user_before', compact($vars))); + + $sql = 'DELETE FROM ' . USER_GROUP_TABLE . " + WHERE group_id = $group_id + AND " . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + + // Clear permissions cache of relevant users + $auth->acl_clear_prefetch($user_id_ary); + + /** + * Event after users are removed from a group + * + * @event core.group_delete_user_after + * @var int group_id ID of the group from which users are deleted + * @var string group_name Name of the group + * @var array user_id_ary IDs of the users which are removed + * @var array username_ary names of the users which are removed + * @since 3.1.7-RC1 + */ + $vars = array('group_id', 'group_name', 'user_id_ary', 'username_ary'); + extract($phpbb_dispatcher->trigger_event('core.group_delete_user_after', compact($vars))); + + if ($log_action) + { + if (!$group_name) + { + $group_name = get_group_name($group_id); + } + + $log = 'LOG_GROUP_REMOVE'; + + if ($group_name) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary))); + } + } + + group_update_listings($group_id); + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->delete_notifications('notification.type.group_request', $user_id_ary, $group_id); + + // Return false - no error + return false; +} + + +/** +* Removes the group avatar of the default group from the users in user_ids who have that group as default. +*/ +function remove_default_avatar($group_id, $user_ids) +{ + global $db; + + if (!is_array($user_ids)) + { + $user_ids = array($user_ids); + } + if (empty($user_ids)) + { + return false; + } + + $user_ids = array_map('intval', $user_ids); + + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . (int) $group_id; + $result = $db->sql_query($sql); + if (!$row = $db->sql_fetchrow($result)) + { + $db->sql_freeresult($result); + return false; + } + $db->sql_freeresult($result); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_avatar = '', + user_avatar_type = '', + user_avatar_width = 0, + user_avatar_height = 0 + WHERE group_id = " . (int) $group_id . " + AND user_avatar = '" . $db->sql_escape($row['group_avatar']) . "' + AND " . $db->sql_in_set('user_id', $user_ids); + + $db->sql_query($sql); +} + +/** +* Removes the group rank of the default group from the users in user_ids who have that group as default. +*/ +function remove_default_rank($group_id, $user_ids) +{ + global $db; + + if (!is_array($user_ids)) + { + $user_ids = array($user_ids); + } + if (empty($user_ids)) + { + return false; + } + + $user_ids = array_map('intval', $user_ids); + + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . (int) $group_id; + $result = $db->sql_query($sql); + if (!$row = $db->sql_fetchrow($result)) + { + $db->sql_freeresult($result); + return false; + } + $db->sql_freeresult($result); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_rank = 0 + WHERE group_id = ' . (int) $group_id . ' + AND user_rank <> 0 + AND user_rank = ' . (int) $row['group_rank'] . ' + AND ' . $db->sql_in_set('user_id', $user_ids); + $db->sql_query($sql); +} + +/** +* This is used to promote (to leader), demote or set as default a member/s +*/ +function group_user_attributes($action, $group_id, $user_id_ary = false, $username_ary = false, $group_name = false, $group_attributes = false) +{ + global $db, $auth, $user, $phpbb_container, $phpbb_log, $phpbb_dispatcher; + + // We need both username and user_id info + $result = user_get_id_name($user_id_ary, $username_ary); + + if (empty($user_id_ary) || $result !== false) + { + return 'NO_USERS'; + } + + if (!$group_name) + { + $group_name = get_group_name($group_id); + } + + switch ($action) + { + case 'demote': + case 'promote': + + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . " + WHERE group_id = $group_id + AND user_pending = 1 + AND " . $db->sql_in_set('user_id', $user_id_ary); + $result = $db->sql_query_limit($sql, 1); + $not_empty = ($db->sql_fetchrow($result)); + $db->sql_freeresult($result); + if ($not_empty) + { + return 'NO_VALID_USERS'; + } + + $sql = 'UPDATE ' . USER_GROUP_TABLE . ' + SET group_leader = ' . (($action == 'promote') ? 1 : 0) . " + WHERE group_id = $group_id + AND user_pending = 0 + AND " . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + + $log = ($action == 'promote') ? 'LOG_GROUP_PROMOTED' : 'LOG_GROUP_DEMOTED'; + break; + + case 'approve': + // Make sure we only approve those which are pending ;) + $sql = 'SELECT u.user_id, u.user_email, u.username, u.username_clean, u.user_notify_type, u.user_jabber, u.user_lang + FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . ' ug + WHERE ug.group_id = ' . $group_id . ' + AND ug.user_pending = 1 + AND ug.user_id = u.user_id + AND ' . $db->sql_in_set('ug.user_id', $user_id_ary); + $result = $db->sql_query($sql); + + $user_id_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_id_ary[] = $row['user_id']; + } + $db->sql_freeresult($result); + + if (!count($user_id_ary)) + { + return false; + } + + $sql = 'UPDATE ' . USER_GROUP_TABLE . " + SET user_pending = 0 + WHERE group_id = $group_id + AND " . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->add_notifications('notification.type.group_request_approved', array( + 'user_ids' => $user_id_ary, + 'group_id' => $group_id, + 'group_name' => $group_name, + )); + $phpbb_notifications->delete_notifications('notification.type.group_request', $user_id_ary, $group_id); + + $log = 'LOG_USERS_APPROVED'; + break; + + case 'default': + // We only set default group for approved members of the group + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . " + WHERE group_id = $group_id + AND user_pending = 0 + AND " . $db->sql_in_set('user_id', $user_id_ary); + $result = $db->sql_query($sql); + + $user_id_ary = $username_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_id_ary[] = $row['user_id']; + } + $db->sql_freeresult($result); + + $result = user_get_id_name($user_id_ary, $username_ary); + if (!count($user_id_ary) || $result !== false) + { + return 'NO_USERS'; + } + + $sql = 'SELECT user_id, group_id + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $user_id_ary, false, true); + $result = $db->sql_query($sql); + + $groups = array(); + while ($row = $db->sql_fetchrow($result)) + { + if (!isset($groups[$row['group_id']])) + { + $groups[$row['group_id']] = array(); + } + $groups[$row['group_id']][] = $row['user_id']; + } + $db->sql_freeresult($result); + + foreach ($groups as $gid => $uids) + { + remove_default_rank($gid, $uids); + remove_default_avatar($gid, $uids); + } + group_set_user_default($group_id, $user_id_ary, $group_attributes); + $log = 'LOG_GROUP_DEFAULTS'; + break; + } + + /** + * Event to perform additional actions on setting user group attributes + * + * @event core.user_set_group_attributes + * @var int group_id ID of the group + * @var string group_name Name of the group + * @var array user_id_ary IDs of the users to set group attributes + * @var array username_ary Names of the users to set group attributes + * @var array group_attributes Group attributes which were changed + * @var string action Action to perform over the group members + * @since 3.1.10-RC1 + */ + $vars = array( + 'group_id', + 'group_name', + 'user_id_ary', + 'username_ary', + 'group_attributes', + 'action', + ); + extract($phpbb_dispatcher->trigger_event('core.user_set_group_attributes', compact($vars))); + + // Clear permissions cache of relevant users + $auth->acl_clear_prefetch($user_id_ary); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, $log, false, array($group_name, implode(', ', $username_ary))); + + group_update_listings($group_id); + + return false; +} + +/** +* A small version of validate_username to check for a group name's existence. To be called directly. +*/ +function group_validate_groupname($group_id, $group_name) +{ + global $db; + + $group_name = utf8_clean_string($group_name); + + if (!empty($group_id)) + { + $sql = 'SELECT group_name + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . (int) $group_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + return false; + } + + $allowed_groupname = utf8_clean_string($row['group_name']); + + if ($allowed_groupname == $group_name) + { + return false; + } + } + + $sql = 'SELECT group_name + FROM ' . GROUPS_TABLE . " + WHERE LOWER(group_name) = '" . $db->sql_escape(utf8_strtolower($group_name)) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + return 'GROUP_NAME_TAKEN'; + } + + return false; +} + +/** +* Set users default group +* +* @access private +*/ +function group_set_user_default($group_id, $user_id_ary, $group_attributes = false, $update_listing = false) +{ + global $config, $phpbb_container, $db, $phpbb_dispatcher; + + if (empty($user_id_ary)) + { + return; + } + + $attribute_ary = array( + 'group_colour' => 'string', + 'group_rank' => 'int', + 'group_avatar' => 'string', + 'group_avatar_type' => 'string', + 'group_avatar_width' => 'int', + 'group_avatar_height' => 'int', + ); + + $sql_ary = array( + 'group_id' => $group_id + ); + + // Were group attributes passed to the function? If not we need to obtain them + if ($group_attributes === false) + { + $sql = 'SELECT ' . implode(', ', array_keys($attribute_ary)) . ' + FROM ' . GROUPS_TABLE . " + WHERE group_id = $group_id"; + $result = $db->sql_query($sql); + $group_attributes = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + foreach ($attribute_ary as $attribute => $type) + { + if (isset($group_attributes[$attribute])) + { + // If we are about to set an avatar or rank, we will not overwrite with empty, unless we are not actually changing the default group + if ((strpos($attribute, 'group_avatar') === 0 || strpos($attribute, 'group_rank') === 0) && !$group_attributes[$attribute]) + { + continue; + } + + settype($group_attributes[$attribute], $type); + $sql_ary[str_replace('group_', 'user_', $attribute)] = $group_attributes[$attribute]; + } + } + + $updated_sql_ary = $sql_ary; + + // Before we update the user attributes, we will update the rank for users that don't have a custom rank + if (isset($sql_ary['user_rank'])) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', array('user_rank' => $sql_ary['user_rank'])) . ' + WHERE user_rank = 0 + AND ' . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + unset($sql_ary['user_rank']); + } + + // Before we update the user attributes, we will update the avatar for users that don't have a custom avatar + $avatar_options = array('user_avatar', 'user_avatar_type', 'user_avatar_height', 'user_avatar_width'); + + if (isset($sql_ary['user_avatar'])) + { + $avatar_sql_ary = array(); + foreach ($avatar_options as $avatar_option) + { + if (isset($sql_ary[$avatar_option])) + { + $avatar_sql_ary[$avatar_option] = $sql_ary[$avatar_option]; + } + } + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $avatar_sql_ary) . " + WHERE user_avatar = '' + AND " . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + } + + // Remove the avatar options, as we already updated them + foreach ($avatar_options as $avatar_option) + { + unset($sql_ary[$avatar_option]); + } + + if (!empty($sql_ary)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE ' . $db->sql_in_set('user_id', $user_id_ary); + $db->sql_query($sql); + } + + if (isset($sql_ary['user_colour'])) + { + // Update any cached colour information for these users + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET forum_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' + WHERE " . $db->sql_in_set('forum_last_poster_id', $user_id_ary); + $db->sql_query($sql); + + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_first_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' + WHERE " . $db->sql_in_set('topic_poster', $user_id_ary); + $db->sql_query($sql); + + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_last_poster_colour = '" . $db->sql_escape($sql_ary['user_colour']) . "' + WHERE " . $db->sql_in_set('topic_last_poster_id', $user_id_ary); + $db->sql_query($sql); + + if (in_array($config['newest_user_id'], $user_id_ary)) + { + $config->set('newest_user_colour', $sql_ary['user_colour'], false); + } + } + + // Make all values available for the event + $sql_ary = $updated_sql_ary; + + /** + * Event when the default group is set for an array of users + * + * @event core.user_set_default_group + * @var int group_id ID of the group + * @var array user_id_ary IDs of the users + * @var array group_attributes Group attributes which were changed + * @var array update_listing Update the list of moderators and foes + * @var array sql_ary User attributes which were changed + * @since 3.1.0-a1 + */ + $vars = array('group_id', 'user_id_ary', 'group_attributes', 'update_listing', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.user_set_default_group', compact($vars))); + + if ($update_listing) + { + group_update_listings($group_id); + } + + // Because some tables/caches use usercolour-specific data we need to purge this here. + $phpbb_container->get('cache.driver')->destroy('sql', MODERATOR_CACHE_TABLE); +} + +/** +* Get group name +*/ +function get_group_name($group_id) +{ + global $db, $phpbb_container; + + $sql = 'SELECT group_name, group_type + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . (int) $group_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + return ''; + } + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + return $group_helper->get_name($row['group_name']); +} + +/** +* Obtain either the members of a specified group, the groups the specified user is subscribed to +* or checking if a specified user is in a specified group. This function does not return pending memberships. +* +* Note: Never use this more than once... first group your users/groups +*/ +function group_memberships($group_id_ary = false, $user_id_ary = false, $return_bool = false) +{ + global $db; + + if (!$group_id_ary && !$user_id_ary) + { + return true; + } + + if ($user_id_ary) + { + $user_id_ary = (!is_array($user_id_ary)) ? array($user_id_ary) : $user_id_ary; + } + + if ($group_id_ary) + { + $group_id_ary = (!is_array($group_id_ary)) ? array($group_id_ary) : $group_id_ary; + } + + $sql = 'SELECT ug.*, u.username, u.username_clean, u.user_email + FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . ' u + WHERE ug.user_id = u.user_id + AND ug.user_pending = 0 AND '; + + if ($group_id_ary) + { + $sql .= ' ' . $db->sql_in_set('ug.group_id', $group_id_ary); + } + + if ($user_id_ary) + { + $sql .= ($group_id_ary) ? ' AND ' : ' '; + $sql .= $db->sql_in_set('ug.user_id', $user_id_ary); + } + + $result = ($return_bool) ? $db->sql_query_limit($sql, 1) : $db->sql_query($sql); + + $row = $db->sql_fetchrow($result); + + if ($return_bool) + { + $db->sql_freeresult($result); + return ($row) ? true : false; + } + + if (!$row) + { + return false; + } + + $return = array(); + + do + { + $return[] = $row; + } + while ($row = $db->sql_fetchrow($result)); + + $db->sql_freeresult($result); + + return $return; +} + +/** +* Re-cache moderators and foes if group has a_ or m_ permissions +*/ +function group_update_listings($group_id) +{ + global $db, $cache, $auth; + + $hold_ary = $auth->acl_group_raw_data($group_id, array('a_', 'm_')); + + if (empty($hold_ary)) + { + return; + } + + $mod_permissions = $admin_permissions = false; + + foreach ($hold_ary as $g_id => $forum_ary) + { + foreach ($forum_ary as $forum_id => $auth_ary) + { + foreach ($auth_ary as $auth_option => $setting) + { + if ($mod_permissions && $admin_permissions) + { + break 3; + } + + if ($setting != ACL_YES) + { + continue; + } + + if ($auth_option == 'm_') + { + $mod_permissions = true; + } + + if ($auth_option == 'a_') + { + $admin_permissions = true; + } + } + } + } + + if ($mod_permissions) + { + if (!function_exists('phpbb_cache_moderators')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + } + phpbb_cache_moderators($db, $cache, $auth); + } + + if ($mod_permissions || $admin_permissions) + { + if (!function_exists('phpbb_update_foes')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + } + phpbb_update_foes($db, $auth, array($group_id)); + } +} + + + +/** +* Funtion to make a user leave the NEWLY_REGISTERED system group. +* @access public +* @param $user_id The id of the user to remove from the group +*/ +function remove_newly_registered($user_id, $user_data = false) +{ + global $db; + + if ($user_data === false) + { + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query($sql); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$user_row) + { + return false; + } + else + { + $user_data = $user_row; + } + } + + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'NEWLY_REGISTERED' + AND group_type = " . GROUP_SPECIAL; + $result = $db->sql_query($sql); + $group_id = (int) $db->sql_fetchfield('group_id'); + $db->sql_freeresult($result); + + if (!$group_id) + { + return false; + } + + // We need to call group_user_del here, because this function makes sure everything is correctly changed. + // Force function to not log the removal of users from newly registered users group + group_user_del($group_id, $user_id, false, false, false); + + // Set user_new to 0 to let this not be triggered again + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_new = 0 + WHERE user_id = ' . $user_id; + $db->sql_query($sql); + + // The new users group was the users default group? + if ($user_data['group_id'] == $group_id) + { + // Which group is now the users default one? + $sql = 'SELECT group_id + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query($sql); + $user_data['group_id'] = $db->sql_fetchfield('group_id'); + $db->sql_freeresult($result); + } + + return $user_data['group_id']; +} + +/** +* Gets user ids of currently banned registered users. +* +* @param array $user_ids Array of users' ids to check for banning, +* leave empty to get complete list of banned ids +* @param bool|int $ban_end Bool True to get users currently banned +* Bool False to only get permanently banned users +* Int Unix timestamp to get users banned until that time +* @return array Array of banned users' ids if any, empty array otherwise +*/ +function phpbb_get_banned_user_ids($user_ids = array(), $ban_end = true) +{ + global $db; + + $sql_user_ids = (!empty($user_ids)) ? $db->sql_in_set('ban_userid', $user_ids) : 'ban_userid <> 0'; + + // Get banned User ID's + // Ignore stale bans which were not wiped yet + $banned_ids_list = array(); + $sql = 'SELECT ban_userid + FROM ' . BANLIST_TABLE . " + WHERE $sql_user_ids + AND ban_exclude <> 1"; + + if ($ban_end === true) + { + // Banned currently + $sql .= " AND (ban_end > " . time() . ' + OR ban_end = 0)'; + } + else if ($ban_end === false) + { + // Permanently banned + $sql .= " AND ban_end = 0"; + } + else + { + // Banned until a specified time + $sql .= " AND (ban_end > " . (int) $ban_end . ' + OR ban_end = 0)'; + } + + $result = $db->sql_query($sql); + while ($row = $db->sql_fetchrow($result)) + { + $user_id = (int) $row['ban_userid']; + $banned_ids_list[$user_id] = $user_id; + } + $db->sql_freeresult($result); + + return $banned_ids_list; +} + +/** +* Function for assigning a template var if the zebra module got included +*/ +function phpbb_module_zebra($mode, &$module_row) +{ + global $template; + + $template->assign_var('S_ZEBRA_ENABLED', true); + + if ($mode == 'friends') + { + $template->assign_var('S_ZEBRA_FRIENDS_ENABLED', true); + } + + if ($mode == 'foes') + { + $template->assign_var('S_ZEBRA_FOES_ENABLED', true); + } +} diff --git a/includes/hooks/index.php b/includes/hooks/index.php new file mode 100644 index 0000000..821242c --- /dev/null +++ b/includes/hooks/index.php @@ -0,0 +1,250 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* phpBB Hook Class +*/ +class phpbb_hook +{ + /** + * Registered hooks + */ + var $hooks = array(); + + /** + * Results returned by functions called + */ + var $hook_result = array(); + + /** + * internal pointer + */ + var $current_hook = NULL; + + /** + * Initialize hook class. + * + * @param array $valid_hooks array containing the hookable functions/methods + */ + function __construct($valid_hooks) + { + foreach ($valid_hooks as $_null => $method) + { + $this->add_hook($method); + } + + if (function_exists('phpbb_hook_register')) + { + phpbb_hook_register($this); + } + } + + /** + * Register function/method to be called within hook + * This function is normally called by the modification/application to attach/register the functions. + * + * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) + * @param mixed $hook The replacement function/method to be called. Passing function name or array with object/class definition + * @param string $mode Specify the priority/chain mode. 'normal' -> hook gets appended to the chain. 'standalone' -> only the specified hook gets called - later hooks are not able to overwrite this (E_NOTICE is triggered then). 'first' -> hook is called as the first one within the chain. 'last' -> hook is called as the last one within the chain. + */ + function register($definition, $hook, $mode = 'normal') + { + $class = (!is_array($definition)) ? '__global' : $definition[0]; + $function = (!is_array($definition)) ? $definition : $definition[1]; + + // Method able to be hooked? + if (isset($this->hooks[$class][$function])) + { + switch ($mode) + { + case 'standalone': + if (!isset($this->hooks[$class][$function]['standalone'])) + { + $this->hooks[$class][$function] = array('standalone' => $hook); + } + else + { + trigger_error('Hook not able to be called standalone, previous hook already standalone.', E_NOTICE); + } + break; + + case 'first': + case 'last': + $this->hooks[$class][$function][$mode][] = $hook; + break; + + case 'normal': + default: + $this->hooks[$class][$function]['normal'][] = $hook; + break; + } + } + } + + /** + * Calling all functions/methods attached to a specified hook. + * Called by the function allowing hooks... + * + * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) + * @return bool False if no hook got executed, true otherwise + */ + function call_hook($definition) + { + $class = (!is_array($definition)) ? '__global' : $definition[0]; + $function = (!is_array($definition)) ? $definition : $definition[1]; + + if (!empty($this->hooks[$class][$function])) + { + // Developer tries to call a hooked function within the hooked function... + if ($this->current_hook !== NULL && $this->current_hook['class'] === $class && $this->current_hook['function'] === $function) + { + return false; + } + + // Call the hook with the arguments attached and store result + $arguments = func_get_args(); + $this->current_hook = array('class' => $class, 'function' => $function); + $arguments[0] = &$this; + + // Call the hook chain... + if (isset($this->hooks[$class][$function]['standalone'])) + { + $this->hook_result[$class][$function] = call_user_func_array($this->hooks[$class][$function]['standalone'], $arguments); + } + else + { + foreach (array('first', 'normal', 'last') as $mode) + { + if (!isset($this->hooks[$class][$function][$mode])) + { + continue; + } + + foreach ($this->hooks[$class][$function][$mode] as $hook) + { + $this->hook_result[$class][$function] = call_user_func_array($hook, $arguments); + } + } + } + + $this->current_hook = NULL; + return true; + } + + $this->current_hook = NULL; + return false; + } + + /** + * Get result from previously called functions/methods for the same hook + * + * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) + * @return mixed False if nothing returned if there is no result, else array('result' => ... ) + */ + function previous_hook_result($definition) + { + $class = (!is_array($definition)) ? '__global' : $definition[0]; + $function = (!is_array($definition)) ? $definition : $definition[1]; + + if (!empty($this->hooks[$class][$function]) && isset($this->hook_result[$class][$function])) + { + return array('result' => $this->hook_result[$class][$function]); + } + + return false; + } + + /** + * Check if the called functions/methods returned something. + * + * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) + * @return bool True if results are there, false if not + */ + function hook_return($definition) + { + $class = (!is_array($definition)) ? '__global' : $definition[0]; + $function = (!is_array($definition)) ? $definition : $definition[1]; + + if (!empty($this->hooks[$class][$function]) && isset($this->hook_result[$class][$function])) + { + return true; + } + + return false; + } + + /** + * Give actual result from called functions/methods back. + * + * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) + * @return mixed The result + */ + function hook_return_result($definition) + { + $class = (!is_array($definition)) ? '__global' : $definition[0]; + $function = (!is_array($definition)) ? $definition : $definition[1]; + + if (!empty($this->hooks[$class][$function]) && isset($this->hook_result[$class][$function])) + { + $result = $this->hook_result[$class][$function]; + unset($this->hook_result[$class][$function]); + return $result; + } + + return; + } + + /** + * Add new function to the allowed hooks. + * + * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) + */ + function add_hook($definition) + { + if (!is_array($definition)) + { + $definition = array('__global', $definition); + } + + $this->hooks[$definition[0]][$definition[1]] = array(); + } + + /** + * Remove function from the allowed hooks. + * + * @param mixed $definition Declaring function (with __FUNCTION__) or class with array(__CLASS__, __FUNCTION__) + */ + function remove_hook($definition) + { + $class = (!is_array($definition)) ? '__global' : $definition[0]; + $function = (!is_array($definition)) ? $definition : $definition[1]; + + if (isset($this->hooks[$class][$function])) + { + unset($this->hooks[$class][$function]); + + if (isset($this->hook_result[$class][$function])) + { + unset($this->hook_result[$class][$function]); + } + } + } +} diff --git a/includes/index.htm b/includes/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/includes/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/includes/mcp/info/mcp_ban.php b/includes/mcp/info/mcp_ban.php new file mode 100644 index 0000000..b4fd327 --- /dev/null +++ b/includes/mcp/info/mcp_ban.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class mcp_ban_info +{ + function module() + { + return array( + 'filename' => 'mcp_ban', + 'title' => 'MCP_BAN', + 'modes' => array( + 'user' => array('title' => 'MCP_BAN_USERNAMES', 'auth' => 'acl_m_ban', 'cat' => array('MCP_BAN')), + 'ip' => array('title' => 'MCP_BAN_IPS', 'auth' => 'acl_m_ban', 'cat' => array('MCP_BAN')), + 'email' => array('title' => 'MCP_BAN_EMAILS', 'auth' => 'acl_m_ban', 'cat' => array('MCP_BAN')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/mcp/info/mcp_logs.php b/includes/mcp/info/mcp_logs.php new file mode 100644 index 0000000..7a0205f --- /dev/null +++ b/includes/mcp/info/mcp_logs.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class mcp_logs_info +{ + function module() + { + return array( + 'filename' => 'mcp_logs', + 'title' => 'MCP_LOGS', + 'modes' => array( + 'front' => array('title' => 'MCP_LOGS_FRONT', 'auth' => 'acl_m_ || aclf_m_', 'cat' => array('MCP_LOGS')), + 'forum_logs' => array('title' => 'MCP_LOGS_FORUM_VIEW', 'auth' => 'acl_m_,$id', 'cat' => array('MCP_LOGS')), + 'topic_logs' => array('title' => 'MCP_LOGS_TOPIC_VIEW', 'auth' => 'acl_m_,$id', 'cat' => array('MCP_LOGS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/mcp/info/mcp_main.php b/includes/mcp/info/mcp_main.php new file mode 100644 index 0000000..c0f0363 --- /dev/null +++ b/includes/mcp/info/mcp_main.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class mcp_main_info +{ + function module() + { + return array( + 'filename' => 'mcp_main', + 'title' => 'MCP_MAIN', + 'modes' => array( + 'front' => array('title' => 'MCP_MAIN_FRONT', 'auth' => '', 'cat' => array('MCP_MAIN')), + 'forum_view' => array('title' => 'MCP_MAIN_FORUM_VIEW', 'auth' => 'acl_m_,$id', 'cat' => array('MCP_MAIN')), + 'topic_view' => array('title' => 'MCP_MAIN_TOPIC_VIEW', 'auth' => 'acl_m_,$id', 'cat' => array('MCP_MAIN')), + 'post_details' => array('title' => 'MCP_MAIN_POST_DETAILS', 'auth' => 'acl_m_,$id || (!$id && aclf_m_)', 'cat' => array('MCP_MAIN')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/mcp/info/mcp_notes.php b/includes/mcp/info/mcp_notes.php new file mode 100644 index 0000000..de4a41d --- /dev/null +++ b/includes/mcp/info/mcp_notes.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class mcp_notes_info +{ + function module() + { + return array( + 'filename' => 'mcp_notes', + 'title' => 'MCP_NOTES', + 'modes' => array( + 'front' => array('title' => 'MCP_NOTES_FRONT', 'auth' => '', 'cat' => array('MCP_NOTES')), + 'user_notes' => array('title' => 'MCP_NOTES_USER', 'auth' => '', 'cat' => array('MCP_NOTES')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/mcp/info/mcp_pm_reports.php b/includes/mcp/info/mcp_pm_reports.php new file mode 100644 index 0000000..2a57c0c --- /dev/null +++ b/includes/mcp/info/mcp_pm_reports.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class mcp_pm_reports_info +{ + function module() + { + return array( + 'filename' => 'mcp_pm_reports', + 'title' => 'MCP_PM_REPORTS', + 'modes' => array( + 'pm_reports' => array('title' => 'MCP_PM_REPORTS_OPEN', 'auth' => 'acl_m_pm_report', 'cat' => array('MCP_REPORTS')), + 'pm_reports_closed' => array('title' => 'MCP_PM_REPORTS_CLOSED', 'auth' => 'acl_m_pm_report', 'cat' => array('MCP_REPORTS')), + 'pm_report_details' => array('title' => 'MCP_PM_REPORT_DETAILS', 'auth' => 'acl_m_pm_report', 'cat' => array('MCP_REPORTS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/mcp/info/mcp_queue.php b/includes/mcp/info/mcp_queue.php new file mode 100644 index 0000000..d5605aa --- /dev/null +++ b/includes/mcp/info/mcp_queue.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class mcp_queue_info +{ + function module() + { + return array( + 'filename' => 'mcp_queue', + 'title' => 'MCP_QUEUE', + 'modes' => array( + 'unapproved_topics' => array('title' => 'MCP_QUEUE_UNAPPROVED_TOPICS', 'auth' => 'aclf_m_approve', 'cat' => array('MCP_QUEUE')), + 'unapproved_posts' => array('title' => 'MCP_QUEUE_UNAPPROVED_POSTS', 'auth' => 'aclf_m_approve', 'cat' => array('MCP_QUEUE')), + 'deleted_topics' => array('title' => 'MCP_QUEUE_DELETED_TOPICS', 'auth' => 'aclf_m_approve', 'cat' => array('MCP_QUEUE')), + 'deleted_posts' => array('title' => 'MCP_QUEUE_DELETED_POSTS', 'auth' => 'aclf_m_approve', 'cat' => array('MCP_QUEUE')), + 'approve_details' => array('title' => 'MCP_QUEUE_APPROVE_DETAILS', 'auth' => 'acl_m_approve,$id || (!$id && aclf_m_approve)', 'cat' => array('MCP_QUEUE')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/mcp/info/mcp_reports.php b/includes/mcp/info/mcp_reports.php new file mode 100644 index 0000000..76e62ef --- /dev/null +++ b/includes/mcp/info/mcp_reports.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class mcp_reports_info +{ + function module() + { + return array( + 'filename' => 'mcp_reports', + 'title' => 'MCP_REPORTS', + 'modes' => array( + 'reports' => array('title' => 'MCP_REPORTS_OPEN', 'auth' => 'aclf_m_report', 'cat' => array('MCP_REPORTS')), + 'reports_closed' => array('title' => 'MCP_REPORTS_CLOSED', 'auth' => 'aclf_m_report', 'cat' => array('MCP_REPORTS')), + 'report_details' => array('title' => 'MCP_REPORT_DETAILS', 'auth' => 'acl_m_report,$id || (!$id && aclf_m_report)', 'cat' => array('MCP_REPORTS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/mcp/info/mcp_warn.php b/includes/mcp/info/mcp_warn.php new file mode 100644 index 0000000..b4f83e7 --- /dev/null +++ b/includes/mcp/info/mcp_warn.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class mcp_warn_info +{ + function module() + { + return array( + 'filename' => 'mcp_warn', + 'title' => 'MCP_WARN', + 'modes' => array( + 'front' => array('title' => 'MCP_WARN_FRONT', 'auth' => 'aclf_m_warn', 'cat' => array('MCP_WARN')), + 'list' => array('title' => 'MCP_WARN_LIST', 'auth' => 'aclf_m_warn', 'cat' => array('MCP_WARN')), + 'warn_user' => array('title' => 'MCP_WARN_USER', 'auth' => 'aclf_m_warn', 'cat' => array('MCP_WARN')), + 'warn_post' => array('title' => 'MCP_WARN_POST', 'auth' => 'acl_m_warn && acl_f_read,$id', 'cat' => array('MCP_WARN')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/mcp/mcp_ban.php b/includes/mcp/mcp_ban.php new file mode 100644 index 0000000..8797f06 --- /dev/null +++ b/includes/mcp/mcp_ban.php @@ -0,0 +1,301 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class mcp_ban +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $auth, $template, $request, $phpbb_dispatcher; + global $phpbb_root_path, $phpEx; + + if (!function_exists('user_ban')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + // Include the admin banning interface... + if (!class_exists('acp_ban')) + { + include($phpbb_root_path . 'includes/acp/acp_ban.' . $phpEx); + } + + $bansubmit = $request->is_set_post('bansubmit'); + $unbansubmit = $request->is_set_post('unbansubmit'); + + $user->add_lang(array('acp/ban', 'acp/users')); + $this->tpl_name = 'mcp_ban'; + + /** + * Use this event to pass perform actions when a ban is issued or revoked + * + * @event core.mcp_ban_main + * @var bool bansubmit True if a ban is issued + * @var bool unbansubmit True if a ban is removed + * @var string mode Mode of the ban that is being worked on + * @since 3.1.0-RC5 + */ + $vars = array( + 'bansubmit', + 'unbansubmit', + 'mode', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_ban_main', compact($vars))); + + // Ban submitted? + if ($bansubmit) + { + // Grab the list of entries + $ban = $request->variable('ban', '', $mode === 'user'); + $ban_length = $request->variable('banlength', 0); + $ban_length_other = $request->variable('banlengthother', ''); + $ban_exclude = $request->variable('banexclude', 0); + $ban_reason = $request->variable('banreason', '', true); + $ban_give_reason = $request->variable('bangivereason', '', true); + + if ($ban) + { + if (confirm_box(true)) + { + $abort_ban = false; + /** + * Use this event to modify the ban details before the ban is performed + * + * @event core.mcp_ban_before + * @var string mode One of the following: user, ip, email + * @var string ban Either string or array with usernames, ips or email addresses + * @var int ban_length Ban length in minutes + * @var string ban_length_other Ban length as a date (YYYY-MM-DD) + * @var bool ban_exclude Are we banning or excluding from another ban + * @var string ban_reason Ban reason displayed to moderators + * @var string ban_give_reason Ban reason displayed to the banned user + * @var mixed abort_ban Either false, or an error message that is displayed to the user. + * If a string is given the bans are not issued. + * @since 3.1.0-RC5 + */ + $vars = array( + 'mode', + 'ban', + 'ban_length', + 'ban_length_other', + 'ban_exclude', + 'ban_reason', + 'ban_give_reason', + 'abort_ban', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_ban_before', compact($vars))); + + if ($abort_ban) + { + trigger_error($abort_ban); + } + user_ban($mode, $ban, $ban_length, $ban_length_other, $ban_exclude, $ban_reason, $ban_give_reason); + + /** + * Use this event to perform actions after the ban has been performed + * + * @event core.mcp_ban_after + * @var string mode One of the following: user, ip, email + * @var string ban Either string or array with usernames, ips or email addresses + * @var int ban_length Ban length in minutes + * @var string ban_length_other Ban length as a date (YYYY-MM-DD) + * @var bool ban_exclude Are we banning or excluding from another ban + * @var string ban_reason Ban reason displayed to moderators + * @var string ban_give_reason Ban reason displayed to the banned user + * @since 3.1.0-RC5 + */ + $vars = array( + 'mode', + 'ban', + 'ban_length', + 'ban_length_other', + 'ban_exclude', + 'ban_reason', + 'ban_give_reason', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_ban_after', compact($vars))); + + trigger_error($user->lang['BAN_UPDATE_SUCCESSFUL'] . '

« ' . $user->lang['BACK_TO_PREV'] . ''); + } + else + { + $hidden_fields = array( + 'mode' => $mode, + 'ban' => $ban, + 'bansubmit' => true, + 'banlength' => $ban_length, + 'banlengthother' => $ban_length_other, + 'banexclude' => $ban_exclude, + 'banreason' => $ban_reason, + 'bangivereason' => $ban_give_reason, + ); + + /** + * Use this event to pass data from the ban form to the confirmation screen + * + * @event core.mcp_ban_confirm + * @var array hidden_fields Hidden fields that are passed through the confirm screen + * @since 3.1.0-RC5 + */ + $vars = array('hidden_fields'); + extract($phpbb_dispatcher->trigger_event('core.mcp_ban_confirm', compact($vars))); + + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields($hidden_fields)); + } + } + } + else if ($unbansubmit) + { + $ban = $request->variable('unban', array('')); + + if ($ban) + { + if (confirm_box(true)) + { + user_unban($mode, $ban); + + trigger_error($user->lang['BAN_UPDATE_SUCCESSFUL'] . '

« ' . $user->lang['BACK_TO_PREV'] . ''); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mode' => $mode, + 'unbansubmit' => true, + 'unban' => $ban))); + } + } + } + + // Ban length options + $ban_end_text = array(0 => $user->lang['PERMANENT'], 30 => $user->lang['30_MINS'], 60 => $user->lang['1_HOUR'], 360 => $user->lang['6_HOURS'], 1440 => $user->lang['1_DAY'], 10080 => $user->lang['7_DAYS'], 20160 => $user->lang['2_WEEKS'], 40320 => $user->lang['1_MONTH'], -1 => $user->lang['UNTIL'] . ' -> '); + + $ban_end_options = ''; + foreach ($ban_end_text as $length => $text) + { + $ban_end_options .= ''; + } + + // Define language vars + $this->page_title = $user->lang[strtoupper($mode) . '_BAN']; + + $l_ban_explain = $user->lang[strtoupper($mode) . '_BAN_EXPLAIN']; + $l_ban_exclude_explain = $user->lang[strtoupper($mode) . '_BAN_EXCLUDE_EXPLAIN']; + $l_unban_title = $user->lang[strtoupper($mode) . '_UNBAN']; + $l_unban_explain = $user->lang[strtoupper($mode) . '_UNBAN_EXPLAIN']; + $l_no_ban_cell = $user->lang[strtoupper($mode) . '_NO_BANNED']; + + switch ($mode) + { + case 'user': + $l_ban_cell = $user->lang['USERNAME']; + break; + + case 'ip': + $l_ban_cell = $user->lang['IP_HOSTNAME']; + break; + + case 'email': + $l_ban_cell = $user->lang['EMAIL_ADDRESS']; + break; + } + + acp_ban::display_ban_options($mode); + + $template->assign_vars(array( + 'L_TITLE' => $this->page_title, + 'L_EXPLAIN' => $l_ban_explain, + 'L_UNBAN_TITLE' => $l_unban_title, + 'L_UNBAN_EXPLAIN' => $l_unban_explain, + 'L_BAN_CELL' => $l_ban_cell, + 'L_BAN_EXCLUDE_EXPLAIN' => $l_ban_exclude_explain, + 'L_NO_BAN_CELL' => $l_no_ban_cell, + + 'S_USERNAME_BAN' => ($mode == 'user') ? true : false, + + 'U_ACTION' => $this->u_action, + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=mcp_ban&field=ban'), + )); + + if ($mode === 'email' && !$auth->acl_get('a_user')) + { + return; + } + + // As a "service" we will check if any post id is specified and populate the username of the poster id if given + $post_id = $request->variable('p', 0); + $user_id = $request->variable('u', 0); + $pre_fill = false; + + if ($user_id && $user_id <> ANONYMOUS) + { + $sql = 'SELECT username, user_email, user_ip + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query($sql); + switch ($mode) + { + case 'user': + $pre_fill = (string) $db->sql_fetchfield('username'); + break; + + case 'ip': + $pre_fill = (string) $db->sql_fetchfield('user_ip'); + break; + + case 'email': + $pre_fill = (string) $db->sql_fetchfield('user_email'); + break; + } + $db->sql_freeresult($result); + } + else if ($post_id) + { + $post_info = phpbb_get_post_data($post_id, 'm_ban'); + + if (count($post_info) && !empty($post_info[$post_id])) + { + switch ($mode) + { + case 'user': + $pre_fill = $post_info[$post_id]['username']; + break; + + case 'ip': + $pre_fill = $post_info[$post_id]['poster_ip']; + break; + + case 'email': + $pre_fill = $post_info[$post_id]['user_email']; + break; + } + + } + } + + if ($pre_fill) + { + // left for legacy template compatibility + $template->assign_var('USERNAMES', $pre_fill); + $template->assign_var('BAN_QUANTIFIER', $pre_fill); + } + } +} diff --git a/includes/mcp/mcp_forum.php b/includes/mcp/mcp_forum.php new file mode 100644 index 0000000..19f71e0 --- /dev/null +++ b/includes/mcp/mcp_forum.php @@ -0,0 +1,548 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* MCP Forum View +*/ +function mcp_forum_view($id, $mode, $action, $forum_info) +{ + global $template, $db, $user, $auth, $cache, $module; + global $phpEx, $phpbb_root_path, $config; + global $request, $phpbb_dispatcher, $phpbb_container; + + $user->add_lang(array('viewtopic', 'viewforum')); + + include_once($phpbb_root_path . 'includes/functions_display.' . $phpEx); + + // merge_topic is the quickmod action, merge_topics is the mcp_forum action, and merge_select is the mcp_topic action + $merge_select = ($action == 'merge_select' || $action == 'merge_topic' || $action == 'merge_topics') ? true : false; + + $forum_id = $forum_info['forum_id']; + $start = $request->variable('start', 0); + $topic_id_list = $request->variable('topic_id_list', array(0)); + $post_id_list = $request->variable('post_id_list', array(0)); + $source_topic_ids = array($request->variable('t', 0)); + $to_topic_id = $request->variable('to_topic_id', 0); + + $url_extra = ''; + $url_extra .= ($forum_id) ? "&f=$forum_id" : ''; + $url_extra .= ($GLOBALS['topic_id']) ? '&t=' . $GLOBALS['topic_id'] : ''; + $url_extra .= ($GLOBALS['post_id']) ? '&p=' . $GLOBALS['post_id'] : ''; + $url_extra .= ($GLOBALS['user_id']) ? '&u=' . $GLOBALS['user_id'] : ''; + + $url = append_sid("{$phpbb_root_path}mcp.$phpEx?$url_extra"); + + // Resync Topics + switch ($action) + { + case 'resync': + $topic_ids = $request->variable('topic_id_list', array(0)); + mcp_resync_topics($topic_ids); + break; + + case 'merge_topics': + $source_topic_ids = $topic_id_list; + case 'merge_topic': + if ($to_topic_id) + { + merge_topics($forum_id, $source_topic_ids, $to_topic_id); + } + break; + } + + /** + * Get some data in order to execute other actions. + * + * @event core.mcp_forum_view_before + * @var string action The action + * @var array forum_info Array with forum infos + * @var int start Start value + * @var array topic_id_list Array of topics ids + * @var array post_id_list Array of posts ids + * @var array source_topic_ids Array of source topics ids + * @var int to_topic_id Array of destination topics ids + * @since 3.1.6-RC1 + */ + $vars = array( + 'action', + 'forum_info', + 'start', + 'topic_id_list', + 'post_id_list', + 'source_topic_ids', + 'to_topic_id', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_forum_view_before', compact($vars))); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + $selected_ids = ''; + if (count($post_id_list) && $action != 'merge_topics') + { + foreach ($post_id_list as $num => $post_id) + { + $selected_ids .= '&post_id_list[' . $num . ']=' . $post_id; + } + } + else if (count($topic_id_list) && $action == 'merge_topics') + { + foreach ($topic_id_list as $num => $topic_id) + { + $selected_ids .= '&topic_id_list[' . $num . ']=' . $topic_id; + } + } + + make_jumpbox($url . "&i=$id&action=$action&mode=$mode" . (($merge_select) ? $selected_ids : ''), $forum_id, false, 'm_', true); + + $topics_per_page = ($forum_info['forum_topics_per_page']) ? $forum_info['forum_topics_per_page'] : $config['topics_per_page']; + + $sort_days = $total = 0; + $sort_key = $sort_dir = ''; + $sort_by_sql = $sort_order_sql = array(); + phpbb_mcp_sorting('viewforum', $sort_days, $sort_key, $sort_dir, $sort_by_sql, $sort_order_sql, $total, $forum_id); + + $forum_topics = ($total == -1) ? $forum_info['forum_topics_approved'] : $total; + $limit_time_sql = ($sort_days) ? 'AND t.topic_last_post_time >= ' . (time() - ($sort_days * 86400)) : ''; + + $base_url = $url . "&i=$id&action=$action&mode=$mode&sd=$sort_dir&sk=$sort_key&st=$sort_days" . (($merge_select) ? $selected_ids : ''); + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $forum_topics, $topics_per_page, $start); + + $template->assign_vars(array( + 'ACTION' => $action, + 'FORUM_NAME' => $forum_info['forum_name'], + 'FORUM_DESCRIPTION' => generate_text_for_display($forum_info['forum_desc'], $forum_info['forum_desc_uid'], $forum_info['forum_desc_bitfield'], $forum_info['forum_desc_options']), + + 'REPORTED_IMG' => $user->img('icon_topic_reported', 'TOPIC_REPORTED'), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', 'TOPIC_UNAPPROVED'), + 'LAST_POST_IMG' => $user->img('icon_topic_latest', 'VIEW_LATEST_POST'), + 'NEWEST_POST_IMG' => $user->img('icon_topic_newest', 'VIEW_NEWEST_POST'), + + 'S_CAN_REPORT' => $auth->acl_get('m_report', $forum_id), + 'S_CAN_DELETE' => $auth->acl_get('m_delete', $forum_id), + 'S_CAN_RESTORE' => $auth->acl_get('m_approve', $forum_id), + 'S_CAN_MERGE' => $auth->acl_get('m_merge', $forum_id), + 'S_CAN_MOVE' => $auth->acl_get('m_move', $forum_id), + 'S_CAN_FORK' => $auth->acl_get('m_', $forum_id), + 'S_CAN_LOCK' => $auth->acl_get('m_lock', $forum_id), + 'S_CAN_SYNC' => $auth->acl_get('m_', $forum_id), + 'S_CAN_APPROVE' => $auth->acl_get('m_approve', $forum_id), + 'S_MERGE_SELECT' => ($merge_select) ? true : false, + 'S_CAN_MAKE_NORMAL' => $auth->acl_gets('f_sticky', 'f_announce', 'f_announce_global', $forum_id), + 'S_CAN_MAKE_STICKY' => $auth->acl_get('f_sticky', $forum_id), + 'S_CAN_MAKE_ANNOUNCE' => $auth->acl_get('f_announce', $forum_id), + 'S_CAN_MAKE_ANNOUNCE_GLOBAL' => $auth->acl_get('f_announce_global', $forum_id), + + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), + 'U_VIEW_FORUM_LOGS' => ($auth->acl_gets('a_', 'm_', $forum_id) && $module->loaded('logs')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=logs&mode=forum_logs&f=' . $forum_id) : '', + + 'S_MCP_ACTION' => $url . "&i=$id&forum_action=$action&mode=$mode&start=$start" . (($merge_select) ? $selected_ids : ''), + + 'TOTAL_TOPICS' => $user->lang('VIEW_FORUM_TOPICS', (int) $forum_topics), + )); + + // Grab icons + $icons = $cache->obtain_icons(); + + $topic_rows = array(); + + if ($config['load_db_lastread']) + { + $read_tracking_join = ' LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id'] . ')'; + $read_tracking_select = ', tt.mark_time'; + } + else + { + $read_tracking_join = $read_tracking_select = ''; + } + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + $sql = 'SELECT t.topic_id + FROM ' . TOPICS_TABLE . ' t + WHERE t.forum_id = ' . $forum_id . ' + AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.') . " + $limit_time_sql + ORDER BY t.topic_type DESC, $sort_order_sql"; + + /** + * Modify SQL query before MCP forum view topic list is queried + * + * @event core.mcp_view_forum_modify_sql + * @var string sql SQL query for forum view topic list + * @var int forum_id ID of the forum + * @var string limit_time_sql SQL query part for limit time + * @var string sort_order_sql SQL query part for sort order + * @var int topics_per_page Number of topics per page + * @var int start Start value + * @since 3.1.2-RC1 + */ + $vars = array('sql', 'forum_id', 'limit_time_sql', 'sort_order_sql', 'topics_per_page', 'start'); + extract($phpbb_dispatcher->trigger_event('core.mcp_view_forum_modify_sql', compact($vars))); + + $result = $db->sql_query_limit($sql, $topics_per_page, $start); + + $topic_list = $topic_tracking_info = array(); + + while ($row_ary = $db->sql_fetchrow($result)) + { + $topic_list[] = $row_ary['topic_id']; + } + $db->sql_freeresult($result); + + $sql = "SELECT t.*$read_tracking_select + FROM " . TOPICS_TABLE . " t $read_tracking_join + WHERE " . $db->sql_in_set('t.topic_id', $topic_list, false, true); + + $result = $db->sql_query($sql); + while ($row_ary = $db->sql_fetchrow($result)) + { + $topic_rows[$row_ary['topic_id']] = $row_ary; + } + $db->sql_freeresult($result); + + // If there is more than one page, but we have no topic list, then the start parameter is... erm... out of sync + if (!count($topic_list) && $forum_topics && $start > 0) + { + redirect($url . "&i=$id&action=$action&mode=$mode"); + } + + // Get topic tracking info + if (count($topic_list)) + { + if ($config['load_db_lastread']) + { + $topic_tracking_info = get_topic_tracking($forum_id, $topic_list, $topic_rows, array($forum_id => $forum_info['mark_time'])); + } + else + { + $topic_tracking_info = get_complete_topic_tracking($forum_id, $topic_list); + } + } + + foreach ($topic_list as $topic_id) + { + $row_ary = &$topic_rows[$topic_id]; + + $replies = $phpbb_content_visibility->get_count('topic_posts', $row_ary, $forum_id) - 1; + + if ($row_ary['topic_status'] == ITEM_MOVED) + { + $unread_topic = false; + } + else + { + $unread_topic = (isset($topic_tracking_info[$topic_id]) && $row_ary['topic_last_post_time'] > $topic_tracking_info[$topic_id]) ? true : false; + } + + // Get folder img, topic status/type related information + $folder_img = $folder_alt = $topic_type = ''; + topic_status($row_ary, $replies, $unread_topic, $folder_img, $folder_alt, $topic_type); + + $topic_title = censor_text($row_ary['topic_title']); + + $topic_unapproved = (($row_ary['topic_visibility'] == ITEM_UNAPPROVED || $row_ary['topic_visibility'] == ITEM_REAPPROVE) && $auth->acl_get('m_approve', $row_ary['forum_id'])) ? true : false; + $posts_unapproved = ($row_ary['topic_visibility'] == ITEM_APPROVED && $row_ary['topic_posts_unapproved'] && $auth->acl_get('m_approve', $row_ary['forum_id'])) ? true : false; + $topic_deleted = $row_ary['topic_visibility'] == ITEM_DELETED; + $u_mcp_queue = ($topic_unapproved || $posts_unapproved) ? $url . '&i=queue&mode=' . (($topic_unapproved) ? 'approve_details' : 'unapproved_posts') . '&t=' . $row_ary['topic_id'] : ''; + $u_mcp_queue = (!$u_mcp_queue && $topic_deleted) ? $url . '&i=queue&mode=deleted_topics&t=' . $topic_id : $u_mcp_queue; + + $topic_row = array( + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row_ary['forum_id']) && $row_ary['topic_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + 'TOPIC_IMG_STYLE' => $folder_img, + 'TOPIC_FOLDER_IMG' => $user->img($folder_img, $folder_alt), + 'TOPIC_ICON_IMG' => (!empty($icons[$row_ary['icon_id']])) ? $icons[$row_ary['icon_id']]['img'] : '', + 'TOPIC_ICON_IMG_WIDTH' => (!empty($icons[$row_ary['icon_id']])) ? $icons[$row_ary['icon_id']]['width'] : '', + 'TOPIC_ICON_IMG_HEIGHT' => (!empty($icons[$row_ary['icon_id']])) ? $icons[$row_ary['icon_id']]['height'] : '', + 'UNAPPROVED_IMG' => ($topic_unapproved || $posts_unapproved) ? $user->img('icon_topic_unapproved', ($topic_unapproved) ? 'TOPIC_UNAPPROVED' : 'POSTS_UNAPPROVED') : '', + 'DELETED_IMG' => ($topic_deleted) ? $user->img('icon_topic_deleted', 'TOPIC_DELETED') : '', + + 'TOPIC_AUTHOR' => get_username_string('username', $row_ary['topic_poster'], $row_ary['topic_first_poster_name'], $row_ary['topic_first_poster_colour']), + 'TOPIC_AUTHOR_COLOUR' => get_username_string('colour', $row_ary['topic_poster'], $row_ary['topic_first_poster_name'], $row_ary['topic_first_poster_colour']), + 'TOPIC_AUTHOR_FULL' => get_username_string('full', $row_ary['topic_poster'], $row_ary['topic_first_poster_name'], $row_ary['topic_first_poster_colour']), + 'U_TOPIC_AUTHOR' => get_username_string('profile', $row_ary['topic_poster'], $row_ary['topic_first_poster_name'], $row_ary['topic_first_poster_colour']), + + 'LAST_POST_AUTHOR' => get_username_string('username', $row_ary['topic_last_poster_id'], $row_ary['topic_last_poster_name'], $row_ary['topic_last_poster_colour']), + 'LAST_POST_AUTHOR_COLOUR' => get_username_string('colour', $row_ary['topic_last_poster_id'], $row_ary['topic_last_poster_name'], $row_ary['topic_last_poster_colour']), + 'LAST_POST_AUTHOR_FULL' => get_username_string('full', $row_ary['topic_last_poster_id'], $row_ary['topic_last_poster_name'], $row_ary['topic_last_poster_colour']), + 'U_LAST_POST_AUTHOR' => get_username_string('profile', $row_ary['topic_last_poster_id'], $row_ary['topic_last_poster_name'], $row_ary['topic_last_poster_colour']), + + 'TOPIC_TYPE' => $topic_type, + 'TOPIC_TITLE' => $topic_title, + 'REPLIES' => $phpbb_content_visibility->get_count('topic_posts', $row_ary, $row_ary['forum_id']) - 1, + 'LAST_POST_TIME' => $user->format_date($row_ary['topic_last_post_time']), + 'FIRST_POST_TIME' => $user->format_date($row_ary['topic_time']), + 'LAST_POST_SUBJECT' => $row_ary['topic_last_post_subject'], + 'LAST_VIEW_TIME' => $user->format_date($row_ary['topic_last_view_time']), + + 'S_TOPIC_REPORTED' => (!empty($row_ary['topic_reported']) && empty($row_ary['topic_moved_id']) && $auth->acl_get('m_report', $row_ary['forum_id'])) ? true : false, + 'S_TOPIC_UNAPPROVED' => $topic_unapproved, + 'S_POSTS_UNAPPROVED' => $posts_unapproved, + 'S_TOPIC_DELETED' => $topic_deleted, + 'S_UNREAD_TOPIC' => $unread_topic, + ); + + if ($row_ary['topic_status'] == ITEM_MOVED) + { + $topic_row = array_merge($topic_row, array( + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t={$row_ary['topic_moved_id']}"), + 'U_DELETE_TOPIC' => ($auth->acl_get('m_delete', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&f=$forum_id&topic_id_list[]={$row_ary['topic_id']}&mode=forum_view&action=delete_topic") : '', + 'S_MOVED_TOPIC' => true, + 'TOPIC_ID' => $row_ary['topic_moved_id'], + )); + } + else + { + if ($action == 'merge_topic' || $action == 'merge_topics') + { + $u_select_topic = $url . "&i=$id&mode=forum_view&action=$action&to_topic_id=" . $row_ary['topic_id'] . $selected_ids; + } + else + { + $u_select_topic = $url . "&i=$id&mode=topic_view&action=merge&to_topic_id=" . $row_ary['topic_id'] . $selected_ids; + } + $topic_row = array_merge($topic_row, array( + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&f=$forum_id&t={$row_ary['topic_id']}&mode=topic_view"), + + 'S_SELECT_TOPIC' => ($merge_select && !in_array($row_ary['topic_id'], $source_topic_ids)) ? true : false, + 'U_SELECT_TOPIC' => $u_select_topic, + 'U_MCP_QUEUE' => $u_mcp_queue, + 'U_MCP_REPORT' => ($auth->acl_get('m_report', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&mode=topic_view&t=' . $row_ary['topic_id'] . '&action=reports') : '', + 'TOPIC_ID' => $row_ary['topic_id'], + 'S_TOPIC_CHECKED' => ($topic_id_list && in_array($row_ary['topic_id'], $topic_id_list)) ? true : false, + )); + } + + $row = $row_ary; + /** + * Modify the topic data before it is assigned to the template in MCP + * + * @event core.mcp_view_forum_modify_topicrow + * @var array row Array with topic data + * @var array topic_row Template array with topic data + * @since 3.1.0-a1 + */ + $vars = array('row', 'topic_row'); + extract($phpbb_dispatcher->trigger_event('core.mcp_view_forum_modify_topicrow', compact($vars))); + $row_ary = $row; + unset($row); + + $template->assign_block_vars('topicrow', $topic_row); + } + unset($topic_rows); +} + +/** +* Resync topics +*/ +function mcp_resync_topics($topic_ids) +{ + global $db, $user, $phpbb_log, $request; + + if (!count($topic_ids)) + { + trigger_error('NO_TOPIC_SELECTED'); + } + + if (!phpbb_check_ids($topic_ids, TOPICS_TABLE, 'topic_id', array('m_'))) + { + return; + } + + // Sync everything and perform extra checks separately + sync('topic_reported', 'topic_id', $topic_ids, false, true); + sync('topic_attachment', 'topic_id', $topic_ids, false, true); + sync('topic', 'topic_id', $topic_ids, true, false); + + $sql = 'SELECT topic_id, forum_id, topic_title + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $result = $db->sql_query($sql); + + // Log this action + while ($row = $db->sql_fetchrow($result)) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_TOPIC_RESYNC', false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $row['topic_id'], + $row['topic_title'] + )); + } + $db->sql_freeresult($result); + + $msg = (count($topic_ids) == 1) ? $user->lang['TOPIC_RESYNC_SUCCESS'] : $user->lang['TOPICS_RESYNC_SUCCESS']; + + $redirect = $request->variable('redirect', $user->data['session_page']); + + meta_refresh(3, $redirect); + trigger_error($msg . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + + return; +} + +/** +* Merge selected topics into selected topic +*/ +function merge_topics($forum_id, $topic_ids, $to_topic_id) +{ + global $db, $template, $user, $phpEx, $phpbb_root_path, $phpbb_log, $request, $phpbb_dispatcher; + + if (!count($topic_ids)) + { + $template->assign_var('MESSAGE', $user->lang['NO_TOPIC_SELECTED']); + return; + } + if (!$to_topic_id) + { + $template->assign_var('MESSAGE', $user->lang['NO_FINAL_TOPIC_SELECTED']); + return; + } + + $sync_topics = array_merge($topic_ids, array($to_topic_id)); + + $all_topic_data = phpbb_get_topic_data($sync_topics, 'm_merge'); + + if (!count($all_topic_data) || empty($all_topic_data[$to_topic_id])) + { + $template->assign_var('MESSAGE', $user->lang['NO_FINAL_TOPIC_SELECTED']); + return; + } + + $sync_forums = array(); + $topic_views = 0; + foreach ($all_topic_data as $data) + { + $sync_forums[$data['forum_id']] = $data['forum_id']; + $topic_views = max($topic_views, $data['topic_views']); + } + + $to_topic_data = $all_topic_data[$to_topic_id]; + + $post_id_list = $request->variable('post_id_list', array(0)); + $start = $request->variable('start', 0); + + if (!count($post_id_list) && count($topic_ids)) + { + $sql = 'SELECT post_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $result = $db->sql_query($sql); + + $post_id_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + $post_id_list[] = $row['post_id']; + } + $db->sql_freeresult($result); + } + + if (!count($post_id_list)) + { + $template->assign_var('MESSAGE', $user->lang['NO_POST_SELECTED']); + return; + } + + if (!phpbb_check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_merge'))) + { + return; + } + + $redirect = $request->variable('redirect', "{$phpbb_root_path}mcp.$phpEx?f=$forum_id&i=main&mode=forum_view"); + + $s_hidden_fields = build_hidden_fields(array( + 'i' => 'main', + 'f' => $forum_id, + 'post_id_list' => $post_id_list, + 'to_topic_id' => $to_topic_id, + 'mode' => 'forum_view', + 'action' => 'merge_topics', + 'start' => $start, + 'redirect' => $redirect, + 'topic_id_list' => $topic_ids) + ); + $return_link = ''; + + if (confirm_box(true)) + { + $to_forum_id = $to_topic_data['forum_id']; + + move_posts($post_id_list, $to_topic_id, false); + + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_MERGE', false, array( + 'forum_id' => $to_forum_id, + 'topic_id' => $to_topic_id, + $to_topic_data['topic_title'] + )); + + // Update topic views count + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_views = ' . $topic_views . ' + WHERE topic_id = ' . $to_topic_id; + $db->sql_query($sql); + + // Message and return links + $success_msg = 'POSTS_MERGED_SUCCESS'; + + if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status')) + { + include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx); + } + + // Update the topic watch table. + phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', $topic_ids, $to_topic_id); + + // Update the bookmarks table. + phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', $topic_ids, $to_topic_id); + + // Re-sync the topics and forums because the auto-sync was deactivated in the call of move_posts() + sync('topic_reported', 'topic_id', $sync_topics); + sync('topic_attachment', 'topic_id', $sync_topics); + sync('topic', 'topic_id', $sync_topics, true); + sync('forum', 'forum_id', $sync_forums, true, true); + + // Link to the new topic + $return_link .= (($return_link) ? '

' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '', ''); + $redirect = $request->variable('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); + $redirect = reapply_sid($redirect); + + /** + * Perform additional actions after merging topics. + * + * @event core.mcp_forum_merge_topics_after + * @var array all_topic_data The data from all topics involved in the merge + * @var int to_topic_id The ID of the topic into which the rest are merged + * @since 3.1.11-RC1 + */ + $vars = array( + 'all_topic_data', + 'to_topic_id', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_forum_merge_topics_after', compact($vars))); + + meta_refresh(3, $redirect); + trigger_error($user->lang[$success_msg] . '

' . $return_link); + } + else + { + confirm_box(false, 'MERGE_TOPICS', $s_hidden_fields); + } +} diff --git a/includes/mcp/mcp_front.php b/includes/mcp/mcp_front.php new file mode 100644 index 0000000..918a987 --- /dev/null +++ b/includes/mcp/mcp_front.php @@ -0,0 +1,394 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* MCP Front Panel +*/ +function mcp_front_view($id, $mode, $action) +{ + global $phpEx, $phpbb_root_path; + global $template, $db, $user, $auth, $module; + global $phpbb_dispatcher, $request; + + // Latest 5 unapproved + if ($module->loaded('queue')) + { + $forum_list = array_values(array_intersect(get_forum_list('f_read'), get_forum_list('m_approve'))); + $post_list = array(); + $forum_names = array(); + + $forum_id = $request->variable('f', 0); + + $template->assign_var('S_SHOW_UNAPPROVED', (!empty($forum_list)) ? true : false); + + if (!empty($forum_list)) + { + $sql_ary = array( + 'SELECT' => 'COUNT(post_id) AS total', + 'FROM' => array( + POSTS_TABLE => 'p', + ), + 'WHERE' => $db->sql_in_set('p.forum_id', $forum_list) . ' + AND ' . $db->sql_in_set('p.post_visibility', array(ITEM_UNAPPROVED, ITEM_REAPPROVE)) + ); + + /** + * Allow altering the query to get the number of unapproved posts + * + * @event core.mcp_front_queue_unapproved_total_before + * @var array sql_ary Query array to get the total number of unapproved posts + * @var array forum_list List of forums to look for unapproved posts + * @since 3.1.5-RC1 + */ + $vars = array('sql_ary', 'forum_list'); + extract($phpbb_dispatcher->trigger_event('core.mcp_front_queue_unapproved_total_before', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + $total = (int) $db->sql_fetchfield('total'); + $db->sql_freeresult($result); + + if ($total) + { + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_list); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_names[$row['forum_id']] = $row['forum_name']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT post_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_list) . ' + AND ' . $db->sql_in_set('post_visibility', array(ITEM_UNAPPROVED, ITEM_REAPPROVE)) . ' + ORDER BY post_time DESC, post_id DESC'; + $result = $db->sql_query_limit($sql, 5); + + while ($row = $db->sql_fetchrow($result)) + { + $post_list[] = $row['post_id']; + } + $db->sql_freeresult($result); + + if (empty($post_list)) + { + $total = 0; + } + } + + /** + * Alter list of posts and total as required + * + * @event core.mcp_front_view_queue_postid_list_after + * @var int total Number of unapproved posts + * @var array post_list List of unapproved posts + * @var array forum_list List of forums that contain the posts + * @var array forum_names Associative array with forum_id as key and it's corresponding forum_name as value + * @since 3.1.0-RC3 + */ + $vars = array('total', 'post_list', 'forum_list', 'forum_names'); + extract($phpbb_dispatcher->trigger_event('core.mcp_front_view_queue_postid_list_after', compact($vars))); + + if ($total) + { + $sql = 'SELECT p.post_id, p.post_subject, p.post_time, p.post_attachment, p.poster_id, p.post_username, u.username, u.username_clean, u.user_colour, t.topic_id, t.topic_title, t.topic_first_post_id, p.forum_id + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_list) . ' + AND t.topic_id = p.topic_id + AND p.poster_id = u.user_id + ORDER BY p.post_time DESC, p.post_id DESC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('unapproved', array( + 'U_POST_DETAILS' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&f=' . $row['forum_id'] . '&p=' . $row['post_id']), + 'U_MCP_FORUM' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&mode=forum_view&f=' . $row['forum_id']), + 'U_MCP_TOPIC' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&mode=topic_view&f=' . $row['forum_id'] . '&t=' . $row['topic_id']), + 'U_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']), + 'U_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . '&t=' . $row['topic_id']), + + 'AUTHOR_FULL' => get_username_string('full', $row['poster_id'], $row['username'], $row['user_colour']), + 'AUTHOR' => get_username_string('username', $row['poster_id'], $row['username'], $row['user_colour']), + 'AUTHOR_COLOUR' => get_username_string('colour', $row['poster_id'], $row['username'], $row['user_colour']), + 'U_AUTHOR' => get_username_string('profile', $row['poster_id'], $row['username'], $row['user_colour']), + + 'FORUM_NAME' => $forum_names[$row['forum_id']], + 'POST_ID' => $row['post_id'], + 'TOPIC_TITLE' => $row['topic_title'], + 'SUBJECT' => ($row['post_subject']) ? $row['post_subject'] : $user->lang['NO_SUBJECT'], + 'POST_TIME' => $user->format_date($row['post_time']), + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['post_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + )); + } + $db->sql_freeresult($result); + } + + $s_hidden_fields = build_hidden_fields(array( + 'redirect' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main' . (($forum_id) ? '&f=' . $forum_id : '')) + )); + + $template->assign_vars(array( + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + 'S_MCP_QUEUE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue"), + 'L_UNAPPROVED_TOTAL' => $user->lang('UNAPPROVED_POSTS_TOTAL', (int) $total), + 'S_HAS_UNAPPROVED_POSTS'=> ($total != 0), + )); + } + } + + // Latest 5 reported + if ($module->loaded('reports')) + { + $forum_list = array_values(array_intersect(get_forum_list('f_read'), get_forum_list('m_report'))); + + $template->assign_var('S_SHOW_REPORTS', (!empty($forum_list)) ? true : false); + + if (!empty($forum_list)) + { + $sql = 'SELECT COUNT(r.report_id) AS total + FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p + WHERE r.post_id = p.post_id + AND r.pm_id = 0 + AND r.report_closed = 0 + AND ' . $db->sql_in_set('p.forum_id', $forum_list); + + /** + * Alter sql query to count the number of reported posts + * + * @event core.mcp_front_reports_count_query_before + * @var string sql The query string used to get the number of reports that exist + * @var array forum_list List of forums that contain the posts + * @since 3.1.5-RC1 + */ + $vars = array('sql', 'forum_list'); + extract($phpbb_dispatcher->trigger_event('core.mcp_front_reports_count_query_before', compact($vars))); + + $result = $db->sql_query($sql); + $total = (int) $db->sql_fetchfield('total'); + $db->sql_freeresult($result); + + if ($total) + { + $sql_ary = array( + 'SELECT' => 'r.report_time, p.post_id, p.post_subject, p.post_time, p.post_attachment, u.username, u.username_clean, u.user_colour, u.user_id, u2.username as author_name, u2.username_clean as author_name_clean, u2.user_colour as author_colour, u2.user_id as author_id, t.topic_id, t.topic_title, f.forum_id, f.forum_name', + + 'FROM' => array( + REPORTS_TABLE => 'r', + REPORTS_REASONS_TABLE => 'rr', + TOPICS_TABLE => 't', + USERS_TABLE => array('u', 'u2'), + POSTS_TABLE => 'p', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'f.forum_id = p.forum_id', + ), + ), + + 'WHERE' => 'r.post_id = p.post_id + AND r.pm_id = 0 + AND r.report_closed = 0 + AND r.reason_id = rr.reason_id + AND p.topic_id = t.topic_id + AND r.user_id = u.user_id + AND p.poster_id = u2.user_id + AND ' . $db->sql_in_set('p.forum_id', $forum_list), + + 'ORDER_BY' => 'p.post_time DESC, p.post_id DESC', + ); + + /** + * Alter sql query to get latest reported posts + * + * @event core.mcp_front_reports_listing_query_before + * @var array sql_ary Associative array with the query to be executed + * @var array forum_list List of forums that contain the posts + * @since 3.1.0-RC3 + */ + $vars = array('sql_ary', 'forum_list'); + extract($phpbb_dispatcher->trigger_event('core.mcp_front_reports_listing_query_before', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query_limit($sql, 5); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('report', array( + 'U_POST_DETAILS' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'f=' . $row['forum_id'] . '&p=' . $row['post_id'] . "&i=reports&mode=report_details"), + 'U_MCP_FORUM' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'f=' . $row['forum_id'] . "&i=$id&mode=forum_view"), + 'U_MCP_TOPIC' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'f=' . $row['forum_id'] . '&t=' . $row['topic_id'] . "&i=$id&mode=topic_view"), + 'U_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']), + 'U_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . '&t=' . $row['topic_id']), + + 'REPORTER_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'REPORTER' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), + 'REPORTER_COLOUR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), + 'U_REPORTER' => get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']), + + 'AUTHOR_FULL' => get_username_string('full', $row['author_id'], $row['author_name'], $row['author_colour']), + 'AUTHOR' => get_username_string('username', $row['author_id'], $row['author_name'], $row['author_colour']), + 'AUTHOR_COLOUR' => get_username_string('colour', $row['author_id'], $row['author_name'], $row['author_colour']), + 'U_AUTHOR' => get_username_string('profile', $row['author_id'], $row['author_name'], $row['author_colour']), + + 'FORUM_NAME' => $row['forum_name'], + 'TOPIC_TITLE' => $row['topic_title'], + 'SUBJECT' => ($row['post_subject']) ? $row['post_subject'] : $user->lang['NO_SUBJECT'], + 'REPORT_TIME' => $user->format_date($row['report_time']), + 'POST_TIME' => $user->format_date($row['post_time']), + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['post_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + )); + } + $db->sql_freeresult($result); + } + + $template->assign_vars(array( + 'L_REPORTS_TOTAL' => $user->lang('REPORTS_TOTAL', (int) $total), + 'S_HAS_REPORTS' => ($total != 0), + )); + } + } + + // Latest 5 reported PMs + if ($module->loaded('pm_reports') && $auth->acl_get('m_pm_report')) + { + $template->assign_var('S_SHOW_PM_REPORTS', true); + $user->add_lang(array('ucp')); + + $sql = 'SELECT COUNT(r.report_id) AS total + FROM ' . REPORTS_TABLE . ' r, ' . PRIVMSGS_TABLE . ' p + WHERE r.post_id = 0 + AND r.pm_id = p.msg_id + AND r.report_closed = 0'; + $result = $db->sql_query($sql); + $total = (int) $db->sql_fetchfield('total'); + $db->sql_freeresult($result); + + if ($total) + { + if (!function_exists('get_recipient_strings')) + { + include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); + } + + $sql_ary = array( + 'SELECT' => 'r.report_id, r.report_time, p.msg_id, p.message_subject, p.message_time, p.to_address, p.bcc_address, p.message_attachment, u.username, u.username_clean, u.user_colour, u.user_id, u2.username as author_name, u2.username_clean as author_name_clean, u2.user_colour as author_colour, u2.user_id as author_id', + + 'FROM' => array( + REPORTS_TABLE => 'r', + REPORTS_REASONS_TABLE => 'rr', + USERS_TABLE => array('u', 'u2'), + PRIVMSGS_TABLE => 'p', + ), + + 'WHERE' => 'r.pm_id = p.msg_id + AND r.post_id = 0 + AND r.report_closed = 0 + AND r.reason_id = rr.reason_id + AND r.user_id = u.user_id + AND p.author_id = u2.user_id', + + 'ORDER_BY' => 'p.message_time DESC', + ); + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query_limit($sql, 5); + + $pm_by_id = $pm_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + $pm_by_id[(int) $row['msg_id']] = $row; + $pm_list[] = (int) $row['msg_id']; + } + $db->sql_freeresult($result); + + $address_list = get_recipient_strings($pm_by_id); + + foreach ($pm_list as $message_id) + { + $row = $pm_by_id[$message_id]; + + $template->assign_block_vars('pm_report', array( + 'U_PM_DETAILS' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'r=' . $row['report_id'] . "&i=pm_reports&mode=pm_report_details"), + + 'REPORTER_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'REPORTER' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), + 'REPORTER_COLOUR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), + 'U_REPORTER' => get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']), + + 'PM_AUTHOR_FULL' => get_username_string('full', $row['author_id'], $row['author_name'], $row['author_colour']), + 'PM_AUTHOR' => get_username_string('username', $row['author_id'], $row['author_name'], $row['author_colour']), + 'PM_AUTHOR_COLOUR' => get_username_string('colour', $row['author_id'], $row['author_name'], $row['author_colour']), + 'U_PM_AUTHOR' => get_username_string('profile', $row['author_id'], $row['author_name'], $row['author_colour']), + + 'PM_SUBJECT' => $row['message_subject'], + 'REPORT_TIME' => $user->format_date($row['report_time']), + 'PM_TIME' => $user->format_date($row['message_time']), + 'RECIPIENTS' => implode(', ', $address_list[$row['msg_id']]), + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $row['message_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + )); + } + } + + $template->assign_vars(array( + 'L_PM_REPORTS_TOTAL' => $user->lang('PM_REPORTS_TOTAL', (int) $total), + 'S_HAS_PM_REPORTS' => ($total != 0), + )); + } + + // Latest 5 logs + if ($module->loaded('logs')) + { + $forum_list = array_values(array_intersect(get_forum_list('f_read'), get_forum_list('m_'))); + + if (!empty($forum_list)) + { + $log_count = false; + $log = array(); + view_log('mod', $log, $log_count, 5, 0, $forum_list); + + foreach ($log as $row) + { + $template->assign_block_vars('log', array( + 'USERNAME' => $row['username_full'], + 'IP' => $row['ip'], + 'TIME' => $user->format_date($row['time']), + 'ACTION' => $row['action'], + 'U_VIEW_TOPIC' => (!empty($row['viewtopic'])) ? $row['viewtopic'] : '', + 'U_VIEWLOGS' => (!empty($row['viewlogs'])) ? $row['viewlogs'] : '') + ); + } + } + + $template->assign_vars(array( + 'S_SHOW_LOGS' => (!empty($forum_list)) ? true : false, + 'S_HAS_LOGS' => (!empty($log)) ? true : false) + ); + } + + $template->assign_var('S_MCP_ACTION', append_sid("{$phpbb_root_path}mcp.$phpEx")); + make_jumpbox(append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&mode=forum_view'), 0, false, 'm_', true); +} diff --git a/includes/mcp/mcp_logs.php b/includes/mcp/mcp_logs.php new file mode 100644 index 0000000..79f9d35 --- /dev/null +++ b/includes/mcp/mcp_logs.php @@ -0,0 +1,230 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* mcp_logs +* Handling warning the users +*/ +class mcp_logs +{ + var $u_action; + var $p_master; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $auth, $db, $user, $template, $request; + global $config, $phpbb_container, $phpbb_log; + + $user->add_lang('acp/common'); + + $action = $request->variable('action', array('' => '')); + + if (is_array($action)) + { + list($action, ) = each($action); + } + else + { + $action = $request->variable('action', ''); + } + + // Set up general vars + $start = $request->variable('start', 0); + $deletemark = ($action == 'del_marked') ? true : false; + $deleteall = ($action == 'del_all') ? true : false; + $marked = $request->variable('mark', array(0)); + + // Sort keys + $sort_days = $request->variable('st', 0); + $sort_key = $request->variable('sk', 't'); + $sort_dir = $request->variable('sd', 'd'); + + $this->tpl_name = 'mcp_logs'; + $this->page_title = 'MCP_LOGS'; + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + $forum_list = array_values(array_intersect(get_forum_list('f_read'), get_forum_list('m_'))); + $forum_list[] = 0; + + $forum_id = $topic_id = 0; + + switch ($mode) + { + case 'front': + break; + + case 'forum_logs': + $forum_id = $request->variable('f', 0); + + if (!in_array($forum_id, $forum_list)) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + + $forum_list = array($forum_id); + break; + + case 'topic_logs': + $topic_id = $request->variable('t', 0); + + $sql = 'SELECT forum_id + FROM ' . TOPICS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + $forum_id = (int) $db->sql_fetchfield('forum_id'); + $db->sql_freeresult($result); + + if (!in_array($forum_id, $forum_list)) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + + $forum_list = array($forum_id); + break; + } + + // Delete entries if requested and able + if (($deletemark || $deleteall) && $auth->acl_get('a_clearlogs')) + { + if (confirm_box(true)) + { + if ($deletemark && count($marked)) + { + $conditions = array( + 'forum_id' => array('IN' => $forum_list), + 'log_id' => array('IN' => $marked), + ); + + $phpbb_log->delete('mod', $conditions); + } + else if ($deleteall) + { + $keywords = $request->variable('keywords', '', true); + + $conditions = array( + 'forum_id' => array('IN' => $forum_list), + 'keywords' => $keywords, + ); + + if ($sort_days) + { + $conditions['log_time'] = array('>=', time() - ($sort_days * 86400)); + } + + if ($mode == 'topic_logs') + { + $conditions['topic_id'] = $topic_id; + } + + $phpbb_log->delete('mod', $conditions); + } + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'f' => $forum_id, + 't' => $topic_id, + 'start' => $start, + 'delmarked' => $deletemark, + 'delall' => $deleteall, + 'mark' => $marked, + 'st' => $sort_days, + 'sk' => $sort_key, + 'sd' => $sort_dir, + 'i' => $id, + 'mode' => $mode, + 'action' => $request->variable('action', array('' => '')))) + ); + } + } + + // Sorting + $limit_days = array(0 => $user->lang['ALL_ENTRIES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('u' => $user->lang['SORT_USERNAME'], 't' => $user->lang['SORT_DATE'], 'i' => $user->lang['SORT_IP'], 'o' => $user->lang['SORT_ACTION']); + $sort_by_sql = array('u' => 'u.username_clean', 't' => 'l.log_time', 'i' => 'l.log_ip', 'o' => 'l.log_operation'); + + $s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param); + + // Define where and sort sql for use in displaying logs + $sql_where = ($sort_days) ? (time() - ($sort_days * 86400)) : 0; + $sql_sort = $sort_by_sql[$sort_key] . ' ' . (($sort_dir == 'd') ? 'DESC' : 'ASC'); + + $keywords = $request->variable('keywords', '', true); + $keywords_param = !empty($keywords) ? '&keywords=' . urlencode(htmlspecialchars_decode($keywords)) : ''; + + // Grab log data + $log_data = array(); + $log_count = 0; + $start = view_log('mod', $log_data, $log_count, $config['topics_per_page'], $start, $forum_list, $topic_id, 0, $sql_where, $sql_sort, $keywords); + + $base_url = $this->u_action . "&$u_sort_param$keywords_param"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'TOTAL' => $user->lang('TOTAL_LOGS', (int) $log_count), + + 'L_TITLE' => $user->lang['MCP_LOGS'], + + 'U_POST_ACTION' => $this->u_action . "&$u_sort_param$keywords_param&start=$start", + 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DAYS' => $s_limit_days, + 'S_LOGS' => ($log_count > 0), + 'S_KEYWORDS' => $keywords, + ) + ); + + foreach ($log_data as $row) + { + $data = array(); + + $checks = array('viewpost', 'viewtopic', 'viewforum'); + foreach ($checks as $check) + { + if (isset($row[$check]) && $row[$check]) + { + $data[] = '' . $user->lang['LOGVIEW_' . strtoupper($check)] . ''; + } + } + + $template->assign_block_vars('log', array( + 'USERNAME' => $row['username_full'], + 'IP' => $row['ip'], + 'DATE' => $user->format_date($row['time']), + 'ACTION' => $row['action'], + 'DATA' => (count($data)) ? implode(' | ', $data) : '', + 'ID' => $row['id'], + ) + ); + } + } +} diff --git a/includes/mcp/mcp_main.php b/includes/mcp/mcp_main.php new file mode 100644 index 0000000..4bd783b --- /dev/null +++ b/includes/mcp/mcp_main.php @@ -0,0 +1,1701 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* mcp_main +* Handling mcp actions +*/ +class mcp_main +{ + var $p_master; + var $u_action; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $auth, $user, $action; + global $phpbb_root_path, $phpEx, $request; + global $phpbb_dispatcher; + + $quickmod = ($mode == 'quickmod') ? true : false; + + switch ($action) + { + case 'lock': + case 'unlock': + $topic_ids = (!$quickmod) ? $request->variable('topic_id_list', array(0)) : array($request->variable('t', 0)); + + if (!count($topic_ids)) + { + trigger_error('NO_TOPIC_SELECTED'); + } + + lock_unlock($action, $topic_ids); + break; + + case 'lock_post': + case 'unlock_post': + + $post_ids = (!$quickmod) ? $request->variable('post_id_list', array(0)) : array($request->variable('p', 0)); + + if (!count($post_ids)) + { + trigger_error('NO_POST_SELECTED'); + } + + lock_unlock($action, $post_ids); + break; + + case 'make_announce': + case 'make_sticky': + case 'make_global': + case 'make_normal': + + $topic_ids = (!$quickmod) ? $request->variable('topic_id_list', array(0)) : array($request->variable('t', 0)); + + if (!count($topic_ids)) + { + trigger_error('NO_TOPIC_SELECTED'); + } + + change_topic_type($action, $topic_ids); + break; + + case 'move': + $user->add_lang('viewtopic'); + + $topic_ids = (!$quickmod) ? $request->variable('topic_id_list', array(0)) : array($request->variable('t', 0)); + + if (!count($topic_ids)) + { + trigger_error('NO_TOPIC_SELECTED'); + } + + mcp_move_topic($topic_ids); + break; + + case 'fork': + $user->add_lang('viewtopic'); + + $topic_ids = (!$quickmod) ? $request->variable('topic_id_list', array(0)) : array($request->variable('t', 0)); + + if (!count($topic_ids)) + { + trigger_error('NO_TOPIC_SELECTED'); + } + + mcp_fork_topic($topic_ids); + break; + + case 'delete_topic': + $user->add_lang('viewtopic'); + + // f parameter is not reliable for permission usage, however we just use it to decide + // which permission we will check later on. So if it is manipulated, we will still catch it later on. + $forum_id = $request->variable('f', 0); + $topic_ids = (!$quickmod) ? $request->variable('topic_id_list', array(0)) : array($request->variable('t', 0)); + $soft_delete = (($request->is_set_post('confirm') && !$request->is_set_post('delete_permanent')) || !$auth->acl_get('m_delete', $forum_id)) ? true : false; + + if (!count($topic_ids)) + { + trigger_error('NO_TOPIC_SELECTED'); + } + + mcp_delete_topic($topic_ids, $soft_delete, $request->variable('delete_reason', '', true)); + break; + + case 'delete_post': + $user->add_lang('posting'); + + // f parameter is not reliable for permission usage, however we just use it to decide + // which permission we will check later on. So if it is manipulated, we will still catch it later on. + $forum_id = $request->variable('f', 0); + $post_ids = (!$quickmod) ? $request->variable('post_id_list', array(0)) : array($request->variable('p', 0)); + $soft_delete = (($request->is_set_post('confirm') && !$request->is_set_post('delete_permanent')) || !$auth->acl_get('m_delete', $forum_id)) ? true : false; + + if (!count($post_ids)) + { + trigger_error('NO_POST_SELECTED'); + } + + mcp_delete_post($post_ids, $soft_delete, $request->variable('delete_reason', '', true)); + break; + + case 'restore_topic': + $user->add_lang('posting'); + + $topic_ids = (!$quickmod) ? $request->variable('topic_id_list', array(0)) : array($request->variable('t', 0)); + + if (!count($topic_ids)) + { + trigger_error('NO_TOPIC_SELECTED'); + } + + mcp_restore_topic($topic_ids); + break; + + default: + /** + * This event allows you to handle custom quickmod options + * + * @event core.modify_quickmod_actions + * @var string action Topic quick moderation action name + * @var bool quickmod Flag indicating whether MCP is in quick moderation mode + * @since 3.1.0-a4 + * @changed 3.1.0-RC4 Added variables: action, quickmod + */ + $vars = array('action', 'quickmod'); + extract($phpbb_dispatcher->trigger_event('core.modify_quickmod_actions', compact($vars))); + break; + } + + switch ($mode) + { + case 'front': + if (!function_exists('mcp_front_view')) + { + include($phpbb_root_path . 'includes/mcp/mcp_front.' . $phpEx); + } + + $user->add_lang('acp/common'); + + mcp_front_view($id, $mode, $action); + + $this->tpl_name = 'mcp_front'; + $this->page_title = 'MCP_MAIN'; + break; + + case 'forum_view': + if (!function_exists('mcp_forum_view')) + { + include($phpbb_root_path . 'includes/mcp/mcp_forum.' . $phpEx); + } + + $user->add_lang('viewforum'); + + $forum_id = $request->variable('f', 0); + + $forum_info = phpbb_get_forum_data($forum_id, 'm_', true); + + if (!count($forum_info)) + { + $this->main('main', 'front'); + return; + } + + $forum_info = $forum_info[$forum_id]; + + mcp_forum_view($id, $mode, $action, $forum_info); + + $this->tpl_name = 'mcp_forum'; + $this->page_title = 'MCP_MAIN_FORUM_VIEW'; + break; + + case 'topic_view': + if (!function_exists('mcp_topic_view')) + { + include($phpbb_root_path . 'includes/mcp/mcp_topic.' . $phpEx); + } + + mcp_topic_view($id, $mode, $action); + + $this->tpl_name = 'mcp_topic'; + $this->page_title = 'MCP_MAIN_TOPIC_VIEW'; + break; + + case 'post_details': + if (!function_exists('mcp_post_details')) + { + include($phpbb_root_path . 'includes/mcp/mcp_post.' . $phpEx); + } + + mcp_post_details($id, $mode, $action); + + $this->tpl_name = ($action == 'whois') ? 'mcp_whois' : 'mcp_post'; + $this->page_title = 'MCP_MAIN_POST_DETAILS'; + break; + + default: + if ($quickmod) + { + switch ($action) + { + case 'lock': + case 'unlock': + case 'make_announce': + case 'make_sticky': + case 'make_global': + case 'make_normal': + case 'make_onindex': + case 'move': + case 'fork': + case 'delete_topic': + trigger_error('TOPIC_NOT_EXIST'); + break; + + case 'lock_post': + case 'unlock_post': + case 'delete_post': + trigger_error('POST_NOT_EXIST'); + break; + } + } + + trigger_error('NO_MODE', E_USER_ERROR); + break; + } + } +} + +/** +* Lock/Unlock Topic/Post +*/ +function lock_unlock($action, $ids) +{ + global $user, $db, $request, $phpbb_log, $phpbb_dispatcher; + + if ($action == 'lock' || $action == 'unlock') + { + $table = TOPICS_TABLE; + $sql_id = 'topic_id'; + $set_id = 'topic_status'; + $l_prefix = 'TOPIC'; + } + else + { + $table = POSTS_TABLE; + $sql_id = 'post_id'; + $set_id = 'post_edit_locked'; + $l_prefix = 'POST'; + } + + $orig_ids = $ids; + + if (!phpbb_check_ids($ids, $table, $sql_id, array('m_lock'))) + { + // Make sure that for f_user_lock only the lock action is triggered. + if ($action != 'lock') + { + return; + } + + $ids = $orig_ids; + + if (!phpbb_check_ids($ids, $table, $sql_id, array('f_user_lock'))) + { + return; + } + } + unset($orig_ids); + + $redirect = $request->variable('redirect', build_url(array('action', 'quickmod'))); + $redirect = reapply_sid($redirect); + + $s_hidden_fields = build_hidden_fields(array( + $sql_id . '_list' => $ids, + 'action' => $action, + 'redirect' => $redirect) + ); + + if (confirm_box(true)) + { + $sql = "UPDATE $table + SET $set_id = " . (($action == 'lock' || $action == 'lock_post') ? ITEM_LOCKED : ITEM_UNLOCKED) . ' + WHERE ' . $db->sql_in_set($sql_id, $ids); + $db->sql_query($sql); + + $data = ($action == 'lock' || $action == 'unlock') ? phpbb_get_topic_data($ids) : phpbb_get_post_data($ids); + + foreach ($data as $id => $row) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_' . strtoupper($action), false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $row['topic_id'], + 'post_id' => isset($row['post_id']) ? $row['post_id'] : 0, + $row['topic_title'] + )); + } + + /** + * Perform additional actions after locking/unlocking posts/topics + * + * @event core.mcp_lock_unlock_after + * @var string action Variable containing the action we perform on the posts/topics ('lock', 'unlock', 'lock_post' or 'unlock_post') + * @var array ids Array containing the post/topic IDs that have been locked/unlocked + * @var array data Array containing posts/topics data + * @since 3.1.7-RC1 + */ + $vars = array( + 'action', + 'ids', + 'data', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_lock_unlock_after', compact($vars))); + + $success_msg = $l_prefix . ((count($ids) == 1) ? '' : 'S') . '_' . (($action == 'lock' || $action == 'lock_post') ? 'LOCKED' : 'UNLOCKED') . '_SUCCESS'; + + meta_refresh(2, $redirect); + $message = $user->lang[$success_msg]; + + if (!$request->is_ajax()) + { + $message .= '

' . $user->lang('RETURN_PAGE', '', ''); + } + trigger_error($message); + } + else + { + confirm_box(false, strtoupper($action) . '_' . $l_prefix . ((count($ids) == 1) ? '' : 'S'), $s_hidden_fields); + } + + redirect($redirect); +} + +/** +* Change Topic Type +*/ +function change_topic_type($action, $topic_ids) +{ + global $user, $db, $request, $phpbb_log, $phpbb_dispatcher; + + switch ($action) + { + case 'make_announce': + $new_topic_type = POST_ANNOUNCE; + $check_acl = 'f_announce'; + $l_new_type = (count($topic_ids) == 1) ? 'MCP_MAKE_ANNOUNCEMENT' : 'MCP_MAKE_ANNOUNCEMENTS'; + break; + + case 'make_global': + $new_topic_type = POST_GLOBAL; + $check_acl = 'f_announce_global'; + $l_new_type = (count($topic_ids) == 1) ? 'MCP_MAKE_GLOBAL' : 'MCP_MAKE_GLOBALS'; + break; + + case 'make_sticky': + $new_topic_type = POST_STICKY; + $check_acl = 'f_sticky'; + $l_new_type = (count($topic_ids) == 1) ? 'MCP_MAKE_STICKY' : 'MCP_MAKE_STICKIES'; + break; + + default: + $new_topic_type = POST_NORMAL; + $check_acl = false; + $l_new_type = (count($topic_ids) == 1) ? 'MCP_MAKE_NORMAL' : 'MCP_MAKE_NORMALS'; + break; + } + + $forum_id = phpbb_check_ids($topic_ids, TOPICS_TABLE, 'topic_id', $check_acl, true); + + if ($forum_id === false) + { + return; + } + + $redirect = $request->variable('redirect', build_url(array('action', 'quickmod'))); + $redirect = reapply_sid($redirect); + + $s_hidden_fields = array( + 'topic_id_list' => $topic_ids, + 'f' => $forum_id, + 'action' => $action, + 'redirect' => $redirect, + ); + + if (confirm_box(true)) + { + + /** + * Perform additional actions before changing topic(s) type + * + * @event core.mcp_change_topic_type_before + * @var int new_topic_type The candidated topic type. + * @var int forum_id The forum ID for the topic ID(s). + * @var array topic_ids Array containing the topic ID(s) that will be changed + * @since 3.2.6-RC1 + */ + $vars = array( + 'new_topic_type', + 'forum_id', + 'topic_ids', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_change_topic_type_before', compact($vars))); + + $db->sql_transaction('begin'); + + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_type = $new_topic_type + WHERE " . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); + + if (($new_topic_type == POST_GLOBAL) && count($topic_ids)) + { + // Delete topic shadows for global announcements + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_moved_id', $topic_ids); + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + + $success_msg = (count($topic_ids) == 1) ? 'TOPIC_TYPE_CHANGED' : 'TOPICS_TYPE_CHANGED'; + + if (count($topic_ids)) + { + $data = phpbb_get_topic_data($topic_ids); + + foreach ($data as $topic_id => $row) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_TOPIC_TYPE_CHANGED', false, array( + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + $row['topic_title'] + )); + } + } + + /** + * Perform additional actions after changing topic types + * + * @event core.mcp_change_topic_type_after + * @var int new_topic_type The newly changed topic type. + * @var int forum_id The forum ID where the newly changed topic type belongs to. + * @var array topic_ids Array containing the topic IDs that have been changed + * @since 3.2.6-RC1 + */ + $vars = array( + 'new_topic_type', + 'forum_id', + 'topic_ids', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_change_topic_type_after', compact($vars))); + + meta_refresh(2, $redirect); + $message = $user->lang[$success_msg]; + + if (!$request->is_ajax()) + { + $message .= '

' . $user->lang('RETURN_PAGE', '', ''); + } + trigger_error($message); + } + else + { + confirm_box(false, $l_new_type, build_hidden_fields($s_hidden_fields)); + } + + redirect($redirect); +} + +/** +* Move Topic +*/ +function mcp_move_topic($topic_ids) +{ + global $auth, $user, $db, $template, $phpbb_log, $request, $phpbb_dispatcher; + global $phpEx, $phpbb_root_path; + + // Here we limit the operation to one forum only + $forum_id = phpbb_check_ids($topic_ids, TOPICS_TABLE, 'topic_id', array('m_move'), true); + + if ($forum_id === false) + { + return; + } + + $to_forum_id = $request->variable('to_forum_id', 0); + $redirect = $request->variable('redirect', build_url(array('action', 'quickmod'))); + $additional_msg = $success_msg = ''; + + $s_hidden_fields = build_hidden_fields(array( + 'topic_id_list' => $topic_ids, + 'f' => $forum_id, + 'action' => 'move', + 'redirect' => $redirect) + ); + + if ($to_forum_id) + { + $forum_data = phpbb_get_forum_data($to_forum_id, 'f_post'); + + if (!count($forum_data)) + { + $additional_msg = $user->lang['FORUM_NOT_EXIST']; + } + else + { + $forum_data = $forum_data[$to_forum_id]; + + if ($forum_data['forum_type'] != FORUM_POST) + { + $additional_msg = $user->lang['FORUM_NOT_POSTABLE']; + } + else if (!$auth->acl_get('f_post', $to_forum_id) || (!$auth->acl_get('m_approve', $to_forum_id) && !$auth->acl_get('f_noapprove', $to_forum_id))) + { + $additional_msg = $user->lang['USER_CANNOT_POST']; + } + else if ($forum_id == $to_forum_id) + { + $additional_msg = $user->lang['CANNOT_MOVE_SAME_FORUM']; + } + } + } + else if (isset($_POST['confirm'])) + { + $additional_msg = $user->lang['FORUM_NOT_EXIST']; + } + + if (!$to_forum_id || $additional_msg) + { + $request->overwrite('confirm', null, \phpbb\request\request_interface::POST); + $request->overwrite('confirm_key', null); + } + + if (confirm_box(true)) + { + $topic_data = phpbb_get_topic_data($topic_ids); + $leave_shadow = (isset($_POST['move_leave_shadow'])) ? true : false; + + $forum_sync_data = array(); + + $forum_sync_data[$forum_id] = current($topic_data); + $forum_sync_data[$to_forum_id] = $forum_data; + + $topics_moved = $topics_moved_unapproved = $topics_moved_softdeleted = 0; + $posts_moved = $posts_moved_unapproved = $posts_moved_softdeleted = 0; + + foreach ($topic_data as $topic_id => $topic_info) + { + if ($topic_info['topic_visibility'] == ITEM_APPROVED) + { + $topics_moved++; + } + else if ($topic_info['topic_visibility'] == ITEM_UNAPPROVED || $topic_info['topic_visibility'] == ITEM_REAPPROVE) + { + $topics_moved_unapproved++; + } + else if ($topic_info['topic_visibility'] == ITEM_DELETED) + { + $topics_moved_softdeleted++; + } + + $posts_moved += $topic_info['topic_posts_approved']; + $posts_moved_unapproved += $topic_info['topic_posts_unapproved']; + $posts_moved_softdeleted += $topic_info['topic_posts_softdeleted']; + } + + $db->sql_transaction('begin'); + + // Move topics, but do not resync yet + move_topics($topic_ids, $to_forum_id, false); + + if ($request->is_set_post('move_lock_topics') && $auth->acl_get('m_lock', $to_forum_id)) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_status = ' . ITEM_LOCKED . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids); + $db->sql_query($sql); + } + + $shadow_topics = 0; + $forum_ids = array($to_forum_id); + foreach ($topic_data as $topic_id => $row) + { + // Get the list of forums to resync + $forum_ids[] = $row['forum_id']; + + // We add the $to_forum_id twice, because 'forum_id' is updated + // when the topic is moved again later. + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_MOVE', false, array( + 'forum_id' => (int) $to_forum_id, + 'topic_id' => (int) $topic_id, + $row['forum_name'], + $forum_data['forum_name'], + (int) $row['forum_id'], + (int) $forum_data['forum_id'], + )); + + // Leave a redirection if required and only if the topic is visible to users + if ($leave_shadow && $row['topic_visibility'] == ITEM_APPROVED && $row['topic_type'] != POST_GLOBAL) + { + $shadow = array( + 'forum_id' => (int) $row['forum_id'], + 'icon_id' => (int) $row['icon_id'], + 'topic_attachment' => (int) $row['topic_attachment'], + 'topic_visibility' => ITEM_APPROVED, // a shadow topic is always approved + 'topic_reported' => 0, // a shadow topic is never reported + 'topic_title' => (string) $row['topic_title'], + 'topic_poster' => (int) $row['topic_poster'], + 'topic_time' => (int) $row['topic_time'], + 'topic_time_limit' => (int) $row['topic_time_limit'], + 'topic_views' => (int) $row['topic_views'], + 'topic_posts_approved' => (int) $row['topic_posts_approved'], + 'topic_posts_unapproved'=> (int) $row['topic_posts_unapproved'], + 'topic_posts_softdeleted'=> (int) $row['topic_posts_softdeleted'], + 'topic_status' => ITEM_MOVED, + 'topic_type' => POST_NORMAL, + 'topic_first_post_id' => (int) $row['topic_first_post_id'], + 'topic_first_poster_colour'=>(string) $row['topic_first_poster_colour'], + 'topic_first_poster_name'=> (string) $row['topic_first_poster_name'], + 'topic_last_post_id' => (int) $row['topic_last_post_id'], + 'topic_last_poster_id' => (int) $row['topic_last_poster_id'], + 'topic_last_poster_colour'=>(string) $row['topic_last_poster_colour'], + 'topic_last_poster_name'=> (string) $row['topic_last_poster_name'], + 'topic_last_post_subject'=> (string) $row['topic_last_post_subject'], + 'topic_last_post_time' => (int) $row['topic_last_post_time'], + 'topic_last_view_time' => (int) $row['topic_last_view_time'], + 'topic_moved_id' => (int) $row['topic_id'], + 'topic_bumped' => (int) $row['topic_bumped'], + 'topic_bumper' => (int) $row['topic_bumper'], + 'poll_title' => (string) $row['poll_title'], + 'poll_start' => (int) $row['poll_start'], + 'poll_length' => (int) $row['poll_length'], + 'poll_max_options' => (int) $row['poll_max_options'], + 'poll_last_vote' => (int) $row['poll_last_vote'] + ); + + /** + * Perform actions before shadow topic is created. + * + * @event core.mcp_main_modify_shadow_sql + * @var array shadow SQL array to be used by $db->sql_build_array + * @var array row Topic data + * @since 3.1.11-RC1 + * @changed 3.1.11-RC1 Added variable: row + */ + $vars = array( + 'shadow', + 'row', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_main_modify_shadow_sql', compact($vars))); + + $db->sql_query('INSERT INTO ' . TOPICS_TABLE . $db->sql_build_array('INSERT', $shadow)); + + // Shadow topics only count on new "topics" and not posts... a shadow topic alone has 0 posts + $shadow_topics++; + } + } + unset($topic_data); + + $sync_sql = array(); + if ($posts_moved) + { + $sync_sql[$to_forum_id][] = 'forum_posts_approved = forum_posts_approved + ' . (int) $posts_moved; + $sync_sql[$forum_id][] = 'forum_posts_approved = forum_posts_approved - ' . (int) $posts_moved; + } + if ($posts_moved_unapproved) + { + $sync_sql[$to_forum_id][] = 'forum_posts_unapproved = forum_posts_unapproved + ' . (int) $posts_moved_unapproved; + $sync_sql[$forum_id][] = 'forum_posts_unapproved = forum_posts_unapproved - ' . (int) $posts_moved_unapproved; + } + if ($posts_moved_softdeleted) + { + $sync_sql[$to_forum_id][] = 'forum_posts_softdeleted = forum_posts_softdeleted + ' . (int) $posts_moved_softdeleted; + $sync_sql[$forum_id][] = 'forum_posts_softdeleted = forum_posts_softdeleted - ' . (int) $posts_moved_softdeleted; + } + + if ($topics_moved) + { + $sync_sql[$to_forum_id][] = 'forum_topics_approved = forum_topics_approved + ' . (int) $topics_moved; + if ($topics_moved - $shadow_topics > 0) + { + $sync_sql[$forum_id][] = 'forum_topics_approved = forum_topics_approved - ' . (int) ($topics_moved - $shadow_topics); + } + } + if ($topics_moved_unapproved) + { + $sync_sql[$to_forum_id][] = 'forum_topics_unapproved = forum_topics_unapproved + ' . (int) $topics_moved_unapproved; + $sync_sql[$forum_id][] = 'forum_topics_unapproved = forum_topics_unapproved - ' . (int) $topics_moved_unapproved; + } + if ($topics_moved_softdeleted) + { + $sync_sql[$to_forum_id][] = 'forum_topics_softdeleted = forum_topics_softdeleted + ' . (int) $topics_moved_softdeleted; + $sync_sql[$forum_id][] = 'forum_topics_softdeleted = forum_topics_softdeleted - ' . (int) $topics_moved_softdeleted; + } + + $success_msg = (count($topic_ids) == 1) ? 'TOPIC_MOVED_SUCCESS' : 'TOPICS_MOVED_SUCCESS'; + + foreach ($sync_sql as $forum_id_key => $array) + { + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET ' . implode(', ', $array) . ' + WHERE forum_id = ' . $forum_id_key; + $db->sql_query($sql); + } + + $db->sql_transaction('commit'); + + sync('forum', 'forum_id', array($forum_id, $to_forum_id)); + } + else + { + $template->assign_vars(array( + 'S_FORUM_SELECT' => make_forum_select($to_forum_id, $forum_id, false, true, true, true), + 'S_CAN_LEAVE_SHADOW' => true, + 'S_CAN_LOCK_TOPIC' => ($auth->acl_get('m_lock', $to_forum_id)) ? true : false, + 'ADDITIONAL_MSG' => $additional_msg) + ); + + confirm_box(false, 'MOVE_TOPIC' . ((count($topic_ids) == 1) ? '' : 'S'), $s_hidden_fields, 'mcp_move.html'); + } + + $redirect = $request->variable('redirect', "index.$phpEx"); + $redirect = reapply_sid($redirect); + + if (!$success_msg) + { + redirect($redirect); + } + else + { + meta_refresh(3, $redirect); + + $message = $user->lang[$success_msg]; + $message .= '

' . sprintf($user->lang['RETURN_PAGE'], '', ''); + $message .= '

' . sprintf($user->lang['RETURN_FORUM'], '', ''); + $message .= '

' . sprintf($user->lang['RETURN_NEW_FORUM'], '', ''); + + trigger_error($message); + } +} + +/** +* Restore Topics +*/ +function mcp_restore_topic($topic_ids) +{ + global $user, $phpEx, $phpbb_root_path, $request, $phpbb_container, $phpbb_log; + + if (!phpbb_check_ids($topic_ids, TOPICS_TABLE, 'topic_id', array('m_approve'))) + { + return; + } + + $redirect = $request->variable('redirect', build_url(array('action', 'quickmod'))); + $forum_id = $request->variable('f', 0); + + $s_hidden_fields = build_hidden_fields(array( + 'topic_id_list' => $topic_ids, + 'f' => $forum_id, + 'action' => 'restore_topic', + 'redirect' => $redirect, + )); + $success_msg = ''; + + if (confirm_box(true)) + { + $success_msg = (count($topic_ids) == 1) ? 'TOPIC_RESTORED_SUCCESS' : 'TOPICS_RESTORED_SUCCESS'; + + $data = phpbb_get_topic_data($topic_ids); + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + foreach ($data as $topic_id => $row) + { + $return = $phpbb_content_visibility->set_topic_visibility(ITEM_APPROVED, $topic_id, $row['forum_id'], $user->data['user_id'], time(), ''); + if (!empty($return)) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_RESTORE_TOPIC', false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $topic_id, + $row['topic_title'], + $row['topic_first_poster_name'] + )); + } + } + } + else + { + confirm_box(false, (count($topic_ids) == 1) ? 'RESTORE_TOPIC' : 'RESTORE_TOPICS', $s_hidden_fields); + } + + $topic_id = $request->variable('t', 0); + if (!$request->is_set('quickmod', \phpbb\request\request_interface::REQUEST)) + { + $redirect = $request->variable('redirect', "index.$phpEx"); + $redirect = reapply_sid($redirect); + $redirect_message = 'PAGE'; + } + else if ($topic_id) + { + $redirect = append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=' . $topic_id); + $redirect_message = 'TOPIC'; + } + else + { + $redirect = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id); + $redirect_message = 'FORUM'; + } + + if (!$success_msg) + { + redirect($redirect); + } + else + { + meta_refresh(3, $redirect); + trigger_error($user->lang[$success_msg] . '

' . sprintf($user->lang['RETURN_' . $redirect_message], '', '')); + } +} + +/** +* Delete Topics +*/ +function mcp_delete_topic($topic_ids, $is_soft = false, $soft_delete_reason = '', $action = 'delete_topic') +{ + global $auth, $user, $db, $phpEx, $phpbb_root_path, $request, $phpbb_container, $phpbb_log; + + $check_permission = ($is_soft) ? 'm_softdelete' : 'm_delete'; + if (!phpbb_check_ids($topic_ids, TOPICS_TABLE, 'topic_id', array($check_permission))) + { + return; + } + + $redirect = $request->variable('redirect', build_url(array('action', 'quickmod'))); + $forum_id = $request->variable('f', 0); + + $s_hidden_fields = array( + 'topic_id_list' => $topic_ids, + 'f' => $forum_id, + 'action' => $action, + 'redirect' => $redirect, + ); + $success_msg = ''; + + if (confirm_box(true)) + { + $success_msg = (count($topic_ids) == 1) ? 'TOPIC_DELETED_SUCCESS' : 'TOPICS_DELETED_SUCCESS'; + + $data = phpbb_get_topic_data($topic_ids); + + foreach ($data as $topic_id => $row) + { + if ($row['topic_moved_id']) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_DELETE_SHADOW_TOPIC', false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $topic_id, + $row['topic_title'] + )); + } + else + { + // Only soft delete non-shadow topics + if ($is_soft) + { + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + $return = $phpbb_content_visibility->set_topic_visibility(ITEM_DELETED, $topic_id, $row['forum_id'], $user->data['user_id'], time(), $soft_delete_reason); + if (!empty($return)) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_SOFTDELETE_TOPIC', false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $topic_id, + $row['topic_title'], + $row['topic_first_poster_name'], + $soft_delete_reason + )); + } + } + else + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_DELETE_TOPIC', false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $topic_id, + $row['topic_title'], + $row['topic_first_poster_name'], + $soft_delete_reason + )); + } + } + } + + if (!$is_soft) + { + delete_topics('topic_id', $topic_ids); + } + } + else + { + global $template; + + $user->add_lang('posting'); + + // If there are only shadow topics, we neither need a reason nor softdelete + $sql = 'SELECT topic_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . ' + AND topic_moved_id = 0'; + $result = $db->sql_query_limit($sql, 1); + $only_shadow = !$db->sql_fetchfield('topic_id'); + $db->sql_freeresult($result); + + $only_softdeleted = false; + if (!$only_shadow && $auth->acl_get('m_delete', $forum_id) && $auth->acl_get('m_softdelete', $forum_id)) + { + // If there are only soft deleted topics, we display a message why the option is not available + $sql = 'SELECT topic_id + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_ids) . ' + AND topic_visibility <> ' . ITEM_DELETED; + $result = $db->sql_query_limit($sql, 1); + $only_softdeleted = !$db->sql_fetchfield('topic_id'); + $db->sql_freeresult($result); + } + + $template->assign_vars(array( + 'S_SHADOW_TOPICS' => $only_shadow, + 'S_SOFTDELETED' => $only_softdeleted, + 'S_TOPIC_MODE' => true, + 'S_ALLOWED_DELETE' => $auth->acl_get('m_delete', $forum_id), + 'S_ALLOWED_SOFTDELETE' => $auth->acl_get('m_softdelete', $forum_id), + 'DELETE_TOPIC_PERMANENTLY_EXPLAIN' => $user->lang('DELETE_TOPIC_PERMANENTLY', count($topic_ids)), + )); + + $count = count($topic_ids); + $l_confirm = $count === 1 ? 'DELETE_TOPIC' : 'DELETE_TOPICS'; + if ($only_softdeleted) + { + $l_confirm = array($l_confirm . '_PERMANENTLY', $count); + $s_hidden_fields['delete_permanent'] = '1'; + } + else if ($only_shadow || !$auth->acl_get('m_softdelete', $forum_id)) + { + $s_hidden_fields['delete_permanent'] = '1'; + } + + confirm_box(false, $l_confirm, build_hidden_fields($s_hidden_fields), 'confirm_delete_body.html'); + } + + $topic_id = $request->variable('t', 0); + if (!$request->is_set('quickmod', \phpbb\request\request_interface::REQUEST)) + { + $redirect = $request->variable('redirect', "index.$phpEx"); + $redirect = reapply_sid($redirect); + $redirect_message = 'PAGE'; + } + else if ($is_soft && $topic_id) + { + $redirect = append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=' . $topic_id); + $redirect_message = 'TOPIC'; + } + else + { + $redirect = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id); + $redirect_message = 'FORUM'; + } + + if (!$success_msg) + { + redirect($redirect); + } + else + { + meta_refresh(3, $redirect); + trigger_error($user->lang[$success_msg] . '

' . sprintf($user->lang['RETURN_' . $redirect_message], '', '')); + } +} + +/** +* Delete Posts +*/ +function mcp_delete_post($post_ids, $is_soft = false, $soft_delete_reason = '', $action = 'delete_post') +{ + global $auth, $user, $db, $phpEx, $phpbb_root_path, $request, $phpbb_container, $phpbb_log; + + $check_permission = ($is_soft) ? 'm_softdelete' : 'm_delete'; + if (!phpbb_check_ids($post_ids, POSTS_TABLE, 'post_id', array($check_permission))) + { + return; + } + + $redirect = $request->variable('redirect', build_url(array('action', 'quickmod'))); + $forum_id = $request->variable('f', 0); + + $s_hidden_fields = array( + 'post_id_list' => $post_ids, + 'f' => $forum_id, + 'action' => $action, + 'redirect' => $redirect, + ); + $success_msg = ''; + + if (confirm_box(true) && $is_soft) + { + $post_info = phpbb_get_post_data($post_ids); + + $topic_info = $approve_log = array(); + + // Group the posts by topic_id + foreach ($post_info as $post_id => $post_data) + { + if ($post_data['post_visibility'] != ITEM_APPROVED) + { + continue; + } + $topic_id = (int) $post_data['topic_id']; + + $topic_info[$topic_id]['posts'][] = (int) $post_id; + $topic_info[$topic_id]['forum_id'] = (int) $post_data['forum_id']; + + if ($post_id == $post_data['topic_first_post_id']) + { + $topic_info[$topic_id]['first_post'] = true; + } + + if ($post_id == $post_data['topic_last_post_id']) + { + $topic_info[$topic_id]['last_post'] = true; + } + + $approve_log[] = array( + 'forum_id' => $post_data['forum_id'], + 'topic_id' => $post_data['topic_id'], + 'post_id' => $post_id, + 'post_subject' => $post_data['post_subject'], + 'poster_id' => $post_data['poster_id'], + 'post_username' => $post_data['post_username'], + 'username' => $post_data['username'], + ); + } + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + foreach ($topic_info as $topic_id => $topic_data) + { + $phpbb_content_visibility->set_post_visibility(ITEM_DELETED, $topic_data['posts'], $topic_id, $topic_data['forum_id'], $user->data['user_id'], time(), $soft_delete_reason, isset($topic_data['first_post']), isset($topic_data['last_post'])); + } + $affected_topics = count($topic_info); + // None of the topics is really deleted, so a redirect won't hurt much. + $deleted_topics = 0; + + $success_msg = (count($post_info) == 1) ? $user->lang['POST_DELETED_SUCCESS'] : $user->lang['POSTS_DELETED_SUCCESS']; + + foreach ($approve_log as $row) + { + $post_username = ($row['poster_id'] == ANONYMOUS && !empty($row['post_username'])) ? $row['post_username'] : $row['username']; + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_SOFTDELETE_POST', false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $row['topic_id'], + 'post_id' => $row['post_id'], + $row['post_subject'], + $post_username, + $soft_delete_reason + )); + } + + $topic_id = $request->variable('t', 0); + + // Return links + $return_link = array(); + if ($affected_topics == 1 && $topic_id) + { + $return_link[] = sprintf($user->lang['RETURN_TOPIC'], '', ''); + } + $return_link[] = sprintf($user->lang['RETURN_FORUM'], '', ''); + + } + else if (confirm_box(true)) + { + if (!function_exists('delete_posts')) + { + include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + } + + // Count the number of topics that are affected + // I did not use COUNT(DISTINCT ...) because I remember having problems + // with it on older versions of MySQL -- Ashe + + $sql = 'SELECT DISTINCT topic_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_id', $post_ids); + $result = $db->sql_query($sql); + + $topic_id_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topic_id_list[] = $row['topic_id']; + } + $affected_topics = count($topic_id_list); + $db->sql_freeresult($result); + + $post_data = phpbb_get_post_data($post_ids); + + foreach ($post_data as $id => $row) + { + $post_username = ($row['poster_id'] == ANONYMOUS && !empty($row['post_username'])) ? $row['post_username'] : $row['username']; + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_DELETE_POST', false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $row['topic_id'], + 'post_id' => $row['post_id'], + $row['post_subject'], + $post_username, + $soft_delete_reason + )); + } + + // Now delete the posts, topics and forums are automatically resync'ed + delete_posts('post_id', $post_ids); + + $sql = 'SELECT COUNT(topic_id) AS topics_left + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topic_id_list); + $result = $db->sql_query_limit($sql, 1); + + $deleted_topics = ($row = $db->sql_fetchrow($result)) ? ($affected_topics - $row['topics_left']) : $affected_topics; + $db->sql_freeresult($result); + + $topic_id = $request->variable('t', 0); + + // Return links + $return_link = array(); + if ($affected_topics == 1 && !$deleted_topics && $topic_id) + { + $return_link[] = sprintf($user->lang['RETURN_TOPIC'], '', ''); + } + $return_link[] = sprintf($user->lang['RETURN_FORUM'], '', ''); + + if (count($post_ids) == 1) + { + if ($deleted_topics) + { + // We deleted the only post of a topic, which in turn has + // been removed from the database + $success_msg = $user->lang['TOPIC_DELETED_SUCCESS']; + } + else + { + $success_msg = $user->lang['POST_DELETED_SUCCESS']; + } + } + else + { + if ($deleted_topics) + { + // Some of topics disappeared + $success_msg = $user->lang['POSTS_DELETED_SUCCESS'] . '

' . $user->lang['EMPTY_TOPICS_REMOVED_WARNING']; + } + else + { + $success_msg = $user->lang['POSTS_DELETED_SUCCESS']; + } + } + } + else + { + global $template; + + $user->add_lang('posting'); + + $only_softdeleted = false; + if ($auth->acl_get('m_delete', $forum_id) && $auth->acl_get('m_softdelete', $forum_id)) + { + // If there are only soft deleted posts, we display a message why the option is not available + $sql = 'SELECT post_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_id', $post_ids) . ' + AND post_visibility <> ' . ITEM_DELETED; + $result = $db->sql_query_limit($sql, 1); + $only_softdeleted = !$db->sql_fetchfield('post_id'); + $db->sql_freeresult($result); + } + + $template->assign_vars(array( + 'S_SOFTDELETED' => $only_softdeleted, + 'S_ALLOWED_DELETE' => $auth->acl_get('m_delete', $forum_id), + 'S_ALLOWED_SOFTDELETE' => $auth->acl_get('m_softdelete', $forum_id), + 'DELETE_POST_PERMANENTLY_EXPLAIN' => $user->lang('DELETE_POST_PERMANENTLY', count($post_ids)), + )); + + $count = count($post_ids); + $l_confirm = $count === 1 ? 'DELETE_POST' : 'DELETE_POSTS'; + if ($only_softdeleted) + { + $l_confirm = array($l_confirm . '_PERMANENTLY', $count); + $s_hidden_fields['delete_permanent'] = '1'; + } + else if (!$auth->acl_get('m_softdelete', $forum_id)) + { + $s_hidden_fields['delete_permanent'] = '1'; + } + + confirm_box(false, $l_confirm, build_hidden_fields($s_hidden_fields), 'confirm_delete_body.html'); + } + + $redirect = $request->variable('redirect', "index.$phpEx"); + $redirect = reapply_sid($redirect); + + if (!$success_msg) + { + redirect($redirect); + } + else + { + if ($affected_topics != 1 || $deleted_topics || !$topic_id) + { + $redirect = append_sid("{$phpbb_root_path}mcp.$phpEx", "f=$forum_id&i=main&mode=forum_view", false); + } + + meta_refresh(3, $redirect); + trigger_error($success_msg . '

' . sprintf($user->lang['RETURN_PAGE'], '', '') . '

' . implode('

', $return_link)); + } +} + +/** +* Fork Topic +*/ +function mcp_fork_topic($topic_ids) +{ + global $auth, $user, $db, $template, $config; + global $phpEx, $phpbb_root_path, $phpbb_log, $request, $phpbb_dispatcher; + + if (!phpbb_check_ids($topic_ids, TOPICS_TABLE, 'topic_id', array('m_'))) + { + return; + } + + $to_forum_id = $request->variable('to_forum_id', 0); + $forum_id = $request->variable('f', 0); + $redirect = $request->variable('redirect', build_url(array('action', 'quickmod'))); + $additional_msg = $success_msg = ''; + $counter = array(); + + $s_hidden_fields = build_hidden_fields(array( + 'topic_id_list' => $topic_ids, + 'f' => $forum_id, + 'action' => 'fork', + 'redirect' => $redirect) + ); + + if ($to_forum_id) + { + $forum_data = phpbb_get_forum_data($to_forum_id, 'f_post'); + + if (!count($topic_ids)) + { + $additional_msg = $user->lang['NO_TOPIC_SELECTED']; + } + else if (!count($forum_data)) + { + $additional_msg = $user->lang['FORUM_NOT_EXIST']; + } + else + { + $forum_data = $forum_data[$to_forum_id]; + + if ($forum_data['forum_type'] != FORUM_POST) + { + $additional_msg = $user->lang['FORUM_NOT_POSTABLE']; + } + else if (!$auth->acl_get('f_post', $to_forum_id)) + { + $additional_msg = $user->lang['USER_CANNOT_POST']; + } + } + } + else if (isset($_POST['confirm'])) + { + $additional_msg = $user->lang['FORUM_NOT_EXIST']; + } + + if ($additional_msg) + { + $request->overwrite('confirm', null, \phpbb\request\request_interface::POST); + $request->overwrite('confirm_key', null); + } + + if (confirm_box(true)) + { + $topic_data = phpbb_get_topic_data($topic_ids, 'f_post'); + + $total_topics = $total_topics_unapproved = $total_topics_softdeleted = 0; + $total_posts = $total_posts_unapproved = $total_posts_softdeleted = 0; + $new_topic_id_list = array(); + + foreach ($topic_data as $topic_id => $topic_row) + { + if (!isset($search_type) && $topic_row['enable_indexing']) + { + // Select the search method and do some additional checks to ensure it can actually be utilised + $search_type = $config['search_type']; + + if (!class_exists($search_type)) + { + trigger_error('NO_SUCH_SEARCH_MODULE'); + } + + $error = false; + $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher); + $search_mode = 'post'; + + if ($error) + { + trigger_error($error); + } + } + else if (!isset($search_type) && !$topic_row['enable_indexing']) + { + $search_type = false; + } + + $sql_ary = array( + 'forum_id' => (int) $to_forum_id, + 'icon_id' => (int) $topic_row['icon_id'], + 'topic_attachment' => (int) $topic_row['topic_attachment'], + 'topic_visibility' => (int) $topic_row['topic_visibility'], + 'topic_reported' => 0, + 'topic_title' => (string) $topic_row['topic_title'], + 'topic_poster' => (int) $topic_row['topic_poster'], + 'topic_time' => (int) $topic_row['topic_time'], + 'topic_posts_approved' => (int) $topic_row['topic_posts_approved'], + 'topic_posts_unapproved' => (int) $topic_row['topic_posts_unapproved'], + 'topic_posts_softdeleted' => (int) $topic_row['topic_posts_softdeleted'], + 'topic_status' => (int) $topic_row['topic_status'], + 'topic_type' => (int) $topic_row['topic_type'], + 'topic_first_poster_name' => (string) $topic_row['topic_first_poster_name'], + 'topic_last_poster_id' => (int) $topic_row['topic_last_poster_id'], + 'topic_last_poster_name' => (string) $topic_row['topic_last_poster_name'], + 'topic_last_post_time' => (int) $topic_row['topic_last_post_time'], + 'topic_last_view_time' => (int) $topic_row['topic_last_view_time'], + 'topic_bumped' => (int) $topic_row['topic_bumped'], + 'topic_bumper' => (int) $topic_row['topic_bumper'], + 'poll_title' => (string) $topic_row['poll_title'], + 'poll_start' => (int) $topic_row['poll_start'], + 'poll_length' => (int) $topic_row['poll_length'], + 'poll_max_options' => (int) $topic_row['poll_max_options'], + 'poll_vote_change' => (int) $topic_row['poll_vote_change'], + ); + + /** + * Perform actions before forked topic is created. + * + * @event core.mcp_main_modify_fork_sql + * @var array sql_ary SQL array to be used by $db->sql_build_array + * @var array topic_row Topic data + * @since 3.1.11-RC1 + * @changed 3.1.11-RC1 Added variable: topic_row + */ + $vars = array( + 'sql_ary', + 'topic_row', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_main_modify_fork_sql', compact($vars))); + + $db->sql_query('INSERT INTO ' . TOPICS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + $new_topic_id = $db->sql_nextid(); + $new_topic_id_list[$topic_id] = $new_topic_id; + + switch ($topic_row['topic_visibility']) + { + case ITEM_APPROVED: + $total_topics++; + break; + case ITEM_UNAPPROVED: + case ITEM_REAPPROVE: + $total_topics_unapproved++; + break; + case ITEM_DELETED: + $total_topics_softdeleted++; + break; + } + + if ($topic_row['poll_start']) + { + $sql = 'SELECT * + FROM ' . POLL_OPTIONS_TABLE . " + WHERE topic_id = $topic_id"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary = array( + 'poll_option_id' => (int) $row['poll_option_id'], + 'topic_id' => (int) $new_topic_id, + 'poll_option_text' => (string) $row['poll_option_text'], + 'poll_option_total' => 0 + ); + + $db->sql_query('INSERT INTO ' . POLL_OPTIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + } + $db->sql_freeresult($result); + } + + $sql = 'SELECT * + FROM ' . POSTS_TABLE . " + WHERE topic_id = $topic_id + ORDER BY post_time ASC, post_id ASC"; + $result = $db->sql_query($sql); + + $post_rows = array(); + while ($row = $db->sql_fetchrow($result)) + { + $post_rows[] = $row; + } + $db->sql_freeresult($result); + + if (!count($post_rows)) + { + continue; + } + + foreach ($post_rows as $row) + { + $sql_ary = array( + 'topic_id' => (int) $new_topic_id, + 'forum_id' => (int) $to_forum_id, + 'poster_id' => (int) $row['poster_id'], + 'icon_id' => (int) $row['icon_id'], + 'poster_ip' => (string) $row['poster_ip'], + 'post_time' => (int) $row['post_time'], + 'post_visibility' => (int) $row['post_visibility'], + 'post_reported' => 0, + 'enable_bbcode' => (int) $row['enable_bbcode'], + 'enable_smilies' => (int) $row['enable_smilies'], + 'enable_magic_url' => (int) $row['enable_magic_url'], + 'enable_sig' => (int) $row['enable_sig'], + 'post_username' => (string) $row['post_username'], + 'post_subject' => (string) $row['post_subject'], + 'post_text' => (string) $row['post_text'], + 'post_edit_reason' => (string) $row['post_edit_reason'], + 'post_edit_user' => (int) $row['post_edit_user'], + 'post_checksum' => (string) $row['post_checksum'], + 'post_attachment' => (int) $row['post_attachment'], + 'bbcode_bitfield' => $row['bbcode_bitfield'], + 'bbcode_uid' => (string) $row['bbcode_uid'], + 'post_edit_time' => (int) $row['post_edit_time'], + 'post_edit_count' => (int) $row['post_edit_count'], + 'post_edit_locked' => (int) $row['post_edit_locked'], + 'post_postcount' => $row['post_postcount'], + ); + // Adjust post count only if the post can be incremented to the user counter + if ($row['post_postcount']) + { + if (isset($counter[$row['poster_id']])) + { + ++$counter[$row['poster_id']]; + } + else + { + $counter[$row['poster_id']] = 1; + } + } + $db->sql_query('INSERT INTO ' . POSTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + $new_post_id = $db->sql_nextid(); + + /** + * Perform actions after forked topic is created. + * + * @event core.mcp_main_fork_sql_after + * @var int new_topic_id The newly created topic ID + * @var int to_forum_id The forum ID where the forked topic has been moved to + * @var int new_post_id The newly created post ID + * @var array row Post data + * @since 3.2.4-RC1 + */ + $vars = array( + 'new_topic_id', + 'to_forum_id', + 'new_post_id', + 'row', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_main_fork_sql_after', compact($vars))); + + switch ($row['post_visibility']) + { + case ITEM_APPROVED: + $total_posts++; + break; + case ITEM_UNAPPROVED: + case ITEM_REAPPROVE: + $total_posts_unapproved++; + break; + case ITEM_DELETED: + $total_posts_softdeleted++; + break; + } + + // Copy whether the topic is dotted + markread('post', $to_forum_id, $new_topic_id, 0, $row['poster_id']); + + if (!empty($search_type)) + { + $search->index($search_mode, $new_post_id, $sql_ary['post_text'], $sql_ary['post_subject'], $sql_ary['poster_id'], ($topic_row['topic_type'] == POST_GLOBAL) ? 0 : $to_forum_id); + $search_mode = 'reply'; // After one we index replies + } + + // Copy Attachments + if ($row['post_attachment']) + { + $sql = 'SELECT * FROM ' . ATTACHMENTS_TABLE . " + WHERE post_msg_id = {$row['post_id']} + AND topic_id = $topic_id + AND in_message = 0"; + $result = $db->sql_query($sql); + + $sql_ary = array(); + while ($attach_row = $db->sql_fetchrow($result)) + { + $sql_ary[] = array( + 'post_msg_id' => (int) $new_post_id, + 'topic_id' => (int) $new_topic_id, + 'in_message' => 0, + 'is_orphan' => (int) $attach_row['is_orphan'], + 'poster_id' => (int) $attach_row['poster_id'], + 'physical_filename' => (string) utf8_basename($attach_row['physical_filename']), + 'real_filename' => (string) utf8_basename($attach_row['real_filename']), + 'download_count' => (int) $attach_row['download_count'], + 'attach_comment' => (string) $attach_row['attach_comment'], + 'extension' => (string) $attach_row['extension'], + 'mimetype' => (string) $attach_row['mimetype'], + 'filesize' => (int) $attach_row['filesize'], + 'filetime' => (int) $attach_row['filetime'], + 'thumbnail' => (int) $attach_row['thumbnail'] + ); + } + $db->sql_freeresult($result); + + if (count($sql_ary)) + { + $db->sql_multi_insert(ATTACHMENTS_TABLE, $sql_ary); + } + } + } + + // Copy topic subscriptions to new topic + $sql = 'SELECT user_id, notify_status + FROM ' . TOPICS_WATCH_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + + $sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary[] = array( + 'topic_id' => (int) $new_topic_id, + 'user_id' => (int) $row['user_id'], + 'notify_status' => (int) $row['notify_status'], + ); + } + $db->sql_freeresult($result); + + if (count($sql_ary)) + { + $db->sql_multi_insert(TOPICS_WATCH_TABLE, $sql_ary); + } + + // Copy bookmarks to new topic + $sql = 'SELECT user_id + FROM ' . BOOKMARKS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + + $sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary[] = array( + 'topic_id' => (int) $new_topic_id, + 'user_id' => (int) $row['user_id'], + ); + } + $db->sql_freeresult($result); + + if (count($sql_ary)) + { + $db->sql_multi_insert(BOOKMARKS_TABLE, $sql_ary); + } + } + + // Sync new topics, parent forums and board stats + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET forum_posts_approved = forum_posts_approved + ' . $total_posts . ', + forum_posts_unapproved = forum_posts_unapproved + ' . $total_posts_unapproved . ', + forum_posts_softdeleted = forum_posts_softdeleted + ' . $total_posts_softdeleted . ', + forum_topics_approved = forum_topics_approved + ' . $total_topics . ', + forum_topics_unapproved = forum_topics_unapproved + ' . $total_topics_unapproved . ', + forum_topics_softdeleted = forum_topics_softdeleted + ' . $total_topics_softdeleted . ' + WHERE forum_id = ' . $to_forum_id; + $db->sql_query($sql); + + if (!empty($counter)) + { + // Do only one query per user and not a query per post. + foreach ($counter as $user_id => $count) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = user_posts + ' . (int) $count . ' + WHERE user_id = ' . (int) $user_id; + $db->sql_query($sql); + } + } + + sync('topic', 'topic_id', $new_topic_id_list); + sync('forum', 'forum_id', $to_forum_id); + + $config->increment('num_topics', count($new_topic_id_list), false); + $config->increment('num_posts', $total_posts, false); + + foreach ($new_topic_id_list as $topic_id => $new_topic_id) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_FORK', false, array( + 'forum_id' => $to_forum_id, + 'topic_id' => $new_topic_id, + $topic_row['forum_name'] + )); + } + + $success_msg = (count($topic_ids) == 1) ? 'TOPIC_FORKED_SUCCESS' : 'TOPICS_FORKED_SUCCESS'; + } + else + { + $template->assign_vars(array( + 'S_FORUM_SELECT' => make_forum_select($to_forum_id, false, false, true, true, true), + 'S_CAN_LEAVE_SHADOW' => false, + 'ADDITIONAL_MSG' => $additional_msg) + ); + + confirm_box(false, 'FORK_TOPIC' . ((count($topic_ids) == 1) ? '' : 'S'), $s_hidden_fields, 'mcp_move.html'); + } + + $redirect = $request->variable('redirect', "index.$phpEx"); + $redirect = reapply_sid($redirect); + + if (!$success_msg) + { + redirect($redirect); + } + else + { + $redirect_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id); + meta_refresh(3, $redirect_url); + $return_link = sprintf($user->lang['RETURN_FORUM'], '', ''); + + if ($forum_id != $to_forum_id) + { + $return_link .= '

' . sprintf($user->lang['RETURN_NEW_FORUM'], '', ''); + } + + trigger_error($user->lang[$success_msg] . '

' . $return_link); + } +} diff --git a/includes/mcp/mcp_notes.php b/includes/mcp/mcp_notes.php new file mode 100644 index 0000000..a4c2356 --- /dev/null +++ b/includes/mcp/mcp_notes.php @@ -0,0 +1,258 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* mcp_notes +* Displays notes about a user +*/ +class mcp_notes +{ + var $p_master; + var $u_action; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $user, $template, $request; + global $phpbb_root_path, $phpEx; + + $action = $request->variable('action', array('' => '')); + + if (is_array($action)) + { + list($action, ) = each($action); + } + + $this->page_title = 'MCP_NOTES'; + + switch ($mode) + { + case 'front': + $template->assign_vars(array( + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=mcp&field=username&select_single=true'), + 'U_POST_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes'), + + 'L_TITLE' => $user->lang['MCP_NOTES'], + )); + + $this->tpl_name = 'mcp_notes_front'; + break; + + case 'user_notes': + $user->add_lang('acp/common'); + + $this->mcp_notes_user_view($action); + $this->tpl_name = 'mcp_notes_user'; + break; + } + } + + /** + * Display user notes + */ + function mcp_notes_user_view($action) + { + global $config, $phpbb_log, $request; + global $template, $db, $user, $auth, $phpbb_container; + + $user_id = $request->variable('u', 0); + $username = $request->variable('username', '', true); + $start = $request->variable('start', 0); + $st = $request->variable('st', 0); + $sk = $request->variable('sk', 'b'); + $sd = $request->variable('sd', 'd'); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + add_form_key('mcp_notes'); + + $sql_where = ($user_id) ? "user_id = $user_id" : "username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; + + $sql = 'SELECT * + FROM ' . USERS_TABLE . " + WHERE $sql_where"; + $result = $db->sql_query($sql); + $userrow = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$userrow) + { + trigger_error('NO_USER'); + } + + $user_id = $userrow['user_id']; + + // Populate user id to the currently active module (this module) + // The following method is another way of adjusting module urls. It is the easy variant if we want + // to directly adjust the current module url based on data retrieved within the same module. + if (strpos($this->u_action, "&u=$user_id") === false) + { + $this->p_master->adjust_url('&u=' . $user_id); + $this->u_action .= "&u=$user_id"; + } + + $deletemark = ($action == 'del_marked') ? true : false; + $deleteall = ($action == 'del_all') ? true : false; + $marked = $request->variable('marknote', array(0)); + $usernote = $request->variable('usernote', '', true); + + // Handle any actions + if (($deletemark || $deleteall) && $auth->acl_get('a_clearlogs')) + { + $where_sql = ''; + if ($deletemark && $marked) + { + $sql_in = array(); + foreach ($marked as $mark) + { + $sql_in[] = $mark; + } + $where_sql = ' AND ' . $db->sql_in_set('log_id', $sql_in); + unset($sql_in); + } + + if ($where_sql || $deleteall) + { + if (check_form_key('mcp_notes')) + { + $sql = 'DELETE FROM ' . LOG_TABLE . ' + WHERE log_type = ' . LOG_USERS . " + AND reportee_id = $user_id + $where_sql"; + $db->sql_query($sql); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CLEAR_USER', false, array($userrow['username'])); + + $msg = ($deletemark) ? 'MARKED_NOTES_DELETED' : 'ALL_NOTES_DELETED'; + } + else + { + $msg = 'FORM_INVALID'; + } + $redirect = $this->u_action . '&u=' . $user_id; + meta_refresh(3, $redirect); + trigger_error($user->lang[$msg] . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + } + } + + if ($usernote && $action == 'add_feedback') + { + if (check_form_key('mcp_notes')) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_FEEDBACK', false, array($userrow['username'])); + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_USER_FEEDBACK', false, array( + 'forum_id' => 0, + 'topic_id' => 0, + $userrow['username'] + )); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_GENERAL', false, array( + 'reportee_id' => $user_id, + $usernote + )); + + $msg = $user->lang['USER_FEEDBACK_ADDED']; + } + else + { + $msg = $user->lang['FORM_INVALID']; + } + $redirect = $this->u_action; + meta_refresh(3, $redirect); + + trigger_error($msg . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + } + + // Generate the appropriate user information for the user we are looking at + + $rank_title = $rank_img = ''; + $avatar_img = phpbb_get_user_avatar($userrow); + + $limit_days = array(0 => $user->lang['ALL_ENTRIES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('a' => $user->lang['SORT_USERNAME'], 'b' => $user->lang['SORT_DATE'], 'c' => $user->lang['SORT_IP'], 'd' => $user->lang['SORT_ACTION']); + $sort_by_sql = array('a' => 'u.username_clean', 'b' => 'l.log_time', 'c' => 'l.log_ip', 'd' => 'l.log_operation'); + + $s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + gen_sort_selects($limit_days, $sort_by_text, $st, $sk, $sd, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param); + + // Define where and sort sql for use in displaying logs + $sql_where = ($st) ? (time() - ($st * 86400)) : 0; + $sql_sort = $sort_by_sql[$sk] . ' ' . (($sd == 'd') ? 'DESC' : 'ASC'); + + $keywords = $request->variable('keywords', '', true); + $keywords_param = !empty($keywords) ? '&keywords=' . urlencode(htmlspecialchars_decode($keywords)) : ''; + + $log_data = array(); + $log_count = 0; + $start = view_log('user', $log_data, $log_count, $config['topics_per_page'], $start, 0, 0, $user_id, $sql_where, $sql_sort, $keywords); + + if ($log_count) + { + $template->assign_var('S_USER_NOTES', true); + + foreach ($log_data as $row) + { + $template->assign_block_vars('usernotes', array( + 'REPORT_BY' => $row['username_full'], + 'REPORT_AT' => $user->format_date($row['time']), + 'ACTION' => $row['action'], + 'IP' => $row['ip'], + 'ID' => $row['id']) + ); + } + } + + $base_url = $this->u_action . "&$u_sort_param$keywords_param"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $log_count, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'U_POST_ACTION' => $this->u_action, + 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DAYS' => $s_limit_days, + 'S_KEYWORDS' => $keywords, + + 'L_TITLE' => $user->lang['MCP_NOTES_USER'], + + 'TOTAL_REPORTS' => $user->lang('LIST_REPORTS', (int) $log_count), + + 'RANK_TITLE' => $rank_title, + 'JOINED' => $user->format_date($userrow['user_regdate']), + 'POSTS' => ($userrow['user_posts']) ? $userrow['user_posts'] : 0, + 'WARNINGS' => ($userrow['user_warnings']) ? $userrow['user_warnings'] : 0, + + 'USERNAME_FULL' => get_username_string('full', $userrow['user_id'], $userrow['username'], $userrow['user_colour']), + 'USERNAME_COLOUR' => get_username_string('colour', $userrow['user_id'], $userrow['username'], $userrow['user_colour']), + 'USERNAME' => get_username_string('username', $userrow['user_id'], $userrow['username'], $userrow['user_colour']), + 'U_PROFILE' => get_username_string('profile', $userrow['user_id'], $userrow['username'], $userrow['user_colour']), + + 'AVATAR_IMG' => $avatar_img, + 'RANK_IMG' => $rank_img, + ) + ); + } + +} diff --git a/includes/mcp/mcp_pm_reports.php b/includes/mcp/mcp_pm_reports.php new file mode 100644 index 0000000..eecfe9c --- /dev/null +++ b/includes/mcp/mcp_pm_reports.php @@ -0,0 +1,326 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* mcp_reports +* Handling the reports queue +*/ +class mcp_pm_reports +{ + var $p_master; + var $u_action; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $auth, $db, $user, $template, $request; + global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container; + + include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + include_once($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $start = $request->variable('start', 0); + + $this->page_title = 'MCP_PM_REPORTS'; + + switch ($action) + { + case 'close': + case 'delete': + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $report_id_list = $request->variable('report_id_list', array(0)); + + if (!count($report_id_list)) + { + trigger_error('NO_REPORT_SELECTED'); + } + + if (!function_exists('close_report')) + { + include($phpbb_root_path . 'includes/mcp/mcp_reports.' . $phpEx); + } + + close_report($report_id_list, $mode, $action, true); + + break; + } + + switch ($mode) + { + case 'pm_report_details': + + $user->add_lang(array('posting', 'viewforum', 'viewtopic', 'ucp')); + + $report_id = $request->variable('r', 0); + + $sql = 'SELECT r.pm_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour + FROM ' . REPORTS_TABLE . ' r, ' . REPORTS_REASONS_TABLE . ' rr, ' . USERS_TABLE . ' u + WHERE r.report_id = ' . $report_id . ' + AND rr.reason_id = r.reason_id + AND r.user_id = u.user_id + AND r.post_id = 0 + ORDER BY report_closed ASC'; + $result = $db->sql_query_limit($sql, 1); + $report = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$report_id || !$report) + { + trigger_error('NO_REPORT'); + } + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications_by_parent('report_pm', $report_id, $user->data['user_id']); + + $pm_id = $report['pm_id']; + $report_id = $report['report_id']; + + $pm_info = phpbb_get_pm_data(array($pm_id)); + + if (!count($pm_info)) + { + trigger_error('NO_REPORT_SELECTED'); + } + + $pm_info = $pm_info[$pm_id]; + + write_pm_addresses(array('to' => $pm_info['to_address'], 'bcc' => $pm_info['bcc_address']), (int) $pm_info['author_id']); + + $reason = array('title' => $report['reason_title'], 'description' => $report['reason_description']); + if (isset($user->lang['report_reasons']['TITLE'][strtoupper($reason['title'])]) && isset($user->lang['report_reasons']['DESCRIPTION'][strtoupper($reason['title'])])) + { + $reason['description'] = $user->lang['report_reasons']['DESCRIPTION'][strtoupper($reason['title'])]; + $reason['title'] = $user->lang['report_reasons']['TITLE'][strtoupper($reason['title'])]; + } + + // Process message, leave it uncensored + $parse_flags = ($pm_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($pm_info['message_text'], $pm_info['bbcode_uid'], $pm_info['bbcode_bitfield'], $parse_flags, false); + + $report['report_text'] = make_clickable(bbcode_nl2br($report['report_text'])); + + if ($pm_info['message_attachment'] && $auth->acl_get('u_pm_download')) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE post_msg_id = ' . $pm_id . ' + AND in_message = 1 + ORDER BY filetime DESC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[] = $row; + } + $db->sql_freeresult($result); + + if (count($attachments)) + { + $update_count = array(); + parse_attachments(0, $message, $attachments, $update_count); + } + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (!empty($attachments)) + { + $template->assign_var('S_HAS_ATTACHMENTS', true); + + foreach ($attachments as $attachment) + { + $template->assign_block_vars('attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + } + } + + $template->assign_vars(array( + 'S_MCP_REPORT' => true, + 'S_PM' => true, + 'S_CLOSE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=pm_reports&mode=pm_report_details&r=' . $report_id), + 'S_CAN_VIEWIP' => $auth->acl_getf_global('m_info'), + 'S_POST_REPORTED' => $pm_info['message_reported'], + 'S_REPORT_CLOSED' => $report['report_closed'], + 'S_USER_NOTES' => true, + + 'U_MCP_REPORT' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=pm_reports&mode=pm_report_details&r=' . $report_id), + 'U_MCP_REPORTER_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $report['user_id']), + 'U_MCP_USER_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $pm_info['author_id']), + 'U_MCP_WARN_REPORTER' => ($auth->acl_get('m_warn')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user&u=' . $report['user_id']) : '', + 'U_MCP_WARN_USER' => ($auth->acl_get('m_warn')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user&u=' . $pm_info['author_id']) : '', + + 'EDIT_IMG' => $user->img('icon_post_edit', $user->lang['EDIT_POST']), + 'MINI_POST_IMG' => $user->img('icon_post_target', 'POST'), + + 'RETURN_REPORTS' => sprintf($user->lang['RETURN_REPORTS'], '', ''), + 'REPORTED_IMG' => $user->img('icon_topic_reported', $user->lang['POST_REPORTED']), + 'REPORT_DATE' => $user->format_date($report['report_time']), + 'REPORT_ID' => $report_id, + 'REPORT_REASON_TITLE' => $reason['title'], + 'REPORT_REASON_DESCRIPTION' => $reason['description'], + 'REPORT_TEXT' => $report['report_text'], + + 'POST_AUTHOR_FULL' => get_username_string('full', $pm_info['author_id'], $pm_info['username'], $pm_info['user_colour']), + 'POST_AUTHOR_COLOUR' => get_username_string('colour', $pm_info['author_id'], $pm_info['username'], $pm_info['user_colour']), + 'POST_AUTHOR' => get_username_string('username', $pm_info['author_id'], $pm_info['username'], $pm_info['user_colour']), + 'U_POST_AUTHOR' => get_username_string('profile', $pm_info['author_id'], $pm_info['username'], $pm_info['user_colour']), + + 'REPORTER_FULL' => get_username_string('full', $report['user_id'], $report['username'], $report['user_colour']), + 'REPORTER_COLOUR' => get_username_string('colour', $report['user_id'], $report['username'], $report['user_colour']), + 'REPORTER_NAME' => get_username_string('username', $report['user_id'], $report['username'], $report['user_colour']), + 'U_VIEW_REPORTER_PROFILE' => get_username_string('profile', $report['user_id'], $report['username'], $report['user_colour']), + + 'POST_PREVIEW' => $message, + 'POST_SUBJECT' => ($pm_info['message_subject']) ? $pm_info['message_subject'] : $user->lang['NO_SUBJECT'], + 'POST_DATE' => $user->format_date($pm_info['message_time']), + 'POST_IP' => $pm_info['author_ip'], + 'POST_IPADDR' => ($auth->acl_getf_global('m_info') && $request->variable('lookup', '')) ? @gethostbyaddr($pm_info['author_ip']) : '', + 'POST_ID' => $pm_info['msg_id'], + + 'U_LOOKUP_IP' => ($auth->acl_getf_global('m_info')) ? $this->u_action . '&r=' . $report_id . '&pm=' . $pm_id . '&lookup=' . $pm_info['author_ip'] . '#ip' : '', + )); + + $this->tpl_name = 'mcp_post'; + + break; + + case 'pm_reports': + case 'pm_reports_closed': + $user->add_lang(array('ucp')); + + $sort_days = $total = 0; + $sort_key = $sort_dir = ''; + $sort_by_sql = $sort_order_sql = array(); + phpbb_mcp_sorting($mode, $sort_days, $sort_key, $sort_dir, $sort_by_sql, $sort_order_sql, $total); + + $limit_time_sql = ($sort_days) ? 'AND r.report_time >= ' . (time() - ($sort_days * 86400)) : ''; + + if ($mode == 'pm_reports') + { + $report_state = 'p.message_reported = 1 AND r.report_closed = 0'; + } + else + { + $report_state = 'r.report_closed = 1'; + } + + $sql = 'SELECT r.report_id + FROM ' . PRIVMSGS_TABLE . ' p, ' . REPORTS_TABLE . ' r ' . (($sort_order_sql[0] == 'u') ? ', ' . USERS_TABLE . ' u' : '') . (($sort_order_sql[0] == 'r') ? ', ' . USERS_TABLE . ' ru' : '') . " + WHERE $report_state + AND r.pm_id = p.msg_id + " . (($sort_order_sql[0] == 'u') ? 'AND u.user_id = p.author_id' : '') . ' + ' . (($sort_order_sql[0] == 'r') ? 'AND ru.user_id = r.user_id' : '') . " + AND r.post_id = 0 + $limit_time_sql + ORDER BY $sort_order_sql"; + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $i = 0; + $report_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $report_ids[] = $row['report_id']; + $row_num[$row['report_id']] = $i++; + } + $db->sql_freeresult($result); + + if (count($report_ids)) + { + $sql = 'SELECT p.*, u.username, u.username_clean, u.user_colour, r.user_id as reporter_id, ru.username as reporter_name, ru.user_colour as reporter_colour, r.report_time, r.report_id + FROM ' . REPORTS_TABLE . ' r, ' . PRIVMSGS_TABLE . ' p, ' . USERS_TABLE . ' u, ' . USERS_TABLE . ' ru + WHERE ' . $db->sql_in_set('r.report_id', $report_ids) . " + AND r.pm_id = p.msg_id + AND p.author_id = u.user_id + AND ru.user_id = r.user_id + ORDER BY $sort_order_sql"; + $result = $db->sql_query($sql); + + $pm_list = $pm_by_id = array(); + while ($row = $db->sql_fetchrow($result)) + { + $pm_by_id[(int) $row['msg_id']] = $row; + $pm_list[] = (int) $row['msg_id']; + } + $db->sql_freeresult($result); + + if (count($pm_list)) + { + $address_list = get_recipient_strings($pm_by_id); + + foreach ($pm_list as $message_id) + { + $row = $pm_by_id[$message_id]; + $template->assign_block_vars('postrow', array( + 'U_VIEW_DETAILS' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=pm_reports&mode=pm_report_details&r={$row['report_id']}"), + + 'PM_AUTHOR_FULL' => get_username_string('full', $row['author_id'], $row['username'], $row['user_colour']), + 'PM_AUTHOR_COLOUR' => get_username_string('colour', $row['author_id'], $row['username'], $row['user_colour']), + 'PM_AUTHOR' => get_username_string('username', $row['author_id'], $row['username'], $row['user_colour']), + 'U_PM_AUTHOR' => get_username_string('profile', $row['author_id'], $row['username'], $row['user_colour']), + + 'REPORTER_FULL' => get_username_string('full', $row['reporter_id'], $row['reporter_name'], $row['reporter_colour']), + 'REPORTER_COLOUR' => get_username_string('colour', $row['reporter_id'], $row['reporter_name'], $row['reporter_colour']), + 'REPORTER' => get_username_string('username', $row['reporter_id'], $row['reporter_name'], $row['reporter_colour']), + 'U_REPORTER' => get_username_string('profile', $row['reporter_id'], $row['reporter_name'], $row['reporter_colour']), + + 'PM_SUBJECT' => ($row['message_subject']) ? $row['message_subject'] : $user->lang['NO_SUBJECT'], + 'PM_TIME' => $user->format_date($row['message_time']), + 'REPORT_ID' => $row['report_id'], + 'REPORT_TIME' => $user->format_date($row['report_time']), + + 'RECIPIENTS' => implode($user->lang['COMMA_SEPARATOR'], $address_list[$row['msg_id']]), + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $row['message_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + )); + } + } + } + + $base_url = $this->u_action . "&st=$sort_days&sk=$sort_key&sd=$sort_dir"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); + + // Now display the page + $template->assign_vars(array( + 'L_EXPLAIN' => ($mode == 'pm_reports') ? $user->lang['MCP_PM_REPORTS_OPEN_EXPLAIN'] : $user->lang['MCP_PM_REPORTS_CLOSED_EXPLAIN'], + 'L_TITLE' => ($mode == 'pm_reports') ? $user->lang['MCP_PM_REPORTS_OPEN'] : $user->lang['MCP_PM_REPORTS_CLOSED'], + + 'S_PM' => true, + 'S_MCP_ACTION' => $this->u_action, + 'S_CLOSED' => ($mode == 'pm_reports_closed') ? true : false, + + 'TOTAL' => $total, + 'TOTAL_REPORTS' => $user->lang('LIST_REPORTS', (int) $total), + ) + ); + + $this->tpl_name = 'mcp_reports'; + break; + } + } +} diff --git a/includes/mcp/mcp_post.php b/includes/mcp/mcp_post.php new file mode 100644 index 0000000..8d27807 --- /dev/null +++ b/includes/mcp/mcp_post.php @@ -0,0 +1,680 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Handling actions in post details screen +*/ +function mcp_post_details($id, $mode, $action) +{ + global $phpEx, $phpbb_root_path, $config, $request; + global $template, $db, $user, $auth; + global $phpbb_container, $phpbb_dispatcher; + + $user->add_lang('posting'); + + $post_id = $request->variable('p', 0); + $start = $request->variable('start', 0); + + // Get post data + $post_info = phpbb_get_post_data(array($post_id), false, true); + + add_form_key('mcp_post_details'); + + if (!count($post_info)) + { + trigger_error('POST_NOT_EXIST'); + } + + $post_info = $post_info[$post_id]; + $url = append_sid("{$phpbb_root_path}mcp.$phpEx?" . phpbb_extra_url()); + + switch ($action) + { + case 'whois': + + if ($auth->acl_get('m_info', $post_info['forum_id'])) + { + $ip = $request->variable('ip', ''); + if (!function_exists('user_ipwhois')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $template->assign_vars(array( + 'RETURN_POST' => sprintf($user->lang['RETURN_POST'], '', ''), + 'U_RETURN_POST' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&mode=$mode&p=$post_id"), + 'L_RETURN_POST' => sprintf($user->lang['RETURN_POST'], '', ''), + 'WHOIS' => user_ipwhois($ip), + )); + } + + // We're done with the whois page so return + return; + + break; + + case 'chgposter': + case 'chgposter_ip': + + if ($action == 'chgposter') + { + $username = $request->variable('username', '', true); + $sql_where = "username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; + } + else + { + $new_user_id = $request->variable('u', 0); + $sql_where = 'user_id = ' . $new_user_id; + } + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE ' . $sql_where; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_USER'); + } + + if ($auth->acl_get('m_chgposter', $post_info['forum_id'])) + { + if (check_form_key('mcp_post_details')) + { + change_poster($post_info, $row); + } + else + { + trigger_error('FORM_INVALID'); + } + } + + break; + + default: + + /** + * This event allows you to handle custom post moderation options + * + * @event core.mcp_post_additional_options + * @var string action Post moderation action name + * @var array post_info Information on the affected post + * @since 3.1.5-RC1 + */ + $vars = array('action', 'post_info'); + extract($phpbb_dispatcher->trigger_event('core.mcp_post_additional_options', compact($vars))); + + break; + } + + // Set some vars + $users_ary = $usernames_ary = array(); + $attachments = $extensions = array(); + $post_id = $post_info['post_id']; + + // Get topic tracking info + if ($config['load_db_lastread']) + { + $tmp_topic_data = array($post_info['topic_id'] => $post_info); + $topic_tracking_info = get_topic_tracking($post_info['forum_id'], $post_info['topic_id'], $tmp_topic_data, array($post_info['forum_id'] => $post_info['forum_mark_time'])); + unset($tmp_topic_data); + } + else + { + $topic_tracking_info = get_complete_topic_tracking($post_info['forum_id'], $post_info['topic_id']); + } + + $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; + + // Process message, leave it uncensored + $parse_flags = ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($post_info['post_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], $parse_flags, false); + + if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE post_msg_id = ' . $post_id . ' + AND in_message = 0 + ORDER BY filetime DESC, post_msg_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[] = $row; + } + $db->sql_freeresult($result); + + if (count($attachments)) + { + $user->add_lang('viewtopic'); + $update_count = array(); + parse_attachments($post_info['forum_id'], $message, $attachments, $update_count); + } + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (!empty($attachments)) + { + $template->assign_var('S_HAS_ATTACHMENTS', true); + + foreach ($attachments as $attachment) + { + $template->assign_block_vars('attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + } + } + + // Deleting information + if ($post_info['post_visibility'] == ITEM_DELETED && $post_info['post_delete_user']) + { + // User having deleted the post also being the post author? + if (!$post_info['post_delete_user'] || $post_info['post_delete_user'] == $post_info['poster_id']) + { + $display_username = get_username_string('full', $post_info['poster_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']); + } + else + { + $sql = 'SELECT user_id, username, user_colour + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $post_info['post_delete_user']; + $result = $db->sql_query($sql); + $user_delete_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + $display_username = get_username_string('full', $post_info['post_delete_user'], $user_delete_row['username'], $user_delete_row['user_colour']); + } + + $user->add_lang('viewtopic'); + $l_deleted_by = $user->lang('DELETED_INFORMATION', $display_username, $user->format_date($post_info['post_delete_time'], false, true)); + } + else + { + $l_deleted_by = ''; + } + + // parse signature + $parse_flags = ($post_info['user_sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $post_info['user_sig'] = generate_text_for_display($post_info['user_sig'], $post_info['user_sig_bbcode_uid'], $post_info['user_sig_bbcode_bitfield'], $parse_flags, true); + + $mcp_post_template_data = array( + 'U_MCP_ACTION' => "$url&i=main&quickmod=1&mode=post_details", // Use this for mode paramaters + 'U_POST_ACTION' => "$url&i=$id&mode=post_details", // Use this for action parameters + 'U_APPROVE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&p=$post_id&f={$post_info['forum_id']}"), + + 'S_CAN_VIEWIP' => $auth->acl_get('m_info', $post_info['forum_id']), + 'S_CAN_CHGPOSTER' => $auth->acl_get('m_chgposter', $post_info['forum_id']), + 'S_CAN_LOCK_POST' => $auth->acl_get('m_lock', $post_info['forum_id']), + 'S_CAN_DELETE_POST' => $auth->acl_get('m_delete', $post_info['forum_id']), + + 'S_POST_REPORTED' => ($post_info['post_reported']) ? true : false, + 'S_POST_UNAPPROVED' => ($post_info['post_visibility'] == ITEM_UNAPPROVED || $post_info['post_visibility'] == ITEM_REAPPROVE) ? true : false, + 'S_POST_DELETED' => ($post_info['post_visibility'] == ITEM_DELETED) ? true : false, + 'S_POST_LOCKED' => ($post_info['post_edit_locked']) ? true : false, + 'S_USER_NOTES' => true, + 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, + 'DELETED_MESSAGE' => $l_deleted_by, + 'DELETE_REASON' => $post_info['post_delete_reason'], + + 'U_EDIT' => ($auth->acl_get('m_edit', $post_info['forum_id'])) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=edit&f={$post_info['forum_id']}&p={$post_info['post_id']}") : '', + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=mcp_chgposter&field=username&select_single=true'), + 'U_MCP_APPROVE' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&f=' . $post_info['forum_id'] . '&p=' . $post_id), + 'U_MCP_REPORT' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&f=' . $post_info['forum_id'] . '&p=' . $post_id), + 'U_MCP_USER_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $post_info['user_id']), + 'U_MCP_WARN_USER' => ($auth->acl_get('m_warn')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user&u=' . $post_info['user_id']) : '', + 'U_VIEW_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $post_info['forum_id'] . '&p=' . $post_info['post_id'] . '#p' . $post_info['post_id']), + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $post_info['forum_id'] . '&t=' . $post_info['topic_id']), + + 'MINI_POST_IMG' => ($post_unread) ? $user->img('icon_post_target_unread', 'UNREAD_POST') : $user->img('icon_post_target', 'POST'), + + 'RETURN_TOPIC' => sprintf($user->lang['RETURN_TOPIC'], '", ''), + 'RETURN_FORUM' => sprintf($user->lang['RETURN_FORUM'], '', ''), + 'REPORTED_IMG' => $user->img('icon_topic_reported', $user->lang['POST_REPORTED']), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', $user->lang['POST_UNAPPROVED']), + 'DELETED_IMG' => $user->img('icon_topic_deleted', $user->lang['POST_DELETED']), + 'EDIT_IMG' => $user->img('icon_post_edit', $user->lang['EDIT_POST']), + 'SEARCH_IMG' => $user->img('icon_user_search', $user->lang['SEARCH']), + + 'POST_AUTHOR_FULL' => get_username_string('full', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'POST_AUTHOR_COLOUR' => get_username_string('colour', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'POST_AUTHOR' => get_username_string('username', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'U_POST_AUTHOR' => get_username_string('profile', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + + 'POST_PREVIEW' => $message, + 'POST_SUBJECT' => $post_info['post_subject'], + 'POST_DATE' => $user->format_date($post_info['post_time']), + 'POST_IP' => $post_info['poster_ip'], + 'POST_IPADDR' => ($auth->acl_get('m_info', $post_info['forum_id']) && $request->variable('lookup', '')) ? @gethostbyaddr($post_info['poster_ip']) : '', + 'POST_ID' => $post_info['post_id'], + 'SIGNATURE' => $post_info['user_sig'], + + 'U_LOOKUP_IP' => ($auth->acl_get('m_info', $post_info['forum_id'])) ? "$url&i=$id&mode=$mode&lookup={$post_info['poster_ip']}#ip" : '', + 'U_WHOIS' => ($auth->acl_get('m_info', $post_info['forum_id'])) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&mode=$mode&action=whois&p=$post_id&ip={$post_info['poster_ip']}") : '', + ); + + $s_additional_opts = false; + + /** + * Event to add/modify MCP post template data + * + * @event core.mcp_post_template_data + * @var array post_info Array with the post information + * @var array mcp_post_template_data Array with the MCP post template data + * @var array attachments Array with the post attachments, if any + * @var bool s_additional_opts Must be set to true in extension if additional options are presented in MCP post panel + * @since 3.1.5-RC1 + */ + $vars = array( + 'post_info', + 'mcp_post_template_data', + 'attachments', + 's_additional_opts', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_post_template_data', compact($vars))); + + $template->assign_vars($mcp_post_template_data); + $template->assign_var('S_MCP_POST_ADDITIONAL_OPTS', $s_additional_opts); + + unset($mcp_post_template_data); + + // Get User Notes + $log_data = array(); + $log_count = false; + view_log('user', $log_data, $log_count, $config['posts_per_page'], 0, 0, 0, $post_info['user_id']); + + if (!empty($log_data)) + { + $template->assign_var('S_USER_NOTES', true); + + foreach ($log_data as $row) + { + $template->assign_block_vars('usernotes', array( + 'REPORT_BY' => $row['username_full'], + 'REPORT_AT' => $user->format_date($row['time']), + 'ACTION' => $row['action'], + 'ID' => $row['id']) + ); + } + } + + // Get Reports + if ($auth->acl_get('m_report', $post_info['forum_id'])) + { + $sql = 'SELECT r.*, re.*, u.user_id, u.username + FROM ' . REPORTS_TABLE . ' r, ' . USERS_TABLE . ' u, ' . REPORTS_REASONS_TABLE . " re + WHERE r.post_id = $post_id + AND r.reason_id = re.reason_id + AND u.user_id = r.user_id + ORDER BY r.report_time DESC"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $template->assign_var('S_SHOW_REPORTS', true); + + do + { + // If the reason is defined within the language file, we will use the localized version, else just use the database entry... + if (isset($user->lang['report_reasons']['TITLE'][strtoupper($row['reason_title'])]) && isset($user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])])) + { + $row['reson_description'] = $user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])]; + $row['reason_title'] = $user->lang['report_reasons']['TITLE'][strtoupper($row['reason_title'])]; + } + + $template->assign_block_vars('reports', array( + 'REPORT_ID' => $row['report_id'], + 'REASON_TITLE' => $row['reason_title'], + 'REASON_DESC' => $row['reason_description'], + 'REPORTER' => get_username_string('username', $row['user_id'], $row['username']), + 'U_REPORTER' => get_username_string('profile', $row['user_id'], $row['username']), + 'USER_NOTIFY' => ($row['user_notify']) ? true : false, + 'REPORT_TIME' => $user->format_date($row['report_time']), + 'REPORT_TEXT' => bbcode_nl2br(trim($row['report_text'])), + )); + } + while ($row = $db->sql_fetchrow($result)); + } + $db->sql_freeresult($result); + } + + // Get IP + if ($auth->acl_get('m_info', $post_info['forum_id'])) + { + /** @var \phpbb\pagination $pagination */ + $pagination = $phpbb_container->get('pagination'); + + $rdns_ip_num = $request->variable('rdns', ''); + $start_users = $request->variable('start_users', 0); + + if ($rdns_ip_num != 'all') + { + $template->assign_vars(array( + 'U_LOOKUP_ALL' => "$url&i=main&mode=post_details&rdns=all") + ); + } + + $num_users = false; + if ($start_users) + { + $num_users = phpbb_get_num_posters_for_ip($db, $post_info['poster_ip']); + $start_users = $pagination->validate_start($start_users, $config['posts_per_page'], $num_users); + } + + // Get other users who've posted under this IP + $sql = 'SELECT poster_id, COUNT(poster_id) as postings + FROM ' . POSTS_TABLE . " + WHERE poster_ip = '" . $db->sql_escape($post_info['poster_ip']) . "' + AND poster_id <> " . (int) $post_info['poster_id'] . " + GROUP BY poster_id + ORDER BY postings DESC, poster_id ASC"; + $result = $db->sql_query_limit($sql, $config['posts_per_page'], $start_users); + + $page_users = 0; + while ($row = $db->sql_fetchrow($result)) + { + $page_users++; + $users_ary[$row['poster_id']] = $row; + } + $db->sql_freeresult($result); + + if ($page_users == $config['posts_per_page'] || $start_users) + { + if ($num_users === false) + { + $num_users = phpbb_get_num_posters_for_ip($db, $post_info['poster_ip']); + } + + $pagination->generate_template_pagination( + $url . '&i=main&mode=post_details', + 'pagination', + 'start_users', + $num_users, + $config['posts_per_page'], + $start_users + ); + } + + if (count($users_ary)) + { + // Get the usernames + $sql = 'SELECT user_id, username + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', array_keys($users_ary)); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $users_ary[$row['user_id']]['username'] = $row['username']; + $usernames_ary[utf8_clean_string($row['username'])] = $users_ary[$row['user_id']]; + } + $db->sql_freeresult($result); + + foreach ($users_ary as $user_id => $user_row) + { + $template->assign_block_vars('userrow', array( + 'USERNAME' => get_username_string('username', $user_id, $user_row['username']), + 'NUM_POSTS' => $user_row['postings'], + 'L_POST_S' => ($user_row['postings'] == 1) ? $user->lang['POST'] : $user->lang['POSTS'], + + 'U_PROFILE' => get_username_string('profile', $user_id, $user_row['username']), + 'U_SEARCHPOSTS' => append_sid("{$phpbb_root_path}search.$phpEx", 'author_id=' . $user_id . '&sr=topics')) + ); + } + } + + // Get other IP's this user has posted under + + // A compound index on poster_id, poster_ip (posts table) would help speed up this query a lot, + // but the extra size is only valuable if there are persons having more than a thousands posts. + // This is better left to the really really big forums. + $start_ips = $request->variable('start_ips', 0); + + $num_ips = false; + if ($start_ips) + { + $num_ips = phpbb_get_num_ips_for_poster($db, $post_info['poster_id']); + $start_ips = $pagination->validate_start($start_ips, $config['posts_per_page'], $num_ips); + } + + $sql = 'SELECT poster_ip, COUNT(poster_ip) AS postings + FROM ' . POSTS_TABLE . ' + WHERE poster_id = ' . $post_info['poster_id'] . " + GROUP BY poster_ip + ORDER BY postings DESC, poster_ip ASC"; + $result = $db->sql_query_limit($sql, $config['posts_per_page'], $start_ips); + + $page_ips = 0; + while ($row = $db->sql_fetchrow($result)) + { + $page_ips++; + $hostname = (($rdns_ip_num == $row['poster_ip'] || $rdns_ip_num == 'all') && $row['poster_ip']) ? @gethostbyaddr($row['poster_ip']) : ''; + + $template->assign_block_vars('iprow', array( + 'IP' => $row['poster_ip'], + 'HOSTNAME' => $hostname, + 'NUM_POSTS' => $row['postings'], + 'L_POST_S' => ($row['postings'] == 1) ? $user->lang['POST'] : $user->lang['POSTS'], + + 'U_LOOKUP_IP' => ($rdns_ip_num == $row['poster_ip'] || $rdns_ip_num == 'all') ? '' : "$url&i=$id&mode=post_details&rdns={$row['poster_ip']}#ip", + 'U_WHOIS' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&mode=$mode&action=whois&p=$post_id&ip={$row['poster_ip']}")) + ); + } + $db->sql_freeresult($result); + + if ($page_ips == $config['posts_per_page'] || $start_ips) + { + if ($num_ips === false) + { + $num_ips = phpbb_get_num_ips_for_poster($db, $post_info['poster_id']); + } + + $pagination->generate_template_pagination( + $url . '&i=main&mode=post_details', + 'pagination_ips', + 'start_ips', + $num_ips, + $config['posts_per_page'], + $start_ips + ); + } + + $user_select = ''; + + if (count($usernames_ary)) + { + ksort($usernames_ary); + + foreach ($usernames_ary as $row) + { + $user_select .= '\n"; + } + } + + $template->assign_var('S_USER_SELECT', $user_select); + } + +} + +/** + * Get the number of posters for a given ip + * + * @param \phpbb\db\driver\driver_interface $db DBAL interface + * @param string $poster_ip IP + * @return int Number of posters + */ +function phpbb_get_num_posters_for_ip(\phpbb\db\driver\driver_interface $db, $poster_ip) +{ + $sql = 'SELECT COUNT(DISTINCT poster_id) as num_users + FROM ' . POSTS_TABLE . " + WHERE poster_ip = '" . $db->sql_escape($poster_ip) . "'"; + $result = $db->sql_query($sql); + $num_users = (int) $db->sql_fetchfield('num_users'); + $db->sql_freeresult($result); + + return $num_users; +} + +/** + * Get the number of ips for a given poster + * + * @param \phpbb\db\driver\driver_interface $db + * @param int $poster_id Poster user ID + * @return int Number of IPs for given poster + */ +function phpbb_get_num_ips_for_poster(\phpbb\db\driver\driver_interface $db, $poster_id) +{ + $sql = 'SELECT COUNT(DISTINCT poster_ip) as num_ips + FROM ' . POSTS_TABLE . ' + WHERE poster_id = ' . (int) $poster_id; + $result = $db->sql_query($sql); + $num_ips = (int) $db->sql_fetchfield('num_ips'); + $db->sql_freeresult($result); + + return $num_ips; +} + +/** +* Change a post's poster +*/ +function change_poster(&$post_info, $userdata) +{ + global $auth, $db, $config, $phpbb_root_path, $phpEx, $user, $phpbb_log, $phpbb_dispatcher; + + if (empty($userdata) || $userdata['user_id'] == $post_info['user_id']) + { + return; + } + + $post_id = $post_info['post_id']; + + $sql = 'UPDATE ' . POSTS_TABLE . " + SET poster_id = {$userdata['user_id']} + WHERE post_id = $post_id"; + $db->sql_query($sql); + + // Resync topic/forum if needed + if ($post_info['topic_last_post_id'] == $post_id || $post_info['forum_last_post_id'] == $post_id || $post_info['topic_first_post_id'] == $post_id) + { + sync('topic', 'topic_id', $post_info['topic_id'], false, false); + sync('forum', 'forum_id', $post_info['forum_id'], false, false); + } + + // Adjust post counts... only if the post is approved (else, it was not added the users post count anyway) + if ($post_info['post_postcount'] && $post_info['post_visibility'] == ITEM_APPROVED) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = user_posts - 1 + WHERE user_id = ' . $post_info['user_id'] .' + AND user_posts > 0'; + $db->sql_query($sql); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_posts = user_posts + 1 + WHERE user_id = ' . $userdata['user_id']; + $db->sql_query($sql); + } + + // Add posted to information for this topic for the new user + markread('post', $post_info['forum_id'], $post_info['topic_id'], time(), $userdata['user_id']); + + // Remove the dotted topic option if the old user has no more posts within this topic + if ($config['load_db_track'] && $post_info['user_id'] != ANONYMOUS) + { + $sql = 'SELECT topic_id + FROM ' . POSTS_TABLE . ' + WHERE topic_id = ' . $post_info['topic_id'] . ' + AND poster_id = ' . $post_info['user_id']; + $result = $db->sql_query_limit($sql, 1); + $topic_id = (int) $db->sql_fetchfield('topic_id'); + $db->sql_freeresult($result); + + if (!$topic_id) + { + $sql = 'DELETE FROM ' . TOPICS_POSTED_TABLE . ' + WHERE user_id = ' . $post_info['user_id'] . ' + AND topic_id = ' . $post_info['topic_id']; + $db->sql_query($sql); + } + } + + // change the poster_id within the attachments table, else the data becomes out of sync and errors displayed because of wrong ownership + if ($post_info['post_attachment']) + { + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET poster_id = ' . $userdata['user_id'] . ' + WHERE poster_id = ' . $post_info['user_id'] . ' + AND post_msg_id = ' . $post_info['post_id'] . ' + AND topic_id = ' . $post_info['topic_id']; + $db->sql_query($sql); + } + + // refresh search cache of this post + $search_type = $config['search_type']; + + if (class_exists($search_type)) + { + // We do some additional checks in the module to ensure it can actually be utilised + $error = false; + $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher); + + if (!$error && method_exists($search, 'destroy_cache')) + { + $search->destroy_cache(array(), array($post_info['user_id'], $userdata['user_id'])); + } + } + + $from_username = $post_info['username']; + $to_username = $userdata['username']; + + /** + * This event allows you to perform additional tasks after changing a post's poster + * + * @event core.mcp_change_poster_after + * @var array userdata Information on a post's new poster + * @var array post_info Information on the affected post + * @since 3.1.6-RC1 + * @changed 3.1.7-RC1 Change location to prevent post_info from being set to the new post information + */ + $vars = array('userdata', 'post_info'); + extract($phpbb_dispatcher->trigger_event('core.mcp_change_poster_after', compact($vars))); + + // Renew post info + $post_info = phpbb_get_post_data(array($post_id), false, true); + + if (!count($post_info)) + { + trigger_error('POST_NOT_EXIST'); + } + + $post_info = $post_info[$post_id]; + + // Now add log entry + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_MCP_CHANGE_POSTER', false, array( + 'forum_id' => $post_info['forum_id'], + 'topic_id' => $post_info['topic_id'], + 'post_id' => $post_info['post_id'], + $post_info['topic_title'], + $from_username, + $to_username + )); +} diff --git a/includes/mcp/mcp_queue.php b/includes/mcp/mcp_queue.php new file mode 100644 index 0000000..dec583f --- /dev/null +++ b/includes/mcp/mcp_queue.php @@ -0,0 +1,1530 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* mcp_queue +* Handling the moderation queue +*/ +class mcp_queue +{ + var $p_master; + var $u_action; + + public function __construct($p_master) + { + $this->p_master = $p_master; + } + + public function main($id, $mode) + { + global $auth, $db, $user, $template, $request; + global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container; + global $phpbb_dispatcher; + + include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + + $forum_id = $request->variable('f', 0); + $start = $request->variable('start', 0); + + $this->page_title = 'MCP_QUEUE'; + + switch ($action) + { + case 'approve': + case 'restore': + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $post_id_list = $request->variable('post_id_list', array(0)); + $topic_id_list = $request->variable('topic_id_list', array(0)); + + if (!empty($post_id_list)) + { + self::approve_posts($action, $post_id_list, 'queue', $mode); + } + else if (!empty($topic_id_list)) + { + self::approve_topics($action, $topic_id_list, 'queue', $mode); + } + else + { + trigger_error('NO_POST_SELECTED'); + } + break; + + case 'delete': + $post_id_list = $request->variable('post_id_list', array(0)); + $topic_id_list = $request->variable('topic_id_list', array(0)); + $delete_reason = $request->variable('delete_reason', '', true); + + if (!empty($post_id_list)) + { + if (!function_exists('mcp_delete_post')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/mcp/mcp_main.' . $phpEx); + } + mcp_delete_post($post_id_list, false, $delete_reason, $action); + } + else if (!empty($topic_id_list)) + { + if (!function_exists('mcp_delete_topic')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/mcp/mcp_main.' . $phpEx); + } + mcp_delete_topic($topic_id_list, false, $delete_reason, $action); + } + else + { + trigger_error('NO_POST_SELECTED'); + } + break; + + case 'disapprove': + $post_id_list = $request->variable('post_id_list', array(0)); + $topic_id_list = $request->variable('topic_id_list', array(0)); + + if (!empty($topic_id_list) && $mode == 'deleted_topics') + { + if (!function_exists('mcp_delete_topic')) + { + global $phpbb_root_path, $phpEx; + include($phpbb_root_path . 'includes/mcp/mcp_main.' . $phpEx); + } + mcp_delete_topic($topic_id_list, false, '', 'disapprove'); + return; + } + + if (!class_exists('messenger')) + { + include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + } + + if (!empty($topic_id_list)) + { + $post_visibility = ($mode == 'deleted_topics') ? ITEM_DELETED : array(ITEM_UNAPPROVED, ITEM_REAPPROVE); + $sql = 'SELECT post_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_visibility', $post_visibility) . ' + AND ' . $db->sql_in_set('topic_id', $topic_id_list); + $result = $db->sql_query($sql); + + $post_id_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + $post_id_list[] = (int) $row['post_id']; + } + $db->sql_freeresult($result); + } + + if (!empty($post_id_list)) + { + self::disapprove_posts($post_id_list, 'queue', $mode); + } + else + { + trigger_error('NO_POST_SELECTED'); + } + break; + } + + switch ($mode) + { + case 'approve_details': + + $this->tpl_name = 'mcp_post'; + + $user->add_lang(array('posting', 'viewtopic')); + + $post_id = $request->variable('p', 0); + $topic_id = $request->variable('t', 0); + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + if ($topic_id) + { + $topic_info = phpbb_get_topic_data(array($topic_id), 'm_approve'); + if (isset($topic_info[$topic_id]['topic_first_post_id'])) + { + $post_id = (int) $topic_info[$topic_id]['topic_first_post_id']; + + $phpbb_notifications->mark_notifications('topic_in_queue', $topic_id, $user->data['user_id']); + } + else + { + $topic_id = 0; + } + } + + $phpbb_notifications->mark_notifications('post_in_queue', $post_id, $user->data['user_id']); + + $post_info = phpbb_get_post_data(array($post_id), 'm_approve', true); + + if (!count($post_info)) + { + trigger_error('NO_POST_SELECTED'); + } + + $post_info = $post_info[$post_id]; + + if ($post_info['topic_first_post_id'] != $post_id && topic_review($post_info['topic_id'], $post_info['forum_id'], 'topic_review', 0, false)) + { + $template->assign_vars(array( + 'S_TOPIC_REVIEW' => true, + 'S_BBCODE_ALLOWED' => $post_info['enable_bbcode'], + 'TOPIC_TITLE' => $post_info['topic_title'], + )); + } + + $attachments = $topic_tracking_info = array(); + + // Get topic tracking info + if ($config['load_db_lastread']) + { + $tmp_topic_data = array($post_info['topic_id'] => $post_info); + $topic_tracking_info = get_topic_tracking($post_info['forum_id'], $post_info['topic_id'], $tmp_topic_data, array($post_info['forum_id'] => $post_info['forum_mark_time'])); + unset($tmp_topic_data); + } + else + { + $topic_tracking_info = get_complete_topic_tracking($post_info['forum_id'], $post_info['topic_id']); + } + + $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; + + // Process message, leave it uncensored + $parse_flags = ($post_info['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($post_info['post_text'], $post_info['bbcode_uid'], $post_info['bbcode_bitfield'], $parse_flags, false); + + if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE post_msg_id = ' . $post_id . ' + AND in_message = 0 + ORDER BY filetime DESC, post_msg_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[] = $row; + } + $db->sql_freeresult($result); + + if (count($attachments)) + { + $update_count = array(); + parse_attachments($post_info['forum_id'], $message, $attachments, $update_count); + } + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (!empty($attachments)) + { + $template->assign_var('S_HAS_ATTACHMENTS', true); + + foreach ($attachments as $attachment) + { + $template->assign_block_vars('attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment, + )); + } + } + } + + // Deleting information + if ($post_info['post_visibility'] == ITEM_DELETED && $post_info['post_delete_user']) + { + // User having deleted the post also being the post author? + if (!$post_info['post_delete_user'] || $post_info['post_delete_user'] == $post_info['poster_id']) + { + $display_username = get_username_string('full', $post_info['poster_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']); + } + else + { + $sql = 'SELECT u.user_id, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE p.post_id = ' . $post_info['post_id'] . ' + AND p.post_delete_user = u.user_id'; + $result = $db->sql_query($sql); + $post_delete_userinfo = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + $display_username = get_username_string('full', $post_info['post_delete_user'], $post_delete_userinfo['username'], $post_delete_userinfo['user_colour']); + } + + $l_deleted_by = $user->lang('DELETED_INFORMATION', $display_username, $user->format_date($post_info['post_delete_time'], false, true)); + } + else + { + $l_deleted_by = ''; + } + + $post_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $post_info['forum_id'] . '&p=' . $post_info['post_id'] . '#p' . $post_info['post_id']); + $topic_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $post_info['forum_id'] . '&t=' . $post_info['topic_id']); + + $post_data = array( + 'S_MCP_QUEUE' => true, + 'U_APPROVE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&p=$post_id&f=$forum_id"), + 'S_CAN_DELETE_POST' => $auth->acl_get('m_delete', $post_info['forum_id']), + 'S_CAN_VIEWIP' => $auth->acl_get('m_info', $post_info['forum_id']), + 'S_POST_REPORTED' => $post_info['post_reported'], + 'S_POST_UNAPPROVED' => $post_info['post_visibility'] == ITEM_UNAPPROVED || $post_info['post_visibility'] == ITEM_REAPPROVE, + 'S_POST_LOCKED' => $post_info['post_edit_locked'], + 'S_USER_NOTES' => true, + 'S_POST_DELETED' => ($post_info['post_visibility'] == ITEM_DELETED), + 'DELETED_MESSAGE' => $l_deleted_by, + 'DELETE_REASON' => $post_info['post_delete_reason'], + + 'U_EDIT' => ($auth->acl_get('m_edit', $post_info['forum_id'])) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=edit&f={$post_info['forum_id']}&p={$post_info['post_id']}") : '', + 'U_MCP_APPROVE' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&f=' . $post_info['forum_id'] . '&p=' . $post_id), + 'U_MCP_REPORT' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&f=' . $post_info['forum_id'] . '&p=' . $post_id), + 'U_MCP_USER_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $post_info['user_id']), + 'U_MCP_WARN_USER' => ($auth->acl_get('m_warn')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user&u=' . $post_info['user_id']) : '', + 'U_VIEW_POST' => $post_url, + 'U_VIEW_TOPIC' => $topic_url, + + 'MINI_POST_IMG' => ($post_unread) ? $user->img('icon_post_target_unread', 'UNREAD_POST') : $user->img('icon_post_target', 'POST'), + + 'RETURN_QUEUE' => sprintf($user->lang['RETURN_QUEUE'], '', ''), + 'RETURN_POST' => sprintf($user->lang['RETURN_POST'], '', ''), + 'RETURN_TOPIC_SIMPLE' => sprintf($user->lang['RETURN_TOPIC_SIMPLE'], '', ''), + 'REPORTED_IMG' => $user->img('icon_topic_reported', $user->lang['POST_REPORTED']), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', $user->lang['POST_UNAPPROVED']), + 'EDIT_IMG' => $user->img('icon_post_edit', $user->lang['EDIT_POST']), + + 'POST_AUTHOR_FULL' => get_username_string('full', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'POST_AUTHOR_COLOUR' => get_username_string('colour', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'POST_AUTHOR' => get_username_string('username', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'U_POST_AUTHOR' => get_username_string('profile', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + + 'POST_PREVIEW' => $message, + 'POST_SUBJECT' => $post_info['post_subject'], + 'POST_DATE' => $user->format_date($post_info['post_time']), + 'POST_IP' => $post_info['poster_ip'], + 'POST_IPADDR' => ($auth->acl_get('m_info', $post_info['forum_id']) && $request->variable('lookup', '')) ? @gethostbyaddr($post_info['poster_ip']) : '', + 'POST_ID' => $post_info['post_id'], + 'S_FIRST_POST' => ($post_info['topic_first_post_id'] == $post_id), + + 'U_LOOKUP_IP' => ($auth->acl_get('m_info', $post_info['forum_id'])) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&f=' . $post_info['forum_id'] . '&p=' . $post_id . '&lookup=' . $post_info['poster_ip']) . '#ip' : '', + ); + + /** + * Alter post awaiting approval template before it is rendered + * + * @event core.mcp_queue_approve_details_template + * @var int post_id Post ID + * @var int topic_id Topic ID + * @var array topic_info Topic data + * @var array post_info Post data + * @var array post_data Post template data + * @var string message Post message + * @var string post_url Post URL + * @var string topic_url Topic URL + * @since 3.2.2-RC1 + */ + $vars = array( + 'post_id', + 'topic_id', + 'topic_info', + 'post_info', + 'post_data', + 'message', + 'post_url', + 'topic_url', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_queue_approve_details_template', compact($vars))); + + $template->assign_vars($post_data); + + break; + + case 'unapproved_topics': + case 'unapproved_posts': + case 'deleted_topics': + case 'deleted_posts': + $m_perm = 'm_approve'; + $is_topics = ($mode == 'unapproved_topics' || $mode == 'deleted_topics') ? true : false; + $is_restore = ($mode == 'deleted_posts' || $mode == 'deleted_topics') ? true : false; + $visibility_const = (!$is_restore) ? array(ITEM_UNAPPROVED, ITEM_REAPPROVE) : ITEM_DELETED; + + $user->add_lang(array('viewtopic', 'viewforum')); + + $topic_id = $request->variable('t', 0); + $forum_info = array(); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + if ($topic_id) + { + $topic_info = phpbb_get_topic_data(array($topic_id)); + + if (!count($topic_info)) + { + trigger_error('TOPIC_NOT_EXIST'); + } + + $topic_info = $topic_info[$topic_id]; + $forum_id = $topic_info['forum_id']; + } + + $forum_list_approve = get_forum_list($m_perm, false, true); + $forum_list_read = array_flip(get_forum_list('f_read', true, true)); // Flipped so we can isset() the forum IDs + + // Remove forums we cannot read + foreach ($forum_list_approve as $k => $forum_data) + { + if (!isset($forum_list_read[$forum_data['forum_id']])) + { + unset($forum_list_approve[$k]); + } + } + unset($forum_list_read); + + if (!$forum_id) + { + $forum_list = array(); + foreach ($forum_list_approve as $row) + { + $forum_list[] = $row['forum_id']; + } + + if (!count($forum_list)) + { + trigger_error('NOT_MODERATOR'); + } + + $sql = 'SELECT SUM(forum_topics_approved) as sum_forum_topics + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_list); + $result = $db->sql_query($sql); + $forum_info['forum_topics_approved'] = (int) $db->sql_fetchfield('sum_forum_topics'); + $db->sql_freeresult($result); + } + else + { + $forum_info = phpbb_get_forum_data(array($forum_id), $m_perm); + + if (!count($forum_info)) + { + trigger_error('NOT_MODERATOR'); + } + + $forum_list = $forum_id; + } + + $forum_options = ''; + foreach ($forum_list_approve as $row) + { + $forum_options .= ''; + } + + $sort_days = $total = 0; + $sort_key = $sort_dir = ''; + $sort_by_sql = $sort_order_sql = array(); + phpbb_mcp_sorting($mode, $sort_days, $sort_key, $sort_dir, $sort_by_sql, $sort_order_sql, $total, $forum_id, $topic_id); + + $limit_time_sql = ($sort_days) ? 'AND t.topic_last_post_time >= ' . (time() - ($sort_days * 86400)) : ''; + + $forum_names = array(); + + if (!$is_topics) + { + $sql = 'SELECT p.post_id + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t' . (($sort_order_sql[0] == 'u') ? ', ' . USERS_TABLE . ' u' : '') . ' + WHERE ' . $db->sql_in_set('p.forum_id', $forum_list) . ' + AND ' . $db->sql_in_set('p.post_visibility', $visibility_const) . ' + ' . (($sort_order_sql[0] == 'u') ? 'AND u.user_id = p.poster_id' : '') . ' + ' . (($topic_id) ? 'AND p.topic_id = ' . $topic_id : '') . " + AND t.topic_id = p.topic_id + AND (t.topic_visibility <> p.post_visibility + OR t.topic_delete_user = 0) + $limit_time_sql + ORDER BY $sort_order_sql"; + + /** + * Alter sql query to get posts in queue to be accepted + * + * @event core.mcp_queue_get_posts_query_before + * @var string sql Associative array with the query to be executed + * @var array forum_list List of forums that contain the posts + * @var int visibility_const Integer with one of the possible ITEM_* constant values + * @var int topic_id If topic_id not equal to 0, the topic id to filter the posts to display + * @var string limit_time_sql String with the SQL code to limit the time interval of the post (Note: May be empty string) + * @var string sort_order_sql String with the ORDER BY SQL code used in this query + * @since 3.1.0-RC3 + */ + $vars = array( + 'sql', + 'forum_list', + 'visibility_const', + 'topic_id', + 'limit_time_sql', + 'sort_order_sql', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_queue_get_posts_query_before', compact($vars))); + + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $i = 0; + $post_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $post_ids[] = $row['post_id']; + $row_num[$row['post_id']] = $i++; + } + $db->sql_freeresult($result); + + if (count($post_ids)) + { + $sql = 'SELECT t.topic_id, t.topic_title, t.forum_id, p.post_id, p.post_subject, p.post_username, p.poster_id, p.post_time, p.post_attachment, u.username, u.username_clean, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_ids) . ' + AND t.topic_id = p.topic_id + AND u.user_id = p.poster_id + ORDER BY ' . $sort_order_sql; + + /** + * Alter sql query to get information on all posts in queue + * + * @event core.mcp_queue_get_posts_for_posts_query_before + * @var string sql String with the query to be executed + * @var array forum_list List of forums that contain the posts + * @var int visibility_const Integer with one of the possible ITEM_* constant values + * @var int topic_id topic_id in the page request + * @var string limit_time_sql String with the SQL code to limit the time interval of the post (Note: May be empty string) + * @var string sort_order_sql String with the ORDER BY SQL code used in this query + * @since 3.2.3-RC2 + */ + $vars = array( + 'sql', + 'forum_list', + 'visibility_const', + 'topic_id', + 'limit_time_sql', + 'sort_order_sql', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_queue_get_posts_for_posts_query_before', compact($vars))); + + $result = $db->sql_query($sql); + + $post_data = $rowset = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_names[] = $row['forum_id']; + $post_data[$row['post_id']] = $row; + } + $db->sql_freeresult($result); + + foreach ($post_ids as $post_id) + { + $rowset[] = $post_data[$post_id]; + } + unset($post_data, $post_ids); + } + else + { + $rowset = array(); + } + } + else + { + $sql = 'SELECT t.forum_id, t.topic_id, t.topic_title, t.topic_title AS post_subject, t.topic_time AS post_time, t.topic_poster AS poster_id, t.topic_first_post_id AS post_id, t.topic_attachment AS post_attachment, t.topic_first_poster_name AS username, t.topic_first_poster_colour AS user_colour + FROM ' . TOPICS_TABLE . ' t + WHERE ' . $db->sql_in_set('forum_id', $forum_list) . ' + AND ' . $db->sql_in_set('topic_visibility', $visibility_const) . " + AND topic_delete_user <> 0 + $limit_time_sql + ORDER BY $sort_order_sql"; + + /** + * Alter sql query to get information on all topics in the list of forums provided. + * + * @event core.mcp_queue_get_posts_for_topics_query_before + * @var string sql String with the query to be executed + * @var array forum_list List of forums that contain the posts + * @var int visibility_const Integer with one of the possible ITEM_* constant values + * @var int topic_id topic_id in the page request + * @var string limit_time_sql String with the SQL code to limit the time interval of the post (Note: May be empty string) + * @var string sort_order_sql String with the ORDER BY SQL code used in this query + * @since 3.1.0-RC3 + */ + $vars = array( + 'sql', + 'forum_list', + 'visibility_const', + 'topic_id', + 'limit_time_sql', + 'sort_order_sql', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_queue_get_posts_for_topics_query_before', compact($vars))); + + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $rowset = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_names[] = $row['forum_id']; + $rowset[] = $row; + } + $db->sql_freeresult($result); + } + + if (count($forum_names)) + { + // Select the names for the forum_ids + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_names); + $result = $db->sql_query($sql, 3600); + + $forum_names = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forum_names[$row['forum_id']] = $row['forum_name']; + } + $db->sql_freeresult($result); + } + + foreach ($rowset as $row) + { + if (empty($row['post_username'])) + { + $row['post_username'] = $row['username'] ?: $user->lang['GUEST']; + } + + $post_row = array( + 'U_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . '&t=' . $row['topic_id']), + 'U_VIEWFORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']), + 'U_VIEWPOST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . '&p=' . $row['post_id']) . (($mode == 'unapproved_posts') ? '#p' . $row['post_id'] : ''), + 'U_VIEW_DETAILS' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&start=$start&mode=approve_details&f={$row['forum_id']}&p={$row['post_id']}" . (($mode == 'unapproved_topics') ? "&t={$row['topic_id']}" : '')), + + 'POST_AUTHOR_FULL' => get_username_string('full', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR_COLOUR' => get_username_string('colour', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR' => get_username_string('username', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'U_POST_AUTHOR' => get_username_string('profile', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + + 'POST_ID' => $row['post_id'], + 'TOPIC_ID' => $row['topic_id'], + 'FORUM_NAME' => $forum_names[$row['forum_id']], + 'POST_SUBJECT' => ($row['post_subject'] != '') ? $row['post_subject'] : $user->lang['NO_SUBJECT'], + 'TOPIC_TITLE' => $row['topic_title'], + 'POST_TIME' => $user->format_date($row['post_time']), + 'S_HAS_ATTACHMENTS' => $auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['post_attachment'], + ); + + /** + * Alter sql query to get information on all topics in the list of forums provided. + * + * @event core.mcp_queue_get_posts_modify_post_row + * @var array post_row Template variables for current post + * @var array row Post data + * @var array forum_names Forum names + * @since 3.2.3-RC2 + */ + $vars = array( + 'post_row', + 'row', + 'forum_names', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_queue_get_posts_modify_post_row', compact($vars))); + + $template->assign_block_vars('postrow', $post_row); + } + unset($rowset, $forum_names); + + $base_url = $this->u_action . "&f=$forum_id&st=$sort_days&sk=$sort_key&sd=$sort_dir"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); + + // Now display the page + $template->assign_vars(array( + 'L_DISPLAY_ITEMS' => (!$is_topics) ? $user->lang['DISPLAY_POSTS'] : $user->lang['DISPLAY_TOPICS'], + 'L_EXPLAIN' => $user->lang['MCP_QUEUE_' . strtoupper($mode) . '_EXPLAIN'], + 'L_TITLE' => $user->lang['MCP_QUEUE_' . strtoupper($mode)], + 'L_ONLY_TOPIC' => ($topic_id) ? sprintf($user->lang['ONLY_TOPIC'], $topic_info['topic_title']) : '', + + 'S_FORUM_OPTIONS' => $forum_options, + 'S_MCP_ACTION' => build_url(array('t', 'f', 'sd', 'st', 'sk')), + 'S_TOPICS' => $is_topics, + 'S_RESTORE' => $is_restore, + + 'TOPIC_ID' => $topic_id, + 'TOTAL' => $user->lang(((!$is_topics) ? 'VIEW_TOPIC_POSTS' : 'VIEW_FORUM_TOPICS'), (int) $total), + )); + + $this->tpl_name = 'mcp_queue'; + break; + } + } + + /** + * Approve/Restore posts + * + * @param $action string Action we perform on the posts ('approve' or 'restore') + * @param $post_id_list array IDs of the posts to approve/restore + * @param $id mixed Category of the current active module + * @param $mode string Active module + * @return null + */ + static public function approve_posts($action, $post_id_list, $id, $mode) + { + global $template, $user, $request, $phpbb_container, $phpbb_dispatcher; + global $phpEx, $phpbb_root_path, $phpbb_log; + + if (!phpbb_check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_approve'))) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + + $redirect = $request->variable('redirect', build_url(array('quickmod'))); + $redirect = reapply_sid($redirect); + $post_url = ''; + $approve_log = array(); + $num_topics = 0; + + $s_hidden_fields = build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'post_id_list' => $post_id_list, + 'action' => $action, + 'redirect' => $redirect, + )); + + $post_info = phpbb_get_post_data($post_id_list, 'm_approve'); + + if (confirm_box(true)) + { + $notify_poster = ($action == 'approve' && isset($_REQUEST['notify_poster'])); + + $topic_info = array(); + + // Group the posts by topic_id + foreach ($post_info as $post_id => $post_data) + { + if ($post_data['post_visibility'] == ITEM_APPROVED) + { + continue; + } + $topic_id = (int) $post_data['topic_id']; + + $topic_info[$topic_id]['posts'][] = (int) $post_id; + $topic_info[$topic_id]['forum_id'] = (int) $post_data['forum_id']; + + // Refresh the first post, if the time or id is older then the current one + if ($post_id <= $post_data['topic_first_post_id'] || $post_data['post_time'] <= $post_data['topic_time']) + { + $topic_info[$topic_id]['first_post'] = true; + } + + // Refresh the last post, if the time or id is newer then the current one + if ($post_id >= $post_data['topic_last_post_id'] || $post_data['post_time'] >= $post_data['topic_last_post_time']) + { + $topic_info[$topic_id]['last_post'] = true; + } + + $post_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f={$post_data['forum_id']}&t={$post_data['topic_id']}&p={$post_data['post_id']}") . '#p' . $post_data['post_id']; + + $approve_log[] = array( + 'forum_id' => $post_data['forum_id'], + 'topic_id' => $post_data['topic_id'], + 'post_id' => $post_id, + 'post_subject' => $post_data['post_subject'], + ); + } + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + foreach ($topic_info as $topic_id => $topic_data) + { + $phpbb_content_visibility->set_post_visibility(ITEM_APPROVED, $topic_data['posts'], $topic_id, $topic_data['forum_id'], $user->data['user_id'], time(), '', isset($topic_data['first_post']), isset($topic_data['last_post'])); + } + + foreach ($approve_log as $log_data) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_POST_' . strtoupper($action) . 'D', false, array( + 'forum_id' => $log_data['forum_id'], + 'topic_id' => $log_data['topic_id'], + 'post_id' => $log_data['post_id'], + $log_data['post_subject'] + )); + } + + // Only send out the mails, when the posts are being approved + if ($action == 'approve') + { + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + // Handle notifications + foreach ($post_info as $post_id => $post_data) + { + // A single topic approval may also happen here, so handle deleting the respective notification. + if (!$post_data['topic_posts_approved']) + { + $phpbb_notifications->delete_notifications('notification.type.topic_in_queue', $post_data['topic_id']); + + if ($post_data['post_visibility'] == ITEM_UNAPPROVED) + { + $phpbb_notifications->add_notifications(array('notification.type.topic'), $post_data); + } + if ($post_data['post_visibility'] != ITEM_APPROVED) + { + $num_topics++; + } + } + else + { + // Only add notifications, if we are not reapproving post + // When the topic was already approved, but was edited and + // now needs re-approval, we don't want to notify the users + // again. + if ($post_data['post_visibility'] == ITEM_UNAPPROVED) + { + $phpbb_notifications->add_notifications(array( + 'notification.type.bookmark', + 'notification.type.post', + ), $post_data); + } + } + $phpbb_notifications->add_notifications(array('notification.type.quote'), $post_data); + $phpbb_notifications->delete_notifications('notification.type.post_in_queue', $post_id); + + $phpbb_notifications->mark_notifications(array( + 'notification.type.quote', + 'notification.type.bookmark', + 'notification.type.post', + ), $post_data['post_id'], $user->data['user_id']); + + // Notify Poster? + if ($notify_poster) + { + if ($post_data['poster_id'] == ANONYMOUS) + { + continue; + } + + if (!$post_data['topic_posts_approved']) + { + $phpbb_notifications->add_notifications('notification.type.approve_topic', $post_data); + } + else + { + $phpbb_notifications->add_notifications('notification.type.approve_post', $post_data); + } + } + } + } + + if ($num_topics >= 1) + { + $success_msg = ($num_topics == 1) ? 'TOPIC_' . strtoupper($action) . 'D_SUCCESS' : 'TOPICS_' . strtoupper($action) . 'D_SUCCESS'; + } + else + { + $success_msg = (count($post_info) == 1) ? 'POST_' . strtoupper($action) . 'D_SUCCESS' : 'POSTS_' . strtoupper($action) . 'D_SUCCESS'; + } + + /** + * Perform additional actions during post(s) approval + * + * @event core.approve_posts_after + * @var string action Variable containing the action we perform on the posts ('approve' or 'restore') + * @var array post_info Array containing info for all posts being approved + * @var array topic_info Array containing info for all parent topics of the posts + * @var int num_topics Variable containing number of topics + * @var bool notify_poster Variable telling if the post should be notified or not + * @var string success_msg Variable containing the language key for the success message + * @var string redirect Variable containing the redirect url + * @since 3.1.4-RC1 + */ + $vars = array( + 'action', + 'post_info', + 'topic_info', + 'num_topics', + 'notify_poster', + 'success_msg', + 'redirect', + ); + extract($phpbb_dispatcher->trigger_event('core.approve_posts_after', compact($vars))); + + meta_refresh(3, $redirect); + $message = $user->lang[$success_msg]; + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $message, + 'REFRESH_DATA' => null, + 'visible' => true, + )); + } + $message .= '

' . $user->lang('RETURN_PAGE', '', ''); + + // If approving one post, also give links back to post... + if (count($post_info) == 1 && $post_url) + { + $message .= '

' . $user->lang('RETURN_POST', '', ''); + } + trigger_error($message); + } + else + { + $show_notify = false; + + if ($action == 'approve') + { + foreach ($post_info as $post_data) + { + if (!$post_data['topic_posts_approved']) + { + $num_topics++; + } + + if (!$show_notify && $post_data['poster_id'] != ANONYMOUS) + { + $show_notify = true; + } + } + } + + $template->assign_vars(array( + 'S_NOTIFY_POSTER' => $show_notify, + 'S_' . strtoupper($action) => true, + )); + + // Create the confirm box message + $action_msg = strtoupper($action); + $num_posts = count($post_id_list) - $num_topics; + if ($num_topics > 0 && $num_posts <= 0) + { + $action_msg .= '_TOPIC' . (($num_topics == 1) ? '' : 'S'); + } + else + { + $action_msg .= '_POST' . ((count($post_id_list) == 1) ? '' : 'S'); + } + confirm_box(false, $action_msg, $s_hidden_fields, 'mcp_approve.html'); + } + + redirect($redirect); + } + + /** + * Approve/Restore topics + * + * @param $action string Action we perform on the posts ('approve' or 'restore') + * @param $topic_id_list array IDs of the topics to approve/restore + * @param $id mixed Category of the current active module + * @param $mode string Active module + * @return null + */ + static public function approve_topics($action, $topic_id_list, $id, $mode) + { + global $db, $template, $user, $phpbb_log; + global $phpEx, $phpbb_root_path, $request, $phpbb_container, $phpbb_dispatcher; + + if (!phpbb_check_ids($topic_id_list, TOPICS_TABLE, 'topic_id', array('m_approve'))) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + + $redirect = $request->variable('redirect', build_url(array('quickmod'))); + $redirect = reapply_sid($redirect); + $success_msg = $topic_url = ''; + $approve_log = array(); + + $s_hidden_fields = build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'topic_id_list' => $topic_id_list, + 'action' => $action, + 'redirect' => $redirect, + )); + + $topic_info = phpbb_get_topic_data($topic_id_list, 'm_approve'); + + if (confirm_box(true)) + { + $notify_poster = ($action == 'approve' && isset($_REQUEST['notify_poster'])) ? true : false; + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + $first_post_ids = array(); + + foreach ($topic_info as $topic_id => $topic_data) + { + $phpbb_content_visibility->set_topic_visibility(ITEM_APPROVED, $topic_id, $topic_data['forum_id'], $user->data['user_id'], time(), ''); + $first_post_ids[$topic_id] = (int) $topic_data['topic_first_post_id']; + + $topic_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f={$topic_data['forum_id']}&t={$topic_id}"); + + $approve_log[] = array( + 'forum_id' => $topic_data['forum_id'], + 'topic_id' => $topic_data['topic_id'], + 'topic_title' => $topic_data['topic_title'], + ); + } + + if (count($topic_info) >= 1) + { + $success_msg = (count($topic_info) == 1) ? 'TOPIC_' . strtoupper($action) . 'D_SUCCESS' : 'TOPICS_' . strtoupper($action) . 'D_SUCCESS'; + } + + foreach ($approve_log as $log_data) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_TOPIC_' . strtoupper($action) . 'D', false, array( + 'forum_id' => $log_data['forum_id'], + 'topic_id' => $log_data['topic_id'], + $log_data['topic_title'] + )); + } + + // Only send out the mails, when the posts are being approved + if ($action == 'approve') + { + // Grab the first post text as it's needed for the quote notification. + $sql = 'SELECT topic_id, post_text + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_id', $first_post_ids); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_info[$row['topic_id']]['post_text'] = $row['post_text']; + } + $db->sql_freeresult($result); + + // Handle notifications + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + foreach ($topic_info as $topic_id => $topic_data) + { + $topic_data = array_merge($topic_data, array( + 'post_id' => $topic_data['topic_first_post_id'], + 'post_subject' => $topic_data['topic_title'], + 'post_time' => $topic_data['topic_time'], + 'poster_id' => $topic_data['topic_poster'], + 'post_username' => $topic_data['topic_first_poster_name'], + )); + + $phpbb_notifications->delete_notifications('notification.type.topic_in_queue', $topic_id); + + // Only add notifications, if we are not reapproving post + // When the topic was already approved, but was edited and + // now needs re-approval, we don't want to notify the users + // again. + if ($topic_data['topic_visibility'] == ITEM_UNAPPROVED) + { + $phpbb_notifications->add_notifications(array( + 'notification.type.quote', + 'notification.type.topic', + ), $topic_data); + } + + $phpbb_notifications->mark_notifications('quote', $topic_data['post_id'], $user->data['user_id']); + $phpbb_notifications->mark_notifications('topic', $topic_id, $user->data['user_id']); + + if ($notify_poster) + { + $phpbb_notifications->add_notifications('notification.type.approve_topic', $topic_data); + } + } + } + + /** + * Perform additional actions during topics(s) approval + * + * @event core.approve_topics_after + * @var string action Variable containing the action we perform on the posts ('approve' or 'restore') + * @var mixed topic_info Array containing info for all topics being approved + * @var array first_post_ids Array containing ids of all first posts + * @var bool notify_poster Variable telling if the poster should be notified or not + * @var string success_msg Variable containing the language key for the success message + * @var string redirect Variable containing the redirect url + * @since 3.1.4-RC1 + */ + $vars = array( + 'action', + 'topic_info', + 'first_post_ids', + 'notify_poster', + 'success_msg', + 'redirect', + ); + extract($phpbb_dispatcher->trigger_event('core.approve_topics_after', compact($vars))); + + meta_refresh(3, $redirect); + $message = $user->lang[$success_msg]; + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $message, + 'REFRESH_DATA' => null, + 'visible' => true, + )); + } + $message .= '

' . $user->lang('RETURN_PAGE', '', ''); + + // If approving one topic, also give links back to topic... + if (count($topic_info) == 1 && $topic_url) + { + $message .= '

' . $user->lang('RETURN_TOPIC', '', ''); + } + trigger_error($message); + } + else + { + $show_notify = false; + + if ($action == 'approve') + { + foreach ($topic_info as $topic_data) + { + if ($topic_data['topic_poster'] == ANONYMOUS) + { + continue; + } + else + { + $show_notify = true; + break; + } + } + } + + $template->assign_vars(array( + 'S_NOTIFY_POSTER' => $show_notify, + 'S_' . strtoupper($action) => true, + )); + + confirm_box(false, strtoupper($action) . '_TOPIC' . ((count($topic_id_list) == 1) ? '' : 'S'), $s_hidden_fields, 'mcp_approve.html'); + } + + redirect($redirect); + } + + /** + * Disapprove Post + * + * @param $post_id_list array IDs of the posts to disapprove/delete + * @param $id mixed Category of the current active module + * @param $mode string Active module + * @return null + */ + static public function disapprove_posts($post_id_list, $id, $mode) + { + global $db, $template, $user, $phpbb_container, $phpbb_dispatcher; + global $phpEx, $phpbb_root_path, $request, $phpbb_log; + + if (!phpbb_check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_approve'))) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + + $redirect = $request->variable('redirect', build_url(array('t', 'mode', 'quickmod')) . "&mode=$mode"); + $redirect = reapply_sid($redirect); + $reason = $request->variable('reason', '', true); + $reason_id = $request->variable('reason_id', 0); + $additional_msg = ''; + + $s_hidden_fields = build_hidden_fields(array( + 'i' => $id, + 'mode' => $mode, + 'post_id_list' => $post_id_list, + 'action' => 'disapprove', + 'redirect' => $redirect, + )); + + $notify_poster = $request->is_set('notify_poster'); + $disapprove_reason = ''; + + if ($reason_id) + { + $sql = 'SELECT reason_title, reason_description + FROM ' . REPORTS_REASONS_TABLE . " + WHERE reason_id = $reason_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row || (!$reason && strtolower($row['reason_title']) == 'other')) + { + $additional_msg = $user->lang['NO_REASON_DISAPPROVAL']; + + $request->overwrite('confirm', null, \phpbb\request\request_interface::POST); + $request->overwrite('confirm_key', null, \phpbb\request\request_interface::POST); + $request->overwrite('confirm_key', null, \phpbb\request\request_interface::REQUEST); + } + else + { + // If the reason is defined within the language file, we will use the localized version, else just use the database entry... + $disapprove_reason = (strtolower($row['reason_title']) != 'other') ? ((isset($user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])])) ? $user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])] : $row['reason_description']) : ''; + $disapprove_reason .= ($reason) ? "\n\n" . $reason : ''; + + if (isset($user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])])) + { + $disapprove_reason_lang = strtoupper($row['reason_title']); + } + } + } + + $post_info = phpbb_get_post_data($post_id_list, 'm_approve'); + + $is_disapproving = false; + foreach ($post_info as $post_id => $post_data) + { + if ($post_data['post_visibility'] == ITEM_DELETED) + { + continue; + } + + $is_disapproving = true; + } + + if (confirm_box(true)) + { + $disapprove_log_topics = $disapprove_log_posts = array(); + $topic_posts_unapproved = $post_disapprove_list = $topic_information = array(); + + // Build a list of posts to be disapproved and get the related topics real replies count + foreach ($post_info as $post_id => $post_data) + { + if ($mode === 'unapproved_topics' && $post_data['post_visibility'] == ITEM_APPROVED) + { + continue; + } + + $post_disapprove_list[$post_id] = $post_data['topic_id']; + if (!isset($topic_posts_unapproved[$post_data['topic_id']])) + { + $topic_information[$post_data['topic_id']] = $post_data; + $topic_posts_unapproved[$post_data['topic_id']] = 0; + } + $topic_posts_unapproved[$post_data['topic_id']]++; + } + + // Do not try to disapprove if no posts are selected + if (empty($post_disapprove_list)) + { + trigger_error('NO_POST_SELECTED'); + } + + // Now we build the log array + foreach ($post_disapprove_list as $post_id => $topic_id) + { + // If the count of disapproved posts for the topic is equal + // to the number of unapproved posts in the topic, and there are no different + // posts, we disapprove the hole topic + if ($topic_information[$topic_id]['topic_posts_approved'] == 0 && + $topic_information[$topic_id]['topic_posts_softdeleted'] == 0 && + $topic_information[$topic_id]['topic_posts_unapproved'] == $topic_posts_unapproved[$topic_id]) + { + // Don't write the log more than once for every topic + if (!isset($disapprove_log_topics[$topic_id])) + { + // Build disapproved topics log + $disapprove_log_topics[$topic_id] = array( + 'type' => 'topic', + 'post_subject' => $post_info[$post_id]['topic_title'], + 'forum_id' => $post_info[$post_id]['forum_id'], + 'topic_id' => 0, // useless to log a topic id, as it will be deleted + 'post_username' => ($post_info[$post_id]['poster_id'] == ANONYMOUS && !empty($post_info[$post_id]['post_username'])) ? $post_info[$post_id]['post_username'] : $post_info[$post_id]['username'], + ); + } + } + else + { + // Build disapproved posts log + $disapprove_log_posts[] = array( + 'type' => 'post', + 'post_subject' => $post_info[$post_id]['post_subject'], + 'forum_id' => $post_info[$post_id]['forum_id'], + 'topic_id' => $post_info[$post_id]['topic_id'], + 'post_username' => ($post_info[$post_id]['poster_id'] == ANONYMOUS && !empty($post_info[$post_id]['post_username'])) ? $post_info[$post_id]['post_username'] : $post_info[$post_id]['username'], + ); + + } + } + + // Get disapproved posts/topics counts separately + $num_disapproved_topics = count($disapprove_log_topics); + $num_disapproved_posts = count($disapprove_log_posts); + + // Build the whole log + $disapprove_log = array_merge($disapprove_log_topics, $disapprove_log_posts); + + // Unset unneeded arrays + unset($post_data, $disapprove_log_topics, $disapprove_log_posts); + + // Let's do the job - delete disapproved posts + if (count($post_disapprove_list)) + { + if (!function_exists('delete_posts')) + { + include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + } + + // We do not check for permissions here, because the moderator allowed approval/disapproval should be allowed to delete the disapproved posts + // Note: function delete_posts triggers related forums/topics sync, + // so we don't need to call update_post_information later and to adjust real topic replies or forum topics count manually + delete_posts('post_id', array_keys($post_disapprove_list)); + + foreach ($disapprove_log as $log_data) + { + if ($is_disapproving) + { + $l_log_message = ($log_data['type'] == 'topic') ? 'LOG_TOPIC_DISAPPROVED' : 'LOG_POST_DISAPPROVED'; + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, $l_log_message, false, array( + 'forum_id' => $log_data['forum_id'], + 'topic_id' => $log_data['topic_id'], + $log_data['post_subject'], + $disapprove_reason, + $log_data['post_username'] + )); + } + else + { + $l_log_message = ($log_data['type'] == 'topic') ? 'LOG_DELETE_TOPIC' : 'LOG_DELETE_POST'; + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, $l_log_message, false, array( + 'forum_id' => $log_data['forum_id'], + 'topic_id' => $log_data['topic_id'], + $log_data['post_subject'], + $log_data['post_username'] + )); + } + } + } + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $lang_reasons = array(); + + foreach ($post_info as $post_id => $post_data) + { + $disapprove_all_posts_in_topic = $topic_information[$topic_id]['topic_posts_approved'] == 0 && + $topic_information[$topic_id]['topic_posts_softdeleted'] == 0 && + $topic_information[$topic_id]['topic_posts_unapproved'] == $topic_posts_unapproved[$topic_id]; + + $phpbb_notifications->delete_notifications('notification.type.post_in_queue', $post_id); + + // Do we disapprove the whole topic? Remove potential notifications + if ($disapprove_all_posts_in_topic) + { + $phpbb_notifications->delete_notifications('notification.type.topic_in_queue', $post_data['topic_id']); + } + + // Notify Poster? + if ($notify_poster) + { + if ($post_data['poster_id'] == ANONYMOUS) + { + continue; + } + + $post_data['disapprove_reason'] = $disapprove_reason; + if (isset($disapprove_reason_lang)) + { + // Okay we need to get the reason from the posters language + if (!isset($lang_reasons[$post_data['user_lang']])) + { + // Assign the current users translation as the default, this is not ideal but getting the board default adds another layer of complexity. + $lang_reasons[$post_data['user_lang']] = $user->lang['report_reasons']['DESCRIPTION'][$disapprove_reason_lang]; + + // Only load up the language pack if the language is different to the current one + if ($post_data['user_lang'] != $user->lang_name && file_exists($phpbb_root_path . '/language/' . $post_data['user_lang'] . '/mcp.' . $phpEx)) + { + // Load up the language pack + $lang = array(); + @include($phpbb_root_path . '/language/' . basename($post_data['user_lang']) . '/mcp.' . $phpEx); + + // If we find the reason in this language pack use it + if (isset($lang['report_reasons']['DESCRIPTION'][$disapprove_reason_lang])) + { + $lang_reasons[$post_data['user_lang']] = $lang['report_reasons']['DESCRIPTION'][$disapprove_reason_lang]; + } + + unset($lang); // Free memory + } + } + + $post_data['disapprove_reason'] = $lang_reasons[$post_data['user_lang']]; + $post_data['disapprove_reason'] .= ($reason) ? "\n\n" . $reason : ''; + } + + if ($disapprove_all_posts_in_topic && $topic_information[$topic_id]['topic_posts_unapproved'] == 1) + { + // If there is only 1 post when disapproving the topic, + // we send the user a "disapprove topic" notification... + $phpbb_notifications->add_notifications('notification.type.disapprove_topic', $post_data); + } + else + { + // ... otherwise there are multiple unapproved posts and + // all of them are disapproved as posts. + $phpbb_notifications->add_notifications('notification.type.disapprove_post', $post_data); + } + } + } + + if ($num_disapproved_topics) + { + $success_msg = ($num_disapproved_topics == 1) ? 'TOPIC' : 'TOPICS'; + } + else + { + $success_msg = ($num_disapproved_posts == 1) ? 'POST' : 'POSTS'; + } + + if ($is_disapproving) + { + $success_msg .= '_DISAPPROVED_SUCCESS'; + } + else + { + $success_msg .= '_DELETED_SUCCESS'; + } + + // If we came from viewtopic, we try to go back to it. + if (strpos($redirect, $phpbb_root_path . 'viewtopic.' . $phpEx) === 0) + { + if ($num_disapproved_topics == 0) + { + // So we need to remove the post id part from the Url + $redirect = str_replace("&p={$post_id_list[0]}#p{$post_id_list[0]}", '', $redirect); + } + else + { + // However this is only possible if the topic still exists, + // Otherwise we go back to the viewforum page + $redirect = append_sid($phpbb_root_path . 'viewforum.' . $phpEx, 'f=' . $request->variable('f', 0)); + } + } + + /** + * Perform additional actions during post(s) disapproval + * + * @event core.disapprove_posts_after + * @var array post_info Array containing info for all posts being disapproved + * @var array topic_information Array containing information for the topics + * @var array topic_posts_unapproved Array containing list of topic ids and the count of disapproved posts in them + * @var array post_disapprove_list Array containing list of posts and their topic id + * @var int num_disapproved_topics Variable containing the number of disapproved topics + * @var int num_disapproved_posts Variable containing the number of disapproved posts + * @var array lang_reasons Array containing the language keys for reasons + * @var string disapprove_reason Variable containing the language key for the success message + * @var string disapprove_reason_lang Variable containing the language key for the success message + * @var bool is_disapproving Variable telling if anything is going to be disapproved + * @var bool notify_poster Variable telling if the post should be notified or not + * @var string success_msg Variable containing the language key for the success message + * @var string redirect Variable containing the redirect url + * @since 3.1.4-RC1 + */ + $vars = array( + 'post_info', + 'topic_information', + 'topic_posts_unapproved', + 'post_disapprove_list', + 'num_disapproved_topics', + 'num_disapproved_posts', + 'lang_reasons', + 'disapprove_reason', + 'disapprove_reason_lang', + 'is_disapproving', + 'notify_poster', + 'success_msg', + 'redirect', + ); + extract($phpbb_dispatcher->trigger_event('core.disapprove_posts_after', compact($vars))); + + unset($lang_reasons, $post_info, $disapprove_reason, $disapprove_reason_lang); + + meta_refresh(3, $redirect); + $message = $user->lang[$success_msg]; + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $message, + 'REFRESH_DATA' => null, + 'visible' => false, + )); + } + $message .= '

' . $user->lang('RETURN_PAGE', '', ''); + trigger_error($message); + } + else + { + $show_notify = false; + + foreach ($post_info as $post_data) + { + if ($post_data['poster_id'] == ANONYMOUS) + { + continue; + } + else + { + $show_notify = true; + break; + } + } + + $l_confirm_msg = 'DISAPPROVE_POST'; + $confirm_template = 'mcp_approve.html'; + if ($is_disapproving) + { + $phpbb_container->get('phpbb.report.report_reason_list_provider')->display_reasons($reason_id); + } + else + { + $user->add_lang('posting'); + + $l_confirm_msg = 'DELETE_POST_PERMANENTLY'; + $confirm_template = 'confirm_delete_body.html'; + } + $l_confirm_msg .= ((count($post_id_list) == 1) ? '' : 'S'); + + $template->assign_vars(array( + 'S_NOTIFY_POSTER' => $show_notify, + 'S_APPROVE' => false, + 'REASON' => ($is_disapproving) ? $reason : '', + 'ADDITIONAL_MSG' => $additional_msg, + )); + + confirm_box(false, $l_confirm_msg, $s_hidden_fields, $confirm_template); + } + + redirect($redirect); + } +} diff --git a/includes/mcp/mcp_reports.php b/includes/mcp/mcp_reports.php new file mode 100644 index 0000000..4600257 --- /dev/null +++ b/includes/mcp/mcp_reports.php @@ -0,0 +1,834 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* mcp_reports +* Handling the reports queue +*/ +class mcp_reports +{ + var $p_master; + var $u_action; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $auth, $db, $user, $template, $request; + global $config, $phpbb_root_path, $phpEx, $action, $phpbb_container, $phpbb_dispatcher; + + include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + + $forum_id = $request->variable('f', 0); + $start = $request->variable('start', 0); + + $this->page_title = 'MCP_REPORTS'; + + switch ($action) + { + case 'close': + case 'delete': + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $report_id_list = $request->variable('report_id_list', array(0)); + + if (!count($report_id_list)) + { + trigger_error('NO_REPORT_SELECTED'); + } + + close_report($report_id_list, $mode, $action); + + break; + } + + switch ($mode) + { + case 'report_details': + + $user->add_lang(array('posting', 'viewforum', 'viewtopic')); + + $post_id = $request->variable('p', 0); + + // closed reports are accessed by report id + $report_id = $request->variable('r', 0); + + $sql_ary = array( + 'SELECT' => 'r.post_id, r.user_id, r.report_id, r.report_closed, report_time, r.report_text, r.reported_post_text, r.reported_post_uid, r.reported_post_bitfield, r.reported_post_enable_magic_url, r.reported_post_enable_smilies, r.reported_post_enable_bbcode, rr.reason_title, rr.reason_description, u.username, u.username_clean, u.user_colour', + + 'FROM' => array( + REPORTS_TABLE => 'r', + REPORTS_REASONS_TABLE => 'rr', + USERS_TABLE => 'u', + ), + + 'WHERE' => (($report_id) ? 'r.report_id = ' . $report_id : "r.post_id = $post_id") . ' + AND rr.reason_id = r.reason_id + AND r.user_id = u.user_id + AND r.pm_id = 0', + + 'ORDER_BY' => 'report_closed ASC', + ); + + /** + * Allow changing the query to obtain the user-submitted report. + * + * @event core.mcp_reports_report_details_query_before + * @var array sql_ary The array in the format of the query builder with the query + * @var int forum_id The forum_id, the number in the f GET parameter + * @var int post_id The post_id of the report being viewed (if 0, it is meaningless) + * @var int report_id The report_id of the report being viewed + * @since 3.1.5-RC1 + */ + $vars = array( + 'sql_ary', + 'forum_id', + 'post_id', + 'report_id', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_reports_report_details_query_before', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query_limit($sql, 1); + $report = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + /** + * Allow changing the data obtained from the user-submitted report. + * + * @event core.mcp_reports_report_details_query_after + * @var array sql_ary The array in the format of the query builder with the query that had been executted + * @var int forum_id The forum_id, the number in the f GET parameter + * @var int post_id The post_id of the report being viewed (if 0, it is meaningless) + * @var int report_id The report_id of the report being viewed + * @var array report The query's resulting row. + * @since 3.1.5-RC1 + */ + $vars = array( + 'sql_ary', + 'forum_id', + 'post_id', + 'report_id', + 'report', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_reports_report_details_query_after', compact($vars))); + + if (!$report) + { + trigger_error('NO_REPORT'); + } + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $phpbb_notifications->mark_notifications('report_post', $post_id, $user->data['user_id']); + + if (!$report_id && $report['report_closed']) + { + trigger_error('REPORT_CLOSED'); + } + + $post_id = $report['post_id']; + $report_id = $report['report_id']; + + $parse_post_flags = $report['reported_post_enable_bbcode'] ? OPTION_FLAG_BBCODE : 0; + $parse_post_flags += $report['reported_post_enable_smilies'] ? OPTION_FLAG_SMILIES : 0; + $parse_post_flags += $report['reported_post_enable_magic_url'] ? OPTION_FLAG_LINKS : 0; + + $post_info = phpbb_get_post_data(array($post_id), 'm_report', true); + + if (!count($post_info)) + { + trigger_error('NO_REPORT_SELECTED'); + } + + $post_info = $post_info[$post_id]; + + $reason = array('title' => $report['reason_title'], 'description' => $report['reason_description']); + if (isset($user->lang['report_reasons']['TITLE'][strtoupper($reason['title'])]) && isset($user->lang['report_reasons']['DESCRIPTION'][strtoupper($reason['title'])])) + { + $reason['description'] = $user->lang['report_reasons']['DESCRIPTION'][strtoupper($reason['title'])]; + $reason['title'] = $user->lang['report_reasons']['TITLE'][strtoupper($reason['title'])]; + } + + if (topic_review($post_info['topic_id'], $post_info['forum_id'], 'topic_review', 0, false)) + { + $template->assign_vars(array( + 'S_TOPIC_REVIEW' => true, + 'S_BBCODE_ALLOWED' => $post_info['enable_bbcode'], + 'TOPIC_TITLE' => $post_info['topic_title'], + 'REPORTED_POST_ID' => $post_id, + )); + } + + $attachments = array(); + // Get topic tracking info + if ($config['load_db_lastread']) + { + $tmp_topic_data = array($post_info['topic_id'] => $post_info); + $topic_tracking_info = get_topic_tracking($post_info['forum_id'], $post_info['topic_id'], $tmp_topic_data, array($post_info['forum_id'] => $post_info['forum_mark_time'])); + unset($tmp_topic_data); + } + else + { + $topic_tracking_info = get_complete_topic_tracking($post_info['forum_id'], $post_info['topic_id']); + } + + $post_unread = (isset($topic_tracking_info[$post_info['topic_id']]) && $post_info['post_time'] > $topic_tracking_info[$post_info['topic_id']]) ? true : false; + $message = generate_text_for_display( + $report['reported_post_text'], + $report['reported_post_uid'], + $report['reported_post_bitfield'], + $parse_post_flags, + false + ); + + $report['report_text'] = make_clickable(bbcode_nl2br($report['report_text'])); + + if ($post_info['post_attachment'] && $auth->acl_get('u_download') && $auth->acl_get('f_download', $post_info['forum_id'])) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE post_msg_id = ' . $post_id . ' + AND in_message = 0 + AND filetime <= ' . (int) $report['report_time'] . ' + ORDER BY filetime DESC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[] = $row; + } + $db->sql_freeresult($result); + + if (count($attachments)) + { + $update_count = array(); + parse_attachments($post_info['forum_id'], $message, $attachments, $update_count); + } + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (!empty($attachments)) + { + $template->assign_var('S_HAS_ATTACHMENTS', true); + + foreach ($attachments as $attachment) + { + $template->assign_block_vars('attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + } + } + + // parse signature + $parse_flags = ($post_info['user_sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $post_info['user_sig'] = generate_text_for_display($post_info['user_sig'], $post_info['user_sig_bbcode_uid'], $post_info['user_sig_bbcode_bitfield'], $parse_flags, true); + + $topic_id = (int) $post_info['topic_id']; + + // So it can be sent through the event below. + $report_template = array( + 'S_MCP_REPORT' => true, + 'S_CLOSE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&f=' . $post_info['forum_id'] . '&p=' . $post_id), + 'S_CAN_VIEWIP' => $auth->acl_get('m_info', $post_info['forum_id']), + 'S_POST_REPORTED' => $post_info['post_reported'], + 'S_POST_UNAPPROVED' => $post_info['post_visibility'] == ITEM_UNAPPROVED || $post_info['post_visibility'] == ITEM_REAPPROVE, + 'S_POST_LOCKED' => $post_info['post_edit_locked'], + 'S_REPORT_CLOSED' => $report['report_closed'], + 'S_USER_NOTES' => true, + + 'U_EDIT' => ($auth->acl_get('m_edit', $post_info['forum_id'])) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=edit&f={$post_info['forum_id']}&p={$post_info['post_id']}") : '', + 'U_MCP_APPROVE' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&f=' . $post_info['forum_id'] . '&p=' . $post_id), + 'U_MCP_REPORT' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&f=' . $post_info['forum_id'] . '&p=' . $post_id), + 'U_MCP_REPORTER_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $report['user_id']), + 'U_MCP_USER_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $post_info['user_id']), + 'U_MCP_WARN_REPORTER' => ($auth->acl_get('m_warn')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user&u=' . $report['user_id']) : '', + 'U_MCP_WARN_USER' => ($auth->acl_get('m_warn')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user&u=' . $post_info['user_id']) : '', + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $post_info['forum_id']), + 'U_VIEW_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $post_info['forum_id'] . '&p=' . $post_info['post_id'] . '#p' . $post_info['post_id']), + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $post_info['forum_id'] . '&t=' . $post_info['topic_id']), + + 'EDIT_IMG' => $user->img('icon_post_edit', $user->lang['EDIT_POST']), + 'MINI_POST_IMG' => ($post_unread) ? $user->img('icon_post_target_unread', 'UNREAD_POST') : $user->img('icon_post_target', 'POST'), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', $user->lang['POST_UNAPPROVED']), + + 'RETURN_REPORTS' => sprintf($user->lang['RETURN_REPORTS'], '', ''), + 'REPORTED_IMG' => $user->img('icon_topic_reported', $user->lang['POST_REPORTED']), + 'REPORT_DATE' => $user->format_date($report['report_time']), + 'REPORT_ID' => $report_id, + 'REPORT_REASON_TITLE' => $reason['title'], + 'REPORT_REASON_DESCRIPTION' => $reason['description'], + 'REPORT_TEXT' => $report['report_text'], + + 'POST_AUTHOR_FULL' => get_username_string('full', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'POST_AUTHOR_COLOUR' => get_username_string('colour', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'POST_AUTHOR' => get_username_string('username', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + 'U_POST_AUTHOR' => get_username_string('profile', $post_info['user_id'], $post_info['username'], $post_info['user_colour'], $post_info['post_username']), + + 'REPORTER_FULL' => get_username_string('full', $report['user_id'], $report['username'], $report['user_colour']), + 'REPORTER_COLOUR' => get_username_string('colour', $report['user_id'], $report['username'], $report['user_colour']), + 'REPORTER_NAME' => get_username_string('username', $report['user_id'], $report['username'], $report['user_colour']), + 'U_VIEW_REPORTER_PROFILE' => get_username_string('profile', $report['user_id'], $report['username'], $report['user_colour']), + + 'POST_PREVIEW' => $message, + 'POST_SUBJECT' => ($post_info['post_subject']) ? $post_info['post_subject'] : $user->lang['NO_SUBJECT'], + 'POST_DATE' => $user->format_date($post_info['post_time']), + 'POST_IP' => $post_info['poster_ip'], + 'POST_IPADDR' => ($auth->acl_get('m_info', $post_info['forum_id']) && $request->variable('lookup', '')) ? @gethostbyaddr($post_info['poster_ip']) : '', + 'POST_ID' => $post_info['post_id'], + 'SIGNATURE' => $post_info['user_sig'], + + 'U_LOOKUP_IP' => ($auth->acl_get('m_info', $post_info['forum_id'])) ? $this->u_action . '&r=' . $report_id . '&p=' . $post_id . '&f=' . $forum_id . '&lookup=' . $post_info['poster_ip'] . '#ip' : '', + ); + + /** + * Event to add/modify MCP report details template data. + * + * @event core.mcp_report_template_data + * @var int forum_id The forum_id, the number in the f GET parameter + * @var int topic_id The topic_id of the report being viewed + * @var int post_id The post_id of the report being viewed (if 0, it is meaningless) + * @var int report_id The report_id of the report being viewed + * @var array report Array with the report data + * @var array report_template Array with the report template data + * @var array post_info Array with the reported post data + * @since 3.2.5-RC1 + */ + $vars = array( + 'forum_id', + 'topic_id', + 'post_id', + 'report_id', + 'report', + 'report_template', + 'post_info', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_report_template_data', compact($vars))); + + $template->assign_vars($report_template); + + $this->tpl_name = 'mcp_post'; + + break; + + case 'reports': + case 'reports_closed': + $topic_id = $request->variable('t', 0); + + $forum_info = array(); + $forum_list_reports = get_forum_list('m_report', false, true); + $forum_list_read = array_flip(get_forum_list('f_read', true, true)); // Flipped so we can isset() the forum IDs + + // Remove forums we cannot read + foreach ($forum_list_reports as $k => $forum_data) + { + if (!isset($forum_list_read[$forum_data['forum_id']])) + { + unset($forum_list_reports[$k]); + } + } + unset($forum_list_read); + + if ($topic_id) + { + $topic_info = phpbb_get_topic_data(array($topic_id)); + + if (!count($topic_info)) + { + trigger_error('TOPIC_NOT_EXIST'); + } + + if ($forum_id != $topic_info[$topic_id]['forum_id']) + { + $topic_id = 0; + } + else + { + $topic_info = $topic_info[$topic_id]; + $forum_id = (int) $topic_info['forum_id']; + } + } + + $forum_list = array(); + + if (!$forum_id) + { + foreach ($forum_list_reports as $row) + { + $forum_list[] = $row['forum_id']; + } + + if (!count($forum_list)) + { + trigger_error('NOT_MODERATOR'); + } + + $sql = 'SELECT SUM(forum_topics_approved) as sum_forum_topics + FROM ' . FORUMS_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forum_list); + $result = $db->sql_query($sql); + $forum_info['forum_topics_approved'] = (int) $db->sql_fetchfield('sum_forum_topics'); + $db->sql_freeresult($result); + } + else + { + $forum_info = phpbb_get_forum_data(array($forum_id), 'm_report'); + + if (!count($forum_info)) + { + trigger_error('NOT_MODERATOR'); + } + + $forum_list = array($forum_id); + } + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $forum_list[] = 0; + $forum_data = array(); + + $forum_options = ''; + foreach ($forum_list_reports as $row) + { + $forum_options .= ''; + $forum_data[$row['forum_id']] = $row; + } + unset($forum_list_reports); + + $sort_days = $total = 0; + $sort_key = $sort_dir = ''; + $sort_by_sql = $sort_order_sql = array(); + phpbb_mcp_sorting($mode, $sort_days, $sort_key, $sort_dir, $sort_by_sql, $sort_order_sql, $total, $forum_id, $topic_id); + + $limit_time_sql = ($sort_days) ? 'AND r.report_time >= ' . (time() - ($sort_days * 86400)) : ''; + + if ($mode == 'reports') + { + $report_state = 'AND p.post_reported = 1 AND r.report_closed = 0'; + } + else + { + $report_state = 'AND r.report_closed = 1'; + } + + $sql = 'SELECT r.report_id + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . REPORTS_TABLE . ' r ' . (($sort_order_sql[0] == 'u') ? ', ' . USERS_TABLE . ' u' : '') . (($sort_order_sql[0] == 'r') ? ', ' . USERS_TABLE . ' ru' : '') . ' + WHERE ' . $db->sql_in_set('p.forum_id', $forum_list) . " + $report_state + AND r.post_id = p.post_id + " . (($sort_order_sql[0] == 'u') ? 'AND u.user_id = p.poster_id' : '') . ' + ' . (($sort_order_sql[0] == 'r') ? 'AND ru.user_id = r.user_id' : '') . ' + ' . (($topic_id) ? 'AND p.topic_id = ' . $topic_id : '') . " + AND t.topic_id = p.topic_id + AND r.pm_id = 0 + $limit_time_sql + ORDER BY $sort_order_sql"; + + /** + * Alter sql query to get report id of all reports for requested forum and topic or just forum + * + * @event core.mcp_reports_get_reports_query_before + * @var string sql String with the query to be executed + * @var array forum_list List of forums that contain the posts + * @var int topic_id topic_id in the page request + * @var string limit_time_sql String with the SQL code to limit the time interval of the post (Note: May be empty string) + * @var string sort_order_sql String with the ORDER BY SQL code used in this query + * @since 3.1.0-RC4 + */ + $vars = array( + 'sql', + 'forum_list', + 'topic_id', + 'limit_time_sql', + 'sort_order_sql', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_reports_get_reports_query_before', compact($vars))); + + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $i = 0; + $report_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $report_ids[] = $row['report_id']; + $row_num[$row['report_id']] = $i++; + } + $db->sql_freeresult($result); + + if (count($report_ids)) + { + $sql = 'SELECT t.forum_id, t.topic_id, t.topic_title, p.post_id, p.post_subject, p.post_username, p.poster_id, p.post_time, p.post_attachment, u.username, u.username_clean, u.user_colour, r.user_id as reporter_id, ru.username as reporter_name, ru.user_colour as reporter_colour, r.report_time, r.report_id + FROM ' . REPORTS_TABLE . ' r, ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . USERS_TABLE . ' u, ' . USERS_TABLE . ' ru + WHERE ' . $db->sql_in_set('r.report_id', $report_ids) . ' + AND t.topic_id = p.topic_id + AND r.post_id = p.post_id + AND u.user_id = p.poster_id + AND ru.user_id = r.user_id + AND r.pm_id = 0 + ORDER BY ' . $sort_order_sql; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('postrow', array( + 'U_VIEWFORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']), + 'U_VIEWPOST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . '&p=' . $row['post_id']) . '#p' . $row['post_id'], + 'U_VIEW_DETAILS' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=reports&start=$start&mode=report_details&f={$row['forum_id']}&r={$row['report_id']}"), + + 'POST_AUTHOR_FULL' => get_username_string('full', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR_COLOUR' => get_username_string('colour', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR' => get_username_string('username', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'U_POST_AUTHOR' => get_username_string('profile', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + + 'REPORTER_FULL' => get_username_string('full', $row['reporter_id'], $row['reporter_name'], $row['reporter_colour']), + 'REPORTER_COLOUR' => get_username_string('colour', $row['reporter_id'], $row['reporter_name'], $row['reporter_colour']), + 'REPORTER' => get_username_string('username', $row['reporter_id'], $row['reporter_name'], $row['reporter_colour']), + 'U_REPORTER' => get_username_string('profile', $row['reporter_id'], $row['reporter_name'], $row['reporter_colour']), + + 'FORUM_NAME' => $forum_data[$row['forum_id']]['forum_name'], + 'POST_ID' => $row['post_id'], + 'POST_SUBJECT' => ($row['post_subject']) ? $row['post_subject'] : $user->lang['NO_SUBJECT'], + 'POST_TIME' => $user->format_date($row['post_time']), + 'REPORT_ID' => $row['report_id'], + 'REPORT_TIME' => $user->format_date($row['report_time']), + 'TOPIC_TITLE' => $row['topic_title'], + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['post_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + )); + } + $db->sql_freeresult($result); + unset($report_ids, $row); + } + + $base_url = $this->u_action . "&f=$forum_id&t=$topic_id&st=$sort_days&sk=$sort_key&sd=$sort_dir"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $total, $config['topics_per_page'], $start); + + // Now display the page + $template->assign_vars(array( + 'L_EXPLAIN' => ($mode == 'reports') ? $user->lang['MCP_REPORTS_OPEN_EXPLAIN'] : $user->lang['MCP_REPORTS_CLOSED_EXPLAIN'], + 'L_TITLE' => ($mode == 'reports') ? $user->lang['MCP_REPORTS_OPEN'] : $user->lang['MCP_REPORTS_CLOSED'], + 'L_ONLY_TOPIC' => ($topic_id) ? sprintf($user->lang['ONLY_TOPIC'], $topic_info['topic_title']) : '', + + 'S_MCP_ACTION' => $this->u_action, + 'S_FORUM_OPTIONS' => $forum_options, + 'S_CLOSED' => ($mode == 'reports_closed') ? true : false, + + 'TOPIC_ID' => $topic_id, + 'TOTAL' => $total, + 'TOTAL_REPORTS' => $user->lang('LIST_REPORTS', (int) $total), + ) + ); + + $this->tpl_name = 'mcp_reports'; + break; + } + } +} + +/** +* Closes a report +*/ +function close_report($report_id_list, $mode, $action, $pm = false) +{ + global $db, $user, $auth, $phpbb_log, $request; + global $phpEx, $phpbb_root_path, $phpbb_container; + + $pm_where = ($pm) ? ' AND r.post_id = 0 ' : ' AND r.pm_id = 0 '; + $id_column = ($pm) ? 'pm_id' : 'post_id'; + $module = ($pm) ? 'pm_reports' : 'reports'; + $pm_prefix = ($pm) ? 'PM_' : ''; + + $sql = "SELECT r.$id_column + FROM " . REPORTS_TABLE . ' r + WHERE ' . $db->sql_in_set('r.report_id', $report_id_list) . $pm_where; + $result = $db->sql_query($sql); + + $post_id_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + $post_id_list[] = $row[$id_column]; + } + $db->sql_freeresult($result); + $post_id_list = array_unique($post_id_list); + + if ($pm) + { + if (!$auth->acl_getf_global('m_report')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + } + else + { + if (!phpbb_check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_report'))) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + } + + if ($action == 'delete' && strpos($user->data['session_page'], 'mode=report_details') !== false) + { + $redirect = $request->variable('redirect', build_url(array('mode', 'r', 'quickmod')) . '&mode=reports'); + } + else if ($action == 'delete' && strpos($user->data['session_page'], 'mode=pm_report_details') !== false) + { + $redirect = $request->variable('redirect', build_url(array('mode', 'r', 'quickmod')) . '&mode=pm_reports'); + } + else if ($action == 'close' && !$request->variable('r', 0)) + { + $redirect = $request->variable('redirect', build_url(array('mode', 'p', 'quickmod')) . '&mode=' . $module); + } + else + { + $redirect = $request->variable('redirect', build_url(array('quickmod'))); + } + $success_msg = ''; + $forum_ids = array(); + $topic_ids = array(); + + $s_hidden_fields = build_hidden_fields(array( + 'i' => $module, + 'mode' => $mode, + 'report_id_list' => $report_id_list, + 'action' => $action, + 'redirect' => $redirect) + ); + + if (confirm_box(true)) + { + $post_info = ($pm) ? phpbb_get_pm_data($post_id_list) : phpbb_get_post_data($post_id_list, 'm_report'); + + $sql = "SELECT r.report_id, r.$id_column, r.report_closed, r.user_id, r.user_notify, u.username, u.username_clean, u.user_email, u.user_jabber, u.user_lang, u.user_notify_type + FROM " . REPORTS_TABLE . ' r, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('r.report_id', $report_id_list) . ' + ' . (($action == 'close') ? 'AND r.report_closed = 0' : '') . ' + AND r.user_id = u.user_id' . $pm_where; + $result = $db->sql_query($sql); + + $reports = $close_report_posts = $close_report_topics = $notify_reporters = $report_id_list = array(); + while ($report = $db->sql_fetchrow($result)) + { + $reports[$report['report_id']] = $report; + $report_id_list[] = $report['report_id']; + + if (!$report['report_closed']) + { + $close_report_posts[] = $report[$id_column]; + + if (!$pm) + { + $close_report_topics[] = $post_info[$report['post_id']]['topic_id']; + } + } + + if ($report['user_notify'] && !$report['report_closed']) + { + $notify_reporters[$report['report_id']] = &$reports[$report['report_id']]; + } + } + $db->sql_freeresult($result); + + if (count($reports)) + { + $close_report_posts = array_unique($close_report_posts); + $close_report_topics = array_unique($close_report_topics); + + if (!$pm && count($close_report_posts)) + { + // Get a list of topics that still contain reported posts + $sql = 'SELECT DISTINCT topic_id + FROM ' . POSTS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $close_report_topics) . ' + AND post_reported = 1 + AND ' . $db->sql_in_set('post_id', $close_report_posts, true); + $result = $db->sql_query($sql); + + $keep_report_topics = array(); + while ($row = $db->sql_fetchrow($result)) + { + $keep_report_topics[] = $row['topic_id']; + } + $db->sql_freeresult($result); + + $close_report_topics = array_diff($close_report_topics, $keep_report_topics); + unset($keep_report_topics); + } + + $db->sql_transaction('begin'); + + if ($action == 'close') + { + $sql = 'UPDATE ' . REPORTS_TABLE . ' + SET report_closed = 1 + WHERE ' . $db->sql_in_set('report_id', $report_id_list); + } + else + { + $sql = 'DELETE FROM ' . REPORTS_TABLE . ' + WHERE ' . $db->sql_in_set('report_id', $report_id_list); + } + $db->sql_query($sql); + + if (count($close_report_posts)) + { + if ($pm) + { + $sql = 'UPDATE ' . PRIVMSGS_TABLE . ' + SET message_reported = 0 + WHERE ' . $db->sql_in_set('msg_id', $close_report_posts); + $db->sql_query($sql); + + if ($action == 'delete') + { + delete_pm(ANONYMOUS, $close_report_posts, PRIVMSGS_INBOX); + } + } + else + { + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_reported = 0 + WHERE ' . $db->sql_in_set('post_id', $close_report_posts); + $db->sql_query($sql); + + if (count($close_report_topics)) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_reported = 0 + WHERE ' . $db->sql_in_set('topic_id', $close_report_topics) . ' + OR ' . $db->sql_in_set('topic_moved_id', $close_report_topics); + $db->sql_query($sql); + } + } + } + + $db->sql_transaction('commit'); + } + unset($close_report_posts, $close_report_topics); + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + foreach ($reports as $report) + { + if ($pm) + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_PM_REPORT_' . strtoupper($action) . 'D', false, array( + 'forum_id' => 0, + 'topic_id' => 0, + $post_info[$report['pm_id']]['message_subject'] + )); + $phpbb_notifications->delete_notifications('notification.type.report_pm', $report['pm_id']); + } + else + { + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_REPORT_' . strtoupper($action) . 'D', false, array( + 'forum_id' => $post_info[$report['post_id']]['forum_id'], + 'topic_id' => $post_info[$report['post_id']]['topic_id'], + 'post_id' => $report['post_id'], + $post_info[$report['post_id']]['post_subject'] + )); + $phpbb_notifications->delete_notifications('notification.type.report_post', $report['post_id']); + } + } + + // Notify reporters + if (count($notify_reporters)) + { + foreach ($notify_reporters as $report_id => $reporter) + { + if ($reporter['user_id'] == ANONYMOUS) + { + continue; + } + + $post_id = $reporter[$id_column]; + + if ($pm) + { + $phpbb_notifications->add_notifications('notification.type.report_pm_closed', array_merge($post_info[$post_id], array( + 'reporter' => $reporter['user_id'], + 'closer_id' => $user->data['user_id'], + 'from_user_id' => $post_info[$post_id]['author_id'], + ))); + } + else + { + $phpbb_notifications->add_notifications('notification.type.report_post_closed', array_merge($post_info[$post_id], array( + 'reporter' => $reporter['user_id'], + 'closer_id' => $user->data['user_id'], + ))); + } + } + } + + if (!$pm) + { + foreach ($post_info as $post) + { + $forum_ids[$post['forum_id']] = $post['forum_id']; + $topic_ids[$post['topic_id']] = $post['topic_id']; + } + } + + unset($notify_reporters, $post_info, $reports); + + $success_msg = (count($report_id_list) == 1) ? "{$pm_prefix}REPORT_" . strtoupper($action) . 'D_SUCCESS' : "{$pm_prefix}REPORTS_" . strtoupper($action) . 'D_SUCCESS'; + } + else + { + confirm_box(false, $user->lang[strtoupper($action) . "_{$pm_prefix}REPORT" . ((count($report_id_list) == 1) ? '' : 'S') . '_CONFIRM'], $s_hidden_fields); + } + + $redirect = $request->variable('redirect', "index.$phpEx"); + $redirect = reapply_sid($redirect); + + if (!$success_msg) + { + redirect($redirect); + } + else + { + meta_refresh(3, $redirect); + + $return_forum = ''; + $return_topic = ''; + + if (!$pm) + { + if (count($forum_ids) === 1) + { + $return_forum = sprintf($user->lang['RETURN_FORUM'], '', '') . '

'; + } + + if (count($topic_ids) === 1) + { + $return_topic = sprintf($user->lang['RETURN_TOPIC'], '', '') . '

'; + } + } + + trigger_error($user->lang[$success_msg] . '

' . $return_forum . $return_topic . sprintf($user->lang['RETURN_PAGE'], "", '')); + } +} diff --git a/includes/mcp/mcp_topic.php b/includes/mcp/mcp_topic.php new file mode 100644 index 0000000..68a65aa --- /dev/null +++ b/includes/mcp/mcp_topic.php @@ -0,0 +1,812 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* View topic in MCP +*/ +function mcp_topic_view($id, $mode, $action) +{ + global $phpEx, $phpbb_root_path, $config, $request; + global $template, $db, $user, $auth, $phpbb_container, $phpbb_dispatcher; + + $url = append_sid("{$phpbb_root_path}mcp.$phpEx?" . phpbb_extra_url()); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $user->add_lang('viewtopic'); + + $topic_id = $request->variable('t', 0); + $topic_info = phpbb_get_topic_data(array($topic_id), false, true); + + if (!count($topic_info)) + { + trigger_error('TOPIC_NOT_EXIST'); + } + + $topic_info = $topic_info[$topic_id]; + + // Set up some vars + $icon_id = $request->variable('icon', 0); + $subject = $request->variable('subject', '', true); + $start = $request->variable('start', 0); + $sort_days_old = $request->variable('st_old', 0); + $forum_id = $request->variable('f', 0); + $to_topic_id = $request->variable('to_topic_id', 0); + $to_forum_id = $request->variable('to_forum_id', 0); + $sort = isset($_POST['sort']) ? true : false; + $submitted_id_list = $request->variable('post_ids', array(0)); + $checked_ids = $post_id_list = $request->variable('post_id_list', array(0)); + + // Resync Topic? + if ($action == 'resync') + { + if (!function_exists('mcp_resync_topics')) + { + include($phpbb_root_path . 'includes/mcp/mcp_forum.' . $phpEx); + } + mcp_resync_topics(array($topic_id)); + } + + // Split Topic? + if ($action == 'split_all' || $action == 'split_beyond') + { + if (!$sort) + { + split_topic($action, $topic_id, $to_forum_id, $subject); + } + $action = 'split'; + } + + // Merge Posts? + if ($action == 'merge_posts') + { + if (!$sort) + { + merge_posts($topic_id, $to_topic_id); + } + $action = 'merge'; + } + + if ($action == 'split' && !$subject) + { + $subject = $topic_info['topic_title']; + } + + // Restore or pprove posts? + if (($action == 'restore' || $action == 'approve') && $auth->acl_get('m_approve', $topic_info['forum_id'])) + { + if (!class_exists('mcp_queue')) + { + include($phpbb_root_path . 'includes/mcp/mcp_queue.' . $phpEx); + } + + include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + if (!count($post_id_list)) + { + trigger_error('NO_POST_SELECTED'); + } + + if (!$sort) + { + mcp_queue::approve_posts($action, $post_id_list, $id, $mode); + } + } + + // Jumpbox, sort selects and that kind of things + make_jumpbox($url . "&i=$id&mode=forum_view", $topic_info['forum_id'], false, 'm_', true); + $where_sql = ($action == 'reports') ? 'WHERE post_reported = 1 AND ' : 'WHERE'; + + $sort_days = $total = 0; + $sort_key = $sort_dir = ''; + $sort_by_sql = $sort_order_sql = array(); + phpbb_mcp_sorting('viewtopic', $sort_days, $sort_key, $sort_dir, $sort_by_sql, $sort_order_sql, $total, $topic_info['forum_id'], $topic_id, $where_sql); + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + $limit_time_sql = ($sort_days) ? 'AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + + if ($total == -1) + { + $total = $phpbb_content_visibility->get_count('topic_posts', $topic_info, $topic_info['forum_id']); + } + + $posts_per_page = max(0, $request->variable('posts_per_page', intval($config['posts_per_page']))); + if ($posts_per_page == 0) + { + $posts_per_page = $total; + } + + if ((!empty($sort_days_old) && $sort_days_old != $sort_days) || $total <= $posts_per_page) + { + $start = 0; + } + $start = $pagination->validate_start($start, $posts_per_page, $total); + + $sql = 'SELECT u.username, u.username_clean, u.user_colour, p.* + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE ' . (($action == 'reports') ? 'p.post_reported = 1 AND ' : '') . ' + p.topic_id = ' . $topic_id . ' + AND ' . $phpbb_content_visibility->get_visibility_sql('post', $topic_info['forum_id'], 'p.') . ' + AND p.poster_id = u.user_id ' . + $limit_time_sql . ' + ORDER BY ' . $sort_order_sql; + $result = $db->sql_query_limit($sql, $posts_per_page, $start); + + $rowset = $post_id_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + $rowset[] = $row; + $post_id_list[] = $row['post_id']; + } + $db->sql_freeresult($result); + + // Get topic tracking info + if ($config['load_db_lastread']) + { + $tmp_topic_data = array($topic_id => $topic_info); + $topic_tracking_info = get_topic_tracking($topic_info['forum_id'], $topic_id, $tmp_topic_data, array($topic_info['forum_id'] => $topic_info['forum_mark_time'])); + unset($tmp_topic_data); + } + else + { + $topic_tracking_info = get_complete_topic_tracking($topic_info['forum_id'], $topic_id); + } + + $has_unapproved_posts = $has_deleted_posts = false; + + // Grab extensions + $attachments = array(); + if ($topic_info['topic_attachment'] && count($post_id_list)) + { + // Get attachments... + if ($auth->acl_get('u_download') && $auth->acl_get('f_download', $topic_info['forum_id'])) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_msg_id', $post_id_list) . ' + AND in_message = 0 + ORDER BY filetime DESC, post_msg_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[$row['post_msg_id']][] = $row; + } + $db->sql_freeresult($result); + } + } + + /** + * Event to modify the post data for the MCP topic review before assigning the posts + * + * @event core.mcp_topic_modify_post_data + * @var array attachments List of attachments post_id => array of attachments + * @var int forum_id The forum ID we are currently in + * @var int id ID of the tab we are displaying + * @var string mode Mode of the MCP page we are displaying + * @var array post_id_list Array with post ids we are going to display + * @var array rowset Array with the posts data + * @var int topic_id The topic ID we are currently reviewing + * @since 3.1.7-RC1 + */ + $vars = array( + 'attachments', + 'forum_id', + 'id', + 'mode', + 'post_id_list', + 'rowset', + 'topic_id', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_topic_modify_post_data', compact($vars))); + + foreach ($rowset as $i => $row) + { + $message = $row['post_text']; + $post_subject = ($row['post_subject'] != '') ? $row['post_subject'] : $topic_info['topic_title']; + + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($message, $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, false); + + if (!empty($attachments[$row['post_id']])) + { + $update_count = array(); + parse_attachments($topic_info['forum_id'], $message, $attachments[$row['post_id']], $update_count); + } + + if ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) + { + $has_unapproved_posts = true; + } + + if ($row['post_visibility'] == ITEM_DELETED) + { + $has_deleted_posts = true; + } + + $post_unread = (isset($topic_tracking_info[$topic_id]) && $row['post_time'] > $topic_tracking_info[$topic_id]) ? true : false; + + $post_row = array( + 'POST_AUTHOR_FULL' => get_username_string('full', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR_COLOUR' => get_username_string('colour', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR' => get_username_string('username', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + 'U_POST_AUTHOR' => get_username_string('profile', $row['poster_id'], $row['username'], $row['user_colour'], $row['post_username']), + + 'POST_DATE' => $user->format_date($row['post_time']), + 'POST_SUBJECT' => $post_subject, + 'MESSAGE' => $message, + 'POST_ID' => $row['post_id'], + 'RETURN_TOPIC' => sprintf($user->lang['RETURN_TOPIC'], '', ''), + + 'MINI_POST_IMG' => ($post_unread) ? $user->img('icon_post_target_unread', 'UNREAD_POST') : $user->img('icon_post_target', 'POST'), + + 'S_POST_REPORTED' => ($row['post_reported'] && $auth->acl_get('m_report', $topic_info['forum_id'])), + 'S_POST_UNAPPROVED' => (($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) && $auth->acl_get('m_approve', $topic_info['forum_id'])), + 'S_POST_DELETED' => ($row['post_visibility'] == ITEM_DELETED && $auth->acl_get('m_approve', $topic_info['forum_id'])), + 'S_CHECKED' => (($submitted_id_list && !in_array(intval($row['post_id']), $submitted_id_list)) || in_array(intval($row['post_id']), $checked_ids)) ? true : false, + 'S_HAS_ATTACHMENTS' => (!empty($attachments[$row['post_id']])) ? true : false, + + 'U_POST_DETAILS' => "$url&i=$id&p={$row['post_id']}&mode=post_details" . (($forum_id) ? "&f=$forum_id" : ''), + 'U_MCP_APPROVE' => ($auth->acl_get('m_approve', $topic_info['forum_id'])) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&f=' . $topic_info['forum_id'] . '&p=' . $row['post_id']) : '', + 'U_MCP_REPORT' => ($auth->acl_get('m_report', $topic_info['forum_id'])) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&f=' . $topic_info['forum_id'] . '&p=' . $row['post_id']) : '', + ); + + /** + * Event to modify the template data block for topic reviews in the MCP + * + * @event core.mcp_topic_review_modify_row + * @var int id ID of the tab we are displaying + * @var string mode Mode of the MCP page we are displaying + * @var int topic_id The topic ID we are currently reviewing + * @var int forum_id The forum ID we are currently in + * @var int start Start item of this page + * @var int current_row_number Number of the post on this page + * @var array post_row Template block array of the current post + * @var array row Array with original post and user data + * @var array topic_info Array with topic data + * @var int total Total posts count + * @since 3.1.4-RC1 + */ + $vars = array( + 'id', + 'mode', + 'topic_id', + 'forum_id', + 'start', + 'current_row_number', + 'post_row', + 'row', + 'topic_info', + 'total', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_topic_review_modify_row', compact($vars))); + + $template->assign_block_vars('postrow', $post_row); + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (!empty($attachments[$row['post_id']])) + { + foreach ($attachments[$row['post_id']] as $attachment) + { + $template->assign_block_vars('postrow.attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + } + + unset($rowset[$i]); + } + + // Display topic icons for split topic + $s_topic_icons = false; + + if ($auth->acl_gets('m_split', 'm_merge', (int) $topic_info['forum_id'])) + { + include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + $s_topic_icons = posting_gen_topic_icons('', $icon_id); + + // Has the user selected a topic for merge? + if ($to_topic_id) + { + $to_topic_info = phpbb_get_topic_data(array($to_topic_id), 'm_merge'); + + if (!count($to_topic_info)) + { + $to_topic_id = 0; + } + else + { + $to_topic_info = $to_topic_info[$to_topic_id]; + + if (!$to_topic_info['enable_icons'] || $auth->acl_get('!f_icons', $topic_info['forum_id'])) + { + $s_topic_icons = false; + } + } + } + } + + $s_hidden_fields = build_hidden_fields(array( + 'st_old' => $sort_days, + 'post_ids' => $post_id_list, + )); + + $base_url = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=$id&t={$topic_info['topic_id']}&mode=$mode&action=$action&to_topic_id=$to_topic_id&posts_per_page=$posts_per_page&st=$sort_days&sk=$sort_key&sd=$sort_dir"); + if ($posts_per_page) + { + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $total, $posts_per_page, $start); + } + + $template->assign_vars(array( + 'TOPIC_TITLE' => $topic_info['topic_title'], + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $topic_info['forum_id'] . '&t=' . $topic_info['topic_id']), + + 'TO_TOPIC_ID' => $to_topic_id, + 'TO_TOPIC_INFO' => ($to_topic_id) ? sprintf($user->lang['YOU_SELECTED_TOPIC'], $to_topic_id, '' . $to_topic_info['topic_title'] . '') : '', + + 'SPLIT_SUBJECT' => $subject, + 'POSTS_PER_PAGE' => $posts_per_page, + 'ACTION' => $action, + + 'REPORTED_IMG' => $user->img('icon_topic_reported', 'POST_REPORTED'), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', 'POST_UNAPPROVED'), + 'DELETED_IMG' => $user->img('icon_topic_deleted', 'POST_DELETED_RESTORE'), + 'INFO_IMG' => $user->img('icon_post_info', 'VIEW_INFO'), + + 'S_MCP_ACTION' => "$url&i=$id&mode=$mode&action=$action&start=$start", + 'S_FORUM_SELECT' => ($to_forum_id) ? make_forum_select($to_forum_id, false, false, true, true, true) : make_forum_select($topic_info['forum_id'], false, false, true, true, true), + 'S_CAN_SPLIT' => ($auth->acl_get('m_split', $topic_info['forum_id'])) ? true : false, + 'S_CAN_MERGE' => ($auth->acl_get('m_merge', $topic_info['forum_id'])) ? true : false, + 'S_CAN_DELETE' => ($auth->acl_get('m_delete', $topic_info['forum_id'])) ? true : false, + 'S_CAN_APPROVE' => ($has_unapproved_posts && $auth->acl_get('m_approve', $topic_info['forum_id'])) ? true : false, + 'S_CAN_RESTORE' => ($has_deleted_posts && $auth->acl_get('m_approve', $topic_info['forum_id'])) ? true : false, + 'S_CAN_LOCK' => ($auth->acl_get('m_lock', $topic_info['forum_id'])) ? true : false, + 'S_CAN_REPORT' => ($auth->acl_get('m_report', $topic_info['forum_id'])) ? true : false, + 'S_CAN_SYNC' => $auth->acl_get('m_', $topic_info['forum_id']), + 'S_REPORT_VIEW' => ($action == 'reports') ? true : false, + 'S_MERGE_VIEW' => ($action == 'merge') ? true : false, + 'S_SPLIT_VIEW' => ($action == 'split') ? true : false, + + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + + 'S_SHOW_TOPIC_ICONS' => $s_topic_icons, + 'S_TOPIC_ICON' => $icon_id, + + 'U_SELECT_TOPIC' => "$url&i=$id&mode=forum_view&action=merge_select" . (($forum_id) ? "&f=$forum_id" : ''), + + 'RETURN_TOPIC' => sprintf($user->lang['RETURN_TOPIC'], '', ''), + 'RETURN_FORUM' => sprintf($user->lang['RETURN_FORUM'], '', ''), + + 'TOTAL_POSTS' => $user->lang('VIEW_TOPIC_POSTS', (int) $total), + )); +} + +/** +* Split topic +*/ +function split_topic($action, $topic_id, $to_forum_id, $subject) +{ + global $db, $template, $user, $phpEx, $phpbb_root_path, $auth, $config, $phpbb_log, $request, $phpbb_dispatcher; + + $post_id_list = $request->variable('post_id_list', array(0)); + $forum_id = $request->variable('forum_id', 0); + $start = $request->variable('start', 0); + + if (!count($post_id_list)) + { + $template->assign_var('MESSAGE', $user->lang['NO_POST_SELECTED']); + return; + } + + if (!phpbb_check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_split'))) + { + return; + } + + $post_id = $post_id_list[0]; + $post_info = phpbb_get_post_data(array($post_id)); + + if (!count($post_info)) + { + $template->assign_var('MESSAGE', $user->lang['NO_POST_SELECTED']); + return; + } + + $post_info = $post_info[$post_id]; + $subject = trim($subject); + + // Make some tests + if (!$subject) + { + $template->assign_var('MESSAGE', $user->lang['EMPTY_SUBJECT']); + return; + } + + if ($to_forum_id <= 0) + { + $template->assign_var('MESSAGE', $user->lang['NO_DESTINATION_FORUM']); + return; + } + + $forum_info = phpbb_get_forum_data(array($to_forum_id), 'f_post'); + + if (!count($forum_info)) + { + $template->assign_var('MESSAGE', $user->lang['USER_CANNOT_POST']); + return; + } + + $forum_info = $forum_info[$to_forum_id]; + + if ($forum_info['forum_type'] != FORUM_POST) + { + $template->assign_var('MESSAGE', $user->lang['FORUM_NOT_POSTABLE']); + return; + } + + $redirect = $request->variable('redirect', build_url(array('quickmod'))); + + $s_hidden_fields = build_hidden_fields(array( + 'i' => 'main', + 'post_id_list' => $post_id_list, + 'f' => $forum_id, + 'mode' => 'topic_view', + 'start' => $start, + 'action' => $action, + 't' => $topic_id, + 'redirect' => $redirect, + 'subject' => $subject, + 'to_forum_id' => $to_forum_id, + 'icon' => $request->variable('icon', 0)) + ); + + if (confirm_box(true)) + { + if ($action == 'split_beyond') + { + $sort_days = $total = 0; + $sort_key = $sort_dir = ''; + $sort_by_sql = $sort_order_sql = array(); + phpbb_mcp_sorting('viewtopic', $sort_days, $sort_key, $sort_dir, $sort_by_sql, $sort_order_sql, $total, $forum_id, $topic_id); + + $limit_time_sql = ($sort_days) ? 'AND t.topic_last_post_time >= ' . (time() - ($sort_days * 86400)) : ''; + + if ($sort_order_sql[0] == 'u') + { + $sql = 'SELECT p.post_id, p.forum_id, p.post_visibility + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . " u + WHERE p.topic_id = $topic_id + AND p.poster_id = u.user_id + $limit_time_sql + ORDER BY $sort_order_sql"; + } + else + { + $sql = 'SELECT p.post_id, p.forum_id, p.post_visibility + FROM ' . POSTS_TABLE . " p + WHERE p.topic_id = $topic_id + $limit_time_sql + ORDER BY $sort_order_sql"; + } + $result = $db->sql_query_limit($sql, 0, $start); + + $store = false; + $post_id_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + // If split from selected post (split_beyond), we split the unapproved items too. + if (($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) && !$auth->acl_get('m_approve', $row['forum_id'])) + { +// continue; + } + + // Start to store post_ids as soon as we see the first post that was selected + if ($row['post_id'] == $post_id) + { + $store = true; + } + + if ($store) + { + $post_id_list[] = $row['post_id']; + } + } + $db->sql_freeresult($result); + } + + if (!count($post_id_list)) + { + trigger_error('NO_POST_SELECTED'); + } + + $icon_id = $request->variable('icon', 0); + + $sql_ary = array( + 'forum_id' => $to_forum_id, + 'topic_title' => $subject, + 'icon_id' => $icon_id, + 'topic_visibility' => 1 + ); + + $sql = 'INSERT INTO ' . TOPICS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + + $to_topic_id = $db->sql_nextid(); + move_posts($post_id_list, $to_topic_id); + + $topic_info = phpbb_get_topic_data(array($topic_id)); + $topic_info = $topic_info[$topic_id]; + + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_SPLIT_DESTINATION', false, array( + 'forum_id' => $to_forum_id, + 'topic_id' => $to_topic_id, + $subject + )); + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_SPLIT_SOURCE', false, array( + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + $topic_info['topic_title'] + )); + + // Change topic title of first post + $sql = 'UPDATE ' . POSTS_TABLE . " + SET post_subject = '" . $db->sql_escape($subject) . "' + WHERE post_id = {$post_id_list[0]}"; + $db->sql_query($sql); + + // Grab data for first post in split topic + $sql_array = array( + 'SELECT' => 'p.post_id, p.forum_id, p.poster_id, p.post_text, f.enable_indexing', + 'FROM' => array( + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'p.forum_id = f.forum_id', + ) + ), + 'WHERE' => "post_id = {$post_id_list[0]}", + ); + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + $first_post_data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Index first post as if it were edited + if ($first_post_data['enable_indexing']) + { + // Select the search method and do some additional checks to ensure it can actually be utilised + $search_type = $config['search_type']; + + if (!class_exists($search_type)) + { + trigger_error('NO_SUCH_SEARCH_MODULE'); + } + + $error = false; + $search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher); + + if ($error) + { + trigger_error($error); + } + + $search->index('edit', $first_post_data['post_id'], $first_post_data['post_text'], $subject, $first_post_data['poster_id'], $first_post_data['forum_id']); + } + + // Copy topic subscriptions to new topic + $sql = 'SELECT user_id, notify_status + FROM ' . TOPICS_WATCH_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + + $sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary[] = array( + 'topic_id' => (int) $to_topic_id, + 'user_id' => (int) $row['user_id'], + 'notify_status' => (int) $row['notify_status'], + ); + } + $db->sql_freeresult($result); + + if (count($sql_ary)) + { + $db->sql_multi_insert(TOPICS_WATCH_TABLE, $sql_ary); + } + + // Copy bookmarks to new topic + $sql = 'SELECT user_id + FROM ' . BOOKMARKS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + + $sql_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + $sql_ary[] = array( + 'topic_id' => (int) $to_topic_id, + 'user_id' => (int) $row['user_id'], + ); + } + $db->sql_freeresult($result); + + if (count($sql_ary)) + { + $db->sql_multi_insert(BOOKMARKS_TABLE, $sql_ary); + } + + $success_msg = 'TOPIC_SPLIT_SUCCESS'; + + // Update forum statistics + $config->increment('num_topics', 1, false); + + // Link back to both topics + $return_link = sprintf($user->lang['RETURN_TOPIC'], '', '') . '

' . sprintf($user->lang['RETURN_NEW_TOPIC'], '', ''); + $redirect = $request->variable('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); + $redirect = reapply_sid($redirect); + + meta_refresh(3, $redirect); + trigger_error($user->lang[$success_msg] . '

' . $return_link); + } + else + { + confirm_box(false, ($action == 'split_all') ? 'SPLIT_TOPIC_ALL' : 'SPLIT_TOPIC_BEYOND', $s_hidden_fields); + } +} + +/** +* Merge selected posts into selected topic +*/ +function merge_posts($topic_id, $to_topic_id) +{ + global $db, $template, $user, $phpEx, $phpbb_root_path, $phpbb_log, $request, $phpbb_dispatcher; + + if (!$to_topic_id) + { + $template->assign_var('MESSAGE', $user->lang['NO_FINAL_TOPIC_SELECTED']); + return; + } + + $sync_topics = array($topic_id, $to_topic_id); + + $topic_data = phpbb_get_topic_data($sync_topics, 'm_merge'); + + if (!count($topic_data) || empty($topic_data[$to_topic_id])) + { + $template->assign_var('MESSAGE', $user->lang['NO_FINAL_TOPIC_SELECTED']); + return; + } + + $sync_forums = array(); + foreach ($topic_data as $data) + { + $sync_forums[$data['forum_id']] = $data['forum_id']; + } + + $topic_data = $topic_data[$to_topic_id]; + + $post_id_list = $request->variable('post_id_list', array(0)); + $start = $request->variable('start', 0); + + if (!count($post_id_list)) + { + $template->assign_var('MESSAGE', $user->lang['NO_POST_SELECTED']); + return; + } + + if (!phpbb_check_ids($post_id_list, POSTS_TABLE, 'post_id', array('m_merge'))) + { + return; + } + + $redirect = $request->variable('redirect', build_url(array('quickmod'))); + + $s_hidden_fields = build_hidden_fields(array( + 'i' => 'main', + 'post_id_list' => $post_id_list, + 'to_topic_id' => $to_topic_id, + 'mode' => 'topic_view', + 'action' => 'merge_posts', + 'start' => $start, + 'redirect' => $redirect, + 't' => $topic_id) + ); + $return_link = ''; + + if (confirm_box(true)) + { + $to_forum_id = $topic_data['forum_id']; + + move_posts($post_id_list, $to_topic_id, false); + + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_MERGE', false, array( + 'forum_id' => $to_forum_id, + 'topic_id' => $to_topic_id, + $topic_data['topic_title'] + )); + + // Message and return links + $success_msg = 'POSTS_MERGED_SUCCESS'; + + // Does the original topic still exist? If yes, link back to it + $sql = 'SELECT forum_id + FROM ' . POSTS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $return_link .= sprintf($user->lang['RETURN_TOPIC'], '', ''); + } + else + { + if (!function_exists('phpbb_update_rows_avoiding_duplicates_notify_status')) + { + include($phpbb_root_path . 'includes/functions_database_helper.' . $phpEx); + } + + // If the topic no longer exist, we will update the topic watch table. + phpbb_update_rows_avoiding_duplicates_notify_status($db, TOPICS_WATCH_TABLE, 'topic_id', array($topic_id), $to_topic_id); + + // If the topic no longer exist, we will update the bookmarks table. + phpbb_update_rows_avoiding_duplicates($db, BOOKMARKS_TABLE, 'topic_id', array($topic_id), $to_topic_id); + } + + // Re-sync the topics and forums because the auto-sync was deactivated in the call of move_posts() + sync('topic_reported', 'topic_id', $sync_topics); + sync('topic_attachment', 'topic_id', $sync_topics); + sync('topic', 'topic_id', $sync_topics, true); + sync('forum', 'forum_id', $sync_forums, true, true); + + // Link to the new topic + $return_link .= (($return_link) ? '

' : '') . sprintf($user->lang['RETURN_NEW_TOPIC'], '', ''); + $redirect = $request->variable('redirect', "{$phpbb_root_path}viewtopic.$phpEx?f=$to_forum_id&t=$to_topic_id"); + $redirect = reapply_sid($redirect); + + /** + * Perform additional actions after merging posts. + * + * @event core.mcp_topics_merge_posts_after + * @var int topic_id The topic ID from which posts are being moved + * @var int to_topic_id The topic ID to which posts are being moved + * @since 3.1.11-RC1 + */ + $vars = array( + 'topic_id', + 'to_topic_id', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_topics_merge_posts_after', compact($vars))); + + meta_refresh(3, $redirect); + trigger_error($user->lang[$success_msg] . '

' . $return_link); + } + else + { + confirm_box(false, 'MERGE_POSTS', $s_hidden_fields); + } +} diff --git a/includes/mcp/mcp_warn.php b/includes/mcp/mcp_warn.php new file mode 100644 index 0000000..df17513 --- /dev/null +++ b/includes/mcp/mcp_warn.php @@ -0,0 +1,610 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* mcp_warn +* Handling warning the users +*/ +class mcp_warn +{ + var $p_master; + var $u_action; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $request; + + $action = $request->variable('action', array('' => '')); + + if (is_array($action)) + { + list($action, ) = each($action); + } + + $this->page_title = 'MCP_WARN'; + + add_form_key('mcp_warn'); + + switch ($mode) + { + case 'front': + $this->mcp_warn_front_view(); + $this->tpl_name = 'mcp_warn_front'; + break; + + case 'list': + $this->mcp_warn_list_view($action); + $this->tpl_name = 'mcp_warn_list'; + break; + + case 'warn_post': + $this->mcp_warn_post_view($action); + $this->tpl_name = 'mcp_warn_post'; + break; + + case 'warn_user': + $this->mcp_warn_user_view($action); + $this->tpl_name = 'mcp_warn_user'; + break; + } + } + + /** + * Generates the summary on the main page of the warning module + */ + function mcp_warn_front_view() + { + global $phpEx, $phpbb_root_path; + global $template, $db, $user; + + $template->assign_vars(array( + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=mcp&field=username&select_single=true'), + 'U_POST_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_user'), + )); + + // Obtain a list of the 5 naughtiest users.... + // These are the 5 users with the highest warning count + $highest = array(); + $count = 0; + + view_warned_users($highest, $count, 5); + + foreach ($highest as $row) + { + $template->assign_block_vars('highest', array( + 'U_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $row['user_id']), + + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + + 'WARNING_TIME' => $user->format_date($row['user_last_warning']), + 'WARNINGS' => $row['user_warnings'], + )); + } + + // And now the 5 most recent users to get in trouble + $sql = 'SELECT u.user_id, u.username, u.username_clean, u.user_colour, u.user_warnings, w.warning_time + FROM ' . USERS_TABLE . ' u, ' . WARNINGS_TABLE . ' w + WHERE u.user_id = w.user_id + ORDER BY w.warning_time DESC'; + $result = $db->sql_query_limit($sql, 5); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('latest', array( + 'U_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $row['user_id']), + + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + + 'WARNING_TIME' => $user->format_date($row['warning_time']), + 'WARNINGS' => $row['user_warnings'], + )); + } + $db->sql_freeresult($result); + } + + /** + * Lists all users with warnings + */ + function mcp_warn_list_view($action) + { + global $phpEx, $phpbb_root_path, $config, $phpbb_container; + global $template, $user, $auth, $request; + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $user->add_lang('memberlist'); + + $start = $request->variable('start', 0); + $st = $request->variable('st', 0); + $sk = $request->variable('sk', 'b'); + $sd = $request->variable('sd', 'd'); + + $limit_days = array(0 => $user->lang['ALL_ENTRIES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + $sort_by_text = array('a' => $user->lang['SORT_USERNAME'], 'b' => $user->lang['SORT_DATE'], 'c' => $user->lang['SORT_WARNINGS']); + $sort_by_sql = array('a' => 'username_clean', 'b' => 'user_last_warning', 'c' => 'user_warnings'); + + $s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + gen_sort_selects($limit_days, $sort_by_text, $st, $sk, $sd, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param); + + // Define where and sort sql for use in displaying logs + $sql_where = ($st) ? (time() - ($st * 86400)) : 0; + $sql_sort = $sort_by_sql[$sk] . ' ' . (($sd == 'd') ? 'DESC' : 'ASC'); + + $users = array(); + $user_count = 0; + + view_warned_users($users, $user_count, $config['topics_per_page'], $start, $sql_where, $sql_sort); + + foreach ($users as $row) + { + $template->assign_block_vars('user', array( + 'U_NOTES' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $row['user_id']), + + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + + 'WARNING_TIME' => $user->format_date($row['user_last_warning']), + 'WARNINGS' => $row['user_warnings'], + )); + } + + $base_url = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=warn&mode=list&st=$st&sk=$sk&sd=$sd"); + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $user_count, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'U_POST_ACTION' => $this->u_action, + 'S_CLEAR_ALLOWED' => ($auth->acl_get('a_clearlogs')) ? true : false, + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DAYS' => $s_limit_days, + + 'TOTAL_USERS' => $user->lang('LIST_USERS', (int) $user_count), + )); + } + + /** + * Handles warning the user when the warning is for a specific post + */ + function mcp_warn_post_view($action) + { + global $phpEx, $phpbb_root_path, $config, $request; + global $template, $db, $user, $phpbb_dispatcher; + + $post_id = $request->variable('p', 0); + $forum_id = $request->variable('f', 0); + $notify = (isset($_REQUEST['notify_user'])) ? true : false; + $warning = $request->variable('warning', '', true); + + $sql = 'SELECT u.*, p.* + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . " u + WHERE p.post_id = $post_id + AND u.user_id = p.poster_id"; + $result = $db->sql_query($sql); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$user_row) + { + trigger_error('NO_POST'); + } + + // There is no point issuing a warning to ignored users (ie anonymous and bots) + if ($user_row['user_type'] == USER_IGNORE) + { + trigger_error('CANNOT_WARN_ANONYMOUS'); + } + + // Prevent someone from warning themselves + if ($user_row['user_id'] == $user->data['user_id']) + { + trigger_error('CANNOT_WARN_SELF'); + } + + // Check if there is already a warning for this post to prevent multiple + // warnings for the same offence + $sql = 'SELECT post_id + FROM ' . WARNINGS_TABLE . " + WHERE post_id = $post_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + trigger_error('ALREADY_WARNED'); + } + + $user_id = $user_row['user_id']; + + if (strpos($this->u_action, "&f=$forum_id&p=$post_id") === false) + { + $this->p_master->adjust_url("&f=$forum_id&p=$post_id"); + $this->u_action .= "&f=$forum_id&p=$post_id"; + } + + // Check if can send a notification + if ($config['allow_privmsg']) + { + $auth2 = new \phpbb\auth\auth(); + $auth2->acl($user_row); + $s_can_notify = ($auth2->acl_get('u_readpm')) ? true : false; + unset($auth2); + } + else + { + $s_can_notify = false; + } + + // Prevent against clever people + if ($notify && !$s_can_notify) + { + $notify = false; + } + + if ($warning && $action == 'add_warning') + { + if (check_form_key('mcp_warn')) + { + $s_mcp_warn_post = true; + + /** + * Event for before warning a user for a post. + * + * @event core.mcp_warn_post_before + * @var array user_row The entire user row + * @var string warning The warning message + * @var bool notify If true, we notify the user for the warning + * @var int post_id The post id for which the warning is added + * @var bool s_mcp_warn_post If true, we add the warning else we omit it + * @since 3.1.0-b4 + */ + $vars = array( + 'user_row', + 'warning', + 'notify', + 'post_id', + 's_mcp_warn_post', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_warn_post_before', compact($vars))); + + if ($s_mcp_warn_post) + { + add_warning($user_row, $warning, $notify, $post_id); + $message = $user->lang['USER_WARNING_ADDED']; + + /** + * Event for after warning a user for a post. + * + * @event core.mcp_warn_post_after + * @var array user_row The entire user row + * @var string warning The warning message + * @var bool notify If true, the user was notified for the warning + * @var int post_id The post id for which the warning is added + * @var string message Message displayed to the moderator + * @since 3.1.0-b4 + */ + $vars = array( + 'user_row', + 'warning', + 'notify', + 'post_id', + 'message', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_warn_post_after', compact($vars))); + } + } + else + { + $message = $user->lang['FORM_INVALID']; + } + + if (!empty($message)) + { + $redirect = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=notes&mode=user_notes&u=$user_id"); + meta_refresh(2, $redirect); + trigger_error($message . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + } + } + + // OK, they didn't submit a warning so lets build the page for them to do so + + // We want to make the message available here as a reminder + // Parse the message and subject + $parse_flags = OPTION_FLAG_SMILIES | ($user_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0); + $message = generate_text_for_display($user_row['post_text'], $user_row['bbcode_uid'], $user_row['bbcode_bitfield'], $parse_flags, true); + + // Generate the appropriate user information for the user we are looking at + if (!function_exists('phpbb_get_user_rank')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + $user_rank_data = phpbb_get_user_rank($user_row, $user_row['user_posts']); + $avatar_img = phpbb_get_user_avatar($user_row); + + $template->assign_vars(array( + 'U_POST_ACTION' => $this->u_action, + + 'POST' => $message, + 'USERNAME' => $user_row['username'], + 'USER_COLOR' => (!empty($user_row['user_colour'])) ? $user_row['user_colour'] : '', + 'RANK_TITLE' => $user_rank_data['title'], + 'JOINED' => $user->format_date($user_row['user_regdate']), + 'POSTS' => ($user_row['user_posts']) ? $user_row['user_posts'] : 0, + 'WARNINGS' => ($user_row['user_warnings']) ? $user_row['user_warnings'] : 0, + + 'AVATAR_IMG' => $avatar_img, + 'RANK_IMG' => $user_rank_data['img'], + + 'L_WARNING_POST_DEFAULT' => sprintf($user->lang['WARNING_POST_DEFAULT'], generate_board_url() . "/viewtopic.$phpEx?f=$forum_id&p=$post_id#p$post_id"), + + 'S_CAN_NOTIFY' => $s_can_notify, + )); + } + + /** + * Handles warning the user + */ + function mcp_warn_user_view($action) + { + global $phpEx, $phpbb_root_path, $config, $request; + global $template, $db, $user, $phpbb_dispatcher; + + $user_id = $request->variable('u', 0); + $username = $request->variable('username', '', true); + $notify = (isset($_REQUEST['notify_user'])) ? true : false; + $warning = $request->variable('warning', '', true); + + $sql_where = ($user_id) ? "user_id = $user_id" : "username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE ' . $sql_where; + $result = $db->sql_query($sql); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$user_row) + { + trigger_error('NO_USER'); + } + + // Prevent someone from warning themselves + if ($user_row['user_id'] == $user->data['user_id']) + { + trigger_error('CANNOT_WARN_SELF'); + } + + $user_id = $user_row['user_id']; + + if (strpos($this->u_action, "&u=$user_id") === false) + { + $this->p_master->adjust_url('&u=' . $user_id); + $this->u_action .= "&u=$user_id"; + } + + // Check if can send a notification + if ($config['allow_privmsg']) + { + $auth2 = new \phpbb\auth\auth(); + $auth2->acl($user_row); + $s_can_notify = ($auth2->acl_get('u_readpm')) ? true : false; + unset($auth2); + } + else + { + $s_can_notify = false; + } + + // Prevent against clever people + if ($notify && !$s_can_notify) + { + $notify = false; + } + + if ($warning && $action == 'add_warning') + { + if (check_form_key('mcp_warn')) + { + $s_mcp_warn_user = true; + + /** + * Event for before warning a user from MCP. + * + * @event core.mcp_warn_user_before + * @var array user_row The entire user row + * @var string warning The warning message + * @var bool notify If true, we notify the user for the warning + * @var bool s_mcp_warn_user If true, we add the warning else we omit it + * @since 3.1.0-b4 + */ + $vars = array( + 'user_row', + 'warning', + 'notify', + 's_mcp_warn_user', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_warn_user_before', compact($vars))); + + if ($s_mcp_warn_user) + { + add_warning($user_row, $warning, $notify); + $message = $user->lang['USER_WARNING_ADDED']; + + /** + * Event for after warning a user from MCP. + * + * @event core.mcp_warn_user_after + * @var array user_row The entire user row + * @var string warning The warning message + * @var bool notify If true, the user was notified for the warning + * @var string message Message displayed to the moderator + * @since 3.1.0-b4 + */ + $vars = array( + 'user_row', + 'warning', + 'notify', + 'message', + ); + extract($phpbb_dispatcher->trigger_event('core.mcp_warn_user_after', compact($vars))); + } + } + else + { + $message = $user->lang['FORM_INVALID']; + } + + if (!empty($message)) + { + $redirect = append_sid("{$phpbb_root_path}mcp.$phpEx", "i=notes&mode=user_notes&u=$user_id"); + meta_refresh(2, $redirect); + trigger_error($message . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + } + } + + // Generate the appropriate user information for the user we are looking at + if (!function_exists('phpbb_get_user_rank')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + $user_rank_data = phpbb_get_user_rank($user_row, $user_row['user_posts']); + $avatar_img = phpbb_get_user_avatar($user_row); + + // OK, they didn't submit a warning so lets build the page for them to do so + $template->assign_vars(array( + 'U_POST_ACTION' => $this->u_action, + + 'RANK_TITLE' => $user_rank_data['title'], + 'JOINED' => $user->format_date($user_row['user_regdate']), + 'POSTS' => ($user_row['user_posts']) ? $user_row['user_posts'] : 0, + 'WARNINGS' => ($user_row['user_warnings']) ? $user_row['user_warnings'] : 0, + + 'USERNAME_FULL' => get_username_string('full', $user_row['user_id'], $user_row['username'], $user_row['user_colour']), + 'USERNAME_COLOUR' => get_username_string('colour', $user_row['user_id'], $user_row['username'], $user_row['user_colour']), + 'USERNAME' => get_username_string('username', $user_row['user_id'], $user_row['username'], $user_row['user_colour']), + 'U_PROFILE' => get_username_string('profile', $user_row['user_id'], $user_row['username'], $user_row['user_colour']), + + 'AVATAR_IMG' => $avatar_img, + 'RANK_IMG' => $user_rank_data['img'], + + 'S_CAN_NOTIFY' => $s_can_notify, + )); + + return $user_id; + } +} + +/** +* Insert the warning into the database +*/ +function add_warning($user_row, $warning, $send_pm = true, $post_id = 0) +{ + global $phpEx, $phpbb_root_path, $config, $phpbb_log; + global $db, $user; + + if ($send_pm) + { + include_once($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); + include_once($phpbb_root_path . 'includes/message_parser.' . $phpEx); + + // Attempt to translate warning to language of user being warned if user's language differs from issuer's language + if ($user_row['user_lang'] != $user->lang_name) + { + $lang = array(); + + $user_row['user_lang'] = (file_exists($phpbb_root_path . 'language/' . basename($user_row['user_lang']) . "/mcp." . $phpEx)) ? $user_row['user_lang'] : $config['default_lang']; + include($phpbb_root_path . 'language/' . basename($user_row['user_lang']) . "/mcp." . $phpEx); + + $warn_pm_subject = $lang['WARNING_PM_SUBJECT']; + $warn_pm_body = sprintf($lang['WARNING_PM_BODY'], $warning); + + unset($lang); + } + else + { + $warn_pm_subject = $user->lang('WARNING_PM_SUBJECT'); + $warn_pm_body = $user->lang('WARNING_PM_BODY', $warning); + } + + $message_parser = new parse_message(); + + $message_parser->message = $warn_pm_body; + $message_parser->parse(true, true, true, false, false, true, true); + + $pm_data = array( + 'from_user_id' => $user->data['user_id'], + 'from_user_ip' => $user->ip, + 'from_username' => $user->data['username'], + 'enable_sig' => false, + 'enable_bbcode' => true, + 'enable_smilies' => true, + 'enable_urls' => false, + 'icon_id' => 0, + 'bbcode_bitfield' => $message_parser->bbcode_bitfield, + 'bbcode_uid' => $message_parser->bbcode_uid, + 'message' => $message_parser->message, + 'address_list' => array('u' => array($user_row['user_id'] => 'to')), + ); + + submit_pm('post', $warn_pm_subject, $pm_data, false); + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_WARNING', false, array($user_row['username'])); + $log_id = $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_WARNING_BODY', false, array( + 'reportee_id' => $user_row['user_id'], + $warning + )); + + $sql_ary = array( + 'user_id' => $user_row['user_id'], + 'post_id' => $post_id, + 'log_id' => $log_id, + 'warning_time' => time(), + ); + + $db->sql_query('INSERT INTO ' . WARNINGS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_warnings = user_warnings + 1, + user_last_warning = ' . time() . ' + WHERE user_id = ' . $user_row['user_id']; + $db->sql_query($sql); + + // We add this to the mod log too for moderators to see that a specific user got warned. + $sql = 'SELECT forum_id, topic_id + FROM ' . POSTS_TABLE . ' + WHERE post_id = ' . $post_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $phpbb_log->add('mod', $user->data['user_id'], $user->ip, 'LOG_USER_WARNING', false, array( + 'forum_id' => $row['forum_id'], + 'topic_id' => $row['topic_id'], + 'post_id' => $post_id, + $user_row['username'] + )); +} diff --git a/includes/message_parser.php b/includes/message_parser.php new file mode 100644 index 0000000..0b79cca --- /dev/null +++ b/includes/message_parser.php @@ -0,0 +1,2059 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (!class_exists('bbcode')) +{ + // The following lines are for extensions which include message_parser.php + // while $phpbb_root_path and $phpEx are out of the script scope + // which may lead to the 'Undefined variable' and 'failed to open stream' errors + if (!isset($phpbb_root_path)) + { + global $phpbb_root_path; + } + + if (!isset($phpEx)) + { + global $phpEx; + } + + include($phpbb_root_path . 'includes/bbcode.' . $phpEx); +} + +/** +* BBCODE FIRSTPASS +* BBCODE first pass class (functions for parsing messages for db storage) +*/ +class bbcode_firstpass extends bbcode +{ + var $message = ''; + var $warn_msg = array(); + var $parsed_items = array(); + + /** + * Parse BBCode + */ + function parse_bbcode() + { + if (!$this->bbcodes) + { + $this->bbcode_init(); + } + + global $user; + + $this->bbcode_bitfield = ''; + $bitfield = new bitfield(); + + foreach ($this->bbcodes as $bbcode_name => $bbcode_data) + { + if (isset($bbcode_data['disabled']) && $bbcode_data['disabled']) + { + foreach ($bbcode_data['regexp'] as $regexp => $replacement) + { + if (preg_match($regexp, $this->message)) + { + $this->warn_msg[] = sprintf($user->lang['UNAUTHORISED_BBCODE'] , '[' . $bbcode_name . ']'); + continue; + } + } + } + else + { + foreach ($bbcode_data['regexp'] as $regexp => $replacement) + { + // The pattern gets compiled and cached by the PCRE extension, + // it should not demand recompilation + if (preg_match($regexp, $this->message)) + { + if (is_callable($replacement)) + { + $this->message = preg_replace_callback($regexp, $replacement, $this->message); + } + else + { + $this->message = preg_replace($regexp, $replacement, $this->message); + } + $bitfield->set($bbcode_data['bbcode_id']); + } + } + } + } + + $this->bbcode_bitfield = $bitfield->get_base64(); + } + + /** + * Prepare some bbcodes for better parsing + */ + function prepare_bbcodes() + { + // Ok, seems like users instead want the no-parsing of urls, smilies, etc. after and before and within quote tags being tagged as "not a bug". + // Fine by me ;) Will ease our live... but do not come back and cry at us, we won't hear you. + + /* Add newline at the end and in front of each quote block to prevent parsing errors (urls, smilies, etc.) + if (strpos($this->message, '[quote') !== false && strpos($this->message, '[/quote]') !== false) + { + $this->message = str_replace("\r\n", "\n", $this->message); + + // We strip newlines and spaces after and before quotes in quotes (trimming) and then add exactly one newline + $this->message = preg_replace('#\[quote(=".*?")?\]\s*(.*?)\s*\[/quote\]#siu', '[quote\1]' . "\n" . '\2' ."\n[/quote]", $this->message); + } + */ + + // Add other checks which needs to be placed before actually parsing anything (be it bbcodes, smilies, urls...) + } + + /** + * Init bbcode data for later parsing + */ + function bbcode_init($allow_custom_bbcode = true) + { + global $phpbb_dispatcher; + + static $rowset; + + $bbcode_class = $this; + + // This array holds all bbcode data. BBCodes will be processed in this + // order, so it is important to keep [code] in first position and + // [quote] in second position. + // To parse multiline URL we enable dotall option setting only for URL text + // but not for link itself, thus [url][/url] is not affected. + // + // To perform custom validation in extension, use $this->validate_bbcode_by_extension() + // method which accepts variable number of parameters + $this->bbcodes = array( + 'code' => array('bbcode_id' => BBCODE_ID_CODE, 'regexp' => array('#\[code(?:=([a-z]+))?\](.+\[/code\])#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_code($match[1], $match[2]); + } + )), + 'quote' => array('bbcode_id' => BBCODE_ID_QUOTE, 'regexp' => array('#\[quote(?:="(.*?)")?\](.+)\[/quote\]#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_quote($match[0]); + } + )), + 'attachment' => array('bbcode_id' => BBCODE_ID_ATTACH, 'regexp' => array('#\[attachment=([0-9]+)\](.*?)\[/attachment\]#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_attachment($match[1], $match[2]); + } + )), + 'b' => array('bbcode_id' => BBCODE_ID_B, 'regexp' => array('#\[b\](.*?)\[/b\]#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_strong($match[1]); + } + )), + 'i' => array('bbcode_id' => BBCODE_ID_I, 'regexp' => array('#\[i\](.*?)\[/i\]#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_italic($match[1]); + } + )), + 'url' => array('bbcode_id' => BBCODE_ID_URL, 'regexp' => array('#\[url(=(.*))?\](?(1)((?s).*(?-s))|(.*))\[/url\]#uiU' => function ($match) use($bbcode_class) + { + return $bbcode_class->validate_url($match[2], ($match[3]) ? $match[3] : $match[4]); + } + )), + 'img' => array('bbcode_id' => BBCODE_ID_IMG, 'regexp' => array('#\[img\](.*)\[/img\]#uiU' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_img($match[1]); + } + )), + 'size' => array('bbcode_id' => BBCODE_ID_SIZE, 'regexp' => array('#\[size=([\-\+]?\d+)\](.*?)\[/size\]#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_size($match[1], $match[2]); + } + )), + 'color' => array('bbcode_id' => BBCODE_ID_COLOR, 'regexp' => array('!\[color=(#[0-9a-f]{3}|#[0-9a-f]{6}|[a-z\-]+)\](.*?)\[/color\]!uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_color($match[1], $match[2]); + } + )), + 'u' => array('bbcode_id' => BBCODE_ID_U, 'regexp' => array('#\[u\](.*?)\[/u\]#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_underline($match[1]); + } + )), + 'list' => array('bbcode_id' => BBCODE_ID_LIST, 'regexp' => array('#\[list(?:=(?:[a-z0-9]|disc|circle|square))?].*\[/list]#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_parse_list($match[0]); + } + )), + 'email' => array('bbcode_id' => BBCODE_ID_EMAIL, 'regexp' => array('#\[email=?(.*?)?\](.*?)\[/email\]#uis' => function ($match) use($bbcode_class) + { + return $bbcode_class->validate_email($match[1], $match[2]); + } + )), + 'flash' => array('bbcode_id' => BBCODE_ID_FLASH, 'regexp' => array('#\[flash=([0-9]+),([0-9]+)\](.*?)\[/flash\]#ui' => function ($match) use($bbcode_class) + { + return $bbcode_class->bbcode_flash($match[1], $match[2], $match[3]); + } + )) + ); + + // Zero the parsed items array + $this->parsed_items = array(); + + foreach ($this->bbcodes as $tag => $bbcode_data) + { + $this->parsed_items[$tag] = 0; + } + + if (!$allow_custom_bbcode) + { + return; + } + + if (!is_array($rowset)) + { + global $db; + $rowset = array(); + + $sql = 'SELECT * + FROM ' . BBCODES_TABLE; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $rowset[] = $row; + } + $db->sql_freeresult($result); + } + + foreach ($rowset as $row) + { + $this->bbcodes[$row['bbcode_tag']] = array( + 'bbcode_id' => (int) $row['bbcode_id'], + 'regexp' => array($row['first_pass_match'] => str_replace('$uid', $this->bbcode_uid, $row['first_pass_replace'])) + ); + } + + $bbcodes = $this->bbcodes; + + /** + * Event to modify the bbcode data for later parsing + * + * @event core.modify_bbcode_init + * @var array bbcodes Array of bbcode data for use in parsing + * @var array rowset Array of bbcode data from the database + * @since 3.1.0-a3 + */ + $vars = array('bbcodes', 'rowset'); + extract($phpbb_dispatcher->trigger_event('core.modify_bbcode_init', compact($vars))); + + $this->bbcodes = $bbcodes; + } + + /** + * Making some pre-checks for bbcodes as well as increasing the number of parsed items + */ + function check_bbcode($bbcode, &$in) + { + // when using the /e modifier, preg_replace slashes double-quotes but does not + // seem to slash anything else + $in = str_replace("\r\n", "\n", str_replace('\"', '"', $in)); + + // Trimming here to make sure no empty bbcodes are parsed accidently + if (trim($in) == '') + { + return false; + } + + $this->parsed_items[$bbcode]++; + + return true; + } + + /** + * Transform some characters in valid bbcodes + */ + function bbcode_specialchars($text) + { + $str_from = array('<', '>', '[', ']', '.', ':'); + $str_to = array('<', '>', '[', ']', '.', ':'); + + return str_replace($str_from, $str_to, $text); + } + + /** + * Parse size tag + */ + function bbcode_size($stx, $in) + { + global $user, $config; + + if (!$this->check_bbcode('size', $in)) + { + return $in; + } + + if ($config['max_' . $this->mode . '_font_size'] && $config['max_' . $this->mode . '_font_size'] < $stx) + { + $this->warn_msg[] = $user->lang('MAX_FONT_SIZE_EXCEEDED', (int) $config['max_' . $this->mode . '_font_size']); + + return '[size=' . $stx . ']' . $in . '[/size]'; + } + + // Do not allow size=0 + if ($stx <= 0) + { + return '[size=' . $stx . ']' . $in . '[/size]'; + } + + return '[size=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/size:' . $this->bbcode_uid . ']'; + } + + /** + * Parse color tag + */ + function bbcode_color($stx, $in) + { + if (!$this->check_bbcode('color', $in)) + { + return $in; + } + + return '[color=' . $stx . ':' . $this->bbcode_uid . ']' . $in . '[/color:' . $this->bbcode_uid . ']'; + } + + /** + * Parse u tag + */ + function bbcode_underline($in) + { + if (!$this->check_bbcode('u', $in)) + { + return $in; + } + + return '[u:' . $this->bbcode_uid . ']' . $in . '[/u:' . $this->bbcode_uid . ']'; + } + + /** + * Parse b tag + */ + function bbcode_strong($in) + { + if (!$this->check_bbcode('b', $in)) + { + return $in; + } + + return '[b:' . $this->bbcode_uid . ']' . $in . '[/b:' . $this->bbcode_uid . ']'; + } + + /** + * Parse i tag + */ + function bbcode_italic($in) + { + if (!$this->check_bbcode('i', $in)) + { + return $in; + } + + return '[i:' . $this->bbcode_uid . ']' . $in . '[/i:' . $this->bbcode_uid . ']'; + } + + /** + * Parse img tag + */ + function bbcode_img($in) + { + global $user, $config; + + if (!$this->check_bbcode('img', $in)) + { + return $in; + } + + $in = trim($in); + $error = false; + + $in = str_replace(' ', '%20', $in); + + // Checking urls + if (!preg_match('#^' . get_preg_expression('url') . '$#iu', $in) && !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in)) + { + return '[img]' . $in . '[/img]'; + } + + // Try to cope with a common user error... not specifying a protocol but only a subdomain + if (!preg_match('#^[a-z0-9]+://#i', $in)) + { + $in = 'http://' . $in; + } + + if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width']) + { + $imagesize = new \FastImageSize\FastImageSize(); + $size_info = $imagesize->getImageSize(htmlspecialchars_decode($in)); + + if ($size_info === false) + { + $error = true; + $this->warn_msg[] = $user->lang['UNABLE_GET_IMAGE_SIZE']; + } + else + { + if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $size_info['height']) + { + $error = true; + $this->warn_msg[] = $user->lang('MAX_IMG_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']); + } + + if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $size_info['width']) + { + $error = true; + $this->warn_msg[] = $user->lang('MAX_IMG_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']); + } + } + } + + if ($error || $this->path_in_domain($in)) + { + return '[img]' . $in . '[/img]'; + } + + return '[img:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/img:' . $this->bbcode_uid . ']'; + } + + /** + * Parse flash tag + */ + function bbcode_flash($width, $height, $in) + { + global $user, $config; + + if (!$this->check_bbcode('flash', $in)) + { + return $in; + } + + $in = trim($in); + $error = false; + + // Do not allow 0-sizes generally being entered + if ($width <= 0 || $height <= 0) + { + return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]'; + } + + $in = str_replace(' ', '%20', $in); + + // Make sure $in is a URL. + if (!preg_match('#^' . get_preg_expression('url') . '$#iu', $in) && + !preg_match('#^' . get_preg_expression('www_url') . '$#iu', $in)) + { + return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]'; + } + + // Apply the same size checks on flash files as on images + if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width']) + { + if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $height) + { + $error = true; + $this->warn_msg[] = $user->lang('MAX_FLASH_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']); + } + + if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $width) + { + $error = true; + $this->warn_msg[] = $user->lang('MAX_FLASH_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']); + } + } + + if ($error || $this->path_in_domain($in)) + { + return '[flash=' . $width . ',' . $height . ']' . $in . '[/flash]'; + } + + return '[flash=' . $width . ',' . $height . ':' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($in) . '[/flash:' . $this->bbcode_uid . ']'; + } + + /** + * Parse inline attachments [ia] + */ + function bbcode_attachment($stx, $in) + { + if (!$this->check_bbcode('attachment', $in)) + { + return $in; + } + + return '[attachment=' . $stx . ':' . $this->bbcode_uid . ']' . trim($in) . '[/attachment:' . $this->bbcode_uid . ']'; + } + + /** + * Parse code text from code tag + * @access private + */ + function bbcode_parse_code($stx, &$code) + { + switch (strtolower($stx)) + { + case 'php': + + $remove_tags = false; + + $str_from = array('<', '>', '[', ']', '.', ':', ':'); + $str_to = array('<', '>', '[', ']', '.', ':', ':'); + $code = str_replace($str_from, $str_to, $code); + + if (!preg_match('/\<\?.*?\?\>/is', $code)) + { + $remove_tags = true; + $code = ""; + } + + $conf = array('highlight.bg', 'highlight.comment', 'highlight.default', 'highlight.html', 'highlight.keyword', 'highlight.string'); + foreach ($conf as $ini_var) + { + @ini_set($ini_var, str_replace('highlight.', 'syntax', $ini_var)); + } + + // Because highlight_string is specialcharing the text (but we already did this before), we have to reverse this in order to get correct results + $code = htmlspecialchars_decode($code); + $code = highlight_string($code, true); + + $str_from = array('', '', '','[', ']', '.', ':'); + $str_to = array('', '', '', '[', ']', '.', ':'); + + if ($remove_tags) + { + $str_from[] = '<?php '; + $str_to[] = ''; + $str_from[] = '<?php '; + $str_to[] = ''; + } + + $code = str_replace($str_from, $str_to, $code); + $code = preg_replace('#^()\n?(.*?)\n?()$#is', '$1$2$3', $code); + + if ($remove_tags) + { + $code = preg_replace('#()?\?>()#', '$1 $2', $code); + } + + $code = preg_replace('#^(.*)#s', '$2', $code); + $code = preg_replace('#(?:\s++| )*+$#u', '', $code); + + // remove newline at the end + if (!empty($code) && substr($code, -1) == "\n") + { + $code = substr($code, 0, -1); + } + + return "[code=$stx:" . $this->bbcode_uid . ']' . $code . '[/code:' . $this->bbcode_uid . ']'; + break; + + default: + return '[code:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($code) . '[/code:' . $this->bbcode_uid . ']'; + break; + } + } + + /** + * Parse code tag + * Expects the argument to start right after the opening [code] tag and to end with [/code] + */ + function bbcode_code($stx, $in) + { + if (!$this->check_bbcode('code', $in)) + { + return $in; + } + + // We remove the hardcoded elements from the code block here because it is not used in code blocks + // Having it here saves us one preg_replace per message containing [code] blocks + // Additionally, magic url parsing should go after parsing bbcodes, but for safety those are stripped out too... + $htm_match = get_preg_expression('bbcode_htm'); + unset($htm_match[4], $htm_match[5]); + $htm_replace = array('\1', '\1', '\2', '\1'); + + $out = $code_block = ''; + $open = 1; + + while ($in) + { + // Determine position and tag length of next code block + preg_match('#(.*?)(\[code(?:=([a-z]+))?\])(.+)#is', $in, $buffer); + $pos = (isset($buffer[1])) ? strlen($buffer[1]) : false; + $tag_length = (isset($buffer[2])) ? strlen($buffer[2]) : false; + + // Determine position of ending code tag + $pos2 = stripos($in, '[/code]'); + + // Which is the next block, ending code or code block + if ($pos !== false && $pos < $pos2) + { + // Open new block + if (!$open) + { + $out .= substr($in, 0, $pos); + $in = substr($in, $pos); + $stx = (isset($buffer[3])) ? $buffer[3] : ''; + $code_block = ''; + } + else + { + // Already opened block, just append to the current block + $code_block .= substr($in, 0, $pos) . ((isset($buffer[2])) ? $buffer[2] : ''); + $in = substr($in, $pos); + } + + $in = substr($in, $tag_length); + $open++; + } + else + { + // Close the block + if ($open == 1) + { + $code_block .= substr($in, 0, $pos2); + $code_block = preg_replace($htm_match, $htm_replace, $code_block); + + // Parse this code block + $out .= $this->bbcode_parse_code($stx, $code_block); + $code_block = ''; + $open--; + } + else if ($open) + { + // Close one open tag... add to the current code block + $code_block .= substr($in, 0, $pos2 + 7); + $open--; + } + else + { + // end code without opening code... will be always outside code block + $out .= substr($in, 0, $pos2 + 7); + } + + $in = substr($in, $pos2 + 7); + } + } + + // if now $code_block has contents we need to parse the remaining code while removing the last closing tag to match up. + if ($code_block) + { + $code_block = substr($code_block, 0, -7); + $code_block = preg_replace($htm_match, $htm_replace, $code_block); + + $out .= $this->bbcode_parse_code($stx, $code_block); + } + + return $out; + } + + /** + * Parse list bbcode + * Expects the argument to start with a tag + */ + function bbcode_parse_list($in) + { + if (!$this->check_bbcode('list', $in)) + { + return $in; + } + + // $tok holds characters to stop at. Since the string starts with a '[' we'll get everything up to the first ']' which should be the opening [list] tag + $tok = ']'; + $out = '['; + + // First character is [ + $in = substr($in, 1); + $list_end_tags = $item_end_tags = array(); + + do + { + $pos = strlen($in); + + for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i) + { + $tmp_pos = strpos($in, $tok[$i]); + + if ($tmp_pos !== false && $tmp_pos < $pos) + { + $pos = $tmp_pos; + } + } + + $buffer = substr($in, 0, $pos); + $tok = $in[$pos]; + + $in = substr($in, $pos + 1); + + if ($tok == ']') + { + // if $tok is ']' the buffer holds a tag + if (strtolower($buffer) == '/list' && count($list_end_tags)) + { + // valid [/list] tag, check nesting so that we don't hit false positives + if (count($item_end_tags) && count($item_end_tags) >= count($list_end_tags)) + { + // current li tag has not been closed + $out = preg_replace('/\n?\[$/', '[', $out) . array_pop($item_end_tags) . ']['; + } + + $out .= array_pop($list_end_tags) . ']'; + $tok = '['; + } + else if (preg_match('#^list(=[0-9a-z]+)?$#i', $buffer, $m)) + { + // sub-list, add a closing tag + if (empty($m[1]) || preg_match('/^=(?:disc|square|circle)$/i', $m[1])) + { + array_push($list_end_tags, '/list:u:' . $this->bbcode_uid); + } + else + { + array_push($list_end_tags, '/list:o:' . $this->bbcode_uid); + } + $out .= 'list' . substr($buffer, 4) . ':' . $this->bbcode_uid . ']'; + $tok = '['; + } + else + { + if (($buffer == '*' || substr($buffer, -2) == '[*') && count($list_end_tags)) + { + // the buffer holds a bullet tag and we have a [list] tag open + if (count($item_end_tags) >= count($list_end_tags)) + { + if (substr($buffer, -2) == '[*') + { + $out .= substr($buffer, 0, -2) . '['; + } + // current li tag has not been closed + if (preg_match('/\n\[$/', $out, $m)) + { + $out = preg_replace('/\n\[$/', '[', $out); + $buffer = array_pop($item_end_tags) . "]\n[*:" . $this->bbcode_uid; + } + else + { + $buffer = array_pop($item_end_tags) . '][*:' . $this->bbcode_uid; + } + } + else + { + $buffer = '*:' . $this->bbcode_uid; + } + + $item_end_tags[] = '/*:m:' . $this->bbcode_uid; + } + else if ($buffer == '/*') + { + array_pop($item_end_tags); + $buffer = '/*:' . $this->bbcode_uid; + } + + $out .= $buffer . $tok; + $tok = '[]'; + } + } + else + { + // Not within a tag, just add buffer to the return string + $out .= $buffer . $tok; + $tok = ($tok == '[') ? ']' : '[]'; + } + } + while ($in); + + // do we have some tags open? close them now + if (count($item_end_tags)) + { + $out .= '[' . implode('][', $item_end_tags) . ']'; + } + if (count($list_end_tags)) + { + $out .= '[' . implode('][', $list_end_tags) . ']'; + } + + return $out; + } + + /** + * Parse quote bbcode + * Expects the argument to start with a tag + */ + function bbcode_quote($in) + { + $in = str_replace("\r\n", "\n", str_replace('\"', '"', trim($in))); + + if (!$in) + { + return ''; + } + + // To let the parser not catch tokens within quote_username quotes we encode them before we start this... + $in = preg_replace_callback('#quote="(.*?)"\]#i', function ($match) { + return 'quote="' . str_replace(array('[', ']', '\\\"'), array('[', ']', '\"'), $match[1]) . '"]'; + }, $in); + + $tok = ']'; + $out = '['; + + $in = substr($in, 1); + $close_tags = $error_ary = array(); + $buffer = ''; + + do + { + $pos = strlen($in); + for ($i = 0, $tok_len = strlen($tok); $i < $tok_len; ++$i) + { + $tmp_pos = strpos($in, $tok[$i]); + if ($tmp_pos !== false && $tmp_pos < $pos) + { + $pos = $tmp_pos; + } + } + + $buffer .= substr($in, 0, $pos); + $tok = $in[$pos]; + $in = substr($in, $pos + 1); + + if ($tok == ']') + { + if (strtolower($buffer) == '/quote' && count($close_tags) && substr($out, -1, 1) == '[') + { + // we have found a closing tag + $out .= array_pop($close_tags) . ']'; + $tok = '['; + $buffer = ''; + + /* Add space at the end of the closing tag if not happened before to allow following urls/smilies to be parsed correctly + * Do not try to think for the user. :/ Do not parse urls/smilies if there is no space - is the same as with other bbcodes too. + * Also, we won't have any spaces within $in anyway, only adding up spaces -> #10982 + if (!$in || $in[0] !== ' ') + { + $out .= ' '; + }*/ + } + else if (preg_match('#^quote(?:="(.*?)")?$#is', $buffer, $m) && substr($out, -1, 1) == '[') + { + $this->parsed_items['quote']++; + array_push($close_tags, '/quote:' . $this->bbcode_uid); + + if (isset($m[1]) && $m[1]) + { + $username = str_replace(array('[', ']'), array('[', ']'), $m[1]); + $username = preg_replace('#\[(?!b|i|u|color|url|email|/b|/i|/u|/color|/url|/email)#iU', '[$1', $username); + + $end_tags = array(); + $error = false; + + preg_match_all('#\[((?:/)?(?:[a-z]+))#i', $username, $tags); + foreach ($tags[1] as $tag) + { + if ($tag[0] != '/') + { + $end_tags[] = '/' . $tag; + } + else + { + $end_tag = array_pop($end_tags); + $error = ($end_tag != $tag) ? true : false; + } + } + + if ($error) + { + $username = $m[1]; + } + + $out .= 'quote="' . $username . '":' . $this->bbcode_uid . ']'; + } + else + { + $out .= 'quote:' . $this->bbcode_uid . ']'; + } + + $tok = '['; + $buffer = ''; + } + else if (preg_match('#^quote="(.*?)#is', $buffer, $m)) + { + // the buffer holds an invalid opening tag + $buffer .= ']'; + } + else + { + $out .= $buffer . $tok; + $tok = '[]'; + $buffer = ''; + } + } + else + { +/** +* Old quote code working fine, but having errors listed in bug #3572 +* +* $out .= $buffer . $tok; +* $tok = ($tok == '[') ? ']' : '[]'; +* $buffer = ''; +*/ + + $out .= $buffer . $tok; + + if ($tok == '[') + { + // Search the text for the next tok... if an ending quote comes first, then change tok to [] + $pos1 = stripos($in, '[/quote'); + // If the token ] comes first, we change it to ] + $pos2 = strpos($in, ']'); + // If the token [ comes first, we change it to [ + $pos3 = strpos($in, '['); + + if ($pos1 !== false && ($pos2 === false || $pos1 < $pos2) && ($pos3 === false || $pos1 < $pos3)) + { + $tok = '[]'; + } + else if ($pos3 !== false && ($pos2 === false || $pos3 < $pos2)) + { + $tok = '['; + } + else + { + $tok = ']'; + } + } + else + { + $tok = '[]'; + } + $buffer = ''; + } + } + while ($in); + + $out .= $buffer; + + if (count($close_tags)) + { + $out .= '[' . implode('][', $close_tags) . ']'; + } + + foreach ($error_ary as $error_msg) + { + $this->warn_msg[] = $error_msg; + } + + return $out; + } + + /** + * Validate email + */ + function validate_email($var1, $var2) + { + $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1))); + $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2))); + + $txt = $var2; + $email = ($var1) ? $var1 : $var2; + + $validated = true; + + if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email)) + { + $validated = false; + } + + if (!$validated) + { + return '[email' . (($var1) ? "=$var1" : '') . ']' . $var2 . '[/email]'; + } + + $this->parsed_items['email']++; + + if ($var1) + { + $retval = '[email=' . $this->bbcode_specialchars($email) . ':' . $this->bbcode_uid . ']' . $txt . '[/email:' . $this->bbcode_uid . ']'; + } + else + { + $retval = '[email:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($email) . '[/email:' . $this->bbcode_uid . ']'; + } + + return $retval; + } + + /** + * Validate url + * + * @param string $var1 optional url parameter for url bbcode: [url(=$var1)]$var2[/url] + * @param string $var2 url bbcode content: [url(=$var1)]$var2[/url] + */ + function validate_url($var1, $var2) + { + $var1 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var1))); + $var2 = str_replace("\r\n", "\n", str_replace('\"', '"', trim($var2))); + + $url = ($var1) ? $var1 : $var2; + + if ($var1 && !$var2) + { + $var2 = $var1; + } + + if (!$url) + { + return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]'; + } + + $valid = false; + + $url = str_replace(' ', '%20', $url); + + // Checking urls + if (preg_match('#^' . get_preg_expression('url') . '$#iu', $url) || + preg_match('#^' . get_preg_expression('www_url') . '$#iu', $url) || + preg_match('#^' . preg_quote(generate_board_url(), '#') . get_preg_expression('relative_url') . '$#iu', $url)) + { + $valid = true; + } + + if ($valid) + { + $this->parsed_items['url']++; + + // if there is no scheme, then add http schema + if (!preg_match('#^[a-z][a-z\d+\-.]*:/{2}#i', $url)) + { + $url = 'http://' . $url; + } + + // Is this a link to somewhere inside this board? If so then remove the session id from the url + if (strpos($url, generate_board_url()) !== false && strpos($url, 'sid=') !== false) + { + $url = preg_replace('/(&|\?)sid=[0-9a-f]{32}&/', '\1', $url); + $url = preg_replace('/(&|\?)sid=[0-9a-f]{32}$/', '', $url); + $url = append_sid($url); + } + + return ($var1) ? '[url=' . $this->bbcode_specialchars($url) . ':' . $this->bbcode_uid . ']' . $var2 . '[/url:' . $this->bbcode_uid . ']' : '[url:' . $this->bbcode_uid . ']' . $this->bbcode_specialchars($url) . '[/url:' . $this->bbcode_uid . ']'; + } + + return '[url' . (($var1) ? '=' . $var1 : '') . ']' . $var2 . '[/url]'; + } + + /** + * Check if url is pointing to this domain/script_path/php-file + * + * @param string $url the url to check + * @return true if the url is pointing to this domain/script_path/php-file, false if not + * + * @access private + */ + function path_in_domain($url) + { + global $config, $phpEx, $user; + + if ($config['force_server_vars']) + { + $check_path = !empty($config['script_path']) ? $config['script_path'] : '/'; + } + else + { + $check_path = ($user->page['root_script_path'] != '/') ? substr($user->page['root_script_path'], 0, -1) : '/'; + } + + // Is the user trying to link to a php file in this domain and script path? + if (strpos($url, ".{$phpEx}") !== false && strpos($url, $check_path) !== false) + { + $server_name = $user->host; + + // Forcing server vars is the only way to specify/override the protocol + if ($config['force_server_vars'] || !$server_name) + { + $server_name = $config['server_name']; + } + + // Check again in correct order... + $pos_ext = strpos($url, ".{$phpEx}"); + $pos_path = strpos($url, $check_path); + $pos_domain = strpos($url, $server_name); + + if ($pos_domain !== false && $pos_path >= $pos_domain && $pos_ext >= $pos_path) + { + // Ok, actually we allow linking to some files (this may be able to be extended in some way later...) + if (strpos($url, '/' . $check_path . '/download/file.' . $phpEx) !== 0) + { + return false; + } + + return true; + } + } + + return false; + } +} + +/** +* Main message parser for posting, pm, etc. takes raw message +* and parses it for attachments, bbcode and smilies +*/ +class parse_message extends bbcode_firstpass +{ + var $attachment_data = array(); + var $filename_data = array(); + + // Helps ironing out user error + var $message_status = ''; + + var $allow_img_bbcode = true; + var $allow_flash_bbcode = true; + var $allow_quote_bbcode = true; + var $allow_url_bbcode = true; + + var $mode; + + /** + * The plupload object used for dealing with attachments + * @var \phpbb\plupload\plupload + */ + protected $plupload; + + /** + * Init - give message here or manually + */ + function __construct($message = '') + { + // Init BBCode UID + $this->bbcode_uid = substr(base_convert(unique_id(), 16, 36), 0, BBCODE_UID_LEN); + $this->message = $message; + } + + /** + * Parse Message + */ + function parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post') + { + global $config, $user, $phpbb_dispatcher, $phpbb_container; + + $this->mode = $mode; + + foreach (array('chars', 'smilies', 'urls', 'font_size', 'img_height', 'img_width') as $key) + { + if (!isset($config['max_' . $mode . '_' . $key])) + { + $config['max_' . $mode . '_' . $key] = 0; + } + } + + $this->allow_img_bbcode = $allow_img_bbcode; + $this->allow_flash_bbcode = $allow_flash_bbcode; + $this->allow_quote_bbcode = $allow_quote_bbcode; + $this->allow_url_bbcode = $allow_url_bbcode; + + // If false, then $this->message won't be altered, the text will be returned instead. + if (!$update_this_message) + { + $tmp_message = $this->message; + $return_message = &$this->message; + } + + if ($this->message_status == 'display') + { + $this->decode_message(); + } + + // Store message length... + $message_length = ($mode == 'post') ? utf8_strlen($this->message) : utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)); + + // Maximum message length check. 0 disables this check completely. + if ((int) $config['max_' . $mode . '_chars'] > 0 && $message_length > (int) $config['max_' . $mode . '_chars']) + { + $this->warn_msg[] = $user->lang('CHARS_' . strtoupper($mode) . '_CONTAINS', $message_length) . '
' . $user->lang('TOO_MANY_CHARS_LIMIT', (int) $config['max_' . $mode . '_chars']); + return (!$update_this_message) ? $return_message : $this->warn_msg; + } + + // Minimum message length check for post only + if ($mode === 'post') + { + if (!$message_length || $message_length < (int) $config['min_post_chars']) + { + $this->warn_msg[] = (!$message_length) ? $user->lang['TOO_FEW_CHARS'] : ($user->lang('CHARS_POST_CONTAINS', $message_length) . '
' . $user->lang('TOO_FEW_CHARS_LIMIT', (int) $config['min_post_chars'])); + return (!$update_this_message) ? $return_message : $this->warn_msg; + } + } + + /** + * This event can be used for additional message checks/cleanup before parsing + * + * @event core.message_parser_check_message + * @var bool allow_bbcode Do we allow BBCodes + * @var bool allow_magic_url Do we allow magic urls + * @var bool allow_smilies Do we allow smilies + * @var bool allow_img_bbcode Do we allow image BBCode + * @var bool allow_flash_bbcode Do we allow flash BBCode + * @var bool allow_quote_bbcode Do we allow quote BBCode + * @var bool allow_url_bbcode Do we allow url BBCode + * @var bool update_this_message Do we alter the parsed message + * @var string mode Posting mode + * @var string message The message text to parse + * @var string bbcode_bitfield The bbcode_bitfield before parsing + * @var string bbcode_uid The bbcode_uid before parsing + * @var bool return Do we return after the event is triggered if $warn_msg is not empty + * @var array warn_msg Array of the warning messages + * @since 3.1.2-RC1 + * @changed 3.1.3-RC1 Added vars $bbcode_bitfield and $bbcode_uid + */ + $message = $this->message; + $warn_msg = $this->warn_msg; + $return = false; + $bbcode_bitfield = $this->bbcode_bitfield; + $bbcode_uid = $this->bbcode_uid; + $vars = array( + 'allow_bbcode', + 'allow_magic_url', + 'allow_smilies', + 'allow_img_bbcode', + 'allow_flash_bbcode', + 'allow_quote_bbcode', + 'allow_url_bbcode', + 'update_this_message', + 'mode', + 'message', + 'bbcode_bitfield', + 'bbcode_uid', + 'return', + 'warn_msg', + ); + extract($phpbb_dispatcher->trigger_event('core.message_parser_check_message', compact($vars))); + $this->message = $message; + $this->warn_msg = $warn_msg; + $this->bbcode_bitfield = $bbcode_bitfield; + $this->bbcode_uid = $bbcode_uid; + if ($return && !empty($this->warn_msg)) + { + return (!$update_this_message) ? $return_message : $this->warn_msg; + } + + // Get the parser + $parser = $phpbb_container->get('text_formatter.parser'); + + // Set the parser's options + ($allow_bbcode) ? $parser->enable_bbcodes() : $parser->disable_bbcodes(); + ($allow_magic_url) ? $parser->enable_magic_url() : $parser->disable_magic_url(); + ($allow_smilies) ? $parser->enable_smilies() : $parser->disable_smilies(); + ($allow_img_bbcode) ? $parser->enable_bbcode('img') : $parser->disable_bbcode('img'); + ($allow_flash_bbcode) ? $parser->enable_bbcode('flash') : $parser->disable_bbcode('flash'); + ($allow_quote_bbcode) ? $parser->enable_bbcode('quote') : $parser->disable_bbcode('quote'); + ($allow_url_bbcode) ? $parser->enable_bbcode('url') : $parser->disable_bbcode('url'); + + // Set some config values + $parser->set_vars(array( + 'max_font_size' => $config['max_' . $this->mode . '_font_size'], + 'max_img_height' => $config['max_' . $this->mode . '_img_height'], + 'max_img_width' => $config['max_' . $this->mode . '_img_width'], + 'max_smilies' => $config['max_' . $this->mode . '_smilies'], + 'max_urls' => $config['max_' . $this->mode . '_urls'] + )); + + // Parse this message + $this->message = $parser->parse(htmlspecialchars_decode($this->message, ENT_QUOTES)); + + // Remove quotes that are nested too deep + if ($config['max_quote_depth'] > 0) + { + $this->remove_nested_quotes($config['max_quote_depth']); + } + + // Check for "empty" message. We do not check here for maximum length, because bbcode, smilies, etc. can add to the length. + // The maximum length check happened before any parsings. + if ($mode === 'post' && utf8_clean_string($this->message) === '') + { + $this->warn_msg[] = $user->lang['TOO_FEW_CHARS']; + return (!$update_this_message) ? $return_message : $this->warn_msg; + } + + // Remove quotes that are nested too deep + if ($config['max_quote_depth'] > 0) + { + $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode( + $this->message, + 'quote', + $config['max_quote_depth'] + ); + } + + // Check for errors + $errors = $parser->get_errors(); + if ($errors) + { + foreach ($errors as $i => $args) + { + // Translate each error with $user->lang() + $errors[$i] = call_user_func_array(array($user, 'lang'), $args); + } + $this->warn_msg = array_merge($this->warn_msg, $errors); + + return (!$update_this_message) ? $return_message : $this->warn_msg; + } + + if (!$update_this_message) + { + unset($this->message); + $this->message = $tmp_message; + return $return_message; + } + + $this->message_status = 'parsed'; + return false; + } + + /** + * Formatting text for display + */ + function format_display($allow_bbcode, $allow_magic_url, $allow_smilies, $update_this_message = true) + { + global $phpbb_container, $phpbb_dispatcher; + + // If false, then the parsed message get returned but internal message not processed. + if (!$update_this_message) + { + $tmp_message = $this->message; + $return_message = &$this->message; + } + + $text = $this->message; + $uid = $this->bbcode_uid; + + /** + * Event to modify the text before it is parsed + * + * @event core.modify_format_display_text_before + * @var string text The message text to parse + * @var string uid The bbcode uid + * @var bool allow_bbcode Do we allow bbcodes + * @var bool allow_magic_url Do we allow magic urls + * @var bool allow_smilies Do we allow smilies + * @var bool update_this_message Do we update the internal message + * with the parsed result + * @since 3.1.6-RC1 + */ + $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message'); + extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_before', compact($vars))); + + $this->message = $text; + $this->bbcode_uid = $uid; + unset($text, $uid); + + // NOTE: message_status is unreliable for detecting unparsed text because some callers + // change $this->message without resetting $this->message_status to 'plain' so we + // inspect the message instead + //if ($this->message_status == 'plain') + if (!preg_match('/^<[rt][ >]/', $this->message)) + { + // Force updating message - of course. + $this->parse($allow_bbcode, $allow_magic_url, $allow_smilies, $this->allow_img_bbcode, $this->allow_flash_bbcode, $this->allow_quote_bbcode, $this->allow_url_bbcode, true); + } + + // There's a bug when previewing a topic with no poll, because the empty title of the poll + // gets parsed but $this->message still ends up empty. This fixes it, until a proper fix is + // devised + if ($this->message === '') + { + $this->message = $phpbb_container->get('text_formatter.parser')->parse($this->message); + } + + $this->message = $phpbb_container->get('text_formatter.renderer')->render($this->message); + + $text = $this->message; + $uid = $this->bbcode_uid; + + /** + * Event to modify the text after it is parsed + * + * @event core.modify_format_display_text_after + * @var string text The message text to parse + * @var string uid The bbcode uid + * @var bool allow_bbcode Do we allow bbcodes + * @var bool allow_magic_url Do we allow magic urls + * @var bool allow_smilies Do we allow smilies + * @var bool update_this_message Do we update the internal message + * with the parsed result + * @since 3.1.0-a3 + */ + $vars = array('text', 'uid', 'allow_bbcode', 'allow_magic_url', 'allow_smilies', 'update_this_message'); + extract($phpbb_dispatcher->trigger_event('core.modify_format_display_text_after', compact($vars))); + + $this->message = $text; + $this->bbcode_uid = $uid; + + if (!$update_this_message) + { + unset($this->message); + $this->message = $tmp_message; + return $return_message; + } + + $this->message_status = 'display'; + return false; + } + + /** + * Decode message to be placed back into form box + */ + function decode_message($custom_bbcode_uid = '', $update_this_message = true) + { + // If false, then the parsed message get returned but internal message not processed. + if (!$update_this_message) + { + $tmp_message = $this->message; + $return_message = &$this->message; + } + + ($custom_bbcode_uid) ? decode_message($this->message, $custom_bbcode_uid) : decode_message($this->message, $this->bbcode_uid); + + if (!$update_this_message) + { + unset($this->message); + $this->message = $tmp_message; + return $return_message; + } + + $this->message_status = 'plain'; + return false; + } + + /** + * Replace magic urls of form http://xxx.xxx., www.xxx. and xxx@xxx.xxx. + * Cuts down displayed size of link if over 50 chars, turns absolute links + * into relative versions when the server/script path matches the link + */ + function magic_url($server_url) + { + // We use the global make_clickable function + $this->message = make_clickable($this->message, $server_url); + } + + /** + * Parse Smilies + */ + function smilies($max_smilies = 0) + { + global $db, $user; + static $match; + static $replace; + + // See if the static arrays have already been filled on an earlier invocation + if (!is_array($match)) + { + $match = $replace = array(); + + // NOTE: obtain_* function? chaching the table contents? + + // For now setting the ttl to 10 minutes + switch ($db->get_sql_layer()) + { + case 'mssql_odbc': + case 'mssqlnative': + $sql = 'SELECT * + FROM ' . SMILIES_TABLE . ' + ORDER BY LEN(code) DESC'; + break; + + // LENGTH supported by MySQL, IBM DB2, Oracle and Access for sure... + default: + $sql = 'SELECT * + FROM ' . SMILIES_TABLE . ' + ORDER BY LENGTH(code) DESC'; + break; + } + $result = $db->sql_query($sql, 600); + + while ($row = $db->sql_fetchrow($result)) + { + if (empty($row['code'])) + { + continue; + } + + // (assertion) + $match[] = preg_quote($row['code'], '#'); + $replace[] = '' . $row['code'] . ''; + } + $db->sql_freeresult($result); + } + + if (count($match)) + { + if ($max_smilies) + { + // 'u' modifier has been added to correctly parse smilies within unicode strings + // For details: http://tracker.phpbb.com/browse/PHPBB3-10117 + $num_matches = preg_match_all('#(?<=^|[\n .])(?:' . implode('|', $match) . ')(?![^<>]*>)#u', $this->message, $matches); + unset($matches); + + if ($num_matches !== false && $num_matches > $max_smilies) + { + $this->warn_msg[] = sprintf($user->lang['TOO_MANY_SMILIES'], $max_smilies); + return; + } + } + + // Make sure the delimiter # is added in front and at the end of every element within $match + // 'u' modifier has been added to correctly parse smilies within unicode strings + // For details: http://tracker.phpbb.com/browse/PHPBB3-10117 + + $this->message = trim(preg_replace(explode(chr(0), '#(?<=^|[\n .])' . implode('(?![^<>]*>)#u' . chr(0) . '#(?<=^|[\n .])', $match) . '(?![^<>]*>)#u'), $replace, $this->message)); + } + } + + /** + * Parse Attachments + */ + function parse_attachments($form_name, $mode, $forum_id, $submit, $preview, $refresh, $is_message = false) + { + global $config, $auth, $user, $phpbb_root_path, $phpEx, $db, $request; + global $phpbb_container, $phpbb_dispatcher; + + $error = array(); + + $num_attachments = count($this->attachment_data); + $this->filename_data['filecomment'] = $request->variable('filecomment', '', true); + $upload = $request->file($form_name); + $upload_file = (!empty($upload) && $upload['name'] !== 'none' && trim($upload['name'])); + + $add_file = (isset($_POST['add_file'])) ? true : false; + $delete_file = (isset($_POST['delete_file'])) ? true : false; + + // First of all adjust comments if changed + $actual_comment_list = $request->variable('comment_list', array(''), true); + + foreach ($actual_comment_list as $comment_key => $comment) + { + if (!isset($this->attachment_data[$comment_key])) + { + continue; + } + + if ($this->attachment_data[$comment_key]['attach_comment'] != $actual_comment_list[$comment_key]) + { + $this->attachment_data[$comment_key]['attach_comment'] = $actual_comment_list[$comment_key]; + } + } + + $cfg = array(); + $cfg['max_attachments'] = ($is_message) ? $config['max_attachments_pm'] : $config['max_attachments']; + $forum_id = ($is_message) ? 0 : $forum_id; + + if ($submit && in_array($mode, array('post', 'reply', 'quote', 'edit')) && $upload_file) + { + if ($num_attachments < $cfg['max_attachments'] || $auth->acl_get('a_') || $auth->acl_get('m_', $forum_id)) + { + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message); + $error = $filedata['error']; + + if ($filedata['post_attach'] && !count($error)) + { + $sql_ary = array( + 'physical_filename' => $filedata['physical_filename'], + 'attach_comment' => $this->filename_data['filecomment'], + 'real_filename' => $filedata['real_filename'], + 'extension' => $filedata['extension'], + 'mimetype' => $filedata['mimetype'], + 'filesize' => $filedata['filesize'], + 'filetime' => $filedata['filetime'], + 'thumbnail' => $filedata['thumbnail'], + 'is_orphan' => 1, + 'in_message' => ($is_message) ? 1 : 0, + 'poster_id' => $user->data['user_id'], + ); + + /** + * Modify attachment sql array on submit + * + * @event core.modify_attachment_sql_ary_on_submit + * @var array sql_ary Array containing SQL data + * @since 3.2.6-RC1 + */ + $vars = array('sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_submit', compact($vars))); + + $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + + $new_entry = array( + 'attach_id' => $db->sql_nextid(), + 'is_orphan' => 1, + 'real_filename' => $filedata['real_filename'], + 'attach_comment'=> $this->filename_data['filecomment'], + 'filesize' => $filedata['filesize'], + ); + + $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data); + + /** + * Modify attachment data on submit + * + * @event core.modify_attachment_data_on_submit + * @var array attachment_data Array containing attachment data + * @since 3.2.2-RC1 + */ + $attachment_data = $this->attachment_data; + $vars = array('attachment_data'); + extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_submit', compact($vars))); + $this->attachment_data = $attachment_data; + unset($attachment_data); + + $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) { + return '[attachment='.($match[1] + 1).']' . $match[2] . '[/attachment]'; + }, $this->message); + + $this->filename_data['filecomment'] = ''; + + // This Variable is set to false here, because Attachments are entered into the + // Database in two modes, one if the id_list is 0 and the second one if post_attach is true + // Since post_attach is automatically switched to true if an Attachment got added to the filesystem, + // but we are assigning an id of 0 here, we have to reset the post_attach variable to false. + // + // This is very relevant, because it could happen that the post got not submitted, but we do not + // know this circumstance here. We could be at the posting page or we could be redirected to the entered + // post. :) + $filedata['post_attach'] = false; + } + } + else + { + $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']); + } + } + + if ($preview || $refresh || count($error)) + { + if (isset($this->plupload) && $this->plupload->is_active()) + { + $json_response = new \phpbb\json_response(); + } + + // Perform actions on temporary attachments + if ($delete_file) + { + include_once($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + + $index = array_keys($request->variable('delete_file', array(0 => 0))); + $index = (!empty($index)) ? $index[0] : false; + + if ($index !== false && !empty($this->attachment_data[$index])) + { + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + + // delete selected attachment + if ($this->attachment_data[$index]['is_orphan']) + { + $sql = 'SELECT attach_id, physical_filename, thumbnail + FROM ' . ATTACHMENTS_TABLE . ' + WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id'] . ' + AND is_orphan = 1 + AND poster_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $attachment_manager->unlink($row['physical_filename'], 'file'); + + if ($row['thumbnail']) + { + $attachment_manager->unlink($row['physical_filename'], 'thumbnail'); + } + + $db->sql_query('DELETE FROM ' . ATTACHMENTS_TABLE . ' WHERE attach_id = ' . (int) $this->attachment_data[$index]['attach_id']); + } + } + else + { + $attachment_manager->delete('attach', $this->attachment_data[$index]['attach_id']); + } + + unset($this->attachment_data[$index]); + $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) use($index) { + return ($match[1] == $index) ? '' : (($match[1] > $index) ? '[attachment=' . ($match[1] - 1) . ']' . $match[2] . '[/attachment]' : $match[0]); + }, $this->message); + + // Reindex Array + $this->attachment_data = array_values($this->attachment_data); + if (isset($this->plupload) && $this->plupload->is_active()) + { + $json_response->send($this->attachment_data); + } + } + } + else if (($add_file || $preview) && $upload_file) + { + if ($num_attachments < $cfg['max_attachments'] || $auth->acl_gets('m_', 'a_', $forum_id)) + { + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $filedata = $attachment_manager->upload($form_name, $forum_id, false, '', $is_message); + $error = array_merge($error, $filedata['error']); + + if (!count($error)) + { + $sql_ary = array( + 'physical_filename' => $filedata['physical_filename'], + 'attach_comment' => $this->filename_data['filecomment'], + 'real_filename' => $filedata['real_filename'], + 'extension' => $filedata['extension'], + 'mimetype' => $filedata['mimetype'], + 'filesize' => $filedata['filesize'], + 'filetime' => $filedata['filetime'], + 'thumbnail' => $filedata['thumbnail'], + 'is_orphan' => 1, + 'in_message' => ($is_message) ? 1 : 0, + 'poster_id' => $user->data['user_id'], + ); + + /** + * Modify attachment sql array on upload + * + * @event core.modify_attachment_sql_ary_on_upload + * @var array sql_ary Array containing SQL data + * @since 3.2.6-RC1 + */ + $vars = array('sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.modify_attachment_sql_ary_on_upload', compact($vars))); + + $db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary)); + + $new_entry = array( + 'attach_id' => $db->sql_nextid(), + 'is_orphan' => 1, + 'real_filename' => $filedata['real_filename'], + 'attach_comment'=> $this->filename_data['filecomment'], + 'filesize' => $filedata['filesize'], + ); + + $this->attachment_data = array_merge(array(0 => $new_entry), $this->attachment_data); + + /** + * Modify attachment data on upload + * + * @event core.modify_attachment_data_on_upload + * @var array attachment_data Array containing attachment data + * @since 3.2.2-RC1 + */ + $attachment_data = $this->attachment_data; + $vars = array('attachment_data'); + extract($phpbb_dispatcher->trigger_event('core.modify_attachment_data_on_upload', compact($vars))); + $this->attachment_data = $attachment_data; + unset($attachment_data); + + $this->message = preg_replace_callback('#\[attachment=([0-9]+)\](.*?)\[\/attachment\]#', function ($match) { + return '[attachment=' . ($match[1] + 1) . ']' . $match[2] . '[/attachment]'; + }, $this->message); + $this->filename_data['filecomment'] = ''; + + if (isset($this->plupload) && $this->plupload->is_active()) + { + $download_url = append_sid("{$phpbb_root_path}download/file.{$phpEx}", 'mode=view&id=' . $new_entry['attach_id']); + + // Send the client the attachment data to maintain state + $json_response->send(array('data' => $this->attachment_data, 'download_url' => $download_url)); + } + } + } + else + { + $error[] = $user->lang('TOO_MANY_ATTACHMENTS', (int) $cfg['max_attachments']); + } + + if (!empty($error) && isset($this->plupload) && $this->plupload->is_active()) + { + // If this is a plupload (and thus ajax) request, give the + // client the first error we have + $json_response->send(array( + 'jsonrpc' => '2.0', + 'id' => 'id', + 'error' => array( + 'code' => 105, + 'message' => current($error), + ), + )); + } + } + } + + foreach ($error as $error_msg) + { + $this->warn_msg[] = $error_msg; + } + } + + /** + * Get Attachment Data + */ + function get_submitted_attachment_data($check_user_id = false) + { + global $user, $db; + global $request; + + $this->filename_data['filecomment'] = $request->variable('filecomment', '', true); + $attachment_data = $request->variable('attachment_data', array(0 => array('' => '')), true, \phpbb\request\request_interface::POST); + $this->attachment_data = array(); + + $check_user_id = ($check_user_id === false) ? $user->data['user_id'] : $check_user_id; + + if (!count($attachment_data)) + { + return; + } + + $not_orphan = $orphan = array(); + + foreach ($attachment_data as $pos => $var_ary) + { + if ($var_ary['is_orphan']) + { + $orphan[(int) $var_ary['attach_id']] = $pos; + } + else + { + $not_orphan[(int) $var_ary['attach_id']] = $pos; + } + } + + // Regenerate already posted attachments + if (count($not_orphan)) + { + // Get the attachment data, based on the poster id... + $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', array_keys($not_orphan)) . ' + AND poster_id = ' . $check_user_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $pos = $not_orphan[$row['attach_id']]; + $this->attachment_data[$pos] = $row; + $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment']; + + unset($not_orphan[$row['attach_id']]); + } + $db->sql_freeresult($result); + } + + if (count($not_orphan)) + { + trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR); + } + + // Regenerate newly uploaded attachments + if (count($orphan)) + { + $sql = 'SELECT attach_id, is_orphan, real_filename, attach_comment, filesize + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('attach_id', array_keys($orphan)) . ' + AND poster_id = ' . $user->data['user_id'] . ' + AND is_orphan = 1'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $pos = $orphan[$row['attach_id']]; + $this->attachment_data[$pos] = $row; + $this->attachment_data[$pos]['attach_comment'] = $attachment_data[$pos]['attach_comment']; + + unset($orphan[$row['attach_id']]); + } + $db->sql_freeresult($result); + } + + if (count($orphan)) + { + trigger_error('NO_ACCESS_ATTACHMENT', E_USER_ERROR); + } + + ksort($this->attachment_data); + } + + /** + * Parse Poll + */ + function parse_poll(&$poll) + { + global $user, $config; + + $poll_max_options = $poll['poll_max_options']; + + // Parse Poll Option text + $tmp_message = $this->message; + + $poll['poll_options'] = preg_split('/\s*?\n\s*/', trim($poll['poll_option_text'])); + $poll['poll_options_size'] = count($poll['poll_options']); + + foreach ($poll['poll_options'] as &$poll_option) + { + $this->message = $poll_option; + $poll_option = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false, 'poll'); + } + unset($poll_option); + $poll['poll_option_text'] = implode("\n", $poll['poll_options']); + + // Parse Poll Title + $this->message = $poll['poll_title']; + if (!$poll['poll_title'] && $poll['poll_options_size']) + { + $this->warn_msg[] = $user->lang['NO_POLL_TITLE']; + } + else + { + if (utf8_strlen(preg_replace('#\[\/?[a-z\*\+\-]+(=[\S]+)?\]#ius', ' ', $this->message)) > 100) + { + $this->warn_msg[] = $user->lang['POLL_TITLE_TOO_LONG']; + } + $poll['poll_title'] = $this->parse($poll['enable_bbcode'], ($config['allow_post_links']) ? $poll['enable_urls'] : false, $poll['enable_smilies'], $poll['img_status'], false, false, $config['allow_post_links'], false, 'poll'); + if (strlen($poll['poll_title']) > 255) + { + $this->warn_msg[] = $user->lang['POLL_TITLE_COMP_TOO_LONG']; + } + } + + if (count($poll['poll_options']) == 1) + { + $this->warn_msg[] = $user->lang['TOO_FEW_POLL_OPTIONS']; + } + else if ($poll['poll_options_size'] > (int) $config['max_poll_options']) + { + $this->warn_msg[] = $user->lang['TOO_MANY_POLL_OPTIONS']; + } + else if ($poll_max_options > $poll['poll_options_size']) + { + $this->warn_msg[] = $user->lang['TOO_MANY_USER_OPTIONS']; + } + + $poll['poll_max_options'] = ($poll['poll_max_options'] < 1) ? 1 : (($poll['poll_max_options'] > $config['max_poll_options']) ? $config['max_poll_options'] : $poll['poll_max_options']); + + $this->message = $tmp_message; + } + + /** + * Remove nested quotes at given depth in current parsed message + * + * @param integer $max_depth Depth limit + * @return null + */ + public function remove_nested_quotes($max_depth) + { + global $phpbb_container; + + if (preg_match('#^<[rt][ >]#', $this->message)) + { + $this->message = $phpbb_container->get('text_formatter.utils')->remove_bbcode( + $this->message, + 'quote', + $max_depth + ); + + return; + } + + // Capture all [quote] and [/quote] tags + preg_match_all('(\\[/?quote(?:="(.*?)")?:' . $this->bbcode_uid . '\\])', $this->message, $matches, PREG_OFFSET_CAPTURE); + + // Iterate over the quote tags to mark the ranges that must be removed + $depth = 0; + $ranges = array(); + $start_pos = 0; + foreach ($matches[0] as $match) + { + if ($match[0][1] === '/') + { + --$depth; + if ($depth == $max_depth) + { + $end_pos = $match[1] + strlen($match[0]); + $length = $end_pos - $start_pos; + $ranges[] = array($start_pos, $length); + } + } + else + { + ++$depth; + if ($depth == $max_depth + 1) + { + $start_pos = $match[1]; + } + } + } + + foreach (array_reverse($ranges) as $range) + { + list($start_pos, $length) = $range; + $this->message = substr_replace($this->message, '', $start_pos, $length); + } + } + + /** + * Setter function for passing the plupload object + * + * @param \phpbb\plupload\plupload $plupload The plupload object + * + * @return null + */ + public function set_plupload(\phpbb\plupload\plupload $plupload) + { + $this->plupload = $plupload; + } + + /** + * Function to perform custom bbcode validation by extensions + * can be used in bbcode_init() to assign regexp replacement + * Example: 'regexp' => array('#\[b\](.*?)\[/b\]#uise' => "\$this->validate_bbcode_by_extension('\$1')") + * + * Accepts variable number of parameters + * + * @return mixed Validation result + */ + public function validate_bbcode_by_extension() + { + global $phpbb_dispatcher; + + $return = false; + $params_array = func_get_args(); + + /** + * Event to validate bbcode with the custom validating methods + * provided by extensions + * + * @event core.validate_bbcode_by_extension + * @var array params_array Array with the function parameters + * @var mixed return Validation result to return + * + * @since 3.1.5-RC1 + */ + $vars = array('params_array', 'return'); + extract($phpbb_dispatcher->trigger_event('core.validate_bbcode_by_extension', compact($vars))); + + return $return; + } +} diff --git a/includes/questionnaire/questionnaire.php b/includes/questionnaire/questionnaire.php new file mode 100644 index 0000000..2f80582 --- /dev/null +++ b/includes/questionnaire/questionnaire.php @@ -0,0 +1,501 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* This class collects data which is used to create some usage statistics. +* +* The collected data is - after authorization of the administrator - submitted +* to a central server. For privacy reasons we try to collect only data which aren't private +* or don't give any information which might help to identify the user. +* +* @author Johannes Schlueter +* @copyright (c) 2007-2008 Johannes Schlueter +*/ +class phpbb_questionnaire_data_collector +{ + var $providers; + var $data = null; + var $install_id = ''; + + /** + * Constructor. + * + * @param string + */ + function __construct($install_id) + { + $this->install_id = $install_id; + $this->providers = array(); + } + + function add_data_provider($provider) + { + $this->providers[] = $provider; + } + + /** + * Get data as an array. + * + * @return array All Data + */ + function get_data_raw() + { + if (!$this->data) + { + $this->collect(); + } + + return $this->data; + } + + function get_data_for_form() + { + return base64_encode(serialize($this->get_data_raw())); + } + + /** + * Collect info into the data property. + * + * @return null + */ + function collect() + { + foreach (array_keys($this->providers) as $key) + { + $provider = $this->providers[$key]; + $this->data[$provider->get_identifier()] = $provider->get_data(); + } + $this->data['install_id'] = $this->install_id; + } +} + +/** interface: get_indentifier(), get_data() */ + +/** +* Questionnaire PHP data provider +*/ +class phpbb_questionnaire_php_data_provider +{ + function get_identifier() + { + return 'PHP'; + } + + /** + * Get data about the PHP runtime setup. + * + * @return array + */ + function get_data() + { + return array( + 'version' => PHP_VERSION, + 'sapi' => PHP_SAPI, + 'int_size' => defined('PHP_INT_SIZE') ? PHP_INT_SIZE : '', + 'safe_mode' => (int) @ini_get('safe_mode'), + 'open_basedir' => (int) @ini_get('open_basedir'), + 'memory_limit' => @ini_get('memory_limit'), + 'allow_url_fopen' => (int) @ini_get('allow_url_fopen'), + 'allow_url_include' => (int) @ini_get('allow_url_include'), + 'file_uploads' => (int) @ini_get('file_uploads'), + 'upload_max_filesize' => @ini_get('upload_max_filesize'), + 'post_max_size' => @ini_get('post_max_size'), + 'disable_functions' => @ini_get('disable_functions'), + 'disable_classes' => @ini_get('disable_classes'), + 'enable_dl' => (int) @ini_get('enable_dl'), + 'magic_quotes_gpc' => (int) @ini_get('magic_quotes_gpc'), + 'register_globals' => (int) @ini_get('register_globals'), + 'filter.default' => @ini_get('filter.default'), + 'zend.ze1_compatibility_mode' => (int) @ini_get('zend.ze1_compatibility_mode'), + 'unicode.semantics' => (int) @ini_get('unicode.semantics'), + 'zend_thread_safty' => (int) function_exists('zend_thread_id'), + 'extensions' => get_loaded_extensions(), + ); + } +} + +/** +* Questionnaire System data provider +*/ +class phpbb_questionnaire_system_data_provider +{ + function get_identifier() + { + return 'System'; + } + + /** + * Get data about the general system information, like OS or IP (shortened). + * + * @return array + */ + function get_data() + { + global $request; + + // Start discovering the IPV4 server address, if available + // Try apache, IIS, fall back to 0.0.0.0 + $server_address = htmlspecialchars_decode($request->server('SERVER_ADDR', $request->server('LOCAL_ADDR', '0.0.0.0'))); + + return array( + 'os' => PHP_OS, + 'httpd' => htmlspecialchars_decode($request->server('SERVER_SOFTWARE')), + // we don't want the real IP address (for privacy policy reasons) but only + // a network address to see whether your installation is running on a private or public network. + 'private_ip' => $this->is_private_ip($server_address), + 'ipv6' => strpos($server_address, ':') !== false, + ); + } + + /** + * Checks whether the given IP is in a private network. + * + * @param string $ip IP in v4 dot-decimal or v6 hex format + * @return bool true if the IP is from a private network, else false + */ + function is_private_ip($ip) + { + // IPv4 + if (strpos($ip, ':') === false) + { + $ip_address_ary = explode('.', $ip); + + // build ip + if (!isset($ip_address_ary[0]) || !isset($ip_address_ary[1])) + { + $ip_address_ary = explode('.', '0.0.0.0'); + } + + // IANA reserved addresses for private networks (RFC 1918) are: + // - 10.0.0.0/8 + // - 172.16.0.0/12 + // - 192.168.0.0/16 + if ($ip_address_ary[0] == '10' || + ($ip_address_ary[0] == '172' && intval($ip_address_ary[1]) > 15 && intval($ip_address_ary[1]) < 32) || + ($ip_address_ary[0] == '192' && $ip_address_ary[1] == '168')) + { + return true; + } + } + // IPv6 + else + { + // unique local unicast + $prefix = substr($ip, 0, 2); + if ($prefix == 'fc' || $prefix == 'fd') + { + return true; + } + } + + return false; + } +} + +/** +* Questionnaire phpBB data provider +*/ +class phpbb_questionnaire_phpbb_data_provider +{ + var $config; + var $unique_id; + + /** + * Constructor. + * + * @param array $config + */ + function __construct($config) + { + // generate a unique id if necessary + if (empty($config['questionnaire_unique_id'])) + { + $this->unique_id = unique_id(); + $config->set('questionnaire_unique_id', $this->unique_id); + } + else + { + $this->unique_id = $config['questionnaire_unique_id']; + } + + $this->config = $config; + } + + /** + * Returns a string identifier for this data provider + * + * @return string "phpBB" + */ + function get_identifier() + { + return 'phpBB'; + } + + /** + * Get data about this phpBB installation. + * + * @return array Relevant anonymous config options + */ + function get_data() + { + global $phpbb_config_php_file; + + extract($phpbb_config_php_file->get_all()); + unset($dbhost, $dbport, $dbname, $dbuser, $dbpasswd); // Just a precaution + + $dbms = $phpbb_config_php_file->convert_30_dbms_to_31($dbms); + + // Only send certain config vars + $config_vars = array( + 'active_sessions' => true, + 'allow_attachments' => true, + 'allow_autologin' => true, + 'allow_avatar' => true, + 'allow_avatar_local' => true, + 'allow_avatar_remote' => true, + 'allow_avatar_upload' => true, + 'allow_bbcode' => true, + 'allow_birthdays' => true, + 'allow_bookmarks' => true, + 'allow_emailreuse' => true, + 'allow_forum_notify' => true, + 'allow_mass_pm' => true, + 'allow_name_chars' => true, + 'allow_namechange' => true, + 'allow_nocensors' => true, + 'allow_pm_attach' => true, + 'allow_pm_report' => true, + 'allow_post_flash' => true, + 'allow_post_links' => true, + 'allow_privmsg' => true, + 'allow_quick_reply' => true, + 'allow_sig' => true, + 'allow_sig_bbcode' => true, + 'allow_sig_flash' => true, + 'allow_sig_img' => true, + 'allow_sig_links' => true, + 'allow_sig_pm' => true, + 'allow_sig_smilies' => true, + 'allow_smilies' => true, + 'allow_topic_notify' => true, + 'attachment_quota' => true, + 'auth_bbcode_pm' => true, + 'auth_flash_pm' => true, + 'auth_img_pm' => true, + 'auth_method' => true, + 'auth_smilies_pm' => true, + 'avatar_filesize' => true, + 'avatar_max_height' => true, + 'avatar_max_width' => true, + 'avatar_min_height' => true, + 'avatar_min_width' => true, + 'board_email_form' => true, + 'board_hide_emails' => true, + 'board_timezone' => true, + 'browser_check' => true, + 'bump_interval' => true, + 'bump_type' => true, + 'cache_gc' => true, + 'captcha_plugin' => true, + 'captcha_gd' => true, + 'captcha_gd_foreground_noise' => true, + 'captcha_gd_x_grid' => true, + 'captcha_gd_y_grid' => true, + 'captcha_gd_wave' => true, + 'captcha_gd_3d_noise' => true, + 'captcha_gd_fonts' => true, + 'confirm_refresh' => true, + 'check_attachment_content' => true, + 'check_dnsbl' => true, + 'chg_passforce' => true, + 'cookie_secure' => true, + 'coppa_enable' => true, + 'database_gc' => true, + 'dbms_version' => true, + 'default_dateformat' => true, + 'default_lang' => true, + 'display_last_edited' => true, + 'display_order' => true, + 'edit_time' => true, + 'email_check_mx' => true, + 'email_enable' => true, + 'email_force_sender' => true, + 'email_package_size' => true, + 'enable_confirm' => true, + 'enable_pm_icons' => true, + 'enable_post_confirm' => true, + 'feed_enable' => true, + 'feed_http_auth' => true, + 'feed_limit_post' => true, + 'feed_limit_topic' => true, + 'feed_overall' => true, + 'feed_overall_forums' => true, + 'feed_forum' => true, + 'feed_topic' => true, + 'feed_topics_new' => true, + 'feed_topics_active' => true, + 'feed_item_statistics' => true, + 'flood_interval' => true, + 'force_server_vars' => true, + 'form_token_lifetime' => true, + 'form_token_mintime' => true, + 'form_token_sid_guests' => true, + 'forward_pm' => true, + 'forwarded_for_check' => true, + 'full_folder_action' => true, + 'fulltext_native_common_thres' => true, + 'fulltext_native_load_upd' => true, + 'fulltext_native_max_chars' => true, + 'fulltext_native_min_chars' => true, + 'gzip_compress' => true, + 'hot_threshold' => true, + 'img_create_thumbnail' => true, + 'img_display_inlined' => true, + 'img_link_height' => true, + 'img_link_width' => true, + 'img_max_height' => true, + 'img_max_thumb_width' => true, + 'img_max_width' => true, + 'img_min_thumb_filesize' => true, + 'ip_check' => true, + 'jab_enable' => true, + 'jab_package_size' => true, + 'jab_use_ssl' => true, + 'limit_load' => true, + 'limit_search_load' => true, + 'load_anon_lastread' => true, + 'load_birthdays' => true, + 'load_cpf_memberlist' => true, + 'load_cpf_viewprofile' => true, + 'load_cpf_viewtopic' => true, + 'load_db_lastread' => true, + 'load_db_track' => true, + 'load_jumpbox' => true, + 'load_moderators' => true, + 'load_online' => true, + 'load_online_guests' => true, + 'load_online_time' => true, + 'load_onlinetrack' => true, + 'load_search' => true, + 'load_tplcompile' => true, + 'load_user_activity' => true, + 'max_attachments' => true, + 'max_attachments_pm' => true, + 'max_autologin_time' => true, + 'max_filesize' => true, + 'max_filesize_pm' => true, + 'max_login_attempts' => true, + 'max_name_chars' => true, + 'max_num_search_keywords' => true, + 'max_pass_chars' => true, + 'max_poll_options' => true, + 'max_post_chars' => true, + 'max_post_font_size' => true, + 'max_post_img_height' => true, + 'max_post_img_width' => true, + 'max_post_smilies' => true, + 'max_post_urls' => true, + 'max_quote_depth' => true, + 'max_reg_attempts' => true, + 'max_sig_chars' => true, + 'max_sig_font_size' => true, + 'max_sig_img_height' => true, + 'max_sig_img_width' => true, + 'max_sig_smilies' => true, + 'max_sig_urls' => true, + 'min_name_chars' => true, + 'min_pass_chars' => true, + 'min_post_chars' => true, + 'min_search_author_chars' => true, + 'mime_triggers' => true, + 'new_member_post_limit' => true, + 'new_member_group_default' => true, + 'override_user_style' => true, + 'pass_complex' => true, + 'pm_edit_time' => true, + 'pm_max_boxes' => true, + 'pm_max_msgs' => true, + 'pm_max_recipients' => true, + 'posts_per_page' => true, + 'print_pm' => true, + 'queue_interval' => true, + 'require_activation' => true, + 'referer_validation' => true, + 'search_block_size' => true, + 'search_gc' => true, + 'search_interval' => true, + 'search_anonymous_interval' => true, + 'search_type' => true, + 'search_store_results' => true, + 'secure_allow_deny' => true, + 'secure_allow_empty_referer' => true, + 'secure_downloads' => true, + 'session_gc' => true, + 'session_length' => true, + 'smtp_auth_method' => true, + 'smtp_delivery' => true, + 'topics_per_page' => true, + 'tpl_allow_php' => true, + 'version' => true, + 'warnings_expire_days' => true, + 'warnings_gc' => true, + + 'num_files' => true, + 'num_posts' => true, + 'num_topics' => true, + 'num_users' => true, + 'record_online_users' => true, + ); + + $result = array(); + foreach ($config_vars as $name => $void) + { + if (isset($this->config[$name])) + { + $result['config_' . $name] = $this->config[$name]; + } + } + + global $db, $request; + + $result['dbms'] = $dbms; + $result['acm_type'] = $acm_type; + $result['user_agent'] = 'Unknown'; + $result['dbms_version'] = $db->sql_server_info(true); + + // Try to get user agent vendor and version + $match = array(); + $user_agent = $request->header('User-Agent'); + $agents = array('firefox', 'msie', 'opera', 'chrome', 'safari', 'mozilla', 'seamonkey', 'konqueror', 'netscape', 'gecko', 'navigator', 'mosaic', 'lynx', 'amaya', 'omniweb', 'avant', 'camino', 'flock', 'aol'); + + // We check here 1 by 1 because some strings occur after others (for example Mozilla [...] Firefox/) + foreach ($agents as $agent) + { + if (preg_match('#(' . $agent . ')[/ ]?([0-9.]*)#i', $user_agent, $match)) + { + $result['user_agent'] = $match[1] . ' ' . $match[2]; + break; + } + } + + return $result; + } +} diff --git a/includes/sphinxapi.php b/includes/sphinxapi.php new file mode 100644 index 0000000..b63a85a --- /dev/null +++ b/includes/sphinxapi.php @@ -0,0 +1,1711 @@ +=8 ) + { + $v = (int)$v; + return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); + } + + // x32, int + if ( is_int($v) ) + return pack ( "NN", $v < 0 ? -1 : 0, $v ); + + // x32, bcmath + if ( function_exists("bcmul") ) + { + if ( bccomp ( $v, 0 ) == -1 ) + $v = bcadd ( "18446744073709551616", $v ); + $h = bcdiv ( $v, "4294967296", 0 ); + $l = bcmod ( $v, "4294967296" ); + return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit + } + + // x32, no-bcmath + $p = max(0, strlen($v) - 13); + $lo = abs((float)substr($v, $p)); + $hi = abs((float)substr($v, 0, $p)); + + $m = $lo + $hi*1316134912.0; // (10 ^ 13) % (1 << 32) = 1316134912 + $q = floor($m/4294967296.0); + $l = $m - ($q*4294967296.0); + $h = $hi*2328.0 + $q; // (10 ^ 13) / (1 << 32) = 2328 + + if ( $v<0 ) + { + if ( $l==0 ) + $h = 4294967296.0 - $h; + else + { + $h = 4294967295.0 - $h; + $l = 4294967296.0 - $l; + } + } + return pack ( "NN", $h, $l ); +} + +/// pack 64-bit unsigned +function sphPackU64 ( $v ) +{ + assert ( is_numeric($v) ); + + // x64 + if ( PHP_INT_SIZE>=8 ) + { + assert ( $v>=0 ); + + // x64, int + if ( is_int($v) ) + return pack ( "NN", $v>>32, $v&0xFFFFFFFF ); + + // x64, bcmath + if ( function_exists("bcmul") ) + { + $h = bcdiv ( $v, 4294967296, 0 ); + $l = bcmod ( $v, 4294967296 ); + return pack ( "NN", $h, $l ); + } + + // x64, no-bcmath + $p = max ( 0, strlen($v) - 13 ); + $lo = (int)substr ( $v, $p ); + $hi = (int)substr ( $v, 0, $p ); + + $m = $lo + $hi*1316134912; + $l = $m % 4294967296; + $h = $hi*2328 + (int)($m/4294967296); + + return pack ( "NN", $h, $l ); + } + + // x32, int + if ( is_int($v) ) + return pack ( "NN", 0, $v ); + + // x32, bcmath + if ( function_exists("bcmul") ) + { + $h = bcdiv ( $v, "4294967296", 0 ); + $l = bcmod ( $v, "4294967296" ); + return pack ( "NN", (float)$h, (float)$l ); // conversion to float is intentional; int would lose 31st bit + } + + // x32, no-bcmath + $p = max(0, strlen($v) - 13); + $lo = (float)substr($v, $p); + $hi = (float)substr($v, 0, $p); + + $m = $lo + $hi*1316134912.0; + $q = floor($m / 4294967296.0); + $l = $m - ($q * 4294967296.0); + $h = $hi*2328.0 + $q; + + return pack ( "NN", $h, $l ); +} + +// unpack 64-bit unsigned +function sphUnpackU64 ( $v ) +{ + list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); + + if ( PHP_INT_SIZE>=8 ) + { + if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again + if ( $lo<0 ) $lo += (1<<32); + + // x64, int + if ( $hi<=2147483647 ) + return ($hi<<32) + $lo; + + // x64, bcmath + if ( function_exists("bcmul") ) + return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); + + // x64, no-bcmath + $C = 100000; + $h = ((int)($hi / $C) << 32) + (int)($lo / $C); + $l = (($hi % $C) << 32) + ($lo % $C); + if ( $l>$C ) + { + $h += (int)($l / $C); + $l = $l % $C; + } + + if ( $h==0 ) + return $l; + return sprintf ( "%d%05d", $h, $l ); + } + + // x32, int + if ( $hi==0 ) + { + if ( $lo>0 ) + return $lo; + return sprintf ( "%u", $lo ); + } + + $hi = sprintf ( "%u", $hi ); + $lo = sprintf ( "%u", $lo ); + + // x32, bcmath + if ( function_exists("bcmul") ) + return bcadd ( $lo, bcmul ( $hi, "4294967296" ) ); + + // x32, no-bcmath + $hi = (float)$hi; + $lo = (float)$lo; + + $q = floor($hi/10000000.0); + $r = $hi - $q*10000000.0; + $m = $lo + $r*4967296.0; + $mq = floor($m/10000000.0); + $l = $m - $mq*10000000.0; + $h = $q*4294967296.0 + $r*429.0 + $mq; + + $h = sprintf ( "%.0f", $h ); + $l = sprintf ( "%07.0f", $l ); + if ( $h=="0" ) + return sprintf( "%.0f", (float)$l ); + return $h . $l; +} + +// unpack 64-bit signed +function sphUnpackI64 ( $v ) +{ + list ( $hi, $lo ) = array_values ( unpack ( "N*N*", $v ) ); + + // x64 + if ( PHP_INT_SIZE>=8 ) + { + if ( $hi<0 ) $hi += (1<<32); // because php 5.2.2 to 5.2.5 is totally fucked up again + if ( $lo<0 ) $lo += (1<<32); + + return ($hi<<32) + $lo; + } + + // x32, int + if ( $hi==0 ) + { + if ( $lo>0 ) + return $lo; + return sprintf ( "%u", $lo ); + } + // x32, int + elseif ( $hi==-1 ) + { + if ( $lo<0 ) + return $lo; + return sprintf ( "%.0f", $lo - 4294967296.0 ); + } + + $neg = ""; + $c = 0; + if ( $hi<0 ) + { + $hi = ~$hi; + $lo = ~$lo; + $c = 1; + $neg = "-"; + } + + $hi = sprintf ( "%u", $hi ); + $lo = sprintf ( "%u", $lo ); + + // x32, bcmath + if ( function_exists("bcmul") ) + return $neg . bcadd ( bcadd ( $lo, bcmul ( $hi, "4294967296" ) ), $c ); + + // x32, no-bcmath + $hi = (float)$hi; + $lo = (float)$lo; + + $q = floor($hi/10000000.0); + $r = $hi - $q*10000000.0; + $m = $lo + $r*4967296.0; + $mq = floor($m/10000000.0); + $l = $m - $mq*10000000.0 + $c; + $h = $q*4294967296.0 + $r*429.0 + $mq; + if ( $l==10000000 ) + { + $l = 0; + $h += 1; + } + + $h = sprintf ( "%.0f", $h ); + $l = sprintf ( "%07.0f", $l ); + if ( $h=="0" ) + return $neg . sprintf( "%.0f", (float)$l ); + return $neg . $h . $l; +} + + +function sphFixUint ( $value ) +{ + if ( PHP_INT_SIZE>=8 ) + { + // x64 route, workaround broken unpack() in 5.2.2+ + if ( $value<0 ) $value += (1<<32); + return $value; + } + else + { + // x32 route, workaround php signed/unsigned braindamage + return sprintf ( "%u", $value ); + } +} + + +/// sphinx searchd client class +class SphinxClient +{ + var $_host; ///< searchd host (default is "localhost") + var $_port; ///< searchd port (default is 9312) + var $_offset; ///< how many records to seek from result-set start (default is 0) + var $_limit; ///< how many records to return from result-set starting at offset (default is 20) + var $_mode; ///< query matching mode (default is SPH_MATCH_ALL) + var $_weights; ///< per-field weights (default is 1 for all fields) + var $_sort; ///< match sorting mode (default is SPH_SORT_RELEVANCE) + var $_sortby; ///< attribute to sort by (defualt is "") + var $_min_id; ///< min ID to match (default is 0, which means no limit) + var $_max_id; ///< max ID to match (default is 0, which means no limit) + var $_filters; ///< search filters + var $_groupby; ///< group-by attribute name + var $_groupfunc; ///< group-by function (to pre-process group-by attribute value with) + var $_groupsort; ///< group-by sorting clause (to sort groups in result set with) + var $_groupdistinct;///< group-by count-distinct attribute + var $_maxmatches; ///< max matches to retrieve + var $_cutoff; ///< cutoff to stop searching at (default is 0) + var $_retrycount; ///< distributed retries count + var $_retrydelay; ///< distributed retries delay + var $_anchor; ///< geographical anchor point + var $_indexweights; ///< per-index weights + var $_ranker; ///< ranking mode (default is SPH_RANK_PROXIMITY_BM25) + var $_rankexpr; ///< ranking mode expression (for SPH_RANK_EXPR) + var $_maxquerytime; ///< max query time, milliseconds (default is 0, do not limit) + var $_fieldweights; ///< per-field-name weights + var $_overrides; ///< per-query attribute values overrides + var $_select; ///< select-list (attributes or expressions, with optional aliases) + + var $_error; ///< last error message + var $_warning; ///< last warning message + var $_connerror; ///< connection error vs remote error flag + + var $_reqs; ///< requests array for multi-query + var $_mbenc; ///< stored mbstring encoding + var $_arrayresult; ///< whether $result["matches"] should be a hash or an array + var $_timeout; ///< connect timeout + + ///////////////////////////////////////////////////////////////////////////// + // common stuff + ///////////////////////////////////////////////////////////////////////////// + + /// create a new client object and fill defaults + function __construct () + { + // per-client-object settings + $this->_host = "localhost"; + $this->_port = 9312; + $this->_path = false; + $this->_socket = false; + + // per-query settings + $this->_offset = 0; + $this->_limit = 20; + $this->_mode = SPH_MATCH_ALL; + $this->_weights = array (); + $this->_sort = SPH_SORT_RELEVANCE; + $this->_sortby = ""; + $this->_min_id = 0; + $this->_max_id = 0; + $this->_filters = array (); + $this->_groupby = ""; + $this->_groupfunc = SPH_GROUPBY_DAY; + $this->_groupsort = "@group desc"; + $this->_groupdistinct= ""; + $this->_maxmatches = 1000; + $this->_cutoff = 0; + $this->_retrycount = 0; + $this->_retrydelay = 0; + $this->_anchor = array (); + $this->_indexweights= array (); + $this->_ranker = SPH_RANK_PROXIMITY_BM25; + $this->_rankexpr = ""; + $this->_maxquerytime= 0; + $this->_fieldweights= array(); + $this->_overrides = array(); + $this->_select = "*"; + + $this->_error = ""; // per-reply fields (for single-query case) + $this->_warning = ""; + $this->_connerror = false; + + $this->_reqs = array (); // requests storage (for multi-query case) + $this->_mbenc = ""; + $this->_arrayresult = false; + $this->_timeout = 0; + } + + function __destruct() + { + if ( $this->_socket !== false ) + fclose ( $this->_socket ); + } + + /// get last error message (string) + function GetLastError () + { + return $this->_error; + } + + /// get last warning message (string) + function GetLastWarning () + { + return $this->_warning; + } + + /// get last error flag (to tell network connection errors from searchd errors or broken responses) + function IsConnectError() + { + return $this->_connerror; + } + + /// set searchd host name (string) and port (integer) + function SetServer ( $host, $port = 0 ) + { + assert ( is_string($host) ); + if ( $host[0] == '/') + { + $this->_path = 'unix://' . $host; + return; + } + if ( substr ( $host, 0, 7 )=="unix://" ) + { + $this->_path = $host; + return; + } + + assert ( is_int($port) ); + $this->_host = $host; + $this->_port = $port; + $this->_path = ''; + + } + + /// set server connection timeout (0 to remove) + function SetConnectTimeout ( $timeout ) + { + assert ( is_numeric($timeout) ); + $this->_timeout = $timeout; + } + + + function _Send ( $handle, $data, $length ) + { + if ( feof($handle) || fwrite ( $handle, $data, $length ) !== $length ) + { + $this->_error = 'connection unexpectedly closed (timed out?)'; + $this->_connerror = true; + return false; + } + return true; + } + + ///////////////////////////////////////////////////////////////////////////// + + /// enter mbstring workaround mode + function _MBPush () + { + $this->_mbenc = ""; + if ( ini_get ( "mbstring.func_overload" ) & 2 ) + { + $this->_mbenc = mb_internal_encoding(); + mb_internal_encoding ( "latin1" ); + } + } + + /// leave mbstring workaround mode + function _MBPop () + { + if ( $this->_mbenc ) + mb_internal_encoding ( $this->_mbenc ); + } + + /// connect to searchd server + function _Connect () + { + if ( $this->_socket!==false ) + { + // we are in persistent connection mode, so we have a socket + // however, need to check whether it's still alive + if ( !@feof ( $this->_socket ) ) + return $this->_socket; + + // force reopen + $this->_socket = false; + } + + $errno = 0; + $errstr = ""; + $this->_connerror = false; + + if ( $this->_path ) + { + $host = $this->_path; + $port = 0; + } + else + { + $host = $this->_host; + $port = $this->_port; + } + + if ( $this->_timeout<=0 ) + $fp = @fsockopen ( $host, $port, $errno, $errstr ); + else + $fp = @fsockopen ( $host, $port, $errno, $errstr, $this->_timeout ); + + if ( !$fp ) + { + if ( $this->_path ) + $location = $this->_path; + else + $location = "{$this->_host}:{$this->_port}"; + + $errstr = trim ( $errstr ); + $this->_error = "connection to $location failed (errno=$errno, msg=$errstr)"; + $this->_connerror = true; + return false; + } + + // send my version + // this is a subtle part. we must do it before (!) reading back from searchd. + // because otherwise under some conditions (reported on FreeBSD for instance) + // TCP stack could throttle write-write-read pattern because of Nagle. + if ( !$this->_Send ( $fp, pack ( "N", 1 ), 4 ) ) + { + fclose ( $fp ); + $this->_error = "failed to send client protocol version"; + return false; + } + + // check version + list(,$v) = unpack ( "N*", fread ( $fp, 4 ) ); + $v = (int)$v; + if ( $v<1 ) + { + fclose ( $fp ); + $this->_error = "expected searchd protocol version 1+, got version '$v'"; + return false; + } + + return $fp; + } + + /// get and check response packet from searchd server + function _GetResponse ( $fp, $client_ver ) + { + $response = ""; + $len = 0; + + $header = fread ( $fp, 8 ); + if ( strlen($header)==8 ) + { + list ( $status, $ver, $len ) = array_values ( unpack ( "n2a/Nb", $header ) ); + $left = $len; + while ( $left>0 && !feof($fp) ) + { + $chunk = fread ( $fp, min ( 8192, $left ) ); + if ( $chunk ) + { + $response .= $chunk; + $left -= strlen($chunk); + } + } + } + if ( $this->_socket === false ) + fclose ( $fp ); + + // check response + $read = strlen ( $response ); + if ( !$response || $read!=$len ) + { + $this->_error = $len + ? "failed to read searchd response (status=$status, ver=$ver, len=$len, read=$read)" + : "received zero-sized searchd response"; + return false; + } + + // check status + if ( $status==SEARCHD_WARNING ) + { + list(,$wlen) = unpack ( "N*", substr ( $response, 0, 4 ) ); + $this->_warning = substr ( $response, 4, $wlen ); + return substr ( $response, 4+$wlen ); + } + if ( $status==SEARCHD_ERROR ) + { + $this->_error = "searchd error: " . substr ( $response, 4 ); + return false; + } + if ( $status==SEARCHD_RETRY ) + { + $this->_error = "temporary searchd error: " . substr ( $response, 4 ); + return false; + } + if ( $status!=SEARCHD_OK ) + { + $this->_error = "unknown status code '$status'"; + return false; + } + + // check version + if ( $ver<$client_ver ) + { + $this->_warning = sprintf ( "searchd command v.%d.%d older than client's v.%d.%d, some options might not work", + $ver>>8, $ver&0xff, $client_ver>>8, $client_ver&0xff ); + } + + return $response; + } + + ///////////////////////////////////////////////////////////////////////////// + // searching + ///////////////////////////////////////////////////////////////////////////// + + /// set offset and count into result set, + /// and optionally set max-matches and cutoff limits + function SetLimits ( $offset, $limit, $max=0, $cutoff=0 ) + { + assert ( is_int($offset) ); + assert ( is_int($limit) ); + assert ( $offset>=0 ); + assert ( $limit>0 ); + assert ( $max>=0 ); + $this->_offset = $offset; + $this->_limit = $limit; + if ( $max>0 ) + $this->_maxmatches = $max; + if ( $cutoff>0 ) + $this->_cutoff = $cutoff; + } + + /// set maximum query time, in milliseconds, per-index + /// integer, 0 means "do not limit" + function SetMaxQueryTime ( $max ) + { + assert ( is_int($max) ); + assert ( $max>=0 ); + $this->_maxquerytime = $max; + } + + /// set matching mode + function SetMatchMode ( $mode ) + { + assert ( $mode==SPH_MATCH_ALL + || $mode==SPH_MATCH_ANY + || $mode==SPH_MATCH_PHRASE + || $mode==SPH_MATCH_BOOLEAN + || $mode==SPH_MATCH_EXTENDED + || $mode==SPH_MATCH_FULLSCAN + || $mode==SPH_MATCH_EXTENDED2 ); + $this->_mode = $mode; + } + + /// set ranking mode + function SetRankingMode ( $ranker, $rankexpr="" ) + { + assert ( $ranker>=0 && $ranker_ranker = $ranker; + $this->_rankexpr = $rankexpr; + } + + /// set matches sorting mode + function SetSortMode ( $mode, $sortby="" ) + { + assert ( + $mode==SPH_SORT_RELEVANCE || + $mode==SPH_SORT_ATTR_DESC || + $mode==SPH_SORT_ATTR_ASC || + $mode==SPH_SORT_TIME_SEGMENTS || + $mode==SPH_SORT_EXTENDED || + $mode==SPH_SORT_EXPR ); + assert ( is_string($sortby) ); + assert ( $mode==SPH_SORT_RELEVANCE || strlen($sortby)>0 ); + + $this->_sort = $mode; + $this->_sortby = $sortby; + } + + /// bind per-field weights by order + /// DEPRECATED; use SetFieldWeights() instead + function SetWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $weight ) + assert ( is_int($weight) ); + + $this->_weights = $weights; + } + + /// bind per-field weights by name + function SetFieldWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $name=>$weight ) + { + assert ( is_string($name) ); + assert ( is_int($weight) ); + } + $this->_fieldweights = $weights; + } + + /// bind per-index weights by name + function SetIndexWeights ( $weights ) + { + assert ( is_array($weights) ); + foreach ( $weights as $index=>$weight ) + { + assert ( is_string($index) ); + assert ( is_int($weight) ); + } + $this->_indexweights = $weights; + } + + /// set IDs range to match + /// only match records if document ID is beetwen $min and $max (inclusive) + function SetIDRange ( $min, $max ) + { + assert ( is_numeric($min) ); + assert ( is_numeric($max) ); + assert ( $min<=$max ); + $this->_min_id = $min; + $this->_max_id = $max; + } + + /// set values set filter + /// only match records where $attribute value is in given set + function SetFilter ( $attribute, $values, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_array($values) ); + assert ( count($values) ); + + if ( is_array($values) && count($values) ) + { + foreach ( $values as $value ) + assert ( is_numeric($value) ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_VALUES, "attr"=>$attribute, "exclude"=>$exclude, "values"=>$values ); + } + } + + /// set range filter + /// only match records if $attribute value is beetwen $min and $max (inclusive) + function SetFilterRange ( $attribute, $min, $max, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_numeric($min) ); + assert ( is_numeric($max) ); + assert ( $min<=$max ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_RANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); + } + + /// set float range filter + /// only match records if $attribute value is beetwen $min and $max (inclusive) + function SetFilterFloatRange ( $attribute, $min, $max, $exclude=false ) + { + assert ( is_string($attribute) ); + assert ( is_float($min) ); + assert ( is_float($max) ); + assert ( $min<=$max ); + + $this->_filters[] = array ( "type"=>SPH_FILTER_FLOATRANGE, "attr"=>$attribute, "exclude"=>$exclude, "min"=>$min, "max"=>$max ); + } + + /// setup anchor point for geosphere distance calculations + /// required to use @geodist in filters and sorting + /// latitude and longitude must be in radians + function SetGeoAnchor ( $attrlat, $attrlong, $lat, $long ) + { + assert ( is_string($attrlat) ); + assert ( is_string($attrlong) ); + assert ( is_float($lat) ); + assert ( is_float($long) ); + + $this->_anchor = array ( "attrlat"=>$attrlat, "attrlong"=>$attrlong, "lat"=>$lat, "long"=>$long ); + } + + /// set grouping attribute and function + function SetGroupBy ( $attribute, $func, $groupsort="@group desc" ) + { + assert ( is_string($attribute) ); + assert ( is_string($groupsort) ); + assert ( $func==SPH_GROUPBY_DAY + || $func==SPH_GROUPBY_WEEK + || $func==SPH_GROUPBY_MONTH + || $func==SPH_GROUPBY_YEAR + || $func==SPH_GROUPBY_ATTR + || $func==SPH_GROUPBY_ATTRPAIR ); + + $this->_groupby = $attribute; + $this->_groupfunc = $func; + $this->_groupsort = $groupsort; + } + + /// set count-distinct attribute for group-by queries + function SetGroupDistinct ( $attribute ) + { + assert ( is_string($attribute) ); + $this->_groupdistinct = $attribute; + } + + /// set distributed retries count and delay + function SetRetries ( $count, $delay=0 ) + { + assert ( is_int($count) && $count>=0 ); + assert ( is_int($delay) && $delay>=0 ); + $this->_retrycount = $count; + $this->_retrydelay = $delay; + } + + /// set result set format (hash or array; hash by default) + /// PHP specific; needed for group-by-MVA result sets that may contain duplicate IDs + function SetArrayResult ( $arrayresult ) + { + assert ( is_bool($arrayresult) ); + $this->_arrayresult = $arrayresult; + } + + /// set attribute values override + /// there can be only one override per attribute + /// $values must be a hash that maps document IDs to attribute values + function SetOverride ( $attrname, $attrtype, $values ) + { + assert ( is_string ( $attrname ) ); + assert ( in_array ( $attrtype, array ( SPH_ATTR_INTEGER, SPH_ATTR_TIMESTAMP, SPH_ATTR_BOOL, SPH_ATTR_FLOAT, SPH_ATTR_BIGINT ) ) ); + assert ( is_array ( $values ) ); + + $this->_overrides[$attrname] = array ( "attr"=>$attrname, "type"=>$attrtype, "values"=>$values ); + } + + /// set select-list (attributes or expressions), SQL-like syntax + function SetSelect ( $select ) + { + assert ( is_string ( $select ) ); + $this->_select = $select; + } + + ////////////////////////////////////////////////////////////////////////////// + + /// clear all filters (for multi-queries) + function ResetFilters () + { + $this->_filters = array(); + $this->_anchor = array(); + } + + /// clear groupby settings (for multi-queries) + function ResetGroupBy () + { + $this->_groupby = ""; + $this->_groupfunc = SPH_GROUPBY_DAY; + $this->_groupsort = "@group desc"; + $this->_groupdistinct= ""; + } + + /// clear all attribute value overrides (for multi-queries) + function ResetOverrides () + { + $this->_overrides = array (); + } + + ////////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, run given search query through given indexes, + /// and return the search results + function Query ( $query, $index="*", $comment="" ) + { + assert ( empty($this->_reqs) ); + + $this->AddQuery ( $query, $index, $comment ); + $results = $this->RunQueries (); + $this->_reqs = array (); // just in case it failed too early + + if ( !is_array($results) ) + return false; // probably network error; error message should be already filled + + $this->_error = $results[0]["error"]; + $this->_warning = $results[0]["warning"]; + if ( $results[0]["status"]==SEARCHD_ERROR ) + return false; + else + return $results[0]; + } + + /// helper to pack floats in network byte order + function _PackFloat ( $f ) + { + $t1 = pack ( "f", $f ); // machine order + list(,$t2) = unpack ( "L*", $t1 ); // int in machine order + return pack ( "N", $t2 ); + } + + /// add query to multi-query batch + /// returns index into results array from RunQueries() call + function AddQuery ( $query, $index="*", $comment="" ) + { + // mbstring workaround + $this->_MBPush (); + + // build request + $req = pack ( "NNNN", $this->_offset, $this->_limit, $this->_mode, $this->_ranker ); + if ( $this->_ranker==SPH_RANK_EXPR ) + $req .= pack ( "N", strlen($this->_rankexpr) ) . $this->_rankexpr; + $req .= pack ( "N", $this->_sort ); // (deprecated) sort mode + $req .= pack ( "N", strlen($this->_sortby) ) . $this->_sortby; + $req .= pack ( "N", strlen($query) ) . $query; // query itself + $req .= pack ( "N", count($this->_weights) ); // weights + foreach ( $this->_weights as $weight ) + $req .= pack ( "N", (int)$weight ); + $req .= pack ( "N", strlen($index) ) . $index; // indexes + $req .= pack ( "N", 1 ); // id64 range marker + $req .= sphPackU64 ( $this->_min_id ) . sphPackU64 ( $this->_max_id ); // id64 range + + // filters + $req .= pack ( "N", count($this->_filters) ); + foreach ( $this->_filters as $filter ) + { + $req .= pack ( "N", strlen($filter["attr"]) ) . $filter["attr"]; + $req .= pack ( "N", $filter["type"] ); + switch ( $filter["type"] ) + { + case SPH_FILTER_VALUES: + $req .= pack ( "N", count($filter["values"]) ); + foreach ( $filter["values"] as $value ) + $req .= sphPackI64 ( $value ); + break; + + case SPH_FILTER_RANGE: + $req .= sphPackI64 ( $filter["min"] ) . sphPackI64 ( $filter["max"] ); + break; + + case SPH_FILTER_FLOATRANGE: + $req .= $this->_PackFloat ( $filter["min"] ) . $this->_PackFloat ( $filter["max"] ); + break; + + default: + assert ( 0 && "internal error: unhandled filter type" ); + } + $req .= pack ( "N", $filter["exclude"] ); + } + + // group-by clause, max-matches count, group-sort clause, cutoff count + $req .= pack ( "NN", $this->_groupfunc, strlen($this->_groupby) ) . $this->_groupby; + $req .= pack ( "N", $this->_maxmatches ); + $req .= pack ( "N", strlen($this->_groupsort) ) . $this->_groupsort; + $req .= pack ( "NNN", $this->_cutoff, $this->_retrycount, $this->_retrydelay ); + $req .= pack ( "N", strlen($this->_groupdistinct) ) . $this->_groupdistinct; + + // anchor point + if ( empty($this->_anchor) ) + { + $req .= pack ( "N", 0 ); + } else + { + $a =& $this->_anchor; + $req .= pack ( "N", 1 ); + $req .= pack ( "N", strlen($a["attrlat"]) ) . $a["attrlat"]; + $req .= pack ( "N", strlen($a["attrlong"]) ) . $a["attrlong"]; + $req .= $this->_PackFloat ( $a["lat"] ) . $this->_PackFloat ( $a["long"] ); + } + + // per-index weights + $req .= pack ( "N", count($this->_indexweights) ); + foreach ( $this->_indexweights as $idx=>$weight ) + $req .= pack ( "N", strlen($idx) ) . $idx . pack ( "N", $weight ); + + // max query time + $req .= pack ( "N", $this->_maxquerytime ); + + // per-field weights + $req .= pack ( "N", count($this->_fieldweights) ); + foreach ( $this->_fieldweights as $field=>$weight ) + $req .= pack ( "N", strlen($field) ) . $field . pack ( "N", $weight ); + + // comment + $req .= pack ( "N", strlen($comment) ) . $comment; + + // attribute overrides + $req .= pack ( "N", count($this->_overrides) ); + foreach ( $this->_overrides as $key => $entry ) + { + $req .= pack ( "N", strlen($entry["attr"]) ) . $entry["attr"]; + $req .= pack ( "NN", $entry["type"], count($entry["values"]) ); + foreach ( $entry["values"] as $id=>$val ) + { + assert ( is_numeric($id) ); + assert ( is_numeric($val) ); + + $req .= sphPackU64 ( $id ); + switch ( $entry["type"] ) + { + case SPH_ATTR_FLOAT: $req .= $this->_PackFloat ( $val ); break; + case SPH_ATTR_BIGINT: $req .= sphPackI64 ( $val ); break; + default: $req .= pack ( "N", $val ); break; + } + } + } + + // select-list + $req .= pack ( "N", strlen($this->_select) ) . $this->_select; + + // mbstring workaround + $this->_MBPop (); + + // store request to requests array + $this->_reqs[] = $req; + return count($this->_reqs)-1; + } + + /// connect to searchd, run queries batch, and return an array of result sets + function RunQueries () + { + if ( empty($this->_reqs) ) + { + $this->_error = "no queries defined, issue AddQuery() first"; + return false; + } + + // mbstring workaround + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop (); + return false; + } + + // send query, get response + $nreqs = count($this->_reqs); + $req = join ( "", $this->_reqs ); + $len = 8+strlen($req); + $req = pack ( "nnNNN", SEARCHD_COMMAND_SEARCH, VER_COMMAND_SEARCH, $len, 0, $nreqs ) . $req; // add header + + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_SEARCH ) ) ) + { + $this->_MBPop (); + return false; + } + + // query sent ok; we can reset reqs now + $this->_reqs = array (); + + // parse and return response + return $this->_ParseSearchResponse ( $response, $nreqs ); + } + + /// parse and return search query (or queries) response + function _ParseSearchResponse ( $response, $nreqs ) + { + $p = 0; // current position + $max = strlen($response); // max position for checks, to protect against broken responses + + $results = array (); + for ( $ires=0; $ires<$nreqs && $p<$max; $ires++ ) + { + $results[] = array(); + $result =& $results[$ires]; + + $result["error"] = ""; + $result["warning"] = ""; + + // extract status + list(,$status) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $result["status"] = $status; + if ( $status!=SEARCHD_OK ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $message = substr ( $response, $p, $len ); $p += $len; + + if ( $status==SEARCHD_WARNING ) + { + $result["warning"] = $message; + } else + { + $result["error"] = $message; + continue; + } + } + + // read schema + $fields = array (); + $attrs = array (); + + list(,$nfields) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + while ( $nfields-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $fields[] = substr ( $response, $p, $len ); $p += $len; + } + $result["fields"] = $fields; + + list(,$nattrs) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + while ( $nattrs-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attr = substr ( $response, $p, $len ); $p += $len; + list(,$type) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attrs[$attr] = $type; + } + $result["attrs"] = $attrs; + + // read match count + list(,$count) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + list(,$id64) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + + // read matches + $idx = -1; + while ( $count-->0 && $p<$max ) + { + // index into result array + $idx++; + + // parse document id and weight + if ( $id64 ) + { + $doc = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; + list(,$weight) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + } + else + { + list ( $doc, $weight ) = array_values ( unpack ( "N*N*", + substr ( $response, $p, 8 ) ) ); + $p += 8; + $doc = sphFixUint($doc); + } + $weight = sprintf ( "%u", $weight ); + + // create match entry + if ( $this->_arrayresult ) + $result["matches"][$idx] = array ( "id"=>$doc, "weight"=>$weight ); + else + $result["matches"][$doc]["weight"] = $weight; + + // parse and create attributes + $attrvals = array (); + foreach ( $attrs as $attr=>$type ) + { + // handle 64bit ints + if ( $type==SPH_ATTR_BIGINT ) + { + $attrvals[$attr] = sphUnpackI64 ( substr ( $response, $p, 8 ) ); $p += 8; + continue; + } + + // handle floats + if ( $type==SPH_ATTR_FLOAT ) + { + list(,$uval) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + list(,$fval) = unpack ( "f*", pack ( "L", $uval ) ); + $attrvals[$attr] = $fval; + continue; + } + + // handle everything else as unsigned ints + list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + if ( $type==SPH_ATTR_MULTI ) + { + $attrvals[$attr] = array (); + $nvalues = $val; + while ( $nvalues-->0 && $p<$max ) + { + list(,$val) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $attrvals[$attr][] = sphFixUint($val); + } + } else if ( $type==SPH_ATTR_MULTI64 ) + { + $attrvals[$attr] = array (); + $nvalues = $val; + while ( $nvalues>0 && $p<$max ) + { + $attrvals[$attr][] = sphUnpackU64 ( substr ( $response, $p, 8 ) ); $p += 8; + $nvalues -= 2; + } + } else if ( $type==SPH_ATTR_STRING ) + { + $attrvals[$attr] = substr ( $response, $p, $val ); + $p += $val; + } else + { + $attrvals[$attr] = sphFixUint($val); + } + } + + if ( $this->_arrayresult ) + $result["matches"][$idx]["attrs"] = $attrvals; + else + $result["matches"][$doc]["attrs"] = $attrvals; + } + + list ( $total, $total_found, $msecs, $words ) = + array_values ( unpack ( "N*N*N*N*", substr ( $response, $p, 16 ) ) ); + $result["total"] = sprintf ( "%u", $total ); + $result["total_found"] = sprintf ( "%u", $total_found ); + $result["time"] = sprintf ( "%.3f", $msecs/1000 ); + $p += 16; + + while ( $words-->0 && $p<$max ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $word = substr ( $response, $p, $len ); $p += $len; + list ( $docs, $hits ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; + $result["words"][$word] = array ( + "docs"=>sprintf ( "%u", $docs ), + "hits"=>sprintf ( "%u", $hits ) ); + } + } + + $this->_MBPop (); + return $results; + } + + ///////////////////////////////////////////////////////////////////////////// + // excerpts generation + ///////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, and generate exceprts (snippets) + /// of given documents for given query. returns false on failure, + /// an array of snippets on success + function BuildExcerpts ( $docs, $index, $words, $opts=array() ) + { + assert ( is_array($docs) ); + assert ( is_string($index) ); + assert ( is_string($words) ); + assert ( is_array($opts) ); + + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + ///////////////// + // fixup options + ///////////////// + + if ( !isset($opts["before_match"]) ) $opts["before_match"] = ""; + if ( !isset($opts["after_match"]) ) $opts["after_match"] = ""; + if ( !isset($opts["chunk_separator"]) ) $opts["chunk_separator"] = " ... "; + if ( !isset($opts["limit"]) ) $opts["limit"] = 256; + if ( !isset($opts["limit_passages"]) ) $opts["limit_passages"] = 0; + if ( !isset($opts["limit_words"]) ) $opts["limit_words"] = 0; + if ( !isset($opts["around"]) ) $opts["around"] = 5; + if ( !isset($opts["exact_phrase"]) ) $opts["exact_phrase"] = false; + if ( !isset($opts["single_passage"]) ) $opts["single_passage"] = false; + if ( !isset($opts["use_boundaries"]) ) $opts["use_boundaries"] = false; + if ( !isset($opts["weight_order"]) ) $opts["weight_order"] = false; + if ( !isset($opts["query_mode"]) ) $opts["query_mode"] = false; + if ( !isset($opts["force_all_words"]) ) $opts["force_all_words"] = false; + if ( !isset($opts["start_passage_id"]) ) $opts["start_passage_id"] = 1; + if ( !isset($opts["load_files"]) ) $opts["load_files"] = false; + if ( !isset($opts["html_strip_mode"]) ) $opts["html_strip_mode"] = "index"; + if ( !isset($opts["allow_empty"]) ) $opts["allow_empty"] = false; + if ( !isset($opts["passage_boundary"]) ) $opts["passage_boundary"] = "none"; + if ( !isset($opts["emit_zones"]) ) $opts["emit_zones"] = false; + if ( !isset($opts["load_files_scattered"]) ) $opts["load_files_scattered"] = false; + + + ///////////////// + // build request + ///////////////// + + // v.1.2 req + $flags = 1; // remove spaces + if ( $opts["exact_phrase"] ) $flags |= 2; + if ( $opts["single_passage"] ) $flags |= 4; + if ( $opts["use_boundaries"] ) $flags |= 8; + if ( $opts["weight_order"] ) $flags |= 16; + if ( $opts["query_mode"] ) $flags |= 32; + if ( $opts["force_all_words"] ) $flags |= 64; + if ( $opts["load_files"] ) $flags |= 128; + if ( $opts["allow_empty"] ) $flags |= 256; + if ( $opts["emit_zones"] ) $flags |= 512; + if ( $opts["load_files_scattered"] ) $flags |= 1024; + $req = pack ( "NN", 0, $flags ); // mode=0, flags=$flags + $req .= pack ( "N", strlen($index) ) . $index; // req index + $req .= pack ( "N", strlen($words) ) . $words; // req words + + // options + $req .= pack ( "N", strlen($opts["before_match"]) ) . $opts["before_match"]; + $req .= pack ( "N", strlen($opts["after_match"]) ) . $opts["after_match"]; + $req .= pack ( "N", strlen($opts["chunk_separator"]) ) . $opts["chunk_separator"]; + $req .= pack ( "NN", (int)$opts["limit"], (int)$opts["around"] ); + $req .= pack ( "NNN", (int)$opts["limit_passages"], (int)$opts["limit_words"], (int)$opts["start_passage_id"] ); // v.1.2 + $req .= pack ( "N", strlen($opts["html_strip_mode"]) ) . $opts["html_strip_mode"]; + $req .= pack ( "N", strlen($opts["passage_boundary"]) ) . $opts["passage_boundary"]; + + // documents + $req .= pack ( "N", count($docs) ); + foreach ( $docs as $doc ) + { + assert ( is_string($doc) ); + $req .= pack ( "N", strlen($doc) ) . $doc; + } + + //////////////////////////// + // send query, get response + //////////////////////////// + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_EXCERPT, VER_COMMAND_EXCERPT, $len ) . $req; // add header + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_EXCERPT ) ) ) + { + $this->_MBPop (); + return false; + } + + ////////////////// + // parse response + ////////////////// + + $pos = 0; + $res = array (); + $rlen = strlen($response); + for ( $i=0; $i $rlen ) + { + $this->_error = "incomplete reply"; + $this->_MBPop (); + return false; + } + $res[] = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + } + + $this->_MBPop (); + return $res; + } + + + ///////////////////////////////////////////////////////////////////////////// + // keyword generation + ///////////////////////////////////////////////////////////////////////////// + + /// connect to searchd server, and generate keyword list for a given query + /// returns false on failure, + /// an array of words on success + function BuildKeywords ( $query, $index, $hits ) + { + assert ( is_string($query) ); + assert ( is_string($index) ); + assert ( is_bool($hits) ); + + $this->_MBPush (); + + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + ///////////////// + // build request + ///////////////// + + // v.1.0 req + $req = pack ( "N", strlen($query) ) . $query; // req query + $req .= pack ( "N", strlen($index) ) . $index; // req index + $req .= pack ( "N", (int)$hits ); + + //////////////////////////// + // send query, get response + //////////////////////////// + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_KEYWORDS, VER_COMMAND_KEYWORDS, $len ) . $req; // add header + if ( !( $this->_Send ( $fp, $req, $len+8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_KEYWORDS ) ) ) + { + $this->_MBPop (); + return false; + } + + ////////////////// + // parse response + ////////////////// + + $pos = 0; + $res = array (); + $rlen = strlen($response); + list(,$nwords) = unpack ( "N*", substr ( $response, $pos, 4 ) ); + $pos += 4; + for ( $i=0; $i<$nwords; $i++ ) + { + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; + $tokenized = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + + list(,$len) = unpack ( "N*", substr ( $response, $pos, 4 ) ); $pos += 4; + $normalized = $len ? substr ( $response, $pos, $len ) : ""; + $pos += $len; + + $res[] = array ( "tokenized"=>$tokenized, "normalized"=>$normalized ); + + if ( $hits ) + { + list($ndocs,$nhits) = array_values ( unpack ( "N*N*", substr ( $response, $pos, 8 ) ) ); + $pos += 8; + $res [$i]["docs"] = $ndocs; + $res [$i]["hits"] = $nhits; + } + + if ( $pos > $rlen ) + { + $this->_error = "incomplete reply"; + $this->_MBPop (); + return false; + } + } + + $this->_MBPop (); + return $res; + } + + function EscapeString ( $string ) + { + $from = array ( '\\', '(',')','|','-','!','@','~','"','&', '/', '^', '$', '=' ); + $to = array ( '\\\\', '\(','\)','\|','\-','\!','\@','\~','\"', '\&', '\/', '\^', '\$', '\=' ); + + return str_replace ( $from, $to, $string ); + } + + ///////////////////////////////////////////////////////////////////////////// + // attribute updates + ///////////////////////////////////////////////////////////////////////////// + + /// batch update given attributes in given rows in given indexes + /// returns amount of updated documents (0 or more) on success, or -1 on failure + function UpdateAttributes ( $index, $attrs, $values, $mva=false ) + { + // verify everything + assert ( is_string($index) ); + assert ( is_bool($mva) ); + + assert ( is_array($attrs) ); + foreach ( $attrs as $attr ) + assert ( is_string($attr) ); + + assert ( is_array($values) ); + foreach ( $values as $id=>$entry ) + { + assert ( is_numeric($id) ); + assert ( is_array($entry) ); + assert ( count($entry)==count($attrs) ); + foreach ( $entry as $v ) + { + if ( $mva ) + { + assert ( is_array($v) ); + foreach ( $v as $vv ) + assert ( is_int($vv) ); + } else + assert ( is_int($v) ); + } + } + + // build request + $this->_MBPush (); + $req = pack ( "N", strlen($index) ) . $index; + + $req .= pack ( "N", count($attrs) ); + foreach ( $attrs as $attr ) + { + $req .= pack ( "N", strlen($attr) ) . $attr; + $req .= pack ( "N", $mva ? 1 : 0 ); + } + + $req .= pack ( "N", count($values) ); + foreach ( $values as $id=>$entry ) + { + $req .= sphPackU64 ( $id ); + foreach ( $entry as $v ) + { + $req .= pack ( "N", $mva ? count($v) : $v ); + if ( $mva ) + foreach ( $v as $vv ) + $req .= pack ( "N", $vv ); + } + } + + // connect, send query, get response + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop (); + return -1; + } + + $len = strlen($req); + $req = pack ( "nnN", SEARCHD_COMMAND_UPDATE, VER_COMMAND_UPDATE, $len ) . $req; // add header + if ( !$this->_Send ( $fp, $req, $len+8 ) ) + { + $this->_MBPop (); + return -1; + } + + if (!( $response = $this->_GetResponse ( $fp, VER_COMMAND_UPDATE ) )) + { + $this->_MBPop (); + return -1; + } + + // parse response + list(,$updated) = unpack ( "N*", substr ( $response, 0, 4 ) ); + $this->_MBPop (); + return $updated; + } + + ///////////////////////////////////////////////////////////////////////////// + // persistent connections + ///////////////////////////////////////////////////////////////////////////// + + function Open() + { + if ( $this->_socket !== false ) + { + $this->_error = 'already connected'; + return false; + } + if ( !$fp = $this->_Connect() ) + return false; + + // command, command version = 0, body length = 4, body = 1 + $req = pack ( "nnNN", SEARCHD_COMMAND_PERSIST, 0, 4, 1 ); + if ( !$this->_Send ( $fp, $req, 12 ) ) + return false; + + $this->_socket = $fp; + return true; + } + + function Close() + { + if ( $this->_socket === false ) + { + $this->_error = 'not connected'; + return false; + } + + fclose ( $this->_socket ); + $this->_socket = false; + + return true; + } + + ////////////////////////////////////////////////////////////////////////// + // status + ////////////////////////////////////////////////////////////////////////// + + function Status () + { + $this->_MBPush (); + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return false; + } + + $req = pack ( "nnNN", SEARCHD_COMMAND_STATUS, VER_COMMAND_STATUS, 4, 1 ); // len=4, body=1 + if ( !( $this->_Send ( $fp, $req, 12 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_STATUS ) ) ) + { + $this->_MBPop (); + return false; + } + + $p = 0; + list ( $rows, $cols ) = array_values ( unpack ( "N*N*", substr ( $response, $p, 8 ) ) ); $p += 8; + + $res = array(); + for ( $i=0; $i<$rows; $i++ ) + for ( $j=0; $j<$cols; $j++ ) + { + list(,$len) = unpack ( "N*", substr ( $response, $p, 4 ) ); $p += 4; + $res[$i][] = substr ( $response, $p, $len ); $p += $len; + } + + $this->_MBPop (); + return $res; + } + + ////////////////////////////////////////////////////////////////////////// + // flush + ////////////////////////////////////////////////////////////////////////// + + function FlushAttributes () + { + $this->_MBPush (); + if (!( $fp = $this->_Connect() )) + { + $this->_MBPop(); + return -1; + } + + $req = pack ( "nnN", SEARCHD_COMMAND_FLUSHATTRS, VER_COMMAND_FLUSHATTRS, 0 ); // len=0 + if ( !( $this->_Send ( $fp, $req, 8 ) ) || + !( $response = $this->_GetResponse ( $fp, VER_COMMAND_FLUSHATTRS ) ) ) + { + $this->_MBPop (); + return -1; + } + + $tag = -1; + if ( strlen($response)==4 ) + list(,$tag) = unpack ( "N*", $response ); + else + $this->_error = "unexpected response length"; + + $this->_MBPop (); + return $tag; + } +} + +// +// $Id: sphinxapi.php 3087 2012-01-30 23:07:35Z shodan $ +// diff --git a/includes/startup.php b/includes/startup.php new file mode 100644 index 0000000..66f8565 --- /dev/null +++ b/includes/startup.php @@ -0,0 +1,86 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +// Report all errors, except notices and deprecation messages +$level = E_ALL & ~E_NOTICE & ~E_DEPRECATED; +error_reporting($level); + +/** +* Minimum Requirement: PHP 5.4.0 +*/ +if (version_compare(PHP_VERSION, '5.4') < 0) +{ + die('You are running an unsupported PHP version. Please upgrade to PHP 5.4.0 or higher before trying to install or update to phpBB 3.2'); +} +// Register globals and magic quotes have been dropped in PHP 5.4 so no need for extra checks + + +// In PHP 5.3.0 the error level has been raised to E_WARNING which causes problems +// because we show E_WARNING errors and do not set a default timezone. +// This is because we have our own timezone handling and work in UTC only anyway. + +// So what we basically want to do is set our timezone to UTC, +// but we don't know what other scripts (such as bridges) are involved, +// so we check whether a timezone is already set by calling date_default_timezone_get(). + +// Unfortunately, date_default_timezone_get() itself might throw E_WARNING +// if no timezone has been set, so we have to keep it quiet with @. + +// date_default_timezone_get() tries to guess the correct timezone first +// and then falls back to UTC when everything fails. +// We just set the timezone to whatever date_default_timezone_get() returns. +date_default_timezone_set(@date_default_timezone_get()); + +// Autoloading of dependencies. +// Three options are supported: +// 1. If dependencies are installed with Composer, Composer will create a +// vendor/autoload.php. If this file exists it will be +// automatically used by phpBB. This is the default mode that phpBB +// will use when shipped. +// 2. To disable composer autoloading, PHPBB_NO_COMPOSER_AUTOLOAD can be specified. +// Additionally specify PHPBB_AUTOLOAD=/path/to/autoload.php in the +// environment. This is useful for running CLI scripts and tests. +// /path/to/autoload.php should define and register class loaders +// for all of phpBB's dependencies. +// 3. You can also set PHPBB_NO_COMPOSER_AUTOLOAD without setting PHPBB_AUTOLOAD. +// In this case autoloading needs to be defined before running any phpBB +// script. This might be useful in cases when phpBB is integrated into a +// larger program. +if (getenv('PHPBB_NO_COMPOSER_AUTOLOAD')) +{ + if (getenv('PHPBB_AUTOLOAD')) + { + require(getenv('PHPBB_AUTOLOAD')); + } +} +else +{ + if (!file_exists($phpbb_root_path . 'vendor/autoload.php')) + { + trigger_error( + 'Composer dependencies have not been set up yet, run ' . + "'php ../composer.phar install' from the phpBB directory to do so.", + E_USER_ERROR + ); + } + require($phpbb_root_path . 'vendor/autoload.php'); +} + +$starttime = microtime(true); diff --git a/includes/ucp/info/ucp_attachments.php b/includes/ucp/info/ucp_attachments.php new file mode 100644 index 0000000..96e7956 --- /dev/null +++ b/includes/ucp/info/ucp_attachments.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_attachments_info +{ + function module() + { + return array( + 'filename' => 'ucp_attachments', + 'title' => 'UCP_ATTACHMENTS', + 'modes' => array( + 'attachments' => array('title' => 'UCP_MAIN_ATTACHMENTS', 'auth' => 'acl_u_attach', 'cat' => array('UCP_MAIN')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/info/ucp_auth_link.php b/includes/ucp/info/ucp_auth_link.php new file mode 100644 index 0000000..57c9269 --- /dev/null +++ b/includes/ucp/info/ucp_auth_link.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_auth_link_info +{ + function module() + { + return array( + 'filename' => 'ucp_auth_link', + 'title' => 'UCP_AUTH_LINK', + 'modes' => array( + 'auth_link' => array('title' => 'UCP_AUTH_LINK_MANAGE', 'auth' => 'authmethod_oauth', 'cat' => array('UCP_PROFILE')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/info/ucp_groups.php b/includes/ucp/info/ucp_groups.php new file mode 100644 index 0000000..42eb285 --- /dev/null +++ b/includes/ucp/info/ucp_groups.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_groups_info +{ + function module() + { + return array( + 'filename' => 'ucp_groups', + 'title' => 'UCP_USERGROUPS', + 'modes' => array( + 'membership' => array('title' => 'UCP_USERGROUPS_MEMBER', 'auth' => '', 'cat' => array('UCP_USERGROUPS')), + 'manage' => array('title' => 'UCP_USERGROUPS_MANAGE', 'auth' => '', 'cat' => array('UCP_USERGROUPS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/info/ucp_main.php b/includes/ucp/info/ucp_main.php new file mode 100644 index 0000000..e967b84 --- /dev/null +++ b/includes/ucp/info/ucp_main.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_main_info +{ + function module() + { + return array( + 'filename' => 'ucp_main', + 'title' => 'UCP_MAIN', + 'modes' => array( + 'front' => array('title' => 'UCP_MAIN_FRONT', 'auth' => '', 'cat' => array('UCP_MAIN')), + 'subscribed' => array('title' => 'UCP_MAIN_SUBSCRIBED', 'auth' => '', 'cat' => array('UCP_MAIN')), + 'bookmarks' => array('title' => 'UCP_MAIN_BOOKMARKS', 'auth' => 'cfg_allow_bookmarks', 'cat' => array('UCP_MAIN')), + 'drafts' => array('title' => 'UCP_MAIN_DRAFTS', 'auth' => '', 'cat' => array('UCP_MAIN')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/info/ucp_notifications.php b/includes/ucp/info/ucp_notifications.php new file mode 100644 index 0000000..94e0467 --- /dev/null +++ b/includes/ucp/info/ucp_notifications.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_notifications_info +{ + function module() + { + return array( + 'filename' => 'ucp_notifications', + 'title' => 'UCP_NOTIFICATION_OPTIONS', + 'modes' => array( + 'notification_options' => array('title' => 'UCP_NOTIFICATION_OPTIONS', 'auth' => '', 'cat' => array('UCP_PREFS')), + 'notification_list' => array('title' => 'UCP_NOTIFICATION_LIST', 'auth' => 'cfg_allow_board_notifications', 'cat' => array('UCP_MAIN')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/info/ucp_pm.php b/includes/ucp/info/ucp_pm.php new file mode 100644 index 0000000..26bd670 --- /dev/null +++ b/includes/ucp/info/ucp_pm.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_pm_info +{ + function module() + { + return array( + 'filename' => 'ucp_pm', + 'title' => 'UCP_PM', + 'modes' => array( + 'view' => array('title' => 'UCP_PM_VIEW', 'auth' => 'cfg_allow_privmsg', 'display' => false, 'cat' => array('UCP_PM')), + 'compose' => array('title' => 'UCP_PM_COMPOSE', 'auth' => 'cfg_allow_privmsg', 'cat' => array('UCP_PM')), + 'drafts' => array('title' => 'UCP_PM_DRAFTS', 'auth' => 'cfg_allow_privmsg', 'cat' => array('UCP_PM')), + 'options' => array('title' => 'UCP_PM_OPTIONS', 'auth' => 'cfg_allow_privmsg', 'cat' => array('UCP_PM')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/info/ucp_prefs.php b/includes/ucp/info/ucp_prefs.php new file mode 100644 index 0000000..4793aa2 --- /dev/null +++ b/includes/ucp/info/ucp_prefs.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_prefs_info +{ + function module() + { + return array( + 'filename' => 'ucp_prefs', + 'title' => 'UCP_PREFS', + 'modes' => array( + 'personal' => array('title' => 'UCP_PREFS_PERSONAL', 'auth' => '', 'cat' => array('UCP_PREFS')), + 'post' => array('title' => 'UCP_PREFS_POST', 'auth' => '', 'cat' => array('UCP_PREFS')), + 'view' => array('title' => 'UCP_PREFS_VIEW', 'auth' => '', 'cat' => array('UCP_PREFS')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/info/ucp_profile.php b/includes/ucp/info/ucp_profile.php new file mode 100644 index 0000000..fc27922 --- /dev/null +++ b/includes/ucp/info/ucp_profile.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_profile_info +{ + function module() + { + return array( + 'filename' => 'ucp_profile', + 'title' => 'UCP_PROFILE', + 'modes' => array( + 'profile_info' => array('title' => 'UCP_PROFILE_PROFILE_INFO', 'auth' => 'acl_u_chgprofileinfo', 'cat' => array('UCP_PROFILE')), + 'signature' => array('title' => 'UCP_PROFILE_SIGNATURE', 'auth' => 'acl_u_sig', 'cat' => array('UCP_PROFILE')), + 'avatar' => array('title' => 'UCP_PROFILE_AVATAR', 'auth' => 'cfg_allow_avatar', 'cat' => array('UCP_PROFILE')), + 'reg_details' => array('title' => 'UCP_PROFILE_REG_DETAILS', 'auth' => '', 'cat' => array('UCP_PROFILE')), + 'autologin_keys'=> array('title' => 'UCP_PROFILE_AUTOLOGIN_KEYS', 'auth' => '', 'cat' => array('UCP_PROFILE')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/info/ucp_zebra.php b/includes/ucp/info/ucp_zebra.php new file mode 100644 index 0000000..69274c2 --- /dev/null +++ b/includes/ucp/info/ucp_zebra.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +class ucp_zebra_info +{ + function module() + { + return array( + 'filename' => 'ucp_zebra', + 'title' => 'UCP_ZEBRA', + 'modes' => array( + 'friends' => array('title' => 'UCP_ZEBRA_FRIENDS', 'auth' => '', 'cat' => array('UCP_ZEBRA')), + 'foes' => array('title' => 'UCP_ZEBRA_FOES', 'auth' => '', 'cat' => array('UCP_ZEBRA')), + ), + ); + } + + function install() + { + } + + function uninstall() + { + } +} diff --git a/includes/ucp/ucp_activate.php b/includes/ucp/ucp_activate.php new file mode 100644 index 0000000..7a90f2e --- /dev/null +++ b/includes/ucp/ucp_activate.php @@ -0,0 +1,170 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_activate +* User activation +*/ +class ucp_activate +{ + var $u_action; + + function main($id, $mode) + { + global $config, $phpbb_root_path, $phpEx, $request; + global $db, $user, $auth, $phpbb_container, $phpbb_log, $phpbb_dispatcher; + + $user_id = $request->variable('u', 0); + $key = $request->variable('k', ''); + + $sql = 'SELECT user_id, username, user_type, user_email, user_newpasswd, user_lang, user_notify_type, user_actkey, user_inactive_reason + FROM ' . USERS_TABLE . " + WHERE user_id = $user_id"; + $result = $db->sql_query($sql); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$user_row) + { + trigger_error('NO_USER'); + } + + if ($user_row['user_type'] <> USER_INACTIVE && !$user_row['user_newpasswd']) + { + meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx")); + trigger_error('ALREADY_ACTIVATED'); + } + + if ($user_row['user_inactive_reason'] == INACTIVE_MANUAL || $user_row['user_actkey'] !== $key) + { + trigger_error('WRONG_ACTIVATION'); + } + + // Do not allow activating by non administrators when admin activation is on + // Only activation type the user should be able to do is INACTIVE_REMIND + // or activate a new password which is not an activation state :@ + if (!$user_row['user_newpasswd'] && $user_row['user_inactive_reason'] != INACTIVE_REMIND && $config['require_activation'] == USER_ACTIVATION_ADMIN && !$auth->acl_get('a_user')) + { + if (!$user->data['is_registered']) + { + login_box('', $user->lang['NO_AUTH_OPERATION']); + } + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_OPERATION'); + } + + $update_password = ($user_row['user_newpasswd']) ? true : false; + + if ($update_password) + { + $sql_ary = array( + 'user_actkey' => '', + 'user_password' => $user_row['user_newpasswd'], + 'user_newpasswd' => '', + 'user_login_attempts' => 0, + ); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user_row['user_id']; + $db->sql_query($sql); + + $user->reset_login_keys($user_row['user_id']); + + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_NEW_PASSWORD', false, array( + 'reportee_id' => $user_row['user_id'], + $user_row['username'] + )); + } + + if (!$update_password) + { + include_once($phpbb_root_path . 'includes/functions_user.' . $phpEx); + + user_active_flip('activate', $user_row['user_id']); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_actkey = '' + WHERE user_id = {$user_row['user_id']}"; + $db->sql_query($sql); + + // Create the correct logs + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_ACTIVE_USER', false, array( + 'reportee_id' => $user_row['user_id'] + )); + + if ($auth->acl_get('a_user')) + { + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_USER_ACTIVE', false, array($user_row['username'])); + } + } + + if ($config['require_activation'] == USER_ACTIVATION_ADMIN && !$update_password) + { + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + $phpbb_notifications->delete_notifications('notification.type.admin_activate_user', $user_row['user_id']); + + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $messenger = new messenger(false); + + $messenger->template('admin_welcome_activated', $user_row['user_lang']); + + $messenger->set_addresses($user_row); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($user_row['username'])) + ); + + $messenger->send($user_row['user_notify_type']); + + $message = 'ACCOUNT_ACTIVE_ADMIN'; + } + else + { + if (!$update_password) + { + $message = ($user_row['user_inactive_reason'] == INACTIVE_PROFILE) ? 'ACCOUNT_ACTIVE_PROFILE' : 'ACCOUNT_ACTIVE'; + } + else + { + $message = 'PASSWORD_ACTIVATED'; + } + } + + /** + * This event can be used to modify data after user account's activation + * + * @event core.ucp_activate_after + * @var array user_row Array with some user data + * @var string message Language string of the message that will be displayed to the user + * @since 3.1.6-RC1 + */ + $vars = array('user_row', 'message'); + extract($phpbb_dispatcher->trigger_event('core.ucp_activate_after', compact($vars))); + + meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx")); + trigger_error($user->lang[$message]); + } +} diff --git a/includes/ucp/ucp_attachments.php b/includes/ucp/ucp_attachments.php new file mode 100644 index 0000000..c1b623c --- /dev/null +++ b/includes/ucp/ucp_attachments.php @@ -0,0 +1,205 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_attachments +* User attachments +*/ +class ucp_attachments +{ + var $u_action; + + function main($id, $mode) + { + global $template, $user, $db, $config, $phpEx, $phpbb_root_path, $phpbb_container, $request; + + $start = $request->variable('start', 0); + $sort_key = $request->variable('sk', 'a'); + $sort_dir = $request->variable('sd', 'a'); + + $delete = (isset($_POST['delete'])) ? true : false; + $delete_ids = array_keys($request->variable('attachment', array(0))); + + if ($delete && count($delete_ids)) + { + // Validate $delete_ids... + $sql = 'SELECT attach_id + FROM ' . ATTACHMENTS_TABLE . ' + WHERE poster_id = ' . $user->data['user_id'] . ' + AND is_orphan = 0 + AND ' . $db->sql_in_set('attach_id', $delete_ids); + $result = $db->sql_query($sql); + + $delete_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $delete_ids[] = $row['attach_id']; + } + $db->sql_freeresult($result); + } + + if ($delete && count($delete_ids)) + { + $s_hidden_fields = array( + 'delete' => 1 + ); + + foreach ($delete_ids as $attachment_id) + { + $s_hidden_fields['attachment'][$attachment_id] = 1; + } + + if (confirm_box(true)) + { + /** @var \phpbb\attachment\manager $attachment_manager */ + $attachment_manager = $phpbb_container->get('attachment.manager'); + $attachment_manager->delete('attach', $delete_ids); + unset($attachment_manager); + + meta_refresh(3, $this->u_action); + $message = ((count($delete_ids) == 1) ? $user->lang['ATTACHMENT_DELETED'] : $user->lang['ATTACHMENTS_DELETED']) . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + else + { + confirm_box(false, (count($delete_ids) == 1) ? 'DELETE_ATTACHMENT' : 'DELETE_ATTACHMENTS', build_hidden_fields($s_hidden_fields)); + } + } + + // Select box eventually + $sort_key_text = array('a' => $user->lang['SORT_FILENAME'], 'b' => $user->lang['SORT_COMMENT'], 'c' => $user->lang['SORT_EXTENSION'], 'd' => $user->lang['SORT_SIZE'], 'e' => $user->lang['SORT_DOWNLOADS'], 'f' => $user->lang['SORT_POST_TIME'], 'g' => $user->lang['SORT_TOPIC_TITLE']); + $sort_key_sql = array('a' => 'a.real_filename', 'b' => 'a.attach_comment', 'c' => 'a.extension', 'd' => 'a.filesize', 'e' => 'a.download_count', 'f' => 'a.filetime', 'g' => 't.topic_title'); + + $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']); + + $s_sort_key = ''; + foreach ($sort_key_text as $key => $value) + { + $selected = ($sort_key == $key) ? ' selected="selected"' : ''; + $s_sort_key .= ''; + } + + $s_sort_dir = ''; + foreach ($sort_dir_text as $key => $value) + { + $selected = ($sort_dir == $key) ? ' selected="selected"' : ''; + $s_sort_dir .= ''; + } + + if (!isset($sort_key_sql[$sort_key])) + { + $sort_key = 'a'; + } + + $order_by = $sort_key_sql[$sort_key] . ' ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'); + + $sql = 'SELECT COUNT(attach_id) as num_attachments + FROM ' . ATTACHMENTS_TABLE . ' + WHERE poster_id = ' . $user->data['user_id'] . ' + AND is_orphan = 0'; + $result = $db->sql_query($sql); + $num_attachments = $db->sql_fetchfield('num_attachments'); + $db->sql_freeresult($result); + + // Ensure start is a valid value + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $start = $pagination->validate_start($start, $config['topics_per_page'], $num_attachments); + + $sql = 'SELECT a.*, t.topic_title, p.message_subject as message_title + FROM ' . ATTACHMENTS_TABLE . ' a + LEFT JOIN ' . TOPICS_TABLE . ' t ON (a.topic_id = t.topic_id AND a.in_message = 0) + LEFT JOIN ' . PRIVMSGS_TABLE . ' p ON (a.post_msg_id = p.msg_id AND a.in_message = 1) + WHERE a.poster_id = ' . $user->data['user_id'] . " + AND a.is_orphan = 0 + ORDER BY $order_by"; + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $row_count = 0; + if ($row = $db->sql_fetchrow($result)) + { + $template->assign_var('S_ATTACHMENT_ROWS', true); + + do + { + if ($row['in_message']) + { + $view_topic = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&p={$row['post_msg_id']}"); + } + else + { + $view_topic = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t={$row['topic_id']}&p={$row['post_msg_id']}") . "#p{$row['post_msg_id']}"; + } + + $template->assign_block_vars('attachrow', array( + 'ROW_NUMBER' => $row_count + ($start + 1), + 'FILENAME' => $row['real_filename'], + 'COMMENT' => bbcode_nl2br($row['attach_comment']), + 'EXTENSION' => $row['extension'], + 'SIZE' => get_formatted_filesize($row['filesize']), + 'DOWNLOAD_COUNT' => $row['download_count'], + 'POST_TIME' => $user->format_date($row['filetime']), + 'TOPIC_TITLE' => ($row['in_message']) ? $row['message_title'] : $row['topic_title'], + + 'ATTACH_ID' => $row['attach_id'], + 'POST_ID' => $row['post_msg_id'], + 'TOPIC_ID' => $row['topic_id'], + + 'S_IN_MESSAGE' => $row['in_message'], + + 'U_VIEW_ATTACHMENT' => append_sid("{$phpbb_root_path}download/file.$phpEx", 'id=' . $row['attach_id']), + 'U_VIEW_TOPIC' => $view_topic) + ); + + $row_count++; + } + while ($row = $db->sql_fetchrow($result)); + } + $db->sql_freeresult($result); + + $base_url = $this->u_action . "&sk=$sort_key&sd=$sort_dir"; + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $num_attachments, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'TOTAL_ATTACHMENTS' => $num_attachments, + 'NUM_ATTACHMENTS' => $user->lang('NUM_ATTACHMENTS', $num_attachments), + + 'L_TITLE' => $user->lang['UCP_ATTACHMENTS'], + + 'U_SORT_FILENAME' => $this->u_action . "&sk=a&sd=" . (($sort_key == 'a' && $sort_dir == 'a') ? 'd' : 'a'), + 'U_SORT_FILE_COMMENT' => $this->u_action . "&sk=b&sd=" . (($sort_key == 'b' && $sort_dir == 'a') ? 'd' : 'a'), + 'U_SORT_EXTENSION' => $this->u_action . "&sk=c&sd=" . (($sort_key == 'c' && $sort_dir == 'a') ? 'd' : 'a'), + 'U_SORT_FILESIZE' => $this->u_action . "&sk=d&sd=" . (($sort_key == 'd' && $sort_dir == 'a') ? 'd' : 'a'), + 'U_SORT_DOWNLOADS' => $this->u_action . "&sk=e&sd=" . (($sort_key == 'e' && $sort_dir == 'a') ? 'd' : 'a'), + 'U_SORT_POST_TIME' => $this->u_action . "&sk=f&sd=" . (($sort_key == 'f' && $sort_dir == 'a') ? 'd' : 'a'), + 'U_SORT_TOPIC_TITLE' => $this->u_action . "&sk=g&sd=" . (($sort_key == 'g' && $sort_dir == 'a') ? 'd' : 'a'), + + 'S_DISPLAY_MARK_ALL' => ($num_attachments) ? true : false, + 'S_DISPLAY_PAGINATION' => ($num_attachments) ? true : false, + 'S_UCP_ACTION' => $this->u_action, + 'S_SORT_OPTIONS' => $s_sort_key, + 'S_ORDER_SELECT' => $s_sort_dir) + ); + + $this->tpl_name = 'ucp_attachments'; + $this->page_title = 'UCP_ATTACHMENTS'; + } +} diff --git a/includes/ucp/ucp_auth_link.php b/includes/ucp/ucp_auth_link.php new file mode 100644 index 0000000..e069f15 --- /dev/null +++ b/includes/ucp/ucp_auth_link.php @@ -0,0 +1,148 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class ucp_auth_link +{ + /** + * @var string + */ + public $u_action; + + /** + * Generates the ucp_auth_link page and handles the auth link process + * + * @param int $id + * @param string $mode + */ + public function main($id, $mode) + { + global $request, $template, $phpbb_container, $user; + + $error = array(); + + /* @var $provider_collection \phpbb\auth\provider_collection */ + $provider_collection = $phpbb_container->get('auth.provider_collection'); + $auth_provider = $provider_collection->get_provider(); + + // confirm that the auth provider supports this page + $provider_data = $auth_provider->get_auth_link_data(); + if ($provider_data === null) + { + $error[] = 'UCP_AUTH_LINK_NOT_SUPPORTED'; + } + + $s_hidden_fields = array(); + add_form_key('ucp_auth_link'); + + $submit = $request->variable('submit', false, false, \phpbb\request\request_interface::POST); + + // This path is only for primary actions + if (!count($error) && $submit) + { + if (!check_form_key('ucp_auth_link')) + { + $error[] = 'FORM_INVALID'; + } + + if (!count($error)) + { + // Any post data could be necessary for auth (un)linking + $link_data = $request->get_super_global(\phpbb\request\request_interface::POST); + + // The current user_id is also necessary + $link_data['user_id'] = $user->data['user_id']; + + // Tell the provider that the method is auth_link not login_link + $link_data['link_method'] = 'auth_link'; + + if ($request->variable('link', 0, false, \phpbb\request\request_interface::POST)) + { + $error[] = $auth_provider->link_account($link_data); + } + else + { + $error[] = $auth_provider->unlink_account($link_data); + } + + // Template data may have changed, get new data + $provider_data = $auth_provider->get_auth_link_data(); + } + } + + // In some cases, a request to an external server may be required. In + // these cases, the GET parameter 'link' should exist and should be true + if ($request->variable('link', false)) + { + // In this case the link data should only be populated with the + // link_method as the provider dictates how data is returned to it. + $link_data = array('link_method' => 'auth_link'); + + $error[] = $auth_provider->link_account($link_data); + + // Template data may have changed, get new data + $provider_data = $auth_provider->get_auth_link_data(); + } + + if (isset($provider_data['VARS'])) + { + // Handle hidden fields separately + if (isset($provider_data['VARS']['HIDDEN_FIELDS'])) + { + $s_hidden_fields = array_merge($s_hidden_fields, $provider_data['VARS']['HIDDEN_FIELDS']); + unset($provider_data['VARS']['HIDDEN_FIELDS']); + } + + $template->assign_vars($provider_data['VARS']); + } + + if (isset($provider_data['BLOCK_VAR_NAME'])) + { + foreach ($provider_data['BLOCK_VARS'] as $block_vars) + { + // See if there are additional hidden fields. This should be an associative array + if (isset($block_vars['HIDDEN_FIELDS'])) + { + $block_vars['HIDDEN_FIELDS'] = build_hidden_fields($block_vars['HIDDEN_FIELDS']); + } + + $template->assign_block_vars($provider_data['BLOCK_VAR_NAME'], $block_vars); + } + } + + $s_hidden_fields = build_hidden_fields($s_hidden_fields); + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + $error = implode('
', $error); + + $template->assign_vars(array( + 'ERROR' => $error, + + 'PROVIDER_TEMPLATE_FILE' => $provider_data['TEMPLATE_FILE'], + + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + 'S_UCP_ACTION' => $this->u_action, + )); + + $this->tpl_name = 'ucp_auth_link'; + $this->page_title = 'UCP_AUTH_LINK'; + } +} diff --git a/includes/ucp/ucp_confirm.php b/includes/ucp/ucp_confirm.php new file mode 100644 index 0000000..cdf4de6 --- /dev/null +++ b/includes/ucp/ucp_confirm.php @@ -0,0 +1,48 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_confirm +* Visual confirmation +* +* Note to potential users of this code ... +* +* Remember this is released under the _GPL_ and is subject +* to that licence. Do not incorporate this within software +* released or distributed in any way under a licence other +* than the GPL. We will be watching ... ;) +*/ +class ucp_confirm +{ + var $u_action; + + function main($id, $mode) + { + global $config, $phpbb_container, $request; + + $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']); + $captcha->init($request->variable('type', 0)); + $captcha->execute(); + + garbage_collection(); + exit_handler(); + } +} diff --git a/includes/ucp/ucp_groups.php b/includes/ucp/ucp_groups.php new file mode 100644 index 0000000..2423af8 --- /dev/null +++ b/includes/ucp/ucp_groups.php @@ -0,0 +1,1137 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_groups +*/ +class ucp_groups +{ + var $u_action; + + function main($id, $mode) + { + global $config, $phpbb_root_path, $phpEx, $phpbb_admin_path; + global $db, $user, $auth, $cache, $template; + global $request, $phpbb_container, $phpbb_log; + + /** @var \phpbb\language\language $language Language object */ + $language = $phpbb_container->get('language'); + + $user->add_lang('groups'); + + $return_page = '

' . sprintf($user->lang['RETURN_PAGE'], '', ''); + + $mark_ary = $request->variable('mark', array(0)); + $submit = $request->variable('submit', false, false, \phpbb\request\request_interface::POST); + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + switch ($mode) + { + case 'membership': + + $this->page_title = 'UCP_USERGROUPS_MEMBER'; + + if ($submit || isset($_POST['change_default'])) + { + $action = (isset($_POST['change_default'])) ? 'change_default' : $request->variable('action', ''); + $group_id = ($action == 'change_default') ? $request->variable('default', 0) : $request->variable('selected', 0); + + if (!$group_id) + { + trigger_error('NO_GROUP_SELECTED'); + } + + $sql = 'SELECT group_id, group_name, group_type + FROM ' . GROUPS_TABLE . " + WHERE group_id IN ($group_id, {$user->data['group_id']})"; + $result = $db->sql_query($sql); + + $group_row = array(); + while ($row = $db->sql_fetchrow($result)) + { + $row['group_name'] = $group_helper->get_name($row['group_name']); + $group_row[$row['group_id']] = $row; + } + $db->sql_freeresult($result); + + if (!count($group_row)) + { + trigger_error('GROUP_NOT_EXIST'); + } + + switch ($action) + { + case 'change_default': + // User already having this group set as default? + if ($group_id == $user->data['group_id']) + { + trigger_error($user->lang['ALREADY_DEFAULT_GROUP'] . $return_page); + } + + if (!$auth->acl_get('u_chggrp')) + { + send_status_line(403, 'Forbidden'); + trigger_error($user->lang['NOT_AUTHORISED'] . $return_page); + } + + // User needs to be member of the group in order to make it default + if (!group_memberships($group_id, $user->data['user_id'], true)) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + + if (confirm_box(true)) + { + group_user_attributes('default', $group_id, $user->data['user_id']); + + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_GROUP_CHANGE', false, array( + 'reportee_id' => $user->data['user_id'], + sprintf($user->lang['USER_GROUP_CHANGE'], $group_row[$user->data['group_id']]['group_name'], $group_row[$group_id]['group_name']) + )); + + meta_refresh(3, $this->u_action); + trigger_error($user->lang['CHANGED_DEFAULT_GROUP'] . $return_page); + } + else + { + $s_hidden_fields = array( + 'default' => $group_id, + 'change_default'=> true + ); + + confirm_box(false, sprintf($user->lang['GROUP_CHANGE_DEFAULT'], $group_row[$group_id]['group_name']), build_hidden_fields($s_hidden_fields)); + } + + break; + + case 'resign': + + // User tries to resign from default group but is not allowed to change it? + if ($group_id == $user->data['group_id'] && !$auth->acl_get('u_chggrp')) + { + trigger_error($user->lang['NOT_RESIGN_FROM_DEFAULT_GROUP'] . $return_page); + } + + if (!($row = group_memberships($group_id, $user->data['user_id']))) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + list(, $row) = each($row); + + $sql = 'SELECT group_type + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . $group_id; + $result = $db->sql_query($sql); + $group_type = (int) $db->sql_fetchfield('group_type'); + $db->sql_freeresult($result); + + if ($group_type != GROUP_OPEN && $group_type != GROUP_FREE) + { + trigger_error($user->lang['CANNOT_RESIGN_GROUP'] . $return_page); + } + + if (confirm_box(true)) + { + group_user_del($group_id, $user->data['user_id']); + + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_GROUP_RESIGN', false, array( + 'reportee_id' => $user->data['user_id'], + $group_row[$group_id]['group_name'] + )); + + meta_refresh(3, $this->u_action); + trigger_error($user->lang[($row['user_pending']) ? 'GROUP_RESIGNED_PENDING' : 'GROUP_RESIGNED_MEMBERSHIP'] . $return_page); + } + else + { + $s_hidden_fields = array( + 'selected' => $group_id, + 'action' => 'resign', + 'submit' => true + ); + + confirm_box(false, ($row['user_pending']) ? 'GROUP_RESIGN_PENDING' : 'GROUP_RESIGN_MEMBERSHIP', build_hidden_fields($s_hidden_fields)); + } + + break; + + case 'join': + + $sql = 'SELECT ug.*, u.username, u.username_clean, u.user_email + FROM ' . USER_GROUP_TABLE . ' ug, ' . USERS_TABLE . ' u + WHERE ug.user_id = u.user_id + AND ug.group_id = ' . $group_id . ' + AND ug.user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + if ($row['user_pending']) + { + trigger_error($user->lang['ALREADY_IN_GROUP_PENDING'] . $return_page); + } + + trigger_error($user->lang['ALREADY_IN_GROUP'] . $return_page); + } + + // Check permission to join (open group or request) + if ($group_row[$group_id]['group_type'] != GROUP_OPEN && $group_row[$group_id]['group_type'] != GROUP_FREE) + { + trigger_error($user->lang['CANNOT_JOIN_GROUP'] . $return_page); + } + + if (confirm_box(true)) + { + if ($group_row[$group_id]['group_type'] == GROUP_FREE) + { + group_user_add($group_id, $user->data['user_id']); + } + else + { + group_user_add($group_id, $user->data['user_id'], false, false, false, 0, 1); + } + + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_GROUP_JOIN' . (($group_row[$group_id]['group_type'] == GROUP_FREE) ? '' : '_PENDING'), false, array( + 'reportee_id' => $user->data['user_id'], + $group_row[$group_id]['group_name'] + )); + + meta_refresh(3, $this->u_action); + trigger_error($user->lang[($group_row[$group_id]['group_type'] == GROUP_FREE) ? 'GROUP_JOINED' : 'GROUP_JOINED_PENDING'] . $return_page); + } + else + { + $s_hidden_fields = array( + 'selected' => $group_id, + 'action' => 'join', + 'submit' => true + ); + + confirm_box(false, ($group_row[$group_id]['group_type'] == GROUP_FREE) ? 'GROUP_JOIN' : 'GROUP_JOIN_PENDING', build_hidden_fields($s_hidden_fields)); + } + + break; + + case 'demote': + + if (!($row = group_memberships($group_id, $user->data['user_id']))) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + list(, $row) = each($row); + + if (!$row['group_leader']) + { + trigger_error($user->lang['NOT_LEADER_OF_GROUP'] . $return_page); + } + + if (confirm_box(true)) + { + group_user_attributes('demote', $group_id, $user->data['user_id']); + + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_GROUP_DEMOTE', false, array( + 'reportee_id' => $user->data['user_id'], + $group_row[$group_id]['group_name'] + )); + + meta_refresh(3, $this->u_action); + trigger_error($user->lang['USER_GROUP_DEMOTED'] . $return_page); + } + else + { + $s_hidden_fields = array( + 'selected' => $group_id, + 'action' => 'demote', + 'submit' => true + ); + + confirm_box(false, 'USER_GROUP_DEMOTE', build_hidden_fields($s_hidden_fields)); + } + + break; + } + } + + $sql = 'SELECT g.*, ug.group_leader, ug.user_pending + FROM ' . GROUPS_TABLE . ' g, ' . USER_GROUP_TABLE . ' ug + WHERE ug.user_id = ' . $user->data['user_id'] . ' + AND g.group_id = ug.group_id + ORDER BY g.group_type DESC, g.group_name'; + $result = $db->sql_query($sql); + + $group_id_ary = array(); + $leader_count = $member_count = $pending_count = 0; + while ($row = $db->sql_fetchrow($result)) + { + $block = ($row['group_leader']) ? 'leader' : (($row['user_pending']) ? 'pending' : 'member'); + + switch ($row['group_type']) + { + case GROUP_OPEN: + $group_status = 'OPEN'; + break; + + case GROUP_CLOSED: + $group_status = 'CLOSED'; + break; + + case GROUP_HIDDEN: + $group_status = 'HIDDEN'; + break; + + case GROUP_SPECIAL: + $group_status = 'SPECIAL'; + break; + + case GROUP_FREE: + $group_status = 'FREE'; + break; + } + + $template->assign_block_vars($block, array( + 'GROUP_ID' => $row['group_id'], + 'GROUP_NAME' => $group_helper->get_name($row['group_name']), + 'GROUP_DESC' => ($row['group_type'] <> GROUP_SPECIAL) ? generate_text_for_display($row['group_desc'], $row['group_desc_uid'], $row['group_desc_bitfield'], $row['group_desc_options']) : $user->lang['GROUP_IS_SPECIAL'], + 'GROUP_SPECIAL' => ($row['group_type'] <> GROUP_SPECIAL) ? false : true, + 'GROUP_STATUS' => $user->lang['GROUP_IS_' . $group_status], + 'GROUP_COLOUR' => $row['group_colour'], + + 'U_VIEW_GROUP' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&g=' . $row['group_id']), + + 'S_GROUP_DEFAULT' => ($row['group_id'] == $user->data['group_id']) ? true : false, + 'S_ROW_COUNT' => ${$block . '_count'}++) + ); + + $group_id_ary[] = (int) $row['group_id']; + } + $db->sql_freeresult($result); + + // Hide hidden groups unless user is an admin with group privileges + $sql_and = ($auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) ? '<> ' . GROUP_SPECIAL : 'NOT IN (' . GROUP_SPECIAL . ', ' . GROUP_HIDDEN . ')'; + + $sql = 'SELECT group_id, group_name, group_colour, group_desc, group_desc_uid, group_desc_bitfield, group_desc_options, group_type, group_founder_manage + FROM ' . GROUPS_TABLE . ' + WHERE ' . ((count($group_id_ary)) ? $db->sql_in_set('group_id', $group_id_ary, true) . ' AND ' : '') . " + group_type $sql_and + ORDER BY group_type DESC, group_name"; + $result = $db->sql_query($sql); + + $nonmember_count = 0; + while ($row = $db->sql_fetchrow($result)) + { + switch ($row['group_type']) + { + case GROUP_OPEN: + $group_status = 'OPEN'; + break; + + case GROUP_CLOSED: + $group_status = 'CLOSED'; + break; + + case GROUP_HIDDEN: + $group_status = 'HIDDEN'; + break; + + case GROUP_SPECIAL: + $group_status = 'SPECIAL'; + break; + + case GROUP_FREE: + $group_status = 'FREE'; + break; + } + + $template->assign_block_vars('nonmember', array( + 'GROUP_ID' => $row['group_id'], + 'GROUP_NAME' => $group_helper->get_name($row['group_name']), + 'GROUP_DESC' => ($row['group_type'] <> GROUP_SPECIAL) ? generate_text_for_display($row['group_desc'], $row['group_desc_uid'], $row['group_desc_bitfield'], $row['group_desc_options']) : $user->lang['GROUP_IS_SPECIAL'], + 'GROUP_SPECIAL' => ($row['group_type'] <> GROUP_SPECIAL) ? false : true, + 'GROUP_CLOSED' => ($row['group_type'] <> GROUP_CLOSED || $auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) ? false : true, + 'GROUP_STATUS' => $user->lang['GROUP_IS_' . $group_status], + 'S_CAN_JOIN' => ($row['group_type'] == GROUP_OPEN || $row['group_type'] == GROUP_FREE) ? true : false, + 'GROUP_COLOUR' => $row['group_colour'], + + 'U_VIEW_GROUP' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&g=' . $row['group_id']), + + 'S_ROW_COUNT' => $nonmember_count++) + ); + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'S_CHANGE_DEFAULT' => ($auth->acl_get('u_chggrp')) ? true : false, + 'S_LEADER_COUNT' => $leader_count, + 'S_MEMBER_COUNT' => $member_count, + 'S_PENDING_COUNT' => $pending_count, + 'S_NONMEMBER_COUNT' => $nonmember_count, + + 'S_UCP_ACTION' => $this->u_action) + ); + + break; + + case 'manage': + + $this->page_title = 'UCP_USERGROUPS_MANAGE'; + $action = (isset($_POST['addusers'])) ? 'addusers' : $request->variable('action', ''); + $group_id = $request->variable('g', 0); + + if (!function_exists('phpbb_get_user_rank')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + add_form_key('ucp_groups'); + + if ($group_id) + { + $sql = 'SELECT g.*, t.teampage_position AS group_teampage + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . TEAMPAGE_TABLE . ' t + ON (t.group_id = g.group_id) + WHERE g.group_id = ' . $group_id; + $result = $db->sql_query($sql); + $group_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$group_row) + { + trigger_error($user->lang['NO_GROUP'] . $return_page); + } + + // Check if the user is allowed to manage this group if set to founder only. + if ($user->data['user_type'] != USER_FOUNDER && $group_row['group_founder_manage']) + { + trigger_error($user->lang['NOT_ALLOWED_MANAGE_GROUP'] . $return_page, E_USER_WARNING); + } + + $group_name = $group_row['group_name']; + $group_type = $group_row['group_type']; + + $avatar = phpbb_get_group_avatar($group_row, 'GROUP_AVATAR', true); + + $template->assign_vars(array( + 'GROUP_NAME' => $group_helper->get_name($group_name), + 'GROUP_INTERNAL_NAME' => $group_name, + 'GROUP_COLOUR' => (isset($group_row['group_colour'])) ? $group_row['group_colour'] : '', + 'GROUP_DESC_DISP' => generate_text_for_display($group_row['group_desc'], $group_row['group_desc_uid'], $group_row['group_desc_bitfield'], $group_row['group_desc_options']), + 'GROUP_TYPE' => $group_row['group_type'], + + 'AVATAR' => (empty($avatar) ? '' : $avatar), + 'AVATAR_IMAGE' => (empty($avatar) ? '' : $avatar), + 'AVATAR_WIDTH' => (isset($group_row['group_avatar_width'])) ? $group_row['group_avatar_width'] : '', + 'AVATAR_HEIGHT' => (isset($group_row['group_avatar_height'])) ? $group_row['group_avatar_height'] : '', + )); + } + + switch ($action) + { + case 'edit': + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . $return_page); + } + + if (!($row = group_memberships($group_id, $user->data['user_id']))) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + list(, $row) = each($row); + + if (!$row['group_leader']) + { + trigger_error($user->lang['NOT_LEADER_OF_GROUP'] . $return_page); + } + + $user->add_lang(array('acp/groups', 'acp/common')); + + $update = (isset($_POST['update'])) ? true : false; + + $error = array(); + + // Setup avatar data for later + $avatars_enabled = false; + $avatar_drivers = null; + $avatar_data = null; + $avatar_error = array(); + + /** @var \phpbb\avatar\manager $phpbb_avatar_manager */ + $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); + + if ($config['allow_avatar']) + { + $avatar_drivers = $phpbb_avatar_manager->get_enabled_drivers(); + + // This is normalised data, without the group_ prefix + $avatar_data = \phpbb\avatar\manager::clean_row($group_row, 'group'); + } + + // Handle deletion of avatars + if ($request->is_set_post('avatar_delete')) + { + if (confirm_box(true)) + { + $phpbb_avatar_manager->handle_avatar_delete($db, $user, $avatar_data, GROUPS_TABLE, 'group_'); + $cache->destroy('sql', GROUPS_TABLE); + + $message = ($action == 'edit') ? 'GROUP_UPDATED' : 'GROUP_CREATED'; + trigger_error($user->lang[$message] . $return_page); + } + else + { + confirm_box(false, $user->lang('CONFIRM_AVATAR_DELETE'), build_hidden_fields(array( + 'avatar_delete' => true, + 'i' => $id, + 'mode' => $mode, + 'g' => $group_id, + 'action' => $action)) + ); + } + } + + // Did we submit? + if ($update) + { + $group_name = $request->variable('group_name', '', true); + $group_desc = $request->variable('group_desc', '', true); + $group_type = $request->variable('group_type', GROUP_FREE); + + $allow_desc_bbcode = $request->variable('desc_parse_bbcode', false); + $allow_desc_urls = $request->variable('desc_parse_urls', false); + $allow_desc_smilies = $request->variable('desc_parse_smilies', false); + + $submit_ary = array( + 'colour' => $request->variable('group_colour', ''), + 'rank' => $request->variable('group_rank', 0), + 'receive_pm' => isset($_REQUEST['group_receive_pm']) ? 1 : 0, + 'message_limit' => $request->variable('group_message_limit', 0), + 'max_recipients'=> $request->variable('group_max_recipients', 0), + 'legend' => $group_row['group_legend'], + 'teampage' => $group_row['group_teampage'], + ); + + if ($config['allow_avatar']) + { + // Handle avatar + $driver_name = $phpbb_avatar_manager->clean_driver_name($request->variable('avatar_driver', '')); + + if (in_array($driver_name, $avatar_drivers) && !$request->is_set_post('avatar_delete')) + { + $driver = $phpbb_avatar_manager->get_driver($driver_name); + $result = $driver->process_form($request, $template, $user, $avatar_data, $avatar_error); + + if ($result && empty($avatar_error)) + { + $result['avatar_type'] = $driver_name; + + $submit_ary = array_merge($submit_ary, $result); + } + } + + // Merge any avatars errors into the primary error array + $error = array_merge($error, $phpbb_avatar_manager->localize_errors($user, $avatar_error)); + } + + if (!check_form_key('ucp_groups')) + { + $error[] = $user->lang['FORM_INVALID']; + } + + // Validate submitted colour value + if ($colour_error = validate_data($submit_ary, array('colour' => array('hex_colour', true)))) + { + // Replace "error" string with its real, localised form + $error = array_merge($error, $colour_error); + } + + if (!count($error)) + { + // Only set the rank, colour, etc. if it's changed or if we're adding a new + // group. This prevents existing group members being updated if no changes + // were made. + // However there are some attributes that need to be set everytime, + // otherwise the group gets removed from the feature. + $set_attributes = array('legend', 'teampage'); + + $group_attributes = array(); + $test_variables = array( + 'rank' => 'int', + 'colour' => 'string', + 'avatar' => 'string', + 'avatar_type' => 'string', + 'avatar_width' => 'int', + 'avatar_height' => 'int', + 'receive_pm' => 'int', + 'legend' => 'int', + 'teampage' => 'int', + 'message_limit' => 'int', + 'max_recipients'=> 'int', + ); + + foreach ($test_variables as $test => $type) + { + if (isset($submit_ary[$test]) && ($action == 'add' || $group_row['group_' . $test] != $submit_ary[$test] || isset($group_attributes['group_avatar']) && strpos($test, 'avatar') === 0 || in_array($test, $set_attributes))) + { + settype($submit_ary[$test], $type); + $group_attributes['group_' . $test] = $group_row['group_' . $test] = $submit_ary[$test]; + } + } + + if (!($error = group_create($group_id, $group_type, $group_name, $group_desc, $group_attributes, $allow_desc_bbcode, $allow_desc_urls, $allow_desc_smilies))) + { + $cache->destroy('sql', GROUPS_TABLE); + $cache->destroy('sql', TEAMPAGE_TABLE); + + $message = ($action == 'edit') ? 'GROUP_UPDATED' : 'GROUP_CREATED'; + trigger_error($user->lang[$message] . $return_page); + } + } + + if (count($error)) + { + $error = array_map(array(&$user, 'lang'), $error); + $group_rank = $submit_ary['rank']; + + $group_desc_data = array( + 'text' => $group_desc, + 'allow_bbcode' => $allow_desc_bbcode, + 'allow_smilies' => $allow_desc_smilies, + 'allow_urls' => $allow_desc_urls + ); + } + } + else if (!$group_id) + { + $group_desc_data = array( + 'text' => '', + 'allow_bbcode' => true, + 'allow_smilies' => true, + 'allow_urls' => true + ); + $group_rank = 0; + $group_type = GROUP_OPEN; + } + else + { + $group_desc_data = generate_text_for_edit($group_row['group_desc'], $group_row['group_desc_uid'], $group_row['group_desc_options']); + $group_rank = $group_row['group_rank']; + } + + $sql = 'SELECT * + FROM ' . RANKS_TABLE . ' + WHERE rank_special = 1 + ORDER BY rank_title'; + $result = $db->sql_query($sql); + + $rank_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + $selected = ($group_rank && $row['rank_id'] == $group_rank) ? ' selected="selected"' : ''; + $rank_options .= ''; + } + $db->sql_freeresult($result); + + $type_free = ($group_type == GROUP_FREE) ? ' checked="checked"' : ''; + $type_open = ($group_type == GROUP_OPEN) ? ' checked="checked"' : ''; + $type_closed = ($group_type == GROUP_CLOSED) ? ' checked="checked"' : ''; + $type_hidden = ($group_type == GROUP_HIDDEN) ? ' checked="checked"' : ''; + + // Load up stuff for avatars + if ($config['allow_avatar']) + { + $avatars_enabled = false; + $selected_driver = $phpbb_avatar_manager->clean_driver_name($request->variable('avatar_driver', $avatar_data['avatar_type'])); + + // Assign min and max values before generating avatar driver html + $template->assign_vars(array( + 'AVATAR_MIN_WIDTH' => $config['avatar_min_width'], + 'AVATAR_MAX_WIDTH' => $config['avatar_max_width'], + 'AVATAR_MIN_HEIGHT' => $config['avatar_min_height'], + 'AVATAR_MAX_HEIGHT' => $config['avatar_max_height'], + )); + + foreach ($avatar_drivers as $current_driver) + { + $driver = $phpbb_avatar_manager->get_driver($current_driver); + + $avatars_enabled = true; + $template->set_filenames(array( + 'avatar' => $driver->get_template_name(), + )); + + if ($driver->prepare_form($request, $template, $user, $avatar_data, $avatar_error)) + { + $driver_name = $phpbb_avatar_manager->prepare_driver_name($current_driver); + $driver_upper = strtoupper($driver_name); + $template->assign_block_vars('avatar_drivers', array( + 'L_TITLE' => $user->lang($driver_upper . '_TITLE'), + 'L_EXPLAIN' => $user->lang($driver_upper . '_EXPLAIN'), + + 'DRIVER' => $driver_name, + 'SELECTED' => $current_driver == $selected_driver, + 'OUTPUT' => $template->assign_display('avatar'), + )); + } + } + } + + if (isset($phpbb_avatar_manager) && !$update) + { + // Merge any avatars errors into the primary error array + $error = array_merge($error, $phpbb_avatar_manager->localize_errors($user, $avatar_error)); + } + + $template->assign_vars(array( + 'S_EDIT' => true, + 'S_INCLUDE_SWATCH' => true, + 'S_FORM_ENCTYPE' => ' enctype="multipart/form-data"', + 'S_ERROR' => (count($error)) ? true : false, + 'S_SPECIAL_GROUP' => ($group_type == GROUP_SPECIAL) ? true : false, + 'S_AVATARS_ENABLED' => ($config['allow_avatar'] && $avatars_enabled), + 'S_GROUP_MANAGE' => true, + + 'ERROR_MSG' => (count($error)) ? implode('
', $error) : '', + 'GROUP_RECEIVE_PM' => (isset($group_row['group_receive_pm']) && $group_row['group_receive_pm']) ? ' checked="checked"' : '', + 'GROUP_MESSAGE_LIMIT' => (isset($group_row['group_message_limit'])) ? $group_row['group_message_limit'] : 0, + 'GROUP_MAX_RECIPIENTS' => (isset($group_row['group_max_recipients'])) ? $group_row['group_max_recipients'] : 0, + + 'GROUP_DESC' => $group_desc_data['text'], + 'S_DESC_BBCODE_CHECKED' => $group_desc_data['allow_bbcode'], + 'S_DESC_URLS_CHECKED' => $group_desc_data['allow_urls'], + 'S_DESC_SMILIES_CHECKED'=> $group_desc_data['allow_smilies'], + + 'S_RANK_OPTIONS' => $rank_options, + + 'GROUP_TYPE_FREE' => GROUP_FREE, + 'GROUP_TYPE_OPEN' => GROUP_OPEN, + 'GROUP_TYPE_CLOSED' => GROUP_CLOSED, + 'GROUP_TYPE_HIDDEN' => GROUP_HIDDEN, + 'GROUP_TYPE_SPECIAL' => GROUP_SPECIAL, + + 'GROUP_FREE' => $type_free, + 'GROUP_OPEN' => $type_open, + 'GROUP_CLOSED' => $type_closed, + 'GROUP_HIDDEN' => $type_hidden, + + 'S_UCP_ACTION' => $this->u_action . "&action=$action&g=$group_id", + 'L_AVATAR_EXPLAIN' => phpbb_avatar_explanation_string(), + )); + + break; + + case 'list': + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . $return_page); + } + + if (!($row = group_memberships($group_id, $user->data['user_id']))) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + list(, $row) = each($row); + + if (!$row['group_leader']) + { + trigger_error($user->lang['NOT_LEADER_OF_GROUP'] . $return_page); + } + + $user->add_lang(array('acp/groups', 'acp/common')); + $start = $request->variable('start', 0); + + // Grab the leaders - always, on every page... + $sql = 'SELECT u.user_id, u.username, u.username_clean, u.user_colour, u.user_regdate, u.user_posts, u.group_id, ug.group_leader, ug.user_pending + FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . " ug + WHERE ug.group_id = $group_id + AND u.user_id = ug.user_id + AND ug.group_leader = 1 + ORDER BY ug.user_pending DESC, u.username_clean"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('leader', array( + 'USERNAME' => $row['username'], + 'USERNAME_COLOUR' => $row['user_colour'], + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'U_USER_VIEW' => get_username_string('profile', $row['user_id'], $row['username']), + 'S_GROUP_DEFAULT' => ($row['group_id'] == $group_id) ? true : false, + 'JOINED' => ($row['user_regdate']) ? $user->format_date($row['user_regdate']) : ' - ', + 'USER_POSTS' => $row['user_posts'], + 'USER_ID' => $row['user_id']) + ); + } + $db->sql_freeresult($result); + + // Total number of group members (non-leaders) + $sql = 'SELECT COUNT(user_id) AS total_members + FROM ' . USER_GROUP_TABLE . " + WHERE group_id = $group_id + AND group_leader = 0"; + $result = $db->sql_query($sql); + $total_members = (int) $db->sql_fetchfield('total_members'); + $db->sql_freeresult($result); + + // Grab the members + $sql = 'SELECT u.user_id, u.username, u.username_clean, u.user_colour, u.user_regdate, u.user_posts, u.group_id, ug.group_leader, ug.user_pending + FROM ' . USERS_TABLE . ' u, ' . USER_GROUP_TABLE . " ug + WHERE ug.group_id = $group_id + AND u.user_id = ug.user_id + AND ug.group_leader = 0 + ORDER BY ug.user_pending DESC, u.username_clean"; + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $pending = false; + $approved = false; + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['user_pending'] && !$pending) + { + $template->assign_block_vars('member', array( + 'S_PENDING' => true) + ); + $template->assign_var('S_PENDING_SET', true); + + $pending = true; + } + else if (!$row['user_pending'] && !$approved) + { + $template->assign_block_vars('member', array( + 'S_APPROVED' => true) + ); + $template->assign_var('S_APPROVED_SET', true); + + $approved = true; + } + + $template->assign_block_vars('member', array( + 'USERNAME' => $row['username'], + 'USERNAME_COLOUR' => $row['user_colour'], + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'U_USER_VIEW' => get_username_string('profile', $row['user_id'], $row['username']), + 'S_GROUP_DEFAULT' => ($row['group_id'] == $group_id) ? true : false, + 'JOINED' => ($row['user_regdate']) ? $user->format_date($row['user_regdate']) : ' - ', + 'USER_POSTS' => $row['user_posts'], + 'USER_ID' => $row['user_id']) + ); + } + $db->sql_freeresult($result); + + $s_action_options = ''; + $options = array('default' => 'DEFAULT', 'approve' => 'APPROVE', 'deleteusers' => 'DELETE'); + + foreach ($options as $option => $lang) + { + $s_action_options .= ''; + } + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $base_url = $this->u_action . "&action=$action&g=$group_id"; + $start = $pagination->validate_start($start, $config['topics_per_page'], $total_members); + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $total_members, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'S_LIST' => true, + 'S_ACTION_OPTIONS' => $s_action_options, + + 'U_ACTION' => $this->u_action . "&g=$group_id", + 'S_UCP_ACTION' => $this->u_action . "&g=$group_id", + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=ucp&field=usernames'), + )); + + break; + + case 'approve': + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . $return_page); + } + + if (!($row = group_memberships($group_id, $user->data['user_id']))) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + list(, $row) = each($row); + + if (!$row['group_leader']) + { + trigger_error($user->lang['NOT_LEADER_OF_GROUP'] . $return_page); + } + + $user->add_lang('acp/groups'); + + // Approve, demote or promote + group_user_attributes('approve', $group_id, $mark_ary, false, false); + + trigger_error($user->lang['USERS_APPROVED'] . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + + break; + + case 'default': + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . $return_page); + } + + if (!($row = group_memberships($group_id, $user->data['user_id']))) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + list(, $row) = each($row); + + if (!$row['group_leader']) + { + trigger_error($user->lang['NOT_LEADER_OF_GROUP'] . $return_page); + } + + $group_row['group_name'] = $group_helper->get_name($group_row['group_name']); + + if (confirm_box(true)) + { + if (!count($mark_ary)) + { + $start = 0; + + do + { + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . " + WHERE group_id = $group_id + ORDER BY user_id"; + $result = $db->sql_query_limit($sql, 200, $start); + + $mark_ary = array(); + if ($row = $db->sql_fetchrow($result)) + { + do + { + $mark_ary[] = $row['user_id']; + } + while ($row = $db->sql_fetchrow($result)); + + group_user_attributes('default', $group_id, $mark_ary, false, $group_row['group_name'], $group_row); + + $start = (count($mark_ary) < 200) ? 0 : $start + 200; + } + else + { + $start = 0; + } + $db->sql_freeresult($result); + } + while ($start); + } + else + { + group_user_attributes('default', $group_id, $mark_ary, false, $group_row['group_name'], $group_row); + } + + $user->add_lang('acp/groups'); + + trigger_error($user->lang['GROUP_DEFS_UPDATED'] . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + } + else + { + $user->add_lang('acp/common'); + + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } + + // redirect to last screen + redirect($this->u_action . '&action=list&g=' . $group_id); + + break; + + case 'deleteusers': + + $user->add_lang(array('acp/groups', 'acp/common')); + + if (!($row = group_memberships($group_id, $user->data['user_id']))) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + list(, $row) = each($row); + + if (!$row['group_leader']) + { + trigger_error($user->lang['NOT_LEADER_OF_GROUP'] . $return_page); + } + + $group_row['group_name'] = $group_helper->get_name($group_row['group_name']); + + if (confirm_box(true)) + { + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . $return_page); + } + + $error = group_user_del($group_id, $mark_ary, false, $group_row['group_name']); + + if ($error) + { + trigger_error($user->lang[$error] . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + } + + trigger_error($user->lang['GROUP_USERS_REMOVE'] . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mark' => $mark_ary, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action)) + ); + } + + // redirect to last screen + redirect($this->u_action . '&action=list&g=' . $group_id); + + break; + + case 'addusers': + + $user->add_lang(array('acp/groups', 'acp/common')); + + $names = $request->variable('usernames', '', true); + + if (!$group_id) + { + trigger_error($user->lang['NO_GROUP'] . $return_page); + } + + if (!$names) + { + trigger_error($user->lang['NO_USERS'] . $return_page); + } + + if (!($row = group_memberships($group_id, $user->data['user_id']))) + { + trigger_error($user->lang['NOT_MEMBER_OF_GROUP'] . $return_page); + } + list(, $row) = each($row); + + if (!$row['group_leader']) + { + trigger_error($user->lang['NOT_LEADER_OF_GROUP'] . $return_page); + } + + $name_ary = array_unique(explode("\n", $names)); + $group_name = $group_helper->get_name($group_row['group_name']); + + $default = $request->variable('default', 0); + + if (confirm_box(true)) + { + $return_manage_page = '

' . $language->lang('RETURN_PAGE', '', ''); + + // Add user/s to group + if ($error = group_user_add($group_id, false, $name_ary, $group_name, $default, 0, 0, $group_row)) + { + $display_message = $language->lang($error); + + if ($error == 'GROUP_USERS_INVALID') + { + // Find which users don't exist + $actual_name_ary = $name_ary; + $actual_user_id_ary = []; + user_get_id_name($actual_user_id_ary, $actual_name_ary, false, true); + + $display_message = $language->lang('GROUP_USERS_INVALID', implode($language->lang('COMMA_SEPARATOR'), array_udiff($name_ary, $actual_name_ary, 'strcasecmp'))); + } + + trigger_error($display_message . $return_manage_page); + } + + trigger_error($language->lang('GROUP_USERS_ADDED') . $return_manage_page); + } + else + { + $s_hidden_fields = array( + 'default' => $default, + 'usernames' => $names, + 'g' => $group_id, + 'i' => $id, + 'mode' => $mode, + 'action' => $action + ); + + confirm_box(false, $user->lang('GROUP_CONFIRM_ADD_USERS', count($name_ary), implode($user->lang['COMMA_SEPARATOR'], $name_ary)), build_hidden_fields($s_hidden_fields)); + } + + trigger_error($user->lang['NO_USERS_ADDED'] . '

' . sprintf($user->lang['RETURN_PAGE'], '', '')); + + break; + + default: + $user->add_lang('acp/common'); + + $sql = 'SELECT g.group_id, g.group_name, g.group_colour, g.group_desc, g.group_desc_uid, g.group_desc_bitfield, g.group_desc_options, g.group_type, ug.group_leader + FROM ' . GROUPS_TABLE . ' g, ' . USER_GROUP_TABLE . ' ug + WHERE ug.user_id = ' . $user->data['user_id'] . ' + AND g.group_id = ug.group_id + AND ug.group_leader = 1 + ORDER BY g.group_type DESC, g.group_name'; + $result = $db->sql_query($sql); + + while ($value = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('leader', array( + 'GROUP_NAME' => $group_helper->get_name($value['group_name']), + 'GROUP_DESC' => generate_text_for_display($value['group_desc'], $value['group_desc_uid'], $value['group_desc_bitfield'], $value['group_desc_options']), + 'GROUP_TYPE' => $value['group_type'], + 'GROUP_ID' => $value['group_id'], + 'GROUP_COLOUR' => $value['group_colour'], + + 'U_LIST' => $this->u_action . "&action=list&g={$value['group_id']}", + 'U_EDIT' => $this->u_action . "&action=edit&g={$value['group_id']}") + ); + } + $db->sql_freeresult($result); + + break; + } + + break; + } + + $this->tpl_name = 'ucp_groups_' . $mode; + } +} diff --git a/includes/ucp/ucp_login_link.php b/includes/ucp/ucp_login_link.php new file mode 100644 index 0000000..c1f307e --- /dev/null +++ b/includes/ucp/ucp_login_link.php @@ -0,0 +1,264 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_login_link +* Allows users of external accounts link those accounts to their phpBB accounts +* during an attempted login. +*/ +class ucp_login_link +{ + /** + * @var string + */ + public $u_action; + + /** + * Generates the ucp_login_link page and handles login link process + * + * @param int $id + * @param string $mode + */ + function main($id, $mode) + { + global $phpbb_container, $request, $template, $user, $phpbb_dispatcher; + global $phpbb_root_path, $phpEx; + + // Initialize necessary variables + $login_error = null; + $login_link_error = null; + $login_username = null; + + // Build the data array + $data = $this->get_login_link_data_array(); + + // Ensure the person was sent here with login_link data + if (empty($data)) + { + $login_link_error = $user->lang['LOGIN_LINK_NO_DATA_PROVIDED']; + } + + // Use the auth_provider requested even if different from configured + /* @var $provider_collection \phpbb\auth\provider_collection */ + $provider_collection = $phpbb_container->get('auth.provider_collection'); + $auth_provider = $provider_collection->get_provider($request->variable('auth_provider', '')); + + // Set the link_method to login_link + $data['link_method'] = 'login_link'; + + // Have the authentication provider check that all necessary data is available + $result = $auth_provider->login_link_has_necessary_data($data); + if ($result !== null) + { + $login_link_error = $user->lang[$result]; + } + + // Perform link action if there is no error + if (!$login_link_error) + { + if ($request->is_set_post('login')) + { + $login_username = $request->variable('login_username', '', true, \phpbb\request\request_interface::POST); + $login_password = $request->untrimmed_variable('login_password', '', true, \phpbb\request\request_interface::POST); + + $login_result = $auth_provider->login($login_username, $login_password); + + // We only care if there is or is not an error + $login_error = $this->process_login_result($login_result); + + if (!$login_error) + { + // Give the user_id to the data + $data['user_id'] = $login_result['user_row']['user_id']; + + // The user is now logged in, attempt to link the user to the external account + $result = $auth_provider->link_account($data); + + if ($result) + { + $login_link_error = $user->lang[$result]; + } + else + { + // Finish login + $user->session_create($login_result['user_row']['user_id'], false, false, true); + + // Perform a redirect as the account has been linked + $this->perform_redirect(); + } + } + } + } + + $tpl_ary = array( + // Common template elements + 'LOGIN_LINK_ERROR' => $login_link_error, + 'PASSWORD_CREDENTIAL' => 'login_password', + 'USERNAME_CREDENTIAL' => 'login_username', + 'S_HIDDEN_FIELDS' => $this->get_hidden_fields($data), + + // Registration elements + 'REGISTER_ACTION' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'), + + // Login elements + 'LOGIN_ERROR' => $login_error, + 'LOGIN_USERNAME' => $login_username, + ); + + /** + * Event to perform additional actions before ucp_login_link is displayed + * + * @event core.ucp_login_link_template_after + * @var array data Login link data + * @var \phpbb\auth\provider_interface auth_provider Auth provider + * @var string login_link_error Login link error + * @var string login_error Login error + * @var string login_username Login username + * @var array tpl_ary Template variables + * @since 3.2.4-RC1 + */ + $vars = array('data', 'auth_provider', 'login_link_error', 'login_error', 'login_username', 'tpl_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_login_link_template_after', compact($vars))); + + $template->assign_vars($tpl_ary); + + $this->tpl_name = 'ucp_login_link'; + $this->page_title = 'UCP_LOGIN_LINK'; + } + + /** + * Builds the hidden fields string from the data array. + * + * @param array $data This function only includes data in the array + * that has a key that begins with 'login_link_' + * @return string A string of hidden fields that can be included in the + * template + */ + protected function get_hidden_fields($data) + { + $fields = array(); + + foreach ($data as $key => $value) + { + $fields['login_link_' . $key] = $value; + } + + return build_hidden_fields($fields); + } + + /** + * Builds the login_link data array + * + * @return array All login_link data. This is all GET data whose names + * begin with 'login_link_' + */ + protected function get_login_link_data_array() + { + global $request; + + $var_names = $request->variable_names(\phpbb\request\request_interface::GET); + $login_link_data = array(); + $string_start_length = strlen('login_link_'); + + foreach ($var_names as $var_name) + { + if (strpos($var_name, 'login_link_') === 0) + { + $key_name = substr($var_name, $string_start_length); + $login_link_data[$key_name] = $request->variable($var_name, '', false, \phpbb\request\request_interface::GET); + } + } + + return $login_link_data; + } + + /** + * Processes the result array from the login process + * @param array $result The login result array + * @return string|null If there was an error in the process, a string is + * returned. If the login was successful, then null is + * returned. + */ + protected function process_login_result($result) + { + global $config, $template, $user, $phpbb_container; + + $login_error = null; + + if ($result['status'] != LOGIN_SUCCESS) + { + // Handle all errors first + if ($result['status'] == LOGIN_BREAK) + { + trigger_error($result['error_msg']); + } + + switch ($result['status']) + { + case LOGIN_ERROR_ATTEMPTS: + + $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_LOGIN); + + $template->assign_vars(array( + 'CAPTCHA_TEMPLATE' => $captcha->get_template(), + )); + + $login_error = $user->lang[$result['error_msg']]; + break; + + case LOGIN_ERROR_PASSWORD_CONVERT: + $login_error = sprintf( + $user->lang[$result['error_msg']], + ($config['email_enable']) ? '' : '', + ($config['email_enable']) ? '' : '', + ($config['board_contact']) ? '' : '', + ($config['board_contact']) ? '' : '' + ); + break; + + // Username, password, etc... + default: + $login_error = $user->lang[$result['error_msg']]; + + // Assign admin contact to some error messages + if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD') + { + $login_error = (!$config['board_contact']) ? sprintf($user->lang[$result['error_msg']], '', '') : sprintf($user->lang[$result['error_msg']], '', ''); + } + + break; + } + } + + return $login_error; + } + + /** + * Performs a post login redirect + */ + protected function perform_redirect() + { + global $phpbb_root_path, $phpEx; + $url = append_sid($phpbb_root_path . 'index.' . $phpEx); + redirect($url); + } +} diff --git a/includes/ucp/ucp_main.php b/includes/ucp/ucp_main.php new file mode 100644 index 0000000..36f45f3 --- /dev/null +++ b/includes/ucp/ucp_main.php @@ -0,0 +1,1028 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_main +* UCP Front Panel +*/ +class ucp_main +{ + var $p_master; + var $u_action; + + function __construct($p_master) + { + $this->p_master = $p_master; + } + + function main($id, $mode) + { + global $config, $db, $user, $auth, $template, $phpbb_root_path, $phpEx, $phpbb_dispatcher; + global $request; + + switch ($mode) + { + case 'front': + + $user->add_lang('memberlist'); + + $sql_from = TOPICS_TABLE . ' t '; + $sql_select = ''; + + if ($config['load_db_track']) + { + $sql_from .= ' LEFT JOIN ' . TOPICS_POSTED_TABLE . ' tp ON (tp.topic_id = t.topic_id + AND tp.user_id = ' . $user->data['user_id'] . ')'; + $sql_select .= ', tp.topic_posted'; + } + + if ($config['load_db_lastread']) + { + $sql_from .= ' LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt ON (tt.topic_id = t.topic_id + AND tt.user_id = ' . $user->data['user_id'] . ')'; + $sql_select .= ', tt.mark_time'; + + $sql_from .= ' LEFT JOIN ' . FORUMS_TRACK_TABLE . ' ft ON (ft.forum_id = t.forum_id + AND ft.user_id = ' . $user->data['user_id'] . ')'; + $sql_select .= ', ft.mark_time AS forum_mark_time'; + } + + $topic_type = $user->lang['VIEW_TOPIC_GLOBAL']; + $folder = 'global_read'; + $folder_new = 'global_unread'; + + // Get cleaned up list... return only those forums having the f_read permission + $forum_ary = $auth->acl_getf('f_read', true); + $forum_ary = array_unique(array_keys($forum_ary)); + $topic_list = $rowset = array(); + + // If the user can't see any forums, he can't read any posts because fid of 0 is invalid + if (!empty($forum_ary)) + { + /** + * Modify sql variables before query is processed + * + * @event core.ucp_main_front_modify_sql + * @var string sql_select SQL select + * @var string sql_from SQL from + * @var array forum_ary Forum array + * @since 3.2.4-RC1 + */ + $vars = array( + 'sql_select', + 'sql_from', + 'forum_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_main_front_modify_sql', compact($vars))); + + $sql = "SELECT t.* $sql_select + FROM $sql_from + WHERE t.topic_type = " . POST_GLOBAL . ' + AND ' . $db->sql_in_set('t.forum_id', $forum_ary) . ' + ORDER BY t.topic_last_post_time DESC, t.topic_last_post_id DESC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_list[] = $row['topic_id']; + $rowset[$row['topic_id']] = $row; + } + $db->sql_freeresult($result); + } + + $topic_forum_list = array(); + foreach ($rowset as $t_id => $row) + { + if (isset($forum_tracking_info[$row['forum_id']])) + { + $row['forum_mark_time'] = $forum_tracking_info[$row['forum_id']]; + } + + $topic_forum_list[$row['forum_id']]['forum_mark_time'] = ($config['load_db_lastread'] && $user->data['is_registered'] && isset($row['forum_mark_time'])) ? $row['forum_mark_time'] : 0; + $topic_forum_list[$row['forum_id']]['topics'][] = (int) $t_id; + } + + $topic_tracking_info = $tracking_topics = array(); + if ($config['load_db_lastread']) + { + foreach ($topic_forum_list as $f_id => $topic_row) + { + $topic_tracking_info += get_topic_tracking($f_id, $topic_row['topics'], $rowset, array($f_id => $topic_row['forum_mark_time'])); + } + } + else + { + foreach ($topic_forum_list as $f_id => $topic_row) + { + $topic_tracking_info += get_complete_topic_tracking($f_id, $topic_row['topics']); + } + } + unset($topic_forum_list); + + foreach ($topic_list as $topic_id) + { + $row = &$rowset[$topic_id]; + + $forum_id = $row['forum_id']; + $topic_id = $row['topic_id']; + + $unread_topic = (isset($topic_tracking_info[$topic_id]) && $row['topic_last_post_time'] > $topic_tracking_info[$topic_id]) ? true : false; + + $folder_img = ($unread_topic) ? $folder_new : $folder; + $folder_alt = ($unread_topic) ? 'UNREAD_POSTS' : (($row['topic_status'] == ITEM_LOCKED) ? 'TOPIC_LOCKED' : 'NO_UNREAD_POSTS'); + + if ($row['topic_status'] == ITEM_LOCKED) + { + $folder_img .= '_locked'; + } + + // Posted image? + if (!empty($row['topic_posted']) && $row['topic_posted']) + { + $folder_img .= '_mine'; + } + + $topicrow = array( + 'FORUM_ID' => $forum_id, + 'TOPIC_ID' => $topic_id, + 'TOPIC_AUTHOR' => get_username_string('username', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'TOPIC_AUTHOR_COLOUR' => get_username_string('colour', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'TOPIC_AUTHOR_FULL' => get_username_string('full', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'FIRST_POST_TIME' => $user->format_date($row['topic_time']), + 'LAST_POST_SUBJECT' => censor_text($row['topic_last_post_subject']), + 'LAST_POST_TIME' => $user->format_date($row['topic_last_post_time']), + 'LAST_VIEW_TIME' => $user->format_date($row['topic_last_view_time']), + 'LAST_POST_AUTHOR' => get_username_string('username', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'LAST_POST_AUTHOR_COLOUR' => get_username_string('colour', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'LAST_POST_AUTHOR_FULL' => get_username_string('full', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'TOPIC_TITLE' => censor_text($row['topic_title']), + 'TOPIC_TYPE' => $topic_type, + + 'TOPIC_IMG_STYLE' => $folder_img, + 'TOPIC_FOLDER_IMG' => $user->img($folder_img, $folder_alt), + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $auth->acl_get('f_download', $forum_id) && $row['topic_attachment']) ? $user->img('icon_topic_attach', '') : '', + + 'S_USER_POSTED' => (!empty($row['topic_posted']) && $row['topic_posted']) ? true : false, + 'S_UNREAD' => $unread_topic, + + 'U_TOPIC_AUTHOR' => get_username_string('profile', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'U_LAST_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&p=" . $row['topic_last_post_id']) . '#p' . $row['topic_last_post_id'], + 'U_LAST_POST_AUTHOR' => get_username_string('profile', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'U_NEWEST_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&view=unread") . '#unread', + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id"), + ); + + /** + * Add template variables to a front topics row. + * + * @event core.ucp_main_front_modify_template_vars + * @var array topicrow Array containing the template variables for the row + * @var array row Array containing the subscribed forum row data + * @var int forum_id Forum ID + * @var string folder_img Folder image + * @var string folder_alt Alt text for the folder image + * @since 3.2.4-RC1 + */ + $vars = array( + 'topicrow', + 'row', + 'forum_id', + 'folder_img', + 'folder_alt', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_main_front_modify_template_vars', compact($vars))); + + $template->assign_block_vars('topicrow', $topicrow); + } + + if ($config['load_user_activity']) + { + if (!function_exists('display_user_activity')) + { + include_once($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + display_user_activity($user->data); + } + + // Do the relevant calculations + $memberdays = max(1, round((time() - $user->data['user_regdate']) / 86400)); + $posts_per_day = $user->data['user_posts'] / $memberdays; + $percentage = ($config['num_posts']) ? min(100, ($user->data['user_posts'] / $config['num_posts']) * 100) : 0; + + $template->assign_vars(array( + 'USER_COLOR' => (!empty($user->data['user_colour'])) ? $user->data['user_colour'] : '', + 'JOINED' => $user->format_date($user->data['user_regdate']), + 'LAST_ACTIVE' => (empty($last_active)) ? ' - ' : $user->format_date($last_active), + 'WARNINGS' => ($user->data['user_warnings']) ? $user->data['user_warnings'] : 0, + 'POSTS' => ($user->data['user_posts']) ? $user->data['user_posts'] : 0, + 'POSTS_DAY' => $user->lang('POST_DAY', $posts_per_day), + 'POSTS_PCT' => $user->lang('POST_PCT', $percentage), + +// 'S_GROUP_OPTIONS' => $group_options, + + 'U_SEARCH_USER' => ($auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", 'author_id=' . $user->data['user_id'] . '&sr=posts') : '', + )); + + break; + + case 'subscribed': + + if (!function_exists('topic_status')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + $user->add_lang('viewforum'); + + add_form_key('ucp_front_subscribed'); + + $unwatch = (isset($_POST['unwatch'])) ? true : false; + + /** + * Read and potentially modify the post data used to remove subscriptions to forums/topics + * + * @event core.ucp_main_subscribed_post_data + * @since 3.1.10-RC1 + */ + $phpbb_dispatcher->dispatch('core.ucp_main_subscribed_post_data'); + + if ($unwatch) + { + if (check_form_key('ucp_front_subscribed')) + { + $forums = array_keys($request->variable('f', array(0 => 0))); + $topics = array_keys($request->variable('t', array(0 => 0))); + + if (count($forums) || count($topics)) + { + $l_unwatch = ''; + if (count($forums)) + { + $sql = 'DELETE FROM ' . FORUMS_WATCH_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $forums) . ' + AND user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $l_unwatch .= '_FORUMS'; + } + + if (count($topics)) + { + $sql = 'DELETE FROM ' . TOPICS_WATCH_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', $topics) . ' + AND user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $l_unwatch .= '_TOPICS'; + } + $msg = $user->lang['UNWATCHED' . $l_unwatch]; + } + else + { + $msg = $user->lang['NO_WATCHED_SELECTED']; + } + } + else + { + $msg = $user->lang['FORM_INVALID']; + } + $message = $msg . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + meta_refresh(3, append_sid("{$phpbb_root_path}ucp.$phpEx", "i=$id&mode=subscribed")); + trigger_error($message); + } + + $forbidden_forums = array(); + + if ($config['allow_forum_notify']) + { + $forbidden_forums = $auth->acl_getf('!f_read', true); + $forbidden_forums = array_unique(array_keys($forbidden_forums)); + + $sql_array = array( + 'SELECT' => 'f.*', + + 'FROM' => array( + FORUMS_WATCH_TABLE => 'fw', + FORUMS_TABLE => 'f' + ), + + 'WHERE' => 'fw.user_id = ' . $user->data['user_id'] . ' + AND f.forum_id = fw.forum_id + AND ' . $db->sql_in_set('f.forum_id', $forbidden_forums, true, true), + + 'ORDER_BY' => 'left_id' + ); + + if ($config['load_db_lastread']) + { + $sql_array['LEFT_JOIN'] = array( + array( + 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'), + 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND ft.forum_id = f.forum_id' + ) + ); + + $sql_array['SELECT'] .= ', ft.mark_time '; + } + else + { + $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE); + $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array(); + } + + /** + * Modify the query used to retrieve a list of subscribed forums + * + * @event core.ucp_main_subscribed_forums_modify_query + * @var array sql_array The subscribed forums query + * @var array forbidden_forums The list of forbidden forums + * @since 3.1.10-RC1 + */ + $vars = array( + 'sql_array', + 'forbidden_forums', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_main_subscribed_forums_modify_query', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_id = $row['forum_id']; + + if ($config['load_db_lastread']) + { + $forum_check = (!empty($row['mark_time'])) ? $row['mark_time'] : $user->data['user_lastmark']; + } + else + { + $forum_check = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark']; + } + + $unread_forum = ($row['forum_last_post_time'] > $forum_check) ? true : false; + + // Which folder should we display? + if ($row['forum_status'] == ITEM_LOCKED) + { + $folder_image = ($unread_forum) ? 'forum_unread_locked' : 'forum_read_locked'; + $folder_alt = 'FORUM_LOCKED'; + } + else + { + $folder_image = ($unread_forum) ? 'forum_unread' : 'forum_read'; + $folder_alt = ($unread_forum) ? 'UNREAD_POSTS' : 'NO_UNREAD_POSTS'; + } + + // Create last post link information, if appropriate + if ($row['forum_last_post_id']) + { + $last_post_time = $user->format_date($row['forum_last_post_time']); + $last_post_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&p=" . $row['forum_last_post_id']) . '#p' . $row['forum_last_post_id']; + } + else + { + $last_post_time = $last_post_url = ''; + } + + $template_vars = array( + 'FORUM_ID' => $forum_id, + 'FORUM_IMG_STYLE' => $folder_image, + 'FORUM_FOLDER_IMG' => $user->img($folder_image, $folder_alt), + 'FORUM_IMAGE' => ($row['forum_image']) ? '' . $user->lang[$folder_alt] . '' : '', + 'FORUM_IMAGE_SRC' => ($row['forum_image']) ? $phpbb_root_path . $row['forum_image'] : '', + 'FORUM_NAME' => $row['forum_name'], + 'FORUM_DESC' => generate_text_for_display($row['forum_desc'], $row['forum_desc_uid'], $row['forum_desc_bitfield'], $row['forum_desc_options']), + 'LAST_POST_SUBJECT' => $row['forum_last_post_subject'], + 'LAST_POST_TIME' => $last_post_time, + + 'LAST_POST_AUTHOR' => get_username_string('username', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), + 'LAST_POST_AUTHOR_COLOUR' => get_username_string('colour', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), + 'LAST_POST_AUTHOR_FULL' => get_username_string('full', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), + 'U_LAST_POST_AUTHOR' => get_username_string('profile', $row['forum_last_poster_id'], $row['forum_last_poster_name'], $row['forum_last_poster_colour']), + + 'S_UNREAD_FORUM' => $unread_forum, + + 'U_LAST_POST' => $last_post_url, + 'U_VIEWFORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']) + ); + + /** + * Add template variables to a subscribed forum row. + * + * @event core.ucp_main_subscribed_forum_modify_template_vars + * @var array template_vars Array containing the template variables for the row + * @var array row Array containing the subscribed forum row data + * @var int forum_id Forum ID + * @var string folder_image Folder image + * @var string folder_alt Alt text for the folder image + * @var bool unread_forum Whether the forum has unread content or not + * @var string last_post_time The time of the most recent post, expressed as a formatted date string + * @var string last_post_url The URL of the most recent post in the forum + * @since 3.1.10-RC1 + */ + $vars = array( + 'template_vars', + 'row', + 'forum_id', + 'folder_image', + 'folder_alt', + 'unread_forum', + 'last_post_time', + 'last_post_url', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_main_subscribed_forum_modify_template_vars', compact($vars))); + + $template->assign_block_vars('forumrow', $template_vars); + } + $db->sql_freeresult($result); + } + + // Subscribed Topics + if ($config['allow_topic_notify']) + { + if (empty($forbidden_forums)) + { + $forbidden_forums = $auth->acl_getf('!f_read', true); + $forbidden_forums = array_unique(array_keys($forbidden_forums)); + } + $this->assign_topiclist('subscribed', $forbidden_forums); + } + + $template->assign_vars(array( + 'S_TOPIC_NOTIFY' => $config['allow_topic_notify'], + 'S_FORUM_NOTIFY' => $config['allow_forum_notify'], + )); + + break; + + case 'bookmarks': + + if (!$config['allow_bookmarks']) + { + $template->assign_vars(array( + 'S_NO_DISPLAY_BOOKMARKS' => true) + ); + break; + } + + if (!function_exists('topic_status')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + $user->add_lang('viewforum'); + + if (isset($_POST['unbookmark'])) + { + $s_hidden_fields = array('unbookmark' => 1); + $topics = (isset($_POST['t'])) ? array_keys($request->variable('t', array(0 => 0))) : array(); + $url = $this->u_action; + + if (!count($topics)) + { + trigger_error('NO_BOOKMARKS_SELECTED'); + } + + foreach ($topics as $topic_id) + { + $s_hidden_fields['t'][$topic_id] = 1; + } + + if (confirm_box(true)) + { + $sql = 'DELETE FROM ' . BOOKMARKS_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . ' + AND ' . $db->sql_in_set('topic_id', $topics); + $db->sql_query($sql); + + meta_refresh(3, $url); + $message = $user->lang['BOOKMARKS_REMOVED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + else + { + confirm_box(false, 'REMOVE_SELECTED_BOOKMARKS', build_hidden_fields($s_hidden_fields)); + } + } + $forbidden_forums = $auth->acl_getf('!f_read', true); + $forbidden_forums = array_unique(array_keys($forbidden_forums)); + + $this->assign_topiclist('bookmarks', $forbidden_forums); + + break; + + case 'drafts': + + $pm_drafts = ($this->p_master->p_name == 'pm') ? true : false; + $template->assign_var('S_SHOW_DRAFTS', true); + + $user->add_lang('posting'); + + $edit = (isset($_REQUEST['edit'])) ? true : false; + $submit = (isset($_POST['submit'])) ? true : false; + $draft_id = $request->variable('edit', 0); + $delete = (isset($_POST['delete'])) ? true : false; + + $s_hidden_fields = ($edit) ? '' : ''; + $draft_subject = $draft_message = ''; + add_form_key('ucp_draft'); + + include_once($phpbb_root_path . 'includes/message_parser.' . $phpEx); + $message_parser = new parse_message(); + + if ($delete) + { + if (check_form_key('ucp_draft')) + { + $drafts = array_keys($request->variable('d', array(0 => 0))); + + if (count($drafts)) + { + $sql = 'DELETE FROM ' . DRAFTS_TABLE . ' + WHERE ' . $db->sql_in_set('draft_id', $drafts) . ' + AND user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + } + $msg = $user->lang['DRAFTS_DELETED']; + unset($drafts); + } + else + { + $msg = $user->lang['FORM_INVALID']; + } + $message = $msg . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + meta_refresh(3, $this->u_action); + trigger_error($message); + } + + if ($submit && $edit) + { + $draft_subject = $request->variable('subject', '', true); + $draft_message = $request->variable('message', '', true); + if (check_form_key('ucp_draft')) + { + if ($draft_message && $draft_subject) + { + // $auth->acl_gets can't be used here because it will check for global forum permissions in this case + // In general we don't need too harsh checking here for permissions, as this will be handled later when submitting + $bbcode_status = $auth->acl_get('u_pm_bbcode') || $auth->acl_getf_global('f_bbcode'); + $smilies_status = $auth->acl_get('u_pm_smilies') || $auth->acl_getf_global('f_smilies'); + $img_status = $auth->acl_get('u_pm_img') || $auth->acl_getf_global('f_img'); + $flash_status = $auth->acl_get('u_pm_flash') || $auth->acl_getf_global('f_flash'); + + $message_parser->message = $draft_message; + $message_parser->parse($bbcode_status, $config['allow_post_links'], $smilies_status, $img_status, $flash_status, true, $config['allow_post_links']); + + $draft_row = array( + 'draft_subject' => $draft_subject, + 'draft_message' => $message_parser->message, + ); + + $sql = 'UPDATE ' . DRAFTS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $draft_row) . " + WHERE draft_id = $draft_id + AND user_id = " . $user->data['user_id']; + $db->sql_query($sql); + + $message = $user->lang['DRAFT_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + + meta_refresh(3, $this->u_action); + trigger_error($message); + } + else + { + $template->assign_var('ERROR', ($draft_message == '') ? $user->lang['EMPTY_DRAFT'] : (($draft_subject == '') ? $user->lang['EMPTY_DRAFT_TITLE'] : '')); + } + } + else + { + $template->assign_var('ERROR', $user->lang['FORM_INVALID']); + } + } + + if (!$pm_drafts) + { + $sql = 'SELECT d.*, f.forum_name + FROM ' . DRAFTS_TABLE . ' d, ' . FORUMS_TABLE . ' f + WHERE d.user_id = ' . $user->data['user_id'] . ' ' . + (($edit) ? "AND d.draft_id = $draft_id" : '') . ' + AND f.forum_id = d.forum_id + ORDER BY d.save_time DESC'; + } + else + { + $sql = 'SELECT * FROM ' . DRAFTS_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . ' ' . + (($edit) ? "AND draft_id = $draft_id" : '') . ' + AND forum_id = 0 + AND topic_id = 0 + ORDER BY save_time DESC'; + } + $result = $db->sql_query($sql); + + $draftrows = $topic_ids = array(); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['topic_id']) + { + $topic_ids[] = (int) $row['topic_id']; + } + $draftrows[] = $row; + } + $db->sql_freeresult($result); + + if (count($topic_ids)) + { + $sql = 'SELECT topic_id, forum_id, topic_title + FROM ' . TOPICS_TABLE . ' + WHERE ' . $db->sql_in_set('topic_id', array_unique($topic_ids)); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $topic_rows[$row['topic_id']] = $row; + } + $db->sql_freeresult($result); + } + unset($topic_ids); + + $template->assign_var('S_EDIT_DRAFT', $edit); + + $row_count = 0; + foreach ($draftrows as $draft) + { + $link_topic = $link_forum = $link_pm = false; + $insert_url = $view_url = $title = ''; + + if (isset($topic_rows[$draft['topic_id']]) && $auth->acl_get('f_read', $topic_rows[$draft['topic_id']]['forum_id'])) + { + $link_topic = true; + $view_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $topic_rows[$draft['topic_id']]['forum_id'] . '&t=' . $draft['topic_id']); + $title = $topic_rows[$draft['topic_id']]['topic_title']; + + $insert_url = append_sid("{$phpbb_root_path}posting.$phpEx", 'f=' . $topic_rows[$draft['topic_id']]['forum_id'] . '&t=' . $draft['topic_id'] . '&mode=reply&d=' . $draft['draft_id']); + } + else if ($auth->acl_get('f_read', $draft['forum_id'])) + { + $link_forum = true; + $view_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $draft['forum_id']); + $title = $draft['forum_name']; + + $insert_url = append_sid("{$phpbb_root_path}posting.$phpEx", 'f=' . $draft['forum_id'] . '&mode=post&d=' . $draft['draft_id']); + } + else if ($pm_drafts) + { + $link_pm = true; + $insert_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=$id&mode=compose&d=" . $draft['draft_id']); + } + + if (!$submit) + { + $message_parser->message = $draft['draft_message']; + $message_parser->decode_message(); + $draft_message = $message_parser->message; + } + + $template_row = array( + 'DATE' => $user->format_date($draft['save_time']), + 'DRAFT_MESSAGE' => $draft_message, + 'DRAFT_SUBJECT' => ($submit) ? $draft_subject : $draft['draft_subject'], + 'TITLE' => $title, + + 'DRAFT_ID' => $draft['draft_id'], + 'FORUM_ID' => $draft['forum_id'], + 'TOPIC_ID' => $draft['topic_id'], + + 'U_VIEW' => $view_url, + 'U_VIEW_EDIT' => $this->u_action . '&edit=' . $draft['draft_id'], + 'U_INSERT' => $insert_url, + + 'S_LINK_TOPIC' => $link_topic, + 'S_LINK_FORUM' => $link_forum, + 'S_LINK_PM' => $link_pm, + 'S_HIDDEN_FIELDS' => $s_hidden_fields + ); + $row_count++; + + ($edit) ? $template->assign_vars($template_row) : $template->assign_block_vars('draftrow', $template_row); + } + + if (!$edit) + { + $template->assign_var('S_DRAFT_ROWS', $row_count); + } + + break; + } + + $template->assign_vars(array( + 'L_TITLE' => $user->lang['UCP_MAIN_' . strtoupper($mode)], + + 'S_DISPLAY_MARK_ALL' => ($mode == 'watched' || ($mode == 'drafts' && !isset($_GET['edit']))) ? true : false, + 'S_HIDDEN_FIELDS' => (isset($s_hidden_fields)) ? $s_hidden_fields : '', + 'S_UCP_ACTION' => $this->u_action, + + 'LAST_POST_IMG' => $user->img('icon_topic_latest', 'VIEW_LATEST_POST'), + 'NEWEST_POST_IMG' => $user->img('icon_topic_newest', 'VIEW_NEWEST_POST'), + )); + + // Set desired template + $this->tpl_name = 'ucp_main_' . $mode; + $this->page_title = 'UCP_MAIN_' . strtoupper($mode); + } + + /** + * Build and assign topiclist for bookmarks/subscribed topics + */ + function assign_topiclist($mode = 'subscribed', $forbidden_forum_ary = array()) + { + global $user, $db, $template, $config, $cache, $auth, $phpbb_root_path, $phpEx, $phpbb_container, $request, $phpbb_dispatcher; + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + $table = ($mode == 'subscribed') ? TOPICS_WATCH_TABLE : BOOKMARKS_TABLE; + $start = $request->variable('start', 0); + + // Grab icons + $icons = $cache->obtain_icons(); + + $sql_array = array( + 'SELECT' => 'COUNT(t.topic_id) as topics_count', + + 'FROM' => array( + $table => 'i', + TOPICS_TABLE => 't' + ), + + 'WHERE' => 'i.topic_id = t.topic_id + AND i.user_id = ' . $user->data['user_id'] . ' + AND ' . $db->sql_in_set('t.forum_id', $forbidden_forum_ary, true, true), + ); + + /** + * Modify the query used to retrieve the count of subscribed/bookmarked topics + * + * @event core.ucp_main_topiclist_count_modify_query + * @var array sql_array The subscribed/bookmarked topics query + * @var array forbidden_forum_ary The list of forbidden forums + * @var string mode The type of topic list ('subscribed' or 'bookmarks') + * @since 3.1.10-RC1 + */ + $vars = array( + 'sql_array', + 'forbidden_forum_ary', + 'mode', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_main_topiclist_count_modify_query', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + $topics_count = (int) $db->sql_fetchfield('topics_count'); + $db->sql_freeresult($result); + + if ($topics_count) + { + $start = $pagination->validate_start($start, $config['topics_per_page'], $topics_count); + $pagination->generate_template_pagination($this->u_action, 'pagination', 'start', $topics_count, $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'TOTAL_TOPICS' => $user->lang('VIEW_FORUM_TOPICS', (int) $topics_count), + )); + } + + if ($mode == 'subscribed') + { + $sql_array = array( + 'SELECT' => 't.*, f.forum_name', + + 'FROM' => array( + TOPICS_WATCH_TABLE => 'tw', + TOPICS_TABLE => 't' + ), + + 'WHERE' => 'tw.user_id = ' . $user->data['user_id'] . ' + AND t.topic_id = tw.topic_id + AND ' . $db->sql_in_set('t.forum_id', $forbidden_forum_ary, true, true), + + 'ORDER_BY' => 't.topic_last_post_time DESC, t.topic_last_post_id DESC' + ); + + $sql_array['LEFT_JOIN'] = array(); + } + else + { + $sql_array = array( + 'SELECT' => 't.*, f.forum_name, b.topic_id as b_topic_id', + + 'FROM' => array( + BOOKMARKS_TABLE => 'b', + ), + + 'WHERE' => 'b.user_id = ' . $user->data['user_id'] . ' + AND ' . $db->sql_in_set('f.forum_id', $forbidden_forum_ary, true, true), + + 'ORDER_BY' => 't.topic_last_post_time DESC, t.topic_last_post_id DESC' + ); + + $sql_array['LEFT_JOIN'] = array(); + $sql_array['LEFT_JOIN'][] = array('FROM' => array(TOPICS_TABLE => 't'), 'ON' => 'b.topic_id = t.topic_id'); + } + + $sql_array['LEFT_JOIN'][] = array('FROM' => array(FORUMS_TABLE => 'f'), 'ON' => 't.forum_id = f.forum_id'); + + if ($config['load_db_lastread']) + { + $sql_array['LEFT_JOIN'][] = array('FROM' => array(FORUMS_TRACK_TABLE => 'ft'), 'ON' => 'ft.forum_id = t.forum_id AND ft.user_id = ' . $user->data['user_id']); + $sql_array['LEFT_JOIN'][] = array('FROM' => array(TOPICS_TRACK_TABLE => 'tt'), 'ON' => 'tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id']); + $sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time AS forum_mark_time'; + } + + if ($config['load_db_track']) + { + $sql_array['LEFT_JOIN'][] = array('FROM' => array(TOPICS_POSTED_TABLE => 'tp'), 'ON' => 'tp.topic_id = t.topic_id AND tp.user_id = ' . $user->data['user_id']); + $sql_array['SELECT'] .= ', tp.topic_posted'; + } + + /** + * Modify the query used to retrieve the list of subscribed/bookmarked topics + * + * @event core.ucp_main_topiclist_modify_query + * @var array sql_array The subscribed/bookmarked topics query + * @var array forbidden_forum_ary The list of forbidden forums + * @var string mode The type of topic list ('subscribed' or 'bookmarks') + * @since 3.1.10-RC1 + */ + $vars = array( + 'sql_array', + 'forbidden_forum_ary', + 'mode', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_main_topiclist_modify_query', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $topic_list = $topic_forum_list = $global_announce_list = $rowset = array(); + while ($row = $db->sql_fetchrow($result)) + { + $topic_id = (isset($row['b_topic_id'])) ? $row['b_topic_id'] : $row['topic_id']; + + $topic_list[] = $topic_id; + $rowset[$topic_id] = $row; + + $topic_forum_list[$row['forum_id']]['forum_mark_time'] = ($config['load_db_lastread']) ? $row['forum_mark_time'] : 0; + $topic_forum_list[$row['forum_id']]['topics'][] = $topic_id; + + if ($row['topic_type'] == POST_GLOBAL) + { + $global_announce_list[] = $topic_id; + } + } + $db->sql_freeresult($result); + + $topic_tracking_info = array(); + if ($config['load_db_lastread']) + { + foreach ($topic_forum_list as $f_id => $topic_row) + { + $topic_tracking_info += get_topic_tracking($f_id, $topic_row['topics'], $rowset, array($f_id => $topic_row['forum_mark_time'])); + } + } + else + { + foreach ($topic_forum_list as $f_id => $topic_row) + { + $topic_tracking_info += get_complete_topic_tracking($f_id, $topic_row['topics']); + } + } + + /* @var $phpbb_content_visibility \phpbb\content_visibility */ + $phpbb_content_visibility = $phpbb_container->get('content.visibility'); + + foreach ($topic_list as $topic_id) + { + $row = &$rowset[$topic_id]; + + $forum_id = $row['forum_id']; + $topic_id = (isset($row['b_topic_id'])) ? $row['b_topic_id'] : $row['topic_id']; + + $unread_topic = (isset($topic_tracking_info[$topic_id]) && $row['topic_last_post_time'] > $topic_tracking_info[$topic_id]) ? true : false; + + // Replies + $replies = $phpbb_content_visibility->get_count('topic_posts', $row, $forum_id) - 1; + + if ($row['topic_status'] == ITEM_MOVED && !empty($row['topic_moved_id'])) + { + $topic_id = $row['topic_moved_id']; + } + + // Get folder img, topic status/type related information + $folder_img = $folder_alt = $topic_type = ''; + topic_status($row, $replies, $unread_topic, $folder_img, $folder_alt, $topic_type); + + $view_topic_url_params = "f=$forum_id&t=$topic_id"; + $view_topic_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", $view_topic_url_params); + + // Send vars to template + $template_vars = array( + 'FORUM_ID' => $forum_id, + 'TOPIC_ID' => $topic_id, + 'FIRST_POST_TIME' => $user->format_date($row['topic_time']), + 'LAST_POST_SUBJECT' => $row['topic_last_post_subject'], + 'LAST_POST_TIME' => $user->format_date($row['topic_last_post_time']), + 'LAST_VIEW_TIME' => $user->format_date($row['topic_last_view_time']), + + 'TOPIC_AUTHOR' => get_username_string('username', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'TOPIC_AUTHOR_COLOUR' => get_username_string('colour', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'TOPIC_AUTHOR_FULL' => get_username_string('full', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'U_TOPIC_AUTHOR' => get_username_string('profile', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + + 'LAST_POST_AUTHOR' => get_username_string('username', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'LAST_POST_AUTHOR_COLOUR' => get_username_string('colour', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'LAST_POST_AUTHOR_FULL' => get_username_string('full', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'U_LAST_POST_AUTHOR' => get_username_string('profile', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + + 'S_DELETED_TOPIC' => (!$row['topic_id']) ? true : false, + + 'REPLIES' => $replies, + 'VIEWS' => $row['topic_views'], + 'TOPIC_TITLE' => censor_text($row['topic_title']), + 'TOPIC_TYPE' => $topic_type, + 'FORUM_NAME' => $row['forum_name'], + + 'TOPIC_IMG_STYLE' => $folder_img, + 'TOPIC_FOLDER_IMG' => $user->img($folder_img, $folder_alt), + 'TOPIC_FOLDER_IMG_ALT' => $user->lang[$folder_alt], + 'TOPIC_ICON_IMG' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['img'] : '', + 'TOPIC_ICON_IMG_WIDTH' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['width'] : '', + 'TOPIC_ICON_IMG_HEIGHT' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['height'] : '', + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $auth->acl_get('f_download', $forum_id) && $row['topic_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + + 'S_TOPIC_TYPE' => $row['topic_type'], + 'S_USER_POSTED' => (!empty($row['topic_posted'])) ? true : false, + 'S_UNREAD_TOPIC' => $unread_topic, + + 'U_NEWEST_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", $view_topic_url_params . '&view=unread') . '#unread', + 'U_LAST_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", $view_topic_url_params . '&p=' . $row['topic_last_post_id']) . '#p' . $row['topic_last_post_id'], + 'U_VIEW_TOPIC' => $view_topic_url, + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), + ); + + /** + * Add template variables to a subscribed/bookmarked topic row. + * + * @event core.ucp_main_topiclist_topic_modify_template_vars + * @var array template_vars Array containing the template variables for the row + * @var array row Array containing the subscribed/bookmarked topic row data + * @var int forum_id ID of the forum containing the topic + * @var int topic_id Topic ID + * @var int replies Number of replies in the topic + * @var string topic_type Topic type + * @var string folder_img Folder image + * @var string folder_alt Alt text for the folder image + * @var array icons Array containing topic icons + * @var bool unread_topic Whether the topic has unread content or not + * @var string view_topic_url The URL of the topic + * @since 3.1.10-RC1 + */ + $vars = array( + 'template_vars', + 'row', + 'forum_id', + 'topic_id', + 'replies', + 'topic_type', + 'folder_img', + 'folder_alt', + 'icons', + 'unread_topic', + 'view_topic_url', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_main_topiclist_topic_modify_template_vars', compact($vars))); + + $template->assign_block_vars('topicrow', $template_vars); + + $pagination->generate_template_pagination(append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'f=' . $row['forum_id'] . "&t=$topic_id"), 'topicrow.pagination', 'start', $replies + 1, $config['posts_per_page'], 1, true, true); + } + } +} diff --git a/includes/ucp/ucp_notifications.php b/includes/ucp/ucp_notifications.php new file mode 100644 index 0000000..a6d925f --- /dev/null +++ b/includes/ucp/ucp_notifications.php @@ -0,0 +1,233 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class ucp_notifications +{ + public $u_action; + + public function main($id, $mode) + { + global $config, $template, $user, $request, $phpbb_container; + global $phpbb_root_path, $phpEx; + + add_form_key('ucp_notification'); + + $start = $request->variable('start', 0); + $form_time = $request->variable('form_time', 0); + $form_time = ($form_time <= 0 || $form_time > time()) ? time() : $form_time; + + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + switch ($mode) + { + case 'notification_options': + $subscriptions = $phpbb_notifications->get_global_subscriptions(false); + + // Add/remove subscriptions + if ($request->is_set_post('submit')) + { + if (!check_form_key('ucp_notification')) + { + trigger_error('FORM_INVALID'); + } + + $notification_methods = $phpbb_notifications->get_subscription_methods(); + + foreach ($phpbb_notifications->get_subscription_types() as $group => $subscription_types) + { + foreach ($subscription_types as $type => $data) + { + foreach ($notification_methods as $method => $method_data) + { + if ($request->is_set_post(str_replace('.', '_', $type . '_' . $method_data['id'])) && (!isset($subscriptions[$type]) || !in_array($method_data['id'], $subscriptions[$type]))) + { + $phpbb_notifications->add_subscription($type, 0, $method_data['id']); + } + else if (!$request->is_set_post(str_replace('.', '_', $type . '_' . $method_data['id'])) && isset($subscriptions[$type]) && in_array($method_data['id'], $subscriptions[$type])) + { + $phpbb_notifications->delete_subscription($type, 0, $method_data['id']); + } + } + } + } + + meta_refresh(3, $this->u_action); + $message = $user->lang['PREFERENCES_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + + $this->output_notification_methods($phpbb_notifications, $template, $user, 'notification_methods'); + + $this->output_notification_types($subscriptions, $phpbb_notifications, $template, $user, 'notification_types'); + + $this->tpl_name = 'ucp_notifications'; + $this->page_title = 'UCP_NOTIFICATION_OPTIONS'; + break; + + case 'notification_list': + default: + // Mark all items read + if ($request->variable('mark', '') == 'all' && check_link_hash($request->variable('token', ''), 'mark_all_notifications_read')) + { + $phpbb_notifications->mark_notifications(false, false, $user->data['user_id'], $form_time); + + meta_refresh(3, $this->u_action); + $message = $user->lang['NOTIFICATIONS_MARK_ALL_READ_SUCCESS']; + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $message, + 'success' => true, + )); + } + $message .= '

' . $user->lang('RETURN_UCP', '', ''); + + trigger_error($message); + } + + // Mark specific notifications read + if ($request->is_set_post('submit')) + { + if (!check_form_key('ucp_notification')) + { + trigger_error('FORM_INVALID'); + } + + $mark_read = $request->variable('mark', array(0)); + + if (!empty($mark_read)) + { + $phpbb_notifications->mark_notifications_by_id('notification.method.board', $mark_read, $form_time); + } + } + + $notifications = $phpbb_notifications->load_notifications('notification.method.board', array( + 'start' => $start, + 'limit' => $config['topics_per_page'], + 'count_total' => true, + )); + + foreach ($notifications['notifications'] as $notification) + { + $template->assign_block_vars('notification_list', $notification->prepare_for_display()); + } + + $base_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=ucp_notifications&mode=notification_list"); + $start = $pagination->validate_start($start, $config['topics_per_page'], $notifications['total_count']); + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $notifications['total_count'], $config['topics_per_page'], $start); + + $template->assign_vars(array( + 'TOTAL_COUNT' => $notifications['total_count'], + 'U_MARK_ALL' => $base_url . '&mark=all&token=' . generate_link_hash('mark_all_notifications_read'), + )); + + $this->tpl_name = 'ucp_notifications'; + $this->page_title = 'UCP_NOTIFICATION_LIST'; + break; + } + + $template->assign_vars(array( + 'TITLE' => $user->lang($this->page_title), + 'TITLE_EXPLAIN' => $user->lang($this->page_title . '_EXPLAIN'), + + 'MODE' => $mode, + + 'FORM_TIME' => time(), + )); + } + + /** + * Output all the notification types to the template + * + * @param array $subscriptions Array containing global subscriptions + * @param \phpbb\notification\manager $phpbb_notifications + * @param \phpbb\template\template $template + * @param \phpbb\user $user + * @param string $block + */ + public function output_notification_types($subscriptions, \phpbb\notification\manager $phpbb_notifications, \phpbb\template\template $template, \phpbb\user $user, $block = 'notification_types') + { + $notification_methods = $phpbb_notifications->get_subscription_methods(); + + foreach ($phpbb_notifications->get_subscription_types() as $group => $subscription_types) + { + $template->assign_block_vars($block, array( + 'GROUP_NAME' => $user->lang($group), + )); + + foreach ($subscription_types as $type => $type_data) + { + $template->assign_block_vars($block, array( + 'TYPE' => $type, + + 'NAME' => $user->lang($type_data['lang']), + 'EXPLAIN' => (isset($user->lang[$type_data['lang'] . '_EXPLAIN'])) ? $user->lang($type_data['lang'] . '_EXPLAIN') : '', + )); + + foreach ($notification_methods as $method => $method_data) + { + $template->assign_block_vars($block . '.notification_methods', array( + 'METHOD' => $method_data['id'], + + 'NAME' => $user->lang($method_data['lang']), + + 'AVAILABLE' => $method_data['method']->is_available($type_data['type']), + + 'SUBSCRIBED' => (isset($subscriptions[$type]) && in_array($method_data['id'], $subscriptions[$type])) ? true : false, + )); + } + } + } + + $template->assign_vars(array( + strtoupper($block) . '_COLS' => count($notification_methods) + 1, + )); + } + + /** + * Output all the notification methods to the template + * + * @param \phpbb\notification\manager $phpbb_notifications + * @param \phpbb\template\template $template + * @param \phpbb\user $user + * @param string $block + */ + public function output_notification_methods(\phpbb\notification\manager $phpbb_notifications, \phpbb\template\template $template, \phpbb\user $user, $block = 'notification_methods') + { + $notification_methods = $phpbb_notifications->get_subscription_methods(); + + foreach ($notification_methods as $method => $method_data) + { + $template->assign_block_vars($block, array( + 'METHOD' => $method_data['id'], + + 'NAME' => $user->lang($method_data['lang']), + )); + } + } +} diff --git a/includes/ucp/ucp_pm.php b/includes/ucp/ucp_pm.php new file mode 100644 index 0000000..4d02620 --- /dev/null +++ b/includes/ucp/ucp_pm.php @@ -0,0 +1,435 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Private Message Class +* +* $_REQUEST['folder'] display folder with the id used +* $_REQUEST['folder'] inbox|outbox|sentbox display folder with the associated name +* +* Display Messages (default to inbox) - mode=view +* Display single message - mode=view&p=[msg_id] or &p=[msg_id] (short linkage) +* +* if the folder id with (&f=[folder_id]) is used when displaying messages, one query will be saved. If it is not used, phpBB needs to grab +* the folder id first in order to display the input boxes and folder names and such things. ;) phpBB always checks this against the database to make +* sure the user is able to view the message. +* +* Composing Messages (mode=compose): +* To specific user (u=[user_id]) +* To specific group (g=[group_id]) +* Quoting a post (action=quotepost&p=[post_id]) +* Quoting a PM (action=quote&p=[msg_id]) +* Forwarding a PM (action=forward&p=[msg_id]) +*/ +class ucp_pm +{ + var $u_action; + + function main($id, $mode) + { + global $user, $template, $phpbb_root_path, $auth, $phpEx, $db, $config, $request; + + if (!$user->data['is_registered']) + { + trigger_error('NO_MESSAGE'); + } + + // Is PM disabled? + if (!$config['allow_privmsg']) + { + trigger_error('PM_DISABLED'); + } + + $user->add_lang('posting'); + $template->assign_var('S_PRIVMSGS', true); + + // Folder directly specified? + $folder_specified = $request->variable('folder', ''); + + if (!in_array($folder_specified, array('inbox', 'outbox', 'sentbox'))) + { + $folder_specified = (int) $folder_specified; + } + else + { + $folder_specified = ($folder_specified == 'inbox') ? PRIVMSGS_INBOX : (($folder_specified == 'outbox') ? PRIVMSGS_OUTBOX : PRIVMSGS_SENTBOX); + } + + if (!$folder_specified) + { + $mode = (!$mode) ? $request->variable('mode', 'view') : $mode; + } + else + { + $mode = 'view'; + } + + if (!function_exists('get_folder')) + { + include($phpbb_root_path . 'includes/functions_privmsgs.' . $phpEx); + } + + switch ($mode) + { + // Compose message + case 'compose': + $action = $request->variable('action', 'post'); + + $user_folders = get_folder($user->data['user_id']); + + if ($action != 'delete' && !$auth->acl_get('u_sendpm')) + { + // trigger_error('NO_AUTH_SEND_MESSAGE'); + $template->assign_vars(array( + 'S_NO_AUTH_SEND_MESSAGE' => true, + 'S_COMPOSE_PM_VIEW' => true, + )); + + $tpl_file = 'ucp_pm_viewfolder'; + break; + } + + if (!function_exists('compose_pm')) + { + include($phpbb_root_path . 'includes/ucp/ucp_pm_compose.' . $phpEx); + } + compose_pm($id, $mode, $action, $user_folders); + + $tpl_file = 'posting_body'; + break; + + case 'options': + set_user_message_limit(); + get_folder($user->data['user_id']); + + if (!function_exists('message_options')) + { + include($phpbb_root_path . 'includes/ucp/ucp_pm_options.' . $phpEx); + } + message_options($id, $mode, $global_privmsgs_rules, $global_rule_conditions); + + $tpl_file = 'ucp_pm_options'; + break; + + case 'drafts': + + get_folder($user->data['user_id']); + $this->p_name = 'pm'; + + if (!class_exists('ucp_main')) + { + include($phpbb_root_path . 'includes/ucp/ucp_main.' . $phpEx); + } + + $module = new ucp_main($this); + $module->u_action = $this->u_action; + $module->main($id, $mode); + + $this->tpl_name = $module->tpl_name; + $this->page_title = 'UCP_PM_DRAFTS'; + + unset($module); + return; + + break; + + case 'view': + + set_user_message_limit(); + + if ($folder_specified) + { + $folder_id = $folder_specified; + $action = 'view_folder'; + } + else + { + $folder_id = $request->variable('f', PRIVMSGS_NO_BOX); + $action = $request->variable('action', 'view_folder'); + } + + $msg_id = $request->variable('p', 0); + $view = $request->variable('view', ''); + + // View message if specified + if ($msg_id) + { + $action = 'view_message'; + } + + if (!$auth->acl_get('u_readpm')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_READ_MESSAGE'); + } + + if ($view == 'print' && (!$config['print_pm'] || !$auth->acl_get('u_pm_printpm'))) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_PRINT_MESSAGE'); + } + + // Do not allow hold messages to be seen + if ($folder_id == PRIVMSGS_HOLD_BOX) + { + trigger_error('NO_AUTH_READ_HOLD_MESSAGE'); + } + + // First Handle Mark actions and moving messages + $submit_mark = (isset($_POST['submit_mark'])) ? true : false; + $move_pm = (isset($_POST['move_pm'])) ? true : false; + $mark_option = $request->variable('mark_option', ''); + $dest_folder = $request->variable('dest_folder', PRIVMSGS_NO_BOX); + + // Is moving PM triggered through mark options? + if (!in_array($mark_option, array('mark_important', 'delete_marked')) && $submit_mark) + { + $move_pm = true; + $dest_folder = (int) $mark_option; + $submit_mark = false; + } + + // Move PM + if ($move_pm) + { + $move_msg_ids = (isset($_POST['marked_msg_id'])) ? $request->variable('marked_msg_id', array(0)) : array(); + $cur_folder_id = $request->variable('cur_folder_id', PRIVMSGS_NO_BOX); + + if (move_pm($user->data['user_id'], $user->data['message_limit'], $move_msg_ids, $dest_folder, $cur_folder_id)) + { + // Return to folder view if single message moved + if ($action == 'view_message') + { + $msg_id = 0; + $folder_id = $request->variable('cur_folder_id', PRIVMSGS_NO_BOX); + $action = 'view_folder'; + } + } + } + + // Message Mark Options + if ($submit_mark) + { + handle_mark_actions($user->data['user_id'], $mark_option); + } + + // If new messages arrived, place them into the appropriate folder + $num_not_moved = $num_removed = 0; + $release = $request->variable('release', 0); + + if ($user->data['user_new_privmsg'] && ($action == 'view_folder' || $action == 'view_message')) + { + $return = place_pm_into_folder($global_privmsgs_rules, $release); + $num_not_moved = $return['not_moved']; + $num_removed = $return['removed']; + } + + if (!$msg_id && $folder_id == PRIVMSGS_NO_BOX) + { + $folder_id = PRIVMSGS_INBOX; + } + else if ($msg_id && $folder_id == PRIVMSGS_NO_BOX) + { + $sql = 'SELECT folder_id + FROM ' . PRIVMSGS_TO_TABLE . " + WHERE msg_id = $msg_id + AND folder_id <> " . PRIVMSGS_NO_BOX . ' + AND user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_MESSAGE'); + } + $folder_id = (int) $row['folder_id']; + } + + if ($request->variable('mark', '') == 'all' && check_link_hash($request->variable('token', ''), 'mark_all_pms_read')) + { + mark_folder_read($user->data['user_id'], $folder_id); + + meta_refresh(3, $this->u_action); + $message = $user->lang['PM_MARK_ALL_READ_SUCCESS']; + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $message, + 'success' => true, + )); + } + $message .= '

' . $user->lang('RETURN_UCP', '', ''); + + trigger_error($message); + } + + $message_row = array(); + if ($action == 'view_message' && $msg_id) + { + // Get Message user want to see + if ($view == 'next' || $view == 'previous') + { + $sql_condition = ($view == 'next') ? '>' : '<'; + $sql_ordering = ($view == 'next') ? 'ASC' : 'DESC'; + + $sql = 'SELECT t.msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p, ' . PRIVMSGS_TABLE . " p2 + WHERE p2.msg_id = $msg_id + AND t.folder_id = $folder_id + AND t.user_id = " . $user->data['user_id'] . " + AND t.msg_id = p.msg_id + AND p.message_time $sql_condition p2.message_time + ORDER BY p.message_time $sql_ordering"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $message = ($view == 'next') ? 'NO_NEWER_PM' : 'NO_OLDER_PM'; + trigger_error($message); + } + else + { + $msg_id = $row['msg_id']; + } + } + + $sql = 'SELECT t.*, p.*, u.* + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE t.user_id = ' . $user->data['user_id'] . " + AND p.author_id = u.user_id + AND t.folder_id = $folder_id + AND t.msg_id = p.msg_id + AND p.msg_id = $msg_id"; + $result = $db->sql_query($sql); + $message_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$message_row) + { + trigger_error('NO_MESSAGE'); + } + + // Update unread status + update_unread_status($message_row['pm_unread'], $message_row['msg_id'], $user->data['user_id'], $folder_id); + } + + $folder = get_folder($user->data['user_id'], $folder_id); + + $s_folder_options = $s_to_folder_options = ''; + foreach ($folder as $f_id => $folder_ary) + { + $option = '' . $folder_ary['folder_name'] . (($folder_ary['unread_messages']) ? ' [' . $folder_ary['unread_messages'] . '] ' : '') . ''; + + $s_to_folder_options .= ($f_id != PRIVMSGS_OUTBOX && $f_id != PRIVMSGS_SENTBOX) ? $option : ''; + $s_folder_options .= $option; + } + clean_sentbox($folder[PRIVMSGS_SENTBOX]['num_messages']); + + // Header for message view - folder and so on + $folder_status = get_folder_status($folder_id, $folder); + + $template->assign_vars(array( + 'CUR_FOLDER_ID' => $folder_id, + 'CUR_FOLDER_NAME' => $folder_status['folder_name'], + 'NUM_NOT_MOVED' => $num_not_moved, + 'NUM_REMOVED' => $num_removed, + 'RELEASE_MESSAGE_INFO' => sprintf($user->lang['RELEASE_MESSAGES'], '', ''), + 'NOT_MOVED_MESSAGES' => $user->lang('NOT_MOVED_MESSAGES', (int) $num_not_moved), + 'RULE_REMOVED_MESSAGES' => $user->lang('RULE_REMOVED_MESSAGES', (int) $num_removed), + + 'S_FOLDER_OPTIONS' => $s_folder_options, + 'S_TO_FOLDER_OPTIONS' => $s_to_folder_options, + 'S_FOLDER_ACTION' => $this->u_action . '&action=view_folder', + 'S_PM_ACTION' => $this->u_action . '&action=' . $action, + + 'U_INBOX' => $this->u_action . '&folder=inbox', + 'U_OUTBOX' => $this->u_action . '&folder=outbox', + 'U_SENTBOX' => $this->u_action . '&folder=sentbox', + 'U_CREATE_FOLDER' => $this->u_action . '&mode=options', + 'U_CURRENT_FOLDER' => $this->u_action . '&folder=' . $folder_id, + 'U_MARK_ALL' => $this->u_action . '&folder=' . $folder_id . '&mark=all&token=' . generate_link_hash('mark_all_pms_read'), + + 'S_IN_INBOX' => ($folder_id == PRIVMSGS_INBOX) ? true : false, + 'S_IN_OUTBOX' => ($folder_id == PRIVMSGS_OUTBOX) ? true : false, + 'S_IN_SENTBOX' => ($folder_id == PRIVMSGS_SENTBOX) ? true : false, + + 'FOLDER_STATUS' => $folder_status['message'], + 'FOLDER_MAX_MESSAGES' => $folder_status['max'], + 'FOLDER_CUR_MESSAGES' => $folder_status['cur'], + 'FOLDER_REMAINING_MESSAGES' => $folder_status['remaining'], + 'FOLDER_PERCENT' => $folder_status['percent']) + ); + + if ($action == 'view_folder') + { + if (!function_exists('view_folder')) + { + include($phpbb_root_path . 'includes/ucp/ucp_pm_viewfolder.' . $phpEx); + } + view_folder($id, $mode, $folder_id, $folder); + + $tpl_file = 'ucp_pm_viewfolder'; + } + else if ($action == 'view_message') + { + $template->assign_vars(array( + 'S_VIEW_MESSAGE' => true, + 'L_RETURN_TO_FOLDER' => $user->lang('RETURN_TO', $folder_status['folder_name']), + 'MSG_ID' => $msg_id, + )); + + if (!$msg_id) + { + trigger_error('NO_MESSAGE'); + } + + if (!function_exists('view_message')) + { + include($phpbb_root_path . 'includes/ucp/ucp_pm_viewmessage.' . $phpEx); + } + view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row); + + $tpl_file = ($view == 'print') ? 'ucp_pm_viewmessage_print' : 'ucp_pm_viewmessage'; + } + + break; + + default: + trigger_error('NO_ACTION_MODE', E_USER_ERROR); + break; + } + + $template->assign_vars(array( + 'L_TITLE' => $user->lang['UCP_PM_' . strtoupper($mode)], + 'S_UCP_ACTION' => $this->u_action . ((isset($action)) ? "&action=$action" : '')) + ); + + // Set desired template + $this->tpl_name = $tpl_file; + $this->page_title = 'UCP_PM_' . strtoupper($mode); + } +} diff --git a/includes/ucp/ucp_pm_compose.php b/includes/ucp/ucp_pm_compose.php new file mode 100644 index 0000000..543db4f --- /dev/null +++ b/includes/ucp/ucp_pm_compose.php @@ -0,0 +1,1546 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Compose private message +* Called from ucp_pm with mode == 'compose' +*/ +function compose_pm($id, $mode, $action, $user_folders = array()) +{ + global $template, $db, $auth, $user, $cache; + global $phpbb_root_path, $phpEx, $config; + global $request, $phpbb_dispatcher, $phpbb_container; + + // Damn php and globals - i know, this is horrible + // Needed for handle_message_list_actions() + global $refresh, $submit, $preview; + + if (!function_exists('generate_smilies')) + { + include($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + } + + if (!function_exists('display_custom_bbcodes')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + if (!class_exists('parse_message')) + { + include($phpbb_root_path . 'includes/message_parser.' . $phpEx); + } + + if (!$action) + { + $action = 'post'; + } + add_form_key('ucp_pm_compose'); + + // Grab only parameters needed here + $to_user_id = $request->variable('u', 0); + $to_group_id = $request->variable('g', 0); + $msg_id = $request->variable('p', 0); + $draft_id = $request->variable('d', 0); + + // Reply to all triggered (quote/reply) + $reply_to_all = $request->variable('reply_to_all', 0); + + $address_list = $request->variable('address_list', array('' => array(0 => ''))); + + $preview = (isset($_POST['preview'])) ? true : false; + $save = (isset($_POST['save'])) ? true : false; + $load = (isset($_POST['load'])) ? true : false; + $cancel = (isset($_POST['cancel']) && !isset($_POST['save'])) ? true : false; + $delete = (isset($_POST['delete'])) ? true : false; + + $remove_u = (isset($_REQUEST['remove_u'])) ? true : false; + $remove_g = (isset($_REQUEST['remove_g'])) ? true : false; + $add_to = (isset($_REQUEST['add_to'])) ? true : false; + $add_bcc = (isset($_REQUEST['add_bcc'])) ? true : false; + + $refresh = isset($_POST['add_file']) || isset($_POST['delete_file']) || $save || $load + || $remove_u || $remove_g || $add_to || $add_bcc; + $submit = $request->is_set_post('post') && !$refresh && !$preview; + + $action = ($delete && !$preview && !$refresh && $submit) ? 'delete' : $action; + $select_single = ($config['allow_mass_pm'] && $auth->acl_get('u_masspm')) ? false : true; + + $error = array(); + $current_time = time(); + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + // Was cancel pressed? If so then redirect to the appropriate page + if ($cancel) + { + if ($msg_id) + { + redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=view&action=view_message&p=' . $msg_id)); + } + redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm')); + } + + // Since viewtopic.php language entries are used in several modes, + // we include the language file here + $user->add_lang('viewtopic'); + + /** + * Modify the default vars before composing a PM + * + * @event core.ucp_pm_compose_modify_data + * @var int msg_id post_id in the page request + * @var int to_user_id The id of whom the message is to + * @var int to_group_id The id of the group the message is to + * @var bool submit Whether the form has been submitted + * @var bool preview Whether the user is previewing the PM or not + * @var string action One of: post, reply, quote, forward, quotepost, edit, delete, smilies + * @var bool delete Whether the user is deleting the PM + * @var int reply_to_all Value of reply_to_all request variable. + * @since 3.1.4-RC1 + */ + $vars = array( + 'msg_id', + 'to_user_id', + 'to_group_id', + 'submit', + 'preview', + 'action', + 'delete', + 'reply_to_all', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_compose_modify_data', compact($vars))); + + // Output PM_TO box if message composing + if ($action != 'edit') + { + // Add groups to PM box + if ($config['allow_mass_pm'] && $auth->acl_get('u_masspm_group')) + { + $sql = 'SELECT g.group_id, g.group_name, g.group_type, g.group_colour + FROM ' . GROUPS_TABLE . ' g'; + + if (!$auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) + { + $sql .= ' LEFT JOIN ' . USER_GROUP_TABLE . ' ug + ON ( + g.group_id = ug.group_id + AND ug.user_id = ' . $user->data['user_id'] . ' + AND ug.user_pending = 0 + ) + WHERE (g.group_type <> ' . GROUP_HIDDEN . ' OR ug.user_id = ' . $user->data['user_id'] . ')'; + } + + $sql .= ($auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) ? ' WHERE ' : ' AND '; + + $sql .= 'g.group_receive_pm = 1 + ORDER BY g.group_type DESC, g.group_name ASC'; + $result = $db->sql_query($sql); + + $group_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + $group_options .= '' . $group_helper->get_name($row['group_name']) . ''; + } + $db->sql_freeresult($result); + } + + $template->assign_vars(array( + 'S_SHOW_PM_BOX' => true, + 'S_ALLOW_MASS_PM' => ($config['allow_mass_pm'] && $auth->acl_get('u_masspm')) ? true : false, + 'S_GROUP_OPTIONS' => ($config['allow_mass_pm'] && $auth->acl_get('u_masspm_group')) ? $group_options : '', + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=searchuser&form=postform&field=username_list&select_single=" . (int) $select_single), + )); + } + + $sql = ''; + $folder_id = 0; + + // What is all this following SQL for? Well, we need to know + // some basic information in all cases before we do anything. + switch ($action) + { + case 'post': + if (!$auth->acl_get('u_sendpm')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_SEND_MESSAGE'); + } + break; + + case 'reply': + case 'quote': + case 'forward': + case 'quotepost': + if (!$msg_id) + { + trigger_error('NO_MESSAGE'); + } + + if (!$auth->acl_get('u_sendpm')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_SEND_MESSAGE'); + } + + if ($action == 'quotepost') + { + $sql = 'SELECT p.post_id as msg_id, p.forum_id, p.post_text as message_text, p.poster_id as author_id, p.post_time as message_time, p.bbcode_bitfield, p.bbcode_uid, p.enable_sig, p.enable_smilies, p.enable_magic_url, t.topic_title as message_subject, u.username as quote_username + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t, ' . USERS_TABLE . " u + WHERE p.post_id = $msg_id + AND t.topic_id = p.topic_id + AND u.user_id = p.poster_id"; + } + else + { + $sql = 'SELECT t.folder_id, p.*, u.username as quote_username + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE t.user_id = ' . $user->data['user_id'] . " + AND p.author_id = u.user_id + AND t.msg_id = p.msg_id + AND p.msg_id = $msg_id"; + } + break; + + case 'edit': + if (!$msg_id) + { + trigger_error('NO_MESSAGE'); + } + + // check for outbox (not read) status, we do not allow editing if one user already having the message + $sql = 'SELECT p.*, t.folder_id + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p + WHERE t.user_id = ' . $user->data['user_id'] . ' + AND t.folder_id = ' . PRIVMSGS_OUTBOX . " + AND t.msg_id = $msg_id + AND t.msg_id = p.msg_id"; + break; + + case 'delete': + if (!$auth->acl_get('u_pm_delete')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_DELETE_MESSAGE'); + } + + if (!$msg_id) + { + trigger_error('NO_MESSAGE'); + } + + $sql = 'SELECT msg_id, pm_unread, pm_new, author_id, folder_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . " + AND msg_id = $msg_id"; + break; + + case 'smilies': + generate_smilies('window', 0); + break; + + default: + trigger_error('NO_ACTION_MODE', E_USER_ERROR); + break; + } + + if ($action == 'forward' && (!$config['forward_pm'] || !$auth->acl_get('u_pm_forward'))) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_FORWARD_MESSAGE'); + } + + if ($action == 'edit' && !$auth->acl_get('u_pm_edit')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_EDIT_MESSAGE'); + } + + if ($sql) + { + /** + * Alter sql query to get message for user to write the PM + * + * @event core.ucp_pm_compose_compose_pm_basic_info_query_before + * @var string sql String with the query to be executed + * @var int msg_id topic_id in the page request + * @var int to_user_id The id of whom the message is to + * @var int to_group_id The id of the group whom the message is to + * @var bool submit Whether the user is sending the PM or not + * @var bool preview Whether the user is previewing the PM or not + * @var string action One of: post, reply, quote, forward, quotepost, edit, delete, smilies + * @var bool delete Whether the user is deleting the PM + * @var int reply_to_all Value of reply_to_all request variable. + * @since 3.1.0-RC5 + * @changed 3.2.0-a1 Removed undefined variables + */ + $vars = array( + 'sql', + 'msg_id', + 'to_user_id', + 'to_group_id', + 'submit', + 'preview', + 'action', + 'delete', + 'reply_to_all', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_compose_compose_pm_basic_info_query_before', compact($vars))); + + $result = $db->sql_query($sql); + $post = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$post) + { + // If editing it could be the recipient already read the message... + if ($action == 'edit') + { + $sql = 'SELECT p.*, t.folder_id + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p + WHERE t.user_id = ' . $user->data['user_id'] . " + AND t.msg_id = $msg_id + AND t.msg_id = p.msg_id"; + $result = $db->sql_query($sql); + $post = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($post) + { + trigger_error('NO_EDIT_READ_MESSAGE'); + } + } + + trigger_error('NO_MESSAGE'); + } + + if ($action == 'quotepost') + { + if (($post['forum_id'] && !$auth->acl_get('f_read', $post['forum_id'])) || (!$post['forum_id'] && !$auth->acl_getf_global('f_read'))) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + + /** + * Get the result of querying for the post to be quoted in the pm message + * + * @event core.ucp_pm_compose_quotepost_query_after + * @var string sql The original SQL used in the query + * @var array post Associative array with the data of the quoted post + * @var array msg_id The post_id that was searched to get the message for quoting + * @var int to_user_id Users the message is sent to + * @var int to_group_id Groups the message is sent to + * @var bool submit Whether the user is sending the PM or not + * @var bool preview Whether the user is previewing the PM or not + * @var string action One of: post, reply, quote, forward, quotepost, edit, delete, smilies + * @var bool delete If deleting message + * @var int reply_to_all Value of reply_to_all request variable. + * @since 3.1.0-RC5 + * @changed 3.2.0-a1 Removed undefined variables + */ + $vars = array( + 'sql', + 'post', + 'msg_id', + 'to_user_id', + 'to_group_id', + 'submit', + 'preview', + 'action', + 'delete', + 'reply_to_all', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_compose_quotepost_query_after', compact($vars))); + + // Passworded forum? + if ($post['forum_id']) + { + $sql = 'SELECT forum_id, forum_name, forum_password + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . (int) $post['forum_id']; + $result = $db->sql_query($sql); + $forum_data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!empty($forum_data['forum_password'])) + { + login_forum_box($forum_data); + } + } + } + + $msg_id = (int) $post['msg_id']; + $folder_id = (isset($post['folder_id'])) ? $post['folder_id'] : 0; + $message_text = (isset($post['message_text'])) ? $post['message_text'] : ''; + + if ((!$post['author_id'] || ($post['author_id'] == ANONYMOUS && $action != 'delete')) && $msg_id) + { + trigger_error('NO_AUTHOR'); + } + + if ($action == 'quotepost') + { + // Decode text for message display + decode_message($message_text, $post['bbcode_uid']); + } + + if ($action != 'delete') + { + $enable_urls = $post['enable_magic_url']; + $enable_sig = (isset($post['enable_sig'])) ? $post['enable_sig'] : 0; + + $message_attachment = (isset($post['message_attachment'])) ? $post['message_attachment'] : 0; + $message_subject = $post['message_subject']; + $message_time = $post['message_time']; + $bbcode_uid = $post['bbcode_uid']; + + $quote_username = (isset($post['quote_username'])) ? $post['quote_username'] : ''; + $icon_id = (isset($post['icon_id'])) ? $post['icon_id'] : 0; + + if (($action == 'reply' || $action == 'quote' || $action == 'quotepost') && !count($address_list) && !$refresh && !$submit && !$preview) + { + // Add the original author as the recipient if quoting a post or only replying and not having checked "reply to all" + if ($action == 'quotepost' || !$reply_to_all) + { + $address_list = array('u' => array($post['author_id'] => 'to')); + } + else + { + // We try to include every previously listed member from the TO Header - Reply to all + $address_list = rebuild_header(array('to' => $post['to_address'])); + + // Add the author (if he is already listed then this is no shame (it will be overwritten)) + $address_list['u'][$post['author_id']] = 'to'; + + // Now, make sure the user itself is not listed. ;) + if (isset($address_list['u'][$user->data['user_id']])) + { + unset($address_list['u'][$user->data['user_id']]); + } + } + } + else if ($action == 'edit' && !count($address_list) && !$refresh && !$submit && !$preview) + { + // Rebuild TO and BCC Header + $address_list = rebuild_header(array('to' => $post['to_address'], 'bcc' => $post['bcc_address'])); + } + + if ($action == 'quotepost') + { + $check_value = 0; + } + else + { + $check_value = (($post['enable_bbcode']+1) << 8) + (($post['enable_smilies']+1) << 4) + (($enable_urls+1) << 2) + (($post['enable_sig']+1) << 1); + } + } + } + else + { + $message_attachment = 0; + $message_text = $message_subject = ''; + + /** + * Predefine message text and subject + * + * @event core.ucp_pm_compose_predefined_message + * @var string message_text Message text + * @var string message_subject Messate subject + * @since 3.1.11-RC1 + */ + $vars = array('message_text', 'message_subject'); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_compose_predefined_message', compact($vars))); + + if ($to_user_id && $to_user_id != ANONYMOUS && $action == 'post') + { + $address_list['u'][$to_user_id] = 'to'; + } + else if ($to_group_id && $action == 'post') + { + $address_list['g'][$to_group_id] = 'to'; + } + $check_value = 0; + } + + if (($to_group_id || isset($address_list['g'])) && (!$config['allow_mass_pm'] || !$auth->acl_get('u_masspm_group'))) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_GROUP_MESSAGE'); + } + + if ($action == 'edit' && !$refresh && !$preview && !$submit) + { + if (!($message_time > time() - ($config['pm_edit_time'] * 60) || !$config['pm_edit_time'])) + { + trigger_error('CANNOT_EDIT_MESSAGE_TIME'); + } + } + + if ($action == 'post') + { + $template->assign_var('S_NEW_MESSAGE', true); + } + + if (!isset($icon_id)) + { + $icon_id = 0; + } + + /* @var $plupload \phpbb\plupload\plupload */ + $plupload = $phpbb_container->get('plupload'); + $message_parser = new parse_message(); + $message_parser->set_plupload($plupload); + + $message_parser->message = ($action == 'reply') ? '' : $message_text; + unset($message_text); + + $s_action = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=$id&mode=$mode&action=$action", true, $user->session_id); + $s_action .= (($folder_id) ? "&f=$folder_id" : '') . (($msg_id) ? "&p=$msg_id" : ''); + + // Delete triggered ? + if ($action == 'delete') + { + // Folder id has been determined by the SQL Statement + // $folder_id = $request->variable('f', PRIVMSGS_NO_BOX); + + // Do we need to confirm ? + if (confirm_box(true)) + { + delete_pm($user->data['user_id'], $msg_id, $folder_id); + + // jump to next message in "history"? nope, not for the moment. But able to be included later. + $meta_info = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&folder=$folder_id"); + $message = $user->lang['MESSAGE_DELETED']; + + meta_refresh(3, $meta_info); + $message .= '

' . sprintf($user->lang['RETURN_FOLDER'], '', ''); + trigger_error($message); + } + else + { + $s_hidden_fields = array( + 'p' => $msg_id, + 'f' => $folder_id, + 'action' => 'delete' + ); + + // "{$phpbb_root_path}ucp.$phpEx?i=pm&mode=compose" + confirm_box(false, 'DELETE_MESSAGE', build_hidden_fields($s_hidden_fields)); + } + + redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=view&action=view_message&p=' . $msg_id)); + } + + // Get maximum number of allowed recipients + $max_recipients = phpbb_get_max_setting_from_group($db, $user->data['user_id'], 'max_recipients'); + + // If it is 0, there is no limit set and we use the maximum value within the config. + $max_recipients = (!$max_recipients) ? $config['pm_max_recipients'] : $max_recipients; + + // If this is a quote/reply "to all"... we may increase the max_recpients to the number of original recipients + if (($action == 'reply' || $action == 'quote') && $max_recipients && $reply_to_all) + { + // We try to include every previously listed member from the TO Header + $list = rebuild_header(array('to' => $post['to_address'])); + + // Can be an empty array too ;) + $list = (!empty($list['u'])) ? $list['u'] : array(); + $list[$post['author_id']] = 'to'; + + if (isset($list[$user->data['user_id']])) + { + unset($list[$user->data['user_id']]); + } + + $max_recipients = ($max_recipients < count($list)) ? count($list) : $max_recipients; + + unset($list); + } + + // Handle User/Group adding/removing + handle_message_list_actions($address_list, $error, $remove_u, $remove_g, $add_to, $add_bcc); + + // Check mass pm to group permission + if ((!$config['allow_mass_pm'] || !$auth->acl_get('u_masspm_group')) && !empty($address_list['g'])) + { + $address_list = array(); + $error[] = $user->lang['NO_AUTH_GROUP_MESSAGE']; + } + + // Check mass pm to users permission + if ((!$config['allow_mass_pm'] || !$auth->acl_get('u_masspm')) && num_recipients($address_list) > 1) + { + $address_list = get_recipients($address_list, 1); + $error[] = $user->lang('TOO_MANY_RECIPIENTS', 1); + } + + // Check for too many recipients + if (!empty($address_list['u']) && $max_recipients && count($address_list['u']) > $max_recipients) + { + $address_list = get_recipients($address_list, $max_recipients); + $error[] = $user->lang('TOO_MANY_RECIPIENTS', $max_recipients); + } + + // Always check if the submitted attachment data is valid and belongs to the user. + // Further down (especially in submit_post()) we do not check this again. + $message_parser->get_submitted_attachment_data(); + + if ($message_attachment && !$submit && !$refresh && !$preview && $action == 'edit') + { + // Do not change to SELECT * + $sql = 'SELECT attach_id, is_orphan, attach_comment, real_filename, filesize + FROM ' . ATTACHMENTS_TABLE . " + WHERE post_msg_id = $msg_id + AND in_message = 1 + AND is_orphan = 0 + ORDER BY filetime DESC"; + $result = $db->sql_query($sql); + $message_parser->attachment_data = array_merge($message_parser->attachment_data, $db->sql_fetchrowset($result)); + $db->sql_freeresult($result); + } + + if (!in_array($action, array('quote', 'edit', 'delete', 'forward'))) + { + $enable_sig = ($config['allow_sig'] && $config['allow_sig_pm'] && $auth->acl_get('u_sig') && $user->optionget('attachsig')); + $enable_smilies = ($config['allow_smilies'] && $auth->acl_get('u_pm_smilies') && $user->optionget('smilies')); + $enable_bbcode = ($config['allow_bbcode'] && $auth->acl_get('u_pm_bbcode') && $user->optionget('bbcode')); + $enable_urls = true; + } + + $drafts = false; + + // User own some drafts? + if ($auth->acl_get('u_savedrafts') && $action != 'delete') + { + $sql = 'SELECT draft_id + FROM ' . DRAFTS_TABLE . ' + WHERE forum_id = 0 + AND topic_id = 0 + AND user_id = ' . $user->data['user_id'] . + (($draft_id) ? " AND draft_id <> $draft_id" : ''); + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $drafts = true; + } + } + + if ($action == 'edit') + { + $message_parser->bbcode_uid = $bbcode_uid; + } + + $bbcode_status = ($config['allow_bbcode'] && $config['auth_bbcode_pm'] && $auth->acl_get('u_pm_bbcode')) ? true : false; + $smilies_status = ($config['allow_smilies'] && $config['auth_smilies_pm'] && $auth->acl_get('u_pm_smilies')) ? true : false; + $img_status = ($config['auth_img_pm'] && $auth->acl_get('u_pm_img')) ? true : false; + $flash_status = ($config['auth_flash_pm'] && $auth->acl_get('u_pm_flash')) ? true : false; + $url_status = ($config['allow_post_links']) ? true : false; + + // Save Draft + if ($save && $auth->acl_get('u_savedrafts')) + { + $subject = $request->variable('subject', '', true); + $subject = (!$subject && $action != 'post') ? $user->lang['NEW_MESSAGE'] : $subject; + $message = $request->variable('message', '', true); + + if ($subject && $message) + { + if (confirm_box(true)) + { + $message_parser->message = $message; + $message_parser->parse($bbcode_status, $url_status, $smilies_status, $img_status, $flash_status, true, $url_status); + + $sql = 'INSERT INTO ' . DRAFTS_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'user_id' => $user->data['user_id'], + 'topic_id' => 0, + 'forum_id' => 0, + 'save_time' => $current_time, + 'draft_subject' => $subject, + 'draft_message' => $message_parser->message, + ) + ); + $db->sql_query($sql); + + $redirect_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=$mode"); + + meta_refresh(3, $redirect_url); + $message = $user->lang['DRAFT_SAVED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + + trigger_error($message); + } + else + { + $s_hidden_fields = build_hidden_fields(array( + 'mode' => $mode, + 'action' => $action, + 'save' => true, + 'subject' => $subject, + 'message' => $message, + 'u' => $to_user_id, + 'g' => $to_group_id, + 'p' => $msg_id) + ); + $s_hidden_fields .= build_address_field($address_list); + + confirm_box(false, 'SAVE_DRAFT', $s_hidden_fields); + } + } + else + { + if (utf8_clean_string($subject) === '') + { + $error[] = $user->lang['EMPTY_MESSAGE_SUBJECT']; + } + + if (utf8_clean_string($message) === '') + { + $error[] = $user->lang['TOO_FEW_CHARS']; + } + } + + unset($subject, $message); + } + + // Load Draft + if ($draft_id && $auth->acl_get('u_savedrafts')) + { + $sql = 'SELECT draft_subject, draft_message + FROM ' . DRAFTS_TABLE . " + WHERE draft_id = $draft_id + AND topic_id = 0 + AND forum_id = 0 + AND user_id = " . $user->data['user_id']; + $result = $db->sql_query_limit($sql, 1); + + if ($row = $db->sql_fetchrow($result)) + { + $message_parser->message = $row['draft_message']; + $message_subject = $row['draft_subject']; + + $template->assign_var('S_DRAFT_LOADED', true); + } + else + { + $draft_id = 0; + } + $db->sql_freeresult($result); + } + + // Load Drafts + if ($load && $drafts) + { + load_drafts(0, 0, $id, $action, $msg_id); + } + + if ($submit || $preview || $refresh) + { + if (($submit || $preview) && !check_form_key('ucp_pm_compose')) + { + $error[] = $user->lang['FORM_INVALID']; + } + $subject = $request->variable('subject', '', true); + $message_parser->message = $request->variable('message', '', true); + + $icon_id = $request->variable('icon', 0); + + $enable_bbcode = (!$bbcode_status || isset($_POST['disable_bbcode'])) ? false : true; + $enable_smilies = (!$smilies_status || isset($_POST['disable_smilies'])) ? false : true; + $enable_urls = (isset($_POST['disable_magic_url'])) ? 0 : 1; + $enable_sig = (!$config['allow_sig'] ||!$config['allow_sig_pm']) ? false : ((isset($_POST['attach_sig'])) ? true : false); + + /** + * Modify private message + * + * @event core.ucp_pm_compose_modify_parse_before + * @var bool enable_bbcode Whether or not bbcode is enabled + * @var bool enable_smilies Whether or not smilies are enabled + * @var bool enable_urls Whether or not urls are enabled + * @var bool enable_sig Whether or not signature is enabled + * @var string subject PM subject text + * @var object message_parser The message parser object + * @var bool submit Whether or not the form has been sumitted + * @var bool preview Whether or not the signature is being previewed + * @var array error Any error strings + * @since 3.1.10-RC1 + */ + $vars = array( + 'enable_bbcode', + 'enable_smilies', + 'enable_urls', + 'enable_sig', + 'subject', + 'message_parser', + 'submit', + 'preview', + 'error', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_compose_modify_parse_before', compact($vars))); + + // Parse Attachments - before checksum is calculated + $message_parser->parse_attachments('fileupload', $action, 0, $submit, $preview, $refresh, true); + + if (count($message_parser->warn_msg) && !($remove_u || $remove_g || $add_to || $add_bcc)) + { + $error[] = implode('
', $message_parser->warn_msg); + $message_parser->warn_msg = array(); + } + + // Parse message + $message_parser->parse($enable_bbcode, ($config['allow_post_links']) ? $enable_urls : false, $enable_smilies, $img_status, $flash_status, true, $config['allow_post_links']); + + // On a refresh we do not care about message parsing errors + if (count($message_parser->warn_msg) && !$refresh) + { + $error[] = implode('
', $message_parser->warn_msg); + } + + if ($action != 'edit' && !$preview && !$refresh && $config['flood_interval'] && !$auth->acl_get('u_ignoreflood')) + { + // Flood check + $last_post_time = $user->data['user_lastpost_time']; + + if ($last_post_time) + { + if ($last_post_time && ($current_time - $last_post_time) < intval($config['flood_interval'])) + { + $error[] = $user->lang['FLOOD_ERROR']; + } + } + } + + // Subject defined + if ($submit) + { + if (utf8_clean_string($subject) === '') + { + $error[] = $user->lang['EMPTY_MESSAGE_SUBJECT']; + } + + if (!count($address_list)) + { + $error[] = $user->lang['NO_RECIPIENT']; + } + } + + // Store message, sync counters + if (!count($error) && $submit) + { + $pm_data = array( + 'msg_id' => (int) $msg_id, + 'from_user_id' => $user->data['user_id'], + 'from_user_ip' => $user->ip, + 'from_username' => $user->data['username'], + 'reply_from_root_level' => (isset($post['root_level'])) ? (int) $post['root_level'] : 0, + 'reply_from_msg_id' => (int) $msg_id, + 'icon_id' => (int) $icon_id, + 'enable_sig' => (bool) $enable_sig, + 'enable_bbcode' => (bool) $enable_bbcode, + 'enable_smilies' => (bool) $enable_smilies, + 'enable_urls' => (bool) $enable_urls, + 'bbcode_bitfield' => $message_parser->bbcode_bitfield, + 'bbcode_uid' => $message_parser->bbcode_uid, + 'message' => $message_parser->message, + 'attachment_data' => $message_parser->attachment_data, + 'filename_data' => $message_parser->filename_data, + 'address_list' => $address_list + ); + + // ((!$message_subject) ? $subject : $message_subject) + $msg_id = submit_pm($action, $subject, $pm_data); + + $return_message_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=view&p=' . $msg_id); + $inbox_folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=inbox'); + $outbox_folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=outbox'); + + $folder_url = ''; + if (($folder_id > 0) && isset($user_folders[$folder_id])) + { + $folder_url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&folder=' . $folder_id); + } + + $return_box_url = ($action === 'post' || $action === 'edit') ? $outbox_folder_url : $inbox_folder_url; + $return_box_lang = ($action === 'post' || $action === 'edit') ? 'PM_OUTBOX' : 'PM_INBOX'; + + $save_message = ($action === 'edit') ? $user->lang['MESSAGE_EDITED'] : $user->lang['MESSAGE_STORED']; + $message = $save_message . '

' . $user->lang('VIEW_PRIVATE_MESSAGE', '', ''); + + $last_click_type = 'CLICK_RETURN_FOLDER'; + if ($folder_url) + { + $message .= '

' . sprintf($user->lang['CLICK_RETURN_FOLDER'], '', '', $user_folders[$folder_id]['folder_name']); + $last_click_type = 'CLICK_GOTO_FOLDER'; + } + $message .= '

' . sprintf($user->lang[$last_click_type], '', '', $user->lang[$return_box_lang]); + + meta_refresh(3, $return_message_url); + trigger_error($message); + } + + $message_subject = $subject; + } + + // Preview + if (!count($error) && $preview) + { + $preview_message = $message_parser->format_display($enable_bbcode, $enable_urls, $enable_smilies, false); + + $preview_signature = $user->data['user_sig']; + $preview_signature_uid = $user->data['user_sig_bbcode_uid']; + $preview_signature_bitfield = $user->data['user_sig_bbcode_bitfield']; + + // Signature + if ($enable_sig && $config['allow_sig'] && $preview_signature) + { + $bbcode_flags = ($enable_bbcode ? OPTION_FLAG_BBCODE : 0) + ($enable_smilies ? OPTION_FLAG_SMILIES : 0) + ($enable_urls ? OPTION_FLAG_LINKS : 0); + $preview_signature = generate_text_for_display($preview_signature, $preview_signature_uid, $preview_signature_bitfield, $bbcode_flags); + } + else + { + $preview_signature = ''; + } + + // Attachment Preview + if (count($message_parser->attachment_data)) + { + $template->assign_var('S_HAS_ATTACHMENTS', true); + + $update_count = array(); + $attachment_data = $message_parser->attachment_data; + + parse_attachments(false, $preview_message, $attachment_data, $update_count, true); + + foreach ($attachment_data as $i => $attachment) + { + $template->assign_block_vars('attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + unset($attachment_data); + } + + $preview_subject = censor_text($subject); + + if (!count($error)) + { + $template->assign_vars(array( + 'PREVIEW_SUBJECT' => $preview_subject, + 'PREVIEW_MESSAGE' => $preview_message, + 'PREVIEW_SIGNATURE' => $preview_signature, + + 'S_DISPLAY_PREVIEW' => true) + ); + } + unset($message_text); + } + + // Decode text for message display + $bbcode_uid = (($action == 'quote' || $action == 'forward') && !$preview && !$refresh && (!count($error) || (count($error) && !$submit))) ? $bbcode_uid : $message_parser->bbcode_uid; + + $message_parser->decode_message($bbcode_uid); + + if (($action == 'quote' || $action == 'quotepost') && !$preview && !$refresh && !$submit) + { + if ($action == 'quotepost') + { + $post_id = $request->variable('p', 0); + if ($config['allow_post_links']) + { + $message_link = generate_board_url() . "/viewtopic.$phpEx?p={$post_id}#p{$post_id}"; + $message_link_subject = "{$user->lang['SUBJECT']}{$user->lang['COLON']} {$message_subject}"; + if ($bbcode_status) + { + $message_link = "[url=" . $message_link . "]" . $message_link_subject . "[/url]\n\n"; + } + else + { + $message_link = $message_link . " - " . $message_link_subject . "\n\n"; + } + } + else + { + $message_link = $user->lang['SUBJECT'] . $user->lang['COLON'] . ' ' . $message_subject . " (" . generate_board_url() . "/viewtopic.$phpEx?p={$post_id}#p{$post_id})\n\n"; + } + } + else + { + $message_link = ''; + } + $quote_attributes = array( + 'author' => $quote_username, + 'time' => $post['message_time'], + 'user_id' => $post['author_id'], + ); + if ($action === 'quotepost') + { + $quote_attributes['post_id'] = $post['msg_id']; + } + + /** @var \phpbb\language\language $language */ + $language = $phpbb_container->get('language'); + /** @var \phpbb\textformatter\utils_interface $text_formatter_utils */ + $text_formatter_utils = $phpbb_container->get('text_formatter.utils'); + phpbb_format_quote($language, $message_parser, $text_formatter_utils, $bbcode_status, $quote_attributes, $message_link); + } + + if (($action == 'reply' || $action == 'quote' || $action == 'quotepost') && !$preview && !$refresh) + { + $message_subject = ((!preg_match('/^Re:/', $message_subject)) ? 'Re: ' : '') . censor_text($message_subject); + } + + if ($action == 'forward' && !$preview && !$refresh && !$submit) + { + $fwd_to_field = write_pm_addresses(array('to' => $post['to_address']), 0, true); + + if ($config['allow_post_links']) + { + $quote_username_text = '[url=' . generate_board_url() . "/memberlist.$phpEx?mode=viewprofile&u={$post['author_id']}]{$quote_username}[/url]"; + } + else + { + $quote_username_text = $quote_username . ' (' . generate_board_url() . "/memberlist.$phpEx?mode=viewprofile&u={$post['author_id']})"; + } + + $forward_text = array(); + $forward_text[] = $user->lang['FWD_ORIGINAL_MESSAGE']; + $forward_text[] = sprintf($user->lang['FWD_SUBJECT'], censor_text($message_subject)); + $forward_text[] = sprintf($user->lang['FWD_DATE'], $user->format_date($message_time, false, true)); + $forward_text[] = sprintf($user->lang['FWD_FROM'], $quote_username_text); + $forward_text[] = sprintf($user->lang['FWD_TO'], implode($user->lang['COMMA_SEPARATOR'], $fwd_to_field['to'])); + + $quote_text = $phpbb_container->get('text_formatter.utils')->generate_quote( + censor_text($message_parser->message), + array('author' => $quote_username) + ); + $message_parser->message = implode("\n", $forward_text) . "\n\n" . $quote_text; + $message_subject = ((!preg_match('/^Fwd:/', $message_subject)) ? 'Fwd: ' : '') . censor_text($message_subject); + } + + $attachment_data = $message_parser->attachment_data; + $filename_data = $message_parser->filename_data; + $message_text = $message_parser->message; + + // MAIN PM PAGE BEGINS HERE + + // Generate smiley listing + generate_smilies('inline', 0); + + // Generate PM Icons + $s_pm_icons = false; + if ($config['enable_pm_icons']) + { + $s_pm_icons = posting_gen_topic_icons($action, $icon_id); + } + + // Generate inline attachment select box + posting_gen_inline_attachments($attachment_data); + + // Build address list for display + // array('u' => array($author_id => 'to')); + if (count($address_list)) + { + // Get Usernames and Group Names + $result = array(); + if (!empty($address_list['u'])) + { + $sql = 'SELECT user_id as id, username as name, user_colour as colour + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', array_map('intval', array_keys($address_list['u']))) . ' + ORDER BY username_clean ASC'; + $result['u'] = $db->sql_query($sql); + } + + if (!empty($address_list['g'])) + { + $sql = 'SELECT g.group_id AS id, g.group_name AS name, g.group_colour AS colour, g.group_type + FROM ' . GROUPS_TABLE . ' g'; + + if (!$auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) + { + $sql .= ' LEFT JOIN ' . USER_GROUP_TABLE . ' ug + ON ( + g.group_id = ug.group_id + AND ug.user_id = ' . $user->data['user_id'] . ' + AND ug.user_pending = 0 + ) + WHERE (g.group_type <> ' . GROUP_HIDDEN . ' OR ug.user_id = ' . $user->data['user_id'] . ')'; + } + + $sql .= ($auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) ? ' WHERE ' : ' AND '; + + $sql .= 'g.group_receive_pm = 1 + AND ' . $db->sql_in_set('g.group_id', array_map('intval', array_keys($address_list['g']))) . ' + ORDER BY g.group_name ASC'; + + $result['g'] = $db->sql_query($sql); + } + + $u = $g = array(); + $_types = array('u', 'g'); + foreach ($_types as $type) + { + if (isset($result[$type]) && $result[$type]) + { + while ($row = $db->sql_fetchrow($result[$type])) + { + if ($type == 'g') + { + $row['name'] = $group_helper->get_name($row['name']); + } + + ${$type}[$row['id']] = array('name' => $row['name'], 'colour' => $row['colour']); + } + $db->sql_freeresult($result[$type]); + } + } + + // Now Build the address list + foreach ($address_list as $type => $adr_ary) + { + foreach ($adr_ary as $id => $field) + { + if (!isset(${$type}[$id])) + { + unset($address_list[$type][$id]); + continue; + } + + $field = ($field == 'to') ? 'to' : 'bcc'; + $type = ($type == 'u') ? 'u' : 'g'; + $id = (int) $id; + + $tpl_ary = array( + 'IS_GROUP' => ($type == 'g') ? true : false, + 'IS_USER' => ($type == 'u') ? true : false, + 'UG_ID' => $id, + 'NAME' => ${$type}[$id]['name'], + 'COLOUR' => (${$type}[$id]['colour']) ? '#' . ${$type}[$id]['colour'] : '', + 'TYPE' => $type, + ); + + if ($type == 'u') + { + $tpl_ary = array_merge($tpl_ary, array( + 'U_VIEW' => get_username_string('profile', $id, ${$type}[$id]['name'], ${$type}[$id]['colour']), + 'NAME_FULL' => get_username_string('full', $id, ${$type}[$id]['name'], ${$type}[$id]['colour']), + )); + } + else + { + $tpl_ary = array_merge($tpl_ary, array( + 'U_VIEW' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&g=' . $id), + )); + } + + $template->assign_block_vars($field . '_recipient', $tpl_ary); + } + } + } + + // Build hidden address list + $s_hidden_address_field = build_address_field($address_list); + + $bbcode_checked = (isset($enable_bbcode)) ? !$enable_bbcode : (($config['allow_bbcode'] && $auth->acl_get('u_pm_bbcode')) ? !$user->optionget('bbcode') : 1); + $smilies_checked = (isset($enable_smilies)) ? !$enable_smilies : (($config['allow_smilies'] && $auth->acl_get('u_pm_smilies')) ? !$user->optionget('smilies') : 1); + $urls_checked = (isset($enable_urls)) ? !$enable_urls : 0; + $sig_checked = $enable_sig; + + switch ($action) + { + case 'post': + $page_title = $user->lang['POST_NEW_PM']; + break; + + case 'quote': + $page_title = $user->lang['POST_QUOTE_PM']; + break; + + case 'quotepost': + $page_title = $user->lang['POST_PM_POST']; + break; + + case 'reply': + $page_title = $user->lang['POST_REPLY_PM']; + break; + + case 'edit': + $page_title = $user->lang['POST_EDIT_PM']; + break; + + case 'forward': + $page_title = $user->lang['POST_FORWARD_PM']; + break; + + default: + trigger_error('NO_ACTION_MODE', E_USER_ERROR); + break; + } + + $s_hidden_fields = (isset($check_value)) ? '' : ''; + $s_hidden_fields .= ($draft_id || isset($_REQUEST['draft_loaded'])) ? '' : ''; + + $form_enctype = (@ini_get('file_uploads') == '0' || strtolower(@ini_get('file_uploads')) == 'off' || !$config['allow_pm_attach'] || !$auth->acl_get('u_pm_attach')) ? '' : ' enctype="multipart/form-data"'; + + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + + // Start assigning vars for main posting page ... + $template_ary = array( + 'L_POST_A' => $page_title, + 'L_ICON' => $user->lang['PM_ICON'], + 'L_MESSAGE_BODY_EXPLAIN' => $user->lang('MESSAGE_BODY_EXPLAIN', (int) $config['max_post_chars']), + + 'SUBJECT' => (isset($message_subject)) ? $message_subject : '', + 'MESSAGE' => $message_text, + 'BBCODE_STATUS' => $user->lang(($bbcode_status ? 'BBCODE_IS_ON' : 'BBCODE_IS_OFF'), '', ''), + 'IMG_STATUS' => ($img_status) ? $user->lang['IMAGES_ARE_ON'] : $user->lang['IMAGES_ARE_OFF'], + 'FLASH_STATUS' => ($flash_status) ? $user->lang['FLASH_IS_ON'] : $user->lang['FLASH_IS_OFF'], + 'SMILIES_STATUS' => ($smilies_status) ? $user->lang['SMILIES_ARE_ON'] : $user->lang['SMILIES_ARE_OFF'], + 'URL_STATUS' => ($url_status) ? $user->lang['URL_IS_ON'] : $user->lang['URL_IS_OFF'], + 'MAX_FONT_SIZE' => (int) $config['max_post_font_size'], + 'MINI_POST_IMG' => $user->img('icon_post_target', $user->lang['PM']), + 'ERROR' => (count($error)) ? implode('
', $error) : '', + 'MAX_RECIPIENTS' => ($config['allow_mass_pm'] && ($auth->acl_get('u_masspm') || $auth->acl_get('u_masspm_group'))) ? $max_recipients : 0, + + 'S_COMPOSE_PM' => true, + 'S_EDIT_POST' => ($action == 'edit'), + 'S_SHOW_PM_ICONS' => $s_pm_icons, + 'S_BBCODE_ALLOWED' => ($bbcode_status) ? 1 : 0, + 'S_BBCODE_CHECKED' => ($bbcode_checked) ? ' checked="checked"' : '', + 'S_SMILIES_ALLOWED' => $smilies_status, + 'S_SMILIES_CHECKED' => ($smilies_checked) ? ' checked="checked"' : '', + 'S_SIG_ALLOWED' => ($config['allow_sig'] && $config['allow_sig_pm'] && $auth->acl_get('u_sig')), + 'S_SIGNATURE_CHECKED' => ($sig_checked) ? ' checked="checked"' : '', + 'S_LINKS_ALLOWED' => $url_status, + 'S_MAGIC_URL_CHECKED' => ($urls_checked) ? ' checked="checked"' : '', + 'S_SAVE_ALLOWED' => ($auth->acl_get('u_savedrafts') && $action != 'edit') ? true : false, + 'S_HAS_DRAFTS' => ($auth->acl_get('u_savedrafts') && $drafts), + 'S_FORM_ENCTYPE' => $form_enctype, + 'S_ATTACH_DATA' => json_encode($message_parser->attachment_data), + + 'S_BBCODE_IMG' => $img_status, + 'S_BBCODE_FLASH' => $flash_status, + 'S_BBCODE_QUOTE' => true, + 'S_BBCODE_URL' => $url_status, + + 'S_POST_ACTION' => $s_action, + 'S_HIDDEN_ADDRESS_FIELD' => $s_hidden_address_field, + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + + 'S_CLOSE_PROGRESS_WINDOW' => isset($_POST['add_file']), + 'U_PROGRESS_BAR' => append_sid("{$phpbb_root_path}posting.$phpEx", 'f=0&mode=popup'), + 'UA_PROGRESS_BAR' => addslashes(append_sid("{$phpbb_root_path}posting.$phpEx", 'f=0&mode=popup')), + ); + + /** + * Modify the default template vars + * + * @event core.ucp_pm_compose_template + * @var array template_ary Template variables + * @since 3.2.6-RC1 + */ + $vars = array('template_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_compose_template', compact($vars))); + + $template->assign_vars($template_ary); + + // Build custom bbcodes array + display_custom_bbcodes(); + + // Show attachment box for adding attachments if true + $allowed = ($auth->acl_get('u_pm_attach') && $config['allow_pm_attach'] && $form_enctype); + + if ($allowed) + { + $max_files = ($auth->acl_gets('a_', 'm_')) ? 0 : (int) $config['max_attachments_pm']; + $plupload->configure($cache, $template, $s_action, false, $max_files); + } + + // Attachment entry + posting_gen_attachment_entry($attachment_data, $filename_data, $allowed); + + // Message History + if ($action == 'reply' || $action == 'quote' || $action == 'forward') + { + if (message_history($msg_id, $user->data['user_id'], $post, array(), true)) + { + $template->assign_var('S_DISPLAY_HISTORY', true); + } + } +} + +/** +* For composing messages, handle list actions +*/ +function handle_message_list_actions(&$address_list, &$error, $remove_u, $remove_g, $add_to, $add_bcc) +{ + global $auth, $db, $user; + global $request, $phpbb_dispatcher; + + // Delete User [TO/BCC] + if ($remove_u && $request->variable('remove_u', array(0 => ''))) + { + $remove_user_id = array_keys($request->variable('remove_u', array(0 => ''))); + + if (isset($remove_user_id[0])) + { + unset($address_list['u'][(int) $remove_user_id[0]]); + } + } + + // Delete Group [TO/BCC] + if ($remove_g && $request->variable('remove_g', array(0 => ''))) + { + $remove_group_id = array_keys($request->variable('remove_g', array(0 => ''))); + + if (isset($remove_group_id[0])) + { + unset($address_list['g'][(int) $remove_group_id[0]]); + } + } + + // Add Selected Groups + $group_list = $request->variable('group_list', array(0)); + + // Build usernames to add + $usernames = $request->variable('username', '', true); + $usernames = (empty($usernames)) ? array() : array($usernames); + + $username_list = $request->variable('username_list', '', true); + if ($username_list) + { + $usernames = array_merge($usernames, explode("\n", $username_list)); + } + + // If add to or add bcc not pressed, users could still have usernames listed they want to add... + if (!$add_to && !$add_bcc && (count($group_list) || count($usernames))) + { + $add_to = true; + + global $refresh, $submit, $preview; + + $refresh = true; + $submit = false; + + // Preview is only true if there was also a message entered + if ($request->variable('message', '')) + { + $preview = true; + } + } + + // Add User/Group [TO] + if ($add_to || $add_bcc) + { + $type = ($add_to) ? 'to' : 'bcc'; + + if (count($group_list)) + { + foreach ($group_list as $group_id) + { + $address_list['g'][$group_id] = $type; + } + } + + // User ID's to add... + $user_id_ary = array(); + + // Reveal the correct user_ids + if (count($usernames)) + { + $user_id_ary = array(); + user_get_id_name($user_id_ary, $usernames, array(USER_NORMAL, USER_FOUNDER, USER_INACTIVE)); + + // If there are users not existing, we will at least print a notice... + if (!count($user_id_ary)) + { + $error[] = $user->lang['PM_NO_USERS']; + } + } + + // Add Friends if specified + $friend_list = array_keys($request->variable('add_' . $type, array(0))); + $user_id_ary = array_merge($user_id_ary, $friend_list); + + foreach ($user_id_ary as $user_id) + { + if ($user_id == ANONYMOUS) + { + continue; + } + + $address_list['u'][$user_id] = $type; + } + } + + // Check for disallowed recipients + if (!empty($address_list['u'])) + { + $can_ignore_allow_pm = $auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'); + + // Administrator deactivated users check and we need to check their + // PM status (do they want to receive PM's?) + // Only check PM status if not a moderator or admin, since they + // are allowed to override this user setting + $sql = 'SELECT user_id, user_allow_pm + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', array_keys($address_list['u'])) . ' + AND ( + (user_type = ' . USER_INACTIVE . ' + AND user_inactive_reason = ' . INACTIVE_MANUAL . ') + ' . ($can_ignore_allow_pm ? '' : ' OR user_allow_pm = 0') . ' + )'; + + $result = $db->sql_query($sql); + + $removed_no_pm = $removed_no_permission = false; + while ($row = $db->sql_fetchrow($result)) + { + if (!$can_ignore_allow_pm && !$row['user_allow_pm']) + { + $removed_no_pm = true; + } + else + { + $removed_no_permission = true; + } + + unset($address_list['u'][$row['user_id']]); + } + $db->sql_freeresult($result); + + // print a notice about users not being added who do not want to receive pms + if ($removed_no_pm) + { + $error[] = $user->lang['PM_USERS_REMOVED_NO_PM']; + } + + // print a notice about users not being added who do not have permission to receive PMs + if ($removed_no_permission) + { + $error[] = $user->lang['PM_USERS_REMOVED_NO_PERMISSION']; + } + + if (!count(array_keys($address_list['u']))) + { + return; + } + + // Check if users have permission to read PMs + $can_read = $auth->acl_get_list(array_keys($address_list['u']), 'u_readpm'); + $can_read = (empty($can_read) || !isset($can_read[0]['u_readpm'])) ? array() : $can_read[0]['u_readpm']; + $cannot_read_list = array_diff(array_keys($address_list['u']), $can_read); + if (!empty($cannot_read_list)) + { + foreach ($cannot_read_list as $cannot_read) + { + unset($address_list['u'][$cannot_read]); + } + + $error[] = $user->lang['PM_USERS_REMOVED_NO_PERMISSION']; + } + + // Check if users are banned + $banned_user_list = phpbb_get_banned_user_ids(array_keys($address_list['u']), false); + if (!empty($banned_user_list)) + { + foreach ($banned_user_list as $banned_user) + { + unset($address_list['u'][$banned_user]); + } + + $error[] = $user->lang['PM_USERS_REMOVED_NO_PERMISSION']; + } + } + + /** + * Event for additional message list actions + * + * @event core.message_list_actions + * @var array address_list The assoc array with the recipient user/group ids + * @var array error The array containing error data + * @var bool remove_u The variable for removing a user + * @var bool remove_g The variable for removing a group + * @var bool add_to The variable for adding a user to the [TO] field + * @var bool add_bcc The variable for adding a user to the [BCC] field + * @since 3.2.4-RC1 + */ + $vars = array('address_list', 'error', 'remove_u', 'remove_g', 'add_to', 'add_bcc'); + extract($phpbb_dispatcher->trigger_event('core.message_list_actions', compact($vars))); +} + +/** +* Build the hidden field for the recipients. Needed, as the variable is not read via $request->variable(). +*/ +function build_address_field($address_list) +{ + $s_hidden_address_field = ''; + foreach ($address_list as $type => $adr_ary) + { + foreach ($adr_ary as $id => $field) + { + $s_hidden_address_field .= ''; + } + } + return $s_hidden_address_field; +} + +/** +* Return number of private message recipients +*/ +function num_recipients($address_list) +{ + $num_recipients = 0; + + foreach ($address_list as $field => $adr_ary) + { + $num_recipients += count($adr_ary); + } + + return $num_recipients; +} + +/** +* Get number of 'num_recipients' recipients from first position +*/ +function get_recipients($address_list, $num_recipients = 1) +{ + $recipient = array(); + + $count = 0; + foreach ($address_list as $field => $adr_ary) + { + foreach ($adr_ary as $id => $type) + { + if ($count >= $num_recipients) + { + break 2; + } + $recipient[$field][$id] = $type; + $count++; + } + } + + return $recipient; +} diff --git a/includes/ucp/ucp_pm_options.php b/includes/ucp/ucp_pm_options.php new file mode 100644 index 0000000..3861962 --- /dev/null +++ b/includes/ucp/ucp_pm_options.php @@ -0,0 +1,874 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Execute message options +*/ +function message_options($id, $mode, $global_privmsgs_rules, $global_rule_conditions) +{ + global $phpbb_root_path, $phpEx, $user, $template, $config, $db, $request; + + $redirect_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=options"); + + add_form_key('ucp_pm_options'); + // Change "full folder" setting - what to do if folder is full + if (isset($_POST['fullfolder'])) + { + if (!check_form_key('ucp_pm_options')) + { + trigger_error('FORM_INVALID'); + } + + $full_action = $request->variable('full_action', 0); + + $set_folder_id = 0; + switch ($full_action) + { + case 1: + $set_folder_id = FULL_FOLDER_DELETE; + break; + + case 2: + $set_folder_id = $request->variable('full_move_to', PRIVMSGS_INBOX); + break; + + case 3: + $set_folder_id = FULL_FOLDER_HOLD; + break; + + default: + $full_action = 0; + break; + } + + if ($full_action) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_full_folder = ' . $set_folder_id . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $user->data['user_full_folder'] = $set_folder_id; + + $message = $user->lang['FULL_FOLDER_OPTION_CHANGED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + meta_refresh(3, $redirect_url); + trigger_error($message); + } + } + + // Add Folder + if (isset($_POST['addfolder'])) + { + if (check_form_key('ucp_pm_options')) + { + $folder_name = $request->variable('foldername', '', true); + + if ($folder_name) + { + $sql = 'SELECT folder_name + FROM ' . PRIVMSGS_FOLDER_TABLE . " + WHERE folder_name = '" . $db->sql_escape($folder_name) . "' + AND user_id = " . $user->data['user_id']; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + trigger_error(sprintf($user->lang['FOLDER_NAME_EXIST'], $folder_name)); + } + + $sql = 'SELECT COUNT(folder_id) as num_folder + FROM ' . PRIVMSGS_FOLDER_TABLE . ' + WHERE user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + $num_folder = (int) $db->sql_fetchfield('num_folder'); + $db->sql_freeresult($result); + + if ($num_folder >= $config['pm_max_boxes']) + { + trigger_error('MAX_FOLDER_REACHED'); + } + + $sql = 'INSERT INTO ' . PRIVMSGS_FOLDER_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'user_id' => (int) $user->data['user_id'], + 'folder_name' => $folder_name) + ); + $db->sql_query($sql); + $msg = $user->lang['FOLDER_ADDED']; + } + else + { + $msg = $user->lang['FOLDER_NAME_EMPTY']; + } + } + else + { + $msg = $user->lang['FORM_INVALID']; + } + $message = $msg . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + meta_refresh(3, $redirect_url); + trigger_error($message); + } + + // Rename folder + if (isset($_POST['rename_folder'])) + { + if (check_form_key('ucp_pm_options')) + { + $new_folder_name = $request->variable('new_folder_name', '', true); + $rename_folder_id= $request->variable('rename_folder_id', 0); + + if (!$new_folder_name) + { + trigger_error('NO_NEW_FOLDER_NAME'); + } + + // Select custom folder + $sql = 'SELECT folder_name, pm_count + FROM ' . PRIVMSGS_FOLDER_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND folder_id = $rename_folder_id"; + $result = $db->sql_query_limit($sql, 1); + $folder_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$folder_row) + { + trigger_error('CANNOT_RENAME_FOLDER'); + } + + $sql = 'UPDATE ' . PRIVMSGS_FOLDER_TABLE . " + SET folder_name = '" . $db->sql_escape($new_folder_name) . "' + WHERE folder_id = $rename_folder_id + AND user_id = {$user->data['user_id']}"; + $db->sql_query($sql); + $msg = $user->lang['FOLDER_RENAMED']; + } + else + { + $msg = $user->lang['FORM_INVALID']; + } + + $message = $msg . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + + meta_refresh(3, $redirect_url); + trigger_error($message); + } + + // Remove Folder + if (isset($_POST['remove_folder'])) + { + $remove_folder_id = $request->variable('remove_folder_id', 0); + + // Default to "move all messages to inbox" + $remove_action = $request->variable('remove_action', 1); + $move_to = $request->variable('move_to', PRIVMSGS_INBOX); + + // Move to same folder? + if ($remove_action == 1 && $remove_folder_id == $move_to) + { + trigger_error('CANNOT_MOVE_TO_SAME_FOLDER'); + } + + // Select custom folder + $sql = 'SELECT folder_name, pm_count + FROM ' . PRIVMSGS_FOLDER_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND folder_id = $remove_folder_id"; + $result = $db->sql_query_limit($sql, 1); + $folder_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$folder_row) + { + trigger_error('CANNOT_REMOVE_FOLDER'); + } + + $s_hidden_fields = array( + 'remove_folder_id' => $remove_folder_id, + 'remove_action' => $remove_action, + 'move_to' => $move_to, + 'remove_folder' => 1 + ); + + // Do we need to confirm? + if (confirm_box(true)) + { + // Gather message ids + $sql = 'SELECT msg_id + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . " + AND folder_id = $remove_folder_id"; + $result = $db->sql_query($sql); + + $msg_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $msg_ids[] = (int) $row['msg_id']; + } + $db->sql_freeresult($result); + + // First of all, copy all messages to another folder... or delete all messages + switch ($remove_action) + { + // Move Messages + case 1: + $num_moved = move_pm($user->data['user_id'], $user->data['message_limit'], $msg_ids, $move_to, $remove_folder_id); + + // Something went wrong, only partially moved? + if ($num_moved != $folder_row['pm_count']) + { + trigger_error($user->lang('MOVE_PM_ERROR', $user->lang('MESSAGES_COUNT', (int) $folder_row['pm_count']), $num_moved)); + } + break; + + // Remove Messages + case 2: + delete_pm($user->data['user_id'], $msg_ids, $remove_folder_id); + break; + } + + // Remove folder + $sql = 'DELETE FROM ' . PRIVMSGS_FOLDER_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND folder_id = $remove_folder_id"; + $db->sql_query($sql); + + // Check full folder option. If the removed folder has been specified as destination switch back to inbox + if ($user->data['user_full_folder'] == $remove_folder_id) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_full_folder = ' . PRIVMSGS_INBOX . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $user->data['user_full_folder'] = PRIVMSGS_INBOX; + } + + // Now make sure the folder is not used for rules + // We assign another folder id (the one the messages got moved to) or assign the INBOX (to not have to remove any rule) + $sql = 'UPDATE ' . PRIVMSGS_RULES_TABLE . ' SET rule_folder_id = '; + $sql .= ($remove_action == 1) ? $move_to : PRIVMSGS_INBOX; + $sql .= ' WHERE rule_folder_id = ' . $remove_folder_id; + + $db->sql_query($sql); + + $meta_info = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=$mode"); + $message = $user->lang['FOLDER_REMOVED']; + + meta_refresh(3, $meta_info); + $message .= '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + else + { + confirm_box(false, 'REMOVE_FOLDER', build_hidden_fields($s_hidden_fields)); + } + } + + // Add Rule + if (isset($_POST['add_rule'])) + { + if (check_form_key('ucp_pm_options')) + { + $check_option = $request->variable('check_option', 0); + $rule_option = $request->variable('rule_option', 0); + $cond_option = $request->variable('cond_option', ''); + $action_option = explode('|', $request->variable('action_option', '')); + $rule_string = ($cond_option != 'none') ? $request->variable('rule_string', '', true) : ''; + $rule_user_id = ($cond_option != 'none') ? $request->variable('rule_user_id', 0) : 0; + $rule_group_id = ($cond_option != 'none') ? $request->variable('rule_group_id', 0) : 0; + + $action = (int) $action_option[0]; + $folder_id = (int) $action_option[1]; + + if (!$action || !$check_option || !$rule_option || !$cond_option || ($cond_option != 'none' && !$rule_string)) + { + trigger_error('RULE_NOT_DEFINED'); + } + + if (($cond_option == 'user' && !$rule_user_id) || ($cond_option == 'group' && !$rule_group_id)) + { + trigger_error('RULE_NOT_DEFINED'); + } + + $rule_ary = array( + 'user_id' => $user->data['user_id'], + 'rule_check' => $check_option, + 'rule_connection' => $rule_option, + 'rule_string' => $rule_string, + 'rule_user_id' => $rule_user_id, + 'rule_group_id' => $rule_group_id, + 'rule_action' => $action, + 'rule_folder_id' => $folder_id + ); + + $sql = 'SELECT rule_id + FROM ' . PRIVMSGS_RULES_TABLE . ' + WHERE ' . $db->sql_build_array('SELECT', $rule_ary); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + trigger_error('RULE_ALREADY_DEFINED'); + } + + // Prevent users from flooding the rules table + $sql = 'SELECT COUNT(rule_id) AS num_rules + FROM ' . PRIVMSGS_RULES_TABLE . ' + WHERE user_id = ' . (int) $user->data['user_id']; + $result = $db->sql_query($sql); + $num_rules = (int) $db->sql_fetchfield('num_rules'); + $db->sql_freeresult($result); + + if ($num_rules >= 5000) + { + trigger_error('RULE_LIMIT_REACHED'); + } + + $sql = 'INSERT INTO ' . PRIVMSGS_RULES_TABLE . ' ' . $db->sql_build_array('INSERT', $rule_ary); + $db->sql_query($sql); + + // Set the user_message_rules bit + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_message_rules = 1 + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $msg = $user->lang['RULE_ADDED']; + } + else + { + $msg = $user->lang['FORM_INVALID']; + } + $message = $msg . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + meta_refresh(3, $redirect_url); + trigger_error($message); + } + + // Remove Rule + if (isset($_POST['delete_rule']) && !isset($_POST['cancel'])) + { + $delete_id = array_keys($request->variable('delete_rule', array(0 => 0))); + $delete_id = (!empty($delete_id[0])) ? $delete_id[0] : 0; + + if (!$delete_id) + { + redirect(append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=' . $mode)); + } + + // Do we need to confirm? + if (confirm_box(true)) + { + $sql = 'DELETE FROM ' . PRIVMSGS_RULES_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . " + AND rule_id = $delete_id"; + $db->sql_query($sql); + + $meta_info = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=' . $mode); + $message = $user->lang['RULE_DELETED']; + + // Reset user_message_rules if no more assigned + $sql = 'SELECT rule_id + FROM ' . PRIVMSGS_RULES_TABLE . ' + WHERE user_id = ' . $user->data['user_id']; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Unset the user_message_rules bit + if (!$row) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_message_rules = 0 + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + } + + meta_refresh(3, $meta_info); + $message .= '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + else + { + confirm_box(false, 'DELETE_RULE', build_hidden_fields(array('delete_rule' => array($delete_id => 1)))); + } + } + + $folder = array(); + + $sql = 'SELECT COUNT(msg_id) as num_messages + FROM ' . PRIVMSGS_TO_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . ' + AND folder_id = ' . PRIVMSGS_INBOX; + $result = $db->sql_query($sql); + $num_messages = (int) $db->sql_fetchfield('num_messages'); + $db->sql_freeresult($result); + + $folder[PRIVMSGS_INBOX] = array( + 'folder_name' => $user->lang['PM_INBOX'], + 'message_status' => $user->lang('FOLDER_MESSAGE_STATUS', $user->lang('MESSAGES_COUNT', (int) $user->data['message_limit']), $num_messages), + ); + + $sql = 'SELECT folder_id, folder_name, pm_count + FROM ' . PRIVMSGS_FOLDER_TABLE . ' + WHERE user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + + $num_user_folder = 0; + while ($row = $db->sql_fetchrow($result)) + { + $num_user_folder++; + $folder[$row['folder_id']] = array( + 'folder_name' => $row['folder_name'], + 'message_status' => $user->lang('FOLDER_MESSAGE_STATUS', $user->lang('MESSAGES_COUNT', (int) $user->data['message_limit']), (int) $row['pm_count']), + ); + } + $db->sql_freeresult($result); + + $s_full_folder_options = $s_to_folder_options = $s_folder_options = ''; + + if ($user->data['user_full_folder'] == FULL_FOLDER_NONE) + { + // -3 here to let the correct folder id be selected + $to_folder_id = $config['full_folder_action'] - 3; + } + else + { + $to_folder_id = $user->data['user_full_folder']; + } + + foreach ($folder as $folder_id => $folder_ary) + { + $s_full_folder_options .= ''; + $s_to_folder_options .= ''; + + if ($folder_id != PRIVMSGS_INBOX) + { + $s_folder_options .= ''; + } + } + + $s_delete_checked = ($user->data['user_full_folder'] == FULL_FOLDER_DELETE) ? ' checked="checked"' : ''; + $s_hold_checked = ($user->data['user_full_folder'] == FULL_FOLDER_HOLD) ? ' checked="checked"' : ''; + $s_move_checked = ($user->data['user_full_folder'] >= 0) ? ' checked="checked"' : ''; + + if ($user->data['user_full_folder'] == FULL_FOLDER_NONE) + { + switch ($config['full_folder_action']) + { + case 1: + $s_delete_checked = ' checked="checked"'; + break; + + case 2: + $s_hold_checked = ' checked="checked"'; + break; + } + } + + $template->assign_vars(array( + 'S_FULL_FOLDER_OPTIONS' => $s_full_folder_options, + 'S_TO_FOLDER_OPTIONS' => $s_to_folder_options, + 'S_FOLDER_OPTIONS' => $s_folder_options, + 'S_DELETE_CHECKED' => $s_delete_checked, + 'S_HOLD_CHECKED' => $s_hold_checked, + 'S_MOVE_CHECKED' => $s_move_checked, + 'S_MAX_FOLDER_REACHED' => ($num_user_folder >= $config['pm_max_boxes']) ? true : false, + 'S_MAX_FOLDER_ZERO' => ($config['pm_max_boxes'] == 0) ? true : false, + + 'DEFAULT_ACTION' => ($config['full_folder_action'] == 1) ? $user->lang['DELETE_OLDEST_MESSAGES'] : $user->lang['HOLD_NEW_MESSAGES'], + + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=ucp&field=rule_string&select_single=true'), + )); + + $rule_lang = $action_lang = $check_lang = array(); + + // Build all three language arrays + preg_replace_callback('#^((RULE|ACTION|CHECK)_([A-Z0-9_]+))$#', function ($match) use(&$rule_lang, &$action_lang, &$check_lang, $user) { + ${strtolower($match[2]) . '_lang'}[constant($match[1])] = $user->lang['PM_' . $match[2]][$match[3]]; + }, array_keys(get_defined_constants())); + + /* + Rule Ordering: + -> CHECK_* -> RULE_* [IN $global_privmsgs_rules:CHECK_*] -> [IF $rule_conditions[RULE_*] [|text|bool|user|group|own_group]] -> ACTION_* + */ + + $check_option = $request->variable('check_option', 0); + $rule_option = $request->variable('rule_option', 0); + $cond_option = $request->variable('cond_option', ''); + $action_option = $request->variable('action_option', ''); + $back = (isset($_REQUEST['back'])) ? $request->variable('back', array('' => 0)) : array(); + + if (count($back)) + { + if ($action_option) + { + $action_option = ''; + } + else if ($cond_option) + { + $cond_option = ''; + } + else if ($rule_option) + { + $rule_option = 0; + } + else if ($check_option) + { + $check_option = 0; + } + } + + if (isset($back['action']) && $cond_option == 'none') + { + $back['cond'] = true; + } + + // Check + if (!isset($global_privmsgs_rules[$check_option])) + { + $check_option = 0; + } + + define_check_option(($check_option && !isset($back['rule'])) ? true : false, $check_option, $check_lang); + + if ($check_option && !isset($back['rule'])) + { + define_rule_option(($rule_option && !isset($back['cond'])) ? true : false, $rule_option, $rule_lang, $global_privmsgs_rules[$check_option]); + } + + if ($rule_option && !isset($back['cond'])) + { + if (!isset($global_rule_conditions[$rule_option])) + { + $cond_option = 'none'; + $template->assign_var('NONE_CONDITION', true); + } + else + { + define_cond_option(($cond_option && !isset($back['action'])) ? true : false, $cond_option, $rule_option, $global_rule_conditions); + } + } + + if ($cond_option && !isset($back['action'])) + { + define_action_option(false, $action_option, $action_lang, $folder); + } + + show_defined_rules($user->data['user_id'], $check_lang, $rule_lang, $action_lang, $folder); +} + +/** +* Defining check option for message rules +*/ +function define_check_option($hardcoded, $check_option, $check_lang) +{ + global $template; + + $s_check_options = ''; + if (!$hardcoded) + { + foreach ($check_lang as $value => $lang) + { + $s_check_options .= ''; + } + } + + $template->assign_vars(array( + 'S_CHECK_DEFINED' => true, + 'S_CHECK_SELECT' => ($hardcoded) ? false : true, + 'CHECK_CURRENT' => isset($check_lang[$check_option]) ? $check_lang[$check_option] : '', + 'S_CHECK_OPTIONS' => $s_check_options, + 'CHECK_OPTION' => $check_option) + ); +} + +/** +* Defining action option for message rules +*/ +function define_action_option($hardcoded, $action_option, $action_lang, $folder) +{ + global $template; + + $l_action = $s_action_options = ''; + if ($hardcoded) + { + $option = explode('|', $action_option); + $action = (int) $option[0]; + $folder_id = (int) $option[1]; + + $l_action = $action_lang[$action]; + if ($action == ACTION_PLACE_INTO_FOLDER) + { + $l_action .= ' -> ' . $folder[$folder_id]['folder_name']; + } + } + else + { + foreach ($action_lang as $action => $lang) + { + if ($action == ACTION_PLACE_INTO_FOLDER) + { + foreach ($folder as $folder_id => $folder_ary) + { + $s_action_options .= ''; + } + } + else + { + $s_action_options .= ''; + } + } + } + + $template->assign_vars(array( + 'S_ACTION_DEFINED' => true, + 'S_ACTION_SELECT' => ($hardcoded) ? false : true, + 'ACTION_CURRENT' => $l_action, + 'S_ACTION_OPTIONS' => $s_action_options, + 'ACTION_OPTION' => $action_option) + ); +} + +/** +* Defining rule option for message rules +*/ +function define_rule_option($hardcoded, $rule_option, $rule_lang, $check_ary) +{ + global $template; + global $module; + + $exclude = array(); + + if (!$module->loaded('zebra', 'friends')) + { + $exclude[RULE_IS_FRIEND] = true; + } + + if (!$module->loaded('zebra', 'foes')) + { + $exclude[RULE_IS_FOE] = true; + } + + $s_rule_options = ''; + if (!$hardcoded) + { + foreach ($check_ary as $value => $_check) + { + if (isset($exclude[$value])) + { + continue; + } + $s_rule_options .= ''; + } + } + + $template->assign_vars(array( + 'S_RULE_DEFINED' => true, + 'S_RULE_SELECT' => !$hardcoded, + 'RULE_CURRENT' => isset($rule_lang[$rule_option]) ? $rule_lang[$rule_option] : '', + 'S_RULE_OPTIONS' => $s_rule_options, + 'RULE_OPTION' => $rule_option) + ); +} + +/** +* Defining condition option for message rules +*/ +function define_cond_option($hardcoded, $cond_option, $rule_option, $global_rule_conditions) +{ + global $db, $template, $auth, $user, $request, $phpbb_container; + + /** @var \phpbb\group\helper $group_helper */ + $group_helper = $phpbb_container->get('group_helper'); + + $template->assign_vars(array( + 'S_COND_DEFINED' => true, + 'S_COND_SELECT' => (!$hardcoded && isset($global_rule_conditions[$rule_option])) ? true : false) + ); + + // Define COND_OPTION + if (!isset($global_rule_conditions[$rule_option])) + { + $template->assign_vars(array( + 'COND_OPTION' => 'none', + 'COND_CURRENT' => false) + ); + return; + } + + // Define Condition + $condition = $global_rule_conditions[$rule_option]; + + switch ($condition) + { + case 'text': + $rule_string = $request->variable('rule_string', '', true); + + $template->assign_vars(array( + 'S_TEXT_CONDITION' => true, + 'CURRENT_STRING' => $rule_string, + 'CURRENT_USER_ID' => 0, + 'CURRENT_GROUP_ID' => 0) + ); + + $current_value = $rule_string; + break; + + case 'user': + $rule_user_id = $request->variable('rule_user_id', 0); + $rule_string = $request->variable('rule_string', '', true); + + if ($rule_string && !$rule_user_id) + { + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($rule_string)) . "'"; + $result = $db->sql_query($sql); + $rule_user_id = (int) $db->sql_fetchfield('user_id'); + $db->sql_freeresult($result); + + if (!$rule_user_id) + { + $rule_string = ''; + } + } + else if (!$rule_string && $rule_user_id) + { + $sql = 'SELECT username + FROM ' . USERS_TABLE . " + WHERE user_id = $rule_user_id"; + $result = $db->sql_query($sql); + $rule_string = $db->sql_fetchfield('username'); + $db->sql_freeresult($result); + + if (!$rule_string) + { + $rule_user_id = 0; + } + } + + $template->assign_vars(array( + 'S_USER_CONDITION' => true, + 'CURRENT_STRING' => $rule_string, + 'CURRENT_USER_ID' => $rule_user_id, + 'CURRENT_GROUP_ID' => 0) + ); + + $current_value = $rule_string; + break; + + case 'group': + $rule_group_id = $request->variable('rule_group_id', 0); + $rule_string = $request->variable('rule_string', '', true); + + $sql = 'SELECT g.group_id, g.group_name, g.group_type + FROM ' . GROUPS_TABLE . ' g '; + + if (!$auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) + { + $sql .= 'LEFT JOIN ' . USER_GROUP_TABLE . ' ug + ON ( + g.group_id = ug.group_id + AND ug.user_id = ' . $user->data['user_id'] . ' + AND ug.user_pending = 0 + ) + WHERE (ug.user_id = ' . $user->data['user_id'] . ' OR g.group_type <> ' . GROUP_HIDDEN . ') + AND'; + } + else + { + $sql .= 'WHERE'; + } + + $sql .= " (g.group_name NOT IN ('GUESTS', 'BOTS') OR g.group_type <> " . GROUP_SPECIAL . ') + ORDER BY g.group_type DESC, g.group_name ASC'; + + $result = $db->sql_query($sql); + + $s_group_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + if ($rule_group_id && ($row['group_id'] == $rule_group_id)) + { + $rule_string = $group_helper->get_name($row['group_name']); + } + + $s_class = ($row['group_type'] == GROUP_SPECIAL) ? ' class="sep"' : ''; + $s_selected = ($row['group_id'] == $rule_group_id) ? ' selected="selected"' : ''; + + $s_group_options .= ''; + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'S_GROUP_CONDITION' => true, + 'S_GROUP_OPTIONS' => $s_group_options, + 'CURRENT_STRING' => $rule_string, + 'CURRENT_USER_ID' => 0, + 'CURRENT_GROUP_ID' => $rule_group_id) + ); + + $current_value = $rule_string; + break; + + default: + return; + } + + $template->assign_vars(array( + 'COND_OPTION' => $condition, + 'COND_CURRENT' => $current_value) + ); +} + +/** +* Display defined message rules +*/ +function show_defined_rules($user_id, $check_lang, $rule_lang, $action_lang, $folder) +{ + global $db, $template; + + $sql = 'SELECT * + FROM ' . PRIVMSGS_RULES_TABLE . ' + WHERE user_id = ' . $user_id . ' + ORDER BY rule_id ASC'; + $result = $db->sql_query($sql); + + $count = 0; + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('rule', array( + 'COUNT' => ++$count, + 'RULE_ID' => $row['rule_id'], + 'CHECK' => $check_lang[$row['rule_check']], + 'RULE' => $rule_lang[$row['rule_connection']], + 'STRING' => $row['rule_string'], + 'ACTION' => $action_lang[$row['rule_action']], + 'FOLDER' => ($row['rule_action'] == ACTION_PLACE_INTO_FOLDER) ? $folder[$row['rule_folder_id']]['folder_name'] : '') + ); + } + $db->sql_freeresult($result); +} diff --git a/includes/ucp/ucp_pm_viewfolder.php b/includes/ucp/ucp_pm_viewfolder.php new file mode 100644 index 0000000..09e7bf4 --- /dev/null +++ b/includes/ucp/ucp_pm_viewfolder.php @@ -0,0 +1,604 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* View message folder +* Called from ucp_pm with mode == 'view' && action == 'view_folder' +*/ +function view_folder($id, $mode, $folder_id, $folder) +{ + global $user, $template, $auth, $db, $cache, $request; + global $phpbb_root_path, $config, $phpEx; + + $submit_export = (isset($_POST['submit_export'])) ? true : false; + + $folder_info = get_pm_from($folder_id, $folder, $user->data['user_id']); + + if (!$submit_export) + { + $user->add_lang('viewforum'); + + // Grab icons + $icons = $cache->obtain_icons(); + + $color_rows = array('message_reported', 'marked', 'replied'); + + $_module = new p_master(); + $_module->list_modules('ucp'); + $_module->set_active('zebra'); + + $zebra_enabled = ($_module->active_module === false) ? false : true; + + unset($_module); + + if ($zebra_enabled) + { + $color_rows = array_merge($color_rows, array('friend', 'foe')); + } + + foreach ($color_rows as $var) + { + $template->assign_block_vars('pm_colour_info', array( + 'IMG' => $user->img("pm_{$var}", ''), + 'CLASS' => "pm_{$var}_colour", + 'LANG' => $user->lang[strtoupper($var) . '_MESSAGE']) + ); + } + + $mark_options = array('mark_important', 'delete_marked'); + + // Minimise edits + if (!$auth->acl_get('u_pm_delete') && $key = array_search('delete_marked', $mark_options)) + { + unset($mark_options[$key]); + } + + $s_mark_options = ''; + foreach ($mark_options as $mark_option) + { + $s_mark_options .= ''; + } + + // We do the folder moving options here too, for template authors to use... + $s_folder_move_options = ''; + if ($folder_id != PRIVMSGS_NO_BOX && $folder_id != PRIVMSGS_OUTBOX) + { + foreach ($folder as $f_id => $folder_ary) + { + if ($f_id == PRIVMSGS_OUTBOX || $f_id == PRIVMSGS_SENTBOX || $f_id == $folder_id) + { + continue; + } + + $s_folder_move_options .= ''; + $s_folder_move_options .= sprintf($user->lang['MOVE_MARKED_TO_FOLDER'], $folder_ary['folder_name']); + $s_folder_move_options .= (($folder_ary['unread_messages']) ? ' [' . $folder_ary['unread_messages'] . '] ' : '') . ''; + } + } + $friend = $foe = array(); + + // Get friends and foes + $sql = 'SELECT * + FROM ' . ZEBRA_TABLE . ' + WHERE user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $friend[$row['zebra_id']] = $row['friend']; + $foe[$row['zebra_id']] = $row['foe']; + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'S_MARK_OPTIONS' => $s_mark_options, + 'S_MOVE_MARKED_OPTIONS' => $s_folder_move_options) + ); + + // Okay, lets dump out the page ... + if (count($folder_info['pm_list'])) + { + $address_list = array(); + + // Build Recipient List if in outbox/sentbox - max two additional queries + if ($folder_id == PRIVMSGS_OUTBOX || $folder_id == PRIVMSGS_SENTBOX) + { + $address_list = get_recipient_strings($folder_info['rowset']); + } + + foreach ($folder_info['pm_list'] as $message_id) + { + $row = &$folder_info['rowset'][$message_id]; + + $folder_img = ($row['pm_unread']) ? 'pm_unread' : 'pm_read'; + $folder_alt = ($row['pm_unread']) ? 'NEW_MESSAGES' : 'NO_NEW_MESSAGES'; + + // Generate all URIs ... + $view_message_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=$id&mode=view&f=$folder_id&p=$message_id"); + $remove_message_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=$id&mode=compose&action=delete&p=$message_id"); + + $row_indicator = ''; + foreach ($color_rows as $var) + { + if (($var !== 'friend' && $var !== 'foe' && $row[($var === 'message_reported') ? $var : "pm_{$var}"]) + || + (($var === 'friend' || $var === 'foe') && isset(${$var}[$row['author_id']]) && ${$var}[$row['author_id']])) + { + $row_indicator = $var; + break; + } + } + + // Send vars to template + $template->assign_block_vars('messagerow', array( + 'PM_CLASS' => ($row_indicator) ? 'pm_' . $row_indicator . '_colour' : '', + + 'MESSAGE_AUTHOR_FULL' => get_username_string('full', $row['author_id'], $row['username'], $row['user_colour'], $row['username']), + 'MESSAGE_AUTHOR_COLOUR' => get_username_string('colour', $row['author_id'], $row['username'], $row['user_colour'], $row['username']), + 'MESSAGE_AUTHOR' => get_username_string('username', $row['author_id'], $row['username'], $row['user_colour'], $row['username']), + 'U_MESSAGE_AUTHOR' => get_username_string('profile', $row['author_id'], $row['username'], $row['user_colour'], $row['username']), + + 'FOLDER_ID' => $folder_id, + 'MESSAGE_ID' => $message_id, + 'SENT_TIME' => $user->format_date($row['message_time']), + 'SUBJECT' => censor_text($row['message_subject']), + 'FOLDER' => (isset($folder[$row['folder_id']])) ? $folder[$row['folder_id']]['folder_name'] : '', + 'U_FOLDER' => (isset($folder[$row['folder_id']])) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'folder=' . $row['folder_id']) : '', + 'PM_ICON_IMG' => (!empty($icons[$row['icon_id']])) ? '' : '', + 'PM_ICON_URL' => (!empty($icons[$row['icon_id']])) ? $config['icons_path'] . '/' . $icons[$row['icon_id']]['img'] : '', + 'FOLDER_IMG' => $user->img($folder_img, $folder_alt), + 'FOLDER_IMG_STYLE' => $folder_img, + 'PM_IMG' => ($row_indicator) ? $user->img('pm_' . $row_indicator, '') : '', + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_pm_download') && $row['message_attachment'] && $config['allow_pm_attach']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + + 'S_PM_UNREAD' => ($row['pm_unread']) ? true : false, + 'S_PM_DELETED' => ($row['pm_deleted']) ? true : false, + 'S_PM_REPORTED' => (isset($row['report_id'])) ? true : false, + 'S_AUTHOR_DELETED' => ($row['author_id'] == ANONYMOUS) ? true : false, + + 'U_VIEW_PM' => ($row['pm_deleted']) ? '' : $view_message_url, + 'U_REMOVE_PM' => ($row['pm_deleted']) ? $remove_message_url : '', + 'U_MCP_REPORT' => (isset($row['report_id'])) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=pm_reports&mode=pm_report_details&r=' . $row['report_id']) : '', + 'RECIPIENTS' => ($folder_id == PRIVMSGS_OUTBOX || $folder_id == PRIVMSGS_SENTBOX) ? implode($user->lang['COMMA_SEPARATOR'], $address_list[$message_id]) : '') + ); + } + unset($folder_info['rowset']); + + $template->assign_vars(array( + 'S_SHOW_RECIPIENTS' => ($folder_id == PRIVMSGS_OUTBOX || $folder_id == PRIVMSGS_SENTBOX) ? true : false, + 'S_SHOW_COLOUR_LEGEND' => true, + + 'REPORTED_IMG' => $user->img('icon_topic_reported', 'PM_REPORTED'), + 'S_PM_ICONS' => ($config['enable_pm_icons']) ? true : false) + ); + } + } + else + { + $export_type = $request->variable('export_option', ''); + $enclosure = $request->variable('enclosure', ''); + $delimiter = $request->variable('delimiter', ''); + + if ($export_type == 'CSV' && ($delimiter === '' || $enclosure === '')) + { + $template->assign_var('PROMPT', true); + } + else + { + // Build Recipient List if in outbox/sentbox + + $address_temp = $address = $data = array(); + + if ($folder_id == PRIVMSGS_OUTBOX || $folder_id == PRIVMSGS_SENTBOX) + { + foreach ($folder_info['rowset'] as $message_id => $row) + { + $address_temp[$message_id] = rebuild_header(array('to' => $row['to_address'], 'bcc' => $row['bcc_address'])); + $address[$message_id] = array(); + } + } + + foreach ($folder_info['pm_list'] as $message_id) + { + $row = &$folder_info['rowset'][$message_id]; + + include_once($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + + $sql = 'SELECT p.message_text, p.bbcode_uid + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE t.user_id = ' . $user->data['user_id'] . " + AND p.author_id = u.user_id + AND t.folder_id = $folder_id + AND t.msg_id = p.msg_id + AND p.msg_id = $message_id"; + $result = $db->sql_query_limit($sql, 1); + $message_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $_types = array('u', 'g'); + foreach ($_types as $ug_type) + { + if (isset($address_temp[$message_id][$ug_type]) && count($address_temp[$message_id][$ug_type])) + { + if (!isset($address[$message_id][$ug_type])) + { + $address[$message_id][$ug_type] = array(); + } + if ($ug_type == 'u') + { + $sql = 'SELECT user_id as id, username as name + FROM ' . USERS_TABLE . ' + WHERE '; + } + else + { + $sql = 'SELECT group_id as id, group_name as name + FROM ' . GROUPS_TABLE . ' + WHERE '; + } + $sql .= $db->sql_in_set(($ug_type == 'u') ? 'user_id' : 'group_id', array_map('intval', array_keys($address_temp[$message_id][$ug_type]))); + + $result = $db->sql_query($sql); + + while ($info_row = $db->sql_fetchrow($result)) + { + $address[$message_id][$ug_type][$address_temp[$message_id][$ug_type][$info_row['id']]][] = $info_row['name']; + unset($address_temp[$message_id][$ug_type][$info_row['id']]); + } + $db->sql_freeresult($result); + } + } + + // There is the chance that all recipients of the message got deleted. To avoid creating + // exports without recipients, we add a bogus "undisclosed recipient". + if (!(isset($address[$message_id]['g']) && count($address[$message_id]['g'])) && + !(isset($address[$message_id]['u']) && count($address[$message_id]['u']))) + { + $address[$message_id]['u'] = array(); + $address[$message_id]['u']['to'] = array(); + $address[$message_id]['u']['to'][] = $user->lang['UNDISCLOSED_RECIPIENT']; + } + + decode_message($message_row['message_text'], $message_row['bbcode_uid']); + + $data[] = array( + 'subject' => censor_text($row['message_subject']), + 'sender' => $row['username'], + // ISO 8601 date. For PHP4 we are able to hardcode the timezone because $user->format_date() does not set it. + 'date' => $user->format_date($row['message_time'], 'c', true), + 'to' => ($folder_id == PRIVMSGS_OUTBOX || $folder_id == PRIVMSGS_SENTBOX) ? $address[$message_id] : '', + 'message' => $message_row['message_text'] + ); + } + + switch ($export_type) + { + case 'CSV': + case 'CSV_EXCEL': + $mimetype = 'text/csv'; + $filetype = 'csv'; + + if ($export_type == 'CSV_EXCEL') + { + $enclosure = '"'; + $delimiter = ','; + $newline = "\r\n"; + } + else + { + $newline = "\n"; + } + + $string = ''; + foreach ($data as $value) + { + $recipients = $value['to']; + $value['to'] = $value['bcc'] = ''; + + if (is_array($recipients)) + { + foreach ($recipients as $values) + { + $value['bcc'] .= (isset($values['bcc']) && is_array($values['bcc'])) ? ',' . implode(',', $values['bcc']) : ''; + $value['to'] .= (isset($values['to']) && is_array($values['to'])) ? ',' . implode(',', $values['to']) : ''; + } + + // Remove the commas which will appear before the first entry. + $value['to'] = substr($value['to'], 1); + $value['bcc'] = substr($value['bcc'], 1); + } + + foreach ($value as $tag => $text) + { + $cell = str_replace($enclosure, $enclosure . $enclosure, $text); + + if (strpos($cell, $enclosure) !== false || strpos($cell, $delimiter) !== false || strpos($cell, $newline) !== false) + { + $string .= $enclosure . $text . $enclosure . $delimiter; + } + else + { + $string .= $cell . $delimiter; + } + } + $string = substr($string, 0, -1) . $newline; + } + break; + + case 'XML': + $mimetype = 'application/xml'; + $filetype = 'xml'; + $string = '' . "\n"; + $string .= "\n"; + + foreach ($data as $value) + { + $string .= "\t\n"; + + if (is_array($value['to'])) + { + foreach ($value['to'] as $key => $values) + { + foreach ($values as $type => $types) + { + foreach ($types as $name) + { + $string .= "\t\t$name\n"; + } + } + } + } + + unset($value['to']); + + foreach ($value as $tag => $text) + { + $string .= "\t\t<$tag>$text\n"; + } + + $string .= "\t\n"; + } + $string .= ''; + break; + } + + header('Cache-Control: private, no-cache'); + header("Content-Type: $mimetype; name=\"data.$filetype\""); + header("Content-disposition: attachment; filename=data.$filetype"); + echo $string; + exit; + } + } +} + +/** +* Get Messages from folder/user +*/ +function get_pm_from($folder_id, $folder, $user_id) +{ + global $user, $db, $template, $config, $auth, $phpbb_container, $phpbb_root_path, $phpEx, $request, $phpbb_dispatcher; + + $start = $request->variable('start', 0); + + // Additional vars later, pm ordering is mostly different from post ordering. :/ + $sort_days = $request->variable('st', 0); + $sort_key = $request->variable('sk', 't'); + $sort_dir = $request->variable('sd', 'd'); + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + // PM ordering options + $limit_days = array(0 => $user->lang['ALL_MESSAGES'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + + // No sort by Author for sentbox/outbox (already only author available) + // Also, sort by msg_id for the time - private messages are not as prone to errors as posts are. + if ($folder_id == PRIVMSGS_OUTBOX || $folder_id == PRIVMSGS_SENTBOX) + { + $sort_by_text = array('t' => $user->lang['POST_TIME'], 's' => $user->lang['SUBJECT']); + $sort_by_sql = array('t' => 'p.message_time', 's' => array('p.message_subject', 'p.message_time')); + } + else + { + $sort_by_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 's' => $user->lang['SUBJECT']); + $sort_by_sql = array('a' => array('u.username_clean', 'p.message_time'), 't' => 'p.message_time', 's' => array('p.message_subject', 'p.message_time')); + } + + $s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param); + + $folder_sql = 't.folder_id = ' . (int) $folder_id; + + // Limit pms to certain time frame, obtain correct pm count + if ($sort_days) + { + $min_post_time = time() - ($sort_days * 86400); + + if (isset($_POST['sort'])) + { + $start = 0; + } + + $sql = 'SELECT COUNT(t.msg_id) AS pm_count + FROM ' . PRIVMSGS_TO_TABLE . ' t, ' . PRIVMSGS_TABLE . " p + WHERE $folder_sql + AND t.user_id = $user_id + AND t.msg_id = p.msg_id + AND p.message_time >= $min_post_time"; + $result = $db->sql_query_limit($sql, 1); + $pm_count = (int) $db->sql_fetchfield('pm_count'); + $db->sql_freeresult($result); + + $sql_limit_time = "AND p.message_time >= $min_post_time"; + } + else + { + $pm_count = (!empty($folder[$folder_id]['num_messages'])) ? $folder[$folder_id]['num_messages'] : 0; + $sql_limit_time = ''; + } + + $base_url = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=view&action=view_folder&f=$folder_id&$u_sort_param"); + $start = $pagination->validate_start($start, $config['topics_per_page'], $pm_count); + $pagination->generate_template_pagination($base_url, 'pagination', 'start', $pm_count, $config['topics_per_page'], $start); + + $template_vars = array( + 'TOTAL_MESSAGES' => $user->lang('VIEW_PM_MESSAGES', (int) $pm_count), + + 'POST_IMG' => (!$auth->acl_get('u_sendpm')) ? $user->img('button_topic_locked', 'POST_PM_LOCKED') : $user->img('button_pm_new', 'POST_NEW_PM'), + + 'S_NO_AUTH_SEND_MESSAGE' => !$auth->acl_get('u_sendpm'), + + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DAYS' => $s_limit_days, + 'S_TOPIC_ICONS' => ($config['enable_pm_icons']) ? true : false, + + 'U_POST_NEW_TOPIC' => ($auth->acl_get('u_sendpm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose') : '', + 'S_PM_ACTION' => append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&mode=view&action=view_folder&f=$folder_id" . (($start !== 0) ? "&start=$start" : '')), + ); + + /** + * Modify template variables before they are assigned + * + * @event core.ucp_pm_view_folder_get_pm_from_template + * @var int folder_id Folder ID + * @var array folder Folder data + * @var int user_id User ID + * @var string base_url Pagination base URL + * @var int start Pagination start + * @var int pm_count Count of PMs + * @var array template_vars Template variables to be assigned + * @since 3.1.11-RC1 + */ + $vars = array( + 'folder_id', + 'folder', + 'user_id', + 'base_url', + 'start', + 'pm_count', + 'template_vars', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_view_folder_get_pm_from_template', compact($vars))); + + $template->assign_vars($template_vars); + + // Grab all pm data + $rowset = $pm_list = array(); + + // If the user is trying to reach late pages, start searching from the end + $store_reverse = false; + $sql_limit = $config['topics_per_page']; + if ($start > $pm_count / 2) + { + $store_reverse = true; + + // Select the sort order + $direction = ($sort_dir == 'd') ? 'ASC' : 'DESC'; + $sql_limit = $pagination->reverse_limit($start, $sql_limit, $pm_count); + $sql_start = $pagination->reverse_start($start, $sql_limit, $pm_count); + } + else + { + // Select the sort order + $direction = ($sort_dir == 'd') ? 'DESC' : 'ASC'; + $sql_start = $start; + } + + // Sql sort order + if (is_array($sort_by_sql[$sort_key])) + { + $sql_sort_order = implode(' ' . $direction . ', ', $sort_by_sql[$sort_key]) . ' ' . $direction; + } + else + { + $sql_sort_order = $sort_by_sql[$sort_key] . ' ' . $direction; + } + + $sql_ary = array( + 'SELECT' => 't.*, p.root_level, p.message_time, p.message_subject, p.icon_id, p.to_address, p.message_attachment, p.bcc_address, u.username, u.username_clean, u.user_colour, p.message_reported', + 'FROM' => array( + PRIVMSGS_TO_TABLE => 't', + PRIVMSGS_TABLE => 'p', + USERS_TABLE => 'u', + ), + 'WHERE' => "t.user_id = $user_id + AND p.author_id = u.user_id + AND $folder_sql + AND t.msg_id = p.msg_id + $sql_limit_time", + 'ORDER_BY' => $sql_sort_order, + ); + + /** + * Modify SQL before it is executed + * + * @event core.ucp_pm_view_folder_get_pm_from_sql + * @var array sql_ary SQL array + * @var int sql_limit SQL limit + * @var int sql_start SQL start + * @since 3.1.11-RC1 + */ + $vars = array( + 'sql_ary', + 'sql_limit', + 'sql_start', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_view_folder_get_pm_from_sql', compact($vars))); + + $result = $db->sql_query_limit($db->sql_build_query('SELECT', $sql_ary), $sql_limit, $sql_start); + + $pm_reported = array(); + while ($row = $db->sql_fetchrow($result)) + { + $rowset[$row['msg_id']] = $row; + $pm_list[] = $row['msg_id']; + if ($row['message_reported']) + { + $pm_reported[] = $row['msg_id']; + } + } + $db->sql_freeresult($result); + + // Fetch the report_ids, if there are any reported pms. + if (!empty($pm_reported) && $auth->acl_getf_global('m_report')) + { + $sql = 'SELECT pm_id, report_id + FROM ' . REPORTS_TABLE . ' + WHERE report_closed = 0 + AND ' . $db->sql_in_set('pm_id', $pm_reported); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $rowset[$row['pm_id']]['report_id'] = $row['report_id']; + } + $db->sql_freeresult($result); + } + + $pm_list = ($store_reverse) ? array_reverse($pm_list) : $pm_list; + + return array( + 'pm_count' => $pm_count, + 'pm_list' => $pm_list, + 'rowset' => $rowset + ); +} diff --git a/includes/ucp/ucp_pm_viewmessage.php b/includes/ucp/ucp_pm_viewmessage.php new file mode 100644 index 0000000..7c0091e --- /dev/null +++ b/includes/ucp/ucp_pm_viewmessage.php @@ -0,0 +1,449 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* View private message +*/ +function view_message($id, $mode, $folder_id, $msg_id, $folder, $message_row) +{ + global $user, $template, $auth, $db, $phpbb_container; + global $phpbb_root_path, $request, $phpEx, $config, $phpbb_dispatcher; + + $user->add_lang(array('viewtopic', 'memberlist')); + + $msg_id = (int) $msg_id; + $folder_id = (int) $folder_id; + $author_id = (int) $message_row['author_id']; + $view = $request->variable('view', ''); + + // Not able to view message, it was deleted by the sender + if ($message_row['pm_deleted']) + { + $meta_info = append_sid("{$phpbb_root_path}ucp.$phpEx", "i=pm&folder=$folder_id"); + $message = $user->lang['NO_AUTH_READ_REMOVED_MESSAGE']; + + $message .= '

' . sprintf($user->lang['RETURN_FOLDER'], '', ''); + send_status_line(403, 'Forbidden'); + trigger_error($message); + } + + // Do not allow hold messages to be seen + if ($folder_id == PRIVMSGS_HOLD_BOX) + { + trigger_error('NO_AUTH_READ_HOLD_MESSAGE'); + } + + // Load the custom profile fields + if ($config['load_cpf_pm']) + { + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + + $profile_fields = $cp->grab_profile_fields_data($author_id); + } + + // Assign TO/BCC Addresses to template + write_pm_addresses(array('to' => $message_row['to_address'], 'bcc' => $message_row['bcc_address']), $author_id); + + $user_info = get_user_information($author_id, $message_row); + + // Parse the message and subject + $parse_flags = ($message_row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($message_row['message_text'], $message_row['bbcode_uid'], $message_row['bbcode_bitfield'], $parse_flags, true); + + // Replace naughty words such as farty pants + $message_row['message_subject'] = censor_text($message_row['message_subject']); + + // Editing information + if ($message_row['message_edit_count'] && $config['display_last_edited']) + { + if (!$message_row['message_edit_user']) + { + $display_username = get_username_string('full', $author_id, $user_info['username'], $user_info['user_colour']); + } + else + { + $edit_user_info = get_user_information($message_row['message_edit_user'], false); + $display_username = get_username_string('full', $message_row['message_edit_user'], $edit_user_info['username'], $edit_user_info['user_colour']); + } + $l_edited_by = '

' . $user->lang('EDITED_TIMES_TOTAL', (int) $message_row['message_edit_count'], $display_username, $user->format_date($message_row['message_edit_time'], false, true)); + } + else + { + $l_edited_by = ''; + } + + // Pull attachment data + $display_notice = false; + $attachments = array(); + + if ($message_row['message_attachment'] && $config['allow_pm_attach']) + { + if ($auth->acl_get('u_pm_download')) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . " + WHERE post_msg_id = $msg_id + AND in_message = 1 + ORDER BY filetime DESC, post_msg_id ASC"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[] = $row; + } + $db->sql_freeresult($result); + + // No attachments exist, but message table thinks they do so go ahead and reset attach flags + if (!count($attachments)) + { + $sql = 'UPDATE ' . PRIVMSGS_TABLE . " + SET message_attachment = 0 + WHERE msg_id = $msg_id"; + $db->sql_query($sql); + } + } + else + { + $display_notice = true; + } + } + + // Assign inline attachments + if (!empty($attachments)) + { + $update_count = array(); + parse_attachments(false, $message, $attachments, $update_count); + + // Update the attachment download counts + if (count($update_count)) + { + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET download_count = download_count + 1 + WHERE ' . $db->sql_in_set('attach_id', array_unique($update_count)); + $db->sql_query($sql); + } + } + + $user_info['sig'] = ''; + + $signature = ($message_row['enable_sig'] && $config['allow_sig'] && $auth->acl_get('u_sig') && $user->optionget('viewsigs')) ? $user_info['user_sig'] : ''; + + // End signature parsing, only if needed + if ($signature) + { + $parse_flags = ($user_info['user_sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $signature = generate_text_for_display($signature, $user_info['user_sig_bbcode_uid'], $user_info['user_sig_bbcode_bitfield'], $parse_flags, true); + } + + $url = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm'); + + // Number of "to" recipients + $num_recipients = (int) preg_match_all('/:?(u|g)_([0-9]+):?/', $message_row['to_address'], $match); + + $bbcode_status = ($config['allow_bbcode'] && $config['auth_bbcode_pm'] && $auth->acl_get('u_pm_bbcode')) ? true : false; + + // Get the profile fields template data + $cp_row = array(); + if ($config['load_cpf_pm'] && isset($profile_fields[$author_id])) + { + // Filter the fields we don't want to show + foreach ($profile_fields[$author_id] as $used_ident => $profile_field) + { + if (!$profile_field['data']['field_show_on_pm']) + { + unset($profile_fields[$author_id][$used_ident]); + } + } + + if (isset($profile_fields[$author_id])) + { + $cp_row = $cp->generate_profile_fields_template_data($profile_fields[$author_id]); + } + } + + $u_pm = $u_jabber = ''; + + if ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && ($user_info['user_allow_pm'] || $auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'))) + { + $u_pm = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $author_id); + } + + if ($config['jab_enable'] && $user_info['user_jabber'] && $auth->acl_get('u_sendim')) + { + $u_jabber = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contact&action=jabber&u=' . $author_id); + } + + $msg_data = array( + 'MESSAGE_AUTHOR_FULL' => get_username_string('full', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), + 'MESSAGE_AUTHOR_COLOUR' => get_username_string('colour', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), + 'MESSAGE_AUTHOR' => get_username_string('username', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), + 'U_MESSAGE_AUTHOR' => get_username_string('profile', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username']), + + 'RANK_TITLE' => $user_info['rank_title'], + 'RANK_IMG' => $user_info['rank_image'], + 'AUTHOR_AVATAR' => (isset($user_info['avatar'])) ? $user_info['avatar'] : '', + 'AUTHOR_JOINED' => $user->format_date($user_info['user_regdate']), + 'AUTHOR_POSTS' => (int) $user_info['user_posts'], + 'U_AUTHOR_POSTS' => ($config['load_search'] && $auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id=$author_id&sr=posts") : '', + 'CONTACT_USER' => $user->lang('CONTACT_USER', get_username_string('username', $author_id, $user_info['username'], $user_info['user_colour'], $user_info['username'])), + + 'ONLINE_IMG' => (!$config['load_onlinetrack']) ? '' : ((isset($user_info['online']) && $user_info['online']) ? $user->img('icon_user_online', $user->lang['ONLINE']) : $user->img('icon_user_offline', $user->lang['OFFLINE'])), + 'S_ONLINE' => (!$config['load_onlinetrack']) ? false : ((isset($user_info['online']) && $user_info['online']) ? true : false), + 'DELETE_IMG' => $user->img('icon_post_delete', $user->lang['DELETE_MESSAGE']), + 'INFO_IMG' => $user->img('icon_post_info', $user->lang['VIEW_PM_INFO']), + 'PROFILE_IMG' => $user->img('icon_user_profile', $user->lang['READ_PROFILE']), + 'EMAIL_IMG' => $user->img('icon_contact_email', $user->lang['SEND_EMAIL']), + 'QUOTE_IMG' => $user->img('icon_post_quote', $user->lang['POST_QUOTE_PM']), + 'REPLY_IMG' => $user->img('button_pm_reply', $user->lang['POST_REPLY_PM']), + 'REPORT_IMG' => $user->img('icon_post_report', 'REPORT_PM'), + 'EDIT_IMG' => $user->img('icon_post_edit', $user->lang['POST_EDIT_PM']), + 'MINI_POST_IMG' => $user->img('icon_post_target', $user->lang['PM']), + + 'SENT_DATE' => ($view == 'print') ? $user->format_date($message_row['message_time'], false, true) : $user->format_date($message_row['message_time']), + 'SUBJECT' => $message_row['message_subject'], + 'MESSAGE' => $message, + 'SIGNATURE' => ($message_row['enable_sig']) ? $signature : '', + 'EDITED_MESSAGE' => $l_edited_by, + 'MESSAGE_ID' => $message_row['msg_id'], + + 'U_PM' => $u_pm, + 'U_JABBER' => $u_jabber, + + 'U_DELETE' => ($auth->acl_get('u_pm_delete')) ? "$url&mode=compose&action=delete&f=$folder_id&p=" . $message_row['msg_id'] : '', + 'U_EMAIL' => $user_info['email'], + 'U_REPORT' => ($config['allow_pm_report']) ? $phpbb_container->get('controller.helper')->route('phpbb_report_pm_controller', array('id' => $message_row['msg_id'])) : '', + 'U_QUOTE' => ($auth->acl_get('u_sendpm') && $author_id != ANONYMOUS) ? "$url&mode=compose&action=quote&f=$folder_id&p=" . $message_row['msg_id'] : '', + 'U_EDIT' => (($message_row['message_time'] > time() - ($config['pm_edit_time'] * 60) || !$config['pm_edit_time']) && $folder_id == PRIVMSGS_OUTBOX && $auth->acl_get('u_pm_edit')) ? "$url&mode=compose&action=edit&f=$folder_id&p=" . $message_row['msg_id'] : '', + 'U_POST_REPLY_PM' => ($auth->acl_get('u_sendpm') && $author_id != ANONYMOUS) ? "$url&mode=compose&action=reply&f=$folder_id&p=" . $message_row['msg_id'] : '', + 'U_POST_REPLY_ALL' => ($auth->acl_get('u_sendpm') && $author_id != ANONYMOUS) ? "$url&mode=compose&action=reply&f=$folder_id&reply_to_all=1&p=" . $message_row['msg_id'] : '', + 'U_PREVIOUS_PM' => "$url&f=$folder_id&p=" . $message_row['msg_id'] . "&view=previous", + 'U_NEXT_PM' => "$url&f=$folder_id&p=" . $message_row['msg_id'] . "&view=next", + + 'U_PM_ACTION' => $url . '&mode=compose&f=' . $folder_id . '&p=' . $message_row['msg_id'], + + 'S_HAS_ATTACHMENTS' => (count($attachments)) ? true : false, + 'S_DISPLAY_NOTICE' => $display_notice && $message_row['message_attachment'], + 'S_AUTHOR_DELETED' => ($author_id == ANONYMOUS) ? true : false, + 'S_SPECIAL_FOLDER' => in_array($folder_id, array(PRIVMSGS_NO_BOX, PRIVMSGS_OUTBOX)), + 'S_PM_RECIPIENTS' => $num_recipients, + 'S_BBCODE_ALLOWED' => ($bbcode_status) ? 1 : 0, + 'S_CUSTOM_FIELDS' => (!empty($cp_row['row'])) ? true : false, + + 'U_PRINT_PM' => ($config['print_pm'] && $auth->acl_get('u_pm_printpm')) ? "$url&f=$folder_id&p=" . $message_row['msg_id'] . "&view=print" : '', + 'U_FORWARD_PM' => ($config['forward_pm'] && $auth->acl_get('u_sendpm') && $auth->acl_get('u_pm_forward')) ? "$url&mode=compose&action=forward&f=$folder_id&p=" . $message_row['msg_id'] : '', + ); + + /** + * Modify pm and sender data before it is assigned to the template + * + * @event core.ucp_pm_view_messsage + * @var mixed id Active module category (can be int or string) + * @var string mode Active module + * @var int folder_id ID of the folder the message is in + * @var int msg_id ID of the private message + * @var array folder Array with data of user's message folders + * @var array message_row Array with message data + * @var array cp_row Array with senders custom profile field data + * @var array msg_data Template array with message data + * @var array user_info User data of the sender + * @since 3.1.0-a1 + * @changed 3.1.6-RC1 Added user_info into event + * @changed 3.2.2-RC1 Deprecated + * @deprecated 4.0.0 Event name is misspelled and is replaced with new event with correct name + */ + $vars = array( + 'id', + 'mode', + 'folder_id', + 'msg_id', + 'folder', + 'message_row', + 'cp_row', + 'msg_data', + 'user_info', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_view_messsage', compact($vars))); + + /** + * Modify pm and sender data before it is assigned to the template + * + * @event core.ucp_pm_view_message + * @var mixed id Active module category (can be int or string) + * @var string mode Active module + * @var int folder_id ID of the folder the message is in + * @var int msg_id ID of the private message + * @var array folder Array with data of user's message folders + * @var array message_row Array with message data + * @var array cp_row Array with senders custom profile field data + * @var array msg_data Template array with message data + * @var array user_info User data of the sender + * @var array attachments Attachments data + * @since 3.2.2-RC1 + * @changed 3.2.5-RC1 Added attachments + */ + $vars = array( + 'id', + 'mode', + 'folder_id', + 'msg_id', + 'folder', + 'message_row', + 'cp_row', + 'msg_data', + 'user_info', + 'attachments', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_pm_view_message', compact($vars))); + + $template->assign_vars($msg_data); + + $contact_fields = array( + array( + 'ID' => 'pm', + 'NAME' => $user->lang['SEND_PRIVATE_MESSAGE'], + 'U_CONTACT' => $u_pm, + ), + array( + 'ID' => 'email', + 'NAME' => $user->lang['SEND_EMAIL'], + 'U_CONTACT' => $user_info['email'], + ), + array( + 'ID' => 'jabber', + 'NAME' => $user->lang['JABBER'], + 'U_CONTACT' => $u_jabber, + ), + ); + + foreach ($contact_fields as $field) + { + if ($field['U_CONTACT']) + { + $template->assign_block_vars('contact', $field); + } + } + + // Display the custom profile fields + if (!empty($cp_row['row'])) + { + $template->assign_vars($cp_row['row']); + + foreach ($cp_row['blockrow'] as $cp_block_row) + { + $template->assign_block_vars('custom_fields', $cp_block_row); + + if ($cp_block_row['S_PROFILE_CONTACT']) + { + $template->assign_block_vars('contact', array( + 'ID' => $cp_block_row['PROFILE_FIELD_IDENT'], + 'NAME' => $cp_block_row['PROFILE_FIELD_NAME'], + 'U_CONTACT' => $cp_block_row['PROFILE_FIELD_CONTACT'], + )); + } + } + } + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (isset($attachments) && count($attachments)) + { + foreach ($attachments as $attachment) + { + $template->assign_block_vars('attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + } + + if (!isset($_REQUEST['view']) || $request->variable('view', '') != 'print') + { + // Message History + if (message_history($msg_id, $user->data['user_id'], $message_row, $folder)) + { + $template->assign_var('S_DISPLAY_HISTORY', true); + } + } +} + +/** +* Get user information (only for message display) +*/ +function get_user_information($user_id, $user_row) +{ + global $db, $auth, $user; + global $phpbb_root_path, $phpEx, $config; + + if (!$user_id) + { + return array(); + } + + if (empty($user_row)) + { + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $user_id; + $result = $db->sql_query($sql); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + // Some standard values + $user_row['online'] = false; + $user_row['rank_title'] = $user_row['rank_image'] = $user_row['rank_image_src'] = $user_row['email'] = ''; + + // Generate online information for user + if ($config['load_onlinetrack']) + { + $sql = 'SELECT session_user_id, MAX(session_time) as online_time, MIN(session_viewonline) AS viewonline + FROM ' . SESSIONS_TABLE . " + WHERE session_user_id = $user_id + GROUP BY session_user_id"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $update_time = $config['load_online_time'] * 60; + if ($row) + { + $user_row['online'] = (time() - $update_time < $row['online_time'] && ($row['viewonline'] || $auth->acl_get('u_viewonline'))) ? true : false; + } + } + + $user_row['avatar'] = ($user->optionget('viewavatars')) ? phpbb_get_user_avatar($user_row) : ''; + + if (!function_exists('phpbb_get_user_rank')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + $user_rank_data = phpbb_get_user_rank($user_row, $user_row['user_posts']); + $user_row['rank_title'] = $user_rank_data['title']; + $user_row['rank_image'] = $user_rank_data['img']; + $user_row['rank_image_src'] = $user_rank_data['img_src']; + + if ((!empty($user_row['user_allow_viewemail']) && $auth->acl_get('u_sendemail')) || $auth->acl_get('a_email')) + { + $user_row['email'] = ($config['board_email_form'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=email&u=$user_id") : ((($config['board_hide_emails'] && !$auth->acl_get('a_email')) || empty($user_row['user_email'])) ? '' : 'mailto:' . $user_row['user_email']); + } + + return $user_row; +} diff --git a/includes/ucp/ucp_prefs.php b/includes/ucp/ucp_prefs.php new file mode 100644 index 0000000..7785aeb --- /dev/null +++ b/includes/ucp/ucp_prefs.php @@ -0,0 +1,537 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_prefs +* Changing user preferences +*/ +class ucp_prefs +{ + var $u_action; + + function main($id, $mode) + { + global $config, $db, $user, $auth, $template, $phpbb_dispatcher, $request; + + $submit = (isset($_POST['submit'])) ? true : false; + $error = $data = array(); + $s_hidden_fields = ''; + + switch ($mode) + { + case 'personal': + add_form_key('ucp_prefs_personal'); + $data = array( + 'notifymethod' => $request->variable('notifymethod', $user->data['user_notify_type']), + 'dateformat' => $request->variable('dateformat', $user->data['user_dateformat'], true), + 'lang' => basename($request->variable('lang', $user->data['user_lang'])), + 'user_style' => $request->variable('user_style', (int) $user->data['user_style']), + 'tz' => $request->variable('tz', $user->data['user_timezone']), + + 'viewemail' => $request->variable('viewemail', (bool) $user->data['user_allow_viewemail']), + 'massemail' => $request->variable('massemail', (bool) $user->data['user_allow_massemail']), + 'hideonline' => $request->variable('hideonline', (bool) !$user->data['user_allow_viewonline']), + 'allowpm' => $request->variable('allowpm', (bool) $user->data['user_allow_pm']), + ); + + if ($data['notifymethod'] == NOTIFY_IM && (!$config['jab_enable'] || !$user->data['user_jabber'] || !@extension_loaded('xml'))) + { + // Jabber isnt enabled, or no jabber field filled in. Update the users table to be sure its correct. + $data['notifymethod'] = NOTIFY_BOTH; + } + + /** + * Add UCP edit global settings data before they are assigned to the template or submitted + * + * To assign data to the template, use $template->assign_vars() + * + * @event core.ucp_prefs_personal_data + * @var bool submit Do we display the form only + * or did the user press submit + * @var array data Array with current ucp options data + * @var array error Array with list of errors + * @since 3.1.0-a1 + * @changed 3.1.4-RC1 Added error variable to the event + */ + $vars = array('submit', 'data', 'error'); + extract($phpbb_dispatcher->trigger_event('core.ucp_prefs_personal_data', compact($vars))); + + if ($submit) + { + if ($config['override_user_style']) + { + $data['user_style'] = (int) $config['default_style']; + } + else if (!phpbb_style_is_active($data['user_style'])) + { + $data['user_style'] = (int) $user->data['user_style']; + } + + $error = array_merge(validate_data($data, array( + 'dateformat' => array('string', false, 1, 64), + 'lang' => array('language_iso_name'), + 'tz' => array('timezone'), + )), $error); + + if (!check_form_key('ucp_prefs_personal')) + { + $error[] = 'FORM_INVALID'; + } + + if (!count($error)) + { + $sql_ary = array( + 'user_allow_pm' => $data['allowpm'], + 'user_allow_viewemail' => $data['viewemail'], + 'user_allow_massemail' => $data['massemail'], + 'user_allow_viewonline' => ($auth->acl_get('u_hideonline')) ? !$data['hideonline'] : $user->data['user_allow_viewonline'], + 'user_notify_type' => $data['notifymethod'], + 'user_options' => $user->data['user_options'], + + 'user_dateformat' => $data['dateformat'], + 'user_lang' => $data['lang'], + 'user_timezone' => $data['tz'], + 'user_style' => $data['user_style'], + ); + + /** + * Update UCP edit global settings data on form submit + * + * @event core.ucp_prefs_personal_update_data + * @var array data Submitted display options data + * @var array sql_ary Display options data we update + * @since 3.1.0-a1 + */ + $vars = array('data', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_prefs_personal_update_data', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + meta_refresh(3, $this->u_action); + $message = $user->lang['PREFERENCES_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + } + + $dateformat_options = ''; + + foreach ($user->lang['dateformats'] as $format => $null) + { + $dateformat_options .= ''; + } + + $s_custom = false; + + $dateformat_options .= ''; + + phpbb_timezone_select($template, $user, $data['tz'], true); + + // check if there are any user-selectable languages + $sql = 'SELECT COUNT(lang_id) as languages_count + FROM ' . LANG_TABLE; + $result = $db->sql_query($sql); + if ($db->sql_fetchfield('languages_count') > 1) + { + $s_more_languages = true; + } + else + { + $s_more_languages = false; + } + $db->sql_freeresult($result); + + // check if there are any user-selectable styles + $sql = 'SELECT COUNT(style_id) as styles_count + FROM ' . STYLES_TABLE . ' + WHERE style_active = 1'; + $result = $db->sql_query($sql); + if ($db->sql_fetchfield('styles_count') > 1) + { + $s_more_styles = true; + } + else + { + $s_more_styles = false; + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'ERROR' => (count($error)) ? implode('
', $error) : '', + + 'S_NOTIFY_EMAIL' => ($data['notifymethod'] == NOTIFY_EMAIL) ? true : false, + 'S_NOTIFY_IM' => ($data['notifymethod'] == NOTIFY_IM) ? true : false, + 'S_NOTIFY_BOTH' => ($data['notifymethod'] == NOTIFY_BOTH) ? true : false, + 'S_VIEW_EMAIL' => $data['viewemail'], + 'S_MASS_EMAIL' => $data['massemail'], + 'S_ALLOW_PM' => $data['allowpm'], + 'S_HIDE_ONLINE' => $data['hideonline'], + + 'DATE_FORMAT' => $data['dateformat'], + 'A_DATE_FORMAT' => addslashes($data['dateformat']), + 'S_DATEFORMAT_OPTIONS' => $dateformat_options, + 'S_CUSTOM_DATEFORMAT' => $s_custom, + 'DEFAULT_DATEFORMAT' => $config['default_dateformat'], + 'A_DEFAULT_DATEFORMAT' => addslashes($config['default_dateformat']), + + 'S_MORE_LANGUAGES' => $s_more_languages, + 'S_MORE_STYLES' => $s_more_styles, + + 'S_LANG_OPTIONS' => language_select($data['lang']), + 'S_STYLE_OPTIONS' => ($config['override_user_style']) ? '' : style_select($data['user_style']), + 'S_CAN_HIDE_ONLINE' => ($auth->acl_get('u_hideonline')) ? true : false, + 'S_SELECT_NOTIFY' => ($config['jab_enable'] && $user->data['user_jabber'] && @extension_loaded('xml')) ? true : false) + ); + + break; + + case 'view': + + add_form_key('ucp_prefs_view'); + + $data = array( + 'topic_sk' => $request->variable('topic_sk', (!empty($user->data['user_topic_sortby_type'])) ? $user->data['user_topic_sortby_type'] : 't'), + 'topic_sd' => $request->variable('topic_sd', (!empty($user->data['user_topic_sortby_dir'])) ? $user->data['user_topic_sortby_dir'] : 'd'), + 'topic_st' => $request->variable('topic_st', (!empty($user->data['user_topic_show_days'])) ? (int) $user->data['user_topic_show_days'] : 0), + + 'post_sk' => $request->variable('post_sk', (!empty($user->data['user_post_sortby_type'])) ? $user->data['user_post_sortby_type'] : 't'), + 'post_sd' => $request->variable('post_sd', (!empty($user->data['user_post_sortby_dir'])) ? $user->data['user_post_sortby_dir'] : 'a'), + 'post_st' => $request->variable('post_st', (!empty($user->data['user_post_show_days'])) ? (int) $user->data['user_post_show_days'] : 0), + + 'images' => $request->variable('images', (bool) $user->optionget('viewimg')), + 'flash' => $request->variable('flash', (bool) $user->optionget('viewflash')), + 'smilies' => $request->variable('smilies', (bool) $user->optionget('viewsmilies')), + 'sigs' => $request->variable('sigs', (bool) $user->optionget('viewsigs')), + 'avatars' => $request->variable('avatars', (bool) $user->optionget('viewavatars')), + 'wordcensor' => $request->variable('wordcensor', (bool) $user->optionget('viewcensors')), + ); + + /** + * Add UCP edit display options data before they are assigned to the template or submitted + * + * To assign data to the template, use $template->assign_vars() + * + * @event core.ucp_prefs_view_data + * @var bool submit Do we display the form only + * or did the user press submit + * @var array data Array with current ucp options data + * @since 3.1.0-a1 + */ + $vars = array('submit', 'data'); + extract($phpbb_dispatcher->trigger_event('core.ucp_prefs_view_data', compact($vars))); + + if ($submit) + { + $error = validate_data($data, array( + 'topic_sk' => array( + array('string', false, 1, 1), + array('match', false, '#(a|r|s|t|v)#'), + ), + 'topic_sd' => array( + array('string', false, 1, 1), + array('match', false, '#(a|d)#'), + ), + 'post_sk' => array( + array('string', false, 1, 1), + array('match', false, '#(a|s|t)#'), + ), + 'post_sd' => array( + array('string', false, 1, 1), + array('match', false, '#(a|d)#'), + ), + )); + + if (!check_form_key('ucp_prefs_view')) + { + $error[] = 'FORM_INVALID'; + } + + if (!count($error)) + { + $user->optionset('viewimg', $data['images']); + $user->optionset('viewflash', $data['flash']); + $user->optionset('viewsmilies', $data['smilies']); + $user->optionset('viewsigs', $data['sigs']); + $user->optionset('viewavatars', $data['avatars']); + + if ($auth->acl_get('u_chgcensors')) + { + $user->optionset('viewcensors', $data['wordcensor']); + } + + $sql_ary = array( + 'user_options' => $user->data['user_options'], + 'user_topic_sortby_type' => $data['topic_sk'], + 'user_post_sortby_type' => $data['post_sk'], + 'user_topic_sortby_dir' => $data['topic_sd'], + 'user_post_sortby_dir' => $data['post_sd'], + + 'user_topic_show_days' => $data['topic_st'], + 'user_post_show_days' => $data['post_st'], + ); + + /** + * Update UCP edit display options data on form submit + * + * @event core.ucp_prefs_view_update_data + * @var array data Submitted display options data + * @var array sql_ary Display options data we update + * @since 3.1.0-a1 + */ + $vars = array('data', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_prefs_view_update_data', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + meta_refresh(3, $this->u_action); + $message = $user->lang['PREFERENCES_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + } + + $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']); + + // Topic ordering options + $limit_topic_days = array(0 => $user->lang['ALL_TOPICS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + + $sort_by_topic_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 'r' => $user->lang['REPLIES'], 's' => $user->lang['SUBJECT'], 'v' => $user->lang['VIEWS']); + $sort_by_topic_sql = array('a' => 't.topic_first_poster_name', 't' => array('t.topic_last_post_time', 't.topic_last_post_id'), 'r' => 't.topic_posts_approved', 's' => 't.topic_title', 'v' => 't.topic_views'); + + // Post ordering options + $limit_post_days = array(0 => $user->lang['ALL_POSTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + + $sort_by_post_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 's' => $user->lang['SUBJECT']); + $sort_by_post_sql = array('a' => 'u.username_clean', 't' => 'p.post_id', 's' => 'p.post_subject'); + + $_options = array('topic', 'post'); + foreach ($_options as $sort_option) + { + ${'s_limit_' . $sort_option . '_days'} = ''; + + ${'s_sort_' . $sort_option . '_key'} = ''; + + ${'s_sort_' . $sort_option . '_dir'} = ''; + } + + /** + * Run code before view form is displayed + * + * @event core.ucp_prefs_view_after + * @var bool submit Do we display the form only + * or did the user press submit + * @var array data Array with current ucp options data + * @var array sort_dir_text Array with sort dir language strings + * @var array limit_topic_days Topic ordering options + * @var array sort_by_topic_text Topic ordering language strings + * @var array sort_by_topic_sql Topic ordering sql + * @var array limit_post_days Post ordering options + * @var array sort_by_post_text Post ordering language strings + * @var array sort_by_post_sql Post ordering sql + * @var array _options Sort options + * @var string s_limit_topic_days Sort limit topic by days select box + * @var string s_sort_topic_key Sort topic key select box + * @var string s_sort_topic_dir Sort topic dir select box + * @var string s_limit_post_days Sort limit post by days select box + * @var string s_sort_post_key Sort post key select box + * @var string s_sort_post_dir Sort post dir select box + * @since 3.1.8-RC1 + */ + $vars = array( + 'submit', + 'data', + 'sort_dir_text', + 'limit_topic_days', + 'sort_by_topic_text', + 'sort_by_topic_sql', + 'limit_post_days', + 'sort_by_post_text', + 'sort_by_post_sql', + '_options', + 's_limit_topic_days', + 's_sort_topic_key', + 's_sort_topic_dir', + 's_limit_post_days', + 's_sort_post_key', + 's_sort_post_dir', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_prefs_view_after', compact($vars))); + + $template->assign_vars(array( + 'ERROR' => (count($error)) ? implode('
', $error) : '', + + 'S_IMAGES' => $data['images'], + 'S_FLASH' => $data['flash'], + 'S_SMILIES' => $data['smilies'], + 'S_SIGS' => $data['sigs'], + 'S_AVATARS' => $data['avatars'], + 'S_DISABLE_CENSORS' => $data['wordcensor'], + + 'S_CHANGE_CENSORS' => ($auth->acl_get('u_chgcensors') && $config['allow_nocensors']) ? true : false, + + 'S_TOPIC_SORT_DAYS' => $s_limit_topic_days, + 'S_TOPIC_SORT_KEY' => $s_sort_topic_key, + 'S_TOPIC_SORT_DIR' => $s_sort_topic_dir, + 'S_POST_SORT_DAYS' => $s_limit_post_days, + 'S_POST_SORT_KEY' => $s_sort_post_key, + 'S_POST_SORT_DIR' => $s_sort_post_dir) + ); + + break; + + case 'post': + + $data = array( + 'bbcode' => $request->variable('bbcode', $user->optionget('bbcode')), + 'smilies' => $request->variable('smilies', $user->optionget('smilies')), + 'sig' => $request->variable('sig', $user->optionget('attachsig')), + 'notify' => $request->variable('notify', (bool) $user->data['user_notify']), + ); + add_form_key('ucp_prefs_post'); + + /** + * Add UCP edit posting defaults data before they are assigned to the template or submitted + * + * To assign data to the template, use $template->assign_vars() + * + * @event core.ucp_prefs_post_data + * @var bool submit Do we display the form only + * or did the user press submit + * @var array data Array with current ucp options data + * @since 3.1.0-a1 + */ + $vars = array('submit', 'data'); + extract($phpbb_dispatcher->trigger_event('core.ucp_prefs_post_data', compact($vars))); + + if ($submit) + { + if (check_form_key('ucp_prefs_post')) + { + $user->optionset('bbcode', $data['bbcode']); + $user->optionset('smilies', $data['smilies']); + $user->optionset('attachsig', $data['sig']); + + $sql_ary = array( + 'user_options' => $user->data['user_options'], + 'user_notify' => $data['notify'], + ); + + /** + * Update UCP edit posting defaults data on form submit + * + * @event core.ucp_prefs_post_update_data + * @var array data Submitted display options data + * @var array sql_ary Display options data we update + * @since 3.1.0-a1 + */ + $vars = array('data', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_prefs_post_update_data', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $msg = $user->lang['PREFERENCES_UPDATED']; + } + else + { + $msg = $user->lang['FORM_INVALID']; + } + meta_refresh(3, $this->u_action); + $message = $msg . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + + $template->assign_vars(array( + 'S_BBCODE' => $data['bbcode'], + 'S_SMILIES' => $data['smilies'], + 'S_SIG' => $data['sig'], + 'S_NOTIFY' => $data['notify']) + ); + break; + } + + /** + * Modify UCP preferences data before the page load + * + * @event core.ucp_prefs_modify_common + * @var array data Array with current/submitted UCP options data + * @var array error Errors data + * @var string mode UCP prefs operation mode + * @var string s_hidden_fields Hidden fields data + * @since 3.1.0-RC3 + */ + $vars = array( + 'data', + 'error', + 'mode', + 's_hidden_fields', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_prefs_modify_common', compact($vars))); + + $template->assign_vars(array( + 'L_TITLE' => $user->lang['UCP_PREFS_' . strtoupper($mode)], + + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + 'S_UCP_ACTION' => $this->u_action) + ); + + $this->tpl_name = 'ucp_prefs_' . $mode; + $this->page_title = 'UCP_PREFS_' . strtoupper($mode); + } +} diff --git a/includes/ucp/ucp_profile.php b/includes/ucp/ucp_profile.php new file mode 100644 index 0000000..9a12840 --- /dev/null +++ b/includes/ucp/ucp_profile.php @@ -0,0 +1,847 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_profile +* Changing profile settings +* +* @todo what about pertaining user_sig_options? +*/ +class ucp_profile +{ + var $u_action; + + function main($id, $mode) + { + global $config, $db, $user, $auth, $template, $phpbb_root_path, $phpEx; + global $request, $phpbb_container, $phpbb_log, $phpbb_dispatcher; + + $user->add_lang('posting'); + + $submit = $request->variable('submit', false, false, \phpbb\request\request_interface::POST); + $error = $data = array(); + $s_hidden_fields = ''; + + switch ($mode) + { + case 'reg_details': + + $data = array( + 'username' => $request->variable('username', $user->data['username'], true), + 'email' => strtolower($request->variable('email', $user->data['user_email'])), + 'new_password' => $request->variable('new_password', '', true), + 'cur_password' => $request->variable('cur_password', '', true), + 'password_confirm' => $request->variable('password_confirm', '', true), + ); + + /** + * Modify user registration data on editing account settings in UCP + * + * @event core.ucp_profile_reg_details_data + * @var array data Array with current or updated user registration data + * @var bool submit Flag indicating if submit button has been pressed + * @since 3.1.4-RC1 + */ + $vars = array('data', 'submit'); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_reg_details_data', compact($vars))); + + add_form_key('ucp_reg_details'); + + if ($submit) + { + // Do not check cur_password, it is the old one. + $check_ary = array( + 'new_password' => array( + array('string', true, $config['min_pass_chars'], $config['max_pass_chars']), + array('password')), + 'password_confirm' => array('string', true, $config['min_pass_chars'], $config['max_pass_chars']), + 'email' => array( + array('string', false, 6, 60), + array('user_email')), + ); + + if ($auth->acl_get('u_chgname') && $config['allow_namechange']) + { + $check_ary['username'] = array( + array('string', false, $config['min_name_chars'], $config['max_name_chars']), + array('username'), + ); + } + + $error = validate_data($data, $check_ary); + + if ($auth->acl_get('u_chgpasswd') && $data['new_password'] && $data['password_confirm'] != $data['new_password']) + { + $error[] = ($data['password_confirm']) ? 'NEW_PASSWORD_ERROR' : 'NEW_PASSWORD_CONFIRM_EMPTY'; + } + + // Instantiate passwords manager + /* @var $passwords_manager \phpbb\passwords\manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + + // Only check the new password against the previous password if there have been no errors + if (!count($error) && $auth->acl_get('u_chgpasswd') && $data['new_password'] && $passwords_manager->check($data['new_password'], $user->data['user_password'])) + { + $error[] = 'SAME_PASSWORD_ERROR'; + } + + if (!$passwords_manager->check($data['cur_password'], $user->data['user_password'])) + { + $error[] = ($data['cur_password']) ? 'CUR_PASSWORD_ERROR' : 'CUR_PASSWORD_EMPTY'; + } + + if (!check_form_key('ucp_reg_details')) + { + $error[] = 'FORM_INVALID'; + } + + /** + * Validate user data on editing registration data in UCP + * + * @event core.ucp_profile_reg_details_validate + * @var array data Array with user profile data + * @var bool submit Flag indicating if submit button has been pressed + * @var array error Array of any generated errors + * @since 3.1.4-RC1 + */ + $vars = array('data', 'submit', 'error'); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_reg_details_validate', compact($vars))); + + if (!count($error)) + { + $sql_ary = array( + 'username' => ($auth->acl_get('u_chgname') && $config['allow_namechange']) ? $data['username'] : $user->data['username'], + 'username_clean' => ($auth->acl_get('u_chgname') && $config['allow_namechange']) ? utf8_clean_string($data['username']) : $user->data['username_clean'], + 'user_email' => ($auth->acl_get('u_chgemail')) ? $data['email'] : $user->data['user_email'], + 'user_email_hash' => ($auth->acl_get('u_chgemail')) ? phpbb_email_hash($data['email']) : $user->data['user_email_hash'], + 'user_password' => ($auth->acl_get('u_chgpasswd') && $data['new_password']) ? $passwords_manager->hash($data['new_password']) : $user->data['user_password'], + 'user_passchg' => ($auth->acl_get('u_chgpasswd') && $data['new_password']) ? time() : 0, + ); + + if ($auth->acl_get('u_chgname') && $config['allow_namechange'] && $data['username'] != $user->data['username']) + { + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_UPDATE_NAME', false, array( + 'reportee_id' => $user->data['user_id'], + $user->data['username'], + $data['username'] + )); + } + + if ($auth->acl_get('u_chgpasswd') && $data['new_password'] && !$passwords_manager->check($data['new_password'], $user->data['user_password'])) + { + $user->reset_login_keys(); + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_NEW_PASSWORD', false, array( + 'reportee_id' => $user->data['user_id'], + $user->data['username'] + )); + } + + if ($auth->acl_get('u_chgemail') && $data['email'] != $user->data['user_email']) + { + $phpbb_log->add('user', $user->data['user_id'], $user->ip, 'LOG_USER_UPDATE_EMAIL', false, array( + 'reportee_id' => $user->data['user_id'], + $user->data['username'], + $user->data['user_email'], + $data['email'] + )); + } + + $message = 'PROFILE_UPDATED'; + + if ($auth->acl_get('u_chgemail') && $config['email_enable'] && $data['email'] != $user->data['user_email'] && $user->data['user_type'] != USER_FOUNDER && ($config['require_activation'] == USER_ACTIVATION_SELF || $config['require_activation'] == USER_ACTIVATION_ADMIN)) + { + $message = ($config['require_activation'] == USER_ACTIVATION_SELF) ? 'ACCOUNT_EMAIL_CHANGED' : 'ACCOUNT_EMAIL_CHANGED_ADMIN'; + + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $server_url = generate_board_url(); + + $user_actkey = gen_rand_string(mt_rand(6, 10)); + + $messenger = new messenger(false); + + $template_file = ($config['require_activation'] == USER_ACTIVATION_ADMIN) ? 'user_activate_inactive' : 'user_activate'; + $messenger->template($template_file, $user->data['user_lang']); + + $messenger->to($data['email'], $data['username']); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($data['username']), + 'U_ACTIVATE' => "$server_url/ucp.$phpEx?mode=activate&u={$user->data['user_id']}&k=$user_actkey") + ); + + $messenger->send(NOTIFY_EMAIL); + + if ($config['require_activation'] == USER_ACTIVATION_ADMIN) + { + $notifications_manager = $phpbb_container->get('notification_manager'); + $notifications_manager->add_notifications('notification.type.admin_activate_user', array( + 'user_id' => $user->data['user_id'], + 'user_actkey' => $user_actkey, + 'user_regdate' => time(), // Notification time + )); + } + + user_active_flip('deactivate', $user->data['user_id'], INACTIVE_PROFILE); + + // Because we want the profile to be reactivated we set user_newpasswd to empty (else the reactivation will fail) + $sql_ary['user_actkey'] = $user_actkey; + $sql_ary['user_newpasswd'] = ''; + } + + /** + * Modify user registration data before submitting it to the database + * + * @event core.ucp_profile_reg_details_sql_ary + * @var array data Array with current or updated user registration data + * @var array sql_ary Array with user registration data to submit to the database + * @since 3.1.4-RC1 + */ + $vars = array('data', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_reg_details_sql_ary', compact($vars))); + + if (count($sql_ary)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + } + + // Need to update config, forum, topic, posting, messages, etc. + if ($data['username'] != $user->data['username'] && $auth->acl_get('u_chgname') && $config['allow_namechange']) + { + user_update_name($user->data['username'], $data['username']); + } + + // Now, we can remove the user completely (kill the session) - NOT BEFORE!!! + if (!empty($sql_ary['user_actkey'])) + { + meta_refresh(5, append_sid($phpbb_root_path . 'index.' . $phpEx)); + $message = $user->lang[$message] . '

' . sprintf($user->lang['RETURN_INDEX'], '', ''); + + // Because the user gets deactivated we log him out too, killing his session + $user->session_kill(); + } + else + { + meta_refresh(3, $this->u_action); + $message = $user->lang[$message] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + } + + trigger_error($message); + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + } + + $template->assign_vars(array( + 'ERROR' => (count($error)) ? implode('
', $error) : '', + + 'USERNAME' => $data['username'], + 'EMAIL' => $data['email'], + 'PASSWORD_CONFIRM' => $data['password_confirm'], + 'NEW_PASSWORD' => $data['new_password'], + 'CUR_PASSWORD' => '', + + 'L_USERNAME_EXPLAIN' => $user->lang($config['allow_name_chars'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_name_chars']), $user->lang('CHARACTERS', (int) $config['max_name_chars'])), + 'L_CHANGE_PASSWORD_EXPLAIN' => $user->lang($config['pass_complex'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_pass_chars']), $user->lang('CHARACTERS', (int) $config['max_pass_chars'])), + + 'S_FORCE_PASSWORD' => ($auth->acl_get('u_chgpasswd') && $config['chg_passforce'] && $user->data['user_passchg'] < time() - ($config['chg_passforce'] * 86400)) ? true : false, + 'S_CHANGE_USERNAME' => ($config['allow_namechange'] && $auth->acl_get('u_chgname')) ? true : false, + 'S_CHANGE_EMAIL' => ($auth->acl_get('u_chgemail')) ? true : false, + 'S_CHANGE_PASSWORD' => ($auth->acl_get('u_chgpasswd')) ? true : false) + ); + break; + + case 'profile_info': + // Do not display profile information panel if not authed to do so + if (!$auth->acl_get('u_chgprofileinfo')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_PROFILEINFO'); + } + + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + + $cp_data = $cp_error = array(); + + $data = array( + 'jabber' => $request->variable('jabber', $user->data['user_jabber'], true), + ); + + if ($config['allow_birthdays']) + { + $data['bday_day'] = $data['bday_month'] = $data['bday_year'] = 0; + + if ($user->data['user_birthday']) + { + list($data['bday_day'], $data['bday_month'], $data['bday_year']) = explode('-', $user->data['user_birthday']); + } + + $data['bday_day'] = $request->variable('bday_day', $data['bday_day']); + $data['bday_month'] = $request->variable('bday_month', $data['bday_month']); + $data['bday_year'] = $request->variable('bday_year', $data['bday_year']); + $data['user_birthday'] = sprintf('%2d-%2d-%4d', $data['bday_day'], $data['bday_month'], $data['bday_year']); + } + + /** + * Modify user data on editing profile in UCP + * + * @event core.ucp_profile_modify_profile_info + * @var array data Array with user profile data + * @var bool submit Flag indicating if submit button has been pressed + * @since 3.1.4-RC1 + */ + $vars = array('data', 'submit'); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_modify_profile_info', compact($vars))); + + add_form_key('ucp_profile_info'); + + if ($submit) + { + $validate_array = array( + 'jabber' => array( + array('string', true, 5, 255), + array('jabber')), + ); + + if ($config['allow_birthdays']) + { + $validate_array = array_merge($validate_array, array( + 'bday_day' => array('num', true, 1, 31), + 'bday_month' => array('num', true, 1, 12), + 'bday_year' => array('num', true, 1901, gmdate('Y', time()) + 50), + 'user_birthday' => array('date', true), + )); + } + + $error = validate_data($data, $validate_array); + + // validate custom profile fields + $cp->submit_cp_field('profile', $user->get_iso_lang_id(), $cp_data, $cp_error); + + if (count($cp_error)) + { + $error = array_merge($error, $cp_error); + } + + if (!check_form_key('ucp_profile_info')) + { + $error[] = 'FORM_INVALID'; + } + + /** + * Validate user data on editing profile in UCP + * + * @event core.ucp_profile_validate_profile_info + * @var array data Array with user profile data + * @var bool submit Flag indicating if submit button has been pressed + * @var array error Array of any generated errors + * @since 3.1.4-RC1 + */ + $vars = array('data', 'submit', 'error'); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_validate_profile_info', compact($vars))); + + if (!count($error)) + { + $data['notify'] = $user->data['user_notify_type']; + + if ($data['notify'] == NOTIFY_IM && (!$config['jab_enable'] || !$data['jabber'] || !@extension_loaded('xml'))) + { + // User has not filled in a jabber address (Or one of the modules is disabled or jabber is disabled) + // Disable notify by Jabber now for this user. + $data['notify'] = NOTIFY_EMAIL; + } + + $sql_ary = array( + 'user_jabber' => $data['jabber'], + 'user_notify_type' => $data['notify'], + ); + + if ($config['allow_birthdays']) + { + $sql_ary['user_birthday'] = $data['user_birthday']; + } + + /** + * Modify profile data in UCP before submitting to the database + * + * @event core.ucp_profile_info_modify_sql_ary + * @var array cp_data Array with the user custom profile fields data + * @var array data Array with user profile data + * @var array sql_ary user options data we update + * @since 3.1.4-RC1 + */ + $vars = array('cp_data', 'data', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_info_modify_sql_ary', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + // Update Custom Fields + $cp->update_profile_field_data($user->data['user_id'], $cp_data); + + meta_refresh(3, $this->u_action); + $message = $user->lang['PROFILE_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + } + + if ($config['allow_birthdays']) + { + $s_birthday_day_options = ''; + for ($i = 1; $i < 32; $i++) + { + $selected = ($i == $data['bday_day']) ? ' selected="selected"' : ''; + $s_birthday_day_options .= ""; + } + + $s_birthday_month_options = ''; + for ($i = 1; $i < 13; $i++) + { + $selected = ($i == $data['bday_month']) ? ' selected="selected"' : ''; + $s_birthday_month_options .= ""; + } + + $now = getdate(); + $s_birthday_year_options = ''; + for ($i = $now['year'] - 100; $i <= $now['year']; $i++) + { + $selected = ($i == $data['bday_year']) ? ' selected="selected"' : ''; + $s_birthday_year_options .= ""; + } + unset($now); + + $template->assign_vars(array( + 'S_BIRTHDAY_DAY_OPTIONS' => $s_birthday_day_options, + 'S_BIRTHDAY_MONTH_OPTIONS' => $s_birthday_month_options, + 'S_BIRTHDAY_YEAR_OPTIONS' => $s_birthday_year_options, + 'S_BIRTHDAYS_ENABLED' => true, + )); + } + + $template->assign_vars(array( + 'ERROR' => (count($error)) ? implode('
', $error) : '', + 'S_JABBER_ENABLED' => $config['jab_enable'], + 'JABBER' => $data['jabber'], + )); + + // Get additional profile fields and assign them to the template block var 'profile_fields' + $user->get_profile_fields($user->data['user_id']); + + $cp->generate_profile_fields('profile', $user->get_iso_lang_id()); + + break; + + case 'signature': + + if (!$auth->acl_get('u_sig')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_SIGNATURE'); + } + + if (!function_exists('generate_smilies')) + { + include($phpbb_root_path . 'includes/functions_posting.' . $phpEx); + } + + if (!function_exists('display_custom_bbcodes')) + { + include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + } + + $preview = $request->is_set_post('preview'); + + $enable_bbcode = ($config['allow_sig_bbcode']) ? $user->optionget('sig_bbcode') : false; + $enable_smilies = ($config['allow_sig_smilies']) ? $user->optionget('sig_smilies') : false; + $enable_urls = ($config['allow_sig_links']) ? $user->optionget('sig_links') : false; + + $bbcode_flags = ($enable_bbcode ? OPTION_FLAG_BBCODE : 0) + ($enable_smilies ? OPTION_FLAG_SMILIES : 0) + ($enable_urls ? OPTION_FLAG_LINKS : 0); + + $decoded_message = generate_text_for_edit($user->data['user_sig'], $user->data['user_sig_bbcode_uid'], $bbcode_flags); + $signature = $request->variable('signature', $decoded_message['text'], true); + $signature_preview = ''; + + if ($submit || $preview) + { + $enable_bbcode = ($config['allow_sig_bbcode']) ? !$request->variable('disable_bbcode', false) : false; + $enable_smilies = ($config['allow_sig_smilies']) ? !$request->variable('disable_smilies', false) : false; + $enable_urls = ($config['allow_sig_links']) ? !$request->variable('disable_magic_url', false) : false; + + if (!check_form_key('ucp_sig')) + { + $error[] = 'FORM_INVALID'; + } + } + + /** + * Modify user signature on editing profile in UCP + * + * @event core.ucp_profile_modify_signature + * @var bool enable_bbcode Whether or not bbcode is enabled + * @var bool enable_smilies Whether or not smilies are enabled + * @var bool enable_urls Whether or not urls are enabled + * @var string signature Users signature text + * @var array error Any error strings + * @var bool submit Whether or not the form has been sumitted + * @var bool preview Whether or not the signature is being previewed + * @since 3.1.10-RC1 + * @changed 3.2.0-RC2 Removed message parser + */ + $vars = array( + 'enable_bbcode', + 'enable_smilies', + 'enable_urls', + 'signature', + 'error', + 'submit', + 'preview', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_modify_signature', compact($vars))); + + $bbcode_uid = $bbcode_bitfield = $bbcode_flags = ''; + $warn_msg = generate_text_for_storage( + $signature, + $bbcode_uid, + $bbcode_bitfield, + $bbcode_flags, + $enable_bbcode, + $enable_urls, + $enable_smilies, + $config['allow_sig_img'], + $config['allow_sig_flash'], + true, + $config['allow_sig_links'], + 'sig' + ); + + if (count($warn_msg)) + { + $error += $warn_msg; + } + + if (!$submit) + { + // Parse it for displaying + $signature_preview = generate_text_for_display($signature, $bbcode_uid, $bbcode_bitfield, $bbcode_flags); + } + else + { + if (!count($error)) + { + $user->optionset('sig_bbcode', $enable_bbcode); + $user->optionset('sig_smilies', $enable_smilies); + $user->optionset('sig_links', $enable_urls); + + $sql_ary = array( + 'user_sig' => $signature, + 'user_options' => $user->data['user_options'], + 'user_sig_bbcode_uid' => $bbcode_uid, + 'user_sig_bbcode_bitfield' => $bbcode_bitfield + ); + + /** + * Modify user registration data before submitting it to the database + * + * @event core.ucp_profile_modify_signature_sql_ary + * @var array sql_ary Array with user signature data to submit to the database + * @since 3.1.10-RC1 + */ + $vars = array('sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_modify_signature_sql_ary', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $message = $user->lang['PROFILE_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + + if ($request->is_set_post('preview')) + { + $decoded_message = generate_text_for_edit($signature, $bbcode_uid, $bbcode_flags); + } + + /** @var \phpbb\controller\helper $controller_helper */ + $controller_helper = $phpbb_container->get('controller.helper'); + + $template->assign_vars(array( + 'ERROR' => (count($error)) ? implode('
', $error) : '', + 'SIGNATURE' => $decoded_message['text'], + 'SIGNATURE_PREVIEW' => $signature_preview, + + 'S_BBCODE_CHECKED' => (!$enable_bbcode) ? ' checked="checked"' : '', + 'S_SMILIES_CHECKED' => (!$enable_smilies) ? ' checked="checked"' : '', + 'S_MAGIC_URL_CHECKED' => (!$enable_urls) ? ' checked="checked"' : '', + + 'BBCODE_STATUS' => $user->lang(($config['allow_sig_bbcode'] ? 'BBCODE_IS_ON' : 'BBCODE_IS_OFF'), '', ''), + 'SMILIES_STATUS' => ($config['allow_sig_smilies']) ? $user->lang['SMILIES_ARE_ON'] : $user->lang['SMILIES_ARE_OFF'], + 'IMG_STATUS' => ($config['allow_sig_img']) ? $user->lang['IMAGES_ARE_ON'] : $user->lang['IMAGES_ARE_OFF'], + 'FLASH_STATUS' => ($config['allow_sig_flash']) ? $user->lang['FLASH_IS_ON'] : $user->lang['FLASH_IS_OFF'], + 'URL_STATUS' => ($config['allow_sig_links']) ? $user->lang['URL_IS_ON'] : $user->lang['URL_IS_OFF'], + 'MAX_FONT_SIZE' => (int) $config['max_sig_font_size'], + + 'L_SIGNATURE_EXPLAIN' => $user->lang('SIGNATURE_EXPLAIN', (int) $config['max_sig_chars']), + + 'S_BBCODE_ALLOWED' => $config['allow_sig_bbcode'], + 'S_SMILIES_ALLOWED' => $config['allow_sig_smilies'], + 'S_BBCODE_IMG' => ($config['allow_sig_img']) ? true : false, + 'S_BBCODE_FLASH' => ($config['allow_sig_flash']) ? true : false, + 'S_LINKS_ALLOWED' => ($config['allow_sig_links']) ? true : false) + ); + + add_form_key('ucp_sig'); + + // Build custom bbcodes array + display_custom_bbcodes(); + + // Generate smiley listing + generate_smilies('inline', 0); + + break; + + case 'avatar': + + add_form_key('ucp_avatar'); + + $avatars_enabled = false; + + if ($config['allow_avatar'] && $auth->acl_get('u_chgavatar')) + { + /* @var $phpbb_avatar_manager \phpbb\avatar\manager */ + $phpbb_avatar_manager = $phpbb_container->get('avatar.manager'); + $avatar_drivers = $phpbb_avatar_manager->get_enabled_drivers(); + + // This is normalised data, without the user_ prefix + $avatar_data = \phpbb\avatar\manager::clean_row($user->data, 'user'); + + if ($submit) + { + if (check_form_key('ucp_avatar')) + { + $driver_name = $phpbb_avatar_manager->clean_driver_name($request->variable('avatar_driver', '')); + + if (in_array($driver_name, $avatar_drivers) && !$request->is_set_post('avatar_delete')) + { + $driver = $phpbb_avatar_manager->get_driver($driver_name); + $result = $driver->process_form($request, $template, $user, $avatar_data, $error); + + if ($result && empty($error)) + { + // Success! Lets save the result in the database + $result = array( + 'user_avatar_type' => $driver_name, + 'user_avatar' => $result['avatar'], + 'user_avatar_width' => $result['avatar_width'], + 'user_avatar_height' => $result['avatar_height'], + ); + + /** + * Trigger events on successfull avatar change + * + * @event core.ucp_profile_avatar_sql + * @var array result Array with data to be stored in DB + * @since 3.1.11-RC1 + */ + $vars = array('result'); + extract($phpbb_dispatcher->trigger_event('core.ucp_profile_avatar_sql', compact($vars))); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $result) . ' + WHERE user_id = ' . (int) $user->data['user_id']; + $db->sql_query($sql); + + meta_refresh(3, $this->u_action); + $message = $user->lang['PROFILE_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + } + } + else + { + $error[] = 'FORM_INVALID'; + } + } + + // Handle deletion of avatars + if ($request->is_set_post('avatar_delete')) + { + if (!confirm_box(true)) + { + confirm_box(false, $user->lang('CONFIRM_AVATAR_DELETE'), build_hidden_fields(array( + 'avatar_delete' => true, + 'i' => $id, + 'mode' => $mode)) + ); + } + else + { + $phpbb_avatar_manager->handle_avatar_delete($db, $user, $avatar_data, USERS_TABLE, 'user_'); + + meta_refresh(3, $this->u_action); + $message = $user->lang['PROFILE_UPDATED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + } + + $selected_driver = $phpbb_avatar_manager->clean_driver_name($request->variable('avatar_driver', $user->data['user_avatar_type'])); + + $template->assign_vars(array( + 'AVATAR_MIN_WIDTH' => $config['avatar_min_width'], + 'AVATAR_MAX_WIDTH' => $config['avatar_max_width'], + 'AVATAR_MIN_HEIGHT' => $config['avatar_min_height'], + 'AVATAR_MAX_HEIGHT' => $config['avatar_max_height'], + )); + + foreach ($avatar_drivers as $current_driver) + { + $driver = $phpbb_avatar_manager->get_driver($current_driver); + + $avatars_enabled = true; + $template->set_filenames(array( + 'avatar' => $driver->get_template_name(), + )); + + if ($driver->prepare_form($request, $template, $user, $avatar_data, $error)) + { + $driver_name = $phpbb_avatar_manager->prepare_driver_name($current_driver); + $driver_upper = strtoupper($driver_name); + + $template->assign_block_vars('avatar_drivers', array( + 'L_TITLE' => $user->lang($driver_upper . '_TITLE'), + 'L_EXPLAIN' => $user->lang($driver_upper . '_EXPLAIN'), + + 'DRIVER' => $driver_name, + 'SELECTED' => $current_driver == $selected_driver, + 'OUTPUT' => $template->assign_display('avatar'), + )); + } + } + + // Replace "error" strings with their real, localised form + $error = $phpbb_avatar_manager->localize_errors($user, $error); + } + + $avatar = phpbb_get_user_avatar($user->data, 'USER_AVATAR', true); + + $template->assign_vars(array( + 'ERROR' => (count($error)) ? implode('
', $error) : '', + 'AVATAR' => $avatar, + + 'S_FORM_ENCTYPE' => ' enctype="multipart/form-data"', + + 'L_AVATAR_EXPLAIN' => phpbb_avatar_explanation_string(), + + 'S_AVATARS_ENABLED' => ($config['allow_avatar'] && $avatars_enabled), + )); + + break; + + case 'autologin_keys': + + add_form_key('ucp_autologin_keys'); + + if ($submit) + { + $keys = $request->variable('keys', array('')); + + if (!check_form_key('ucp_autologin_keys')) + { + $error[] = 'FORM_INVALID'; + } + + if (!count($error)) + { + if (!empty($keys)) + { + foreach ($keys as $key => $id) + { + $keys[$key] = $db->sql_like_expression($id . $db->get_any_char()); + } + $sql_where = '(key_id ' . implode(' OR key_id ', $keys) . ')'; + $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' + WHERE user_id = ' . (int) $user->data['user_id'] . ' + AND ' . $sql_where ; + + $db->sql_query($sql); + + meta_refresh(3, $this->u_action); + $message = $user->lang['AUTOLOGIN_SESSION_KEYS_DELETED'] . '

' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + } + + $sql = 'SELECT key_id, last_ip, last_login + FROM ' . SESSIONS_KEYS_TABLE . ' + WHERE user_id = ' . (int) $user->data['user_id'] . ' + ORDER BY last_login ASC'; + + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $template->assign_block_vars('sessions', array( + 'KEY' => substr($row['key_id'], 0, 8), + 'IP' => $row['last_ip'], + 'LOGIN_TIME' => $user->format_date($row['last_login']), + )); + } + + $db->sql_freeresult($result); + + break; + } + + $template->assign_vars(array( + 'ERROR' => (count($error)) ? implode('
', $error) : '', + + 'L_TITLE' => $user->lang['UCP_PROFILE_' . strtoupper($mode)], + + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + 'S_UCP_ACTION' => $this->u_action) + ); + + // Set desired template + $this->tpl_name = 'ucp_profile_' . $mode; + $this->page_title = 'UCP_PROFILE_' . strtoupper($mode); + } +} diff --git a/includes/ucp/ucp_register.php b/includes/ucp/ucp_register.php new file mode 100644 index 0000000..0e673cb --- /dev/null +++ b/includes/ucp/ucp_register.php @@ -0,0 +1,709 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_register +* Board registration +*/ +class ucp_register +{ + var $u_action; + + function main($id, $mode) + { + global $config, $db, $user, $template, $phpbb_root_path, $phpEx; + global $request, $phpbb_container, $phpbb_dispatcher; + + // + if ($config['require_activation'] == USER_ACTIVATION_DISABLE || + (in_array($config['require_activation'], array(USER_ACTIVATION_SELF, USER_ACTIVATION_ADMIN)) && !$config['email_enable'])) + { + trigger_error('UCP_REGISTER_DISABLE'); + } + + $coppa = $request->is_set('coppa') ? (int) $request->variable('coppa', false) : false; + $agreed = $request->variable('agreed', false); + $submit = $request->is_set_post('submit'); + $change_lang = $request->variable('change_lang', ''); + $user_lang = $request->variable('lang', $user->lang_name); + + /** + * Add UCP register data before they are assigned to the template or submitted + * + * To assign data to the template, use $template->assign_vars() + * + * @event core.ucp_register_requests_after + * @var bool coppa Is set coppa + * @var bool agreed Did user agree to coppa? + * @var bool submit Is set post submit? + * @var string change_lang Change language request + * @var string user_lang User language request + * @since 3.1.11-RC1 + */ + $vars = array( + 'coppa', + 'agreed', + 'submit', + 'change_lang', + 'user_lang', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_register_requests_after', compact($vars))); + + if ($agreed) + { + add_form_key('ucp_register'); + } + else + { + add_form_key('ucp_register_terms'); + } + + if ($change_lang || $user_lang != $config['default_lang']) + { + $use_lang = ($change_lang) ? basename($change_lang) : basename($user_lang); + + if (!validate_language_iso_name($use_lang)) + { + if ($change_lang) + { + $submit = false; + + // Setting back agreed to let the user view the agreement in his/her language + $agreed = false; + } + + $user_lang = $use_lang; + } + else + { + $change_lang = ''; + $user_lang = $user->lang_name; + } + } + + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + + $error = $cp_data = $cp_error = array(); + $s_hidden_fields = array(); + + // Handle login_link data added to $_hidden_fields + $login_link_data = $this->get_login_link_data_array(); + + if (!empty($login_link_data)) + { + // Confirm that we have all necessary data + /* @var $provider_collection \phpbb\auth\provider_collection */ + $provider_collection = $phpbb_container->get('auth.provider_collection'); + $auth_provider = $provider_collection->get_provider($request->variable('auth_provider', '')); + + $result = $auth_provider->login_link_has_necessary_data($login_link_data); + if ($result !== null) + { + $error[] = $user->lang[$result]; + } + + $s_hidden_fields = array_merge($s_hidden_fields, $this->get_login_link_data_for_hidden_fields($login_link_data)); + } + + if (!$agreed || ($coppa === false && $config['coppa_enable']) || ($coppa && !$config['coppa_enable'])) + { + $add_coppa = ($coppa !== false) ? '&coppa=' . $coppa : ''; + + $s_hidden_fields = array_merge($s_hidden_fields, array( + 'change_lang' => '', + )); + + // If we change the language, we want to pass on some more possible parameter. + if ($change_lang) + { + // We do not include the password + $s_hidden_fields = array_merge($s_hidden_fields, array( + 'username' => $request->variable('username', '', true), + 'email' => strtolower($request->variable('email', '')), + 'lang' => $user->lang_name, + 'tz' => $request->variable('tz', $config['board_timezone']), + )); + + } + + // Checking amount of available languages + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE; + $result = $db->sql_query($sql); + + $lang_row = array(); + while ($row = $db->sql_fetchrow($result)) + { + $lang_row[] = $row; + } + $db->sql_freeresult($result); + + if ($coppa === false && $config['coppa_enable']) + { + $now = getdate(); + $coppa_birthday = $user->create_datetime() + ->setDate($now['year'] - 13, $now['mon'], $now['mday'] - 1) + ->setTime(0, 0, 0) + ->format($user->lang['DATE_FORMAT'], true); + unset($now); + + $template_vars = array( + 'S_LANG_OPTIONS' => (count($lang_row) > 1) ? language_select($user_lang) : '', + 'L_COPPA_NO' => sprintf($user->lang['UCP_COPPA_BEFORE'], $coppa_birthday), + 'L_COPPA_YES' => sprintf($user->lang['UCP_COPPA_ON_AFTER'], $coppa_birthday), + + 'U_COPPA_NO' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register&coppa=0'), + 'U_COPPA_YES' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register&coppa=1'), + + 'S_SHOW_COPPA' => true, + 'S_HIDDEN_FIELDS' => build_hidden_fields($s_hidden_fields), + 'S_UCP_ACTION' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'), + + 'COOKIE_NAME' => $config['cookie_name'], + 'COOKIE_PATH' => $config['cookie_path'], + ); + } + else + { + $template_vars = array( + 'S_LANG_OPTIONS' => (count($lang_row) > 1) ? language_select($user_lang) : '', + 'L_TERMS_OF_USE' => sprintf($user->lang['TERMS_OF_USE_CONTENT'], $config['sitename'], generate_board_url()), + + 'S_SHOW_COPPA' => false, + 'S_REGISTRATION' => true, + 'S_HIDDEN_FIELDS' => build_hidden_fields($s_hidden_fields), + 'S_UCP_ACTION' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register' . $add_coppa), + + 'COOKIE_NAME' => $config['cookie_name'], + 'COOKIE_PATH' => $config['cookie_path'], + ); + } + + $tpl_name = 'ucp_agreement'; + + /** + * Allows to modify the agreements. + * + * @event core.ucp_register_agreement_modify_template_data + * @var string tpl_name Template file + * @var array template_vars Array with data about to be assigned to the template + * @var array s_hidden_fields Array with hidden form elements + * @var array lang_row Array with available languages, read only + * @since 3.2.2-RC1 + */ + $vars = array('tpl_name', 'template_vars', 's_hidden_fields', 'lang_row'); + extract($phpbb_dispatcher->trigger_event('core.ucp_register_agreement_modify_template_data', compact($vars))); + + unset($lang_row); + + $template_vars = array_merge($template_vars, array( + 'S_HIDDEN_FIELDS' => build_hidden_fields($s_hidden_fields), + )); + + $template->assign_vars($template_vars); + + /** + * Allows to modify the agreements. + * + * To assign data to the template, use $template->assign_vars() + * + * @event core.ucp_register_agreement + * @since 3.1.6-RC1 + * @deprecated 3.2.2-RC1 Replaced by core.ucp_register_agreement_modify_template_data and to be removed in 3.3.0-RC1 + */ + $phpbb_dispatcher->dispatch('core.ucp_register_agreement'); + + $this->tpl_name = $tpl_name; + return; + } + + // The CAPTCHA kicks in here. We can't help that the information gets lost on language change. + if ($config['enable_confirm']) + { + $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']); + $captcha->init(CONFIRM_REG); + } + + $timezone = $config['board_timezone']; + + $data = array( + 'username' => $request->variable('username', '', true), + 'new_password' => $request->variable('new_password', '', true), + 'password_confirm' => $request->variable('password_confirm', '', true), + 'email' => strtolower($request->variable('email', '')), + 'lang' => basename($request->variable('lang', $user->lang_name)), + 'tz' => $request->variable('tz', $timezone), + ); + /** + * Add UCP register data before they are assigned to the template or submitted + * + * To assign data to the template, use $template->assign_vars() + * + * @event core.ucp_register_data_before + * @var bool submit Do we display the form only + * or did the user press submit + * @var array data Array with current ucp registration data + * @since 3.1.4-RC1 + */ + $vars = array('submit', 'data'); + extract($phpbb_dispatcher->trigger_event('core.ucp_register_data_before', compact($vars))); + + // Check and initialize some variables if needed + if ($submit) + { + $error = validate_data($data, array( + 'username' => array( + array('string', false, $config['min_name_chars'], $config['max_name_chars']), + array('username', '')), + 'new_password' => array( + array('string', false, $config['min_pass_chars'], $config['max_pass_chars']), + array('password')), + 'password_confirm' => array('string', false, $config['min_pass_chars'], $config['max_pass_chars']), + 'email' => array( + array('string', false, 6, 60), + array('user_email')), + 'tz' => array('timezone'), + 'lang' => array('language_iso_name'), + )); + + if (!check_form_key('ucp_register')) + { + $error[] = $user->lang['FORM_INVALID']; + } + + // Replace "error" strings with their real, localised form + $error = array_map(array($user, 'lang'), $error); + + if ($config['enable_confirm']) + { + $vc_response = $captcha->validate($data); + if ($vc_response !== false) + { + $error[] = $vc_response; + } + + if ($config['max_reg_attempts'] && $captcha->get_attempt_count() > $config['max_reg_attempts']) + { + $error[] = $user->lang['TOO_MANY_REGISTERS']; + } + } + + // DNSBL check + if ($config['check_dnsbl']) + { + if (($dnsbl = $user->check_dnsbl('register')) !== false) + { + $error[] = sprintf($user->lang['IP_BLACKLISTED'], $user->ip, $dnsbl[1]); + } + } + + // validate custom profile fields + $cp->submit_cp_field('register', $user->get_iso_lang_id(), $cp_data, $error); + + if (!count($error)) + { + if ($data['new_password'] != $data['password_confirm']) + { + $error[] = $user->lang['NEW_PASSWORD_ERROR']; + } + } + /** + * Check UCP registration data after they are submitted + * + * @event core.ucp_register_data_after + * @var bool submit Do we display the form only + * or did the user press submit + * @var array data Array with current ucp registration data + * @var array cp_data Array with custom profile fields data + * @var array error Array with list of errors + * @since 3.1.4-RC1 + */ + $vars = array('submit', 'data', 'cp_data', 'error'); + extract($phpbb_dispatcher->trigger_event('core.ucp_register_data_after', compact($vars))); + + if (!count($error)) + { + $server_url = generate_board_url(); + + // Which group by default? + $group_name = ($coppa) ? 'REGISTERED_COPPA' : 'REGISTERED'; + + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $db->sql_escape($group_name) . "' + AND group_type = " . GROUP_SPECIAL; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_GROUP'); + } + + $group_id = $row['group_id']; + + if (($coppa || + $config['require_activation'] == USER_ACTIVATION_SELF || + $config['require_activation'] == USER_ACTIVATION_ADMIN) && $config['email_enable']) + { + $user_actkey = gen_rand_string(mt_rand(6, 10)); + $user_type = USER_INACTIVE; + $user_inactive_reason = INACTIVE_REGISTER; + $user_inactive_time = time(); + } + else + { + $user_type = USER_NORMAL; + $user_actkey = ''; + $user_inactive_reason = 0; + $user_inactive_time = 0; + } + + // Instantiate passwords manager + /* @var $passwords_manager \phpbb\passwords\manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + + $user_row = array( + 'username' => $data['username'], + 'user_password' => $passwords_manager->hash($data['new_password']), + 'user_email' => $data['email'], + 'group_id' => (int) $group_id, + 'user_timezone' => $data['tz'], + 'user_lang' => $data['lang'], + 'user_type' => $user_type, + 'user_actkey' => $user_actkey, + 'user_ip' => $user->ip, + 'user_regdate' => time(), + 'user_inactive_reason' => $user_inactive_reason, + 'user_inactive_time' => $user_inactive_time, + ); + + if ($config['new_member_post_limit']) + { + $user_row['user_new'] = 1; + } + /** + * Add into $user_row before user_add + * + * user_add allows adding more data into the users table + * + * @event core.ucp_register_user_row_after + * @var bool submit Do we display the form only + * or did the user press submit + * @var array cp_data Array with custom profile fields data + * @var array user_row Array with current ucp registration data + * @since 3.1.4-RC1 + */ + $vars = array('submit', 'cp_data', 'user_row'); + extract($phpbb_dispatcher->trigger_event('core.ucp_register_user_row_after', compact($vars))); + + // Register user... + $user_id = user_add($user_row, $cp_data); + + // This should not happen, because the required variables are listed above... + if ($user_id === false) + { + trigger_error('NO_USER', E_USER_ERROR); + } + + // Okay, captcha, your job is done. + if ($config['enable_confirm'] && isset($captcha)) + { + $captcha->reset(); + } + + if ($coppa && $config['email_enable']) + { + $message = $user->lang['ACCOUNT_COPPA']; + $email_template = 'coppa_welcome_inactive'; + } + else if ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) + { + $message = $user->lang['ACCOUNT_INACTIVE']; + $email_template = 'user_welcome_inactive'; + } + else if ($config['require_activation'] == USER_ACTIVATION_ADMIN && $config['email_enable']) + { + $message = $user->lang['ACCOUNT_INACTIVE_ADMIN']; + $email_template = 'admin_welcome_inactive'; + } + else + { + $message = $user->lang['ACCOUNT_ADDED']; + $email_template = 'user_welcome'; + } + + if ($config['email_enable']) + { + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $messenger = new messenger(false); + + $messenger->template($email_template, $data['lang']); + + $messenger->to($data['email'], $data['username']); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'WELCOME_MSG' => htmlspecialchars_decode(sprintf($user->lang['WELCOME_SUBJECT'], $config['sitename'])), + 'USERNAME' => htmlspecialchars_decode($data['username']), + 'PASSWORD' => htmlspecialchars_decode($data['new_password']), + 'U_ACTIVATE' => "$server_url/ucp.$phpEx?mode=activate&u=$user_id&k=$user_actkey") + ); + + if ($coppa) + { + $messenger->assign_vars(array( + 'FAX_INFO' => $config['coppa_fax'], + 'MAIL_INFO' => $config['coppa_mail'], + 'EMAIL_ADDRESS' => $data['email']) + ); + } + + /** + * Modify messenger data before welcome mail is sent + * + * @event core.ucp_register_welcome_email_before + * @var array user_row Array with user registration data + * @var array cp_data Array with custom profile fields data + * @var array data Array with current ucp registration data + * @var string message Message to be displayed to the user after registration + * @var string server_url Server URL + * @var int user_id New user ID + * @var string user_actkey User activation key + * @var messenger messenger phpBB Messenger + * @since 3.2.4-RC1 + */ + $vars = array( + 'user_row', + 'cp_data', + 'data', + 'message', + 'server_url', + 'user_id', + 'user_actkey', + 'messenger', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_register_welcome_email_before', compact($vars))); + + $messenger->send(NOTIFY_EMAIL); + } + + if ($config['require_activation'] == USER_ACTIVATION_ADMIN) + { + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + $phpbb_notifications->add_notifications('notification.type.admin_activate_user', array( + 'user_id' => $user_id, + 'user_actkey' => $user_row['user_actkey'], + 'user_regdate' => $user_row['user_regdate'], + )); + } + + // Perform account linking if necessary + if (!empty($login_link_data)) + { + $login_link_data['user_id'] = $user_id; + + $result = $auth_provider->link_account($login_link_data); + + if ($result) + { + $message = $message . '

' . $user->lang[$result]; + } + } + + /** + * Perform additional actions after user registration + * + * @event core.ucp_register_register_after + * @var array user_row Array with user registration data + * @var array cp_data Array with custom profile fields data + * @var array data Array with current ucp registration data + * @var string message Message to be displayed to the user after registration + * @var string server_url Server URL + * @var int user_id New user ID + * @var string user_actkey User activation key + * @since 3.2.4-RC1 + */ + $vars = array( + 'user_row', + 'cp_data', + 'data', + 'message', + 'server_url', + 'user_id', + 'user_actkey', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_register_register_after', compact($vars))); + + $message = $message . '

' . sprintf($user->lang['RETURN_INDEX'], '', ''); + trigger_error($message); + } + } + + $s_hidden_fields = array_merge($s_hidden_fields, array( + 'agreed' => 'true', + 'change_lang' => 0, + )); + + if ($config['coppa_enable']) + { + $s_hidden_fields['coppa'] = $coppa; + } + + if ($config['enable_confirm']) + { + $s_hidden_fields = array_merge($s_hidden_fields, $captcha->get_hidden_fields()); + } + + // Visual Confirmation - Show images + if ($config['enable_confirm']) + { + $template->assign_vars(array( + 'CAPTCHA_TEMPLATE' => $captcha->get_template(), + )); + } + + // + $l_reg_cond = ''; + switch ($config['require_activation']) + { + case USER_ACTIVATION_SELF: + $l_reg_cond = $user->lang['UCP_EMAIL_ACTIVATE']; + break; + + case USER_ACTIVATION_ADMIN: + $l_reg_cond = $user->lang['UCP_ADMIN_ACTIVATE']; + break; + } + + // Assign template vars for timezone select + phpbb_timezone_select($template, $user, $data['tz'], true); + + $template_vars = array( + 'USERNAME' => $data['username'], + 'PASSWORD' => $data['new_password'], + 'PASSWORD_CONFIRM' => $data['password_confirm'], + 'EMAIL' => $data['email'], + + 'L_REG_COND' => $l_reg_cond, + 'L_USERNAME_EXPLAIN' => $user->lang($config['allow_name_chars'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_name_chars']), $user->lang('CHARACTERS', (int) $config['max_name_chars'])), + 'L_PASSWORD_EXPLAIN' => $user->lang($config['pass_complex'] . '_EXPLAIN', $user->lang('CHARACTERS', (int) $config['min_pass_chars']), $user->lang('CHARACTERS', (int) $config['max_pass_chars'])), + + 'S_LANG_OPTIONS' => language_select($data['lang']), + 'S_TZ_PRESELECT' => !$submit, + 'S_CONFIRM_REFRESH' => ($config['enable_confirm'] && $config['confirm_refresh']) ? true : false, + 'S_REGISTRATION' => true, + 'S_COPPA' => $coppa, + 'S_UCP_ACTION' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'), + + 'COOKIE_NAME' => $config['cookie_name'], + 'COOKIE_PATH' => $config['cookie_path'], + ); + + $tpl_name = 'ucp_register'; + + /** + * Modify template data on the registration page + * + * @event core.ucp_register_modify_template_data + * @var array template_vars Array with template data + * @var array data Array with user data, read only + * @var array error Array with errors + * @var array s_hidden_fields Array with hidden field elements + * @var string tpl_name Template name + * @since 3.2.2-RC1 + */ + $vars = array( + 'template_vars', + 'data', + 'error', + 's_hidden_fields', + 'tpl_name', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_register_modify_template_data', compact($vars))); + + $template_vars = array_merge($template_vars, array( + 'ERROR' => (count($error)) ? implode('
', $error) : '', + 'S_HIDDEN_FIELDS' => build_hidden_fields($s_hidden_fields), + )); + + $template->assign_vars($template_vars); + + // + $user->profile_fields = array(); + + // Generate profile fields -> Template Block Variable profile_fields + $cp->generate_profile_fields('register', $user->get_iso_lang_id()); + + // + $this->tpl_name = $tpl_name; + } + + /** + * Creates the login_link data array + * + * @return array Returns an array of all POST paramaters whose names + * begin with 'login_link_' + */ + protected function get_login_link_data_array() + { + global $request; + + $var_names = $request->variable_names(\phpbb\request\request_interface::POST); + $login_link_data = array(); + $string_start_length = strlen('login_link_'); + + foreach ($var_names as $var_name) + { + if (strpos($var_name, 'login_link_') === 0) + { + $key_name = substr($var_name, $string_start_length); + $login_link_data[$key_name] = $request->variable($var_name, '', false, \phpbb\request\request_interface::POST); + } + } + + return $login_link_data; + } + + /** + * Prepends they key names of an associative array with 'login_link_' for + * inclusion on the page as hidden fields. + * + * @param array $data The array to be modified + * @return array The modified array + */ + protected function get_login_link_data_for_hidden_fields($data) + { + $new_data = array(); + + foreach ($data as $key => $value) + { + $new_data['login_link_' . $key] = $value; + } + + return $new_data; + } +} diff --git a/includes/ucp/ucp_remind.php b/includes/ucp/ucp_remind.php new file mode 100644 index 0000000..e50428b --- /dev/null +++ b/includes/ucp/ucp_remind.php @@ -0,0 +1,174 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_remind +* Sending password reminders +*/ +class ucp_remind +{ + var $u_action; + + function main($id, $mode) + { + global $config, $phpbb_root_path, $phpEx, $request; + global $db, $user, $template, $phpbb_container, $phpbb_dispatcher; + + if (!$config['allow_password_reset']) + { + trigger_error($user->lang('UCP_PASSWORD_RESET_DISABLED', '', '')); + } + + $username = $request->variable('username', '', true); + $email = strtolower($request->variable('email', '')); + $submit = (isset($_POST['submit'])) ? true : false; + + add_form_key('ucp_remind'); + + if ($submit) + { + if (!check_form_key('ucp_remind')) + { + trigger_error('FORM_INVALID'); + } + + if (empty($email)) + { + trigger_error('NO_EMAIL_USER'); + } + + $sql_array = array( + 'SELECT' => 'user_id, username, user_permissions, user_email, user_jabber, user_notify_type, user_type, user_lang, user_inactive_reason', + 'FROM' => array(USERS_TABLE => 'u'), + 'WHERE' => "user_email_hash = '" . $db->sql_escape(phpbb_email_hash($email)) . "'" . + (!empty($username) ? " AND username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'" : ''), + ); + + /** + * Change SQL query for fetching user data + * + * @event core.ucp_remind_modify_select_sql + * @var string email User's email from the form + * @var string username User's username from the form + * @var array sql_array Fully assembled SQL query with keys SELECT, FROM, WHERE + * @since 3.1.11-RC1 + */ + $vars = array( + 'email', + 'username', + 'sql_array', + ); + extract($phpbb_dispatcher->trigger_event('core.ucp_remind_modify_select_sql', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query_limit($sql, 2); // don't waste resources on more rows than we need + $rowset = $db->sql_fetchrowset($result); + + if (count($rowset) > 1) + { + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'USERNAME_REQUIRED' => true, + 'EMAIL' => $email, + )); + } + else + { + $message = $user->lang['PASSWORD_UPDATED_IF_EXISTED'] . '

' . sprintf($user->lang['RETURN_INDEX'], '', ''); + + if (empty($rowset)) + { + trigger_error($message); + } + + $user_row = $rowset[0]; + $db->sql_freeresult($result); + + if (!$user_row) + { + trigger_error($message); + } + + if ($user_row['user_type'] == USER_IGNORE || $user_row['user_type'] == USER_INACTIVE) + { + trigger_error($message); + } + + // Check users permissions + $auth2 = new \phpbb\auth\auth(); + $auth2->acl($user_row); + + if (!$auth2->acl_get('u_chgpasswd')) + { + trigger_error($message); + } + + $server_url = generate_board_url(); + + // Make password at least 8 characters long, make it longer if admin wants to. + // gen_rand_string() however has a limit of 12 or 13. + $user_password = gen_rand_string_friendly(max(8, mt_rand((int) $config['min_pass_chars'], (int) $config['max_pass_chars']))); + + // For the activation key a random length between 6 and 10 will do. + $user_actkey = gen_rand_string(mt_rand(6, 10)); + + // Instantiate passwords manager + /* @var $manager \phpbb\passwords\manager */ + $passwords_manager = $phpbb_container->get('passwords.manager'); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_newpasswd = '" . $db->sql_escape($passwords_manager->hash($user_password)) . "', user_actkey = '" . $db->sql_escape($user_actkey) . "' + WHERE user_id = " . $user_row['user_id']; + $db->sql_query($sql); + + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $messenger = new messenger(false); + + $messenger->template('user_activate_passwd', $user_row['user_lang']); + + $messenger->set_addresses($user_row); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($user_row['username']), + 'PASSWORD' => htmlspecialchars_decode($user_password), + 'U_ACTIVATE' => "$server_url/ucp.$phpEx?mode=activate&u={$user_row['user_id']}&k=$user_actkey") + ); + + $messenger->send($user_row['user_notify_type']); + + trigger_error($message); + } + } + + $template->assign_vars(array( + 'USERNAME' => $username, + 'EMAIL' => $email, + 'S_PROFILE_ACTION' => append_sid($phpbb_root_path . 'ucp.' . $phpEx, 'mode=sendpassword')) + ); + + $this->tpl_name = 'ucp_remind'; + $this->page_title = 'UCP_REMIND'; + } +} diff --git a/includes/ucp/ucp_resend.php b/includes/ucp/ucp_resend.php new file mode 100644 index 0000000..44c5410 --- /dev/null +++ b/includes/ucp/ucp_resend.php @@ -0,0 +1,163 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* ucp_resend +* Resending activation emails +*/ +class ucp_resend +{ + var $u_action; + + function main($id, $mode) + { + global $config, $phpbb_root_path, $phpEx; + global $db, $user, $auth, $template, $request; + + $username = $request->variable('username', '', true); + $email = strtolower($request->variable('email', '')); + $submit = (isset($_POST['submit'])) ? true : false; + + add_form_key('ucp_resend'); + + if ($submit) + { + if (!check_form_key('ucp_resend')) + { + trigger_error('FORM_INVALID'); + } + + $sql = 'SELECT user_id, group_id, username, user_email, user_type, user_lang, user_actkey, user_inactive_reason + FROM ' . USERS_TABLE . " + WHERE user_email_hash = '" . $db->sql_escape(phpbb_email_hash($email)) . "' + AND username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; + $result = $db->sql_query($sql); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$user_row) + { + trigger_error('NO_EMAIL_USER'); + } + + if ($user_row['user_type'] == USER_IGNORE) + { + trigger_error('NO_USER'); + } + + if (!$user_row['user_actkey'] && $user_row['user_type'] != USER_INACTIVE) + { + trigger_error('ACCOUNT_ALREADY_ACTIVATED'); + } + + if (!$user_row['user_actkey'] || ($user_row['user_type'] == USER_INACTIVE && $user_row['user_inactive_reason'] == INACTIVE_MANUAL)) + { + trigger_error('ACCOUNT_DEACTIVATED'); + } + + // Determine coppa status on group (REGISTERED(_COPPA)) + $sql = 'SELECT group_name, group_type + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . $user_row['group_id']; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_GROUP'); + } + + $coppa = ($row['group_name'] == 'REGISTERED_COPPA' && $row['group_type'] == GROUP_SPECIAL) ? true : false; + + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + $messenger = new messenger(false); + + if ($config['require_activation'] == USER_ACTIVATION_SELF || $coppa) + { + $messenger->template(($coppa) ? 'coppa_resend_inactive' : 'user_resend_inactive', $user_row['user_lang']); + $messenger->set_addresses($user_row); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'WELCOME_MSG' => htmlspecialchars_decode(sprintf($user->lang['WELCOME_SUBJECT'], $config['sitename'])), + 'USERNAME' => htmlspecialchars_decode($user_row['username']), + 'U_ACTIVATE' => generate_board_url() . "/ucp.$phpEx?mode=activate&u={$user_row['user_id']}&k={$user_row['user_actkey']}") + ); + + if ($coppa) + { + $messenger->assign_vars(array( + 'FAX_INFO' => $config['coppa_fax'], + 'MAIL_INFO' => $config['coppa_mail'], + 'EMAIL_ADDRESS' => $user_row['user_email']) + ); + } + + $messenger->send(NOTIFY_EMAIL); + } + + if ($config['require_activation'] == USER_ACTIVATION_ADMIN) + { + // Grab an array of user_id's with a_user permissions ... these users can activate a user + $admin_ary = $auth->acl_get_list(false, 'a_user', false); + + $sql = 'SELECT user_id, username, user_email, user_lang, user_jabber, user_notify_type + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_id', $admin_ary[0]['a_user']); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $messenger->template('admin_activate', $row['user_lang']); + $messenger->set_addresses($row); + + $messenger->anti_abuse_headers($config, $user); + + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($user_row['username']), + 'U_USER_DETAILS' => generate_board_url() . "/memberlist.$phpEx?mode=viewprofile&u={$user_row['user_id']}", + 'U_ACTIVATE' => generate_board_url() . "/ucp.$phpEx?mode=activate&u={$user_row['user_id']}&k={$user_row['user_actkey']}") + ); + + $messenger->send($row['user_notify_type']); + } + $db->sql_freeresult($result); + } + + meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx")); + + $message = ($config['require_activation'] == USER_ACTIVATION_ADMIN) ? $user->lang['ACTIVATION_EMAIL_SENT_ADMIN'] : $user->lang['ACTIVATION_EMAIL_SENT']; + $message .= '

' . sprintf($user->lang['RETURN_INDEX'], '', ''); + trigger_error($message); + } + + $template->assign_vars(array( + 'USERNAME' => $username, + 'EMAIL' => $email, + 'S_PROFILE_ACTION' => append_sid($phpbb_root_path . 'ucp.' . $phpEx, 'mode=resend_act')) + ); + + $this->tpl_name = 'ucp_resend'; + $this->page_title = 'UCP_RESEND'; + } +} diff --git a/includes/ucp/ucp_zebra.php b/includes/ucp/ucp_zebra.php new file mode 100644 index 0000000..b4c561f --- /dev/null +++ b/includes/ucp/ucp_zebra.php @@ -0,0 +1,296 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +class ucp_zebra +{ + var $u_action; + + function main($id, $mode) + { + global $db, $user, $auth, $template, $phpbb_root_path, $phpEx, $request, $phpbb_dispatcher; + + $submit = (isset($_POST['submit']) || isset($_GET['add']) || isset($_GET['remove'])) ? true : false; + $s_hidden_fields = ''; + + $l_mode = strtoupper($mode); + + if ($submit) + { + $data = $error = array(); + $updated = false; + + $var_ary = array( + 'usernames' => array(0), + 'add' => '', + ); + + foreach ($var_ary as $var => $default) + { + $data[$var] = $request->variable($var, $default, true); + } + + if (!empty($data['add']) || count($data['usernames'])) + { + if (confirm_box(true)) + { + // Remove users + if (!empty($data['usernames'])) + { + $user_ids = $data['usernames']; + + /** + * Remove users from friends/foes + * + * @event core.ucp_remove_zebra + * @var string mode Zebra type: friends|foes + * @var array user_ids User ids we remove + * @since 3.1.0-a1 + */ + $vars = array('mode', 'user_ids'); + extract($phpbb_dispatcher->trigger_event('core.ucp_remove_zebra', compact($vars))); + + $sql = 'DELETE FROM ' . ZEBRA_TABLE . ' + WHERE user_id = ' . $user->data['user_id'] . ' + AND ' . $db->sql_in_set('zebra_id', $user_ids); + $db->sql_query($sql); + + $updated = true; + } + + // Add users + if ($data['add']) + { + $data['add'] = array_map('trim', array_map('utf8_clean_string', explode("\n", $data['add']))); + + // Do these name/s exist on a list already? If so, ignore ... we could be + // 'nice' and automatically handle names added to one list present on + // the other (by removing the existing one) ... but I have a feeling this + // may lead to complaints + $sql = 'SELECT z.*, u.username, u.username_clean + FROM ' . ZEBRA_TABLE . ' z, ' . USERS_TABLE . ' u + WHERE z.user_id = ' . $user->data['user_id'] . ' + AND u.user_id = z.zebra_id'; + $result = $db->sql_query($sql); + + $friends = $foes = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['friend']) + { + $friends[] = utf8_clean_string($row['username']); + } + else + { + $foes[] = utf8_clean_string($row['username']); + } + } + $db->sql_freeresult($result); + + // remove friends from the username array + $n = count($data['add']); + $data['add'] = array_diff($data['add'], $friends); + + if (count($data['add']) < $n && $mode == 'foes') + { + $error[] = $user->lang['NOT_ADDED_FOES_FRIENDS']; + } + + // remove foes from the username array + $n = count($data['add']); + $data['add'] = array_diff($data['add'], $foes); + + if (count($data['add']) < $n && $mode == 'friends') + { + $error[] = $user->lang['NOT_ADDED_FRIENDS_FOES']; + } + + // remove the user himself from the username array + $n = count($data['add']); + $data['add'] = array_diff($data['add'], array(utf8_clean_string($user->data['username']))); + + if (count($data['add']) < $n) + { + $error[] = $user->lang['NOT_ADDED_' . $l_mode . '_SELF']; + } + + unset($friends, $foes, $n); + + if (count($data['add'])) + { + $sql = 'SELECT user_id, user_type + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('username_clean', $data['add']) . ' + AND user_type <> ' . USER_INACTIVE; + $result = $db->sql_query($sql); + + $user_id_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['user_id'] != ANONYMOUS && $row['user_type'] != USER_IGNORE) + { + $user_id_ary[] = $row['user_id']; + } + else if ($row['user_id'] != ANONYMOUS) + { + $error[] = $user->lang['NOT_ADDED_' . $l_mode . '_BOTS']; + } + else + { + $error[] = $user->lang['NOT_ADDED_' . $l_mode . '_ANONYMOUS']; + } + } + $db->sql_freeresult($result); + + if (count($user_id_ary)) + { + // Remove users from foe list if they are admins or moderators + if ($mode == 'foes') + { + $perms = array(); + foreach ($auth->acl_get_list($user_id_ary, array('a_', 'm_')) as $forum_id => $forum_ary) + { + foreach ($forum_ary as $auth_option => $user_ary) + { + $perms = array_merge($perms, $user_ary); + } + } + + $perms = array_unique($perms); + + if (count($perms)) + { + $error[] = $user->lang['NOT_ADDED_FOES_MOD_ADMIN']; + } + + // This may not be right ... it may yield true when perms equate to deny + $user_id_ary = array_diff($user_id_ary, $perms); + unset($perms); + } + + if (count($user_id_ary)) + { + $sql_mode = ($mode == 'friends') ? 'friend' : 'foe'; + + $sql_ary = array(); + foreach ($user_id_ary as $zebra_id) + { + $sql_ary[] = array( + 'user_id' => (int) $user->data['user_id'], + 'zebra_id' => (int) $zebra_id, + $sql_mode => 1 + ); + } + + /** + * Add users to friends/foes + * + * @event core.ucp_add_zebra + * @var string mode Zebra type: + * friends|foes + * @var array sql_ary Array of + * entries we add + * @since 3.1.0-a1 + */ + $vars = array('mode', 'sql_ary'); + extract($phpbb_dispatcher->trigger_event('core.ucp_add_zebra', compact($vars))); + + $db->sql_multi_insert(ZEBRA_TABLE, $sql_ary); + + $updated = true; + } + unset($user_id_ary); + } + else if (!count($error)) + { + $error[] = $user->lang['USER_NOT_FOUND_OR_INACTIVE']; + } + } + } + + if ($request->is_ajax()) + { + $message = ($updated) ? $user->lang[$l_mode . '_UPDATED'] : implode('
', $error); + + $json_response = new \phpbb\json_response; + $json_response->send(array( + 'success' => $updated, + + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $message, + 'REFRESH_DATA' => array( + 'time' => 3, + 'url' => $this->u_action + ) + )); + } + else if ($updated) + { + meta_refresh(3, $this->u_action); + $message = $user->lang[$l_mode . '_UPDATED'] . '
' . implode('
', $error) . ((count($error)) ? '
' : '') . '
' . sprintf($user->lang['RETURN_UCP'], '', ''); + trigger_error($message); + } + else + { + $template->assign_var('ERROR', implode('
', $error)); + } + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'mode' => $mode, + 'submit' => true, + 'usernames' => $data['usernames'], + 'add' => $data['add'])) + ); + } + } + } + + $sql_and = ($mode == 'friends') ? 'z.friend = 1' : 'z.foe = 1'; + $sql = 'SELECT z.*, u.username, u.username_clean + FROM ' . ZEBRA_TABLE . ' z, ' . USERS_TABLE . ' u + WHERE z.user_id = ' . $user->data['user_id'] . " + AND $sql_and + AND u.user_id = z.zebra_id + ORDER BY u.username_clean ASC"; + $result = $db->sql_query($sql); + + $s_username_options = ''; + while ($row = $db->sql_fetchrow($result)) + { + $s_username_options .= ''; + } + $db->sql_freeresult($result); + + $template->assign_vars(array( + 'L_TITLE' => $user->lang['UCP_ZEBRA_' . $l_mode], + + 'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=ucp&field=add'), + + 'S_USERNAME_OPTIONS' => $s_username_options, + 'S_HIDDEN_FIELDS' => $s_hidden_fields, + 'S_UCP_ACTION' => $this->u_action) + ); + + $this->tpl_name = 'ucp_zebra_' . $mode; + $this->page_title = 'UCP_ZEBRA_' . $l_mode; + } +} diff --git a/includes/utf/data/case_fold_c.php b/includes/utf/data/case_fold_c.php new file mode 100644 index 0000000..a5e3fc7 --- /dev/null +++ b/includes/utf/data/case_fold_c.php @@ -0,0 +1 @@ +'a','B'=>'b','C'=>'c','D'=>'d','E'=>'e','F'=>'f','G'=>'g','H'=>'h','I'=>'i','J'=>'j','K'=>'k','L'=>'l','M'=>'m','N'=>'n','O'=>'o','P'=>'p','Q'=>'q','R'=>'r','S'=>'s','T'=>'t','U'=>'u','V'=>'v','W'=>'w','X'=>'x','Y'=>'y','Z'=>'z','µ'=>'μ','À'=>'à','Á'=>'á','Â'=>'â','Ã'=>'ã','Ä'=>'ä','Å'=>'å','Æ'=>'æ','Ç'=>'ç','È'=>'è','É'=>'é','Ê'=>'ê','Ë'=>'ë','Ì'=>'ì','Í'=>'í','Î'=>'î','Ï'=>'ï','Ð'=>'ð','Ñ'=>'ñ','Ò'=>'ò','Ó'=>'ó','Ô'=>'ô','Õ'=>'õ','Ö'=>'ö','Ø'=>'ø','Ù'=>'ù','Ú'=>'ú','Û'=>'û','Ü'=>'ü','Ý'=>'ý','Þ'=>'þ','Ā'=>'ā','Ă'=>'ă','Ą'=>'ą','Ć'=>'ć','Ĉ'=>'ĉ','Ċ'=>'ċ','Č'=>'č','Ď'=>'ď','Đ'=>'đ','Ē'=>'ē','Ĕ'=>'ĕ','Ė'=>'ė','Ę'=>'ę','Ě'=>'ě','Ĝ'=>'ĝ','Ğ'=>'ğ','Ġ'=>'ġ','Ģ'=>'ģ','Ĥ'=>'ĥ','Ħ'=>'ħ','Ĩ'=>'ĩ','Ī'=>'ī','Ĭ'=>'ĭ','Į'=>'į','IJ'=>'ij','Ĵ'=>'ĵ','Ķ'=>'ķ','Ĺ'=>'ĺ','Ļ'=>'ļ','Ľ'=>'ľ','Ŀ'=>'ŀ','Ł'=>'ł','Ń'=>'ń','Ņ'=>'ņ','Ň'=>'ň','Ŋ'=>'ŋ','Ō'=>'ō','Ŏ'=>'ŏ','Ő'=>'ő','Œ'=>'œ','Ŕ'=>'ŕ','Ŗ'=>'ŗ','Ř'=>'ř','Ś'=>'ś','Ŝ'=>'ŝ','Ş'=>'ş','Š'=>'š','Ţ'=>'ţ','Ť'=>'ť','Ŧ'=>'ŧ','Ũ'=>'ũ','Ū'=>'ū','Ŭ'=>'ŭ','Ů'=>'ů','Ű'=>'ű','Ų'=>'ų','Ŵ'=>'ŵ','Ŷ'=>'ŷ','Ÿ'=>'ÿ','Ź'=>'ź','Ż'=>'ż','Ž'=>'ž','ſ'=>'s','Ɓ'=>'ɓ','Ƃ'=>'ƃ','Ƅ'=>'ƅ','Ɔ'=>'ɔ','Ƈ'=>'ƈ','Ɖ'=>'ɖ','Ɗ'=>'ɗ','Ƌ'=>'ƌ','Ǝ'=>'ǝ','Ə'=>'ə','Ɛ'=>'ɛ','Ƒ'=>'ƒ','Ɠ'=>'ɠ','Ɣ'=>'ɣ','Ɩ'=>'ɩ','Ɨ'=>'ɨ','Ƙ'=>'ƙ','Ɯ'=>'ɯ','Ɲ'=>'ɲ','Ɵ'=>'ɵ','Ơ'=>'ơ','Ƣ'=>'ƣ','Ƥ'=>'ƥ','Ʀ'=>'ʀ','Ƨ'=>'ƨ','Ʃ'=>'ʃ','Ƭ'=>'ƭ','Ʈ'=>'ʈ','Ư'=>'ư','Ʊ'=>'ʊ','Ʋ'=>'ʋ','Ƴ'=>'ƴ','Ƶ'=>'ƶ','Ʒ'=>'ʒ','Ƹ'=>'ƹ','Ƽ'=>'ƽ','DŽ'=>'dž','Dž'=>'dž','LJ'=>'lj','Lj'=>'lj','NJ'=>'nj','Nj'=>'nj','Ǎ'=>'ǎ','Ǐ'=>'ǐ','Ǒ'=>'ǒ','Ǔ'=>'ǔ','Ǖ'=>'ǖ','Ǘ'=>'ǘ','Ǚ'=>'ǚ','Ǜ'=>'ǜ','Ǟ'=>'ǟ','Ǡ'=>'ǡ','Ǣ'=>'ǣ','Ǥ'=>'ǥ','Ǧ'=>'ǧ','Ǩ'=>'ǩ','Ǫ'=>'ǫ','Ǭ'=>'ǭ','Ǯ'=>'ǯ','DZ'=>'dz','Dz'=>'dz','Ǵ'=>'ǵ','Ƕ'=>'ƕ','Ƿ'=>'ƿ','Ǹ'=>'ǹ','Ǻ'=>'ǻ','Ǽ'=>'ǽ','Ǿ'=>'ǿ','Ȁ'=>'ȁ','Ȃ'=>'ȃ','Ȅ'=>'ȅ','Ȇ'=>'ȇ','Ȉ'=>'ȉ','Ȋ'=>'ȋ','Ȍ'=>'ȍ','Ȏ'=>'ȏ','Ȑ'=>'ȑ','Ȓ'=>'ȓ','Ȕ'=>'ȕ','Ȗ'=>'ȗ','Ș'=>'ș','Ț'=>'ț','Ȝ'=>'ȝ','Ȟ'=>'ȟ','Ƞ'=>'ƞ','Ȣ'=>'ȣ','Ȥ'=>'ȥ','Ȧ'=>'ȧ','Ȩ'=>'ȩ','Ȫ'=>'ȫ','Ȭ'=>'ȭ','Ȯ'=>'ȯ','Ȱ'=>'ȱ','Ȳ'=>'ȳ','Ⱥ'=>'ⱥ','Ȼ'=>'ȼ','Ƚ'=>'ƚ','Ⱦ'=>'ⱦ','Ɂ'=>'ɂ','Ƀ'=>'ƀ','Ʉ'=>'ʉ','Ʌ'=>'ʌ','Ɇ'=>'ɇ','Ɉ'=>'ɉ','Ɋ'=>'ɋ','Ɍ'=>'ɍ','Ɏ'=>'ɏ','ͅ'=>'ι','Ά'=>'ά','Έ'=>'έ','Ή'=>'ή','Ί'=>'ί','Ό'=>'ό','Ύ'=>'ύ','Ώ'=>'ώ','Α'=>'α','Β'=>'β','Γ'=>'γ','Δ'=>'δ','Ε'=>'ε','Ζ'=>'ζ','Η'=>'η','Θ'=>'θ','Ι'=>'ι','Κ'=>'κ','Λ'=>'λ','Μ'=>'μ','Ν'=>'ν','Ξ'=>'ξ','Ο'=>'ο','Π'=>'π','Ρ'=>'ρ','Σ'=>'σ','Τ'=>'τ','Υ'=>'υ','Φ'=>'φ','Χ'=>'χ','Ψ'=>'ψ','Ω'=>'ω','Ϊ'=>'ϊ','Ϋ'=>'ϋ','ς'=>'σ','ϐ'=>'β','ϑ'=>'θ','ϕ'=>'φ','ϖ'=>'π','Ϙ'=>'ϙ','Ϛ'=>'ϛ','Ϝ'=>'ϝ','Ϟ'=>'ϟ','Ϡ'=>'ϡ','Ϣ'=>'ϣ','Ϥ'=>'ϥ','Ϧ'=>'ϧ','Ϩ'=>'ϩ','Ϫ'=>'ϫ','Ϭ'=>'ϭ','Ϯ'=>'ϯ','ϰ'=>'κ','ϱ'=>'ρ','ϴ'=>'θ','ϵ'=>'ε','Ϸ'=>'ϸ','Ϲ'=>'ϲ','Ϻ'=>'ϻ','Ͻ'=>'ͻ','Ͼ'=>'ͼ','Ͽ'=>'ͽ','Ѐ'=>'ѐ','Ё'=>'ё','Ђ'=>'ђ','Ѓ'=>'ѓ','Є'=>'є','Ѕ'=>'ѕ','І'=>'і','Ї'=>'ї','Ј'=>'ј','Љ'=>'љ','Њ'=>'њ','Ћ'=>'ћ','Ќ'=>'ќ','Ѝ'=>'ѝ','Ў'=>'ў','Џ'=>'џ','А'=>'а','Б'=>'б','В'=>'в','Г'=>'г','Д'=>'д','Е'=>'е','Ж'=>'ж','З'=>'з','И'=>'и','Й'=>'й','К'=>'к','Л'=>'л','М'=>'м','Н'=>'н','О'=>'о','П'=>'п','Р'=>'р','С'=>'с','Т'=>'т','У'=>'у','Ф'=>'ф','Х'=>'х','Ц'=>'ц','Ч'=>'ч','Ш'=>'ш','Щ'=>'щ','Ъ'=>'ъ','Ы'=>'ы','Ь'=>'ь','Э'=>'э','Ю'=>'ю','Я'=>'я','Ѡ'=>'ѡ','Ѣ'=>'ѣ','Ѥ'=>'ѥ','Ѧ'=>'ѧ','Ѩ'=>'ѩ','Ѫ'=>'ѫ','Ѭ'=>'ѭ','Ѯ'=>'ѯ','Ѱ'=>'ѱ','Ѳ'=>'ѳ','Ѵ'=>'ѵ','Ѷ'=>'ѷ','Ѹ'=>'ѹ','Ѻ'=>'ѻ','Ѽ'=>'ѽ','Ѿ'=>'ѿ','Ҁ'=>'ҁ','Ҋ'=>'ҋ','Ҍ'=>'ҍ','Ҏ'=>'ҏ','Ґ'=>'ґ','Ғ'=>'ғ','Ҕ'=>'ҕ','Җ'=>'җ','Ҙ'=>'ҙ','Қ'=>'қ','Ҝ'=>'ҝ','Ҟ'=>'ҟ','Ҡ'=>'ҡ','Ң'=>'ң','Ҥ'=>'ҥ','Ҧ'=>'ҧ','Ҩ'=>'ҩ','Ҫ'=>'ҫ','Ҭ'=>'ҭ','Ү'=>'ү','Ұ'=>'ұ','Ҳ'=>'ҳ','Ҵ'=>'ҵ','Ҷ'=>'ҷ','Ҹ'=>'ҹ','Һ'=>'һ','Ҽ'=>'ҽ','Ҿ'=>'ҿ','Ӏ'=>'ӏ','Ӂ'=>'ӂ','Ӄ'=>'ӄ','Ӆ'=>'ӆ','Ӈ'=>'ӈ','Ӊ'=>'ӊ','Ӌ'=>'ӌ','Ӎ'=>'ӎ','Ӑ'=>'ӑ','Ӓ'=>'ӓ','Ӕ'=>'ӕ','Ӗ'=>'ӗ','Ә'=>'ә','Ӛ'=>'ӛ','Ӝ'=>'ӝ','Ӟ'=>'ӟ','Ӡ'=>'ӡ','Ӣ'=>'ӣ','Ӥ'=>'ӥ','Ӧ'=>'ӧ','Ө'=>'ө','Ӫ'=>'ӫ','Ӭ'=>'ӭ','Ӯ'=>'ӯ','Ӱ'=>'ӱ','Ӳ'=>'ӳ','Ӵ'=>'ӵ','Ӷ'=>'ӷ','Ӹ'=>'ӹ','Ӻ'=>'ӻ','Ӽ'=>'ӽ','Ӿ'=>'ӿ','Ԁ'=>'ԁ','Ԃ'=>'ԃ','Ԅ'=>'ԅ','Ԇ'=>'ԇ','Ԉ'=>'ԉ','Ԋ'=>'ԋ','Ԍ'=>'ԍ','Ԏ'=>'ԏ','Ԑ'=>'ԑ','Ԓ'=>'ԓ','Ա'=>'ա','Բ'=>'բ','Գ'=>'գ','Դ'=>'դ','Ե'=>'ե','Զ'=>'զ','Է'=>'է','Ը'=>'ը','Թ'=>'թ','Ժ'=>'ժ','Ի'=>'ի','Լ'=>'լ','Խ'=>'խ','Ծ'=>'ծ','Կ'=>'կ','Հ'=>'հ','Ձ'=>'ձ','Ղ'=>'ղ','Ճ'=>'ճ','Մ'=>'մ','Յ'=>'յ','Ն'=>'ն','Շ'=>'շ','Ո'=>'ո','Չ'=>'չ','Պ'=>'պ','Ջ'=>'ջ','Ռ'=>'ռ','Ս'=>'ս','Վ'=>'վ','Տ'=>'տ','Ր'=>'ր','Ց'=>'ց','Ւ'=>'ւ','Փ'=>'փ','Ք'=>'ք','Օ'=>'օ','Ֆ'=>'ֆ','Ⴀ'=>'ⴀ','Ⴁ'=>'ⴁ','Ⴂ'=>'ⴂ','Ⴃ'=>'ⴃ','Ⴄ'=>'ⴄ','Ⴅ'=>'ⴅ','Ⴆ'=>'ⴆ','Ⴇ'=>'ⴇ','Ⴈ'=>'ⴈ','Ⴉ'=>'ⴉ','Ⴊ'=>'ⴊ','Ⴋ'=>'ⴋ','Ⴌ'=>'ⴌ','Ⴍ'=>'ⴍ','Ⴎ'=>'ⴎ','Ⴏ'=>'ⴏ','Ⴐ'=>'ⴐ','Ⴑ'=>'ⴑ','Ⴒ'=>'ⴒ','Ⴓ'=>'ⴓ','Ⴔ'=>'ⴔ','Ⴕ'=>'ⴕ','Ⴖ'=>'ⴖ','Ⴗ'=>'ⴗ','Ⴘ'=>'ⴘ','Ⴙ'=>'ⴙ','Ⴚ'=>'ⴚ','Ⴛ'=>'ⴛ','Ⴜ'=>'ⴜ','Ⴝ'=>'ⴝ','Ⴞ'=>'ⴞ','Ⴟ'=>'ⴟ','Ⴠ'=>'ⴠ','Ⴡ'=>'ⴡ','Ⴢ'=>'ⴢ','Ⴣ'=>'ⴣ','Ⴤ'=>'ⴤ','Ⴥ'=>'ⴥ','Ḁ'=>'ḁ','Ḃ'=>'ḃ','Ḅ'=>'ḅ','Ḇ'=>'ḇ','Ḉ'=>'ḉ','Ḋ'=>'ḋ','Ḍ'=>'ḍ','Ḏ'=>'ḏ','Ḑ'=>'ḑ','Ḓ'=>'ḓ','Ḕ'=>'ḕ','Ḗ'=>'ḗ','Ḙ'=>'ḙ','Ḛ'=>'ḛ','Ḝ'=>'ḝ','Ḟ'=>'ḟ','Ḡ'=>'ḡ','Ḣ'=>'ḣ','Ḥ'=>'ḥ','Ḧ'=>'ḧ','Ḩ'=>'ḩ','Ḫ'=>'ḫ','Ḭ'=>'ḭ','Ḯ'=>'ḯ','Ḱ'=>'ḱ','Ḳ'=>'ḳ','Ḵ'=>'ḵ','Ḷ'=>'ḷ','Ḹ'=>'ḹ','Ḻ'=>'ḻ','Ḽ'=>'ḽ','Ḿ'=>'ḿ','Ṁ'=>'ṁ','Ṃ'=>'ṃ','Ṅ'=>'ṅ','Ṇ'=>'ṇ','Ṉ'=>'ṉ','Ṋ'=>'ṋ','Ṍ'=>'ṍ','Ṏ'=>'ṏ','Ṑ'=>'ṑ','Ṓ'=>'ṓ','Ṕ'=>'ṕ','Ṗ'=>'ṗ','Ṙ'=>'ṙ','Ṛ'=>'ṛ','Ṝ'=>'ṝ','Ṟ'=>'ṟ','Ṡ'=>'ṡ','Ṣ'=>'ṣ','Ṥ'=>'ṥ','Ṧ'=>'ṧ','Ṩ'=>'ṩ','Ṫ'=>'ṫ','Ṭ'=>'ṭ','Ṯ'=>'ṯ','Ṱ'=>'ṱ','Ṳ'=>'ṳ','Ṵ'=>'ṵ','Ṷ'=>'ṷ','Ṹ'=>'ṹ','Ṻ'=>'ṻ','Ṽ'=>'ṽ','Ṿ'=>'ṿ','Ẁ'=>'ẁ','Ẃ'=>'ẃ','Ẅ'=>'ẅ','Ẇ'=>'ẇ','Ẉ'=>'ẉ','Ẋ'=>'ẋ','Ẍ'=>'ẍ','Ẏ'=>'ẏ','Ẑ'=>'ẑ','Ẓ'=>'ẓ','Ẕ'=>'ẕ','ẛ'=>'ṡ','Ạ'=>'ạ','Ả'=>'ả','Ấ'=>'ấ','Ầ'=>'ầ','Ẩ'=>'ẩ','Ẫ'=>'ẫ','Ậ'=>'ậ','Ắ'=>'ắ','Ằ'=>'ằ','Ẳ'=>'ẳ','Ẵ'=>'ẵ','Ặ'=>'ặ','Ẹ'=>'ẹ','Ẻ'=>'ẻ','Ẽ'=>'ẽ','Ế'=>'ế','Ề'=>'ề','Ể'=>'ể','Ễ'=>'ễ','Ệ'=>'ệ','Ỉ'=>'ỉ','Ị'=>'ị','Ọ'=>'ọ','Ỏ'=>'ỏ','Ố'=>'ố','Ồ'=>'ồ','Ổ'=>'ổ','Ỗ'=>'ỗ','Ộ'=>'ộ','Ớ'=>'ớ','Ờ'=>'ờ','Ở'=>'ở','Ỡ'=>'ỡ','Ợ'=>'ợ','Ụ'=>'ụ','Ủ'=>'ủ','Ứ'=>'ứ','Ừ'=>'ừ','Ử'=>'ử','Ữ'=>'ữ','Ự'=>'ự','Ỳ'=>'ỳ','Ỵ'=>'ỵ','Ỷ'=>'ỷ','Ỹ'=>'ỹ','Ἀ'=>'ἀ','Ἁ'=>'ἁ','Ἂ'=>'ἂ','Ἃ'=>'ἃ','Ἄ'=>'ἄ','Ἅ'=>'ἅ','Ἆ'=>'ἆ','Ἇ'=>'ἇ','Ἐ'=>'ἐ','Ἑ'=>'ἑ','Ἒ'=>'ἒ','Ἓ'=>'ἓ','Ἔ'=>'ἔ','Ἕ'=>'ἕ','Ἠ'=>'ἠ','Ἡ'=>'ἡ','Ἢ'=>'ἢ','Ἣ'=>'ἣ','Ἤ'=>'ἤ','Ἥ'=>'ἥ','Ἦ'=>'ἦ','Ἧ'=>'ἧ','Ἰ'=>'ἰ','Ἱ'=>'ἱ','Ἲ'=>'ἲ','Ἳ'=>'ἳ','Ἴ'=>'ἴ','Ἵ'=>'ἵ','Ἶ'=>'ἶ','Ἷ'=>'ἷ','Ὀ'=>'ὀ','Ὁ'=>'ὁ','Ὂ'=>'ὂ','Ὃ'=>'ὃ','Ὄ'=>'ὄ','Ὅ'=>'ὅ','Ὑ'=>'ὑ','Ὓ'=>'ὓ','Ὕ'=>'ὕ','Ὗ'=>'ὗ','Ὠ'=>'ὠ','Ὡ'=>'ὡ','Ὢ'=>'ὢ','Ὣ'=>'ὣ','Ὤ'=>'ὤ','Ὥ'=>'ὥ','Ὦ'=>'ὦ','Ὧ'=>'ὧ','Ᾰ'=>'ᾰ','Ᾱ'=>'ᾱ','Ὰ'=>'ὰ','Ά'=>'ά','ι'=>'ι','Ὲ'=>'ὲ','Έ'=>'έ','Ὴ'=>'ὴ','Ή'=>'ή','Ῐ'=>'ῐ','Ῑ'=>'ῑ','Ὶ'=>'ὶ','Ί'=>'ί','Ῠ'=>'ῠ','Ῡ'=>'ῡ','Ὺ'=>'ὺ','Ύ'=>'ύ','Ῥ'=>'ῥ','Ὸ'=>'ὸ','Ό'=>'ό','Ὼ'=>'ὼ','Ώ'=>'ώ','Ω'=>'ω','K'=>'k','Å'=>'å','Ⅎ'=>'ⅎ','Ⅰ'=>'ⅰ','Ⅱ'=>'ⅱ','Ⅲ'=>'ⅲ','Ⅳ'=>'ⅳ','Ⅴ'=>'ⅴ','Ⅵ'=>'ⅵ','Ⅶ'=>'ⅶ','Ⅷ'=>'ⅷ','Ⅸ'=>'ⅸ','Ⅹ'=>'ⅹ','Ⅺ'=>'ⅺ','Ⅻ'=>'ⅻ','Ⅼ'=>'ⅼ','Ⅽ'=>'ⅽ','Ⅾ'=>'ⅾ','Ⅿ'=>'ⅿ','Ↄ'=>'ↄ','Ⓐ'=>'ⓐ','Ⓑ'=>'ⓑ','Ⓒ'=>'ⓒ','Ⓓ'=>'ⓓ','Ⓔ'=>'ⓔ','Ⓕ'=>'ⓕ','Ⓖ'=>'ⓖ','Ⓗ'=>'ⓗ','Ⓘ'=>'ⓘ','Ⓙ'=>'ⓙ','Ⓚ'=>'ⓚ','Ⓛ'=>'ⓛ','Ⓜ'=>'ⓜ','Ⓝ'=>'ⓝ','Ⓞ'=>'ⓞ','Ⓟ'=>'ⓟ','Ⓠ'=>'ⓠ','Ⓡ'=>'ⓡ','Ⓢ'=>'ⓢ','Ⓣ'=>'ⓣ','Ⓤ'=>'ⓤ','Ⓥ'=>'ⓥ','Ⓦ'=>'ⓦ','Ⓧ'=>'ⓧ','Ⓨ'=>'ⓨ','Ⓩ'=>'ⓩ','Ⰰ'=>'ⰰ','Ⰱ'=>'ⰱ','Ⰲ'=>'ⰲ','Ⰳ'=>'ⰳ','Ⰴ'=>'ⰴ','Ⰵ'=>'ⰵ','Ⰶ'=>'ⰶ','Ⰷ'=>'ⰷ','Ⰸ'=>'ⰸ','Ⰹ'=>'ⰹ','Ⰺ'=>'ⰺ','Ⰻ'=>'ⰻ','Ⰼ'=>'ⰼ','Ⰽ'=>'ⰽ','Ⰾ'=>'ⰾ','Ⰿ'=>'ⰿ','Ⱀ'=>'ⱀ','Ⱁ'=>'ⱁ','Ⱂ'=>'ⱂ','Ⱃ'=>'ⱃ','Ⱄ'=>'ⱄ','Ⱅ'=>'ⱅ','Ⱆ'=>'ⱆ','Ⱇ'=>'ⱇ','Ⱈ'=>'ⱈ','Ⱉ'=>'ⱉ','Ⱊ'=>'ⱊ','Ⱋ'=>'ⱋ','Ⱌ'=>'ⱌ','Ⱍ'=>'ⱍ','Ⱎ'=>'ⱎ','Ⱏ'=>'ⱏ','Ⱐ'=>'ⱐ','Ⱑ'=>'ⱑ','Ⱒ'=>'ⱒ','Ⱓ'=>'ⱓ','Ⱔ'=>'ⱔ','Ⱕ'=>'ⱕ','Ⱖ'=>'ⱖ','Ⱗ'=>'ⱗ','Ⱘ'=>'ⱘ','Ⱙ'=>'ⱙ','Ⱚ'=>'ⱚ','Ⱛ'=>'ⱛ','Ⱜ'=>'ⱜ','Ⱝ'=>'ⱝ','Ⱞ'=>'ⱞ','Ⱡ'=>'ⱡ','Ɫ'=>'ɫ','Ᵽ'=>'ᵽ','Ɽ'=>'ɽ','Ⱨ'=>'ⱨ','Ⱪ'=>'ⱪ','Ⱬ'=>'ⱬ','Ⱶ'=>'ⱶ','Ⲁ'=>'ⲁ','Ⲃ'=>'ⲃ','Ⲅ'=>'ⲅ','Ⲇ'=>'ⲇ','Ⲉ'=>'ⲉ','Ⲋ'=>'ⲋ','Ⲍ'=>'ⲍ','Ⲏ'=>'ⲏ','Ⲑ'=>'ⲑ','Ⲓ'=>'ⲓ','Ⲕ'=>'ⲕ','Ⲗ'=>'ⲗ','Ⲙ'=>'ⲙ','Ⲛ'=>'ⲛ','Ⲝ'=>'ⲝ','Ⲟ'=>'ⲟ','Ⲡ'=>'ⲡ','Ⲣ'=>'ⲣ','Ⲥ'=>'ⲥ','Ⲧ'=>'ⲧ','Ⲩ'=>'ⲩ','Ⲫ'=>'ⲫ','Ⲭ'=>'ⲭ','Ⲯ'=>'ⲯ','Ⲱ'=>'ⲱ','Ⲳ'=>'ⲳ','Ⲵ'=>'ⲵ','Ⲷ'=>'ⲷ','Ⲹ'=>'ⲹ','Ⲻ'=>'ⲻ','Ⲽ'=>'ⲽ','Ⲿ'=>'ⲿ','Ⳁ'=>'ⳁ','Ⳃ'=>'ⳃ','Ⳅ'=>'ⳅ','Ⳇ'=>'ⳇ','Ⳉ'=>'ⳉ','Ⳋ'=>'ⳋ','Ⳍ'=>'ⳍ','Ⳏ'=>'ⳏ','Ⳑ'=>'ⳑ','Ⳓ'=>'ⳓ','Ⳕ'=>'ⳕ','Ⳗ'=>'ⳗ','Ⳙ'=>'ⳙ','Ⳛ'=>'ⳛ','Ⳝ'=>'ⳝ','Ⳟ'=>'ⳟ','Ⳡ'=>'ⳡ','Ⳣ'=>'ⳣ','A'=>'a','B'=>'b','C'=>'c','D'=>'d','E'=>'e','F'=>'f','G'=>'g','H'=>'h','I'=>'i','J'=>'j','K'=>'k','L'=>'l','M'=>'m','N'=>'n','O'=>'o','P'=>'p','Q'=>'q','R'=>'r','S'=>'s','T'=>'t','U'=>'u','V'=>'v','W'=>'w','X'=>'x','Y'=>'y','Z'=>'z','𐐀'=>'𐐨','𐐁'=>'𐐩','𐐂'=>'𐐪','𐐃'=>'𐐫','𐐄'=>'𐐬','𐐅'=>'𐐭','𐐆'=>'𐐮','𐐇'=>'𐐯','𐐈'=>'𐐰','𐐉'=>'𐐱','𐐊'=>'𐐲','𐐋'=>'𐐳','𐐌'=>'𐐴','𐐍'=>'𐐵','𐐎'=>'𐐶','𐐏'=>'𐐷','𐐐'=>'𐐸','𐐑'=>'𐐹','𐐒'=>'𐐺','𐐓'=>'𐐻','𐐔'=>'𐐼','𐐕'=>'𐐽','𐐖'=>'𐐾','𐐗'=>'𐐿','𐐘'=>'𐑀','𐐙'=>'𐑁','𐐚'=>'𐑂','𐐛'=>'𐑃','𐐜'=>'𐑄','𐐝'=>'𐑅','𐐞'=>'𐑆','𐐟'=>'𐑇','𐐠'=>'𐑈','𐐡'=>'𐑉','𐐢'=>'𐑊','𐐣'=>'𐑋','𐐤'=>'𐑌','𐐥'=>'𐑍','𐐦'=>'𐑎','𐐧'=>'𐑏'); diff --git a/includes/utf/data/case_fold_f.php b/includes/utf/data/case_fold_f.php new file mode 100644 index 0000000..5f2d88e --- /dev/null +++ b/includes/utf/data/case_fold_f.php @@ -0,0 +1 @@ +'ss','İ'=>'i̇','ʼn'=>'ʼn','ǰ'=>'ǰ','ΐ'=>'ΐ','ΰ'=>'ΰ','և'=>'եւ','ẖ'=>'ẖ','ẗ'=>'ẗ','ẘ'=>'ẘ','ẙ'=>'ẙ','ẚ'=>'aʾ','ὐ'=>'ὐ','ὒ'=>'ὒ','ὔ'=>'ὔ','ὖ'=>'ὖ','ᾀ'=>'ἀι','ᾁ'=>'ἁι','ᾂ'=>'ἂι','ᾃ'=>'ἃι','ᾄ'=>'ἄι','ᾅ'=>'ἅι','ᾆ'=>'ἆι','ᾇ'=>'ἇι','ᾈ'=>'ἀι','ᾉ'=>'ἁι','ᾊ'=>'ἂι','ᾋ'=>'ἃι','ᾌ'=>'ἄι','ᾍ'=>'ἅι','ᾎ'=>'ἆι','ᾏ'=>'ἇι','ᾐ'=>'ἠι','ᾑ'=>'ἡι','ᾒ'=>'ἢι','ᾓ'=>'ἣι','ᾔ'=>'ἤι','ᾕ'=>'ἥι','ᾖ'=>'ἦι','ᾗ'=>'ἧι','ᾘ'=>'ἠι','ᾙ'=>'ἡι','ᾚ'=>'ἢι','ᾛ'=>'ἣι','ᾜ'=>'ἤι','ᾝ'=>'ἥι','ᾞ'=>'ἦι','ᾟ'=>'ἧι','ᾠ'=>'ὠι','ᾡ'=>'ὡι','ᾢ'=>'ὢι','ᾣ'=>'ὣι','ᾤ'=>'ὤι','ᾥ'=>'ὥι','ᾦ'=>'ὦι','ᾧ'=>'ὧι','ᾨ'=>'ὠι','ᾩ'=>'ὡι','ᾪ'=>'ὢι','ᾫ'=>'ὣι','ᾬ'=>'ὤι','ᾭ'=>'ὥι','ᾮ'=>'ὦι','ᾯ'=>'ὧι','ᾲ'=>'ὰι','ᾳ'=>'αι','ᾴ'=>'άι','ᾶ'=>'ᾶ','ᾷ'=>'ᾶι','ᾼ'=>'αι','ῂ'=>'ὴι','ῃ'=>'ηι','ῄ'=>'ήι','ῆ'=>'ῆ','ῇ'=>'ῆι','ῌ'=>'ηι','ῒ'=>'ῒ','ΐ'=>'ΐ','ῖ'=>'ῖ','ῗ'=>'ῗ','ῢ'=>'ῢ','ΰ'=>'ΰ','ῤ'=>'ῤ','ῦ'=>'ῦ','ῧ'=>'ῧ','ῲ'=>'ὼι','ῳ'=>'ωι','ῴ'=>'ώι','ῶ'=>'ῶ','ῷ'=>'ῶι','ῼ'=>'ωι','ff'=>'ff','fi'=>'fi','fl'=>'fl','ffi'=>'ffi','ffl'=>'ffl','ſt'=>'st','st'=>'st','ﬓ'=>'մն','ﬔ'=>'մե','ﬕ'=>'մի','ﬖ'=>'վն','ﬗ'=>'մխ'); diff --git a/includes/utf/data/case_fold_s.php b/includes/utf/data/case_fold_s.php new file mode 100644 index 0000000..4ee5d8d --- /dev/null +++ b/includes/utf/data/case_fold_s.php @@ -0,0 +1 @@ +'ᾀ','ᾉ'=>'ᾁ','ᾊ'=>'ᾂ','ᾋ'=>'ᾃ','ᾌ'=>'ᾄ','ᾍ'=>'ᾅ','ᾎ'=>'ᾆ','ᾏ'=>'ᾇ','ᾘ'=>'ᾐ','ᾙ'=>'ᾑ','ᾚ'=>'ᾒ','ᾛ'=>'ᾓ','ᾜ'=>'ᾔ','ᾝ'=>'ᾕ','ᾞ'=>'ᾖ','ᾟ'=>'ᾗ','ᾨ'=>'ᾠ','ᾩ'=>'ᾡ','ᾪ'=>'ᾢ','ᾫ'=>'ᾣ','ᾬ'=>'ᾤ','ᾭ'=>'ᾥ','ᾮ'=>'ᾦ','ᾯ'=>'ᾧ','ᾼ'=>'ᾳ','ῌ'=>'ῃ','ῼ'=>'ῳ'); diff --git a/includes/utf/data/confusables.php b/includes/utf/data/confusables.php new file mode 100644 index 0000000..767d242 --- /dev/null +++ b/includes/utf/data/confusables.php @@ -0,0 +1 @@ +'i','ǃ'=>'!','α'=>'a',' '=>' ','­'=>'','۝'=>'','܏'=>'','᠆'=>'','᠎'=>'','​'=>'','‌'=>'','‍'=>'','
'=>'','
'=>'','⁠'=>'','⁡'=>'','⁢'=>'','⁣'=>'',''=>'',''=>'',''=>'',''=>'',''=>'',''=>'',''=>'',''=>'',''=>'',''=>'',''=>'','𝅳'=>'','𝅴'=>'','𝅵'=>'','𝅶'=>'','𝅷'=>'','𝅸'=>'','𝅹'=>'','𝅺'=>'','۬'=>'۟','̓'=>'̓','ُ'=>'̓','֜'=>'́','́'=>'́','݇'=>'́','॔'=>'́','̀'=>'̀','॓'=>'̀','̌'=>'̆','̑'=>'̂','֯'=>'̊','ஂ'=>'̊','ํ'=>'̊','ໍ'=>'̊','ံ'=>'̊','ំ'=>'̊','៓'=>'̊','゚'=>'̊','゚'=>'̊','ͦ'=>'̊','͂'=>'̃','ׄ'=>'̇','ֹ'=>'̇','ׂ'=>'̇','ׁ'=>'̇','݁'=>'̇','ं'=>'̇','ਂ'=>'̇','ં'=>'̇','்'=>'̇','̅'=>'̄','〬'=>'̉','̱'=>'̠','॒'=>'̠','̧'=>'̡','̦'=>'̡','̨'=>'̢','़'=>'̣','়'=>'̣','਼'=>'̣','઼'=>'̣','଼'=>'̣','͇'=>'̳','̶'=>'̵','ﱞ'=>'ﹲّ','ﱟ'=>'ﹴّ','ﳲ'=>'ﹷّ','ﱠ'=>'ﹶّ','ﳳ'=>'ﹹّ','ﱡ'=>'ﹸّ','ﳴ'=>'ﹻّ','ﱢ'=>'ﹺّ','ﱣ'=>'ﹼٰ','ٴ'=>'ٔ','݂'=>'ܼ','౦'=>'o','೦'=>'o','゙'=>'゙',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ',' '=>' ','`'=>'`','`'=>'`','῀'=>'˜','^'=>'^','︿'=>'^','_'=>'_','﹍'=>'_','﹎'=>'_','﹏'=>'_','⌇'=>'︴','-'=>'-','‐'=>'-','‑'=>'-','‒'=>'-','–'=>'-','﹘'=>'-','∼'=>'⁓','・'=>'・','•'=>'・',','=>',','‚'=>',','٬'=>'،','、'=>'、',';'=>';',';'=>';',':'=>':','։'=>':','︰'=>':','׃'=>':','⩴'=>'::=','.'=>'.','․'=>'.','܂'=>'.','‥'=>'..','…'=>'...','。'=>'。','·'=>'·','‧'=>'·','∙'=>'·','⋅'=>'·','ᐧ'=>'·','ᔯ'=>'·4','ᐌ'=>'·ᐁ','ᐎ'=>'·ᐃ','ᐐ'=>'·ᐄ','ᐒ'=>'·ᐅ','ᐔ'=>'·ᐆ','ᐗ'=>'·ᐊ','ᐙ'=>'·ᐋ','ᐷ'=>'·ᐳ','ᑀ'=>'·ᐳ','ᑂ'=>'·ᐴ','ᑄ'=>'·ᐸ','ᑆ'=>'·ᐹ','ᑗ'=>'·ᑌ','ᑙ'=>'·ᑎ','ᑛ'=>'·ᑏ','ᑔ'=>'·ᑐ','ᑝ'=>'·ᑐ','ᑟ'=>'·ᑑ','ᑡ'=>'·ᑕ','ᑣ'=>'·ᑖ','ᑴ'=>'·ᑫ','ᑸ'=>'·ᑮ','ᑼ'=>'·ᑰ','ᑾ'=>'·ᑲ','ᒀ'=>'·ᑳ','ᒒ'=>'·ᒉ','ᒔ'=>'·ᒋ','ᒖ'=>'·ᒌ','ᒚ'=>'·ᒎ','ᒜ'=>'·ᒐ','ᒞ'=>'·ᒑ','ᒬ'=>'·ᒣ','ᒮ'=>'·ᒥ','ᒰ'=>'·ᒦ','ᒲ'=>'·ᒧ','ᒴ'=>'·ᒨ','ᒶ'=>'·L','ᒸ'=>'·ᒫ','ᓉ'=>'·ᓀ','ᓋ'=>'·ᓇ','ᓍ'=>'·ᓈ','ᓜ'=>'·ᓓ','ᓞ'=>'·ᓕ','ᓠ'=>'·ᓖ','ᓢ'=>'·ᓗ','ᓤ'=>'·ᓘ','ᓦ'=>'·ᓚ','ᓨ'=>'·ᓛ','ᓶ'=>'·ᓭ','ᓸ'=>'·ᓯ','ᓺ'=>'·ᓰ','ᓼ'=>'·ᓱ','ᓾ'=>'·ᓲ','ᔀ'=>'·ᓴ','ᔂ'=>'·ᓵ','ᔗ'=>'·ᔐ','ᔙ'=>'·ᔑ','ᔛ'=>'·ᔒ','ᔝ'=>'·ᔓ','ᔟ'=>'·ᔔ','ᔡ'=>'·ᔕ','ᔣ'=>'·ᔖ','ᔱ'=>'·ᔨ','ᔳ'=>'·ᔩ','ᔵ'=>'·ᔪ','ᔷ'=>'·ᔫ','ᔹ'=>'·ᔭ','ᔻ'=>'·ᔮ','ᕎ'=>'·ᕌ','ᕛ'=>'·ᕚ','ᕨ'=>'·ᕧ','('=>'(','⑴'=>'(1)','⒧'=>'(l)','⑽'=>'(10)','⑾'=>'(11)','⑿'=>'(12)','⒀'=>'(13)','⒁'=>'(14)','⒂'=>'(15)','⒃'=>'(16)','⒄'=>'(17)','⒅'=>'(18)','⒆'=>'(19)','⑵'=>'(2)','⒇'=>'(20)','⑶'=>'(3)','⑷'=>'(4)','⑸'=>'(5)','⑹'=>'(6)','⑺'=>'(7)','⑻'=>'(8)','⑼'=>'(9)','⒜'=>'(a)','⒝'=>'(b)','⒞'=>'(c)','⒟'=>'(d)','⒠'=>'(e)','⒡'=>'(f)','⒢'=>'(g)','⒣'=>'(h)','⒤'=>'(i)','⒥'=>'(j)','⒦'=>'(k)','⒨'=>'(m)','⒩'=>'(n)','⒪'=>'(o)','⒫'=>'(p)','⒬'=>'(q)','⒭'=>'(r)','⒮'=>'(s)','⒯'=>'(t)','⒰'=>'(u)','⒱'=>'(v)','⒲'=>'(w)','⒳'=>'(x)','⒴'=>'(y)','⒵'=>'(z)','㈀'=>'(ᄀ)','㈎'=>'(가)','㈁'=>'(ᄂ)','㈏'=>'(나)','㈂'=>'(ᄃ)','㈐'=>'(다)','㈃'=>'(ᄅ)','㈑'=>'(라)','㈄'=>'(ᄆ)','㈒'=>'(마)','㈅'=>'(ᄇ)','㈓'=>'(바)','㈆'=>'(ᄉ)','㈔'=>'(사)','㈇'=>'(ᄋ)','㈕'=>'(아)','㈝'=>'(오전)','㈞'=>'(오후)','㈈'=>'(ᄌ)','㈖'=>'(자)','㈜'=>'(주)','㈉'=>'(ᄎ)','㈗'=>'(차)','㈊'=>'(ᄏ)','㈘'=>'(카)','㈋'=>'(ᄐ)','㈙'=>'(타)','㈌'=>'(ᄑ)','㈚'=>'(파)','㈍'=>'(ᄒ)','㈛'=>'(하)','㈠'=>'(一)','㈦'=>'(七)','㈢'=>'(三)','㈨'=>'(九)','㈡'=>'(二)','㈤'=>'(五)','㈹'=>'(代)','㈽'=>'(企)','㉁'=>'(休)','㈧'=>'(八)','㈥'=>'(六)','㈸'=>'(労)','㈩'=>'(十)','㈿'=>'(協)','㈴'=>'(名)','㈺'=>'(呼)','㈣'=>'(四)','㈯'=>'(土)','㈻'=>'(学)','㈰'=>'(日)','㈪'=>'(月)','㈲'=>'(有)','㈭'=>'(木)','㈱'=>'(株)','㈬'=>'(水)','㈫'=>'(火)','㈵'=>'(特)','㈼'=>'(監)','㈳'=>'(社)','㈷'=>'(祝)','㉀'=>'(祭)','㉂'=>'(自)','㉃'=>'(至)','㈶'=>'(財)','㈾'=>'(資)','㈮'=>'(金)',')'=>')','['=>'[','〔'=>'[',']'=>']','〕'=>']','{'=>'{','}'=>'}','⦅'=>'⦅','⦆'=>'⦆','「'=>'「','」'=>'」','@'=>'@','*'=>'*','/'=>'/','⁄'=>'/','∕'=>'/','\'=>'\\','&'=>'&','#'=>'#','%'=>'%','‶'=>'‵‵','‷'=>'‵‵‵','༌'=>'་','´'=>'ʹ','΄'=>'ʹ','´'=>'ʹ','\''=>'ʹ','''=>'ʹ','′'=>'ʹ','׳'=>'ʹ','ʹ'=>'ʹ','ˊ'=>'ʹ','"'=>'ʹʹ','"'=>'ʹʹ','″'=>'ʹʹ','〃'=>'ʹʹ','״'=>'ʹʹ','ʺ'=>'ʹʹ','‴'=>'ʹʹʹ','⁗'=>'ʹʹʹʹ','¯'=>'ˉ',' ̄'=>'ˉ','‾'=>'ˉ','﹉'=>'ˉ','﹊'=>'ˉ','﹋'=>'ˉ','﹌'=>'ˉ','˚'=>'°','௵'=>'௳','←'=>'←','→'=>'→','↑'=>'↑','↓'=>'↓','↵'=>'↲','⨡'=>'↾','𝛛'=>'∂','𝜕'=>'∂','𝝏'=>'∂','𝞉'=>'∂','𝟃'=>'∂','𝛁'=>'∇','𝛻'=>'∇','𝜵'=>'∇','𝝯'=>'∇','𝞩'=>'∇','+'=>'+','﬩'=>'+','‹'=>'<','<'=>'<','='=>'=','⩵'=>'==','⩶'=>'===','›'=>'>','>'=>'>','¬'=>'¬','¦'=>'¦','〜'=>'~','~'=>'~','﹨'=>'∖','⋀'=>'∧','⋁'=>'∨','⋂'=>'∩','⋃'=>'∪','∯'=>'∮∮','∰'=>'∮∮∮','≣'=>'≡','♁'=>'⊕','☉'=>'⊙','⟂'=>'⊥','▷'=>'⊲','⨝'=>'⋈','⨽'=>'⌙','☸'=>'⎈','⎮'=>'⎥','│'=>'│','▐'=>'▌','■'=>'■','☐'=>'□','○'=>'○','⦾'=>'◎','〛'=>'⟧','〈'=>'⟨','〈'=>'⟨','〉'=>'⟩','〉'=>'⟩','⧙'=>'⦚','〶'=>'〒','ー'=>'ー','¢'=>'¢','$'=>'$','£'=>'£','¥'=>'Y̵','₩'=>'W̵','0'=>'0','𝟎'=>'0','𝟘'=>'0','𝟢'=>'0','𝟬'=>'0','𝟶'=>'0','০'=>'0','୦'=>'0','௦'=>'0','᠐'=>'0','〇'=>'0','𝐎'=>'0','𝑂'=>'0','𝑶'=>'0','𝒪'=>'0','𝓞'=>'0','𝔒'=>'0','𝕆'=>'0','𝕺'=>'0','𝖮'=>'0','𝗢'=>'0','𝘖'=>'0','𝙊'=>'0','𝙾'=>'0','𝚶'=>'0','𝛰'=>'0','𝜪'=>'0','𝝤'=>'0','𝞞'=>'0','ⵔ'=>'0','ഠ'=>'0','⊖'=>'0̵','𝚯'=>'0̵','𝚹'=>'0̵','𝛩'=>'0̵','𝛳'=>'0̵','𝜣'=>'0̵','𝜭'=>'0̵','𝝝'=>'0̵','𝝧'=>'0̵','𝞗'=>'0̵','𝞡'=>'0̵','ⴱ'=>'0̵','Ꮎ'=>'0̵','۰'=>'٠','᭜'=>'᭐','㍘'=>'0点','1'=>'1','𝟏'=>'1','𝟙'=>'1','𝟣'=>'1','𝟭'=>'1','𝟷'=>'1','ℐ'=>'1','ℑ'=>'1','𝐈'=>'1','𝐼'=>'1','𝑰'=>'1','𝓘'=>'1','𝕀'=>'1','𝕴'=>'1','𝖨'=>'1','𝗜'=>'1','𝘐'=>'1','𝙄'=>'1','𝙸'=>'1','l'=>'l','l'=>'l','ⅼ'=>'1','ℓ'=>'l','𝐥'=>'l','𝑙'=>'l','𝒍'=>'l','𝓁'=>'l','𝓵'=>'l','𝔩'=>'l','𝕝'=>'l','𝖑'=>'l','𝗅'=>'l','𝗹'=>'l','𝘭'=>'l','𝙡'=>'l','𝚕'=>'l','𝚰'=>'l','𝛪'=>'l','𝜤'=>'l','𝝞'=>'l','𝞘'=>'l','①'=>'➀','ɭ'=>'l̢','ɫ'=>'l̴','ƚ'=>'l̵','ł'=>'l̷','۱'=>'١','⒈'=>'1.','ŀ'=>'l·','ᒷ'=>'1·','⑩'=>'➉','⒑'=>'10.','㏩'=>'10日','㋉'=>'10月','㍢'=>'10点','⒒'=>'11.','㏪'=>'11日','㋊'=>'11月','㍣'=>'11点','⒓'=>'12.','㏫'=>'12日','㋋'=>'12月','㍤'=>'12点','⒔'=>'13.','㏬'=>'13日','㍥'=>'13点','⒕'=>'14.','㏭'=>'14日','㍦'=>'14点','⒖'=>'15.','㏮'=>'15日','㍧'=>'15点','⒗'=>'16.','㏯'=>'16日','㍨'=>'16点','⒘'=>'17.','㏰'=>'17日','㍩'=>'17点','⒙'=>'18.','㏱'=>'18日','㍪'=>'18点','⒚'=>'19.','㏲'=>'19日','㍫'=>'19点','lj'=>'lj','㏠'=>'1日','㋀'=>'1月','㍙'=>'1点','2'=>'2','𝟐'=>'2','𝟚'=>'2','𝟤'=>'2','𝟮'=>'2','𝟸'=>'2','ᒿ'=>'2','②'=>'➁','۲'=>'٢','⒉'=>'2.','⒛'=>'20.','㏳'=>'20日','㍬'=>'20点','㏴'=>'21日','㍭'=>'21点','㏵'=>'22日','㍮'=>'22点','㏶'=>'23日','㍯'=>'23点','㏷'=>'24日','㍰'=>'24点','㏸'=>'25日','㏹'=>'26日','㏺'=>'27日','㏻'=>'28日','㏼'=>'29日','㏡'=>'2日','㋁'=>'2月','㍚'=>'2点','3'=>'3','𝟑'=>'3','𝟛'=>'3','𝟥'=>'3','𝟯'=>'3','𝟹'=>'3','③'=>'➂','۳'=>'٣','⒊'=>'3.','㏽'=>'30日','㏾'=>'31日','㏢'=>'3日','㋂'=>'3月','㍛'=>'3点','4'=>'4','𝟒'=>'4','𝟜'=>'4','𝟦'=>'4','𝟰'=>'4','𝟺'=>'4','Ꮞ'=>'4','④'=>'➃','⒋'=>'4.','ᔰ'=>'4·','㏣'=>'4日','㋃'=>'4月','㍜'=>'4点','5'=>'5','𝟓'=>'5','𝟝'=>'5','𝟧'=>'5','𝟱'=>'5','𝟻'=>'5','⑤'=>'➄','⒌'=>'5.','㏤'=>'5日','㋄'=>'5月','㍝'=>'5点','6'=>'6','𝟔'=>'6','𝟞'=>'6','𝟨'=>'6','𝟲'=>'6','𝟼'=>'6','б'=>'6','⑥'=>'➅','⒍'=>'6.','㏥'=>'6日','㋅'=>'6月','㍞'=>'6点','7'=>'7','𝟕'=>'7','𝟟'=>'7','𝟩'=>'7','𝟳'=>'7','𝟽'=>'7','⑦'=>'➆','۷'=>'٧','⒎'=>'7.','㏦'=>'7日','㋆'=>'7月','㍟'=>'7点','ଃ'=>'8','৪'=>'8','੪'=>'8','8'=>'8','𝟖'=>'8','𝟠'=>'8','𝟪'=>'8','𝟴'=>'8','𝟾'=>'8','ȣ'=>'8','⑧'=>'➇','۸'=>'٨','⒏'=>'8.','㏧'=>'8日','㋇'=>'8月','㍠'=>'8点','੧'=>'9','୨'=>'9','৭'=>'9','9'=>'9','𝟗'=>'9','𝟡'=>'9','𝟫'=>'9','𝟵'=>'9','𝟿'=>'9','⑨'=>'➈','۹'=>'٩','⒐'=>'9.','㏨'=>'9日','㋈'=>'9月','㍡'=>'9点','a'=>'a','𝐚'=>'a','𝑎'=>'a','𝒂'=>'a','𝒶'=>'a','𝓪'=>'a','𝔞'=>'a','𝕒'=>'a','𝖆'=>'a','𝖺'=>'a','𝗮'=>'a','𝘢'=>'a','𝙖'=>'a','𝚊'=>'a','℀'=>'a/c','℁'=>'a/s','æ'=>'ae','b'=>'b','𝐛'=>'b','𝑏'=>'b','𝒃'=>'b','𝒷'=>'b','𝓫'=>'b','𝔟'=>'b','𝕓'=>'b','𝖇'=>'b','𝖻'=>'b','𝗯'=>'b','𝘣'=>'b','𝙗'=>'b','𝚋'=>'b','ɓ'=>'b̔','ƃ'=>'b̄','ƀ'=>'b̵','c'=>'c','ⅽ'=>'c','𝐜'=>'c','𝑐'=>'c','𝒄'=>'c','𝒸'=>'c','𝓬'=>'c','𝔠'=>'c','𝕔'=>'c','𝖈'=>'c','𝖼'=>'c','𝗰'=>'c','𝘤'=>'c','𝙘'=>'c','𝚌'=>'c','𝛓'=>'c','𝜍'=>'c','𝝇'=>'c','𝞁'=>'c','𝞻'=>'c','℅'=>'c/o','℆'=>'c/u','d'=>'d','ⅾ'=>'d','ⅆ'=>'d','𝐝'=>'d','𝑑'=>'d','𝒅'=>'d','𝒹'=>'d','𝓭'=>'d','𝔡'=>'d','𝕕'=>'d','𝖉'=>'d','𝖽'=>'d','𝗱'=>'d','𝘥'=>'d','𝙙'=>'d','𝚍'=>'d','ɗ'=>'d̔','ƌ'=>'d̄','ɖ'=>'d̢','đ'=>'d̵','dz'=>'dz','dž'=>'dž','e'=>'e','ℯ'=>'e','ⅇ'=>'e','𝐞'=>'e','𝑒'=>'e','𝒆'=>'e','𝓮'=>'e','𝔢'=>'e','𝕖'=>'e','𝖊'=>'e','𝖾'=>'e','𝗲'=>'e','𝘦'=>'e','𝙚'=>'e','𝚎'=>'e','ⴹ'=>'E','ə'=>'ǝ','ɚ'=>'ǝ˞','⋴'=>'ɛ','𝛆'=>'ɛ','𝛜'=>'ɛ','𝜀'=>'ɛ','𝜖'=>'ɛ','𝜺'=>'ɛ','𝝐'=>'ɛ','𝝴'=>'ɛ','𝞊'=>'ɛ','𝞮'=>'ɛ','𝟄'=>'ɛ','f'=>'f','𝐟'=>'f','𝑓'=>'f','𝒇'=>'f','𝒻'=>'f','𝓯'=>'f','𝔣'=>'f','𝕗'=>'f','𝖋'=>'f','𝖿'=>'f','𝗳'=>'f','𝘧'=>'f','𝙛'=>'f','𝚏'=>'f','ƒ'=>'f̡','g'=>'g','ℊ'=>'g','𝐠'=>'g','𝑔'=>'g','𝒈'=>'g','𝓰'=>'g','𝔤'=>'g','𝕘'=>'g','𝖌'=>'g','𝗀'=>'g','𝗴'=>'g','𝘨'=>'g','𝙜'=>'g','𝚐'=>'g','ɡ'=>'g','ɠ'=>'g̔','ǥ'=>'g̵','h'=>'h','ℎ'=>'h','𝐡'=>'h','𝒉'=>'h','𝒽'=>'h','𝓱'=>'h','𝔥'=>'h','𝕙'=>'h','𝖍'=>'h','𝗁'=>'h','𝗵'=>'h','𝘩'=>'h','𝙝'=>'h','𝚑'=>'h','ɦ'=>'h̔','ħ'=>'h̵','ℏ'=>'h̵','῾'=>'ʻ','‘'=>'ʻ','‛'=>'ʻ','ʽ'=>'ʻ','⍳'=>'i','i'=>'i','ⅰ'=>'i','ℹ'=>'i','ⅈ'=>'i','𝐢'=>'i','𝑖'=>'i','𝒊'=>'i','𝒾'=>'i','𝓲'=>'i','𝔦'=>'i','𝕚'=>'i','𝖎'=>'i','𝗂'=>'i','𝗶'=>'i','𝘪'=>'i','𝙞'=>'i','𝚒'=>'i','ı'=>'i','𝚤'=>'i','ɪ'=>'i','ɩ'=>'i','𝛊'=>'i','𝜄'=>'i','𝜾'=>'i','𝝸'=>'i','𝞲'=>'i','ɨ'=>'i̵','ⅱ'=>'ii','ⅲ'=>'iii','ij'=>'ij','ⅳ'=>'iv','ⅸ'=>'ix','j'=>'j','ⅉ'=>'j','𝐣'=>'j','𝑗'=>'j','𝒋'=>'j','𝒿'=>'j','𝓳'=>'j','𝔧'=>'j','𝕛'=>'j','𝖏'=>'j','𝗃'=>'j','𝗷'=>'j','𝘫'=>'j','𝙟'=>'j','𝚓'=>'j','ϳ'=>'j','𝚥'=>'ȷ','k'=>'k','𝐤'=>'k','𝑘'=>'k','𝒌'=>'k','𝓀'=>'k','𝓴'=>'k','𝔨'=>'k','𝕜'=>'k','𝖐'=>'k','𝗄'=>'k','𝗸'=>'k','𝘬'=>'k','𝙠'=>'k','𝚔'=>'k','ƙ'=>'k̔','m'=>'m','ⅿ'=>'m','𝐦'=>'m','𝑚'=>'m','𝒎'=>'m','𝓂'=>'m','𝓶'=>'m','𝔪'=>'m','𝕞'=>'m','𝖒'=>'m','𝗆'=>'m','𝗺'=>'m','𝘮'=>'m','𝙢'=>'m','𝚖'=>'m','ɱ'=>'m̡','n'=>'n','𝐧'=>'n','𝑛'=>'n','𝒏'=>'n','𝓃'=>'n','𝓷'=>'n','𝔫'=>'n','𝕟'=>'n','𝖓'=>'n','𝗇'=>'n','𝗻'=>'n','𝘯'=>'n','𝙣'=>'n','𝚗'=>'n','𝐍'=>'N','𝑁'=>'N','𝑵'=>'N','𝒩'=>'N','𝓝'=>'N','𝔑'=>'N','𝕹'=>'N','𝖭'=>'N','𝗡'=>'N','𝘕'=>'N','𝙉'=>'N','𝙽'=>'N','𝚴'=>'N','𝛮'=>'N','𝜨'=>'N','𝝢'=>'N','𝞜'=>'N','ɲ'=>'ņ','ɳ'=>'n̢','ƞ'=>'n̩','𝛈'=>'n̩','𝜂'=>'n̩','𝜼'=>'n̩','𝝶'=>'n̩','𝞰'=>'n̩','nj'=>'nj','o'=>'o','ℴ'=>'o','𝐨'=>'o','𝑜'=>'o','𝒐'=>'o','𝓸'=>'o','𝔬'=>'o','𝕠'=>'o','𝖔'=>'o','𝗈'=>'o','𝗼'=>'o','𝘰'=>'o','𝙤'=>'o','𝚘'=>'o','ᴏ'=>'o','𝛐'=>'o','𝜊'=>'o','𝝄'=>'o','𝝾'=>'o','𝞸'=>'o','ɵ'=>'o̵','ǿ'=>'ó̵','ø'=>'o̷','œ'=>'oe','ơ'=>'oʼ','⍴'=>'p','p'=>'p','𝐩'=>'p','𝑝'=>'p','𝒑'=>'p','𝓅'=>'p','𝓹'=>'p','𝔭'=>'p','𝕡'=>'p','𝖕'=>'p','𝗉'=>'p','𝗽'=>'p','𝘱'=>'p','𝙥'=>'p','𝚙'=>'p','𝛒'=>'p','𝛠'=>'p','𝜌'=>'p','𝜚'=>'p','𝝆'=>'p','𝝔'=>'p','𝞀'=>'p','𝞎'=>'p','𝞺'=>'p','𝟈'=>'p','ƥ'=>'p̔','q'=>'q','𝐪'=>'q','𝑞'=>'q','𝒒'=>'q','𝓆'=>'q','𝓺'=>'q','𝔮'=>'q','𝕢'=>'q','𝖖'=>'q','𝗊'=>'q','𝗾'=>'q','𝘲'=>'q','𝙦'=>'q','𝚚'=>'q','𝐐'=>'Q','𝑄'=>'Q','𝑸'=>'Q','𝒬'=>'Q','𝓠'=>'Q','𝔔'=>'Q','𝕼'=>'Q','𝖰'=>'Q','𝗤'=>'Q','𝘘'=>'Q','𝙌'=>'Q','𝚀'=>'Q','ʠ'=>'q̔','𝛋'=>'ĸ','𝛞'=>'ĸ','𝜅'=>'ĸ','𝜘'=>'ĸ','𝜿'=>'ĸ','𝝒'=>'ĸ','𝝹'=>'ĸ','𝞌'=>'ĸ','𝞳'=>'ĸ','𝟆'=>'ĸ','r'=>'r','𝐫'=>'r','𝑟'=>'r','𝒓'=>'r','𝓇'=>'r','𝓻'=>'r','𝔯'=>'r','𝕣'=>'r','𝖗'=>'r','𝗋'=>'r','𝗿'=>'r','𝘳'=>'r','𝙧'=>'r','𝚛'=>'r','ɽ'=>'r̢','ɼ'=>'r̩','s'=>'s','𝐬'=>'s','𝑠'=>'s','𝒔'=>'s','𝓈'=>'s','𝓼'=>'s','𝔰'=>'s','𝕤'=>'s','𝖘'=>'s','𝗌'=>'s','𝘀'=>'s','𝘴'=>'s','𝙨'=>'s','𝚜'=>'s','ƽ'=>'s','ʂ'=>'s̢','∫'=>'ʃ','∬'=>'ʃʃ','∭'=>'ʃʃʃ','⨌'=>'ʃʃʃʃ','t'=>'t','𝐭'=>'t','𝑡'=>'t','𝒕'=>'t','𝓉'=>'t','𝓽'=>'t','𝔱'=>'t','𝕥'=>'t','𝖙'=>'t','𝗍'=>'t','𝘁'=>'t','𝘵'=>'t','𝙩'=>'t','𝚝'=>'t','𝑇'=>'T','𝑻'=>'T','𝒯'=>'T','𝓣'=>'T','𝔗'=>'T','𝕋'=>'T','𝕿'=>'T','𝖳'=>'T','𝗧'=>'T','𝘛'=>'T','𝙏'=>'T','𝚃'=>'T','𝚻'=>'T','𝛵'=>'T','𝜯'=>'T','𝝩'=>'T','𝞣'=>'T','ƭ'=>'t̔','ț'=>'ţ','ƫ'=>'ţ','ŧ'=>'t̵','u'=>'u','𝐮'=>'u','𝑢'=>'u','𝒖'=>'u','𝓊'=>'u','𝓾'=>'u','𝔲'=>'u','𝕦'=>'u','𝖚'=>'u','𝗎'=>'u','𝘂'=>'u','𝘶'=>'u','𝙪'=>'u','𝚞'=>'u','ʊ'=>'u','ʋ'=>'u','𝛖'=>'u','𝜐'=>'u','𝝊'=>'u','𝞄'=>'u','𝞾'=>'u','𝑈'=>'U','𝑼'=>'U','𝒰'=>'U','𝓤'=>'U','𝔘'=>'U','𝕌'=>'U','𝖀'=>'U','𝖴'=>'U','𝗨'=>'U','𝘜'=>'U','𝙐'=>'U','𝚄'=>'U','v'=>'v','ⅴ'=>'v','𝐯'=>'v','𝑣'=>'v','𝒗'=>'v','𝓋'=>'v','𝓿'=>'v','𝔳'=>'v','𝕧'=>'v','𝖛'=>'v','𝗏'=>'v','𝘃'=>'v','𝘷'=>'v','𝙫'=>'v','𝚟'=>'v','𝛎'=>'v','𝜈'=>'v','𝝂'=>'v','𝝼'=>'v','𝞶'=>'v','ⅵ'=>'vi','ⅶ'=>'vii','ⅷ'=>'viii','ɯ'=>'w','w'=>'w','𝐰'=>'w','𝑤'=>'w','𝒘'=>'w','𝓌'=>'w','𝔀'=>'w','𝔴'=>'w','𝕨'=>'w','𝖜'=>'w','𝗐'=>'w','𝘄'=>'w','𝘸'=>'w','𝙬'=>'w','𝚠'=>'w','𝑊'=>'W','𝑾'=>'W','𝒲'=>'W','𝓦'=>'W','𝔚'=>'W','𝕎'=>'W','𝖂'=>'W','𝖶'=>'W','𝗪'=>'W','𝘞'=>'W','𝙒'=>'W','𝚆'=>'W','×'=>'x','x'=>'x','ⅹ'=>'x','𝐱'=>'x','𝑥'=>'x','𝒙'=>'x','𝓍'=>'x','𝔁'=>'x','𝔵'=>'x','𝕩'=>'x','𝖝'=>'x','𝗑'=>'x','𝘅'=>'x','𝘹'=>'x','𝙭'=>'x','𝚡'=>'x','᙭'=>'X','𝑋'=>'X','𝑿'=>'X','𝒳'=>'X','𝓧'=>'X','𝔛'=>'X','𝕏'=>'X','𝖃'=>'X','𝖷'=>'X','𝗫'=>'X','𝘟'=>'X','𝙓'=>'X','𝚇'=>'X','𝚾'=>'X','𝛸'=>'X','𝜲'=>'X','𝝬'=>'X','𝞦'=>'X','ⅺ'=>'xi','ⅻ'=>'xii','y'=>'y','𝐲'=>'y','𝑦'=>'y','𝒚'=>'y','𝓎'=>'y','𝔂'=>'y','𝔶'=>'y','𝕪'=>'y','𝖞'=>'y','𝗒'=>'y','𝘆'=>'y','𝘺'=>'y','𝙮'=>'y','𝚢'=>'y','ƴ'=>'y̔','z'=>'z','𝐳'=>'z','𝑧'=>'z','𝒛'=>'z','𝓏'=>'z','𝔃'=>'z','𝔷'=>'z','𝕫'=>'z','𝖟'=>'z','𝗓'=>'z','𝘇'=>'z','𝘻'=>'z','𝙯'=>'z','𝚣'=>'z','ȥ'=>'z̡','ʐ'=>'z̢','ƶ'=>'z̵','ȝ'=>'ʒ','?'=>'ʔ','?'=>'ʔ','⁇'=>'ʔʔ','⁈'=>'ʔǃ','᾽'=>'ʼ','᾿'=>'ʼ','’'=>'ʼ','ʾ'=>'ʼ','!'=>'ǃ','!'=>'ǃ','⁉'=>'ǃʔ','‼'=>'ǃǃ','⍺'=>'α','𝛂'=>'α','𝛼'=>'α','𝜶'=>'α','𝝰'=>'α','𝞪'=>'α','𝛃'=>'β','𝛽'=>'β','𝜷'=>'β','𝝱'=>'β','𝞫'=>'β','ℽ'=>'γ','𝛄'=>'γ','𝛾'=>'γ','𝜸'=>'γ','𝝲'=>'γ','𝞬'=>'γ','𝛅'=>'δ','𝛿'=>'δ','𝜹'=>'δ','𝝳'=>'δ','𝞭'=>'δ','𝟋'=>'ϝ','𝛇'=>'ζ','𝜁'=>'ζ','𝜻'=>'ζ','𝝵'=>'ζ','𝞯'=>'ζ','⍬'=>'θ','𝛉'=>'θ','𝛝'=>'θ','𝜃'=>'θ','𝜗'=>'θ','𝜽'=>'θ','𝝑'=>'θ','𝝷'=>'θ','𝞋'=>'θ','𝞱'=>'θ','𝟅'=>'θ','𝛌'=>'λ','𝜆'=>'λ','𝝀'=>'λ','𝝺'=>'λ','𝞴'=>'λ','𝛬'=>'Λ','𝜦'=>'Λ','𝝠'=>'Λ','𝞚'=>'Λ','𝛍'=>'μ','𝜇'=>'μ','𝝁'=>'μ','𝝻'=>'μ','𝞵'=>'μ','𝛏'=>'ξ','𝜉'=>'ξ','𝝃'=>'ξ','𝝽'=>'ξ','𝞷'=>'ξ','𝛯'=>'Ξ','𝜩'=>'Ξ','𝝣'=>'Ξ','𝞝'=>'Ξ','ℼ'=>'π','𝛑'=>'π','𝛡'=>'π','𝜋'=>'π','𝜛'=>'π','𝝅'=>'π','𝝕'=>'π','𝝿'=>'π','𝞏'=>'π','𝞹'=>'π','𝟉'=>'π','ᴨ'=>'π','∏'=>'Π','𝚷'=>'Π','𝛱'=>'Π','𝜫'=>'Π','𝝥'=>'Π','𝞟'=>'Π','𝛔'=>'σ','𝜎'=>'σ','𝝈'=>'σ','𝞂'=>'σ','𝞼'=>'σ','𝛕'=>'τ','𝜏'=>'τ','𝝉'=>'τ','𝞃'=>'τ','𝞽'=>'τ','𝐘'=>'Y','𝑌'=>'Y','𝒀'=>'Y','𝒴'=>'Y','𝓨'=>'Y','𝔜'=>'Y','𝕐'=>'Y','𝖄'=>'Y','𝖸'=>'Y','𝗬'=>'Y','𝘠'=>'Y','𝙔'=>'Y','𝚈'=>'Y','𝚼'=>'Y','𝛶'=>'Y','𝜰'=>'Y','𝝪'=>'Y','𝞤'=>'Y','𝛗'=>'φ','𝛟'=>'φ','𝜑'=>'φ','𝜙'=>'φ','𝝋'=>'φ','𝝓'=>'φ','𝞅'=>'φ','𝞍'=>'φ','𝞿'=>'φ','𝟇'=>'φ','𝛷'=>'Φ','𝜱'=>'Φ','𝝫'=>'Φ','𝞥'=>'Φ','𝛘'=>'χ','𝜒'=>'χ','𝝌'=>'χ','𝞆'=>'χ','𝟀'=>'χ','𝛙'=>'ψ','𝜓'=>'ψ','𝝍'=>'ψ','𝞇'=>'ψ','𝟁'=>'ψ','𝛹'=>'Ψ','𝜳'=>'Ψ','𝝭'=>'Ψ','𝞧'=>'Ψ','⍵'=>'ω','𝛚'=>'ω','𝜔'=>'ω','𝝎'=>'ω','𝞈'=>'ω','𝟂'=>'ω','ӕ'=>'ae','ғ'=>'r̵','ґ'=>'rᑊ','җ'=>'ж̩','ҙ'=>'з̡','ӏ'=>'i','ҋ'=>'й̡','қ'=>'ĸ̩','ҟ'=>'ĸ̵','ᴫ'=>'л','ӆ'=>'л̡','ӎ'=>'м̡','ӊ'=>'н̡','ӈ'=>'н̡','ң'=>'н̩','ө'=>'o̵','ѳ'=>'o̵','ҫ'=>'c̡','ҭ'=>'т̩','ү'=>'y','ұ'=>'y̵','ћ'=>'h̵','ѽ'=>'ѡ҃','ӌ'=>'ҷ','ҿ'=>'ҽ̢','ҍ'=>'Ь̵','զ'=>'q','ռ'=>'n','ℵ'=>'א','ﬡ'=>'א','אָ'=>'אַ','אּ'=>'אַ','ﭏ'=>'אל','ℶ'=>'ב','ℷ'=>'ג','ℸ'=>'ד','ﬢ'=>'ד','ﬣ'=>'ה','ﬤ'=>'כ','ﬥ'=>'ל','ﬦ'=>'ם','ﬠ'=>'ע','ﬧ'=>'ר','ﬨ'=>'ת','ﺀ'=>'ء','ﺂ'=>'آ','ﺁ'=>'آ','ﺄ'=>'أ','ﺃ'=>'أ','ٵ'=>'أ','ﭑ'=>'ٱ','ﭐ'=>'ٱ','ﺆ'=>'ؤ','ﺅ'=>'ؤ','ٶ'=>'ؤ','ﺈ'=>'إ','ﺇ'=>'إ','ﺋ'=>'ئ','ﺌ'=>'ئ','ﺊ'=>'ئ','ﺉ'=>'ئ','ﯫ'=>'ئا','ﯪ'=>'ئا','ﯸ'=>'ئٻ','ﯷ'=>'ئٻ','ﯶ'=>'ئٻ','ﲗ'=>'ئج','ﰀ'=>'ئج','ﲘ'=>'ئح','ﰁ'=>'ئح','ﲙ'=>'ئخ','ﱤ'=>'ئر','ﱥ'=>'ئز','ﲚ'=>'ئم','ﳟ'=>'ئم','ﱦ'=>'ئم','ﰂ'=>'ئم','ﱧ'=>'ئن','ﲛ'=>'ئه','ﳠ'=>'ئه','ﯭ'=>'ئه','ﯬ'=>'ئه','ﯯ'=>'ئو','ﯮ'=>'ئو','ﯳ'=>'ئۆ','ﯲ'=>'ئۆ','ﯱ'=>'ئۇ','ﯰ'=>'ئۇ','ﯵ'=>'ئۈ','ﯴ'=>'ئۈ','ﯻ'=>'ئى','ﯺ'=>'ئى','ﱨ'=>'ئى','ﯹ'=>'ئى','ﰃ'=>'ئى','ﱩ'=>'ئى','ﰄ'=>'ئى','ﺎ'=>'ا','ﺍ'=>'ا','ﴼ'=>'اً','ﴽ'=>'اً','ﷳ'=>'اكبر','ﷲ'=>'الله','ﺑ'=>'ب','ﺒ'=>'ب','ﺐ'=>'ب','ﺏ'=>'ب','ﲜ'=>'بج','ﰅ'=>'بج','ﲝ'=>'بح','ﰆ'=>'بح','ﷂ'=>'بحى','ﲞ'=>'بخ','ﰇ'=>'بخ','ﶞ'=>'بخى','ﱪ'=>'بر','ﱫ'=>'بز','ﲟ'=>'بم','ﳡ'=>'بم','ﱬ'=>'بم','ﰈ'=>'بم','ﱭ'=>'بن','ﲠ'=>'به','ﳢ'=>'به','ﱮ'=>'بى','ﰉ'=>'بى','ﱯ'=>'بى','ﰊ'=>'بى','ﭔ'=>'ٻ','ﭕ'=>'ٻ','ﭓ'=>'ٻ','ﭒ'=>'ٻ','ې'=>'ٻ','ﯦ'=>'ٻ','ﯧ'=>'ٻ','ﯥ'=>'ٻ','ﯤ'=>'ٻ','ﭘ'=>'پ','ﭙ'=>'پ','ﭗ'=>'پ','ﭖ'=>'پ','ﭜ'=>'ڀ','ﭝ'=>'ڀ','ﭛ'=>'ڀ','ﭚ'=>'ڀ','ﺔ'=>'ة','ﺓ'=>'ة','ﺗ'=>'ت','ﺘ'=>'ت','ﺖ'=>'ت','ﺕ'=>'ت','ﲡ'=>'تج','ﰋ'=>'تج','ﵐ'=>'تجم','ﶠ'=>'تجى','ﶟ'=>'تجى','ﲢ'=>'تح','ﰌ'=>'تح','ﵒ'=>'تحج','ﵑ'=>'تحج','ﵓ'=>'تحم','ﲣ'=>'تخ','ﰍ'=>'تخ','ﵔ'=>'تخم','ﶢ'=>'تخى','ﶡ'=>'تخى','ﱰ'=>'تر','ﱱ'=>'تز','ﲤ'=>'تم','ﳣ'=>'تم','ﱲ'=>'تم','ﰎ'=>'تم','ﵕ'=>'تمج','ﵖ'=>'تمح','ﵗ'=>'تمخ','ﶤ'=>'تمى','ﶣ'=>'تمى','ﱳ'=>'تن','ﲥ'=>'ته','ﳤ'=>'ته','ﱴ'=>'تى','ﰏ'=>'تى','ﱵ'=>'تى','ﰐ'=>'تى','ﺛ'=>'ث','ﺜ'=>'ث','ﺚ'=>'ث','ﺙ'=>'ث','ﰑ'=>'ثج','ﱶ'=>'ثر','ﱷ'=>'ثز','ﲦ'=>'ثم','ﳥ'=>'ثم','ﱸ'=>'ثم','ﰒ'=>'ثم','ﱹ'=>'ثن','ﳦ'=>'ثه','ﱺ'=>'ثى','ﰓ'=>'ثى','ﱻ'=>'ثى','ﰔ'=>'ثى','ﭨ'=>'ٹ','ﭩ'=>'ٹ','ﭧ'=>'ٹ','ﭦ'=>'ٹ','ڻ'=>'ٹ','ﮢ'=>'ٹ','ﮣ'=>'ٹ','ﮡ'=>'ٹ','ﮠ'=>'ٹ','ﭠ'=>'ٺ','ﭡ'=>'ٺ','ﭟ'=>'ٺ','ﭞ'=>'ٺ','ﭤ'=>'ٿ','ﭥ'=>'ٿ','ﭣ'=>'ٿ','ﭢ'=>'ٿ','ﺟ'=>'ج','ﺠ'=>'ج','ﺞ'=>'ج','ﺝ'=>'ج','ﲧ'=>'جح','ﰕ'=>'جح','ﶦ'=>'جحى','ﶾ'=>'جحى','ﷻ'=>'جل جلاله','ﲨ'=>'جم','ﰖ'=>'جم','ﵙ'=>'جمح','ﵘ'=>'جمح','ﶧ'=>'جمى','ﶥ'=>'جمى','ﴝ'=>'جى','ﴁ'=>'جى','ﴞ'=>'جى','ﴂ'=>'جى','ﭸ'=>'ڃ','ﭹ'=>'ڃ','ﭷ'=>'ڃ','ﭶ'=>'ڃ','ﭴ'=>'ڄ','ﭵ'=>'ڄ','ﭳ'=>'ڄ','ﭲ'=>'ڄ','ﭼ'=>'چ','ﭽ'=>'چ','ﭻ'=>'چ','ﭺ'=>'چ','ﮀ'=>'ڇ','ﮁ'=>'ڇ','ﭿ'=>'ڇ','ﭾ'=>'ڇ','ﺣ'=>'ح','ﺤ'=>'ح','ﺢ'=>'ح','ﺡ'=>'ح','ﲩ'=>'حج','ﰗ'=>'حج','ﶿ'=>'حجى','ﲪ'=>'حم','ﰘ'=>'حم','ﵛ'=>'حمى','ﵚ'=>'حمى','ﴛ'=>'حى','ﳿ'=>'حى','ﴜ'=>'حى','ﴀ'=>'حى','ﺧ'=>'خ','ﺨ'=>'خ','ﺦ'=>'خ','ﺥ'=>'خ','ﲫ'=>'خج','ﰙ'=>'خج','ﰚ'=>'خح','ﲬ'=>'خم','ﰛ'=>'خم','ﴟ'=>'خى','ﴃ'=>'خى','ﴠ'=>'خى','ﴄ'=>'خى','ﺪ'=>'د','ﺩ'=>'د','ﺬ'=>'ذ','ﺫ'=>'ذ','ﱛ'=>'ذٰ','ﮉ'=>'ڈ','ﮈ'=>'ڈ','ﮅ'=>'ڌ','ﮄ'=>'ڌ','ﮃ'=>'ڍ','ﮂ'=>'ڍ','ﮇ'=>'ڎ','ﮆ'=>'ڎ','ﺮ'=>'ر','ﺭ'=>'ر','ﱜ'=>'رٰ','ﷶ'=>'رسول','﷼'=>'رىال','ﺰ'=>'ز','ﺯ'=>'ز','ﮍ'=>'ڑ','ﮌ'=>'ڑ','ﮋ'=>'ژ','ﮊ'=>'ژ','ﺳ'=>'س','ﺴ'=>'س','ﺲ'=>'س','ﺱ'=>'س','ﲭ'=>'سج','ﴴ'=>'سج','ﰜ'=>'سج','ﵝ'=>'سجح','ﵞ'=>'سجى','ﲮ'=>'سح','ﴵ'=>'سح','ﰝ'=>'سح','ﵜ'=>'سحج','ﲯ'=>'سخ','ﴶ'=>'سخ','ﰞ'=>'سخ','ﶨ'=>'سخى','ﷆ'=>'سخى','ﴪ'=>'سر','ﴎ'=>'سر','ﲰ'=>'سم','ﳧ'=>'سم','ﰟ'=>'سم','ﵡ'=>'سمج','ﵠ'=>'سمح','ﵟ'=>'سمح','ﵣ'=>'سمم','ﵢ'=>'سمم','ﴱ'=>'سه','ﳨ'=>'سه','ﴗ'=>'سى','ﳻ'=>'سى','ﴘ'=>'سى','ﳼ'=>'سى','ﺷ'=>'ش','ﺸ'=>'ش','ﺶ'=>'ش','ﺵ'=>'ش','ﴭ'=>'شج','ﴷ'=>'شج','ﴥ'=>'شج','ﴉ'=>'شج','ﵩ'=>'شجى','ﴮ'=>'شح','ﴸ'=>'شح','ﴦ'=>'شح','ﴊ'=>'شح','ﵨ'=>'شحم','ﵧ'=>'شحم','ﶪ'=>'شحى','ﴯ'=>'شخ','ﴹ'=>'شخ','ﴧ'=>'شخ','ﴋ'=>'شخ','ﴩ'=>'شر','ﴍ'=>'شر','ﴰ'=>'شم','ﳩ'=>'شم','ﴨ'=>'شم','ﴌ'=>'شم','ﵫ'=>'شمخ','ﵪ'=>'شمخ','ﵭ'=>'شمم','ﵬ'=>'شمم','ﴲ'=>'شه','ﳪ'=>'شه','ﴙ'=>'شى','ﳽ'=>'شى','ﴚ'=>'شى','ﳾ'=>'شى','ﺻ'=>'ص','ﺼ'=>'ص','ﺺ'=>'ص','ﺹ'=>'ص','ﲱ'=>'صح','ﰠ'=>'صح','ﵥ'=>'صحح','ﵤ'=>'صحح','ﶩ'=>'صحى','ﲲ'=>'صخ','ﴫ'=>'صر','ﴏ'=>'صر','ﷵ'=>'صلعم','ﷹ'=>'صلى','ﷺ'=>'صلى الله علىه وسلم','ﷰ'=>'صلے','ﲳ'=>'صم','ﰡ'=>'صم','ﷅ'=>'صمم','ﵦ'=>'صمم','ﴡ'=>'صى','ﴅ'=>'صى','ﴢ'=>'صى','ﴆ'=>'صى','ﺿ'=>'ض','ﻀ'=>'ض','ﺾ'=>'ض','ﺽ'=>'ض','ﲴ'=>'ضج','ﰢ'=>'ضج','ﲵ'=>'ضح','ﰣ'=>'ضح','ﵮ'=>'ضحى','ﶫ'=>'ضحى','ﲶ'=>'ضخ','ﰤ'=>'ضخ','ﵰ'=>'ضخم','ﵯ'=>'ضخم','ﴬ'=>'ضر','ﴐ'=>'ضر','ﲷ'=>'ضم','ﰥ'=>'ضم','ﴣ'=>'ضى','ﴇ'=>'ضى','ﴤ'=>'ضى','ﴈ'=>'ضى','ﻃ'=>'ط','ﻄ'=>'ط','ﻂ'=>'ط','ﻁ'=>'ط','ﲸ'=>'طح','ﰦ'=>'طح','ﴳ'=>'طم','ﴺ'=>'طم','ﰧ'=>'طم','ﵲ'=>'طمح','ﵱ'=>'طمح','ﵳ'=>'طمم','ﵴ'=>'طمى','ﴑ'=>'طى','ﳵ'=>'طى','ﴒ'=>'طى','ﳶ'=>'طى','ﻇ'=>'ظ','ﻈ'=>'ظ','ﻆ'=>'ظ','ﻅ'=>'ظ','ﲹ'=>'ظم','ﴻ'=>'ظم','ﰨ'=>'ظم','ﻋ'=>'ع','ﻌ'=>'ع','ﻊ'=>'ع','ﻉ'=>'ع','ﲺ'=>'عج','ﰩ'=>'عج','ﷄ'=>'عجم','ﵵ'=>'عجم','ﷷ'=>'علىه','ﲻ'=>'عم','ﰪ'=>'عم','ﵷ'=>'عمم','ﵶ'=>'عمم','ﵸ'=>'عمى','ﶶ'=>'عمى','ﴓ'=>'عى','ﳷ'=>'عى','ﴔ'=>'عى','ﳸ'=>'عى','ﻏ'=>'غ','ﻐ'=>'غ','ﻎ'=>'غ','ﻍ'=>'غ','ﲼ'=>'غج','ﰫ'=>'غج','ﲽ'=>'غم','ﰬ'=>'غم','ﵹ'=>'غمم','ﵻ'=>'غمى','ﵺ'=>'غمى','ﴕ'=>'غى','ﳹ'=>'غى','ﴖ'=>'غى','ﳺ'=>'غى','ﻓ'=>'ف','ﻔ'=>'ف','ﻒ'=>'ف','ﻑ'=>'ف','ﲾ'=>'فج','ﰭ'=>'فج','ﲿ'=>'فح','ﰮ'=>'فح','ﳀ'=>'فخ','ﰯ'=>'فخ','ﵽ'=>'فخم','ﵼ'=>'فخم','ﳁ'=>'فم','ﰰ'=>'فم','ﷁ'=>'فمى','ﱼ'=>'فى','ﰱ'=>'فى','ﱽ'=>'فى','ﰲ'=>'فى','ﭬ'=>'ڤ','ﭭ'=>'ڤ','ﭫ'=>'ڤ','ﭪ'=>'ڤ','ﭰ'=>'ڦ','ﭱ'=>'ڦ','ﭯ'=>'ڦ','ﭮ'=>'ڦ','ﻗ'=>'ق','ﻘ'=>'ق','ﻖ'=>'ق','ﻕ'=>'ق','ﳂ'=>'قح','ﰳ'=>'قح','ﷱ'=>'قلے','ﳃ'=>'قم','ﰴ'=>'قم','ﶴ'=>'قمح','ﵾ'=>'قمح','ﵿ'=>'قمم','ﶲ'=>'قمى','ﱾ'=>'قى','ﰵ'=>'قى','ﱿ'=>'قى','ﰶ'=>'قى','ﻛ'=>'ك','ﻜ'=>'ك','ﻚ'=>'ك','ﻙ'=>'ك','ک'=>'ك','ﮐ'=>'ك','ﮑ'=>'ك','ﮏ'=>'ك','ﮎ'=>'ك','ﲀ'=>'كا','ﰷ'=>'كا','ﳄ'=>'كج','ﰸ'=>'كج','ﳅ'=>'كح','ﰹ'=>'كح','ﳆ'=>'كخ','ﰺ'=>'كخ','ﳇ'=>'كل','ﳫ'=>'كل','ﲁ'=>'كل','ﰻ'=>'كل','ﳈ'=>'كم','ﳬ'=>'كم','ﲂ'=>'كم','ﰼ'=>'كم','ﷃ'=>'كمم','ﶻ'=>'كمم','ﶷ'=>'كمى','ﲃ'=>'كى','ﰽ'=>'كى','ﲄ'=>'كى','ﰾ'=>'كى','ﯕ'=>'ڭ','ﯖ'=>'ڭ','ﯔ'=>'ڭ','ﯓ'=>'ڭ','ﮔ'=>'گ','ﮕ'=>'گ','ﮓ'=>'گ','ﮒ'=>'گ','ﮜ'=>'ڱ','ﮝ'=>'ڱ','ﮛ'=>'ڱ','ﮚ'=>'ڱ','ﮘ'=>'ڳ','ﮙ'=>'ڳ','ﮗ'=>'ڳ','ﮖ'=>'ڳ','ﻟ'=>'ل','ﻠ'=>'ل','ﻞ'=>'ل','ﻝ'=>'ل','ﻶ'=>'لآ','ﻵ'=>'لآ','ﻸ'=>'لأ','ﻷ'=>'لأ','ﻺ'=>'لإ','ﻹ'=>'لإ','ﻼ'=>'لا','ﻻ'=>'لا','ﳉ'=>'لج','ﰿ'=>'لج','ﶃ'=>'لجج','ﶄ'=>'لجج','ﶺ'=>'لجم','ﶼ'=>'لجم','ﶬ'=>'لجى','ﳊ'=>'لح','ﱀ'=>'لح','ﶵ'=>'لحم','ﶀ'=>'لحم','ﶂ'=>'لحى','ﶁ'=>'لحى','ﳋ'=>'لخ','ﱁ'=>'لخ','ﶆ'=>'لخم','ﶅ'=>'لخم','ﳌ'=>'لم','ﳭ'=>'لم','ﲅ'=>'لم','ﱂ'=>'لم','ﶈ'=>'لمح','ﶇ'=>'لمح','ﶭ'=>'لمى','ﳍ'=>'له','ﲆ'=>'لى','ﱃ'=>'لى','ﲇ'=>'لى','ﱄ'=>'لى','ﻣ'=>'م','ﻤ'=>'م','ﻢ'=>'م','ﻡ'=>'م','ﲈ'=>'ما','ﳎ'=>'مج','ﱅ'=>'مج','ﶌ'=>'مجح','ﶒ'=>'مجخ','ﶍ'=>'مجم','ﷀ'=>'مجى','ﳏ'=>'مح','ﱆ'=>'مح','ﶉ'=>'محج','ﶊ'=>'محم','ﷴ'=>'محمد','ﶋ'=>'محى','ﳐ'=>'مخ','ﱇ'=>'مخ','ﶎ'=>'مخج','ﶏ'=>'مخم','ﶹ'=>'مخى','ﳑ'=>'مم','ﲉ'=>'مم','ﱈ'=>'مم','ﶱ'=>'ممى','ﱉ'=>'مى','ﱊ'=>'مى','ﻧ'=>'ن','ﻨ'=>'ن','ﻦ'=>'ن','ﻥ'=>'ن','ﳒ'=>'نج','ﱋ'=>'نج','ﶸ'=>'نجح','ﶽ'=>'نجح','ﶘ'=>'نجم','ﶗ'=>'نجم','ﶙ'=>'نجى','ﷇ'=>'نجى','ﳓ'=>'نح','ﱌ'=>'نح','ﶕ'=>'نحم','ﶖ'=>'نحى','ﶳ'=>'نحى','ﳔ'=>'نخ','ﱍ'=>'نخ','ﲊ'=>'نر','ﲋ'=>'نز','ﳕ'=>'نم','ﳮ'=>'نم','ﲌ'=>'نم','ﱎ'=>'نم','ﶛ'=>'نمى','ﶚ'=>'نمى','ﲍ'=>'نن','ﳖ'=>'نه','ﳯ'=>'نه','ﲎ'=>'نى','ﱏ'=>'نى','ﲏ'=>'نى','ﱐ'=>'نى','ﮟ'=>'ں','ﮞ'=>'ں','ﻫ'=>'ه','ﻬ'=>'ه','ﻪ'=>'ه','ﻩ'=>'ه','ھ'=>'ه','ﮬ'=>'ه','ﮭ'=>'ه','ﮫ'=>'ه','ﮪ'=>'ه','ہ'=>'ه','ﮨ'=>'ه','ﮩ'=>'ه','ﮧ'=>'ه','ﮦ'=>'ه','ە'=>'ه','ﳙ'=>'هٰ','ﳗ'=>'هج','ﱑ'=>'هج','ﳘ'=>'هم','ﱒ'=>'هم','ﶓ'=>'همج','ﶔ'=>'همم','ﱓ'=>'هى','ﱔ'=>'هى','ﮥ'=>'ۀ','ﮤ'=>'ۀ','ﻮ'=>'و','ﻭ'=>'و','ﷸ'=>'وسلم','ﯡ'=>'ۅ','ﯠ'=>'ۅ','ﯚ'=>'ۆ','ﯙ'=>'ۆ','ﯘ'=>'ۇ','ﯗ'=>'ۇ','ٷ'=>'ۇٔ','ﯝ'=>'ۇٔ','ﯜ'=>'ۈ','ﯛ'=>'ۈ','ﯣ'=>'ۉ','ﯢ'=>'ۉ','ﯟ'=>'ۋ','ﯞ'=>'ۋ','ﯨ'=>'ى','ﯩ'=>'ى','ﻰ'=>'ى','ﻯ'=>'ى','ي'=>'ى','ﻳ'=>'ى','ﻴ'=>'ى','ﻲ'=>'ى','ﻱ'=>'ى','ی'=>'ى','ﯾ'=>'ى','ﯿ'=>'ى','ﯽ'=>'ى','ﯼ'=>'ى','ٸ'=>'ىٔ','ﲐ'=>'ىٰ','ﱝ'=>'ىٰ','ﳚ'=>'ىج','ﱕ'=>'ىج','ﶯ'=>'ىجى','ﳛ'=>'ىح','ﱖ'=>'ىح','ﶮ'=>'ىحى','ﳜ'=>'ىخ','ﱗ'=>'ىخ','ﲑ'=>'ىر','ﲒ'=>'ىز','ﳝ'=>'ىم','ﳰ'=>'ىم','ﲓ'=>'ىم','ﱘ'=>'ىم','ﶝ'=>'ىمم','ﶜ'=>'ىمم','ﶰ'=>'ىمى','ﲔ'=>'ىن','ﳞ'=>'ىه','ﳱ'=>'ىه','ﲕ'=>'ىى','ﱙ'=>'ىى','ﲖ'=>'ىى','ﱚ'=>'ىى','ۧ'=>'ۦ','ﮯ'=>'ے','ﮮ'=>'ے','ﮱ'=>'ۓ','ﮰ'=>'ۓ','∃'=>'ⴺ','आ'=>'अा','ऒ'=>'अाॆ','ओ'=>'अाे','औ'=>'अाै','ऄ'=>'अॆ','ऑ'=>'अॉ','ऍ'=>'एॅ','ऎ'=>'एॆ','ऐ'=>'एे','ई'=>'र्इ','আ'=>'অা','ৠ'=>'ঋৃ','ৡ'=>'ঌৢ','ਉ'=>'ੳੁ','ਊ'=>'ੳੂ','ਆ'=>'ਅਾ','ਐ'=>'ਅੈ','ਔ'=>'ਅੌ','ਇ'=>'ੲਿ','ਈ'=>'ੲੀ','ਏ'=>'ੲੇ','આ'=>'અા','ઑ'=>'અાૅ','ઓ'=>'અાે','ઔ'=>'અાૈ','ઍ'=>'અૅ','એ'=>'અે','ઐ'=>'અૈ','ଆ'=>'ଅା','௮'=>'அ','ர'=>'ஈ','ா'=>'ஈ','௫'=>'ஈு','௨'=>'உ','ஊ'=>'உள','௭'=>'எ','௷'=>'எவ','ஜ'=>'ஐ','௧'=>'க','௪'=>'ச','௬'=>'சு','௲'=>'சூ','௺'=>'நீ','ை'=>'ன','௴'=>'மீ','௰'=>'ய','ௗ'=>'ள','௸'=>'ஷ','ொ'=>'ெஈ','ௌ'=>'ெள','ோ'=>'ேஈ','ౠ'=>'ఋా','ౡ'=>'ఌా','ఔ'=>'ఒౌ','ఓ'=>'ఒౕ','ఢ'=>'డ̣','భ'=>'బ̣','ష'=>'వ̣','హ'=>'వా','మ'=>'వు','ూ'=>'ుా','ౄ'=>'ృా','ೡ'=>'ಌಾ','ಔ'=>'ఒౌ','ഈ'=>'ഇൗ','ഊ'=>'உൗ','ഐ'=>'എെ','ഓ'=>'ഒാ','ഔ'=>'ഒൗ','ൡ'=>'ഞ','൫'=>'ദ്ര','ഌ'=>'നூ','ങ'=>'നூ','൯'=>'ന്','റ'=>'ര','൪'=>'ര്','൮'=>'വ്','ീ'=>'ி','ൂ'=>'ூ','ൃ'=>'ூ','ൈ'=>'െെ','ฃ'=>'ข','ด'=>'ค','ต'=>'ค','ม'=>'ฆ','ซ'=>'ช','ฏ'=>'ฎ','ท'=>'ฑ','ๅ'=>'า','ำ'=>'̊า','แ'=>'เเ','ໜ'=>'ຫນ','ໝ'=>'ຫມ','ຳ'=>'̊າ','ཷ'=>'ྲཱྀ','ཹ'=>'ླཱྀ','၀'=>'o','ឣ'=>'អ','᧐'=>'ᦞ','᭒'=>'ᬍ','᭓'=>'ᬑ','᭘'=>'ᬨ','ᢖ'=>'ᡜ','ᡕ'=>'ᠵ','Ꮢ'=>'Ꭱ','Ꮍ'=>'y','𝐀'=>'A','𝐴'=>'A','𝑨'=>'A','𝒜'=>'A','𝓐'=>'A','𝔄'=>'A','𝔸'=>'A','𝕬'=>'A','𝖠'=>'A','𝗔'=>'A','𝘈'=>'A','𝘼'=>'A','𝙰'=>'A','𝚨'=>'A','𝛢'=>'A','𝜜'=>'A','𝝖'=>'A','𝞐'=>'A','𝐉'=>'J','𝐽'=>'J','𝑱'=>'J','𝒥'=>'J','𝓙'=>'J','𝔍'=>'J','𝕁'=>'J','𝕵'=>'J','𝖩'=>'J','𝗝'=>'J','𝘑'=>'J','𝙅'=>'J','𝙹'=>'J','Ꮷ'=>'J','⋿'=>'E','ℰ'=>'E','𝐄'=>'E','𝐸'=>'E','𝑬'=>'E','𝓔'=>'E','𝔈'=>'E','𝔼'=>'E','𝕰'=>'E','𝖤'=>'E','𝗘'=>'E','𝘌'=>'E','𝙀'=>'E','𝙴'=>'E','𝚬'=>'E','𝛦'=>'E','𝜠'=>'E','𝝚'=>'E','𝞔'=>'E','ℾ'=>'Ꮁ','𝚪'=>'Ꮁ','𝛤'=>'Ꮁ','𝜞'=>'Ꮁ','𝝘'=>'Ꮁ','𝞒'=>'Ꮁ','Ꮤ'=>'w','ℳ'=>'M','𝐌'=>'M','𝑀'=>'M','𝑴'=>'M','𝓜'=>'M','𝔐'=>'M','𝕄'=>'M','𝕸'=>'M','𝖬'=>'M','𝗠'=>'M','𝘔'=>'M','𝙈'=>'M','𝙼'=>'M','𝚳'=>'M','𝛭'=>'M','𝜧'=>'M','𝝡'=>'M','𝞛'=>'M','ℋ'=>'H','ℌ'=>'H','ℍ'=>'H','𝐇'=>'H','𝐻'=>'H','𝑯'=>'H','𝓗'=>'H','𝕳'=>'H','𝖧'=>'H','𝗛'=>'H','𝘏'=>'H','𝙃'=>'H','𝙷'=>'H','𝚮'=>'H','𝛨'=>'H','𝜢'=>'H','𝝜'=>'H','𝞖'=>'H','𝐆'=>'G','𝐺'=>'G','𝑮'=>'G','𝒢'=>'G','𝓖'=>'G','𝔊'=>'G','𝔾'=>'G','𝕲'=>'G','𝖦'=>'G','𝗚'=>'G','𝘎'=>'G','𝙂'=>'G','𝙶'=>'G','Ᏻ'=>'G','ℤ'=>'Z','ℨ'=>'Z','𝐙'=>'Z','𝑍'=>'Z','𝒁'=>'Z','𝒵'=>'Z','𝓩'=>'Z','𝖅'=>'Z','𝖹'=>'Z','𝗭'=>'Z','𝘡'=>'Z','𝙕'=>'Z','𝚉'=>'Z','𝚭'=>'Z','𝛧'=>'Z','𝜡'=>'Z','𝝛'=>'Z','𝞕'=>'Z','𝐒'=>'S','𝑆'=>'S','𝑺'=>'S','𝒮'=>'S','𝓢'=>'S','𝔖'=>'S','𝕊'=>'S','𝕾'=>'S','𝖲'=>'S','𝗦'=>'S','𝘚'=>'S','𝙎'=>'S','𝚂'=>'S','Ꮪ'=>'S','𝐕'=>'V','𝑉'=>'V','𝑽'=>'V','𝒱'=>'V','𝓥'=>'V','𝔙'=>'V','𝕍'=>'V','𝖁'=>'V','𝖵'=>'V','𝗩'=>'V','𝘝'=>'V','𝙑'=>'V','𝚅'=>'V','ℒ'=>'L','𝐋'=>'L','𝐿'=>'L','𝑳'=>'L','𝓛'=>'L','𝔏'=>'L','𝕃'=>'L','𝕷'=>'L','𝖫'=>'L','𝗟'=>'L','𝘓'=>'L','𝙇'=>'L','𝙻'=>'L','∑'=>'C','⅀'=>'C','ℂ'=>'C','ℭ'=>'C','𝐂'=>'C','𝐶'=>'C','𝑪'=>'C','𝒞'=>'C','𝓒'=>'C','𝕮'=>'C','𝖢'=>'C','𝗖'=>'C','𝘊'=>'C','𝘾'=>'C','𝙲'=>'C','𝚺'=>'C','𝛴'=>'C','𝜮'=>'C','𝝨'=>'C','𝞢'=>'C','ℙ'=>'P','𝐏'=>'P','𝑃'=>'P','𝑷'=>'P','𝒫'=>'P','𝓟'=>'P','𝔓'=>'P','𝕻'=>'P','𝖯'=>'P','𝗣'=>'P','𝘗'=>'P','𝙋'=>'P','𝙿'=>'P','𝚸'=>'P','𝛲'=>'P','𝜬'=>'P','𝝦'=>'P','𝞠'=>'P','𝐊'=>'K','𝐾'=>'K','𝑲'=>'K','𝒦'=>'K','𝓚'=>'K','𝔎'=>'K','𝕂'=>'K','𝕶'=>'K','𝖪'=>'K','𝗞'=>'K','𝘒'=>'K','𝙆'=>'K','𝙺'=>'K','𝚱'=>'K','𝛫'=>'K','𝜥'=>'K','𝝟'=>'K','𝞙'=>'K','ℬ'=>'B','𝐁'=>'B','𝐵'=>'B','𝑩'=>'B','𝓑'=>'B','𝔅'=>'B','𝔹'=>'B','𝕭'=>'B','𝖡'=>'B','𝗕'=>'B','𝘉'=>'B','𝘽'=>'B','𝙱'=>'B','𝚩'=>'B','𝛣'=>'B','𝜝'=>'B','𝝗'=>'B','𝞑'=>'B','ᐍ'=>'ᐁ·','∆'=>'ᐃ','𝚫'=>'ᐃ','𝛥'=>'ᐃ','𝜟'=>'ᐃ','𝝙'=>'ᐃ','𝞓'=>'ᐃ','ᐏ'=>'ᐃ·','ᐑ'=>'ᐄ·','ᐓ'=>'ᐅ·','ᐕ'=>'ᐆ·','ᐘ'=>'ᐊ·','ᐚ'=>'ᐋ·','ᓑ'=>'ᐡ','ᑶ'=>'·P','ᑺ'=>'·d','ᒘ'=>'·J','ᑁ'=>'ᐳ·','ᑃ'=>'ᐴ·','ᑅ'=>'ᐸ·','ᑇ'=>'ᐹ·','ˈ'=>'ᑊ','ᑘ'=>'ᑌ·','ᑧ'=>'ᑌᑊ','ᑚ'=>'ᑎ·','ᑨ'=>'ᑎᑊ','ᑜ'=>'ᑏ·','ᑞ'=>'ᑐ·','ᑩ'=>'ᑐᑊ','ᑠ'=>'ᑑ·','ᑢ'=>'ᑕ·','ᑪ'=>'ᑕᑊ','ᑤ'=>'ᑖ·','ᑵ'=>'ᑫ·','ᒅ'=>'ᑫᑊ','ᑷ'=>'P·','ᒆ'=>'Pᑊ','ᑹ'=>'ᑮ·','ᑻ'=>'d·','ᒇ'=>'dᑊ','ᑽ'=>'ᑰ·','ᑿ'=>'ᑲ·','ᒈ'=>'ᑲᑊ','ᒁ'=>'ᑳ·','ᘃ'=>'ᒉ','ᒓ'=>'ᒉ·','ᒕ'=>'ᒋ·','ᒗ'=>'ᒌ·','ᒙ'=>'J·','ᒛ'=>'ᒎ·','ᘂ'=>'ᒐ','ᒝ'=>'ᒐ·','ᒟ'=>'ᒑ·','ᒭ'=>'ᒣ·','ᒯ'=>'ᒥ·','ᒱ'=>'ᒦ·','ᒳ'=>'ᒧ·','ᒵ'=>'ᒨ·','ᒹ'=>'ᒫ·','ᓊ'=>'ᓀ·','ᓌ'=>'ᓇ·','ᓎ'=>'ᓈᒫ','ᘄ'=>'ᓓ','ᓝ'=>'ᓓ·','ᓟ'=>'ᓕ·','ᓡ'=>'ᓖ·','ᓣ'=>'ᓗ·','ᓥ'=>'ᓘ·','ᘇ'=>'ᓚ','ᓧ'=>'ᓚ·','ᓩ'=>'ᓛ·','ᓷ'=>'ᓭ·','ᓹ'=>'ᓯ·','ᓻ'=>'ᓰ·','ᓽ'=>'ᓱ·','ᓿ'=>'ᓲ·','ᔁ'=>'ᓴ·','ᔃ'=>'ᓵ·','ᔌ'=>'ᔋᐸ','ᔍ'=>'ᔋᑕ','ᔎ'=>'ᔋᑲ','ᔏ'=>'ᔋᒐ','ᔘ'=>'ᔐ·','ᔚ'=>'ᔑ·','ᔜ'=>'ᔒ·','ᔞ'=>'ᔓ·','ᔠ'=>'ᔔ·','ᔢ'=>'ᔕ·','ᔤ'=>'ᔖ·','ᔲ'=>'ᔨ·','ᔴ'=>'ᔩ·','ᔶ'=>'ᔪ·','ᔸ'=>'ᔫ·','ᔺ'=>'ᔭ·','ᔼ'=>'ᔮ·','᙮'=>'x','ᕽ'=>'x','ᘢ'=>'ᕃ','ᘣ'=>'ᕆ','ᘤ'=>'ᕊ','ᕏ'=>'ᕌ·','ᙯ'=>'ᕐᑫ','ᕾ'=>'ᕐᑬ','ᕿ'=>'ᕐP','ᖀ'=>'ᕐᑮ','ᖁ'=>'ᕐd','ᖂ'=>'ᕐᑰ','ᖃ'=>'ᕐᑲ','ᖄ'=>'ᕐᑳ','ᖅ'=>'ᕐᒃ','ᕜ'=>'ᕚ·','ᕩ'=>'ᕧ·','ℛ'=>'R','ℜ'=>'R','ℝ'=>'R','𝐑'=>'R','𝑅'=>'R','𝑹'=>'R','𝓡'=>'R','𝕽'=>'R','𝖱'=>'R','𝗥'=>'R','𝘙'=>'R','𝙍'=>'R','𝚁'=>'R','ᙰ'=>'ᖕᒉ','ᖎ'=>'ᖕᒊ','ᖏ'=>'ᖕᒋ','ᖐ'=>'ᖕᒌ','ᖑ'=>'ᖕJ','ᖒ'=>'ᖕᒎ','ᖓ'=>'ᖕᒐ','ᖔ'=>'ᖕᒑ','ᙱ'=>'ᖖᒋ','ᙲ'=>'ᖖᒌ','ᙳ'=>'ᖖJ','ᙴ'=>'ᖖᒎ','ᙵ'=>'ᖖᒐ','ᙶ'=>'ᖖᒑ','ℱ'=>'F','𝐅'=>'F','𝐹'=>'F','𝑭'=>'F','𝓕'=>'F','𝔉'=>'F','𝔽'=>'F','𝕱'=>'F','𝖥'=>'F','𝗙'=>'F','𝘍'=>'F','𝙁'=>'F','𝙵'=>'F','𝟊'=>'F','ⅅ'=>'D','𝐃'=>'D','𝐷'=>'D','𝑫'=>'D','𝒟'=>'D','𝓓'=>'D','𝔇'=>'D','𝔻'=>'D','𝕯'=>'D','𝖣'=>'D','𝗗'=>'D','𝘋'=>'D','𝘿'=>'D','𝙳'=>'D','ᗪ'=>'D','℧'=>'ᘮ','ᘴ'=>'ᘮ','𝛀'=>'ᘯ','𝛺'=>'ᘯ','𝜴'=>'ᘯ','𝝮'=>'ᘯ','𝞨'=>'ᘯ','ᘵ'=>'ᘯ','ㄱ'=>'ᄀ','ᄀ'=>'ᄀ','ᆨ'=>'ᄀ','ㄲ'=>'ᄁ','ᄁ'=>'ᄁ','ᆩ'=>'ᄁ','ㄴ'=>'ᄂ','ᄂ'=>'ᄂ','ᆫ'=>'ᄂ','ㄷ'=>'ᄃ','ᄃ'=>'ᄃ','ᆮ'=>'ᄃ','ㄸ'=>'ᄄ','ᄄ'=>'ᄄ','ㄹ'=>'ᄅ','ᄅ'=>'ᄅ','ᆯ'=>'ᄅ','ㅁ'=>'ᄆ','ᄆ'=>'ᄆ','ᆷ'=>'ᄆ','ㅂ'=>'ᄇ','ᄇ'=>'ᄇ','ᆸ'=>'ᄇ','ㅃ'=>'ᄈ','ᄈ'=>'ᄈ','ㅅ'=>'ᄉ','ᄉ'=>'ᄉ','ᆺ'=>'ᄉ','ㅆ'=>'ᄊ','ᄊ'=>'ᄊ','ᆻ'=>'ᄊ','ㅇ'=>'ᄋ','ᄋ'=>'ᄋ','ᆼ'=>'ᄋ','ㅈ'=>'ᄌ','ᄌ'=>'ᄌ','ᆽ'=>'ᄌ','ㅉ'=>'ᄍ','ᄍ'=>'ᄍ','ㅊ'=>'ᄎ','ᄎ'=>'ᄎ','ᆾ'=>'ᄎ','ㅋ'=>'ᄏ','ᄏ'=>'ᄏ','ᆿ'=>'ᄏ','ㅌ'=>'ᄐ','ᄐ'=>'ᄐ','ᇀ'=>'ᄐ','ㅍ'=>'ᄑ','ᄑ'=>'ᄑ','ᇁ'=>'ᄑ','ㅎ'=>'ᄒ','ᄒ'=>'ᄒ','ᇂ'=>'ᄒ','ᇅ'=>'ᄓ','ㅥ'=>'ᄔ','ㅦ'=>'ᄕ','ᇆ'=>'ᄕ','ᇊ'=>'ᄗ','ᇍ'=>'ᄘ','ᇐ'=>'ᄙ','ㅀ'=>'ᄚ','ᄚ'=>'ᄚ','ᄻ'=>'ᄚ','ᆶ'=>'ᄚ','ㅮ'=>'ᄜ','ᇜ'=>'ᄜ','ㅱ'=>'ᄝ','ᇢ'=>'ᄝ','ㅲ'=>'ᄞ','ㅳ'=>'ᄠ','ㅄ'=>'ᄡ','ᄡ'=>'ᄡ','ᆹ'=>'ᄡ','ㅴ'=>'ᄢ','ㅵ'=>'ᄣ','ㅶ'=>'ᄧ','ㅷ'=>'ᄩ','ㅸ'=>'ᄫ','ᇦ'=>'ᄫ','ㅹ'=>'ᄬ','ㅺ'=>'ᄭ','ᇧ'=>'ᄭ','ㅻ'=>'ᄮ','ㅼ'=>'ᄯ','ᇨ'=>'ᄯ','ᇩ'=>'ᄰ','ㅽ'=>'ᄲ','ᇪ'=>'ᄲ','ㅾ'=>'ᄶ','ㅿ'=>'ᅀ','ᇫ'=>'ᅀ','ᇬ'=>'ᅁ','ᇱ'=>'ᅅ','ㆂ'=>'ᅅ','ᇲ'=>'ᅆ','ㆃ'=>'ᅆ','ㆀ'=>'ᅇ','ᇮ'=>'ᅇ','ㆁ'=>'ᅌ','ᇰ'=>'ᅌ','ᇳ'=>'ᅖ','ㆄ'=>'ᅗ','ᇴ'=>'ᅗ','ㆅ'=>'ᅘ','ㆆ'=>'ᅙ','ᇹ'=>'ᅙ','ㅤ'=>'ᅠ','ᅠ'=>'ᅠ','ㅏ'=>'ᅡ','ᅡ'=>'ᅡ','ㅐ'=>'ᅢ','ᅢ'=>'ᅢ','ㅑ'=>'ᅣ','ᅣ'=>'ᅣ','ㅒ'=>'ᅤ','ᅤ'=>'ᅤ','ㅓ'=>'ᅥ','ᅥ'=>'ᅥ','ㅔ'=>'ᅦ','ᅦ'=>'ᅦ','ㅕ'=>'ᅧ','ᅧ'=>'ᅧ','ㅖ'=>'ᅨ','ᅨ'=>'ᅨ','ㅗ'=>'ᅩ','ᅩ'=>'ᅩ','ㅘ'=>'ᅪ','ᅪ'=>'ᅪ','ㅙ'=>'ᅫ','ᅫ'=>'ᅫ','ㅚ'=>'ᅬ','ᅬ'=>'ᅬ','ㅛ'=>'ᅭ','ᅭ'=>'ᅭ','ㅜ'=>'ᅮ','ᅮ'=>'ᅮ','ㅝ'=>'ᅯ','ᅯ'=>'ᅯ','ㅞ'=>'ᅰ','ᅰ'=>'ᅰ','ㅟ'=>'ᅱ','ᅱ'=>'ᅱ','ㅠ'=>'ᅲ','ᅲ'=>'ᅲ','ㅡ'=>'一','ᅳ'=>'一','ㅢ'=>'ᅴ','ᅴ'=>'ᅴ','ㅣ'=>'丨','ᅵ'=>'丨','ㆇ'=>'ᆄ','ᆆ'=>'ᆄ','ㆈ'=>'ᆅ','ㆉ'=>'ᆈ','ㆊ'=>'ᆑ','ㆋ'=>'ᆒ','ㆌ'=>'ᆔ','ㆍ'=>'ᆞ','ㆎ'=>'ᆡ','ㄳ'=>'ᆪ','ᆪ'=>'ᆪ','ㄵ'=>'ᆬ','ᆬ'=>'ᆬ','ㄶ'=>'ᆭ','ᆭ'=>'ᆭ','ㄺ'=>'ᆰ','ᆰ'=>'ᆰ','ㄻ'=>'ᆱ','ᆱ'=>'ᆱ','ㄼ'=>'ᆲ','ᆲ'=>'ᆲ','ㄽ'=>'ᆳ','ᆳ'=>'ᆳ','ㄾ'=>'ᆴ','ᆴ'=>'ᆴ','ㄿ'=>'ᆵ','ᆵ'=>'ᆵ','ㅧ'=>'ᇇ','ㅨ'=>'ᇈ','ㅩ'=>'ᇌ','ㅪ'=>'ᇎ','ㅫ'=>'ᇓ','ㅬ'=>'ᇗ','ㅭ'=>'ᇙ','ㅯ'=>'ᇝ','ㅰ'=>'ᇟ','ァ'=>'ァ','ア'=>'ア','ィ'=>'ィ','イ'=>'イ','ゥ'=>'ゥ','ウ'=>'ウ','ェ'=>'ェ','エ'=>'エ','ォ'=>'ォ','オ'=>'オ','カ'=>'カ','キ'=>'キ','ク'=>'ク','ケ'=>'ケ','コ'=>'コ','サ'=>'サ','シ'=>'シ','ス'=>'ス','セ'=>'セ','ソ'=>'ソ','タ'=>'タ','チ'=>'チ','ッ'=>'ッ','ツ'=>'ツ','テ'=>'テ','ト'=>'ト','ナ'=>'ナ','ニ'=>'ニ','ヌ'=>'ヌ','ネ'=>'ネ','ノ'=>'ノ','ハ'=>'ハ','ヒ'=>'ヒ','フ'=>'フ','ヘ'=>'へ','ホ'=>'ホ','マ'=>'マ','⧄'=>'〼','ミ'=>'ミ','ム'=>'ム','メ'=>'メ','モ'=>'モ','ャ'=>'ャ','ヤ'=>'ヤ','ュ'=>'ュ','ユ'=>'ユ','ョ'=>'ョ','ヨ'=>'ヨ','ラ'=>'ラ','リ'=>'リ','ル'=>'ル','レ'=>'レ','ロ'=>'ロ','ワ'=>'ワ','ヲ'=>'ヲ','ン'=>'ン','꒞'=>'ꁊ','꒬'=>'ꁐ','꒜'=>'ꃀ','꒿'=>'ꉙ','꒾'=>'ꊱ','꓀'=>'ꎫ','꓂'=>'ꎵ','꒺'=>'ꎿ','꒰'=>'ꏂ','𐒠'=>'𐒆','—'=>'一','―'=>'一','−'=>'一','─'=>'一','⼀'=>'一','不'=>'不','並'=>'並','|'=>'丨','|'=>'丨','∣'=>'丨','⼁'=>'丨','‖'=>'丨丨','∥'=>'丨丨','串'=>'串','⼂'=>'丶','丸'=>'丸','丹'=>'丹','丽'=>'丽','⼃'=>'丿','乁'=>'乁','⼄'=>'乙','亂'=>'亂','⼅'=>'亅','了'=>'了','⼆'=>'二','⼇'=>'亠','亮'=>'亮','⼈'=>'人','什'=>'什','仌'=>'仌','令'=>'令','你'=>'你','倂'=>'併','倂'=>'併','侀'=>'侀','來'=>'來','例'=>'例','侮'=>'侮','侮'=>'侮','侻'=>'侻','便'=>'便','值'=>'値','倫'=>'倫','偺'=>'偺','備'=>'備','像'=>'像','僚'=>'僚','僧'=>'僧','僧'=>'僧','⼉'=>'儿','兀'=>'兀','充'=>'充','免'=>'免','免'=>'免','兔'=>'兔','兤'=>'兤','⼊'=>'入','內'=>'內','全'=>'全','兩'=>'兩','⼋'=>'八','六'=>'六','具'=>'具','冀'=>'冀','⼌'=>'冂','再'=>'再','冒'=>'冒','冕'=>'冕','⼍'=>'冖','冗'=>'冗','冤'=>'冤','⼎'=>'冫','冬'=>'冬','况'=>'况','况'=>'况','冷'=>'冷','凉'=>'凉','凌'=>'凌','凜'=>'凜','凞'=>'凞','⼏'=>'几','凵'=>'凵','⼐'=>'凵','⼑'=>'刀','刃'=>'刃','切'=>'切','切'=>'切','列'=>'列','利'=>'利','刺'=>'刺','刻'=>'刻','剆'=>'剆','割'=>'割','剷'=>'剷','劉'=>'劉','力'=>'力','⼒'=>'力','劣'=>'劣','劳'=>'劳','勇'=>'勇','勇'=>'勇','勉'=>'勉','勉'=>'勉','勒'=>'勒','勞'=>'勞','勤'=>'勤','勤'=>'勤','勵'=>'勵','⼓'=>'勹','勺'=>'勺','勺'=>'勺','包'=>'包','匆'=>'匆','⼔'=>'匕','北'=>'北','北'=>'北','⼕'=>'匚','⼖'=>'匸','匿'=>'匿','⼗'=>'十','〸'=>'十','〹'=>'卄','〺'=>'卅','卉'=>'卉','卑'=>'卑','卑'=>'卑','博'=>'博','⼘'=>'卜','⼙'=>'卩','即'=>'即','卵'=>'卵','卽'=>'卽','卿'=>'卿','卿'=>'卿','卿'=>'卿','⼚'=>'厂','⼛'=>'厶','參'=>'參','⼜'=>'又','及'=>'及','叟'=>'叟','⼝'=>'口','句'=>'句','叫'=>'叫','叱'=>'叱','吆'=>'吆','吏'=>'吏','吝'=>'吝','吸'=>'吸','呂'=>'呂','呈'=>'呈','周'=>'周','咞'=>'咞','咢'=>'咢','咽'=>'咽','哶'=>'哶','唐'=>'唐','啓'=>'啓','啟'=>'啓','啕'=>'啕','啣'=>'啣','善'=>'善','善'=>'善','喇'=>'喇','喙'=>'喙','喙'=>'喙','喝'=>'喝','喝'=>'喝','喫'=>'喫','喳'=>'喳','嗀'=>'嗀','嗂'=>'嗂','嗢'=>'嗢','嘆'=>'嘆','嘆'=>'嘆','噑'=>'噑','器'=>'器','噴'=>'噴','⼞'=>'囗','囹'=>'囹','圖'=>'圖','圗'=>'圗','⼟'=>'土','型'=>'型','城'=>'城','埴'=>'埴','堍'=>'堍','報'=>'報','堲'=>'堲','塀'=>'塀','塚'=>'塚','塚'=>'塚','塞'=>'塞','填'=>'塡','墨'=>'墨','壿'=>'墫','墬'=>'墬','墳'=>'墳','壘'=>'壘','壟'=>'壟','⼠'=>'士','壮'=>'壮','売'=>'売','壷'=>'壷','⼡'=>'夂','夆'=>'夆','⼢'=>'夊','⼣'=>'夕','多'=>'多','夢'=>'夢','⼤'=>'大','奄'=>'奄','奈'=>'奈','契'=>'契','奔'=>'奔','奢'=>'奢','女'=>'女','⼥'=>'女','姘'=>'姘','姬'=>'姬','娛'=>'娛','娧'=>'娧','婢'=>'婢','婦'=>'婦','嬀'=>'媯','媵'=>'媵','嬈'=>'嬈','嬨'=>'嬨','嬾'=>'嬾','嬾'=>'嬾','⼦'=>'子','⼧'=>'宀','宅'=>'宅','寃'=>'寃','寘'=>'寘','寧'=>'寧','寧'=>'寧','寧'=>'寧','寮'=>'寮','寳'=>'寳','⼨'=>'寸','寿'=>'寿','将'=>'将','⼩'=>'小','尢'=>'尢','⼪'=>'尢','⼫'=>'尸','尿'=>'尿','屠'=>'屠','屢'=>'屢','層'=>'層','履'=>'履','屮'=>'屮','屮'=>'屮','⼬'=>'屮','⼭'=>'山','岍'=>'岍','峀'=>'峀','崙'=>'崙','嵃'=>'嵃','嵐'=>'嵐','嵫'=>'嵫','嵮'=>'嵮','嵼'=>'嵼','嶲'=>'嶲','嶺'=>'嶺','⼮'=>'巛','巡'=>'巡','巢'=>'巢','⼯'=>'工','⼰'=>'己','巽'=>'巽','⼱'=>'巾','帲'=>'帡','帨'=>'帨','帽'=>'帽','幩'=>'幩','⼲'=>'干','年'=>'年','⼳'=>'幺','⼴'=>'广','度'=>'度','庰'=>'庰','庳'=>'庳','庶'=>'庶','廉'=>'廉','廊'=>'廊','廊'=>'廊','廒'=>'廒','廓'=>'廓','廙'=>'廙','廬'=>'廬','⼵'=>'廴','廾'=>'廾','⼶'=>'廾','弄'=>'弄','⼷'=>'弋','⼸'=>'弓','弢'=>'弢','弢'=>'弢','⼹'=>'彐','当'=>'当','⼺'=>'彡','形'=>'形','彩'=>'彩','彫'=>'彫','⼻'=>'彳','律'=>'律','徚'=>'徚','復'=>'復','徭'=>'徭','⼼'=>'心','忍'=>'忍','志'=>'志','念'=>'念','忹'=>'忹','怒'=>'怒','怜'=>'怜','悁'=>'悁','悔'=>'悔','悔'=>'悔','惇'=>'惇','惘'=>'惘','惡'=>'惡','愈'=>'愈','慄'=>'慄','慈'=>'慈','慌'=>'慌','慌'=>'慌','慎'=>'慎','慎'=>'慎','慠'=>'慠','慨'=>'慨','慺'=>'慺','憎'=>'憎','憎'=>'憎','憎'=>'憎','憐'=>'憐','憤'=>'憤','憯'=>'憯','憲'=>'憲','懞'=>'懞','懲'=>'懲','懲'=>'懲','懲'=>'懲','懶'=>'懶','懶'=>'懶','戀'=>'戀','⼽'=>'戈','成'=>'成','戛'=>'戛','戮'=>'戮','戴'=>'戴','⼾'=>'戶','⼿'=>'手','扝'=>'扝','抱'=>'抱','拉'=>'拉','拏'=>'拏','拓'=>'拓','拔'=>'拔','拼'=>'拼','拾'=>'拾','挽'=>'挽','捐'=>'捐','捨'=>'捨','捻'=>'捻','掃'=>'掃','掠'=>'掠','掩'=>'掩','揄'=>'揄','揅'=>'揅','揤'=>'揤','㩁'=>'搉','搜'=>'搜','搢'=>'搢','摒'=>'摒','摩'=>'摩','摷'=>'摷','摾'=>'摾','撚'=>'撚','撝'=>'撝','擄'=>'擄','⽀'=>'支','⽁'=>'攴','敏'=>'敏','敏'=>'敏','敖'=>'敖','敬'=>'敬','數'=>'數','⽂'=>'文','⽃'=>'斗','料'=>'料','⽄'=>'斤','⽅'=>'方','旅'=>'旅','⽆'=>'无','既'=>'既','旣'=>'旣','⽇'=>'日','易'=>'易','晉'=>'晉','晩'=>'晚','䀿'=>'晣','晴'=>'晴','晴'=>'晴','暈'=>'暈','暑'=>'暑','暑'=>'暑','暜'=>'暜','暴'=>'暴','曆'=>'曆','⽈'=>'曰','更'=>'更','㫚'=>'曶','書'=>'書','最'=>'最','⽉'=>'月','肦'=>'朌','胐'=>'朏','胊'=>'朐','脁'=>'朓','朗'=>'朗','朗'=>'朗','朗'=>'朗','脧'=>'朘','望'=>'望','望'=>'望','朡'=>'朡','膧'=>'朣','⽊'=>'木','李'=>'李','杓'=>'杓','杖'=>'杖','杞'=>'杞','柿'=>'杮','杻'=>'杻','枅'=>'枅','林'=>'林','柳'=>'柳','柺'=>'柺','栗'=>'栗','栟'=>'栟','桒'=>'桒','梁'=>'梁','梅'=>'梅','梅'=>'梅','梎'=>'梎','梨'=>'梨','椔'=>'椔','楂'=>'楂','樧'=>'榝','榣'=>'榣','槪'=>'槪','樂'=>'樂','樂'=>'樂','樂'=>'樂','樓'=>'樓','檨'=>'檨','櫓'=>'櫓','櫛'=>'櫛','欄'=>'欄','⽋'=>'欠','次'=>'次','歔'=>'歔','⽌'=>'止','歲'=>'歲','歷'=>'歷','歹'=>'歹','⽍'=>'歹','殟'=>'殟','殮'=>'殮','⽎'=>'殳','殺'=>'殺','殺'=>'殺','殺'=>'殺','殻'=>'殻','⽏'=>'毋','⺟'=>'母','⽐'=>'比','⽑'=>'毛','⽒'=>'氏','⽓'=>'气','⽔'=>'水','汎'=>'汎','汧'=>'汧','沈'=>'沈','沿'=>'沿','泌'=>'泌','泍'=>'泍','泥'=>'泥','洖'=>'洖','洛'=>'洛','洞'=>'洞','洴'=>'洴','派'=>'派','流'=>'流','流'=>'流','流'=>'流','浩'=>'浩','浪'=>'浪','海'=>'海','海'=>'海','浸'=>'浸','涅'=>'涅','淋'=>'淋','淚'=>'淚','淪'=>'淪','淹'=>'淹','渚'=>'渚','港'=>'港','湮'=>'湮','潙'=>'溈','溜'=>'溜','溺'=>'溺','滇'=>'滇','滋'=>'滋','滋'=>'滋','滑'=>'滑','滛'=>'滛','漏'=>'漏','漢'=>'漢','漢'=>'漢','漣'=>'漣','潮'=>'潮','濆'=>'濆','濫'=>'濫','濾'=>'濾','瀛'=>'瀛','瀞'=>'瀞','瀞'=>'瀞','瀹'=>'瀹','灊'=>'灊','⽕'=>'火','灰'=>'灰','灷'=>'灷','災'=>'災','炙'=>'炙','炭'=>'炭','烈'=>'烈','烙'=>'烙','煅'=>'煅','煉'=>'煉','煮'=>'煮','煮'=>'煮','熜'=>'熜','燎'=>'燎','燐'=>'燐','爐'=>'爐','爛'=>'爛','爨'=>'爨','⽖'=>'爪','爫'=>'爫','⺤'=>'爫','爵'=>'爵','爵'=>'爵','⽗'=>'父','⽘'=>'爻','⽙'=>'爿','⽚'=>'片','牐'=>'牐','⽛'=>'牙','⽜'=>'牛','牢'=>'牢','犀'=>'犀','犕'=>'犕','⽝'=>'犬','犯'=>'犯','狀'=>'狀','狼'=>'狼','猪'=>'猪','猪'=>'猪','獵'=>'獵','獺'=>'獺','⽞'=>'玄','率'=>'率','率'=>'率','⽟'=>'玉','王'=>'王','玥'=>'玥','玲'=>'玲','珞'=>'珞','理'=>'理','琉'=>'琉','琢'=>'琢','瑇'=>'瑇','瑜'=>'瑜','瑩'=>'瑩','瑱'=>'瑱','瑱'=>'瑱','璅'=>'璅','璉'=>'璉','璘'=>'璘','瓊'=>'瓊','⽠'=>'瓜','⽡'=>'瓦','甆'=>'甆','⽢'=>'甘','⽣'=>'生','甤'=>'甤','⽤'=>'用','⽥'=>'田','画'=>'画','甾'=>'甾','留'=>'留','略'=>'略','異'=>'異','異'=>'異','⽦'=>'疋','⽧'=>'疒','痢'=>'痢','瘐'=>'瘐','瘝'=>'瘝','瘟'=>'瘟','療'=>'療','癩'=>'癩','⽨'=>'癶','⽩'=>'白','⽪'=>'皮','⽫'=>'皿','益'=>'益','益'=>'益','盛'=>'盛','盧'=>'盧','⽬'=>'目','直'=>'直','直'=>'直','省'=>'省','眞'=>'眞','真'=>'真','真'=>'真','着'=>'着','睊'=>'睊','睊'=>'睊','瞋'=>'瞋','瞧'=>'瞧','⽭'=>'矛','⽮'=>'矢','⽯'=>'石','硏'=>'研','硎'=>'硎','硫'=>'硫','碌'=>'碌','碌'=>'碌','碑'=>'碑','磊'=>'磊','磌'=>'磌','磌'=>'磌','磻'=>'磻','礪'=>'礪','⽰'=>'示','礼'=>'礼','社'=>'社','祈'=>'祈','祉'=>'祉','祐'=>'祐','祖'=>'祖','祖'=>'祖','祝'=>'祝','神'=>'神','祥'=>'祥','祿'=>'祿','禍'=>'禍','禎'=>'禎','福'=>'福','福'=>'福','禮'=>'禮','⽱'=>'禸','⽲'=>'禾','秊'=>'秊','秫'=>'秫','稜'=>'稜','穀'=>'穀','穀'=>'穀','穊'=>'穊','穏'=>'穏','⽳'=>'穴','突'=>'突','窱'=>'窱','立'=>'立','⽴'=>'立','竮'=>'竮','⽵'=>'竹','笠'=>'笠','節'=>'節','節'=>'節','篆'=>'篆','築'=>'築','簾'=>'簾','籠'=>'籠','⽶'=>'米','类'=>'类','粒'=>'粒','精'=>'精','糒'=>'糒','糖'=>'糖','糣'=>'糣','糧'=>'糧','糨'=>'糨','⽷'=>'糸','紀'=>'紀','紐'=>'紐','索'=>'索','累'=>'累','絶'=>'絕','絛'=>'絛','絣'=>'絣','綠'=>'綠','綾'=>'綾','緇'=>'緇','練'=>'練','練'=>'練','練'=>'練','縂'=>'縂','縉'=>'縉','縷'=>'縷','繁'=>'繁','繅'=>'繅','⽸'=>'缶','缾'=>'缾','⽹'=>'网','⺫'=>'罒','署'=>'署','罹'=>'罹','罺'=>'罺','羅'=>'羅','⽺'=>'羊','羕'=>'羕','羚'=>'羚','羽'=>'羽','⽻'=>'羽','翺'=>'翺','老'=>'老','⽼'=>'老','者'=>'者','者'=>'者','者'=>'者','⽽'=>'而','⽾'=>'耒','⽿'=>'耳','聆'=>'聆','聠'=>'聠','聯'=>'聯','聰'=>'聰','聾'=>'聾','⾀'=>'聿','⾁'=>'肉','肋'=>'肋','肭'=>'肭','育'=>'育','㬵'=>'胶','腁'=>'胼','脃'=>'脃','脾'=>'脾','臘'=>'臘','⾂'=>'臣','臨'=>'臨','⾃'=>'自','臭'=>'臭','⾄'=>'至','⾅'=>'臼','舁'=>'舁','舁'=>'舁','舄'=>'舄','⾆'=>'舌','⾇'=>'舛','⾈'=>'舟','⾉'=>'艮','良'=>'良','⾊'=>'色','⾋'=>'艸','艹'=>'艹','艹'=>'艹','芋'=>'芋','芑'=>'芑','芝'=>'芝','花'=>'花','芳'=>'芳','芽'=>'芽','若'=>'若','若'=>'若','苦'=>'苦','茝'=>'茝','茣'=>'茣','茶'=>'茶','荒'=>'荒','荓'=>'荓','荣'=>'荣','莭'=>'莭','莽'=>'莽','菉'=>'菉','菊'=>'菊','菌'=>'菌','菜'=>'菜','菧'=>'菧','華'=>'華','菱'=>'菱','落'=>'落','葉'=>'葉','著'=>'著','著'=>'著','蔿'=>'蒍','蓮'=>'蓮','蓱'=>'蓱','蓳'=>'蓳','蓼'=>'蓼','蔖'=>'蔖','蕤'=>'蕤','藍'=>'藍','藺'=>'藺','蘆'=>'蘆','蘒'=>'蘒','蘭'=>'蘭','虁'=>'蘷','蘿'=>'蘿','⾌'=>'虍','虐'=>'虐','虜'=>'虜','虜'=>'虜','虧'=>'虧','虩'=>'虩','⾍'=>'虫','蚈'=>'蚈','蚩'=>'蚩','蛢'=>'蛢','蜎'=>'蜎','蜨'=>'蜨','蝫'=>'蝫','蝹'=>'蝹','蝹'=>'蝹','螆'=>'螆','螺'=>'螺','蟡'=>'蟡','蠁'=>'蠁','蠟'=>'蠟','⾎'=>'血','行'=>'行','⾏'=>'行','衠'=>'衠','衣'=>'衣','⾐'=>'衣','裂'=>'裂','裏'=>'裏','裗'=>'裗','裞'=>'裞','裡'=>'裡','裸'=>'裸','裺'=>'裺','褐'=>'褐','襁'=>'襁','襤'=>'襤','⾑'=>'襾','覆'=>'覆','見'=>'見','⾒'=>'見','視'=>'視','視'=>'視','⾓'=>'角','⾔'=>'言','䚶'=>'訞','詽'=>'訮','誠'=>'誠','說'=>'說','說'=>'說','調'=>'調','請'=>'請','諒'=>'諒','論'=>'論','諭'=>'諭','諭'=>'諭','諸'=>'諸','諸'=>'諸','諾'=>'諾','諾'=>'諾','謁'=>'謁','謁'=>'謁','謹'=>'謹','謹'=>'謹','識'=>'識','讀'=>'讀','讏'=>'讆','變'=>'變','變'=>'變','⾕'=>'谷','⾖'=>'豆','豈'=>'豈','豕'=>'豕','⾗'=>'豕','⾘'=>'豸','⾙'=>'貝','貫'=>'貫','賁'=>'賁','賂'=>'賂','賈'=>'賈','賓'=>'賓','贈'=>'贈','贈'=>'贈','贛'=>'贛','⾚'=>'赤','⾛'=>'走','起'=>'起','趆'=>'赿','⾜'=>'足','趼'=>'趼','跋'=>'跋','跺'=>'跥','路'=>'路','跰'=>'跰','躛'=>'躗','⾝'=>'身','車'=>'車','⾞'=>'車','軔'=>'軔','輧'=>'軿','輦'=>'輦','輪'=>'輪','輸'=>'輸','輸'=>'輸','輻'=>'輻','轢'=>'轢','⾟'=>'辛','辞'=>'辞','辰'=>'辰','⾠'=>'辰','⾡'=>'辵','辶'=>'辶','⻌'=>'辶','連'=>'連','逸'=>'逸','逸'=>'逸','遲'=>'遲','遼'=>'遼','邏'=>'邏','⾢'=>'邑','邔'=>'邔','郎'=>'郎','郱'=>'郱','都'=>'都','鄑'=>'鄑','鄛'=>'鄛','⾣'=>'酉','酪'=>'酪','醙'=>'醙','醴'=>'醴','⾤'=>'釆','里'=>'里','⾥'=>'里','量'=>'量','金'=>'金','⾦'=>'金','鈴'=>'鈴','鈸'=>'鈸','鉶'=>'鉶','鉼'=>'鉼','鋗'=>'鋗','鋘'=>'鋘','錄'=>'錄','鍊'=>'鍊','鎮'=>'鎭','鏹'=>'鏹','鐕'=>'鐕','⾧'=>'長','⾨'=>'門','開'=>'開','閭'=>'閭','閷'=>'閷','⾩'=>'阜','阮'=>'阮','陋'=>'陋','降'=>'降','陵'=>'陵','陸'=>'陸','陼'=>'陼','隆'=>'隆','隣'=>'隣','⾪'=>'隶','隸'=>'隸','⾫'=>'隹','雃'=>'雃','離'=>'離','難'=>'難','難'=>'難','⾬'=>'雨','零'=>'零','雷'=>'雷','霣'=>'霣','露'=>'露','靈'=>'靈','⾭'=>'靑','靖'=>'靖','靖'=>'靖','⾮'=>'非','⾯'=>'面','⾰'=>'革','⾱'=>'韋','韛'=>'韛','韠'=>'韠','⾲'=>'韭','⾳'=>'音','響'=>'響','響'=>'響','⾴'=>'頁','頋'=>'頋','頋'=>'頋','頋'=>'頋','領'=>'領','頩'=>'頩','頻'=>'頻','頻'=>'頻','類'=>'類','⾵'=>'風','⾶'=>'飛','⻝'=>'食','⾷'=>'食','飢'=>'飢','飯'=>'飯','飼'=>'飼','館'=>'館','餩'=>'餩','⾸'=>'首','⾹'=>'香','馧'=>'馧','⾺'=>'馬','駂'=>'駂','駱'=>'駱','駾'=>'駾','驪'=>'驪','⾻'=>'骨','⾼'=>'高','⾽'=>'髟','鬒'=>'鬒','鬒'=>'鬒','⾾'=>'鬥','⾿'=>'鬯','⿀'=>'鬲','⿁'=>'鬼','⿂'=>'魚','魯'=>'魯','鱀'=>'鱀','鱗'=>'鱗','⿃'=>'鳥','鳽'=>'鳽','鵧'=>'鵧','鶴'=>'鶴','鷺'=>'鷺','鸞'=>'鸞','鹃'=>'鹂','⿄'=>'鹵','鹿'=>'鹿','⿅'=>'鹿','麗'=>'麗','麟'=>'麟','⿆'=>'麥','麻'=>'麻','⿇'=>'麻','⿈'=>'黃','⿉'=>'黍','黎'=>'黎','⿊'=>'黑','黹'=>'黹','⿋'=>'黹','⿌'=>'黽','黾'=>'黾','鼅'=>'鼅','⿍'=>'鼎','鼏'=>'鼏','⿎'=>'鼓','鼖'=>'鼖','⿏'=>'鼠','鼻'=>'鼻','⿐'=>'鼻','齃'=>'齃','⿑'=>'齊','⿒'=>'齒','龍'=>'龍','⿓'=>'龍','龎'=>'龎','龜'=>'龜','龜'=>'龜','龜'=>'龜','⿔'=>'龜','⻳'=>'龟','⿕'=>'龠','㒞'=>'㒞','㒹'=>'㒹','㒻'=>'㒻','㓟'=>'㓟','㔕'=>'㔕','䎛'=>'㖈','㛮'=>'㛮','㛼'=>'㛼','㞁'=>'㞁','㠯'=>'㠯','㡢'=>'㡢','㡼'=>'㡼','㣇'=>'㣇','㣣'=>'㣣','㤜'=>'㤜','㤺'=>'㤺','㨮'=>'㨮','㩬'=>'㩬','㫤'=>'㫤','㬈'=>'㬈','㬙'=>'㬙','䐠'=>'㬻','㭉'=>'㭉','㮝'=>'㮝','㮝'=>'㮝','㰘'=>'㰘','㱎'=>'㱎','㴳'=>'㴳','㶖'=>'㶖','㺬'=>'㺬','㺸'=>'㺸','㺸'=>'㺸','㼛'=>'㼛','㿼'=>'㿼','䀈'=>'䀈','䀘'=>'䀘','䀹'=>'䀹','䀹'=>'䀹','䁆'=>'䁆','䂖'=>'䂖','䃣'=>'䃣','䄯'=>'䄯','䈂'=>'䈂','䈧'=>'䈧','䊠'=>'䊠','䌁'=>'䌁','䌴'=>'䌴','䍙'=>'䍙','䏕'=>'䏕','䏙'=>'䏙','䐋'=>'䐋','䑫'=>'䑫','䔫'=>'䔫','䕝'=>'䕝','䕡'=>'䕡','䕫'=>'䕫','䗗'=>'䗗','䗹'=>'䗹','䘵'=>'䘵','䚾'=>'䚾','䛇'=>'䛇','䦕'=>'䦕','䧦'=>'䧦','䩮'=>'䩮','䩶'=>'䩶','䪲'=>'䪲','䬳'=>'䬳','䯎'=>'䯎','䳎'=>'䳎','䳭'=>'䳭','䳸'=>'䳸','䵖'=>'䵖','𠄢'=>'𠄢','𠔜'=>'𠔜','𠔥'=>'𠔥','𠕋'=>'𠕋','𠘺'=>'𠘺','𠠄'=>'𠠄','𠣞'=>'𠣞','𠨬'=>'𠨬','𠭣'=>'𠭣','𡓤'=>'𡓤','𡚨'=>'𡚨','𡛪'=>'𡛪','𡧈'=>'𡧈','𡬘'=>'𡬘','𡴋'=>'𡴋','𡷤'=>'𡷤','𡷦'=>'𡷦','𢆃'=>'𢆃','𢆟'=>'𢆟','𢌱'=>'𢌱','𢌱'=>'𢌱','𢛔'=>'𢛔','𢡄'=>'𢡄','𢡊'=>'𢡊','𢬌'=>'𢬌','𢯱'=>'𢯱','𣀊'=>'𣀊','𣊸'=>'𣊸','𣍟'=>'𣍟','𣎓'=>'𣎓','𣎜'=>'𣎜','𣏃'=>'𣏃','𣏕'=>'𣏕','𣑭'=>'𣑭','𣚣'=>'𣚣','𣢧'=>'𣢧','𣪍'=>'𣪍','𣫺'=>'𣫺','𣲼'=>'𣲼','𣴞'=>'𣴞','𣻑'=>'𣻑','𣽞'=>'𣽞','𣾎'=>'𣾎','𤉣'=>'𤉣','𤎫'=>'𤎫','𤘈'=>'𤘈','𤜵'=>'𤜵','𤠔'=>'𤠔','𤰶'=>'𤰶','𤲒'=>'𤲒','𤾡'=>'𤾡','𤾸'=>'𤾸','𥁄'=>'𥁄','𥃲'=>'𥃲','𥃳'=>'𥃳','𥄙'=>'𥄙','𥄳'=>'𥄳','𥉉'=>'𥉉','𥐝'=>'𥐝','𥘦'=>'𥘦','𥚚'=>'𥚚','𥛅'=>'𥛅','𥥼'=>'𥥼','𥪧'=>'𥪧','𥪧'=>'𥪧','𥮫'=>'𥮫','𥲀'=>'𥲀','𥳐'=>'𥳐','𥾆'=>'𥾆','𦇚'=>'𦇚','𦈨'=>'𦈨','𦉇'=>'𦉇','𦋙'=>'𦋙','𦌾'=>'𦌾','𦓚'=>'𦓚','𦔣'=>'𦔣','𦖨'=>'𦖨','𦞧'=>'𦞧','𦞵'=>'𦞵','𦬼'=>'𦬼','𦰶'=>'𦰶','𦳕'=>'𦳕','𦵫'=>'𦵫','𦼬'=>'𦼬','𦾱'=>'𦾱','𧃒'=>'𧃒','𧏊'=>'𧏊','𧙧'=>'𧙧','𧢮'=>'𧢮','𧥦'=>'𧥦','𧲨'=>'𧲨','𧻓'=>'𧻓','𧼯'=>'𧼯','𨗒'=>'𨗒','𨗭'=>'𨗭','𨜮'=>'𨜮','𨯺'=>'𨯺','𨵷'=>'𨵷','𩅅'=>'𩅅','𩇟'=>'𩇟','𩈚'=>'𩈚','𩐊'=>'𩐊','𩒖'=>'𩒖','𩖶'=>'𩖶','𩬰'=>'𩬰','𪃎'=>'𪃎','𪄅'=>'𪄅','𪈎'=>'𪈎','𪊑'=>'𪊑','𪎒'=>'𪎒','𪘀'=>'𪘀','℃'=>'°C','℉'=>'°F','ℇ'=>'Ɛ','℻'=>'FAX','ℕ'=>'N','№'=>'No','ℚ'=>'Q','₨'=>'Rs','𝐓'=>'T','℡'=>'TEL','𝐔'=>'U','𝐖'=>'W','₩'=>'W̵','𝐗'=>'X','¥'=>'Y̵','𝚲'=>'Λ','𝚵'=>'Ξ','ℿ'=>'Π','ϲ'=>'c','ϒ'=>'Y','𝚽'=>'Φ','𝚿'=>'Ψ','ѣ'=>'Ь̵','ਃ'=>'ঃ','ಃ'=>'ః','່'=>'่','់'=>'่','້'=>'้','໊'=>'๊','໋'=>'๋','៕'=>'๚','៚'=>'๛','ъ'=>'ˉb','៙'=>'๏','೧'=>'౧','૨'=>'२','೨'=>'౨','૩'=>'३','૪'=>'४','૮'=>'८','೯'=>'౯','а'=>'a','Ꮟ'=>'b','ᖯ'=>'b','с'=>'c','ԁ'=>'d','ᑯ'=>'d','е'=>'e','ә'=>'ǝ','ε'=>'ɛ','є'=>'ɛ','ք'=>'f','ց'=>'g','һ'=>'h','հ'=>'h','Ꮒ'=>'h','Ᏺ'=>'h̔','ι'=>'i','і'=>'i','Ꭵ'=>'i','ј'=>'j','յ'=>'j','ᗰ'=>'m','ո'=>'n','η'=>'n̩','ం'=>'o','ಂ'=>'o','ം'=>'o','०'=>'o','੦'=>'o','૦'=>'o','๐'=>'o','໐'=>'o','ο'=>'o','о'=>'o','օ'=>'o','ဝ'=>'o','ρ'=>'p','р'=>'p','ᴩ'=>'ᴘ','գ'=>'q','κ'=>'ĸ','к'=>'ĸ','ᴦ'=>'r','г'=>'r','ѕ'=>'s','υ'=>'u','ս'=>'u','ν'=>'v','ѵ'=>'v','Ꮃ'=>'w','ᗯ'=>'w','х'=>'x','ᕁ'=>'x','у'=>'y','Ꭹ'=>'y','ӡ'=>'ʒ','ჳ'=>'ʒ','ϩ'=>'ƨ','ь'=>'ƅ','ы'=>'ƅi','ɑ'=>'α','ծ'=>'δ','ᕷ'=>'δ','п'=>'π','ɸ'=>'φ','ф'=>'φ','ʙ'=>'в','ɜ'=>'з','ᴍ'=>'м','ʜ'=>'н','ɢ'=>'ԍ','ᴛ'=>'т','ᴙ'=>'я','ઽ'=>'ऽ','ુ'=>'ु','ૂ'=>'ू','ੋ'=>'ॆ','੍'=>'्','્'=>'्','ഉ'=>'உ','ജ'=>'ஐ','ണ'=>'ண','ഴ'=>'ழ','ി'=>'ி','ു'=>'ூ','ಅ'=>'అ','ಆ'=>'ఆ','ಇ'=>'ఇ','ಒ'=>'ఒ','ಓ'=>'ఒౕ','ಜ'=>'జ','ಞ'=>'ఞ','ಣ'=>'ణ','థ'=>'ధּ','ಯ'=>'య','ఠ'=>'రּ','ಱ'=>'ఱ','ಲ'=>'ల','ඌ'=>'ന്ന','ஶ'=>'ശ','ຈ'=>'จ','ບ'=>'บ','ປ'=>'ป','ຝ'=>'ฝ','ພ'=>'พ','ຟ'=>'ฟ','ຍ'=>'ย','។'=>'ฯ','ិ'=>'ิ','ី'=>'ี','ឹ'=>'ึ','ឺ'=>'ื','ຸ'=>'ุ','ູ'=>'ู','ᗅ'=>'A','ᒍ'=>'J','ᕼ'=>'H','ᐯ'=>'V','ᑭ'=>'P','ᗷ'=>'B','ヘ'=>'へ','𐏑'=>'𐎂','𐏓'=>'𐎓','𒀸'=>'𐎚','ᅳ'=>'一','ǀ'=>'丨','ᅵ'=>'丨','Ꭺ'=>'A','Ᏼ'=>'B','Ꮯ'=>'C','ᗞ'=>'D','Ꭼ'=>'E','ᖴ'=>'F','Ꮐ'=>'G','Ꮋ'=>'H','Ꭻ'=>'J','Ꮶ'=>'K','Ꮮ'=>'L','Ꮇ'=>'M','Ꮲ'=>'P','ᖇ'=>'R','Ꮥ'=>'S','Ꮩ'=>'V','Ꮓ'=>'Z'); diff --git a/includes/utf/data/recode_basic.php b/includes/utf/data/recode_basic.php new file mode 100644 index 0000000..8a9dc54 --- /dev/null +++ b/includes/utf/data/recode_basic.php @@ -0,0 +1,1541 @@ + "\xC2\x80", + "\x81" => "\xC2\x81", + "\x82" => "\xC2\x82", + "\x83" => "\xC2\x83", + "\x84" => "\xC2\x84", + "\x85" => "\xC2\x85", + "\x86" => "\xC2\x86", + "\x87" => "\xC2\x87", + "\x88" => "\xC2\x88", + "\x89" => "\xC2\x89", + "\x8A" => "\xC2\x8A", + "\x8B" => "\xC2\x8B", + "\x8C" => "\xC2\x8C", + "\x8D" => "\xC2\x8D", + "\x8E" => "\xC2\x8E", + "\x8F" => "\xC2\x8F", + "\x90" => "\xC2\x90", + "\x91" => "\xC2\x91", + "\x92" => "\xC2\x92", + "\x93" => "\xC2\x93", + "\x94" => "\xC2\x94", + "\x95" => "\xC2\x95", + "\x96" => "\xC2\x96", + "\x97" => "\xC2\x97", + "\x98" => "\xC2\x98", + "\x99" => "\xC2\x99", + "\x9A" => "\xC2\x9A", + "\x9B" => "\xC2\x9B", + "\x9C" => "\xC2\x9C", + "\x9D" => "\xC2\x9D", + "\x9E" => "\xC2\x9E", + "\x9F" => "\xC2\x9F", + "\xA0" => "\xC2\xA0", + "\xA1" => "\xC4\x84", + "\xA2" => "\xCB\x98", + "\xA3" => "\xC5\x81", + "\xA4" => "\xC2\xA4", + "\xA5" => "\xC4\xBD", + "\xA6" => "\xC5\x9A", + "\xA7" => "\xC2\xA7", + "\xA8" => "\xC2\xA8", + "\xA9" => "\xC5\xA0", + "\xAA" => "\xC5\x9E", + "\xAB" => "\xC5\xA4", + "\xAC" => "\xC5\xB9", + "\xAD" => "\xC2\xAD", + "\xAE" => "\xC5\xBD", + "\xAF" => "\xC5\xBB", + "\xB0" => "\xC2\xB0", + "\xB1" => "\xC4\x85", + "\xB2" => "\xCB\x9B", + "\xB3" => "\xC5\x82", + "\xB4" => "\xC2\xB4", + "\xB5" => "\xC4\xBE", + "\xB6" => "\xC5\x9B", + "\xB7" => "\xCB\x87", + "\xB8" => "\xC2\xB8", + "\xB9" => "\xC5\xA1", + "\xBA" => "\xC5\x9F", + "\xBB" => "\xC5\xA5", + "\xBC" => "\xC5\xBA", + "\xBD" => "\xCB\x9D", + "\xBE" => "\xC5\xBE", + "\xBF" => "\xC5\xBC", + "\xC0" => "\xC5\x94", + "\xC1" => "\xC3\x81", + "\xC2" => "\xC3\x82", + "\xC3" => "\xC4\x82", + "\xC4" => "\xC3\x84", + "\xC5" => "\xC4\xB9", + "\xC6" => "\xC4\x86", + "\xC7" => "\xC3\x87", + "\xC8" => "\xC4\x8C", + "\xC9" => "\xC3\x89", + "\xCA" => "\xC4\x98", + "\xCB" => "\xC3\x8B", + "\xCC" => "\xC4\x9A", + "\xCD" => "\xC3\x8D", + "\xCE" => "\xC3\x8E", + "\xCF" => "\xC4\x8E", + "\xD0" => "\xC4\x90", + "\xD1" => "\xC5\x83", + "\xD2" => "\xC5\x87", + "\xD3" => "\xC3\x93", + "\xD4" => "\xC3\x94", + "\xD5" => "\xC5\x90", + "\xD6" => "\xC3\x96", + "\xD7" => "\xC3\x97", + "\xD8" => "\xC5\x98", + "\xD9" => "\xC5\xAE", + "\xDA" => "\xC3\x9A", + "\xDB" => "\xC5\xB0", + "\xDC" => "\xC3\x9C", + "\xDD" => "\xC3\x9D", + "\xDE" => "\xC5\xA2", + "\xDF" => "\xC3\x9F", + "\xE0" => "\xC5\x95", + "\xE1" => "\xC3\xA1", + "\xE2" => "\xC3\xA2", + "\xE3" => "\xC4\x83", + "\xE4" => "\xC3\xA4", + "\xE5" => "\xC4\xBA", + "\xE6" => "\xC4\x87", + "\xE7" => "\xC3\xA7", + "\xE8" => "\xC4\x8D", + "\xE9" => "\xC3\xA9", + "\xEA" => "\xC4\x99", + "\xEB" => "\xC3\xAB", + "\xEC" => "\xC4\x9B", + "\xED" => "\xC3\xAD", + "\xEE" => "\xC3\xAE", + "\xEF" => "\xC4\x8F", + "\xF0" => "\xC4\x91", + "\xF1" => "\xC5\x84", + "\xF2" => "\xC5\x88", + "\xF3" => "\xC3\xB3", + "\xF4" => "\xC3\xB4", + "\xF5" => "\xC5\x91", + "\xF6" => "\xC3\xB6", + "\xF7" => "\xC3\xB7", + "\xF8" => "\xC5\x99", + "\xF9" => "\xC5\xAF", + "\xFA" => "\xC3\xBA", + "\xFB" => "\xC5\xB1", + "\xFC" => "\xC3\xBC", + "\xFD" => "\xC3\xBD", + "\xFE" => "\xC5\xA3", + "\xFF" => "\xCB\x99", + ); + return strtr($string, $transform); +} + +function iso_8859_4($string) +{ + static $transform = array( + "\x80" => "\xC2\x80", + "\x81" => "\xC2\x81", + "\x82" => "\xC2\x82", + "\x83" => "\xC2\x83", + "\x84" => "\xC2\x84", + "\x85" => "\xC2\x85", + "\x86" => "\xC2\x86", + "\x87" => "\xC2\x87", + "\x88" => "\xC2\x88", + "\x89" => "\xC2\x89", + "\x8A" => "\xC2\x8A", + "\x8B" => "\xC2\x8B", + "\x8C" => "\xC2\x8C", + "\x8D" => "\xC2\x8D", + "\x8E" => "\xC2\x8E", + "\x8F" => "\xC2\x8F", + "\x90" => "\xC2\x90", + "\x91" => "\xC2\x91", + "\x92" => "\xC2\x92", + "\x93" => "\xC2\x93", + "\x94" => "\xC2\x94", + "\x95" => "\xC2\x95", + "\x96" => "\xC2\x96", + "\x97" => "\xC2\x97", + "\x98" => "\xC2\x98", + "\x99" => "\xC2\x99", + "\x9A" => "\xC2\x9A", + "\x9B" => "\xC2\x9B", + "\x9C" => "\xC2\x9C", + "\x9D" => "\xC2\x9D", + "\x9E" => "\xC2\x9E", + "\x9F" => "\xC2\x9F", + "\xA0" => "\xC2\xA0", + "\xA1" => "\xC4\x84", + "\xA2" => "\xC4\xB8", + "\xA3" => "\xC5\x96", + "\xA4" => "\xC2\xA4", + "\xA5" => "\xC4\xA8", + "\xA6" => "\xC4\xBB", + "\xA7" => "\xC2\xA7", + "\xA8" => "\xC2\xA8", + "\xA9" => "\xC5\xA0", + "\xAA" => "\xC4\x92", + "\xAB" => "\xC4\xA2", + "\xAC" => "\xC5\xA6", + "\xAD" => "\xC2\xAD", + "\xAE" => "\xC5\xBD", + "\xAF" => "\xC2\xAF", + "\xB0" => "\xC2\xB0", + "\xB1" => "\xC4\x85", + "\xB2" => "\xCB\x9B", + "\xB3" => "\xC5\x97", + "\xB4" => "\xC2\xB4", + "\xB5" => "\xC4\xA9", + "\xB6" => "\xC4\xBC", + "\xB7" => "\xCB\x87", + "\xB8" => "\xC2\xB8", + "\xB9" => "\xC5\xA1", + "\xBA" => "\xC4\x93", + "\xBB" => "\xC4\xA3", + "\xBC" => "\xC5\xA7", + "\xBD" => "\xC5\x8A", + "\xBE" => "\xC5\xBE", + "\xBF" => "\xC5\x8B", + "\xC0" => "\xC4\x80", + "\xC1" => "\xC3\x81", + "\xC2" => "\xC3\x82", + "\xC3" => "\xC3\x83", + "\xC4" => "\xC3\x84", + "\xC5" => "\xC3\x85", + "\xC6" => "\xC3\x86", + "\xC7" => "\xC4\xAE", + "\xC8" => "\xC4\x8C", + "\xC9" => "\xC3\x89", + "\xCA" => "\xC4\x98", + "\xCB" => "\xC3\x8B", + "\xCC" => "\xC4\x96", + "\xCD" => "\xC3\x8D", + "\xCE" => "\xC3\x8E", + "\xCF" => "\xC4\xAA", + "\xD0" => "\xC4\x90", + "\xD1" => "\xC5\x85", + "\xD2" => "\xC5\x8C", + "\xD3" => "\xC4\xB6", + "\xD4" => "\xC3\x94", + "\xD5" => "\xC3\x95", + "\xD6" => "\xC3\x96", + "\xD7" => "\xC3\x97", + "\xD8" => "\xC3\x98", + "\xD9" => "\xC5\xB2", + "\xDA" => "\xC3\x9A", + "\xDB" => "\xC3\x9B", + "\xDC" => "\xC3\x9C", + "\xDD" => "\xC5\xA8", + "\xDE" => "\xC5\xAA", + "\xDF" => "\xC3\x9F", + "\xE0" => "\xC4\x81", + "\xE1" => "\xC3\xA1", + "\xE2" => "\xC3\xA2", + "\xE3" => "\xC3\xA3", + "\xE4" => "\xC3\xA4", + "\xE5" => "\xC3\xA5", + "\xE6" => "\xC3\xA6", + "\xE7" => "\xC4\xAF", + "\xE8" => "\xC4\x8D", + "\xE9" => "\xC3\xA9", + "\xEA" => "\xC4\x99", + "\xEB" => "\xC3\xAB", + "\xEC" => "\xC4\x97", + "\xED" => "\xC3\xAD", + "\xEE" => "\xC3\xAE", + "\xEF" => "\xC4\xAB", + "\xF0" => "\xC4\x91", + "\xF1" => "\xC5\x86", + "\xF2" => "\xC5\x8D", + "\xF3" => "\xC4\xB7", + "\xF4" => "\xC3\xB4", + "\xF5" => "\xC3\xB5", + "\xF6" => "\xC3\xB6", + "\xF7" => "\xC3\xB7", + "\xF8" => "\xC3\xB8", + "\xF9" => "\xC5\xB3", + "\xFA" => "\xC3\xBA", + "\xFB" => "\xC3\xBB", + "\xFC" => "\xC3\xBC", + "\xFD" => "\xC5\xA9", + "\xFE" => "\xC5\xAB", + "\xFF" => "\xCB\x99", + ); + return strtr($string, $transform); +} + +function iso_8859_7($string) +{ + static $transform = array( + "\x80" => "\xC2\x80", + "\x81" => "\xC2\x81", + "\x82" => "\xC2\x82", + "\x83" => "\xC2\x83", + "\x84" => "\xC2\x84", + "\x85" => "\xC2\x85", + "\x86" => "\xC2\x86", + "\x87" => "\xC2\x87", + "\x88" => "\xC2\x88", + "\x89" => "\xC2\x89", + "\x8A" => "\xC2\x8A", + "\x8B" => "\xC2\x8B", + "\x8C" => "\xC2\x8C", + "\x8D" => "\xC2\x8D", + "\x8E" => "\xC2\x8E", + "\x8F" => "\xC2\x8F", + "\x90" => "\xC2\x90", + "\x91" => "\xC2\x91", + "\x92" => "\xC2\x92", + "\x93" => "\xC2\x93", + "\x94" => "\xC2\x94", + "\x95" => "\xC2\x95", + "\x96" => "\xC2\x96", + "\x97" => "\xC2\x97", + "\x98" => "\xC2\x98", + "\x99" => "\xC2\x99", + "\x9A" => "\xC2\x9A", + "\x9B" => "\xC2\x9B", + "\x9C" => "\xC2\x9C", + "\x9D" => "\xC2\x9D", + "\x9E" => "\xC2\x9E", + "\x9F" => "\xC2\x9F", + "\xA0" => "\xC2\xA0", + "\xA1" => "\xE2\x80\x98", + "\xA2" => "\xE2\x80\x99", + "\xA3" => "\xC2\xA3", + "\xA4" => "\xE2\x82\xAC", + "\xA5" => "\xE2\x82\xAF", + "\xA6" => "\xC2\xA6", + "\xA7" => "\xC2\xA7", + "\xA8" => "\xC2\xA8", + "\xA9" => "\xC2\xA9", + "\xAA" => "\xCD\xBA", + "\xAB" => "\xC2\xAB", + "\xAC" => "\xC2\xAC", + "\xAD" => "\xC2\xAD", + "\xAF" => "\xE2\x80\x95", + "\xB0" => "\xC2\xB0", + "\xB1" => "\xC2\xB1", + "\xB2" => "\xC2\xB2", + "\xB3" => "\xC2\xB3", + "\xB4" => "\xCE\x84", + "\xB5" => "\xCE\x85", + "\xB6" => "\xCE\x86", + "\xB7" => "\xC2\xB7", + "\xB8" => "\xCE\x88", + "\xB9" => "\xCE\x89", + "\xBA" => "\xCE\x8A", + "\xBB" => "\xC2\xBB", + "\xBC" => "\xCE\x8C", + "\xBD" => "\xC2\xBD", + "\xBE" => "\xCE\x8E", + "\xBF" => "\xCE\x8F", + "\xC0" => "\xCE\x90", + "\xC1" => "\xCE\x91", + "\xC2" => "\xCE\x92", + "\xC3" => "\xCE\x93", + "\xC4" => "\xCE\x94", + "\xC5" => "\xCE\x95", + "\xC6" => "\xCE\x96", + "\xC7" => "\xCE\x97", + "\xC8" => "\xCE\x98", + "\xC9" => "\xCE\x99", + "\xCA" => "\xCE\x9A", + "\xCB" => "\xCE\x9B", + "\xCC" => "\xCE\x9C", + "\xCD" => "\xCE\x9D", + "\xCE" => "\xCE\x9E", + "\xCF" => "\xCE\x9F", + "\xD0" => "\xCE\xA0", + "\xD1" => "\xCE\xA1", + "\xD3" => "\xCE\xA3", + "\xD4" => "\xCE\xA4", + "\xD5" => "\xCE\xA5", + "\xD6" => "\xCE\xA6", + "\xD7" => "\xCE\xA7", + "\xD8" => "\xCE\xA8", + "\xD9" => "\xCE\xA9", + "\xDA" => "\xCE\xAA", + "\xDB" => "\xCE\xAB", + "\xDC" => "\xCE\xAC", + "\xDD" => "\xCE\xAD", + "\xDE" => "\xCE\xAE", + "\xDF" => "\xCE\xAF", + "\xE0" => "\xCE\xB0", + "\xE1" => "\xCE\xB1", + "\xE2" => "\xCE\xB2", + "\xE3" => "\xCE\xB3", + "\xE4" => "\xCE\xB4", + "\xE5" => "\xCE\xB5", + "\xE6" => "\xCE\xB6", + "\xE7" => "\xCE\xB7", + "\xE8" => "\xCE\xB8", + "\xE9" => "\xCE\xB9", + "\xEA" => "\xCE\xBA", + "\xEB" => "\xCE\xBB", + "\xEC" => "\xCE\xBC", + "\xED" => "\xCE\xBD", + "\xEE" => "\xCE\xBE", + "\xEF" => "\xCE\xBF", + "\xF0" => "\xCF\x80", + "\xF1" => "\xCF\x81", + "\xF2" => "\xCF\x82", + "\xF3" => "\xCF\x83", + "\xF4" => "\xCF\x84", + "\xF5" => "\xCF\x85", + "\xF6" => "\xCF\x86", + "\xF7" => "\xCF\x87", + "\xF8" => "\xCF\x88", + "\xF9" => "\xCF\x89", + "\xFA" => "\xCF\x8A", + "\xFB" => "\xCF\x8B", + "\xFC" => "\xCF\x8C", + "\xFD" => "\xCF\x8D", + "\xFE" => "\xCF\x8E", + ); + return strtr($string, $transform); +} + +function iso_8859_8($string) +{ + static $transform = array( + "\xC2\xAA" => "\xC3\x97", + "\xC2\xBA" => "\xC3\xB7", + "\xC3\x9F" => "\xE2\x80\x97", + "\xC3\xA0" => "\xD7\x90", + "\xC3\xA1" => "\xD7\x91", + "\xC3\xA2" => "\xD7\x92", + "\xC3\xA3" => "\xD7\x93", + "\xC3\xA4" => "\xD7\x94", + "\xC3\xA5" => "\xD7\x95", + "\xC3\xA6" => "\xD7\x96", + "\xC3\xA7" => "\xD7\x97", + "\xC3\xA8" => "\xD7\x98", + "\xC3\xA9" => "\xD7\x99", + "\xC3\xAA" => "\xD7\x9A", + "\xC3\xAB" => "\xD7\x9B", + "\xC3\xAC" => "\xD7\x9C", + "\xC3\xAD" => "\xD7\x9D", + "\xC3\xAE" => "\xD7\x9E", + "\xC3\xAF" => "\xD7\x9F", + "\xC3\xB0" => "\xD7\xA0", + "\xC3\xB1" => "\xD7\xA1", + "\xC3\xB2" => "\xD7\xA2", + "\xC3\xB3" => "\xD7\xA3", + "\xC3\xB4" => "\xD7\xA4", + "\xC3\xB5" => "\xD7\xA5", + "\xC3\xB6" => "\xD7\xA6", + "\xC3\xB7" => "\xD7\xA7", + "\xC3\xB8" => "\xD7\xA8", + "\xC3\xB9" => "\xD7\xA9", + "\xC3\xBA" => "\xD7\xAA", + "\xC3\xBD" => "\xE2\x80\x8E", + "\xC3\xBE" => "\xE2\x80\x8F", + ); + return strtr(utf8_encode($string), $transform); +} + +function iso_8859_9($string) +{ + static $transform = array( + "\xC3\x90" => "\xC4\x9E", + "\xC3\x9D" => "\xC4\xB0", + "\xC3\x9E" => "\xC5\x9E", + "\xC3\xB0" => "\xC4\x9F", + "\xC3\xBD" => "\xC4\xB1", + "\xC3\xBE" => "\xC5\x9F", + ); + return strtr(utf8_encode($string), $transform); +} + +function iso_8859_15($string) +{ + static $transform = array( + "\xC2\xA4" => "\xE2\x82\xAC", + "\xC2\xA6" => "\xC5\xA0", + "\xC2\xA8" => "\xC5\xA1", + "\xC2\xB4" => "\xC5\xBD", + "\xC2\xB8" => "\xC5\xBE", + "\xC2\xBC" => "\xC5\x92", + "\xC2\xBD" => "\xC5\x93", + "\xC2\xBE" => "\xC5\xB8", + ); + return strtr(utf8_encode($string), $transform); +} + +// nearly the same as iso-8859-11 +function tis_620($string) +{ + static $transform = array( + "\x80" => "\xC2\x80", + "\x81" => "\xC2\x81", + "\x82" => "\xC2\x82", + "\x83" => "\xC2\x83", + "\x84" => "\xC2\x84", + "\x85" => "\xC2\x85", + "\x86" => "\xC2\x86", + "\x87" => "\xC2\x87", + "\x88" => "\xC2\x88", + "\x89" => "\xC2\x89", + "\x8A" => "\xC2\x8A", + "\x8B" => "\xC2\x8B", + "\x8C" => "\xC2\x8C", + "\x8D" => "\xC2\x8D", + "\x8E" => "\xC2\x8E", + "\x8F" => "\xC2\x8F", + "\x90" => "\xC2\x90", + "\x91" => "\xC2\x91", + "\x92" => "\xC2\x92", + "\x93" => "\xC2\x93", + "\x94" => "\xC2\x94", + "\x95" => "\xC2\x95", + "\x96" => "\xC2\x96", + "\x97" => "\xC2\x97", + "\x98" => "\xC2\x98", + "\x99" => "\xC2\x99", + "\x9A" => "\xC2\x9A", + "\x9B" => "\xC2\x9B", + "\x9C" => "\xC2\x9C", + "\x9D" => "\xC2\x9D", + "\x9E" => "\xC2\x9E", + "\x9F" => "\xC2\x9F", + "\xA1" => "\xE0\xB8\x81", + "\xA2" => "\xE0\xB8\x82", + "\xA3" => "\xE0\xB8\x83", + "\xA4" => "\xE0\xB8\x84", + "\xA5" => "\xE0\xB8\x85", + "\xA6" => "\xE0\xB8\x86", + "\xA7" => "\xE0\xB8\x87", + "\xA8" => "\xE0\xB8\x88", + "\xA9" => "\xE0\xB8\x89", + "\xAA" => "\xE0\xB8\x8A", + "\xAB" => "\xE0\xB8\x8B", + "\xAC" => "\xE0\xB8\x8C", + "\xAD" => "\xE0\xB8\x8D", + "\xAE" => "\xE0\xB8\x8E", + "\xAF" => "\xE0\xB8\x8F", + "\xB0" => "\xE0\xB8\x90", + "\xB1" => "\xE0\xB8\x91", + "\xB2" => "\xE0\xB8\x92", + "\xB3" => "\xE0\xB8\x93", + "\xB4" => "\xE0\xB8\x94", + "\xB5" => "\xE0\xB8\x95", + "\xB6" => "\xE0\xB8\x96", + "\xB7" => "\xE0\xB8\x97", + "\xB8" => "\xE0\xB8\x98", + "\xB9" => "\xE0\xB8\x99", + "\xBA" => "\xE0\xB8\x9A", + "\xBB" => "\xE0\xB8\x9B", + "\xBC" => "\xE0\xB8\x9C", + "\xBD" => "\xE0\xB8\x9D", + "\xBE" => "\xE0\xB8\x9E", + "\xBF" => "\xE0\xB8\x9F", + "\xC0" => "\xE0\xB8\xA0", + "\xC1" => "\xE0\xB8\xA1", + "\xC2" => "\xE0\xB8\xA2", + "\xC3" => "\xE0\xB8\xA3", + "\xC4" => "\xE0\xB8\xA4", + "\xC5" => "\xE0\xB8\xA5", + "\xC6" => "\xE0\xB8\xA6", + "\xC7" => "\xE0\xB8\xA7", + "\xC8" => "\xE0\xB8\xA8", + "\xC9" => "\xE0\xB8\xA9", + "\xCA" => "\xE0\xB8\xAA", + "\xCB" => "\xE0\xB8\xAB", + "\xCC" => "\xE0\xB8\xAC", + "\xCD" => "\xE0\xB8\xAD", + "\xCE" => "\xE0\xB8\xAE", + "\xCF" => "\xE0\xB8\xAF", + "\xD0" => "\xE0\xB8\xB0", + "\xD1" => "\xE0\xB8\xB1", + "\xD2" => "\xE0\xB8\xB2", + "\xD3" => "\xE0\xB8\xB3", + "\xD4" => "\xE0\xB8\xB4", + "\xD5" => "\xE0\xB8\xB5", + "\xD6" => "\xE0\xB8\xB6", + "\xD7" => "\xE0\xB8\xB7", + "\xD8" => "\xE0\xB8\xB8", + "\xD9" => "\xE0\xB8\xB9", + "\xDA" => "\xE0\xB8\xBA", + "\xDF" => "\xE0\xB8\xBF", + "\xE0" => "\xE0\xB9\x80", + "\xE1" => "\xE0\xB9\x81", + "\xE2" => "\xE0\xB9\x82", + "\xE3" => "\xE0\xB9\x83", + "\xE4" => "\xE0\xB9\x84", + "\xE5" => "\xE0\xB9\x85", + "\xE6" => "\xE0\xB9\x86", + "\xE7" => "\xE0\xB9\x87", + "\xE8" => "\xE0\xB9\x88", + "\xE9" => "\xE0\xB9\x89", + "\xEA" => "\xE0\xB9\x8A", + "\xEB" => "\xE0\xB9\x8B", + "\xEC" => "\xE0\xB9\x8C", + "\xED" => "\xE0\xB9\x8D", + "\xEE" => "\xE0\xB9\x8E", + "\xEF" => "\xE0\xB9\x8F", + "\xF0" => "\xE0\xB9\x90", + "\xF1" => "\xE0\xB9\x91", + "\xF2" => "\xE0\xB9\x92", + "\xF3" => "\xE0\xB9\x93", + "\xF4" => "\xE0\xB9\x94", + "\xF5" => "\xE0\xB9\x95", + "\xF6" => "\xE0\xB9\x96", + "\xF7" => "\xE0\xB9\x97", + "\xF8" => "\xE0\xB9\x98", + "\xF9" => "\xE0\xB9\x99", + "\xFA" => "\xE0\xB9\x9A", + "\xFB" => "\xE0\xB9\x9B", + ); + return strtr($string, $transform); +} + +function cp874($string) +{ + static $transform = array( + "\x80" => "\xE2\x82\xAC", + "\x85" => "\xE2\x80\xA6", + "\x91" => "\xE2\x80\x98", + "\x92" => "\xE2\x80\x99", + "\x93" => "\xE2\x80\x9C", + "\x94" => "\xE2\x80\x9D", + "\x95" => "\xE2\x80\xA2", + "\x96" => "\xE2\x80\x93", + "\x97" => "\xE2\x80\x94", + "\xA0" => "\xC2\xA0", + "\xA1" => "\xE0\xB8\x81", + "\xA2" => "\xE0\xB8\x82", + "\xA3" => "\xE0\xB8\x83", + "\xA4" => "\xE0\xB8\x84", + "\xA5" => "\xE0\xB8\x85", + "\xA6" => "\xE0\xB8\x86", + "\xA7" => "\xE0\xB8\x87", + "\xA8" => "\xE0\xB8\x88", + "\xA9" => "\xE0\xB8\x89", + "\xAA" => "\xE0\xB8\x8A", + "\xAB" => "\xE0\xB8\x8B", + "\xAC" => "\xE0\xB8\x8C", + "\xAD" => "\xE0\xB8\x8D", + "\xAE" => "\xE0\xB8\x8E", + "\xAF" => "\xE0\xB8\x8F", + "\xB0" => "\xE0\xB8\x90", + "\xB1" => "\xE0\xB8\x91", + "\xB2" => "\xE0\xB8\x92", + "\xB3" => "\xE0\xB8\x93", + "\xB4" => "\xE0\xB8\x94", + "\xB5" => "\xE0\xB8\x95", + "\xB6" => "\xE0\xB8\x96", + "\xB7" => "\xE0\xB8\x97", + "\xB8" => "\xE0\xB8\x98", + "\xB9" => "\xE0\xB8\x99", + "\xBA" => "\xE0\xB8\x9A", + "\xBB" => "\xE0\xB8\x9B", + "\xBC" => "\xE0\xB8\x9C", + "\xBD" => "\xE0\xB8\x9D", + "\xBE" => "\xE0\xB8\x9E", + "\xBF" => "\xE0\xB8\x9F", + "\xC0" => "\xE0\xB8\xA0", + "\xC1" => "\xE0\xB8\xA1", + "\xC2" => "\xE0\xB8\xA2", + "\xC3" => "\xE0\xB8\xA3", + "\xC4" => "\xE0\xB8\xA4", + "\xC5" => "\xE0\xB8\xA5", + "\xC6" => "\xE0\xB8\xA6", + "\xC7" => "\xE0\xB8\xA7", + "\xC8" => "\xE0\xB8\xA8", + "\xC9" => "\xE0\xB8\xA9", + "\xCA" => "\xE0\xB8\xAA", + "\xCB" => "\xE0\xB8\xAB", + "\xCC" => "\xE0\xB8\xAC", + "\xCD" => "\xE0\xB8\xAD", + "\xCE" => "\xE0\xB8\xAE", + "\xCF" => "\xE0\xB8\xAF", + "\xD0" => "\xE0\xB8\xB0", + "\xD1" => "\xE0\xB8\xB1", + "\xD2" => "\xE0\xB8\xB2", + "\xD3" => "\xE0\xB8\xB3", + "\xD4" => "\xE0\xB8\xB4", + "\xD5" => "\xE0\xB8\xB5", + "\xD6" => "\xE0\xB8\xB6", + "\xD7" => "\xE0\xB8\xB7", + "\xD8" => "\xE0\xB8\xB8", + "\xD9" => "\xE0\xB8\xB9", + "\xDA" => "\xE0\xB8\xBA", + "\xDF" => "\xE0\xB8\xBF", + "\xE0" => "\xE0\xB9\x80", + "\xE1" => "\xE0\xB9\x81", + "\xE2" => "\xE0\xB9\x82", + "\xE3" => "\xE0\xB9\x83", + "\xE4" => "\xE0\xB9\x84", + "\xE5" => "\xE0\xB9\x85", + "\xE6" => "\xE0\xB9\x86", + "\xE7" => "\xE0\xB9\x87", + "\xE8" => "\xE0\xB9\x88", + "\xE9" => "\xE0\xB9\x89", + "\xEA" => "\xE0\xB9\x8A", + "\xEB" => "\xE0\xB9\x8B", + "\xEC" => "\xE0\xB9\x8C", + "\xED" => "\xE0\xB9\x8D", + "\xEE" => "\xE0\xB9\x8E", + "\xEF" => "\xE0\xB9\x8F", + "\xF0" => "\xE0\xB9\x90", + "\xF1" => "\xE0\xB9\x91", + "\xF2" => "\xE0\xB9\x92", + "\xF3" => "\xE0\xB9\x93", + "\xF4" => "\xE0\xB9\x94", + "\xF5" => "\xE0\xB9\x95", + "\xF6" => "\xE0\xB9\x96", + "\xF7" => "\xE0\xB9\x97", + "\xF8" => "\xE0\xB9\x98", + "\xF9" => "\xE0\xB9\x99", + "\xFA" => "\xE0\xB9\x9A", + "\xFB" => "\xE0\xB9\x9B", + ); + return strtr($string, $transform); +} + +function cp1250($string) +{ + static $transform = array( + "\x80" => "\xE2\x82\xAC", + "\x82" => "\xE2\x80\x9A", + "\x84" => "\xE2\x80\x9E", + "\x85" => "\xE2\x80\xA6", + "\x86" => "\xE2\x80\xA0", + "\x87" => "\xE2\x80\xA1", + "\x89" => "\xE2\x80\xB0", + "\x8A" => "\xC5\xA0", + "\x8B" => "\xE2\x80\xB9", + "\x8C" => "\xC5\x9A", + "\x8D" => "\xC5\xA4", + "\x8E" => "\xC5\xBD", + "\x8F" => "\xC5\xB9", + "\x91" => "\xE2\x80\x98", + "\x92" => "\xE2\x80\x99", + "\x93" => "\xE2\x80\x9C", + "\x94" => "\xE2\x80\x9D", + "\x95" => "\xE2\x80\xA2", + "\x96" => "\xE2\x80\x93", + "\x97" => "\xE2\x80\x94", + "\x99" => "\xE2\x84\xA2", + "\x9A" => "\xC5\xA1", + "\x9B" => "\xE2\x80\xBA", + "\x9C" => "\xC5\x9B", + "\x9D" => "\xC5\xA5", + "\x9E" => "\xC5\xBE", + "\x9F" => "\xC5\xBA", + "\xA0" => "\xC2\xA0", + "\xA1" => "\xCB\x87", + "\xA2" => "\xCB\x98", + "\xA3" => "\xC5\x81", + "\xA4" => "\xC2\xA4", + "\xA5" => "\xC4\x84", + "\xA6" => "\xC2\xA6", + "\xA7" => "\xC2\xA7", + "\xA8" => "\xC2\xA8", + "\xA9" => "\xC2\xA9", + "\xAA" => "\xC5\x9E", + "\xAB" => "\xC2\xAB", + "\xAC" => "\xC2\xAC", + "\xAD" => "\xC2\xAD", + "\xAE" => "\xC2\xAE", + "\xAF" => "\xC5\xBB", + "\xB0" => "\xC2\xB0", + "\xB1" => "\xC2\xB1", + "\xB2" => "\xCB\x9B", + "\xB3" => "\xC5\x82", + "\xB4" => "\xC2\xB4", + "\xB5" => "\xC2\xB5", + "\xB6" => "\xC2\xB6", + "\xB7" => "\xC2\xB7", + "\xB8" => "\xC2\xB8", + "\xB9" => "\xC4\x85", + "\xBA" => "\xC5\x9F", + "\xBB" => "\xC2\xBB", + "\xBC" => "\xC4\xBD", + "\xBD" => "\xCB\x9D", + "\xBE" => "\xC4\xBE", + "\xBF" => "\xC5\xBC", + "\xC0" => "\xC5\x94", + "\xC1" => "\xC3\x81", + "\xC2" => "\xC3\x82", + "\xC3" => "\xC4\x82", + "\xC4" => "\xC3\x84", + "\xC5" => "\xC4\xB9", + "\xC6" => "\xC4\x86", + "\xC7" => "\xC3\x87", + "\xC8" => "\xC4\x8C", + "\xC9" => "\xC3\x89", + "\xCA" => "\xC4\x98", + "\xCB" => "\xC3\x8B", + "\xCC" => "\xC4\x9A", + "\xCD" => "\xC3\x8D", + "\xCE" => "\xC3\x8E", + "\xCF" => "\xC4\x8E", + "\xD0" => "\xC4\x90", + "\xD1" => "\xC5\x83", + "\xD2" => "\xC5\x87", + "\xD3" => "\xC3\x93", + "\xD4" => "\xC3\x94", + "\xD5" => "\xC5\x90", + "\xD6" => "\xC3\x96", + "\xD7" => "\xC3\x97", + "\xD8" => "\xC5\x98", + "\xD9" => "\xC5\xAE", + "\xDA" => "\xC3\x9A", + "\xDB" => "\xC5\xB0", + "\xDC" => "\xC3\x9C", + "\xDD" => "\xC3\x9D", + "\xDE" => "\xC5\xA2", + "\xDF" => "\xC3\x9F", + "\xE0" => "\xC5\x95", + "\xE1" => "\xC3\xA1", + "\xE2" => "\xC3\xA2", + "\xE3" => "\xC4\x83", + "\xE4" => "\xC3\xA4", + "\xE5" => "\xC4\xBA", + "\xE6" => "\xC4\x87", + "\xE7" => "\xC3\xA7", + "\xE8" => "\xC4\x8D", + "\xE9" => "\xC3\xA9", + "\xEA" => "\xC4\x99", + "\xEB" => "\xC3\xAB", + "\xEC" => "\xC4\x9B", + "\xED" => "\xC3\xAD", + "\xEE" => "\xC3\xAE", + "\xEF" => "\xC4\x8F", + "\xF0" => "\xC4\x91", + "\xF1" => "\xC5\x84", + "\xF2" => "\xC5\x88", + "\xF3" => "\xC3\xB3", + "\xF4" => "\xC3\xB4", + "\xF5" => "\xC5\x91", + "\xF6" => "\xC3\xB6", + "\xF7" => "\xC3\xB7", + "\xF8" => "\xC5\x99", + "\xF9" => "\xC5\xAF", + "\xFA" => "\xC3\xBA", + "\xFB" => "\xC5\xB1", + "\xFC" => "\xC3\xBC", + "\xFD" => "\xC3\xBD", + "\xFE" => "\xC5\xA3", + "\xFF" => "\xCB\x99", + ); + return strtr($string, $transform); +} + +function cp1251($string) +{ + static $transform = array( + "\x80" => "\xD0\x82", + "\x81" => "\xD0\x83", + "\x82" => "\xE2\x80\x9A", + "\x83" => "\xD1\x93", + "\x84" => "\xE2\x80\x9E", + "\x85" => "\xE2\x80\xA6", + "\x86" => "\xE2\x80\xA0", + "\x87" => "\xE2\x80\xA1", + "\x88" => "\xE2\x82\xAC", + "\x89" => "\xE2\x80\xB0", + "\x8A" => "\xD0\x89", + "\x8B" => "\xE2\x80\xB9", + "\x8C" => "\xD0\x8A", + "\x8D" => "\xD0\x8C", + "\x8E" => "\xD0\x8B", + "\x8F" => "\xD0\x8F", + "\x90" => "\xD1\x92", + "\x91" => "\xE2\x80\x98", + "\x92" => "\xE2\x80\x99", + "\x93" => "\xE2\x80\x9C", + "\x94" => "\xE2\x80\x9D", + "\x95" => "\xE2\x80\xA2", + "\x96" => "\xE2\x80\x93", + "\x97" => "\xE2\x80\x94", + "\x99" => "\xE2\x84\xA2", + "\x9A" => "\xD1\x99", + "\x9B" => "\xE2\x80\xBA", + "\x9C" => "\xD1\x9A", + "\x9D" => "\xD1\x9C", + "\x9E" => "\xD1\x9B", + "\x9F" => "\xD1\x9F", + "\xA0" => "\xC2\xA0", + "\xA1" => "\xD0\x8E", + "\xA2" => "\xD1\x9E", + "\xA3" => "\xD0\x88", + "\xA4" => "\xC2\xA4", + "\xA5" => "\xD2\x90", + "\xA6" => "\xC2\xA6", + "\xA7" => "\xC2\xA7", + "\xA8" => "\xD0\x81", + "\xA9" => "\xC2\xA9", + "\xAA" => "\xD0\x84", + "\xAB" => "\xC2\xAB", + "\xAC" => "\xC2\xAC", + "\xAD" => "\xC2\xAD", + "\xAE" => "\xC2\xAE", + "\xAF" => "\xD0\x87", + "\xB0" => "\xC2\xB0", + "\xB1" => "\xC2\xB1", + "\xB2" => "\xD0\x86", + "\xB3" => "\xD1\x96", + "\xB4" => "\xD2\x91", + "\xB5" => "\xC2\xB5", + "\xB6" => "\xC2\xB6", + "\xB7" => "\xC2\xB7", + "\xB8" => "\xD1\x91", + "\xB9" => "\xE2\x84\x96", + "\xBA" => "\xD1\x94", + "\xBB" => "\xC2\xBB", + "\xBC" => "\xD1\x98", + "\xBD" => "\xD0\x85", + "\xBE" => "\xD1\x95", + "\xBF" => "\xD1\x97", + "\xC0" => "\xD0\x90", + "\xC1" => "\xD0\x91", + "\xC2" => "\xD0\x92", + "\xC3" => "\xD0\x93", + "\xC4" => "\xD0\x94", + "\xC5" => "\xD0\x95", + "\xC6" => "\xD0\x96", + "\xC7" => "\xD0\x97", + "\xC8" => "\xD0\x98", + "\xC9" => "\xD0\x99", + "\xCA" => "\xD0\x9A", + "\xCB" => "\xD0\x9B", + "\xCC" => "\xD0\x9C", + "\xCD" => "\xD0\x9D", + "\xCE" => "\xD0\x9E", + "\xCF" => "\xD0\x9F", + "\xD0" => "\xD0\xA0", + "\xD1" => "\xD0\xA1", + "\xD2" => "\xD0\xA2", + "\xD3" => "\xD0\xA3", + "\xD4" => "\xD0\xA4", + "\xD5" => "\xD0\xA5", + "\xD6" => "\xD0\xA6", + "\xD7" => "\xD0\xA7", + "\xD8" => "\xD0\xA8", + "\xD9" => "\xD0\xA9", + "\xDA" => "\xD0\xAA", + "\xDB" => "\xD0\xAB", + "\xDC" => "\xD0\xAC", + "\xDD" => "\xD0\xAD", + "\xDE" => "\xD0\xAE", + "\xDF" => "\xD0\xAF", + "\xE0" => "\xD0\xB0", + "\xE1" => "\xD0\xB1", + "\xE2" => "\xD0\xB2", + "\xE3" => "\xD0\xB3", + "\xE4" => "\xD0\xB4", + "\xE5" => "\xD0\xB5", + "\xE6" => "\xD0\xB6", + "\xE7" => "\xD0\xB7", + "\xE8" => "\xD0\xB8", + "\xE9" => "\xD0\xB9", + "\xEA" => "\xD0\xBA", + "\xEB" => "\xD0\xBB", + "\xEC" => "\xD0\xBC", + "\xED" => "\xD0\xBD", + "\xEE" => "\xD0\xBE", + "\xEF" => "\xD0\xBF", + "\xF0" => "\xD1\x80", + "\xF1" => "\xD1\x81", + "\xF2" => "\xD1\x82", + "\xF3" => "\xD1\x83", + "\xF4" => "\xD1\x84", + "\xF5" => "\xD1\x85", + "\xF6" => "\xD1\x86", + "\xF7" => "\xD1\x87", + "\xF8" => "\xD1\x88", + "\xF9" => "\xD1\x89", + "\xFA" => "\xD1\x8A", + "\xFB" => "\xD1\x8B", + "\xFC" => "\xD1\x8C", + "\xFD" => "\xD1\x8D", + "\xFE" => "\xD1\x8E", + "\xFF" => "\xD1\x8F", + ); + return strtr($string, $transform); +} + +function cp1252($string) +{ + static $transform = array( + "\xC2\x80" => "\xE2\x82\xAC", + "\xC2\x82" => "\xE2\x80\x9A", + "\xC2\x83" => "\xC6\x92", + "\xC2\x84" => "\xE2\x80\x9E", + "\xC2\x85" => "\xE2\x80\xA6", + "\xC2\x86" => "\xE2\x80\xA0", + "\xC2\x87" => "\xE2\x80\xA1", + "\xC2\x88" => "\xCB\x86", + "\xC2\x89" => "\xE2\x80\xB0", + "\xC2\x8A" => "\xC5\xA0", + "\xC2\x8B" => "\xE2\x80\xB9", + "\xC2\x8C" => "\xC5\x92", + "\xC2\x8E" => "\xC5\xBD", + "\xC2\x91" => "\xE2\x80\x98", + "\xC2\x92" => "\xE2\x80\x99", + "\xC2\x93" => "\xE2\x80\x9C", + "\xC2\x94" => "\xE2\x80\x9D", + "\xC2\x95" => "\xE2\x80\xA2", + "\xC2\x96" => "\xE2\x80\x93", + "\xC2\x97" => "\xE2\x80\x94", + "\xC2\x98" => "\xCB\x9C", + "\xC2\x99" => "\xE2\x84\xA2", + "\xC2\x9A" => "\xC5\xA1", + "\xC2\x9B" => "\xE2\x80\xBA", + "\xC2\x9C" => "\xC5\x93", + "\xC2\x9E" => "\xC5\xBE", + "\xC2\x9F" => "\xC5\xB8" + ); + return strtr(utf8_encode($string), $transform); +} + +function cp1254($string) +{ + static $transform = array( + "\xC2\x80" => "\xE2\x82\xAC", + "\xC2\x82" => "\xE2\x80\x9A", + "\xC2\x83" => "\xC6\x92", + "\xC2\x84" => "\xE2\x80\x9E", + "\xC2\x85" => "\xE2\x80\xA6", + "\xC2\x86" => "\xE2\x80\xA0", + "\xC2\x87" => "\xE2\x80\xA1", + "\xC2\x88" => "\xCB\x86", + "\xC2\x89" => "\xE2\x80\xB0", + "\xC2\x8A" => "\xC5\xA0", + "\xC2\x8B" => "\xE2\x80\xB9", + "\xC2\x8C" => "\xC5\x92", + "\xC2\x91" => "\xE2\x80\x98", + "\xC2\x92" => "\xE2\x80\x99", + "\xC2\x93" => "\xE2\x80\x9C", + "\xC2\x94" => "\xE2\x80\x9D", + "\xC2\x95" => "\xE2\x80\xA2", + "\xC2\x96" => "\xE2\x80\x93", + "\xC2\x97" => "\xE2\x80\x94", + "\xC2\x98" => "\xCB\x9C", + "\xC2\x99" => "\xE2\x84\xA2", + "\xC2\x9A" => "\xC5\xA1", + "\xC2\x9B" => "\xE2\x80\xBA", + "\xC2\x9C" => "\xC5\x93", + "\xC2\x9F" => "\xC5\xB8", + "\xC3\x90" => "\xC4\x9E", + "\xC3\x9D" => "\xC4\xB0", + "\xC3\x9E" => "\xC5\x9E", + "\xC3\xB0" => "\xC4\x9F", + "\xC3\xBD" => "\xC4\xB1", + "\xC3\xBE" => "\xC5\x9F", + ); + return strtr(utf8_encode($string), $transform); +} + +function cp1255($string) +{ + static $transform = array( + "\x80" => "\xE2\x82\xAC", + "\x82" => "\xE2\x80\x9A", + "\x83" => "\xC6\x92", + "\x84" => "\xE2\x80\x9E", + "\x85" => "\xE2\x80\xA6", + "\x86" => "\xE2\x80\xA0", + "\x87" => "\xE2\x80\xA1", + "\x88" => "\xCB\x86", + "\x89" => "\xE2\x80\xB0", + "\x8B" => "\xE2\x80\xB9", + "\x91" => "\xE2\x80\x98", + "\x92" => "\xE2\x80\x99", + "\x93" => "\xE2\x80\x9C", + "\x94" => "\xE2\x80\x9D", + "\x95" => "\xE2\x80\xA2", + "\x96" => "\xE2\x80\x93", + "\x97" => "\xE2\x80\x94", + "\x98" => "\xCB\x9C", + "\x99" => "\xE2\x84\xA2", + "\x9B" => "\xE2\x80\xBA", + "\xA0" => "\xC2\xA0", + "\xA1" => "\xC2\xA1", + "\xA2" => "\xC2\xA2", + "\xA3" => "\xC2\xA3", + "\xA4" => "\xE2\x82\xAA", + "\xA5" => "\xC2\xA5", + "\xA6" => "\xC2\xA6", + "\xA7" => "\xC2\xA7", + "\xA8" => "\xC2\xA8", + "\xA9" => "\xC2\xA9", + "\xAA" => "\xC3\x97", + "\xAB" => "\xC2\xAB", + "\xAC" => "\xC2\xAC", + "\xAD" => "\xC2\xAD", + "\xAE" => "\xC2\xAE", + "\xAF" => "\xC2\xAF", + "\xB0" => "\xC2\xB0", + "\xB1" => "\xC2\xB1", + "\xB2" => "\xC2\xB2", + "\xB3" => "\xC2\xB3", + "\xB4" => "\xC2\xB4", + "\xB5" => "\xC2\xB5", + "\xB6" => "\xC2\xB6", + "\xB7" => "\xC2\xB7", + "\xB8" => "\xC2\xB8", + "\xB9" => "\xC2\xB9", + "\xBA" => "\xC3\xB7", + "\xBB" => "\xC2\xBB", + "\xBC" => "\xC2\xBC", + "\xBD" => "\xC2\xBD", + "\xBE" => "\xC2\xBE", + "\xBF" => "\xC2\xBF", + "\xC0" => "\xD6\xB0", + "\xC1" => "\xD6\xB1", + "\xC2" => "\xD6\xB2", + "\xC3" => "\xD6\xB3", + "\xC4" => "\xD6\xB4", + "\xC5" => "\xD6\xB5", + "\xC6" => "\xD6\xB6", + "\xC7" => "\xD6\xB7", + "\xC8" => "\xD6\xB8", + "\xC9" => "\xD6\xB9", + "\xCB" => "\xD6\xBB", + "\xCC" => "\xD6\xBC", + "\xCD" => "\xD6\xBD", + "\xCE" => "\xD6\xBE", + "\xCF" => "\xD6\xBF", + "\xD0" => "\xD7\x80", + "\xD1" => "\xD7\x81", + "\xD2" => "\xD7\x82", + "\xD3" => "\xD7\x83", + "\xD4" => "\xD7\xB0", + "\xD5" => "\xD7\xB1", + "\xD6" => "\xD7\xB2", + "\xD7" => "\xD7\xB3", + "\xD8" => "\xD7\xB4", + "\xE0" => "\xD7\x90", + "\xE1" => "\xD7\x91", + "\xE2" => "\xD7\x92", + "\xE3" => "\xD7\x93", + "\xE4" => "\xD7\x94", + "\xE5" => "\xD7\x95", + "\xE6" => "\xD7\x96", + "\xE7" => "\xD7\x97", + "\xE8" => "\xD7\x98", + "\xE9" => "\xD7\x99", + "\xEA" => "\xD7\x9A", + "\xEB" => "\xD7\x9B", + "\xEC" => "\xD7\x9C", + "\xED" => "\xD7\x9D", + "\xEE" => "\xD7\x9E", + "\xEF" => "\xD7\x9F", + "\xF0" => "\xD7\xA0", + "\xF1" => "\xD7\xA1", + "\xF2" => "\xD7\xA2", + "\xF3" => "\xD7\xA3", + "\xF4" => "\xD7\xA4", + "\xF5" => "\xD7\xA5", + "\xF6" => "\xD7\xA6", + "\xF7" => "\xD7\xA7", + "\xF8" => "\xD7\xA8", + "\xF9" => "\xD7\xA9", + "\xFA" => "\xD7\xAA", + "\xFD" => "\xE2\x80\x8E", + "\xFE" => "\xE2\x80\x8F", + ); + return strtr($string, $transform); +} + +function cp1256($string) +{ + static $transform = array( + "\x80" => "\xE2\x82\xAC", + "\x81" => "\xD9\xBE", + "\x82" => "\xE2\x80\x9A", + "\x83" => "\xC6\x92", + "\x84" => "\xE2\x80\x9E", + "\x85" => "\xE2\x80\xA6", + "\x86" => "\xE2\x80\xA0", + "\x87" => "\xE2\x80\xA1", + "\x88" => "\xCB\x86", + "\x89" => "\xE2\x80\xB0", + "\x8A" => "\xD9\xB9", + "\x8B" => "\xE2\x80\xB9", + "\x8C" => "\xC5\x92", + "\x8D" => "\xDA\x86", + "\x8E" => "\xDA\x98", + "\x8F" => "\xDA\x88", + "\x90" => "\xDA\xAF", + "\x91" => "\xE2\x80\x98", + "\x92" => "\xE2\x80\x99", + "\x93" => "\xE2\x80\x9C", + "\x94" => "\xE2\x80\x9D", + "\x95" => "\xE2\x80\xA2", + "\x96" => "\xE2\x80\x93", + "\x97" => "\xE2\x80\x94", + "\x98" => "\xDA\xA9", + "\x99" => "\xE2\x84\xA2", + "\x9A" => "\xDA\x91", + "\x9B" => "\xE2\x80\xBA", + "\x9C" => "\xC5\x93", + "\x9D" => "\xE2\x80\x8C", + "\x9E" => "\xE2\x80\x8D", + "\x9F" => "\xDA\xBA", + "\xA0" => "\xC2\xA0", + "\xA1" => "\xD8\x8C", + "\xA2" => "\xC2\xA2", + "\xA3" => "\xC2\xA3", + "\xA4" => "\xC2\xA4", + "\xA5" => "\xC2\xA5", + "\xA6" => "\xC2\xA6", + "\xA7" => "\xC2\xA7", + "\xA8" => "\xC2\xA8", + "\xA9" => "\xC2\xA9", + "\xAA" => "\xDA\xBE", + "\xAB" => "\xC2\xAB", + "\xAC" => "\xC2\xAC", + "\xAD" => "\xC2\xAD", + "\xAE" => "\xC2\xAE", + "\xAF" => "\xC2\xAF", + "\xB0" => "\xC2\xB0", + "\xB1" => "\xC2\xB1", + "\xB2" => "\xC2\xB2", + "\xB3" => "\xC2\xB3", + "\xB4" => "\xC2\xB4", + "\xB5" => "\xC2\xB5", + "\xB6" => "\xC2\xB6", + "\xB7" => "\xC2\xB7", + "\xB8" => "\xC2\xB8", + "\xB9" => "\xC2\xB9", + "\xBA" => "\xD8\x9B", + "\xBB" => "\xC2\xBB", + "\xBC" => "\xC2\xBC", + "\xBD" => "\xC2\xBD", + "\xBE" => "\xC2\xBE", + "\xBF" => "\xD8\x9F", + "\xC0" => "\xDB\x81", + "\xC1" => "\xD8\xA1", + "\xC2" => "\xD8\xA2", + "\xC3" => "\xD8\xA3", + "\xC4" => "\xD8\xA4", + "\xC5" => "\xD8\xA5", + "\xC6" => "\xD8\xA6", + "\xC7" => "\xD8\xA7", + "\xC8" => "\xD8\xA8", + "\xC9" => "\xD8\xA9", + "\xCA" => "\xD8\xAA", + "\xCB" => "\xD8\xAB", + "\xCC" => "\xD8\xAC", + "\xCD" => "\xD8\xAD", + "\xCE" => "\xD8\xAE", + "\xCF" => "\xD8\xAF", + "\xD0" => "\xD8\xB0", + "\xD1" => "\xD8\xB1", + "\xD2" => "\xD8\xB2", + "\xD3" => "\xD8\xB3", + "\xD4" => "\xD8\xB4", + "\xD5" => "\xD8\xB5", + "\xD6" => "\xD8\xB6", + "\xD7" => "\xC3\x97", + "\xD8" => "\xD8\xB7", + "\xD9" => "\xD8\xB8", + "\xDA" => "\xD8\xB9", + "\xDB" => "\xD8\xBA", + "\xDC" => "\xD9\x80", + "\xDD" => "\xD9\x81", + "\xDE" => "\xD9\x82", + "\xDF" => "\xD9\x83", + "\xE0" => "\xC3\xA0", + "\xE1" => "\xD9\x84", + "\xE2" => "\xC3\xA2", + "\xE3" => "\xD9\x85", + "\xE4" => "\xD9\x86", + "\xE5" => "\xD9\x87", + "\xE6" => "\xD9\x88", + "\xE7" => "\xC3\xA7", + "\xE8" => "\xC3\xA8", + "\xE9" => "\xC3\xA9", + "\xEA" => "\xC3\xAA", + "\xEB" => "\xC3\xAB", + "\xEC" => "\xD9\x89", + "\xED" => "\xD9\x8A", + "\xEE" => "\xC3\xAE", + "\xEF" => "\xC3\xAF", + "\xF0" => "\xD9\x8B", + "\xF1" => "\xD9\x8C", + "\xF2" => "\xD9\x8D", + "\xF3" => "\xD9\x8E", + "\xF4" => "\xC3\xB4", + "\xF5" => "\xD9\x8F", + "\xF6" => "\xD9\x90", + "\xF7" => "\xC3\xB7", + "\xF8" => "\xD9\x91", + "\xF9" => "\xC3\xB9", + "\xFA" => "\xD9\x92", + "\xFB" => "\xC3\xBB", + "\xFC" => "\xC3\xBC", + "\xFD" => "\xE2\x80\x8E", + "\xFE" => "\xE2\x80\x8F", + "\xFF" => "\xDB\x92", + ); + return strtr($string, $transform); +} + +function cp1257($string) +{ + static $transform = array( + "\x80" => "\xE2\x82\xAC", + "\x82" => "\xE2\x80\x9A", + "\x84" => "\xE2\x80\x9E", + "\x85" => "\xE2\x80\xA6", + "\x86" => "\xE2\x80\xA0", + "\x87" => "\xE2\x80\xA1", + "\x89" => "\xE2\x80\xB0", + "\x8B" => "\xE2\x80\xB9", + "\x8D" => "\xC2\xA8", + "\x8E" => "\xCB\x87", + "\x8F" => "\xC2\xB8", + "\x91" => "\xE2\x80\x98", + "\x92" => "\xE2\x80\x99", + "\x93" => "\xE2\x80\x9C", + "\x94" => "\xE2\x80\x9D", + "\x95" => "\xE2\x80\xA2", + "\x96" => "\xE2\x80\x93", + "\x97" => "\xE2\x80\x94", + "\x99" => "\xE2\x84\xA2", + "\x9B" => "\xE2\x80\xBA", + "\x9D" => "\xC2\xAF", + "\x9E" => "\xCB\x9B", + "\xA0" => "\xC2\xA0", + "\xA2" => "\xC2\xA2", + "\xA3" => "\xC2\xA3", + "\xA4" => "\xC2\xA4", + "\xA6" => "\xC2\xA6", + "\xA7" => "\xC2\xA7", + "\xA8" => "\xC3\x98", + "\xA9" => "\xC2\xA9", + "\xAA" => "\xC5\x96", + "\xAB" => "\xC2\xAB", + "\xAC" => "\xC2\xAC", + "\xAD" => "\xC2\xAD", + "\xAE" => "\xC2\xAE", + "\xAF" => "\xC3\x86", + "\xB0" => "\xC2\xB0", + "\xB1" => "\xC2\xB1", + "\xB2" => "\xC2\xB2", + "\xB3" => "\xC2\xB3", + "\xB4" => "\xC2\xB4", + "\xB5" => "\xC2\xB5", + "\xB6" => "\xC2\xB6", + "\xB7" => "\xC2\xB7", + "\xB8" => "\xC3\xB8", + "\xB9" => "\xC2\xB9", + "\xBA" => "\xC5\x97", + "\xBB" => "\xC2\xBB", + "\xBC" => "\xC2\xBC", + "\xBD" => "\xC2\xBD", + "\xBE" => "\xC2\xBE", + "\xBF" => "\xC3\xA6", + "\xC0" => "\xC4\x84", + "\xC1" => "\xC4\xAE", + "\xC2" => "\xC4\x80", + "\xC3" => "\xC4\x86", + "\xC4" => "\xC3\x84", + "\xC5" => "\xC3\x85", + "\xC6" => "\xC4\x98", + "\xC7" => "\xC4\x92", + "\xC8" => "\xC4\x8C", + "\xC9" => "\xC3\x89", + "\xCA" => "\xC5\xB9", + "\xCB" => "\xC4\x96", + "\xCC" => "\xC4\xA2", + "\xCD" => "\xC4\xB6", + "\xCE" => "\xC4\xAA", + "\xCF" => "\xC4\xBB", + "\xD0" => "\xC5\xA0", + "\xD1" => "\xC5\x83", + "\xD2" => "\xC5\x85", + "\xD3" => "\xC3\x93", + "\xD4" => "\xC5\x8C", + "\xD5" => "\xC3\x95", + "\xD6" => "\xC3\x96", + "\xD7" => "\xC3\x97", + "\xD8" => "\xC5\xB2", + "\xD9" => "\xC5\x81", + "\xDA" => "\xC5\x9A", + "\xDB" => "\xC5\xAA", + "\xDC" => "\xC3\x9C", + "\xDD" => "\xC5\xBB", + "\xDE" => "\xC5\xBD", + "\xDF" => "\xC3\x9F", + "\xE0" => "\xC4\x85", + "\xE1" => "\xC4\xAF", + "\xE2" => "\xC4\x81", + "\xE3" => "\xC4\x87", + "\xE4" => "\xC3\xA4", + "\xE5" => "\xC3\xA5", + "\xE6" => "\xC4\x99", + "\xE7" => "\xC4\x93", + "\xE8" => "\xC4\x8D", + "\xE9" => "\xC3\xA9", + "\xEA" => "\xC5\xBA", + "\xEB" => "\xC4\x97", + "\xEC" => "\xC4\xA3", + "\xED" => "\xC4\xB7", + "\xEE" => "\xC4\xAB", + "\xEF" => "\xC4\xBC", + "\xF0" => "\xC5\xA1", + "\xF1" => "\xC5\x84", + "\xF2" => "\xC5\x86", + "\xF3" => "\xC3\xB3", + "\xF4" => "\xC5\x8D", + "\xF5" => "\xC3\xB5", + "\xF6" => "\xC3\xB6", + "\xF7" => "\xC3\xB7", + "\xF8" => "\xC5\xB3", + "\xF9" => "\xC5\x82", + "\xFA" => "\xC5\x9B", + "\xFB" => "\xC5\xAB", + "\xFC" => "\xC3\xBC", + "\xFD" => "\xC5\xBC", + "\xFE" => "\xC5\xBE", + "\xFF" => "\xCB\x99", + ); + return strtr($string, $transform); +} + +function utf8_to_cp1252($string) +{ + static $transform = array( + "\xE2\x82\xAC" => "\x80", + "\xE2\x80\x9A" => "\x82", + "\xC6\x92" => "\x83", + "\xE2\x80\x9E" => "\x84", + "\xE2\x80\xA6" => "\x85", + "\xE2\x80\xA0" => "\x86", + "\xE2\x80\xA1" => "\x87", + "\xCB\x86" => "\x88", + "\xE2\x80\xB0" => "\x89", + "\xC5\xA0" => "\x8A", + "\xE2\x80\xB9" => "\x8B", + "\xC5\x92" => "\x8C", + "\xC5\xBD" => "\x8E", + "\xE2\x80\x98" => "\x91", + "\xE2\x80\x99" => "\x92", + "\xE2\x80\x9C" => "\x93", + "\xE2\x80\x9D" => "\x94", + "\xE2\x80\xA2" => "\x95", + "\xE2\x80\x93" => "\x96", + "\xE2\x80\x94" => "\x97", + "\xCB\x9C" => "\x98", + "\xE2\x84\xA2" => "\x99", + "\xC5\xA1" => "\x9A", + "\xE2\x80\xBA" => "\x9B", + "\xC5\x93" => "\x9C", + "\xC5\xBE" => "\x9E", + "\xC5\xB8" => "\x9F", + "\xC2\xA0" => "\xA0", + "\xC2\xA1" => "\xA1", + "\xC2\xA2" => "\xA2", + "\xC2\xA3" => "\xA3", + "\xC2\xA4" => "\xA4", + "\xC2\xA5" => "\xA5", + "\xC2\xA6" => "\xA6", + "\xC2\xA7" => "\xA7", + "\xC2\xA8" => "\xA8", + "\xC2\xA9" => "\xA9", + "\xC2\xAA" => "\xAA", + "\xC2\xAB" => "\xAB", + "\xC2\xAC" => "\xAC", + "\xC2\xAD" => "\xAD", + "\xC2\xAE" => "\xAE", + "\xC2\xAF" => "\xAF", + "\xC2\xB0" => "\xB0", + "\xC2\xB1" => "\xB1", + "\xC2\xB2" => "\xB2", + "\xC2\xB3" => "\xB3", + "\xC2\xB4" => "\xB4", + "\xC2\xB5" => "\xB5", + "\xC2\xB6" => "\xB6", + "\xC2\xB7" => "\xB7", + "\xC2\xB8" => "\xB8", + "\xC2\xB9" => "\xB9", + "\xC2\xBA" => "\xBA", + "\xC2\xBB" => "\xBB", + "\xC2\xBC" => "\xBC", + "\xC2\xBD" => "\xBD", + "\xC2\xBE" => "\xBE", + "\xC2\xBF" => "\xBF", + "\xC3\x80" => "\xC0", + "\xC3\x81" => "\xC1", + "\xC3\x82" => "\xC2", + "\xC3\x83" => "\xC3", + "\xC3\x84" => "\xC4", + "\xC3\x85" => "\xC5", + "\xC3\x86" => "\xC6", + "\xC3\x87" => "\xC7", + "\xC3\x88" => "\xC8", + "\xC3\x89" => "\xC9", + "\xC3\x8A" => "\xCA", + "\xC3\x8B" => "\xCB", + "\xC3\x8C" => "\xCC", + "\xC3\x8D" => "\xCD", + "\xC3\x8E" => "\xCE", + "\xC3\x8F" => "\xCF", + "\xC3\x90" => "\xD0", + "\xC3\x91" => "\xD1", + "\xC3\x92" => "\xD2", + "\xC3\x93" => "\xD3", + "\xC3\x94" => "\xD4", + "\xC3\x95" => "\xD5", + "\xC3\x96" => "\xD6", + "\xC3\x97" => "\xD7", + "\xC3\x98" => "\xD8", + "\xC3\x99" => "\xD9", + "\xC3\x9A" => "\xDA", + "\xC3\x9B" => "\xDB", + "\xC3\x9C" => "\xDC", + "\xC3\x9D" => "\xDD", + "\xC3\x9E" => "\xDE", + "\xC3\x9F" => "\xDF", + "\xC3\xA0" => "\xE0", + "\xC3\xA1" => "\xE1", + "\xC3\xA2" => "\xE2", + "\xC3\xA3" => "\xE3", + "\xC3\xA4" => "\xE4", + "\xC3\xA5" => "\xE5", + "\xC3\xA6" => "\xE6", + "\xC3\xA7" => "\xE7", + "\xC3\xA8" => "\xE8", + "\xC3\xA9" => "\xE9", + "\xC3\xAA" => "\xEA", + "\xC3\xAB" => "\xEB", + "\xC3\xAC" => "\xEC", + "\xC3\xAD" => "\xED", + "\xC3\xAE" => "\xEE", + "\xC3\xAF" => "\xEF", + "\xC3\xB0" => "\xF0", + "\xC3\xB1" => "\xF1", + "\xC3\xB2" => "\xF2", + "\xC3\xB3" => "\xF3", + "\xC3\xB4" => "\xF4", + "\xC3\xB5" => "\xF5", + "\xC3\xB6" => "\xF6", + "\xC3\xB7" => "\xF7", + "\xC3\xB8" => "\xF8", + "\xC3\xB9" => "\xF9", + "\xC3\xBA" => "\xFA", + "\xC3\xBB" => "\xFB", + "\xC3\xBC" => "\xFC", + "\xC3\xBD" => "\xFD", + "\xC3\xBE" => "\xFE", + "\xC3\xBF" => "\xFF" + ); + return strtr($string, $transform); +} diff --git a/includes/utf/data/recode_cjk.php b/includes/utf/data/recode_cjk.php new file mode 100644 index 0000000..8c65274 --- /dev/null +++ b/includes/utf/data/recode_cjk.php @@ -0,0 +1,45177 @@ + "\xE3\x80\x80", + "\xA1\xA2" => "\xE3\x80\x81", + "\xA1\xA3" => "\xE3\x80\x82", + "\xA1\xA4" => "\xE3\x83\xBB", + "\xA1\xA5" => "\xCB\x89", + "\xA1\xA6" => "\xCB\x87", + "\xA1\xA7" => "\xC2\xA8", + "\xA1\xA8" => "\xE3\x80\x83", + "\xA1\xA9" => "\xE3\x80\x85", + "\xA1\xAA" => "\xE2\x80\x95", + "\xA1\xAB" => "\xEF\xBD\x9E", + "\xA1\xAC" => "\xE2\x80\x96", + "\xA1\xAD" => "\xE2\x80\xA6", + "\xA1\xAE" => "\xE2\x80\x98", + "\xA1\xAF" => "\xE2\x80\x99", + "\xA1\xB0" => "\xE2\x80\x9C", + "\xA1\xB1" => "\xE2\x80\x9D", + "\xA1\xB2" => "\xE3\x80\x94", + "\xA1\xB3" => "\xE3\x80\x95", + "\xA1\xB4" => "\xE3\x80\x88", + "\xA1\xB5" => "\xE3\x80\x89", + "\xA1\xB6" => "\xE3\x80\x8A", + "\xA1\xB7" => "\xE3\x80\x8B", + "\xA1\xB8" => "\xE3\x80\x8C", + "\xA1\xB9" => "\xE3\x80\x8D", + "\xA1\xBA" => "\xE3\x80\x8E", + "\xA1\xBB" => "\xE3\x80\x8F", + "\xA1\xBC" => "\xE3\x80\x96", + "\xA1\xBD" => "\xE3\x80\x97", + "\xA1\xBE" => "\xE3\x80\x90", + "\xA1\xBF" => "\xE3\x80\x91", + "\xA1\xC0" => "\xC2\xB1", + "\xA1\xC1" => "\xC3\x97", + "\xA1\xC2" => "\xC3\xB7", + "\xA1\xC3" => "\xE2\x88\xB6", + "\xA1\xC4" => "\xE2\x88\xA7", + "\xA1\xC5" => "\xE2\x88\xA8", + "\xA1\xC6" => "\xE2\x88\x91", + "\xA1\xC7" => "\xE2\x88\x8F", + "\xA1\xC8" => "\xE2\x88\xAA", + "\xA1\xC9" => "\xE2\x88\xA9", + "\xA1\xCA" => "\xE2\x88\x88", + "\xA1\xCB" => "\xE2\x88\xB7", + "\xA1\xCC" => "\xE2\x88\x9A", + "\xA1\xCD" => "\xE2\x8A\xA5", + "\xA1\xCE" => "\xE2\x88\xA5", + "\xA1\xCF" => "\xE2\x88\xA0", + "\xA1\xD0" => "\xE2\x8C\x92", + "\xA1\xD1" => "\xE2\x8A\x99", + "\xA1\xD2" => "\xE2\x88\xAB", + "\xA1\xD3" => "\xE2\x88\xAE", + "\xA1\xD4" => "\xE2\x89\xA1", + "\xA1\xD5" => "\xE2\x89\x8C", + "\xA1\xD6" => "\xE2\x89\x88", + "\xA1\xD7" => "\xE2\x88\xBD", + "\xA1\xD8" => "\xE2\x88\x9D", + "\xA1\xD9" => "\xE2\x89\xA0", + "\xA1\xDA" => "\xE2\x89\xAE", + "\xA1\xDB" => "\xE2\x89\xAF", + "\xA1\xDC" => "\xE2\x89\xA4", + "\xA1\xDD" => "\xE2\x89\xA5", + "\xA1\xDE" => "\xE2\x88\x9E", + "\xA1\xDF" => "\xE2\x88\xB5", + "\xA1\xE0" => "\xE2\x88\xB4", + "\xA1\xE1" => "\xE2\x99\x82", + "\xA1\xE2" => "\xE2\x99\x80", + "\xA1\xE3" => "\xC2\xB0", + "\xA1\xE4" => "\xE2\x80\xB2", + "\xA1\xE5" => "\xE2\x80\xB3", + "\xA1\xE6" => "\xE2\x84\x83", + "\xA1\xE7" => "\xEF\xBC\x84", + "\xA1\xE8" => "\xC2\xA4", + "\xA1\xE9" => "\xEF\xBF\xA0", + "\xA1\xEA" => "\xEF\xBF\xA1", + "\xA1\xEB" => "\xE2\x80\xB0", + "\xA1\xEC" => "\xC2\xA7", + "\xA1\xED" => "\xE2\x84\x96", + "\xA1\xEE" => "\xE2\x98\x86", + "\xA1\xEF" => "\xE2\x98\x85", + "\xA1\xF0" => "\xE2\x97\x8B", + "\xA1\xF1" => "\xE2\x97\x8F", + "\xA1\xF2" => "\xE2\x97\x8E", + "\xA1\xF3" => "\xE2\x97\x87", + "\xA1\xF4" => "\xE2\x97\x86", + "\xA1\xF5" => "\xE2\x96\xA1", + "\xA1\xF6" => "\xE2\x96\xA0", + "\xA1\xF7" => "\xE2\x96\xB3", + "\xA1\xF8" => "\xE2\x96\xB2", + "\xA1\xF9" => "\xE2\x80\xBB", + "\xA1\xFA" => "\xE2\x86\x92", + "\xA1\xFB" => "\xE2\x86\x90", + "\xA1\xFC" => "\xE2\x86\x91", + "\xA1\xFD" => "\xE2\x86\x93", + "\xA1\xFE" => "\xE3\x80\x93", + "\xA2\xB1" => "\xE2\x92\x88", + "\xA2\xB2" => "\xE2\x92\x89", + "\xA2\xB3" => "\xE2\x92\x8A", + "\xA2\xB4" => "\xE2\x92\x8B", + "\xA2\xB5" => "\xE2\x92\x8C", + "\xA2\xB6" => "\xE2\x92\x8D", + "\xA2\xB7" => "\xE2\x92\x8E", + "\xA2\xB8" => "\xE2\x92\x8F", + "\xA2\xB9" => "\xE2\x92\x90", + "\xA2\xBA" => "\xE2\x92\x91", + "\xA2\xBB" => "\xE2\x92\x92", + "\xA2\xBC" => "\xE2\x92\x93", + "\xA2\xBD" => "\xE2\x92\x94", + "\xA2\xBE" => "\xE2\x92\x95", + "\xA2\xBF" => "\xE2\x92\x96", + "\xA2\xC0" => "\xE2\x92\x97", + "\xA2\xC1" => "\xE2\x92\x98", + "\xA2\xC2" => "\xE2\x92\x99", + "\xA2\xC3" => "\xE2\x92\x9A", + "\xA2\xC4" => "\xE2\x92\x9B", + "\xA2\xC5" => "\xE2\x91\xB4", + "\xA2\xC6" => "\xE2\x91\xB5", + "\xA2\xC7" => "\xE2\x91\xB6", + "\xA2\xC8" => "\xE2\x91\xB7", + "\xA2\xC9" => "\xE2\x91\xB8", + "\xA2\xCA" => "\xE2\x91\xB9", + "\xA2\xCB" => "\xE2\x91\xBA", + "\xA2\xCC" => "\xE2\x91\xBB", + "\xA2\xCD" => "\xE2\x91\xBC", + "\xA2\xCE" => "\xE2\x91\xBD", + "\xA2\xCF" => "\xE2\x91\xBE", + "\xA2\xD0" => "\xE2\x91\xBF", + "\xA2\xD1" => "\xE2\x92\x80", + "\xA2\xD2" => "\xE2\x92\x81", + "\xA2\xD3" => "\xE2\x92\x82", + "\xA2\xD4" => "\xE2\x92\x83", + "\xA2\xD5" => "\xE2\x92\x84", + "\xA2\xD6" => "\xE2\x92\x85", + "\xA2\xD7" => "\xE2\x92\x86", + "\xA2\xD8" => "\xE2\x92\x87", + "\xA2\xD9" => "\xE2\x91\xA0", + "\xA2\xDA" => "\xE2\x91\xA1", + "\xA2\xDB" => "\xE2\x91\xA2", + "\xA2\xDC" => "\xE2\x91\xA3", + "\xA2\xDD" => "\xE2\x91\xA4", + "\xA2\xDE" => "\xE2\x91\xA5", + "\xA2\xDF" => "\xE2\x91\xA6", + "\xA2\xE0" => "\xE2\x91\xA7", + "\xA2\xE1" => "\xE2\x91\xA8", + "\xA2\xE2" => "\xE2\x91\xA9", + "\xA2\xE5" => "\xE3\x88\xA0", + "\xA2\xE6" => "\xE3\x88\xA1", + "\xA2\xE7" => "\xE3\x88\xA2", + "\xA2\xE8" => "\xE3\x88\xA3", + "\xA2\xE9" => "\xE3\x88\xA4", + "\xA2\xEA" => "\xE3\x88\xA5", + "\xA2\xEB" => "\xE3\x88\xA6", + "\xA2\xEC" => "\xE3\x88\xA7", + "\xA2\xED" => "\xE3\x88\xA8", + "\xA2\xEE" => "\xE3\x88\xA9", + "\xA2\xF1" => "\xE2\x85\xA0", + "\xA2\xF2" => "\xE2\x85\xA1", + "\xA2\xF3" => "\xE2\x85\xA2", + "\xA2\xF4" => "\xE2\x85\xA3", + "\xA2\xF5" => "\xE2\x85\xA4", + "\xA2\xF6" => "\xE2\x85\xA5", + "\xA2\xF7" => "\xE2\x85\xA6", + "\xA2\xF8" => "\xE2\x85\xA7", + "\xA2\xF9" => "\xE2\x85\xA8", + "\xA2\xFA" => "\xE2\x85\xA9", + "\xA2\xFB" => "\xE2\x85\xAA", + "\xA2\xFC" => "\xE2\x85\xAB", + "\xA3\xA1" => "\xEF\xBC\x81", + "\xA3\xA2" => "\xEF\xBC\x82", + "\xA3\xA3" => "\xEF\xBC\x83", + "\xA3\xA4" => "\xEF\xBF\xA5", + "\xA3\xA5" => "\xEF\xBC\x85", + "\xA3\xA6" => "\xEF\xBC\x86", + "\xA3\xA7" => "\xEF\xBC\x87", + "\xA3\xA8" => "\xEF\xBC\x88", + "\xA3\xA9" => "\xEF\xBC\x89", + "\xA3\xAA" => "\xEF\xBC\x8A", + "\xA3\xAB" => "\xEF\xBC\x8B", + "\xA3\xAC" => "\xEF\xBC\x8C", + "\xA3\xAD" => "\xEF\xBC\x8D", + "\xA3\xAE" => "\xEF\xBC\x8E", + "\xA3\xAF" => "\xEF\xBC\x8F", + "\xA3\xB0" => "\xEF\xBC\x90", + "\xA3\xB1" => "\xEF\xBC\x91", + "\xA3\xB2" => "\xEF\xBC\x92", + "\xA3\xB3" => "\xEF\xBC\x93", + "\xA3\xB4" => "\xEF\xBC\x94", + "\xA3\xB5" => "\xEF\xBC\x95", + "\xA3\xB6" => "\xEF\xBC\x96", + "\xA3\xB7" => "\xEF\xBC\x97", + "\xA3\xB8" => "\xEF\xBC\x98", + "\xA3\xB9" => "\xEF\xBC\x99", + "\xA3\xBA" => "\xEF\xBC\x9A", + "\xA3\xBB" => "\xEF\xBC\x9B", + "\xA3\xBC" => "\xEF\xBC\x9C", + "\xA3\xBD" => "\xEF\xBC\x9D", + "\xA3\xBE" => "\xEF\xBC\x9E", + "\xA3\xBF" => "\xEF\xBC\x9F", + "\xA3\xC0" => "\xEF\xBC\xA0", + "\xA3\xC1" => "\xEF\xBC\xA1", + "\xA3\xC2" => "\xEF\xBC\xA2", + "\xA3\xC3" => "\xEF\xBC\xA3", + "\xA3\xC4" => "\xEF\xBC\xA4", + "\xA3\xC5" => "\xEF\xBC\xA5", + "\xA3\xC6" => "\xEF\xBC\xA6", + "\xA3\xC7" => "\xEF\xBC\xA7", + "\xA3\xC8" => "\xEF\xBC\xA8", + "\xA3\xC9" => "\xEF\xBC\xA9", + "\xA3\xCA" => "\xEF\xBC\xAA", + "\xA3\xCB" => "\xEF\xBC\xAB", + "\xA3\xCC" => "\xEF\xBC\xAC", + "\xA3\xCD" => "\xEF\xBC\xAD", + "\xA3\xCE" => "\xEF\xBC\xAE", + "\xA3\xCF" => "\xEF\xBC\xAF", + "\xA3\xD0" => "\xEF\xBC\xB0", + "\xA3\xD1" => "\xEF\xBC\xB1", + "\xA3\xD2" => "\xEF\xBC\xB2", + "\xA3\xD3" => "\xEF\xBC\xB3", + "\xA3\xD4" => "\xEF\xBC\xB4", + "\xA3\xD5" => "\xEF\xBC\xB5", + "\xA3\xD6" => "\xEF\xBC\xB6", + "\xA3\xD7" => "\xEF\xBC\xB7", + "\xA3\xD8" => "\xEF\xBC\xB8", + "\xA3\xD9" => "\xEF\xBC\xB9", + "\xA3\xDA" => "\xEF\xBC\xBA", + "\xA3\xDB" => "\xEF\xBC\xBB", + "\xA3\xDC" => "\xEF\xBC\xBC", + "\xA3\xDD" => "\xEF\xBC\xBD", + "\xA3\xDE" => "\xEF\xBC\xBE", + "\xA3\xDF" => "\xEF\xBC\xBF", + "\xA3\xE0" => "\xEF\xBD\x80", + "\xA3\xE1" => "\xEF\xBD\x81", + "\xA3\xE2" => "\xEF\xBD\x82", + "\xA3\xE3" => "\xEF\xBD\x83", + "\xA3\xE4" => "\xEF\xBD\x84", + "\xA3\xE5" => "\xEF\xBD\x85", + "\xA3\xE6" => "\xEF\xBD\x86", + "\xA3\xE7" => "\xEF\xBD\x87", + "\xA3\xE8" => "\xEF\xBD\x88", + "\xA3\xE9" => "\xEF\xBD\x89", + "\xA3\xEA" => "\xEF\xBD\x8A", + "\xA3\xEB" => "\xEF\xBD\x8B", + "\xA3\xEC" => "\xEF\xBD\x8C", + "\xA3\xED" => "\xEF\xBD\x8D", + "\xA3\xEE" => "\xEF\xBD\x8E", + "\xA3\xEF" => "\xEF\xBD\x8F", + "\xA3\xF0" => "\xEF\xBD\x90", + "\xA3\xF1" => "\xEF\xBD\x91", + "\xA3\xF2" => "\xEF\xBD\x92", + "\xA3\xF3" => "\xEF\xBD\x93", + "\xA3\xF4" => "\xEF\xBD\x94", + "\xA3\xF5" => "\xEF\xBD\x95", + "\xA3\xF6" => "\xEF\xBD\x96", + "\xA3\xF7" => "\xEF\xBD\x97", + "\xA3\xF8" => "\xEF\xBD\x98", + "\xA3\xF9" => "\xEF\xBD\x99", + "\xA3\xFA" => "\xEF\xBD\x9A", + "\xA3\xFB" => "\xEF\xBD\x9B", + "\xA3\xFC" => "\xEF\xBD\x9C", + "\xA3\xFD" => "\xEF\xBD\x9D", + "\xA3\xFE" => "\xEF\xBF\xA3", + "\xA4\xA1" => "\xE3\x81\x81", + "\xA4\xA2" => "\xE3\x81\x82", + "\xA4\xA3" => "\xE3\x81\x83", + "\xA4\xA4" => "\xE3\x81\x84", + "\xA4\xA5" => "\xE3\x81\x85", + "\xA4\xA6" => "\xE3\x81\x86", + "\xA4\xA7" => "\xE3\x81\x87", + "\xA4\xA8" => "\xE3\x81\x88", + "\xA4\xA9" => "\xE3\x81\x89", + "\xA4\xAA" => "\xE3\x81\x8A", + "\xA4\xAB" => "\xE3\x81\x8B", + "\xA4\xAC" => "\xE3\x81\x8C", + "\xA4\xAD" => "\xE3\x81\x8D", + "\xA4\xAE" => "\xE3\x81\x8E", + "\xA4\xAF" => "\xE3\x81\x8F", + "\xA4\xB0" => "\xE3\x81\x90", + "\xA4\xB1" => "\xE3\x81\x91", + "\xA4\xB2" => "\xE3\x81\x92", + "\xA4\xB3" => "\xE3\x81\x93", + "\xA4\xB4" => "\xE3\x81\x94", + "\xA4\xB5" => "\xE3\x81\x95", + "\xA4\xB6" => "\xE3\x81\x96", + "\xA4\xB7" => "\xE3\x81\x97", + "\xA4\xB8" => "\xE3\x81\x98", + "\xA4\xB9" => "\xE3\x81\x99", + "\xA4\xBA" => "\xE3\x81\x9A", + "\xA4\xBB" => "\xE3\x81\x9B", + "\xA4\xBC" => "\xE3\x81\x9C", + "\xA4\xBD" => "\xE3\x81\x9D", + "\xA4\xBE" => "\xE3\x81\x9E", + "\xA4\xBF" => "\xE3\x81\x9F", + "\xA4\xC0" => "\xE3\x81\xA0", + "\xA4\xC1" => "\xE3\x81\xA1", + "\xA4\xC2" => "\xE3\x81\xA2", + "\xA4\xC3" => "\xE3\x81\xA3", + "\xA4\xC4" => "\xE3\x81\xA4", + "\xA4\xC5" => "\xE3\x81\xA5", + "\xA4\xC6" => "\xE3\x81\xA6", + "\xA4\xC7" => "\xE3\x81\xA7", + "\xA4\xC8" => "\xE3\x81\xA8", + "\xA4\xC9" => "\xE3\x81\xA9", + "\xA4\xCA" => "\xE3\x81\xAA", + "\xA4\xCB" => "\xE3\x81\xAB", + "\xA4\xCC" => "\xE3\x81\xAC", + "\xA4\xCD" => "\xE3\x81\xAD", + "\xA4\xCE" => "\xE3\x81\xAE", + "\xA4\xCF" => "\xE3\x81\xAF", + "\xA4\xD0" => "\xE3\x81\xB0", + "\xA4\xD1" => "\xE3\x81\xB1", + "\xA4\xD2" => "\xE3\x81\xB2", + "\xA4\xD3" => "\xE3\x81\xB3", + "\xA4\xD4" => "\xE3\x81\xB4", + "\xA4\xD5" => "\xE3\x81\xB5", + "\xA4\xD6" => "\xE3\x81\xB6", + "\xA4\xD7" => "\xE3\x81\xB7", + "\xA4\xD8" => "\xE3\x81\xB8", + "\xA4\xD9" => "\xE3\x81\xB9", + "\xA4\xDA" => "\xE3\x81\xBA", + "\xA4\xDB" => "\xE3\x81\xBB", + "\xA4\xDC" => "\xE3\x81\xBC", + "\xA4\xDD" => "\xE3\x81\xBD", + "\xA4\xDE" => "\xE3\x81\xBE", + "\xA4\xDF" => "\xE3\x81\xBF", + "\xA4\xE0" => "\xE3\x82\x80", + "\xA4\xE1" => "\xE3\x82\x81", + "\xA4\xE2" => "\xE3\x82\x82", + "\xA4\xE3" => "\xE3\x82\x83", + "\xA4\xE4" => "\xE3\x82\x84", + "\xA4\xE5" => "\xE3\x82\x85", + "\xA4\xE6" => "\xE3\x82\x86", + "\xA4\xE7" => "\xE3\x82\x87", + "\xA4\xE8" => "\xE3\x82\x88", + "\xA4\xE9" => "\xE3\x82\x89", + "\xA4\xEA" => "\xE3\x82\x8A", + "\xA4\xEB" => "\xE3\x82\x8B", + "\xA4\xEC" => "\xE3\x82\x8C", + "\xA4\xED" => "\xE3\x82\x8D", + "\xA4\xEE" => "\xE3\x82\x8E", + "\xA4\xEF" => "\xE3\x82\x8F", + "\xA4\xF0" => "\xE3\x82\x90", + "\xA4\xF1" => "\xE3\x82\x91", + "\xA4\xF2" => "\xE3\x82\x92", + "\xA4\xF3" => "\xE3\x82\x93", + "\xA5\xA1" => "\xE3\x82\xA1", + "\xA5\xA2" => "\xE3\x82\xA2", + "\xA5\xA3" => "\xE3\x82\xA3", + "\xA5\xA4" => "\xE3\x82\xA4", + "\xA5\xA5" => "\xE3\x82\xA5", + "\xA5\xA6" => "\xE3\x82\xA6", + "\xA5\xA7" => "\xE3\x82\xA7", + "\xA5\xA8" => "\xE3\x82\xA8", + "\xA5\xA9" => "\xE3\x82\xA9", + "\xA5\xAA" => "\xE3\x82\xAA", + "\xA5\xAB" => "\xE3\x82\xAB", + "\xA5\xAC" => "\xE3\x82\xAC", + "\xA5\xAD" => "\xE3\x82\xAD", + "\xA5\xAE" => "\xE3\x82\xAE", + "\xA5\xAF" => "\xE3\x82\xAF", + "\xA5\xB0" => "\xE3\x82\xB0", + "\xA5\xB1" => "\xE3\x82\xB1", + "\xA5\xB2" => "\xE3\x82\xB2", + "\xA5\xB3" => "\xE3\x82\xB3", + "\xA5\xB4" => "\xE3\x82\xB4", + "\xA5\xB5" => "\xE3\x82\xB5", + "\xA5\xB6" => "\xE3\x82\xB6", + "\xA5\xB7" => "\xE3\x82\xB7", + "\xA5\xB8" => "\xE3\x82\xB8", + "\xA5\xB9" => "\xE3\x82\xB9", + "\xA5\xBA" => "\xE3\x82\xBA", + "\xA5\xBB" => "\xE3\x82\xBB", + "\xA5\xBC" => "\xE3\x82\xBC", + "\xA5\xBD" => "\xE3\x82\xBD", + "\xA5\xBE" => "\xE3\x82\xBE", + "\xA5\xBF" => "\xE3\x82\xBF", + "\xA5\xC0" => "\xE3\x83\x80", + "\xA5\xC1" => "\xE3\x83\x81", + "\xA5\xC2" => "\xE3\x83\x82", + "\xA5\xC3" => "\xE3\x83\x83", + "\xA5\xC4" => "\xE3\x83\x84", + "\xA5\xC5" => "\xE3\x83\x85", + "\xA5\xC6" => "\xE3\x83\x86", + "\xA5\xC7" => "\xE3\x83\x87", + "\xA5\xC8" => "\xE3\x83\x88", + "\xA5\xC9" => "\xE3\x83\x89", + "\xA5\xCA" => "\xE3\x83\x8A", + "\xA5\xCB" => "\xE3\x83\x8B", + "\xA5\xCC" => "\xE3\x83\x8C", + "\xA5\xCD" => "\xE3\x83\x8D", + "\xA5\xCE" => "\xE3\x83\x8E", + "\xA5\xCF" => "\xE3\x83\x8F", + "\xA5\xD0" => "\xE3\x83\x90", + "\xA5\xD1" => "\xE3\x83\x91", + "\xA5\xD2" => "\xE3\x83\x92", + "\xA5\xD3" => "\xE3\x83\x93", + "\xA5\xD4" => "\xE3\x83\x94", + "\xA5\xD5" => "\xE3\x83\x95", + "\xA5\xD6" => "\xE3\x83\x96", + "\xA5\xD7" => "\xE3\x83\x97", + "\xA5\xD8" => "\xE3\x83\x98", + "\xA5\xD9" => "\xE3\x83\x99", + "\xA5\xDA" => "\xE3\x83\x9A", + "\xA5\xDB" => "\xE3\x83\x9B", + "\xA5\xDC" => "\xE3\x83\x9C", + "\xA5\xDD" => "\xE3\x83\x9D", + "\xA5\xDE" => "\xE3\x83\x9E", + "\xA5\xDF" => "\xE3\x83\x9F", + "\xA5\xE0" => "\xE3\x83\xA0", + "\xA5\xE1" => "\xE3\x83\xA1", + "\xA5\xE2" => "\xE3\x83\xA2", + "\xA5\xE3" => "\xE3\x83\xA3", + "\xA5\xE4" => "\xE3\x83\xA4", + "\xA5\xE5" => "\xE3\x83\xA5", + "\xA5\xE6" => "\xE3\x83\xA6", + "\xA5\xE7" => "\xE3\x83\xA7", + "\xA5\xE8" => "\xE3\x83\xA8", + "\xA5\xE9" => "\xE3\x83\xA9", + "\xA5\xEA" => "\xE3\x83\xAA", + "\xA5\xEB" => "\xE3\x83\xAB", + "\xA5\xEC" => "\xE3\x83\xAC", + "\xA5\xED" => "\xE3\x83\xAD", + "\xA5\xEE" => "\xE3\x83\xAE", + "\xA5\xEF" => "\xE3\x83\xAF", + "\xA5\xF0" => "\xE3\x83\xB0", + "\xA5\xF1" => "\xE3\x83\xB1", + "\xA5\xF2" => "\xE3\x83\xB2", + "\xA5\xF3" => "\xE3\x83\xB3", + "\xA5\xF4" => "\xE3\x83\xB4", + "\xA5\xF5" => "\xE3\x83\xB5", + "\xA5\xF6" => "\xE3\x83\xB6", + "\xA6\xA1" => "\xCE\x91", + "\xA6\xA2" => "\xCE\x92", + "\xA6\xA3" => "\xCE\x93", + "\xA6\xA4" => "\xCE\x94", + "\xA6\xA5" => "\xCE\x95", + "\xA6\xA6" => "\xCE\x96", + "\xA6\xA7" => "\xCE\x97", + "\xA6\xA8" => "\xCE\x98", + "\xA6\xA9" => "\xCE\x99", + "\xA6\xAA" => "\xCE\x9A", + "\xA6\xAB" => "\xCE\x9B", + "\xA6\xAC" => "\xCE\x9C", + "\xA6\xAD" => "\xCE\x9D", + "\xA6\xAE" => "\xCE\x9E", + "\xA6\xAF" => "\xCE\x9F", + "\xA6\xB0" => "\xCE\xA0", + "\xA6\xB1" => "\xCE\xA1", + "\xA6\xB2" => "\xCE\xA3", + "\xA6\xB3" => "\xCE\xA4", + "\xA6\xB4" => "\xCE\xA5", + "\xA6\xB5" => "\xCE\xA6", + "\xA6\xB6" => "\xCE\xA7", + "\xA6\xB7" => "\xCE\xA8", + "\xA6\xB8" => "\xCE\xA9", + "\xA6\xC1" => "\xCE\xB1", + "\xA6\xC2" => "\xCE\xB2", + "\xA6\xC3" => "\xCE\xB3", + "\xA6\xC4" => "\xCE\xB4", + "\xA6\xC5" => "\xCE\xB5", + "\xA6\xC6" => "\xCE\xB6", + "\xA6\xC7" => "\xCE\xB7", + "\xA6\xC8" => "\xCE\xB8", + "\xA6\xC9" => "\xCE\xB9", + "\xA6\xCA" => "\xCE\xBA", + "\xA6\xCB" => "\xCE\xBB", + "\xA6\xCC" => "\xCE\xBC", + "\xA6\xCD" => "\xCE\xBD", + "\xA6\xCE" => "\xCE\xBE", + "\xA6\xCF" => "\xCE\xBF", + "\xA6\xD0" => "\xCF\x80", + "\xA6\xD1" => "\xCF\x81", + "\xA6\xD2" => "\xCF\x83", + "\xA6\xD3" => "\xCF\x84", + "\xA6\xD4" => "\xCF\x85", + "\xA6\xD5" => "\xCF\x86", + "\xA6\xD6" => "\xCF\x87", + "\xA6\xD7" => "\xCF\x88", + "\xA6\xD8" => "\xCF\x89", + "\xA7\xA1" => "\xD0\x90", + "\xA7\xA2" => "\xD0\x91", + "\xA7\xA3" => "\xD0\x92", + "\xA7\xA4" => "\xD0\x93", + "\xA7\xA5" => "\xD0\x94", + "\xA7\xA6" => "\xD0\x95", + "\xA7\xA7" => "\xD0\x81", + "\xA7\xA8" => "\xD0\x96", + "\xA7\xA9" => "\xD0\x97", + "\xA7\xAA" => "\xD0\x98", + "\xA7\xAB" => "\xD0\x99", + "\xA7\xAC" => "\xD0\x9A", + "\xA7\xAD" => "\xD0\x9B", + "\xA7\xAE" => "\xD0\x9C", + "\xA7\xAF" => "\xD0\x9D", + "\xA7\xB0" => "\xD0\x9E", + "\xA7\xB1" => "\xD0\x9F", + "\xA7\xB2" => "\xD0\xA0", + "\xA7\xB3" => "\xD0\xA1", + "\xA7\xB4" => "\xD0\xA2", + "\xA7\xB5" => "\xD0\xA3", + "\xA7\xB6" => "\xD0\xA4", + "\xA7\xB7" => "\xD0\xA5", + "\xA7\xB8" => "\xD0\xA6", + "\xA7\xB9" => "\xD0\xA7", + "\xA7\xBA" => "\xD0\xA8", + "\xA7\xBB" => "\xD0\xA9", + "\xA7\xBC" => "\xD0\xAA", + "\xA7\xBD" => "\xD0\xAB", + "\xA7\xBE" => "\xD0\xAC", + "\xA7\xBF" => "\xD0\xAD", + "\xA7\xC0" => "\xD0\xAE", + "\xA7\xC1" => "\xD0\xAF", + "\xA7\xD1" => "\xD0\xB0", + "\xA7\xD2" => "\xD0\xB1", + "\xA7\xD3" => "\xD0\xB2", + "\xA7\xD4" => "\xD0\xB3", + "\xA7\xD5" => "\xD0\xB4", + "\xA7\xD6" => "\xD0\xB5", + "\xA7\xD7" => "\xD1\x91", + "\xA7\xD8" => "\xD0\xB6", + "\xA7\xD9" => "\xD0\xB7", + "\xA7\xDA" => "\xD0\xB8", + "\xA7\xDB" => "\xD0\xB9", + "\xA7\xDC" => "\xD0\xBA", + "\xA7\xDD" => "\xD0\xBB", + "\xA7\xDE" => "\xD0\xBC", + "\xA7\xDF" => "\xD0\xBD", + "\xA7\xE0" => "\xD0\xBE", + "\xA7\xE1" => "\xD0\xBF", + "\xA7\xE2" => "\xD1\x80", + "\xA7\xE3" => "\xD1\x81", + "\xA7\xE4" => "\xD1\x82", + "\xA7\xE5" => "\xD1\x83", + "\xA7\xE6" => "\xD1\x84", + "\xA7\xE7" => "\xD1\x85", + "\xA7\xE8" => "\xD1\x86", + "\xA7\xE9" => "\xD1\x87", + "\xA7\xEA" => "\xD1\x88", + "\xA7\xEB" => "\xD1\x89", + "\xA7\xEC" => "\xD1\x8A", + "\xA7\xED" => "\xD1\x8B", + "\xA7\xEE" => "\xD1\x8C", + "\xA7\xEF" => "\xD1\x8D", + "\xA7\xF0" => "\xD1\x8E", + "\xA7\xF1" => "\xD1\x8F", + "\xA8\xA1" => "\xC4\x81", + "\xA8\xA2" => "\xC3\xA1", + "\xA8\xA3" => "\xC7\x8E", + "\xA8\xA4" => "\xC3\xA0", + "\xA8\xA5" => "\xC4\x93", + "\xA8\xA6" => "\xC3\xA9", + "\xA8\xA7" => "\xC4\x9B", + "\xA8\xA8" => "\xC3\xA8", + "\xA8\xA9" => "\xC4\xAB", + "\xA8\xAA" => "\xC3\xAD", + "\xA8\xAB" => "\xC7\x90", + "\xA8\xAC" => "\xC3\xAC", + "\xA8\xAD" => "\xC5\x8D", + "\xA8\xAE" => "\xC3\xB3", + "\xA8\xAF" => "\xC7\x92", + "\xA8\xB0" => "\xC3\xB2", + "\xA8\xB1" => "\xC5\xAB", + "\xA8\xB2" => "\xC3\xBA", + "\xA8\xB3" => "\xC7\x94", + "\xA8\xB4" => "\xC3\xB9", + "\xA8\xB5" => "\xC7\x96", + "\xA8\xB6" => "\xC7\x98", + "\xA8\xB7" => "\xC7\x9A", + "\xA8\xB8" => "\xC7\x9C", + "\xA8\xB9" => "\xC3\xBC", + "\xA8\xBA" => "\xC3\xAA", + "\xA8\xC5" => "\xE3\x84\x85", + "\xA8\xC6" => "\xE3\x84\x86", + "\xA8\xC7" => "\xE3\x84\x87", + "\xA8\xC8" => "\xE3\x84\x88", + "\xA8\xC9" => "\xE3\x84\x89", + "\xA8\xCA" => "\xE3\x84\x8A", + "\xA8\xCB" => "\xE3\x84\x8B", + "\xA8\xCC" => "\xE3\x84\x8C", + "\xA8\xCD" => "\xE3\x84\x8D", + "\xA8\xCE" => "\xE3\x84\x8E", + "\xA8\xCF" => "\xE3\x84\x8F", + "\xA8\xD0" => "\xE3\x84\x90", + "\xA8\xD1" => "\xE3\x84\x91", + "\xA8\xD2" => "\xE3\x84\x92", + "\xA8\xD3" => "\xE3\x84\x93", + "\xA8\xD4" => "\xE3\x84\x94", + "\xA8\xD5" => "\xE3\x84\x95", + "\xA8\xD6" => "\xE3\x84\x96", + "\xA8\xD7" => "\xE3\x84\x97", + "\xA8\xD8" => "\xE3\x84\x98", + "\xA8\xD9" => "\xE3\x84\x99", + "\xA8\xDA" => "\xE3\x84\x9A", + "\xA8\xDB" => "\xE3\x84\x9B", + "\xA8\xDC" => "\xE3\x84\x9C", + "\xA8\xDD" => "\xE3\x84\x9D", + "\xA8\xDE" => "\xE3\x84\x9E", + "\xA8\xDF" => "\xE3\x84\x9F", + "\xA8\xE0" => "\xE3\x84\xA0", + "\xA8\xE1" => "\xE3\x84\xA1", + "\xA8\xE2" => "\xE3\x84\xA2", + "\xA8\xE3" => "\xE3\x84\xA3", + "\xA8\xE4" => "\xE3\x84\xA4", + "\xA8\xE5" => "\xE3\x84\xA5", + "\xA8\xE6" => "\xE3\x84\xA6", + "\xA8\xE7" => "\xE3\x84\xA7", + "\xA8\xE8" => "\xE3\x84\xA8", + "\xA8\xE9" => "\xE3\x84\xA9", + "\xA9\xA4" => "\xE2\x94\x80", + "\xA9\xA5" => "\xE2\x94\x81", + "\xA9\xA6" => "\xE2\x94\x82", + "\xA9\xA7" => "\xE2\x94\x83", + "\xA9\xA8" => "\xE2\x94\x84", + "\xA9\xA9" => "\xE2\x94\x85", + "\xA9\xAA" => "\xE2\x94\x86", + "\xA9\xAB" => "\xE2\x94\x87", + "\xA9\xAC" => "\xE2\x94\x88", + "\xA9\xAD" => "\xE2\x94\x89", + "\xA9\xAE" => "\xE2\x94\x8A", + "\xA9\xAF" => "\xE2\x94\x8B", + "\xA9\xB0" => "\xE2\x94\x8C", + "\xA9\xB1" => "\xE2\x94\x8D", + "\xA9\xB2" => "\xE2\x94\x8E", + "\xA9\xB3" => "\xE2\x94\x8F", + "\xA9\xB4" => "\xE2\x94\x90", + "\xA9\xB5" => "\xE2\x94\x91", + "\xA9\xB6" => "\xE2\x94\x92", + "\xA9\xB7" => "\xE2\x94\x93", + "\xA9\xB8" => "\xE2\x94\x94", + "\xA9\xB9" => "\xE2\x94\x95", + "\xA9\xBA" => "\xE2\x94\x96", + "\xA9\xBB" => "\xE2\x94\x97", + "\xA9\xBC" => "\xE2\x94\x98", + "\xA9\xBD" => "\xE2\x94\x99", + "\xA9\xBE" => "\xE2\x94\x9A", + "\xA9\xBF" => "\xE2\x94\x9B", + "\xA9\xC0" => "\xE2\x94\x9C", + "\xA9\xC1" => "\xE2\x94\x9D", + "\xA9\xC2" => "\xE2\x94\x9E", + "\xA9\xC3" => "\xE2\x94\x9F", + "\xA9\xC4" => "\xE2\x94\xA0", + "\xA9\xC5" => "\xE2\x94\xA1", + "\xA9\xC6" => "\xE2\x94\xA2", + "\xA9\xC7" => "\xE2\x94\xA3", + "\xA9\xC8" => "\xE2\x94\xA4", + "\xA9\xC9" => "\xE2\x94\xA5", + "\xA9\xCA" => "\xE2\x94\xA6", + "\xA9\xCB" => "\xE2\x94\xA7", + "\xA9\xCC" => "\xE2\x94\xA8", + "\xA9\xCD" => "\xE2\x94\xA9", + "\xA9\xCE" => "\xE2\x94\xAA", + "\xA9\xCF" => "\xE2\x94\xAB", + "\xA9\xD0" => "\xE2\x94\xAC", + "\xA9\xD1" => "\xE2\x94\xAD", + "\xA9\xD2" => "\xE2\x94\xAE", + "\xA9\xD3" => "\xE2\x94\xAF", + "\xA9\xD4" => "\xE2\x94\xB0", + "\xA9\xD5" => "\xE2\x94\xB1", + "\xA9\xD6" => "\xE2\x94\xB2", + "\xA9\xD7" => "\xE2\x94\xB3", + "\xA9\xD8" => "\xE2\x94\xB4", + "\xA9\xD9" => "\xE2\x94\xB5", + "\xA9\xDA" => "\xE2\x94\xB6", + "\xA9\xDB" => "\xE2\x94\xB7", + "\xA9\xDC" => "\xE2\x94\xB8", + "\xA9\xDD" => "\xE2\x94\xB9", + "\xA9\xDE" => "\xE2\x94\xBA", + "\xA9\xDF" => "\xE2\x94\xBB", + "\xA9\xE0" => "\xE2\x94\xBC", + "\xA9\xE1" => "\xE2\x94\xBD", + "\xA9\xE2" => "\xE2\x94\xBE", + "\xA9\xE3" => "\xE2\x94\xBF", + "\xA9\xE4" => "\xE2\x95\x80", + "\xA9\xE5" => "\xE2\x95\x81", + "\xA9\xE6" => "\xE2\x95\x82", + "\xA9\xE7" => "\xE2\x95\x83", + "\xA9\xE8" => "\xE2\x95\x84", + "\xA9\xE9" => "\xE2\x95\x85", + "\xA9\xEA" => "\xE2\x95\x86", + "\xA9\xEB" => "\xE2\x95\x87", + "\xA9\xEC" => "\xE2\x95\x88", + "\xA9\xED" => "\xE2\x95\x89", + "\xA9\xEE" => "\xE2\x95\x8A", + "\xA9\xEF" => "\xE2\x95\x8B", + "\xB0\xA1" => "\xE5\x95\x8A", + "\xB0\xA2" => "\xE9\x98\xBF", + "\xB0\xA3" => "\xE5\x9F\x83", + "\xB0\xA4" => "\xE6\x8C\xA8", + "\xB0\xA5" => "\xE5\x93\x8E", + "\xB0\xA6" => "\xE5\x94\x89", + "\xB0\xA7" => "\xE5\x93\x80", + "\xB0\xA8" => "\xE7\x9A\x91", + "\xB0\xA9" => "\xE7\x99\x8C", + "\xB0\xAA" => "\xE8\x94\xBC", + "\xB0\xAB" => "\xE7\x9F\xAE", + "\xB0\xAC" => "\xE8\x89\xBE", + "\xB0\xAD" => "\xE7\xA2\x8D", + "\xB0\xAE" => "\xE7\x88\xB1", + "\xB0\xAF" => "\xE9\x9A\x98", + "\xB0\xB0" => "\xE9\x9E\x8D", + "\xB0\xB1" => "\xE6\xB0\xA8", + "\xB0\xB2" => "\xE5\xAE\x89", + "\xB0\xB3" => "\xE4\xBF\xBA", + "\xB0\xB4" => "\xE6\x8C\x89", + "\xB0\xB5" => "\xE6\x9A\x97", + "\xB0\xB6" => "\xE5\xB2\xB8", + "\xB0\xB7" => "\xE8\x83\xBA", + "\xB0\xB8" => "\xE6\xA1\x88", + "\xB0\xB9" => "\xE8\x82\xAE", + "\xB0\xBA" => "\xE6\x98\x82", + "\xB0\xBB" => "\xE7\x9B\x8E", + "\xB0\xBC" => "\xE5\x87\xB9", + "\xB0\xBD" => "\xE6\x95\x96", + "\xB0\xBE" => "\xE7\x86\xAC", + "\xB0\xBF" => "\xE7\xBF\xB1", + "\xB0\xC0" => "\xE8\xA2\x84", + "\xB0\xC1" => "\xE5\x82\xB2", + "\xB0\xC2" => "\xE5\xA5\xA5", + "\xB0\xC3" => "\xE6\x87\x8A", + "\xB0\xC4" => "\xE6\xBE\xB3", + "\xB0\xC5" => "\xE8\x8A\xAD", + "\xB0\xC6" => "\xE6\x8D\x8C", + "\xB0\xC7" => "\xE6\x89\x92", + "\xB0\xC8" => "\xE5\x8F\xAD", + "\xB0\xC9" => "\xE5\x90\xA7", + "\xB0\xCA" => "\xE7\xAC\x86", + "\xB0\xCB" => "\xE5\x85\xAB", + "\xB0\xCC" => "\xE7\x96\xA4", + "\xB0\xCD" => "\xE5\xB7\xB4", + "\xB0\xCE" => "\xE6\x8B\x94", + "\xB0\xCF" => "\xE8\xB7\x8B", + "\xB0\xD0" => "\xE9\x9D\xB6", + "\xB0\xD1" => "\xE6\x8A\x8A", + "\xB0\xD2" => "\xE8\x80\x99", + "\xB0\xD3" => "\xE5\x9D\x9D", + "\xB0\xD4" => "\xE9\x9C\xB8", + "\xB0\xD5" => "\xE7\xBD\xA2", + "\xB0\xD6" => "\xE7\x88\xB8", + "\xB0\xD7" => "\xE7\x99\xBD", + "\xB0\xD8" => "\xE6\x9F\x8F", + "\xB0\xD9" => "\xE7\x99\xBE", + "\xB0\xDA" => "\xE6\x91\x86", + "\xB0\xDB" => "\xE4\xBD\xB0", + "\xB0\xDC" => "\xE8\xB4\xA5", + "\xB0\xDD" => "\xE6\x8B\x9C", + "\xB0\xDE" => "\xE7\xA8\x97", + "\xB0\xDF" => "\xE6\x96\x91", + "\xB0\xE0" => "\xE7\x8F\xAD", + "\xB0\xE1" => "\xE6\x90\xAC", + "\xB0\xE2" => "\xE6\x89\xB3", + "\xB0\xE3" => "\xE8\x88\xAC", + "\xB0\xE4" => "\xE9\xA2\x81", + "\xB0\xE5" => "\xE6\x9D\xBF", + "\xB0\xE6" => "\xE7\x89\x88", + "\xB0\xE7" => "\xE6\x89\xAE", + "\xB0\xE8" => "\xE6\x8B\x8C", + "\xB0\xE9" => "\xE4\xBC\xB4", + "\xB0\xEA" => "\xE7\x93\xA3", + "\xB0\xEB" => "\xE5\x8D\x8A", + "\xB0\xEC" => "\xE5\x8A\x9E", + "\xB0\xED" => "\xE7\xBB\x8A", + "\xB0\xEE" => "\xE9\x82\xA6", + "\xB0\xEF" => "\xE5\xB8\xAE", + "\xB0\xF0" => "\xE6\xA2\x86", + "\xB0\xF1" => "\xE6\xA6\x9C", + "\xB0\xF2" => "\xE8\x86\x80", + "\xB0\xF3" => "\xE7\xBB\x91", + "\xB0\xF4" => "\xE6\xA3\x92", + "\xB0\xF5" => "\xE7\xA3\x85", + "\xB0\xF6" => "\xE8\x9A\x8C", + "\xB0\xF7" => "\xE9\x95\x91", + "\xB0\xF8" => "\xE5\x82\x8D", + "\xB0\xF9" => "\xE8\xB0\xA4", + "\xB0\xFA" => "\xE8\x8B\x9E", + "\xB0\xFB" => "\xE8\x83\x9E", + "\xB0\xFC" => "\xE5\x8C\x85", + "\xB0\xFD" => "\xE8\xA4\x92", + "\xB0\xFE" => "\xE5\x89\xA5", + "\xB1\xA1" => "\xE8\x96\x84", + "\xB1\xA2" => "\xE9\x9B\xB9", + "\xB1\xA3" => "\xE4\xBF\x9D", + "\xB1\xA4" => "\xE5\xA0\xA1", + "\xB1\xA5" => "\xE9\xA5\xB1", + "\xB1\xA6" => "\xE5\xAE\x9D", + "\xB1\xA7" => "\xE6\x8A\xB1", + "\xB1\xA8" => "\xE6\x8A\xA5", + "\xB1\xA9" => "\xE6\x9A\xB4", + "\xB1\xAA" => "\xE8\xB1\xB9", + "\xB1\xAB" => "\xE9\xB2\x8D", + "\xB1\xAC" => "\xE7\x88\x86", + "\xB1\xAD" => "\xE6\x9D\xAF", + "\xB1\xAE" => "\xE7\xA2\x91", + "\xB1\xAF" => "\xE6\x82\xB2", + "\xB1\xB0" => "\xE5\x8D\x91", + "\xB1\xB1" => "\xE5\x8C\x97", + "\xB1\xB2" => "\xE8\xBE\x88", + "\xB1\xB3" => "\xE8\x83\x8C", + "\xB1\xB4" => "\xE8\xB4\x9D", + "\xB1\xB5" => "\xE9\x92\xA1", + "\xB1\xB6" => "\xE5\x80\x8D", + "\xB1\xB7" => "\xE7\x8B\x88", + "\xB1\xB8" => "\xE5\xA4\x87", + "\xB1\xB9" => "\xE6\x83\xAB", + "\xB1\xBA" => "\xE7\x84\x99", + "\xB1\xBB" => "\xE8\xA2\xAB", + "\xB1\xBC" => "\xE5\xA5\x94", + "\xB1\xBD" => "\xE8\x8B\xAF", + "\xB1\xBE" => "\xE6\x9C\xAC", + "\xB1\xBF" => "\xE7\xAC\xA8", + "\xB1\xC0" => "\xE5\xB4\xA9", + "\xB1\xC1" => "\xE7\xBB\xB7", + "\xB1\xC2" => "\xE7\x94\xAD", + "\xB1\xC3" => "\xE6\xB3\xB5", + "\xB1\xC4" => "\xE8\xB9\xA6", + "\xB1\xC5" => "\xE8\xBF\xB8", + "\xB1\xC6" => "\xE9\x80\xBC", + "\xB1\xC7" => "\xE9\xBC\xBB", + "\xB1\xC8" => "\xE6\xAF\x94", + "\xB1\xC9" => "\xE9\x84\x99", + "\xB1\xCA" => "\xE7\xAC\x94", + "\xB1\xCB" => "\xE5\xBD\xBC", + "\xB1\xCC" => "\xE7\xA2\xA7", + "\xB1\xCD" => "\xE8\x93\x96", + "\xB1\xCE" => "\xE8\x94\xBD", + "\xB1\xCF" => "\xE6\xAF\x95", + "\xB1\xD0" => "\xE6\xAF\x99", + "\xB1\xD1" => "\xE6\xAF\x96", + "\xB1\xD2" => "\xE5\xB8\x81", + "\xB1\xD3" => "\xE5\xBA\x87", + "\xB1\xD4" => "\xE7\x97\xB9", + "\xB1\xD5" => "\xE9\x97\xAD", + "\xB1\xD6" => "\xE6\x95\x9D", + "\xB1\xD7" => "\xE5\xBC\x8A", + "\xB1\xD8" => "\xE5\xBF\x85", + "\xB1\xD9" => "\xE8\xBE\x9F", + "\xB1\xDA" => "\xE5\xA3\x81", + "\xB1\xDB" => "\xE8\x87\x82", + "\xB1\xDC" => "\xE9\x81\xBF", + "\xB1\xDD" => "\xE9\x99\x9B", + "\xB1\xDE" => "\xE9\x9E\xAD", + "\xB1\xDF" => "\xE8\xBE\xB9", + "\xB1\xE0" => "\xE7\xBC\x96", + "\xB1\xE1" => "\xE8\xB4\xAC", + "\xB1\xE2" => "\xE6\x89\x81", + "\xB1\xE3" => "\xE4\xBE\xBF", + "\xB1\xE4" => "\xE5\x8F\x98", + "\xB1\xE5" => "\xE5\x8D\x9E", + "\xB1\xE6" => "\xE8\xBE\xA8", + "\xB1\xE7" => "\xE8\xBE\xA9", + "\xB1\xE8" => "\xE8\xBE\xAB", + "\xB1\xE9" => "\xE9\x81\x8D", + "\xB1\xEA" => "\xE6\xA0\x87", + "\xB1\xEB" => "\xE5\xBD\xAA", + "\xB1\xEC" => "\xE8\x86\x98", + "\xB1\xED" => "\xE8\xA1\xA8", + "\xB1\xEE" => "\xE9\xB3\x96", + "\xB1\xEF" => "\xE6\x86\x8B", + "\xB1\xF0" => "\xE5\x88\xAB", + "\xB1\xF1" => "\xE7\x98\xAA", + "\xB1\xF2" => "\xE5\xBD\xAC", + "\xB1\xF3" => "\xE6\x96\x8C", + "\xB1\xF4" => "\xE6\xBF\x92", + "\xB1\xF5" => "\xE6\xBB\xA8", + "\xB1\xF6" => "\xE5\xAE\xBE", + "\xB1\xF7" => "\xE6\x91\x88", + "\xB1\xF8" => "\xE5\x85\xB5", + "\xB1\xF9" => "\xE5\x86\xB0", + "\xB1\xFA" => "\xE6\x9F\x84", + "\xB1\xFB" => "\xE4\xB8\x99", + "\xB1\xFC" => "\xE7\xA7\x89", + "\xB1\xFD" => "\xE9\xA5\xBC", + "\xB1\xFE" => "\xE7\x82\xB3", + "\xB2\xA1" => "\xE7\x97\x85", + "\xB2\xA2" => "\xE5\xB9\xB6", + "\xB2\xA3" => "\xE7\x8E\xBB", + "\xB2\xA4" => "\xE8\x8F\xA0", + "\xB2\xA5" => "\xE6\x92\xAD", + "\xB2\xA6" => "\xE6\x8B\xA8", + "\xB2\xA7" => "\xE9\x92\xB5", + "\xB2\xA8" => "\xE6\xB3\xA2", + "\xB2\xA9" => "\xE5\x8D\x9A", + "\xB2\xAA" => "\xE5\x8B\x83", + "\xB2\xAB" => "\xE6\x90\x8F", + "\xB2\xAC" => "\xE9\x93\x82", + "\xB2\xAD" => "\xE7\xAE\x94", + "\xB2\xAE" => "\xE4\xBC\xAF", + "\xB2\xAF" => "\xE5\xB8\x9B", + "\xB2\xB0" => "\xE8\x88\xB6", + "\xB2\xB1" => "\xE8\x84\x96", + "\xB2\xB2" => "\xE8\x86\x8A", + "\xB2\xB3" => "\xE6\xB8\xA4", + "\xB2\xB4" => "\xE6\xB3\x8A", + "\xB2\xB5" => "\xE9\xA9\xB3", + "\xB2\xB6" => "\xE6\x8D\x95", + "\xB2\xB7" => "\xE5\x8D\x9C", + "\xB2\xB8" => "\xE5\x93\xBA", + "\xB2\xB9" => "\xE8\xA1\xA5", + "\xB2\xBA" => "\xE5\x9F\xA0", + "\xB2\xBB" => "\xE4\xB8\x8D", + "\xB2\xBC" => "\xE5\xB8\x83", + "\xB2\xBD" => "\xE6\xAD\xA5", + "\xB2\xBE" => "\xE7\xB0\xBF", + "\xB2\xBF" => "\xE9\x83\xA8", + "\xB2\xC0" => "\xE6\x80\x96", + "\xB2\xC1" => "\xE6\x93\xA6", + "\xB2\xC2" => "\xE7\x8C\x9C", + "\xB2\xC3" => "\xE8\xA3\x81", + "\xB2\xC4" => "\xE6\x9D\x90", + "\xB2\xC5" => "\xE6\x89\x8D", + "\xB2\xC6" => "\xE8\xB4\xA2", + "\xB2\xC7" => "\xE7\x9D\xAC", + "\xB2\xC8" => "\xE8\xB8\xA9", + "\xB2\xC9" => "\xE9\x87\x87", + "\xB2\xCA" => "\xE5\xBD\xA9", + "\xB2\xCB" => "\xE8\x8F\x9C", + "\xB2\xCC" => "\xE8\x94\xA1", + "\xB2\xCD" => "\xE9\xA4\x90", + "\xB2\xCE" => "\xE5\x8F\x82", + "\xB2\xCF" => "\xE8\x9A\x95", + "\xB2\xD0" => "\xE6\xAE\x8B", + "\xB2\xD1" => "\xE6\x83\xAD", + "\xB2\xD2" => "\xE6\x83\xA8", + "\xB2\xD3" => "\xE7\x81\xBF", + "\xB2\xD4" => "\xE8\x8B\x8D", + "\xB2\xD5" => "\xE8\x88\xB1", + "\xB2\xD6" => "\xE4\xBB\x93", + "\xB2\xD7" => "\xE6\xB2\xA7", + "\xB2\xD8" => "\xE8\x97\x8F", + "\xB2\xD9" => "\xE6\x93\x8D", + "\xB2\xDA" => "\xE7\xB3\x99", + "\xB2\xDB" => "\xE6\xA7\xBD", + "\xB2\xDC" => "\xE6\x9B\xB9", + "\xB2\xDD" => "\xE8\x8D\x89", + "\xB2\xDE" => "\xE5\x8E\x95", + "\xB2\xDF" => "\xE7\xAD\x96", + "\xB2\xE0" => "\xE4\xBE\xA7", + "\xB2\xE1" => "\xE5\x86\x8C", + "\xB2\xE2" => "\xE6\xB5\x8B", + "\xB2\xE3" => "\xE5\xB1\x82", + "\xB2\xE4" => "\xE8\xB9\xAD", + "\xB2\xE5" => "\xE6\x8F\x92", + "\xB2\xE6" => "\xE5\x8F\x89", + "\xB2\xE7" => "\xE8\x8C\xAC", + "\xB2\xE8" => "\xE8\x8C\xB6", + "\xB2\xE9" => "\xE6\x9F\xA5", + "\xB2\xEA" => "\xE7\xA2\xB4", + "\xB2\xEB" => "\xE6\x90\xBD", + "\xB2\xEC" => "\xE5\xAF\x9F", + "\xB2\xED" => "\xE5\xB2\x94", + "\xB2\xEE" => "\xE5\xB7\xAE", + "\xB2\xEF" => "\xE8\xAF\xA7", + "\xB2\xF0" => "\xE6\x8B\x86", + "\xB2\xF1" => "\xE6\x9F\xB4", + "\xB2\xF2" => "\xE8\xB1\xBA", + "\xB2\xF3" => "\xE6\x90\x80", + "\xB2\xF4" => "\xE6\x8E\xBA", + "\xB2\xF5" => "\xE8\x9D\x89", + "\xB2\xF6" => "\xE9\xA6\x8B", + "\xB2\xF7" => "\xE8\xB0\x97", + "\xB2\xF8" => "\xE7\xBC\xA0", + "\xB2\xF9" => "\xE9\x93\xB2", + "\xB2\xFA" => "\xE4\xBA\xA7", + "\xB2\xFB" => "\xE9\x98\x90", + "\xB2\xFC" => "\xE9\xA2\xA4", + "\xB2\xFD" => "\xE6\x98\x8C", + "\xB2\xFE" => "\xE7\x8C\x96", + "\xB3\xA1" => "\xE5\x9C\xBA", + "\xB3\xA2" => "\xE5\xB0\x9D", + "\xB3\xA3" => "\xE5\xB8\xB8", + "\xB3\xA4" => "\xE9\x95\xBF", + "\xB3\xA5" => "\xE5\x81\xBF", + "\xB3\xA6" => "\xE8\x82\xA0", + "\xB3\xA7" => "\xE5\x8E\x82", + "\xB3\xA8" => "\xE6\x95\x9E", + "\xB3\xA9" => "\xE7\x95\x85", + "\xB3\xAA" => "\xE5\x94\xB1", + "\xB3\xAB" => "\xE5\x80\xA1", + "\xB3\xAC" => "\xE8\xB6\x85", + "\xB3\xAD" => "\xE6\x8A\x84", + "\xB3\xAE" => "\xE9\x92\x9E", + "\xB3\xAF" => "\xE6\x9C\x9D", + "\xB3\xB0" => "\xE5\x98\xB2", + "\xB3\xB1" => "\xE6\xBD\xAE", + "\xB3\xB2" => "\xE5\xB7\xA2", + "\xB3\xB3" => "\xE5\x90\xB5", + "\xB3\xB4" => "\xE7\x82\x92", + "\xB3\xB5" => "\xE8\xBD\xA6", + "\xB3\xB6" => "\xE6\x89\xAF", + "\xB3\xB7" => "\xE6\x92\xA4", + "\xB3\xB8" => "\xE6\x8E\xA3", + "\xB3\xB9" => "\xE5\xBD\xBB", + "\xB3\xBA" => "\xE6\xBE\x88", + "\xB3\xBB" => "\xE9\x83\xB4", + "\xB3\xBC" => "\xE8\x87\xA3", + "\xB3\xBD" => "\xE8\xBE\xB0", + "\xB3\xBE" => "\xE5\xB0\x98", + "\xB3\xBF" => "\xE6\x99\xA8", + "\xB3\xC0" => "\xE5\xBF\xB1", + "\xB3\xC1" => "\xE6\xB2\x89", + "\xB3\xC2" => "\xE9\x99\x88", + "\xB3\xC3" => "\xE8\xB6\x81", + "\xB3\xC4" => "\xE8\xA1\xAC", + "\xB3\xC5" => "\xE6\x92\x91", + "\xB3\xC6" => "\xE7\xA7\xB0", + "\xB3\xC7" => "\xE5\x9F\x8E", + "\xB3\xC8" => "\xE6\xA9\x99", + "\xB3\xC9" => "\xE6\x88\x90", + "\xB3\xCA" => "\xE5\x91\x88", + "\xB3\xCB" => "\xE4\xB9\x98", + "\xB3\xCC" => "\xE7\xA8\x8B", + "\xB3\xCD" => "\xE6\x83\xA9", + "\xB3\xCE" => "\xE6\xBE\x84", + "\xB3\xCF" => "\xE8\xAF\x9A", + "\xB3\xD0" => "\xE6\x89\xBF", + "\xB3\xD1" => "\xE9\x80\x9E", + "\xB3\xD2" => "\xE9\xAA\x8B", + "\xB3\xD3" => "\xE7\xA7\xA4", + "\xB3\xD4" => "\xE5\x90\x83", + "\xB3\xD5" => "\xE7\x97\xB4", + "\xB3\xD6" => "\xE6\x8C\x81", + "\xB3\xD7" => "\xE5\x8C\x99", + "\xB3\xD8" => "\xE6\xB1\xA0", + "\xB3\xD9" => "\xE8\xBF\x9F", + "\xB3\xDA" => "\xE5\xBC\x9B", + "\xB3\xDB" => "\xE9\xA9\xB0", + "\xB3\xDC" => "\xE8\x80\xBB", + "\xB3\xDD" => "\xE9\xBD\xBF", + "\xB3\xDE" => "\xE4\xBE\x88", + "\xB3\xDF" => "\xE5\xB0\xBA", + "\xB3\xE0" => "\xE8\xB5\xA4", + "\xB3\xE1" => "\xE7\xBF\x85", + "\xB3\xE2" => "\xE6\x96\xA5", + "\xB3\xE3" => "\xE7\x82\xBD", + "\xB3\xE4" => "\xE5\x85\x85", + "\xB3\xE5" => "\xE5\x86\xB2", + "\xB3\xE6" => "\xE8\x99\xAB", + "\xB3\xE7" => "\xE5\xB4\x87", + "\xB3\xE8" => "\xE5\xAE\xA0", + "\xB3\xE9" => "\xE6\x8A\xBD", + "\xB3\xEA" => "\xE9\x85\xAC", + "\xB3\xEB" => "\xE7\x95\xB4", + "\xB3\xEC" => "\xE8\xB8\x8C", + "\xB3\xED" => "\xE7\xA8\xA0", + "\xB3\xEE" => "\xE6\x84\x81", + "\xB3\xEF" => "\xE7\xAD\xB9", + "\xB3\xF0" => "\xE4\xBB\x87", + "\xB3\xF1" => "\xE7\xBB\xB8", + "\xB3\xF2" => "\xE7\x9E\x85", + "\xB3\xF3" => "\xE4\xB8\x91", + "\xB3\xF4" => "\xE8\x87\xAD", + "\xB3\xF5" => "\xE5\x88\x9D", + "\xB3\xF6" => "\xE5\x87\xBA", + "\xB3\xF7" => "\xE6\xA9\xB1", + "\xB3\xF8" => "\xE5\x8E\xA8", + "\xB3\xF9" => "\xE8\xBA\x87", + "\xB3\xFA" => "\xE9\x94\x84", + "\xB3\xFB" => "\xE9\x9B\x8F", + "\xB3\xFC" => "\xE6\xBB\x81", + "\xB3\xFD" => "\xE9\x99\xA4", + "\xB3\xFE" => "\xE6\xA5\x9A", + "\xB4\xA1" => "\xE7\xA1\x80", + "\xB4\xA2" => "\xE5\x82\xA8", + "\xB4\xA3" => "\xE7\x9F\x97", + "\xB4\xA4" => "\xE6\x90\x90", + "\xB4\xA5" => "\xE8\xA7\xA6", + "\xB4\xA6" => "\xE5\xA4\x84", + "\xB4\xA7" => "\xE6\x8F\xA3", + "\xB4\xA8" => "\xE5\xB7\x9D", + "\xB4\xA9" => "\xE7\xA9\xBF", + "\xB4\xAA" => "\xE6\xA4\xBD", + "\xB4\xAB" => "\xE4\xBC\xA0", + "\xB4\xAC" => "\xE8\x88\xB9", + "\xB4\xAD" => "\xE5\x96\x98", + "\xB4\xAE" => "\xE4\xB8\xB2", + "\xB4\xAF" => "\xE7\x96\xAE", + "\xB4\xB0" => "\xE7\xAA\x97", + "\xB4\xB1" => "\xE5\xB9\xA2", + "\xB4\xB2" => "\xE5\xBA\x8A", + "\xB4\xB3" => "\xE9\x97\xAF", + "\xB4\xB4" => "\xE5\x88\x9B", + "\xB4\xB5" => "\xE5\x90\xB9", + "\xB4\xB6" => "\xE7\x82\x8A", + "\xB4\xB7" => "\xE6\x8D\xB6", + "\xB4\xB8" => "\xE9\x94\xA4", + "\xB4\xB9" => "\xE5\x9E\x82", + "\xB4\xBA" => "\xE6\x98\xA5", + "\xB4\xBB" => "\xE6\xA4\xBF", + "\xB4\xBC" => "\xE9\x86\x87", + "\xB4\xBD" => "\xE5\x94\x87", + "\xB4\xBE" => "\xE6\xB7\xB3", + "\xB4\xBF" => "\xE7\xBA\xAF", + "\xB4\xC0" => "\xE8\xA0\xA2", + "\xB4\xC1" => "\xE6\x88\xB3", + "\xB4\xC2" => "\xE7\xBB\xB0", + "\xB4\xC3" => "\xE7\x96\xB5", + "\xB4\xC4" => "\xE8\x8C\xA8", + "\xB4\xC5" => "\xE7\xA3\x81", + "\xB4\xC6" => "\xE9\x9B\x8C", + "\xB4\xC7" => "\xE8\xBE\x9E", + "\xB4\xC8" => "\xE6\x85\x88", + "\xB4\xC9" => "\xE7\x93\xB7", + "\xB4\xCA" => "\xE8\xAF\x8D", + "\xB4\xCB" => "\xE6\xAD\xA4", + "\xB4\xCC" => "\xE5\x88\xBA", + "\xB4\xCD" => "\xE8\xB5\x90", + "\xB4\xCE" => "\xE6\xAC\xA1", + "\xB4\xCF" => "\xE8\x81\xAA", + "\xB4\xD0" => "\xE8\x91\xB1", + "\xB4\xD1" => "\xE5\x9B\xB1", + "\xB4\xD2" => "\xE5\x8C\x86", + "\xB4\xD3" => "\xE4\xBB\x8E", + "\xB4\xD4" => "\xE4\xB8\x9B", + "\xB4\xD5" => "\xE5\x87\x91", + "\xB4\xD6" => "\xE7\xB2\x97", + "\xB4\xD7" => "\xE9\x86\x8B", + "\xB4\xD8" => "\xE7\xB0\x87", + "\xB4\xD9" => "\xE4\xBF\x83", + "\xB4\xDA" => "\xE8\xB9\xBF", + "\xB4\xDB" => "\xE7\xAF\xA1", + "\xB4\xDC" => "\xE7\xAA\x9C", + "\xB4\xDD" => "\xE6\x91\xA7", + "\xB4\xDE" => "\xE5\xB4\x94", + "\xB4\xDF" => "\xE5\x82\xAC", + "\xB4\xE0" => "\xE8\x84\x86", + "\xB4\xE1" => "\xE7\x98\x81", + "\xB4\xE2" => "\xE7\xB2\xB9", + "\xB4\xE3" => "\xE6\xB7\xAC", + "\xB4\xE4" => "\xE7\xBF\xA0", + "\xB4\xE5" => "\xE6\x9D\x91", + "\xB4\xE6" => "\xE5\xAD\x98", + "\xB4\xE7" => "\xE5\xAF\xB8", + "\xB4\xE8" => "\xE7\xA3\x8B", + "\xB4\xE9" => "\xE6\x92\xAE", + "\xB4\xEA" => "\xE6\x90\x93", + "\xB4\xEB" => "\xE6\x8E\xAA", + "\xB4\xEC" => "\xE6\x8C\xAB", + "\xB4\xED" => "\xE9\x94\x99", + "\xB4\xEE" => "\xE6\x90\xAD", + "\xB4\xEF" => "\xE8\xBE\xBE", + "\xB4\xF0" => "\xE7\xAD\x94", + "\xB4\xF1" => "\xE7\x98\xA9", + "\xB4\xF2" => "\xE6\x89\x93", + "\xB4\xF3" => "\xE5\xA4\xA7", + "\xB4\xF4" => "\xE5\x91\x86", + "\xB4\xF5" => "\xE6\xAD\xB9", + "\xB4\xF6" => "\xE5\x82\xA3", + "\xB4\xF7" => "\xE6\x88\xB4", + "\xB4\xF8" => "\xE5\xB8\xA6", + "\xB4\xF9" => "\xE6\xAE\x86", + "\xB4\xFA" => "\xE4\xBB\xA3", + "\xB4\xFB" => "\xE8\xB4\xB7", + "\xB4\xFC" => "\xE8\xA2\x8B", + "\xB4\xFD" => "\xE5\xBE\x85", + "\xB4\xFE" => "\xE9\x80\xAE", + "\xB5\xA1" => "\xE6\x80\xA0", + "\xB5\xA2" => "\xE8\x80\xBD", + "\xB5\xA3" => "\xE6\x8B\x85", + "\xB5\xA4" => "\xE4\xB8\xB9", + "\xB5\xA5" => "\xE5\x8D\x95", + "\xB5\xA6" => "\xE9\x83\xB8", + "\xB5\xA7" => "\xE6\x8E\xB8", + "\xB5\xA8" => "\xE8\x83\x86", + "\xB5\xA9" => "\xE6\x97\xA6", + "\xB5\xAA" => "\xE6\xB0\xAE", + "\xB5\xAB" => "\xE4\xBD\x86", + "\xB5\xAC" => "\xE6\x83\xAE", + "\xB5\xAD" => "\xE6\xB7\xA1", + "\xB5\xAE" => "\xE8\xAF\x9E", + "\xB5\xAF" => "\xE5\xBC\xB9", + "\xB5\xB0" => "\xE8\x9B\x8B", + "\xB5\xB1" => "\xE5\xBD\x93", + "\xB5\xB2" => "\xE6\x8C\xA1", + "\xB5\xB3" => "\xE5\x85\x9A", + "\xB5\xB4" => "\xE8\x8D\xA1", + "\xB5\xB5" => "\xE6\xA1\xA3", + "\xB5\xB6" => "\xE5\x88\x80", + "\xB5\xB7" => "\xE6\x8D\xA3", + "\xB5\xB8" => "\xE8\xB9\x88", + "\xB5\xB9" => "\xE5\x80\x92", + "\xB5\xBA" => "\xE5\xB2\x9B", + "\xB5\xBB" => "\xE7\xA5\xB7", + "\xB5\xBC" => "\xE5\xAF\xBC", + "\xB5\xBD" => "\xE5\x88\xB0", + "\xB5\xBE" => "\xE7\xA8\xBB", + "\xB5\xBF" => "\xE6\x82\xBC", + "\xB5\xC0" => "\xE9\x81\x93", + "\xB5\xC1" => "\xE7\x9B\x97", + "\xB5\xC2" => "\xE5\xBE\xB7", + "\xB5\xC3" => "\xE5\xBE\x97", + "\xB5\xC4" => "\xE7\x9A\x84", + "\xB5\xC5" => "\xE8\xB9\xAC", + "\xB5\xC6" => "\xE7\x81\xAF", + "\xB5\xC7" => "\xE7\x99\xBB", + "\xB5\xC8" => "\xE7\xAD\x89", + "\xB5\xC9" => "\xE7\x9E\xAA", + "\xB5\xCA" => "\xE5\x87\xB3", + "\xB5\xCB" => "\xE9\x82\x93", + "\xB5\xCC" => "\xE5\xA0\xA4", + "\xB5\xCD" => "\xE4\xBD\x8E", + "\xB5\xCE" => "\xE6\xBB\xB4", + "\xB5\xCF" => "\xE8\xBF\xAA", + "\xB5\xD0" => "\xE6\x95\x8C", + "\xB5\xD1" => "\xE7\xAC\x9B", + "\xB5\xD2" => "\xE7\x8B\x84", + "\xB5\xD3" => "\xE6\xB6\xA4", + "\xB5\xD4" => "\xE7\xBF\x9F", + "\xB5\xD5" => "\xE5\xAB\xA1", + "\xB5\xD6" => "\xE6\x8A\xB5", + "\xB5\xD7" => "\xE5\xBA\x95", + "\xB5\xD8" => "\xE5\x9C\xB0", + "\xB5\xD9" => "\xE8\x92\x82", + "\xB5\xDA" => "\xE7\xAC\xAC", + "\xB5\xDB" => "\xE5\xB8\x9D", + "\xB5\xDC" => "\xE5\xBC\x9F", + "\xB5\xDD" => "\xE9\x80\x92", + "\xB5\xDE" => "\xE7\xBC\x94", + "\xB5\xDF" => "\xE9\xA2\xA0", + "\xB5\xE0" => "\xE6\x8E\x82", + "\xB5\xE1" => "\xE6\xBB\x87", + "\xB5\xE2" => "\xE7\xA2\x98", + "\xB5\xE3" => "\xE7\x82\xB9", + "\xB5\xE4" => "\xE5\x85\xB8", + "\xB5\xE5" => "\xE9\x9D\x9B", + "\xB5\xE6" => "\xE5\x9E\xAB", + "\xB5\xE7" => "\xE7\x94\xB5", + "\xB5\xE8" => "\xE4\xBD\x83", + "\xB5\xE9" => "\xE7\x94\xB8", + "\xB5\xEA" => "\xE5\xBA\x97", + "\xB5\xEB" => "\xE6\x83\xA6", + "\xB5\xEC" => "\xE5\xA5\xA0", + "\xB5\xED" => "\xE6\xB7\x80", + "\xB5\xEE" => "\xE6\xAE\xBF", + "\xB5\xEF" => "\xE7\xA2\x89", + "\xB5\xF0" => "\xE5\x8F\xBC", + "\xB5\xF1" => "\xE9\x9B\x95", + "\xB5\xF2" => "\xE5\x87\x8B", + "\xB5\xF3" => "\xE5\x88\x81", + "\xB5\xF4" => "\xE6\x8E\x89", + "\xB5\xF5" => "\xE5\x90\x8A", + "\xB5\xF6" => "\xE9\x92\x93", + "\xB5\xF7" => "\xE8\xB0\x83", + "\xB5\xF8" => "\xE8\xB7\x8C", + "\xB5\xF9" => "\xE7\x88\xB9", + "\xB5\xFA" => "\xE7\xA2\x9F", + "\xB5\xFB" => "\xE8\x9D\xB6", + "\xB5\xFC" => "\xE8\xBF\xAD", + "\xB5\xFD" => "\xE8\xB0\x8D", + "\xB5\xFE" => "\xE5\x8F\xA0", + "\xB6\xA1" => "\xE4\xB8\x81", + "\xB6\xA2" => "\xE7\x9B\xAF", + "\xB6\xA3" => "\xE5\x8F\xAE", + "\xB6\xA4" => "\xE9\x92\x89", + "\xB6\xA5" => "\xE9\xA1\xB6", + "\xB6\xA6" => "\xE9\xBC\x8E", + "\xB6\xA7" => "\xE9\x94\xAD", + "\xB6\xA8" => "\xE5\xAE\x9A", + "\xB6\xA9" => "\xE8\xAE\xA2", + "\xB6\xAA" => "\xE4\xB8\xA2", + "\xB6\xAB" => "\xE4\xB8\x9C", + "\xB6\xAC" => "\xE5\x86\xAC", + "\xB6\xAD" => "\xE8\x91\xA3", + "\xB6\xAE" => "\xE6\x87\x82", + "\xB6\xAF" => "\xE5\x8A\xA8", + "\xB6\xB0" => "\xE6\xA0\x8B", + "\xB6\xB1" => "\xE4\xBE\x97", + "\xB6\xB2" => "\xE6\x81\xAB", + "\xB6\xB3" => "\xE5\x86\xBB", + "\xB6\xB4" => "\xE6\xB4\x9E", + "\xB6\xB5" => "\xE5\x85\x9C", + "\xB6\xB6" => "\xE6\x8A\x96", + "\xB6\xB7" => "\xE6\x96\x97", + "\xB6\xB8" => "\xE9\x99\xA1", + "\xB6\xB9" => "\xE8\xB1\x86", + "\xB6\xBA" => "\xE9\x80\x97", + "\xB6\xBB" => "\xE7\x97\x98", + "\xB6\xBC" => "\xE9\x83\xBD", + "\xB6\xBD" => "\xE7\x9D\xA3", + "\xB6\xBE" => "\xE6\xAF\x92", + "\xB6\xBF" => "\xE7\x8A\x8A", + "\xB6\xC0" => "\xE7\x8B\xAC", + "\xB6\xC1" => "\xE8\xAF\xBB", + "\xB6\xC2" => "\xE5\xA0\xB5", + "\xB6\xC3" => "\xE7\x9D\xB9", + "\xB6\xC4" => "\xE8\xB5\x8C", + "\xB6\xC5" => "\xE6\x9D\x9C", + "\xB6\xC6" => "\xE9\x95\x80", + "\xB6\xC7" => "\xE8\x82\x9A", + "\xB6\xC8" => "\xE5\xBA\xA6", + "\xB6\xC9" => "\xE6\xB8\xA1", + "\xB6\xCA" => "\xE5\xA6\x92", + "\xB6\xCB" => "\xE7\xAB\xAF", + "\xB6\xCC" => "\xE7\x9F\xAD", + "\xB6\xCD" => "\xE9\x94\xBB", + "\xB6\xCE" => "\xE6\xAE\xB5", + "\xB6\xCF" => "\xE6\x96\xAD", + "\xB6\xD0" => "\xE7\xBC\x8E", + "\xB6\xD1" => "\xE5\xA0\x86", + "\xB6\xD2" => "\xE5\x85\x91", + "\xB6\xD3" => "\xE9\x98\x9F", + "\xB6\xD4" => "\xE5\xAF\xB9", + "\xB6\xD5" => "\xE5\xA2\xA9", + "\xB6\xD6" => "\xE5\x90\xA8", + "\xB6\xD7" => "\xE8\xB9\xB2", + "\xB6\xD8" => "\xE6\x95\xA6", + "\xB6\xD9" => "\xE9\xA1\xBF", + "\xB6\xDA" => "\xE5\x9B\xA4", + "\xB6\xDB" => "\xE9\x92\x9D", + "\xB6\xDC" => "\xE7\x9B\xBE", + "\xB6\xDD" => "\xE9\x81\x81", + "\xB6\xDE" => "\xE6\x8E\x87", + "\xB6\xDF" => "\xE5\x93\x86", + "\xB6\xE0" => "\xE5\xA4\x9A", + "\xB6\xE1" => "\xE5\xA4\xBA", + "\xB6\xE2" => "\xE5\x9E\x9B", + "\xB6\xE3" => "\xE8\xBA\xB2", + "\xB6\xE4" => "\xE6\x9C\xB5", + "\xB6\xE5" => "\xE8\xB7\xBA", + "\xB6\xE6" => "\xE8\x88\xB5", + "\xB6\xE7" => "\xE5\x89\x81", + "\xB6\xE8" => "\xE6\x83\xB0", + "\xB6\xE9" => "\xE5\xA0\x95", + "\xB6\xEA" => "\xE8\x9B\xBE", + "\xB6\xEB" => "\xE5\xB3\xA8", + "\xB6\xEC" => "\xE9\xB9\x85", + "\xB6\xED" => "\xE4\xBF\x84", + "\xB6\xEE" => "\xE9\xA2\x9D", + "\xB6\xEF" => "\xE8\xAE\xB9", + "\xB6\xF0" => "\xE5\xA8\xA5", + "\xB6\xF1" => "\xE6\x81\xB6", + "\xB6\xF2" => "\xE5\x8E\x84", + "\xB6\xF3" => "\xE6\x89\xBC", + "\xB6\xF4" => "\xE9\x81\x8F", + "\xB6\xF5" => "\xE9\x84\x82", + "\xB6\xF6" => "\xE9\xA5\xBF", + "\xB6\xF7" => "\xE6\x81\xA9", + "\xB6\xF8" => "\xE8\x80\x8C", + "\xB6\xF9" => "\xE5\x84\xBF", + "\xB6\xFA" => "\xE8\x80\xB3", + "\xB6\xFB" => "\xE5\xB0\x94", + "\xB6\xFC" => "\xE9\xA5\xB5", + "\xB6\xFD" => "\xE6\xB4\xB1", + "\xB6\xFE" => "\xE4\xBA\x8C", + "\xB7\xA1" => "\xE8\xB4\xB0", + "\xB7\xA2" => "\xE5\x8F\x91", + "\xB7\xA3" => "\xE7\xBD\x9A", + "\xB7\xA4" => "\xE7\xAD\x8F", + "\xB7\xA5" => "\xE4\xBC\x90", + "\xB7\xA6" => "\xE4\xB9\x8F", + "\xB7\xA7" => "\xE9\x98\x80", + "\xB7\xA8" => "\xE6\xB3\x95", + "\xB7\xA9" => "\xE7\x8F\x90", + "\xB7\xAA" => "\xE8\x97\xA9", + "\xB7\xAB" => "\xE5\xB8\x86", + "\xB7\xAC" => "\xE7\x95\xAA", + "\xB7\xAD" => "\xE7\xBF\xBB", + "\xB7\xAE" => "\xE6\xA8\x8A", + "\xB7\xAF" => "\xE7\x9F\xBE", + "\xB7\xB0" => "\xE9\x92\x92", + "\xB7\xB1" => "\xE7\xB9\x81", + "\xB7\xB2" => "\xE5\x87\xA1", + "\xB7\xB3" => "\xE7\x83\xA6", + "\xB7\xB4" => "\xE5\x8F\x8D", + "\xB7\xB5" => "\xE8\xBF\x94", + "\xB7\xB6" => "\xE8\x8C\x83", + "\xB7\xB7" => "\xE8\xB4\xA9", + "\xB7\xB8" => "\xE7\x8A\xAF", + "\xB7\xB9" => "\xE9\xA5\xAD", + "\xB7\xBA" => "\xE6\xB3\x9B", + "\xB7\xBB" => "\xE5\x9D\x8A", + "\xB7\xBC" => "\xE8\x8A\xB3", + "\xB7\xBD" => "\xE6\x96\xB9", + "\xB7\xBE" => "\xE8\x82\xAA", + "\xB7\xBF" => "\xE6\x88\xBF", + "\xB7\xC0" => "\xE9\x98\xB2", + "\xB7\xC1" => "\xE5\xA6\xA8", + "\xB7\xC2" => "\xE4\xBB\xBF", + "\xB7\xC3" => "\xE8\xAE\xBF", + "\xB7\xC4" => "\xE7\xBA\xBA", + "\xB7\xC5" => "\xE6\x94\xBE", + "\xB7\xC6" => "\xE8\x8F\xB2", + "\xB7\xC7" => "\xE9\x9D\x9E", + "\xB7\xC8" => "\xE5\x95\xA1", + "\xB7\xC9" => "\xE9\xA3\x9E", + "\xB7\xCA" => "\xE8\x82\xA5", + "\xB7\xCB" => "\xE5\x8C\xAA", + "\xB7\xCC" => "\xE8\xAF\xBD", + "\xB7\xCD" => "\xE5\x90\xA0", + "\xB7\xCE" => "\xE8\x82\xBA", + "\xB7\xCF" => "\xE5\xBA\x9F", + "\xB7\xD0" => "\xE6\xB2\xB8", + "\xB7\xD1" => "\xE8\xB4\xB9", + "\xB7\xD2" => "\xE8\x8A\xAC", + "\xB7\xD3" => "\xE9\x85\x9A", + "\xB7\xD4" => "\xE5\x90\xA9", + "\xB7\xD5" => "\xE6\xB0\x9B", + "\xB7\xD6" => "\xE5\x88\x86", + "\xB7\xD7" => "\xE7\xBA\xB7", + "\xB7\xD8" => "\xE5\x9D\x9F", + "\xB7\xD9" => "\xE7\x84\x9A", + "\xB7\xDA" => "\xE6\xB1\xBE", + "\xB7\xDB" => "\xE7\xB2\x89", + "\xB7\xDC" => "\xE5\xA5\x8B", + "\xB7\xDD" => "\xE4\xBB\xBD", + "\xB7\xDE" => "\xE5\xBF\xBF", + "\xB7\xDF" => "\xE6\x84\xA4", + "\xB7\xE0" => "\xE7\xB2\xAA", + "\xB7\xE1" => "\xE4\xB8\xB0", + "\xB7\xE2" => "\xE5\xB0\x81", + "\xB7\xE3" => "\xE6\x9E\xAB", + "\xB7\xE4" => "\xE8\x9C\x82", + "\xB7\xE5" => "\xE5\xB3\xB0", + "\xB7\xE6" => "\xE9\x94\x8B", + "\xB7\xE7" => "\xE9\xA3\x8E", + "\xB7\xE8" => "\xE7\x96\xAF", + "\xB7\xE9" => "\xE7\x83\xBD", + "\xB7\xEA" => "\xE9\x80\xA2", + "\xB7\xEB" => "\xE5\x86\xAF", + "\xB7\xEC" => "\xE7\xBC\x9D", + "\xB7\xED" => "\xE8\xAE\xBD", + "\xB7\xEE" => "\xE5\xA5\x89", + "\xB7\xEF" => "\xE5\x87\xA4", + "\xB7\xF0" => "\xE4\xBD\x9B", + "\xB7\xF1" => "\xE5\x90\xA6", + "\xB7\xF2" => "\xE5\xA4\xAB", + "\xB7\xF3" => "\xE6\x95\xB7", + "\xB7\xF4" => "\xE8\x82\xA4", + "\xB7\xF5" => "\xE5\xAD\xB5", + "\xB7\xF6" => "\xE6\x89\xB6", + "\xB7\xF7" => "\xE6\x8B\x82", + "\xB7\xF8" => "\xE8\xBE\x90", + "\xB7\xF9" => "\xE5\xB9\x85", + "\xB7\xFA" => "\xE6\xB0\x9F", + "\xB7\xFB" => "\xE7\xAC\xA6", + "\xB7\xFC" => "\xE4\xBC\x8F", + "\xB7\xFD" => "\xE4\xBF\x98", + "\xB7\xFE" => "\xE6\x9C\x8D", + "\xB8\xA1" => "\xE6\xB5\xAE", + "\xB8\xA2" => "\xE6\xB6\xAA", + "\xB8\xA3" => "\xE7\xA6\x8F", + "\xB8\xA4" => "\xE8\xA2\xB1", + "\xB8\xA5" => "\xE5\xBC\x97", + "\xB8\xA6" => "\xE7\x94\xAB", + "\xB8\xA7" => "\xE6\x8A\x9A", + "\xB8\xA8" => "\xE8\xBE\x85", + "\xB8\xA9" => "\xE4\xBF\xAF", + "\xB8\xAA" => "\xE9\x87\x9C", + "\xB8\xAB" => "\xE6\x96\xA7", + "\xB8\xAC" => "\xE8\x84\xAF", + "\xB8\xAD" => "\xE8\x85\x91", + "\xB8\xAE" => "\xE5\xBA\x9C", + "\xB8\xAF" => "\xE8\x85\x90", + "\xB8\xB0" => "\xE8\xB5\xB4", + "\xB8\xB1" => "\xE5\x89\xAF", + "\xB8\xB2" => "\xE8\xA6\x86", + "\xB8\xB3" => "\xE8\xB5\x8B", + "\xB8\xB4" => "\xE5\xA4\x8D", + "\xB8\xB5" => "\xE5\x82\x85", + "\xB8\xB6" => "\xE4\xBB\x98", + "\xB8\xB7" => "\xE9\x98\x9C", + "\xB8\xB8" => "\xE7\x88\xB6", + "\xB8\xB9" => "\xE8\x85\xB9", + "\xB8\xBA" => "\xE8\xB4\x9F", + "\xB8\xBB" => "\xE5\xAF\x8C", + "\xB8\xBC" => "\xE8\xAE\xA3", + "\xB8\xBD" => "\xE9\x99\x84", + "\xB8\xBE" => "\xE5\xA6\x87", + "\xB8\xBF" => "\xE7\xBC\x9A", + "\xB8\xC0" => "\xE5\x92\x90", + "\xB8\xC1" => "\xE5\x99\xB6", + "\xB8\xC2" => "\xE5\x98\x8E", + "\xB8\xC3" => "\xE8\xAF\xA5", + "\xB8\xC4" => "\xE6\x94\xB9", + "\xB8\xC5" => "\xE6\xA6\x82", + "\xB8\xC6" => "\xE9\x92\x99", + "\xB8\xC7" => "\xE7\x9B\x96", + "\xB8\xC8" => "\xE6\xBA\x89", + "\xB8\xC9" => "\xE5\xB9\xB2", + "\xB8\xCA" => "\xE7\x94\x98", + "\xB8\xCB" => "\xE6\x9D\x86", + "\xB8\xCC" => "\xE6\x9F\x91", + "\xB8\xCD" => "\xE7\xAB\xBF", + "\xB8\xCE" => "\xE8\x82\x9D", + "\xB8\xCF" => "\xE8\xB5\xB6", + "\xB8\xD0" => "\xE6\x84\x9F", + "\xB8\xD1" => "\xE7\xA7\x86", + "\xB8\xD2" => "\xE6\x95\xA2", + "\xB8\xD3" => "\xE8\xB5\xA3", + "\xB8\xD4" => "\xE5\x86\x88", + "\xB8\xD5" => "\xE5\x88\x9A", + "\xB8\xD6" => "\xE9\x92\xA2", + "\xB8\xD7" => "\xE7\xBC\xB8", + "\xB8\xD8" => "\xE8\x82\x9B", + "\xB8\xD9" => "\xE7\xBA\xB2", + "\xB8\xDA" => "\xE5\xB2\x97", + "\xB8\xDB" => "\xE6\xB8\xAF", + "\xB8\xDC" => "\xE6\x9D\xA0", + "\xB8\xDD" => "\xE7\xAF\x99", + "\xB8\xDE" => "\xE7\x9A\x8B", + "\xB8\xDF" => "\xE9\xAB\x98", + "\xB8\xE0" => "\xE8\x86\x8F", + "\xB8\xE1" => "\xE7\xBE\x94", + "\xB8\xE2" => "\xE7\xB3\x95", + "\xB8\xE3" => "\xE6\x90\x9E", + "\xB8\xE4" => "\xE9\x95\x90", + "\xB8\xE5" => "\xE7\xA8\xBF", + "\xB8\xE6" => "\xE5\x91\x8A", + "\xB8\xE7" => "\xE5\x93\xA5", + "\xB8\xE8" => "\xE6\xAD\x8C", + "\xB8\xE9" => "\xE6\x90\x81", + "\xB8\xEA" => "\xE6\x88\x88", + "\xB8\xEB" => "\xE9\xB8\xBD", + "\xB8\xEC" => "\xE8\x83\xB3", + "\xB8\xED" => "\xE7\x96\x99", + "\xB8\xEE" => "\xE5\x89\xB2", + "\xB8\xEF" => "\xE9\x9D\xA9", + "\xB8\xF0" => "\xE8\x91\x9B", + "\xB8\xF1" => "\xE6\xA0\xBC", + "\xB8\xF2" => "\xE8\x9B\xA4", + "\xB8\xF3" => "\xE9\x98\x81", + "\xB8\xF4" => "\xE9\x9A\x94", + "\xB8\xF5" => "\xE9\x93\xAC", + "\xB8\xF6" => "\xE4\xB8\xAA", + "\xB8\xF7" => "\xE5\x90\x84", + "\xB8\xF8" => "\xE7\xBB\x99", + "\xB8\xF9" => "\xE6\xA0\xB9", + "\xB8\xFA" => "\xE8\xB7\x9F", + "\xB8\xFB" => "\xE8\x80\x95", + "\xB8\xFC" => "\xE6\x9B\xB4", + "\xB8\xFD" => "\xE5\xBA\x9A", + "\xB8\xFE" => "\xE7\xBE\xB9", + "\xB9\xA1" => "\xE5\x9F\x82", + "\xB9\xA2" => "\xE8\x80\xBF", + "\xB9\xA3" => "\xE6\xA2\x97", + "\xB9\xA4" => "\xE5\xB7\xA5", + "\xB9\xA5" => "\xE6\x94\xBB", + "\xB9\xA6" => "\xE5\x8A\x9F", + "\xB9\xA7" => "\xE6\x81\xAD", + "\xB9\xA8" => "\xE9\xBE\x9A", + "\xB9\xA9" => "\xE4\xBE\x9B", + "\xB9\xAA" => "\xE8\xBA\xAC", + "\xB9\xAB" => "\xE5\x85\xAC", + "\xB9\xAC" => "\xE5\xAE\xAB", + "\xB9\xAD" => "\xE5\xBC\x93", + "\xB9\xAE" => "\xE5\xB7\xA9", + "\xB9\xAF" => "\xE6\xB1\x9E", + "\xB9\xB0" => "\xE6\x8B\xB1", + "\xB9\xB1" => "\xE8\xB4\xA1", + "\xB9\xB2" => "\xE5\x85\xB1", + "\xB9\xB3" => "\xE9\x92\xA9", + "\xB9\xB4" => "\xE5\x8B\xBE", + "\xB9\xB5" => "\xE6\xB2\x9F", + "\xB9\xB6" => "\xE8\x8B\x9F", + "\xB9\xB7" => "\xE7\x8B\x97", + "\xB9\xB8" => "\xE5\x9E\xA2", + "\xB9\xB9" => "\xE6\x9E\x84", + "\xB9\xBA" => "\xE8\xB4\xAD", + "\xB9\xBB" => "\xE5\xA4\x9F", + "\xB9\xBC" => "\xE8\xBE\x9C", + "\xB9\xBD" => "\xE8\x8F\x87", + "\xB9\xBE" => "\xE5\x92\x95", + "\xB9\xBF" => "\xE7\xAE\x8D", + "\xB9\xC0" => "\xE4\xBC\xB0", + "\xB9\xC1" => "\xE6\xB2\xBD", + "\xB9\xC2" => "\xE5\xAD\xA4", + "\xB9\xC3" => "\xE5\xA7\x91", + "\xB9\xC4" => "\xE9\xBC\x93", + "\xB9\xC5" => "\xE5\x8F\xA4", + "\xB9\xC6" => "\xE8\x9B\x8A", + "\xB9\xC7" => "\xE9\xAA\xA8", + "\xB9\xC8" => "\xE8\xB0\xB7", + "\xB9\xC9" => "\xE8\x82\xA1", + "\xB9\xCA" => "\xE6\x95\x85", + "\xB9\xCB" => "\xE9\xA1\xBE", + "\xB9\xCC" => "\xE5\x9B\xBA", + "\xB9\xCD" => "\xE9\x9B\x87", + "\xB9\xCE" => "\xE5\x88\xAE", + "\xB9\xCF" => "\xE7\x93\x9C", + "\xB9\xD0" => "\xE5\x89\x90", + "\xB9\xD1" => "\xE5\xAF\xA1", + "\xB9\xD2" => "\xE6\x8C\x82", + "\xB9\xD3" => "\xE8\xA4\x82", + "\xB9\xD4" => "\xE4\xB9\x96", + "\xB9\xD5" => "\xE6\x8B\x90", + "\xB9\xD6" => "\xE6\x80\xAA", + "\xB9\xD7" => "\xE6\xA3\xBA", + "\xB9\xD8" => "\xE5\x85\xB3", + "\xB9\xD9" => "\xE5\xAE\x98", + "\xB9\xDA" => "\xE5\x86\xA0", + "\xB9\xDB" => "\xE8\xA7\x82", + "\xB9\xDC" => "\xE7\xAE\xA1", + "\xB9\xDD" => "\xE9\xA6\x86", + "\xB9\xDE" => "\xE7\xBD\x90", + "\xB9\xDF" => "\xE6\x83\xAF", + "\xB9\xE0" => "\xE7\x81\x8C", + "\xB9\xE1" => "\xE8\xB4\xAF", + "\xB9\xE2" => "\xE5\x85\x89", + "\xB9\xE3" => "\xE5\xB9\xBF", + "\xB9\xE4" => "\xE9\x80\x9B", + "\xB9\xE5" => "\xE7\x91\xB0", + "\xB9\xE6" => "\xE8\xA7\x84", + "\xB9\xE7" => "\xE5\x9C\xAD", + "\xB9\xE8" => "\xE7\xA1\x85", + "\xB9\xE9" => "\xE5\xBD\x92", + "\xB9\xEA" => "\xE9\xBE\x9F", + "\xB9\xEB" => "\xE9\x97\xBA", + "\xB9\xEC" => "\xE8\xBD\xA8", + "\xB9\xED" => "\xE9\xAC\xBC", + "\xB9\xEE" => "\xE8\xAF\xA1", + "\xB9\xEF" => "\xE7\x99\xB8", + "\xB9\xF0" => "\xE6\xA1\x82", + "\xB9\xF1" => "\xE6\x9F\x9C", + "\xB9\xF2" => "\xE8\xB7\xAA", + "\xB9\xF3" => "\xE8\xB4\xB5", + "\xB9\xF4" => "\xE5\x88\xBD", + "\xB9\xF5" => "\xE8\xBE\x8A", + "\xB9\xF6" => "\xE6\xBB\x9A", + "\xB9\xF7" => "\xE6\xA3\x8D", + "\xB9\xF8" => "\xE9\x94\x85", + "\xB9\xF9" => "\xE9\x83\xAD", + "\xB9\xFA" => "\xE5\x9B\xBD", + "\xB9\xFB" => "\xE6\x9E\x9C", + "\xB9\xFC" => "\xE8\xA3\xB9", + "\xB9\xFD" => "\xE8\xBF\x87", + "\xB9\xFE" => "\xE5\x93\x88", + "\xBA\xA1" => "\xE9\xAA\xB8", + "\xBA\xA2" => "\xE5\xAD\xA9", + "\xBA\xA3" => "\xE6\xB5\xB7", + "\xBA\xA4" => "\xE6\xB0\xA6", + "\xBA\xA5" => "\xE4\xBA\xA5", + "\xBA\xA6" => "\xE5\xAE\xB3", + "\xBA\xA7" => "\xE9\xAA\x87", + "\xBA\xA8" => "\xE9\x85\xA3", + "\xBA\xA9" => "\xE6\x86\xA8", + "\xBA\xAA" => "\xE9\x82\xAF", + "\xBA\xAB" => "\xE9\x9F\xA9", + "\xBA\xAC" => "\xE5\x90\xAB", + "\xBA\xAD" => "\xE6\xB6\xB5", + "\xBA\xAE" => "\xE5\xAF\x92", + "\xBA\xAF" => "\xE5\x87\xBD", + "\xBA\xB0" => "\xE5\x96\x8A", + "\xBA\xB1" => "\xE7\xBD\x95", + "\xBA\xB2" => "\xE7\xBF\xB0", + "\xBA\xB3" => "\xE6\x92\xBC", + "\xBA\xB4" => "\xE6\x8D\x8D", + "\xBA\xB5" => "\xE6\x97\xB1", + "\xBA\xB6" => "\xE6\x86\xBE", + "\xBA\xB7" => "\xE6\x82\x8D", + "\xBA\xB8" => "\xE7\x84\x8A", + "\xBA\xB9" => "\xE6\xB1\x97", + "\xBA\xBA" => "\xE6\xB1\x89", + "\xBA\xBB" => "\xE5\xA4\xAF", + "\xBA\xBC" => "\xE6\x9D\xAD", + "\xBA\xBD" => "\xE8\x88\xAA", + "\xBA\xBE" => "\xE5\xA3\x95", + "\xBA\xBF" => "\xE5\x9A\x8E", + "\xBA\xC0" => "\xE8\xB1\xAA", + "\xBA\xC1" => "\xE6\xAF\xAB", + "\xBA\xC2" => "\xE9\x83\x9D", + "\xBA\xC3" => "\xE5\xA5\xBD", + "\xBA\xC4" => "\xE8\x80\x97", + "\xBA\xC5" => "\xE5\x8F\xB7", + "\xBA\xC6" => "\xE6\xB5\xA9", + "\xBA\xC7" => "\xE5\x91\xB5", + "\xBA\xC8" => "\xE5\x96\x9D", + "\xBA\xC9" => "\xE8\x8D\xB7", + "\xBA\xCA" => "\xE8\x8F\x8F", + "\xBA\xCB" => "\xE6\xA0\xB8", + "\xBA\xCC" => "\xE7\xA6\xBE", + "\xBA\xCD" => "\xE5\x92\x8C", + "\xBA\xCE" => "\xE4\xBD\x95", + "\xBA\xCF" => "\xE5\x90\x88", + "\xBA\xD0" => "\xE7\x9B\x92", + "\xBA\xD1" => "\xE8\xB2\x89", + "\xBA\xD2" => "\xE9\x98\x82", + "\xBA\xD3" => "\xE6\xB2\xB3", + "\xBA\xD4" => "\xE6\xB6\xB8", + "\xBA\xD5" => "\xE8\xB5\xAB", + "\xBA\xD6" => "\xE8\xA4\x90", + "\xBA\xD7" => "\xE9\xB9\xA4", + "\xBA\xD8" => "\xE8\xB4\xBA", + "\xBA\xD9" => "\xE5\x98\xBF", + "\xBA\xDA" => "\xE9\xBB\x91", + "\xBA\xDB" => "\xE7\x97\x95", + "\xBA\xDC" => "\xE5\xBE\x88", + "\xBA\xDD" => "\xE7\x8B\xA0", + "\xBA\xDE" => "\xE6\x81\xA8", + "\xBA\xDF" => "\xE5\x93\xBC", + "\xBA\xE0" => "\xE4\xBA\xA8", + "\xBA\xE1" => "\xE6\xA8\xAA", + "\xBA\xE2" => "\xE8\xA1\xA1", + "\xBA\xE3" => "\xE6\x81\x92", + "\xBA\xE4" => "\xE8\xBD\xB0", + "\xBA\xE5" => "\xE5\x93\x84", + "\xBA\xE6" => "\xE7\x83\x98", + "\xBA\xE7" => "\xE8\x99\xB9", + "\xBA\xE8" => "\xE9\xB8\xBF", + "\xBA\xE9" => "\xE6\xB4\xAA", + "\xBA\xEA" => "\xE5\xAE\x8F", + "\xBA\xEB" => "\xE5\xBC\x98", + "\xBA\xEC" => "\xE7\xBA\xA2", + "\xBA\xED" => "\xE5\x96\x89", + "\xBA\xEE" => "\xE4\xBE\xAF", + "\xBA\xEF" => "\xE7\x8C\xB4", + "\xBA\xF0" => "\xE5\x90\xBC", + "\xBA\xF1" => "\xE5\x8E\x9A", + "\xBA\xF2" => "\xE5\x80\x99", + "\xBA\xF3" => "\xE5\x90\x8E", + "\xBA\xF4" => "\xE5\x91\xBC", + "\xBA\xF5" => "\xE4\xB9\x8E", + "\xBA\xF6" => "\xE5\xBF\xBD", + "\xBA\xF7" => "\xE7\x91\x9A", + "\xBA\xF8" => "\xE5\xA3\xB6", + "\xBA\xF9" => "\xE8\x91\xAB", + "\xBA\xFA" => "\xE8\x83\xA1", + "\xBA\xFB" => "\xE8\x9D\xB4", + "\xBA\xFC" => "\xE7\x8B\x90", + "\xBA\xFD" => "\xE7\xB3\x8A", + "\xBA\xFE" => "\xE6\xB9\x96", + "\xBB\xA1" => "\xE5\xBC\xA7", + "\xBB\xA2" => "\xE8\x99\x8E", + "\xBB\xA3" => "\xE5\x94\xAC", + "\xBB\xA4" => "\xE6\x8A\xA4", + "\xBB\xA5" => "\xE4\xBA\x92", + "\xBB\xA6" => "\xE6\xB2\xAA", + "\xBB\xA7" => "\xE6\x88\xB7", + "\xBB\xA8" => "\xE8\x8A\xB1", + "\xBB\xA9" => "\xE5\x93\x97", + "\xBB\xAA" => "\xE5\x8D\x8E", + "\xBB\xAB" => "\xE7\x8C\xBE", + "\xBB\xAC" => "\xE6\xBB\x91", + "\xBB\xAD" => "\xE7\x94\xBB", + "\xBB\xAE" => "\xE5\x88\x92", + "\xBB\xAF" => "\xE5\x8C\x96", + "\xBB\xB0" => "\xE8\xAF\x9D", + "\xBB\xB1" => "\xE6\xA7\x90", + "\xBB\xB2" => "\xE5\xBE\x8A", + "\xBB\xB3" => "\xE6\x80\x80", + "\xBB\xB4" => "\xE6\xB7\xAE", + "\xBB\xB5" => "\xE5\x9D\x8F", + "\xBB\xB6" => "\xE6\xAC\xA2", + "\xBB\xB7" => "\xE7\x8E\xAF", + "\xBB\xB8" => "\xE6\xA1\x93", + "\xBB\xB9" => "\xE8\xBF\x98", + "\xBB\xBA" => "\xE7\xBC\x93", + "\xBB\xBB" => "\xE6\x8D\xA2", + "\xBB\xBC" => "\xE6\x82\xA3", + "\xBB\xBD" => "\xE5\x94\xA4", + "\xBB\xBE" => "\xE7\x97\xAA", + "\xBB\xBF" => "\xE8\xB1\xA2", + "\xBB\xC0" => "\xE7\x84\x95", + "\xBB\xC1" => "\xE6\xB6\xA3", + "\xBB\xC2" => "\xE5\xAE\xA6", + "\xBB\xC3" => "\xE5\xB9\xBB", + "\xBB\xC4" => "\xE8\x8D\x92", + "\xBB\xC5" => "\xE6\x85\x8C", + "\xBB\xC6" => "\xE9\xBB\x84", + "\xBB\xC7" => "\xE7\xA3\xBA", + "\xBB\xC8" => "\xE8\x9D\x97", + "\xBB\xC9" => "\xE7\xB0\xA7", + "\xBB\xCA" => "\xE7\x9A\x87", + "\xBB\xCB" => "\xE5\x87\xB0", + "\xBB\xCC" => "\xE6\x83\xB6", + "\xBB\xCD" => "\xE7\x85\x8C", + "\xBB\xCE" => "\xE6\x99\x83", + "\xBB\xCF" => "\xE5\xB9\x8C", + "\xBB\xD0" => "\xE6\x81\x8D", + "\xBB\xD1" => "\xE8\xB0\x8E", + "\xBB\xD2" => "\xE7\x81\xB0", + "\xBB\xD3" => "\xE6\x8C\xA5", + "\xBB\xD4" => "\xE8\xBE\x89", + "\xBB\xD5" => "\xE5\xBE\xBD", + "\xBB\xD6" => "\xE6\x81\xA2", + "\xBB\xD7" => "\xE8\x9B\x94", + "\xBB\xD8" => "\xE5\x9B\x9E", + "\xBB\xD9" => "\xE6\xAF\x81", + "\xBB\xDA" => "\xE6\x82\x94", + "\xBB\xDB" => "\xE6\x85\xA7", + "\xBB\xDC" => "\xE5\x8D\x89", + "\xBB\xDD" => "\xE6\x83\xA0", + "\xBB\xDE" => "\xE6\x99\xA6", + "\xBB\xDF" => "\xE8\xB4\xBF", + "\xBB\xE0" => "\xE7\xA7\xBD", + "\xBB\xE1" => "\xE4\xBC\x9A", + "\xBB\xE2" => "\xE7\x83\xA9", + "\xBB\xE3" => "\xE6\xB1\x87", + "\xBB\xE4" => "\xE8\xAE\xB3", + "\xBB\xE5" => "\xE8\xAF\xB2", + "\xBB\xE6" => "\xE7\xBB\x98", + "\xBB\xE7" => "\xE8\x8D\xA4", + "\xBB\xE8" => "\xE6\x98\x8F", + "\xBB\xE9" => "\xE5\xA9\x9A", + "\xBB\xEA" => "\xE9\xAD\x82", + "\xBB\xEB" => "\xE6\xB5\x91", + "\xBB\xEC" => "\xE6\xB7\xB7", + "\xBB\xED" => "\xE8\xB1\x81", + "\xBB\xEE" => "\xE6\xB4\xBB", + "\xBB\xEF" => "\xE4\xBC\x99", + "\xBB\xF0" => "\xE7\x81\xAB", + "\xBB\xF1" => "\xE8\x8E\xB7", + "\xBB\xF2" => "\xE6\x88\x96", + "\xBB\xF3" => "\xE6\x83\x91", + "\xBB\xF4" => "\xE9\x9C\x8D", + "\xBB\xF5" => "\xE8\xB4\xA7", + "\xBB\xF6" => "\xE7\xA5\xB8", + "\xBB\xF7" => "\xE5\x87\xBB", + "\xBB\xF8" => "\xE5\x9C\xBE", + "\xBB\xF9" => "\xE5\x9F\xBA", + "\xBB\xFA" => "\xE6\x9C\xBA", + "\xBB\xFB" => "\xE7\x95\xB8", + "\xBB\xFC" => "\xE7\xA8\xBD", + "\xBB\xFD" => "\xE7\xA7\xAF", + "\xBB\xFE" => "\xE7\xAE\x95", + "\xBC\xA1" => "\xE8\x82\x8C", + "\xBC\xA2" => "\xE9\xA5\xA5", + "\xBC\xA3" => "\xE8\xBF\xB9", + "\xBC\xA4" => "\xE6\xBF\x80", + "\xBC\xA5" => "\xE8\xAE\xA5", + "\xBC\xA6" => "\xE9\xB8\xA1", + "\xBC\xA7" => "\xE5\xA7\xAC", + "\xBC\xA8" => "\xE7\xBB\xA9", + "\xBC\xA9" => "\xE7\xBC\x89", + "\xBC\xAA" => "\xE5\x90\x89", + "\xBC\xAB" => "\xE6\x9E\x81", + "\xBC\xAC" => "\xE6\xA3\x98", + "\xBC\xAD" => "\xE8\xBE\x91", + "\xBC\xAE" => "\xE7\xB1\x8D", + "\xBC\xAF" => "\xE9\x9B\x86", + "\xBC\xB0" => "\xE5\x8F\x8A", + "\xBC\xB1" => "\xE6\x80\xA5", + "\xBC\xB2" => "\xE7\x96\xBE", + "\xBC\xB3" => "\xE6\xB1\xB2", + "\xBC\xB4" => "\xE5\x8D\xB3", + "\xBC\xB5" => "\xE5\xAB\x89", + "\xBC\xB6" => "\xE7\xBA\xA7", + "\xBC\xB7" => "\xE6\x8C\xA4", + "\xBC\xB8" => "\xE5\x87\xA0", + "\xBC\xB9" => "\xE8\x84\x8A", + "\xBC\xBA" => "\xE5\xB7\xB1", + "\xBC\xBB" => "\xE8\x93\x9F", + "\xBC\xBC" => "\xE6\x8A\x80", + "\xBC\xBD" => "\xE5\x86\x80", + "\xBC\xBE" => "\xE5\xAD\xA3", + "\xBC\xBF" => "\xE4\xBC\x8E", + "\xBC\xC0" => "\xE7\xA5\xAD", + "\xBC\xC1" => "\xE5\x89\x82", + "\xBC\xC2" => "\xE6\x82\xB8", + "\xBC\xC3" => "\xE6\xB5\x8E", + "\xBC\xC4" => "\xE5\xAF\x84", + "\xBC\xC5" => "\xE5\xAF\x82", + "\xBC\xC6" => "\xE8\xAE\xA1", + "\xBC\xC7" => "\xE8\xAE\xB0", + "\xBC\xC8" => "\xE6\x97\xA2", + "\xBC\xC9" => "\xE5\xBF\x8C", + "\xBC\xCA" => "\xE9\x99\x85", + "\xBC\xCB" => "\xE5\xA6\x93", + "\xBC\xCC" => "\xE7\xBB\xA7", + "\xBC\xCD" => "\xE7\xBA\xAA", + "\xBC\xCE" => "\xE5\x98\x89", + "\xBC\xCF" => "\xE6\x9E\xB7", + "\xBC\xD0" => "\xE5\xA4\xB9", + "\xBC\xD1" => "\xE4\xBD\xB3", + "\xBC\xD2" => "\xE5\xAE\xB6", + "\xBC\xD3" => "\xE5\x8A\xA0", + "\xBC\xD4" => "\xE8\x8D\x9A", + "\xBC\xD5" => "\xE9\xA2\x8A", + "\xBC\xD6" => "\xE8\xB4\xBE", + "\xBC\xD7" => "\xE7\x94\xB2", + "\xBC\xD8" => "\xE9\x92\xBE", + "\xBC\xD9" => "\xE5\x81\x87", + "\xBC\xDA" => "\xE7\xA8\xBC", + "\xBC\xDB" => "\xE4\xBB\xB7", + "\xBC\xDC" => "\xE6\x9E\xB6", + "\xBC\xDD" => "\xE9\xA9\xBE", + "\xBC\xDE" => "\xE5\xAB\x81", + "\xBC\xDF" => "\xE6\xAD\xBC", + "\xBC\xE0" => "\xE7\x9B\x91", + "\xBC\xE1" => "\xE5\x9D\x9A", + "\xBC\xE2" => "\xE5\xB0\x96", + "\xBC\xE3" => "\xE7\xAC\xBA", + "\xBC\xE4" => "\xE9\x97\xB4", + "\xBC\xE5" => "\xE7\x85\x8E", + "\xBC\xE6" => "\xE5\x85\xBC", + "\xBC\xE7" => "\xE8\x82\xA9", + "\xBC\xE8" => "\xE8\x89\xB0", + "\xBC\xE9" => "\xE5\xA5\xB8", + "\xBC\xEA" => "\xE7\xBC\x84", + "\xBC\xEB" => "\xE8\x8C\xA7", + "\xBC\xEC" => "\xE6\xA3\x80", + "\xBC\xED" => "\xE6\x9F\xAC", + "\xBC\xEE" => "\xE7\xA2\xB1", + "\xBC\xEF" => "\xE7\xA1\xB7", + "\xBC\xF0" => "\xE6\x8B\xA3", + "\xBC\xF1" => "\xE6\x8D\xA1", + "\xBC\xF2" => "\xE7\xAE\x80", + "\xBC\xF3" => "\xE4\xBF\xAD", + "\xBC\xF4" => "\xE5\x89\xAA", + "\xBC\xF5" => "\xE5\x87\x8F", + "\xBC\xF6" => "\xE8\x8D\x90", + "\xBC\xF7" => "\xE6\xA7\x9B", + "\xBC\xF8" => "\xE9\x89\xB4", + "\xBC\xF9" => "\xE8\xB7\xB5", + "\xBC\xFA" => "\xE8\xB4\xB1", + "\xBC\xFB" => "\xE8\xA7\x81", + "\xBC\xFC" => "\xE9\x94\xAE", + "\xBC\xFD" => "\xE7\xAE\xAD", + "\xBC\xFE" => "\xE4\xBB\xB6", + "\xBD\xA1" => "\xE5\x81\xA5", + "\xBD\xA2" => "\xE8\x88\xB0", + "\xBD\xA3" => "\xE5\x89\x91", + "\xBD\xA4" => "\xE9\xA5\xAF", + "\xBD\xA5" => "\xE6\xB8\x90", + "\xBD\xA6" => "\xE6\xBA\x85", + "\xBD\xA7" => "\xE6\xB6\xA7", + "\xBD\xA8" => "\xE5\xBB\xBA", + "\xBD\xA9" => "\xE5\x83\xB5", + "\xBD\xAA" => "\xE5\xA7\x9C", + "\xBD\xAB" => "\xE5\xB0\x86", + "\xBD\xAC" => "\xE6\xB5\x86", + "\xBD\xAD" => "\xE6\xB1\x9F", + "\xBD\xAE" => "\xE7\x96\x86", + "\xBD\xAF" => "\xE8\x92\x8B", + "\xBD\xB0" => "\xE6\xA1\xA8", + "\xBD\xB1" => "\xE5\xA5\x96", + "\xBD\xB2" => "\xE8\xAE\xB2", + "\xBD\xB3" => "\xE5\x8C\xA0", + "\xBD\xB4" => "\xE9\x85\xB1", + "\xBD\xB5" => "\xE9\x99\x8D", + "\xBD\xB6" => "\xE8\x95\x89", + "\xBD\xB7" => "\xE6\xA4\x92", + "\xBD\xB8" => "\xE7\xA4\x81", + "\xBD\xB9" => "\xE7\x84\xA6", + "\xBD\xBA" => "\xE8\x83\xB6", + "\xBD\xBB" => "\xE4\xBA\xA4", + "\xBD\xBC" => "\xE9\x83\x8A", + "\xBD\xBD" => "\xE6\xB5\x87", + "\xBD\xBE" => "\xE9\xAA\x84", + "\xBD\xBF" => "\xE5\xA8\x87", + "\xBD\xC0" => "\xE5\x9A\xBC", + "\xBD\xC1" => "\xE6\x90\x85", + "\xBD\xC2" => "\xE9\x93\xB0", + "\xBD\xC3" => "\xE7\x9F\xAB", + "\xBD\xC4" => "\xE4\xBE\xA5", + "\xBD\xC5" => "\xE8\x84\x9A", + "\xBD\xC6" => "\xE7\x8B\xA1", + "\xBD\xC7" => "\xE8\xA7\x92", + "\xBD\xC8" => "\xE9\xA5\xBA", + "\xBD\xC9" => "\xE7\xBC\xB4", + "\xBD\xCA" => "\xE7\xBB\x9E", + "\xBD\xCB" => "\xE5\x89\xBF", + "\xBD\xCC" => "\xE6\x95\x99", + "\xBD\xCD" => "\xE9\x85\xB5", + "\xBD\xCE" => "\xE8\xBD\xBF", + "\xBD\xCF" => "\xE8\xBE\x83", + "\xBD\xD0" => "\xE5\x8F\xAB", + "\xBD\xD1" => "\xE7\xAA\x96", + "\xBD\xD2" => "\xE6\x8F\xAD", + "\xBD\xD3" => "\xE6\x8E\xA5", + "\xBD\xD4" => "\xE7\x9A\x86", + "\xBD\xD5" => "\xE7\xA7\xB8", + "\xBD\xD6" => "\xE8\xA1\x97", + "\xBD\xD7" => "\xE9\x98\xB6", + "\xBD\xD8" => "\xE6\x88\xAA", + "\xBD\xD9" => "\xE5\x8A\xAB", + "\xBD\xDA" => "\xE8\x8A\x82", + "\xBD\xDB" => "\xE6\xA1\x94", + "\xBD\xDC" => "\xE6\x9D\xB0", + "\xBD\xDD" => "\xE6\x8D\xB7", + "\xBD\xDE" => "\xE7\x9D\xAB", + "\xBD\xDF" => "\xE7\xAB\xAD", + "\xBD\xE0" => "\xE6\xB4\x81", + "\xBD\xE1" => "\xE7\xBB\x93", + "\xBD\xE2" => "\xE8\xA7\xA3", + "\xBD\xE3" => "\xE5\xA7\x90", + "\xBD\xE4" => "\xE6\x88\x92", + "\xBD\xE5" => "\xE8\x97\x89", + "\xBD\xE6" => "\xE8\x8A\xA5", + "\xBD\xE7" => "\xE7\x95\x8C", + "\xBD\xE8" => "\xE5\x80\x9F", + "\xBD\xE9" => "\xE4\xBB\x8B", + "\xBD\xEA" => "\xE7\x96\xA5", + "\xBD\xEB" => "\xE8\xAF\xAB", + "\xBD\xEC" => "\xE5\xB1\x8A", + "\xBD\xED" => "\xE5\xB7\xBE", + "\xBD\xEE" => "\xE7\xAD\x8B", + "\xBD\xEF" => "\xE6\x96\xA4", + "\xBD\xF0" => "\xE9\x87\x91", + "\xBD\xF1" => "\xE4\xBB\x8A", + "\xBD\xF2" => "\xE6\xB4\xA5", + "\xBD\xF3" => "\xE8\xA5\x9F", + "\xBD\xF4" => "\xE7\xB4\xA7", + "\xBD\xF5" => "\xE9\x94\xA6", + "\xBD\xF6" => "\xE4\xBB\x85", + "\xBD\xF7" => "\xE8\xB0\xA8", + "\xBD\xF8" => "\xE8\xBF\x9B", + "\xBD\xF9" => "\xE9\x9D\xB3", + "\xBD\xFA" => "\xE6\x99\x8B", + "\xBD\xFB" => "\xE7\xA6\x81", + "\xBD\xFC" => "\xE8\xBF\x91", + "\xBD\xFD" => "\xE7\x83\xAC", + "\xBD\xFE" => "\xE6\xB5\xB8", + "\xBE\xA1" => "\xE5\xB0\xBD", + "\xBE\xA2" => "\xE5\x8A\xB2", + "\xBE\xA3" => "\xE8\x8D\x86", + "\xBE\xA4" => "\xE5\x85\xA2", + "\xBE\xA5" => "\xE8\x8C\x8E", + "\xBE\xA6" => "\xE7\x9D\x9B", + "\xBE\xA7" => "\xE6\x99\xB6", + "\xBE\xA8" => "\xE9\xB2\xB8", + "\xBE\xA9" => "\xE4\xBA\xAC", + "\xBE\xAA" => "\xE6\x83\x8A", + "\xBE\xAB" => "\xE7\xB2\xBE", + "\xBE\xAC" => "\xE7\xB2\xB3", + "\xBE\xAD" => "\xE7\xBB\x8F", + "\xBE\xAE" => "\xE4\xBA\x95", + "\xBE\xAF" => "\xE8\xAD\xA6", + "\xBE\xB0" => "\xE6\x99\xAF", + "\xBE\xB1" => "\xE9\xA2\x88", + "\xBE\xB2" => "\xE9\x9D\x99", + "\xBE\xB3" => "\xE5\xA2\x83", + "\xBE\xB4" => "\xE6\x95\xAC", + "\xBE\xB5" => "\xE9\x95\x9C", + "\xBE\xB6" => "\xE5\xBE\x84", + "\xBE\xB7" => "\xE7\x97\x89", + "\xBE\xB8" => "\xE9\x9D\x96", + "\xBE\xB9" => "\xE7\xAB\x9F", + "\xBE\xBA" => "\xE7\xAB\x9E", + "\xBE\xBB" => "\xE5\x87\x80", + "\xBE\xBC" => "\xE7\x82\xAF", + "\xBE\xBD" => "\xE7\xAA\x98", + "\xBE\xBE" => "\xE6\x8F\xAA", + "\xBE\xBF" => "\xE7\xA9\xB6", + "\xBE\xC0" => "\xE7\xBA\xA0", + "\xBE\xC1" => "\xE7\x8E\x96", + "\xBE\xC2" => "\xE9\x9F\xAD", + "\xBE\xC3" => "\xE4\xB9\x85", + "\xBE\xC4" => "\xE7\x81\xB8", + "\xBE\xC5" => "\xE4\xB9\x9D", + "\xBE\xC6" => "\xE9\x85\x92", + "\xBE\xC7" => "\xE5\x8E\xA9", + "\xBE\xC8" => "\xE6\x95\x91", + "\xBE\xC9" => "\xE6\x97\xA7", + "\xBE\xCA" => "\xE8\x87\xBC", + "\xBE\xCB" => "\xE8\x88\x85", + "\xBE\xCC" => "\xE5\x92\x8E", + "\xBE\xCD" => "\xE5\xB0\xB1", + "\xBE\xCE" => "\xE7\x96\x9A", + "\xBE\xCF" => "\xE9\x9E\xA0", + "\xBE\xD0" => "\xE6\x8B\x98", + "\xBE\xD1" => "\xE7\x8B\x99", + "\xBE\xD2" => "\xE7\x96\xBD", + "\xBE\xD3" => "\xE5\xB1\x85", + "\xBE\xD4" => "\xE9\xA9\xB9", + "\xBE\xD5" => "\xE8\x8F\x8A", + "\xBE\xD6" => "\xE5\xB1\x80", + "\xBE\xD7" => "\xE5\x92\x80", + "\xBE\xD8" => "\xE7\x9F\xA9", + "\xBE\xD9" => "\xE4\xB8\xBE", + "\xBE\xDA" => "\xE6\xB2\xAE", + "\xBE\xDB" => "\xE8\x81\x9A", + "\xBE\xDC" => "\xE6\x8B\x92", + "\xBE\xDD" => "\xE6\x8D\xAE", + "\xBE\xDE" => "\xE5\xB7\xA8", + "\xBE\xDF" => "\xE5\x85\xB7", + "\xBE\xE0" => "\xE8\xB7\x9D", + "\xBE\xE1" => "\xE8\xB8\x9E", + "\xBE\xE2" => "\xE9\x94\xAF", + "\xBE\xE3" => "\xE4\xBF\xB1", + "\xBE\xE4" => "\xE5\x8F\xA5", + "\xBE\xE5" => "\xE6\x83\xA7", + "\xBE\xE6" => "\xE7\x82\xAC", + "\xBE\xE7" => "\xE5\x89\xA7", + "\xBE\xE8" => "\xE6\x8D\x90", + "\xBE\xE9" => "\xE9\xB9\x83", + "\xBE\xEA" => "\xE5\xA8\x9F", + "\xBE\xEB" => "\xE5\x80\xA6", + "\xBE\xEC" => "\xE7\x9C\xB7", + "\xBE\xED" => "\xE5\x8D\xB7", + "\xBE\xEE" => "\xE7\xBB\xA2", + "\xBE\xEF" => "\xE6\x92\x85", + "\xBE\xF0" => "\xE6\x94\xAB", + "\xBE\xF1" => "\xE6\x8A\x89", + "\xBE\xF2" => "\xE6\x8E\x98", + "\xBE\xF3" => "\xE5\x80\x94", + "\xBE\xF4" => "\xE7\x88\xB5", + "\xBE\xF5" => "\xE8\xA7\x89", + "\xBE\xF6" => "\xE5\x86\xB3", + "\xBE\xF7" => "\xE8\xAF\x80", + "\xBE\xF8" => "\xE7\xBB\x9D", + "\xBE\xF9" => "\xE5\x9D\x87", + "\xBE\xFA" => "\xE8\x8F\x8C", + "\xBE\xFB" => "\xE9\x92\xA7", + "\xBE\xFC" => "\xE5\x86\x9B", + "\xBE\xFD" => "\xE5\x90\x9B", + "\xBE\xFE" => "\xE5\xB3\xBB", + "\xBF\xA1" => "\xE4\xBF\x8A", + "\xBF\xA2" => "\xE7\xAB\xA3", + "\xBF\xA3" => "\xE6\xB5\x9A", + "\xBF\xA4" => "\xE9\x83\xA1", + "\xBF\xA5" => "\xE9\xAA\x8F", + "\xBF\xA6" => "\xE5\x96\x80", + "\xBF\xA7" => "\xE5\x92\x96", + "\xBF\xA8" => "\xE5\x8D\xA1", + "\xBF\xA9" => "\xE5\x92\xAF", + "\xBF\xAA" => "\xE5\xBC\x80", + "\xBF\xAB" => "\xE6\x8F\xA9", + "\xBF\xAC" => "\xE6\xA5\xB7", + "\xBF\xAD" => "\xE5\x87\xAF", + "\xBF\xAE" => "\xE6\x85\xA8", + "\xBF\xAF" => "\xE5\x88\x8A", + "\xBF\xB0" => "\xE5\xA0\xAA", + "\xBF\xB1" => "\xE5\x8B\x98", + "\xBF\xB2" => "\xE5\x9D\x8E", + "\xBF\xB3" => "\xE7\xA0\x8D", + "\xBF\xB4" => "\xE7\x9C\x8B", + "\xBF\xB5" => "\xE5\xBA\xB7", + "\xBF\xB6" => "\xE6\x85\xB7", + "\xBF\xB7" => "\xE7\xB3\xA0", + "\xBF\xB8" => "\xE6\x89\x9B", + "\xBF\xB9" => "\xE6\x8A\x97", + "\xBF\xBA" => "\xE4\xBA\xA2", + "\xBF\xBB" => "\xE7\x82\x95", + "\xBF\xBC" => "\xE8\x80\x83", + "\xBF\xBD" => "\xE6\x8B\xB7", + "\xBF\xBE" => "\xE7\x83\xA4", + "\xBF\xBF" => "\xE9\x9D\xA0", + "\xBF\xC0" => "\xE5\x9D\xB7", + "\xBF\xC1" => "\xE8\x8B\x9B", + "\xBF\xC2" => "\xE6\x9F\xAF", + "\xBF\xC3" => "\xE6\xA3\xB5", + "\xBF\xC4" => "\xE7\xA3\x95", + "\xBF\xC5" => "\xE9\xA2\x97", + "\xBF\xC6" => "\xE7\xA7\x91", + "\xBF\xC7" => "\xE5\xA3\xB3", + "\xBF\xC8" => "\xE5\x92\xB3", + "\xBF\xC9" => "\xE5\x8F\xAF", + "\xBF\xCA" => "\xE6\xB8\xB4", + "\xBF\xCB" => "\xE5\x85\x8B", + "\xBF\xCC" => "\xE5\x88\xBB", + "\xBF\xCD" => "\xE5\xAE\xA2", + "\xBF\xCE" => "\xE8\xAF\xBE", + "\xBF\xCF" => "\xE8\x82\xAF", + "\xBF\xD0" => "\xE5\x95\x83", + "\xBF\xD1" => "\xE5\x9E\xA6", + "\xBF\xD2" => "\xE6\x81\xB3", + "\xBF\xD3" => "\xE5\x9D\x91", + "\xBF\xD4" => "\xE5\x90\xAD", + "\xBF\xD5" => "\xE7\xA9\xBA", + "\xBF\xD6" => "\xE6\x81\x90", + "\xBF\xD7" => "\xE5\xAD\x94", + "\xBF\xD8" => "\xE6\x8E\xA7", + "\xBF\xD9" => "\xE6\x8A\xA0", + "\xBF\xDA" => "\xE5\x8F\xA3", + "\xBF\xDB" => "\xE6\x89\xA3", + "\xBF\xDC" => "\xE5\xAF\x87", + "\xBF\xDD" => "\xE6\x9E\xAF", + "\xBF\xDE" => "\xE5\x93\xAD", + "\xBF\xDF" => "\xE7\xAA\x9F", + "\xBF\xE0" => "\xE8\x8B\xA6", + "\xBF\xE1" => "\xE9\x85\xB7", + "\xBF\xE2" => "\xE5\xBA\x93", + "\xBF\xE3" => "\xE8\xA3\xA4", + "\xBF\xE4" => "\xE5\xA4\xB8", + "\xBF\xE5" => "\xE5\x9E\xAE", + "\xBF\xE6" => "\xE6\x8C\x8E", + "\xBF\xE7" => "\xE8\xB7\xA8", + "\xBF\xE8" => "\xE8\x83\xAF", + "\xBF\xE9" => "\xE5\x9D\x97", + "\xBF\xEA" => "\xE7\xAD\xB7", + "\xBF\xEB" => "\xE4\xBE\xA9", + "\xBF\xEC" => "\xE5\xBF\xAB", + "\xBF\xED" => "\xE5\xAE\xBD", + "\xBF\xEE" => "\xE6\xAC\xBE", + "\xBF\xEF" => "\xE5\x8C\xA1", + "\xBF\xF0" => "\xE7\xAD\x90", + "\xBF\xF1" => "\xE7\x8B\x82", + "\xBF\xF2" => "\xE6\xA1\x86", + "\xBF\xF3" => "\xE7\x9F\xBF", + "\xBF\xF4" => "\xE7\x9C\xB6", + "\xBF\xF5" => "\xE6\x97\xB7", + "\xBF\xF6" => "\xE5\x86\xB5", + "\xBF\xF7" => "\xE4\xBA\x8F", + "\xBF\xF8" => "\xE7\x9B\x94", + "\xBF\xF9" => "\xE5\xB2\xBF", + "\xBF\xFA" => "\xE7\xAA\xA5", + "\xBF\xFB" => "\xE8\x91\xB5", + "\xBF\xFC" => "\xE5\xA5\x8E", + "\xBF\xFD" => "\xE9\xAD\x81", + "\xBF\xFE" => "\xE5\x82\x80", + "\xC0\xA1" => "\xE9\xA6\x88", + "\xC0\xA2" => "\xE6\x84\xA7", + "\xC0\xA3" => "\xE6\xBA\x83", + "\xC0\xA4" => "\xE5\x9D\xA4", + "\xC0\xA5" => "\xE6\x98\x86", + "\xC0\xA6" => "\xE6\x8D\x86", + "\xC0\xA7" => "\xE5\x9B\xB0", + "\xC0\xA8" => "\xE6\x8B\xAC", + "\xC0\xA9" => "\xE6\x89\xA9", + "\xC0\xAA" => "\xE5\xBB\x93", + "\xC0\xAB" => "\xE9\x98\x94", + "\xC0\xAC" => "\xE5\x9E\x83", + "\xC0\xAD" => "\xE6\x8B\x89", + "\xC0\xAE" => "\xE5\x96\x87", + "\xC0\xAF" => "\xE8\x9C\xA1", + "\xC0\xB0" => "\xE8\x85\x8A", + "\xC0\xB1" => "\xE8\xBE\xA3", + "\xC0\xB2" => "\xE5\x95\xA6", + "\xC0\xB3" => "\xE8\x8E\xB1", + "\xC0\xB4" => "\xE6\x9D\xA5", + "\xC0\xB5" => "\xE8\xB5\x96", + "\xC0\xB6" => "\xE8\x93\x9D", + "\xC0\xB7" => "\xE5\xA9\xAA", + "\xC0\xB8" => "\xE6\xA0\x8F", + "\xC0\xB9" => "\xE6\x8B\xA6", + "\xC0\xBA" => "\xE7\xAF\xAE", + "\xC0\xBB" => "\xE9\x98\x91", + "\xC0\xBC" => "\xE5\x85\xB0", + "\xC0\xBD" => "\xE6\xBE\x9C", + "\xC0\xBE" => "\xE8\xB0\xB0", + "\xC0\xBF" => "\xE6\x8F\xBD", + "\xC0\xC0" => "\xE8\xA7\x88", + "\xC0\xC1" => "\xE6\x87\x92", + "\xC0\xC2" => "\xE7\xBC\x86", + "\xC0\xC3" => "\xE7\x83\x82", + "\xC0\xC4" => "\xE6\xBB\xA5", + "\xC0\xC5" => "\xE7\x90\x85", + "\xC0\xC6" => "\xE6\xA6\x94", + "\xC0\xC7" => "\xE7\x8B\xBC", + "\xC0\xC8" => "\xE5\xBB\x8A", + "\xC0\xC9" => "\xE9\x83\x8E", + "\xC0\xCA" => "\xE6\x9C\x97", + "\xC0\xCB" => "\xE6\xB5\xAA", + "\xC0\xCC" => "\xE6\x8D\x9E", + "\xC0\xCD" => "\xE5\x8A\xB3", + "\xC0\xCE" => "\xE7\x89\xA2", + "\xC0\xCF" => "\xE8\x80\x81", + "\xC0\xD0" => "\xE4\xBD\xAC", + "\xC0\xD1" => "\xE5\xA7\xA5", + "\xC0\xD2" => "\xE9\x85\xAA", + "\xC0\xD3" => "\xE7\x83\x99", + "\xC0\xD4" => "\xE6\xB6\x9D", + "\xC0\xD5" => "\xE5\x8B\x92", + "\xC0\xD6" => "\xE4\xB9\x90", + "\xC0\xD7" => "\xE9\x9B\xB7", + "\xC0\xD8" => "\xE9\x95\xAD", + "\xC0\xD9" => "\xE8\x95\xBE", + "\xC0\xDA" => "\xE7\xA3\x8A", + "\xC0\xDB" => "\xE7\xB4\xAF", + "\xC0\xDC" => "\xE5\x84\xA1", + "\xC0\xDD" => "\xE5\x9E\x92", + "\xC0\xDE" => "\xE6\x93\x82", + "\xC0\xDF" => "\xE8\x82\x8B", + "\xC0\xE0" => "\xE7\xB1\xBB", + "\xC0\xE1" => "\xE6\xB3\xAA", + "\xC0\xE2" => "\xE6\xA3\xB1", + "\xC0\xE3" => "\xE6\xA5\x9E", + "\xC0\xE4" => "\xE5\x86\xB7", + "\xC0\xE5" => "\xE5\x8E\x98", + "\xC0\xE6" => "\xE6\xA2\xA8", + "\xC0\xE7" => "\xE7\x8A\x81", + "\xC0\xE8" => "\xE9\xBB\x8E", + "\xC0\xE9" => "\xE7\xAF\xB1", + "\xC0\xEA" => "\xE7\x8B\xB8", + "\xC0\xEB" => "\xE7\xA6\xBB", + "\xC0\xEC" => "\xE6\xBC\x93", + "\xC0\xED" => "\xE7\x90\x86", + "\xC0\xEE" => "\xE6\x9D\x8E", + "\xC0\xEF" => "\xE9\x87\x8C", + "\xC0\xF0" => "\xE9\xB2\xA4", + "\xC0\xF1" => "\xE7\xA4\xBC", + "\xC0\xF2" => "\xE8\x8E\x89", + "\xC0\xF3" => "\xE8\x8D\x94", + "\xC0\xF4" => "\xE5\x90\x8F", + "\xC0\xF5" => "\xE6\xA0\x97", + "\xC0\xF6" => "\xE4\xB8\xBD", + "\xC0\xF7" => "\xE5\x8E\x89", + "\xC0\xF8" => "\xE5\x8A\xB1", + "\xC0\xF9" => "\xE7\xA0\xBE", + "\xC0\xFA" => "\xE5\x8E\x86", + "\xC0\xFB" => "\xE5\x88\xA9", + "\xC0\xFC" => "\xE5\x82\x88", + "\xC0\xFD" => "\xE4\xBE\x8B", + "\xC0\xFE" => "\xE4\xBF\x90", + "\xC1\xA1" => "\xE7\x97\xA2", + "\xC1\xA2" => "\xE7\xAB\x8B", + "\xC1\xA3" => "\xE7\xB2\x92", + "\xC1\xA4" => "\xE6\xB2\xA5", + "\xC1\xA5" => "\xE9\x9A\xB6", + "\xC1\xA6" => "\xE5\x8A\x9B", + "\xC1\xA7" => "\xE7\x92\x83", + "\xC1\xA8" => "\xE5\x93\xA9", + "\xC1\xA9" => "\xE4\xBF\xA9", + "\xC1\xAA" => "\xE8\x81\x94", + "\xC1\xAB" => "\xE8\x8E\xB2", + "\xC1\xAC" => "\xE8\xBF\x9E", + "\xC1\xAD" => "\xE9\x95\xB0", + "\xC1\xAE" => "\xE5\xBB\x89", + "\xC1\xAF" => "\xE6\x80\x9C", + "\xC1\xB0" => "\xE6\xB6\x9F", + "\xC1\xB1" => "\xE5\xB8\x98", + "\xC1\xB2" => "\xE6\x95\x9B", + "\xC1\xB3" => "\xE8\x84\xB8", + "\xC1\xB4" => "\xE9\x93\xBE", + "\xC1\xB5" => "\xE6\x81\x8B", + "\xC1\xB6" => "\xE7\x82\xBC", + "\xC1\xB7" => "\xE7\xBB\x83", + "\xC1\xB8" => "\xE7\xB2\xAE", + "\xC1\xB9" => "\xE5\x87\x89", + "\xC1\xBA" => "\xE6\xA2\x81", + "\xC1\xBB" => "\xE7\xB2\xB1", + "\xC1\xBC" => "\xE8\x89\xAF", + "\xC1\xBD" => "\xE4\xB8\xA4", + "\xC1\xBE" => "\xE8\xBE\x86", + "\xC1\xBF" => "\xE9\x87\x8F", + "\xC1\xC0" => "\xE6\x99\xBE", + "\xC1\xC1" => "\xE4\xBA\xAE", + "\xC1\xC2" => "\xE8\xB0\x85", + "\xC1\xC3" => "\xE6\x92\xA9", + "\xC1\xC4" => "\xE8\x81\x8A", + "\xC1\xC5" => "\xE5\x83\x9A", + "\xC1\xC6" => "\xE7\x96\x97", + "\xC1\xC7" => "\xE7\x87\x8E", + "\xC1\xC8" => "\xE5\xAF\xA5", + "\xC1\xC9" => "\xE8\xBE\xBD", + "\xC1\xCA" => "\xE6\xBD\xA6", + "\xC1\xCB" => "\xE4\xBA\x86", + "\xC1\xCC" => "\xE6\x92\x82", + "\xC1\xCD" => "\xE9\x95\xA3", + "\xC1\xCE" => "\xE5\xBB\x96", + "\xC1\xCF" => "\xE6\x96\x99", + "\xC1\xD0" => "\xE5\x88\x97", + "\xC1\xD1" => "\xE8\xA3\x82", + "\xC1\xD2" => "\xE7\x83\x88", + "\xC1\xD3" => "\xE5\x8A\xA3", + "\xC1\xD4" => "\xE7\x8C\x8E", + "\xC1\xD5" => "\xE7\x90\xB3", + "\xC1\xD6" => "\xE6\x9E\x97", + "\xC1\xD7" => "\xE7\xA3\xB7", + "\xC1\xD8" => "\xE9\x9C\x96", + "\xC1\xD9" => "\xE4\xB8\xB4", + "\xC1\xDA" => "\xE9\x82\xBB", + "\xC1\xDB" => "\xE9\xB3\x9E", + "\xC1\xDC" => "\xE6\xB7\x8B", + "\xC1\xDD" => "\xE5\x87\x9B", + "\xC1\xDE" => "\xE8\xB5\x81", + "\xC1\xDF" => "\xE5\x90\x9D", + "\xC1\xE0" => "\xE6\x8B\x8E", + "\xC1\xE1" => "\xE7\x8E\xB2", + "\xC1\xE2" => "\xE8\x8F\xB1", + "\xC1\xE3" => "\xE9\x9B\xB6", + "\xC1\xE4" => "\xE9\xBE\x84", + "\xC1\xE5" => "\xE9\x93\x83", + "\xC1\xE6" => "\xE4\xBC\xB6", + "\xC1\xE7" => "\xE7\xBE\x9A", + "\xC1\xE8" => "\xE5\x87\x8C", + "\xC1\xE9" => "\xE7\x81\xB5", + "\xC1\xEA" => "\xE9\x99\xB5", + "\xC1\xEB" => "\xE5\xB2\xAD", + "\xC1\xEC" => "\xE9\xA2\x86", + "\xC1\xED" => "\xE5\x8F\xA6", + "\xC1\xEE" => "\xE4\xBB\xA4", + "\xC1\xEF" => "\xE6\xBA\x9C", + "\xC1\xF0" => "\xE7\x90\x89", + "\xC1\xF1" => "\xE6\xA6\xB4", + "\xC1\xF2" => "\xE7\xA1\xAB", + "\xC1\xF3" => "\xE9\xA6\x8F", + "\xC1\xF4" => "\xE7\x95\x99", + "\xC1\xF5" => "\xE5\x88\x98", + "\xC1\xF6" => "\xE7\x98\xA4", + "\xC1\xF7" => "\xE6\xB5\x81", + "\xC1\xF8" => "\xE6\x9F\xB3", + "\xC1\xF9" => "\xE5\x85\xAD", + "\xC1\xFA" => "\xE9\xBE\x99", + "\xC1\xFB" => "\xE8\x81\x8B", + "\xC1\xFC" => "\xE5\x92\x99", + "\xC1\xFD" => "\xE7\xAC\xBC", + "\xC1\xFE" => "\xE7\xAA\xBF", + "\xC2\xA1" => "\xE9\x9A\x86", + "\xC2\xA2" => "\xE5\x9E\x84", + "\xC2\xA3" => "\xE6\x8B\xA2", + "\xC2\xA4" => "\xE9\x99\x87", + "\xC2\xA5" => "\xE6\xA5\xBC", + "\xC2\xA6" => "\xE5\xA8\x84", + "\xC2\xA7" => "\xE6\x90\x82", + "\xC2\xA8" => "\xE7\xAF\x93", + "\xC2\xA9" => "\xE6\xBC\x8F", + "\xC2\xAA" => "\xE9\x99\x8B", + "\xC2\xAB" => "\xE8\x8A\xA6", + "\xC2\xAC" => "\xE5\x8D\xA2", + "\xC2\xAD" => "\xE9\xA2\x85", + "\xC2\xAE" => "\xE5\xBA\x90", + "\xC2\xAF" => "\xE7\x82\x89", + "\xC2\xB0" => "\xE6\x8E\xB3", + "\xC2\xB1" => "\xE5\x8D\xA4", + "\xC2\xB2" => "\xE8\x99\x8F", + "\xC2\xB3" => "\xE9\xB2\x81", + "\xC2\xB4" => "\xE9\xBA\x93", + "\xC2\xB5" => "\xE7\xA2\x8C", + "\xC2\xB6" => "\xE9\x9C\xB2", + "\xC2\xB7" => "\xE8\xB7\xAF", + "\xC2\xB8" => "\xE8\xB5\x82", + "\xC2\xB9" => "\xE9\xB9\xBF", + "\xC2\xBA" => "\xE6\xBD\x9E", + "\xC2\xBB" => "\xE7\xA6\x84", + "\xC2\xBC" => "\xE5\xBD\x95", + "\xC2\xBD" => "\xE9\x99\x86", + "\xC2\xBE" => "\xE6\x88\xAE", + "\xC2\xBF" => "\xE9\xA9\xB4", + "\xC2\xC0" => "\xE5\x90\x95", + "\xC2\xC1" => "\xE9\x93\x9D", + "\xC2\xC2" => "\xE4\xBE\xA3", + "\xC2\xC3" => "\xE6\x97\x85", + "\xC2\xC4" => "\xE5\xB1\xA5", + "\xC2\xC5" => "\xE5\xB1\xA1", + "\xC2\xC6" => "\xE7\xBC\x95", + "\xC2\xC7" => "\xE8\x99\x91", + "\xC2\xC8" => "\xE6\xB0\xAF", + "\xC2\xC9" => "\xE5\xBE\x8B", + "\xC2\xCA" => "\xE7\x8E\x87", + "\xC2\xCB" => "\xE6\xBB\xA4", + "\xC2\xCC" => "\xE7\xBB\xBF", + "\xC2\xCD" => "\xE5\xB3\xA6", + "\xC2\xCE" => "\xE6\x8C\x9B", + "\xC2\xCF" => "\xE5\xAD\xAA", + "\xC2\xD0" => "\xE6\xBB\xA6", + "\xC2\xD1" => "\xE5\x8D\xB5", + "\xC2\xD2" => "\xE4\xB9\xB1", + "\xC2\xD3" => "\xE6\x8E\xA0", + "\xC2\xD4" => "\xE7\x95\xA5", + "\xC2\xD5" => "\xE6\x8A\xA1", + "\xC2\xD6" => "\xE8\xBD\xAE", + "\xC2\xD7" => "\xE4\xBC\xA6", + "\xC2\xD8" => "\xE4\xBB\x91", + "\xC2\xD9" => "\xE6\xB2\xA6", + "\xC2\xDA" => "\xE7\xBA\xB6", + "\xC2\xDB" => "\xE8\xAE\xBA", + "\xC2\xDC" => "\xE8\x90\x9D", + "\xC2\xDD" => "\xE8\x9E\xBA", + "\xC2\xDE" => "\xE7\xBD\x97", + "\xC2\xDF" => "\xE9\x80\xBB", + "\xC2\xE0" => "\xE9\x94\xA3", + "\xC2\xE1" => "\xE7\xAE\xA9", + "\xC2\xE2" => "\xE9\xAA\xA1", + "\xC2\xE3" => "\xE8\xA3\xB8", + "\xC2\xE4" => "\xE8\x90\xBD", + "\xC2\xE5" => "\xE6\xB4\x9B", + "\xC2\xE6" => "\xE9\xAA\x86", + "\xC2\xE7" => "\xE7\xBB\x9C", + "\xC2\xE8" => "\xE5\xA6\x88", + "\xC2\xE9" => "\xE9\xBA\xBB", + "\xC2\xEA" => "\xE7\x8E\x9B", + "\xC2\xEB" => "\xE7\xA0\x81", + "\xC2\xEC" => "\xE8\x9A\x82", + "\xC2\xED" => "\xE9\xA9\xAC", + "\xC2\xEE" => "\xE9\xAA\x82", + "\xC2\xEF" => "\xE5\x98\x9B", + "\xC2\xF0" => "\xE5\x90\x97", + "\xC2\xF1" => "\xE5\x9F\x8B", + "\xC2\xF2" => "\xE4\xB9\xB0", + "\xC2\xF3" => "\xE9\xBA\xA6", + "\xC2\xF4" => "\xE5\x8D\x96", + "\xC2\xF5" => "\xE8\xBF\x88", + "\xC2\xF6" => "\xE8\x84\x89", + "\xC2\xF7" => "\xE7\x9E\x92", + "\xC2\xF8" => "\xE9\xA6\x92", + "\xC2\xF9" => "\xE8\x9B\xAE", + "\xC2\xFA" => "\xE6\xBB\xA1", + "\xC2\xFB" => "\xE8\x94\x93", + "\xC2\xFC" => "\xE6\x9B\xBC", + "\xC2\xFD" => "\xE6\x85\xA2", + "\xC2\xFE" => "\xE6\xBC\xAB", + "\xC3\xA1" => "\xE8\xB0\xA9", + "\xC3\xA2" => "\xE8\x8A\x92", + "\xC3\xA3" => "\xE8\x8C\xAB", + "\xC3\xA4" => "\xE7\x9B\xB2", + "\xC3\xA5" => "\xE6\xB0\x93", + "\xC3\xA6" => "\xE5\xBF\x99", + "\xC3\xA7" => "\xE8\x8E\xBD", + "\xC3\xA8" => "\xE7\x8C\xAB", + "\xC3\xA9" => "\xE8\x8C\x85", + "\xC3\xAA" => "\xE9\x94\x9A", + "\xC3\xAB" => "\xE6\xAF\x9B", + "\xC3\xAC" => "\xE7\x9F\x9B", + "\xC3\xAD" => "\xE9\x93\x86", + "\xC3\xAE" => "\xE5\x8D\xAF", + "\xC3\xAF" => "\xE8\x8C\x82", + "\xC3\xB0" => "\xE5\x86\x92", + "\xC3\xB1" => "\xE5\xB8\xBD", + "\xC3\xB2" => "\xE8\xB2\x8C", + "\xC3\xB3" => "\xE8\xB4\xB8", + "\xC3\xB4" => "\xE4\xB9\x88", + "\xC3\xB5" => "\xE7\x8E\xAB", + "\xC3\xB6" => "\xE6\x9E\x9A", + "\xC3\xB7" => "\xE6\xA2\x85", + "\xC3\xB8" => "\xE9\x85\xB6", + "\xC3\xB9" => "\xE9\x9C\x89", + "\xC3\xBA" => "\xE7\x85\xA4", + "\xC3\xBB" => "\xE6\xB2\xA1", + "\xC3\xBC" => "\xE7\x9C\x89", + "\xC3\xBD" => "\xE5\xAA\x92", + "\xC3\xBE" => "\xE9\x95\x81", + "\xC3\xBF" => "\xE6\xAF\x8F", + "\xC3\xC0" => "\xE7\xBE\x8E", + "\xC3\xC1" => "\xE6\x98\xA7", + "\xC3\xC2" => "\xE5\xAF\x90", + "\xC3\xC3" => "\xE5\xA6\xB9", + "\xC3\xC4" => "\xE5\xAA\x9A", + "\xC3\xC5" => "\xE9\x97\xA8", + "\xC3\xC6" => "\xE9\x97\xB7", + "\xC3\xC7" => "\xE4\xBB\xAC", + "\xC3\xC8" => "\xE8\x90\x8C", + "\xC3\xC9" => "\xE8\x92\x99", + "\xC3\xCA" => "\xE6\xAA\xAC", + "\xC3\xCB" => "\xE7\x9B\x9F", + "\xC3\xCC" => "\xE9\x94\xB0", + "\xC3\xCD" => "\xE7\x8C\x9B", + "\xC3\xCE" => "\xE6\xA2\xA6", + "\xC3\xCF" => "\xE5\xAD\x9F", + "\xC3\xD0" => "\xE7\x9C\xAF", + "\xC3\xD1" => "\xE9\x86\x9A", + "\xC3\xD2" => "\xE9\x9D\xA1", + "\xC3\xD3" => "\xE7\xB3\x9C", + "\xC3\xD4" => "\xE8\xBF\xB7", + "\xC3\xD5" => "\xE8\xB0\x9C", + "\xC3\xD6" => "\xE5\xBC\xA5", + "\xC3\xD7" => "\xE7\xB1\xB3", + "\xC3\xD8" => "\xE7\xA7\x98", + "\xC3\xD9" => "\xE8\xA7\x85", + "\xC3\xDA" => "\xE6\xB3\x8C", + "\xC3\xDB" => "\xE8\x9C\x9C", + "\xC3\xDC" => "\xE5\xAF\x86", + "\xC3\xDD" => "\xE5\xB9\x82", + "\xC3\xDE" => "\xE6\xA3\x89", + "\xC3\xDF" => "\xE7\x9C\xA0", + "\xC3\xE0" => "\xE7\xBB\xB5", + "\xC3\xE1" => "\xE5\x86\x95", + "\xC3\xE2" => "\xE5\x85\x8D", + "\xC3\xE3" => "\xE5\x8B\x89", + "\xC3\xE4" => "\xE5\xA8\xA9", + "\xC3\xE5" => "\xE7\xBC\x85", + "\xC3\xE6" => "\xE9\x9D\xA2", + "\xC3\xE7" => "\xE8\x8B\x97", + "\xC3\xE8" => "\xE6\x8F\x8F", + "\xC3\xE9" => "\xE7\x9E\x84", + "\xC3\xEA" => "\xE8\x97\x90", + "\xC3\xEB" => "\xE7\xA7\x92", + "\xC3\xEC" => "\xE6\xB8\xBA", + "\xC3\xED" => "\xE5\xBA\x99", + "\xC3\xEE" => "\xE5\xA6\x99", + "\xC3\xEF" => "\xE8\x94\x91", + "\xC3\xF0" => "\xE7\x81\xAD", + "\xC3\xF1" => "\xE6\xB0\x91", + "\xC3\xF2" => "\xE6\x8A\xBF", + "\xC3\xF3" => "\xE7\x9A\xBF", + "\xC3\xF4" => "\xE6\x95\x8F", + "\xC3\xF5" => "\xE6\x82\xAF", + "\xC3\xF6" => "\xE9\x97\xBD", + "\xC3\xF7" => "\xE6\x98\x8E", + "\xC3\xF8" => "\xE8\x9E\x9F", + "\xC3\xF9" => "\xE9\xB8\xA3", + "\xC3\xFA" => "\xE9\x93\xAD", + "\xC3\xFB" => "\xE5\x90\x8D", + "\xC3\xFC" => "\xE5\x91\xBD", + "\xC3\xFD" => "\xE8\xB0\xAC", + "\xC3\xFE" => "\xE6\x91\xB8", + "\xC4\xA1" => "\xE6\x91\xB9", + "\xC4\xA2" => "\xE8\x98\x91", + "\xC4\xA3" => "\xE6\xA8\xA1", + "\xC4\xA4" => "\xE8\x86\x9C", + "\xC4\xA5" => "\xE7\xA3\xA8", + "\xC4\xA6" => "\xE6\x91\xA9", + "\xC4\xA7" => "\xE9\xAD\x94", + "\xC4\xA8" => "\xE6\x8A\xB9", + "\xC4\xA9" => "\xE6\x9C\xAB", + "\xC4\xAA" => "\xE8\x8E\xAB", + "\xC4\xAB" => "\xE5\xA2\xA8", + "\xC4\xAC" => "\xE9\xBB\x98", + "\xC4\xAD" => "\xE6\xB2\xAB", + "\xC4\xAE" => "\xE6\xBC\xA0", + "\xC4\xAF" => "\xE5\xAF\x9E", + "\xC4\xB0" => "\xE9\x99\x8C", + "\xC4\xB1" => "\xE8\xB0\x8B", + "\xC4\xB2" => "\xE7\x89\x9F", + "\xC4\xB3" => "\xE6\x9F\x90", + "\xC4\xB4" => "\xE6\x8B\x87", + "\xC4\xB5" => "\xE7\x89\xA1", + "\xC4\xB6" => "\xE4\xBA\xA9", + "\xC4\xB7" => "\xE5\xA7\x86", + "\xC4\xB8" => "\xE6\xAF\x8D", + "\xC4\xB9" => "\xE5\xA2\x93", + "\xC4\xBA" => "\xE6\x9A\xAE", + "\xC4\xBB" => "\xE5\xB9\x95", + "\xC4\xBC" => "\xE5\x8B\x9F", + "\xC4\xBD" => "\xE6\x85\x95", + "\xC4\xBE" => "\xE6\x9C\xA8", + "\xC4\xBF" => "\xE7\x9B\xAE", + "\xC4\xC0" => "\xE7\x9D\xA6", + "\xC4\xC1" => "\xE7\x89\xA7", + "\xC4\xC2" => "\xE7\xA9\x86", + "\xC4\xC3" => "\xE6\x8B\xBF", + "\xC4\xC4" => "\xE5\x93\xAA", + "\xC4\xC5" => "\xE5\x91\x90", + "\xC4\xC6" => "\xE9\x92\xA0", + "\xC4\xC7" => "\xE9\x82\xA3", + "\xC4\xC8" => "\xE5\xA8\x9C", + "\xC4\xC9" => "\xE7\xBA\xB3", + "\xC4\xCA" => "\xE6\xB0\x96", + "\xC4\xCB" => "\xE4\xB9\x83", + "\xC4\xCC" => "\xE5\xA5\xB6", + "\xC4\xCD" => "\xE8\x80\x90", + "\xC4\xCE" => "\xE5\xA5\x88", + "\xC4\xCF" => "\xE5\x8D\x97", + "\xC4\xD0" => "\xE7\x94\xB7", + "\xC4\xD1" => "\xE9\x9A\xBE", + "\xC4\xD2" => "\xE5\x9B\x8A", + "\xC4\xD3" => "\xE6\x8C\xA0", + "\xC4\xD4" => "\xE8\x84\x91", + "\xC4\xD5" => "\xE6\x81\xBC", + "\xC4\xD6" => "\xE9\x97\xB9", + "\xC4\xD7" => "\xE6\xB7\x96", + "\xC4\xD8" => "\xE5\x91\xA2", + "\xC4\xD9" => "\xE9\xA6\x81", + "\xC4\xDA" => "\xE5\x86\x85", + "\xC4\xDB" => "\xE5\xAB\xA9", + "\xC4\xDC" => "\xE8\x83\xBD", + "\xC4\xDD" => "\xE5\xA6\xAE", + "\xC4\xDE" => "\xE9\x9C\x93", + "\xC4\xDF" => "\xE5\x80\xAA", + "\xC4\xE0" => "\xE6\xB3\xA5", + "\xC4\xE1" => "\xE5\xB0\xBC", + "\xC4\xE2" => "\xE6\x8B\x9F", + "\xC4\xE3" => "\xE4\xBD\xA0", + "\xC4\xE4" => "\xE5\x8C\xBF", + "\xC4\xE5" => "\xE8\x85\xBB", + "\xC4\xE6" => "\xE9\x80\x86", + "\xC4\xE7" => "\xE6\xBA\xBA", + "\xC4\xE8" => "\xE8\x94\xAB", + "\xC4\xE9" => "\xE6\x8B\x88", + "\xC4\xEA" => "\xE5\xB9\xB4", + "\xC4\xEB" => "\xE7\xA2\xBE", + "\xC4\xEC" => "\xE6\x92\xB5", + "\xC4\xED" => "\xE6\x8D\xBB", + "\xC4\xEE" => "\xE5\xBF\xB5", + "\xC4\xEF" => "\xE5\xA8\x98", + "\xC4\xF0" => "\xE9\x85\xBF", + "\xC4\xF1" => "\xE9\xB8\x9F", + "\xC4\xF2" => "\xE5\xB0\xBF", + "\xC4\xF3" => "\xE6\x8D\x8F", + "\xC4\xF4" => "\xE8\x81\x82", + "\xC4\xF5" => "\xE5\xAD\xBD", + "\xC4\xF6" => "\xE5\x95\xAE", + "\xC4\xF7" => "\xE9\x95\x8A", + "\xC4\xF8" => "\xE9\x95\x8D", + "\xC4\xF9" => "\xE6\xB6\x85", + "\xC4\xFA" => "\xE6\x82\xA8", + "\xC4\xFB" => "\xE6\x9F\xA0", + "\xC4\xFC" => "\xE7\x8B\x9E", + "\xC4\xFD" => "\xE5\x87\x9D", + "\xC4\xFE" => "\xE5\xAE\x81", + "\xC5\xA1" => "\xE6\x8B\xA7", + "\xC5\xA2" => "\xE6\xB3\x9E", + "\xC5\xA3" => "\xE7\x89\x9B", + "\xC5\xA4" => "\xE6\x89\xAD", + "\xC5\xA5" => "\xE9\x92\xAE", + "\xC5\xA6" => "\xE7\xBA\xBD", + "\xC5\xA7" => "\xE8\x84\x93", + "\xC5\xA8" => "\xE6\xB5\x93", + "\xC5\xA9" => "\xE5\x86\x9C", + "\xC5\xAA" => "\xE5\xBC\x84", + "\xC5\xAB" => "\xE5\xA5\xB4", + "\xC5\xAC" => "\xE5\x8A\xAA", + "\xC5\xAD" => "\xE6\x80\x92", + "\xC5\xAE" => "\xE5\xA5\xB3", + "\xC5\xAF" => "\xE6\x9A\x96", + "\xC5\xB0" => "\xE8\x99\x90", + "\xC5\xB1" => "\xE7\x96\x9F", + "\xC5\xB2" => "\xE6\x8C\xAA", + "\xC5\xB3" => "\xE6\x87\xA6", + "\xC5\xB4" => "\xE7\xB3\xAF", + "\xC5\xB5" => "\xE8\xAF\xBA", + "\xC5\xB6" => "\xE5\x93\xA6", + "\xC5\xB7" => "\xE6\xAC\xA7", + "\xC5\xB8" => "\xE9\xB8\xA5", + "\xC5\xB9" => "\xE6\xAE\xB4", + "\xC5\xBA" => "\xE8\x97\x95", + "\xC5\xBB" => "\xE5\x91\x95", + "\xC5\xBC" => "\xE5\x81\xB6", + "\xC5\xBD" => "\xE6\xB2\xA4", + "\xC5\xBE" => "\xE5\x95\xAA", + "\xC5\xBF" => "\xE8\xB6\xB4", + "\xC5\xC0" => "\xE7\x88\xAC", + "\xC5\xC1" => "\xE5\xB8\x95", + "\xC5\xC2" => "\xE6\x80\x95", + "\xC5\xC3" => "\xE7\x90\xB6", + "\xC5\xC4" => "\xE6\x8B\x8D", + "\xC5\xC5" => "\xE6\x8E\x92", + "\xC5\xC6" => "\xE7\x89\x8C", + "\xC5\xC7" => "\xE5\xBE\x98", + "\xC5\xC8" => "\xE6\xB9\x83", + "\xC5\xC9" => "\xE6\xB4\xBE", + "\xC5\xCA" => "\xE6\x94\x80", + "\xC5\xCB" => "\xE6\xBD\x98", + "\xC5\xCC" => "\xE7\x9B\x98", + "\xC5\xCD" => "\xE7\xA3\x90", + "\xC5\xCE" => "\xE7\x9B\xBC", + "\xC5\xCF" => "\xE7\x95\x94", + "\xC5\xD0" => "\xE5\x88\xA4", + "\xC5\xD1" => "\xE5\x8F\x9B", + "\xC5\xD2" => "\xE4\xB9\x93", + "\xC5\xD3" => "\xE5\xBA\x9E", + "\xC5\xD4" => "\xE6\x97\x81", + "\xC5\xD5" => "\xE8\x80\xAA", + "\xC5\xD6" => "\xE8\x83\x96", + "\xC5\xD7" => "\xE6\x8A\x9B", + "\xC5\xD8" => "\xE5\x92\x86", + "\xC5\xD9" => "\xE5\x88\xA8", + "\xC5\xDA" => "\xE7\x82\xAE", + "\xC5\xDB" => "\xE8\xA2\x8D", + "\xC5\xDC" => "\xE8\xB7\x91", + "\xC5\xDD" => "\xE6\xB3\xA1", + "\xC5\xDE" => "\xE5\x91\xB8", + "\xC5\xDF" => "\xE8\x83\x9A", + "\xC5\xE0" => "\xE5\x9F\xB9", + "\xC5\xE1" => "\xE8\xA3\xB4", + "\xC5\xE2" => "\xE8\xB5\x94", + "\xC5\xE3" => "\xE9\x99\xAA", + "\xC5\xE4" => "\xE9\x85\x8D", + "\xC5\xE5" => "\xE4\xBD\xA9", + "\xC5\xE6" => "\xE6\xB2\x9B", + "\xC5\xE7" => "\xE5\x96\xB7", + "\xC5\xE8" => "\xE7\x9B\x86", + "\xC5\xE9" => "\xE7\xA0\xB0", + "\xC5\xEA" => "\xE6\x8A\xA8", + "\xC5\xEB" => "\xE7\x83\xB9", + "\xC5\xEC" => "\xE6\xBE\x8E", + "\xC5\xED" => "\xE5\xBD\xAD", + "\xC5\xEE" => "\xE8\x93\xAC", + "\xC5\xEF" => "\xE6\xA3\x9A", + "\xC5\xF0" => "\xE7\xA1\xBC", + "\xC5\xF1" => "\xE7\xAF\xB7", + "\xC5\xF2" => "\xE8\x86\xA8", + "\xC5\xF3" => "\xE6\x9C\x8B", + "\xC5\xF4" => "\xE9\xB9\x8F", + "\xC5\xF5" => "\xE6\x8D\xA7", + "\xC5\xF6" => "\xE7\xA2\xB0", + "\xC5\xF7" => "\xE5\x9D\xAF", + "\xC5\xF8" => "\xE7\xA0\x92", + "\xC5\xF9" => "\xE9\x9C\xB9", + "\xC5\xFA" => "\xE6\x89\xB9", + "\xC5\xFB" => "\xE6\x8A\xAB", + "\xC5\xFC" => "\xE5\x8A\x88", + "\xC5\xFD" => "\xE7\x90\xB5", + "\xC5\xFE" => "\xE6\xAF\x97", + "\xC6\xA1" => "\xE5\x95\xA4", + "\xC6\xA2" => "\xE8\x84\xBE", + "\xC6\xA3" => "\xE7\x96\xB2", + "\xC6\xA4" => "\xE7\x9A\xAE", + "\xC6\xA5" => "\xE5\x8C\xB9", + "\xC6\xA6" => "\xE7\x97\x9E", + "\xC6\xA7" => "\xE5\x83\xBB", + "\xC6\xA8" => "\xE5\xB1\x81", + "\xC6\xA9" => "\xE8\xAD\xAC", + "\xC6\xAA" => "\xE7\xAF\x87", + "\xC6\xAB" => "\xE5\x81\x8F", + "\xC6\xAC" => "\xE7\x89\x87", + "\xC6\xAD" => "\xE9\xAA\x97", + "\xC6\xAE" => "\xE9\xA3\x98", + "\xC6\xAF" => "\xE6\xBC\x82", + "\xC6\xB0" => "\xE7\x93\xA2", + "\xC6\xB1" => "\xE7\xA5\xA8", + "\xC6\xB2" => "\xE6\x92\x87", + "\xC6\xB3" => "\xE7\x9E\xA5", + "\xC6\xB4" => "\xE6\x8B\xBC", + "\xC6\xB5" => "\xE9\xA2\x91", + "\xC6\xB6" => "\xE8\xB4\xAB", + "\xC6\xB7" => "\xE5\x93\x81", + "\xC6\xB8" => "\xE8\x81\x98", + "\xC6\xB9" => "\xE4\xB9\x92", + "\xC6\xBA" => "\xE5\x9D\xAA", + "\xC6\xBB" => "\xE8\x8B\xB9", + "\xC6\xBC" => "\xE8\x90\x8D", + "\xC6\xBD" => "\xE5\xB9\xB3", + "\xC6\xBE" => "\xE5\x87\xAD", + "\xC6\xBF" => "\xE7\x93\xB6", + "\xC6\xC0" => "\xE8\xAF\x84", + "\xC6\xC1" => "\xE5\xB1\x8F", + "\xC6\xC2" => "\xE5\x9D\xA1", + "\xC6\xC3" => "\xE6\xB3\xBC", + "\xC6\xC4" => "\xE9\xA2\x87", + "\xC6\xC5" => "\xE5\xA9\x86", + "\xC6\xC6" => "\xE7\xA0\xB4", + "\xC6\xC7" => "\xE9\xAD\x84", + "\xC6\xC8" => "\xE8\xBF\xAB", + "\xC6\xC9" => "\xE7\xB2\x95", + "\xC6\xCA" => "\xE5\x89\x96", + "\xC6\xCB" => "\xE6\x89\x91", + "\xC6\xCC" => "\xE9\x93\xBA", + "\xC6\xCD" => "\xE4\xBB\x86", + "\xC6\xCE" => "\xE8\x8E\x86", + "\xC6\xCF" => "\xE8\x91\xA1", + "\xC6\xD0" => "\xE8\x8F\xA9", + "\xC6\xD1" => "\xE8\x92\xB2", + "\xC6\xD2" => "\xE5\x9F\x94", + "\xC6\xD3" => "\xE6\x9C\xB4", + "\xC6\xD4" => "\xE5\x9C\x83", + "\xC6\xD5" => "\xE6\x99\xAE", + "\xC6\xD6" => "\xE6\xB5\xA6", + "\xC6\xD7" => "\xE8\xB0\xB1", + "\xC6\xD8" => "\xE6\x9B\x9D", + "\xC6\xD9" => "\xE7\x80\x91", + "\xC6\xDA" => "\xE6\x9C\x9F", + "\xC6\xDB" => "\xE6\xAC\xBA", + "\xC6\xDC" => "\xE6\xA0\x96", + "\xC6\xDD" => "\xE6\x88\x9A", + "\xC6\xDE" => "\xE5\xA6\xBB", + "\xC6\xDF" => "\xE4\xB8\x83", + "\xC6\xE0" => "\xE5\x87\x84", + "\xC6\xE1" => "\xE6\xBC\x86", + "\xC6\xE2" => "\xE6\x9F\x92", + "\xC6\xE3" => "\xE6\xB2\x8F", + "\xC6\xE4" => "\xE5\x85\xB6", + "\xC6\xE5" => "\xE6\xA3\x8B", + "\xC6\xE6" => "\xE5\xA5\x87", + "\xC6\xE7" => "\xE6\xAD\xA7", + "\xC6\xE8" => "\xE7\x95\xA6", + "\xC6\xE9" => "\xE5\xB4\x8E", + "\xC6\xEA" => "\xE8\x84\x90", + "\xC6\xEB" => "\xE9\xBD\x90", + "\xC6\xEC" => "\xE6\x97\x97", + "\xC6\xED" => "\xE7\xA5\x88", + "\xC6\xEE" => "\xE7\xA5\x81", + "\xC6\xEF" => "\xE9\xAA\x91", + "\xC6\xF0" => "\xE8\xB5\xB7", + "\xC6\xF1" => "\xE5\xB2\x82", + "\xC6\xF2" => "\xE4\xB9\x9E", + "\xC6\xF3" => "\xE4\xBC\x81", + "\xC6\xF4" => "\xE5\x90\xAF", + "\xC6\xF5" => "\xE5\xA5\x91", + "\xC6\xF6" => "\xE7\xA0\x8C", + "\xC6\xF7" => "\xE5\x99\xA8", + "\xC6\xF8" => "\xE6\xB0\x94", + "\xC6\xF9" => "\xE8\xBF\x84", + "\xC6\xFA" => "\xE5\xBC\x83", + "\xC6\xFB" => "\xE6\xB1\xBD", + "\xC6\xFC" => "\xE6\xB3\xA3", + "\xC6\xFD" => "\xE8\xAE\xAB", + "\xC6\xFE" => "\xE6\x8E\x90", + "\xC7\xA1" => "\xE6\x81\xB0", + "\xC7\xA2" => "\xE6\xB4\xBD", + "\xC7\xA3" => "\xE7\x89\xB5", + "\xC7\xA4" => "\xE6\x89\xA6", + "\xC7\xA5" => "\xE9\x92\x8E", + "\xC7\xA6" => "\xE9\x93\x85", + "\xC7\xA7" => "\xE5\x8D\x83", + "\xC7\xA8" => "\xE8\xBF\x81", + "\xC7\xA9" => "\xE7\xAD\xBE", + "\xC7\xAA" => "\xE4\xBB\x9F", + "\xC7\xAB" => "\xE8\xB0\xA6", + "\xC7\xAC" => "\xE4\xB9\xBE", + "\xC7\xAD" => "\xE9\xBB\x94", + "\xC7\xAE" => "\xE9\x92\xB1", + "\xC7\xAF" => "\xE9\x92\xB3", + "\xC7\xB0" => "\xE5\x89\x8D", + "\xC7\xB1" => "\xE6\xBD\x9C", + "\xC7\xB2" => "\xE9\x81\xA3", + "\xC7\xB3" => "\xE6\xB5\x85", + "\xC7\xB4" => "\xE8\xB0\xB4", + "\xC7\xB5" => "\xE5\xA0\x91", + "\xC7\xB6" => "\xE5\xB5\x8C", + "\xC7\xB7" => "\xE6\xAC\xA0", + "\xC7\xB8" => "\xE6\xAD\x89", + "\xC7\xB9" => "\xE6\x9E\xAA", + "\xC7\xBA" => "\xE5\x91\x9B", + "\xC7\xBB" => "\xE8\x85\x94", + "\xC7\xBC" => "\xE7\xBE\x8C", + "\xC7\xBD" => "\xE5\xA2\x99", + "\xC7\xBE" => "\xE8\x94\xB7", + "\xC7\xBF" => "\xE5\xBC\xBA", + "\xC7\xC0" => "\xE6\x8A\xA2", + "\xC7\xC1" => "\xE6\xA9\x87", + "\xC7\xC2" => "\xE9\x94\xB9", + "\xC7\xC3" => "\xE6\x95\xB2", + "\xC7\xC4" => "\xE6\x82\x84", + "\xC7\xC5" => "\xE6\xA1\xA5", + "\xC7\xC6" => "\xE7\x9E\xA7", + "\xC7\xC7" => "\xE4\xB9\x94", + "\xC7\xC8" => "\xE4\xBE\xA8", + "\xC7\xC9" => "\xE5\xB7\xA7", + "\xC7\xCA" => "\xE9\x9E\x98", + "\xC7\xCB" => "\xE6\x92\xAC", + "\xC7\xCC" => "\xE7\xBF\x98", + "\xC7\xCD" => "\xE5\xB3\xAD", + "\xC7\xCE" => "\xE4\xBF\x8F", + "\xC7\xCF" => "\xE7\xAA\x8D", + "\xC7\xD0" => "\xE5\x88\x87", + "\xC7\xD1" => "\xE8\x8C\x84", + "\xC7\xD2" => "\xE4\xB8\x94", + "\xC7\xD3" => "\xE6\x80\xAF", + "\xC7\xD4" => "\xE7\xAA\x83", + "\xC7\xD5" => "\xE9\x92\xA6", + "\xC7\xD6" => "\xE4\xBE\xB5", + "\xC7\xD7" => "\xE4\xBA\xB2", + "\xC7\xD8" => "\xE7\xA7\xA6", + "\xC7\xD9" => "\xE7\x90\xB4", + "\xC7\xDA" => "\xE5\x8B\xA4", + "\xC7\xDB" => "\xE8\x8A\xB9", + "\xC7\xDC" => "\xE6\x93\x92", + "\xC7\xDD" => "\xE7\xA6\xBD", + "\xC7\xDE" => "\xE5\xAF\x9D", + "\xC7\xDF" => "\xE6\xB2\x81", + "\xC7\xE0" => "\xE9\x9D\x92", + "\xC7\xE1" => "\xE8\xBD\xBB", + "\xC7\xE2" => "\xE6\xB0\xA2", + "\xC7\xE3" => "\xE5\x80\xBE", + "\xC7\xE4" => "\xE5\x8D\xBF", + "\xC7\xE5" => "\xE6\xB8\x85", + "\xC7\xE6" => "\xE6\x93\x8E", + "\xC7\xE7" => "\xE6\x99\xB4", + "\xC7\xE8" => "\xE6\xB0\xB0", + "\xC7\xE9" => "\xE6\x83\x85", + "\xC7\xEA" => "\xE9\xA1\xB7", + "\xC7\xEB" => "\xE8\xAF\xB7", + "\xC7\xEC" => "\xE5\xBA\x86", + "\xC7\xED" => "\xE7\x90\xBC", + "\xC7\xEE" => "\xE7\xA9\xB7", + "\xC7\xEF" => "\xE7\xA7\x8B", + "\xC7\xF0" => "\xE4\xB8\x98", + "\xC7\xF1" => "\xE9\x82\xB1", + "\xC7\xF2" => "\xE7\x90\x83", + "\xC7\xF3" => "\xE6\xB1\x82", + "\xC7\xF4" => "\xE5\x9B\x9A", + "\xC7\xF5" => "\xE9\x85\x8B", + "\xC7\xF6" => "\xE6\xB3\x85", + "\xC7\xF7" => "\xE8\xB6\x8B", + "\xC7\xF8" => "\xE5\x8C\xBA", + "\xC7\xF9" => "\xE8\x9B\x86", + "\xC7\xFA" => "\xE6\x9B\xB2", + "\xC7\xFB" => "\xE8\xBA\xAF", + "\xC7\xFC" => "\xE5\xB1\x88", + "\xC7\xFD" => "\xE9\xA9\xB1", + "\xC7\xFE" => "\xE6\xB8\xA0", + "\xC8\xA1" => "\xE5\x8F\x96", + "\xC8\xA2" => "\xE5\xA8\xB6", + "\xC8\xA3" => "\xE9\xBE\x8B", + "\xC8\xA4" => "\xE8\xB6\xA3", + "\xC8\xA5" => "\xE5\x8E\xBB", + "\xC8\xA6" => "\xE5\x9C\x88", + "\xC8\xA7" => "\xE9\xA2\xA7", + "\xC8\xA8" => "\xE6\x9D\x83", + "\xC8\xA9" => "\xE9\x86\x9B", + "\xC8\xAA" => "\xE6\xB3\x89", + "\xC8\xAB" => "\xE5\x85\xA8", + "\xC8\xAC" => "\xE7\x97\x8A", + "\xC8\xAD" => "\xE6\x8B\xB3", + "\xC8\xAE" => "\xE7\x8A\xAC", + "\xC8\xAF" => "\xE5\x88\xB8", + "\xC8\xB0" => "\xE5\x8A\x9D", + "\xC8\xB1" => "\xE7\xBC\xBA", + "\xC8\xB2" => "\xE7\x82\x94", + "\xC8\xB3" => "\xE7\x98\xB8", + "\xC8\xB4" => "\xE5\x8D\xB4", + "\xC8\xB5" => "\xE9\xB9\x8A", + "\xC8\xB6" => "\xE6\xA6\xB7", + "\xC8\xB7" => "\xE7\xA1\xAE", + "\xC8\xB8" => "\xE9\x9B\x80", + "\xC8\xB9" => "\xE8\xA3\x99", + "\xC8\xBA" => "\xE7\xBE\xA4", + "\xC8\xBB" => "\xE7\x84\xB6", + "\xC8\xBC" => "\xE7\x87\x83", + "\xC8\xBD" => "\xE5\x86\x89", + "\xC8\xBE" => "\xE6\x9F\x93", + "\xC8\xBF" => "\xE7\x93\xA4", + "\xC8\xC0" => "\xE5\xA3\xA4", + "\xC8\xC1" => "\xE6\x94\x98", + "\xC8\xC2" => "\xE5\x9A\xB7", + "\xC8\xC3" => "\xE8\xAE\xA9", + "\xC8\xC4" => "\xE9\xA5\xB6", + "\xC8\xC5" => "\xE6\x89\xB0", + "\xC8\xC6" => "\xE7\xBB\x95", + "\xC8\xC7" => "\xE6\x83\xB9", + "\xC8\xC8" => "\xE7\x83\xAD", + "\xC8\xC9" => "\xE5\xA3\xAC", + "\xC8\xCA" => "\xE4\xBB\x81", + "\xC8\xCB" => "\xE4\xBA\xBA", + "\xC8\xCC" => "\xE5\xBF\x8D", + "\xC8\xCD" => "\xE9\x9F\xA7", + "\xC8\xCE" => "\xE4\xBB\xBB", + "\xC8\xCF" => "\xE8\xAE\xA4", + "\xC8\xD0" => "\xE5\x88\x83", + "\xC8\xD1" => "\xE5\xA6\x8A", + "\xC8\xD2" => "\xE7\xBA\xAB", + "\xC8\xD3" => "\xE6\x89\x94", + "\xC8\xD4" => "\xE4\xBB\x8D", + "\xC8\xD5" => "\xE6\x97\xA5", + "\xC8\xD6" => "\xE6\x88\x8E", + "\xC8\xD7" => "\xE8\x8C\xB8", + "\xC8\xD8" => "\xE8\x93\x89", + "\xC8\xD9" => "\xE8\x8D\xA3", + "\xC8\xDA" => "\xE8\x9E\x8D", + "\xC8\xDB" => "\xE7\x86\x94", + "\xC8\xDC" => "\xE6\xBA\xB6", + "\xC8\xDD" => "\xE5\xAE\xB9", + "\xC8\xDE" => "\xE7\xBB\x92", + "\xC8\xDF" => "\xE5\x86\x97", + "\xC8\xE0" => "\xE6\x8F\x89", + "\xC8\xE1" => "\xE6\x9F\x94", + "\xC8\xE2" => "\xE8\x82\x89", + "\xC8\xE3" => "\xE8\x8C\xB9", + "\xC8\xE4" => "\xE8\xA0\x95", + "\xC8\xE5" => "\xE5\x84\x92", + "\xC8\xE6" => "\xE5\xAD\xBA", + "\xC8\xE7" => "\xE5\xA6\x82", + "\xC8\xE8" => "\xE8\xBE\xB1", + "\xC8\xE9" => "\xE4\xB9\xB3", + "\xC8\xEA" => "\xE6\xB1\x9D", + "\xC8\xEB" => "\xE5\x85\xA5", + "\xC8\xEC" => "\xE8\xA4\xA5", + "\xC8\xED" => "\xE8\xBD\xAF", + "\xC8\xEE" => "\xE9\x98\xAE", + "\xC8\xEF" => "\xE8\x95\x8A", + "\xC8\xF0" => "\xE7\x91\x9E", + "\xC8\xF1" => "\xE9\x94\x90", + "\xC8\xF2" => "\xE9\x97\xB0", + "\xC8\xF3" => "\xE6\xB6\xA6", + "\xC8\xF4" => "\xE8\x8B\xA5", + "\xC8\xF5" => "\xE5\xBC\xB1", + "\xC8\xF6" => "\xE6\x92\x92", + "\xC8\xF7" => "\xE6\xB4\x92", + "\xC8\xF8" => "\xE8\x90\xA8", + "\xC8\xF9" => "\xE8\x85\xAE", + "\xC8\xFA" => "\xE9\xB3\x83", + "\xC8\xFB" => "\xE5\xA1\x9E", + "\xC8\xFC" => "\xE8\xB5\x9B", + "\xC8\xFD" => "\xE4\xB8\x89", + "\xC8\xFE" => "\xE5\x8F\x81", + "\xC9\xA1" => "\xE4\xBC\x9E", + "\xC9\xA2" => "\xE6\x95\xA3", + "\xC9\xA3" => "\xE6\xA1\x91", + "\xC9\xA4" => "\xE5\x97\x93", + "\xC9\xA5" => "\xE4\xB8\xA7", + "\xC9\xA6" => "\xE6\x90\x94", + "\xC9\xA7" => "\xE9\xAA\x9A", + "\xC9\xA8" => "\xE6\x89\xAB", + "\xC9\xA9" => "\xE5\xAB\x82", + "\xC9\xAA" => "\xE7\x91\x9F", + "\xC9\xAB" => "\xE8\x89\xB2", + "\xC9\xAC" => "\xE6\xB6\xA9", + "\xC9\xAD" => "\xE6\xA3\xAE", + "\xC9\xAE" => "\xE5\x83\xA7", + "\xC9\xAF" => "\xE8\x8E\x8E", + "\xC9\xB0" => "\xE7\xA0\x82", + "\xC9\xB1" => "\xE6\x9D\x80", + "\xC9\xB2" => "\xE5\x88\xB9", + "\xC9\xB3" => "\xE6\xB2\x99", + "\xC9\xB4" => "\xE7\xBA\xB1", + "\xC9\xB5" => "\xE5\x82\xBB", + "\xC9\xB6" => "\xE5\x95\xA5", + "\xC9\xB7" => "\xE7\x85\x9E", + "\xC9\xB8" => "\xE7\xAD\x9B", + "\xC9\xB9" => "\xE6\x99\x92", + "\xC9\xBA" => "\xE7\x8F\x8A", + "\xC9\xBB" => "\xE8\x8B\xAB", + "\xC9\xBC" => "\xE6\x9D\x89", + "\xC9\xBD" => "\xE5\xB1\xB1", + "\xC9\xBE" => "\xE5\x88\xA0", + "\xC9\xBF" => "\xE7\x85\xBD", + "\xC9\xC0" => "\xE8\xA1\xAB", + "\xC9\xC1" => "\xE9\x97\xAA", + "\xC9\xC2" => "\xE9\x99\x95", + "\xC9\xC3" => "\xE6\x93\x85", + "\xC9\xC4" => "\xE8\xB5\xA1", + "\xC9\xC5" => "\xE8\x86\xB3", + "\xC9\xC6" => "\xE5\x96\x84", + "\xC9\xC7" => "\xE6\xB1\x95", + "\xC9\xC8" => "\xE6\x89\x87", + "\xC9\xC9" => "\xE7\xBC\xAE", + "\xC9\xCA" => "\xE5\xA2\x92", + "\xC9\xCB" => "\xE4\xBC\xA4", + "\xC9\xCC" => "\xE5\x95\x86", + "\xC9\xCD" => "\xE8\xB5\x8F", + "\xC9\xCE" => "\xE6\x99\x8C", + "\xC9\xCF" => "\xE4\xB8\x8A", + "\xC9\xD0" => "\xE5\xB0\x9A", + "\xC9\xD1" => "\xE8\xA3\xB3", + "\xC9\xD2" => "\xE6\xA2\xA2", + "\xC9\xD3" => "\xE6\x8D\x8E", + "\xC9\xD4" => "\xE7\xA8\x8D", + "\xC9\xD5" => "\xE7\x83\xA7", + "\xC9\xD6" => "\xE8\x8A\x8D", + "\xC9\xD7" => "\xE5\x8B\xBA", + "\xC9\xD8" => "\xE9\x9F\xB6", + "\xC9\xD9" => "\xE5\xB0\x91", + "\xC9\xDA" => "\xE5\x93\xA8", + "\xC9\xDB" => "\xE9\x82\xB5", + "\xC9\xDC" => "\xE7\xBB\x8D", + "\xC9\xDD" => "\xE5\xA5\xA2", + "\xC9\xDE" => "\xE8\xB5\x8A", + "\xC9\xDF" => "\xE8\x9B\x87", + "\xC9\xE0" => "\xE8\x88\x8C", + "\xC9\xE1" => "\xE8\x88\x8D", + "\xC9\xE2" => "\xE8\xB5\xA6", + "\xC9\xE3" => "\xE6\x91\x84", + "\xC9\xE4" => "\xE5\xB0\x84", + "\xC9\xE5" => "\xE6\x85\x91", + "\xC9\xE6" => "\xE6\xB6\x89", + "\xC9\xE7" => "\xE7\xA4\xBE", + "\xC9\xE8" => "\xE8\xAE\xBE", + "\xC9\xE9" => "\xE7\xA0\xB7", + "\xC9\xEA" => "\xE7\x94\xB3", + "\xC9\xEB" => "\xE5\x91\xBB", + "\xC9\xEC" => "\xE4\xBC\xB8", + "\xC9\xED" => "\xE8\xBA\xAB", + "\xC9\xEE" => "\xE6\xB7\xB1", + "\xC9\xEF" => "\xE5\xA8\xA0", + "\xC9\xF0" => "\xE7\xBB\x85", + "\xC9\xF1" => "\xE7\xA5\x9E", + "\xC9\xF2" => "\xE6\xB2\x88", + "\xC9\xF3" => "\xE5\xAE\xA1", + "\xC9\xF4" => "\xE5\xA9\xB6", + "\xC9\xF5" => "\xE7\x94\x9A", + "\xC9\xF6" => "\xE8\x82\xBE", + "\xC9\xF7" => "\xE6\x85\x8E", + "\xC9\xF8" => "\xE6\xB8\x97", + "\xC9\xF9" => "\xE5\xA3\xB0", + "\xC9\xFA" => "\xE7\x94\x9F", + "\xC9\xFB" => "\xE7\x94\xA5", + "\xC9\xFC" => "\xE7\x89\xB2", + "\xC9\xFD" => "\xE5\x8D\x87", + "\xC9\xFE" => "\xE7\xBB\xB3", + "\xCA\xA1" => "\xE7\x9C\x81", + "\xCA\xA2" => "\xE7\x9B\x9B", + "\xCA\xA3" => "\xE5\x89\xA9", + "\xCA\xA4" => "\xE8\x83\x9C", + "\xCA\xA5" => "\xE5\x9C\xA3", + "\xCA\xA6" => "\xE5\xB8\x88", + "\xCA\xA7" => "\xE5\xA4\xB1", + "\xCA\xA8" => "\xE7\x8B\xAE", + "\xCA\xA9" => "\xE6\x96\xBD", + "\xCA\xAA" => "\xE6\xB9\xBF", + "\xCA\xAB" => "\xE8\xAF\x97", + "\xCA\xAC" => "\xE5\xB0\xB8", + "\xCA\xAD" => "\xE8\x99\xB1", + "\xCA\xAE" => "\xE5\x8D\x81", + "\xCA\xAF" => "\xE7\x9F\xB3", + "\xCA\xB0" => "\xE6\x8B\xBE", + "\xCA\xB1" => "\xE6\x97\xB6", + "\xCA\xB2" => "\xE4\xBB\x80", + "\xCA\xB3" => "\xE9\xA3\x9F", + "\xCA\xB4" => "\xE8\x9A\x80", + "\xCA\xB5" => "\xE5\xAE\x9E", + "\xCA\xB6" => "\xE8\xAF\x86", + "\xCA\xB7" => "\xE5\x8F\xB2", + "\xCA\xB8" => "\xE7\x9F\xA2", + "\xCA\xB9" => "\xE4\xBD\xBF", + "\xCA\xBA" => "\xE5\xB1\x8E", + "\xCA\xBB" => "\xE9\xA9\xB6", + "\xCA\xBC" => "\xE5\xA7\x8B", + "\xCA\xBD" => "\xE5\xBC\x8F", + "\xCA\xBE" => "\xE7\xA4\xBA", + "\xCA\xBF" => "\xE5\xA3\xAB", + "\xCA\xC0" => "\xE4\xB8\x96", + "\xCA\xC1" => "\xE6\x9F\xBF", + "\xCA\xC2" => "\xE4\xBA\x8B", + "\xCA\xC3" => "\xE6\x8B\xAD", + "\xCA\xC4" => "\xE8\xAA\x93", + "\xCA\xC5" => "\xE9\x80\x9D", + "\xCA\xC6" => "\xE5\x8A\xBF", + "\xCA\xC7" => "\xE6\x98\xAF", + "\xCA\xC8" => "\xE5\x97\x9C", + "\xCA\xC9" => "\xE5\x99\xAC", + "\xCA\xCA" => "\xE9\x80\x82", + "\xCA\xCB" => "\xE4\xBB\x95", + "\xCA\xCC" => "\xE4\xBE\x8D", + "\xCA\xCD" => "\xE9\x87\x8A", + "\xCA\xCE" => "\xE9\xA5\xB0", + "\xCA\xCF" => "\xE6\xB0\x8F", + "\xCA\xD0" => "\xE5\xB8\x82", + "\xCA\xD1" => "\xE6\x81\x83", + "\xCA\xD2" => "\xE5\xAE\xA4", + "\xCA\xD3" => "\xE8\xA7\x86", + "\xCA\xD4" => "\xE8\xAF\x95", + "\xCA\xD5" => "\xE6\x94\xB6", + "\xCA\xD6" => "\xE6\x89\x8B", + "\xCA\xD7" => "\xE9\xA6\x96", + "\xCA\xD8" => "\xE5\xAE\x88", + "\xCA\xD9" => "\xE5\xAF\xBF", + "\xCA\xDA" => "\xE6\x8E\x88", + "\xCA\xDB" => "\xE5\x94\xAE", + "\xCA\xDC" => "\xE5\x8F\x97", + "\xCA\xDD" => "\xE7\x98\xA6", + "\xCA\xDE" => "\xE5\x85\xBD", + "\xCA\xDF" => "\xE8\x94\xAC", + "\xCA\xE0" => "\xE6\x9E\xA2", + "\xCA\xE1" => "\xE6\xA2\xB3", + "\xCA\xE2" => "\xE6\xAE\x8A", + "\xCA\xE3" => "\xE6\x8A\x92", + "\xCA\xE4" => "\xE8\xBE\x93", + "\xCA\xE5" => "\xE5\x8F\x94", + "\xCA\xE6" => "\xE8\x88\x92", + "\xCA\xE7" => "\xE6\xB7\x91", + "\xCA\xE8" => "\xE7\x96\x8F", + "\xCA\xE9" => "\xE4\xB9\xA6", + "\xCA\xEA" => "\xE8\xB5\x8E", + "\xCA\xEB" => "\xE5\xAD\xB0", + "\xCA\xEC" => "\xE7\x86\x9F", + "\xCA\xED" => "\xE8\x96\xAF", + "\xCA\xEE" => "\xE6\x9A\x91", + "\xCA\xEF" => "\xE6\x9B\x99", + "\xCA\xF0" => "\xE7\xBD\xB2", + "\xCA\xF1" => "\xE8\x9C\x80", + "\xCA\xF2" => "\xE9\xBB\x8D", + "\xCA\xF3" => "\xE9\xBC\xA0", + "\xCA\xF4" => "\xE5\xB1\x9E", + "\xCA\xF5" => "\xE6\x9C\xAF", + "\xCA\xF6" => "\xE8\xBF\xB0", + "\xCA\xF7" => "\xE6\xA0\x91", + "\xCA\xF8" => "\xE6\x9D\x9F", + "\xCA\xF9" => "\xE6\x88\x8D", + "\xCA\xFA" => "\xE7\xAB\x96", + "\xCA\xFB" => "\xE5\xA2\x85", + "\xCA\xFC" => "\xE5\xBA\xB6", + "\xCA\xFD" => "\xE6\x95\xB0", + "\xCA\xFE" => "\xE6\xBC\xB1", + "\xCB\xA1" => "\xE6\x81\x95", + "\xCB\xA2" => "\xE5\x88\xB7", + "\xCB\xA3" => "\xE8\x80\x8D", + "\xCB\xA4" => "\xE6\x91\x94", + "\xCB\xA5" => "\xE8\xA1\xB0", + "\xCB\xA6" => "\xE7\x94\xA9", + "\xCB\xA7" => "\xE5\xB8\x85", + "\xCB\xA8" => "\xE6\xA0\x93", + "\xCB\xA9" => "\xE6\x8B\xB4", + "\xCB\xAA" => "\xE9\x9C\x9C", + "\xCB\xAB" => "\xE5\x8F\x8C", + "\xCB\xAC" => "\xE7\x88\xBD", + "\xCB\xAD" => "\xE8\xB0\x81", + "\xCB\xAE" => "\xE6\xB0\xB4", + "\xCB\xAF" => "\xE7\x9D\xA1", + "\xCB\xB0" => "\xE7\xA8\x8E", + "\xCB\xB1" => "\xE5\x90\xAE", + "\xCB\xB2" => "\xE7\x9E\xAC", + "\xCB\xB3" => "\xE9\xA1\xBA", + "\xCB\xB4" => "\xE8\x88\x9C", + "\xCB\xB5" => "\xE8\xAF\xB4", + "\xCB\xB6" => "\xE7\xA1\x95", + "\xCB\xB7" => "\xE6\x9C\x94", + "\xCB\xB8" => "\xE7\x83\x81", + "\xCB\xB9" => "\xE6\x96\xAF", + "\xCB\xBA" => "\xE6\x92\x95", + "\xCB\xBB" => "\xE5\x98\xB6", + "\xCB\xBC" => "\xE6\x80\x9D", + "\xCB\xBD" => "\xE7\xA7\x81", + "\xCB\xBE" => "\xE5\x8F\xB8", + "\xCB\xBF" => "\xE4\xB8\x9D", + "\xCB\xC0" => "\xE6\xAD\xBB", + "\xCB\xC1" => "\xE8\x82\x86", + "\xCB\xC2" => "\xE5\xAF\xBA", + "\xCB\xC3" => "\xE5\x97\xA3", + "\xCB\xC4" => "\xE5\x9B\x9B", + "\xCB\xC5" => "\xE4\xBC\xBA", + "\xCB\xC6" => "\xE4\xBC\xBC", + "\xCB\xC7" => "\xE9\xA5\xB2", + "\xCB\xC8" => "\xE5\xB7\xB3", + "\xCB\xC9" => "\xE6\x9D\xBE", + "\xCB\xCA" => "\xE8\x80\xB8", + "\xCB\xCB" => "\xE6\x80\x82", + "\xCB\xCC" => "\xE9\xA2\x82", + "\xCB\xCD" => "\xE9\x80\x81", + "\xCB\xCE" => "\xE5\xAE\x8B", + "\xCB\xCF" => "\xE8\xAE\xBC", + "\xCB\xD0" => "\xE8\xAF\xB5", + "\xCB\xD1" => "\xE6\x90\x9C", + "\xCB\xD2" => "\xE8\x89\x98", + "\xCB\xD3" => "\xE6\x93\x9E", + "\xCB\xD4" => "\xE5\x97\xBD", + "\xCB\xD5" => "\xE8\x8B\x8F", + "\xCB\xD6" => "\xE9\x85\xA5", + "\xCB\xD7" => "\xE4\xBF\x97", + "\xCB\xD8" => "\xE7\xB4\xA0", + "\xCB\xD9" => "\xE9\x80\x9F", + "\xCB\xDA" => "\xE7\xB2\x9F", + "\xCB\xDB" => "\xE5\x83\xB3", + "\xCB\xDC" => "\xE5\xA1\x91", + "\xCB\xDD" => "\xE6\xBA\xAF", + "\xCB\xDE" => "\xE5\xAE\xBF", + "\xCB\xDF" => "\xE8\xAF\x89", + "\xCB\xE0" => "\xE8\x82\x83", + "\xCB\xE1" => "\xE9\x85\xB8", + "\xCB\xE2" => "\xE8\x92\x9C", + "\xCB\xE3" => "\xE7\xAE\x97", + "\xCB\xE4" => "\xE8\x99\xBD", + "\xCB\xE5" => "\xE9\x9A\x8B", + "\xCB\xE6" => "\xE9\x9A\x8F", + "\xCB\xE7" => "\xE7\xBB\xA5", + "\xCB\xE8" => "\xE9\xAB\x93", + "\xCB\xE9" => "\xE7\xA2\x8E", + "\xCB\xEA" => "\xE5\xB2\x81", + "\xCB\xEB" => "\xE7\xA9\x97", + "\xCB\xEC" => "\xE9\x81\x82", + "\xCB\xED" => "\xE9\x9A\xA7", + "\xCB\xEE" => "\xE7\xA5\x9F", + "\xCB\xEF" => "\xE5\xAD\x99", + "\xCB\xF0" => "\xE6\x8D\x9F", + "\xCB\xF1" => "\xE7\xAC\x8B", + "\xCB\xF2" => "\xE8\x93\x91", + "\xCB\xF3" => "\xE6\xA2\xAD", + "\xCB\xF4" => "\xE5\x94\x86", + "\xCB\xF5" => "\xE7\xBC\xA9", + "\xCB\xF6" => "\xE7\x90\x90", + "\xCB\xF7" => "\xE7\xB4\xA2", + "\xCB\xF8" => "\xE9\x94\x81", + "\xCB\xF9" => "\xE6\x89\x80", + "\xCB\xFA" => "\xE5\xA1\x8C", + "\xCB\xFB" => "\xE4\xBB\x96", + "\xCB\xFC" => "\xE5\xAE\x83", + "\xCB\xFD" => "\xE5\xA5\xB9", + "\xCB\xFE" => "\xE5\xA1\x94", + "\xCC\xA1" => "\xE7\x8D\xAD", + "\xCC\xA2" => "\xE6\x8C\x9E", + "\xCC\xA3" => "\xE8\xB9\x8B", + "\xCC\xA4" => "\xE8\xB8\x8F", + "\xCC\xA5" => "\xE8\x83\x8E", + "\xCC\xA6" => "\xE8\x8B\x94", + "\xCC\xA7" => "\xE6\x8A\xAC", + "\xCC\xA8" => "\xE5\x8F\xB0", + "\xCC\xA9" => "\xE6\xB3\xB0", + "\xCC\xAA" => "\xE9\x85\x9E", + "\xCC\xAB" => "\xE5\xA4\xAA", + "\xCC\xAC" => "\xE6\x80\x81", + "\xCC\xAD" => "\xE6\xB1\xB0", + "\xCC\xAE" => "\xE5\x9D\x8D", + "\xCC\xAF" => "\xE6\x91\x8A", + "\xCC\xB0" => "\xE8\xB4\xAA", + "\xCC\xB1" => "\xE7\x98\xAB", + "\xCC\xB2" => "\xE6\xBB\xA9", + "\xCC\xB3" => "\xE5\x9D\x9B", + "\xCC\xB4" => "\xE6\xAA\x80", + "\xCC\xB5" => "\xE7\x97\xB0", + "\xCC\xB6" => "\xE6\xBD\xAD", + "\xCC\xB7" => "\xE8\xB0\xAD", + "\xCC\xB8" => "\xE8\xB0\x88", + "\xCC\xB9" => "\xE5\x9D\xA6", + "\xCC\xBA" => "\xE6\xAF\xAF", + "\xCC\xBB" => "\xE8\xA2\x92", + "\xCC\xBC" => "\xE7\xA2\xB3", + "\xCC\xBD" => "\xE6\x8E\xA2", + "\xCC\xBE" => "\xE5\x8F\xB9", + "\xCC\xBF" => "\xE7\x82\xAD", + "\xCC\xC0" => "\xE6\xB1\xA4", + "\xCC\xC1" => "\xE5\xA1\x98", + "\xCC\xC2" => "\xE6\x90\xAA", + "\xCC\xC3" => "\xE5\xA0\x82", + "\xCC\xC4" => "\xE6\xA3\xA0", + "\xCC\xC5" => "\xE8\x86\x9B", + "\xCC\xC6" => "\xE5\x94\x90", + "\xCC\xC7" => "\xE7\xB3\x96", + "\xCC\xC8" => "\xE5\x80\x98", + "\xCC\xC9" => "\xE8\xBA\xBA", + "\xCC\xCA" => "\xE6\xB7\x8C", + "\xCC\xCB" => "\xE8\xB6\x9F", + "\xCC\xCC" => "\xE7\x83\xAB", + "\xCC\xCD" => "\xE6\x8E\x8F", + "\xCC\xCE" => "\xE6\xB6\x9B", + "\xCC\xCF" => "\xE6\xBB\x94", + "\xCC\xD0" => "\xE7\xBB\xA6", + "\xCC\xD1" => "\xE8\x90\x84", + "\xCC\xD2" => "\xE6\xA1\x83", + "\xCC\xD3" => "\xE9\x80\x83", + "\xCC\xD4" => "\xE6\xB7\x98", + "\xCC\xD5" => "\xE9\x99\xB6", + "\xCC\xD6" => "\xE8\xAE\xA8", + "\xCC\xD7" => "\xE5\xA5\x97", + "\xCC\xD8" => "\xE7\x89\xB9", + "\xCC\xD9" => "\xE8\x97\xA4", + "\xCC\xDA" => "\xE8\x85\xBE", + "\xCC\xDB" => "\xE7\x96\xBC", + "\xCC\xDC" => "\xE8\xAA\x8A", + "\xCC\xDD" => "\xE6\xA2\xAF", + "\xCC\xDE" => "\xE5\x89\x94", + "\xCC\xDF" => "\xE8\xB8\xA2", + "\xCC\xE0" => "\xE9\x94\x91", + "\xCC\xE1" => "\xE6\x8F\x90", + "\xCC\xE2" => "\xE9\xA2\x98", + "\xCC\xE3" => "\xE8\xB9\x84", + "\xCC\xE4" => "\xE5\x95\xBC", + "\xCC\xE5" => "\xE4\xBD\x93", + "\xCC\xE6" => "\xE6\x9B\xBF", + "\xCC\xE7" => "\xE5\x9A\x8F", + "\xCC\xE8" => "\xE6\x83\x95", + "\xCC\xE9" => "\xE6\xB6\x95", + "\xCC\xEA" => "\xE5\x89\x83", + "\xCC\xEB" => "\xE5\xB1\x89", + "\xCC\xEC" => "\xE5\xA4\xA9", + "\xCC\xED" => "\xE6\xB7\xBB", + "\xCC\xEE" => "\xE5\xA1\xAB", + "\xCC\xEF" => "\xE7\x94\xB0", + "\xCC\xF0" => "\xE7\x94\x9C", + "\xCC\xF1" => "\xE6\x81\xAC", + "\xCC\xF2" => "\xE8\x88\x94", + "\xCC\xF3" => "\xE8\x85\x86", + "\xCC\xF4" => "\xE6\x8C\x91", + "\xCC\xF5" => "\xE6\x9D\xA1", + "\xCC\xF6" => "\xE8\xBF\xA2", + "\xCC\xF7" => "\xE7\x9C\xBA", + "\xCC\xF8" => "\xE8\xB7\xB3", + "\xCC\xF9" => "\xE8\xB4\xB4", + "\xCC\xFA" => "\xE9\x93\x81", + "\xCC\xFB" => "\xE5\xB8\x96", + "\xCC\xFC" => "\xE5\x8E\x85", + "\xCC\xFD" => "\xE5\x90\xAC", + "\xCC\xFE" => "\xE7\x83\x83", + "\xCD\xA1" => "\xE6\xB1\x80", + "\xCD\xA2" => "\xE5\xBB\xB7", + "\xCD\xA3" => "\xE5\x81\x9C", + "\xCD\xA4" => "\xE4\xBA\xAD", + "\xCD\xA5" => "\xE5\xBA\xAD", + "\xCD\xA6" => "\xE6\x8C\xBA", + "\xCD\xA7" => "\xE8\x89\x87", + "\xCD\xA8" => "\xE9\x80\x9A", + "\xCD\xA9" => "\xE6\xA1\x90", + "\xCD\xAA" => "\xE9\x85\xAE", + "\xCD\xAB" => "\xE7\x9E\xB3", + "\xCD\xAC" => "\xE5\x90\x8C", + "\xCD\xAD" => "\xE9\x93\x9C", + "\xCD\xAE" => "\xE5\xBD\xA4", + "\xCD\xAF" => "\xE7\xAB\xA5", + "\xCD\xB0" => "\xE6\xA1\xB6", + "\xCD\xB1" => "\xE6\x8D\x85", + "\xCD\xB2" => "\xE7\xAD\x92", + "\xCD\xB3" => "\xE7\xBB\x9F", + "\xCD\xB4" => "\xE7\x97\x9B", + "\xCD\xB5" => "\xE5\x81\xB7", + "\xCD\xB6" => "\xE6\x8A\x95", + "\xCD\xB7" => "\xE5\xA4\xB4", + "\xCD\xB8" => "\xE9\x80\x8F", + "\xCD\xB9" => "\xE5\x87\xB8", + "\xCD\xBA" => "\xE7\xA7\x83", + "\xCD\xBB" => "\xE7\xAA\x81", + "\xCD\xBC" => "\xE5\x9B\xBE", + "\xCD\xBD" => "\xE5\xBE\x92", + "\xCD\xBE" => "\xE9\x80\x94", + "\xCD\xBF" => "\xE6\xB6\x82", + "\xCD\xC0" => "\xE5\xB1\xA0", + "\xCD\xC1" => "\xE5\x9C\x9F", + "\xCD\xC2" => "\xE5\x90\x90", + "\xCD\xC3" => "\xE5\x85\x94", + "\xCD\xC4" => "\xE6\xB9\x8D", + "\xCD\xC5" => "\xE5\x9B\xA2", + "\xCD\xC6" => "\xE6\x8E\xA8", + "\xCD\xC7" => "\xE9\xA2\x93", + "\xCD\xC8" => "\xE8\x85\xBF", + "\xCD\xC9" => "\xE8\x9C\x95", + "\xCD\xCA" => "\xE8\xA4\xAA", + "\xCD\xCB" => "\xE9\x80\x80", + "\xCD\xCC" => "\xE5\x90\x9E", + "\xCD\xCD" => "\xE5\xB1\xAF", + "\xCD\xCE" => "\xE8\x87\x80", + "\xCD\xCF" => "\xE6\x8B\x96", + "\xCD\xD0" => "\xE6\x89\x98", + "\xCD\xD1" => "\xE8\x84\xB1", + "\xCD\xD2" => "\xE9\xB8\xB5", + "\xCD\xD3" => "\xE9\x99\x80", + "\xCD\xD4" => "\xE9\xA9\xAE", + "\xCD\xD5" => "\xE9\xA9\xBC", + "\xCD\xD6" => "\xE6\xA4\xAD", + "\xCD\xD7" => "\xE5\xA6\xA5", + "\xCD\xD8" => "\xE6\x8B\x93", + "\xCD\xD9" => "\xE5\x94\xBE", + "\xCD\xDA" => "\xE6\x8C\x96", + "\xCD\xDB" => "\xE5\x93\x87", + "\xCD\xDC" => "\xE8\x9B\x99", + "\xCD\xDD" => "\xE6\xB4\xBC", + "\xCD\xDE" => "\xE5\xA8\x83", + "\xCD\xDF" => "\xE7\x93\xA6", + "\xCD\xE0" => "\xE8\xA2\x9C", + "\xCD\xE1" => "\xE6\xAD\xAA", + "\xCD\xE2" => "\xE5\xA4\x96", + "\xCD\xE3" => "\xE8\xB1\x8C", + "\xCD\xE4" => "\xE5\xBC\xAF", + "\xCD\xE5" => "\xE6\xB9\xBE", + "\xCD\xE6" => "\xE7\x8E\xA9", + "\xCD\xE7" => "\xE9\xA1\xBD", + "\xCD\xE8" => "\xE4\xB8\xB8", + "\xCD\xE9" => "\xE7\x83\xB7", + "\xCD\xEA" => "\xE5\xAE\x8C", + "\xCD\xEB" => "\xE7\xA2\x97", + "\xCD\xEC" => "\xE6\x8C\xBD", + "\xCD\xED" => "\xE6\x99\x9A", + "\xCD\xEE" => "\xE7\x9A\x96", + "\xCD\xEF" => "\xE6\x83\x8B", + "\xCD\xF0" => "\xE5\xAE\x9B", + "\xCD\xF1" => "\xE5\xA9\x89", + "\xCD\xF2" => "\xE4\xB8\x87", + "\xCD\xF3" => "\xE8\x85\x95", + "\xCD\xF4" => "\xE6\xB1\xAA", + "\xCD\xF5" => "\xE7\x8E\x8B", + "\xCD\xF6" => "\xE4\xBA\xA1", + "\xCD\xF7" => "\xE6\x9E\x89", + "\xCD\xF8" => "\xE7\xBD\x91", + "\xCD\xF9" => "\xE5\xBE\x80", + "\xCD\xFA" => "\xE6\x97\xBA", + "\xCD\xFB" => "\xE6\x9C\x9B", + "\xCD\xFC" => "\xE5\xBF\x98", + "\xCD\xFD" => "\xE5\xA6\x84", + "\xCD\xFE" => "\xE5\xA8\x81", + "\xCE\xA1" => "\xE5\xB7\x8D", + "\xCE\xA2" => "\xE5\xBE\xAE", + "\xCE\xA3" => "\xE5\x8D\xB1", + "\xCE\xA4" => "\xE9\x9F\xA6", + "\xCE\xA5" => "\xE8\xBF\x9D", + "\xCE\xA6" => "\xE6\xA1\x85", + "\xCE\xA7" => "\xE5\x9B\xB4", + "\xCE\xA8" => "\xE5\x94\xAF", + "\xCE\xA9" => "\xE6\x83\x9F", + "\xCE\xAA" => "\xE4\xB8\xBA", + "\xCE\xAB" => "\xE6\xBD\x8D", + "\xCE\xAC" => "\xE7\xBB\xB4", + "\xCE\xAD" => "\xE8\x8B\x87", + "\xCE\xAE" => "\xE8\x90\x8E", + "\xCE\xAF" => "\xE5\xA7\x94", + "\xCE\xB0" => "\xE4\xBC\x9F", + "\xCE\xB1" => "\xE4\xBC\xAA", + "\xCE\xB2" => "\xE5\xB0\xBE", + "\xCE\xB3" => "\xE7\xBA\xAC", + "\xCE\xB4" => "\xE6\x9C\xAA", + "\xCE\xB5" => "\xE8\x94\x9A", + "\xCE\xB6" => "\xE5\x91\xB3", + "\xCE\xB7" => "\xE7\x95\x8F", + "\xCE\xB8" => "\xE8\x83\x83", + "\xCE\xB9" => "\xE5\x96\x82", + "\xCE\xBA" => "\xE9\xAD\x8F", + "\xCE\xBB" => "\xE4\xBD\x8D", + "\xCE\xBC" => "\xE6\xB8\xAD", + "\xCE\xBD" => "\xE8\xB0\x93", + "\xCE\xBE" => "\xE5\xB0\x89", + "\xCE\xBF" => "\xE6\x85\xB0", + "\xCE\xC0" => "\xE5\x8D\xAB", + "\xCE\xC1" => "\xE7\x98\x9F", + "\xCE\xC2" => "\xE6\xB8\xA9", + "\xCE\xC3" => "\xE8\x9A\x8A", + "\xCE\xC4" => "\xE6\x96\x87", + "\xCE\xC5" => "\xE9\x97\xBB", + "\xCE\xC6" => "\xE7\xBA\xB9", + "\xCE\xC7" => "\xE5\x90\xBB", + "\xCE\xC8" => "\xE7\xA8\xB3", + "\xCE\xC9" => "\xE7\xB4\x8A", + "\xCE\xCA" => "\xE9\x97\xAE", + "\xCE\xCB" => "\xE5\x97\xA1", + "\xCE\xCC" => "\xE7\xBF\x81", + "\xCE\xCD" => "\xE7\x93\xAE", + "\xCE\xCE" => "\xE6\x8C\x9D", + "\xCE\xCF" => "\xE8\x9C\x97", + "\xCE\xD0" => "\xE6\xB6\xA1", + "\xCE\xD1" => "\xE7\xAA\x9D", + "\xCE\xD2" => "\xE6\x88\x91", + "\xCE\xD3" => "\xE6\x96\xA1", + "\xCE\xD4" => "\xE5\x8D\xA7", + "\xCE\xD5" => "\xE6\x8F\xA1", + "\xCE\xD6" => "\xE6\xB2\x83", + "\xCE\xD7" => "\xE5\xB7\xAB", + "\xCE\xD8" => "\xE5\x91\x9C", + "\xCE\xD9" => "\xE9\x92\xA8", + "\xCE\xDA" => "\xE4\xB9\x8C", + "\xCE\xDB" => "\xE6\xB1\xA1", + "\xCE\xDC" => "\xE8\xAF\xAC", + "\xCE\xDD" => "\xE5\xB1\x8B", + "\xCE\xDE" => "\xE6\x97\xA0", + "\xCE\xDF" => "\xE8\x8A\x9C", + "\xCE\xE0" => "\xE6\xA2\xA7", + "\xCE\xE1" => "\xE5\x90\xBE", + "\xCE\xE2" => "\xE5\x90\xB4", + "\xCE\xE3" => "\xE6\xAF\x8B", + "\xCE\xE4" => "\xE6\xAD\xA6", + "\xCE\xE5" => "\xE4\xBA\x94", + "\xCE\xE6" => "\xE6\x8D\x82", + "\xCE\xE7" => "\xE5\x8D\x88", + "\xCE\xE8" => "\xE8\x88\x9E", + "\xCE\xE9" => "\xE4\xBC\x8D", + "\xCE\xEA" => "\xE4\xBE\xAE", + "\xCE\xEB" => "\xE5\x9D\x9E", + "\xCE\xEC" => "\xE6\x88\x8A", + "\xCE\xED" => "\xE9\x9B\xBE", + "\xCE\xEE" => "\xE6\x99\xA4", + "\xCE\xEF" => "\xE7\x89\xA9", + "\xCE\xF0" => "\xE5\x8B\xBF", + "\xCE\xF1" => "\xE5\x8A\xA1", + "\xCE\xF2" => "\xE6\x82\x9F", + "\xCE\xF3" => "\xE8\xAF\xAF", + "\xCE\xF4" => "\xE6\x98\x94", + "\xCE\xF5" => "\xE7\x86\x99", + "\xCE\xF6" => "\xE6\x9E\x90", + "\xCE\xF7" => "\xE8\xA5\xBF", + "\xCE\xF8" => "\xE7\xA1\x92", + "\xCE\xF9" => "\xE7\x9F\xBD", + "\xCE\xFA" => "\xE6\x99\xB0", + "\xCE\xFB" => "\xE5\x98\xBB", + "\xCE\xFC" => "\xE5\x90\xB8", + "\xCE\xFD" => "\xE9\x94\xA1", + "\xCE\xFE" => "\xE7\x89\xBA", + "\xCF\xA1" => "\xE7\xA8\x80", + "\xCF\xA2" => "\xE6\x81\xAF", + "\xCF\xA3" => "\xE5\xB8\x8C", + "\xCF\xA4" => "\xE6\x82\x89", + "\xCF\xA5" => "\xE8\x86\x9D", + "\xCF\xA6" => "\xE5\xA4\x95", + "\xCF\xA7" => "\xE6\x83\x9C", + "\xCF\xA8" => "\xE7\x86\x84", + "\xCF\xA9" => "\xE7\x83\xAF", + "\xCF\xAA" => "\xE6\xBA\xAA", + "\xCF\xAB" => "\xE6\xB1\x90", + "\xCF\xAC" => "\xE7\x8A\x80", + "\xCF\xAD" => "\xE6\xAA\x84", + "\xCF\xAE" => "\xE8\xA2\xAD", + "\xCF\xAF" => "\xE5\xB8\xAD", + "\xCF\xB0" => "\xE4\xB9\xA0", + "\xCF\xB1" => "\xE5\xAA\xB3", + "\xCF\xB2" => "\xE5\x96\x9C", + "\xCF\xB3" => "\xE9\x93\xA3", + "\xCF\xB4" => "\xE6\xB4\x97", + "\xCF\xB5" => "\xE7\xB3\xBB", + "\xCF\xB6" => "\xE9\x9A\x99", + "\xCF\xB7" => "\xE6\x88\x8F", + "\xCF\xB8" => "\xE7\xBB\x86", + "\xCF\xB9" => "\xE7\x9E\x8E", + "\xCF\xBA" => "\xE8\x99\xBE", + "\xCF\xBB" => "\xE5\x8C\xA3", + "\xCF\xBC" => "\xE9\x9C\x9E", + "\xCF\xBD" => "\xE8\xBE\x96", + "\xCF\xBE" => "\xE6\x9A\x87", + "\xCF\xBF" => "\xE5\xB3\xA1", + "\xCF\xC0" => "\xE4\xBE\xA0", + "\xCF\xC1" => "\xE7\x8B\xAD", + "\xCF\xC2" => "\xE4\xB8\x8B", + "\xCF\xC3" => "\xE5\x8E\xA6", + "\xCF\xC4" => "\xE5\xA4\x8F", + "\xCF\xC5" => "\xE5\x90\x93", + "\xCF\xC6" => "\xE6\x8E\x80", + "\xCF\xC7" => "\xE9\x94\xA8", + "\xCF\xC8" => "\xE5\x85\x88", + "\xCF\xC9" => "\xE4\xBB\x99", + "\xCF\xCA" => "\xE9\xB2\x9C", + "\xCF\xCB" => "\xE7\xBA\xA4", + "\xCF\xCC" => "\xE5\x92\xB8", + "\xCF\xCD" => "\xE8\xB4\xA4", + "\xCF\xCE" => "\xE8\xA1\x94", + "\xCF\xCF" => "\xE8\x88\xB7", + "\xCF\xD0" => "\xE9\x97\xB2", + "\xCF\xD1" => "\xE6\xB6\x8E", + "\xCF\xD2" => "\xE5\xBC\xA6", + "\xCF\xD3" => "\xE5\xAB\x8C", + "\xCF\xD4" => "\xE6\x98\xBE", + "\xCF\xD5" => "\xE9\x99\xA9", + "\xCF\xD6" => "\xE7\x8E\xB0", + "\xCF\xD7" => "\xE7\x8C\xAE", + "\xCF\xD8" => "\xE5\x8E\xBF", + "\xCF\xD9" => "\xE8\x85\xBA", + "\xCF\xDA" => "\xE9\xA6\x85", + "\xCF\xDB" => "\xE7\xBE\xA1", + "\xCF\xDC" => "\xE5\xAE\xAA", + "\xCF\xDD" => "\xE9\x99\xB7", + "\xCF\xDE" => "\xE9\x99\x90", + "\xCF\xDF" => "\xE7\xBA\xBF", + "\xCF\xE0" => "\xE7\x9B\xB8", + "\xCF\xE1" => "\xE5\x8E\xA2", + "\xCF\xE2" => "\xE9\x95\xB6", + "\xCF\xE3" => "\xE9\xA6\x99", + "\xCF\xE4" => "\xE7\xAE\xB1", + "\xCF\xE5" => "\xE8\xA5\x84", + "\xCF\xE6" => "\xE6\xB9\x98", + "\xCF\xE7" => "\xE4\xB9\xA1", + "\xCF\xE8" => "\xE7\xBF\x94", + "\xCF\xE9" => "\xE7\xA5\xA5", + "\xCF\xEA" => "\xE8\xAF\xA6", + "\xCF\xEB" => "\xE6\x83\xB3", + "\xCF\xEC" => "\xE5\x93\x8D", + "\xCF\xED" => "\xE4\xBA\xAB", + "\xCF\xEE" => "\xE9\xA1\xB9", + "\xCF\xEF" => "\xE5\xB7\xB7", + "\xCF\xF0" => "\xE6\xA9\xA1", + "\xCF\xF1" => "\xE5\x83\x8F", + "\xCF\xF2" => "\xE5\x90\x91", + "\xCF\xF3" => "\xE8\xB1\xA1", + "\xCF\xF4" => "\xE8\x90\xA7", + "\xCF\xF5" => "\xE7\xA1\x9D", + "\xCF\xF6" => "\xE9\x9C\x84", + "\xCF\xF7" => "\xE5\x89\x8A", + "\xCF\xF8" => "\xE5\x93\xAE", + "\xCF\xF9" => "\xE5\x9A\xA3", + "\xCF\xFA" => "\xE9\x94\x80", + "\xCF\xFB" => "\xE6\xB6\x88", + "\xCF\xFC" => "\xE5\xAE\xB5", + "\xCF\xFD" => "\xE6\xB7\x86", + "\xCF\xFE" => "\xE6\x99\x93", + "\xD0\xA1" => "\xE5\xB0\x8F", + "\xD0\xA2" => "\xE5\xAD\x9D", + "\xD0\xA3" => "\xE6\xA0\xA1", + "\xD0\xA4" => "\xE8\x82\x96", + "\xD0\xA5" => "\xE5\x95\xB8", + "\xD0\xA6" => "\xE7\xAC\x91", + "\xD0\xA7" => "\xE6\x95\x88", + "\xD0\xA8" => "\xE6\xA5\x94", + "\xD0\xA9" => "\xE4\xBA\x9B", + "\xD0\xAA" => "\xE6\xAD\x87", + "\xD0\xAB" => "\xE8\x9D\x8E", + "\xD0\xAC" => "\xE9\x9E\x8B", + "\xD0\xAD" => "\xE5\x8D\x8F", + "\xD0\xAE" => "\xE6\x8C\x9F", + "\xD0\xAF" => "\xE6\x90\xBA", + "\xD0\xB0" => "\xE9\x82\xAA", + "\xD0\xB1" => "\xE6\x96\x9C", + "\xD0\xB2" => "\xE8\x83\x81", + "\xD0\xB3" => "\xE8\xB0\x90", + "\xD0\xB4" => "\xE5\x86\x99", + "\xD0\xB5" => "\xE6\xA2\xB0", + "\xD0\xB6" => "\xE5\x8D\xB8", + "\xD0\xB7" => "\xE8\x9F\xB9", + "\xD0\xB8" => "\xE6\x87\x88", + "\xD0\xB9" => "\xE6\xB3\x84", + "\xD0\xBA" => "\xE6\xB3\xBB", + "\xD0\xBB" => "\xE8\xB0\xA2", + "\xD0\xBC" => "\xE5\xB1\x91", + "\xD0\xBD" => "\xE8\x96\xAA", + "\xD0\xBE" => "\xE8\x8A\xAF", + "\xD0\xBF" => "\xE9\x94\x8C", + "\xD0\xC0" => "\xE6\xAC\xA3", + "\xD0\xC1" => "\xE8\xBE\x9B", + "\xD0\xC2" => "\xE6\x96\xB0", + "\xD0\xC3" => "\xE5\xBF\xBB", + "\xD0\xC4" => "\xE5\xBF\x83", + "\xD0\xC5" => "\xE4\xBF\xA1", + "\xD0\xC6" => "\xE8\xA1\x85", + "\xD0\xC7" => "\xE6\x98\x9F", + "\xD0\xC8" => "\xE8\x85\xA5", + "\xD0\xC9" => "\xE7\x8C\xA9", + "\xD0\xCA" => "\xE6\x83\xBA", + "\xD0\xCB" => "\xE5\x85\xB4", + "\xD0\xCC" => "\xE5\x88\x91", + "\xD0\xCD" => "\xE5\x9E\x8B", + "\xD0\xCE" => "\xE5\xBD\xA2", + "\xD0\xCF" => "\xE9\x82\xA2", + "\xD0\xD0" => "\xE8\xA1\x8C", + "\xD0\xD1" => "\xE9\x86\x92", + "\xD0\xD2" => "\xE5\xB9\xB8", + "\xD0\xD3" => "\xE6\x9D\x8F", + "\xD0\xD4" => "\xE6\x80\xA7", + "\xD0\xD5" => "\xE5\xA7\x93", + "\xD0\xD6" => "\xE5\x85\x84", + "\xD0\xD7" => "\xE5\x87\xB6", + "\xD0\xD8" => "\xE8\x83\xB8", + "\xD0\xD9" => "\xE5\x8C\x88", + "\xD0\xDA" => "\xE6\xB1\xB9", + "\xD0\xDB" => "\xE9\x9B\x84", + "\xD0\xDC" => "\xE7\x86\x8A", + "\xD0\xDD" => "\xE4\xBC\x91", + "\xD0\xDE" => "\xE4\xBF\xAE", + "\xD0\xDF" => "\xE7\xBE\x9E", + "\xD0\xE0" => "\xE6\x9C\xBD", + "\xD0\xE1" => "\xE5\x97\x85", + "\xD0\xE2" => "\xE9\x94\x88", + "\xD0\xE3" => "\xE7\xA7\x80", + "\xD0\xE4" => "\xE8\xA2\x96", + "\xD0\xE5" => "\xE7\xBB\xA3", + "\xD0\xE6" => "\xE5\xA2\x9F", + "\xD0\xE7" => "\xE6\x88\x8C", + "\xD0\xE8" => "\xE9\x9C\x80", + "\xD0\xE9" => "\xE8\x99\x9A", + "\xD0\xEA" => "\xE5\x98\x98", + "\xD0\xEB" => "\xE9\xA1\xBB", + "\xD0\xEC" => "\xE5\xBE\x90", + "\xD0\xED" => "\xE8\xAE\xB8", + "\xD0\xEE" => "\xE8\x93\x84", + "\xD0\xEF" => "\xE9\x85\x97", + "\xD0\xF0" => "\xE5\x8F\x99", + "\xD0\xF1" => "\xE6\x97\xAD", + "\xD0\xF2" => "\xE5\xBA\x8F", + "\xD0\xF3" => "\xE7\x95\x9C", + "\xD0\xF4" => "\xE6\x81\xA4", + "\xD0\xF5" => "\xE7\xB5\xAE", + "\xD0\xF6" => "\xE5\xA9\xBF", + "\xD0\xF7" => "\xE7\xBB\xAA", + "\xD0\xF8" => "\xE7\xBB\xAD", + "\xD0\xF9" => "\xE8\xBD\xA9", + "\xD0\xFA" => "\xE5\x96\xA7", + "\xD0\xFB" => "\xE5\xAE\xA3", + "\xD0\xFC" => "\xE6\x82\xAC", + "\xD0\xFD" => "\xE6\x97\x8B", + "\xD0\xFE" => "\xE7\x8E\x84", + "\xD1\xA1" => "\xE9\x80\x89", + "\xD1\xA2" => "\xE7\x99\xA3", + "\xD1\xA3" => "\xE7\x9C\xA9", + "\xD1\xA4" => "\xE7\xBB\x9A", + "\xD1\xA5" => "\xE9\x9D\xB4", + "\xD1\xA6" => "\xE8\x96\x9B", + "\xD1\xA7" => "\xE5\xAD\xA6", + "\xD1\xA8" => "\xE7\xA9\xB4", + "\xD1\xA9" => "\xE9\x9B\xAA", + "\xD1\xAA" => "\xE8\xA1\x80", + "\xD1\xAB" => "\xE5\x8B\x8B", + "\xD1\xAC" => "\xE7\x86\x8F", + "\xD1\xAD" => "\xE5\xBE\xAA", + "\xD1\xAE" => "\xE6\x97\xAC", + "\xD1\xAF" => "\xE8\xAF\xA2", + "\xD1\xB0" => "\xE5\xAF\xBB", + "\xD1\xB1" => "\xE9\xA9\xAF", + "\xD1\xB2" => "\xE5\xB7\xA1", + "\xD1\xB3" => "\xE6\xAE\x89", + "\xD1\xB4" => "\xE6\xB1\x9B", + "\xD1\xB5" => "\xE8\xAE\xAD", + "\xD1\xB6" => "\xE8\xAE\xAF", + "\xD1\xB7" => "\xE9\x80\x8A", + "\xD1\xB8" => "\xE8\xBF\x85", + "\xD1\xB9" => "\xE5\x8E\x8B", + "\xD1\xBA" => "\xE6\x8A\xBC", + "\xD1\xBB" => "\xE9\xB8\xA6", + "\xD1\xBC" => "\xE9\xB8\xAD", + "\xD1\xBD" => "\xE5\x91\x80", + "\xD1\xBE" => "\xE4\xB8\xAB", + "\xD1\xBF" => "\xE8\x8A\xBD", + "\xD1\xC0" => "\xE7\x89\x99", + "\xD1\xC1" => "\xE8\x9A\x9C", + "\xD1\xC2" => "\xE5\xB4\x96", + "\xD1\xC3" => "\xE8\xA1\x99", + "\xD1\xC4" => "\xE6\xB6\xAF", + "\xD1\xC5" => "\xE9\x9B\x85", + "\xD1\xC6" => "\xE5\x93\x91", + "\xD1\xC7" => "\xE4\xBA\x9A", + "\xD1\xC8" => "\xE8\xAE\xB6", + "\xD1\xC9" => "\xE7\x84\x89", + "\xD1\xCA" => "\xE5\x92\xBD", + "\xD1\xCB" => "\xE9\x98\x89", + "\xD1\xCC" => "\xE7\x83\x9F", + "\xD1\xCD" => "\xE6\xB7\xB9", + "\xD1\xCE" => "\xE7\x9B\x90", + "\xD1\xCF" => "\xE4\xB8\xA5", + "\xD1\xD0" => "\xE7\xA0\x94", + "\xD1\xD1" => "\xE8\x9C\x92", + "\xD1\xD2" => "\xE5\xB2\xA9", + "\xD1\xD3" => "\xE5\xBB\xB6", + "\xD1\xD4" => "\xE8\xA8\x80", + "\xD1\xD5" => "\xE9\xA2\x9C", + "\xD1\xD6" => "\xE9\x98\x8E", + "\xD1\xD7" => "\xE7\x82\x8E", + "\xD1\xD8" => "\xE6\xB2\xBF", + "\xD1\xD9" => "\xE5\xA5\x84", + "\xD1\xDA" => "\xE6\x8E\xA9", + "\xD1\xDB" => "\xE7\x9C\xBC", + "\xD1\xDC" => "\xE8\xA1\x8D", + "\xD1\xDD" => "\xE6\xBC\x94", + "\xD1\xDE" => "\xE8\x89\xB3", + "\xD1\xDF" => "\xE5\xA0\xB0", + "\xD1\xE0" => "\xE7\x87\x95", + "\xD1\xE1" => "\xE5\x8E\x8C", + "\xD1\xE2" => "\xE7\xA0\x9A", + "\xD1\xE3" => "\xE9\x9B\x81", + "\xD1\xE4" => "\xE5\x94\x81", + "\xD1\xE5" => "\xE5\xBD\xA6", + "\xD1\xE6" => "\xE7\x84\xB0", + "\xD1\xE7" => "\xE5\xAE\xB4", + "\xD1\xE8" => "\xE8\xB0\x9A", + "\xD1\xE9" => "\xE9\xAA\x8C", + "\xD1\xEA" => "\xE6\xAE\x83", + "\xD1\xEB" => "\xE5\xA4\xAE", + "\xD1\xEC" => "\xE9\xB8\xAF", + "\xD1\xED" => "\xE7\xA7\xA7", + "\xD1\xEE" => "\xE6\x9D\xA8", + "\xD1\xEF" => "\xE6\x89\xAC", + "\xD1\xF0" => "\xE4\xBD\xAF", + "\xD1\xF1" => "\xE7\x96\xA1", + "\xD1\xF2" => "\xE7\xBE\x8A", + "\xD1\xF3" => "\xE6\xB4\x8B", + "\xD1\xF4" => "\xE9\x98\xB3", + "\xD1\xF5" => "\xE6\xB0\xA7", + "\xD1\xF6" => "\xE4\xBB\xB0", + "\xD1\xF7" => "\xE7\x97\x92", + "\xD1\xF8" => "\xE5\x85\xBB", + "\xD1\xF9" => "\xE6\xA0\xB7", + "\xD1\xFA" => "\xE6\xBC\xBE", + "\xD1\xFB" => "\xE9\x82\x80", + "\xD1\xFC" => "\xE8\x85\xB0", + "\xD1\xFD" => "\xE5\xA6\x96", + "\xD1\xFE" => "\xE7\x91\xB6", + "\xD2\xA1" => "\xE6\x91\x87", + "\xD2\xA2" => "\xE5\xB0\xA7", + "\xD2\xA3" => "\xE9\x81\xA5", + "\xD2\xA4" => "\xE7\xAA\x91", + "\xD2\xA5" => "\xE8\xB0\xA3", + "\xD2\xA6" => "\xE5\xA7\x9A", + "\xD2\xA7" => "\xE5\x92\xAC", + "\xD2\xA8" => "\xE8\x88\x80", + "\xD2\xA9" => "\xE8\x8D\xAF", + "\xD2\xAA" => "\xE8\xA6\x81", + "\xD2\xAB" => "\xE8\x80\x80", + "\xD2\xAC" => "\xE6\xA4\xB0", + "\xD2\xAD" => "\xE5\x99\x8E", + "\xD2\xAE" => "\xE8\x80\xB6", + "\xD2\xAF" => "\xE7\x88\xB7", + "\xD2\xB0" => "\xE9\x87\x8E", + "\xD2\xB1" => "\xE5\x86\xB6", + "\xD2\xB2" => "\xE4\xB9\x9F", + "\xD2\xB3" => "\xE9\xA1\xB5", + "\xD2\xB4" => "\xE6\x8E\x96", + "\xD2\xB5" => "\xE4\xB8\x9A", + "\xD2\xB6" => "\xE5\x8F\xB6", + "\xD2\xB7" => "\xE6\x9B\xB3", + "\xD2\xB8" => "\xE8\x85\x8B", + "\xD2\xB9" => "\xE5\xA4\x9C", + "\xD2\xBA" => "\xE6\xB6\xB2", + "\xD2\xBB" => "\xE4\xB8\x80", + "\xD2\xBC" => "\xE5\xA3\xB9", + "\xD2\xBD" => "\xE5\x8C\xBB", + "\xD2\xBE" => "\xE6\x8F\x96", + "\xD2\xBF" => "\xE9\x93\xB1", + "\xD2\xC0" => "\xE4\xBE\x9D", + "\xD2\xC1" => "\xE4\xBC\x8A", + "\xD2\xC2" => "\xE8\xA1\xA3", + "\xD2\xC3" => "\xE9\xA2\x90", + "\xD2\xC4" => "\xE5\xA4\xB7", + "\xD2\xC5" => "\xE9\x81\x97", + "\xD2\xC6" => "\xE7\xA7\xBB", + "\xD2\xC7" => "\xE4\xBB\xAA", + "\xD2\xC8" => "\xE8\x83\xB0", + "\xD2\xC9" => "\xE7\x96\x91", + "\xD2\xCA" => "\xE6\xB2\x82", + "\xD2\xCB" => "\xE5\xAE\x9C", + "\xD2\xCC" => "\xE5\xA7\xA8", + "\xD2\xCD" => "\xE5\xBD\x9D", + "\xD2\xCE" => "\xE6\xA4\x85", + "\xD2\xCF" => "\xE8\x9A\x81", + "\xD2\xD0" => "\xE5\x80\x9A", + "\xD2\xD1" => "\xE5\xB7\xB2", + "\xD2\xD2" => "\xE4\xB9\x99", + "\xD2\xD3" => "\xE7\x9F\xA3", + "\xD2\xD4" => "\xE4\xBB\xA5", + "\xD2\xD5" => "\xE8\x89\xBA", + "\xD2\xD6" => "\xE6\x8A\x91", + "\xD2\xD7" => "\xE6\x98\x93", + "\xD2\xD8" => "\xE9\x82\x91", + "\xD2\xD9" => "\xE5\xB1\xB9", + "\xD2\xDA" => "\xE4\xBA\xBF", + "\xD2\xDB" => "\xE5\xBD\xB9", + "\xD2\xDC" => "\xE8\x87\x86", + "\xD2\xDD" => "\xE9\x80\xB8", + "\xD2\xDE" => "\xE8\x82\x84", + "\xD2\xDF" => "\xE7\x96\xAB", + "\xD2\xE0" => "\xE4\xBA\xA6", + "\xD2\xE1" => "\xE8\xA3\x94", + "\xD2\xE2" => "\xE6\x84\x8F", + "\xD2\xE3" => "\xE6\xAF\x85", + "\xD2\xE4" => "\xE5\xBF\x86", + "\xD2\xE5" => "\xE4\xB9\x89", + "\xD2\xE6" => "\xE7\x9B\x8A", + "\xD2\xE7" => "\xE6\xBA\xA2", + "\xD2\xE8" => "\xE8\xAF\xA3", + "\xD2\xE9" => "\xE8\xAE\xAE", + "\xD2\xEA" => "\xE8\xB0\x8A", + "\xD2\xEB" => "\xE8\xAF\x91", + "\xD2\xEC" => "\xE5\xBC\x82", + "\xD2\xED" => "\xE7\xBF\xBC", + "\xD2\xEE" => "\xE7\xBF\x8C", + "\xD2\xEF" => "\xE7\xBB\x8E", + "\xD2\xF0" => "\xE8\x8C\xB5", + "\xD2\xF1" => "\xE8\x8D\xAB", + "\xD2\xF2" => "\xE5\x9B\xA0", + "\xD2\xF3" => "\xE6\xAE\xB7", + "\xD2\xF4" => "\xE9\x9F\xB3", + "\xD2\xF5" => "\xE9\x98\xB4", + "\xD2\xF6" => "\xE5\xA7\xBB", + "\xD2\xF7" => "\xE5\x90\x9F", + "\xD2\xF8" => "\xE9\x93\xB6", + "\xD2\xF9" => "\xE6\xB7\xAB", + "\xD2\xFA" => "\xE5\xAF\x85", + "\xD2\xFB" => "\xE9\xA5\xAE", + "\xD2\xFC" => "\xE5\xB0\xB9", + "\xD2\xFD" => "\xE5\xBC\x95", + "\xD2\xFE" => "\xE9\x9A\x90", + "\xD3\xA1" => "\xE5\x8D\xB0", + "\xD3\xA2" => "\xE8\x8B\xB1", + "\xD3\xA3" => "\xE6\xA8\xB1", + "\xD3\xA4" => "\xE5\xA9\xB4", + "\xD3\xA5" => "\xE9\xB9\xB0", + "\xD3\xA6" => "\xE5\xBA\x94", + "\xD3\xA7" => "\xE7\xBC\xA8", + "\xD3\xA8" => "\xE8\x8E\xB9", + "\xD3\xA9" => "\xE8\x90\xA4", + "\xD3\xAA" => "\xE8\x90\xA5", + "\xD3\xAB" => "\xE8\x8D\xA7", + "\xD3\xAC" => "\xE8\x9D\x87", + "\xD3\xAD" => "\xE8\xBF\x8E", + "\xD3\xAE" => "\xE8\xB5\xA2", + "\xD3\xAF" => "\xE7\x9B\x88", + "\xD3\xB0" => "\xE5\xBD\xB1", + "\xD3\xB1" => "\xE9\xA2\x96", + "\xD3\xB2" => "\xE7\xA1\xAC", + "\xD3\xB3" => "\xE6\x98\xA0", + "\xD3\xB4" => "\xE5\x93\x9F", + "\xD3\xB5" => "\xE6\x8B\xA5", + "\xD3\xB6" => "\xE4\xBD\xA3", + "\xD3\xB7" => "\xE8\x87\x83", + "\xD3\xB8" => "\xE7\x97\x88", + "\xD3\xB9" => "\xE5\xBA\xB8", + "\xD3\xBA" => "\xE9\x9B\x8D", + "\xD3\xBB" => "\xE8\xB8\x8A", + "\xD3\xBC" => "\xE8\x9B\xB9", + "\xD3\xBD" => "\xE5\x92\x8F", + "\xD3\xBE" => "\xE6\xB3\xB3", + "\xD3\xBF" => "\xE6\xB6\x8C", + "\xD3\xC0" => "\xE6\xB0\xB8", + "\xD3\xC1" => "\xE6\x81\xBF", + "\xD3\xC2" => "\xE5\x8B\x87", + "\xD3\xC3" => "\xE7\x94\xA8", + "\xD3\xC4" => "\xE5\xB9\xBD", + "\xD3\xC5" => "\xE4\xBC\x98", + "\xD3\xC6" => "\xE6\x82\xA0", + "\xD3\xC7" => "\xE5\xBF\xA7", + "\xD3\xC8" => "\xE5\xB0\xA4", + "\xD3\xC9" => "\xE7\x94\xB1", + "\xD3\xCA" => "\xE9\x82\xAE", + "\xD3\xCB" => "\xE9\x93\x80", + "\xD3\xCC" => "\xE7\x8A\xB9", + "\xD3\xCD" => "\xE6\xB2\xB9", + "\xD3\xCE" => "\xE6\xB8\xB8", + "\xD3\xCF" => "\xE9\x85\x89", + "\xD3\xD0" => "\xE6\x9C\x89", + "\xD3\xD1" => "\xE5\x8F\x8B", + "\xD3\xD2" => "\xE5\x8F\xB3", + "\xD3\xD3" => "\xE4\xBD\x91", + "\xD3\xD4" => "\xE9\x87\x89", + "\xD3\xD5" => "\xE8\xAF\xB1", + "\xD3\xD6" => "\xE5\x8F\x88", + "\xD3\xD7" => "\xE5\xB9\xBC", + "\xD3\xD8" => "\xE8\xBF\x82", + "\xD3\xD9" => "\xE6\xB7\xA4", + "\xD3\xDA" => "\xE4\xBA\x8E", + "\xD3\xDB" => "\xE7\x9B\x82", + "\xD3\xDC" => "\xE6\xA6\x86", + "\xD3\xDD" => "\xE8\x99\x9E", + "\xD3\xDE" => "\xE6\x84\x9A", + "\xD3\xDF" => "\xE8\x88\x86", + "\xD3\xE0" => "\xE4\xBD\x99", + "\xD3\xE1" => "\xE4\xBF\x9E", + "\xD3\xE2" => "\xE9\x80\xBE", + "\xD3\xE3" => "\xE9\xB1\xBC", + "\xD3\xE4" => "\xE6\x84\x89", + "\xD3\xE5" => "\xE6\xB8\x9D", + "\xD3\xE6" => "\xE6\xB8\x94", + "\xD3\xE7" => "\xE9\x9A\x85", + "\xD3\xE8" => "\xE4\xBA\x88", + "\xD3\xE9" => "\xE5\xA8\xB1", + "\xD3\xEA" => "\xE9\x9B\xA8", + "\xD3\xEB" => "\xE4\xB8\x8E", + "\xD3\xEC" => "\xE5\xB1\xBF", + "\xD3\xED" => "\xE7\xA6\xB9", + "\xD3\xEE" => "\xE5\xAE\x87", + "\xD3\xEF" => "\xE8\xAF\xAD", + "\xD3\xF0" => "\xE7\xBE\xBD", + "\xD3\xF1" => "\xE7\x8E\x89", + "\xD3\xF2" => "\xE5\x9F\x9F", + "\xD3\xF3" => "\xE8\x8A\x8B", + "\xD3\xF4" => "\xE9\x83\x81", + "\xD3\xF5" => "\xE5\x90\x81", + "\xD3\xF6" => "\xE9\x81\x87", + "\xD3\xF7" => "\xE5\x96\xBB", + "\xD3\xF8" => "\xE5\xB3\xAA", + "\xD3\xF9" => "\xE5\xBE\xA1", + "\xD3\xFA" => "\xE6\x84\x88", + "\xD3\xFB" => "\xE6\xAC\xB2", + "\xD3\xFC" => "\xE7\x8B\xB1", + "\xD3\xFD" => "\xE8\x82\xB2", + "\xD3\xFE" => "\xE8\xAA\x89", + "\xD4\xA1" => "\xE6\xB5\xB4", + "\xD4\xA2" => "\xE5\xAF\x93", + "\xD4\xA3" => "\xE8\xA3\x95", + "\xD4\xA4" => "\xE9\xA2\x84", + "\xD4\xA5" => "\xE8\xB1\xAB", + "\xD4\xA6" => "\xE9\xA9\xAD", + "\xD4\xA7" => "\xE9\xB8\xB3", + "\xD4\xA8" => "\xE6\xB8\x8A", + "\xD4\xA9" => "\xE5\x86\xA4", + "\xD4\xAA" => "\xE5\x85\x83", + "\xD4\xAB" => "\xE5\x9E\xA3", + "\xD4\xAC" => "\xE8\xA2\x81", + "\xD4\xAD" => "\xE5\x8E\x9F", + "\xD4\xAE" => "\xE6\x8F\xB4", + "\xD4\xAF" => "\xE8\xBE\x95", + "\xD4\xB0" => "\xE5\x9B\xAD", + "\xD4\xB1" => "\xE5\x91\x98", + "\xD4\xB2" => "\xE5\x9C\x86", + "\xD4\xB3" => "\xE7\x8C\xBF", + "\xD4\xB4" => "\xE6\xBA\x90", + "\xD4\xB5" => "\xE7\xBC\x98", + "\xD4\xB6" => "\xE8\xBF\x9C", + "\xD4\xB7" => "\xE8\x8B\x91", + "\xD4\xB8" => "\xE6\x84\xBF", + "\xD4\xB9" => "\xE6\x80\xA8", + "\xD4\xBA" => "\xE9\x99\xA2", + "\xD4\xBB" => "\xE6\x9B\xB0", + "\xD4\xBC" => "\xE7\xBA\xA6", + "\xD4\xBD" => "\xE8\xB6\x8A", + "\xD4\xBE" => "\xE8\xB7\x83", + "\xD4\xBF" => "\xE9\x92\xA5", + "\xD4\xC0" => "\xE5\xB2\xB3", + "\xD4\xC1" => "\xE7\xB2\xA4", + "\xD4\xC2" => "\xE6\x9C\x88", + "\xD4\xC3" => "\xE6\x82\xA6", + "\xD4\xC4" => "\xE9\x98\x85", + "\xD4\xC5" => "\xE8\x80\x98", + "\xD4\xC6" => "\xE4\xBA\x91", + "\xD4\xC7" => "\xE9\x83\xA7", + "\xD4\xC8" => "\xE5\x8C\x80", + "\xD4\xC9" => "\xE9\x99\xA8", + "\xD4\xCA" => "\xE5\x85\x81", + "\xD4\xCB" => "\xE8\xBF\x90", + "\xD4\xCC" => "\xE8\x95\xB4", + "\xD4\xCD" => "\xE9\x85\x9D", + "\xD4\xCE" => "\xE6\x99\x95", + "\xD4\xCF" => "\xE9\x9F\xB5", + "\xD4\xD0" => "\xE5\xAD\x95", + "\xD4\xD1" => "\xE5\x8C\x9D", + "\xD4\xD2" => "\xE7\xA0\xB8", + "\xD4\xD3" => "\xE6\x9D\x82", + "\xD4\xD4" => "\xE6\xA0\xBD", + "\xD4\xD5" => "\xE5\x93\x89", + "\xD4\xD6" => "\xE7\x81\xBE", + "\xD4\xD7" => "\xE5\xAE\xB0", + "\xD4\xD8" => "\xE8\xBD\xBD", + "\xD4\xD9" => "\xE5\x86\x8D", + "\xD4\xDA" => "\xE5\x9C\xA8", + "\xD4\xDB" => "\xE5\x92\xB1", + "\xD4\xDC" => "\xE6\x94\x92", + "\xD4\xDD" => "\xE6\x9A\x82", + "\xD4\xDE" => "\xE8\xB5\x9E", + "\xD4\xDF" => "\xE8\xB5\x83", + "\xD4\xE0" => "\xE8\x84\x8F", + "\xD4\xE1" => "\xE8\x91\xAC", + "\xD4\xE2" => "\xE9\x81\xAD", + "\xD4\xE3" => "\xE7\xB3\x9F", + "\xD4\xE4" => "\xE5\x87\xBF", + "\xD4\xE5" => "\xE8\x97\xBB", + "\xD4\xE6" => "\xE6\x9E\xA3", + "\xD4\xE7" => "\xE6\x97\xA9", + "\xD4\xE8" => "\xE6\xBE\xA1", + "\xD4\xE9" => "\xE8\x9A\xA4", + "\xD4\xEA" => "\xE8\xBA\x81", + "\xD4\xEB" => "\xE5\x99\xAA", + "\xD4\xEC" => "\xE9\x80\xA0", + "\xD4\xED" => "\xE7\x9A\x82", + "\xD4\xEE" => "\xE7\x81\xB6", + "\xD4\xEF" => "\xE7\x87\xA5", + "\xD4\xF0" => "\xE8\xB4\xA3", + "\xD4\xF1" => "\xE6\x8B\xA9", + "\xD4\xF2" => "\xE5\x88\x99", + "\xD4\xF3" => "\xE6\xB3\xBD", + "\xD4\xF4" => "\xE8\xB4\xBC", + "\xD4\xF5" => "\xE6\x80\x8E", + "\xD4\xF6" => "\xE5\xA2\x9E", + "\xD4\xF7" => "\xE6\x86\x8E", + "\xD4\xF8" => "\xE6\x9B\xBE", + "\xD4\xF9" => "\xE8\xB5\xA0", + "\xD4\xFA" => "\xE6\x89\x8E", + "\xD4\xFB" => "\xE5\x96\xB3", + "\xD4\xFC" => "\xE6\xB8\xA3", + "\xD4\xFD" => "\xE6\x9C\xAD", + "\xD4\xFE" => "\xE8\xBD\xA7", + "\xD5\xA1" => "\xE9\x93\xA1", + "\xD5\xA2" => "\xE9\x97\xB8", + "\xD5\xA3" => "\xE7\x9C\xA8", + "\xD5\xA4" => "\xE6\xA0\x85", + "\xD5\xA5" => "\xE6\xA6\xA8", + "\xD5\xA6" => "\xE5\x92\x8B", + "\xD5\xA7" => "\xE4\xB9\x8D", + "\xD5\xA8" => "\xE7\x82\xB8", + "\xD5\xA9" => "\xE8\xAF\x88", + "\xD5\xAA" => "\xE6\x91\x98", + "\xD5\xAB" => "\xE6\x96\x8B", + "\xD5\xAC" => "\xE5\xAE\x85", + "\xD5\xAD" => "\xE7\xAA\x84", + "\xD5\xAE" => "\xE5\x80\xBA", + "\xD5\xAF" => "\xE5\xAF\xA8", + "\xD5\xB0" => "\xE7\x9E\xBB", + "\xD5\xB1" => "\xE6\xAF\xA1", + "\xD5\xB2" => "\xE8\xA9\xB9", + "\xD5\xB3" => "\xE7\xB2\x98", + "\xD5\xB4" => "\xE6\xB2\xBE", + "\xD5\xB5" => "\xE7\x9B\x8F", + "\xD5\xB6" => "\xE6\x96\xA9", + "\xD5\xB7" => "\xE8\xBE\x97", + "\xD5\xB8" => "\xE5\xB4\xAD", + "\xD5\xB9" => "\xE5\xB1\x95", + "\xD5\xBA" => "\xE8\x98\xB8", + "\xD5\xBB" => "\xE6\xA0\x88", + "\xD5\xBC" => "\xE5\x8D\xA0", + "\xD5\xBD" => "\xE6\x88\x98", + "\xD5\xBE" => "\xE7\xAB\x99", + "\xD5\xBF" => "\xE6\xB9\x9B", + "\xD5\xC0" => "\xE7\xBB\xBD", + "\xD5\xC1" => "\xE6\xA8\x9F", + "\xD5\xC2" => "\xE7\xAB\xA0", + "\xD5\xC3" => "\xE5\xBD\xB0", + "\xD5\xC4" => "\xE6\xBC\xB3", + "\xD5\xC5" => "\xE5\xBC\xA0", + "\xD5\xC6" => "\xE6\x8E\x8C", + "\xD5\xC7" => "\xE6\xB6\xA8", + "\xD5\xC8" => "\xE6\x9D\x96", + "\xD5\xC9" => "\xE4\xB8\x88", + "\xD5\xCA" => "\xE5\xB8\x90", + "\xD5\xCB" => "\xE8\xB4\xA6", + "\xD5\xCC" => "\xE4\xBB\x97", + "\xD5\xCD" => "\xE8\x83\x80", + "\xD5\xCE" => "\xE7\x98\xB4", + "\xD5\xCF" => "\xE9\x9A\x9C", + "\xD5\xD0" => "\xE6\x8B\x9B", + "\xD5\xD1" => "\xE6\x98\xAD", + "\xD5\xD2" => "\xE6\x89\xBE", + "\xD5\xD3" => "\xE6\xB2\xBC", + "\xD5\xD4" => "\xE8\xB5\xB5", + "\xD5\xD5" => "\xE7\x85\xA7", + "\xD5\xD6" => "\xE7\xBD\xA9", + "\xD5\xD7" => "\xE5\x85\x86", + "\xD5\xD8" => "\xE8\x82\x87", + "\xD5\xD9" => "\xE5\x8F\xAC", + "\xD5\xDA" => "\xE9\x81\xAE", + "\xD5\xDB" => "\xE6\x8A\x98", + "\xD5\xDC" => "\xE5\x93\xB2", + "\xD5\xDD" => "\xE8\x9B\xB0", + "\xD5\xDE" => "\xE8\xBE\x99", + "\xD5\xDF" => "\xE8\x80\x85", + "\xD5\xE0" => "\xE9\x94\x97", + "\xD5\xE1" => "\xE8\x94\x97", + "\xD5\xE2" => "\xE8\xBF\x99", + "\xD5\xE3" => "\xE6\xB5\x99", + "\xD5\xE4" => "\xE7\x8F\x8D", + "\xD5\xE5" => "\xE6\x96\x9F", + "\xD5\xE6" => "\xE7\x9C\x9F", + "\xD5\xE7" => "\xE7\x94\x84", + "\xD5\xE8" => "\xE7\xA0\xA7", + "\xD5\xE9" => "\xE8\x87\xBB", + "\xD5\xEA" => "\xE8\xB4\x9E", + "\xD5\xEB" => "\xE9\x92\x88", + "\xD5\xEC" => "\xE4\xBE\xA6", + "\xD5\xED" => "\xE6\x9E\x95", + "\xD5\xEE" => "\xE7\x96\xB9", + "\xD5\xEF" => "\xE8\xAF\x8A", + "\xD5\xF0" => "\xE9\x9C\x87", + "\xD5\xF1" => "\xE6\x8C\xAF", + "\xD5\xF2" => "\xE9\x95\x87", + "\xD5\xF3" => "\xE9\x98\xB5", + "\xD5\xF4" => "\xE8\x92\xB8", + "\xD5\xF5" => "\xE6\x8C\xA3", + "\xD5\xF6" => "\xE7\x9D\x81", + "\xD5\xF7" => "\xE5\xBE\x81", + "\xD5\xF8" => "\xE7\x8B\xB0", + "\xD5\xF9" => "\xE4\xBA\x89", + "\xD5\xFA" => "\xE6\x80\x94", + "\xD5\xFB" => "\xE6\x95\xB4", + "\xD5\xFC" => "\xE6\x8B\xAF", + "\xD5\xFD" => "\xE6\xAD\xA3", + "\xD5\xFE" => "\xE6\x94\xBF", + "\xD6\xA1" => "\xE5\xB8\xA7", + "\xD6\xA2" => "\xE7\x97\x87", + "\xD6\xA3" => "\xE9\x83\x91", + "\xD6\xA4" => "\xE8\xAF\x81", + "\xD6\xA5" => "\xE8\x8A\x9D", + "\xD6\xA6" => "\xE6\x9E\x9D", + "\xD6\xA7" => "\xE6\x94\xAF", + "\xD6\xA8" => "\xE5\x90\xB1", + "\xD6\xA9" => "\xE8\x9C\x98", + "\xD6\xAA" => "\xE7\x9F\xA5", + "\xD6\xAB" => "\xE8\x82\xA2", + "\xD6\xAC" => "\xE8\x84\x82", + "\xD6\xAD" => "\xE6\xB1\x81", + "\xD6\xAE" => "\xE4\xB9\x8B", + "\xD6\xAF" => "\xE7\xBB\x87", + "\xD6\xB0" => "\xE8\x81\x8C", + "\xD6\xB1" => "\xE7\x9B\xB4", + "\xD6\xB2" => "\xE6\xA4\x8D", + "\xD6\xB3" => "\xE6\xAE\x96", + "\xD6\xB4" => "\xE6\x89\xA7", + "\xD6\xB5" => "\xE5\x80\xBC", + "\xD6\xB6" => "\xE4\xBE\x84", + "\xD6\xB7" => "\xE5\x9D\x80", + "\xD6\xB8" => "\xE6\x8C\x87", + "\xD6\xB9" => "\xE6\xAD\xA2", + "\xD6\xBA" => "\xE8\xB6\xBE", + "\xD6\xBB" => "\xE5\x8F\xAA", + "\xD6\xBC" => "\xE6\x97\xA8", + "\xD6\xBD" => "\xE7\xBA\xB8", + "\xD6\xBE" => "\xE5\xBF\x97", + "\xD6\xBF" => "\xE6\x8C\x9A", + "\xD6\xC0" => "\xE6\x8E\xB7", + "\xD6\xC1" => "\xE8\x87\xB3", + "\xD6\xC2" => "\xE8\x87\xB4", + "\xD6\xC3" => "\xE7\xBD\xAE", + "\xD6\xC4" => "\xE5\xB8\x9C", + "\xD6\xC5" => "\xE5\xB3\x99", + "\xD6\xC6" => "\xE5\x88\xB6", + "\xD6\xC7" => "\xE6\x99\xBA", + "\xD6\xC8" => "\xE7\xA7\xA9", + "\xD6\xC9" => "\xE7\xA8\x9A", + "\xD6\xCA" => "\xE8\xB4\xA8", + "\xD6\xCB" => "\xE7\x82\x99", + "\xD6\xCC" => "\xE7\x97\x94", + "\xD6\xCD" => "\xE6\xBB\x9E", + "\xD6\xCE" => "\xE6\xB2\xBB", + "\xD6\xCF" => "\xE7\xAA\x92", + "\xD6\xD0" => "\xE4\xB8\xAD", + "\xD6\xD1" => "\xE7\x9B\x85", + "\xD6\xD2" => "\xE5\xBF\xA0", + "\xD6\xD3" => "\xE9\x92\x9F", + "\xD6\xD4" => "\xE8\xA1\xB7", + "\xD6\xD5" => "\xE7\xBB\x88", + "\xD6\xD6" => "\xE7\xA7\x8D", + "\xD6\xD7" => "\xE8\x82\xBF", + "\xD6\xD8" => "\xE9\x87\x8D", + "\xD6\xD9" => "\xE4\xBB\xB2", + "\xD6\xDA" => "\xE4\xBC\x97", + "\xD6\xDB" => "\xE8\x88\x9F", + "\xD6\xDC" => "\xE5\x91\xA8", + "\xD6\xDD" => "\xE5\xB7\x9E", + "\xD6\xDE" => "\xE6\xB4\xB2", + "\xD6\xDF" => "\xE8\xAF\x8C", + "\xD6\xE0" => "\xE7\xB2\xA5", + "\xD6\xE1" => "\xE8\xBD\xB4", + "\xD6\xE2" => "\xE8\x82\x98", + "\xD6\xE3" => "\xE5\xB8\x9A", + "\xD6\xE4" => "\xE5\x92\x92", + "\xD6\xE5" => "\xE7\x9A\xB1", + "\xD6\xE6" => "\xE5\xAE\x99", + "\xD6\xE7" => "\xE6\x98\xBC", + "\xD6\xE8" => "\xE9\xAA\xA4", + "\xD6\xE9" => "\xE7\x8F\xA0", + "\xD6\xEA" => "\xE6\xA0\xAA", + "\xD6\xEB" => "\xE8\x9B\x9B", + "\xD6\xEC" => "\xE6\x9C\xB1", + "\xD6\xED" => "\xE7\x8C\xAA", + "\xD6\xEE" => "\xE8\xAF\xB8", + "\xD6\xEF" => "\xE8\xAF\x9B", + "\xD6\xF0" => "\xE9\x80\x90", + "\xD6\xF1" => "\xE7\xAB\xB9", + "\xD6\xF2" => "\xE7\x83\x9B", + "\xD6\xF3" => "\xE7\x85\xAE", + "\xD6\xF4" => "\xE6\x8B\x84", + "\xD6\xF5" => "\xE7\x9E\xA9", + "\xD6\xF6" => "\xE5\x98\xB1", + "\xD6\xF7" => "\xE4\xB8\xBB", + "\xD6\xF8" => "\xE8\x91\x97", + "\xD6\xF9" => "\xE6\x9F\xB1", + "\xD6\xFA" => "\xE5\x8A\xA9", + "\xD6\xFB" => "\xE8\x9B\x80", + "\xD6\xFC" => "\xE8\xB4\xAE", + "\xD6\xFD" => "\xE9\x93\xB8", + "\xD6\xFE" => "\xE7\xAD\x91", + "\xD7\xA1" => "\xE4\xBD\x8F", + "\xD7\xA2" => "\xE6\xB3\xA8", + "\xD7\xA3" => "\xE7\xA5\x9D", + "\xD7\xA4" => "\xE9\xA9\xBB", + "\xD7\xA5" => "\xE6\x8A\x93", + "\xD7\xA6" => "\xE7\x88\xAA", + "\xD7\xA7" => "\xE6\x8B\xBD", + "\xD7\xA8" => "\xE4\xB8\x93", + "\xD7\xA9" => "\xE7\xA0\x96", + "\xD7\xAA" => "\xE8\xBD\xAC", + "\xD7\xAB" => "\xE6\x92\xB0", + "\xD7\xAC" => "\xE8\xB5\x9A", + "\xD7\xAD" => "\xE7\xAF\x86", + "\xD7\xAE" => "\xE6\xA1\xA9", + "\xD7\xAF" => "\xE5\xBA\x84", + "\xD7\xB0" => "\xE8\xA3\x85", + "\xD7\xB1" => "\xE5\xA6\x86", + "\xD7\xB2" => "\xE6\x92\x9E", + "\xD7\xB3" => "\xE5\xA3\xAE", + "\xD7\xB4" => "\xE7\x8A\xB6", + "\xD7\xB5" => "\xE6\xA4\x8E", + "\xD7\xB6" => "\xE9\x94\xA5", + "\xD7\xB7" => "\xE8\xBF\xBD", + "\xD7\xB8" => "\xE8\xB5\x98", + "\xD7\xB9" => "\xE5\x9D\xA0", + "\xD7\xBA" => "\xE7\xBC\x80", + "\xD7\xBB" => "\xE8\xB0\x86", + "\xD7\xBC" => "\xE5\x87\x86", + "\xD7\xBD" => "\xE6\x8D\x89", + "\xD7\xBE" => "\xE6\x8B\x99", + "\xD7\xBF" => "\xE5\x8D\x93", + "\xD7\xC0" => "\xE6\xA1\x8C", + "\xD7\xC1" => "\xE7\x90\xA2", + "\xD7\xC2" => "\xE8\x8C\x81", + "\xD7\xC3" => "\xE9\x85\x8C", + "\xD7\xC4" => "\xE5\x95\x84", + "\xD7\xC5" => "\xE7\x9D\x80", + "\xD7\xC6" => "\xE7\x81\xBC", + "\xD7\xC7" => "\xE6\xB5\x8A", + "\xD7\xC8" => "\xE5\x85\xB9", + "\xD7\xC9" => "\xE5\x92\xA8", + "\xD7\xCA" => "\xE8\xB5\x84", + "\xD7\xCB" => "\xE5\xA7\xBF", + "\xD7\xCC" => "\xE6\xBB\x8B", + "\xD7\xCD" => "\xE6\xB7\x84", + "\xD7\xCE" => "\xE5\xAD\x9C", + "\xD7\xCF" => "\xE7\xB4\xAB", + "\xD7\xD0" => "\xE4\xBB\x94", + "\xD7\xD1" => "\xE7\xB1\xBD", + "\xD7\xD2" => "\xE6\xBB\x93", + "\xD7\xD3" => "\xE5\xAD\x90", + "\xD7\xD4" => "\xE8\x87\xAA", + "\xD7\xD5" => "\xE6\xB8\x8D", + "\xD7\xD6" => "\xE5\xAD\x97", + "\xD7\xD7" => "\xE9\xAC\x83", + "\xD7\xD8" => "\xE6\xA3\x95", + "\xD7\xD9" => "\xE8\xB8\xAA", + "\xD7\xDA" => "\xE5\xAE\x97", + "\xD7\xDB" => "\xE7\xBB\xBC", + "\xD7\xDC" => "\xE6\x80\xBB", + "\xD7\xDD" => "\xE7\xBA\xB5", + "\xD7\xDE" => "\xE9\x82\xB9", + "\xD7\xDF" => "\xE8\xB5\xB0", + "\xD7\xE0" => "\xE5\xA5\x8F", + "\xD7\xE1" => "\xE6\x8F\x8D", + "\xD7\xE2" => "\xE7\xA7\x9F", + "\xD7\xE3" => "\xE8\xB6\xB3", + "\xD7\xE4" => "\xE5\x8D\x92", + "\xD7\xE5" => "\xE6\x97\x8F", + "\xD7\xE6" => "\xE7\xA5\x96", + "\xD7\xE7" => "\xE8\xAF\x85", + "\xD7\xE8" => "\xE9\x98\xBB", + "\xD7\xE9" => "\xE7\xBB\x84", + "\xD7\xEA" => "\xE9\x92\xBB", + "\xD7\xEB" => "\xE7\xBA\x82", + "\xD7\xEC" => "\xE5\x98\xB4", + "\xD7\xED" => "\xE9\x86\x89", + "\xD7\xEE" => "\xE6\x9C\x80", + "\xD7\xEF" => "\xE7\xBD\xAA", + "\xD7\xF0" => "\xE5\xB0\x8A", + "\xD7\xF1" => "\xE9\x81\xB5", + "\xD7\xF2" => "\xE6\x98\xA8", + "\xD7\xF3" => "\xE5\xB7\xA6", + "\xD7\xF4" => "\xE4\xBD\x90", + "\xD7\xF5" => "\xE6\x9F\x9E", + "\xD7\xF6" => "\xE5\x81\x9A", + "\xD7\xF7" => "\xE4\xBD\x9C", + "\xD7\xF8" => "\xE5\x9D\x90", + "\xD7\xF9" => "\xE5\xBA\xA7", + "\xD8\xA1" => "\xE4\xBA\x8D", + "\xD8\xA2" => "\xE4\xB8\x8C", + "\xD8\xA3" => "\xE5\x85\x80", + "\xD8\xA4" => "\xE4\xB8\x90", + "\xD8\xA5" => "\xE5\xBB\xBF", + "\xD8\xA6" => "\xE5\x8D\x85", + "\xD8\xA7" => "\xE4\xB8\x95", + "\xD8\xA8" => "\xE4\xBA\x98", + "\xD8\xA9" => "\xE4\xB8\x9E", + "\xD8\xAA" => "\xE9\xAC\xB2", + "\xD8\xAB" => "\xE5\xAD\xAC", + "\xD8\xAC" => "\xE5\x99\xA9", + "\xD8\xAD" => "\xE4\xB8\xA8", + "\xD8\xAE" => "\xE7\xA6\xBA", + "\xD8\xAF" => "\xE4\xB8\xBF", + "\xD8\xB0" => "\xE5\x8C\x95", + "\xD8\xB1" => "\xE4\xB9\x87", + "\xD8\xB2" => "\xE5\xA4\xAD", + "\xD8\xB3" => "\xE7\x88\xBB", + "\xD8\xB4" => "\xE5\x8D\xAE", + "\xD8\xB5" => "\xE6\xB0\x90", + "\xD8\xB6" => "\xE5\x9B\x9F", + "\xD8\xB7" => "\xE8\x83\xA4", + "\xD8\xB8" => "\xE9\xA6\x97", + "\xD8\xB9" => "\xE6\xAF\x93", + "\xD8\xBA" => "\xE7\x9D\xBE", + "\xD8\xBB" => "\xE9\xBC\x97", + "\xD8\xBC" => "\xE4\xB8\xB6", + "\xD8\xBD" => "\xE4\xBA\x9F", + "\xD8\xBE" => "\xE9\xBC\x90", + "\xD8\xBF" => "\xE4\xB9\x9C", + "\xD8\xC0" => "\xE4\xB9\xA9", + "\xD8\xC1" => "\xE4\xBA\x93", + "\xD8\xC2" => "\xE8\x8A\x88", + "\xD8\xC3" => "\xE5\xAD\x9B", + "\xD8\xC4" => "\xE5\x95\xAC", + "\xD8\xC5" => "\xE5\x98\x8F", + "\xD8\xC6" => "\xE4\xBB\x84", + "\xD8\xC7" => "\xE5\x8E\x8D", + "\xD8\xC8" => "\xE5\x8E\x9D", + "\xD8\xC9" => "\xE5\x8E\xA3", + "\xD8\xCA" => "\xE5\x8E\xA5", + "\xD8\xCB" => "\xE5\x8E\xAE", + "\xD8\xCC" => "\xE9\x9D\xA5", + "\xD8\xCD" => "\xE8\xB5\x9D", + "\xD8\xCE" => "\xE5\x8C\x9A", + "\xD8\xCF" => "\xE5\x8F\xB5", + "\xD8\xD0" => "\xE5\x8C\xA6", + "\xD8\xD1" => "\xE5\x8C\xAE", + "\xD8\xD2" => "\xE5\x8C\xBE", + "\xD8\xD3" => "\xE8\xB5\x9C", + "\xD8\xD4" => "\xE5\x8D\xA6", + "\xD8\xD5" => "\xE5\x8D\xA3", + "\xD8\xD6" => "\xE5\x88\x82", + "\xD8\xD7" => "\xE5\x88\x88", + "\xD8\xD8" => "\xE5\x88\x8E", + "\xD8\xD9" => "\xE5\x88\xAD", + "\xD8\xDA" => "\xE5\x88\xB3", + "\xD8\xDB" => "\xE5\x88\xBF", + "\xD8\xDC" => "\xE5\x89\x80", + "\xD8\xDD" => "\xE5\x89\x8C", + "\xD8\xDE" => "\xE5\x89\x9E", + "\xD8\xDF" => "\xE5\x89\xA1", + "\xD8\xE0" => "\xE5\x89\x9C", + "\xD8\xE1" => "\xE8\x92\xAF", + "\xD8\xE2" => "\xE5\x89\xBD", + "\xD8\xE3" => "\xE5\x8A\x82", + "\xD8\xE4" => "\xE5\x8A\x81", + "\xD8\xE5" => "\xE5\x8A\x90", + "\xD8\xE6" => "\xE5\x8A\x93", + "\xD8\xE7" => "\xE5\x86\x82", + "\xD8\xE8" => "\xE7\xBD\x94", + "\xD8\xE9" => "\xE4\xBA\xBB", + "\xD8\xEA" => "\xE4\xBB\x83", + "\xD8\xEB" => "\xE4\xBB\x89", + "\xD8\xEC" => "\xE4\xBB\x82", + "\xD8\xED" => "\xE4\xBB\xA8", + "\xD8\xEE" => "\xE4\xBB\xA1", + "\xD8\xEF" => "\xE4\xBB\xAB", + "\xD8\xF0" => "\xE4\xBB\x9E", + "\xD8\xF1" => "\xE4\xBC\x9B", + "\xD8\xF2" => "\xE4\xBB\xB3", + "\xD8\xF3" => "\xE4\xBC\xA2", + "\xD8\xF4" => "\xE4\xBD\xA4", + "\xD8\xF5" => "\xE4\xBB\xB5", + "\xD8\xF6" => "\xE4\xBC\xA5", + "\xD8\xF7" => "\xE4\xBC\xA7", + "\xD8\xF8" => "\xE4\xBC\x89", + "\xD8\xF9" => "\xE4\xBC\xAB", + "\xD8\xFA" => "\xE4\xBD\x9E", + "\xD8\xFB" => "\xE4\xBD\xA7", + "\xD8\xFC" => "\xE6\x94\xB8", + "\xD8\xFD" => "\xE4\xBD\x9A", + "\xD8\xFE" => "\xE4\xBD\x9D", + "\xD9\xA1" => "\xE4\xBD\x9F", + "\xD9\xA2" => "\xE4\xBD\x97", + "\xD9\xA3" => "\xE4\xBC\xB2", + "\xD9\xA4" => "\xE4\xBC\xBD", + "\xD9\xA5" => "\xE4\xBD\xB6", + "\xD9\xA6" => "\xE4\xBD\xB4", + "\xD9\xA7" => "\xE4\xBE\x91", + "\xD9\xA8" => "\xE4\xBE\x89", + "\xD9\xA9" => "\xE4\xBE\x83", + "\xD9\xAA" => "\xE4\xBE\x8F", + "\xD9\xAB" => "\xE4\xBD\xBE", + "\xD9\xAC" => "\xE4\xBD\xBB", + "\xD9\xAD" => "\xE4\xBE\xAA", + "\xD9\xAE" => "\xE4\xBD\xBC", + "\xD9\xAF" => "\xE4\xBE\xAC", + "\xD9\xB0" => "\xE4\xBE\x94", + "\xD9\xB1" => "\xE4\xBF\xA6", + "\xD9\xB2" => "\xE4\xBF\xA8", + "\xD9\xB3" => "\xE4\xBF\xAA", + "\xD9\xB4" => "\xE4\xBF\x85", + "\xD9\xB5" => "\xE4\xBF\x9A", + "\xD9\xB6" => "\xE4\xBF\xA3", + "\xD9\xB7" => "\xE4\xBF\x9C", + "\xD9\xB8" => "\xE4\xBF\x91", + "\xD9\xB9" => "\xE4\xBF\x9F", + "\xD9\xBA" => "\xE4\xBF\xB8", + "\xD9\xBB" => "\xE5\x80\xA9", + "\xD9\xBC" => "\xE5\x81\x8C", + "\xD9\xBD" => "\xE4\xBF\xB3", + "\xD9\xBE" => "\xE5\x80\xAC", + "\xD9\xBF" => "\xE5\x80\x8F", + "\xD9\xC0" => "\xE5\x80\xAE", + "\xD9\xC1" => "\xE5\x80\xAD", + "\xD9\xC2" => "\xE4\xBF\xBE", + "\xD9\xC3" => "\xE5\x80\x9C", + "\xD9\xC4" => "\xE5\x80\x8C", + "\xD9\xC5" => "\xE5\x80\xA5", + "\xD9\xC6" => "\xE5\x80\xA8", + "\xD9\xC7" => "\xE5\x81\xBE", + "\xD9\xC8" => "\xE5\x81\x83", + "\xD9\xC9" => "\xE5\x81\x95", + "\xD9\xCA" => "\xE5\x81\x88", + "\xD9\xCB" => "\xE5\x81\x8E", + "\xD9\xCC" => "\xE5\x81\xAC", + "\xD9\xCD" => "\xE5\x81\xBB", + "\xD9\xCE" => "\xE5\x82\xA5", + "\xD9\xCF" => "\xE5\x82\xA7", + "\xD9\xD0" => "\xE5\x82\xA9", + "\xD9\xD1" => "\xE5\x82\xBA", + "\xD9\xD2" => "\xE5\x83\x96", + "\xD9\xD3" => "\xE5\x84\x86", + "\xD9\xD4" => "\xE5\x83\xAD", + "\xD9\xD5" => "\xE5\x83\xAC", + "\xD9\xD6" => "\xE5\x83\xA6", + "\xD9\xD7" => "\xE5\x83\xAE", + "\xD9\xD8" => "\xE5\x84\x87", + "\xD9\xD9" => "\xE5\x84\x8B", + "\xD9\xDA" => "\xE4\xBB\x9D", + "\xD9\xDB" => "\xE6\xB0\xBD", + "\xD9\xDC" => "\xE4\xBD\x98", + "\xD9\xDD" => "\xE4\xBD\xA5", + "\xD9\xDE" => "\xE4\xBF\x8E", + "\xD9\xDF" => "\xE9\xBE\xA0", + "\xD9\xE0" => "\xE6\xB1\x86", + "\xD9\xE1" => "\xE7\xB1\xB4", + "\xD9\xE2" => "\xE5\x85\xAE", + "\xD9\xE3" => "\xE5\xB7\xBD", + "\xD9\xE4" => "\xE9\xBB\x89", + "\xD9\xE5" => "\xE9\xA6\x98", + "\xD9\xE6" => "\xE5\x86\x81", + "\xD9\xE7" => "\xE5\xA4\x94", + "\xD9\xE8" => "\xE5\x8B\xB9", + "\xD9\xE9" => "\xE5\x8C\x8D", + "\xD9\xEA" => "\xE8\xA8\x87", + "\xD9\xEB" => "\xE5\x8C\x90", + "\xD9\xEC" => "\xE5\x87\xAB", + "\xD9\xED" => "\xE5\xA4\x99", + "\xD9\xEE" => "\xE5\x85\x95", + "\xD9\xEF" => "\xE4\xBA\xA0", + "\xD9\xF0" => "\xE5\x85\x96", + "\xD9\xF1" => "\xE4\xBA\xB3", + "\xD9\xF2" => "\xE8\xA1\xAE", + "\xD9\xF3" => "\xE8\xA2\xA4", + "\xD9\xF4" => "\xE4\xBA\xB5", + "\xD9\xF5" => "\xE8\x84\x94", + "\xD9\xF6" => "\xE8\xA3\x92", + "\xD9\xF7" => "\xE7\xA6\x80", + "\xD9\xF8" => "\xE5\xAC\xB4", + "\xD9\xF9" => "\xE8\xA0\x83", + "\xD9\xFA" => "\xE7\xBE\xB8", + "\xD9\xFB" => "\xE5\x86\xAB", + "\xD9\xFC" => "\xE5\x86\xB1", + "\xD9\xFD" => "\xE5\x86\xBD", + "\xD9\xFE" => "\xE5\x86\xBC", + "\xDA\xA1" => "\xE5\x87\x87", + "\xDA\xA2" => "\xE5\x86\x96", + "\xDA\xA3" => "\xE5\x86\xA2", + "\xDA\xA4" => "\xE5\x86\xA5", + "\xDA\xA5" => "\xE8\xAE\xA0", + "\xDA\xA6" => "\xE8\xAE\xA6", + "\xDA\xA7" => "\xE8\xAE\xA7", + "\xDA\xA8" => "\xE8\xAE\xAA", + "\xDA\xA9" => "\xE8\xAE\xB4", + "\xDA\xAA" => "\xE8\xAE\xB5", + "\xDA\xAB" => "\xE8\xAE\xB7", + "\xDA\xAC" => "\xE8\xAF\x82", + "\xDA\xAD" => "\xE8\xAF\x83", + "\xDA\xAE" => "\xE8\xAF\x8B", + "\xDA\xAF" => "\xE8\xAF\x8F", + "\xDA\xB0" => "\xE8\xAF\x8E", + "\xDA\xB1" => "\xE8\xAF\x92", + "\xDA\xB2" => "\xE8\xAF\x93", + "\xDA\xB3" => "\xE8\xAF\x94", + "\xDA\xB4" => "\xE8\xAF\x96", + "\xDA\xB5" => "\xE8\xAF\x98", + "\xDA\xB6" => "\xE8\xAF\x99", + "\xDA\xB7" => "\xE8\xAF\x9C", + "\xDA\xB8" => "\xE8\xAF\x9F", + "\xDA\xB9" => "\xE8\xAF\xA0", + "\xDA\xBA" => "\xE8\xAF\xA4", + "\xDA\xBB" => "\xE8\xAF\xA8", + "\xDA\xBC" => "\xE8\xAF\xA9", + "\xDA\xBD" => "\xE8\xAF\xAE", + "\xDA\xBE" => "\xE8\xAF\xB0", + "\xDA\xBF" => "\xE8\xAF\xB3", + "\xDA\xC0" => "\xE8\xAF\xB6", + "\xDA\xC1" => "\xE8\xAF\xB9", + "\xDA\xC2" => "\xE8\xAF\xBC", + "\xDA\xC3" => "\xE8\xAF\xBF", + "\xDA\xC4" => "\xE8\xB0\x80", + "\xDA\xC5" => "\xE8\xB0\x82", + "\xDA\xC6" => "\xE8\xB0\x84", + "\xDA\xC7" => "\xE8\xB0\x87", + "\xDA\xC8" => "\xE8\xB0\x8C", + "\xDA\xC9" => "\xE8\xB0\x8F", + "\xDA\xCA" => "\xE8\xB0\x91", + "\xDA\xCB" => "\xE8\xB0\x92", + "\xDA\xCC" => "\xE8\xB0\x94", + "\xDA\xCD" => "\xE8\xB0\x95", + "\xDA\xCE" => "\xE8\xB0\x96", + "\xDA\xCF" => "\xE8\xB0\x99", + "\xDA\xD0" => "\xE8\xB0\x9B", + "\xDA\xD1" => "\xE8\xB0\x98", + "\xDA\xD2" => "\xE8\xB0\x9D", + "\xDA\xD3" => "\xE8\xB0\x9F", + "\xDA\xD4" => "\xE8\xB0\xA0", + "\xDA\xD5" => "\xE8\xB0\xA1", + "\xDA\xD6" => "\xE8\xB0\xA5", + "\xDA\xD7" => "\xE8\xB0\xA7", + "\xDA\xD8" => "\xE8\xB0\xAA", + "\xDA\xD9" => "\xE8\xB0\xAB", + "\xDA\xDA" => "\xE8\xB0\xAE", + "\xDA\xDB" => "\xE8\xB0\xAF", + "\xDA\xDC" => "\xE8\xB0\xB2", + "\xDA\xDD" => "\xE8\xB0\xB3", + "\xDA\xDE" => "\xE8\xB0\xB5", + "\xDA\xDF" => "\xE8\xB0\xB6", + "\xDA\xE0" => "\xE5\x8D\xA9", + "\xDA\xE1" => "\xE5\x8D\xBA", + "\xDA\xE2" => "\xE9\x98\x9D", + "\xDA\xE3" => "\xE9\x98\xA2", + "\xDA\xE4" => "\xE9\x98\xA1", + "\xDA\xE5" => "\xE9\x98\xB1", + "\xDA\xE6" => "\xE9\x98\xAA", + "\xDA\xE7" => "\xE9\x98\xBD", + "\xDA\xE8" => "\xE9\x98\xBC", + "\xDA\xE9" => "\xE9\x99\x82", + "\xDA\xEA" => "\xE9\x99\x89", + "\xDA\xEB" => "\xE9\x99\x94", + "\xDA\xEC" => "\xE9\x99\x9F", + "\xDA\xED" => "\xE9\x99\xA7", + "\xDA\xEE" => "\xE9\x99\xAC", + "\xDA\xEF" => "\xE9\x99\xB2", + "\xDA\xF0" => "\xE9\x99\xB4", + "\xDA\xF1" => "\xE9\x9A\x88", + "\xDA\xF2" => "\xE9\x9A\x8D", + "\xDA\xF3" => "\xE9\x9A\x97", + "\xDA\xF4" => "\xE9\x9A\xB0", + "\xDA\xF5" => "\xE9\x82\x97", + "\xDA\xF6" => "\xE9\x82\x9B", + "\xDA\xF7" => "\xE9\x82\x9D", + "\xDA\xF8" => "\xE9\x82\x99", + "\xDA\xF9" => "\xE9\x82\xAC", + "\xDA\xFA" => "\xE9\x82\xA1", + "\xDA\xFB" => "\xE9\x82\xB4", + "\xDA\xFC" => "\xE9\x82\xB3", + "\xDA\xFD" => "\xE9\x82\xB6", + "\xDA\xFE" => "\xE9\x82\xBA", + "\xDB\xA1" => "\xE9\x82\xB8", + "\xDB\xA2" => "\xE9\x82\xB0", + "\xDB\xA3" => "\xE9\x83\x8F", + "\xDB\xA4" => "\xE9\x83\x85", + "\xDB\xA5" => "\xE9\x82\xBE", + "\xDB\xA6" => "\xE9\x83\x90", + "\xDB\xA7" => "\xE9\x83\x84", + "\xDB\xA8" => "\xE9\x83\x87", + "\xDB\xA9" => "\xE9\x83\x93", + "\xDB\xAA" => "\xE9\x83\xA6", + "\xDB\xAB" => "\xE9\x83\xA2", + "\xDB\xAC" => "\xE9\x83\x9C", + "\xDB\xAD" => "\xE9\x83\x97", + "\xDB\xAE" => "\xE9\x83\x9B", + "\xDB\xAF" => "\xE9\x83\xAB", + "\xDB\xB0" => "\xE9\x83\xAF", + "\xDB\xB1" => "\xE9\x83\xBE", + "\xDB\xB2" => "\xE9\x84\x84", + "\xDB\xB3" => "\xE9\x84\xA2", + "\xDB\xB4" => "\xE9\x84\x9E", + "\xDB\xB5" => "\xE9\x84\xA3", + "\xDB\xB6" => "\xE9\x84\xB1", + "\xDB\xB7" => "\xE9\x84\xAF", + "\xDB\xB8" => "\xE9\x84\xB9", + "\xDB\xB9" => "\xE9\x85\x83", + "\xDB\xBA" => "\xE9\x85\x86", + "\xDB\xBB" => "\xE5\x88\x8D", + "\xDB\xBC" => "\xE5\xA5\x82", + "\xDB\xBD" => "\xE5\x8A\xA2", + "\xDB\xBE" => "\xE5\x8A\xAC", + "\xDB\xBF" => "\xE5\x8A\xAD", + "\xDB\xC0" => "\xE5\x8A\xBE", + "\xDB\xC1" => "\xE5\x93\xBF", + "\xDB\xC2" => "\xE5\x8B\x90", + "\xDB\xC3" => "\xE5\x8B\x96", + "\xDB\xC4" => "\xE5\x8B\xB0", + "\xDB\xC5" => "\xE5\x8F\x9F", + "\xDB\xC6" => "\xE7\x87\xAE", + "\xDB\xC7" => "\xE7\x9F\x8D", + "\xDB\xC8" => "\xE5\xBB\xB4", + "\xDB\xC9" => "\xE5\x87\xB5", + "\xDB\xCA" => "\xE5\x87\xBC", + "\xDB\xCB" => "\xE9\xAC\xAF", + "\xDB\xCC" => "\xE5\x8E\xB6", + "\xDB\xCD" => "\xE5\xBC\x81", + "\xDB\xCE" => "\xE7\x95\x9A", + "\xDB\xCF" => "\xE5\xB7\xAF", + "\xDB\xD0" => "\xE5\x9D\x8C", + "\xDB\xD1" => "\xE5\x9E\xA9", + "\xDB\xD2" => "\xE5\x9E\xA1", + "\xDB\xD3" => "\xE5\xA1\xBE", + "\xDB\xD4" => "\xE5\xA2\xBC", + "\xDB\xD5" => "\xE5\xA3\x85", + "\xDB\xD6" => "\xE5\xA3\x91", + "\xDB\xD7" => "\xE5\x9C\xA9", + "\xDB\xD8" => "\xE5\x9C\xAC", + "\xDB\xD9" => "\xE5\x9C\xAA", + "\xDB\xDA" => "\xE5\x9C\xB3", + "\xDB\xDB" => "\xE5\x9C\xB9", + "\xDB\xDC" => "\xE5\x9C\xAE", + "\xDB\xDD" => "\xE5\x9C\xAF", + "\xDB\xDE" => "\xE5\x9D\x9C", + "\xDB\xDF" => "\xE5\x9C\xBB", + "\xDB\xE0" => "\xE5\x9D\x82", + "\xDB\xE1" => "\xE5\x9D\xA9", + "\xDB\xE2" => "\xE5\x9E\x85", + "\xDB\xE3" => "\xE5\x9D\xAB", + "\xDB\xE4" => "\xE5\x9E\x86", + "\xDB\xE5" => "\xE5\x9D\xBC", + "\xDB\xE6" => "\xE5\x9D\xBB", + "\xDB\xE7" => "\xE5\x9D\xA8", + "\xDB\xE8" => "\xE5\x9D\xAD", + "\xDB\xE9" => "\xE5\x9D\xB6", + "\xDB\xEA" => "\xE5\x9D\xB3", + "\xDB\xEB" => "\xE5\x9E\xAD", + "\xDB\xEC" => "\xE5\x9E\xA4", + "\xDB\xED" => "\xE5\x9E\x8C", + "\xDB\xEE" => "\xE5\x9E\xB2", + "\xDB\xEF" => "\xE5\x9F\x8F", + "\xDB\xF0" => "\xE5\x9E\xA7", + "\xDB\xF1" => "\xE5\x9E\xB4", + "\xDB\xF2" => "\xE5\x9E\x93", + "\xDB\xF3" => "\xE5\x9E\xA0", + "\xDB\xF4" => "\xE5\x9F\x95", + "\xDB\xF5" => "\xE5\x9F\x98", + "\xDB\xF6" => "\xE5\x9F\x9A", + "\xDB\xF7" => "\xE5\x9F\x99", + "\xDB\xF8" => "\xE5\x9F\x92", + "\xDB\xF9" => "\xE5\x9E\xB8", + "\xDB\xFA" => "\xE5\x9F\xB4", + "\xDB\xFB" => "\xE5\x9F\xAF", + "\xDB\xFC" => "\xE5\x9F\xB8", + "\xDB\xFD" => "\xE5\x9F\xA4", + "\xDB\xFE" => "\xE5\x9F\x9D", + "\xDC\xA1" => "\xE5\xA0\x8B", + "\xDC\xA2" => "\xE5\xA0\x8D", + "\xDC\xA3" => "\xE5\x9F\xBD", + "\xDC\xA4" => "\xE5\x9F\xAD", + "\xDC\xA5" => "\xE5\xA0\x80", + "\xDC\xA6" => "\xE5\xA0\x9E", + "\xDC\xA7" => "\xE5\xA0\x99", + "\xDC\xA8" => "\xE5\xA1\x84", + "\xDC\xA9" => "\xE5\xA0\xA0", + "\xDC\xAA" => "\xE5\xA1\xA5", + "\xDC\xAB" => "\xE5\xA1\xAC", + "\xDC\xAC" => "\xE5\xA2\x81", + "\xDC\xAD" => "\xE5\xA2\x89", + "\xDC\xAE" => "\xE5\xA2\x9A", + "\xDC\xAF" => "\xE5\xA2\x80", + "\xDC\xB0" => "\xE9\xA6\xA8", + "\xDC\xB1" => "\xE9\xBC\x99", + "\xDC\xB2" => "\xE6\x87\xBF", + "\xDC\xB3" => "\xE8\x89\xB9", + "\xDC\xB4" => "\xE8\x89\xBD", + "\xDC\xB5" => "\xE8\x89\xBF", + "\xDC\xB6" => "\xE8\x8A\x8F", + "\xDC\xB7" => "\xE8\x8A\x8A", + "\xDC\xB8" => "\xE8\x8A\xA8", + "\xDC\xB9" => "\xE8\x8A\x84", + "\xDC\xBA" => "\xE8\x8A\x8E", + "\xDC\xBB" => "\xE8\x8A\x91", + "\xDC\xBC" => "\xE8\x8A\x97", + "\xDC\xBD" => "\xE8\x8A\x99", + "\xDC\xBE" => "\xE8\x8A\xAB", + "\xDC\xBF" => "\xE8\x8A\xB8", + "\xDC\xC0" => "\xE8\x8A\xBE", + "\xDC\xC1" => "\xE8\x8A\xB0", + "\xDC\xC2" => "\xE8\x8B\x88", + "\xDC\xC3" => "\xE8\x8B\x8A", + "\xDC\xC4" => "\xE8\x8B\xA3", + "\xDC\xC5" => "\xE8\x8A\x98", + "\xDC\xC6" => "\xE8\x8A\xB7", + "\xDC\xC7" => "\xE8\x8A\xAE", + "\xDC\xC8" => "\xE8\x8B\x8B", + "\xDC\xC9" => "\xE8\x8B\x8C", + "\xDC\xCA" => "\xE8\x8B\x81", + "\xDC\xCB" => "\xE8\x8A\xA9", + "\xDC\xCC" => "\xE8\x8A\xB4", + "\xDC\xCD" => "\xE8\x8A\xA1", + "\xDC\xCE" => "\xE8\x8A\xAA", + "\xDC\xCF" => "\xE8\x8A\x9F", + "\xDC\xD0" => "\xE8\x8B\x84", + "\xDC\xD1" => "\xE8\x8B\x8E", + "\xDC\xD2" => "\xE8\x8A\xA4", + "\xDC\xD3" => "\xE8\x8B\xA1", + "\xDC\xD4" => "\xE8\x8C\x89", + "\xDC\xD5" => "\xE8\x8B\xB7", + "\xDC\xD6" => "\xE8\x8B\xA4", + "\xDC\xD7" => "\xE8\x8C\x8F", + "\xDC\xD8" => "\xE8\x8C\x87", + "\xDC\xD9" => "\xE8\x8B\x9C", + "\xDC\xDA" => "\xE8\x8B\xB4", + "\xDC\xDB" => "\xE8\x8B\x92", + "\xDC\xDC" => "\xE8\x8B\x98", + "\xDC\xDD" => "\xE8\x8C\x8C", + "\xDC\xDE" => "\xE8\x8B\xBB", + "\xDC\xDF" => "\xE8\x8B\x93", + "\xDC\xE0" => "\xE8\x8C\x91", + "\xDC\xE1" => "\xE8\x8C\x9A", + "\xDC\xE2" => "\xE8\x8C\x86", + "\xDC\xE3" => "\xE8\x8C\x94", + "\xDC\xE4" => "\xE8\x8C\x95", + "\xDC\xE5" => "\xE8\x8B\xA0", + "\xDC\xE6" => "\xE8\x8B\x95", + "\xDC\xE7" => "\xE8\x8C\x9C", + "\xDC\xE8" => "\xE8\x8D\x91", + "\xDC\xE9" => "\xE8\x8D\x9B", + "\xDC\xEA" => "\xE8\x8D\x9C", + "\xDC\xEB" => "\xE8\x8C\x88", + "\xDC\xEC" => "\xE8\x8E\x92", + "\xDC\xED" => "\xE8\x8C\xBC", + "\xDC\xEE" => "\xE8\x8C\xB4", + "\xDC\xEF" => "\xE8\x8C\xB1", + "\xDC\xF0" => "\xE8\x8E\x9B", + "\xDC\xF1" => "\xE8\x8D\x9E", + "\xDC\xF2" => "\xE8\x8C\xAF", + "\xDC\xF3" => "\xE8\x8D\x8F", + "\xDC\xF4" => "\xE8\x8D\x87", + "\xDC\xF5" => "\xE8\x8D\x83", + "\xDC\xF6" => "\xE8\x8D\x9F", + "\xDC\xF7" => "\xE8\x8D\x80", + "\xDC\xF8" => "\xE8\x8C\x97", + "\xDC\xF9" => "\xE8\x8D\xA0", + "\xDC\xFA" => "\xE8\x8C\xAD", + "\xDC\xFB" => "\xE8\x8C\xBA", + "\xDC\xFC" => "\xE8\x8C\xB3", + "\xDC\xFD" => "\xE8\x8D\xA6", + "\xDC\xFE" => "\xE8\x8D\xA5", + "\xDD\xA1" => "\xE8\x8D\xA8", + "\xDD\xA2" => "\xE8\x8C\x9B", + "\xDD\xA3" => "\xE8\x8D\xA9", + "\xDD\xA4" => "\xE8\x8D\xAC", + "\xDD\xA5" => "\xE8\x8D\xAA", + "\xDD\xA6" => "\xE8\x8D\xAD", + "\xDD\xA7" => "\xE8\x8D\xAE", + "\xDD\xA8" => "\xE8\x8E\xB0", + "\xDD\xA9" => "\xE8\x8D\xB8", + "\xDD\xAA" => "\xE8\x8E\xB3", + "\xDD\xAB" => "\xE8\x8E\xB4", + "\xDD\xAC" => "\xE8\x8E\xA0", + "\xDD\xAD" => "\xE8\x8E\xAA", + "\xDD\xAE" => "\xE8\x8E\x93", + "\xDD\xAF" => "\xE8\x8E\x9C", + "\xDD\xB0" => "\xE8\x8E\x85", + "\xDD\xB1" => "\xE8\x8D\xBC", + "\xDD\xB2" => "\xE8\x8E\xB6", + "\xDD\xB3" => "\xE8\x8E\xA9", + "\xDD\xB4" => "\xE8\x8D\xBD", + "\xDD\xB5" => "\xE8\x8E\xB8", + "\xDD\xB6" => "\xE8\x8D\xBB", + "\xDD\xB7" => "\xE8\x8E\x98", + "\xDD\xB8" => "\xE8\x8E\x9E", + "\xDD\xB9" => "\xE8\x8E\xA8", + "\xDD\xBA" => "\xE8\x8E\xBA", + "\xDD\xBB" => "\xE8\x8E\xBC", + "\xDD\xBC" => "\xE8\x8F\x81", + "\xDD\xBD" => "\xE8\x90\x81", + "\xDD\xBE" => "\xE8\x8F\xA5", + "\xDD\xBF" => "\xE8\x8F\x98", + "\xDD\xC0" => "\xE5\xA0\x87", + "\xDD\xC1" => "\xE8\x90\x98", + "\xDD\xC2" => "\xE8\x90\x8B", + "\xDD\xC3" => "\xE8\x8F\x9D", + "\xDD\xC4" => "\xE8\x8F\xBD", + "\xDD\xC5" => "\xE8\x8F\x96", + "\xDD\xC6" => "\xE8\x90\x9C", + "\xDD\xC7" => "\xE8\x90\xB8", + "\xDD\xC8" => "\xE8\x90\x91", + "\xDD\xC9" => "\xE8\x90\x86", + "\xDD\xCA" => "\xE8\x8F\x94", + "\xDD\xCB" => "\xE8\x8F\x9F", + "\xDD\xCC" => "\xE8\x90\x8F", + "\xDD\xCD" => "\xE8\x90\x83", + "\xDD\xCE" => "\xE8\x8F\xB8", + "\xDD\xCF" => "\xE8\x8F\xB9", + "\xDD\xD0" => "\xE8\x8F\xAA", + "\xDD\xD1" => "\xE8\x8F\x85", + "\xDD\xD2" => "\xE8\x8F\x80", + "\xDD\xD3" => "\xE8\x90\xA6", + "\xDD\xD4" => "\xE8\x8F\xB0", + "\xDD\xD5" => "\xE8\x8F\xA1", + "\xDD\xD6" => "\xE8\x91\x9C", + "\xDD\xD7" => "\xE8\x91\x91", + "\xDD\xD8" => "\xE8\x91\x9A", + "\xDD\xD9" => "\xE8\x91\x99", + "\xDD\xDA" => "\xE8\x91\xB3", + "\xDD\xDB" => "\xE8\x92\x87", + "\xDD\xDC" => "\xE8\x92\x88", + "\xDD\xDD" => "\xE8\x91\xBA", + "\xDD\xDE" => "\xE8\x92\x89", + "\xDD\xDF" => "\xE8\x91\xB8", + "\xDD\xE0" => "\xE8\x90\xBC", + "\xDD\xE1" => "\xE8\x91\x86", + "\xDD\xE2" => "\xE8\x91\xA9", + "\xDD\xE3" => "\xE8\x91\xB6", + "\xDD\xE4" => "\xE8\x92\x8C", + "\xDD\xE5" => "\xE8\x92\x8E", + "\xDD\xE6" => "\xE8\x90\xB1", + "\xDD\xE7" => "\xE8\x91\xAD", + "\xDD\xE8" => "\xE8\x93\x81", + "\xDD\xE9" => "\xE8\x93\x8D", + "\xDD\xEA" => "\xE8\x93\x90", + "\xDD\xEB" => "\xE8\x93\xA6", + "\xDD\xEC" => "\xE8\x92\xBD", + "\xDD\xED" => "\xE8\x93\x93", + "\xDD\xEE" => "\xE8\x93\x8A", + "\xDD\xEF" => "\xE8\x92\xBF", + "\xDD\xF0" => "\xE8\x92\xBA", + "\xDD\xF1" => "\xE8\x93\xA0", + "\xDD\xF2" => "\xE8\x92\xA1", + "\xDD\xF3" => "\xE8\x92\xB9", + "\xDD\xF4" => "\xE8\x92\xB4", + "\xDD\xF5" => "\xE8\x92\x97", + "\xDD\xF6" => "\xE8\x93\xA5", + "\xDD\xF7" => "\xE8\x93\xA3", + "\xDD\xF8" => "\xE8\x94\x8C", + "\xDD\xF9" => "\xE7\x94\x8D", + "\xDD\xFA" => "\xE8\x94\xB8", + "\xDD\xFB" => "\xE8\x93\xB0", + "\xDD\xFC" => "\xE8\x94\xB9", + "\xDD\xFD" => "\xE8\x94\x9F", + "\xDD\xFE" => "\xE8\x94\xBA", + "\xDE\xA1" => "\xE8\x95\x96", + "\xDE\xA2" => "\xE8\x94\xBB", + "\xDE\xA3" => "\xE8\x93\xBF", + "\xDE\xA4" => "\xE8\x93\xBC", + "\xDE\xA5" => "\xE8\x95\x99", + "\xDE\xA6" => "\xE8\x95\x88", + "\xDE\xA7" => "\xE8\x95\xA8", + "\xDE\xA8" => "\xE8\x95\xA4", + "\xDE\xA9" => "\xE8\x95\x9E", + "\xDE\xAA" => "\xE8\x95\xBA", + "\xDE\xAB" => "\xE7\x9E\xA2", + "\xDE\xAC" => "\xE8\x95\x83", + "\xDE\xAD" => "\xE8\x95\xB2", + "\xDE\xAE" => "\xE8\x95\xBB", + "\xDE\xAF" => "\xE8\x96\xA4", + "\xDE\xB0" => "\xE8\x96\xA8", + "\xDE\xB1" => "\xE8\x96\x87", + "\xDE\xB2" => "\xE8\x96\x8F", + "\xDE\xB3" => "\xE8\x95\xB9", + "\xDE\xB4" => "\xE8\x96\xAE", + "\xDE\xB5" => "\xE8\x96\x9C", + "\xDE\xB6" => "\xE8\x96\x85", + "\xDE\xB7" => "\xE8\x96\xB9", + "\xDE\xB8" => "\xE8\x96\xB7", + "\xDE\xB9" => "\xE8\x96\xB0", + "\xDE\xBA" => "\xE8\x97\x93", + "\xDE\xBB" => "\xE8\x97\x81", + "\xDE\xBC" => "\xE8\x97\x9C", + "\xDE\xBD" => "\xE8\x97\xBF", + "\xDE\xBE" => "\xE8\x98\xA7", + "\xDE\xBF" => "\xE8\x98\x85", + "\xDE\xC0" => "\xE8\x98\xA9", + "\xDE\xC1" => "\xE8\x98\x96", + "\xDE\xC2" => "\xE8\x98\xBC", + "\xDE\xC3" => "\xE5\xBB\xBE", + "\xDE\xC4" => "\xE5\xBC\x88", + "\xDE\xC5" => "\xE5\xA4\xBC", + "\xDE\xC6" => "\xE5\xA5\x81", + "\xDE\xC7" => "\xE8\x80\xB7", + "\xDE\xC8" => "\xE5\xA5\x95", + "\xDE\xC9" => "\xE5\xA5\x9A", + "\xDE\xCA" => "\xE5\xA5\x98", + "\xDE\xCB" => "\xE5\x8C\x8F", + "\xDE\xCC" => "\xE5\xB0\xA2", + "\xDE\xCD" => "\xE5\xB0\xA5", + "\xDE\xCE" => "\xE5\xB0\xAC", + "\xDE\xCF" => "\xE5\xB0\xB4", + "\xDE\xD0" => "\xE6\x89\x8C", + "\xDE\xD1" => "\xE6\x89\xAA", + "\xDE\xD2" => "\xE6\x8A\x9F", + "\xDE\xD3" => "\xE6\x8A\xBB", + "\xDE\xD4" => "\xE6\x8B\x8A", + "\xDE\xD5" => "\xE6\x8B\x9A", + "\xDE\xD6" => "\xE6\x8B\x97", + "\xDE\xD7" => "\xE6\x8B\xAE", + "\xDE\xD8" => "\xE6\x8C\xA2", + "\xDE\xD9" => "\xE6\x8B\xB6", + "\xDE\xDA" => "\xE6\x8C\xB9", + "\xDE\xDB" => "\xE6\x8D\x8B", + "\xDE\xDC" => "\xE6\x8D\x83", + "\xDE\xDD" => "\xE6\x8E\xAD", + "\xDE\xDE" => "\xE6\x8F\xB6", + "\xDE\xDF" => "\xE6\x8D\xB1", + "\xDE\xE0" => "\xE6\x8D\xBA", + "\xDE\xE1" => "\xE6\x8E\x8E", + "\xDE\xE2" => "\xE6\x8E\xB4", + "\xDE\xE3" => "\xE6\x8D\xAD", + "\xDE\xE4" => "\xE6\x8E\xAC", + "\xDE\xE5" => "\xE6\x8E\x8A", + "\xDE\xE6" => "\xE6\x8D\xA9", + "\xDE\xE7" => "\xE6\x8E\xAE", + "\xDE\xE8" => "\xE6\x8E\xBC", + "\xDE\xE9" => "\xE6\x8F\xB2", + "\xDE\xEA" => "\xE6\x8F\xB8", + "\xDE\xEB" => "\xE6\x8F\xA0", + "\xDE\xEC" => "\xE6\x8F\xBF", + "\xDE\xED" => "\xE6\x8F\x84", + "\xDE\xEE" => "\xE6\x8F\x9E", + "\xDE\xEF" => "\xE6\x8F\x8E", + "\xDE\xF0" => "\xE6\x91\x92", + "\xDE\xF1" => "\xE6\x8F\x86", + "\xDE\xF2" => "\xE6\x8E\xBE", + "\xDE\xF3" => "\xE6\x91\x85", + "\xDE\xF4" => "\xE6\x91\x81", + "\xDE\xF5" => "\xE6\x90\x8B", + "\xDE\xF6" => "\xE6\x90\x9B", + "\xDE\xF7" => "\xE6\x90\xA0", + "\xDE\xF8" => "\xE6\x90\x8C", + "\xDE\xF9" => "\xE6\x90\xA6", + "\xDE\xFA" => "\xE6\x90\xA1", + "\xDE\xFB" => "\xE6\x91\x9E", + "\xDE\xFC" => "\xE6\x92\x84", + "\xDE\xFD" => "\xE6\x91\xAD", + "\xDE\xFE" => "\xE6\x92\x96", + "\xDF\xA1" => "\xE6\x91\xBA", + "\xDF\xA2" => "\xE6\x92\xB7", + "\xDF\xA3" => "\xE6\x92\xB8", + "\xDF\xA4" => "\xE6\x92\x99", + "\xDF\xA5" => "\xE6\x92\xBA", + "\xDF\xA6" => "\xE6\x93\x80", + "\xDF\xA7" => "\xE6\x93\x90", + "\xDF\xA8" => "\xE6\x93\x97", + "\xDF\xA9" => "\xE6\x93\xA4", + "\xDF\xAA" => "\xE6\x93\xA2", + "\xDF\xAB" => "\xE6\x94\x89", + "\xDF\xAC" => "\xE6\x94\xA5", + "\xDF\xAD" => "\xE6\x94\xAE", + "\xDF\xAE" => "\xE5\xBC\x8B", + "\xDF\xAF" => "\xE5\xBF\x92", + "\xDF\xB0" => "\xE7\x94\x99", + "\xDF\xB1" => "\xE5\xBC\x91", + "\xDF\xB2" => "\xE5\x8D\x9F", + "\xDF\xB3" => "\xE5\x8F\xB1", + "\xDF\xB4" => "\xE5\x8F\xBD", + "\xDF\xB5" => "\xE5\x8F\xA9", + "\xDF\xB6" => "\xE5\x8F\xA8", + "\xDF\xB7" => "\xE5\x8F\xBB", + "\xDF\xB8" => "\xE5\x90\x92", + "\xDF\xB9" => "\xE5\x90\x96", + "\xDF\xBA" => "\xE5\x90\x86", + "\xDF\xBB" => "\xE5\x91\x8B", + "\xDF\xBC" => "\xE5\x91\x92", + "\xDF\xBD" => "\xE5\x91\x93", + "\xDF\xBE" => "\xE5\x91\x94", + "\xDF\xBF" => "\xE5\x91\x96", + "\xDF\xC0" => "\xE5\x91\x83", + "\xDF\xC1" => "\xE5\x90\xA1", + "\xDF\xC2" => "\xE5\x91\x97", + "\xDF\xC3" => "\xE5\x91\x99", + "\xDF\xC4" => "\xE5\x90\xA3", + "\xDF\xC5" => "\xE5\x90\xB2", + "\xDF\xC6" => "\xE5\x92\x82", + "\xDF\xC7" => "\xE5\x92\x94", + "\xDF\xC8" => "\xE5\x91\xB7", + "\xDF\xC9" => "\xE5\x91\xB1", + "\xDF\xCA" => "\xE5\x91\xA4", + "\xDF\xCB" => "\xE5\x92\x9A", + "\xDF\xCC" => "\xE5\x92\x9B", + "\xDF\xCD" => "\xE5\x92\x84", + "\xDF\xCE" => "\xE5\x91\xB6", + "\xDF\xCF" => "\xE5\x91\xA6", + "\xDF\xD0" => "\xE5\x92\x9D", + "\xDF\xD1" => "\xE5\x93\x90", + "\xDF\xD2" => "\xE5\x92\xAD", + "\xDF\xD3" => "\xE5\x93\x82", + "\xDF\xD4" => "\xE5\x92\xB4", + "\xDF\xD5" => "\xE5\x93\x92", + "\xDF\xD6" => "\xE5\x92\xA7", + "\xDF\xD7" => "\xE5\x92\xA6", + "\xDF\xD8" => "\xE5\x93\x93", + "\xDF\xD9" => "\xE5\x93\x94", + "\xDF\xDA" => "\xE5\x91\xB2", + "\xDF\xDB" => "\xE5\x92\xA3", + "\xDF\xDC" => "\xE5\x93\x95", + "\xDF\xDD" => "\xE5\x92\xBB", + "\xDF\xDE" => "\xE5\x92\xBF", + "\xDF\xDF" => "\xE5\x93\x8C", + "\xDF\xE0" => "\xE5\x93\x99", + "\xDF\xE1" => "\xE5\x93\x9A", + "\xDF\xE2" => "\xE5\x93\x9C", + "\xDF\xE3" => "\xE5\x92\xA9", + "\xDF\xE4" => "\xE5\x92\xAA", + "\xDF\xE5" => "\xE5\x92\xA4", + "\xDF\xE6" => "\xE5\x93\x9D", + "\xDF\xE7" => "\xE5\x93\x8F", + "\xDF\xE8" => "\xE5\x93\x9E", + "\xDF\xE9" => "\xE5\x94\x9B", + "\xDF\xEA" => "\xE5\x93\xA7", + "\xDF\xEB" => "\xE5\x94\xA0", + "\xDF\xEC" => "\xE5\x93\xBD", + "\xDF\xED" => "\xE5\x94\x94", + "\xDF\xEE" => "\xE5\x93\xB3", + "\xDF\xEF" => "\xE5\x94\xA2", + "\xDF\xF0" => "\xE5\x94\xA3", + "\xDF\xF1" => "\xE5\x94\x8F", + "\xDF\xF2" => "\xE5\x94\x91", + "\xDF\xF3" => "\xE5\x94\xA7", + "\xDF\xF4" => "\xE5\x94\xAA", + "\xDF\xF5" => "\xE5\x95\xA7", + "\xDF\xF6" => "\xE5\x96\x8F", + "\xDF\xF7" => "\xE5\x96\xB5", + "\xDF\xF8" => "\xE5\x95\x89", + "\xDF\xF9" => "\xE5\x95\xAD", + "\xDF\xFA" => "\xE5\x95\x81", + "\xDF\xFB" => "\xE5\x95\x95", + "\xDF\xFC" => "\xE5\x94\xBF", + "\xDF\xFD" => "\xE5\x95\x90", + "\xDF\xFE" => "\xE5\x94\xBC", + "\xE0\xA1" => "\xE5\x94\xB7", + "\xE0\xA2" => "\xE5\x95\x96", + "\xE0\xA3" => "\xE5\x95\xB5", + "\xE0\xA4" => "\xE5\x95\xB6", + "\xE0\xA5" => "\xE5\x95\xB7", + "\xE0\xA6" => "\xE5\x94\xB3", + "\xE0\xA7" => "\xE5\x94\xB0", + "\xE0\xA8" => "\xE5\x95\x9C", + "\xE0\xA9" => "\xE5\x96\x8B", + "\xE0\xAA" => "\xE5\x97\x92", + "\xE0\xAB" => "\xE5\x96\x83", + "\xE0\xAC" => "\xE5\x96\xB1", + "\xE0\xAD" => "\xE5\x96\xB9", + "\xE0\xAE" => "\xE5\x96\x88", + "\xE0\xAF" => "\xE5\x96\x81", + "\xE0\xB0" => "\xE5\x96\x9F", + "\xE0\xB1" => "\xE5\x95\xBE", + "\xE0\xB2" => "\xE5\x97\x96", + "\xE0\xB3" => "\xE5\x96\x91", + "\xE0\xB4" => "\xE5\x95\xBB", + "\xE0\xB5" => "\xE5\x97\x9F", + "\xE0\xB6" => "\xE5\x96\xBD", + "\xE0\xB7" => "\xE5\x96\xBE", + "\xE0\xB8" => "\xE5\x96\x94", + "\xE0\xB9" => "\xE5\x96\x99", + "\xE0\xBA" => "\xE5\x97\xAA", + "\xE0\xBB" => "\xE5\x97\xB7", + "\xE0\xBC" => "\xE5\x97\x89", + "\xE0\xBD" => "\xE5\x98\x9F", + "\xE0\xBE" => "\xE5\x97\x91", + "\xE0\xBF" => "\xE5\x97\xAB", + "\xE0\xC0" => "\xE5\x97\xAC", + "\xE0\xC1" => "\xE5\x97\x94", + "\xE0\xC2" => "\xE5\x97\xA6", + "\xE0\xC3" => "\xE5\x97\x9D", + "\xE0\xC4" => "\xE5\x97\x84", + "\xE0\xC5" => "\xE5\x97\xAF", + "\xE0\xC6" => "\xE5\x97\xA5", + "\xE0\xC7" => "\xE5\x97\xB2", + "\xE0\xC8" => "\xE5\x97\xB3", + "\xE0\xC9" => "\xE5\x97\x8C", + "\xE0\xCA" => "\xE5\x97\x8D", + "\xE0\xCB" => "\xE5\x97\xA8", + "\xE0\xCC" => "\xE5\x97\xB5", + "\xE0\xCD" => "\xE5\x97\xA4", + "\xE0\xCE" => "\xE8\xBE\x94", + "\xE0\xCF" => "\xE5\x98\x9E", + "\xE0\xD0" => "\xE5\x98\x88", + "\xE0\xD1" => "\xE5\x98\x8C", + "\xE0\xD2" => "\xE5\x98\x81", + "\xE0\xD3" => "\xE5\x98\xA4", + "\xE0\xD4" => "\xE5\x98\xA3", + "\xE0\xD5" => "\xE5\x97\xBE", + "\xE0\xD6" => "\xE5\x98\x80", + "\xE0\xD7" => "\xE5\x98\xA7", + "\xE0\xD8" => "\xE5\x98\xAD", + "\xE0\xD9" => "\xE5\x99\x98", + "\xE0\xDA" => "\xE5\x98\xB9", + "\xE0\xDB" => "\xE5\x99\x97", + "\xE0\xDC" => "\xE5\x98\xAC", + "\xE0\xDD" => "\xE5\x99\x8D", + "\xE0\xDE" => "\xE5\x99\xA2", + "\xE0\xDF" => "\xE5\x99\x99", + "\xE0\xE0" => "\xE5\x99\x9C", + "\xE0\xE1" => "\xE5\x99\x8C", + "\xE0\xE2" => "\xE5\x99\x94", + "\xE0\xE3" => "\xE5\x9A\x86", + "\xE0\xE4" => "\xE5\x99\xA4", + "\xE0\xE5" => "\xE5\x99\xB1", + "\xE0\xE6" => "\xE5\x99\xAB", + "\xE0\xE7" => "\xE5\x99\xBB", + "\xE0\xE8" => "\xE5\x99\xBC", + "\xE0\xE9" => "\xE5\x9A\x85", + "\xE0\xEA" => "\xE5\x9A\x93", + "\xE0\xEB" => "\xE5\x9A\xAF", + "\xE0\xEC" => "\xE5\x9B\x94", + "\xE0\xED" => "\xE5\x9B\x97", + "\xE0\xEE" => "\xE5\x9B\x9D", + "\xE0\xEF" => "\xE5\x9B\xA1", + "\xE0\xF0" => "\xE5\x9B\xB5", + "\xE0\xF1" => "\xE5\x9B\xAB", + "\xE0\xF2" => "\xE5\x9B\xB9", + "\xE0\xF3" => "\xE5\x9B\xBF", + "\xE0\xF4" => "\xE5\x9C\x84", + "\xE0\xF5" => "\xE5\x9C\x8A", + "\xE0\xF6" => "\xE5\x9C\x89", + "\xE0\xF7" => "\xE5\x9C\x9C", + "\xE0\xF8" => "\xE5\xB8\x8F", + "\xE0\xF9" => "\xE5\xB8\x99", + "\xE0\xFA" => "\xE5\xB8\x94", + "\xE0\xFB" => "\xE5\xB8\x91", + "\xE0\xFC" => "\xE5\xB8\xB1", + "\xE0\xFD" => "\xE5\xB8\xBB", + "\xE0\xFE" => "\xE5\xB8\xBC", + "\xE1\xA1" => "\xE5\xB8\xB7", + "\xE1\xA2" => "\xE5\xB9\x84", + "\xE1\xA3" => "\xE5\xB9\x94", + "\xE1\xA4" => "\xE5\xB9\x9B", + "\xE1\xA5" => "\xE5\xB9\x9E", + "\xE1\xA6" => "\xE5\xB9\xA1", + "\xE1\xA7" => "\xE5\xB2\x8C", + "\xE1\xA8" => "\xE5\xB1\xBA", + "\xE1\xA9" => "\xE5\xB2\x8D", + "\xE1\xAA" => "\xE5\xB2\x90", + "\xE1\xAB" => "\xE5\xB2\x96", + "\xE1\xAC" => "\xE5\xB2\x88", + "\xE1\xAD" => "\xE5\xB2\x98", + "\xE1\xAE" => "\xE5\xB2\x99", + "\xE1\xAF" => "\xE5\xB2\x91", + "\xE1\xB0" => "\xE5\xB2\x9A", + "\xE1\xB1" => "\xE5\xB2\x9C", + "\xE1\xB2" => "\xE5\xB2\xB5", + "\xE1\xB3" => "\xE5\xB2\xA2", + "\xE1\xB4" => "\xE5\xB2\xBD", + "\xE1\xB5" => "\xE5\xB2\xAC", + "\xE1\xB6" => "\xE5\xB2\xAB", + "\xE1\xB7" => "\xE5\xB2\xB1", + "\xE1\xB8" => "\xE5\xB2\xA3", + "\xE1\xB9" => "\xE5\xB3\x81", + "\xE1\xBA" => "\xE5\xB2\xB7", + "\xE1\xBB" => "\xE5\xB3\x84", + "\xE1\xBC" => "\xE5\xB3\x92", + "\xE1\xBD" => "\xE5\xB3\xA4", + "\xE1\xBE" => "\xE5\xB3\x8B", + "\xE1\xBF" => "\xE5\xB3\xA5", + "\xE1\xC0" => "\xE5\xB4\x82", + "\xE1\xC1" => "\xE5\xB4\x83", + "\xE1\xC2" => "\xE5\xB4\xA7", + "\xE1\xC3" => "\xE5\xB4\xA6", + "\xE1\xC4" => "\xE5\xB4\xAE", + "\xE1\xC5" => "\xE5\xB4\xA4", + "\xE1\xC6" => "\xE5\xB4\x9E", + "\xE1\xC7" => "\xE5\xB4\x86", + "\xE1\xC8" => "\xE5\xB4\x9B", + "\xE1\xC9" => "\xE5\xB5\x98", + "\xE1\xCA" => "\xE5\xB4\xBE", + "\xE1\xCB" => "\xE5\xB4\xB4", + "\xE1\xCC" => "\xE5\xB4\xBD", + "\xE1\xCD" => "\xE5\xB5\xAC", + "\xE1\xCE" => "\xE5\xB5\x9B", + "\xE1\xCF" => "\xE5\xB5\xAF", + "\xE1\xD0" => "\xE5\xB5\x9D", + "\xE1\xD1" => "\xE5\xB5\xAB", + "\xE1\xD2" => "\xE5\xB5\x8B", + "\xE1\xD3" => "\xE5\xB5\x8A", + "\xE1\xD4" => "\xE5\xB5\xA9", + "\xE1\xD5" => "\xE5\xB5\xB4", + "\xE1\xD6" => "\xE5\xB6\x82", + "\xE1\xD7" => "\xE5\xB6\x99", + "\xE1\xD8" => "\xE5\xB6\x9D", + "\xE1\xD9" => "\xE8\xB1\xB3", + "\xE1\xDA" => "\xE5\xB6\xB7", + "\xE1\xDB" => "\xE5\xB7\x85", + "\xE1\xDC" => "\xE5\xBD\xB3", + "\xE1\xDD" => "\xE5\xBD\xB7", + "\xE1\xDE" => "\xE5\xBE\x82", + "\xE1\xDF" => "\xE5\xBE\x87", + "\xE1\xE0" => "\xE5\xBE\x89", + "\xE1\xE1" => "\xE5\xBE\x8C", + "\xE1\xE2" => "\xE5\xBE\x95", + "\xE1\xE3" => "\xE5\xBE\x99", + "\xE1\xE4" => "\xE5\xBE\x9C", + "\xE1\xE5" => "\xE5\xBE\xA8", + "\xE1\xE6" => "\xE5\xBE\xAD", + "\xE1\xE7" => "\xE5\xBE\xB5", + "\xE1\xE8" => "\xE5\xBE\xBC", + "\xE1\xE9" => "\xE8\xA1\xA2", + "\xE1\xEA" => "\xE5\xBD\xA1", + "\xE1\xEB" => "\xE7\x8A\xAD", + "\xE1\xEC" => "\xE7\x8A\xB0", + "\xE1\xED" => "\xE7\x8A\xB4", + "\xE1\xEE" => "\xE7\x8A\xB7", + "\xE1\xEF" => "\xE7\x8A\xB8", + "\xE1\xF0" => "\xE7\x8B\x83", + "\xE1\xF1" => "\xE7\x8B\x81", + "\xE1\xF2" => "\xE7\x8B\x8E", + "\xE1\xF3" => "\xE7\x8B\x8D", + "\xE1\xF4" => "\xE7\x8B\x92", + "\xE1\xF5" => "\xE7\x8B\xA8", + "\xE1\xF6" => "\xE7\x8B\xAF", + "\xE1\xF7" => "\xE7\x8B\xA9", + "\xE1\xF8" => "\xE7\x8B\xB2", + "\xE1\xF9" => "\xE7\x8B\xB4", + "\xE1\xFA" => "\xE7\x8B\xB7", + "\xE1\xFB" => "\xE7\x8C\x81", + "\xE1\xFC" => "\xE7\x8B\xB3", + "\xE1\xFD" => "\xE7\x8C\x83", + "\xE1\xFE" => "\xE7\x8B\xBA", + "\xE2\xA1" => "\xE7\x8B\xBB", + "\xE2\xA2" => "\xE7\x8C\x97", + "\xE2\xA3" => "\xE7\x8C\x93", + "\xE2\xA4" => "\xE7\x8C\xA1", + "\xE2\xA5" => "\xE7\x8C\x8A", + "\xE2\xA6" => "\xE7\x8C\x9E", + "\xE2\xA7" => "\xE7\x8C\x9D", + "\xE2\xA8" => "\xE7\x8C\x95", + "\xE2\xA9" => "\xE7\x8C\xA2", + "\xE2\xAA" => "\xE7\x8C\xB9", + "\xE2\xAB" => "\xE7\x8C\xA5", + "\xE2\xAC" => "\xE7\x8C\xAC", + "\xE2\xAD" => "\xE7\x8C\xB8", + "\xE2\xAE" => "\xE7\x8C\xB1", + "\xE2\xAF" => "\xE7\x8D\x90", + "\xE2\xB0" => "\xE7\x8D\x8D", + "\xE2\xB1" => "\xE7\x8D\x97", + "\xE2\xB2" => "\xE7\x8D\xA0", + "\xE2\xB3" => "\xE7\x8D\xAC", + "\xE2\xB4" => "\xE7\x8D\xAF", + "\xE2\xB5" => "\xE7\x8D\xBE", + "\xE2\xB6" => "\xE8\x88\x9B", + "\xE2\xB7" => "\xE5\xA4\xA5", + "\xE2\xB8" => "\xE9\xA3\xA7", + "\xE2\xB9" => "\xE5\xA4\xA4", + "\xE2\xBA" => "\xE5\xA4\x82", + "\xE2\xBB" => "\xE9\xA5\xA3", + "\xE2\xBC" => "\xE9\xA5\xA7", + "\xE2\xBD" => "\xE9\xA5\xA8", + "\xE2\xBE" => "\xE9\xA5\xA9", + "\xE2\xBF" => "\xE9\xA5\xAA", + "\xE2\xC0" => "\xE9\xA5\xAB", + "\xE2\xC1" => "\xE9\xA5\xAC", + "\xE2\xC2" => "\xE9\xA5\xB4", + "\xE2\xC3" => "\xE9\xA5\xB7", + "\xE2\xC4" => "\xE9\xA5\xBD", + "\xE2\xC5" => "\xE9\xA6\x80", + "\xE2\xC6" => "\xE9\xA6\x84", + "\xE2\xC7" => "\xE9\xA6\x87", + "\xE2\xC8" => "\xE9\xA6\x8A", + "\xE2\xC9" => "\xE9\xA6\x8D", + "\xE2\xCA" => "\xE9\xA6\x90", + "\xE2\xCB" => "\xE9\xA6\x91", + "\xE2\xCC" => "\xE9\xA6\x93", + "\xE2\xCD" => "\xE9\xA6\x94", + "\xE2\xCE" => "\xE9\xA6\x95", + "\xE2\xCF" => "\xE5\xBA\x80", + "\xE2\xD0" => "\xE5\xBA\x91", + "\xE2\xD1" => "\xE5\xBA\x8B", + "\xE2\xD2" => "\xE5\xBA\x96", + "\xE2\xD3" => "\xE5\xBA\xA5", + "\xE2\xD4" => "\xE5\xBA\xA0", + "\xE2\xD5" => "\xE5\xBA\xB9", + "\xE2\xD6" => "\xE5\xBA\xB5", + "\xE2\xD7" => "\xE5\xBA\xBE", + "\xE2\xD8" => "\xE5\xBA\xB3", + "\xE2\xD9" => "\xE8\xB5\x93", + "\xE2\xDA" => "\xE5\xBB\x92", + "\xE2\xDB" => "\xE5\xBB\x91", + "\xE2\xDC" => "\xE5\xBB\x9B", + "\xE2\xDD" => "\xE5\xBB\xA8", + "\xE2\xDE" => "\xE5\xBB\xAA", + "\xE2\xDF" => "\xE8\x86\xBA", + "\xE2\xE0" => "\xE5\xBF\x84", + "\xE2\xE1" => "\xE5\xBF\x89", + "\xE2\xE2" => "\xE5\xBF\x96", + "\xE2\xE3" => "\xE5\xBF\x8F", + "\xE2\xE4" => "\xE6\x80\x83", + "\xE2\xE5" => "\xE5\xBF\xAE", + "\xE2\xE6" => "\xE6\x80\x84", + "\xE2\xE7" => "\xE5\xBF\xA1", + "\xE2\xE8" => "\xE5\xBF\xA4", + "\xE2\xE9" => "\xE5\xBF\xBE", + "\xE2\xEA" => "\xE6\x80\x85", + "\xE2\xEB" => "\xE6\x80\x86", + "\xE2\xEC" => "\xE5\xBF\xAA", + "\xE2\xED" => "\xE5\xBF\xAD", + "\xE2\xEE" => "\xE5\xBF\xB8", + "\xE2\xEF" => "\xE6\x80\x99", + "\xE2\xF0" => "\xE6\x80\xB5", + "\xE2\xF1" => "\xE6\x80\xA6", + "\xE2\xF2" => "\xE6\x80\x9B", + "\xE2\xF3" => "\xE6\x80\x8F", + "\xE2\xF4" => "\xE6\x80\x8D", + "\xE2\xF5" => "\xE6\x80\xA9", + "\xE2\xF6" => "\xE6\x80\xAB", + "\xE2\xF7" => "\xE6\x80\x8A", + "\xE2\xF8" => "\xE6\x80\xBF", + "\xE2\xF9" => "\xE6\x80\xA1", + "\xE2\xFA" => "\xE6\x81\xB8", + "\xE2\xFB" => "\xE6\x81\xB9", + "\xE2\xFC" => "\xE6\x81\xBB", + "\xE2\xFD" => "\xE6\x81\xBA", + "\xE2\xFE" => "\xE6\x81\x82", + "\xE3\xA1" => "\xE6\x81\xAA", + "\xE3\xA2" => "\xE6\x81\xBD", + "\xE3\xA3" => "\xE6\x82\x96", + "\xE3\xA4" => "\xE6\x82\x9A", + "\xE3\xA5" => "\xE6\x82\xAD", + "\xE3\xA6" => "\xE6\x82\x9D", + "\xE3\xA7" => "\xE6\x82\x83", + "\xE3\xA8" => "\xE6\x82\x92", + "\xE3\xA9" => "\xE6\x82\x8C", + "\xE3\xAA" => "\xE6\x82\x9B", + "\xE3\xAB" => "\xE6\x83\xAC", + "\xE3\xAC" => "\xE6\x82\xBB", + "\xE3\xAD" => "\xE6\x82\xB1", + "\xE3\xAE" => "\xE6\x83\x9D", + "\xE3\xAF" => "\xE6\x83\x98", + "\xE3\xB0" => "\xE6\x83\x86", + "\xE3\xB1" => "\xE6\x83\x9A", + "\xE3\xB2" => "\xE6\x82\xB4", + "\xE3\xB3" => "\xE6\x84\xA0", + "\xE3\xB4" => "\xE6\x84\xA6", + "\xE3\xB5" => "\xE6\x84\x95", + "\xE3\xB6" => "\xE6\x84\xA3", + "\xE3\xB7" => "\xE6\x83\xB4", + "\xE3\xB8" => "\xE6\x84\x80", + "\xE3\xB9" => "\xE6\x84\x8E", + "\xE3\xBA" => "\xE6\x84\xAB", + "\xE3\xBB" => "\xE6\x85\x8A", + "\xE3\xBC" => "\xE6\x85\xB5", + "\xE3\xBD" => "\xE6\x86\xAC", + "\xE3\xBE" => "\xE6\x86\x94", + "\xE3\xBF" => "\xE6\x86\xA7", + "\xE3\xC0" => "\xE6\x86\xB7", + "\xE3\xC1" => "\xE6\x87\x94", + "\xE3\xC2" => "\xE6\x87\xB5", + "\xE3\xC3" => "\xE5\xBF\x9D", + "\xE3\xC4" => "\xE9\x9A\xB3", + "\xE3\xC5" => "\xE9\x97\xA9", + "\xE3\xC6" => "\xE9\x97\xAB", + "\xE3\xC7" => "\xE9\x97\xB1", + "\xE3\xC8" => "\xE9\x97\xB3", + "\xE3\xC9" => "\xE9\x97\xB5", + "\xE3\xCA" => "\xE9\x97\xB6", + "\xE3\xCB" => "\xE9\x97\xBC", + "\xE3\xCC" => "\xE9\x97\xBE", + "\xE3\xCD" => "\xE9\x98\x83", + "\xE3\xCE" => "\xE9\x98\x84", + "\xE3\xCF" => "\xE9\x98\x86", + "\xE3\xD0" => "\xE9\x98\x88", + "\xE3\xD1" => "\xE9\x98\x8A", + "\xE3\xD2" => "\xE9\x98\x8B", + "\xE3\xD3" => "\xE9\x98\x8C", + "\xE3\xD4" => "\xE9\x98\x8D", + "\xE3\xD5" => "\xE9\x98\x8F", + "\xE3\xD6" => "\xE9\x98\x92", + "\xE3\xD7" => "\xE9\x98\x95", + "\xE3\xD8" => "\xE9\x98\x96", + "\xE3\xD9" => "\xE9\x98\x97", + "\xE3\xDA" => "\xE9\x98\x99", + "\xE3\xDB" => "\xE9\x98\x9A", + "\xE3\xDC" => "\xE4\xB8\xAC", + "\xE3\xDD" => "\xE7\x88\xBF", + "\xE3\xDE" => "\xE6\x88\x95", + "\xE3\xDF" => "\xE6\xB0\xB5", + "\xE3\xE0" => "\xE6\xB1\x94", + "\xE3\xE1" => "\xE6\xB1\x9C", + "\xE3\xE2" => "\xE6\xB1\x8A", + "\xE3\xE3" => "\xE6\xB2\xA3", + "\xE3\xE4" => "\xE6\xB2\x85", + "\xE3\xE5" => "\xE6\xB2\x90", + "\xE3\xE6" => "\xE6\xB2\x94", + "\xE3\xE7" => "\xE6\xB2\x8C", + "\xE3\xE8" => "\xE6\xB1\xA8", + "\xE3\xE9" => "\xE6\xB1\xA9", + "\xE3\xEA" => "\xE6\xB1\xB4", + "\xE3\xEB" => "\xE6\xB1\xB6", + "\xE3\xEC" => "\xE6\xB2\x86", + "\xE3\xED" => "\xE6\xB2\xA9", + "\xE3\xEE" => "\xE6\xB3\x90", + "\xE3\xEF" => "\xE6\xB3\x94", + "\xE3\xF0" => "\xE6\xB2\xAD", + "\xE3\xF1" => "\xE6\xB3\xB7", + "\xE3\xF2" => "\xE6\xB3\xB8", + "\xE3\xF3" => "\xE6\xB3\xB1", + "\xE3\xF4" => "\xE6\xB3\x97", + "\xE3\xF5" => "\xE6\xB2\xB2", + "\xE3\xF6" => "\xE6\xB3\xA0", + "\xE3\xF7" => "\xE6\xB3\x96", + "\xE3\xF8" => "\xE6\xB3\xBA", + "\xE3\xF9" => "\xE6\xB3\xAB", + "\xE3\xFA" => "\xE6\xB3\xAE", + "\xE3\xFB" => "\xE6\xB2\xB1", + "\xE3\xFC" => "\xE6\xB3\x93", + "\xE3\xFD" => "\xE6\xB3\xAF", + "\xE3\xFE" => "\xE6\xB3\xBE", + "\xE4\xA1" => "\xE6\xB4\xB9", + "\xE4\xA2" => "\xE6\xB4\xA7", + "\xE4\xA3" => "\xE6\xB4\x8C", + "\xE4\xA4" => "\xE6\xB5\x83", + "\xE4\xA5" => "\xE6\xB5\x88", + "\xE4\xA6" => "\xE6\xB4\x87", + "\xE4\xA7" => "\xE6\xB4\x84", + "\xE4\xA8" => "\xE6\xB4\x99", + "\xE4\xA9" => "\xE6\xB4\x8E", + "\xE4\xAA" => "\xE6\xB4\xAB", + "\xE4\xAB" => "\xE6\xB5\x8D", + "\xE4\xAC" => "\xE6\xB4\xAE", + "\xE4\xAD" => "\xE6\xB4\xB5", + "\xE4\xAE" => "\xE6\xB4\x9A", + "\xE4\xAF" => "\xE6\xB5\x8F", + "\xE4\xB0" => "\xE6\xB5\x92", + "\xE4\xB1" => "\xE6\xB5\x94", + "\xE4\xB2" => "\xE6\xB4\xB3", + "\xE4\xB3" => "\xE6\xB6\x91", + "\xE4\xB4" => "\xE6\xB5\xAF", + "\xE4\xB5" => "\xE6\xB6\x9E", + "\xE4\xB6" => "\xE6\xB6\xA0", + "\xE4\xB7" => "\xE6\xB5\x9E", + "\xE4\xB8" => "\xE6\xB6\x93", + "\xE4\xB9" => "\xE6\xB6\x94", + "\xE4\xBA" => "\xE6\xB5\x9C", + "\xE4\xBB" => "\xE6\xB5\xA0", + "\xE4\xBC" => "\xE6\xB5\xBC", + "\xE4\xBD" => "\xE6\xB5\xA3", + "\xE4\xBE" => "\xE6\xB8\x9A", + "\xE4\xBF" => "\xE6\xB7\x87", + "\xE4\xC0" => "\xE6\xB7\x85", + "\xE4\xC1" => "\xE6\xB7\x9E", + "\xE4\xC2" => "\xE6\xB8\x8E", + "\xE4\xC3" => "\xE6\xB6\xBF", + "\xE4\xC4" => "\xE6\xB7\xA0", + "\xE4\xC5" => "\xE6\xB8\x91", + "\xE4\xC6" => "\xE6\xB7\xA6", + "\xE4\xC7" => "\xE6\xB7\x9D", + "\xE4\xC8" => "\xE6\xB7\x99", + "\xE4\xC9" => "\xE6\xB8\x96", + "\xE4\xCA" => "\xE6\xB6\xAB", + "\xE4\xCB" => "\xE6\xB8\x8C", + "\xE4\xCC" => "\xE6\xB6\xAE", + "\xE4\xCD" => "\xE6\xB8\xAB", + "\xE4\xCE" => "\xE6\xB9\xAE", + "\xE4\xCF" => "\xE6\xB9\x8E", + "\xE4\xD0" => "\xE6\xB9\xAB", + "\xE4\xD1" => "\xE6\xBA\xB2", + "\xE4\xD2" => "\xE6\xB9\x9F", + "\xE4\xD3" => "\xE6\xBA\x86", + "\xE4\xD4" => "\xE6\xB9\x93", + "\xE4\xD5" => "\xE6\xB9\x94", + "\xE4\xD6" => "\xE6\xB8\xB2", + "\xE4\xD7" => "\xE6\xB8\xA5", + "\xE4\xD8" => "\xE6\xB9\x84", + "\xE4\xD9" => "\xE6\xBB\x9F", + "\xE4\xDA" => "\xE6\xBA\xB1", + "\xE4\xDB" => "\xE6\xBA\x98", + "\xE4\xDC" => "\xE6\xBB\xA0", + "\xE4\xDD" => "\xE6\xBC\xAD", + "\xE4\xDE" => "\xE6\xBB\xA2", + "\xE4\xDF" => "\xE6\xBA\xA5", + "\xE4\xE0" => "\xE6\xBA\xA7", + "\xE4\xE1" => "\xE6\xBA\xBD", + "\xE4\xE2" => "\xE6\xBA\xBB", + "\xE4\xE3" => "\xE6\xBA\xB7", + "\xE4\xE4" => "\xE6\xBB\x97", + "\xE4\xE5" => "\xE6\xBA\xB4", + "\xE4\xE6" => "\xE6\xBB\x8F", + "\xE4\xE7" => "\xE6\xBA\x8F", + "\xE4\xE8" => "\xE6\xBB\x82", + "\xE4\xE9" => "\xE6\xBA\x9F", + "\xE4\xEA" => "\xE6\xBD\xA2", + "\xE4\xEB" => "\xE6\xBD\x86", + "\xE4\xEC" => "\xE6\xBD\x87", + "\xE4\xED" => "\xE6\xBC\xA4", + "\xE4\xEE" => "\xE6\xBC\x95", + "\xE4\xEF" => "\xE6\xBB\xB9", + "\xE4\xF0" => "\xE6\xBC\xAF", + "\xE4\xF1" => "\xE6\xBC\xB6", + "\xE4\xF2" => "\xE6\xBD\x8B", + "\xE4\xF3" => "\xE6\xBD\xB4", + "\xE4\xF4" => "\xE6\xBC\xAA", + "\xE4\xF5" => "\xE6\xBC\x89", + "\xE4\xF6" => "\xE6\xBC\xA9", + "\xE4\xF7" => "\xE6\xBE\x89", + "\xE4\xF8" => "\xE6\xBE\x8D", + "\xE4\xF9" => "\xE6\xBE\x8C", + "\xE4\xFA" => "\xE6\xBD\xB8", + "\xE4\xFB" => "\xE6\xBD\xB2", + "\xE4\xFC" => "\xE6\xBD\xBC", + "\xE4\xFD" => "\xE6\xBD\xBA", + "\xE4\xFE" => "\xE6\xBF\x91", + "\xE5\xA1" => "\xE6\xBF\x89", + "\xE5\xA2" => "\xE6\xBE\xA7", + "\xE5\xA3" => "\xE6\xBE\xB9", + "\xE5\xA4" => "\xE6\xBE\xB6", + "\xE5\xA5" => "\xE6\xBF\x82", + "\xE5\xA6" => "\xE6\xBF\xA1", + "\xE5\xA7" => "\xE6\xBF\xAE", + "\xE5\xA8" => "\xE6\xBF\x9E", + "\xE5\xA9" => "\xE6\xBF\xA0", + "\xE5\xAA" => "\xE6\xBF\xAF", + "\xE5\xAB" => "\xE7\x80\x9A", + "\xE5\xAC" => "\xE7\x80\xA3", + "\xE5\xAD" => "\xE7\x80\x9B", + "\xE5\xAE" => "\xE7\x80\xB9", + "\xE5\xAF" => "\xE7\x80\xB5", + "\xE5\xB0" => "\xE7\x81\x8F", + "\xE5\xB1" => "\xE7\x81\x9E", + "\xE5\xB2" => "\xE5\xAE\x80", + "\xE5\xB3" => "\xE5\xAE\x84", + "\xE5\xB4" => "\xE5\xAE\x95", + "\xE5\xB5" => "\xE5\xAE\x93", + "\xE5\xB6" => "\xE5\xAE\xA5", + "\xE5\xB7" => "\xE5\xAE\xB8", + "\xE5\xB8" => "\xE7\x94\xAF", + "\xE5\xB9" => "\xE9\xAA\x9E", + "\xE5\xBA" => "\xE6\x90\xB4", + "\xE5\xBB" => "\xE5\xAF\xA4", + "\xE5\xBC" => "\xE5\xAF\xAE", + "\xE5\xBD" => "\xE8\xA4\xB0", + "\xE5\xBE" => "\xE5\xAF\xB0", + "\xE5\xBF" => "\xE8\xB9\x87", + "\xE5\xC0" => "\xE8\xAC\x87", + "\xE5\xC1" => "\xE8\xBE\xB6", + "\xE5\xC2" => "\xE8\xBF\x93", + "\xE5\xC3" => "\xE8\xBF\x95", + "\xE5\xC4" => "\xE8\xBF\xA5", + "\xE5\xC5" => "\xE8\xBF\xAE", + "\xE5\xC6" => "\xE8\xBF\xA4", + "\xE5\xC7" => "\xE8\xBF\xA9", + "\xE5\xC8" => "\xE8\xBF\xA6", + "\xE5\xC9" => "\xE8\xBF\xB3", + "\xE5\xCA" => "\xE8\xBF\xA8", + "\xE5\xCB" => "\xE9\x80\x85", + "\xE5\xCC" => "\xE9\x80\x84", + "\xE5\xCD" => "\xE9\x80\x8B", + "\xE5\xCE" => "\xE9\x80\xA6", + "\xE5\xCF" => "\xE9\x80\x91", + "\xE5\xD0" => "\xE9\x80\x8D", + "\xE5\xD1" => "\xE9\x80\x96", + "\xE5\xD2" => "\xE9\x80\xA1", + "\xE5\xD3" => "\xE9\x80\xB5", + "\xE5\xD4" => "\xE9\x80\xB6", + "\xE5\xD5" => "\xE9\x80\xAD", + "\xE5\xD6" => "\xE9\x80\xAF", + "\xE5\xD7" => "\xE9\x81\x84", + "\xE5\xD8" => "\xE9\x81\x91", + "\xE5\xD9" => "\xE9\x81\x92", + "\xE5\xDA" => "\xE9\x81\x90", + "\xE5\xDB" => "\xE9\x81\xA8", + "\xE5\xDC" => "\xE9\x81\x98", + "\xE5\xDD" => "\xE9\x81\xA2", + "\xE5\xDE" => "\xE9\x81\x9B", + "\xE5\xDF" => "\xE6\x9A\xB9", + "\xE5\xE0" => "\xE9\x81\xB4", + "\xE5\xE1" => "\xE9\x81\xBD", + "\xE5\xE2" => "\xE9\x82\x82", + "\xE5\xE3" => "\xE9\x82\x88", + "\xE5\xE4" => "\xE9\x82\x83", + "\xE5\xE5" => "\xE9\x82\x8B", + "\xE5\xE6" => "\xE5\xBD\x90", + "\xE5\xE7" => "\xE5\xBD\x97", + "\xE5\xE8" => "\xE5\xBD\x96", + "\xE5\xE9" => "\xE5\xBD\x98", + "\xE5\xEA" => "\xE5\xB0\xBB", + "\xE5\xEB" => "\xE5\x92\xAB", + "\xE5\xEC" => "\xE5\xB1\x90", + "\xE5\xED" => "\xE5\xB1\x99", + "\xE5\xEE" => "\xE5\xAD\xB1", + "\xE5\xEF" => "\xE5\xB1\xA3", + "\xE5\xF0" => "\xE5\xB1\xA6", + "\xE5\xF1" => "\xE7\xBE\xBC", + "\xE5\xF2" => "\xE5\xBC\xAA", + "\xE5\xF3" => "\xE5\xBC\xA9", + "\xE5\xF4" => "\xE5\xBC\xAD", + "\xE5\xF5" => "\xE8\x89\xB4", + "\xE5\xF6" => "\xE5\xBC\xBC", + "\xE5\xF7" => "\xE9\xAC\xBB", + "\xE5\xF8" => "\xE5\xB1\xAE", + "\xE5\xF9" => "\xE5\xA6\x81", + "\xE5\xFA" => "\xE5\xA6\x83", + "\xE5\xFB" => "\xE5\xA6\x8D", + "\xE5\xFC" => "\xE5\xA6\xA9", + "\xE5\xFD" => "\xE5\xA6\xAA", + "\xE5\xFE" => "\xE5\xA6\xA3", + "\xE6\xA1" => "\xE5\xA6\x97", + "\xE6\xA2" => "\xE5\xA7\x8A", + "\xE6\xA3" => "\xE5\xA6\xAB", + "\xE6\xA4" => "\xE5\xA6\x9E", + "\xE6\xA5" => "\xE5\xA6\xA4", + "\xE6\xA6" => "\xE5\xA7\x92", + "\xE6\xA7" => "\xE5\xA6\xB2", + "\xE6\xA8" => "\xE5\xA6\xAF", + "\xE6\xA9" => "\xE5\xA7\x97", + "\xE6\xAA" => "\xE5\xA6\xBE", + "\xE6\xAB" => "\xE5\xA8\x85", + "\xE6\xAC" => "\xE5\xA8\x86", + "\xE6\xAD" => "\xE5\xA7\x9D", + "\xE6\xAE" => "\xE5\xA8\x88", + "\xE6\xAF" => "\xE5\xA7\xA3", + "\xE6\xB0" => "\xE5\xA7\x98", + "\xE6\xB1" => "\xE5\xA7\xB9", + "\xE6\xB2" => "\xE5\xA8\x8C", + "\xE6\xB3" => "\xE5\xA8\x89", + "\xE6\xB4" => "\xE5\xA8\xB2", + "\xE6\xB5" => "\xE5\xA8\xB4", + "\xE6\xB6" => "\xE5\xA8\x91", + "\xE6\xB7" => "\xE5\xA8\xA3", + "\xE6\xB8" => "\xE5\xA8\x93", + "\xE6\xB9" => "\xE5\xA9\x80", + "\xE6\xBA" => "\xE5\xA9\xA7", + "\xE6\xBB" => "\xE5\xA9\x8A", + "\xE6\xBC" => "\xE5\xA9\x95", + "\xE6\xBD" => "\xE5\xA8\xBC", + "\xE6\xBE" => "\xE5\xA9\xA2", + "\xE6\xBF" => "\xE5\xA9\xB5", + "\xE6\xC0" => "\xE8\x83\xAC", + "\xE6\xC1" => "\xE5\xAA\xAA", + "\xE6\xC2" => "\xE5\xAA\x9B", + "\xE6\xC3" => "\xE5\xA9\xB7", + "\xE6\xC4" => "\xE5\xA9\xBA", + "\xE6\xC5" => "\xE5\xAA\xBE", + "\xE6\xC6" => "\xE5\xAB\xAB", + "\xE6\xC7" => "\xE5\xAA\xB2", + "\xE6\xC8" => "\xE5\xAB\x92", + "\xE6\xC9" => "\xE5\xAB\x94", + "\xE6\xCA" => "\xE5\xAA\xB8", + "\xE6\xCB" => "\xE5\xAB\xA0", + "\xE6\xCC" => "\xE5\xAB\xA3", + "\xE6\xCD" => "\xE5\xAB\xB1", + "\xE6\xCE" => "\xE5\xAB\x96", + "\xE6\xCF" => "\xE5\xAB\xA6", + "\xE6\xD0" => "\xE5\xAB\x98", + "\xE6\xD1" => "\xE5\xAB\x9C", + "\xE6\xD2" => "\xE5\xAC\x89", + "\xE6\xD3" => "\xE5\xAC\x97", + "\xE6\xD4" => "\xE5\xAC\x96", + "\xE6\xD5" => "\xE5\xAC\xB2", + "\xE6\xD6" => "\xE5\xAC\xB7", + "\xE6\xD7" => "\xE5\xAD\x80", + "\xE6\xD8" => "\xE5\xB0\x95", + "\xE6\xD9" => "\xE5\xB0\x9C", + "\xE6\xDA" => "\xE5\xAD\x9A", + "\xE6\xDB" => "\xE5\xAD\xA5", + "\xE6\xDC" => "\xE5\xAD\xB3", + "\xE6\xDD" => "\xE5\xAD\x91", + "\xE6\xDE" => "\xE5\xAD\x93", + "\xE6\xDF" => "\xE5\xAD\xA2", + "\xE6\xE0" => "\xE9\xA9\xB5", + "\xE6\xE1" => "\xE9\xA9\xB7", + "\xE6\xE2" => "\xE9\xA9\xB8", + "\xE6\xE3" => "\xE9\xA9\xBA", + "\xE6\xE4" => "\xE9\xA9\xBF", + "\xE6\xE5" => "\xE9\xA9\xBD", + "\xE6\xE6" => "\xE9\xAA\x80", + "\xE6\xE7" => "\xE9\xAA\x81", + "\xE6\xE8" => "\xE9\xAA\x85", + "\xE6\xE9" => "\xE9\xAA\x88", + "\xE6\xEA" => "\xE9\xAA\x8A", + "\xE6\xEB" => "\xE9\xAA\x90", + "\xE6\xEC" => "\xE9\xAA\x92", + "\xE6\xED" => "\xE9\xAA\x93", + "\xE6\xEE" => "\xE9\xAA\x96", + "\xE6\xEF" => "\xE9\xAA\x98", + "\xE6\xF0" => "\xE9\xAA\x9B", + "\xE6\xF1" => "\xE9\xAA\x9C", + "\xE6\xF2" => "\xE9\xAA\x9D", + "\xE6\xF3" => "\xE9\xAA\x9F", + "\xE6\xF4" => "\xE9\xAA\xA0", + "\xE6\xF5" => "\xE9\xAA\xA2", + "\xE6\xF6" => "\xE9\xAA\xA3", + "\xE6\xF7" => "\xE9\xAA\xA5", + "\xE6\xF8" => "\xE9\xAA\xA7", + "\xE6\xF9" => "\xE7\xBA\x9F", + "\xE6\xFA" => "\xE7\xBA\xA1", + "\xE6\xFB" => "\xE7\xBA\xA3", + "\xE6\xFC" => "\xE7\xBA\xA5", + "\xE6\xFD" => "\xE7\xBA\xA8", + "\xE6\xFE" => "\xE7\xBA\xA9", + "\xE7\xA1" => "\xE7\xBA\xAD", + "\xE7\xA2" => "\xE7\xBA\xB0", + "\xE7\xA3" => "\xE7\xBA\xBE", + "\xE7\xA4" => "\xE7\xBB\x80", + "\xE7\xA5" => "\xE7\xBB\x81", + "\xE7\xA6" => "\xE7\xBB\x82", + "\xE7\xA7" => "\xE7\xBB\x89", + "\xE7\xA8" => "\xE7\xBB\x8B", + "\xE7\xA9" => "\xE7\xBB\x8C", + "\xE7\xAA" => "\xE7\xBB\x90", + "\xE7\xAB" => "\xE7\xBB\x94", + "\xE7\xAC" => "\xE7\xBB\x97", + "\xE7\xAD" => "\xE7\xBB\x9B", + "\xE7\xAE" => "\xE7\xBB\xA0", + "\xE7\xAF" => "\xE7\xBB\xA1", + "\xE7\xB0" => "\xE7\xBB\xA8", + "\xE7\xB1" => "\xE7\xBB\xAB", + "\xE7\xB2" => "\xE7\xBB\xAE", + "\xE7\xB3" => "\xE7\xBB\xAF", + "\xE7\xB4" => "\xE7\xBB\xB1", + "\xE7\xB5" => "\xE7\xBB\xB2", + "\xE7\xB6" => "\xE7\xBC\x8D", + "\xE7\xB7" => "\xE7\xBB\xB6", + "\xE7\xB8" => "\xE7\xBB\xBA", + "\xE7\xB9" => "\xE7\xBB\xBB", + "\xE7\xBA" => "\xE7\xBB\xBE", + "\xE7\xBB" => "\xE7\xBC\x81", + "\xE7\xBC" => "\xE7\xBC\x82", + "\xE7\xBD" => "\xE7\xBC\x83", + "\xE7\xBE" => "\xE7\xBC\x87", + "\xE7\xBF" => "\xE7\xBC\x88", + "\xE7\xC0" => "\xE7\xBC\x8B", + "\xE7\xC1" => "\xE7\xBC\x8C", + "\xE7\xC2" => "\xE7\xBC\x8F", + "\xE7\xC3" => "\xE7\xBC\x91", + "\xE7\xC4" => "\xE7\xBC\x92", + "\xE7\xC5" => "\xE7\xBC\x97", + "\xE7\xC6" => "\xE7\xBC\x99", + "\xE7\xC7" => "\xE7\xBC\x9C", + "\xE7\xC8" => "\xE7\xBC\x9B", + "\xE7\xC9" => "\xE7\xBC\x9F", + "\xE7\xCA" => "\xE7\xBC\xA1", + "\xE7\xCB" => "\xE7\xBC\xA2", + "\xE7\xCC" => "\xE7\xBC\xA3", + "\xE7\xCD" => "\xE7\xBC\xA4", + "\xE7\xCE" => "\xE7\xBC\xA5", + "\xE7\xCF" => "\xE7\xBC\xA6", + "\xE7\xD0" => "\xE7\xBC\xA7", + "\xE7\xD1" => "\xE7\xBC\xAA", + "\xE7\xD2" => "\xE7\xBC\xAB", + "\xE7\xD3" => "\xE7\xBC\xAC", + "\xE7\xD4" => "\xE7\xBC\xAD", + "\xE7\xD5" => "\xE7\xBC\xAF", + "\xE7\xD6" => "\xE7\xBC\xB0", + "\xE7\xD7" => "\xE7\xBC\xB1", + "\xE7\xD8" => "\xE7\xBC\xB2", + "\xE7\xD9" => "\xE7\xBC\xB3", + "\xE7\xDA" => "\xE7\xBC\xB5", + "\xE7\xDB" => "\xE5\xB9\xBA", + "\xE7\xDC" => "\xE7\x95\xBF", + "\xE7\xDD" => "\xE5\xB7\x9B", + "\xE7\xDE" => "\xE7\x94\xBE", + "\xE7\xDF" => "\xE9\x82\x95", + "\xE7\xE0" => "\xE7\x8E\x8E", + "\xE7\xE1" => "\xE7\x8E\x91", + "\xE7\xE2" => "\xE7\x8E\xAE", + "\xE7\xE3" => "\xE7\x8E\xA2", + "\xE7\xE4" => "\xE7\x8E\x9F", + "\xE7\xE5" => "\xE7\x8F\x8F", + "\xE7\xE6" => "\xE7\x8F\x82", + "\xE7\xE7" => "\xE7\x8F\x91", + "\xE7\xE8" => "\xE7\x8E\xB7", + "\xE7\xE9" => "\xE7\x8E\xB3", + "\xE7\xEA" => "\xE7\x8F\x80", + "\xE7\xEB" => "\xE7\x8F\x89", + "\xE7\xEC" => "\xE7\x8F\x88", + "\xE7\xED" => "\xE7\x8F\xA5", + "\xE7\xEE" => "\xE7\x8F\x99", + "\xE7\xEF" => "\xE9\xA1\xBC", + "\xE7\xF0" => "\xE7\x90\x8A", + "\xE7\xF1" => "\xE7\x8F\xA9", + "\xE7\xF2" => "\xE7\x8F\xA7", + "\xE7\xF3" => "\xE7\x8F\x9E", + "\xE7\xF4" => "\xE7\x8E\xBA", + "\xE7\xF5" => "\xE7\x8F\xB2", + "\xE7\xF6" => "\xE7\x90\x8F", + "\xE7\xF7" => "\xE7\x90\xAA", + "\xE7\xF8" => "\xE7\x91\x9B", + "\xE7\xF9" => "\xE7\x90\xA6", + "\xE7\xFA" => "\xE7\x90\xA5", + "\xE7\xFB" => "\xE7\x90\xA8", + "\xE7\xFC" => "\xE7\x90\xB0", + "\xE7\xFD" => "\xE7\x90\xAE", + "\xE7\xFE" => "\xE7\x90\xAC", + "\xE8\xA1" => "\xE7\x90\x9B", + "\xE8\xA2" => "\xE7\x90\x9A", + "\xE8\xA3" => "\xE7\x91\x81", + "\xE8\xA4" => "\xE7\x91\x9C", + "\xE8\xA5" => "\xE7\x91\x97", + "\xE8\xA6" => "\xE7\x91\x95", + "\xE8\xA7" => "\xE7\x91\x99", + "\xE8\xA8" => "\xE7\x91\xB7", + "\xE8\xA9" => "\xE7\x91\xAD", + "\xE8\xAA" => "\xE7\x91\xBE", + "\xE8\xAB" => "\xE7\x92\x9C", + "\xE8\xAC" => "\xE7\x92\x8E", + "\xE8\xAD" => "\xE7\x92\x80", + "\xE8\xAE" => "\xE7\x92\x81", + "\xE8\xAF" => "\xE7\x92\x87", + "\xE8\xB0" => "\xE7\x92\x8B", + "\xE8\xB1" => "\xE7\x92\x9E", + "\xE8\xB2" => "\xE7\x92\xA8", + "\xE8\xB3" => "\xE7\x92\xA9", + "\xE8\xB4" => "\xE7\x92\x90", + "\xE8\xB5" => "\xE7\x92\xA7", + "\xE8\xB6" => "\xE7\x93\x92", + "\xE8\xB7" => "\xE7\x92\xBA", + "\xE8\xB8" => "\xE9\x9F\xAA", + "\xE8\xB9" => "\xE9\x9F\xAB", + "\xE8\xBA" => "\xE9\x9F\xAC", + "\xE8\xBB" => "\xE6\x9D\x8C", + "\xE8\xBC" => "\xE6\x9D\x93", + "\xE8\xBD" => "\xE6\x9D\x9E", + "\xE8\xBE" => "\xE6\x9D\x88", + "\xE8\xBF" => "\xE6\x9D\xA9", + "\xE8\xC0" => "\xE6\x9E\xA5", + "\xE8\xC1" => "\xE6\x9E\x87", + "\xE8\xC2" => "\xE6\x9D\xAA", + "\xE8\xC3" => "\xE6\x9D\xB3", + "\xE8\xC4" => "\xE6\x9E\x98", + "\xE8\xC5" => "\xE6\x9E\xA7", + "\xE8\xC6" => "\xE6\x9D\xB5", + "\xE8\xC7" => "\xE6\x9E\xA8", + "\xE8\xC8" => "\xE6\x9E\x9E", + "\xE8\xC9" => "\xE6\x9E\xAD", + "\xE8\xCA" => "\xE6\x9E\x8B", + "\xE8\xCB" => "\xE6\x9D\xB7", + "\xE8\xCC" => "\xE6\x9D\xBC", + "\xE8\xCD" => "\xE6\x9F\xB0", + "\xE8\xCE" => "\xE6\xA0\x89", + "\xE8\xCF" => "\xE6\x9F\x98", + "\xE8\xD0" => "\xE6\xA0\x8A", + "\xE8\xD1" => "\xE6\x9F\xA9", + "\xE8\xD2" => "\xE6\x9E\xB0", + "\xE8\xD3" => "\xE6\xA0\x8C", + "\xE8\xD4" => "\xE6\x9F\x99", + "\xE8\xD5" => "\xE6\x9E\xB5", + "\xE8\xD6" => "\xE6\x9F\x9A", + "\xE8\xD7" => "\xE6\x9E\xB3", + "\xE8\xD8" => "\xE6\x9F\x9D", + "\xE8\xD9" => "\xE6\xA0\x80", + "\xE8\xDA" => "\xE6\x9F\x83", + "\xE8\xDB" => "\xE6\x9E\xB8", + "\xE8\xDC" => "\xE6\x9F\xA2", + "\xE8\xDD" => "\xE6\xA0\x8E", + "\xE8\xDE" => "\xE6\x9F\x81", + "\xE8\xDF" => "\xE6\x9F\xBD", + "\xE8\xE0" => "\xE6\xA0\xB2", + "\xE8\xE1" => "\xE6\xA0\xB3", + "\xE8\xE2" => "\xE6\xA1\xA0", + "\xE8\xE3" => "\xE6\xA1\xA1", + "\xE8\xE4" => "\xE6\xA1\x8E", + "\xE8\xE5" => "\xE6\xA1\xA2", + "\xE8\xE6" => "\xE6\xA1\x84", + "\xE8\xE7" => "\xE6\xA1\xA4", + "\xE8\xE8" => "\xE6\xA2\x83", + "\xE8\xE9" => "\xE6\xA0\x9D", + "\xE8\xEA" => "\xE6\xA1\x95", + "\xE8\xEB" => "\xE6\xA1\xA6", + "\xE8\xEC" => "\xE6\xA1\x81", + "\xE8\xED" => "\xE6\xA1\xA7", + "\xE8\xEE" => "\xE6\xA1\x80", + "\xE8\xEF" => "\xE6\xA0\xBE", + "\xE8\xF0" => "\xE6\xA1\x8A", + "\xE8\xF1" => "\xE6\xA1\x89", + "\xE8\xF2" => "\xE6\xA0\xA9", + "\xE8\xF3" => "\xE6\xA2\xB5", + "\xE8\xF4" => "\xE6\xA2\x8F", + "\xE8\xF5" => "\xE6\xA1\xB4", + "\xE8\xF6" => "\xE6\xA1\xB7", + "\xE8\xF7" => "\xE6\xA2\x93", + "\xE8\xF8" => "\xE6\xA1\xAB", + "\xE8\xF9" => "\xE6\xA3\x82", + "\xE8\xFA" => "\xE6\xA5\xAE", + "\xE8\xFB" => "\xE6\xA3\xBC", + "\xE8\xFC" => "\xE6\xA4\x9F", + "\xE8\xFD" => "\xE6\xA4\xA0", + "\xE8\xFE" => "\xE6\xA3\xB9", + "\xE9\xA1" => "\xE6\xA4\xA4", + "\xE9\xA2" => "\xE6\xA3\xB0", + "\xE9\xA3" => "\xE6\xA4\x8B", + "\xE9\xA4" => "\xE6\xA4\x81", + "\xE9\xA5" => "\xE6\xA5\x97", + "\xE9\xA6" => "\xE6\xA3\xA3", + "\xE9\xA7" => "\xE6\xA4\x90", + "\xE9\xA8" => "\xE6\xA5\xB1", + "\xE9\xA9" => "\xE6\xA4\xB9", + "\xE9\xAA" => "\xE6\xA5\xA0", + "\xE9\xAB" => "\xE6\xA5\x82", + "\xE9\xAC" => "\xE6\xA5\x9D", + "\xE9\xAD" => "\xE6\xA6\x84", + "\xE9\xAE" => "\xE6\xA5\xAB", + "\xE9\xAF" => "\xE6\xA6\x80", + "\xE9\xB0" => "\xE6\xA6\x98", + "\xE9\xB1" => "\xE6\xA5\xB8", + "\xE9\xB2" => "\xE6\xA4\xB4", + "\xE9\xB3" => "\xE6\xA7\x8C", + "\xE9\xB4" => "\xE6\xA6\x87", + "\xE9\xB5" => "\xE6\xA6\x88", + "\xE9\xB6" => "\xE6\xA7\x8E", + "\xE9\xB7" => "\xE6\xA6\x89", + "\xE9\xB8" => "\xE6\xA5\xA6", + "\xE9\xB9" => "\xE6\xA5\xA3", + "\xE9\xBA" => "\xE6\xA5\xB9", + "\xE9\xBB" => "\xE6\xA6\x9B", + "\xE9\xBC" => "\xE6\xA6\xA7", + "\xE9\xBD" => "\xE6\xA6\xBB", + "\xE9\xBE" => "\xE6\xA6\xAB", + "\xE9\xBF" => "\xE6\xA6\xAD", + "\xE9\xC0" => "\xE6\xA7\x94", + "\xE9\xC1" => "\xE6\xA6\xB1", + "\xE9\xC2" => "\xE6\xA7\x81", + "\xE9\xC3" => "\xE6\xA7\x8A", + "\xE9\xC4" => "\xE6\xA7\x9F", + "\xE9\xC5" => "\xE6\xA6\x95", + "\xE9\xC6" => "\xE6\xA7\xA0", + "\xE9\xC7" => "\xE6\xA6\x8D", + "\xE9\xC8" => "\xE6\xA7\xBF", + "\xE9\xC9" => "\xE6\xA8\xAF", + "\xE9\xCA" => "\xE6\xA7\xAD", + "\xE9\xCB" => "\xE6\xA8\x97", + "\xE9\xCC" => "\xE6\xA8\x98", + "\xE9\xCD" => "\xE6\xA9\xA5", + "\xE9\xCE" => "\xE6\xA7\xB2", + "\xE9\xCF" => "\xE6\xA9\x84", + "\xE9\xD0" => "\xE6\xA8\xBE", + "\xE9\xD1" => "\xE6\xAA\xA0", + "\xE9\xD2" => "\xE6\xA9\x90", + "\xE9\xD3" => "\xE6\xA9\x9B", + "\xE9\xD4" => "\xE6\xA8\xB5", + "\xE9\xD5" => "\xE6\xAA\x8E", + "\xE9\xD6" => "\xE6\xA9\xB9", + "\xE9\xD7" => "\xE6\xA8\xBD", + "\xE9\xD8" => "\xE6\xA8\xA8", + "\xE9\xD9" => "\xE6\xA9\x98", + "\xE9\xDA" => "\xE6\xA9\xBC", + "\xE9\xDB" => "\xE6\xAA\x91", + "\xE9\xDC" => "\xE6\xAA\x90", + "\xE9\xDD" => "\xE6\xAA\xA9", + "\xE9\xDE" => "\xE6\xAA\x97", + "\xE9\xDF" => "\xE6\xAA\xAB", + "\xE9\xE0" => "\xE7\x8C\xB7", + "\xE9\xE1" => "\xE7\x8D\x92", + "\xE9\xE2" => "\xE6\xAE\x81", + "\xE9\xE3" => "\xE6\xAE\x82", + "\xE9\xE4" => "\xE6\xAE\x87", + "\xE9\xE5" => "\xE6\xAE\x84", + "\xE9\xE6" => "\xE6\xAE\x92", + "\xE9\xE7" => "\xE6\xAE\x93", + "\xE9\xE8" => "\xE6\xAE\x8D", + "\xE9\xE9" => "\xE6\xAE\x9A", + "\xE9\xEA" => "\xE6\xAE\x9B", + "\xE9\xEB" => "\xE6\xAE\xA1", + "\xE9\xEC" => "\xE6\xAE\xAA", + "\xE9\xED" => "\xE8\xBD\xAB", + "\xE9\xEE" => "\xE8\xBD\xAD", + "\xE9\xEF" => "\xE8\xBD\xB1", + "\xE9\xF0" => "\xE8\xBD\xB2", + "\xE9\xF1" => "\xE8\xBD\xB3", + "\xE9\xF2" => "\xE8\xBD\xB5", + "\xE9\xF3" => "\xE8\xBD\xB6", + "\xE9\xF4" => "\xE8\xBD\xB8", + "\xE9\xF5" => "\xE8\xBD\xB7", + "\xE9\xF6" => "\xE8\xBD\xB9", + "\xE9\xF7" => "\xE8\xBD\xBA", + "\xE9\xF8" => "\xE8\xBD\xBC", + "\xE9\xF9" => "\xE8\xBD\xBE", + "\xE9\xFA" => "\xE8\xBE\x81", + "\xE9\xFB" => "\xE8\xBE\x82", + "\xE9\xFC" => "\xE8\xBE\x84", + "\xE9\xFD" => "\xE8\xBE\x87", + "\xE9\xFE" => "\xE8\xBE\x8B", + "\xEA\xA1" => "\xE8\xBE\x8D", + "\xEA\xA2" => "\xE8\xBE\x8E", + "\xEA\xA3" => "\xE8\xBE\x8F", + "\xEA\xA4" => "\xE8\xBE\x98", + "\xEA\xA5" => "\xE8\xBE\x9A", + "\xEA\xA6" => "\xE8\xBB\x8E", + "\xEA\xA7" => "\xE6\x88\x8B", + "\xEA\xA8" => "\xE6\x88\x97", + "\xEA\xA9" => "\xE6\x88\x9B", + "\xEA\xAA" => "\xE6\x88\x9F", + "\xEA\xAB" => "\xE6\x88\xA2", + "\xEA\xAC" => "\xE6\x88\xA1", + "\xEA\xAD" => "\xE6\x88\xA5", + "\xEA\xAE" => "\xE6\x88\xA4", + "\xEA\xAF" => "\xE6\x88\xAC", + "\xEA\xB0" => "\xE8\x87\xA7", + "\xEA\xB1" => "\xE7\x93\xAF", + "\xEA\xB2" => "\xE7\x93\xB4", + "\xEA\xB3" => "\xE7\x93\xBF", + "\xEA\xB4" => "\xE7\x94\x8F", + "\xEA\xB5" => "\xE7\x94\x91", + "\xEA\xB6" => "\xE7\x94\x93", + "\xEA\xB7" => "\xE6\x94\xB4", + "\xEA\xB8" => "\xE6\x97\xAE", + "\xEA\xB9" => "\xE6\x97\xAF", + "\xEA\xBA" => "\xE6\x97\xB0", + "\xEA\xBB" => "\xE6\x98\x8A", + "\xEA\xBC" => "\xE6\x98\x99", + "\xEA\xBD" => "\xE6\x9D\xB2", + "\xEA\xBE" => "\xE6\x98\x83", + "\xEA\xBF" => "\xE6\x98\x95", + "\xEA\xC0" => "\xE6\x98\x80", + "\xEA\xC1" => "\xE7\x82\x85", + "\xEA\xC2" => "\xE6\x9B\xB7", + "\xEA\xC3" => "\xE6\x98\x9D", + "\xEA\xC4" => "\xE6\x98\xB4", + "\xEA\xC5" => "\xE6\x98\xB1", + "\xEA\xC6" => "\xE6\x98\xB6", + "\xEA\xC7" => "\xE6\x98\xB5", + "\xEA\xC8" => "\xE8\x80\x86", + "\xEA\xC9" => "\xE6\x99\x9F", + "\xEA\xCA" => "\xE6\x99\x94", + "\xEA\xCB" => "\xE6\x99\x81", + "\xEA\xCC" => "\xE6\x99\x8F", + "\xEA\xCD" => "\xE6\x99\x96", + "\xEA\xCE" => "\xE6\x99\xA1", + "\xEA\xCF" => "\xE6\x99\x97", + "\xEA\xD0" => "\xE6\x99\xB7", + "\xEA\xD1" => "\xE6\x9A\x84", + "\xEA\xD2" => "\xE6\x9A\x8C", + "\xEA\xD3" => "\xE6\x9A\xA7", + "\xEA\xD4" => "\xE6\x9A\x9D", + "\xEA\xD5" => "\xE6\x9A\xBE", + "\xEA\xD6" => "\xE6\x9B\x9B", + "\xEA\xD7" => "\xE6\x9B\x9C", + "\xEA\xD8" => "\xE6\x9B\xA6", + "\xEA\xD9" => "\xE6\x9B\xA9", + "\xEA\xDA" => "\xE8\xB4\xB2", + "\xEA\xDB" => "\xE8\xB4\xB3", + "\xEA\xDC" => "\xE8\xB4\xB6", + "\xEA\xDD" => "\xE8\xB4\xBB", + "\xEA\xDE" => "\xE8\xB4\xBD", + "\xEA\xDF" => "\xE8\xB5\x80", + "\xEA\xE0" => "\xE8\xB5\x85", + "\xEA\xE1" => "\xE8\xB5\x86", + "\xEA\xE2" => "\xE8\xB5\x88", + "\xEA\xE3" => "\xE8\xB5\x89", + "\xEA\xE4" => "\xE8\xB5\x87", + "\xEA\xE5" => "\xE8\xB5\x8D", + "\xEA\xE6" => "\xE8\xB5\x95", + "\xEA\xE7" => "\xE8\xB5\x99", + "\xEA\xE8" => "\xE8\xA7\x87", + "\xEA\xE9" => "\xE8\xA7\x8A", + "\xEA\xEA" => "\xE8\xA7\x8B", + "\xEA\xEB" => "\xE8\xA7\x8C", + "\xEA\xEC" => "\xE8\xA7\x8E", + "\xEA\xED" => "\xE8\xA7\x8F", + "\xEA\xEE" => "\xE8\xA7\x90", + "\xEA\xEF" => "\xE8\xA7\x91", + "\xEA\xF0" => "\xE7\x89\xAE", + "\xEA\xF1" => "\xE7\x8A\x9F", + "\xEA\xF2" => "\xE7\x89\x9D", + "\xEA\xF3" => "\xE7\x89\xA6", + "\xEA\xF4" => "\xE7\x89\xAF", + "\xEA\xF5" => "\xE7\x89\xBE", + "\xEA\xF6" => "\xE7\x89\xBF", + "\xEA\xF7" => "\xE7\x8A\x84", + "\xEA\xF8" => "\xE7\x8A\x8B", + "\xEA\xF9" => "\xE7\x8A\x8D", + "\xEA\xFA" => "\xE7\x8A\x8F", + "\xEA\xFB" => "\xE7\x8A\x92", + "\xEA\xFC" => "\xE6\x8C\x88", + "\xEA\xFD" => "\xE6\x8C\xB2", + "\xEA\xFE" => "\xE6\x8E\xB0", + "\xEB\xA1" => "\xE6\x90\xBF", + "\xEB\xA2" => "\xE6\x93\x98", + "\xEB\xA3" => "\xE8\x80\x84", + "\xEB\xA4" => "\xE6\xAF\xAA", + "\xEB\xA5" => "\xE6\xAF\xB3", + "\xEB\xA6" => "\xE6\xAF\xBD", + "\xEB\xA7" => "\xE6\xAF\xB5", + "\xEB\xA8" => "\xE6\xAF\xB9", + "\xEB\xA9" => "\xE6\xB0\x85", + "\xEB\xAA" => "\xE6\xB0\x87", + "\xEB\xAB" => "\xE6\xB0\x86", + "\xEB\xAC" => "\xE6\xB0\x8D", + "\xEB\xAD" => "\xE6\xB0\x95", + "\xEB\xAE" => "\xE6\xB0\x98", + "\xEB\xAF" => "\xE6\xB0\x99", + "\xEB\xB0" => "\xE6\xB0\x9A", + "\xEB\xB1" => "\xE6\xB0\xA1", + "\xEB\xB2" => "\xE6\xB0\xA9", + "\xEB\xB3" => "\xE6\xB0\xA4", + "\xEB\xB4" => "\xE6\xB0\xAA", + "\xEB\xB5" => "\xE6\xB0\xB2", + "\xEB\xB6" => "\xE6\x94\xB5", + "\xEB\xB7" => "\xE6\x95\x95", + "\xEB\xB8" => "\xE6\x95\xAB", + "\xEB\xB9" => "\xE7\x89\x8D", + "\xEB\xBA" => "\xE7\x89\x92", + "\xEB\xBB" => "\xE7\x89\x96", + "\xEB\xBC" => "\xE7\x88\xB0", + "\xEB\xBD" => "\xE8\x99\xA2", + "\xEB\xBE" => "\xE5\x88\x96", + "\xEB\xBF" => "\xE8\x82\x9F", + "\xEB\xC0" => "\xE8\x82\x9C", + "\xEB\xC1" => "\xE8\x82\x93", + "\xEB\xC2" => "\xE8\x82\xBC", + "\xEB\xC3" => "\xE6\x9C\x8A", + "\xEB\xC4" => "\xE8\x82\xBD", + "\xEB\xC5" => "\xE8\x82\xB1", + "\xEB\xC6" => "\xE8\x82\xAB", + "\xEB\xC7" => "\xE8\x82\xAD", + "\xEB\xC8" => "\xE8\x82\xB4", + "\xEB\xC9" => "\xE8\x82\xB7", + "\xEB\xCA" => "\xE8\x83\xA7", + "\xEB\xCB" => "\xE8\x83\xA8", + "\xEB\xCC" => "\xE8\x83\xA9", + "\xEB\xCD" => "\xE8\x83\xAA", + "\xEB\xCE" => "\xE8\x83\x9B", + "\xEB\xCF" => "\xE8\x83\x82", + "\xEB\xD0" => "\xE8\x83\x84", + "\xEB\xD1" => "\xE8\x83\x99", + "\xEB\xD2" => "\xE8\x83\x8D", + "\xEB\xD3" => "\xE8\x83\x97", + "\xEB\xD4" => "\xE6\x9C\x90", + "\xEB\xD5" => "\xE8\x83\x9D", + "\xEB\xD6" => "\xE8\x83\xAB", + "\xEB\xD7" => "\xE8\x83\xB1", + "\xEB\xD8" => "\xE8\x83\xB4", + "\xEB\xD9" => "\xE8\x83\xAD", + "\xEB\xDA" => "\xE8\x84\x8D", + "\xEB\xDB" => "\xE8\x84\x8E", + "\xEB\xDC" => "\xE8\x83\xB2", + "\xEB\xDD" => "\xE8\x83\xBC", + "\xEB\xDE" => "\xE6\x9C\x95", + "\xEB\xDF" => "\xE8\x84\x92", + "\xEB\xE0" => "\xE8\xB1\x9A", + "\xEB\xE1" => "\xE8\x84\xB6", + "\xEB\xE2" => "\xE8\x84\x9E", + "\xEB\xE3" => "\xE8\x84\xAC", + "\xEB\xE4" => "\xE8\x84\x98", + "\xEB\xE5" => "\xE8\x84\xB2", + "\xEB\xE6" => "\xE8\x85\x88", + "\xEB\xE7" => "\xE8\x85\x8C", + "\xEB\xE8" => "\xE8\x85\x93", + "\xEB\xE9" => "\xE8\x85\xB4", + "\xEB\xEA" => "\xE8\x85\x99", + "\xEB\xEB" => "\xE8\x85\x9A", + "\xEB\xEC" => "\xE8\x85\xB1", + "\xEB\xED" => "\xE8\x85\xA0", + "\xEB\xEE" => "\xE8\x85\xA9", + "\xEB\xEF" => "\xE8\x85\xBC", + "\xEB\xF0" => "\xE8\x85\xBD", + "\xEB\xF1" => "\xE8\x85\xAD", + "\xEB\xF2" => "\xE8\x85\xA7", + "\xEB\xF3" => "\xE5\xA1\x8D", + "\xEB\xF4" => "\xE5\xAA\xB5", + "\xEB\xF5" => "\xE8\x86\x88", + "\xEB\xF6" => "\xE8\x86\x82", + "\xEB\xF7" => "\xE8\x86\x91", + "\xEB\xF8" => "\xE6\xBB\x95", + "\xEB\xF9" => "\xE8\x86\xA3", + "\xEB\xFA" => "\xE8\x86\xAA", + "\xEB\xFB" => "\xE8\x87\x8C", + "\xEB\xFC" => "\xE6\x9C\xA6", + "\xEB\xFD" => "\xE8\x87\x8A", + "\xEB\xFE" => "\xE8\x86\xBB", + "\xEC\xA1" => "\xE8\x87\x81", + "\xEC\xA2" => "\xE8\x86\xA6", + "\xEC\xA3" => "\xE6\xAC\xA4", + "\xEC\xA4" => "\xE6\xAC\xB7", + "\xEC\xA5" => "\xE6\xAC\xB9", + "\xEC\xA6" => "\xE6\xAD\x83", + "\xEC\xA7" => "\xE6\xAD\x86", + "\xEC\xA8" => "\xE6\xAD\x99", + "\xEC\xA9" => "\xE9\xA3\x91", + "\xEC\xAA" => "\xE9\xA3\x92", + "\xEC\xAB" => "\xE9\xA3\x93", + "\xEC\xAC" => "\xE9\xA3\x95", + "\xEC\xAD" => "\xE9\xA3\x99", + "\xEC\xAE" => "\xE9\xA3\x9A", + "\xEC\xAF" => "\xE6\xAE\xB3", + "\xEC\xB0" => "\xE5\xBD\x80", + "\xEC\xB1" => "\xE6\xAF\x82", + "\xEC\xB2" => "\xE8\xA7\xB3", + "\xEC\xB3" => "\xE6\x96\x90", + "\xEC\xB4" => "\xE9\xBD\x91", + "\xEC\xB5" => "\xE6\x96\x93", + "\xEC\xB6" => "\xE6\x96\xBC", + "\xEC\xB7" => "\xE6\x97\x86", + "\xEC\xB8" => "\xE6\x97\x84", + "\xEC\xB9" => "\xE6\x97\x83", + "\xEC\xBA" => "\xE6\x97\x8C", + "\xEC\xBB" => "\xE6\x97\x8E", + "\xEC\xBC" => "\xE6\x97\x92", + "\xEC\xBD" => "\xE6\x97\x96", + "\xEC\xBE" => "\xE7\x82\x80", + "\xEC\xBF" => "\xE7\x82\x9C", + "\xEC\xC0" => "\xE7\x82\x96", + "\xEC\xC1" => "\xE7\x82\x9D", + "\xEC\xC2" => "\xE7\x82\xBB", + "\xEC\xC3" => "\xE7\x83\x80", + "\xEC\xC4" => "\xE7\x82\xB7", + "\xEC\xC5" => "\xE7\x82\xAB", + "\xEC\xC6" => "\xE7\x82\xB1", + "\xEC\xC7" => "\xE7\x83\xA8", + "\xEC\xC8" => "\xE7\x83\x8A", + "\xEC\xC9" => "\xE7\x84\x90", + "\xEC\xCA" => "\xE7\x84\x93", + "\xEC\xCB" => "\xE7\x84\x96", + "\xEC\xCC" => "\xE7\x84\xAF", + "\xEC\xCD" => "\xE7\x84\xB1", + "\xEC\xCE" => "\xE7\x85\xB3", + "\xEC\xCF" => "\xE7\x85\x9C", + "\xEC\xD0" => "\xE7\x85\xA8", + "\xEC\xD1" => "\xE7\x85\x85", + "\xEC\xD2" => "\xE7\x85\xB2", + "\xEC\xD3" => "\xE7\x85\x8A", + "\xEC\xD4" => "\xE7\x85\xB8", + "\xEC\xD5" => "\xE7\x85\xBA", + "\xEC\xD6" => "\xE7\x86\x98", + "\xEC\xD7" => "\xE7\x86\xB3", + "\xEC\xD8" => "\xE7\x86\xB5", + "\xEC\xD9" => "\xE7\x86\xA8", + "\xEC\xDA" => "\xE7\x86\xA0", + "\xEC\xDB" => "\xE7\x87\xA0", + "\xEC\xDC" => "\xE7\x87\x94", + "\xEC\xDD" => "\xE7\x87\xA7", + "\xEC\xDE" => "\xE7\x87\xB9", + "\xEC\xDF" => "\xE7\x88\x9D", + "\xEC\xE0" => "\xE7\x88\xA8", + "\xEC\xE1" => "\xE7\x81\xAC", + "\xEC\xE2" => "\xE7\x84\x98", + "\xEC\xE3" => "\xE7\x85\xA6", + "\xEC\xE4" => "\xE7\x86\xB9", + "\xEC\xE5" => "\xE6\x88\xBE", + "\xEC\xE6" => "\xE6\x88\xBD", + "\xEC\xE7" => "\xE6\x89\x83", + "\xEC\xE8" => "\xE6\x89\x88", + "\xEC\xE9" => "\xE6\x89\x89", + "\xEC\xEA" => "\xE7\xA4\xBB", + "\xEC\xEB" => "\xE7\xA5\x80", + "\xEC\xEC" => "\xE7\xA5\x86", + "\xEC\xED" => "\xE7\xA5\x89", + "\xEC\xEE" => "\xE7\xA5\x9B", + "\xEC\xEF" => "\xE7\xA5\x9C", + "\xEC\xF0" => "\xE7\xA5\x93", + "\xEC\xF1" => "\xE7\xA5\x9A", + "\xEC\xF2" => "\xE7\xA5\xA2", + "\xEC\xF3" => "\xE7\xA5\x97", + "\xEC\xF4" => "\xE7\xA5\xA0", + "\xEC\xF5" => "\xE7\xA5\xAF", + "\xEC\xF6" => "\xE7\xA5\xA7", + "\xEC\xF7" => "\xE7\xA5\xBA", + "\xEC\xF8" => "\xE7\xA6\x85", + "\xEC\xF9" => "\xE7\xA6\x8A", + "\xEC\xFA" => "\xE7\xA6\x9A", + "\xEC\xFB" => "\xE7\xA6\xA7", + "\xEC\xFC" => "\xE7\xA6\xB3", + "\xEC\xFD" => "\xE5\xBF\x91", + "\xEC\xFE" => "\xE5\xBF\x90", + "\xED\xA1" => "\xE6\x80\xBC", + "\xED\xA2" => "\xE6\x81\x9D", + "\xED\xA3" => "\xE6\x81\x9A", + "\xED\xA4" => "\xE6\x81\xA7", + "\xED\xA5" => "\xE6\x81\x81", + "\xED\xA6" => "\xE6\x81\x99", + "\xED\xA7" => "\xE6\x81\xA3", + "\xED\xA8" => "\xE6\x82\xAB", + "\xED\xA9" => "\xE6\x84\x86", + "\xED\xAA" => "\xE6\x84\x8D", + "\xED\xAB" => "\xE6\x85\x9D", + "\xED\xAC" => "\xE6\x86\xA9", + "\xED\xAD" => "\xE6\x86\x9D", + "\xED\xAE" => "\xE6\x87\x8B", + "\xED\xAF" => "\xE6\x87\x91", + "\xED\xB0" => "\xE6\x88\x86", + "\xED\xB1" => "\xE8\x82\x80", + "\xED\xB2" => "\xE8\x81\xBF", + "\xED\xB3" => "\xE6\xB2\x93", + "\xED\xB4" => "\xE6\xB3\xB6", + "\xED\xB5" => "\xE6\xB7\xBC", + "\xED\xB6" => "\xE7\x9F\xB6", + "\xED\xB7" => "\xE7\x9F\xB8", + "\xED\xB8" => "\xE7\xA0\x80", + "\xED\xB9" => "\xE7\xA0\x89", + "\xED\xBA" => "\xE7\xA0\x97", + "\xED\xBB" => "\xE7\xA0\x98", + "\xED\xBC" => "\xE7\xA0\x91", + "\xED\xBD" => "\xE6\x96\xAB", + "\xED\xBE" => "\xE7\xA0\xAD", + "\xED\xBF" => "\xE7\xA0\x9C", + "\xED\xC0" => "\xE7\xA0\x9D", + "\xED\xC1" => "\xE7\xA0\xB9", + "\xED\xC2" => "\xE7\xA0\xBA", + "\xED\xC3" => "\xE7\xA0\xBB", + "\xED\xC4" => "\xE7\xA0\x9F", + "\xED\xC5" => "\xE7\xA0\xBC", + "\xED\xC6" => "\xE7\xA0\xA5", + "\xED\xC7" => "\xE7\xA0\xAC", + "\xED\xC8" => "\xE7\xA0\xA3", + "\xED\xC9" => "\xE7\xA0\xA9", + "\xED\xCA" => "\xE7\xA1\x8E", + "\xED\xCB" => "\xE7\xA1\xAD", + "\xED\xCC" => "\xE7\xA1\x96", + "\xED\xCD" => "\xE7\xA1\x97", + "\xED\xCE" => "\xE7\xA0\xA6", + "\xED\xCF" => "\xE7\xA1\x90", + "\xED\xD0" => "\xE7\xA1\x87", + "\xED\xD1" => "\xE7\xA1\x8C", + "\xED\xD2" => "\xE7\xA1\xAA", + "\xED\xD3" => "\xE7\xA2\x9B", + "\xED\xD4" => "\xE7\xA2\x93", + "\xED\xD5" => "\xE7\xA2\x9A", + "\xED\xD6" => "\xE7\xA2\x87", + "\xED\xD7" => "\xE7\xA2\x9C", + "\xED\xD8" => "\xE7\xA2\xA1", + "\xED\xD9" => "\xE7\xA2\xA3", + "\xED\xDA" => "\xE7\xA2\xB2", + "\xED\xDB" => "\xE7\xA2\xB9", + "\xED\xDC" => "\xE7\xA2\xA5", + "\xED\xDD" => "\xE7\xA3\x94", + "\xED\xDE" => "\xE7\xA3\x99", + "\xED\xDF" => "\xE7\xA3\x89", + "\xED\xE0" => "\xE7\xA3\xAC", + "\xED\xE1" => "\xE7\xA3\xB2", + "\xED\xE2" => "\xE7\xA4\x85", + "\xED\xE3" => "\xE7\xA3\xB4", + "\xED\xE4" => "\xE7\xA4\x93", + "\xED\xE5" => "\xE7\xA4\xA4", + "\xED\xE6" => "\xE7\xA4\x9E", + "\xED\xE7" => "\xE7\xA4\xB4", + "\xED\xE8" => "\xE9\xBE\x9B", + "\xED\xE9" => "\xE9\xBB\xB9", + "\xED\xEA" => "\xE9\xBB\xBB", + "\xED\xEB" => "\xE9\xBB\xBC", + "\xED\xEC" => "\xE7\x9B\xB1", + "\xED\xED" => "\xE7\x9C\x84", + "\xED\xEE" => "\xE7\x9C\x8D", + "\xED\xEF" => "\xE7\x9B\xB9", + "\xED\xF0" => "\xE7\x9C\x87", + "\xED\xF1" => "\xE7\x9C\x88", + "\xED\xF2" => "\xE7\x9C\x9A", + "\xED\xF3" => "\xE7\x9C\xA2", + "\xED\xF4" => "\xE7\x9C\x99", + "\xED\xF5" => "\xE7\x9C\xAD", + "\xED\xF6" => "\xE7\x9C\xA6", + "\xED\xF7" => "\xE7\x9C\xB5", + "\xED\xF8" => "\xE7\x9C\xB8", + "\xED\xF9" => "\xE7\x9D\x90", + "\xED\xFA" => "\xE7\x9D\x91", + "\xED\xFB" => "\xE7\x9D\x87", + "\xED\xFC" => "\xE7\x9D\x83", + "\xED\xFD" => "\xE7\x9D\x9A", + "\xED\xFE" => "\xE7\x9D\xA8", + "\xEE\xA1" => "\xE7\x9D\xA2", + "\xEE\xA2" => "\xE7\x9D\xA5", + "\xEE\xA3" => "\xE7\x9D\xBF", + "\xEE\xA4" => "\xE7\x9E\x8D", + "\xEE\xA5" => "\xE7\x9D\xBD", + "\xEE\xA6" => "\xE7\x9E\x80", + "\xEE\xA7" => "\xE7\x9E\x8C", + "\xEE\xA8" => "\xE7\x9E\x91", + "\xEE\xA9" => "\xE7\x9E\x9F", + "\xEE\xAA" => "\xE7\x9E\xA0", + "\xEE\xAB" => "\xE7\x9E\xB0", + "\xEE\xAC" => "\xE7\x9E\xB5", + "\xEE\xAD" => "\xE7\x9E\xBD", + "\xEE\xAE" => "\xE7\x94\xBA", + "\xEE\xAF" => "\xE7\x95\x80", + "\xEE\xB0" => "\xE7\x95\x8E", + "\xEE\xB1" => "\xE7\x95\x8B", + "\xEE\xB2" => "\xE7\x95\x88", + "\xEE\xB3" => "\xE7\x95\x9B", + "\xEE\xB4" => "\xE7\x95\xB2", + "\xEE\xB5" => "\xE7\x95\xB9", + "\xEE\xB6" => "\xE7\x96\x83", + "\xEE\xB7" => "\xE7\xBD\x98", + "\xEE\xB8" => "\xE7\xBD\xA1", + "\xEE\xB9" => "\xE7\xBD\x9F", + "\xEE\xBA" => "\xE8\xA9\x88", + "\xEE\xBB" => "\xE7\xBD\xA8", + "\xEE\xBC" => "\xE7\xBD\xB4", + "\xEE\xBD" => "\xE7\xBD\xB1", + "\xEE\xBE" => "\xE7\xBD\xB9", + "\xEE\xBF" => "\xE7\xBE\x81", + "\xEE\xC0" => "\xE7\xBD\xBE", + "\xEE\xC1" => "\xE7\x9B\x8D", + "\xEE\xC2" => "\xE7\x9B\xA5", + "\xEE\xC3" => "\xE8\xA0\xB2", + "\xEE\xC4" => "\xE9\x92\x85", + "\xEE\xC5" => "\xE9\x92\x86", + "\xEE\xC6" => "\xE9\x92\x87", + "\xEE\xC7" => "\xE9\x92\x8B", + "\xEE\xC8" => "\xE9\x92\x8A", + "\xEE\xC9" => "\xE9\x92\x8C", + "\xEE\xCA" => "\xE9\x92\x8D", + "\xEE\xCB" => "\xE9\x92\x8F", + "\xEE\xCC" => "\xE9\x92\x90", + "\xEE\xCD" => "\xE9\x92\x94", + "\xEE\xCE" => "\xE9\x92\x97", + "\xEE\xCF" => "\xE9\x92\x95", + "\xEE\xD0" => "\xE9\x92\x9A", + "\xEE\xD1" => "\xE9\x92\x9B", + "\xEE\xD2" => "\xE9\x92\x9C", + "\xEE\xD3" => "\xE9\x92\xA3", + "\xEE\xD4" => "\xE9\x92\xA4", + "\xEE\xD5" => "\xE9\x92\xAB", + "\xEE\xD6" => "\xE9\x92\xAA", + "\xEE\xD7" => "\xE9\x92\xAD", + "\xEE\xD8" => "\xE9\x92\xAC", + "\xEE\xD9" => "\xE9\x92\xAF", + "\xEE\xDA" => "\xE9\x92\xB0", + "\xEE\xDB" => "\xE9\x92\xB2", + "\xEE\xDC" => "\xE9\x92\xB4", + "\xEE\xDD" => "\xE9\x92\xB6", + "\xEE\xDE" => "\xE9\x92\xB7", + "\xEE\xDF" => "\xE9\x92\xB8", + "\xEE\xE0" => "\xE9\x92\xB9", + "\xEE\xE1" => "\xE9\x92\xBA", + "\xEE\xE2" => "\xE9\x92\xBC", + "\xEE\xE3" => "\xE9\x92\xBD", + "\xEE\xE4" => "\xE9\x92\xBF", + "\xEE\xE5" => "\xE9\x93\x84", + "\xEE\xE6" => "\xE9\x93\x88", + "\xEE\xE7" => "\xE9\x93\x89", + "\xEE\xE8" => "\xE9\x93\x8A", + "\xEE\xE9" => "\xE9\x93\x8B", + "\xEE\xEA" => "\xE9\x93\x8C", + "\xEE\xEB" => "\xE9\x93\x8D", + "\xEE\xEC" => "\xE9\x93\x8E", + "\xEE\xED" => "\xE9\x93\x90", + "\xEE\xEE" => "\xE9\x93\x91", + "\xEE\xEF" => "\xE9\x93\x92", + "\xEE\xF0" => "\xE9\x93\x95", + "\xEE\xF1" => "\xE9\x93\x96", + "\xEE\xF2" => "\xE9\x93\x97", + "\xEE\xF3" => "\xE9\x93\x99", + "\xEE\xF4" => "\xE9\x93\x98", + "\xEE\xF5" => "\xE9\x93\x9B", + "\xEE\xF6" => "\xE9\x93\x9E", + "\xEE\xF7" => "\xE9\x93\x9F", + "\xEE\xF8" => "\xE9\x93\xA0", + "\xEE\xF9" => "\xE9\x93\xA2", + "\xEE\xFA" => "\xE9\x93\xA4", + "\xEE\xFB" => "\xE9\x93\xA5", + "\xEE\xFC" => "\xE9\x93\xA7", + "\xEE\xFD" => "\xE9\x93\xA8", + "\xEE\xFE" => "\xE9\x93\xAA", + "\xEF\xA1" => "\xE9\x93\xA9", + "\xEF\xA2" => "\xE9\x93\xAB", + "\xEF\xA3" => "\xE9\x93\xAE", + "\xEF\xA4" => "\xE9\x93\xAF", + "\xEF\xA5" => "\xE9\x93\xB3", + "\xEF\xA6" => "\xE9\x93\xB4", + "\xEF\xA7" => "\xE9\x93\xB5", + "\xEF\xA8" => "\xE9\x93\xB7", + "\xEF\xA9" => "\xE9\x93\xB9", + "\xEF\xAA" => "\xE9\x93\xBC", + "\xEF\xAB" => "\xE9\x93\xBD", + "\xEF\xAC" => "\xE9\x93\xBF", + "\xEF\xAD" => "\xE9\x94\x83", + "\xEF\xAE" => "\xE9\x94\x82", + "\xEF\xAF" => "\xE9\x94\x86", + "\xEF\xB0" => "\xE9\x94\x87", + "\xEF\xB1" => "\xE9\x94\x89", + "\xEF\xB2" => "\xE9\x94\x8A", + "\xEF\xB3" => "\xE9\x94\x8D", + "\xEF\xB4" => "\xE9\x94\x8E", + "\xEF\xB5" => "\xE9\x94\x8F", + "\xEF\xB6" => "\xE9\x94\x92", + "\xEF\xB7" => "\xE9\x94\x93", + "\xEF\xB8" => "\xE9\x94\x94", + "\xEF\xB9" => "\xE9\x94\x95", + "\xEF\xBA" => "\xE9\x94\x96", + "\xEF\xBB" => "\xE9\x94\x98", + "\xEF\xBC" => "\xE9\x94\x9B", + "\xEF\xBD" => "\xE9\x94\x9D", + "\xEF\xBE" => "\xE9\x94\x9E", + "\xEF\xBF" => "\xE9\x94\x9F", + "\xEF\xC0" => "\xE9\x94\xA2", + "\xEF\xC1" => "\xE9\x94\xAA", + "\xEF\xC2" => "\xE9\x94\xAB", + "\xEF\xC3" => "\xE9\x94\xA9", + "\xEF\xC4" => "\xE9\x94\xAC", + "\xEF\xC5" => "\xE9\x94\xB1", + "\xEF\xC6" => "\xE9\x94\xB2", + "\xEF\xC7" => "\xE9\x94\xB4", + "\xEF\xC8" => "\xE9\x94\xB6", + "\xEF\xC9" => "\xE9\x94\xB7", + "\xEF\xCA" => "\xE9\x94\xB8", + "\xEF\xCB" => "\xE9\x94\xBC", + "\xEF\xCC" => "\xE9\x94\xBE", + "\xEF\xCD" => "\xE9\x94\xBF", + "\xEF\xCE" => "\xE9\x95\x82", + "\xEF\xCF" => "\xE9\x94\xB5", + "\xEF\xD0" => "\xE9\x95\x84", + "\xEF\xD1" => "\xE9\x95\x85", + "\xEF\xD2" => "\xE9\x95\x86", + "\xEF\xD3" => "\xE9\x95\x89", + "\xEF\xD4" => "\xE9\x95\x8C", + "\xEF\xD5" => "\xE9\x95\x8E", + "\xEF\xD6" => "\xE9\x95\x8F", + "\xEF\xD7" => "\xE9\x95\x92", + "\xEF\xD8" => "\xE9\x95\x93", + "\xEF\xD9" => "\xE9\x95\x94", + "\xEF\xDA" => "\xE9\x95\x96", + "\xEF\xDB" => "\xE9\x95\x97", + "\xEF\xDC" => "\xE9\x95\x98", + "\xEF\xDD" => "\xE9\x95\x99", + "\xEF\xDE" => "\xE9\x95\x9B", + "\xEF\xDF" => "\xE9\x95\x9E", + "\xEF\xE0" => "\xE9\x95\x9F", + "\xEF\xE1" => "\xE9\x95\x9D", + "\xEF\xE2" => "\xE9\x95\xA1", + "\xEF\xE3" => "\xE9\x95\xA2", + "\xEF\xE4" => "\xE9\x95\xA4", + "\xEF\xE5" => "\xE9\x95\xA5", + "\xEF\xE6" => "\xE9\x95\xA6", + "\xEF\xE7" => "\xE9\x95\xA7", + "\xEF\xE8" => "\xE9\x95\xA8", + "\xEF\xE9" => "\xE9\x95\xA9", + "\xEF\xEA" => "\xE9\x95\xAA", + "\xEF\xEB" => "\xE9\x95\xAB", + "\xEF\xEC" => "\xE9\x95\xAC", + "\xEF\xED" => "\xE9\x95\xAF", + "\xEF\xEE" => "\xE9\x95\xB1", + "\xEF\xEF" => "\xE9\x95\xB2", + "\xEF\xF0" => "\xE9\x95\xB3", + "\xEF\xF1" => "\xE9\x94\xBA", + "\xEF\xF2" => "\xE7\x9F\xA7", + "\xEF\xF3" => "\xE7\x9F\xAC", + "\xEF\xF4" => "\xE9\x9B\x89", + "\xEF\xF5" => "\xE7\xA7\x95", + "\xEF\xF6" => "\xE7\xA7\xAD", + "\xEF\xF7" => "\xE7\xA7\xA3", + "\xEF\xF8" => "\xE7\xA7\xAB", + "\xEF\xF9" => "\xE7\xA8\x86", + "\xEF\xFA" => "\xE5\xB5\x87", + "\xEF\xFB" => "\xE7\xA8\x83", + "\xEF\xFC" => "\xE7\xA8\x82", + "\xEF\xFD" => "\xE7\xA8\x9E", + "\xEF\xFE" => "\xE7\xA8\x94", + "\xF0\xA1" => "\xE7\xA8\xB9", + "\xF0\xA2" => "\xE7\xA8\xB7", + "\xF0\xA3" => "\xE7\xA9\x91", + "\xF0\xA4" => "\xE9\xBB\x8F", + "\xF0\xA5" => "\xE9\xA6\xA5", + "\xF0\xA6" => "\xE7\xA9\xB0", + "\xF0\xA7" => "\xE7\x9A\x88", + "\xF0\xA8" => "\xE7\x9A\x8E", + "\xF0\xA9" => "\xE7\x9A\x93", + "\xF0\xAA" => "\xE7\x9A\x99", + "\xF0\xAB" => "\xE7\x9A\xA4", + "\xF0\xAC" => "\xE7\x93\x9E", + "\xF0\xAD" => "\xE7\x93\xA0", + "\xF0\xAE" => "\xE7\x94\xAC", + "\xF0\xAF" => "\xE9\xB8\xA0", + "\xF0\xB0" => "\xE9\xB8\xA2", + "\xF0\xB1" => "\xE9\xB8\xA8", + "\xF0\xB2" => "\xE9\xB8\xA9", + "\xF0\xB3" => "\xE9\xB8\xAA", + "\xF0\xB4" => "\xE9\xB8\xAB", + "\xF0\xB5" => "\xE9\xB8\xAC", + "\xF0\xB6" => "\xE9\xB8\xB2", + "\xF0\xB7" => "\xE9\xB8\xB1", + "\xF0\xB8" => "\xE9\xB8\xB6", + "\xF0\xB9" => "\xE9\xB8\xB8", + "\xF0\xBA" => "\xE9\xB8\xB7", + "\xF0\xBB" => "\xE9\xB8\xB9", + "\xF0\xBC" => "\xE9\xB8\xBA", + "\xF0\xBD" => "\xE9\xB8\xBE", + "\xF0\xBE" => "\xE9\xB9\x81", + "\xF0\xBF" => "\xE9\xB9\x82", + "\xF0\xC0" => "\xE9\xB9\x84", + "\xF0\xC1" => "\xE9\xB9\x86", + "\xF0\xC2" => "\xE9\xB9\x87", + "\xF0\xC3" => "\xE9\xB9\x88", + "\xF0\xC4" => "\xE9\xB9\x89", + "\xF0\xC5" => "\xE9\xB9\x8B", + "\xF0\xC6" => "\xE9\xB9\x8C", + "\xF0\xC7" => "\xE9\xB9\x8E", + "\xF0\xC8" => "\xE9\xB9\x91", + "\xF0\xC9" => "\xE9\xB9\x95", + "\xF0\xCA" => "\xE9\xB9\x97", + "\xF0\xCB" => "\xE9\xB9\x9A", + "\xF0\xCC" => "\xE9\xB9\x9B", + "\xF0\xCD" => "\xE9\xB9\x9C", + "\xF0\xCE" => "\xE9\xB9\x9E", + "\xF0\xCF" => "\xE9\xB9\xA3", + "\xF0\xD0" => "\xE9\xB9\xA6", + "\xF0\xD1" => "\xE9\xB9\xA7", + "\xF0\xD2" => "\xE9\xB9\xA8", + "\xF0\xD3" => "\xE9\xB9\xA9", + "\xF0\xD4" => "\xE9\xB9\xAA", + "\xF0\xD5" => "\xE9\xB9\xAB", + "\xF0\xD6" => "\xE9\xB9\xAC", + "\xF0\xD7" => "\xE9\xB9\xB1", + "\xF0\xD8" => "\xE9\xB9\xAD", + "\xF0\xD9" => "\xE9\xB9\xB3", + "\xF0\xDA" => "\xE7\x96\x92", + "\xF0\xDB" => "\xE7\x96\x94", + "\xF0\xDC" => "\xE7\x96\x96", + "\xF0\xDD" => "\xE7\x96\xA0", + "\xF0\xDE" => "\xE7\x96\x9D", + "\xF0\xDF" => "\xE7\x96\xAC", + "\xF0\xE0" => "\xE7\x96\xA3", + "\xF0\xE1" => "\xE7\x96\xB3", + "\xF0\xE2" => "\xE7\x96\xB4", + "\xF0\xE3" => "\xE7\x96\xB8", + "\xF0\xE4" => "\xE7\x97\x84", + "\xF0\xE5" => "\xE7\x96\xB1", + "\xF0\xE6" => "\xE7\x96\xB0", + "\xF0\xE7" => "\xE7\x97\x83", + "\xF0\xE8" => "\xE7\x97\x82", + "\xF0\xE9" => "\xE7\x97\x96", + "\xF0\xEA" => "\xE7\x97\x8D", + "\xF0\xEB" => "\xE7\x97\xA3", + "\xF0\xEC" => "\xE7\x97\xA8", + "\xF0\xED" => "\xE7\x97\xA6", + "\xF0\xEE" => "\xE7\x97\xA4", + "\xF0\xEF" => "\xE7\x97\xAB", + "\xF0\xF0" => "\xE7\x97\xA7", + "\xF0\xF1" => "\xE7\x98\x83", + "\xF0\xF2" => "\xE7\x97\xB1", + "\xF0\xF3" => "\xE7\x97\xBC", + "\xF0\xF4" => "\xE7\x97\xBF", + "\xF0\xF5" => "\xE7\x98\x90", + "\xF0\xF6" => "\xE7\x98\x80", + "\xF0\xF7" => "\xE7\x98\x85", + "\xF0\xF8" => "\xE7\x98\x8C", + "\xF0\xF9" => "\xE7\x98\x97", + "\xF0\xFA" => "\xE7\x98\x8A", + "\xF0\xFB" => "\xE7\x98\xA5", + "\xF0\xFC" => "\xE7\x98\x98", + "\xF0\xFD" => "\xE7\x98\x95", + "\xF0\xFE" => "\xE7\x98\x99", + "\xF1\xA1" => "\xE7\x98\x9B", + "\xF1\xA2" => "\xE7\x98\xBC", + "\xF1\xA3" => "\xE7\x98\xA2", + "\xF1\xA4" => "\xE7\x98\xA0", + "\xF1\xA5" => "\xE7\x99\x80", + "\xF1\xA6" => "\xE7\x98\xAD", + "\xF1\xA7" => "\xE7\x98\xB0", + "\xF1\xA8" => "\xE7\x98\xBF", + "\xF1\xA9" => "\xE7\x98\xB5", + "\xF1\xAA" => "\xE7\x99\x83", + "\xF1\xAB" => "\xE7\x98\xBE", + "\xF1\xAC" => "\xE7\x98\xB3", + "\xF1\xAD" => "\xE7\x99\x8D", + "\xF1\xAE" => "\xE7\x99\x9E", + "\xF1\xAF" => "\xE7\x99\x94", + "\xF1\xB0" => "\xE7\x99\x9C", + "\xF1\xB1" => "\xE7\x99\x96", + "\xF1\xB2" => "\xE7\x99\xAB", + "\xF1\xB3" => "\xE7\x99\xAF", + "\xF1\xB4" => "\xE7\xBF\x8A", + "\xF1\xB5" => "\xE7\xAB\xA6", + "\xF1\xB6" => "\xE7\xA9\xB8", + "\xF1\xB7" => "\xE7\xA9\xB9", + "\xF1\xB8" => "\xE7\xAA\x80", + "\xF1\xB9" => "\xE7\xAA\x86", + "\xF1\xBA" => "\xE7\xAA\x88", + "\xF1\xBB" => "\xE7\xAA\x95", + "\xF1\xBC" => "\xE7\xAA\xA6", + "\xF1\xBD" => "\xE7\xAA\xA0", + "\xF1\xBE" => "\xE7\xAA\xAC", + "\xF1\xBF" => "\xE7\xAA\xA8", + "\xF1\xC0" => "\xE7\xAA\xAD", + "\xF1\xC1" => "\xE7\xAA\xB3", + "\xF1\xC2" => "\xE8\xA1\xA4", + "\xF1\xC3" => "\xE8\xA1\xA9", + "\xF1\xC4" => "\xE8\xA1\xB2", + "\xF1\xC5" => "\xE8\xA1\xBD", + "\xF1\xC6" => "\xE8\xA1\xBF", + "\xF1\xC7" => "\xE8\xA2\x82", + "\xF1\xC8" => "\xE8\xA2\xA2", + "\xF1\xC9" => "\xE8\xA3\x86", + "\xF1\xCA" => "\xE8\xA2\xB7", + "\xF1\xCB" => "\xE8\xA2\xBC", + "\xF1\xCC" => "\xE8\xA3\x89", + "\xF1\xCD" => "\xE8\xA3\xA2", + "\xF1\xCE" => "\xE8\xA3\x8E", + "\xF1\xCF" => "\xE8\xA3\xA3", + "\xF1\xD0" => "\xE8\xA3\xA5", + "\xF1\xD1" => "\xE8\xA3\xB1", + "\xF1\xD2" => "\xE8\xA4\x9A", + "\xF1\xD3" => "\xE8\xA3\xBC", + "\xF1\xD4" => "\xE8\xA3\xA8", + "\xF1\xD5" => "\xE8\xA3\xBE", + "\xF1\xD6" => "\xE8\xA3\xB0", + "\xF1\xD7" => "\xE8\xA4\xA1", + "\xF1\xD8" => "\xE8\xA4\x99", + "\xF1\xD9" => "\xE8\xA4\x93", + "\xF1\xDA" => "\xE8\xA4\x9B", + "\xF1\xDB" => "\xE8\xA4\x8A", + "\xF1\xDC" => "\xE8\xA4\xB4", + "\xF1\xDD" => "\xE8\xA4\xAB", + "\xF1\xDE" => "\xE8\xA4\xB6", + "\xF1\xDF" => "\xE8\xA5\x81", + "\xF1\xE0" => "\xE8\xA5\xA6", + "\xF1\xE1" => "\xE8\xA5\xBB", + "\xF1\xE2" => "\xE7\x96\x8B", + "\xF1\xE3" => "\xE8\x83\xA5", + "\xF1\xE4" => "\xE7\x9A\xB2", + "\xF1\xE5" => "\xE7\x9A\xB4", + "\xF1\xE6" => "\xE7\x9F\x9C", + "\xF1\xE7" => "\xE8\x80\x92", + "\xF1\xE8" => "\xE8\x80\x94", + "\xF1\xE9" => "\xE8\x80\x96", + "\xF1\xEA" => "\xE8\x80\x9C", + "\xF1\xEB" => "\xE8\x80\xA0", + "\xF1\xEC" => "\xE8\x80\xA2", + "\xF1\xED" => "\xE8\x80\xA5", + "\xF1\xEE" => "\xE8\x80\xA6", + "\xF1\xEF" => "\xE8\x80\xA7", + "\xF1\xF0" => "\xE8\x80\xA9", + "\xF1\xF1" => "\xE8\x80\xA8", + "\xF1\xF2" => "\xE8\x80\xB1", + "\xF1\xF3" => "\xE8\x80\x8B", + "\xF1\xF4" => "\xE8\x80\xB5", + "\xF1\xF5" => "\xE8\x81\x83", + "\xF1\xF6" => "\xE8\x81\x86", + "\xF1\xF7" => "\xE8\x81\x8D", + "\xF1\xF8" => "\xE8\x81\x92", + "\xF1\xF9" => "\xE8\x81\xA9", + "\xF1\xFA" => "\xE8\x81\xB1", + "\xF1\xFB" => "\xE8\xA6\x83", + "\xF1\xFC" => "\xE9\xA1\xB8", + "\xF1\xFD" => "\xE9\xA2\x80", + "\xF1\xFE" => "\xE9\xA2\x83", + "\xF2\xA1" => "\xE9\xA2\x89", + "\xF2\xA2" => "\xE9\xA2\x8C", + "\xF2\xA3" => "\xE9\xA2\x8D", + "\xF2\xA4" => "\xE9\xA2\x8F", + "\xF2\xA5" => "\xE9\xA2\x94", + "\xF2\xA6" => "\xE9\xA2\x9A", + "\xF2\xA7" => "\xE9\xA2\x9B", + "\xF2\xA8" => "\xE9\xA2\x9E", + "\xF2\xA9" => "\xE9\xA2\x9F", + "\xF2\xAA" => "\xE9\xA2\xA1", + "\xF2\xAB" => "\xE9\xA2\xA2", + "\xF2\xAC" => "\xE9\xA2\xA5", + "\xF2\xAD" => "\xE9\xA2\xA6", + "\xF2\xAE" => "\xE8\x99\x8D", + "\xF2\xAF" => "\xE8\x99\x94", + "\xF2\xB0" => "\xE8\x99\xAC", + "\xF2\xB1" => "\xE8\x99\xAE", + "\xF2\xB2" => "\xE8\x99\xBF", + "\xF2\xB3" => "\xE8\x99\xBA", + "\xF2\xB4" => "\xE8\x99\xBC", + "\xF2\xB5" => "\xE8\x99\xBB", + "\xF2\xB6" => "\xE8\x9A\xA8", + "\xF2\xB7" => "\xE8\x9A\x8D", + "\xF2\xB8" => "\xE8\x9A\x8B", + "\xF2\xB9" => "\xE8\x9A\xAC", + "\xF2\xBA" => "\xE8\x9A\x9D", + "\xF2\xBB" => "\xE8\x9A\xA7", + "\xF2\xBC" => "\xE8\x9A\xA3", + "\xF2\xBD" => "\xE8\x9A\xAA", + "\xF2\xBE" => "\xE8\x9A\x93", + "\xF2\xBF" => "\xE8\x9A\xA9", + "\xF2\xC0" => "\xE8\x9A\xB6", + "\xF2\xC1" => "\xE8\x9B\x84", + "\xF2\xC2" => "\xE8\x9A\xB5", + "\xF2\xC3" => "\xE8\x9B\x8E", + "\xF2\xC4" => "\xE8\x9A\xB0", + "\xF2\xC5" => "\xE8\x9A\xBA", + "\xF2\xC6" => "\xE8\x9A\xB1", + "\xF2\xC7" => "\xE8\x9A\xAF", + "\xF2\xC8" => "\xE8\x9B\x89", + "\xF2\xC9" => "\xE8\x9B\x8F", + "\xF2\xCA" => "\xE8\x9A\xB4", + "\xF2\xCB" => "\xE8\x9B\xA9", + "\xF2\xCC" => "\xE8\x9B\xB1", + "\xF2\xCD" => "\xE8\x9B\xB2", + "\xF2\xCE" => "\xE8\x9B\xAD", + "\xF2\xCF" => "\xE8\x9B\xB3", + "\xF2\xD0" => "\xE8\x9B\x90", + "\xF2\xD1" => "\xE8\x9C\x93", + "\xF2\xD2" => "\xE8\x9B\x9E", + "\xF2\xD3" => "\xE8\x9B\xB4", + "\xF2\xD4" => "\xE8\x9B\x9F", + "\xF2\xD5" => "\xE8\x9B\x98", + "\xF2\xD6" => "\xE8\x9B\x91", + "\xF2\xD7" => "\xE8\x9C\x83", + "\xF2\xD8" => "\xE8\x9C\x87", + "\xF2\xD9" => "\xE8\x9B\xB8", + "\xF2\xDA" => "\xE8\x9C\x88", + "\xF2\xDB" => "\xE8\x9C\x8A", + "\xF2\xDC" => "\xE8\x9C\x8D", + "\xF2\xDD" => "\xE8\x9C\x89", + "\xF2\xDE" => "\xE8\x9C\xA3", + "\xF2\xDF" => "\xE8\x9C\xBB", + "\xF2\xE0" => "\xE8\x9C\x9E", + "\xF2\xE1" => "\xE8\x9C\xA5", + "\xF2\xE2" => "\xE8\x9C\xAE", + "\xF2\xE3" => "\xE8\x9C\x9A", + "\xF2\xE4" => "\xE8\x9C\xBE", + "\xF2\xE5" => "\xE8\x9D\x88", + "\xF2\xE6" => "\xE8\x9C\xB4", + "\xF2\xE7" => "\xE8\x9C\xB1", + "\xF2\xE8" => "\xE8\x9C\xA9", + "\xF2\xE9" => "\xE8\x9C\xB7", + "\xF2\xEA" => "\xE8\x9C\xBF", + "\xF2\xEB" => "\xE8\x9E\x82", + "\xF2\xEC" => "\xE8\x9C\xA2", + "\xF2\xED" => "\xE8\x9D\xBD", + "\xF2\xEE" => "\xE8\x9D\xBE", + "\xF2\xEF" => "\xE8\x9D\xBB", + "\xF2\xF0" => "\xE8\x9D\xA0", + "\xF2\xF1" => "\xE8\x9D\xB0", + "\xF2\xF2" => "\xE8\x9D\x8C", + "\xF2\xF3" => "\xE8\x9D\xAE", + "\xF2\xF4" => "\xE8\x9E\x8B", + "\xF2\xF5" => "\xE8\x9D\x93", + "\xF2\xF6" => "\xE8\x9D\xA3", + "\xF2\xF7" => "\xE8\x9D\xBC", + "\xF2\xF8" => "\xE8\x9D\xA4", + "\xF2\xF9" => "\xE8\x9D\x99", + "\xF2\xFA" => "\xE8\x9D\xA5", + "\xF2\xFB" => "\xE8\x9E\x93", + "\xF2\xFC" => "\xE8\x9E\xAF", + "\xF2\xFD" => "\xE8\x9E\xA8", + "\xF2\xFE" => "\xE8\x9F\x92", + "\xF3\xA1" => "\xE8\x9F\x86", + "\xF3\xA2" => "\xE8\x9E\x88", + "\xF3\xA3" => "\xE8\x9E\x85", + "\xF3\xA4" => "\xE8\x9E\xAD", + "\xF3\xA5" => "\xE8\x9E\x97", + "\xF3\xA6" => "\xE8\x9E\x83", + "\xF3\xA7" => "\xE8\x9E\xAB", + "\xF3\xA8" => "\xE8\x9F\xA5", + "\xF3\xA9" => "\xE8\x9E\xAC", + "\xF3\xAA" => "\xE8\x9E\xB5", + "\xF3\xAB" => "\xE8\x9E\xB3", + "\xF3\xAC" => "\xE8\x9F\x8B", + "\xF3\xAD" => "\xE8\x9F\x93", + "\xF3\xAE" => "\xE8\x9E\xBD", + "\xF3\xAF" => "\xE8\x9F\x91", + "\xF3\xB0" => "\xE8\x9F\x80", + "\xF3\xB1" => "\xE8\x9F\x8A", + "\xF3\xB2" => "\xE8\x9F\x9B", + "\xF3\xB3" => "\xE8\x9F\xAA", + "\xF3\xB4" => "\xE8\x9F\xA0", + "\xF3\xB5" => "\xE8\x9F\xAE", + "\xF3\xB6" => "\xE8\xA0\x96", + "\xF3\xB7" => "\xE8\xA0\x93", + "\xF3\xB8" => "\xE8\x9F\xBE", + "\xF3\xB9" => "\xE8\xA0\x8A", + "\xF3\xBA" => "\xE8\xA0\x9B", + "\xF3\xBB" => "\xE8\xA0\xA1", + "\xF3\xBC" => "\xE8\xA0\xB9", + "\xF3\xBD" => "\xE8\xA0\xBC", + "\xF3\xBE" => "\xE7\xBC\xB6", + "\xF3\xBF" => "\xE7\xBD\x82", + "\xF3\xC0" => "\xE7\xBD\x84", + "\xF3\xC1" => "\xE7\xBD\x85", + "\xF3\xC2" => "\xE8\x88\x90", + "\xF3\xC3" => "\xE7\xAB\xBA", + "\xF3\xC4" => "\xE7\xAB\xBD", + "\xF3\xC5" => "\xE7\xAC\x88", + "\xF3\xC6" => "\xE7\xAC\x83", + "\xF3\xC7" => "\xE7\xAC\x84", + "\xF3\xC8" => "\xE7\xAC\x95", + "\xF3\xC9" => "\xE7\xAC\x8A", + "\xF3\xCA" => "\xE7\xAC\xAB", + "\xF3\xCB" => "\xE7\xAC\x8F", + "\xF3\xCC" => "\xE7\xAD\x87", + "\xF3\xCD" => "\xE7\xAC\xB8", + "\xF3\xCE" => "\xE7\xAC\xAA", + "\xF3\xCF" => "\xE7\xAC\x99", + "\xF3\xD0" => "\xE7\xAC\xAE", + "\xF3\xD1" => "\xE7\xAC\xB1", + "\xF3\xD2" => "\xE7\xAC\xA0", + "\xF3\xD3" => "\xE7\xAC\xA5", + "\xF3\xD4" => "\xE7\xAC\xA4", + "\xF3\xD5" => "\xE7\xAC\xB3", + "\xF3\xD6" => "\xE7\xAC\xBE", + "\xF3\xD7" => "\xE7\xAC\x9E", + "\xF3\xD8" => "\xE7\xAD\x98", + "\xF3\xD9" => "\xE7\xAD\x9A", + "\xF3\xDA" => "\xE7\xAD\x85", + "\xF3\xDB" => "\xE7\xAD\xB5", + "\xF3\xDC" => "\xE7\xAD\x8C", + "\xF3\xDD" => "\xE7\xAD\x9D", + "\xF3\xDE" => "\xE7\xAD\xA0", + "\xF3\xDF" => "\xE7\xAD\xAE", + "\xF3\xE0" => "\xE7\xAD\xBB", + "\xF3\xE1" => "\xE7\xAD\xA2", + "\xF3\xE2" => "\xE7\xAD\xB2", + "\xF3\xE3" => "\xE7\xAD\xB1", + "\xF3\xE4" => "\xE7\xAE\x90", + "\xF3\xE5" => "\xE7\xAE\xA6", + "\xF3\xE6" => "\xE7\xAE\xA7", + "\xF3\xE7" => "\xE7\xAE\xB8", + "\xF3\xE8" => "\xE7\xAE\xAC", + "\xF3\xE9" => "\xE7\xAE\x9D", + "\xF3\xEA" => "\xE7\xAE\xA8", + "\xF3\xEB" => "\xE7\xAE\x85", + "\xF3\xEC" => "\xE7\xAE\xAA", + "\xF3\xED" => "\xE7\xAE\x9C", + "\xF3\xEE" => "\xE7\xAE\xA2", + "\xF3\xEF" => "\xE7\xAE\xAB", + "\xF3\xF0" => "\xE7\xAE\xB4", + "\xF3\xF1" => "\xE7\xAF\x91", + "\xF3\xF2" => "\xE7\xAF\x81", + "\xF3\xF3" => "\xE7\xAF\x8C", + "\xF3\xF4" => "\xE7\xAF\x9D", + "\xF3\xF5" => "\xE7\xAF\x9A", + "\xF3\xF6" => "\xE7\xAF\xA5", + "\xF3\xF7" => "\xE7\xAF\xA6", + "\xF3\xF8" => "\xE7\xAF\xAA", + "\xF3\xF9" => "\xE7\xB0\x8C", + "\xF3\xFA" => "\xE7\xAF\xBE", + "\xF3\xFB" => "\xE7\xAF\xBC", + "\xF3\xFC" => "\xE7\xB0\x8F", + "\xF3\xFD" => "\xE7\xB0\x96", + "\xF3\xFE" => "\xE7\xB0\x8B", + "\xF4\xA1" => "\xE7\xB0\x9F", + "\xF4\xA2" => "\xE7\xB0\xAA", + "\xF4\xA3" => "\xE7\xB0\xA6", + "\xF4\xA4" => "\xE7\xB0\xB8", + "\xF4\xA5" => "\xE7\xB1\x81", + "\xF4\xA6" => "\xE7\xB1\x80", + "\xF4\xA7" => "\xE8\x87\xBE", + "\xF4\xA8" => "\xE8\x88\x81", + "\xF4\xA9" => "\xE8\x88\x82", + "\xF4\xAA" => "\xE8\x88\x84", + "\xF4\xAB" => "\xE8\x87\xAC", + "\xF4\xAC" => "\xE8\xA1\x84", + "\xF4\xAD" => "\xE8\x88\xA1", + "\xF4\xAE" => "\xE8\x88\xA2", + "\xF4\xAF" => "\xE8\x88\xA3", + "\xF4\xB0" => "\xE8\x88\xAD", + "\xF4\xB1" => "\xE8\x88\xAF", + "\xF4\xB2" => "\xE8\x88\xA8", + "\xF4\xB3" => "\xE8\x88\xAB", + "\xF4\xB4" => "\xE8\x88\xB8", + "\xF4\xB5" => "\xE8\x88\xBB", + "\xF4\xB6" => "\xE8\x88\xB3", + "\xF4\xB7" => "\xE8\x88\xB4", + "\xF4\xB8" => "\xE8\x88\xBE", + "\xF4\xB9" => "\xE8\x89\x84", + "\xF4\xBA" => "\xE8\x89\x89", + "\xF4\xBB" => "\xE8\x89\x8B", + "\xF4\xBC" => "\xE8\x89\x8F", + "\xF4\xBD" => "\xE8\x89\x9A", + "\xF4\xBE" => "\xE8\x89\x9F", + "\xF4\xBF" => "\xE8\x89\xA8", + "\xF4\xC0" => "\xE8\xA1\xBE", + "\xF4\xC1" => "\xE8\xA2\x85", + "\xF4\xC2" => "\xE8\xA2\x88", + "\xF4\xC3" => "\xE8\xA3\x98", + "\xF4\xC4" => "\xE8\xA3\x9F", + "\xF4\xC5" => "\xE8\xA5\x9E", + "\xF4\xC6" => "\xE7\xBE\x9D", + "\xF4\xC7" => "\xE7\xBE\x9F", + "\xF4\xC8" => "\xE7\xBE\xA7", + "\xF4\xC9" => "\xE7\xBE\xAF", + "\xF4\xCA" => "\xE7\xBE\xB0", + "\xF4\xCB" => "\xE7\xBE\xB2", + "\xF4\xCC" => "\xE7\xB1\xBC", + "\xF4\xCD" => "\xE6\x95\x89", + "\xF4\xCE" => "\xE7\xB2\x91", + "\xF4\xCF" => "\xE7\xB2\x9D", + "\xF4\xD0" => "\xE7\xB2\x9C", + "\xF4\xD1" => "\xE7\xB2\x9E", + "\xF4\xD2" => "\xE7\xB2\xA2", + "\xF4\xD3" => "\xE7\xB2\xB2", + "\xF4\xD4" => "\xE7\xB2\xBC", + "\xF4\xD5" => "\xE7\xB2\xBD", + "\xF4\xD6" => "\xE7\xB3\x81", + "\xF4\xD7" => "\xE7\xB3\x87", + "\xF4\xD8" => "\xE7\xB3\x8C", + "\xF4\xD9" => "\xE7\xB3\x8D", + "\xF4\xDA" => "\xE7\xB3\x88", + "\xF4\xDB" => "\xE7\xB3\x85", + "\xF4\xDC" => "\xE7\xB3\x97", + "\xF4\xDD" => "\xE7\xB3\xA8", + "\xF4\xDE" => "\xE8\x89\xAE", + "\xF4\xDF" => "\xE6\x9A\xA8", + "\xF4\xE0" => "\xE7\xBE\xBF", + "\xF4\xE1" => "\xE7\xBF\x8E", + "\xF4\xE2" => "\xE7\xBF\x95", + "\xF4\xE3" => "\xE7\xBF\xA5", + "\xF4\xE4" => "\xE7\xBF\xA1", + "\xF4\xE5" => "\xE7\xBF\xA6", + "\xF4\xE6" => "\xE7\xBF\xA9", + "\xF4\xE7" => "\xE7\xBF\xAE", + "\xF4\xE8" => "\xE7\xBF\xB3", + "\xF4\xE9" => "\xE7\xB3\xB8", + "\xF4\xEA" => "\xE7\xB5\xB7", + "\xF4\xEB" => "\xE7\xB6\xA6", + "\xF4\xEC" => "\xE7\xB6\xAE", + "\xF4\xED" => "\xE7\xB9\x87", + "\xF4\xEE" => "\xE7\xBA\x9B", + "\xF4\xEF" => "\xE9\xBA\xB8", + "\xF4\xF0" => "\xE9\xBA\xB4", + "\xF4\xF1" => "\xE8\xB5\xB3", + "\xF4\xF2" => "\xE8\xB6\x84", + "\xF4\xF3" => "\xE8\xB6\x94", + "\xF4\xF4" => "\xE8\xB6\x91", + "\xF4\xF5" => "\xE8\xB6\xB1", + "\xF4\xF6" => "\xE8\xB5\xA7", + "\xF4\xF7" => "\xE8\xB5\xAD", + "\xF4\xF8" => "\xE8\xB1\x87", + "\xF4\xF9" => "\xE8\xB1\x89", + "\xF4\xFA" => "\xE9\x85\x8A", + "\xF4\xFB" => "\xE9\x85\x90", + "\xF4\xFC" => "\xE9\x85\x8E", + "\xF4\xFD" => "\xE9\x85\x8F", + "\xF4\xFE" => "\xE9\x85\xA4", + "\xF5\xA1" => "\xE9\x85\xA2", + "\xF5\xA2" => "\xE9\x85\xA1", + "\xF5\xA3" => "\xE9\x85\xB0", + "\xF5\xA4" => "\xE9\x85\xA9", + "\xF5\xA5" => "\xE9\x85\xAF", + "\xF5\xA6" => "\xE9\x85\xBD", + "\xF5\xA7" => "\xE9\x85\xBE", + "\xF5\xA8" => "\xE9\x85\xB2", + "\xF5\xA9" => "\xE9\x85\xB4", + "\xF5\xAA" => "\xE9\x85\xB9", + "\xF5\xAB" => "\xE9\x86\x8C", + "\xF5\xAC" => "\xE9\x86\x85", + "\xF5\xAD" => "\xE9\x86\x90", + "\xF5\xAE" => "\xE9\x86\x8D", + "\xF5\xAF" => "\xE9\x86\x91", + "\xF5\xB0" => "\xE9\x86\xA2", + "\xF5\xB1" => "\xE9\x86\xA3", + "\xF5\xB2" => "\xE9\x86\xAA", + "\xF5\xB3" => "\xE9\x86\xAD", + "\xF5\xB4" => "\xE9\x86\xAE", + "\xF5\xB5" => "\xE9\x86\xAF", + "\xF5\xB6" => "\xE9\x86\xB5", + "\xF5\xB7" => "\xE9\x86\xB4", + "\xF5\xB8" => "\xE9\x86\xBA", + "\xF5\xB9" => "\xE8\xB1\x95", + "\xF5\xBA" => "\xE9\xB9\xBE", + "\xF5\xBB" => "\xE8\xB6\xB8", + "\xF5\xBC" => "\xE8\xB7\xAB", + "\xF5\xBD" => "\xE8\xB8\x85", + "\xF5\xBE" => "\xE8\xB9\x99", + "\xF5\xBF" => "\xE8\xB9\xA9", + "\xF5\xC0" => "\xE8\xB6\xB5", + "\xF5\xC1" => "\xE8\xB6\xBF", + "\xF5\xC2" => "\xE8\xB6\xBC", + "\xF5\xC3" => "\xE8\xB6\xBA", + "\xF5\xC4" => "\xE8\xB7\x84", + "\xF5\xC5" => "\xE8\xB7\x96", + "\xF5\xC6" => "\xE8\xB7\x97", + "\xF5\xC7" => "\xE8\xB7\x9A", + "\xF5\xC8" => "\xE8\xB7\x9E", + "\xF5\xC9" => "\xE8\xB7\x8E", + "\xF5\xCA" => "\xE8\xB7\x8F", + "\xF5\xCB" => "\xE8\xB7\x9B", + "\xF5\xCC" => "\xE8\xB7\x86", + "\xF5\xCD" => "\xE8\xB7\xAC", + "\xF5\xCE" => "\xE8\xB7\xB7", + "\xF5\xCF" => "\xE8\xB7\xB8", + "\xF5\xD0" => "\xE8\xB7\xA3", + "\xF5\xD1" => "\xE8\xB7\xB9", + "\xF5\xD2" => "\xE8\xB7\xBB", + "\xF5\xD3" => "\xE8\xB7\xA4", + "\xF5\xD4" => "\xE8\xB8\x89", + "\xF5\xD5" => "\xE8\xB7\xBD", + "\xF5\xD6" => "\xE8\xB8\x94", + "\xF5\xD7" => "\xE8\xB8\x9D", + "\xF5\xD8" => "\xE8\xB8\x9F", + "\xF5\xD9" => "\xE8\xB8\xAC", + "\xF5\xDA" => "\xE8\xB8\xAE", + "\xF5\xDB" => "\xE8\xB8\xA3", + "\xF5\xDC" => "\xE8\xB8\xAF", + "\xF5\xDD" => "\xE8\xB8\xBA", + "\xF5\xDE" => "\xE8\xB9\x80", + "\xF5\xDF" => "\xE8\xB8\xB9", + "\xF5\xE0" => "\xE8\xB8\xB5", + "\xF5\xE1" => "\xE8\xB8\xBD", + "\xF5\xE2" => "\xE8\xB8\xB1", + "\xF5\xE3" => "\xE8\xB9\x89", + "\xF5\xE4" => "\xE8\xB9\x81", + "\xF5\xE5" => "\xE8\xB9\x82", + "\xF5\xE6" => "\xE8\xB9\x91", + "\xF5\xE7" => "\xE8\xB9\x92", + "\xF5\xE8" => "\xE8\xB9\x8A", + "\xF5\xE9" => "\xE8\xB9\xB0", + "\xF5\xEA" => "\xE8\xB9\xB6", + "\xF5\xEB" => "\xE8\xB9\xBC", + "\xF5\xEC" => "\xE8\xB9\xAF", + "\xF5\xED" => "\xE8\xB9\xB4", + "\xF5\xEE" => "\xE8\xBA\x85", + "\xF5\xEF" => "\xE8\xBA\x8F", + "\xF5\xF0" => "\xE8\xBA\x94", + "\xF5\xF1" => "\xE8\xBA\x90", + "\xF5\xF2" => "\xE8\xBA\x9C", + "\xF5\xF3" => "\xE8\xBA\x9E", + "\xF5\xF4" => "\xE8\xB1\xB8", + "\xF5\xF5" => "\xE8\xB2\x82", + "\xF5\xF6" => "\xE8\xB2\x8A", + "\xF5\xF7" => "\xE8\xB2\x85", + "\xF5\xF8" => "\xE8\xB2\x98", + "\xF5\xF9" => "\xE8\xB2\x94", + "\xF5\xFA" => "\xE6\x96\x9B", + "\xF5\xFB" => "\xE8\xA7\x96", + "\xF5\xFC" => "\xE8\xA7\x9E", + "\xF5\xFD" => "\xE8\xA7\x9A", + "\xF5\xFE" => "\xE8\xA7\x9C", + "\xF6\xA1" => "\xE8\xA7\xA5", + "\xF6\xA2" => "\xE8\xA7\xAB", + "\xF6\xA3" => "\xE8\xA7\xAF", + "\xF6\xA4" => "\xE8\xA8\xBE", + "\xF6\xA5" => "\xE8\xAC\xA6", + "\xF6\xA6" => "\xE9\x9D\x93", + "\xF6\xA7" => "\xE9\x9B\xA9", + "\xF6\xA8" => "\xE9\x9B\xB3", + "\xF6\xA9" => "\xE9\x9B\xAF", + "\xF6\xAA" => "\xE9\x9C\x86", + "\xF6\xAB" => "\xE9\x9C\x81", + "\xF6\xAC" => "\xE9\x9C\x88", + "\xF6\xAD" => "\xE9\x9C\x8F", + "\xF6\xAE" => "\xE9\x9C\x8E", + "\xF6\xAF" => "\xE9\x9C\xAA", + "\xF6\xB0" => "\xE9\x9C\xAD", + "\xF6\xB1" => "\xE9\x9C\xB0", + "\xF6\xB2" => "\xE9\x9C\xBE", + "\xF6\xB3" => "\xE9\xBE\x80", + "\xF6\xB4" => "\xE9\xBE\x83", + "\xF6\xB5" => "\xE9\xBE\x85", + "\xF6\xB6" => "\xE9\xBE\x86", + "\xF6\xB7" => "\xE9\xBE\x87", + "\xF6\xB8" => "\xE9\xBE\x88", + "\xF6\xB9" => "\xE9\xBE\x89", + "\xF6\xBA" => "\xE9\xBE\x8A", + "\xF6\xBB" => "\xE9\xBE\x8C", + "\xF6\xBC" => "\xE9\xBB\xBE", + "\xF6\xBD" => "\xE9\xBC\x8B", + "\xF6\xBE" => "\xE9\xBC\x8D", + "\xF6\xBF" => "\xE9\x9A\xB9", + "\xF6\xC0" => "\xE9\x9A\xBC", + "\xF6\xC1" => "\xE9\x9A\xBD", + "\xF6\xC2" => "\xE9\x9B\x8E", + "\xF6\xC3" => "\xE9\x9B\x92", + "\xF6\xC4" => "\xE7\x9E\xBF", + "\xF6\xC5" => "\xE9\x9B\xA0", + "\xF6\xC6" => "\xE9\x8A\x8E", + "\xF6\xC7" => "\xE9\x8A\xAE", + "\xF6\xC8" => "\xE9\x8B\x88", + "\xF6\xC9" => "\xE9\x8C\xBE", + "\xF6\xCA" => "\xE9\x8D\xAA", + "\xF6\xCB" => "\xE9\x8F\x8A", + "\xF6\xCC" => "\xE9\x8E\x8F", + "\xF6\xCD" => "\xE9\x90\xBE", + "\xF6\xCE" => "\xE9\x91\xAB", + "\xF6\xCF" => "\xE9\xB1\xBF", + "\xF6\xD0" => "\xE9\xB2\x82", + "\xF6\xD1" => "\xE9\xB2\x85", + "\xF6\xD2" => "\xE9\xB2\x86", + "\xF6\xD3" => "\xE9\xB2\x87", + "\xF6\xD4" => "\xE9\xB2\x88", + "\xF6\xD5" => "\xE7\xA8\xA3", + "\xF6\xD6" => "\xE9\xB2\x8B", + "\xF6\xD7" => "\xE9\xB2\x8E", + "\xF6\xD8" => "\xE9\xB2\x90", + "\xF6\xD9" => "\xE9\xB2\x91", + "\xF6\xDA" => "\xE9\xB2\x92", + "\xF6\xDB" => "\xE9\xB2\x94", + "\xF6\xDC" => "\xE9\xB2\x95", + "\xF6\xDD" => "\xE9\xB2\x9A", + "\xF6\xDE" => "\xE9\xB2\x9B", + "\xF6\xDF" => "\xE9\xB2\x9E", + "\xF6\xE0" => "\xE9\xB2\x9F", + "\xF6\xE1" => "\xE9\xB2\xA0", + "\xF6\xE2" => "\xE9\xB2\xA1", + "\xF6\xE3" => "\xE9\xB2\xA2", + "\xF6\xE4" => "\xE9\xB2\xA3", + "\xF6\xE5" => "\xE9\xB2\xA5", + "\xF6\xE6" => "\xE9\xB2\xA6", + "\xF6\xE7" => "\xE9\xB2\xA7", + "\xF6\xE8" => "\xE9\xB2\xA8", + "\xF6\xE9" => "\xE9\xB2\xA9", + "\xF6\xEA" => "\xE9\xB2\xAB", + "\xF6\xEB" => "\xE9\xB2\xAD", + "\xF6\xEC" => "\xE9\xB2\xAE", + "\xF6\xED" => "\xE9\xB2\xB0", + "\xF6\xEE" => "\xE9\xB2\xB1", + "\xF6\xEF" => "\xE9\xB2\xB2", + "\xF6\xF0" => "\xE9\xB2\xB3", + "\xF6\xF1" => "\xE9\xB2\xB4", + "\xF6\xF2" => "\xE9\xB2\xB5", + "\xF6\xF3" => "\xE9\xB2\xB6", + "\xF6\xF4" => "\xE9\xB2\xB7", + "\xF6\xF5" => "\xE9\xB2\xBA", + "\xF6\xF6" => "\xE9\xB2\xBB", + "\xF6\xF7" => "\xE9\xB2\xBC", + "\xF6\xF8" => "\xE9\xB2\xBD", + "\xF6\xF9" => "\xE9\xB3\x84", + "\xF6\xFA" => "\xE9\xB3\x85", + "\xF6\xFB" => "\xE9\xB3\x86", + "\xF6\xFC" => "\xE9\xB3\x87", + "\xF6\xFD" => "\xE9\xB3\x8A", + "\xF6\xFE" => "\xE9\xB3\x8B", + "\xF7\xA1" => "\xE9\xB3\x8C", + "\xF7\xA2" => "\xE9\xB3\x8D", + "\xF7\xA3" => "\xE9\xB3\x8E", + "\xF7\xA4" => "\xE9\xB3\x8F", + "\xF7\xA5" => "\xE9\xB3\x90", + "\xF7\xA6" => "\xE9\xB3\x93", + "\xF7\xA7" => "\xE9\xB3\x94", + "\xF7\xA8" => "\xE9\xB3\x95", + "\xF7\xA9" => "\xE9\xB3\x97", + "\xF7\xAA" => "\xE9\xB3\x98", + "\xF7\xAB" => "\xE9\xB3\x99", + "\xF7\xAC" => "\xE9\xB3\x9C", + "\xF7\xAD" => "\xE9\xB3\x9D", + "\xF7\xAE" => "\xE9\xB3\x9F", + "\xF7\xAF" => "\xE9\xB3\xA2", + "\xF7\xB0" => "\xE9\x9D\xBC", + "\xF7\xB1" => "\xE9\x9E\x85", + "\xF7\xB2" => "\xE9\x9E\x91", + "\xF7\xB3" => "\xE9\x9E\x92", + "\xF7\xB4" => "\xE9\x9E\x94", + "\xF7\xB5" => "\xE9\x9E\xAF", + "\xF7\xB6" => "\xE9\x9E\xAB", + "\xF7\xB7" => "\xE9\x9E\xA3", + "\xF7\xB8" => "\xE9\x9E\xB2", + "\xF7\xB9" => "\xE9\x9E\xB4", + "\xF7\xBA" => "\xE9\xAA\xB1", + "\xF7\xBB" => "\xE9\xAA\xB0", + "\xF7\xBC" => "\xE9\xAA\xB7", + "\xF7\xBD" => "\xE9\xB9\x98", + "\xF7\xBE" => "\xE9\xAA\xB6", + "\xF7\xBF" => "\xE9\xAA\xBA", + "\xF7\xC0" => "\xE9\xAA\xBC", + "\xF7\xC1" => "\xE9\xAB\x81", + "\xF7\xC2" => "\xE9\xAB\x80", + "\xF7\xC3" => "\xE9\xAB\x85", + "\xF7\xC4" => "\xE9\xAB\x82", + "\xF7\xC5" => "\xE9\xAB\x8B", + "\xF7\xC6" => "\xE9\xAB\x8C", + "\xF7\xC7" => "\xE9\xAB\x91", + "\xF7\xC8" => "\xE9\xAD\x85", + "\xF7\xC9" => "\xE9\xAD\x83", + "\xF7\xCA" => "\xE9\xAD\x87", + "\xF7\xCB" => "\xE9\xAD\x89", + "\xF7\xCC" => "\xE9\xAD\x88", + "\xF7\xCD" => "\xE9\xAD\x8D", + "\xF7\xCE" => "\xE9\xAD\x91", + "\xF7\xCF" => "\xE9\xA3\xA8", + "\xF7\xD0" => "\xE9\xA4\x8D", + "\xF7\xD1" => "\xE9\xA4\xAE", + "\xF7\xD2" => "\xE9\xA5\x95", + "\xF7\xD3" => "\xE9\xA5\x94", + "\xF7\xD4" => "\xE9\xAB\x9F", + "\xF7\xD5" => "\xE9\xAB\xA1", + "\xF7\xD6" => "\xE9\xAB\xA6", + "\xF7\xD7" => "\xE9\xAB\xAF", + "\xF7\xD8" => "\xE9\xAB\xAB", + "\xF7\xD9" => "\xE9\xAB\xBB", + "\xF7\xDA" => "\xE9\xAB\xAD", + "\xF7\xDB" => "\xE9\xAB\xB9", + "\xF7\xDC" => "\xE9\xAC\x88", + "\xF7\xDD" => "\xE9\xAC\x8F", + "\xF7\xDE" => "\xE9\xAC\x93", + "\xF7\xDF" => "\xE9\xAC\x9F", + "\xF7\xE0" => "\xE9\xAC\xA3", + "\xF7\xE1" => "\xE9\xBA\xBD", + "\xF7\xE2" => "\xE9\xBA\xBE", + "\xF7\xE3" => "\xE7\xB8\xBB", + "\xF7\xE4" => "\xE9\xBA\x82", + "\xF7\xE5" => "\xE9\xBA\x87", + "\xF7\xE6" => "\xE9\xBA\x88", + "\xF7\xE7" => "\xE9\xBA\x8B", + "\xF7\xE8" => "\xE9\xBA\x92", + "\xF7\xE9" => "\xE9\x8F\x96", + "\xF7\xEA" => "\xE9\xBA\x9D", + "\xF7\xEB" => "\xE9\xBA\x9F", + "\xF7\xEC" => "\xE9\xBB\x9B", + "\xF7\xED" => "\xE9\xBB\x9C", + "\xF7\xEE" => "\xE9\xBB\x9D", + "\xF7\xEF" => "\xE9\xBB\xA0", + "\xF7\xF0" => "\xE9\xBB\x9F", + "\xF7\xF1" => "\xE9\xBB\xA2", + "\xF7\xF2" => "\xE9\xBB\xA9", + "\xF7\xF3" => "\xE9\xBB\xA7", + "\xF7\xF4" => "\xE9\xBB\xA5", + "\xF7\xF5" => "\xE9\xBB\xAA", + "\xF7\xF6" => "\xE9\xBB\xAF", + "\xF7\xF7" => "\xE9\xBC\xA2", + "\xF7\xF8" => "\xE9\xBC\xAC", + "\xF7\xF9" => "\xE9\xBC\xAF", + "\xF7\xFA" => "\xE9\xBC\xB9", + "\xF7\xFB" => "\xE9\xBC\xB7", + "\xF7\xFC" => "\xE9\xBC\xBD", + "\xF7\xFD" => "\xE9\xBC\xBE", + "\xF7\xFE" => "\xE9\xBD\x84", + ); + return strtr($string, $transform); +} + +function sjis($string) +{ + static $transform = array( + "\x5C"=>"\xC2\xA5", + "\x7E"=>"\xE2\x80\xBE", + "\x81\x40"=>"\xE3\x80\x80", + "\x81\x41"=>"\xE3\x80\x81", + "\x81\x42"=>"\xE3\x80\x82", + "\x81\x43"=>"\xEF\xBC\x8C", + "\x81\x44"=>"\xEF\xBC\x8E", + "\x81\x45"=>"\xE3\x83\xBB", + "\x81\x46"=>"\xEF\xBC\x9A", + "\x81\x47"=>"\xEF\xBC\x9B", + "\x81\x48"=>"\xEF\xBC\x9F", + "\x81\x49"=>"\xEF\xBC\x81", + "\x81\x4A"=>"\xE3\x82\x9B", + "\x81\x4B"=>"\xE3\x82\x9C", + "\x81\x4C"=>"\xC2\xB4", + "\x81\x4D"=>"\xEF\xBD\x80", + "\x81\x4E"=>"\xC2\xA8", + "\x81\x4F"=>"\xEF\xBC\xBE", + "\x81\x50"=>"\xEF\xBF\xA3", + "\x81\x51"=>"\xEF\xBC\xBF", + "\x81\x52"=>"\xE3\x83\xBD", + "\x81\x53"=>"\xE3\x83\xBE", + "\x81\x54"=>"\xE3\x82\x9D", + "\x81\x55"=>"\xE3\x82\x9E", + "\x81\x56"=>"\xE3\x80\x83", + "\x81\x57"=>"\xE4\xBB\x9D", + "\x81\x58"=>"\xE3\x80\x85", + "\x81\x59"=>"\xE3\x80\x86", + "\x81\x5A"=>"\xE3\x80\x87", + "\x81\x5B"=>"\xE3\x83\xBC", + "\x81\x5C"=>"\xE2\x80\x95", // set as U+2015 but could be U+2014 + "\x81\x5D"=>"\xE2\x80\x90", + "\x81\x5E"=>"\xEF\xBC\x8F", + "\x81\x5F"=>"\xEF\xBC\xBC", // or U+005C + "\x81\x60"=>"\xE3\x80\x9C", + "\x81\x61"=>"\xE2\x80\x96", + "\x81\x62"=>"\xEF\xBD\x9C", + "\x81\x63"=>"\xE2\x80\xA6", + "\x81\x64"=>"\xE2\x80\xA5", + "\x81\x65"=>"\xE2\x80\x98", + "\x81\x66"=>"\xE2\x80\x99", + "\x81\x67"=>"\xE2\x80\x9C", + "\x81\x68"=>"\xE2\x80\x9D", + "\x81\x69"=>"\xEF\xBC\x88", + "\x81\x6A"=>"\xEF\xBC\x89", + "\x81\x6B"=>"\xE3\x80\x94", + "\x81\x6C"=>"\xE3\x80\x95", + "\x81\x6D"=>"\xEF\xBC\xBB", + "\x81\x6E"=>"\xEF\xBC\xBD", + "\x81\x6F"=>"\xEF\xBD\x9B", + "\x81\x70"=>"\xEF\xBD\x9D", + "\x81\x71"=>"\xE3\x80\x88", + "\x81\x72"=>"\xE3\x80\x89", + "\x81\x73"=>"\xE3\x80\x8A", + "\x81\x74"=>"\xE3\x80\x8B", + "\x81\x75"=>"\xE3\x80\x8C", + "\x81\x76"=>"\xE3\x80\x8D", + "\x81\x77"=>"\xE3\x80\x8E", + "\x81\x78"=>"\xE3\x80\x8F", + "\x81\x79"=>"\xE3\x80\x90", + "\x81\x7A"=>"\xE3\x80\x91", + "\x81\x7B"=>"\xEF\xBC\x8B", + "\x81\x7C"=>"\xE2\x88\x92", + "\x81\x7D"=>"\xC2\xB1", + "\x81\x7E"=>"\xC3\x97", + "\x81\x80"=>"\xC3\xB7", + "\x81\x81"=>"\xEF\xBC\x9D", + "\x81\x82"=>"\xE2\x89\xA0", + "\x81\x83"=>"\xEF\xBC\x9C", + "\x81\x84"=>"\xEF\xBC\x9E", + "\x81\x85"=>"\xE2\x89\xA6", + "\x81\x86"=>"\xE2\x89\xA7", + "\x81\x87"=>"\xE2\x88\x9E", + "\x81\x88"=>"\xE2\x88\xB4", + "\x81\x89"=>"\xE2\x99\x82", + "\x81\x8A"=>"\xE2\x99\x80", + "\x81\x8B"=>"\xC2\xB0", + "\x81\x8C"=>"\xE2\x80\xB2", + "\x81\x8D"=>"\xE2\x80\xB3", + "\x81\x8E"=>"\xE2\x84\x83", + "\x81\x8F"=>"\xEF\xBF\xA5", + "\x81\x90"=>"\xEF\xBC\x84", + "\x81\x91"=>"\xC2\xA2", + "\x81\x92"=>"\xC2\xA3", + "\x81\x93"=>"\xEF\xBC\x85", + "\x81\x94"=>"\xEF\xBC\x83", + "\x81\x95"=>"\xEF\xBC\x86", + "\x81\x96"=>"\xEF\xBC\x8A", + "\x81\x97"=>"\xEF\xBC\xA0", + "\x81\x98"=>"\xC2\xA7", + "\x81\x99"=>"\xE2\x98\x86", + "\x81\x9A"=>"\xE2\x98\x85", + "\x81\x9B"=>"\xE2\x97\x8B", + "\x81\x9C"=>"\xE2\x97\x8F", + "\x81\x9D"=>"\xE2\x97\x8E", + "\x81\x9E"=>"\xE2\x97\x87", + "\x81\x9F"=>"\xE2\x97\x86", + "\x81\xA0"=>"\xE2\x96\xA1", + "\x81\xA1"=>"\xE2\x96\xA0", + "\x81\xA2"=>"\xE2\x96\xB3", + "\x81\xA3"=>"\xE2\x96\xB2", + "\x81\xA4"=>"\xE2\x96\xBD", + "\x81\xA5"=>"\xE2\x96\xBC", + "\x81\xA6"=>"\xE2\x80\xBB", + "\x81\xA7"=>"\xE3\x80\x92", + "\x81\xA8"=>"\xE2\x86\x92", + "\x81\xA9"=>"\xE2\x86\x90", + "\x81\xAA"=>"\xE2\x86\x91", + "\x81\xAB"=>"\xE2\x86\x93", + "\x81\xAC"=>"\xE3\x80\x93", + "\x81\xAD"=>"\xEF\xBC\x87", // extra?! + "\x81\xB8"=>"\xE2\x88\x88", + "\x81\xB9"=>"\xE2\x88\x8B", + "\x81\xBA"=>"\xE2\x8A\x86", + "\x81\xBB"=>"\xE2\x8A\x87", + "\x81\xBC"=>"\xE2\x8A\x82", + "\x81\xBD"=>"\xE2\x8A\x83", + "\x81\xBE"=>"\xE2\x88\xAA", + "\x81\xBF"=>"\xE2\x88\xA9", + "\x81\xC8"=>"\xE2\x88\xA7", + "\x81\xC9"=>"\xE2\x88\xA8", + "\x81\xCA"=>"\xC2\xAC", + "\x81\xCB"=>"\xE2\x87\x92", + "\x81\xCC"=>"\xE2\x87\x94", + "\x81\xCD"=>"\xE2\x88\x80", + "\x81\xCE"=>"\xE2\x88\x83", + "\x81\xDA"=>"\xE2\x88\xA0", + "\x81\xDB"=>"\xE2\x8A\xA5", + "\x81\xDC"=>"\xE2\x8C\x92", + "\x81\xDD"=>"\xE2\x88\x82", + "\x81\xDE"=>"\xE2\x88\x87", + "\x81\xDF"=>"\xE2\x89\xA1", + "\x81\xE0"=>"\xE2\x89\x92", + "\x81\xE1"=>"\xE2\x89\xAA", + "\x81\xE2"=>"\xE2\x89\xAB", + "\x81\xE3"=>"\xE2\x88\x9A", + "\x81\xE4"=>"\xE2\x88\xBD", + "\x81\xE5"=>"\xE2\x88\x9D", + "\x81\xE6"=>"\xE2\x88\xB5", + "\x81\xE7"=>"\xE2\x88\xAB", + "\x81\xE8"=>"\xE2\x88\xAC", + "\x81\xF0"=>"\xE2\x84\xAB", + "\x81\xF1"=>"\xE2\x80\xB0", + "\x81\xF2"=>"\xE2\x99\xAF", + "\x81\xF3"=>"\xE2\x99\xAD", + "\x81\xF4"=>"\xE2\x99\xAA", + "\x81\xF5"=>"\xE2\x80\xA0", + "\x81\xF6"=>"\xE2\x80\xA1", + "\x81\xF7"=>"\xC2\xB6", + "\x81\xFC"=>"\xE2\x97\xAF", + "\x82\x4F"=>"\xEF\xBC\x90", + "\x82\x50"=>"\xEF\xBC\x91", + "\x82\x51"=>"\xEF\xBC\x92", + "\x82\x52"=>"\xEF\xBC\x93", + "\x82\x53"=>"\xEF\xBC\x94", + "\x82\x54"=>"\xEF\xBC\x95", + "\x82\x55"=>"\xEF\xBC\x96", + "\x82\x56"=>"\xEF\xBC\x97", + "\x82\x57"=>"\xEF\xBC\x98", + "\x82\x58"=>"\xEF\xBC\x99", + "\x82\x60"=>"\xEF\xBC\xA1", + "\x82\x61"=>"\xEF\xBC\xA2", + "\x82\x62"=>"\xEF\xBC\xA3", + "\x82\x63"=>"\xEF\xBC\xA4", + "\x82\x64"=>"\xEF\xBC\xA5", + "\x82\x65"=>"\xEF\xBC\xA6", + "\x82\x66"=>"\xEF\xBC\xA7", + "\x82\x67"=>"\xEF\xBC\xA8", + "\x82\x68"=>"\xEF\xBC\xA9", + "\x82\x69"=>"\xEF\xBC\xAA", + "\x82\x6A"=>"\xEF\xBC\xAB", + "\x82\x6B"=>"\xEF\xBC\xAC", + "\x82\x6C"=>"\xEF\xBC\xAD", + "\x82\x6D"=>"\xEF\xBC\xAE", + "\x82\x6E"=>"\xEF\xBC\xAF", + "\x82\x6F"=>"\xEF\xBC\xB0", + "\x82\x70"=>"\xEF\xBC\xB1", + "\x82\x71"=>"\xEF\xBC\xB2", + "\x82\x72"=>"\xEF\xBC\xB3", + "\x82\x73"=>"\xEF\xBC\xB4", + "\x82\x74"=>"\xEF\xBC\xB5", + "\x82\x75"=>"\xEF\xBC\xB6", + "\x82\x76"=>"\xEF\xBC\xB7", + "\x82\x77"=>"\xEF\xBC\xB8", + "\x82\x78"=>"\xEF\xBC\xB9", + "\x82\x79"=>"\xEF\xBC\xBA", + "\x82\x81"=>"\xEF\xBD\x81", + "\x82\x82"=>"\xEF\xBD\x82", + "\x82\x83"=>"\xEF\xBD\x83", + "\x82\x84"=>"\xEF\xBD\x84", + "\x82\x85"=>"\xEF\xBD\x85", + "\x82\x86"=>"\xEF\xBD\x86", + "\x82\x87"=>"\xEF\xBD\x87", + "\x82\x88"=>"\xEF\xBD\x88", + "\x82\x89"=>"\xEF\xBD\x89", + "\x82\x8A"=>"\xEF\xBD\x8A", + "\x82\x8B"=>"\xEF\xBD\x8B", + "\x82\x8C"=>"\xEF\xBD\x8C", + "\x82\x8D"=>"\xEF\xBD\x8D", + "\x82\x8E"=>"\xEF\xBD\x8E", + "\x82\x8F"=>"\xEF\xBD\x8F", + "\x82\x90"=>"\xEF\xBD\x90", + "\x82\x91"=>"\xEF\xBD\x91", + "\x82\x92"=>"\xEF\xBD\x92", + "\x82\x93"=>"\xEF\xBD\x93", + "\x82\x94"=>"\xEF\xBD\x94", + "\x82\x95"=>"\xEF\xBD\x95", + "\x82\x96"=>"\xEF\xBD\x96", + "\x82\x97"=>"\xEF\xBD\x97", + "\x82\x98"=>"\xEF\xBD\x98", + "\x82\x99"=>"\xEF\xBD\x99", + "\x82\x9A"=>"\xEF\xBD\x9A", + "\x82\x9F"=>"\xE3\x81\x81", + "\x82\xA0"=>"\xE3\x81\x82", + "\x82\xA1"=>"\xE3\x81\x83", + "\x82\xA2"=>"\xE3\x81\x84", + "\x82\xA3"=>"\xE3\x81\x85", + "\x82\xA4"=>"\xE3\x81\x86", + "\x82\xA5"=>"\xE3\x81\x87", + "\x82\xA6"=>"\xE3\x81\x88", + "\x82\xA7"=>"\xE3\x81\x89", + "\x82\xA8"=>"\xE3\x81\x8A", + "\x82\xA9"=>"\xE3\x81\x8B", + "\x82\xAA"=>"\xE3\x81\x8C", + "\x82\xAB"=>"\xE3\x81\x8D", + "\x82\xAC"=>"\xE3\x81\x8E", + "\x82\xAD"=>"\xE3\x81\x8F", + "\x82\xAE"=>"\xE3\x81\x90", + "\x82\xAF"=>"\xE3\x81\x91", + "\x82\xB0"=>"\xE3\x81\x92", + "\x82\xB1"=>"\xE3\x81\x93", + "\x82\xB2"=>"\xE3\x81\x94", + "\x82\xB3"=>"\xE3\x81\x95", + "\x82\xB4"=>"\xE3\x81\x96", + "\x82\xB5"=>"\xE3\x81\x97", + "\x82\xB6"=>"\xE3\x81\x98", + "\x82\xB7"=>"\xE3\x81\x99", + "\x82\xB8"=>"\xE3\x81\x9A", + "\x82\xB9"=>"\xE3\x81\x9B", + "\x82\xBA"=>"\xE3\x81\x9C", + "\x82\xBB"=>"\xE3\x81\x9D", + "\x82\xBC"=>"\xE3\x81\x9E", + "\x82\xBD"=>"\xE3\x81\x9F", + "\x82\xBE"=>"\xE3\x81\xA0", + "\x82\xBF"=>"\xE3\x81\xA1", + "\x82\xC0"=>"\xE3\x81\xA2", + "\x82\xC1"=>"\xE3\x81\xA3", + "\x82\xC2"=>"\xE3\x81\xA4", + "\x82\xC3"=>"\xE3\x81\xA5", + "\x82\xC4"=>"\xE3\x81\xA6", + "\x82\xC5"=>"\xE3\x81\xA7", + "\x82\xC6"=>"\xE3\x81\xA8", + "\x82\xC7"=>"\xE3\x81\xA9", + "\x82\xC8"=>"\xE3\x81\xAA", + "\x82\xC9"=>"\xE3\x81\xAB", + "\x82\xCA"=>"\xE3\x81\xAC", + "\x82\xCB"=>"\xE3\x81\xAD", + "\x82\xCC"=>"\xE3\x81\xAE", + "\x82\xCD"=>"\xE3\x81\xAF", + "\x82\xCE"=>"\xE3\x81\xB0", + "\x82\xCF"=>"\xE3\x81\xB1", + "\x82\xD0"=>"\xE3\x81\xB2", + "\x82\xD1"=>"\xE3\x81\xB3", + "\x82\xD2"=>"\xE3\x81\xB4", + "\x82\xD3"=>"\xE3\x81\xB5", + "\x82\xD4"=>"\xE3\x81\xB6", + "\x82\xD5"=>"\xE3\x81\xB7", + "\x82\xD6"=>"\xE3\x81\xB8", + "\x82\xD7"=>"\xE3\x81\xB9", + "\x82\xD8"=>"\xE3\x81\xBA", + "\x82\xD9"=>"\xE3\x81\xBB", + "\x82\xDA"=>"\xE3\x81\xBC", + "\x82\xDB"=>"\xE3\x81\xBD", + "\x82\xDC"=>"\xE3\x81\xBE", + "\x82\xDD"=>"\xE3\x81\xBF", + "\x82\xDE"=>"\xE3\x82\x80", + "\x82\xDF"=>"\xE3\x82\x81", + "\x82\xE0"=>"\xE3\x82\x82", + "\x82\xE1"=>"\xE3\x82\x83", + "\x82\xE2"=>"\xE3\x82\x84", + "\x82\xE3"=>"\xE3\x82\x85", + "\x82\xE4"=>"\xE3\x82\x86", + "\x82\xE5"=>"\xE3\x82\x87", + "\x82\xE6"=>"\xE3\x82\x88", + "\x82\xE7"=>"\xE3\x82\x89", + "\x82\xE8"=>"\xE3\x82\x8A", + "\x82\xE9"=>"\xE3\x82\x8B", + "\x82\xEA"=>"\xE3\x82\x8C", + "\x82\xEB"=>"\xE3\x82\x8D", + "\x82\xEC"=>"\xE3\x82\x8E", + "\x82\xED"=>"\xE3\x82\x8F", + "\x82\xEE"=>"\xE3\x82\x90", + "\x82\xEF"=>"\xE3\x82\x91", + "\x82\xF0"=>"\xE3\x82\x92", + "\x82\xF1"=>"\xE3\x82\x93", + "\x83\x40"=>"\xE3\x82\xA1", + "\x83\x41"=>"\xE3\x82\xA2", + "\x83\x42"=>"\xE3\x82\xA3", + "\x83\x43"=>"\xE3\x82\xA4", + "\x83\x44"=>"\xE3\x82\xA5", + "\x83\x45"=>"\xE3\x82\xA6", + "\x83\x46"=>"\xE3\x82\xA7", + "\x83\x47"=>"\xE3\x82\xA8", + "\x83\x48"=>"\xE3\x82\xA9", + "\x83\x49"=>"\xE3\x82\xAA", + "\x83\x4A"=>"\xE3\x82\xAB", + "\x83\x4B"=>"\xE3\x82\xAC", + "\x83\x4C"=>"\xE3\x82\xAD", + "\x83\x4D"=>"\xE3\x82\xAE", + "\x83\x4E"=>"\xE3\x82\xAF", + "\x83\x4F"=>"\xE3\x82\xB0", + "\x83\x50"=>"\xE3\x82\xB1", + "\x83\x51"=>"\xE3\x82\xB2", + "\x83\x52"=>"\xE3\x82\xB3", + "\x83\x53"=>"\xE3\x82\xB4", + "\x83\x54"=>"\xE3\x82\xB5", + "\x83\x55"=>"\xE3\x82\xB6", + "\x83\x56"=>"\xE3\x82\xB7", + "\x83\x57"=>"\xE3\x82\xB8", + "\x83\x58"=>"\xE3\x82\xB9", + "\x83\x59"=>"\xE3\x82\xBA", + "\x83\x5A"=>"\xE3\x82\xBB", + "\x83\x5B"=>"\xE3\x82\xBC", + "\x83\x5C"=>"\xE3\x82\xBD", + "\x83\x5D"=>"\xE3\x82\xBE", + "\x83\x5E"=>"\xE3\x82\xBF", + "\x83\x5F"=>"\xE3\x83\x80", + "\x83\x60"=>"\xE3\x83\x81", + "\x83\x61"=>"\xE3\x83\x82", + "\x83\x62"=>"\xE3\x83\x83", + "\x83\x63"=>"\xE3\x83\x84", + "\x83\x64"=>"\xE3\x83\x85", + "\x83\x65"=>"\xE3\x83\x86", + "\x83\x66"=>"\xE3\x83\x87", + "\x83\x67"=>"\xE3\x83\x88", + "\x83\x68"=>"\xE3\x83\x89", + "\x83\x69"=>"\xE3\x83\x8A", + "\x83\x6A"=>"\xE3\x83\x8B", + "\x83\x6B"=>"\xE3\x83\x8C", + "\x83\x6C"=>"\xE3\x83\x8D", + "\x83\x6D"=>"\xE3\x83\x8E", + "\x83\x6E"=>"\xE3\x83\x8F", + "\x83\x6F"=>"\xE3\x83\x90", + "\x83\x70"=>"\xE3\x83\x91", + "\x83\x71"=>"\xE3\x83\x92", + "\x83\x72"=>"\xE3\x83\x93", + "\x83\x73"=>"\xE3\x83\x94", + "\x83\x74"=>"\xE3\x83\x95", + "\x83\x75"=>"\xE3\x83\x96", + "\x83\x76"=>"\xE3\x83\x97", + "\x83\x77"=>"\xE3\x83\x98", + "\x83\x78"=>"\xE3\x83\x99", + "\x83\x79"=>"\xE3\x83\x9A", + "\x83\x7A"=>"\xE3\x83\x9B", + "\x83\x7B"=>"\xE3\x83\x9C", + "\x83\x7C"=>"\xE3\x83\x9D", + "\x83\x7D"=>"\xE3\x83\x9E", + "\x83\x7E"=>"\xE3\x83\x9F", + "\x83\x80"=>"\xE3\x83\xA0", + "\x83\x81"=>"\xE3\x83\xA1", + "\x83\x82"=>"\xE3\x83\xA2", + "\x83\x83"=>"\xE3\x83\xA3", + "\x83\x84"=>"\xE3\x83\xA4", + "\x83\x85"=>"\xE3\x83\xA5", + "\x83\x86"=>"\xE3\x83\xA6", + "\x83\x87"=>"\xE3\x83\xA7", + "\x83\x88"=>"\xE3\x83\xA8", + "\x83\x89"=>"\xE3\x83\xA9", + "\x83\x8A"=>"\xE3\x83\xAA", + "\x83\x8B"=>"\xE3\x83\xAB", + "\x83\x8C"=>"\xE3\x83\xAC", + "\x83\x8D"=>"\xE3\x83\xAD", + "\x83\x8E"=>"\xE3\x83\xAE", + "\x83\x8F"=>"\xE3\x83\xAF", + "\x83\x90"=>"\xE3\x83\xB0", + "\x83\x91"=>"\xE3\x83\xB1", + "\x83\x92"=>"\xE3\x83\xB2", + "\x83\x93"=>"\xE3\x83\xB3", + "\x83\x94"=>"\xE3\x83\xB4", + "\x83\x95"=>"\xE3\x83\xB5", + "\x83\x96"=>"\xE3\x83\xB6", + "\x83\x9F"=>"\xCE\x91", + "\x83\xA0"=>"\xCE\x92", + "\x83\xA1"=>"\xCE\x93", + "\x83\xA2"=>"\xCE\x94", + "\x83\xA3"=>"\xCE\x95", + "\x83\xA4"=>"\xCE\x96", + "\x83\xA5"=>"\xCE\x97", + "\x83\xA6"=>"\xCE\x98", + "\x83\xA7"=>"\xCE\x99", + "\x83\xA8"=>"\xCE\x9A", + "\x83\xA9"=>"\xCE\x9B", + "\x83\xAA"=>"\xCE\x9C", + "\x83\xAB"=>"\xCE\x9D", + "\x83\xAC"=>"\xCE\x9E", + "\x83\xAD"=>"\xCE\x9F", + "\x83\xAE"=>"\xCE\xA0", + "\x83\xAF"=>"\xCE\xA1", + "\x83\xB0"=>"\xCE\xA3", + "\x83\xB1"=>"\xCE\xA4", + "\x83\xB2"=>"\xCE\xA5", + "\x83\xB3"=>"\xCE\xA6", + "\x83\xB4"=>"\xCE\xA7", + "\x83\xB5"=>"\xCE\xA8", + "\x83\xB6"=>"\xCE\xA9", + "\x83\xBF"=>"\xCE\xB1", + "\x83\xC0"=>"\xCE\xB2", + "\x83\xC1"=>"\xCE\xB3", + "\x83\xC2"=>"\xCE\xB4", + "\x83\xC3"=>"\xCE\xB5", + "\x83\xC4"=>"\xCE\xB6", + "\x83\xC5"=>"\xCE\xB7", + "\x83\xC6"=>"\xCE\xB8", + "\x83\xC7"=>"\xCE\xB9", + "\x83\xC8"=>"\xCE\xBA", + "\x83\xC9"=>"\xCE\xBB", + "\x83\xCA"=>"\xCE\xBC", + "\x83\xCB"=>"\xCE\xBD", + "\x83\xCC"=>"\xCE\xBE", + "\x83\xCD"=>"\xCE\xBF", + "\x83\xCE"=>"\xCF\x80", + "\x83\xCF"=>"\xCF\x81", + "\x83\xD0"=>"\xCF\x83", + "\x83\xD1"=>"\xCF\x84", + "\x83\xD2"=>"\xCF\x85", + "\x83\xD3"=>"\xCF\x86", + "\x83\xD4"=>"\xCF\x87", + "\x83\xD5"=>"\xCF\x88", + "\x83\xD6"=>"\xCF\x89", + "\x84\x40"=>"\xD0\x90", + "\x84\x41"=>"\xD0\x91", + "\x84\x42"=>"\xD0\x92", + "\x84\x43"=>"\xD0\x93", + "\x84\x44"=>"\xD0\x94", + "\x84\x45"=>"\xD0\x95", + "\x84\x46"=>"\xD0\x81", + "\x84\x47"=>"\xD0\x96", + "\x84\x48"=>"\xD0\x97", + "\x84\x49"=>"\xD0\x98", + "\x84\x4A"=>"\xD0\x99", + "\x84\x4B"=>"\xD0\x9A", + "\x84\x4C"=>"\xD0\x9B", + "\x84\x4D"=>"\xD0\x9C", + "\x84\x4E"=>"\xD0\x9D", + "\x84\x4F"=>"\xD0\x9E", + "\x84\x50"=>"\xD0\x9F", + "\x84\x51"=>"\xD0\xA0", + "\x84\x52"=>"\xD0\xA1", + "\x84\x53"=>"\xD0\xA2", + "\x84\x54"=>"\xD0\xA3", + "\x84\x55"=>"\xD0\xA4", + "\x84\x56"=>"\xD0\xA5", + "\x84\x57"=>"\xD0\xA6", + "\x84\x58"=>"\xD0\xA7", + "\x84\x59"=>"\xD0\xA8", + "\x84\x5A"=>"\xD0\xA9", + "\x84\x5B"=>"\xD0\xAA", + "\x84\x5C"=>"\xD0\xAB", + "\x84\x5D"=>"\xD0\xAC", + "\x84\x5E"=>"\xD0\xAD", + "\x84\x5F"=>"\xD0\xAE", + "\x84\x60"=>"\xD0\xAF", + "\x84\x70"=>"\xD0\xB0", + "\x84\x71"=>"\xD0\xB1", + "\x84\x72"=>"\xD0\xB2", + "\x84\x73"=>"\xD0\xB3", + "\x84\x74"=>"\xD0\xB4", + "\x84\x75"=>"\xD0\xB5", + "\x84\x76"=>"\xD1\x91", + "\x84\x77"=>"\xD0\xB6", + "\x84\x78"=>"\xD0\xB7", + "\x84\x79"=>"\xD0\xB8", + "\x84\x7A"=>"\xD0\xB9", + "\x84\x7B"=>"\xD0\xBA", + "\x84\x7C"=>"\xD0\xBB", + "\x84\x7D"=>"\xD0\xBC", + "\x84\x7E"=>"\xD0\xBD", + "\x84\x80"=>"\xD0\xBE", + "\x84\x81"=>"\xD0\xBF", + "\x84\x82"=>"\xD1\x80", + "\x84\x83"=>"\xD1\x81", + "\x84\x84"=>"\xD1\x82", + "\x84\x85"=>"\xD1\x83", + "\x84\x86"=>"\xD1\x84", + "\x84\x87"=>"\xD1\x85", + "\x84\x88"=>"\xD1\x86", + "\x84\x89"=>"\xD1\x87", + "\x84\x8A"=>"\xD1\x88", + "\x84\x8B"=>"\xD1\x89", + "\x84\x8C"=>"\xD1\x8A", + "\x84\x8D"=>"\xD1\x8B", + "\x84\x8E"=>"\xD1\x8C", + "\x84\x8F"=>"\xD1\x8D", + "\x84\x90"=>"\xD1\x8E", + "\x84\x91"=>"\xD1\x8F", + "\x84\x9F"=>"\xE2\x94\x80", + "\x84\xA0"=>"\xE2\x94\x82", + "\x84\xA1"=>"\xE2\x94\x8C", + "\x84\xA2"=>"\xE2\x94\x90", + "\x84\xA3"=>"\xE2\x94\x98", + "\x84\xA4"=>"\xE2\x94\x94", + "\x84\xA5"=>"\xE2\x94\x9C", + "\x84\xA6"=>"\xE2\x94\xAC", + "\x84\xA7"=>"\xE2\x94\xA4", + "\x84\xA8"=>"\xE2\x94\xB4", + "\x84\xA9"=>"\xE2\x94\xBC", + "\x84\xAA"=>"\xE2\x94\x81", + "\x84\xAB"=>"\xE2\x94\x83", + "\x84\xAC"=>"\xE2\x94\x8F", + "\x84\xAD"=>"\xE2\x94\x93", + "\x84\xAE"=>"\xE2\x94\x9B", + "\x84\xAF"=>"\xE2\x94\x97", + "\x84\xB0"=>"\xE2\x94\xA3", + "\x84\xB1"=>"\xE2\x94\xB3", + "\x84\xB2"=>"\xE2\x94\xAB", + "\x84\xB3"=>"\xE2\x94\xBB", + "\x84\xB4"=>"\xE2\x95\x8B", + "\x84\xB5"=>"\xE2\x94\xA0", + "\x84\xB6"=>"\xE2\x94\xAF", + "\x84\xB7"=>"\xE2\x94\xA8", + "\x84\xB8"=>"\xE2\x94\xB7", + "\x84\xB9"=>"\xE2\x94\xBF", + "\x84\xBA"=>"\xE2\x94\x9D", + "\x84\xBB"=>"\xE2\x94\xB0", + "\x84\xBC"=>"\xE2\x94\xA5", + "\x84\xBD"=>"\xE2\x94\xB8", + "\x84\xBE"=>"\xE2\x95\x82", + "\x88\x9F"=>"\xE4\xBA\x9C", + "\x88\xA0"=>"\xE5\x94\x96", + "\x88\xA1"=>"\xE5\xA8\x83", + "\x88\xA2"=>"\xE9\x98\xBF", + "\x88\xA3"=>"\xE5\x93\x80", + "\x88\xA4"=>"\xE6\x84\x9B", + "\x88\xA5"=>"\xE6\x8C\xA8", + "\x88\xA6"=>"\xE5\xA7\xB6", + "\x88\xA7"=>"\xE9\x80\xA2", + "\x88\xA8"=>"\xE8\x91\xB5", + "\x88\xA9"=>"\xE8\x8C\x9C", + "\x88\xAA"=>"\xE7\xA9\x90", + "\x88\xAB"=>"\xE6\x82\xAA", + "\x88\xAC"=>"\xE6\x8F\xA1", + "\x88\xAD"=>"\xE6\xB8\xA5", + "\x88\xAE"=>"\xE6\x97\xAD", + "\x88\xAF"=>"\xE8\x91\xA6", + "\x88\xB0"=>"\xE8\x8A\xA6", + "\x88\xB1"=>"\xE9\xAF\xB5", + "\x88\xB2"=>"\xE6\xA2\x93", + "\x88\xB3"=>"\xE5\x9C\xA7", + "\x88\xB4"=>"\xE6\x96\xA1", + "\x88\xB5"=>"\xE6\x89\xB1", + "\x88\xB6"=>"\xE5\xAE\x9B", + "\x88\xB7"=>"\xE5\xA7\x90", + "\x88\xB8"=>"\xE8\x99\xBB", + "\x88\xB9"=>"\xE9\xA3\xB4", + "\x88\xBA"=>"\xE7\xB5\xA2", + "\x88\xBB"=>"\xE7\xB6\xBE", + "\x88\xBC"=>"\xE9\xAE\x8E", + "\x88\xBD"=>"\xE6\x88\x96", + "\x88\xBE"=>"\xE7\xB2\x9F", + "\x88\xBF"=>"\xE8\xA2\xB7", + "\x88\xC0"=>"\xE5\xAE\x89", + "\x88\xC1"=>"\xE5\xBA\xB5", + "\x88\xC2"=>"\xE6\x8C\x89", + "\x88\xC3"=>"\xE6\x9A\x97", + "\x88\xC4"=>"\xE6\xA1\x88", + "\x88\xC5"=>"\xE9\x97\x87", + "\x88\xC6"=>"\xE9\x9E\x8D", + "\x88\xC7"=>"\xE6\x9D\x8F", + "\x88\xC8"=>"\xE4\xBB\xA5", + "\x88\xC9"=>"\xE4\xBC\x8A", + "\x88\xCA"=>"\xE4\xBD\x8D", + "\x88\xCB"=>"\xE4\xBE\x9D", + "\x88\xCC"=>"\xE5\x81\x89", + "\x88\xCD"=>"\xE5\x9B\xB2", + "\x88\xCE"=>"\xE5\xA4\xB7", + "\x88\xCF"=>"\xE5\xA7\x94", + "\x88\xD0"=>"\xE5\xA8\x81", + "\x88\xD1"=>"\xE5\xB0\x89", + "\x88\xD2"=>"\xE6\x83\x9F", + "\x88\xD3"=>"\xE6\x84\x8F", + "\x88\xD4"=>"\xE6\x85\xB0", + "\x88\xD5"=>"\xE6\x98\x93", + "\x88\xD6"=>"\xE6\xA4\x85", + "\x88\xD7"=>"\xE7\x82\xBA", + "\x88\xD8"=>"\xE7\x95\x8F", + "\x88\xD9"=>"\xE7\x95\xB0", + "\x88\xDA"=>"\xE7\xA7\xBB", + "\x88\xDB"=>"\xE7\xB6\xAD", + "\x88\xDC"=>"\xE7\xB7\xAF", + "\x88\xDD"=>"\xE8\x83\x83", + "\x88\xDE"=>"\xE8\x90\x8E", + "\x88\xDF"=>"\xE8\xA1\xA3", + "\x88\xE0"=>"\xE8\xAC\x82", + "\x88\xE1"=>"\xE9\x81\x95", + "\x88\xE2"=>"\xE9\x81\xBA", + "\x88\xE3"=>"\xE5\x8C\xBB", + "\x88\xE4"=>"\xE4\xBA\x95", + "\x88\xE5"=>"\xE4\xBA\xA5", + "\x88\xE6"=>"\xE5\x9F\x9F", + "\x88\xE7"=>"\xE8\x82\xB2", + "\x88\xE8"=>"\xE9\x83\x81", + "\x88\xE9"=>"\xE7\xA3\xAF", + "\x88\xEA"=>"\xE4\xB8\x80", + "\x88\xEB"=>"\xE5\xA3\xB1", + "\x88\xEC"=>"\xE6\xBA\xA2", + "\x88\xED"=>"\xE9\x80\xB8", + "\x88\xEE"=>"\xE7\xA8\xB2", + "\x88\xEF"=>"\xE8\x8C\xA8", + "\x88\xF0"=>"\xE8\x8A\x8B", + "\x88\xF1"=>"\xE9\xB0\xAF", + "\x88\xF2"=>"\xE5\x85\x81", + "\x88\xF3"=>"\xE5\x8D\xB0", + "\x88\xF4"=>"\xE5\x92\xBD", + "\x88\xF5"=>"\xE5\x93\xA1", + "\x88\xF6"=>"\xE5\x9B\xA0", + "\x88\xF7"=>"\xE5\xA7\xBB", + "\x88\xF8"=>"\xE5\xBC\x95", + "\x88\xF9"=>"\xE9\xA3\xB2", + "\x88\xFA"=>"\xE6\xB7\xAB", + "\x88\xFB"=>"\xE8\x83\xA4", + "\x88\xFC"=>"\xE8\x94\xAD", + "\x89\x40"=>"\xE9\x99\xA2", + "\x89\x41"=>"\xE9\x99\xB0", + "\x89\x42"=>"\xE9\x9A\xA0", + "\x89\x43"=>"\xE9\x9F\xBB", + "\x89\x44"=>"\xE5\x90\x8B", + "\x89\x45"=>"\xE5\x8F\xB3", + "\x89\x46"=>"\xE5\xAE\x87", + "\x89\x47"=>"\xE7\x83\x8F", + "\x89\x48"=>"\xE7\xBE\xBD", + "\x89\x49"=>"\xE8\xBF\x82", + "\x89\x4A"=>"\xE9\x9B\xA8", + "\x89\x4B"=>"\xE5\x8D\xAF", + "\x89\x4C"=>"\xE9\xB5\x9C", + "\x89\x4D"=>"\xE7\xAA\xBA", + "\x89\x4E"=>"\xE4\xB8\x91", + "\x89\x4F"=>"\xE7\xA2\x93", + "\x89\x50"=>"\xE8\x87\xBC", + "\x89\x51"=>"\xE6\xB8\xA6", + "\x89\x52"=>"\xE5\x98\x98", + "\x89\x53"=>"\xE5\x94\x84", + "\x89\x54"=>"\xE6\xAC\x9D", + "\x89\x55"=>"\xE8\x94\x9A", + "\x89\x56"=>"\xE9\xB0\xBB", + "\x89\x57"=>"\xE5\xA7\xA5", + "\x89\x58"=>"\xE5\x8E\xA9", + "\x89\x59"=>"\xE6\xB5\xA6", + "\x89\x5A"=>"\xE7\x93\x9C", + "\x89\x5B"=>"\xE9\x96\x8F", + "\x89\x5C"=>"\xE5\x99\x82", + "\x89\x5D"=>"\xE4\xBA\x91", + "\x89\x5E"=>"\xE9\x81\x8B", + "\x89\x5F"=>"\xE9\x9B\xB2", + "\x89\x60"=>"\xE8\x8D\x8F", + "\x89\x61"=>"\xE9\xA4\x8C", + "\x89\x62"=>"\xE5\x8F\xA1", + "\x89\x63"=>"\xE5\x96\xB6", + "\x89\x64"=>"\xE5\xAC\xB0", + "\x89\x65"=>"\xE5\xBD\xB1", + "\x89\x66"=>"\xE6\x98\xA0", + "\x89\x67"=>"\xE6\x9B\xB3", + "\x89\x68"=>"\xE6\xA0\x84", + "\x89\x69"=>"\xE6\xB0\xB8", + "\x89\x6A"=>"\xE6\xB3\xB3", + "\x89\x6B"=>"\xE6\xB4\xA9", + "\x89\x6C"=>"\xE7\x91\x9B", + "\x89\x6D"=>"\xE7\x9B\x88", + "\x89\x6E"=>"\xE7\xA9\x8E", + "\x89\x6F"=>"\xE9\xA0\xB4", + "\x89\x70"=>"\xE8\x8B\xB1", + "\x89\x71"=>"\xE8\xA1\x9B", + "\x89\x72"=>"\xE8\xA9\xA0", + "\x89\x73"=>"\xE9\x8B\xAD", + "\x89\x74"=>"\xE6\xB6\xB2", + "\x89\x75"=>"\xE7\x96\xAB", + "\x89\x76"=>"\xE7\x9B\x8A", + "\x89\x77"=>"\xE9\xA7\x85", + "\x89\x78"=>"\xE6\x82\xA6", + "\x89\x79"=>"\xE8\xAC\x81", + "\x89\x7A"=>"\xE8\xB6\x8A", + "\x89\x7B"=>"\xE9\x96\xB2", + "\x89\x7C"=>"\xE6\xA6\x8E", + "\x89\x7D"=>"\xE5\x8E\xAD", + "\x89\x7E"=>"\xE5\x86\x86", + "\x89\x80"=>"\xE5\x9C\x92", + "\x89\x81"=>"\xE5\xA0\xB0", + "\x89\x82"=>"\xE5\xA5\x84", + "\x89\x83"=>"\xE5\xAE\xB4", + "\x89\x84"=>"\xE5\xBB\xB6", + "\x89\x85"=>"\xE6\x80\xA8", + "\x89\x86"=>"\xE6\x8E\xA9", + "\x89\x87"=>"\xE6\x8F\xB4", + "\x89\x88"=>"\xE6\xB2\xBF", + "\x89\x89"=>"\xE6\xBC\x94", + "\x89\x8A"=>"\xE7\x82\x8E", + "\x89\x8B"=>"\xE7\x84\x94", + "\x89\x8C"=>"\xE7\x85\x99", + "\x89\x8D"=>"\xE7\x87\x95", + "\x89\x8E"=>"\xE7\x8C\xBF", + "\x89\x8F"=>"\xE7\xB8\x81", + "\x89\x90"=>"\xE8\x89\xB6", + "\x89\x91"=>"\xE8\x8B\x91", + "\x89\x92"=>"\xE8\x96\x97", + "\x89\x93"=>"\xE9\x81\xA0", + "\x89\x94"=>"\xE9\x89\x9B", + "\x89\x95"=>"\xE9\xB4\x9B", + "\x89\x96"=>"\xE5\xA1\xA9", + "\x89\x97"=>"\xE6\x96\xBC", + "\x89\x98"=>"\xE6\xB1\x9A", + "\x89\x99"=>"\xE7\x94\xA5", + "\x89\x9A"=>"\xE5\x87\xB9", + "\x89\x9B"=>"\xE5\xA4\xAE", + "\x89\x9C"=>"\xE5\xA5\xA5", + "\x89\x9D"=>"\xE5\xBE\x80", + "\x89\x9E"=>"\xE5\xBF\x9C", + "\x89\x9F"=>"\xE6\x8A\xBC", + "\x89\xA0"=>"\xE6\x97\xBA", + "\x89\xA1"=>"\xE6\xA8\xAA", + "\x89\xA2"=>"\xE6\xAC\xA7", + "\x89\xA3"=>"\xE6\xAE\xB4", + "\x89\xA4"=>"\xE7\x8E\x8B", + "\x89\xA5"=>"\xE7\xBF\x81", + "\x89\xA6"=>"\xE8\xA5\x96", + "\x89\xA7"=>"\xE9\xB4\xAC", + "\x89\xA8"=>"\xE9\xB4\x8E", + "\x89\xA9"=>"\xE9\xBB\x84", + "\x89\xAA"=>"\xE5\xB2\xA1", + "\x89\xAB"=>"\xE6\xB2\x96", + "\x89\xAC"=>"\xE8\x8D\xBB", + "\x89\xAD"=>"\xE5\x84\x84", + "\x89\xAE"=>"\xE5\xB1\x8B", + "\x89\xAF"=>"\xE6\x86\xB6", + "\x89\xB0"=>"\xE8\x87\x86", + "\x89\xB1"=>"\xE6\xA1\xB6", + "\x89\xB2"=>"\xE7\x89\xA1", + "\x89\xB3"=>"\xE4\xB9\x99", + "\x89\xB4"=>"\xE4\xBF\xBA", + "\x89\xB5"=>"\xE5\x8D\xB8", + "\x89\xB6"=>"\xE6\x81\xA9", + "\x89\xB7"=>"\xE6\xB8\xA9", + "\x89\xB8"=>"\xE7\xA9\x8F", + "\x89\xB9"=>"\xE9\x9F\xB3", + "\x89\xBA"=>"\xE4\xB8\x8B", + "\x89\xBB"=>"\xE5\x8C\x96", + "\x89\xBC"=>"\xE4\xBB\xAE", + "\x89\xBD"=>"\xE4\xBD\x95", + "\x89\xBE"=>"\xE4\xBC\xBD", + "\x89\xBF"=>"\xE4\xBE\xA1", + "\x89\xC0"=>"\xE4\xBD\xB3", + "\x89\xC1"=>"\xE5\x8A\xA0", + "\x89\xC2"=>"\xE5\x8F\xAF", + "\x89\xC3"=>"\xE5\x98\x89", + "\x89\xC4"=>"\xE5\xA4\x8F", + "\x89\xC5"=>"\xE5\xAB\x81", + "\x89\xC6"=>"\xE5\xAE\xB6", + "\x89\xC7"=>"\xE5\xAF\xA1", + "\x89\xC8"=>"\xE7\xA7\x91", + "\x89\xC9"=>"\xE6\x9A\x87", + "\x89\xCA"=>"\xE6\x9E\x9C", + "\x89\xCB"=>"\xE6\x9E\xB6", + "\x89\xCC"=>"\xE6\xAD\x8C", + "\x89\xCD"=>"\xE6\xB2\xB3", + "\x89\xCE"=>"\xE7\x81\xAB", + "\x89\xCF"=>"\xE7\x8F\x82", + "\x89\xD0"=>"\xE7\xA6\x8D", + "\x89\xD1"=>"\xE7\xA6\xBE", + "\x89\xD2"=>"\xE7\xA8\xBC", + "\x89\xD3"=>"\xE7\xAE\x87", + "\x89\xD4"=>"\xE8\x8A\xB1", + "\x89\xD5"=>"\xE8\x8B\x9B", + "\x89\xD6"=>"\xE8\x8C\x84", + "\x89\xD7"=>"\xE8\x8D\xB7", + "\x89\xD8"=>"\xE8\x8F\xAF", + "\x89\xD9"=>"\xE8\x8F\x93", + "\x89\xDA"=>"\xE8\x9D\xA6", + "\x89\xDB"=>"\xE8\xAA\xB2", + "\x89\xDC"=>"\xE5\x98\xA9", + "\x89\xDD"=>"\xE8\xB2\xA8", + "\x89\xDE"=>"\xE8\xBF\xA6", + "\x89\xDF"=>"\xE9\x81\x8E", + "\x89\xE0"=>"\xE9\x9C\x9E", + "\x89\xE1"=>"\xE8\x9A\x8A", + "\x89\xE2"=>"\xE4\xBF\x84", + "\x89\xE3"=>"\xE5\xB3\xA8", + "\x89\xE4"=>"\xE6\x88\x91", + "\x89\xE5"=>"\xE7\x89\x99", + "\x89\xE6"=>"\xE7\x94\xBB", + "\x89\xE7"=>"\xE8\x87\xA5", + "\x89\xE8"=>"\xE8\x8A\xBD", + "\x89\xE9"=>"\xE8\x9B\xBE", + "\x89\xEA"=>"\xE8\xB3\x80", + "\x89\xEB"=>"\xE9\x9B\x85", + "\x89\xEC"=>"\xE9\xA4\x93", + "\x89\xED"=>"\xE9\xA7\x95", + "\x89\xEE"=>"\xE4\xBB\x8B", + "\x89\xEF"=>"\xE4\xBC\x9A", + "\x89\xF0"=>"\xE8\xA7\xA3", + "\x89\xF1"=>"\xE5\x9B\x9E", + "\x89\xF2"=>"\xE5\xA1\x8A", + "\x89\xF3"=>"\xE5\xA3\x8A", + "\x89\xF4"=>"\xE5\xBB\xBB", + "\x89\xF5"=>"\xE5\xBF\xAB", + "\x89\xF6"=>"\xE6\x80\xAA", + "\x89\xF7"=>"\xE6\x82\x94", + "\x89\xF8"=>"\xE6\x81\xA2", + "\x89\xF9"=>"\xE6\x87\x90", + "\x89\xFA"=>"\xE6\x88\x92", + "\x89\xFB"=>"\xE6\x8B\x90", + "\x89\xFC"=>"\xE6\x94\xB9", + "\x8A\x40"=>"\xE9\xAD\x81", + "\x8A\x41"=>"\xE6\x99\xA6", + "\x8A\x42"=>"\xE6\xA2\xB0", + "\x8A\x43"=>"\xE6\xB5\xB7", + "\x8A\x44"=>"\xE7\x81\xB0", + "\x8A\x45"=>"\xE7\x95\x8C", + "\x8A\x46"=>"\xE7\x9A\x86", + "\x8A\x47"=>"\xE7\xB5\xB5", + "\x8A\x48"=>"\xE8\x8A\xA5", + "\x8A\x49"=>"\xE8\x9F\xB9", + "\x8A\x4A"=>"\xE9\x96\x8B", + "\x8A\x4B"=>"\xE9\x9A\x8E", + "\x8A\x4C"=>"\xE8\xB2\x9D", + "\x8A\x4D"=>"\xE5\x87\xB1", + "\x8A\x4E"=>"\xE5\x8A\xBE", + "\x8A\x4F"=>"\xE5\xA4\x96", + "\x8A\x50"=>"\xE5\x92\xB3", + "\x8A\x51"=>"\xE5\xAE\xB3", + "\x8A\x52"=>"\xE5\xB4\x96", + "\x8A\x53"=>"\xE6\x85\xA8", + "\x8A\x54"=>"\xE6\xA6\x82", + "\x8A\x55"=>"\xE6\xB6\xAF", + "\x8A\x56"=>"\xE7\xA2\x8D", + "\x8A\x57"=>"\xE8\x93\x8B", + "\x8A\x58"=>"\xE8\xA1\x97", + "\x8A\x59"=>"\xE8\xA9\xB2", + "\x8A\x5A"=>"\xE9\x8E\xA7", + "\x8A\x5B"=>"\xE9\xAA\xB8", + "\x8A\x5C"=>"\xE6\xB5\xAC", + "\x8A\x5D"=>"\xE9\xA6\xA8", + "\x8A\x5E"=>"\xE8\x9B\x99", + "\x8A\x5F"=>"\xE5\x9E\xA3", + "\x8A\x60"=>"\xE6\x9F\xBF", + "\x8A\x61"=>"\xE8\x9B\x8E", + "\x8A\x62"=>"\xE9\x88\x8E", + "\x8A\x63"=>"\xE5\x8A\x83", + "\x8A\x64"=>"\xE5\x9A\x87", + "\x8A\x65"=>"\xE5\x90\x84", + "\x8A\x66"=>"\xE5\xBB\x93", + "\x8A\x67"=>"\xE6\x8B\xA1", + "\x8A\x68"=>"\xE6\x92\xB9", + "\x8A\x69"=>"\xE6\xA0\xBC", + "\x8A\x6A"=>"\xE6\xA0\xB8", + "\x8A\x6B"=>"\xE6\xAE\xBB", + "\x8A\x6C"=>"\xE7\x8D\xB2", + "\x8A\x6D"=>"\xE7\xA2\xBA", + "\x8A\x6E"=>"\xE7\xA9\xAB", + "\x8A\x6F"=>"\xE8\xA6\x9A", + "\x8A\x70"=>"\xE8\xA7\x92", + "\x8A\x71"=>"\xE8\xB5\xAB", + "\x8A\x72"=>"\xE8\xBC\x83", + "\x8A\x73"=>"\xE9\x83\xAD", + "\x8A\x74"=>"\xE9\x96\xA3", + "\x8A\x75"=>"\xE9\x9A\x94", + "\x8A\x76"=>"\xE9\x9D\xA9", + "\x8A\x77"=>"\xE5\xAD\xA6", + "\x8A\x78"=>"\xE5\xB2\xB3", + "\x8A\x79"=>"\xE6\xA5\xBD", + "\x8A\x7A"=>"\xE9\xA1\x8D", + "\x8A\x7B"=>"\xE9\xA1\x8E", + "\x8A\x7C"=>"\xE6\x8E\x9B", + "\x8A\x7D"=>"\xE7\xAC\xA0", + "\x8A\x7E"=>"\xE6\xA8\xAB", + "\x8A\x80"=>"\xE6\xA9\xBF", + "\x8A\x81"=>"\xE6\xA2\xB6", + "\x8A\x82"=>"\xE9\xB0\x8D", + "\x8A\x83"=>"\xE6\xBD\x9F", + "\x8A\x84"=>"\xE5\x89\xB2", + "\x8A\x85"=>"\xE5\x96\x9D", + "\x8A\x86"=>"\xE6\x81\xB0", + "\x8A\x87"=>"\xE6\x8B\xAC", + "\x8A\x88"=>"\xE6\xB4\xBB", + "\x8A\x89"=>"\xE6\xB8\x87", + "\x8A\x8A"=>"\xE6\xBB\x91", + "\x8A\x8B"=>"\xE8\x91\x9B", + "\x8A\x8C"=>"\xE8\xA4\x90", + "\x8A\x8D"=>"\xE8\xBD\x84", + "\x8A\x8E"=>"\xE4\xB8\x94", + "\x8A\x8F"=>"\xE9\xB0\xB9", + "\x8A\x90"=>"\xE5\x8F\xB6", + "\x8A\x91"=>"\xE6\xA4\x9B", + "\x8A\x92"=>"\xE6\xA8\xBA", + "\x8A\x93"=>"\xE9\x9E\x84", + "\x8A\x94"=>"\xE6\xA0\xAA", + "\x8A\x95"=>"\xE5\x85\x9C", + "\x8A\x96"=>"\xE7\xAB\x83", + "\x8A\x97"=>"\xE8\x92\xB2", + "\x8A\x98"=>"\xE9\x87\x9C", + "\x8A\x99"=>"\xE9\x8E\x8C", + "\x8A\x9A"=>"\xE5\x99\x9B", + "\x8A\x9B"=>"\xE9\xB4\xA8", + "\x8A\x9C"=>"\xE6\xA0\xA2", + "\x8A\x9D"=>"\xE8\x8C\x85", + "\x8A\x9E"=>"\xE8\x90\xB1", + "\x8A\x9F"=>"\xE7\xB2\xA5", + "\x8A\xA0"=>"\xE5\x88\x88", + "\x8A\xA1"=>"\xE8\x8B\x85", + "\x8A\xA2"=>"\xE7\x93\xA6", + "\x8A\xA3"=>"\xE4\xB9\xBE", + "\x8A\xA4"=>"\xE4\xBE\x83", + "\x8A\xA5"=>"\xE5\x86\xA0", + "\x8A\xA6"=>"\xE5\xAF\x92", + "\x8A\xA7"=>"\xE5\x88\x8A", + "\x8A\xA8"=>"\xE5\x8B\x98", + "\x8A\xA9"=>"\xE5\x8B\xA7", + "\x8A\xAA"=>"\xE5\xB7\xBB", + "\x8A\xAB"=>"\xE5\x96\x9A", + "\x8A\xAC"=>"\xE5\xA0\xAA", + "\x8A\xAD"=>"\xE5\xA7\xA6", + "\x8A\xAE"=>"\xE5\xAE\x8C", + "\x8A\xAF"=>"\xE5\xAE\x98", + "\x8A\xB0"=>"\xE5\xAF\x9B", + "\x8A\xB1"=>"\xE5\xB9\xB2", + "\x8A\xB2"=>"\xE5\xB9\xB9", + "\x8A\xB3"=>"\xE6\x82\xA3", + "\x8A\xB4"=>"\xE6\x84\x9F", + "\x8A\xB5"=>"\xE6\x85\xA3", + "\x8A\xB6"=>"\xE6\x86\xBE", + "\x8A\xB7"=>"\xE6\x8F\x9B", + "\x8A\xB8"=>"\xE6\x95\xA2", + "\x8A\xB9"=>"\xE6\x9F\x91", + "\x8A\xBA"=>"\xE6\xA1\x93", + "\x8A\xBB"=>"\xE6\xA3\xBA", + "\x8A\xBC"=>"\xE6\xAC\xBE", + "\x8A\xBD"=>"\xE6\xAD\x93", + "\x8A\xBE"=>"\xE6\xB1\x97", + "\x8A\xBF"=>"\xE6\xBC\xA2", + "\x8A\xC0"=>"\xE6\xBE\x97", + "\x8A\xC1"=>"\xE6\xBD\x85", + "\x8A\xC2"=>"\xE7\x92\xB0", + "\x8A\xC3"=>"\xE7\x94\x98", + "\x8A\xC4"=>"\xE7\x9B\xA3", + "\x8A\xC5"=>"\xE7\x9C\x8B", + "\x8A\xC6"=>"\xE7\xAB\xBF", + "\x8A\xC7"=>"\xE7\xAE\xA1", + "\x8A\xC8"=>"\xE7\xB0\xA1", + "\x8A\xC9"=>"\xE7\xB7\xA9", + "\x8A\xCA"=>"\xE7\xBC\xB6", + "\x8A\xCB"=>"\xE7\xBF\xB0", + "\x8A\xCC"=>"\xE8\x82\x9D", + "\x8A\xCD"=>"\xE8\x89\xA6", + "\x8A\xCE"=>"\xE8\x8E\x9E", + "\x8A\xCF"=>"\xE8\xA6\xB3", + "\x8A\xD0"=>"\xE8\xAB\x8C", + "\x8A\xD1"=>"\xE8\xB2\xAB", + "\x8A\xD2"=>"\xE9\x82\x84", + "\x8A\xD3"=>"\xE9\x91\x91", + "\x8A\xD4"=>"\xE9\x96\x93", + "\x8A\xD5"=>"\xE9\x96\x91", + "\x8A\xD6"=>"\xE9\x96\xA2", + "\x8A\xD7"=>"\xE9\x99\xA5", + "\x8A\xD8"=>"\xE9\x9F\x93", + "\x8A\xD9"=>"\xE9\xA4\xA8", + "\x8A\xDA"=>"\xE8\x88\x98", + "\x8A\xDB"=>"\xE4\xB8\xB8", + "\x8A\xDC"=>"\xE5\x90\xAB", + "\x8A\xDD"=>"\xE5\xB2\xB8", + "\x8A\xDE"=>"\xE5\xB7\x8C", + "\x8A\xDF"=>"\xE7\x8E\xA9", + "\x8A\xE0"=>"\xE7\x99\x8C", + "\x8A\xE1"=>"\xE7\x9C\xBC", + "\x8A\xE2"=>"\xE5\xB2\xA9", + "\x8A\xE3"=>"\xE7\xBF\xAB", + "\x8A\xE4"=>"\xE8\xB4\x8B", + "\x8A\xE5"=>"\xE9\x9B\x81", + "\x8A\xE6"=>"\xE9\xA0\x91", + "\x8A\xE7"=>"\xE9\xA1\x94", + "\x8A\xE8"=>"\xE9\xA1\x98", + "\x8A\xE9"=>"\xE4\xBC\x81", + "\x8A\xEA"=>"\xE4\xBC\x8E", + "\x8A\xEB"=>"\xE5\x8D\xB1", + "\x8A\xEC"=>"\xE5\x96\x9C", + "\x8A\xED"=>"\xE5\x99\xA8", + "\x8A\xEE"=>"\xE5\x9F\xBA", + "\x8A\xEF"=>"\xE5\xA5\x87", + "\x8A\xF0"=>"\xE5\xAC\x89", + "\x8A\xF1"=>"\xE5\xAF\x84", + "\x8A\xF2"=>"\xE5\xB2\x90", + "\x8A\xF3"=>"\xE5\xB8\x8C", + "\x8A\xF4"=>"\xE5\xB9\xBE", + "\x8A\xF5"=>"\xE5\xBF\x8C", + "\x8A\xF6"=>"\xE6\x8F\xAE", + "\x8A\xF7"=>"\xE6\x9C\xBA", + "\x8A\xF8"=>"\xE6\x97\x97", + "\x8A\xF9"=>"\xE6\x97\xA2", + "\x8A\xFA"=>"\xE6\x9C\x9F", + "\x8A\xFB"=>"\xE6\xA3\x8B", + "\x8A\xFC"=>"\xE6\xA3\x84", + "\x8B\x40"=>"\xE6\xA9\x9F", + "\x8B\x41"=>"\xE5\xB8\xB0", + "\x8B\x42"=>"\xE6\xAF\x85", + "\x8B\x43"=>"\xE6\xB0\x97", + "\x8B\x44"=>"\xE6\xB1\xBD", + "\x8B\x45"=>"\xE7\x95\xBF", + "\x8B\x46"=>"\xE7\xA5\x88", + "\x8B\x47"=>"\xE5\xAD\xA3", + "\x8B\x48"=>"\xE7\xA8\x80", + "\x8B\x49"=>"\xE7\xB4\x80", + "\x8B\x4A"=>"\xE5\xBE\xBD", + "\x8B\x4B"=>"\xE8\xA6\x8F", + "\x8B\x4C"=>"\xE8\xA8\x98", + "\x8B\x4D"=>"\xE8\xB2\xB4", + "\x8B\x4E"=>"\xE8\xB5\xB7", + "\x8B\x4F"=>"\xE8\xBB\x8C", + "\x8B\x50"=>"\xE8\xBC\x9D", + "\x8B\x51"=>"\xE9\xA3\xA2", + "\x8B\x52"=>"\xE9\xA8\x8E", + "\x8B\x53"=>"\xE9\xAC\xBC", + "\x8B\x54"=>"\xE4\xBA\x80", + "\x8B\x55"=>"\xE5\x81\xBD", + "\x8B\x56"=>"\xE5\x84\x80", + "\x8B\x57"=>"\xE5\xA6\x93", + "\x8B\x58"=>"\xE5\xAE\x9C", + "\x8B\x59"=>"\xE6\x88\xAF", + "\x8B\x5A"=>"\xE6\x8A\x80", + "\x8B\x5B"=>"\xE6\x93\xAC", + "\x8B\x5C"=>"\xE6\xAC\xBA", + "\x8B\x5D"=>"\xE7\x8A\xA0", + "\x8B\x5E"=>"\xE7\x96\x91", + "\x8B\x5F"=>"\xE7\xA5\x87", + "\x8B\x60"=>"\xE7\xBE\xA9", + "\x8B\x61"=>"\xE8\x9F\xBB", + "\x8B\x62"=>"\xE8\xAA\xBC", + "\x8B\x63"=>"\xE8\xAD\xB0", + "\x8B\x64"=>"\xE6\x8E\xAC", + "\x8B\x65"=>"\xE8\x8F\x8A", + "\x8B\x66"=>"\xE9\x9E\xA0", + "\x8B\x67"=>"\xE5\x90\x89", + "\x8B\x68"=>"\xE5\x90\x83", + "\x8B\x69"=>"\xE5\x96\xAB", + "\x8B\x6A"=>"\xE6\xA1\x94", + "\x8B\x6B"=>"\xE6\xA9\x98", + "\x8B\x6C"=>"\xE8\xA9\xB0", + "\x8B\x6D"=>"\xE7\xA0\xA7", + "\x8B\x6E"=>"\xE6\x9D\xB5", + "\x8B\x6F"=>"\xE9\xBB\x8D", + "\x8B\x70"=>"\xE5\x8D\xB4", + "\x8B\x71"=>"\xE5\xAE\xA2", + "\x8B\x72"=>"\xE8\x84\x9A", + "\x8B\x73"=>"\xE8\x99\x90", + "\x8B\x74"=>"\xE9\x80\x86", + "\x8B\x75"=>"\xE4\xB8\x98", + "\x8B\x76"=>"\xE4\xB9\x85", + "\x8B\x77"=>"\xE4\xBB\x87", + "\x8B\x78"=>"\xE4\xBC\x91", + "\x8B\x79"=>"\xE5\x8F\x8A", + "\x8B\x7A"=>"\xE5\x90\xB8", + "\x8B\x7B"=>"\xE5\xAE\xAE", + "\x8B\x7C"=>"\xE5\xBC\x93", + "\x8B\x7D"=>"\xE6\x80\xA5", + "\x8B\x7E"=>"\xE6\x95\x91", + "\x8B\x80"=>"\xE6\x9C\xBD", + "\x8B\x81"=>"\xE6\xB1\x82", + "\x8B\x82"=>"\xE6\xB1\xB2", + "\x8B\x83"=>"\xE6\xB3\xA3", + "\x8B\x84"=>"\xE7\x81\xB8", + "\x8B\x85"=>"\xE7\x90\x83", + "\x8B\x86"=>"\xE7\xA9\xB6", + "\x8B\x87"=>"\xE7\xAA\xAE", + "\x8B\x88"=>"\xE7\xAC\x88", + "\x8B\x89"=>"\xE7\xB4\x9A", + "\x8B\x8A"=>"\xE7\xB3\xBE", + "\x8B\x8B"=>"\xE7\xB5\xA6", + "\x8B\x8C"=>"\xE6\x97\xA7", + "\x8B\x8D"=>"\xE7\x89\x9B", + "\x8B\x8E"=>"\xE5\x8E\xBB", + "\x8B\x8F"=>"\xE5\xB1\x85", + "\x8B\x90"=>"\xE5\xB7\xA8", + "\x8B\x91"=>"\xE6\x8B\x92", + "\x8B\x92"=>"\xE6\x8B\xA0", + "\x8B\x93"=>"\xE6\x8C\x99", + "\x8B\x94"=>"\xE6\xB8\xA0", + "\x8B\x95"=>"\xE8\x99\x9A", + "\x8B\x96"=>"\xE8\xA8\xB1", + "\x8B\x97"=>"\xE8\xB7\x9D", + "\x8B\x98"=>"\xE9\x8B\xB8", + "\x8B\x99"=>"\xE6\xBC\x81", + "\x8B\x9A"=>"\xE7\xA6\xA6", + "\x8B\x9B"=>"\xE9\xAD\x9A", + "\x8B\x9C"=>"\xE4\xBA\xA8", + "\x8B\x9D"=>"\xE4\xBA\xAB", + "\x8B\x9E"=>"\xE4\xBA\xAC", + "\x8B\x9F"=>"\xE4\xBE\x9B", + "\x8B\xA0"=>"\xE4\xBE\xA0", + "\x8B\xA1"=>"\xE5\x83\x91", + "\x8B\xA2"=>"\xE5\x85\x87", + "\x8B\xA3"=>"\xE7\xAB\xB6", + "\x8B\xA4"=>"\xE5\x85\xB1", + "\x8B\xA5"=>"\xE5\x87\xB6", + "\x8B\xA6"=>"\xE5\x8D\x94", + "\x8B\xA7"=>"\xE5\x8C\xA1", + "\x8B\xA8"=>"\xE5\x8D\xBF", + "\x8B\xA9"=>"\xE5\x8F\xAB", + "\x8B\xAA"=>"\xE5\x96\xAC", + "\x8B\xAB"=>"\xE5\xA2\x83", + "\x8B\xAC"=>"\xE5\xB3\xA1", + "\x8B\xAD"=>"\xE5\xBC\xB7", + "\x8B\xAE"=>"\xE5\xBD\x8A", + "\x8B\xAF"=>"\xE6\x80\xAF", + "\x8B\xB0"=>"\xE6\x81\x90", + "\x8B\xB1"=>"\xE6\x81\xAD", + "\x8B\xB2"=>"\xE6\x8C\x9F", + "\x8B\xB3"=>"\xE6\x95\x99", + "\x8B\xB4"=>"\xE6\xA9\x8B", + "\x8B\xB5"=>"\xE6\xB3\x81", + "\x8B\xB6"=>"\xE7\x8B\x82", + "\x8B\xB7"=>"\xE7\x8B\xAD", + "\x8B\xB8"=>"\xE7\x9F\xAF", + "\x8B\xB9"=>"\xE8\x83\xB8", + "\x8B\xBA"=>"\xE8\x84\x85", + "\x8B\xBB"=>"\xE8\x88\x88", + "\x8B\xBC"=>"\xE8\x95\x8E", + "\x8B\xBD"=>"\xE9\x83\xB7", + "\x8B\xBE"=>"\xE9\x8F\xA1", + "\x8B\xBF"=>"\xE9\x9F\xBF", + "\x8B\xC0"=>"\xE9\xA5\x97", + "\x8B\xC1"=>"\xE9\xA9\x9A", + "\x8B\xC2"=>"\xE4\xBB\xB0", + "\x8B\xC3"=>"\xE5\x87\x9D", + "\x8B\xC4"=>"\xE5\xB0\xAD", + "\x8B\xC5"=>"\xE6\x9A\x81", + "\x8B\xC6"=>"\xE6\xA5\xAD", + "\x8B\xC7"=>"\xE5\xB1\x80", + "\x8B\xC8"=>"\xE6\x9B\xB2", + "\x8B\xC9"=>"\xE6\xA5\xB5", + "\x8B\xCA"=>"\xE7\x8E\x89", + "\x8B\xCB"=>"\xE6\xA1\x90", + "\x8B\xCC"=>"\xE7\xB2\x81", + "\x8B\xCD"=>"\xE5\x83\x85", + "\x8B\xCE"=>"\xE5\x8B\xA4", + "\x8B\xCF"=>"\xE5\x9D\x87", + "\x8B\xD0"=>"\xE5\xB7\xBE", + "\x8B\xD1"=>"\xE9\x8C\xA6", + "\x8B\xD2"=>"\xE6\x96\xA4", + "\x8B\xD3"=>"\xE6\xAC\xA3", + "\x8B\xD4"=>"\xE6\xAC\xBD", + "\x8B\xD5"=>"\xE7\x90\xB4", + "\x8B\xD6"=>"\xE7\xA6\x81", + "\x8B\xD7"=>"\xE7\xA6\xBD", + "\x8B\xD8"=>"\xE7\xAD\x8B", + "\x8B\xD9"=>"\xE7\xB7\x8A", + "\x8B\xDA"=>"\xE8\x8A\xB9", + "\x8B\xDB"=>"\xE8\x8F\x8C", + "\x8B\xDC"=>"\xE8\xA1\xBF", + "\x8B\xDD"=>"\xE8\xA5\x9F", + "\x8B\xDE"=>"\xE8\xAC\xB9", + "\x8B\xDF"=>"\xE8\xBF\x91", + "\x8B\xE0"=>"\xE9\x87\x91", + "\x8B\xE1"=>"\xE5\x90\x9F", + "\x8B\xE2"=>"\xE9\x8A\x80", + "\x8B\xE3"=>"\xE4\xB9\x9D", + "\x8B\xE4"=>"\xE5\x80\xB6", + "\x8B\xE5"=>"\xE5\x8F\xA5", + "\x8B\xE6"=>"\xE5\x8C\xBA", + "\x8B\xE7"=>"\xE7\x8B\x97", + "\x8B\xE8"=>"\xE7\x8E\x96", + "\x8B\xE9"=>"\xE7\x9F\xA9", + "\x8B\xEA"=>"\xE8\x8B\xA6", + "\x8B\xEB"=>"\xE8\xBA\xAF", + "\x8B\xEC"=>"\xE9\xA7\x86", + "\x8B\xED"=>"\xE9\xA7\x88", + "\x8B\xEE"=>"\xE9\xA7\x92", + "\x8B\xEF"=>"\xE5\x85\xB7", + "\x8B\xF0"=>"\xE6\x84\x9A", + "\x8B\xF1"=>"\xE8\x99\x9E", + "\x8B\xF2"=>"\xE5\x96\xB0", + "\x8B\xF3"=>"\xE7\xA9\xBA", + "\x8B\xF4"=>"\xE5\x81\xB6", + "\x8B\xF5"=>"\xE5\xAF\x93", + "\x8B\xF6"=>"\xE9\x81\x87", + "\x8B\xF7"=>"\xE9\x9A\x85", + "\x8B\xF8"=>"\xE4\xB8\xB2", + "\x8B\xF9"=>"\xE6\xAB\x9B", + "\x8B\xFA"=>"\xE9\x87\xA7", + "\x8B\xFB"=>"\xE5\xB1\x91", + "\x8B\xFC"=>"\xE5\xB1\x88", + "\x8C\x40"=>"\xE6\x8E\x98", + "\x8C\x41"=>"\xE7\xAA\x9F", + "\x8C\x42"=>"\xE6\xB2\x93", + "\x8C\x43"=>"\xE9\x9D\xB4", + "\x8C\x44"=>"\xE8\xBD\xA1", + "\x8C\x45"=>"\xE7\xAA\xAA", + "\x8C\x46"=>"\xE7\x86\x8A", + "\x8C\x47"=>"\xE9\x9A\x88", + "\x8C\x48"=>"\xE7\xB2\x82", + "\x8C\x49"=>"\xE6\xA0\x97", + "\x8C\x4A"=>"\xE7\xB9\xB0", + "\x8C\x4B"=>"\xE6\xA1\x91", + "\x8C\x4C"=>"\xE9\x8D\xAC", + "\x8C\x4D"=>"\xE5\x8B\xB2", + "\x8C\x4E"=>"\xE5\x90\x9B", + "\x8C\x4F"=>"\xE8\x96\xAB", + "\x8C\x50"=>"\xE8\xA8\x93", + "\x8C\x51"=>"\xE7\xBE\xA4", + "\x8C\x52"=>"\xE8\xBB\x8D", + "\x8C\x53"=>"\xE9\x83\xA1", + "\x8C\x54"=>"\xE5\x8D\xA6", + "\x8C\x55"=>"\xE8\xA2\x88", + "\x8C\x56"=>"\xE7\xA5\x81", + "\x8C\x57"=>"\xE4\xBF\x82", + "\x8C\x58"=>"\xE5\x82\xBE", + "\x8C\x59"=>"\xE5\x88\x91", + "\x8C\x5A"=>"\xE5\x85\x84", + "\x8C\x5B"=>"\xE5\x95\x93", + "\x8C\x5C"=>"\xE5\x9C\xAD", + "\x8C\x5D"=>"\xE7\x8F\xAA", + "\x8C\x5E"=>"\xE5\x9E\x8B", + "\x8C\x5F"=>"\xE5\xA5\x91", + "\x8C\x60"=>"\xE5\xBD\xA2", + "\x8C\x61"=>"\xE5\xBE\x84", + "\x8C\x62"=>"\xE6\x81\xB5", + "\x8C\x63"=>"\xE6\x85\xB6", + "\x8C\x64"=>"\xE6\x85\xA7", + "\x8C\x65"=>"\xE6\x86\xA9", + "\x8C\x66"=>"\xE6\x8E\xB2", + "\x8C\x67"=>"\xE6\x90\xBA", + "\x8C\x68"=>"\xE6\x95\xAC", + "\x8C\x69"=>"\xE6\x99\xAF", + "\x8C\x6A"=>"\xE6\xA1\x82", + "\x8C\x6B"=>"\xE6\xB8\x93", + "\x8C\x6C"=>"\xE7\x95\xA6", + "\x8C\x6D"=>"\xE7\xA8\xBD", + "\x8C\x6E"=>"\xE7\xB3\xBB", + "\x8C\x6F"=>"\xE7\xB5\x8C", + "\x8C\x70"=>"\xE7\xB6\x99", + "\x8C\x71"=>"\xE7\xB9\x8B", + "\x8C\x72"=>"\xE7\xBD\xAB", + "\x8C\x73"=>"\xE8\x8C\x8E", + "\x8C\x74"=>"\xE8\x8D\x8A", + "\x8C\x75"=>"\xE8\x9B\x8D", + "\x8C\x76"=>"\xE8\xA8\x88", + "\x8C\x77"=>"\xE8\xA9\xA3", + "\x8C\x78"=>"\xE8\xAD\xA6", + "\x8C\x79"=>"\xE8\xBB\xBD", + "\x8C\x7A"=>"\xE9\xA0\x9A", + "\x8C\x7B"=>"\xE9\xB6\x8F", + "\x8C\x7C"=>"\xE8\x8A\xB8", + "\x8C\x7D"=>"\xE8\xBF\x8E", + "\x8C\x7E"=>"\xE9\xAF\xA8", + "\x8C\x80"=>"\xE5\x8A\x87", + "\x8C\x81"=>"\xE6\x88\x9F", + "\x8C\x82"=>"\xE6\x92\x83", + "\x8C\x83"=>"\xE6\xBF\x80", + "\x8C\x84"=>"\xE9\x9A\x99", + "\x8C\x85"=>"\xE6\xA1\x81", + "\x8C\x86"=>"\xE5\x82\x91", + "\x8C\x87"=>"\xE6\xAC\xA0", + "\x8C\x88"=>"\xE6\xB1\xBA", + "\x8C\x89"=>"\xE6\xBD\x94", + "\x8C\x8A"=>"\xE7\xA9\xB4", + "\x8C\x8B"=>"\xE7\xB5\x90", + "\x8C\x8C"=>"\xE8\xA1\x80", + "\x8C\x8D"=>"\xE8\xA8\xA3", + "\x8C\x8E"=>"\xE6\x9C\x88", + "\x8C\x8F"=>"\xE4\xBB\xB6", + "\x8C\x90"=>"\xE5\x80\xB9", + "\x8C\x91"=>"\xE5\x80\xA6", + "\x8C\x92"=>"\xE5\x81\xA5", + "\x8C\x93"=>"\xE5\x85\xBC", + "\x8C\x94"=>"\xE5\x88\xB8", + "\x8C\x95"=>"\xE5\x89\xA3", + "\x8C\x96"=>"\xE5\x96\xA7", + "\x8C\x97"=>"\xE5\x9C\x8F", + "\x8C\x98"=>"\xE5\xA0\x85", + "\x8C\x99"=>"\xE5\xAB\x8C", + "\x8C\x9A"=>"\xE5\xBB\xBA", + "\x8C\x9B"=>"\xE6\x86\xB2", + "\x8C\x9C"=>"\xE6\x87\xB8", + "\x8C\x9D"=>"\xE6\x8B\xB3", + "\x8C\x9E"=>"\xE6\x8D\xB2", + "\x8C\x9F"=>"\xE6\xA4\x9C", + "\x8C\xA0"=>"\xE6\xA8\xA9", + "\x8C\xA1"=>"\xE7\x89\xBD", + "\x8C\xA2"=>"\xE7\x8A\xAC", + "\x8C\xA3"=>"\xE7\x8C\xAE", + "\x8C\xA4"=>"\xE7\xA0\x94", + "\x8C\xA5"=>"\xE7\xA1\xAF", + "\x8C\xA6"=>"\xE7\xB5\xB9", + "\x8C\xA7"=>"\xE7\x9C\x8C", + "\x8C\xA8"=>"\xE8\x82\xA9", + "\x8C\xA9"=>"\xE8\xA6\x8B", + "\x8C\xAA"=>"\xE8\xAC\x99", + "\x8C\xAB"=>"\xE8\xB3\xA2", + "\x8C\xAC"=>"\xE8\xBB\x92", + "\x8C\xAD"=>"\xE9\x81\xA3", + "\x8C\xAE"=>"\xE9\x8D\xB5", + "\x8C\xAF"=>"\xE9\x99\xBA", + "\x8C\xB0"=>"\xE9\xA1\x95", + "\x8C\xB1"=>"\xE9\xA8\x93", + "\x8C\xB2"=>"\xE9\xB9\xB8", + "\x8C\xB3"=>"\xE5\x85\x83", + "\x8C\xB4"=>"\xE5\x8E\x9F", + "\x8C\xB5"=>"\xE5\x8E\xB3", + "\x8C\xB6"=>"\xE5\xB9\xBB", + "\x8C\xB7"=>"\xE5\xBC\xA6", + "\x8C\xB8"=>"\xE6\xB8\x9B", + "\x8C\xB9"=>"\xE6\xBA\x90", + "\x8C\xBA"=>"\xE7\x8E\x84", + "\x8C\xBB"=>"\xE7\x8F\xBE", + "\x8C\xBC"=>"\xE7\xB5\x83", + "\x8C\xBD"=>"\xE8\x88\xB7", + "\x8C\xBE"=>"\xE8\xA8\x80", + "\x8C\xBF"=>"\xE8\xAB\xBA", + "\x8C\xC0"=>"\xE9\x99\x90", + "\x8C\xC1"=>"\xE4\xB9\x8E", + "\x8C\xC2"=>"\xE5\x80\x8B", + "\x8C\xC3"=>"\xE5\x8F\xA4", + "\x8C\xC4"=>"\xE5\x91\xBC", + "\x8C\xC5"=>"\xE5\x9B\xBA", + "\x8C\xC6"=>"\xE5\xA7\x91", + "\x8C\xC7"=>"\xE5\xAD\xA4", + "\x8C\xC8"=>"\xE5\xB7\xB1", + "\x8C\xC9"=>"\xE5\xBA\xAB", + "\x8C\xCA"=>"\xE5\xBC\xA7", + "\x8C\xCB"=>"\xE6\x88\xB8", + "\x8C\xCC"=>"\xE6\x95\x85", + "\x8C\xCD"=>"\xE6\x9E\xAF", + "\x8C\xCE"=>"\xE6\xB9\x96", + "\x8C\xCF"=>"\xE7\x8B\x90", + "\x8C\xD0"=>"\xE7\xB3\x8A", + "\x8C\xD1"=>"\xE8\xA2\xB4", + "\x8C\xD2"=>"\xE8\x82\xA1", + "\x8C\xD3"=>"\xE8\x83\xA1", + "\x8C\xD4"=>"\xE8\x8F\xB0", + "\x8C\xD5"=>"\xE8\x99\x8E", + "\x8C\xD6"=>"\xE8\xAA\x87", + "\x8C\xD7"=>"\xE8\xB7\xA8", + "\x8C\xD8"=>"\xE9\x88\xB7", + "\x8C\xD9"=>"\xE9\x9B\x87", + "\x8C\xDA"=>"\xE9\xA1\xA7", + "\x8C\xDB"=>"\xE9\xBC\x93", + "\x8C\xDC"=>"\xE4\xBA\x94", + "\x8C\xDD"=>"\xE4\xBA\x92", + "\x8C\xDE"=>"\xE4\xBC\x8D", + "\x8C\xDF"=>"\xE5\x8D\x88", + "\x8C\xE0"=>"\xE5\x91\x89", + "\x8C\xE1"=>"\xE5\x90\xBE", + "\x8C\xE2"=>"\xE5\xA8\xAF", + "\x8C\xE3"=>"\xE5\xBE\x8C", + "\x8C\xE4"=>"\xE5\xBE\xA1", + "\x8C\xE5"=>"\xE6\x82\x9F", + "\x8C\xE6"=>"\xE6\xA2\xA7", + "\x8C\xE7"=>"\xE6\xAA\x8E", + "\x8C\xE8"=>"\xE7\x91\x9A", + "\x8C\xE9"=>"\xE7\xA2\x81", + "\x8C\xEA"=>"\xE8\xAA\x9E", + "\x8C\xEB"=>"\xE8\xAA\xA4", + "\x8C\xEC"=>"\xE8\xAD\xB7", + "\x8C\xED"=>"\xE9\x86\x90", + "\x8C\xEE"=>"\xE4\xB9\x9E", + "\x8C\xEF"=>"\xE9\xAF\x89", + "\x8C\xF0"=>"\xE4\xBA\xA4", + "\x8C\xF1"=>"\xE4\xBD\xBC", + "\x8C\xF2"=>"\xE4\xBE\xAF", + "\x8C\xF3"=>"\xE5\x80\x99", + "\x8C\xF4"=>"\xE5\x80\x96", + "\x8C\xF5"=>"\xE5\x85\x89", + "\x8C\xF6"=>"\xE5\x85\xAC", + "\x8C\xF7"=>"\xE5\x8A\x9F", + "\x8C\xF8"=>"\xE5\x8A\xB9", + "\x8C\xF9"=>"\xE5\x8B\xBE", + "\x8C\xFA"=>"\xE5\x8E\x9A", + "\x8C\xFB"=>"\xE5\x8F\xA3", + "\x8C\xFC"=>"\xE5\x90\x91", + "\x8D\x40"=>"\xE5\x90\x8E", + "\x8D\x41"=>"\xE5\x96\x89", + "\x8D\x42"=>"\xE5\x9D\x91", + "\x8D\x43"=>"\xE5\x9E\xA2", + "\x8D\x44"=>"\xE5\xA5\xBD", + "\x8D\x45"=>"\xE5\xAD\x94", + "\x8D\x46"=>"\xE5\xAD\x9D", + "\x8D\x47"=>"\xE5\xAE\x8F", + "\x8D\x48"=>"\xE5\xB7\xA5", + "\x8D\x49"=>"\xE5\xB7\xA7", + "\x8D\x4A"=>"\xE5\xB7\xB7", + "\x8D\x4B"=>"\xE5\xB9\xB8", + "\x8D\x4C"=>"\xE5\xBA\x83", + "\x8D\x4D"=>"\xE5\xBA\x9A", + "\x8D\x4E"=>"\xE5\xBA\xB7", + "\x8D\x4F"=>"\xE5\xBC\x98", + "\x8D\x50"=>"\xE6\x81\x92", + "\x8D\x51"=>"\xE6\x85\x8C", + "\x8D\x52"=>"\xE6\x8A\x97", + "\x8D\x53"=>"\xE6\x8B\x98", + "\x8D\x54"=>"\xE6\x8E\xA7", + "\x8D\x55"=>"\xE6\x94\xBB", + "\x8D\x56"=>"\xE6\x98\x82", + "\x8D\x57"=>"\xE6\x99\x83", + "\x8D\x58"=>"\xE6\x9B\xB4", + "\x8D\x59"=>"\xE6\x9D\xAD", + "\x8D\x5A"=>"\xE6\xA0\xA1", + "\x8D\x5B"=>"\xE6\xA2\x97", + "\x8D\x5C"=>"\xE6\xA7\x8B", + "\x8D\x5D"=>"\xE6\xB1\x9F", + "\x8D\x5E"=>"\xE6\xB4\xAA", + "\x8D\x5F"=>"\xE6\xB5\xA9", + "\x8D\x60"=>"\xE6\xB8\xAF", + "\x8D\x61"=>"\xE6\xBA\x9D", + "\x8D\x62"=>"\xE7\x94\xB2", + "\x8D\x63"=>"\xE7\x9A\x87", + "\x8D\x64"=>"\xE7\xA1\xAC", + "\x8D\x65"=>"\xE7\xA8\xBF", + "\x8D\x66"=>"\xE7\xB3\xA0", + "\x8D\x67"=>"\xE7\xB4\x85", + "\x8D\x68"=>"\xE7\xB4\x98", + "\x8D\x69"=>"\xE7\xB5\x9E", + "\x8D\x6A"=>"\xE7\xB6\xB1", + "\x8D\x6B"=>"\xE8\x80\x95", + "\x8D\x6C"=>"\xE8\x80\x83", + "\x8D\x6D"=>"\xE8\x82\xAF", + "\x8D\x6E"=>"\xE8\x82\xB1", + "\x8D\x6F"=>"\xE8\x85\x94", + "\x8D\x70"=>"\xE8\x86\x8F", + "\x8D\x71"=>"\xE8\x88\xAA", + "\x8D\x72"=>"\xE8\x8D\x92", + "\x8D\x73"=>"\xE8\xA1\x8C", + "\x8D\x74"=>"\xE8\xA1\xA1", + "\x8D\x75"=>"\xE8\xAC\x9B", + "\x8D\x76"=>"\xE8\xB2\xA2", + "\x8D\x77"=>"\xE8\xB3\xBC", + "\x8D\x78"=>"\xE9\x83\x8A", + "\x8D\x79"=>"\xE9\x85\xB5", + "\x8D\x7A"=>"\xE9\x89\xB1", + "\x8D\x7B"=>"\xE7\xA0\xBF", + "\x8D\x7C"=>"\xE9\x8B\xBC", + "\x8D\x7D"=>"\xE9\x96\xA4", + "\x8D\x7E"=>"\xE9\x99\x8D", + "\x8D\x80"=>"\xE9\xA0\x85", + "\x8D\x81"=>"\xE9\xA6\x99", + "\x8D\x82"=>"\xE9\xAB\x98", + "\x8D\x83"=>"\xE9\xB4\xBB", + "\x8D\x84"=>"\xE5\x89\x9B", + "\x8D\x85"=>"\xE5\x8A\xAB", + "\x8D\x86"=>"\xE5\x8F\xB7", + "\x8D\x87"=>"\xE5\x90\x88", + "\x8D\x88"=>"\xE5\xA3\x95", + "\x8D\x89"=>"\xE6\x8B\xB7", + "\x8D\x8A"=>"\xE6\xBF\xA0", + "\x8D\x8B"=>"\xE8\xB1\xAA", + "\x8D\x8C"=>"\xE8\xBD\x9F", + "\x8D\x8D"=>"\xE9\xBA\xB9", + "\x8D\x8E"=>"\xE5\x85\x8B", + "\x8D\x8F"=>"\xE5\x88\xBB", + "\x8D\x90"=>"\xE5\x91\x8A", + "\x8D\x91"=>"\xE5\x9B\xBD", + "\x8D\x92"=>"\xE7\xA9\x80", + "\x8D\x93"=>"\xE9\x85\xB7", + "\x8D\x94"=>"\xE9\xB5\xA0", + "\x8D\x95"=>"\xE9\xBB\x92", + "\x8D\x96"=>"\xE7\x8D\x84", + "\x8D\x97"=>"\xE6\xBC\x89", + "\x8D\x98"=>"\xE8\x85\xB0", + "\x8D\x99"=>"\xE7\x94\x91", + "\x8D\x9A"=>"\xE5\xBF\xBD", + "\x8D\x9B"=>"\xE6\x83\x9A", + "\x8D\x9C"=>"\xE9\xAA\xA8", + "\x8D\x9D"=>"\xE7\x8B\x9B", + "\x8D\x9E"=>"\xE8\xBE\xBC", + "\x8D\x9F"=>"\xE6\xAD\xA4", + "\x8D\xA0"=>"\xE9\xA0\x83", + "\x8D\xA1"=>"\xE4\xBB\x8A", + "\x8D\xA2"=>"\xE5\x9B\xB0", + "\x8D\xA3"=>"\xE5\x9D\xA4", + "\x8D\xA4"=>"\xE5\xA2\xBE", + "\x8D\xA5"=>"\xE5\xA9\x9A", + "\x8D\xA6"=>"\xE6\x81\xA8", + "\x8D\xA7"=>"\xE6\x87\x87", + "\x8D\xA8"=>"\xE6\x98\x8F", + "\x8D\xA9"=>"\xE6\x98\x86", + "\x8D\xAA"=>"\xE6\xA0\xB9", + "\x8D\xAB"=>"\xE6\xA2\xB1", + "\x8D\xAC"=>"\xE6\xB7\xB7", + "\x8D\xAD"=>"\xE7\x97\x95", + "\x8D\xAE"=>"\xE7\xB4\xBA", + "\x8D\xAF"=>"\xE8\x89\xAE", + "\x8D\xB0"=>"\xE9\xAD\x82", + "\x8D\xB1"=>"\xE4\xBA\x9B", + "\x8D\xB2"=>"\xE4\xBD\x90", + "\x8D\xB3"=>"\xE5\x8F\x89", + "\x8D\xB4"=>"\xE5\x94\x86", + "\x8D\xB5"=>"\xE5\xB5\xAF", + "\x8D\xB6"=>"\xE5\xB7\xA6", + "\x8D\xB7"=>"\xE5\xB7\xAE", + "\x8D\xB8"=>"\xE6\x9F\xBB", + "\x8D\xB9"=>"\xE6\xB2\x99", + "\x8D\xBA"=>"\xE7\x91\xB3", + "\x8D\xBB"=>"\xE7\xA0\x82", + "\x8D\xBC"=>"\xE8\xA9\x90", + "\x8D\xBD"=>"\xE9\x8E\x96", + "\x8D\xBE"=>"\xE8\xA3\x9F", + "\x8D\xBF"=>"\xE5\x9D\x90", + "\x8D\xC0"=>"\xE5\xBA\xA7", + "\x8D\xC1"=>"\xE6\x8C\xAB", + "\x8D\xC2"=>"\xE5\x82\xB5", + "\x8D\xC3"=>"\xE5\x82\xAC", + "\x8D\xC4"=>"\xE5\x86\x8D", + "\x8D\xC5"=>"\xE6\x9C\x80", + "\x8D\xC6"=>"\xE5\x93\x89", + "\x8D\xC7"=>"\xE5\xA1\x9E", + "\x8D\xC8"=>"\xE5\xA6\xBB", + "\x8D\xC9"=>"\xE5\xAE\xB0", + "\x8D\xCA"=>"\xE5\xBD\xA9", + "\x8D\xCB"=>"\xE6\x89\x8D", + "\x8D\xCC"=>"\xE6\x8E\xA1", + "\x8D\xCD"=>"\xE6\xA0\xBD", + "\x8D\xCE"=>"\xE6\xAD\xB3", + "\x8D\xCF"=>"\xE6\xB8\x88", + "\x8D\xD0"=>"\xE7\x81\xBD", + "\x8D\xD1"=>"\xE9\x87\x87", + "\x8D\xD2"=>"\xE7\x8A\x80", + "\x8D\xD3"=>"\xE7\xA0\x95", + "\x8D\xD4"=>"\xE7\xA0\xA6", + "\x8D\xD5"=>"\xE7\xA5\xAD", + "\x8D\xD6"=>"\xE6\x96\x8E", + "\x8D\xD7"=>"\xE7\xB4\xB0", + "\x8D\xD8"=>"\xE8\x8F\x9C", + "\x8D\xD9"=>"\xE8\xA3\x81", + "\x8D\xDA"=>"\xE8\xBC\x89", + "\x8D\xDB"=>"\xE9\x9A\x9B", + "\x8D\xDC"=>"\xE5\x89\xA4", + "\x8D\xDD"=>"\xE5\x9C\xA8", + "\x8D\xDE"=>"\xE6\x9D\x90", + "\x8D\xDF"=>"\xE7\xBD\xAA", + "\x8D\xE0"=>"\xE8\xB2\xA1", + "\x8D\xE1"=>"\xE5\x86\xB4", + "\x8D\xE2"=>"\xE5\x9D\x82", + "\x8D\xE3"=>"\xE9\x98\xAA", + "\x8D\xE4"=>"\xE5\xA0\xBA", + "\x8D\xE5"=>"\xE6\xA6\x8A", + "\x8D\xE6"=>"\xE8\x82\xB4", + "\x8D\xE7"=>"\xE5\x92\xB2", + "\x8D\xE8"=>"\xE5\xB4\x8E", + "\x8D\xE9"=>"\xE5\x9F\xBC", + "\x8D\xEA"=>"\xE7\xA2\x95", + "\x8D\xEB"=>"\xE9\xB7\xBA", + "\x8D\xEC"=>"\xE4\xBD\x9C", + "\x8D\xED"=>"\xE5\x89\x8A", + "\x8D\xEE"=>"\xE5\x92\x8B", + "\x8D\xEF"=>"\xE6\x90\xBE", + "\x8D\xF0"=>"\xE6\x98\xA8", + "\x8D\xF1"=>"\xE6\x9C\x94", + "\x8D\xF2"=>"\xE6\x9F\xB5", + "\x8D\xF3"=>"\xE7\xAA\x84", + "\x8D\xF4"=>"\xE7\xAD\x96", + "\x8D\xF5"=>"\xE7\xB4\xA2", + "\x8D\xF6"=>"\xE9\x8C\xAF", + "\x8D\xF7"=>"\xE6\xA1\x9C", + "\x8D\xF8"=>"\xE9\xAE\xAD", + "\x8D\xF9"=>"\xE7\xAC\xB9", + "\x8D\xFA"=>"\xE5\x8C\x99", + "\x8D\xFB"=>"\xE5\x86\x8A", + "\x8D\xFC"=>"\xE5\x88\xB7", + "\x8E\x40"=>"\xE5\xAF\x9F", + "\x8E\x41"=>"\xE6\x8B\xB6", + "\x8E\x42"=>"\xE6\x92\xAE", + "\x8E\x43"=>"\xE6\x93\xA6", + "\x8E\x44"=>"\xE6\x9C\xAD", + "\x8E\x45"=>"\xE6\xAE\xBA", + "\x8E\x46"=>"\xE8\x96\xA9", + "\x8E\x47"=>"\xE9\x9B\x91", + "\x8E\x48"=>"\xE7\x9A\x90", + "\x8E\x49"=>"\xE9\xAF\x96", + "\x8E\x4A"=>"\xE6\x8D\x8C", + "\x8E\x4B"=>"\xE9\x8C\x86", + "\x8E\x4C"=>"\xE9\xAE\xAB", + "\x8E\x4D"=>"\xE7\x9A\xBF", + "\x8E\x4E"=>"\xE6\x99\x92", + "\x8E\x4F"=>"\xE4\xB8\x89", + "\x8E\x50"=>"\xE5\x82\x98", + "\x8E\x51"=>"\xE5\x8F\x82", + "\x8E\x52"=>"\xE5\xB1\xB1", + "\x8E\x53"=>"\xE6\x83\xA8", + "\x8E\x54"=>"\xE6\x92\x92", + "\x8E\x55"=>"\xE6\x95\xA3", + "\x8E\x56"=>"\xE6\xA1\x9F", + "\x8E\x57"=>"\xE7\x87\xA6", + "\x8E\x58"=>"\xE7\x8F\x8A", + "\x8E\x59"=>"\xE7\x94\xA3", + "\x8E\x5A"=>"\xE7\xAE\x97", + "\x8E\x5B"=>"\xE7\xBA\x82", + "\x8E\x5C"=>"\xE8\x9A\x95", + "\x8E\x5D"=>"\xE8\xAE\x83", + "\x8E\x5E"=>"\xE8\xB3\x9B", + "\x8E\x5F"=>"\xE9\x85\xB8", + "\x8E\x60"=>"\xE9\xA4\x90", + "\x8E\x61"=>"\xE6\x96\xAC", + "\x8E\x62"=>"\xE6\x9A\xAB", + "\x8E\x63"=>"\xE6\xAE\x8B", + "\x8E\x64"=>"\xE4\xBB\x95", + "\x8E\x65"=>"\xE4\xBB\x94", + "\x8E\x66"=>"\xE4\xBC\xBA", + "\x8E\x67"=>"\xE4\xBD\xBF", + "\x8E\x68"=>"\xE5\x88\xBA", + "\x8E\x69"=>"\xE5\x8F\xB8", + "\x8E\x6A"=>"\xE5\x8F\xB2", + "\x8E\x6B"=>"\xE5\x97\xA3", + "\x8E\x6C"=>"\xE5\x9B\x9B", + "\x8E\x6D"=>"\xE5\xA3\xAB", + "\x8E\x6E"=>"\xE5\xA7\x8B", + "\x8E\x6F"=>"\xE5\xA7\x89", + "\x8E\x70"=>"\xE5\xA7\xBF", + "\x8E\x71"=>"\xE5\xAD\x90", + "\x8E\x72"=>"\xE5\xB1\x8D", + "\x8E\x73"=>"\xE5\xB8\x82", + "\x8E\x74"=>"\xE5\xB8\xAB", + "\x8E\x75"=>"\xE5\xBF\x97", + "\x8E\x76"=>"\xE6\x80\x9D", + "\x8E\x77"=>"\xE6\x8C\x87", + "\x8E\x78"=>"\xE6\x94\xAF", + "\x8E\x79"=>"\xE5\xAD\x9C", + "\x8E\x7A"=>"\xE6\x96\xAF", + "\x8E\x7B"=>"\xE6\x96\xBD", + "\x8E\x7C"=>"\xE6\x97\xA8", + "\x8E\x7D"=>"\xE6\x9E\x9D", + "\x8E\x7E"=>"\xE6\xAD\xA2", + "\x8E\x80"=>"\xE6\xAD\xBB", + "\x8E\x81"=>"\xE6\xB0\x8F", + "\x8E\x82"=>"\xE7\x8D\x85", + "\x8E\x83"=>"\xE7\xA5\x89", + "\x8E\x84"=>"\xE7\xA7\x81", + "\x8E\x85"=>"\xE7\xB3\xB8", + "\x8E\x86"=>"\xE7\xB4\x99", + "\x8E\x87"=>"\xE7\xB4\xAB", + "\x8E\x88"=>"\xE8\x82\xA2", + "\x8E\x89"=>"\xE8\x84\x82", + "\x8E\x8A"=>"\xE8\x87\xB3", + "\x8E\x8B"=>"\xE8\xA6\x96", + "\x8E\x8C"=>"\xE8\xA9\x9E", + "\x8E\x8D"=>"\xE8\xA9\xA9", + "\x8E\x8E"=>"\xE8\xA9\xA6", + "\x8E\x8F"=>"\xE8\xAA\x8C", + "\x8E\x90"=>"\xE8\xAB\xAE", + "\x8E\x91"=>"\xE8\xB3\x87", + "\x8E\x92"=>"\xE8\xB3\x9C", + "\x8E\x93"=>"\xE9\x9B\x8C", + "\x8E\x94"=>"\xE9\xA3\xBC", + "\x8E\x95"=>"\xE6\xAD\xAF", + "\x8E\x96"=>"\xE4\xBA\x8B", + "\x8E\x97"=>"\xE4\xBC\xBC", + "\x8E\x98"=>"\xE4\xBE\x8D", + "\x8E\x99"=>"\xE5\x85\x90", + "\x8E\x9A"=>"\xE5\xAD\x97", + "\x8E\x9B"=>"\xE5\xAF\xBA", + "\x8E\x9C"=>"\xE6\x85\x88", + "\x8E\x9D"=>"\xE6\x8C\x81", + "\x8E\x9E"=>"\xE6\x99\x82", + "\x8E\x9F"=>"\xE6\xAC\xA1", + "\x8E\xA0"=>"\xE6\xBB\x8B", + "\x8E\xA1"=>"\xE6\xB2\xBB", + "\x8E\xA2"=>"\xE7\x88\xBE", + "\x8E\xA3"=>"\xE7\x92\xBD", + "\x8E\xA4"=>"\xE7\x97\x94", + "\x8E\xA5"=>"\xE7\xA3\x81", + "\x8E\xA6"=>"\xE7\xA4\xBA", + "\x8E\xA7"=>"\xE8\x80\x8C", + "\x8E\xA8"=>"\xE8\x80\xB3", + "\x8E\xA9"=>"\xE8\x87\xAA", + "\x8E\xAA"=>"\xE8\x92\x94", + "\x8E\xAB"=>"\xE8\xBE\x9E", + "\x8E\xAC"=>"\xE6\xB1\x90", + "\x8E\xAD"=>"\xE9\xB9\xBF", + "\x8E\xAE"=>"\xE5\xBC\x8F", + "\x8E\xAF"=>"\xE8\xAD\x98", + "\x8E\xB0"=>"\xE9\xB4\xAB", + "\x8E\xB1"=>"\xE7\xAB\xBA", + "\x8E\xB2"=>"\xE8\xBB\xB8", + "\x8E\xB3"=>"\xE5\xAE\x8D", + "\x8E\xB4"=>"\xE9\x9B\xAB", + "\x8E\xB5"=>"\xE4\xB8\x83", + "\x8E\xB6"=>"\xE5\x8F\xB1", + "\x8E\xB7"=>"\xE5\x9F\xB7", + "\x8E\xB8"=>"\xE5\xA4\xB1", + "\x8E\xB9"=>"\xE5\xAB\x89", + "\x8E\xBA"=>"\xE5\xAE\xA4", + "\x8E\xBB"=>"\xE6\x82\x89", + "\x8E\xBC"=>"\xE6\xB9\xBF", + "\x8E\xBD"=>"\xE6\xBC\x86", + "\x8E\xBE"=>"\xE7\x96\xBE", + "\x8E\xBF"=>"\xE8\xB3\xAA", + "\x8E\xC0"=>"\xE5\xAE\x9F", + "\x8E\xC1"=>"\xE8\x94\x80", + "\x8E\xC2"=>"\xE7\xAF\xA0", + "\x8E\xC3"=>"\xE5\x81\xB2", + "\x8E\xC4"=>"\xE6\x9F\xB4", + "\x8E\xC5"=>"\xE8\x8A\x9D", + "\x8E\xC6"=>"\xE5\xB1\xA1", + "\x8E\xC7"=>"\xE8\x95\x8A", + "\x8E\xC8"=>"\xE7\xB8\x9E", + "\x8E\xC9"=>"\xE8\x88\x8E", + "\x8E\xCA"=>"\xE5\x86\x99", + "\x8E\xCB"=>"\xE5\xB0\x84", + "\x8E\xCC"=>"\xE6\x8D\xA8", + "\x8E\xCD"=>"\xE8\xB5\xA6", + "\x8E\xCE"=>"\xE6\x96\x9C", + "\x8E\xCF"=>"\xE7\x85\xAE", + "\x8E\xD0"=>"\xE7\xA4\xBE", + "\x8E\xD1"=>"\xE7\xB4\x97", + "\x8E\xD2"=>"\xE8\x80\x85", + "\x8E\xD3"=>"\xE8\xAC\x9D", + "\x8E\xD4"=>"\xE8\xBB\x8A", + "\x8E\xD5"=>"\xE9\x81\xAE", + "\x8E\xD6"=>"\xE8\x9B\x87", + "\x8E\xD7"=>"\xE9\x82\xAA", + "\x8E\xD8"=>"\xE5\x80\x9F", + "\x8E\xD9"=>"\xE5\x8B\xBA", + "\x8E\xDA"=>"\xE5\xB0\xBA", + "\x8E\xDB"=>"\xE6\x9D\x93", + "\x8E\xDC"=>"\xE7\x81\xBC", + "\x8E\xDD"=>"\xE7\x88\xB5", + "\x8E\xDE"=>"\xE9\x85\x8C", + "\x8E\xDF"=>"\xE9\x87\x88", + "\x8E\xE0"=>"\xE9\x8C\xAB", + "\x8E\xE1"=>"\xE8\x8B\xA5", + "\x8E\xE2"=>"\xE5\xAF\x82", + "\x8E\xE3"=>"\xE5\xBC\xB1", + "\x8E\xE4"=>"\xE6\x83\xB9", + "\x8E\xE5"=>"\xE4\xB8\xBB", + "\x8E\xE6"=>"\xE5\x8F\x96", + "\x8E\xE7"=>"\xE5\xAE\x88", + "\x8E\xE8"=>"\xE6\x89\x8B", + "\x8E\xE9"=>"\xE6\x9C\xB1", + "\x8E\xEA"=>"\xE6\xAE\x8A", + "\x8E\xEB"=>"\xE7\x8B\xA9", + "\x8E\xEC"=>"\xE7\x8F\xA0", + "\x8E\xED"=>"\xE7\xA8\xAE", + "\x8E\xEE"=>"\xE8\x85\xAB", + "\x8E\xEF"=>"\xE8\xB6\xA3", + "\x8E\xF0"=>"\xE9\x85\x92", + "\x8E\xF1"=>"\xE9\xA6\x96", + "\x8E\xF2"=>"\xE5\x84\x92", + "\x8E\xF3"=>"\xE5\x8F\x97", + "\x8E\xF4"=>"\xE5\x91\xAA", + "\x8E\xF5"=>"\xE5\xAF\xBF", + "\x8E\xF6"=>"\xE6\x8E\x88", + "\x8E\xF7"=>"\xE6\xA8\xB9", + "\x8E\xF8"=>"\xE7\xB6\xAC", + "\x8E\xF9"=>"\xE9\x9C\x80", + "\x8E\xFA"=>"\xE5\x9B\x9A", + "\x8E\xFB"=>"\xE5\x8F\x8E", + "\x8E\xFC"=>"\xE5\x91\xA8", + "\x8F\x40"=>"\xE5\xAE\x97", + "\x8F\x41"=>"\xE5\xB0\xB1", + "\x8F\x42"=>"\xE5\xB7\x9E", + "\x8F\x43"=>"\xE4\xBF\xAE", + "\x8F\x44"=>"\xE6\x84\x81", + "\x8F\x45"=>"\xE6\x8B\xBE", + "\x8F\x46"=>"\xE6\xB4\xB2", + "\x8F\x47"=>"\xE7\xA7\x80", + "\x8F\x48"=>"\xE7\xA7\x8B", + "\x8F\x49"=>"\xE7\xB5\x82", + "\x8F\x4A"=>"\xE7\xB9\x8D", + "\x8F\x4B"=>"\xE7\xBF\x92", + "\x8F\x4C"=>"\xE8\x87\xAD", + "\x8F\x4D"=>"\xE8\x88\x9F", + "\x8F\x4E"=>"\xE8\x92\x90", + "\x8F\x4F"=>"\xE8\xA1\x86", + "\x8F\x50"=>"\xE8\xA5\xB2", + "\x8F\x51"=>"\xE8\xAE\x90", + "\x8F\x52"=>"\xE8\xB9\xB4", + "\x8F\x53"=>"\xE8\xBC\xAF", + "\x8F\x54"=>"\xE9\x80\xB1", + "\x8F\x55"=>"\xE9\x85\x8B", + "\x8F\x56"=>"\xE9\x85\xAC", + "\x8F\x57"=>"\xE9\x9B\x86", + "\x8F\x58"=>"\xE9\x86\x9C", + "\x8F\x59"=>"\xE4\xBB\x80", + "\x8F\x5A"=>"\xE4\xBD\x8F", + "\x8F\x5B"=>"\xE5\x85\x85", + "\x8F\x5C"=>"\xE5\x8D\x81", + "\x8F\x5D"=>"\xE5\xBE\x93", + "\x8F\x5E"=>"\xE6\x88\x8E", + "\x8F\x5F"=>"\xE6\x9F\x94", + "\x8F\x60"=>"\xE6\xB1\x81", + "\x8F\x61"=>"\xE6\xB8\x8B", + "\x8F\x62"=>"\xE7\x8D\xA3", + "\x8F\x63"=>"\xE7\xB8\xA6", + "\x8F\x64"=>"\xE9\x87\x8D", + "\x8F\x65"=>"\xE9\x8A\x83", + "\x8F\x66"=>"\xE5\x8F\x94", + "\x8F\x67"=>"\xE5\xA4\x99", + "\x8F\x68"=>"\xE5\xAE\xBF", + "\x8F\x69"=>"\xE6\xB7\x91", + "\x8F\x6A"=>"\xE7\xA5\x9D", + "\x8F\x6B"=>"\xE7\xB8\xAE", + "\x8F\x6C"=>"\xE7\xB2\x9B", + "\x8F\x6D"=>"\xE5\xA1\xBE", + "\x8F\x6E"=>"\xE7\x86\x9F", + "\x8F\x6F"=>"\xE5\x87\xBA", + "\x8F\x70"=>"\xE8\xA1\x93", + "\x8F\x71"=>"\xE8\xBF\xB0", + "\x8F\x72"=>"\xE4\xBF\x8A", + "\x8F\x73"=>"\xE5\xB3\xBB", + "\x8F\x74"=>"\xE6\x98\xA5", + "\x8F\x75"=>"\xE7\x9E\xAC", + "\x8F\x76"=>"\xE7\xAB\xA3", + "\x8F\x77"=>"\xE8\x88\x9C", + "\x8F\x78"=>"\xE9\xA7\xBF", + "\x8F\x79"=>"\xE5\x87\x86", + "\x8F\x7A"=>"\xE5\xBE\xAA", + "\x8F\x7B"=>"\xE6\x97\xAC", + "\x8F\x7C"=>"\xE6\xA5\xAF", + "\x8F\x7D"=>"\xE6\xAE\x89", + "\x8F\x7E"=>"\xE6\xB7\xB3", + "\x8F\x80"=>"\xE6\xBA\x96", + "\x8F\x81"=>"\xE6\xBD\xA4", + "\x8F\x82"=>"\xE7\x9B\xBE", + "\x8F\x83"=>"\xE7\xB4\x94", + "\x8F\x84"=>"\xE5\xB7\xA1", + "\x8F\x85"=>"\xE9\x81\xB5", + "\x8F\x86"=>"\xE9\x86\x87", + "\x8F\x87"=>"\xE9\xA0\x86", + "\x8F\x88"=>"\xE5\x87\xA6", + "\x8F\x89"=>"\xE5\x88\x9D", + "\x8F\x8A"=>"\xE6\x89\x80", + "\x8F\x8B"=>"\xE6\x9A\x91", + "\x8F\x8C"=>"\xE6\x9B\x99", + "\x8F\x8D"=>"\xE6\xB8\x9A", + "\x8F\x8E"=>"\xE5\xBA\xB6", + "\x8F\x8F"=>"\xE7\xB7\x92", + "\x8F\x90"=>"\xE7\xBD\xB2", + "\x8F\x91"=>"\xE6\x9B\xB8", + "\x8F\x92"=>"\xE8\x96\xAF", + "\x8F\x93"=>"\xE8\x97\xB7", + "\x8F\x94"=>"\xE8\xAB\xB8", + "\x8F\x95"=>"\xE5\x8A\xA9", + "\x8F\x96"=>"\xE5\x8F\x99", + "\x8F\x97"=>"\xE5\xA5\xB3", + "\x8F\x98"=>"\xE5\xBA\x8F", + "\x8F\x99"=>"\xE5\xBE\x90", + "\x8F\x9A"=>"\xE6\x81\x95", + "\x8F\x9B"=>"\xE9\x8B\xA4", + "\x8F\x9C"=>"\xE9\x99\xA4", + "\x8F\x9D"=>"\xE5\x82\xB7", + "\x8F\x9E"=>"\xE5\x84\x9F", + "\x8F\x9F"=>"\xE5\x8B\x9D", + "\x8F\xA0"=>"\xE5\x8C\xA0", + "\x8F\xA1"=>"\xE5\x8D\x87", + "\x8F\xA2"=>"\xE5\x8F\xAC", + "\x8F\xA3"=>"\xE5\x93\xA8", + "\x8F\xA4"=>"\xE5\x95\x86", + "\x8F\xA5"=>"\xE5\x94\xB1", + "\x8F\xA6"=>"\xE5\x98\x97", + "\x8F\xA7"=>"\xE5\xA5\xA8", + "\x8F\xA8"=>"\xE5\xA6\xBE", + "\x8F\xA9"=>"\xE5\xA8\xBC", + "\x8F\xAA"=>"\xE5\xAE\xB5", + "\x8F\xAB"=>"\xE5\xB0\x86", + "\x8F\xAC"=>"\xE5\xB0\x8F", + "\x8F\xAD"=>"\xE5\xB0\x91", + "\x8F\xAE"=>"\xE5\xB0\x9A", + "\x8F\xAF"=>"\xE5\xBA\x84", + "\x8F\xB0"=>"\xE5\xBA\x8A", + "\x8F\xB1"=>"\xE5\xBB\xA0", + "\x8F\xB2"=>"\xE5\xBD\xB0", + "\x8F\xB3"=>"\xE6\x89\xBF", + "\x8F\xB4"=>"\xE6\x8A\x84", + "\x8F\xB5"=>"\xE6\x8B\x9B", + "\x8F\xB6"=>"\xE6\x8E\x8C", + "\x8F\xB7"=>"\xE6\x8D\xB7", + "\x8F\xB8"=>"\xE6\x98\x87", + "\x8F\xB9"=>"\xE6\x98\x8C", + "\x8F\xBA"=>"\xE6\x98\xAD", + "\x8F\xBB"=>"\xE6\x99\xB6", + "\x8F\xBC"=>"\xE6\x9D\xBE", + "\x8F\xBD"=>"\xE6\xA2\xA2", + "\x8F\xBE"=>"\xE6\xA8\x9F", + "\x8F\xBF"=>"\xE6\xA8\xB5", + "\x8F\xC0"=>"\xE6\xB2\xBC", + "\x8F\xC1"=>"\xE6\xB6\x88", + "\x8F\xC2"=>"\xE6\xB8\x89", + "\x8F\xC3"=>"\xE6\xB9\x98", + "\x8F\xC4"=>"\xE7\x84\xBC", + "\x8F\xC5"=>"\xE7\x84\xA6", + "\x8F\xC6"=>"\xE7\x85\xA7", + "\x8F\xC7"=>"\xE7\x97\x87", + "\x8F\xC8"=>"\xE7\x9C\x81", + "\x8F\xC9"=>"\xE7\xA1\x9D", + "\x8F\xCA"=>"\xE7\xA4\x81", + "\x8F\xCB"=>"\xE7\xA5\xA5", + "\x8F\xCC"=>"\xE7\xA7\xB0", + "\x8F\xCD"=>"\xE7\xAB\xA0", + "\x8F\xCE"=>"\xE7\xAC\x91", + "\x8F\xCF"=>"\xE7\xB2\xA7", + "\x8F\xD0"=>"\xE7\xB4\xB9", + "\x8F\xD1"=>"\xE8\x82\x96", + "\x8F\xD2"=>"\xE8\x8F\x96", + "\x8F\xD3"=>"\xE8\x92\x8B", + "\x8F\xD4"=>"\xE8\x95\x89", + "\x8F\xD5"=>"\xE8\xA1\x9D", + "\x8F\xD6"=>"\xE8\xA3\xB3", + "\x8F\xD7"=>"\xE8\xA8\x9F", + "\x8F\xD8"=>"\xE8\xA8\xBC", + "\x8F\xD9"=>"\xE8\xA9\x94", + "\x8F\xDA"=>"\xE8\xA9\xB3", + "\x8F\xDB"=>"\xE8\xB1\xA1", + "\x8F\xDC"=>"\xE8\xB3\x9E", + "\x8F\xDD"=>"\xE9\x86\xA4", + "\x8F\xDE"=>"\xE9\x89\xA6", + "\x8F\xDF"=>"\xE9\x8D\xBE", + "\x8F\xE0"=>"\xE9\x90\x98", + "\x8F\xE1"=>"\xE9\x9A\x9C", + "\x8F\xE2"=>"\xE9\x9E\x98", + "\x8F\xE3"=>"\xE4\xB8\x8A", + "\x8F\xE4"=>"\xE4\xB8\x88", + "\x8F\xE5"=>"\xE4\xB8\x9E", + "\x8F\xE6"=>"\xE4\xB9\x97", + "\x8F\xE7"=>"\xE5\x86\x97", + "\x8F\xE8"=>"\xE5\x89\xB0", + "\x8F\xE9"=>"\xE5\x9F\x8E", + "\x8F\xEA"=>"\xE5\xA0\xB4", + "\x8F\xEB"=>"\xE5\xA3\x8C", + "\x8F\xEC"=>"\xE5\xAC\xA2", + "\x8F\xED"=>"\xE5\xB8\xB8", + "\x8F\xEE"=>"\xE6\x83\x85", + "\x8F\xEF"=>"\xE6\x93\xBE", + "\x8F\xF0"=>"\xE6\x9D\xA1", + "\x8F\xF1"=>"\xE6\x9D\x96", + "\x8F\xF2"=>"\xE6\xB5\x84", + "\x8F\xF3"=>"\xE7\x8A\xB6", + "\x8F\xF4"=>"\xE7\x95\xB3", + "\x8F\xF5"=>"\xE7\xA9\xA3", + "\x8F\xF6"=>"\xE8\x92\xB8", + "\x8F\xF7"=>"\xE8\xAD\xB2", + "\x8F\xF8"=>"\xE9\x86\xB8", + "\x8F\xF9"=>"\xE9\x8C\xA0", + "\x8F\xFA"=>"\xE5\x98\xB1", + "\x8F\xFB"=>"\xE5\x9F\xB4", + "\x8F\xFC"=>"\xE9\xA3\xBE", + "\x90\x40"=>"\xE6\x8B\xAD", + "\x90\x41"=>"\xE6\xA4\x8D", + "\x90\x42"=>"\xE6\xAE\x96", + "\x90\x43"=>"\xE7\x87\xAD", + "\x90\x44"=>"\xE7\xB9\x94", + "\x90\x45"=>"\xE8\x81\xB7", + "\x90\x46"=>"\xE8\x89\xB2", + "\x90\x47"=>"\xE8\xA7\xA6", + "\x90\x48"=>"\xE9\xA3\x9F", + "\x90\x49"=>"\xE8\x9D\x95", + "\x90\x4A"=>"\xE8\xBE\xB1", + "\x90\x4B"=>"\xE5\xB0\xBB", + "\x90\x4C"=>"\xE4\xBC\xB8", + "\x90\x4D"=>"\xE4\xBF\xA1", + "\x90\x4E"=>"\xE4\xBE\xB5", + "\x90\x4F"=>"\xE5\x94\x87", + "\x90\x50"=>"\xE5\xA8\xA0", + "\x90\x51"=>"\xE5\xAF\x9D", + "\x90\x52"=>"\xE5\xAF\xA9", + "\x90\x53"=>"\xE5\xBF\x83", + "\x90\x54"=>"\xE6\x85\x8E", + "\x90\x55"=>"\xE6\x8C\xAF", + "\x90\x56"=>"\xE6\x96\xB0", + "\x90\x57"=>"\xE6\x99\x8B", + "\x90\x58"=>"\xE6\xA3\xAE", + "\x90\x59"=>"\xE6\xA6\x9B", + "\x90\x5A"=>"\xE6\xB5\xB8", + "\x90\x5B"=>"\xE6\xB7\xB1", + "\x90\x5C"=>"\xE7\x94\xB3", + "\x90\x5D"=>"\xE7\x96\xB9", + "\x90\x5E"=>"\xE7\x9C\x9F", + "\x90\x5F"=>"\xE7\xA5\x9E", + "\x90\x60"=>"\xE7\xA7\xA6", + "\x90\x61"=>"\xE7\xB4\xB3", + "\x90\x62"=>"\xE8\x87\xA3", + "\x90\x63"=>"\xE8\x8A\xAF", + "\x90\x64"=>"\xE8\x96\xAA", + "\x90\x65"=>"\xE8\xA6\xAA", + "\x90\x66"=>"\xE8\xA8\xBA", + "\x90\x67"=>"\xE8\xBA\xAB", + "\x90\x68"=>"\xE8\xBE\x9B", + "\x90\x69"=>"\xE9\x80\xB2", + "\x90\x6A"=>"\xE9\x87\x9D", + "\x90\x6B"=>"\xE9\x9C\x87", + "\x90\x6C"=>"\xE4\xBA\xBA", + "\x90\x6D"=>"\xE4\xBB\x81", + "\x90\x6E"=>"\xE5\x88\x83", + "\x90\x6F"=>"\xE5\xA1\xB5", + "\x90\x70"=>"\xE5\xA3\xAC", + "\x90\x71"=>"\xE5\xB0\x8B", + "\x90\x72"=>"\xE7\x94\x9A", + "\x90\x73"=>"\xE5\xB0\xBD", + "\x90\x74"=>"\xE8\x85\x8E", + "\x90\x75"=>"\xE8\xA8\x8A", + "\x90\x76"=>"\xE8\xBF\x85", + "\x90\x77"=>"\xE9\x99\xA3", + "\x90\x78"=>"\xE9\x9D\xAD", + "\x90\x79"=>"\xE7\xAC\xA5", + "\x90\x7A"=>"\xE8\xAB\x8F", + "\x90\x7B"=>"\xE9\xA0\x88", + "\x90\x7C"=>"\xE9\x85\xA2", + "\x90\x7D"=>"\xE5\x9B\xB3", + "\x90\x7E"=>"\xE5\x8E\xA8", + "\x90\x80"=>"\xE9\x80\x97", + "\x90\x81"=>"\xE5\x90\xB9", + "\x90\x82"=>"\xE5\x9E\x82", + "\x90\x83"=>"\xE5\xB8\xA5", + "\x90\x84"=>"\xE6\x8E\xA8", + "\x90\x85"=>"\xE6\xB0\xB4", + "\x90\x86"=>"\xE7\x82\x8A", + "\x90\x87"=>"\xE7\x9D\xA1", + "\x90\x88"=>"\xE7\xB2\x8B", + "\x90\x89"=>"\xE7\xBF\xA0", + "\x90\x8A"=>"\xE8\xA1\xB0", + "\x90\x8B"=>"\xE9\x81\x82", + "\x90\x8C"=>"\xE9\x85\x94", + "\x90\x8D"=>"\xE9\x8C\x90", + "\x90\x8E"=>"\xE9\x8C\x98", + "\x90\x8F"=>"\xE9\x9A\x8F", + "\x90\x90"=>"\xE7\x91\x9E", + "\x90\x91"=>"\xE9\xAB\x84", + "\x90\x92"=>"\xE5\xB4\x87", + "\x90\x93"=>"\xE5\xB5\xA9", + "\x90\x94"=>"\xE6\x95\xB0", + "\x90\x95"=>"\xE6\x9E\xA2", + "\x90\x96"=>"\xE8\xB6\xA8", + "\x90\x97"=>"\xE9\x9B\x9B", + "\x90\x98"=>"\xE6\x8D\xAE", + "\x90\x99"=>"\xE6\x9D\x89", + "\x90\x9A"=>"\xE6\xA4\x99", + "\x90\x9B"=>"\xE8\x8F\x85", + "\x90\x9C"=>"\xE9\xA0\x97", + "\x90\x9D"=>"\xE9\x9B\x80", + "\x90\x9E"=>"\xE8\xA3\xBE", + "\x90\x9F"=>"\xE6\xBE\x84", + "\x90\xA0"=>"\xE6\x91\xBA", + "\x90\xA1"=>"\xE5\xAF\xB8", + "\x90\xA2"=>"\xE4\xB8\x96", + "\x90\xA3"=>"\xE7\x80\xAC", + "\x90\xA4"=>"\xE7\x95\x9D", + "\x90\xA5"=>"\xE6\x98\xAF", + "\x90\xA6"=>"\xE5\x87\x84", + "\x90\xA7"=>"\xE5\x88\xB6", + "\x90\xA8"=>"\xE5\x8B\xA2", + "\x90\xA9"=>"\xE5\xA7\x93", + "\x90\xAA"=>"\xE5\xBE\x81", + "\x90\xAB"=>"\xE6\x80\xA7", + "\x90\xAC"=>"\xE6\x88\x90", + "\x90\xAD"=>"\xE6\x94\xBF", + "\x90\xAE"=>"\xE6\x95\xB4", + "\x90\xAF"=>"\xE6\x98\x9F", + "\x90\xB0"=>"\xE6\x99\xB4", + "\x90\xB1"=>"\xE6\xA3\xB2", + "\x90\xB2"=>"\xE6\xA0\x96", + "\x90\xB3"=>"\xE6\xAD\xA3", + "\x90\xB4"=>"\xE6\xB8\x85", + "\x90\xB5"=>"\xE7\x89\xB2", + "\x90\xB6"=>"\xE7\x94\x9F", + "\x90\xB7"=>"\xE7\x9B\x9B", + "\x90\xB8"=>"\xE7\xB2\xBE", + "\x90\xB9"=>"\xE8\x81\x96", + "\x90\xBA"=>"\xE5\xA3\xB0", + "\x90\xBB"=>"\xE8\xA3\xBD", + "\x90\xBC"=>"\xE8\xA5\xBF", + "\x90\xBD"=>"\xE8\xAA\xA0", + "\x90\xBE"=>"\xE8\xAA\x93", + "\x90\xBF"=>"\xE8\xAB\x8B", + "\x90\xC0"=>"\xE9\x80\x9D", + "\x90\xC1"=>"\xE9\x86\x92", + "\x90\xC2"=>"\xE9\x9D\x92", + "\x90\xC3"=>"\xE9\x9D\x99", + "\x90\xC4"=>"\xE6\x96\x89", + "\x90\xC5"=>"\xE7\xA8\x8E", + "\x90\xC6"=>"\xE8\x84\x86", + "\x90\xC7"=>"\xE9\x9A\xBB", + "\x90\xC8"=>"\xE5\xB8\xAD", + "\x90\xC9"=>"\xE6\x83\x9C", + "\x90\xCA"=>"\xE6\x88\x9A", + "\x90\xCB"=>"\xE6\x96\xA5", + "\x90\xCC"=>"\xE6\x98\x94", + "\x90\xCD"=>"\xE6\x9E\x90", + "\x90\xCE"=>"\xE7\x9F\xB3", + "\x90\xCF"=>"\xE7\xA9\x8D", + "\x90\xD0"=>"\xE7\xB1\x8D", + "\x90\xD1"=>"\xE7\xB8\xBE", + "\x90\xD2"=>"\xE8\x84\x8A", + "\x90\xD3"=>"\xE8\xB2\xAC", + "\x90\xD4"=>"\xE8\xB5\xA4", + "\x90\xD5"=>"\xE8\xB7\xA1", + "\x90\xD6"=>"\xE8\xB9\x9F", + "\x90\xD7"=>"\xE7\xA2\xA9", + "\x90\xD8"=>"\xE5\x88\x87", + "\x90\xD9"=>"\xE6\x8B\x99", + "\x90\xDA"=>"\xE6\x8E\xA5", + "\x90\xDB"=>"\xE6\x91\x82", + "\x90\xDC"=>"\xE6\x8A\x98", + "\x90\xDD"=>"\xE8\xA8\xAD", + "\x90\xDE"=>"\xE7\xAA\x83", + "\x90\xDF"=>"\xE7\xAF\x80", + "\x90\xE0"=>"\xE8\xAA\xAC", + "\x90\xE1"=>"\xE9\x9B\xAA", + "\x90\xE2"=>"\xE7\xB5\xB6", + "\x90\xE3"=>"\xE8\x88\x8C", + "\x90\xE4"=>"\xE8\x9D\x89", + "\x90\xE5"=>"\xE4\xBB\x99", + "\x90\xE6"=>"\xE5\x85\x88", + "\x90\xE7"=>"\xE5\x8D\x83", + "\x90\xE8"=>"\xE5\x8D\xA0", + "\x90\xE9"=>"\xE5\xAE\xA3", + "\x90\xEA"=>"\xE5\xB0\x82", + "\x90\xEB"=>"\xE5\xB0\x96", + "\x90\xEC"=>"\xE5\xB7\x9D", + "\x90\xED"=>"\xE6\x88\xA6", + "\x90\xEE"=>"\xE6\x89\x87", + "\x90\xEF"=>"\xE6\x92\xB0", + "\x90\xF0"=>"\xE6\xA0\x93", + "\x90\xF1"=>"\xE6\xA0\xB4", + "\x90\xF2"=>"\xE6\xB3\x89", + "\x90\xF3"=>"\xE6\xB5\x85", + "\x90\xF4"=>"\xE6\xB4\x97", + "\x90\xF5"=>"\xE6\x9F\x93", + "\x90\xF6"=>"\xE6\xBD\x9C", + "\x90\xF7"=>"\xE7\x85\x8E", + "\x90\xF8"=>"\xE7\x85\xBD", + "\x90\xF9"=>"\xE6\x97\x8B", + "\x90\xFA"=>"\xE7\xA9\xBF", + "\x90\xFB"=>"\xE7\xAE\xAD", + "\x90\xFC"=>"\xE7\xB7\x9A", + "\x91\x40"=>"\xE7\xB9\x8A", + "\x91\x41"=>"\xE7\xBE\xA8", + "\x91\x42"=>"\xE8\x85\xBA", + "\x91\x43"=>"\xE8\x88\x9B", + "\x91\x44"=>"\xE8\x88\xB9", + "\x91\x45"=>"\xE8\x96\xA6", + "\x91\x46"=>"\xE8\xA9\xAE", + "\x91\x47"=>"\xE8\xB3\x8E", + "\x91\x48"=>"\xE8\xB7\xB5", + "\x91\x49"=>"\xE9\x81\xB8", + "\x91\x4A"=>"\xE9\x81\xB7", + "\x91\x4B"=>"\xE9\x8A\xAD", + "\x91\x4C"=>"\xE9\x8A\x91", + "\x91\x4D"=>"\xE9\x96\x83", + "\x91\x4E"=>"\xE9\xAE\xAE", + "\x91\x4F"=>"\xE5\x89\x8D", + "\x91\x50"=>"\xE5\x96\x84", + "\x91\x51"=>"\xE6\xBC\xB8", + "\x91\x52"=>"\xE7\x84\xB6", + "\x91\x53"=>"\xE5\x85\xA8", + "\x91\x54"=>"\xE7\xA6\x85", + "\x91\x55"=>"\xE7\xB9\x95", + "\x91\x56"=>"\xE8\x86\xB3", + "\x91\x57"=>"\xE7\xB3\x8E", + "\x91\x58"=>"\xE5\x99\x8C", + "\x91\x59"=>"\xE5\xA1\x91", + "\x91\x5A"=>"\xE5\xB2\xA8", + "\x91\x5B"=>"\xE6\x8E\xAA", + "\x91\x5C"=>"\xE6\x9B\xBE", + "\x91\x5D"=>"\xE6\x9B\xBD", + "\x91\x5E"=>"\xE6\xA5\x9A", + "\x91\x5F"=>"\xE7\x8B\x99", + "\x91\x60"=>"\xE7\x96\x8F", + "\x91\x61"=>"\xE7\x96\x8E", + "\x91\x62"=>"\xE7\xA4\x8E", + "\x91\x63"=>"\xE7\xA5\x96", + "\x91\x64"=>"\xE7\xA7\x9F", + "\x91\x65"=>"\xE7\xB2\x97", + "\x91\x66"=>"\xE7\xB4\xA0", + "\x91\x67"=>"\xE7\xB5\x84", + "\x91\x68"=>"\xE8\x98\x87", + "\x91\x69"=>"\xE8\xA8\xB4", + "\x91\x6A"=>"\xE9\x98\xBB", + "\x91\x6B"=>"\xE9\x81\xA1", + "\x91\x6C"=>"\xE9\xBC\xA0", + "\x91\x6D"=>"\xE5\x83\xA7", + "\x91\x6E"=>"\xE5\x89\xB5", + "\x91\x6F"=>"\xE5\x8F\x8C", + "\x91\x70"=>"\xE5\x8F\xA2", + "\x91\x71"=>"\xE5\x80\x89", + "\x91\x72"=>"\xE5\x96\xAA", + "\x91\x73"=>"\xE5\xA3\xAE", + "\x91\x74"=>"\xE5\xA5\x8F", + "\x91\x75"=>"\xE7\x88\xBD", + "\x91\x76"=>"\xE5\xAE\x8B", + "\x91\x77"=>"\xE5\xB1\xA4", + "\x91\x78"=>"\xE5\x8C\x9D", + "\x91\x79"=>"\xE6\x83\xA3", + "\x91\x7A"=>"\xE6\x83\xB3", + "\x91\x7B"=>"\xE6\x8D\x9C", + "\x91\x7C"=>"\xE6\x8E\x83", + "\x91\x7D"=>"\xE6\x8C\xBF", + "\x91\x7E"=>"\xE6\x8E\xBB", + "\x91\x80"=>"\xE6\x93\x8D", + "\x91\x81"=>"\xE6\x97\xA9", + "\x91\x82"=>"\xE6\x9B\xB9", + "\x91\x83"=>"\xE5\xB7\xA3", + "\x91\x84"=>"\xE6\xA7\x8D", + "\x91\x85"=>"\xE6\xA7\xBD", + "\x91\x86"=>"\xE6\xBC\x95", + "\x91\x87"=>"\xE7\x87\xA5", + "\x91\x88"=>"\xE4\xBA\x89", + "\x91\x89"=>"\xE7\x97\xA9", + "\x91\x8A"=>"\xE7\x9B\xB8", + "\x91\x8B"=>"\xE7\xAA\x93", + "\x91\x8C"=>"\xE7\xB3\x9F", + "\x91\x8D"=>"\xE7\xB7\x8F", + "\x91\x8E"=>"\xE7\xB6\x9C", + "\x91\x8F"=>"\xE8\x81\xA1", + "\x91\x90"=>"\xE8\x8D\x89", + "\x91\x91"=>"\xE8\x8D\x98", + "\x91\x92"=>"\xE8\x91\xAC", + "\x91\x93"=>"\xE8\x92\xBC", + "\x91\x94"=>"\xE8\x97\xBB", + "\x91\x95"=>"\xE8\xA3\x85", + "\x91\x96"=>"\xE8\xB5\xB0", + "\x91\x97"=>"\xE9\x80\x81", + "\x91\x98"=>"\xE9\x81\xAD", + "\x91\x99"=>"\xE9\x8E\x97", + "\x91\x9A"=>"\xE9\x9C\x9C", + "\x91\x9B"=>"\xE9\xA8\x92", + "\x91\x9C"=>"\xE5\x83\x8F", + "\x91\x9D"=>"\xE5\xA2\x97", + "\x91\x9E"=>"\xE6\x86\x8E", + "\x91\x9F"=>"\xE8\x87\x93", + "\x91\xA0"=>"\xE8\x94\xB5", + "\x91\xA1"=>"\xE8\xB4\x88", + "\x91\xA2"=>"\xE9\x80\xA0", + "\x91\xA3"=>"\xE4\xBF\x83", + "\x91\xA4"=>"\xE5\x81\xB4", + "\x91\xA5"=>"\xE5\x89\x87", + "\x91\xA6"=>"\xE5\x8D\xB3", + "\x91\xA7"=>"\xE6\x81\xAF", + "\x91\xA8"=>"\xE6\x8D\x89", + "\x91\xA9"=>"\xE6\x9D\x9F", + "\x91\xAA"=>"\xE6\xB8\xAC", + "\x91\xAB"=>"\xE8\xB6\xB3", + "\x91\xAC"=>"\xE9\x80\x9F", + "\x91\xAD"=>"\xE4\xBF\x97", + "\x91\xAE"=>"\xE5\xB1\x9E", + "\x91\xAF"=>"\xE8\xB3\x8A", + "\x91\xB0"=>"\xE6\x97\x8F", + "\x91\xB1"=>"\xE7\xB6\x9A", + "\x91\xB2"=>"\xE5\x8D\x92", + "\x91\xB3"=>"\xE8\xA2\x96", + "\x91\xB4"=>"\xE5\x85\xB6", + "\x91\xB5"=>"\xE6\x8F\x83", + "\x91\xB6"=>"\xE5\xAD\x98", + "\x91\xB7"=>"\xE5\xAD\xAB", + "\x91\xB8"=>"\xE5\xB0\x8A", + "\x91\xB9"=>"\xE6\x90\x8D", + "\x91\xBA"=>"\xE6\x9D\x91", + "\x91\xBB"=>"\xE9\x81\x9C", + "\x91\xBC"=>"\xE4\xBB\x96", + "\x91\xBD"=>"\xE5\xA4\x9A", + "\x91\xBE"=>"\xE5\xA4\xAA", + "\x91\xBF"=>"\xE6\xB1\xB0", + "\x91\xC0"=>"\xE8\xA9\x91", + "\x91\xC1"=>"\xE5\x94\xBE", + "\x91\xC2"=>"\xE5\xA0\x95", + "\x91\xC3"=>"\xE5\xA6\xA5", + "\x91\xC4"=>"\xE6\x83\xB0", + "\x91\xC5"=>"\xE6\x89\x93", + "\x91\xC6"=>"\xE6\x9F\x81", + "\x91\xC7"=>"\xE8\x88\xB5", + "\x91\xC8"=>"\xE6\xA5\x95", + "\x91\xC9"=>"\xE9\x99\x80", + "\x91\xCA"=>"\xE9\xA7\x84", + "\x91\xCB"=>"\xE9\xA8\xA8", + "\x91\xCC"=>"\xE4\xBD\x93", + "\x91\xCD"=>"\xE5\xA0\x86", + "\x91\xCE"=>"\xE5\xAF\xBE", + "\x91\xCF"=>"\xE8\x80\x90", + "\x91\xD0"=>"\xE5\xB2\xB1", + "\x91\xD1"=>"\xE5\xB8\xAF", + "\x91\xD2"=>"\xE5\xBE\x85", + "\x91\xD3"=>"\xE6\x80\xA0", + "\x91\xD4"=>"\xE6\x85\x8B", + "\x91\xD5"=>"\xE6\x88\xB4", + "\x91\xD6"=>"\xE6\x9B\xBF", + "\x91\xD7"=>"\xE6\xB3\xB0", + "\x91\xD8"=>"\xE6\xBB\x9E", + "\x91\xD9"=>"\xE8\x83\x8E", + "\x91\xDA"=>"\xE8\x85\xBF", + "\x91\xDB"=>"\xE8\x8B\x94", + "\x91\xDC"=>"\xE8\xA2\x8B", + "\x91\xDD"=>"\xE8\xB2\xB8", + "\x91\xDE"=>"\xE9\x80\x80", + "\x91\xDF"=>"\xE9\x80\xAE", + "\x91\xE0"=>"\xE9\x9A\x8A", + "\x91\xE1"=>"\xE9\xBB\x9B", + "\x91\xE2"=>"\xE9\xAF\x9B", + "\x91\xE3"=>"\xE4\xBB\xA3", + "\x91\xE4"=>"\xE5\x8F\xB0", + "\x91\xE5"=>"\xE5\xA4\xA7", + "\x91\xE6"=>"\xE7\xAC\xAC", + "\x91\xE7"=>"\xE9\x86\x8D", + "\x91\xE8"=>"\xE9\xA1\x8C", + "\x91\xE9"=>"\xE9\xB7\xB9", + "\x91\xEA"=>"\xE6\xBB\x9D", + "\x91\xEB"=>"\xE7\x80\xA7", + "\x91\xEC"=>"\xE5\x8D\x93", + "\x91\xED"=>"\xE5\x95\x84", + "\x91\xEE"=>"\xE5\xAE\x85", + "\x91\xEF"=>"\xE6\x89\x98", + "\x91\xF0"=>"\xE6\x8A\x9E", + "\x91\xF1"=>"\xE6\x8B\x93", + "\x91\xF2"=>"\xE6\xB2\xA2", + "\x91\xF3"=>"\xE6\xBF\xAF", + "\x91\xF4"=>"\xE7\x90\xA2", + "\x91\xF5"=>"\xE8\xA8\x97", + "\x91\xF6"=>"\xE9\x90\xB8", + "\x91\xF7"=>"\xE6\xBF\x81", + "\x91\xF8"=>"\xE8\xAB\xBE", + "\x91\xF9"=>"\xE8\x8C\xB8", + "\x91\xFA"=>"\xE5\x87\xA7", + "\x91\xFB"=>"\xE8\x9B\xB8", + "\x91\xFC"=>"\xE5\x8F\xAA", + "\x92\x40"=>"\xE5\x8F\xA9", + "\x92\x41"=>"\xE4\xBD\x86", + "\x92\x42"=>"\xE9\x81\x94", + "\x92\x43"=>"\xE8\xBE\xB0", + "\x92\x44"=>"\xE5\xA5\xAA", + "\x92\x45"=>"\xE8\x84\xB1", + "\x92\x46"=>"\xE5\xB7\xBD", + "\x92\x47"=>"\xE7\xAB\xAA", + "\x92\x48"=>"\xE8\xBE\xBF", + "\x92\x49"=>"\xE6\xA3\x9A", + "\x92\x4A"=>"\xE8\xB0\xB7", + "\x92\x4B"=>"\xE7\x8B\xB8", + "\x92\x4C"=>"\xE9\xB1\x88", + "\x92\x4D"=>"\xE6\xA8\xBD", + "\x92\x4E"=>"\xE8\xAA\xB0", + "\x92\x4F"=>"\xE4\xB8\xB9", + "\x92\x50"=>"\xE5\x8D\x98", + "\x92\x51"=>"\xE5\x98\x86", + "\x92\x52"=>"\xE5\x9D\xA6", + "\x92\x53"=>"\xE6\x8B\x85", + "\x92\x54"=>"\xE6\x8E\xA2", + "\x92\x55"=>"\xE6\x97\xA6", + "\x92\x56"=>"\xE6\xAD\x8E", + "\x92\x57"=>"\xE6\xB7\xA1", + "\x92\x58"=>"\xE6\xB9\x9B", + "\x92\x59"=>"\xE7\x82\xAD", + "\x92\x5A"=>"\xE7\x9F\xAD", + "\x92\x5B"=>"\xE7\xAB\xAF", + "\x92\x5C"=>"\xE7\xAE\xAA", + "\x92\x5D"=>"\xE7\xB6\xBB", + "\x92\x5E"=>"\xE8\x80\xBD", + "\x92\x5F"=>"\xE8\x83\x86", + "\x92\x60"=>"\xE8\x9B\x8B", + "\x92\x61"=>"\xE8\xAA\x95", + "\x92\x62"=>"\xE9\x8D\x9B", + "\x92\x63"=>"\xE5\x9B\xA3", + "\x92\x64"=>"\xE5\xA3\x87", + "\x92\x65"=>"\xE5\xBC\xBE", + "\x92\x66"=>"\xE6\x96\xAD", + "\x92\x67"=>"\xE6\x9A\x96", + "\x92\x68"=>"\xE6\xAA\x80", + "\x92\x69"=>"\xE6\xAE\xB5", + "\x92\x6A"=>"\xE7\x94\xB7", + "\x92\x6B"=>"\xE8\xAB\x87", + "\x92\x6C"=>"\xE5\x80\xA4", + "\x92\x6D"=>"\xE7\x9F\xA5", + "\x92\x6E"=>"\xE5\x9C\xB0", + "\x92\x6F"=>"\xE5\xBC\x9B", + "\x92\x70"=>"\xE6\x81\xA5", + "\x92\x71"=>"\xE6\x99\xBA", + "\x92\x72"=>"\xE6\xB1\xA0", + "\x92\x73"=>"\xE7\x97\xB4", + "\x92\x74"=>"\xE7\xA8\x9A", + "\x92\x75"=>"\xE7\xBD\xAE", + "\x92\x76"=>"\xE8\x87\xB4", + "\x92\x77"=>"\xE8\x9C\x98", + "\x92\x78"=>"\xE9\x81\x85", + "\x92\x79"=>"\xE9\xA6\xB3", + "\x92\x7A"=>"\xE7\xAF\x89", + "\x92\x7B"=>"\xE7\x95\x9C", + "\x92\x7C"=>"\xE7\xAB\xB9", + "\x92\x7D"=>"\xE7\xAD\x91", + "\x92\x7E"=>"\xE8\x93\x84", + "\x92\x80"=>"\xE9\x80\x90", + "\x92\x81"=>"\xE7\xA7\xA9", + "\x92\x82"=>"\xE7\xAA\x92", + "\x92\x83"=>"\xE8\x8C\xB6", + "\x92\x84"=>"\xE5\xAB\xA1", + "\x92\x85"=>"\xE7\x9D\x80", + "\x92\x86"=>"\xE4\xB8\xAD", + "\x92\x87"=>"\xE4\xBB\xB2", + "\x92\x88"=>"\xE5\xAE\x99", + "\x92\x89"=>"\xE5\xBF\xA0", + "\x92\x8A"=>"\xE6\x8A\xBD", + "\x92\x8B"=>"\xE6\x98\xBC", + "\x92\x8C"=>"\xE6\x9F\xB1", + "\x92\x8D"=>"\xE6\xB3\xA8", + "\x92\x8E"=>"\xE8\x99\xAB", + "\x92\x8F"=>"\xE8\xA1\xB7", + "\x92\x90"=>"\xE8\xA8\xBB", + "\x92\x91"=>"\xE9\x85\x8E", + "\x92\x92"=>"\xE9\x8B\xB3", + "\x92\x93"=>"\xE9\xA7\x90", + "\x92\x94"=>"\xE6\xA8\x97", + "\x92\x95"=>"\xE7\x80\xA6", + "\x92\x96"=>"\xE7\x8C\xAA", + "\x92\x97"=>"\xE8\x8B\xA7", + "\x92\x98"=>"\xE8\x91\x97", + "\x92\x99"=>"\xE8\xB2\xAF", + "\x92\x9A"=>"\xE4\xB8\x81", + "\x92\x9B"=>"\xE5\x85\x86", + "\x92\x9C"=>"\xE5\x87\x8B", + "\x92\x9D"=>"\xE5\x96\x8B", + "\x92\x9E"=>"\xE5\xAF\xB5", + "\x92\x9F"=>"\xE5\xB8\x96", + "\x92\xA0"=>"\xE5\xB8\xB3", + "\x92\xA1"=>"\xE5\xBA\x81", + "\x92\xA2"=>"\xE5\xBC\x94", + "\x92\xA3"=>"\xE5\xBC\xB5", + "\x92\xA4"=>"\xE5\xBD\xAB", + "\x92\xA5"=>"\xE5\xBE\xB4", + "\x92\xA6"=>"\xE6\x87\xB2", + "\x92\xA7"=>"\xE6\x8C\x91", + "\x92\xA8"=>"\xE6\x9A\xA2", + "\x92\xA9"=>"\xE6\x9C\x9D", + "\x92\xAA"=>"\xE6\xBD\xAE", + "\x92\xAB"=>"\xE7\x89\x92", + "\x92\xAC"=>"\xE7\x94\xBA", + "\x92\xAD"=>"\xE7\x9C\xBA", + "\x92\xAE"=>"\xE8\x81\xB4", + "\x92\xAF"=>"\xE8\x84\xB9", + "\x92\xB0"=>"\xE8\x85\xB8", + "\x92\xB1"=>"\xE8\x9D\xB6", + "\x92\xB2"=>"\xE8\xAA\xBF", + "\x92\xB3"=>"\xE8\xAB\x9C", + "\x92\xB4"=>"\xE8\xB6\x85", + "\x92\xB5"=>"\xE8\xB7\xB3", + "\x92\xB6"=>"\xE9\x8A\x9A", + "\x92\xB7"=>"\xE9\x95\xB7", + "\x92\xB8"=>"\xE9\xA0\x82", + "\x92\xB9"=>"\xE9\xB3\xA5", + "\x92\xBA"=>"\xE5\x8B\x85", + "\x92\xBB"=>"\xE6\x8D\x97", + "\x92\xBC"=>"\xE7\x9B\xB4", + "\x92\xBD"=>"\xE6\x9C\x95", + "\x92\xBE"=>"\xE6\xB2\x88", + "\x92\xBF"=>"\xE7\x8F\x8D", + "\x92\xC0"=>"\xE8\xB3\x83", + "\x92\xC1"=>"\xE9\x8E\xAE", + "\x92\xC2"=>"\xE9\x99\xB3", + "\x92\xC3"=>"\xE6\xB4\xA5", + "\x92\xC4"=>"\xE5\xA2\x9C", + "\x92\xC5"=>"\xE6\xA4\x8E", + "\x92\xC6"=>"\xE6\xA7\x8C", + "\x92\xC7"=>"\xE8\xBF\xBD", + "\x92\xC8"=>"\xE9\x8E\x9A", + "\x92\xC9"=>"\xE7\x97\x9B", + "\x92\xCA"=>"\xE9\x80\x9A", + "\x92\xCB"=>"\xE5\xA1\x9A", + "\x92\xCC"=>"\xE6\xA0\x82", + "\x92\xCD"=>"\xE6\x8E\xB4", + "\x92\xCE"=>"\xE6\xA7\xBB", + "\x92\xCF"=>"\xE4\xBD\x83", + "\x92\xD0"=>"\xE6\xBC\xAC", + "\x92\xD1"=>"\xE6\x9F\x98", + "\x92\xD2"=>"\xE8\xBE\xBB", + "\x92\xD3"=>"\xE8\x94\xA6", + "\x92\xD4"=>"\xE7\xB6\xB4", + "\x92\xD5"=>"\xE9\x8D\x94", + "\x92\xD6"=>"\xE6\xA4\xBF", + "\x92\xD7"=>"\xE6\xBD\xB0", + "\x92\xD8"=>"\xE5\x9D\xAA", + "\x92\xD9"=>"\xE5\xA3\xB7", + "\x92\xDA"=>"\xE5\xAC\xAC", + "\x92\xDB"=>"\xE7\xB4\xAC", + "\x92\xDC"=>"\xE7\x88\xAA", + "\x92\xDD"=>"\xE5\x90\x8A", + "\x92\xDE"=>"\xE9\x87\xA3", + "\x92\xDF"=>"\xE9\xB6\xB4", + "\x92\xE0"=>"\xE4\xBA\xAD", + "\x92\xE1"=>"\xE4\xBD\x8E", + "\x92\xE2"=>"\xE5\x81\x9C", + "\x92\xE3"=>"\xE5\x81\xB5", + "\x92\xE4"=>"\xE5\x89\x83", + "\x92\xE5"=>"\xE8\xB2\x9E", + "\x92\xE6"=>"\xE5\x91\x88", + "\x92\xE7"=>"\xE5\xA0\xA4", + "\x92\xE8"=>"\xE5\xAE\x9A", + "\x92\xE9"=>"\xE5\xB8\x9D", + "\x92\xEA"=>"\xE5\xBA\x95", + "\x92\xEB"=>"\xE5\xBA\xAD", + "\x92\xEC"=>"\xE5\xBB\xB7", + "\x92\xED"=>"\xE5\xBC\x9F", + "\x92\xEE"=>"\xE6\x82\x8C", + "\x92\xEF"=>"\xE6\x8A\xB5", + "\x92\xF0"=>"\xE6\x8C\xBA", + "\x92\xF1"=>"\xE6\x8F\x90", + "\x92\xF2"=>"\xE6\xA2\xAF", + "\x92\xF3"=>"\xE6\xB1\x80", + "\x92\xF4"=>"\xE7\xA2\x87", + "\x92\xF5"=>"\xE7\xA6\x8E", + "\x92\xF6"=>"\xE7\xA8\x8B", + "\x92\xF7"=>"\xE7\xB7\xA0", + "\x92\xF8"=>"\xE8\x89\x87", + "\x92\xF9"=>"\xE8\xA8\x82", + "\x92\xFA"=>"\xE8\xAB\xA6", + "\x92\xFB"=>"\xE8\xB9\x84", + "\x92\xFC"=>"\xE9\x80\x93", + "\x93\x40"=>"\xE9\x82\xB8", + "\x93\x41"=>"\xE9\x84\xAD", + "\x93\x42"=>"\xE9\x87\x98", + "\x93\x43"=>"\xE9\xBC\x8E", + "\x93\x44"=>"\xE6\xB3\xA5", + "\x93\x45"=>"\xE6\x91\x98", + "\x93\x46"=>"\xE6\x93\xA2", + "\x93\x47"=>"\xE6\x95\xB5", + "\x93\x48"=>"\xE6\xBB\xB4", + "\x93\x49"=>"\xE7\x9A\x84", + "\x93\x4A"=>"\xE7\xAC\x9B", + "\x93\x4B"=>"\xE9\x81\xA9", + "\x93\x4C"=>"\xE9\x8F\x91", + "\x93\x4D"=>"\xE6\xBA\xBA", + "\x93\x4E"=>"\xE5\x93\xB2", + "\x93\x4F"=>"\xE5\xBE\xB9", + "\x93\x50"=>"\xE6\x92\xA4", + "\x93\x51"=>"\xE8\xBD\x8D", + "\x93\x52"=>"\xE8\xBF\xAD", + "\x93\x53"=>"\xE9\x89\x84", + "\x93\x54"=>"\xE5\x85\xB8", + "\x93\x55"=>"\xE5\xA1\xAB", + "\x93\x56"=>"\xE5\xA4\xA9", + "\x93\x57"=>"\xE5\xB1\x95", + "\x93\x58"=>"\xE5\xBA\x97", + "\x93\x59"=>"\xE6\xB7\xBB", + "\x93\x5A"=>"\xE7\xBA\x8F", + "\x93\x5B"=>"\xE7\x94\x9C", + "\x93\x5C"=>"\xE8\xB2\xBC", + "\x93\x5D"=>"\xE8\xBB\xA2", + "\x93\x5E"=>"\xE9\xA1\x9B", + "\x93\x5F"=>"\xE7\x82\xB9", + "\x93\x60"=>"\xE4\xBC\x9D", + "\x93\x61"=>"\xE6\xAE\xBF", + "\x93\x62"=>"\xE6\xBE\xB1", + "\x93\x63"=>"\xE7\x94\xB0", + "\x93\x64"=>"\xE9\x9B\xBB", + "\x93\x65"=>"\xE5\x85\x8E", + "\x93\x66"=>"\xE5\x90\x90", + "\x93\x67"=>"\xE5\xA0\xB5", + "\x93\x68"=>"\xE5\xA1\x97", + "\x93\x69"=>"\xE5\xA6\xAC", + "\x93\x6A"=>"\xE5\xB1\xA0", + "\x93\x6B"=>"\xE5\xBE\x92", + "\x93\x6C"=>"\xE6\x96\x97", + "\x93\x6D"=>"\xE6\x9D\x9C", + "\x93\x6E"=>"\xE6\xB8\xA1", + "\x93\x6F"=>"\xE7\x99\xBB", + "\x93\x70"=>"\xE8\x8F\x9F", + "\x93\x71"=>"\xE8\xB3\xAD", + "\x93\x72"=>"\xE9\x80\x94", + "\x93\x73"=>"\xE9\x83\xBD", + "\x93\x74"=>"\xE9\x8D\x8D", + "\x93\x75"=>"\xE7\xA0\xA5", + "\x93\x76"=>"\xE7\xA0\xBA", + "\x93\x77"=>"\xE5\x8A\xAA", + "\x93\x78"=>"\xE5\xBA\xA6", + "\x93\x79"=>"\xE5\x9C\x9F", + "\x93\x7A"=>"\xE5\xA5\xB4", + "\x93\x7B"=>"\xE6\x80\x92", + "\x93\x7C"=>"\xE5\x80\x92", + "\x93\x7D"=>"\xE5\x85\x9A", + "\x93\x7E"=>"\xE5\x86\xAC", + "\x93\x80"=>"\xE5\x87\x8D", + "\x93\x81"=>"\xE5\x88\x80", + "\x93\x82"=>"\xE5\x94\x90", + "\x93\x83"=>"\xE5\xA1\x94", + "\x93\x84"=>"\xE5\xA1\x98", + "\x93\x85"=>"\xE5\xA5\x97", + "\x93\x86"=>"\xE5\xAE\x95", + "\x93\x87"=>"\xE5\xB3\xB6", + "\x93\x88"=>"\xE5\xB6\x8B", + "\x93\x89"=>"\xE6\x82\xBC", + "\x93\x8A"=>"\xE6\x8A\x95", + "\x93\x8B"=>"\xE6\x90\xAD", + "\x93\x8C"=>"\xE6\x9D\xB1", + "\x93\x8D"=>"\xE6\xA1\x83", + "\x93\x8E"=>"\xE6\xA2\xBC", + "\x93\x8F"=>"\xE6\xA3\x9F", + "\x93\x90"=>"\xE7\x9B\x97", + "\x93\x91"=>"\xE6\xB7\x98", + "\x93\x92"=>"\xE6\xB9\xAF", + "\x93\x93"=>"\xE6\xB6\x9B", + "\x93\x94"=>"\xE7\x81\xAF", + "\x93\x95"=>"\xE7\x87\x88", + "\x93\x96"=>"\xE5\xBD\x93", + "\x93\x97"=>"\xE7\x97\x98", + "\x93\x98"=>"\xE7\xA5\xB7", + "\x93\x99"=>"\xE7\xAD\x89", + "\x93\x9A"=>"\xE7\xAD\x94", + "\x93\x9B"=>"\xE7\xAD\x92", + "\x93\x9C"=>"\xE7\xB3\x96", + "\x93\x9D"=>"\xE7\xB5\xB1", + "\x93\x9E"=>"\xE5\x88\xB0", + "\x93\x9F"=>"\xE8\x91\xA3", + "\x93\xA0"=>"\xE8\x95\xA9", + "\x93\xA1"=>"\xE8\x97\xA4", + "\x93\xA2"=>"\xE8\xA8\x8E", + "\x93\xA3"=>"\xE8\xAC\x84", + "\x93\xA4"=>"\xE8\xB1\x86", + "\x93\xA5"=>"\xE8\xB8\x8F", + "\x93\xA6"=>"\xE9\x80\x83", + "\x93\xA7"=>"\xE9\x80\x8F", + "\x93\xA8"=>"\xE9\x90\x99", + "\x93\xA9"=>"\xE9\x99\xB6", + "\x93\xAA"=>"\xE9\xA0\xAD", + "\x93\xAB"=>"\xE9\xA8\xB0", + "\x93\xAC"=>"\xE9\x97\x98", + "\x93\xAD"=>"\xE5\x83\x8D", + "\x93\xAE"=>"\xE5\x8B\x95", + "\x93\xAF"=>"\xE5\x90\x8C", + "\x93\xB0"=>"\xE5\xA0\x82", + "\x93\xB1"=>"\xE5\xB0\x8E", + "\x93\xB2"=>"\xE6\x86\xA7", + "\x93\xB3"=>"\xE6\x92\x9E", + "\x93\xB4"=>"\xE6\xB4\x9E", + "\x93\xB5"=>"\xE7\x9E\xB3", + "\x93\xB6"=>"\xE7\xAB\xA5", + "\x93\xB7"=>"\xE8\x83\xB4", + "\x93\xB8"=>"\xE8\x90\x84", + "\x93\xB9"=>"\xE9\x81\x93", + "\x93\xBA"=>"\xE9\x8A\x85", + "\x93\xBB"=>"\xE5\xB3\xA0", + "\x93\xBC"=>"\xE9\xB4\x87", + "\x93\xBD"=>"\xE5\x8C\xBF", + "\x93\xBE"=>"\xE5\xBE\x97", + "\x93\xBF"=>"\xE5\xBE\xB3", + "\x93\xC0"=>"\xE6\xB6\x9C", + "\x93\xC1"=>"\xE7\x89\xB9", + "\x93\xC2"=>"\xE7\x9D\xA3", + "\x93\xC3"=>"\xE7\xA6\xBF", + "\x93\xC4"=>"\xE7\xAF\xA4", + "\x93\xC5"=>"\xE6\xAF\x92", + "\x93\xC6"=>"\xE7\x8B\xAC", + "\x93\xC7"=>"\xE8\xAA\xAD", + "\x93\xC8"=>"\xE6\xA0\x83", + "\x93\xC9"=>"\xE6\xA9\xA1", + "\x93\xCA"=>"\xE5\x87\xB8", + "\x93\xCB"=>"\xE7\xAA\x81", + "\x93\xCC"=>"\xE6\xA4\xB4", + "\x93\xCD"=>"\xE5\xB1\x8A", + "\x93\xCE"=>"\xE9\xB3\xB6", + "\x93\xCF"=>"\xE8\x8B\xAB", + "\x93\xD0"=>"\xE5\xAF\x85", + "\x93\xD1"=>"\xE9\x85\x89", + "\x93\xD2"=>"\xE7\x80\x9E", + "\x93\xD3"=>"\xE5\x99\xB8", + "\x93\xD4"=>"\xE5\xB1\xAF", + "\x93\xD5"=>"\xE6\x83\x87", + "\x93\xD6"=>"\xE6\x95\xA6", + "\x93\xD7"=>"\xE6\xB2\x8C", + "\x93\xD8"=>"\xE8\xB1\x9A", + "\x93\xD9"=>"\xE9\x81\x81", + "\x93\xDA"=>"\xE9\xA0\x93", + "\x93\xDB"=>"\xE5\x91\x91", + "\x93\xDC"=>"\xE6\x9B\x87", + "\x93\xDD"=>"\xE9\x88\x8D", + "\x93\xDE"=>"\xE5\xA5\x88", + "\x93\xDF"=>"\xE9\x82\xA3", + "\x93\xE0"=>"\xE5\x86\x85", + "\x93\xE1"=>"\xE4\xB9\x8D", + "\x93\xE2"=>"\xE5\x87\xAA", + "\x93\xE3"=>"\xE8\x96\x99", + "\x93\xE4"=>"\xE8\xAC\x8E", + "\x93\xE5"=>"\xE7\x81\x98", + "\x93\xE6"=>"\xE6\x8D\xBA", + "\x93\xE7"=>"\xE9\x8D\x8B", + "\x93\xE8"=>"\xE6\xA5\xA2", + "\x93\xE9"=>"\xE9\xA6\xB4", + "\x93\xEA"=>"\xE7\xB8\x84", + "\x93\xEB"=>"\xE7\x95\xB7", + "\x93\xEC"=>"\xE5\x8D\x97", + "\x93\xED"=>"\xE6\xA5\xA0", + "\x93\xEE"=>"\xE8\xBB\x9F", + "\x93\xEF"=>"\xE9\x9B\xA3", + "\x93\xF0"=>"\xE6\xB1\x9D", + "\x93\xF1"=>"\xE4\xBA\x8C", + "\x93\xF2"=>"\xE5\xB0\xBC", + "\x93\xF3"=>"\xE5\xBC\x90", + "\x93\xF4"=>"\xE8\xBF\xA9", + "\x93\xF5"=>"\xE5\x8C\x82", + "\x93\xF6"=>"\xE8\xB3\x91", + "\x93\xF7"=>"\xE8\x82\x89", + "\x93\xF8"=>"\xE8\x99\xB9", + "\x93\xF9"=>"\xE5\xBB\xBF", + "\x93\xFA"=>"\xE6\x97\xA5", + "\x93\xFB"=>"\xE4\xB9\xB3", + "\x93\xFC"=>"\xE5\x85\xA5", + "\x94\x40"=>"\xE5\xA6\x82", + "\x94\x41"=>"\xE5\xB0\xBF", + "\x94\x42"=>"\xE9\x9F\xAE", + "\x94\x43"=>"\xE4\xBB\xBB", + "\x94\x44"=>"\xE5\xA6\x8A", + "\x94\x45"=>"\xE5\xBF\x8D", + "\x94\x46"=>"\xE8\xAA\x8D", + "\x94\x47"=>"\xE6\xBF\xA1", + "\x94\x48"=>"\xE7\xA6\xB0", + "\x94\x49"=>"\xE7\xA5\xA2", + "\x94\x4A"=>"\xE5\xAF\xA7", + "\x94\x4B"=>"\xE8\x91\xB1", + "\x94\x4C"=>"\xE7\x8C\xAB", + "\x94\x4D"=>"\xE7\x86\xB1", + "\x94\x4E"=>"\xE5\xB9\xB4", + "\x94\x4F"=>"\xE5\xBF\xB5", + "\x94\x50"=>"\xE6\x8D\xBB", + "\x94\x51"=>"\xE6\x92\x9A", + "\x94\x52"=>"\xE7\x87\x83", + "\x94\x53"=>"\xE7\xB2\x98", + "\x94\x54"=>"\xE4\xB9\x83", + "\x94\x55"=>"\xE5\xBB\xBC", + "\x94\x56"=>"\xE4\xB9\x8B", + "\x94\x57"=>"\xE5\x9F\x9C", + "\x94\x58"=>"\xE5\x9A\xA2", + "\x94\x59"=>"\xE6\x82\xA9", + "\x94\x5A"=>"\xE6\xBF\x83", + "\x94\x5B"=>"\xE7\xB4\x8D", + "\x94\x5C"=>"\xE8\x83\xBD", + "\x94\x5D"=>"\xE8\x84\xB3", + "\x94\x5E"=>"\xE8\x86\xBF", + "\x94\x5F"=>"\xE8\xBE\xB2", + "\x94\x60"=>"\xE8\xA6\x97", + "\x94\x61"=>"\xE8\x9A\xA4", + "\x94\x62"=>"\xE5\xB7\xB4", + "\x94\x63"=>"\xE6\x8A\x8A", + "\x94\x64"=>"\xE6\x92\xAD", + "\x94\x65"=>"\xE8\xA6\x87", + "\x94\x66"=>"\xE6\x9D\xB7", + "\x94\x67"=>"\xE6\xB3\xA2", + "\x94\x68"=>"\xE6\xB4\xBE", + "\x94\x69"=>"\xE7\x90\xB6", + "\x94\x6A"=>"\xE7\xA0\xB4", + "\x94\x6B"=>"\xE5\xA9\x86", + "\x94\x6C"=>"\xE7\xBD\xB5", + "\x94\x6D"=>"\xE8\x8A\xAD", + "\x94\x6E"=>"\xE9\xA6\xAC", + "\x94\x6F"=>"\xE4\xBF\xB3", + "\x94\x70"=>"\xE5\xBB\x83", + "\x94\x71"=>"\xE6\x8B\x9D", + "\x94\x72"=>"\xE6\x8E\x92", + "\x94\x73"=>"\xE6\x95\x97", + "\x94\x74"=>"\xE6\x9D\xAF", + "\x94\x75"=>"\xE7\x9B\x83", + "\x94\x76"=>"\xE7\x89\x8C", + "\x94\x77"=>"\xE8\x83\x8C", + "\x94\x78"=>"\xE8\x82\xBA", + "\x94\x79"=>"\xE8\xBC\xA9", + "\x94\x7A"=>"\xE9\x85\x8D", + "\x94\x7B"=>"\xE5\x80\x8D", + "\x94\x7C"=>"\xE5\x9F\xB9", + "\x94\x7D"=>"\xE5\xAA\x92", + "\x94\x7E"=>"\xE6\xA2\x85", + "\x94\x80"=>"\xE6\xA5\xB3", + "\x94\x81"=>"\xE7\x85\xA4", + "\x94\x82"=>"\xE7\x8B\xBD", + "\x94\x83"=>"\xE8\xB2\xB7", + "\x94\x84"=>"\xE5\xA3\xB2", + "\x94\x85"=>"\xE8\xB3\xA0", + "\x94\x86"=>"\xE9\x99\xAA", + "\x94\x87"=>"\xE9\x80\x99", + "\x94\x88"=>"\xE8\x9D\xBF", + "\x94\x89"=>"\xE7\xA7\xA4", + "\x94\x8A"=>"\xE7\x9F\xA7", + "\x94\x8B"=>"\xE8\x90\xA9", + "\x94\x8C"=>"\xE4\xBC\xAF", + "\x94\x8D"=>"\xE5\x89\xA5", + "\x94\x8E"=>"\xE5\x8D\x9A", + "\x94\x8F"=>"\xE6\x8B\x8D", + "\x94\x90"=>"\xE6\x9F\x8F", + "\x94\x91"=>"\xE6\xB3\x8A", + "\x94\x92"=>"\xE7\x99\xBD", + "\x94\x93"=>"\xE7\xAE\x94", + "\x94\x94"=>"\xE7\xB2\x95", + "\x94\x95"=>"\xE8\x88\xB6", + "\x94\x96"=>"\xE8\x96\x84", + "\x94\x97"=>"\xE8\xBF\xAB", + "\x94\x98"=>"\xE6\x9B\x9D", + "\x94\x99"=>"\xE6\xBC\xA0", + "\x94\x9A"=>"\xE7\x88\x86", + "\x94\x9B"=>"\xE7\xB8\x9B", + "\x94\x9C"=>"\xE8\x8E\xAB", + "\x94\x9D"=>"\xE9\xA7\x81", + "\x94\x9E"=>"\xE9\xBA\xA6", + "\x94\x9F"=>"\xE5\x87\xBD", + "\x94\xA0"=>"\xE7\xAE\xB1", + "\x94\xA1"=>"\xE7\xA1\xB2", + "\x94\xA2"=>"\xE7\xAE\xB8", + "\x94\xA3"=>"\xE8\x82\x87", + "\x94\xA4"=>"\xE7\xAD\x88", + "\x94\xA5"=>"\xE6\xAB\xA8", + "\x94\xA6"=>"\xE5\xB9\xA1", + "\x94\xA7"=>"\xE8\x82\x8C", + "\x94\xA8"=>"\xE7\x95\x91", + "\x94\xA9"=>"\xE7\x95\xA0", + "\x94\xAA"=>"\xE5\x85\xAB", + "\x94\xAB"=>"\xE9\x89\xA2", + "\x94\xAC"=>"\xE6\xBA\x8C", + "\x94\xAD"=>"\xE7\x99\xBA", + "\x94\xAE"=>"\xE9\x86\x97", + "\x94\xAF"=>"\xE9\xAB\xAA", + "\x94\xB0"=>"\xE4\xBC\x90", + "\x94\xB1"=>"\xE7\xBD\xB0", + "\x94\xB2"=>"\xE6\x8A\x9C", + "\x94\xB3"=>"\xE7\xAD\x8F", + "\x94\xB4"=>"\xE9\x96\xA5", + "\x94\xB5"=>"\xE9\xB3\xA9", + "\x94\xB6"=>"\xE5\x99\xBA", + "\x94\xB7"=>"\xE5\xA1\x99", + "\x94\xB8"=>"\xE8\x9B\xA4", + "\x94\xB9"=>"\xE9\x9A\xBC", + "\x94\xBA"=>"\xE4\xBC\xB4", + "\x94\xBB"=>"\xE5\x88\xA4", + "\x94\xBC"=>"\xE5\x8D\x8A", + "\x94\xBD"=>"\xE5\x8F\x8D", + "\x94\xBE"=>"\xE5\x8F\x9B", + "\x94\xBF"=>"\xE5\xB8\x86", + "\x94\xC0"=>"\xE6\x90\xAC", + "\x94\xC1"=>"\xE6\x96\x91", + "\x94\xC2"=>"\xE6\x9D\xBF", + "\x94\xC3"=>"\xE6\xB0\xBE", + "\x94\xC4"=>"\xE6\xB1\x8E", + "\x94\xC5"=>"\xE7\x89\x88", + "\x94\xC6"=>"\xE7\x8A\xAF", + "\x94\xC7"=>"\xE7\x8F\xAD", + "\x94\xC8"=>"\xE7\x95\x94", + "\x94\xC9"=>"\xE7\xB9\x81", + "\x94\xCA"=>"\xE8\x88\xAC", + "\x94\xCB"=>"\xE8\x97\xA9", + "\x94\xCC"=>"\xE8\xB2\xA9", + "\x94\xCD"=>"\xE7\xAF\x84", + "\x94\xCE"=>"\xE9\x87\x86", + "\x94\xCF"=>"\xE7\x85\xA9", + "\x94\xD0"=>"\xE9\xA0\x92", + "\x94\xD1"=>"\xE9\xA3\xAF", + "\x94\xD2"=>"\xE6\x8C\xBD", + "\x94\xD3"=>"\xE6\x99\xA9", + "\x94\xD4"=>"\xE7\x95\xAA", + "\x94\xD5"=>"\xE7\x9B\xA4", + "\x94\xD6"=>"\xE7\xA3\x90", + "\x94\xD7"=>"\xE8\x95\x83", + "\x94\xD8"=>"\xE8\x9B\xAE", + "\x94\xD9"=>"\xE5\x8C\xAA", + "\x94\xDA"=>"\xE5\x8D\x91", + "\x94\xDB"=>"\xE5\x90\xA6", + "\x94\xDC"=>"\xE5\xA6\x83", + "\x94\xDD"=>"\xE5\xBA\x87", + "\x94\xDE"=>"\xE5\xBD\xBC", + "\x94\xDF"=>"\xE6\x82\xB2", + "\x94\xE0"=>"\xE6\x89\x89", + "\x94\xE1"=>"\xE6\x89\xB9", + "\x94\xE2"=>"\xE6\x8A\xAB", + "\x94\xE3"=>"\xE6\x96\x90", + "\x94\xE4"=>"\xE6\xAF\x94", + "\x94\xE5"=>"\xE6\xB3\x8C", + "\x94\xE6"=>"\xE7\x96\xB2", + "\x94\xE7"=>"\xE7\x9A\xAE", + "\x94\xE8"=>"\xE7\xA2\x91", + "\x94\xE9"=>"\xE7\xA7\x98", + "\x94\xEA"=>"\xE7\xB7\x8B", + "\x94\xEB"=>"\xE7\xBD\xB7", + "\x94\xEC"=>"\xE8\x82\xA5", + "\x94\xED"=>"\xE8\xA2\xAB", + "\x94\xEE"=>"\xE8\xAA\xB9", + "\x94\xEF"=>"\xE8\xB2\xBB", + "\x94\xF0"=>"\xE9\x81\xBF", + "\x94\xF1"=>"\xE9\x9D\x9E", + "\x94\xF2"=>"\xE9\xA3\x9B", + "\x94\xF3"=>"\xE6\xA8\x8B", + "\x94\xF4"=>"\xE7\xB0\xB8", + "\x94\xF5"=>"\xE5\x82\x99", + "\x94\xF6"=>"\xE5\xB0\xBE", + "\x94\xF7"=>"\xE5\xBE\xAE", + "\x94\xF8"=>"\xE6\x9E\x87", + "\x94\xF9"=>"\xE6\xAF\x98", + "\x94\xFA"=>"\xE7\x90\xB5", + "\x94\xFB"=>"\xE7\x9C\x89", + "\x94\xFC"=>"\xE7\xBE\x8E", + "\x95\x40"=>"\xE9\xBC\xBB", + "\x95\x41"=>"\xE6\x9F\x8A", + "\x95\x42"=>"\xE7\xA8\x97", + "\x95\x43"=>"\xE5\x8C\xB9", + "\x95\x44"=>"\xE7\x96\x8B", + "\x95\x45"=>"\xE9\xAB\xAD", + "\x95\x46"=>"\xE5\xBD\xA6", + "\x95\x47"=>"\xE8\x86\x9D", + "\x95\x48"=>"\xE8\x8F\xB1", + "\x95\x49"=>"\xE8\x82\x98", + "\x95\x4A"=>"\xE5\xBC\xBC", + "\x95\x4B"=>"\xE5\xBF\x85", + "\x95\x4C"=>"\xE7\x95\xA2", + "\x95\x4D"=>"\xE7\xAD\x86", + "\x95\x4E"=>"\xE9\x80\xBC", + "\x95\x4F"=>"\xE6\xA1\xA7", + "\x95\x50"=>"\xE5\xA7\xAB", + "\x95\x51"=>"\xE5\xAA\x9B", + "\x95\x52"=>"\xE7\xB4\x90", + "\x95\x53"=>"\xE7\x99\xBE", + "\x95\x54"=>"\xE8\xAC\xAC", + "\x95\x55"=>"\xE4\xBF\xB5", + "\x95\x56"=>"\xE5\xBD\xAA", + "\x95\x57"=>"\xE6\xA8\x99", + "\x95\x58"=>"\xE6\xB0\xB7", + "\x95\x59"=>"\xE6\xBC\x82", + "\x95\x5A"=>"\xE7\x93\xA2", + "\x95\x5B"=>"\xE7\xA5\xA8", + "\x95\x5C"=>"\xE8\xA1\xA8", + "\x95\x5D"=>"\xE8\xA9\x95", + "\x95\x5E"=>"\xE8\xB1\xB9", + "\x95\x5F"=>"\xE5\xBB\x9F", + "\x95\x60"=>"\xE6\x8F\x8F", + "\x95\x61"=>"\xE7\x97\x85", + "\x95\x62"=>"\xE7\xA7\x92", + "\x95\x63"=>"\xE8\x8B\x97", + "\x95\x64"=>"\xE9\x8C\xA8", + "\x95\x65"=>"\xE9\x8B\xB2", + "\x95\x66"=>"\xE8\x92\x9C", + "\x95\x67"=>"\xE8\x9B\xAD", + "\x95\x68"=>"\xE9\xB0\xAD", + "\x95\x69"=>"\xE5\x93\x81", + "\x95\x6A"=>"\xE5\xBD\xAC", + "\x95\x6B"=>"\xE6\x96\x8C", + "\x95\x6C"=>"\xE6\xB5\x9C", + "\x95\x6D"=>"\xE7\x80\x95", + "\x95\x6E"=>"\xE8\xB2\xA7", + "\x95\x6F"=>"\xE8\xB3\x93", + "\x95\x70"=>"\xE9\xA0\xBB", + "\x95\x71"=>"\xE6\x95\x8F", + "\x95\x72"=>"\xE7\x93\xB6", + "\x95\x73"=>"\xE4\xB8\x8D", + "\x95\x74"=>"\xE4\xBB\x98", + "\x95\x75"=>"\xE5\x9F\xA0", + "\x95\x76"=>"\xE5\xA4\xAB", + "\x95\x77"=>"\xE5\xA9\xA6", + "\x95\x78"=>"\xE5\xAF\x8C", + "\x95\x79"=>"\xE5\x86\xA8", + "\x95\x7A"=>"\xE5\xB8\x83", + "\x95\x7B"=>"\xE5\xBA\x9C", + "\x95\x7C"=>"\xE6\x80\x96", + "\x95\x7D"=>"\xE6\x89\xB6", + "\x95\x7E"=>"\xE6\x95\xB7", + "\x95\x80"=>"\xE6\x96\xA7", + "\x95\x81"=>"\xE6\x99\xAE", + "\x95\x82"=>"\xE6\xB5\xAE", + "\x95\x83"=>"\xE7\x88\xB6", + "\x95\x84"=>"\xE7\xAC\xA6", + "\x95\x85"=>"\xE8\x85\x90", + "\x95\x86"=>"\xE8\x86\x9A", + "\x95\x87"=>"\xE8\x8A\x99", + "\x95\x88"=>"\xE8\xAD\x9C", + "\x95\x89"=>"\xE8\xB2\xA0", + "\x95\x8A"=>"\xE8\xB3\xA6", + "\x95\x8B"=>"\xE8\xB5\xB4", + "\x95\x8C"=>"\xE9\x98\x9C", + "\x95\x8D"=>"\xE9\x99\x84", + "\x95\x8E"=>"\xE4\xBE\xAE", + "\x95\x8F"=>"\xE6\x92\xAB", + "\x95\x90"=>"\xE6\xAD\xA6", + "\x95\x91"=>"\xE8\x88\x9E", + "\x95\x92"=>"\xE8\x91\xA1", + "\x95\x93"=>"\xE8\x95\xAA", + "\x95\x94"=>"\xE9\x83\xA8", + "\x95\x95"=>"\xE5\xB0\x81", + "\x95\x96"=>"\xE6\xA5\x93", + "\x95\x97"=>"\xE9\xA2\xA8", + "\x95\x98"=>"\xE8\x91\xBA", + "\x95\x99"=>"\xE8\x95\x97", + "\x95\x9A"=>"\xE4\xBC\x8F", + "\x95\x9B"=>"\xE5\x89\xAF", + "\x95\x9C"=>"\xE5\xBE\xA9", + "\x95\x9D"=>"\xE5\xB9\x85", + "\x95\x9E"=>"\xE6\x9C\x8D", + "\x95\x9F"=>"\xE7\xA6\x8F", + "\x95\xA0"=>"\xE8\x85\xB9", + "\x95\xA1"=>"\xE8\xA4\x87", + "\x95\xA2"=>"\xE8\xA6\x86", + "\x95\xA3"=>"\xE6\xB7\xB5", + "\x95\xA4"=>"\xE5\xBC\x97", + "\x95\xA5"=>"\xE6\x89\x95", + "\x95\xA6"=>"\xE6\xB2\xB8", + "\x95\xA7"=>"\xE4\xBB\x8F", + "\x95\xA8"=>"\xE7\x89\xA9", + "\x95\xA9"=>"\xE9\xAE\x92", + "\x95\xAA"=>"\xE5\x88\x86", + "\x95\xAB"=>"\xE5\x90\xBB", + "\x95\xAC"=>"\xE5\x99\xB4", + "\x95\xAD"=>"\xE5\xA2\xB3", + "\x95\xAE"=>"\xE6\x86\xA4", + "\x95\xAF"=>"\xE6\x89\xAE", + "\x95\xB0"=>"\xE7\x84\x9A", + "\x95\xB1"=>"\xE5\xA5\xAE", + "\x95\xB2"=>"\xE7\xB2\x89", + "\x95\xB3"=>"\xE7\xB3\x9E", + "\x95\xB4"=>"\xE7\xB4\x9B", + "\x95\xB5"=>"\xE9\x9B\xB0", + "\x95\xB6"=>"\xE6\x96\x87", + "\x95\xB7"=>"\xE8\x81\x9E", + "\x95\xB8"=>"\xE4\xB8\x99", + "\x95\xB9"=>"\xE4\xBD\xB5", + "\x95\xBA"=>"\xE5\x85\xB5", + "\x95\xBB"=>"\xE5\xA1\x80", + "\x95\xBC"=>"\xE5\xB9\xA3", + "\x95\xBD"=>"\xE5\xB9\xB3", + "\x95\xBE"=>"\xE5\xBC\x8A", + "\x95\xBF"=>"\xE6\x9F\x84", + "\x95\xC0"=>"\xE4\xB8\xA6", + "\x95\xC1"=>"\xE8\x94\xBD", + "\x95\xC2"=>"\xE9\x96\x89", + "\x95\xC3"=>"\xE9\x99\x9B", + "\x95\xC4"=>"\xE7\xB1\xB3", + "\x95\xC5"=>"\xE9\xA0\x81", + "\x95\xC6"=>"\xE5\x83\xBB", + "\x95\xC7"=>"\xE5\xA3\x81", + "\x95\xC8"=>"\xE7\x99\x96", + "\x95\xC9"=>"\xE7\xA2\xA7", + "\x95\xCA"=>"\xE5\x88\xA5", + "\x95\xCB"=>"\xE7\x9E\xA5", + "\x95\xCC"=>"\xE8\x94\x91", + "\x95\xCD"=>"\xE7\xAE\x86", + "\x95\xCE"=>"\xE5\x81\x8F", + "\x95\xCF"=>"\xE5\xA4\x89", + "\x95\xD0"=>"\xE7\x89\x87", + "\x95\xD1"=>"\xE7\xAF\x87", + "\x95\xD2"=>"\xE7\xB7\xA8", + "\x95\xD3"=>"\xE8\xBE\xBA", + "\x95\xD4"=>"\xE8\xBF\x94", + "\x95\xD5"=>"\xE9\x81\x8D", + "\x95\xD6"=>"\xE4\xBE\xBF", + "\x95\xD7"=>"\xE5\x8B\x89", + "\x95\xD8"=>"\xE5\xA8\xA9", + "\x95\xD9"=>"\xE5\xBC\x81", + "\x95\xDA"=>"\xE9\x9E\xAD", + "\x95\xDB"=>"\xE4\xBF\x9D", + "\x95\xDC"=>"\xE8\x88\x97", + "\x95\xDD"=>"\xE9\x8B\xAA", + "\x95\xDE"=>"\xE5\x9C\x83", + "\x95\xDF"=>"\xE6\x8D\x95", + "\x95\xE0"=>"\xE6\xAD\xA9", + "\x95\xE1"=>"\xE7\x94\xAB", + "\x95\xE2"=>"\xE8\xA3\x9C", + "\x95\xE3"=>"\xE8\xBC\x94", + "\x95\xE4"=>"\xE7\xA9\x82", + "\x95\xE5"=>"\xE5\x8B\x9F", + "\x95\xE6"=>"\xE5\xA2\x93", + "\x95\xE7"=>"\xE6\x85\x95", + "\x95\xE8"=>"\xE6\x88\x8A", + "\x95\xE9"=>"\xE6\x9A\xAE", + "\x95\xEA"=>"\xE6\xAF\x8D", + "\x95\xEB"=>"\xE7\xB0\xBF", + "\x95\xEC"=>"\xE8\x8F\xA9", + "\x95\xED"=>"\xE5\x80\xA3", + "\x95\xEE"=>"\xE4\xBF\xB8", + "\x95\xEF"=>"\xE5\x8C\x85", + "\x95\xF0"=>"\xE5\x91\x86", + "\x95\xF1"=>"\xE5\xA0\xB1", + "\x95\xF2"=>"\xE5\xA5\x89", + "\x95\xF3"=>"\xE5\xAE\x9D", + "\x95\xF4"=>"\xE5\xB3\xB0", + "\x95\xF5"=>"\xE5\xB3\xAF", + "\x95\xF6"=>"\xE5\xB4\xA9", + "\x95\xF7"=>"\xE5\xBA\x96", + "\x95\xF8"=>"\xE6\x8A\xB1", + "\x95\xF9"=>"\xE6\x8D\xA7", + "\x95\xFA"=>"\xE6\x94\xBE", + "\x95\xFB"=>"\xE6\x96\xB9", + "\x95\xFC"=>"\xE6\x9C\x8B", + "\x96\x40"=>"\xE6\xB3\x95", + "\x96\x41"=>"\xE6\xB3\xA1", + "\x96\x42"=>"\xE7\x83\xB9", + "\x96\x43"=>"\xE7\xA0\xB2", + "\x96\x44"=>"\xE7\xB8\xAB", + "\x96\x45"=>"\xE8\x83\x9E", + "\x96\x46"=>"\xE8\x8A\xB3", + "\x96\x47"=>"\xE8\x90\x8C", + "\x96\x48"=>"\xE8\x93\xAC", + "\x96\x49"=>"\xE8\x9C\x82", + "\x96\x4A"=>"\xE8\xA4\x92", + "\x96\x4B"=>"\xE8\xA8\xAA", + "\x96\x4C"=>"\xE8\xB1\x8A", + "\x96\x4D"=>"\xE9\x82\xA6", + "\x96\x4E"=>"\xE9\x8B\x92", + "\x96\x4F"=>"\xE9\xA3\xBD", + "\x96\x50"=>"\xE9\xB3\xB3", + "\x96\x51"=>"\xE9\xB5\xAC", + "\x96\x52"=>"\xE4\xB9\x8F", + "\x96\x53"=>"\xE4\xBA\xA1", + "\x96\x54"=>"\xE5\x82\x8D", + "\x96\x55"=>"\xE5\x89\x96", + "\x96\x56"=>"\xE5\x9D\x8A", + "\x96\x57"=>"\xE5\xA6\xA8", + "\x96\x58"=>"\xE5\xB8\xBD", + "\x96\x59"=>"\xE5\xBF\x98", + "\x96\x5A"=>"\xE5\xBF\x99", + "\x96\x5B"=>"\xE6\x88\xBF", + "\x96\x5C"=>"\xE6\x9A\xB4", + "\x96\x5D"=>"\xE6\x9C\x9B", + "\x96\x5E"=>"\xE6\x9F\x90", + "\x96\x5F"=>"\xE6\xA3\x92", + "\x96\x60"=>"\xE5\x86\x92", + "\x96\x61"=>"\xE7\xB4\xA1", + "\x96\x62"=>"\xE8\x82\xAA", + "\x96\x63"=>"\xE8\x86\xA8", + "\x96\x64"=>"\xE8\xAC\x80", + "\x96\x65"=>"\xE8\xB2\x8C", + "\x96\x66"=>"\xE8\xB2\xBF", + "\x96\x67"=>"\xE9\x89\xBE", + "\x96\x68"=>"\xE9\x98\xB2", + "\x96\x69"=>"\xE5\x90\xA0", + "\x96\x6A"=>"\xE9\xA0\xAC", + "\x96\x6B"=>"\xE5\x8C\x97", + "\x96\x6C"=>"\xE5\x83\x95", + "\x96\x6D"=>"\xE5\x8D\x9C", + "\x96\x6E"=>"\xE5\xA2\xA8", + "\x96\x6F"=>"\xE6\x92\xB2", + "\x96\x70"=>"\xE6\x9C\xB4", + "\x96\x71"=>"\xE7\x89\xA7", + "\x96\x72"=>"\xE7\x9D\xA6", + "\x96\x73"=>"\xE7\xA9\x86", + "\x96\x74"=>"\xE9\x87\xA6", + "\x96\x75"=>"\xE5\x8B\x83", + "\x96\x76"=>"\xE6\xB2\xA1", + "\x96\x77"=>"\xE6\xAE\x86", + "\x96\x78"=>"\xE5\xA0\x80", + "\x96\x79"=>"\xE5\xB9\x8C", + "\x96\x7A"=>"\xE5\xA5\x94", + "\x96\x7B"=>"\xE6\x9C\xAC", + "\x96\x7C"=>"\xE7\xBF\xBB", + "\x96\x7D"=>"\xE5\x87\xA1", + "\x96\x7E"=>"\xE7\x9B\x86", + "\x96\x80"=>"\xE6\x91\xA9", + "\x96\x81"=>"\xE7\xA3\xA8", + "\x96\x82"=>"\xE9\xAD\x94", + "\x96\x83"=>"\xE9\xBA\xBB", + "\x96\x84"=>"\xE5\x9F\x8B", + "\x96\x85"=>"\xE5\xA6\xB9", + "\x96\x86"=>"\xE6\x98\xA7", + "\x96\x87"=>"\xE6\x9E\x9A", + "\x96\x88"=>"\xE6\xAF\x8E", + "\x96\x89"=>"\xE5\x93\xA9", + "\x96\x8A"=>"\xE6\xA7\x99", + "\x96\x8B"=>"\xE5\xB9\x95", + "\x96\x8C"=>"\xE8\x86\x9C", + "\x96\x8D"=>"\xE6\x9E\x95", + "\x96\x8E"=>"\xE9\xAE\xAA", + "\x96\x8F"=>"\xE6\x9F\xBE", + "\x96\x90"=>"\xE9\xB1\x92", + "\x96\x91"=>"\xE6\xA1\x9D", + "\x96\x92"=>"\xE4\xBA\xA6", + "\x96\x93"=>"\xE4\xBF\xA3", + "\x96\x94"=>"\xE5\x8F\x88", + "\x96\x95"=>"\xE6\x8A\xB9", + "\x96\x96"=>"\xE6\x9C\xAB", + "\x96\x97"=>"\xE6\xB2\xAB", + "\x96\x98"=>"\xE8\xBF\x84", + "\x96\x99"=>"\xE4\xBE\xAD", + "\x96\x9A"=>"\xE7\xB9\xAD", + "\x96\x9B"=>"\xE9\xBA\xBF", + "\x96\x9C"=>"\xE4\xB8\x87", + "\x96\x9D"=>"\xE6\x85\xA2", + "\x96\x9E"=>"\xE6\xBA\x80", + "\x96\x9F"=>"\xE6\xBC\xAB", + "\x96\xA0"=>"\xE8\x94\x93", + "\x96\xA1"=>"\xE5\x91\xB3", + "\x96\xA2"=>"\xE6\x9C\xAA", + "\x96\xA3"=>"\xE9\xAD\x85", + "\x96\xA4"=>"\xE5\xB7\xB3", + "\x96\xA5"=>"\xE7\xAE\x95", + "\x96\xA6"=>"\xE5\xB2\xAC", + "\x96\xA7"=>"\xE5\xAF\x86", + "\x96\xA8"=>"\xE8\x9C\x9C", + "\x96\xA9"=>"\xE6\xB9\x8A", + "\x96\xAA"=>"\xE8\x93\x91", + "\x96\xAB"=>"\xE7\xA8\x94", + "\x96\xAC"=>"\xE8\x84\x88", + "\x96\xAD"=>"\xE5\xA6\x99", + "\x96\xAE"=>"\xE7\xB2\x8D", + "\x96\xAF"=>"\xE6\xB0\x91", + "\x96\xB0"=>"\xE7\x9C\xA0", + "\x96\xB1"=>"\xE5\x8B\x99", + "\x96\xB2"=>"\xE5\xA4\xA2", + "\x96\xB3"=>"\xE7\x84\xA1", + "\x96\xB4"=>"\xE7\x89\x9F", + "\x96\xB5"=>"\xE7\x9F\x9B", + "\x96\xB6"=>"\xE9\x9C\xA7", + "\x96\xB7"=>"\xE9\xB5\xA1", + "\x96\xB8"=>"\xE6\xA4\x8B", + "\x96\xB9"=>"\xE5\xA9\xBF", + "\x96\xBA"=>"\xE5\xA8\x98", + "\x96\xBB"=>"\xE5\x86\xA5", + "\x96\xBC"=>"\xE5\x90\x8D", + "\x96\xBD"=>"\xE5\x91\xBD", + "\x96\xBE"=>"\xE6\x98\x8E", + "\x96\xBF"=>"\xE7\x9B\x9F", + "\x96\xC0"=>"\xE8\xBF\xB7", + "\x96\xC1"=>"\xE9\x8A\x98", + "\x96\xC2"=>"\xE9\xB3\xB4", + "\x96\xC3"=>"\xE5\xA7\xAA", + "\x96\xC4"=>"\xE7\x89\x9D", + "\x96\xC5"=>"\xE6\xBB\x85", + "\x96\xC6"=>"\xE5\x85\x8D", + "\x96\xC7"=>"\xE6\xA3\x89", + "\x96\xC8"=>"\xE7\xB6\xBF", + "\x96\xC9"=>"\xE7\xB7\xAC", + "\x96\xCA"=>"\xE9\x9D\xA2", + "\x96\xCB"=>"\xE9\xBA\xBA", + "\x96\xCC"=>"\xE6\x91\xB8", + "\x96\xCD"=>"\xE6\xA8\xA1", + "\x96\xCE"=>"\xE8\x8C\x82", + "\x96\xCF"=>"\xE5\xA6\x84", + "\x96\xD0"=>"\xE5\xAD\x9F", + "\x96\xD1"=>"\xE6\xAF\x9B", + "\x96\xD2"=>"\xE7\x8C\x9B", + "\x96\xD3"=>"\xE7\x9B\xB2", + "\x96\xD4"=>"\xE7\xB6\xB2", + "\x96\xD5"=>"\xE8\x80\x97", + "\x96\xD6"=>"\xE8\x92\x99", + "\x96\xD7"=>"\xE5\x84\xB2", + "\x96\xD8"=>"\xE6\x9C\xA8", + "\x96\xD9"=>"\xE9\xBB\x99", + "\x96\xDA"=>"\xE7\x9B\xAE", + "\x96\xDB"=>"\xE6\x9D\xA2", + "\x96\xDC"=>"\xE5\x8B\xBF", + "\x96\xDD"=>"\xE9\xA4\x85", + "\x96\xDE"=>"\xE5\xB0\xA4", + "\x96\xDF"=>"\xE6\x88\xBB", + "\x96\xE0"=>"\xE7\xB1\xBE", + "\x96\xE1"=>"\xE8\xB2\xB0", + "\x96\xE2"=>"\xE5\x95\x8F", + "\x96\xE3"=>"\xE6\x82\xB6", + "\x96\xE4"=>"\xE7\xB4\x8B", + "\x96\xE5"=>"\xE9\x96\x80", + "\x96\xE6"=>"\xE5\x8C\x81", + "\x96\xE7"=>"\xE4\xB9\x9F", + "\x96\xE8"=>"\xE5\x86\xB6", + "\x96\xE9"=>"\xE5\xA4\x9C", + "\x96\xEA"=>"\xE7\x88\xBA", + "\x96\xEB"=>"\xE8\x80\xB6", + "\x96\xEC"=>"\xE9\x87\x8E", + "\x96\xED"=>"\xE5\xBC\xA5", + "\x96\xEE"=>"\xE7\x9F\xA2", + "\x96\xEF"=>"\xE5\x8E\x84", + "\x96\xF0"=>"\xE5\xBD\xB9", + "\x96\xF1"=>"\xE7\xB4\x84", + "\x96\xF2"=>"\xE8\x96\xAC", + "\x96\xF3"=>"\xE8\xA8\xB3", + "\x96\xF4"=>"\xE8\xBA\x8D", + "\x96\xF5"=>"\xE9\x9D\x96", + "\x96\xF6"=>"\xE6\x9F\xB3", + "\x96\xF7"=>"\xE8\x96\xAE", + "\x96\xF8"=>"\xE9\x91\x93", + "\x96\xF9"=>"\xE6\x84\x89", + "\x96\xFA"=>"\xE6\x84\x88", + "\x96\xFB"=>"\xE6\xB2\xB9", + "\x96\xFC"=>"\xE7\x99\x92", + "\x97\x40"=>"\xE8\xAB\xAD", + "\x97\x41"=>"\xE8\xBC\xB8", + "\x97\x42"=>"\xE5\x94\xAF", + "\x97\x43"=>"\xE4\xBD\x91", + "\x97\x44"=>"\xE5\x84\xAA", + "\x97\x45"=>"\xE5\x8B\x87", + "\x97\x46"=>"\xE5\x8F\x8B", + "\x97\x47"=>"\xE5\xAE\xA5", + "\x97\x48"=>"\xE5\xB9\xBD", + "\x97\x49"=>"\xE6\x82\xA0", + "\x97\x4A"=>"\xE6\x86\x82", + "\x97\x4B"=>"\xE6\x8F\x96", + "\x97\x4C"=>"\xE6\x9C\x89", + "\x97\x4D"=>"\xE6\x9F\x9A", + "\x97\x4E"=>"\xE6\xB9\xA7", + "\x97\x4F"=>"\xE6\xB6\x8C", + "\x97\x50"=>"\xE7\x8C\xB6", + "\x97\x51"=>"\xE7\x8C\xB7", + "\x97\x52"=>"\xE7\x94\xB1", + "\x97\x53"=>"\xE7\xA5\x90", + "\x97\x54"=>"\xE8\xA3\x95", + "\x97\x55"=>"\xE8\xAA\x98", + "\x97\x56"=>"\xE9\x81\x8A", + "\x97\x57"=>"\xE9\x82\x91", + "\x97\x58"=>"\xE9\x83\xB5", + "\x97\x59"=>"\xE9\x9B\x84", + "\x97\x5A"=>"\xE8\x9E\x8D", + "\x97\x5B"=>"\xE5\xA4\x95", + "\x97\x5C"=>"\xE4\xBA\x88", + "\x97\x5D"=>"\xE4\xBD\x99", + "\x97\x5E"=>"\xE4\xB8\x8E", + "\x97\x5F"=>"\xE8\xAA\x89", + "\x97\x60"=>"\xE8\xBC\xBF", + "\x97\x61"=>"\xE9\xA0\x90", + "\x97\x62"=>"\xE5\x82\xAD", + "\x97\x63"=>"\xE5\xB9\xBC", + "\x97\x64"=>"\xE5\xA6\x96", + "\x97\x65"=>"\xE5\xAE\xB9", + "\x97\x66"=>"\xE5\xBA\xB8", + "\x97\x67"=>"\xE6\x8F\x9A", + "\x97\x68"=>"\xE6\x8F\xBA", + "\x97\x69"=>"\xE6\x93\x81", + "\x97\x6A"=>"\xE6\x9B\x9C", + "\x97\x6B"=>"\xE6\xA5\x8A", + "\x97\x6C"=>"\xE6\xA7\x98", + "\x97\x6D"=>"\xE6\xB4\x8B", + "\x97\x6E"=>"\xE6\xBA\xB6", + "\x97\x6F"=>"\xE7\x86\x94", + "\x97\x70"=>"\xE7\x94\xA8", + "\x97\x71"=>"\xE7\xAA\xAF", + "\x97\x72"=>"\xE7\xBE\x8A", + "\x97\x73"=>"\xE8\x80\x80", + "\x97\x74"=>"\xE8\x91\x89", + "\x97\x75"=>"\xE8\x93\x89", + "\x97\x76"=>"\xE8\xA6\x81", + "\x97\x77"=>"\xE8\xAC\xA1", + "\x97\x78"=>"\xE8\xB8\x8A", + "\x97\x79"=>"\xE9\x81\xA5", + "\x97\x7A"=>"\xE9\x99\xBD", + "\x97\x7B"=>"\xE9\xA4\x8A", + "\x97\x7C"=>"\xE6\x85\xBE", + "\x97\x7D"=>"\xE6\x8A\x91", + "\x97\x7E"=>"\xE6\xAC\xB2", + "\x97\x80"=>"\xE6\xB2\x83", + "\x97\x81"=>"\xE6\xB5\xB4", + "\x97\x82"=>"\xE7\xBF\x8C", + "\x97\x83"=>"\xE7\xBF\xBC", + "\x97\x84"=>"\xE6\xB7\x80", + "\x97\x85"=>"\xE7\xBE\x85", + "\x97\x86"=>"\xE8\x9E\xBA", + "\x97\x87"=>"\xE8\xA3\xB8", + "\x97\x88"=>"\xE6\x9D\xA5", + "\x97\x89"=>"\xE8\x8E\xB1", + "\x97\x8A"=>"\xE9\xA0\xBC", + "\x97\x8B"=>"\xE9\x9B\xB7", + "\x97\x8C"=>"\xE6\xB4\x9B", + "\x97\x8D"=>"\xE7\xB5\xA1", + "\x97\x8E"=>"\xE8\x90\xBD", + "\x97\x8F"=>"\xE9\x85\xAA", + "\x97\x90"=>"\xE4\xB9\xB1", + "\x97\x91"=>"\xE5\x8D\xB5", + "\x97\x92"=>"\xE5\xB5\x90", + "\x97\x93"=>"\xE6\xAC\x84", + "\x97\x94"=>"\xE6\xBF\xAB", + "\x97\x95"=>"\xE8\x97\x8D", + "\x97\x96"=>"\xE8\x98\xAD", + "\x97\x97"=>"\xE8\xA6\xA7", + "\x97\x98"=>"\xE5\x88\xA9", + "\x97\x99"=>"\xE5\x90\x8F", + "\x97\x9A"=>"\xE5\xB1\xA5", + "\x97\x9B"=>"\xE6\x9D\x8E", + "\x97\x9C"=>"\xE6\xA2\xA8", + "\x97\x9D"=>"\xE7\x90\x86", + "\x97\x9E"=>"\xE7\x92\x83", + "\x97\x9F"=>"\xE7\x97\xA2", + "\x97\xA0"=>"\xE8\xA3\x8F", + "\x97\xA1"=>"\xE8\xA3\xA1", + "\x97\xA2"=>"\xE9\x87\x8C", + "\x97\xA3"=>"\xE9\x9B\xA2", + "\x97\xA4"=>"\xE9\x99\xB8", + "\x97\xA5"=>"\xE5\xBE\x8B", + "\x97\xA6"=>"\xE7\x8E\x87", + "\x97\xA7"=>"\xE7\xAB\x8B", + "\x97\xA8"=>"\xE8\x91\x8E", + "\x97\xA9"=>"\xE6\x8E\xA0", + "\x97\xAA"=>"\xE7\x95\xA5", + "\x97\xAB"=>"\xE5\x8A\x89", + "\x97\xAC"=>"\xE6\xB5\x81", + "\x97\xAD"=>"\xE6\xBA\x9C", + "\x97\xAE"=>"\xE7\x90\x89", + "\x97\xAF"=>"\xE7\x95\x99", + "\x97\xB0"=>"\xE7\xA1\xAB", + "\x97\xB1"=>"\xE7\xB2\x92", + "\x97\xB2"=>"\xE9\x9A\x86", + "\x97\xB3"=>"\xE7\xAB\x9C", + "\x97\xB4"=>"\xE9\xBE\x8D", + "\x97\xB5"=>"\xE4\xBE\xB6", + "\x97\xB6"=>"\xE6\x85\xAE", + "\x97\xB7"=>"\xE6\x97\x85", + "\x97\xB8"=>"\xE8\x99\x9C", + "\x97\xB9"=>"\xE4\xBA\x86", + "\x97\xBA"=>"\xE4\xBA\xAE", + "\x97\xBB"=>"\xE5\x83\x9A", + "\x97\xBC"=>"\xE4\xB8\xA1", + "\x97\xBD"=>"\xE5\x87\x8C", + "\x97\xBE"=>"\xE5\xAF\xAE", + "\x97\xBF"=>"\xE6\x96\x99", + "\x97\xC0"=>"\xE6\xA2\x81", + "\x97\xC1"=>"\xE6\xB6\xBC", + "\x97\xC2"=>"\xE7\x8C\x9F", + "\x97\xC3"=>"\xE7\x99\x82", + "\x97\xC4"=>"\xE7\x9E\xAD", + "\x97\xC5"=>"\xE7\xA8\x9C", + "\x97\xC6"=>"\xE7\xB3\xA7", + "\x97\xC7"=>"\xE8\x89\xAF", + "\x97\xC8"=>"\xE8\xAB\x92", + "\x97\xC9"=>"\xE9\x81\xBC", + "\x97\xCA"=>"\xE9\x87\x8F", + "\x97\xCB"=>"\xE9\x99\xB5", + "\x97\xCC"=>"\xE9\xA0\x98", + "\x97\xCD"=>"\xE5\x8A\x9B", + "\x97\xCE"=>"\xE7\xB7\x91", + "\x97\xCF"=>"\xE5\x80\xAB", + "\x97\xD0"=>"\xE5\x8E\x98", + "\x97\xD1"=>"\xE6\x9E\x97", + "\x97\xD2"=>"\xE6\xB7\x8B", + "\x97\xD3"=>"\xE7\x87\x90", + "\x97\xD4"=>"\xE7\x90\xB3", + "\x97\xD5"=>"\xE8\x87\xA8", + "\x97\xD6"=>"\xE8\xBC\xAA", + "\x97\xD7"=>"\xE9\x9A\xA3", + "\x97\xD8"=>"\xE9\xB1\x97", + "\x97\xD9"=>"\xE9\xBA\x9F", + "\x97\xDA"=>"\xE7\x91\xA0", + "\x97\xDB"=>"\xE5\xA1\x81", + "\x97\xDC"=>"\xE6\xB6\x99", + "\x97\xDD"=>"\xE7\xB4\xAF", + "\x97\xDE"=>"\xE9\xA1\x9E", + "\x97\xDF"=>"\xE4\xBB\xA4", + "\x97\xE0"=>"\xE4\xBC\xB6", + "\x97\xE1"=>"\xE4\xBE\x8B", + "\x97\xE2"=>"\xE5\x86\xB7", + "\x97\xE3"=>"\xE5\x8A\xB1", + "\x97\xE4"=>"\xE5\xB6\xBA", + "\x97\xE5"=>"\xE6\x80\x9C", + "\x97\xE6"=>"\xE7\x8E\xB2", + "\x97\xE7"=>"\xE7\xA4\xBC", + "\x97\xE8"=>"\xE8\x8B\x93", + "\x97\xE9"=>"\xE9\x88\xB4", + "\x97\xEA"=>"\xE9\x9A\xB7", + "\x97\xEB"=>"\xE9\x9B\xB6", + "\x97\xEC"=>"\xE9\x9C\x8A", + "\x97\xED"=>"\xE9\xBA\x97", + "\x97\xEE"=>"\xE9\xBD\xA2", + "\x97\xEF"=>"\xE6\x9A\xA6", + "\x97\xF0"=>"\xE6\xAD\xB4", + "\x97\xF1"=>"\xE5\x88\x97", + "\x97\xF2"=>"\xE5\x8A\xA3", + "\x97\xF3"=>"\xE7\x83\x88", + "\x97\xF4"=>"\xE8\xA3\x82", + "\x97\xF5"=>"\xE5\xBB\x89", + "\x97\xF6"=>"\xE6\x81\x8B", + "\x97\xF7"=>"\xE6\x86\x90", + "\x97\xF8"=>"\xE6\xBC\xA3", + "\x97\xF9"=>"\xE7\x85\x89", + "\x97\xFA"=>"\xE7\xB0\xBE", + "\x97\xFB"=>"\xE7\xB7\xB4", + "\x97\xFC"=>"\xE8\x81\xAF", + "\x98\x40"=>"\xE8\x93\xAE", + "\x98\x41"=>"\xE9\x80\xA3", + "\x98\x42"=>"\xE9\x8C\xAC", + "\x98\x43"=>"\xE5\x91\x82", + "\x98\x44"=>"\xE9\xAD\xAF", + "\x98\x45"=>"\xE6\xAB\x93", + "\x98\x46"=>"\xE7\x82\x89", + "\x98\x47"=>"\xE8\xB3\x82", + "\x98\x48"=>"\xE8\xB7\xAF", + "\x98\x49"=>"\xE9\x9C\xB2", + "\x98\x4A"=>"\xE5\x8A\xB4", + "\x98\x4B"=>"\xE5\xA9\x81", + "\x98\x4C"=>"\xE5\xBB\x8A", + "\x98\x4D"=>"\xE5\xBC\x84", + "\x98\x4E"=>"\xE6\x9C\x97", + "\x98\x4F"=>"\xE6\xA5\xBC", + "\x98\x50"=>"\xE6\xA6\x94", + "\x98\x51"=>"\xE6\xB5\xAA", + "\x98\x52"=>"\xE6\xBC\x8F", + "\x98\x53"=>"\xE7\x89\xA2", + "\x98\x54"=>"\xE7\x8B\xBC", + "\x98\x55"=>"\xE7\xAF\xAD", + "\x98\x56"=>"\xE8\x80\x81", + "\x98\x57"=>"\xE8\x81\xBE", + "\x98\x58"=>"\xE8\x9D\x8B", + "\x98\x59"=>"\xE9\x83\x8E", + "\x98\x5A"=>"\xE5\x85\xAD", + "\x98\x5B"=>"\xE9\xBA\x93", + "\x98\x5C"=>"\xE7\xA6\x84", + "\x98\x5D"=>"\xE8\x82\x8B", + "\x98\x5E"=>"\xE9\x8C\xB2", + "\x98\x5F"=>"\xE8\xAB\x96", + "\x98\x60"=>"\xE5\x80\xAD", + "\x98\x61"=>"\xE5\x92\x8C", + "\x98\x62"=>"\xE8\xA9\xB1", + "\x98\x63"=>"\xE6\xAD\xAA", + "\x98\x64"=>"\xE8\xB3\x84", + "\x98\x65"=>"\xE8\x84\x87", + "\x98\x66"=>"\xE6\x83\x91", + "\x98\x67"=>"\xE6\x9E\xA0", + "\x98\x68"=>"\xE9\xB7\xB2", + "\x98\x69"=>"\xE4\xBA\x99", + "\x98\x6A"=>"\xE4\xBA\x98", + "\x98\x6B"=>"\xE9\xB0\x90", + "\x98\x6C"=>"\xE8\xA9\xAB", + "\x98\x6D"=>"\xE8\x97\x81", + "\x98\x6E"=>"\xE8\x95\xA8", + "\x98\x6F"=>"\xE6\xA4\x80", + "\x98\x70"=>"\xE6\xB9\xBE", + "\x98\x71"=>"\xE7\xA2\x97", + "\x98\x72"=>"\xE8\x85\x95", + "\x98\x9F"=>"\xE5\xBC\x8C", + "\x98\xA0"=>"\xE4\xB8\x90", + "\x98\xA1"=>"\xE4\xB8\x95", + "\x98\xA2"=>"\xE4\xB8\xAA", + "\x98\xA3"=>"\xE4\xB8\xB1", + "\x98\xA4"=>"\xE4\xB8\xB6", + "\x98\xA5"=>"\xE4\xB8\xBC", + "\x98\xA6"=>"\xE4\xB8\xBF", + "\x98\xA7"=>"\xE4\xB9\x82", + "\x98\xA8"=>"\xE4\xB9\x96", + "\x98\xA9"=>"\xE4\xB9\x98", + "\x98\xAA"=>"\xE4\xBA\x82", + "\x98\xAB"=>"\xE4\xBA\x85", + "\x98\xAC"=>"\xE8\xB1\xAB", + "\x98\xAD"=>"\xE4\xBA\x8A", + "\x98\xAE"=>"\xE8\x88\x92", + "\x98\xAF"=>"\xE5\xBC\x8D", + "\x98\xB0"=>"\xE4\xBA\x8E", + "\x98\xB1"=>"\xE4\xBA\x9E", + "\x98\xB2"=>"\xE4\xBA\x9F", + "\x98\xB3"=>"\xE4\xBA\xA0", + "\x98\xB4"=>"\xE4\xBA\xA2", + "\x98\xB5"=>"\xE4\xBA\xB0", + "\x98\xB6"=>"\xE4\xBA\xB3", + "\x98\xB7"=>"\xE4\xBA\xB6", + "\x98\xB8"=>"\xE4\xBB\x8E", + "\x98\xB9"=>"\xE4\xBB\x8D", + "\x98\xBA"=>"\xE4\xBB\x84", + "\x98\xBB"=>"\xE4\xBB\x86", + "\x98\xBC"=>"\xE4\xBB\x82", + "\x98\xBD"=>"\xE4\xBB\x97", + "\x98\xBE"=>"\xE4\xBB\x9E", + "\x98\xBF"=>"\xE4\xBB\xAD", + "\x98\xC0"=>"\xE4\xBB\x9F", + "\x98\xC1"=>"\xE4\xBB\xB7", + "\x98\xC2"=>"\xE4\xBC\x89", + "\x98\xC3"=>"\xE4\xBD\x9A", + "\x98\xC4"=>"\xE4\xBC\xB0", + "\x98\xC5"=>"\xE4\xBD\x9B", + "\x98\xC6"=>"\xE4\xBD\x9D", + "\x98\xC7"=>"\xE4\xBD\x97", + "\x98\xC8"=>"\xE4\xBD\x87", + "\x98\xC9"=>"\xE4\xBD\xB6", + "\x98\xCA"=>"\xE4\xBE\x88", + "\x98\xCB"=>"\xE4\xBE\x8F", + "\x98\xCC"=>"\xE4\xBE\x98", + "\x98\xCD"=>"\xE4\xBD\xBB", + "\x98\xCE"=>"\xE4\xBD\xA9", + "\x98\xCF"=>"\xE4\xBD\xB0", + "\x98\xD0"=>"\xE4\xBE\x91", + "\x98\xD1"=>"\xE4\xBD\xAF", + "\x98\xD2"=>"\xE4\xBE\x86", + "\x98\xD3"=>"\xE4\xBE\x96", + "\x98\xD4"=>"\xE5\x84\x98", + "\x98\xD5"=>"\xE4\xBF\x94", + "\x98\xD6"=>"\xE4\xBF\x9F", + "\x98\xD7"=>"\xE4\xBF\x8E", + "\x98\xD8"=>"\xE4\xBF\x98", + "\x98\xD9"=>"\xE4\xBF\x9B", + "\x98\xDA"=>"\xE4\xBF\x91", + "\x98\xDB"=>"\xE4\xBF\x9A", + "\x98\xDC"=>"\xE4\xBF\x90", + "\x98\xDD"=>"\xE4\xBF\xA4", + "\x98\xDE"=>"\xE4\xBF\xA5", + "\x98\xDF"=>"\xE5\x80\x9A", + "\x98\xE0"=>"\xE5\x80\xA8", + "\x98\xE1"=>"\xE5\x80\x94", + "\x98\xE2"=>"\xE5\x80\xAA", + "\x98\xE3"=>"\xE5\x80\xA5", + "\x98\xE4"=>"\xE5\x80\x85", + "\x98\xE5"=>"\xE4\xBC\x9C", + "\x98\xE6"=>"\xE4\xBF\xB6", + "\x98\xE7"=>"\xE5\x80\xA1", + "\x98\xE8"=>"\xE5\x80\xA9", + "\x98\xE9"=>"\xE5\x80\xAC", + "\x98\xEA"=>"\xE4\xBF\xBE", + "\x98\xEB"=>"\xE4\xBF\xAF", + "\x98\xEC"=>"\xE5\x80\x91", + "\x98\xED"=>"\xE5\x80\x86", + "\x98\xEE"=>"\xE5\x81\x83", + "\x98\xEF"=>"\xE5\x81\x87", + "\x98\xF0"=>"\xE6\x9C\x83", + "\x98\xF1"=>"\xE5\x81\x95", + "\x98\xF2"=>"\xE5\x81\x90", + "\x98\xF3"=>"\xE5\x81\x88", + "\x98\xF4"=>"\xE5\x81\x9A", + "\x98\xF5"=>"\xE5\x81\x96", + "\x98\xF6"=>"\xE5\x81\xAC", + "\x98\xF7"=>"\xE5\x81\xB8", + "\x98\xF8"=>"\xE5\x82\x80", + "\x98\xF9"=>"\xE5\x82\x9A", + "\x98\xFA"=>"\xE5\x82\x85", + "\x98\xFB"=>"\xE5\x82\xB4", + "\x98\xFC"=>"\xE5\x82\xB2", + "\x99\x40"=>"\xE5\x83\x89", + "\x99\x41"=>"\xE5\x83\x8A", + "\x99\x42"=>"\xE5\x82\xB3", + "\x99\x43"=>"\xE5\x83\x82", + "\x99\x44"=>"\xE5\x83\x96", + "\x99\x45"=>"\xE5\x83\x9E", + "\x99\x46"=>"\xE5\x83\xA5", + "\x99\x47"=>"\xE5\x83\xAD", + "\x99\x48"=>"\xE5\x83\xA3", + "\x99\x49"=>"\xE5\x83\xAE", + "\x99\x4A"=>"\xE5\x83\xB9", + "\x99\x4B"=>"\xE5\x83\xB5", + "\x99\x4C"=>"\xE5\x84\x89", + "\x99\x4D"=>"\xE5\x84\x81", + "\x99\x4E"=>"\xE5\x84\x82", + "\x99\x4F"=>"\xE5\x84\x96", + "\x99\x50"=>"\xE5\x84\x95", + "\x99\x51"=>"\xE5\x84\x94", + "\x99\x52"=>"\xE5\x84\x9A", + "\x99\x53"=>"\xE5\x84\xA1", + "\x99\x54"=>"\xE5\x84\xBA", + "\x99\x55"=>"\xE5\x84\xB7", + "\x99\x56"=>"\xE5\x84\xBC", + "\x99\x57"=>"\xE5\x84\xBB", + "\x99\x58"=>"\xE5\x84\xBF", + "\x99\x59"=>"\xE5\x85\x80", + "\x99\x5A"=>"\xE5\x85\x92", + "\x99\x5B"=>"\xE5\x85\x8C", + "\x99\x5C"=>"\xE5\x85\x94", + "\x99\x5D"=>"\xE5\x85\xA2", + "\x99\x5E"=>"\xE7\xAB\xB8", + "\x99\x5F"=>"\xE5\x85\xA9", + "\x99\x60"=>"\xE5\x85\xAA", + "\x99\x61"=>"\xE5\x85\xAE", + "\x99\x62"=>"\xE5\x86\x80", + "\x99\x63"=>"\xE5\x86\x82", + "\x99\x64"=>"\xE5\x9B\x98", + "\x99\x65"=>"\xE5\x86\x8C", + "\x99\x66"=>"\xE5\x86\x89", + "\x99\x67"=>"\xE5\x86\x8F", + "\x99\x68"=>"\xE5\x86\x91", + "\x99\x69"=>"\xE5\x86\x93", + "\x99\x6A"=>"\xE5\x86\x95", + "\x99\x6B"=>"\xE5\x86\x96", + "\x99\x6C"=>"\xE5\x86\xA4", + "\x99\x6D"=>"\xE5\x86\xA6", + "\x99\x6E"=>"\xE5\x86\xA2", + "\x99\x6F"=>"\xE5\x86\xA9", + "\x99\x70"=>"\xE5\x86\xAA", + "\x99\x71"=>"\xE5\x86\xAB", + "\x99\x72"=>"\xE5\x86\xB3", + "\x99\x73"=>"\xE5\x86\xB1", + "\x99\x74"=>"\xE5\x86\xB2", + "\x99\x75"=>"\xE5\x86\xB0", + "\x99\x76"=>"\xE5\x86\xB5", + "\x99\x77"=>"\xE5\x86\xBD", + "\x99\x78"=>"\xE5\x87\x85", + "\x99\x79"=>"\xE5\x87\x89", + "\x99\x7A"=>"\xE5\x87\x9B", + "\x99\x7B"=>"\xE5\x87\xA0", + "\x99\x7C"=>"\xE8\x99\x95", + "\x99\x7D"=>"\xE5\x87\xA9", + "\x99\x7E"=>"\xE5\x87\xAD", + "\x99\x80"=>"\xE5\x87\xB0", + "\x99\x81"=>"\xE5\x87\xB5", + "\x99\x82"=>"\xE5\x87\xBE", + "\x99\x83"=>"\xE5\x88\x84", + "\x99\x84"=>"\xE5\x88\x8B", + "\x99\x85"=>"\xE5\x88\x94", + "\x99\x86"=>"\xE5\x88\x8E", + "\x99\x87"=>"\xE5\x88\xA7", + "\x99\x88"=>"\xE5\x88\xAA", + "\x99\x89"=>"\xE5\x88\xAE", + "\x99\x8A"=>"\xE5\x88\xB3", + "\x99\x8B"=>"\xE5\x88\xB9", + "\x99\x8C"=>"\xE5\x89\x8F", + "\x99\x8D"=>"\xE5\x89\x84", + "\x99\x8E"=>"\xE5\x89\x8B", + "\x99\x8F"=>"\xE5\x89\x8C", + "\x99\x90"=>"\xE5\x89\x9E", + "\x99\x91"=>"\xE5\x89\x94", + "\x99\x92"=>"\xE5\x89\xAA", + "\x99\x93"=>"\xE5\x89\xB4", + "\x99\x94"=>"\xE5\x89\xA9", + "\x99\x95"=>"\xE5\x89\xB3", + "\x99\x96"=>"\xE5\x89\xBF", + "\x99\x97"=>"\xE5\x89\xBD", + "\x99\x98"=>"\xE5\x8A\x8D", + "\x99\x99"=>"\xE5\x8A\x94", + "\x99\x9A"=>"\xE5\x8A\x92", + "\x99\x9B"=>"\xE5\x89\xB1", + "\x99\x9C"=>"\xE5\x8A\x88", + "\x99\x9D"=>"\xE5\x8A\x91", + "\x99\x9E"=>"\xE8\xBE\xA8", + "\x99\x9F"=>"\xE8\xBE\xA7", + "\x99\xA0"=>"\xE5\x8A\xAC", + "\x99\xA1"=>"\xE5\x8A\xAD", + "\x99\xA2"=>"\xE5\x8A\xBC", + "\x99\xA3"=>"\xE5\x8A\xB5", + "\x99\xA4"=>"\xE5\x8B\x81", + "\x99\xA5"=>"\xE5\x8B\x8D", + "\x99\xA6"=>"\xE5\x8B\x97", + "\x99\xA7"=>"\xE5\x8B\x9E", + "\x99\xA8"=>"\xE5\x8B\xA3", + "\x99\xA9"=>"\xE5\x8B\xA6", + "\x99\xAA"=>"\xE9\xA3\xAD", + "\x99\xAB"=>"\xE5\x8B\xA0", + "\x99\xAC"=>"\xE5\x8B\xB3", + "\x99\xAD"=>"\xE5\x8B\xB5", + "\x99\xAE"=>"\xE5\x8B\xB8", + "\x99\xAF"=>"\xE5\x8B\xB9", + "\x99\xB0"=>"\xE5\x8C\x86", + "\x99\xB1"=>"\xE5\x8C\x88", + "\x99\xB2"=>"\xE7\x94\xB8", + "\x99\xB3"=>"\xE5\x8C\x8D", + "\x99\xB4"=>"\xE5\x8C\x90", + "\x99\xB5"=>"\xE5\x8C\x8F", + "\x99\xB6"=>"\xE5\x8C\x95", + "\x99\xB7"=>"\xE5\x8C\x9A", + "\x99\xB8"=>"\xE5\x8C\xA3", + "\x99\xB9"=>"\xE5\x8C\xAF", + "\x99\xBA"=>"\xE5\x8C\xB1", + "\x99\xBB"=>"\xE5\x8C\xB3", + "\x99\xBC"=>"\xE5\x8C\xB8", + "\x99\xBD"=>"\xE5\x8D\x80", + "\x99\xBE"=>"\xE5\x8D\x86", + "\x99\xBF"=>"\xE5\x8D\x85", + "\x99\xC0"=>"\xE4\xB8\x97", + "\x99\xC1"=>"\xE5\x8D\x89", + "\x99\xC2"=>"\xE5\x8D\x8D", + "\x99\xC3"=>"\xE5\x87\x96", + "\x99\xC4"=>"\xE5\x8D\x9E", + "\x99\xC5"=>"\xE5\x8D\xA9", + "\x99\xC6"=>"\xE5\x8D\xAE", + "\x99\xC7"=>"\xE5\xA4\x98", + "\x99\xC8"=>"\xE5\x8D\xBB", + "\x99\xC9"=>"\xE5\x8D\xB7", + "\x99\xCA"=>"\xE5\x8E\x82", + "\x99\xCB"=>"\xE5\x8E\x96", + "\x99\xCC"=>"\xE5\x8E\xA0", + "\x99\xCD"=>"\xE5\x8E\xA6", + "\x99\xCE"=>"\xE5\x8E\xA5", + "\x99\xCF"=>"\xE5\x8E\xAE", + "\x99\xD0"=>"\xE5\x8E\xB0", + "\x99\xD1"=>"\xE5\x8E\xB6", + "\x99\xD2"=>"\xE5\x8F\x83", + "\x99\xD3"=>"\xE7\xB0\x92", + "\x99\xD4"=>"\xE9\x9B\x99", + "\x99\xD5"=>"\xE5\x8F\x9F", + "\x99\xD6"=>"\xE6\x9B\xBC", + "\x99\xD7"=>"\xE7\x87\xAE", + "\x99\xD8"=>"\xE5\x8F\xAE", + "\x99\xD9"=>"\xE5\x8F\xA8", + "\x99\xDA"=>"\xE5\x8F\xAD", + "\x99\xDB"=>"\xE5\x8F\xBA", + "\x99\xDC"=>"\xE5\x90\x81", + "\x99\xDD"=>"\xE5\x90\xBD", + "\x99\xDE"=>"\xE5\x91\x80", + "\x99\xDF"=>"\xE5\x90\xAC", + "\x99\xE0"=>"\xE5\x90\xAD", + "\x99\xE1"=>"\xE5\x90\xBC", + "\x99\xE2"=>"\xE5\x90\xAE", + "\x99\xE3"=>"\xE5\x90\xB6", + "\x99\xE4"=>"\xE5\x90\xA9", + "\x99\xE5"=>"\xE5\x90\x9D", + "\x99\xE6"=>"\xE5\x91\x8E", + "\x99\xE7"=>"\xE5\x92\x8F", + "\x99\xE8"=>"\xE5\x91\xB5", + "\x99\xE9"=>"\xE5\x92\x8E", + "\x99\xEA"=>"\xE5\x91\x9F", + "\x99\xEB"=>"\xE5\x91\xB1", + "\x99\xEC"=>"\xE5\x91\xB7", + "\x99\xED"=>"\xE5\x91\xB0", + "\x99\xEE"=>"\xE5\x92\x92", + "\x99\xEF"=>"\xE5\x91\xBB", + "\x99\xF0"=>"\xE5\x92\x80", + "\x99\xF1"=>"\xE5\x91\xB6", + "\x99\xF2"=>"\xE5\x92\x84", + "\x99\xF3"=>"\xE5\x92\x90", + "\x99\xF4"=>"\xE5\x92\x86", + "\x99\xF5"=>"\xE5\x93\x87", + "\x99\xF6"=>"\xE5\x92\xA2", + "\x99\xF7"=>"\xE5\x92\xB8", + "\x99\xF8"=>"\xE5\x92\xA5", + "\x99\xF9"=>"\xE5\x92\xAC", + "\x99\xFA"=>"\xE5\x93\x84", + "\x99\xFB"=>"\xE5\x93\x88", + "\x99\xFC"=>"\xE5\x92\xA8", + "\x9A\x40"=>"\xE5\x92\xAB", + "\x9A\x41"=>"\xE5\x93\x82", + "\x9A\x42"=>"\xE5\x92\xA4", + "\x9A\x43"=>"\xE5\x92\xBE", + "\x9A\x44"=>"\xE5\x92\xBC", + "\x9A\x45"=>"\xE5\x93\x98", + "\x9A\x46"=>"\xE5\x93\xA5", + "\x9A\x47"=>"\xE5\x93\xA6", + "\x9A\x48"=>"\xE5\x94\x8F", + "\x9A\x49"=>"\xE5\x94\x94", + "\x9A\x4A"=>"\xE5\x93\xBD", + "\x9A\x4B"=>"\xE5\x93\xAE", + "\x9A\x4C"=>"\xE5\x93\xAD", + "\x9A\x4D"=>"\xE5\x93\xBA", + "\x9A\x4E"=>"\xE5\x93\xA2", + "\x9A\x4F"=>"\xE5\x94\xB9", + "\x9A\x50"=>"\xE5\x95\x80", + "\x9A\x51"=>"\xE5\x95\xA3", + "\x9A\x52"=>"\xE5\x95\x8C", + "\x9A\x53"=>"\xE5\x94\xAE", + "\x9A\x54"=>"\xE5\x95\x9C", + "\x9A\x55"=>"\xE5\x95\x85", + "\x9A\x56"=>"\xE5\x95\x96", + "\x9A\x57"=>"\xE5\x95\x97", + "\x9A\x58"=>"\xE5\x94\xB8", + "\x9A\x59"=>"\xE5\x94\xB3", + "\x9A\x5A"=>"\xE5\x95\x9D", + "\x9A\x5B"=>"\xE5\x96\x99", + "\x9A\x5C"=>"\xE5\x96\x80", + "\x9A\x5D"=>"\xE5\x92\xAF", + "\x9A\x5E"=>"\xE5\x96\x8A", + "\x9A\x5F"=>"\xE5\x96\x9F", + "\x9A\x60"=>"\xE5\x95\xBB", + "\x9A\x61"=>"\xE5\x95\xBE", + "\x9A\x62"=>"\xE5\x96\x98", + "\x9A\x63"=>"\xE5\x96\x9E", + "\x9A\x64"=>"\xE5\x96\xAE", + "\x9A\x65"=>"\xE5\x95\xBC", + "\x9A\x66"=>"\xE5\x96\x83", + "\x9A\x67"=>"\xE5\x96\xA9", + "\x9A\x68"=>"\xE5\x96\x87", + "\x9A\x69"=>"\xE5\x96\xA8", + "\x9A\x6A"=>"\xE5\x97\x9A", + "\x9A\x6B"=>"\xE5\x97\x85", + "\x9A\x6C"=>"\xE5\x97\x9F", + "\x9A\x6D"=>"\xE5\x97\x84", + "\x9A\x6E"=>"\xE5\x97\x9C", + "\x9A\x6F"=>"\xE5\x97\xA4", + "\x9A\x70"=>"\xE5\x97\x94", + "\x9A\x71"=>"\xE5\x98\x94", + "\x9A\x72"=>"\xE5\x97\xB7", + "\x9A\x73"=>"\xE5\x98\x96", + "\x9A\x74"=>"\xE5\x97\xBE", + "\x9A\x75"=>"\xE5\x97\xBD", + "\x9A\x76"=>"\xE5\x98\x9B", + "\x9A\x77"=>"\xE5\x97\xB9", + "\x9A\x78"=>"\xE5\x99\x8E", + "\x9A\x79"=>"\xE5\x99\x90", + "\x9A\x7A"=>"\xE7\x87\x9F", + "\x9A\x7B"=>"\xE5\x98\xB4", + "\x9A\x7C"=>"\xE5\x98\xB6", + "\x9A\x7D"=>"\xE5\x98\xB2", + "\x9A\x7E"=>"\xE5\x98\xB8", + "\x9A\x80"=>"\xE5\x99\xAB", + "\x9A\x81"=>"\xE5\x99\xA4", + "\x9A\x82"=>"\xE5\x98\xAF", + "\x9A\x83"=>"\xE5\x99\xAC", + "\x9A\x84"=>"\xE5\x99\xAA", + "\x9A\x85"=>"\xE5\x9A\x86", + "\x9A\x86"=>"\xE5\x9A\x80", + "\x9A\x87"=>"\xE5\x9A\x8A", + "\x9A\x88"=>"\xE5\x9A\xA0", + "\x9A\x89"=>"\xE5\x9A\x94", + "\x9A\x8A"=>"\xE5\x9A\x8F", + "\x9A\x8B"=>"\xE5\x9A\xA5", + "\x9A\x8C"=>"\xE5\x9A\xAE", + "\x9A\x8D"=>"\xE5\x9A\xB6", + "\x9A\x8E"=>"\xE5\x9A\xB4", + "\x9A\x8F"=>"\xE5\x9B\x82", + "\x9A\x90"=>"\xE5\x9A\xBC", + "\x9A\x91"=>"\xE5\x9B\x81", + "\x9A\x92"=>"\xE5\x9B\x83", + "\x9A\x93"=>"\xE5\x9B\x80", + "\x9A\x94"=>"\xE5\x9B\x88", + "\x9A\x95"=>"\xE5\x9B\x8E", + "\x9A\x96"=>"\xE5\x9B\x91", + "\x9A\x97"=>"\xE5\x9B\x93", + "\x9A\x98"=>"\xE5\x9B\x97", + "\x9A\x99"=>"\xE5\x9B\xAE", + "\x9A\x9A"=>"\xE5\x9B\xB9", + "\x9A\x9B"=>"\xE5\x9C\x80", + "\x9A\x9C"=>"\xE5\x9B\xBF", + "\x9A\x9D"=>"\xE5\x9C\x84", + "\x9A\x9E"=>"\xE5\x9C\x89", + "\x9A\x9F"=>"\xE5\x9C\x88", + "\x9A\xA0"=>"\xE5\x9C\x8B", + "\x9A\xA1"=>"\xE5\x9C\x8D", + "\x9A\xA2"=>"\xE5\x9C\x93", + "\x9A\xA3"=>"\xE5\x9C\x98", + "\x9A\xA4"=>"\xE5\x9C\x96", + "\x9A\xA5"=>"\xE5\x97\x87", + "\x9A\xA6"=>"\xE5\x9C\x9C", + "\x9A\xA7"=>"\xE5\x9C\xA6", + "\x9A\xA8"=>"\xE5\x9C\xB7", + "\x9A\xA9"=>"\xE5\x9C\xB8", + "\x9A\xAA"=>"\xE5\x9D\x8E", + "\x9A\xAB"=>"\xE5\x9C\xBB", + "\x9A\xAC"=>"\xE5\x9D\x80", + "\x9A\xAD"=>"\xE5\x9D\x8F", + "\x9A\xAE"=>"\xE5\x9D\xA9", + "\x9A\xAF"=>"\xE5\x9F\x80", + "\x9A\xB0"=>"\xE5\x9E\x88", + "\x9A\xB1"=>"\xE5\x9D\xA1", + "\x9A\xB2"=>"\xE5\x9D\xBF", + "\x9A\xB3"=>"\xE5\x9E\x89", + "\x9A\xB4"=>"\xE5\x9E\x93", + "\x9A\xB5"=>"\xE5\x9E\xA0", + "\x9A\xB6"=>"\xE5\x9E\xB3", + "\x9A\xB7"=>"\xE5\x9E\xA4", + "\x9A\xB8"=>"\xE5\x9E\xAA", + "\x9A\xB9"=>"\xE5\x9E\xB0", + "\x9A\xBA"=>"\xE5\x9F\x83", + "\x9A\xBB"=>"\xE5\x9F\x86", + "\x9A\xBC"=>"\xE5\x9F\x94", + "\x9A\xBD"=>"\xE5\x9F\x92", + "\x9A\xBE"=>"\xE5\x9F\x93", + "\x9A\xBF"=>"\xE5\xA0\x8A", + "\x9A\xC0"=>"\xE5\x9F\x96", + "\x9A\xC1"=>"\xE5\x9F\xA3", + "\x9A\xC2"=>"\xE5\xA0\x8B", + "\x9A\xC3"=>"\xE5\xA0\x99", + "\x9A\xC4"=>"\xE5\xA0\x9D", + "\x9A\xC5"=>"\xE5\xA1\xB2", + "\x9A\xC6"=>"\xE5\xA0\xA1", + "\x9A\xC7"=>"\xE5\xA1\xA2", + "\x9A\xC8"=>"\xE5\xA1\x8B", + "\x9A\xC9"=>"\xE5\xA1\xB0", + "\x9A\xCA"=>"\xE6\xAF\x80", + "\x9A\xCB"=>"\xE5\xA1\x92", + "\x9A\xCC"=>"\xE5\xA0\xBD", + "\x9A\xCD"=>"\xE5\xA1\xB9", + "\x9A\xCE"=>"\xE5\xA2\x85", + "\x9A\xCF"=>"\xE5\xA2\xB9", + "\x9A\xD0"=>"\xE5\xA2\x9F", + "\x9A\xD1"=>"\xE5\xA2\xAB", + "\x9A\xD2"=>"\xE5\xA2\xBA", + "\x9A\xD3"=>"\xE5\xA3\x9E", + "\x9A\xD4"=>"\xE5\xA2\xBB", + "\x9A\xD5"=>"\xE5\xA2\xB8", + "\x9A\xD6"=>"\xE5\xA2\xAE", + "\x9A\xD7"=>"\xE5\xA3\x85", + "\x9A\xD8"=>"\xE5\xA3\x93", + "\x9A\xD9"=>"\xE5\xA3\x91", + "\x9A\xDA"=>"\xE5\xA3\x97", + "\x9A\xDB"=>"\xE5\xA3\x99", + "\x9A\xDC"=>"\xE5\xA3\x98", + "\x9A\xDD"=>"\xE5\xA3\xA5", + "\x9A\xDE"=>"\xE5\xA3\x9C", + "\x9A\xDF"=>"\xE5\xA3\xA4", + "\x9A\xE0"=>"\xE5\xA3\x9F", + "\x9A\xE1"=>"\xE5\xA3\xAF", + "\x9A\xE2"=>"\xE5\xA3\xBA", + "\x9A\xE3"=>"\xE5\xA3\xB9", + "\x9A\xE4"=>"\xE5\xA3\xBB", + "\x9A\xE5"=>"\xE5\xA3\xBC", + "\x9A\xE6"=>"\xE5\xA3\xBD", + "\x9A\xE7"=>"\xE5\xA4\x82", + "\x9A\xE8"=>"\xE5\xA4\x8A", + "\x9A\xE9"=>"\xE5\xA4\x90", + "\x9A\xEA"=>"\xE5\xA4\x9B", + "\x9A\xEB"=>"\xE6\xA2\xA6", + "\x9A\xEC"=>"\xE5\xA4\xA5", + "\x9A\xED"=>"\xE5\xA4\xAC", + "\x9A\xEE"=>"\xE5\xA4\xAD", + "\x9A\xEF"=>"\xE5\xA4\xB2", + "\x9A\xF0"=>"\xE5\xA4\xB8", + "\x9A\xF1"=>"\xE5\xA4\xBE", + "\x9A\xF2"=>"\xE7\xAB\x92", + "\x9A\xF3"=>"\xE5\xA5\x95", + "\x9A\xF4"=>"\xE5\xA5\x90", + "\x9A\xF5"=>"\xE5\xA5\x8E", + "\x9A\xF6"=>"\xE5\xA5\x9A", + "\x9A\xF7"=>"\xE5\xA5\x98", + "\x9A\xF8"=>"\xE5\xA5\xA2", + "\x9A\xF9"=>"\xE5\xA5\xA0", + "\x9A\xFA"=>"\xE5\xA5\xA7", + "\x9A\xFB"=>"\xE5\xA5\xAC", + "\x9A\xFC"=>"\xE5\xA5\xA9", + "\x9B\x40"=>"\xE5\xA5\xB8", + "\x9B\x41"=>"\xE5\xA6\x81", + "\x9B\x42"=>"\xE5\xA6\x9D", + "\x9B\x43"=>"\xE4\xBD\x9E", + "\x9B\x44"=>"\xE4\xBE\xAB", + "\x9B\x45"=>"\xE5\xA6\xA3", + "\x9B\x46"=>"\xE5\xA6\xB2", + "\x9B\x47"=>"\xE5\xA7\x86", + "\x9B\x48"=>"\xE5\xA7\xA8", + "\x9B\x49"=>"\xE5\xA7\x9C", + "\x9B\x4A"=>"\xE5\xA6\x8D", + "\x9B\x4B"=>"\xE5\xA7\x99", + "\x9B\x4C"=>"\xE5\xA7\x9A", + "\x9B\x4D"=>"\xE5\xA8\xA5", + "\x9B\x4E"=>"\xE5\xA8\x9F", + "\x9B\x4F"=>"\xE5\xA8\x91", + "\x9B\x50"=>"\xE5\xA8\x9C", + "\x9B\x51"=>"\xE5\xA8\x89", + "\x9B\x52"=>"\xE5\xA8\x9A", + "\x9B\x53"=>"\xE5\xA9\x80", + "\x9B\x54"=>"\xE5\xA9\xAC", + "\x9B\x55"=>"\xE5\xA9\x89", + "\x9B\x56"=>"\xE5\xA8\xB5", + "\x9B\x57"=>"\xE5\xA8\xB6", + "\x9B\x58"=>"\xE5\xA9\xA2", + "\x9B\x59"=>"\xE5\xA9\xAA", + "\x9B\x5A"=>"\xE5\xAA\x9A", + "\x9B\x5B"=>"\xE5\xAA\xBC", + "\x9B\x5C"=>"\xE5\xAA\xBE", + "\x9B\x5D"=>"\xE5\xAB\x8B", + "\x9B\x5E"=>"\xE5\xAB\x82", + "\x9B\x5F"=>"\xE5\xAA\xBD", + "\x9B\x60"=>"\xE5\xAB\xA3", + "\x9B\x61"=>"\xE5\xAB\x97", + "\x9B\x62"=>"\xE5\xAB\xA6", + "\x9B\x63"=>"\xE5\xAB\xA9", + "\x9B\x64"=>"\xE5\xAB\x96", + "\x9B\x65"=>"\xE5\xAB\xBA", + "\x9B\x66"=>"\xE5\xAB\xBB", + "\x9B\x67"=>"\xE5\xAC\x8C", + "\x9B\x68"=>"\xE5\xAC\x8B", + "\x9B\x69"=>"\xE5\xAC\x96", + "\x9B\x6A"=>"\xE5\xAC\xB2", + "\x9B\x6B"=>"\xE5\xAB\x90", + "\x9B\x6C"=>"\xE5\xAC\xAA", + "\x9B\x6D"=>"\xE5\xAC\xB6", + "\x9B\x6E"=>"\xE5\xAC\xBE", + "\x9B\x6F"=>"\xE5\xAD\x83", + "\x9B\x70"=>"\xE5\xAD\x85", + "\x9B\x71"=>"\xE5\xAD\x80", + "\x9B\x72"=>"\xE5\xAD\x91", + "\x9B\x73"=>"\xE5\xAD\x95", + "\x9B\x74"=>"\xE5\xAD\x9A", + "\x9B\x75"=>"\xE5\xAD\x9B", + "\x9B\x76"=>"\xE5\xAD\xA5", + "\x9B\x77"=>"\xE5\xAD\xA9", + "\x9B\x78"=>"\xE5\xAD\xB0", + "\x9B\x79"=>"\xE5\xAD\xB3", + "\x9B\x7A"=>"\xE5\xAD\xB5", + "\x9B\x7B"=>"\xE5\xAD\xB8", + "\x9B\x7C"=>"\xE6\x96\x88", + "\x9B\x7D"=>"\xE5\xAD\xBA", + "\x9B\x7E"=>"\xE5\xAE\x80", + "\x9B\x80"=>"\xE5\xAE\x83", + "\x9B\x81"=>"\xE5\xAE\xA6", + "\x9B\x82"=>"\xE5\xAE\xB8", + "\x9B\x83"=>"\xE5\xAF\x83", + "\x9B\x84"=>"\xE5\xAF\x87", + "\x9B\x85"=>"\xE5\xAF\x89", + "\x9B\x86"=>"\xE5\xAF\x94", + "\x9B\x87"=>"\xE5\xAF\x90", + "\x9B\x88"=>"\xE5\xAF\xA4", + "\x9B\x89"=>"\xE5\xAF\xA6", + "\x9B\x8A"=>"\xE5\xAF\xA2", + "\x9B\x8B"=>"\xE5\xAF\x9E", + "\x9B\x8C"=>"\xE5\xAF\xA5", + "\x9B\x8D"=>"\xE5\xAF\xAB", + "\x9B\x8E"=>"\xE5\xAF\xB0", + "\x9B\x8F"=>"\xE5\xAF\xB6", + "\x9B\x90"=>"\xE5\xAF\xB3", + "\x9B\x91"=>"\xE5\xB0\x85", + "\x9B\x92"=>"\xE5\xB0\x87", + "\x9B\x93"=>"\xE5\xB0\x88", + "\x9B\x94"=>"\xE5\xB0\x8D", + "\x9B\x95"=>"\xE5\xB0\x93", + "\x9B\x96"=>"\xE5\xB0\xA0", + "\x9B\x97"=>"\xE5\xB0\xA2", + "\x9B\x98"=>"\xE5\xB0\xA8", + "\x9B\x99"=>"\xE5\xB0\xB8", + "\x9B\x9A"=>"\xE5\xB0\xB9", + "\x9B\x9B"=>"\xE5\xB1\x81", + "\x9B\x9C"=>"\xE5\xB1\x86", + "\x9B\x9D"=>"\xE5\xB1\x8E", + "\x9B\x9E"=>"\xE5\xB1\x93", + "\x9B\x9F"=>"\xE5\xB1\x90", + "\x9B\xA0"=>"\xE5\xB1\x8F", + "\x9B\xA1"=>"\xE5\xAD\xB1", + "\x9B\xA2"=>"\xE5\xB1\xAC", + "\x9B\xA3"=>"\xE5\xB1\xAE", + "\x9B\xA4"=>"\xE4\xB9\xA2", + "\x9B\xA5"=>"\xE5\xB1\xB6", + "\x9B\xA6"=>"\xE5\xB1\xB9", + "\x9B\xA7"=>"\xE5\xB2\x8C", + "\x9B\xA8"=>"\xE5\xB2\x91", + "\x9B\xA9"=>"\xE5\xB2\x94", + "\x9B\xAA"=>"\xE5\xA6\x9B", + "\x9B\xAB"=>"\xE5\xB2\xAB", + "\x9B\xAC"=>"\xE5\xB2\xBB", + "\x9B\xAD"=>"\xE5\xB2\xB6", + "\x9B\xAE"=>"\xE5\xB2\xBC", + "\x9B\xAF"=>"\xE5\xB2\xB7", + "\x9B\xB0"=>"\xE5\xB3\x85", + "\x9B\xB1"=>"\xE5\xB2\xBE", + "\x9B\xB2"=>"\xE5\xB3\x87", + "\x9B\xB3"=>"\xE5\xB3\x99", + "\x9B\xB4"=>"\xE5\xB3\xA9", + "\x9B\xB5"=>"\xE5\xB3\xBD", + "\x9B\xB6"=>"\xE5\xB3\xBA", + "\x9B\xB7"=>"\xE5\xB3\xAD", + "\x9B\xB8"=>"\xE5\xB6\x8C", + "\x9B\xB9"=>"\xE5\xB3\xAA", + "\x9B\xBA"=>"\xE5\xB4\x8B", + "\x9B\xBB"=>"\xE5\xB4\x95", + "\x9B\xBC"=>"\xE5\xB4\x97", + "\x9B\xBD"=>"\xE5\xB5\x9C", + "\x9B\xBE"=>"\xE5\xB4\x9F", + "\x9B\xBF"=>"\xE5\xB4\x9B", + "\x9B\xC0"=>"\xE5\xB4\x91", + "\x9B\xC1"=>"\xE5\xB4\x94", + "\x9B\xC2"=>"\xE5\xB4\xA2", + "\x9B\xC3"=>"\xE5\xB4\x9A", + "\x9B\xC4"=>"\xE5\xB4\x99", + "\x9B\xC5"=>"\xE5\xB4\x98", + "\x9B\xC6"=>"\xE5\xB5\x8C", + "\x9B\xC7"=>"\xE5\xB5\x92", + "\x9B\xC8"=>"\xE5\xB5\x8E", + "\x9B\xC9"=>"\xE5\xB5\x8B", + "\x9B\xCA"=>"\xE5\xB5\xAC", + "\x9B\xCB"=>"\xE5\xB5\xB3", + "\x9B\xCC"=>"\xE5\xB5\xB6", + "\x9B\xCD"=>"\xE5\xB6\x87", + "\x9B\xCE"=>"\xE5\xB6\x84", + "\x9B\xCF"=>"\xE5\xB6\x82", + "\x9B\xD0"=>"\xE5\xB6\xA2", + "\x9B\xD1"=>"\xE5\xB6\x9D", + "\x9B\xD2"=>"\xE5\xB6\xAC", + "\x9B\xD3"=>"\xE5\xB6\xAE", + "\x9B\xD4"=>"\xE5\xB6\xBD", + "\x9B\xD5"=>"\xE5\xB6\x90", + "\x9B\xD6"=>"\xE5\xB6\xB7", + "\x9B\xD7"=>"\xE5\xB6\xBC", + "\x9B\xD8"=>"\xE5\xB7\x89", + "\x9B\xD9"=>"\xE5\xB7\x8D", + "\x9B\xDA"=>"\xE5\xB7\x93", + "\x9B\xDB"=>"\xE5\xB7\x92", + "\x9B\xDC"=>"\xE5\xB7\x96", + "\x9B\xDD"=>"\xE5\xB7\x9B", + "\x9B\xDE"=>"\xE5\xB7\xAB", + "\x9B\xDF"=>"\xE5\xB7\xB2", + "\x9B\xE0"=>"\xE5\xB7\xB5", + "\x9B\xE1"=>"\xE5\xB8\x8B", + "\x9B\xE2"=>"\xE5\xB8\x9A", + "\x9B\xE3"=>"\xE5\xB8\x99", + "\x9B\xE4"=>"\xE5\xB8\x91", + "\x9B\xE5"=>"\xE5\xB8\x9B", + "\x9B\xE6"=>"\xE5\xB8\xB6", + "\x9B\xE7"=>"\xE5\xB8\xB7", + "\x9B\xE8"=>"\xE5\xB9\x84", + "\x9B\xE9"=>"\xE5\xB9\x83", + "\x9B\xEA"=>"\xE5\xB9\x80", + "\x9B\xEB"=>"\xE5\xB9\x8E", + "\x9B\xEC"=>"\xE5\xB9\x97", + "\x9B\xED"=>"\xE5\xB9\x94", + "\x9B\xEE"=>"\xE5\xB9\x9F", + "\x9B\xEF"=>"\xE5\xB9\xA2", + "\x9B\xF0"=>"\xE5\xB9\xA4", + "\x9B\xF1"=>"\xE5\xB9\x87", + "\x9B\xF2"=>"\xE5\xB9\xB5", + "\x9B\xF3"=>"\xE5\xB9\xB6", + "\x9B\xF4"=>"\xE5\xB9\xBA", + "\x9B\xF5"=>"\xE9\xBA\xBC", + "\x9B\xF6"=>"\xE5\xB9\xBF", + "\x9B\xF7"=>"\xE5\xBA\xA0", + "\x9B\xF8"=>"\xE5\xBB\x81", + "\x9B\xF9"=>"\xE5\xBB\x82", + "\x9B\xFA"=>"\xE5\xBB\x88", + "\x9B\xFB"=>"\xE5\xBB\x90", + "\x9B\xFC"=>"\xE5\xBB\x8F", + "\x9C\x40"=>"\xE5\xBB\x96", + "\x9C\x41"=>"\xE5\xBB\xA3", + "\x9C\x42"=>"\xE5\xBB\x9D", + "\x9C\x43"=>"\xE5\xBB\x9A", + "\x9C\x44"=>"\xE5\xBB\x9B", + "\x9C\x45"=>"\xE5\xBB\xA2", + "\x9C\x46"=>"\xE5\xBB\xA1", + "\x9C\x47"=>"\xE5\xBB\xA8", + "\x9C\x48"=>"\xE5\xBB\xA9", + "\x9C\x49"=>"\xE5\xBB\xAC", + "\x9C\x4A"=>"\xE5\xBB\xB1", + "\x9C\x4B"=>"\xE5\xBB\xB3", + "\x9C\x4C"=>"\xE5\xBB\xB0", + "\x9C\x4D"=>"\xE5\xBB\xB4", + "\x9C\x4E"=>"\xE5\xBB\xB8", + "\x9C\x4F"=>"\xE5\xBB\xBE", + "\x9C\x50"=>"\xE5\xBC\x83", + "\x9C\x51"=>"\xE5\xBC\x89", + "\x9C\x52"=>"\xE5\xBD\x9D", + "\x9C\x53"=>"\xE5\xBD\x9C", + "\x9C\x54"=>"\xE5\xBC\x8B", + "\x9C\x55"=>"\xE5\xBC\x91", + "\x9C\x56"=>"\xE5\xBC\x96", + "\x9C\x57"=>"\xE5\xBC\xA9", + "\x9C\x58"=>"\xE5\xBC\xAD", + "\x9C\x59"=>"\xE5\xBC\xB8", + "\x9C\x5A"=>"\xE5\xBD\x81", + "\x9C\x5B"=>"\xE5\xBD\x88", + "\x9C\x5C"=>"\xE5\xBD\x8C", + "\x9C\x5D"=>"\xE5\xBD\x8E", + "\x9C\x5E"=>"\xE5\xBC\xAF", + "\x9C\x5F"=>"\xE5\xBD\x91", + "\x9C\x60"=>"\xE5\xBD\x96", + "\x9C\x61"=>"\xE5\xBD\x97", + "\x9C\x62"=>"\xE5\xBD\x99", + "\x9C\x63"=>"\xE5\xBD\xA1", + "\x9C\x64"=>"\xE5\xBD\xAD", + "\x9C\x65"=>"\xE5\xBD\xB3", + "\x9C\x66"=>"\xE5\xBD\xB7", + "\x9C\x67"=>"\xE5\xBE\x83", + "\x9C\x68"=>"\xE5\xBE\x82", + "\x9C\x69"=>"\xE5\xBD\xBF", + "\x9C\x6A"=>"\xE5\xBE\x8A", + "\x9C\x6B"=>"\xE5\xBE\x88", + "\x9C\x6C"=>"\xE5\xBE\x91", + "\x9C\x6D"=>"\xE5\xBE\x87", + "\x9C\x6E"=>"\xE5\xBE\x9E", + "\x9C\x6F"=>"\xE5\xBE\x99", + "\x9C\x70"=>"\xE5\xBE\x98", + "\x9C\x71"=>"\xE5\xBE\xA0", + "\x9C\x72"=>"\xE5\xBE\xA8", + "\x9C\x73"=>"\xE5\xBE\xAD", + "\x9C\x74"=>"\xE5\xBE\xBC", + "\x9C\x75"=>"\xE5\xBF\x96", + "\x9C\x76"=>"\xE5\xBF\xBB", + "\x9C\x77"=>"\xE5\xBF\xA4", + "\x9C\x78"=>"\xE5\xBF\xB8", + "\x9C\x79"=>"\xE5\xBF\xB1", + "\x9C\x7A"=>"\xE5\xBF\x9D", + "\x9C\x7B"=>"\xE6\x82\xB3", + "\x9C\x7C"=>"\xE5\xBF\xBF", + "\x9C\x7D"=>"\xE6\x80\xA1", + "\x9C\x7E"=>"\xE6\x81\xA0", + "\x9C\x80"=>"\xE6\x80\x99", + "\x9C\x81"=>"\xE6\x80\x90", + "\x9C\x82"=>"\xE6\x80\xA9", + "\x9C\x83"=>"\xE6\x80\x8E", + "\x9C\x84"=>"\xE6\x80\xB1", + "\x9C\x85"=>"\xE6\x80\x9B", + "\x9C\x86"=>"\xE6\x80\x95", + "\x9C\x87"=>"\xE6\x80\xAB", + "\x9C\x88"=>"\xE6\x80\xA6", + "\x9C\x89"=>"\xE6\x80\x8F", + "\x9C\x8A"=>"\xE6\x80\xBA", + "\x9C\x8B"=>"\xE6\x81\x9A", + "\x9C\x8C"=>"\xE6\x81\x81", + "\x9C\x8D"=>"\xE6\x81\xAA", + "\x9C\x8E"=>"\xE6\x81\xB7", + "\x9C\x8F"=>"\xE6\x81\x9F", + "\x9C\x90"=>"\xE6\x81\x8A", + "\x9C\x91"=>"\xE6\x81\x86", + "\x9C\x92"=>"\xE6\x81\x8D", + "\x9C\x93"=>"\xE6\x81\xA3", + "\x9C\x94"=>"\xE6\x81\x83", + "\x9C\x95"=>"\xE6\x81\xA4", + "\x9C\x96"=>"\xE6\x81\x82", + "\x9C\x97"=>"\xE6\x81\xAC", + "\x9C\x98"=>"\xE6\x81\xAB", + "\x9C\x99"=>"\xE6\x81\x99", + "\x9C\x9A"=>"\xE6\x82\x81", + "\x9C\x9B"=>"\xE6\x82\x8D", + "\x9C\x9C"=>"\xE6\x83\xA7", + "\x9C\x9D"=>"\xE6\x82\x83", + "\x9C\x9E"=>"\xE6\x82\x9A", + "\x9C\x9F"=>"\xE6\x82\x84", + "\x9C\xA0"=>"\xE6\x82\x9B", + "\x9C\xA1"=>"\xE6\x82\x96", + "\x9C\xA2"=>"\xE6\x82\x97", + "\x9C\xA3"=>"\xE6\x82\x92", + "\x9C\xA4"=>"\xE6\x82\xA7", + "\x9C\xA5"=>"\xE6\x82\x8B", + "\x9C\xA6"=>"\xE6\x83\xA1", + "\x9C\xA7"=>"\xE6\x82\xB8", + "\x9C\xA8"=>"\xE6\x83\xA0", + "\x9C\xA9"=>"\xE6\x83\x93", + "\x9C\xAA"=>"\xE6\x82\xB4", + "\x9C\xAB"=>"\xE5\xBF\xB0", + "\x9C\xAC"=>"\xE6\x82\xBD", + "\x9C\xAD"=>"\xE6\x83\x86", + "\x9C\xAE"=>"\xE6\x82\xB5", + "\x9C\xAF"=>"\xE6\x83\x98", + "\x9C\xB0"=>"\xE6\x85\x8D", + "\x9C\xB1"=>"\xE6\x84\x95", + "\x9C\xB2"=>"\xE6\x84\x86", + "\x9C\xB3"=>"\xE6\x83\xB6", + "\x9C\xB4"=>"\xE6\x83\xB7", + "\x9C\xB5"=>"\xE6\x84\x80", + "\x9C\xB6"=>"\xE6\x83\xB4", + "\x9C\xB7"=>"\xE6\x83\xBA", + "\x9C\xB8"=>"\xE6\x84\x83", + "\x9C\xB9"=>"\xE6\x84\xA1", + "\x9C\xBA"=>"\xE6\x83\xBB", + "\x9C\xBB"=>"\xE6\x83\xB1", + "\x9C\xBC"=>"\xE6\x84\x8D", + "\x9C\xBD"=>"\xE6\x84\x8E", + "\x9C\xBE"=>"\xE6\x85\x87", + "\x9C\xBF"=>"\xE6\x84\xBE", + "\x9C\xC0"=>"\xE6\x84\xA8", + "\x9C\xC1"=>"\xE6\x84\xA7", + "\x9C\xC2"=>"\xE6\x85\x8A", + "\x9C\xC3"=>"\xE6\x84\xBF", + "\x9C\xC4"=>"\xE6\x84\xBC", + "\x9C\xC5"=>"\xE6\x84\xAC", + "\x9C\xC6"=>"\xE6\x84\xB4", + "\x9C\xC7"=>"\xE6\x84\xBD", + "\x9C\xC8"=>"\xE6\x85\x82", + "\x9C\xC9"=>"\xE6\x85\x84", + "\x9C\xCA"=>"\xE6\x85\xB3", + "\x9C\xCB"=>"\xE6\x85\xB7", + "\x9C\xCC"=>"\xE6\x85\x98", + "\x9C\xCD"=>"\xE6\x85\x99", + "\x9C\xCE"=>"\xE6\x85\x9A", + "\x9C\xCF"=>"\xE6\x85\xAB", + "\x9C\xD0"=>"\xE6\x85\xB4", + "\x9C\xD1"=>"\xE6\x85\xAF", + "\x9C\xD2"=>"\xE6\x85\xA5", + "\x9C\xD3"=>"\xE6\x85\xB1", + "\x9C\xD4"=>"\xE6\x85\x9F", + "\x9C\xD5"=>"\xE6\x85\x9D", + "\x9C\xD6"=>"\xE6\x85\x93", + "\x9C\xD7"=>"\xE6\x85\xB5", + "\x9C\xD8"=>"\xE6\x86\x99", + "\x9C\xD9"=>"\xE6\x86\x96", + "\x9C\xDA"=>"\xE6\x86\x87", + "\x9C\xDB"=>"\xE6\x86\xAC", + "\x9C\xDC"=>"\xE6\x86\x94", + "\x9C\xDD"=>"\xE6\x86\x9A", + "\x9C\xDE"=>"\xE6\x86\x8A", + "\x9C\xDF"=>"\xE6\x86\x91", + "\x9C\xE0"=>"\xE6\x86\xAB", + "\x9C\xE1"=>"\xE6\x86\xAE", + "\x9C\xE2"=>"\xE6\x87\x8C", + "\x9C\xE3"=>"\xE6\x87\x8A", + "\x9C\xE4"=>"\xE6\x87\x89", + "\x9C\xE5"=>"\xE6\x87\xB7", + "\x9C\xE6"=>"\xE6\x87\x88", + "\x9C\xE7"=>"\xE6\x87\x83", + "\x9C\xE8"=>"\xE6\x87\x86", + "\x9C\xE9"=>"\xE6\x86\xBA", + "\x9C\xEA"=>"\xE6\x87\x8B", + "\x9C\xEB"=>"\xE7\xBD\xB9", + "\x9C\xEC"=>"\xE6\x87\x8D", + "\x9C\xED"=>"\xE6\x87\xA6", + "\x9C\xEE"=>"\xE6\x87\xA3", + "\x9C\xEF"=>"\xE6\x87\xB6", + "\x9C\xF0"=>"\xE6\x87\xBA", + "\x9C\xF1"=>"\xE6\x87\xB4", + "\x9C\xF2"=>"\xE6\x87\xBF", + "\x9C\xF3"=>"\xE6\x87\xBD", + "\x9C\xF4"=>"\xE6\x87\xBC", + "\x9C\xF5"=>"\xE6\x87\xBE", + "\x9C\xF6"=>"\xE6\x88\x80", + "\x9C\xF7"=>"\xE6\x88\x88", + "\x9C\xF8"=>"\xE6\x88\x89", + "\x9C\xF9"=>"\xE6\x88\x8D", + "\x9C\xFA"=>"\xE6\x88\x8C", + "\x9C\xFB"=>"\xE6\x88\x94", + "\x9C\xFC"=>"\xE6\x88\x9B", + "\x9D\x40"=>"\xE6\x88\x9E", + "\x9D\x41"=>"\xE6\x88\xA1", + "\x9D\x42"=>"\xE6\x88\xAA", + "\x9D\x43"=>"\xE6\x88\xAE", + "\x9D\x44"=>"\xE6\x88\xB0", + "\x9D\x45"=>"\xE6\x88\xB2", + "\x9D\x46"=>"\xE6\x88\xB3", + "\x9D\x47"=>"\xE6\x89\x81", + "\x9D\x48"=>"\xE6\x89\x8E", + "\x9D\x49"=>"\xE6\x89\x9E", + "\x9D\x4A"=>"\xE6\x89\xA3", + "\x9D\x4B"=>"\xE6\x89\x9B", + "\x9D\x4C"=>"\xE6\x89\xA0", + "\x9D\x4D"=>"\xE6\x89\xA8", + "\x9D\x4E"=>"\xE6\x89\xBC", + "\x9D\x4F"=>"\xE6\x8A\x82", + "\x9D\x50"=>"\xE6\x8A\x89", + "\x9D\x51"=>"\xE6\x89\xBE", + "\x9D\x52"=>"\xE6\x8A\x92", + "\x9D\x53"=>"\xE6\x8A\x93", + "\x9D\x54"=>"\xE6\x8A\x96", + "\x9D\x55"=>"\xE6\x8B\x94", + "\x9D\x56"=>"\xE6\x8A\x83", + "\x9D\x57"=>"\xE6\x8A\x94", + "\x9D\x58"=>"\xE6\x8B\x97", + "\x9D\x59"=>"\xE6\x8B\x91", + "\x9D\x5A"=>"\xE6\x8A\xBB", + "\x9D\x5B"=>"\xE6\x8B\x8F", + "\x9D\x5C"=>"\xE6\x8B\xBF", + "\x9D\x5D"=>"\xE6\x8B\x86", + "\x9D\x5E"=>"\xE6\x93\x94", + "\x9D\x5F"=>"\xE6\x8B\x88", + "\x9D\x60"=>"\xE6\x8B\x9C", + "\x9D\x61"=>"\xE6\x8B\x8C", + "\x9D\x62"=>"\xE6\x8B\x8A", + "\x9D\x63"=>"\xE6\x8B\x82", + "\x9D\x64"=>"\xE6\x8B\x87", + "\x9D\x65"=>"\xE6\x8A\x9B", + "\x9D\x66"=>"\xE6\x8B\x89", + "\x9D\x67"=>"\xE6\x8C\x8C", + "\x9D\x68"=>"\xE6\x8B\xAE", + "\x9D\x69"=>"\xE6\x8B\xB1", + "\x9D\x6A"=>"\xE6\x8C\xA7", + "\x9D\x6B"=>"\xE6\x8C\x82", + "\x9D\x6C"=>"\xE6\x8C\x88", + "\x9D\x6D"=>"\xE6\x8B\xAF", + "\x9D\x6E"=>"\xE6\x8B\xB5", + "\x9D\x6F"=>"\xE6\x8D\x90", + "\x9D\x70"=>"\xE6\x8C\xBE", + "\x9D\x71"=>"\xE6\x8D\x8D", + "\x9D\x72"=>"\xE6\x90\x9C", + "\x9D\x73"=>"\xE6\x8D\x8F", + "\x9D\x74"=>"\xE6\x8E\x96", + "\x9D\x75"=>"\xE6\x8E\x8E", + "\x9D\x76"=>"\xE6\x8E\x80", + "\x9D\x77"=>"\xE6\x8E\xAB", + "\x9D\x78"=>"\xE6\x8D\xB6", + "\x9D\x79"=>"\xE6\x8E\xA3", + "\x9D\x7A"=>"\xE6\x8E\x8F", + "\x9D\x7B"=>"\xE6\x8E\x89", + "\x9D\x7C"=>"\xE6\x8E\x9F", + "\x9D\x7D"=>"\xE6\x8E\xB5", + "\x9D\x7E"=>"\xE6\x8D\xAB", + "\x9D\x80"=>"\xE6\x8D\xA9", + "\x9D\x81"=>"\xE6\x8E\xBE", + "\x9D\x82"=>"\xE6\x8F\xA9", + "\x9D\x83"=>"\xE6\x8F\x80", + "\x9D\x84"=>"\xE6\x8F\x86", + "\x9D\x85"=>"\xE6\x8F\xA3", + "\x9D\x86"=>"\xE6\x8F\x89", + "\x9D\x87"=>"\xE6\x8F\x92", + "\x9D\x88"=>"\xE6\x8F\xB6", + "\x9D\x89"=>"\xE6\x8F\x84", + "\x9D\x8A"=>"\xE6\x90\x96", + "\x9D\x8B"=>"\xE6\x90\xB4", + "\x9D\x8C"=>"\xE6\x90\x86", + "\x9D\x8D"=>"\xE6\x90\x93", + "\x9D\x8E"=>"\xE6\x90\xA6", + "\x9D\x8F"=>"\xE6\x90\xB6", + "\x9D\x90"=>"\xE6\x94\x9D", + "\x9D\x91"=>"\xE6\x90\x97", + "\x9D\x92"=>"\xE6\x90\xA8", + "\x9D\x93"=>"\xE6\x90\x8F", + "\x9D\x94"=>"\xE6\x91\xA7", + "\x9D\x95"=>"\xE6\x91\xAF", + "\x9D\x96"=>"\xE6\x91\xB6", + "\x9D\x97"=>"\xE6\x91\x8E", + "\x9D\x98"=>"\xE6\x94\xAA", + "\x9D\x99"=>"\xE6\x92\x95", + "\x9D\x9A"=>"\xE6\x92\x93", + "\x9D\x9B"=>"\xE6\x92\xA5", + "\x9D\x9C"=>"\xE6\x92\xA9", + "\x9D\x9D"=>"\xE6\x92\x88", + "\x9D\x9E"=>"\xE6\x92\xBC", + "\x9D\x9F"=>"\xE6\x93\x9A", + "\x9D\xA0"=>"\xE6\x93\x92", + "\x9D\xA1"=>"\xE6\x93\x85", + "\x9D\xA2"=>"\xE6\x93\x87", + "\x9D\xA3"=>"\xE6\x92\xBB", + "\x9D\xA4"=>"\xE6\x93\x98", + "\x9D\xA5"=>"\xE6\x93\x82", + "\x9D\xA6"=>"\xE6\x93\xB1", + "\x9D\xA7"=>"\xE6\x93\xA7", + "\x9D\xA8"=>"\xE8\x88\x89", + "\x9D\xA9"=>"\xE6\x93\xA0", + "\x9D\xAA"=>"\xE6\x93\xA1", + "\x9D\xAB"=>"\xE6\x8A\xAC", + "\x9D\xAC"=>"\xE6\x93\xA3", + "\x9D\xAD"=>"\xE6\x93\xAF", + "\x9D\xAE"=>"\xE6\x94\xAC", + "\x9D\xAF"=>"\xE6\x93\xB6", + "\x9D\xB0"=>"\xE6\x93\xB4", + "\x9D\xB1"=>"\xE6\x93\xB2", + "\x9D\xB2"=>"\xE6\x93\xBA", + "\x9D\xB3"=>"\xE6\x94\x80", + "\x9D\xB4"=>"\xE6\x93\xBD", + "\x9D\xB5"=>"\xE6\x94\x98", + "\x9D\xB6"=>"\xE6\x94\x9C", + "\x9D\xB7"=>"\xE6\x94\x85", + "\x9D\xB8"=>"\xE6\x94\xA4", + "\x9D\xB9"=>"\xE6\x94\xA3", + "\x9D\xBA"=>"\xE6\x94\xAB", + "\x9D\xBB"=>"\xE6\x94\xB4", + "\x9D\xBC"=>"\xE6\x94\xB5", + "\x9D\xBD"=>"\xE6\x94\xB7", + "\x9D\xBE"=>"\xE6\x94\xB6", + "\x9D\xBF"=>"\xE6\x94\xB8", + "\x9D\xC0"=>"\xE7\x95\x8B", + "\x9D\xC1"=>"\xE6\x95\x88", + "\x9D\xC2"=>"\xE6\x95\x96", + "\x9D\xC3"=>"\xE6\x95\x95", + "\x9D\xC4"=>"\xE6\x95\x8D", + "\x9D\xC5"=>"\xE6\x95\x98", + "\x9D\xC6"=>"\xE6\x95\x9E", + "\x9D\xC7"=>"\xE6\x95\x9D", + "\x9D\xC8"=>"\xE6\x95\xB2", + "\x9D\xC9"=>"\xE6\x95\xB8", + "\x9D\xCA"=>"\xE6\x96\x82", + "\x9D\xCB"=>"\xE6\x96\x83", + "\x9D\xCC"=>"\xE8\xAE\x8A", + "\x9D\xCD"=>"\xE6\x96\x9B", + "\x9D\xCE"=>"\xE6\x96\x9F", + "\x9D\xCF"=>"\xE6\x96\xAB", + "\x9D\xD0"=>"\xE6\x96\xB7", + "\x9D\xD1"=>"\xE6\x97\x83", + "\x9D\xD2"=>"\xE6\x97\x86", + "\x9D\xD3"=>"\xE6\x97\x81", + "\x9D\xD4"=>"\xE6\x97\x84", + "\x9D\xD5"=>"\xE6\x97\x8C", + "\x9D\xD6"=>"\xE6\x97\x92", + "\x9D\xD7"=>"\xE6\x97\x9B", + "\x9D\xD8"=>"\xE6\x97\x99", + "\x9D\xD9"=>"\xE6\x97\xA0", + "\x9D\xDA"=>"\xE6\x97\xA1", + "\x9D\xDB"=>"\xE6\x97\xB1", + "\x9D\xDC"=>"\xE6\x9D\xB2", + "\x9D\xDD"=>"\xE6\x98\x8A", + "\x9D\xDE"=>"\xE6\x98\x83", + "\x9D\xDF"=>"\xE6\x97\xBB", + "\x9D\xE0"=>"\xE6\x9D\xB3", + "\x9D\xE1"=>"\xE6\x98\xB5", + "\x9D\xE2"=>"\xE6\x98\xB6", + "\x9D\xE3"=>"\xE6\x98\xB4", + "\x9D\xE4"=>"\xE6\x98\x9C", + "\x9D\xE5"=>"\xE6\x99\x8F", + "\x9D\xE6"=>"\xE6\x99\x84", + "\x9D\xE7"=>"\xE6\x99\x89", + "\x9D\xE8"=>"\xE6\x99\x81", + "\x9D\xE9"=>"\xE6\x99\x9E", + "\x9D\xEA"=>"\xE6\x99\x9D", + "\x9D\xEB"=>"\xE6\x99\xA4", + "\x9D\xEC"=>"\xE6\x99\xA7", + "\x9D\xED"=>"\xE6\x99\xA8", + "\x9D\xEE"=>"\xE6\x99\x9F", + "\x9D\xEF"=>"\xE6\x99\xA2", + "\x9D\xF0"=>"\xE6\x99\xB0", + "\x9D\xF1"=>"\xE6\x9A\x83", + "\x9D\xF2"=>"\xE6\x9A\x88", + "\x9D\xF3"=>"\xE6\x9A\x8E", + "\x9D\xF4"=>"\xE6\x9A\x89", + "\x9D\xF5"=>"\xE6\x9A\x84", + "\x9D\xF6"=>"\xE6\x9A\x98", + "\x9D\xF7"=>"\xE6\x9A\x9D", + "\x9D\xF8"=>"\xE6\x9B\x81", + "\x9D\xF9"=>"\xE6\x9A\xB9", + "\x9D\xFA"=>"\xE6\x9B\x89", + "\x9D\xFB"=>"\xE6\x9A\xBE", + "\x9D\xFC"=>"\xE6\x9A\xBC", + "\x9E\x40"=>"\xE6\x9B\x84", + "\x9E\x41"=>"\xE6\x9A\xB8", + "\x9E\x42"=>"\xE6\x9B\x96", + "\x9E\x43"=>"\xE6\x9B\x9A", + "\x9E\x44"=>"\xE6\x9B\xA0", + "\x9E\x45"=>"\xE6\x98\xBF", + "\x9E\x46"=>"\xE6\x9B\xA6", + "\x9E\x47"=>"\xE6\x9B\xA9", + "\x9E\x48"=>"\xE6\x9B\xB0", + "\x9E\x49"=>"\xE6\x9B\xB5", + "\x9E\x4A"=>"\xE6\x9B\xB7", + "\x9E\x4B"=>"\xE6\x9C\x8F", + "\x9E\x4C"=>"\xE6\x9C\x96", + "\x9E\x4D"=>"\xE6\x9C\x9E", + "\x9E\x4E"=>"\xE6\x9C\xA6", + "\x9E\x4F"=>"\xE6\x9C\xA7", + "\x9E\x50"=>"\xE9\x9C\xB8", + "\x9E\x51"=>"\xE6\x9C\xAE", + "\x9E\x52"=>"\xE6\x9C\xBF", + "\x9E\x53"=>"\xE6\x9C\xB6", + "\x9E\x54"=>"\xE6\x9D\x81", + "\x9E\x55"=>"\xE6\x9C\xB8", + "\x9E\x56"=>"\xE6\x9C\xB7", + "\x9E\x57"=>"\xE6\x9D\x86", + "\x9E\x58"=>"\xE6\x9D\x9E", + "\x9E\x59"=>"\xE6\x9D\xA0", + "\x9E\x5A"=>"\xE6\x9D\x99", + "\x9E\x5B"=>"\xE6\x9D\xA3", + "\x9E\x5C"=>"\xE6\x9D\xA4", + "\x9E\x5D"=>"\xE6\x9E\x89", + "\x9E\x5E"=>"\xE6\x9D\xB0", + "\x9E\x5F"=>"\xE6\x9E\xA9", + "\x9E\x60"=>"\xE6\x9D\xBC", + "\x9E\x61"=>"\xE6\x9D\xAA", + "\x9E\x62"=>"\xE6\x9E\x8C", + "\x9E\x63"=>"\xE6\x9E\x8B", + "\x9E\x64"=>"\xE6\x9E\xA6", + "\x9E\x65"=>"\xE6\x9E\xA1", + "\x9E\x66"=>"\xE6\x9E\x85", + "\x9E\x67"=>"\xE6\x9E\xB7", + "\x9E\x68"=>"\xE6\x9F\xAF", + "\x9E\x69"=>"\xE6\x9E\xB4", + "\x9E\x6A"=>"\xE6\x9F\xAC", + "\x9E\x6B"=>"\xE6\x9E\xB3", + "\x9E\x6C"=>"\xE6\x9F\xA9", + "\x9E\x6D"=>"\xE6\x9E\xB8", + "\x9E\x6E"=>"\xE6\x9F\xA4", + "\x9E\x6F"=>"\xE6\x9F\x9E", + "\x9E\x70"=>"\xE6\x9F\x9D", + "\x9E\x71"=>"\xE6\x9F\xA2", + "\x9E\x72"=>"\xE6\x9F\xAE", + "\x9E\x73"=>"\xE6\x9E\xB9", + "\x9E\x74"=>"\xE6\x9F\x8E", + "\x9E\x75"=>"\xE6\x9F\x86", + "\x9E\x76"=>"\xE6\x9F\xA7", + "\x9E\x77"=>"\xE6\xAA\x9C", + "\x9E\x78"=>"\xE6\xA0\x9E", + "\x9E\x79"=>"\xE6\xA1\x86", + "\x9E\x7A"=>"\xE6\xA0\xA9", + "\x9E\x7B"=>"\xE6\xA1\x80", + "\x9E\x7C"=>"\xE6\xA1\x8D", + "\x9E\x7D"=>"\xE6\xA0\xB2", + "\x9E\x7E"=>"\xE6\xA1\x8E", + "\x9E\x80"=>"\xE6\xA2\xB3", + "\x9E\x81"=>"\xE6\xA0\xAB", + "\x9E\x82"=>"\xE6\xA1\x99", + "\x9E\x83"=>"\xE6\xA1\xA3", + "\x9E\x84"=>"\xE6\xA1\xB7", + "\x9E\x85"=>"\xE6\xA1\xBF", + "\x9E\x86"=>"\xE6\xA2\x9F", + "\x9E\x87"=>"\xE6\xA2\x8F", + "\x9E\x88"=>"\xE6\xA2\xAD", + "\x9E\x89"=>"\xE6\xA2\x94", + "\x9E\x8A"=>"\xE6\xA2\x9D", + "\x9E\x8B"=>"\xE6\xA2\x9B", + "\x9E\x8C"=>"\xE6\xA2\x83", + "\x9E\x8D"=>"\xE6\xAA\xAE", + "\x9E\x8E"=>"\xE6\xA2\xB9", + "\x9E\x8F"=>"\xE6\xA1\xB4", + "\x9E\x90"=>"\xE6\xA2\xB5", + "\x9E\x91"=>"\xE6\xA2\xA0", + "\x9E\x92"=>"\xE6\xA2\xBA", + "\x9E\x93"=>"\xE6\xA4\x8F", + "\x9E\x94"=>"\xE6\xA2\x8D", + "\x9E\x95"=>"\xE6\xA1\xBE", + "\x9E\x96"=>"\xE6\xA4\x81", + "\x9E\x97"=>"\xE6\xA3\x8A", + "\x9E\x98"=>"\xE6\xA4\x88", + "\x9E\x99"=>"\xE6\xA3\x98", + "\x9E\x9A"=>"\xE6\xA4\xA2", + "\x9E\x9B"=>"\xE6\xA4\xA6", + "\x9E\x9C"=>"\xE6\xA3\xA1", + "\x9E\x9D"=>"\xE6\xA4\x8C", + "\x9E\x9E"=>"\xE6\xA3\x8D", + "\x9E\x9F"=>"\xE6\xA3\x94", + "\x9E\xA0"=>"\xE6\xA3\xA7", + "\x9E\xA1"=>"\xE6\xA3\x95", + "\x9E\xA2"=>"\xE6\xA4\xB6", + "\x9E\xA3"=>"\xE6\xA4\x92", + "\x9E\xA4"=>"\xE6\xA4\x84", + "\x9E\xA5"=>"\xE6\xA3\x97", + "\x9E\xA6"=>"\xE6\xA3\xA3", + "\x9E\xA7"=>"\xE6\xA4\xA5", + "\x9E\xA8"=>"\xE6\xA3\xB9", + "\x9E\xA9"=>"\xE6\xA3\xA0", + "\x9E\xAA"=>"\xE6\xA3\xAF", + "\x9E\xAB"=>"\xE6\xA4\xA8", + "\x9E\xAC"=>"\xE6\xA4\xAA", + "\x9E\xAD"=>"\xE6\xA4\x9A", + "\x9E\xAE"=>"\xE6\xA4\xA3", + "\x9E\xAF"=>"\xE6\xA4\xA1", + "\x9E\xB0"=>"\xE6\xA3\x86", + "\x9E\xB1"=>"\xE6\xA5\xB9", + "\x9E\xB2"=>"\xE6\xA5\xB7", + "\x9E\xB3"=>"\xE6\xA5\x9C", + "\x9E\xB4"=>"\xE6\xA5\xB8", + "\x9E\xB5"=>"\xE6\xA5\xAB", + "\x9E\xB6"=>"\xE6\xA5\x94", + "\x9E\xB7"=>"\xE6\xA5\xBE", + "\x9E\xB8"=>"\xE6\xA5\xAE", + "\x9E\xB9"=>"\xE6\xA4\xB9", + "\x9E\xBA"=>"\xE6\xA5\xB4", + "\x9E\xBB"=>"\xE6\xA4\xBD", + "\x9E\xBC"=>"\xE6\xA5\x99", + "\x9E\xBD"=>"\xE6\xA4\xB0", + "\x9E\xBE"=>"\xE6\xA5\xA1", + "\x9E\xBF"=>"\xE6\xA5\x9E", + "\x9E\xC0"=>"\xE6\xA5\x9D", + "\x9E\xC1"=>"\xE6\xA6\x81", + "\x9E\xC2"=>"\xE6\xA5\xAA", + "\x9E\xC3"=>"\xE6\xA6\xB2", + "\x9E\xC4"=>"\xE6\xA6\xAE", + "\x9E\xC5"=>"\xE6\xA7\x90", + "\x9E\xC6"=>"\xE6\xA6\xBF", + "\x9E\xC7"=>"\xE6\xA7\x81", + "\x9E\xC8"=>"\xE6\xA7\x93", + "\x9E\xC9"=>"\xE6\xA6\xBE", + "\x9E\xCA"=>"\xE6\xA7\x8E", + "\x9E\xCB"=>"\xE5\xAF\xA8", + "\x9E\xCC"=>"\xE6\xA7\x8A", + "\x9E\xCD"=>"\xE6\xA7\x9D", + "\x9E\xCE"=>"\xE6\xA6\xBB", + "\x9E\xCF"=>"\xE6\xA7\x83", + "\x9E\xD0"=>"\xE6\xA6\xA7", + "\x9E\xD1"=>"\xE6\xA8\xAE", + "\x9E\xD2"=>"\xE6\xA6\x91", + "\x9E\xD3"=>"\xE6\xA6\xA0", + "\x9E\xD4"=>"\xE6\xA6\x9C", + "\x9E\xD5"=>"\xE6\xA6\x95", + "\x9E\xD6"=>"\xE6\xA6\xB4", + "\x9E\xD7"=>"\xE6\xA7\x9E", + "\x9E\xD8"=>"\xE6\xA7\xA8", + "\x9E\xD9"=>"\xE6\xA8\x82", + "\x9E\xDA"=>"\xE6\xA8\x9B", + "\x9E\xDB"=>"\xE6\xA7\xBF", + "\x9E\xDC"=>"\xE6\xAC\x8A", + "\x9E\xDD"=>"\xE6\xA7\xB9", + "\x9E\xDE"=>"\xE6\xA7\xB2", + "\x9E\xDF"=>"\xE6\xA7\xA7", + "\x9E\xE0"=>"\xE6\xA8\x85", + "\x9E\xE1"=>"\xE6\xA6\xB1", + "\x9E\xE2"=>"\xE6\xA8\x9E", + "\x9E\xE3"=>"\xE6\xA7\xAD", + "\x9E\xE4"=>"\xE6\xA8\x94", + "\x9E\xE5"=>"\xE6\xA7\xAB", + "\x9E\xE6"=>"\xE6\xA8\x8A", + "\x9E\xE7"=>"\xE6\xA8\x92", + "\x9E\xE8"=>"\xE6\xAB\x81", + "\x9E\xE9"=>"\xE6\xA8\xA3", + "\x9E\xEA"=>"\xE6\xA8\x93", + "\x9E\xEB"=>"\xE6\xA9\x84", + "\x9E\xEC"=>"\xE6\xA8\x8C", + "\x9E\xED"=>"\xE6\xA9\xB2", + "\x9E\xEE"=>"\xE6\xA8\xB6", + "\x9E\xEF"=>"\xE6\xA9\xB8", + "\x9E\xF0"=>"\xE6\xA9\x87", + "\x9E\xF1"=>"\xE6\xA9\xA2", + "\x9E\xF2"=>"\xE6\xA9\x99", + "\x9E\xF3"=>"\xE6\xA9\xA6", + "\x9E\xF4"=>"\xE6\xA9\x88", + "\x9E\xF5"=>"\xE6\xA8\xB8", + "\x9E\xF6"=>"\xE6\xA8\xA2", + "\x9E\xF7"=>"\xE6\xAA\x90", + "\x9E\xF8"=>"\xE6\xAA\x8D", + "\x9E\xF9"=>"\xE6\xAA\xA0", + "\x9E\xFA"=>"\xE6\xAA\x84", + "\x9E\xFB"=>"\xE6\xAA\xA2", + "\x9E\xFC"=>"\xE6\xAA\xA3", + "\x9F\x40"=>"\xE6\xAA\x97", + "\x9F\x41"=>"\xE8\x98\x97", + "\x9F\x42"=>"\xE6\xAA\xBB", + "\x9F\x43"=>"\xE6\xAB\x83", + "\x9F\x44"=>"\xE6\xAB\x82", + "\x9F\x45"=>"\xE6\xAA\xB8", + "\x9F\x46"=>"\xE6\xAA\xB3", + "\x9F\x47"=>"\xE6\xAA\xAC", + "\x9F\x48"=>"\xE6\xAB\x9E", + "\x9F\x49"=>"\xE6\xAB\x91", + "\x9F\x4A"=>"\xE6\xAB\x9F", + "\x9F\x4B"=>"\xE6\xAA\xAA", + "\x9F\x4C"=>"\xE6\xAB\x9A", + "\x9F\x4D"=>"\xE6\xAB\xAA", + "\x9F\x4E"=>"\xE6\xAB\xBB", + "\x9F\x4F"=>"\xE6\xAC\x85", + "\x9F\x50"=>"\xE8\x98\x96", + "\x9F\x51"=>"\xE6\xAB\xBA", + "\x9F\x52"=>"\xE6\xAC\x92", + "\x9F\x53"=>"\xE6\xAC\x96", + "\x9F\x54"=>"\xE9\xAC\xB1", + "\x9F\x55"=>"\xE6\xAC\x9F", + "\x9F\x56"=>"\xE6\xAC\xB8", + "\x9F\x57"=>"\xE6\xAC\xB7", + "\x9F\x58"=>"\xE7\x9B\x9C", + "\x9F\x59"=>"\xE6\xAC\xB9", + "\x9F\x5A"=>"\xE9\xA3\xAE", + "\x9F\x5B"=>"\xE6\xAD\x87", + "\x9F\x5C"=>"\xE6\xAD\x83", + "\x9F\x5D"=>"\xE6\xAD\x89", + "\x9F\x5E"=>"\xE6\xAD\x90", + "\x9F\x5F"=>"\xE6\xAD\x99", + "\x9F\x60"=>"\xE6\xAD\x94", + "\x9F\x61"=>"\xE6\xAD\x9B", + "\x9F\x62"=>"\xE6\xAD\x9F", + "\x9F\x63"=>"\xE6\xAD\xA1", + "\x9F\x64"=>"\xE6\xAD\xB8", + "\x9F\x65"=>"\xE6\xAD\xB9", + "\x9F\x66"=>"\xE6\xAD\xBF", + "\x9F\x67"=>"\xE6\xAE\x80", + "\x9F\x68"=>"\xE6\xAE\x84", + "\x9F\x69"=>"\xE6\xAE\x83", + "\x9F\x6A"=>"\xE6\xAE\x8D", + "\x9F\x6B"=>"\xE6\xAE\x98", + "\x9F\x6C"=>"\xE6\xAE\x95", + "\x9F\x6D"=>"\xE6\xAE\x9E", + "\x9F\x6E"=>"\xE6\xAE\xA4", + "\x9F\x6F"=>"\xE6\xAE\xAA", + "\x9F\x70"=>"\xE6\xAE\xAB", + "\x9F\x71"=>"\xE6\xAE\xAF", + "\x9F\x72"=>"\xE6\xAE\xB2", + "\x9F\x73"=>"\xE6\xAE\xB1", + "\x9F\x74"=>"\xE6\xAE\xB3", + "\x9F\x75"=>"\xE6\xAE\xB7", + "\x9F\x76"=>"\xE6\xAE\xBC", + "\x9F\x77"=>"\xE6\xAF\x86", + "\x9F\x78"=>"\xE6\xAF\x8B", + "\x9F\x79"=>"\xE6\xAF\x93", + "\x9F\x7A"=>"\xE6\xAF\x9F", + "\x9F\x7B"=>"\xE6\xAF\xAC", + "\x9F\x7C"=>"\xE6\xAF\xAB", + "\x9F\x7D"=>"\xE6\xAF\xB3", + "\x9F\x7E"=>"\xE6\xAF\xAF", + "\x9F\x80"=>"\xE9\xBA\xBE", + "\x9F\x81"=>"\xE6\xB0\x88", + "\x9F\x82"=>"\xE6\xB0\x93", + "\x9F\x83"=>"\xE6\xB0\x94", + "\x9F\x84"=>"\xE6\xB0\x9B", + "\x9F\x85"=>"\xE6\xB0\xA4", + "\x9F\x86"=>"\xE6\xB0\xA3", + "\x9F\x87"=>"\xE6\xB1\x9E", + "\x9F\x88"=>"\xE6\xB1\x95", + "\x9F\x89"=>"\xE6\xB1\xA2", + "\x9F\x8A"=>"\xE6\xB1\xAA", + "\x9F\x8B"=>"\xE6\xB2\x82", + "\x9F\x8C"=>"\xE6\xB2\x8D", + "\x9F\x8D"=>"\xE6\xB2\x9A", + "\x9F\x8E"=>"\xE6\xB2\x81", + "\x9F\x8F"=>"\xE6\xB2\x9B", + "\x9F\x90"=>"\xE6\xB1\xBE", + "\x9F\x91"=>"\xE6\xB1\xA8", + "\x9F\x92"=>"\xE6\xB1\xB3", + "\x9F\x93"=>"\xE6\xB2\x92", + "\x9F\x94"=>"\xE6\xB2\x90", + "\x9F\x95"=>"\xE6\xB3\x84", + "\x9F\x96"=>"\xE6\xB3\xB1", + "\x9F\x97"=>"\xE6\xB3\x93", + "\x9F\x98"=>"\xE6\xB2\xBD", + "\x9F\x99"=>"\xE6\xB3\x97", + "\x9F\x9A"=>"\xE6\xB3\x85", + "\x9F\x9B"=>"\xE6\xB3\x9D", + "\x9F\x9C"=>"\xE6\xB2\xAE", + "\x9F\x9D"=>"\xE6\xB2\xB1", + "\x9F\x9E"=>"\xE6\xB2\xBE", + "\x9F\x9F"=>"\xE6\xB2\xBA", + "\x9F\xA0"=>"\xE6\xB3\x9B", + "\x9F\xA1"=>"\xE6\xB3\xAF", + "\x9F\xA2"=>"\xE6\xB3\x99", + "\x9F\xA3"=>"\xE6\xB3\xAA", + "\x9F\xA4"=>"\xE6\xB4\x9F", + "\x9F\xA5"=>"\xE8\xA1\x8D", + "\x9F\xA6"=>"\xE6\xB4\xB6", + "\x9F\xA7"=>"\xE6\xB4\xAB", + "\x9F\xA8"=>"\xE6\xB4\xBD", + "\x9F\xA9"=>"\xE6\xB4\xB8", + "\x9F\xAA"=>"\xE6\xB4\x99", + "\x9F\xAB"=>"\xE6\xB4\xB5", + "\x9F\xAC"=>"\xE6\xB4\xB3", + "\x9F\xAD"=>"\xE6\xB4\x92", + "\x9F\xAE"=>"\xE6\xB4\x8C", + "\x9F\xAF"=>"\xE6\xB5\xA3", + "\x9F\xB0"=>"\xE6\xB6\x93", + "\x9F\xB1"=>"\xE6\xB5\xA4", + "\x9F\xB2"=>"\xE6\xB5\x9A", + "\x9F\xB3"=>"\xE6\xB5\xB9", + "\x9F\xB4"=>"\xE6\xB5\x99", + "\x9F\xB5"=>"\xE6\xB6\x8E", + "\x9F\xB6"=>"\xE6\xB6\x95", + "\x9F\xB7"=>"\xE6\xBF\xA4", + "\x9F\xB8"=>"\xE6\xB6\x85", + "\x9F\xB9"=>"\xE6\xB7\xB9", + "\x9F\xBA"=>"\xE6\xB8\x95", + "\x9F\xBB"=>"\xE6\xB8\x8A", + "\x9F\xBC"=>"\xE6\xB6\xB5", + "\x9F\xBD"=>"\xE6\xB7\x87", + "\x9F\xBE"=>"\xE6\xB7\xA6", + "\x9F\xBF"=>"\xE6\xB6\xB8", + "\x9F\xC0"=>"\xE6\xB7\x86", + "\x9F\xC1"=>"\xE6\xB7\xAC", + "\x9F\xC2"=>"\xE6\xB7\x9E", + "\x9F\xC3"=>"\xE6\xB7\x8C", + "\x9F\xC4"=>"\xE6\xB7\xA8", + "\x9F\xC5"=>"\xE6\xB7\x92", + "\x9F\xC6"=>"\xE6\xB7\x85", + "\x9F\xC7"=>"\xE6\xB7\xBA", + "\x9F\xC8"=>"\xE6\xB7\x99", + "\x9F\xC9"=>"\xE6\xB7\xA4", + "\x9F\xCA"=>"\xE6\xB7\x95", + "\x9F\xCB"=>"\xE6\xB7\xAA", + "\x9F\xCC"=>"\xE6\xB7\xAE", + "\x9F\xCD"=>"\xE6\xB8\xAD", + "\x9F\xCE"=>"\xE6\xB9\xAE", + "\x9F\xCF"=>"\xE6\xB8\xAE", + "\x9F\xD0"=>"\xE6\xB8\x99", + "\x9F\xD1"=>"\xE6\xB9\xB2", + "\x9F\xD2"=>"\xE6\xB9\x9F", + "\x9F\xD3"=>"\xE6\xB8\xBE", + "\x9F\xD4"=>"\xE6\xB8\xA3", + "\x9F\xD5"=>"\xE6\xB9\xAB", + "\x9F\xD6"=>"\xE6\xB8\xAB", + "\x9F\xD7"=>"\xE6\xB9\xB6", + "\x9F\xD8"=>"\xE6\xB9\x8D", + "\x9F\xD9"=>"\xE6\xB8\x9F", + "\x9F\xDA"=>"\xE6\xB9\x83", + "\x9F\xDB"=>"\xE6\xB8\xBA", + "\x9F\xDC"=>"\xE6\xB9\x8E", + "\x9F\xDD"=>"\xE6\xB8\xA4", + "\x9F\xDE"=>"\xE6\xBB\xBF", + "\x9F\xDF"=>"\xE6\xB8\x9D", + "\x9F\xE0"=>"\xE6\xB8\xB8", + "\x9F\xE1"=>"\xE6\xBA\x82", + "\x9F\xE2"=>"\xE6\xBA\xAA", + "\x9F\xE3"=>"\xE6\xBA\x98", + "\x9F\xE4"=>"\xE6\xBB\x89", + "\x9F\xE5"=>"\xE6\xBA\xB7", + "\x9F\xE6"=>"\xE6\xBB\x93", + "\x9F\xE7"=>"\xE6\xBA\xBD", + "\x9F\xE8"=>"\xE6\xBA\xAF", + "\x9F\xE9"=>"\xE6\xBB\x84", + "\x9F\xEA"=>"\xE6\xBA\xB2", + "\x9F\xEB"=>"\xE6\xBB\x94", + "\x9F\xEC"=>"\xE6\xBB\x95", + "\x9F\xED"=>"\xE6\xBA\x8F", + "\x9F\xEE"=>"\xE6\xBA\xA5", + "\x9F\xEF"=>"\xE6\xBB\x82", + "\x9F\xF0"=>"\xE6\xBA\x9F", + "\x9F\xF1"=>"\xE6\xBD\x81", + "\x9F\xF2"=>"\xE6\xBC\x91", + "\x9F\xF3"=>"\xE7\x81\x8C", + "\x9F\xF4"=>"\xE6\xBB\xAC", + "\x9F\xF5"=>"\xE6\xBB\xB8", + "\x9F\xF6"=>"\xE6\xBB\xBE", + "\x9F\xF7"=>"\xE6\xBC\xBF", + "\x9F\xF8"=>"\xE6\xBB\xB2", + "\x9F\xF9"=>"\xE6\xBC\xB1", + "\x9F\xFA"=>"\xE6\xBB\xAF", + "\x9F\xFB"=>"\xE6\xBC\xB2", + "\x9F\xFC"=>"\xE6\xBB\x8C", + "\xA1"=>"\xEF\xBD\xA1", + "\xA2"=>"\xEF\xBD\xA2", + "\xA3"=>"\xEF\xBD\xA3", + "\xA4"=>"\xEF\xBD\xA4", + "\xA5"=>"\xEF\xBD\xA5", + "\xA6"=>"\xEF\xBD\xA6", + "\xA7"=>"\xEF\xBD\xA7", + "\xA8"=>"\xEF\xBD\xA8", + "\xA9"=>"\xEF\xBD\xA9", + "\xAA"=>"\xEF\xBD\xAA", + "\xAB"=>"\xEF\xBD\xAB", + "\xAC"=>"\xEF\xBD\xAC", + "\xAD"=>"\xEF\xBD\xAD", + "\xAE"=>"\xEF\xBD\xAE", + "\xAF"=>"\xEF\xBD\xAF", + "\xB0"=>"\xEF\xBD\xB0", + "\xB1"=>"\xEF\xBD\xB1", + "\xB2"=>"\xEF\xBD\xB2", + "\xB3"=>"\xEF\xBD\xB3", + "\xB4"=>"\xEF\xBD\xB4", + "\xB5"=>"\xEF\xBD\xB5", + "\xB6"=>"\xEF\xBD\xB6", + "\xB7"=>"\xEF\xBD\xB7", + "\xB8"=>"\xEF\xBD\xB8", + "\xB9"=>"\xEF\xBD\xB9", + "\xBA"=>"\xEF\xBD\xBA", + "\xBB"=>"\xEF\xBD\xBB", + "\xBC"=>"\xEF\xBD\xBC", + "\xBD"=>"\xEF\xBD\xBD", + "\xBE"=>"\xEF\xBD\xBE", + "\xBF"=>"\xEF\xBD\xBF", + "\xC0"=>"\xEF\xBE\x80", + "\xC1"=>"\xEF\xBE\x81", + "\xC2"=>"\xEF\xBE\x82", + "\xC3"=>"\xEF\xBE\x83", + "\xC4"=>"\xEF\xBE\x84", + "\xC5"=>"\xEF\xBE\x85", + "\xC6"=>"\xEF\xBE\x86", + "\xC7"=>"\xEF\xBE\x87", + "\xC8"=>"\xEF\xBE\x88", + "\xC9"=>"\xEF\xBE\x89", + "\xCA"=>"\xEF\xBE\x8A", + "\xCB"=>"\xEF\xBE\x8B", + "\xCC"=>"\xEF\xBE\x8C", + "\xCD"=>"\xEF\xBE\x8D", + "\xCE"=>"\xEF\xBE\x8E", + "\xCF"=>"\xEF\xBE\x8F", + "\xD0"=>"\xEF\xBE\x90", + "\xD1"=>"\xEF\xBE\x91", + "\xD2"=>"\xEF\xBE\x92", + "\xD3"=>"\xEF\xBE\x93", + "\xD4"=>"\xEF\xBE\x94", + "\xD5"=>"\xEF\xBE\x95", + "\xD6"=>"\xEF\xBE\x96", + "\xD7"=>"\xEF\xBE\x97", + "\xD8"=>"\xEF\xBE\x98", + "\xD9"=>"\xEF\xBE\x99", + "\xDA"=>"\xEF\xBE\x9A", + "\xDB"=>"\xEF\xBE\x9B", + "\xDC"=>"\xEF\xBE\x9C", + "\xDD"=>"\xEF\xBE\x9D", + "\xDE"=>"\xEF\xBE\x9E", + "\xDF"=>"\xEF\xBE\x9F", + "\xE0\x40"=>"\xE6\xBC\xBE", + "\xE0\x41"=>"\xE6\xBC\x93", + "\xE0\x42"=>"\xE6\xBB\xB7", + "\xE0\x43"=>"\xE6\xBE\x86", + "\xE0\x44"=>"\xE6\xBD\xBA", + "\xE0\x45"=>"\xE6\xBD\xB8", + "\xE0\x46"=>"\xE6\xBE\x81", + "\xE0\x47"=>"\xE6\xBE\x80", + "\xE0\x48"=>"\xE6\xBD\xAF", + "\xE0\x49"=>"\xE6\xBD\x9B", + "\xE0\x4A"=>"\xE6\xBF\xB3", + "\xE0\x4B"=>"\xE6\xBD\xAD", + "\xE0\x4C"=>"\xE6\xBE\x82", + "\xE0\x4D"=>"\xE6\xBD\xBC", + "\xE0\x4E"=>"\xE6\xBD\x98", + "\xE0\x4F"=>"\xE6\xBE\x8E", + "\xE0\x50"=>"\xE6\xBE\x91", + "\xE0\x51"=>"\xE6\xBF\x82", + "\xE0\x52"=>"\xE6\xBD\xA6", + "\xE0\x53"=>"\xE6\xBE\xB3", + "\xE0\x54"=>"\xE6\xBE\xA3", + "\xE0\x55"=>"\xE6\xBE\xA1", + "\xE0\x56"=>"\xE6\xBE\xA4", + "\xE0\x57"=>"\xE6\xBE\xB9", + "\xE0\x58"=>"\xE6\xBF\x86", + "\xE0\x59"=>"\xE6\xBE\xAA", + "\xE0\x5A"=>"\xE6\xBF\x9F", + "\xE0\x5B"=>"\xE6\xBF\x95", + "\xE0\x5C"=>"\xE6\xBF\xAC", + "\xE0\x5D"=>"\xE6\xBF\x94", + "\xE0\x5E"=>"\xE6\xBF\x98", + "\xE0\x5F"=>"\xE6\xBF\xB1", + "\xE0\x60"=>"\xE6\xBF\xAE", + "\xE0\x61"=>"\xE6\xBF\x9B", + "\xE0\x62"=>"\xE7\x80\x89", + "\xE0\x63"=>"\xE7\x80\x8B", + "\xE0\x64"=>"\xE6\xBF\xBA", + "\xE0\x65"=>"\xE7\x80\x91", + "\xE0\x66"=>"\xE7\x80\x81", + "\xE0\x67"=>"\xE7\x80\x8F", + "\xE0\x68"=>"\xE6\xBF\xBE", + "\xE0\x69"=>"\xE7\x80\x9B", + "\xE0\x6A"=>"\xE7\x80\x9A", + "\xE0\x6B"=>"\xE6\xBD\xB4", + "\xE0\x6C"=>"\xE7\x80\x9D", + "\xE0\x6D"=>"\xE7\x80\x98", + "\xE0\x6E"=>"\xE7\x80\x9F", + "\xE0\x6F"=>"\xE7\x80\xB0", + "\xE0\x70"=>"\xE7\x80\xBE", + "\xE0\x71"=>"\xE7\x80\xB2", + "\xE0\x72"=>"\xE7\x81\x91", + "\xE0\x73"=>"\xE7\x81\xA3", + "\xE0\x74"=>"\xE7\x82\x99", + "\xE0\x75"=>"\xE7\x82\x92", + "\xE0\x76"=>"\xE7\x82\xAF", + "\xE0\x77"=>"\xE7\x83\xB1", + "\xE0\x78"=>"\xE7\x82\xAC", + "\xE0\x79"=>"\xE7\x82\xB8", + "\xE0\x7A"=>"\xE7\x82\xB3", + "\xE0\x7B"=>"\xE7\x82\xAE", + "\xE0\x7C"=>"\xE7\x83\x9F", + "\xE0\x7D"=>"\xE7\x83\x8B", + "\xE0\x7E"=>"\xE7\x83\x9D", + "\xE0\x80"=>"\xE7\x83\x99", + "\xE0\x81"=>"\xE7\x84\x89", + "\xE0\x82"=>"\xE7\x83\xBD", + "\xE0\x83"=>"\xE7\x84\x9C", + "\xE0\x84"=>"\xE7\x84\x99", + "\xE0\x85"=>"\xE7\x85\xA5", + "\xE0\x86"=>"\xE7\x85\x95", + "\xE0\x87"=>"\xE7\x86\x88", + "\xE0\x88"=>"\xE7\x85\xA6", + "\xE0\x89"=>"\xE7\x85\xA2", + "\xE0\x8A"=>"\xE7\x85\x8C", + "\xE0\x8B"=>"\xE7\x85\x96", + "\xE0\x8C"=>"\xE7\x85\xAC", + "\xE0\x8D"=>"\xE7\x86\x8F", + "\xE0\x8E"=>"\xE7\x87\xBB", + "\xE0\x8F"=>"\xE7\x86\x84", + "\xE0\x90"=>"\xE7\x86\x95", + "\xE0\x91"=>"\xE7\x86\xA8", + "\xE0\x92"=>"\xE7\x86\xAC", + "\xE0\x93"=>"\xE7\x87\x97", + "\xE0\x94"=>"\xE7\x86\xB9", + "\xE0\x95"=>"\xE7\x86\xBE", + "\xE0\x96"=>"\xE7\x87\x92", + "\xE0\x97"=>"\xE7\x87\x89", + "\xE0\x98"=>"\xE7\x87\x94", + "\xE0\x99"=>"\xE7\x87\x8E", + "\xE0\x9A"=>"\xE7\x87\xA0", + "\xE0\x9B"=>"\xE7\x87\xAC", + "\xE0\x9C"=>"\xE7\x87\xA7", + "\xE0\x9D"=>"\xE7\x87\xB5", + "\xE0\x9E"=>"\xE7\x87\xBC", + "\xE0\x9F"=>"\xE7\x87\xB9", + "\xE0\xA0"=>"\xE7\x87\xBF", + "\xE0\xA1"=>"\xE7\x88\x8D", + "\xE0\xA2"=>"\xE7\x88\x90", + "\xE0\xA3"=>"\xE7\x88\x9B", + "\xE0\xA4"=>"\xE7\x88\xA8", + "\xE0\xA5"=>"\xE7\x88\xAD", + "\xE0\xA6"=>"\xE7\x88\xAC", + "\xE0\xA7"=>"\xE7\x88\xB0", + "\xE0\xA8"=>"\xE7\x88\xB2", + "\xE0\xA9"=>"\xE7\x88\xBB", + "\xE0\xAA"=>"\xE7\x88\xBC", + "\xE0\xAB"=>"\xE7\x88\xBF", + "\xE0\xAC"=>"\xE7\x89\x80", + "\xE0\xAD"=>"\xE7\x89\x86", + "\xE0\xAE"=>"\xE7\x89\x8B", + "\xE0\xAF"=>"\xE7\x89\x98", + "\xE0\xB0"=>"\xE7\x89\xB4", + "\xE0\xB1"=>"\xE7\x89\xBE", + "\xE0\xB2"=>"\xE7\x8A\x82", + "\xE0\xB3"=>"\xE7\x8A\x81", + "\xE0\xB4"=>"\xE7\x8A\x87", + "\xE0\xB5"=>"\xE7\x8A\x92", + "\xE0\xB6"=>"\xE7\x8A\x96", + "\xE0\xB7"=>"\xE7\x8A\xA2", + "\xE0\xB8"=>"\xE7\x8A\xA7", + "\xE0\xB9"=>"\xE7\x8A\xB9", + "\xE0\xBA"=>"\xE7\x8A\xB2", + "\xE0\xBB"=>"\xE7\x8B\x83", + "\xE0\xBC"=>"\xE7\x8B\x86", + "\xE0\xBD"=>"\xE7\x8B\x84", + "\xE0\xBE"=>"\xE7\x8B\x8E", + "\xE0\xBF"=>"\xE7\x8B\x92", + "\xE0\xC0"=>"\xE7\x8B\xA2", + "\xE0\xC1"=>"\xE7\x8B\xA0", + "\xE0\xC2"=>"\xE7\x8B\xA1", + "\xE0\xC3"=>"\xE7\x8B\xB9", + "\xE0\xC4"=>"\xE7\x8B\xB7", + "\xE0\xC5"=>"\xE5\x80\x8F", + "\xE0\xC6"=>"\xE7\x8C\x97", + "\xE0\xC7"=>"\xE7\x8C\x8A", + "\xE0\xC8"=>"\xE7\x8C\x9C", + "\xE0\xC9"=>"\xE7\x8C\x96", + "\xE0\xCA"=>"\xE7\x8C\x9D", + "\xE0\xCB"=>"\xE7\x8C\xB4", + "\xE0\xCC"=>"\xE7\x8C\xAF", + "\xE0\xCD"=>"\xE7\x8C\xA9", + "\xE0\xCE"=>"\xE7\x8C\xA5", + "\xE0\xCF"=>"\xE7\x8C\xBE", + "\xE0\xD0"=>"\xE7\x8D\x8E", + "\xE0\xD1"=>"\xE7\x8D\x8F", + "\xE0\xD2"=>"\xE9\xBB\x98", + "\xE0\xD3"=>"\xE7\x8D\x97", + "\xE0\xD4"=>"\xE7\x8D\xAA", + "\xE0\xD5"=>"\xE7\x8D\xA8", + "\xE0\xD6"=>"\xE7\x8D\xB0", + "\xE0\xD7"=>"\xE7\x8D\xB8", + "\xE0\xD8"=>"\xE7\x8D\xB5", + "\xE0\xD9"=>"\xE7\x8D\xBB", + "\xE0\xDA"=>"\xE7\x8D\xBA", + "\xE0\xDB"=>"\xE7\x8F\x88", + "\xE0\xDC"=>"\xE7\x8E\xB3", + "\xE0\xDD"=>"\xE7\x8F\x8E", + "\xE0\xDE"=>"\xE7\x8E\xBB", + "\xE0\xDF"=>"\xE7\x8F\x80", + "\xE0\xE0"=>"\xE7\x8F\xA5", + "\xE0\xE1"=>"\xE7\x8F\xAE", + "\xE0\xE2"=>"\xE7\x8F\x9E", + "\xE0\xE3"=>"\xE7\x92\xA2", + "\xE0\xE4"=>"\xE7\x90\x85", + "\xE0\xE5"=>"\xE7\x91\xAF", + "\xE0\xE6"=>"\xE7\x90\xA5", + "\xE0\xE7"=>"\xE7\x8F\xB8", + "\xE0\xE8"=>"\xE7\x90\xB2", + "\xE0\xE9"=>"\xE7\x90\xBA", + "\xE0\xEA"=>"\xE7\x91\x95", + "\xE0\xEB"=>"\xE7\x90\xBF", + "\xE0\xEC"=>"\xE7\x91\x9F", + "\xE0\xED"=>"\xE7\x91\x99", + "\xE0\xEE"=>"\xE7\x91\x81", + "\xE0\xEF"=>"\xE7\x91\x9C", + "\xE0\xF0"=>"\xE7\x91\xA9", + "\xE0\xF1"=>"\xE7\x91\xB0", + "\xE0\xF2"=>"\xE7\x91\xA3", + "\xE0\xF3"=>"\xE7\x91\xAA", + "\xE0\xF4"=>"\xE7\x91\xB6", + "\xE0\xF5"=>"\xE7\x91\xBE", + "\xE0\xF6"=>"\xE7\x92\x8B", + "\xE0\xF7"=>"\xE7\x92\x9E", + "\xE0\xF8"=>"\xE7\x92\xA7", + "\xE0\xF9"=>"\xE7\x93\x8A", + "\xE0\xFA"=>"\xE7\x93\x8F", + "\xE0\xFB"=>"\xE7\x93\x94", + "\xE0\xFC"=>"\xE7\x8F\xB1", + "\xE1\x40"=>"\xE7\x93\xA0", + "\xE1\x41"=>"\xE7\x93\xA3", + "\xE1\x42"=>"\xE7\x93\xA7", + "\xE1\x43"=>"\xE7\x93\xA9", + "\xE1\x44"=>"\xE7\x93\xAE", + "\xE1\x45"=>"\xE7\x93\xB2", + "\xE1\x46"=>"\xE7\x93\xB0", + "\xE1\x47"=>"\xE7\x93\xB1", + "\xE1\x48"=>"\xE7\x93\xB8", + "\xE1\x49"=>"\xE7\x93\xB7", + "\xE1\x4A"=>"\xE7\x94\x84", + "\xE1\x4B"=>"\xE7\x94\x83", + "\xE1\x4C"=>"\xE7\x94\x85", + "\xE1\x4D"=>"\xE7\x94\x8C", + "\xE1\x4E"=>"\xE7\x94\x8E", + "\xE1\x4F"=>"\xE7\x94\x8D", + "\xE1\x50"=>"\xE7\x94\x95", + "\xE1\x51"=>"\xE7\x94\x93", + "\xE1\x52"=>"\xE7\x94\x9E", + "\xE1\x53"=>"\xE7\x94\xA6", + "\xE1\x54"=>"\xE7\x94\xAC", + "\xE1\x55"=>"\xE7\x94\xBC", + "\xE1\x56"=>"\xE7\x95\x84", + "\xE1\x57"=>"\xE7\x95\x8D", + "\xE1\x58"=>"\xE7\x95\x8A", + "\xE1\x59"=>"\xE7\x95\x89", + "\xE1\x5A"=>"\xE7\x95\x9B", + "\xE1\x5B"=>"\xE7\x95\x86", + "\xE1\x5C"=>"\xE7\x95\x9A", + "\xE1\x5D"=>"\xE7\x95\xA9", + "\xE1\x5E"=>"\xE7\x95\xA4", + "\xE1\x5F"=>"\xE7\x95\xA7", + "\xE1\x60"=>"\xE7\x95\xAB", + "\xE1\x61"=>"\xE7\x95\xAD", + "\xE1\x62"=>"\xE7\x95\xB8", + "\xE1\x63"=>"\xE7\x95\xB6", + "\xE1\x64"=>"\xE7\x96\x86", + "\xE1\x65"=>"\xE7\x96\x87", + "\xE1\x66"=>"\xE7\x95\xB4", + "\xE1\x67"=>"\xE7\x96\x8A", + "\xE1\x68"=>"\xE7\x96\x89", + "\xE1\x69"=>"\xE7\x96\x82", + "\xE1\x6A"=>"\xE7\x96\x94", + "\xE1\x6B"=>"\xE7\x96\x9A", + "\xE1\x6C"=>"\xE7\x96\x9D", + "\xE1\x6D"=>"\xE7\x96\xA5", + "\xE1\x6E"=>"\xE7\x96\xA3", + "\xE1\x6F"=>"\xE7\x97\x82", + "\xE1\x70"=>"\xE7\x96\xB3", + "\xE1\x71"=>"\xE7\x97\x83", + "\xE1\x72"=>"\xE7\x96\xB5", + "\xE1\x73"=>"\xE7\x96\xBD", + "\xE1\x74"=>"\xE7\x96\xB8", + "\xE1\x75"=>"\xE7\x96\xBC", + "\xE1\x76"=>"\xE7\x96\xB1", + "\xE1\x77"=>"\xE7\x97\x8D", + "\xE1\x78"=>"\xE7\x97\x8A", + "\xE1\x79"=>"\xE7\x97\x92", + "\xE1\x7A"=>"\xE7\x97\x99", + "\xE1\x7B"=>"\xE7\x97\xA3", + "\xE1\x7C"=>"\xE7\x97\x9E", + "\xE1\x7D"=>"\xE7\x97\xBE", + "\xE1\x7E"=>"\xE7\x97\xBF", + "\xE1\x80"=>"\xE7\x97\xBC", + "\xE1\x81"=>"\xE7\x98\x81", + "\xE1\x82"=>"\xE7\x97\xB0", + "\xE1\x83"=>"\xE7\x97\xBA", + "\xE1\x84"=>"\xE7\x97\xB2", + "\xE1\x85"=>"\xE7\x97\xB3", + "\xE1\x86"=>"\xE7\x98\x8B", + "\xE1\x87"=>"\xE7\x98\x8D", + "\xE1\x88"=>"\xE7\x98\x89", + "\xE1\x89"=>"\xE7\x98\x9F", + "\xE1\x8A"=>"\xE7\x98\xA7", + "\xE1\x8B"=>"\xE7\x98\xA0", + "\xE1\x8C"=>"\xE7\x98\xA1", + "\xE1\x8D"=>"\xE7\x98\xA2", + "\xE1\x8E"=>"\xE7\x98\xA4", + "\xE1\x8F"=>"\xE7\x98\xB4", + "\xE1\x90"=>"\xE7\x98\xB0", + "\xE1\x91"=>"\xE7\x98\xBB", + "\xE1\x92"=>"\xE7\x99\x87", + "\xE1\x93"=>"\xE7\x99\x88", + "\xE1\x94"=>"\xE7\x99\x86", + "\xE1\x95"=>"\xE7\x99\x9C", + "\xE1\x96"=>"\xE7\x99\x98", + "\xE1\x97"=>"\xE7\x99\xA1", + "\xE1\x98"=>"\xE7\x99\xA2", + "\xE1\x99"=>"\xE7\x99\xA8", + "\xE1\x9A"=>"\xE7\x99\xA9", + "\xE1\x9B"=>"\xE7\x99\xAA", + "\xE1\x9C"=>"\xE7\x99\xA7", + "\xE1\x9D"=>"\xE7\x99\xAC", + "\xE1\x9E"=>"\xE7\x99\xB0", + "\xE1\x9F"=>"\xE7\x99\xB2", + "\xE1\xA0"=>"\xE7\x99\xB6", + "\xE1\xA1"=>"\xE7\x99\xB8", + "\xE1\xA2"=>"\xE7\x99\xBC", + "\xE1\xA3"=>"\xE7\x9A\x80", + "\xE1\xA4"=>"\xE7\x9A\x83", + "\xE1\xA5"=>"\xE7\x9A\x88", + "\xE1\xA6"=>"\xE7\x9A\x8B", + "\xE1\xA7"=>"\xE7\x9A\x8E", + "\xE1\xA8"=>"\xE7\x9A\x96", + "\xE1\xA9"=>"\xE7\x9A\x93", + "\xE1\xAA"=>"\xE7\x9A\x99", + "\xE1\xAB"=>"\xE7\x9A\x9A", + "\xE1\xAC"=>"\xE7\x9A\xB0", + "\xE1\xAD"=>"\xE7\x9A\xB4", + "\xE1\xAE"=>"\xE7\x9A\xB8", + "\xE1\xAF"=>"\xE7\x9A\xB9", + "\xE1\xB0"=>"\xE7\x9A\xBA", + "\xE1\xB1"=>"\xE7\x9B\x82", + "\xE1\xB2"=>"\xE7\x9B\x8D", + "\xE1\xB3"=>"\xE7\x9B\x96", + "\xE1\xB4"=>"\xE7\x9B\x92", + "\xE1\xB5"=>"\xE7\x9B\x9E", + "\xE1\xB6"=>"\xE7\x9B\xA1", + "\xE1\xB7"=>"\xE7\x9B\xA5", + "\xE1\xB8"=>"\xE7\x9B\xA7", + "\xE1\xB9"=>"\xE7\x9B\xAA", + "\xE1\xBA"=>"\xE8\x98\xAF", + "\xE1\xBB"=>"\xE7\x9B\xBB", + "\xE1\xBC"=>"\xE7\x9C\x88", + "\xE1\xBD"=>"\xE7\x9C\x87", + "\xE1\xBE"=>"\xE7\x9C\x84", + "\xE1\xBF"=>"\xE7\x9C\xA9", + "\xE1\xC0"=>"\xE7\x9C\xA4", + "\xE1\xC1"=>"\xE7\x9C\x9E", + "\xE1\xC2"=>"\xE7\x9C\xA5", + "\xE1\xC3"=>"\xE7\x9C\xA6", + "\xE1\xC4"=>"\xE7\x9C\x9B", + "\xE1\xC5"=>"\xE7\x9C\xB7", + "\xE1\xC6"=>"\xE7\x9C\xB8", + "\xE1\xC7"=>"\xE7\x9D\x87", + "\xE1\xC8"=>"\xE7\x9D\x9A", + "\xE1\xC9"=>"\xE7\x9D\xA8", + "\xE1\xCA"=>"\xE7\x9D\xAB", + "\xE1\xCB"=>"\xE7\x9D\x9B", + "\xE1\xCC"=>"\xE7\x9D\xA5", + "\xE1\xCD"=>"\xE7\x9D\xBF", + "\xE1\xCE"=>"\xE7\x9D\xBE", + "\xE1\xCF"=>"\xE7\x9D\xB9", + "\xE1\xD0"=>"\xE7\x9E\x8E", + "\xE1\xD1"=>"\xE7\x9E\x8B", + "\xE1\xD2"=>"\xE7\x9E\x91", + "\xE1\xD3"=>"\xE7\x9E\xA0", + "\xE1\xD4"=>"\xE7\x9E\x9E", + "\xE1\xD5"=>"\xE7\x9E\xB0", + "\xE1\xD6"=>"\xE7\x9E\xB6", + "\xE1\xD7"=>"\xE7\x9E\xB9", + "\xE1\xD8"=>"\xE7\x9E\xBF", + "\xE1\xD9"=>"\xE7\x9E\xBC", + "\xE1\xDA"=>"\xE7\x9E\xBD", + "\xE1\xDB"=>"\xE7\x9E\xBB", + "\xE1\xDC"=>"\xE7\x9F\x87", + "\xE1\xDD"=>"\xE7\x9F\x8D", + "\xE1\xDE"=>"\xE7\x9F\x97", + "\xE1\xDF"=>"\xE7\x9F\x9A", + "\xE1\xE0"=>"\xE7\x9F\x9C", + "\xE1\xE1"=>"\xE7\x9F\xA3", + "\xE1\xE2"=>"\xE7\x9F\xAE", + "\xE1\xE3"=>"\xE7\x9F\xBC", + "\xE1\xE4"=>"\xE7\xA0\x8C", + "\xE1\xE5"=>"\xE7\xA0\x92", + "\xE1\xE6"=>"\xE7\xA4\xA6", + "\xE1\xE7"=>"\xE7\xA0\xA0", + "\xE1\xE8"=>"\xE7\xA4\xAA", + "\xE1\xE9"=>"\xE7\xA1\x85", + "\xE1\xEA"=>"\xE7\xA2\x8E", + "\xE1\xEB"=>"\xE7\xA1\xB4", + "\xE1\xEC"=>"\xE7\xA2\x86", + "\xE1\xED"=>"\xE7\xA1\xBC", + "\xE1\xEE"=>"\xE7\xA2\x9A", + "\xE1\xEF"=>"\xE7\xA2\x8C", + "\xE1\xF0"=>"\xE7\xA2\xA3", + "\xE1\xF1"=>"\xE7\xA2\xB5", + "\xE1\xF2"=>"\xE7\xA2\xAA", + "\xE1\xF3"=>"\xE7\xA2\xAF", + "\xE1\xF4"=>"\xE7\xA3\x91", + "\xE1\xF5"=>"\xE7\xA3\x86", + "\xE1\xF6"=>"\xE7\xA3\x8B", + "\xE1\xF7"=>"\xE7\xA3\x94", + "\xE1\xF8"=>"\xE7\xA2\xBE", + "\xE1\xF9"=>"\xE7\xA2\xBC", + "\xE1\xFA"=>"\xE7\xA3\x85", + "\xE1\xFB"=>"\xE7\xA3\x8A", + "\xE1\xFC"=>"\xE7\xA3\xAC", + "\xE2\x40"=>"\xE7\xA3\xA7", + "\xE2\x41"=>"\xE7\xA3\x9A", + "\xE2\x42"=>"\xE7\xA3\xBD", + "\xE2\x43"=>"\xE7\xA3\xB4", + "\xE2\x44"=>"\xE7\xA4\x87", + "\xE2\x45"=>"\xE7\xA4\x92", + "\xE2\x46"=>"\xE7\xA4\x91", + "\xE2\x47"=>"\xE7\xA4\x99", + "\xE2\x48"=>"\xE7\xA4\xAC", + "\xE2\x49"=>"\xE7\xA4\xAB", + "\xE2\x4A"=>"\xE7\xA5\x80", + "\xE2\x4B"=>"\xE7\xA5\xA0", + "\xE2\x4C"=>"\xE7\xA5\x97", + "\xE2\x4D"=>"\xE7\xA5\x9F", + "\xE2\x4E"=>"\xE7\xA5\x9A", + "\xE2\x4F"=>"\xE7\xA5\x95", + "\xE2\x50"=>"\xE7\xA5\x93", + "\xE2\x51"=>"\xE7\xA5\xBA", + "\xE2\x52"=>"\xE7\xA5\xBF", + "\xE2\x53"=>"\xE7\xA6\x8A", + "\xE2\x54"=>"\xE7\xA6\x9D", + "\xE2\x55"=>"\xE7\xA6\xA7", + "\xE2\x56"=>"\xE9\xBD\x8B", + "\xE2\x57"=>"\xE7\xA6\xAA", + "\xE2\x58"=>"\xE7\xA6\xAE", + "\xE2\x59"=>"\xE7\xA6\xB3", + "\xE2\x5A"=>"\xE7\xA6\xB9", + "\xE2\x5B"=>"\xE7\xA6\xBA", + "\xE2\x5C"=>"\xE7\xA7\x89", + "\xE2\x5D"=>"\xE7\xA7\x95", + "\xE2\x5E"=>"\xE7\xA7\xA7", + "\xE2\x5F"=>"\xE7\xA7\xAC", + "\xE2\x60"=>"\xE7\xA7\xA1", + "\xE2\x61"=>"\xE7\xA7\xA3", + "\xE2\x62"=>"\xE7\xA8\x88", + "\xE2\x63"=>"\xE7\xA8\x8D", + "\xE2\x64"=>"\xE7\xA8\x98", + "\xE2\x65"=>"\xE7\xA8\x99", + "\xE2\x66"=>"\xE7\xA8\xA0", + "\xE2\x67"=>"\xE7\xA8\x9F", + "\xE2\x68"=>"\xE7\xA6\x80", + "\xE2\x69"=>"\xE7\xA8\xB1", + "\xE2\x6A"=>"\xE7\xA8\xBB", + "\xE2\x6B"=>"\xE7\xA8\xBE", + "\xE2\x6C"=>"\xE7\xA8\xB7", + "\xE2\x6D"=>"\xE7\xA9\x83", + "\xE2\x6E"=>"\xE7\xA9\x97", + "\xE2\x6F"=>"\xE7\xA9\x89", + "\xE2\x70"=>"\xE7\xA9\xA1", + "\xE2\x71"=>"\xE7\xA9\xA2", + "\xE2\x72"=>"\xE7\xA9\xA9", + "\xE2\x73"=>"\xE9\xBE\x9D", + "\xE2\x74"=>"\xE7\xA9\xB0", + "\xE2\x75"=>"\xE7\xA9\xB9", + "\xE2\x76"=>"\xE7\xA9\xBD", + "\xE2\x77"=>"\xE7\xAA\x88", + "\xE2\x78"=>"\xE7\xAA\x97", + "\xE2\x79"=>"\xE7\xAA\x95", + "\xE2\x7A"=>"\xE7\xAA\x98", + "\xE2\x7B"=>"\xE7\xAA\x96", + "\xE2\x7C"=>"\xE7\xAA\xA9", + "\xE2\x7D"=>"\xE7\xAB\x88", + "\xE2\x7E"=>"\xE7\xAA\xB0", + "\xE2\x80"=>"\xE7\xAA\xB6", + "\xE2\x81"=>"\xE7\xAB\x85", + "\xE2\x82"=>"\xE7\xAB\x84", + "\xE2\x83"=>"\xE7\xAA\xBF", + "\xE2\x84"=>"\xE9\x82\x83", + "\xE2\x85"=>"\xE7\xAB\x87", + "\xE2\x86"=>"\xE7\xAB\x8A", + "\xE2\x87"=>"\xE7\xAB\x8D", + "\xE2\x88"=>"\xE7\xAB\x8F", + "\xE2\x89"=>"\xE7\xAB\x95", + "\xE2\x8A"=>"\xE7\xAB\x93", + "\xE2\x8B"=>"\xE7\xAB\x99", + "\xE2\x8C"=>"\xE7\xAB\x9A", + "\xE2\x8D"=>"\xE7\xAB\x9D", + "\xE2\x8E"=>"\xE7\xAB\xA1", + "\xE2\x8F"=>"\xE7\xAB\xA2", + "\xE2\x90"=>"\xE7\xAB\xA6", + "\xE2\x91"=>"\xE7\xAB\xAD", + "\xE2\x92"=>"\xE7\xAB\xB0", + "\xE2\x93"=>"\xE7\xAC\x82", + "\xE2\x94"=>"\xE7\xAC\x8F", + "\xE2\x95"=>"\xE7\xAC\x8A", + "\xE2\x96"=>"\xE7\xAC\x86", + "\xE2\x97"=>"\xE7\xAC\xB3", + "\xE2\x98"=>"\xE7\xAC\x98", + "\xE2\x99"=>"\xE7\xAC\x99", + "\xE2\x9A"=>"\xE7\xAC\x9E", + "\xE2\x9B"=>"\xE7\xAC\xB5", + "\xE2\x9C"=>"\xE7\xAC\xA8", + "\xE2\x9D"=>"\xE7\xAC\xB6", + "\xE2\x9E"=>"\xE7\xAD\x90", + "\xE2\x9F"=>"\xE7\xAD\xBA", + "\xE2\xA0"=>"\xE7\xAC\x84", + "\xE2\xA1"=>"\xE7\xAD\x8D", + "\xE2\xA2"=>"\xE7\xAC\x8B", + "\xE2\xA3"=>"\xE7\xAD\x8C", + "\xE2\xA4"=>"\xE7\xAD\x85", + "\xE2\xA5"=>"\xE7\xAD\xB5", + "\xE2\xA6"=>"\xE7\xAD\xA5", + "\xE2\xA7"=>"\xE7\xAD\xB4", + "\xE2\xA8"=>"\xE7\xAD\xA7", + "\xE2\xA9"=>"\xE7\xAD\xB0", + "\xE2\xAA"=>"\xE7\xAD\xB1", + "\xE2\xAB"=>"\xE7\xAD\xAC", + "\xE2\xAC"=>"\xE7\xAD\xAE", + "\xE2\xAD"=>"\xE7\xAE\x9D", + "\xE2\xAE"=>"\xE7\xAE\x98", + "\xE2\xAF"=>"\xE7\xAE\x9F", + "\xE2\xB0"=>"\xE7\xAE\x8D", + "\xE2\xB1"=>"\xE7\xAE\x9C", + "\xE2\xB2"=>"\xE7\xAE\x9A", + "\xE2\xB3"=>"\xE7\xAE\x8B", + "\xE2\xB4"=>"\xE7\xAE\x92", + "\xE2\xB5"=>"\xE7\xAE\x8F", + "\xE2\xB6"=>"\xE7\xAD\x9D", + "\xE2\xB7"=>"\xE7\xAE\x99", + "\xE2\xB8"=>"\xE7\xAF\x8B", + "\xE2\xB9"=>"\xE7\xAF\x81", + "\xE2\xBA"=>"\xE7\xAF\x8C", + "\xE2\xBB"=>"\xE7\xAF\x8F", + "\xE2\xBC"=>"\xE7\xAE\xB4", + "\xE2\xBD"=>"\xE7\xAF\x86", + "\xE2\xBE"=>"\xE7\xAF\x9D", + "\xE2\xBF"=>"\xE7\xAF\xA9", + "\xE2\xC0"=>"\xE7\xB0\x91", + "\xE2\xC1"=>"\xE7\xB0\x94", + "\xE2\xC2"=>"\xE7\xAF\xA6", + "\xE2\xC3"=>"\xE7\xAF\xA5", + "\xE2\xC4"=>"\xE7\xB1\xA0", + "\xE2\xC5"=>"\xE7\xB0\x80", + "\xE2\xC6"=>"\xE7\xB0\x87", + "\xE2\xC7"=>"\xE7\xB0\x93", + "\xE2\xC8"=>"\xE7\xAF\xB3", + "\xE2\xC9"=>"\xE7\xAF\xB7", + "\xE2\xCA"=>"\xE7\xB0\x97", + "\xE2\xCB"=>"\xE7\xB0\x8D", + "\xE2\xCC"=>"\xE7\xAF\xB6", + "\xE2\xCD"=>"\xE7\xB0\xA3", + "\xE2\xCE"=>"\xE7\xB0\xA7", + "\xE2\xCF"=>"\xE7\xB0\xAA", + "\xE2\xD0"=>"\xE7\xB0\x9F", + "\xE2\xD1"=>"\xE7\xB0\xB7", + "\xE2\xD2"=>"\xE7\xB0\xAB", + "\xE2\xD3"=>"\xE7\xB0\xBD", + "\xE2\xD4"=>"\xE7\xB1\x8C", + "\xE2\xD5"=>"\xE7\xB1\x83", + "\xE2\xD6"=>"\xE7\xB1\x94", + "\xE2\xD7"=>"\xE7\xB1\x8F", + "\xE2\xD8"=>"\xE7\xB1\x80", + "\xE2\xD9"=>"\xE7\xB1\x90", + "\xE2\xDA"=>"\xE7\xB1\x98", + "\xE2\xDB"=>"\xE7\xB1\x9F", + "\xE2\xDC"=>"\xE7\xB1\xA4", + "\xE2\xDD"=>"\xE7\xB1\x96", + "\xE2\xDE"=>"\xE7\xB1\xA5", + "\xE2\xDF"=>"\xE7\xB1\xAC", + "\xE2\xE0"=>"\xE7\xB1\xB5", + "\xE2\xE1"=>"\xE7\xB2\x83", + "\xE2\xE2"=>"\xE7\xB2\x90", + "\xE2\xE3"=>"\xE7\xB2\xA4", + "\xE2\xE4"=>"\xE7\xB2\xAD", + "\xE2\xE5"=>"\xE7\xB2\xA2", + "\xE2\xE6"=>"\xE7\xB2\xAB", + "\xE2\xE7"=>"\xE7\xB2\xA1", + "\xE2\xE8"=>"\xE7\xB2\xA8", + "\xE2\xE9"=>"\xE7\xB2\xB3", + "\xE2\xEA"=>"\xE7\xB2\xB2", + "\xE2\xEB"=>"\xE7\xB2\xB1", + "\xE2\xEC"=>"\xE7\xB2\xAE", + "\xE2\xED"=>"\xE7\xB2\xB9", + "\xE2\xEE"=>"\xE7\xB2\xBD", + "\xE2\xEF"=>"\xE7\xB3\x80", + "\xE2\xF0"=>"\xE7\xB3\x85", + "\xE2\xF1"=>"\xE7\xB3\x82", + "\xE2\xF2"=>"\xE7\xB3\x98", + "\xE2\xF3"=>"\xE7\xB3\x92", + "\xE2\xF4"=>"\xE7\xB3\x9C", + "\xE2\xF5"=>"\xE7\xB3\xA2", + "\xE2\xF6"=>"\xE9\xAC\xBB", + "\xE2\xF7"=>"\xE7\xB3\xAF", + "\xE2\xF8"=>"\xE7\xB3\xB2", + "\xE2\xF9"=>"\xE7\xB3\xB4", + "\xE2\xFA"=>"\xE7\xB3\xB6", + "\xE2\xFB"=>"\xE7\xB3\xBA", + "\xE2\xFC"=>"\xE7\xB4\x86", + "\xE3\x40"=>"\xE7\xB4\x82", + "\xE3\x41"=>"\xE7\xB4\x9C", + "\xE3\x42"=>"\xE7\xB4\x95", + "\xE3\x43"=>"\xE7\xB4\x8A", + "\xE3\x44"=>"\xE7\xB5\x85", + "\xE3\x45"=>"\xE7\xB5\x8B", + "\xE3\x46"=>"\xE7\xB4\xAE", + "\xE3\x47"=>"\xE7\xB4\xB2", + "\xE3\x48"=>"\xE7\xB4\xBF", + "\xE3\x49"=>"\xE7\xB4\xB5", + "\xE3\x4A"=>"\xE7\xB5\x86", + "\xE3\x4B"=>"\xE7\xB5\xB3", + "\xE3\x4C"=>"\xE7\xB5\x96", + "\xE3\x4D"=>"\xE7\xB5\x8E", + "\xE3\x4E"=>"\xE7\xB5\xB2", + "\xE3\x4F"=>"\xE7\xB5\xA8", + "\xE3\x50"=>"\xE7\xB5\xAE", + "\xE3\x51"=>"\xE7\xB5\x8F", + "\xE3\x52"=>"\xE7\xB5\xA3", + "\xE3\x53"=>"\xE7\xB6\x93", + "\xE3\x54"=>"\xE7\xB6\x89", + "\xE3\x55"=>"\xE7\xB5\x9B", + "\xE3\x56"=>"\xE7\xB6\x8F", + "\xE3\x57"=>"\xE7\xB5\xBD", + "\xE3\x58"=>"\xE7\xB6\x9B", + "\xE3\x59"=>"\xE7\xB6\xBA", + "\xE3\x5A"=>"\xE7\xB6\xAE", + "\xE3\x5B"=>"\xE7\xB6\xA3", + "\xE3\x5C"=>"\xE7\xB6\xB5", + "\xE3\x5D"=>"\xE7\xB7\x87", + "\xE3\x5E"=>"\xE7\xB6\xBD", + "\xE3\x5F"=>"\xE7\xB6\xAB", + "\xE3\x60"=>"\xE7\xB8\xBD", + "\xE3\x61"=>"\xE7\xB6\xA2", + "\xE3\x62"=>"\xE7\xB6\xAF", + "\xE3\x63"=>"\xE7\xB7\x9C", + "\xE3\x64"=>"\xE7\xB6\xB8", + "\xE3\x65"=>"\xE7\xB6\x9F", + "\xE3\x66"=>"\xE7\xB6\xB0", + "\xE3\x67"=>"\xE7\xB7\x98", + "\xE3\x68"=>"\xE7\xB7\x9D", + "\xE3\x69"=>"\xE7\xB7\xA4", + "\xE3\x6A"=>"\xE7\xB7\x9E", + "\xE3\x6B"=>"\xE7\xB7\xBB", + "\xE3\x6C"=>"\xE7\xB7\xB2", + "\xE3\x6D"=>"\xE7\xB7\xA1", + "\xE3\x6E"=>"\xE7\xB8\x85", + "\xE3\x6F"=>"\xE7\xB8\x8A", + "\xE3\x70"=>"\xE7\xB8\xA3", + "\xE3\x71"=>"\xE7\xB8\xA1", + "\xE3\x72"=>"\xE7\xB8\x92", + "\xE3\x73"=>"\xE7\xB8\xB1", + "\xE3\x74"=>"\xE7\xB8\x9F", + "\xE3\x75"=>"\xE7\xB8\x89", + "\xE3\x76"=>"\xE7\xB8\x8B", + "\xE3\x77"=>"\xE7\xB8\xA2", + "\xE3\x78"=>"\xE7\xB9\x86", + "\xE3\x79"=>"\xE7\xB9\xA6", + "\xE3\x7A"=>"\xE7\xB8\xBB", + "\xE3\x7B"=>"\xE7\xB8\xB5", + "\xE3\x7C"=>"\xE7\xB8\xB9", + "\xE3\x7D"=>"\xE7\xB9\x83", + "\xE3\x7E"=>"\xE7\xB8\xB7", + "\xE3\x80"=>"\xE7\xB8\xB2", + "\xE3\x81"=>"\xE7\xB8\xBA", + "\xE3\x82"=>"\xE7\xB9\xA7", + "\xE3\x83"=>"\xE7\xB9\x9D", + "\xE3\x84"=>"\xE7\xB9\x96", + "\xE3\x85"=>"\xE7\xB9\x9E", + "\xE3\x86"=>"\xE7\xB9\x99", + "\xE3\x87"=>"\xE7\xB9\x9A", + "\xE3\x88"=>"\xE7\xB9\xB9", + "\xE3\x89"=>"\xE7\xB9\xAA", + "\xE3\x8A"=>"\xE7\xB9\xA9", + "\xE3\x8B"=>"\xE7\xB9\xBC", + "\xE3\x8C"=>"\xE7\xB9\xBB", + "\xE3\x8D"=>"\xE7\xBA\x83", + "\xE3\x8E"=>"\xE7\xB7\x95", + "\xE3\x8F"=>"\xE7\xB9\xBD", + "\xE3\x90"=>"\xE8\xBE\xAE", + "\xE3\x91"=>"\xE7\xB9\xBF", + "\xE3\x92"=>"\xE7\xBA\x88", + "\xE3\x93"=>"\xE7\xBA\x89", + "\xE3\x94"=>"\xE7\xBA\x8C", + "\xE3\x95"=>"\xE7\xBA\x92", + "\xE3\x96"=>"\xE7\xBA\x90", + "\xE3\x97"=>"\xE7\xBA\x93", + "\xE3\x98"=>"\xE7\xBA\x94", + "\xE3\x99"=>"\xE7\xBA\x96", + "\xE3\x9A"=>"\xE7\xBA\x8E", + "\xE3\x9B"=>"\xE7\xBA\x9B", + "\xE3\x9C"=>"\xE7\xBA\x9C", + "\xE3\x9D"=>"\xE7\xBC\xB8", + "\xE3\x9E"=>"\xE7\xBC\xBA", + "\xE3\x9F"=>"\xE7\xBD\x85", + "\xE3\xA0"=>"\xE7\xBD\x8C", + "\xE3\xA1"=>"\xE7\xBD\x8D", + "\xE3\xA2"=>"\xE7\xBD\x8E", + "\xE3\xA3"=>"\xE7\xBD\x90", + "\xE3\xA4"=>"\xE7\xBD\x91", + "\xE3\xA5"=>"\xE7\xBD\x95", + "\xE3\xA6"=>"\xE7\xBD\x94", + "\xE3\xA7"=>"\xE7\xBD\x98", + "\xE3\xA8"=>"\xE7\xBD\x9F", + "\xE3\xA9"=>"\xE7\xBD\xA0", + "\xE3\xAA"=>"\xE7\xBD\xA8", + "\xE3\xAB"=>"\xE7\xBD\xA9", + "\xE3\xAC"=>"\xE7\xBD\xA7", + "\xE3\xAD"=>"\xE7\xBD\xB8", + "\xE3\xAE"=>"\xE7\xBE\x82", + "\xE3\xAF"=>"\xE7\xBE\x86", + "\xE3\xB0"=>"\xE7\xBE\x83", + "\xE3\xB1"=>"\xE7\xBE\x88", + "\xE3\xB2"=>"\xE7\xBE\x87", + "\xE3\xB3"=>"\xE7\xBE\x8C", + "\xE3\xB4"=>"\xE7\xBE\x94", + "\xE3\xB5"=>"\xE7\xBE\x9E", + "\xE3\xB6"=>"\xE7\xBE\x9D", + "\xE3\xB7"=>"\xE7\xBE\x9A", + "\xE3\xB8"=>"\xE7\xBE\xA3", + "\xE3\xB9"=>"\xE7\xBE\xAF", + "\xE3\xBA"=>"\xE7\xBE\xB2", + "\xE3\xBB"=>"\xE7\xBE\xB9", + "\xE3\xBC"=>"\xE7\xBE\xAE", + "\xE3\xBD"=>"\xE7\xBE\xB6", + "\xE3\xBE"=>"\xE7\xBE\xB8", + "\xE3\xBF"=>"\xE8\xAD\xB1", + "\xE3\xC0"=>"\xE7\xBF\x85", + "\xE3\xC1"=>"\xE7\xBF\x86", + "\xE3\xC2"=>"\xE7\xBF\x8A", + "\xE3\xC3"=>"\xE7\xBF\x95", + "\xE3\xC4"=>"\xE7\xBF\x94", + "\xE3\xC5"=>"\xE7\xBF\xA1", + "\xE3\xC6"=>"\xE7\xBF\xA6", + "\xE3\xC7"=>"\xE7\xBF\xA9", + "\xE3\xC8"=>"\xE7\xBF\xB3", + "\xE3\xC9"=>"\xE7\xBF\xB9", + "\xE3\xCA"=>"\xE9\xA3\x9C", + "\xE3\xCB"=>"\xE8\x80\x86", + "\xE3\xCC"=>"\xE8\x80\x84", + "\xE3\xCD"=>"\xE8\x80\x8B", + "\xE3\xCE"=>"\xE8\x80\x92", + "\xE3\xCF"=>"\xE8\x80\x98", + "\xE3\xD0"=>"\xE8\x80\x99", + "\xE3\xD1"=>"\xE8\x80\x9C", + "\xE3\xD2"=>"\xE8\x80\xA1", + "\xE3\xD3"=>"\xE8\x80\xA8", + "\xE3\xD4"=>"\xE8\x80\xBF", + "\xE3\xD5"=>"\xE8\x80\xBB", + "\xE3\xD6"=>"\xE8\x81\x8A", + "\xE3\xD7"=>"\xE8\x81\x86", + "\xE3\xD8"=>"\xE8\x81\x92", + "\xE3\xD9"=>"\xE8\x81\x98", + "\xE3\xDA"=>"\xE8\x81\x9A", + "\xE3\xDB"=>"\xE8\x81\x9F", + "\xE3\xDC"=>"\xE8\x81\xA2", + "\xE3\xDD"=>"\xE8\x81\xA8", + "\xE3\xDE"=>"\xE8\x81\xB3", + "\xE3\xDF"=>"\xE8\x81\xB2", + "\xE3\xE0"=>"\xE8\x81\xB0", + "\xE3\xE1"=>"\xE8\x81\xB6", + "\xE3\xE2"=>"\xE8\x81\xB9", + "\xE3\xE3"=>"\xE8\x81\xBD", + "\xE3\xE4"=>"\xE8\x81\xBF", + "\xE3\xE5"=>"\xE8\x82\x84", + "\xE3\xE6"=>"\xE8\x82\x86", + "\xE3\xE7"=>"\xE8\x82\x85", + "\xE3\xE8"=>"\xE8\x82\x9B", + "\xE3\xE9"=>"\xE8\x82\x93", + "\xE3\xEA"=>"\xE8\x82\x9A", + "\xE3\xEB"=>"\xE8\x82\xAD", + "\xE3\xEC"=>"\xE5\x86\x90", + "\xE3\xED"=>"\xE8\x82\xAC", + "\xE3\xEE"=>"\xE8\x83\x9B", + "\xE3\xEF"=>"\xE8\x83\xA5", + "\xE3\xF0"=>"\xE8\x83\x99", + "\xE3\xF1"=>"\xE8\x83\x9D", + "\xE3\xF2"=>"\xE8\x83\x84", + "\xE3\xF3"=>"\xE8\x83\x9A", + "\xE3\xF4"=>"\xE8\x83\x96", + "\xE3\xF5"=>"\xE8\x84\x89", + "\xE3\xF6"=>"\xE8\x83\xAF", + "\xE3\xF7"=>"\xE8\x83\xB1", + "\xE3\xF8"=>"\xE8\x84\x9B", + "\xE3\xF9"=>"\xE8\x84\xA9", + "\xE3\xFA"=>"\xE8\x84\xA3", + "\xE3\xFB"=>"\xE8\x84\xAF", + "\xE3\xFC"=>"\xE8\x85\x8B", + "\xE4\x40"=>"\xE9\x9A\x8B", + "\xE4\x41"=>"\xE8\x85\x86", + "\xE4\x42"=>"\xE8\x84\xBE", + "\xE4\x43"=>"\xE8\x85\x93", + "\xE4\x44"=>"\xE8\x85\x91", + "\xE4\x45"=>"\xE8\x83\xBC", + "\xE4\x46"=>"\xE8\x85\xB1", + "\xE4\x47"=>"\xE8\x85\xAE", + "\xE4\x48"=>"\xE8\x85\xA5", + "\xE4\x49"=>"\xE8\x85\xA6", + "\xE4\x4A"=>"\xE8\x85\xB4", + "\xE4\x4B"=>"\xE8\x86\x83", + "\xE4\x4C"=>"\xE8\x86\x88", + "\xE4\x4D"=>"\xE8\x86\x8A", + "\xE4\x4E"=>"\xE8\x86\x80", + "\xE4\x4F"=>"\xE8\x86\x82", + "\xE4\x50"=>"\xE8\x86\xA0", + "\xE4\x51"=>"\xE8\x86\x95", + "\xE4\x52"=>"\xE8\x86\xA4", + "\xE4\x53"=>"\xE8\x86\xA3", + "\xE4\x54"=>"\xE8\x85\x9F", + "\xE4\x55"=>"\xE8\x86\x93", + "\xE4\x56"=>"\xE8\x86\xA9", + "\xE4\x57"=>"\xE8\x86\xB0", + "\xE4\x58"=>"\xE8\x86\xB5", + "\xE4\x59"=>"\xE8\x86\xBE", + "\xE4\x5A"=>"\xE8\x86\xB8", + "\xE4\x5B"=>"\xE8\x86\xBD", + "\xE4\x5C"=>"\xE8\x87\x80", + "\xE4\x5D"=>"\xE8\x87\x82", + "\xE4\x5E"=>"\xE8\x86\xBA", + "\xE4\x5F"=>"\xE8\x87\x89", + "\xE4\x60"=>"\xE8\x87\x8D", + "\xE4\x61"=>"\xE8\x87\x91", + "\xE4\x62"=>"\xE8\x87\x99", + "\xE4\x63"=>"\xE8\x87\x98", + "\xE4\x64"=>"\xE8\x87\x88", + "\xE4\x65"=>"\xE8\x87\x9A", + "\xE4\x66"=>"\xE8\x87\x9F", + "\xE4\x67"=>"\xE8\x87\xA0", + "\xE4\x68"=>"\xE8\x87\xA7", + "\xE4\x69"=>"\xE8\x87\xBA", + "\xE4\x6A"=>"\xE8\x87\xBB", + "\xE4\x6B"=>"\xE8\x87\xBE", + "\xE4\x6C"=>"\xE8\x88\x81", + "\xE4\x6D"=>"\xE8\x88\x82", + "\xE4\x6E"=>"\xE8\x88\x85", + "\xE4\x6F"=>"\xE8\x88\x87", + "\xE4\x70"=>"\xE8\x88\x8A", + "\xE4\x71"=>"\xE8\x88\x8D", + "\xE4\x72"=>"\xE8\x88\x90", + "\xE4\x73"=>"\xE8\x88\x96", + "\xE4\x74"=>"\xE8\x88\xA9", + "\xE4\x75"=>"\xE8\x88\xAB", + "\xE4\x76"=>"\xE8\x88\xB8", + "\xE4\x77"=>"\xE8\x88\xB3", + "\xE4\x78"=>"\xE8\x89\x80", + "\xE4\x79"=>"\xE8\x89\x99", + "\xE4\x7A"=>"\xE8\x89\x98", + "\xE4\x7B"=>"\xE8\x89\x9D", + "\xE4\x7C"=>"\xE8\x89\x9A", + "\xE4\x7D"=>"\xE8\x89\x9F", + "\xE4\x7E"=>"\xE8\x89\xA4", + "\xE4\x80"=>"\xE8\x89\xA2", + "\xE4\x81"=>"\xE8\x89\xA8", + "\xE4\x82"=>"\xE8\x89\xAA", + "\xE4\x83"=>"\xE8\x89\xAB", + "\xE4\x84"=>"\xE8\x88\xAE", + "\xE4\x85"=>"\xE8\x89\xB1", + "\xE4\x86"=>"\xE8\x89\xB7", + "\xE4\x87"=>"\xE8\x89\xB8", + "\xE4\x88"=>"\xE8\x89\xBE", + "\xE4\x89"=>"\xE8\x8A\x8D", + "\xE4\x8A"=>"\xE8\x8A\x92", + "\xE4\x8B"=>"\xE8\x8A\xAB", + "\xE4\x8C"=>"\xE8\x8A\x9F", + "\xE4\x8D"=>"\xE8\x8A\xBB", + "\xE4\x8E"=>"\xE8\x8A\xAC", + "\xE4\x8F"=>"\xE8\x8B\xA1", + "\xE4\x90"=>"\xE8\x8B\xA3", + "\xE4\x91"=>"\xE8\x8B\x9F", + "\xE4\x92"=>"\xE8\x8B\x92", + "\xE4\x93"=>"\xE8\x8B\xB4", + "\xE4\x94"=>"\xE8\x8B\xB3", + "\xE4\x95"=>"\xE8\x8B\xBA", + "\xE4\x96"=>"\xE8\x8E\x93", + "\xE4\x97"=>"\xE8\x8C\x83", + "\xE4\x98"=>"\xE8\x8B\xBB", + "\xE4\x99"=>"\xE8\x8B\xB9", + "\xE4\x9A"=>"\xE8\x8B\x9E", + "\xE4\x9B"=>"\xE8\x8C\x86", + "\xE4\x9C"=>"\xE8\x8B\x9C", + "\xE4\x9D"=>"\xE8\x8C\x89", + "\xE4\x9E"=>"\xE8\x8B\x99", + "\xE4\x9F"=>"\xE8\x8C\xB5", + "\xE4\xA0"=>"\xE8\x8C\xB4", + "\xE4\xA1"=>"\xE8\x8C\x96", + "\xE4\xA2"=>"\xE8\x8C\xB2", + "\xE4\xA3"=>"\xE8\x8C\xB1", + "\xE4\xA4"=>"\xE8\x8D\x80", + "\xE4\xA5"=>"\xE8\x8C\xB9", + "\xE4\xA6"=>"\xE8\x8D\x90", + "\xE4\xA7"=>"\xE8\x8D\x85", + "\xE4\xA8"=>"\xE8\x8C\xAF", + "\xE4\xA9"=>"\xE8\x8C\xAB", + "\xE4\xAA"=>"\xE8\x8C\x97", + "\xE4\xAB"=>"\xE8\x8C\x98", + "\xE4\xAC"=>"\xE8\x8E\x85", + "\xE4\xAD"=>"\xE8\x8E\x9A", + "\xE4\xAE"=>"\xE8\x8E\xAA", + "\xE4\xAF"=>"\xE8\x8E\x9F", + "\xE4\xB0"=>"\xE8\x8E\xA2", + "\xE4\xB1"=>"\xE8\x8E\x96", + "\xE4\xB2"=>"\xE8\x8C\xA3", + "\xE4\xB3"=>"\xE8\x8E\x8E", + "\xE4\xB4"=>"\xE8\x8E\x87", + "\xE4\xB5"=>"\xE8\x8E\x8A", + "\xE4\xB6"=>"\xE8\x8D\xBC", + "\xE4\xB7"=>"\xE8\x8E\xB5", + "\xE4\xB8"=>"\xE8\x8D\xB3", + "\xE4\xB9"=>"\xE8\x8D\xB5", + "\xE4\xBA"=>"\xE8\x8E\xA0", + "\xE4\xBB"=>"\xE8\x8E\x89", + "\xE4\xBC"=>"\xE8\x8E\xA8", + "\xE4\xBD"=>"\xE8\x8F\xB4", + "\xE4\xBE"=>"\xE8\x90\x93", + "\xE4\xBF"=>"\xE8\x8F\xAB", + "\xE4\xC0"=>"\xE8\x8F\x8E", + "\xE4\xC1"=>"\xE8\x8F\xBD", + "\xE4\xC2"=>"\xE8\x90\x83", + "\xE4\xC3"=>"\xE8\x8F\x98", + "\xE4\xC4"=>"\xE8\x90\x8B", + "\xE4\xC5"=>"\xE8\x8F\x81", + "\xE4\xC6"=>"\xE8\x8F\xB7", + "\xE4\xC7"=>"\xE8\x90\x87", + "\xE4\xC8"=>"\xE8\x8F\xA0", + "\xE4\xC9"=>"\xE8\x8F\xB2", + "\xE4\xCA"=>"\xE8\x90\x8D", + "\xE4\xCB"=>"\xE8\x90\xA2", + "\xE4\xCC"=>"\xE8\x90\xA0", + "\xE4\xCD"=>"\xE8\x8E\xBD", + "\xE4\xCE"=>"\xE8\x90\xB8", + "\xE4\xCF"=>"\xE8\x94\x86", + "\xE4\xD0"=>"\xE8\x8F\xBB", + "\xE4\xD1"=>"\xE8\x91\xAD", + "\xE4\xD2"=>"\xE8\x90\xAA", + "\xE4\xD3"=>"\xE8\x90\xBC", + "\xE4\xD4"=>"\xE8\x95\x9A", + "\xE4\xD5"=>"\xE8\x92\x84", + "\xE4\xD6"=>"\xE8\x91\xB7", + "\xE4\xD7"=>"\xE8\x91\xAB", + "\xE4\xD8"=>"\xE8\x92\xAD", + "\xE4\xD9"=>"\xE8\x91\xAE", + "\xE4\xDA"=>"\xE8\x92\x82", + "\xE4\xDB"=>"\xE8\x91\xA9", + "\xE4\xDC"=>"\xE8\x91\x86", + "\xE4\xDD"=>"\xE8\x90\xAC", + "\xE4\xDE"=>"\xE8\x91\xAF", + "\xE4\xDF"=>"\xE8\x91\xB9", + "\xE4\xE0"=>"\xE8\x90\xB5", + "\xE4\xE1"=>"\xE8\x93\x8A", + "\xE4\xE2"=>"\xE8\x91\xA2", + "\xE4\xE3"=>"\xE8\x92\xB9", + "\xE4\xE4"=>"\xE8\x92\xBF", + "\xE4\xE5"=>"\xE8\x92\x9F", + "\xE4\xE6"=>"\xE8\x93\x99", + "\xE4\xE7"=>"\xE8\x93\x8D", + "\xE4\xE8"=>"\xE8\x92\xBB", + "\xE4\xE9"=>"\xE8\x93\x9A", + "\xE4\xEA"=>"\xE8\x93\x90", + "\xE4\xEB"=>"\xE8\x93\x81", + "\xE4\xEC"=>"\xE8\x93\x86", + "\xE4\xED"=>"\xE8\x93\x96", + "\xE4\xEE"=>"\xE8\x92\xA1", + "\xE4\xEF"=>"\xE8\x94\xA1", + "\xE4\xF0"=>"\xE8\x93\xBF", + "\xE4\xF1"=>"\xE8\x93\xB4", + "\xE4\xF2"=>"\xE8\x94\x97", + "\xE4\xF3"=>"\xE8\x94\x98", + "\xE4\xF4"=>"\xE8\x94\xAC", + "\xE4\xF5"=>"\xE8\x94\x9F", + "\xE4\xF6"=>"\xE8\x94\x95", + "\xE4\xF7"=>"\xE8\x94\x94", + "\xE4\xF8"=>"\xE8\x93\xBC", + "\xE4\xF9"=>"\xE8\x95\x80", + "\xE4\xFA"=>"\xE8\x95\xA3", + "\xE4\xFB"=>"\xE8\x95\x98", + "\xE4\xFC"=>"\xE8\x95\x88", + "\xE5\x40"=>"\xE8\x95\x81", + "\xE5\x41"=>"\xE8\x98\x82", + "\xE5\x42"=>"\xE8\x95\x8B", + "\xE5\x43"=>"\xE8\x95\x95", + "\xE5\x44"=>"\xE8\x96\x80", + "\xE5\x45"=>"\xE8\x96\xA4", + "\xE5\x46"=>"\xE8\x96\x88", + "\xE5\x47"=>"\xE8\x96\x91", + "\xE5\x48"=>"\xE8\x96\x8A", + "\xE5\x49"=>"\xE8\x96\xA8", + "\xE5\x4A"=>"\xE8\x95\xAD", + "\xE5\x4B"=>"\xE8\x96\x94", + "\xE5\x4C"=>"\xE8\x96\x9B", + "\xE5\x4D"=>"\xE8\x97\xAA", + "\xE5\x4E"=>"\xE8\x96\x87", + "\xE5\x4F"=>"\xE8\x96\x9C", + "\xE5\x50"=>"\xE8\x95\xB7", + "\xE5\x51"=>"\xE8\x95\xBE", + "\xE5\x52"=>"\xE8\x96\x90", + "\xE5\x53"=>"\xE8\x97\x89", + "\xE5\x54"=>"\xE8\x96\xBA", + "\xE5\x55"=>"\xE8\x97\x8F", + "\xE5\x56"=>"\xE8\x96\xB9", + "\xE5\x57"=>"\xE8\x97\x90", + "\xE5\x58"=>"\xE8\x97\x95", + "\xE5\x59"=>"\xE8\x97\x9D", + "\xE5\x5A"=>"\xE8\x97\xA5", + "\xE5\x5B"=>"\xE8\x97\x9C", + "\xE5\x5C"=>"\xE8\x97\xB9", + "\xE5\x5D"=>"\xE8\x98\x8A", + "\xE5\x5E"=>"\xE8\x98\x93", + "\xE5\x5F"=>"\xE8\x98\x8B", + "\xE5\x60"=>"\xE8\x97\xBE", + "\xE5\x61"=>"\xE8\x97\xBA", + "\xE5\x62"=>"\xE8\x98\x86", + "\xE5\x63"=>"\xE8\x98\xA2", + "\xE5\x64"=>"\xE8\x98\x9A", + "\xE5\x65"=>"\xE8\x98\xB0", + "\xE5\x66"=>"\xE8\x98\xBF", + "\xE5\x67"=>"\xE8\x99\x8D", + "\xE5\x68"=>"\xE4\xB9\x95", + "\xE5\x69"=>"\xE8\x99\x94", + "\xE5\x6A"=>"\xE8\x99\x9F", + "\xE5\x6B"=>"\xE8\x99\xA7", + "\xE5\x6C"=>"\xE8\x99\xB1", + "\xE5\x6D"=>"\xE8\x9A\x93", + "\xE5\x6E"=>"\xE8\x9A\xA3", + "\xE5\x6F"=>"\xE8\x9A\xA9", + "\xE5\x70"=>"\xE8\x9A\xAA", + "\xE5\x71"=>"\xE8\x9A\x8B", + "\xE5\x72"=>"\xE8\x9A\x8C", + "\xE5\x73"=>"\xE8\x9A\xB6", + "\xE5\x74"=>"\xE8\x9A\xAF", + "\xE5\x75"=>"\xE8\x9B\x84", + "\xE5\x76"=>"\xE8\x9B\x86", + "\xE5\x77"=>"\xE8\x9A\xB0", + "\xE5\x78"=>"\xE8\x9B\x89", + "\xE5\x79"=>"\xE8\xA0\xA3", + "\xE5\x7A"=>"\xE8\x9A\xAB", + "\xE5\x7B"=>"\xE8\x9B\x94", + "\xE5\x7C"=>"\xE8\x9B\x9E", + "\xE5\x7D"=>"\xE8\x9B\xA9", + "\xE5\x7E"=>"\xE8\x9B\xAC", + "\xE5\x80"=>"\xE8\x9B\x9F", + "\xE5\x81"=>"\xE8\x9B\x9B", + "\xE5\x82"=>"\xE8\x9B\xAF", + "\xE5\x83"=>"\xE8\x9C\x92", + "\xE5\x84"=>"\xE8\x9C\x86", + "\xE5\x85"=>"\xE8\x9C\x88", + "\xE5\x86"=>"\xE8\x9C\x80", + "\xE5\x87"=>"\xE8\x9C\x83", + "\xE5\x88"=>"\xE8\x9B\xBB", + "\xE5\x89"=>"\xE8\x9C\x91", + "\xE5\x8A"=>"\xE8\x9C\x89", + "\xE5\x8B"=>"\xE8\x9C\x8D", + "\xE5\x8C"=>"\xE8\x9B\xB9", + "\xE5\x8D"=>"\xE8\x9C\x8A", + "\xE5\x8E"=>"\xE8\x9C\xB4", + "\xE5\x8F"=>"\xE8\x9C\xBF", + "\xE5\x90"=>"\xE8\x9C\xB7", + "\xE5\x91"=>"\xE8\x9C\xBB", + "\xE5\x92"=>"\xE8\x9C\xA5", + "\xE5\x93"=>"\xE8\x9C\xA9", + "\xE5\x94"=>"\xE8\x9C\x9A", + "\xE5\x95"=>"\xE8\x9D\xA0", + "\xE5\x96"=>"\xE8\x9D\x9F", + "\xE5\x97"=>"\xE8\x9D\xB8", + "\xE5\x98"=>"\xE8\x9D\x8C", + "\xE5\x99"=>"\xE8\x9D\x8E", + "\xE5\x9A"=>"\xE8\x9D\xB4", + "\xE5\x9B"=>"\xE8\x9D\x97", + "\xE5\x9C"=>"\xE8\x9D\xA8", + "\xE5\x9D"=>"\xE8\x9D\xAE", + "\xE5\x9E"=>"\xE8\x9D\x99", + "\xE5\x9F"=>"\xE8\x9D\x93", + "\xE5\xA0"=>"\xE8\x9D\xA3", + "\xE5\xA1"=>"\xE8\x9D\xAA", + "\xE5\xA2"=>"\xE8\xA0\x85", + "\xE5\xA3"=>"\xE8\x9E\xA2", + "\xE5\xA4"=>"\xE8\x9E\x9F", + "\xE5\xA5"=>"\xE8\x9E\x82", + "\xE5\xA6"=>"\xE8\x9E\xAF", + "\xE5\xA7"=>"\xE8\x9F\x8B", + "\xE5\xA8"=>"\xE8\x9E\xBD", + "\xE5\xA9"=>"\xE8\x9F\x80", + "\xE5\xAA"=>"\xE8\x9F\x90", + "\xE5\xAB"=>"\xE9\x9B\x96", + "\xE5\xAC"=>"\xE8\x9E\xAB", + "\xE5\xAD"=>"\xE8\x9F\x84", + "\xE5\xAE"=>"\xE8\x9E\xB3", + "\xE5\xAF"=>"\xE8\x9F\x87", + "\xE5\xB0"=>"\xE8\x9F\x86", + "\xE5\xB1"=>"\xE8\x9E\xBB", + "\xE5\xB2"=>"\xE8\x9F\xAF", + "\xE5\xB3"=>"\xE8\x9F\xB2", + "\xE5\xB4"=>"\xE8\x9F\xA0", + "\xE5\xB5"=>"\xE8\xA0\x8F", + "\xE5\xB6"=>"\xE8\xA0\x8D", + "\xE5\xB7"=>"\xE8\x9F\xBE", + "\xE5\xB8"=>"\xE8\x9F\xB6", + "\xE5\xB9"=>"\xE8\x9F\xB7", + "\xE5\xBA"=>"\xE8\xA0\x8E", + "\xE5\xBB"=>"\xE8\x9F\x92", + "\xE5\xBC"=>"\xE8\xA0\x91", + "\xE5\xBD"=>"\xE8\xA0\x96", + "\xE5\xBE"=>"\xE8\xA0\x95", + "\xE5\xBF"=>"\xE8\xA0\xA2", + "\xE5\xC0"=>"\xE8\xA0\xA1", + "\xE5\xC1"=>"\xE8\xA0\xB1", + "\xE5\xC2"=>"\xE8\xA0\xB6", + "\xE5\xC3"=>"\xE8\xA0\xB9", + "\xE5\xC4"=>"\xE8\xA0\xA7", + "\xE5\xC5"=>"\xE8\xA0\xBB", + "\xE5\xC6"=>"\xE8\xA1\x84", + "\xE5\xC7"=>"\xE8\xA1\x82", + "\xE5\xC8"=>"\xE8\xA1\x92", + "\xE5\xC9"=>"\xE8\xA1\x99", + "\xE5\xCA"=>"\xE8\xA1\x9E", + "\xE5\xCB"=>"\xE8\xA1\xA2", + "\xE5\xCC"=>"\xE8\xA1\xAB", + "\xE5\xCD"=>"\xE8\xA2\x81", + "\xE5\xCE"=>"\xE8\xA1\xBE", + "\xE5\xCF"=>"\xE8\xA2\x9E", + "\xE5\xD0"=>"\xE8\xA1\xB5", + "\xE5\xD1"=>"\xE8\xA1\xBD", + "\xE5\xD2"=>"\xE8\xA2\xB5", + "\xE5\xD3"=>"\xE8\xA1\xB2", + "\xE5\xD4"=>"\xE8\xA2\x82", + "\xE5\xD5"=>"\xE8\xA2\x97", + "\xE5\xD6"=>"\xE8\xA2\x92", + "\xE5\xD7"=>"\xE8\xA2\xAE", + "\xE5\xD8"=>"\xE8\xA2\x99", + "\xE5\xD9"=>"\xE8\xA2\xA2", + "\xE5\xDA"=>"\xE8\xA2\x8D", + "\xE5\xDB"=>"\xE8\xA2\xA4", + "\xE5\xDC"=>"\xE8\xA2\xB0", + "\xE5\xDD"=>"\xE8\xA2\xBF", + "\xE5\xDE"=>"\xE8\xA2\xB1", + "\xE5\xDF"=>"\xE8\xA3\x83", + "\xE5\xE0"=>"\xE8\xA3\x84", + "\xE5\xE1"=>"\xE8\xA3\x94", + "\xE5\xE2"=>"\xE8\xA3\x98", + "\xE5\xE3"=>"\xE8\xA3\x99", + "\xE5\xE4"=>"\xE8\xA3\x9D", + "\xE5\xE5"=>"\xE8\xA3\xB9", + "\xE5\xE6"=>"\xE8\xA4\x82", + "\xE5\xE7"=>"\xE8\xA3\xBC", + "\xE5\xE8"=>"\xE8\xA3\xB4", + "\xE5\xE9"=>"\xE8\xA3\xA8", + "\xE5\xEA"=>"\xE8\xA3\xB2", + "\xE5\xEB"=>"\xE8\xA4\x84", + "\xE5\xEC"=>"\xE8\xA4\x8C", + "\xE5\xED"=>"\xE8\xA4\x8A", + "\xE5\xEE"=>"\xE8\xA4\x93", + "\xE5\xEF"=>"\xE8\xA5\x83", + "\xE5\xF0"=>"\xE8\xA4\x9E", + "\xE5\xF1"=>"\xE8\xA4\xA5", + "\xE5\xF2"=>"\xE8\xA4\xAA", + "\xE5\xF3"=>"\xE8\xA4\xAB", + "\xE5\xF4"=>"\xE8\xA5\x81", + "\xE5\xF5"=>"\xE8\xA5\x84", + "\xE5\xF6"=>"\xE8\xA4\xBB", + "\xE5\xF7"=>"\xE8\xA4\xB6", + "\xE5\xF8"=>"\xE8\xA4\xB8", + "\xE5\xF9"=>"\xE8\xA5\x8C", + "\xE5\xFA"=>"\xE8\xA4\x9D", + "\xE5\xFB"=>"\xE8\xA5\xA0", + "\xE5\xFC"=>"\xE8\xA5\x9E", + "\xE6\x40"=>"\xE8\xA5\xA6", + "\xE6\x41"=>"\xE8\xA5\xA4", + "\xE6\x42"=>"\xE8\xA5\xAD", + "\xE6\x43"=>"\xE8\xA5\xAA", + "\xE6\x44"=>"\xE8\xA5\xAF", + "\xE6\x45"=>"\xE8\xA5\xB4", + "\xE6\x46"=>"\xE8\xA5\xB7", + "\xE6\x47"=>"\xE8\xA5\xBE", + "\xE6\x48"=>"\xE8\xA6\x83", + "\xE6\x49"=>"\xE8\xA6\x88", + "\xE6\x4A"=>"\xE8\xA6\x8A", + "\xE6\x4B"=>"\xE8\xA6\x93", + "\xE6\x4C"=>"\xE8\xA6\x98", + "\xE6\x4D"=>"\xE8\xA6\xA1", + "\xE6\x4E"=>"\xE8\xA6\xA9", + "\xE6\x4F"=>"\xE8\xA6\xA6", + "\xE6\x50"=>"\xE8\xA6\xAC", + "\xE6\x51"=>"\xE8\xA6\xAF", + "\xE6\x52"=>"\xE8\xA6\xB2", + "\xE6\x53"=>"\xE8\xA6\xBA", + "\xE6\x54"=>"\xE8\xA6\xBD", + "\xE6\x55"=>"\xE8\xA6\xBF", + "\xE6\x56"=>"\xE8\xA7\x80", + "\xE6\x57"=>"\xE8\xA7\x9A", + "\xE6\x58"=>"\xE8\xA7\x9C", + "\xE6\x59"=>"\xE8\xA7\x9D", + "\xE6\x5A"=>"\xE8\xA7\xA7", + "\xE6\x5B"=>"\xE8\xA7\xB4", + "\xE6\x5C"=>"\xE8\xA7\xB8", + "\xE6\x5D"=>"\xE8\xA8\x83", + "\xE6\x5E"=>"\xE8\xA8\x96", + "\xE6\x5F"=>"\xE8\xA8\x90", + "\xE6\x60"=>"\xE8\xA8\x8C", + "\xE6\x61"=>"\xE8\xA8\x9B", + "\xE6\x62"=>"\xE8\xA8\x9D", + "\xE6\x63"=>"\xE8\xA8\xA5", + "\xE6\x64"=>"\xE8\xA8\xB6", + "\xE6\x65"=>"\xE8\xA9\x81", + "\xE6\x66"=>"\xE8\xA9\x9B", + "\xE6\x67"=>"\xE8\xA9\x92", + "\xE6\x68"=>"\xE8\xA9\x86", + "\xE6\x69"=>"\xE8\xA9\x88", + "\xE6\x6A"=>"\xE8\xA9\xBC", + "\xE6\x6B"=>"\xE8\xA9\xAD", + "\xE6\x6C"=>"\xE8\xA9\xAC", + "\xE6\x6D"=>"\xE8\xA9\xA2", + "\xE6\x6E"=>"\xE8\xAA\x85", + "\xE6\x6F"=>"\xE8\xAA\x82", + "\xE6\x70"=>"\xE8\xAA\x84", + "\xE6\x71"=>"\xE8\xAA\xA8", + "\xE6\x72"=>"\xE8\xAA\xA1", + "\xE6\x73"=>"\xE8\xAA\x91", + "\xE6\x74"=>"\xE8\xAA\xA5", + "\xE6\x75"=>"\xE8\xAA\xA6", + "\xE6\x76"=>"\xE8\xAA\x9A", + "\xE6\x77"=>"\xE8\xAA\xA3", + "\xE6\x78"=>"\xE8\xAB\x84", + "\xE6\x79"=>"\xE8\xAB\x8D", + "\xE6\x7A"=>"\xE8\xAB\x82", + "\xE6\x7B"=>"\xE8\xAB\x9A", + "\xE6\x7C"=>"\xE8\xAB\xAB", + "\xE6\x7D"=>"\xE8\xAB\xB3", + "\xE6\x7E"=>"\xE8\xAB\xA7", + "\xE6\x80"=>"\xE8\xAB\xA4", + "\xE6\x81"=>"\xE8\xAB\xB1", + "\xE6\x82"=>"\xE8\xAC\x94", + "\xE6\x83"=>"\xE8\xAB\xA0", + "\xE6\x84"=>"\xE8\xAB\xA2", + "\xE6\x85"=>"\xE8\xAB\xB7", + "\xE6\x86"=>"\xE8\xAB\x9E", + "\xE6\x87"=>"\xE8\xAB\x9B", + "\xE6\x88"=>"\xE8\xAC\x8C", + "\xE6\x89"=>"\xE8\xAC\x87", + "\xE6\x8A"=>"\xE8\xAC\x9A", + "\xE6\x8B"=>"\xE8\xAB\xA1", + "\xE6\x8C"=>"\xE8\xAC\x96", + "\xE6\x8D"=>"\xE8\xAC\x90", + "\xE6\x8E"=>"\xE8\xAC\x97", + "\xE6\x8F"=>"\xE8\xAC\xA0", + "\xE6\x90"=>"\xE8\xAC\xB3", + "\xE6\x91"=>"\xE9\x9E\xAB", + "\xE6\x92"=>"\xE8\xAC\xA6", + "\xE6\x93"=>"\xE8\xAC\xAB", + "\xE6\x94"=>"\xE8\xAC\xBE", + "\xE6\x95"=>"\xE8\xAC\xA8", + "\xE6\x96"=>"\xE8\xAD\x81", + "\xE6\x97"=>"\xE8\xAD\x8C", + "\xE6\x98"=>"\xE8\xAD\x8F", + "\xE6\x99"=>"\xE8\xAD\x8E", + "\xE6\x9A"=>"\xE8\xAD\x89", + "\xE6\x9B"=>"\xE8\xAD\x96", + "\xE6\x9C"=>"\xE8\xAD\x9B", + "\xE6\x9D"=>"\xE8\xAD\x9A", + "\xE6\x9E"=>"\xE8\xAD\xAB", + "\xE6\x9F"=>"\xE8\xAD\x9F", + "\xE6\xA0"=>"\xE8\xAD\xAC", + "\xE6\xA1"=>"\xE8\xAD\xAF", + "\xE6\xA2"=>"\xE8\xAD\xB4", + "\xE6\xA3"=>"\xE8\xAD\xBD", + "\xE6\xA4"=>"\xE8\xAE\x80", + "\xE6\xA5"=>"\xE8\xAE\x8C", + "\xE6\xA6"=>"\xE8\xAE\x8E", + "\xE6\xA7"=>"\xE8\xAE\x92", + "\xE6\xA8"=>"\xE8\xAE\x93", + "\xE6\xA9"=>"\xE8\xAE\x96", + "\xE6\xAA"=>"\xE8\xAE\x99", + "\xE6\xAB"=>"\xE8\xAE\x9A", + "\xE6\xAC"=>"\xE8\xB0\xBA", + "\xE6\xAD"=>"\xE8\xB1\x81", + "\xE6\xAE"=>"\xE8\xB0\xBF", + "\xE6\xAF"=>"\xE8\xB1\x88", + "\xE6\xB0"=>"\xE8\xB1\x8C", + "\xE6\xB1"=>"\xE8\xB1\x8E", + "\xE6\xB2"=>"\xE8\xB1\x90", + "\xE6\xB3"=>"\xE8\xB1\x95", + "\xE6\xB4"=>"\xE8\xB1\xA2", + "\xE6\xB5"=>"\xE8\xB1\xAC", + "\xE6\xB6"=>"\xE8\xB1\xB8", + "\xE6\xB7"=>"\xE8\xB1\xBA", + "\xE6\xB8"=>"\xE8\xB2\x82", + "\xE6\xB9"=>"\xE8\xB2\x89", + "\xE6\xBA"=>"\xE8\xB2\x85", + "\xE6\xBB"=>"\xE8\xB2\x8A", + "\xE6\xBC"=>"\xE8\xB2\x8D", + "\xE6\xBD"=>"\xE8\xB2\x8E", + "\xE6\xBE"=>"\xE8\xB2\x94", + "\xE6\xBF"=>"\xE8\xB1\xBC", + "\xE6\xC0"=>"\xE8\xB2\x98", + "\xE6\xC1"=>"\xE6\x88\x9D", + "\xE6\xC2"=>"\xE8\xB2\xAD", + "\xE6\xC3"=>"\xE8\xB2\xAA", + "\xE6\xC4"=>"\xE8\xB2\xBD", + "\xE6\xC5"=>"\xE8\xB2\xB2", + "\xE6\xC6"=>"\xE8\xB2\xB3", + "\xE6\xC7"=>"\xE8\xB2\xAE", + "\xE6\xC8"=>"\xE8\xB2\xB6", + "\xE6\xC9"=>"\xE8\xB3\x88", + "\xE6\xCA"=>"\xE8\xB3\x81", + "\xE6\xCB"=>"\xE8\xB3\xA4", + "\xE6\xCC"=>"\xE8\xB3\xA3", + "\xE6\xCD"=>"\xE8\xB3\x9A", + "\xE6\xCE"=>"\xE8\xB3\xBD", + "\xE6\xCF"=>"\xE8\xB3\xBA", + "\xE6\xD0"=>"\xE8\xB3\xBB", + "\xE6\xD1"=>"\xE8\xB4\x84", + "\xE6\xD2"=>"\xE8\xB4\x85", + "\xE6\xD3"=>"\xE8\xB4\x8A", + "\xE6\xD4"=>"\xE8\xB4\x87", + "\xE6\xD5"=>"\xE8\xB4\x8F", + "\xE6\xD6"=>"\xE8\xB4\x8D", + "\xE6\xD7"=>"\xE8\xB4\x90", + "\xE6\xD8"=>"\xE9\xBD\x8E", + "\xE6\xD9"=>"\xE8\xB4\x93", + "\xE6\xDA"=>"\xE8\xB3\x8D", + "\xE6\xDB"=>"\xE8\xB4\x94", + "\xE6\xDC"=>"\xE8\xB4\x96", + "\xE6\xDD"=>"\xE8\xB5\xA7", + "\xE6\xDE"=>"\xE8\xB5\xAD", + "\xE6\xDF"=>"\xE8\xB5\xB1", + "\xE6\xE0"=>"\xE8\xB5\xB3", + "\xE6\xE1"=>"\xE8\xB6\x81", + "\xE6\xE2"=>"\xE8\xB6\x99", + "\xE6\xE3"=>"\xE8\xB7\x82", + "\xE6\xE4"=>"\xE8\xB6\xBE", + "\xE6\xE5"=>"\xE8\xB6\xBA", + "\xE6\xE6"=>"\xE8\xB7\x8F", + "\xE6\xE7"=>"\xE8\xB7\x9A", + "\xE6\xE8"=>"\xE8\xB7\x96", + "\xE6\xE9"=>"\xE8\xB7\x8C", + "\xE6\xEA"=>"\xE8\xB7\x9B", + "\xE6\xEB"=>"\xE8\xB7\x8B", + "\xE6\xEC"=>"\xE8\xB7\xAA", + "\xE6\xED"=>"\xE8\xB7\xAB", + "\xE6\xEE"=>"\xE8\xB7\x9F", + "\xE6\xEF"=>"\xE8\xB7\xA3", + "\xE6\xF0"=>"\xE8\xB7\xBC", + "\xE6\xF1"=>"\xE8\xB8\x88", + "\xE6\xF2"=>"\xE8\xB8\x89", + "\xE6\xF3"=>"\xE8\xB7\xBF", + "\xE6\xF4"=>"\xE8\xB8\x9D", + "\xE6\xF5"=>"\xE8\xB8\x9E", + "\xE6\xF6"=>"\xE8\xB8\x90", + "\xE6\xF7"=>"\xE8\xB8\x9F", + "\xE6\xF8"=>"\xE8\xB9\x82", + "\xE6\xF9"=>"\xE8\xB8\xB5", + "\xE6\xFA"=>"\xE8\xB8\xB0", + "\xE6\xFB"=>"\xE8\xB8\xB4", + "\xE6\xFC"=>"\xE8\xB9\x8A", + "\xE7\x40"=>"\xE8\xB9\x87", + "\xE7\x41"=>"\xE8\xB9\x89", + "\xE7\x42"=>"\xE8\xB9\x8C", + "\xE7\x43"=>"\xE8\xB9\x90", + "\xE7\x44"=>"\xE8\xB9\x88", + "\xE7\x45"=>"\xE8\xB9\x99", + "\xE7\x46"=>"\xE8\xB9\xA4", + "\xE7\x47"=>"\xE8\xB9\xA0", + "\xE7\x48"=>"\xE8\xB8\xAA", + "\xE7\x49"=>"\xE8\xB9\xA3", + "\xE7\x4A"=>"\xE8\xB9\x95", + "\xE7\x4B"=>"\xE8\xB9\xB6", + "\xE7\x4C"=>"\xE8\xB9\xB2", + "\xE7\x4D"=>"\xE8\xB9\xBC", + "\xE7\x4E"=>"\xE8\xBA\x81", + "\xE7\x4F"=>"\xE8\xBA\x87", + "\xE7\x50"=>"\xE8\xBA\x85", + "\xE7\x51"=>"\xE8\xBA\x84", + "\xE7\x52"=>"\xE8\xBA\x8B", + "\xE7\x53"=>"\xE8\xBA\x8A", + "\xE7\x54"=>"\xE8\xBA\x93", + "\xE7\x55"=>"\xE8\xBA\x91", + "\xE7\x56"=>"\xE8\xBA\x94", + "\xE7\x57"=>"\xE8\xBA\x99", + "\xE7\x58"=>"\xE8\xBA\xAA", + "\xE7\x59"=>"\xE8\xBA\xA1", + "\xE7\x5A"=>"\xE8\xBA\xAC", + "\xE7\x5B"=>"\xE8\xBA\xB0", + "\xE7\x5C"=>"\xE8\xBB\x86", + "\xE7\x5D"=>"\xE8\xBA\xB1", + "\xE7\x5E"=>"\xE8\xBA\xBE", + "\xE7\x5F"=>"\xE8\xBB\x85", + "\xE7\x60"=>"\xE8\xBB\x88", + "\xE7\x61"=>"\xE8\xBB\x8B", + "\xE7\x62"=>"\xE8\xBB\x9B", + "\xE7\x63"=>"\xE8\xBB\xA3", + "\xE7\x64"=>"\xE8\xBB\xBC", + "\xE7\x65"=>"\xE8\xBB\xBB", + "\xE7\x66"=>"\xE8\xBB\xAB", + "\xE7\x67"=>"\xE8\xBB\xBE", + "\xE7\x68"=>"\xE8\xBC\x8A", + "\xE7\x69"=>"\xE8\xBC\x85", + "\xE7\x6A"=>"\xE8\xBC\x95", + "\xE7\x6B"=>"\xE8\xBC\x92", + "\xE7\x6C"=>"\xE8\xBC\x99", + "\xE7\x6D"=>"\xE8\xBC\x93", + "\xE7\x6E"=>"\xE8\xBC\x9C", + "\xE7\x6F"=>"\xE8\xBC\x9F", + "\xE7\x70"=>"\xE8\xBC\x9B", + "\xE7\x71"=>"\xE8\xBC\x8C", + "\xE7\x72"=>"\xE8\xBC\xA6", + "\xE7\x73"=>"\xE8\xBC\xB3", + "\xE7\x74"=>"\xE8\xBC\xBB", + "\xE7\x75"=>"\xE8\xBC\xB9", + "\xE7\x76"=>"\xE8\xBD\x85", + "\xE7\x77"=>"\xE8\xBD\x82", + "\xE7\x78"=>"\xE8\xBC\xBE", + "\xE7\x79"=>"\xE8\xBD\x8C", + "\xE7\x7A"=>"\xE8\xBD\x89", + "\xE7\x7B"=>"\xE8\xBD\x86", + "\xE7\x7C"=>"\xE8\xBD\x8E", + "\xE7\x7D"=>"\xE8\xBD\x97", + "\xE7\x7E"=>"\xE8\xBD\x9C", + "\xE7\x80"=>"\xE8\xBD\xA2", + "\xE7\x81"=>"\xE8\xBD\xA3", + "\xE7\x82"=>"\xE8\xBD\xA4", + "\xE7\x83"=>"\xE8\xBE\x9C", + "\xE7\x84"=>"\xE8\xBE\x9F", + "\xE7\x85"=>"\xE8\xBE\xA3", + "\xE7\x86"=>"\xE8\xBE\xAD", + "\xE7\x87"=>"\xE8\xBE\xAF", + "\xE7\x88"=>"\xE8\xBE\xB7", + "\xE7\x89"=>"\xE8\xBF\x9A", + "\xE7\x8A"=>"\xE8\xBF\xA5", + "\xE7\x8B"=>"\xE8\xBF\xA2", + "\xE7\x8C"=>"\xE8\xBF\xAA", + "\xE7\x8D"=>"\xE8\xBF\xAF", + "\xE7\x8E"=>"\xE9\x82\x87", + "\xE7\x8F"=>"\xE8\xBF\xB4", + "\xE7\x90"=>"\xE9\x80\x85", + "\xE7\x91"=>"\xE8\xBF\xB9", + "\xE7\x92"=>"\xE8\xBF\xBA", + "\xE7\x93"=>"\xE9\x80\x91", + "\xE7\x94"=>"\xE9\x80\x95", + "\xE7\x95"=>"\xE9\x80\xA1", + "\xE7\x96"=>"\xE9\x80\x8D", + "\xE7\x97"=>"\xE9\x80\x9E", + "\xE7\x98"=>"\xE9\x80\x96", + "\xE7\x99"=>"\xE9\x80\x8B", + "\xE7\x9A"=>"\xE9\x80\xA7", + "\xE7\x9B"=>"\xE9\x80\xB6", + "\xE7\x9C"=>"\xE9\x80\xB5", + "\xE7\x9D"=>"\xE9\x80\xB9", + "\xE7\x9E"=>"\xE8\xBF\xB8", + "\xE7\x9F"=>"\xE9\x81\x8F", + "\xE7\xA0"=>"\xE9\x81\x90", + "\xE7\xA1"=>"\xE9\x81\x91", + "\xE7\xA2"=>"\xE9\x81\x92", + "\xE7\xA3"=>"\xE9\x80\x8E", + "\xE7\xA4"=>"\xE9\x81\x89", + "\xE7\xA5"=>"\xE9\x80\xBE", + "\xE7\xA6"=>"\xE9\x81\x96", + "\xE7\xA7"=>"\xE9\x81\x98", + "\xE7\xA8"=>"\xE9\x81\x9E", + "\xE7\xA9"=>"\xE9\x81\xA8", + "\xE7\xAA"=>"\xE9\x81\xAF", + "\xE7\xAB"=>"\xE9\x81\xB6", + "\xE7\xAC"=>"\xE9\x9A\xA8", + "\xE7\xAD"=>"\xE9\x81\xB2", + "\xE7\xAE"=>"\xE9\x82\x82", + "\xE7\xAF"=>"\xE9\x81\xBD", + "\xE7\xB0"=>"\xE9\x82\x81", + "\xE7\xB1"=>"\xE9\x82\x80", + "\xE7\xB2"=>"\xE9\x82\x8A", + "\xE7\xB3"=>"\xE9\x82\x89", + "\xE7\xB4"=>"\xE9\x82\x8F", + "\xE7\xB5"=>"\xE9\x82\xA8", + "\xE7\xB6"=>"\xE9\x82\xAF", + "\xE7\xB7"=>"\xE9\x82\xB1", + "\xE7\xB8"=>"\xE9\x82\xB5", + "\xE7\xB9"=>"\xE9\x83\xA2", + "\xE7\xBA"=>"\xE9\x83\xA4", + "\xE7\xBB"=>"\xE6\x89\x88", + "\xE7\xBC"=>"\xE9\x83\x9B", + "\xE7\xBD"=>"\xE9\x84\x82", + "\xE7\xBE"=>"\xE9\x84\x92", + "\xE7\xBF"=>"\xE9\x84\x99", + "\xE7\xC0"=>"\xE9\x84\xB2", + "\xE7\xC1"=>"\xE9\x84\xB0", + "\xE7\xC2"=>"\xE9\x85\x8A", + "\xE7\xC3"=>"\xE9\x85\x96", + "\xE7\xC4"=>"\xE9\x85\x98", + "\xE7\xC5"=>"\xE9\x85\xA3", + "\xE7\xC6"=>"\xE9\x85\xA5", + "\xE7\xC7"=>"\xE9\x85\xA9", + "\xE7\xC8"=>"\xE9\x85\xB3", + "\xE7\xC9"=>"\xE9\x85\xB2", + "\xE7\xCA"=>"\xE9\x86\x8B", + "\xE7\xCB"=>"\xE9\x86\x89", + "\xE7\xCC"=>"\xE9\x86\x82", + "\xE7\xCD"=>"\xE9\x86\xA2", + "\xE7\xCE"=>"\xE9\x86\xAB", + "\xE7\xCF"=>"\xE9\x86\xAF", + "\xE7\xD0"=>"\xE9\x86\xAA", + "\xE7\xD1"=>"\xE9\x86\xB5", + "\xE7\xD2"=>"\xE9\x86\xB4", + "\xE7\xD3"=>"\xE9\x86\xBA", + "\xE7\xD4"=>"\xE9\x87\x80", + "\xE7\xD5"=>"\xE9\x87\x81", + "\xE7\xD6"=>"\xE9\x87\x89", + "\xE7\xD7"=>"\xE9\x87\x8B", + "\xE7\xD8"=>"\xE9\x87\x90", + "\xE7\xD9"=>"\xE9\x87\x96", + "\xE7\xDA"=>"\xE9\x87\x9F", + "\xE7\xDB"=>"\xE9\x87\xA1", + "\xE7\xDC"=>"\xE9\x87\x9B", + "\xE7\xDD"=>"\xE9\x87\xBC", + "\xE7\xDE"=>"\xE9\x87\xB5", + "\xE7\xDF"=>"\xE9\x87\xB6", + "\xE7\xE0"=>"\xE9\x88\x9E", + "\xE7\xE1"=>"\xE9\x87\xBF", + "\xE7\xE2"=>"\xE9\x88\x94", + "\xE7\xE3"=>"\xE9\x88\xAC", + "\xE7\xE4"=>"\xE9\x88\x95", + "\xE7\xE5"=>"\xE9\x88\x91", + "\xE7\xE6"=>"\xE9\x89\x9E", + "\xE7\xE7"=>"\xE9\x89\x97", + "\xE7\xE8"=>"\xE9\x89\x85", + "\xE7\xE9"=>"\xE9\x89\x89", + "\xE7\xEA"=>"\xE9\x89\xA4", + "\xE7\xEB"=>"\xE9\x89\x88", + "\xE7\xEC"=>"\xE9\x8A\x95", + "\xE7\xED"=>"\xE9\x88\xBF", + "\xE7\xEE"=>"\xE9\x89\x8B", + "\xE7\xEF"=>"\xE9\x89\x90", + "\xE7\xF0"=>"\xE9\x8A\x9C", + "\xE7\xF1"=>"\xE9\x8A\x96", + "\xE7\xF2"=>"\xE9\x8A\x93", + "\xE7\xF3"=>"\xE9\x8A\x9B", + "\xE7\xF4"=>"\xE9\x89\x9A", + "\xE7\xF5"=>"\xE9\x8B\x8F", + "\xE7\xF6"=>"\xE9\x8A\xB9", + "\xE7\xF7"=>"\xE9\x8A\xB7", + "\xE7\xF8"=>"\xE9\x8B\xA9", + "\xE7\xF9"=>"\xE9\x8C\x8F", + "\xE7\xFA"=>"\xE9\x8B\xBA", + "\xE7\xFB"=>"\xE9\x8D\x84", + "\xE7\xFC"=>"\xE9\x8C\xAE", + "\xE8\x40"=>"\xE9\x8C\x99", + "\xE8\x41"=>"\xE9\x8C\xA2", + "\xE8\x42"=>"\xE9\x8C\x9A", + "\xE8\x43"=>"\xE9\x8C\xA3", + "\xE8\x44"=>"\xE9\x8C\xBA", + "\xE8\x45"=>"\xE9\x8C\xB5", + "\xE8\x46"=>"\xE9\x8C\xBB", + "\xE8\x47"=>"\xE9\x8D\x9C", + "\xE8\x48"=>"\xE9\x8D\xA0", + "\xE8\x49"=>"\xE9\x8D\xBC", + "\xE8\x4A"=>"\xE9\x8D\xAE", + "\xE8\x4B"=>"\xE9\x8D\x96", + "\xE8\x4C"=>"\xE9\x8E\xB0", + "\xE8\x4D"=>"\xE9\x8E\xAC", + "\xE8\x4E"=>"\xE9\x8E\xAD", + "\xE8\x4F"=>"\xE9\x8E\x94", + "\xE8\x50"=>"\xE9\x8E\xB9", + "\xE8\x51"=>"\xE9\x8F\x96", + "\xE8\x52"=>"\xE9\x8F\x97", + "\xE8\x53"=>"\xE9\x8F\xA8", + "\xE8\x54"=>"\xE9\x8F\xA5", + "\xE8\x55"=>"\xE9\x8F\x98", + "\xE8\x56"=>"\xE9\x8F\x83", + "\xE8\x57"=>"\xE9\x8F\x9D", + "\xE8\x58"=>"\xE9\x8F\x90", + "\xE8\x59"=>"\xE9\x8F\x88", + "\xE8\x5A"=>"\xE9\x8F\xA4", + "\xE8\x5B"=>"\xE9\x90\x9A", + "\xE8\x5C"=>"\xE9\x90\x94", + "\xE8\x5D"=>"\xE9\x90\x93", + "\xE8\x5E"=>"\xE9\x90\x83", + "\xE8\x5F"=>"\xE9\x90\x87", + "\xE8\x60"=>"\xE9\x90\x90", + "\xE8\x61"=>"\xE9\x90\xB6", + "\xE8\x62"=>"\xE9\x90\xAB", + "\xE8\x63"=>"\xE9\x90\xB5", + "\xE8\x64"=>"\xE9\x90\xA1", + "\xE8\x65"=>"\xE9\x90\xBA", + "\xE8\x66"=>"\xE9\x91\x81", + "\xE8\x67"=>"\xE9\x91\x92", + "\xE8\x68"=>"\xE9\x91\x84", + "\xE8\x69"=>"\xE9\x91\x9B", + "\xE8\x6A"=>"\xE9\x91\xA0", + "\xE8\x6B"=>"\xE9\x91\xA2", + "\xE8\x6C"=>"\xE9\x91\x9E", + "\xE8\x6D"=>"\xE9\x91\xAA", + "\xE8\x6E"=>"\xE9\x88\xA9", + "\xE8\x6F"=>"\xE9\x91\xB0", + "\xE8\x70"=>"\xE9\x91\xB5", + "\xE8\x71"=>"\xE9\x91\xB7", + "\xE8\x72"=>"\xE9\x91\xBD", + "\xE8\x73"=>"\xE9\x91\x9A", + "\xE8\x74"=>"\xE9\x91\xBC", + "\xE8\x75"=>"\xE9\x91\xBE", + "\xE8\x76"=>"\xE9\x92\x81", + "\xE8\x77"=>"\xE9\x91\xBF", + "\xE8\x78"=>"\xE9\x96\x82", + "\xE8\x79"=>"\xE9\x96\x87", + "\xE8\x7A"=>"\xE9\x96\x8A", + "\xE8\x7B"=>"\xE9\x96\x94", + "\xE8\x7C"=>"\xE9\x96\x96", + "\xE8\x7D"=>"\xE9\x96\x98", + "\xE8\x7E"=>"\xE9\x96\x99", + "\xE8\x80"=>"\xE9\x96\xA0", + "\xE8\x81"=>"\xE9\x96\xA8", + "\xE8\x82"=>"\xE9\x96\xA7", + "\xE8\x83"=>"\xE9\x96\xAD", + "\xE8\x84"=>"\xE9\x96\xBC", + "\xE8\x85"=>"\xE9\x96\xBB", + "\xE8\x86"=>"\xE9\x96\xB9", + "\xE8\x87"=>"\xE9\x96\xBE", + "\xE8\x88"=>"\xE9\x97\x8A", + "\xE8\x89"=>"\xE6\xBF\xB6", + "\xE8\x8A"=>"\xE9\x97\x83", + "\xE8\x8B"=>"\xE9\x97\x8D", + "\xE8\x8C"=>"\xE9\x97\x8C", + "\xE8\x8D"=>"\xE9\x97\x95", + "\xE8\x8E"=>"\xE9\x97\x94", + "\xE8\x8F"=>"\xE9\x97\x96", + "\xE8\x90"=>"\xE9\x97\x9C", + "\xE8\x91"=>"\xE9\x97\xA1", + "\xE8\x92"=>"\xE9\x97\xA5", + "\xE8\x93"=>"\xE9\x97\xA2", + "\xE8\x94"=>"\xE9\x98\xA1", + "\xE8\x95"=>"\xE9\x98\xA8", + "\xE8\x96"=>"\xE9\x98\xAE", + "\xE8\x97"=>"\xE9\x98\xAF", + "\xE8\x98"=>"\xE9\x99\x82", + "\xE8\x99"=>"\xE9\x99\x8C", + "\xE8\x9A"=>"\xE9\x99\x8F", + "\xE8\x9B"=>"\xE9\x99\x8B", + "\xE8\x9C"=>"\xE9\x99\xB7", + "\xE8\x9D"=>"\xE9\x99\x9C", + "\xE8\x9E"=>"\xE9\x99\x9E", + "\xE8\x9F"=>"\xE9\x99\x9D", + "\xE8\xA0"=>"\xE9\x99\x9F", + "\xE8\xA1"=>"\xE9\x99\xA6", + "\xE8\xA2"=>"\xE9\x99\xB2", + "\xE8\xA3"=>"\xE9\x99\xAC", + "\xE8\xA4"=>"\xE9\x9A\x8D", + "\xE8\xA5"=>"\xE9\x9A\x98", + "\xE8\xA6"=>"\xE9\x9A\x95", + "\xE8\xA7"=>"\xE9\x9A\x97", + "\xE8\xA8"=>"\xE9\x9A\xAA", + "\xE8\xA9"=>"\xE9\x9A\xA7", + "\xE8\xAA"=>"\xE9\x9A\xB1", + "\xE8\xAB"=>"\xE9\x9A\xB2", + "\xE8\xAC"=>"\xE9\x9A\xB0", + "\xE8\xAD"=>"\xE9\x9A\xB4", + "\xE8\xAE"=>"\xE9\x9A\xB6", + "\xE8\xAF"=>"\xE9\x9A\xB8", + "\xE8\xB0"=>"\xE9\x9A\xB9", + "\xE8\xB1"=>"\xE9\x9B\x8E", + "\xE8\xB2"=>"\xE9\x9B\x8B", + "\xE8\xB3"=>"\xE9\x9B\x89", + "\xE8\xB4"=>"\xE9\x9B\x8D", + "\xE8\xB5"=>"\xE8\xA5\x8D", + "\xE8\xB6"=>"\xE9\x9B\x9C", + "\xE8\xB7"=>"\xE9\x9C\x8D", + "\xE8\xB8"=>"\xE9\x9B\x95", + "\xE8\xB9"=>"\xE9\x9B\xB9", + "\xE8\xBA"=>"\xE9\x9C\x84", + "\xE8\xBB"=>"\xE9\x9C\x86", + "\xE8\xBC"=>"\xE9\x9C\x88", + "\xE8\xBD"=>"\xE9\x9C\x93", + "\xE8\xBE"=>"\xE9\x9C\x8E", + "\xE8\xBF"=>"\xE9\x9C\x91", + "\xE8\xC0"=>"\xE9\x9C\x8F", + "\xE8\xC1"=>"\xE9\x9C\x96", + "\xE8\xC2"=>"\xE9\x9C\x99", + "\xE8\xC3"=>"\xE9\x9C\xA4", + "\xE8\xC4"=>"\xE9\x9C\xAA", + "\xE8\xC5"=>"\xE9\x9C\xB0", + "\xE8\xC6"=>"\xE9\x9C\xB9", + "\xE8\xC7"=>"\xE9\x9C\xBD", + "\xE8\xC8"=>"\xE9\x9C\xBE", + "\xE8\xC9"=>"\xE9\x9D\x84", + "\xE8\xCA"=>"\xE9\x9D\x86", + "\xE8\xCB"=>"\xE9\x9D\x88", + "\xE8\xCC"=>"\xE9\x9D\x82", + "\xE8\xCD"=>"\xE9\x9D\x89", + "\xE8\xCE"=>"\xE9\x9D\x9C", + "\xE8\xCF"=>"\xE9\x9D\xA0", + "\xE8\xD0"=>"\xE9\x9D\xA4", + "\xE8\xD1"=>"\xE9\x9D\xA6", + "\xE8\xD2"=>"\xE9\x9D\xA8", + "\xE8\xD3"=>"\xE5\x8B\x92", + "\xE8\xD4"=>"\xE9\x9D\xAB", + "\xE8\xD5"=>"\xE9\x9D\xB1", + "\xE8\xD6"=>"\xE9\x9D\xB9", + "\xE8\xD7"=>"\xE9\x9E\x85", + "\xE8\xD8"=>"\xE9\x9D\xBC", + "\xE8\xD9"=>"\xE9\x9E\x81", + "\xE8\xDA"=>"\xE9\x9D\xBA", + "\xE8\xDB"=>"\xE9\x9E\x86", + "\xE8\xDC"=>"\xE9\x9E\x8B", + "\xE8\xDD"=>"\xE9\x9E\x8F", + "\xE8\xDE"=>"\xE9\x9E\x90", + "\xE8\xDF"=>"\xE9\x9E\x9C", + "\xE8\xE0"=>"\xE9\x9E\xA8", + "\xE8\xE1"=>"\xE9\x9E\xA6", + "\xE8\xE2"=>"\xE9\x9E\xA3", + "\xE8\xE3"=>"\xE9\x9E\xB3", + "\xE8\xE4"=>"\xE9\x9E\xB4", + "\xE8\xE5"=>"\xE9\x9F\x83", + "\xE8\xE6"=>"\xE9\x9F\x86", + "\xE8\xE7"=>"\xE9\x9F\x88", + "\xE8\xE8"=>"\xE9\x9F\x8B", + "\xE8\xE9"=>"\xE9\x9F\x9C", + "\xE8\xEA"=>"\xE9\x9F\xAD", + "\xE8\xEB"=>"\xE9\xBD\x8F", + "\xE8\xEC"=>"\xE9\x9F\xB2", + "\xE8\xED"=>"\xE7\xAB\x9F", + "\xE8\xEE"=>"\xE9\x9F\xB6", + "\xE8\xEF"=>"\xE9\x9F\xB5", + "\xE8\xF0"=>"\xE9\xA0\x8F", + "\xE8\xF1"=>"\xE9\xA0\x8C", + "\xE8\xF2"=>"\xE9\xA0\xB8", + "\xE8\xF3"=>"\xE9\xA0\xA4", + "\xE8\xF4"=>"\xE9\xA0\xA1", + "\xE8\xF5"=>"\xE9\xA0\xB7", + "\xE8\xF6"=>"\xE9\xA0\xBD", + "\xE8\xF7"=>"\xE9\xA1\x86", + "\xE8\xF8"=>"\xE9\xA1\x8F", + "\xE8\xF9"=>"\xE9\xA1\x8B", + "\xE8\xFA"=>"\xE9\xA1\xAB", + "\xE8\xFB"=>"\xE9\xA1\xAF", + "\xE8\xFC"=>"\xE9\xA1\xB0", + "\xE9\x40"=>"\xE9\xA1\xB1", + "\xE9\x41"=>"\xE9\xA1\xB4", + "\xE9\x42"=>"\xE9\xA1\xB3", + "\xE9\x43"=>"\xE9\xA2\xAA", + "\xE9\x44"=>"\xE9\xA2\xAF", + "\xE9\x45"=>"\xE9\xA2\xB1", + "\xE9\x46"=>"\xE9\xA2\xB6", + "\xE9\x47"=>"\xE9\xA3\x84", + "\xE9\x48"=>"\xE9\xA3\x83", + "\xE9\x49"=>"\xE9\xA3\x86", + "\xE9\x4A"=>"\xE9\xA3\xA9", + "\xE9\x4B"=>"\xE9\xA3\xAB", + "\xE9\x4C"=>"\xE9\xA4\x83", + "\xE9\x4D"=>"\xE9\xA4\x89", + "\xE9\x4E"=>"\xE9\xA4\x92", + "\xE9\x4F"=>"\xE9\xA4\x94", + "\xE9\x50"=>"\xE9\xA4\x98", + "\xE9\x51"=>"\xE9\xA4\xA1", + "\xE9\x52"=>"\xE9\xA4\x9D", + "\xE9\x53"=>"\xE9\xA4\x9E", + "\xE9\x54"=>"\xE9\xA4\xA4", + "\xE9\x55"=>"\xE9\xA4\xA0", + "\xE9\x56"=>"\xE9\xA4\xAC", + "\xE9\x57"=>"\xE9\xA4\xAE", + "\xE9\x58"=>"\xE9\xA4\xBD", + "\xE9\x59"=>"\xE9\xA4\xBE", + "\xE9\x5A"=>"\xE9\xA5\x82", + "\xE9\x5B"=>"\xE9\xA5\x89", + "\xE9\x5C"=>"\xE9\xA5\x85", + "\xE9\x5D"=>"\xE9\xA5\x90", + "\xE9\x5E"=>"\xE9\xA5\x8B", + "\xE9\x5F"=>"\xE9\xA5\x91", + "\xE9\x60"=>"\xE9\xA5\x92", + "\xE9\x61"=>"\xE9\xA5\x8C", + "\xE9\x62"=>"\xE9\xA5\x95", + "\xE9\x63"=>"\xE9\xA6\x97", + "\xE9\x64"=>"\xE9\xA6\x98", + "\xE9\x65"=>"\xE9\xA6\xA5", + "\xE9\x66"=>"\xE9\xA6\xAD", + "\xE9\x67"=>"\xE9\xA6\xAE", + "\xE9\x68"=>"\xE9\xA6\xBC", + "\xE9\x69"=>"\xE9\xA7\x9F", + "\xE9\x6A"=>"\xE9\xA7\x9B", + "\xE9\x6B"=>"\xE9\xA7\x9D", + "\xE9\x6C"=>"\xE9\xA7\x98", + "\xE9\x6D"=>"\xE9\xA7\x91", + "\xE9\x6E"=>"\xE9\xA7\xAD", + "\xE9\x6F"=>"\xE9\xA7\xAE", + "\xE9\x70"=>"\xE9\xA7\xB1", + "\xE9\x71"=>"\xE9\xA7\xB2", + "\xE9\x72"=>"\xE9\xA7\xBB", + "\xE9\x73"=>"\xE9\xA7\xB8", + "\xE9\x74"=>"\xE9\xA8\x81", + "\xE9\x75"=>"\xE9\xA8\x8F", + "\xE9\x76"=>"\xE9\xA8\x85", + "\xE9\x77"=>"\xE9\xA7\xA2", + "\xE9\x78"=>"\xE9\xA8\x99", + "\xE9\x79"=>"\xE9\xA8\xAB", + "\xE9\x7A"=>"\xE9\xA8\xB7", + "\xE9\x7B"=>"\xE9\xA9\x85", + "\xE9\x7C"=>"\xE9\xA9\x82", + "\xE9\x7D"=>"\xE9\xA9\x80", + "\xE9\x7E"=>"\xE9\xA9\x83", + "\xE9\x80"=>"\xE9\xA8\xBE", + "\xE9\x81"=>"\xE9\xA9\x95", + "\xE9\x82"=>"\xE9\xA9\x8D", + "\xE9\x83"=>"\xE9\xA9\x9B", + "\xE9\x84"=>"\xE9\xA9\x97", + "\xE9\x85"=>"\xE9\xA9\x9F", + "\xE9\x86"=>"\xE9\xA9\xA2", + "\xE9\x87"=>"\xE9\xA9\xA5", + "\xE9\x88"=>"\xE9\xA9\xA4", + "\xE9\x89"=>"\xE9\xA9\xA9", + "\xE9\x8A"=>"\xE9\xA9\xAB", + "\xE9\x8B"=>"\xE9\xA9\xAA", + "\xE9\x8C"=>"\xE9\xAA\xAD", + "\xE9\x8D"=>"\xE9\xAA\xB0", + "\xE9\x8E"=>"\xE9\xAA\xBC", + "\xE9\x8F"=>"\xE9\xAB\x80", + "\xE9\x90"=>"\xE9\xAB\x8F", + "\xE9\x91"=>"\xE9\xAB\x91", + "\xE9\x92"=>"\xE9\xAB\x93", + "\xE9\x93"=>"\xE9\xAB\x94", + "\xE9\x94"=>"\xE9\xAB\x9E", + "\xE9\x95"=>"\xE9\xAB\x9F", + "\xE9\x96"=>"\xE9\xAB\xA2", + "\xE9\x97"=>"\xE9\xAB\xA3", + "\xE9\x98"=>"\xE9\xAB\xA6", + "\xE9\x99"=>"\xE9\xAB\xAF", + "\xE9\x9A"=>"\xE9\xAB\xAB", + "\xE9\x9B"=>"\xE9\xAB\xAE", + "\xE9\x9C"=>"\xE9\xAB\xB4", + "\xE9\x9D"=>"\xE9\xAB\xB1", + "\xE9\x9E"=>"\xE9\xAB\xB7", + "\xE9\x9F"=>"\xE9\xAB\xBB", + "\xE9\xA0"=>"\xE9\xAC\x86", + "\xE9\xA1"=>"\xE9\xAC\x98", + "\xE9\xA2"=>"\xE9\xAC\x9A", + "\xE9\xA3"=>"\xE9\xAC\x9F", + "\xE9\xA4"=>"\xE9\xAC\xA2", + "\xE9\xA5"=>"\xE9\xAC\xA3", + "\xE9\xA6"=>"\xE9\xAC\xA5", + "\xE9\xA7"=>"\xE9\xAC\xA7", + "\xE9\xA8"=>"\xE9\xAC\xA8", + "\xE9\xA9"=>"\xE9\xAC\xA9", + "\xE9\xAA"=>"\xE9\xAC\xAA", + "\xE9\xAB"=>"\xE9\xAC\xAE", + "\xE9\xAC"=>"\xE9\xAC\xAF", + "\xE9\xAD"=>"\xE9\xAC\xB2", + "\xE9\xAE"=>"\xE9\xAD\x84", + "\xE9\xAF"=>"\xE9\xAD\x83", + "\xE9\xB0"=>"\xE9\xAD\x8F", + "\xE9\xB1"=>"\xE9\xAD\x8D", + "\xE9\xB2"=>"\xE9\xAD\x8E", + "\xE9\xB3"=>"\xE9\xAD\x91", + "\xE9\xB4"=>"\xE9\xAD\x98", + "\xE9\xB5"=>"\xE9\xAD\xB4", + "\xE9\xB6"=>"\xE9\xAE\x93", + "\xE9\xB7"=>"\xE9\xAE\x83", + "\xE9\xB8"=>"\xE9\xAE\x91", + "\xE9\xB9"=>"\xE9\xAE\x96", + "\xE9\xBA"=>"\xE9\xAE\x97", + "\xE9\xBB"=>"\xE9\xAE\x9F", + "\xE9\xBC"=>"\xE9\xAE\xA0", + "\xE9\xBD"=>"\xE9\xAE\xA8", + "\xE9\xBE"=>"\xE9\xAE\xB4", + "\xE9\xBF"=>"\xE9\xAF\x80", + "\xE9\xC0"=>"\xE9\xAF\x8A", + "\xE9\xC1"=>"\xE9\xAE\xB9", + "\xE9\xC2"=>"\xE9\xAF\x86", + "\xE9\xC3"=>"\xE9\xAF\x8F", + "\xE9\xC4"=>"\xE9\xAF\x91", + "\xE9\xC5"=>"\xE9\xAF\x92", + "\xE9\xC6"=>"\xE9\xAF\xA3", + "\xE9\xC7"=>"\xE9\xAF\xA2", + "\xE9\xC8"=>"\xE9\xAF\xA4", + "\xE9\xC9"=>"\xE9\xAF\x94", + "\xE9\xCA"=>"\xE9\xAF\xA1", + "\xE9\xCB"=>"\xE9\xB0\xBA", + "\xE9\xCC"=>"\xE9\xAF\xB2", + "\xE9\xCD"=>"\xE9\xAF\xB1", + "\xE9\xCE"=>"\xE9\xAF\xB0", + "\xE9\xCF"=>"\xE9\xB0\x95", + "\xE9\xD0"=>"\xE9\xB0\x94", + "\xE9\xD1"=>"\xE9\xB0\x89", + "\xE9\xD2"=>"\xE9\xB0\x93", + "\xE9\xD3"=>"\xE9\xB0\x8C", + "\xE9\xD4"=>"\xE9\xB0\x86", + "\xE9\xD5"=>"\xE9\xB0\x88", + "\xE9\xD6"=>"\xE9\xB0\x92", + "\xE9\xD7"=>"\xE9\xB0\x8A", + "\xE9\xD8"=>"\xE9\xB0\x84", + "\xE9\xD9"=>"\xE9\xB0\xAE", + "\xE9\xDA"=>"\xE9\xB0\x9B", + "\xE9\xDB"=>"\xE9\xB0\xA5", + "\xE9\xDC"=>"\xE9\xB0\xA4", + "\xE9\xDD"=>"\xE9\xB0\xA1", + "\xE9\xDE"=>"\xE9\xB0\xB0", + "\xE9\xDF"=>"\xE9\xB1\x87", + "\xE9\xE0"=>"\xE9\xB0\xB2", + "\xE9\xE1"=>"\xE9\xB1\x86", + "\xE9\xE2"=>"\xE9\xB0\xBE", + "\xE9\xE3"=>"\xE9\xB1\x9A", + "\xE9\xE4"=>"\xE9\xB1\xA0", + "\xE9\xE5"=>"\xE9\xB1\xA7", + "\xE9\xE6"=>"\xE9\xB1\xB6", + "\xE9\xE7"=>"\xE9\xB1\xB8", + "\xE9\xE8"=>"\xE9\xB3\xA7", + "\xE9\xE9"=>"\xE9\xB3\xAC", + "\xE9\xEA"=>"\xE9\xB3\xB0", + "\xE9\xEB"=>"\xE9\xB4\x89", + "\xE9\xEC"=>"\xE9\xB4\x88", + "\xE9\xED"=>"\xE9\xB3\xAB", + "\xE9\xEE"=>"\xE9\xB4\x83", + "\xE9\xEF"=>"\xE9\xB4\x86", + "\xE9\xF0"=>"\xE9\xB4\xAA", + "\xE9\xF1"=>"\xE9\xB4\xA6", + "\xE9\xF2"=>"\xE9\xB6\xAF", + "\xE9\xF3"=>"\xE9\xB4\xA3", + "\xE9\xF4"=>"\xE9\xB4\x9F", + "\xE9\xF5"=>"\xE9\xB5\x84", + "\xE9\xF6"=>"\xE9\xB4\x95", + "\xE9\xF7"=>"\xE9\xB4\x92", + "\xE9\xF8"=>"\xE9\xB5\x81", + "\xE9\xF9"=>"\xE9\xB4\xBF", + "\xE9\xFA"=>"\xE9\xB4\xBE", + "\xE9\xFB"=>"\xE9\xB5\x86", + "\xE9\xFC"=>"\xE9\xB5\x88", + "\xEA\x40"=>"\xE9\xB5\x9D", + "\xEA\x41"=>"\xE9\xB5\x9E", + "\xEA\x42"=>"\xE9\xB5\xA4", + "\xEA\x43"=>"\xE9\xB5\x91", + "\xEA\x44"=>"\xE9\xB5\x90", + "\xEA\x45"=>"\xE9\xB5\x99", + "\xEA\x46"=>"\xE9\xB5\xB2", + "\xEA\x47"=>"\xE9\xB6\x89", + "\xEA\x48"=>"\xE9\xB6\x87", + "\xEA\x49"=>"\xE9\xB6\xAB", + "\xEA\x4A"=>"\xE9\xB5\xAF", + "\xEA\x4B"=>"\xE9\xB5\xBA", + "\xEA\x4C"=>"\xE9\xB6\x9A", + "\xEA\x4D"=>"\xE9\xB6\xA4", + "\xEA\x4E"=>"\xE9\xB6\xA9", + "\xEA\x4F"=>"\xE9\xB6\xB2", + "\xEA\x50"=>"\xE9\xB7\x84", + "\xEA\x51"=>"\xE9\xB7\x81", + "\xEA\x52"=>"\xE9\xB6\xBB", + "\xEA\x53"=>"\xE9\xB6\xB8", + "\xEA\x54"=>"\xE9\xB6\xBA", + "\xEA\x55"=>"\xE9\xB7\x86", + "\xEA\x56"=>"\xE9\xB7\x8F", + "\xEA\x57"=>"\xE9\xB7\x82", + "\xEA\x58"=>"\xE9\xB7\x99", + "\xEA\x59"=>"\xE9\xB7\x93", + "\xEA\x5A"=>"\xE9\xB7\xB8", + "\xEA\x5B"=>"\xE9\xB7\xA6", + "\xEA\x5C"=>"\xE9\xB7\xAD", + "\xEA\x5D"=>"\xE9\xB7\xAF", + "\xEA\x5E"=>"\xE9\xB7\xBD", + "\xEA\x5F"=>"\xE9\xB8\x9A", + "\xEA\x60"=>"\xE9\xB8\x9B", + "\xEA\x61"=>"\xE9\xB8\x9E", + "\xEA\x62"=>"\xE9\xB9\xB5", + "\xEA\x63"=>"\xE9\xB9\xB9", + "\xEA\x64"=>"\xE9\xB9\xBD", + "\xEA\x65"=>"\xE9\xBA\x81", + "\xEA\x66"=>"\xE9\xBA\x88", + "\xEA\x67"=>"\xE9\xBA\x8B", + "\xEA\x68"=>"\xE9\xBA\x8C", + "\xEA\x69"=>"\xE9\xBA\x92", + "\xEA\x6A"=>"\xE9\xBA\x95", + "\xEA\x6B"=>"\xE9\xBA\x91", + "\xEA\x6C"=>"\xE9\xBA\x9D", + "\xEA\x6D"=>"\xE9\xBA\xA5", + "\xEA\x6E"=>"\xE9\xBA\xA9", + "\xEA\x6F"=>"\xE9\xBA\xB8", + "\xEA\x70"=>"\xE9\xBA\xAA", + "\xEA\x71"=>"\xE9\xBA\xAD", + "\xEA\x72"=>"\xE9\x9D\xA1", + "\xEA\x73"=>"\xE9\xBB\x8C", + "\xEA\x74"=>"\xE9\xBB\x8E", + "\xEA\x75"=>"\xE9\xBB\x8F", + "\xEA\x76"=>"\xE9\xBB\x90", + "\xEA\x77"=>"\xE9\xBB\x94", + "\xEA\x78"=>"\xE9\xBB\x9C", + "\xEA\x79"=>"\xE9\xBB\x9E", + "\xEA\x7A"=>"\xE9\xBB\x9D", + "\xEA\x7B"=>"\xE9\xBB\xA0", + "\xEA\x7C"=>"\xE9\xBB\xA5", + "\xEA\x7D"=>"\xE9\xBB\xA8", + "\xEA\x7E"=>"\xE9\xBB\xAF", + "\xEA\x80"=>"\xE9\xBB\xB4", + "\xEA\x81"=>"\xE9\xBB\xB6", + "\xEA\x82"=>"\xE9\xBB\xB7", + "\xEA\x83"=>"\xE9\xBB\xB9", + "\xEA\x84"=>"\xE9\xBB\xBB", + "\xEA\x85"=>"\xE9\xBB\xBC", + "\xEA\x86"=>"\xE9\xBB\xBD", + "\xEA\x87"=>"\xE9\xBC\x87", + "\xEA\x88"=>"\xE9\xBC\x88", + "\xEA\x89"=>"\xE7\x9A\xB7", + "\xEA\x8A"=>"\xE9\xBC\x95", + "\xEA\x8B"=>"\xE9\xBC\xA1", + "\xEA\x8C"=>"\xE9\xBC\xAC", + "\xEA\x8D"=>"\xE9\xBC\xBE", + "\xEA\x8E"=>"\xE9\xBD\x8A", + "\xEA\x8F"=>"\xE9\xBD\x92", + "\xEA\x90"=>"\xE9\xBD\x94", + "\xEA\x91"=>"\xE9\xBD\xA3", + "\xEA\x92"=>"\xE9\xBD\x9F", + "\xEA\x93"=>"\xE9\xBD\xA0", + "\xEA\x94"=>"\xE9\xBD\xA1", + "\xEA\x95"=>"\xE9\xBD\xA6", + "\xEA\x96"=>"\xE9\xBD\xA7", + "\xEA\x97"=>"\xE9\xBD\xAC", + "\xEA\x98"=>"\xE9\xBD\xAA", + "\xEA\x99"=>"\xE9\xBD\xB7", + "\xEA\x9A"=>"\xE9\xBD\xB2", + "\xEA\x9B"=>"\xE9\xBD\xB6", + "\xEA\x9C"=>"\xE9\xBE\x95", + "\xEA\x9D"=>"\xE9\xBE\x9C", + "\xEA\x9E"=>"\xE9\xBE\xA0", + "\xEA\x9F"=>"\xE5\xA0\xAF", + "\xEA\xA0"=>"\xE6\xA7\x87", + "\xEA\xA1"=>"\xE9\x81\x99", + "\xEA\xA2"=>"\xE7\x91\xA4", + "\xEA\xA3"=>"\xE5\x87\x9C", + "\xEA\xA4"=>"\xE7\x86\x99" + ); + return strtr($string, $transform); +} + +function euc_kr($string) +{ + static $transform = array( + "\x5c" => "\xE2\x82\xA9", // KX X 1001 + "\x81\x41" => "\xEA\xB0\x82", + "\x81\x42" => "\xEA\xB0\x83", + "\x81\x43" => "\xEA\xB0\x85", + "\x81\x44" => "\xEA\xB0\x86", + "\x81\x45" => "\xEA\xB0\x8B", + "\x81\x46" => "\xEA\xB0\x8C", + "\x81\x47" => "\xEA\xB0\x8D", + "\x81\x48" => "\xEA\xB0\x8E", + "\x81\x49" => "\xEA\xB0\x8F", + "\x81\x4A" => "\xEA\xB0\x98", + "\x81\x4B" => "\xEA\xB0\x9E", + "\x81\x4C" => "\xEA\xB0\x9F", + "\x81\x4D" => "\xEA\xB0\xA1", + "\x81\x4E" => "\xEA\xB0\xA2", + "\x81\x4F" => "\xEA\xB0\xA3", + "\x81\x50" => "\xEA\xB0\xA5", + "\x81\x51" => "\xEA\xB0\xA6", + "\x81\x52" => "\xEA\xB0\xA7", + "\x81\x53" => "\xEA\xB0\xA8", + "\x81\x54" => "\xEA\xB0\xA9", + "\x81\x55" => "\xEA\xB0\xAA", + "\x81\x56" => "\xEA\xB0\xAB", + "\x81\x57" => "\xEA\xB0\xAE", + "\x81\x58" => "\xEA\xB0\xB2", + "\x81\x59" => "\xEA\xB0\xB3", + "\x81\x5A" => "\xEA\xB0\xB4", + "\x81\x61" => "\xEA\xB0\xB5", + "\x81\x62" => "\xEA\xB0\xB6", + "\x81\x63" => "\xEA\xB0\xB7", + "\x81\x64" => "\xEA\xB0\xBA", + "\x81\x65" => "\xEA\xB0\xBB", + "\x81\x66" => "\xEA\xB0\xBD", + "\x81\x67" => "\xEA\xB0\xBE", + "\x81\x68" => "\xEA\xB0\xBF", + "\x81\x69" => "\xEA\xB1\x81", + "\x81\x6A" => "\xEA\xB1\x82", + "\x81\x6B" => "\xEA\xB1\x83", + "\x81\x6C" => "\xEA\xB1\x84", + "\x81\x6D" => "\xEA\xB1\x85", + "\x81\x6E" => "\xEA\xB1\x86", + "\x81\x6F" => "\xEA\xB1\x87", + "\x81\x70" => "\xEA\xB1\x88", + "\x81\x71" => "\xEA\xB1\x89", + "\x81\x72" => "\xEA\xB1\x8A", + "\x81\x73" => "\xEA\xB1\x8C", + "\x81\x74" => "\xEA\xB1\x8E", + "\x81\x75" => "\xEA\xB1\x8F", + "\x81\x76" => "\xEA\xB1\x90", + "\x81\x77" => "\xEA\xB1\x91", + "\x81\x78" => "\xEA\xB1\x92", + "\x81\x79" => "\xEA\xB1\x93", + "\x81\x7A" => "\xEA\xB1\x95", + "\x81\x81" => "\xEA\xB1\x96", + "\x81\x82" => "\xEA\xB1\x97", + "\x81\x83" => "\xEA\xB1\x99", + "\x81\x84" => "\xEA\xB1\x9A", + "\x81\x85" => "\xEA\xB1\x9B", + "\x81\x86" => "\xEA\xB1\x9D", + "\x81\x87" => "\xEA\xB1\x9E", + "\x81\x88" => "\xEA\xB1\x9F", + "\x81\x89" => "\xEA\xB1\xA0", + "\x81\x8A" => "\xEA\xB1\xA1", + "\x81\x8B" => "\xEA\xB1\xA2", + "\x81\x8C" => "\xEA\xB1\xA3", + "\x81\x8D" => "\xEA\xB1\xA4", + "\x81\x8E" => "\xEA\xB1\xA5", + "\x81\x8F" => "\xEA\xB1\xA6", + "\x81\x90" => "\xEA\xB1\xA7", + "\x81\x91" => "\xEA\xB1\xA8", + "\x81\x92" => "\xEA\xB1\xA9", + "\x81\x93" => "\xEA\xB1\xAA", + "\x81\x94" => "\xEA\xB1\xAB", + "\x81\x95" => "\xEA\xB1\xAC", + "\x81\x96" => "\xEA\xB1\xAD", + "\x81\x97" => "\xEA\xB1\xAE", + "\x81\x98" => "\xEA\xB1\xAF", + "\x81\x99" => "\xEA\xB1\xB2", + "\x81\x9A" => "\xEA\xB1\xB3", + "\x81\x9B" => "\xEA\xB1\xB5", + "\x81\x9C" => "\xEA\xB1\xB6", + "\x81\x9D" => "\xEA\xB1\xB9", + "\x81\x9E" => "\xEA\xB1\xBB", + "\x81\x9F" => "\xEA\xB1\xBC", + "\x81\xA0" => "\xEA\xB1\xBD", + "\x81\xA1" => "\xEA\xB1\xBE", + "\x81\xA2" => "\xEA\xB1\xBF", + "\x81\xA3" => "\xEA\xB2\x82", + "\x81\xA4" => "\xEA\xB2\x87", + "\x81\xA5" => "\xEA\xB2\x88", + "\x81\xA6" => "\xEA\xB2\x8D", + "\x81\xA7" => "\xEA\xB2\x8E", + "\x81\xA8" => "\xEA\xB2\x8F", + "\x81\xA9" => "\xEA\xB2\x91", + "\x81\xAA" => "\xEA\xB2\x92", + "\x81\xAB" => "\xEA\xB2\x93", + "\x81\xAC" => "\xEA\xB2\x95", + "\x81\xAD" => "\xEA\xB2\x96", + "\x81\xAE" => "\xEA\xB2\x97", + "\x81\xAF" => "\xEA\xB2\x98", + "\x81\xB0" => "\xEA\xB2\x99", + "\x81\xB1" => "\xEA\xB2\x9A", + "\x81\xB2" => "\xEA\xB2\x9B", + "\x81\xB3" => "\xEA\xB2\x9E", + "\x81\xB4" => "\xEA\xB2\xA2", + "\x81\xB5" => "\xEA\xB2\xA3", + "\x81\xB6" => "\xEA\xB2\xA4", + "\x81\xB7" => "\xEA\xB2\xA5", + "\x81\xB8" => "\xEA\xB2\xA6", + "\x81\xB9" => "\xEA\xB2\xA7", + "\x81\xBA" => "\xEA\xB2\xAB", + "\x81\xBB" => "\xEA\xB2\xAD", + "\x81\xBC" => "\xEA\xB2\xAE", + "\x81\xBD" => "\xEA\xB2\xB1", + "\x81\xBE" => "\xEA\xB2\xB2", + "\x81\xBF" => "\xEA\xB2\xB3", + "\x81\xC0" => "\xEA\xB2\xB4", + "\x81\xC1" => "\xEA\xB2\xB5", + "\x81\xC2" => "\xEA\xB2\xB6", + "\x81\xC3" => "\xEA\xB2\xB7", + "\x81\xC4" => "\xEA\xB2\xBA", + "\x81\xC5" => "\xEA\xB2\xBE", + "\x81\xC6" => "\xEA\xB2\xBF", + "\x81\xC7" => "\xEA\xB3\x80", + "\x81\xC8" => "\xEA\xB3\x82", + "\x81\xC9" => "\xEA\xB3\x83", + "\x81\xCA" => "\xEA\xB3\x85", + "\x81\xCB" => "\xEA\xB3\x86", + "\x81\xCC" => "\xEA\xB3\x87", + "\x81\xCD" => "\xEA\xB3\x89", + "\x81\xCE" => "\xEA\xB3\x8A", + "\x81\xCF" => "\xEA\xB3\x8B", + "\x81\xD0" => "\xEA\xB3\x8D", + "\x81\xD1" => "\xEA\xB3\x8E", + "\x81\xD2" => "\xEA\xB3\x8F", + "\x81\xD3" => "\xEA\xB3\x90", + "\x81\xD4" => "\xEA\xB3\x91", + "\x81\xD5" => "\xEA\xB3\x92", + "\x81\xD6" => "\xEA\xB3\x93", + "\x81\xD7" => "\xEA\xB3\x94", + "\x81\xD8" => "\xEA\xB3\x96", + "\x81\xD9" => "\xEA\xB3\x98", + "\x81\xDA" => "\xEA\xB3\x99", + "\x81\xDB" => "\xEA\xB3\x9A", + "\x81\xDC" => "\xEA\xB3\x9B", + "\x81\xDD" => "\xEA\xB3\x9C", + "\x81\xDE" => "\xEA\xB3\x9D", + "\x81\xDF" => "\xEA\xB3\x9E", + "\x81\xE0" => "\xEA\xB3\x9F", + "\x81\xE1" => "\xEA\xB3\xA2", + "\x81\xE2" => "\xEA\xB3\xA3", + "\x81\xE3" => "\xEA\xB3\xA5", + "\x81\xE4" => "\xEA\xB3\xA6", + "\x81\xE5" => "\xEA\xB3\xA9", + "\x81\xE6" => "\xEA\xB3\xAB", + "\x81\xE7" => "\xEA\xB3\xAD", + "\x81\xE8" => "\xEA\xB3\xAE", + "\x81\xE9" => "\xEA\xB3\xB2", + "\x81\xEA" => "\xEA\xB3\xB4", + "\x81\xEB" => "\xEA\xB3\xB7", + "\x81\xEC" => "\xEA\xB3\xB8", + "\x81\xED" => "\xEA\xB3\xB9", + "\x81\xEE" => "\xEA\xB3\xBA", + "\x81\xEF" => "\xEA\xB3\xBB", + "\x81\xF0" => "\xEA\xB3\xBE", + "\x81\xF1" => "\xEA\xB3\xBF", + "\x81\xF2" => "\xEA\xB4\x81", + "\x81\xF3" => "\xEA\xB4\x82", + "\x81\xF4" => "\xEA\xB4\x83", + "\x81\xF5" => "\xEA\xB4\x85", + "\x81\xF6" => "\xEA\xB4\x87", + "\x81\xF7" => "\xEA\xB4\x88", + "\x81\xF8" => "\xEA\xB4\x89", + "\x81\xF9" => "\xEA\xB4\x8A", + "\x81\xFA" => "\xEA\xB4\x8B", + "\x81\xFB" => "\xEA\xB4\x8E", + "\x81\xFC" => "\xEA\xB4\x90", + "\x81\xFD" => "\xEA\xB4\x92", + "\x81\xFE" => "\xEA\xB4\x93", + "\x82\x41" => "\xEA\xB4\x94", + "\x82\x42" => "\xEA\xB4\x95", + "\x82\x43" => "\xEA\xB4\x96", + "\x82\x44" => "\xEA\xB4\x97", + "\x82\x45" => "\xEA\xB4\x99", + "\x82\x46" => "\xEA\xB4\x9A", + "\x82\x47" => "\xEA\xB4\x9B", + "\x82\x48" => "\xEA\xB4\x9D", + "\x82\x49" => "\xEA\xB4\x9E", + "\x82\x4A" => "\xEA\xB4\x9F", + "\x82\x4B" => "\xEA\xB4\xA1", + "\x82\x4C" => "\xEA\xB4\xA2", + "\x82\x4D" => "\xEA\xB4\xA3", + "\x82\x4E" => "\xEA\xB4\xA4", + "\x82\x4F" => "\xEA\xB4\xA5", + "\x82\x50" => "\xEA\xB4\xA6", + "\x82\x51" => "\xEA\xB4\xA7", + "\x82\x52" => "\xEA\xB4\xA8", + "\x82\x53" => "\xEA\xB4\xAA", + "\x82\x54" => "\xEA\xB4\xAB", + "\x82\x55" => "\xEA\xB4\xAE", + "\x82\x56" => "\xEA\xB4\xAF", + "\x82\x57" => "\xEA\xB4\xB0", + "\x82\x58" => "\xEA\xB4\xB1", + "\x82\x59" => "\xEA\xB4\xB2", + "\x82\x5A" => "\xEA\xB4\xB3", + "\x82\x61" => "\xEA\xB4\xB6", + "\x82\x62" => "\xEA\xB4\xB7", + "\x82\x63" => "\xEA\xB4\xB9", + "\x82\x64" => "\xEA\xB4\xBA", + "\x82\x65" => "\xEA\xB4\xBB", + "\x82\x66" => "\xEA\xB4\xBD", + "\x82\x67" => "\xEA\xB4\xBE", + "\x82\x68" => "\xEA\xB4\xBF", + "\x82\x69" => "\xEA\xB5\x80", + "\x82\x6A" => "\xEA\xB5\x81", + "\x82\x6B" => "\xEA\xB5\x82", + "\x82\x6C" => "\xEA\xB5\x83", + "\x82\x6D" => "\xEA\xB5\x86", + "\x82\x6E" => "\xEA\xB5\x88", + "\x82\x6F" => "\xEA\xB5\x8A", + "\x82\x70" => "\xEA\xB5\x8B", + "\x82\x71" => "\xEA\xB5\x8C", + "\x82\x72" => "\xEA\xB5\x8D", + "\x82\x73" => "\xEA\xB5\x8E", + "\x82\x74" => "\xEA\xB5\x8F", + "\x82\x75" => "\xEA\xB5\x91", + "\x82\x76" => "\xEA\xB5\x92", + "\x82\x77" => "\xEA\xB5\x93", + "\x82\x78" => "\xEA\xB5\x95", + "\x82\x79" => "\xEA\xB5\x96", + "\x82\x7A" => "\xEA\xB5\x97", + "\x82\x81" => "\xEA\xB5\x99", + "\x82\x82" => "\xEA\xB5\x9A", + "\x82\x83" => "\xEA\xB5\x9B", + "\x82\x84" => "\xEA\xB5\x9C", + "\x82\x85" => "\xEA\xB5\x9D", + "\x82\x86" => "\xEA\xB5\x9E", + "\x82\x87" => "\xEA\xB5\x9F", + "\x82\x88" => "\xEA\xB5\xA0", + "\x82\x89" => "\xEA\xB5\xA2", + "\x82\x8A" => "\xEA\xB5\xA4", + "\x82\x8B" => "\xEA\xB5\xA5", + "\x82\x8C" => "\xEA\xB5\xA6", + "\x82\x8D" => "\xEA\xB5\xA7", + "\x82\x8E" => "\xEA\xB5\xA8", + "\x82\x8F" => "\xEA\xB5\xA9", + "\x82\x90" => "\xEA\xB5\xAA", + "\x82\x91" => "\xEA\xB5\xAB", + "\x82\x92" => "\xEA\xB5\xAE", + "\x82\x93" => "\xEA\xB5\xAF", + "\x82\x94" => "\xEA\xB5\xB1", + "\x82\x95" => "\xEA\xB5\xB2", + "\x82\x96" => "\xEA\xB5\xB7", + "\x82\x97" => "\xEA\xB5\xB8", + "\x82\x98" => "\xEA\xB5\xB9", + "\x82\x99" => "\xEA\xB5\xBA", + "\x82\x9A" => "\xEA\xB5\xBE", + "\x82\x9B" => "\xEA\xB6\x80", + "\x82\x9C" => "\xEA\xB6\x83", + "\x82\x9D" => "\xEA\xB6\x84", + "\x82\x9E" => "\xEA\xB6\x85", + "\x82\x9F" => "\xEA\xB6\x86", + "\x82\xA0" => "\xEA\xB6\x87", + "\x82\xA1" => "\xEA\xB6\x8A", + "\x82\xA2" => "\xEA\xB6\x8B", + "\x82\xA3" => "\xEA\xB6\x8D", + "\x82\xA4" => "\xEA\xB6\x8E", + "\x82\xA5" => "\xEA\xB6\x8F", + "\x82\xA6" => "\xEA\xB6\x91", + "\x82\xA7" => "\xEA\xB6\x92", + "\x82\xA8" => "\xEA\xB6\x93", + "\x82\xA9" => "\xEA\xB6\x94", + "\x82\xAA" => "\xEA\xB6\x95", + "\x82\xAB" => "\xEA\xB6\x96", + "\x82\xAC" => "\xEA\xB6\x97", + "\x82\xAD" => "\xEA\xB6\x98", + "\x82\xAE" => "\xEA\xB6\x99", + "\x82\xAF" => "\xEA\xB6\x9A", + "\x82\xB0" => "\xEA\xB6\x9B", + "\x82\xB1" => "\xEA\xB6\x9E", + "\x82\xB2" => "\xEA\xB6\x9F", + "\x82\xB3" => "\xEA\xB6\xA0", + "\x82\xB4" => "\xEA\xB6\xA1", + "\x82\xB5" => "\xEA\xB6\xA2", + "\x82\xB6" => "\xEA\xB6\xA3", + "\x82\xB7" => "\xEA\xB6\xA5", + "\x82\xB8" => "\xEA\xB6\xA6", + "\x82\xB9" => "\xEA\xB6\xA7", + "\x82\xBA" => "\xEA\xB6\xA8", + "\x82\xBB" => "\xEA\xB6\xA9", + "\x82\xBC" => "\xEA\xB6\xAA", + "\x82\xBD" => "\xEA\xB6\xAB", + "\x82\xBE" => "\xEA\xB6\xAC", + "\x82\xBF" => "\xEA\xB6\xAD", + "\x82\xC0" => "\xEA\xB6\xAE", + "\x82\xC1" => "\xEA\xB6\xAF", + "\x82\xC2" => "\xEA\xB6\xB0", + "\x82\xC3" => "\xEA\xB6\xB1", + "\x82\xC4" => "\xEA\xB6\xB2", + "\x82\xC5" => "\xEA\xB6\xB3", + "\x82\xC6" => "\xEA\xB6\xB4", + "\x82\xC7" => "\xEA\xB6\xB5", + "\x82\xC8" => "\xEA\xB6\xB6", + "\x82\xC9" => "\xEA\xB6\xB8", + "\x82\xCA" => "\xEA\xB6\xB9", + "\x82\xCB" => "\xEA\xB6\xBA", + "\x82\xCC" => "\xEA\xB6\xBB", + "\x82\xCD" => "\xEA\xB6\xBC", + "\x82\xCE" => "\xEA\xB6\xBD", + "\x82\xCF" => "\xEA\xB6\xBE", + "\x82\xD0" => "\xEA\xB6\xBF", + "\x82\xD1" => "\xEA\xB7\x82", + "\x82\xD2" => "\xEA\xB7\x83", + "\x82\xD3" => "\xEA\xB7\x85", + "\x82\xD4" => "\xEA\xB7\x86", + "\x82\xD5" => "\xEA\xB7\x87", + "\x82\xD6" => "\xEA\xB7\x89", + "\x82\xD7" => "\xEA\xB7\x8A", + "\x82\xD8" => "\xEA\xB7\x8B", + "\x82\xD9" => "\xEA\xB7\x8C", + "\x82\xDA" => "\xEA\xB7\x8D", + "\x82\xDB" => "\xEA\xB7\x8E", + "\x82\xDC" => "\xEA\xB7\x8F", + "\x82\xDD" => "\xEA\xB7\x92", + "\x82\xDE" => "\xEA\xB7\x94", + "\x82\xDF" => "\xEA\xB7\x95", + "\x82\xE0" => "\xEA\xB7\x96", + "\x82\xE1" => "\xEA\xB7\x97", + "\x82\xE2" => "\xEA\xB7\x98", + "\x82\xE3" => "\xEA\xB7\x99", + "\x82\xE4" => "\xEA\xB7\x9A", + "\x82\xE5" => "\xEA\xB7\x9B", + "\x82\xE6" => "\xEA\xB7\x9D", + "\x82\xE7" => "\xEA\xB7\x9E", + "\x82\xE8" => "\xEA\xB7\x9F", + "\x82\xE9" => "\xEA\xB7\xA1", + "\x82\xEA" => "\xEA\xB7\xA2", + "\x82\xEB" => "\xEA\xB7\xA3", + "\x82\xEC" => "\xEA\xB7\xA5", + "\x82\xED" => "\xEA\xB7\xA6", + "\x82\xEE" => "\xEA\xB7\xA7", + "\x82\xEF" => "\xEA\xB7\xA8", + "\x82\xF0" => "\xEA\xB7\xA9", + "\x82\xF1" => "\xEA\xB7\xAA", + "\x82\xF2" => "\xEA\xB7\xAB", + "\x82\xF3" => "\xEA\xB7\xAC", + "\x82\xF4" => "\xEA\xB7\xAD", + "\x82\xF5" => "\xEA\xB7\xAE", + "\x82\xF6" => "\xEA\xB7\xAF", + "\x82\xF7" => "\xEA\xB7\xB0", + "\x82\xF8" => "\xEA\xB7\xB1", + "\x82\xF9" => "\xEA\xB7\xB2", + "\x82\xFA" => "\xEA\xB7\xB3", + "\x82\xFB" => "\xEA\xB7\xB4", + "\x82\xFC" => "\xEA\xB7\xB5", + "\x82\xFD" => "\xEA\xB7\xB6", + "\x82\xFE" => "\xEA\xB7\xB7", + "\x83\x41" => "\xEA\xB7\xBA", + "\x83\x42" => "\xEA\xB7\xBB", + "\x83\x43" => "\xEA\xB7\xBD", + "\x83\x44" => "\xEA\xB7\xBE", + "\x83\x45" => "\xEA\xB8\x82", + "\x83\x46" => "\xEA\xB8\x83", + "\x83\x47" => "\xEA\xB8\x84", + "\x83\x48" => "\xEA\xB8\x85", + "\x83\x49" => "\xEA\xB8\x86", + "\x83\x4A" => "\xEA\xB8\x87", + "\x83\x4B" => "\xEA\xB8\x8A", + "\x83\x4C" => "\xEA\xB8\x8C", + "\x83\x4D" => "\xEA\xB8\x8E", + "\x83\x4E" => "\xEA\xB8\x8F", + "\x83\x4F" => "\xEA\xB8\x90", + "\x83\x50" => "\xEA\xB8\x91", + "\x83\x51" => "\xEA\xB8\x92", + "\x83\x52" => "\xEA\xB8\x93", + "\x83\x53" => "\xEA\xB8\x95", + "\x83\x54" => "\xEA\xB8\x96", + "\x83\x55" => "\xEA\xB8\x97", + "\x83\x56" => "\xEA\xB8\x98", + "\x83\x57" => "\xEA\xB8\x99", + "\x83\x58" => "\xEA\xB8\x9A", + "\x83\x59" => "\xEA\xB8\x9B", + "\x83\x5A" => "\xEA\xB8\x9C", + "\x83\x61" => "\xEA\xB8\x9D", + "\x83\x62" => "\xEA\xB8\x9E", + "\x83\x63" => "\xEA\xB8\x9F", + "\x83\x64" => "\xEA\xB8\xA0", + "\x83\x65" => "\xEA\xB8\xA1", + "\x83\x66" => "\xEA\xB8\xA2", + "\x83\x67" => "\xEA\xB8\xA3", + "\x83\x68" => "\xEA\xB8\xA4", + "\x83\x69" => "\xEA\xB8\xA5", + "\x83\x6A" => "\xEA\xB8\xA6", + "\x83\x6B" => "\xEA\xB8\xA7", + "\x83\x6C" => "\xEA\xB8\xA8", + "\x83\x6D" => "\xEA\xB8\xA9", + "\x83\x6E" => "\xEA\xB8\xAA", + "\x83\x6F" => "\xEA\xB8\xAB", + "\x83\x70" => "\xEA\xB8\xAC", + "\x83\x71" => "\xEA\xB8\xAD", + "\x83\x72" => "\xEA\xB8\xAE", + "\x83\x73" => "\xEA\xB8\xAF", + "\x83\x74" => "\xEA\xB8\xB2", + "\x83\x75" => "\xEA\xB8\xB3", + "\x83\x76" => "\xEA\xB8\xB5", + "\x83\x77" => "\xEA\xB8\xB6", + "\x83\x78" => "\xEA\xB8\xB9", + "\x83\x79" => "\xEA\xB8\xBB", + "\x83\x7A" => "\xEA\xB8\xBC", + "\x83\x81" => "\xEA\xB8\xBD", + "\x83\x82" => "\xEA\xB8\xBE", + "\x83\x83" => "\xEA\xB8\xBF", + "\x83\x84" => "\xEA\xB9\x82", + "\x83\x85" => "\xEA\xB9\x84", + "\x83\x86" => "\xEA\xB9\x87", + "\x83\x87" => "\xEA\xB9\x88", + "\x83\x88" => "\xEA\xB9\x89", + "\x83\x89" => "\xEA\xB9\x8B", + "\x83\x8A" => "\xEA\xB9\x8F", + "\x83\x8B" => "\xEA\xB9\x91", + "\x83\x8C" => "\xEA\xB9\x92", + "\x83\x8D" => "\xEA\xB9\x93", + "\x83\x8E" => "\xEA\xB9\x95", + "\x83\x8F" => "\xEA\xB9\x97", + "\x83\x90" => "\xEA\xB9\x98", + "\x83\x91" => "\xEA\xB9\x99", + "\x83\x92" => "\xEA\xB9\x9A", + "\x83\x93" => "\xEA\xB9\x9B", + "\x83\x94" => "\xEA\xB9\x9E", + "\x83\x95" => "\xEA\xB9\xA2", + "\x83\x96" => "\xEA\xB9\xA3", + "\x83\x97" => "\xEA\xB9\xA4", + "\x83\x98" => "\xEA\xB9\xA6", + "\x83\x99" => "\xEA\xB9\xA7", + "\x83\x9A" => "\xEA\xB9\xAA", + "\x83\x9B" => "\xEA\xB9\xAB", + "\x83\x9C" => "\xEA\xB9\xAD", + "\x83\x9D" => "\xEA\xB9\xAE", + "\x83\x9E" => "\xEA\xB9\xAF", + "\x83\x9F" => "\xEA\xB9\xB1", + "\x83\xA0" => "\xEA\xB9\xB2", + "\x83\xA1" => "\xEA\xB9\xB3", + "\x83\xA2" => "\xEA\xB9\xB4", + "\x83\xA3" => "\xEA\xB9\xB5", + "\x83\xA4" => "\xEA\xB9\xB6", + "\x83\xA5" => "\xEA\xB9\xB7", + "\x83\xA6" => "\xEA\xB9\xBA", + "\x83\xA7" => "\xEA\xB9\xBE", + "\x83\xA8" => "\xEA\xB9\xBF", + "\x83\xA9" => "\xEA\xBA\x80", + "\x83\xAA" => "\xEA\xBA\x81", + "\x83\xAB" => "\xEA\xBA\x82", + "\x83\xAC" => "\xEA\xBA\x83", + "\x83\xAD" => "\xEA\xBA\x86", + "\x83\xAE" => "\xEA\xBA\x87", + "\x83\xAF" => "\xEA\xBA\x88", + "\x83\xB0" => "\xEA\xBA\x89", + "\x83\xB1" => "\xEA\xBA\x8A", + "\x83\xB2" => "\xEA\xBA\x8B", + "\x83\xB3" => "\xEA\xBA\x8D", + "\x83\xB4" => "\xEA\xBA\x8E", + "\x83\xB5" => "\xEA\xBA\x8F", + "\x83\xB6" => "\xEA\xBA\x90", + "\x83\xB7" => "\xEA\xBA\x91", + "\x83\xB8" => "\xEA\xBA\x92", + "\x83\xB9" => "\xEA\xBA\x93", + "\x83\xBA" => "\xEA\xBA\x94", + "\x83\xBB" => "\xEA\xBA\x95", + "\x83\xBC" => "\xEA\xBA\x96", + "\x83\xBD" => "\xEA\xBA\x97", + "\x83\xBE" => "\xEA\xBA\x98", + "\x83\xBF" => "\xEA\xBA\x99", + "\x83\xC0" => "\xEA\xBA\x9A", + "\x83\xC1" => "\xEA\xBA\x9B", + "\x83\xC2" => "\xEA\xBA\x9C", + "\x83\xC3" => "\xEA\xBA\x9D", + "\x83\xC4" => "\xEA\xBA\x9E", + "\x83\xC5" => "\xEA\xBA\x9F", + "\x83\xC6" => "\xEA\xBA\xA0", + "\x83\xC7" => "\xEA\xBA\xA1", + "\x83\xC8" => "\xEA\xBA\xA2", + "\x83\xC9" => "\xEA\xBA\xA3", + "\x83\xCA" => "\xEA\xBA\xA4", + "\x83\xCB" => "\xEA\xBA\xA5", + "\x83\xCC" => "\xEA\xBA\xA6", + "\x83\xCD" => "\xEA\xBA\xA7", + "\x83\xCE" => "\xEA\xBA\xA8", + "\x83\xCF" => "\xEA\xBA\xA9", + "\x83\xD0" => "\xEA\xBA\xAA", + "\x83\xD1" => "\xEA\xBA\xAB", + "\x83\xD2" => "\xEA\xBA\xAC", + "\x83\xD3" => "\xEA\xBA\xAD", + "\x83\xD4" => "\xEA\xBA\xAE", + "\x83\xD5" => "\xEA\xBA\xAF", + "\x83\xD6" => "\xEA\xBA\xB0", + "\x83\xD7" => "\xEA\xBA\xB1", + "\x83\xD8" => "\xEA\xBA\xB2", + "\x83\xD9" => "\xEA\xBA\xB3", + "\x83\xDA" => "\xEA\xBA\xB4", + "\x83\xDB" => "\xEA\xBA\xB5", + "\x83\xDC" => "\xEA\xBA\xB6", + "\x83\xDD" => "\xEA\xBA\xB7", + "\x83\xDE" => "\xEA\xBA\xB8", + "\x83\xDF" => "\xEA\xBA\xB9", + "\x83\xE0" => "\xEA\xBA\xBA", + "\x83\xE1" => "\xEA\xBA\xBB", + "\x83\xE2" => "\xEA\xBA\xBF", + "\x83\xE3" => "\xEA\xBB\x81", + "\x83\xE4" => "\xEA\xBB\x82", + "\x83\xE5" => "\xEA\xBB\x83", + "\x83\xE6" => "\xEA\xBB\x85", + "\x83\xE7" => "\xEA\xBB\x86", + "\x83\xE8" => "\xEA\xBB\x87", + "\x83\xE9" => "\xEA\xBB\x88", + "\x83\xEA" => "\xEA\xBB\x89", + "\x83\xEB" => "\xEA\xBB\x8A", + "\x83\xEC" => "\xEA\xBB\x8B", + "\x83\xED" => "\xEA\xBB\x8E", + "\x83\xEE" => "\xEA\xBB\x92", + "\x83\xEF" => "\xEA\xBB\x93", + "\x83\xF0" => "\xEA\xBB\x94", + "\x83\xF1" => "\xEA\xBB\x95", + "\x83\xF2" => "\xEA\xBB\x96", + "\x83\xF3" => "\xEA\xBB\x97", + "\x83\xF4" => "\xEA\xBB\x9A", + "\x83\xF5" => "\xEA\xBB\x9B", + "\x83\xF6" => "\xEA\xBB\x9D", + "\x83\xF7" => "\xEA\xBB\x9E", + "\x83\xF8" => "\xEA\xBB\x9F", + "\x83\xF9" => "\xEA\xBB\xA0", + "\x83\xFA" => "\xEA\xBB\xA1", + "\x83\xFB" => "\xEA\xBB\xA2", + "\x83\xFC" => "\xEA\xBB\xA3", + "\x83\xFD" => "\xEA\xBB\xA4", + "\x83\xFE" => "\xEA\xBB\xA5", + "\x84\x41" => "\xEA\xBB\xA6", + "\x84\x42" => "\xEA\xBB\xA7", + "\x84\x43" => "\xEA\xBB\xA9", + "\x84\x44" => "\xEA\xBB\xAA", + "\x84\x45" => "\xEA\xBB\xAC", + "\x84\x46" => "\xEA\xBB\xAE", + "\x84\x47" => "\xEA\xBB\xAF", + "\x84\x48" => "\xEA\xBB\xB0", + "\x84\x49" => "\xEA\xBB\xB1", + "\x84\x4A" => "\xEA\xBB\xB2", + "\x84\x4B" => "\xEA\xBB\xB3", + "\x84\x4C" => "\xEA\xBB\xB5", + "\x84\x4D" => "\xEA\xBB\xB6", + "\x84\x4E" => "\xEA\xBB\xB7", + "\x84\x4F" => "\xEA\xBB\xB9", + "\x84\x50" => "\xEA\xBB\xBA", + "\x84\x51" => "\xEA\xBB\xBB", + "\x84\x52" => "\xEA\xBB\xBD", + "\x84\x53" => "\xEA\xBB\xBE", + "\x84\x54" => "\xEA\xBB\xBF", + "\x84\x55" => "\xEA\xBC\x80", + "\x84\x56" => "\xEA\xBC\x81", + "\x84\x57" => "\xEA\xBC\x82", + "\x84\x58" => "\xEA\xBC\x83", + "\x84\x59" => "\xEA\xBC\x84", + "\x84\x5A" => "\xEA\xBC\x85", + "\x84\x61" => "\xEA\xBC\x86", + "\x84\x62" => "\xEA\xBC\x89", + "\x84\x63" => "\xEA\xBC\x8A", + "\x84\x64" => "\xEA\xBC\x8B", + "\x84\x65" => "\xEA\xBC\x8C", + "\x84\x66" => "\xEA\xBC\x8E", + "\x84\x67" => "\xEA\xBC\x8F", + "\x84\x68" => "\xEA\xBC\x91", + "\x84\x69" => "\xEA\xBC\x92", + "\x84\x6A" => "\xEA\xBC\x93", + "\x84\x6B" => "\xEA\xBC\x94", + "\x84\x6C" => "\xEA\xBC\x95", + "\x84\x6D" => "\xEA\xBC\x96", + "\x84\x6E" => "\xEA\xBC\x97", + "\x84\x6F" => "\xEA\xBC\x98", + "\x84\x70" => "\xEA\xBC\x99", + "\x84\x71" => "\xEA\xBC\x9A", + "\x84\x72" => "\xEA\xBC\x9B", + "\x84\x73" => "\xEA\xBC\x9C", + "\x84\x74" => "\xEA\xBC\x9D", + "\x84\x75" => "\xEA\xBC\x9E", + "\x84\x76" => "\xEA\xBC\x9F", + "\x84\x77" => "\xEA\xBC\xA0", + "\x84\x78" => "\xEA\xBC\xA1", + "\x84\x79" => "\xEA\xBC\xA2", + "\x84\x7A" => "\xEA\xBC\xA3", + "\x84\x81" => "\xEA\xBC\xA4", + "\x84\x82" => "\xEA\xBC\xA5", + "\x84\x83" => "\xEA\xBC\xA6", + "\x84\x84" => "\xEA\xBC\xA7", + "\x84\x85" => "\xEA\xBC\xA8", + "\x84\x86" => "\xEA\xBC\xA9", + "\x84\x87" => "\xEA\xBC\xAA", + "\x84\x88" => "\xEA\xBC\xAB", + "\x84\x89" => "\xEA\xBC\xAE", + "\x84\x8A" => "\xEA\xBC\xAF", + "\x84\x8B" => "\xEA\xBC\xB1", + "\x84\x8C" => "\xEA\xBC\xB3", + "\x84\x8D" => "\xEA\xBC\xB5", + "\x84\x8E" => "\xEA\xBC\xB6", + "\x84\x8F" => "\xEA\xBC\xB7", + "\x84\x90" => "\xEA\xBC\xB8", + "\x84\x91" => "\xEA\xBC\xB9", + "\x84\x92" => "\xEA\xBC\xBA", + "\x84\x93" => "\xEA\xBC\xBB", + "\x84\x94" => "\xEA\xBC\xBE", + "\x84\x95" => "\xEA\xBD\x80", + "\x84\x96" => "\xEA\xBD\x84", + "\x84\x97" => "\xEA\xBD\x85", + "\x84\x98" => "\xEA\xBD\x86", + "\x84\x99" => "\xEA\xBD\x87", + "\x84\x9A" => "\xEA\xBD\x8A", + "\x84\x9B" => "\xEA\xBD\x8B", + "\x84\x9C" => "\xEA\xBD\x8C", + "\x84\x9D" => "\xEA\xBD\x8D", + "\x84\x9E" => "\xEA\xBD\x8E", + "\x84\x9F" => "\xEA\xBD\x8F", + "\x84\xA0" => "\xEA\xBD\x91", + "\x84\xA1" => "\xEA\xBD\x92", + "\x84\xA2" => "\xEA\xBD\x93", + "\x84\xA3" => "\xEA\xBD\x94", + "\x84\xA4" => "\xEA\xBD\x95", + "\x84\xA5" => "\xEA\xBD\x96", + "\x84\xA6" => "\xEA\xBD\x97", + "\x84\xA7" => "\xEA\xBD\x98", + "\x84\xA8" => "\xEA\xBD\x99", + "\x84\xA9" => "\xEA\xBD\x9A", + "\x84\xAA" => "\xEA\xBD\x9B", + "\x84\xAB" => "\xEA\xBD\x9E", + "\x84\xAC" => "\xEA\xBD\x9F", + "\x84\xAD" => "\xEA\xBD\xA0", + "\x84\xAE" => "\xEA\xBD\xA1", + "\x84\xAF" => "\xEA\xBD\xA2", + "\x84\xB0" => "\xEA\xBD\xA3", + "\x84\xB1" => "\xEA\xBD\xA6", + "\x84\xB2" => "\xEA\xBD\xA7", + "\x84\xB3" => "\xEA\xBD\xA8", + "\x84\xB4" => "\xEA\xBD\xA9", + "\x84\xB5" => "\xEA\xBD\xAA", + "\x84\xB6" => "\xEA\xBD\xAB", + "\x84\xB7" => "\xEA\xBD\xAC", + "\x84\xB8" => "\xEA\xBD\xAD", + "\x84\xB9" => "\xEA\xBD\xAE", + "\x84\xBA" => "\xEA\xBD\xAF", + "\x84\xBB" => "\xEA\xBD\xB0", + "\x84\xBC" => "\xEA\xBD\xB1", + "\x84\xBD" => "\xEA\xBD\xB2", + "\x84\xBE" => "\xEA\xBD\xB3", + "\x84\xBF" => "\xEA\xBD\xB4", + "\x84\xC0" => "\xEA\xBD\xB5", + "\x84\xC1" => "\xEA\xBD\xB6", + "\x84\xC2" => "\xEA\xBD\xB7", + "\x84\xC3" => "\xEA\xBD\xB8", + "\x84\xC4" => "\xEA\xBD\xBA", + "\x84\xC5" => "\xEA\xBD\xBB", + "\x84\xC6" => "\xEA\xBD\xBC", + "\x84\xC7" => "\xEA\xBD\xBD", + "\x84\xC8" => "\xEA\xBD\xBE", + "\x84\xC9" => "\xEA\xBD\xBF", + "\x84\xCA" => "\xEA\xBE\x81", + "\x84\xCB" => "\xEA\xBE\x82", + "\x84\xCC" => "\xEA\xBE\x83", + "\x84\xCD" => "\xEA\xBE\x85", + "\x84\xCE" => "\xEA\xBE\x86", + "\x84\xCF" => "\xEA\xBE\x87", + "\x84\xD0" => "\xEA\xBE\x89", + "\x84\xD1" => "\xEA\xBE\x8A", + "\x84\xD2" => "\xEA\xBE\x8B", + "\x84\xD3" => "\xEA\xBE\x8C", + "\x84\xD4" => "\xEA\xBE\x8D", + "\x84\xD5" => "\xEA\xBE\x8E", + "\x84\xD6" => "\xEA\xBE\x8F", + "\x84\xD7" => "\xEA\xBE\x92", + "\x84\xD8" => "\xEA\xBE\x93", + "\x84\xD9" => "\xEA\xBE\x94", + "\x84\xDA" => "\xEA\xBE\x96", + "\x84\xDB" => "\xEA\xBE\x97", + "\x84\xDC" => "\xEA\xBE\x98", + "\x84\xDD" => "\xEA\xBE\x99", + "\x84\xDE" => "\xEA\xBE\x9A", + "\x84\xDF" => "\xEA\xBE\x9B", + "\x84\xE0" => "\xEA\xBE\x9D", + "\x84\xE1" => "\xEA\xBE\x9E", + "\x84\xE2" => "\xEA\xBE\x9F", + "\x84\xE3" => "\xEA\xBE\xA0", + "\x84\xE4" => "\xEA\xBE\xA1", + "\x84\xE5" => "\xEA\xBE\xA2", + "\x84\xE6" => "\xEA\xBE\xA3", + "\x84\xE7" => "\xEA\xBE\xA4", + "\x84\xE8" => "\xEA\xBE\xA5", + "\x84\xE9" => "\xEA\xBE\xA6", + "\x84\xEA" => "\xEA\xBE\xA7", + "\x84\xEB" => "\xEA\xBE\xA8", + "\x84\xEC" => "\xEA\xBE\xA9", + "\x84\xED" => "\xEA\xBE\xAA", + "\x84\xEE" => "\xEA\xBE\xAB", + "\x84\xEF" => "\xEA\xBE\xAC", + "\x84\xF0" => "\xEA\xBE\xAD", + "\x84\xF1" => "\xEA\xBE\xAE", + "\x84\xF2" => "\xEA\xBE\xAF", + "\x84\xF3" => "\xEA\xBE\xB0", + "\x84\xF4" => "\xEA\xBE\xB1", + "\x84\xF5" => "\xEA\xBE\xB2", + "\x84\xF6" => "\xEA\xBE\xB3", + "\x84\xF7" => "\xEA\xBE\xB4", + "\x84\xF8" => "\xEA\xBE\xB5", + "\x84\xF9" => "\xEA\xBE\xB6", + "\x84\xFA" => "\xEA\xBE\xB7", + "\x84\xFB" => "\xEA\xBE\xBA", + "\x84\xFC" => "\xEA\xBE\xBB", + "\x84\xFD" => "\xEA\xBE\xBD", + "\x84\xFE" => "\xEA\xBE\xBE", + "\x85\x41" => "\xEA\xBE\xBF", + "\x85\x42" => "\xEA\xBF\x81", + "\x85\x43" => "\xEA\xBF\x82", + "\x85\x44" => "\xEA\xBF\x83", + "\x85\x45" => "\xEA\xBF\x84", + "\x85\x46" => "\xEA\xBF\x85", + "\x85\x47" => "\xEA\xBF\x86", + "\x85\x48" => "\xEA\xBF\x8A", + "\x85\x49" => "\xEA\xBF\x8C", + "\x85\x4A" => "\xEA\xBF\x8F", + "\x85\x4B" => "\xEA\xBF\x90", + "\x85\x4C" => "\xEA\xBF\x91", + "\x85\x4D" => "\xEA\xBF\x92", + "\x85\x4E" => "\xEA\xBF\x93", + "\x85\x4F" => "\xEA\xBF\x95", + "\x85\x50" => "\xEA\xBF\x96", + "\x85\x51" => "\xEA\xBF\x97", + "\x85\x52" => "\xEA\xBF\x98", + "\x85\x53" => "\xEA\xBF\x99", + "\x85\x54" => "\xEA\xBF\x9A", + "\x85\x55" => "\xEA\xBF\x9B", + "\x85\x56" => "\xEA\xBF\x9D", + "\x85\x57" => "\xEA\xBF\x9E", + "\x85\x58" => "\xEA\xBF\x9F", + "\x85\x59" => "\xEA\xBF\xA0", + "\x85\x5A" => "\xEA\xBF\xA1", + "\x85\x61" => "\xEA\xBF\xA2", + "\x85\x62" => "\xEA\xBF\xA3", + "\x85\x63" => "\xEA\xBF\xA4", + "\x85\x64" => "\xEA\xBF\xA5", + "\x85\x65" => "\xEA\xBF\xA6", + "\x85\x66" => "\xEA\xBF\xA7", + "\x85\x67" => "\xEA\xBF\xAA", + "\x85\x68" => "\xEA\xBF\xAB", + "\x85\x69" => "\xEA\xBF\xAC", + "\x85\x6A" => "\xEA\xBF\xAD", + "\x85\x6B" => "\xEA\xBF\xAE", + "\x85\x6C" => "\xEA\xBF\xAF", + "\x85\x6D" => "\xEA\xBF\xB2", + "\x85\x6E" => "\xEA\xBF\xB3", + "\x85\x6F" => "\xEA\xBF\xB5", + "\x85\x70" => "\xEA\xBF\xB6", + "\x85\x71" => "\xEA\xBF\xB7", + "\x85\x72" => "\xEA\xBF\xB9", + "\x85\x73" => "\xEA\xBF\xBA", + "\x85\x74" => "\xEA\xBF\xBB", + "\x85\x75" => "\xEA\xBF\xBC", + "\x85\x76" => "\xEA\xBF\xBD", + "\x85\x77" => "\xEA\xBF\xBE", + "\x85\x78" => "\xEA\xBF\xBF", + "\x85\x79" => "\xEB\x80\x82", + "\x85\x7A" => "\xEB\x80\x83", + "\x85\x81" => "\xEB\x80\x85", + "\x85\x82" => "\xEB\x80\x86", + "\x85\x83" => "\xEB\x80\x87", + "\x85\x84" => "\xEB\x80\x88", + "\x85\x85" => "\xEB\x80\x89", + "\x85\x86" => "\xEB\x80\x8A", + "\x85\x87" => "\xEB\x80\x8B", + "\x85\x88" => "\xEB\x80\x8D", + "\x85\x89" => "\xEB\x80\x8E", + "\x85\x8A" => "\xEB\x80\x8F", + "\x85\x8B" => "\xEB\x80\x91", + "\x85\x8C" => "\xEB\x80\x92", + "\x85\x8D" => "\xEB\x80\x93", + "\x85\x8E" => "\xEB\x80\x95", + "\x85\x8F" => "\xEB\x80\x96", + "\x85\x90" => "\xEB\x80\x97", + "\x85\x91" => "\xEB\x80\x98", + "\x85\x92" => "\xEB\x80\x99", + "\x85\x93" => "\xEB\x80\x9A", + "\x85\x94" => "\xEB\x80\x9B", + "\x85\x95" => "\xEB\x80\x9E", + "\x85\x96" => "\xEB\x80\x9F", + "\x85\x97" => "\xEB\x80\xA0", + "\x85\x98" => "\xEB\x80\xA1", + "\x85\x99" => "\xEB\x80\xA2", + "\x85\x9A" => "\xEB\x80\xA3", + "\x85\x9B" => "\xEB\x80\xA4", + "\x85\x9C" => "\xEB\x80\xA5", + "\x85\x9D" => "\xEB\x80\xA6", + "\x85\x9E" => "\xEB\x80\xA7", + "\x85\x9F" => "\xEB\x80\xA9", + "\x85\xA0" => "\xEB\x80\xAA", + "\x85\xA1" => "\xEB\x80\xAB", + "\x85\xA2" => "\xEB\x80\xAC", + "\x85\xA3" => "\xEB\x80\xAD", + "\x85\xA4" => "\xEB\x80\xAE", + "\x85\xA5" => "\xEB\x80\xAF", + "\x85\xA6" => "\xEB\x80\xB0", + "\x85\xA7" => "\xEB\x80\xB1", + "\x85\xA8" => "\xEB\x80\xB2", + "\x85\xA9" => "\xEB\x80\xB3", + "\x85\xAA" => "\xEB\x80\xB4", + "\x85\xAB" => "\xEB\x80\xB5", + "\x85\xAC" => "\xEB\x80\xB6", + "\x85\xAD" => "\xEB\x80\xB7", + "\x85\xAE" => "\xEB\x80\xB8", + "\x85\xAF" => "\xEB\x80\xB9", + "\x85\xB0" => "\xEB\x80\xBA", + "\x85\xB1" => "\xEB\x80\xBB", + "\x85\xB2" => "\xEB\x80\xBC", + "\x85\xB3" => "\xEB\x80\xBD", + "\x85\xB4" => "\xEB\x80\xBE", + "\x85\xB5" => "\xEB\x80\xBF", + "\x85\xB6" => "\xEB\x81\x80", + "\x85\xB7" => "\xEB\x81\x81", + "\x85\xB8" => "\xEB\x81\x82", + "\x85\xB9" => "\xEB\x81\x83", + "\x85\xBA" => "\xEB\x81\x86", + "\x85\xBB" => "\xEB\x81\x87", + "\x85\xBC" => "\xEB\x81\x89", + "\x85\xBD" => "\xEB\x81\x8B", + "\x85\xBE" => "\xEB\x81\x8D", + "\x85\xBF" => "\xEB\x81\x8F", + "\x85\xC0" => "\xEB\x81\x90", + "\x85\xC1" => "\xEB\x81\x91", + "\x85\xC2" => "\xEB\x81\x92", + "\x85\xC3" => "\xEB\x81\x96", + "\x85\xC4" => "\xEB\x81\x98", + "\x85\xC5" => "\xEB\x81\x9A", + "\x85\xC6" => "\xEB\x81\x9B", + "\x85\xC7" => "\xEB\x81\x9C", + "\x85\xC8" => "\xEB\x81\x9E", + "\x85\xC9" => "\xEB\x81\x9F", + "\x85\xCA" => "\xEB\x81\xA0", + "\x85\xCB" => "\xEB\x81\xA1", + "\x85\xCC" => "\xEB\x81\xA2", + "\x85\xCD" => "\xEB\x81\xA3", + "\x85\xCE" => "\xEB\x81\xA4", + "\x85\xCF" => "\xEB\x81\xA5", + "\x85\xD0" => "\xEB\x81\xA6", + "\x85\xD1" => "\xEB\x81\xA7", + "\x85\xD2" => "\xEB\x81\xA8", + "\x85\xD3" => "\xEB\x81\xA9", + "\x85\xD4" => "\xEB\x81\xAA", + "\x85\xD5" => "\xEB\x81\xAB", + "\x85\xD6" => "\xEB\x81\xAC", + "\x85\xD7" => "\xEB\x81\xAD", + "\x85\xD8" => "\xEB\x81\xAE", + "\x85\xD9" => "\xEB\x81\xAF", + "\x85\xDA" => "\xEB\x81\xB0", + "\x85\xDB" => "\xEB\x81\xB1", + "\x85\xDC" => "\xEB\x81\xB2", + "\x85\xDD" => "\xEB\x81\xB3", + "\x85\xDE" => "\xEB\x81\xB4", + "\x85\xDF" => "\xEB\x81\xB5", + "\x85\xE0" => "\xEB\x81\xB6", + "\x85\xE1" => "\xEB\x81\xB7", + "\x85\xE2" => "\xEB\x81\xB8", + "\x85\xE3" => "\xEB\x81\xB9", + "\x85\xE4" => "\xEB\x81\xBA", + "\x85\xE5" => "\xEB\x81\xBB", + "\x85\xE6" => "\xEB\x81\xBE", + "\x85\xE7" => "\xEB\x81\xBF", + "\x85\xE8" => "\xEB\x82\x81", + "\x85\xE9" => "\xEB\x82\x82", + "\x85\xEA" => "\xEB\x82\x83", + "\x85\xEB" => "\xEB\x82\x85", + "\x85\xEC" => "\xEB\x82\x86", + "\x85\xED" => "\xEB\x82\x87", + "\x85\xEE" => "\xEB\x82\x88", + "\x85\xEF" => "\xEB\x82\x89", + "\x85\xF0" => "\xEB\x82\x8A", + "\x85\xF1" => "\xEB\x82\x8B", + "\x85\xF2" => "\xEB\x82\x8E", + "\x85\xF3" => "\xEB\x82\x90", + "\x85\xF4" => "\xEB\x82\x92", + "\x85\xF5" => "\xEB\x82\x93", + "\x85\xF6" => "\xEB\x82\x94", + "\x85\xF7" => "\xEB\x82\x95", + "\x85\xF8" => "\xEB\x82\x96", + "\x85\xF9" => "\xEB\x82\x97", + "\x85\xFA" => "\xEB\x82\x9B", + "\x85\xFB" => "\xEB\x82\x9D", + "\x85\xFC" => "\xEB\x82\x9E", + "\x85\xFD" => "\xEB\x82\xA3", + "\x85\xFE" => "\xEB\x82\xA4", + "\x86\x41" => "\xEB\x82\xA5", + "\x86\x42" => "\xEB\x82\xA6", + "\x86\x43" => "\xEB\x82\xA7", + "\x86\x44" => "\xEB\x82\xAA", + "\x86\x45" => "\xEB\x82\xB0", + "\x86\x46" => "\xEB\x82\xB2", + "\x86\x47" => "\xEB\x82\xB6", + "\x86\x48" => "\xEB\x82\xB7", + "\x86\x49" => "\xEB\x82\xB9", + "\x86\x4A" => "\xEB\x82\xBA", + "\x86\x4B" => "\xEB\x82\xBB", + "\x86\x4C" => "\xEB\x82\xBD", + "\x86\x4D" => "\xEB\x82\xBE", + "\x86\x4E" => "\xEB\x82\xBF", + "\x86\x4F" => "\xEB\x83\x80", + "\x86\x50" => "\xEB\x83\x81", + "\x86\x51" => "\xEB\x83\x82", + "\x86\x52" => "\xEB\x83\x83", + "\x86\x53" => "\xEB\x83\x86", + "\x86\x54" => "\xEB\x83\x8A", + "\x86\x55" => "\xEB\x83\x8B", + "\x86\x56" => "\xEB\x83\x8C", + "\x86\x57" => "\xEB\x83\x8D", + "\x86\x58" => "\xEB\x83\x8E", + "\x86\x59" => "\xEB\x83\x8F", + "\x86\x5A" => "\xEB\x83\x92", + "\x86\x61" => "\xEB\x83\x93", + "\x86\x62" => "\xEB\x83\x95", + "\x86\x63" => "\xEB\x83\x96", + "\x86\x64" => "\xEB\x83\x97", + "\x86\x65" => "\xEB\x83\x99", + "\x86\x66" => "\xEB\x83\x9A", + "\x86\x67" => "\xEB\x83\x9B", + "\x86\x68" => "\xEB\x83\x9C", + "\x86\x69" => "\xEB\x83\x9D", + "\x86\x6A" => "\xEB\x83\x9E", + "\x86\x6B" => "\xEB\x83\x9F", + "\x86\x6C" => "\xEB\x83\xA1", + "\x86\x6D" => "\xEB\x83\xA2", + "\x86\x6E" => "\xEB\x83\xA3", + "\x86\x6F" => "\xEB\x83\xA4", + "\x86\x70" => "\xEB\x83\xA6", + "\x86\x71" => "\xEB\x83\xA7", + "\x86\x72" => "\xEB\x83\xA8", + "\x86\x73" => "\xEB\x83\xA9", + "\x86\x74" => "\xEB\x83\xAA", + "\x86\x75" => "\xEB\x83\xAB", + "\x86\x76" => "\xEB\x83\xAC", + "\x86\x77" => "\xEB\x83\xAD", + "\x86\x78" => "\xEB\x83\xAE", + "\x86\x79" => "\xEB\x83\xAF", + "\x86\x7A" => "\xEB\x83\xB0", + "\x86\x81" => "\xEB\x83\xB1", + "\x86\x82" => "\xEB\x83\xB2", + "\x86\x83" => "\xEB\x83\xB3", + "\x86\x84" => "\xEB\x83\xB4", + "\x86\x85" => "\xEB\x83\xB5", + "\x86\x86" => "\xEB\x83\xB6", + "\x86\x87" => "\xEB\x83\xB7", + "\x86\x88" => "\xEB\x83\xB8", + "\x86\x89" => "\xEB\x83\xB9", + "\x86\x8A" => "\xEB\x83\xBA", + "\x86\x8B" => "\xEB\x83\xBB", + "\x86\x8C" => "\xEB\x83\xBC", + "\x86\x8D" => "\xEB\x83\xBD", + "\x86\x8E" => "\xEB\x83\xBE", + "\x86\x8F" => "\xEB\x83\xBF", + "\x86\x90" => "\xEB\x84\x80", + "\x86\x91" => "\xEB\x84\x81", + "\x86\x92" => "\xEB\x84\x82", + "\x86\x93" => "\xEB\x84\x83", + "\x86\x94" => "\xEB\x84\x84", + "\x86\x95" => "\xEB\x84\x85", + "\x86\x96" => "\xEB\x84\x86", + "\x86\x97" => "\xEB\x84\x87", + "\x86\x98" => "\xEB\x84\x8A", + "\x86\x99" => "\xEB\x84\x8D", + "\x86\x9A" => "\xEB\x84\x8E", + "\x86\x9B" => "\xEB\x84\x8F", + "\x86\x9C" => "\xEB\x84\x91", + "\x86\x9D" => "\xEB\x84\x94", + "\x86\x9E" => "\xEB\x84\x95", + "\x86\x9F" => "\xEB\x84\x96", + "\x86\xA0" => "\xEB\x84\x97", + "\x86\xA1" => "\xEB\x84\x9A", + "\x86\xA2" => "\xEB\x84\x9E", + "\x86\xA3" => "\xEB\x84\x9F", + "\x86\xA4" => "\xEB\x84\xA0", + "\x86\xA5" => "\xEB\x84\xA1", + "\x86\xA6" => "\xEB\x84\xA2", + "\x86\xA7" => "\xEB\x84\xA6", + "\x86\xA8" => "\xEB\x84\xA7", + "\x86\xA9" => "\xEB\x84\xA9", + "\x86\xAA" => "\xEB\x84\xAA", + "\x86\xAB" => "\xEB\x84\xAB", + "\x86\xAC" => "\xEB\x84\xAD", + "\x86\xAD" => "\xEB\x84\xAE", + "\x86\xAE" => "\xEB\x84\xAF", + "\x86\xAF" => "\xEB\x84\xB0", + "\x86\xB0" => "\xEB\x84\xB1", + "\x86\xB1" => "\xEB\x84\xB2", + "\x86\xB2" => "\xEB\x84\xB3", + "\x86\xB3" => "\xEB\x84\xB6", + "\x86\xB4" => "\xEB\x84\xBA", + "\x86\xB5" => "\xEB\x84\xBB", + "\x86\xB6" => "\xEB\x84\xBC", + "\x86\xB7" => "\xEB\x84\xBD", + "\x86\xB8" => "\xEB\x84\xBE", + "\x86\xB9" => "\xEB\x84\xBF", + "\x86\xBA" => "\xEB\x85\x82", + "\x86\xBB" => "\xEB\x85\x83", + "\x86\xBC" => "\xEB\x85\x85", + "\x86\xBD" => "\xEB\x85\x86", + "\x86\xBE" => "\xEB\x85\x87", + "\x86\xBF" => "\xEB\x85\x89", + "\x86\xC0" => "\xEB\x85\x8A", + "\x86\xC1" => "\xEB\x85\x8B", + "\x86\xC2" => "\xEB\x85\x8C", + "\x86\xC3" => "\xEB\x85\x8D", + "\x86\xC4" => "\xEB\x85\x8E", + "\x86\xC5" => "\xEB\x85\x8F", + "\x86\xC6" => "\xEB\x85\x92", + "\x86\xC7" => "\xEB\x85\x93", + "\x86\xC8" => "\xEB\x85\x96", + "\x86\xC9" => "\xEB\x85\x97", + "\x86\xCA" => "\xEB\x85\x99", + "\x86\xCB" => "\xEB\x85\x9A", + "\x86\xCC" => "\xEB\x85\x9B", + "\x86\xCD" => "\xEB\x85\x9D", + "\x86\xCE" => "\xEB\x85\x9E", + "\x86\xCF" => "\xEB\x85\x9F", + "\x86\xD0" => "\xEB\x85\xA1", + "\x86\xD1" => "\xEB\x85\xA2", + "\x86\xD2" => "\xEB\x85\xA3", + "\x86\xD3" => "\xEB\x85\xA4", + "\x86\xD4" => "\xEB\x85\xA5", + "\x86\xD5" => "\xEB\x85\xA6", + "\x86\xD6" => "\xEB\x85\xA7", + "\x86\xD7" => "\xEB\x85\xA8", + "\x86\xD8" => "\xEB\x85\xA9", + "\x86\xD9" => "\xEB\x85\xAA", + "\x86\xDA" => "\xEB\x85\xAB", + "\x86\xDB" => "\xEB\x85\xAC", + "\x86\xDC" => "\xEB\x85\xAD", + "\x86\xDD" => "\xEB\x85\xAE", + "\x86\xDE" => "\xEB\x85\xAF", + "\x86\xDF" => "\xEB\x85\xB0", + "\x86\xE0" => "\xEB\x85\xB1", + "\x86\xE1" => "\xEB\x85\xB2", + "\x86\xE2" => "\xEB\x85\xB3", + "\x86\xE3" => "\xEB\x85\xB4", + "\x86\xE4" => "\xEB\x85\xB5", + "\x86\xE5" => "\xEB\x85\xB6", + "\x86\xE6" => "\xEB\x85\xB7", + "\x86\xE7" => "\xEB\x85\xBA", + "\x86\xE8" => "\xEB\x85\xBB", + "\x86\xE9" => "\xEB\x85\xBD", + "\x86\xEA" => "\xEB\x85\xBE", + "\x86\xEB" => "\xEB\x85\xBF", + "\x86\xEC" => "\xEB\x86\x81", + "\x86\xED" => "\xEB\x86\x83", + "\x86\xEE" => "\xEB\x86\x84", + "\x86\xEF" => "\xEB\x86\x85", + "\x86\xF0" => "\xEB\x86\x86", + "\x86\xF1" => "\xEB\x86\x87", + "\x86\xF2" => "\xEB\x86\x8A", + "\x86\xF3" => "\xEB\x86\x8C", + "\x86\xF4" => "\xEB\x86\x8E", + "\x86\xF5" => "\xEB\x86\x8F", + "\x86\xF6" => "\xEB\x86\x90", + "\x86\xF7" => "\xEB\x86\x91", + "\x86\xF8" => "\xEB\x86\x95", + "\x86\xF9" => "\xEB\x86\x96", + "\x86\xFA" => "\xEB\x86\x97", + "\x86\xFB" => "\xEB\x86\x99", + "\x86\xFC" => "\xEB\x86\x9A", + "\x86\xFD" => "\xEB\x86\x9B", + "\x86\xFE" => "\xEB\x86\x9D", + "\x87\x41" => "\xEB\x86\x9E", + "\x87\x42" => "\xEB\x86\x9F", + "\x87\x43" => "\xEB\x86\xA0", + "\x87\x44" => "\xEB\x86\xA1", + "\x87\x45" => "\xEB\x86\xA2", + "\x87\x46" => "\xEB\x86\xA3", + "\x87\x47" => "\xEB\x86\xA4", + "\x87\x48" => "\xEB\x86\xA5", + "\x87\x49" => "\xEB\x86\xA6", + "\x87\x4A" => "\xEB\x86\xA7", + "\x87\x4B" => "\xEB\x86\xA9", + "\x87\x4C" => "\xEB\x86\xAA", + "\x87\x4D" => "\xEB\x86\xAB", + "\x87\x4E" => "\xEB\x86\xAC", + "\x87\x4F" => "\xEB\x86\xAD", + "\x87\x50" => "\xEB\x86\xAE", + "\x87\x51" => "\xEB\x86\xAF", + "\x87\x52" => "\xEB\x86\xB0", + "\x87\x53" => "\xEB\x86\xB1", + "\x87\x54" => "\xEB\x86\xB2", + "\x87\x55" => "\xEB\x86\xB3", + "\x87\x56" => "\xEB\x86\xB4", + "\x87\x57" => "\xEB\x86\xB5", + "\x87\x58" => "\xEB\x86\xB6", + "\x87\x59" => "\xEB\x86\xB7", + "\x87\x5A" => "\xEB\x86\xB8", + "\x87\x61" => "\xEB\x86\xB9", + "\x87\x62" => "\xEB\x86\xBA", + "\x87\x63" => "\xEB\x86\xBB", + "\x87\x64" => "\xEB\x86\xBC", + "\x87\x65" => "\xEB\x86\xBD", + "\x87\x66" => "\xEB\x86\xBE", + "\x87\x67" => "\xEB\x86\xBF", + "\x87\x68" => "\xEB\x87\x80", + "\x87\x69" => "\xEB\x87\x81", + "\x87\x6A" => "\xEB\x87\x82", + "\x87\x6B" => "\xEB\x87\x83", + "\x87\x6C" => "\xEB\x87\x84", + "\x87\x6D" => "\xEB\x87\x85", + "\x87\x6E" => "\xEB\x87\x86", + "\x87\x6F" => "\xEB\x87\x87", + "\x87\x70" => "\xEB\x87\x88", + "\x87\x71" => "\xEB\x87\x89", + "\x87\x72" => "\xEB\x87\x8A", + "\x87\x73" => "\xEB\x87\x8B", + "\x87\x74" => "\xEB\x87\x8D", + "\x87\x75" => "\xEB\x87\x8E", + "\x87\x76" => "\xEB\x87\x8F", + "\x87\x77" => "\xEB\x87\x91", + "\x87\x78" => "\xEB\x87\x92", + "\x87\x79" => "\xEB\x87\x93", + "\x87\x7A" => "\xEB\x87\x95", + "\x87\x81" => "\xEB\x87\x96", + "\x87\x82" => "\xEB\x87\x97", + "\x87\x83" => "\xEB\x87\x98", + "\x87\x84" => "\xEB\x87\x99", + "\x87\x85" => "\xEB\x87\x9A", + "\x87\x86" => "\xEB\x87\x9B", + "\x87\x87" => "\xEB\x87\x9E", + "\x87\x88" => "\xEB\x87\xA0", + "\x87\x89" => "\xEB\x87\xA1", + "\x87\x8A" => "\xEB\x87\xA2", + "\x87\x8B" => "\xEB\x87\xA3", + "\x87\x8C" => "\xEB\x87\xA4", + "\x87\x8D" => "\xEB\x87\xA5", + "\x87\x8E" => "\xEB\x87\xA6", + "\x87\x8F" => "\xEB\x87\xA7", + "\x87\x90" => "\xEB\x87\xAA", + "\x87\x91" => "\xEB\x87\xAB", + "\x87\x92" => "\xEB\x87\xAD", + "\x87\x93" => "\xEB\x87\xAE", + "\x87\x94" => "\xEB\x87\xAF", + "\x87\x95" => "\xEB\x87\xB1", + "\x87\x96" => "\xEB\x87\xB2", + "\x87\x97" => "\xEB\x87\xB3", + "\x87\x98" => "\xEB\x87\xB4", + "\x87\x99" => "\xEB\x87\xB5", + "\x87\x9A" => "\xEB\x87\xB6", + "\x87\x9B" => "\xEB\x87\xB7", + "\x87\x9C" => "\xEB\x87\xB8", + "\x87\x9D" => "\xEB\x87\xBA", + "\x87\x9E" => "\xEB\x87\xBC", + "\x87\x9F" => "\xEB\x87\xBE", + "\x87\xA0" => "\xEB\x87\xBF", + "\x87\xA1" => "\xEB\x88\x80", + "\x87\xA2" => "\xEB\x88\x81", + "\x87\xA3" => "\xEB\x88\x82", + "\x87\xA4" => "\xEB\x88\x83", + "\x87\xA5" => "\xEB\x88\x86", + "\x87\xA6" => "\xEB\x88\x87", + "\x87\xA7" => "\xEB\x88\x89", + "\x87\xA8" => "\xEB\x88\x8A", + "\x87\xA9" => "\xEB\x88\x8D", + "\x87\xAA" => "\xEB\x88\x8E", + "\x87\xAB" => "\xEB\x88\x8F", + "\x87\xAC" => "\xEB\x88\x90", + "\x87\xAD" => "\xEB\x88\x91", + "\x87\xAE" => "\xEB\x88\x92", + "\x87\xAF" => "\xEB\x88\x93", + "\x87\xB0" => "\xEB\x88\x96", + "\x87\xB1" => "\xEB\x88\x98", + "\x87\xB2" => "\xEB\x88\x9A", + "\x87\xB3" => "\xEB\x88\x9B", + "\x87\xB4" => "\xEB\x88\x9C", + "\x87\xB5" => "\xEB\x88\x9D", + "\x87\xB6" => "\xEB\x88\x9E", + "\x87\xB7" => "\xEB\x88\x9F", + "\x87\xB8" => "\xEB\x88\xA1", + "\x87\xB9" => "\xEB\x88\xA2", + "\x87\xBA" => "\xEB\x88\xA3", + "\x87\xBB" => "\xEB\x88\xA4", + "\x87\xBC" => "\xEB\x88\xA5", + "\x87\xBD" => "\xEB\x88\xA6", + "\x87\xBE" => "\xEB\x88\xA7", + "\x87\xBF" => "\xEB\x88\xA8", + "\x87\xC0" => "\xEB\x88\xA9", + "\x87\xC1" => "\xEB\x88\xAA", + "\x87\xC2" => "\xEB\x88\xAB", + "\x87\xC3" => "\xEB\x88\xAC", + "\x87\xC4" => "\xEB\x88\xAD", + "\x87\xC5" => "\xEB\x88\xAE", + "\x87\xC6" => "\xEB\x88\xAF", + "\x87\xC7" => "\xEB\x88\xB0", + "\x87\xC8" => "\xEB\x88\xB1", + "\x87\xC9" => "\xEB\x88\xB2", + "\x87\xCA" => "\xEB\x88\xB3", + "\x87\xCB" => "\xEB\x88\xB5", + "\x87\xCC" => "\xEB\x88\xB6", + "\x87\xCD" => "\xEB\x88\xB7", + "\x87\xCE" => "\xEB\x88\xB8", + "\x87\xCF" => "\xEB\x88\xB9", + "\x87\xD0" => "\xEB\x88\xBA", + "\x87\xD1" => "\xEB\x88\xBB", + "\x87\xD2" => "\xEB\x88\xBD", + "\x87\xD3" => "\xEB\x88\xBE", + "\x87\xD4" => "\xEB\x88\xBF", + "\x87\xD5" => "\xEB\x89\x80", + "\x87\xD6" => "\xEB\x89\x81", + "\x87\xD7" => "\xEB\x89\x82", + "\x87\xD8" => "\xEB\x89\x83", + "\x87\xD9" => "\xEB\x89\x84", + "\x87\xDA" => "\xEB\x89\x85", + "\x87\xDB" => "\xEB\x89\x86", + "\x87\xDC" => "\xEB\x89\x87", + "\x87\xDD" => "\xEB\x89\x88", + "\x87\xDE" => "\xEB\x89\x89", + "\x87\xDF" => "\xEB\x89\x8A", + "\x87\xE0" => "\xEB\x89\x8B", + "\x87\xE1" => "\xEB\x89\x8C", + "\x87\xE2" => "\xEB\x89\x8D", + "\x87\xE3" => "\xEB\x89\x8E", + "\x87\xE4" => "\xEB\x89\x8F", + "\x87\xE5" => "\xEB\x89\x90", + "\x87\xE6" => "\xEB\x89\x91", + "\x87\xE7" => "\xEB\x89\x92", + "\x87\xE8" => "\xEB\x89\x93", + "\x87\xE9" => "\xEB\x89\x94", + "\x87\xEA" => "\xEB\x89\x95", + "\x87\xEB" => "\xEB\x89\x96", + "\x87\xEC" => "\xEB\x89\x97", + "\x87\xED" => "\xEB\x89\x99", + "\x87\xEE" => "\xEB\x89\x9A", + "\x87\xEF" => "\xEB\x89\x9B", + "\x87\xF0" => "\xEB\x89\x9D", + "\x87\xF1" => "\xEB\x89\x9E", + "\x87\xF2" => "\xEB\x89\x9F", + "\x87\xF3" => "\xEB\x89\xA1", + "\x87\xF4" => "\xEB\x89\xA2", + "\x87\xF5" => "\xEB\x89\xA3", + "\x87\xF6" => "\xEB\x89\xA4", + "\x87\xF7" => "\xEB\x89\xA5", + "\x87\xF8" => "\xEB\x89\xA6", + "\x87\xF9" => "\xEB\x89\xA7", + "\x87\xFA" => "\xEB\x89\xAA", + "\x87\xFB" => "\xEB\x89\xAB", + "\x87\xFC" => "\xEB\x89\xAC", + "\x87\xFD" => "\xEB\x89\xAD", + "\x87\xFE" => "\xEB\x89\xAE", + "\x88\x41" => "\xEB\x89\xAF", + "\x88\x42" => "\xEB\x89\xB0", + "\x88\x43" => "\xEB\x89\xB1", + "\x88\x44" => "\xEB\x89\xB2", + "\x88\x45" => "\xEB\x89\xB3", + "\x88\x46" => "\xEB\x89\xB6", + "\x88\x47" => "\xEB\x89\xB7", + "\x88\x48" => "\xEB\x89\xB8", + "\x88\x49" => "\xEB\x89\xB9", + "\x88\x4A" => "\xEB\x89\xBA", + "\x88\x4B" => "\xEB\x89\xBB", + "\x88\x4C" => "\xEB\x89\xBD", + "\x88\x4D" => "\xEB\x89\xBE", + "\x88\x4E" => "\xEB\x89\xBF", + "\x88\x4F" => "\xEB\x8A\x80", + "\x88\x50" => "\xEB\x8A\x81", + "\x88\x51" => "\xEB\x8A\x82", + "\x88\x52" => "\xEB\x8A\x83", + "\x88\x53" => "\xEB\x8A\x86", + "\x88\x54" => "\xEB\x8A\x87", + "\x88\x55" => "\xEB\x8A\x88", + "\x88\x56" => "\xEB\x8A\x8A", + "\x88\x57" => "\xEB\x8A\x8B", + "\x88\x58" => "\xEB\x8A\x8C", + "\x88\x59" => "\xEB\x8A\x8D", + "\x88\x5A" => "\xEB\x8A\x8E", + "\x88\x61" => "\xEB\x8A\x8F", + "\x88\x62" => "\xEB\x8A\x92", + "\x88\x63" => "\xEB\x8A\x93", + "\x88\x64" => "\xEB\x8A\x95", + "\x88\x65" => "\xEB\x8A\x96", + "\x88\x66" => "\xEB\x8A\x97", + "\x88\x67" => "\xEB\x8A\x9B", + "\x88\x68" => "\xEB\x8A\x9C", + "\x88\x69" => "\xEB\x8A\x9D", + "\x88\x6A" => "\xEB\x8A\x9E", + "\x88\x6B" => "\xEB\x8A\x9F", + "\x88\x6C" => "\xEB\x8A\xA2", + "\x88\x6D" => "\xEB\x8A\xA4", + "\x88\x6E" => "\xEB\x8A\xA7", + "\x88\x6F" => "\xEB\x8A\xA8", + "\x88\x70" => "\xEB\x8A\xA9", + "\x88\x71" => "\xEB\x8A\xAB", + "\x88\x72" => "\xEB\x8A\xAD", + "\x88\x73" => "\xEB\x8A\xAE", + "\x88\x74" => "\xEB\x8A\xAF", + "\x88\x75" => "\xEB\x8A\xB1", + "\x88\x76" => "\xEB\x8A\xB2", + "\x88\x77" => "\xEB\x8A\xB3", + "\x88\x78" => "\xEB\x8A\xB5", + "\x88\x79" => "\xEB\x8A\xB6", + "\x88\x7A" => "\xEB\x8A\xB7", + "\x88\x81" => "\xEB\x8A\xB8", + "\x88\x82" => "\xEB\x8A\xB9", + "\x88\x83" => "\xEB\x8A\xBA", + "\x88\x84" => "\xEB\x8A\xBB", + "\x88\x85" => "\xEB\x8A\xBC", + "\x88\x86" => "\xEB\x8A\xBD", + "\x88\x87" => "\xEB\x8A\xBE", + "\x88\x88" => "\xEB\x8A\xBF", + "\x88\x89" => "\xEB\x8B\x80", + "\x88\x8A" => "\xEB\x8B\x81", + "\x88\x8B" => "\xEB\x8B\x82", + "\x88\x8C" => "\xEB\x8B\x83", + "\x88\x8D" => "\xEB\x8B\x84", + "\x88\x8E" => "\xEB\x8B\x85", + "\x88\x8F" => "\xEB\x8B\x86", + "\x88\x90" => "\xEB\x8B\x87", + "\x88\x91" => "\xEB\x8B\x8A", + "\x88\x92" => "\xEB\x8B\x8B", + "\x88\x93" => "\xEB\x8B\x8D", + "\x88\x94" => "\xEB\x8B\x8E", + "\x88\x95" => "\xEB\x8B\x8F", + "\x88\x96" => "\xEB\x8B\x91", + "\x88\x97" => "\xEB\x8B\x93", + "\x88\x98" => "\xEB\x8B\x94", + "\x88\x99" => "\xEB\x8B\x95", + "\x88\x9A" => "\xEB\x8B\x96", + "\x88\x9B" => "\xEB\x8B\x97", + "\x88\x9C" => "\xEB\x8B\x9A", + "\x88\x9D" => "\xEB\x8B\x9C", + "\x88\x9E" => "\xEB\x8B\x9E", + "\x88\x9F" => "\xEB\x8B\x9F", + "\x88\xA0" => "\xEB\x8B\xA0", + "\x88\xA1" => "\xEB\x8B\xA1", + "\x88\xA2" => "\xEB\x8B\xA3", + "\x88\xA3" => "\xEB\x8B\xA7", + "\x88\xA4" => "\xEB\x8B\xA9", + "\x88\xA5" => "\xEB\x8B\xAA", + "\x88\xA6" => "\xEB\x8B\xB0", + "\x88\xA7" => "\xEB\x8B\xB1", + "\x88\xA8" => "\xEB\x8B\xB2", + "\x88\xA9" => "\xEB\x8B\xB6", + "\x88\xAA" => "\xEB\x8B\xBC", + "\x88\xAB" => "\xEB\x8B\xBD", + "\x88\xAC" => "\xEB\x8B\xBE", + "\x88\xAD" => "\xEB\x8C\x82", + "\x88\xAE" => "\xEB\x8C\x83", + "\x88\xAF" => "\xEB\x8C\x85", + "\x88\xB0" => "\xEB\x8C\x86", + "\x88\xB1" => "\xEB\x8C\x87", + "\x88\xB2" => "\xEB\x8C\x89", + "\x88\xB3" => "\xEB\x8C\x8A", + "\x88\xB4" => "\xEB\x8C\x8B", + "\x88\xB5" => "\xEB\x8C\x8C", + "\x88\xB6" => "\xEB\x8C\x8D", + "\x88\xB7" => "\xEB\x8C\x8E", + "\x88\xB8" => "\xEB\x8C\x8F", + "\x88\xB9" => "\xEB\x8C\x92", + "\x88\xBA" => "\xEB\x8C\x96", + "\x88\xBB" => "\xEB\x8C\x97", + "\x88\xBC" => "\xEB\x8C\x98", + "\x88\xBD" => "\xEB\x8C\x99", + "\x88\xBE" => "\xEB\x8C\x9A", + "\x88\xBF" => "\xEB\x8C\x9B", + "\x88\xC0" => "\xEB\x8C\x9D", + "\x88\xC1" => "\xEB\x8C\x9E", + "\x88\xC2" => "\xEB\x8C\x9F", + "\x88\xC3" => "\xEB\x8C\xA0", + "\x88\xC4" => "\xEB\x8C\xA1", + "\x88\xC5" => "\xEB\x8C\xA2", + "\x88\xC6" => "\xEB\x8C\xA3", + "\x88\xC7" => "\xEB\x8C\xA4", + "\x88\xC8" => "\xEB\x8C\xA5", + "\x88\xC9" => "\xEB\x8C\xA6", + "\x88\xCA" => "\xEB\x8C\xA7", + "\x88\xCB" => "\xEB\x8C\xA8", + "\x88\xCC" => "\xEB\x8C\xA9", + "\x88\xCD" => "\xEB\x8C\xAA", + "\x88\xCE" => "\xEB\x8C\xAB", + "\x88\xCF" => "\xEB\x8C\xAC", + "\x88\xD0" => "\xEB\x8C\xAD", + "\x88\xD1" => "\xEB\x8C\xAE", + "\x88\xD2" => "\xEB\x8C\xAF", + "\x88\xD3" => "\xEB\x8C\xB0", + "\x88\xD4" => "\xEB\x8C\xB1", + "\x88\xD5" => "\xEB\x8C\xB2", + "\x88\xD6" => "\xEB\x8C\xB3", + "\x88\xD7" => "\xEB\x8C\xB4", + "\x88\xD8" => "\xEB\x8C\xB5", + "\x88\xD9" => "\xEB\x8C\xB6", + "\x88\xDA" => "\xEB\x8C\xB7", + "\x88\xDB" => "\xEB\x8C\xB8", + "\x88\xDC" => "\xEB\x8C\xB9", + "\x88\xDD" => "\xEB\x8C\xBA", + "\x88\xDE" => "\xEB\x8C\xBB", + "\x88\xDF" => "\xEB\x8C\xBC", + "\x88\xE0" => "\xEB\x8C\xBD", + "\x88\xE1" => "\xEB\x8C\xBE", + "\x88\xE2" => "\xEB\x8C\xBF", + "\x88\xE3" => "\xEB\x8D\x80", + "\x88\xE4" => "\xEB\x8D\x81", + "\x88\xE5" => "\xEB\x8D\x82", + "\x88\xE6" => "\xEB\x8D\x83", + "\x88\xE7" => "\xEB\x8D\x84", + "\x88\xE8" => "\xEB\x8D\x85", + "\x88\xE9" => "\xEB\x8D\x86", + "\x88\xEA" => "\xEB\x8D\x87", + "\x88\xEB" => "\xEB\x8D\x88", + "\x88\xEC" => "\xEB\x8D\x89", + "\x88\xED" => "\xEB\x8D\x8A", + "\x88\xEE" => "\xEB\x8D\x8B", + "\x88\xEF" => "\xEB\x8D\x8C", + "\x88\xF0" => "\xEB\x8D\x8D", + "\x88\xF1" => "\xEB\x8D\x8E", + "\x88\xF2" => "\xEB\x8D\x8F", + "\x88\xF3" => "\xEB\x8D\x90", + "\x88\xF4" => "\xEB\x8D\x91", + "\x88\xF5" => "\xEB\x8D\x92", + "\x88\xF6" => "\xEB\x8D\x93", + "\x88\xF7" => "\xEB\x8D\x97", + "\x88\xF8" => "\xEB\x8D\x99", + "\x88\xF9" => "\xEB\x8D\x9A", + "\x88\xFA" => "\xEB\x8D\x9D", + "\x88\xFB" => "\xEB\x8D\xA0", + "\x88\xFC" => "\xEB\x8D\xA1", + "\x88\xFD" => "\xEB\x8D\xA2", + "\x88\xFE" => "\xEB\x8D\xA3", + "\x89\x41" => "\xEB\x8D\xA6", + "\x89\x42" => "\xEB\x8D\xA8", + "\x89\x43" => "\xEB\x8D\xAA", + "\x89\x44" => "\xEB\x8D\xAC", + "\x89\x45" => "\xEB\x8D\xAD", + "\x89\x46" => "\xEB\x8D\xAF", + "\x89\x47" => "\xEB\x8D\xB2", + "\x89\x48" => "\xEB\x8D\xB3", + "\x89\x49" => "\xEB\x8D\xB5", + "\x89\x4A" => "\xEB\x8D\xB6", + "\x89\x4B" => "\xEB\x8D\xB7", + "\x89\x4C" => "\xEB\x8D\xB9", + "\x89\x4D" => "\xEB\x8D\xBA", + "\x89\x4E" => "\xEB\x8D\xBB", + "\x89\x4F" => "\xEB\x8D\xBC", + "\x89\x50" => "\xEB\x8D\xBD", + "\x89\x51" => "\xEB\x8D\xBE", + "\x89\x52" => "\xEB\x8D\xBF", + "\x89\x53" => "\xEB\x8E\x82", + "\x89\x54" => "\xEB\x8E\x86", + "\x89\x55" => "\xEB\x8E\x87", + "\x89\x56" => "\xEB\x8E\x88", + "\x89\x57" => "\xEB\x8E\x89", + "\x89\x58" => "\xEB\x8E\x8A", + "\x89\x59" => "\xEB\x8E\x8B", + "\x89\x5A" => "\xEB\x8E\x8D", + "\x89\x61" => "\xEB\x8E\x8E", + "\x89\x62" => "\xEB\x8E\x8F", + "\x89\x63" => "\xEB\x8E\x91", + "\x89\x64" => "\xEB\x8E\x92", + "\x89\x65" => "\xEB\x8E\x93", + "\x89\x66" => "\xEB\x8E\x95", + "\x89\x67" => "\xEB\x8E\x96", + "\x89\x68" => "\xEB\x8E\x97", + "\x89\x69" => "\xEB\x8E\x98", + "\x89\x6A" => "\xEB\x8E\x99", + "\x89\x6B" => "\xEB\x8E\x9A", + "\x89\x6C" => "\xEB\x8E\x9B", + "\x89\x6D" => "\xEB\x8E\x9C", + "\x89\x6E" => "\xEB\x8E\x9D", + "\x89\x6F" => "\xEB\x8E\x9E", + "\x89\x70" => "\xEB\x8E\x9F", + "\x89\x71" => "\xEB\x8E\xA2", + "\x89\x72" => "\xEB\x8E\xA3", + "\x89\x73" => "\xEB\x8E\xA4", + "\x89\x74" => "\xEB\x8E\xA5", + "\x89\x75" => "\xEB\x8E\xA6", + "\x89\x76" => "\xEB\x8E\xA7", + "\x89\x77" => "\xEB\x8E\xA9", + "\x89\x78" => "\xEB\x8E\xAA", + "\x89\x79" => "\xEB\x8E\xAB", + "\x89\x7A" => "\xEB\x8E\xAD", + "\x89\x81" => "\xEB\x8E\xAE", + "\x89\x82" => "\xEB\x8E\xAF", + "\x89\x83" => "\xEB\x8E\xB0", + "\x89\x84" => "\xEB\x8E\xB1", + "\x89\x85" => "\xEB\x8E\xB2", + "\x89\x86" => "\xEB\x8E\xB3", + "\x89\x87" => "\xEB\x8E\xB4", + "\x89\x88" => "\xEB\x8E\xB5", + "\x89\x89" => "\xEB\x8E\xB6", + "\x89\x8A" => "\xEB\x8E\xB7", + "\x89\x8B" => "\xEB\x8E\xB8", + "\x89\x8C" => "\xEB\x8E\xB9", + "\x89\x8D" => "\xEB\x8E\xBA", + "\x89\x8E" => "\xEB\x8E\xBB", + "\x89\x8F" => "\xEB\x8E\xBC", + "\x89\x90" => "\xEB\x8E\xBD", + "\x89\x91" => "\xEB\x8E\xBE", + "\x89\x92" => "\xEB\x8E\xBF", + "\x89\x93" => "\xEB\x8F\x80", + "\x89\x94" => "\xEB\x8F\x81", + "\x89\x95" => "\xEB\x8F\x82", + "\x89\x96" => "\xEB\x8F\x83", + "\x89\x97" => "\xEB\x8F\x86", + "\x89\x98" => "\xEB\x8F\x87", + "\x89\x99" => "\xEB\x8F\x89", + "\x89\x9A" => "\xEB\x8F\x8A", + "\x89\x9B" => "\xEB\x8F\x8D", + "\x89\x9C" => "\xEB\x8F\x8F", + "\x89\x9D" => "\xEB\x8F\x91", + "\x89\x9E" => "\xEB\x8F\x92", + "\x89\x9F" => "\xEB\x8F\x93", + "\x89\xA0" => "\xEB\x8F\x96", + "\x89\xA1" => "\xEB\x8F\x98", + "\x89\xA2" => "\xEB\x8F\x9A", + "\x89\xA3" => "\xEB\x8F\x9C", + "\x89\xA4" => "\xEB\x8F\x9E", + "\x89\xA5" => "\xEB\x8F\x9F", + "\x89\xA6" => "\xEB\x8F\xA1", + "\x89\xA7" => "\xEB\x8F\xA2", + "\x89\xA8" => "\xEB\x8F\xA3", + "\x89\xA9" => "\xEB\x8F\xA5", + "\x89\xAA" => "\xEB\x8F\xA6", + "\x89\xAB" => "\xEB\x8F\xA7", + "\x89\xAC" => "\xEB\x8F\xA9", + "\x89\xAD" => "\xEB\x8F\xAA", + "\x89\xAE" => "\xEB\x8F\xAB", + "\x89\xAF" => "\xEB\x8F\xAC", + "\x89\xB0" => "\xEB\x8F\xAD", + "\x89\xB1" => "\xEB\x8F\xAE", + "\x89\xB2" => "\xEB\x8F\xAF", + "\x89\xB3" => "\xEB\x8F\xB0", + "\x89\xB4" => "\xEB\x8F\xB1", + "\x89\xB5" => "\xEB\x8F\xB2", + "\x89\xB6" => "\xEB\x8F\xB3", + "\x89\xB7" => "\xEB\x8F\xB4", + "\x89\xB8" => "\xEB\x8F\xB5", + "\x89\xB9" => "\xEB\x8F\xB6", + "\x89\xBA" => "\xEB\x8F\xB7", + "\x89\xBB" => "\xEB\x8F\xB8", + "\x89\xBC" => "\xEB\x8F\xB9", + "\x89\xBD" => "\xEB\x8F\xBA", + "\x89\xBE" => "\xEB\x8F\xBB", + "\x89\xBF" => "\xEB\x8F\xBD", + "\x89\xC0" => "\xEB\x8F\xBE", + "\x89\xC1" => "\xEB\x8F\xBF", + "\x89\xC2" => "\xEB\x90\x80", + "\x89\xC3" => "\xEB\x90\x81", + "\x89\xC4" => "\xEB\x90\x82", + "\x89\xC5" => "\xEB\x90\x83", + "\x89\xC6" => "\xEB\x90\x84", + "\x89\xC7" => "\xEB\x90\x85", + "\x89\xC8" => "\xEB\x90\x86", + "\x89\xC9" => "\xEB\x90\x87", + "\x89\xCA" => "\xEB\x90\x88", + "\x89\xCB" => "\xEB\x90\x89", + "\x89\xCC" => "\xEB\x90\x8A", + "\x89\xCD" => "\xEB\x90\x8B", + "\x89\xCE" => "\xEB\x90\x8C", + "\x89\xCF" => "\xEB\x90\x8D", + "\x89\xD0" => "\xEB\x90\x8E", + "\x89\xD1" => "\xEB\x90\x8F", + "\x89\xD2" => "\xEB\x90\x91", + "\x89\xD3" => "\xEB\x90\x92", + "\x89\xD4" => "\xEB\x90\x93", + "\x89\xD5" => "\xEB\x90\x94", + "\x89\xD6" => "\xEB\x90\x95", + "\x89\xD7" => "\xEB\x90\x96", + "\x89\xD8" => "\xEB\x90\x97", + "\x89\xD9" => "\xEB\x90\x99", + "\x89\xDA" => "\xEB\x90\x9A", + "\x89\xDB" => "\xEB\x90\x9B", + "\x89\xDC" => "\xEB\x90\x9D", + "\x89\xDD" => "\xEB\x90\x9E", + "\x89\xDE" => "\xEB\x90\x9F", + "\x89\xDF" => "\xEB\x90\xA1", + "\x89\xE0" => "\xEB\x90\xA2", + "\x89\xE1" => "\xEB\x90\xA3", + "\x89\xE2" => "\xEB\x90\xA4", + "\x89\xE3" => "\xEB\x90\xA5", + "\x89\xE4" => "\xEB\x90\xA6", + "\x89\xE5" => "\xEB\x90\xA7", + "\x89\xE6" => "\xEB\x90\xAA", + "\x89\xE7" => "\xEB\x90\xAC", + "\x89\xE8" => "\xEB\x90\xAD", + "\x89\xE9" => "\xEB\x90\xAE", + "\x89\xEA" => "\xEB\x90\xAF", + "\x89\xEB" => "\xEB\x90\xB0", + "\x89\xEC" => "\xEB\x90\xB1", + "\x89\xED" => "\xEB\x90\xB2", + "\x89\xEE" => "\xEB\x90\xB3", + "\x89\xEF" => "\xEB\x90\xB5", + "\x89\xF0" => "\xEB\x90\xB6", + "\x89\xF1" => "\xEB\x90\xB7", + "\x89\xF2" => "\xEB\x90\xB8", + "\x89\xF3" => "\xEB\x90\xB9", + "\x89\xF4" => "\xEB\x90\xBA", + "\x89\xF5" => "\xEB\x90\xBB", + "\x89\xF6" => "\xEB\x90\xBC", + "\x89\xF7" => "\xEB\x90\xBD", + "\x89\xF8" => "\xEB\x90\xBE", + "\x89\xF9" => "\xEB\x90\xBF", + "\x89\xFA" => "\xEB\x91\x80", + "\x89\xFB" => "\xEB\x91\x81", + "\x89\xFC" => "\xEB\x91\x82", + "\x89\xFD" => "\xEB\x91\x83", + "\x89\xFE" => "\xEB\x91\x84", + "\x8A\x41" => "\xEB\x91\x85", + "\x8A\x42" => "\xEB\x91\x86", + "\x8A\x43" => "\xEB\x91\x87", + "\x8A\x44" => "\xEB\x91\x88", + "\x8A\x45" => "\xEB\x91\x89", + "\x8A\x46" => "\xEB\x91\x8A", + "\x8A\x47" => "\xEB\x91\x8B", + "\x8A\x48" => "\xEB\x91\x8C", + "\x8A\x49" => "\xEB\x91\x8D", + "\x8A\x4A" => "\xEB\x91\x8E", + "\x8A\x4B" => "\xEB\x91\x8F", + "\x8A\x4C" => "\xEB\x91\x92", + "\x8A\x4D" => "\xEB\x91\x93", + "\x8A\x4E" => "\xEB\x91\x95", + "\x8A\x4F" => "\xEB\x91\x96", + "\x8A\x50" => "\xEB\x91\x97", + "\x8A\x51" => "\xEB\x91\x99", + "\x8A\x52" => "\xEB\x91\x9A", + "\x8A\x53" => "\xEB\x91\x9B", + "\x8A\x54" => "\xEB\x91\x9C", + "\x8A\x55" => "\xEB\x91\x9D", + "\x8A\x56" => "\xEB\x91\x9E", + "\x8A\x57" => "\xEB\x91\x9F", + "\x8A\x58" => "\xEB\x91\xA2", + "\x8A\x59" => "\xEB\x91\xA4", + "\x8A\x5A" => "\xEB\x91\xA6", + "\x8A\x61" => "\xEB\x91\xA7", + "\x8A\x62" => "\xEB\x91\xA8", + "\x8A\x63" => "\xEB\x91\xA9", + "\x8A\x64" => "\xEB\x91\xAA", + "\x8A\x65" => "\xEB\x91\xAB", + "\x8A\x66" => "\xEB\x91\xAD", + "\x8A\x67" => "\xEB\x91\xAE", + "\x8A\x68" => "\xEB\x91\xAF", + "\x8A\x69" => "\xEB\x91\xB0", + "\x8A\x6A" => "\xEB\x91\xB1", + "\x8A\x6B" => "\xEB\x91\xB2", + "\x8A\x6C" => "\xEB\x91\xB3", + "\x8A\x6D" => "\xEB\x91\xB4", + "\x8A\x6E" => "\xEB\x91\xB5", + "\x8A\x6F" => "\xEB\x91\xB6", + "\x8A\x70" => "\xEB\x91\xB7", + "\x8A\x71" => "\xEB\x91\xB8", + "\x8A\x72" => "\xEB\x91\xB9", + "\x8A\x73" => "\xEB\x91\xBA", + "\x8A\x74" => "\xEB\x91\xBB", + "\x8A\x75" => "\xEB\x91\xBC", + "\x8A\x76" => "\xEB\x91\xBD", + "\x8A\x77" => "\xEB\x91\xBE", + "\x8A\x78" => "\xEB\x91\xBF", + "\x8A\x79" => "\xEB\x92\x81", + "\x8A\x7A" => "\xEB\x92\x82", + "\x8A\x81" => "\xEB\x92\x83", + "\x8A\x82" => "\xEB\x92\x84", + "\x8A\x83" => "\xEB\x92\x85", + "\x8A\x84" => "\xEB\x92\x86", + "\x8A\x85" => "\xEB\x92\x87", + "\x8A\x86" => "\xEB\x92\x89", + "\x8A\x87" => "\xEB\x92\x8A", + "\x8A\x88" => "\xEB\x92\x8B", + "\x8A\x89" => "\xEB\x92\x8C", + "\x8A\x8A" => "\xEB\x92\x8D", + "\x8A\x8B" => "\xEB\x92\x8E", + "\x8A\x8C" => "\xEB\x92\x8F", + "\x8A\x8D" => "\xEB\x92\x90", + "\x8A\x8E" => "\xEB\x92\x91", + "\x8A\x8F" => "\xEB\x92\x92", + "\x8A\x90" => "\xEB\x92\x93", + "\x8A\x91" => "\xEB\x92\x94", + "\x8A\x92" => "\xEB\x92\x95", + "\x8A\x93" => "\xEB\x92\x96", + "\x8A\x94" => "\xEB\x92\x97", + "\x8A\x95" => "\xEB\x92\x98", + "\x8A\x96" => "\xEB\x92\x99", + "\x8A\x97" => "\xEB\x92\x9A", + "\x8A\x98" => "\xEB\x92\x9B", + "\x8A\x99" => "\xEB\x92\x9C", + "\x8A\x9A" => "\xEB\x92\x9E", + "\x8A\x9B" => "\xEB\x92\x9F", + "\x8A\x9C" => "\xEB\x92\xA0", + "\x8A\x9D" => "\xEB\x92\xA1", + "\x8A\x9E" => "\xEB\x92\xA2", + "\x8A\x9F" => "\xEB\x92\xA3", + "\x8A\xA0" => "\xEB\x92\xA5", + "\x8A\xA1" => "\xEB\x92\xA6", + "\x8A\xA2" => "\xEB\x92\xA7", + "\x8A\xA3" => "\xEB\x92\xA9", + "\x8A\xA4" => "\xEB\x92\xAA", + "\x8A\xA5" => "\xEB\x92\xAB", + "\x8A\xA6" => "\xEB\x92\xAD", + "\x8A\xA7" => "\xEB\x92\xAE", + "\x8A\xA8" => "\xEB\x92\xAF", + "\x8A\xA9" => "\xEB\x92\xB0", + "\x8A\xAA" => "\xEB\x92\xB1", + "\x8A\xAB" => "\xEB\x92\xB2", + "\x8A\xAC" => "\xEB\x92\xB3", + "\x8A\xAD" => "\xEB\x92\xB4", + "\x8A\xAE" => "\xEB\x92\xB6", + "\x8A\xAF" => "\xEB\x92\xB8", + "\x8A\xB0" => "\xEB\x92\xBA", + "\x8A\xB1" => "\xEB\x92\xBB", + "\x8A\xB2" => "\xEB\x92\xBC", + "\x8A\xB3" => "\xEB\x92\xBD", + "\x8A\xB4" => "\xEB\x92\xBE", + "\x8A\xB5" => "\xEB\x92\xBF", + "\x8A\xB6" => "\xEB\x93\x81", + "\x8A\xB7" => "\xEB\x93\x82", + "\x8A\xB8" => "\xEB\x93\x83", + "\x8A\xB9" => "\xEB\x93\x85", + "\x8A\xBA" => "\xEB\x93\x86", + "\x8A\xBB" => "\xEB\x93\x87", + "\x8A\xBC" => "\xEB\x93\x89", + "\x8A\xBD" => "\xEB\x93\x8A", + "\x8A\xBE" => "\xEB\x93\x8B", + "\x8A\xBF" => "\xEB\x93\x8C", + "\x8A\xC0" => "\xEB\x93\x8D", + "\x8A\xC1" => "\xEB\x93\x8E", + "\x8A\xC2" => "\xEB\x93\x8F", + "\x8A\xC3" => "\xEB\x93\x91", + "\x8A\xC4" => "\xEB\x93\x92", + "\x8A\xC5" => "\xEB\x93\x93", + "\x8A\xC6" => "\xEB\x93\x94", + "\x8A\xC7" => "\xEB\x93\x96", + "\x8A\xC8" => "\xEB\x93\x97", + "\x8A\xC9" => "\xEB\x93\x98", + "\x8A\xCA" => "\xEB\x93\x99", + "\x8A\xCB" => "\xEB\x93\x9A", + "\x8A\xCC" => "\xEB\x93\x9B", + "\x8A\xCD" => "\xEB\x93\x9E", + "\x8A\xCE" => "\xEB\x93\x9F", + "\x8A\xCF" => "\xEB\x93\xA1", + "\x8A\xD0" => "\xEB\x93\xA2", + "\x8A\xD1" => "\xEB\x93\xA5", + "\x8A\xD2" => "\xEB\x93\xA7", + "\x8A\xD3" => "\xEB\x93\xA8", + "\x8A\xD4" => "\xEB\x93\xA9", + "\x8A\xD5" => "\xEB\x93\xAA", + "\x8A\xD6" => "\xEB\x93\xAB", + "\x8A\xD7" => "\xEB\x93\xAE", + "\x8A\xD8" => "\xEB\x93\xB0", + "\x8A\xD9" => "\xEB\x93\xB2", + "\x8A\xDA" => "\xEB\x93\xB3", + "\x8A\xDB" => "\xEB\x93\xB4", + "\x8A\xDC" => "\xEB\x93\xB5", + "\x8A\xDD" => "\xEB\x93\xB6", + "\x8A\xDE" => "\xEB\x93\xB7", + "\x8A\xDF" => "\xEB\x93\xB9", + "\x8A\xE0" => "\xEB\x93\xBA", + "\x8A\xE1" => "\xEB\x93\xBB", + "\x8A\xE2" => "\xEB\x93\xBC", + "\x8A\xE3" => "\xEB\x93\xBD", + "\x8A\xE4" => "\xEB\x93\xBE", + "\x8A\xE5" => "\xEB\x93\xBF", + "\x8A\xE6" => "\xEB\x94\x80", + "\x8A\xE7" => "\xEB\x94\x81", + "\x8A\xE8" => "\xEB\x94\x82", + "\x8A\xE9" => "\xEB\x94\x83", + "\x8A\xEA" => "\xEB\x94\x84", + "\x8A\xEB" => "\xEB\x94\x85", + "\x8A\xEC" => "\xEB\x94\x86", + "\x8A\xED" => "\xEB\x94\x87", + "\x8A\xEE" => "\xEB\x94\x88", + "\x8A\xEF" => "\xEB\x94\x89", + "\x8A\xF0" => "\xEB\x94\x8A", + "\x8A\xF1" => "\xEB\x94\x8B", + "\x8A\xF2" => "\xEB\x94\x8C", + "\x8A\xF3" => "\xEB\x94\x8D", + "\x8A\xF4" => "\xEB\x94\x8E", + "\x8A\xF5" => "\xEB\x94\x8F", + "\x8A\xF6" => "\xEB\x94\x90", + "\x8A\xF7" => "\xEB\x94\x91", + "\x8A\xF8" => "\xEB\x94\x92", + "\x8A\xF9" => "\xEB\x94\x93", + "\x8A\xFA" => "\xEB\x94\x96", + "\x8A\xFB" => "\xEB\x94\x97", + "\x8A\xFC" => "\xEB\x94\x99", + "\x8A\xFD" => "\xEB\x94\x9A", + "\x8A\xFE" => "\xEB\x94\x9D", + "\x8B\x41" => "\xEB\x94\x9E", + "\x8B\x42" => "\xEB\x94\x9F", + "\x8B\x43" => "\xEB\x94\xA0", + "\x8B\x44" => "\xEB\x94\xA1", + "\x8B\x45" => "\xEB\x94\xA2", + "\x8B\x46" => "\xEB\x94\xA3", + "\x8B\x47" => "\xEB\x94\xA6", + "\x8B\x48" => "\xEB\x94\xAB", + "\x8B\x49" => "\xEB\x94\xAC", + "\x8B\x4A" => "\xEB\x94\xAD", + "\x8B\x4B" => "\xEB\x94\xAE", + "\x8B\x4C" => "\xEB\x94\xAF", + "\x8B\x4D" => "\xEB\x94\xB2", + "\x8B\x4E" => "\xEB\x94\xB3", + "\x8B\x4F" => "\xEB\x94\xB5", + "\x8B\x50" => "\xEB\x94\xB6", + "\x8B\x51" => "\xEB\x94\xB7", + "\x8B\x52" => "\xEB\x94\xB9", + "\x8B\x53" => "\xEB\x94\xBA", + "\x8B\x54" => "\xEB\x94\xBB", + "\x8B\x55" => "\xEB\x94\xBC", + "\x8B\x56" => "\xEB\x94\xBD", + "\x8B\x57" => "\xEB\x94\xBE", + "\x8B\x58" => "\xEB\x94\xBF", + "\x8B\x59" => "\xEB\x95\x82", + "\x8B\x5A" => "\xEB\x95\x86", + "\x8B\x61" => "\xEB\x95\x87", + "\x8B\x62" => "\xEB\x95\x88", + "\x8B\x63" => "\xEB\x95\x89", + "\x8B\x64" => "\xEB\x95\x8A", + "\x8B\x65" => "\xEB\x95\x8E", + "\x8B\x66" => "\xEB\x95\x8F", + "\x8B\x67" => "\xEB\x95\x91", + "\x8B\x68" => "\xEB\x95\x92", + "\x8B\x69" => "\xEB\x95\x93", + "\x8B\x6A" => "\xEB\x95\x95", + "\x8B\x6B" => "\xEB\x95\x96", + "\x8B\x6C" => "\xEB\x95\x97", + "\x8B\x6D" => "\xEB\x95\x98", + "\x8B\x6E" => "\xEB\x95\x99", + "\x8B\x6F" => "\xEB\x95\x9A", + "\x8B\x70" => "\xEB\x95\x9B", + "\x8B\x71" => "\xEB\x95\x9E", + "\x8B\x72" => "\xEB\x95\xA2", + "\x8B\x73" => "\xEB\x95\xA3", + "\x8B\x74" => "\xEB\x95\xA4", + "\x8B\x75" => "\xEB\x95\xA5", + "\x8B\x76" => "\xEB\x95\xA6", + "\x8B\x77" => "\xEB\x95\xA7", + "\x8B\x78" => "\xEB\x95\xA8", + "\x8B\x79" => "\xEB\x95\xA9", + "\x8B\x7A" => "\xEB\x95\xAA", + "\x8B\x81" => "\xEB\x95\xAB", + "\x8B\x82" => "\xEB\x95\xAC", + "\x8B\x83" => "\xEB\x95\xAD", + "\x8B\x84" => "\xEB\x95\xAE", + "\x8B\x85" => "\xEB\x95\xAF", + "\x8B\x86" => "\xEB\x95\xB0", + "\x8B\x87" => "\xEB\x95\xB1", + "\x8B\x88" => "\xEB\x95\xB2", + "\x8B\x89" => "\xEB\x95\xB3", + "\x8B\x8A" => "\xEB\x95\xB4", + "\x8B\x8B" => "\xEB\x95\xB5", + "\x8B\x8C" => "\xEB\x95\xB6", + "\x8B\x8D" => "\xEB\x95\xB7", + "\x8B\x8E" => "\xEB\x95\xB8", + "\x8B\x8F" => "\xEB\x95\xB9", + "\x8B\x90" => "\xEB\x95\xBA", + "\x8B\x91" => "\xEB\x95\xBB", + "\x8B\x92" => "\xEB\x95\xBC", + "\x8B\x93" => "\xEB\x95\xBD", + "\x8B\x94" => "\xEB\x95\xBE", + "\x8B\x95" => "\xEB\x95\xBF", + "\x8B\x96" => "\xEB\x96\x80", + "\x8B\x97" => "\xEB\x96\x81", + "\x8B\x98" => "\xEB\x96\x82", + "\x8B\x99" => "\xEB\x96\x83", + "\x8B\x9A" => "\xEB\x96\x84", + "\x8B\x9B" => "\xEB\x96\x85", + "\x8B\x9C" => "\xEB\x96\x86", + "\x8B\x9D" => "\xEB\x96\x87", + "\x8B\x9E" => "\xEB\x96\x88", + "\x8B\x9F" => "\xEB\x96\x89", + "\x8B\xA0" => "\xEB\x96\x8A", + "\x8B\xA1" => "\xEB\x96\x8B", + "\x8B\xA2" => "\xEB\x96\x8C", + "\x8B\xA3" => "\xEB\x96\x8D", + "\x8B\xA4" => "\xEB\x96\x8E", + "\x8B\xA5" => "\xEB\x96\x8F", + "\x8B\xA6" => "\xEB\x96\x90", + "\x8B\xA7" => "\xEB\x96\x91", + "\x8B\xA8" => "\xEB\x96\x92", + "\x8B\xA9" => "\xEB\x96\x93", + "\x8B\xAA" => "\xEB\x96\x94", + "\x8B\xAB" => "\xEB\x96\x95", + "\x8B\xAC" => "\xEB\x96\x96", + "\x8B\xAD" => "\xEB\x96\x97", + "\x8B\xAE" => "\xEB\x96\x98", + "\x8B\xAF" => "\xEB\x96\x99", + "\x8B\xB0" => "\xEB\x96\x9A", + "\x8B\xB1" => "\xEB\x96\x9B", + "\x8B\xB2" => "\xEB\x96\x9C", + "\x8B\xB3" => "\xEB\x96\x9D", + "\x8B\xB4" => "\xEB\x96\x9E", + "\x8B\xB5" => "\xEB\x96\x9F", + "\x8B\xB6" => "\xEB\x96\xA2", + "\x8B\xB7" => "\xEB\x96\xA3", + "\x8B\xB8" => "\xEB\x96\xA5", + "\x8B\xB9" => "\xEB\x96\xA6", + "\x8B\xBA" => "\xEB\x96\xA7", + "\x8B\xBB" => "\xEB\x96\xA9", + "\x8B\xBC" => "\xEB\x96\xAC", + "\x8B\xBD" => "\xEB\x96\xAD", + "\x8B\xBE" => "\xEB\x96\xAE", + "\x8B\xBF" => "\xEB\x96\xAF", + "\x8B\xC0" => "\xEB\x96\xB2", + "\x8B\xC1" => "\xEB\x96\xB6", + "\x8B\xC2" => "\xEB\x96\xB7", + "\x8B\xC3" => "\xEB\x96\xB8", + "\x8B\xC4" => "\xEB\x96\xB9", + "\x8B\xC5" => "\xEB\x96\xBA", + "\x8B\xC6" => "\xEB\x96\xBE", + "\x8B\xC7" => "\xEB\x96\xBF", + "\x8B\xC8" => "\xEB\x97\x81", + "\x8B\xC9" => "\xEB\x97\x82", + "\x8B\xCA" => "\xEB\x97\x83", + "\x8B\xCB" => "\xEB\x97\x85", + "\x8B\xCC" => "\xEB\x97\x86", + "\x8B\xCD" => "\xEB\x97\x87", + "\x8B\xCE" => "\xEB\x97\x88", + "\x8B\xCF" => "\xEB\x97\x89", + "\x8B\xD0" => "\xEB\x97\x8A", + "\x8B\xD1" => "\xEB\x97\x8B", + "\x8B\xD2" => "\xEB\x97\x8E", + "\x8B\xD3" => "\xEB\x97\x92", + "\x8B\xD4" => "\xEB\x97\x93", + "\x8B\xD5" => "\xEB\x97\x94", + "\x8B\xD6" => "\xEB\x97\x95", + "\x8B\xD7" => "\xEB\x97\x96", + "\x8B\xD8" => "\xEB\x97\x97", + "\x8B\xD9" => "\xEB\x97\x99", + "\x8B\xDA" => "\xEB\x97\x9A", + "\x8B\xDB" => "\xEB\x97\x9B", + "\x8B\xDC" => "\xEB\x97\x9C", + "\x8B\xDD" => "\xEB\x97\x9D", + "\x8B\xDE" => "\xEB\x97\x9E", + "\x8B\xDF" => "\xEB\x97\x9F", + "\x8B\xE0" => "\xEB\x97\xA0", + "\x8B\xE1" => "\xEB\x97\xA1", + "\x8B\xE2" => "\xEB\x97\xA2", + "\x8B\xE3" => "\xEB\x97\xA3", + "\x8B\xE4" => "\xEB\x97\xA4", + "\x8B\xE5" => "\xEB\x97\xA5", + "\x8B\xE6" => "\xEB\x97\xA6", + "\x8B\xE7" => "\xEB\x97\xA7", + "\x8B\xE8" => "\xEB\x97\xA8", + "\x8B\xE9" => "\xEB\x97\xA9", + "\x8B\xEA" => "\xEB\x97\xAA", + "\x8B\xEB" => "\xEB\x97\xAB", + "\x8B\xEC" => "\xEB\x97\xAD", + "\x8B\xED" => "\xEB\x97\xAE", + "\x8B\xEE" => "\xEB\x97\xAF", + "\x8B\xEF" => "\xEB\x97\xB0", + "\x8B\xF0" => "\xEB\x97\xB1", + "\x8B\xF1" => "\xEB\x97\xB2", + "\x8B\xF2" => "\xEB\x97\xB3", + "\x8B\xF3" => "\xEB\x97\xB4", + "\x8B\xF4" => "\xEB\x97\xB5", + "\x8B\xF5" => "\xEB\x97\xB6", + "\x8B\xF6" => "\xEB\x97\xB7", + "\x8B\xF7" => "\xEB\x97\xB8", + "\x8B\xF8" => "\xEB\x97\xB9", + "\x8B\xF9" => "\xEB\x97\xBA", + "\x8B\xFA" => "\xEB\x97\xBB", + "\x8B\xFB" => "\xEB\x97\xBC", + "\x8B\xFC" => "\xEB\x97\xBD", + "\x8B\xFD" => "\xEB\x97\xBE", + "\x8B\xFE" => "\xEB\x97\xBF", + "\x8C\x41" => "\xEB\x98\x80", + "\x8C\x42" => "\xEB\x98\x81", + "\x8C\x43" => "\xEB\x98\x82", + "\x8C\x44" => "\xEB\x98\x83", + "\x8C\x45" => "\xEB\x98\x84", + "\x8C\x46" => "\xEB\x98\x85", + "\x8C\x47" => "\xEB\x98\x86", + "\x8C\x48" => "\xEB\x98\x87", + "\x8C\x49" => "\xEB\x98\x88", + "\x8C\x4A" => "\xEB\x98\x89", + "\x8C\x4B" => "\xEB\x98\x8A", + "\x8C\x4C" => "\xEB\x98\x8B", + "\x8C\x4D" => "\xEB\x98\x8C", + "\x8C\x4E" => "\xEB\x98\x8D", + "\x8C\x4F" => "\xEB\x98\x8E", + "\x8C\x50" => "\xEB\x98\x8F", + "\x8C\x51" => "\xEB\x98\x92", + "\x8C\x52" => "\xEB\x98\x93", + "\x8C\x53" => "\xEB\x98\x95", + "\x8C\x54" => "\xEB\x98\x96", + "\x8C\x55" => "\xEB\x98\x97", + "\x8C\x56" => "\xEB\x98\x99", + "\x8C\x57" => "\xEB\x98\x9A", + "\x8C\x58" => "\xEB\x98\x9B", + "\x8C\x59" => "\xEB\x98\x9C", + "\x8C\x5A" => "\xEB\x98\x9D", + "\x8C\x61" => "\xEB\x98\x9E", + "\x8C\x62" => "\xEB\x98\x9F", + "\x8C\x63" => "\xEB\x98\xA0", + "\x8C\x64" => "\xEB\x98\xA1", + "\x8C\x65" => "\xEB\x98\xA2", + "\x8C\x66" => "\xEB\x98\xA3", + "\x8C\x67" => "\xEB\x98\xA4", + "\x8C\x68" => "\xEB\x98\xA6", + "\x8C\x69" => "\xEB\x98\xA7", + "\x8C\x6A" => "\xEB\x98\xA8", + "\x8C\x6B" => "\xEB\x98\xA9", + "\x8C\x6C" => "\xEB\x98\xAA", + "\x8C\x6D" => "\xEB\x98\xAB", + "\x8C\x6E" => "\xEB\x98\xAD", + "\x8C\x6F" => "\xEB\x98\xAE", + "\x8C\x70" => "\xEB\x98\xAF", + "\x8C\x71" => "\xEB\x98\xB0", + "\x8C\x72" => "\xEB\x98\xB1", + "\x8C\x73" => "\xEB\x98\xB2", + "\x8C\x74" => "\xEB\x98\xB3", + "\x8C\x75" => "\xEB\x98\xB5", + "\x8C\x76" => "\xEB\x98\xB6", + "\x8C\x77" => "\xEB\x98\xB7", + "\x8C\x78" => "\xEB\x98\xB8", + "\x8C\x79" => "\xEB\x98\xB9", + "\x8C\x7A" => "\xEB\x98\xBA", + "\x8C\x81" => "\xEB\x98\xBB", + "\x8C\x82" => "\xEB\x98\xBC", + "\x8C\x83" => "\xEB\x98\xBD", + "\x8C\x84" => "\xEB\x98\xBE", + "\x8C\x85" => "\xEB\x98\xBF", + "\x8C\x86" => "\xEB\x99\x80", + "\x8C\x87" => "\xEB\x99\x81", + "\x8C\x88" => "\xEB\x99\x82", + "\x8C\x89" => "\xEB\x99\x83", + "\x8C\x8A" => "\xEB\x99\x84", + "\x8C\x8B" => "\xEB\x99\x85", + "\x8C\x8C" => "\xEB\x99\x86", + "\x8C\x8D" => "\xEB\x99\x87", + "\x8C\x8E" => "\xEB\x99\x89", + "\x8C\x8F" => "\xEB\x99\x8A", + "\x8C\x90" => "\xEB\x99\x8B", + "\x8C\x91" => "\xEB\x99\x8C", + "\x8C\x92" => "\xEB\x99\x8D", + "\x8C\x93" => "\xEB\x99\x8E", + "\x8C\x94" => "\xEB\x99\x8F", + "\x8C\x95" => "\xEB\x99\x90", + "\x8C\x96" => "\xEB\x99\x91", + "\x8C\x97" => "\xEB\x99\x92", + "\x8C\x98" => "\xEB\x99\x93", + "\x8C\x99" => "\xEB\x99\x94", + "\x8C\x9A" => "\xEB\x99\x95", + "\x8C\x9B" => "\xEB\x99\x96", + "\x8C\x9C" => "\xEB\x99\x97", + "\x8C\x9D" => "\xEB\x99\x98", + "\x8C\x9E" => "\xEB\x99\x99", + "\x8C\x9F" => "\xEB\x99\x9A", + "\x8C\xA0" => "\xEB\x99\x9B", + "\x8C\xA1" => "\xEB\x99\x9C", + "\x8C\xA2" => "\xEB\x99\x9D", + "\x8C\xA3" => "\xEB\x99\x9E", + "\x8C\xA4" => "\xEB\x99\x9F", + "\x8C\xA5" => "\xEB\x99\xA0", + "\x8C\xA6" => "\xEB\x99\xA1", + "\x8C\xA7" => "\xEB\x99\xA2", + "\x8C\xA8" => "\xEB\x99\xA3", + "\x8C\xA9" => "\xEB\x99\xA5", + "\x8C\xAA" => "\xEB\x99\xA6", + "\x8C\xAB" => "\xEB\x99\xA7", + "\x8C\xAC" => "\xEB\x99\xA9", + "\x8C\xAD" => "\xEB\x99\xAA", + "\x8C\xAE" => "\xEB\x99\xAB", + "\x8C\xAF" => "\xEB\x99\xAC", + "\x8C\xB0" => "\xEB\x99\xAD", + "\x8C\xB1" => "\xEB\x99\xAE", + "\x8C\xB2" => "\xEB\x99\xAF", + "\x8C\xB3" => "\xEB\x99\xB0", + "\x8C\xB4" => "\xEB\x99\xB1", + "\x8C\xB5" => "\xEB\x99\xB2", + "\x8C\xB6" => "\xEB\x99\xB3", + "\x8C\xB7" => "\xEB\x99\xB4", + "\x8C\xB8" => "\xEB\x99\xB5", + "\x8C\xB9" => "\xEB\x99\xB6", + "\x8C\xBA" => "\xEB\x99\xB7", + "\x8C\xBB" => "\xEB\x99\xB8", + "\x8C\xBC" => "\xEB\x99\xB9", + "\x8C\xBD" => "\xEB\x99\xBA", + "\x8C\xBE" => "\xEB\x99\xBB", + "\x8C\xBF" => "\xEB\x99\xBC", + "\x8C\xC0" => "\xEB\x99\xBD", + "\x8C\xC1" => "\xEB\x99\xBE", + "\x8C\xC2" => "\xEB\x99\xBF", + "\x8C\xC3" => "\xEB\x9A\x80", + "\x8C\xC4" => "\xEB\x9A\x81", + "\x8C\xC5" => "\xEB\x9A\x82", + "\x8C\xC6" => "\xEB\x9A\x83", + "\x8C\xC7" => "\xEB\x9A\x84", + "\x8C\xC8" => "\xEB\x9A\x85", + "\x8C\xC9" => "\xEB\x9A\x86", + "\x8C\xCA" => "\xEB\x9A\x87", + "\x8C\xCB" => "\xEB\x9A\x88", + "\x8C\xCC" => "\xEB\x9A\x89", + "\x8C\xCD" => "\xEB\x9A\x8A", + "\x8C\xCE" => "\xEB\x9A\x8B", + "\x8C\xCF" => "\xEB\x9A\x8C", + "\x8C\xD0" => "\xEB\x9A\x8D", + "\x8C\xD1" => "\xEB\x9A\x8E", + "\x8C\xD2" => "\xEB\x9A\x8F", + "\x8C\xD3" => "\xEB\x9A\x90", + "\x8C\xD4" => "\xEB\x9A\x91", + "\x8C\xD5" => "\xEB\x9A\x92", + "\x8C\xD6" => "\xEB\x9A\x93", + "\x8C\xD7" => "\xEB\x9A\x94", + "\x8C\xD8" => "\xEB\x9A\x95", + "\x8C\xD9" => "\xEB\x9A\x96", + "\x8C\xDA" => "\xEB\x9A\x97", + "\x8C\xDB" => "\xEB\x9A\x98", + "\x8C\xDC" => "\xEB\x9A\x99", + "\x8C\xDD" => "\xEB\x9A\x9A", + "\x8C\xDE" => "\xEB\x9A\x9B", + "\x8C\xDF" => "\xEB\x9A\x9E", + "\x8C\xE0" => "\xEB\x9A\x9F", + "\x8C\xE1" => "\xEB\x9A\xA1", + "\x8C\xE2" => "\xEB\x9A\xA2", + "\x8C\xE3" => "\xEB\x9A\xA3", + "\x8C\xE4" => "\xEB\x9A\xA5", + "\x8C\xE5" => "\xEB\x9A\xA6", + "\x8C\xE6" => "\xEB\x9A\xA7", + "\x8C\xE7" => "\xEB\x9A\xA8", + "\x8C\xE8" => "\xEB\x9A\xA9", + "\x8C\xE9" => "\xEB\x9A\xAA", + "\x8C\xEA" => "\xEB\x9A\xAD", + "\x8C\xEB" => "\xEB\x9A\xAE", + "\x8C\xEC" => "\xEB\x9A\xAF", + "\x8C\xED" => "\xEB\x9A\xB0", + "\x8C\xEE" => "\xEB\x9A\xB2", + "\x8C\xEF" => "\xEB\x9A\xB3", + "\x8C\xF0" => "\xEB\x9A\xB4", + "\x8C\xF1" => "\xEB\x9A\xB5", + "\x8C\xF2" => "\xEB\x9A\xB6", + "\x8C\xF3" => "\xEB\x9A\xB7", + "\x8C\xF4" => "\xEB\x9A\xB8", + "\x8C\xF5" => "\xEB\x9A\xB9", + "\x8C\xF6" => "\xEB\x9A\xBA", + "\x8C\xF7" => "\xEB\x9A\xBB", + "\x8C\xF8" => "\xEB\x9A\xBC", + "\x8C\xF9" => "\xEB\x9A\xBD", + "\x8C\xFA" => "\xEB\x9A\xBE", + "\x8C\xFB" => "\xEB\x9A\xBF", + "\x8C\xFC" => "\xEB\x9B\x80", + "\x8C\xFD" => "\xEB\x9B\x81", + "\x8C\xFE" => "\xEB\x9B\x82", + "\x8D\x41" => "\xEB\x9B\x83", + "\x8D\x42" => "\xEB\x9B\x84", + "\x8D\x43" => "\xEB\x9B\x85", + "\x8D\x44" => "\xEB\x9B\x86", + "\x8D\x45" => "\xEB\x9B\x87", + "\x8D\x46" => "\xEB\x9B\x88", + "\x8D\x47" => "\xEB\x9B\x89", + "\x8D\x48" => "\xEB\x9B\x8A", + "\x8D\x49" => "\xEB\x9B\x8B", + "\x8D\x4A" => "\xEB\x9B\x8C", + "\x8D\x4B" => "\xEB\x9B\x8D", + "\x8D\x4C" => "\xEB\x9B\x8E", + "\x8D\x4D" => "\xEB\x9B\x8F", + "\x8D\x4E" => "\xEB\x9B\x90", + "\x8D\x4F" => "\xEB\x9B\x91", + "\x8D\x50" => "\xEB\x9B\x92", + "\x8D\x51" => "\xEB\x9B\x93", + "\x8D\x52" => "\xEB\x9B\x95", + "\x8D\x53" => "\xEB\x9B\x96", + "\x8D\x54" => "\xEB\x9B\x97", + "\x8D\x55" => "\xEB\x9B\x98", + "\x8D\x56" => "\xEB\x9B\x99", + "\x8D\x57" => "\xEB\x9B\x9A", + "\x8D\x58" => "\xEB\x9B\x9B", + "\x8D\x59" => "\xEB\x9B\x9C", + "\x8D\x5A" => "\xEB\x9B\x9D", + "\x8D\x61" => "\xEB\x9B\x9E", + "\x8D\x62" => "\xEB\x9B\x9F", + "\x8D\x63" => "\xEB\x9B\xA0", + "\x8D\x64" => "\xEB\x9B\xA1", + "\x8D\x65" => "\xEB\x9B\xA2", + "\x8D\x66" => "\xEB\x9B\xA3", + "\x8D\x67" => "\xEB\x9B\xA4", + "\x8D\x68" => "\xEB\x9B\xA5", + "\x8D\x69" => "\xEB\x9B\xA6", + "\x8D\x6A" => "\xEB\x9B\xA7", + "\x8D\x6B" => "\xEB\x9B\xA8", + "\x8D\x6C" => "\xEB\x9B\xA9", + "\x8D\x6D" => "\xEB\x9B\xAA", + "\x8D\x6E" => "\xEB\x9B\xAB", + "\x8D\x6F" => "\xEB\x9B\xAC", + "\x8D\x70" => "\xEB\x9B\xAD", + "\x8D\x71" => "\xEB\x9B\xAE", + "\x8D\x72" => "\xEB\x9B\xAF", + "\x8D\x73" => "\xEB\x9B\xB1", + "\x8D\x74" => "\xEB\x9B\xB2", + "\x8D\x75" => "\xEB\x9B\xB3", + "\x8D\x76" => "\xEB\x9B\xB5", + "\x8D\x77" => "\xEB\x9B\xB6", + "\x8D\x78" => "\xEB\x9B\xB7", + "\x8D\x79" => "\xEB\x9B\xB9", + "\x8D\x7A" => "\xEB\x9B\xBA", + "\x8D\x81" => "\xEB\x9B\xBB", + "\x8D\x82" => "\xEB\x9B\xBC", + "\x8D\x83" => "\xEB\x9B\xBD", + "\x8D\x84" => "\xEB\x9B\xBE", + "\x8D\x85" => "\xEB\x9B\xBF", + "\x8D\x86" => "\xEB\x9C\x82", + "\x8D\x87" => "\xEB\x9C\x83", + "\x8D\x88" => "\xEB\x9C\x84", + "\x8D\x89" => "\xEB\x9C\x86", + "\x8D\x8A" => "\xEB\x9C\x87", + "\x8D\x8B" => "\xEB\x9C\x88", + "\x8D\x8C" => "\xEB\x9C\x89", + "\x8D\x8D" => "\xEB\x9C\x8A", + "\x8D\x8E" => "\xEB\x9C\x8B", + "\x8D\x8F" => "\xEB\x9C\x8C", + "\x8D\x90" => "\xEB\x9C\x8D", + "\x8D\x91" => "\xEB\x9C\x8E", + "\x8D\x92" => "\xEB\x9C\x8F", + "\x8D\x93" => "\xEB\x9C\x90", + "\x8D\x94" => "\xEB\x9C\x91", + "\x8D\x95" => "\xEB\x9C\x92", + "\x8D\x96" => "\xEB\x9C\x93", + "\x8D\x97" => "\xEB\x9C\x94", + "\x8D\x98" => "\xEB\x9C\x95", + "\x8D\x99" => "\xEB\x9C\x96", + "\x8D\x9A" => "\xEB\x9C\x97", + "\x8D\x9B" => "\xEB\x9C\x98", + "\x8D\x9C" => "\xEB\x9C\x99", + "\x8D\x9D" => "\xEB\x9C\x9A", + "\x8D\x9E" => "\xEB\x9C\x9B", + "\x8D\x9F" => "\xEB\x9C\x9C", + "\x8D\xA0" => "\xEB\x9C\x9D", + "\x8D\xA1" => "\xEB\x9C\x9E", + "\x8D\xA2" => "\xEB\x9C\x9F", + "\x8D\xA3" => "\xEB\x9C\xA0", + "\x8D\xA4" => "\xEB\x9C\xA1", + "\x8D\xA5" => "\xEB\x9C\xA2", + "\x8D\xA6" => "\xEB\x9C\xA3", + "\x8D\xA7" => "\xEB\x9C\xA4", + "\x8D\xA8" => "\xEB\x9C\xA5", + "\x8D\xA9" => "\xEB\x9C\xA6", + "\x8D\xAA" => "\xEB\x9C\xA7", + "\x8D\xAB" => "\xEB\x9C\xAA", + "\x8D\xAC" => "\xEB\x9C\xAB", + "\x8D\xAD" => "\xEB\x9C\xAD", + "\x8D\xAE" => "\xEB\x9C\xAE", + "\x8D\xAF" => "\xEB\x9C\xB1", + "\x8D\xB0" => "\xEB\x9C\xB2", + "\x8D\xB1" => "\xEB\x9C\xB3", + "\x8D\xB2" => "\xEB\x9C\xB4", + "\x8D\xB3" => "\xEB\x9C\xB5", + "\x8D\xB4" => "\xEB\x9C\xB6", + "\x8D\xB5" => "\xEB\x9C\xB7", + "\x8D\xB6" => "\xEB\x9C\xBA", + "\x8D\xB7" => "\xEB\x9C\xBC", + "\x8D\xB8" => "\xEB\x9C\xBD", + "\x8D\xB9" => "\xEB\x9C\xBE", + "\x8D\xBA" => "\xEB\x9C\xBF", + "\x8D\xBB" => "\xEB\x9D\x80", + "\x8D\xBC" => "\xEB\x9D\x81", + "\x8D\xBD" => "\xEB\x9D\x82", + "\x8D\xBE" => "\xEB\x9D\x83", + "\x8D\xBF" => "\xEB\x9D\x85", + "\x8D\xC0" => "\xEB\x9D\x86", + "\x8D\xC1" => "\xEB\x9D\x87", + "\x8D\xC2" => "\xEB\x9D\x89", + "\x8D\xC3" => "\xEB\x9D\x8A", + "\x8D\xC4" => "\xEB\x9D\x8B", + "\x8D\xC5" => "\xEB\x9D\x8D", + "\x8D\xC6" => "\xEB\x9D\x8E", + "\x8D\xC7" => "\xEB\x9D\x8F", + "\x8D\xC8" => "\xEB\x9D\x90", + "\x8D\xC9" => "\xEB\x9D\x91", + "\x8D\xCA" => "\xEB\x9D\x92", + "\x8D\xCB" => "\xEB\x9D\x93", + "\x8D\xCC" => "\xEB\x9D\x96", + "\x8D\xCD" => "\xEB\x9D\x97", + "\x8D\xCE" => "\xEB\x9D\x98", + "\x8D\xCF" => "\xEB\x9D\x99", + "\x8D\xD0" => "\xEB\x9D\x9A", + "\x8D\xD1" => "\xEB\x9D\x9B", + "\x8D\xD2" => "\xEB\x9D\x9C", + "\x8D\xD3" => "\xEB\x9D\x9D", + "\x8D\xD4" => "\xEB\x9D\x9E", + "\x8D\xD5" => "\xEB\x9D\x9F", + "\x8D\xD6" => "\xEB\x9D\xA1", + "\x8D\xD7" => "\xEB\x9D\xA2", + "\x8D\xD8" => "\xEB\x9D\xA3", + "\x8D\xD9" => "\xEB\x9D\xA5", + "\x8D\xDA" => "\xEB\x9D\xA6", + "\x8D\xDB" => "\xEB\x9D\xA7", + "\x8D\xDC" => "\xEB\x9D\xA9", + "\x8D\xDD" => "\xEB\x9D\xAA", + "\x8D\xDE" => "\xEB\x9D\xAB", + "\x8D\xDF" => "\xEB\x9D\xAC", + "\x8D\xE0" => "\xEB\x9D\xAD", + "\x8D\xE1" => "\xEB\x9D\xAE", + "\x8D\xE2" => "\xEB\x9D\xAF", + "\x8D\xE3" => "\xEB\x9D\xB2", + "\x8D\xE4" => "\xEB\x9D\xB4", + "\x8D\xE5" => "\xEB\x9D\xB6", + "\x8D\xE6" => "\xEB\x9D\xB7", + "\x8D\xE7" => "\xEB\x9D\xB8", + "\x8D\xE8" => "\xEB\x9D\xB9", + "\x8D\xE9" => "\xEB\x9D\xBA", + "\x8D\xEA" => "\xEB\x9D\xBB", + "\x8D\xEB" => "\xEB\x9D\xBE", + "\x8D\xEC" => "\xEB\x9D\xBF", + "\x8D\xED" => "\xEB\x9E\x81", + "\x8D\xEE" => "\xEB\x9E\x82", + "\x8D\xEF" => "\xEB\x9E\x83", + "\x8D\xF0" => "\xEB\x9E\x85", + "\x8D\xF1" => "\xEB\x9E\x86", + "\x8D\xF2" => "\xEB\x9E\x87", + "\x8D\xF3" => "\xEB\x9E\x88", + "\x8D\xF4" => "\xEB\x9E\x89", + "\x8D\xF5" => "\xEB\x9E\x8A", + "\x8D\xF6" => "\xEB\x9E\x8B", + "\x8D\xF7" => "\xEB\x9E\x8E", + "\x8D\xF8" => "\xEB\x9E\x93", + "\x8D\xF9" => "\xEB\x9E\x94", + "\x8D\xFA" => "\xEB\x9E\x95", + "\x8D\xFB" => "\xEB\x9E\x9A", + "\x8D\xFC" => "\xEB\x9E\x9B", + "\x8D\xFD" => "\xEB\x9E\x9D", + "\x8D\xFE" => "\xEB\x9E\x9E", + "\x8E\x41" => "\xEB\x9E\x9F", + "\x8E\x42" => "\xEB\x9E\xA1", + "\x8E\x43" => "\xEB\x9E\xA2", + "\x8E\x44" => "\xEB\x9E\xA3", + "\x8E\x45" => "\xEB\x9E\xA4", + "\x8E\x46" => "\xEB\x9E\xA5", + "\x8E\x47" => "\xEB\x9E\xA6", + "\x8E\x48" => "\xEB\x9E\xA7", + "\x8E\x49" => "\xEB\x9E\xAA", + "\x8E\x4A" => "\xEB\x9E\xAE", + "\x8E\x4B" => "\xEB\x9E\xAF", + "\x8E\x4C" => "\xEB\x9E\xB0", + "\x8E\x4D" => "\xEB\x9E\xB1", + "\x8E\x4E" => "\xEB\x9E\xB2", + "\x8E\x4F" => "\xEB\x9E\xB3", + "\x8E\x50" => "\xEB\x9E\xB6", + "\x8E\x51" => "\xEB\x9E\xB7", + "\x8E\x52" => "\xEB\x9E\xB9", + "\x8E\x53" => "\xEB\x9E\xBA", + "\x8E\x54" => "\xEB\x9E\xBB", + "\x8E\x55" => "\xEB\x9E\xBC", + "\x8E\x56" => "\xEB\x9E\xBD", + "\x8E\x57" => "\xEB\x9E\xBE", + "\x8E\x58" => "\xEB\x9E\xBF", + "\x8E\x59" => "\xEB\x9F\x80", + "\x8E\x5A" => "\xEB\x9F\x81", + "\x8E\x61" => "\xEB\x9F\x82", + "\x8E\x62" => "\xEB\x9F\x83", + "\x8E\x63" => "\xEB\x9F\x84", + "\x8E\x64" => "\xEB\x9F\x85", + "\x8E\x65" => "\xEB\x9F\x86", + "\x8E\x66" => "\xEB\x9F\x88", + "\x8E\x67" => "\xEB\x9F\x8A", + "\x8E\x68" => "\xEB\x9F\x8B", + "\x8E\x69" => "\xEB\x9F\x8C", + "\x8E\x6A" => "\xEB\x9F\x8D", + "\x8E\x6B" => "\xEB\x9F\x8E", + "\x8E\x6C" => "\xEB\x9F\x8F", + "\x8E\x6D" => "\xEB\x9F\x90", + "\x8E\x6E" => "\xEB\x9F\x91", + "\x8E\x6F" => "\xEB\x9F\x92", + "\x8E\x70" => "\xEB\x9F\x93", + "\x8E\x71" => "\xEB\x9F\x94", + "\x8E\x72" => "\xEB\x9F\x95", + "\x8E\x73" => "\xEB\x9F\x96", + "\x8E\x74" => "\xEB\x9F\x97", + "\x8E\x75" => "\xEB\x9F\x98", + "\x8E\x76" => "\xEB\x9F\x99", + "\x8E\x77" => "\xEB\x9F\x9A", + "\x8E\x78" => "\xEB\x9F\x9B", + "\x8E\x79" => "\xEB\x9F\x9C", + "\x8E\x7A" => "\xEB\x9F\x9D", + "\x8E\x81" => "\xEB\x9F\x9E", + "\x8E\x82" => "\xEB\x9F\x9F", + "\x8E\x83" => "\xEB\x9F\xA0", + "\x8E\x84" => "\xEB\x9F\xA1", + "\x8E\x85" => "\xEB\x9F\xA2", + "\x8E\x86" => "\xEB\x9F\xA3", + "\x8E\x87" => "\xEB\x9F\xA4", + "\x8E\x88" => "\xEB\x9F\xA5", + "\x8E\x89" => "\xEB\x9F\xA6", + "\x8E\x8A" => "\xEB\x9F\xA7", + "\x8E\x8B" => "\xEB\x9F\xA8", + "\x8E\x8C" => "\xEB\x9F\xA9", + "\x8E\x8D" => "\xEB\x9F\xAA", + "\x8E\x8E" => "\xEB\x9F\xAB", + "\x8E\x8F" => "\xEB\x9F\xAE", + "\x8E\x90" => "\xEB\x9F\xAF", + "\x8E\x91" => "\xEB\x9F\xB1", + "\x8E\x92" => "\xEB\x9F\xB2", + "\x8E\x93" => "\xEB\x9F\xB3", + "\x8E\x94" => "\xEB\x9F\xB5", + "\x8E\x95" => "\xEB\x9F\xB6", + "\x8E\x96" => "\xEB\x9F\xB7", + "\x8E\x97" => "\xEB\x9F\xB8", + "\x8E\x98" => "\xEB\x9F\xB9", + "\x8E\x99" => "\xEB\x9F\xBA", + "\x8E\x9A" => "\xEB\x9F\xBB", + "\x8E\x9B" => "\xEB\x9F\xBE", + "\x8E\x9C" => "\xEB\xA0\x82", + "\x8E\x9D" => "\xEB\xA0\x83", + "\x8E\x9E" => "\xEB\xA0\x84", + "\x8E\x9F" => "\xEB\xA0\x85", + "\x8E\xA0" => "\xEB\xA0\x86", + "\x8E\xA1" => "\xEB\xA0\x8A", + "\x8E\xA2" => "\xEB\xA0\x8B", + "\x8E\xA3" => "\xEB\xA0\x8D", + "\x8E\xA4" => "\xEB\xA0\x8E", + "\x8E\xA5" => "\xEB\xA0\x8F", + "\x8E\xA6" => "\xEB\xA0\x91", + "\x8E\xA7" => "\xEB\xA0\x92", + "\x8E\xA8" => "\xEB\xA0\x93", + "\x8E\xA9" => "\xEB\xA0\x94", + "\x8E\xAA" => "\xEB\xA0\x95", + "\x8E\xAB" => "\xEB\xA0\x96", + "\x8E\xAC" => "\xEB\xA0\x97", + "\x8E\xAD" => "\xEB\xA0\x9A", + "\x8E\xAE" => "\xEB\xA0\x9C", + "\x8E\xAF" => "\xEB\xA0\x9E", + "\x8E\xB0" => "\xEB\xA0\x9F", + "\x8E\xB1" => "\xEB\xA0\xA0", + "\x8E\xB2" => "\xEB\xA0\xA1", + "\x8E\xB3" => "\xEB\xA0\xA2", + "\x8E\xB4" => "\xEB\xA0\xA3", + "\x8E\xB5" => "\xEB\xA0\xA6", + "\x8E\xB6" => "\xEB\xA0\xA7", + "\x8E\xB7" => "\xEB\xA0\xA9", + "\x8E\xB8" => "\xEB\xA0\xAA", + "\x8E\xB9" => "\xEB\xA0\xAB", + "\x8E\xBA" => "\xEB\xA0\xAD", + "\x8E\xBB" => "\xEB\xA0\xAE", + "\x8E\xBC" => "\xEB\xA0\xAF", + "\x8E\xBD" => "\xEB\xA0\xB0", + "\x8E\xBE" => "\xEB\xA0\xB1", + "\x8E\xBF" => "\xEB\xA0\xB2", + "\x8E\xC0" => "\xEB\xA0\xB3", + "\x8E\xC1" => "\xEB\xA0\xB6", + "\x8E\xC2" => "\xEB\xA0\xBA", + "\x8E\xC3" => "\xEB\xA0\xBB", + "\x8E\xC4" => "\xEB\xA0\xBC", + "\x8E\xC5" => "\xEB\xA0\xBD", + "\x8E\xC6" => "\xEB\xA0\xBE", + "\x8E\xC7" => "\xEB\xA0\xBF", + "\x8E\xC8" => "\xEB\xA1\x81", + "\x8E\xC9" => "\xEB\xA1\x82", + "\x8E\xCA" => "\xEB\xA1\x83", + "\x8E\xCB" => "\xEB\xA1\x85", + "\x8E\xCC" => "\xEB\xA1\x86", + "\x8E\xCD" => "\xEB\xA1\x87", + "\x8E\xCE" => "\xEB\xA1\x88", + "\x8E\xCF" => "\xEB\xA1\x89", + "\x8E\xD0" => "\xEB\xA1\x8A", + "\x8E\xD1" => "\xEB\xA1\x8B", + "\x8E\xD2" => "\xEB\xA1\x8C", + "\x8E\xD3" => "\xEB\xA1\x8D", + "\x8E\xD4" => "\xEB\xA1\x8E", + "\x8E\xD5" => "\xEB\xA1\x8F", + "\x8E\xD6" => "\xEB\xA1\x90", + "\x8E\xD7" => "\xEB\xA1\x92", + "\x8E\xD8" => "\xEB\xA1\x94", + "\x8E\xD9" => "\xEB\xA1\x95", + "\x8E\xDA" => "\xEB\xA1\x96", + "\x8E\xDB" => "\xEB\xA1\x97", + "\x8E\xDC" => "\xEB\xA1\x98", + "\x8E\xDD" => "\xEB\xA1\x99", + "\x8E\xDE" => "\xEB\xA1\x9A", + "\x8E\xDF" => "\xEB\xA1\x9B", + "\x8E\xE0" => "\xEB\xA1\x9E", + "\x8E\xE1" => "\xEB\xA1\x9F", + "\x8E\xE2" => "\xEB\xA1\xA1", + "\x8E\xE3" => "\xEB\xA1\xA2", + "\x8E\xE4" => "\xEB\xA1\xA3", + "\x8E\xE5" => "\xEB\xA1\xA5", + "\x8E\xE6" => "\xEB\xA1\xA6", + "\x8E\xE7" => "\xEB\xA1\xA7", + "\x8E\xE8" => "\xEB\xA1\xA8", + "\x8E\xE9" => "\xEB\xA1\xA9", + "\x8E\xEA" => "\xEB\xA1\xAA", + "\x8E\xEB" => "\xEB\xA1\xAB", + "\x8E\xEC" => "\xEB\xA1\xAE", + "\x8E\xED" => "\xEB\xA1\xB0", + "\x8E\xEE" => "\xEB\xA1\xB2", + "\x8E\xEF" => "\xEB\xA1\xB3", + "\x8E\xF0" => "\xEB\xA1\xB4", + "\x8E\xF1" => "\xEB\xA1\xB5", + "\x8E\xF2" => "\xEB\xA1\xB6", + "\x8E\xF3" => "\xEB\xA1\xB7", + "\x8E\xF4" => "\xEB\xA1\xB9", + "\x8E\xF5" => "\xEB\xA1\xBA", + "\x8E\xF6" => "\xEB\xA1\xBB", + "\x8E\xF7" => "\xEB\xA1\xBD", + "\x8E\xF8" => "\xEB\xA1\xBE", + "\x8E\xF9" => "\xEB\xA1\xBF", + "\x8E\xFA" => "\xEB\xA2\x80", + "\x8E\xFB" => "\xEB\xA2\x81", + "\x8E\xFC" => "\xEB\xA2\x82", + "\x8E\xFD" => "\xEB\xA2\x83", + "\x8E\xFE" => "\xEB\xA2\x84", + "\x8F\x41" => "\xEB\xA2\x85", + "\x8F\x42" => "\xEB\xA2\x86", + "\x8F\x43" => "\xEB\xA2\x87", + "\x8F\x44" => "\xEB\xA2\x88", + "\x8F\x45" => "\xEB\xA2\x89", + "\x8F\x46" => "\xEB\xA2\x8A", + "\x8F\x47" => "\xEB\xA2\x8B", + "\x8F\x48" => "\xEB\xA2\x8C", + "\x8F\x49" => "\xEB\xA2\x8E", + "\x8F\x4A" => "\xEB\xA2\x8F", + "\x8F\x4B" => "\xEB\xA2\x90", + "\x8F\x4C" => "\xEB\xA2\x91", + "\x8F\x4D" => "\xEB\xA2\x92", + "\x8F\x4E" => "\xEB\xA2\x93", + "\x8F\x4F" => "\xEB\xA2\x94", + "\x8F\x50" => "\xEB\xA2\x95", + "\x8F\x51" => "\xEB\xA2\x96", + "\x8F\x52" => "\xEB\xA2\x97", + "\x8F\x53" => "\xEB\xA2\x98", + "\x8F\x54" => "\xEB\xA2\x99", + "\x8F\x55" => "\xEB\xA2\x9A", + "\x8F\x56" => "\xEB\xA2\x9B", + "\x8F\x57" => "\xEB\xA2\x9C", + "\x8F\x58" => "\xEB\xA2\x9D", + "\x8F\x59" => "\xEB\xA2\x9E", + "\x8F\x5A" => "\xEB\xA2\x9F", + "\x8F\x61" => "\xEB\xA2\xA0", + "\x8F\x62" => "\xEB\xA2\xA1", + "\x8F\x63" => "\xEB\xA2\xA2", + "\x8F\x64" => "\xEB\xA2\xA3", + "\x8F\x65" => "\xEB\xA2\xA4", + "\x8F\x66" => "\xEB\xA2\xA5", + "\x8F\x67" => "\xEB\xA2\xA6", + "\x8F\x68" => "\xEB\xA2\xA7", + "\x8F\x69" => "\xEB\xA2\xA9", + "\x8F\x6A" => "\xEB\xA2\xAA", + "\x8F\x6B" => "\xEB\xA2\xAB", + "\x8F\x6C" => "\xEB\xA2\xAC", + "\x8F\x6D" => "\xEB\xA2\xAD", + "\x8F\x6E" => "\xEB\xA2\xAE", + "\x8F\x6F" => "\xEB\xA2\xAF", + "\x8F\x70" => "\xEB\xA2\xB1", + "\x8F\x71" => "\xEB\xA2\xB2", + "\x8F\x72" => "\xEB\xA2\xB3", + "\x8F\x73" => "\xEB\xA2\xB5", + "\x8F\x74" => "\xEB\xA2\xB6", + "\x8F\x75" => "\xEB\xA2\xB7", + "\x8F\x76" => "\xEB\xA2\xB9", + "\x8F\x77" => "\xEB\xA2\xBA", + "\x8F\x78" => "\xEB\xA2\xBB", + "\x8F\x79" => "\xEB\xA2\xBC", + "\x8F\x7A" => "\xEB\xA2\xBD", + "\x8F\x81" => "\xEB\xA2\xBE", + "\x8F\x82" => "\xEB\xA2\xBF", + "\x8F\x83" => "\xEB\xA3\x82", + "\x8F\x84" => "\xEB\xA3\x84", + "\x8F\x85" => "\xEB\xA3\x86", + "\x8F\x86" => "\xEB\xA3\x87", + "\x8F\x87" => "\xEB\xA3\x88", + "\x8F\x88" => "\xEB\xA3\x89", + "\x8F\x89" => "\xEB\xA3\x8A", + "\x8F\x8A" => "\xEB\xA3\x8B", + "\x8F\x8B" => "\xEB\xA3\x8D", + "\x8F\x8C" => "\xEB\xA3\x8E", + "\x8F\x8D" => "\xEB\xA3\x8F", + "\x8F\x8E" => "\xEB\xA3\x91", + "\x8F\x8F" => "\xEB\xA3\x92", + "\x8F\x90" => "\xEB\xA3\x93", + "\x8F\x91" => "\xEB\xA3\x95", + "\x8F\x92" => "\xEB\xA3\x96", + "\x8F\x93" => "\xEB\xA3\x97", + "\x8F\x94" => "\xEB\xA3\x98", + "\x8F\x95" => "\xEB\xA3\x99", + "\x8F\x96" => "\xEB\xA3\x9A", + "\x8F\x97" => "\xEB\xA3\x9B", + "\x8F\x98" => "\xEB\xA3\x9C", + "\x8F\x99" => "\xEB\xA3\x9E", + "\x8F\x9A" => "\xEB\xA3\xA0", + "\x8F\x9B" => "\xEB\xA3\xA2", + "\x8F\x9C" => "\xEB\xA3\xA3", + "\x8F\x9D" => "\xEB\xA3\xA4", + "\x8F\x9E" => "\xEB\xA3\xA5", + "\x8F\x9F" => "\xEB\xA3\xA6", + "\x8F\xA0" => "\xEB\xA3\xA7", + "\x8F\xA1" => "\xEB\xA3\xAA", + "\x8F\xA2" => "\xEB\xA3\xAB", + "\x8F\xA3" => "\xEB\xA3\xAD", + "\x8F\xA4" => "\xEB\xA3\xAE", + "\x8F\xA5" => "\xEB\xA3\xAF", + "\x8F\xA6" => "\xEB\xA3\xB1", + "\x8F\xA7" => "\xEB\xA3\xB2", + "\x8F\xA8" => "\xEB\xA3\xB3", + "\x8F\xA9" => "\xEB\xA3\xB4", + "\x8F\xAA" => "\xEB\xA3\xB5", + "\x8F\xAB" => "\xEB\xA3\xB6", + "\x8F\xAC" => "\xEB\xA3\xB7", + "\x8F\xAD" => "\xEB\xA3\xBA", + "\x8F\xAE" => "\xEB\xA3\xBC", + "\x8F\xAF" => "\xEB\xA3\xBE", + "\x8F\xB0" => "\xEB\xA3\xBF", + "\x8F\xB1" => "\xEB\xA4\x80", + "\x8F\xB2" => "\xEB\xA4\x81", + "\x8F\xB3" => "\xEB\xA4\x82", + "\x8F\xB4" => "\xEB\xA4\x83", + "\x8F\xB5" => "\xEB\xA4\x85", + "\x8F\xB6" => "\xEB\xA4\x86", + "\x8F\xB7" => "\xEB\xA4\x87", + "\x8F\xB8" => "\xEB\xA4\x88", + "\x8F\xB9" => "\xEB\xA4\x89", + "\x8F\xBA" => "\xEB\xA4\x8A", + "\x8F\xBB" => "\xEB\xA4\x8B", + "\x8F\xBC" => "\xEB\xA4\x8C", + "\x8F\xBD" => "\xEB\xA4\x8D", + "\x8F\xBE" => "\xEB\xA4\x8E", + "\x8F\xBF" => "\xEB\xA4\x8F", + "\x8F\xC0" => "\xEB\xA4\x90", + "\x8F\xC1" => "\xEB\xA4\x91", + "\x8F\xC2" => "\xEB\xA4\x92", + "\x8F\xC3" => "\xEB\xA4\x93", + "\x8F\xC4" => "\xEB\xA4\x94", + "\x8F\xC5" => "\xEB\xA4\x95", + "\x8F\xC6" => "\xEB\xA4\x96", + "\x8F\xC7" => "\xEB\xA4\x97", + "\x8F\xC8" => "\xEB\xA4\x99", + "\x8F\xC9" => "\xEB\xA4\x9A", + "\x8F\xCA" => "\xEB\xA4\x9B", + "\x8F\xCB" => "\xEB\xA4\x9C", + "\x8F\xCC" => "\xEB\xA4\x9D", + "\x8F\xCD" => "\xEB\xA4\x9E", + "\x8F\xCE" => "\xEB\xA4\x9F", + "\x8F\xCF" => "\xEB\xA4\xA1", + "\x8F\xD0" => "\xEB\xA4\xA2", + "\x8F\xD1" => "\xEB\xA4\xA3", + "\x8F\xD2" => "\xEB\xA4\xA4", + "\x8F\xD3" => "\xEB\xA4\xA5", + "\x8F\xD4" => "\xEB\xA4\xA6", + "\x8F\xD5" => "\xEB\xA4\xA7", + "\x8F\xD6" => "\xEB\xA4\xA8", + "\x8F\xD7" => "\xEB\xA4\xA9", + "\x8F\xD8" => "\xEB\xA4\xAA", + "\x8F\xD9" => "\xEB\xA4\xAB", + "\x8F\xDA" => "\xEB\xA4\xAC", + "\x8F\xDB" => "\xEB\xA4\xAD", + "\x8F\xDC" => "\xEB\xA4\xAE", + "\x8F\xDD" => "\xEB\xA4\xAF", + "\x8F\xDE" => "\xEB\xA4\xB0", + "\x8F\xDF" => "\xEB\xA4\xB1", + "\x8F\xE0" => "\xEB\xA4\xB2", + "\x8F\xE1" => "\xEB\xA4\xB3", + "\x8F\xE2" => "\xEB\xA4\xB4", + "\x8F\xE3" => "\xEB\xA4\xB5", + "\x8F\xE4" => "\xEB\xA4\xB6", + "\x8F\xE5" => "\xEB\xA4\xB7", + "\x8F\xE6" => "\xEB\xA4\xB8", + "\x8F\xE7" => "\xEB\xA4\xB9", + "\x8F\xE8" => "\xEB\xA4\xBA", + "\x8F\xE9" => "\xEB\xA4\xBB", + "\x8F\xEA" => "\xEB\xA4\xBE", + "\x8F\xEB" => "\xEB\xA4\xBF", + "\x8F\xEC" => "\xEB\xA5\x81", + "\x8F\xED" => "\xEB\xA5\x82", + "\x8F\xEE" => "\xEB\xA5\x83", + "\x8F\xEF" => "\xEB\xA5\x85", + "\x8F\xF0" => "\xEB\xA5\x86", + "\x8F\xF1" => "\xEB\xA5\x87", + "\x8F\xF2" => "\xEB\xA5\x88", + "\x8F\xF3" => "\xEB\xA5\x89", + "\x8F\xF4" => "\xEB\xA5\x8A", + "\x8F\xF5" => "\xEB\xA5\x8B", + "\x8F\xF6" => "\xEB\xA5\x8D", + "\x8F\xF7" => "\xEB\xA5\x8E", + "\x8F\xF8" => "\xEB\xA5\x90", + "\x8F\xF9" => "\xEB\xA5\x92", + "\x8F\xFA" => "\xEB\xA5\x93", + "\x8F\xFB" => "\xEB\xA5\x94", + "\x8F\xFC" => "\xEB\xA5\x95", + "\x8F\xFD" => "\xEB\xA5\x96", + "\x8F\xFE" => "\xEB\xA5\x97", + "\x90\x41" => "\xEB\xA5\x9A", + "\x90\x42" => "\xEB\xA5\x9B", + "\x90\x43" => "\xEB\xA5\x9D", + "\x90\x44" => "\xEB\xA5\x9E", + "\x90\x45" => "\xEB\xA5\x9F", + "\x90\x46" => "\xEB\xA5\xA1", + "\x90\x47" => "\xEB\xA5\xA2", + "\x90\x48" => "\xEB\xA5\xA3", + "\x90\x49" => "\xEB\xA5\xA4", + "\x90\x4A" => "\xEB\xA5\xA5", + "\x90\x4B" => "\xEB\xA5\xA6", + "\x90\x4C" => "\xEB\xA5\xA7", + "\x90\x4D" => "\xEB\xA5\xAA", + "\x90\x4E" => "\xEB\xA5\xAC", + "\x90\x4F" => "\xEB\xA5\xAE", + "\x90\x50" => "\xEB\xA5\xAF", + "\x90\x51" => "\xEB\xA5\xB0", + "\x90\x52" => "\xEB\xA5\xB1", + "\x90\x53" => "\xEB\xA5\xB2", + "\x90\x54" => "\xEB\xA5\xB3", + "\x90\x55" => "\xEB\xA5\xB6", + "\x90\x56" => "\xEB\xA5\xB7", + "\x90\x57" => "\xEB\xA5\xB9", + "\x90\x58" => "\xEB\xA5\xBA", + "\x90\x59" => "\xEB\xA5\xBB", + "\x90\x5A" => "\xEB\xA5\xBD", + "\x90\x61" => "\xEB\xA5\xBE", + "\x90\x62" => "\xEB\xA5\xBF", + "\x90\x63" => "\xEB\xA6\x80", + "\x90\x64" => "\xEB\xA6\x81", + "\x90\x65" => "\xEB\xA6\x82", + "\x90\x66" => "\xEB\xA6\x83", + "\x90\x67" => "\xEB\xA6\x86", + "\x90\x68" => "\xEB\xA6\x88", + "\x90\x69" => "\xEB\xA6\x8B", + "\x90\x6A" => "\xEB\xA6\x8C", + "\x90\x6B" => "\xEB\xA6\x8F", + "\x90\x6C" => "\xEB\xA6\x90", + "\x90\x6D" => "\xEB\xA6\x91", + "\x90\x6E" => "\xEB\xA6\x92", + "\x90\x6F" => "\xEB\xA6\x93", + "\x90\x70" => "\xEB\xA6\x94", + "\x90\x71" => "\xEB\xA6\x95", + "\x90\x72" => "\xEB\xA6\x96", + "\x90\x73" => "\xEB\xA6\x97", + "\x90\x74" => "\xEB\xA6\x98", + "\x90\x75" => "\xEB\xA6\x99", + "\x90\x76" => "\xEB\xA6\x9A", + "\x90\x77" => "\xEB\xA6\x9B", + "\x90\x78" => "\xEB\xA6\x9C", + "\x90\x79" => "\xEB\xA6\x9D", + "\x90\x7A" => "\xEB\xA6\x9E", + "\x90\x81" => "\xEB\xA6\x9F", + "\x90\x82" => "\xEB\xA6\xA0", + "\x90\x83" => "\xEB\xA6\xA1", + "\x90\x84" => "\xEB\xA6\xA2", + "\x90\x85" => "\xEB\xA6\xA3", + "\x90\x86" => "\xEB\xA6\xA4", + "\x90\x87" => "\xEB\xA6\xA5", + "\x90\x88" => "\xEB\xA6\xA6", + "\x90\x89" => "\xEB\xA6\xA7", + "\x90\x8A" => "\xEB\xA6\xA8", + "\x90\x8B" => "\xEB\xA6\xA9", + "\x90\x8C" => "\xEB\xA6\xAA", + "\x90\x8D" => "\xEB\xA6\xAB", + "\x90\x8E" => "\xEB\xA6\xAE", + "\x90\x8F" => "\xEB\xA6\xAF", + "\x90\x90" => "\xEB\xA6\xB1", + "\x90\x91" => "\xEB\xA6\xB2", + "\x90\x92" => "\xEB\xA6\xB3", + "\x90\x93" => "\xEB\xA6\xB5", + "\x90\x94" => "\xEB\xA6\xB6", + "\x90\x95" => "\xEB\xA6\xB7", + "\x90\x96" => "\xEB\xA6\xB8", + "\x90\x97" => "\xEB\xA6\xB9", + "\x90\x98" => "\xEB\xA6\xBA", + "\x90\x99" => "\xEB\xA6\xBB", + "\x90\x9A" => "\xEB\xA6\xBE", + "\x90\x9B" => "\xEB\xA7\x80", + "\x90\x9C" => "\xEB\xA7\x82", + "\x90\x9D" => "\xEB\xA7\x83", + "\x90\x9E" => "\xEB\xA7\x84", + "\x90\x9F" => "\xEB\xA7\x85", + "\x90\xA0" => "\xEB\xA7\x86", + "\x90\xA1" => "\xEB\xA7\x87", + "\x90\xA2" => "\xEB\xA7\x8A", + "\x90\xA3" => "\xEB\xA7\x8B", + "\x90\xA4" => "\xEB\xA7\x8D", + "\x90\xA5" => "\xEB\xA7\x93", + "\x90\xA6" => "\xEB\xA7\x94", + "\x90\xA7" => "\xEB\xA7\x95", + "\x90\xA8" => "\xEB\xA7\x96", + "\x90\xA9" => "\xEB\xA7\x97", + "\x90\xAA" => "\xEB\xA7\x9A", + "\x90\xAB" => "\xEB\xA7\x9C", + "\x90\xAC" => "\xEB\xA7\x9F", + "\x90\xAD" => "\xEB\xA7\xA0", + "\x90\xAE" => "\xEB\xA7\xA2", + "\x90\xAF" => "\xEB\xA7\xA6", + "\x90\xB0" => "\xEB\xA7\xA7", + "\x90\xB1" => "\xEB\xA7\xA9", + "\x90\xB2" => "\xEB\xA7\xAA", + "\x90\xB3" => "\xEB\xA7\xAB", + "\x90\xB4" => "\xEB\xA7\xAD", + "\x90\xB5" => "\xEB\xA7\xAE", + "\x90\xB6" => "\xEB\xA7\xAF", + "\x90\xB7" => "\xEB\xA7\xB0", + "\x90\xB8" => "\xEB\xA7\xB1", + "\x90\xB9" => "\xEB\xA7\xB2", + "\x90\xBA" => "\xEB\xA7\xB3", + "\x90\xBB" => "\xEB\xA7\xB6", + "\x90\xBC" => "\xEB\xA7\xBB", + "\x90\xBD" => "\xEB\xA7\xBC", + "\x90\xBE" => "\xEB\xA7\xBD", + "\x90\xBF" => "\xEB\xA7\xBE", + "\x90\xC0" => "\xEB\xA7\xBF", + "\x90\xC1" => "\xEB\xA8\x82", + "\x90\xC2" => "\xEB\xA8\x83", + "\x90\xC3" => "\xEB\xA8\x84", + "\x90\xC4" => "\xEB\xA8\x85", + "\x90\xC5" => "\xEB\xA8\x86", + "\x90\xC6" => "\xEB\xA8\x87", + "\x90\xC7" => "\xEB\xA8\x89", + "\x90\xC8" => "\xEB\xA8\x8A", + "\x90\xC9" => "\xEB\xA8\x8B", + "\x90\xCA" => "\xEB\xA8\x8C", + "\x90\xCB" => "\xEB\xA8\x8D", + "\x90\xCC" => "\xEB\xA8\x8E", + "\x90\xCD" => "\xEB\xA8\x8F", + "\x90\xCE" => "\xEB\xA8\x90", + "\x90\xCF" => "\xEB\xA8\x91", + "\x90\xD0" => "\xEB\xA8\x92", + "\x90\xD1" => "\xEB\xA8\x93", + "\x90\xD2" => "\xEB\xA8\x94", + "\x90\xD3" => "\xEB\xA8\x96", + "\x90\xD4" => "\xEB\xA8\x97", + "\x90\xD5" => "\xEB\xA8\x98", + "\x90\xD6" => "\xEB\xA8\x99", + "\x90\xD7" => "\xEB\xA8\x9A", + "\x90\xD8" => "\xEB\xA8\x9B", + "\x90\xD9" => "\xEB\xA8\x9C", + "\x90\xDA" => "\xEB\xA8\x9D", + "\x90\xDB" => "\xEB\xA8\x9E", + "\x90\xDC" => "\xEB\xA8\x9F", + "\x90\xDD" => "\xEB\xA8\xA0", + "\x90\xDE" => "\xEB\xA8\xA1", + "\x90\xDF" => "\xEB\xA8\xA2", + "\x90\xE0" => "\xEB\xA8\xA3", + "\x90\xE1" => "\xEB\xA8\xA4", + "\x90\xE2" => "\xEB\xA8\xA5", + "\x90\xE3" => "\xEB\xA8\xA6", + "\x90\xE4" => "\xEB\xA8\xA7", + "\x90\xE5" => "\xEB\xA8\xA8", + "\x90\xE6" => "\xEB\xA8\xA9", + "\x90\xE7" => "\xEB\xA8\xAA", + "\x90\xE8" => "\xEB\xA8\xAB", + "\x90\xE9" => "\xEB\xA8\xAC", + "\x90\xEA" => "\xEB\xA8\xAD", + "\x90\xEB" => "\xEB\xA8\xAE", + "\x90\xEC" => "\xEB\xA8\xAF", + "\x90\xED" => "\xEB\xA8\xB0", + "\x90\xEE" => "\xEB\xA8\xB1", + "\x90\xEF" => "\xEB\xA8\xB2", + "\x90\xF0" => "\xEB\xA8\xB3", + "\x90\xF1" => "\xEB\xA8\xB4", + "\x90\xF2" => "\xEB\xA8\xB5", + "\x90\xF3" => "\xEB\xA8\xB6", + "\x90\xF4" => "\xEB\xA8\xB7", + "\x90\xF5" => "\xEB\xA8\xBA", + "\x90\xF6" => "\xEB\xA8\xBB", + "\x90\xF7" => "\xEB\xA8\xBD", + "\x90\xF8" => "\xEB\xA8\xBE", + "\x90\xF9" => "\xEB\xA8\xBF", + "\x90\xFA" => "\xEB\xA9\x81", + "\x90\xFB" => "\xEB\xA9\x83", + "\x90\xFC" => "\xEB\xA9\x84", + "\x90\xFD" => "\xEB\xA9\x85", + "\x90\xFE" => "\xEB\xA9\x86", + "\x91\x41" => "\xEB\xA9\x87", + "\x91\x42" => "\xEB\xA9\x8A", + "\x91\x43" => "\xEB\xA9\x8C", + "\x91\x44" => "\xEB\xA9\x8F", + "\x91\x45" => "\xEB\xA9\x90", + "\x91\x46" => "\xEB\xA9\x91", + "\x91\x47" => "\xEB\xA9\x92", + "\x91\x48" => "\xEB\xA9\x96", + "\x91\x49" => "\xEB\xA9\x97", + "\x91\x4A" => "\xEB\xA9\x99", + "\x91\x4B" => "\xEB\xA9\x9A", + "\x91\x4C" => "\xEB\xA9\x9B", + "\x91\x4D" => "\xEB\xA9\x9D", + "\x91\x4E" => "\xEB\xA9\x9E", + "\x91\x4F" => "\xEB\xA9\x9F", + "\x91\x50" => "\xEB\xA9\xA0", + "\x91\x51" => "\xEB\xA9\xA1", + "\x91\x52" => "\xEB\xA9\xA2", + "\x91\x53" => "\xEB\xA9\xA3", + "\x91\x54" => "\xEB\xA9\xA6", + "\x91\x55" => "\xEB\xA9\xAA", + "\x91\x56" => "\xEB\xA9\xAB", + "\x91\x57" => "\xEB\xA9\xAC", + "\x91\x58" => "\xEB\xA9\xAD", + "\x91\x59" => "\xEB\xA9\xAE", + "\x91\x5A" => "\xEB\xA9\xAF", + "\x91\x61" => "\xEB\xA9\xB2", + "\x91\x62" => "\xEB\xA9\xB3", + "\x91\x63" => "\xEB\xA9\xB5", + "\x91\x64" => "\xEB\xA9\xB6", + "\x91\x65" => "\xEB\xA9\xB7", + "\x91\x66" => "\xEB\xA9\xB9", + "\x91\x67" => "\xEB\xA9\xBA", + "\x91\x68" => "\xEB\xA9\xBB", + "\x91\x69" => "\xEB\xA9\xBC", + "\x91\x6A" => "\xEB\xA9\xBD", + "\x91\x6B" => "\xEB\xA9\xBE", + "\x91\x6C" => "\xEB\xA9\xBF", + "\x91\x6D" => "\xEB\xAA\x80", + "\x91\x6E" => "\xEB\xAA\x81", + "\x91\x6F" => "\xEB\xAA\x82", + "\x91\x70" => "\xEB\xAA\x86", + "\x91\x71" => "\xEB\xAA\x88", + "\x91\x72" => "\xEB\xAA\x89", + "\x91\x73" => "\xEB\xAA\x8A", + "\x91\x74" => "\xEB\xAA\x8B", + "\x91\x75" => "\xEB\xAA\x8D", + "\x91\x76" => "\xEB\xAA\x8E", + "\x91\x77" => "\xEB\xAA\x8F", + "\x91\x78" => "\xEB\xAA\x90", + "\x91\x79" => "\xEB\xAA\x91", + "\x91\x7A" => "\xEB\xAA\x92", + "\x91\x81" => "\xEB\xAA\x93", + "\x91\x82" => "\xEB\xAA\x94", + "\x91\x83" => "\xEB\xAA\x95", + "\x91\x84" => "\xEB\xAA\x96", + "\x91\x85" => "\xEB\xAA\x97", + "\x91\x86" => "\xEB\xAA\x98", + "\x91\x87" => "\xEB\xAA\x99", + "\x91\x88" => "\xEB\xAA\x9A", + "\x91\x89" => "\xEB\xAA\x9B", + "\x91\x8A" => "\xEB\xAA\x9C", + "\x91\x8B" => "\xEB\xAA\x9D", + "\x91\x8C" => "\xEB\xAA\x9E", + "\x91\x8D" => "\xEB\xAA\x9F", + "\x91\x8E" => "\xEB\xAA\xA0", + "\x91\x8F" => "\xEB\xAA\xA1", + "\x91\x90" => "\xEB\xAA\xA2", + "\x91\x91" => "\xEB\xAA\xA3", + "\x91\x92" => "\xEB\xAA\xA4", + "\x91\x93" => "\xEB\xAA\xA5", + "\x91\x94" => "\xEB\xAA\xA6", + "\x91\x95" => "\xEB\xAA\xA7", + "\x91\x96" => "\xEB\xAA\xAA", + "\x91\x97" => "\xEB\xAA\xAD", + "\x91\x98" => "\xEB\xAA\xAE", + "\x91\x99" => "\xEB\xAA\xAF", + "\x91\x9A" => "\xEB\xAA\xB1", + "\x91\x9B" => "\xEB\xAA\xB3", + "\x91\x9C" => "\xEB\xAA\xB4", + "\x91\x9D" => "\xEB\xAA\xB5", + "\x91\x9E" => "\xEB\xAA\xB6", + "\x91\x9F" => "\xEB\xAA\xB7", + "\x91\xA0" => "\xEB\xAA\xBA", + "\x91\xA1" => "\xEB\xAA\xBC", + "\x91\xA2" => "\xEB\xAA\xBE", + "\x91\xA3" => "\xEB\xAA\xBF", + "\x91\xA4" => "\xEB\xAB\x80", + "\x91\xA5" => "\xEB\xAB\x81", + "\x91\xA6" => "\xEB\xAB\x82", + "\x91\xA7" => "\xEB\xAB\x83", + "\x91\xA8" => "\xEB\xAB\x85", + "\x91\xA9" => "\xEB\xAB\x86", + "\x91\xAA" => "\xEB\xAB\x87", + "\x91\xAB" => "\xEB\xAB\x89", + "\x91\xAC" => "\xEB\xAB\x8A", + "\x91\xAD" => "\xEB\xAB\x8B", + "\x91\xAE" => "\xEB\xAB\x8C", + "\x91\xAF" => "\xEB\xAB\x8D", + "\x91\xB0" => "\xEB\xAB\x8E", + "\x91\xB1" => "\xEB\xAB\x8F", + "\x91\xB2" => "\xEB\xAB\x90", + "\x91\xB3" => "\xEB\xAB\x91", + "\x91\xB4" => "\xEB\xAB\x92", + "\x91\xB5" => "\xEB\xAB\x93", + "\x91\xB6" => "\xEB\xAB\x94", + "\x91\xB7" => "\xEB\xAB\x95", + "\x91\xB8" => "\xEB\xAB\x96", + "\x91\xB9" => "\xEB\xAB\x97", + "\x91\xBA" => "\xEB\xAB\x9A", + "\x91\xBB" => "\xEB\xAB\x9B", + "\x91\xBC" => "\xEB\xAB\x9C", + "\x91\xBD" => "\xEB\xAB\x9D", + "\x91\xBE" => "\xEB\xAB\x9E", + "\x91\xBF" => "\xEB\xAB\x9F", + "\x91\xC0" => "\xEB\xAB\xA0", + "\x91\xC1" => "\xEB\xAB\xA1", + "\x91\xC2" => "\xEB\xAB\xA2", + "\x91\xC3" => "\xEB\xAB\xA3", + "\x91\xC4" => "\xEB\xAB\xA4", + "\x91\xC5" => "\xEB\xAB\xA5", + "\x91\xC6" => "\xEB\xAB\xA6", + "\x91\xC7" => "\xEB\xAB\xA7", + "\x91\xC8" => "\xEB\xAB\xA8", + "\x91\xC9" => "\xEB\xAB\xA9", + "\x91\xCA" => "\xEB\xAB\xAA", + "\x91\xCB" => "\xEB\xAB\xAB", + "\x91\xCC" => "\xEB\xAB\xAC", + "\x91\xCD" => "\xEB\xAB\xAD", + "\x91\xCE" => "\xEB\xAB\xAE", + "\x91\xCF" => "\xEB\xAB\xAF", + "\x91\xD0" => "\xEB\xAB\xB0", + "\x91\xD1" => "\xEB\xAB\xB1", + "\x91\xD2" => "\xEB\xAB\xB2", + "\x91\xD3" => "\xEB\xAB\xB3", + "\x91\xD4" => "\xEB\xAB\xB4", + "\x91\xD5" => "\xEB\xAB\xB5", + "\x91\xD6" => "\xEB\xAB\xB6", + "\x91\xD7" => "\xEB\xAB\xB7", + "\x91\xD8" => "\xEB\xAB\xB8", + "\x91\xD9" => "\xEB\xAB\xB9", + "\x91\xDA" => "\xEB\xAB\xBA", + "\x91\xDB" => "\xEB\xAB\xBB", + "\x91\xDC" => "\xEB\xAB\xBD", + "\x91\xDD" => "\xEB\xAB\xBE", + "\x91\xDE" => "\xEB\xAB\xBF", + "\x91\xDF" => "\xEB\xAC\x81", + "\x91\xE0" => "\xEB\xAC\x82", + "\x91\xE1" => "\xEB\xAC\x83", + "\x91\xE2" => "\xEB\xAC\x85", + "\x91\xE3" => "\xEB\xAC\x86", + "\x91\xE4" => "\xEB\xAC\x87", + "\x91\xE5" => "\xEB\xAC\x88", + "\x91\xE6" => "\xEB\xAC\x89", + "\x91\xE7" => "\xEB\xAC\x8A", + "\x91\xE8" => "\xEB\xAC\x8B", + "\x91\xE9" => "\xEB\xAC\x8C", + "\x91\xEA" => "\xEB\xAC\x8E", + "\x91\xEB" => "\xEB\xAC\x90", + "\x91\xEC" => "\xEB\xAC\x92", + "\x91\xED" => "\xEB\xAC\x93", + "\x91\xEE" => "\xEB\xAC\x94", + "\x91\xEF" => "\xEB\xAC\x95", + "\x91\xF0" => "\xEB\xAC\x96", + "\x91\xF1" => "\xEB\xAC\x97", + "\x91\xF2" => "\xEB\xAC\x99", + "\x91\xF3" => "\xEB\xAC\x9A", + "\x91\xF4" => "\xEB\xAC\x9B", + "\x91\xF5" => "\xEB\xAC\x9D", + "\x91\xF6" => "\xEB\xAC\x9E", + "\x91\xF7" => "\xEB\xAC\x9F", + "\x91\xF8" => "\xEB\xAC\xA1", + "\x91\xF9" => "\xEB\xAC\xA2", + "\x91\xFA" => "\xEB\xAC\xA3", + "\x91\xFB" => "\xEB\xAC\xA4", + "\x91\xFC" => "\xEB\xAC\xA5", + "\x91\xFD" => "\xEB\xAC\xA6", + "\x91\xFE" => "\xEB\xAC\xA7", + "\x92\x41" => "\xEB\xAC\xA8", + "\x92\x42" => "\xEB\xAC\xAA", + "\x92\x43" => "\xEB\xAC\xAC", + "\x92\x44" => "\xEB\xAC\xAD", + "\x92\x45" => "\xEB\xAC\xAE", + "\x92\x46" => "\xEB\xAC\xAF", + "\x92\x47" => "\xEB\xAC\xB0", + "\x92\x48" => "\xEB\xAC\xB1", + "\x92\x49" => "\xEB\xAC\xB2", + "\x92\x4A" => "\xEB\xAC\xB3", + "\x92\x4B" => "\xEB\xAC\xB7", + "\x92\x4C" => "\xEB\xAC\xB9", + "\x92\x4D" => "\xEB\xAC\xBA", + "\x92\x4E" => "\xEB\xAC\xBF", + "\x92\x4F" => "\xEB\xAD\x80", + "\x92\x50" => "\xEB\xAD\x81", + "\x92\x51" => "\xEB\xAD\x82", + "\x92\x52" => "\xEB\xAD\x83", + "\x92\x53" => "\xEB\xAD\x86", + "\x92\x54" => "\xEB\xAD\x88", + "\x92\x55" => "\xEB\xAD\x8A", + "\x92\x56" => "\xEB\xAD\x8B", + "\x92\x57" => "\xEB\xAD\x8C", + "\x92\x58" => "\xEB\xAD\x8E", + "\x92\x59" => "\xEB\xAD\x91", + "\x92\x5A" => "\xEB\xAD\x92", + "\x92\x61" => "\xEB\xAD\x93", + "\x92\x62" => "\xEB\xAD\x95", + "\x92\x63" => "\xEB\xAD\x96", + "\x92\x64" => "\xEB\xAD\x97", + "\x92\x65" => "\xEB\xAD\x99", + "\x92\x66" => "\xEB\xAD\x9A", + "\x92\x67" => "\xEB\xAD\x9B", + "\x92\x68" => "\xEB\xAD\x9C", + "\x92\x69" => "\xEB\xAD\x9D", + "\x92\x6A" => "\xEB\xAD\x9E", + "\x92\x6B" => "\xEB\xAD\x9F", + "\x92\x6C" => "\xEB\xAD\xA0", + "\x92\x6D" => "\xEB\xAD\xA2", + "\x92\x6E" => "\xEB\xAD\xA4", + "\x92\x6F" => "\xEB\xAD\xA5", + "\x92\x70" => "\xEB\xAD\xA6", + "\x92\x71" => "\xEB\xAD\xA7", + "\x92\x72" => "\xEB\xAD\xA8", + "\x92\x73" => "\xEB\xAD\xA9", + "\x92\x74" => "\xEB\xAD\xAA", + "\x92\x75" => "\xEB\xAD\xAB", + "\x92\x76" => "\xEB\xAD\xAD", + "\x92\x77" => "\xEB\xAD\xAE", + "\x92\x78" => "\xEB\xAD\xAF", + "\x92\x79" => "\xEB\xAD\xB0", + "\x92\x7A" => "\xEB\xAD\xB1", + "\x92\x81" => "\xEB\xAD\xB2", + "\x92\x82" => "\xEB\xAD\xB3", + "\x92\x83" => "\xEB\xAD\xB4", + "\x92\x84" => "\xEB\xAD\xB5", + "\x92\x85" => "\xEB\xAD\xB6", + "\x92\x86" => "\xEB\xAD\xB7", + "\x92\x87" => "\xEB\xAD\xB8", + "\x92\x88" => "\xEB\xAD\xB9", + "\x92\x89" => "\xEB\xAD\xBA", + "\x92\x8A" => "\xEB\xAD\xBB", + "\x92\x8B" => "\xEB\xAD\xBC", + "\x92\x8C" => "\xEB\xAD\xBD", + "\x92\x8D" => "\xEB\xAD\xBE", + "\x92\x8E" => "\xEB\xAD\xBF", + "\x92\x8F" => "\xEB\xAE\x80", + "\x92\x90" => "\xEB\xAE\x81", + "\x92\x91" => "\xEB\xAE\x82", + "\x92\x92" => "\xEB\xAE\x83", + "\x92\x93" => "\xEB\xAE\x84", + "\x92\x94" => "\xEB\xAE\x85", + "\x92\x95" => "\xEB\xAE\x86", + "\x92\x96" => "\xEB\xAE\x87", + "\x92\x97" => "\xEB\xAE\x89", + "\x92\x98" => "\xEB\xAE\x8A", + "\x92\x99" => "\xEB\xAE\x8B", + "\x92\x9A" => "\xEB\xAE\x8D", + "\x92\x9B" => "\xEB\xAE\x8E", + "\x92\x9C" => "\xEB\xAE\x8F", + "\x92\x9D" => "\xEB\xAE\x91", + "\x92\x9E" => "\xEB\xAE\x92", + "\x92\x9F" => "\xEB\xAE\x93", + "\x92\xA0" => "\xEB\xAE\x94", + "\x92\xA1" => "\xEB\xAE\x95", + "\x92\xA2" => "\xEB\xAE\x96", + "\x92\xA3" => "\xEB\xAE\x97", + "\x92\xA4" => "\xEB\xAE\x98", + "\x92\xA5" => "\xEB\xAE\x99", + "\x92\xA6" => "\xEB\xAE\x9A", + "\x92\xA7" => "\xEB\xAE\x9B", + "\x92\xA8" => "\xEB\xAE\x9C", + "\x92\xA9" => "\xEB\xAE\x9D", + "\x92\xAA" => "\xEB\xAE\x9E", + "\x92\xAB" => "\xEB\xAE\x9F", + "\x92\xAC" => "\xEB\xAE\xA0", + "\x92\xAD" => "\xEB\xAE\xA1", + "\x92\xAE" => "\xEB\xAE\xA2", + "\x92\xAF" => "\xEB\xAE\xA3", + "\x92\xB0" => "\xEB\xAE\xA5", + "\x92\xB1" => "\xEB\xAE\xA6", + "\x92\xB2" => "\xEB\xAE\xA7", + "\x92\xB3" => "\xEB\xAE\xA9", + "\x92\xB4" => "\xEB\xAE\xAA", + "\x92\xB5" => "\xEB\xAE\xAB", + "\x92\xB6" => "\xEB\xAE\xAD", + "\x92\xB7" => "\xEB\xAE\xAE", + "\x92\xB8" => "\xEB\xAE\xAF", + "\x92\xB9" => "\xEB\xAE\xB0", + "\x92\xBA" => "\xEB\xAE\xB1", + "\x92\xBB" => "\xEB\xAE\xB2", + "\x92\xBC" => "\xEB\xAE\xB3", + "\x92\xBD" => "\xEB\xAE\xB5", + "\x92\xBE" => "\xEB\xAE\xB6", + "\x92\xBF" => "\xEB\xAE\xB8", + "\x92\xC0" => "\xEB\xAE\xB9", + "\x92\xC1" => "\xEB\xAE\xBA", + "\x92\xC2" => "\xEB\xAE\xBB", + "\x92\xC3" => "\xEB\xAE\xBC", + "\x92\xC4" => "\xEB\xAE\xBD", + "\x92\xC5" => "\xEB\xAE\xBE", + "\x92\xC6" => "\xEB\xAE\xBF", + "\x92\xC7" => "\xEB\xAF\x81", + "\x92\xC8" => "\xEB\xAF\x82", + "\x92\xC9" => "\xEB\xAF\x83", + "\x92\xCA" => "\xEB\xAF\x85", + "\x92\xCB" => "\xEB\xAF\x86", + "\x92\xCC" => "\xEB\xAF\x87", + "\x92\xCD" => "\xEB\xAF\x89", + "\x92\xCE" => "\xEB\xAF\x8A", + "\x92\xCF" => "\xEB\xAF\x8B", + "\x92\xD0" => "\xEB\xAF\x8C", + "\x92\xD1" => "\xEB\xAF\x8D", + "\x92\xD2" => "\xEB\xAF\x8E", + "\x92\xD3" => "\xEB\xAF\x8F", + "\x92\xD4" => "\xEB\xAF\x91", + "\x92\xD5" => "\xEB\xAF\x92", + "\x92\xD6" => "\xEB\xAF\x94", + "\x92\xD7" => "\xEB\xAF\x95", + "\x92\xD8" => "\xEB\xAF\x96", + "\x92\xD9" => "\xEB\xAF\x97", + "\x92\xDA" => "\xEB\xAF\x98", + "\x92\xDB" => "\xEB\xAF\x99", + "\x92\xDC" => "\xEB\xAF\x9A", + "\x92\xDD" => "\xEB\xAF\x9B", + "\x92\xDE" => "\xEB\xAF\x9C", + "\x92\xDF" => "\xEB\xAF\x9D", + "\x92\xE0" => "\xEB\xAF\x9E", + "\x92\xE1" => "\xEB\xAF\x9F", + "\x92\xE2" => "\xEB\xAF\xA0", + "\x92\xE3" => "\xEB\xAF\xA1", + "\x92\xE4" => "\xEB\xAF\xA2", + "\x92\xE5" => "\xEB\xAF\xA3", + "\x92\xE6" => "\xEB\xAF\xA4", + "\x92\xE7" => "\xEB\xAF\xA5", + "\x92\xE8" => "\xEB\xAF\xA6", + "\x92\xE9" => "\xEB\xAF\xA7", + "\x92\xEA" => "\xEB\xAF\xA8", + "\x92\xEB" => "\xEB\xAF\xA9", + "\x92\xEC" => "\xEB\xAF\xAA", + "\x92\xED" => "\xEB\xAF\xAB", + "\x92\xEE" => "\xEB\xAF\xAC", + "\x92\xEF" => "\xEB\xAF\xAD", + "\x92\xF0" => "\xEB\xAF\xAE", + "\x92\xF1" => "\xEB\xAF\xAF", + "\x92\xF2" => "\xEB\xAF\xB0", + "\x92\xF3" => "\xEB\xAF\xB1", + "\x92\xF4" => "\xEB\xAF\xB2", + "\x92\xF5" => "\xEB\xAF\xB3", + "\x92\xF6" => "\xEB\xAF\xB4", + "\x92\xF7" => "\xEB\xAF\xB5", + "\x92\xF8" => "\xEB\xAF\xB6", + "\x92\xF9" => "\xEB\xAF\xB7", + "\x92\xFA" => "\xEB\xAF\xBA", + "\x92\xFB" => "\xEB\xAF\xBB", + "\x92\xFC" => "\xEB\xAF\xBD", + "\x92\xFD" => "\xEB\xAF\xBE", + "\x92\xFE" => "\xEB\xB0\x81", + "\x93\x41" => "\xEB\xB0\x83", + "\x93\x42" => "\xEB\xB0\x84", + "\x93\x43" => "\xEB\xB0\x85", + "\x93\x44" => "\xEB\xB0\x86", + "\x93\x45" => "\xEB\xB0\x87", + "\x93\x46" => "\xEB\xB0\x8A", + "\x93\x47" => "\xEB\xB0\x8E", + "\x93\x48" => "\xEB\xB0\x90", + "\x93\x49" => "\xEB\xB0\x92", + "\x93\x4A" => "\xEB\xB0\x93", + "\x93\x4B" => "\xEB\xB0\x99", + "\x93\x4C" => "\xEB\xB0\x9A", + "\x93\x4D" => "\xEB\xB0\xA0", + "\x93\x4E" => "\xEB\xB0\xA1", + "\x93\x4F" => "\xEB\xB0\xA2", + "\x93\x50" => "\xEB\xB0\xA3", + "\x93\x51" => "\xEB\xB0\xA6", + "\x93\x52" => "\xEB\xB0\xA8", + "\x93\x53" => "\xEB\xB0\xAA", + "\x93\x54" => "\xEB\xB0\xAB", + "\x93\x55" => "\xEB\xB0\xAC", + "\x93\x56" => "\xEB\xB0\xAE", + "\x93\x57" => "\xEB\xB0\xAF", + "\x93\x58" => "\xEB\xB0\xB2", + "\x93\x59" => "\xEB\xB0\xB3", + "\x93\x5A" => "\xEB\xB0\xB5", + "\x93\x61" => "\xEB\xB0\xB6", + "\x93\x62" => "\xEB\xB0\xB7", + "\x93\x63" => "\xEB\xB0\xB9", + "\x93\x64" => "\xEB\xB0\xBA", + "\x93\x65" => "\xEB\xB0\xBB", + "\x93\x66" => "\xEB\xB0\xBC", + "\x93\x67" => "\xEB\xB0\xBD", + "\x93\x68" => "\xEB\xB0\xBE", + "\x93\x69" => "\xEB\xB0\xBF", + "\x93\x6A" => "\xEB\xB1\x82", + "\x93\x6B" => "\xEB\xB1\x86", + "\x93\x6C" => "\xEB\xB1\x87", + "\x93\x6D" => "\xEB\xB1\x88", + "\x93\x6E" => "\xEB\xB1\x8A", + "\x93\x6F" => "\xEB\xB1\x8B", + "\x93\x70" => "\xEB\xB1\x8E", + "\x93\x71" => "\xEB\xB1\x8F", + "\x93\x72" => "\xEB\xB1\x91", + "\x93\x73" => "\xEB\xB1\x92", + "\x93\x74" => "\xEB\xB1\x93", + "\x93\x75" => "\xEB\xB1\x94", + "\x93\x76" => "\xEB\xB1\x95", + "\x93\x77" => "\xEB\xB1\x96", + "\x93\x78" => "\xEB\xB1\x97", + "\x93\x79" => "\xEB\xB1\x98", + "\x93\x7A" => "\xEB\xB1\x99", + "\x93\x81" => "\xEB\xB1\x9A", + "\x93\x82" => "\xEB\xB1\x9B", + "\x93\x83" => "\xEB\xB1\x9C", + "\x93\x84" => "\xEB\xB1\x9E", + "\x93\x85" => "\xEB\xB1\x9F", + "\x93\x86" => "\xEB\xB1\xA0", + "\x93\x87" => "\xEB\xB1\xA1", + "\x93\x88" => "\xEB\xB1\xA2", + "\x93\x89" => "\xEB\xB1\xA3", + "\x93\x8A" => "\xEB\xB1\xA4", + "\x93\x8B" => "\xEB\xB1\xA5", + "\x93\x8C" => "\xEB\xB1\xA6", + "\x93\x8D" => "\xEB\xB1\xA7", + "\x93\x8E" => "\xEB\xB1\xA8", + "\x93\x8F" => "\xEB\xB1\xA9", + "\x93\x90" => "\xEB\xB1\xAA", + "\x93\x91" => "\xEB\xB1\xAB", + "\x93\x92" => "\xEB\xB1\xAC", + "\x93\x93" => "\xEB\xB1\xAD", + "\x93\x94" => "\xEB\xB1\xAE", + "\x93\x95" => "\xEB\xB1\xAF", + "\x93\x96" => "\xEB\xB1\xB0", + "\x93\x97" => "\xEB\xB1\xB1", + "\x93\x98" => "\xEB\xB1\xB2", + "\x93\x99" => "\xEB\xB1\xB3", + "\x93\x9A" => "\xEB\xB1\xB4", + "\x93\x9B" => "\xEB\xB1\xB5", + "\x93\x9C" => "\xEB\xB1\xB6", + "\x93\x9D" => "\xEB\xB1\xB7", + "\x93\x9E" => "\xEB\xB1\xB8", + "\x93\x9F" => "\xEB\xB1\xB9", + "\x93\xA0" => "\xEB\xB1\xBA", + "\x93\xA1" => "\xEB\xB1\xBB", + "\x93\xA2" => "\xEB\xB1\xBC", + "\x93\xA3" => "\xEB\xB1\xBD", + "\x93\xA4" => "\xEB\xB1\xBE", + "\x93\xA5" => "\xEB\xB1\xBF", + "\x93\xA6" => "\xEB\xB2\x80", + "\x93\xA7" => "\xEB\xB2\x81", + "\x93\xA8" => "\xEB\xB2\x82", + "\x93\xA9" => "\xEB\xB2\x83", + "\x93\xAA" => "\xEB\xB2\x86", + "\x93\xAB" => "\xEB\xB2\x87", + "\x93\xAC" => "\xEB\xB2\x89", + "\x93\xAD" => "\xEB\xB2\x8A", + "\x93\xAE" => "\xEB\xB2\x8D", + "\x93\xAF" => "\xEB\xB2\x8F", + "\x93\xB0" => "\xEB\xB2\x90", + "\x93\xB1" => "\xEB\xB2\x91", + "\x93\xB2" => "\xEB\xB2\x92", + "\x93\xB3" => "\xEB\xB2\x93", + "\x93\xB4" => "\xEB\xB2\x96", + "\x93\xB5" => "\xEB\xB2\x98", + "\x93\xB6" => "\xEB\xB2\x9B", + "\x93\xB7" => "\xEB\xB2\x9C", + "\x93\xB8" => "\xEB\xB2\x9D", + "\x93\xB9" => "\xEB\xB2\x9E", + "\x93\xBA" => "\xEB\xB2\x9F", + "\x93\xBB" => "\xEB\xB2\xA2", + "\x93\xBC" => "\xEB\xB2\xA3", + "\x93\xBD" => "\xEB\xB2\xA5", + "\x93\xBE" => "\xEB\xB2\xA6", + "\x93\xBF" => "\xEB\xB2\xA9", + "\x93\xC0" => "\xEB\xB2\xAA", + "\x93\xC1" => "\xEB\xB2\xAB", + "\x93\xC2" => "\xEB\xB2\xAC", + "\x93\xC3" => "\xEB\xB2\xAD", + "\x93\xC4" => "\xEB\xB2\xAE", + "\x93\xC5" => "\xEB\xB2\xAF", + "\x93\xC6" => "\xEB\xB2\xB2", + "\x93\xC7" => "\xEB\xB2\xB6", + "\x93\xC8" => "\xEB\xB2\xB7", + "\x93\xC9" => "\xEB\xB2\xB8", + "\x93\xCA" => "\xEB\xB2\xB9", + "\x93\xCB" => "\xEB\xB2\xBA", + "\x93\xCC" => "\xEB\xB2\xBB", + "\x93\xCD" => "\xEB\xB2\xBE", + "\x93\xCE" => "\xEB\xB2\xBF", + "\x93\xCF" => "\xEB\xB3\x81", + "\x93\xD0" => "\xEB\xB3\x82", + "\x93\xD1" => "\xEB\xB3\x83", + "\x93\xD2" => "\xEB\xB3\x85", + "\x93\xD3" => "\xEB\xB3\x86", + "\x93\xD4" => "\xEB\xB3\x87", + "\x93\xD5" => "\xEB\xB3\x88", + "\x93\xD6" => "\xEB\xB3\x89", + "\x93\xD7" => "\xEB\xB3\x8A", + "\x93\xD8" => "\xEB\xB3\x8B", + "\x93\xD9" => "\xEB\xB3\x8C", + "\x93\xDA" => "\xEB\xB3\x8E", + "\x93\xDB" => "\xEB\xB3\x92", + "\x93\xDC" => "\xEB\xB3\x93", + "\x93\xDD" => "\xEB\xB3\x94", + "\x93\xDE" => "\xEB\xB3\x96", + "\x93\xDF" => "\xEB\xB3\x97", + "\x93\xE0" => "\xEB\xB3\x99", + "\x93\xE1" => "\xEB\xB3\x9A", + "\x93\xE2" => "\xEB\xB3\x9B", + "\x93\xE3" => "\xEB\xB3\x9D", + "\x93\xE4" => "\xEB\xB3\x9E", + "\x93\xE5" => "\xEB\xB3\x9F", + "\x93\xE6" => "\xEB\xB3\xA0", + "\x93\xE7" => "\xEB\xB3\xA1", + "\x93\xE8" => "\xEB\xB3\xA2", + "\x93\xE9" => "\xEB\xB3\xA3", + "\x93\xEA" => "\xEB\xB3\xA4", + "\x93\xEB" => "\xEB\xB3\xA5", + "\x93\xEC" => "\xEB\xB3\xA6", + "\x93\xED" => "\xEB\xB3\xA7", + "\x93\xEE" => "\xEB\xB3\xA8", + "\x93\xEF" => "\xEB\xB3\xA9", + "\x93\xF0" => "\xEB\xB3\xAA", + "\x93\xF1" => "\xEB\xB3\xAB", + "\x93\xF2" => "\xEB\xB3\xAC", + "\x93\xF3" => "\xEB\xB3\xAD", + "\x93\xF4" => "\xEB\xB3\xAE", + "\x93\xF5" => "\xEB\xB3\xAF", + "\x93\xF6" => "\xEB\xB3\xB0", + "\x93\xF7" => "\xEB\xB3\xB1", + "\x93\xF8" => "\xEB\xB3\xB2", + "\x93\xF9" => "\xEB\xB3\xB3", + "\x93\xFA" => "\xEB\xB3\xB7", + "\x93\xFB" => "\xEB\xB3\xB9", + "\x93\xFC" => "\xEB\xB3\xBA", + "\x93\xFD" => "\xEB\xB3\xBB", + "\x93\xFE" => "\xEB\xB3\xBD", + "\x94\x41" => "\xEB\xB3\xBE", + "\x94\x42" => "\xEB\xB3\xBF", + "\x94\x43" => "\xEB\xB4\x80", + "\x94\x44" => "\xEB\xB4\x81", + "\x94\x45" => "\xEB\xB4\x82", + "\x94\x46" => "\xEB\xB4\x83", + "\x94\x47" => "\xEB\xB4\x86", + "\x94\x48" => "\xEB\xB4\x88", + "\x94\x49" => "\xEB\xB4\x8A", + "\x94\x4A" => "\xEB\xB4\x8B", + "\x94\x4B" => "\xEB\xB4\x8C", + "\x94\x4C" => "\xEB\xB4\x8D", + "\x94\x4D" => "\xEB\xB4\x8E", + "\x94\x4E" => "\xEB\xB4\x8F", + "\x94\x4F" => "\xEB\xB4\x91", + "\x94\x50" => "\xEB\xB4\x92", + "\x94\x51" => "\xEB\xB4\x93", + "\x94\x52" => "\xEB\xB4\x95", + "\x94\x53" => "\xEB\xB4\x96", + "\x94\x54" => "\xEB\xB4\x97", + "\x94\x55" => "\xEB\xB4\x98", + "\x94\x56" => "\xEB\xB4\x99", + "\x94\x57" => "\xEB\xB4\x9A", + "\x94\x58" => "\xEB\xB4\x9B", + "\x94\x59" => "\xEB\xB4\x9C", + "\x94\x5A" => "\xEB\xB4\x9D", + "\x94\x61" => "\xEB\xB4\x9E", + "\x94\x62" => "\xEB\xB4\x9F", + "\x94\x63" => "\xEB\xB4\xA0", + "\x94\x64" => "\xEB\xB4\xA1", + "\x94\x65" => "\xEB\xB4\xA2", + "\x94\x66" => "\xEB\xB4\xA3", + "\x94\x67" => "\xEB\xB4\xA5", + "\x94\x68" => "\xEB\xB4\xA6", + "\x94\x69" => "\xEB\xB4\xA7", + "\x94\x6A" => "\xEB\xB4\xA8", + "\x94\x6B" => "\xEB\xB4\xA9", + "\x94\x6C" => "\xEB\xB4\xAA", + "\x94\x6D" => "\xEB\xB4\xAB", + "\x94\x6E" => "\xEB\xB4\xAD", + "\x94\x6F" => "\xEB\xB4\xAE", + "\x94\x70" => "\xEB\xB4\xAF", + "\x94\x71" => "\xEB\xB4\xB0", + "\x94\x72" => "\xEB\xB4\xB1", + "\x94\x73" => "\xEB\xB4\xB2", + "\x94\x74" => "\xEB\xB4\xB3", + "\x94\x75" => "\xEB\xB4\xB4", + "\x94\x76" => "\xEB\xB4\xB5", + "\x94\x77" => "\xEB\xB4\xB6", + "\x94\x78" => "\xEB\xB4\xB7", + "\x94\x79" => "\xEB\xB4\xB8", + "\x94\x7A" => "\xEB\xB4\xB9", + "\x94\x81" => "\xEB\xB4\xBA", + "\x94\x82" => "\xEB\xB4\xBB", + "\x94\x83" => "\xEB\xB4\xBC", + "\x94\x84" => "\xEB\xB4\xBD", + "\x94\x85" => "\xEB\xB4\xBE", + "\x94\x86" => "\xEB\xB4\xBF", + "\x94\x87" => "\xEB\xB5\x81", + "\x94\x88" => "\xEB\xB5\x82", + "\x94\x89" => "\xEB\xB5\x83", + "\x94\x8A" => "\xEB\xB5\x84", + "\x94\x8B" => "\xEB\xB5\x85", + "\x94\x8C" => "\xEB\xB5\x86", + "\x94\x8D" => "\xEB\xB5\x87", + "\x94\x8E" => "\xEB\xB5\x8A", + "\x94\x8F" => "\xEB\xB5\x8B", + "\x94\x90" => "\xEB\xB5\x8D", + "\x94\x91" => "\xEB\xB5\x8E", + "\x94\x92" => "\xEB\xB5\x8F", + "\x94\x93" => "\xEB\xB5\x91", + "\x94\x94" => "\xEB\xB5\x92", + "\x94\x95" => "\xEB\xB5\x93", + "\x94\x96" => "\xEB\xB5\x94", + "\x94\x97" => "\xEB\xB5\x95", + "\x94\x98" => "\xEB\xB5\x96", + "\x94\x99" => "\xEB\xB5\x97", + "\x94\x9A" => "\xEB\xB5\x9A", + "\x94\x9B" => "\xEB\xB5\x9B", + "\x94\x9C" => "\xEB\xB5\x9C", + "\x94\x9D" => "\xEB\xB5\x9D", + "\x94\x9E" => "\xEB\xB5\x9E", + "\x94\x9F" => "\xEB\xB5\x9F", + "\x94\xA0" => "\xEB\xB5\xA0", + "\x94\xA1" => "\xEB\xB5\xA1", + "\x94\xA2" => "\xEB\xB5\xA2", + "\x94\xA3" => "\xEB\xB5\xA3", + "\x94\xA4" => "\xEB\xB5\xA5", + "\x94\xA5" => "\xEB\xB5\xA6", + "\x94\xA6" => "\xEB\xB5\xA7", + "\x94\xA7" => "\xEB\xB5\xA9", + "\x94\xA8" => "\xEB\xB5\xAA", + "\x94\xA9" => "\xEB\xB5\xAB", + "\x94\xAA" => "\xEB\xB5\xAC", + "\x94\xAB" => "\xEB\xB5\xAD", + "\x94\xAC" => "\xEB\xB5\xAE", + "\x94\xAD" => "\xEB\xB5\xAF", + "\x94\xAE" => "\xEB\xB5\xB0", + "\x94\xAF" => "\xEB\xB5\xB1", + "\x94\xB0" => "\xEB\xB5\xB2", + "\x94\xB1" => "\xEB\xB5\xB3", + "\x94\xB2" => "\xEB\xB5\xB4", + "\x94\xB3" => "\xEB\xB5\xB5", + "\x94\xB4" => "\xEB\xB5\xB6", + "\x94\xB5" => "\xEB\xB5\xB7", + "\x94\xB6" => "\xEB\xB5\xB8", + "\x94\xB7" => "\xEB\xB5\xB9", + "\x94\xB8" => "\xEB\xB5\xBA", + "\x94\xB9" => "\xEB\xB5\xBB", + "\x94\xBA" => "\xEB\xB5\xBC", + "\x94\xBB" => "\xEB\xB5\xBD", + "\x94\xBC" => "\xEB\xB5\xBE", + "\x94\xBD" => "\xEB\xB5\xBF", + "\x94\xBE" => "\xEB\xB6\x82", + "\x94\xBF" => "\xEB\xB6\x83", + "\x94\xC0" => "\xEB\xB6\x85", + "\x94\xC1" => "\xEB\xB6\x86", + "\x94\xC2" => "\xEB\xB6\x8B", + "\x94\xC3" => "\xEB\xB6\x8C", + "\x94\xC4" => "\xEB\xB6\x8D", + "\x94\xC5" => "\xEB\xB6\x8E", + "\x94\xC6" => "\xEB\xB6\x8F", + "\x94\xC7" => "\xEB\xB6\x92", + "\x94\xC8" => "\xEB\xB6\x94", + "\x94\xC9" => "\xEB\xB6\x96", + "\x94\xCA" => "\xEB\xB6\x97", + "\x94\xCB" => "\xEB\xB6\x98", + "\x94\xCC" => "\xEB\xB6\x9B", + "\x94\xCD" => "\xEB\xB6\x9D", + "\x94\xCE" => "\xEB\xB6\x9E", + "\x94\xCF" => "\xEB\xB6\x9F", + "\x94\xD0" => "\xEB\xB6\xA0", + "\x94\xD1" => "\xEB\xB6\xA1", + "\x94\xD2" => "\xEB\xB6\xA2", + "\x94\xD3" => "\xEB\xB6\xA3", + "\x94\xD4" => "\xEB\xB6\xA5", + "\x94\xD5" => "\xEB\xB6\xA6", + "\x94\xD6" => "\xEB\xB6\xA7", + "\x94\xD7" => "\xEB\xB6\xA8", + "\x94\xD8" => "\xEB\xB6\xA9", + "\x94\xD9" => "\xEB\xB6\xAA", + "\x94\xDA" => "\xEB\xB6\xAB", + "\x94\xDB" => "\xEB\xB6\xAC", + "\x94\xDC" => "\xEB\xB6\xAD", + "\x94\xDD" => "\xEB\xB6\xAE", + "\x94\xDE" => "\xEB\xB6\xAF", + "\x94\xDF" => "\xEB\xB6\xB1", + "\x94\xE0" => "\xEB\xB6\xB2", + "\x94\xE1" => "\xEB\xB6\xB3", + "\x94\xE2" => "\xEB\xB6\xB4", + "\x94\xE3" => "\xEB\xB6\xB5", + "\x94\xE4" => "\xEB\xB6\xB6", + "\x94\xE5" => "\xEB\xB6\xB7", + "\x94\xE6" => "\xEB\xB6\xB9", + "\x94\xE7" => "\xEB\xB6\xBA", + "\x94\xE8" => "\xEB\xB6\xBB", + "\x94\xE9" => "\xEB\xB6\xBC", + "\x94\xEA" => "\xEB\xB6\xBD", + "\x94\xEB" => "\xEB\xB6\xBE", + "\x94\xEC" => "\xEB\xB6\xBF", + "\x94\xED" => "\xEB\xB7\x80", + "\x94\xEE" => "\xEB\xB7\x81", + "\x94\xEF" => "\xEB\xB7\x82", + "\x94\xF0" => "\xEB\xB7\x83", + "\x94\xF1" => "\xEB\xB7\x84", + "\x94\xF2" => "\xEB\xB7\x85", + "\x94\xF3" => "\xEB\xB7\x86", + "\x94\xF4" => "\xEB\xB7\x87", + "\x94\xF5" => "\xEB\xB7\x88", + "\x94\xF6" => "\xEB\xB7\x89", + "\x94\xF7" => "\xEB\xB7\x8A", + "\x94\xF8" => "\xEB\xB7\x8B", + "\x94\xF9" => "\xEB\xB7\x8C", + "\x94\xFA" => "\xEB\xB7\x8D", + "\x94\xFB" => "\xEB\xB7\x8E", + "\x94\xFC" => "\xEB\xB7\x8F", + "\x94\xFD" => "\xEB\xB7\x90", + "\x94\xFE" => "\xEB\xB7\x91", + "\x95\x41" => "\xEB\xB7\x92", + "\x95\x42" => "\xEB\xB7\x93", + "\x95\x43" => "\xEB\xB7\x96", + "\x95\x44" => "\xEB\xB7\x97", + "\x95\x45" => "\xEB\xB7\x99", + "\x95\x46" => "\xEB\xB7\x9A", + "\x95\x47" => "\xEB\xB7\x9B", + "\x95\x48" => "\xEB\xB7\x9D", + "\x95\x49" => "\xEB\xB7\x9E", + "\x95\x4A" => "\xEB\xB7\x9F", + "\x95\x4B" => "\xEB\xB7\xA0", + "\x95\x4C" => "\xEB\xB7\xA1", + "\x95\x4D" => "\xEB\xB7\xA2", + "\x95\x4E" => "\xEB\xB7\xA3", + "\x95\x4F" => "\xEB\xB7\xA4", + "\x95\x50" => "\xEB\xB7\xA5", + "\x95\x51" => "\xEB\xB7\xA6", + "\x95\x52" => "\xEB\xB7\xA7", + "\x95\x53" => "\xEB\xB7\xA8", + "\x95\x54" => "\xEB\xB7\xAA", + "\x95\x55" => "\xEB\xB7\xAB", + "\x95\x56" => "\xEB\xB7\xAC", + "\x95\x57" => "\xEB\xB7\xAD", + "\x95\x58" => "\xEB\xB7\xAE", + "\x95\x59" => "\xEB\xB7\xAF", + "\x95\x5A" => "\xEB\xB7\xB1", + "\x95\x61" => "\xEB\xB7\xB2", + "\x95\x62" => "\xEB\xB7\xB3", + "\x95\x63" => "\xEB\xB7\xB5", + "\x95\x64" => "\xEB\xB7\xB6", + "\x95\x65" => "\xEB\xB7\xB7", + "\x95\x66" => "\xEB\xB7\xB9", + "\x95\x67" => "\xEB\xB7\xBA", + "\x95\x68" => "\xEB\xB7\xBB", + "\x95\x69" => "\xEB\xB7\xBC", + "\x95\x6A" => "\xEB\xB7\xBD", + "\x95\x6B" => "\xEB\xB7\xBE", + "\x95\x6C" => "\xEB\xB7\xBF", + "\x95\x6D" => "\xEB\xB8\x81", + "\x95\x6E" => "\xEB\xB8\x82", + "\x95\x6F" => "\xEB\xB8\x84", + "\x95\x70" => "\xEB\xB8\x86", + "\x95\x71" => "\xEB\xB8\x87", + "\x95\x72" => "\xEB\xB8\x88", + "\x95\x73" => "\xEB\xB8\x89", + "\x95\x74" => "\xEB\xB8\x8A", + "\x95\x75" => "\xEB\xB8\x8B", + "\x95\x76" => "\xEB\xB8\x8E", + "\x95\x77" => "\xEB\xB8\x8F", + "\x95\x78" => "\xEB\xB8\x91", + "\x95\x79" => "\xEB\xB8\x92", + "\x95\x7A" => "\xEB\xB8\x93", + "\x95\x81" => "\xEB\xB8\x95", + "\x95\x82" => "\xEB\xB8\x96", + "\x95\x83" => "\xEB\xB8\x97", + "\x95\x84" => "\xEB\xB8\x98", + "\x95\x85" => "\xEB\xB8\x99", + "\x95\x86" => "\xEB\xB8\x9A", + "\x95\x87" => "\xEB\xB8\x9B", + "\x95\x88" => "\xEB\xB8\x9E", + "\x95\x89" => "\xEB\xB8\xA0", + "\x95\x8A" => "\xEB\xB8\xA1", + "\x95\x8B" => "\xEB\xB8\xA2", + "\x95\x8C" => "\xEB\xB8\xA3", + "\x95\x8D" => "\xEB\xB8\xA4", + "\x95\x8E" => "\xEB\xB8\xA5", + "\x95\x8F" => "\xEB\xB8\xA6", + "\x95\x90" => "\xEB\xB8\xA7", + "\x95\x91" => "\xEB\xB8\xA8", + "\x95\x92" => "\xEB\xB8\xA9", + "\x95\x93" => "\xEB\xB8\xAA", + "\x95\x94" => "\xEB\xB8\xAB", + "\x95\x95" => "\xEB\xB8\xAC", + "\x95\x96" => "\xEB\xB8\xAD", + "\x95\x97" => "\xEB\xB8\xAE", + "\x95\x98" => "\xEB\xB8\xAF", + "\x95\x99" => "\xEB\xB8\xB0", + "\x95\x9A" => "\xEB\xB8\xB1", + "\x95\x9B" => "\xEB\xB8\xB2", + "\x95\x9C" => "\xEB\xB8\xB3", + "\x95\x9D" => "\xEB\xB8\xB4", + "\x95\x9E" => "\xEB\xB8\xB5", + "\x95\x9F" => "\xEB\xB8\xB6", + "\x95\xA0" => "\xEB\xB8\xB7", + "\x95\xA1" => "\xEB\xB8\xB8", + "\x95\xA2" => "\xEB\xB8\xB9", + "\x95\xA3" => "\xEB\xB8\xBA", + "\x95\xA4" => "\xEB\xB8\xBB", + "\x95\xA5" => "\xEB\xB8\xBC", + "\x95\xA6" => "\xEB\xB8\xBD", + "\x95\xA7" => "\xEB\xB8\xBE", + "\x95\xA8" => "\xEB\xB8\xBF", + "\x95\xA9" => "\xEB\xB9\x80", + "\x95\xAA" => "\xEB\xB9\x81", + "\x95\xAB" => "\xEB\xB9\x82", + "\x95\xAC" => "\xEB\xB9\x83", + "\x95\xAD" => "\xEB\xB9\x86", + "\x95\xAE" => "\xEB\xB9\x87", + "\x95\xAF" => "\xEB\xB9\x89", + "\x95\xB0" => "\xEB\xB9\x8A", + "\x95\xB1" => "\xEB\xB9\x8B", + "\x95\xB2" => "\xEB\xB9\x8D", + "\x95\xB3" => "\xEB\xB9\x8F", + "\x95\xB4" => "\xEB\xB9\x90", + "\x95\xB5" => "\xEB\xB9\x91", + "\x95\xB6" => "\xEB\xB9\x92", + "\x95\xB7" => "\xEB\xB9\x93", + "\x95\xB8" => "\xEB\xB9\x96", + "\x95\xB9" => "\xEB\xB9\x98", + "\x95\xBA" => "\xEB\xB9\x9C", + "\x95\xBB" => "\xEB\xB9\x9D", + "\x95\xBC" => "\xEB\xB9\x9E", + "\x95\xBD" => "\xEB\xB9\x9F", + "\x95\xBE" => "\xEB\xB9\xA2", + "\x95\xBF" => "\xEB\xB9\xA3", + "\x95\xC0" => "\xEB\xB9\xA5", + "\x95\xC1" => "\xEB\xB9\xA6", + "\x95\xC2" => "\xEB\xB9\xA7", + "\x95\xC3" => "\xEB\xB9\xA9", + "\x95\xC4" => "\xEB\xB9\xAB", + "\x95\xC5" => "\xEB\xB9\xAC", + "\x95\xC6" => "\xEB\xB9\xAD", + "\x95\xC7" => "\xEB\xB9\xAE", + "\x95\xC8" => "\xEB\xB9\xAF", + "\x95\xC9" => "\xEB\xB9\xB2", + "\x95\xCA" => "\xEB\xB9\xB6", + "\x95\xCB" => "\xEB\xB9\xB7", + "\x95\xCC" => "\xEB\xB9\xB8", + "\x95\xCD" => "\xEB\xB9\xB9", + "\x95\xCE" => "\xEB\xB9\xBA", + "\x95\xCF" => "\xEB\xB9\xBE", + "\x95\xD0" => "\xEB\xB9\xBF", + "\x95\xD1" => "\xEB\xBA\x81", + "\x95\xD2" => "\xEB\xBA\x82", + "\x95\xD3" => "\xEB\xBA\x83", + "\x95\xD4" => "\xEB\xBA\x85", + "\x95\xD5" => "\xEB\xBA\x86", + "\x95\xD6" => "\xEB\xBA\x87", + "\x95\xD7" => "\xEB\xBA\x88", + "\x95\xD8" => "\xEB\xBA\x89", + "\x95\xD9" => "\xEB\xBA\x8A", + "\x95\xDA" => "\xEB\xBA\x8B", + "\x95\xDB" => "\xEB\xBA\x8E", + "\x95\xDC" => "\xEB\xBA\x92", + "\x95\xDD" => "\xEB\xBA\x93", + "\x95\xDE" => "\xEB\xBA\x94", + "\x95\xDF" => "\xEB\xBA\x95", + "\x95\xE0" => "\xEB\xBA\x96", + "\x95\xE1" => "\xEB\xBA\x97", + "\x95\xE2" => "\xEB\xBA\x9A", + "\x95\xE3" => "\xEB\xBA\x9B", + "\x95\xE4" => "\xEB\xBA\x9C", + "\x95\xE5" => "\xEB\xBA\x9D", + "\x95\xE6" => "\xEB\xBA\x9E", + "\x95\xE7" => "\xEB\xBA\x9F", + "\x95\xE8" => "\xEB\xBA\xA0", + "\x95\xE9" => "\xEB\xBA\xA1", + "\x95\xEA" => "\xEB\xBA\xA2", + "\x95\xEB" => "\xEB\xBA\xA3", + "\x95\xEC" => "\xEB\xBA\xA4", + "\x95\xED" => "\xEB\xBA\xA5", + "\x95\xEE" => "\xEB\xBA\xA6", + "\x95\xEF" => "\xEB\xBA\xA7", + "\x95\xF0" => "\xEB\xBA\xA9", + "\x95\xF1" => "\xEB\xBA\xAA", + "\x95\xF2" => "\xEB\xBA\xAB", + "\x95\xF3" => "\xEB\xBA\xAC", + "\x95\xF4" => "\xEB\xBA\xAD", + "\x95\xF5" => "\xEB\xBA\xAE", + "\x95\xF6" => "\xEB\xBA\xAF", + "\x95\xF7" => "\xEB\xBA\xB0", + "\x95\xF8" => "\xEB\xBA\xB1", + "\x95\xF9" => "\xEB\xBA\xB2", + "\x95\xFA" => "\xEB\xBA\xB3", + "\x95\xFB" => "\xEB\xBA\xB4", + "\x95\xFC" => "\xEB\xBA\xB5", + "\x95\xFD" => "\xEB\xBA\xB6", + "\x95\xFE" => "\xEB\xBA\xB7", + "\x96\x41" => "\xEB\xBA\xB8", + "\x96\x42" => "\xEB\xBA\xB9", + "\x96\x43" => "\xEB\xBA\xBA", + "\x96\x44" => "\xEB\xBA\xBB", + "\x96\x45" => "\xEB\xBA\xBC", + "\x96\x46" => "\xEB\xBA\xBD", + "\x96\x47" => "\xEB\xBA\xBE", + "\x96\x48" => "\xEB\xBA\xBF", + "\x96\x49" => "\xEB\xBB\x80", + "\x96\x4A" => "\xEB\xBB\x81", + "\x96\x4B" => "\xEB\xBB\x82", + "\x96\x4C" => "\xEB\xBB\x83", + "\x96\x4D" => "\xEB\xBB\x84", + "\x96\x4E" => "\xEB\xBB\x85", + "\x96\x4F" => "\xEB\xBB\x86", + "\x96\x50" => "\xEB\xBB\x87", + "\x96\x51" => "\xEB\xBB\x88", + "\x96\x52" => "\xEB\xBB\x89", + "\x96\x53" => "\xEB\xBB\x8A", + "\x96\x54" => "\xEB\xBB\x8B", + "\x96\x55" => "\xEB\xBB\x8C", + "\x96\x56" => "\xEB\xBB\x8D", + "\x96\x57" => "\xEB\xBB\x8E", + "\x96\x58" => "\xEB\xBB\x8F", + "\x96\x59" => "\xEB\xBB\x92", + "\x96\x5A" => "\xEB\xBB\x93", + "\x96\x61" => "\xEB\xBB\x95", + "\x96\x62" => "\xEB\xBB\x96", + "\x96\x63" => "\xEB\xBB\x99", + "\x96\x64" => "\xEB\xBB\x9A", + "\x96\x65" => "\xEB\xBB\x9B", + "\x96\x66" => "\xEB\xBB\x9C", + "\x96\x67" => "\xEB\xBB\x9D", + "\x96\x68" => "\xEB\xBB\x9E", + "\x96\x69" => "\xEB\xBB\x9F", + "\x96\x6A" => "\xEB\xBB\xA1", + "\x96\x6B" => "\xEB\xBB\xA2", + "\x96\x6C" => "\xEB\xBB\xA6", + "\x96\x6D" => "\xEB\xBB\xA7", + "\x96\x6E" => "\xEB\xBB\xA8", + "\x96\x6F" => "\xEB\xBB\xA9", + "\x96\x70" => "\xEB\xBB\xAA", + "\x96\x71" => "\xEB\xBB\xAB", + "\x96\x72" => "\xEB\xBB\xAD", + "\x96\x73" => "\xEB\xBB\xAE", + "\x96\x74" => "\xEB\xBB\xAF", + "\x96\x75" => "\xEB\xBB\xB0", + "\x96\x76" => "\xEB\xBB\xB1", + "\x96\x77" => "\xEB\xBB\xB2", + "\x96\x78" => "\xEB\xBB\xB3", + "\x96\x79" => "\xEB\xBB\xB4", + "\x96\x7A" => "\xEB\xBB\xB5", + "\x96\x81" => "\xEB\xBB\xB6", + "\x96\x82" => "\xEB\xBB\xB7", + "\x96\x83" => "\xEB\xBB\xB8", + "\x96\x84" => "\xEB\xBB\xB9", + "\x96\x85" => "\xEB\xBB\xBA", + "\x96\x86" => "\xEB\xBB\xBB", + "\x96\x87" => "\xEB\xBB\xBC", + "\x96\x88" => "\xEB\xBB\xBD", + "\x96\x89" => "\xEB\xBB\xBE", + "\x96\x8A" => "\xEB\xBB\xBF", + "\x96\x8B" => "\xEB\xBC\x80", + "\x96\x8C" => "\xEB\xBC\x82", + "\x96\x8D" => "\xEB\xBC\x83", + "\x96\x8E" => "\xEB\xBC\x84", + "\x96\x8F" => "\xEB\xBC\x85", + "\x96\x90" => "\xEB\xBC\x86", + "\x96\x91" => "\xEB\xBC\x87", + "\x96\x92" => "\xEB\xBC\x8A", + "\x96\x93" => "\xEB\xBC\x8B", + "\x96\x94" => "\xEB\xBC\x8C", + "\x96\x95" => "\xEB\xBC\x8D", + "\x96\x96" => "\xEB\xBC\x8E", + "\x96\x97" => "\xEB\xBC\x8F", + "\x96\x98" => "\xEB\xBC\x90", + "\x96\x99" => "\xEB\xBC\x91", + "\x96\x9A" => "\xEB\xBC\x92", + "\x96\x9B" => "\xEB\xBC\x93", + "\x96\x9C" => "\xEB\xBC\x94", + "\x96\x9D" => "\xEB\xBC\x95", + "\x96\x9E" => "\xEB\xBC\x96", + "\x96\x9F" => "\xEB\xBC\x97", + "\x96\xA0" => "\xEB\xBC\x9A", + "\x96\xA1" => "\xEB\xBC\x9E", + "\x96\xA2" => "\xEB\xBC\x9F", + "\x96\xA3" => "\xEB\xBC\xA0", + "\x96\xA4" => "\xEB\xBC\xA1", + "\x96\xA5" => "\xEB\xBC\xA2", + "\x96\xA6" => "\xEB\xBC\xA3", + "\x96\xA7" => "\xEB\xBC\xA4", + "\x96\xA8" => "\xEB\xBC\xA5", + "\x96\xA9" => "\xEB\xBC\xA6", + "\x96\xAA" => "\xEB\xBC\xA7", + "\x96\xAB" => "\xEB\xBC\xA8", + "\x96\xAC" => "\xEB\xBC\xA9", + "\x96\xAD" => "\xEB\xBC\xAA", + "\x96\xAE" => "\xEB\xBC\xAB", + "\x96\xAF" => "\xEB\xBC\xAC", + "\x96\xB0" => "\xEB\xBC\xAD", + "\x96\xB1" => "\xEB\xBC\xAE", + "\x96\xB2" => "\xEB\xBC\xAF", + "\x96\xB3" => "\xEB\xBC\xB0", + "\x96\xB4" => "\xEB\xBC\xB1", + "\x96\xB5" => "\xEB\xBC\xB2", + "\x96\xB6" => "\xEB\xBC\xB3", + "\x96\xB7" => "\xEB\xBC\xB4", + "\x96\xB8" => "\xEB\xBC\xB5", + "\x96\xB9" => "\xEB\xBC\xB6", + "\x96\xBA" => "\xEB\xBC\xB7", + "\x96\xBB" => "\xEB\xBC\xB8", + "\x96\xBC" => "\xEB\xBC\xB9", + "\x96\xBD" => "\xEB\xBC\xBA", + "\x96\xBE" => "\xEB\xBC\xBB", + "\x96\xBF" => "\xEB\xBC\xBC", + "\x96\xC0" => "\xEB\xBC\xBD", + "\x96\xC1" => "\xEB\xBC\xBE", + "\x96\xC2" => "\xEB\xBC\xBF", + "\x96\xC3" => "\xEB\xBD\x82", + "\x96\xC4" => "\xEB\xBD\x83", + "\x96\xC5" => "\xEB\xBD\x85", + "\x96\xC6" => "\xEB\xBD\x86", + "\x96\xC7" => "\xEB\xBD\x87", + "\x96\xC8" => "\xEB\xBD\x89", + "\x96\xC9" => "\xEB\xBD\x8A", + "\x96\xCA" => "\xEB\xBD\x8B", + "\x96\xCB" => "\xEB\xBD\x8C", + "\x96\xCC" => "\xEB\xBD\x8D", + "\x96\xCD" => "\xEB\xBD\x8E", + "\x96\xCE" => "\xEB\xBD\x8F", + "\x96\xCF" => "\xEB\xBD\x92", + "\x96\xD0" => "\xEB\xBD\x93", + "\x96\xD1" => "\xEB\xBD\x94", + "\x96\xD2" => "\xEB\xBD\x96", + "\x96\xD3" => "\xEB\xBD\x97", + "\x96\xD4" => "\xEB\xBD\x98", + "\x96\xD5" => "\xEB\xBD\x99", + "\x96\xD6" => "\xEB\xBD\x9A", + "\x96\xD7" => "\xEB\xBD\x9B", + "\x96\xD8" => "\xEB\xBD\x9C", + "\x96\xD9" => "\xEB\xBD\x9D", + "\x96\xDA" => "\xEB\xBD\x9E", + "\x96\xDB" => "\xEB\xBD\x9F", + "\x96\xDC" => "\xEB\xBD\xA0", + "\x96\xDD" => "\xEB\xBD\xA1", + "\x96\xDE" => "\xEB\xBD\xA2", + "\x96\xDF" => "\xEB\xBD\xA3", + "\x96\xE0" => "\xEB\xBD\xA4", + "\x96\xE1" => "\xEB\xBD\xA5", + "\x96\xE2" => "\xEB\xBD\xA6", + "\x96\xE3" => "\xEB\xBD\xA7", + "\x96\xE4" => "\xEB\xBD\xA8", + "\x96\xE5" => "\xEB\xBD\xA9", + "\x96\xE6" => "\xEB\xBD\xAA", + "\x96\xE7" => "\xEB\xBD\xAB", + "\x96\xE8" => "\xEB\xBD\xAC", + "\x96\xE9" => "\xEB\xBD\xAD", + "\x96\xEA" => "\xEB\xBD\xAE", + "\x96\xEB" => "\xEB\xBD\xAF", + "\x96\xEC" => "\xEB\xBD\xB0", + "\x96\xED" => "\xEB\xBD\xB1", + "\x96\xEE" => "\xEB\xBD\xB2", + "\x96\xEF" => "\xEB\xBD\xB3", + "\x96\xF0" => "\xEB\xBD\xB4", + "\x96\xF1" => "\xEB\xBD\xB5", + "\x96\xF2" => "\xEB\xBD\xB6", + "\x96\xF3" => "\xEB\xBD\xB7", + "\x96\xF4" => "\xEB\xBD\xB8", + "\x96\xF5" => "\xEB\xBD\xB9", + "\x96\xF6" => "\xEB\xBD\xBA", + "\x96\xF7" => "\xEB\xBD\xBB", + "\x96\xF8" => "\xEB\xBD\xBC", + "\x96\xF9" => "\xEB\xBD\xBD", + "\x96\xFA" => "\xEB\xBD\xBE", + "\x96\xFB" => "\xEB\xBD\xBF", + "\x96\xFC" => "\xEB\xBE\x80", + "\x96\xFD" => "\xEB\xBE\x81", + "\x96\xFE" => "\xEB\xBE\x82", + "\x97\x41" => "\xEB\xBE\x83", + "\x97\x42" => "\xEB\xBE\x84", + "\x97\x43" => "\xEB\xBE\x85", + "\x97\x44" => "\xEB\xBE\x86", + "\x97\x45" => "\xEB\xBE\x87", + "\x97\x46" => "\xEB\xBE\x88", + "\x97\x47" => "\xEB\xBE\x89", + "\x97\x48" => "\xEB\xBE\x8A", + "\x97\x49" => "\xEB\xBE\x8B", + "\x97\x4A" => "\xEB\xBE\x8C", + "\x97\x4B" => "\xEB\xBE\x8D", + "\x97\x4C" => "\xEB\xBE\x8E", + "\x97\x4D" => "\xEB\xBE\x8F", + "\x97\x4E" => "\xEB\xBE\x90", + "\x97\x4F" => "\xEB\xBE\x91", + "\x97\x50" => "\xEB\xBE\x92", + "\x97\x51" => "\xEB\xBE\x93", + "\x97\x52" => "\xEB\xBE\x95", + "\x97\x53" => "\xEB\xBE\x96", + "\x97\x54" => "\xEB\xBE\x97", + "\x97\x55" => "\xEB\xBE\x98", + "\x97\x56" => "\xEB\xBE\x99", + "\x97\x57" => "\xEB\xBE\x9A", + "\x97\x58" => "\xEB\xBE\x9B", + "\x97\x59" => "\xEB\xBE\x9C", + "\x97\x5A" => "\xEB\xBE\x9D", + "\x97\x61" => "\xEB\xBE\x9E", + "\x97\x62" => "\xEB\xBE\x9F", + "\x97\x63" => "\xEB\xBE\xA0", + "\x97\x64" => "\xEB\xBE\xA1", + "\x97\x65" => "\xEB\xBE\xA2", + "\x97\x66" => "\xEB\xBE\xA3", + "\x97\x67" => "\xEB\xBE\xA4", + "\x97\x68" => "\xEB\xBE\xA5", + "\x97\x69" => "\xEB\xBE\xA6", + "\x97\x6A" => "\xEB\xBE\xA7", + "\x97\x6B" => "\xEB\xBE\xA8", + "\x97\x6C" => "\xEB\xBE\xA9", + "\x97\x6D" => "\xEB\xBE\xAA", + "\x97\x6E" => "\xEB\xBE\xAB", + "\x97\x6F" => "\xEB\xBE\xAC", + "\x97\x70" => "\xEB\xBE\xAD", + "\x97\x71" => "\xEB\xBE\xAE", + "\x97\x72" => "\xEB\xBE\xAF", + "\x97\x73" => "\xEB\xBE\xB1", + "\x97\x74" => "\xEB\xBE\xB2", + "\x97\x75" => "\xEB\xBE\xB3", + "\x97\x76" => "\xEB\xBE\xB4", + "\x97\x77" => "\xEB\xBE\xB5", + "\x97\x78" => "\xEB\xBE\xB6", + "\x97\x79" => "\xEB\xBE\xB7", + "\x97\x7A" => "\xEB\xBE\xB8", + "\x97\x81" => "\xEB\xBE\xB9", + "\x97\x82" => "\xEB\xBE\xBA", + "\x97\x83" => "\xEB\xBE\xBB", + "\x97\x84" => "\xEB\xBE\xBC", + "\x97\x85" => "\xEB\xBE\xBD", + "\x97\x86" => "\xEB\xBE\xBE", + "\x97\x87" => "\xEB\xBE\xBF", + "\x97\x88" => "\xEB\xBF\x80", + "\x97\x89" => "\xEB\xBF\x81", + "\x97\x8A" => "\xEB\xBF\x82", + "\x97\x8B" => "\xEB\xBF\x83", + "\x97\x8C" => "\xEB\xBF\x84", + "\x97\x8D" => "\xEB\xBF\x86", + "\x97\x8E" => "\xEB\xBF\x87", + "\x97\x8F" => "\xEB\xBF\x88", + "\x97\x90" => "\xEB\xBF\x89", + "\x97\x91" => "\xEB\xBF\x8A", + "\x97\x92" => "\xEB\xBF\x8B", + "\x97\x93" => "\xEB\xBF\x8E", + "\x97\x94" => "\xEB\xBF\x8F", + "\x97\x95" => "\xEB\xBF\x91", + "\x97\x96" => "\xEB\xBF\x92", + "\x97\x97" => "\xEB\xBF\x93", + "\x97\x98" => "\xEB\xBF\x95", + "\x97\x99" => "\xEB\xBF\x96", + "\x97\x9A" => "\xEB\xBF\x97", + "\x97\x9B" => "\xEB\xBF\x98", + "\x97\x9C" => "\xEB\xBF\x99", + "\x97\x9D" => "\xEB\xBF\x9A", + "\x97\x9E" => "\xEB\xBF\x9B", + "\x97\x9F" => "\xEB\xBF\x9D", + "\x97\xA0" => "\xEB\xBF\x9E", + "\x97\xA1" => "\xEB\xBF\xA0", + "\x97\xA2" => "\xEB\xBF\xA2", + "\x97\xA3" => "\xEB\xBF\xA3", + "\x97\xA4" => "\xEB\xBF\xA4", + "\x97\xA5" => "\xEB\xBF\xA5", + "\x97\xA6" => "\xEB\xBF\xA6", + "\x97\xA7" => "\xEB\xBF\xA7", + "\x97\xA8" => "\xEB\xBF\xA8", + "\x97\xA9" => "\xEB\xBF\xA9", + "\x97\xAA" => "\xEB\xBF\xAA", + "\x97\xAB" => "\xEB\xBF\xAB", + "\x97\xAC" => "\xEB\xBF\xAC", + "\x97\xAD" => "\xEB\xBF\xAD", + "\x97\xAE" => "\xEB\xBF\xAE", + "\x97\xAF" => "\xEB\xBF\xAF", + "\x97\xB0" => "\xEB\xBF\xB0", + "\x97\xB1" => "\xEB\xBF\xB1", + "\x97\xB2" => "\xEB\xBF\xB2", + "\x97\xB3" => "\xEB\xBF\xB3", + "\x97\xB4" => "\xEB\xBF\xB4", + "\x97\xB5" => "\xEB\xBF\xB5", + "\x97\xB6" => "\xEB\xBF\xB6", + "\x97\xB7" => "\xEB\xBF\xB7", + "\x97\xB8" => "\xEB\xBF\xB8", + "\x97\xB9" => "\xEB\xBF\xB9", + "\x97\xBA" => "\xEB\xBF\xBA", + "\x97\xBB" => "\xEB\xBF\xBB", + "\x97\xBC" => "\xEB\xBF\xBC", + "\x97\xBD" => "\xEB\xBF\xBD", + "\x97\xBE" => "\xEB\xBF\xBE", + "\x97\xBF" => "\xEB\xBF\xBF", + "\x97\xC0" => "\xEC\x80\x80", + "\x97\xC1" => "\xEC\x80\x81", + "\x97\xC2" => "\xEC\x80\x82", + "\x97\xC3" => "\xEC\x80\x83", + "\x97\xC4" => "\xEC\x80\x84", + "\x97\xC5" => "\xEC\x80\x85", + "\x97\xC6" => "\xEC\x80\x86", + "\x97\xC7" => "\xEC\x80\x87", + "\x97\xC8" => "\xEC\x80\x88", + "\x97\xC9" => "\xEC\x80\x89", + "\x97\xCA" => "\xEC\x80\x8A", + "\x97\xCB" => "\xEC\x80\x8B", + "\x97\xCC" => "\xEC\x80\x8C", + "\x97\xCD" => "\xEC\x80\x8D", + "\x97\xCE" => "\xEC\x80\x8E", + "\x97\xCF" => "\xEC\x80\x8F", + "\x97\xD0" => "\xEC\x80\x90", + "\x97\xD1" => "\xEC\x80\x91", + "\x97\xD2" => "\xEC\x80\x92", + "\x97\xD3" => "\xEC\x80\x93", + "\x97\xD4" => "\xEC\x80\x94", + "\x97\xD5" => "\xEC\x80\x95", + "\x97\xD6" => "\xEC\x80\x96", + "\x97\xD7" => "\xEC\x80\x97", + "\x97\xD8" => "\xEC\x80\x98", + "\x97\xD9" => "\xEC\x80\x99", + "\x97\xDA" => "\xEC\x80\x9A", + "\x97\xDB" => "\xEC\x80\x9B", + "\x97\xDC" => "\xEC\x80\x9C", + "\x97\xDD" => "\xEC\x80\x9D", + "\x97\xDE" => "\xEC\x80\x9E", + "\x97\xDF" => "\xEC\x80\x9F", + "\x97\xE0" => "\xEC\x80\xA0", + "\x97\xE1" => "\xEC\x80\xA1", + "\x97\xE2" => "\xEC\x80\xA2", + "\x97\xE3" => "\xEC\x80\xA3", + "\x97\xE4" => "\xEC\x80\xA4", + "\x97\xE5" => "\xEC\x80\xA5", + "\x97\xE6" => "\xEC\x80\xA6", + "\x97\xE7" => "\xEC\x80\xA7", + "\x97\xE8" => "\xEC\x80\xA8", + "\x97\xE9" => "\xEC\x80\xA9", + "\x97\xEA" => "\xEC\x80\xAA", + "\x97\xEB" => "\xEC\x80\xAB", + "\x97\xEC" => "\xEC\x80\xAC", + "\x97\xED" => "\xEC\x80\xAD", + "\x97\xEE" => "\xEC\x80\xAE", + "\x97\xEF" => "\xEC\x80\xAF", + "\x97\xF0" => "\xEC\x80\xB0", + "\x97\xF1" => "\xEC\x80\xB1", + "\x97\xF2" => "\xEC\x80\xB2", + "\x97\xF3" => "\xEC\x80\xB3", + "\x97\xF4" => "\xEC\x80\xB4", + "\x97\xF5" => "\xEC\x80\xB5", + "\x97\xF6" => "\xEC\x80\xB6", + "\x97\xF7" => "\xEC\x80\xB7", + "\x97\xF8" => "\xEC\x80\xB8", + "\x97\xF9" => "\xEC\x80\xB9", + "\x97\xFA" => "\xEC\x80\xBA", + "\x97\xFB" => "\xEC\x80\xBB", + "\x97\xFC" => "\xEC\x80\xBD", + "\x97\xFD" => "\xEC\x80\xBE", + "\x97\xFE" => "\xEC\x80\xBF", + "\x98\x41" => "\xEC\x81\x80", + "\x98\x42" => "\xEC\x81\x81", + "\x98\x43" => "\xEC\x81\x82", + "\x98\x44" => "\xEC\x81\x83", + "\x98\x45" => "\xEC\x81\x84", + "\x98\x46" => "\xEC\x81\x85", + "\x98\x47" => "\xEC\x81\x86", + "\x98\x48" => "\xEC\x81\x87", + "\x98\x49" => "\xEC\x81\x88", + "\x98\x4A" => "\xEC\x81\x89", + "\x98\x4B" => "\xEC\x81\x8A", + "\x98\x4C" => "\xEC\x81\x8B", + "\x98\x4D" => "\xEC\x81\x8C", + "\x98\x4E" => "\xEC\x81\x8D", + "\x98\x4F" => "\xEC\x81\x8E", + "\x98\x50" => "\xEC\x81\x8F", + "\x98\x51" => "\xEC\x81\x90", + "\x98\x52" => "\xEC\x81\x92", + "\x98\x53" => "\xEC\x81\x93", + "\x98\x54" => "\xEC\x81\x94", + "\x98\x55" => "\xEC\x81\x95", + "\x98\x56" => "\xEC\x81\x96", + "\x98\x57" => "\xEC\x81\x97", + "\x98\x58" => "\xEC\x81\x99", + "\x98\x59" => "\xEC\x81\x9A", + "\x98\x5A" => "\xEC\x81\x9B", + "\x98\x61" => "\xEC\x81\x9D", + "\x98\x62" => "\xEC\x81\x9E", + "\x98\x63" => "\xEC\x81\x9F", + "\x98\x64" => "\xEC\x81\xA1", + "\x98\x65" => "\xEC\x81\xA2", + "\x98\x66" => "\xEC\x81\xA3", + "\x98\x67" => "\xEC\x81\xA4", + "\x98\x68" => "\xEC\x81\xA5", + "\x98\x69" => "\xEC\x81\xA6", + "\x98\x6A" => "\xEC\x81\xA7", + "\x98\x6B" => "\xEC\x81\xAA", + "\x98\x6C" => "\xEC\x81\xAB", + "\x98\x6D" => "\xEC\x81\xAC", + "\x98\x6E" => "\xEC\x81\xAD", + "\x98\x6F" => "\xEC\x81\xAE", + "\x98\x70" => "\xEC\x81\xAF", + "\x98\x71" => "\xEC\x81\xB0", + "\x98\x72" => "\xEC\x81\xB1", + "\x98\x73" => "\xEC\x81\xB2", + "\x98\x74" => "\xEC\x81\xB3", + "\x98\x75" => "\xEC\x81\xB4", + "\x98\x76" => "\xEC\x81\xB5", + "\x98\x77" => "\xEC\x81\xB6", + "\x98\x78" => "\xEC\x81\xB7", + "\x98\x79" => "\xEC\x81\xB8", + "\x98\x7A" => "\xEC\x81\xB9", + "\x98\x81" => "\xEC\x81\xBA", + "\x98\x82" => "\xEC\x81\xBB", + "\x98\x83" => "\xEC\x81\xBC", + "\x98\x84" => "\xEC\x81\xBD", + "\x98\x85" => "\xEC\x81\xBE", + "\x98\x86" => "\xEC\x81\xBF", + "\x98\x87" => "\xEC\x82\x80", + "\x98\x88" => "\xEC\x82\x81", + "\x98\x89" => "\xEC\x82\x82", + "\x98\x8A" => "\xEC\x82\x83", + "\x98\x8B" => "\xEC\x82\x84", + "\x98\x8C" => "\xEC\x82\x85", + "\x98\x8D" => "\xEC\x82\x86", + "\x98\x8E" => "\xEC\x82\x87", + "\x98\x8F" => "\xEC\x82\x88", + "\x98\x90" => "\xEC\x82\x89", + "\x98\x91" => "\xEC\x82\x8A", + "\x98\x92" => "\xEC\x82\x8B", + "\x98\x93" => "\xEC\x82\x8C", + "\x98\x94" => "\xEC\x82\x8D", + "\x98\x95" => "\xEC\x82\x8E", + "\x98\x96" => "\xEC\x82\x8F", + "\x98\x97" => "\xEC\x82\x92", + "\x98\x98" => "\xEC\x82\x93", + "\x98\x99" => "\xEC\x82\x95", + "\x98\x9A" => "\xEC\x82\x96", + "\x98\x9B" => "\xEC\x82\x97", + "\x98\x9C" => "\xEC\x82\x99", + "\x98\x9D" => "\xEC\x82\x9A", + "\x98\x9E" => "\xEC\x82\x9B", + "\x98\x9F" => "\xEC\x82\x9C", + "\x98\xA0" => "\xEC\x82\x9D", + "\x98\xA1" => "\xEC\x82\x9E", + "\x98\xA2" => "\xEC\x82\x9F", + "\x98\xA3" => "\xEC\x82\xA2", + "\x98\xA4" => "\xEC\x82\xA4", + "\x98\xA5" => "\xEC\x82\xA6", + "\x98\xA6" => "\xEC\x82\xA7", + "\x98\xA7" => "\xEC\x82\xA8", + "\x98\xA8" => "\xEC\x82\xA9", + "\x98\xA9" => "\xEC\x82\xAA", + "\x98\xAA" => "\xEC\x82\xAB", + "\x98\xAB" => "\xEC\x82\xAE", + "\x98\xAC" => "\xEC\x82\xB1", + "\x98\xAD" => "\xEC\x82\xB2", + "\x98\xAE" => "\xEC\x82\xB7", + "\x98\xAF" => "\xEC\x82\xB8", + "\x98\xB0" => "\xEC\x82\xB9", + "\x98\xB1" => "\xEC\x82\xBA", + "\x98\xB2" => "\xEC\x82\xBB", + "\x98\xB3" => "\xEC\x82\xBE", + "\x98\xB4" => "\xEC\x83\x82", + "\x98\xB5" => "\xEC\x83\x83", + "\x98\xB6" => "\xEC\x83\x84", + "\x98\xB7" => "\xEC\x83\x86", + "\x98\xB8" => "\xEC\x83\x87", + "\x98\xB9" => "\xEC\x83\x8A", + "\x98\xBA" => "\xEC\x83\x8B", + "\x98\xBB" => "\xEC\x83\x8D", + "\x98\xBC" => "\xEC\x83\x8E", + "\x98\xBD" => "\xEC\x83\x8F", + "\x98\xBE" => "\xEC\x83\x91", + "\x98\xBF" => "\xEC\x83\x92", + "\x98\xC0" => "\xEC\x83\x93", + "\x98\xC1" => "\xEC\x83\x94", + "\x98\xC2" => "\xEC\x83\x95", + "\x98\xC3" => "\xEC\x83\x96", + "\x98\xC4" => "\xEC\x83\x97", + "\x98\xC5" => "\xEC\x83\x9A", + "\x98\xC6" => "\xEC\x83\x9E", + "\x98\xC7" => "\xEC\x83\x9F", + "\x98\xC8" => "\xEC\x83\xA0", + "\x98\xC9" => "\xEC\x83\xA1", + "\x98\xCA" => "\xEC\x83\xA2", + "\x98\xCB" => "\xEC\x83\xA3", + "\x98\xCC" => "\xEC\x83\xA6", + "\x98\xCD" => "\xEC\x83\xA7", + "\x98\xCE" => "\xEC\x83\xA9", + "\x98\xCF" => "\xEC\x83\xAA", + "\x98\xD0" => "\xEC\x83\xAB", + "\x98\xD1" => "\xEC\x83\xAD", + "\x98\xD2" => "\xEC\x83\xAE", + "\x98\xD3" => "\xEC\x83\xAF", + "\x98\xD4" => "\xEC\x83\xB0", + "\x98\xD5" => "\xEC\x83\xB1", + "\x98\xD6" => "\xEC\x83\xB2", + "\x98\xD7" => "\xEC\x83\xB3", + "\x98\xD8" => "\xEC\x83\xB6", + "\x98\xD9" => "\xEC\x83\xB8", + "\x98\xDA" => "\xEC\x83\xBA", + "\x98\xDB" => "\xEC\x83\xBB", + "\x98\xDC" => "\xEC\x83\xBC", + "\x98\xDD" => "\xEC\x83\xBD", + "\x98\xDE" => "\xEC\x83\xBE", + "\x98\xDF" => "\xEC\x83\xBF", + "\x98\xE0" => "\xEC\x84\x81", + "\x98\xE1" => "\xEC\x84\x82", + "\x98\xE2" => "\xEC\x84\x83", + "\x98\xE3" => "\xEC\x84\x85", + "\x98\xE4" => "\xEC\x84\x86", + "\x98\xE5" => "\xEC\x84\x87", + "\x98\xE6" => "\xEC\x84\x89", + "\x98\xE7" => "\xEC\x84\x8A", + "\x98\xE8" => "\xEC\x84\x8B", + "\x98\xE9" => "\xEC\x84\x8C", + "\x98\xEA" => "\xEC\x84\x8D", + "\x98\xEB" => "\xEC\x84\x8E", + "\x98\xEC" => "\xEC\x84\x8F", + "\x98\xED" => "\xEC\x84\x91", + "\x98\xEE" => "\xEC\x84\x92", + "\x98\xEF" => "\xEC\x84\x93", + "\x98\xF0" => "\xEC\x84\x94", + "\x98\xF1" => "\xEC\x84\x96", + "\x98\xF2" => "\xEC\x84\x97", + "\x98\xF3" => "\xEC\x84\x98", + "\x98\xF4" => "\xEC\x84\x99", + "\x98\xF5" => "\xEC\x84\x9A", + "\x98\xF6" => "\xEC\x84\x9B", + "\x98\xF7" => "\xEC\x84\xA1", + "\x98\xF8" => "\xEC\x84\xA2", + "\x98\xF9" => "\xEC\x84\xA5", + "\x98\xFA" => "\xEC\x84\xA8", + "\x98\xFB" => "\xEC\x84\xA9", + "\x98\xFC" => "\xEC\x84\xAA", + "\x98\xFD" => "\xEC\x84\xAB", + "\x98\xFE" => "\xEC\x84\xAE", + "\x99\x41" => "\xEC\x84\xB2", + "\x99\x42" => "\xEC\x84\xB3", + "\x99\x43" => "\xEC\x84\xB4", + "\x99\x44" => "\xEC\x84\xB5", + "\x99\x45" => "\xEC\x84\xB7", + "\x99\x46" => "\xEC\x84\xBA", + "\x99\x47" => "\xEC\x84\xBB", + "\x99\x48" => "\xEC\x84\xBD", + "\x99\x49" => "\xEC\x84\xBE", + "\x99\x4A" => "\xEC\x84\xBF", + "\x99\x4B" => "\xEC\x85\x81", + "\x99\x4C" => "\xEC\x85\x82", + "\x99\x4D" => "\xEC\x85\x83", + "\x99\x4E" => "\xEC\x85\x84", + "\x99\x4F" => "\xEC\x85\x85", + "\x99\x50" => "\xEC\x85\x86", + "\x99\x51" => "\xEC\x85\x87", + "\x99\x52" => "\xEC\x85\x8A", + "\x99\x53" => "\xEC\x85\x8E", + "\x99\x54" => "\xEC\x85\x8F", + "\x99\x55" => "\xEC\x85\x90", + "\x99\x56" => "\xEC\x85\x91", + "\x99\x57" => "\xEC\x85\x92", + "\x99\x58" => "\xEC\x85\x93", + "\x99\x59" => "\xEC\x85\x96", + "\x99\x5A" => "\xEC\x85\x97", + "\x99\x61" => "\xEC\x85\x99", + "\x99\x62" => "\xEC\x85\x9A", + "\x99\x63" => "\xEC\x85\x9B", + "\x99\x64" => "\xEC\x85\x9D", + "\x99\x65" => "\xEC\x85\x9E", + "\x99\x66" => "\xEC\x85\x9F", + "\x99\x67" => "\xEC\x85\xA0", + "\x99\x68" => "\xEC\x85\xA1", + "\x99\x69" => "\xEC\x85\xA2", + "\x99\x6A" => "\xEC\x85\xA3", + "\x99\x6B" => "\xEC\x85\xA6", + "\x99\x6C" => "\xEC\x85\xAA", + "\x99\x6D" => "\xEC\x85\xAB", + "\x99\x6E" => "\xEC\x85\xAC", + "\x99\x6F" => "\xEC\x85\xAD", + "\x99\x70" => "\xEC\x85\xAE", + "\x99\x71" => "\xEC\x85\xAF", + "\x99\x72" => "\xEC\x85\xB1", + "\x99\x73" => "\xEC\x85\xB2", + "\x99\x74" => "\xEC\x85\xB3", + "\x99\x75" => "\xEC\x85\xB5", + "\x99\x76" => "\xEC\x85\xB6", + "\x99\x77" => "\xEC\x85\xB7", + "\x99\x78" => "\xEC\x85\xB9", + "\x99\x79" => "\xEC\x85\xBA", + "\x99\x7A" => "\xEC\x85\xBB", + "\x99\x81" => "\xEC\x85\xBC", + "\x99\x82" => "\xEC\x85\xBD", + "\x99\x83" => "\xEC\x85\xBE", + "\x99\x84" => "\xEC\x85\xBF", + "\x99\x85" => "\xEC\x86\x80", + "\x99\x86" => "\xEC\x86\x81", + "\x99\x87" => "\xEC\x86\x82", + "\x99\x88" => "\xEC\x86\x83", + "\x99\x89" => "\xEC\x86\x84", + "\x99\x8A" => "\xEC\x86\x86", + "\x99\x8B" => "\xEC\x86\x87", + "\x99\x8C" => "\xEC\x86\x88", + "\x99\x8D" => "\xEC\x86\x89", + "\x99\x8E" => "\xEC\x86\x8A", + "\x99\x8F" => "\xEC\x86\x8B", + "\x99\x90" => "\xEC\x86\x8F", + "\x99\x91" => "\xEC\x86\x91", + "\x99\x92" => "\xEC\x86\x92", + "\x99\x93" => "\xEC\x86\x93", + "\x99\x94" => "\xEC\x86\x95", + "\x99\x95" => "\xEC\x86\x97", + "\x99\x96" => "\xEC\x86\x98", + "\x99\x97" => "\xEC\x86\x99", + "\x99\x98" => "\xEC\x86\x9A", + "\x99\x99" => "\xEC\x86\x9B", + "\x99\x9A" => "\xEC\x86\x9E", + "\x99\x9B" => "\xEC\x86\xA0", + "\x99\x9C" => "\xEC\x86\xA2", + "\x99\x9D" => "\xEC\x86\xA3", + "\x99\x9E" => "\xEC\x86\xA4", + "\x99\x9F" => "\xEC\x86\xA6", + "\x99\xA0" => "\xEC\x86\xA7", + "\x99\xA1" => "\xEC\x86\xAA", + "\x99\xA2" => "\xEC\x86\xAB", + "\x99\xA3" => "\xEC\x86\xAD", + "\x99\xA4" => "\xEC\x86\xAE", + "\x99\xA5" => "\xEC\x86\xAF", + "\x99\xA6" => "\xEC\x86\xB1", + "\x99\xA7" => "\xEC\x86\xB2", + "\x99\xA8" => "\xEC\x86\xB3", + "\x99\xA9" => "\xEC\x86\xB4", + "\x99\xAA" => "\xEC\x86\xB5", + "\x99\xAB" => "\xEC\x86\xB6", + "\x99\xAC" => "\xEC\x86\xB7", + "\x99\xAD" => "\xEC\x86\xB8", + "\x99\xAE" => "\xEC\x86\xB9", + "\x99\xAF" => "\xEC\x86\xBA", + "\x99\xB0" => "\xEC\x86\xBB", + "\x99\xB1" => "\xEC\x86\xBC", + "\x99\xB2" => "\xEC\x86\xBE", + "\x99\xB3" => "\xEC\x86\xBF", + "\x99\xB4" => "\xEC\x87\x80", + "\x99\xB5" => "\xEC\x87\x81", + "\x99\xB6" => "\xEC\x87\x82", + "\x99\xB7" => "\xEC\x87\x83", + "\x99\xB8" => "\xEC\x87\x85", + "\x99\xB9" => "\xEC\x87\x86", + "\x99\xBA" => "\xEC\x87\x87", + "\x99\xBB" => "\xEC\x87\x89", + "\x99\xBC" => "\xEC\x87\x8A", + "\x99\xBD" => "\xEC\x87\x8B", + "\x99\xBE" => "\xEC\x87\x8D", + "\x99\xBF" => "\xEC\x87\x8E", + "\x99\xC0" => "\xEC\x87\x8F", + "\x99\xC1" => "\xEC\x87\x90", + "\x99\xC2" => "\xEC\x87\x91", + "\x99\xC3" => "\xEC\x87\x92", + "\x99\xC4" => "\xEC\x87\x93", + "\x99\xC5" => "\xEC\x87\x95", + "\x99\xC6" => "\xEC\x87\x96", + "\x99\xC7" => "\xEC\x87\x99", + "\x99\xC8" => "\xEC\x87\x9A", + "\x99\xC9" => "\xEC\x87\x9B", + "\x99\xCA" => "\xEC\x87\x9C", + "\x99\xCB" => "\xEC\x87\x9D", + "\x99\xCC" => "\xEC\x87\x9E", + "\x99\xCD" => "\xEC\x87\x9F", + "\x99\xCE" => "\xEC\x87\xA1", + "\x99\xCF" => "\xEC\x87\xA2", + "\x99\xD0" => "\xEC\x87\xA3", + "\x99\xD1" => "\xEC\x87\xA5", + "\x99\xD2" => "\xEC\x87\xA6", + "\x99\xD3" => "\xEC\x87\xA7", + "\x99\xD4" => "\xEC\x87\xA9", + "\x99\xD5" => "\xEC\x87\xAA", + "\x99\xD6" => "\xEC\x87\xAB", + "\x99\xD7" => "\xEC\x87\xAC", + "\x99\xD8" => "\xEC\x87\xAD", + "\x99\xD9" => "\xEC\x87\xAE", + "\x99\xDA" => "\xEC\x87\xAF", + "\x99\xDB" => "\xEC\x87\xB2", + "\x99\xDC" => "\xEC\x87\xB4", + "\x99\xDD" => "\xEC\x87\xB5", + "\x99\xDE" => "\xEC\x87\xB6", + "\x99\xDF" => "\xEC\x87\xB7", + "\x99\xE0" => "\xEC\x87\xB8", + "\x99\xE1" => "\xEC\x87\xB9", + "\x99\xE2" => "\xEC\x87\xBA", + "\x99\xE3" => "\xEC\x87\xBB", + "\x99\xE4" => "\xEC\x87\xBE", + "\x99\xE5" => "\xEC\x87\xBF", + "\x99\xE6" => "\xEC\x88\x81", + "\x99\xE7" => "\xEC\x88\x82", + "\x99\xE8" => "\xEC\x88\x83", + "\x99\xE9" => "\xEC\x88\x85", + "\x99\xEA" => "\xEC\x88\x86", + "\x99\xEB" => "\xEC\x88\x87", + "\x99\xEC" => "\xEC\x88\x88", + "\x99\xED" => "\xEC\x88\x89", + "\x99\xEE" => "\xEC\x88\x8A", + "\x99\xEF" => "\xEC\x88\x8B", + "\x99\xF0" => "\xEC\x88\x8E", + "\x99\xF1" => "\xEC\x88\x90", + "\x99\xF2" => "\xEC\x88\x92", + "\x99\xF3" => "\xEC\x88\x93", + "\x99\xF4" => "\xEC\x88\x94", + "\x99\xF5" => "\xEC\x88\x95", + "\x99\xF6" => "\xEC\x88\x96", + "\x99\xF7" => "\xEC\x88\x97", + "\x99\xF8" => "\xEC\x88\x9A", + "\x99\xF9" => "\xEC\x88\x9B", + "\x99\xFA" => "\xEC\x88\x9D", + "\x99\xFB" => "\xEC\x88\x9E", + "\x99\xFC" => "\xEC\x88\xA1", + "\x99\xFD" => "\xEC\x88\xA2", + "\x99\xFE" => "\xEC\x88\xA3", + "\x9A\x41" => "\xEC\x88\xA4", + "\x9A\x42" => "\xEC\x88\xA5", + "\x9A\x43" => "\xEC\x88\xA6", + "\x9A\x44" => "\xEC\x88\xA7", + "\x9A\x45" => "\xEC\x88\xAA", + "\x9A\x46" => "\xEC\x88\xAC", + "\x9A\x47" => "\xEC\x88\xAE", + "\x9A\x48" => "\xEC\x88\xB0", + "\x9A\x49" => "\xEC\x88\xB3", + "\x9A\x4A" => "\xEC\x88\xB5", + "\x9A\x4B" => "\xEC\x88\xB6", + "\x9A\x4C" => "\xEC\x88\xB7", + "\x9A\x4D" => "\xEC\x88\xB8", + "\x9A\x4E" => "\xEC\x88\xB9", + "\x9A\x4F" => "\xEC\x88\xBA", + "\x9A\x50" => "\xEC\x88\xBB", + "\x9A\x51" => "\xEC\x88\xBC", + "\x9A\x52" => "\xEC\x88\xBD", + "\x9A\x53" => "\xEC\x88\xBE", + "\x9A\x54" => "\xEC\x88\xBF", + "\x9A\x55" => "\xEC\x89\x80", + "\x9A\x56" => "\xEC\x89\x81", + "\x9A\x57" => "\xEC\x89\x82", + "\x9A\x58" => "\xEC\x89\x83", + "\x9A\x59" => "\xEC\x89\x84", + "\x9A\x5A" => "\xEC\x89\x85", + "\x9A\x61" => "\xEC\x89\x86", + "\x9A\x62" => "\xEC\x89\x87", + "\x9A\x63" => "\xEC\x89\x89", + "\x9A\x64" => "\xEC\x89\x8A", + "\x9A\x65" => "\xEC\x89\x8B", + "\x9A\x66" => "\xEC\x89\x8C", + "\x9A\x67" => "\xEC\x89\x8D", + "\x9A\x68" => "\xEC\x89\x8E", + "\x9A\x69" => "\xEC\x89\x8F", + "\x9A\x6A" => "\xEC\x89\x92", + "\x9A\x6B" => "\xEC\x89\x93", + "\x9A\x6C" => "\xEC\x89\x95", + "\x9A\x6D" => "\xEC\x89\x96", + "\x9A\x6E" => "\xEC\x89\x97", + "\x9A\x6F" => "\xEC\x89\x99", + "\x9A\x70" => "\xEC\x89\x9A", + "\x9A\x71" => "\xEC\x89\x9B", + "\x9A\x72" => "\xEC\x89\x9C", + "\x9A\x73" => "\xEC\x89\x9D", + "\x9A\x74" => "\xEC\x89\x9E", + "\x9A\x75" => "\xEC\x89\x9F", + "\x9A\x76" => "\xEC\x89\xA1", + "\x9A\x77" => "\xEC\x89\xA2", + "\x9A\x78" => "\xEC\x89\xA3", + "\x9A\x79" => "\xEC\x89\xA4", + "\x9A\x7A" => "\xEC\x89\xA6", + "\x9A\x81" => "\xEC\x89\xA7", + "\x9A\x82" => "\xEC\x89\xA8", + "\x9A\x83" => "\xEC\x89\xA9", + "\x9A\x84" => "\xEC\x89\xAA", + "\x9A\x85" => "\xEC\x89\xAB", + "\x9A\x86" => "\xEC\x89\xAE", + "\x9A\x87" => "\xEC\x89\xAF", + "\x9A\x88" => "\xEC\x89\xB1", + "\x9A\x89" => "\xEC\x89\xB2", + "\x9A\x8A" => "\xEC\x89\xB3", + "\x9A\x8B" => "\xEC\x89\xB5", + "\x9A\x8C" => "\xEC\x89\xB6", + "\x9A\x8D" => "\xEC\x89\xB7", + "\x9A\x8E" => "\xEC\x89\xB8", + "\x9A\x8F" => "\xEC\x89\xB9", + "\x9A\x90" => "\xEC\x89\xBA", + "\x9A\x91" => "\xEC\x89\xBB", + "\x9A\x92" => "\xEC\x89\xBE", + "\x9A\x93" => "\xEC\x8A\x80", + "\x9A\x94" => "\xEC\x8A\x82", + "\x9A\x95" => "\xEC\x8A\x83", + "\x9A\x96" => "\xEC\x8A\x84", + "\x9A\x97" => "\xEC\x8A\x85", + "\x9A\x98" => "\xEC\x8A\x86", + "\x9A\x99" => "\xEC\x8A\x87", + "\x9A\x9A" => "\xEC\x8A\x8A", + "\x9A\x9B" => "\xEC\x8A\x8B", + "\x9A\x9C" => "\xEC\x8A\x8C", + "\x9A\x9D" => "\xEC\x8A\x8D", + "\x9A\x9E" => "\xEC\x8A\x8E", + "\x9A\x9F" => "\xEC\x8A\x8F", + "\x9A\xA0" => "\xEC\x8A\x91", + "\x9A\xA1" => "\xEC\x8A\x92", + "\x9A\xA2" => "\xEC\x8A\x93", + "\x9A\xA3" => "\xEC\x8A\x94", + "\x9A\xA4" => "\xEC\x8A\x95", + "\x9A\xA5" => "\xEC\x8A\x96", + "\x9A\xA6" => "\xEC\x8A\x97", + "\x9A\xA7" => "\xEC\x8A\x99", + "\x9A\xA8" => "\xEC\x8A\x9A", + "\x9A\xA9" => "\xEC\x8A\x9C", + "\x9A\xAA" => "\xEC\x8A\x9E", + "\x9A\xAB" => "\xEC\x8A\x9F", + "\x9A\xAC" => "\xEC\x8A\xA0", + "\x9A\xAD" => "\xEC\x8A\xA1", + "\x9A\xAE" => "\xEC\x8A\xA2", + "\x9A\xAF" => "\xEC\x8A\xA3", + "\x9A\xB0" => "\xEC\x8A\xA6", + "\x9A\xB1" => "\xEC\x8A\xA7", + "\x9A\xB2" => "\xEC\x8A\xA9", + "\x9A\xB3" => "\xEC\x8A\xAA", + "\x9A\xB4" => "\xEC\x8A\xAB", + "\x9A\xB5" => "\xEC\x8A\xAE", + "\x9A\xB6" => "\xEC\x8A\xAF", + "\x9A\xB7" => "\xEC\x8A\xB0", + "\x9A\xB8" => "\xEC\x8A\xB1", + "\x9A\xB9" => "\xEC\x8A\xB2", + "\x9A\xBA" => "\xEC\x8A\xB3", + "\x9A\xBB" => "\xEC\x8A\xB6", + "\x9A\xBC" => "\xEC\x8A\xB8", + "\x9A\xBD" => "\xEC\x8A\xBA", + "\x9A\xBE" => "\xEC\x8A\xBB", + "\x9A\xBF" => "\xEC\x8A\xBC", + "\x9A\xC0" => "\xEC\x8A\xBD", + "\x9A\xC1" => "\xEC\x8A\xBE", + "\x9A\xC2" => "\xEC\x8A\xBF", + "\x9A\xC3" => "\xEC\x8B\x80", + "\x9A\xC4" => "\xEC\x8B\x81", + "\x9A\xC5" => "\xEC\x8B\x82", + "\x9A\xC6" => "\xEC\x8B\x83", + "\x9A\xC7" => "\xEC\x8B\x84", + "\x9A\xC8" => "\xEC\x8B\x85", + "\x9A\xC9" => "\xEC\x8B\x86", + "\x9A\xCA" => "\xEC\x8B\x87", + "\x9A\xCB" => "\xEC\x8B\x88", + "\x9A\xCC" => "\xEC\x8B\x89", + "\x9A\xCD" => "\xEC\x8B\x8A", + "\x9A\xCE" => "\xEC\x8B\x8B", + "\x9A\xCF" => "\xEC\x8B\x8C", + "\x9A\xD0" => "\xEC\x8B\x8D", + "\x9A\xD1" => "\xEC\x8B\x8E", + "\x9A\xD2" => "\xEC\x8B\x8F", + "\x9A\xD3" => "\xEC\x8B\x90", + "\x9A\xD4" => "\xEC\x8B\x91", + "\x9A\xD5" => "\xEC\x8B\x92", + "\x9A\xD6" => "\xEC\x8B\x93", + "\x9A\xD7" => "\xEC\x8B\x94", + "\x9A\xD8" => "\xEC\x8B\x95", + "\x9A\xD9" => "\xEC\x8B\x96", + "\x9A\xDA" => "\xEC\x8B\x97", + "\x9A\xDB" => "\xEC\x8B\x98", + "\x9A\xDC" => "\xEC\x8B\x99", + "\x9A\xDD" => "\xEC\x8B\x9A", + "\x9A\xDE" => "\xEC\x8B\x9B", + "\x9A\xDF" => "\xEC\x8B\x9E", + "\x9A\xE0" => "\xEC\x8B\x9F", + "\x9A\xE1" => "\xEC\x8B\xA1", + "\x9A\xE2" => "\xEC\x8B\xA2", + "\x9A\xE3" => "\xEC\x8B\xA5", + "\x9A\xE4" => "\xEC\x8B\xA6", + "\x9A\xE5" => "\xEC\x8B\xA7", + "\x9A\xE6" => "\xEC\x8B\xA8", + "\x9A\xE7" => "\xEC\x8B\xA9", + "\x9A\xE8" => "\xEC\x8B\xAA", + "\x9A\xE9" => "\xEC\x8B\xAE", + "\x9A\xEA" => "\xEC\x8B\xB0", + "\x9A\xEB" => "\xEC\x8B\xB2", + "\x9A\xEC" => "\xEC\x8B\xB3", + "\x9A\xED" => "\xEC\x8B\xB4", + "\x9A\xEE" => "\xEC\x8B\xB5", + "\x9A\xEF" => "\xEC\x8B\xB7", + "\x9A\xF0" => "\xEC\x8B\xBA", + "\x9A\xF1" => "\xEC\x8B\xBD", + "\x9A\xF2" => "\xEC\x8B\xBE", + "\x9A\xF3" => "\xEC\x8B\xBF", + "\x9A\xF4" => "\xEC\x8C\x81", + "\x9A\xF5" => "\xEC\x8C\x82", + "\x9A\xF6" => "\xEC\x8C\x83", + "\x9A\xF7" => "\xEC\x8C\x84", + "\x9A\xF8" => "\xEC\x8C\x85", + "\x9A\xF9" => "\xEC\x8C\x86", + "\x9A\xFA" => "\xEC\x8C\x87", + "\x9A\xFB" => "\xEC\x8C\x8A", + "\x9A\xFC" => "\xEC\x8C\x8B", + "\x9A\xFD" => "\xEC\x8C\x8E", + "\x9A\xFE" => "\xEC\x8C\x8F", + "\x9B\x41" => "\xEC\x8C\x90", + "\x9B\x42" => "\xEC\x8C\x91", + "\x9B\x43" => "\xEC\x8C\x92", + "\x9B\x44" => "\xEC\x8C\x96", + "\x9B\x45" => "\xEC\x8C\x97", + "\x9B\x46" => "\xEC\x8C\x99", + "\x9B\x47" => "\xEC\x8C\x9A", + "\x9B\x48" => "\xEC\x8C\x9B", + "\x9B\x49" => "\xEC\x8C\x9D", + "\x9B\x4A" => "\xEC\x8C\x9E", + "\x9B\x4B" => "\xEC\x8C\x9F", + "\x9B\x4C" => "\xEC\x8C\xA0", + "\x9B\x4D" => "\xEC\x8C\xA1", + "\x9B\x4E" => "\xEC\x8C\xA2", + "\x9B\x4F" => "\xEC\x8C\xA3", + "\x9B\x50" => "\xEC\x8C\xA6", + "\x9B\x51" => "\xEC\x8C\xA7", + "\x9B\x52" => "\xEC\x8C\xAA", + "\x9B\x53" => "\xEC\x8C\xAB", + "\x9B\x54" => "\xEC\x8C\xAC", + "\x9B\x55" => "\xEC\x8C\xAD", + "\x9B\x56" => "\xEC\x8C\xAE", + "\x9B\x57" => "\xEC\x8C\xAF", + "\x9B\x58" => "\xEC\x8C\xB0", + "\x9B\x59" => "\xEC\x8C\xB1", + "\x9B\x5A" => "\xEC\x8C\xB2", + "\x9B\x61" => "\xEC\x8C\xB3", + "\x9B\x62" => "\xEC\x8C\xB4", + "\x9B\x63" => "\xEC\x8C\xB5", + "\x9B\x64" => "\xEC\x8C\xB6", + "\x9B\x65" => "\xEC\x8C\xB7", + "\x9B\x66" => "\xEC\x8C\xB8", + "\x9B\x67" => "\xEC\x8C\xB9", + "\x9B\x68" => "\xEC\x8C\xBA", + "\x9B\x69" => "\xEC\x8C\xBB", + "\x9B\x6A" => "\xEC\x8C\xBC", + "\x9B\x6B" => "\xEC\x8C\xBD", + "\x9B\x6C" => "\xEC\x8C\xBE", + "\x9B\x6D" => "\xEC\x8C\xBF", + "\x9B\x6E" => "\xEC\x8D\x80", + "\x9B\x6F" => "\xEC\x8D\x81", + "\x9B\x70" => "\xEC\x8D\x82", + "\x9B\x71" => "\xEC\x8D\x83", + "\x9B\x72" => "\xEC\x8D\x84", + "\x9B\x73" => "\xEC\x8D\x86", + "\x9B\x74" => "\xEC\x8D\x87", + "\x9B\x75" => "\xEC\x8D\x88", + "\x9B\x76" => "\xEC\x8D\x89", + "\x9B\x77" => "\xEC\x8D\x8A", + "\x9B\x78" => "\xEC\x8D\x8B", + "\x9B\x79" => "\xEC\x8D\x8C", + "\x9B\x7A" => "\xEC\x8D\x8D", + "\x9B\x81" => "\xEC\x8D\x8E", + "\x9B\x82" => "\xEC\x8D\x8F", + "\x9B\x83" => "\xEC\x8D\x90", + "\x9B\x84" => "\xEC\x8D\x91", + "\x9B\x85" => "\xEC\x8D\x92", + "\x9B\x86" => "\xEC\x8D\x93", + "\x9B\x87" => "\xEC\x8D\x94", + "\x9B\x88" => "\xEC\x8D\x95", + "\x9B\x89" => "\xEC\x8D\x96", + "\x9B\x8A" => "\xEC\x8D\x97", + "\x9B\x8B" => "\xEC\x8D\x98", + "\x9B\x8C" => "\xEC\x8D\x99", + "\x9B\x8D" => "\xEC\x8D\x9A", + "\x9B\x8E" => "\xEC\x8D\x9B", + "\x9B\x8F" => "\xEC\x8D\x9C", + "\x9B\x90" => "\xEC\x8D\x9D", + "\x9B\x91" => "\xEC\x8D\x9E", + "\x9B\x92" => "\xEC\x8D\x9F", + "\x9B\x93" => "\xEC\x8D\xA0", + "\x9B\x94" => "\xEC\x8D\xA1", + "\x9B\x95" => "\xEC\x8D\xA2", + "\x9B\x96" => "\xEC\x8D\xA3", + "\x9B\x97" => "\xEC\x8D\xA4", + "\x9B\x98" => "\xEC\x8D\xA5", + "\x9B\x99" => "\xEC\x8D\xA6", + "\x9B\x9A" => "\xEC\x8D\xA7", + "\x9B\x9B" => "\xEC\x8D\xAA", + "\x9B\x9C" => "\xEC\x8D\xAB", + "\x9B\x9D" => "\xEC\x8D\xAD", + "\x9B\x9E" => "\xEC\x8D\xAE", + "\x9B\x9F" => "\xEC\x8D\xAF", + "\x9B\xA0" => "\xEC\x8D\xB1", + "\x9B\xA1" => "\xEC\x8D\xB3", + "\x9B\xA2" => "\xEC\x8D\xB4", + "\x9B\xA3" => "\xEC\x8D\xB5", + "\x9B\xA4" => "\xEC\x8D\xB6", + "\x9B\xA5" => "\xEC\x8D\xB7", + "\x9B\xA6" => "\xEC\x8D\xBA", + "\x9B\xA7" => "\xEC\x8D\xBB", + "\x9B\xA8" => "\xEC\x8D\xBE", + "\x9B\xA9" => "\xEC\x8D\xBF", + "\x9B\xAA" => "\xEC\x8E\x80", + "\x9B\xAB" => "\xEC\x8E\x81", + "\x9B\xAC" => "\xEC\x8E\x82", + "\x9B\xAD" => "\xEC\x8E\x83", + "\x9B\xAE" => "\xEC\x8E\x85", + "\x9B\xAF" => "\xEC\x8E\x86", + "\x9B\xB0" => "\xEC\x8E\x87", + "\x9B\xB1" => "\xEC\x8E\x89", + "\x9B\xB2" => "\xEC\x8E\x8A", + "\x9B\xB3" => "\xEC\x8E\x8B", + "\x9B\xB4" => "\xEC\x8E\x8D", + "\x9B\xB5" => "\xEC\x8E\x8E", + "\x9B\xB6" => "\xEC\x8E\x8F", + "\x9B\xB7" => "\xEC\x8E\x90", + "\x9B\xB8" => "\xEC\x8E\x91", + "\x9B\xB9" => "\xEC\x8E\x92", + "\x9B\xBA" => "\xEC\x8E\x93", + "\x9B\xBB" => "\xEC\x8E\x94", + "\x9B\xBC" => "\xEC\x8E\x95", + "\x9B\xBD" => "\xEC\x8E\x96", + "\x9B\xBE" => "\xEC\x8E\x97", + "\x9B\xBF" => "\xEC\x8E\x98", + "\x9B\xC0" => "\xEC\x8E\x99", + "\x9B\xC1" => "\xEC\x8E\x9A", + "\x9B\xC2" => "\xEC\x8E\x9B", + "\x9B\xC3" => "\xEC\x8E\x9C", + "\x9B\xC4" => "\xEC\x8E\x9D", + "\x9B\xC5" => "\xEC\x8E\x9E", + "\x9B\xC6" => "\xEC\x8E\x9F", + "\x9B\xC7" => "\xEC\x8E\xA0", + "\x9B\xC8" => "\xEC\x8E\xA1", + "\x9B\xC9" => "\xEC\x8E\xA2", + "\x9B\xCA" => "\xEC\x8E\xA3", + "\x9B\xCB" => "\xEC\x8E\xA4", + "\x9B\xCC" => "\xEC\x8E\xA5", + "\x9B\xCD" => "\xEC\x8E\xA6", + "\x9B\xCE" => "\xEC\x8E\xA7", + "\x9B\xCF" => "\xEC\x8E\xA8", + "\x9B\xD0" => "\xEC\x8E\xA9", + "\x9B\xD1" => "\xEC\x8E\xAA", + "\x9B\xD2" => "\xEC\x8E\xAB", + "\x9B\xD3" => "\xEC\x8E\xAC", + "\x9B\xD4" => "\xEC\x8E\xAD", + "\x9B\xD5" => "\xEC\x8E\xAE", + "\x9B\xD6" => "\xEC\x8E\xAF", + "\x9B\xD7" => "\xEC\x8E\xB0", + "\x9B\xD8" => "\xEC\x8E\xB1", + "\x9B\xD9" => "\xEC\x8E\xB2", + "\x9B\xDA" => "\xEC\x8E\xB3", + "\x9B\xDB" => "\xEC\x8E\xB4", + "\x9B\xDC" => "\xEC\x8E\xB5", + "\x9B\xDD" => "\xEC\x8E\xB6", + "\x9B\xDE" => "\xEC\x8E\xB7", + "\x9B\xDF" => "\xEC\x8E\xB8", + "\x9B\xE0" => "\xEC\x8E\xB9", + "\x9B\xE1" => "\xEC\x8E\xBA", + "\x9B\xE2" => "\xEC\x8E\xBB", + "\x9B\xE3" => "\xEC\x8E\xBC", + "\x9B\xE4" => "\xEC\x8E\xBD", + "\x9B\xE5" => "\xEC\x8E\xBE", + "\x9B\xE6" => "\xEC\x8E\xBF", + "\x9B\xE7" => "\xEC\x8F\x81", + "\x9B\xE8" => "\xEC\x8F\x82", + "\x9B\xE9" => "\xEC\x8F\x83", + "\x9B\xEA" => "\xEC\x8F\x84", + "\x9B\xEB" => "\xEC\x8F\x85", + "\x9B\xEC" => "\xEC\x8F\x86", + "\x9B\xED" => "\xEC\x8F\x87", + "\x9B\xEE" => "\xEC\x8F\x88", + "\x9B\xEF" => "\xEC\x8F\x89", + "\x9B\xF0" => "\xEC\x8F\x8A", + "\x9B\xF1" => "\xEC\x8F\x8B", + "\x9B\xF2" => "\xEC\x8F\x8C", + "\x9B\xF3" => "\xEC\x8F\x8D", + "\x9B\xF4" => "\xEC\x8F\x8E", + "\x9B\xF5" => "\xEC\x8F\x8F", + "\x9B\xF6" => "\xEC\x8F\x90", + "\x9B\xF7" => "\xEC\x8F\x91", + "\x9B\xF8" => "\xEC\x8F\x92", + "\x9B\xF9" => "\xEC\x8F\x93", + "\x9B\xFA" => "\xEC\x8F\x94", + "\x9B\xFB" => "\xEC\x8F\x95", + "\x9B\xFC" => "\xEC\x8F\x96", + "\x9B\xFD" => "\xEC\x8F\x97", + "\x9B\xFE" => "\xEC\x8F\x9A", + "\x9C\x41" => "\xEC\x8F\x9B", + "\x9C\x42" => "\xEC\x8F\x9D", + "\x9C\x43" => "\xEC\x8F\x9E", + "\x9C\x44" => "\xEC\x8F\xA1", + "\x9C\x45" => "\xEC\x8F\xA3", + "\x9C\x46" => "\xEC\x8F\xA4", + "\x9C\x47" => "\xEC\x8F\xA5", + "\x9C\x48" => "\xEC\x8F\xA6", + "\x9C\x49" => "\xEC\x8F\xA7", + "\x9C\x4A" => "\xEC\x8F\xAA", + "\x9C\x4B" => "\xEC\x8F\xAB", + "\x9C\x4C" => "\xEC\x8F\xAC", + "\x9C\x4D" => "\xEC\x8F\xAE", + "\x9C\x4E" => "\xEC\x8F\xAF", + "\x9C\x4F" => "\xEC\x8F\xB0", + "\x9C\x50" => "\xEC\x8F\xB1", + "\x9C\x51" => "\xEC\x8F\xB2", + "\x9C\x52" => "\xEC\x8F\xB3", + "\x9C\x53" => "\xEC\x8F\xB6", + "\x9C\x54" => "\xEC\x8F\xB7", + "\x9C\x55" => "\xEC\x8F\xB9", + "\x9C\x56" => "\xEC\x8F\xBA", + "\x9C\x57" => "\xEC\x8F\xBB", + "\x9C\x58" => "\xEC\x8F\xBC", + "\x9C\x59" => "\xEC\x8F\xBD", + "\x9C\x5A" => "\xEC\x8F\xBE", + "\x9C\x61" => "\xEC\x8F\xBF", + "\x9C\x62" => "\xEC\x90\x80", + "\x9C\x63" => "\xEC\x90\x81", + "\x9C\x64" => "\xEC\x90\x82", + "\x9C\x65" => "\xEC\x90\x83", + "\x9C\x66" => "\xEC\x90\x84", + "\x9C\x67" => "\xEC\x90\x85", + "\x9C\x68" => "\xEC\x90\x86", + "\x9C\x69" => "\xEC\x90\x87", + "\x9C\x6A" => "\xEC\x90\x89", + "\x9C\x6B" => "\xEC\x90\x8A", + "\x9C\x6C" => "\xEC\x90\x8B", + "\x9C\x6D" => "\xEC\x90\x8C", + "\x9C\x6E" => "\xEC\x90\x8D", + "\x9C\x6F" => "\xEC\x90\x8E", + "\x9C\x70" => "\xEC\x90\x8F", + "\x9C\x71" => "\xEC\x90\x91", + "\x9C\x72" => "\xEC\x90\x92", + "\x9C\x73" => "\xEC\x90\x93", + "\x9C\x74" => "\xEC\x90\x94", + "\x9C\x75" => "\xEC\x90\x95", + "\x9C\x76" => "\xEC\x90\x96", + "\x9C\x77" => "\xEC\x90\x97", + "\x9C\x78" => "\xEC\x90\x98", + "\x9C\x79" => "\xEC\x90\x99", + "\x9C\x7A" => "\xEC\x90\x9A", + "\x9C\x81" => "\xEC\x90\x9B", + "\x9C\x82" => "\xEC\x90\x9C", + "\x9C\x83" => "\xEC\x90\x9D", + "\x9C\x84" => "\xEC\x90\x9E", + "\x9C\x85" => "\xEC\x90\x9F", + "\x9C\x86" => "\xEC\x90\xA0", + "\x9C\x87" => "\xEC\x90\xA1", + "\x9C\x88" => "\xEC\x90\xA2", + "\x9C\x89" => "\xEC\x90\xA3", + "\x9C\x8A" => "\xEC\x90\xA5", + "\x9C\x8B" => "\xEC\x90\xA6", + "\x9C\x8C" => "\xEC\x90\xA7", + "\x9C\x8D" => "\xEC\x90\xA8", + "\x9C\x8E" => "\xEC\x90\xA9", + "\x9C\x8F" => "\xEC\x90\xAA", + "\x9C\x90" => "\xEC\x90\xAB", + "\x9C\x91" => "\xEC\x90\xAD", + "\x9C\x92" => "\xEC\x90\xAE", + "\x9C\x93" => "\xEC\x90\xAF", + "\x9C\x94" => "\xEC\x90\xB1", + "\x9C\x95" => "\xEC\x90\xB2", + "\x9C\x96" => "\xEC\x90\xB3", + "\x9C\x97" => "\xEC\x90\xB5", + "\x9C\x98" => "\xEC\x90\xB6", + "\x9C\x99" => "\xEC\x90\xB7", + "\x9C\x9A" => "\xEC\x90\xB8", + "\x9C\x9B" => "\xEC\x90\xB9", + "\x9C\x9C" => "\xEC\x90\xBA", + "\x9C\x9D" => "\xEC\x90\xBB", + "\x9C\x9E" => "\xEC\x90\xBE", + "\x9C\x9F" => "\xEC\x90\xBF", + "\x9C\xA0" => "\xEC\x91\x80", + "\x9C\xA1" => "\xEC\x91\x81", + "\x9C\xA2" => "\xEC\x91\x82", + "\x9C\xA3" => "\xEC\x91\x83", + "\x9C\xA4" => "\xEC\x91\x84", + "\x9C\xA5" => "\xEC\x91\x85", + "\x9C\xA6" => "\xEC\x91\x86", + "\x9C\xA7" => "\xEC\x91\x87", + "\x9C\xA8" => "\xEC\x91\x89", + "\x9C\xA9" => "\xEC\x91\x8A", + "\x9C\xAA" => "\xEC\x91\x8B", + "\x9C\xAB" => "\xEC\x91\x8C", + "\x9C\xAC" => "\xEC\x91\x8D", + "\x9C\xAD" => "\xEC\x91\x8E", + "\x9C\xAE" => "\xEC\x91\x8F", + "\x9C\xAF" => "\xEC\x91\x90", + "\x9C\xB0" => "\xEC\x91\x91", + "\x9C\xB1" => "\xEC\x91\x92", + "\x9C\xB2" => "\xEC\x91\x93", + "\x9C\xB3" => "\xEC\x91\x94", + "\x9C\xB4" => "\xEC\x91\x95", + "\x9C\xB5" => "\xEC\x91\x96", + "\x9C\xB6" => "\xEC\x91\x97", + "\x9C\xB7" => "\xEC\x91\x98", + "\x9C\xB8" => "\xEC\x91\x99", + "\x9C\xB9" => "\xEC\x91\x9A", + "\x9C\xBA" => "\xEC\x91\x9B", + "\x9C\xBB" => "\xEC\x91\x9C", + "\x9C\xBC" => "\xEC\x91\x9D", + "\x9C\xBD" => "\xEC\x91\x9E", + "\x9C\xBE" => "\xEC\x91\x9F", + "\x9C\xBF" => "\xEC\x91\xA0", + "\x9C\xC0" => "\xEC\x91\xA1", + "\x9C\xC1" => "\xEC\x91\xA2", + "\x9C\xC2" => "\xEC\x91\xA3", + "\x9C\xC3" => "\xEC\x91\xA6", + "\x9C\xC4" => "\xEC\x91\xA7", + "\x9C\xC5" => "\xEC\x91\xA9", + "\x9C\xC6" => "\xEC\x91\xAA", + "\x9C\xC7" => "\xEC\x91\xAB", + "\x9C\xC8" => "\xEC\x91\xAD", + "\x9C\xC9" => "\xEC\x91\xAE", + "\x9C\xCA" => "\xEC\x91\xAF", + "\x9C\xCB" => "\xEC\x91\xB0", + "\x9C\xCC" => "\xEC\x91\xB1", + "\x9C\xCD" => "\xEC\x91\xB2", + "\x9C\xCE" => "\xEC\x91\xB3", + "\x9C\xCF" => "\xEC\x91\xB6", + "\x9C\xD0" => "\xEC\x91\xB7", + "\x9C\xD1" => "\xEC\x91\xB8", + "\x9C\xD2" => "\xEC\x91\xBA", + "\x9C\xD3" => "\xEC\x91\xBB", + "\x9C\xD4" => "\xEC\x91\xBC", + "\x9C\xD5" => "\xEC\x91\xBD", + "\x9C\xD6" => "\xEC\x91\xBE", + "\x9C\xD7" => "\xEC\x91\xBF", + "\x9C\xD8" => "\xEC\x92\x81", + "\x9C\xD9" => "\xEC\x92\x82", + "\x9C\xDA" => "\xEC\x92\x83", + "\x9C\xDB" => "\xEC\x92\x84", + "\x9C\xDC" => "\xEC\x92\x85", + "\x9C\xDD" => "\xEC\x92\x86", + "\x9C\xDE" => "\xEC\x92\x87", + "\x9C\xDF" => "\xEC\x92\x88", + "\x9C\xE0" => "\xEC\x92\x89", + "\x9C\xE1" => "\xEC\x92\x8A", + "\x9C\xE2" => "\xEC\x92\x8B", + "\x9C\xE3" => "\xEC\x92\x8C", + "\x9C\xE4" => "\xEC\x92\x8D", + "\x9C\xE5" => "\xEC\x92\x8E", + "\x9C\xE6" => "\xEC\x92\x8F", + "\x9C\xE7" => "\xEC\x92\x90", + "\x9C\xE8" => "\xEC\x92\x91", + "\x9C\xE9" => "\xEC\x92\x92", + "\x9C\xEA" => "\xEC\x92\x93", + "\x9C\xEB" => "\xEC\x92\x95", + "\x9C\xEC" => "\xEC\x92\x96", + "\x9C\xED" => "\xEC\x92\x97", + "\x9C\xEE" => "\xEC\x92\x98", + "\x9C\xEF" => "\xEC\x92\x99", + "\x9C\xF0" => "\xEC\x92\x9A", + "\x9C\xF1" => "\xEC\x92\x9B", + "\x9C\xF2" => "\xEC\x92\x9D", + "\x9C\xF3" => "\xEC\x92\x9E", + "\x9C\xF4" => "\xEC\x92\x9F", + "\x9C\xF5" => "\xEC\x92\xA0", + "\x9C\xF6" => "\xEC\x92\xA1", + "\x9C\xF7" => "\xEC\x92\xA2", + "\x9C\xF8" => "\xEC\x92\xA3", + "\x9C\xF9" => "\xEC\x92\xA4", + "\x9C\xFA" => "\xEC\x92\xA5", + "\x9C\xFB" => "\xEC\x92\xA6", + "\x9C\xFC" => "\xEC\x92\xA7", + "\x9C\xFD" => "\xEC\x92\xA8", + "\x9C\xFE" => "\xEC\x92\xA9", + "\x9D\x41" => "\xEC\x92\xAA", + "\x9D\x42" => "\xEC\x92\xAB", + "\x9D\x43" => "\xEC\x92\xAC", + "\x9D\x44" => "\xEC\x92\xAD", + "\x9D\x45" => "\xEC\x92\xAE", + "\x9D\x46" => "\xEC\x92\xAF", + "\x9D\x47" => "\xEC\x92\xB0", + "\x9D\x48" => "\xEC\x92\xB1", + "\x9D\x49" => "\xEC\x92\xB2", + "\x9D\x4A" => "\xEC\x92\xB3", + "\x9D\x4B" => "\xEC\x92\xB4", + "\x9D\x4C" => "\xEC\x92\xB5", + "\x9D\x4D" => "\xEC\x92\xB6", + "\x9D\x4E" => "\xEC\x92\xB7", + "\x9D\x4F" => "\xEC\x92\xB9", + "\x9D\x50" => "\xEC\x92\xBA", + "\x9D\x51" => "\xEC\x92\xBB", + "\x9D\x52" => "\xEC\x92\xBD", + "\x9D\x53" => "\xEC\x92\xBE", + "\x9D\x54" => "\xEC\x92\xBF", + "\x9D\x55" => "\xEC\x93\x80", + "\x9D\x56" => "\xEC\x93\x81", + "\x9D\x57" => "\xEC\x93\x82", + "\x9D\x58" => "\xEC\x93\x83", + "\x9D\x59" => "\xEC\x93\x84", + "\x9D\x5A" => "\xEC\x93\x85", + "\x9D\x61" => "\xEC\x93\x86", + "\x9D\x62" => "\xEC\x93\x87", + "\x9D\x63" => "\xEC\x93\x88", + "\x9D\x64" => "\xEC\x93\x89", + "\x9D\x65" => "\xEC\x93\x8A", + "\x9D\x66" => "\xEC\x93\x8B", + "\x9D\x67" => "\xEC\x93\x8C", + "\x9D\x68" => "\xEC\x93\x8D", + "\x9D\x69" => "\xEC\x93\x8E", + "\x9D\x6A" => "\xEC\x93\x8F", + "\x9D\x6B" => "\xEC\x93\x90", + "\x9D\x6C" => "\xEC\x93\x91", + "\x9D\x6D" => "\xEC\x93\x92", + "\x9D\x6E" => "\xEC\x93\x93", + "\x9D\x6F" => "\xEC\x93\x94", + "\x9D\x70" => "\xEC\x93\x95", + "\x9D\x71" => "\xEC\x93\x96", + "\x9D\x72" => "\xEC\x93\x97", + "\x9D\x73" => "\xEC\x93\x98", + "\x9D\x74" => "\xEC\x93\x99", + "\x9D\x75" => "\xEC\x93\x9A", + "\x9D\x76" => "\xEC\x93\x9B", + "\x9D\x77" => "\xEC\x93\x9C", + "\x9D\x78" => "\xEC\x93\x9D", + "\x9D\x79" => "\xEC\x93\x9E", + "\x9D\x7A" => "\xEC\x93\x9F", + "\x9D\x81" => "\xEC\x93\xA0", + "\x9D\x82" => "\xEC\x93\xA1", + "\x9D\x83" => "\xEC\x93\xA2", + "\x9D\x84" => "\xEC\x93\xA3", + "\x9D\x85" => "\xEC\x93\xA4", + "\x9D\x86" => "\xEC\x93\xA5", + "\x9D\x87" => "\xEC\x93\xA6", + "\x9D\x88" => "\xEC\x93\xA7", + "\x9D\x89" => "\xEC\x93\xA8", + "\x9D\x8A" => "\xEC\x93\xAA", + "\x9D\x8B" => "\xEC\x93\xAB", + "\x9D\x8C" => "\xEC\x93\xAC", + "\x9D\x8D" => "\xEC\x93\xAD", + "\x9D\x8E" => "\xEC\x93\xAE", + "\x9D\x8F" => "\xEC\x93\xAF", + "\x9D\x90" => "\xEC\x93\xB2", + "\x9D\x91" => "\xEC\x93\xB3", + "\x9D\x92" => "\xEC\x93\xB5", + "\x9D\x93" => "\xEC\x93\xB6", + "\x9D\x94" => "\xEC\x93\xB7", + "\x9D\x95" => "\xEC\x93\xB9", + "\x9D\x96" => "\xEC\x93\xBB", + "\x9D\x97" => "\xEC\x93\xBC", + "\x9D\x98" => "\xEC\x93\xBD", + "\x9D\x99" => "\xEC\x93\xBE", + "\x9D\x9A" => "\xEC\x94\x82", + "\x9D\x9B" => "\xEC\x94\x83", + "\x9D\x9C" => "\xEC\x94\x84", + "\x9D\x9D" => "\xEC\x94\x85", + "\x9D\x9E" => "\xEC\x94\x86", + "\x9D\x9F" => "\xEC\x94\x87", + "\x9D\xA0" => "\xEC\x94\x88", + "\x9D\xA1" => "\xEC\x94\x89", + "\x9D\xA2" => "\xEC\x94\x8A", + "\x9D\xA3" => "\xEC\x94\x8B", + "\x9D\xA4" => "\xEC\x94\x8D", + "\x9D\xA5" => "\xEC\x94\x8E", + "\x9D\xA6" => "\xEC\x94\x8F", + "\x9D\xA7" => "\xEC\x94\x91", + "\x9D\xA8" => "\xEC\x94\x92", + "\x9D\xA9" => "\xEC\x94\x93", + "\x9D\xAA" => "\xEC\x94\x95", + "\x9D\xAB" => "\xEC\x94\x96", + "\x9D\xAC" => "\xEC\x94\x97", + "\x9D\xAD" => "\xEC\x94\x98", + "\x9D\xAE" => "\xEC\x94\x99", + "\x9D\xAF" => "\xEC\x94\x9A", + "\x9D\xB0" => "\xEC\x94\x9B", + "\x9D\xB1" => "\xEC\x94\x9D", + "\x9D\xB2" => "\xEC\x94\x9E", + "\x9D\xB3" => "\xEC\x94\x9F", + "\x9D\xB4" => "\xEC\x94\xA0", + "\x9D\xB5" => "\xEC\x94\xA1", + "\x9D\xB6" => "\xEC\x94\xA2", + "\x9D\xB7" => "\xEC\x94\xA3", + "\x9D\xB8" => "\xEC\x94\xA4", + "\x9D\xB9" => "\xEC\x94\xA5", + "\x9D\xBA" => "\xEC\x94\xA6", + "\x9D\xBB" => "\xEC\x94\xA7", + "\x9D\xBC" => "\xEC\x94\xAA", + "\x9D\xBD" => "\xEC\x94\xAB", + "\x9D\xBE" => "\xEC\x94\xAD", + "\x9D\xBF" => "\xEC\x94\xAE", + "\x9D\xC0" => "\xEC\x94\xAF", + "\x9D\xC1" => "\xEC\x94\xB1", + "\x9D\xC2" => "\xEC\x94\xB2", + "\x9D\xC3" => "\xEC\x94\xB3", + "\x9D\xC4" => "\xEC\x94\xB4", + "\x9D\xC5" => "\xEC\x94\xB5", + "\x9D\xC6" => "\xEC\x94\xB6", + "\x9D\xC7" => "\xEC\x94\xB7", + "\x9D\xC8" => "\xEC\x94\xBA", + "\x9D\xC9" => "\xEC\x94\xBC", + "\x9D\xCA" => "\xEC\x94\xBE", + "\x9D\xCB" => "\xEC\x94\xBF", + "\x9D\xCC" => "\xEC\x95\x80", + "\x9D\xCD" => "\xEC\x95\x81", + "\x9D\xCE" => "\xEC\x95\x82", + "\x9D\xCF" => "\xEC\x95\x83", + "\x9D\xD0" => "\xEC\x95\x86", + "\x9D\xD1" => "\xEC\x95\x87", + "\x9D\xD2" => "\xEC\x95\x8B", + "\x9D\xD3" => "\xEC\x95\x8F", + "\x9D\xD4" => "\xEC\x95\x90", + "\x9D\xD5" => "\xEC\x95\x91", + "\x9D\xD6" => "\xEC\x95\x92", + "\x9D\xD7" => "\xEC\x95\x96", + "\x9D\xD8" => "\xEC\x95\x9A", + "\x9D\xD9" => "\xEC\x95\x9B", + "\x9D\xDA" => "\xEC\x95\x9C", + "\x9D\xDB" => "\xEC\x95\x9F", + "\x9D\xDC" => "\xEC\x95\xA2", + "\x9D\xDD" => "\xEC\x95\xA3", + "\x9D\xDE" => "\xEC\x95\xA5", + "\x9D\xDF" => "\xEC\x95\xA6", + "\x9D\xE0" => "\xEC\x95\xA7", + "\x9D\xE1" => "\xEC\x95\xA9", + "\x9D\xE2" => "\xEC\x95\xAA", + "\x9D\xE3" => "\xEC\x95\xAB", + "\x9D\xE4" => "\xEC\x95\xAC", + "\x9D\xE5" => "\xEC\x95\xAD", + "\x9D\xE6" => "\xEC\x95\xAE", + "\x9D\xE7" => "\xEC\x95\xAF", + "\x9D\xE8" => "\xEC\x95\xB2", + "\x9D\xE9" => "\xEC\x95\xB6", + "\x9D\xEA" => "\xEC\x95\xB7", + "\x9D\xEB" => "\xEC\x95\xB8", + "\x9D\xEC" => "\xEC\x95\xB9", + "\x9D\xED" => "\xEC\x95\xBA", + "\x9D\xEE" => "\xEC\x95\xBB", + "\x9D\xEF" => "\xEC\x95\xBE", + "\x9D\xF0" => "\xEC\x95\xBF", + "\x9D\xF1" => "\xEC\x96\x81", + "\x9D\xF2" => "\xEC\x96\x82", + "\x9D\xF3" => "\xEC\x96\x83", + "\x9D\xF4" => "\xEC\x96\x85", + "\x9D\xF5" => "\xEC\x96\x86", + "\x9D\xF6" => "\xEC\x96\x88", + "\x9D\xF7" => "\xEC\x96\x89", + "\x9D\xF8" => "\xEC\x96\x8A", + "\x9D\xF9" => "\xEC\x96\x8B", + "\x9D\xFA" => "\xEC\x96\x8E", + "\x9D\xFB" => "\xEC\x96\x90", + "\x9D\xFC" => "\xEC\x96\x92", + "\x9D\xFD" => "\xEC\x96\x93", + "\x9D\xFE" => "\xEC\x96\x94", + "\x9E\x41" => "\xEC\x96\x96", + "\x9E\x42" => "\xEC\x96\x99", + "\x9E\x43" => "\xEC\x96\x9A", + "\x9E\x44" => "\xEC\x96\x9B", + "\x9E\x45" => "\xEC\x96\x9D", + "\x9E\x46" => "\xEC\x96\x9E", + "\x9E\x47" => "\xEC\x96\x9F", + "\x9E\x48" => "\xEC\x96\xA1", + "\x9E\x49" => "\xEC\x96\xA2", + "\x9E\x4A" => "\xEC\x96\xA3", + "\x9E\x4B" => "\xEC\x96\xA4", + "\x9E\x4C" => "\xEC\x96\xA5", + "\x9E\x4D" => "\xEC\x96\xA6", + "\x9E\x4E" => "\xEC\x96\xA7", + "\x9E\x4F" => "\xEC\x96\xA8", + "\x9E\x50" => "\xEC\x96\xAA", + "\x9E\x51" => "\xEC\x96\xAB", + "\x9E\x52" => "\xEC\x96\xAC", + "\x9E\x53" => "\xEC\x96\xAD", + "\x9E\x54" => "\xEC\x96\xAE", + "\x9E\x55" => "\xEC\x96\xAF", + "\x9E\x56" => "\xEC\x96\xB0", + "\x9E\x57" => "\xEC\x96\xB1", + "\x9E\x58" => "\xEC\x96\xB2", + "\x9E\x59" => "\xEC\x96\xB3", + "\x9E\x5A" => "\xEC\x96\xB6", + "\x9E\x61" => "\xEC\x96\xB7", + "\x9E\x62" => "\xEC\x96\xBA", + "\x9E\x63" => "\xEC\x96\xBF", + "\x9E\x64" => "\xEC\x97\x80", + "\x9E\x65" => "\xEC\x97\x81", + "\x9E\x66" => "\xEC\x97\x82", + "\x9E\x67" => "\xEC\x97\x83", + "\x9E\x68" => "\xEC\x97\x8B", + "\x9E\x69" => "\xEC\x97\x8D", + "\x9E\x6A" => "\xEC\x97\x8F", + "\x9E\x6B" => "\xEC\x97\x92", + "\x9E\x6C" => "\xEC\x97\x93", + "\x9E\x6D" => "\xEC\x97\x95", + "\x9E\x6E" => "\xEC\x97\x96", + "\x9E\x6F" => "\xEC\x97\x97", + "\x9E\x70" => "\xEC\x97\x99", + "\x9E\x71" => "\xEC\x97\x9A", + "\x9E\x72" => "\xEC\x97\x9B", + "\x9E\x73" => "\xEC\x97\x9C", + "\x9E\x74" => "\xEC\x97\x9D", + "\x9E\x75" => "\xEC\x97\x9E", + "\x9E\x76" => "\xEC\x97\x9F", + "\x9E\x77" => "\xEC\x97\xA2", + "\x9E\x78" => "\xEC\x97\xA4", + "\x9E\x79" => "\xEC\x97\xA6", + "\x9E\x7A" => "\xEC\x97\xA7", + "\x9E\x81" => "\xEC\x97\xA8", + "\x9E\x82" => "\xEC\x97\xA9", + "\x9E\x83" => "\xEC\x97\xAA", + "\x9E\x84" => "\xEC\x97\xAB", + "\x9E\x85" => "\xEC\x97\xAF", + "\x9E\x86" => "\xEC\x97\xB1", + "\x9E\x87" => "\xEC\x97\xB2", + "\x9E\x88" => "\xEC\x97\xB3", + "\x9E\x89" => "\xEC\x97\xB5", + "\x9E\x8A" => "\xEC\x97\xB8", + "\x9E\x8B" => "\xEC\x97\xB9", + "\x9E\x8C" => "\xEC\x97\xBA", + "\x9E\x8D" => "\xEC\x97\xBB", + "\x9E\x8E" => "\xEC\x98\x82", + "\x9E\x8F" => "\xEC\x98\x83", + "\x9E\x90" => "\xEC\x98\x84", + "\x9E\x91" => "\xEC\x98\x89", + "\x9E\x92" => "\xEC\x98\x8A", + "\x9E\x93" => "\xEC\x98\x8B", + "\x9E\x94" => "\xEC\x98\x8D", + "\x9E\x95" => "\xEC\x98\x8E", + "\x9E\x96" => "\xEC\x98\x8F", + "\x9E\x97" => "\xEC\x98\x91", + "\x9E\x98" => "\xEC\x98\x92", + "\x9E\x99" => "\xEC\x98\x93", + "\x9E\x9A" => "\xEC\x98\x94", + "\x9E\x9B" => "\xEC\x98\x95", + "\x9E\x9C" => "\xEC\x98\x96", + "\x9E\x9D" => "\xEC\x98\x97", + "\x9E\x9E" => "\xEC\x98\x9A", + "\x9E\x9F" => "\xEC\x98\x9D", + "\x9E\xA0" => "\xEC\x98\x9E", + "\x9E\xA1" => "\xEC\x98\x9F", + "\x9E\xA2" => "\xEC\x98\xA0", + "\x9E\xA3" => "\xEC\x98\xA1", + "\x9E\xA4" => "\xEC\x98\xA2", + "\x9E\xA5" => "\xEC\x98\xA3", + "\x9E\xA6" => "\xEC\x98\xA6", + "\x9E\xA7" => "\xEC\x98\xA7", + "\x9E\xA8" => "\xEC\x98\xA9", + "\x9E\xA9" => "\xEC\x98\xAA", + "\x9E\xAA" => "\xEC\x98\xAB", + "\x9E\xAB" => "\xEC\x98\xAF", + "\x9E\xAC" => "\xEC\x98\xB1", + "\x9E\xAD" => "\xEC\x98\xB2", + "\x9E\xAE" => "\xEC\x98\xB6", + "\x9E\xAF" => "\xEC\x98\xB8", + "\x9E\xB0" => "\xEC\x98\xBA", + "\x9E\xB1" => "\xEC\x98\xBC", + "\x9E\xB2" => "\xEC\x98\xBD", + "\x9E\xB3" => "\xEC\x98\xBE", + "\x9E\xB4" => "\xEC\x98\xBF", + "\x9E\xB5" => "\xEC\x99\x82", + "\x9E\xB6" => "\xEC\x99\x83", + "\x9E\xB7" => "\xEC\x99\x85", + "\x9E\xB8" => "\xEC\x99\x86", + "\x9E\xB9" => "\xEC\x99\x87", + "\x9E\xBA" => "\xEC\x99\x89", + "\x9E\xBB" => "\xEC\x99\x8A", + "\x9E\xBC" => "\xEC\x99\x8B", + "\x9E\xBD" => "\xEC\x99\x8C", + "\x9E\xBE" => "\xEC\x99\x8D", + "\x9E\xBF" => "\xEC\x99\x8E", + "\x9E\xC0" => "\xEC\x99\x8F", + "\x9E\xC1" => "\xEC\x99\x92", + "\x9E\xC2" => "\xEC\x99\x96", + "\x9E\xC3" => "\xEC\x99\x97", + "\x9E\xC4" => "\xEC\x99\x98", + "\x9E\xC5" => "\xEC\x99\x99", + "\x9E\xC6" => "\xEC\x99\x9A", + "\x9E\xC7" => "\xEC\x99\x9B", + "\x9E\xC8" => "\xEC\x99\x9E", + "\x9E\xC9" => "\xEC\x99\x9F", + "\x9E\xCA" => "\xEC\x99\xA1", + "\x9E\xCB" => "\xEC\x99\xA2", + "\x9E\xCC" => "\xEC\x99\xA3", + "\x9E\xCD" => "\xEC\x99\xA4", + "\x9E\xCE" => "\xEC\x99\xA5", + "\x9E\xCF" => "\xEC\x99\xA6", + "\x9E\xD0" => "\xEC\x99\xA7", + "\x9E\xD1" => "\xEC\x99\xA8", + "\x9E\xD2" => "\xEC\x99\xA9", + "\x9E\xD3" => "\xEC\x99\xAA", + "\x9E\xD4" => "\xEC\x99\xAB", + "\x9E\xD5" => "\xEC\x99\xAD", + "\x9E\xD6" => "\xEC\x99\xAE", + "\x9E\xD7" => "\xEC\x99\xB0", + "\x9E\xD8" => "\xEC\x99\xB2", + "\x9E\xD9" => "\xEC\x99\xB3", + "\x9E\xDA" => "\xEC\x99\xB4", + "\x9E\xDB" => "\xEC\x99\xB5", + "\x9E\xDC" => "\xEC\x99\xB6", + "\x9E\xDD" => "\xEC\x99\xB7", + "\x9E\xDE" => "\xEC\x99\xBA", + "\x9E\xDF" => "\xEC\x99\xBB", + "\x9E\xE0" => "\xEC\x99\xBD", + "\x9E\xE1" => "\xEC\x99\xBE", + "\x9E\xE2" => "\xEC\x99\xBF", + "\x9E\xE3" => "\xEC\x9A\x81", + "\x9E\xE4" => "\xEC\x9A\x82", + "\x9E\xE5" => "\xEC\x9A\x83", + "\x9E\xE6" => "\xEC\x9A\x84", + "\x9E\xE7" => "\xEC\x9A\x85", + "\x9E\xE8" => "\xEC\x9A\x86", + "\x9E\xE9" => "\xEC\x9A\x87", + "\x9E\xEA" => "\xEC\x9A\x8A", + "\x9E\xEB" => "\xEC\x9A\x8C", + "\x9E\xEC" => "\xEC\x9A\x8E", + "\x9E\xED" => "\xEC\x9A\x8F", + "\x9E\xEE" => "\xEC\x9A\x90", + "\x9E\xEF" => "\xEC\x9A\x91", + "\x9E\xF0" => "\xEC\x9A\x92", + "\x9E\xF1" => "\xEC\x9A\x93", + "\x9E\xF2" => "\xEC\x9A\x96", + "\x9E\xF3" => "\xEC\x9A\x97", + "\x9E\xF4" => "\xEC\x9A\x99", + "\x9E\xF5" => "\xEC\x9A\x9A", + "\x9E\xF6" => "\xEC\x9A\x9B", + "\x9E\xF7" => "\xEC\x9A\x9D", + "\x9E\xF8" => "\xEC\x9A\x9E", + "\x9E\xF9" => "\xEC\x9A\x9F", + "\x9E\xFA" => "\xEC\x9A\xA0", + "\x9E\xFB" => "\xEC\x9A\xA1", + "\x9E\xFC" => "\xEC\x9A\xA2", + "\x9E\xFD" => "\xEC\x9A\xA3", + "\x9E\xFE" => "\xEC\x9A\xA6", + "\x9F\x41" => "\xEC\x9A\xA8", + "\x9F\x42" => "\xEC\x9A\xAA", + "\x9F\x43" => "\xEC\x9A\xAB", + "\x9F\x44" => "\xEC\x9A\xAC", + "\x9F\x45" => "\xEC\x9A\xAD", + "\x9F\x46" => "\xEC\x9A\xAE", + "\x9F\x47" => "\xEC\x9A\xAF", + "\x9F\x48" => "\xEC\x9A\xB2", + "\x9F\x49" => "\xEC\x9A\xB3", + "\x9F\x4A" => "\xEC\x9A\xB5", + "\x9F\x4B" => "\xEC\x9A\xB6", + "\x9F\x4C" => "\xEC\x9A\xB7", + "\x9F\x4D" => "\xEC\x9A\xBB", + "\x9F\x4E" => "\xEC\x9A\xBC", + "\x9F\x4F" => "\xEC\x9A\xBD", + "\x9F\x50" => "\xEC\x9A\xBE", + "\x9F\x51" => "\xEC\x9A\xBF", + "\x9F\x52" => "\xEC\x9B\x82", + "\x9F\x53" => "\xEC\x9B\x84", + "\x9F\x54" => "\xEC\x9B\x86", + "\x9F\x55" => "\xEC\x9B\x87", + "\x9F\x56" => "\xEC\x9B\x88", + "\x9F\x57" => "\xEC\x9B\x89", + "\x9F\x58" => "\xEC\x9B\x8A", + "\x9F\x59" => "\xEC\x9B\x8B", + "\x9F\x5A" => "\xEC\x9B\x8E", + "\x9F\x61" => "\xEC\x9B\x8F", + "\x9F\x62" => "\xEC\x9B\x91", + "\x9F\x63" => "\xEC\x9B\x92", + "\x9F\x64" => "\xEC\x9B\x93", + "\x9F\x65" => "\xEC\x9B\x95", + "\x9F\x66" => "\xEC\x9B\x96", + "\x9F\x67" => "\xEC\x9B\x97", + "\x9F\x68" => "\xEC\x9B\x98", + "\x9F\x69" => "\xEC\x9B\x99", + "\x9F\x6A" => "\xEC\x9B\x9A", + "\x9F\x6B" => "\xEC\x9B\x9B", + "\x9F\x6C" => "\xEC\x9B\x9E", + "\x9F\x6D" => "\xEC\x9B\x9F", + "\x9F\x6E" => "\xEC\x9B\xA2", + "\x9F\x6F" => "\xEC\x9B\xA3", + "\x9F\x70" => "\xEC\x9B\xA4", + "\x9F\x71" => "\xEC\x9B\xA5", + "\x9F\x72" => "\xEC\x9B\xA6", + "\x9F\x73" => "\xEC\x9B\xA7", + "\x9F\x74" => "\xEC\x9B\xAA", + "\x9F\x75" => "\xEC\x9B\xAB", + "\x9F\x76" => "\xEC\x9B\xAD", + "\x9F\x77" => "\xEC\x9B\xAE", + "\x9F\x78" => "\xEC\x9B\xAF", + "\x9F\x79" => "\xEC\x9B\xB1", + "\x9F\x7A" => "\xEC\x9B\xB2", + "\x9F\x81" => "\xEC\x9B\xB3", + "\x9F\x82" => "\xEC\x9B\xB4", + "\x9F\x83" => "\xEC\x9B\xB5", + "\x9F\x84" => "\xEC\x9B\xB6", + "\x9F\x85" => "\xEC\x9B\xB7", + "\x9F\x86" => "\xEC\x9B\xBA", + "\x9F\x87" => "\xEC\x9B\xBB", + "\x9F\x88" => "\xEC\x9B\xBC", + "\x9F\x89" => "\xEC\x9B\xBE", + "\x9F\x8A" => "\xEC\x9B\xBF", + "\x9F\x8B" => "\xEC\x9C\x80", + "\x9F\x8C" => "\xEC\x9C\x81", + "\x9F\x8D" => "\xEC\x9C\x82", + "\x9F\x8E" => "\xEC\x9C\x83", + "\x9F\x8F" => "\xEC\x9C\x86", + "\x9F\x90" => "\xEC\x9C\x87", + "\x9F\x91" => "\xEC\x9C\x89", + "\x9F\x92" => "\xEC\x9C\x8A", + "\x9F\x93" => "\xEC\x9C\x8B", + "\x9F\x94" => "\xEC\x9C\x8D", + "\x9F\x95" => "\xEC\x9C\x8E", + "\x9F\x96" => "\xEC\x9C\x8F", + "\x9F\x97" => "\xEC\x9C\x90", + "\x9F\x98" => "\xEC\x9C\x91", + "\x9F\x99" => "\xEC\x9C\x92", + "\x9F\x9A" => "\xEC\x9C\x93", + "\x9F\x9B" => "\xEC\x9C\x96", + "\x9F\x9C" => "\xEC\x9C\x98", + "\x9F\x9D" => "\xEC\x9C\x9A", + "\x9F\x9E" => "\xEC\x9C\x9B", + "\x9F\x9F" => "\xEC\x9C\x9C", + "\x9F\xA0" => "\xEC\x9C\x9D", + "\x9F\xA1" => "\xEC\x9C\x9E", + "\x9F\xA2" => "\xEC\x9C\x9F", + "\x9F\xA3" => "\xEC\x9C\xA2", + "\x9F\xA4" => "\xEC\x9C\xA3", + "\x9F\xA5" => "\xEC\x9C\xA5", + "\x9F\xA6" => "\xEC\x9C\xA6", + "\x9F\xA7" => "\xEC\x9C\xA7", + "\x9F\xA8" => "\xEC\x9C\xA9", + "\x9F\xA9" => "\xEC\x9C\xAA", + "\x9F\xAA" => "\xEC\x9C\xAB", + "\x9F\xAB" => "\xEC\x9C\xAC", + "\x9F\xAC" => "\xEC\x9C\xAD", + "\x9F\xAD" => "\xEC\x9C\xAE", + "\x9F\xAE" => "\xEC\x9C\xAF", + "\x9F\xAF" => "\xEC\x9C\xB2", + "\x9F\xB0" => "\xEC\x9C\xB4", + "\x9F\xB1" => "\xEC\x9C\xB6", + "\x9F\xB2" => "\xEC\x9C\xB8", + "\x9F\xB3" => "\xEC\x9C\xB9", + "\x9F\xB4" => "\xEC\x9C\xBA", + "\x9F\xB5" => "\xEC\x9C\xBB", + "\x9F\xB6" => "\xEC\x9C\xBE", + "\x9F\xB7" => "\xEC\x9C\xBF", + "\x9F\xB8" => "\xEC\x9D\x81", + "\x9F\xB9" => "\xEC\x9D\x82", + "\x9F\xBA" => "\xEC\x9D\x83", + "\x9F\xBB" => "\xEC\x9D\x85", + "\x9F\xBC" => "\xEC\x9D\x86", + "\x9F\xBD" => "\xEC\x9D\x87", + "\x9F\xBE" => "\xEC\x9D\x88", + "\x9F\xBF" => "\xEC\x9D\x89", + "\x9F\xC0" => "\xEC\x9D\x8B", + "\x9F\xC1" => "\xEC\x9D\x8E", + "\x9F\xC2" => "\xEC\x9D\x90", + "\x9F\xC3" => "\xEC\x9D\x99", + "\x9F\xC4" => "\xEC\x9D\x9A", + "\x9F\xC5" => "\xEC\x9D\x9B", + "\x9F\xC6" => "\xEC\x9D\x9D", + "\x9F\xC7" => "\xEC\x9D\x9E", + "\x9F\xC8" => "\xEC\x9D\x9F", + "\x9F\xC9" => "\xEC\x9D\xA1", + "\x9F\xCA" => "\xEC\x9D\xA2", + "\x9F\xCB" => "\xEC\x9D\xA3", + "\x9F\xCC" => "\xEC\x9D\xA4", + "\x9F\xCD" => "\xEC\x9D\xA5", + "\x9F\xCE" => "\xEC\x9D\xA6", + "\x9F\xCF" => "\xEC\x9D\xA7", + "\x9F\xD0" => "\xEC\x9D\xA9", + "\x9F\xD1" => "\xEC\x9D\xAA", + "\x9F\xD2" => "\xEC\x9D\xAC", + "\x9F\xD3" => "\xEC\x9D\xAD", + "\x9F\xD4" => "\xEC\x9D\xAE", + "\x9F\xD5" => "\xEC\x9D\xAF", + "\x9F\xD6" => "\xEC\x9D\xB0", + "\x9F\xD7" => "\xEC\x9D\xB1", + "\x9F\xD8" => "\xEC\x9D\xB2", + "\x9F\xD9" => "\xEC\x9D\xB3", + "\x9F\xDA" => "\xEC\x9D\xB6", + "\x9F\xDB" => "\xEC\x9D\xB7", + "\x9F\xDC" => "\xEC\x9D\xB9", + "\x9F\xDD" => "\xEC\x9D\xBA", + "\x9F\xDE" => "\xEC\x9D\xBB", + "\x9F\xDF" => "\xEC\x9D\xBF", + "\x9F\xE0" => "\xEC\x9E\x80", + "\x9F\xE1" => "\xEC\x9E\x81", + "\x9F\xE2" => "\xEC\x9E\x82", + "\x9F\xE3" => "\xEC\x9E\x86", + "\x9F\xE4" => "\xEC\x9E\x8B", + "\x9F\xE5" => "\xEC\x9E\x8C", + "\x9F\xE6" => "\xEC\x9E\x8D", + "\x9F\xE7" => "\xEC\x9E\x8F", + "\x9F\xE8" => "\xEC\x9E\x92", + "\x9F\xE9" => "\xEC\x9E\x93", + "\x9F\xEA" => "\xEC\x9E\x95", + "\x9F\xEB" => "\xEC\x9E\x99", + "\x9F\xEC" => "\xEC\x9E\x9B", + "\x9F\xED" => "\xEC\x9E\x9C", + "\x9F\xEE" => "\xEC\x9E\x9D", + "\x9F\xEF" => "\xEC\x9E\x9E", + "\x9F\xF0" => "\xEC\x9E\x9F", + "\x9F\xF1" => "\xEC\x9E\xA2", + "\x9F\xF2" => "\xEC\x9E\xA7", + "\x9F\xF3" => "\xEC\x9E\xA8", + "\x9F\xF4" => "\xEC\x9E\xA9", + "\x9F\xF5" => "\xEC\x9E\xAA", + "\x9F\xF6" => "\xEC\x9E\xAB", + "\x9F\xF7" => "\xEC\x9E\xAE", + "\x9F\xF8" => "\xEC\x9E\xAF", + "\x9F\xF9" => "\xEC\x9E\xB1", + "\x9F\xFA" => "\xEC\x9E\xB2", + "\x9F\xFB" => "\xEC\x9E\xB3", + "\x9F\xFC" => "\xEC\x9E\xB5", + "\x9F\xFD" => "\xEC\x9E\xB6", + "\x9F\xFE" => "\xEC\x9E\xB7", + "\xA0\x41" => "\xEC\x9E\xB8", + "\xA0\x42" => "\xEC\x9E\xB9", + "\xA0\x43" => "\xEC\x9E\xBA", + "\xA0\x44" => "\xEC\x9E\xBB", + "\xA0\x45" => "\xEC\x9E\xBE", + "\xA0\x46" => "\xEC\x9F\x82", + "\xA0\x47" => "\xEC\x9F\x83", + "\xA0\x48" => "\xEC\x9F\x84", + "\xA0\x49" => "\xEC\x9F\x85", + "\xA0\x4A" => "\xEC\x9F\x86", + "\xA0\x4B" => "\xEC\x9F\x87", + "\xA0\x4C" => "\xEC\x9F\x8A", + "\xA0\x4D" => "\xEC\x9F\x8B", + "\xA0\x4E" => "\xEC\x9F\x8D", + "\xA0\x4F" => "\xEC\x9F\x8F", + "\xA0\x50" => "\xEC\x9F\x91", + "\xA0\x51" => "\xEC\x9F\x92", + "\xA0\x52" => "\xEC\x9F\x93", + "\xA0\x53" => "\xEC\x9F\x94", + "\xA0\x54" => "\xEC\x9F\x95", + "\xA0\x55" => "\xEC\x9F\x96", + "\xA0\x56" => "\xEC\x9F\x97", + "\xA0\x57" => "\xEC\x9F\x99", + "\xA0\x58" => "\xEC\x9F\x9A", + "\xA0\x59" => "\xEC\x9F\x9B", + "\xA0\x5A" => "\xEC\x9F\x9C", + "\xA0\x61" => "\xEC\x9F\x9E", + "\xA0\x62" => "\xEC\x9F\x9F", + "\xA0\x63" => "\xEC\x9F\xA0", + "\xA0\x64" => "\xEC\x9F\xA1", + "\xA0\x65" => "\xEC\x9F\xA2", + "\xA0\x66" => "\xEC\x9F\xA3", + "\xA0\x67" => "\xEC\x9F\xA5", + "\xA0\x68" => "\xEC\x9F\xA6", + "\xA0\x69" => "\xEC\x9F\xA7", + "\xA0\x6A" => "\xEC\x9F\xA9", + "\xA0\x6B" => "\xEC\x9F\xAA", + "\xA0\x6C" => "\xEC\x9F\xAB", + "\xA0\x6D" => "\xEC\x9F\xAD", + "\xA0\x6E" => "\xEC\x9F\xAE", + "\xA0\x6F" => "\xEC\x9F\xAF", + "\xA0\x70" => "\xEC\x9F\xB0", + "\xA0\x71" => "\xEC\x9F\xB1", + "\xA0\x72" => "\xEC\x9F\xB2", + "\xA0\x73" => "\xEC\x9F\xB3", + "\xA0\x74" => "\xEC\x9F\xB4", + "\xA0\x75" => "\xEC\x9F\xB5", + "\xA0\x76" => "\xEC\x9F\xB6", + "\xA0\x77" => "\xEC\x9F\xB7", + "\xA0\x78" => "\xEC\x9F\xB8", + "\xA0\x79" => "\xEC\x9F\xB9", + "\xA0\x7A" => "\xEC\x9F\xBA", + "\xA0\x81" => "\xEC\x9F\xBB", + "\xA0\x82" => "\xEC\x9F\xBC", + "\xA0\x83" => "\xEC\x9F\xBD", + "\xA0\x84" => "\xEC\x9F\xBE", + "\xA0\x85" => "\xEC\x9F\xBF", + "\xA0\x86" => "\xEC\xA0\x82", + "\xA0\x87" => "\xEC\xA0\x83", + "\xA0\x88" => "\xEC\xA0\x85", + "\xA0\x89" => "\xEC\xA0\x86", + "\xA0\x8A" => "\xEC\xA0\x87", + "\xA0\x8B" => "\xEC\xA0\x89", + "\xA0\x8C" => "\xEC\xA0\x8B", + "\xA0\x8D" => "\xEC\xA0\x8C", + "\xA0\x8E" => "\xEC\xA0\x8D", + "\xA0\x8F" => "\xEC\xA0\x8E", + "\xA0\x90" => "\xEC\xA0\x8F", + "\xA0\x91" => "\xEC\xA0\x92", + "\xA0\x92" => "\xEC\xA0\x94", + "\xA0\x93" => "\xEC\xA0\x97", + "\xA0\x94" => "\xEC\xA0\x98", + "\xA0\x95" => "\xEC\xA0\x99", + "\xA0\x96" => "\xEC\xA0\x9A", + "\xA0\x97" => "\xEC\xA0\x9B", + "\xA0\x98" => "\xEC\xA0\x9E", + "\xA0\x99" => "\xEC\xA0\x9F", + "\xA0\x9A" => "\xEC\xA0\xA1", + "\xA0\x9B" => "\xEC\xA0\xA2", + "\xA0\x9C" => "\xEC\xA0\xA3", + "\xA0\x9D" => "\xEC\xA0\xA5", + "\xA0\x9E" => "\xEC\xA0\xA6", + "\xA0\x9F" => "\xEC\xA0\xA7", + "\xA0\xA0" => "\xEC\xA0\xA8", + "\xA0\xA1" => "\xEC\xA0\xA9", + "\xA0\xA2" => "\xEC\xA0\xAA", + "\xA0\xA3" => "\xEC\xA0\xAB", + "\xA0\xA4" => "\xEC\xA0\xAE", + "\xA0\xA5" => "\xEC\xA0\xB0", + "\xA0\xA6" => "\xEC\xA0\xB2", + "\xA0\xA7" => "\xEC\xA0\xB3", + "\xA0\xA8" => "\xEC\xA0\xB4", + "\xA0\xA9" => "\xEC\xA0\xB5", + "\xA0\xAA" => "\xEC\xA0\xB6", + "\xA0\xAB" => "\xEC\xA0\xB7", + "\xA0\xAC" => "\xEC\xA0\xB9", + "\xA0\xAD" => "\xEC\xA0\xBA", + "\xA0\xAE" => "\xEC\xA0\xBB", + "\xA0\xAF" => "\xEC\xA0\xBD", + "\xA0\xB0" => "\xEC\xA0\xBE", + "\xA0\xB1" => "\xEC\xA0\xBF", + "\xA0\xB2" => "\xEC\xA1\x81", + "\xA0\xB3" => "\xEC\xA1\x82", + "\xA0\xB4" => "\xEC\xA1\x83", + "\xA0\xB5" => "\xEC\xA1\x84", + "\xA0\xB6" => "\xEC\xA1\x85", + "\xA0\xB7" => "\xEC\xA1\x86", + "\xA0\xB8" => "\xEC\xA1\x87", + "\xA0\xB9" => "\xEC\xA1\x8A", + "\xA0\xBA" => "\xEC\xA1\x8B", + "\xA0\xBB" => "\xEC\xA1\x8E", + "\xA0\xBC" => "\xEC\xA1\x8F", + "\xA0\xBD" => "\xEC\xA1\x90", + "\xA0\xBE" => "\xEC\xA1\x91", + "\xA0\xBF" => "\xEC\xA1\x92", + "\xA0\xC0" => "\xEC\xA1\x93", + "\xA0\xC1" => "\xEC\xA1\x95", + "\xA0\xC2" => "\xEC\xA1\x96", + "\xA0\xC3" => "\xEC\xA1\x97", + "\xA0\xC4" => "\xEC\xA1\x98", + "\xA0\xC5" => "\xEC\xA1\x99", + "\xA0\xC6" => "\xEC\xA1\x9A", + "\xA0\xC7" => "\xEC\xA1\x9B", + "\xA0\xC8" => "\xEC\xA1\x9C", + "\xA0\xC9" => "\xEC\xA1\x9D", + "\xA0\xCA" => "\xEC\xA1\x9E", + "\xA0\xCB" => "\xEC\xA1\x9F", + "\xA0\xCC" => "\xEC\xA1\xA0", + "\xA0\xCD" => "\xEC\xA1\xA1", + "\xA0\xCE" => "\xEC\xA1\xA2", + "\xA0\xCF" => "\xEC\xA1\xA3", + "\xA0\xD0" => "\xEC\xA1\xA4", + "\xA0\xD1" => "\xEC\xA1\xA5", + "\xA0\xD2" => "\xEC\xA1\xA6", + "\xA0\xD3" => "\xEC\xA1\xA7", + "\xA0\xD4" => "\xEC\xA1\xA8", + "\xA0\xD5" => "\xEC\xA1\xA9", + "\xA0\xD6" => "\xEC\xA1\xAA", + "\xA0\xD7" => "\xEC\xA1\xAB", + "\xA0\xD8" => "\xEC\xA1\xAC", + "\xA0\xD9" => "\xEC\xA1\xAD", + "\xA0\xDA" => "\xEC\xA1\xAE", + "\xA0\xDB" => "\xEC\xA1\xAF", + "\xA0\xDC" => "\xEC\xA1\xB2", + "\xA0\xDD" => "\xEC\xA1\xB3", + "\xA0\xDE" => "\xEC\xA1\xB5", + "\xA0\xDF" => "\xEC\xA1\xB6", + "\xA0\xE0" => "\xEC\xA1\xB7", + "\xA0\xE1" => "\xEC\xA1\xB9", + "\xA0\xE2" => "\xEC\xA1\xBB", + "\xA0\xE3" => "\xEC\xA1\xBC", + "\xA0\xE4" => "\xEC\xA1\xBD", + "\xA0\xE5" => "\xEC\xA1\xBE", + "\xA0\xE6" => "\xEC\xA1\xBF", + "\xA0\xE7" => "\xEC\xA2\x82", + "\xA0\xE8" => "\xEC\xA2\x84", + "\xA0\xE9" => "\xEC\xA2\x88", + "\xA0\xEA" => "\xEC\xA2\x89", + "\xA0\xEB" => "\xEC\xA2\x8A", + "\xA0\xEC" => "\xEC\xA2\x8E", + "\xA0\xED" => "\xEC\xA2\x8F", + "\xA0\xEE" => "\xEC\xA2\x90", + "\xA0\xEF" => "\xEC\xA2\x91", + "\xA0\xF0" => "\xEC\xA2\x92", + "\xA0\xF1" => "\xEC\xA2\x93", + "\xA0\xF2" => "\xEC\xA2\x95", + "\xA0\xF3" => "\xEC\xA2\x96", + "\xA0\xF4" => "\xEC\xA2\x97", + "\xA0\xF5" => "\xEC\xA2\x98", + "\xA0\xF6" => "\xEC\xA2\x99", + "\xA0\xF7" => "\xEC\xA2\x9A", + "\xA0\xF8" => "\xEC\xA2\x9B", + "\xA0\xF9" => "\xEC\xA2\x9C", + "\xA0\xFA" => "\xEC\xA2\x9E", + "\xA0\xFB" => "\xEC\xA2\xA0", + "\xA0\xFC" => "\xEC\xA2\xA2", + "\xA0\xFD" => "\xEC\xA2\xA3", + "\xA0\xFE" => "\xEC\xA2\xA4", + "\xA1\x41" => "\xEC\xA2\xA5", + "\xA1\x42" => "\xEC\xA2\xA6", + "\xA1\x43" => "\xEC\xA2\xA7", + "\xA1\x44" => "\xEC\xA2\xA9", + "\xA1\x45" => "\xEC\xA2\xAA", + "\xA1\x46" => "\xEC\xA2\xAB", + "\xA1\x47" => "\xEC\xA2\xAC", + "\xA1\x48" => "\xEC\xA2\xAD", + "\xA1\x49" => "\xEC\xA2\xAE", + "\xA1\x4A" => "\xEC\xA2\xAF", + "\xA1\x4B" => "\xEC\xA2\xB0", + "\xA1\x4C" => "\xEC\xA2\xB1", + "\xA1\x4D" => "\xEC\xA2\xB2", + "\xA1\x4E" => "\xEC\xA2\xB3", + "\xA1\x4F" => "\xEC\xA2\xB4", + "\xA1\x50" => "\xEC\xA2\xB5", + "\xA1\x51" => "\xEC\xA2\xB6", + "\xA1\x52" => "\xEC\xA2\xB7", + "\xA1\x53" => "\xEC\xA2\xB8", + "\xA1\x54" => "\xEC\xA2\xB9", + "\xA1\x55" => "\xEC\xA2\xBA", + "\xA1\x56" => "\xEC\xA2\xBB", + "\xA1\x57" => "\xEC\xA2\xBE", + "\xA1\x58" => "\xEC\xA2\xBF", + "\xA1\x59" => "\xEC\xA3\x80", + "\xA1\x5A" => "\xEC\xA3\x81", + "\xA1\x61" => "\xEC\xA3\x82", + "\xA1\x62" => "\xEC\xA3\x83", + "\xA1\x63" => "\xEC\xA3\x85", + "\xA1\x64" => "\xEC\xA3\x86", + "\xA1\x65" => "\xEC\xA3\x87", + "\xA1\x66" => "\xEC\xA3\x89", + "\xA1\x67" => "\xEC\xA3\x8A", + "\xA1\x68" => "\xEC\xA3\x8B", + "\xA1\x69" => "\xEC\xA3\x8D", + "\xA1\x6A" => "\xEC\xA3\x8E", + "\xA1\x6B" => "\xEC\xA3\x8F", + "\xA1\x6C" => "\xEC\xA3\x90", + "\xA1\x6D" => "\xEC\xA3\x91", + "\xA1\x6E" => "\xEC\xA3\x92", + "\xA1\x6F" => "\xEC\xA3\x93", + "\xA1\x70" => "\xEC\xA3\x96", + "\xA1\x71" => "\xEC\xA3\x98", + "\xA1\x72" => "\xEC\xA3\x9A", + "\xA1\x73" => "\xEC\xA3\x9B", + "\xA1\x74" => "\xEC\xA3\x9C", + "\xA1\x75" => "\xEC\xA3\x9D", + "\xA1\x76" => "\xEC\xA3\x9E", + "\xA1\x77" => "\xEC\xA3\x9F", + "\xA1\x78" => "\xEC\xA3\xA2", + "\xA1\x79" => "\xEC\xA3\xA3", + "\xA1\x7A" => "\xEC\xA3\xA5", + "\xA1\x81" => "\xEC\xA3\xA6", + "\xA1\x82" => "\xEC\xA3\xA7", + "\xA1\x83" => "\xEC\xA3\xA8", + "\xA1\x84" => "\xEC\xA3\xA9", + "\xA1\x85" => "\xEC\xA3\xAA", + "\xA1\x86" => "\xEC\xA3\xAB", + "\xA1\x87" => "\xEC\xA3\xAC", + "\xA1\x88" => "\xEC\xA3\xAD", + "\xA1\x89" => "\xEC\xA3\xAE", + "\xA1\x8A" => "\xEC\xA3\xAF", + "\xA1\x8B" => "\xEC\xA3\xB0", + "\xA1\x8C" => "\xEC\xA3\xB1", + "\xA1\x8D" => "\xEC\xA3\xB2", + "\xA1\x8E" => "\xEC\xA3\xB3", + "\xA1\x8F" => "\xEC\xA3\xB4", + "\xA1\x90" => "\xEC\xA3\xB6", + "\xA1\x91" => "\xEC\xA3\xB7", + "\xA1\x92" => "\xEC\xA3\xB8", + "\xA1\x93" => "\xEC\xA3\xB9", + "\xA1\x94" => "\xEC\xA3\xBA", + "\xA1\x95" => "\xEC\xA3\xBB", + "\xA1\x96" => "\xEC\xA3\xBE", + "\xA1\x97" => "\xEC\xA3\xBF", + "\xA1\x98" => "\xEC\xA4\x81", + "\xA1\x99" => "\xEC\xA4\x82", + "\xA1\x9A" => "\xEC\xA4\x83", + "\xA1\x9B" => "\xEC\xA4\x87", + "\xA1\x9C" => "\xEC\xA4\x88", + "\xA1\x9D" => "\xEC\xA4\x89", + "\xA1\x9E" => "\xEC\xA4\x8A", + "\xA1\x9F" => "\xEC\xA4\x8B", + "\xA1\xA0" => "\xEC\xA4\x8E", + "\xA1\xA1" => "\xE3\x80\x80", + "\xA1\xA2" => "\xE3\x80\x81", + "\xA1\xA3" => "\xE3\x80\x82", + "\xA1\xA4" => "\xC2\xB7", + "\xA1\xA5" => "\xE2\x80\xA5", + "\xA1\xA6" => "\xE2\x80\xA6", + "\xA1\xA7" => "\xC2\xA8", + "\xA1\xA8" => "\xE3\x80\x83", + "\xA1\xA9" => "\xC2\xAD", + "\xA1\xAA" => "\xE2\x80\x95", + "\xA1\xAB" => "\xE2\x88\xA5", + "\xA1\xAC" => "\xEF\xBC\xBC", + "\xA1\xAD" => "\xE2\x88\xBC", + "\xA1\xAE" => "\xE2\x80\x98", + "\xA1\xAF" => "\xE2\x80\x99", + "\xA1\xB0" => "\xE2\x80\x9C", + "\xA1\xB1" => "\xE2\x80\x9D", + "\xA1\xB2" => "\xE3\x80\x94", + "\xA1\xB3" => "\xE3\x80\x95", + "\xA1\xB4" => "\xE3\x80\x88", + "\xA1\xB5" => "\xE3\x80\x89", + "\xA1\xB6" => "\xE3\x80\x8A", + "\xA1\xB7" => "\xE3\x80\x8B", + "\xA1\xB8" => "\xE3\x80\x8C", + "\xA1\xB9" => "\xE3\x80\x8D", + "\xA1\xBA" => "\xE3\x80\x8E", + "\xA1\xBB" => "\xE3\x80\x8F", + "\xA1\xBC" => "\xE3\x80\x90", + "\xA1\xBD" => "\xE3\x80\x91", + "\xA1\xBE" => "\xC2\xB1", + "\xA1\xBF" => "\xC3\x97", + "\xA1\xC0" => "\xC3\xB7", + "\xA1\xC1" => "\xE2\x89\xA0", + "\xA1\xC2" => "\xE2\x89\xA4", + "\xA1\xC3" => "\xE2\x89\xA5", + "\xA1\xC4" => "\xE2\x88\x9E", + "\xA1\xC5" => "\xE2\x88\xB4", + "\xA1\xC6" => "\xC2\xB0", + "\xA1\xC7" => "\xE2\x80\xB2", + "\xA1\xC8" => "\xE2\x80\xB3", + "\xA1\xC9" => "\xE2\x84\x83", + "\xA1\xCA" => "\xE2\x84\xAB", + "\xA1\xCB" => "\xEF\xBF\xA0", + "\xA1\xCC" => "\xEF\xBF\xA1", + "\xA1\xCD" => "\xEF\xBF\xA5", + "\xA1\xCE" => "\xE2\x99\x82", + "\xA1\xCF" => "\xE2\x99\x80", + "\xA1\xD0" => "\xE2\x88\xA0", + "\xA1\xD1" => "\xE2\x8A\xA5", + "\xA1\xD2" => "\xE2\x8C\x92", + "\xA1\xD3" => "\xE2\x88\x82", + "\xA1\xD4" => "\xE2\x88\x87", + "\xA1\xD5" => "\xE2\x89\xA1", + "\xA1\xD6" => "\xE2\x89\x92", + "\xA1\xD7" => "\xC2\xA7", + "\xA1\xD8" => "\xE2\x80\xBB", + "\xA1\xD9" => "\xE2\x98\x86", + "\xA1\xDA" => "\xE2\x98\x85", + "\xA1\xDB" => "\xE2\x97\x8B", + "\xA1\xDC" => "\xE2\x97\x8F", + "\xA1\xDD" => "\xE2\x97\x8E", + "\xA1\xDE" => "\xE2\x97\x87", + "\xA1\xDF" => "\xE2\x97\x86", + "\xA1\xE0" => "\xE2\x96\xA1", + "\xA1\xE1" => "\xE2\x96\xA0", + "\xA1\xE2" => "\xE2\x96\xB3", + "\xA1\xE3" => "\xE2\x96\xB2", + "\xA1\xE4" => "\xE2\x96\xBD", + "\xA1\xE5" => "\xE2\x96\xBC", + "\xA1\xE6" => "\xE2\x86\x92", + "\xA1\xE7" => "\xE2\x86\x90", + "\xA1\xE8" => "\xE2\x86\x91", + "\xA1\xE9" => "\xE2\x86\x93", + "\xA1\xEA" => "\xE2\x86\x94", + "\xA1\xEB" => "\xE3\x80\x93", + "\xA1\xEC" => "\xE2\x89\xAA", + "\xA1\xED" => "\xE2\x89\xAB", + "\xA1\xEE" => "\xE2\x88\x9A", + "\xA1\xEF" => "\xE2\x88\xBD", + "\xA1\xF0" => "\xE2\x88\x9D", + "\xA1\xF1" => "\xE2\x88\xB5", + "\xA1\xF2" => "\xE2\x88\xAB", + "\xA1\xF3" => "\xE2\x88\xAC", + "\xA1\xF4" => "\xE2\x88\x88", + "\xA1\xF5" => "\xE2\x88\x8B", + "\xA1\xF6" => "\xE2\x8A\x86", + "\xA1\xF7" => "\xE2\x8A\x87", + "\xA1\xF8" => "\xE2\x8A\x82", + "\xA1\xF9" => "\xE2\x8A\x83", + "\xA1\xFA" => "\xE2\x88\xAA", + "\xA1\xFB" => "\xE2\x88\xA9", + "\xA1\xFC" => "\xE2\x88\xA7", + "\xA1\xFD" => "\xE2\x88\xA8", + "\xA1\xFE" => "\xEF\xBF\xA2", + "\xA2\x41" => "\xEC\xA4\x90", + "\xA2\x42" => "\xEC\xA4\x92", + "\xA2\x43" => "\xEC\xA4\x93", + "\xA2\x44" => "\xEC\xA4\x94", + "\xA2\x45" => "\xEC\xA4\x95", + "\xA2\x46" => "\xEC\xA4\x96", + "\xA2\x47" => "\xEC\xA4\x97", + "\xA2\x48" => "\xEC\xA4\x99", + "\xA2\x49" => "\xEC\xA4\x9A", + "\xA2\x4A" => "\xEC\xA4\x9B", + "\xA2\x4B" => "\xEC\xA4\x9C", + "\xA2\x4C" => "\xEC\xA4\x9D", + "\xA2\x4D" => "\xEC\xA4\x9E", + "\xA2\x4E" => "\xEC\xA4\x9F", + "\xA2\x4F" => "\xEC\xA4\xA0", + "\xA2\x50" => "\xEC\xA4\xA1", + "\xA2\x51" => "\xEC\xA4\xA2", + "\xA2\x52" => "\xEC\xA4\xA3", + "\xA2\x53" => "\xEC\xA4\xA4", + "\xA2\x54" => "\xEC\xA4\xA5", + "\xA2\x55" => "\xEC\xA4\xA6", + "\xA2\x56" => "\xEC\xA4\xA7", + "\xA2\x57" => "\xEC\xA4\xA8", + "\xA2\x58" => "\xEC\xA4\xA9", + "\xA2\x59" => "\xEC\xA4\xAA", + "\xA2\x5A" => "\xEC\xA4\xAB", + "\xA2\x61" => "\xEC\xA4\xAD", + "\xA2\x62" => "\xEC\xA4\xAE", + "\xA2\x63" => "\xEC\xA4\xAF", + "\xA2\x64" => "\xEC\xA4\xB0", + "\xA2\x65" => "\xEC\xA4\xB1", + "\xA2\x66" => "\xEC\xA4\xB2", + "\xA2\x67" => "\xEC\xA4\xB3", + "\xA2\x68" => "\xEC\xA4\xB5", + "\xA2\x69" => "\xEC\xA4\xB6", + "\xA2\x6A" => "\xEC\xA4\xB7", + "\xA2\x6B" => "\xEC\xA4\xB8", + "\xA2\x6C" => "\xEC\xA4\xB9", + "\xA2\x6D" => "\xEC\xA4\xBA", + "\xA2\x6E" => "\xEC\xA4\xBB", + "\xA2\x6F" => "\xEC\xA4\xBC", + "\xA2\x70" => "\xEC\xA4\xBD", + "\xA2\x71" => "\xEC\xA4\xBE", + "\xA2\x72" => "\xEC\xA4\xBF", + "\xA2\x73" => "\xEC\xA5\x80", + "\xA2\x74" => "\xEC\xA5\x81", + "\xA2\x75" => "\xEC\xA5\x82", + "\xA2\x76" => "\xEC\xA5\x83", + "\xA2\x77" => "\xEC\xA5\x84", + "\xA2\x78" => "\xEC\xA5\x85", + "\xA2\x79" => "\xEC\xA5\x86", + "\xA2\x7A" => "\xEC\xA5\x87", + "\xA2\x81" => "\xEC\xA5\x88", + "\xA2\x82" => "\xEC\xA5\x89", + "\xA2\x83" => "\xEC\xA5\x8A", + "\xA2\x84" => "\xEC\xA5\x8B", + "\xA2\x85" => "\xEC\xA5\x8C", + "\xA2\x86" => "\xEC\xA5\x8D", + "\xA2\x87" => "\xEC\xA5\x8E", + "\xA2\x88" => "\xEC\xA5\x8F", + "\xA2\x89" => "\xEC\xA5\x92", + "\xA2\x8A" => "\xEC\xA5\x93", + "\xA2\x8B" => "\xEC\xA5\x95", + "\xA2\x8C" => "\xEC\xA5\x96", + "\xA2\x8D" => "\xEC\xA5\x97", + "\xA2\x8E" => "\xEC\xA5\x99", + "\xA2\x8F" => "\xEC\xA5\x9A", + "\xA2\x90" => "\xEC\xA5\x9B", + "\xA2\x91" => "\xEC\xA5\x9C", + "\xA2\x92" => "\xEC\xA5\x9D", + "\xA2\x93" => "\xEC\xA5\x9E", + "\xA2\x94" => "\xEC\xA5\x9F", + "\xA2\x95" => "\xEC\xA5\xA2", + "\xA2\x96" => "\xEC\xA5\xA4", + "\xA2\x97" => "\xEC\xA5\xA5", + "\xA2\x98" => "\xEC\xA5\xA6", + "\xA2\x99" => "\xEC\xA5\xA7", + "\xA2\x9A" => "\xEC\xA5\xA8", + "\xA2\x9B" => "\xEC\xA5\xA9", + "\xA2\x9C" => "\xEC\xA5\xAA", + "\xA2\x9D" => "\xEC\xA5\xAB", + "\xA2\x9E" => "\xEC\xA5\xAD", + "\xA2\x9F" => "\xEC\xA5\xAE", + "\xA2\xA0" => "\xEC\xA5\xAF", + "\xA2\xA1" => "\xE2\x87\x92", + "\xA2\xA2" => "\xE2\x87\x94", + "\xA2\xA3" => "\xE2\x88\x80", + "\xA2\xA4" => "\xE2\x88\x83", + "\xA2\xA5" => "\xC2\xB4", + "\xA2\xA6" => "\xEF\xBD\x9E", + "\xA2\xA7" => "\xCB\x87", + "\xA2\xA8" => "\xCB\x98", + "\xA2\xA9" => "\xCB\x9D", + "\xA2\xAA" => "\xCB\x9A", + "\xA2\xAB" => "\xCB\x99", + "\xA2\xAC" => "\xC2\xB8", + "\xA2\xAD" => "\xCB\x9B", + "\xA2\xAE" => "\xC2\xA1", + "\xA2\xAF" => "\xC2\xBF", + "\xA2\xB0" => "\xCB\x90", + "\xA2\xB1" => "\xE2\x88\xAE", + "\xA2\xB2" => "\xE2\x88\x91", + "\xA2\xB3" => "\xE2\x88\x8F", + "\xA2\xB4" => "\xC2\xA4", + "\xA2\xB5" => "\xE2\x84\x89", + "\xA2\xB6" => "\xE2\x80\xB0", + "\xA2\xB7" => "\xE2\x97\x81", + "\xA2\xB8" => "\xE2\x97\x80", + "\xA2\xB9" => "\xE2\x96\xB7", + "\xA2\xBA" => "\xE2\x96\xB6", + "\xA2\xBB" => "\xE2\x99\xA4", + "\xA2\xBC" => "\xE2\x99\xA0", + "\xA2\xBD" => "\xE2\x99\xA1", + "\xA2\xBE" => "\xE2\x99\xA5", + "\xA2\xBF" => "\xE2\x99\xA7", + "\xA2\xC0" => "\xE2\x99\xA3", + "\xA2\xC1" => "\xE2\x8A\x99", + "\xA2\xC2" => "\xE2\x97\x88", + "\xA2\xC3" => "\xE2\x96\xA3", + "\xA2\xC4" => "\xE2\x97\x90", + "\xA2\xC5" => "\xE2\x97\x91", + "\xA2\xC6" => "\xE2\x96\x92", + "\xA2\xC7" => "\xE2\x96\xA4", + "\xA2\xC8" => "\xE2\x96\xA5", + "\xA2\xC9" => "\xE2\x96\xA8", + "\xA2\xCA" => "\xE2\x96\xA7", + "\xA2\xCB" => "\xE2\x96\xA6", + "\xA2\xCC" => "\xE2\x96\xA9", + "\xA2\xCD" => "\xE2\x99\xA8", + "\xA2\xCE" => "\xE2\x98\x8F", + "\xA2\xCF" => "\xE2\x98\x8E", + "\xA2\xD0" => "\xE2\x98\x9C", + "\xA2\xD1" => "\xE2\x98\x9E", + "\xA2\xD2" => "\xC2\xB6", + "\xA2\xD3" => "\xE2\x80\xA0", + "\xA2\xD4" => "\xE2\x80\xA1", + "\xA2\xD5" => "\xE2\x86\x95", + "\xA2\xD6" => "\xE2\x86\x97", + "\xA2\xD7" => "\xE2\x86\x99", + "\xA2\xD8" => "\xE2\x86\x96", + "\xA2\xD9" => "\xE2\x86\x98", + "\xA2\xDA" => "\xE2\x99\xAD", + "\xA2\xDB" => "\xE2\x99\xA9", + "\xA2\xDC" => "\xE2\x99\xAA", + "\xA2\xDD" => "\xE2\x99\xAC", + "\xA2\xDE" => "\xE3\x89\xBF", + "\xA2\xDF" => "\xE3\x88\x9C", + "\xA2\xE0" => "\xE2\x84\x96", + "\xA2\xE1" => "\xE3\x8F\x87", + "\xA2\xE2" => "\xE2\x84\xA2", + "\xA2\xE3" => "\xE3\x8F\x82", + "\xA2\xE4" => "\xE3\x8F\x98", + "\xA2\xE5" => "\xE2\x84\xA1", + "\xA3\x41" => "\xEC\xA5\xB1", + "\xA3\x42" => "\xEC\xA5\xB2", + "\xA3\x43" => "\xEC\xA5\xB3", + "\xA3\x44" => "\xEC\xA5\xB5", + "\xA3\x45" => "\xEC\xA5\xB6", + "\xA3\x46" => "\xEC\xA5\xB7", + "\xA3\x47" => "\xEC\xA5\xB8", + "\xA3\x48" => "\xEC\xA5\xB9", + "\xA3\x49" => "\xEC\xA5\xBA", + "\xA3\x4A" => "\xEC\xA5\xBB", + "\xA3\x4B" => "\xEC\xA5\xBD", + "\xA3\x4C" => "\xEC\xA5\xBE", + "\xA3\x4D" => "\xEC\xA5\xBF", + "\xA3\x4E" => "\xEC\xA6\x80", + "\xA3\x4F" => "\xEC\xA6\x81", + "\xA3\x50" => "\xEC\xA6\x82", + "\xA3\x51" => "\xEC\xA6\x83", + "\xA3\x52" => "\xEC\xA6\x84", + "\xA3\x53" => "\xEC\xA6\x85", + "\xA3\x54" => "\xEC\xA6\x86", + "\xA3\x55" => "\xEC\xA6\x87", + "\xA3\x56" => "\xEC\xA6\x8A", + "\xA3\x57" => "\xEC\xA6\x8B", + "\xA3\x58" => "\xEC\xA6\x8D", + "\xA3\x59" => "\xEC\xA6\x8E", + "\xA3\x5A" => "\xEC\xA6\x8F", + "\xA3\x61" => "\xEC\xA6\x91", + "\xA3\x62" => "\xEC\xA6\x92", + "\xA3\x63" => "\xEC\xA6\x93", + "\xA3\x64" => "\xEC\xA6\x94", + "\xA3\x65" => "\xEC\xA6\x95", + "\xA3\x66" => "\xEC\xA6\x96", + "\xA3\x67" => "\xEC\xA6\x97", + "\xA3\x68" => "\xEC\xA6\x9A", + "\xA3\x69" => "\xEC\xA6\x9C", + "\xA3\x6A" => "\xEC\xA6\x9E", + "\xA3\x6B" => "\xEC\xA6\x9F", + "\xA3\x6C" => "\xEC\xA6\xA0", + "\xA3\x6D" => "\xEC\xA6\xA1", + "\xA3\x6E" => "\xEC\xA6\xA2", + "\xA3\x6F" => "\xEC\xA6\xA3", + "\xA3\x70" => "\xEC\xA6\xA4", + "\xA3\x71" => "\xEC\xA6\xA5", + "\xA3\x72" => "\xEC\xA6\xA6", + "\xA3\x73" => "\xEC\xA6\xA7", + "\xA3\x74" => "\xEC\xA6\xA8", + "\xA3\x75" => "\xEC\xA6\xA9", + "\xA3\x76" => "\xEC\xA6\xAA", + "\xA3\x77" => "\xEC\xA6\xAB", + "\xA3\x78" => "\xEC\xA6\xAC", + "\xA3\x79" => "\xEC\xA6\xAD", + "\xA3\x7A" => "\xEC\xA6\xAE", + "\xA3\x81" => "\xEC\xA6\xAF", + "\xA3\x82" => "\xEC\xA6\xB0", + "\xA3\x83" => "\xEC\xA6\xB1", + "\xA3\x84" => "\xEC\xA6\xB2", + "\xA3\x85" => "\xEC\xA6\xB3", + "\xA3\x86" => "\xEC\xA6\xB4", + "\xA3\x87" => "\xEC\xA6\xB5", + "\xA3\x88" => "\xEC\xA6\xB6", + "\xA3\x89" => "\xEC\xA6\xB7", + "\xA3\x8A" => "\xEC\xA6\xB8", + "\xA3\x8B" => "\xEC\xA6\xB9", + "\xA3\x8C" => "\xEC\xA6\xBA", + "\xA3\x8D" => "\xEC\xA6\xBB", + "\xA3\x8E" => "\xEC\xA6\xBC", + "\xA3\x8F" => "\xEC\xA6\xBD", + "\xA3\x90" => "\xEC\xA6\xBE", + "\xA3\x91" => "\xEC\xA6\xBF", + "\xA3\x92" => "\xEC\xA7\x82", + "\xA3\x93" => "\xEC\xA7\x83", + "\xA3\x94" => "\xEC\xA7\x85", + "\xA3\x95" => "\xEC\xA7\x86", + "\xA3\x96" => "\xEC\xA7\x89", + "\xA3\x97" => "\xEC\xA7\x8B", + "\xA3\x98" => "\xEC\xA7\x8C", + "\xA3\x99" => "\xEC\xA7\x8D", + "\xA3\x9A" => "\xEC\xA7\x8E", + "\xA3\x9B" => "\xEC\xA7\x8F", + "\xA3\x9C" => "\xEC\xA7\x92", + "\xA3\x9D" => "\xEC\xA7\x94", + "\xA3\x9E" => "\xEC\xA7\x97", + "\xA3\x9F" => "\xEC\xA7\x98", + "\xA3\xA0" => "\xEC\xA7\x9B", + "\xA3\xA1" => "\xEF\xBC\x81", + "\xA3\xA2" => "\xEF\xBC\x82", + "\xA3\xA3" => "\xEF\xBC\x83", + "\xA3\xA4" => "\xEF\xBC\x84", + "\xA3\xA5" => "\xEF\xBC\x85", + "\xA3\xA6" => "\xEF\xBC\x86", + "\xA3\xA7" => "\xEF\xBC\x87", + "\xA3\xA8" => "\xEF\xBC\x88", + "\xA3\xA9" => "\xEF\xBC\x89", + "\xA3\xAA" => "\xEF\xBC\x8A", + "\xA3\xAB" => "\xEF\xBC\x8B", + "\xA3\xAC" => "\xEF\xBC\x8C", + "\xA3\xAD" => "\xEF\xBC\x8D", + "\xA3\xAE" => "\xEF\xBC\x8E", + "\xA3\xAF" => "\xEF\xBC\x8F", + "\xA3\xB0" => "\xEF\xBC\x90", + "\xA3\xB1" => "\xEF\xBC\x91", + "\xA3\xB2" => "\xEF\xBC\x92", + "\xA3\xB3" => "\xEF\xBC\x93", + "\xA3\xB4" => "\xEF\xBC\x94", + "\xA3\xB5" => "\xEF\xBC\x95", + "\xA3\xB6" => "\xEF\xBC\x96", + "\xA3\xB7" => "\xEF\xBC\x97", + "\xA3\xB8" => "\xEF\xBC\x98", + "\xA3\xB9" => "\xEF\xBC\x99", + "\xA3\xBA" => "\xEF\xBC\x9A", + "\xA3\xBB" => "\xEF\xBC\x9B", + "\xA3\xBC" => "\xEF\xBC\x9C", + "\xA3\xBD" => "\xEF\xBC\x9D", + "\xA3\xBE" => "\xEF\xBC\x9E", + "\xA3\xBF" => "\xEF\xBC\x9F", + "\xA3\xC0" => "\xEF\xBC\xA0", + "\xA3\xC1" => "\xEF\xBC\xA1", + "\xA3\xC2" => "\xEF\xBC\xA2", + "\xA3\xC3" => "\xEF\xBC\xA3", + "\xA3\xC4" => "\xEF\xBC\xA4", + "\xA3\xC5" => "\xEF\xBC\xA5", + "\xA3\xC6" => "\xEF\xBC\xA6", + "\xA3\xC7" => "\xEF\xBC\xA7", + "\xA3\xC8" => "\xEF\xBC\xA8", + "\xA3\xC9" => "\xEF\xBC\xA9", + "\xA3\xCA" => "\xEF\xBC\xAA", + "\xA3\xCB" => "\xEF\xBC\xAB", + "\xA3\xCC" => "\xEF\xBC\xAC", + "\xA3\xCD" => "\xEF\xBC\xAD", + "\xA3\xCE" => "\xEF\xBC\xAE", + "\xA3\xCF" => "\xEF\xBC\xAF", + "\xA3\xD0" => "\xEF\xBC\xB0", + "\xA3\xD1" => "\xEF\xBC\xB1", + "\xA3\xD2" => "\xEF\xBC\xB2", + "\xA3\xD3" => "\xEF\xBC\xB3", + "\xA3\xD4" => "\xEF\xBC\xB4", + "\xA3\xD5" => "\xEF\xBC\xB5", + "\xA3\xD6" => "\xEF\xBC\xB6", + "\xA3\xD7" => "\xEF\xBC\xB7", + "\xA3\xD8" => "\xEF\xBC\xB8", + "\xA3\xD9" => "\xEF\xBC\xB9", + "\xA3\xDA" => "\xEF\xBC\xBA", + "\xA3\xDB" => "\xEF\xBC\xBB", + "\xA3\xDC" => "\xEF\xBF\xA6", + "\xA3\xDD" => "\xEF\xBC\xBD", + "\xA3\xDE" => "\xEF\xBC\xBE", + "\xA3\xDF" => "\xEF\xBC\xBF", + "\xA3\xE0" => "\xEF\xBD\x80", + "\xA3\xE1" => "\xEF\xBD\x81", + "\xA3\xE2" => "\xEF\xBD\x82", + "\xA3\xE3" => "\xEF\xBD\x83", + "\xA3\xE4" => "\xEF\xBD\x84", + "\xA3\xE5" => "\xEF\xBD\x85", + "\xA3\xE6" => "\xEF\xBD\x86", + "\xA3\xE7" => "\xEF\xBD\x87", + "\xA3\xE8" => "\xEF\xBD\x88", + "\xA3\xE9" => "\xEF\xBD\x89", + "\xA3\xEA" => "\xEF\xBD\x8A", + "\xA3\xEB" => "\xEF\xBD\x8B", + "\xA3\xEC" => "\xEF\xBD\x8C", + "\xA3\xED" => "\xEF\xBD\x8D", + "\xA3\xEE" => "\xEF\xBD\x8E", + "\xA3\xEF" => "\xEF\xBD\x8F", + "\xA3\xF0" => "\xEF\xBD\x90", + "\xA3\xF1" => "\xEF\xBD\x91", + "\xA3\xF2" => "\xEF\xBD\x92", + "\xA3\xF3" => "\xEF\xBD\x93", + "\xA3\xF4" => "\xEF\xBD\x94", + "\xA3\xF5" => "\xEF\xBD\x95", + "\xA3\xF6" => "\xEF\xBD\x96", + "\xA3\xF7" => "\xEF\xBD\x97", + "\xA3\xF8" => "\xEF\xBD\x98", + "\xA3\xF9" => "\xEF\xBD\x99", + "\xA3\xFA" => "\xEF\xBD\x9A", + "\xA3\xFB" => "\xEF\xBD\x9B", + "\xA3\xFC" => "\xEF\xBD\x9C", + "\xA3\xFD" => "\xEF\xBD\x9D", + "\xA3\xFE" => "\xEF\xBF\xA3", + "\xA4\x41" => "\xEC\xA7\x9E", + "\xA4\x42" => "\xEC\xA7\x9F", + "\xA4\x43" => "\xEC\xA7\xA1", + "\xA4\x44" => "\xEC\xA7\xA3", + "\xA4\x45" => "\xEC\xA7\xA5", + "\xA4\x46" => "\xEC\xA7\xA6", + "\xA4\x47" => "\xEC\xA7\xA8", + "\xA4\x48" => "\xEC\xA7\xA9", + "\xA4\x49" => "\xEC\xA7\xAA", + "\xA4\x4A" => "\xEC\xA7\xAB", + "\xA4\x4B" => "\xEC\xA7\xAE", + "\xA4\x4C" => "\xEC\xA7\xB2", + "\xA4\x4D" => "\xEC\xA7\xB3", + "\xA4\x4E" => "\xEC\xA7\xB4", + "\xA4\x4F" => "\xEC\xA7\xB5", + "\xA4\x50" => "\xEC\xA7\xB6", + "\xA4\x51" => "\xEC\xA7\xB7", + "\xA4\x52" => "\xEC\xA7\xBA", + "\xA4\x53" => "\xEC\xA7\xBB", + "\xA4\x54" => "\xEC\xA7\xBD", + "\xA4\x55" => "\xEC\xA7\xBE", + "\xA4\x56" => "\xEC\xA7\xBF", + "\xA4\x57" => "\xEC\xA8\x81", + "\xA4\x58" => "\xEC\xA8\x82", + "\xA4\x59" => "\xEC\xA8\x83", + "\xA4\x5A" => "\xEC\xA8\x84", + "\xA4\x61" => "\xEC\xA8\x85", + "\xA4\x62" => "\xEC\xA8\x86", + "\xA4\x63" => "\xEC\xA8\x87", + "\xA4\x64" => "\xEC\xA8\x8A", + "\xA4\x65" => "\xEC\xA8\x8E", + "\xA4\x66" => "\xEC\xA8\x8F", + "\xA4\x67" => "\xEC\xA8\x90", + "\xA4\x68" => "\xEC\xA8\x91", + "\xA4\x69" => "\xEC\xA8\x92", + "\xA4\x6A" => "\xEC\xA8\x93", + "\xA4\x6B" => "\xEC\xA8\x95", + "\xA4\x6C" => "\xEC\xA8\x96", + "\xA4\x6D" => "\xEC\xA8\x97", + "\xA4\x6E" => "\xEC\xA8\x99", + "\xA4\x6F" => "\xEC\xA8\x9A", + "\xA4\x70" => "\xEC\xA8\x9B", + "\xA4\x71" => "\xEC\xA8\x9C", + "\xA4\x72" => "\xEC\xA8\x9D", + "\xA4\x73" => "\xEC\xA8\x9E", + "\xA4\x74" => "\xEC\xA8\x9F", + "\xA4\x75" => "\xEC\xA8\xA0", + "\xA4\x76" => "\xEC\xA8\xA1", + "\xA4\x77" => "\xEC\xA8\xA2", + "\xA4\x78" => "\xEC\xA8\xA3", + "\xA4\x79" => "\xEC\xA8\xA4", + "\xA4\x7A" => "\xEC\xA8\xA5", + "\xA4\x81" => "\xEC\xA8\xA6", + "\xA4\x82" => "\xEC\xA8\xA7", + "\xA4\x83" => "\xEC\xA8\xA8", + "\xA4\x84" => "\xEC\xA8\xAA", + "\xA4\x85" => "\xEC\xA8\xAB", + "\xA4\x86" => "\xEC\xA8\xAC", + "\xA4\x87" => "\xEC\xA8\xAD", + "\xA4\x88" => "\xEC\xA8\xAE", + "\xA4\x89" => "\xEC\xA8\xAF", + "\xA4\x8A" => "\xEC\xA8\xB0", + "\xA4\x8B" => "\xEC\xA8\xB1", + "\xA4\x8C" => "\xEC\xA8\xB2", + "\xA4\x8D" => "\xEC\xA8\xB3", + "\xA4\x8E" => "\xEC\xA8\xB4", + "\xA4\x8F" => "\xEC\xA8\xB5", + "\xA4\x90" => "\xEC\xA8\xB6", + "\xA4\x91" => "\xEC\xA8\xB7", + "\xA4\x92" => "\xEC\xA8\xB8", + "\xA4\x93" => "\xEC\xA8\xB9", + "\xA4\x94" => "\xEC\xA8\xBA", + "\xA4\x95" => "\xEC\xA8\xBB", + "\xA4\x96" => "\xEC\xA8\xBC", + "\xA4\x97" => "\xEC\xA8\xBD", + "\xA4\x98" => "\xEC\xA8\xBE", + "\xA4\x99" => "\xEC\xA8\xBF", + "\xA4\x9A" => "\xEC\xA9\x80", + "\xA4\x9B" => "\xEC\xA9\x81", + "\xA4\x9C" => "\xEC\xA9\x82", + "\xA4\x9D" => "\xEC\xA9\x83", + "\xA4\x9E" => "\xEC\xA9\x84", + "\xA4\x9F" => "\xEC\xA9\x85", + "\xA4\xA0" => "\xEC\xA9\x86", + "\xA4\xA1" => "\xE3\x84\xB1", + "\xA4\xA2" => "\xE3\x84\xB2", + "\xA4\xA3" => "\xE3\x84\xB3", + "\xA4\xA4" => "\xE3\x84\xB4", + "\xA4\xA5" => "\xE3\x84\xB5", + "\xA4\xA6" => "\xE3\x84\xB6", + "\xA4\xA7" => "\xE3\x84\xB7", + "\xA4\xA8" => "\xE3\x84\xB8", + "\xA4\xA9" => "\xE3\x84\xB9", + "\xA4\xAA" => "\xE3\x84\xBA", + "\xA4\xAB" => "\xE3\x84\xBB", + "\xA4\xAC" => "\xE3\x84\xBC", + "\xA4\xAD" => "\xE3\x84\xBD", + "\xA4\xAE" => "\xE3\x84\xBE", + "\xA4\xAF" => "\xE3\x84\xBF", + "\xA4\xB0" => "\xE3\x85\x80", + "\xA4\xB1" => "\xE3\x85\x81", + "\xA4\xB2" => "\xE3\x85\x82", + "\xA4\xB3" => "\xE3\x85\x83", + "\xA4\xB4" => "\xE3\x85\x84", + "\xA4\xB5" => "\xE3\x85\x85", + "\xA4\xB6" => "\xE3\x85\x86", + "\xA4\xB7" => "\xE3\x85\x87", + "\xA4\xB8" => "\xE3\x85\x88", + "\xA4\xB9" => "\xE3\x85\x89", + "\xA4\xBA" => "\xE3\x85\x8A", + "\xA4\xBB" => "\xE3\x85\x8B", + "\xA4\xBC" => "\xE3\x85\x8C", + "\xA4\xBD" => "\xE3\x85\x8D", + "\xA4\xBE" => "\xE3\x85\x8E", + "\xA4\xBF" => "\xE3\x85\x8F", + "\xA4\xC0" => "\xE3\x85\x90", + "\xA4\xC1" => "\xE3\x85\x91", + "\xA4\xC2" => "\xE3\x85\x92", + "\xA4\xC3" => "\xE3\x85\x93", + "\xA4\xC4" => "\xE3\x85\x94", + "\xA4\xC5" => "\xE3\x85\x95", + "\xA4\xC6" => "\xE3\x85\x96", + "\xA4\xC7" => "\xE3\x85\x97", + "\xA4\xC8" => "\xE3\x85\x98", + "\xA4\xC9" => "\xE3\x85\x99", + "\xA4\xCA" => "\xE3\x85\x9A", + "\xA4\xCB" => "\xE3\x85\x9B", + "\xA4\xCC" => "\xE3\x85\x9C", + "\xA4\xCD" => "\xE3\x85\x9D", + "\xA4\xCE" => "\xE3\x85\x9E", + "\xA4\xCF" => "\xE3\x85\x9F", + "\xA4\xD0" => "\xE3\x85\xA0", + "\xA4\xD1" => "\xE3\x85\xA1", + "\xA4\xD2" => "\xE3\x85\xA2", + "\xA4\xD3" => "\xE3\x85\xA3", + "\xA4\xD4" => "\xE3\x85\xA4", + "\xA4\xD5" => "\xE3\x85\xA5", + "\xA4\xD6" => "\xE3\x85\xA6", + "\xA4\xD7" => "\xE3\x85\xA7", + "\xA4\xD8" => "\xE3\x85\xA8", + "\xA4\xD9" => "\xE3\x85\xA9", + "\xA4\xDA" => "\xE3\x85\xAA", + "\xA4\xDB" => "\xE3\x85\xAB", + "\xA4\xDC" => "\xE3\x85\xAC", + "\xA4\xDD" => "\xE3\x85\xAD", + "\xA4\xDE" => "\xE3\x85\xAE", + "\xA4\xDF" => "\xE3\x85\xAF", + "\xA4\xE0" => "\xE3\x85\xB0", + "\xA4\xE1" => "\xE3\x85\xB1", + "\xA4\xE2" => "\xE3\x85\xB2", + "\xA4\xE3" => "\xE3\x85\xB3", + "\xA4\xE4" => "\xE3\x85\xB4", + "\xA4\xE5" => "\xE3\x85\xB5", + "\xA4\xE6" => "\xE3\x85\xB6", + "\xA4\xE7" => "\xE3\x85\xB7", + "\xA4\xE8" => "\xE3\x85\xB8", + "\xA4\xE9" => "\xE3\x85\xB9", + "\xA4\xEA" => "\xE3\x85\xBA", + "\xA4\xEB" => "\xE3\x85\xBB", + "\xA4\xEC" => "\xE3\x85\xBC", + "\xA4\xED" => "\xE3\x85\xBD", + "\xA4\xEE" => "\xE3\x85\xBE", + "\xA4\xEF" => "\xE3\x85\xBF", + "\xA4\xF0" => "\xE3\x86\x80", + "\xA4\xF1" => "\xE3\x86\x81", + "\xA4\xF2" => "\xE3\x86\x82", + "\xA4\xF3" => "\xE3\x86\x83", + "\xA4\xF4" => "\xE3\x86\x84", + "\xA4\xF5" => "\xE3\x86\x85", + "\xA4\xF6" => "\xE3\x86\x86", + "\xA4\xF7" => "\xE3\x86\x87", + "\xA4\xF8" => "\xE3\x86\x88", + "\xA4\xF9" => "\xE3\x86\x89", + "\xA4\xFA" => "\xE3\x86\x8A", + "\xA4\xFB" => "\xE3\x86\x8B", + "\xA4\xFC" => "\xE3\x86\x8C", + "\xA4\xFD" => "\xE3\x86\x8D", + "\xA4\xFE" => "\xE3\x86\x8E", + "\xA5\x41" => "\xEC\xA9\x87", + "\xA5\x42" => "\xEC\xA9\x88", + "\xA5\x43" => "\xEC\xA9\x89", + "\xA5\x44" => "\xEC\xA9\x8A", + "\xA5\x45" => "\xEC\xA9\x8B", + "\xA5\x46" => "\xEC\xA9\x8E", + "\xA5\x47" => "\xEC\xA9\x8F", + "\xA5\x48" => "\xEC\xA9\x91", + "\xA5\x49" => "\xEC\xA9\x92", + "\xA5\x4A" => "\xEC\xA9\x93", + "\xA5\x4B" => "\xEC\xA9\x95", + "\xA5\x4C" => "\xEC\xA9\x96", + "\xA5\x4D" => "\xEC\xA9\x97", + "\xA5\x4E" => "\xEC\xA9\x98", + "\xA5\x4F" => "\xEC\xA9\x99", + "\xA5\x50" => "\xEC\xA9\x9A", + "\xA5\x51" => "\xEC\xA9\x9B", + "\xA5\x52" => "\xEC\xA9\x9E", + "\xA5\x53" => "\xEC\xA9\xA2", + "\xA5\x54" => "\xEC\xA9\xA3", + "\xA5\x55" => "\xEC\xA9\xA4", + "\xA5\x56" => "\xEC\xA9\xA5", + "\xA5\x57" => "\xEC\xA9\xA6", + "\xA5\x58" => "\xEC\xA9\xA7", + "\xA5\x59" => "\xEC\xA9\xA9", + "\xA5\x5A" => "\xEC\xA9\xAA", + "\xA5\x61" => "\xEC\xA9\xAB", + "\xA5\x62" => "\xEC\xA9\xAC", + "\xA5\x63" => "\xEC\xA9\xAD", + "\xA5\x64" => "\xEC\xA9\xAE", + "\xA5\x65" => "\xEC\xA9\xAF", + "\xA5\x66" => "\xEC\xA9\xB0", + "\xA5\x67" => "\xEC\xA9\xB1", + "\xA5\x68" => "\xEC\xA9\xB2", + "\xA5\x69" => "\xEC\xA9\xB3", + "\xA5\x6A" => "\xEC\xA9\xB4", + "\xA5\x6B" => "\xEC\xA9\xB5", + "\xA5\x6C" => "\xEC\xA9\xB6", + "\xA5\x6D" => "\xEC\xA9\xB7", + "\xA5\x6E" => "\xEC\xA9\xB8", + "\xA5\x6F" => "\xEC\xA9\xB9", + "\xA5\x70" => "\xEC\xA9\xBA", + "\xA5\x71" => "\xEC\xA9\xBB", + "\xA5\x72" => "\xEC\xA9\xBC", + "\xA5\x73" => "\xEC\xA9\xBE", + "\xA5\x74" => "\xEC\xA9\xBF", + "\xA5\x75" => "\xEC\xAA\x80", + "\xA5\x76" => "\xEC\xAA\x81", + "\xA5\x77" => "\xEC\xAA\x82", + "\xA5\x78" => "\xEC\xAA\x83", + "\xA5\x79" => "\xEC\xAA\x85", + "\xA5\x7A" => "\xEC\xAA\x86", + "\xA5\x81" => "\xEC\xAA\x87", + "\xA5\x82" => "\xEC\xAA\x88", + "\xA5\x83" => "\xEC\xAA\x89", + "\xA5\x84" => "\xEC\xAA\x8A", + "\xA5\x85" => "\xEC\xAA\x8B", + "\xA5\x86" => "\xEC\xAA\x8C", + "\xA5\x87" => "\xEC\xAA\x8D", + "\xA5\x88" => "\xEC\xAA\x8E", + "\xA5\x89" => "\xEC\xAA\x8F", + "\xA5\x8A" => "\xEC\xAA\x90", + "\xA5\x8B" => "\xEC\xAA\x91", + "\xA5\x8C" => "\xEC\xAA\x92", + "\xA5\x8D" => "\xEC\xAA\x93", + "\xA5\x8E" => "\xEC\xAA\x94", + "\xA5\x8F" => "\xEC\xAA\x95", + "\xA5\x90" => "\xEC\xAA\x96", + "\xA5\x91" => "\xEC\xAA\x97", + "\xA5\x92" => "\xEC\xAA\x99", + "\xA5\x93" => "\xEC\xAA\x9A", + "\xA5\x94" => "\xEC\xAA\x9B", + "\xA5\x95" => "\xEC\xAA\x9C", + "\xA5\x96" => "\xEC\xAA\x9D", + "\xA5\x97" => "\xEC\xAA\x9E", + "\xA5\x98" => "\xEC\xAA\x9F", + "\xA5\x99" => "\xEC\xAA\xA0", + "\xA5\x9A" => "\xEC\xAA\xA1", + "\xA5\x9B" => "\xEC\xAA\xA2", + "\xA5\x9C" => "\xEC\xAA\xA3", + "\xA5\x9D" => "\xEC\xAA\xA4", + "\xA5\x9E" => "\xEC\xAA\xA5", + "\xA5\x9F" => "\xEC\xAA\xA6", + "\xA5\xA0" => "\xEC\xAA\xA7", + "\xA5\xA1" => "\xE2\x85\xB0", + "\xA5\xA2" => "\xE2\x85\xB1", + "\xA5\xA3" => "\xE2\x85\xB2", + "\xA5\xA4" => "\xE2\x85\xB3", + "\xA5\xA5" => "\xE2\x85\xB4", + "\xA5\xA6" => "\xE2\x85\xB5", + "\xA5\xA7" => "\xE2\x85\xB6", + "\xA5\xA8" => "\xE2\x85\xB7", + "\xA5\xA9" => "\xE2\x85\xB8", + "\xA5\xAA" => "\xE2\x85\xB9", + "\xA5\xB0" => "\xE2\x85\xA0", + "\xA5\xB1" => "\xE2\x85\xA1", + "\xA5\xB2" => "\xE2\x85\xA2", + "\xA5\xB3" => "\xE2\x85\xA3", + "\xA5\xB4" => "\xE2\x85\xA4", + "\xA5\xB5" => "\xE2\x85\xA5", + "\xA5\xB6" => "\xE2\x85\xA6", + "\xA5\xB7" => "\xE2\x85\xA7", + "\xA5\xB8" => "\xE2\x85\xA8", + "\xA5\xB9" => "\xE2\x85\xA9", + "\xA5\xC1" => "\xCE\x91", + "\xA5\xC2" => "\xCE\x92", + "\xA5\xC3" => "\xCE\x93", + "\xA5\xC4" => "\xCE\x94", + "\xA5\xC5" => "\xCE\x95", + "\xA5\xC6" => "\xCE\x96", + "\xA5\xC7" => "\xCE\x97", + "\xA5\xC8" => "\xCE\x98", + "\xA5\xC9" => "\xCE\x99", + "\xA5\xCA" => "\xCE\x9A", + "\xA5\xCB" => "\xCE\x9B", + "\xA5\xCC" => "\xCE\x9C", + "\xA5\xCD" => "\xCE\x9D", + "\xA5\xCE" => "\xCE\x9E", + "\xA5\xCF" => "\xCE\x9F", + "\xA5\xD0" => "\xCE\xA0", + "\xA5\xD1" => "\xCE\xA1", + "\xA5\xD2" => "\xCE\xA3", + "\xA5\xD3" => "\xCE\xA4", + "\xA5\xD4" => "\xCE\xA5", + "\xA5\xD5" => "\xCE\xA6", + "\xA5\xD6" => "\xCE\xA7", + "\xA5\xD7" => "\xCE\xA8", + "\xA5\xD8" => "\xCE\xA9", + "\xA5\xE1" => "\xCE\xB1", + "\xA5\xE2" => "\xCE\xB2", + "\xA5\xE3" => "\xCE\xB3", + "\xA5\xE4" => "\xCE\xB4", + "\xA5\xE5" => "\xCE\xB5", + "\xA5\xE6" => "\xCE\xB6", + "\xA5\xE7" => "\xCE\xB7", + "\xA5\xE8" => "\xCE\xB8", + "\xA5\xE9" => "\xCE\xB9", + "\xA5\xEA" => "\xCE\xBA", + "\xA5\xEB" => "\xCE\xBB", + "\xA5\xEC" => "\xCE\xBC", + "\xA5\xED" => "\xCE\xBD", + "\xA5\xEE" => "\xCE\xBE", + "\xA5\xEF" => "\xCE\xBF", + "\xA5\xF0" => "\xCF\x80", + "\xA5\xF1" => "\xCF\x81", + "\xA5\xF2" => "\xCF\x83", + "\xA5\xF3" => "\xCF\x84", + "\xA5\xF4" => "\xCF\x85", + "\xA5\xF5" => "\xCF\x86", + "\xA5\xF6" => "\xCF\x87", + "\xA5\xF7" => "\xCF\x88", + "\xA5\xF8" => "\xCF\x89", + "\xA6\x41" => "\xEC\xAA\xA8", + "\xA6\x42" => "\xEC\xAA\xA9", + "\xA6\x43" => "\xEC\xAA\xAA", + "\xA6\x44" => "\xEC\xAA\xAB", + "\xA6\x45" => "\xEC\xAA\xAC", + "\xA6\x46" => "\xEC\xAA\xAD", + "\xA6\x47" => "\xEC\xAA\xAE", + "\xA6\x48" => "\xEC\xAA\xAF", + "\xA6\x49" => "\xEC\xAA\xB0", + "\xA6\x4A" => "\xEC\xAA\xB1", + "\xA6\x4B" => "\xEC\xAA\xB2", + "\xA6\x4C" => "\xEC\xAA\xB3", + "\xA6\x4D" => "\xEC\xAA\xB4", + "\xA6\x4E" => "\xEC\xAA\xB5", + "\xA6\x4F" => "\xEC\xAA\xB6", + "\xA6\x50" => "\xEC\xAA\xB7", + "\xA6\x51" => "\xEC\xAA\xB8", + "\xA6\x52" => "\xEC\xAA\xB9", + "\xA6\x53" => "\xEC\xAA\xBA", + "\xA6\x54" => "\xEC\xAA\xBB", + "\xA6\x55" => "\xEC\xAA\xBE", + "\xA6\x56" => "\xEC\xAA\xBF", + "\xA6\x57" => "\xEC\xAB\x81", + "\xA6\x58" => "\xEC\xAB\x82", + "\xA6\x59" => "\xEC\xAB\x83", + "\xA6\x5A" => "\xEC\xAB\x85", + "\xA6\x61" => "\xEC\xAB\x86", + "\xA6\x62" => "\xEC\xAB\x87", + "\xA6\x63" => "\xEC\xAB\x88", + "\xA6\x64" => "\xEC\xAB\x89", + "\xA6\x65" => "\xEC\xAB\x8A", + "\xA6\x66" => "\xEC\xAB\x8B", + "\xA6\x67" => "\xEC\xAB\x8E", + "\xA6\x68" => "\xEC\xAB\x90", + "\xA6\x69" => "\xEC\xAB\x92", + "\xA6\x6A" => "\xEC\xAB\x94", + "\xA6\x6B" => "\xEC\xAB\x95", + "\xA6\x6C" => "\xEC\xAB\x96", + "\xA6\x6D" => "\xEC\xAB\x97", + "\xA6\x6E" => "\xEC\xAB\x9A", + "\xA6\x6F" => "\xEC\xAB\x9B", + "\xA6\x70" => "\xEC\xAB\x9C", + "\xA6\x71" => "\xEC\xAB\x9D", + "\xA6\x72" => "\xEC\xAB\x9E", + "\xA6\x73" => "\xEC\xAB\x9F", + "\xA6\x74" => "\xEC\xAB\xA1", + "\xA6\x75" => "\xEC\xAB\xA2", + "\xA6\x76" => "\xEC\xAB\xA3", + "\xA6\x77" => "\xEC\xAB\xA4", + "\xA6\x78" => "\xEC\xAB\xA5", + "\xA6\x79" => "\xEC\xAB\xA6", + "\xA6\x7A" => "\xEC\xAB\xA7", + "\xA6\x81" => "\xEC\xAB\xA8", + "\xA6\x82" => "\xEC\xAB\xA9", + "\xA6\x83" => "\xEC\xAB\xAA", + "\xA6\x84" => "\xEC\xAB\xAB", + "\xA6\x85" => "\xEC\xAB\xAD", + "\xA6\x86" => "\xEC\xAB\xAE", + "\xA6\x87" => "\xEC\xAB\xAF", + "\xA6\x88" => "\xEC\xAB\xB0", + "\xA6\x89" => "\xEC\xAB\xB1", + "\xA6\x8A" => "\xEC\xAB\xB2", + "\xA6\x8B" => "\xEC\xAB\xB3", + "\xA6\x8C" => "\xEC\xAB\xB5", + "\xA6\x8D" => "\xEC\xAB\xB6", + "\xA6\x8E" => "\xEC\xAB\xB7", + "\xA6\x8F" => "\xEC\xAB\xB8", + "\xA6\x90" => "\xEC\xAB\xB9", + "\xA6\x91" => "\xEC\xAB\xBA", + "\xA6\x92" => "\xEC\xAB\xBB", + "\xA6\x93" => "\xEC\xAB\xBC", + "\xA6\x94" => "\xEC\xAB\xBD", + "\xA6\x95" => "\xEC\xAB\xBE", + "\xA6\x96" => "\xEC\xAB\xBF", + "\xA6\x97" => "\xEC\xAC\x80", + "\xA6\x98" => "\xEC\xAC\x81", + "\xA6\x99" => "\xEC\xAC\x82", + "\xA6\x9A" => "\xEC\xAC\x83", + "\xA6\x9B" => "\xEC\xAC\x84", + "\xA6\x9C" => "\xEC\xAC\x85", + "\xA6\x9D" => "\xEC\xAC\x86", + "\xA6\x9E" => "\xEC\xAC\x87", + "\xA6\x9F" => "\xEC\xAC\x89", + "\xA6\xA0" => "\xEC\xAC\x8A", + "\xA6\xA1" => "\xE2\x94\x80", + "\xA6\xA2" => "\xE2\x94\x82", + "\xA6\xA3" => "\xE2\x94\x8C", + "\xA6\xA4" => "\xE2\x94\x90", + "\xA6\xA5" => "\xE2\x94\x98", + "\xA6\xA6" => "\xE2\x94\x94", + "\xA6\xA7" => "\xE2\x94\x9C", + "\xA6\xA8" => "\xE2\x94\xAC", + "\xA6\xA9" => "\xE2\x94\xA4", + "\xA6\xAA" => "\xE2\x94\xB4", + "\xA6\xAB" => "\xE2\x94\xBC", + "\xA6\xAC" => "\xE2\x94\x81", + "\xA6\xAD" => "\xE2\x94\x83", + "\xA6\xAE" => "\xE2\x94\x8F", + "\xA6\xAF" => "\xE2\x94\x93", + "\xA6\xB0" => "\xE2\x94\x9B", + "\xA6\xB1" => "\xE2\x94\x97", + "\xA6\xB2" => "\xE2\x94\xA3", + "\xA6\xB3" => "\xE2\x94\xB3", + "\xA6\xB4" => "\xE2\x94\xAB", + "\xA6\xB5" => "\xE2\x94\xBB", + "\xA6\xB6" => "\xE2\x95\x8B", + "\xA6\xB7" => "\xE2\x94\xA0", + "\xA6\xB8" => "\xE2\x94\xAF", + "\xA6\xB9" => "\xE2\x94\xA8", + "\xA6\xBA" => "\xE2\x94\xB7", + "\xA6\xBB" => "\xE2\x94\xBF", + "\xA6\xBC" => "\xE2\x94\x9D", + "\xA6\xBD" => "\xE2\x94\xB0", + "\xA6\xBE" => "\xE2\x94\xA5", + "\xA6\xBF" => "\xE2\x94\xB8", + "\xA6\xC0" => "\xE2\x95\x82", + "\xA6\xC1" => "\xE2\x94\x92", + "\xA6\xC2" => "\xE2\x94\x91", + "\xA6\xC3" => "\xE2\x94\x9A", + "\xA6\xC4" => "\xE2\x94\x99", + "\xA6\xC5" => "\xE2\x94\x96", + "\xA6\xC6" => "\xE2\x94\x95", + "\xA6\xC7" => "\xE2\x94\x8E", + "\xA6\xC8" => "\xE2\x94\x8D", + "\xA6\xC9" => "\xE2\x94\x9E", + "\xA6\xCA" => "\xE2\x94\x9F", + "\xA6\xCB" => "\xE2\x94\xA1", + "\xA6\xCC" => "\xE2\x94\xA2", + "\xA6\xCD" => "\xE2\x94\xA6", + "\xA6\xCE" => "\xE2\x94\xA7", + "\xA6\xCF" => "\xE2\x94\xA9", + "\xA6\xD0" => "\xE2\x94\xAA", + "\xA6\xD1" => "\xE2\x94\xAD", + "\xA6\xD2" => "\xE2\x94\xAE", + "\xA6\xD3" => "\xE2\x94\xB1", + "\xA6\xD4" => "\xE2\x94\xB2", + "\xA6\xD5" => "\xE2\x94\xB5", + "\xA6\xD6" => "\xE2\x94\xB6", + "\xA6\xD7" => "\xE2\x94\xB9", + "\xA6\xD8" => "\xE2\x94\xBA", + "\xA6\xD9" => "\xE2\x94\xBD", + "\xA6\xDA" => "\xE2\x94\xBE", + "\xA6\xDB" => "\xE2\x95\x80", + "\xA6\xDC" => "\xE2\x95\x81", + "\xA6\xDD" => "\xE2\x95\x83", + "\xA6\xDE" => "\xE2\x95\x84", + "\xA6\xDF" => "\xE2\x95\x85", + "\xA6\xE0" => "\xE2\x95\x86", + "\xA6\xE1" => "\xE2\x95\x87", + "\xA6\xE2" => "\xE2\x95\x88", + "\xA6\xE3" => "\xE2\x95\x89", + "\xA6\xE4" => "\xE2\x95\x8A", + "\xA7\x41" => "\xEC\xAC\x8B", + "\xA7\x42" => "\xEC\xAC\x8C", + "\xA7\x43" => "\xEC\xAC\x8D", + "\xA7\x44" => "\xEC\xAC\x8E", + "\xA7\x45" => "\xEC\xAC\x8F", + "\xA7\x46" => "\xEC\xAC\x91", + "\xA7\x47" => "\xEC\xAC\x92", + "\xA7\x48" => "\xEC\xAC\x93", + "\xA7\x49" => "\xEC\xAC\x95", + "\xA7\x4A" => "\xEC\xAC\x96", + "\xA7\x4B" => "\xEC\xAC\x97", + "\xA7\x4C" => "\xEC\xAC\x99", + "\xA7\x4D" => "\xEC\xAC\x9A", + "\xA7\x4E" => "\xEC\xAC\x9B", + "\xA7\x4F" => "\xEC\xAC\x9C", + "\xA7\x50" => "\xEC\xAC\x9D", + "\xA7\x51" => "\xEC\xAC\x9E", + "\xA7\x52" => "\xEC\xAC\x9F", + "\xA7\x53" => "\xEC\xAC\xA2", + "\xA7\x54" => "\xEC\xAC\xA3", + "\xA7\x55" => "\xEC\xAC\xA4", + "\xA7\x56" => "\xEC\xAC\xA5", + "\xA7\x57" => "\xEC\xAC\xA6", + "\xA7\x58" => "\xEC\xAC\xA7", + "\xA7\x59" => "\xEC\xAC\xA8", + "\xA7\x5A" => "\xEC\xAC\xA9", + "\xA7\x61" => "\xEC\xAC\xAA", + "\xA7\x62" => "\xEC\xAC\xAB", + "\xA7\x63" => "\xEC\xAC\xAC", + "\xA7\x64" => "\xEC\xAC\xAD", + "\xA7\x65" => "\xEC\xAC\xAE", + "\xA7\x66" => "\xEC\xAC\xAF", + "\xA7\x67" => "\xEC\xAC\xB0", + "\xA7\x68" => "\xEC\xAC\xB1", + "\xA7\x69" => "\xEC\xAC\xB2", + "\xA7\x6A" => "\xEC\xAC\xB3", + "\xA7\x6B" => "\xEC\xAC\xB4", + "\xA7\x6C" => "\xEC\xAC\xB5", + "\xA7\x6D" => "\xEC\xAC\xB6", + "\xA7\x6E" => "\xEC\xAC\xB7", + "\xA7\x6F" => "\xEC\xAC\xB8", + "\xA7\x70" => "\xEC\xAC\xB9", + "\xA7\x71" => "\xEC\xAC\xBA", + "\xA7\x72" => "\xEC\xAC\xBB", + "\xA7\x73" => "\xEC\xAC\xBC", + "\xA7\x74" => "\xEC\xAC\xBD", + "\xA7\x75" => "\xEC\xAC\xBE", + "\xA7\x76" => "\xEC\xAC\xBF", + "\xA7\x77" => "\xEC\xAD\x80", + "\xA7\x78" => "\xEC\xAD\x82", + "\xA7\x79" => "\xEC\xAD\x83", + "\xA7\x7A" => "\xEC\xAD\x84", + "\xA7\x81" => "\xEC\xAD\x85", + "\xA7\x82" => "\xEC\xAD\x86", + "\xA7\x83" => "\xEC\xAD\x87", + "\xA7\x84" => "\xEC\xAD\x8A", + "\xA7\x85" => "\xEC\xAD\x8B", + "\xA7\x86" => "\xEC\xAD\x8D", + "\xA7\x87" => "\xEC\xAD\x8E", + "\xA7\x88" => "\xEC\xAD\x8F", + "\xA7\x89" => "\xEC\xAD\x91", + "\xA7\x8A" => "\xEC\xAD\x92", + "\xA7\x8B" => "\xEC\xAD\x93", + "\xA7\x8C" => "\xEC\xAD\x94", + "\xA7\x8D" => "\xEC\xAD\x95", + "\xA7\x8E" => "\xEC\xAD\x96", + "\xA7\x8F" => "\xEC\xAD\x97", + "\xA7\x90" => "\xEC\xAD\x9A", + "\xA7\x91" => "\xEC\xAD\x9B", + "\xA7\x92" => "\xEC\xAD\x9C", + "\xA7\x93" => "\xEC\xAD\x9E", + "\xA7\x94" => "\xEC\xAD\x9F", + "\xA7\x95" => "\xEC\xAD\xA0", + "\xA7\x96" => "\xEC\xAD\xA1", + "\xA7\x97" => "\xEC\xAD\xA2", + "\xA7\x98" => "\xEC\xAD\xA3", + "\xA7\x99" => "\xEC\xAD\xA5", + "\xA7\x9A" => "\xEC\xAD\xA6", + "\xA7\x9B" => "\xEC\xAD\xA7", + "\xA7\x9C" => "\xEC\xAD\xA8", + "\xA7\x9D" => "\xEC\xAD\xA9", + "\xA7\x9E" => "\xEC\xAD\xAA", + "\xA7\x9F" => "\xEC\xAD\xAB", + "\xA7\xA0" => "\xEC\xAD\xAC", + "\xA7\xA1" => "\xE3\x8E\x95", + "\xA7\xA2" => "\xE3\x8E\x96", + "\xA7\xA3" => "\xE3\x8E\x97", + "\xA7\xA4" => "\xE2\x84\x93", + "\xA7\xA5" => "\xE3\x8E\x98", + "\xA7\xA6" => "\xE3\x8F\x84", + "\xA7\xA7" => "\xE3\x8E\xA3", + "\xA7\xA8" => "\xE3\x8E\xA4", + "\xA7\xA9" => "\xE3\x8E\xA5", + "\xA7\xAA" => "\xE3\x8E\xA6", + "\xA7\xAB" => "\xE3\x8E\x99", + "\xA7\xAC" => "\xE3\x8E\x9A", + "\xA7\xAD" => "\xE3\x8E\x9B", + "\xA7\xAE" => "\xE3\x8E\x9C", + "\xA7\xAF" => "\xE3\x8E\x9D", + "\xA7\xB0" => "\xE3\x8E\x9E", + "\xA7\xB1" => "\xE3\x8E\x9F", + "\xA7\xB2" => "\xE3\x8E\xA0", + "\xA7\xB3" => "\xE3\x8E\xA1", + "\xA7\xB4" => "\xE3\x8E\xA2", + "\xA7\xB5" => "\xE3\x8F\x8A", + "\xA7\xB6" => "\xE3\x8E\x8D", + "\xA7\xB7" => "\xE3\x8E\x8E", + "\xA7\xB8" => "\xE3\x8E\x8F", + "\xA7\xB9" => "\xE3\x8F\x8F", + "\xA7\xBA" => "\xE3\x8E\x88", + "\xA7\xBB" => "\xE3\x8E\x89", + "\xA7\xBC" => "\xE3\x8F\x88", + "\xA7\xBD" => "\xE3\x8E\xA7", + "\xA7\xBE" => "\xE3\x8E\xA8", + "\xA7\xBF" => "\xE3\x8E\xB0", + "\xA7\xC0" => "\xE3\x8E\xB1", + "\xA7\xC1" => "\xE3\x8E\xB2", + "\xA7\xC2" => "\xE3\x8E\xB3", + "\xA7\xC3" => "\xE3\x8E\xB4", + "\xA7\xC4" => "\xE3\x8E\xB5", + "\xA7\xC5" => "\xE3\x8E\xB6", + "\xA7\xC6" => "\xE3\x8E\xB7", + "\xA7\xC7" => "\xE3\x8E\xB8", + "\xA7\xC8" => "\xE3\x8E\xB9", + "\xA7\xC9" => "\xE3\x8E\x80", + "\xA7\xCA" => "\xE3\x8E\x81", + "\xA7\xCB" => "\xE3\x8E\x82", + "\xA7\xCC" => "\xE3\x8E\x83", + "\xA7\xCD" => "\xE3\x8E\x84", + "\xA7\xCE" => "\xE3\x8E\xBA", + "\xA7\xCF" => "\xE3\x8E\xBB", + "\xA7\xD0" => "\xE3\x8E\xBC", + "\xA7\xD1" => "\xE3\x8E\xBD", + "\xA7\xD2" => "\xE3\x8E\xBE", + "\xA7\xD3" => "\xE3\x8E\xBF", + "\xA7\xD4" => "\xE3\x8E\x90", + "\xA7\xD5" => "\xE3\x8E\x91", + "\xA7\xD6" => "\xE3\x8E\x92", + "\xA7\xD7" => "\xE3\x8E\x93", + "\xA7\xD8" => "\xE3\x8E\x94", + "\xA7\xD9" => "\xE2\x84\xA6", + "\xA7\xDA" => "\xE3\x8F\x80", + "\xA7\xDB" => "\xE3\x8F\x81", + "\xA7\xDC" => "\xE3\x8E\x8A", + "\xA7\xDD" => "\xE3\x8E\x8B", + "\xA7\xDE" => "\xE3\x8E\x8C", + "\xA7\xDF" => "\xE3\x8F\x96", + "\xA7\xE0" => "\xE3\x8F\x85", + "\xA7\xE1" => "\xE3\x8E\xAD", + "\xA7\xE2" => "\xE3\x8E\xAE", + "\xA7\xE3" => "\xE3\x8E\xAF", + "\xA7\xE4" => "\xE3\x8F\x9B", + "\xA7\xE5" => "\xE3\x8E\xA9", + "\xA7\xE6" => "\xE3\x8E\xAA", + "\xA7\xE7" => "\xE3\x8E\xAB", + "\xA7\xE8" => "\xE3\x8E\xAC", + "\xA7\xE9" => "\xE3\x8F\x9D", + "\xA7\xEA" => "\xE3\x8F\x90", + "\xA7\xEB" => "\xE3\x8F\x93", + "\xA7\xEC" => "\xE3\x8F\x83", + "\xA7\xED" => "\xE3\x8F\x89", + "\xA7\xEE" => "\xE3\x8F\x9C", + "\xA7\xEF" => "\xE3\x8F\x86", + "\xA8\x41" => "\xEC\xAD\xAD", + "\xA8\x42" => "\xEC\xAD\xAE", + "\xA8\x43" => "\xEC\xAD\xAF", + "\xA8\x44" => "\xEC\xAD\xB0", + "\xA8\x45" => "\xEC\xAD\xB1", + "\xA8\x46" => "\xEC\xAD\xB2", + "\xA8\x47" => "\xEC\xAD\xB3", + "\xA8\x48" => "\xEC\xAD\xB4", + "\xA8\x49" => "\xEC\xAD\xB5", + "\xA8\x4A" => "\xEC\xAD\xB6", + "\xA8\x4B" => "\xEC\xAD\xB7", + "\xA8\x4C" => "\xEC\xAD\xBA", + "\xA8\x4D" => "\xEC\xAD\xBB", + "\xA8\x4E" => "\xEC\xAD\xBC", + "\xA8\x4F" => "\xEC\xAD\xBD", + "\xA8\x50" => "\xEC\xAD\xBE", + "\xA8\x51" => "\xEC\xAD\xBF", + "\xA8\x52" => "\xEC\xAE\x80", + "\xA8\x53" => "\xEC\xAE\x81", + "\xA8\x54" => "\xEC\xAE\x82", + "\xA8\x55" => "\xEC\xAE\x83", + "\xA8\x56" => "\xEC\xAE\x84", + "\xA8\x57" => "\xEC\xAE\x85", + "\xA8\x58" => "\xEC\xAE\x86", + "\xA8\x59" => "\xEC\xAE\x87", + "\xA8\x5A" => "\xEC\xAE\x88", + "\xA8\x61" => "\xEC\xAE\x89", + "\xA8\x62" => "\xEC\xAE\x8A", + "\xA8\x63" => "\xEC\xAE\x8B", + "\xA8\x64" => "\xEC\xAE\x8C", + "\xA8\x65" => "\xEC\xAE\x8D", + "\xA8\x66" => "\xEC\xAE\x8E", + "\xA8\x67" => "\xEC\xAE\x8F", + "\xA8\x68" => "\xEC\xAE\x90", + "\xA8\x69" => "\xEC\xAE\x91", + "\xA8\x6A" => "\xEC\xAE\x92", + "\xA8\x6B" => "\xEC\xAE\x93", + "\xA8\x6C" => "\xEC\xAE\x94", + "\xA8\x6D" => "\xEC\xAE\x95", + "\xA8\x6E" => "\xEC\xAE\x96", + "\xA8\x6F" => "\xEC\xAE\x97", + "\xA8\x70" => "\xEC\xAE\x98", + "\xA8\x71" => "\xEC\xAE\x99", + "\xA8\x72" => "\xEC\xAE\x9A", + "\xA8\x73" => "\xEC\xAE\x9B", + "\xA8\x74" => "\xEC\xAE\x9D", + "\xA8\x75" => "\xEC\xAE\x9E", + "\xA8\x76" => "\xEC\xAE\x9F", + "\xA8\x77" => "\xEC\xAE\xA0", + "\xA8\x78" => "\xEC\xAE\xA1", + "\xA8\x79" => "\xEC\xAE\xA2", + "\xA8\x7A" => "\xEC\xAE\xA3", + "\xA8\x81" => "\xEC\xAE\xA4", + "\xA8\x82" => "\xEC\xAE\xA5", + "\xA8\x83" => "\xEC\xAE\xA6", + "\xA8\x84" => "\xEC\xAE\xA7", + "\xA8\x85" => "\xEC\xAE\xA8", + "\xA8\x86" => "\xEC\xAE\xA9", + "\xA8\x87" => "\xEC\xAE\xAA", + "\xA8\x88" => "\xEC\xAE\xAB", + "\xA8\x89" => "\xEC\xAE\xAC", + "\xA8\x8A" => "\xEC\xAE\xAD", + "\xA8\x8B" => "\xEC\xAE\xAE", + "\xA8\x8C" => "\xEC\xAE\xAF", + "\xA8\x8D" => "\xEC\xAE\xB0", + "\xA8\x8E" => "\xEC\xAE\xB1", + "\xA8\x8F" => "\xEC\xAE\xB2", + "\xA8\x90" => "\xEC\xAE\xB3", + "\xA8\x91" => "\xEC\xAE\xB4", + "\xA8\x92" => "\xEC\xAE\xB5", + "\xA8\x93" => "\xEC\xAE\xB6", + "\xA8\x94" => "\xEC\xAE\xB7", + "\xA8\x95" => "\xEC\xAE\xB9", + "\xA8\x96" => "\xEC\xAE\xBA", + "\xA8\x97" => "\xEC\xAE\xBB", + "\xA8\x98" => "\xEC\xAE\xBC", + "\xA8\x99" => "\xEC\xAE\xBD", + "\xA8\x9A" => "\xEC\xAE\xBE", + "\xA8\x9B" => "\xEC\xAE\xBF", + "\xA8\x9C" => "\xEC\xAF\x80", + "\xA8\x9D" => "\xEC\xAF\x81", + "\xA8\x9E" => "\xEC\xAF\x82", + "\xA8\x9F" => "\xEC\xAF\x83", + "\xA8\xA0" => "\xEC\xAF\x84", + "\xA8\xA1" => "\xC3\x86", + "\xA8\xA2" => "\xC3\x90", + "\xA8\xA3" => "\xC2\xAA", + "\xA8\xA4" => "\xC4\xA6", + "\xA8\xA6" => "\xC4\xB2", + "\xA8\xA8" => "\xC4\xBF", + "\xA8\xA9" => "\xC5\x81", + "\xA8\xAA" => "\xC3\x98", + "\xA8\xAB" => "\xC5\x92", + "\xA8\xAC" => "\xC2\xBA", + "\xA8\xAD" => "\xC3\x9E", + "\xA8\xAE" => "\xC5\xA6", + "\xA8\xAF" => "\xC5\x8A", + "\xA8\xB1" => "\xE3\x89\xA0", + "\xA8\xB2" => "\xE3\x89\xA1", + "\xA8\xB3" => "\xE3\x89\xA2", + "\xA8\xB4" => "\xE3\x89\xA3", + "\xA8\xB5" => "\xE3\x89\xA4", + "\xA8\xB6" => "\xE3\x89\xA5", + "\xA8\xB7" => "\xE3\x89\xA6", + "\xA8\xB8" => "\xE3\x89\xA7", + "\xA8\xB9" => "\xE3\x89\xA8", + "\xA8\xBA" => "\xE3\x89\xA9", + "\xA8\xBB" => "\xE3\x89\xAA", + "\xA8\xBC" => "\xE3\x89\xAB", + "\xA8\xBD" => "\xE3\x89\xAC", + "\xA8\xBE" => "\xE3\x89\xAD", + "\xA8\xBF" => "\xE3\x89\xAE", + "\xA8\xC0" => "\xE3\x89\xAF", + "\xA8\xC1" => "\xE3\x89\xB0", + "\xA8\xC2" => "\xE3\x89\xB1", + "\xA8\xC3" => "\xE3\x89\xB2", + "\xA8\xC4" => "\xE3\x89\xB3", + "\xA8\xC5" => "\xE3\x89\xB4", + "\xA8\xC6" => "\xE3\x89\xB5", + "\xA8\xC7" => "\xE3\x89\xB6", + "\xA8\xC8" => "\xE3\x89\xB7", + "\xA8\xC9" => "\xE3\x89\xB8", + "\xA8\xCA" => "\xE3\x89\xB9", + "\xA8\xCB" => "\xE3\x89\xBA", + "\xA8\xCC" => "\xE3\x89\xBB", + "\xA8\xCD" => "\xE2\x93\x90", + "\xA8\xCE" => "\xE2\x93\x91", + "\xA8\xCF" => "\xE2\x93\x92", + "\xA8\xD0" => "\xE2\x93\x93", + "\xA8\xD1" => "\xE2\x93\x94", + "\xA8\xD2" => "\xE2\x93\x95", + "\xA8\xD3" => "\xE2\x93\x96", + "\xA8\xD4" => "\xE2\x93\x97", + "\xA8\xD5" => "\xE2\x93\x98", + "\xA8\xD6" => "\xE2\x93\x99", + "\xA8\xD7" => "\xE2\x93\x9A", + "\xA8\xD8" => "\xE2\x93\x9B", + "\xA8\xD9" => "\xE2\x93\x9C", + "\xA8\xDA" => "\xE2\x93\x9D", + "\xA8\xDB" => "\xE2\x93\x9E", + "\xA8\xDC" => "\xE2\x93\x9F", + "\xA8\xDD" => "\xE2\x93\xA0", + "\xA8\xDE" => "\xE2\x93\xA1", + "\xA8\xDF" => "\xE2\x93\xA2", + "\xA8\xE0" => "\xE2\x93\xA3", + "\xA8\xE1" => "\xE2\x93\xA4", + "\xA8\xE2" => "\xE2\x93\xA5", + "\xA8\xE3" => "\xE2\x93\xA6", + "\xA8\xE4" => "\xE2\x93\xA7", + "\xA8\xE5" => "\xE2\x93\xA8", + "\xA8\xE6" => "\xE2\x93\xA9", + "\xA8\xE7" => "\xE2\x91\xA0", + "\xA8\xE8" => "\xE2\x91\xA1", + "\xA8\xE9" => "\xE2\x91\xA2", + "\xA8\xEA" => "\xE2\x91\xA3", + "\xA8\xEB" => "\xE2\x91\xA4", + "\xA8\xEC" => "\xE2\x91\xA5", + "\xA8\xED" => "\xE2\x91\xA6", + "\xA8\xEE" => "\xE2\x91\xA7", + "\xA8\xEF" => "\xE2\x91\xA8", + "\xA8\xF0" => "\xE2\x91\xA9", + "\xA8\xF1" => "\xE2\x91\xAA", + "\xA8\xF2" => "\xE2\x91\xAB", + "\xA8\xF3" => "\xE2\x91\xAC", + "\xA8\xF4" => "\xE2\x91\xAD", + "\xA8\xF5" => "\xE2\x91\xAE", + "\xA8\xF6" => "\xC2\xBD", + "\xA8\xF7" => "\xE2\x85\x93", + "\xA8\xF8" => "\xE2\x85\x94", + "\xA8\xF9" => "\xC2\xBC", + "\xA8\xFA" => "\xC2\xBE", + "\xA8\xFB" => "\xE2\x85\x9B", + "\xA8\xFC" => "\xE2\x85\x9C", + "\xA8\xFD" => "\xE2\x85\x9D", + "\xA8\xFE" => "\xE2\x85\x9E", + "\xA9\x41" => "\xEC\xAF\x85", + "\xA9\x42" => "\xEC\xAF\x86", + "\xA9\x43" => "\xEC\xAF\x87", + "\xA9\x44" => "\xEC\xAF\x88", + "\xA9\x45" => "\xEC\xAF\x89", + "\xA9\x46" => "\xEC\xAF\x8A", + "\xA9\x47" => "\xEC\xAF\x8B", + "\xA9\x48" => "\xEC\xAF\x8C", + "\xA9\x49" => "\xEC\xAF\x8D", + "\xA9\x4A" => "\xEC\xAF\x8E", + "\xA9\x4B" => "\xEC\xAF\x8F", + "\xA9\x4C" => "\xEC\xAF\x90", + "\xA9\x4D" => "\xEC\xAF\x91", + "\xA9\x4E" => "\xEC\xAF\x92", + "\xA9\x4F" => "\xEC\xAF\x93", + "\xA9\x50" => "\xEC\xAF\x95", + "\xA9\x51" => "\xEC\xAF\x96", + "\xA9\x52" => "\xEC\xAF\x97", + "\xA9\x53" => "\xEC\xAF\x98", + "\xA9\x54" => "\xEC\xAF\x99", + "\xA9\x55" => "\xEC\xAF\x9A", + "\xA9\x56" => "\xEC\xAF\x9B", + "\xA9\x57" => "\xEC\xAF\x9C", + "\xA9\x58" => "\xEC\xAF\x9D", + "\xA9\x59" => "\xEC\xAF\x9E", + "\xA9\x5A" => "\xEC\xAF\x9F", + "\xA9\x61" => "\xEC\xAF\xA0", + "\xA9\x62" => "\xEC\xAF\xA1", + "\xA9\x63" => "\xEC\xAF\xA2", + "\xA9\x64" => "\xEC\xAF\xA3", + "\xA9\x65" => "\xEC\xAF\xA5", + "\xA9\x66" => "\xEC\xAF\xA6", + "\xA9\x67" => "\xEC\xAF\xA8", + "\xA9\x68" => "\xEC\xAF\xAA", + "\xA9\x69" => "\xEC\xAF\xAB", + "\xA9\x6A" => "\xEC\xAF\xAC", + "\xA9\x6B" => "\xEC\xAF\xAD", + "\xA9\x6C" => "\xEC\xAF\xAE", + "\xA9\x6D" => "\xEC\xAF\xAF", + "\xA9\x6E" => "\xEC\xAF\xB0", + "\xA9\x6F" => "\xEC\xAF\xB1", + "\xA9\x70" => "\xEC\xAF\xB2", + "\xA9\x71" => "\xEC\xAF\xB3", + "\xA9\x72" => "\xEC\xAF\xB4", + "\xA9\x73" => "\xEC\xAF\xB5", + "\xA9\x74" => "\xEC\xAF\xB6", + "\xA9\x75" => "\xEC\xAF\xB7", + "\xA9\x76" => "\xEC\xAF\xB8", + "\xA9\x77" => "\xEC\xAF\xB9", + "\xA9\x78" => "\xEC\xAF\xBA", + "\xA9\x79" => "\xEC\xAF\xBB", + "\xA9\x7A" => "\xEC\xAF\xBC", + "\xA9\x81" => "\xEC\xAF\xBD", + "\xA9\x82" => "\xEC\xAF\xBE", + "\xA9\x83" => "\xEC\xAF\xBF", + "\xA9\x84" => "\xEC\xB0\x80", + "\xA9\x85" => "\xEC\xB0\x81", + "\xA9\x86" => "\xEC\xB0\x82", + "\xA9\x87" => "\xEC\xB0\x83", + "\xA9\x88" => "\xEC\xB0\x84", + "\xA9\x89" => "\xEC\xB0\x85", + "\xA9\x8A" => "\xEC\xB0\x86", + "\xA9\x8B" => "\xEC\xB0\x87", + "\xA9\x8C" => "\xEC\xB0\x88", + "\xA9\x8D" => "\xEC\xB0\x89", + "\xA9\x8E" => "\xEC\xB0\x8A", + "\xA9\x8F" => "\xEC\xB0\x8B", + "\xA9\x90" => "\xEC\xB0\x8E", + "\xA9\x91" => "\xEC\xB0\x8F", + "\xA9\x92" => "\xEC\xB0\x91", + "\xA9\x93" => "\xEC\xB0\x92", + "\xA9\x94" => "\xEC\xB0\x93", + "\xA9\x95" => "\xEC\xB0\x95", + "\xA9\x96" => "\xEC\xB0\x96", + "\xA9\x97" => "\xEC\xB0\x97", + "\xA9\x98" => "\xEC\xB0\x98", + "\xA9\x99" => "\xEC\xB0\x99", + "\xA9\x9A" => "\xEC\xB0\x9A", + "\xA9\x9B" => "\xEC\xB0\x9B", + "\xA9\x9C" => "\xEC\xB0\x9E", + "\xA9\x9D" => "\xEC\xB0\x9F", + "\xA9\x9E" => "\xEC\xB0\xA0", + "\xA9\x9F" => "\xEC\xB0\xA3", + "\xA9\xA0" => "\xEC\xB0\xA4", + "\xA9\xA1" => "\xC3\xA6", + "\xA9\xA2" => "\xC4\x91", + "\xA9\xA3" => "\xC3\xB0", + "\xA9\xA4" => "\xC4\xA7", + "\xA9\xA5" => "\xC4\xB1", + "\xA9\xA6" => "\xC4\xB3", + "\xA9\xA7" => "\xC4\xB8", + "\xA9\xA8" => "\xC5\x80", + "\xA9\xA9" => "\xC5\x82", + "\xA9\xAA" => "\xC3\xB8", + "\xA9\xAB" => "\xC5\x93", + "\xA9\xAC" => "\xC3\x9F", + "\xA9\xAD" => "\xC3\xBE", + "\xA9\xAE" => "\xC5\xA7", + "\xA9\xAF" => "\xC5\x8B", + "\xA9\xB0" => "\xC5\x89", + "\xA9\xB1" => "\xE3\x88\x80", + "\xA9\xB2" => "\xE3\x88\x81", + "\xA9\xB3" => "\xE3\x88\x82", + "\xA9\xB4" => "\xE3\x88\x83", + "\xA9\xB5" => "\xE3\x88\x84", + "\xA9\xB6" => "\xE3\x88\x85", + "\xA9\xB7" => "\xE3\x88\x86", + "\xA9\xB8" => "\xE3\x88\x87", + "\xA9\xB9" => "\xE3\x88\x88", + "\xA9\xBA" => "\xE3\x88\x89", + "\xA9\xBB" => "\xE3\x88\x8A", + "\xA9\xBC" => "\xE3\x88\x8B", + "\xA9\xBD" => "\xE3\x88\x8C", + "\xA9\xBE" => "\xE3\x88\x8D", + "\xA9\xBF" => "\xE3\x88\x8E", + "\xA9\xC0" => "\xE3\x88\x8F", + "\xA9\xC1" => "\xE3\x88\x90", + "\xA9\xC2" => "\xE3\x88\x91", + "\xA9\xC3" => "\xE3\x88\x92", + "\xA9\xC4" => "\xE3\x88\x93", + "\xA9\xC5" => "\xE3\x88\x94", + "\xA9\xC6" => "\xE3\x88\x95", + "\xA9\xC7" => "\xE3\x88\x96", + "\xA9\xC8" => "\xE3\x88\x97", + "\xA9\xC9" => "\xE3\x88\x98", + "\xA9\xCA" => "\xE3\x88\x99", + "\xA9\xCB" => "\xE3\x88\x9A", + "\xA9\xCC" => "\xE3\x88\x9B", + "\xA9\xCD" => "\xE2\x92\x9C", + "\xA9\xCE" => "\xE2\x92\x9D", + "\xA9\xCF" => "\xE2\x92\x9E", + "\xA9\xD0" => "\xE2\x92\x9F", + "\xA9\xD1" => "\xE2\x92\xA0", + "\xA9\xD2" => "\xE2\x92\xA1", + "\xA9\xD3" => "\xE2\x92\xA2", + "\xA9\xD4" => "\xE2\x92\xA3", + "\xA9\xD5" => "\xE2\x92\xA4", + "\xA9\xD6" => "\xE2\x92\xA5", + "\xA9\xD7" => "\xE2\x92\xA6", + "\xA9\xD8" => "\xE2\x92\xA7", + "\xA9\xD9" => "\xE2\x92\xA8", + "\xA9\xDA" => "\xE2\x92\xA9", + "\xA9\xDB" => "\xE2\x92\xAA", + "\xA9\xDC" => "\xE2\x92\xAB", + "\xA9\xDD" => "\xE2\x92\xAC", + "\xA9\xDE" => "\xE2\x92\xAD", + "\xA9\xDF" => "\xE2\x92\xAE", + "\xA9\xE0" => "\xE2\x92\xAF", + "\xA9\xE1" => "\xE2\x92\xB0", + "\xA9\xE2" => "\xE2\x92\xB1", + "\xA9\xE3" => "\xE2\x92\xB2", + "\xA9\xE4" => "\xE2\x92\xB3", + "\xA9\xE5" => "\xE2\x92\xB4", + "\xA9\xE6" => "\xE2\x92\xB5", + "\xA9\xE7" => "\xE2\x91\xB4", + "\xA9\xE8" => "\xE2\x91\xB5", + "\xA9\xE9" => "\xE2\x91\xB6", + "\xA9\xEA" => "\xE2\x91\xB7", + "\xA9\xEB" => "\xE2\x91\xB8", + "\xA9\xEC" => "\xE2\x91\xB9", + "\xA9\xED" => "\xE2\x91\xBA", + "\xA9\xEE" => "\xE2\x91\xBB", + "\xA9\xEF" => "\xE2\x91\xBC", + "\xA9\xF0" => "\xE2\x91\xBD", + "\xA9\xF1" => "\xE2\x91\xBE", + "\xA9\xF2" => "\xE2\x91\xBF", + "\xA9\xF3" => "\xE2\x92\x80", + "\xA9\xF4" => "\xE2\x92\x81", + "\xA9\xF5" => "\xE2\x92\x82", + "\xA9\xF6" => "\xC2\xB9", + "\xA9\xF7" => "\xC2\xB2", + "\xA9\xF8" => "\xC2\xB3", + "\xA9\xF9" => "\xE2\x81\xB4", + "\xA9\xFA" => "\xE2\x81\xBF", + "\xA9\xFB" => "\xE2\x82\x81", + "\xA9\xFC" => "\xE2\x82\x82", + "\xA9\xFD" => "\xE2\x82\x83", + "\xA9\xFE" => "\xE2\x82\x84", + "\xAA\x41" => "\xEC\xB0\xA5", + "\xAA\x42" => "\xEC\xB0\xA6", + "\xAA\x43" => "\xEC\xB0\xAA", + "\xAA\x44" => "\xEC\xB0\xAB", + "\xAA\x45" => "\xEC\xB0\xAD", + "\xAA\x46" => "\xEC\xB0\xAF", + "\xAA\x47" => "\xEC\xB0\xB1", + "\xAA\x48" => "\xEC\xB0\xB2", + "\xAA\x49" => "\xEC\xB0\xB3", + "\xAA\x4A" => "\xEC\xB0\xB4", + "\xAA\x4B" => "\xEC\xB0\xB5", + "\xAA\x4C" => "\xEC\xB0\xB6", + "\xAA\x4D" => "\xEC\xB0\xB7", + "\xAA\x4E" => "\xEC\xB0\xBA", + "\xAA\x4F" => "\xEC\xB0\xBF", + "\xAA\x50" => "\xEC\xB1\x80", + "\xAA\x51" => "\xEC\xB1\x81", + "\xAA\x52" => "\xEC\xB1\x82", + "\xAA\x53" => "\xEC\xB1\x83", + "\xAA\x54" => "\xEC\xB1\x86", + "\xAA\x55" => "\xEC\xB1\x87", + "\xAA\x56" => "\xEC\xB1\x89", + "\xAA\x57" => "\xEC\xB1\x8A", + "\xAA\x58" => "\xEC\xB1\x8B", + "\xAA\x59" => "\xEC\xB1\x8D", + "\xAA\x5A" => "\xEC\xB1\x8E", + "\xAA\x61" => "\xEC\xB1\x8F", + "\xAA\x62" => "\xEC\xB1\x90", + "\xAA\x63" => "\xEC\xB1\x91", + "\xAA\x64" => "\xEC\xB1\x92", + "\xAA\x65" => "\xEC\xB1\x93", + "\xAA\x66" => "\xEC\xB1\x96", + "\xAA\x67" => "\xEC\xB1\x9A", + "\xAA\x68" => "\xEC\xB1\x9B", + "\xAA\x69" => "\xEC\xB1\x9C", + "\xAA\x6A" => "\xEC\xB1\x9D", + "\xAA\x6B" => "\xEC\xB1\x9E", + "\xAA\x6C" => "\xEC\xB1\x9F", + "\xAA\x6D" => "\xEC\xB1\xA1", + "\xAA\x6E" => "\xEC\xB1\xA2", + "\xAA\x6F" => "\xEC\xB1\xA3", + "\xAA\x70" => "\xEC\xB1\xA5", + "\xAA\x71" => "\xEC\xB1\xA7", + "\xAA\x72" => "\xEC\xB1\xA9", + "\xAA\x73" => "\xEC\xB1\xAA", + "\xAA\x74" => "\xEC\xB1\xAB", + "\xAA\x75" => "\xEC\xB1\xAC", + "\xAA\x76" => "\xEC\xB1\xAD", + "\xAA\x77" => "\xEC\xB1\xAE", + "\xAA\x78" => "\xEC\xB1\xAF", + "\xAA\x79" => "\xEC\xB1\xB1", + "\xAA\x7A" => "\xEC\xB1\xB2", + "\xAA\x81" => "\xEC\xB1\xB3", + "\xAA\x82" => "\xEC\xB1\xB4", + "\xAA\x83" => "\xEC\xB1\xB6", + "\xAA\x84" => "\xEC\xB1\xB7", + "\xAA\x85" => "\xEC\xB1\xB8", + "\xAA\x86" => "\xEC\xB1\xB9", + "\xAA\x87" => "\xEC\xB1\xBA", + "\xAA\x88" => "\xEC\xB1\xBB", + "\xAA\x89" => "\xEC\xB1\xBC", + "\xAA\x8A" => "\xEC\xB1\xBD", + "\xAA\x8B" => "\xEC\xB1\xBE", + "\xAA\x8C" => "\xEC\xB1\xBF", + "\xAA\x8D" => "\xEC\xB2\x80", + "\xAA\x8E" => "\xEC\xB2\x81", + "\xAA\x8F" => "\xEC\xB2\x82", + "\xAA\x90" => "\xEC\xB2\x83", + "\xAA\x91" => "\xEC\xB2\x84", + "\xAA\x92" => "\xEC\xB2\x85", + "\xAA\x93" => "\xEC\xB2\x86", + "\xAA\x94" => "\xEC\xB2\x87", + "\xAA\x95" => "\xEC\xB2\x88", + "\xAA\x96" => "\xEC\xB2\x89", + "\xAA\x97" => "\xEC\xB2\x8A", + "\xAA\x98" => "\xEC\xB2\x8B", + "\xAA\x99" => "\xEC\xB2\x8C", + "\xAA\x9A" => "\xEC\xB2\x8D", + "\xAA\x9B" => "\xEC\xB2\x8E", + "\xAA\x9C" => "\xEC\xB2\x8F", + "\xAA\x9D" => "\xEC\xB2\x90", + "\xAA\x9E" => "\xEC\xB2\x91", + "\xAA\x9F" => "\xEC\xB2\x92", + "\xAA\xA0" => "\xEC\xB2\x93", + "\xAA\xA1" => "\xE3\x81\x81", + "\xAA\xA2" => "\xE3\x81\x82", + "\xAA\xA3" => "\xE3\x81\x83", + "\xAA\xA4" => "\xE3\x81\x84", + "\xAA\xA5" => "\xE3\x81\x85", + "\xAA\xA6" => "\xE3\x81\x86", + "\xAA\xA7" => "\xE3\x81\x87", + "\xAA\xA8" => "\xE3\x81\x88", + "\xAA\xA9" => "\xE3\x81\x89", + "\xAA\xAA" => "\xE3\x81\x8A", + "\xAA\xAB" => "\xE3\x81\x8B", + "\xAA\xAC" => "\xE3\x81\x8C", + "\xAA\xAD" => "\xE3\x81\x8D", + "\xAA\xAE" => "\xE3\x81\x8E", + "\xAA\xAF" => "\xE3\x81\x8F", + "\xAA\xB0" => "\xE3\x81\x90", + "\xAA\xB1" => "\xE3\x81\x91", + "\xAA\xB2" => "\xE3\x81\x92", + "\xAA\xB3" => "\xE3\x81\x93", + "\xAA\xB4" => "\xE3\x81\x94", + "\xAA\xB5" => "\xE3\x81\x95", + "\xAA\xB6" => "\xE3\x81\x96", + "\xAA\xB7" => "\xE3\x81\x97", + "\xAA\xB8" => "\xE3\x81\x98", + "\xAA\xB9" => "\xE3\x81\x99", + "\xAA\xBA" => "\xE3\x81\x9A", + "\xAA\xBB" => "\xE3\x81\x9B", + "\xAA\xBC" => "\xE3\x81\x9C", + "\xAA\xBD" => "\xE3\x81\x9D", + "\xAA\xBE" => "\xE3\x81\x9E", + "\xAA\xBF" => "\xE3\x81\x9F", + "\xAA\xC0" => "\xE3\x81\xA0", + "\xAA\xC1" => "\xE3\x81\xA1", + "\xAA\xC2" => "\xE3\x81\xA2", + "\xAA\xC3" => "\xE3\x81\xA3", + "\xAA\xC4" => "\xE3\x81\xA4", + "\xAA\xC5" => "\xE3\x81\xA5", + "\xAA\xC6" => "\xE3\x81\xA6", + "\xAA\xC7" => "\xE3\x81\xA7", + "\xAA\xC8" => "\xE3\x81\xA8", + "\xAA\xC9" => "\xE3\x81\xA9", + "\xAA\xCA" => "\xE3\x81\xAA", + "\xAA\xCB" => "\xE3\x81\xAB", + "\xAA\xCC" => "\xE3\x81\xAC", + "\xAA\xCD" => "\xE3\x81\xAD", + "\xAA\xCE" => "\xE3\x81\xAE", + "\xAA\xCF" => "\xE3\x81\xAF", + "\xAA\xD0" => "\xE3\x81\xB0", + "\xAA\xD1" => "\xE3\x81\xB1", + "\xAA\xD2" => "\xE3\x81\xB2", + "\xAA\xD3" => "\xE3\x81\xB3", + "\xAA\xD4" => "\xE3\x81\xB4", + "\xAA\xD5" => "\xE3\x81\xB5", + "\xAA\xD6" => "\xE3\x81\xB6", + "\xAA\xD7" => "\xE3\x81\xB7", + "\xAA\xD8" => "\xE3\x81\xB8", + "\xAA\xD9" => "\xE3\x81\xB9", + "\xAA\xDA" => "\xE3\x81\xBA", + "\xAA\xDB" => "\xE3\x81\xBB", + "\xAA\xDC" => "\xE3\x81\xBC", + "\xAA\xDD" => "\xE3\x81\xBD", + "\xAA\xDE" => "\xE3\x81\xBE", + "\xAA\xDF" => "\xE3\x81\xBF", + "\xAA\xE0" => "\xE3\x82\x80", + "\xAA\xE1" => "\xE3\x82\x81", + "\xAA\xE2" => "\xE3\x82\x82", + "\xAA\xE3" => "\xE3\x82\x83", + "\xAA\xE4" => "\xE3\x82\x84", + "\xAA\xE5" => "\xE3\x82\x85", + "\xAA\xE6" => "\xE3\x82\x86", + "\xAA\xE7" => "\xE3\x82\x87", + "\xAA\xE8" => "\xE3\x82\x88", + "\xAA\xE9" => "\xE3\x82\x89", + "\xAA\xEA" => "\xE3\x82\x8A", + "\xAA\xEB" => "\xE3\x82\x8B", + "\xAA\xEC" => "\xE3\x82\x8C", + "\xAA\xED" => "\xE3\x82\x8D", + "\xAA\xEE" => "\xE3\x82\x8E", + "\xAA\xEF" => "\xE3\x82\x8F", + "\xAA\xF0" => "\xE3\x82\x90", + "\xAA\xF1" => "\xE3\x82\x91", + "\xAA\xF2" => "\xE3\x82\x92", + "\xAA\xF3" => "\xE3\x82\x93", + "\xAB\x41" => "\xEC\xB2\x94", + "\xAB\x42" => "\xEC\xB2\x95", + "\xAB\x43" => "\xEC\xB2\x96", + "\xAB\x44" => "\xEC\xB2\x97", + "\xAB\x45" => "\xEC\xB2\x9A", + "\xAB\x46" => "\xEC\xB2\x9B", + "\xAB\x47" => "\xEC\xB2\x9D", + "\xAB\x48" => "\xEC\xB2\x9E", + "\xAB\x49" => "\xEC\xB2\x9F", + "\xAB\x4A" => "\xEC\xB2\xA1", + "\xAB\x4B" => "\xEC\xB2\xA2", + "\xAB\x4C" => "\xEC\xB2\xA3", + "\xAB\x4D" => "\xEC\xB2\xA4", + "\xAB\x4E" => "\xEC\xB2\xA5", + "\xAB\x4F" => "\xEC\xB2\xA6", + "\xAB\x50" => "\xEC\xB2\xA7", + "\xAB\x51" => "\xEC\xB2\xAA", + "\xAB\x52" => "\xEC\xB2\xAE", + "\xAB\x53" => "\xEC\xB2\xAF", + "\xAB\x54" => "\xEC\xB2\xB0", + "\xAB\x55" => "\xEC\xB2\xB1", + "\xAB\x56" => "\xEC\xB2\xB2", + "\xAB\x57" => "\xEC\xB2\xB3", + "\xAB\x58" => "\xEC\xB2\xB6", + "\xAB\x59" => "\xEC\xB2\xB7", + "\xAB\x5A" => "\xEC\xB2\xB9", + "\xAB\x61" => "\xEC\xB2\xBA", + "\xAB\x62" => "\xEC\xB2\xBB", + "\xAB\x63" => "\xEC\xB2\xBD", + "\xAB\x64" => "\xEC\xB2\xBE", + "\xAB\x65" => "\xEC\xB2\xBF", + "\xAB\x66" => "\xEC\xB3\x80", + "\xAB\x67" => "\xEC\xB3\x81", + "\xAB\x68" => "\xEC\xB3\x82", + "\xAB\x69" => "\xEC\xB3\x83", + "\xAB\x6A" => "\xEC\xB3\x86", + "\xAB\x6B" => "\xEC\xB3\x88", + "\xAB\x6C" => "\xEC\xB3\x8A", + "\xAB\x6D" => "\xEC\xB3\x8B", + "\xAB\x6E" => "\xEC\xB3\x8C", + "\xAB\x6F" => "\xEC\xB3\x8D", + "\xAB\x70" => "\xEC\xB3\x8E", + "\xAB\x71" => "\xEC\xB3\x8F", + "\xAB\x72" => "\xEC\xB3\x91", + "\xAB\x73" => "\xEC\xB3\x92", + "\xAB\x74" => "\xEC\xB3\x93", + "\xAB\x75" => "\xEC\xB3\x95", + "\xAB\x76" => "\xEC\xB3\x96", + "\xAB\x77" => "\xEC\xB3\x97", + "\xAB\x78" => "\xEC\xB3\x98", + "\xAB\x79" => "\xEC\xB3\x99", + "\xAB\x7A" => "\xEC\xB3\x9A", + "\xAB\x81" => "\xEC\xB3\x9B", + "\xAB\x82" => "\xEC\xB3\x9C", + "\xAB\x83" => "\xEC\xB3\x9D", + "\xAB\x84" => "\xEC\xB3\x9E", + "\xAB\x85" => "\xEC\xB3\x9F", + "\xAB\x86" => "\xEC\xB3\xA0", + "\xAB\x87" => "\xEC\xB3\xA1", + "\xAB\x88" => "\xEC\xB3\xA2", + "\xAB\x89" => "\xEC\xB3\xA3", + "\xAB\x8A" => "\xEC\xB3\xA5", + "\xAB\x8B" => "\xEC\xB3\xA6", + "\xAB\x8C" => "\xEC\xB3\xA7", + "\xAB\x8D" => "\xEC\xB3\xA8", + "\xAB\x8E" => "\xEC\xB3\xA9", + "\xAB\x8F" => "\xEC\xB3\xAA", + "\xAB\x90" => "\xEC\xB3\xAB", + "\xAB\x91" => "\xEC\xB3\xAD", + "\xAB\x92" => "\xEC\xB3\xAE", + "\xAB\x93" => "\xEC\xB3\xAF", + "\xAB\x94" => "\xEC\xB3\xB1", + "\xAB\x95" => "\xEC\xB3\xB2", + "\xAB\x96" => "\xEC\xB3\xB3", + "\xAB\x97" => "\xEC\xB3\xB4", + "\xAB\x98" => "\xEC\xB3\xB5", + "\xAB\x99" => "\xEC\xB3\xB6", + "\xAB\x9A" => "\xEC\xB3\xB7", + "\xAB\x9B" => "\xEC\xB3\xB8", + "\xAB\x9C" => "\xEC\xB3\xB9", + "\xAB\x9D" => "\xEC\xB3\xBA", + "\xAB\x9E" => "\xEC\xB3\xBB", + "\xAB\x9F" => "\xEC\xB3\xBC", + "\xAB\xA0" => "\xEC\xB3\xBD", + "\xAB\xA1" => "\xE3\x82\xA1", + "\xAB\xA2" => "\xE3\x82\xA2", + "\xAB\xA3" => "\xE3\x82\xA3", + "\xAB\xA4" => "\xE3\x82\xA4", + "\xAB\xA5" => "\xE3\x82\xA5", + "\xAB\xA6" => "\xE3\x82\xA6", + "\xAB\xA7" => "\xE3\x82\xA7", + "\xAB\xA8" => "\xE3\x82\xA8", + "\xAB\xA9" => "\xE3\x82\xA9", + "\xAB\xAA" => "\xE3\x82\xAA", + "\xAB\xAB" => "\xE3\x82\xAB", + "\xAB\xAC" => "\xE3\x82\xAC", + "\xAB\xAD" => "\xE3\x82\xAD", + "\xAB\xAE" => "\xE3\x82\xAE", + "\xAB\xAF" => "\xE3\x82\xAF", + "\xAB\xB0" => "\xE3\x82\xB0", + "\xAB\xB1" => "\xE3\x82\xB1", + "\xAB\xB2" => "\xE3\x82\xB2", + "\xAB\xB3" => "\xE3\x82\xB3", + "\xAB\xB4" => "\xE3\x82\xB4", + "\xAB\xB5" => "\xE3\x82\xB5", + "\xAB\xB6" => "\xE3\x82\xB6", + "\xAB\xB7" => "\xE3\x82\xB7", + "\xAB\xB8" => "\xE3\x82\xB8", + "\xAB\xB9" => "\xE3\x82\xB9", + "\xAB\xBA" => "\xE3\x82\xBA", + "\xAB\xBB" => "\xE3\x82\xBB", + "\xAB\xBC" => "\xE3\x82\xBC", + "\xAB\xBD" => "\xE3\x82\xBD", + "\xAB\xBE" => "\xE3\x82\xBE", + "\xAB\xBF" => "\xE3\x82\xBF", + "\xAB\xC0" => "\xE3\x83\x80", + "\xAB\xC1" => "\xE3\x83\x81", + "\xAB\xC2" => "\xE3\x83\x82", + "\xAB\xC3" => "\xE3\x83\x83", + "\xAB\xC4" => "\xE3\x83\x84", + "\xAB\xC5" => "\xE3\x83\x85", + "\xAB\xC6" => "\xE3\x83\x86", + "\xAB\xC7" => "\xE3\x83\x87", + "\xAB\xC8" => "\xE3\x83\x88", + "\xAB\xC9" => "\xE3\x83\x89", + "\xAB\xCA" => "\xE3\x83\x8A", + "\xAB\xCB" => "\xE3\x83\x8B", + "\xAB\xCC" => "\xE3\x83\x8C", + "\xAB\xCD" => "\xE3\x83\x8D", + "\xAB\xCE" => "\xE3\x83\x8E", + "\xAB\xCF" => "\xE3\x83\x8F", + "\xAB\xD0" => "\xE3\x83\x90", + "\xAB\xD1" => "\xE3\x83\x91", + "\xAB\xD2" => "\xE3\x83\x92", + "\xAB\xD3" => "\xE3\x83\x93", + "\xAB\xD4" => "\xE3\x83\x94", + "\xAB\xD5" => "\xE3\x83\x95", + "\xAB\xD6" => "\xE3\x83\x96", + "\xAB\xD7" => "\xE3\x83\x97", + "\xAB\xD8" => "\xE3\x83\x98", + "\xAB\xD9" => "\xE3\x83\x99", + "\xAB\xDA" => "\xE3\x83\x9A", + "\xAB\xDB" => "\xE3\x83\x9B", + "\xAB\xDC" => "\xE3\x83\x9C", + "\xAB\xDD" => "\xE3\x83\x9D", + "\xAB\xDE" => "\xE3\x83\x9E", + "\xAB\xDF" => "\xE3\x83\x9F", + "\xAB\xE0" => "\xE3\x83\xA0", + "\xAB\xE1" => "\xE3\x83\xA1", + "\xAB\xE2" => "\xE3\x83\xA2", + "\xAB\xE3" => "\xE3\x83\xA3", + "\xAB\xE4" => "\xE3\x83\xA4", + "\xAB\xE5" => "\xE3\x83\xA5", + "\xAB\xE6" => "\xE3\x83\xA6", + "\xAB\xE7" => "\xE3\x83\xA7", + "\xAB\xE8" => "\xE3\x83\xA8", + "\xAB\xE9" => "\xE3\x83\xA9", + "\xAB\xEA" => "\xE3\x83\xAA", + "\xAB\xEB" => "\xE3\x83\xAB", + "\xAB\xEC" => "\xE3\x83\xAC", + "\xAB\xED" => "\xE3\x83\xAD", + "\xAB\xEE" => "\xE3\x83\xAE", + "\xAB\xEF" => "\xE3\x83\xAF", + "\xAB\xF0" => "\xE3\x83\xB0", + "\xAB\xF1" => "\xE3\x83\xB1", + "\xAB\xF2" => "\xE3\x83\xB2", + "\xAB\xF3" => "\xE3\x83\xB3", + "\xAB\xF4" => "\xE3\x83\xB4", + "\xAB\xF5" => "\xE3\x83\xB5", + "\xAB\xF6" => "\xE3\x83\xB6", + "\xAC\x41" => "\xEC\xB3\xBE", + "\xAC\x42" => "\xEC\xB3\xBF", + "\xAC\x43" => "\xEC\xB4\x80", + "\xAC\x44" => "\xEC\xB4\x82", + "\xAC\x45" => "\xEC\xB4\x83", + "\xAC\x46" => "\xEC\xB4\x84", + "\xAC\x47" => "\xEC\xB4\x85", + "\xAC\x48" => "\xEC\xB4\x86", + "\xAC\x49" => "\xEC\xB4\x87", + "\xAC\x4A" => "\xEC\xB4\x8A", + "\xAC\x4B" => "\xEC\xB4\x8B", + "\xAC\x4C" => "\xEC\xB4\x8D", + "\xAC\x4D" => "\xEC\xB4\x8E", + "\xAC\x4E" => "\xEC\xB4\x8F", + "\xAC\x4F" => "\xEC\xB4\x91", + "\xAC\x50" => "\xEC\xB4\x92", + "\xAC\x51" => "\xEC\xB4\x93", + "\xAC\x52" => "\xEC\xB4\x94", + "\xAC\x53" => "\xEC\xB4\x95", + "\xAC\x54" => "\xEC\xB4\x96", + "\xAC\x55" => "\xEC\xB4\x97", + "\xAC\x56" => "\xEC\xB4\x9A", + "\xAC\x57" => "\xEC\xB4\x9C", + "\xAC\x58" => "\xEC\xB4\x9E", + "\xAC\x59" => "\xEC\xB4\x9F", + "\xAC\x5A" => "\xEC\xB4\xA0", + "\xAC\x61" => "\xEC\xB4\xA1", + "\xAC\x62" => "\xEC\xB4\xA2", + "\xAC\x63" => "\xEC\xB4\xA3", + "\xAC\x64" => "\xEC\xB4\xA5", + "\xAC\x65" => "\xEC\xB4\xA6", + "\xAC\x66" => "\xEC\xB4\xA7", + "\xAC\x67" => "\xEC\xB4\xA9", + "\xAC\x68" => "\xEC\xB4\xAA", + "\xAC\x69" => "\xEC\xB4\xAB", + "\xAC\x6A" => "\xEC\xB4\xAD", + "\xAC\x6B" => "\xEC\xB4\xAE", + "\xAC\x6C" => "\xEC\xB4\xAF", + "\xAC\x6D" => "\xEC\xB4\xB0", + "\xAC\x6E" => "\xEC\xB4\xB1", + "\xAC\x6F" => "\xEC\xB4\xB2", + "\xAC\x70" => "\xEC\xB4\xB3", + "\xAC\x71" => "\xEC\xB4\xB4", + "\xAC\x72" => "\xEC\xB4\xB5", + "\xAC\x73" => "\xEC\xB4\xB6", + "\xAC\x74" => "\xEC\xB4\xB7", + "\xAC\x75" => "\xEC\xB4\xB8", + "\xAC\x76" => "\xEC\xB4\xBA", + "\xAC\x77" => "\xEC\xB4\xBB", + "\xAC\x78" => "\xEC\xB4\xBC", + "\xAC\x79" => "\xEC\xB4\xBD", + "\xAC\x7A" => "\xEC\xB4\xBE", + "\xAC\x81" => "\xEC\xB4\xBF", + "\xAC\x82" => "\xEC\xB5\x80", + "\xAC\x83" => "\xEC\xB5\x81", + "\xAC\x84" => "\xEC\xB5\x82", + "\xAC\x85" => "\xEC\xB5\x83", + "\xAC\x86" => "\xEC\xB5\x84", + "\xAC\x87" => "\xEC\xB5\x85", + "\xAC\x88" => "\xEC\xB5\x86", + "\xAC\x89" => "\xEC\xB5\x87", + "\xAC\x8A" => "\xEC\xB5\x88", + "\xAC\x8B" => "\xEC\xB5\x89", + "\xAC\x8C" => "\xEC\xB5\x8A", + "\xAC\x8D" => "\xEC\xB5\x8B", + "\xAC\x8E" => "\xEC\xB5\x8C", + "\xAC\x8F" => "\xEC\xB5\x8D", + "\xAC\x90" => "\xEC\xB5\x8E", + "\xAC\x91" => "\xEC\xB5\x8F", + "\xAC\x92" => "\xEC\xB5\x90", + "\xAC\x93" => "\xEC\xB5\x91", + "\xAC\x94" => "\xEC\xB5\x92", + "\xAC\x95" => "\xEC\xB5\x93", + "\xAC\x96" => "\xEC\xB5\x94", + "\xAC\x97" => "\xEC\xB5\x95", + "\xAC\x98" => "\xEC\xB5\x96", + "\xAC\x99" => "\xEC\xB5\x97", + "\xAC\x9A" => "\xEC\xB5\x98", + "\xAC\x9B" => "\xEC\xB5\x99", + "\xAC\x9C" => "\xEC\xB5\x9A", + "\xAC\x9D" => "\xEC\xB5\x9B", + "\xAC\x9E" => "\xEC\xB5\x9D", + "\xAC\x9F" => "\xEC\xB5\x9E", + "\xAC\xA0" => "\xEC\xB5\x9F", + "\xAC\xA1" => "\xD0\x90", + "\xAC\xA2" => "\xD0\x91", + "\xAC\xA3" => "\xD0\x92", + "\xAC\xA4" => "\xD0\x93", + "\xAC\xA5" => "\xD0\x94", + "\xAC\xA6" => "\xD0\x95", + "\xAC\xA7" => "\xD0\x81", + "\xAC\xA8" => "\xD0\x96", + "\xAC\xA9" => "\xD0\x97", + "\xAC\xAA" => "\xD0\x98", + "\xAC\xAB" => "\xD0\x99", + "\xAC\xAC" => "\xD0\x9A", + "\xAC\xAD" => "\xD0\x9B", + "\xAC\xAE" => "\xD0\x9C", + "\xAC\xAF" => "\xD0\x9D", + "\xAC\xB0" => "\xD0\x9E", + "\xAC\xB1" => "\xD0\x9F", + "\xAC\xB2" => "\xD0\xA0", + "\xAC\xB3" => "\xD0\xA1", + "\xAC\xB4" => "\xD0\xA2", + "\xAC\xB5" => "\xD0\xA3", + "\xAC\xB6" => "\xD0\xA4", + "\xAC\xB7" => "\xD0\xA5", + "\xAC\xB8" => "\xD0\xA6", + "\xAC\xB9" => "\xD0\xA7", + "\xAC\xBA" => "\xD0\xA8", + "\xAC\xBB" => "\xD0\xA9", + "\xAC\xBC" => "\xD0\xAA", + "\xAC\xBD" => "\xD0\xAB", + "\xAC\xBE" => "\xD0\xAC", + "\xAC\xBF" => "\xD0\xAD", + "\xAC\xC0" => "\xD0\xAE", + "\xAC\xC1" => "\xD0\xAF", + "\xAC\xD1" => "\xD0\xB0", + "\xAC\xD2" => "\xD0\xB1", + "\xAC\xD3" => "\xD0\xB2", + "\xAC\xD4" => "\xD0\xB3", + "\xAC\xD5" => "\xD0\xB4", + "\xAC\xD6" => "\xD0\xB5", + "\xAC\xD7" => "\xD1\x91", + "\xAC\xD8" => "\xD0\xB6", + "\xAC\xD9" => "\xD0\xB7", + "\xAC\xDA" => "\xD0\xB8", + "\xAC\xDB" => "\xD0\xB9", + "\xAC\xDC" => "\xD0\xBA", + "\xAC\xDD" => "\xD0\xBB", + "\xAC\xDE" => "\xD0\xBC", + "\xAC\xDF" => "\xD0\xBD", + "\xAC\xE0" => "\xD0\xBE", + "\xAC\xE1" => "\xD0\xBF", + "\xAC\xE2" => "\xD1\x80", + "\xAC\xE3" => "\xD1\x81", + "\xAC\xE4" => "\xD1\x82", + "\xAC\xE5" => "\xD1\x83", + "\xAC\xE6" => "\xD1\x84", + "\xAC\xE7" => "\xD1\x85", + "\xAC\xE8" => "\xD1\x86", + "\xAC\xE9" => "\xD1\x87", + "\xAC\xEA" => "\xD1\x88", + "\xAC\xEB" => "\xD1\x89", + "\xAC\xEC" => "\xD1\x8A", + "\xAC\xED" => "\xD1\x8B", + "\xAC\xEE" => "\xD1\x8C", + "\xAC\xEF" => "\xD1\x8D", + "\xAC\xF0" => "\xD1\x8E", + "\xAC\xF1" => "\xD1\x8F", + "\xAD\x41" => "\xEC\xB5\xA1", + "\xAD\x42" => "\xEC\xB5\xA2", + "\xAD\x43" => "\xEC\xB5\xA3", + "\xAD\x44" => "\xEC\xB5\xA5", + "\xAD\x45" => "\xEC\xB5\xA6", + "\xAD\x46" => "\xEC\xB5\xA7", + "\xAD\x47" => "\xEC\xB5\xA8", + "\xAD\x48" => "\xEC\xB5\xA9", + "\xAD\x49" => "\xEC\xB5\xAA", + "\xAD\x4A" => "\xEC\xB5\xAB", + "\xAD\x4B" => "\xEC\xB5\xAE", + "\xAD\x4C" => "\xEC\xB5\xB0", + "\xAD\x4D" => "\xEC\xB5\xB2", + "\xAD\x4E" => "\xEC\xB5\xB3", + "\xAD\x4F" => "\xEC\xB5\xB4", + "\xAD\x50" => "\xEC\xB5\xB5", + "\xAD\x51" => "\xEC\xB5\xB6", + "\xAD\x52" => "\xEC\xB5\xB7", + "\xAD\x53" => "\xEC\xB5\xB9", + "\xAD\x54" => "\xEC\xB5\xBA", + "\xAD\x55" => "\xEC\xB5\xBB", + "\xAD\x56" => "\xEC\xB5\xBC", + "\xAD\x57" => "\xEC\xB5\xBD", + "\xAD\x58" => "\xEC\xB5\xBE", + "\xAD\x59" => "\xEC\xB5\xBF", + "\xAD\x5A" => "\xEC\xB6\x80", + "\xAD\x61" => "\xEC\xB6\x81", + "\xAD\x62" => "\xEC\xB6\x82", + "\xAD\x63" => "\xEC\xB6\x83", + "\xAD\x64" => "\xEC\xB6\x84", + "\xAD\x65" => "\xEC\xB6\x85", + "\xAD\x66" => "\xEC\xB6\x86", + "\xAD\x67" => "\xEC\xB6\x87", + "\xAD\x68" => "\xEC\xB6\x89", + "\xAD\x69" => "\xEC\xB6\x8A", + "\xAD\x6A" => "\xEC\xB6\x8B", + "\xAD\x6B" => "\xEC\xB6\x8C", + "\xAD\x6C" => "\xEC\xB6\x8D", + "\xAD\x6D" => "\xEC\xB6\x8E", + "\xAD\x6E" => "\xEC\xB6\x8F", + "\xAD\x6F" => "\xEC\xB6\x90", + "\xAD\x70" => "\xEC\xB6\x91", + "\xAD\x71" => "\xEC\xB6\x92", + "\xAD\x72" => "\xEC\xB6\x93", + "\xAD\x73" => "\xEC\xB6\x96", + "\xAD\x74" => "\xEC\xB6\x97", + "\xAD\x75" => "\xEC\xB6\x99", + "\xAD\x76" => "\xEC\xB6\x9A", + "\xAD\x77" => "\xEC\xB6\x9B", + "\xAD\x78" => "\xEC\xB6\x9D", + "\xAD\x79" => "\xEC\xB6\x9E", + "\xAD\x7A" => "\xEC\xB6\x9F", + "\xAD\x81" => "\xEC\xB6\xA0", + "\xAD\x82" => "\xEC\xB6\xA1", + "\xAD\x83" => "\xEC\xB6\xA2", + "\xAD\x84" => "\xEC\xB6\xA3", + "\xAD\x85" => "\xEC\xB6\xA6", + "\xAD\x86" => "\xEC\xB6\xA8", + "\xAD\x87" => "\xEC\xB6\xAA", + "\xAD\x88" => "\xEC\xB6\xAB", + "\xAD\x89" => "\xEC\xB6\xAC", + "\xAD\x8A" => "\xEC\xB6\xAD", + "\xAD\x8B" => "\xEC\xB6\xAE", + "\xAD\x8C" => "\xEC\xB6\xAF", + "\xAD\x8D" => "\xEC\xB6\xB1", + "\xAD\x8E" => "\xEC\xB6\xB2", + "\xAD\x8F" => "\xEC\xB6\xB3", + "\xAD\x90" => "\xEC\xB6\xB4", + "\xAD\x91" => "\xEC\xB6\xB5", + "\xAD\x92" => "\xEC\xB6\xB6", + "\xAD\x93" => "\xEC\xB6\xB7", + "\xAD\x94" => "\xEC\xB6\xB8", + "\xAD\x95" => "\xEC\xB6\xB9", + "\xAD\x96" => "\xEC\xB6\xBA", + "\xAD\x97" => "\xEC\xB6\xBB", + "\xAD\x98" => "\xEC\xB6\xBC", + "\xAD\x99" => "\xEC\xB6\xBD", + "\xAD\x9A" => "\xEC\xB6\xBE", + "\xAD\x9B" => "\xEC\xB6\xBF", + "\xAD\x9C" => "\xEC\xB7\x80", + "\xAD\x9D" => "\xEC\xB7\x81", + "\xAD\x9E" => "\xEC\xB7\x82", + "\xAD\x9F" => "\xEC\xB7\x83", + "\xAD\xA0" => "\xEC\xB7\x85", + "\xAE\x41" => "\xEC\xB7\x86", + "\xAE\x42" => "\xEC\xB7\x87", + "\xAE\x43" => "\xEC\xB7\x88", + "\xAE\x44" => "\xEC\xB7\x89", + "\xAE\x45" => "\xEC\xB7\x8A", + "\xAE\x46" => "\xEC\xB7\x8B", + "\xAE\x47" => "\xEC\xB7\x8D", + "\xAE\x48" => "\xEC\xB7\x8E", + "\xAE\x49" => "\xEC\xB7\x8F", + "\xAE\x4A" => "\xEC\xB7\x91", + "\xAE\x4B" => "\xEC\xB7\x92", + "\xAE\x4C" => "\xEC\xB7\x93", + "\xAE\x4D" => "\xEC\xB7\x94", + "\xAE\x4E" => "\xEC\xB7\x95", + "\xAE\x4F" => "\xEC\xB7\x96", + "\xAE\x50" => "\xEC\xB7\x97", + "\xAE\x51" => "\xEC\xB7\x98", + "\xAE\x52" => "\xEC\xB7\x99", + "\xAE\x53" => "\xEC\xB7\x9A", + "\xAE\x54" => "\xEC\xB7\x9B", + "\xAE\x55" => "\xEC\xB7\x9C", + "\xAE\x56" => "\xEC\xB7\x9D", + "\xAE\x57" => "\xEC\xB7\x9E", + "\xAE\x58" => "\xEC\xB7\x9F", + "\xAE\x59" => "\xEC\xB7\xA0", + "\xAE\x5A" => "\xEC\xB7\xA1", + "\xAE\x61" => "\xEC\xB7\xA2", + "\xAE\x62" => "\xEC\xB7\xA3", + "\xAE\x63" => "\xEC\xB7\xA4", + "\xAE\x64" => "\xEC\xB7\xA5", + "\xAE\x65" => "\xEC\xB7\xA6", + "\xAE\x66" => "\xEC\xB7\xA7", + "\xAE\x67" => "\xEC\xB7\xA9", + "\xAE\x68" => "\xEC\xB7\xAA", + "\xAE\x69" => "\xEC\xB7\xAB", + "\xAE\x6A" => "\xEC\xB7\xAD", + "\xAE\x6B" => "\xEC\xB7\xAE", + "\xAE\x6C" => "\xEC\xB7\xAF", + "\xAE\x6D" => "\xEC\xB7\xB1", + "\xAE\x6E" => "\xEC\xB7\xB2", + "\xAE\x6F" => "\xEC\xB7\xB3", + "\xAE\x70" => "\xEC\xB7\xB4", + "\xAE\x71" => "\xEC\xB7\xB5", + "\xAE\x72" => "\xEC\xB7\xB6", + "\xAE\x73" => "\xEC\xB7\xB7", + "\xAE\x74" => "\xEC\xB7\xBA", + "\xAE\x75" => "\xEC\xB7\xBC", + "\xAE\x76" => "\xEC\xB7\xBE", + "\xAE\x77" => "\xEC\xB7\xBF", + "\xAE\x78" => "\xEC\xB8\x80", + "\xAE\x79" => "\xEC\xB8\x81", + "\xAE\x7A" => "\xEC\xB8\x82", + "\xAE\x81" => "\xEC\xB8\x83", + "\xAE\x82" => "\xEC\xB8\x85", + "\xAE\x83" => "\xEC\xB8\x86", + "\xAE\x84" => "\xEC\xB8\x87", + "\xAE\x85" => "\xEC\xB8\x89", + "\xAE\x86" => "\xEC\xB8\x8A", + "\xAE\x87" => "\xEC\xB8\x8B", + "\xAE\x88" => "\xEC\xB8\x8D", + "\xAE\x89" => "\xEC\xB8\x8E", + "\xAE\x8A" => "\xEC\xB8\x8F", + "\xAE\x8B" => "\xEC\xB8\x90", + "\xAE\x8C" => "\xEC\xB8\x91", + "\xAE\x8D" => "\xEC\xB8\x92", + "\xAE\x8E" => "\xEC\xB8\x93", + "\xAE\x8F" => "\xEC\xB8\x95", + "\xAE\x90" => "\xEC\xB8\x96", + "\xAE\x91" => "\xEC\xB8\x97", + "\xAE\x92" => "\xEC\xB8\x98", + "\xAE\x93" => "\xEC\xB8\x9A", + "\xAE\x94" => "\xEC\xB8\x9B", + "\xAE\x95" => "\xEC\xB8\x9C", + "\xAE\x96" => "\xEC\xB8\x9D", + "\xAE\x97" => "\xEC\xB8\x9E", + "\xAE\x98" => "\xEC\xB8\x9F", + "\xAE\x99" => "\xEC\xB8\xA2", + "\xAE\x9A" => "\xEC\xB8\xA3", + "\xAE\x9B" => "\xEC\xB8\xA5", + "\xAE\x9C" => "\xEC\xB8\xA6", + "\xAE\x9D" => "\xEC\xB8\xA7", + "\xAE\x9E" => "\xEC\xB8\xA9", + "\xAE\x9F" => "\xEC\xB8\xAA", + "\xAE\xA0" => "\xEC\xB8\xAB", + "\xAF\x41" => "\xEC\xB8\xAC", + "\xAF\x42" => "\xEC\xB8\xAD", + "\xAF\x43" => "\xEC\xB8\xAE", + "\xAF\x44" => "\xEC\xB8\xAF", + "\xAF\x45" => "\xEC\xB8\xB2", + "\xAF\x46" => "\xEC\xB8\xB4", + "\xAF\x47" => "\xEC\xB8\xB6", + "\xAF\x48" => "\xEC\xB8\xB7", + "\xAF\x49" => "\xEC\xB8\xB8", + "\xAF\x4A" => "\xEC\xB8\xB9", + "\xAF\x4B" => "\xEC\xB8\xBA", + "\xAF\x4C" => "\xEC\xB8\xBB", + "\xAF\x4D" => "\xEC\xB8\xBC", + "\xAF\x4E" => "\xEC\xB8\xBD", + "\xAF\x4F" => "\xEC\xB8\xBE", + "\xAF\x50" => "\xEC\xB8\xBF", + "\xAF\x51" => "\xEC\xB9\x80", + "\xAF\x52" => "\xEC\xB9\x81", + "\xAF\x53" => "\xEC\xB9\x82", + "\xAF\x54" => "\xEC\xB9\x83", + "\xAF\x55" => "\xEC\xB9\x84", + "\xAF\x56" => "\xEC\xB9\x85", + "\xAF\x57" => "\xEC\xB9\x86", + "\xAF\x58" => "\xEC\xB9\x87", + "\xAF\x59" => "\xEC\xB9\x88", + "\xAF\x5A" => "\xEC\xB9\x89", + "\xAF\x61" => "\xEC\xB9\x8A", + "\xAF\x62" => "\xEC\xB9\x8B", + "\xAF\x63" => "\xEC\xB9\x8C", + "\xAF\x64" => "\xEC\xB9\x8D", + "\xAF\x65" => "\xEC\xB9\x8E", + "\xAF\x66" => "\xEC\xB9\x8F", + "\xAF\x67" => "\xEC\xB9\x90", + "\xAF\x68" => "\xEC\xB9\x91", + "\xAF\x69" => "\xEC\xB9\x92", + "\xAF\x6A" => "\xEC\xB9\x93", + "\xAF\x6B" => "\xEC\xB9\x94", + "\xAF\x6C" => "\xEC\xB9\x95", + "\xAF\x6D" => "\xEC\xB9\x96", + "\xAF\x6E" => "\xEC\xB9\x97", + "\xAF\x6F" => "\xEC\xB9\x9A", + "\xAF\x70" => "\xEC\xB9\x9B", + "\xAF\x71" => "\xEC\xB9\x9D", + "\xAF\x72" => "\xEC\xB9\x9E", + "\xAF\x73" => "\xEC\xB9\xA2", + "\xAF\x74" => "\xEC\xB9\xA3", + "\xAF\x75" => "\xEC\xB9\xA4", + "\xAF\x76" => "\xEC\xB9\xA5", + "\xAF\x77" => "\xEC\xB9\xA6", + "\xAF\x78" => "\xEC\xB9\xA7", + "\xAF\x79" => "\xEC\xB9\xAA", + "\xAF\x7A" => "\xEC\xB9\xAC", + "\xAF\x81" => "\xEC\xB9\xAE", + "\xAF\x82" => "\xEC\xB9\xAF", + "\xAF\x83" => "\xEC\xB9\xB0", + "\xAF\x84" => "\xEC\xB9\xB1", + "\xAF\x85" => "\xEC\xB9\xB2", + "\xAF\x86" => "\xEC\xB9\xB3", + "\xAF\x87" => "\xEC\xB9\xB6", + "\xAF\x88" => "\xEC\xB9\xB7", + "\xAF\x89" => "\xEC\xB9\xB9", + "\xAF\x8A" => "\xEC\xB9\xBA", + "\xAF\x8B" => "\xEC\xB9\xBB", + "\xAF\x8C" => "\xEC\xB9\xBD", + "\xAF\x8D" => "\xEC\xB9\xBE", + "\xAF\x8E" => "\xEC\xB9\xBF", + "\xAF\x8F" => "\xEC\xBA\x80", + "\xAF\x90" => "\xEC\xBA\x81", + "\xAF\x91" => "\xEC\xBA\x82", + "\xAF\x92" => "\xEC\xBA\x83", + "\xAF\x93" => "\xEC\xBA\x86", + "\xAF\x94" => "\xEC\xBA\x88", + "\xAF\x95" => "\xEC\xBA\x8A", + "\xAF\x96" => "\xEC\xBA\x8B", + "\xAF\x97" => "\xEC\xBA\x8C", + "\xAF\x98" => "\xEC\xBA\x8D", + "\xAF\x99" => "\xEC\xBA\x8E", + "\xAF\x9A" => "\xEC\xBA\x8F", + "\xAF\x9B" => "\xEC\xBA\x92", + "\xAF\x9C" => "\xEC\xBA\x93", + "\xAF\x9D" => "\xEC\xBA\x95", + "\xAF\x9E" => "\xEC\xBA\x96", + "\xAF\x9F" => "\xEC\xBA\x97", + "\xAF\xA0" => "\xEC\xBA\x99", + "\xB0\x41" => "\xEC\xBA\x9A", + "\xB0\x42" => "\xEC\xBA\x9B", + "\xB0\x43" => "\xEC\xBA\x9C", + "\xB0\x44" => "\xEC\xBA\x9D", + "\xB0\x45" => "\xEC\xBA\x9E", + "\xB0\x46" => "\xEC\xBA\x9F", + "\xB0\x47" => "\xEC\xBA\xA2", + "\xB0\x48" => "\xEC\xBA\xA6", + "\xB0\x49" => "\xEC\xBA\xA7", + "\xB0\x4A" => "\xEC\xBA\xA8", + "\xB0\x4B" => "\xEC\xBA\xA9", + "\xB0\x4C" => "\xEC\xBA\xAA", + "\xB0\x4D" => "\xEC\xBA\xAB", + "\xB0\x4E" => "\xEC\xBA\xAE", + "\xB0\x4F" => "\xEC\xBA\xAF", + "\xB0\x50" => "\xEC\xBA\xB0", + "\xB0\x51" => "\xEC\xBA\xB1", + "\xB0\x52" => "\xEC\xBA\xB2", + "\xB0\x53" => "\xEC\xBA\xB3", + "\xB0\x54" => "\xEC\xBA\xB4", + "\xB0\x55" => "\xEC\xBA\xB5", + "\xB0\x56" => "\xEC\xBA\xB6", + "\xB0\x57" => "\xEC\xBA\xB7", + "\xB0\x58" => "\xEC\xBA\xB8", + "\xB0\x59" => "\xEC\xBA\xB9", + "\xB0\x5A" => "\xEC\xBA\xBA", + "\xB0\x61" => "\xEC\xBA\xBB", + "\xB0\x62" => "\xEC\xBA\xBC", + "\xB0\x63" => "\xEC\xBA\xBD", + "\xB0\x64" => "\xEC\xBA\xBE", + "\xB0\x65" => "\xEC\xBA\xBF", + "\xB0\x66" => "\xEC\xBB\x80", + "\xB0\x67" => "\xEC\xBB\x82", + "\xB0\x68" => "\xEC\xBB\x83", + "\xB0\x69" => "\xEC\xBB\x84", + "\xB0\x6A" => "\xEC\xBB\x85", + "\xB0\x6B" => "\xEC\xBB\x86", + "\xB0\x6C" => "\xEC\xBB\x87", + "\xB0\x6D" => "\xEC\xBB\x88", + "\xB0\x6E" => "\xEC\xBB\x89", + "\xB0\x6F" => "\xEC\xBB\x8A", + "\xB0\x70" => "\xEC\xBB\x8B", + "\xB0\x71" => "\xEC\xBB\x8C", + "\xB0\x72" => "\xEC\xBB\x8D", + "\xB0\x73" => "\xEC\xBB\x8E", + "\xB0\x74" => "\xEC\xBB\x8F", + "\xB0\x75" => "\xEC\xBB\x90", + "\xB0\x76" => "\xEC\xBB\x91", + "\xB0\x77" => "\xEC\xBB\x92", + "\xB0\x78" => "\xEC\xBB\x93", + "\xB0\x79" => "\xEC\xBB\x94", + "\xB0\x7A" => "\xEC\xBB\x95", + "\xB0\x81" => "\xEC\xBB\x96", + "\xB0\x82" => "\xEC\xBB\x97", + "\xB0\x83" => "\xEC\xBB\x98", + "\xB0\x84" => "\xEC\xBB\x99", + "\xB0\x85" => "\xEC\xBB\x9A", + "\xB0\x86" => "\xEC\xBB\x9B", + "\xB0\x87" => "\xEC\xBB\x9C", + "\xB0\x88" => "\xEC\xBB\x9D", + "\xB0\x89" => "\xEC\xBB\x9E", + "\xB0\x8A" => "\xEC\xBB\x9F", + "\xB0\x8B" => "\xEC\xBB\xA0", + "\xB0\x8C" => "\xEC\xBB\xA1", + "\xB0\x8D" => "\xEC\xBB\xA2", + "\xB0\x8E" => "\xEC\xBB\xA3", + "\xB0\x8F" => "\xEC\xBB\xA6", + "\xB0\x90" => "\xEC\xBB\xA7", + "\xB0\x91" => "\xEC\xBB\xA9", + "\xB0\x92" => "\xEC\xBB\xAA", + "\xB0\x93" => "\xEC\xBB\xAD", + "\xB0\x94" => "\xEC\xBB\xAE", + "\xB0\x95" => "\xEC\xBB\xAF", + "\xB0\x96" => "\xEC\xBB\xB0", + "\xB0\x97" => "\xEC\xBB\xB1", + "\xB0\x98" => "\xEC\xBB\xB2", + "\xB0\x99" => "\xEC\xBB\xB3", + "\xB0\x9A" => "\xEC\xBB\xB6", + "\xB0\x9B" => "\xEC\xBB\xBA", + "\xB0\x9C" => "\xEC\xBB\xBB", + "\xB0\x9D" => "\xEC\xBB\xBC", + "\xB0\x9E" => "\xEC\xBB\xBD", + "\xB0\x9F" => "\xEC\xBB\xBE", + "\xB0\xA0" => "\xEC\xBB\xBF", + "\xB0\xA1" => "\xEA\xB0\x80", + "\xB0\xA2" => "\xEA\xB0\x81", + "\xB0\xA3" => "\xEA\xB0\x84", + "\xB0\xA4" => "\xEA\xB0\x87", + "\xB0\xA5" => "\xEA\xB0\x88", + "\xB0\xA6" => "\xEA\xB0\x89", + "\xB0\xA7" => "\xEA\xB0\x8A", + "\xB0\xA8" => "\xEA\xB0\x90", + "\xB0\xA9" => "\xEA\xB0\x91", + "\xB0\xAA" => "\xEA\xB0\x92", + "\xB0\xAB" => "\xEA\xB0\x93", + "\xB0\xAC" => "\xEA\xB0\x94", + "\xB0\xAD" => "\xEA\xB0\x95", + "\xB0\xAE" => "\xEA\xB0\x96", + "\xB0\xAF" => "\xEA\xB0\x97", + "\xB0\xB0" => "\xEA\xB0\x99", + "\xB0\xB1" => "\xEA\xB0\x9A", + "\xB0\xB2" => "\xEA\xB0\x9B", + "\xB0\xB3" => "\xEA\xB0\x9C", + "\xB0\xB4" => "\xEA\xB0\x9D", + "\xB0\xB5" => "\xEA\xB0\xA0", + "\xB0\xB6" => "\xEA\xB0\xA4", + "\xB0\xB7" => "\xEA\xB0\xAC", + "\xB0\xB8" => "\xEA\xB0\xAD", + "\xB0\xB9" => "\xEA\xB0\xAF", + "\xB0\xBA" => "\xEA\xB0\xB0", + "\xB0\xBB" => "\xEA\xB0\xB1", + "\xB0\xBC" => "\xEA\xB0\xB8", + "\xB0\xBD" => "\xEA\xB0\xB9", + "\xB0\xBE" => "\xEA\xB0\xBC", + "\xB0\xBF" => "\xEA\xB1\x80", + "\xB0\xC0" => "\xEA\xB1\x8B", + "\xB0\xC1" => "\xEA\xB1\x8D", + "\xB0\xC2" => "\xEA\xB1\x94", + "\xB0\xC3" => "\xEA\xB1\x98", + "\xB0\xC4" => "\xEA\xB1\x9C", + "\xB0\xC5" => "\xEA\xB1\xB0", + "\xB0\xC6" => "\xEA\xB1\xB1", + "\xB0\xC7" => "\xEA\xB1\xB4", + "\xB0\xC8" => "\xEA\xB1\xB7", + "\xB0\xC9" => "\xEA\xB1\xB8", + "\xB0\xCA" => "\xEA\xB1\xBA", + "\xB0\xCB" => "\xEA\xB2\x80", + "\xB0\xCC" => "\xEA\xB2\x81", + "\xB0\xCD" => "\xEA\xB2\x83", + "\xB0\xCE" => "\xEA\xB2\x84", + "\xB0\xCF" => "\xEA\xB2\x85", + "\xB0\xD0" => "\xEA\xB2\x86", + "\xB0\xD1" => "\xEA\xB2\x89", + "\xB0\xD2" => "\xEA\xB2\x8A", + "\xB0\xD3" => "\xEA\xB2\x8B", + "\xB0\xD4" => "\xEA\xB2\x8C", + "\xB0\xD5" => "\xEA\xB2\x90", + "\xB0\xD6" => "\xEA\xB2\x94", + "\xB0\xD7" => "\xEA\xB2\x9C", + "\xB0\xD8" => "\xEA\xB2\x9D", + "\xB0\xD9" => "\xEA\xB2\x9F", + "\xB0\xDA" => "\xEA\xB2\xA0", + "\xB0\xDB" => "\xEA\xB2\xA1", + "\xB0\xDC" => "\xEA\xB2\xA8", + "\xB0\xDD" => "\xEA\xB2\xA9", + "\xB0\xDE" => "\xEA\xB2\xAA", + "\xB0\xDF" => "\xEA\xB2\xAC", + "\xB0\xE0" => "\xEA\xB2\xAF", + "\xB0\xE1" => "\xEA\xB2\xB0", + "\xB0\xE2" => "\xEA\xB2\xB8", + "\xB0\xE3" => "\xEA\xB2\xB9", + "\xB0\xE4" => "\xEA\xB2\xBB", + "\xB0\xE5" => "\xEA\xB2\xBC", + "\xB0\xE6" => "\xEA\xB2\xBD", + "\xB0\xE7" => "\xEA\xB3\x81", + "\xB0\xE8" => "\xEA\xB3\x84", + "\xB0\xE9" => "\xEA\xB3\x88", + "\xB0\xEA" => "\xEA\xB3\x8C", + "\xB0\xEB" => "\xEA\xB3\x95", + "\xB0\xEC" => "\xEA\xB3\x97", + "\xB0\xED" => "\xEA\xB3\xA0", + "\xB0\xEE" => "\xEA\xB3\xA1", + "\xB0\xEF" => "\xEA\xB3\xA4", + "\xB0\xF0" => "\xEA\xB3\xA7", + "\xB0\xF1" => "\xEA\xB3\xA8", + "\xB0\xF2" => "\xEA\xB3\xAA", + "\xB0\xF3" => "\xEA\xB3\xAC", + "\xB0\xF4" => "\xEA\xB3\xAF", + "\xB0\xF5" => "\xEA\xB3\xB0", + "\xB0\xF6" => "\xEA\xB3\xB1", + "\xB0\xF7" => "\xEA\xB3\xB3", + "\xB0\xF8" => "\xEA\xB3\xB5", + "\xB0\xF9" => "\xEA\xB3\xB6", + "\xB0\xFA" => "\xEA\xB3\xBC", + "\xB0\xFB" => "\xEA\xB3\xBD", + "\xB0\xFC" => "\xEA\xB4\x80", + "\xB0\xFD" => "\xEA\xB4\x84", + "\xB0\xFE" => "\xEA\xB4\x86", + "\xB1\x41" => "\xEC\xBC\x82", + "\xB1\x42" => "\xEC\xBC\x83", + "\xB1\x43" => "\xEC\xBC\x85", + "\xB1\x44" => "\xEC\xBC\x86", + "\xB1\x45" => "\xEC\xBC\x87", + "\xB1\x46" => "\xEC\xBC\x89", + "\xB1\x47" => "\xEC\xBC\x8A", + "\xB1\x48" => "\xEC\xBC\x8B", + "\xB1\x49" => "\xEC\xBC\x8C", + "\xB1\x4A" => "\xEC\xBC\x8D", + "\xB1\x4B" => "\xEC\xBC\x8E", + "\xB1\x4C" => "\xEC\xBC\x8F", + "\xB1\x4D" => "\xEC\xBC\x92", + "\xB1\x4E" => "\xEC\xBC\x94", + "\xB1\x4F" => "\xEC\xBC\x96", + "\xB1\x50" => "\xEC\xBC\x97", + "\xB1\x51" => "\xEC\xBC\x98", + "\xB1\x52" => "\xEC\xBC\x99", + "\xB1\x53" => "\xEC\xBC\x9A", + "\xB1\x54" => "\xEC\xBC\x9B", + "\xB1\x55" => "\xEC\xBC\x9D", + "\xB1\x56" => "\xEC\xBC\x9E", + "\xB1\x57" => "\xEC\xBC\x9F", + "\xB1\x58" => "\xEC\xBC\xA1", + "\xB1\x59" => "\xEC\xBC\xA2", + "\xB1\x5A" => "\xEC\xBC\xA3", + "\xB1\x61" => "\xEC\xBC\xA5", + "\xB1\x62" => "\xEC\xBC\xA6", + "\xB1\x63" => "\xEC\xBC\xA7", + "\xB1\x64" => "\xEC\xBC\xA8", + "\xB1\x65" => "\xEC\xBC\xA9", + "\xB1\x66" => "\xEC\xBC\xAA", + "\xB1\x67" => "\xEC\xBC\xAB", + "\xB1\x68" => "\xEC\xBC\xAE", + "\xB1\x69" => "\xEC\xBC\xB2", + "\xB1\x6A" => "\xEC\xBC\xB3", + "\xB1\x6B" => "\xEC\xBC\xB4", + "\xB1\x6C" => "\xEC\xBC\xB5", + "\xB1\x6D" => "\xEC\xBC\xB6", + "\xB1\x6E" => "\xEC\xBC\xB7", + "\xB1\x6F" => "\xEC\xBC\xB9", + "\xB1\x70" => "\xEC\xBC\xBA", + "\xB1\x71" => "\xEC\xBC\xBB", + "\xB1\x72" => "\xEC\xBC\xBC", + "\xB1\x73" => "\xEC\xBC\xBD", + "\xB1\x74" => "\xEC\xBC\xBE", + "\xB1\x75" => "\xEC\xBC\xBF", + "\xB1\x76" => "\xEC\xBD\x80", + "\xB1\x77" => "\xEC\xBD\x81", + "\xB1\x78" => "\xEC\xBD\x82", + "\xB1\x79" => "\xEC\xBD\x83", + "\xB1\x7A" => "\xEC\xBD\x84", + "\xB1\x81" => "\xEC\xBD\x85", + "\xB1\x82" => "\xEC\xBD\x86", + "\xB1\x83" => "\xEC\xBD\x87", + "\xB1\x84" => "\xEC\xBD\x88", + "\xB1\x85" => "\xEC\xBD\x89", + "\xB1\x86" => "\xEC\xBD\x8A", + "\xB1\x87" => "\xEC\xBD\x8B", + "\xB1\x88" => "\xEC\xBD\x8C", + "\xB1\x89" => "\xEC\xBD\x8D", + "\xB1\x8A" => "\xEC\xBD\x8E", + "\xB1\x8B" => "\xEC\xBD\x8F", + "\xB1\x8C" => "\xEC\xBD\x90", + "\xB1\x8D" => "\xEC\xBD\x91", + "\xB1\x8E" => "\xEC\xBD\x92", + "\xB1\x8F" => "\xEC\xBD\x93", + "\xB1\x90" => "\xEC\xBD\x96", + "\xB1\x91" => "\xEC\xBD\x97", + "\xB1\x92" => "\xEC\xBD\x99", + "\xB1\x93" => "\xEC\xBD\x9A", + "\xB1\x94" => "\xEC\xBD\x9B", + "\xB1\x95" => "\xEC\xBD\x9D", + "\xB1\x96" => "\xEC\xBD\x9E", + "\xB1\x97" => "\xEC\xBD\x9F", + "\xB1\x98" => "\xEC\xBD\xA0", + "\xB1\x99" => "\xEC\xBD\xA1", + "\xB1\x9A" => "\xEC\xBD\xA2", + "\xB1\x9B" => "\xEC\xBD\xA3", + "\xB1\x9C" => "\xEC\xBD\xA6", + "\xB1\x9D" => "\xEC\xBD\xA8", + "\xB1\x9E" => "\xEC\xBD\xAA", + "\xB1\x9F" => "\xEC\xBD\xAB", + "\xB1\xA0" => "\xEC\xBD\xAC", + "\xB1\xA1" => "\xEA\xB4\x8C", + "\xB1\xA2" => "\xEA\xB4\x8D", + "\xB1\xA3" => "\xEA\xB4\x8F", + "\xB1\xA4" => "\xEA\xB4\x91", + "\xB1\xA5" => "\xEA\xB4\x98", + "\xB1\xA6" => "\xEA\xB4\x9C", + "\xB1\xA7" => "\xEA\xB4\xA0", + "\xB1\xA8" => "\xEA\xB4\xA9", + "\xB1\xA9" => "\xEA\xB4\xAC", + "\xB1\xAA" => "\xEA\xB4\xAD", + "\xB1\xAB" => "\xEA\xB4\xB4", + "\xB1\xAC" => "\xEA\xB4\xB5", + "\xB1\xAD" => "\xEA\xB4\xB8", + "\xB1\xAE" => "\xEA\xB4\xBC", + "\xB1\xAF" => "\xEA\xB5\x84", + "\xB1\xB0" => "\xEA\xB5\x85", + "\xB1\xB1" => "\xEA\xB5\x87", + "\xB1\xB2" => "\xEA\xB5\x89", + "\xB1\xB3" => "\xEA\xB5\x90", + "\xB1\xB4" => "\xEA\xB5\x94", + "\xB1\xB5" => "\xEA\xB5\x98", + "\xB1\xB6" => "\xEA\xB5\xA1", + "\xB1\xB7" => "\xEA\xB5\xA3", + "\xB1\xB8" => "\xEA\xB5\xAC", + "\xB1\xB9" => "\xEA\xB5\xAD", + "\xB1\xBA" => "\xEA\xB5\xB0", + "\xB1\xBB" => "\xEA\xB5\xB3", + "\xB1\xBC" => "\xEA\xB5\xB4", + "\xB1\xBD" => "\xEA\xB5\xB5", + "\xB1\xBE" => "\xEA\xB5\xB6", + "\xB1\xBF" => "\xEA\xB5\xBB", + "\xB1\xC0" => "\xEA\xB5\xBC", + "\xB1\xC1" => "\xEA\xB5\xBD", + "\xB1\xC2" => "\xEA\xB5\xBF", + "\xB1\xC3" => "\xEA\xB6\x81", + "\xB1\xC4" => "\xEA\xB6\x82", + "\xB1\xC5" => "\xEA\xB6\x88", + "\xB1\xC6" => "\xEA\xB6\x89", + "\xB1\xC7" => "\xEA\xB6\x8C", + "\xB1\xC8" => "\xEA\xB6\x90", + "\xB1\xC9" => "\xEA\xB6\x9C", + "\xB1\xCA" => "\xEA\xB6\x9D", + "\xB1\xCB" => "\xEA\xB6\xA4", + "\xB1\xCC" => "\xEA\xB6\xB7", + "\xB1\xCD" => "\xEA\xB7\x80", + "\xB1\xCE" => "\xEA\xB7\x81", + "\xB1\xCF" => "\xEA\xB7\x84", + "\xB1\xD0" => "\xEA\xB7\x88", + "\xB1\xD1" => "\xEA\xB7\x90", + "\xB1\xD2" => "\xEA\xB7\x91", + "\xB1\xD3" => "\xEA\xB7\x93", + "\xB1\xD4" => "\xEA\xB7\x9C", + "\xB1\xD5" => "\xEA\xB7\xA0", + "\xB1\xD6" => "\xEA\xB7\xA4", + "\xB1\xD7" => "\xEA\xB7\xB8", + "\xB1\xD8" => "\xEA\xB7\xB9", + "\xB1\xD9" => "\xEA\xB7\xBC", + "\xB1\xDA" => "\xEA\xB7\xBF", + "\xB1\xDB" => "\xEA\xB8\x80", + "\xB1\xDC" => "\xEA\xB8\x81", + "\xB1\xDD" => "\xEA\xB8\x88", + "\xB1\xDE" => "\xEA\xB8\x89", + "\xB1\xDF" => "\xEA\xB8\x8B", + "\xB1\xE0" => "\xEA\xB8\x8D", + "\xB1\xE1" => "\xEA\xB8\x94", + "\xB1\xE2" => "\xEA\xB8\xB0", + "\xB1\xE3" => "\xEA\xB8\xB1", + "\xB1\xE4" => "\xEA\xB8\xB4", + "\xB1\xE5" => "\xEA\xB8\xB7", + "\xB1\xE6" => "\xEA\xB8\xB8", + "\xB1\xE7" => "\xEA\xB8\xBA", + "\xB1\xE8" => "\xEA\xB9\x80", + "\xB1\xE9" => "\xEA\xB9\x81", + "\xB1\xEA" => "\xEA\xB9\x83", + "\xB1\xEB" => "\xEA\xB9\x85", + "\xB1\xEC" => "\xEA\xB9\x86", + "\xB1\xED" => "\xEA\xB9\x8A", + "\xB1\xEE" => "\xEA\xB9\x8C", + "\xB1\xEF" => "\xEA\xB9\x8D", + "\xB1\xF0" => "\xEA\xB9\x8E", + "\xB1\xF1" => "\xEA\xB9\x90", + "\xB1\xF2" => "\xEA\xB9\x94", + "\xB1\xF3" => "\xEA\xB9\x96", + "\xB1\xF4" => "\xEA\xB9\x9C", + "\xB1\xF5" => "\xEA\xB9\x9D", + "\xB1\xF6" => "\xEA\xB9\x9F", + "\xB1\xF7" => "\xEA\xB9\xA0", + "\xB1\xF8" => "\xEA\xB9\xA1", + "\xB1\xF9" => "\xEA\xB9\xA5", + "\xB1\xFA" => "\xEA\xB9\xA8", + "\xB1\xFB" => "\xEA\xB9\xA9", + "\xB1\xFC" => "\xEA\xB9\xAC", + "\xB1\xFD" => "\xEA\xB9\xB0", + "\xB1\xFE" => "\xEA\xB9\xB8", + "\xB2\x41" => "\xEC\xBD\xAD", + "\xB2\x42" => "\xEC\xBD\xAE", + "\xB2\x43" => "\xEC\xBD\xAF", + "\xB2\x44" => "\xEC\xBD\xB2", + "\xB2\x45" => "\xEC\xBD\xB3", + "\xB2\x46" => "\xEC\xBD\xB5", + "\xB2\x47" => "\xEC\xBD\xB6", + "\xB2\x48" => "\xEC\xBD\xB7", + "\xB2\x49" => "\xEC\xBD\xB9", + "\xB2\x4A" => "\xEC\xBD\xBA", + "\xB2\x4B" => "\xEC\xBD\xBB", + "\xB2\x4C" => "\xEC\xBD\xBC", + "\xB2\x4D" => "\xEC\xBD\xBD", + "\xB2\x4E" => "\xEC\xBD\xBE", + "\xB2\x4F" => "\xEC\xBD\xBF", + "\xB2\x50" => "\xEC\xBE\x81", + "\xB2\x51" => "\xEC\xBE\x82", + "\xB2\x52" => "\xEC\xBE\x83", + "\xB2\x53" => "\xEC\xBE\x84", + "\xB2\x54" => "\xEC\xBE\x86", + "\xB2\x55" => "\xEC\xBE\x87", + "\xB2\x56" => "\xEC\xBE\x88", + "\xB2\x57" => "\xEC\xBE\x89", + "\xB2\x58" => "\xEC\xBE\x8A", + "\xB2\x59" => "\xEC\xBE\x8B", + "\xB2\x5A" => "\xEC\xBE\x8D", + "\xB2\x61" => "\xEC\xBE\x8E", + "\xB2\x62" => "\xEC\xBE\x8F", + "\xB2\x63" => "\xEC\xBE\x90", + "\xB2\x64" => "\xEC\xBE\x91", + "\xB2\x65" => "\xEC\xBE\x92", + "\xB2\x66" => "\xEC\xBE\x93", + "\xB2\x67" => "\xEC\xBE\x94", + "\xB2\x68" => "\xEC\xBE\x95", + "\xB2\x69" => "\xEC\xBE\x96", + "\xB2\x6A" => "\xEC\xBE\x97", + "\xB2\x6B" => "\xEC\xBE\x98", + "\xB2\x6C" => "\xEC\xBE\x99", + "\xB2\x6D" => "\xEC\xBE\x9A", + "\xB2\x6E" => "\xEC\xBE\x9B", + "\xB2\x6F" => "\xEC\xBE\x9C", + "\xB2\x70" => "\xEC\xBE\x9D", + "\xB2\x71" => "\xEC\xBE\x9E", + "\xB2\x72" => "\xEC\xBE\x9F", + "\xB2\x73" => "\xEC\xBE\xA0", + "\xB2\x74" => "\xEC\xBE\xA2", + "\xB2\x75" => "\xEC\xBE\xA3", + "\xB2\x76" => "\xEC\xBE\xA4", + "\xB2\x77" => "\xEC\xBE\xA5", + "\xB2\x78" => "\xEC\xBE\xA6", + "\xB2\x79" => "\xEC\xBE\xA7", + "\xB2\x7A" => "\xEC\xBE\xA9", + "\xB2\x81" => "\xEC\xBE\xAA", + "\xB2\x82" => "\xEC\xBE\xAB", + "\xB2\x83" => "\xEC\xBE\xAC", + "\xB2\x84" => "\xEC\xBE\xAD", + "\xB2\x85" => "\xEC\xBE\xAE", + "\xB2\x86" => "\xEC\xBE\xAF", + "\xB2\x87" => "\xEC\xBE\xB1", + "\xB2\x88" => "\xEC\xBE\xB2", + "\xB2\x89" => "\xEC\xBE\xB3", + "\xB2\x8A" => "\xEC\xBE\xB4", + "\xB2\x8B" => "\xEC\xBE\xB5", + "\xB2\x8C" => "\xEC\xBE\xB6", + "\xB2\x8D" => "\xEC\xBE\xB7", + "\xB2\x8E" => "\xEC\xBE\xB8", + "\xB2\x8F" => "\xEC\xBE\xB9", + "\xB2\x90" => "\xEC\xBE\xBA", + "\xB2\x91" => "\xEC\xBE\xBB", + "\xB2\x92" => "\xEC\xBE\xBC", + "\xB2\x93" => "\xEC\xBE\xBD", + "\xB2\x94" => "\xEC\xBE\xBE", + "\xB2\x95" => "\xEC\xBE\xBF", + "\xB2\x96" => "\xEC\xBF\x80", + "\xB2\x97" => "\xEC\xBF\x81", + "\xB2\x98" => "\xEC\xBF\x82", + "\xB2\x99" => "\xEC\xBF\x83", + "\xB2\x9A" => "\xEC\xBF\x85", + "\xB2\x9B" => "\xEC\xBF\x86", + "\xB2\x9C" => "\xEC\xBF\x87", + "\xB2\x9D" => "\xEC\xBF\x88", + "\xB2\x9E" => "\xEC\xBF\x89", + "\xB2\x9F" => "\xEC\xBF\x8A", + "\xB2\xA0" => "\xEC\xBF\x8B", + "\xB2\xA1" => "\xEA\xB9\xB9", + "\xB2\xA2" => "\xEA\xB9\xBB", + "\xB2\xA3" => "\xEA\xB9\xBC", + "\xB2\xA4" => "\xEA\xB9\xBD", + "\xB2\xA5" => "\xEA\xBA\x84", + "\xB2\xA6" => "\xEA\xBA\x85", + "\xB2\xA7" => "\xEA\xBA\x8C", + "\xB2\xA8" => "\xEA\xBA\xBC", + "\xB2\xA9" => "\xEA\xBA\xBD", + "\xB2\xAA" => "\xEA\xBA\xBE", + "\xB2\xAB" => "\xEA\xBB\x80", + "\xB2\xAC" => "\xEA\xBB\x84", + "\xB2\xAD" => "\xEA\xBB\x8C", + "\xB2\xAE" => "\xEA\xBB\x8D", + "\xB2\xAF" => "\xEA\xBB\x8F", + "\xB2\xB0" => "\xEA\xBB\x90", + "\xB2\xB1" => "\xEA\xBB\x91", + "\xB2\xB2" => "\xEA\xBB\x98", + "\xB2\xB3" => "\xEA\xBB\x99", + "\xB2\xB4" => "\xEA\xBB\x9C", + "\xB2\xB5" => "\xEA\xBB\xA8", + "\xB2\xB6" => "\xEA\xBB\xAB", + "\xB2\xB7" => "\xEA\xBB\xAD", + "\xB2\xB8" => "\xEA\xBB\xB4", + "\xB2\xB9" => "\xEA\xBB\xB8", + "\xB2\xBA" => "\xEA\xBB\xBC", + "\xB2\xBB" => "\xEA\xBC\x87", + "\xB2\xBC" => "\xEA\xBC\x88", + "\xB2\xBD" => "\xEA\xBC\x8D", + "\xB2\xBE" => "\xEA\xBC\x90", + "\xB2\xBF" => "\xEA\xBC\xAC", + "\xB2\xC0" => "\xEA\xBC\xAD", + "\xB2\xC1" => "\xEA\xBC\xB0", + "\xB2\xC2" => "\xEA\xBC\xB2", + "\xB2\xC3" => "\xEA\xBC\xB4", + "\xB2\xC4" => "\xEA\xBC\xBC", + "\xB2\xC5" => "\xEA\xBC\xBD", + "\xB2\xC6" => "\xEA\xBC\xBF", + "\xB2\xC7" => "\xEA\xBD\x81", + "\xB2\xC8" => "\xEA\xBD\x82", + "\xB2\xC9" => "\xEA\xBD\x83", + "\xB2\xCA" => "\xEA\xBD\x88", + "\xB2\xCB" => "\xEA\xBD\x89", + "\xB2\xCC" => "\xEA\xBD\x90", + "\xB2\xCD" => "\xEA\xBD\x9C", + "\xB2\xCE" => "\xEA\xBD\x9D", + "\xB2\xCF" => "\xEA\xBD\xA4", + "\xB2\xD0" => "\xEA\xBD\xA5", + "\xB2\xD1" => "\xEA\xBD\xB9", + "\xB2\xD2" => "\xEA\xBE\x80", + "\xB2\xD3" => "\xEA\xBE\x84", + "\xB2\xD4" => "\xEA\xBE\x88", + "\xB2\xD5" => "\xEA\xBE\x90", + "\xB2\xD6" => "\xEA\xBE\x91", + "\xB2\xD7" => "\xEA\xBE\x95", + "\xB2\xD8" => "\xEA\xBE\x9C", + "\xB2\xD9" => "\xEA\xBE\xB8", + "\xB2\xDA" => "\xEA\xBE\xB9", + "\xB2\xDB" => "\xEA\xBE\xBC", + "\xB2\xDC" => "\xEA\xBF\x80", + "\xB2\xDD" => "\xEA\xBF\x87", + "\xB2\xDE" => "\xEA\xBF\x88", + "\xB2\xDF" => "\xEA\xBF\x89", + "\xB2\xE0" => "\xEA\xBF\x8B", + "\xB2\xE1" => "\xEA\xBF\x8D", + "\xB2\xE2" => "\xEA\xBF\x8E", + "\xB2\xE3" => "\xEA\xBF\x94", + "\xB2\xE4" => "\xEA\xBF\x9C", + "\xB2\xE5" => "\xEA\xBF\xA8", + "\xB2\xE6" => "\xEA\xBF\xA9", + "\xB2\xE7" => "\xEA\xBF\xB0", + "\xB2\xE8" => "\xEA\xBF\xB1", + "\xB2\xE9" => "\xEA\xBF\xB4", + "\xB2\xEA" => "\xEA\xBF\xB8", + "\xB2\xEB" => "\xEB\x80\x80", + "\xB2\xEC" => "\xEB\x80\x81", + "\xB2\xED" => "\xEB\x80\x84", + "\xB2\xEE" => "\xEB\x80\x8C", + "\xB2\xEF" => "\xEB\x80\x90", + "\xB2\xF0" => "\xEB\x80\x94", + "\xB2\xF1" => "\xEB\x80\x9C", + "\xB2\xF2" => "\xEB\x80\x9D", + "\xB2\xF3" => "\xEB\x80\xA8", + "\xB2\xF4" => "\xEB\x81\x84", + "\xB2\xF5" => "\xEB\x81\x85", + "\xB2\xF6" => "\xEB\x81\x88", + "\xB2\xF7" => "\xEB\x81\x8A", + "\xB2\xF8" => "\xEB\x81\x8C", + "\xB2\xF9" => "\xEB\x81\x8E", + "\xB2\xFA" => "\xEB\x81\x93", + "\xB2\xFB" => "\xEB\x81\x94", + "\xB2\xFC" => "\xEB\x81\x95", + "\xB2\xFD" => "\xEB\x81\x97", + "\xB2\xFE" => "\xEB\x81\x99", + "\xB3\x41" => "\xEC\xBF\x8C", + "\xB3\x42" => "\xEC\xBF\x8D", + "\xB3\x43" => "\xEC\xBF\x8E", + "\xB3\x44" => "\xEC\xBF\x8F", + "\xB3\x45" => "\xEC\xBF\x90", + "\xB3\x46" => "\xEC\xBF\x91", + "\xB3\x47" => "\xEC\xBF\x92", + "\xB3\x48" => "\xEC\xBF\x93", + "\xB3\x49" => "\xEC\xBF\x94", + "\xB3\x4A" => "\xEC\xBF\x95", + "\xB3\x4B" => "\xEC\xBF\x96", + "\xB3\x4C" => "\xEC\xBF\x97", + "\xB3\x4D" => "\xEC\xBF\x98", + "\xB3\x4E" => "\xEC\xBF\x99", + "\xB3\x4F" => "\xEC\xBF\x9A", + "\xB3\x50" => "\xEC\xBF\x9B", + "\xB3\x51" => "\xEC\xBF\x9C", + "\xB3\x52" => "\xEC\xBF\x9D", + "\xB3\x53" => "\xEC\xBF\x9E", + "\xB3\x54" => "\xEC\xBF\x9F", + "\xB3\x55" => "\xEC\xBF\xA2", + "\xB3\x56" => "\xEC\xBF\xA3", + "\xB3\x57" => "\xEC\xBF\xA5", + "\xB3\x58" => "\xEC\xBF\xA6", + "\xB3\x59" => "\xEC\xBF\xA7", + "\xB3\x5A" => "\xEC\xBF\xA9", + "\xB3\x61" => "\xEC\xBF\xAA", + "\xB3\x62" => "\xEC\xBF\xAB", + "\xB3\x63" => "\xEC\xBF\xAC", + "\xB3\x64" => "\xEC\xBF\xAD", + "\xB3\x65" => "\xEC\xBF\xAE", + "\xB3\x66" => "\xEC\xBF\xAF", + "\xB3\x67" => "\xEC\xBF\xB2", + "\xB3\x68" => "\xEC\xBF\xB4", + "\xB3\x69" => "\xEC\xBF\xB6", + "\xB3\x6A" => "\xEC\xBF\xB7", + "\xB3\x6B" => "\xEC\xBF\xB8", + "\xB3\x6C" => "\xEC\xBF\xB9", + "\xB3\x6D" => "\xEC\xBF\xBA", + "\xB3\x6E" => "\xEC\xBF\xBB", + "\xB3\x6F" => "\xEC\xBF\xBD", + "\xB3\x70" => "\xEC\xBF\xBE", + "\xB3\x71" => "\xEC\xBF\xBF", + "\xB3\x72" => "\xED\x80\x81", + "\xB3\x73" => "\xED\x80\x82", + "\xB3\x74" => "\xED\x80\x83", + "\xB3\x75" => "\xED\x80\x85", + "\xB3\x76" => "\xED\x80\x86", + "\xB3\x77" => "\xED\x80\x87", + "\xB3\x78" => "\xED\x80\x88", + "\xB3\x79" => "\xED\x80\x89", + "\xB3\x7A" => "\xED\x80\x8A", + "\xB3\x81" => "\xED\x80\x8B", + "\xB3\x82" => "\xED\x80\x8C", + "\xB3\x83" => "\xED\x80\x8D", + "\xB3\x84" => "\xED\x80\x8E", + "\xB3\x85" => "\xED\x80\x8F", + "\xB3\x86" => "\xED\x80\x90", + "\xB3\x87" => "\xED\x80\x92", + "\xB3\x88" => "\xED\x80\x93", + "\xB3\x89" => "\xED\x80\x94", + "\xB3\x8A" => "\xED\x80\x95", + "\xB3\x8B" => "\xED\x80\x96", + "\xB3\x8C" => "\xED\x80\x97", + "\xB3\x8D" => "\xED\x80\x99", + "\xB3\x8E" => "\xED\x80\x9A", + "\xB3\x8F" => "\xED\x80\x9B", + "\xB3\x90" => "\xED\x80\x9C", + "\xB3\x91" => "\xED\x80\x9D", + "\xB3\x92" => "\xED\x80\x9E", + "\xB3\x93" => "\xED\x80\x9F", + "\xB3\x94" => "\xED\x80\xA0", + "\xB3\x95" => "\xED\x80\xA1", + "\xB3\x96" => "\xED\x80\xA2", + "\xB3\x97" => "\xED\x80\xA3", + "\xB3\x98" => "\xED\x80\xA4", + "\xB3\x99" => "\xED\x80\xA5", + "\xB3\x9A" => "\xED\x80\xA6", + "\xB3\x9B" => "\xED\x80\xA7", + "\xB3\x9C" => "\xED\x80\xA8", + "\xB3\x9D" => "\xED\x80\xA9", + "\xB3\x9E" => "\xED\x80\xAA", + "\xB3\x9F" => "\xED\x80\xAB", + "\xB3\xA0" => "\xED\x80\xAC", + "\xB3\xA1" => "\xEB\x81\x9D", + "\xB3\xA2" => "\xEB\x81\xBC", + "\xB3\xA3" => "\xEB\x81\xBD", + "\xB3\xA4" => "\xEB\x82\x80", + "\xB3\xA5" => "\xEB\x82\x84", + "\xB3\xA6" => "\xEB\x82\x8C", + "\xB3\xA7" => "\xEB\x82\x8D", + "\xB3\xA8" => "\xEB\x82\x8F", + "\xB3\xA9" => "\xEB\x82\x91", + "\xB3\xAA" => "\xEB\x82\x98", + "\xB3\xAB" => "\xEB\x82\x99", + "\xB3\xAC" => "\xEB\x82\x9A", + "\xB3\xAD" => "\xEB\x82\x9C", + "\xB3\xAE" => "\xEB\x82\x9F", + "\xB3\xAF" => "\xEB\x82\xA0", + "\xB3\xB0" => "\xEB\x82\xA1", + "\xB3\xB1" => "\xEB\x82\xA2", + "\xB3\xB2" => "\xEB\x82\xA8", + "\xB3\xB3" => "\xEB\x82\xA9", + "\xB3\xB4" => "\xEB\x82\xAB", + "\xB3\xB5" => "\xEB\x82\xAC", + "\xB3\xB6" => "\xEB\x82\xAD", + "\xB3\xB7" => "\xEB\x82\xAE", + "\xB3\xB8" => "\xEB\x82\xAF", + "\xB3\xB9" => "\xEB\x82\xB1", + "\xB3\xBA" => "\xEB\x82\xB3", + "\xB3\xBB" => "\xEB\x82\xB4", + "\xB3\xBC" => "\xEB\x82\xB5", + "\xB3\xBD" => "\xEB\x82\xB8", + "\xB3\xBE" => "\xEB\x82\xBC", + "\xB3\xBF" => "\xEB\x83\x84", + "\xB3\xC0" => "\xEB\x83\x85", + "\xB3\xC1" => "\xEB\x83\x87", + "\xB3\xC2" => "\xEB\x83\x88", + "\xB3\xC3" => "\xEB\x83\x89", + "\xB3\xC4" => "\xEB\x83\x90", + "\xB3\xC5" => "\xEB\x83\x91", + "\xB3\xC6" => "\xEB\x83\x94", + "\xB3\xC7" => "\xEB\x83\x98", + "\xB3\xC8" => "\xEB\x83\xA0", + "\xB3\xC9" => "\xEB\x83\xA5", + "\xB3\xCA" => "\xEB\x84\x88", + "\xB3\xCB" => "\xEB\x84\x89", + "\xB3\xCC" => "\xEB\x84\x8B", + "\xB3\xCD" => "\xEB\x84\x8C", + "\xB3\xCE" => "\xEB\x84\x90", + "\xB3\xCF" => "\xEB\x84\x92", + "\xB3\xD0" => "\xEB\x84\x93", + "\xB3\xD1" => "\xEB\x84\x98", + "\xB3\xD2" => "\xEB\x84\x99", + "\xB3\xD3" => "\xEB\x84\x9B", + "\xB3\xD4" => "\xEB\x84\x9C", + "\xB3\xD5" => "\xEB\x84\x9D", + "\xB3\xD6" => "\xEB\x84\xA3", + "\xB3\xD7" => "\xEB\x84\xA4", + "\xB3\xD8" => "\xEB\x84\xA5", + "\xB3\xD9" => "\xEB\x84\xA8", + "\xB3\xDA" => "\xEB\x84\xAC", + "\xB3\xDB" => "\xEB\x84\xB4", + "\xB3\xDC" => "\xEB\x84\xB5", + "\xB3\xDD" => "\xEB\x84\xB7", + "\xB3\xDE" => "\xEB\x84\xB8", + "\xB3\xDF" => "\xEB\x84\xB9", + "\xB3\xE0" => "\xEB\x85\x80", + "\xB3\xE1" => "\xEB\x85\x81", + "\xB3\xE2" => "\xEB\x85\x84", + "\xB3\xE3" => "\xEB\x85\x88", + "\xB3\xE4" => "\xEB\x85\x90", + "\xB3\xE5" => "\xEB\x85\x91", + "\xB3\xE6" => "\xEB\x85\x94", + "\xB3\xE7" => "\xEB\x85\x95", + "\xB3\xE8" => "\xEB\x85\x98", + "\xB3\xE9" => "\xEB\x85\x9C", + "\xB3\xEA" => "\xEB\x85\xA0", + "\xB3\xEB" => "\xEB\x85\xB8", + "\xB3\xEC" => "\xEB\x85\xB9", + "\xB3\xED" => "\xEB\x85\xBC", + "\xB3\xEE" => "\xEB\x86\x80", + "\xB3\xEF" => "\xEB\x86\x82", + "\xB3\xF0" => "\xEB\x86\x88", + "\xB3\xF1" => "\xEB\x86\x89", + "\xB3\xF2" => "\xEB\x86\x8B", + "\xB3\xF3" => "\xEB\x86\x8D", + "\xB3\xF4" => "\xEB\x86\x92", + "\xB3\xF5" => "\xEB\x86\x93", + "\xB3\xF6" => "\xEB\x86\x94", + "\xB3\xF7" => "\xEB\x86\x98", + "\xB3\xF8" => "\xEB\x86\x9C", + "\xB3\xF9" => "\xEB\x86\xA8", + "\xB3\xFA" => "\xEB\x87\x8C", + "\xB3\xFB" => "\xEB\x87\x90", + "\xB3\xFC" => "\xEB\x87\x94", + "\xB3\xFD" => "\xEB\x87\x9C", + "\xB3\xFE" => "\xEB\x87\x9D", + "\xB4\x41" => "\xED\x80\xAE", + "\xB4\x42" => "\xED\x80\xAF", + "\xB4\x43" => "\xED\x80\xB0", + "\xB4\x44" => "\xED\x80\xB1", + "\xB4\x45" => "\xED\x80\xB2", + "\xB4\x46" => "\xED\x80\xB3", + "\xB4\x47" => "\xED\x80\xB6", + "\xB4\x48" => "\xED\x80\xB7", + "\xB4\x49" => "\xED\x80\xB9", + "\xB4\x4A" => "\xED\x80\xBA", + "\xB4\x4B" => "\xED\x80\xBB", + "\xB4\x4C" => "\xED\x80\xBD", + "\xB4\x4D" => "\xED\x80\xBE", + "\xB4\x4E" => "\xED\x80\xBF", + "\xB4\x4F" => "\xED\x81\x80", + "\xB4\x50" => "\xED\x81\x81", + "\xB4\x51" => "\xED\x81\x82", + "\xB4\x52" => "\xED\x81\x83", + "\xB4\x53" => "\xED\x81\x86", + "\xB4\x54" => "\xED\x81\x88", + "\xB4\x55" => "\xED\x81\x8A", + "\xB4\x56" => "\xED\x81\x8B", + "\xB4\x57" => "\xED\x81\x8C", + "\xB4\x58" => "\xED\x81\x8D", + "\xB4\x59" => "\xED\x81\x8E", + "\xB4\x5A" => "\xED\x81\x8F", + "\xB4\x61" => "\xED\x81\x91", + "\xB4\x62" => "\xED\x81\x92", + "\xB4\x63" => "\xED\x81\x93", + "\xB4\x64" => "\xED\x81\x95", + "\xB4\x65" => "\xED\x81\x96", + "\xB4\x66" => "\xED\x81\x97", + "\xB4\x67" => "\xED\x81\x99", + "\xB4\x68" => "\xED\x81\x9A", + "\xB4\x69" => "\xED\x81\x9B", + "\xB4\x6A" => "\xED\x81\x9C", + "\xB4\x6B" => "\xED\x81\x9D", + "\xB4\x6C" => "\xED\x81\x9E", + "\xB4\x6D" => "\xED\x81\x9F", + "\xB4\x6E" => "\xED\x81\xA1", + "\xB4\x6F" => "\xED\x81\xA2", + "\xB4\x70" => "\xED\x81\xA3", + "\xB4\x71" => "\xED\x81\xA4", + "\xB4\x72" => "\xED\x81\xA5", + "\xB4\x73" => "\xED\x81\xA6", + "\xB4\x74" => "\xED\x81\xA7", + "\xB4\x75" => "\xED\x81\xA8", + "\xB4\x76" => "\xED\x81\xA9", + "\xB4\x77" => "\xED\x81\xAA", + "\xB4\x78" => "\xED\x81\xAB", + "\xB4\x79" => "\xED\x81\xAE", + "\xB4\x7A" => "\xED\x81\xAF", + "\xB4\x81" => "\xED\x81\xB1", + "\xB4\x82" => "\xED\x81\xB2", + "\xB4\x83" => "\xED\x81\xB3", + "\xB4\x84" => "\xED\x81\xB5", + "\xB4\x85" => "\xED\x81\xB6", + "\xB4\x86" => "\xED\x81\xB7", + "\xB4\x87" => "\xED\x81\xB8", + "\xB4\x88" => "\xED\x81\xB9", + "\xB4\x89" => "\xED\x81\xBA", + "\xB4\x8A" => "\xED\x81\xBB", + "\xB4\x8B" => "\xED\x81\xBE", + "\xB4\x8C" => "\xED\x81\xBF", + "\xB4\x8D" => "\xED\x82\x80", + "\xB4\x8E" => "\xED\x82\x82", + "\xB4\x8F" => "\xED\x82\x83", + "\xB4\x90" => "\xED\x82\x84", + "\xB4\x91" => "\xED\x82\x85", + "\xB4\x92" => "\xED\x82\x86", + "\xB4\x93" => "\xED\x82\x87", + "\xB4\x94" => "\xED\x82\x88", + "\xB4\x95" => "\xED\x82\x89", + "\xB4\x96" => "\xED\x82\x8A", + "\xB4\x97" => "\xED\x82\x8B", + "\xB4\x98" => "\xED\x82\x8C", + "\xB4\x99" => "\xED\x82\x8D", + "\xB4\x9A" => "\xED\x82\x8E", + "\xB4\x9B" => "\xED\x82\x8F", + "\xB4\x9C" => "\xED\x82\x90", + "\xB4\x9D" => "\xED\x82\x91", + "\xB4\x9E" => "\xED\x82\x92", + "\xB4\x9F" => "\xED\x82\x93", + "\xB4\xA0" => "\xED\x82\x94", + "\xB4\xA1" => "\xEB\x87\x9F", + "\xB4\xA2" => "\xEB\x87\xA8", + "\xB4\xA3" => "\xEB\x87\xA9", + "\xB4\xA4" => "\xEB\x87\xAC", + "\xB4\xA5" => "\xEB\x87\xB0", + "\xB4\xA6" => "\xEB\x87\xB9", + "\xB4\xA7" => "\xEB\x87\xBB", + "\xB4\xA8" => "\xEB\x87\xBD", + "\xB4\xA9" => "\xEB\x88\x84", + "\xB4\xAA" => "\xEB\x88\x85", + "\xB4\xAB" => "\xEB\x88\x88", + "\xB4\xAC" => "\xEB\x88\x8B", + "\xB4\xAD" => "\xEB\x88\x8C", + "\xB4\xAE" => "\xEB\x88\x94", + "\xB4\xAF" => "\xEB\x88\x95", + "\xB4\xB0" => "\xEB\x88\x97", + "\xB4\xB1" => "\xEB\x88\x99", + "\xB4\xB2" => "\xEB\x88\xA0", + "\xB4\xB3" => "\xEB\x88\xB4", + "\xB4\xB4" => "\xEB\x88\xBC", + "\xB4\xB5" => "\xEB\x89\x98", + "\xB4\xB6" => "\xEB\x89\x9C", + "\xB4\xB7" => "\xEB\x89\xA0", + "\xB4\xB8" => "\xEB\x89\xA8", + "\xB4\xB9" => "\xEB\x89\xA9", + "\xB4\xBA" => "\xEB\x89\xB4", + "\xB4\xBB" => "\xEB\x89\xB5", + "\xB4\xBC" => "\xEB\x89\xBC", + "\xB4\xBD" => "\xEB\x8A\x84", + "\xB4\xBE" => "\xEB\x8A\x85", + "\xB4\xBF" => "\xEB\x8A\x89", + "\xB4\xC0" => "\xEB\x8A\x90", + "\xB4\xC1" => "\xEB\x8A\x91", + "\xB4\xC2" => "\xEB\x8A\x94", + "\xB4\xC3" => "\xEB\x8A\x98", + "\xB4\xC4" => "\xEB\x8A\x99", + "\xB4\xC5" => "\xEB\x8A\x9A", + "\xB4\xC6" => "\xEB\x8A\xA0", + "\xB4\xC7" => "\xEB\x8A\xA1", + "\xB4\xC8" => "\xEB\x8A\xA3", + "\xB4\xC9" => "\xEB\x8A\xA5", + "\xB4\xCA" => "\xEB\x8A\xA6", + "\xB4\xCB" => "\xEB\x8A\xAA", + "\xB4\xCC" => "\xEB\x8A\xAC", + "\xB4\xCD" => "\xEB\x8A\xB0", + "\xB4\xCE" => "\xEB\x8A\xB4", + "\xB4\xCF" => "\xEB\x8B\x88", + "\xB4\xD0" => "\xEB\x8B\x89", + "\xB4\xD1" => "\xEB\x8B\x8C", + "\xB4\xD2" => "\xEB\x8B\x90", + "\xB4\xD3" => "\xEB\x8B\x92", + "\xB4\xD4" => "\xEB\x8B\x98", + "\xB4\xD5" => "\xEB\x8B\x99", + "\xB4\xD6" => "\xEB\x8B\x9B", + "\xB4\xD7" => "\xEB\x8B\x9D", + "\xB4\xD8" => "\xEB\x8B\xA2", + "\xB4\xD9" => "\xEB\x8B\xA4", + "\xB4\xDA" => "\xEB\x8B\xA5", + "\xB4\xDB" => "\xEB\x8B\xA6", + "\xB4\xDC" => "\xEB\x8B\xA8", + "\xB4\xDD" => "\xEB\x8B\xAB", + "\xB4\xDE" => "\xEB\x8B\xAC", + "\xB4\xDF" => "\xEB\x8B\xAD", + "\xB4\xE0" => "\xEB\x8B\xAE", + "\xB4\xE1" => "\xEB\x8B\xAF", + "\xB4\xE2" => "\xEB\x8B\xB3", + "\xB4\xE3" => "\xEB\x8B\xB4", + "\xB4\xE4" => "\xEB\x8B\xB5", + "\xB4\xE5" => "\xEB\x8B\xB7", + "\xB4\xE6" => "\xEB\x8B\xB8", + "\xB4\xE7" => "\xEB\x8B\xB9", + "\xB4\xE8" => "\xEB\x8B\xBA", + "\xB4\xE9" => "\xEB\x8B\xBB", + "\xB4\xEA" => "\xEB\x8B\xBF", + "\xB4\xEB" => "\xEB\x8C\x80", + "\xB4\xEC" => "\xEB\x8C\x81", + "\xB4\xED" => "\xEB\x8C\x84", + "\xB4\xEE" => "\xEB\x8C\x88", + "\xB4\xEF" => "\xEB\x8C\x90", + "\xB4\xF0" => "\xEB\x8C\x91", + "\xB4\xF1" => "\xEB\x8C\x93", + "\xB4\xF2" => "\xEB\x8C\x94", + "\xB4\xF3" => "\xEB\x8C\x95", + "\xB4\xF4" => "\xEB\x8C\x9C", + "\xB4\xF5" => "\xEB\x8D\x94", + "\xB4\xF6" => "\xEB\x8D\x95", + "\xB4\xF7" => "\xEB\x8D\x96", + "\xB4\xF8" => "\xEB\x8D\x98", + "\xB4\xF9" => "\xEB\x8D\x9B", + "\xB4\xFA" => "\xEB\x8D\x9C", + "\xB4\xFB" => "\xEB\x8D\x9E", + "\xB4\xFC" => "\xEB\x8D\x9F", + "\xB4\xFD" => "\xEB\x8D\xA4", + "\xB4\xFE" => "\xEB\x8D\xA5", + "\xB5\x41" => "\xED\x82\x95", + "\xB5\x42" => "\xED\x82\x96", + "\xB5\x43" => "\xED\x82\x97", + "\xB5\x44" => "\xED\x82\x98", + "\xB5\x45" => "\xED\x82\x99", + "\xB5\x46" => "\xED\x82\x9A", + "\xB5\x47" => "\xED\x82\x9B", + "\xB5\x48" => "\xED\x82\x9C", + "\xB5\x49" => "\xED\x82\x9D", + "\xB5\x4A" => "\xED\x82\x9E", + "\xB5\x4B" => "\xED\x82\x9F", + "\xB5\x4C" => "\xED\x82\xA0", + "\xB5\x4D" => "\xED\x82\xA1", + "\xB5\x4E" => "\xED\x82\xA2", + "\xB5\x4F" => "\xED\x82\xA3", + "\xB5\x50" => "\xED\x82\xA6", + "\xB5\x51" => "\xED\x82\xA7", + "\xB5\x52" => "\xED\x82\xA9", + "\xB5\x53" => "\xED\x82\xAA", + "\xB5\x54" => "\xED\x82\xAB", + "\xB5\x55" => "\xED\x82\xAD", + "\xB5\x56" => "\xED\x82\xAE", + "\xB5\x57" => "\xED\x82\xAF", + "\xB5\x58" => "\xED\x82\xB0", + "\xB5\x59" => "\xED\x82\xB1", + "\xB5\x5A" => "\xED\x82\xB2", + "\xB5\x61" => "\xED\x82\xB3", + "\xB5\x62" => "\xED\x82\xB6", + "\xB5\x63" => "\xED\x82\xB8", + "\xB5\x64" => "\xED\x82\xBA", + "\xB5\x65" => "\xED\x82\xBB", + "\xB5\x66" => "\xED\x82\xBC", + "\xB5\x67" => "\xED\x82\xBD", + "\xB5\x68" => "\xED\x82\xBE", + "\xB5\x69" => "\xED\x82\xBF", + "\xB5\x6A" => "\xED\x83\x82", + "\xB5\x6B" => "\xED\x83\x83", + "\xB5\x6C" => "\xED\x83\x85", + "\xB5\x6D" => "\xED\x83\x86", + "\xB5\x6E" => "\xED\x83\x87", + "\xB5\x6F" => "\xED\x83\x8A", + "\xB5\x70" => "\xED\x83\x8B", + "\xB5\x71" => "\xED\x83\x8C", + "\xB5\x72" => "\xED\x83\x8D", + "\xB5\x73" => "\xED\x83\x8E", + "\xB5\x74" => "\xED\x83\x8F", + "\xB5\x75" => "\xED\x83\x92", + "\xB5\x76" => "\xED\x83\x96", + "\xB5\x77" => "\xED\x83\x97", + "\xB5\x78" => "\xED\x83\x98", + "\xB5\x79" => "\xED\x83\x99", + "\xB5\x7A" => "\xED\x83\x9A", + "\xB5\x81" => "\xED\x83\x9B", + "\xB5\x82" => "\xED\x83\x9E", + "\xB5\x83" => "\xED\x83\x9F", + "\xB5\x84" => "\xED\x83\xA1", + "\xB5\x85" => "\xED\x83\xA2", + "\xB5\x86" => "\xED\x83\xA3", + "\xB5\x87" => "\xED\x83\xA5", + "\xB5\x88" => "\xED\x83\xA6", + "\xB5\x89" => "\xED\x83\xA7", + "\xB5\x8A" => "\xED\x83\xA8", + "\xB5\x8B" => "\xED\x83\xA9", + "\xB5\x8C" => "\xED\x83\xAA", + "\xB5\x8D" => "\xED\x83\xAB", + "\xB5\x8E" => "\xED\x83\xAE", + "\xB5\x8F" => "\xED\x83\xB2", + "\xB5\x90" => "\xED\x83\xB3", + "\xB5\x91" => "\xED\x83\xB4", + "\xB5\x92" => "\xED\x83\xB5", + "\xB5\x93" => "\xED\x83\xB6", + "\xB5\x94" => "\xED\x83\xB7", + "\xB5\x95" => "\xED\x83\xB9", + "\xB5\x96" => "\xED\x83\xBA", + "\xB5\x97" => "\xED\x83\xBB", + "\xB5\x98" => "\xED\x83\xBC", + "\xB5\x99" => "\xED\x83\xBD", + "\xB5\x9A" => "\xED\x83\xBE", + "\xB5\x9B" => "\xED\x83\xBF", + "\xB5\x9C" => "\xED\x84\x80", + "\xB5\x9D" => "\xED\x84\x81", + "\xB5\x9E" => "\xED\x84\x82", + "\xB5\x9F" => "\xED\x84\x83", + "\xB5\xA0" => "\xED\x84\x84", + "\xB5\xA1" => "\xEB\x8D\xA7", + "\xB5\xA2" => "\xEB\x8D\xA9", + "\xB5\xA3" => "\xEB\x8D\xAB", + "\xB5\xA4" => "\xEB\x8D\xAE", + "\xB5\xA5" => "\xEB\x8D\xB0", + "\xB5\xA6" => "\xEB\x8D\xB1", + "\xB5\xA7" => "\xEB\x8D\xB4", + "\xB5\xA8" => "\xEB\x8D\xB8", + "\xB5\xA9" => "\xEB\x8E\x80", + "\xB5\xAA" => "\xEB\x8E\x81", + "\xB5\xAB" => "\xEB\x8E\x83", + "\xB5\xAC" => "\xEB\x8E\x84", + "\xB5\xAD" => "\xEB\x8E\x85", + "\xB5\xAE" => "\xEB\x8E\x8C", + "\xB5\xAF" => "\xEB\x8E\x90", + "\xB5\xB0" => "\xEB\x8E\x94", + "\xB5\xB1" => "\xEB\x8E\xA0", + "\xB5\xB2" => "\xEB\x8E\xA1", + "\xB5\xB3" => "\xEB\x8E\xA8", + "\xB5\xB4" => "\xEB\x8E\xAC", + "\xB5\xB5" => "\xEB\x8F\x84", + "\xB5\xB6" => "\xEB\x8F\x85", + "\xB5\xB7" => "\xEB\x8F\x88", + "\xB5\xB8" => "\xEB\x8F\x8B", + "\xB5\xB9" => "\xEB\x8F\x8C", + "\xB5\xBA" => "\xEB\x8F\x8E", + "\xB5\xBB" => "\xEB\x8F\x90", + "\xB5\xBC" => "\xEB\x8F\x94", + "\xB5\xBD" => "\xEB\x8F\x95", + "\xB5\xBE" => "\xEB\x8F\x97", + "\xB5\xBF" => "\xEB\x8F\x99", + "\xB5\xC0" => "\xEB\x8F\x9B", + "\xB5\xC1" => "\xEB\x8F\x9D", + "\xB5\xC2" => "\xEB\x8F\xA0", + "\xB5\xC3" => "\xEB\x8F\xA4", + "\xB5\xC4" => "\xEB\x8F\xA8", + "\xB5\xC5" => "\xEB\x8F\xBC", + "\xB5\xC6" => "\xEB\x90\x90", + "\xB5\xC7" => "\xEB\x90\x98", + "\xB5\xC8" => "\xEB\x90\x9C", + "\xB5\xC9" => "\xEB\x90\xA0", + "\xB5\xCA" => "\xEB\x90\xA8", + "\xB5\xCB" => "\xEB\x90\xA9", + "\xB5\xCC" => "\xEB\x90\xAB", + "\xB5\xCD" => "\xEB\x90\xB4", + "\xB5\xCE" => "\xEB\x91\x90", + "\xB5\xCF" => "\xEB\x91\x91", + "\xB5\xD0" => "\xEB\x91\x94", + "\xB5\xD1" => "\xEB\x91\x98", + "\xB5\xD2" => "\xEB\x91\xA0", + "\xB5\xD3" => "\xEB\x91\xA1", + "\xB5\xD4" => "\xEB\x91\xA3", + "\xB5\xD5" => "\xEB\x91\xA5", + "\xB5\xD6" => "\xEB\x91\xAC", + "\xB5\xD7" => "\xEB\x92\x80", + "\xB5\xD8" => "\xEB\x92\x88", + "\xB5\xD9" => "\xEB\x92\x9D", + "\xB5\xDA" => "\xEB\x92\xA4", + "\xB5\xDB" => "\xEB\x92\xA8", + "\xB5\xDC" => "\xEB\x92\xAC", + "\xB5\xDD" => "\xEB\x92\xB5", + "\xB5\xDE" => "\xEB\x92\xB7", + "\xB5\xDF" => "\xEB\x92\xB9", + "\xB5\xE0" => "\xEB\x93\x80", + "\xB5\xE1" => "\xEB\x93\x84", + "\xB5\xE2" => "\xEB\x93\x88", + "\xB5\xE3" => "\xEB\x93\x90", + "\xB5\xE4" => "\xEB\x93\x95", + "\xB5\xE5" => "\xEB\x93\x9C", + "\xB5\xE6" => "\xEB\x93\x9D", + "\xB5\xE7" => "\xEB\x93\xA0", + "\xB5\xE8" => "\xEB\x93\xA3", + "\xB5\xE9" => "\xEB\x93\xA4", + "\xB5\xEA" => "\xEB\x93\xA6", + "\xB5\xEB" => "\xEB\x93\xAC", + "\xB5\xEC" => "\xEB\x93\xAD", + "\xB5\xED" => "\xEB\x93\xAF", + "\xB5\xEE" => "\xEB\x93\xB1", + "\xB5\xEF" => "\xEB\x93\xB8", + "\xB5\xF0" => "\xEB\x94\x94", + "\xB5\xF1" => "\xEB\x94\x95", + "\xB5\xF2" => "\xEB\x94\x98", + "\xB5\xF3" => "\xEB\x94\x9B", + "\xB5\xF4" => "\xEB\x94\x9C", + "\xB5\xF5" => "\xEB\x94\xA4", + "\xB5\xF6" => "\xEB\x94\xA5", + "\xB5\xF7" => "\xEB\x94\xA7", + "\xB5\xF8" => "\xEB\x94\xA8", + "\xB5\xF9" => "\xEB\x94\xA9", + "\xB5\xFA" => "\xEB\x94\xAA", + "\xB5\xFB" => "\xEB\x94\xB0", + "\xB5\xFC" => "\xEB\x94\xB1", + "\xB5\xFD" => "\xEB\x94\xB4", + "\xB5\xFE" => "\xEB\x94\xB8", + "\xB6\x41" => "\xED\x84\x85", + "\xB6\x42" => "\xED\x84\x86", + "\xB6\x43" => "\xED\x84\x87", + "\xB6\x44" => "\xED\x84\x88", + "\xB6\x45" => "\xED\x84\x89", + "\xB6\x46" => "\xED\x84\x8A", + "\xB6\x47" => "\xED\x84\x8B", + "\xB6\x48" => "\xED\x84\x8C", + "\xB6\x49" => "\xED\x84\x8E", + "\xB6\x4A" => "\xED\x84\x8F", + "\xB6\x4B" => "\xED\x84\x90", + "\xB6\x4C" => "\xED\x84\x91", + "\xB6\x4D" => "\xED\x84\x92", + "\xB6\x4E" => "\xED\x84\x93", + "\xB6\x4F" => "\xED\x84\x94", + "\xB6\x50" => "\xED\x84\x95", + "\xB6\x51" => "\xED\x84\x96", + "\xB6\x52" => "\xED\x84\x97", + "\xB6\x53" => "\xED\x84\x98", + "\xB6\x54" => "\xED\x84\x99", + "\xB6\x55" => "\xED\x84\x9A", + "\xB6\x56" => "\xED\x84\x9B", + "\xB6\x57" => "\xED\x84\x9C", + "\xB6\x58" => "\xED\x84\x9D", + "\xB6\x59" => "\xED\x84\x9E", + "\xB6\x5A" => "\xED\x84\x9F", + "\xB6\x61" => "\xED\x84\xA0", + "\xB6\x62" => "\xED\x84\xA1", + "\xB6\x63" => "\xED\x84\xA2", + "\xB6\x64" => "\xED\x84\xA3", + "\xB6\x65" => "\xED\x84\xA4", + "\xB6\x66" => "\xED\x84\xA5", + "\xB6\x67" => "\xED\x84\xA6", + "\xB6\x68" => "\xED\x84\xA7", + "\xB6\x69" => "\xED\x84\xA8", + "\xB6\x6A" => "\xED\x84\xA9", + "\xB6\x6B" => "\xED\x84\xAA", + "\xB6\x6C" => "\xED\x84\xAB", + "\xB6\x6D" => "\xED\x84\xAC", + "\xB6\x6E" => "\xED\x84\xAD", + "\xB6\x6F" => "\xED\x84\xAE", + "\xB6\x70" => "\xED\x84\xAF", + "\xB6\x71" => "\xED\x84\xB2", + "\xB6\x72" => "\xED\x84\xB3", + "\xB6\x73" => "\xED\x84\xB5", + "\xB6\x74" => "\xED\x84\xB6", + "\xB6\x75" => "\xED\x84\xB7", + "\xB6\x76" => "\xED\x84\xB9", + "\xB6\x77" => "\xED\x84\xBB", + "\xB6\x78" => "\xED\x84\xBC", + "\xB6\x79" => "\xED\x84\xBD", + "\xB6\x7A" => "\xED\x84\xBE", + "\xB6\x81" => "\xED\x84\xBF", + "\xB6\x82" => "\xED\x85\x82", + "\xB6\x83" => "\xED\x85\x86", + "\xB6\x84" => "\xED\x85\x87", + "\xB6\x85" => "\xED\x85\x88", + "\xB6\x86" => "\xED\x85\x89", + "\xB6\x87" => "\xED\x85\x8A", + "\xB6\x88" => "\xED\x85\x8B", + "\xB6\x89" => "\xED\x85\x8E", + "\xB6\x8A" => "\xED\x85\x8F", + "\xB6\x8B" => "\xED\x85\x91", + "\xB6\x8C" => "\xED\x85\x92", + "\xB6\x8D" => "\xED\x85\x93", + "\xB6\x8E" => "\xED\x85\x95", + "\xB6\x8F" => "\xED\x85\x96", + "\xB6\x90" => "\xED\x85\x97", + "\xB6\x91" => "\xED\x85\x98", + "\xB6\x92" => "\xED\x85\x99", + "\xB6\x93" => "\xED\x85\x9A", + "\xB6\x94" => "\xED\x85\x9B", + "\xB6\x95" => "\xED\x85\x9E", + "\xB6\x96" => "\xED\x85\xA0", + "\xB6\x97" => "\xED\x85\xA2", + "\xB6\x98" => "\xED\x85\xA3", + "\xB6\x99" => "\xED\x85\xA4", + "\xB6\x9A" => "\xED\x85\xA5", + "\xB6\x9B" => "\xED\x85\xA6", + "\xB6\x9C" => "\xED\x85\xA7", + "\xB6\x9D" => "\xED\x85\xA9", + "\xB6\x9E" => "\xED\x85\xAA", + "\xB6\x9F" => "\xED\x85\xAB", + "\xB6\xA0" => "\xED\x85\xAD", + "\xB6\xA1" => "\xEB\x95\x80", + "\xB6\xA2" => "\xEB\x95\x81", + "\xB6\xA3" => "\xEB\x95\x83", + "\xB6\xA4" => "\xEB\x95\x84", + "\xB6\xA5" => "\xEB\x95\x85", + "\xB6\xA6" => "\xEB\x95\x8B", + "\xB6\xA7" => "\xEB\x95\x8C", + "\xB6\xA8" => "\xEB\x95\x8D", + "\xB6\xA9" => "\xEB\x95\x90", + "\xB6\xAA" => "\xEB\x95\x94", + "\xB6\xAB" => "\xEB\x95\x9C", + "\xB6\xAC" => "\xEB\x95\x9D", + "\xB6\xAD" => "\xEB\x95\x9F", + "\xB6\xAE" => "\xEB\x95\xA0", + "\xB6\xAF" => "\xEB\x95\xA1", + "\xB6\xB0" => "\xEB\x96\xA0", + "\xB6\xB1" => "\xEB\x96\xA1", + "\xB6\xB2" => "\xEB\x96\xA4", + "\xB6\xB3" => "\xEB\x96\xA8", + "\xB6\xB4" => "\xEB\x96\xAA", + "\xB6\xB5" => "\xEB\x96\xAB", + "\xB6\xB6" => "\xEB\x96\xB0", + "\xB6\xB7" => "\xEB\x96\xB1", + "\xB6\xB8" => "\xEB\x96\xB3", + "\xB6\xB9" => "\xEB\x96\xB4", + "\xB6\xBA" => "\xEB\x96\xB5", + "\xB6\xBB" => "\xEB\x96\xBB", + "\xB6\xBC" => "\xEB\x96\xBC", + "\xB6\xBD" => "\xEB\x96\xBD", + "\xB6\xBE" => "\xEB\x97\x80", + "\xB6\xBF" => "\xEB\x97\x84", + "\xB6\xC0" => "\xEB\x97\x8C", + "\xB6\xC1" => "\xEB\x97\x8D", + "\xB6\xC2" => "\xEB\x97\x8F", + "\xB6\xC3" => "\xEB\x97\x90", + "\xB6\xC4" => "\xEB\x97\x91", + "\xB6\xC5" => "\xEB\x97\x98", + "\xB6\xC6" => "\xEB\x97\xAC", + "\xB6\xC7" => "\xEB\x98\x90", + "\xB6\xC8" => "\xEB\x98\x91", + "\xB6\xC9" => "\xEB\x98\x94", + "\xB6\xCA" => "\xEB\x98\x98", + "\xB6\xCB" => "\xEB\x98\xA5", + "\xB6\xCC" => "\xEB\x98\xAC", + "\xB6\xCD" => "\xEB\x98\xB4", + "\xB6\xCE" => "\xEB\x99\x88", + "\xB6\xCF" => "\xEB\x99\xA4", + "\xB6\xD0" => "\xEB\x99\xA8", + "\xB6\xD1" => "\xEB\x9A\x9C", + "\xB6\xD2" => "\xEB\x9A\x9D", + "\xB6\xD3" => "\xEB\x9A\xA0", + "\xB6\xD4" => "\xEB\x9A\xA4", + "\xB6\xD5" => "\xEB\x9A\xAB", + "\xB6\xD6" => "\xEB\x9A\xAC", + "\xB6\xD7" => "\xEB\x9A\xB1", + "\xB6\xD8" => "\xEB\x9B\x94", + "\xB6\xD9" => "\xEB\x9B\xB0", + "\xB6\xDA" => "\xEB\x9B\xB4", + "\xB6\xDB" => "\xEB\x9B\xB8", + "\xB6\xDC" => "\xEB\x9C\x80", + "\xB6\xDD" => "\xEB\x9C\x81", + "\xB6\xDE" => "\xEB\x9C\x85", + "\xB6\xDF" => "\xEB\x9C\xA8", + "\xB6\xE0" => "\xEB\x9C\xA9", + "\xB6\xE1" => "\xEB\x9C\xAC", + "\xB6\xE2" => "\xEB\x9C\xAF", + "\xB6\xE3" => "\xEB\x9C\xB0", + "\xB6\xE4" => "\xEB\x9C\xB8", + "\xB6\xE5" => "\xEB\x9C\xB9", + "\xB6\xE6" => "\xEB\x9C\xBB", + "\xB6\xE7" => "\xEB\x9D\x84", + "\xB6\xE8" => "\xEB\x9D\x88", + "\xB6\xE9" => "\xEB\x9D\x8C", + "\xB6\xEA" => "\xEB\x9D\x94", + "\xB6\xEB" => "\xEB\x9D\x95", + "\xB6\xEC" => "\xEB\x9D\xA0", + "\xB6\xED" => "\xEB\x9D\xA4", + "\xB6\xEE" => "\xEB\x9D\xA8", + "\xB6\xEF" => "\xEB\x9D\xB0", + "\xB6\xF0" => "\xEB\x9D\xB1", + "\xB6\xF1" => "\xEB\x9D\xB3", + "\xB6\xF2" => "\xEB\x9D\xB5", + "\xB6\xF3" => "\xEB\x9D\xBC", + "\xB6\xF4" => "\xEB\x9D\xBD", + "\xB6\xF5" => "\xEB\x9E\x80", + "\xB6\xF6" => "\xEB\x9E\x84", + "\xB6\xF7" => "\xEB\x9E\x8C", + "\xB6\xF8" => "\xEB\x9E\x8D", + "\xB6\xF9" => "\xEB\x9E\x8F", + "\xB6\xFA" => "\xEB\x9E\x90", + "\xB6\xFB" => "\xEB\x9E\x91", + "\xB6\xFC" => "\xEB\x9E\x92", + "\xB6\xFD" => "\xEB\x9E\x96", + "\xB6\xFE" => "\xEB\x9E\x97", + "\xB7\x41" => "\xED\x85\xAE", + "\xB7\x42" => "\xED\x85\xAF", + "\xB7\x43" => "\xED\x85\xB0", + "\xB7\x44" => "\xED\x85\xB1", + "\xB7\x45" => "\xED\x85\xB2", + "\xB7\x46" => "\xED\x85\xB3", + "\xB7\x47" => "\xED\x85\xB4", + "\xB7\x48" => "\xED\x85\xB5", + "\xB7\x49" => "\xED\x85\xB6", + "\xB7\x4A" => "\xED\x85\xB7", + "\xB7\x4B" => "\xED\x85\xB8", + "\xB7\x4C" => "\xED\x85\xB9", + "\xB7\x4D" => "\xED\x85\xBA", + "\xB7\x4E" => "\xED\x85\xBB", + "\xB7\x4F" => "\xED\x85\xBD", + "\xB7\x50" => "\xED\x85\xBE", + "\xB7\x51" => "\xED\x85\xBF", + "\xB7\x52" => "\xED\x86\x80", + "\xB7\x53" => "\xED\x86\x81", + "\xB7\x54" => "\xED\x86\x82", + "\xB7\x55" => "\xED\x86\x83", + "\xB7\x56" => "\xED\x86\x85", + "\xB7\x57" => "\xED\x86\x86", + "\xB7\x58" => "\xED\x86\x87", + "\xB7\x59" => "\xED\x86\x89", + "\xB7\x5A" => "\xED\x86\x8A", + "\xB7\x61" => "\xED\x86\x8B", + "\xB7\x62" => "\xED\x86\x8C", + "\xB7\x63" => "\xED\x86\x8D", + "\xB7\x64" => "\xED\x86\x8E", + "\xB7\x65" => "\xED\x86\x8F", + "\xB7\x66" => "\xED\x86\x90", + "\xB7\x67" => "\xED\x86\x91", + "\xB7\x68" => "\xED\x86\x92", + "\xB7\x69" => "\xED\x86\x93", + "\xB7\x6A" => "\xED\x86\x94", + "\xB7\x6B" => "\xED\x86\x95", + "\xB7\x6C" => "\xED\x86\x96", + "\xB7\x6D" => "\xED\x86\x97", + "\xB7\x6E" => "\xED\x86\x98", + "\xB7\x6F" => "\xED\x86\x99", + "\xB7\x70" => "\xED\x86\x9A", + "\xB7\x71" => "\xED\x86\x9B", + "\xB7\x72" => "\xED\x86\x9C", + "\xB7\x73" => "\xED\x86\x9D", + "\xB7\x74" => "\xED\x86\x9E", + "\xB7\x75" => "\xED\x86\x9F", + "\xB7\x76" => "\xED\x86\xA2", + "\xB7\x77" => "\xED\x86\xA3", + "\xB7\x78" => "\xED\x86\xA5", + "\xB7\x79" => "\xED\x86\xA6", + "\xB7\x7A" => "\xED\x86\xA7", + "\xB7\x81" => "\xED\x86\xA9", + "\xB7\x82" => "\xED\x86\xAA", + "\xB7\x83" => "\xED\x86\xAB", + "\xB7\x84" => "\xED\x86\xAC", + "\xB7\x85" => "\xED\x86\xAD", + "\xB7\x86" => "\xED\x86\xAE", + "\xB7\x87" => "\xED\x86\xAF", + "\xB7\x88" => "\xED\x86\xB2", + "\xB7\x89" => "\xED\x86\xB4", + "\xB7\x8A" => "\xED\x86\xB6", + "\xB7\x8B" => "\xED\x86\xB7", + "\xB7\x8C" => "\xED\x86\xB8", + "\xB7\x8D" => "\xED\x86\xB9", + "\xB7\x8E" => "\xED\x86\xBB", + "\xB7\x8F" => "\xED\x86\xBD", + "\xB7\x90" => "\xED\x86\xBE", + "\xB7\x91" => "\xED\x86\xBF", + "\xB7\x92" => "\xED\x87\x81", + "\xB7\x93" => "\xED\x87\x82", + "\xB7\x94" => "\xED\x87\x83", + "\xB7\x95" => "\xED\x87\x84", + "\xB7\x96" => "\xED\x87\x85", + "\xB7\x97" => "\xED\x87\x86", + "\xB7\x98" => "\xED\x87\x87", + "\xB7\x99" => "\xED\x87\x88", + "\xB7\x9A" => "\xED\x87\x89", + "\xB7\x9B" => "\xED\x87\x8A", + "\xB7\x9C" => "\xED\x87\x8B", + "\xB7\x9D" => "\xED\x87\x8C", + "\xB7\x9E" => "\xED\x87\x8D", + "\xB7\x9F" => "\xED\x87\x8E", + "\xB7\xA0" => "\xED\x87\x8F", + "\xB7\xA1" => "\xEB\x9E\x98", + "\xB7\xA2" => "\xEB\x9E\x99", + "\xB7\xA3" => "\xEB\x9E\x9C", + "\xB7\xA4" => "\xEB\x9E\xA0", + "\xB7\xA5" => "\xEB\x9E\xA8", + "\xB7\xA6" => "\xEB\x9E\xA9", + "\xB7\xA7" => "\xEB\x9E\xAB", + "\xB7\xA8" => "\xEB\x9E\xAC", + "\xB7\xA9" => "\xEB\x9E\xAD", + "\xB7\xAA" => "\xEB\x9E\xB4", + "\xB7\xAB" => "\xEB\x9E\xB5", + "\xB7\xAC" => "\xEB\x9E\xB8", + "\xB7\xAD" => "\xEB\x9F\x87", + "\xB7\xAE" => "\xEB\x9F\x89", + "\xB7\xAF" => "\xEB\x9F\xAC", + "\xB7\xB0" => "\xEB\x9F\xAD", + "\xB7\xB1" => "\xEB\x9F\xB0", + "\xB7\xB2" => "\xEB\x9F\xB4", + "\xB7\xB3" => "\xEB\x9F\xBC", + "\xB7\xB4" => "\xEB\x9F\xBD", + "\xB7\xB5" => "\xEB\x9F\xBF", + "\xB7\xB6" => "\xEB\xA0\x80", + "\xB7\xB7" => "\xEB\xA0\x81", + "\xB7\xB8" => "\xEB\xA0\x87", + "\xB7\xB9" => "\xEB\xA0\x88", + "\xB7\xBA" => "\xEB\xA0\x89", + "\xB7\xBB" => "\xEB\xA0\x8C", + "\xB7\xBC" => "\xEB\xA0\x90", + "\xB7\xBD" => "\xEB\xA0\x98", + "\xB7\xBE" => "\xEB\xA0\x99", + "\xB7\xBF" => "\xEB\xA0\x9B", + "\xB7\xC0" => "\xEB\xA0\x9D", + "\xB7\xC1" => "\xEB\xA0\xA4", + "\xB7\xC2" => "\xEB\xA0\xA5", + "\xB7\xC3" => "\xEB\xA0\xA8", + "\xB7\xC4" => "\xEB\xA0\xAC", + "\xB7\xC5" => "\xEB\xA0\xB4", + "\xB7\xC6" => "\xEB\xA0\xB5", + "\xB7\xC7" => "\xEB\xA0\xB7", + "\xB7\xC8" => "\xEB\xA0\xB8", + "\xB7\xC9" => "\xEB\xA0\xB9", + "\xB7\xCA" => "\xEB\xA1\x80", + "\xB7\xCB" => "\xEB\xA1\x84", + "\xB7\xCC" => "\xEB\xA1\x91", + "\xB7\xCD" => "\xEB\xA1\x93", + "\xB7\xCE" => "\xEB\xA1\x9C", + "\xB7\xCF" => "\xEB\xA1\x9D", + "\xB7\xD0" => "\xEB\xA1\xA0", + "\xB7\xD1" => "\xEB\xA1\xA4", + "\xB7\xD2" => "\xEB\xA1\xAC", + "\xB7\xD3" => "\xEB\xA1\xAD", + "\xB7\xD4" => "\xEB\xA1\xAF", + "\xB7\xD5" => "\xEB\xA1\xB1", + "\xB7\xD6" => "\xEB\xA1\xB8", + "\xB7\xD7" => "\xEB\xA1\xBC", + "\xB7\xD8" => "\xEB\xA2\x8D", + "\xB7\xD9" => "\xEB\xA2\xA8", + "\xB7\xDA" => "\xEB\xA2\xB0", + "\xB7\xDB" => "\xEB\xA2\xB4", + "\xB7\xDC" => "\xEB\xA2\xB8", + "\xB7\xDD" => "\xEB\xA3\x80", + "\xB7\xDE" => "\xEB\xA3\x81", + "\xB7\xDF" => "\xEB\xA3\x83", + "\xB7\xE0" => "\xEB\xA3\x85", + "\xB7\xE1" => "\xEB\xA3\x8C", + "\xB7\xE2" => "\xEB\xA3\x90", + "\xB7\xE3" => "\xEB\xA3\x94", + "\xB7\xE4" => "\xEB\xA3\x9D", + "\xB7\xE5" => "\xEB\xA3\x9F", + "\xB7\xE6" => "\xEB\xA3\xA1", + "\xB7\xE7" => "\xEB\xA3\xA8", + "\xB7\xE8" => "\xEB\xA3\xA9", + "\xB7\xE9" => "\xEB\xA3\xAC", + "\xB7\xEA" => "\xEB\xA3\xB0", + "\xB7\xEB" => "\xEB\xA3\xB8", + "\xB7\xEC" => "\xEB\xA3\xB9", + "\xB7\xED" => "\xEB\xA3\xBB", + "\xB7\xEE" => "\xEB\xA3\xBD", + "\xB7\xEF" => "\xEB\xA4\x84", + "\xB7\xF0" => "\xEB\xA4\x98", + "\xB7\xF1" => "\xEB\xA4\xA0", + "\xB7\xF2" => "\xEB\xA4\xBC", + "\xB7\xF3" => "\xEB\xA4\xBD", + "\xB7\xF4" => "\xEB\xA5\x80", + "\xB7\xF5" => "\xEB\xA5\x84", + "\xB7\xF6" => "\xEB\xA5\x8C", + "\xB7\xF7" => "\xEB\xA5\x8F", + "\xB7\xF8" => "\xEB\xA5\x91", + "\xB7\xF9" => "\xEB\xA5\x98", + "\xB7\xFA" => "\xEB\xA5\x99", + "\xB7\xFB" => "\xEB\xA5\x9C", + "\xB7\xFC" => "\xEB\xA5\xA0", + "\xB7\xFD" => "\xEB\xA5\xA8", + "\xB7\xFE" => "\xEB\xA5\xA9", + "\xB8\x41" => "\xED\x87\x90", + "\xB8\x42" => "\xED\x87\x91", + "\xB8\x43" => "\xED\x87\x92", + "\xB8\x44" => "\xED\x87\x93", + "\xB8\x45" => "\xED\x87\x94", + "\xB8\x46" => "\xED\x87\x95", + "\xB8\x47" => "\xED\x87\x96", + "\xB8\x48" => "\xED\x87\x97", + "\xB8\x49" => "\xED\x87\x99", + "\xB8\x4A" => "\xED\x87\x9A", + "\xB8\x4B" => "\xED\x87\x9B", + "\xB8\x4C" => "\xED\x87\x9C", + "\xB8\x4D" => "\xED\x87\x9D", + "\xB8\x4E" => "\xED\x87\x9E", + "\xB8\x4F" => "\xED\x87\x9F", + "\xB8\x50" => "\xED\x87\xA0", + "\xB8\x51" => "\xED\x87\xA1", + "\xB8\x52" => "\xED\x87\xA2", + "\xB8\x53" => "\xED\x87\xA3", + "\xB8\x54" => "\xED\x87\xA4", + "\xB8\x55" => "\xED\x87\xA5", + "\xB8\x56" => "\xED\x87\xA6", + "\xB8\x57" => "\xED\x87\xA7", + "\xB8\x58" => "\xED\x87\xA8", + "\xB8\x59" => "\xED\x87\xA9", + "\xB8\x5A" => "\xED\x87\xAA", + "\xB8\x61" => "\xED\x87\xAB", + "\xB8\x62" => "\xED\x87\xAC", + "\xB8\x63" => "\xED\x87\xAD", + "\xB8\x64" => "\xED\x87\xAE", + "\xB8\x65" => "\xED\x87\xAF", + "\xB8\x66" => "\xED\x87\xB0", + "\xB8\x67" => "\xED\x87\xB1", + "\xB8\x68" => "\xED\x87\xB2", + "\xB8\x69" => "\xED\x87\xB3", + "\xB8\x6A" => "\xED\x87\xB5", + "\xB8\x6B" => "\xED\x87\xB6", + "\xB8\x6C" => "\xED\x87\xB7", + "\xB8\x6D" => "\xED\x87\xB9", + "\xB8\x6E" => "\xED\x87\xBA", + "\xB8\x6F" => "\xED\x87\xBB", + "\xB8\x70" => "\xED\x87\xBC", + "\xB8\x71" => "\xED\x87\xBD", + "\xB8\x72" => "\xED\x87\xBE", + "\xB8\x73" => "\xED\x87\xBF", + "\xB8\x74" => "\xED\x88\x80", + "\xB8\x75" => "\xED\x88\x81", + "\xB8\x76" => "\xED\x88\x82", + "\xB8\x77" => "\xED\x88\x83", + "\xB8\x78" => "\xED\x88\x84", + "\xB8\x79" => "\xED\x88\x85", + "\xB8\x7A" => "\xED\x88\x86", + "\xB8\x81" => "\xED\x88\x88", + "\xB8\x82" => "\xED\x88\x8A", + "\xB8\x83" => "\xED\x88\x8B", + "\xB8\x84" => "\xED\x88\x8C", + "\xB8\x85" => "\xED\x88\x8D", + "\xB8\x86" => "\xED\x88\x8E", + "\xB8\x87" => "\xED\x88\x8F", + "\xB8\x88" => "\xED\x88\x91", + "\xB8\x89" => "\xED\x88\x92", + "\xB8\x8A" => "\xED\x88\x93", + "\xB8\x8B" => "\xED\x88\x94", + "\xB8\x8C" => "\xED\x88\x95", + "\xB8\x8D" => "\xED\x88\x96", + "\xB8\x8E" => "\xED\x88\x97", + "\xB8\x8F" => "\xED\x88\x98", + "\xB8\x90" => "\xED\x88\x99", + "\xB8\x91" => "\xED\x88\x9A", + "\xB8\x92" => "\xED\x88\x9B", + "\xB8\x93" => "\xED\x88\x9C", + "\xB8\x94" => "\xED\x88\x9D", + "\xB8\x95" => "\xED\x88\x9E", + "\xB8\x96" => "\xED\x88\x9F", + "\xB8\x97" => "\xED\x88\xA0", + "\xB8\x98" => "\xED\x88\xA1", + "\xB8\x99" => "\xED\x88\xA2", + "\xB8\x9A" => "\xED\x88\xA3", + "\xB8\x9B" => "\xED\x88\xA4", + "\xB8\x9C" => "\xED\x88\xA5", + "\xB8\x9D" => "\xED\x88\xA6", + "\xB8\x9E" => "\xED\x88\xA7", + "\xB8\x9F" => "\xED\x88\xA8", + "\xB8\xA0" => "\xED\x88\xA9", + "\xB8\xA1" => "\xEB\xA5\xAB", + "\xB8\xA2" => "\xEB\xA5\xAD", + "\xB8\xA3" => "\xEB\xA5\xB4", + "\xB8\xA4" => "\xEB\xA5\xB5", + "\xB8\xA5" => "\xEB\xA5\xB8", + "\xB8\xA6" => "\xEB\xA5\xBC", + "\xB8\xA7" => "\xEB\xA6\x84", + "\xB8\xA8" => "\xEB\xA6\x85", + "\xB8\xA9" => "\xEB\xA6\x87", + "\xB8\xAA" => "\xEB\xA6\x89", + "\xB8\xAB" => "\xEB\xA6\x8A", + "\xB8\xAC" => "\xEB\xA6\x8D", + "\xB8\xAD" => "\xEB\xA6\x8E", + "\xB8\xAE" => "\xEB\xA6\xAC", + "\xB8\xAF" => "\xEB\xA6\xAD", + "\xB8\xB0" => "\xEB\xA6\xB0", + "\xB8\xB1" => "\xEB\xA6\xB4", + "\xB8\xB2" => "\xEB\xA6\xBC", + "\xB8\xB3" => "\xEB\xA6\xBD", + "\xB8\xB4" => "\xEB\xA6\xBF", + "\xB8\xB5" => "\xEB\xA7\x81", + "\xB8\xB6" => "\xEB\xA7\x88", + "\xB8\xB7" => "\xEB\xA7\x89", + "\xB8\xB8" => "\xEB\xA7\x8C", + "\xB8\xB9" => "\xEB\xA7\x8E", + "\xB8\xBA" => "\xEB\xA7\x8F", + "\xB8\xBB" => "\xEB\xA7\x90", + "\xB8\xBC" => "\xEB\xA7\x91", + "\xB8\xBD" => "\xEB\xA7\x92", + "\xB8\xBE" => "\xEB\xA7\x98", + "\xB8\xBF" => "\xEB\xA7\x99", + "\xB8\xC0" => "\xEB\xA7\x9B", + "\xB8\xC1" => "\xEB\xA7\x9D", + "\xB8\xC2" => "\xEB\xA7\x9E", + "\xB8\xC3" => "\xEB\xA7\xA1", + "\xB8\xC4" => "\xEB\xA7\xA3", + "\xB8\xC5" => "\xEB\xA7\xA4", + "\xB8\xC6" => "\xEB\xA7\xA5", + "\xB8\xC7" => "\xEB\xA7\xA8", + "\xB8\xC8" => "\xEB\xA7\xAC", + "\xB8\xC9" => "\xEB\xA7\xB4", + "\xB8\xCA" => "\xEB\xA7\xB5", + "\xB8\xCB" => "\xEB\xA7\xB7", + "\xB8\xCC" => "\xEB\xA7\xB8", + "\xB8\xCD" => "\xEB\xA7\xB9", + "\xB8\xCE" => "\xEB\xA7\xBA", + "\xB8\xCF" => "\xEB\xA8\x80", + "\xB8\xD0" => "\xEB\xA8\x81", + "\xB8\xD1" => "\xEB\xA8\x88", + "\xB8\xD2" => "\xEB\xA8\x95", + "\xB8\xD3" => "\xEB\xA8\xB8", + "\xB8\xD4" => "\xEB\xA8\xB9", + "\xB8\xD5" => "\xEB\xA8\xBC", + "\xB8\xD6" => "\xEB\xA9\x80", + "\xB8\xD7" => "\xEB\xA9\x82", + "\xB8\xD8" => "\xEB\xA9\x88", + "\xB8\xD9" => "\xEB\xA9\x89", + "\xB8\xDA" => "\xEB\xA9\x8B", + "\xB8\xDB" => "\xEB\xA9\x8D", + "\xB8\xDC" => "\xEB\xA9\x8E", + "\xB8\xDD" => "\xEB\xA9\x93", + "\xB8\xDE" => "\xEB\xA9\x94", + "\xB8\xDF" => "\xEB\xA9\x95", + "\xB8\xE0" => "\xEB\xA9\x98", + "\xB8\xE1" => "\xEB\xA9\x9C", + "\xB8\xE2" => "\xEB\xA9\xA4", + "\xB8\xE3" => "\xEB\xA9\xA5", + "\xB8\xE4" => "\xEB\xA9\xA7", + "\xB8\xE5" => "\xEB\xA9\xA8", + "\xB8\xE6" => "\xEB\xA9\xA9", + "\xB8\xE7" => "\xEB\xA9\xB0", + "\xB8\xE8" => "\xEB\xA9\xB1", + "\xB8\xE9" => "\xEB\xA9\xB4", + "\xB8\xEA" => "\xEB\xA9\xB8", + "\xB8\xEB" => "\xEB\xAA\x83", + "\xB8\xEC" => "\xEB\xAA\x84", + "\xB8\xED" => "\xEB\xAA\x85", + "\xB8\xEE" => "\xEB\xAA\x87", + "\xB8\xEF" => "\xEB\xAA\x8C", + "\xB8\xF0" => "\xEB\xAA\xA8", + "\xB8\xF1" => "\xEB\xAA\xA9", + "\xB8\xF2" => "\xEB\xAA\xAB", + "\xB8\xF3" => "\xEB\xAA\xAC", + "\xB8\xF4" => "\xEB\xAA\xB0", + "\xB8\xF5" => "\xEB\xAA\xB2", + "\xB8\xF6" => "\xEB\xAA\xB8", + "\xB8\xF7" => "\xEB\xAA\xB9", + "\xB8\xF8" => "\xEB\xAA\xBB", + "\xB8\xF9" => "\xEB\xAA\xBD", + "\xB8\xFA" => "\xEB\xAB\x84", + "\xB8\xFB" => "\xEB\xAB\x88", + "\xB8\xFC" => "\xEB\xAB\x98", + "\xB8\xFD" => "\xEB\xAB\x99", + "\xB8\xFE" => "\xEB\xAB\xBC", + "\xB9\x41" => "\xED\x88\xAA", + "\xB9\x42" => "\xED\x88\xAB", + "\xB9\x43" => "\xED\x88\xAE", + "\xB9\x44" => "\xED\x88\xAF", + "\xB9\x45" => "\xED\x88\xB1", + "\xB9\x46" => "\xED\x88\xB2", + "\xB9\x47" => "\xED\x88\xB3", + "\xB9\x48" => "\xED\x88\xB5", + "\xB9\x49" => "\xED\x88\xB6", + "\xB9\x4A" => "\xED\x88\xB7", + "\xB9\x4B" => "\xED\x88\xB8", + "\xB9\x4C" => "\xED\x88\xB9", + "\xB9\x4D" => "\xED\x88\xBA", + "\xB9\x4E" => "\xED\x88\xBB", + "\xB9\x4F" => "\xED\x88\xBE", + "\xB9\x50" => "\xED\x89\x80", + "\xB9\x51" => "\xED\x89\x82", + "\xB9\x52" => "\xED\x89\x83", + "\xB9\x53" => "\xED\x89\x84", + "\xB9\x54" => "\xED\x89\x85", + "\xB9\x55" => "\xED\x89\x86", + "\xB9\x56" => "\xED\x89\x87", + "\xB9\x57" => "\xED\x89\x89", + "\xB9\x58" => "\xED\x89\x8A", + "\xB9\x59" => "\xED\x89\x8B", + "\xB9\x5A" => "\xED\x89\x8C", + "\xB9\x61" => "\xED\x89\x8D", + "\xB9\x62" => "\xED\x89\x8E", + "\xB9\x63" => "\xED\x89\x8F", + "\xB9\x64" => "\xED\x89\x90", + "\xB9\x65" => "\xED\x89\x91", + "\xB9\x66" => "\xED\x89\x92", + "\xB9\x67" => "\xED\x89\x93", + "\xB9\x68" => "\xED\x89\x94", + "\xB9\x69" => "\xED\x89\x95", + "\xB9\x6A" => "\xED\x89\x96", + "\xB9\x6B" => "\xED\x89\x97", + "\xB9\x6C" => "\xED\x89\x98", + "\xB9\x6D" => "\xED\x89\x99", + "\xB9\x6E" => "\xED\x89\x9A", + "\xB9\x6F" => "\xED\x89\x9B", + "\xB9\x70" => "\xED\x89\x9D", + "\xB9\x71" => "\xED\x89\x9E", + "\xB9\x72" => "\xED\x89\x9F", + "\xB9\x73" => "\xED\x89\xA0", + "\xB9\x74" => "\xED\x89\xA1", + "\xB9\x75" => "\xED\x89\xA2", + "\xB9\x76" => "\xED\x89\xA3", + "\xB9\x77" => "\xED\x89\xA5", + "\xB9\x78" => "\xED\x89\xA6", + "\xB9\x79" => "\xED\x89\xA7", + "\xB9\x7A" => "\xED\x89\xA8", + "\xB9\x81" => "\xED\x89\xA9", + "\xB9\x82" => "\xED\x89\xAA", + "\xB9\x83" => "\xED\x89\xAB", + "\xB9\x84" => "\xED\x89\xAC", + "\xB9\x85" => "\xED\x89\xAD", + "\xB9\x86" => "\xED\x89\xAE", + "\xB9\x87" => "\xED\x89\xAF", + "\xB9\x88" => "\xED\x89\xB0", + "\xB9\x89" => "\xED\x89\xB1", + "\xB9\x8A" => "\xED\x89\xB2", + "\xB9\x8B" => "\xED\x89\xB3", + "\xB9\x8C" => "\xED\x89\xB4", + "\xB9\x8D" => "\xED\x89\xB5", + "\xB9\x8E" => "\xED\x89\xB6", + "\xB9\x8F" => "\xED\x89\xB7", + "\xB9\x90" => "\xED\x89\xB8", + "\xB9\x91" => "\xED\x89\xB9", + "\xB9\x92" => "\xED\x89\xBA", + "\xB9\x93" => "\xED\x89\xBB", + "\xB9\x94" => "\xED\x89\xBC", + "\xB9\x95" => "\xED\x89\xBD", + "\xB9\x96" => "\xED\x89\xBE", + "\xB9\x97" => "\xED\x89\xBF", + "\xB9\x98" => "\xED\x8A\x82", + "\xB9\x99" => "\xED\x8A\x83", + "\xB9\x9A" => "\xED\x8A\x85", + "\xB9\x9B" => "\xED\x8A\x86", + "\xB9\x9C" => "\xED\x8A\x87", + "\xB9\x9D" => "\xED\x8A\x89", + "\xB9\x9E" => "\xED\x8A\x8A", + "\xB9\x9F" => "\xED\x8A\x8B", + "\xB9\xA0" => "\xED\x8A\x8C", + "\xB9\xA1" => "\xEB\xAC\x80", + "\xB9\xA2" => "\xEB\xAC\x84", + "\xB9\xA3" => "\xEB\xAC\x8D", + "\xB9\xA4" => "\xEB\xAC\x8F", + "\xB9\xA5" => "\xEB\xAC\x91", + "\xB9\xA6" => "\xEB\xAC\x98", + "\xB9\xA7" => "\xEB\xAC\x9C", + "\xB9\xA8" => "\xEB\xAC\xA0", + "\xB9\xA9" => "\xEB\xAC\xA9", + "\xB9\xAA" => "\xEB\xAC\xAB", + "\xB9\xAB" => "\xEB\xAC\xB4", + "\xB9\xAC" => "\xEB\xAC\xB5", + "\xB9\xAD" => "\xEB\xAC\xB6", + "\xB9\xAE" => "\xEB\xAC\xB8", + "\xB9\xAF" => "\xEB\xAC\xBB", + "\xB9\xB0" => "\xEB\xAC\xBC", + "\xB9\xB1" => "\xEB\xAC\xBD", + "\xB9\xB2" => "\xEB\xAC\xBE", + "\xB9\xB3" => "\xEB\xAD\x84", + "\xB9\xB4" => "\xEB\xAD\x85", + "\xB9\xB5" => "\xEB\xAD\x87", + "\xB9\xB6" => "\xEB\xAD\x89", + "\xB9\xB7" => "\xEB\xAD\x8D", + "\xB9\xB8" => "\xEB\xAD\x8F", + "\xB9\xB9" => "\xEB\xAD\x90", + "\xB9\xBA" => "\xEB\xAD\x94", + "\xB9\xBB" => "\xEB\xAD\x98", + "\xB9\xBC" => "\xEB\xAD\xA1", + "\xB9\xBD" => "\xEB\xAD\xA3", + "\xB9\xBE" => "\xEB\xAD\xAC", + "\xB9\xBF" => "\xEB\xAE\x88", + "\xB9\xC0" => "\xEB\xAE\x8C", + "\xB9\xC1" => "\xEB\xAE\x90", + "\xB9\xC2" => "\xEB\xAE\xA4", + "\xB9\xC3" => "\xEB\xAE\xA8", + "\xB9\xC4" => "\xEB\xAE\xAC", + "\xB9\xC5" => "\xEB\xAE\xB4", + "\xB9\xC6" => "\xEB\xAE\xB7", + "\xB9\xC7" => "\xEB\xAF\x80", + "\xB9\xC8" => "\xEB\xAF\x84", + "\xB9\xC9" => "\xEB\xAF\x88", + "\xB9\xCA" => "\xEB\xAF\x90", + "\xB9\xCB" => "\xEB\xAF\x93", + "\xB9\xCC" => "\xEB\xAF\xB8", + "\xB9\xCD" => "\xEB\xAF\xB9", + "\xB9\xCE" => "\xEB\xAF\xBC", + "\xB9\xCF" => "\xEB\xAF\xBF", + "\xB9\xD0" => "\xEB\xB0\x80", + "\xB9\xD1" => "\xEB\xB0\x82", + "\xB9\xD2" => "\xEB\xB0\x88", + "\xB9\xD3" => "\xEB\xB0\x89", + "\xB9\xD4" => "\xEB\xB0\x8B", + "\xB9\xD5" => "\xEB\xB0\x8C", + "\xB9\xD6" => "\xEB\xB0\x8D", + "\xB9\xD7" => "\xEB\xB0\x8F", + "\xB9\xD8" => "\xEB\xB0\x91", + "\xB9\xD9" => "\xEB\xB0\x94", + "\xB9\xDA" => "\xEB\xB0\x95", + "\xB9\xDB" => "\xEB\xB0\x96", + "\xB9\xDC" => "\xEB\xB0\x97", + "\xB9\xDD" => "\xEB\xB0\x98", + "\xB9\xDE" => "\xEB\xB0\x9B", + "\xB9\xDF" => "\xEB\xB0\x9C", + "\xB9\xE0" => "\xEB\xB0\x9D", + "\xB9\xE1" => "\xEB\xB0\x9E", + "\xB9\xE2" => "\xEB\xB0\x9F", + "\xB9\xE3" => "\xEB\xB0\xA4", + "\xB9\xE4" => "\xEB\xB0\xA5", + "\xB9\xE5" => "\xEB\xB0\xA7", + "\xB9\xE6" => "\xEB\xB0\xA9", + "\xB9\xE7" => "\xEB\xB0\xAD", + "\xB9\xE8" => "\xEB\xB0\xB0", + "\xB9\xE9" => "\xEB\xB0\xB1", + "\xB9\xEA" => "\xEB\xB0\xB4", + "\xB9\xEB" => "\xEB\xB0\xB8", + "\xB9\xEC" => "\xEB\xB1\x80", + "\xB9\xED" => "\xEB\xB1\x81", + "\xB9\xEE" => "\xEB\xB1\x83", + "\xB9\xEF" => "\xEB\xB1\x84", + "\xB9\xF0" => "\xEB\xB1\x85", + "\xB9\xF1" => "\xEB\xB1\x89", + "\xB9\xF2" => "\xEB\xB1\x8C", + "\xB9\xF3" => "\xEB\xB1\x8D", + "\xB9\xF4" => "\xEB\xB1\x90", + "\xB9\xF5" => "\xEB\xB1\x9D", + "\xB9\xF6" => "\xEB\xB2\x84", + "\xB9\xF7" => "\xEB\xB2\x85", + "\xB9\xF8" => "\xEB\xB2\x88", + "\xB9\xF9" => "\xEB\xB2\x8B", + "\xB9\xFA" => "\xEB\xB2\x8C", + "\xB9\xFB" => "\xEB\xB2\x8E", + "\xB9\xFC" => "\xEB\xB2\x94", + "\xB9\xFD" => "\xEB\xB2\x95", + "\xB9\xFE" => "\xEB\xB2\x97", + "\xBA\x41" => "\xED\x8A\x8D", + "\xBA\x42" => "\xED\x8A\x8E", + "\xBA\x43" => "\xED\x8A\x8F", + "\xBA\x44" => "\xED\x8A\x92", + "\xBA\x45" => "\xED\x8A\x93", + "\xBA\x46" => "\xED\x8A\x94", + "\xBA\x47" => "\xED\x8A\x96", + "\xBA\x48" => "\xED\x8A\x97", + "\xBA\x49" => "\xED\x8A\x98", + "\xBA\x4A" => "\xED\x8A\x99", + "\xBA\x4B" => "\xED\x8A\x9A", + "\xBA\x4C" => "\xED\x8A\x9B", + "\xBA\x4D" => "\xED\x8A\x9D", + "\xBA\x4E" => "\xED\x8A\x9E", + "\xBA\x4F" => "\xED\x8A\x9F", + "\xBA\x50" => "\xED\x8A\xA1", + "\xBA\x51" => "\xED\x8A\xA2", + "\xBA\x52" => "\xED\x8A\xA3", + "\xBA\x53" => "\xED\x8A\xA5", + "\xBA\x54" => "\xED\x8A\xA6", + "\xBA\x55" => "\xED\x8A\xA7", + "\xBA\x56" => "\xED\x8A\xA8", + "\xBA\x57" => "\xED\x8A\xA9", + "\xBA\x58" => "\xED\x8A\xAA", + "\xBA\x59" => "\xED\x8A\xAB", + "\xBA\x5A" => "\xED\x8A\xAD", + "\xBA\x61" => "\xED\x8A\xAE", + "\xBA\x62" => "\xED\x8A\xAF", + "\xBA\x63" => "\xED\x8A\xB0", + "\xBA\x64" => "\xED\x8A\xB2", + "\xBA\x65" => "\xED\x8A\xB3", + "\xBA\x66" => "\xED\x8A\xB4", + "\xBA\x67" => "\xED\x8A\xB5", + "\xBA\x68" => "\xED\x8A\xB6", + "\xBA\x69" => "\xED\x8A\xB7", + "\xBA\x6A" => "\xED\x8A\xBA", + "\xBA\x6B" => "\xED\x8A\xBB", + "\xBA\x6C" => "\xED\x8A\xBD", + "\xBA\x6D" => "\xED\x8A\xBE", + "\xBA\x6E" => "\xED\x8B\x81", + "\xBA\x6F" => "\xED\x8B\x83", + "\xBA\x70" => "\xED\x8B\x84", + "\xBA\x71" => "\xED\x8B\x85", + "\xBA\x72" => "\xED\x8B\x86", + "\xBA\x73" => "\xED\x8B\x87", + "\xBA\x74" => "\xED\x8B\x8A", + "\xBA\x75" => "\xED\x8B\x8C", + "\xBA\x76" => "\xED\x8B\x8D", + "\xBA\x77" => "\xED\x8B\x8E", + "\xBA\x78" => "\xED\x8B\x8F", + "\xBA\x79" => "\xED\x8B\x90", + "\xBA\x7A" => "\xED\x8B\x91", + "\xBA\x81" => "\xED\x8B\x92", + "\xBA\x82" => "\xED\x8B\x93", + "\xBA\x83" => "\xED\x8B\x95", + "\xBA\x84" => "\xED\x8B\x96", + "\xBA\x85" => "\xED\x8B\x97", + "\xBA\x86" => "\xED\x8B\x99", + "\xBA\x87" => "\xED\x8B\x9A", + "\xBA\x88" => "\xED\x8B\x9B", + "\xBA\x89" => "\xED\x8B\x9D", + "\xBA\x8A" => "\xED\x8B\x9E", + "\xBA\x8B" => "\xED\x8B\x9F", + "\xBA\x8C" => "\xED\x8B\xA0", + "\xBA\x8D" => "\xED\x8B\xA1", + "\xBA\x8E" => "\xED\x8B\xA2", + "\xBA\x8F" => "\xED\x8B\xA3", + "\xBA\x90" => "\xED\x8B\xA6", + "\xBA\x91" => "\xED\x8B\xA7", + "\xBA\x92" => "\xED\x8B\xA8", + "\xBA\x93" => "\xED\x8B\xA9", + "\xBA\x94" => "\xED\x8B\xAA", + "\xBA\x95" => "\xED\x8B\xAB", + "\xBA\x96" => "\xED\x8B\xAC", + "\xBA\x97" => "\xED\x8B\xAD", + "\xBA\x98" => "\xED\x8B\xAE", + "\xBA\x99" => "\xED\x8B\xAF", + "\xBA\x9A" => "\xED\x8B\xB2", + "\xBA\x9B" => "\xED\x8B\xB3", + "\xBA\x9C" => "\xED\x8B\xB5", + "\xBA\x9D" => "\xED\x8B\xB6", + "\xBA\x9E" => "\xED\x8B\xB7", + "\xBA\x9F" => "\xED\x8B\xB9", + "\xBA\xA0" => "\xED\x8B\xBA", + "\xBA\xA1" => "\xEB\xB2\x99", + "\xBA\xA2" => "\xEB\xB2\x9A", + "\xBA\xA3" => "\xEB\xB2\xA0", + "\xBA\xA4" => "\xEB\xB2\xA1", + "\xBA\xA5" => "\xEB\xB2\xA4", + "\xBA\xA6" => "\xEB\xB2\xA7", + "\xBA\xA7" => "\xEB\xB2\xA8", + "\xBA\xA8" => "\xEB\xB2\xB0", + "\xBA\xA9" => "\xEB\xB2\xB1", + "\xBA\xAA" => "\xEB\xB2\xB3", + "\xBA\xAB" => "\xEB\xB2\xB4", + "\xBA\xAC" => "\xEB\xB2\xB5", + "\xBA\xAD" => "\xEB\xB2\xBC", + "\xBA\xAE" => "\xEB\xB2\xBD", + "\xBA\xAF" => "\xEB\xB3\x80", + "\xBA\xB0" => "\xEB\xB3\x84", + "\xBA\xB1" => "\xEB\xB3\x8D", + "\xBA\xB2" => "\xEB\xB3\x8F", + "\xBA\xB3" => "\xEB\xB3\x90", + "\xBA\xB4" => "\xEB\xB3\x91", + "\xBA\xB5" => "\xEB\xB3\x95", + "\xBA\xB6" => "\xEB\xB3\x98", + "\xBA\xB7" => "\xEB\xB3\x9C", + "\xBA\xB8" => "\xEB\xB3\xB4", + "\xBA\xB9" => "\xEB\xB3\xB5", + "\xBA\xBA" => "\xEB\xB3\xB6", + "\xBA\xBB" => "\xEB\xB3\xB8", + "\xBA\xBC" => "\xEB\xB3\xBC", + "\xBA\xBD" => "\xEB\xB4\x84", + "\xBA\xBE" => "\xEB\xB4\x85", + "\xBA\xBF" => "\xEB\xB4\x87", + "\xBA\xC0" => "\xEB\xB4\x89", + "\xBA\xC1" => "\xEB\xB4\x90", + "\xBA\xC2" => "\xEB\xB4\x94", + "\xBA\xC3" => "\xEB\xB4\xA4", + "\xBA\xC4" => "\xEB\xB4\xAC", + "\xBA\xC5" => "\xEB\xB5\x80", + "\xBA\xC6" => "\xEB\xB5\x88", + "\xBA\xC7" => "\xEB\xB5\x89", + "\xBA\xC8" => "\xEB\xB5\x8C", + "\xBA\xC9" => "\xEB\xB5\x90", + "\xBA\xCA" => "\xEB\xB5\x98", + "\xBA\xCB" => "\xEB\xB5\x99", + "\xBA\xCC" => "\xEB\xB5\xA4", + "\xBA\xCD" => "\xEB\xB5\xA8", + "\xBA\xCE" => "\xEB\xB6\x80", + "\xBA\xCF" => "\xEB\xB6\x81", + "\xBA\xD0" => "\xEB\xB6\x84", + "\xBA\xD1" => "\xEB\xB6\x87", + "\xBA\xD2" => "\xEB\xB6\x88", + "\xBA\xD3" => "\xEB\xB6\x89", + "\xBA\xD4" => "\xEB\xB6\x8A", + "\xBA\xD5" => "\xEB\xB6\x90", + "\xBA\xD6" => "\xEB\xB6\x91", + "\xBA\xD7" => "\xEB\xB6\x93", + "\xBA\xD8" => "\xEB\xB6\x95", + "\xBA\xD9" => "\xEB\xB6\x99", + "\xBA\xDA" => "\xEB\xB6\x9A", + "\xBA\xDB" => "\xEB\xB6\x9C", + "\xBA\xDC" => "\xEB\xB6\xA4", + "\xBA\xDD" => "\xEB\xB6\xB0", + "\xBA\xDE" => "\xEB\xB6\xB8", + "\xBA\xDF" => "\xEB\xB7\x94", + "\xBA\xE0" => "\xEB\xB7\x95", + "\xBA\xE1" => "\xEB\xB7\x98", + "\xBA\xE2" => "\xEB\xB7\x9C", + "\xBA\xE3" => "\xEB\xB7\xA9", + "\xBA\xE4" => "\xEB\xB7\xB0", + "\xBA\xE5" => "\xEB\xB7\xB4", + "\xBA\xE6" => "\xEB\xB7\xB8", + "\xBA\xE7" => "\xEB\xB8\x80", + "\xBA\xE8" => "\xEB\xB8\x83", + "\xBA\xE9" => "\xEB\xB8\x85", + "\xBA\xEA" => "\xEB\xB8\x8C", + "\xBA\xEB" => "\xEB\xB8\x8D", + "\xBA\xEC" => "\xEB\xB8\x90", + "\xBA\xED" => "\xEB\xB8\x94", + "\xBA\xEE" => "\xEB\xB8\x9C", + "\xBA\xEF" => "\xEB\xB8\x9D", + "\xBA\xF0" => "\xEB\xB8\x9F", + "\xBA\xF1" => "\xEB\xB9\x84", + "\xBA\xF2" => "\xEB\xB9\x85", + "\xBA\xF3" => "\xEB\xB9\x88", + "\xBA\xF4" => "\xEB\xB9\x8C", + "\xBA\xF5" => "\xEB\xB9\x8E", + "\xBA\xF6" => "\xEB\xB9\x94", + "\xBA\xF7" => "\xEB\xB9\x95", + "\xBA\xF8" => "\xEB\xB9\x97", + "\xBA\xF9" => "\xEB\xB9\x99", + "\xBA\xFA" => "\xEB\xB9\x9A", + "\xBA\xFB" => "\xEB\xB9\x9B", + "\xBA\xFC" => "\xEB\xB9\xA0", + "\xBA\xFD" => "\xEB\xB9\xA1", + "\xBA\xFE" => "\xEB\xB9\xA4", + "\xBB\x41" => "\xED\x8B\xBB", + "\xBB\x42" => "\xED\x8B\xBC", + "\xBB\x43" => "\xED\x8B\xBD", + "\xBB\x44" => "\xED\x8B\xBE", + "\xBB\x45" => "\xED\x8B\xBF", + "\xBB\x46" => "\xED\x8C\x82", + "\xBB\x47" => "\xED\x8C\x84", + "\xBB\x48" => "\xED\x8C\x86", + "\xBB\x49" => "\xED\x8C\x87", + "\xBB\x4A" => "\xED\x8C\x88", + "\xBB\x4B" => "\xED\x8C\x89", + "\xBB\x4C" => "\xED\x8C\x8A", + "\xBB\x4D" => "\xED\x8C\x8B", + "\xBB\x4E" => "\xED\x8C\x8F", + "\xBB\x4F" => "\xED\x8C\x91", + "\xBB\x50" => "\xED\x8C\x92", + "\xBB\x51" => "\xED\x8C\x93", + "\xBB\x52" => "\xED\x8C\x95", + "\xBB\x53" => "\xED\x8C\x97", + "\xBB\x54" => "\xED\x8C\x98", + "\xBB\x55" => "\xED\x8C\x99", + "\xBB\x56" => "\xED\x8C\x9A", + "\xBB\x57" => "\xED\x8C\x9B", + "\xBB\x58" => "\xED\x8C\x9E", + "\xBB\x59" => "\xED\x8C\xA2", + "\xBB\x5A" => "\xED\x8C\xA3", + "\xBB\x61" => "\xED\x8C\xA4", + "\xBB\x62" => "\xED\x8C\xA6", + "\xBB\x63" => "\xED\x8C\xA7", + "\xBB\x64" => "\xED\x8C\xAA", + "\xBB\x65" => "\xED\x8C\xAB", + "\xBB\x66" => "\xED\x8C\xAD", + "\xBB\x67" => "\xED\x8C\xAE", + "\xBB\x68" => "\xED\x8C\xAF", + "\xBB\x69" => "\xED\x8C\xB1", + "\xBB\x6A" => "\xED\x8C\xB2", + "\xBB\x6B" => "\xED\x8C\xB3", + "\xBB\x6C" => "\xED\x8C\xB4", + "\xBB\x6D" => "\xED\x8C\xB5", + "\xBB\x6E" => "\xED\x8C\xB6", + "\xBB\x6F" => "\xED\x8C\xB7", + "\xBB\x70" => "\xED\x8C\xBA", + "\xBB\x71" => "\xED\x8C\xBE", + "\xBB\x72" => "\xED\x8C\xBF", + "\xBB\x73" => "\xED\x8D\x80", + "\xBB\x74" => "\xED\x8D\x81", + "\xBB\x75" => "\xED\x8D\x82", + "\xBB\x76" => "\xED\x8D\x83", + "\xBB\x77" => "\xED\x8D\x86", + "\xBB\x78" => "\xED\x8D\x87", + "\xBB\x79" => "\xED\x8D\x88", + "\xBB\x7A" => "\xED\x8D\x89", + "\xBB\x81" => "\xED\x8D\x8A", + "\xBB\x82" => "\xED\x8D\x8B", + "\xBB\x83" => "\xED\x8D\x8C", + "\xBB\x84" => "\xED\x8D\x8D", + "\xBB\x85" => "\xED\x8D\x8E", + "\xBB\x86" => "\xED\x8D\x8F", + "\xBB\x87" => "\xED\x8D\x90", + "\xBB\x88" => "\xED\x8D\x91", + "\xBB\x89" => "\xED\x8D\x92", + "\xBB\x8A" => "\xED\x8D\x93", + "\xBB\x8B" => "\xED\x8D\x94", + "\xBB\x8C" => "\xED\x8D\x95", + "\xBB\x8D" => "\xED\x8D\x96", + "\xBB\x8E" => "\xED\x8D\x97", + "\xBB\x8F" => "\xED\x8D\x98", + "\xBB\x90" => "\xED\x8D\x99", + "\xBB\x91" => "\xED\x8D\x9A", + "\xBB\x92" => "\xED\x8D\x9B", + "\xBB\x93" => "\xED\x8D\x9C", + "\xBB\x94" => "\xED\x8D\x9D", + "\xBB\x95" => "\xED\x8D\x9E", + "\xBB\x96" => "\xED\x8D\x9F", + "\xBB\x97" => "\xED\x8D\xA0", + "\xBB\x98" => "\xED\x8D\xA1", + "\xBB\x99" => "\xED\x8D\xA2", + "\xBB\x9A" => "\xED\x8D\xA3", + "\xBB\x9B" => "\xED\x8D\xA4", + "\xBB\x9C" => "\xED\x8D\xA5", + "\xBB\x9D" => "\xED\x8D\xA6", + "\xBB\x9E" => "\xED\x8D\xA7", + "\xBB\x9F" => "\xED\x8D\xA8", + "\xBB\xA0" => "\xED\x8D\xA9", + "\xBB\xA1" => "\xEB\xB9\xA8", + "\xBB\xA2" => "\xEB\xB9\xAA", + "\xBB\xA3" => "\xEB\xB9\xB0", + "\xBB\xA4" => "\xEB\xB9\xB1", + "\xBB\xA5" => "\xEB\xB9\xB3", + "\xBB\xA6" => "\xEB\xB9\xB4", + "\xBB\xA7" => "\xEB\xB9\xB5", + "\xBB\xA8" => "\xEB\xB9\xBB", + "\xBB\xA9" => "\xEB\xB9\xBC", + "\xBB\xAA" => "\xEB\xB9\xBD", + "\xBB\xAB" => "\xEB\xBA\x80", + "\xBB\xAC" => "\xEB\xBA\x84", + "\xBB\xAD" => "\xEB\xBA\x8C", + "\xBB\xAE" => "\xEB\xBA\x8D", + "\xBB\xAF" => "\xEB\xBA\x8F", + "\xBB\xB0" => "\xEB\xBA\x90", + "\xBB\xB1" => "\xEB\xBA\x91", + "\xBB\xB2" => "\xEB\xBA\x98", + "\xBB\xB3" => "\xEB\xBA\x99", + "\xBB\xB4" => "\xEB\xBA\xA8", + "\xBB\xB5" => "\xEB\xBB\x90", + "\xBB\xB6" => "\xEB\xBB\x91", + "\xBB\xB7" => "\xEB\xBB\x94", + "\xBB\xB8" => "\xEB\xBB\x97", + "\xBB\xB9" => "\xEB\xBB\x98", + "\xBB\xBA" => "\xEB\xBB\xA0", + "\xBB\xBB" => "\xEB\xBB\xA3", + "\xBB\xBC" => "\xEB\xBB\xA4", + "\xBB\xBD" => "\xEB\xBB\xA5", + "\xBB\xBE" => "\xEB\xBB\xAC", + "\xBB\xBF" => "\xEB\xBC\x81", + "\xBB\xC0" => "\xEB\xBC\x88", + "\xBB\xC1" => "\xEB\xBC\x89", + "\xBB\xC2" => "\xEB\xBC\x98", + "\xBB\xC3" => "\xEB\xBC\x99", + "\xBB\xC4" => "\xEB\xBC\x9B", + "\xBB\xC5" => "\xEB\xBC\x9C", + "\xBB\xC6" => "\xEB\xBC\x9D", + "\xBB\xC7" => "\xEB\xBD\x80", + "\xBB\xC8" => "\xEB\xBD\x81", + "\xBB\xC9" => "\xEB\xBD\x84", + "\xBB\xCA" => "\xEB\xBD\x88", + "\xBB\xCB" => "\xEB\xBD\x90", + "\xBB\xCC" => "\xEB\xBD\x91", + "\xBB\xCD" => "\xEB\xBD\x95", + "\xBB\xCE" => "\xEB\xBE\x94", + "\xBB\xCF" => "\xEB\xBE\xB0", + "\xBB\xD0" => "\xEB\xBF\x85", + "\xBB\xD1" => "\xEB\xBF\x8C", + "\xBB\xD2" => "\xEB\xBF\x8D", + "\xBB\xD3" => "\xEB\xBF\x90", + "\xBB\xD4" => "\xEB\xBF\x94", + "\xBB\xD5" => "\xEB\xBF\x9C", + "\xBB\xD6" => "\xEB\xBF\x9F", + "\xBB\xD7" => "\xEB\xBF\xA1", + "\xBB\xD8" => "\xEC\x80\xBC", + "\xBB\xD9" => "\xEC\x81\x91", + "\xBB\xDA" => "\xEC\x81\x98", + "\xBB\xDB" => "\xEC\x81\x9C", + "\xBB\xDC" => "\xEC\x81\xA0", + "\xBB\xDD" => "\xEC\x81\xA8", + "\xBB\xDE" => "\xEC\x81\xA9", + "\xBB\xDF" => "\xEC\x82\x90", + "\xBB\xE0" => "\xEC\x82\x91", + "\xBB\xE1" => "\xEC\x82\x94", + "\xBB\xE2" => "\xEC\x82\x98", + "\xBB\xE3" => "\xEC\x82\xA0", + "\xBB\xE4" => "\xEC\x82\xA1", + "\xBB\xE5" => "\xEC\x82\xA3", + "\xBB\xE6" => "\xEC\x82\xA5", + "\xBB\xE7" => "\xEC\x82\xAC", + "\xBB\xE8" => "\xEC\x82\xAD", + "\xBB\xE9" => "\xEC\x82\xAF", + "\xBB\xEA" => "\xEC\x82\xB0", + "\xBB\xEB" => "\xEC\x82\xB3", + "\xBB\xEC" => "\xEC\x82\xB4", + "\xBB\xED" => "\xEC\x82\xB5", + "\xBB\xEE" => "\xEC\x82\xB6", + "\xBB\xEF" => "\xEC\x82\xBC", + "\xBB\xF0" => "\xEC\x82\xBD", + "\xBB\xF1" => "\xEC\x82\xBF", + "\xBB\xF2" => "\xEC\x83\x80", + "\xBB\xF3" => "\xEC\x83\x81", + "\xBB\xF4" => "\xEC\x83\x85", + "\xBB\xF5" => "\xEC\x83\x88", + "\xBB\xF6" => "\xEC\x83\x89", + "\xBB\xF7" => "\xEC\x83\x8C", + "\xBB\xF8" => "\xEC\x83\x90", + "\xBB\xF9" => "\xEC\x83\x98", + "\xBB\xFA" => "\xEC\x83\x99", + "\xBB\xFB" => "\xEC\x83\x9B", + "\xBB\xFC" => "\xEC\x83\x9C", + "\xBB\xFD" => "\xEC\x83\x9D", + "\xBB\xFE" => "\xEC\x83\xA4", + "\xBC\x41" => "\xED\x8D\xAA", + "\xBC\x42" => "\xED\x8D\xAB", + "\xBC\x43" => "\xED\x8D\xAC", + "\xBC\x44" => "\xED\x8D\xAD", + "\xBC\x45" => "\xED\x8D\xAE", + "\xBC\x46" => "\xED\x8D\xAF", + "\xBC\x47" => "\xED\x8D\xB0", + "\xBC\x48" => "\xED\x8D\xB1", + "\xBC\x49" => "\xED\x8D\xB2", + "\xBC\x4A" => "\xED\x8D\xB3", + "\xBC\x4B" => "\xED\x8D\xB4", + "\xBC\x4C" => "\xED\x8D\xB5", + "\xBC\x4D" => "\xED\x8D\xB6", + "\xBC\x4E" => "\xED\x8D\xB7", + "\xBC\x4F" => "\xED\x8D\xB8", + "\xBC\x50" => "\xED\x8D\xB9", + "\xBC\x51" => "\xED\x8D\xBA", + "\xBC\x52" => "\xED\x8D\xBB", + "\xBC\x53" => "\xED\x8D\xBE", + "\xBC\x54" => "\xED\x8D\xBF", + "\xBC\x55" => "\xED\x8E\x81", + "\xBC\x56" => "\xED\x8E\x82", + "\xBC\x57" => "\xED\x8E\x83", + "\xBC\x58" => "\xED\x8E\x85", + "\xBC\x59" => "\xED\x8E\x86", + "\xBC\x5A" => "\xED\x8E\x87", + "\xBC\x61" => "\xED\x8E\x88", + "\xBC\x62" => "\xED\x8E\x89", + "\xBC\x63" => "\xED\x8E\x8A", + "\xBC\x64" => "\xED\x8E\x8B", + "\xBC\x65" => "\xED\x8E\x8E", + "\xBC\x66" => "\xED\x8E\x92", + "\xBC\x67" => "\xED\x8E\x93", + "\xBC\x68" => "\xED\x8E\x94", + "\xBC\x69" => "\xED\x8E\x95", + "\xBC\x6A" => "\xED\x8E\x96", + "\xBC\x6B" => "\xED\x8E\x97", + "\xBC\x6C" => "\xED\x8E\x9A", + "\xBC\x6D" => "\xED\x8E\x9B", + "\xBC\x6E" => "\xED\x8E\x9D", + "\xBC\x6F" => "\xED\x8E\x9E", + "\xBC\x70" => "\xED\x8E\x9F", + "\xBC\x71" => "\xED\x8E\xA1", + "\xBC\x72" => "\xED\x8E\xA2", + "\xBC\x73" => "\xED\x8E\xA3", + "\xBC\x74" => "\xED\x8E\xA4", + "\xBC\x75" => "\xED\x8E\xA5", + "\xBC\x76" => "\xED\x8E\xA6", + "\xBC\x77" => "\xED\x8E\xA7", + "\xBC\x78" => "\xED\x8E\xAA", + "\xBC\x79" => "\xED\x8E\xAC", + "\xBC\x7A" => "\xED\x8E\xAE", + "\xBC\x81" => "\xED\x8E\xAF", + "\xBC\x82" => "\xED\x8E\xB0", + "\xBC\x83" => "\xED\x8E\xB1", + "\xBC\x84" => "\xED\x8E\xB2", + "\xBC\x85" => "\xED\x8E\xB3", + "\xBC\x86" => "\xED\x8E\xB5", + "\xBC\x87" => "\xED\x8E\xB6", + "\xBC\x88" => "\xED\x8E\xB7", + "\xBC\x89" => "\xED\x8E\xB9", + "\xBC\x8A" => "\xED\x8E\xBA", + "\xBC\x8B" => "\xED\x8E\xBB", + "\xBC\x8C" => "\xED\x8E\xBD", + "\xBC\x8D" => "\xED\x8E\xBE", + "\xBC\x8E" => "\xED\x8E\xBF", + "\xBC\x8F" => "\xED\x8F\x80", + "\xBC\x90" => "\xED\x8F\x81", + "\xBC\x91" => "\xED\x8F\x82", + "\xBC\x92" => "\xED\x8F\x83", + "\xBC\x93" => "\xED\x8F\x86", + "\xBC\x94" => "\xED\x8F\x87", + "\xBC\x95" => "\xED\x8F\x8A", + "\xBC\x96" => "\xED\x8F\x8B", + "\xBC\x97" => "\xED\x8F\x8C", + "\xBC\x98" => "\xED\x8F\x8D", + "\xBC\x99" => "\xED\x8F\x8E", + "\xBC\x9A" => "\xED\x8F\x8F", + "\xBC\x9B" => "\xED\x8F\x91", + "\xBC\x9C" => "\xED\x8F\x92", + "\xBC\x9D" => "\xED\x8F\x93", + "\xBC\x9E" => "\xED\x8F\x94", + "\xBC\x9F" => "\xED\x8F\x95", + "\xBC\xA0" => "\xED\x8F\x96", + "\xBC\xA1" => "\xEC\x83\xA5", + "\xBC\xA2" => "\xEC\x83\xA8", + "\xBC\xA3" => "\xEC\x83\xAC", + "\xBC\xA4" => "\xEC\x83\xB4", + "\xBC\xA5" => "\xEC\x83\xB5", + "\xBC\xA6" => "\xEC\x83\xB7", + "\xBC\xA7" => "\xEC\x83\xB9", + "\xBC\xA8" => "\xEC\x84\x80", + "\xBC\xA9" => "\xEC\x84\x84", + "\xBC\xAA" => "\xEC\x84\x88", + "\xBC\xAB" => "\xEC\x84\x90", + "\xBC\xAC" => "\xEC\x84\x95", + "\xBC\xAD" => "\xEC\x84\x9C", + "\xBC\xAE" => "\xEC\x84\x9D", + "\xBC\xAF" => "\xEC\x84\x9E", + "\xBC\xB0" => "\xEC\x84\x9F", + "\xBC\xB1" => "\xEC\x84\xA0", + "\xBC\xB2" => "\xEC\x84\xA3", + "\xBC\xB3" => "\xEC\x84\xA4", + "\xBC\xB4" => "\xEC\x84\xA6", + "\xBC\xB5" => "\xEC\x84\xA7", + "\xBC\xB6" => "\xEC\x84\xAC", + "\xBC\xB7" => "\xEC\x84\xAD", + "\xBC\xB8" => "\xEC\x84\xAF", + "\xBC\xB9" => "\xEC\x84\xB0", + "\xBC\xBA" => "\xEC\x84\xB1", + "\xBC\xBB" => "\xEC\x84\xB6", + "\xBC\xBC" => "\xEC\x84\xB8", + "\xBC\xBD" => "\xEC\x84\xB9", + "\xBC\xBE" => "\xEC\x84\xBC", + "\xBC\xBF" => "\xEC\x85\x80", + "\xBC\xC0" => "\xEC\x85\x88", + "\xBC\xC1" => "\xEC\x85\x89", + "\xBC\xC2" => "\xEC\x85\x8B", + "\xBC\xC3" => "\xEC\x85\x8C", + "\xBC\xC4" => "\xEC\x85\x8D", + "\xBC\xC5" => "\xEC\x85\x94", + "\xBC\xC6" => "\xEC\x85\x95", + "\xBC\xC7" => "\xEC\x85\x98", + "\xBC\xC8" => "\xEC\x85\x9C", + "\xBC\xC9" => "\xEC\x85\xA4", + "\xBC\xCA" => "\xEC\x85\xA5", + "\xBC\xCB" => "\xEC\x85\xA7", + "\xBC\xCC" => "\xEC\x85\xA8", + "\xBC\xCD" => "\xEC\x85\xA9", + "\xBC\xCE" => "\xEC\x85\xB0", + "\xBC\xCF" => "\xEC\x85\xB4", + "\xBC\xD0" => "\xEC\x85\xB8", + "\xBC\xD1" => "\xEC\x86\x85", + "\xBC\xD2" => "\xEC\x86\x8C", + "\xBC\xD3" => "\xEC\x86\x8D", + "\xBC\xD4" => "\xEC\x86\x8E", + "\xBC\xD5" => "\xEC\x86\x90", + "\xBC\xD6" => "\xEC\x86\x94", + "\xBC\xD7" => "\xEC\x86\x96", + "\xBC\xD8" => "\xEC\x86\x9C", + "\xBC\xD9" => "\xEC\x86\x9D", + "\xBC\xDA" => "\xEC\x86\x9F", + "\xBC\xDB" => "\xEC\x86\xA1", + "\xBC\xDC" => "\xEC\x86\xA5", + "\xBC\xDD" => "\xEC\x86\xA8", + "\xBC\xDE" => "\xEC\x86\xA9", + "\xBC\xDF" => "\xEC\x86\xAC", + "\xBC\xE0" => "\xEC\x86\xB0", + "\xBC\xE1" => "\xEC\x86\xBD", + "\xBC\xE2" => "\xEC\x87\x84", + "\xBC\xE3" => "\xEC\x87\x88", + "\xBC\xE4" => "\xEC\x87\x8C", + "\xBC\xE5" => "\xEC\x87\x94", + "\xBC\xE6" => "\xEC\x87\x97", + "\xBC\xE7" => "\xEC\x87\x98", + "\xBC\xE8" => "\xEC\x87\xA0", + "\xBC\xE9" => "\xEC\x87\xA4", + "\xBC\xEA" => "\xEC\x87\xA8", + "\xBC\xEB" => "\xEC\x87\xB0", + "\xBC\xEC" => "\xEC\x87\xB1", + "\xBC\xED" => "\xEC\x87\xB3", + "\xBC\xEE" => "\xEC\x87\xBC", + "\xBC\xEF" => "\xEC\x87\xBD", + "\xBC\xF0" => "\xEC\x88\x80", + "\xBC\xF1" => "\xEC\x88\x84", + "\xBC\xF2" => "\xEC\x88\x8C", + "\xBC\xF3" => "\xEC\x88\x8D", + "\xBC\xF4" => "\xEC\x88\x8F", + "\xBC\xF5" => "\xEC\x88\x91", + "\xBC\xF6" => "\xEC\x88\x98", + "\xBC\xF7" => "\xEC\x88\x99", + "\xBC\xF8" => "\xEC\x88\x9C", + "\xBC\xF9" => "\xEC\x88\x9F", + "\xBC\xFA" => "\xEC\x88\xA0", + "\xBC\xFB" => "\xEC\x88\xA8", + "\xBC\xFC" => "\xEC\x88\xA9", + "\xBC\xFD" => "\xEC\x88\xAB", + "\xBC\xFE" => "\xEC\x88\xAD", + "\xBD\x41" => "\xED\x8F\x97", + "\xBD\x42" => "\xED\x8F\x99", + "\xBD\x43" => "\xED\x8F\x9A", + "\xBD\x44" => "\xED\x8F\x9B", + "\xBD\x45" => "\xED\x8F\x9C", + "\xBD\x46" => "\xED\x8F\x9D", + "\xBD\x47" => "\xED\x8F\x9E", + "\xBD\x48" => "\xED\x8F\x9F", + "\xBD\x49" => "\xED\x8F\xA0", + "\xBD\x4A" => "\xED\x8F\xA2", + "\xBD\x4B" => "\xED\x8F\xA4", + "\xBD\x4C" => "\xED\x8F\xA5", + "\xBD\x4D" => "\xED\x8F\xA6", + "\xBD\x4E" => "\xED\x8F\xA7", + "\xBD\x4F" => "\xED\x8F\xA8", + "\xBD\x50" => "\xED\x8F\xA9", + "\xBD\x51" => "\xED\x8F\xAA", + "\xBD\x52" => "\xED\x8F\xAB", + "\xBD\x53" => "\xED\x8F\xAE", + "\xBD\x54" => "\xED\x8F\xAF", + "\xBD\x55" => "\xED\x8F\xB1", + "\xBD\x56" => "\xED\x8F\xB2", + "\xBD\x57" => "\xED\x8F\xB3", + "\xBD\x58" => "\xED\x8F\xB5", + "\xBD\x59" => "\xED\x8F\xB6", + "\xBD\x5A" => "\xED\x8F\xB7", + "\xBD\x61" => "\xED\x8F\xB8", + "\xBD\x62" => "\xED\x8F\xB9", + "\xBD\x63" => "\xED\x8F\xBA", + "\xBD\x64" => "\xED\x8F\xBB", + "\xBD\x65" => "\xED\x8F\xBE", + "\xBD\x66" => "\xED\x90\x80", + "\xBD\x67" => "\xED\x90\x82", + "\xBD\x68" => "\xED\x90\x83", + "\xBD\x69" => "\xED\x90\x84", + "\xBD\x6A" => "\xED\x90\x85", + "\xBD\x6B" => "\xED\x90\x86", + "\xBD\x6C" => "\xED\x90\x87", + "\xBD\x6D" => "\xED\x90\x89", + "\xBD\x6E" => "\xED\x90\x8A", + "\xBD\x6F" => "\xED\x90\x8B", + "\xBD\x70" => "\xED\x90\x8C", + "\xBD\x71" => "\xED\x90\x8D", + "\xBD\x72" => "\xED\x90\x8E", + "\xBD\x73" => "\xED\x90\x8F", + "\xBD\x74" => "\xED\x90\x90", + "\xBD\x75" => "\xED\x90\x91", + "\xBD\x76" => "\xED\x90\x92", + "\xBD\x77" => "\xED\x90\x93", + "\xBD\x78" => "\xED\x90\x94", + "\xBD\x79" => "\xED\x90\x95", + "\xBD\x7A" => "\xED\x90\x96", + "\xBD\x81" => "\xED\x90\x97", + "\xBD\x82" => "\xED\x90\x98", + "\xBD\x83" => "\xED\x90\x99", + "\xBD\x84" => "\xED\x90\x9A", + "\xBD\x85" => "\xED\x90\x9B", + "\xBD\x86" => "\xED\x90\x9C", + "\xBD\x87" => "\xED\x90\x9E", + "\xBD\x88" => "\xED\x90\x9F", + "\xBD\x89" => "\xED\x90\xA0", + "\xBD\x8A" => "\xED\x90\xA1", + "\xBD\x8B" => "\xED\x90\xA2", + "\xBD\x8C" => "\xED\x90\xA3", + "\xBD\x8D" => "\xED\x90\xA4", + "\xBD\x8E" => "\xED\x90\xA5", + "\xBD\x8F" => "\xED\x90\xA6", + "\xBD\x90" => "\xED\x90\xA7", + "\xBD\x91" => "\xED\x90\xA8", + "\xBD\x92" => "\xED\x90\xA9", + "\xBD\x93" => "\xED\x90\xAA", + "\xBD\x94" => "\xED\x90\xAB", + "\xBD\x95" => "\xED\x90\xAC", + "\xBD\x96" => "\xED\x90\xAD", + "\xBD\x97" => "\xED\x90\xAE", + "\xBD\x98" => "\xED\x90\xAF", + "\xBD\x99" => "\xED\x90\xB0", + "\xBD\x9A" => "\xED\x90\xB1", + "\xBD\x9B" => "\xED\x90\xB2", + "\xBD\x9C" => "\xED\x90\xB3", + "\xBD\x9D" => "\xED\x90\xB4", + "\xBD\x9E" => "\xED\x90\xB5", + "\xBD\x9F" => "\xED\x90\xB6", + "\xBD\xA0" => "\xED\x90\xB7", + "\xBD\xA1" => "\xEC\x88\xAF", + "\xBD\xA2" => "\xEC\x88\xB1", + "\xBD\xA3" => "\xEC\x88\xB2", + "\xBD\xA4" => "\xEC\x88\xB4", + "\xBD\xA5" => "\xEC\x89\x88", + "\xBD\xA6" => "\xEC\x89\x90", + "\xBD\xA7" => "\xEC\x89\x91", + "\xBD\xA8" => "\xEC\x89\x94", + "\xBD\xA9" => "\xEC\x89\x98", + "\xBD\xAA" => "\xEC\x89\xA0", + "\xBD\xAB" => "\xEC\x89\xA5", + "\xBD\xAC" => "\xEC\x89\xAC", + "\xBD\xAD" => "\xEC\x89\xAD", + "\xBD\xAE" => "\xEC\x89\xB0", + "\xBD\xAF" => "\xEC\x89\xB4", + "\xBD\xB0" => "\xEC\x89\xBC", + "\xBD\xB1" => "\xEC\x89\xBD", + "\xBD\xB2" => "\xEC\x89\xBF", + "\xBD\xB3" => "\xEC\x8A\x81", + "\xBD\xB4" => "\xEC\x8A\x88", + "\xBD\xB5" => "\xEC\x8A\x89", + "\xBD\xB6" => "\xEC\x8A\x90", + "\xBD\xB7" => "\xEC\x8A\x98", + "\xBD\xB8" => "\xEC\x8A\x9B", + "\xBD\xB9" => "\xEC\x8A\x9D", + "\xBD\xBA" => "\xEC\x8A\xA4", + "\xBD\xBB" => "\xEC\x8A\xA5", + "\xBD\xBC" => "\xEC\x8A\xA8", + "\xBD\xBD" => "\xEC\x8A\xAC", + "\xBD\xBE" => "\xEC\x8A\xAD", + "\xBD\xBF" => "\xEC\x8A\xB4", + "\xBD\xC0" => "\xEC\x8A\xB5", + "\xBD\xC1" => "\xEC\x8A\xB7", + "\xBD\xC2" => "\xEC\x8A\xB9", + "\xBD\xC3" => "\xEC\x8B\x9C", + "\xBD\xC4" => "\xEC\x8B\x9D", + "\xBD\xC5" => "\xEC\x8B\xA0", + "\xBD\xC6" => "\xEC\x8B\xA3", + "\xBD\xC7" => "\xEC\x8B\xA4", + "\xBD\xC8" => "\xEC\x8B\xAB", + "\xBD\xC9" => "\xEC\x8B\xAC", + "\xBD\xCA" => "\xEC\x8B\xAD", + "\xBD\xCB" => "\xEC\x8B\xAF", + "\xBD\xCC" => "\xEC\x8B\xB1", + "\xBD\xCD" => "\xEC\x8B\xB6", + "\xBD\xCE" => "\xEC\x8B\xB8", + "\xBD\xCF" => "\xEC\x8B\xB9", + "\xBD\xD0" => "\xEC\x8B\xBB", + "\xBD\xD1" => "\xEC\x8B\xBC", + "\xBD\xD2" => "\xEC\x8C\x80", + "\xBD\xD3" => "\xEC\x8C\x88", + "\xBD\xD4" => "\xEC\x8C\x89", + "\xBD\xD5" => "\xEC\x8C\x8C", + "\xBD\xD6" => "\xEC\x8C\x8D", + "\xBD\xD7" => "\xEC\x8C\x93", + "\xBD\xD8" => "\xEC\x8C\x94", + "\xBD\xD9" => "\xEC\x8C\x95", + "\xBD\xDA" => "\xEC\x8C\x98", + "\xBD\xDB" => "\xEC\x8C\x9C", + "\xBD\xDC" => "\xEC\x8C\xA4", + "\xBD\xDD" => "\xEC\x8C\xA5", + "\xBD\xDE" => "\xEC\x8C\xA8", + "\xBD\xDF" => "\xEC\x8C\xA9", + "\xBD\xE0" => "\xEC\x8D\x85", + "\xBD\xE1" => "\xEC\x8D\xA8", + "\xBD\xE2" => "\xEC\x8D\xA9", + "\xBD\xE3" => "\xEC\x8D\xAC", + "\xBD\xE4" => "\xEC\x8D\xB0", + "\xBD\xE5" => "\xEC\x8D\xB2", + "\xBD\xE6" => "\xEC\x8D\xB8", + "\xBD\xE7" => "\xEC\x8D\xB9", + "\xBD\xE8" => "\xEC\x8D\xBC", + "\xBD\xE9" => "\xEC\x8D\xBD", + "\xBD\xEA" => "\xEC\x8E\x84", + "\xBD\xEB" => "\xEC\x8E\x88", + "\xBD\xEC" => "\xEC\x8E\x8C", + "\xBD\xED" => "\xEC\x8F\x80", + "\xBD\xEE" => "\xEC\x8F\x98", + "\xBD\xEF" => "\xEC\x8F\x99", + "\xBD\xF0" => "\xEC\x8F\x9C", + "\xBD\xF1" => "\xEC\x8F\x9F", + "\xBD\xF2" => "\xEC\x8F\xA0", + "\xBD\xF3" => "\xEC\x8F\xA2", + "\xBD\xF4" => "\xEC\x8F\xA8", + "\xBD\xF5" => "\xEC\x8F\xA9", + "\xBD\xF6" => "\xEC\x8F\xAD", + "\xBD\xF7" => "\xEC\x8F\xB4", + "\xBD\xF8" => "\xEC\x8F\xB5", + "\xBD\xF9" => "\xEC\x8F\xB8", + "\xBD\xFA" => "\xEC\x90\x88", + "\xBD\xFB" => "\xEC\x90\x90", + "\xBD\xFC" => "\xEC\x90\xA4", + "\xBD\xFD" => "\xEC\x90\xAC", + "\xBD\xFE" => "\xEC\x90\xB0", + "\xBE\x41" => "\xED\x90\xB8", + "\xBE\x42" => "\xED\x90\xB9", + "\xBE\x43" => "\xED\x90\xBA", + "\xBE\x44" => "\xED\x90\xBB", + "\xBE\x45" => "\xED\x90\xBC", + "\xBE\x46" => "\xED\x90\xBD", + "\xBE\x47" => "\xED\x90\xBE", + "\xBE\x48" => "\xED\x90\xBF", + "\xBE\x49" => "\xED\x91\x81", + "\xBE\x4A" => "\xED\x91\x82", + "\xBE\x4B" => "\xED\x91\x83", + "\xBE\x4C" => "\xED\x91\x85", + "\xBE\x4D" => "\xED\x91\x86", + "\xBE\x4E" => "\xED\x91\x87", + "\xBE\x4F" => "\xED\x91\x88", + "\xBE\x50" => "\xED\x91\x89", + "\xBE\x51" => "\xED\x91\x8A", + "\xBE\x52" => "\xED\x91\x8B", + "\xBE\x53" => "\xED\x91\x8C", + "\xBE\x54" => "\xED\x91\x8D", + "\xBE\x55" => "\xED\x91\x8E", + "\xBE\x56" => "\xED\x91\x8F", + "\xBE\x57" => "\xED\x91\x90", + "\xBE\x58" => "\xED\x91\x91", + "\xBE\x59" => "\xED\x91\x92", + "\xBE\x5A" => "\xED\x91\x93", + "\xBE\x61" => "\xED\x91\x94", + "\xBE\x62" => "\xED\x91\x95", + "\xBE\x63" => "\xED\x91\x96", + "\xBE\x64" => "\xED\x91\x97", + "\xBE\x65" => "\xED\x91\x98", + "\xBE\x66" => "\xED\x91\x99", + "\xBE\x67" => "\xED\x91\x9A", + "\xBE\x68" => "\xED\x91\x9B", + "\xBE\x69" => "\xED\x91\x9D", + "\xBE\x6A" => "\xED\x91\x9E", + "\xBE\x6B" => "\xED\x91\x9F", + "\xBE\x6C" => "\xED\x91\xA1", + "\xBE\x6D" => "\xED\x91\xA2", + "\xBE\x6E" => "\xED\x91\xA3", + "\xBE\x6F" => "\xED\x91\xA5", + "\xBE\x70" => "\xED\x91\xA6", + "\xBE\x71" => "\xED\x91\xA7", + "\xBE\x72" => "\xED\x91\xA8", + "\xBE\x73" => "\xED\x91\xA9", + "\xBE\x74" => "\xED\x91\xAA", + "\xBE\x75" => "\xED\x91\xAB", + "\xBE\x76" => "\xED\x91\xAC", + "\xBE\x77" => "\xED\x91\xAE", + "\xBE\x78" => "\xED\x91\xB0", + "\xBE\x79" => "\xED\x91\xB1", + "\xBE\x7A" => "\xED\x91\xB2", + "\xBE\x81" => "\xED\x91\xB3", + "\xBE\x82" => "\xED\x91\xB4", + "\xBE\x83" => "\xED\x91\xB5", + "\xBE\x84" => "\xED\x91\xB6", + "\xBE\x85" => "\xED\x91\xB7", + "\xBE\x86" => "\xED\x91\xBA", + "\xBE\x87" => "\xED\x91\xBB", + "\xBE\x88" => "\xED\x91\xBD", + "\xBE\x89" => "\xED\x91\xBE", + "\xBE\x8A" => "\xED\x92\x81", + "\xBE\x8B" => "\xED\x92\x83", + "\xBE\x8C" => "\xED\x92\x84", + "\xBE\x8D" => "\xED\x92\x85", + "\xBE\x8E" => "\xED\x92\x86", + "\xBE\x8F" => "\xED\x92\x87", + "\xBE\x90" => "\xED\x92\x8A", + "\xBE\x91" => "\xED\x92\x8C", + "\xBE\x92" => "\xED\x92\x8E", + "\xBE\x93" => "\xED\x92\x8F", + "\xBE\x94" => "\xED\x92\x90", + "\xBE\x95" => "\xED\x92\x91", + "\xBE\x96" => "\xED\x92\x92", + "\xBE\x97" => "\xED\x92\x93", + "\xBE\x98" => "\xED\x92\x95", + "\xBE\x99" => "\xED\x92\x96", + "\xBE\x9A" => "\xED\x92\x97", + "\xBE\x9B" => "\xED\x92\x98", + "\xBE\x9C" => "\xED\x92\x99", + "\xBE\x9D" => "\xED\x92\x9A", + "\xBE\x9E" => "\xED\x92\x9B", + "\xBE\x9F" => "\xED\x92\x9C", + "\xBE\xA0" => "\xED\x92\x9D", + "\xBE\xA1" => "\xEC\x90\xB4", + "\xBE\xA2" => "\xEC\x90\xBC", + "\xBE\xA3" => "\xEC\x90\xBD", + "\xBE\xA4" => "\xEC\x91\x88", + "\xBE\xA5" => "\xEC\x91\xA4", + "\xBE\xA6" => "\xEC\x91\xA5", + "\xBE\xA7" => "\xEC\x91\xA8", + "\xBE\xA8" => "\xEC\x91\xAC", + "\xBE\xA9" => "\xEC\x91\xB4", + "\xBE\xAA" => "\xEC\x91\xB5", + "\xBE\xAB" => "\xEC\x91\xB9", + "\xBE\xAC" => "\xEC\x92\x80", + "\xBE\xAD" => "\xEC\x92\x94", + "\xBE\xAE" => "\xEC\x92\x9C", + "\xBE\xAF" => "\xEC\x92\xB8", + "\xBE\xB0" => "\xEC\x92\xBC", + "\xBE\xB1" => "\xEC\x93\xA9", + "\xBE\xB2" => "\xEC\x93\xB0", + "\xBE\xB3" => "\xEC\x93\xB1", + "\xBE\xB4" => "\xEC\x93\xB4", + "\xBE\xB5" => "\xEC\x93\xB8", + "\xBE\xB6" => "\xEC\x93\xBA", + "\xBE\xB7" => "\xEC\x93\xBF", + "\xBE\xB8" => "\xEC\x94\x80", + "\xBE\xB9" => "\xEC\x94\x81", + "\xBE\xBA" => "\xEC\x94\x8C", + "\xBE\xBB" => "\xEC\x94\x90", + "\xBE\xBC" => "\xEC\x94\x94", + "\xBE\xBD" => "\xEC\x94\x9C", + "\xBE\xBE" => "\xEC\x94\xA8", + "\xBE\xBF" => "\xEC\x94\xA9", + "\xBE\xC0" => "\xEC\x94\xAC", + "\xBE\xC1" => "\xEC\x94\xB0", + "\xBE\xC2" => "\xEC\x94\xB8", + "\xBE\xC3" => "\xEC\x94\xB9", + "\xBE\xC4" => "\xEC\x94\xBB", + "\xBE\xC5" => "\xEC\x94\xBD", + "\xBE\xC6" => "\xEC\x95\x84", + "\xBE\xC7" => "\xEC\x95\x85", + "\xBE\xC8" => "\xEC\x95\x88", + "\xBE\xC9" => "\xEC\x95\x89", + "\xBE\xCA" => "\xEC\x95\x8A", + "\xBE\xCB" => "\xEC\x95\x8C", + "\xBE\xCC" => "\xEC\x95\x8D", + "\xBE\xCD" => "\xEC\x95\x8E", + "\xBE\xCE" => "\xEC\x95\x93", + "\xBE\xCF" => "\xEC\x95\x94", + "\xBE\xD0" => "\xEC\x95\x95", + "\xBE\xD1" => "\xEC\x95\x97", + "\xBE\xD2" => "\xEC\x95\x98", + "\xBE\xD3" => "\xEC\x95\x99", + "\xBE\xD4" => "\xEC\x95\x9D", + "\xBE\xD5" => "\xEC\x95\x9E", + "\xBE\xD6" => "\xEC\x95\xA0", + "\xBE\xD7" => "\xEC\x95\xA1", + "\xBE\xD8" => "\xEC\x95\xA4", + "\xBE\xD9" => "\xEC\x95\xA8", + "\xBE\xDA" => "\xEC\x95\xB0", + "\xBE\xDB" => "\xEC\x95\xB1", + "\xBE\xDC" => "\xEC\x95\xB3", + "\xBE\xDD" => "\xEC\x95\xB4", + "\xBE\xDE" => "\xEC\x95\xB5", + "\xBE\xDF" => "\xEC\x95\xBC", + "\xBE\xE0" => "\xEC\x95\xBD", + "\xBE\xE1" => "\xEC\x96\x80", + "\xBE\xE2" => "\xEC\x96\x84", + "\xBE\xE3" => "\xEC\x96\x87", + "\xBE\xE4" => "\xEC\x96\x8C", + "\xBE\xE5" => "\xEC\x96\x8D", + "\xBE\xE6" => "\xEC\x96\x8F", + "\xBE\xE7" => "\xEC\x96\x91", + "\xBE\xE8" => "\xEC\x96\x95", + "\xBE\xE9" => "\xEC\x96\x97", + "\xBE\xEA" => "\xEC\x96\x98", + "\xBE\xEB" => "\xEC\x96\x9C", + "\xBE\xEC" => "\xEC\x96\xA0", + "\xBE\xED" => "\xEC\x96\xA9", + "\xBE\xEE" => "\xEC\x96\xB4", + "\xBE\xEF" => "\xEC\x96\xB5", + "\xBE\xF0" => "\xEC\x96\xB8", + "\xBE\xF1" => "\xEC\x96\xB9", + "\xBE\xF2" => "\xEC\x96\xBB", + "\xBE\xF3" => "\xEC\x96\xBC", + "\xBE\xF4" => "\xEC\x96\xBD", + "\xBE\xF5" => "\xEC\x96\xBE", + "\xBE\xF6" => "\xEC\x97\x84", + "\xBE\xF7" => "\xEC\x97\x85", + "\xBE\xF8" => "\xEC\x97\x86", + "\xBE\xF9" => "\xEC\x97\x87", + "\xBE\xFA" => "\xEC\x97\x88", + "\xBE\xFB" => "\xEC\x97\x89", + "\xBE\xFC" => "\xEC\x97\x8A", + "\xBE\xFD" => "\xEC\x97\x8C", + "\xBE\xFE" => "\xEC\x97\x8E", + "\xBF\x41" => "\xED\x92\x9E", + "\xBF\x42" => "\xED\x92\x9F", + "\xBF\x43" => "\xED\x92\xA0", + "\xBF\x44" => "\xED\x92\xA1", + "\xBF\x45" => "\xED\x92\xA2", + "\xBF\x46" => "\xED\x92\xA3", + "\xBF\x47" => "\xED\x92\xA4", + "\xBF\x48" => "\xED\x92\xA5", + "\xBF\x49" => "\xED\x92\xA6", + "\xBF\x4A" => "\xED\x92\xA7", + "\xBF\x4B" => "\xED\x92\xA8", + "\xBF\x4C" => "\xED\x92\xAA", + "\xBF\x4D" => "\xED\x92\xAB", + "\xBF\x4E" => "\xED\x92\xAC", + "\xBF\x4F" => "\xED\x92\xAD", + "\xBF\x50" => "\xED\x92\xAE", + "\xBF\x51" => "\xED\x92\xAF", + "\xBF\x52" => "\xED\x92\xB0", + "\xBF\x53" => "\xED\x92\xB1", + "\xBF\x54" => "\xED\x92\xB2", + "\xBF\x55" => "\xED\x92\xB3", + "\xBF\x56" => "\xED\x92\xB4", + "\xBF\x57" => "\xED\x92\xB5", + "\xBF\x58" => "\xED\x92\xB6", + "\xBF\x59" => "\xED\x92\xB7", + "\xBF\x5A" => "\xED\x92\xB8", + "\xBF\x61" => "\xED\x92\xB9", + "\xBF\x62" => "\xED\x92\xBA", + "\xBF\x63" => "\xED\x92\xBB", + "\xBF\x64" => "\xED\x92\xBC", + "\xBF\x65" => "\xED\x92\xBD", + "\xBF\x66" => "\xED\x92\xBE", + "\xBF\x67" => "\xED\x92\xBF", + "\xBF\x68" => "\xED\x93\x80", + "\xBF\x69" => "\xED\x93\x81", + "\xBF\x6A" => "\xED\x93\x82", + "\xBF\x6B" => "\xED\x93\x83", + "\xBF\x6C" => "\xED\x93\x84", + "\xBF\x6D" => "\xED\x93\x85", + "\xBF\x6E" => "\xED\x93\x86", + "\xBF\x6F" => "\xED\x93\x87", + "\xBF\x70" => "\xED\x93\x88", + "\xBF\x71" => "\xED\x93\x89", + "\xBF\x72" => "\xED\x93\x8A", + "\xBF\x73" => "\xED\x93\x8B", + "\xBF\x74" => "\xED\x93\x8D", + "\xBF\x75" => "\xED\x93\x8E", + "\xBF\x76" => "\xED\x93\x8F", + "\xBF\x77" => "\xED\x93\x91", + "\xBF\x78" => "\xED\x93\x92", + "\xBF\x79" => "\xED\x93\x93", + "\xBF\x7A" => "\xED\x93\x95", + "\xBF\x81" => "\xED\x93\x96", + "\xBF\x82" => "\xED\x93\x97", + "\xBF\x83" => "\xED\x93\x98", + "\xBF\x84" => "\xED\x93\x99", + "\xBF\x85" => "\xED\x93\x9A", + "\xBF\x86" => "\xED\x93\x9B", + "\xBF\x87" => "\xED\x93\x9D", + "\xBF\x88" => "\xED\x93\x9E", + "\xBF\x89" => "\xED\x93\xA0", + "\xBF\x8A" => "\xED\x93\xA1", + "\xBF\x8B" => "\xED\x93\xA2", + "\xBF\x8C" => "\xED\x93\xA3", + "\xBF\x8D" => "\xED\x93\xA4", + "\xBF\x8E" => "\xED\x93\xA5", + "\xBF\x8F" => "\xED\x93\xA6", + "\xBF\x90" => "\xED\x93\xA7", + "\xBF\x91" => "\xED\x93\xA9", + "\xBF\x92" => "\xED\x93\xAA", + "\xBF\x93" => "\xED\x93\xAB", + "\xBF\x94" => "\xED\x93\xAD", + "\xBF\x95" => "\xED\x93\xAE", + "\xBF\x96" => "\xED\x93\xAF", + "\xBF\x97" => "\xED\x93\xB1", + "\xBF\x98" => "\xED\x93\xB2", + "\xBF\x99" => "\xED\x93\xB3", + "\xBF\x9A" => "\xED\x93\xB4", + "\xBF\x9B" => "\xED\x93\xB5", + "\xBF\x9C" => "\xED\x93\xB6", + "\xBF\x9D" => "\xED\x93\xB7", + "\xBF\x9E" => "\xED\x93\xB9", + "\xBF\x9F" => "\xED\x93\xBA", + "\xBF\xA0" => "\xED\x93\xBC", + "\xBF\xA1" => "\xEC\x97\x90", + "\xBF\xA2" => "\xEC\x97\x91", + "\xBF\xA3" => "\xEC\x97\x94", + "\xBF\xA4" => "\xEC\x97\x98", + "\xBF\xA5" => "\xEC\x97\xA0", + "\xBF\xA6" => "\xEC\x97\xA1", + "\xBF\xA7" => "\xEC\x97\xA3", + "\xBF\xA8" => "\xEC\x97\xA5", + "\xBF\xA9" => "\xEC\x97\xAC", + "\xBF\xAA" => "\xEC\x97\xAD", + "\xBF\xAB" => "\xEC\x97\xAE", + "\xBF\xAC" => "\xEC\x97\xB0", + "\xBF\xAD" => "\xEC\x97\xB4", + "\xBF\xAE" => "\xEC\x97\xB6", + "\xBF\xAF" => "\xEC\x97\xB7", + "\xBF\xB0" => "\xEC\x97\xBC", + "\xBF\xB1" => "\xEC\x97\xBD", + "\xBF\xB2" => "\xEC\x97\xBE", + "\xBF\xB3" => "\xEC\x97\xBF", + "\xBF\xB4" => "\xEC\x98\x80", + "\xBF\xB5" => "\xEC\x98\x81", + "\xBF\xB6" => "\xEC\x98\x85", + "\xBF\xB7" => "\xEC\x98\x86", + "\xBF\xB8" => "\xEC\x98\x87", + "\xBF\xB9" => "\xEC\x98\x88", + "\xBF\xBA" => "\xEC\x98\x8C", + "\xBF\xBB" => "\xEC\x98\x90", + "\xBF\xBC" => "\xEC\x98\x98", + "\xBF\xBD" => "\xEC\x98\x99", + "\xBF\xBE" => "\xEC\x98\x9B", + "\xBF\xBF" => "\xEC\x98\x9C", + "\xBF\xC0" => "\xEC\x98\xA4", + "\xBF\xC1" => "\xEC\x98\xA5", + "\xBF\xC2" => "\xEC\x98\xA8", + "\xBF\xC3" => "\xEC\x98\xAC", + "\xBF\xC4" => "\xEC\x98\xAD", + "\xBF\xC5" => "\xEC\x98\xAE", + "\xBF\xC6" => "\xEC\x98\xB0", + "\xBF\xC7" => "\xEC\x98\xB3", + "\xBF\xC8" => "\xEC\x98\xB4", + "\xBF\xC9" => "\xEC\x98\xB5", + "\xBF\xCA" => "\xEC\x98\xB7", + "\xBF\xCB" => "\xEC\x98\xB9", + "\xBF\xCC" => "\xEC\x98\xBB", + "\xBF\xCD" => "\xEC\x99\x80", + "\xBF\xCE" => "\xEC\x99\x81", + "\xBF\xCF" => "\xEC\x99\x84", + "\xBF\xD0" => "\xEC\x99\x88", + "\xBF\xD1" => "\xEC\x99\x90", + "\xBF\xD2" => "\xEC\x99\x91", + "\xBF\xD3" => "\xEC\x99\x93", + "\xBF\xD4" => "\xEC\x99\x94", + "\xBF\xD5" => "\xEC\x99\x95", + "\xBF\xD6" => "\xEC\x99\x9C", + "\xBF\xD7" => "\xEC\x99\x9D", + "\xBF\xD8" => "\xEC\x99\xA0", + "\xBF\xD9" => "\xEC\x99\xAC", + "\xBF\xDA" => "\xEC\x99\xAF", + "\xBF\xDB" => "\xEC\x99\xB1", + "\xBF\xDC" => "\xEC\x99\xB8", + "\xBF\xDD" => "\xEC\x99\xB9", + "\xBF\xDE" => "\xEC\x99\xBC", + "\xBF\xDF" => "\xEC\x9A\x80", + "\xBF\xE0" => "\xEC\x9A\x88", + "\xBF\xE1" => "\xEC\x9A\x89", + "\xBF\xE2" => "\xEC\x9A\x8B", + "\xBF\xE3" => "\xEC\x9A\x8D", + "\xBF\xE4" => "\xEC\x9A\x94", + "\xBF\xE5" => "\xEC\x9A\x95", + "\xBF\xE6" => "\xEC\x9A\x98", + "\xBF\xE7" => "\xEC\x9A\x9C", + "\xBF\xE8" => "\xEC\x9A\xA4", + "\xBF\xE9" => "\xEC\x9A\xA5", + "\xBF\xEA" => "\xEC\x9A\xA7", + "\xBF\xEB" => "\xEC\x9A\xA9", + "\xBF\xEC" => "\xEC\x9A\xB0", + "\xBF\xED" => "\xEC\x9A\xB1", + "\xBF\xEE" => "\xEC\x9A\xB4", + "\xBF\xEF" => "\xEC\x9A\xB8", + "\xBF\xF0" => "\xEC\x9A\xB9", + "\xBF\xF1" => "\xEC\x9A\xBA", + "\xBF\xF2" => "\xEC\x9B\x80", + "\xBF\xF3" => "\xEC\x9B\x81", + "\xBF\xF4" => "\xEC\x9B\x83", + "\xBF\xF5" => "\xEC\x9B\x85", + "\xBF\xF6" => "\xEC\x9B\x8C", + "\xBF\xF7" => "\xEC\x9B\x8D", + "\xBF\xF8" => "\xEC\x9B\x90", + "\xBF\xF9" => "\xEC\x9B\x94", + "\xBF\xFA" => "\xEC\x9B\x9C", + "\xBF\xFB" => "\xEC\x9B\x9D", + "\xBF\xFC" => "\xEC\x9B\xA0", + "\xBF\xFD" => "\xEC\x9B\xA1", + "\xBF\xFE" => "\xEC\x9B\xA8", + "\xC0\x41" => "\xED\x93\xBE", + "\xC0\x42" => "\xED\x93\xBF", + "\xC0\x43" => "\xED\x94\x80", + "\xC0\x44" => "\xED\x94\x81", + "\xC0\x45" => "\xED\x94\x82", + "\xC0\x46" => "\xED\x94\x83", + "\xC0\x47" => "\xED\x94\x85", + "\xC0\x48" => "\xED\x94\x86", + "\xC0\x49" => "\xED\x94\x87", + "\xC0\x4A" => "\xED\x94\x89", + "\xC0\x4B" => "\xED\x94\x8A", + "\xC0\x4C" => "\xED\x94\x8B", + "\xC0\x4D" => "\xED\x94\x8D", + "\xC0\x4E" => "\xED\x94\x8E", + "\xC0\x4F" => "\xED\x94\x8F", + "\xC0\x50" => "\xED\x94\x90", + "\xC0\x51" => "\xED\x94\x91", + "\xC0\x52" => "\xED\x94\x92", + "\xC0\x53" => "\xED\x94\x93", + "\xC0\x54" => "\xED\x94\x96", + "\xC0\x55" => "\xED\x94\x98", + "\xC0\x56" => "\xED\x94\x99", + "\xC0\x57" => "\xED\x94\x9A", + "\xC0\x58" => "\xED\x94\x9B", + "\xC0\x59" => "\xED\x94\x9C", + "\xC0\x5A" => "\xED\x94\x9D", + "\xC0\x61" => "\xED\x94\x9E", + "\xC0\x62" => "\xED\x94\x9F", + "\xC0\x63" => "\xED\x94\xA0", + "\xC0\x64" => "\xED\x94\xA1", + "\xC0\x65" => "\xED\x94\xA2", + "\xC0\x66" => "\xED\x94\xA3", + "\xC0\x67" => "\xED\x94\xA4", + "\xC0\x68" => "\xED\x94\xA5", + "\xC0\x69" => "\xED\x94\xA6", + "\xC0\x6A" => "\xED\x94\xA7", + "\xC0\x6B" => "\xED\x94\xA8", + "\xC0\x6C" => "\xED\x94\xA9", + "\xC0\x6D" => "\xED\x94\xAA", + "\xC0\x6E" => "\xED\x94\xAB", + "\xC0\x6F" => "\xED\x94\xAC", + "\xC0\x70" => "\xED\x94\xAD", + "\xC0\x71" => "\xED\x94\xAE", + "\xC0\x72" => "\xED\x94\xAF", + "\xC0\x73" => "\xED\x94\xB0", + "\xC0\x74" => "\xED\x94\xB1", + "\xC0\x75" => "\xED\x94\xB2", + "\xC0\x76" => "\xED\x94\xB3", + "\xC0\x77" => "\xED\x94\xB4", + "\xC0\x78" => "\xED\x94\xB5", + "\xC0\x79" => "\xED\x94\xB6", + "\xC0\x7A" => "\xED\x94\xB7", + "\xC0\x81" => "\xED\x94\xB8", + "\xC0\x82" => "\xED\x94\xB9", + "\xC0\x83" => "\xED\x94\xBA", + "\xC0\x84" => "\xED\x94\xBB", + "\xC0\x85" => "\xED\x94\xBE", + "\xC0\x86" => "\xED\x94\xBF", + "\xC0\x87" => "\xED\x95\x81", + "\xC0\x88" => "\xED\x95\x82", + "\xC0\x89" => "\xED\x95\x83", + "\xC0\x8A" => "\xED\x95\x85", + "\xC0\x8B" => "\xED\x95\x86", + "\xC0\x8C" => "\xED\x95\x87", + "\xC0\x8D" => "\xED\x95\x88", + "\xC0\x8E" => "\xED\x95\x89", + "\xC0\x8F" => "\xED\x95\x8A", + "\xC0\x90" => "\xED\x95\x8B", + "\xC0\x91" => "\xED\x95\x8E", + "\xC0\x92" => "\xED\x95\x90", + "\xC0\x93" => "\xED\x95\x92", + "\xC0\x94" => "\xED\x95\x93", + "\xC0\x95" => "\xED\x95\x94", + "\xC0\x96" => "\xED\x95\x95", + "\xC0\x97" => "\xED\x95\x96", + "\xC0\x98" => "\xED\x95\x97", + "\xC0\x99" => "\xED\x95\x9A", + "\xC0\x9A" => "\xED\x95\x9B", + "\xC0\x9B" => "\xED\x95\x9D", + "\xC0\x9C" => "\xED\x95\x9E", + "\xC0\x9D" => "\xED\x95\x9F", + "\xC0\x9E" => "\xED\x95\xA1", + "\xC0\x9F" => "\xED\x95\xA2", + "\xC0\xA0" => "\xED\x95\xA3", + "\xC0\xA1" => "\xEC\x9B\xA9", + "\xC0\xA2" => "\xEC\x9B\xAC", + "\xC0\xA3" => "\xEC\x9B\xB0", + "\xC0\xA4" => "\xEC\x9B\xB8", + "\xC0\xA5" => "\xEC\x9B\xB9", + "\xC0\xA6" => "\xEC\x9B\xBD", + "\xC0\xA7" => "\xEC\x9C\x84", + "\xC0\xA8" => "\xEC\x9C\x85", + "\xC0\xA9" => "\xEC\x9C\x88", + "\xC0\xAA" => "\xEC\x9C\x8C", + "\xC0\xAB" => "\xEC\x9C\x94", + "\xC0\xAC" => "\xEC\x9C\x95", + "\xC0\xAD" => "\xEC\x9C\x97", + "\xC0\xAE" => "\xEC\x9C\x99", + "\xC0\xAF" => "\xEC\x9C\xA0", + "\xC0\xB0" => "\xEC\x9C\xA1", + "\xC0\xB1" => "\xEC\x9C\xA4", + "\xC0\xB2" => "\xEC\x9C\xA8", + "\xC0\xB3" => "\xEC\x9C\xB0", + "\xC0\xB4" => "\xEC\x9C\xB1", + "\xC0\xB5" => "\xEC\x9C\xB3", + "\xC0\xB6" => "\xEC\x9C\xB5", + "\xC0\xB7" => "\xEC\x9C\xB7", + "\xC0\xB8" => "\xEC\x9C\xBC", + "\xC0\xB9" => "\xEC\x9C\xBD", + "\xC0\xBA" => "\xEC\x9D\x80", + "\xC0\xBB" => "\xEC\x9D\x84", + "\xC0\xBC" => "\xEC\x9D\x8A", + "\xC0\xBD" => "\xEC\x9D\x8C", + "\xC0\xBE" => "\xEC\x9D\x8D", + "\xC0\xBF" => "\xEC\x9D\x8F", + "\xC0\xC0" => "\xEC\x9D\x91", + "\xC0\xC1" => "\xEC\x9D\x92", + "\xC0\xC2" => "\xEC\x9D\x93", + "\xC0\xC3" => "\xEC\x9D\x94", + "\xC0\xC4" => "\xEC\x9D\x95", + "\xC0\xC5" => "\xEC\x9D\x96", + "\xC0\xC6" => "\xEC\x9D\x97", + "\xC0\xC7" => "\xEC\x9D\x98", + "\xC0\xC8" => "\xEC\x9D\x9C", + "\xC0\xC9" => "\xEC\x9D\xA0", + "\xC0\xCA" => "\xEC\x9D\xA8", + "\xC0\xCB" => "\xEC\x9D\xAB", + "\xC0\xCC" => "\xEC\x9D\xB4", + "\xC0\xCD" => "\xEC\x9D\xB5", + "\xC0\xCE" => "\xEC\x9D\xB8", + "\xC0\xCF" => "\xEC\x9D\xBC", + "\xC0\xD0" => "\xEC\x9D\xBD", + "\xC0\xD1" => "\xEC\x9D\xBE", + "\xC0\xD2" => "\xEC\x9E\x83", + "\xC0\xD3" => "\xEC\x9E\x84", + "\xC0\xD4" => "\xEC\x9E\x85", + "\xC0\xD5" => "\xEC\x9E\x87", + "\xC0\xD6" => "\xEC\x9E\x88", + "\xC0\xD7" => "\xEC\x9E\x89", + "\xC0\xD8" => "\xEC\x9E\x8A", + "\xC0\xD9" => "\xEC\x9E\x8E", + "\xC0\xDA" => "\xEC\x9E\x90", + "\xC0\xDB" => "\xEC\x9E\x91", + "\xC0\xDC" => "\xEC\x9E\x94", + "\xC0\xDD" => "\xEC\x9E\x96", + "\xC0\xDE" => "\xEC\x9E\x97", + "\xC0\xDF" => "\xEC\x9E\x98", + "\xC0\xE0" => "\xEC\x9E\x9A", + "\xC0\xE1" => "\xEC\x9E\xA0", + "\xC0\xE2" => "\xEC\x9E\xA1", + "\xC0\xE3" => "\xEC\x9E\xA3", + "\xC0\xE4" => "\xEC\x9E\xA4", + "\xC0\xE5" => "\xEC\x9E\xA5", + "\xC0\xE6" => "\xEC\x9E\xA6", + "\xC0\xE7" => "\xEC\x9E\xAC", + "\xC0\xE8" => "\xEC\x9E\xAD", + "\xC0\xE9" => "\xEC\x9E\xB0", + "\xC0\xEA" => "\xEC\x9E\xB4", + "\xC0\xEB" => "\xEC\x9E\xBC", + "\xC0\xEC" => "\xEC\x9E\xBD", + "\xC0\xED" => "\xEC\x9E\xBF", + "\xC0\xEE" => "\xEC\x9F\x80", + "\xC0\xEF" => "\xEC\x9F\x81", + "\xC0\xF0" => "\xEC\x9F\x88", + "\xC0\xF1" => "\xEC\x9F\x89", + "\xC0\xF2" => "\xEC\x9F\x8C", + "\xC0\xF3" => "\xEC\x9F\x8E", + "\xC0\xF4" => "\xEC\x9F\x90", + "\xC0\xF5" => "\xEC\x9F\x98", + "\xC0\xF6" => "\xEC\x9F\x9D", + "\xC0\xF7" => "\xEC\x9F\xA4", + "\xC0\xF8" => "\xEC\x9F\xA8", + "\xC0\xF9" => "\xEC\x9F\xAC", + "\xC0\xFA" => "\xEC\xA0\x80", + "\xC0\xFB" => "\xEC\xA0\x81", + "\xC0\xFC" => "\xEC\xA0\x84", + "\xC0\xFD" => "\xEC\xA0\x88", + "\xC0\xFE" => "\xEC\xA0\x8A", + "\xC1\x41" => "\xED\x95\xA4", + "\xC1\x42" => "\xED\x95\xA6", + "\xC1\x43" => "\xED\x95\xA7", + "\xC1\x44" => "\xED\x95\xAA", + "\xC1\x45" => "\xED\x95\xAC", + "\xC1\x46" => "\xED\x95\xAE", + "\xC1\x47" => "\xED\x95\xAF", + "\xC1\x48" => "\xED\x95\xB0", + "\xC1\x49" => "\xED\x95\xB1", + "\xC1\x4A" => "\xED\x95\xB2", + "\xC1\x4B" => "\xED\x95\xB3", + "\xC1\x4C" => "\xED\x95\xB6", + "\xC1\x4D" => "\xED\x95\xB7", + "\xC1\x4E" => "\xED\x95\xB9", + "\xC1\x4F" => "\xED\x95\xBA", + "\xC1\x50" => "\xED\x95\xBB", + "\xC1\x51" => "\xED\x95\xBD", + "\xC1\x52" => "\xED\x95\xBE", + "\xC1\x53" => "\xED\x95\xBF", + "\xC1\x54" => "\xED\x96\x80", + "\xC1\x55" => "\xED\x96\x81", + "\xC1\x56" => "\xED\x96\x82", + "\xC1\x57" => "\xED\x96\x83", + "\xC1\x58" => "\xED\x96\x86", + "\xC1\x59" => "\xED\x96\x8A", + "\xC1\x5A" => "\xED\x96\x8B", + "\xC1\x61" => "\xED\x96\x8C", + "\xC1\x62" => "\xED\x96\x8D", + "\xC1\x63" => "\xED\x96\x8E", + "\xC1\x64" => "\xED\x96\x8F", + "\xC1\x65" => "\xED\x96\x91", + "\xC1\x66" => "\xED\x96\x92", + "\xC1\x67" => "\xED\x96\x93", + "\xC1\x68" => "\xED\x96\x94", + "\xC1\x69" => "\xED\x96\x95", + "\xC1\x6A" => "\xED\x96\x96", + "\xC1\x6B" => "\xED\x96\x97", + "\xC1\x6C" => "\xED\x96\x98", + "\xC1\x6D" => "\xED\x96\x99", + "\xC1\x6E" => "\xED\x96\x9A", + "\xC1\x6F" => "\xED\x96\x9B", + "\xC1\x70" => "\xED\x96\x9C", + "\xC1\x71" => "\xED\x96\x9D", + "\xC1\x72" => "\xED\x96\x9E", + "\xC1\x73" => "\xED\x96\x9F", + "\xC1\x74" => "\xED\x96\xA0", + "\xC1\x75" => "\xED\x96\xA1", + "\xC1\x76" => "\xED\x96\xA2", + "\xC1\x77" => "\xED\x96\xA3", + "\xC1\x78" => "\xED\x96\xA4", + "\xC1\x79" => "\xED\x96\xA6", + "\xC1\x7A" => "\xED\x96\xA7", + "\xC1\x81" => "\xED\x96\xA8", + "\xC1\x82" => "\xED\x96\xA9", + "\xC1\x83" => "\xED\x96\xAA", + "\xC1\x84" => "\xED\x96\xAB", + "\xC1\x85" => "\xED\x96\xAC", + "\xC1\x86" => "\xED\x96\xAD", + "\xC1\x87" => "\xED\x96\xAE", + "\xC1\x88" => "\xED\x96\xAF", + "\xC1\x89" => "\xED\x96\xB0", + "\xC1\x8A" => "\xED\x96\xB1", + "\xC1\x8B" => "\xED\x96\xB2", + "\xC1\x8C" => "\xED\x96\xB3", + "\xC1\x8D" => "\xED\x96\xB4", + "\xC1\x8E" => "\xED\x96\xB5", + "\xC1\x8F" => "\xED\x96\xB6", + "\xC1\x90" => "\xED\x96\xB7", + "\xC1\x91" => "\xED\x96\xB8", + "\xC1\x92" => "\xED\x96\xB9", + "\xC1\x93" => "\xED\x96\xBA", + "\xC1\x94" => "\xED\x96\xBB", + "\xC1\x95" => "\xED\x96\xBC", + "\xC1\x96" => "\xED\x96\xBD", + "\xC1\x97" => "\xED\x96\xBE", + "\xC1\x98" => "\xED\x96\xBF", + "\xC1\x99" => "\xED\x97\x80", + "\xC1\x9A" => "\xED\x97\x81", + "\xC1\x9B" => "\xED\x97\x82", + "\xC1\x9C" => "\xED\x97\x83", + "\xC1\x9D" => "\xED\x97\x84", + "\xC1\x9E" => "\xED\x97\x85", + "\xC1\x9F" => "\xED\x97\x86", + "\xC1\xA0" => "\xED\x97\x87", + "\xC1\xA1" => "\xEC\xA0\x90", + "\xC1\xA2" => "\xEC\xA0\x91", + "\xC1\xA3" => "\xEC\xA0\x93", + "\xC1\xA4" => "\xEC\xA0\x95", + "\xC1\xA5" => "\xEC\xA0\x96", + "\xC1\xA6" => "\xEC\xA0\x9C", + "\xC1\xA7" => "\xEC\xA0\x9D", + "\xC1\xA8" => "\xEC\xA0\xA0", + "\xC1\xA9" => "\xEC\xA0\xA4", + "\xC1\xAA" => "\xEC\xA0\xAC", + "\xC1\xAB" => "\xEC\xA0\xAD", + "\xC1\xAC" => "\xEC\xA0\xAF", + "\xC1\xAD" => "\xEC\xA0\xB1", + "\xC1\xAE" => "\xEC\xA0\xB8", + "\xC1\xAF" => "\xEC\xA0\xBC", + "\xC1\xB0" => "\xEC\xA1\x80", + "\xC1\xB1" => "\xEC\xA1\x88", + "\xC1\xB2" => "\xEC\xA1\x89", + "\xC1\xB3" => "\xEC\xA1\x8C", + "\xC1\xB4" => "\xEC\xA1\x8D", + "\xC1\xB5" => "\xEC\xA1\x94", + "\xC1\xB6" => "\xEC\xA1\xB0", + "\xC1\xB7" => "\xEC\xA1\xB1", + "\xC1\xB8" => "\xEC\xA1\xB4", + "\xC1\xB9" => "\xEC\xA1\xB8", + "\xC1\xBA" => "\xEC\xA1\xBA", + "\xC1\xBB" => "\xEC\xA2\x80", + "\xC1\xBC" => "\xEC\xA2\x81", + "\xC1\xBD" => "\xEC\xA2\x83", + "\xC1\xBE" => "\xEC\xA2\x85", + "\xC1\xBF" => "\xEC\xA2\x86", + "\xC1\xC0" => "\xEC\xA2\x87", + "\xC1\xC1" => "\xEC\xA2\x8B", + "\xC1\xC2" => "\xEC\xA2\x8C", + "\xC1\xC3" => "\xEC\xA2\x8D", + "\xC1\xC4" => "\xEC\xA2\x94", + "\xC1\xC5" => "\xEC\xA2\x9D", + "\xC1\xC6" => "\xEC\xA2\x9F", + "\xC1\xC7" => "\xEC\xA2\xA1", + "\xC1\xC8" => "\xEC\xA2\xA8", + "\xC1\xC9" => "\xEC\xA2\xBC", + "\xC1\xCA" => "\xEC\xA2\xBD", + "\xC1\xCB" => "\xEC\xA3\x84", + "\xC1\xCC" => "\xEC\xA3\x88", + "\xC1\xCD" => "\xEC\xA3\x8C", + "\xC1\xCE" => "\xEC\xA3\x94", + "\xC1\xCF" => "\xEC\xA3\x95", + "\xC1\xD0" => "\xEC\xA3\x97", + "\xC1\xD1" => "\xEC\xA3\x99", + "\xC1\xD2" => "\xEC\xA3\xA0", + "\xC1\xD3" => "\xEC\xA3\xA1", + "\xC1\xD4" => "\xEC\xA3\xA4", + "\xC1\xD5" => "\xEC\xA3\xB5", + "\xC1\xD6" => "\xEC\xA3\xBC", + "\xC1\xD7" => "\xEC\xA3\xBD", + "\xC1\xD8" => "\xEC\xA4\x80", + "\xC1\xD9" => "\xEC\xA4\x84", + "\xC1\xDA" => "\xEC\xA4\x85", + "\xC1\xDB" => "\xEC\xA4\x86", + "\xC1\xDC" => "\xEC\xA4\x8C", + "\xC1\xDD" => "\xEC\xA4\x8D", + "\xC1\xDE" => "\xEC\xA4\x8F", + "\xC1\xDF" => "\xEC\xA4\x91", + "\xC1\xE0" => "\xEC\xA4\x98", + "\xC1\xE1" => "\xEC\xA4\xAC", + "\xC1\xE2" => "\xEC\xA4\xB4", + "\xC1\xE3" => "\xEC\xA5\x90", + "\xC1\xE4" => "\xEC\xA5\x91", + "\xC1\xE5" => "\xEC\xA5\x94", + "\xC1\xE6" => "\xEC\xA5\x98", + "\xC1\xE7" => "\xEC\xA5\xA0", + "\xC1\xE8" => "\xEC\xA5\xA1", + "\xC1\xE9" => "\xEC\xA5\xA3", + "\xC1\xEA" => "\xEC\xA5\xAC", + "\xC1\xEB" => "\xEC\xA5\xB0", + "\xC1\xEC" => "\xEC\xA5\xB4", + "\xC1\xED" => "\xEC\xA5\xBC", + "\xC1\xEE" => "\xEC\xA6\x88", + "\xC1\xEF" => "\xEC\xA6\x89", + "\xC1\xF0" => "\xEC\xA6\x8C", + "\xC1\xF1" => "\xEC\xA6\x90", + "\xC1\xF2" => "\xEC\xA6\x98", + "\xC1\xF3" => "\xEC\xA6\x99", + "\xC1\xF4" => "\xEC\xA6\x9B", + "\xC1\xF5" => "\xEC\xA6\x9D", + "\xC1\xF6" => "\xEC\xA7\x80", + "\xC1\xF7" => "\xEC\xA7\x81", + "\xC1\xF8" => "\xEC\xA7\x84", + "\xC1\xF9" => "\xEC\xA7\x87", + "\xC1\xFA" => "\xEC\xA7\x88", + "\xC1\xFB" => "\xEC\xA7\x8A", + "\xC1\xFC" => "\xEC\xA7\x90", + "\xC1\xFD" => "\xEC\xA7\x91", + "\xC1\xFE" => "\xEC\xA7\x93", + "\xC2\x41" => "\xED\x97\x8A", + "\xC2\x42" => "\xED\x97\x8B", + "\xC2\x43" => "\xED\x97\x8D", + "\xC2\x44" => "\xED\x97\x8E", + "\xC2\x45" => "\xED\x97\x8F", + "\xC2\x46" => "\xED\x97\x91", + "\xC2\x47" => "\xED\x97\x93", + "\xC2\x48" => "\xED\x97\x94", + "\xC2\x49" => "\xED\x97\x95", + "\xC2\x4A" => "\xED\x97\x96", + "\xC2\x4B" => "\xED\x97\x97", + "\xC2\x4C" => "\xED\x97\x9A", + "\xC2\x4D" => "\xED\x97\x9C", + "\xC2\x4E" => "\xED\x97\x9E", + "\xC2\x4F" => "\xED\x97\x9F", + "\xC2\x50" => "\xED\x97\xA0", + "\xC2\x51" => "\xED\x97\xA1", + "\xC2\x52" => "\xED\x97\xA2", + "\xC2\x53" => "\xED\x97\xA3", + "\xC2\x54" => "\xED\x97\xA6", + "\xC2\x55" => "\xED\x97\xA7", + "\xC2\x56" => "\xED\x97\xA9", + "\xC2\x57" => "\xED\x97\xAA", + "\xC2\x58" => "\xED\x97\xAB", + "\xC2\x59" => "\xED\x97\xAD", + "\xC2\x5A" => "\xED\x97\xAE", + "\xC2\x61" => "\xED\x97\xAF", + "\xC2\x62" => "\xED\x97\xB0", + "\xC2\x63" => "\xED\x97\xB1", + "\xC2\x64" => "\xED\x97\xB2", + "\xC2\x65" => "\xED\x97\xB3", + "\xC2\x66" => "\xED\x97\xB6", + "\xC2\x67" => "\xED\x97\xB8", + "\xC2\x68" => "\xED\x97\xBA", + "\xC2\x69" => "\xED\x97\xBB", + "\xC2\x6A" => "\xED\x97\xBC", + "\xC2\x6B" => "\xED\x97\xBD", + "\xC2\x6C" => "\xED\x97\xBE", + "\xC2\x6D" => "\xED\x97\xBF", + "\xC2\x6E" => "\xED\x98\x82", + "\xC2\x6F" => "\xED\x98\x83", + "\xC2\x70" => "\xED\x98\x85", + "\xC2\x71" => "\xED\x98\x86", + "\xC2\x72" => "\xED\x98\x87", + "\xC2\x73" => "\xED\x98\x89", + "\xC2\x74" => "\xED\x98\x8A", + "\xC2\x75" => "\xED\x98\x8B", + "\xC2\x76" => "\xED\x98\x8C", + "\xC2\x77" => "\xED\x98\x8D", + "\xC2\x78" => "\xED\x98\x8E", + "\xC2\x79" => "\xED\x98\x8F", + "\xC2\x7A" => "\xED\x98\x92", + "\xC2\x81" => "\xED\x98\x96", + "\xC2\x82" => "\xED\x98\x97", + "\xC2\x83" => "\xED\x98\x98", + "\xC2\x84" => "\xED\x98\x99", + "\xC2\x85" => "\xED\x98\x9A", + "\xC2\x86" => "\xED\x98\x9B", + "\xC2\x87" => "\xED\x98\x9D", + "\xC2\x88" => "\xED\x98\x9E", + "\xC2\x89" => "\xED\x98\x9F", + "\xC2\x8A" => "\xED\x98\xA1", + "\xC2\x8B" => "\xED\x98\xA2", + "\xC2\x8C" => "\xED\x98\xA3", + "\xC2\x8D" => "\xED\x98\xA5", + "\xC2\x8E" => "\xED\x98\xA6", + "\xC2\x8F" => "\xED\x98\xA7", + "\xC2\x90" => "\xED\x98\xA8", + "\xC2\x91" => "\xED\x98\xA9", + "\xC2\x92" => "\xED\x98\xAA", + "\xC2\x93" => "\xED\x98\xAB", + "\xC2\x94" => "\xED\x98\xAC", + "\xC2\x95" => "\xED\x98\xAE", + "\xC2\x96" => "\xED\x98\xAF", + "\xC2\x97" => "\xED\x98\xB0", + "\xC2\x98" => "\xED\x98\xB1", + "\xC2\x99" => "\xED\x98\xB2", + "\xC2\x9A" => "\xED\x98\xB3", + "\xC2\x9B" => "\xED\x98\xB4", + "\xC2\x9C" => "\xED\x98\xB5", + "\xC2\x9D" => "\xED\x98\xB6", + "\xC2\x9E" => "\xED\x98\xB7", + "\xC2\x9F" => "\xED\x98\xBA", + "\xC2\xA0" => "\xED\x98\xBB", + "\xC2\xA1" => "\xEC\xA7\x95", + "\xC2\xA2" => "\xEC\xA7\x96", + "\xC2\xA3" => "\xEC\xA7\x99", + "\xC2\xA4" => "\xEC\xA7\x9A", + "\xC2\xA5" => "\xEC\xA7\x9C", + "\xC2\xA6" => "\xEC\xA7\x9D", + "\xC2\xA7" => "\xEC\xA7\xA0", + "\xC2\xA8" => "\xEC\xA7\xA2", + "\xC2\xA9" => "\xEC\xA7\xA4", + "\xC2\xAA" => "\xEC\xA7\xA7", + "\xC2\xAB" => "\xEC\xA7\xAC", + "\xC2\xAC" => "\xEC\xA7\xAD", + "\xC2\xAD" => "\xEC\xA7\xAF", + "\xC2\xAE" => "\xEC\xA7\xB0", + "\xC2\xAF" => "\xEC\xA7\xB1", + "\xC2\xB0" => "\xEC\xA7\xB8", + "\xC2\xB1" => "\xEC\xA7\xB9", + "\xC2\xB2" => "\xEC\xA7\xBC", + "\xC2\xB3" => "\xEC\xA8\x80", + "\xC2\xB4" => "\xEC\xA8\x88", + "\xC2\xB5" => "\xEC\xA8\x89", + "\xC2\xB6" => "\xEC\xA8\x8B", + "\xC2\xB7" => "\xEC\xA8\x8C", + "\xC2\xB8" => "\xEC\xA8\x8D", + "\xC2\xB9" => "\xEC\xA8\x94", + "\xC2\xBA" => "\xEC\xA8\x98", + "\xC2\xBB" => "\xEC\xA8\xA9", + "\xC2\xBC" => "\xEC\xA9\x8C", + "\xC2\xBD" => "\xEC\xA9\x8D", + "\xC2\xBE" => "\xEC\xA9\x90", + "\xC2\xBF" => "\xEC\xA9\x94", + "\xC2\xC0" => "\xEC\xA9\x9C", + "\xC2\xC1" => "\xEC\xA9\x9D", + "\xC2\xC2" => "\xEC\xA9\x9F", + "\xC2\xC3" => "\xEC\xA9\xA0", + "\xC2\xC4" => "\xEC\xA9\xA1", + "\xC2\xC5" => "\xEC\xA9\xA8", + "\xC2\xC6" => "\xEC\xA9\xBD", + "\xC2\xC7" => "\xEC\xAA\x84", + "\xC2\xC8" => "\xEC\xAA\x98", + "\xC2\xC9" => "\xEC\xAA\xBC", + "\xC2\xCA" => "\xEC\xAA\xBD", + "\xC2\xCB" => "\xEC\xAB\x80", + "\xC2\xCC" => "\xEC\xAB\x84", + "\xC2\xCD" => "\xEC\xAB\x8C", + "\xC2\xCE" => "\xEC\xAB\x8D", + "\xC2\xCF" => "\xEC\xAB\x8F", + "\xC2\xD0" => "\xEC\xAB\x91", + "\xC2\xD1" => "\xEC\xAB\x93", + "\xC2\xD2" => "\xEC\xAB\x98", + "\xC2\xD3" => "\xEC\xAB\x99", + "\xC2\xD4" => "\xEC\xAB\xA0", + "\xC2\xD5" => "\xEC\xAB\xAC", + "\xC2\xD6" => "\xEC\xAB\xB4", + "\xC2\xD7" => "\xEC\xAC\x88", + "\xC2\xD8" => "\xEC\xAC\x90", + "\xC2\xD9" => "\xEC\xAC\x94", + "\xC2\xDA" => "\xEC\xAC\x98", + "\xC2\xDB" => "\xEC\xAC\xA0", + "\xC2\xDC" => "\xEC\xAC\xA1", + "\xC2\xDD" => "\xEC\xAD\x81", + "\xC2\xDE" => "\xEC\xAD\x88", + "\xC2\xDF" => "\xEC\xAD\x89", + "\xC2\xE0" => "\xEC\xAD\x8C", + "\xC2\xE1" => "\xEC\xAD\x90", + "\xC2\xE2" => "\xEC\xAD\x98", + "\xC2\xE3" => "\xEC\xAD\x99", + "\xC2\xE4" => "\xEC\xAD\x9D", + "\xC2\xE5" => "\xEC\xAD\xA4", + "\xC2\xE6" => "\xEC\xAD\xB8", + "\xC2\xE7" => "\xEC\xAD\xB9", + "\xC2\xE8" => "\xEC\xAE\x9C", + "\xC2\xE9" => "\xEC\xAE\xB8", + "\xC2\xEA" => "\xEC\xAF\x94", + "\xC2\xEB" => "\xEC\xAF\xA4", + "\xC2\xEC" => "\xEC\xAF\xA7", + "\xC2\xED" => "\xEC\xAF\xA9", + "\xC2\xEE" => "\xEC\xB0\x8C", + "\xC2\xEF" => "\xEC\xB0\x8D", + "\xC2\xF0" => "\xEC\xB0\x90", + "\xC2\xF1" => "\xEC\xB0\x94", + "\xC2\xF2" => "\xEC\xB0\x9C", + "\xC2\xF3" => "\xEC\xB0\x9D", + "\xC2\xF4" => "\xEC\xB0\xA1", + "\xC2\xF5" => "\xEC\xB0\xA2", + "\xC2\xF6" => "\xEC\xB0\xA7", + "\xC2\xF7" => "\xEC\xB0\xA8", + "\xC2\xF8" => "\xEC\xB0\xA9", + "\xC2\xF9" => "\xEC\xB0\xAC", + "\xC2\xFA" => "\xEC\xB0\xAE", + "\xC2\xFB" => "\xEC\xB0\xB0", + "\xC2\xFC" => "\xEC\xB0\xB8", + "\xC2\xFD" => "\xEC\xB0\xB9", + "\xC2\xFE" => "\xEC\xB0\xBB", + "\xC3\x41" => "\xED\x98\xBD", + "\xC3\x42" => "\xED\x98\xBE", + "\xC3\x43" => "\xED\x98\xBF", + "\xC3\x44" => "\xED\x99\x81", + "\xC3\x45" => "\xED\x99\x82", + "\xC3\x46" => "\xED\x99\x83", + "\xC3\x47" => "\xED\x99\x84", + "\xC3\x48" => "\xED\x99\x86", + "\xC3\x49" => "\xED\x99\x87", + "\xC3\x4A" => "\xED\x99\x8A", + "\xC3\x4B" => "\xED\x99\x8C", + "\xC3\x4C" => "\xED\x99\x8E", + "\xC3\x4D" => "\xED\x99\x8F", + "\xC3\x4E" => "\xED\x99\x90", + "\xC3\x4F" => "\xED\x99\x92", + "\xC3\x50" => "\xED\x99\x93", + "\xC3\x51" => "\xED\x99\x96", + "\xC3\x52" => "\xED\x99\x97", + "\xC3\x53" => "\xED\x99\x99", + "\xC3\x54" => "\xED\x99\x9A", + "\xC3\x55" => "\xED\x99\x9B", + "\xC3\x56" => "\xED\x99\x9D", + "\xC3\x57" => "\xED\x99\x9E", + "\xC3\x58" => "\xED\x99\x9F", + "\xC3\x59" => "\xED\x99\xA0", + "\xC3\x5A" => "\xED\x99\xA1", + "\xC3\x61" => "\xED\x99\xA2", + "\xC3\x62" => "\xED\x99\xA3", + "\xC3\x63" => "\xED\x99\xA4", + "\xC3\x64" => "\xED\x99\xA5", + "\xC3\x65" => "\xED\x99\xA6", + "\xC3\x66" => "\xED\x99\xA8", + "\xC3\x67" => "\xED\x99\xAA", + "\xC3\x68" => "\xED\x99\xAB", + "\xC3\x69" => "\xED\x99\xAC", + "\xC3\x6A" => "\xED\x99\xAD", + "\xC3\x6B" => "\xED\x99\xAE", + "\xC3\x6C" => "\xED\x99\xAF", + "\xC3\x6D" => "\xED\x99\xB2", + "\xC3\x6E" => "\xED\x99\xB3", + "\xC3\x6F" => "\xED\x99\xB5", + "\xC3\x70" => "\xED\x99\xB6", + "\xC3\x71" => "\xED\x99\xB7", + "\xC3\x72" => "\xED\x99\xB8", + "\xC3\x73" => "\xED\x99\xB9", + "\xC3\x74" => "\xED\x99\xBA", + "\xC3\x75" => "\xED\x99\xBB", + "\xC3\x76" => "\xED\x99\xBC", + "\xC3\x77" => "\xED\x99\xBD", + "\xC3\x78" => "\xED\x99\xBE", + "\xC3\x79" => "\xED\x99\xBF", + "\xC3\x7A" => "\xED\x9A\x80", + "\xC3\x81" => "\xED\x9A\x81", + "\xC3\x82" => "\xED\x9A\x82", + "\xC3\x83" => "\xED\x9A\x84", + "\xC3\x84" => "\xED\x9A\x86", + "\xC3\x85" => "\xED\x9A\x87", + "\xC3\x86" => "\xED\x9A\x88", + "\xC3\x87" => "\xED\x9A\x89", + "\xC3\x88" => "\xED\x9A\x8A", + "\xC3\x89" => "\xED\x9A\x8B", + "\xC3\x8A" => "\xED\x9A\x8E", + "\xC3\x8B" => "\xED\x9A\x8F", + "\xC3\x8C" => "\xED\x9A\x91", + "\xC3\x8D" => "\xED\x9A\x92", + "\xC3\x8E" => "\xED\x9A\x93", + "\xC3\x8F" => "\xED\x9A\x95", + "\xC3\x90" => "\xED\x9A\x96", + "\xC3\x91" => "\xED\x9A\x97", + "\xC3\x92" => "\xED\x9A\x98", + "\xC3\x93" => "\xED\x9A\x99", + "\xC3\x94" => "\xED\x9A\x9A", + "\xC3\x95" => "\xED\x9A\x9B", + "\xC3\x96" => "\xED\x9A\x9C", + "\xC3\x97" => "\xED\x9A\x9E", + "\xC3\x98" => "\xED\x9A\xA0", + "\xC3\x99" => "\xED\x9A\xA2", + "\xC3\x9A" => "\xED\x9A\xA3", + "\xC3\x9B" => "\xED\x9A\xA4", + "\xC3\x9C" => "\xED\x9A\xA5", + "\xC3\x9D" => "\xED\x9A\xA6", + "\xC3\x9E" => "\xED\x9A\xA7", + "\xC3\x9F" => "\xED\x9A\xA9", + "\xC3\xA0" => "\xED\x9A\xAA", + "\xC3\xA1" => "\xEC\xB0\xBC", + "\xC3\xA2" => "\xEC\xB0\xBD", + "\xC3\xA3" => "\xEC\xB0\xBE", + "\xC3\xA4" => "\xEC\xB1\x84", + "\xC3\xA5" => "\xEC\xB1\x85", + "\xC3\xA6" => "\xEC\xB1\x88", + "\xC3\xA7" => "\xEC\xB1\x8C", + "\xC3\xA8" => "\xEC\xB1\x94", + "\xC3\xA9" => "\xEC\xB1\x95", + "\xC3\xAA" => "\xEC\xB1\x97", + "\xC3\xAB" => "\xEC\xB1\x98", + "\xC3\xAC" => "\xEC\xB1\x99", + "\xC3\xAD" => "\xEC\xB1\xA0", + "\xC3\xAE" => "\xEC\xB1\xA4", + "\xC3\xAF" => "\xEC\xB1\xA6", + "\xC3\xB0" => "\xEC\xB1\xA8", + "\xC3\xB1" => "\xEC\xB1\xB0", + "\xC3\xB2" => "\xEC\xB1\xB5", + "\xC3\xB3" => "\xEC\xB2\x98", + "\xC3\xB4" => "\xEC\xB2\x99", + "\xC3\xB5" => "\xEC\xB2\x9C", + "\xC3\xB6" => "\xEC\xB2\xA0", + "\xC3\xB7" => "\xEC\xB2\xA8", + "\xC3\xB8" => "\xEC\xB2\xA9", + "\xC3\xB9" => "\xEC\xB2\xAB", + "\xC3\xBA" => "\xEC\xB2\xAC", + "\xC3\xBB" => "\xEC\xB2\xAD", + "\xC3\xBC" => "\xEC\xB2\xB4", + "\xC3\xBD" => "\xEC\xB2\xB5", + "\xC3\xBE" => "\xEC\xB2\xB8", + "\xC3\xBF" => "\xEC\xB2\xBC", + "\xC3\xC0" => "\xEC\xB3\x84", + "\xC3\xC1" => "\xEC\xB3\x85", + "\xC3\xC2" => "\xEC\xB3\x87", + "\xC3\xC3" => "\xEC\xB3\x89", + "\xC3\xC4" => "\xEC\xB3\x90", + "\xC3\xC5" => "\xEC\xB3\x94", + "\xC3\xC6" => "\xEC\xB3\xA4", + "\xC3\xC7" => "\xEC\xB3\xAC", + "\xC3\xC8" => "\xEC\xB3\xB0", + "\xC3\xC9" => "\xEC\xB4\x81", + "\xC3\xCA" => "\xEC\xB4\x88", + "\xC3\xCB" => "\xEC\xB4\x89", + "\xC3\xCC" => "\xEC\xB4\x8C", + "\xC3\xCD" => "\xEC\xB4\x90", + "\xC3\xCE" => "\xEC\xB4\x98", + "\xC3\xCF" => "\xEC\xB4\x99", + "\xC3\xD0" => "\xEC\xB4\x9B", + "\xC3\xD1" => "\xEC\xB4\x9D", + "\xC3\xD2" => "\xEC\xB4\xA4", + "\xC3\xD3" => "\xEC\xB4\xA8", + "\xC3\xD4" => "\xEC\xB4\xAC", + "\xC3\xD5" => "\xEC\xB4\xB9", + "\xC3\xD6" => "\xEC\xB5\x9C", + "\xC3\xD7" => "\xEC\xB5\xA0", + "\xC3\xD8" => "\xEC\xB5\xA4", + "\xC3\xD9" => "\xEC\xB5\xAC", + "\xC3\xDA" => "\xEC\xB5\xAD", + "\xC3\xDB" => "\xEC\xB5\xAF", + "\xC3\xDC" => "\xEC\xB5\xB1", + "\xC3\xDD" => "\xEC\xB5\xB8", + "\xC3\xDE" => "\xEC\xB6\x88", + "\xC3\xDF" => "\xEC\xB6\x94", + "\xC3\xE0" => "\xEC\xB6\x95", + "\xC3\xE1" => "\xEC\xB6\x98", + "\xC3\xE2" => "\xEC\xB6\x9C", + "\xC3\xE3" => "\xEC\xB6\xA4", + "\xC3\xE4" => "\xEC\xB6\xA5", + "\xC3\xE5" => "\xEC\xB6\xA7", + "\xC3\xE6" => "\xEC\xB6\xA9", + "\xC3\xE7" => "\xEC\xB6\xB0", + "\xC3\xE8" => "\xEC\xB7\x84", + "\xC3\xE9" => "\xEC\xB7\x8C", + "\xC3\xEA" => "\xEC\xB7\x90", + "\xC3\xEB" => "\xEC\xB7\xA8", + "\xC3\xEC" => "\xEC\xB7\xAC", + "\xC3\xED" => "\xEC\xB7\xB0", + "\xC3\xEE" => "\xEC\xB7\xB8", + "\xC3\xEF" => "\xEC\xB7\xB9", + "\xC3\xF0" => "\xEC\xB7\xBB", + "\xC3\xF1" => "\xEC\xB7\xBD", + "\xC3\xF2" => "\xEC\xB8\x84", + "\xC3\xF3" => "\xEC\xB8\x88", + "\xC3\xF4" => "\xEC\xB8\x8C", + "\xC3\xF5" => "\xEC\xB8\x94", + "\xC3\xF6" => "\xEC\xB8\x99", + "\xC3\xF7" => "\xEC\xB8\xA0", + "\xC3\xF8" => "\xEC\xB8\xA1", + "\xC3\xF9" => "\xEC\xB8\xA4", + "\xC3\xFA" => "\xEC\xB8\xA8", + "\xC3\xFB" => "\xEC\xB8\xB0", + "\xC3\xFC" => "\xEC\xB8\xB1", + "\xC3\xFD" => "\xEC\xB8\xB3", + "\xC3\xFE" => "\xEC\xB8\xB5", + "\xC4\x41" => "\xED\x9A\xAB", + "\xC4\x42" => "\xED\x9A\xAD", + "\xC4\x43" => "\xED\x9A\xAE", + "\xC4\x44" => "\xED\x9A\xAF", + "\xC4\x45" => "\xED\x9A\xB1", + "\xC4\x46" => "\xED\x9A\xB2", + "\xC4\x47" => "\xED\x9A\xB3", + "\xC4\x48" => "\xED\x9A\xB4", + "\xC4\x49" => "\xED\x9A\xB5", + "\xC4\x4A" => "\xED\x9A\xB6", + "\xC4\x4B" => "\xED\x9A\xB7", + "\xC4\x4C" => "\xED\x9A\xB8", + "\xC4\x4D" => "\xED\x9A\xBA", + "\xC4\x4E" => "\xED\x9A\xBC", + "\xC4\x4F" => "\xED\x9A\xBD", + "\xC4\x50" => "\xED\x9A\xBE", + "\xC4\x51" => "\xED\x9A\xBF", + "\xC4\x52" => "\xED\x9B\x80", + "\xC4\x53" => "\xED\x9B\x81", + "\xC4\x54" => "\xED\x9B\x82", + "\xC4\x55" => "\xED\x9B\x83", + "\xC4\x56" => "\xED\x9B\x86", + "\xC4\x57" => "\xED\x9B\x87", + "\xC4\x58" => "\xED\x9B\x89", + "\xC4\x59" => "\xED\x9B\x8A", + "\xC4\x5A" => "\xED\x9B\x8B", + "\xC4\x61" => "\xED\x9B\x8D", + "\xC4\x62" => "\xED\x9B\x8E", + "\xC4\x63" => "\xED\x9B\x8F", + "\xC4\x64" => "\xED\x9B\x90", + "\xC4\x65" => "\xED\x9B\x92", + "\xC4\x66" => "\xED\x9B\x93", + "\xC4\x67" => "\xED\x9B\x95", + "\xC4\x68" => "\xED\x9B\x96", + "\xC4\x69" => "\xED\x9B\x98", + "\xC4\x6A" => "\xED\x9B\x9A", + "\xC4\x6B" => "\xED\x9B\x9B", + "\xC4\x6C" => "\xED\x9B\x9C", + "\xC4\x6D" => "\xED\x9B\x9D", + "\xC4\x6E" => "\xED\x9B\x9E", + "\xC4\x6F" => "\xED\x9B\x9F", + "\xC4\x70" => "\xED\x9B\xA1", + "\xC4\x71" => "\xED\x9B\xA2", + "\xC4\x72" => "\xED\x9B\xA3", + "\xC4\x73" => "\xED\x9B\xA5", + "\xC4\x74" => "\xED\x9B\xA6", + "\xC4\x75" => "\xED\x9B\xA7", + "\xC4\x76" => "\xED\x9B\xA9", + "\xC4\x77" => "\xED\x9B\xAA", + "\xC4\x78" => "\xED\x9B\xAB", + "\xC4\x79" => "\xED\x9B\xAC", + "\xC4\x7A" => "\xED\x9B\xAD", + "\xC4\x81" => "\xED\x9B\xAE", + "\xC4\x82" => "\xED\x9B\xAF", + "\xC4\x83" => "\xED\x9B\xB1", + "\xC4\x84" => "\xED\x9B\xB2", + "\xC4\x85" => "\xED\x9B\xB3", + "\xC4\x86" => "\xED\x9B\xB4", + "\xC4\x87" => "\xED\x9B\xB6", + "\xC4\x88" => "\xED\x9B\xB7", + "\xC4\x89" => "\xED\x9B\xB8", + "\xC4\x8A" => "\xED\x9B\xB9", + "\xC4\x8B" => "\xED\x9B\xBA", + "\xC4\x8C" => "\xED\x9B\xBB", + "\xC4\x8D" => "\xED\x9B\xBE", + "\xC4\x8E" => "\xED\x9B\xBF", + "\xC4\x8F" => "\xED\x9C\x81", + "\xC4\x90" => "\xED\x9C\x82", + "\xC4\x91" => "\xED\x9C\x83", + "\xC4\x92" => "\xED\x9C\x85", + "\xC4\x93" => "\xED\x9C\x86", + "\xC4\x94" => "\xED\x9C\x87", + "\xC4\x95" => "\xED\x9C\x88", + "\xC4\x96" => "\xED\x9C\x89", + "\xC4\x97" => "\xED\x9C\x8A", + "\xC4\x98" => "\xED\x9C\x8B", + "\xC4\x99" => "\xED\x9C\x8C", + "\xC4\x9A" => "\xED\x9C\x8D", + "\xC4\x9B" => "\xED\x9C\x8E", + "\xC4\x9C" => "\xED\x9C\x8F", + "\xC4\x9D" => "\xED\x9C\x90", + "\xC4\x9E" => "\xED\x9C\x92", + "\xC4\x9F" => "\xED\x9C\x93", + "\xC4\xA0" => "\xED\x9C\x94", + "\xC4\xA1" => "\xEC\xB9\x98", + "\xC4\xA2" => "\xEC\xB9\x99", + "\xC4\xA3" => "\xEC\xB9\x9C", + "\xC4\xA4" => "\xEC\xB9\x9F", + "\xC4\xA5" => "\xEC\xB9\xA0", + "\xC4\xA6" => "\xEC\xB9\xA1", + "\xC4\xA7" => "\xEC\xB9\xA8", + "\xC4\xA8" => "\xEC\xB9\xA9", + "\xC4\xA9" => "\xEC\xB9\xAB", + "\xC4\xAA" => "\xEC\xB9\xAD", + "\xC4\xAB" => "\xEC\xB9\xB4", + "\xC4\xAC" => "\xEC\xB9\xB5", + "\xC4\xAD" => "\xEC\xB9\xB8", + "\xC4\xAE" => "\xEC\xB9\xBC", + "\xC4\xAF" => "\xEC\xBA\x84", + "\xC4\xB0" => "\xEC\xBA\x85", + "\xC4\xB1" => "\xEC\xBA\x87", + "\xC4\xB2" => "\xEC\xBA\x89", + "\xC4\xB3" => "\xEC\xBA\x90", + "\xC4\xB4" => "\xEC\xBA\x91", + "\xC4\xB5" => "\xEC\xBA\x94", + "\xC4\xB6" => "\xEC\xBA\x98", + "\xC4\xB7" => "\xEC\xBA\xA0", + "\xC4\xB8" => "\xEC\xBA\xA1", + "\xC4\xB9" => "\xEC\xBA\xA3", + "\xC4\xBA" => "\xEC\xBA\xA4", + "\xC4\xBB" => "\xEC\xBA\xA5", + "\xC4\xBC" => "\xEC\xBA\xAC", + "\xC4\xBD" => "\xEC\xBA\xAD", + "\xC4\xBE" => "\xEC\xBB\x81", + "\xC4\xBF" => "\xEC\xBB\xA4", + "\xC4\xC0" => "\xEC\xBB\xA5", + "\xC4\xC1" => "\xEC\xBB\xA8", + "\xC4\xC2" => "\xEC\xBB\xAB", + "\xC4\xC3" => "\xEC\xBB\xAC", + "\xC4\xC4" => "\xEC\xBB\xB4", + "\xC4\xC5" => "\xEC\xBB\xB5", + "\xC4\xC6" => "\xEC\xBB\xB7", + "\xC4\xC7" => "\xEC\xBB\xB8", + "\xC4\xC8" => "\xEC\xBB\xB9", + "\xC4\xC9" => "\xEC\xBC\x80", + "\xC4\xCA" => "\xEC\xBC\x81", + "\xC4\xCB" => "\xEC\xBC\x84", + "\xC4\xCC" => "\xEC\xBC\x88", + "\xC4\xCD" => "\xEC\xBC\x90", + "\xC4\xCE" => "\xEC\xBC\x91", + "\xC4\xCF" => "\xEC\xBC\x93", + "\xC4\xD0" => "\xEC\xBC\x95", + "\xC4\xD1" => "\xEC\xBC\x9C", + "\xC4\xD2" => "\xEC\xBC\xA0", + "\xC4\xD3" => "\xEC\xBC\xA4", + "\xC4\xD4" => "\xEC\xBC\xAC", + "\xC4\xD5" => "\xEC\xBC\xAD", + "\xC4\xD6" => "\xEC\xBC\xAF", + "\xC4\xD7" => "\xEC\xBC\xB0", + "\xC4\xD8" => "\xEC\xBC\xB1", + "\xC4\xD9" => "\xEC\xBC\xB8", + "\xC4\xDA" => "\xEC\xBD\x94", + "\xC4\xDB" => "\xEC\xBD\x95", + "\xC4\xDC" => "\xEC\xBD\x98", + "\xC4\xDD" => "\xEC\xBD\x9C", + "\xC4\xDE" => "\xEC\xBD\xA4", + "\xC4\xDF" => "\xEC\xBD\xA5", + "\xC4\xE0" => "\xEC\xBD\xA7", + "\xC4\xE1" => "\xEC\xBD\xA9", + "\xC4\xE2" => "\xEC\xBD\xB0", + "\xC4\xE3" => "\xEC\xBD\xB1", + "\xC4\xE4" => "\xEC\xBD\xB4", + "\xC4\xE5" => "\xEC\xBD\xB8", + "\xC4\xE6" => "\xEC\xBE\x80", + "\xC4\xE7" => "\xEC\xBE\x85", + "\xC4\xE8" => "\xEC\xBE\x8C", + "\xC4\xE9" => "\xEC\xBE\xA1", + "\xC4\xEA" => "\xEC\xBE\xA8", + "\xC4\xEB" => "\xEC\xBE\xB0", + "\xC4\xEC" => "\xEC\xBF\x84", + "\xC4\xED" => "\xEC\xBF\xA0", + "\xC4\xEE" => "\xEC\xBF\xA1", + "\xC4\xEF" => "\xEC\xBF\xA4", + "\xC4\xF0" => "\xEC\xBF\xA8", + "\xC4\xF1" => "\xEC\xBF\xB0", + "\xC4\xF2" => "\xEC\xBF\xB1", + "\xC4\xF3" => "\xEC\xBF\xB3", + "\xC4\xF4" => "\xEC\xBF\xB5", + "\xC4\xF5" => "\xEC\xBF\xBC", + "\xC4\xF6" => "\xED\x80\x80", + "\xC4\xF7" => "\xED\x80\x84", + "\xC4\xF8" => "\xED\x80\x91", + "\xC4\xF9" => "\xED\x80\x98", + "\xC4\xFA" => "\xED\x80\xAD", + "\xC4\xFB" => "\xED\x80\xB4", + "\xC4\xFC" => "\xED\x80\xB5", + "\xC4\xFD" => "\xED\x80\xB8", + "\xC4\xFE" => "\xED\x80\xBC", + "\xC5\x41" => "\xED\x9C\x95", + "\xC5\x42" => "\xED\x9C\x96", + "\xC5\x43" => "\xED\x9C\x97", + "\xC5\x44" => "\xED\x9C\x9A", + "\xC5\x45" => "\xED\x9C\x9B", + "\xC5\x46" => "\xED\x9C\x9D", + "\xC5\x47" => "\xED\x9C\x9E", + "\xC5\x48" => "\xED\x9C\x9F", + "\xC5\x49" => "\xED\x9C\xA1", + "\xC5\x4A" => "\xED\x9C\xA2", + "\xC5\x4B" => "\xED\x9C\xA3", + "\xC5\x4C" => "\xED\x9C\xA4", + "\xC5\x4D" => "\xED\x9C\xA5", + "\xC5\x4E" => "\xED\x9C\xA6", + "\xC5\x4F" => "\xED\x9C\xA7", + "\xC5\x50" => "\xED\x9C\xAA", + "\xC5\x51" => "\xED\x9C\xAC", + "\xC5\x52" => "\xED\x9C\xAE", + "\xC5\x53" => "\xED\x9C\xAF", + "\xC5\x54" => "\xED\x9C\xB0", + "\xC5\x55" => "\xED\x9C\xB1", + "\xC5\x56" => "\xED\x9C\xB2", + "\xC5\x57" => "\xED\x9C\xB3", + "\xC5\x58" => "\xED\x9C\xB6", + "\xC5\x59" => "\xED\x9C\xB7", + "\xC5\x5A" => "\xED\x9C\xB9", + "\xC5\x61" => "\xED\x9C\xBA", + "\xC5\x62" => "\xED\x9C\xBB", + "\xC5\x63" => "\xED\x9C\xBD", + "\xC5\x64" => "\xED\x9C\xBE", + "\xC5\x65" => "\xED\x9C\xBF", + "\xC5\x66" => "\xED\x9D\x80", + "\xC5\x67" => "\xED\x9D\x81", + "\xC5\x68" => "\xED\x9D\x82", + "\xC5\x69" => "\xED\x9D\x83", + "\xC5\x6A" => "\xED\x9D\x85", + "\xC5\x6B" => "\xED\x9D\x86", + "\xC5\x6C" => "\xED\x9D\x88", + "\xC5\x6D" => "\xED\x9D\x8A", + "\xC5\x6E" => "\xED\x9D\x8B", + "\xC5\x6F" => "\xED\x9D\x8C", + "\xC5\x70" => "\xED\x9D\x8D", + "\xC5\x71" => "\xED\x9D\x8E", + "\xC5\x72" => "\xED\x9D\x8F", + "\xC5\x73" => "\xED\x9D\x92", + "\xC5\x74" => "\xED\x9D\x93", + "\xC5\x75" => "\xED\x9D\x95", + "\xC5\x76" => "\xED\x9D\x9A", + "\xC5\x77" => "\xED\x9D\x9B", + "\xC5\x78" => "\xED\x9D\x9C", + "\xC5\x79" => "\xED\x9D\x9D", + "\xC5\x7A" => "\xED\x9D\x9E", + "\xC5\x81" => "\xED\x9D\x9F", + "\xC5\x82" => "\xED\x9D\xA2", + "\xC5\x83" => "\xED\x9D\xA4", + "\xC5\x84" => "\xED\x9D\xA6", + "\xC5\x85" => "\xED\x9D\xA7", + "\xC5\x86" => "\xED\x9D\xA8", + "\xC5\x87" => "\xED\x9D\xAA", + "\xC5\x88" => "\xED\x9D\xAB", + "\xC5\x89" => "\xED\x9D\xAD", + "\xC5\x8A" => "\xED\x9D\xAE", + "\xC5\x8B" => "\xED\x9D\xAF", + "\xC5\x8C" => "\xED\x9D\xB1", + "\xC5\x8D" => "\xED\x9D\xB2", + "\xC5\x8E" => "\xED\x9D\xB3", + "\xC5\x8F" => "\xED\x9D\xB5", + "\xC5\x90" => "\xED\x9D\xB6", + "\xC5\x91" => "\xED\x9D\xB7", + "\xC5\x92" => "\xED\x9D\xB8", + "\xC5\x93" => "\xED\x9D\xB9", + "\xC5\x94" => "\xED\x9D\xBA", + "\xC5\x95" => "\xED\x9D\xBB", + "\xC5\x96" => "\xED\x9D\xBE", + "\xC5\x97" => "\xED\x9D\xBF", + "\xC5\x98" => "\xED\x9E\x80", + "\xC5\x99" => "\xED\x9E\x82", + "\xC5\x9A" => "\xED\x9E\x83", + "\xC5\x9B" => "\xED\x9E\x84", + "\xC5\x9C" => "\xED\x9E\x85", + "\xC5\x9D" => "\xED\x9E\x86", + "\xC5\x9E" => "\xED\x9E\x87", + "\xC5\x9F" => "\xED\x9E\x8A", + "\xC5\xA0" => "\xED\x9E\x8B", + "\xC5\xA1" => "\xED\x81\x84", + "\xC5\xA2" => "\xED\x81\x85", + "\xC5\xA3" => "\xED\x81\x87", + "\xC5\xA4" => "\xED\x81\x89", + "\xC5\xA5" => "\xED\x81\x90", + "\xC5\xA6" => "\xED\x81\x94", + "\xC5\xA7" => "\xED\x81\x98", + "\xC5\xA8" => "\xED\x81\xA0", + "\xC5\xA9" => "\xED\x81\xAC", + "\xC5\xAA" => "\xED\x81\xAD", + "\xC5\xAB" => "\xED\x81\xB0", + "\xC5\xAC" => "\xED\x81\xB4", + "\xC5\xAD" => "\xED\x81\xBC", + "\xC5\xAE" => "\xED\x81\xBD", + "\xC5\xAF" => "\xED\x82\x81", + "\xC5\xB0" => "\xED\x82\xA4", + "\xC5\xB1" => "\xED\x82\xA5", + "\xC5\xB2" => "\xED\x82\xA8", + "\xC5\xB3" => "\xED\x82\xAC", + "\xC5\xB4" => "\xED\x82\xB4", + "\xC5\xB5" => "\xED\x82\xB5", + "\xC5\xB6" => "\xED\x82\xB7", + "\xC5\xB7" => "\xED\x82\xB9", + "\xC5\xB8" => "\xED\x83\x80", + "\xC5\xB9" => "\xED\x83\x81", + "\xC5\xBA" => "\xED\x83\x84", + "\xC5\xBB" => "\xED\x83\x88", + "\xC5\xBC" => "\xED\x83\x89", + "\xC5\xBD" => "\xED\x83\x90", + "\xC5\xBE" => "\xED\x83\x91", + "\xC5\xBF" => "\xED\x83\x93", + "\xC5\xC0" => "\xED\x83\x94", + "\xC5\xC1" => "\xED\x83\x95", + "\xC5\xC2" => "\xED\x83\x9C", + "\xC5\xC3" => "\xED\x83\x9D", + "\xC5\xC4" => "\xED\x83\xA0", + "\xC5\xC5" => "\xED\x83\xA4", + "\xC5\xC6" => "\xED\x83\xAC", + "\xC5\xC7" => "\xED\x83\xAD", + "\xC5\xC8" => "\xED\x83\xAF", + "\xC5\xC9" => "\xED\x83\xB0", + "\xC5\xCA" => "\xED\x83\xB1", + "\xC5\xCB" => "\xED\x83\xB8", + "\xC5\xCC" => "\xED\x84\x8D", + "\xC5\xCD" => "\xED\x84\xB0", + "\xC5\xCE" => "\xED\x84\xB1", + "\xC5\xCF" => "\xED\x84\xB4", + "\xC5\xD0" => "\xED\x84\xB8", + "\xC5\xD1" => "\xED\x84\xBA", + "\xC5\xD2" => "\xED\x85\x80", + "\xC5\xD3" => "\xED\x85\x81", + "\xC5\xD4" => "\xED\x85\x83", + "\xC5\xD5" => "\xED\x85\x84", + "\xC5\xD6" => "\xED\x85\x85", + "\xC5\xD7" => "\xED\x85\x8C", + "\xC5\xD8" => "\xED\x85\x8D", + "\xC5\xD9" => "\xED\x85\x90", + "\xC5\xDA" => "\xED\x85\x94", + "\xC5\xDB" => "\xED\x85\x9C", + "\xC5\xDC" => "\xED\x85\x9D", + "\xC5\xDD" => "\xED\x85\x9F", + "\xC5\xDE" => "\xED\x85\xA1", + "\xC5\xDF" => "\xED\x85\xA8", + "\xC5\xE0" => "\xED\x85\xAC", + "\xC5\xE1" => "\xED\x85\xBC", + "\xC5\xE2" => "\xED\x86\x84", + "\xC5\xE3" => "\xED\x86\x88", + "\xC5\xE4" => "\xED\x86\xA0", + "\xC5\xE5" => "\xED\x86\xA1", + "\xC5\xE6" => "\xED\x86\xA4", + "\xC5\xE7" => "\xED\x86\xA8", + "\xC5\xE8" => "\xED\x86\xB0", + "\xC5\xE9" => "\xED\x86\xB1", + "\xC5\xEA" => "\xED\x86\xB3", + "\xC5\xEB" => "\xED\x86\xB5", + "\xC5\xEC" => "\xED\x86\xBA", + "\xC5\xED" => "\xED\x86\xBC", + "\xC5\xEE" => "\xED\x87\x80", + "\xC5\xEF" => "\xED\x87\x98", + "\xC5\xF0" => "\xED\x87\xB4", + "\xC5\xF1" => "\xED\x87\xB8", + "\xC5\xF2" => "\xED\x88\x87", + "\xC5\xF3" => "\xED\x88\x89", + "\xC5\xF4" => "\xED\x88\x90", + "\xC5\xF5" => "\xED\x88\xAC", + "\xC5\xF6" => "\xED\x88\xAD", + "\xC5\xF7" => "\xED\x88\xB0", + "\xC5\xF8" => "\xED\x88\xB4", + "\xC5\xF9" => "\xED\x88\xBC", + "\xC5\xFA" => "\xED\x88\xBD", + "\xC5\xFB" => "\xED\x88\xBF", + "\xC5\xFC" => "\xED\x89\x81", + "\xC5\xFD" => "\xED\x89\x88", + "\xC5\xFE" => "\xED\x89\x9C", + "\xC6\x41" => "\xED\x9E\x8D", + "\xC6\x42" => "\xED\x9E\x8E", + "\xC6\x43" => "\xED\x9E\x8F", + "\xC6\x44" => "\xED\x9E\x91", + "\xC6\x45" => "\xED\x9E\x92", + "\xC6\x46" => "\xED\x9E\x93", + "\xC6\x47" => "\xED\x9E\x94", + "\xC6\x48" => "\xED\x9E\x95", + "\xC6\x49" => "\xED\x9E\x96", + "\xC6\x4A" => "\xED\x9E\x97", + "\xC6\x4B" => "\xED\x9E\x9A", + "\xC6\x4C" => "\xED\x9E\x9C", + "\xC6\x4D" => "\xED\x9E\x9E", + "\xC6\x4E" => "\xED\x9E\x9F", + "\xC6\x4F" => "\xED\x9E\xA0", + "\xC6\x50" => "\xED\x9E\xA1", + "\xC6\x51" => "\xED\x9E\xA2", + "\xC6\x52" => "\xED\x9E\xA3", + "\xC6\xA1" => "\xED\x89\xA4", + "\xC6\xA2" => "\xED\x8A\x80", + "\xC6\xA3" => "\xED\x8A\x81", + "\xC6\xA4" => "\xED\x8A\x84", + "\xC6\xA5" => "\xED\x8A\x88", + "\xC6\xA6" => "\xED\x8A\x90", + "\xC6\xA7" => "\xED\x8A\x91", + "\xC6\xA8" => "\xED\x8A\x95", + "\xC6\xA9" => "\xED\x8A\x9C", + "\xC6\xAA" => "\xED\x8A\xA0", + "\xC6\xAB" => "\xED\x8A\xA4", + "\xC6\xAC" => "\xED\x8A\xAC", + "\xC6\xAD" => "\xED\x8A\xB1", + "\xC6\xAE" => "\xED\x8A\xB8", + "\xC6\xAF" => "\xED\x8A\xB9", + "\xC6\xB0" => "\xED\x8A\xBC", + "\xC6\xB1" => "\xED\x8A\xBF", + "\xC6\xB2" => "\xED\x8B\x80", + "\xC6\xB3" => "\xED\x8B\x82", + "\xC6\xB4" => "\xED\x8B\x88", + "\xC6\xB5" => "\xED\x8B\x89", + "\xC6\xB6" => "\xED\x8B\x8B", + "\xC6\xB7" => "\xED\x8B\x94", + "\xC6\xB8" => "\xED\x8B\x98", + "\xC6\xB9" => "\xED\x8B\x9C", + "\xC6\xBA" => "\xED\x8B\xA4", + "\xC6\xBB" => "\xED\x8B\xA5", + "\xC6\xBC" => "\xED\x8B\xB0", + "\xC6\xBD" => "\xED\x8B\xB1", + "\xC6\xBE" => "\xED\x8B\xB4", + "\xC6\xBF" => "\xED\x8B\xB8", + "\xC6\xC0" => "\xED\x8C\x80", + "\xC6\xC1" => "\xED\x8C\x81", + "\xC6\xC2" => "\xED\x8C\x83", + "\xC6\xC3" => "\xED\x8C\x85", + "\xC6\xC4" => "\xED\x8C\x8C", + "\xC6\xC5" => "\xED\x8C\x8D", + "\xC6\xC6" => "\xED\x8C\x8E", + "\xC6\xC7" => "\xED\x8C\x90", + "\xC6\xC8" => "\xED\x8C\x94", + "\xC6\xC9" => "\xED\x8C\x96", + "\xC6\xCA" => "\xED\x8C\x9C", + "\xC6\xCB" => "\xED\x8C\x9D", + "\xC6\xCC" => "\xED\x8C\x9F", + "\xC6\xCD" => "\xED\x8C\xA0", + "\xC6\xCE" => "\xED\x8C\xA1", + "\xC6\xCF" => "\xED\x8C\xA5", + "\xC6\xD0" => "\xED\x8C\xA8", + "\xC6\xD1" => "\xED\x8C\xA9", + "\xC6\xD2" => "\xED\x8C\xAC", + "\xC6\xD3" => "\xED\x8C\xB0", + "\xC6\xD4" => "\xED\x8C\xB8", + "\xC6\xD5" => "\xED\x8C\xB9", + "\xC6\xD6" => "\xED\x8C\xBB", + "\xC6\xD7" => "\xED\x8C\xBC", + "\xC6\xD8" => "\xED\x8C\xBD", + "\xC6\xD9" => "\xED\x8D\x84", + "\xC6\xDA" => "\xED\x8D\x85", + "\xC6\xDB" => "\xED\x8D\xBC", + "\xC6\xDC" => "\xED\x8D\xBD", + "\xC6\xDD" => "\xED\x8E\x80", + "\xC6\xDE" => "\xED\x8E\x84", + "\xC6\xDF" => "\xED\x8E\x8C", + "\xC6\xE0" => "\xED\x8E\x8D", + "\xC6\xE1" => "\xED\x8E\x8F", + "\xC6\xE2" => "\xED\x8E\x90", + "\xC6\xE3" => "\xED\x8E\x91", + "\xC6\xE4" => "\xED\x8E\x98", + "\xC6\xE5" => "\xED\x8E\x99", + "\xC6\xE6" => "\xED\x8E\x9C", + "\xC6\xE7" => "\xED\x8E\xA0", + "\xC6\xE8" => "\xED\x8E\xA8", + "\xC6\xE9" => "\xED\x8E\xA9", + "\xC6\xEA" => "\xED\x8E\xAB", + "\xC6\xEB" => "\xED\x8E\xAD", + "\xC6\xEC" => "\xED\x8E\xB4", + "\xC6\xED" => "\xED\x8E\xB8", + "\xC6\xEE" => "\xED\x8E\xBC", + "\xC6\xEF" => "\xED\x8F\x84", + "\xC6\xF0" => "\xED\x8F\x85", + "\xC6\xF1" => "\xED\x8F\x88", + "\xC6\xF2" => "\xED\x8F\x89", + "\xC6\xF3" => "\xED\x8F\x90", + "\xC6\xF4" => "\xED\x8F\x98", + "\xC6\xF5" => "\xED\x8F\xA1", + "\xC6\xF6" => "\xED\x8F\xA3", + "\xC6\xF7" => "\xED\x8F\xAC", + "\xC6\xF8" => "\xED\x8F\xAD", + "\xC6\xF9" => "\xED\x8F\xB0", + "\xC6\xFA" => "\xED\x8F\xB4", + "\xC6\xFB" => "\xED\x8F\xBC", + "\xC6\xFC" => "\xED\x8F\xBD", + "\xC6\xFD" => "\xED\x8F\xBF", + "\xC6\xFE" => "\xED\x90\x81", + "\xC7\xA1" => "\xED\x90\x88", + "\xC7\xA2" => "\xED\x90\x9D", + "\xC7\xA3" => "\xED\x91\x80", + "\xC7\xA4" => "\xED\x91\x84", + "\xC7\xA5" => "\xED\x91\x9C", + "\xC7\xA6" => "\xED\x91\xA0", + "\xC7\xA7" => "\xED\x91\xA4", + "\xC7\xA8" => "\xED\x91\xAD", + "\xC7\xA9" => "\xED\x91\xAF", + "\xC7\xAA" => "\xED\x91\xB8", + "\xC7\xAB" => "\xED\x91\xB9", + "\xC7\xAC" => "\xED\x91\xBC", + "\xC7\xAD" => "\xED\x91\xBF", + "\xC7\xAE" => "\xED\x92\x80", + "\xC7\xAF" => "\xED\x92\x82", + "\xC7\xB0" => "\xED\x92\x88", + "\xC7\xB1" => "\xED\x92\x89", + "\xC7\xB2" => "\xED\x92\x8B", + "\xC7\xB3" => "\xED\x92\x8D", + "\xC7\xB4" => "\xED\x92\x94", + "\xC7\xB5" => "\xED\x92\xA9", + "\xC7\xB6" => "\xED\x93\x8C", + "\xC7\xB7" => "\xED\x93\x90", + "\xC7\xB8" => "\xED\x93\x94", + "\xC7\xB9" => "\xED\x93\x9C", + "\xC7\xBA" => "\xED\x93\x9F", + "\xC7\xBB" => "\xED\x93\xA8", + "\xC7\xBC" => "\xED\x93\xAC", + "\xC7\xBD" => "\xED\x93\xB0", + "\xC7\xBE" => "\xED\x93\xB8", + "\xC7\xBF" => "\xED\x93\xBB", + "\xC7\xC0" => "\xED\x93\xBD", + "\xC7\xC1" => "\xED\x94\x84", + "\xC7\xC2" => "\xED\x94\x88", + "\xC7\xC3" => "\xED\x94\x8C", + "\xC7\xC4" => "\xED\x94\x94", + "\xC7\xC5" => "\xED\x94\x95", + "\xC7\xC6" => "\xED\x94\x97", + "\xC7\xC7" => "\xED\x94\xBC", + "\xC7\xC8" => "\xED\x94\xBD", + "\xC7\xC9" => "\xED\x95\x80", + "\xC7\xCA" => "\xED\x95\x84", + "\xC7\xCB" => "\xED\x95\x8C", + "\xC7\xCC" => "\xED\x95\x8D", + "\xC7\xCD" => "\xED\x95\x8F", + "\xC7\xCE" => "\xED\x95\x91", + "\xC7\xCF" => "\xED\x95\x98", + "\xC7\xD0" => "\xED\x95\x99", + "\xC7\xD1" => "\xED\x95\x9C", + "\xC7\xD2" => "\xED\x95\xA0", + "\xC7\xD3" => "\xED\x95\xA5", + "\xC7\xD4" => "\xED\x95\xA8", + "\xC7\xD5" => "\xED\x95\xA9", + "\xC7\xD6" => "\xED\x95\xAB", + "\xC7\xD7" => "\xED\x95\xAD", + "\xC7\xD8" => "\xED\x95\xB4", + "\xC7\xD9" => "\xED\x95\xB5", + "\xC7\xDA" => "\xED\x95\xB8", + "\xC7\xDB" => "\xED\x95\xBC", + "\xC7\xDC" => "\xED\x96\x84", + "\xC7\xDD" => "\xED\x96\x85", + "\xC7\xDE" => "\xED\x96\x87", + "\xC7\xDF" => "\xED\x96\x88", + "\xC7\xE0" => "\xED\x96\x89", + "\xC7\xE1" => "\xED\x96\x90", + "\xC7\xE2" => "\xED\x96\xA5", + "\xC7\xE3" => "\xED\x97\x88", + "\xC7\xE4" => "\xED\x97\x89", + "\xC7\xE5" => "\xED\x97\x8C", + "\xC7\xE6" => "\xED\x97\x90", + "\xC7\xE7" => "\xED\x97\x92", + "\xC7\xE8" => "\xED\x97\x98", + "\xC7\xE9" => "\xED\x97\x99", + "\xC7\xEA" => "\xED\x97\x9B", + "\xC7\xEB" => "\xED\x97\x9D", + "\xC7\xEC" => "\xED\x97\xA4", + "\xC7\xED" => "\xED\x97\xA5", + "\xC7\xEE" => "\xED\x97\xA8", + "\xC7\xEF" => "\xED\x97\xAC", + "\xC7\xF0" => "\xED\x97\xB4", + "\xC7\xF1" => "\xED\x97\xB5", + "\xC7\xF2" => "\xED\x97\xB7", + "\xC7\xF3" => "\xED\x97\xB9", + "\xC7\xF4" => "\xED\x98\x80", + "\xC7\xF5" => "\xED\x98\x81", + "\xC7\xF6" => "\xED\x98\x84", + "\xC7\xF7" => "\xED\x98\x88", + "\xC7\xF8" => "\xED\x98\x90", + "\xC7\xF9" => "\xED\x98\x91", + "\xC7\xFA" => "\xED\x98\x93", + "\xC7\xFB" => "\xED\x98\x94", + "\xC7\xFC" => "\xED\x98\x95", + "\xC7\xFD" => "\xED\x98\x9C", + "\xC7\xFE" => "\xED\x98\xA0", + "\xC8\xA1" => "\xED\x98\xA4", + "\xC8\xA2" => "\xED\x98\xAD", + "\xC8\xA3" => "\xED\x98\xB8", + "\xC8\xA4" => "\xED\x98\xB9", + "\xC8\xA5" => "\xED\x98\xBC", + "\xC8\xA6" => "\xED\x99\x80", + "\xC8\xA7" => "\xED\x99\x85", + "\xC8\xA8" => "\xED\x99\x88", + "\xC8\xA9" => "\xED\x99\x89", + "\xC8\xAA" => "\xED\x99\x8B", + "\xC8\xAB" => "\xED\x99\x8D", + "\xC8\xAC" => "\xED\x99\x91", + "\xC8\xAD" => "\xED\x99\x94", + "\xC8\xAE" => "\xED\x99\x95", + "\xC8\xAF" => "\xED\x99\x98", + "\xC8\xB0" => "\xED\x99\x9C", + "\xC8\xB1" => "\xED\x99\xA7", + "\xC8\xB2" => "\xED\x99\xA9", + "\xC8\xB3" => "\xED\x99\xB0", + "\xC8\xB4" => "\xED\x99\xB1", + "\xC8\xB5" => "\xED\x99\xB4", + "\xC8\xB6" => "\xED\x9A\x83", + "\xC8\xB7" => "\xED\x9A\x85", + "\xC8\xB8" => "\xED\x9A\x8C", + "\xC8\xB9" => "\xED\x9A\x8D", + "\xC8\xBA" => "\xED\x9A\x90", + "\xC8\xBB" => "\xED\x9A\x94", + "\xC8\xBC" => "\xED\x9A\x9D", + "\xC8\xBD" => "\xED\x9A\x9F", + "\xC8\xBE" => "\xED\x9A\xA1", + "\xC8\xBF" => "\xED\x9A\xA8", + "\xC8\xC0" => "\xED\x9A\xAC", + "\xC8\xC1" => "\xED\x9A\xB0", + "\xC8\xC2" => "\xED\x9A\xB9", + "\xC8\xC3" => "\xED\x9A\xBB", + "\xC8\xC4" => "\xED\x9B\x84", + "\xC8\xC5" => "\xED\x9B\x85", + "\xC8\xC6" => "\xED\x9B\x88", + "\xC8\xC7" => "\xED\x9B\x8C", + "\xC8\xC8" => "\xED\x9B\x91", + "\xC8\xC9" => "\xED\x9B\x94", + "\xC8\xCA" => "\xED\x9B\x97", + "\xC8\xCB" => "\xED\x9B\x99", + "\xC8\xCC" => "\xED\x9B\xA0", + "\xC8\xCD" => "\xED\x9B\xA4", + "\xC8\xCE" => "\xED\x9B\xA8", + "\xC8\xCF" => "\xED\x9B\xB0", + "\xC8\xD0" => "\xED\x9B\xB5", + "\xC8\xD1" => "\xED\x9B\xBC", + "\xC8\xD2" => "\xED\x9B\xBD", + "\xC8\xD3" => "\xED\x9C\x80", + "\xC8\xD4" => "\xED\x9C\x84", + "\xC8\xD5" => "\xED\x9C\x91", + "\xC8\xD6" => "\xED\x9C\x98", + "\xC8\xD7" => "\xED\x9C\x99", + "\xC8\xD8" => "\xED\x9C\x9C", + "\xC8\xD9" => "\xED\x9C\xA0", + "\xC8\xDA" => "\xED\x9C\xA8", + "\xC8\xDB" => "\xED\x9C\xA9", + "\xC8\xDC" => "\xED\x9C\xAB", + "\xC8\xDD" => "\xED\x9C\xAD", + "\xC8\xDE" => "\xED\x9C\xB4", + "\xC8\xDF" => "\xED\x9C\xB5", + "\xC8\xE0" => "\xED\x9C\xB8", + "\xC8\xE1" => "\xED\x9C\xBC", + "\xC8\xE2" => "\xED\x9D\x84", + "\xC8\xE3" => "\xED\x9D\x87", + "\xC8\xE4" => "\xED\x9D\x89", + "\xC8\xE5" => "\xED\x9D\x90", + "\xC8\xE6" => "\xED\x9D\x91", + "\xC8\xE7" => "\xED\x9D\x94", + "\xC8\xE8" => "\xED\x9D\x96", + "\xC8\xE9" => "\xED\x9D\x97", + "\xC8\xEA" => "\xED\x9D\x98", + "\xC8\xEB" => "\xED\x9D\x99", + "\xC8\xEC" => "\xED\x9D\xA0", + "\xC8\xED" => "\xED\x9D\xA1", + "\xC8\xEE" => "\xED\x9D\xA3", + "\xC8\xEF" => "\xED\x9D\xA5", + "\xC8\xF0" => "\xED\x9D\xA9", + "\xC8\xF1" => "\xED\x9D\xAC", + "\xC8\xF2" => "\xED\x9D\xB0", + "\xC8\xF3" => "\xED\x9D\xB4", + "\xC8\xF4" => "\xED\x9D\xBC", + "\xC8\xF5" => "\xED\x9D\xBD", + "\xC8\xF6" => "\xED\x9E\x81", + "\xC8\xF7" => "\xED\x9E\x88", + "\xC8\xF8" => "\xED\x9E\x89", + "\xC8\xF9" => "\xED\x9E\x8C", + "\xC8\xFA" => "\xED\x9E\x90", + "\xC8\xFB" => "\xED\x9E\x98", + "\xC8\xFC" => "\xED\x9E\x99", + "\xC8\xFD" => "\xED\x9E\x9B", + "\xC8\xFE" => "\xED\x9E\x9D", + "\xCA\xA1" => "\xE4\xBC\xBD", + "\xCA\xA2" => "\xE4\xBD\xB3", + "\xCA\xA3" => "\xE5\x81\x87", + "\xCA\xA4" => "\xE5\x83\xB9", + "\xCA\xA5" => "\xE5\x8A\xA0", + "\xCA\xA6" => "\xE5\x8F\xAF", + "\xCA\xA7" => "\xE5\x91\xB5", + "\xCA\xA8" => "\xE5\x93\xA5", + "\xCA\xA9" => "\xE5\x98\x89", + "\xCA\xAA" => "\xE5\xAB\x81", + "\xCA\xAB" => "\xE5\xAE\xB6", + "\xCA\xAC" => "\xE6\x9A\x87", + "\xCA\xAD" => "\xE6\x9E\xB6", + "\xCA\xAE" => "\xE6\x9E\xB7", + "\xCA\xAF" => "\xE6\x9F\xAF", + "\xCA\xB0" => "\xE6\xAD\x8C", + "\xCA\xB1" => "\xE7\x8F\x82", + "\xCA\xB2" => "\xE7\x97\x82", + "\xCA\xB3" => "\xE7\xA8\xBC", + "\xCA\xB4" => "\xE8\x8B\x9B", + "\xCA\xB5" => "\xE8\x8C\x84", + "\xCA\xB6" => "\xE8\xA1\x97", + "\xCA\xB7" => "\xE8\xA2\x88", + "\xCA\xB8" => "\xE8\xA8\xB6", + "\xCA\xB9" => "\xE8\xB3\x88", + "\xCA\xBA" => "\xE8\xB7\x8F", + "\xCA\xBB" => "\xE8\xBB\xBB", + "\xCA\xBC" => "\xE8\xBF\xA6", + "\xCA\xBD" => "\xE9\xA7\x95", + "\xCA\xBE" => "\xE5\x88\xBB", + "\xCA\xBF" => "\xE5\x8D\xB4", + "\xCA\xC0" => "\xE5\x90\x84", + "\xCA\xC1" => "\xE6\x81\xAA", + "\xCA\xC2" => "\xE6\x85\xA4", + "\xCA\xC3" => "\xE6\xAE\xBC", + "\xCA\xC4" => "\xE7\x8F\x8F", + "\xCA\xC5" => "\xE8\x84\x9A", + "\xCA\xC6" => "\xE8\xA6\xBA", + "\xCA\xC7" => "\xE8\xA7\x92", + "\xCA\xC8" => "\xE9\x96\xA3", + "\xCA\xC9" => "\xE4\xBE\x83", + "\xCA\xCA" => "\xE5\x88\x8A", + "\xCA\xCB" => "\xE5\xA2\xBE", + "\xCA\xCC" => "\xE5\xA5\xB8", + "\xCA\xCD" => "\xE5\xA7\xA6", + "\xCA\xCE" => "\xE5\xB9\xB2", + "\xCA\xCF" => "\xE5\xB9\xB9", + "\xCA\xD0" => "\xE6\x87\x87", + "\xCA\xD1" => "\xE6\x8F\x80", + "\xCA\xD2" => "\xE6\x9D\x86", + "\xCA\xD3" => "\xE6\x9F\xAC", + "\xCA\xD4" => "\xE6\xA1\xBF", + "\xCA\xD5" => "\xE6\xBE\x97", + "\xCA\xD6" => "\xE7\x99\x8E", + "\xCA\xD7" => "\xE7\x9C\x8B", + "\xCA\xD8" => "\xE7\xA3\xB5", + "\xCA\xD9" => "\xE7\xA8\x88", + "\xCA\xDA" => "\xE7\xAB\xBF", + "\xCA\xDB" => "\xE7\xB0\xA1", + "\xCA\xDC" => "\xE8\x82\x9D", + "\xCA\xDD" => "\xE8\x89\xAE", + "\xCA\xDE" => "\xE8\x89\xB1", + "\xCA\xDF" => "\xE8\xAB\xAB", + "\xCA\xE0" => "\xE9\x96\x93", + "\xCA\xE1" => "\xE4\xB9\xAB", + "\xCA\xE2" => "\xE5\x96\x9D", + "\xCA\xE3" => "\xE6\x9B\xB7", + "\xCA\xE4" => "\xE6\xB8\xB4", + "\xCA\xE5" => "\xE7\xA2\xA3", + "\xCA\xE6" => "\xE7\xAB\xAD", + "\xCA\xE7" => "\xE8\x91\x9B", + "\xCA\xE8" => "\xE8\xA4\x90", + "\xCA\xE9" => "\xE8\x9D\x8E", + "\xCA\xEA" => "\xE9\x9E\xA8", + "\xCA\xEB" => "\xE5\x8B\x98", + "\xCA\xEC" => "\xE5\x9D\x8E", + "\xCA\xED" => "\xE5\xA0\xAA", + "\xCA\xEE" => "\xE5\xB5\x8C", + "\xCA\xEF" => "\xE6\x84\x9F", + "\xCA\xF0" => "\xE6\x86\xBE", + "\xCA\xF1" => "\xE6\x88\xA1", + "\xCA\xF2" => "\xE6\x95\xA2", + "\xCA\xF3" => "\xE6\x9F\x91", + "\xCA\xF4" => "\xE6\xA9\x84", + "\xCA\xF5" => "\xE6\xB8\x9B", + "\xCA\xF6" => "\xE7\x94\x98", + "\xCA\xF7" => "\xE7\x96\xB3", + "\xCA\xF8" => "\xE7\x9B\xA3", + "\xCA\xF9" => "\xE7\x9E\xB0", + "\xCA\xFA" => "\xE7\xB4\xBA", + "\xCA\xFB" => "\xE9\x82\xAF", + "\xCA\xFC" => "\xE9\x91\x91", + "\xCA\xFD" => "\xE9\x91\x92", + "\xCA\xFE" => "\xE9\xBE\x95", + "\xCB\xA1" => "\xE5\x8C\xA3", + "\xCB\xA2" => "\xE5\xB2\xAC", + "\xCB\xA3" => "\xE7\x94\xB2", + "\xCB\xA4" => "\xE8\x83\x9B", + "\xCB\xA5" => "\xE9\x89\x80", + "\xCB\xA6" => "\xE9\x96\x98", + "\xCB\xA7" => "\xE5\x89\x9B", + "\xCB\xA8" => "\xE5\xA0\x88", + "\xCB\xA9" => "\xE5\xA7\x9C", + "\xCB\xAA" => "\xE5\xB2\xA1", + "\xCB\xAB" => "\xE5\xB4\x97", + "\xCB\xAC" => "\xE5\xBA\xB7", + "\xCB\xAD" => "\xE5\xBC\xBA", + "\xCB\xAE" => "\xE5\xBD\x8A", + "\xCB\xAF" => "\xE6\x85\xB7", + "\xCB\xB0" => "\xE6\xB1\x9F", + "\xCB\xB1" => "\xE7\x95\xBA", + "\xCB\xB2" => "\xE7\x96\x86", + "\xCB\xB3" => "\xE7\xB3\xA0", + "\xCB\xB4" => "\xE7\xB5\xB3", + "\xCB\xB5" => "\xE7\xB6\xB1", + "\xCB\xB6" => "\xE7\xBE\x8C", + "\xCB\xB7" => "\xE8\x85\x94", + "\xCB\xB8" => "\xE8\x88\xA1", + "\xCB\xB9" => "\xE8\x96\x91", + "\xCB\xBA" => "\xE8\xA5\x81", + "\xCB\xBB" => "\xE8\xAC\x9B", + "\xCB\xBC" => "\xE9\x8B\xBC", + "\xCB\xBD" => "\xE9\x99\x8D", + "\xCB\xBE" => "\xE9\xB1\x87", + "\xCB\xBF" => "\xE4\xBB\x8B", + "\xCB\xC0" => "\xE4\xBB\xB7", + "\xCB\xC1" => "\xE5\x80\x8B", + "\xCB\xC2" => "\xE5\x87\xB1", + "\xCB\xC3" => "\xE5\xA1\x8F", + "\xCB\xC4" => "\xE6\x84\xB7", + "\xCB\xC5" => "\xE6\x84\xBE", + "\xCB\xC6" => "\xE6\x85\xA8", + "\xCB\xC7" => "\xE6\x94\xB9", + "\xCB\xC8" => "\xE6\xA7\xAA", + "\xCB\xC9" => "\xE6\xBC\x91", + "\xCB\xCA" => "\xE7\x96\xA5", + "\xCB\xCB" => "\xE7\x9A\x86", + "\xCB\xCC" => "\xE7\x9B\x96", + "\xCB\xCD" => "\xE7\xAE\x87", + "\xCB\xCE" => "\xE8\x8A\xA5", + "\xCB\xCF" => "\xE8\x93\x8B", + "\xCB\xD0" => "\xEF\xA4\x80", + "\xCB\xD1" => "\xE9\x8E\xA7", + "\xCB\xD2" => "\xE9\x96\x8B", + "\xCB\xD3" => "\xE5\x96\x80", + "\xCB\xD4" => "\xE5\xAE\xA2", + "\xCB\xD5" => "\xE5\x9D\x91", + "\xCB\xD6" => "\xEF\xA4\x81", + "\xCB\xD7" => "\xE7\xB2\xB3", + "\xCB\xD8" => "\xE7\xBE\xB9", + "\xCB\xD9" => "\xE9\x86\xB5", + "\xCB\xDA" => "\xE5\x80\xA8", + "\xCB\xDB" => "\xE5\x8E\xBB", + "\xCB\xDC" => "\xE5\xB1\x85", + "\xCB\xDD" => "\xE5\xB7\xA8", + "\xCB\xDE" => "\xE6\x8B\x92", + "\xCB\xDF" => "\xE6\x8D\xAE", + "\xCB\xE0" => "\xE6\x93\x9A", + "\xCB\xE1" => "\xE6\x93\xA7", + "\xCB\xE2" => "\xE6\xB8\xA0", + "\xCB\xE3" => "\xE7\x82\xAC", + "\xCB\xE4" => "\xE7\xA5\x9B", + "\xCB\xE5" => "\xE8\xB7\x9D", + "\xCB\xE6" => "\xE8\xB8\x9E", + "\xCB\xE7" => "\xEF\xA4\x82", + "\xCB\xE8" => "\xE9\x81\xBD", + "\xCB\xE9" => "\xE9\x89\x85", + "\xCB\xEA" => "\xE9\x8B\xB8", + "\xCB\xEB" => "\xE4\xB9\xBE", + "\xCB\xEC" => "\xE4\xBB\xB6", + "\xCB\xED" => "\xE5\x81\xA5", + "\xCB\xEE" => "\xE5\xB7\xBE", + "\xCB\xEF" => "\xE5\xBB\xBA", + "\xCB\xF0" => "\xE6\x84\x86", + "\xCB\xF1" => "\xE6\xA5\x97", + "\xCB\xF2" => "\xE8\x85\xB1", + "\xCB\xF3" => "\xE8\x99\x94", + "\xCB\xF4" => "\xE8\xB9\x87", + "\xCB\xF5" => "\xE9\x8D\xB5", + "\xCB\xF6" => "\xE9\xA8\xAB", + "\xCB\xF7" => "\xE4\xB9\x9E", + "\xCB\xF8" => "\xE5\x82\x91", + "\xCB\xF9" => "\xE6\x9D\xB0", + "\xCB\xFA" => "\xE6\xA1\x80", + "\xCB\xFB" => "\xE5\x84\x89", + "\xCB\xFC" => "\xE5\x8A\x8D", + "\xCB\xFD" => "\xE5\x8A\x92", + "\xCB\xFE" => "\xE6\xAA\xA2", + "\xCC\xA1" => "\xE7\x9E\xBC", + "\xCC\xA2" => "\xE9\x88\x90", + "\xCC\xA3" => "\xE9\xBB\x94", + "\xCC\xA4" => "\xE5\x8A\xAB", + "\xCC\xA5" => "\xE6\x80\xAF", + "\xCC\xA6" => "\xE8\xBF\xB2", + "\xCC\xA7" => "\xE5\x81\x88", + "\xCC\xA8" => "\xE6\x86\xA9", + "\xCC\xA9" => "\xE6\x8F\xAD", + "\xCC\xAA" => "\xE6\x93\x8A", + "\xCC\xAB" => "\xE6\xA0\xBC", + "\xCC\xAC" => "\xE6\xAA\x84", + "\xCC\xAD" => "\xE6\xBF\x80", + "\xCC\xAE" => "\xE8\x86\x88", + "\xCC\xAF" => "\xE8\xA6\xA1", + "\xCC\xB0" => "\xE9\x9A\x94", + "\xCC\xB1" => "\xE5\xA0\x85", + "\xCC\xB2" => "\xE7\x89\xBD", + "\xCC\xB3" => "\xE7\x8A\xAC", + "\xCC\xB4" => "\xE7\x94\x84", + "\xCC\xB5" => "\xE7\xB5\xB9", + "\xCC\xB6" => "\xE7\xB9\xAD", + "\xCC\xB7" => "\xE8\x82\xA9", + "\xCC\xB8" => "\xE8\xA6\x8B", + "\xCC\xB9" => "\xE8\xAD\xB4", + "\xCC\xBA" => "\xE9\x81\xA3", + "\xCC\xBB" => "\xE9\xB5\x91", + "\xCC\xBC" => "\xE6\x8A\x89", + "\xCC\xBD" => "\xE6\xB1\xBA", + "\xCC\xBE" => "\xE6\xBD\x94", + "\xCC\xBF" => "\xE7\xB5\x90", + "\xCC\xC0" => "\xE7\xBC\xBA", + "\xCC\xC1" => "\xE8\xA8\xA3", + "\xCC\xC2" => "\xE5\x85\xBC", + "\xCC\xC3" => "\xE6\x85\x8A", + "\xCC\xC4" => "\xE7\xAE\x9D", + "\xCC\xC5" => "\xE8\xAC\x99", + "\xCC\xC6" => "\xE9\x89\x97", + "\xCC\xC7" => "\xE9\x8E\x8C", + "\xCC\xC8" => "\xE4\xBA\xAC", + "\xCC\xC9" => "\xE4\xBF\x93", + "\xCC\xCA" => "\xE5\x80\x9E", + "\xCC\xCB" => "\xE5\x82\xBE", + "\xCC\xCC" => "\xE5\x84\x86", + "\xCC\xCD" => "\xE5\x8B\x81", + "\xCC\xCE" => "\xE5\x8B\x8D", + "\xCC\xCF" => "\xE5\x8D\xBF", + "\xCC\xD0" => "\xE5\x9D\xB0", + "\xCC\xD1" => "\xE5\xA2\x83", + "\xCC\xD2" => "\xE5\xBA\x9A", + "\xCC\xD3" => "\xE5\xBE\x91", + "\xCC\xD4" => "\xE6\x85\xB6", + "\xCC\xD5" => "\xE6\x86\xAC", + "\xCC\xD6" => "\xE6\x93\x8E", + "\xCC\xD7" => "\xE6\x95\xAC", + "\xCC\xD8" => "\xE6\x99\xAF", + "\xCC\xD9" => "\xE6\x9A\xBB", + "\xCC\xDA" => "\xE6\x9B\xB4", + "\xCC\xDB" => "\xE6\xA2\x97", + "\xCC\xDC" => "\xE6\xB6\x87", + "\xCC\xDD" => "\xE7\x82\x85", + "\xCC\xDE" => "\xE7\x83\xB1", + "\xCC\xDF" => "\xE7\x92\x9F", + "\xCC\xE0" => "\xE7\x92\xA5", + "\xCC\xE1" => "\xE7\x93\x8A", + "\xCC\xE2" => "\xE7\x97\x99", + "\xCC\xE3" => "\xE7\xA1\xAC", + "\xCC\xE4" => "\xE7\xA3\xAC", + "\xCC\xE5" => "\xE7\xAB\x9F", + "\xCC\xE6" => "\xE7\xAB\xB6", + "\xCC\xE7" => "\xE7\xB5\x85", + "\xCC\xE8" => "\xE7\xB6\x93", + "\xCC\xE9" => "\xE8\x80\x95", + "\xCC\xEA" => "\xE8\x80\xBF", + "\xCC\xEB" => "\xE8\x84\x9B", + "\xCC\xEC" => "\xE8\x8E\x96", + "\xCC\xED" => "\xE8\xAD\xA6", + "\xCC\xEE" => "\xE8\xBC\x95", + "\xCC\xEF" => "\xE9\x80\x95", + "\xCC\xF0" => "\xE9\x8F\xA1", + "\xCC\xF1" => "\xE9\xA0\x83", + "\xCC\xF2" => "\xE9\xA0\xB8", + "\xCC\xF3" => "\xE9\xA9\x9A", + "\xCC\xF4" => "\xE9\xAF\xA8", + "\xCC\xF5" => "\xE4\xBF\x82", + "\xCC\xF6" => "\xE5\x95\x93", + "\xCC\xF7" => "\xE5\xA0\xBA", + "\xCC\xF8" => "\xE5\xA5\x91", + "\xCC\xF9" => "\xE5\xAD\xA3", + "\xCC\xFA" => "\xE5\xB1\x86", + "\xCC\xFB" => "\xE6\x82\xB8", + "\xCC\xFC" => "\xE6\x88\x92", + "\xCC\xFD" => "\xE6\xA1\x82", + "\xCC\xFE" => "\xE6\xA2\xB0", + "\xCD\xA1" => "\xE6\xA3\xA8", + "\xCD\xA2" => "\xE6\xBA\xAA", + "\xCD\xA3" => "\xE7\x95\x8C", + "\xCD\xA4" => "\xE7\x99\xB8", + "\xCD\xA5" => "\xE7\xA3\x8E", + "\xCD\xA6" => "\xE7\xA8\xBD", + "\xCD\xA7" => "\xE7\xB3\xBB", + "\xCD\xA8" => "\xE7\xB9\xAB", + "\xCD\xA9" => "\xE7\xB9\xBC", + "\xCD\xAA" => "\xE8\xA8\x88", + "\xCD\xAB" => "\xE8\xAA\xA1", + "\xCD\xAC" => "\xE8\xB0\xBF", + "\xCD\xAD" => "\xE9\x9A\x8E", + "\xCD\xAE" => "\xE9\xB7\x84", + "\xCD\xAF" => "\xE5\x8F\xA4", + "\xCD\xB0" => "\xE5\x8F\xA9", + "\xCD\xB1" => "\xE5\x91\x8A", + "\xCD\xB2" => "\xE5\x91\xB1", + "\xCD\xB3" => "\xE5\x9B\xBA", + "\xCD\xB4" => "\xE5\xA7\x91", + "\xCD\xB5" => "\xE5\xAD\xA4", + "\xCD\xB6" => "\xE5\xB0\xBB", + "\xCD\xB7" => "\xE5\xBA\xAB", + "\xCD\xB8" => "\xE6\x8B\xB7", + "\xCD\xB9" => "\xE6\x94\xB7", + "\xCD\xBA" => "\xE6\x95\x85", + "\xCD\xBB" => "\xE6\x95\xB2", + "\xCD\xBC" => "\xE6\x9A\xA0", + "\xCD\xBD" => "\xE6\x9E\xAF", + "\xCD\xBE" => "\xE6\xA7\x81", + "\xCD\xBF" => "\xE6\xB2\xBD", + "\xCD\xC0" => "\xE7\x97\xBC", + "\xCD\xC1" => "\xE7\x9A\x90", + "\xCD\xC2" => "\xE7\x9D\xBE", + "\xCD\xC3" => "\xE7\xA8\xBF", + "\xCD\xC4" => "\xE7\xBE\x94", + "\xCD\xC5" => "\xE8\x80\x83", + "\xCD\xC6" => "\xE8\x82\xA1", + "\xCD\xC7" => "\xE8\x86\x8F", + "\xCD\xC8" => "\xE8\x8B\xA6", + "\xCD\xC9" => "\xE8\x8B\xBD", + "\xCD\xCA" => "\xE8\x8F\xB0", + "\xCD\xCB" => "\xE8\x97\x81", + "\xCD\xCC" => "\xE8\xA0\xB1", + "\xCD\xCD" => "\xE8\xA2\xB4", + "\xCD\xCE" => "\xE8\xAA\xA5", + "\xCD\xCF" => "\xEF\xA4\x83", + "\xCD\xD0" => "\xE8\xBE\x9C", + "\xCD\xD1" => "\xE9\x8C\xAE", + "\xCD\xD2" => "\xE9\x9B\x87", + "\xCD\xD3" => "\xE9\xA1\xA7", + "\xCD\xD4" => "\xE9\xAB\x98", + "\xCD\xD5" => "\xE9\xBC\x93", + "\xCD\xD6" => "\xE5\x93\xAD", + "\xCD\xD7" => "\xE6\x96\x9B", + "\xCD\xD8" => "\xE6\x9B\xB2", + "\xCD\xD9" => "\xE6\xA2\x8F", + "\xCD\xDA" => "\xE7\xA9\x80", + "\xCD\xDB" => "\xE8\xB0\xB7", + "\xCD\xDC" => "\xE9\xB5\xA0", + "\xCD\xDD" => "\xE5\x9B\xB0", + "\xCD\xDE" => "\xE5\x9D\xA4", + "\xCD\xDF" => "\xE5\xB4\x91", + "\xCD\xE0" => "\xE6\x98\x86", + "\xCD\xE1" => "\xE6\xA2\xB1", + "\xCD\xE2" => "\xE6\xA3\x8D", + "\xCD\xE3" => "\xE6\xBB\xBE", + "\xCD\xE4" => "\xE7\x90\xA8", + "\xCD\xE5" => "\xE8\xA2\x9E", + "\xCD\xE6" => "\xE9\xAF\xA4", + "\xCD\xE7" => "\xE6\xB1\xA8", + "\xCD\xE8" => "\xEF\xA4\x84", + "\xCD\xE9" => "\xE9\xAA\xA8", + "\xCD\xEA" => "\xE4\xBE\x9B", + "\xCD\xEB" => "\xE5\x85\xAC", + "\xCD\xEC" => "\xE5\x85\xB1", + "\xCD\xED" => "\xE5\x8A\x9F", + "\xCD\xEE" => "\xE5\xAD\x94", + "\xCD\xEF" => "\xE5\xB7\xA5", + "\xCD\xF0" => "\xE6\x81\x90", + "\xCD\xF1" => "\xE6\x81\xAD", + "\xCD\xF2" => "\xE6\x8B\xB1", + "\xCD\xF3" => "\xE6\x8E\xA7", + "\xCD\xF4" => "\xE6\x94\xBB", + "\xCD\xF5" => "\xE7\x8F\x99", + "\xCD\xF6" => "\xE7\xA9\xBA", + "\xCD\xF7" => "\xE8\x9A\xA3", + "\xCD\xF8" => "\xE8\xB2\xA2", + "\xCD\xF9" => "\xE9\x9E\x8F", + "\xCD\xFA" => "\xE4\xB8\xB2", + "\xCD\xFB" => "\xE5\xAF\xA1", + "\xCD\xFC" => "\xE6\x88\x88", + "\xCD\xFD" => "\xE6\x9E\x9C", + "\xCD\xFE" => "\xE7\x93\x9C", + "\xCE\xA1" => "\xE7\xA7\x91", + "\xCE\xA2" => "\xE8\x8F\x93", + "\xCE\xA3" => "\xE8\xAA\x87", + "\xCE\xA4" => "\xE8\xAA\xB2", + "\xCE\xA5" => "\xE8\xB7\xA8", + "\xCE\xA6" => "\xE9\x81\x8E", + "\xCE\xA7" => "\xE9\x8D\x8B", + "\xCE\xA8" => "\xE9\xA1\x86", + "\xCE\xA9" => "\xE5\xBB\x93", + "\xCE\xAA" => "\xE6\xA7\xA8", + "\xCE\xAB" => "\xE8\x97\xBF", + "\xCE\xAC" => "\xE9\x83\xAD", + "\xCE\xAD" => "\xEF\xA4\x85", + "\xCE\xAE" => "\xE5\x86\xA0", + "\xCE\xAF" => "\xE5\xAE\x98", + "\xCE\xB0" => "\xE5\xAF\xAC", + "\xCE\xB1" => "\xE6\x85\xA3", + "\xCE\xB2" => "\xE6\xA3\xBA", + "\xCE\xB3" => "\xE6\xAC\xBE", + "\xCE\xB4" => "\xE7\x81\x8C", + "\xCE\xB5" => "\xE7\x90\xAF", + "\xCE\xB6" => "\xE7\x93\x98", + "\xCE\xB7" => "\xE7\xAE\xA1", + "\xCE\xB8" => "\xE7\xBD\x90", + "\xCE\xB9" => "\xE8\x8F\x85", + "\xCE\xBA" => "\xE8\xA7\x80", + "\xCE\xBB" => "\xE8\xB2\xAB", + "\xCE\xBC" => "\xE9\x97\x9C", + "\xCE\xBD" => "\xE9\xA4\xA8", + "\xCE\xBE" => "\xE5\x88\xAE", + "\xCE\xBF" => "\xE6\x81\x9D", + "\xCE\xC0" => "\xE6\x8B\xAC", + "\xCE\xC1" => "\xE9\x80\x82", + "\xCE\xC2" => "\xE4\xBE\x8A", + "\xCE\xC3" => "\xE5\x85\x89", + "\xCE\xC4" => "\xE5\x8C\xA1", + "\xCE\xC5" => "\xE5\xA3\x99", + "\xCE\xC6" => "\xE5\xBB\xA3", + "\xCE\xC7" => "\xE6\x9B\xA0", + "\xCE\xC8" => "\xE6\xB4\xB8", + "\xCE\xC9" => "\xE7\x82\x9A", + "\xCE\xCA" => "\xE7\x8B\x82", + "\xCE\xCB" => "\xE7\x8F\x96", + "\xCE\xCC" => "\xE7\xAD\x90", + "\xCE\xCD" => "\xE8\x83\xB1", + "\xCE\xCE" => "\xE9\x91\x9B", + "\xCE\xCF" => "\xE5\x8D\xA6", + "\xCE\xD0" => "\xE6\x8E\x9B", + "\xCE\xD1" => "\xE7\xBD\xAB", + "\xCE\xD2" => "\xE4\xB9\x96", + "\xCE\xD3" => "\xE5\x82\x80", + "\xCE\xD4" => "\xE5\xA1\x8A", + "\xCE\xD5" => "\xE5\xA3\x9E", + "\xCE\xD6" => "\xE6\x80\xAA", + "\xCE\xD7" => "\xE6\x84\xA7", + "\xCE\xD8" => "\xE6\x8B\x90", + "\xCE\xD9" => "\xE6\xA7\x90", + "\xCE\xDA" => "\xE9\xAD\x81", + "\xCE\xDB" => "\xE5\xAE\x8F", + "\xCE\xDC" => "\xE7\xB4\x98", + "\xCE\xDD" => "\xE8\x82\xB1", + "\xCE\xDE" => "\xE8\xBD\x9F", + "\xCE\xDF" => "\xE4\xBA\xA4", + "\xCE\xE0" => "\xE5\x83\x91", + "\xCE\xE1" => "\xE5\x92\xAC", + "\xCE\xE2" => "\xE5\x96\xAC", + "\xCE\xE3" => "\xE5\xAC\x8C", + "\xCE\xE4" => "\xE5\xB6\xA0", + "\xCE\xE5" => "\xE5\xB7\xA7", + "\xCE\xE6" => "\xE6\x94\xAA", + "\xCE\xE7" => "\xE6\x95\x8E", + "\xCE\xE8" => "\xE6\xA0\xA1", + "\xCE\xE9" => "\xE6\xA9\x8B", + "\xCE\xEA" => "\xE7\x8B\xA1", + "\xCE\xEB" => "\xE7\x9A\x8E", + "\xCE\xEC" => "\xE7\x9F\xAF", + "\xCE\xED" => "\xE7\xB5\x9E", + "\xCE\xEE" => "\xE7\xBF\xB9", + "\xCE\xEF" => "\xE8\x86\xA0", + "\xCE\xF0" => "\xE8\x95\x8E", + "\xCE\xF1" => "\xE8\x9B\x9F", + "\xCE\xF2" => "\xE8\xBC\x83", + "\xCE\xF3" => "\xE8\xBD\x8E", + "\xCE\xF4" => "\xE9\x83\x8A", + "\xCE\xF5" => "\xE9\xA4\x83", + "\xCE\xF6" => "\xE9\xA9\x95", + "\xCE\xF7" => "\xE9\xAE\xAB", + "\xCE\xF8" => "\xE4\xB8\x98", + "\xCE\xF9" => "\xE4\xB9\x85", + "\xCE\xFA" => "\xE4\xB9\x9D", + "\xCE\xFB" => "\xE4\xBB\x87", + "\xCE\xFC" => "\xE4\xBF\xB1", + "\xCE\xFD" => "\xE5\x85\xB7", + "\xCE\xFE" => "\xE5\x8B\xBE", + "\xCF\xA1" => "\xE5\x8D\x80", + "\xCF\xA2" => "\xE5\x8F\xA3", + "\xCF\xA3" => "\xE5\x8F\xA5", + "\xCF\xA4" => "\xE5\x92\x8E", + "\xCF\xA5" => "\xE5\x98\x94", + "\xCF\xA6" => "\xE5\x9D\xB5", + "\xCF\xA7" => "\xE5\x9E\xA2", + "\xCF\xA8" => "\xE5\xAF\x87", + "\xCF\xA9" => "\xE5\xB6\x87", + "\xCF\xAA" => "\xE5\xBB\x90", + "\xCF\xAB" => "\xE6\x87\xBC", + "\xCF\xAC" => "\xE6\x8B\x98", + "\xCF\xAD" => "\xE6\x95\x91", + "\xCF\xAE" => "\xE6\x9E\xB8", + "\xCF\xAF" => "\xE6\x9F\xA9", + "\xCF\xB0" => "\xE6\xA7\x8B", + "\xCF\xB1" => "\xE6\xAD\x90", + "\xCF\xB2" => "\xE6\xAF\x86", + "\xCF\xB3" => "\xE6\xAF\xAC", + "\xCF\xB4" => "\xE6\xB1\x82", + "\xCF\xB5" => "\xE6\xBA\x9D", + "\xCF\xB6" => "\xE7\x81\xB8", + "\xCF\xB7" => "\xE7\x8B\x97", + "\xCF\xB8" => "\xE7\x8E\x96", + "\xCF\xB9" => "\xE7\x90\x83", + "\xCF\xBA" => "\xE7\x9E\xBF", + "\xCF\xBB" => "\xE7\x9F\xA9", + "\xCF\xBC" => "\xE7\xA9\xB6", + "\xCF\xBD" => "\xE7\xB5\xBF", + "\xCF\xBE" => "\xE8\x80\x89", + "\xCF\xBF" => "\xE8\x87\xBC", + "\xCF\xC0" => "\xE8\x88\x85", + "\xCF\xC1" => "\xE8\x88\x8A", + "\xCF\xC2" => "\xE8\x8B\x9F", + "\xCF\xC3" => "\xE8\xA1\xA2", + "\xCF\xC4" => "\xE8\xAC\xB3", + "\xCF\xC5" => "\xE8\xB3\xBC", + "\xCF\xC6" => "\xE8\xBB\x80", + "\xCF\xC7" => "\xE9\x80\x91", + "\xCF\xC8" => "\xE9\x82\xB1", + "\xCF\xC9" => "\xE9\x89\xA4", + "\xCF\xCA" => "\xE9\x8A\xB6", + "\xCF\xCB" => "\xE9\xA7\x92", + "\xCF\xCC" => "\xE9\xA9\x85", + "\xCF\xCD" => "\xE9\xB3\xA9", + "\xCF\xCE" => "\xE9\xB7\x97", + "\xCF\xCF" => "\xE9\xBE\x9C", + "\xCF\xD0" => "\xE5\x9C\x8B", + "\xCF\xD1" => "\xE5\xB1\x80", + "\xCF\xD2" => "\xE8\x8F\x8A", + "\xCF\xD3" => "\xE9\x9E\xA0", + "\xCF\xD4" => "\xE9\x9E\xAB", + "\xCF\xD5" => "\xE9\xBA\xB4", + "\xCF\xD6" => "\xE5\x90\x9B", + "\xCF\xD7" => "\xE7\xAA\x98", + "\xCF\xD8" => "\xE7\xBE\xA4", + "\xCF\xD9" => "\xE8\xA3\x99", + "\xCF\xDA" => "\xE8\xBB\x8D", + "\xCF\xDB" => "\xE9\x83\xA1", + "\xCF\xDC" => "\xE5\xA0\x80", + "\xCF\xDD" => "\xE5\xB1\x88", + "\xCF\xDE" => "\xE6\x8E\x98", + "\xCF\xDF" => "\xE7\xAA\x9F", + "\xCF\xE0" => "\xE5\xAE\xAE", + "\xCF\xE1" => "\xE5\xBC\x93", + "\xCF\xE2" => "\xE7\xA9\xB9", + "\xCF\xE3" => "\xE7\xAA\xAE", + "\xCF\xE4" => "\xE8\x8A\x8E", + "\xCF\xE5" => "\xE8\xBA\xAC", + "\xCF\xE6" => "\xE5\x80\xA6", + "\xCF\xE7" => "\xE5\x88\xB8", + "\xCF\xE8" => "\xE5\x8B\xB8", + "\xCF\xE9" => "\xE5\x8D\xB7", + "\xCF\xEA" => "\xE5\x9C\x88", + "\xCF\xEB" => "\xE6\x8B\xB3", + "\xCF\xEC" => "\xE6\x8D\xB2", + "\xCF\xED" => "\xE6\xAC\x8A", + "\xCF\xEE" => "\xE6\xB7\x83", + "\xCF\xEF" => "\xE7\x9C\xB7", + "\xCF\xF0" => "\xE5\x8E\xA5", + "\xCF\xF1" => "\xE7\x8D\x97", + "\xCF\xF2" => "\xE8\x95\xA8", + "\xCF\xF3" => "\xE8\xB9\xB6", + "\xCF\xF4" => "\xE9\x97\x95", + "\xCF\xF5" => "\xE6\x9C\xBA", + "\xCF\xF6" => "\xE6\xAB\x83", + "\xCF\xF7" => "\xE6\xBD\xB0", + "\xCF\xF8" => "\xE8\xA9\xAD", + "\xCF\xF9" => "\xE8\xBB\x8C", + "\xCF\xFA" => "\xE9\xA5\x8B", + "\xCF\xFB" => "\xEF\xA4\x86", + "\xCF\xFC" => "\xE6\x99\xB7", + "\xCF\xFD" => "\xE6\xAD\xB8", + "\xCF\xFE" => "\xE8\xB2\xB4", + "\xD0\xA1" => "\xE9\xAC\xBC", + "\xD0\xA2" => "\xEF\xA4\x87", + "\xD0\xA3" => "\xE5\x8F\xAB", + "\xD0\xA4" => "\xE5\x9C\xAD", + "\xD0\xA5" => "\xE5\xA5\x8E", + "\xD0\xA6" => "\xE6\x8F\x86", + "\xD0\xA7" => "\xE6\xA7\xBB", + "\xD0\xA8" => "\xE7\x8F\xAA", + "\xD0\xA9" => "\xE7\xA1\x85", + "\xD0\xAA" => "\xE7\xAA\xBA", + "\xD0\xAB" => "\xE7\xAB\x85", + "\xD0\xAC" => "\xE7\xB3\xBE", + "\xD0\xAD" => "\xE8\x91\xB5", + "\xD0\xAE" => "\xE8\xA6\x8F", + "\xD0\xAF" => "\xE8\xB5\xB3", + "\xD0\xB0" => "\xE9\x80\xB5", + "\xD0\xB1" => "\xE9\x96\xA8", + "\xD0\xB2" => "\xE5\x8B\xBB", + "\xD0\xB3" => "\xE5\x9D\x87", + "\xD0\xB4" => "\xE7\x95\x87", + "\xD0\xB5" => "\xE7\xAD\xA0", + "\xD0\xB6" => "\xE8\x8F\x8C", + "\xD0\xB7" => "\xE9\x88\x9E", + "\xD0\xB8" => "\xEF\xA4\x88", + "\xD0\xB9" => "\xE6\xA9\x98", + "\xD0\xBA" => "\xE5\x85\x8B", + "\xD0\xBB" => "\xE5\x89\x8B", + "\xD0\xBC" => "\xE5\x8A\x87", + "\xD0\xBD" => "\xE6\x88\x9F", + "\xD0\xBE" => "\xE6\xA3\x98", + "\xD0\xBF" => "\xE6\xA5\xB5", + "\xD0\xC0" => "\xE9\x9A\x99", + "\xD0\xC1" => "\xE5\x83\x85", + "\xD0\xC2" => "\xE5\x8A\xA4", + "\xD0\xC3" => "\xE5\x8B\xA4", + "\xD0\xC4" => "\xE6\x87\x83", + "\xD0\xC5" => "\xE6\x96\xA4", + "\xD0\xC6" => "\xE6\xA0\xB9", + "\xD0\xC7" => "\xE6\xA7\xBF", + "\xD0\xC8" => "\xE7\x91\xBE", + "\xD0\xC9" => "\xE7\xAD\x8B", + "\xD0\xCA" => "\xE8\x8A\xB9", + "\xD0\xCB" => "\xE8\x8F\xAB", + "\xD0\xCC" => "\xE8\xA6\xB2", + "\xD0\xCD" => "\xE8\xAC\xB9", + "\xD0\xCE" => "\xE8\xBF\x91", + "\xD0\xCF" => "\xE9\xA5\x89", + "\xD0\xD0" => "\xEF\xA4\x89", + "\xD0\xD1" => "\xE4\xBB\x8A", + "\xD0\xD2" => "\xE5\xA6\x97", + "\xD0\xD3" => "\xE6\x93\x92", + "\xD0\xD4" => "\xE6\x98\x91", + "\xD0\xD5" => "\xE6\xAA\x8E", + "\xD0\xD6" => "\xE7\x90\xB4", + "\xD0\xD7" => "\xE7\xA6\x81", + "\xD0\xD8" => "\xE7\xA6\xBD", + "\xD0\xD9" => "\xE8\x8A\xA9", + "\xD0\xDA" => "\xE8\xA1\xBE", + "\xD0\xDB" => "\xE8\xA1\xBF", + "\xD0\xDC" => "\xE8\xA5\x9F", + "\xD0\xDD" => "\xEF\xA4\x8A", + "\xD0\xDE" => "\xE9\x8C\xA6", + "\xD0\xDF" => "\xE4\xBC\x8B", + "\xD0\xE0" => "\xE5\x8F\x8A", + "\xD0\xE1" => "\xE6\x80\xA5", + "\xD0\xE2" => "\xE6\x89\xB1", + "\xD0\xE3" => "\xE6\xB1\xB2", + "\xD0\xE4" => "\xE7\xB4\x9A", + "\xD0\xE5" => "\xE7\xB5\xA6", + "\xD0\xE6" => "\xE4\xBA\x98", + "\xD0\xE7" => "\xE5\x85\xA2", + "\xD0\xE8" => "\xE7\x9F\x9C", + "\xD0\xE9" => "\xE8\x82\xAF", + "\xD0\xEA" => "\xE4\xBC\x81", + "\xD0\xEB" => "\xE4\xBC\x8E", + "\xD0\xEC" => "\xE5\x85\xB6", + "\xD0\xED" => "\xE5\x86\x80", + "\xD0\xEE" => "\xE5\x97\x9C", + "\xD0\xEF" => "\xE5\x99\xA8", + "\xD0\xF0" => "\xE5\x9C\xBB", + "\xD0\xF1" => "\xE5\x9F\xBA", + "\xD0\xF2" => "\xE5\x9F\xBC", + "\xD0\xF3" => "\xE5\xA4\x94", + "\xD0\xF4" => "\xE5\xA5\x87", + "\xD0\xF5" => "\xE5\xA6\x93", + "\xD0\xF6" => "\xE5\xAF\x84", + "\xD0\xF7" => "\xE5\xB2\x90", + "\xD0\xF8" => "\xE5\xB4\x8E", + "\xD0\xF9" => "\xE5\xB7\xB1", + "\xD0\xFA" => "\xE5\xB9\xBE", + "\xD0\xFB" => "\xE5\xBF\x8C", + "\xD0\xFC" => "\xE6\x8A\x80", + "\xD0\xFD" => "\xE6\x97\x97", + "\xD0\xFE" => "\xE6\x97\xA3", + "\xD1\xA1" => "\xE6\x9C\x9E", + "\xD1\xA2" => "\xE6\x9C\x9F", + "\xD1\xA3" => "\xE6\x9D\x9E", + "\xD1\xA4" => "\xE6\xA3\x8B", + "\xD1\xA5" => "\xE6\xA3\x84", + "\xD1\xA6" => "\xE6\xA9\x9F", + "\xD1\xA7" => "\xE6\xAC\xBA", + "\xD1\xA8" => "\xE6\xB0\xA3", + "\xD1\xA9" => "\xE6\xB1\xBD", + "\xD1\xAA" => "\xE6\xB2\x82", + "\xD1\xAB" => "\xE6\xB7\x87", + "\xD1\xAC" => "\xE7\x8E\x98", + "\xD1\xAD" => "\xE7\x90\xA6", + "\xD1\xAE" => "\xE7\x90\xAA", + "\xD1\xAF" => "\xE7\x92\x82", + "\xD1\xB0" => "\xE7\x92\xA3", + "\xD1\xB1" => "\xE7\x95\xB8", + "\xD1\xB2" => "\xE7\x95\xBF", + "\xD1\xB3" => "\xE7\xA2\x81", + "\xD1\xB4" => "\xE7\xA3\xAF", + "\xD1\xB5" => "\xE7\xA5\x81", + "\xD1\xB6" => "\xE7\xA5\x87", + "\xD1\xB7" => "\xE7\xA5\x88", + "\xD1\xB8" => "\xE7\xA5\xBA", + "\xD1\xB9" => "\xE7\xAE\x95", + "\xD1\xBA" => "\xE7\xB4\x80", + "\xD1\xBB" => "\xE7\xB6\xBA", + "\xD1\xBC" => "\xE7\xBE\x88", + "\xD1\xBD" => "\xE8\x80\x86", + "\xD1\xBE" => "\xE8\x80\xAD", + "\xD1\xBF" => "\xE8\x82\x8C", + "\xD1\xC0" => "\xE8\xA8\x98", + "\xD1\xC1" => "\xE8\xAD\x8F", + "\xD1\xC2" => "\xE8\xB1\x88", + "\xD1\xC3" => "\xE8\xB5\xB7", + "\xD1\xC4" => "\xE9\x8C\xA1", + "\xD1\xC5" => "\xE9\x8C\xA4", + "\xD1\xC6" => "\xE9\xA3\xA2", + "\xD1\xC7" => "\xE9\xA5\x91", + "\xD1\xC8" => "\xE9\xA8\x8E", + "\xD1\xC9" => "\xE9\xA8\x8F", + "\xD1\xCA" => "\xE9\xA9\xA5", + "\xD1\xCB" => "\xE9\xBA\x92", + "\xD1\xCC" => "\xE7\xB7\x8A", + "\xD1\xCD" => "\xE4\xBD\xB6", + "\xD1\xCE" => "\xE5\x90\x89", + "\xD1\xCF" => "\xE6\x8B\xAE", + "\xD1\xD0" => "\xE6\xA1\x94", + "\xD1\xD1" => "\xE9\x87\x91", + "\xD1\xD2" => "\xE5\x96\xAB", + "\xD1\xD3" => "\xE5\x84\xBA", + "\xD1\xD4" => "\xEF\xA4\x8B", + "\xD1\xD5" => "\xEF\xA4\x8C", + "\xD1\xD6" => "\xE5\xA8\x9C", + "\xD1\xD7" => "\xE6\x87\xA6", + "\xD1\xD8" => "\xEF\xA4\x8D", + "\xD1\xD9" => "\xE6\x8B\x8F", + "\xD1\xDA" => "\xE6\x8B\xBF", + "\xD1\xDB" => "\xEF\xA4\x8E", + "\xD1\xDC" => "\xEF\xA4\x8F", + "\xD1\xDD" => "\xEF\xA4\x90", + "\xD1\xDE" => "\xEF\xA4\x91", + "\xD1\xDF" => "\xEF\xA4\x92", + "\xD1\xE0" => "\xEF\xA4\x93", + "\xD1\xE1" => "\xE9\x82\xA3", + "\xD1\xE2" => "\xEF\xA4\x94", + "\xD1\xE3" => "\xEF\xA4\x95", + "\xD1\xE4" => "\xEF\xA4\x96", + "\xD1\xE5" => "\xEF\xA4\x97", + "\xD1\xE6" => "\xEF\xA4\x98", + "\xD1\xE7" => "\xE8\xAB\xBE", + "\xD1\xE8" => "\xEF\xA4\x99", + "\xD1\xE9" => "\xEF\xA4\x9A", + "\xD1\xEA" => "\xEF\xA4\x9B", + "\xD1\xEB" => "\xEF\xA4\x9C", + "\xD1\xEC" => "\xE6\x9A\x96", + "\xD1\xED" => "\xEF\xA4\x9D", + "\xD1\xEE" => "\xE7\x85\x96", + "\xD1\xEF" => "\xEF\xA4\x9E", + "\xD1\xF0" => "\xEF\xA4\x9F", + "\xD1\xF1" => "\xE9\x9B\xA3", + "\xD1\xF2" => "\xEF\xA4\xA0", + "\xD1\xF3" => "\xE6\x8D\x8F", + "\xD1\xF4" => "\xE6\x8D\xBA", + "\xD1\xF5" => "\xE5\x8D\x97", + "\xD1\xF6" => "\xEF\xA4\xA1", + "\xD1\xF7" => "\xE6\x9E\x8F", + "\xD1\xF8" => "\xE6\xA5\xA0", + "\xD1\xF9" => "\xE6\xB9\xB3", + "\xD1\xFA" => "\xEF\xA4\xA2", + "\xD1\xFB" => "\xE7\x94\xB7", + "\xD1\xFC" => "\xEF\xA4\xA3", + "\xD1\xFD" => "\xEF\xA4\xA4", + "\xD1\xFE" => "\xEF\xA4\xA5", + "\xD2\xA1" => "\xE7\xB4\x8D", + "\xD2\xA2" => "\xEF\xA4\xA6", + "\xD2\xA3" => "\xEF\xA4\xA7", + "\xD2\xA4" => "\xE8\xA1\xB2", + "\xD2\xA5" => "\xE5\x9B\x8A", + "\xD2\xA6" => "\xE5\xA8\x98", + "\xD2\xA7" => "\xEF\xA4\xA8", + "\xD2\xA8" => "\xEF\xA4\xA9", + "\xD2\xA9" => "\xEF\xA4\xAA", + "\xD2\xAA" => "\xEF\xA4\xAB", + "\xD2\xAB" => "\xEF\xA4\xAC", + "\xD2\xAC" => "\xE4\xB9\x83", + "\xD2\xAD" => "\xEF\xA4\xAD", + "\xD2\xAE" => "\xE5\x85\xA7", + "\xD2\xAF" => "\xE5\xA5\x88", + "\xD2\xB0" => "\xE6\x9F\xB0", + "\xD2\xB1" => "\xE8\x80\x90", + "\xD2\xB2" => "\xEF\xA4\xAE", + "\xD2\xB3" => "\xE5\xA5\xB3", + "\xD2\xB4" => "\xE5\xB9\xB4", + "\xD2\xB5" => "\xE6\x92\x9A", + "\xD2\xB6" => "\xE7\xA7\x8A", + "\xD2\xB7" => "\xE5\xBF\xB5", + "\xD2\xB8" => "\xE6\x81\xAC", + "\xD2\xB9" => "\xE6\x8B\x88", + "\xD2\xBA" => "\xE6\x8D\xBB", + "\xD2\xBB" => "\xE5\xAF\xA7", + "\xD2\xBC" => "\xE5\xAF\x97", + "\xD2\xBD" => "\xE5\x8A\xAA", + "\xD2\xBE" => "\xEF\xA4\xAF", + "\xD2\xBF" => "\xE5\xA5\xB4", + "\xD2\xC0" => "\xE5\xBC\xA9", + "\xD2\xC1" => "\xE6\x80\x92", + "\xD2\xC2" => "\xEF\xA4\xB0", + "\xD2\xC3" => "\xEF\xA4\xB1", + "\xD2\xC4" => "\xEF\xA4\xB2", + "\xD2\xC5" => "\xE7\x91\x99", + "\xD2\xC6" => "\xEF\xA4\xB3", + "\xD2\xC7" => "\xEF\xA4\xB4", + "\xD2\xC8" => "\xEF\xA4\xB5", + "\xD2\xC9" => "\xEF\xA4\xB6", + "\xD2\xCA" => "\xEF\xA4\xB7", + "\xD2\xCB" => "\xEF\xA4\xB8", + "\xD2\xCC" => "\xE9\xA7\x91", + "\xD2\xCD" => "\xEF\xA4\xB9", + "\xD2\xCE" => "\xEF\xA4\xBA", + "\xD2\xCF" => "\xEF\xA4\xBB", + "\xD2\xD0" => "\xEF\xA4\xBC", + "\xD2\xD1" => "\xEF\xA4\xBD", + "\xD2\xD2" => "\xEF\xA4\xBE", + "\xD2\xD3" => "\xEF\xA4\xBF", + "\xD2\xD4" => "\xEF\xA5\x80", + "\xD2\xD5" => "\xEF\xA5\x81", + "\xD2\xD6" => "\xEF\xA5\x82", + "\xD2\xD7" => "\xEF\xA5\x83", + "\xD2\xD8" => "\xE6\xBF\x83", + "\xD2\xD9" => "\xEF\xA5\x84", + "\xD2\xDA" => "\xEF\xA5\x85", + "\xD2\xDB" => "\xE8\x86\xBF", + "\xD2\xDC" => "\xE8\xBE\xB2", + "\xD2\xDD" => "\xE6\x83\xB1", + "\xD2\xDE" => "\xEF\xA5\x86", + "\xD2\xDF" => "\xEF\xA5\x87", + "\xD2\xE0" => "\xE8\x85\xA6", + "\xD2\xE1" => "\xEF\xA5\x88", + "\xD2\xE2" => "\xEF\xA5\x89", + "\xD2\xE3" => "\xE5\xB0\xBF", + "\xD2\xE4" => "\xEF\xA5\x8A", + "\xD2\xE5" => "\xEF\xA5\x8B", + "\xD2\xE6" => "\xEF\xA5\x8C", + "\xD2\xE7" => "\xEF\xA5\x8D", + "\xD2\xE8" => "\xEF\xA5\x8E", + "\xD2\xE9" => "\xEF\xA5\x8F", + "\xD2\xEA" => "\xEF\xA5\x90", + "\xD2\xEB" => "\xEF\xA5\x91", + "\xD2\xEC" => "\xE5\xAB\xA9", + "\xD2\xED" => "\xE8\xA8\xA5", + "\xD2\xEE" => "\xE6\x9D\xBB", + "\xD2\xEF" => "\xE7\xB4\x90", + "\xD2\xF0" => "\xEF\xA5\x92", + "\xD2\xF1" => "\xEF\xA5\x93", + "\xD2\xF2" => "\xEF\xA5\x94", + "\xD2\xF3" => "\xEF\xA5\x95", + "\xD2\xF4" => "\xEF\xA5\x96", + "\xD2\xF5" => "\xEF\xA5\x97", + "\xD2\xF6" => "\xE8\x83\xBD", + "\xD2\xF7" => "\xEF\xA5\x98", + "\xD2\xF8" => "\xEF\xA5\x99", + "\xD2\xF9" => "\xE5\xB0\xBC", + "\xD2\xFA" => "\xE6\xB3\xA5", + "\xD2\xFB" => "\xE5\x8C\xBF", + "\xD2\xFC" => "\xE6\xBA\xBA", + "\xD2\xFD" => "\xE5\xA4\x9A", + "\xD2\xFE" => "\xE8\x8C\xB6", + "\xD3\xA1" => "\xE4\xB8\xB9", + "\xD3\xA2" => "\xE4\xBA\xB6", + "\xD3\xA3" => "\xE4\xBD\x86", + "\xD3\xA4" => "\xE5\x96\xAE", + "\xD3\xA5" => "\xE5\x9C\x98", + "\xD3\xA6" => "\xE5\xA3\x87", + "\xD3\xA7" => "\xE5\xBD\x96", + "\xD3\xA8" => "\xE6\x96\xB7", + "\xD3\xA9" => "\xE6\x97\xA6", + "\xD3\xAA" => "\xE6\xAA\x80", + "\xD3\xAB" => "\xE6\xAE\xB5", + "\xD3\xAC" => "\xE6\xB9\x8D", + "\xD3\xAD" => "\xE7\x9F\xAD", + "\xD3\xAE" => "\xE7\xAB\xAF", + "\xD3\xAF" => "\xE7\xB0\x9E", + "\xD3\xB0" => "\xE7\xB7\x9E", + "\xD3\xB1" => "\xE8\x9B\x8B", + "\xD3\xB2" => "\xE8\xA2\x92", + "\xD3\xB3" => "\xE9\x84\xB2", + "\xD3\xB4" => "\xE9\x8D\x9B", + "\xD3\xB5" => "\xE6\x92\xBB", + "\xD3\xB6" => "\xE6\xBE\xBE", + "\xD3\xB7" => "\xE7\x8D\xBA", + "\xD3\xB8" => "\xE7\x96\xB8", + "\xD3\xB9" => "\xE9\x81\x94", + "\xD3\xBA" => "\xE5\x95\x96", + "\xD3\xBB" => "\xE5\x9D\x8D", + "\xD3\xBC" => "\xE6\x86\xBA", + "\xD3\xBD" => "\xE6\x93\x94", + "\xD3\xBE" => "\xE6\x9B\x87", + "\xD3\xBF" => "\xE6\xB7\xA1", + "\xD3\xC0" => "\xE6\xB9\x9B", + "\xD3\xC1" => "\xE6\xBD\xAD", + "\xD3\xC2" => "\xE6\xBE\xB9", + "\xD3\xC3" => "\xE7\x97\xB0", + "\xD3\xC4" => "\xE8\x81\x83", + "\xD3\xC5" => "\xE8\x86\xBD", + "\xD3\xC6" => "\xE8\x95\x81", + "\xD3\xC7" => "\xE8\xA6\x83", + "\xD3\xC8" => "\xE8\xAB\x87", + "\xD3\xC9" => "\xE8\xAD\x9A", + "\xD3\xCA" => "\xE9\x8C\x9F", + "\xD3\xCB" => "\xE6\xB2\x93", + "\xD3\xCC" => "\xE7\x95\x93", + "\xD3\xCD" => "\xE7\xAD\x94", + "\xD3\xCE" => "\xE8\xB8\x8F", + "\xD3\xCF" => "\xE9\x81\x9D", + "\xD3\xD0" => "\xE5\x94\x90", + "\xD3\xD1" => "\xE5\xA0\x82", + "\xD3\xD2" => "\xE5\xA1\x98", + "\xD3\xD3" => "\xE5\xB9\xA2", + "\xD3\xD4" => "\xE6\x88\x87", + "\xD3\xD5" => "\xE6\x92\x9E", + "\xD3\xD6" => "\xE6\xA3\xA0", + "\xD3\xD7" => "\xE7\x95\xB6", + "\xD3\xD8" => "\xE7\xB3\x96", + "\xD3\xD9" => "\xE8\x9E\xB3", + "\xD3\xDA" => "\xE9\xBB\xA8", + "\xD3\xDB" => "\xE4\xBB\xA3", + "\xD3\xDC" => "\xE5\x9E\x88", + "\xD3\xDD" => "\xE5\x9D\xAE", + "\xD3\xDE" => "\xE5\xA4\xA7", + "\xD3\xDF" => "\xE5\xB0\x8D", + "\xD3\xE0" => "\xE5\xB2\xB1", + "\xD3\xE1" => "\xE5\xB8\xB6", + "\xD3\xE2" => "\xE5\xBE\x85", + "\xD3\xE3" => "\xE6\x88\xB4", + "\xD3\xE4" => "\xE6\x93\xA1", + "\xD3\xE5" => "\xE7\x8E\xB3", + "\xD3\xE6" => "\xE8\x87\xBA", + "\xD3\xE7" => "\xE8\xA2\x8B", + "\xD3\xE8" => "\xE8\xB2\xB8", + "\xD3\xE9" => "\xE9\x9A\x8A", + "\xD3\xEA" => "\xE9\xBB\x9B", + "\xD3\xEB" => "\xE5\xAE\x85", + "\xD3\xEC" => "\xE5\xBE\xB7", + "\xD3\xED" => "\xE6\x82\xB3", + "\xD3\xEE" => "\xE5\x80\x92", + "\xD3\xEF" => "\xE5\x88\x80", + "\xD3\xF0" => "\xE5\x88\xB0", + "\xD3\xF1" => "\xE5\x9C\x96", + "\xD3\xF2" => "\xE5\xA0\xB5", + "\xD3\xF3" => "\xE5\xA1\x97", + "\xD3\xF4" => "\xE5\xB0\x8E", + "\xD3\xF5" => "\xE5\xB1\xA0", + "\xD3\xF6" => "\xE5\xB3\xB6", + "\xD3\xF7" => "\xE5\xB6\x8B", + "\xD3\xF8" => "\xE5\xBA\xA6", + "\xD3\xF9" => "\xE5\xBE\x92", + "\xD3\xFA" => "\xE6\x82\xBC", + "\xD3\xFB" => "\xE6\x8C\x91", + "\xD3\xFC" => "\xE6\x8E\x89", + "\xD3\xFD" => "\xE6\x90\x97", + "\xD3\xFE" => "\xE6\xA1\x83", + "\xD4\xA1" => "\xE6\xA3\xB9", + "\xD4\xA2" => "\xE6\xAB\x82", + "\xD4\xA3" => "\xE6\xB7\x98", + "\xD4\xA4" => "\xE6\xB8\xA1", + "\xD4\xA5" => "\xE6\xBB\x94", + "\xD4\xA6" => "\xE6\xBF\xA4", + "\xD4\xA7" => "\xE7\x87\xBE", + "\xD4\xA8" => "\xE7\x9B\x9C", + "\xD4\xA9" => "\xE7\x9D\xB9", + "\xD4\xAA" => "\xE7\xA6\xB1", + "\xD4\xAB" => "\xE7\xA8\xBB", + "\xD4\xAC" => "\xE8\x90\x84", + "\xD4\xAD" => "\xE8\xA6\xA9", + "\xD4\xAE" => "\xE8\xB3\xAD", + "\xD4\xAF" => "\xE8\xB7\xB3", + "\xD4\xB0" => "\xE8\xB9\x88", + "\xD4\xB1" => "\xE9\x80\x83", + "\xD4\xB2" => "\xE9\x80\x94", + "\xD4\xB3" => "\xE9\x81\x93", + "\xD4\xB4" => "\xE9\x83\xBD", + "\xD4\xB5" => "\xE9\x8D\x8D", + "\xD4\xB6" => "\xE9\x99\xB6", + "\xD4\xB7" => "\xE9\x9F\x9C", + "\xD4\xB8" => "\xE6\xAF\x92", + "\xD4\xB9" => "\xE7\x80\x86", + "\xD4\xBA" => "\xE7\x89\x98", + "\xD4\xBB" => "\xE7\x8A\xA2", + "\xD4\xBC" => "\xE7\x8D\xA8", + "\xD4\xBD" => "\xE7\x9D\xA3", + "\xD4\xBE" => "\xE7\xA6\xBF", + "\xD4\xBF" => "\xE7\xAF\xA4", + "\xD4\xC0" => "\xE7\xBA\x9B", + "\xD4\xC1" => "\xE8\xAE\x80", + "\xD4\xC2" => "\xE5\xA2\xA9", + "\xD4\xC3" => "\xE6\x83\x87", + "\xD4\xC4" => "\xE6\x95\xA6", + "\xD4\xC5" => "\xE6\x97\xBD", + "\xD4\xC6" => "\xE6\x9A\xBE", + "\xD4\xC7" => "\xE6\xB2\x8C", + "\xD4\xC8" => "\xE7\x84\x9E", + "\xD4\xC9" => "\xE7\x87\x89", + "\xD4\xCA" => "\xE8\xB1\x9A", + "\xD4\xCB" => "\xE9\xA0\x93", + "\xD4\xCC" => "\xE4\xB9\xAD", + "\xD4\xCD" => "\xE7\xAA\x81", + "\xD4\xCE" => "\xE4\xBB\x9D", + "\xD4\xCF" => "\xE5\x86\xAC", + "\xD4\xD0" => "\xE5\x87\x8D", + "\xD4\xD1" => "\xE5\x8B\x95", + "\xD4\xD2" => "\xE5\x90\x8C", + "\xD4\xD3" => "\xE6\x86\xA7", + "\xD4\xD4" => "\xE6\x9D\xB1", + "\xD4\xD5" => "\xE6\xA1\x90", + "\xD4\xD6" => "\xE6\xA3\x9F", + "\xD4\xD7" => "\xE6\xB4\x9E", + "\xD4\xD8" => "\xE6\xBD\xBC", + "\xD4\xD9" => "\xE7\x96\xBC", + "\xD4\xDA" => "\xE7\x9E\xB3", + "\xD4\xDB" => "\xE7\xAB\xA5", + "\xD4\xDC" => "\xE8\x83\xB4", + "\xD4\xDD" => "\xE8\x91\xA3", + "\xD4\xDE" => "\xE9\x8A\x85", + "\xD4\xDF" => "\xE5\x85\x9C", + "\xD4\xE0" => "\xE6\x96\x97", + "\xD4\xE1" => "\xE6\x9D\x9C", + "\xD4\xE2" => "\xE6\x9E\x93", + "\xD4\xE3" => "\xE7\x97\x98", + "\xD4\xE4" => "\xE7\xAB\x87", + "\xD4\xE5" => "\xE8\x8D\xB3", + "\xD4\xE6" => "\xEF\xA5\x9A", + "\xD4\xE7" => "\xE8\xB1\x86", + "\xD4\xE8" => "\xE9\x80\x97", + "\xD4\xE9" => "\xE9\xA0\xAD", + "\xD4\xEA" => "\xE5\xB1\xAF", + "\xD4\xEB" => "\xE8\x87\x80", + "\xD4\xEC" => "\xE8\x8A\x9A", + "\xD4\xED" => "\xE9\x81\x81", + "\xD4\xEE" => "\xE9\x81\xAF", + "\xD4\xEF" => "\xE9\x88\x8D", + "\xD4\xF0" => "\xE5\xBE\x97", + "\xD4\xF1" => "\xE5\xB6\x9D", + "\xD4\xF2" => "\xE6\xA9\x99", + "\xD4\xF3" => "\xE7\x87\x88", + "\xD4\xF4" => "\xE7\x99\xBB", + "\xD4\xF5" => "\xE7\xAD\x89", + "\xD4\xF6" => "\xE8\x97\xA4", + "\xD4\xF7" => "\xE8\xAC\x84", + "\xD4\xF8" => "\xE9\x84\xA7", + "\xD4\xF9" => "\xE9\xA8\xB0", + "\xD4\xFA" => "\xE5\x96\x87", + "\xD4\xFB" => "\xE6\x87\xB6", + "\xD4\xFC" => "\xEF\xA5\x9B", + "\xD4\xFD" => "\xE7\x99\xA9", + "\xD4\xFE" => "\xE7\xBE\x85", + "\xD5\xA1" => "\xE8\x98\xBF", + "\xD5\xA2" => "\xE8\x9E\xBA", + "\xD5\xA3" => "\xE8\xA3\xB8", + "\xD5\xA4" => "\xE9\x82\x8F", + "\xD5\xA5" => "\xEF\xA5\x9C", + "\xD5\xA6" => "\xE6\xB4\x9B", + "\xD5\xA7" => "\xE7\x83\x99", + "\xD5\xA8" => "\xE7\x8F\x9E", + "\xD5\xA9" => "\xE7\xB5\xA1", + "\xD5\xAA" => "\xE8\x90\xBD", + "\xD5\xAB" => "\xEF\xA5\x9D", + "\xD5\xAC" => "\xE9\x85\xAA", + "\xD5\xAD" => "\xE9\xA7\xB1", + "\xD5\xAE" => "\xEF\xA5\x9E", + "\xD5\xAF" => "\xE4\xBA\x82", + "\xD5\xB0" => "\xE5\x8D\xB5", + "\xD5\xB1" => "\xE6\xAC\x84", + "\xD5\xB2" => "\xE6\xAC\x92", + "\xD5\xB3" => "\xE7\x80\xBE", + "\xD5\xB4" => "\xE7\x88\x9B", + "\xD5\xB5" => "\xE8\x98\xAD", + "\xD5\xB6" => "\xE9\xB8\x9E", + "\xD5\xB7" => "\xE5\x89\x8C", + "\xD5\xB8" => "\xE8\xBE\xA3", + "\xD5\xB9" => "\xE5\xB5\x90", + "\xD5\xBA" => "\xE6\x93\xA5", + "\xD5\xBB" => "\xE6\x94\xAC", + "\xD5\xBC" => "\xE6\xAC\x96", + "\xD5\xBD" => "\xE6\xBF\xAB", + "\xD5\xBE" => "\xE7\xB1\x83", + "\xD5\xBF" => "\xE7\xBA\x9C", + "\xD5\xC0" => "\xE8\x97\x8D", + "\xD5\xC1" => "\xE8\xA5\xA4", + "\xD5\xC2" => "\xE8\xA6\xBD", + "\xD5\xC3" => "\xE6\x8B\x89", + "\xD5\xC4" => "\xE8\x87\x98", + "\xD5\xC5" => "\xE8\xA0\x9F", + "\xD5\xC6" => "\xE5\xBB\x8A", + "\xD5\xC7" => "\xE6\x9C\x97", + "\xD5\xC8" => "\xE6\xB5\xAA", + "\xD5\xC9" => "\xE7\x8B\xBC", + "\xD5\xCA" => "\xE7\x90\x85", + "\xD5\xCB" => "\xE7\x91\xAF", + "\xD5\xCC" => "\xE8\x9E\x82", + "\xD5\xCD" => "\xE9\x83\x9E", + "\xD5\xCE" => "\xE4\xBE\x86", + "\xD5\xCF" => "\xE5\xB4\x8D", + "\xD5\xD0" => "\xE5\xBE\xA0", + "\xD5\xD1" => "\xE8\x90\x8A", + "\xD5\xD2" => "\xE5\x86\xB7", + "\xD5\xD3" => "\xE6\x8E\xA0", + "\xD5\xD4" => "\xE7\x95\xA5", + "\xD5\xD5" => "\xE4\xBA\xAE", + "\xD5\xD6" => "\xE5\x80\x86", + "\xD5\xD7" => "\xE5\x85\xA9", + "\xD5\xD8" => "\xE5\x87\x89", + "\xD5\xD9" => "\xE6\xA2\x81", + "\xD5\xDA" => "\xE6\xA8\x91", + "\xD5\xDB" => "\xE7\xB2\xAE", + "\xD5\xDC" => "\xE7\xB2\xB1", + "\xD5\xDD" => "\xE7\xB3\xA7", + "\xD5\xDE" => "\xE8\x89\xAF", + "\xD5\xDF" => "\xE8\xAB\x92", + "\xD5\xE0" => "\xE8\xBC\x9B", + "\xD5\xE1" => "\xE9\x87\x8F", + "\xD5\xE2" => "\xE4\xBE\xB6", + "\xD5\xE3" => "\xE5\x84\xB7", + "\xD5\xE4" => "\xE5\x8B\xB5", + "\xD5\xE5" => "\xE5\x91\x82", + "\xD5\xE6" => "\xE5\xBB\xAC", + "\xD5\xE7" => "\xE6\x85\xAE", + "\xD5\xE8" => "\xE6\x88\xBE", + "\xD5\xE9" => "\xE6\x97\x85", + "\xD5\xEA" => "\xE6\xAB\x9A", + "\xD5\xEB" => "\xE6\xBF\xBE", + "\xD5\xEC" => "\xE7\xA4\xAA", + "\xD5\xED" => "\xE8\x97\x9C", + "\xD5\xEE" => "\xE8\xA0\xA3", + "\xD5\xEF" => "\xE9\x96\xAD", + "\xD5\xF0" => "\xE9\xA9\xA2", + "\xD5\xF1" => "\xE9\xA9\xAA", + "\xD5\xF2" => "\xE9\xBA\x97", + "\xD5\xF3" => "\xE9\xBB\x8E", + "\xD5\xF4" => "\xE5\x8A\x9B", + "\xD5\xF5" => "\xE6\x9B\x86", + "\xD5\xF6" => "\xE6\xAD\xB7", + "\xD5\xF7" => "\xE7\x80\x9D", + "\xD5\xF8" => "\xE7\xA4\xAB", + "\xD5\xF9" => "\xE8\xBD\xA2", + "\xD5\xFA" => "\xE9\x9D\x82", + "\xD5\xFB" => "\xE6\x86\x90", + "\xD5\xFC" => "\xE6\x88\x80", + "\xD5\xFD" => "\xE6\x94\xA3", + "\xD5\xFE" => "\xE6\xBC\xA3", + "\xD6\xA1" => "\xE7\x85\x89", + "\xD6\xA2" => "\xE7\x92\x89", + "\xD6\xA3" => "\xE7\xB7\xB4", + "\xD6\xA4" => "\xE8\x81\xAF", + "\xD6\xA5" => "\xE8\x93\xAE", + "\xD6\xA6" => "\xE8\xBC\xA6", + "\xD6\xA7" => "\xE9\x80\xA3", + "\xD6\xA8" => "\xE9\x8D\x8A", + "\xD6\xA9" => "\xE5\x86\xBD", + "\xD6\xAA" => "\xE5\x88\x97", + "\xD6\xAB" => "\xE5\x8A\xA3", + "\xD6\xAC" => "\xE6\xB4\x8C", + "\xD6\xAD" => "\xE7\x83\x88", + "\xD6\xAE" => "\xE8\xA3\x82", + "\xD6\xAF" => "\xE5\xBB\x89", + "\xD6\xB0" => "\xE6\x96\x82", + "\xD6\xB1" => "\xE6\xAE\xAE", + "\xD6\xB2" => "\xE6\xBF\x82", + "\xD6\xB3" => "\xE7\xB0\xBE", + "\xD6\xB4" => "\xE7\x8D\xB5", + "\xD6\xB5" => "\xE4\xBB\xA4", + "\xD6\xB6" => "\xE4\xBC\xB6", + "\xD6\xB7" => "\xE5\x9B\xB9", + "\xD6\xB8" => "\xEF\xA5\x9F", + "\xD6\xB9" => "\xE5\xB2\xBA", + "\xD6\xBA" => "\xE5\xB6\xBA", + "\xD6\xBB" => "\xE6\x80\x9C", + "\xD6\xBC" => "\xE7\x8E\xB2", + "\xD6\xBD" => "\xE7\xAC\xAD", + "\xD6\xBE" => "\xE7\xBE\x9A", + "\xD6\xBF" => "\xE7\xBF\x8E", + "\xD6\xC0" => "\xE8\x81\x86", + "\xD6\xC1" => "\xE9\x80\x9E", + "\xD6\xC2" => "\xE9\x88\xB4", + "\xD6\xC3" => "\xE9\x9B\xB6", + "\xD6\xC4" => "\xE9\x9D\x88", + "\xD6\xC5" => "\xE9\xA0\x98", + "\xD6\xC6" => "\xE9\xBD\xA1", + "\xD6\xC7" => "\xE4\xBE\x8B", + "\xD6\xC8" => "\xE6\xBE\xA7", + "\xD6\xC9" => "\xE7\xA6\xAE", + "\xD6\xCA" => "\xE9\x86\xB4", + "\xD6\xCB" => "\xE9\x9A\xB7", + "\xD6\xCC" => "\xE5\x8B\x9E", + "\xD6\xCD" => "\xEF\xA5\xA0", + "\xD6\xCE" => "\xE6\x92\x88", + "\xD6\xCF" => "\xE6\x93\x84", + "\xD6\xD0" => "\xE6\xAB\x93", + "\xD6\xD1" => "\xE6\xBD\x9E", + "\xD6\xD2" => "\xE7\x80\x98", + "\xD6\xD3" => "\xE7\x88\x90", + "\xD6\xD4" => "\xE7\x9B\xA7", + "\xD6\xD5" => "\xE8\x80\x81", + "\xD6\xD6" => "\xE8\x98\x86", + "\xD6\xD7" => "\xE8\x99\x9C", + "\xD6\xD8" => "\xE8\xB7\xAF", + "\xD6\xD9" => "\xE8\xBC\x85", + "\xD6\xDA" => "\xE9\x9C\xB2", + "\xD6\xDB" => "\xE9\xAD\xAF", + "\xD6\xDC" => "\xE9\xB7\xBA", + "\xD6\xDD" => "\xE9\xB9\xB5", + "\xD6\xDE" => "\xE7\xA2\x8C", + "\xD6\xDF" => "\xE7\xA5\xBF", + "\xD6\xE0" => "\xE7\xB6\xA0", + "\xD6\xE1" => "\xE8\x8F\x89", + "\xD6\xE2" => "\xE9\x8C\x84", + "\xD6\xE3" => "\xE9\xB9\xBF", + "\xD6\xE4" => "\xE9\xBA\x93", + "\xD6\xE5" => "\xE8\xAB\x96", + "\xD6\xE6" => "\xE5\xA3\x9F", + "\xD6\xE7" => "\xE5\xBC\x84", + "\xD6\xE8" => "\xE6\x9C\xA7", + "\xD6\xE9" => "\xE7\x80\xA7", + "\xD6\xEA" => "\xE7\x93\x8F", + "\xD6\xEB" => "\xE7\xB1\xA0", + "\xD6\xEC" => "\xE8\x81\xBE", + "\xD6\xED" => "\xE5\x84\xA1", + "\xD6\xEE" => "\xE7\x80\xA8", + "\xD6\xEF" => "\xE7\x89\xA2", + "\xD6\xF0" => "\xE7\xA3\x8A", + "\xD6\xF1" => "\xE8\xB3\x82", + "\xD6\xF2" => "\xE8\xB3\x9A", + "\xD6\xF3" => "\xE8\xB3\xB4", + "\xD6\xF4" => "\xE9\x9B\xB7", + "\xD6\xF5" => "\xE4\xBA\x86", + "\xD6\xF6" => "\xE5\x83\x9A", + "\xD6\xF7" => "\xE5\xAF\xAE", + "\xD6\xF8" => "\xE5\xBB\x96", + "\xD6\xF9" => "\xE6\x96\x99", + "\xD6\xFA" => "\xE7\x87\x8E", + "\xD6\xFB" => "\xE7\x99\x82", + "\xD6\xFC" => "\xE7\x9E\xAD", + "\xD6\xFD" => "\xE8\x81\x8A", + "\xD6\xFE" => "\xE8\x93\xBC", + "\xD7\xA1" => "\xE9\x81\xBC", + "\xD7\xA2" => "\xE9\xAC\xA7", + "\xD7\xA3" => "\xE9\xBE\x8D", + "\xD7\xA4" => "\xE5\xA3\x98", + "\xD7\xA5" => "\xE5\xA9\x81", + "\xD7\xA6" => "\xE5\xB1\xA2", + "\xD7\xA7" => "\xE6\xA8\x93", + "\xD7\xA8" => "\xE6\xB7\x9A", + "\xD7\xA9" => "\xE6\xBC\x8F", + "\xD7\xAA" => "\xE7\x98\xBB", + "\xD7\xAB" => "\xE7\xB4\xAF", + "\xD7\xAC" => "\xE7\xB8\xB7", + "\xD7\xAD" => "\xE8\x94\x9E", + "\xD7\xAE" => "\xE8\xA4\xB8", + "\xD7\xAF" => "\xE9\x8F\xA4", + "\xD7\xB0" => "\xE9\x99\x8B", + "\xD7\xB1" => "\xE5\x8A\x89", + "\xD7\xB2" => "\xE6\x97\x92", + "\xD7\xB3" => "\xE6\x9F\xB3", + "\xD7\xB4" => "\xE6\xA6\xB4", + "\xD7\xB5" => "\xE6\xB5\x81", + "\xD7\xB6" => "\xE6\xBA\x9C", + "\xD7\xB7" => "\xE7\x80\x8F", + "\xD7\xB8" => "\xE7\x90\x89", + "\xD7\xB9" => "\xE7\x91\xA0", + "\xD7\xBA" => "\xE7\x95\x99", + "\xD7\xBB" => "\xE7\x98\xA4", + "\xD7\xBC" => "\xE7\xA1\xAB", + "\xD7\xBD" => "\xE8\xAC\xAC", + "\xD7\xBE" => "\xE9\xA1\x9E", + "\xD7\xBF" => "\xE5\x85\xAD", + "\xD7\xC0" => "\xE6\x88\xAE", + "\xD7\xC1" => "\xE9\x99\xB8", + "\xD7\xC2" => "\xE4\xBE\x96", + "\xD7\xC3" => "\xE5\x80\xAB", + "\xD7\xC4" => "\xE5\xB4\x99", + "\xD7\xC5" => "\xE6\xB7\xAA", + "\xD7\xC6" => "\xE7\xB6\xB8", + "\xD7\xC7" => "\xE8\xBC\xAA", + "\xD7\xC8" => "\xE5\xBE\x8B", + "\xD7\xC9" => "\xE6\x85\x84", + "\xD7\xCA" => "\xE6\xA0\x97", + "\xD7\xCB" => "\xEF\xA5\xA1", + "\xD7\xCC" => "\xE9\x9A\x86", + "\xD7\xCD" => "\xE5\x8B\x92", + "\xD7\xCE" => "\xE8\x82\x8B", + "\xD7\xCF" => "\xE5\x87\x9C", + "\xD7\xD0" => "\xE5\x87\x8C", + "\xD7\xD1" => "\xE6\xA5\x9E", + "\xD7\xD2" => "\xE7\xA8\x9C", + "\xD7\xD3" => "\xE7\xB6\xBE", + "\xD7\xD4" => "\xE8\x8F\xB1", + "\xD7\xD5" => "\xE9\x99\xB5", + "\xD7\xD6" => "\xE4\xBF\x9A", + "\xD7\xD7" => "\xE5\x88\xA9", + "\xD7\xD8" => "\xE5\x8E\x98", + "\xD7\xD9" => "\xE5\x90\x8F", + "\xD7\xDA" => "\xE5\x94\x8E", + "\xD7\xDB" => "\xE5\xB1\xA5", + "\xD7\xDC" => "\xE6\x82\xA7", + "\xD7\xDD" => "\xE6\x9D\x8E", + "\xD7\xDE" => "\xE6\xA2\xA8", + "\xD7\xDF" => "\xE6\xB5\xAC", + "\xD7\xE0" => "\xE7\x8A\x81", + "\xD7\xE1" => "\xE7\x8B\xB8", + "\xD7\xE2" => "\xE7\x90\x86", + "\xD7\xE3" => "\xE7\x92\x83", + "\xD7\xE4" => "\xEF\xA5\xA2", + "\xD7\xE5" => "\xE7\x97\xA2", + "\xD7\xE6" => "\xE7\xB1\xAC", + "\xD7\xE7" => "\xE7\xBD\xB9", + "\xD7\xE8" => "\xE7\xBE\xB8", + "\xD7\xE9" => "\xE8\x8E\x89", + "\xD7\xEA" => "\xE8\xA3\x8F", + "\xD7\xEB" => "\xE8\xA3\xA1", + "\xD7\xEC" => "\xE9\x87\x8C", + "\xD7\xED" => "\xE9\x87\x90", + "\xD7\xEE" => "\xE9\x9B\xA2", + "\xD7\xEF" => "\xE9\xAF\x89", + "\xD7\xF0" => "\xE5\x90\x9D", + "\xD7\xF1" => "\xE6\xBD\xBE", + "\xD7\xF2" => "\xE7\x87\x90", + "\xD7\xF3" => "\xE7\x92\x98", + "\xD7\xF4" => "\xE8\x97\xBA", + "\xD7\xF5" => "\xE8\xBA\xAA", + "\xD7\xF6" => "\xE9\x9A\xA3", + "\xD7\xF7" => "\xE9\xB1\x97", + "\xD7\xF8" => "\xE9\xBA\x9F", + "\xD7\xF9" => "\xE6\x9E\x97", + "\xD7\xFA" => "\xE6\xB7\x8B", + "\xD7\xFB" => "\xE7\x90\xB3", + "\xD7\xFC" => "\xE8\x87\xA8", + "\xD7\xFD" => "\xE9\x9C\x96", + "\xD7\xFE" => "\xE7\xA0\xAC", + "\xD8\xA1" => "\xE7\xAB\x8B", + "\xD8\xA2" => "\xE7\xAC\xA0", + "\xD8\xA3" => "\xE7\xB2\x92", + "\xD8\xA4" => "\xE6\x91\xA9", + "\xD8\xA5" => "\xE7\x91\xAA", + "\xD8\xA6" => "\xE7\x97\xB2", + "\xD8\xA7" => "\xE7\xA2\xBC", + "\xD8\xA8" => "\xE7\xA3\xA8", + "\xD8\xA9" => "\xE9\xA6\xAC", + "\xD8\xAA" => "\xE9\xAD\x94", + "\xD8\xAB" => "\xE9\xBA\xBB", + "\xD8\xAC" => "\xE5\xAF\x9E", + "\xD8\xAD" => "\xE5\xB9\x95", + "\xD8\xAE" => "\xE6\xBC\xA0", + "\xD8\xAF" => "\xE8\x86\x9C", + "\xD8\xB0" => "\xE8\x8E\xAB", + "\xD8\xB1" => "\xE9\x82\x88", + "\xD8\xB2" => "\xE4\xB8\x87", + "\xD8\xB3" => "\xE5\x8D\x8D", + "\xD8\xB4" => "\xE5\xA8\xA9", + "\xD8\xB5" => "\xE5\xB7\x92", + "\xD8\xB6" => "\xE5\xBD\x8E", + "\xD8\xB7" => "\xE6\x85\xA2", + "\xD8\xB8" => "\xE6\x8C\xBD", + "\xD8\xB9" => "\xE6\x99\xA9", + "\xD8\xBA" => "\xE6\x9B\xBC", + "\xD8\xBB" => "\xE6\xBB\xBF", + "\xD8\xBC" => "\xE6\xBC\xAB", + "\xD8\xBD" => "\xE7\x81\xA3", + "\xD8\xBE" => "\xE7\x9E\x9E", + "\xD8\xBF" => "\xE8\x90\xAC", + "\xD8\xC0" => "\xE8\x94\x93", + "\xD8\xC1" => "\xE8\xA0\xBB", + "\xD8\xC2" => "\xE8\xBC\x93", + "\xD8\xC3" => "\xE9\xA5\x85", + "\xD8\xC4" => "\xE9\xB0\xBB", + "\xD8\xC5" => "\xE5\x94\x9C", + "\xD8\xC6" => "\xE6\x8A\xB9", + "\xD8\xC7" => "\xE6\x9C\xAB", + "\xD8\xC8" => "\xE6\xB2\xAB", + "\xD8\xC9" => "\xE8\x8C\x89", + "\xD8\xCA" => "\xE8\xA5\xAA", + "\xD8\xCB" => "\xE9\x9D\xBA", + "\xD8\xCC" => "\xE4\xBA\xA1", + "\xD8\xCD" => "\xE5\xA6\x84", + "\xD8\xCE" => "\xE5\xBF\x98", + "\xD8\xCF" => "\xE5\xBF\x99", + "\xD8\xD0" => "\xE6\x9C\x9B", + "\xD8\xD1" => "\xE7\xB6\xB2", + "\xD8\xD2" => "\xE7\xBD\x94", + "\xD8\xD3" => "\xE8\x8A\x92", + "\xD8\xD4" => "\xE8\x8C\xAB", + "\xD8\xD5" => "\xE8\x8E\xBD", + "\xD8\xD6" => "\xE8\xBC\x9E", + "\xD8\xD7" => "\xE9\x82\x99", + "\xD8\xD8" => "\xE5\x9F\x8B", + "\xD8\xD9" => "\xE5\xA6\xB9", + "\xD8\xDA" => "\xE5\xAA\x92", + "\xD8\xDB" => "\xE5\xAF\x90", + "\xD8\xDC" => "\xE6\x98\xA7", + "\xD8\xDD" => "\xE6\x9E\x9A", + "\xD8\xDE" => "\xE6\xA2\x85", + "\xD8\xDF" => "\xE6\xAF\x8F", + "\xD8\xE0" => "\xE7\x85\xA4", + "\xD8\xE1" => "\xE7\xBD\xB5", + "\xD8\xE2" => "\xE8\xB2\xB7", + "\xD8\xE3" => "\xE8\xB3\xA3", + "\xD8\xE4" => "\xE9\x82\x81", + "\xD8\xE5" => "\xE9\xAD\x85", + "\xD8\xE6" => "\xE8\x84\x88", + "\xD8\xE7" => "\xE8\xB2\x8A", + "\xD8\xE8" => "\xE9\x99\x8C", + "\xD8\xE9" => "\xE9\xA9\x80", + "\xD8\xEA" => "\xE9\xBA\xA5", + "\xD8\xEB" => "\xE5\xAD\x9F", + "\xD8\xEC" => "\xE6\xB0\x93", + "\xD8\xED" => "\xE7\x8C\x9B", + "\xD8\xEE" => "\xE7\x9B\xB2", + "\xD8\xEF" => "\xE7\x9B\x9F", + "\xD8\xF0" => "\xE8\x90\x8C", + "\xD8\xF1" => "\xE5\x86\xAA", + "\xD8\xF2" => "\xE8\xA6\x93", + "\xD8\xF3" => "\xE5\x85\x8D", + "\xD8\xF4" => "\xE5\x86\x95", + "\xD8\xF5" => "\xE5\x8B\x89", + "\xD8\xF6" => "\xE6\xA3\x89", + "\xD8\xF7" => "\xE6\xB2\x94", + "\xD8\xF8" => "\xE7\x9C\x84", + "\xD8\xF9" => "\xE7\x9C\xA0", + "\xD8\xFA" => "\xE7\xB6\xBF", + "\xD8\xFB" => "\xE7\xB7\xAC", + "\xD8\xFC" => "\xE9\x9D\xA2", + "\xD8\xFD" => "\xE9\xBA\xB5", + "\xD8\xFE" => "\xE6\xBB\x85", + "\xD9\xA1" => "\xE8\x94\x91", + "\xD9\xA2" => "\xE5\x86\xA5", + "\xD9\xA3" => "\xE5\x90\x8D", + "\xD9\xA4" => "\xE5\x91\xBD", + "\xD9\xA5" => "\xE6\x98\x8E", + "\xD9\xA6" => "\xE6\x9A\x9D", + "\xD9\xA7" => "\xE6\xA4\xA7", + "\xD9\xA8" => "\xE6\xBA\x9F", + "\xD9\xA9" => "\xE7\x9A\xBF", + "\xD9\xAA" => "\xE7\x9E\x91", + "\xD9\xAB" => "\xE8\x8C\x97", + "\xD9\xAC" => "\xE8\x93\x82", + "\xD9\xAD" => "\xE8\x9E\x9F", + "\xD9\xAE" => "\xE9\x85\xA9", + "\xD9\xAF" => "\xE9\x8A\x98", + "\xD9\xB0" => "\xE9\xB3\xB4", + "\xD9\xB1" => "\xE8\xA2\x82", + "\xD9\xB2" => "\xE4\xBE\xAE", + "\xD9\xB3" => "\xE5\x86\x92", + "\xD9\xB4" => "\xE5\x8B\x9F", + "\xD9\xB5" => "\xE5\xA7\x86", + "\xD9\xB6" => "\xE5\xB8\xBD", + "\xD9\xB7" => "\xE6\x85\x95", + "\xD9\xB8" => "\xE6\x91\xB8", + "\xD9\xB9" => "\xE6\x91\xB9", + "\xD9\xBA" => "\xE6\x9A\xAE", + "\xD9\xBB" => "\xE6\x9F\x90", + "\xD9\xBC" => "\xE6\xA8\xA1", + "\xD9\xBD" => "\xE6\xAF\x8D", + "\xD9\xBE" => "\xE6\xAF\x9B", + "\xD9\xBF" => "\xE7\x89\x9F", + "\xD9\xC0" => "\xE7\x89\xA1", + "\xD9\xC1" => "\xE7\x91\x81", + "\xD9\xC2" => "\xE7\x9C\xB8", + "\xD9\xC3" => "\xE7\x9F\x9B", + "\xD9\xC4" => "\xE8\x80\x97", + "\xD9\xC5" => "\xE8\x8A\xBC", + "\xD9\xC6" => "\xE8\x8C\x85", + "\xD9\xC7" => "\xE8\xAC\x80", + "\xD9\xC8" => "\xE8\xAC\xA8", + "\xD9\xC9" => "\xE8\xB2\x8C", + "\xD9\xCA" => "\xE6\x9C\xA8", + "\xD9\xCB" => "\xE6\xB2\x90", + "\xD9\xCC" => "\xE7\x89\xA7", + "\xD9\xCD" => "\xE7\x9B\xAE", + "\xD9\xCE" => "\xE7\x9D\xA6", + "\xD9\xCF" => "\xE7\xA9\x86", + "\xD9\xD0" => "\xE9\xB6\xA9", + "\xD9\xD1" => "\xE6\xAD\xBF", + "\xD9\xD2" => "\xE6\xB2\x92", + "\xD9\xD3" => "\xE5\xA4\xA2", + "\xD9\xD4" => "\xE6\x9C\xA6", + "\xD9\xD5" => "\xE8\x92\x99", + "\xD9\xD6" => "\xE5\x8D\xAF", + "\xD9\xD7" => "\xE5\xA2\x93", + "\xD9\xD8" => "\xE5\xA6\x99", + "\xD9\xD9" => "\xE5\xBB\x9F", + "\xD9\xDA" => "\xE6\x8F\x8F", + "\xD9\xDB" => "\xE6\x98\xB4", + "\xD9\xDC" => "\xE6\x9D\xB3", + "\xD9\xDD" => "\xE6\xB8\xBA", + "\xD9\xDE" => "\xE7\x8C\xAB", + "\xD9\xDF" => "\xE7\xAB\x97", + "\xD9\xE0" => "\xE8\x8B\x97", + "\xD9\xE1" => "\xE9\x8C\xA8", + "\xD9\xE2" => "\xE5\x8B\x99", + "\xD9\xE3" => "\xE5\xB7\xAB", + "\xD9\xE4" => "\xE6\x86\xAE", + "\xD9\xE5" => "\xE6\x87\x8B", + "\xD9\xE6" => "\xE6\x88\x8A", + "\xD9\xE7" => "\xE6\x8B\x87", + "\xD9\xE8" => "\xE6\x92\xAB", + "\xD9\xE9" => "\xE6\x97\xA0", + "\xD9\xEA" => "\xE6\xA5\x99", + "\xD9\xEB" => "\xE6\xAD\xA6", + "\xD9\xEC" => "\xE6\xAF\x8B", + "\xD9\xED" => "\xE7\x84\xA1", + "\xD9\xEE" => "\xE7\x8F\xB7", + "\xD9\xEF" => "\xE7\x95\x9D", + "\xD9\xF0" => "\xE7\xB9\x86", + "\xD9\xF1" => "\xE8\x88\x9E", + "\xD9\xF2" => "\xE8\x8C\x82", + "\xD9\xF3" => "\xE8\x95\xAA", + "\xD9\xF4" => "\xE8\xAA\xA3", + "\xD9\xF5" => "\xE8\xB2\xBF", + "\xD9\xF6" => "\xE9\x9C\xA7", + "\xD9\xF7" => "\xE9\xB5\xA1", + "\xD9\xF8" => "\xE5\xA2\xA8", + "\xD9\xF9" => "\xE9\xBB\x98", + "\xD9\xFA" => "\xE5\x80\x91", + "\xD9\xFB" => "\xE5\x88\x8E", + "\xD9\xFC" => "\xE5\x90\xBB", + "\xD9\xFD" => "\xE5\x95\x8F", + "\xD9\xFE" => "\xE6\x96\x87", + "\xDA\xA1" => "\xE6\xB1\xB6", + "\xDA\xA2" => "\xE7\xB4\x8A", + "\xDA\xA3" => "\xE7\xB4\x8B", + "\xDA\xA4" => "\xE8\x81\x9E", + "\xDA\xA5" => "\xE8\x9A\x8A", + "\xDA\xA6" => "\xE9\x96\x80", + "\xDA\xA7" => "\xE9\x9B\xAF", + "\xDA\xA8" => "\xE5\x8B\xBF", + "\xDA\xA9" => "\xE6\xB2\x95", + "\xDA\xAA" => "\xE7\x89\xA9", + "\xDA\xAB" => "\xE5\x91\xB3", + "\xDA\xAC" => "\xE5\xAA\x9A", + "\xDA\xAD" => "\xE5\xB0\xBE", + "\xDA\xAE" => "\xE5\xB5\x8B", + "\xDA\xAF" => "\xE5\xBD\x8C", + "\xDA\xB0" => "\xE5\xBE\xAE", + "\xDA\xB1" => "\xE6\x9C\xAA", + "\xDA\xB2" => "\xE6\xA2\xB6", + "\xDA\xB3" => "\xE6\xA5\xA3", + "\xDA\xB4" => "\xE6\xB8\xBC", + "\xDA\xB5" => "\xE6\xB9\x84", + "\xDA\xB6" => "\xE7\x9C\x89", + "\xDA\xB7" => "\xE7\xB1\xB3", + "\xDA\xB8" => "\xE7\xBE\x8E", + "\xDA\xB9" => "\xE8\x96\x87", + "\xDA\xBA" => "\xE8\xAC\x8E", + "\xDA\xBB" => "\xE8\xBF\xB7", + "\xDA\xBC" => "\xE9\x9D\xA1", + "\xDA\xBD" => "\xE9\xBB\xB4", + "\xDA\xBE" => "\xE5\xB2\xB7", + "\xDA\xBF" => "\xE6\x82\xB6", + "\xDA\xC0" => "\xE6\x84\x8D", + "\xDA\xC1" => "\xE6\x86\xAB", + "\xDA\xC2" => "\xE6\x95\x8F", + "\xDA\xC3" => "\xE6\x97\xBB", + "\xDA\xC4" => "\xE6\x97\xBC", + "\xDA\xC5" => "\xE6\xB0\x91", + "\xDA\xC6" => "\xE6\xB3\xAF", + "\xDA\xC7" => "\xE7\x8E\x9F", + "\xDA\xC8" => "\xE7\x8F\x89", + "\xDA\xC9" => "\xE7\xB7\xA1", + "\xDA\xCA" => "\xE9\x96\x94", + "\xDA\xCB" => "\xE5\xAF\x86", + "\xDA\xCC" => "\xE8\x9C\x9C", + "\xDA\xCD" => "\xE8\xAC\x90", + "\xDA\xCE" => "\xE5\x89\x9D", + "\xDA\xCF" => "\xE5\x8D\x9A", + "\xDA\xD0" => "\xE6\x8B\x8D", + "\xDA\xD1" => "\xE6\x90\x8F", + "\xDA\xD2" => "\xE6\x92\xB2", + "\xDA\xD3" => "\xE6\x9C\xB4", + "\xDA\xD4" => "\xE6\xA8\xB8", + "\xDA\xD5" => "\xE6\xB3\x8A", + "\xDA\xD6" => "\xE7\x8F\x80", + "\xDA\xD7" => "\xE7\x92\x9E", + "\xDA\xD8" => "\xE7\xAE\x94", + "\xDA\xD9" => "\xE7\xB2\x95", + "\xDA\xDA" => "\xE7\xB8\x9B", + "\xDA\xDB" => "\xE8\x86\x8A", + "\xDA\xDC" => "\xE8\x88\xB6", + "\xDA\xDD" => "\xE8\x96\x84", + "\xDA\xDE" => "\xE8\xBF\xAB", + "\xDA\xDF" => "\xE9\x9B\xB9", + "\xDA\xE0" => "\xE9\xA7\x81", + "\xDA\xE1" => "\xE4\xBC\xB4", + "\xDA\xE2" => "\xE5\x8D\x8A", + "\xDA\xE3" => "\xE5\x8F\x8D", + "\xDA\xE4" => "\xE5\x8F\x9B", + "\xDA\xE5" => "\xE6\x8B\x8C", + "\xDA\xE6" => "\xE6\x90\xAC", + "\xDA\xE7" => "\xE6\x94\x80", + "\xDA\xE8" => "\xE6\x96\x91", + "\xDA\xE9" => "\xE6\xA7\x83", + "\xDA\xEA" => "\xE6\xB3\xAE", + "\xDA\xEB" => "\xE6\xBD\x98", + "\xDA\xEC" => "\xE7\x8F\xAD", + "\xDA\xED" => "\xE7\x95\x94", + "\xDA\xEE" => "\xE7\x98\xA2", + "\xDA\xEF" => "\xE7\x9B\xA4", + "\xDA\xF0" => "\xE7\x9B\xBC", + "\xDA\xF1" => "\xE7\xA3\x90", + "\xDA\xF2" => "\xE7\xA3\xBB", + "\xDA\xF3" => "\xE7\xA4\xAC", + "\xDA\xF4" => "\xE7\xB5\x86", + "\xDA\xF5" => "\xE8\x88\xAC", + "\xDA\xF6" => "\xE8\x9F\xA0", + "\xDA\xF7" => "\xE8\xBF\x94", + "\xDA\xF8" => "\xE9\xA0\x92", + "\xDA\xF9" => "\xE9\xA3\xAF", + "\xDA\xFA" => "\xE5\x8B\x83", + "\xDA\xFB" => "\xE6\x8B\x94", + "\xDA\xFC" => "\xE6\x92\xA5", + "\xDA\xFD" => "\xE6\xB8\xA4", + "\xDA\xFE" => "\xE6\xBD\x91", + "\xDB\xA1" => "\xE7\x99\xBC", + "\xDB\xA2" => "\xE8\xB7\x8B", + "\xDB\xA3" => "\xE9\x86\xB1", + "\xDB\xA4" => "\xE9\x89\xA2", + "\xDB\xA5" => "\xE9\xAB\xAE", + "\xDB\xA6" => "\xE9\xAD\x83", + "\xDB\xA7" => "\xE5\x80\xA3", + "\xDB\xA8" => "\xE5\x82\x8D", + "\xDB\xA9" => "\xE5\x9D\x8A", + "\xDB\xAA" => "\xE5\xA6\xA8", + "\xDB\xAB" => "\xE5\xB0\xA8", + "\xDB\xAC" => "\xE5\xB9\x87", + "\xDB\xAD" => "\xE5\xBD\xB7", + "\xDB\xAE" => "\xE6\x88\xBF", + "\xDB\xAF" => "\xE6\x94\xBE", + "\xDB\xB0" => "\xE6\x96\xB9", + "\xDB\xB1" => "\xE6\x97\x81", + "\xDB\xB2" => "\xE6\x98\x89", + "\xDB\xB3" => "\xE6\x9E\x8B", + "\xDB\xB4" => "\xE6\xA6\x9C", + "\xDB\xB5" => "\xE6\xBB\x82", + "\xDB\xB6" => "\xE7\xA3\x85", + "\xDB\xB7" => "\xE7\xB4\xA1", + "\xDB\xB8" => "\xE8\x82\xAA", + "\xDB\xB9" => "\xE8\x86\x80", + "\xDB\xBA" => "\xE8\x88\xAB", + "\xDB\xBB" => "\xE8\x8A\xB3", + "\xDB\xBC" => "\xE8\x92\xA1", + "\xDB\xBD" => "\xE8\x9A\x8C", + "\xDB\xBE" => "\xE8\xA8\xAA", + "\xDB\xBF" => "\xE8\xAC\x97", + "\xDB\xC0" => "\xE9\x82\xA6", + "\xDB\xC1" => "\xE9\x98\xB2", + "\xDB\xC2" => "\xE9\xBE\x90", + "\xDB\xC3" => "\xE5\x80\x8D", + "\xDB\xC4" => "\xE4\xBF\xB3", + "\xDB\xC5" => "\xEF\xA5\xA3", + "\xDB\xC6" => "\xE5\x9F\xB9", + "\xDB\xC7" => "\xE5\xBE\x98", + "\xDB\xC8" => "\xE6\x8B\x9C", + "\xDB\xC9" => "\xE6\x8E\x92", + "\xDB\xCA" => "\xE6\x9D\xAF", + "\xDB\xCB" => "\xE6\xB9\x83", + "\xDB\xCC" => "\xE7\x84\x99", + "\xDB\xCD" => "\xE7\x9B\x83", + "\xDB\xCE" => "\xE8\x83\x8C", + "\xDB\xCF" => "\xE8\x83\x9A", + "\xDB\xD0" => "\xE8\xA3\xB4", + "\xDB\xD1" => "\xE8\xA3\xB5", + "\xDB\xD2" => "\xE8\xA4\x99", + "\xDB\xD3" => "\xE8\xB3\xA0", + "\xDB\xD4" => "\xE8\xBC\xA9", + "\xDB\xD5" => "\xE9\x85\x8D", + "\xDB\xD6" => "\xE9\x99\xAA", + "\xDB\xD7" => "\xE4\xBC\xAF", + "\xDB\xD8" => "\xE4\xBD\xB0", + "\xDB\xD9" => "\xE5\xB8\x9B", + "\xDB\xDA" => "\xE6\x9F\x8F", + "\xDB\xDB" => "\xE6\xA0\xA2", + "\xDB\xDC" => "\xE7\x99\xBD", + "\xDB\xDD" => "\xE7\x99\xBE", + "\xDB\xDE" => "\xE9\xAD\x84", + "\xDB\xDF" => "\xE5\xB9\xA1", + "\xDB\xE0" => "\xE6\xA8\x8A", + "\xDB\xE1" => "\xE7\x85\xA9", + "\xDB\xE2" => "\xE7\x87\x94", + "\xDB\xE3" => "\xE7\x95\xAA", + "\xDB\xE4" => "\xEF\xA5\xA4", + "\xDB\xE5" => "\xE7\xB9\x81", + "\xDB\xE6" => "\xE8\x95\x83", + "\xDB\xE7" => "\xE8\x97\xA9", + "\xDB\xE8" => "\xE9\xA3\x9C", + "\xDB\xE9" => "\xE4\xBC\x90", + "\xDB\xEA" => "\xE7\xAD\x8F", + "\xDB\xEB" => "\xE7\xBD\xB0", + "\xDB\xEC" => "\xE9\x96\xA5", + "\xDB\xED" => "\xE5\x87\xA1", + "\xDB\xEE" => "\xE5\xB8\x86", + "\xDB\xEF" => "\xE6\xA2\xB5", + "\xDB\xF0" => "\xE6\xB0\xBE", + "\xDB\xF1" => "\xE6\xB1\x8E", + "\xDB\xF2" => "\xE6\xB3\x9B", + "\xDB\xF3" => "\xE7\x8A\xAF", + "\xDB\xF4" => "\xE7\xAF\x84", + "\xDB\xF5" => "\xE8\x8C\x83", + "\xDB\xF6" => "\xE6\xB3\x95", + "\xDB\xF7" => "\xE7\x90\xBA", + "\xDB\xF8" => "\xE5\x83\xBB", + "\xDB\xF9" => "\xE5\x8A\x88", + "\xDB\xFA" => "\xE5\xA3\x81", + "\xDB\xFB" => "\xE6\x93\x98", + "\xDB\xFC" => "\xE6\xAA\x97", + "\xDB\xFD" => "\xE7\x92\xA7", + "\xDB\xFE" => "\xE7\x99\x96", + "\xDC\xA1" => "\xE7\xA2\xA7", + "\xDC\xA2" => "\xE8\x98\x97", + "\xDC\xA3" => "\xE9\x97\xA2", + "\xDC\xA4" => "\xE9\x9C\xB9", + "\xDC\xA5" => "\xEF\xA5\xA5", + "\xDC\xA6" => "\xE5\x8D\x9E", + "\xDC\xA7" => "\xE5\xBC\x81", + "\xDC\xA8" => "\xE8\xAE\x8A", + "\xDC\xA9" => "\xE8\xBE\xA8", + "\xDC\xAA" => "\xE8\xBE\xAF", + "\xDC\xAB" => "\xE9\x82\x8A", + "\xDC\xAC" => "\xE5\x88\xA5", + "\xDC\xAD" => "\xE7\x9E\xA5", + "\xDC\xAE" => "\xE9\xB1\x89", + "\xDC\xAF" => "\xE9\xBC\x88", + "\xDC\xB0" => "\xE4\xB8\x99", + "\xDC\xB1" => "\xE5\x80\x82", + "\xDC\xB2" => "\xE5\x85\xB5", + "\xDC\xB3" => "\xE5\xB1\x9B", + "\xDC\xB4" => "\xE5\xB9\xB7", + "\xDC\xB5" => "\xE6\x98\x9E", + "\xDC\xB6" => "\xE6\x98\xBA", + "\xDC\xB7" => "\xE6\x9F\x84", + "\xDC\xB8" => "\xE6\xA3\x85", + "\xDC\xB9" => "\xE7\x82\xB3", + "\xDC\xBA" => "\xE7\x94\x81", + "\xDC\xBB" => "\xE7\x97\x85", + "\xDC\xBC" => "\xE7\xA7\x89", + "\xDC\xBD" => "\xE7\xAB\x9D", + "\xDC\xBE" => "\xE8\xBC\xA7", + "\xDC\xBF" => "\xE9\xA4\xA0", + "\xDC\xC0" => "\xE9\xA8\x88", + "\xDC\xC1" => "\xE4\xBF\x9D", + "\xDC\xC2" => "\xE5\xA0\xA1", + "\xDC\xC3" => "\xE5\xA0\xB1", + "\xDC\xC4" => "\xE5\xAF\xB6", + "\xDC\xC5" => "\xE6\x99\xAE", + "\xDC\xC6" => "\xE6\xAD\xA5", + "\xDC\xC7" => "\xE6\xB4\x91", + "\xDC\xC8" => "\xE6\xB9\xBA", + "\xDC\xC9" => "\xE6\xBD\xBD", + "\xDC\xCA" => "\xE7\x8F\xA4", + "\xDC\xCB" => "\xE7\x94\xAB", + "\xDC\xCC" => "\xE8\x8F\xA9", + "\xDC\xCD" => "\xE8\xA3\x9C", + "\xDC\xCE" => "\xE8\xA4\x93", + "\xDC\xCF" => "\xE8\xAD\x9C", + "\xDC\xD0" => "\xE8\xBC\x94", + "\xDC\xD1" => "\xE4\xBC\x8F", + "\xDC\xD2" => "\xE5\x83\x95", + "\xDC\xD3" => "\xE5\x8C\x90", + "\xDC\xD4" => "\xE5\x8D\x9C", + "\xDC\xD5" => "\xE5\xAE\x93", + "\xDC\xD6" => "\xE5\xBE\xA9", + "\xDC\xD7" => "\xE6\x9C\x8D", + "\xDC\xD8" => "\xE7\xA6\x8F", + "\xDC\xD9" => "\xE8\x85\xB9", + "\xDC\xDA" => "\xE8\x8C\xAF", + "\xDC\xDB" => "\xE8\x94\x94", + "\xDC\xDC" => "\xE8\xA4\x87", + "\xDC\xDD" => "\xE8\xA6\x86", + "\xDC\xDE" => "\xE8\xBC\xB9", + "\xDC\xDF" => "\xE8\xBC\xBB", + "\xDC\xE0" => "\xE9\xA6\xA5", + "\xDC\xE1" => "\xE9\xB0\x92", + "\xDC\xE2" => "\xE6\x9C\xAC", + "\xDC\xE3" => "\xE4\xB9\xB6", + "\xDC\xE4" => "\xE4\xBF\xB8", + "\xDC\xE5" => "\xE5\xA5\x89", + "\xDC\xE6" => "\xE5\xB0\x81", + "\xDC\xE7" => "\xE5\xB3\xAF", + "\xDC\xE8" => "\xE5\xB3\xB0", + "\xDC\xE9" => "\xE6\x8D\xA7", + "\xDC\xEA" => "\xE6\xA3\x92", + "\xDC\xEB" => "\xE7\x83\xBD", + "\xDC\xEC" => "\xE7\x86\xA2", + "\xDC\xED" => "\xE7\x90\xAB", + "\xDC\xEE" => "\xE7\xB8\xAB", + "\xDC\xEF" => "\xE8\x93\xAC", + "\xDC\xF0" => "\xE8\x9C\x82", + "\xDC\xF1" => "\xE9\x80\xA2", + "\xDC\xF2" => "\xE9\x8B\x92", + "\xDC\xF3" => "\xE9\xB3\xB3", + "\xDC\xF4" => "\xE4\xB8\x8D", + "\xDC\xF5" => "\xE4\xBB\x98", + "\xDC\xF6" => "\xE4\xBF\xAF", + "\xDC\xF7" => "\xE5\x82\x85", + "\xDC\xF8" => "\xE5\x89\x96", + "\xDC\xF9" => "\xE5\x89\xAF", + "\xDC\xFA" => "\xE5\x90\xA6", + "\xDC\xFB" => "\xE5\x92\x90", + "\xDC\xFC" => "\xE5\x9F\xA0", + "\xDC\xFD" => "\xE5\xA4\xAB", + "\xDC\xFE" => "\xE5\xA9\xA6", + "\xDD\xA1" => "\xE5\xAD\x9A", + "\xDD\xA2" => "\xE5\xAD\xB5", + "\xDD\xA3" => "\xE5\xAF\x8C", + "\xDD\xA4" => "\xE5\xBA\x9C", + "\xDD\xA5" => "\xEF\xA5\xA6", + "\xDD\xA6" => "\xE6\x89\xB6", + "\xDD\xA7" => "\xE6\x95\xB7", + "\xDD\xA8" => "\xE6\x96\xA7", + "\xDD\xA9" => "\xE6\xB5\xAE", + "\xDD\xAA" => "\xE6\xBA\xA5", + "\xDD\xAB" => "\xE7\x88\xB6", + "\xDD\xAC" => "\xE7\xAC\xA6", + "\xDD\xAD" => "\xE7\xB0\xBF", + "\xDD\xAE" => "\xE7\xBC\xB6", + "\xDD\xAF" => "\xE8\x85\x90", + "\xDD\xB0" => "\xE8\x85\x91", + "\xDD\xB1" => "\xE8\x86\x9A", + "\xDD\xB2" => "\xE8\x89\x80", + "\xDD\xB3" => "\xE8\x8A\x99", + "\xDD\xB4" => "\xE8\x8E\xA9", + "\xDD\xB5" => "\xE8\xA8\x83", + "\xDD\xB6" => "\xE8\xB2\xA0", + "\xDD\xB7" => "\xE8\xB3\xA6", + "\xDD\xB8" => "\xE8\xB3\xBB", + "\xDD\xB9" => "\xE8\xB5\xB4", + "\xDD\xBA" => "\xE8\xB6\xBA", + "\xDD\xBB" => "\xE9\x83\xA8", + "\xDD\xBC" => "\xE9\x87\x9C", + "\xDD\xBD" => "\xE9\x98\x9C", + "\xDD\xBE" => "\xE9\x99\x84", + "\xDD\xBF" => "\xE9\xA7\x99", + "\xDD\xC0" => "\xE9\xB3\xA7", + "\xDD\xC1" => "\xE5\x8C\x97", + "\xDD\xC2" => "\xE5\x88\x86", + "\xDD\xC3" => "\xE5\x90\xA9", + "\xDD\xC4" => "\xE5\x99\xB4", + "\xDD\xC5" => "\xE5\xA2\xB3", + "\xDD\xC6" => "\xE5\xA5\x94", + "\xDD\xC7" => "\xE5\xA5\xAE", + "\xDD\xC8" => "\xE5\xBF\xBF", + "\xDD\xC9" => "\xE6\x86\xA4", + "\xDD\xCA" => "\xE6\x89\xAE", + "\xDD\xCB" => "\xE6\x98\x90", + "\xDD\xCC" => "\xE6\xB1\xBE", + "\xDD\xCD" => "\xE7\x84\x9A", + "\xDD\xCE" => "\xE7\x9B\x86", + "\xDD\xCF" => "\xE7\xB2\x89", + "\xDD\xD0" => "\xE7\xB3\x9E", + "\xDD\xD1" => "\xE7\xB4\x9B", + "\xDD\xD2" => "\xE8\x8A\xAC", + "\xDD\xD3" => "\xE8\xB3\x81", + "\xDD\xD4" => "\xE9\x9B\xB0", + "\xDD\xD5" => "\xEF\xA5\xA7", + "\xDD\xD6" => "\xE4\xBD\x9B", + "\xDD\xD7" => "\xE5\xBC\x97", + "\xDD\xD8" => "\xE5\xBD\xBF", + "\xDD\xD9" => "\xE6\x8B\x82", + "\xDD\xDA" => "\xE5\xB4\xA9", + "\xDD\xDB" => "\xE6\x9C\x8B", + "\xDD\xDC" => "\xE6\xA3\x9A", + "\xDD\xDD" => "\xE7\xA1\xBC", + "\xDD\xDE" => "\xE7\xB9\x83", + "\xDD\xDF" => "\xE9\xB5\xAC", + "\xDD\xE0" => "\xE4\xB8\x95", + "\xDD\xE1" => "\xE5\x82\x99", + "\xDD\xE2" => "\xE5\x8C\x95", + "\xDD\xE3" => "\xE5\x8C\xAA", + "\xDD\xE4" => "\xE5\x8D\x91", + "\xDD\xE5" => "\xE5\xA6\x83", + "\xDD\xE6" => "\xE5\xA9\xA2", + "\xDD\xE7" => "\xE5\xBA\x87", + "\xDD\xE8" => "\xE6\x82\xB2", + "\xDD\xE9" => "\xE6\x86\x8A", + "\xDD\xEA" => "\xE6\x89\x89", + "\xDD\xEB" => "\xE6\x89\xB9", + "\xDD\xEC" => "\xE6\x96\x90", + "\xDD\xED" => "\xE6\x9E\x87", + "\xDD\xEE" => "\xE6\xA6\xA7", + "\xDD\xEF" => "\xE6\xAF\x94", + "\xDD\xF0" => "\xE6\xAF\x96", + "\xDD\xF1" => "\xE6\xAF\x97", + "\xDD\xF2" => "\xE6\xAF\x98", + "\xDD\xF3" => "\xE6\xB2\xB8", + "\xDD\xF4" => "\xEF\xA5\xA8", + "\xDD\xF5" => "\xE7\x90\xB5", + "\xDD\xF6" => "\xE7\x97\xBA", + "\xDD\xF7" => "\xE7\xA0\x92", + "\xDD\xF8" => "\xE7\xA2\x91", + "\xDD\xF9" => "\xE7\xA7\x95", + "\xDD\xFA" => "\xE7\xA7\x98", + "\xDD\xFB" => "\xE7\xB2\x83", + "\xDD\xFC" => "\xE7\xB7\x8B", + "\xDD\xFD" => "\xE7\xBF\xA1", + "\xDD\xFE" => "\xE8\x82\xA5", + "\xDE\xA1" => "\xE8\x84\xBE", + "\xDE\xA2" => "\xE8\x87\x82", + "\xDE\xA3" => "\xE8\x8F\xB2", + "\xDE\xA4" => "\xE8\x9C\x9A", + "\xDE\xA5" => "\xE8\xA3\xA8", + "\xDE\xA6" => "\xE8\xAA\xB9", + "\xDE\xA7" => "\xE8\xAD\xAC", + "\xDE\xA8" => "\xE8\xB2\xBB", + "\xDE\xA9" => "\xE9\x84\x99", + "\xDE\xAA" => "\xE9\x9D\x9E", + "\xDE\xAB" => "\xE9\xA3\x9B", + "\xDE\xAC" => "\xE9\xBC\xBB", + "\xDE\xAD" => "\xE5\x9A\xAC", + "\xDE\xAE" => "\xE5\xAC\xAA", + "\xDE\xAF" => "\xE5\xBD\xAC", + "\xDE\xB0" => "\xE6\x96\x8C", + "\xDE\xB1" => "\xE6\xAA\xB3", + "\xDE\xB2" => "\xE6\xAE\xAF", + "\xDE\xB3" => "\xE6\xB5\x9C", + "\xDE\xB4" => "\xE6\xBF\xB1", + "\xDE\xB5" => "\xE7\x80\x95", + "\xDE\xB6" => "\xE7\x89\x9D", + "\xDE\xB7" => "\xE7\x8E\xAD", + "\xDE\xB8" => "\xE8\xB2\xA7", + "\xDE\xB9" => "\xE8\xB3\x93", + "\xDE\xBA" => "\xE9\xA0\xBB", + "\xDE\xBB" => "\xE6\x86\x91", + "\xDE\xBC" => "\xE6\xB0\xB7", + "\xDE\xBD" => "\xE8\x81\x98", + "\xDE\xBE" => "\xE9\xA8\x81", + "\xDE\xBF" => "\xE4\xB9\x8D", + "\xDE\xC0" => "\xE4\xBA\x8B", + "\xDE\xC1" => "\xE4\xBA\x9B", + "\xDE\xC2" => "\xE4\xBB\x95", + "\xDE\xC3" => "\xE4\xBC\xBA", + "\xDE\xC4" => "\xE4\xBC\xBC", + "\xDE\xC5" => "\xE4\xBD\xBF", + "\xDE\xC6" => "\xE4\xBF\x9F", + "\xDE\xC7" => "\xE5\x83\xBF", + "\xDE\xC8" => "\xE5\x8F\xB2", + "\xDE\xC9" => "\xE5\x8F\xB8", + "\xDE\xCA" => "\xE5\x94\x86", + "\xDE\xCB" => "\xE5\x97\xA3", + "\xDE\xCC" => "\xE5\x9B\x9B", + "\xDE\xCD" => "\xE5\xA3\xAB", + "\xDE\xCE" => "\xE5\xA5\xA2", + "\xDE\xCF" => "\xE5\xA8\x91", + "\xDE\xD0" => "\xE5\xAF\xAB", + "\xDE\xD1" => "\xE5\xAF\xBA", + "\xDE\xD2" => "\xE5\xB0\x84", + "\xDE\xD3" => "\xE5\xB7\xB3", + "\xDE\xD4" => "\xE5\xB8\xAB", + "\xDE\xD5" => "\xE5\xBE\x99", + "\xDE\xD6" => "\xE6\x80\x9D", + "\xDE\xD7" => "\xE6\x8D\xA8", + "\xDE\xD8" => "\xE6\x96\x9C", + "\xDE\xD9" => "\xE6\x96\xAF", + "\xDE\xDA" => "\xE6\x9F\xB6", + "\xDE\xDB" => "\xE6\x9F\xBB", + "\xDE\xDC" => "\xE6\xA2\xAD", + "\xDE\xDD" => "\xE6\xAD\xBB", + "\xDE\xDE" => "\xE6\xB2\x99", + "\xDE\xDF" => "\xE6\xB3\x97", + "\xDE\xE0" => "\xE6\xB8\xA3", + "\xDE\xE1" => "\xE7\x80\x89", + "\xDE\xE2" => "\xE7\x8D\x85", + "\xDE\xE3" => "\xE7\xA0\x82", + "\xDE\xE4" => "\xE7\xA4\xBE", + "\xDE\xE5" => "\xE7\xA5\x80", + "\xDE\xE6" => "\xE7\xA5\xA0", + "\xDE\xE7" => "\xE7\xA7\x81", + "\xDE\xE8" => "\xE7\xAF\xA9", + "\xDE\xE9" => "\xE7\xB4\x97", + "\xDE\xEA" => "\xE7\xB5\xB2", + "\xDE\xEB" => "\xE8\x82\x86", + "\xDE\xEC" => "\xE8\x88\x8D", + "\xDE\xED" => "\xE8\x8E\x8E", + "\xDE\xEE" => "\xE8\x93\x91", + "\xDE\xEF" => "\xE8\x9B\x87", + "\xDE\xF0" => "\xE8\xA3\x9F", + "\xDE\xF1" => "\xE8\xA9\x90", + "\xDE\xF2" => "\xE8\xA9\x9E", + "\xDE\xF3" => "\xE8\xAC\x9D", + "\xDE\xF4" => "\xE8\xB3\x9C", + "\xDE\xF5" => "\xE8\xB5\xA6", + "\xDE\xF6" => "\xE8\xBE\xAD", + "\xDE\xF7" => "\xE9\x82\xAA", + "\xDE\xF8" => "\xE9\xA3\xBC", + "\xDE\xF9" => "\xE9\xA7\x9F", + "\xDE\xFA" => "\xE9\xBA\x9D", + "\xDE\xFB" => "\xE5\x89\x8A", + "\xDE\xFC" => "\xEF\xA5\xA9", + "\xDE\xFD" => "\xE6\x9C\x94", + "\xDE\xFE" => "\xEF\xA5\xAA", + "\xDF\xA1" => "\xE5\x82\x98", + "\xDF\xA2" => "\xE5\x88\xAA", + "\xDF\xA3" => "\xE5\xB1\xB1", + "\xDF\xA4" => "\xE6\x95\xA3", + "\xDF\xA5" => "\xE6\xB1\x95", + "\xDF\xA6" => "\xE7\x8F\x8A", + "\xDF\xA7" => "\xE7\x94\xA3", + "\xDF\xA8" => "\xE7\x96\x9D", + "\xDF\xA9" => "\xE7\xAE\x97", + "\xDF\xAA" => "\xE8\x92\x9C", + "\xDF\xAB" => "\xE9\x85\xB8", + "\xDF\xAC" => "\xE9\x9C\xB0", + "\xDF\xAD" => "\xE4\xB9\xB7", + "\xDF\xAE" => "\xE6\x92\x92", + "\xDF\xAF" => "\xE6\xAE\xBA", + "\xDF\xB0" => "\xE7\x85\x9E", + "\xDF\xB1" => "\xE8\x96\xA9", + "\xDF\xB2" => "\xE4\xB8\x89", + "\xDF\xB3" => "\xEF\xA5\xAB", + "\xDF\xB4" => "\xE6\x9D\x89", + "\xDF\xB5" => "\xE6\xA3\xAE", + "\xDF\xB6" => "\xE6\xB8\x97", + "\xDF\xB7" => "\xE8\x8A\x9F", + "\xDF\xB8" => "\xE8\x94\x98", + "\xDF\xB9" => "\xE8\xA1\xAB", + "\xDF\xBA" => "\xE6\x8F\xB7", + "\xDF\xBB" => "\xE6\xBE\x81", + "\xDF\xBC" => "\xE9\x88\x92", + "\xDF\xBD" => "\xE9\xA2\xAF", + "\xDF\xBE" => "\xE4\xB8\x8A", + "\xDF\xBF" => "\xE5\x82\xB7", + "\xDF\xC0" => "\xE5\x83\x8F", + "\xDF\xC1" => "\xE5\x84\x9F", + "\xDF\xC2" => "\xE5\x95\x86", + "\xDF\xC3" => "\xE5\x96\xAA", + "\xDF\xC4" => "\xE5\x98\x97", + "\xDF\xC5" => "\xE5\xAD\x80", + "\xDF\xC6" => "\xE5\xB0\x99", + "\xDF\xC7" => "\xE5\xB3\xA0", + "\xDF\xC8" => "\xE5\xB8\xB8", + "\xDF\xC9" => "\xE5\xBA\x8A", + "\xDF\xCA" => "\xE5\xBA\xA0", + "\xDF\xCB" => "\xE5\xBB\x82", + "\xDF\xCC" => "\xE6\x83\xB3", + "\xDF\xCD" => "\xE6\xA1\x91", + "\xDF\xCE" => "\xE6\xA9\xA1", + "\xDF\xCF" => "\xE6\xB9\x98", + "\xDF\xD0" => "\xE7\x88\xBD", + "\xDF\xD1" => "\xE7\x89\x80", + "\xDF\xD2" => "\xE7\x8B\x80", + "\xDF\xD3" => "\xE7\x9B\xB8", + "\xDF\xD4" => "\xE7\xA5\xA5", + "\xDF\xD5" => "\xE7\xAE\xB1", + "\xDF\xD6" => "\xE7\xBF\x94", + "\xDF\xD7" => "\xE8\xA3\xB3", + "\xDF\xD8" => "\xE8\xA7\xB4", + "\xDF\xD9" => "\xE8\xA9\xB3", + "\xDF\xDA" => "\xE8\xB1\xA1", + "\xDF\xDB" => "\xE8\xB3\x9E", + "\xDF\xDC" => "\xE9\x9C\x9C", + "\xDF\xDD" => "\xE5\xA1\x9E", + "\xDF\xDE" => "\xE7\x92\xBD", + "\xDF\xDF" => "\xE8\xB3\xBD", + "\xDF\xE0" => "\xE5\x97\x87", + "\xDF\xE1" => "\xEF\xA5\xAC", + "\xDF\xE2" => "\xE7\xA9\xA1", + "\xDF\xE3" => "\xE7\xB4\xA2", + "\xDF\xE4" => "\xE8\x89\xB2", + "\xDF\xE5" => "\xE7\x89\xB2", + "\xDF\xE6" => "\xE7\x94\x9F", + "\xDF\xE7" => "\xE7\x94\xA5", + "\xDF\xE8" => "\xEF\xA5\xAD", + "\xDF\xE9" => "\xE7\xAC\x99", + "\xDF\xEA" => "\xE5\xA2\x85", + "\xDF\xEB" => "\xE5\xA3\xBB", + "\xDF\xEC" => "\xE5\xB6\xBC", + "\xDF\xED" => "\xE5\xBA\x8F", + "\xDF\xEE" => "\xE5\xBA\xB6", + "\xDF\xEF" => "\xE5\xBE\x90", + "\xDF\xF0" => "\xE6\x81\x95", + "\xDF\xF1" => "\xE6\x8A\x92", + "\xDF\xF2" => "\xE6\x8D\xBF", + "\xDF\xF3" => "\xE6\x95\x8D", + "\xDF\xF4" => "\xE6\x9A\x91", + "\xDF\xF5" => "\xE6\x9B\x99", + "\xDF\xF6" => "\xE6\x9B\xB8", + "\xDF\xF7" => "\xE6\xA0\x96", + "\xDF\xF8" => "\xE6\xA3\xB2", + "\xDF\xF9" => "\xE7\x8A\x80", + "\xDF\xFA" => "\xE7\x91\x9E", + "\xDF\xFB" => "\xE7\xAD\xAE", + "\xDF\xFC" => "\xE7\xB5\xAE", + "\xDF\xFD" => "\xE7\xB7\x96", + "\xDF\xFE" => "\xE7\xBD\xB2", + "\xE0\xA1" => "\xE8\x83\xA5", + "\xE0\xA2" => "\xE8\x88\x92", + "\xE0\xA3" => "\xE8\x96\xAF", + "\xE0\xA4" => "\xE8\xA5\xBF", + "\xE0\xA5" => "\xE8\xAA\x93", + "\xE0\xA6" => "\xE9\x80\x9D", + "\xE0\xA7" => "\xE9\x8B\xA4", + "\xE0\xA8" => "\xE9\xBB\x8D", + "\xE0\xA9" => "\xE9\xBC\xA0", + "\xE0\xAA" => "\xE5\xA4\x95", + "\xE0\xAB" => "\xE5\xA5\xAD", + "\xE0\xAC" => "\xE5\xB8\xAD", + "\xE0\xAD" => "\xE6\x83\x9C", + "\xE0\xAE" => "\xE6\x98\x94", + "\xE0\xAF" => "\xE6\x99\xB3", + "\xE0\xB0" => "\xE6\x9E\x90", + "\xE0\xB1" => "\xE6\xB1\x90", + "\xE0\xB2" => "\xE6\xB7\x85", + "\xE0\xB3" => "\xE6\xBD\x9F", + "\xE0\xB4" => "\xE7\x9F\xB3", + "\xE0\xB5" => "\xE7\xA2\xA9", + "\xE0\xB6" => "\xE8\x93\x86", + "\xE0\xB7" => "\xE9\x87\x8B", + "\xE0\xB8" => "\xE9\x8C\xAB", + "\xE0\xB9" => "\xE4\xBB\x99", + "\xE0\xBA" => "\xE5\x83\x8A", + "\xE0\xBB" => "\xE5\x85\x88", + "\xE0\xBC" => "\xE5\x96\x84", + "\xE0\xBD" => "\xE5\xAC\x8B", + "\xE0\xBE" => "\xE5\xAE\xA3", + "\xE0\xBF" => "\xE6\x89\x87", + "\xE0\xC0" => "\xE6\x95\xBE", + "\xE0\xC1" => "\xE6\x97\x8B", + "\xE0\xC2" => "\xE6\xB8\xB2", + "\xE0\xC3" => "\xE7\x85\xBD", + "\xE0\xC4" => "\xE7\x90\x81", + "\xE0\xC5" => "\xE7\x91\x84", + "\xE0\xC6" => "\xE7\x92\x87", + "\xE0\xC7" => "\xE7\x92\xBF", + "\xE0\xC8" => "\xE7\x99\xAC", + "\xE0\xC9" => "\xE7\xA6\xAA", + "\xE0\xCA" => "\xE7\xB7\x9A", + "\xE0\xCB" => "\xE7\xB9\x95", + "\xE0\xCC" => "\xE7\xBE\xA8", + "\xE0\xCD" => "\xE8\x85\xBA", + "\xE0\xCE" => "\xE8\x86\xB3", + "\xE0\xCF" => "\xE8\x88\xB9", + "\xE0\xD0" => "\xE8\x98\x9A", + "\xE0\xD1" => "\xE8\x9F\xAC", + "\xE0\xD2" => "\xE8\xA9\xB5", + "\xE0\xD3" => "\xE8\xB7\xA3", + "\xE0\xD4" => "\xE9\x81\xB8", + "\xE0\xD5" => "\xE9\x8A\x91", + "\xE0\xD6" => "\xE9\x90\xA5", + "\xE0\xD7" => "\xE9\xA5\x8D", + "\xE0\xD8" => "\xE9\xAE\xAE", + "\xE0\xD9" => "\xE5\x8D\xA8", + "\xE0\xDA" => "\xE5\xB1\x91", + "\xE0\xDB" => "\xE6\xA5\x94", + "\xE0\xDC" => "\xE6\xB3\x84", + "\xE0\xDD" => "\xE6\xB4\xA9", + "\xE0\xDE" => "\xE6\xB8\xAB", + "\xE0\xDF" => "\xE8\x88\x8C", + "\xE0\xE0" => "\xE8\x96\x9B", + "\xE0\xE1" => "\xE8\xA4\xBB", + "\xE0\xE2" => "\xE8\xA8\xAD", + "\xE0\xE3" => "\xE8\xAA\xAA", + "\xE0\xE4" => "\xE9\x9B\xAA", + "\xE0\xE5" => "\xE9\xBD\xA7", + "\xE0\xE6" => "\xE5\x89\xA1", + "\xE0\xE7" => "\xE6\x9A\xB9", + "\xE0\xE8" => "\xE6\xAE\xB2", + "\xE0\xE9" => "\xE7\xBA\x96", + "\xE0\xEA" => "\xE8\x9F\xBE", + "\xE0\xEB" => "\xE8\xB4\x8D", + "\xE0\xEC" => "\xE9\x96\x83", + "\xE0\xED" => "\xE9\x99\x9D", + "\xE0\xEE" => "\xE6\x94\x9D", + "\xE0\xEF" => "\xE6\xB6\x89", + "\xE0\xF0" => "\xE7\x87\xAE", + "\xE0\xF1" => "\xEF\xA5\xAE", + "\xE0\xF2" => "\xE5\x9F\x8E", + "\xE0\xF3" => "\xE5\xA7\x93", + "\xE0\xF4" => "\xE5\xAE\xAC", + "\xE0\xF5" => "\xE6\x80\xA7", + "\xE0\xF6" => "\xE6\x83\xBA", + "\xE0\xF7" => "\xE6\x88\x90", + "\xE0\xF8" => "\xE6\x98\x9F", + "\xE0\xF9" => "\xE6\x99\x9F", + "\xE0\xFA" => "\xE7\x8C\xA9", + "\xE0\xFB" => "\xE7\x8F\xB9", + "\xE0\xFC" => "\xE7\x9B\x9B", + "\xE0\xFD" => "\xE7\x9C\x81", + "\xE0\xFE" => "\xE7\xAD\xAC", + "\xE1\xA1" => "\xE8\x81\x96", + "\xE1\xA2" => "\xE8\x81\xB2", + "\xE1\xA3" => "\xE8\x85\xA5", + "\xE1\xA4" => "\xE8\xAA\xA0", + "\xE1\xA5" => "\xE9\x86\x92", + "\xE1\xA6" => "\xE4\xB8\x96", + "\xE1\xA7" => "\xE5\x8B\xA2", + "\xE1\xA8" => "\xE6\xAD\xB2", + "\xE1\xA9" => "\xE6\xB4\x97", + "\xE1\xAA" => "\xE7\xA8\x85", + "\xE1\xAB" => "\xE7\xAC\xB9", + "\xE1\xAC" => "\xE7\xB4\xB0", + "\xE1\xAD" => "\xEF\xA5\xAF", + "\xE1\xAE" => "\xE8\xB2\xB0", + "\xE1\xAF" => "\xE5\x8F\xAC", + "\xE1\xB0" => "\xE5\x98\xAF", + "\xE1\xB1" => "\xE5\xA1\x91", + "\xE1\xB2" => "\xE5\xAE\xB5", + "\xE1\xB3" => "\xE5\xB0\x8F", + "\xE1\xB4" => "\xE5\xB0\x91", + "\xE1\xB5" => "\xE5\xB7\xA2", + "\xE1\xB6" => "\xE6\x89\x80", + "\xE1\xB7" => "\xE6\x8E\x83", + "\xE1\xB8" => "\xE6\x90\x94", + "\xE1\xB9" => "\xE6\x98\xAD", + "\xE1\xBA" => "\xE6\xA2\xB3", + "\xE1\xBB" => "\xE6\xB2\xBC", + "\xE1\xBC" => "\xE6\xB6\x88", + "\xE1\xBD" => "\xE6\xBA\xAF", + "\xE1\xBE" => "\xE7\x80\x9F", + "\xE1\xBF" => "\xE7\x82\xA4", + "\xE1\xC0" => "\xE7\x87\x92", + "\xE1\xC1" => "\xE7\x94\xA6", + "\xE1\xC2" => "\xE7\x96\x8F", + "\xE1\xC3" => "\xE7\x96\x8E", + "\xE1\xC4" => "\xE7\x98\x99", + "\xE1\xC5" => "\xE7\xAC\x91", + "\xE1\xC6" => "\xE7\xAF\xA0", + "\xE1\xC7" => "\xE7\xB0\xAB", + "\xE1\xC8" => "\xE7\xB4\xA0", + "\xE1\xC9" => "\xE7\xB4\xB9", + "\xE1\xCA" => "\xE8\x94\xAC", + "\xE1\xCB" => "\xE8\x95\xAD", + "\xE1\xCC" => "\xE8\x98\x87", + "\xE1\xCD" => "\xE8\xA8\xB4", + "\xE1\xCE" => "\xE9\x80\x8D", + "\xE1\xCF" => "\xE9\x81\xA1", + "\xE1\xD0" => "\xE9\x82\xB5", + "\xE1\xD1" => "\xE9\x8A\xB7", + "\xE1\xD2" => "\xE9\x9F\xB6", + "\xE1\xD3" => "\xE9\xA8\xB7", + "\xE1\xD4" => "\xE4\xBF\x97", + "\xE1\xD5" => "\xE5\xB1\xAC", + "\xE1\xD6" => "\xE6\x9D\x9F", + "\xE1\xD7" => "\xE6\xB6\x91", + "\xE1\xD8" => "\xE7\xB2\x9F", + "\xE1\xD9" => "\xE7\xBA\x8C", + "\xE1\xDA" => "\xE8\xAC\x96", + "\xE1\xDB" => "\xE8\xB4\x96", + "\xE1\xDC" => "\xE9\x80\x9F", + "\xE1\xDD" => "\xE5\xAD\xAB", + "\xE1\xDE" => "\xE5\xB7\xBD", + "\xE1\xDF" => "\xE6\x90\x8D", + "\xE1\xE0" => "\xE8\x93\x80", + "\xE1\xE1" => "\xE9\x81\x9C", + "\xE1\xE2" => "\xE9\xA3\xA1", + "\xE1\xE3" => "\xE7\x8E\x87", + "\xE1\xE4" => "\xE5\xAE\x8B", + "\xE1\xE5" => "\xE6\x82\x9A", + "\xE1\xE6" => "\xE6\x9D\xBE", + "\xE1\xE7" => "\xE6\xB7\x9E", + "\xE1\xE8" => "\xE8\xA8\x9F", + "\xE1\xE9" => "\xE8\xAA\xA6", + "\xE1\xEA" => "\xE9\x80\x81", + "\xE1\xEB" => "\xE9\xA0\x8C", + "\xE1\xEC" => "\xE5\x88\xB7", + "\xE1\xED" => "\xEF\xA5\xB0", + "\xE1\xEE" => "\xE7\x81\x91", + "\xE1\xEF" => "\xE7\xA2\x8E", + "\xE1\xF0" => "\xE9\x8E\x96", + "\xE1\xF1" => "\xE8\xA1\xB0", + "\xE1\xF2" => "\xE9\x87\x97", + "\xE1\xF3" => "\xE4\xBF\xAE", + "\xE1\xF4" => "\xE5\x8F\x97", + "\xE1\xF5" => "\xE5\x97\xBD", + "\xE1\xF6" => "\xE5\x9B\x9A", + "\xE1\xF7" => "\xE5\x9E\x82", + "\xE1\xF8" => "\xE5\xA3\xBD", + "\xE1\xF9" => "\xE5\xAB\x82", + "\xE1\xFA" => "\xE5\xAE\x88", + "\xE1\xFB" => "\xE5\xB2\xAB", + "\xE1\xFC" => "\xE5\xB3\x80", + "\xE1\xFD" => "\xE5\xB8\xA5", + "\xE1\xFE" => "\xE6\x84\x81", + "\xE2\xA1" => "\xE6\x88\x8D", + "\xE2\xA2" => "\xE6\x89\x8B", + "\xE2\xA3" => "\xE6\x8E\x88", + "\xE2\xA4" => "\xE6\x90\x9C", + "\xE2\xA5" => "\xE6\x94\xB6", + "\xE2\xA6" => "\xE6\x95\xB8", + "\xE2\xA7" => "\xE6\xA8\xB9", + "\xE2\xA8" => "\xE6\xAE\x8A", + "\xE2\xA9" => "\xE6\xB0\xB4", + "\xE2\xAA" => "\xE6\xB4\x99", + "\xE2\xAB" => "\xE6\xBC\xB1", + "\xE2\xAC" => "\xE7\x87\xA7", + "\xE2\xAD" => "\xE7\x8B\xA9", + "\xE2\xAE" => "\xE7\x8D\xB8", + "\xE2\xAF" => "\xE7\x90\x87", + "\xE2\xB0" => "\xE7\x92\xB2", + "\xE2\xB1" => "\xE7\x98\xA6", + "\xE2\xB2" => "\xE7\x9D\xA1", + "\xE2\xB3" => "\xE7\xA7\x80", + "\xE2\xB4" => "\xE7\xA9\x97", + "\xE2\xB5" => "\xE7\xAB\xAA", + "\xE2\xB6" => "\xE7\xB2\xB9", + "\xE2\xB7" => "\xE7\xB6\x8F", + "\xE2\xB8" => "\xE7\xB6\xAC", + "\xE2\xB9" => "\xE7\xB9\xA1", + "\xE2\xBA" => "\xE7\xBE\x9E", + "\xE2\xBB" => "\xE8\x84\xA9", + "\xE2\xBC" => "\xE8\x8C\xB1", + "\xE2\xBD" => "\xE8\x92\x90", + "\xE2\xBE" => "\xE8\x93\x9A", + "\xE2\xBF" => "\xE8\x97\xAA", + "\xE2\xC0" => "\xE8\xA2\x96", + "\xE2\xC1" => "\xE8\xAA\xB0", + "\xE2\xC2" => "\xE8\xAE\x90", + "\xE2\xC3" => "\xE8\xBC\xB8", + "\xE2\xC4" => "\xE9\x81\x82", + "\xE2\xC5" => "\xE9\x82\x83", + "\xE2\xC6" => "\xE9\x85\xAC", + "\xE2\xC7" => "\xE9\x8A\x96", + "\xE2\xC8" => "\xE9\x8A\xB9", + "\xE2\xC9" => "\xE9\x9A\x8B", + "\xE2\xCA" => "\xE9\x9A\xA7", + "\xE2\xCB" => "\xE9\x9A\xA8", + "\xE2\xCC" => "\xE9\x9B\x96", + "\xE2\xCD" => "\xE9\x9C\x80", + "\xE2\xCE" => "\xE9\xA0\x88", + "\xE2\xCF" => "\xE9\xA6\x96", + "\xE2\xD0" => "\xE9\xAB\x93", + "\xE2\xD1" => "\xE9\xAC\x9A", + "\xE2\xD2" => "\xE5\x8F\x94", + "\xE2\xD3" => "\xE5\xA1\xBE", + "\xE2\xD4" => "\xE5\xA4\x99", + "\xE2\xD5" => "\xE5\xAD\xB0", + "\xE2\xD6" => "\xE5\xAE\xBF", + "\xE2\xD7" => "\xE6\xB7\x91", + "\xE2\xD8" => "\xE6\xBD\x9A", + "\xE2\xD9" => "\xE7\x86\x9F", + "\xE2\xDA" => "\xE7\x90\xA1", + "\xE2\xDB" => "\xE7\x92\xB9", + "\xE2\xDC" => "\xE8\x82\x85", + "\xE2\xDD" => "\xE8\x8F\xBD", + "\xE2\xDE" => "\xE5\xB7\xA1", + "\xE2\xDF" => "\xE5\xBE\x87", + "\xE2\xE0" => "\xE5\xBE\xAA", + "\xE2\xE1" => "\xE6\x81\x82", + "\xE2\xE2" => "\xE6\x97\xAC", + "\xE2\xE3" => "\xE6\xA0\x92", + "\xE2\xE4" => "\xE6\xA5\xAF", + "\xE2\xE5" => "\xE6\xA9\x93", + "\xE2\xE6" => "\xE6\xAE\x89", + "\xE2\xE7" => "\xE6\xB4\xB5", + "\xE2\xE8" => "\xE6\xB7\xB3", + "\xE2\xE9" => "\xE7\x8F\xA3", + "\xE2\xEA" => "\xE7\x9B\xBE", + "\xE2\xEB" => "\xE7\x9E\xAC", + "\xE2\xEC" => "\xE7\xAD\x8D", + "\xE2\xED" => "\xE7\xB4\x94", + "\xE2\xEE" => "\xE8\x84\xA3", + "\xE2\xEF" => "\xE8\x88\x9C", + "\xE2\xF0" => "\xE8\x8D\x80", + "\xE2\xF1" => "\xE8\x93\xB4", + "\xE2\xF2" => "\xE8\x95\xA3", + "\xE2\xF3" => "\xE8\xA9\xA2", + "\xE2\xF4" => "\xE8\xAB\x84", + "\xE2\xF5" => "\xE9\x86\x87", + "\xE2\xF6" => "\xE9\x8C\x9E", + "\xE2\xF7" => "\xE9\xA0\x86", + "\xE2\xF8" => "\xE9\xA6\xB4", + "\xE2\xF9" => "\xE6\x88\x8C", + "\xE2\xFA" => "\xE8\xA1\x93", + "\xE2\xFB" => "\xE8\xBF\xB0", + "\xE2\xFC" => "\xE9\x89\xA5", + "\xE2\xFD" => "\xE5\xB4\x87", + "\xE2\xFE" => "\xE5\xB4\xA7", + "\xE3\xA1" => "\xE5\xB5\xA9", + "\xE3\xA2" => "\xE7\x91\x9F", + "\xE3\xA3" => "\xE8\x86\x9D", + "\xE3\xA4" => "\xE8\x9D\xA8", + "\xE3\xA5" => "\xE6\xBF\x95", + "\xE3\xA6" => "\xE6\x8B\xBE", + "\xE3\xA7" => "\xE7\xBF\x92", + "\xE3\xA8" => "\xE8\xA4\xB6", + "\xE3\xA9" => "\xE8\xA5\xB2", + "\xE3\xAA" => "\xE4\xB8\x9E", + "\xE3\xAB" => "\xE4\xB9\x98", + "\xE3\xAC" => "\xE5\x83\xA7", + "\xE3\xAD" => "\xE5\x8B\x9D", + "\xE3\xAE" => "\xE5\x8D\x87", + "\xE3\xAF" => "\xE6\x89\xBF", + "\xE3\xB0" => "\xE6\x98\x87", + "\xE3\xB1" => "\xE7\xB9\xA9", + "\xE3\xB2" => "\xE8\xA0\x85", + "\xE3\xB3" => "\xE9\x99\x9E", + "\xE3\xB4" => "\xE4\xBE\x8D", + "\xE3\xB5" => "\xE5\x8C\x99", + "\xE3\xB6" => "\xE5\x98\xB6", + "\xE3\xB7" => "\xE5\xA7\x8B", + "\xE3\xB8" => "\xE5\xAA\xA4", + "\xE3\xB9" => "\xE5\xB0\xB8", + "\xE3\xBA" => "\xE5\xB1\x8E", + "\xE3\xBB" => "\xE5\xB1\x8D", + "\xE3\xBC" => "\xE5\xB8\x82", + "\xE3\xBD" => "\xE5\xBC\x91", + "\xE3\xBE" => "\xE6\x81\x83", + "\xE3\xBF" => "\xE6\x96\xBD", + "\xE3\xC0" => "\xE6\x98\xAF", + "\xE3\xC1" => "\xE6\x99\x82", + "\xE3\xC2" => "\xE6\x9E\xBE", + "\xE3\xC3" => "\xE6\x9F\xB4", + "\xE3\xC4" => "\xE7\x8C\x9C", + "\xE3\xC5" => "\xE7\x9F\xA2", + "\xE3\xC6" => "\xE7\xA4\xBA", + "\xE3\xC7" => "\xE7\xBF\x85", + "\xE3\xC8" => "\xE8\x92\x94", + "\xE3\xC9" => "\xE8\x93\x8D", + "\xE3\xCA" => "\xE8\xA6\x96", + "\xE3\xCB" => "\xE8\xA9\xA6", + "\xE3\xCC" => "\xE8\xA9\xA9", + "\xE3\xCD" => "\xE8\xAB\xA1", + "\xE3\xCE" => "\xE8\xB1\x95", + "\xE3\xCF" => "\xE8\xB1\xBA", + "\xE3\xD0" => "\xE5\x9F\xB4", + "\xE3\xD1" => "\xE5\xAF\x94", + "\xE3\xD2" => "\xE5\xBC\x8F", + "\xE3\xD3" => "\xE6\x81\xAF", + "\xE3\xD4" => "\xE6\x8B\xAD", + "\xE3\xD5" => "\xE6\xA4\x8D", + "\xE3\xD6" => "\xE6\xAE\x96", + "\xE3\xD7" => "\xE6\xB9\x9C", + "\xE3\xD8" => "\xE7\x86\x84", + "\xE3\xD9" => "\xE7\xAF\x92", + "\xE3\xDA" => "\xE8\x9D\x95", + "\xE3\xDB" => "\xE8\xAD\x98", + "\xE3\xDC" => "\xE8\xBB\xBE", + "\xE3\xDD" => "\xE9\xA3\x9F", + "\xE3\xDE" => "\xE9\xA3\xBE", + "\xE3\xDF" => "\xE4\xBC\xB8", + "\xE3\xE0" => "\xE4\xBE\x81", + "\xE3\xE1" => "\xE4\xBF\xA1", + "\xE3\xE2" => "\xE5\x91\xBB", + "\xE3\xE3" => "\xE5\xA8\xA0", + "\xE3\xE4" => "\xE5\xAE\xB8", + "\xE3\xE5" => "\xE6\x84\xBC", + "\xE3\xE6" => "\xE6\x96\xB0", + "\xE3\xE7" => "\xE6\x99\xA8", + "\xE3\xE8" => "\xE7\x87\xBC", + "\xE3\xE9" => "\xE7\x94\xB3", + "\xE3\xEA" => "\xE7\xA5\x9E", + "\xE3\xEB" => "\xE7\xB4\xB3", + "\xE3\xEC" => "\xE8\x85\x8E", + "\xE3\xED" => "\xE8\x87\xA3", + "\xE3\xEE" => "\xE8\x8E\x98", + "\xE3\xEF" => "\xE8\x96\xAA", + "\xE3\xF0" => "\xE8\x97\x8E", + "\xE3\xF1" => "\xE8\x9C\x83", + "\xE3\xF2" => "\xE8\xA8\x8A", + "\xE3\xF3" => "\xE8\xBA\xAB", + "\xE3\xF4" => "\xE8\xBE\x9B", + "\xE3\xF5" => "\xEF\xA5\xB1", + "\xE3\xF6" => "\xE8\xBF\x85", + "\xE3\xF7" => "\xE5\xA4\xB1", + "\xE3\xF8" => "\xE5\xAE\xA4", + "\xE3\xF9" => "\xE5\xAF\xA6", + "\xE3\xFA" => "\xE6\x82\x89", + "\xE3\xFB" => "\xE5\xAF\xA9", + "\xE3\xFC" => "\xE5\xB0\x8B", + "\xE3\xFD" => "\xE5\xBF\x83", + "\xE3\xFE" => "\xE6\xB2\x81", + "\xE4\xA1" => "\xEF\xA5\xB2", + "\xE4\xA2" => "\xE6\xB7\xB1", + "\xE4\xA3" => "\xE7\x80\x8B", + "\xE4\xA4" => "\xE7\x94\x9A", + "\xE4\xA5" => "\xE8\x8A\xAF", + "\xE4\xA6" => "\xE8\xAB\xB6", + "\xE4\xA7" => "\xE4\xBB\x80", + "\xE4\xA8" => "\xE5\x8D\x81", + "\xE4\xA9" => "\xEF\xA5\xB3", + "\xE4\xAA" => "\xE9\x9B\x99", + "\xE4\xAB" => "\xE6\xB0\x8F", + "\xE4\xAC" => "\xE4\xBA\x9E", + "\xE4\xAD" => "\xE4\xBF\x84", + "\xE4\xAE" => "\xE5\x85\x92", + "\xE4\xAF" => "\xE5\x95\x9E", + "\xE4\xB0" => "\xE5\xA8\xA5", + "\xE4\xB1" => "\xE5\xB3\xA8", + "\xE4\xB2" => "\xE6\x88\x91", + "\xE4\xB3" => "\xE7\x89\x99", + "\xE4\xB4" => "\xE8\x8A\xBD", + "\xE4\xB5" => "\xE8\x8E\xAA", + "\xE4\xB6" => "\xE8\x9B\xBE", + "\xE4\xB7" => "\xE8\xA1\x99", + "\xE4\xB8" => "\xE8\xA8\x9D", + "\xE4\xB9" => "\xE9\x98\xBF", + "\xE4\xBA" => "\xE9\x9B\x85", + "\xE4\xBB" => "\xE9\xA4\x93", + "\xE4\xBC" => "\xE9\xB4\x89", + "\xE4\xBD" => "\xE9\xB5\x9D", + "\xE4\xBE" => "\xE5\xA0\x8A", + "\xE4\xBF" => "\xE5\xB2\xB3", + "\xE4\xC0" => "\xE5\xB6\xBD", + "\xE4\xC1" => "\xE5\xB9\x84", + "\xE4\xC2" => "\xE6\x83\xA1", + "\xE4\xC3" => "\xE6\x84\x95", + "\xE4\xC4" => "\xE6\x8F\xA1", + "\xE4\xC5" => "\xE6\xA8\x82", + "\xE4\xC6" => "\xE6\xB8\xA5", + "\xE4\xC7" => "\xE9\x84\x82", + "\xE4\xC8" => "\xE9\x8D\x94", + "\xE4\xC9" => "\xE9\xA1\x8E", + "\xE4\xCA" => "\xE9\xB0\x90", + "\xE4\xCB" => "\xE9\xBD\xB7", + "\xE4\xCC" => "\xE5\xAE\x89", + "\xE4\xCD" => "\xE5\xB2\xB8", + "\xE4\xCE" => "\xE6\x8C\x89", + "\xE4\xCF" => "\xE6\x99\x8F", + "\xE4\xD0" => "\xE6\xA1\x88", + "\xE4\xD1" => "\xE7\x9C\xBC", + "\xE4\xD2" => "\xE9\x9B\x81", + "\xE4\xD3" => "\xE9\x9E\x8D", + "\xE4\xD4" => "\xE9\xA1\x94", + "\xE4\xD5" => "\xE9\xAE\x9F", + "\xE4\xD6" => "\xE6\x96\xA1", + "\xE4\xD7" => "\xE8\xAC\x81", + "\xE4\xD8" => "\xE8\xBB\x8B", + "\xE4\xD9" => "\xE9\x96\xBC", + "\xE4\xDA" => "\xE5\x94\xB5", + "\xE4\xDB" => "\xE5\xB2\xA9", + "\xE4\xDC" => "\xE5\xB7\x96", + "\xE4\xDD" => "\xE5\xBA\xB5", + "\xE4\xDE" => "\xE6\x9A\x97", + "\xE4\xDF" => "\xE7\x99\x8C", + "\xE4\xE0" => "\xE8\x8F\xB4", + "\xE4\xE1" => "\xE9\x97\x87", + "\xE4\xE2" => "\xE5\xA3\x93", + "\xE4\xE3" => "\xE6\x8A\xBC", + "\xE4\xE4" => "\xE7\x8B\x8E", + "\xE4\xE5" => "\xE9\xB4\xA8", + "\xE4\xE6" => "\xE4\xBB\xB0", + "\xE4\xE7" => "\xE5\xA4\xAE", + "\xE4\xE8" => "\xE6\x80\x8F", + "\xE4\xE9" => "\xE6\x98\xBB", + "\xE4\xEA" => "\xE6\xAE\x83", + "\xE4\xEB" => "\xE7\xA7\xA7", + "\xE4\xEC" => "\xE9\xB4\xA6", + "\xE4\xED" => "\xE5\x8E\x93", + "\xE4\xEE" => "\xE5\x93\x80", + "\xE4\xEF" => "\xE5\x9F\x83", + "\xE4\xF0" => "\xE5\xB4\x96", + "\xE4\xF1" => "\xE6\x84\x9B", + "\xE4\xF2" => "\xE6\x9B\x96", + "\xE4\xF3" => "\xE6\xB6\xAF", + "\xE4\xF4" => "\xE7\xA2\x8D", + "\xE4\xF5" => "\xE8\x89\xBE", + "\xE4\xF6" => "\xE9\x9A\x98", + "\xE4\xF7" => "\xE9\x9D\x84", + "\xE4\xF8" => "\xE5\x8E\x84", + "\xE4\xF9" => "\xE6\x89\xBC", + "\xE4\xFA" => "\xE6\x8E\x96", + "\xE4\xFB" => "\xE6\xB6\xB2", + "\xE4\xFC" => "\xE7\xB8\x8A", + "\xE4\xFD" => "\xE8\x85\x8B", + "\xE4\xFE" => "\xE9\xA1\x8D", + "\xE5\xA1" => "\xE6\xAB\xBB", + "\xE5\xA2" => "\xE7\xBD\x8C", + "\xE5\xA3" => "\xE9\xB6\xAF", + "\xE5\xA4" => "\xE9\xB8\x9A", + "\xE5\xA5" => "\xE4\xB9\x9F", + "\xE5\xA6" => "\xE5\x80\xBB", + "\xE5\xA7" => "\xE5\x86\xB6", + "\xE5\xA8" => "\xE5\xA4\x9C", + "\xE5\xA9" => "\xE6\x83\xB9", + "\xE5\xAA" => "\xE6\x8F\xB6", + "\xE5\xAB" => "\xE6\xA4\xB0", + "\xE5\xAC" => "\xE7\x88\xBA", + "\xE5\xAD" => "\xE8\x80\xB6", + "\xE5\xAE" => "\xEF\xA5\xB4", + "\xE5\xAF" => "\xE9\x87\x8E", + "\xE5\xB0" => "\xE5\xBC\xB1", + "\xE5\xB1" => "\xEF\xA5\xB5", + "\xE5\xB2" => "\xEF\xA5\xB6", + "\xE5\xB3" => "\xE7\xB4\x84", + "\xE5\xB4" => "\xE8\x8B\xA5", + "\xE5\xB5" => "\xE8\x91\xAF", + "\xE5\xB6" => "\xE8\x92\xBB", + "\xE5\xB7" => "\xE8\x97\xA5", + "\xE5\xB8" => "\xE8\xBA\x8D", + "\xE5\xB9" => "\xEF\xA5\xB7", + "\xE5\xBA" => "\xE4\xBD\xAF", + "\xE5\xBB" => "\xEF\xA5\xB8", + "\xE5\xBC" => "\xEF\xA5\xB9", + "\xE5\xBD" => "\xE5\xA3\xA4", + "\xE5\xBE" => "\xE5\xAD\x83", + "\xE5\xBF" => "\xE6\x81\x99", + "\xE5\xC0" => "\xE6\x8F\x9A", + "\xE5\xC1" => "\xE6\x94\x98", + "\xE5\xC2" => "\xE6\x95\xAD", + "\xE5\xC3" => "\xE6\x9A\x98", + "\xE5\xC4" => "\xEF\xA5\xBA", + "\xE5\xC5" => "\xE6\xA5\x8A", + "\xE5\xC6" => "\xE6\xA8\xA3", + "\xE5\xC7" => "\xE6\xB4\x8B", + "\xE5\xC8" => "\xE7\x80\x81", + "\xE5\xC9" => "\xE7\x85\xAC", + "\xE5\xCA" => "\xE7\x97\x92", + "\xE5\xCB" => "\xE7\x98\x8D", + "\xE5\xCC" => "\xE7\xA6\xB3", + "\xE5\xCD" => "\xE7\xA9\xB0", + "\xE5\xCE" => "\xEF\xA5\xBB", + "\xE5\xCF" => "\xE7\xBE\x8A", + "\xE5\xD0" => "\xEF\xA5\xBC", + "\xE5\xD1" => "\xE8\xA5\x84", + "\xE5\xD2" => "\xEF\xA5\xBD", + "\xE5\xD3" => "\xE8\xAE\x93", + "\xE5\xD4" => "\xE9\x87\x80", + "\xE5\xD5" => "\xE9\x99\xBD", + "\xE5\xD6" => "\xEF\xA5\xBE", + "\xE5\xD7" => "\xE9\xA4\x8A", + "\xE5\xD8" => "\xE5\x9C\x84", + "\xE5\xD9" => "\xE5\xBE\xA1", + "\xE5\xDA" => "\xE6\x96\xBC", + "\xE5\xDB" => "\xE6\xBC\x81", + "\xE5\xDC" => "\xE7\x98\x80", + "\xE5\xDD" => "\xE7\xA6\xA6", + "\xE5\xDE" => "\xE8\xAA\x9E", + "\xE5\xDF" => "\xE9\xA6\xAD", + "\xE5\xE0" => "\xE9\xAD\x9A", + "\xE5\xE1" => "\xE9\xBD\xAC", + "\xE5\xE2" => "\xE5\x84\x84", + "\xE5\xE3" => "\xE6\x86\xB6", + "\xE5\xE4" => "\xE6\x8A\x91", + "\xE5\xE5" => "\xE6\xAA\x8D", + "\xE5\xE6" => "\xE8\x87\x86", + "\xE5\xE7" => "\xE5\x81\x83", + "\xE5\xE8" => "\xE5\xA0\xB0", + "\xE5\xE9" => "\xE5\xBD\xA6", + "\xE5\xEA" => "\xE7\x84\x89", + "\xE5\xEB" => "\xE8\xA8\x80", + "\xE5\xEC" => "\xE8\xAB\xBA", + "\xE5\xED" => "\xE5\xAD\xBC", + "\xE5\xEE" => "\xE8\x98\x96", + "\xE5\xEF" => "\xE4\xBF\xBA", + "\xE5\xF0" => "\xE5\x84\xBC", + "\xE5\xF1" => "\xE5\x9A\xB4", + "\xE5\xF2" => "\xE5\xA5\x84", + "\xE5\xF3" => "\xE6\x8E\xA9", + "\xE5\xF4" => "\xE6\xB7\xB9", + "\xE5\xF5" => "\xE5\xB6\xAA", + "\xE5\xF6" => "\xE6\xA5\xAD", + "\xE5\xF7" => "\xE5\x86\x86", + "\xE5\xF8" => "\xE4\xBA\x88", + "\xE5\xF9" => "\xE4\xBD\x99", + "\xE5\xFA" => "\xEF\xA5\xBF", + "\xE5\xFB" => "\xEF\xA6\x80", + "\xE5\xFC" => "\xEF\xA6\x81", + "\xE5\xFD" => "\xE5\xA6\x82", + "\xE5\xFE" => "\xEF\xA6\x82", + "\xE6\xA1" => "\xEF\xA6\x83", + "\xE6\xA2" => "\xE6\xAD\x9F", + "\xE6\xA3" => "\xE6\xB1\x9D", + "\xE6\xA4" => "\xEF\xA6\x84", + "\xE6\xA5" => "\xE7\x92\xB5", + "\xE6\xA6" => "\xE7\xA4\x96", + "\xE6\xA7" => "\xEF\xA6\x85", + "\xE6\xA8" => "\xE8\x88\x87", + "\xE6\xA9" => "\xE8\x89\x85", + "\xE6\xAA" => "\xE8\x8C\xB9", + "\xE6\xAB" => "\xE8\xBC\xBF", + "\xE6\xAC" => "\xE8\xBD\x9D", + "\xE6\xAD" => "\xEF\xA6\x86", + "\xE6\xAE" => "\xE9\xA4\x98", + "\xE6\xAF" => "\xEF\xA6\x87", + "\xE6\xB0" => "\xEF\xA6\x88", + "\xE6\xB1" => "\xEF\xA6\x89", + "\xE6\xB2" => "\xE4\xBA\xA6", + "\xE6\xB3" => "\xEF\xA6\x8A", + "\xE6\xB4" => "\xE5\x9F\x9F", + "\xE6\xB5" => "\xE5\xBD\xB9", + "\xE6\xB6" => "\xE6\x98\x93", + "\xE6\xB7" => "\xEF\xA6\x8B", + "\xE6\xB8" => "\xEF\xA6\x8C", + "\xE6\xB9" => "\xE7\x96\xAB", + "\xE6\xBA" => "\xE7\xB9\xB9", + "\xE6\xBB" => "\xE8\xAD\xAF", + "\xE6\xBC" => "\xEF\xA6\x8D", + "\xE6\xBD" => "\xE9\x80\x86", + "\xE6\xBE" => "\xE9\xA9\x9B", + "\xE6\xBF" => "\xE5\x9A\xA5", + "\xE6\xC0" => "\xE5\xA0\xA7", + "\xE6\xC1" => "\xE5\xA7\xB8", + "\xE6\xC2" => "\xE5\xA8\x9F", + "\xE6\xC3" => "\xE5\xAE\xB4", + "\xE6\xC4" => "\xEF\xA6\x8E", + "\xE6\xC5" => "\xE5\xBB\xB6", + "\xE6\xC6" => "\xEF\xA6\x8F", + "\xE6\xC7" => "\xEF\xA6\x90", + "\xE6\xC8" => "\xE6\x8D\x90", + "\xE6\xC9" => "\xE6\x8C\xBB", + "\xE6\xCA" => "\xEF\xA6\x91", + "\xE6\xCB" => "\xE6\xA4\xBD", + "\xE6\xCC" => "\xE6\xB2\x87", + "\xE6\xCD" => "\xE6\xB2\xBF", + "\xE6\xCE" => "\xE6\xB6\x8E", + "\xE6\xCF" => "\xE6\xB6\x93", + "\xE6\xD0" => "\xE6\xB7\xB5", + "\xE6\xD1" => "\xE6\xBC\x94", + "\xE6\xD2" => "\xEF\xA6\x92", + "\xE6\xD3" => "\xE7\x83\x9F", + "\xE6\xD4" => "\xE7\x84\xB6", + "\xE6\xD5" => "\xE7\x85\x99", + "\xE6\xD6" => "\xEF\xA6\x93", + "\xE6\xD7" => "\xE7\x87\x83", + "\xE6\xD8" => "\xE7\x87\x95", + "\xE6\xD9" => "\xEF\xA6\x94", + "\xE6\xDA" => "\xE7\xA1\x8F", + "\xE6\xDB" => "\xE7\xA1\xAF", + "\xE6\xDC" => "\xEF\xA6\x95", + "\xE6\xDD" => "\xE7\xAD\xB5", + "\xE6\xDE" => "\xE7\xB7\xA3", + "\xE6\xDF" => "\xEF\xA6\x96", + "\xE6\xE0" => "\xE7\xB8\xAF", + "\xE6\xE1" => "\xEF\xA6\x97", + "\xE6\xE2" => "\xE8\xA1\x8D", + "\xE6\xE3" => "\xE8\xBB\x9F", + "\xE6\xE4" => "\xEF\xA6\x98", + "\xE6\xE5" => "\xEF\xA6\x99", + "\xE6\xE6" => "\xEF\xA6\x9A", + "\xE6\xE7" => "\xE9\x89\x9B", + "\xE6\xE8" => "\xEF\xA6\x9B", + "\xE6\xE9" => "\xE9\xB3\xB6", + "\xE6\xEA" => "\xEF\xA6\x9C", + "\xE6\xEB" => "\xEF\xA6\x9D", + "\xE6\xEC" => "\xEF\xA6\x9E", + "\xE6\xED" => "\xE6\x82\x85", + "\xE6\xEE" => "\xE6\xB6\x85", + "\xE6\xEF" => "\xEF\xA6\x9F", + "\xE6\xF0" => "\xE7\x86\xB1", + "\xE6\xF1" => "\xEF\xA6\xA0", + "\xE6\xF2" => "\xEF\xA6\xA1", + "\xE6\xF3" => "\xE9\x96\xB1", + "\xE6\xF4" => "\xE5\x8E\xAD", + "\xE6\xF5" => "\xEF\xA6\xA2", + "\xE6\xF6" => "\xEF\xA6\xA3", + "\xE6\xF7" => "\xEF\xA6\xA4", + "\xE6\xF8" => "\xE6\x9F\x93", + "\xE6\xF9" => "\xEF\xA6\xA5", + "\xE6\xFA" => "\xE7\x82\x8E", + "\xE6\xFB" => "\xE7\x84\xB0", + "\xE6\xFC" => "\xE7\x90\xB0", + "\xE6\xFD" => "\xE8\x89\xB6", + "\xE6\xFE" => "\xE8\x8B\x92", + "\xE7\xA1" => "\xEF\xA6\xA6", + "\xE7\xA2" => "\xE9\x96\xBB", + "\xE7\xA3" => "\xE9\xAB\xA5", + "\xE7\xA4" => "\xE9\xB9\xBD", + "\xE7\xA5" => "\xE6\x9B\x84", + "\xE7\xA6" => "\xEF\xA6\xA7", + "\xE7\xA7" => "\xE7\x87\x81", + "\xE7\xA8" => "\xE8\x91\x89", + "\xE7\xA9" => "\xEF\xA6\xA8", + "\xE7\xAA" => "\xEF\xA6\xA9", + "\xE7\xAB" => "\xE5\xA1\x8B", + "\xE7\xAC" => "\xEF\xA6\xAA", + "\xE7\xAD" => "\xEF\xA6\xAB", + "\xE7\xAE" => "\xE5\xB6\xB8", + "\xE7\xAF" => "\xE5\xBD\xB1", + "\xE7\xB0" => "\xEF\xA6\xAC", + "\xE7\xB1" => "\xE6\x98\xA0", + "\xE7\xB2" => "\xE6\x9A\x8E", + "\xE7\xB3" => "\xE6\xA5\xB9", + "\xE7\xB4" => "\xE6\xA6\xAE", + "\xE7\xB5" => "\xE6\xB0\xB8", + "\xE7\xB6" => "\xE6\xB3\xB3", + "\xE7\xB7" => "\xE6\xB8\xB6", + "\xE7\xB8" => "\xE6\xBD\x81", + "\xE7\xB9" => "\xE6\xBF\x9A", + "\xE7\xBA" => "\xE7\x80\x9B", + "\xE7\xBB" => "\xE7\x80\xAF", + "\xE7\xBC" => "\xE7\x85\x90", + "\xE7\xBD" => "\xE7\x87\x9F", + "\xE7\xBE" => "\xE7\x8D\xB0", + "\xE7\xBF" => "\xEF\xA6\xAD", + "\xE7\xC0" => "\xE7\x91\x9B", + "\xE7\xC1" => "\xEF\xA6\xAE", + "\xE7\xC2" => "\xE7\x93\x94", + "\xE7\xC3" => "\xE7\x9B\x88", + "\xE7\xC4" => "\xE7\xA9\x8E", + "\xE7\xC5" => "\xE7\xBA\x93", + "\xE7\xC6" => "\xEF\xA6\xAF", + "\xE7\xC7" => "\xEF\xA6\xB0", + "\xE7\xC8" => "\xE8\x8B\xB1", + "\xE7\xC9" => "\xE8\xA9\xA0", + "\xE7\xCA" => "\xE8\xBF\x8E", + "\xE7\xCB" => "\xEF\xA6\xB1", + "\xE7\xCC" => "\xE9\x8D\x88", + "\xE7\xCD" => "\xEF\xA6\xB2", + "\xE7\xCE" => "\xE9\x9C\x99", + "\xE7\xCF" => "\xEF\xA6\xB3", + "\xE7\xD0" => "\xEF\xA6\xB4", + "\xE7\xD1" => "\xE4\xB9\x82", + "\xE7\xD2" => "\xE5\x80\xAA", + "\xE7\xD3" => "\xEF\xA6\xB5", + "\xE7\xD4" => "\xE5\x88\x88", + "\xE7\xD5" => "\xE5\x8F\xA1", + "\xE7\xD6" => "\xE6\x9B\xB3", + "\xE7\xD7" => "\xE6\xB1\xAD", + "\xE7\xD8" => "\xE6\xBF\x8A", + "\xE7\xD9" => "\xE7\x8C\x8A", + "\xE7\xDA" => "\xE7\x9D\xBF", + "\xE7\xDB" => "\xE7\xA9\xA2", + "\xE7\xDC" => "\xE8\x8A\xAE", + "\xE7\xDD" => "\xE8\x97\x9D", + "\xE7\xDE" => "\xE8\x98\x82", + "\xE7\xDF" => "\xEF\xA6\xB6", + "\xE7\xE0" => "\xE8\xA3\x94", + "\xE7\xE1" => "\xE8\xA9\xA3", + "\xE7\xE2" => "\xE8\xAD\xBD", + "\xE7\xE3" => "\xE8\xB1\xAB", + "\xE7\xE4" => "\xEF\xA6\xB7", + "\xE7\xE5" => "\xE9\x8A\xB3", + "\xE7\xE6" => "\xEF\xA6\xB8", + "\xE7\xE7" => "\xE9\x9C\x93", + "\xE7\xE8" => "\xE9\xA0\x90", + "\xE7\xE9" => "\xE4\xBA\x94", + "\xE7\xEA" => "\xE4\xBC\x8D", + "\xE7\xEB" => "\xE4\xBF\x89", + "\xE7\xEC" => "\xE5\x82\xB2", + "\xE7\xED" => "\xE5\x8D\x88", + "\xE7\xEE" => "\xE5\x90\xBE", + "\xE7\xEF" => "\xE5\x90\xB3", + "\xE7\xF0" => "\xE5\x97\x9A", + "\xE7\xF1" => "\xE5\xA1\xA2", + "\xE7\xF2" => "\xE5\xA2\xBA", + "\xE7\xF3" => "\xE5\xA5\xA7", + "\xE7\xF4" => "\xE5\xA8\x9B", + "\xE7\xF5" => "\xE5\xAF\xA4", + "\xE7\xF6" => "\xE6\x82\x9F", + "\xE7\xF7" => "\xEF\xA6\xB9", + "\xE7\xF8" => "\xE6\x87\x8A", + "\xE7\xF9" => "\xE6\x95\x96", + "\xE7\xFA" => "\xE6\x97\xBF", + "\xE7\xFB" => "\xE6\x99\xA4", + "\xE7\xFC" => "\xE6\xA2\xA7", + "\xE7\xFD" => "\xE6\xB1\x9A", + "\xE7\xFE" => "\xE6\xBE\xB3", + "\xE8\xA1" => "\xE7\x83\x8F", + "\xE8\xA2" => "\xE7\x86\xAC", + "\xE8\xA3" => "\xE7\x8D\x92", + "\xE8\xA4" => "\xE7\xAD\xBD", + "\xE8\xA5" => "\xE8\x9C\x88", + "\xE8\xA6" => "\xE8\xAA\xA4", + "\xE8\xA7" => "\xE9\xB0\xB2", + "\xE8\xA8" => "\xE9\xBC\x87", + "\xE8\xA9" => "\xE5\xB1\x8B", + "\xE8\xAA" => "\xE6\xB2\x83", + "\xE8\xAB" => "\xE7\x8D\x84", + "\xE8\xAC" => "\xE7\x8E\x89", + "\xE8\xAD" => "\xE9\x88\xBA", + "\xE8\xAE" => "\xE6\xBA\xAB", + "\xE8\xAF" => "\xE7\x91\xA5", + "\xE8\xB0" => "\xE7\x98\x9F", + "\xE8\xB1" => "\xE7\xA9\xA9", + "\xE8\xB2" => "\xE7\xB8\x95", + "\xE8\xB3" => "\xE8\x98\x8A", + "\xE8\xB4" => "\xE5\x85\x80", + "\xE8\xB5" => "\xE5\xA3\x85", + "\xE8\xB6" => "\xE6\x93\x81", + "\xE8\xB7" => "\xE7\x93\xAE", + "\xE8\xB8" => "\xE7\x94\x95", + "\xE8\xB9" => "\xE7\x99\xB0", + "\xE8\xBA" => "\xE7\xBF\x81", + "\xE8\xBB" => "\xE9\x82\x95", + "\xE8\xBC" => "\xE9\x9B\x8D", + "\xE8\xBD" => "\xE9\xA5\x94", + "\xE8\xBE" => "\xE6\xB8\xA6", + "\xE8\xBF" => "\xE7\x93\xA6", + "\xE8\xC0" => "\xE7\xAA\xA9", + "\xE8\xC1" => "\xE7\xAA\xAA", + "\xE8\xC2" => "\xE8\x87\xA5", + "\xE8\xC3" => "\xE8\x9B\x99", + "\xE8\xC4" => "\xE8\x9D\xB8", + "\xE8\xC5" => "\xE8\xA8\x9B", + "\xE8\xC6" => "\xE5\xA9\x89", + "\xE8\xC7" => "\xE5\xAE\x8C", + "\xE8\xC8" => "\xE5\xAE\x9B", + "\xE8\xC9" => "\xE6\xA2\xA1", + "\xE8\xCA" => "\xE6\xA4\x80", + "\xE8\xCB" => "\xE6\xB5\xA3", + "\xE8\xCC" => "\xE7\x8E\xA9", + "\xE8\xCD" => "\xE7\x90\x93", + "\xE8\xCE" => "\xE7\x90\xAC", + "\xE8\xCF" => "\xE7\xA2\x97", + "\xE8\xD0" => "\xE7\xB7\xA9", + "\xE8\xD1" => "\xE7\xBF\xAB", + "\xE8\xD2" => "\xE8\x84\x98", + "\xE8\xD3" => "\xE8\x85\x95", + "\xE8\xD4" => "\xE8\x8E\x9E", + "\xE8\xD5" => "\xE8\xB1\x8C", + "\xE8\xD6" => "\xE9\x98\xAE", + "\xE8\xD7" => "\xE9\xA0\x91", + "\xE8\xD8" => "\xE6\x9B\xB0", + "\xE8\xD9" => "\xE5\xBE\x80", + "\xE8\xDA" => "\xE6\x97\xBA", + "\xE8\xDB" => "\xE6\x9E\x89", + "\xE8\xDC" => "\xE6\xB1\xAA", + "\xE8\xDD" => "\xE7\x8E\x8B", + "\xE8\xDE" => "\xE5\x80\xAD", + "\xE8\xDF" => "\xE5\xA8\x83", + "\xE8\xE0" => "\xE6\xAD\xAA", + "\xE8\xE1" => "\xE7\x9F\xAE", + "\xE8\xE2" => "\xE5\xA4\x96", + "\xE8\xE3" => "\xE5\xB5\xAC", + "\xE8\xE4" => "\xE5\xB7\x8D", + "\xE8\xE5" => "\xE7\x8C\xA5", + "\xE8\xE6" => "\xE7\x95\x8F", + "\xE8\xE7" => "\xEF\xA6\xBA", + "\xE8\xE8" => "\xEF\xA6\xBB", + "\xE8\xE9" => "\xE5\x83\xA5", + "\xE8\xEA" => "\xE5\x87\xB9", + "\xE8\xEB" => "\xE5\xA0\xAF", + "\xE8\xEC" => "\xE5\xA4\xAD", + "\xE8\xED" => "\xE5\xA6\x96", + "\xE8\xEE" => "\xE5\xA7\x9A", + "\xE8\xEF" => "\xE5\xAF\xA5", + "\xE8\xF0" => "\xEF\xA6\xBC", + "\xE8\xF1" => "\xEF\xA6\xBD", + "\xE8\xF2" => "\xE5\xB6\xA2", + "\xE8\xF3" => "\xE6\x8B\x97", + "\xE8\xF4" => "\xE6\x90\x96", + "\xE8\xF5" => "\xE6\x92\x93", + "\xE8\xF6" => "\xE6\x93\xBE", + "\xE8\xF7" => "\xEF\xA6\xBE", + "\xE8\xF8" => "\xE6\x9B\x9C", + "\xE8\xF9" => "\xEF\xA6\xBF", + "\xE8\xFA" => "\xE6\xA9\x88", + "\xE8\xFB" => "\xEF\xA7\x80", + "\xE8\xFC" => "\xE7\x87\xBF", + "\xE8\xFD" => "\xE7\x91\xA4", + "\xE8\xFE" => "\xEF\xA7\x81", + "\xE9\xA1" => "\xE7\xAA\x88", + "\xE9\xA2" => "\xE7\xAA\xAF", + "\xE9\xA3" => "\xE7\xB9\x87", + "\xE9\xA4" => "\xE7\xB9\x9E", + "\xE9\xA5" => "\xE8\x80\x80", + "\xE9\xA6" => "\xE8\x85\xB0", + "\xE9\xA7" => "\xEF\xA7\x82", + "\xE9\xA8" => "\xE8\x9F\xAF", + "\xE9\xA9" => "\xE8\xA6\x81", + "\xE9\xAA" => "\xE8\xAC\xA0", + "\xE9\xAB" => "\xE9\x81\x99", + "\xE9\xAC" => "\xEF\xA7\x83", + "\xE9\xAD" => "\xE9\x82\x80", + "\xE9\xAE" => "\xE9\xA5\x92", + "\xE9\xAF" => "\xE6\x85\xBE", + "\xE9\xB0" => "\xE6\xAC\xB2", + "\xE9\xB1" => "\xE6\xB5\xB4", + "\xE9\xB2" => "\xE7\xB8\x9F", + "\xE9\xB3" => "\xE8\xA4\xA5", + "\xE9\xB4" => "\xE8\xBE\xB1", + "\xE9\xB5" => "\xE4\xBF\x91", + "\xE9\xB6" => "\xE5\x82\xAD", + "\xE9\xB7" => "\xE5\x86\x97", + "\xE9\xB8" => "\xE5\x8B\x87", + "\xE9\xB9" => "\xE5\x9F\x87", + "\xE9\xBA" => "\xE5\xA2\x89", + "\xE9\xBB" => "\xE5\xAE\xB9", + "\xE9\xBC" => "\xE5\xBA\xB8", + "\xE9\xBD" => "\xE6\x85\x82", + "\xE9\xBE" => "\xE6\xA6\x95", + "\xE9\xBF" => "\xE6\xB6\x8C", + "\xE9\xC0" => "\xE6\xB9\xA7", + "\xE9\xC1" => "\xE6\xBA\xB6", + "\xE9\xC2" => "\xE7\x86\x94", + "\xE9\xC3" => "\xE7\x91\xA2", + "\xE9\xC4" => "\xE7\x94\xA8", + "\xE9\xC5" => "\xE7\x94\xAC", + "\xE9\xC6" => "\xE8\x81\xB3", + "\xE9\xC7" => "\xE8\x8C\xB8", + "\xE9\xC8" => "\xE8\x93\x89", + "\xE9\xC9" => "\xE8\xB8\x8A", + "\xE9\xCA" => "\xE9\x8E\x94", + "\xE9\xCB" => "\xE9\x8F\x9E", + "\xE9\xCC" => "\xEF\xA7\x84", + "\xE9\xCD" => "\xE4\xBA\x8E", + "\xE9\xCE" => "\xE4\xBD\x91", + "\xE9\xCF" => "\xE5\x81\xB6", + "\xE9\xD0" => "\xE5\x84\xAA", + "\xE9\xD1" => "\xE5\x8F\x88", + "\xE9\xD2" => "\xE5\x8F\x8B", + "\xE9\xD3" => "\xE5\x8F\xB3", + "\xE9\xD4" => "\xE5\xAE\x87", + "\xE9\xD5" => "\xE5\xAF\x93", + "\xE9\xD6" => "\xE5\xB0\xA4", + "\xE9\xD7" => "\xE6\x84\x9A", + "\xE9\xD8" => "\xE6\x86\x82", + "\xE9\xD9" => "\xE6\x97\xB4", + "\xE9\xDA" => "\xE7\x89\x9B", + "\xE9\xDB" => "\xE7\x8E\x97", + "\xE9\xDC" => "\xE7\x91\x80", + "\xE9\xDD" => "\xE7\x9B\x82", + "\xE9\xDE" => "\xE7\xA5\x90", + "\xE9\xDF" => "\xE7\xA6\x91", + "\xE9\xE0" => "\xE7\xA6\xB9", + "\xE9\xE1" => "\xE7\xB4\x86", + "\xE9\xE2" => "\xE7\xBE\xBD", + "\xE9\xE3" => "\xE8\x8A\x8B", + "\xE9\xE4" => "\xE8\x97\x95", + "\xE9\xE5" => "\xE8\x99\x9E", + "\xE9\xE6" => "\xE8\xBF\x82", + "\xE9\xE7" => "\xE9\x81\x87", + "\xE9\xE8" => "\xE9\x83\xB5", + "\xE9\xE9" => "\xE9\x87\xAA", + "\xE9\xEA" => "\xE9\x9A\x85", + "\xE9\xEB" => "\xE9\x9B\xA8", + "\xE9\xEC" => "\xE9\x9B\xA9", + "\xE9\xED" => "\xE5\x8B\x96", + "\xE9\xEE" => "\xE5\xBD\xA7", + "\xE9\xEF" => "\xE6\x97\xAD", + "\xE9\xF0" => "\xE6\x98\xB1", + "\xE9\xF1" => "\xE6\xA0\xAF", + "\xE9\xF2" => "\xE7\x85\x9C", + "\xE9\xF3" => "\xE7\xA8\xB6", + "\xE9\xF4" => "\xE9\x83\x81", + "\xE9\xF5" => "\xE9\xA0\x8A", + "\xE9\xF6" => "\xE4\xBA\x91", + "\xE9\xF7" => "\xEF\xA7\x85", + "\xE9\xF8" => "\xE6\xA9\x92", + "\xE9\xF9" => "\xE6\xAE\x9E", + "\xE9\xFA" => "\xE6\xBE\x90", + "\xE9\xFB" => "\xE7\x86\x89", + "\xE9\xFC" => "\xE8\x80\x98", + "\xE9\xFD" => "\xE8\x8A\xB8", + "\xE9\xFE" => "\xE8\x95\x93", + "\xEA\xA1" => "\xE9\x81\x8B", + "\xEA\xA2" => "\xE9\x9A\x95", + "\xEA\xA3" => "\xE9\x9B\xB2", + "\xEA\xA4" => "\xE9\x9F\xBB", + "\xEA\xA5" => "\xE8\x94\x9A", + "\xEA\xA6" => "\xE9\xAC\xB1", + "\xEA\xA7" => "\xE4\xBA\x90", + "\xEA\xA8" => "\xE7\x86\x8A", + "\xEA\xA9" => "\xE9\x9B\x84", + "\xEA\xAA" => "\xE5\x85\x83", + "\xEA\xAB" => "\xE5\x8E\x9F", + "\xEA\xAC" => "\xE5\x93\xA1", + "\xEA\xAD" => "\xE5\x9C\x93", + "\xEA\xAE" => "\xE5\x9C\x92", + "\xEA\xAF" => "\xE5\x9E\xA3", + "\xEA\xB0" => "\xE5\xAA\x9B", + "\xEA\xB1" => "\xE5\xAB\x84", + "\xEA\xB2" => "\xE5\xAF\x83", + "\xEA\xB3" => "\xE6\x80\xA8", + "\xEA\xB4" => "\xE6\x84\xBF", + "\xEA\xB5" => "\xE6\x8F\xB4", + "\xEA\xB6" => "\xE6\xB2\x85", + "\xEA\xB7" => "\xE6\xB4\xB9", + "\xEA\xB8" => "\xE6\xB9\xB2", + "\xEA\xB9" => "\xE6\xBA\x90", + "\xEA\xBA" => "\xE7\x88\xB0", + "\xEA\xBB" => "\xE7\x8C\xBF", + "\xEA\xBC" => "\xE7\x91\x97", + "\xEA\xBD" => "\xE8\x8B\x91", + "\xEA\xBE" => "\xE8\xA2\x81", + "\xEA\xBF" => "\xE8\xBD\x85", + "\xEA\xC0" => "\xE9\x81\xA0", + "\xEA\xC1" => "\xEF\xA7\x86", + "\xEA\xC2" => "\xE9\x99\xA2", + "\xEA\xC3" => "\xE9\xA1\x98", + "\xEA\xC4" => "\xE9\xB4\x9B", + "\xEA\xC5" => "\xE6\x9C\x88", + "\xEA\xC6" => "\xE8\xB6\x8A", + "\xEA\xC7" => "\xE9\x89\x9E", + "\xEA\xC8" => "\xE4\xBD\x8D", + "\xEA\xC9" => "\xE5\x81\x89", + "\xEA\xCA" => "\xE5\x83\x9E", + "\xEA\xCB" => "\xE5\x8D\xB1", + "\xEA\xCC" => "\xE5\x9C\x8D", + "\xEA\xCD" => "\xE5\xA7\x94", + "\xEA\xCE" => "\xE5\xA8\x81", + "\xEA\xCF" => "\xE5\xB0\x89", + "\xEA\xD0" => "\xE6\x85\xB0", + "\xEA\xD1" => "\xE6\x9A\x90", + "\xEA\xD2" => "\xE6\xB8\xAD", + "\xEA\xD3" => "\xE7\x88\xB2", + "\xEA\xD4" => "\xE7\x91\x8B", + "\xEA\xD5" => "\xE7\xB7\xAF", + "\xEA\xD6" => "\xE8\x83\x83", + "\xEA\xD7" => "\xE8\x90\x8E", + "\xEA\xD8" => "\xE8\x91\xA6", + "\xEA\xD9" => "\xE8\x94\xBF", + "\xEA\xDA" => "\xE8\x9D\x9F", + "\xEA\xDB" => "\xE8\xA1\x9B", + "\xEA\xDC" => "\xE8\xA4\x98", + "\xEA\xDD" => "\xE8\xAC\x82", + "\xEA\xDE" => "\xE9\x81\x95", + "\xEA\xDF" => "\xE9\x9F\x8B", + "\xEA\xE0" => "\xE9\xAD\x8F", + "\xEA\xE1" => "\xE4\xB9\xB3", + "\xEA\xE2" => "\xE4\xBE\x91", + "\xEA\xE3" => "\xE5\x84\x92", + "\xEA\xE4" => "\xE5\x85\xAA", + "\xEA\xE5" => "\xEF\xA7\x87", + "\xEA\xE6" => "\xE5\x94\xAF", + "\xEA\xE7" => "\xE5\x96\xA9", + "\xEA\xE8" => "\xE5\xAD\xBA", + "\xEA\xE9" => "\xE5\xAE\xA5", + "\xEA\xEA" => "\xE5\xB9\xBC", + "\xEA\xEB" => "\xE5\xB9\xBD", + "\xEA\xEC" => "\xE5\xBA\xBE", + "\xEA\xED" => "\xE6\x82\xA0", + "\xEA\xEE" => "\xE6\x83\x9F", + "\xEA\xEF" => "\xE6\x84\x88", + "\xEA\xF0" => "\xE6\x84\x89", + "\xEA\xF1" => "\xE6\x8F\x84", + "\xEA\xF2" => "\xE6\x94\xB8", + "\xEA\xF3" => "\xE6\x9C\x89", + "\xEA\xF4" => "\xEF\xA7\x88", + "\xEA\xF5" => "\xE6\x9F\x94", + "\xEA\xF6" => "\xE6\x9F\x9A", + "\xEA\xF7" => "\xEF\xA7\x89", + "\xEA\xF8" => "\xE6\xA5\xA1", + "\xEA\xF9" => "\xE6\xA5\xA2", + "\xEA\xFA" => "\xE6\xB2\xB9", + "\xEA\xFB" => "\xE6\xB4\xA7", + "\xEA\xFC" => "\xEF\xA7\x8A", + "\xEA\xFD" => "\xE6\xB8\xB8", + "\xEA\xFE" => "\xEF\xA7\x8B", + "\xEB\xA1" => "\xE6\xBF\xA1", + "\xEB\xA2" => "\xE7\x8C\xB6", + "\xEB\xA3" => "\xE7\x8C\xB7", + "\xEB\xA4" => "\xEF\xA7\x8C", + "\xEB\xA5" => "\xE7\x91\x9C", + "\xEB\xA6" => "\xE7\x94\xB1", + "\xEB\xA7" => "\xEF\xA7\x8D", + "\xEB\xA8" => "\xE7\x99\x92", + "\xEB\xA9" => "\xEF\xA7\x8E", + "\xEB\xAA" => "\xEF\xA7\x8F", + "\xEB\xAB" => "\xE7\xB6\xAD", + "\xEB\xAC" => "\xE8\x87\xBE", + "\xEB\xAD" => "\xE8\x90\xB8", + "\xEB\xAE" => "\xE8\xA3\x95", + "\xEB\xAF" => "\xE8\xAA\x98", + "\xEB\xB0" => "\xE8\xAB\x9B", + "\xEB\xB1" => "\xE8\xAB\xAD", + "\xEB\xB2" => "\xE8\xB8\xB0", + "\xEB\xB3" => "\xE8\xB9\x82", + "\xEB\xB4" => "\xE9\x81\x8A", + "\xEB\xB5" => "\xE9\x80\xBE", + "\xEB\xB6" => "\xE9\x81\xBA", + "\xEB\xB7" => "\xE9\x85\x89", + "\xEB\xB8" => "\xE9\x87\x89", + "\xEB\xB9" => "\xE9\x8D\xAE", + "\xEB\xBA" => "\xEF\xA7\x90", + "\xEB\xBB" => "\xEF\xA7\x91", + "\xEB\xBC" => "\xE5\xA0\x89", + "\xEB\xBD" => "\xEF\xA7\x92", + "\xEB\xBE" => "\xE6\xAF\x93", + "\xEB\xBF" => "\xE8\x82\x89", + "\xEB\xC0" => "\xE8\x82\xB2", + "\xEB\xC1" => "\xEF\xA7\x93", + "\xEB\xC2" => "\xEF\xA7\x94", + "\xEB\xC3" => "\xE5\x85\x81", + "\xEB\xC4" => "\xE5\xA5\xAB", + "\xEB\xC5" => "\xE5\xB0\xB9", + "\xEB\xC6" => "\xEF\xA7\x95", + "\xEB\xC7" => "\xEF\xA7\x96", + "\xEB\xC8" => "\xE6\xBD\xA4", + "\xEB\xC9" => "\xE7\x8E\xA7", + "\xEB\xCA" => "\xE8\x83\xA4", + "\xEB\xCB" => "\xE8\xB4\x87", + "\xEB\xCC" => "\xEF\xA7\x97", + "\xEB\xCD" => "\xE9\x88\x97", + "\xEB\xCE" => "\xE9\x96\x8F", + "\xEB\xCF" => "\xEF\xA7\x98", + "\xEB\xD0" => "\xEF\xA7\x99", + "\xEB\xD1" => "\xEF\xA7\x9A", + "\xEB\xD2" => "\xEF\xA7\x9B", + "\xEB\xD3" => "\xE8\x81\xBF", + "\xEB\xD4" => "\xE6\x88\x8E", + "\xEB\xD5" => "\xE7\x80\x9C", + "\xEB\xD6" => "\xE7\xB5\xA8", + "\xEB\xD7" => "\xE8\x9E\x8D", + "\xEB\xD8" => "\xEF\xA7\x9C", + "\xEB\xD9" => "\xE5\x9E\xA0", + "\xEB\xDA" => "\xE6\x81\xA9", + "\xEB\xDB" => "\xE6\x85\x87", + "\xEB\xDC" => "\xE6\xAE\xB7", + "\xEB\xDD" => "\xE8\xAA\xBE", + "\xEB\xDE" => "\xE9\x8A\x80", + "\xEB\xDF" => "\xE9\x9A\xB1", + "\xEB\xE0" => "\xE4\xB9\x99", + "\xEB\xE1" => "\xE5\x90\x9F", + "\xEB\xE2" => "\xE6\xB7\xAB", + "\xEB\xE3" => "\xE8\x94\xAD", + "\xEB\xE4" => "\xE9\x99\xB0", + "\xEB\xE5" => "\xE9\x9F\xB3", + "\xEB\xE6" => "\xE9\xA3\xAE", + "\xEB\xE7" => "\xE6\x8F\x96", + "\xEB\xE8" => "\xE6\xB3\xA3", + "\xEB\xE9" => "\xE9\x82\x91", + "\xEB\xEA" => "\xE5\x87\x9D", + "\xEB\xEB" => "\xE6\x87\x89", + "\xEB\xEC" => "\xE8\x86\xBA", + "\xEB\xED" => "\xE9\xB7\xB9", + "\xEB\xEE" => "\xE4\xBE\x9D", + "\xEB\xEF" => "\xE5\x80\x9A", + "\xEB\xF0" => "\xE5\x84\x80", + "\xEB\xF1" => "\xE5\xAE\x9C", + "\xEB\xF2" => "\xE6\x84\x8F", + "\xEB\xF3" => "\xE6\x87\xBF", + "\xEB\xF4" => "\xE6\x93\xAC", + "\xEB\xF5" => "\xE6\xA4\x85", + "\xEB\xF6" => "\xE6\xAF\x85", + "\xEB\xF7" => "\xE7\x96\x91", + "\xEB\xF8" => "\xE7\x9F\xA3", + "\xEB\xF9" => "\xE7\xBE\xA9", + "\xEB\xFA" => "\xE8\x89\xA4", + "\xEB\xFB" => "\xE8\x96\x8F", + "\xEB\xFC" => "\xE8\x9F\xBB", + "\xEB\xFD" => "\xE8\xA1\xA3", + "\xEB\xFE" => "\xE8\xAA\xBC", + "\xEC\xA1" => "\xE8\xAD\xB0", + "\xEC\xA2" => "\xE9\x86\xAB", + "\xEC\xA3" => "\xE4\xBA\x8C", + "\xEC\xA4" => "\xE4\xBB\xA5", + "\xEC\xA5" => "\xE4\xBC\x8A", + "\xEC\xA6" => "\xEF\xA7\x9D", + "\xEC\xA7" => "\xEF\xA7\x9E", + "\xEC\xA8" => "\xE5\xA4\xB7", + "\xEC\xA9" => "\xE5\xA7\xA8", + "\xEC\xAA" => "\xEF\xA7\x9F", + "\xEC\xAB" => "\xE5\xB7\xB2", + "\xEC\xAC" => "\xE5\xBC\x9B", + "\xEC\xAD" => "\xE5\xBD\x9B", + "\xEC\xAE" => "\xE6\x80\xA1", + "\xEC\xAF" => "\xEF\xA7\xA0", + "\xEC\xB0" => "\xEF\xA7\xA1", + "\xEC\xB1" => "\xEF\xA7\xA2", + "\xEC\xB2" => "\xEF\xA7\xA3", + "\xEC\xB3" => "\xE7\x88\xBE", + "\xEC\xB4" => "\xE7\x8F\xA5", + "\xEC\xB5" => "\xEF\xA7\xA4", + "\xEC\xB6" => "\xE7\x95\xB0", + "\xEC\xB7" => "\xE7\x97\x8D", + "\xEC\xB8" => "\xEF\xA7\xA5", + "\xEC\xB9" => "\xE7\xA7\xBB", + "\xEC\xBA" => "\xEF\xA7\xA6", + "\xEC\xBB" => "\xE8\x80\x8C", + "\xEC\xBC" => "\xE8\x80\xB3", + "\xEC\xBD" => "\xE8\x82\x84", + "\xEC\xBE" => "\xE8\x8B\xA1", + "\xEC\xBF" => "\xE8\x8D\x91", + "\xEC\xC0" => "\xEF\xA7\xA7", + "\xEC\xC1" => "\xEF\xA7\xA8", + "\xEC\xC2" => "\xE8\xB2\xBD", + "\xEC\xC3" => "\xE8\xB2\xB3", + "\xEC\xC4" => "\xE9\x82\x87", + "\xEC\xC5" => "\xEF\xA7\xA9", + "\xEC\xC6" => "\xEF\xA7\xAA", + "\xEC\xC7" => "\xE9\xA3\xB4", + "\xEC\xC8" => "\xE9\xA4\x8C", + "\xEC\xC9" => "\xEF\xA7\xAB", + "\xEC\xCA" => "\xEF\xA7\xAC", + "\xEC\xCB" => "\xE7\x80\xB7", + "\xEC\xCC" => "\xE7\x9B\x8A", + "\xEC\xCD" => "\xE7\xBF\x8A", + "\xEC\xCE" => "\xE7\xBF\x8C", + "\xEC\xCF" => "\xE7\xBF\xBC", + "\xEC\xD0" => "\xE8\xAC\x9A", + "\xEC\xD1" => "\xE4\xBA\xBA", + "\xEC\xD2" => "\xE4\xBB\x81", + "\xEC\xD3" => "\xE5\x88\x83", + "\xEC\xD4" => "\xE5\x8D\xB0", + "\xEC\xD5" => "\xEF\xA7\xAD", + "\xEC\xD6" => "\xE5\x92\xBD", + "\xEC\xD7" => "\xE5\x9B\xA0", + "\xEC\xD8" => "\xE5\xA7\xBB", + "\xEC\xD9" => "\xE5\xAF\x85", + "\xEC\xDA" => "\xE5\xBC\x95", + "\xEC\xDB" => "\xE5\xBF\x8D", + "\xEC\xDC" => "\xE6\xB9\xAE", + "\xEC\xDD" => "\xEF\xA7\xAE", + "\xEC\xDE" => "\xEF\xA7\xAF", + "\xEC\xDF" => "\xE7\xB5\xAA", + "\xEC\xE0" => "\xE8\x8C\xB5", + "\xEC\xE1" => "\xEF\xA7\xB0", + "\xEC\xE2" => "\xE8\x9A\x93", + "\xEC\xE3" => "\xE8\xAA\x8D", + "\xEC\xE4" => "\xEF\xA7\xB1", + "\xEC\xE5" => "\xE9\x9D\xAD", + "\xEC\xE6" => "\xE9\x9D\xB7", + "\xEC\xE7" => "\xEF\xA7\xB2", + "\xEC\xE8" => "\xEF\xA7\xB3", + "\xEC\xE9" => "\xE4\xB8\x80", + "\xEC\xEA" => "\xE4\xBD\x9A", + "\xEC\xEB" => "\xE4\xBD\xBE", + "\xEC\xEC" => "\xE5\xA3\xB9", + "\xEC\xED" => "\xE6\x97\xA5", + "\xEC\xEE" => "\xE6\xBA\xA2", + "\xEC\xEF" => "\xE9\x80\xB8", + "\xEC\xF0" => "\xE9\x8E\xB0", + "\xEC\xF1" => "\xE9\xA6\xB9", + "\xEC\xF2" => "\xE4\xBB\xBB", + "\xEC\xF3" => "\xE5\xA3\xAC", + "\xEC\xF4" => "\xE5\xA6\x8A", + "\xEC\xF5" => "\xE5\xA7\x99", + "\xEC\xF6" => "\xE6\x81\x81", + "\xEC\xF7" => "\xEF\xA7\xB4", + "\xEC\xF8" => "\xEF\xA7\xB5", + "\xEC\xF9" => "\xE7\xA8\x94", + "\xEC\xFA" => "\xEF\xA7\xB6", + "\xEC\xFB" => "\xE8\x8D\x8F", + "\xEC\xFC" => "\xE8\xB3\x83", + "\xEC\xFD" => "\xE5\x85\xA5", + "\xEC\xFE" => "\xE5\x8D\x84", + "\xED\xA1" => "\xEF\xA7\xB7", + "\xED\xA2" => "\xEF\xA7\xB8", + "\xED\xA3" => "\xEF\xA7\xB9", + "\xED\xA4" => "\xE4\xBB\x8D", + "\xED\xA5" => "\xE5\x89\xA9", + "\xED\xA6" => "\xE5\xAD\x95", + "\xED\xA7" => "\xE8\x8A\xBF", + "\xED\xA8" => "\xE4\xBB\x94", + "\xED\xA9" => "\xE5\x88\xBA", + "\xED\xAA" => "\xE5\x92\xA8", + "\xED\xAB" => "\xE5\xA7\x89", + "\xED\xAC" => "\xE5\xA7\xBF", + "\xED\xAD" => "\xE5\xAD\x90", + "\xED\xAE" => "\xE5\xAD\x97", + "\xED\xAF" => "\xE5\xAD\x9C", + "\xED\xB0" => "\xE6\x81\xA3", + "\xED\xB1" => "\xE6\x85\x88", + "\xED\xB2" => "\xE6\xBB\x8B", + "\xED\xB3" => "\xE7\x82\x99", + "\xED\xB4" => "\xE7\x85\xAE", + "\xED\xB5" => "\xE7\x8E\x86", + "\xED\xB6" => "\xE7\x93\xB7", + "\xED\xB7" => "\xE7\x96\xB5", + "\xED\xB8" => "\xE7\xA3\x81", + "\xED\xB9" => "\xE7\xB4\xAB", + "\xED\xBA" => "\xE8\x80\x85", + "\xED\xBB" => "\xE8\x87\xAA", + "\xED\xBC" => "\xE8\x8C\xA8", + "\xED\xBD" => "\xE8\x94\x97", + "\xED\xBE" => "\xE8\x97\x89", + "\xED\xBF" => "\xE8\xAB\xAE", + "\xED\xC0" => "\xE8\xB3\x87", + "\xED\xC1" => "\xE9\x9B\x8C", + "\xED\xC2" => "\xE4\xBD\x9C", + "\xED\xC3" => "\xE5\x8B\xBA", + "\xED\xC4" => "\xE5\x9A\xBC", + "\xED\xC5" => "\xE6\x96\xAB", + "\xED\xC6" => "\xE6\x98\xA8", + "\xED\xC7" => "\xE7\x81\xBC", + "\xED\xC8" => "\xE7\x82\xB8", + "\xED\xC9" => "\xE7\x88\xB5", + "\xED\xCA" => "\xE7\xB6\xBD", + "\xED\xCB" => "\xE8\x8A\x8D", + "\xED\xCC" => "\xE9\x85\x8C", + "\xED\xCD" => "\xE9\x9B\x80", + "\xED\xCE" => "\xE9\xB5\xB2", + "\xED\xCF" => "\xE5\xAD\xB1", + "\xED\xD0" => "\xE6\xA3\xA7", + "\xED\xD1" => "\xE6\xAE\x98", + "\xED\xD2" => "\xE6\xBD\xBA", + "\xED\xD3" => "\xE7\x9B\x9E", + "\xED\xD4" => "\xE5\xB2\x91", + "\xED\xD5" => "\xE6\x9A\xAB", + "\xED\xD6" => "\xE6\xBD\x9B", + "\xED\xD7" => "\xE7\xAE\xB4", + "\xED\xD8" => "\xE7\xB0\xAA", + "\xED\xD9" => "\xE8\xA0\xB6", + "\xED\xDA" => "\xE9\x9B\x9C", + "\xED\xDB" => "\xE4\xB8\x88", + "\xED\xDC" => "\xE4\xBB\x97", + "\xED\xDD" => "\xE5\x8C\xA0", + "\xED\xDE" => "\xE5\xA0\xB4", + "\xED\xDF" => "\xE5\xA2\xBB", + "\xED\xE0" => "\xE5\xA3\xAF", + "\xED\xE1" => "\xE5\xA5\xAC", + "\xED\xE2" => "\xE5\xB0\x87", + "\xED\xE3" => "\xE5\xB8\xB3", + "\xED\xE4" => "\xE5\xBA\x84", + "\xED\xE5" => "\xE5\xBC\xB5", + "\xED\xE6" => "\xE6\x8E\x8C", + "\xED\xE7" => "\xE6\x9A\xB2", + "\xED\xE8" => "\xE6\x9D\x96", + "\xED\xE9" => "\xE6\xA8\x9F", + "\xED\xEA" => "\xE6\xAA\xA3", + "\xED\xEB" => "\xE6\xAC\x8C", + "\xED\xEC" => "\xE6\xBC\xBF", + "\xED\xED" => "\xE7\x89\x86", + "\xED\xEE" => "\xEF\xA7\xBA", + "\xED\xEF" => "\xE7\x8D\x90", + "\xED\xF0" => "\xE7\x92\x8B", + "\xED\xF1" => "\xE7\xAB\xA0", + "\xED\xF2" => "\xE7\xB2\xA7", + "\xED\xF3" => "\xE8\x85\xB8", + "\xED\xF4" => "\xE8\x87\x9F", + "\xED\xF5" => "\xE8\x87\xA7", + "\xED\xF6" => "\xE8\x8E\x8A", + "\xED\xF7" => "\xE8\x91\xAC", + "\xED\xF8" => "\xE8\x94\xA3", + "\xED\xF9" => "\xE8\x96\x94", + "\xED\xFA" => "\xE8\x97\x8F", + "\xED\xFB" => "\xE8\xA3\x9D", + "\xED\xFC" => "\xE8\xB4\x93", + "\xED\xFD" => "\xE9\x86\xAC", + "\xED\xFE" => "\xE9\x95\xB7", + "\xEE\xA1" => "\xE9\x9A\x9C", + "\xEE\xA2" => "\xE5\x86\x8D", + "\xEE\xA3" => "\xE5\x93\x89", + "\xEE\xA4" => "\xE5\x9C\xA8", + "\xEE\xA5" => "\xE5\xAE\xB0", + "\xEE\xA6" => "\xE6\x89\x8D", + "\xEE\xA7" => "\xE6\x9D\x90", + "\xEE\xA8" => "\xE6\xA0\xBD", + "\xEE\xA9" => "\xE6\xA2\x93", + "\xEE\xAA" => "\xE6\xB8\xBD", + "\xEE\xAB" => "\xE6\xBB\x93", + "\xEE\xAC" => "\xE7\x81\xBD", + "\xEE\xAD" => "\xE7\xB8\xA1", + "\xEE\xAE" => "\xE8\xA3\x81", + "\xEE\xAF" => "\xE8\xB2\xA1", + "\xEE\xB0" => "\xE8\xBC\x89", + "\xEE\xB1" => "\xE9\xBD\x8B", + "\xEE\xB2" => "\xE9\xBD\x8E", + "\xEE\xB3" => "\xE7\x88\xAD", + "\xEE\xB4" => "\xE7\xAE\x8F", + "\xEE\xB5" => "\xE8\xAB\x8D", + "\xEE\xB6" => "\xE9\x8C\x9A", + "\xEE\xB7" => "\xE4\xBD\x87", + "\xEE\xB8" => "\xE4\xBD\x8E", + "\xEE\xB9" => "\xE5\x84\xB2", + "\xEE\xBA" => "\xE5\x92\x80", + "\xEE\xBB" => "\xE5\xA7\x90", + "\xEE\xBC" => "\xE5\xBA\x95", + "\xEE\xBD" => "\xE6\x8A\xB5", + "\xEE\xBE" => "\xE6\x9D\xB5", + "\xEE\xBF" => "\xE6\xA5\xAE", + "\xEE\xC0" => "\xE6\xA8\x97", + "\xEE\xC1" => "\xE6\xB2\xAE", + "\xEE\xC2" => "\xE6\xB8\x9A", + "\xEE\xC3" => "\xE7\x8B\x99", + "\xEE\xC4" => "\xE7\x8C\xAA", + "\xEE\xC5" => "\xE7\x96\xBD", + "\xEE\xC6" => "\xE7\xAE\xB8", + "\xEE\xC7" => "\xE7\xB4\xB5", + "\xEE\xC8" => "\xE8\x8B\xA7", + "\xEE\xC9" => "\xE8\x8F\xB9", + "\xEE\xCA" => "\xE8\x91\x97", + "\xEE\xCB" => "\xE8\x97\xB7", + "\xEE\xCC" => "\xE8\xA9\x9B", + "\xEE\xCD" => "\xE8\xB2\xAF", + "\xEE\xCE" => "\xE8\xBA\x87", + "\xEE\xCF" => "\xE9\x80\x99", + "\xEE\xD0" => "\xE9\x82\xB8", + "\xEE\xD1" => "\xE9\x9B\x8E", + "\xEE\xD2" => "\xE9\xBD\x9F", + "\xEE\xD3" => "\xE5\x8B\xA3", + "\xEE\xD4" => "\xE5\x90\x8A", + "\xEE\xD5" => "\xE5\xAB\xA1", + "\xEE\xD6" => "\xE5\xAF\x82", + "\xEE\xD7" => "\xE6\x91\x98", + "\xEE\xD8" => "\xE6\x95\xB5", + "\xEE\xD9" => "\xE6\xBB\xB4", + "\xEE\xDA" => "\xE7\x8B\x84", + "\xEE\xDB" => "\xEF\xA7\xBB", + "\xEE\xDC" => "\xE7\x9A\x84", + "\xEE\xDD" => "\xE7\xA9\x8D", + "\xEE\xDE" => "\xE7\xAC\x9B", + "\xEE\xDF" => "\xE7\xB1\x8D", + "\xEE\xE0" => "\xE7\xB8\xBE", + "\xEE\xE1" => "\xE7\xBF\x9F", + "\xEE\xE2" => "\xE8\x8D\xBB", + "\xEE\xE3" => "\xE8\xAC\xAB", + "\xEE\xE4" => "\xE8\xB3\x8A", + "\xEE\xE5" => "\xE8\xB5\xA4", + "\xEE\xE6" => "\xE8\xB7\xA1", + "\xEE\xE7" => "\xE8\xB9\x9F", + "\xEE\xE8" => "\xE8\xBF\xAA", + "\xEE\xE9" => "\xE8\xBF\xB9", + "\xEE\xEA" => "\xE9\x81\xA9", + "\xEE\xEB" => "\xE9\x8F\x91", + "\xEE\xEC" => "\xE4\xBD\x83", + "\xEE\xED" => "\xE4\xBD\xBA", + "\xEE\xEE" => "\xE5\x82\xB3", + "\xEE\xEF" => "\xE5\x85\xA8", + "\xEE\xF0" => "\xE5\x85\xB8", + "\xEE\xF1" => "\xE5\x89\x8D", + "\xEE\xF2" => "\xE5\x89\xAA", + "\xEE\xF3" => "\xE5\xA1\xA1", + "\xEE\xF4" => "\xE5\xA1\xBC", + "\xEE\xF5" => "\xE5\xA5\xA0", + "\xEE\xF6" => "\xE5\xB0\x88", + "\xEE\xF7" => "\xE5\xB1\x95", + "\xEE\xF8" => "\xE5\xBB\x9B", + "\xEE\xF9" => "\xE6\x82\x9B", + "\xEE\xFA" => "\xE6\x88\xB0", + "\xEE\xFB" => "\xE6\xA0\x93", + "\xEE\xFC" => "\xE6\xAE\xBF", + "\xEE\xFD" => "\xE6\xB0\x88", + "\xEE\xFE" => "\xE6\xBE\xB1", + "\xEF\xA1" => "\xE7\x85\x8E", + "\xEF\xA2" => "\xE7\x90\xA0", + "\xEF\xA3" => "\xE7\x94\xB0", + "\xEF\xA4" => "\xE7\x94\xB8", + "\xEF\xA5" => "\xE7\x95\x91", + "\xEF\xA6" => "\xE7\x99\xB2", + "\xEF\xA7" => "\xE7\xAD\x8C", + "\xEF\xA8" => "\xE7\xAE\x8B", + "\xEF\xA9" => "\xE7\xAE\xAD", + "\xEF\xAA" => "\xE7\xAF\x86", + "\xEF\xAB" => "\xE7\xBA\x8F", + "\xEF\xAC" => "\xE8\xA9\xAE", + "\xEF\xAD" => "\xE8\xBC\xBE", + "\xEF\xAE" => "\xE8\xBD\x89", + "\xEF\xAF" => "\xE9\x88\xBF", + "\xEF\xB0" => "\xE9\x8A\x93", + "\xEF\xB1" => "\xE9\x8C\xA2", + "\xEF\xB2" => "\xE9\x90\xAB", + "\xEF\xB3" => "\xE9\x9B\xBB", + "\xEF\xB4" => "\xE9\xA1\x9A", + "\xEF\xB5" => "\xE9\xA1\xAB", + "\xEF\xB6" => "\xE9\xA4\x9E", + "\xEF\xB7" => "\xE5\x88\x87", + "\xEF\xB8" => "\xE6\x88\xAA", + "\xEF\xB9" => "\xE6\x8A\x98", + "\xEF\xBA" => "\xE6\xB5\x99", + "\xEF\xBB" => "\xE7\x99\xA4", + "\xEF\xBC" => "\xE7\xAB\x8A", + "\xEF\xBD" => "\xE7\xAF\x80", + "\xEF\xBE" => "\xE7\xB5\xB6", + "\xEF\xBF" => "\xE5\x8D\xA0", + "\xEF\xC0" => "\xE5\xB2\xBE", + "\xEF\xC1" => "\xE5\xBA\x97", + "\xEF\xC2" => "\xE6\xBC\xB8", + "\xEF\xC3" => "\xE7\x82\xB9", + "\xEF\xC4" => "\xE7\xB2\x98", + "\xEF\xC5" => "\xE9\x9C\x91", + "\xEF\xC6" => "\xE9\xAE\x8E", + "\xEF\xC7" => "\xE9\xBB\x9E", + "\xEF\xC8" => "\xE6\x8E\xA5", + "\xEF\xC9" => "\xE6\x91\xBA", + "\xEF\xCA" => "\xE8\x9D\xB6", + "\xEF\xCB" => "\xE4\xB8\x81", + "\xEF\xCC" => "\xE4\xBA\x95", + "\xEF\xCD" => "\xE4\xBA\xAD", + "\xEF\xCE" => "\xE5\x81\x9C", + "\xEF\xCF" => "\xE5\x81\xB5", + "\xEF\xD0" => "\xE5\x91\x88", + "\xEF\xD1" => "\xE5\xA7\x83", + "\xEF\xD2" => "\xE5\xAE\x9A", + "\xEF\xD3" => "\xE5\xB9\x80", + "\xEF\xD4" => "\xE5\xBA\xAD", + "\xEF\xD5" => "\xE5\xBB\xB7", + "\xEF\xD6" => "\xE5\xBE\x81", + "\xEF\xD7" => "\xE6\x83\x85", + "\xEF\xD8" => "\xE6\x8C\xBA", + "\xEF\xD9" => "\xE6\x94\xBF", + "\xEF\xDA" => "\xE6\x95\xB4", + "\xEF\xDB" => "\xE6\x97\x8C", + "\xEF\xDC" => "\xE6\x99\xB6", + "\xEF\xDD" => "\xE6\x99\xB8", + "\xEF\xDE" => "\xE6\x9F\xBE", + "\xEF\xDF" => "\xE6\xA5\xA8", + "\xEF\xE0" => "\xE6\xAA\x89", + "\xEF\xE1" => "\xE6\xAD\xA3", + "\xEF\xE2" => "\xE6\xB1\x80", + "\xEF\xE3" => "\xE6\xB7\x80", + "\xEF\xE4" => "\xE6\xB7\xA8", + "\xEF\xE5" => "\xE6\xB8\x9F", + "\xEF\xE6" => "\xE6\xB9\x9E", + "\xEF\xE7" => "\xE7\x80\x9E", + "\xEF\xE8" => "\xE7\x82\xA1", + "\xEF\xE9" => "\xE7\x8E\x8E", + "\xEF\xEA" => "\xE7\x8F\xBD", + "\xEF\xEB" => "\xE7\x94\xBA", + "\xEF\xEC" => "\xE7\x9D\x9B", + "\xEF\xED" => "\xE7\xA2\x87", + "\xEF\xEE" => "\xE7\xA6\x8E", + "\xEF\xEF" => "\xE7\xA8\x8B", + "\xEF\xF0" => "\xE7\xA9\xBD", + "\xEF\xF1" => "\xE7\xB2\xBE", + "\xEF\xF2" => "\xE7\xB6\x8E", + "\xEF\xF3" => "\xE8\x89\x87", + "\xEF\xF4" => "\xE8\xA8\x82", + "\xEF\xF5" => "\xE8\xAB\xAA", + "\xEF\xF6" => "\xE8\xB2\x9E", + "\xEF\xF7" => "\xE9\x84\xAD", + "\xEF\xF8" => "\xE9\x85\x8A", + "\xEF\xF9" => "\xE9\x87\x98", + "\xEF\xFA" => "\xE9\x89\xA6", + "\xEF\xFB" => "\xE9\x8B\x8C", + "\xEF\xFC" => "\xE9\x8C\xA0", + "\xEF\xFD" => "\xE9\x9C\x86", + "\xEF\xFE" => "\xE9\x9D\x96", + "\xF0\xA1" => "\xE9\x9D\x9C", + "\xF0\xA2" => "\xE9\xA0\x82", + "\xF0\xA3" => "\xE9\xBC\x8E", + "\xF0\xA4" => "\xE5\x88\xB6", + "\xF0\xA5" => "\xE5\x8A\x91", + "\xF0\xA6" => "\xE5\x95\xBC", + "\xF0\xA7" => "\xE5\xA0\xA4", + "\xF0\xA8" => "\xE5\xB8\x9D", + "\xF0\xA9" => "\xE5\xBC\x9F", + "\xF0\xAA" => "\xE6\x82\x8C", + "\xF0\xAB" => "\xE6\x8F\x90", + "\xF0\xAC" => "\xE6\xA2\xAF", + "\xF0\xAD" => "\xE6\xBF\x9F", + "\xF0\xAE" => "\xE7\xA5\xAD", + "\xF0\xAF" => "\xE7\xAC\xAC", + "\xF0\xB0" => "\xE8\x87\x8D", + "\xF0\xB1" => "\xE8\x96\xBA", + "\xF0\xB2" => "\xE8\xA3\xBD", + "\xF0\xB3" => "\xE8\xAB\xB8", + "\xF0\xB4" => "\xE8\xB9\x84", + "\xF0\xB5" => "\xE9\x86\x8D", + "\xF0\xB6" => "\xE9\x99\xA4", + "\xF0\xB7" => "\xE9\x9A\x9B", + "\xF0\xB8" => "\xE9\x9C\xBD", + "\xF0\xB9" => "\xE9\xA1\x8C", + "\xF0\xBA" => "\xE9\xBD\x8A", + "\xF0\xBB" => "\xE4\xBF\x8E", + "\xF0\xBC" => "\xE5\x85\x86", + "\xF0\xBD" => "\xE5\x87\x8B", + "\xF0\xBE" => "\xE5\x8A\xA9", + "\xF0\xBF" => "\xE5\x98\xB2", + "\xF0\xC0" => "\xE5\xBC\x94", + "\xF0\xC1" => "\xE5\xBD\xAB", + "\xF0\xC2" => "\xE6\x8E\xAA", + "\xF0\xC3" => "\xE6\x93\x8D", + "\xF0\xC4" => "\xE6\x97\xA9", + "\xF0\xC5" => "\xE6\x99\x81", + "\xF0\xC6" => "\xE6\x9B\xBA", + "\xF0\xC7" => "\xE6\x9B\xB9", + "\xF0\xC8" => "\xE6\x9C\x9D", + "\xF0\xC9" => "\xE6\xA2\x9D", + "\xF0\xCA" => "\xE6\xA3\x97", + "\xF0\xCB" => "\xE6\xA7\xBD", + "\xF0\xCC" => "\xE6\xBC\x95", + "\xF0\xCD" => "\xE6\xBD\xAE", + "\xF0\xCE" => "\xE7\x85\xA7", + "\xF0\xCF" => "\xE7\x87\xA5", + "\xF0\xD0" => "\xE7\x88\xAA", + "\xF0\xD1" => "\xE7\x92\xAA", + "\xF0\xD2" => "\xE7\x9C\xBA", + "\xF0\xD3" => "\xE7\xA5\x96", + "\xF0\xD4" => "\xE7\xA5\x9A", + "\xF0\xD5" => "\xE7\xA7\x9F", + "\xF0\xD6" => "\xE7\xA8\xA0", + "\xF0\xD7" => "\xE7\xAA\x95", + "\xF0\xD8" => "\xE7\xB2\x97", + "\xF0\xD9" => "\xE7\xB3\x9F", + "\xF0\xDA" => "\xE7\xB5\x84", + "\xF0\xDB" => "\xE7\xB9\xB0", + "\xF0\xDC" => "\xE8\x82\x87", + "\xF0\xDD" => "\xE8\x97\xBB", + "\xF0\xDE" => "\xE8\x9A\xA4", + "\xF0\xDF" => "\xE8\xA9\x94", + "\xF0\xE0" => "\xE8\xAA\xBF", + "\xF0\xE1" => "\xE8\xB6\x99", + "\xF0\xE2" => "\xE8\xBA\x81", + "\xF0\xE3" => "\xE9\x80\xA0", + "\xF0\xE4" => "\xE9\x81\xAD", + "\xF0\xE5" => "\xE9\x87\xA3", + "\xF0\xE6" => "\xE9\x98\xBB", + "\xF0\xE7" => "\xE9\x9B\x95", + "\xF0\xE8" => "\xE9\xB3\xA5", + "\xF0\xE9" => "\xE6\x97\x8F", + "\xF0\xEA" => "\xE7\xB0\x87", + "\xF0\xEB" => "\xE8\xB6\xB3", + "\xF0\xEC" => "\xE9\x8F\x83", + "\xF0\xED" => "\xE5\xAD\x98", + "\xF0\xEE" => "\xE5\xB0\x8A", + "\xF0\xEF" => "\xE5\x8D\x92", + "\xF0\xF0" => "\xE6\x8B\x99", + "\xF0\xF1" => "\xE7\x8C\x9D", + "\xF0\xF2" => "\xE5\x80\xA7", + "\xF0\xF3" => "\xE5\xAE\x97", + "\xF0\xF4" => "\xE5\xBE\x9E", + "\xF0\xF5" => "\xE6\x82\xB0", + "\xF0\xF6" => "\xE6\x85\xAB", + "\xF0\xF7" => "\xE6\xA3\x95", + "\xF0\xF8" => "\xE6\xB7\x99", + "\xF0\xF9" => "\xE7\x90\xAE", + "\xF0\xFA" => "\xE7\xA8\xAE", + "\xF0\xFB" => "\xE7\xB5\x82", + "\xF0\xFC" => "\xE7\xB6\x9C", + "\xF0\xFD" => "\xE7\xB8\xB1", + "\xF0\xFE" => "\xE8\x85\xAB", + "\xF1\xA1" => "\xE8\xB8\xAA", + "\xF1\xA2" => "\xE8\xB8\xB5", + "\xF1\xA3" => "\xE9\x8D\xBE", + "\xF1\xA4" => "\xE9\x90\x98", + "\xF1\xA5" => "\xE4\xBD\x90", + "\xF1\xA6" => "\xE5\x9D\x90", + "\xF1\xA7" => "\xE5\xB7\xA6", + "\xF1\xA8" => "\xE5\xBA\xA7", + "\xF1\xA9" => "\xE6\x8C\xAB", + "\xF1\xAA" => "\xE7\xBD\xAA", + "\xF1\xAB" => "\xE4\xB8\xBB", + "\xF1\xAC" => "\xE4\xBD\x8F", + "\xF1\xAD" => "\xE4\xBE\x8F", + "\xF1\xAE" => "\xE5\x81\x9A", + "\xF1\xAF" => "\xE5\xA7\x9D", + "\xF1\xB0" => "\xE8\x83\x84", + "\xF1\xB1" => "\xE5\x91\xAA", + "\xF1\xB2" => "\xE5\x91\xA8", + "\xF1\xB3" => "\xE5\x97\xBE", + "\xF1\xB4" => "\xE5\xA5\x8F", + "\xF1\xB5" => "\xE5\xAE\x99", + "\xF1\xB6" => "\xE5\xB7\x9E", + "\xF1\xB7" => "\xE5\xBB\x9A", + "\xF1\xB8" => "\xE6\x99\x9D", + "\xF1\xB9" => "\xE6\x9C\xB1", + "\xF1\xBA" => "\xE6\x9F\xB1", + "\xF1\xBB" => "\xE6\xA0\xAA", + "\xF1\xBC" => "\xE6\xB3\xA8", + "\xF1\xBD" => "\xE6\xB4\xB2", + "\xF1\xBE" => "\xE6\xB9\x8A", + "\xF1\xBF" => "\xE6\xBE\x8D", + "\xF1\xC0" => "\xE7\x82\xB7", + "\xF1\xC1" => "\xE7\x8F\xA0", + "\xF1\xC2" => "\xE7\x96\x87", + "\xF1\xC3" => "\xE7\xB1\x8C", + "\xF1\xC4" => "\xE7\xB4\x82", + "\xF1\xC5" => "\xE7\xB4\xAC", + "\xF1\xC6" => "\xE7\xB6\xA2", + "\xF1\xC7" => "\xE8\x88\x9F", + "\xF1\xC8" => "\xE8\x9B\x9B", + "\xF1\xC9" => "\xE8\xA8\xBB", + "\xF1\xCA" => "\xE8\xAA\x85", + "\xF1\xCB" => "\xE8\xB5\xB0", + "\xF1\xCC" => "\xE8\xBA\x8A", + "\xF1\xCD" => "\xE8\xBC\xB3", + "\xF1\xCE" => "\xE9\x80\xB1", + "\xF1\xCF" => "\xE9\x85\x8E", + "\xF1\xD0" => "\xE9\x85\x92", + "\xF1\xD1" => "\xE9\x91\x84", + "\xF1\xD2" => "\xE9\xA7\x90", + "\xF1\xD3" => "\xE7\xAB\xB9", + "\xF1\xD4" => "\xE7\xB2\xA5", + "\xF1\xD5" => "\xE4\xBF\x8A", + "\xF1\xD6" => "\xE5\x84\x81", + "\xF1\xD7" => "\xE5\x87\x86", + "\xF1\xD8" => "\xE5\x9F\x88", + "\xF1\xD9" => "\xE5\xAF\xAF", + "\xF1\xDA" => "\xE5\xB3\xBB", + "\xF1\xDB" => "\xE6\x99\x99", + "\xF1\xDC" => "\xE6\xA8\xBD", + "\xF1\xDD" => "\xE6\xB5\x9A", + "\xF1\xDE" => "\xE6\xBA\x96", + "\xF1\xDF" => "\xE6\xBF\xAC", + "\xF1\xE0" => "\xE7\x84\x8C", + "\xF1\xE1" => "\xE7\x95\xAF", + "\xF1\xE2" => "\xE7\xAB\xA3", + "\xF1\xE3" => "\xE8\xA0\xA2", + "\xF1\xE4" => "\xE9\x80\xA1", + "\xF1\xE5" => "\xE9\x81\xB5", + "\xF1\xE6" => "\xE9\x9B\x8B", + "\xF1\xE7" => "\xE9\xA7\xBF", + "\xF1\xE8" => "\xE8\x8C\x81", + "\xF1\xE9" => "\xE4\xB8\xAD", + "\xF1\xEA" => "\xE4\xBB\xB2", + "\xF1\xEB" => "\xE8\xA1\x86", + "\xF1\xEC" => "\xE9\x87\x8D", + "\xF1\xED" => "\xE5\x8D\xBD", + "\xF1\xEE" => "\xE6\xAB\x9B", + "\xF1\xEF" => "\xE6\xA5\xAB", + "\xF1\xF0" => "\xE6\xB1\x81", + "\xF1\xF1" => "\xE8\x91\xBA", + "\xF1\xF2" => "\xE5\xA2\x9E", + "\xF1\xF3" => "\xE6\x86\x8E", + "\xF1\xF4" => "\xE6\x9B\xBE", + "\xF1\xF5" => "\xE6\x8B\xAF", + "\xF1\xF6" => "\xE7\x83\x9D", + "\xF1\xF7" => "\xE7\x94\x91", + "\xF1\xF8" => "\xE7\x97\x87", + "\xF1\xF9" => "\xE7\xB9\x92", + "\xF1\xFA" => "\xE8\x92\xB8", + "\xF1\xFB" => "\xE8\xAD\x89", + "\xF1\xFC" => "\xE8\xB4\x88", + "\xF1\xFD" => "\xE4\xB9\x8B", + "\xF1\xFE" => "\xE5\x8F\xAA", + "\xF2\xA1" => "\xE5\x92\xAB", + "\xF2\xA2" => "\xE5\x9C\xB0", + "\xF2\xA3" => "\xE5\x9D\x80", + "\xF2\xA4" => "\xE5\xBF\x97", + "\xF2\xA5" => "\xE6\x8C\x81", + "\xF2\xA6" => "\xE6\x8C\x87", + "\xF2\xA7" => "\xE6\x91\xAF", + "\xF2\xA8" => "\xE6\x94\xAF", + "\xF2\xA9" => "\xE6\x97\xA8", + "\xF2\xAA" => "\xE6\x99\xBA", + "\xF2\xAB" => "\xE6\x9E\x9D", + "\xF2\xAC" => "\xE6\x9E\xB3", + "\xF2\xAD" => "\xE6\xAD\xA2", + "\xF2\xAE" => "\xE6\xB1\xA0", + "\xF2\xAF" => "\xE6\xB2\x9A", + "\xF2\xB0" => "\xE6\xBC\xAC", + "\xF2\xB1" => "\xE7\x9F\xA5", + "\xF2\xB2" => "\xE7\xA0\xA5", + "\xF2\xB3" => "\xE7\xA5\x89", + "\xF2\xB4" => "\xE7\xA5\x97", + "\xF2\xB5" => "\xE7\xB4\x99", + "\xF2\xB6" => "\xE8\x82\xA2", + "\xF2\xB7" => "\xE8\x84\x82", + "\xF2\xB8" => "\xE8\x87\xB3", + "\xF2\xB9" => "\xE8\x8A\x9D", + "\xF2\xBA" => "\xE8\x8A\xB7", + "\xF2\xBB" => "\xE8\x9C\x98", + "\xF2\xBC" => "\xE8\xAA\x8C", + "\xF2\xBD" => "\xEF\xA7\xBC", + "\xF2\xBE" => "\xE8\xB4\x84", + "\xF2\xBF" => "\xE8\xB6\xBE", + "\xF2\xC0" => "\xE9\x81\xB2", + "\xF2\xC1" => "\xE7\x9B\xB4", + "\xF2\xC2" => "\xE7\xA8\x99", + "\xF2\xC3" => "\xE7\xA8\xB7", + "\xF2\xC4" => "\xE7\xB9\x94", + "\xF2\xC5" => "\xE8\x81\xB7", + "\xF2\xC6" => "\xE5\x94\x87", + "\xF2\xC7" => "\xE5\x97\x94", + "\xF2\xC8" => "\xE5\xA1\xB5", + "\xF2\xC9" => "\xE6\x8C\xAF", + "\xF2\xCA" => "\xE6\x90\xA2", + "\xF2\xCB" => "\xE6\x99\x89", + "\xF2\xCC" => "\xE6\x99\x8B", + "\xF2\xCD" => "\xE6\xA1\xAD", + "\xF2\xCE" => "\xE6\xA6\x9B", + "\xF2\xCF" => "\xE6\xAE\x84", + "\xF2\xD0" => "\xE6\xB4\xA5", + "\xF2\xD1" => "\xE6\xBA\xB1", + "\xF2\xD2" => "\xE7\x8F\x8D", + "\xF2\xD3" => "\xE7\x91\xA8", + "\xF2\xD4" => "\xE7\x92\xA1", + "\xF2\xD5" => "\xE7\x95\x9B", + "\xF2\xD6" => "\xE7\x96\xB9", + "\xF2\xD7" => "\xE7\x9B\xA1", + "\xF2\xD8" => "\xE7\x9C\x9E", + "\xF2\xD9" => "\xE7\x9E\x8B", + "\xF2\xDA" => "\xE7\xA7\xA6", + "\xF2\xDB" => "\xE7\xB8\x89", + "\xF2\xDC" => "\xE7\xB8\x9D", + "\xF2\xDD" => "\xE8\x87\xBB", + "\xF2\xDE" => "\xE8\x94\xAF", + "\xF2\xDF" => "\xE8\xA2\x97", + "\xF2\xE0" => "\xE8\xA8\xBA", + "\xF2\xE1" => "\xE8\xB3\x91", + "\xF2\xE2" => "\xE8\xBB\xAB", + "\xF2\xE3" => "\xE8\xBE\xB0", + "\xF2\xE4" => "\xE9\x80\xB2", + "\xF2\xE5" => "\xE9\x8E\xAD", + "\xF2\xE6" => "\xE9\x99\xA3", + "\xF2\xE7" => "\xE9\x99\xB3", + "\xF2\xE8" => "\xE9\x9C\x87", + "\xF2\xE9" => "\xE4\xBE\x84", + "\xF2\xEA" => "\xE5\x8F\xB1", + "\xF2\xEB" => "\xE5\xA7\xAA", + "\xF2\xEC" => "\xE5\xAB\x89", + "\xF2\xED" => "\xE5\xB8\x99", + "\xF2\xEE" => "\xE6\xA1\x8E", + "\xF2\xEF" => "\xE7\x93\x86", + "\xF2\xF0" => "\xE7\x96\xBE", + "\xF2\xF1" => "\xE7\xA7\xA9", + "\xF2\xF2" => "\xE7\xAA\x92", + "\xF2\xF3" => "\xE8\x86\xA3", + "\xF2\xF4" => "\xE8\x9B\xAD", + "\xF2\xF5" => "\xE8\xB3\xAA", + "\xF2\xF6" => "\xE8\xB7\x8C", + "\xF2\xF7" => "\xE8\xBF\xAD", + "\xF2\xF8" => "\xE6\x96\x9F", + "\xF2\xF9" => "\xE6\x9C\x95", + "\xF2\xFA" => "\xEF\xA7\xBD", + "\xF2\xFB" => "\xE5\x9F\xB7", + "\xF2\xFC" => "\xE6\xBD\x97", + "\xF2\xFD" => "\xE7\xB7\x9D", + "\xF2\xFE" => "\xE8\xBC\xAF", + "\xF3\xA1" => "\xE9\x8F\xB6", + "\xF3\xA2" => "\xE9\x9B\x86", + "\xF3\xA3" => "\xE5\xBE\xB5", + "\xF3\xA4" => "\xE6\x87\xB2", + "\xF3\xA5" => "\xE6\xBE\x84", + "\xF3\xA6" => "\xE4\xB8\x94", + "\xF3\xA7" => "\xE4\xBE\x98", + "\xF3\xA8" => "\xE5\x80\x9F", + "\xF3\xA9" => "\xE5\x8F\x89", + "\xF3\xAA" => "\xE5\x97\x9F", + "\xF3\xAB" => "\xE5\xB5\xAF", + "\xF3\xAC" => "\xE5\xB7\xAE", + "\xF3\xAD" => "\xE6\xAC\xA1", + "\xF3\xAE" => "\xE6\xAD\xA4", + "\xF3\xAF" => "\xE7\xA3\x8B", + "\xF3\xB0" => "\xE7\xAE\x9A", + "\xF3\xB1" => "\xEF\xA7\xBE", + "\xF3\xB2" => "\xE8\xB9\x89", + "\xF3\xB3" => "\xE8\xBB\x8A", + "\xF3\xB4" => "\xE9\x81\xAE", + "\xF3\xB5" => "\xE6\x8D\x89", + "\xF3\xB6" => "\xE6\x90\xBE", + "\xF3\xB7" => "\xE7\x9D\x80", + "\xF3\xB8" => "\xE7\xAA\x84", + "\xF3\xB9" => "\xE9\x8C\xAF", + "\xF3\xBA" => "\xE9\x91\xBF", + "\xF3\xBB" => "\xE9\xBD\xAA", + "\xF3\xBC" => "\xE6\x92\xB0", + "\xF3\xBD" => "\xE6\xBE\xAF", + "\xF3\xBE" => "\xE7\x87\xA6", + "\xF3\xBF" => "\xE7\x92\xA8", + "\xF3\xC0" => "\xE7\x93\x9A", + "\xF3\xC1" => "\xE7\xAB\x84", + "\xF3\xC2" => "\xE7\xB0\x92", + "\xF3\xC3" => "\xE7\xBA\x82", + "\xF3\xC4" => "\xE7\xB2\xB2", + "\xF3\xC5" => "\xE7\xBA\x98", + "\xF3\xC6" => "\xE8\xAE\x9A", + "\xF3\xC7" => "\xE8\xB4\x8A", + "\xF3\xC8" => "\xE9\x91\xBD", + "\xF3\xC9" => "\xE9\xA4\x90", + "\xF3\xCA" => "\xE9\xA5\x8C", + "\xF3\xCB" => "\xE5\x88\xB9", + "\xF3\xCC" => "\xE5\xAF\x9F", + "\xF3\xCD" => "\xE6\x93\xA6", + "\xF3\xCE" => "\xE6\x9C\xAD", + "\xF3\xCF" => "\xE7\xB4\xAE", + "\xF3\xD0" => "\xE5\x83\xAD", + "\xF3\xD1" => "\xE5\x8F\x83", + "\xF3\xD2" => "\xE5\xA1\xB9", + "\xF3\xD3" => "\xE6\x85\x98", + "\xF3\xD4" => "\xE6\x85\x99", + "\xF3\xD5" => "\xE6\x87\xBA", + "\xF3\xD6" => "\xE6\x96\xAC", + "\xF3\xD7" => "\xE7\xAB\x99", + "\xF3\xD8" => "\xE8\xAE\x92", + "\xF3\xD9" => "\xE8\xAE\x96", + "\xF3\xDA" => "\xE5\x80\x89", + "\xF3\xDB" => "\xE5\x80\xA1", + "\xF3\xDC" => "\xE5\x89\xB5", + "\xF3\xDD" => "\xE5\x94\xB1", + "\xF3\xDE" => "\xE5\xA8\xBC", + "\xF3\xDF" => "\xE5\xBB\xA0", + "\xF3\xE0" => "\xE5\xBD\xB0", + "\xF3\xE1" => "\xE6\x84\xB4", + "\xF3\xE2" => "\xE6\x95\x9E", + "\xF3\xE3" => "\xE6\x98\x8C", + "\xF3\xE4" => "\xE6\x98\xB6", + "\xF3\xE5" => "\xE6\x9A\xA2", + "\xF3\xE6" => "\xE6\xA7\x8D", + "\xF3\xE7" => "\xE6\xBB\x84", + "\xF3\xE8" => "\xE6\xBC\xB2", + "\xF3\xE9" => "\xE7\x8C\x96", + "\xF3\xEA" => "\xE7\x98\xA1", + "\xF3\xEB" => "\xE7\xAA\x93", + "\xF3\xEC" => "\xE8\x84\xB9", + "\xF3\xED" => "\xE8\x89\x99", + "\xF3\xEE" => "\xE8\x8F\x96", + "\xF3\xEF" => "\xE8\x92\xBC", + "\xF3\xF0" => "\xE5\x82\xB5", + "\xF3\xF1" => "\xE5\x9F\xB0", + "\xF3\xF2" => "\xE5\xAF\x80", + "\xF3\xF3" => "\xE5\xAF\xA8", + "\xF3\xF4" => "\xE5\xBD\xA9", + "\xF3\xF5" => "\xE6\x8E\xA1", + "\xF3\xF6" => "\xE7\xA0\xA6", + "\xF3\xF7" => "\xE7\xB6\xB5", + "\xF3\xF8" => "\xE8\x8F\x9C", + "\xF3\xF9" => "\xE8\x94\xA1", + "\xF3\xFA" => "\xE9\x87\x87", + "\xF3\xFB" => "\xE9\x87\xB5", + "\xF3\xFC" => "\xE5\x86\x8A", + "\xF3\xFD" => "\xE6\x9F\xB5", + "\xF3\xFE" => "\xE7\xAD\x96", + "\xF4\xA1" => "\xE8\xB2\xAC", + "\xF4\xA2" => "\xE5\x87\x84", + "\xF4\xA3" => "\xE5\xA6\xBB", + "\xF4\xA4" => "\xE6\x82\xBD", + "\xF4\xA5" => "\xE8\x99\x95", + "\xF4\xA6" => "\xE5\x80\x9C", + "\xF4\xA7" => "\xEF\xA7\xBF", + "\xF4\xA8" => "\xE5\x89\x94", + "\xF4\xA9" => "\xE5\xB0\xBA", + "\xF4\xAA" => "\xE6\x85\xBD", + "\xF4\xAB" => "\xE6\x88\x9A", + "\xF4\xAC" => "\xE6\x8B\x93", + "\xF4\xAD" => "\xE6\x93\xB2", + "\xF4\xAE" => "\xE6\x96\xA5", + "\xF4\xAF" => "\xE6\xBB\x8C", + "\xF4\xB0" => "\xE7\x98\xA0", + "\xF4\xB1" => "\xE8\x84\x8A", + "\xF4\xB2" => "\xE8\xB9\xA0", + "\xF4\xB3" => "\xE9\x99\x9F", + "\xF4\xB4" => "\xE9\x9A\xBB", + "\xF4\xB5" => "\xE4\xBB\x9F", + "\xF4\xB6" => "\xE5\x8D\x83", + "\xF4\xB7" => "\xE5\x96\x98", + "\xF4\xB8" => "\xE5\xA4\xA9", + "\xF4\xB9" => "\xE5\xB7\x9D", + "\xF4\xBA" => "\xE6\x93\x85", + "\xF4\xBB" => "\xE6\xB3\x89", + "\xF4\xBC" => "\xE6\xB7\xBA", + "\xF4\xBD" => "\xE7\x8E\x94", + "\xF4\xBE" => "\xE7\xA9\xBF", + "\xF4\xBF" => "\xE8\x88\x9B", + "\xF4\xC0" => "\xE8\x96\xA6", + "\xF4\xC1" => "\xE8\xB3\xA4", + "\xF4\xC2" => "\xE8\xB8\x90", + "\xF4\xC3" => "\xE9\x81\xB7", + "\xF4\xC4" => "\xE9\x87\xA7", + "\xF4\xC5" => "\xE9\x97\xA1", + "\xF4\xC6" => "\xE9\x98\xA1", + "\xF4\xC7" => "\xE9\x9F\x86", + "\xF4\xC8" => "\xE5\x87\xB8", + "\xF4\xC9" => "\xE5\x93\xB2", + "\xF4\xCA" => "\xE5\x96\x86", + "\xF4\xCB" => "\xE5\xBE\xB9", + "\xF4\xCC" => "\xE6\x92\xA4", + "\xF4\xCD" => "\xE6\xBE\x88", + "\xF4\xCE" => "\xE7\xB6\xB4", + "\xF4\xCF" => "\xE8\xBC\x9F", + "\xF4\xD0" => "\xE8\xBD\x8D", + "\xF4\xD1" => "\xE9\x90\xB5", + "\xF4\xD2" => "\xE5\x83\x89", + "\xF4\xD3" => "\xE5\xB0\x96", + "\xF4\xD4" => "\xE6\xB2\xBE", + "\xF4\xD5" => "\xE6\xB7\xBB", + "\xF4\xD6" => "\xE7\x94\x9B", + "\xF4\xD7" => "\xE7\x9E\xBB", + "\xF4\xD8" => "\xE7\xB0\xBD", + "\xF4\xD9" => "\xE7\xB1\xA4", + "\xF4\xDA" => "\xE8\xA9\xB9", + "\xF4\xDB" => "\xE8\xAB\x82", + "\xF4\xDC" => "\xE5\xA0\x9E", + "\xF4\xDD" => "\xE5\xA6\xBE", + "\xF4\xDE" => "\xE5\xB8\x96", + "\xF4\xDF" => "\xE6\x8D\xB7", + "\xF4\xE0" => "\xE7\x89\x92", + "\xF4\xE1" => "\xE7\x96\x8A", + "\xF4\xE2" => "\xE7\x9D\xAB", + "\xF4\xE3" => "\xE8\xAB\x9C", + "\xF4\xE4" => "\xE8\xB2\xBC", + "\xF4\xE5" => "\xE8\xBC\x92", + "\xF4\xE6" => "\xE5\xBB\xB3", + "\xF4\xE7" => "\xE6\x99\xB4", + "\xF4\xE8" => "\xE6\xB7\xB8", + "\xF4\xE9" => "\xE8\x81\xBD", + "\xF4\xEA" => "\xE8\x8F\x81", + "\xF4\xEB" => "\xE8\xAB\x8B", + "\xF4\xEC" => "\xE9\x9D\x91", + "\xF4\xED" => "\xE9\xAF\x96", + "\xF4\xEE" => "\xEF\xA8\x80", + "\xF4\xEF" => "\xE5\x89\x83", + "\xF4\xF0" => "\xE6\x9B\xBF", + "\xF4\xF1" => "\xE6\xB6\x95", + "\xF4\xF2" => "\xE6\xBB\xAF", + "\xF4\xF3" => "\xE7\xB7\xA0", + "\xF4\xF4" => "\xE8\xAB\xA6", + "\xF4\xF5" => "\xE9\x80\xAE", + "\xF4\xF6" => "\xE9\x81\x9E", + "\xF4\xF7" => "\xE9\xAB\x94", + "\xF4\xF8" => "\xE5\x88\x9D", + "\xF4\xF9" => "\xE5\x89\xBF", + "\xF4\xFA" => "\xE5\x93\xA8", + "\xF4\xFB" => "\xE6\x86\x94", + "\xF4\xFC" => "\xE6\x8A\x84", + "\xF4\xFD" => "\xE6\x8B\x9B", + "\xF4\xFE" => "\xE6\xA2\xA2", + "\xF5\xA1" => "\xE6\xA4\x92", + "\xF5\xA2" => "\xE6\xA5\x9A", + "\xF5\xA3" => "\xE6\xA8\xB5", + "\xF5\xA4" => "\xE7\x82\x92", + "\xF5\xA5" => "\xE7\x84\xA6", + "\xF5\xA6" => "\xE7\xA1\x9D", + "\xF5\xA7" => "\xE7\xA4\x81", + "\xF5\xA8" => "\xE7\xA4\x8E", + "\xF5\xA9" => "\xE7\xA7\x92", + "\xF5\xAA" => "\xE7\xA8\x8D", + "\xF5\xAB" => "\xE8\x82\x96", + "\xF5\xAC" => "\xE8\x89\xB8", + "\xF5\xAD" => "\xE8\x8B\x95", + "\xF5\xAE" => "\xE8\x8D\x89", + "\xF5\xAF" => "\xE8\x95\x89", + "\xF5\xB0" => "\xE8\xB2\x82", + "\xF5\xB1" => "\xE8\xB6\x85", + "\xF5\xB2" => "\xE9\x85\xA2", + "\xF5\xB3" => "\xE9\x86\x8B", + "\xF5\xB4" => "\xE9\x86\xAE", + "\xF5\xB5" => "\xE4\xBF\x83", + "\xF5\xB6" => "\xE5\x9B\x91", + "\xF5\xB7" => "\xE7\x87\xAD", + "\xF5\xB8" => "\xE7\x9F\x97", + "\xF5\xB9" => "\xE8\x9C\x80", + "\xF5\xBA" => "\xE8\xA7\xB8", + "\xF5\xBB" => "\xE5\xAF\xB8", + "\xF5\xBC" => "\xE5\xBF\x96", + "\xF5\xBD" => "\xE6\x9D\x91", + "\xF5\xBE" => "\xE9\x82\xA8", + "\xF5\xBF" => "\xE5\x8F\xA2", + "\xF5\xC0" => "\xE5\xA1\x9A", + "\xF5\xC1" => "\xE5\xAF\xB5", + "\xF5\xC2" => "\xE6\x82\xA4", + "\xF5\xC3" => "\xE6\x86\x81", + "\xF5\xC4" => "\xE6\x91\xA0", + "\xF5\xC5" => "\xE7\xB8\xBD", + "\xF5\xC6" => "\xE8\x81\xB0", + "\xF5\xC7" => "\xE8\x94\xA5", + "\xF5\xC8" => "\xE9\x8A\x83", + "\xF5\xC9" => "\xE6\x92\xAE", + "\xF5\xCA" => "\xE5\x82\xAC", + "\xF5\xCB" => "\xE5\xB4\x94", + "\xF5\xCC" => "\xE6\x9C\x80", + "\xF5\xCD" => "\xE5\xA2\x9C", + "\xF5\xCE" => "\xE6\x8A\xBD", + "\xF5\xCF" => "\xE6\x8E\xA8", + "\xF5\xD0" => "\xE6\xA4\x8E", + "\xF5\xD1" => "\xE6\xA5\xB8", + "\xF5\xD2" => "\xE6\xA8\x9E", + "\xF5\xD3" => "\xE6\xB9\xAB", + "\xF5\xD4" => "\xE7\x9A\xBA", + "\xF5\xD5" => "\xE7\xA7\x8B", + "\xF5\xD6" => "\xE8\x8A\xBB", + "\xF5\xD7" => "\xE8\x90\xA9", + "\xF5\xD8" => "\xE8\xAB\x8F", + "\xF5\xD9" => "\xE8\xB6\xA8", + "\xF5\xDA" => "\xE8\xBF\xBD", + "\xF5\xDB" => "\xE9\x84\x92", + "\xF5\xDC" => "\xE9\x85\x8B", + "\xF5\xDD" => "\xE9\x86\x9C", + "\xF5\xDE" => "\xE9\x8C\x90", + "\xF5\xDF" => "\xE9\x8C\x98", + "\xF5\xE0" => "\xE9\x8E\x9A", + "\xF5\xE1" => "\xE9\x9B\x9B", + "\xF5\xE2" => "\xE9\xA8\xB6", + "\xF5\xE3" => "\xE9\xB0\x8D", + "\xF5\xE4" => "\xE4\xB8\x91", + "\xF5\xE5" => "\xE7\x95\x9C", + "\xF5\xE6" => "\xE7\xA5\x9D", + "\xF5\xE7" => "\xE7\xAB\xBA", + "\xF5\xE8" => "\xE7\xAD\x91", + "\xF5\xE9" => "\xE7\xAF\x89", + "\xF5\xEA" => "\xE7\xB8\xAE", + "\xF5\xEB" => "\xE8\x93\x84", + "\xF5\xEC" => "\xE8\xB9\x99", + "\xF5\xED" => "\xE8\xB9\xB4", + "\xF5\xEE" => "\xE8\xBB\xB8", + "\xF5\xEF" => "\xE9\x80\x90", + "\xF5\xF0" => "\xE6\x98\xA5", + "\xF5\xF1" => "\xE6\xA4\xBF", + "\xF5\xF2" => "\xE7\x91\x83", + "\xF5\xF3" => "\xE5\x87\xBA", + "\xF5\xF4" => "\xE6\x9C\xAE", + "\xF5\xF5" => "\xE9\xBB\x9C", + "\xF5\xF6" => "\xE5\x85\x85", + "\xF5\xF7" => "\xE5\xBF\xA0", + "\xF5\xF8" => "\xE6\xB2\x96", + "\xF5\xF9" => "\xE8\x9F\xB2", + "\xF5\xFA" => "\xE8\xA1\x9D", + "\xF5\xFB" => "\xE8\xA1\xB7", + "\xF5\xFC" => "\xE6\x82\xB4", + "\xF5\xFD" => "\xE8\x86\xB5", + "\xF5\xFE" => "\xE8\x90\x83", + "\xF6\xA1" => "\xE8\xB4\x85", + "\xF6\xA2" => "\xE5\x8F\x96", + "\xF6\xA3" => "\xE5\x90\xB9", + "\xF6\xA4" => "\xE5\x98\xB4", + "\xF6\xA5" => "\xE5\xA8\xB6", + "\xF6\xA6" => "\xE5\xB0\xB1", + "\xF6\xA7" => "\xE7\x82\x8A", + "\xF6\xA8" => "\xE7\xBF\xA0", + "\xF6\xA9" => "\xE8\x81\x9A", + "\xF6\xAA" => "\xE8\x84\x86", + "\xF6\xAB" => "\xE8\x87\xAD", + "\xF6\xAC" => "\xE8\xB6\xA3", + "\xF6\xAD" => "\xE9\x86\x89", + "\xF6\xAE" => "\xE9\xA9\x9F", + "\xF6\xAF" => "\xE9\xB7\xB2", + "\xF6\xB0" => "\xE5\x81\xB4", + "\xF6\xB1" => "\xE4\xBB\x84", + "\xF6\xB2" => "\xE5\x8E\xA0", + "\xF6\xB3" => "\xE6\x83\xBB", + "\xF6\xB4" => "\xE6\xB8\xAC", + "\xF6\xB5" => "\xE5\xB1\xA4", + "\xF6\xB6" => "\xE4\xBE\x88", + "\xF6\xB7" => "\xE5\x80\xA4", + "\xF6\xB8" => "\xE5\x97\xA4", + "\xF6\xB9" => "\xE5\xB3\x99", + "\xF6\xBA" => "\xE5\xB9\x9F", + "\xF6\xBB" => "\xE6\x81\xA5", + "\xF6\xBC" => "\xE6\xA2\x94", + "\xF6\xBD" => "\xE6\xB2\xBB", + "\xF6\xBE" => "\xE6\xB7\x84", + "\xF6\xBF" => "\xE7\x86\xBE", + "\xF6\xC0" => "\xE7\x97\x94", + "\xF6\xC1" => "\xE7\x97\xB4", + "\xF6\xC2" => "\xE7\x99\xA1", + "\xF6\xC3" => "\xE7\xA8\x9A", + "\xF6\xC4" => "\xE7\xA9\x89", + "\xF6\xC5" => "\xE7\xB7\x87", + "\xF6\xC6" => "\xE7\xB7\xBB", + "\xF6\xC7" => "\xE7\xBD\xAE", + "\xF6\xC8" => "\xE8\x87\xB4", + "\xF6\xC9" => "\xE8\x9A\xA9", + "\xF6\xCA" => "\xE8\xBC\x9C", + "\xF6\xCB" => "\xE9\x9B\x89", + "\xF6\xCC" => "\xE9\xA6\xB3", + "\xF6\xCD" => "\xE9\xBD\x92", + "\xF6\xCE" => "\xE5\x89\x87", + "\xF6\xCF" => "\xE5\x8B\x85", + "\xF6\xD0" => "\xE9\xA3\xAD", + "\xF6\xD1" => "\xE8\xA6\xAA", + "\xF6\xD2" => "\xE4\xB8\x83", + "\xF6\xD3" => "\xE6\x9F\x92", + "\xF6\xD4" => "\xE6\xBC\x86", + "\xF6\xD5" => "\xE4\xBE\xB5", + "\xF6\xD6" => "\xE5\xAF\xA2", + "\xF6\xD7" => "\xE6\x9E\x95", + "\xF6\xD8" => "\xE6\xB2\x88", + "\xF6\xD9" => "\xE6\xB5\xB8", + "\xF6\xDA" => "\xE7\x90\x9B", + "\xF6\xDB" => "\xE7\xA0\xA7", + "\xF6\xDC" => "\xE9\x87\x9D", + "\xF6\xDD" => "\xE9\x8D\xBC", + "\xF6\xDE" => "\xE8\x9F\x84", + "\xF6\xDF" => "\xE7\xA7\xA4", + "\xF6\xE0" => "\xE7\xA8\xB1", + "\xF6\xE1" => "\xE5\xBF\xAB", + "\xF6\xE2" => "\xE4\xBB\x96", + "\xF6\xE3" => "\xE5\x92\xA4", + "\xF6\xE4" => "\xE5\x94\xBE", + "\xF6\xE5" => "\xE5\xA2\xAE", + "\xF6\xE6" => "\xE5\xA6\xA5", + "\xF6\xE7" => "\xE6\x83\xB0", + "\xF6\xE8" => "\xE6\x89\x93", + "\xF6\xE9" => "\xE6\x8B\x96", + "\xF6\xEA" => "\xE6\x9C\xB6", + "\xF6\xEB" => "\xE6\xA5\x95", + "\xF6\xEC" => "\xE8\x88\xB5", + "\xF6\xED" => "\xE9\x99\x80", + "\xF6\xEE" => "\xE9\xA6\xB1", + "\xF6\xEF" => "\xE9\xA7\x9D", + "\xF6\xF0" => "\xE5\x80\xAC", + "\xF6\xF1" => "\xE5\x8D\x93", + "\xF6\xF2" => "\xE5\x95\x84", + "\xF6\xF3" => "\xE5\x9D\xBC", + "\xF6\xF4" => "\xEF\xA8\x81", + "\xF6\xF5" => "\xE6\x89\x98", + "\xF6\xF6" => "\xEF\xA8\x82", + "\xF6\xF7" => "\xE6\x93\xA2", + "\xF6\xF8" => "\xE6\x99\xAB", + "\xF6\xF9" => "\xE6\x9F\x9D", + "\xF6\xFA" => "\xE6\xBF\x81", + "\xF6\xFB" => "\xE6\xBF\xAF", + "\xF6\xFC" => "\xE7\x90\xA2", + "\xF6\xFD" => "\xE7\x90\xB8", + "\xF6\xFE" => "\xE8\xA8\x97", + "\xF7\xA1" => "\xE9\x90\xB8", + "\xF7\xA2" => "\xE5\x91\x91", + "\xF7\xA3" => "\xE5\x98\x86", + "\xF7\xA4" => "\xE5\x9D\xA6", + "\xF7\xA5" => "\xE5\xBD\x88", + "\xF7\xA6" => "\xE6\x86\x9A", + "\xF7\xA7" => "\xE6\xAD\x8E", + "\xF7\xA8" => "\xE7\x81\x98", + "\xF7\xA9" => "\xE7\x82\xAD", + "\xF7\xAA" => "\xE7\xB6\xBB", + "\xF7\xAB" => "\xE8\xAA\x95", + "\xF7\xAC" => "\xE5\xA5\xAA", + "\xF7\xAD" => "\xE8\x84\xAB", + "\xF7\xAE" => "\xE6\x8E\xA2", + "\xF7\xAF" => "\xE7\x9C\x88", + "\xF7\xB0" => "\xE8\x80\xBD", + "\xF7\xB1" => "\xE8\xB2\xAA", + "\xF7\xB2" => "\xE5\xA1\x94", + "\xF7\xB3" => "\xE6\x90\xAD", + "\xF7\xB4" => "\xE6\xA6\xBB", + "\xF7\xB5" => "\xE5\xAE\x95", + "\xF7\xB6" => "\xE5\xB8\x91", + "\xF7\xB7" => "\xE6\xB9\xAF", + "\xF7\xB8" => "\xEF\xA8\x83", + "\xF7\xB9" => "\xE8\x95\xA9", + "\xF7\xBA" => "\xE5\x85\x8C", + "\xF7\xBB" => "\xE5\x8F\xB0", + "\xF7\xBC" => "\xE5\xA4\xAA", + "\xF7\xBD" => "\xE6\x80\xA0", + "\xF7\xBE" => "\xE6\x85\x8B", + "\xF7\xBF" => "\xE6\xAE\x86", + "\xF7\xC0" => "\xE6\xB1\xB0", + "\xF7\xC1" => "\xE6\xB3\xB0", + "\xF7\xC2" => "\xE7\xAC\x9E", + "\xF7\xC3" => "\xE8\x83\x8E", + "\xF7\xC4" => "\xE8\x8B\x94", + "\xF7\xC5" => "\xE8\xB7\x86", + "\xF7\xC6" => "\xE9\x82\xB0", + "\xF7\xC7" => "\xE9\xA2\xB1", + "\xF7\xC8" => "\xEF\xA8\x84", + "\xF7\xC9" => "\xE6\x93\x87", + "\xF7\xCA" => "\xE6\xBE\xA4", + "\xF7\xCB" => "\xE6\x92\x91", + "\xF7\xCC" => "\xE6\x94\x84", + "\xF7\xCD" => "\xE5\x85\x8E", + "\xF7\xCE" => "\xE5\x90\x90", + "\xF7\xCF" => "\xE5\x9C\x9F", + "\xF7\xD0" => "\xE8\xA8\x8E", + "\xF7\xD1" => "\xE6\x85\x9F", + "\xF7\xD2" => "\xE6\xA1\xB6", + "\xF7\xD3" => "\xEF\xA8\x85", + "\xF7\xD4" => "\xE7\x97\x9B", + "\xF7\xD5" => "\xE7\xAD\x92", + "\xF7\xD6" => "\xE7\xB5\xB1", + "\xF7\xD7" => "\xE9\x80\x9A", + "\xF7\xD8" => "\xE5\xA0\x86", + "\xF7\xD9" => "\xE6\xA7\x8C", + "\xF7\xDA" => "\xE8\x85\xBF", + "\xF7\xDB" => "\xE8\xA4\xAA", + "\xF7\xDC" => "\xE9\x80\x80", + "\xF7\xDD" => "\xE9\xA0\xB9", + "\xF7\xDE" => "\xE5\x81\xB8", + "\xF7\xDF" => "\xE5\xA5\x97", + "\xF7\xE0" => "\xE5\xA6\xAC", + "\xF7\xE1" => "\xE6\x8A\x95", + "\xF7\xE2" => "\xE9\x80\x8F", + "\xF7\xE3" => "\xE9\xAC\xAA", + "\xF7\xE4" => "\xE6\x85\x9D", + "\xF7\xE5" => "\xE7\x89\xB9", + "\xF7\xE6" => "\xE9\x97\x96", + "\xF7\xE7" => "\xE5\x9D\xA1", + "\xF7\xE8" => "\xE5\xA9\x86", + "\xF7\xE9" => "\xE5\xB7\xB4", + "\xF7\xEA" => "\xE6\x8A\x8A", + "\xF7\xEB" => "\xE6\x92\xAD", + "\xF7\xEC" => "\xE6\x93\xBA", + "\xF7\xED" => "\xE6\x9D\xB7", + "\xF7\xEE" => "\xE6\xB3\xA2", + "\xF7\xEF" => "\xE6\xB4\xBE", + "\xF7\xF0" => "\xE7\x88\xAC", + "\xF7\xF1" => "\xE7\x90\xB6", + "\xF7\xF2" => "\xE7\xA0\xB4", + "\xF7\xF3" => "\xE7\xBD\xB7", + "\xF7\xF4" => "\xE8\x8A\xAD", + "\xF7\xF5" => "\xE8\xB7\x9B", + "\xF7\xF6" => "\xE9\xA0\x97", + "\xF7\xF7" => "\xE5\x88\xA4", + "\xF7\xF8" => "\xE5\x9D\x82", + "\xF7\xF9" => "\xE6\x9D\xBF", + "\xF7\xFA" => "\xE7\x89\x88", + "\xF7\xFB" => "\xE7\x93\xA3", + "\xF7\xFC" => "\xE8\xB2\xA9", + "\xF7\xFD" => "\xE8\xBE\xA6", + "\xF7\xFE" => "\xE9\x88\x91", + "\xF8\xA1" => "\xE9\x98\xAA", + "\xF8\xA2" => "\xE5\x85\xAB", + "\xF8\xA3" => "\xE5\x8F\xAD", + "\xF8\xA4" => "\xE6\x8D\x8C", + "\xF8\xA5" => "\xE4\xBD\xA9", + "\xF8\xA6" => "\xE5\x94\x84", + "\xF8\xA7" => "\xE6\x82\x96", + "\xF8\xA8" => "\xE6\x95\x97", + "\xF8\xA9" => "\xE6\xB2\x9B", + "\xF8\xAA" => "\xE6\xB5\xBF", + "\xF8\xAB" => "\xE7\x89\x8C", + "\xF8\xAC" => "\xE7\x8B\xBD", + "\xF8\xAD" => "\xE7\xA8\x97", + "\xF8\xAE" => "\xE8\xA6\x87", + "\xF8\xAF" => "\xE8\xB2\x9D", + "\xF8\xB0" => "\xE5\xBD\xAD", + "\xF8\xB1" => "\xE6\xBE\x8E", + "\xF8\xB2" => "\xE7\x83\xB9", + "\xF8\xB3" => "\xE8\x86\xA8", + "\xF8\xB4" => "\xE6\x84\x8E", + "\xF8\xB5" => "\xE4\xBE\xBF", + "\xF8\xB6" => "\xE5\x81\x8F", + "\xF8\xB7" => "\xE6\x89\x81", + "\xF8\xB8" => "\xE7\x89\x87", + "\xF8\xB9" => "\xE7\xAF\x87", + "\xF8\xBA" => "\xE7\xB7\xA8", + "\xF8\xBB" => "\xE7\xBF\xA9", + "\xF8\xBC" => "\xE9\x81\x8D", + "\xF8\xBD" => "\xE9\x9E\xAD", + "\xF8\xBE" => "\xE9\xA8\x99", + "\xF8\xBF" => "\xE8\xB2\xB6", + "\xF8\xC0" => "\xE5\x9D\xAA", + "\xF8\xC1" => "\xE5\xB9\xB3", + "\xF8\xC2" => "\xE6\x9E\xB0", + "\xF8\xC3" => "\xE8\x90\x8D", + "\xF8\xC4" => "\xE8\xA9\x95", + "\xF8\xC5" => "\xE5\x90\xA0", + "\xF8\xC6" => "\xE5\xAC\x96", + "\xF8\xC7" => "\xE5\xB9\xA3", + "\xF8\xC8" => "\xE5\xBB\xA2", + "\xF8\xC9" => "\xE5\xBC\x8A", + "\xF8\xCA" => "\xE6\x96\x83", + "\xF8\xCB" => "\xE8\x82\xBA", + "\xF8\xCC" => "\xE8\x94\xBD", + "\xF8\xCD" => "\xE9\x96\x89", + "\xF8\xCE" => "\xE9\x99\x9B", + "\xF8\xCF" => "\xE4\xBD\x88", + "\xF8\xD0" => "\xE5\x8C\x85", + "\xF8\xD1" => "\xE5\x8C\x8D", + "\xF8\xD2" => "\xE5\x8C\x8F", + "\xF8\xD3" => "\xE5\x92\x86", + "\xF8\xD4" => "\xE5\x93\xBA", + "\xF8\xD5" => "\xE5\x9C\x83", + "\xF8\xD6" => "\xE5\xB8\x83", + "\xF8\xD7" => "\xE6\x80\x96", + "\xF8\xD8" => "\xE6\x8A\x9B", + "\xF8\xD9" => "\xE6\x8A\xB1", + "\xF8\xDA" => "\xE6\x8D\x95", + "\xF8\xDB" => "\xEF\xA8\x86", + "\xF8\xDC" => "\xE6\xB3\xA1", + "\xF8\xDD" => "\xE6\xB5\xA6", + "\xF8\xDE" => "\xE7\x96\xB1", + "\xF8\xDF" => "\xE7\xA0\xB2", + "\xF8\xE0" => "\xE8\x83\x9E", + "\xF8\xE1" => "\xE8\x84\xAF", + "\xF8\xE2" => "\xE8\x8B\x9E", + "\xF8\xE3" => "\xE8\x91\xA1", + "\xF8\xE4" => "\xE8\x92\xB2", + "\xF8\xE5" => "\xE8\xA2\x8D", + "\xF8\xE6" => "\xE8\xA4\x92", + "\xF8\xE7" => "\xE9\x80\x8B", + "\xF8\xE8" => "\xE9\x8B\xAA", + "\xF8\xE9" => "\xE9\xA3\xBD", + "\xF8\xEA" => "\xE9\xAE\x91", + "\xF8\xEB" => "\xE5\xB9\x85", + "\xF8\xEC" => "\xE6\x9A\xB4", + "\xF8\xED" => "\xE6\x9B\x9D", + "\xF8\xEE" => "\xE7\x80\x91", + "\xF8\xEF" => "\xE7\x88\x86", + "\xF8\xF0" => "\xEF\xA8\x87", + "\xF8\xF1" => "\xE4\xBF\xB5", + "\xF8\xF2" => "\xE5\x89\xBD", + "\xF8\xF3" => "\xE5\xBD\xAA", + "\xF8\xF4" => "\xE6\x85\x93", + "\xF8\xF5" => "\xE6\x9D\x93", + "\xF8\xF6" => "\xE6\xA8\x99", + "\xF8\xF7" => "\xE6\xBC\x82", + "\xF8\xF8" => "\xE7\x93\xA2", + "\xF8\xF9" => "\xE7\xA5\xA8", + "\xF8\xFA" => "\xE8\xA1\xA8", + "\xF8\xFB" => "\xE8\xB1\xB9", + "\xF8\xFC" => "\xE9\xA3\x87", + "\xF8\xFD" => "\xE9\xA3\x84", + "\xF8\xFE" => "\xE9\xA9\x83", + "\xF9\xA1" => "\xE5\x93\x81", + "\xF9\xA2" => "\xE7\xA8\x9F", + "\xF9\xA3" => "\xE6\xA5\x93", + "\xF9\xA4" => "\xE8\xAB\xB7", + "\xF9\xA5" => "\xE8\xB1\x8A", + "\xF9\xA6" => "\xE9\xA2\xA8", + "\xF9\xA7" => "\xE9\xA6\xAE", + "\xF9\xA8" => "\xE5\xBD\xBC", + "\xF9\xA9" => "\xE6\x8A\xAB", + "\xF9\xAA" => "\xE7\x96\xB2", + "\xF9\xAB" => "\xE7\x9A\xAE", + "\xF9\xAC" => "\xE8\xA2\xAB", + "\xF9\xAD" => "\xE9\x81\xBF", + "\xF9\xAE" => "\xE9\x99\x82", + "\xF9\xAF" => "\xE5\x8C\xB9", + "\xF9\xB0" => "\xE5\xBC\xBC", + "\xF9\xB1" => "\xE5\xBF\x85", + "\xF9\xB2" => "\xE6\xB3\x8C", + "\xF9\xB3" => "\xE7\x8F\x8C", + "\xF9\xB4" => "\xE7\x95\xA2", + "\xF9\xB5" => "\xE7\x96\x8B", + "\xF9\xB6" => "\xE7\xAD\x86", + "\xF9\xB7" => "\xE8\x8B\xBE", + "\xF9\xB8" => "\xE9\xA6\x9D", + "\xF9\xB9" => "\xE4\xB9\x8F", + "\xF9\xBA" => "\xE9\x80\xBC", + "\xF9\xBB" => "\xE4\xB8\x8B", + "\xF9\xBC" => "\xE4\xBD\x95", + "\xF9\xBD" => "\xE5\x8E\xA6", + "\xF9\xBE" => "\xE5\xA4\x8F", + "\xF9\xBF" => "\xE5\xBB\x88", + "\xF9\xC0" => "\xE6\x98\xB0", + "\xF9\xC1" => "\xE6\xB2\xB3", + "\xF9\xC2" => "\xE7\x91\x95", + "\xF9\xC3" => "\xE8\x8D\xB7", + "\xF9\xC4" => "\xE8\x9D\xA6", + "\xF9\xC5" => "\xE8\xB3\x80", + "\xF9\xC6" => "\xE9\x81\x90", + "\xF9\xC7" => "\xE9\x9C\x9E", + "\xF9\xC8" => "\xE9\xB0\x95", + "\xF9\xC9" => "\xE5\xA3\x91", + "\xF9\xCA" => "\xE5\xAD\xB8", + "\xF9\xCB" => "\xE8\x99\x90", + "\xF9\xCC" => "\xE8\xAC\x94", + "\xF9\xCD" => "\xE9\xB6\xB4", + "\xF9\xCE" => "\xE5\xAF\x92", + "\xF9\xCF" => "\xE6\x81\xA8", + "\xF9\xD0" => "\xE6\x82\x8D", + "\xF9\xD1" => "\xE6\x97\xB1", + "\xF9\xD2" => "\xE6\xB1\x97", + "\xF9\xD3" => "\xE6\xBC\xA2", + "\xF9\xD4" => "\xE6\xBE\xA3", + "\xF9\xD5" => "\xE7\x80\x9A", + "\xF9\xD6" => "\xE7\xBD\x95", + "\xF9\xD7" => "\xE7\xBF\xB0", + "\xF9\xD8" => "\xE9\x96\x91", + "\xF9\xD9" => "\xE9\x96\x92", + "\xF9\xDA" => "\xE9\x99\x90", + "\xF9\xDB" => "\xE9\x9F\x93", + "\xF9\xDC" => "\xE5\x89\xB2", + "\xF9\xDD" => "\xE8\xBD\x84", + "\xF9\xDE" => "\xE5\x87\xBD", + "\xF9\xDF" => "\xE5\x90\xAB", + "\xF9\xE0" => "\xE5\x92\xB8", + "\xF9\xE1" => "\xE5\x95\xA3", + "\xF9\xE2" => "\xE5\x96\x8A", + "\xF9\xE3" => "\xE6\xAA\xBB", + "\xF9\xE4" => "\xE6\xB6\xB5", + "\xF9\xE5" => "\xE7\xB7\x98", + "\xF9\xE6" => "\xE8\x89\xA6", + "\xF9\xE7" => "\xE9\x8A\x9C", + "\xF9\xE8" => "\xE9\x99\xB7", + "\xF9\xE9" => "\xE9\xB9\xB9", + "\xF9\xEA" => "\xE5\x90\x88", + "\xF9\xEB" => "\xE5\x93\x88", + "\xF9\xEC" => "\xE7\x9B\x92", + "\xF9\xED" => "\xE8\x9B\xA4", + "\xF9\xEE" => "\xE9\x96\xA4", + "\xF9\xEF" => "\xE9\x97\x94", + "\xF9\xF0" => "\xE9\x99\x9C", + "\xF9\xF1" => "\xE4\xBA\xA2", + "\xF9\xF2" => "\xE4\xBC\x89", + "\xF9\xF3" => "\xE5\xA7\xAE", + "\xF9\xF4" => "\xE5\xAB\xA6", + "\xF9\xF5" => "\xE5\xB7\xB7", + "\xF9\xF6" => "\xE6\x81\x92", + "\xF9\xF7" => "\xE6\x8A\x97", + "\xF9\xF8" => "\xE6\x9D\xAD", + "\xF9\xF9" => "\xE6\xA1\x81", + "\xF9\xFA" => "\xE6\xB2\x86", + "\xF9\xFB" => "\xE6\xB8\xAF", + "\xF9\xFC" => "\xE7\xBC\xB8", + "\xF9\xFD" => "\xE8\x82\x9B", + "\xF9\xFE" => "\xE8\x88\xAA", + "\xFA\xA1" => "\xEF\xA8\x88", + "\xFA\xA2" => "\xEF\xA8\x89", + "\xFA\xA3" => "\xE9\xA0\x85", + "\xFA\xA4" => "\xE4\xBA\xA5", + "\xFA\xA5" => "\xE5\x81\x95", + "\xFA\xA6" => "\xE5\x92\xB3", + "\xFA\xA7" => "\xE5\x9E\x93", + "\xFA\xA8" => "\xE5\xA5\x9A", + "\xFA\xA9" => "\xE5\xAD\xA9", + "\xFA\xAA" => "\xE5\xAE\xB3", + "\xFA\xAB" => "\xE6\x87\x88", + "\xFA\xAC" => "\xE6\xA5\xB7", + "\xFA\xAD" => "\xE6\xB5\xB7", + "\xFA\xAE" => "\xE7\x80\xA3", + "\xFA\xAF" => "\xE8\x9F\xB9", + "\xFA\xB0" => "\xE8\xA7\xA3", + "\xFA\xB1" => "\xE8\xA9\xB2", + "\xFA\xB2" => "\xE8\xAB\xA7", + "\xFA\xB3" => "\xE9\x82\x82", + "\xFA\xB4" => "\xE9\xA7\xAD", + "\xFA\xB5" => "\xE9\xAA\xB8", + "\xFA\xB6" => "\xE5\x8A\xBE", + "\xFA\xB7" => "\xE6\xA0\xB8", + "\xFA\xB8" => "\xE5\x80\x96", + "\xFA\xB9" => "\xE5\xB9\xB8", + "\xFA\xBA" => "\xE6\x9D\x8F", + "\xFA\xBB" => "\xE8\x8D\x87", + "\xFA\xBC" => "\xE8\xA1\x8C", + "\xFA\xBD" => "\xE4\xBA\xAB", + "\xFA\xBE" => "\xE5\x90\x91", + "\xFA\xBF" => "\xE5\x9A\xAE", + "\xFA\xC0" => "\xE7\x8F\xA6", + "\xFA\xC1" => "\xE9\x84\x95", + "\xFA\xC2" => "\xE9\x9F\xBF", + "\xFA\xC3" => "\xE9\xA4\x89", + "\xFA\xC4" => "\xE9\xA5\x97", + "\xFA\xC5" => "\xE9\xA6\x99", + "\xFA\xC6" => "\xE5\x99\x93", + "\xFA\xC7" => "\xE5\xA2\x9F", + "\xFA\xC8" => "\xE8\x99\x9B", + "\xFA\xC9" => "\xE8\xA8\xB1", + "\xFA\xCA" => "\xE6\x86\xB2", + "\xFA\xCB" => "\xE6\xAB\xB6", + "\xFA\xCC" => "\xE7\x8D\xBB", + "\xFA\xCD" => "\xE8\xBB\x92", + "\xFA\xCE" => "\xE6\xAD\x87", + "\xFA\xCF" => "\xE9\x9A\xAA", + "\xFA\xD0" => "\xE9\xA9\x97", + "\xFA\xD1" => "\xE5\xA5\x95", + "\xFA\xD2" => "\xE7\x88\x80", + "\xFA\xD3" => "\xE8\xB5\xAB", + "\xFA\xD4" => "\xE9\x9D\xA9", + "\xFA\xD5" => "\xE4\xBF\x94", + "\xFA\xD6" => "\xE5\xB3\xB4", + "\xFA\xD7" => "\xE5\xBC\xA6", + "\xFA\xD8" => "\xE6\x87\xB8", + "\xFA\xD9" => "\xE6\x99\x9B", + "\xFA\xDA" => "\xE6\xB3\xAB", + "\xFA\xDB" => "\xE7\x82\xAB", + "\xFA\xDC" => "\xE7\x8E\x84", + "\xFA\xDD" => "\xE7\x8E\xB9", + "\xFA\xDE" => "\xE7\x8F\xBE", + "\xFA\xDF" => "\xE7\x9C\xA9", + "\xFA\xE0" => "\xE7\x9D\x8D", + "\xFA\xE1" => "\xE7\xB5\x83", + "\xFA\xE2" => "\xE7\xB5\xA2", + "\xFA\xE3" => "\xE7\xB8\xA3", + "\xFA\xE4" => "\xE8\x88\xB7", + "\xFA\xE5" => "\xE8\xA1\x92", + "\xFA\xE6" => "\xEF\xA8\x8A", + "\xFA\xE7" => "\xE8\xB3\xA2", + "\xFA\xE8" => "\xE9\x89\x89", + "\xFA\xE9" => "\xE9\xA1\xAF", + "\xFA\xEA" => "\xE5\xAD\x91", + "\xFA\xEB" => "\xE7\xA9\xB4", + "\xFA\xEC" => "\xE8\xA1\x80", + "\xFA\xED" => "\xE9\xA0\x81", + "\xFA\xEE" => "\xE5\xAB\x8C", + "\xFA\xEF" => "\xE4\xBF\xA0", + "\xFA\xF0" => "\xE5\x8D\x94", + "\xFA\xF1" => "\xE5\xA4\xBE", + "\xFA\xF2" => "\xE5\xB3\xBD", + "\xFA\xF3" => "\xE6\x8C\xBE", + "\xFA\xF4" => "\xE6\xB5\xB9", + "\xFA\xF5" => "\xE7\x8B\xB9", + "\xFA\xF6" => "\xE8\x84\x85", + "\xFA\xF7" => "\xE8\x84\x87", + "\xFA\xF8" => "\xE8\x8E\xA2", + "\xFA\xF9" => "\xE9\x8B\x8F", + "\xFA\xFA" => "\xE9\xA0\xB0", + "\xFA\xFB" => "\xE4\xBA\xA8", + "\xFA\xFC" => "\xE5\x85\x84", + "\xFA\xFD" => "\xE5\x88\x91", + "\xFA\xFE" => "\xE5\x9E\x8B", + "\xFB\xA1" => "\xE5\xBD\xA2", + "\xFB\xA2" => "\xE6\xB3\x82", + "\xFB\xA3" => "\xE6\xBB\x8E", + "\xFB\xA4" => "\xE7\x80\x85", + "\xFB\xA5" => "\xE7\x81\x90", + "\xFB\xA6" => "\xE7\x82\xAF", + "\xFB\xA7" => "\xE7\x86\x92", + "\xFB\xA8" => "\xE7\x8F\xA9", + "\xFB\xA9" => "\xE7\x91\xA9", + "\xFB\xAA" => "\xE8\x8D\x8A", + "\xFB\xAB" => "\xE8\x9E\xA2", + "\xFB\xAC" => "\xE8\xA1\xA1", + "\xFB\xAD" => "\xE9\x80\x88", + "\xFB\xAE" => "\xE9\x82\xA2", + "\xFB\xAF" => "\xE9\x8E\xA3", + "\xFB\xB0" => "\xE9\xA6\xA8", + "\xFB\xB1" => "\xE5\x85\xAE", + "\xFB\xB2" => "\xE5\xBD\x97", + "\xFB\xB3" => "\xE6\x83\xA0", + "\xFB\xB4" => "\xE6\x85\xA7", + "\xFB\xB5" => "\xE6\x9A\xB3", + "\xFB\xB6" => "\xE8\x95\x99", + "\xFB\xB7" => "\xE8\xB9\x8A", + "\xFB\xB8" => "\xE9\x86\xAF", + "\xFB\xB9" => "\xE9\x9E\x8B", + "\xFB\xBA" => "\xE4\xB9\x8E", + "\xFB\xBB" => "\xE4\xBA\x92", + "\xFB\xBC" => "\xE5\x91\xBC", + "\xFB\xBD" => "\xE5\xA3\x95", + "\xFB\xBE" => "\xE5\xA3\xBA", + "\xFB\xBF" => "\xE5\xA5\xBD", + "\xFB\xC0" => "\xE5\xB2\xB5", + "\xFB\xC1" => "\xE5\xBC\xA7", + "\xFB\xC2" => "\xE6\x88\xB6", + "\xFB\xC3" => "\xE6\x89\x88", + "\xFB\xC4" => "\xE6\x98\x8A", + "\xFB\xC5" => "\xE6\x99\xA7", + "\xFB\xC6" => "\xE6\xAF\xAB", + "\xFB\xC7" => "\xE6\xB5\xA9", + "\xFB\xC8" => "\xE6\xB7\x8F", + "\xFB\xC9" => "\xE6\xB9\x96", + "\xFB\xCA" => "\xE6\xBB\xB8", + "\xFB\xCB" => "\xE6\xBE\x94", + "\xFB\xCC" => "\xE6\xBF\xA0", + "\xFB\xCD" => "\xE6\xBF\xA9", + "\xFB\xCE" => "\xE7\x81\x9D", + "\xFB\xCF" => "\xE7\x8B\x90", + "\xFB\xD0" => "\xE7\x90\xA5", + "\xFB\xD1" => "\xE7\x91\x9A", + "\xFB\xD2" => "\xE7\x93\xA0", + "\xFB\xD3" => "\xE7\x9A\x93", + "\xFB\xD4" => "\xE7\xA5\x9C", + "\xFB\xD5" => "\xE7\xB3\x8A", + "\xFB\xD6" => "\xE7\xB8\x9E", + "\xFB\xD7" => "\xE8\x83\xA1", + "\xFB\xD8" => "\xE8\x8A\xA6", + "\xFB\xD9" => "\xE8\x91\xAB", + "\xFB\xDA" => "\xE8\x92\xBF", + "\xFB\xDB" => "\xE8\x99\x8E", + "\xFB\xDC" => "\xE8\x99\x9F", + "\xFB\xDD" => "\xE8\x9D\xB4", + "\xFB\xDE" => "\xE8\xAD\xB7", + "\xFB\xDF" => "\xE8\xB1\xAA", + "\xFB\xE0" => "\xE9\x8E\xAC", + "\xFB\xE1" => "\xE9\xA0\x80", + "\xFB\xE2" => "\xE9\xA1\xA5", + "\xFB\xE3" => "\xE6\x83\x91", + "\xFB\xE4" => "\xE6\x88\x96", + "\xFB\xE5" => "\xE9\x85\xB7", + "\xFB\xE6" => "\xE5\xA9\x9A", + "\xFB\xE7" => "\xE6\x98\x8F", + "\xFB\xE8" => "\xE6\xB7\xB7", + "\xFB\xE9" => "\xE6\xB8\xBE", + "\xFB\xEA" => "\xE7\x90\xBF", + "\xFB\xEB" => "\xE9\xAD\x82", + "\xFB\xEC" => "\xE5\xBF\xBD", + "\xFB\xED" => "\xE6\x83\x9A", + "\xFB\xEE" => "\xE7\xAC\x8F", + "\xFB\xEF" => "\xE5\x93\x84", + "\xFB\xF0" => "\xE5\xBC\x98", + "\xFB\xF1" => "\xE6\xB1\x9E", + "\xFB\xF2" => "\xE6\xB3\x93", + "\xFB\xF3" => "\xE6\xB4\xAA", + "\xFB\xF4" => "\xE7\x83\x98", + "\xFB\xF5" => "\xE7\xB4\x85", + "\xFB\xF6" => "\xE8\x99\xB9", + "\xFB\xF7" => "\xE8\xA8\x8C", + "\xFB\xF8" => "\xE9\xB4\xBB", + "\xFB\xF9" => "\xE5\x8C\x96", + "\xFB\xFA" => "\xE5\x92\x8C", + "\xFB\xFB" => "\xE5\xAC\x85", + "\xFB\xFC" => "\xE6\xA8\xBA", + "\xFB\xFD" => "\xE7\x81\xAB", + "\xFB\xFE" => "\xE7\x95\xB5", + "\xFC\xA1" => "\xE7\xA6\x8D", + "\xFC\xA2" => "\xE7\xA6\xBE", + "\xFC\xA3" => "\xE8\x8A\xB1", + "\xFC\xA4" => "\xE8\x8F\xAF", + "\xFC\xA5" => "\xE8\xA9\xB1", + "\xFC\xA6" => "\xE8\xAD\x81", + "\xFC\xA7" => "\xE8\xB2\xA8", + "\xFC\xA8" => "\xE9\x9D\xB4", + "\xFC\xA9" => "\xEF\xA8\x8B", + "\xFC\xAA" => "\xE6\x93\xB4", + "\xFC\xAB" => "\xE6\x94\xAB", + "\xFC\xAC" => "\xE7\xA2\xBA", + "\xFC\xAD" => "\xE7\xA2\xBB", + "\xFC\xAE" => "\xE7\xA9\xAB", + "\xFC\xAF" => "\xE4\xB8\xB8", + "\xFC\xB0" => "\xE5\x96\x9A", + "\xFC\xB1" => "\xE5\xA5\x90", + "\xFC\xB2" => "\xE5\xAE\xA6", + "\xFC\xB3" => "\xE5\xB9\xBB", + "\xFC\xB4" => "\xE6\x82\xA3", + "\xFC\xB5" => "\xE6\x8F\x9B", + "\xFC\xB6" => "\xE6\xAD\xA1", + "\xFC\xB7" => "\xE6\x99\xA5", + "\xFC\xB8" => "\xE6\xA1\x93", + "\xFC\xB9" => "\xE6\xB8\x99", + "\xFC\xBA" => "\xE7\x85\xA5", + "\xFC\xBB" => "\xE7\x92\xB0", + "\xFC\xBC" => "\xE7\xB4\x88", + "\xFC\xBD" => "\xE9\x82\x84", + "\xFC\xBE" => "\xE9\xA9\xA9", + "\xFC\xBF" => "\xE9\xB0\xA5", + "\xFC\xC0" => "\xE6\xB4\xBB", + "\xFC\xC1" => "\xE6\xBB\x91", + "\xFC\xC2" => "\xE7\x8C\xBE", + "\xFC\xC3" => "\xE8\xB1\x81", + "\xFC\xC4" => "\xE9\x97\x8A", + "\xFC\xC5" => "\xE5\x87\xB0", + "\xFC\xC6" => "\xE5\xB9\x8C", + "\xFC\xC7" => "\xE5\xBE\xA8", + "\xFC\xC8" => "\xE6\x81\x8D", + "\xFC\xC9" => "\xE6\x83\xB6", + "\xFC\xCA" => "\xE6\x84\xB0", + "\xFC\xCB" => "\xE6\x85\x8C", + "\xFC\xCC" => "\xE6\x99\x83", + "\xFC\xCD" => "\xE6\x99\x84", + "\xFC\xCE" => "\xE6\xA6\xA5", + "\xFC\xCF" => "\xE6\xB3\x81", + "\xFC\xD0" => "\xE6\xB9\x9F", + "\xFC\xD1" => "\xE6\xBB\x89", + "\xFC\xD2" => "\xE6\xBD\xA2", + "\xFC\xD3" => "\xE7\x85\x8C", + "\xFC\xD4" => "\xE7\x92\x9C", + "\xFC\xD5" => "\xE7\x9A\x87", + "\xFC\xD6" => "\xE7\xAF\x81", + "\xFC\xD7" => "\xE7\xB0\xA7", + "\xFC\xD8" => "\xE8\x8D\x92", + "\xFC\xD9" => "\xE8\x9D\x97", + "\xFC\xDA" => "\xE9\x81\x91", + "\xFC\xDB" => "\xE9\x9A\x8D", + "\xFC\xDC" => "\xE9\xBB\x83", + "\xFC\xDD" => "\xE5\x8C\xAF", + "\xFC\xDE" => "\xE5\x9B\x9E", + "\xFC\xDF" => "\xE5\xBB\xBB", + "\xFC\xE0" => "\xE5\xBE\x8A", + "\xFC\xE1" => "\xE6\x81\xA2", + "\xFC\xE2" => "\xE6\x82\x94", + "\xFC\xE3" => "\xE6\x87\xB7", + "\xFC\xE4" => "\xE6\x99\xA6", + "\xFC\xE5" => "\xE6\x9C\x83", + "\xFC\xE6" => "\xE6\xAA\x9C", + "\xFC\xE7" => "\xE6\xB7\xAE", + "\xFC\xE8" => "\xE6\xBE\xAE", + "\xFC\xE9" => "\xE7\x81\xB0", + "\xFC\xEA" => "\xE7\x8D\xAA", + "\xFC\xEB" => "\xE7\xB9\xAA", + "\xFC\xEC" => "\xE8\x86\xBE", + "\xFC\xED" => "\xE8\x8C\xB4", + "\xFC\xEE" => "\xE8\x9B\x94", + "\xFC\xEF" => "\xE8\xAA\xA8", + "\xFC\xF0" => "\xE8\xB3\x84", + "\xFC\xF1" => "\xE5\x8A\x83", + "\xFC\xF2" => "\xE7\x8D\xB2", + "\xFC\xF3" => "\xE5\xAE\x96", + "\xFC\xF4" => "\xE6\xA9\xAB", + "\xFC\xF5" => "\xE9\x90\x84", + "\xFC\xF6" => "\xE5\x93\xAE", + "\xFC\xF7" => "\xE5\x9A\x86", + "\xFC\xF8" => "\xE5\xAD\x9D", + "\xFC\xF9" => "\xE6\x95\x88", + "\xFC\xFA" => "\xE6\x96\x85", + "\xFC\xFB" => "\xE6\x9B\x89", + "\xFC\xFC" => "\xE6\xA2\x9F", + "\xFC\xFD" => "\xE6\xB6\x8D", + "\xFC\xFE" => "\xE6\xB7\x86", + "\xFD\xA1" => "\xE7\x88\xBB", + "\xFD\xA2" => "\xE8\x82\xB4", + "\xFD\xA3" => "\xE9\x85\xB5", + "\xFD\xA4" => "\xE9\xA9\x8D", + "\xFD\xA5" => "\xE4\xBE\xAF", + "\xFD\xA6" => "\xE5\x80\x99", + "\xFD\xA7" => "\xE5\x8E\x9A", + "\xFD\xA8" => "\xE5\x90\x8E", + "\xFD\xA9" => "\xE5\x90\xBC", + "\xFD\xAA" => "\xE5\x96\x89", + "\xFD\xAB" => "\xE5\x97\x85", + "\xFD\xAC" => "\xE5\xB8\xBF", + "\xFD\xAD" => "\xE5\xBE\x8C", + "\xFD\xAE" => "\xE6\x9C\xBD", + "\xFD\xAF" => "\xE7\x85\xA6", + "\xFD\xB0" => "\xE7\x8F\x9D", + "\xFD\xB1" => "\xE9\x80\x85", + "\xFD\xB2" => "\xE5\x8B\x9B", + "\xFD\xB3" => "\xE5\x8B\xB3", + "\xFD\xB4" => "\xE5\xA1\xA4", + "\xFD\xB5" => "\xE5\xA3\x8E", + "\xFD\xB6" => "\xE7\x84\x84", + "\xFD\xB7" => "\xE7\x86\x8F", + "\xFD\xB8" => "\xE7\x87\xBB", + "\xFD\xB9" => "\xE8\x96\xB0", + "\xFD\xBA" => "\xE8\xA8\x93", + "\xFD\xBB" => "\xE6\x9A\x88", + "\xFD\xBC" => "\xE8\x96\xA8", + "\xFD\xBD" => "\xE5\x96\xA7", + "\xFD\xBE" => "\xE6\x9A\x84", + "\xFD\xBF" => "\xE7\x85\x8A", + "\xFD\xC0" => "\xE8\x90\xB1", + "\xFD\xC1" => "\xE5\x8D\x89", + "\xFD\xC2" => "\xE5\x96\x99", + "\xFD\xC3" => "\xE6\xAF\x81", + "\xFD\xC4" => "\xE5\xBD\x99", + "\xFD\xC5" => "\xE5\xBE\xBD", + "\xFD\xC6" => "\xE6\x8F\xAE", + "\xFD\xC7" => "\xE6\x9A\x89", + "\xFD\xC8" => "\xE7\x85\x87", + "\xFD\xC9" => "\xE8\xAB\xB1", + "\xFD\xCA" => "\xE8\xBC\x9D", + "\xFD\xCB" => "\xE9\xBA\xBE", + "\xFD\xCC" => "\xE4\xBC\x91", + "\xFD\xCD" => "\xE6\x90\xBA", + "\xFD\xCE" => "\xE7\x83\x8B", + "\xFD\xCF" => "\xE7\x95\xA6", + "\xFD\xD0" => "\xE8\x99\xA7", + "\xFD\xD1" => "\xE6\x81\xA4", + "\xFD\xD2" => "\xE8\xAD\x8E", + "\xFD\xD3" => "\xE9\xB7\xB8", + "\xFD\xD4" => "\xE5\x85\x87", + "\xFD\xD5" => "\xE5\x87\xB6", + "\xFD\xD6" => "\xE5\x8C\x88", + "\xFD\xD7" => "\xE6\xB4\xB6", + "\xFD\xD8" => "\xE8\x83\xB8", + "\xFD\xD9" => "\xE9\xBB\x91", + "\xFD\xDA" => "\xE6\x98\x95", + "\xFD\xDB" => "\xE6\xAC\xA3", + "\xFD\xDC" => "\xE7\x82\x98", + "\xFD\xDD" => "\xE7\x97\x95", + "\xFD\xDE" => "\xE5\x90\x83", + "\xFD\xDF" => "\xE5\xB1\xB9", + "\xFD\xE0" => "\xE7\xB4\x87", + "\xFD\xE1" => "\xE8\xA8\x96", + "\xFD\xE2" => "\xE6\xAC\xA0", + "\xFD\xE3" => "\xE6\xAC\xBD", + "\xFD\xE4" => "\xE6\xAD\x86", + "\xFD\xE5" => "\xE5\x90\xB8", + "\xFD\xE6" => "\xE6\x81\xB0", + "\xFD\xE7" => "\xE6\xB4\xBD", + "\xFD\xE8" => "\xE7\xBF\x95", + "\xFD\xE9" => "\xE8\x88\x88", + "\xFD\xEA" => "\xE5\x83\x96", + "\xFD\xEB" => "\xE5\x87\x9E", + "\xFD\xEC" => "\xE5\x96\x9C", + "\xFD\xED" => "\xE5\x99\xAB", + "\xFD\xEE" => "\xE5\x9B\x8D", + "\xFD\xEF" => "\xE5\xA7\xAC", + "\xFD\xF0" => "\xE5\xAC\x89", + "\xFD\xF1" => "\xE5\xB8\x8C", + "\xFD\xF2" => "\xE6\x86\x99", + "\xFD\xF3" => "\xE6\x86\x98", + "\xFD\xF4" => "\xE6\x88\xB1", + "\xFD\xF5" => "\xE6\x99\x9E", + "\xFD\xF6" => "\xE6\x9B\xA6", + "\xFD\xF7" => "\xE7\x86\x99", + "\xFD\xF8" => "\xE7\x86\xB9", + "\xFD\xF9" => "\xE7\x86\xBA", + "\xFD\xFA" => "\xE7\x8A\xA7", + "\xFD\xFB" => "\xE7\xA6\xA7", + "\xFD\xFC" => "\xE7\xA8\x80", + "\xFD\xFD" => "\xE7\xBE\xB2", + "\xFD\xFE" => "\xE8\xA9\xB0", + ); + return strtr($string, $transform); +} + +function big5($string) +{ + static $transform = array( + "\xA1\x40" => "\xE3\x80\x80", + "\xA1\x41" => "\xEF\xBC\x8C", + "\xA1\x42" => "\xE3\x80\x81", + "\xA1\x43" => "\xE3\x80\x82", + "\xA1\x44" => "\xEF\xBC\x8E", + "\xA1\x45" => "\xE2\x80\xA2", + "\xA1\x46" => "\xEF\xBC\x9B", + "\xA1\x47" => "\xEF\xBC\x9A", + "\xA1\x48" => "\xEF\xBC\x9F", + "\xA1\x49" => "\xEF\xBC\x81", + "\xA1\x4A" => "\xEF\xB8\xB0", + "\xA1\x4B" => "\xE2\x80\xA6", + "\xA1\x4C" => "\xE2\x80\xA5", + "\xA1\x4D" => "\xEF\xB9\x90", + "\xA1\x4E" => "\xEF\xBD\xA4", + "\xA1\x4F" => "\xEF\xB9\x92", + "\xA1\x50" => "\xC2\xB7", + "\xA1\x51" => "\xEF\xB9\x94", + "\xA1\x52" => "\xEF\xB9\x95", + "\xA1\x53" => "\xEF\xB9\x96", + "\xA1\x54" => "\xEF\xB9\x97", + "\xA1\x55" => "\xEF\xBD\x9C", + "\xA1\x56" => "\xE2\x80\x93", + "\xA1\x57" => "\xEF\xB8\xB1", + "\xA1\x58" => "\xE2\x80\x94", + "\xA1\x59" => "\xEF\xB8\xB3", + "\xA1\x5A" => "\xEF\xBF\xBD", + "\xA1\x5B" => "\xEF\xB8\xB4", + "\xA1\x5C" => "\xEF\xB9\x8F", + "\xA1\x5D" => "\xEF\xBC\x88", + "\xA1\x5E" => "\xEF\xBC\x89", + "\xA1\x5F" => "\xEF\xB8\xB5", + "\xA1\x60" => "\xEF\xB8\xB6", + "\xA1\x61" => "\xEF\xBD\x9B", + "\xA1\x62" => "\xEF\xBD\x9D", + "\xA1\x63" => "\xEF\xB8\xB7", + "\xA1\x64" => "\xEF\xB8\xB8", + "\xA1\x65" => "\xE3\x80\x94", + "\xA1\x66" => "\xE3\x80\x95", + "\xA1\x67" => "\xEF\xB8\xB9", + "\xA1\x68" => "\xEF\xB8\xBA", + "\xA1\x69" => "\xE3\x80\x90", + "\xA1\x6A" => "\xE3\x80\x91", + "\xA1\x6B" => "\xEF\xB8\xBB", + "\xA1\x6C" => "\xEF\xB8\xBC", + "\xA1\x6D" => "\xE3\x80\x8A", + "\xA1\x6E" => "\xE3\x80\x8B", + "\xA1\x6F" => "\xEF\xB8\xBD", + "\xA1\x70" => "\xEF\xB8\xBE", + "\xA1\x71" => "\xE3\x80\x88", + "\xA1\x72" => "\xE3\x80\x89", + "\xA1\x73" => "\xEF\xB8\xBF", + "\xA1\x74" => "\xEF\xB9\x80", + "\xA1\x75" => "\xE3\x80\x8C", + "\xA1\x76" => "\xE3\x80\x8D", + "\xA1\x77" => "\xEF\xB9\x81", + "\xA1\x78" => "\xEF\xB9\x82", + "\xA1\x79" => "\xE3\x80\x8E", + "\xA1\x7A" => "\xE3\x80\x8F", + "\xA1\x7B" => "\xEF\xB9\x83", + "\xA1\x7C" => "\xEF\xB9\x84", + "\xA1\x7D" => "\xEF\xB9\x99", + "\xA1\x7E" => "\xEF\xB9\x9A", + "\xA1\xA1" => "\xEF\xB9\x9B", + "\xA1\xA2" => "\xEF\xB9\x9C", + "\xA1\xA3" => "\xEF\xB9\x9D", + "\xA1\xA4" => "\xEF\xB9\x9E", + "\xA1\xA5" => "\xE2\x80\x98", + "\xA1\xA6" => "\xE2\x80\x99", + "\xA1\xA7" => "\xE2\x80\x9C", + "\xA1\xA8" => "\xE2\x80\x9D", + "\xA1\xA9" => "\xE3\x80\x9D", + "\xA1\xAA" => "\xE3\x80\x9E", + "\xA1\xAB" => "\xE2\x80\xB5", + "\xA1\xAC" => "\xE2\x80\xB2", + "\xA1\xAD" => "\xEF\xBC\x83", + "\xA1\xAE" => "\xEF\xBC\x86", + "\xA1\xAF" => "\xEF\xBC\x8A", + "\xA1\xB0" => "\xE2\x80\xBB", + "\xA1\xB1" => "\xC2\xA7", + "\xA1\xB2" => "\xE3\x80\x83", + "\xA1\xB3" => "\xE2\x97\x8B", + "\xA1\xB4" => "\xE2\x97\x8F", + "\xA1\xB5" => "\xE2\x96\xB3", + "\xA1\xB6" => "\xE2\x96\xB2", + "\xA1\xB7" => "\xE2\x97\x8E", + "\xA1\xB8" => "\xE2\x98\x86", + "\xA1\xB9" => "\xE2\x98\x85", + "\xA1\xBA" => "\xE2\x97\x87", + "\xA1\xBB" => "\xE2\x97\x86", + "\xA1\xBC" => "\xE2\x96\xA1", + "\xA1\xBD" => "\xE2\x96\xA0", + "\xA1\xBE" => "\xE2\x96\xBD", + "\xA1\xBF" => "\xE2\x96\xBC", + "\xA1\xC0" => "\xE3\x8A\xA3", + "\xA1\xC1" => "\xE2\x84\x85", + "\xA1\xC2" => "\xE2\x80\xBE", + "\xA1\xC3" => "\xEF\xBF\xBD", + "\xA1\xC4" => "\xEF\xBC\xBF", + "\xA1\xC5" => "\xEF\xBF\xBD", + "\xA1\xC6" => "\xEF\xB9\x89", + "\xA1\xC7" => "\xEF\xB9\x8A", + "\xA1\xC8" => "\xEF\xB9\x8D", + "\xA1\xC9" => "\xEF\xB9\x8E", + "\xA1\xCA" => "\xEF\xB9\x8B", + "\xA1\xCB" => "\xEF\xB9\x8C", + "\xA1\xCC" => "\xEF\xB9\x9F", + "\xA1\xCD" => "\xEF\xB9\xA0", + "\xA1\xCE" => "\xEF\xB9\xA1", + "\xA1\xCF" => "\xEF\xBC\x8B", + "\xA1\xD0" => "\xEF\xBC\x8D", + "\xA1\xD1" => "\xC3\x97", + "\xA1\xD2" => "\xC3\xB7", + "\xA1\xD3" => "\xC2\xB1", + "\xA1\xD4" => "\xE2\x88\x9A", + "\xA1\xD5" => "\xEF\xBC\x9C", + "\xA1\xD6" => "\xEF\xBC\x9E", + "\xA1\xD7" => "\xEF\xBC\x9D", + "\xA1\xD8" => "\xE2\x89\xA6", + "\xA1\xD9" => "\xE2\x89\xA7", + "\xA1\xDA" => "\xE2\x89\xA0", + "\xA1\xDB" => "\xE2\x88\x9E", + "\xA1\xDC" => "\xE2\x89\x92", + "\xA1\xDD" => "\xE2\x89\xA1", + "\xA1\xDE" => "\xEF\xB9\xA2", + "\xA1\xDF" => "\xEF\xB9\xA3", + "\xA1\xE0" => "\xEF\xB9\xA4", + "\xA1\xE1" => "\xEF\xB9\xA5", + "\xA1\xE2" => "\xEF\xB9\xA6", + "\xA1\xE3" => "\xE2\x88\xBC", + "\xA1\xE4" => "\xE2\x88\xA9", + "\xA1\xE5" => "\xE2\x88\xAA", + "\xA1\xE6" => "\xE2\x8A\xA5", + "\xA1\xE7" => "\xE2\x88\xA0", + "\xA1\xE8" => "\xE2\x88\x9F", + "\xA1\xE9" => "\xE2\x8A\xBF", + "\xA1\xEA" => "\xE3\x8F\x92", + "\xA1\xEB" => "\xE3\x8F\x91", + "\xA1\xEC" => "\xE2\x88\xAB", + "\xA1\xED" => "\xE2\x88\xAE", + "\xA1\xEE" => "\xE2\x88\xB5", + "\xA1\xEF" => "\xE2\x88\xB4", + "\xA1\xF0" => "\xE2\x99\x80", + "\xA1\xF1" => "\xE2\x99\x82", + "\xA1\xF2" => "\xE2\x99\x81", + "\xA1\xF3" => "\xE2\x98\x89", + "\xA1\xF4" => "\xE2\x86\x91", + "\xA1\xF5" => "\xE2\x86\x93", + "\xA1\xF6" => "\xE2\x86\x90", + "\xA1\xF7" => "\xE2\x86\x92", + "\xA1\xF8" => "\xE2\x86\x96", + "\xA1\xF9" => "\xE2\x86\x97", + "\xA1\xFA" => "\xE2\x86\x99", + "\xA1\xFB" => "\xE2\x86\x98", + "\xA1\xFC" => "\xE2\x88\xA5", + "\xA1\xFD" => "\xE2\x88\xA3", + "\xA1\xFE" => "\xEF\xBF\xBD", + "\xA2\x40" => "\xEF\xBF\xBD", + "\xA2\x41" => "\xEF\xBC\x8F", + "\xA2\x42" => "\xEF\xBC\xBC", + "\xA2\x43" => "\xEF\xBC\x84", + "\xA2\x44" => "\xC2\xA5", + "\xA2\x45" => "\xE3\x80\x92", + "\xA2\x46" => "\xC2\xA2", + "\xA2\x47" => "\xC2\xA3", + "\xA2\x48" => "\xEF\xBC\x85", + "\xA2\x49" => "\xEF\xBC\xA0", + "\xA2\x4A" => "\xE2\x84\x83", + "\xA2\x4B" => "\xE2\x84\x89", + "\xA2\x4C" => "\xEF\xB9\xA9", + "\xA2\x4D" => "\xEF\xB9\xAA", + "\xA2\x4E" => "\xEF\xB9\xAB", + "\xA2\x4F" => "\xE3\x8F\x95", + "\xA2\x50" => "\xE3\x8E\x9C", + "\xA2\x51" => "\xE3\x8E\x9D", + "\xA2\x52" => "\xE3\x8E\x9E", + "\xA2\x53" => "\xE3\x8F\x8E", + "\xA2\x54" => "\xE3\x8E\xA1", + "\xA2\x55" => "\xE3\x8E\x8E", + "\xA2\x56" => "\xE3\x8E\x8F", + "\xA2\x57" => "\xE3\x8F\x84", + "\xA2\x58" => "\xC2\xB0", + "\xA2\x59" => "\xE5\x85\x99", + "\xA2\x5A" => "\xE5\x85\x9B", + "\xA2\x5B" => "\xE5\x85\x9E", + "\xA2\x5C" => "\xE5\x85\x9D", + "\xA2\x5D" => "\xE5\x85\xA1", + "\xA2\x5E" => "\xE5\x85\xA3", + "\xA2\x5F" => "\xE5\x97\xA7", + "\xA2\x60" => "\xE7\x93\xA9", + "\xA2\x61" => "\xE7\xB3\x8E", + "\xA2\x62" => "\xE2\x96\x81", + "\xA2\x63" => "\xE2\x96\x82", + "\xA2\x64" => "\xE2\x96\x83", + "\xA2\x65" => "\xE2\x96\x84", + "\xA2\x66" => "\xE2\x96\x85", + "\xA2\x67" => "\xE2\x96\x86", + "\xA2\x68" => "\xE2\x96\x87", + "\xA2\x69" => "\xE2\x96\x88", + "\xA2\x6A" => "\xE2\x96\x8F", + "\xA2\x6B" => "\xE2\x96\x8E", + "\xA2\x6C" => "\xE2\x96\x8D", + "\xA2\x6D" => "\xE2\x96\x8C", + "\xA2\x6E" => "\xE2\x96\x8B", + "\xA2\x6F" => "\xE2\x96\x8A", + "\xA2\x70" => "\xE2\x96\x89", + "\xA2\x71" => "\xE2\x94\xBC", + "\xA2\x72" => "\xE2\x94\xB4", + "\xA2\x73" => "\xE2\x94\xAC", + "\xA2\x74" => "\xE2\x94\xA4", + "\xA2\x75" => "\xE2\x94\x9C", + "\xA2\x76" => "\xE2\x96\x94", + "\xA2\x77" => "\xE2\x94\x80", + "\xA2\x78" => "\xE2\x94\x82", + "\xA2\x79" => "\xE2\x96\x95", + "\xA2\x7A" => "\xE2\x94\x8C", + "\xA2\x7B" => "\xE2\x94\x90", + "\xA2\x7C" => "\xE2\x94\x94", + "\xA2\x7D" => "\xE2\x94\x98", + "\xA2\x7E" => "\xE2\x95\xAD", + "\xA2\xA1" => "\xE2\x95\xAE", + "\xA2\xA2" => "\xE2\x95\xB0", + "\xA2\xA3" => "\xE2\x95\xAF", + "\xA2\xA4" => "\xE2\x95\x90", + "\xA2\xA5" => "\xE2\x95\x9E", + "\xA2\xA6" => "\xE2\x95\xAA", + "\xA2\xA7" => "\xE2\x95\xA1", + "\xA2\xA8" => "\xE2\x97\xA2", + "\xA2\xA9" => "\xE2\x97\xA3", + "\xA2\xAA" => "\xE2\x97\xA5", + "\xA2\xAB" => "\xE2\x97\xA4", + "\xA2\xAC" => "\xE2\x95\xB1", + "\xA2\xAD" => "\xE2\x95\xB2", + "\xA2\xAE" => "\xE2\x95\xB3", + "\xA2\xAF" => "\xEF\xBC\x90", + "\xA2\xB0" => "\xEF\xBC\x91", + "\xA2\xB1" => "\xEF\xBC\x92", + "\xA2\xB2" => "\xEF\xBC\x93", + "\xA2\xB3" => "\xEF\xBC\x94", + "\xA2\xB4" => "\xEF\xBC\x95", + "\xA2\xB5" => "\xEF\xBC\x96", + "\xA2\xB6" => "\xEF\xBC\x97", + "\xA2\xB7" => "\xEF\xBC\x98", + "\xA2\xB8" => "\xEF\xBC\x99", + "\xA2\xB9" => "\xE2\x85\xA0", + "\xA2\xBA" => "\xE2\x85\xA1", + "\xA2\xBB" => "\xE2\x85\xA2", + "\xA2\xBC" => "\xE2\x85\xA3", + "\xA2\xBD" => "\xE2\x85\xA4", + "\xA2\xBE" => "\xE2\x85\xA5", + "\xA2\xBF" => "\xE2\x85\xA6", + "\xA2\xC0" => "\xE2\x85\xA7", + "\xA2\xC1" => "\xE2\x85\xA8", + "\xA2\xC2" => "\xE2\x85\xA9", + "\xA2\xC3" => "\xE3\x80\xA1", + "\xA2\xC4" => "\xE3\x80\xA2", + "\xA2\xC5" => "\xE3\x80\xA3", + "\xA2\xC6" => "\xE3\x80\xA4", + "\xA2\xC7" => "\xE3\x80\xA5", + "\xA2\xC8" => "\xE3\x80\xA6", + "\xA2\xC9" => "\xE3\x80\xA7", + "\xA2\xCA" => "\xE3\x80\xA8", + "\xA2\xCB" => "\xE3\x80\xA9", + "\xA2\xCC" => "\xEF\xBF\xBD", + "\xA2\xCD" => "\xE5\x8D\x84", + "\xA2\xCE" => "\xEF\xBF\xBD", + "\xA2\xCF" => "\xEF\xBC\xA1", + "\xA2\xD0" => "\xEF\xBC\xA2", + "\xA2\xD1" => "\xEF\xBC\xA3", + "\xA2\xD2" => "\xEF\xBC\xA4", + "\xA2\xD3" => "\xEF\xBC\xA5", + "\xA2\xD4" => "\xEF\xBC\xA6", + "\xA2\xD5" => "\xEF\xBC\xA7", + "\xA2\xD6" => "\xEF\xBC\xA8", + "\xA2\xD7" => "\xEF\xBC\xA9", + "\xA2\xD8" => "\xEF\xBC\xAA", + "\xA2\xD9" => "\xEF\xBC\xAB", + "\xA2\xDA" => "\xEF\xBC\xAC", + "\xA2\xDB" => "\xEF\xBC\xAD", + "\xA2\xDC" => "\xEF\xBC\xAE", + "\xA2\xDD" => "\xEF\xBC\xAF", + "\xA2\xDE" => "\xEF\xBC\xB0", + "\xA2\xDF" => "\xEF\xBC\xB1", + "\xA2\xE0" => "\xEF\xBC\xB2", + "\xA2\xE1" => "\xEF\xBC\xB3", + "\xA2\xE2" => "\xEF\xBC\xB4", + "\xA2\xE3" => "\xEF\xBC\xB5", + "\xA2\xE4" => "\xEF\xBC\xB6", + "\xA2\xE5" => "\xEF\xBC\xB7", + "\xA2\xE6" => "\xEF\xBC\xB8", + "\xA2\xE7" => "\xEF\xBC\xB9", + "\xA2\xE8" => "\xEF\xBC\xBA", + "\xA2\xE9" => "\xEF\xBD\x81", + "\xA2\xEA" => "\xEF\xBD\x82", + "\xA2\xEB" => "\xEF\xBD\x83", + "\xA2\xEC" => "\xEF\xBD\x84", + "\xA2\xED" => "\xEF\xBD\x85", + "\xA2\xEE" => "\xEF\xBD\x86", + "\xA2\xEF" => "\xEF\xBD\x87", + "\xA2\xF0" => "\xEF\xBD\x88", + "\xA2\xF1" => "\xEF\xBD\x89", + "\xA2\xF2" => "\xEF\xBD\x8A", + "\xA2\xF3" => "\xEF\xBD\x8B", + "\xA2\xF4" => "\xEF\xBD\x8C", + "\xA2\xF5" => "\xEF\xBD\x8D", + "\xA2\xF6" => "\xEF\xBD\x8E", + "\xA2\xF7" => "\xEF\xBD\x8F", + "\xA2\xF8" => "\xEF\xBD\x90", + "\xA2\xF9" => "\xEF\xBD\x91", + "\xA2\xFA" => "\xEF\xBD\x92", + "\xA2\xFB" => "\xEF\xBD\x93", + "\xA2\xFC" => "\xEF\xBD\x94", + "\xA2\xFD" => "\xEF\xBD\x95", + "\xA2\xFE" => "\xEF\xBD\x96", + "\xA3\x40" => "\xEF\xBD\x97", + "\xA3\x41" => "\xEF\xBD\x98", + "\xA3\x42" => "\xEF\xBD\x99", + "\xA3\x43" => "\xEF\xBD\x9A", + "\xA3\x44" => "\xCE\x91", + "\xA3\x45" => "\xCE\x92", + "\xA3\x46" => "\xCE\x93", + "\xA3\x47" => "\xCE\x94", + "\xA3\x48" => "\xCE\x95", + "\xA3\x49" => "\xCE\x96", + "\xA3\x4A" => "\xCE\x97", + "\xA3\x4B" => "\xCE\x98", + "\xA3\x4C" => "\xCE\x99", + "\xA3\x4D" => "\xCE\x9A", + "\xA3\x4E" => "\xCE\x9B", + "\xA3\x4F" => "\xCE\x9C", + "\xA3\x50" => "\xCE\x9D", + "\xA3\x51" => "\xCE\x9E", + "\xA3\x52" => "\xCE\x9F", + "\xA3\x53" => "\xCE\xA0", + "\xA3\x54" => "\xCE\xA1", + "\xA3\x55" => "\xCE\xA3", + "\xA3\x56" => "\xCE\xA4", + "\xA3\x57" => "\xCE\xA5", + "\xA3\x58" => "\xCE\xA6", + "\xA3\x59" => "\xCE\xA7", + "\xA3\x5A" => "\xCE\xA8", + "\xA3\x5B" => "\xCE\xA9", + "\xA3\x5C" => "\xCE\xB1", + "\xA3\x5D" => "\xCE\xB2", + "\xA3\x5E" => "\xCE\xB3", + "\xA3\x5F" => "\xCE\xB4", + "\xA3\x60" => "\xCE\xB5", + "\xA3\x61" => "\xCE\xB6", + "\xA3\x62" => "\xCE\xB7", + "\xA3\x63" => "\xCE\xB8", + "\xA3\x64" => "\xCE\xB9", + "\xA3\x65" => "\xCE\xBA", + "\xA3\x66" => "\xCE\xBB", + "\xA3\x67" => "\xCE\xBC", + "\xA3\x68" => "\xCE\xBD", + "\xA3\x69" => "\xCE\xBE", + "\xA3\x6A" => "\xCE\xBF", + "\xA3\x6B" => "\xCF\x80", + "\xA3\x6C" => "\xCF\x81", + "\xA3\x6D" => "\xCF\x83", + "\xA3\x6E" => "\xCF\x84", + "\xA3\x6F" => "\xCF\x85", + "\xA3\x70" => "\xCF\x86", + "\xA3\x71" => "\xCF\x87", + "\xA3\x72" => "\xCF\x88", + "\xA3\x73" => "\xCF\x89", + "\xA3\x74" => "\xE3\x84\x85", + "\xA3\x75" => "\xE3\x84\x86", + "\xA3\x76" => "\xE3\x84\x87", + "\xA3\x77" => "\xE3\x84\x88", + "\xA3\x78" => "\xE3\x84\x89", + "\xA3\x79" => "\xE3\x84\x8A", + "\xA3\x7A" => "\xE3\x84\x8B", + "\xA3\x7B" => "\xE3\x84\x8C", + "\xA3\x7C" => "\xE3\x84\x8D", + "\xA3\x7D" => "\xE3\x84\x8E", + "\xA3\x7E" => "\xE3\x84\x8F", + "\xA3\xA1" => "\xE3\x84\x90", + "\xA3\xA2" => "\xE3\x84\x91", + "\xA3\xA3" => "\xE3\x84\x92", + "\xA3\xA4" => "\xE3\x84\x93", + "\xA3\xA5" => "\xE3\x84\x94", + "\xA3\xA6" => "\xE3\x84\x95", + "\xA3\xA7" => "\xE3\x84\x96", + "\xA3\xA8" => "\xE3\x84\x97", + "\xA3\xA9" => "\xE3\x84\x98", + "\xA3\xAA" => "\xE3\x84\x99", + "\xA3\xAB" => "\xE3\x84\x9A", + "\xA3\xAC" => "\xE3\x84\x9B", + "\xA3\xAD" => "\xE3\x84\x9C", + "\xA3\xAE" => "\xE3\x84\x9D", + "\xA3\xAF" => "\xE3\x84\x9E", + "\xA3\xB0" => "\xE3\x84\x9F", + "\xA3\xB1" => "\xE3\x84\xA0", + "\xA3\xB2" => "\xE3\x84\xA1", + "\xA3\xB3" => "\xE3\x84\xA2", + "\xA3\xB4" => "\xE3\x84\xA3", + "\xA3\xB5" => "\xE3\x84\xA4", + "\xA3\xB6" => "\xE3\x84\xA5", + "\xA3\xB7" => "\xE3\x84\xA6", + "\xA3\xB8" => "\xE3\x84\xA7", + "\xA3\xB9" => "\xE3\x84\xA8", + "\xA3\xBA" => "\xE3\x84\xA9", + "\xA3\xBB" => "\xCB\x99", + "\xA3\xBC" => "\xCB\x89", + "\xA3\xBD" => "\xCB\x8A", + "\xA3\xBE" => "\xCB\x87", + "\xA3\xBF" => "\xCB\x8B", + "\xA4\x40" => "\xE4\xB8\x80", + "\xA4\x41" => "\xE4\xB9\x99", + "\xA4\x42" => "\xE4\xB8\x81", + "\xA4\x43" => "\xE4\xB8\x83", + "\xA4\x44" => "\xE4\xB9\x83", + "\xA4\x45" => "\xE4\xB9\x9D", + "\xA4\x46" => "\xE4\xBA\x86", + "\xA4\x47" => "\xE4\xBA\x8C", + "\xA4\x48" => "\xE4\xBA\xBA", + "\xA4\x49" => "\xE5\x84\xBF", + "\xA4\x4A" => "\xE5\x85\xA5", + "\xA4\x4B" => "\xE5\x85\xAB", + "\xA4\x4C" => "\xE5\x87\xA0", + "\xA4\x4D" => "\xE5\x88\x80", + "\xA4\x4E" => "\xE5\x88\x81", + "\xA4\x4F" => "\xE5\x8A\x9B", + "\xA4\x50" => "\xE5\x8C\x95", + "\xA4\x51" => "\xE5\x8D\x81", + "\xA4\x52" => "\xE5\x8D\x9C", + "\xA4\x53" => "\xE5\x8F\x88", + "\xA4\x54" => "\xE4\xB8\x89", + "\xA4\x55" => "\xE4\xB8\x8B", + "\xA4\x56" => "\xE4\xB8\x88", + "\xA4\x57" => "\xE4\xB8\x8A", + "\xA4\x58" => "\xE4\xB8\xAB", + "\xA4\x59" => "\xE4\xB8\xB8", + "\xA4\x5A" => "\xE5\x87\xA1", + "\xA4\x5B" => "\xE4\xB9\x85", + "\xA4\x5C" => "\xE4\xB9\x88", + "\xA4\x5D" => "\xE4\xB9\x9F", + "\xA4\x5E" => "\xE4\xB9\x9E", + "\xA4\x5F" => "\xE4\xBA\x8E", + "\xA4\x60" => "\xE4\xBA\xA1", + "\xA4\x61" => "\xE5\x85\x80", + "\xA4\x62" => "\xE5\x88\x83", + "\xA4\x63" => "\xE5\x8B\xBA", + "\xA4\x64" => "\xE5\x8D\x83", + "\xA4\x65" => "\xE5\x8F\x89", + "\xA4\x66" => "\xE5\x8F\xA3", + "\xA4\x67" => "\xE5\x9C\x9F", + "\xA4\x68" => "\xE5\xA3\xAB", + "\xA4\x69" => "\xE5\xA4\x95", + "\xA4\x6A" => "\xE5\xA4\xA7", + "\xA4\x6B" => "\xE5\xA5\xB3", + "\xA4\x6C" => "\xE5\xAD\x90", + "\xA4\x6D" => "\xE5\xAD\x91", + "\xA4\x6E" => "\xE5\xAD\x93", + "\xA4\x6F" => "\xE5\xAF\xB8", + "\xA4\x70" => "\xE5\xB0\x8F", + "\xA4\x71" => "\xE5\xB0\xA2", + "\xA4\x72" => "\xE5\xB0\xB8", + "\xA4\x73" => "\xE5\xB1\xB1", + "\xA4\x74" => "\xE5\xB7\x9D", + "\xA4\x75" => "\xE5\xB7\xA5", + "\xA4\x76" => "\xE5\xB7\xB1", + "\xA4\x77" => "\xE5\xB7\xB2", + "\xA4\x78" => "\xE5\xB7\xB3", + "\xA4\x79" => "\xE5\xB7\xBE", + "\xA4\x7A" => "\xE5\xB9\xB2", + "\xA4\x7B" => "\xE5\xBB\xBE", + "\xA4\x7C" => "\xE5\xBC\x8B", + "\xA4\x7D" => "\xE5\xBC\x93", + "\xA4\x7E" => "\xE6\x89\x8D", + "\xA4\xA1" => "\xE4\xB8\x91", + "\xA4\xA2" => "\xE4\xB8\x90", + "\xA4\xA3" => "\xE4\xB8\x8D", + "\xA4\xA4" => "\xE4\xB8\xAD", + "\xA4\xA5" => "\xE4\xB8\xB0", + "\xA4\xA6" => "\xE4\xB8\xB9", + "\xA4\xA7" => "\xE4\xB9\x8B", + "\xA4\xA8" => "\xE5\xB0\xB9", + "\xA4\xA9" => "\xE4\xBA\x88", + "\xA4\xAA" => "\xE4\xBA\x91", + "\xA4\xAB" => "\xE4\xBA\x95", + "\xA4\xAC" => "\xE4\xBA\x92", + "\xA4\xAD" => "\xE4\xBA\x94", + "\xA4\xAE" => "\xE4\xBA\xA2", + "\xA4\xAF" => "\xE4\xBB\x81", + "\xA4\xB0" => "\xE4\xBB\x80", + "\xA4\xB1" => "\xE4\xBB\x83", + "\xA4\xB2" => "\xE4\xBB\x86", + "\xA4\xB3" => "\xE4\xBB\x87", + "\xA4\xB4" => "\xE4\xBB\x8D", + "\xA4\xB5" => "\xE4\xBB\x8A", + "\xA4\xB6" => "\xE4\xBB\x8B", + "\xA4\xB7" => "\xE4\xBB\x84", + "\xA4\xB8" => "\xE5\x85\x83", + "\xA4\xB9" => "\xE5\x85\x81", + "\xA4\xBA" => "\xE5\x85\xA7", + "\xA4\xBB" => "\xE5\x85\xAD", + "\xA4\xBC" => "\xE5\x85\xAE", + "\xA4\xBD" => "\xE5\x85\xAC", + "\xA4\xBE" => "\xE5\x86\x97", + "\xA4\xBF" => "\xE5\x87\xB6", + "\xA4\xC0" => "\xE5\x88\x86", + "\xA4\xC1" => "\xE5\x88\x87", + "\xA4\xC2" => "\xE5\x88\x88", + "\xA4\xC3" => "\xE5\x8B\xBB", + "\xA4\xC4" => "\xE5\x8B\xBE", + "\xA4\xC5" => "\xE5\x8B\xBF", + "\xA4\xC6" => "\xE5\x8C\x96", + "\xA4\xC7" => "\xE5\x8C\xB9", + "\xA4\xC8" => "\xE5\x8D\x88", + "\xA4\xC9" => "\xE5\x8D\x87", + "\xA4\xCA" => "\xE5\x8D\x85", + "\xA4\xCB" => "\xE5\x8D\x9E", + "\xA4\xCC" => "\xE5\x8E\x84", + "\xA4\xCD" => "\xE5\x8F\x8B", + "\xA4\xCE" => "\xE5\x8F\x8A", + "\xA4\xCF" => "\xE5\x8F\x8D", + "\xA4\xD0" => "\xE5\xA3\xAC", + "\xA4\xD1" => "\xE5\xA4\xA9", + "\xA4\xD2" => "\xE5\xA4\xAB", + "\xA4\xD3" => "\xE5\xA4\xAA", + "\xA4\xD4" => "\xE5\xA4\xAD", + "\xA4\xD5" => "\xE5\xAD\x94", + "\xA4\xD6" => "\xE5\xB0\x91", + "\xA4\xD7" => "\xE5\xB0\xA4", + "\xA4\xD8" => "\xE5\xB0\xBA", + "\xA4\xD9" => "\xE5\xB1\xAF", + "\xA4\xDA" => "\xE5\xB7\xB4", + "\xA4\xDB" => "\xE5\xB9\xBB", + "\xA4\xDC" => "\xE5\xBB\xBF", + "\xA4\xDD" => "\xE5\xBC\x94", + "\xA4\xDE" => "\xE5\xBC\x95", + "\xA4\xDF" => "\xE5\xBF\x83", + "\xA4\xE0" => "\xE6\x88\x88", + "\xA4\xE1" => "\xE6\x88\xB6", + "\xA4\xE2" => "\xE6\x89\x8B", + "\xA4\xE3" => "\xE6\x89\x8E", + "\xA4\xE4" => "\xE6\x94\xAF", + "\xA4\xE5" => "\xE6\x96\x87", + "\xA4\xE6" => "\xE6\x96\x97", + "\xA4\xE7" => "\xE6\x96\xA4", + "\xA4\xE8" => "\xE6\x96\xB9", + "\xA4\xE9" => "\xE6\x97\xA5", + "\xA4\xEA" => "\xE6\x9B\xB0", + "\xA4\xEB" => "\xE6\x9C\x88", + "\xA4\xEC" => "\xE6\x9C\xA8", + "\xA4\xED" => "\xE6\xAC\xA0", + "\xA4\xEE" => "\xE6\xAD\xA2", + "\xA4\xEF" => "\xE6\xAD\xB9", + "\xA4\xF0" => "\xE6\xAF\x8B", + "\xA4\xF1" => "\xE6\xAF\x94", + "\xA4\xF2" => "\xE6\xAF\x9B", + "\xA4\xF3" => "\xE6\xB0\x8F", + "\xA4\xF4" => "\xE6\xB0\xB4", + "\xA4\xF5" => "\xE7\x81\xAB", + "\xA4\xF6" => "\xE7\x88\xAA", + "\xA4\xF7" => "\xE7\x88\xB6", + "\xA4\xF8" => "\xE7\x88\xBB", + "\xA4\xF9" => "\xE7\x89\x87", + "\xA4\xFA" => "\xE7\x89\x99", + "\xA4\xFB" => "\xE7\x89\x9B", + "\xA4\xFC" => "\xE7\x8A\xAC", + "\xA4\xFD" => "\xE7\x8E\x8B", + "\xA4\xFE" => "\xE4\xB8\x99", + "\xA5\x40" => "\xE4\xB8\x96", + "\xA5\x41" => "\xE4\xB8\x95", + "\xA5\x42" => "\xE4\xB8\x94", + "\xA5\x43" => "\xE4\xB8\x98", + "\xA5\x44" => "\xE4\xB8\xBB", + "\xA5\x45" => "\xE4\xB9\x8D", + "\xA5\x46" => "\xE4\xB9\x8F", + "\xA5\x47" => "\xE4\xB9\x8E", + "\xA5\x48" => "\xE4\xBB\xA5", + "\xA5\x49" => "\xE4\xBB\x98", + "\xA5\x4A" => "\xE4\xBB\x94", + "\xA5\x4B" => "\xE4\xBB\x95", + "\xA5\x4C" => "\xE4\xBB\x96", + "\xA5\x4D" => "\xE4\xBB\x97", + "\xA5\x4E" => "\xE4\xBB\xA3", + "\xA5\x4F" => "\xE4\xBB\xA4", + "\xA5\x50" => "\xE4\xBB\x99", + "\xA5\x51" => "\xE4\xBB\x9E", + "\xA5\x52" => "\xE5\x85\x85", + "\xA5\x53" => "\xE5\x85\x84", + "\xA5\x54" => "\xE5\x86\x89", + "\xA5\x55" => "\xE5\x86\x8A", + "\xA5\x56" => "\xE5\x86\xAC", + "\xA5\x57" => "\xE5\x87\xB9", + "\xA5\x58" => "\xE5\x87\xBA", + "\xA5\x59" => "\xE5\x87\xB8", + "\xA5\x5A" => "\xE5\x88\x8A", + "\xA5\x5B" => "\xE5\x8A\xA0", + "\xA5\x5C" => "\xE5\x8A\x9F", + "\xA5\x5D" => "\xE5\x8C\x85", + "\xA5\x5E" => "\xE5\x8C\x86", + "\xA5\x5F" => "\xE5\x8C\x97", + "\xA5\x60" => "\xE5\x8C\x9D", + "\xA5\x61" => "\xE4\xBB\x9F", + "\xA5\x62" => "\xE5\x8D\x8A", + "\xA5\x63" => "\xE5\x8D\x89", + "\xA5\x64" => "\xE5\x8D\xA1", + "\xA5\x65" => "\xE5\x8D\xA0", + "\xA5\x66" => "\xE5\x8D\xAF", + "\xA5\x67" => "\xE5\x8D\xAE", + "\xA5\x68" => "\xE5\x8E\xBB", + "\xA5\x69" => "\xE5\x8F\xAF", + "\xA5\x6A" => "\xE5\x8F\xA4", + "\xA5\x6B" => "\xE5\x8F\xB3", + "\xA5\x6C" => "\xE5\x8F\xAC", + "\xA5\x6D" => "\xE5\x8F\xAE", + "\xA5\x6E" => "\xE5\x8F\xA9", + "\xA5\x6F" => "\xE5\x8F\xA8", + "\xA5\x70" => "\xE5\x8F\xBC", + "\xA5\x71" => "\xE5\x8F\xB8", + "\xA5\x72" => "\xE5\x8F\xB5", + "\xA5\x73" => "\xE5\x8F\xAB", + "\xA5\x74" => "\xE5\x8F\xA6", + "\xA5\x75" => "\xE5\x8F\xAA", + "\xA5\x76" => "\xE5\x8F\xB2", + "\xA5\x77" => "\xE5\x8F\xB1", + "\xA5\x78" => "\xE5\x8F\xB0", + "\xA5\x79" => "\xE5\x8F\xA5", + "\xA5\x7A" => "\xE5\x8F\xAD", + "\xA5\x7B" => "\xE5\x8F\xBB", + "\xA5\x7C" => "\xE5\x9B\x9B", + "\xA5\x7D" => "\xE5\x9B\x9A", + "\xA5\x7E" => "\xE5\xA4\x96", + "\xA5\xA1" => "\xE5\xA4\xAE", + "\xA5\xA2" => "\xE5\xA4\xB1", + "\xA5\xA3" => "\xE5\xA5\xB4", + "\xA5\xA4" => "\xE5\xA5\xB6", + "\xA5\xA5" => "\xE5\xAD\x95", + "\xA5\xA6" => "\xE5\xAE\x83", + "\xA5\xA7" => "\xE5\xB0\xBC", + "\xA5\xA8" => "\xE5\xB7\xA8", + "\xA5\xA9" => "\xE5\xB7\xA7", + "\xA5\xAA" => "\xE5\xB7\xA6", + "\xA5\xAB" => "\xE5\xB8\x82", + "\xA5\xAC" => "\xE5\xB8\x83", + "\xA5\xAD" => "\xE5\xB9\xB3", + "\xA5\xAE" => "\xE5\xB9\xBC", + "\xA5\xAF" => "\xE5\xBC\x81", + "\xA5\xB0" => "\xE5\xBC\x98", + "\xA5\xB1" => "\xE5\xBC\x97", + "\xA5\xB2" => "\xE5\xBF\x85", + "\xA5\xB3" => "\xE6\x88\x8A", + "\xA5\xB4" => "\xE6\x89\x93", + "\xA5\xB5" => "\xE6\x89\x94", + "\xA5\xB6" => "\xE6\x89\x92", + "\xA5\xB7" => "\xE6\x89\x91", + "\xA5\xB8" => "\xE6\x96\xA5", + "\xA5\xB9" => "\xE6\x97\xA6", + "\xA5\xBA" => "\xE6\x9C\xAE", + "\xA5\xBB" => "\xE6\x9C\xAC", + "\xA5\xBC" => "\xE6\x9C\xAA", + "\xA5\xBD" => "\xE6\x9C\xAB", + "\xA5\xBE" => "\xE6\x9C\xAD", + "\xA5\xBF" => "\xE6\xAD\xA3", + "\xA5\xC0" => "\xE6\xAF\x8D", + "\xA5\xC1" => "\xE6\xB0\x91", + "\xA5\xC2" => "\xE6\xB0\x90", + "\xA5\xC3" => "\xE6\xB0\xB8", + "\xA5\xC4" => "\xE6\xB1\x81", + "\xA5\xC5" => "\xE6\xB1\x80", + "\xA5\xC6" => "\xE6\xB0\xBE", + "\xA5\xC7" => "\xE7\x8A\xAF", + "\xA5\xC8" => "\xE7\x8E\x84", + "\xA5\xC9" => "\xE7\x8E\x89", + "\xA5\xCA" => "\xE7\x93\x9C", + "\xA5\xCB" => "\xE7\x93\xA6", + "\xA5\xCC" => "\xE7\x94\x98", + "\xA5\xCD" => "\xE7\x94\x9F", + "\xA5\xCE" => "\xE7\x94\xA8", + "\xA5\xCF" => "\xE7\x94\xA9", + "\xA5\xD0" => "\xE7\x94\xB0", + "\xA5\xD1" => "\xE7\x94\xB1", + "\xA5\xD2" => "\xE7\x94\xB2", + "\xA5\xD3" => "\xE7\x94\xB3", + "\xA5\xD4" => "\xE7\x96\x8B", + "\xA5\xD5" => "\xE7\x99\xBD", + "\xA5\xD6" => "\xE7\x9A\xAE", + "\xA5\xD7" => "\xE7\x9A\xBF", + "\xA5\xD8" => "\xE7\x9B\xAE", + "\xA5\xD9" => "\xE7\x9F\x9B", + "\xA5\xDA" => "\xE7\x9F\xA2", + "\xA5\xDB" => "\xE7\x9F\xB3", + "\xA5\xDC" => "\xE7\xA4\xBA", + "\xA5\xDD" => "\xE7\xA6\xBE", + "\xA5\xDE" => "\xE7\xA9\xB4", + "\xA5\xDF" => "\xE7\xAB\x8B", + "\xA5\xE0" => "\xE4\xB8\x9E", + "\xA5\xE1" => "\xE4\xB8\x9F", + "\xA5\xE2" => "\xE4\xB9\x92", + "\xA5\xE3" => "\xE4\xB9\x93", + "\xA5\xE4" => "\xE4\xB9\xA9", + "\xA5\xE5" => "\xE4\xBA\x99", + "\xA5\xE6" => "\xE4\xBA\xA4", + "\xA5\xE7" => "\xE4\xBA\xA6", + "\xA5\xE8" => "\xE4\xBA\xA5", + "\xA5\xE9" => "\xE4\xBB\xBF", + "\xA5\xEA" => "\xE4\xBC\x89", + "\xA5\xEB" => "\xE4\xBC\x99", + "\xA5\xEC" => "\xE4\xBC\x8A", + "\xA5\xED" => "\xE4\xBC\x95", + "\xA5\xEE" => "\xE4\xBC\x8D", + "\xA5\xEF" => "\xE4\xBC\x90", + "\xA5\xF0" => "\xE4\xBC\x91", + "\xA5\xF1" => "\xE4\xBC\x8F", + "\xA5\xF2" => "\xE4\xBB\xB2", + "\xA5\xF3" => "\xE4\xBB\xB6", + "\xA5\xF4" => "\xE4\xBB\xBB", + "\xA5\xF5" => "\xE4\xBB\xB0", + "\xA5\xF6" => "\xE4\xBB\xB3", + "\xA5\xF7" => "\xE4\xBB\xBD", + "\xA5\xF8" => "\xE4\xBC\x81", + "\xA5\xF9" => "\xE4\xBC\x8B", + "\xA5\xFA" => "\xE5\x85\x89", + "\xA5\xFB" => "\xE5\x85\x87", + "\xA5\xFC" => "\xE5\x85\x86", + "\xA5\xFD" => "\xE5\x85\x88", + "\xA5\xFE" => "\xE5\x85\xA8", + "\xA6\x40" => "\xE5\x85\xB1", + "\xA6\x41" => "\xE5\x86\x8D", + "\xA6\x42" => "\xE5\x86\xB0", + "\xA6\x43" => "\xE5\x88\x97", + "\xA6\x44" => "\xE5\x88\x91", + "\xA6\x45" => "\xE5\x88\x92", + "\xA6\x46" => "\xE5\x88\x8E", + "\xA6\x47" => "\xE5\x88\x96", + "\xA6\x48" => "\xE5\x8A\xA3", + "\xA6\x49" => "\xE5\x8C\x88", + "\xA6\x4A" => "\xE5\x8C\xA1", + "\xA6\x4B" => "\xE5\x8C\xA0", + "\xA6\x4C" => "\xE5\x8D\xB0", + "\xA6\x4D" => "\xE5\x8D\xB1", + "\xA6\x4E" => "\xE5\x90\x89", + "\xA6\x4F" => "\xE5\x90\x8F", + "\xA6\x50" => "\xE5\x90\x8C", + "\xA6\x51" => "\xE5\x90\x8A", + "\xA6\x52" => "\xE5\x90\x90", + "\xA6\x53" => "\xE5\x90\x81", + "\xA6\x54" => "\xE5\x90\x8B", + "\xA6\x55" => "\xE5\x90\x84", + "\xA6\x56" => "\xE5\x90\x91", + "\xA6\x57" => "\xE5\x90\x8D", + "\xA6\x58" => "\xE5\x90\x88", + "\xA6\x59" => "\xE5\x90\x83", + "\xA6\x5A" => "\xE5\x90\x8E", + "\xA6\x5B" => "\xE5\x90\x86", + "\xA6\x5C" => "\xE5\x90\x92", + "\xA6\x5D" => "\xE5\x9B\xA0", + "\xA6\x5E" => "\xE5\x9B\x9E", + "\xA6\x5F" => "\xE5\x9B\x9D", + "\xA6\x60" => "\xE5\x9C\xB3", + "\xA6\x61" => "\xE5\x9C\xB0", + "\xA6\x62" => "\xE5\x9C\xA8", + "\xA6\x63" => "\xE5\x9C\xAD", + "\xA6\x64" => "\xE5\x9C\xAC", + "\xA6\x65" => "\xE5\x9C\xAF", + "\xA6\x66" => "\xE5\x9C\xA9", + "\xA6\x67" => "\xE5\xA4\x99", + "\xA6\x68" => "\xE5\xA4\x9A", + "\xA6\x69" => "\xE5\xA4\xB7", + "\xA6\x6A" => "\xE5\xA4\xB8", + "\xA6\x6B" => "\xE5\xA6\x84", + "\xA6\x6C" => "\xE5\xA5\xB8", + "\xA6\x6D" => "\xE5\xA6\x83", + "\xA6\x6E" => "\xE5\xA5\xBD", + "\xA6\x6F" => "\xE5\xA5\xB9", + "\xA6\x70" => "\xE5\xA6\x82", + "\xA6\x71" => "\xE5\xA6\x81", + "\xA6\x72" => "\xE5\xAD\x97", + "\xA6\x73" => "\xE5\xAD\x98", + "\xA6\x74" => "\xE5\xAE\x87", + "\xA6\x75" => "\xE5\xAE\x88", + "\xA6\x76" => "\xE5\xAE\x85", + "\xA6\x77" => "\xE5\xAE\x89", + "\xA6\x78" => "\xE5\xAF\xBA", + "\xA6\x79" => "\xE5\xB0\x96", + "\xA6\x7A" => "\xE5\xB1\xB9", + "\xA6\x7B" => "\xE5\xB7\x9E", + "\xA6\x7C" => "\xE5\xB8\x86", + "\xA6\x7D" => "\xE5\xB9\xB6", + "\xA6\x7E" => "\xE5\xB9\xB4", + "\xA6\xA1" => "\xE5\xBC\x8F", + "\xA6\xA2" => "\xE5\xBC\x9B", + "\xA6\xA3" => "\xE5\xBF\x99", + "\xA6\xA4" => "\xE5\xBF\x96", + "\xA6\xA5" => "\xE6\x88\x8E", + "\xA6\xA6" => "\xE6\x88\x8C", + "\xA6\xA7" => "\xE6\x88\x8D", + "\xA6\xA8" => "\xE6\x88\x90", + "\xA6\xA9" => "\xE6\x89\xA3", + "\xA6\xAA" => "\xE6\x89\x9B", + "\xA6\xAB" => "\xE6\x89\x98", + "\xA6\xAC" => "\xE6\x94\xB6", + "\xA6\xAD" => "\xE6\x97\xA9", + "\xA6\xAE" => "\xE6\x97\xA8", + "\xA6\xAF" => "\xE6\x97\xAC", + "\xA6\xB0" => "\xE6\x97\xAD", + "\xA6\xB1" => "\xE6\x9B\xB2", + "\xA6\xB2" => "\xE6\x9B\xB3", + "\xA6\xB3" => "\xE6\x9C\x89", + "\xA6\xB4" => "\xE6\x9C\xBD", + "\xA6\xB5" => "\xE6\x9C\xB4", + "\xA6\xB6" => "\xE6\x9C\xB1", + "\xA6\xB7" => "\xE6\x9C\xB5", + "\xA6\xB8" => "\xE6\xAC\xA1", + "\xA6\xB9" => "\xE6\xAD\xA4", + "\xA6\xBA" => "\xE6\xAD\xBB", + "\xA6\xBB" => "\xE6\xB0\x96", + "\xA6\xBC" => "\xE6\xB1\x9D", + "\xA6\xBD" => "\xE6\xB1\x97", + "\xA6\xBE" => "\xE6\xB1\x99", + "\xA6\xBF" => "\xE6\xB1\x9F", + "\xA6\xC0" => "\xE6\xB1\xA0", + "\xA6\xC1" => "\xE6\xB1\x90", + "\xA6\xC2" => "\xE6\xB1\x95", + "\xA6\xC3" => "\xE6\xB1\xA1", + "\xA6\xC4" => "\xE6\xB1\x9B", + "\xA6\xC5" => "\xE6\xB1\x8D", + "\xA6\xC6" => "\xE6\xB1\x8E", + "\xA6\xC7" => "\xE7\x81\xB0", + "\xA6\xC8" => "\xE7\x89\x9F", + "\xA6\xC9" => "\xE7\x89\x9D", + "\xA6\xCA" => "\xE7\x99\xBE", + "\xA6\xCB" => "\xE7\xAB\xB9", + "\xA6\xCC" => "\xE7\xB1\xB3", + "\xA6\xCD" => "\xE7\xB3\xB8", + "\xA6\xCE" => "\xE7\xBC\xB6", + "\xA6\xCF" => "\xE7\xBE\x8A", + "\xA6\xD0" => "\xE7\xBE\xBD", + "\xA6\xD1" => "\xE8\x80\x81", + "\xA6\xD2" => "\xE8\x80\x83", + "\xA6\xD3" => "\xE8\x80\x8C", + "\xA6\xD4" => "\xE8\x80\x92", + "\xA6\xD5" => "\xE8\x80\xB3", + "\xA6\xD6" => "\xE8\x81\xBF", + "\xA6\xD7" => "\xE8\x82\x89", + "\xA6\xD8" => "\xE8\x82\x8B", + "\xA6\xD9" => "\xE8\x82\x8C", + "\xA6\xDA" => "\xE8\x87\xA3", + "\xA6\xDB" => "\xE8\x87\xAA", + "\xA6\xDC" => "\xE8\x87\xB3", + "\xA6\xDD" => "\xE8\x87\xBC", + "\xA6\xDE" => "\xE8\x88\x8C", + "\xA6\xDF" => "\xE8\x88\x9B", + "\xA6\xE0" => "\xE8\x88\x9F", + "\xA6\xE1" => "\xE8\x89\xAE", + "\xA6\xE2" => "\xE8\x89\xB2", + "\xA6\xE3" => "\xE8\x89\xBE", + "\xA6\xE4" => "\xE8\x99\xAB", + "\xA6\xE5" => "\xE8\xA1\x80", + "\xA6\xE6" => "\xE8\xA1\x8C", + "\xA6\xE7" => "\xE8\xA1\xA3", + "\xA6\xE8" => "\xE8\xA5\xBF", + "\xA6\xE9" => "\xE9\x98\xA1", + "\xA6\xEA" => "\xE4\xB8\xB2", + "\xA6\xEB" => "\xE4\xBA\xA8", + "\xA6\xEC" => "\xE4\xBD\x8D", + "\xA6\xED" => "\xE4\xBD\x8F", + "\xA6\xEE" => "\xE4\xBD\x87", + "\xA6\xEF" => "\xE4\xBD\x97", + "\xA6\xF0" => "\xE4\xBD\x9E", + "\xA6\xF1" => "\xE4\xBC\xB4", + "\xA6\xF2" => "\xE4\xBD\x9B", + "\xA6\xF3" => "\xE4\xBD\x95", + "\xA6\xF4" => "\xE4\xBC\xB0", + "\xA6\xF5" => "\xE4\xBD\x90", + "\xA6\xF6" => "\xE4\xBD\x91", + "\xA6\xF7" => "\xE4\xBC\xBD", + "\xA6\xF8" => "\xE4\xBC\xBA", + "\xA6\xF9" => "\xE4\xBC\xB8", + "\xA6\xFA" => "\xE4\xBD\x83", + "\xA6\xFB" => "\xE4\xBD\x94", + "\xA6\xFC" => "\xE4\xBC\xBC", + "\xA6\xFD" => "\xE4\xBD\x86", + "\xA6\xFE" => "\xE4\xBD\xA3", + "\xA7\x40" => "\xE4\xBD\x9C", + "\xA7\x41" => "\xE4\xBD\xA0", + "\xA7\x42" => "\xE4\xBC\xAF", + "\xA7\x43" => "\xE4\xBD\x8E", + "\xA7\x44" => "\xE4\xBC\xB6", + "\xA7\x45" => "\xE4\xBD\x99", + "\xA7\x46" => "\xE4\xBD\x9D", + "\xA7\x47" => "\xE4\xBD\x88", + "\xA7\x48" => "\xE4\xBD\x9A", + "\xA7\x49" => "\xE5\x85\x8C", + "\xA7\x4A" => "\xE5\x85\x8B", + "\xA7\x4B" => "\xE5\x85\x8D", + "\xA7\x4C" => "\xE5\x85\xB5", + "\xA7\x4D" => "\xE5\x86\xB6", + "\xA7\x4E" => "\xE5\x86\xB7", + "\xA7\x4F" => "\xE5\x88\xA5", + "\xA7\x50" => "\xE5\x88\xA4", + "\xA7\x51" => "\xE5\x88\xA9", + "\xA7\x52" => "\xE5\x88\xAA", + "\xA7\x53" => "\xE5\x88\xA8", + "\xA7\x54" => "\xE5\x8A\xAB", + "\xA7\x55" => "\xE5\x8A\xA9", + "\xA7\x56" => "\xE5\x8A\xAA", + "\xA7\x57" => "\xE5\x8A\xAC", + "\xA7\x58" => "\xE5\x8C\xA3", + "\xA7\x59" => "\xE5\x8D\xB3", + "\xA7\x5A" => "\xE5\x8D\xB5", + "\xA7\x5B" => "\xE5\x90\x9D", + "\xA7\x5C" => "\xE5\x90\xAD", + "\xA7\x5D" => "\xE5\x90\x9E", + "\xA7\x5E" => "\xE5\x90\xBE", + "\xA7\x5F" => "\xE5\x90\xA6", + "\xA7\x60" => "\xE5\x91\x8E", + "\xA7\x61" => "\xE5\x90\xA7", + "\xA7\x62" => "\xE5\x91\x86", + "\xA7\x63" => "\xE5\x91\x83", + "\xA7\x64" => "\xE5\x90\xB3", + "\xA7\x65" => "\xE5\x91\x88", + "\xA7\x66" => "\xE5\x91\x82", + "\xA7\x67" => "\xE5\x90\x9B", + "\xA7\x68" => "\xE5\x90\xA9", + "\xA7\x69" => "\xE5\x91\x8A", + "\xA7\x6A" => "\xE5\x90\xB9", + "\xA7\x6B" => "\xE5\x90\xBB", + "\xA7\x6C" => "\xE5\x90\xB8", + "\xA7\x6D" => "\xE5\x90\xAE", + "\xA7\x6E" => "\xE5\x90\xB5", + "\xA7\x6F" => "\xE5\x90\xB6", + "\xA7\x70" => "\xE5\x90\xA0", + "\xA7\x71" => "\xE5\x90\xBC", + "\xA7\x72" => "\xE5\x91\x80", + "\xA7\x73" => "\xE5\x90\xB1", + "\xA7\x74" => "\xE5\x90\xAB", + "\xA7\x75" => "\xE5\x90\x9F", + "\xA7\x76" => "\xE5\x90\xAC", + "\xA7\x77" => "\xE5\x9B\xAA", + "\xA7\x78" => "\xE5\x9B\xB0", + "\xA7\x79" => "\xE5\x9B\xA4", + "\xA7\x7A" => "\xE5\x9B\xAB", + "\xA7\x7B" => "\xE5\x9D\x8A", + "\xA7\x7C" => "\xE5\x9D\x91", + "\xA7\x7D" => "\xE5\x9D\x80", + "\xA7\x7E" => "\xE5\x9D\x8D", + "\xA7\xA1" => "\xE5\x9D\x87", + "\xA7\xA2" => "\xE5\x9D\x8E", + "\xA7\xA3" => "\xE5\x9C\xBE", + "\xA7\xA4" => "\xE5\x9D\x90", + "\xA7\xA5" => "\xE5\x9D\x8F", + "\xA7\xA6" => "\xE5\x9C\xBB", + "\xA7\xA7" => "\xE5\xA3\xAF", + "\xA7\xA8" => "\xE5\xA4\xBE", + "\xA7\xA9" => "\xE5\xA6\x9D", + "\xA7\xAA" => "\xE5\xA6\x92", + "\xA7\xAB" => "\xE5\xA6\xA8", + "\xA7\xAC" => "\xE5\xA6\x9E", + "\xA7\xAD" => "\xE5\xA6\xA3", + "\xA7\xAE" => "\xE5\xA6\x99", + "\xA7\xAF" => "\xE5\xA6\x96", + "\xA7\xB0" => "\xE5\xA6\x8D", + "\xA7\xB1" => "\xE5\xA6\xA4", + "\xA7\xB2" => "\xE5\xA6\x93", + "\xA7\xB3" => "\xE5\xA6\x8A", + "\xA7\xB4" => "\xE5\xA6\xA5", + "\xA7\xB5" => "\xE5\xAD\x9D", + "\xA7\xB6" => "\xE5\xAD\x9C", + "\xA7\xB7" => "\xE5\xAD\x9A", + "\xA7\xB8" => "\xE5\xAD\x9B", + "\xA7\xB9" => "\xE5\xAE\x8C", + "\xA7\xBA" => "\xE5\xAE\x8B", + "\xA7\xBB" => "\xE5\xAE\x8F", + "\xA7\xBC" => "\xE5\xB0\xAC", + "\xA7\xBD" => "\xE5\xB1\x80", + "\xA7\xBE" => "\xE5\xB1\x81", + "\xA7\xBF" => "\xE5\xB0\xBF", + "\xA7\xC0" => "\xE5\xB0\xBE", + "\xA7\xC1" => "\xE5\xB2\x90", + "\xA7\xC2" => "\xE5\xB2\x91", + "\xA7\xC3" => "\xE5\xB2\x94", + "\xA7\xC4" => "\xE5\xB2\x8C", + "\xA7\xC5" => "\xE5\xB7\xAB", + "\xA7\xC6" => "\xE5\xB8\x8C", + "\xA7\xC7" => "\xE5\xBA\x8F", + "\xA7\xC8" => "\xE5\xBA\x87", + "\xA7\xC9" => "\xE5\xBA\x8A", + "\xA7\xCA" => "\xE5\xBB\xB7", + "\xA7\xCB" => "\xE5\xBC\x84", + "\xA7\xCC" => "\xE5\xBC\x9F", + "\xA7\xCD" => "\xE5\xBD\xA4", + "\xA7\xCE" => "\xE5\xBD\xA2", + "\xA7\xCF" => "\xE5\xBD\xB7", + "\xA7\xD0" => "\xE5\xBD\xB9", + "\xA7\xD1" => "\xE5\xBF\x98", + "\xA7\xD2" => "\xE5\xBF\x8C", + "\xA7\xD3" => "\xE5\xBF\x97", + "\xA7\xD4" => "\xE5\xBF\x8D", + "\xA7\xD5" => "\xE5\xBF\xB1", + "\xA7\xD6" => "\xE5\xBF\xAB", + "\xA7\xD7" => "\xE5\xBF\xB8", + "\xA7\xD8" => "\xE5\xBF\xAA", + "\xA7\xD9" => "\xE6\x88\x92", + "\xA7\xDA" => "\xE6\x88\x91", + "\xA7\xDB" => "\xE6\x8A\x84", + "\xA7\xDC" => "\xE6\x8A\x97", + "\xA7\xDD" => "\xE6\x8A\x96", + "\xA7\xDE" => "\xE6\x8A\x80", + "\xA7\xDF" => "\xE6\x89\xB6", + "\xA7\xE0" => "\xE6\x8A\x89", + "\xA7\xE1" => "\xE6\x89\xAD", + "\xA7\xE2" => "\xE6\x8A\x8A", + "\xA7\xE3" => "\xE6\x89\xBC", + "\xA7\xE4" => "\xE6\x89\xBE", + "\xA7\xE5" => "\xE6\x89\xB9", + "\xA7\xE6" => "\xE6\x89\xB3", + "\xA7\xE7" => "\xE6\x8A\x92", + "\xA7\xE8" => "\xE6\x89\xAF", + "\xA7\xE9" => "\xE6\x8A\x98", + "\xA7\xEA" => "\xE6\x89\xAE", + "\xA7\xEB" => "\xE6\x8A\x95", + "\xA7\xEC" => "\xE6\x8A\x93", + "\xA7\xED" => "\xE6\x8A\x91", + "\xA7\xEE" => "\xE6\x8A\x86", + "\xA7\xEF" => "\xE6\x94\xB9", + "\xA7\xF0" => "\xE6\x94\xBB", + "\xA7\xF1" => "\xE6\x94\xB8", + "\xA7\xF2" => "\xE6\x97\xB1", + "\xA7\xF3" => "\xE6\x9B\xB4", + "\xA7\xF4" => "\xE6\x9D\x9F", + "\xA7\xF5" => "\xE6\x9D\x8E", + "\xA7\xF6" => "\xE6\x9D\x8F", + "\xA7\xF7" => "\xE6\x9D\x90", + "\xA7\xF8" => "\xE6\x9D\x91", + "\xA7\xF9" => "\xE6\x9D\x9C", + "\xA7\xFA" => "\xE6\x9D\x96", + "\xA7\xFB" => "\xE6\x9D\x9E", + "\xA7\xFC" => "\xE6\x9D\x89", + "\xA7\xFD" => "\xE6\x9D\x86", + "\xA7\xFE" => "\xE6\x9D\xA0", + "\xA8\x40" => "\xE6\x9D\x93", + "\xA8\x41" => "\xE6\x9D\x97", + "\xA8\x42" => "\xE6\xAD\xA5", + "\xA8\x43" => "\xE6\xAF\x8F", + "\xA8\x44" => "\xE6\xB1\x82", + "\xA8\x45" => "\xE6\xB1\x9E", + "\xA8\x46" => "\xE6\xB2\x99", + "\xA8\x47" => "\xE6\xB2\x81", + "\xA8\x48" => "\xE6\xB2\x88", + "\xA8\x49" => "\xE6\xB2\x89", + "\xA8\x4A" => "\xE6\xB2\x85", + "\xA8\x4B" => "\xE6\xB2\x9B", + "\xA8\x4C" => "\xE6\xB1\xAA", + "\xA8\x4D" => "\xE6\xB1\xBA", + "\xA8\x4E" => "\xE6\xB2\x90", + "\xA8\x4F" => "\xE6\xB1\xB0", + "\xA8\x50" => "\xE6\xB2\x8C", + "\xA8\x51" => "\xE6\xB1\xA8", + "\xA8\x52" => "\xE6\xB2\x96", + "\xA8\x53" => "\xE6\xB2\x92", + "\xA8\x54" => "\xE6\xB1\xBD", + "\xA8\x55" => "\xE6\xB2\x83", + "\xA8\x56" => "\xE6\xB1\xB2", + "\xA8\x57" => "\xE6\xB1\xBE", + "\xA8\x58" => "\xE6\xB1\xB4", + "\xA8\x59" => "\xE6\xB2\x86", + "\xA8\x5A" => "\xE6\xB1\xB6", + "\xA8\x5B" => "\xE6\xB2\x8D", + "\xA8\x5C" => "\xE6\xB2\x94", + "\xA8\x5D" => "\xE6\xB2\x98", + "\xA8\x5E" => "\xE6\xB2\x82", + "\xA8\x5F" => "\xE7\x81\xB6", + "\xA8\x60" => "\xE7\x81\xBC", + "\xA8\x61" => "\xE7\x81\xBD", + "\xA8\x62" => "\xE7\x81\xB8", + "\xA8\x63" => "\xE7\x89\xA2", + "\xA8\x64" => "\xE7\x89\xA1", + "\xA8\x65" => "\xE7\x89\xA0", + "\xA8\x66" => "\xE7\x8B\x84", + "\xA8\x67" => "\xE7\x8B\x82", + "\xA8\x68" => "\xE7\x8E\x96", + "\xA8\x69" => "\xE7\x94\xAC", + "\xA8\x6A" => "\xE7\x94\xAB", + "\xA8\x6B" => "\xE7\x94\xB7", + "\xA8\x6C" => "\xE7\x94\xB8", + "\xA8\x6D" => "\xE7\x9A\x82", + "\xA8\x6E" => "\xE7\x9B\xAF", + "\xA8\x6F" => "\xE7\x9F\xA3", + "\xA8\x70" => "\xE7\xA7\x81", + "\xA8\x71" => "\xE7\xA7\x80", + "\xA8\x72" => "\xE7\xA6\xBF", + "\xA8\x73" => "\xE7\xA9\xB6", + "\xA8\x74" => "\xE7\xB3\xBB", + "\xA8\x75" => "\xE7\xBD\x95", + "\xA8\x76" => "\xE8\x82\x96", + "\xA8\x77" => "\xE8\x82\x93", + "\xA8\x78" => "\xE8\x82\x9D", + "\xA8\x79" => "\xE8\x82\x98", + "\xA8\x7A" => "\xE8\x82\x9B", + "\xA8\x7B" => "\xE8\x82\x9A", + "\xA8\x7C" => "\xE8\x82\xB2", + "\xA8\x7D" => "\xE8\x89\xAF", + "\xA8\x7E" => "\xE8\x8A\x92", + "\xA8\xA1" => "\xE8\x8A\x8B", + "\xA8\xA2" => "\xE8\x8A\x8D", + "\xA8\xA3" => "\xE8\xA6\x8B", + "\xA8\xA4" => "\xE8\xA7\x92", + "\xA8\xA5" => "\xE8\xA8\x80", + "\xA8\xA6" => "\xE8\xB0\xB7", + "\xA8\xA7" => "\xE8\xB1\x86", + "\xA8\xA8" => "\xE8\xB1\x95", + "\xA8\xA9" => "\xE8\xB2\x9D", + "\xA8\xAA" => "\xE8\xB5\xA4", + "\xA8\xAB" => "\xE8\xB5\xB0", + "\xA8\xAC" => "\xE8\xB6\xB3", + "\xA8\xAD" => "\xE8\xBA\xAB", + "\xA8\xAE" => "\xE8\xBB\x8A", + "\xA8\xAF" => "\xE8\xBE\x9B", + "\xA8\xB0" => "\xE8\xBE\xB0", + "\xA8\xB1" => "\xE8\xBF\x82", + "\xA8\xB2" => "\xE8\xBF\x86", + "\xA8\xB3" => "\xE8\xBF\x85", + "\xA8\xB4" => "\xE8\xBF\x84", + "\xA8\xB5" => "\xE5\xB7\xA1", + "\xA8\xB6" => "\xE9\x82\x91", + "\xA8\xB7" => "\xE9\x82\xA2", + "\xA8\xB8" => "\xE9\x82\xAA", + "\xA8\xB9" => "\xE9\x82\xA6", + "\xA8\xBA" => "\xE9\x82\xA3", + "\xA8\xBB" => "\xE9\x85\x89", + "\xA8\xBC" => "\xE9\x87\x86", + "\xA8\xBD" => "\xE9\x87\x8C", + "\xA8\xBE" => "\xE9\x98\xB2", + "\xA8\xBF" => "\xE9\x98\xAE", + "\xA8\xC0" => "\xE9\x98\xB1", + "\xA8\xC1" => "\xE9\x98\xAA", + "\xA8\xC2" => "\xE9\x98\xAC", + "\xA8\xC3" => "\xE4\xB8\xA6", + "\xA8\xC4" => "\xE4\xB9\x96", + "\xA8\xC5" => "\xE4\xB9\xB3", + "\xA8\xC6" => "\xE4\xBA\x8B", + "\xA8\xC7" => "\xE4\xBA\x9B", + "\xA8\xC8" => "\xE4\xBA\x9E", + "\xA8\xC9" => "\xE4\xBA\xAB", + "\xA8\xCA" => "\xE4\xBA\xAC", + "\xA8\xCB" => "\xE4\xBD\xAF", + "\xA8\xCC" => "\xE4\xBE\x9D", + "\xA8\xCD" => "\xE4\xBE\x8D", + "\xA8\xCE" => "\xE4\xBD\xB3", + "\xA8\xCF" => "\xE4\xBD\xBF", + "\xA8\xD0" => "\xE4\xBD\xAC", + "\xA8\xD1" => "\xE4\xBE\x9B", + "\xA8\xD2" => "\xE4\xBE\x8B", + "\xA8\xD3" => "\xE4\xBE\x86", + "\xA8\xD4" => "\xE4\xBE\x83", + "\xA8\xD5" => "\xE4\xBD\xB0", + "\xA8\xD6" => "\xE4\xBD\xB5", + "\xA8\xD7" => "\xE4\xBE\x88", + "\xA8\xD8" => "\xE4\xBD\xA9", + "\xA8\xD9" => "\xE4\xBD\xBB", + "\xA8\xDA" => "\xE4\xBE\x96", + "\xA8\xDB" => "\xE4\xBD\xBE", + "\xA8\xDC" => "\xE4\xBE\x8F", + "\xA8\xDD" => "\xE4\xBE\x91", + "\xA8\xDE" => "\xE4\xBD\xBA", + "\xA8\xDF" => "\xE5\x85\x94", + "\xA8\xE0" => "\xE5\x85\x92", + "\xA8\xE1" => "\xE5\x85\x95", + "\xA8\xE2" => "\xE5\x85\xA9", + "\xA8\xE3" => "\xE5\x85\xB7", + "\xA8\xE4" => "\xE5\x85\xB6", + "\xA8\xE5" => "\xE5\x85\xB8", + "\xA8\xE6" => "\xE5\x86\xBD", + "\xA8\xE7" => "\xE5\x87\xBD", + "\xA8\xE8" => "\xE5\x88\xBB", + "\xA8\xE9" => "\xE5\x88\xB8", + "\xA8\xEA" => "\xE5\x88\xB7", + "\xA8\xEB" => "\xE5\x88\xBA", + "\xA8\xEC" => "\xE5\x88\xB0", + "\xA8\xED" => "\xE5\x88\xAE", + "\xA8\xEE" => "\xE5\x88\xB6", + "\xA8\xEF" => "\xE5\x89\x81", + "\xA8\xF0" => "\xE5\x8A\xBE", + "\xA8\xF1" => "\xE5\x8A\xBB", + "\xA8\xF2" => "\xE5\x8D\x92", + "\xA8\xF3" => "\xE5\x8D\x94", + "\xA8\xF4" => "\xE5\x8D\x93", + "\xA8\xF5" => "\xE5\x8D\x91", + "\xA8\xF6" => "\xE5\x8D\xA6", + "\xA8\xF7" => "\xE5\x8D\xB7", + "\xA8\xF8" => "\xE5\x8D\xB8", + "\xA8\xF9" => "\xE5\x8D\xB9", + "\xA8\xFA" => "\xE5\x8F\x96", + "\xA8\xFB" => "\xE5\x8F\x94", + "\xA8\xFC" => "\xE5\x8F\x97", + "\xA8\xFD" => "\xE5\x91\xB3", + "\xA8\xFE" => "\xE5\x91\xB5", + "\xA9\x40" => "\xE5\x92\x96", + "\xA9\x41" => "\xE5\x91\xB8", + "\xA9\x42" => "\xE5\x92\x95", + "\xA9\x43" => "\xE5\x92\x80", + "\xA9\x44" => "\xE5\x91\xBB", + "\xA9\x45" => "\xE5\x91\xB7", + "\xA9\x46" => "\xE5\x92\x84", + "\xA9\x47" => "\xE5\x92\x92", + "\xA9\x48" => "\xE5\x92\x86", + "\xA9\x49" => "\xE5\x91\xBC", + "\xA9\x4A" => "\xE5\x92\x90", + "\xA9\x4B" => "\xE5\x91\xB1", + "\xA9\x4C" => "\xE5\x91\xB6", + "\xA9\x4D" => "\xE5\x92\x8C", + "\xA9\x4E" => "\xE5\x92\x9A", + "\xA9\x4F" => "\xE5\x91\xA2", + "\xA9\x50" => "\xE5\x91\xA8", + "\xA9\x51" => "\xE5\x92\x8B", + "\xA9\x52" => "\xE5\x91\xBD", + "\xA9\x53" => "\xE5\x92\x8E", + "\xA9\x54" => "\xE5\x9B\xBA", + "\xA9\x55" => "\xE5\x9E\x83", + "\xA9\x56" => "\xE5\x9D\xB7", + "\xA9\x57" => "\xE5\x9D\xAA", + "\xA9\x58" => "\xE5\x9D\xA9", + "\xA9\x59" => "\xE5\x9D\xA1", + "\xA9\x5A" => "\xE5\x9D\xA6", + "\xA9\x5B" => "\xE5\x9D\xA4", + "\xA9\x5C" => "\xE5\x9D\xBC", + "\xA9\x5D" => "\xE5\xA4\x9C", + "\xA9\x5E" => "\xE5\xA5\x89", + "\xA9\x5F" => "\xE5\xA5\x87", + "\xA9\x60" => "\xE5\xA5\x88", + "\xA9\x61" => "\xE5\xA5\x84", + "\xA9\x62" => "\xE5\xA5\x94", + "\xA9\x63" => "\xE5\xA6\xBE", + "\xA9\x64" => "\xE5\xA6\xBB", + "\xA9\x65" => "\xE5\xA7\x94", + "\xA9\x66" => "\xE5\xA6\xB9", + "\xA9\x67" => "\xE5\xA6\xAE", + "\xA9\x68" => "\xE5\xA7\x91", + "\xA9\x69" => "\xE5\xA7\x86", + "\xA9\x6A" => "\xE5\xA7\x90", + "\xA9\x6B" => "\xE5\xA7\x8D", + "\xA9\x6C" => "\xE5\xA7\x8B", + "\xA9\x6D" => "\xE5\xA7\x93", + "\xA9\x6E" => "\xE5\xA7\x8A", + "\xA9\x6F" => "\xE5\xA6\xAF", + "\xA9\x70" => "\xE5\xA6\xB3", + "\xA9\x71" => "\xE5\xA7\x92", + "\xA9\x72" => "\xE5\xA7\x85", + "\xA9\x73" => "\xE5\xAD\x9F", + "\xA9\x74" => "\xE5\xAD\xA4", + "\xA9\x75" => "\xE5\xAD\xA3", + "\xA9\x76" => "\xE5\xAE\x97", + "\xA9\x77" => "\xE5\xAE\x9A", + "\xA9\x78" => "\xE5\xAE\x98", + "\xA9\x79" => "\xE5\xAE\x9C", + "\xA9\x7A" => "\xE5\xAE\x99", + "\xA9\x7B" => "\xE5\xAE\x9B", + "\xA9\x7C" => "\xE5\xB0\x9A", + "\xA9\x7D" => "\xE5\xB1\x88", + "\xA9\x7E" => "\xE5\xB1\x85", + "\xA9\xA1" => "\xE5\xB1\x86", + "\xA9\xA2" => "\xE5\xB2\xB7", + "\xA9\xA3" => "\xE5\xB2\xA1", + "\xA9\xA4" => "\xE5\xB2\xB8", + "\xA9\xA5" => "\xE5\xB2\xA9", + "\xA9\xA6" => "\xE5\xB2\xAB", + "\xA9\xA7" => "\xE5\xB2\xB1", + "\xA9\xA8" => "\xE5\xB2\xB3", + "\xA9\xA9" => "\xE5\xB8\x98", + "\xA9\xAA" => "\xE5\xB8\x9A", + "\xA9\xAB" => "\xE5\xB8\x96", + "\xA9\xAC" => "\xE5\xB8\x95", + "\xA9\xAD" => "\xE5\xB8\x9B", + "\xA9\xAE" => "\xE5\xB8\x91", + "\xA9\xAF" => "\xE5\xB9\xB8", + "\xA9\xB0" => "\xE5\xBA\x9A", + "\xA9\xB1" => "\xE5\xBA\x97", + "\xA9\xB2" => "\xE5\xBA\x9C", + "\xA9\xB3" => "\xE5\xBA\x95", + "\xA9\xB4" => "\xE5\xBA\x96", + "\xA9\xB5" => "\xE5\xBB\xB6", + "\xA9\xB6" => "\xE5\xBC\xA6", + "\xA9\xB7" => "\xE5\xBC\xA7", + "\xA9\xB8" => "\xE5\xBC\xA9", + "\xA9\xB9" => "\xE5\xBE\x80", + "\xA9\xBA" => "\xE5\xBE\x81", + "\xA9\xBB" => "\xE5\xBD\xBF", + "\xA9\xBC" => "\xE5\xBD\xBC", + "\xA9\xBD" => "\xE5\xBF\x9D", + "\xA9\xBE" => "\xE5\xBF\xA0", + "\xA9\xBF" => "\xE5\xBF\xBD", + "\xA9\xC0" => "\xE5\xBF\xB5", + "\xA9\xC1" => "\xE5\xBF\xBF", + "\xA9\xC2" => "\xE6\x80\x8F", + "\xA9\xC3" => "\xE6\x80\x94", + "\xA9\xC4" => "\xE6\x80\xAF", + "\xA9\xC5" => "\xE6\x80\xB5", + "\xA9\xC6" => "\xE6\x80\x96", + "\xA9\xC7" => "\xE6\x80\xAA", + "\xA9\xC8" => "\xE6\x80\x95", + "\xA9\xC9" => "\xE6\x80\xA1", + "\xA9\xCA" => "\xE6\x80\xA7", + "\xA9\xCB" => "\xE6\x80\xA9", + "\xA9\xCC" => "\xE6\x80\xAB", + "\xA9\xCD" => "\xE6\x80\x9B", + "\xA9\xCE" => "\xE6\x88\x96", + "\xA9\xCF" => "\xE6\x88\x95", + "\xA9\xD0" => "\xE6\x88\xBF", + "\xA9\xD1" => "\xE6\x88\xBE", + "\xA9\xD2" => "\xE6\x89\x80", + "\xA9\xD3" => "\xE6\x89\xBF", + "\xA9\xD4" => "\xE6\x8B\x89", + "\xA9\xD5" => "\xE6\x8B\x8C", + "\xA9\xD6" => "\xE6\x8B\x84", + "\xA9\xD7" => "\xE6\x8A\xBF", + "\xA9\xD8" => "\xE6\x8B\x82", + "\xA9\xD9" => "\xE6\x8A\xB9", + "\xA9\xDA" => "\xE6\x8B\x92", + "\xA9\xDB" => "\xE6\x8B\x9B", + "\xA9\xDC" => "\xE6\x8A\xAB", + "\xA9\xDD" => "\xE6\x8B\x93", + "\xA9\xDE" => "\xE6\x8B\x94", + "\xA9\xDF" => "\xE6\x8B\x8B", + "\xA9\xE0" => "\xE6\x8B\x88", + "\xA9\xE1" => "\xE6\x8A\xA8", + "\xA9\xE2" => "\xE6\x8A\xBD", + "\xA9\xE3" => "\xE6\x8A\xBC", + "\xA9\xE4" => "\xE6\x8B\x90", + "\xA9\xE5" => "\xE6\x8B\x99", + "\xA9\xE6" => "\xE6\x8B\x87", + "\xA9\xE7" => "\xE6\x8B\x8D", + "\xA9\xE8" => "\xE6\x8A\xB5", + "\xA9\xE9" => "\xE6\x8B\x9A", + "\xA9\xEA" => "\xE6\x8A\xB1", + "\xA9\xEB" => "\xE6\x8B\x98", + "\xA9\xEC" => "\xE6\x8B\x96", + "\xA9\xED" => "\xE6\x8B\x97", + "\xA9\xEE" => "\xE6\x8B\x86", + "\xA9\xEF" => "\xE6\x8A\xAC", + "\xA9\xF0" => "\xE6\x8B\x8E", + "\xA9\xF1" => "\xE6\x94\xBE", + "\xA9\xF2" => "\xE6\x96\xA7", + "\xA9\xF3" => "\xE6\x96\xBC", + "\xA9\xF4" => "\xE6\x97\xBA", + "\xA9\xF5" => "\xE6\x98\x94", + "\xA9\xF6" => "\xE6\x98\x93", + "\xA9\xF7" => "\xE6\x98\x8C", + "\xA9\xF8" => "\xE6\x98\x86", + "\xA9\xF9" => "\xE6\x98\x82", + "\xA9\xFA" => "\xE6\x98\x8E", + "\xA9\xFB" => "\xE6\x98\x80", + "\xA9\xFC" => "\xE6\x98\x8F", + "\xA9\xFD" => "\xE6\x98\x95", + "\xA9\xFE" => "\xE6\x98\x8A", + "\xAA\x40" => "\xE6\x98\x87", + "\xAA\x41" => "\xE6\x9C\x8D", + "\xAA\x42" => "\xE6\x9C\x8B", + "\xAA\x43" => "\xE6\x9D\xAD", + "\xAA\x44" => "\xE6\x9E\x8B", + "\xAA\x45" => "\xE6\x9E\x95", + "\xAA\x46" => "\xE6\x9D\xB1", + "\xAA\x47" => "\xE6\x9E\x9C", + "\xAA\x48" => "\xE6\x9D\xB3", + "\xAA\x49" => "\xE6\x9D\xB7", + "\xAA\x4A" => "\xE6\x9E\x87", + "\xAA\x4B" => "\xE6\x9E\x9D", + "\xAA\x4C" => "\xE6\x9E\x97", + "\xAA\x4D" => "\xE6\x9D\xAF", + "\xAA\x4E" => "\xE6\x9D\xB0", + "\xAA\x4F" => "\xE6\x9D\xBF", + "\xAA\x50" => "\xE6\x9E\x89", + "\xAA\x51" => "\xE6\x9D\xBE", + "\xAA\x52" => "\xE6\x9E\x90", + "\xAA\x53" => "\xE6\x9D\xB5", + "\xAA\x54" => "\xE6\x9E\x9A", + "\xAA\x55" => "\xE6\x9E\x93", + "\xAA\x56" => "\xE6\x9D\xBC", + "\xAA\x57" => "\xE6\x9D\xAA", + "\xAA\x58" => "\xE6\x9D\xB2", + "\xAA\x59" => "\xE6\xAC\xA3", + "\xAA\x5A" => "\xE6\xAD\xA6", + "\xAA\x5B" => "\xE6\xAD\xA7", + "\xAA\x5C" => "\xE6\xAD\xBF", + "\xAA\x5D" => "\xE6\xB0\x93", + "\xAA\x5E" => "\xE6\xB0\x9B", + "\xAA\x5F" => "\xE6\xB3\xA3", + "\xAA\x60" => "\xE6\xB3\xA8", + "\xAA\x61" => "\xE6\xB3\xB3", + "\xAA\x62" => "\xE6\xB2\xB1", + "\xAA\x63" => "\xE6\xB3\x8C", + "\xAA\x64" => "\xE6\xB3\xA5", + "\xAA\x65" => "\xE6\xB2\xB3", + "\xAA\x66" => "\xE6\xB2\xBD", + "\xAA\x67" => "\xE6\xB2\xBE", + "\xAA\x68" => "\xE6\xB2\xBC", + "\xAA\x69" => "\xE6\xB3\xA2", + "\xAA\x6A" => "\xE6\xB2\xAB", + "\xAA\x6B" => "\xE6\xB3\x95", + "\xAA\x6C" => "\xE6\xB3\x93", + "\xAA\x6D" => "\xE6\xB2\xB8", + "\xAA\x6E" => "\xE6\xB3\x84", + "\xAA\x6F" => "\xE6\xB2\xB9", + "\xAA\x70" => "\xE6\xB3\x81", + "\xAA\x71" => "\xE6\xB2\xAE", + "\xAA\x72" => "\xE6\xB3\x97", + "\xAA\x73" => "\xE6\xB3\x85", + "\xAA\x74" => "\xE6\xB3\xB1", + "\xAA\x75" => "\xE6\xB2\xBF", + "\xAA\x76" => "\xE6\xB2\xBB", + "\xAA\x77" => "\xE6\xB3\xA1", + "\xAA\x78" => "\xE6\xB3\x9B", + "\xAA\x79" => "\xE6\xB3\x8A", + "\xAA\x7A" => "\xE6\xB2\xAC", + "\xAA\x7B" => "\xE6\xB3\xAF", + "\xAA\x7C" => "\xE6\xB3\x9C", + "\xAA\x7D" => "\xE6\xB3\x96", + "\xAA\x7E" => "\xE6\xB3\xA0", + "\xAA\xA1" => "\xE7\x82\x95", + "\xAA\xA2" => "\xE7\x82\x8E", + "\xAA\xA3" => "\xE7\x82\x92", + "\xAA\xA4" => "\xE7\x82\x8A", + "\xAA\xA5" => "\xE7\x82\x99", + "\xAA\xA6" => "\xE7\x88\xAC", + "\xAA\xA7" => "\xE7\x88\xAD", + "\xAA\xA8" => "\xE7\x88\xB8", + "\xAA\xA9" => "\xE7\x89\x88", + "\xAA\xAA" => "\xE7\x89\xA7", + "\xAA\xAB" => "\xE7\x89\xA9", + "\xAA\xAC" => "\xE7\x8B\x80", + "\xAA\xAD" => "\xE7\x8B\x8E", + "\xAA\xAE" => "\xE7\x8B\x99", + "\xAA\xAF" => "\xE7\x8B\x97", + "\xAA\xB0" => "\xE7\x8B\x90", + "\xAA\xB1" => "\xE7\x8E\xA9", + "\xAA\xB2" => "\xE7\x8E\xA8", + "\xAA\xB3" => "\xE7\x8E\x9F", + "\xAA\xB4" => "\xE7\x8E\xAB", + "\xAA\xB5" => "\xE7\x8E\xA5", + "\xAA\xB6" => "\xE7\x94\xBD", + "\xAA\xB7" => "\xE7\x96\x9D", + "\xAA\xB8" => "\xE7\x96\x99", + "\xAA\xB9" => "\xE7\x96\x9A", + "\xAA\xBA" => "\xE7\x9A\x84", + "\xAA\xBB" => "\xE7\x9B\x82", + "\xAA\xBC" => "\xE7\x9B\xB2", + "\xAA\xBD" => "\xE7\x9B\xB4", + "\xAA\xBE" => "\xE7\x9F\xA5", + "\xAA\xBF" => "\xE7\x9F\xBD", + "\xAA\xC0" => "\xE7\xA4\xBE", + "\xAA\xC1" => "\xE7\xA5\x80", + "\xAA\xC2" => "\xE7\xA5\x81", + "\xAA\xC3" => "\xE7\xA7\x89", + "\xAA\xC4" => "\xE7\xA7\x88", + "\xAA\xC5" => "\xE7\xA9\xBA", + "\xAA\xC6" => "\xE7\xA9\xB9", + "\xAA\xC7" => "\xE7\xAB\xBA", + "\xAA\xC8" => "\xE7\xB3\xBE", + "\xAA\xC9" => "\xE7\xBD\x94", + "\xAA\xCA" => "\xE7\xBE\x8C", + "\xAA\xCB" => "\xE7\xBE\x8B", + "\xAA\xCC" => "\xE8\x80\x85", + "\xAA\xCD" => "\xE8\x82\xBA", + "\xAA\xCE" => "\xE8\x82\xA5", + "\xAA\xCF" => "\xE8\x82\xA2", + "\xAA\xD0" => "\xE8\x82\xB1", + "\xAA\xD1" => "\xE8\x82\xA1", + "\xAA\xD2" => "\xE8\x82\xAB", + "\xAA\xD3" => "\xE8\x82\xA9", + "\xAA\xD4" => "\xE8\x82\xB4", + "\xAA\xD5" => "\xE8\x82\xAA", + "\xAA\xD6" => "\xE8\x82\xAF", + "\xAA\xD7" => "\xE8\x87\xA5", + "\xAA\xD8" => "\xE8\x87\xBE", + "\xAA\xD9" => "\xE8\x88\x8D", + "\xAA\xDA" => "\xE8\x8A\xB3", + "\xAA\xDB" => "\xE8\x8A\x9D", + "\xAA\xDC" => "\xE8\x8A\x99", + "\xAA\xDD" => "\xE8\x8A\xAD", + "\xAA\xDE" => "\xE8\x8A\xBD", + "\xAA\xDF" => "\xE8\x8A\x9F", + "\xAA\xE0" => "\xE8\x8A\xB9", + "\xAA\xE1" => "\xE8\x8A\xB1", + "\xAA\xE2" => "\xE8\x8A\xAC", + "\xAA\xE3" => "\xE8\x8A\xA5", + "\xAA\xE4" => "\xE8\x8A\xAF", + "\xAA\xE5" => "\xE8\x8A\xB8", + "\xAA\xE6" => "\xE8\x8A\xA3", + "\xAA\xE7" => "\xE8\x8A\xB0", + "\xAA\xE8" => "\xE8\x8A\xBE", + "\xAA\xE9" => "\xE8\x8A\xB7", + "\xAA\xEA" => "\xE8\x99\x8E", + "\xAA\xEB" => "\xE8\x99\xB1", + "\xAA\xEC" => "\xE5\x88\x9D", + "\xAA\xED" => "\xE8\xA1\xA8", + "\xAA\xEE" => "\xE8\xBB\x8B", + "\xAA\xEF" => "\xE8\xBF\x8E", + "\xAA\xF0" => "\xE8\xBF\x94", + "\xAA\xF1" => "\xE8\xBF\x91", + "\xAA\xF2" => "\xE9\x82\xB5", + "\xAA\xF3" => "\xE9\x82\xB8", + "\xAA\xF4" => "\xE9\x82\xB1", + "\xAA\xF5" => "\xE9\x82\xB6", + "\xAA\xF6" => "\xE9\x87\x87", + "\xAA\xF7" => "\xE9\x87\x91", + "\xAA\xF8" => "\xE9\x95\xB7", + "\xAA\xF9" => "\xE9\x96\x80", + "\xAA\xFA" => "\xE9\x98\x9C", + "\xAA\xFB" => "\xE9\x99\x80", + "\xAA\xFC" => "\xE9\x98\xBF", + "\xAA\xFD" => "\xE9\x98\xBB", + "\xAA\xFE" => "\xE9\x99\x84", + "\xAB\x40" => "\xE9\x99\x82", + "\xAB\x41" => "\xE9\x9A\xB9", + "\xAB\x42" => "\xE9\x9B\xA8", + "\xAB\x43" => "\xE9\x9D\x92", + "\xAB\x44" => "\xE9\x9D\x9E", + "\xAB\x45" => "\xE4\xBA\x9F", + "\xAB\x46" => "\xE4\xBA\xAD", + "\xAB\x47" => "\xE4\xBA\xAE", + "\xAB\x48" => "\xE4\xBF\xA1", + "\xAB\x49" => "\xE4\xBE\xB5", + "\xAB\x4A" => "\xE4\xBE\xAF", + "\xAB\x4B" => "\xE4\xBE\xBF", + "\xAB\x4C" => "\xE4\xBF\xA0", + "\xAB\x4D" => "\xE4\xBF\x91", + "\xAB\x4E" => "\xE4\xBF\x8F", + "\xAB\x4F" => "\xE4\xBF\x9D", + "\xAB\x50" => "\xE4\xBF\x83", + "\xAB\x51" => "\xE4\xBE\xB6", + "\xAB\x52" => "\xE4\xBF\x98", + "\xAB\x53" => "\xE4\xBF\x9F", + "\xAB\x54" => "\xE4\xBF\x8A", + "\xAB\x55" => "\xE4\xBF\x97", + "\xAB\x56" => "\xE4\xBE\xAE", + "\xAB\x57" => "\xE4\xBF\x90", + "\xAB\x58" => "\xE4\xBF\x84", + "\xAB\x59" => "\xE4\xBF\x82", + "\xAB\x5A" => "\xE4\xBF\x9A", + "\xAB\x5B" => "\xE4\xBF\x8E", + "\xAB\x5C" => "\xE4\xBF\x9E", + "\xAB\x5D" => "\xE4\xBE\xB7", + "\xAB\x5E" => "\xE5\x85\x97", + "\xAB\x5F" => "\xE5\x86\x92", + "\xAB\x60" => "\xE5\x86\x91", + "\xAB\x61" => "\xE5\x86\xA0", + "\xAB\x62" => "\xE5\x89\x8E", + "\xAB\x63" => "\xE5\x89\x83", + "\xAB\x64" => "\xE5\x89\x8A", + "\xAB\x65" => "\xE5\x89\x8D", + "\xAB\x66" => "\xE5\x89\x8C", + "\xAB\x67" => "\xE5\x89\x8B", + "\xAB\x68" => "\xE5\x89\x87", + "\xAB\x69" => "\xE5\x8B\x87", + "\xAB\x6A" => "\xE5\x8B\x89", + "\xAB\x6B" => "\xE5\x8B\x83", + "\xAB\x6C" => "\xE5\x8B\x81", + "\xAB\x6D" => "\xE5\x8C\x8D", + "\xAB\x6E" => "\xE5\x8D\x97", + "\xAB\x6F" => "\xE5\x8D\xBB", + "\xAB\x70" => "\xE5\x8E\x9A", + "\xAB\x71" => "\xE5\x8F\x9B", + "\xAB\x72" => "\xE5\x92\xAC", + "\xAB\x73" => "\xE5\x93\x80", + "\xAB\x74" => "\xE5\x92\xA8", + "\xAB\x75" => "\xE5\x93\x8E", + "\xAB\x76" => "\xE5\x93\x89", + "\xAB\x77" => "\xE5\x92\xB8", + "\xAB\x78" => "\xE5\x92\xA6", + "\xAB\x79" => "\xE5\x92\xB3", + "\xAB\x7A" => "\xE5\x93\x87", + "\xAB\x7B" => "\xE5\x93\x82", + "\xAB\x7C" => "\xE5\x92\xBD", + "\xAB\x7D" => "\xE5\x92\xAA", + "\xAB\x7E" => "\xE5\x93\x81", + "\xAB\xA1" => "\xE5\x93\x84", + "\xAB\xA2" => "\xE5\x93\x88", + "\xAB\xA3" => "\xE5\x92\xAF", + "\xAB\xA4" => "\xE5\x92\xAB", + "\xAB\xA5" => "\xE5\x92\xB1", + "\xAB\xA6" => "\xE5\x92\xBB", + "\xAB\xA7" => "\xE5\x92\xA9", + "\xAB\xA8" => "\xE5\x92\xA7", + "\xAB\xA9" => "\xE5\x92\xBF", + "\xAB\xAA" => "\xE5\x9B\xBF", + "\xAB\xAB" => "\xE5\x9E\x82", + "\xAB\xAC" => "\xE5\x9E\x8B", + "\xAB\xAD" => "\xE5\x9E\xA0", + "\xAB\xAE" => "\xE5\x9E\xA3", + "\xAB\xAF" => "\xE5\x9E\xA2", + "\xAB\xB0" => "\xE5\x9F\x8E", + "\xAB\xB1" => "\xE5\x9E\xAE", + "\xAB\xB2" => "\xE5\x9E\x93", + "\xAB\xB3" => "\xE5\xA5\x95", + "\xAB\xB4" => "\xE5\xA5\x91", + "\xAB\xB5" => "\xE5\xA5\x8F", + "\xAB\xB6" => "\xE5\xA5\x8E", + "\xAB\xB7" => "\xE5\xA5\x90", + "\xAB\xB8" => "\xE5\xA7\x9C", + "\xAB\xB9" => "\xE5\xA7\x98", + "\xAB\xBA" => "\xE5\xA7\xBF", + "\xAB\xBB" => "\xE5\xA7\xA3", + "\xAB\xBC" => "\xE5\xA7\xA8", + "\xAB\xBD" => "\xE5\xA8\x83", + "\xAB\xBE" => "\xE5\xA7\xA5", + "\xAB\xBF" => "\xE5\xA7\xAA", + "\xAB\xC0" => "\xE5\xA7\x9A", + "\xAB\xC1" => "\xE5\xA7\xA6", + "\xAB\xC2" => "\xE5\xA8\x81", + "\xAB\xC3" => "\xE5\xA7\xBB", + "\xAB\xC4" => "\xE5\xAD\xA9", + "\xAB\xC5" => "\xE5\xAE\xA3", + "\xAB\xC6" => "\xE5\xAE\xA6", + "\xAB\xC7" => "\xE5\xAE\xA4", + "\xAB\xC8" => "\xE5\xAE\xA2", + "\xAB\xC9" => "\xE5\xAE\xA5", + "\xAB\xCA" => "\xE5\xB0\x81", + "\xAB\xCB" => "\xE5\xB1\x8E", + "\xAB\xCC" => "\xE5\xB1\x8F", + "\xAB\xCD" => "\xE5\xB1\x8D", + "\xAB\xCE" => "\xE5\xB1\x8B", + "\xAB\xCF" => "\xE5\xB3\x99", + "\xAB\xD0" => "\xE5\xB3\x92", + "\xAB\xD1" => "\xE5\xB7\xB7", + "\xAB\xD2" => "\xE5\xB8\x9D", + "\xAB\xD3" => "\xE5\xB8\xA5", + "\xAB\xD4" => "\xE5\xB8\x9F", + "\xAB\xD5" => "\xE5\xB9\xBD", + "\xAB\xD6" => "\xE5\xBA\xA0", + "\xAB\xD7" => "\xE5\xBA\xA6", + "\xAB\xD8" => "\xE5\xBB\xBA", + "\xAB\xD9" => "\xE5\xBC\x88", + "\xAB\xDA" => "\xE5\xBC\xAD", + "\xAB\xDB" => "\xE5\xBD\xA5", + "\xAB\xDC" => "\xE5\xBE\x88", + "\xAB\xDD" => "\xE5\xBE\x85", + "\xAB\xDE" => "\xE5\xBE\x8A", + "\xAB\xDF" => "\xE5\xBE\x8B", + "\xAB\xE0" => "\xE5\xBE\x87", + "\xAB\xE1" => "\xE5\xBE\x8C", + "\xAB\xE2" => "\xE5\xBE\x89", + "\xAB\xE3" => "\xE6\x80\x92", + "\xAB\xE4" => "\xE6\x80\x9D", + "\xAB\xE5" => "\xE6\x80\xA0", + "\xAB\xE6" => "\xE6\x80\xA5", + "\xAB\xE7" => "\xE6\x80\x8E", + "\xAB\xE8" => "\xE6\x80\xA8", + "\xAB\xE9" => "\xE6\x81\x8D", + "\xAB\xEA" => "\xE6\x81\xB0", + "\xAB\xEB" => "\xE6\x81\xA8", + "\xAB\xEC" => "\xE6\x81\xA2", + "\xAB\xED" => "\xE6\x81\x86", + "\xAB\xEE" => "\xE6\x81\x83", + "\xAB\xEF" => "\xE6\x81\xAC", + "\xAB\xF0" => "\xE6\x81\xAB", + "\xAB\xF1" => "\xE6\x81\xAA", + "\xAB\xF2" => "\xE6\x81\xA4", + "\xAB\xF3" => "\xE6\x89\x81", + "\xAB\xF4" => "\xE6\x8B\x9C", + "\xAB\xF5" => "\xE6\x8C\x96", + "\xAB\xF6" => "\xE6\x8C\x89", + "\xAB\xF7" => "\xE6\x8B\xBC", + "\xAB\xF8" => "\xE6\x8B\xAD", + "\xAB\xF9" => "\xE6\x8C\x81", + "\xAB\xFA" => "\xE6\x8B\xAE", + "\xAB\xFB" => "\xE6\x8B\xBD", + "\xAB\xFC" => "\xE6\x8C\x87", + "\xAB\xFD" => "\xE6\x8B\xB1", + "\xAB\xFE" => "\xE6\x8B\xB7", + "\xAC\x40" => "\xE6\x8B\xAF", + "\xAC\x41" => "\xE6\x8B\xAC", + "\xAC\x42" => "\xE6\x8B\xBE", + "\xAC\x43" => "\xE6\x8B\xB4", + "\xAC\x44" => "\xE6\x8C\x91", + "\xAC\x45" => "\xE6\x8C\x82", + "\xAC\x46" => "\xE6\x94\xBF", + "\xAC\x47" => "\xE6\x95\x85", + "\xAC\x48" => "\xE6\x96\xAB", + "\xAC\x49" => "\xE6\x96\xBD", + "\xAC\x4A" => "\xE6\x97\xA2", + "\xAC\x4B" => "\xE6\x98\xA5", + "\xAC\x4C" => "\xE6\x98\xAD", + "\xAC\x4D" => "\xE6\x98\xA0", + "\xAC\x4E" => "\xE6\x98\xA7", + "\xAC\x4F" => "\xE6\x98\xAF", + "\xAC\x50" => "\xE6\x98\x9F", + "\xAC\x51" => "\xE6\x98\xA8", + "\xAC\x52" => "\xE6\x98\xB1", + "\xAC\x53" => "\xE6\x98\xA4", + "\xAC\x54" => "\xE6\x9B\xB7", + "\xAC\x55" => "\xE6\x9F\xBF", + "\xAC\x56" => "\xE6\x9F\x93", + "\xAC\x57" => "\xE6\x9F\xB1", + "\xAC\x58" => "\xE6\x9F\x94", + "\xAC\x59" => "\xE6\x9F\x90", + "\xAC\x5A" => "\xE6\x9F\xAC", + "\xAC\x5B" => "\xE6\x9E\xB6", + "\xAC\x5C" => "\xE6\x9E\xAF", + "\xAC\x5D" => "\xE6\x9F\xB5", + "\xAC\x5E" => "\xE6\x9F\xA9", + "\xAC\x5F" => "\xE6\x9F\xAF", + "\xAC\x60" => "\xE6\x9F\x84", + "\xAC\x61" => "\xE6\x9F\x91", + "\xAC\x62" => "\xE6\x9E\xB4", + "\xAC\x63" => "\xE6\x9F\x9A", + "\xAC\x64" => "\xE6\x9F\xA5", + "\xAC\x65" => "\xE6\x9E\xB8", + "\xAC\x66" => "\xE6\x9F\x8F", + "\xAC\x67" => "\xE6\x9F\x9E", + "\xAC\x68" => "\xE6\x9F\xB3", + "\xAC\x69" => "\xE6\x9E\xB0", + "\xAC\x6A" => "\xE6\x9F\x99", + "\xAC\x6B" => "\xE6\x9F\xA2", + "\xAC\x6C" => "\xE6\x9F\x9D", + "\xAC\x6D" => "\xE6\x9F\x92", + "\xAC\x6E" => "\xE6\xAD\xAA", + "\xAC\x6F" => "\xE6\xAE\x83", + "\xAC\x70" => "\xE6\xAE\x86", + "\xAC\x71" => "\xE6\xAE\xB5", + "\xAC\x72" => "\xE6\xAF\x92", + "\xAC\x73" => "\xE6\xAF\x97", + "\xAC\x74" => "\xE6\xB0\x9F", + "\xAC\x75" => "\xE6\xB3\x89", + "\xAC\x76" => "\xE6\xB4\x8B", + "\xAC\x77" => "\xE6\xB4\xB2", + "\xAC\x78" => "\xE6\xB4\xAA", + "\xAC\x79" => "\xE6\xB5\x81", + "\xAC\x7A" => "\xE6\xB4\xA5", + "\xAC\x7B" => "\xE6\xB4\x8C", + "\xAC\x7C" => "\xE6\xB4\xB1", + "\xAC\x7D" => "\xE6\xB4\x9E", + "\xAC\x7E" => "\xE6\xB4\x97", + "\xAC\xA1" => "\xE6\xB4\xBB", + "\xAC\xA2" => "\xE6\xB4\xBD", + "\xAC\xA3" => "\xE6\xB4\xBE", + "\xAC\xA4" => "\xE6\xB4\xB6", + "\xAC\xA5" => "\xE6\xB4\x9B", + "\xAC\xA6" => "\xE6\xB3\xB5", + "\xAC\xA7" => "\xE6\xB4\xB9", + "\xAC\xA8" => "\xE6\xB4\xA7", + "\xAC\xA9" => "\xE6\xB4\xB8", + "\xAC\xAA" => "\xE6\xB4\xA9", + "\xAC\xAB" => "\xE6\xB4\xAE", + "\xAC\xAC" => "\xE6\xB4\xB5", + "\xAC\xAD" => "\xE6\xB4\x8E", + "\xAC\xAE" => "\xE6\xB4\xAB", + "\xAC\xAF" => "\xE7\x82\xAB", + "\xAC\xB0" => "\xE7\x82\xBA", + "\xAC\xB1" => "\xE7\x82\xB3", + "\xAC\xB2" => "\xE7\x82\xAC", + "\xAC\xB3" => "\xE7\x82\xAF", + "\xAC\xB4" => "\xE7\x82\xAD", + "\xAC\xB5" => "\xE7\x82\xB8", + "\xAC\xB6" => "\xE7\x82\xAE", + "\xAC\xB7" => "\xE7\x82\xA4", + "\xAC\xB8" => "\xE7\x88\xB0", + "\xAC\xB9" => "\xE7\x89\xB2", + "\xAC\xBA" => "\xE7\x89\xAF", + "\xAC\xBB" => "\xE7\x89\xB4", + "\xAC\xBC" => "\xE7\x8B\xA9", + "\xAC\xBD" => "\xE7\x8B\xA0", + "\xAC\xBE" => "\xE7\x8B\xA1", + "\xAC\xBF" => "\xE7\x8E\xB7", + "\xAC\xC0" => "\xE7\x8F\x8A", + "\xAC\xC1" => "\xE7\x8E\xBB", + "\xAC\xC2" => "\xE7\x8E\xB2", + "\xAC\xC3" => "\xE7\x8F\x8D", + "\xAC\xC4" => "\xE7\x8F\x80", + "\xAC\xC5" => "\xE7\x8E\xB3", + "\xAC\xC6" => "\xE7\x94\x9A", + "\xAC\xC7" => "\xE7\x94\xAD", + "\xAC\xC8" => "\xE7\x95\x8F", + "\xAC\xC9" => "\xE7\x95\x8C", + "\xAC\xCA" => "\xE7\x95\x8E", + "\xAC\xCB" => "\xE7\x95\x8B", + "\xAC\xCC" => "\xE7\x96\xAB", + "\xAC\xCD" => "\xE7\x96\xA4", + "\xAC\xCE" => "\xE7\x96\xA5", + "\xAC\xCF" => "\xE7\x96\xA2", + "\xAC\xD0" => "\xE7\x96\xA3", + "\xAC\xD1" => "\xE7\x99\xB8", + "\xAC\xD2" => "\xE7\x9A\x86", + "\xAC\xD3" => "\xE7\x9A\x87", + "\xAC\xD4" => "\xE7\x9A\x88", + "\xAC\xD5" => "\xE7\x9B\x88", + "\xAC\xD6" => "\xE7\x9B\x86", + "\xAC\xD7" => "\xE7\x9B\x83", + "\xAC\xD8" => "\xE7\x9B\x85", + "\xAC\xD9" => "\xE7\x9C\x81", + "\xAC\xDA" => "\xE7\x9B\xB9", + "\xAC\xDB" => "\xE7\x9B\xB8", + "\xAC\xDC" => "\xE7\x9C\x89", + "\xAC\xDD" => "\xE7\x9C\x8B", + "\xAC\xDE" => "\xE7\x9B\xBE", + "\xAC\xDF" => "\xE7\x9B\xBC", + "\xAC\xE0" => "\xE7\x9C\x87", + "\xAC\xE1" => "\xE7\x9F\x9C", + "\xAC\xE2" => "\xE7\xA0\x82", + "\xAC\xE3" => "\xE7\xA0\x94", + "\xAC\xE4" => "\xE7\xA0\x8C", + "\xAC\xE5" => "\xE7\xA0\x8D", + "\xAC\xE6" => "\xE7\xA5\x86", + "\xAC\xE7" => "\xE7\xA5\x89", + "\xAC\xE8" => "\xE7\xA5\x88", + "\xAC\xE9" => "\xE7\xA5\x87", + "\xAC\xEA" => "\xE7\xA6\xB9", + "\xAC\xEB" => "\xE7\xA6\xBA", + "\xAC\xEC" => "\xE7\xA7\x91", + "\xAC\xED" => "\xE7\xA7\x92", + "\xAC\xEE" => "\xE7\xA7\x8B", + "\xAC\xEF" => "\xE7\xA9\xBF", + "\xAC\xF0" => "\xE7\xAA\x81", + "\xAC\xF1" => "\xE7\xAB\xBF", + "\xAC\xF2" => "\xE7\xAB\xBD", + "\xAC\xF3" => "\xE7\xB1\xBD", + "\xAC\xF4" => "\xE7\xB4\x82", + "\xAC\xF5" => "\xE7\xB4\x85", + "\xAC\xF6" => "\xE7\xB4\x80", + "\xAC\xF7" => "\xE7\xB4\x89", + "\xAC\xF8" => "\xE7\xB4\x87", + "\xAC\xF9" => "\xE7\xB4\x84", + "\xAC\xFA" => "\xE7\xB4\x86", + "\xAC\xFB" => "\xE7\xBC\xB8", + "\xAC\xFC" => "\xE7\xBE\x8E", + "\xAC\xFD" => "\xE7\xBE\xBF", + "\xAC\xFE" => "\xE8\x80\x84", + "\xAD\x40" => "\xE8\x80\x90", + "\xAD\x41" => "\xE8\x80\x8D", + "\xAD\x42" => "\xE8\x80\x91", + "\xAD\x43" => "\xE8\x80\xB6", + "\xAD\x44" => "\xE8\x83\x96", + "\xAD\x45" => "\xE8\x83\xA5", + "\xAD\x46" => "\xE8\x83\x9A", + "\xAD\x47" => "\xE8\x83\x83", + "\xAD\x48" => "\xE8\x83\x84", + "\xAD\x49" => "\xE8\x83\x8C", + "\xAD\x4A" => "\xE8\x83\xA1", + "\xAD\x4B" => "\xE8\x83\x9B", + "\xAD\x4C" => "\xE8\x83\x8E", + "\xAD\x4D" => "\xE8\x83\x9E", + "\xAD\x4E" => "\xE8\x83\xA4", + "\xAD\x4F" => "\xE8\x83\x9D", + "\xAD\x50" => "\xE8\x87\xB4", + "\xAD\x51" => "\xE8\x88\xA2", + "\xAD\x52" => "\xE8\x8B\xA7", + "\xAD\x53" => "\xE8\x8C\x83", + "\xAD\x54" => "\xE8\x8C\x85", + "\xAD\x55" => "\xE8\x8B\xA3", + "\xAD\x56" => "\xE8\x8B\x9B", + "\xAD\x57" => "\xE8\x8B\xA6", + "\xAD\x58" => "\xE8\x8C\x84", + "\xAD\x59" => "\xE8\x8B\xA5", + "\xAD\x5A" => "\xE8\x8C\x82", + "\xAD\x5B" => "\xE8\x8C\x89", + "\xAD\x5C" => "\xE8\x8B\x92", + "\xAD\x5D" => "\xE8\x8B\x97", + "\xAD\x5E" => "\xE8\x8B\xB1", + "\xAD\x5F" => "\xE8\x8C\x81", + "\xAD\x60" => "\xE8\x8B\x9C", + "\xAD\x61" => "\xE8\x8B\x94", + "\xAD\x62" => "\xE8\x8B\x91", + "\xAD\x63" => "\xE8\x8B\x9E", + "\xAD\x64" => "\xE8\x8B\x93", + "\xAD\x65" => "\xE8\x8B\x9F", + "\xAD\x66" => "\xE8\x8B\xAF", + "\xAD\x67" => "\xE8\x8C\x86", + "\xAD\x68" => "\xE8\x99\x90", + "\xAD\x69" => "\xE8\x99\xB9", + "\xAD\x6A" => "\xE8\x99\xBB", + "\xAD\x6B" => "\xE8\x99\xBA", + "\xAD\x6C" => "\xE8\xA1\x8D", + "\xAD\x6D" => "\xE8\xA1\xAB", + "\xAD\x6E" => "\xE8\xA6\x81", + "\xAD\x6F" => "\xE8\xA7\x94", + "\xAD\x70" => "\xE8\xA8\x88", + "\xAD\x71" => "\xE8\xA8\x82", + "\xAD\x72" => "\xE8\xA8\x83", + "\xAD\x73" => "\xE8\xB2\x9E", + "\xAD\x74" => "\xE8\xB2\xA0", + "\xAD\x75" => "\xE8\xB5\xB4", + "\xAD\x76" => "\xE8\xB5\xB3", + "\xAD\x77" => "\xE8\xB6\xB4", + "\xAD\x78" => "\xE8\xBB\x8D", + "\xAD\x79" => "\xE8\xBB\x8C", + "\xAD\x7A" => "\xE8\xBF\xB0", + "\xAD\x7B" => "\xE8\xBF\xA6", + "\xAD\x7C" => "\xE8\xBF\xA2", + "\xAD\x7D" => "\xE8\xBF\xAA", + "\xAD\x7E" => "\xE8\xBF\xA5", + "\xAD\xA1" => "\xE8\xBF\xAD", + "\xAD\xA2" => "\xE8\xBF\xAB", + "\xAD\xA3" => "\xE8\xBF\xA4", + "\xAD\xA4" => "\xE8\xBF\xA8", + "\xAD\xA5" => "\xE9\x83\x8A", + "\xAD\xA6" => "\xE9\x83\x8E", + "\xAD\xA7" => "\xE9\x83\x81", + "\xAD\xA8" => "\xE9\x83\x83", + "\xAD\xA9" => "\xE9\x85\x8B", + "\xAD\xAA" => "\xE9\x85\x8A", + "\xAD\xAB" => "\xE9\x87\x8D", + "\xAD\xAC" => "\xE9\x96\x82", + "\xAD\xAD" => "\xE9\x99\x90", + "\xAD\xAE" => "\xE9\x99\x8B", + "\xAD\xAF" => "\xE9\x99\x8C", + "\xAD\xB0" => "\xE9\x99\x8D", + "\xAD\xB1" => "\xE9\x9D\xA2", + "\xAD\xB2" => "\xE9\x9D\xA9", + "\xAD\xB3" => "\xE9\x9F\x8B", + "\xAD\xB4" => "\xE9\x9F\xAD", + "\xAD\xB5" => "\xE9\x9F\xB3", + "\xAD\xB6" => "\xE9\xA0\x81", + "\xAD\xB7" => "\xE9\xA2\xA8", + "\xAD\xB8" => "\xE9\xA3\x9B", + "\xAD\xB9" => "\xE9\xA3\x9F", + "\xAD\xBA" => "\xE9\xA6\x96", + "\xAD\xBB" => "\xE9\xA6\x99", + "\xAD\xBC" => "\xE4\xB9\x98", + "\xAD\xBD" => "\xE4\xBA\xB3", + "\xAD\xBE" => "\xE5\x80\x8C", + "\xAD\xBF" => "\xE5\x80\x8D", + "\xAD\xC0" => "\xE5\x80\xA3", + "\xAD\xC1" => "\xE4\xBF\xAF", + "\xAD\xC2" => "\xE5\x80\xA6", + "\xAD\xC3" => "\xE5\x80\xA5", + "\xAD\xC4" => "\xE4\xBF\xB8", + "\xAD\xC5" => "\xE5\x80\xA9", + "\xAD\xC6" => "\xE5\x80\x96", + "\xAD\xC7" => "\xE5\x80\x86", + "\xAD\xC8" => "\xE5\x80\xBC", + "\xAD\xC9" => "\xE5\x80\x9F", + "\xAD\xCA" => "\xE5\x80\x9A", + "\xAD\xCB" => "\xE5\x80\x92", + "\xAD\xCC" => "\xE5\x80\x91", + "\xAD\xCD" => "\xE4\xBF\xBA", + "\xAD\xCE" => "\xE5\x80\x80", + "\xAD\xCF" => "\xE5\x80\x94", + "\xAD\xD0" => "\xE5\x80\xA8", + "\xAD\xD1" => "\xE4\xBF\xB1", + "\xAD\xD2" => "\xE5\x80\xA1", + "\xAD\xD3" => "\xE5\x80\x8B", + "\xAD\xD4" => "\xE5\x80\x99", + "\xAD\xD5" => "\xE5\x80\x98", + "\xAD\xD6" => "\xE4\xBF\xB3", + "\xAD\xD7" => "\xE4\xBF\xAE", + "\xAD\xD8" => "\xE5\x80\xAD", + "\xAD\xD9" => "\xE5\x80\xAA", + "\xAD\xDA" => "\xE4\xBF\xBE", + "\xAD\xDB" => "\xE5\x80\xAB", + "\xAD\xDC" => "\xE5\x80\x89", + "\xAD\xDD" => "\xE5\x85\xBC", + "\xAD\xDE" => "\xE5\x86\xA4", + "\xAD\xDF" => "\xE5\x86\xA5", + "\xAD\xE0" => "\xE5\x86\xA2", + "\xAD\xE1" => "\xE5\x87\x8D", + "\xAD\xE2" => "\xE5\x87\x8C", + "\xAD\xE3" => "\xE5\x87\x86", + "\xAD\xE4" => "\xE5\x87\x8B", + "\xAD\xE5" => "\xE5\x89\x96", + "\xAD\xE6" => "\xE5\x89\x9C", + "\xAD\xE7" => "\xE5\x89\x94", + "\xAD\xE8" => "\xE5\x89\x9B", + "\xAD\xE9" => "\xE5\x89\x9D", + "\xAD\xEA" => "\xE5\x8C\xAA", + "\xAD\xEB" => "\xE5\x8D\xBF", + "\xAD\xEC" => "\xE5\x8E\x9F", + "\xAD\xED" => "\xE5\x8E\x9D", + "\xAD\xEE" => "\xE5\x8F\x9F", + "\xAD\xEF" => "\xE5\x93\xA8", + "\xAD\xF0" => "\xE5\x94\x90", + "\xAD\xF1" => "\xE5\x94\x81", + "\xAD\xF2" => "\xE5\x94\xB7", + "\xAD\xF3" => "\xE5\x93\xBC", + "\xAD\xF4" => "\xE5\x93\xA5", + "\xAD\xF5" => "\xE5\x93\xB2", + "\xAD\xF6" => "\xE5\x94\x86", + "\xAD\xF7" => "\xE5\x93\xBA", + "\xAD\xF8" => "\xE5\x94\x94", + "\xAD\xF9" => "\xE5\x93\xA9", + "\xAD\xFA" => "\xE5\x93\xAD", + "\xAD\xFB" => "\xE5\x93\xA1", + "\xAD\xFC" => "\xE5\x94\x89", + "\xAD\xFD" => "\xE5\x93\xAE", + "\xAD\xFE" => "\xE5\x93\xAA", + "\xAE\x40" => "\xE5\x93\xA6", + "\xAE\x41" => "\xE5\x94\xA7", + "\xAE\x42" => "\xE5\x94\x87", + "\xAE\x43" => "\xE5\x93\xBD", + "\xAE\x44" => "\xE5\x94\x8F", + "\xAE\x45" => "\xE5\x9C\x83", + "\xAE\x46" => "\xE5\x9C\x84", + "\xAE\x47" => "\xE5\x9F\x82", + "\xAE\x48" => "\xE5\x9F\x94", + "\xAE\x49" => "\xE5\x9F\x8B", + "\xAE\x4A" => "\xE5\x9F\x83", + "\xAE\x4B" => "\xE5\xA0\x89", + "\xAE\x4C" => "\xE5\xA4\x8F", + "\xAE\x4D" => "\xE5\xA5\x97", + "\xAE\x4E" => "\xE5\xA5\x98", + "\xAE\x4F" => "\xE5\xA5\x9A", + "\xAE\x50" => "\xE5\xA8\x91", + "\xAE\x51" => "\xE5\xA8\x98", + "\xAE\x52" => "\xE5\xA8\x9C", + "\xAE\x53" => "\xE5\xA8\x9F", + "\xAE\x54" => "\xE5\xA8\x9B", + "\xAE\x55" => "\xE5\xA8\x93", + "\xAE\x56" => "\xE5\xA7\xAC", + "\xAE\x57" => "\xE5\xA8\xA0", + "\xAE\x58" => "\xE5\xA8\xA3", + "\xAE\x59" => "\xE5\xA8\xA9", + "\xAE\x5A" => "\xE5\xA8\xA5", + "\xAE\x5B" => "\xE5\xA8\x8C", + "\xAE\x5C" => "\xE5\xA8\x89", + "\xAE\x5D" => "\xE5\xAD\xAB", + "\xAE\x5E" => "\xE5\xB1\x98", + "\xAE\x5F" => "\xE5\xAE\xB0", + "\xAE\x60" => "\xE5\xAE\xB3", + "\xAE\x61" => "\xE5\xAE\xB6", + "\xAE\x62" => "\xE5\xAE\xB4", + "\xAE\x63" => "\xE5\xAE\xAE", + "\xAE\x64" => "\xE5\xAE\xB5", + "\xAE\x65" => "\xE5\xAE\xB9", + "\xAE\x66" => "\xE5\xAE\xB8", + "\xAE\x67" => "\xE5\xB0\x84", + "\xAE\x68" => "\xE5\xB1\x91", + "\xAE\x69" => "\xE5\xB1\x95", + "\xAE\x6A" => "\xE5\xB1\x90", + "\xAE\x6B" => "\xE5\xB3\xAD", + "\xAE\x6C" => "\xE5\xB3\xBD", + "\xAE\x6D" => "\xE5\xB3\xBB", + "\xAE\x6E" => "\xE5\xB3\xAA", + "\xAE\x6F" => "\xE5\xB3\xA8", + "\xAE\x70" => "\xE5\xB3\xB0", + "\xAE\x71" => "\xE5\xB3\xB6", + "\xAE\x72" => "\xE5\xB4\x81", + "\xAE\x73" => "\xE5\xB3\xB4", + "\xAE\x74" => "\xE5\xB7\xAE", + "\xAE\x75" => "\xE5\xB8\xAD", + "\xAE\x76" => "\xE5\xB8\xAB", + "\xAE\x77" => "\xE5\xBA\xAB", + "\xAE\x78" => "\xE5\xBA\xAD", + "\xAE\x79" => "\xE5\xBA\xA7", + "\xAE\x7A" => "\xE5\xBC\xB1", + "\xAE\x7B" => "\xE5\xBE\x92", + "\xAE\x7C" => "\xE5\xBE\x91", + "\xAE\x7D" => "\xE5\xBE\x90", + "\xAE\x7E" => "\xE6\x81\x99", + "\xAE\xA1" => "\xE6\x81\xA3", + "\xAE\xA2" => "\xE6\x81\xA5", + "\xAE\xA3" => "\xE6\x81\x90", + "\xAE\xA4" => "\xE6\x81\x95", + "\xAE\xA5" => "\xE6\x81\xAD", + "\xAE\xA6" => "\xE6\x81\xA9", + "\xAE\xA7" => "\xE6\x81\xAF", + "\xAE\xA8" => "\xE6\x82\x84", + "\xAE\xA9" => "\xE6\x82\x9F", + "\xAE\xAA" => "\xE6\x82\x9A", + "\xAE\xAB" => "\xE6\x82\x8D", + "\xAE\xAC" => "\xE6\x82\x94", + "\xAE\xAD" => "\xE6\x82\x8C", + "\xAE\xAE" => "\xE6\x82\x85", + "\xAE\xAF" => "\xE6\x82\x96", + "\xAE\xB0" => "\xE6\x89\x87", + "\xAE\xB1" => "\xE6\x8B\xB3", + "\xAE\xB2" => "\xE6\x8C\x88", + "\xAE\xB3" => "\xE6\x8B\xBF", + "\xAE\xB4" => "\xE6\x8D\x8E", + "\xAE\xB5" => "\xE6\x8C\xBE", + "\xAE\xB6" => "\xE6\x8C\xAF", + "\xAE\xB7" => "\xE6\x8D\x95", + "\xAE\xB8" => "\xE6\x8D\x82", + "\xAE\xB9" => "\xE6\x8D\x86", + "\xAE\xBA" => "\xE6\x8D\x8F", + "\xAE\xBB" => "\xE6\x8D\x89", + "\xAE\xBC" => "\xE6\x8C\xBA", + "\xAE\xBD" => "\xE6\x8D\x90", + "\xAE\xBE" => "\xE6\x8C\xBD", + "\xAE\xBF" => "\xE6\x8C\xAA", + "\xAE\xC0" => "\xE6\x8C\xAB", + "\xAE\xC1" => "\xE6\x8C\xA8", + "\xAE\xC2" => "\xE6\x8D\x8D", + "\xAE\xC3" => "\xE6\x8D\x8C", + "\xAE\xC4" => "\xE6\x95\x88", + "\xAE\xC5" => "\xE6\x95\x89", + "\xAE\xC6" => "\xE6\x96\x99", + "\xAE\xC7" => "\xE6\x97\x81", + "\xAE\xC8" => "\xE6\x97\x85", + "\xAE\xC9" => "\xE6\x99\x82", + "\xAE\xCA" => "\xE6\x99\x89", + "\xAE\xCB" => "\xE6\x99\x8F", + "\xAE\xCC" => "\xE6\x99\x83", + "\xAE\xCD" => "\xE6\x99\x92", + "\xAE\xCE" => "\xE6\x99\x8C", + "\xAE\xCF" => "\xE6\x99\x85", + "\xAE\xD0" => "\xE6\x99\x81", + "\xAE\xD1" => "\xE6\x9B\xB8", + "\xAE\xD2" => "\xE6\x9C\x94", + "\xAE\xD3" => "\xE6\x9C\x95", + "\xAE\xD4" => "\xE6\x9C\x97", + "\xAE\xD5" => "\xE6\xA0\xA1", + "\xAE\xD6" => "\xE6\xA0\xB8", + "\xAE\xD7" => "\xE6\xA1\x88", + "\xAE\xD8" => "\xE6\xA1\x86", + "\xAE\xD9" => "\xE6\xA1\x93", + "\xAE\xDA" => "\xE6\xA0\xB9", + "\xAE\xDB" => "\xE6\xA1\x82", + "\xAE\xDC" => "\xE6\xA1\x94", + "\xAE\xDD" => "\xE6\xA0\xA9", + "\xAE\xDE" => "\xE6\xA2\xB3", + "\xAE\xDF" => "\xE6\xA0\x97", + "\xAE\xE0" => "\xE6\xA1\x8C", + "\xAE\xE1" => "\xE6\xA1\x91", + "\xAE\xE2" => "\xE6\xA0\xBD", + "\xAE\xE3" => "\xE6\x9F\xB4", + "\xAE\xE4" => "\xE6\xA1\x90", + "\xAE\xE5" => "\xE6\xA1\x80", + "\xAE\xE6" => "\xE6\xA0\xBC", + "\xAE\xE7" => "\xE6\xA1\x83", + "\xAE\xE8" => "\xE6\xA0\xAA", + "\xAE\xE9" => "\xE6\xA1\x85", + "\xAE\xEA" => "\xE6\xA0\x93", + "\xAE\xEB" => "\xE6\xA0\x98", + "\xAE\xEC" => "\xE6\xA1\x81", + "\xAE\xED" => "\xE6\xAE\x8A", + "\xAE\xEE" => "\xE6\xAE\x89", + "\xAE\xEF" => "\xE6\xAE\xB7", + "\xAE\xF0" => "\xE6\xB0\xA3", + "\xAE\xF1" => "\xE6\xB0\xA7", + "\xAE\xF2" => "\xE6\xB0\xA8", + "\xAE\xF3" => "\xE6\xB0\xA6", + "\xAE\xF4" => "\xE6\xB0\xA4", + "\xAE\xF5" => "\xE6\xB3\xB0", + "\xAE\xF6" => "\xE6\xB5\xAA", + "\xAE\xF7" => "\xE6\xB6\x95", + "\xAE\xF8" => "\xE6\xB6\x88", + "\xAE\xF9" => "\xE6\xB6\x87", + "\xAE\xFA" => "\xE6\xB5\xA6", + "\xAE\xFB" => "\xE6\xB5\xB8", + "\xAE\xFC" => "\xE6\xB5\xB7", + "\xAE\xFD" => "\xE6\xB5\x99", + "\xAE\xFE" => "\xE6\xB6\x93", + "\xAF\x40" => "\xE6\xB5\xAC", + "\xAF\x41" => "\xE6\xB6\x89", + "\xAF\x42" => "\xE6\xB5\xAE", + "\xAF\x43" => "\xE6\xB5\x9A", + "\xAF\x44" => "\xE6\xB5\xB4", + "\xAF\x45" => "\xE6\xB5\xA9", + "\xAF\x46" => "\xE6\xB6\x8C", + "\xAF\x47" => "\xE6\xB6\x8A", + "\xAF\x48" => "\xE6\xB5\xB9", + "\xAF\x49" => "\xE6\xB6\x85", + "\xAF\x4A" => "\xE6\xB5\xA5", + "\xAF\x4B" => "\xE6\xB6\x94", + "\xAF\x4C" => "\xE7\x83\x8A", + "\xAF\x4D" => "\xE7\x83\x98", + "\xAF\x4E" => "\xE7\x83\xA4", + "\xAF\x4F" => "\xE7\x83\x99", + "\xAF\x50" => "\xE7\x83\x88", + "\xAF\x51" => "\xE7\x83\x8F", + "\xAF\x52" => "\xE7\x88\xB9", + "\xAF\x53" => "\xE7\x89\xB9", + "\xAF\x54" => "\xE7\x8B\xBC", + "\xAF\x55" => "\xE7\x8B\xB9", + "\xAF\x56" => "\xE7\x8B\xBD", + "\xAF\x57" => "\xE7\x8B\xB8", + "\xAF\x58" => "\xE7\x8B\xB7", + "\xAF\x59" => "\xE7\x8E\x86", + "\xAF\x5A" => "\xE7\x8F\xAD", + "\xAF\x5B" => "\xE7\x90\x89", + "\xAF\x5C" => "\xE7\x8F\xAE", + "\xAF\x5D" => "\xE7\x8F\xA0", + "\xAF\x5E" => "\xE7\x8F\xAA", + "\xAF\x5F" => "\xE7\x8F\x9E", + "\xAF\x60" => "\xE7\x95\x94", + "\xAF\x61" => "\xE7\x95\x9D", + "\xAF\x62" => "\xE7\x95\x9C", + "\xAF\x63" => "\xE7\x95\x9A", + "\xAF\x64" => "\xE7\x95\x99", + "\xAF\x65" => "\xE7\x96\xBE", + "\xAF\x66" => "\xE7\x97\x85", + "\xAF\x67" => "\xE7\x97\x87", + "\xAF\x68" => "\xE7\x96\xB2", + "\xAF\x69" => "\xE7\x96\xB3", + "\xAF\x6A" => "\xE7\x96\xBD", + "\xAF\x6B" => "\xE7\x96\xBC", + "\xAF\x6C" => "\xE7\x96\xB9", + "\xAF\x6D" => "\xE7\x97\x82", + "\xAF\x6E" => "\xE7\x96\xB8", + "\xAF\x6F" => "\xE7\x9A\x8B", + "\xAF\x70" => "\xE7\x9A\xB0", + "\xAF\x71" => "\xE7\x9B\x8A", + "\xAF\x72" => "\xE7\x9B\x8D", + "\xAF\x73" => "\xE7\x9B\x8E", + "\xAF\x74" => "\xE7\x9C\xA9", + "\xAF\x75" => "\xE7\x9C\x9F", + "\xAF\x76" => "\xE7\x9C\xA0", + "\xAF\x77" => "\xE7\x9C\xA8", + "\xAF\x78" => "\xE7\x9F\xA9", + "\xAF\x79" => "\xE7\xA0\xB0", + "\xAF\x7A" => "\xE7\xA0\xA7", + "\xAF\x7B" => "\xE7\xA0\xB8", + "\xAF\x7C" => "\xE7\xA0\x9D", + "\xAF\x7D" => "\xE7\xA0\xB4", + "\xAF\x7E" => "\xE7\xA0\xB7", + "\xAF\xA1" => "\xE7\xA0\xA5", + "\xAF\xA2" => "\xE7\xA0\xAD", + "\xAF\xA3" => "\xE7\xA0\xA0", + "\xAF\xA4" => "\xE7\xA0\x9F", + "\xAF\xA5" => "\xE7\xA0\xB2", + "\xAF\xA6" => "\xE7\xA5\x95", + "\xAF\xA7" => "\xE7\xA5\x90", + "\xAF\xA8" => "\xE7\xA5\xA0", + "\xAF\xA9" => "\xE7\xA5\x9F", + "\xAF\xAA" => "\xE7\xA5\x96", + "\xAF\xAB" => "\xE7\xA5\x9E", + "\xAF\xAC" => "\xE7\xA5\x9D", + "\xAF\xAD" => "\xE7\xA5\x97", + "\xAF\xAE" => "\xE7\xA5\x9A", + "\xAF\xAF" => "\xE7\xA7\xA4", + "\xAF\xB0" => "\xE7\xA7\xA3", + "\xAF\xB1" => "\xE7\xA7\xA7", + "\xAF\xB2" => "\xE7\xA7\x9F", + "\xAF\xB3" => "\xE7\xA7\xA6", + "\xAF\xB4" => "\xE7\xA7\xA9", + "\xAF\xB5" => "\xE7\xA7\x98", + "\xAF\xB6" => "\xE7\xAA\x84", + "\xAF\xB7" => "\xE7\xAA\x88", + "\xAF\xB8" => "\xE7\xAB\x99", + "\xAF\xB9" => "\xE7\xAC\x86", + "\xAF\xBA" => "\xE7\xAC\x91", + "\xAF\xBB" => "\xE7\xB2\x89", + "\xAF\xBC" => "\xE7\xB4\xA1", + "\xAF\xBD" => "\xE7\xB4\x97", + "\xAF\xBE" => "\xE7\xB4\x8B", + "\xAF\xBF" => "\xE7\xB4\x8A", + "\xAF\xC0" => "\xE7\xB4\xA0", + "\xAF\xC1" => "\xE7\xB4\xA2", + "\xAF\xC2" => "\xE7\xB4\x94", + "\xAF\xC3" => "\xE7\xB4\x90", + "\xAF\xC4" => "\xE7\xB4\x95", + "\xAF\xC5" => "\xE7\xB4\x9A", + "\xAF\xC6" => "\xE7\xB4\x9C", + "\xAF\xC7" => "\xE7\xB4\x8D", + "\xAF\xC8" => "\xE7\xB4\x99", + "\xAF\xC9" => "\xE7\xB4\x9B", + "\xAF\xCA" => "\xE7\xBC\xBA", + "\xAF\xCB" => "\xE7\xBD\x9F", + "\xAF\xCC" => "\xE7\xBE\x94", + "\xAF\xCD" => "\xE7\xBF\x85", + "\xAF\xCE" => "\xE7\xBF\x81", + "\xAF\xCF" => "\xE8\x80\x86", + "\xAF\xD0" => "\xE8\x80\x98", + "\xAF\xD1" => "\xE8\x80\x95", + "\xAF\xD2" => "\xE8\x80\x99", + "\xAF\xD3" => "\xE8\x80\x97", + "\xAF\xD4" => "\xE8\x80\xBD", + "\xAF\xD5" => "\xE8\x80\xBF", + "\xAF\xD6" => "\xE8\x83\xB1", + "\xAF\xD7" => "\xE8\x84\x82", + "\xAF\xD8" => "\xE8\x83\xB0", + "\xAF\xD9" => "\xE8\x84\x85", + "\xAF\xDA" => "\xE8\x83\xAD", + "\xAF\xDB" => "\xE8\x83\xB4", + "\xAF\xDC" => "\xE8\x84\x86", + "\xAF\xDD" => "\xE8\x83\xB8", + "\xAF\xDE" => "\xE8\x83\xB3", + "\xAF\xDF" => "\xE8\x84\x88", + "\xAF\xE0" => "\xE8\x83\xBD", + "\xAF\xE1" => "\xE8\x84\x8A", + "\xAF\xE2" => "\xE8\x83\xBC", + "\xAF\xE3" => "\xE8\x83\xAF", + "\xAF\xE4" => "\xE8\x87\xAD", + "\xAF\xE5" => "\xE8\x87\xAC", + "\xAF\xE6" => "\xE8\x88\x80", + "\xAF\xE7" => "\xE8\x88\x90", + "\xAF\xE8" => "\xE8\x88\xAA", + "\xAF\xE9" => "\xE8\x88\xAB", + "\xAF\xEA" => "\xE8\x88\xA8", + "\xAF\xEB" => "\xE8\x88\xAC", + "\xAF\xEC" => "\xE8\x8A\xBB", + "\xAF\xED" => "\xE8\x8C\xAB", + "\xAF\xEE" => "\xE8\x8D\x92", + "\xAF\xEF" => "\xE8\x8D\x94", + "\xAF\xF0" => "\xE8\x8D\x8A", + "\xAF\xF1" => "\xE8\x8C\xB8", + "\xAF\xF2" => "\xE8\x8D\x90", + "\xAF\xF3" => "\xE8\x8D\x89", + "\xAF\xF4" => "\xE8\x8C\xB5", + "\xAF\xF5" => "\xE8\x8C\xB4", + "\xAF\xF6" => "\xE8\x8D\x8F", + "\xAF\xF7" => "\xE8\x8C\xB2", + "\xAF\xF8" => "\xE8\x8C\xB9", + "\xAF\xF9" => "\xE8\x8C\xB6", + "\xAF\xFA" => "\xE8\x8C\x97", + "\xAF\xFB" => "\xE8\x8D\x80", + "\xAF\xFC" => "\xE8\x8C\xB1", + "\xAF\xFD" => "\xE8\x8C\xA8", + "\xAF\xFE" => "\xE8\x8D\x83", + "\xB0\x40" => "\xE8\x99\x94", + "\xB0\x41" => "\xE8\x9A\x8A", + "\xB0\x42" => "\xE8\x9A\xAA", + "\xB0\x43" => "\xE8\x9A\x93", + "\xB0\x44" => "\xE8\x9A\xA4", + "\xB0\x45" => "\xE8\x9A\xA9", + "\xB0\x46" => "\xE8\x9A\x8C", + "\xB0\x47" => "\xE8\x9A\xA3", + "\xB0\x48" => "\xE8\x9A\x9C", + "\xB0\x49" => "\xE8\xA1\xB0", + "\xB0\x4A" => "\xE8\xA1\xB7", + "\xB0\x4B" => "\xE8\xA2\x81", + "\xB0\x4C" => "\xE8\xA2\x82", + "\xB0\x4D" => "\xE8\xA1\xBD", + "\xB0\x4E" => "\xE8\xA1\xB9", + "\xB0\x4F" => "\xE8\xA8\x98", + "\xB0\x50" => "\xE8\xA8\x90", + "\xB0\x51" => "\xE8\xA8\x8E", + "\xB0\x52" => "\xE8\xA8\x8C", + "\xB0\x53" => "\xE8\xA8\x95", + "\xB0\x54" => "\xE8\xA8\x8A", + "\xB0\x55" => "\xE8\xA8\x97", + "\xB0\x56" => "\xE8\xA8\x93", + "\xB0\x57" => "\xE8\xA8\x96", + "\xB0\x58" => "\xE8\xA8\x8F", + "\xB0\x59" => "\xE8\xA8\x91", + "\xB0\x5A" => "\xE8\xB1\x88", + "\xB0\x5B" => "\xE8\xB1\xBA", + "\xB0\x5C" => "\xE8\xB1\xB9", + "\xB0\x5D" => "\xE8\xB2\xA1", + "\xB0\x5E" => "\xE8\xB2\xA2", + "\xB0\x5F" => "\xE8\xB5\xB7", + "\xB0\x60" => "\xE8\xBA\xAC", + "\xB0\x61" => "\xE8\xBB\x92", + "\xB0\x62" => "\xE8\xBB\x94", + "\xB0\x63" => "\xE8\xBB\x8F", + "\xB0\x64" => "\xE8\xBE\xB1", + "\xB0\x65" => "\xE9\x80\x81", + "\xB0\x66" => "\xE9\x80\x86", + "\xB0\x67" => "\xE8\xBF\xB7", + "\xB0\x68" => "\xE9\x80\x80", + "\xB0\x69" => "\xE8\xBF\xBA", + "\xB0\x6A" => "\xE8\xBF\xB4", + "\xB0\x6B" => "\xE9\x80\x83", + "\xB0\x6C" => "\xE8\xBF\xBD", + "\xB0\x6D" => "\xE9\x80\x85", + "\xB0\x6E" => "\xE8\xBF\xB8", + "\xB0\x6F" => "\xE9\x82\x95", + "\xB0\x70" => "\xE9\x83\xA1", + "\xB0\x71" => "\xE9\x83\x9D", + "\xB0\x72" => "\xE9\x83\xA2", + "\xB0\x73" => "\xE9\x85\x92", + "\xB0\x74" => "\xE9\x85\x8D", + "\xB0\x75" => "\xE9\x85\x8C", + "\xB0\x76" => "\xE9\x87\x98", + "\xB0\x77" => "\xE9\x87\x9D", + "\xB0\x78" => "\xE9\x87\x97", + "\xB0\x79" => "\xE9\x87\x9C", + "\xB0\x7A" => "\xE9\x87\x99", + "\xB0\x7B" => "\xE9\x96\x83", + "\xB0\x7C" => "\xE9\x99\xA2", + "\xB0\x7D" => "\xE9\x99\xA3", + "\xB0\x7E" => "\xE9\x99\xA1", + "\xB0\xA1" => "\xE9\x99\x9B", + "\xB0\xA2" => "\xE9\x99\x9D", + "\xB0\xA3" => "\xE9\x99\xA4", + "\xB0\xA4" => "\xE9\x99\x98", + "\xB0\xA5" => "\xE9\x99\x9E", + "\xB0\xA6" => "\xE9\x9A\xBB", + "\xB0\xA7" => "\xE9\xA3\xA2", + "\xB0\xA8" => "\xE9\xA6\xAC", + "\xB0\xA9" => "\xE9\xAA\xA8", + "\xB0\xAA" => "\xE9\xAB\x98", + "\xB0\xAB" => "\xE9\xAC\xA5", + "\xB0\xAC" => "\xE9\xAC\xB2", + "\xB0\xAD" => "\xE9\xAC\xBC", + "\xB0\xAE" => "\xE4\xB9\xBE", + "\xB0\xAF" => "\xE5\x81\xBA", + "\xB0\xB0" => "\xE5\x81\xBD", + "\xB0\xB1" => "\xE5\x81\x9C", + "\xB0\xB2" => "\xE5\x81\x87", + "\xB0\xB3" => "\xE5\x81\x83", + "\xB0\xB4" => "\xE5\x81\x8C", + "\xB0\xB5" => "\xE5\x81\x9A", + "\xB0\xB6" => "\xE5\x81\x89", + "\xB0\xB7" => "\xE5\x81\xA5", + "\xB0\xB8" => "\xE5\x81\xB6", + "\xB0\xB9" => "\xE5\x81\x8E", + "\xB0\xBA" => "\xE5\x81\x95", + "\xB0\xBB" => "\xE5\x81\xB5", + "\xB0\xBC" => "\xE5\x81\xB4", + "\xB0\xBD" => "\xE5\x81\xB7", + "\xB0\xBE" => "\xE5\x81\x8F", + "\xB0\xBF" => "\xE5\x80\x8F", + "\xB0\xC0" => "\xE5\x81\xAF", + "\xB0\xC1" => "\xE5\x81\xAD", + "\xB0\xC2" => "\xE5\x85\x9C", + "\xB0\xC3" => "\xE5\x86\x95", + "\xB0\xC4" => "\xE5\x87\xB0", + "\xB0\xC5" => "\xE5\x89\xAA", + "\xB0\xC6" => "\xE5\x89\xAF", + "\xB0\xC7" => "\xE5\x8B\x92", + "\xB0\xC8" => "\xE5\x8B\x99", + "\xB0\xC9" => "\xE5\x8B\x98", + "\xB0\xCA" => "\xE5\x8B\x95", + "\xB0\xCB" => "\xE5\x8C\x90", + "\xB0\xCC" => "\xE5\x8C\x8F", + "\xB0\xCD" => "\xE5\x8C\x99", + "\xB0\xCE" => "\xE5\x8C\xBF", + "\xB0\xCF" => "\xE5\x8D\x80", + "\xB0\xD0" => "\xE5\x8C\xBE", + "\xB0\xD1" => "\xE5\x8F\x83", + "\xB0\xD2" => "\xE6\x9B\xBC", + "\xB0\xD3" => "\xE5\x95\x86", + "\xB0\xD4" => "\xE5\x95\xAA", + "\xB0\xD5" => "\xE5\x95\xA6", + "\xB0\xD6" => "\xE5\x95\x84", + "\xB0\xD7" => "\xE5\x95\x9E", + "\xB0\xD8" => "\xE5\x95\xA1", + "\xB0\xD9" => "\xE5\x95\x83", + "\xB0\xDA" => "\xE5\x95\x8A", + "\xB0\xDB" => "\xE5\x94\xB1", + "\xB0\xDC" => "\xE5\x95\x96", + "\xB0\xDD" => "\xE5\x95\x8F", + "\xB0\xDE" => "\xE5\x95\x95", + "\xB0\xDF" => "\xE5\x94\xAF", + "\xB0\xE0" => "\xE5\x95\xA4", + "\xB0\xE1" => "\xE5\x94\xB8", + "\xB0\xE2" => "\xE5\x94\xAE", + "\xB0\xE3" => "\xE5\x95\x9C", + "\xB0\xE4" => "\xE5\x94\xAC", + "\xB0\xE5" => "\xE5\x95\xA3", + "\xB0\xE6" => "\xE5\x94\xB3", + "\xB0\xE7" => "\xE5\x95\x81", + "\xB0\xE8" => "\xE5\x95\x97", + "\xB0\xE9" => "\xE5\x9C\x88", + "\xB0\xEA" => "\xE5\x9C\x8B", + "\xB0\xEB" => "\xE5\x9C\x89", + "\xB0\xEC" => "\xE5\x9F\x9F", + "\xB0\xED" => "\xE5\xA0\x85", + "\xB0\xEE" => "\xE5\xA0\x8A", + "\xB0\xEF" => "\xE5\xA0\x86", + "\xB0\xF0" => "\xE5\x9F\xA0", + "\xB0\xF1" => "\xE5\x9F\xA4", + "\xB0\xF2" => "\xE5\x9F\xBA", + "\xB0\xF3" => "\xE5\xA0\x82", + "\xB0\xF4" => "\xE5\xA0\xB5", + "\xB0\xF5" => "\xE5\x9F\xB7", + "\xB0\xF6" => "\xE5\x9F\xB9", + "\xB0\xF7" => "\xE5\xA4\xA0", + "\xB0\xF8" => "\xE5\xA5\xA2", + "\xB0\xF9" => "\xE5\xA8\xB6", + "\xB0\xFA" => "\xE5\xA9\x81", + "\xB0\xFB" => "\xE5\xA9\x89", + "\xB0\xFC" => "\xE5\xA9\xA6", + "\xB0\xFD" => "\xE5\xA9\xAA", + "\xB0\xFE" => "\xE5\xA9\x80", + "\xB1\x40" => "\xE5\xA8\xBC", + "\xB1\x41" => "\xE5\xA9\xA2", + "\xB1\x42" => "\xE5\xA9\x9A", + "\xB1\x43" => "\xE5\xA9\x86", + "\xB1\x44" => "\xE5\xA9\x8A", + "\xB1\x45" => "\xE5\xAD\xB0", + "\xB1\x46" => "\xE5\xAF\x87", + "\xB1\x47" => "\xE5\xAF\x85", + "\xB1\x48" => "\xE5\xAF\x84", + "\xB1\x49" => "\xE5\xAF\x82", + "\xB1\x4A" => "\xE5\xAE\xBF", + "\xB1\x4B" => "\xE5\xAF\x86", + "\xB1\x4C" => "\xE5\xB0\x89", + "\xB1\x4D" => "\xE5\xB0\x88", + "\xB1\x4E" => "\xE5\xB0\x87", + "\xB1\x4F" => "\xE5\xB1\xA0", + "\xB1\x50" => "\xE5\xB1\x9C", + "\xB1\x51" => "\xE5\xB1\x9D", + "\xB1\x52" => "\xE5\xB4\x87", + "\xB1\x53" => "\xE5\xB4\x86", + "\xB1\x54" => "\xE5\xB4\x8E", + "\xB1\x55" => "\xE5\xB4\x9B", + "\xB1\x56" => "\xE5\xB4\x96", + "\xB1\x57" => "\xE5\xB4\xA2", + "\xB1\x58" => "\xE5\xB4\x91", + "\xB1\x59" => "\xE5\xB4\xA9", + "\xB1\x5A" => "\xE5\xB4\x94", + "\xB1\x5B" => "\xE5\xB4\x99", + "\xB1\x5C" => "\xE5\xB4\xA4", + "\xB1\x5D" => "\xE5\xB4\xA7", + "\xB1\x5E" => "\xE5\xB4\x97", + "\xB1\x5F" => "\xE5\xB7\xA2", + "\xB1\x60" => "\xE5\xB8\xB8", + "\xB1\x61" => "\xE5\xB8\xB6", + "\xB1\x62" => "\xE5\xB8\xB3", + "\xB1\x63" => "\xE5\xB8\xB7", + "\xB1\x64" => "\xE5\xBA\xB7", + "\xB1\x65" => "\xE5\xBA\xB8", + "\xB1\x66" => "\xE5\xBA\xB6", + "\xB1\x67" => "\xE5\xBA\xB5", + "\xB1\x68" => "\xE5\xBA\xBE", + "\xB1\x69" => "\xE5\xBC\xB5", + "\xB1\x6A" => "\xE5\xBC\xB7", + "\xB1\x6B" => "\xE5\xBD\x97", + "\xB1\x6C" => "\xE5\xBD\xAC", + "\xB1\x6D" => "\xE5\xBD\xA9", + "\xB1\x6E" => "\xE5\xBD\xAB", + "\xB1\x6F" => "\xE5\xBE\x97", + "\xB1\x70" => "\xE5\xBE\x99", + "\xB1\x71" => "\xE5\xBE\x9E", + "\xB1\x72" => "\xE5\xBE\x98", + "\xB1\x73" => "\xE5\xBE\xA1", + "\xB1\x74" => "\xE5\xBE\xA0", + "\xB1\x75" => "\xE5\xBE\x9C", + "\xB1\x76" => "\xE6\x81\xBF", + "\xB1\x77" => "\xE6\x82\xA3", + "\xB1\x78" => "\xE6\x82\x89", + "\xB1\x79" => "\xE6\x82\xA0", + "\xB1\x7A" => "\xE6\x82\xA8", + "\xB1\x7B" => "\xE6\x83\x8B", + "\xB1\x7C" => "\xE6\x82\xB4", + "\xB1\x7D" => "\xE6\x83\xA6", + "\xB1\x7E" => "\xE6\x82\xBD", + "\xB1\xA1" => "\xE6\x83\x85", + "\xB1\xA2" => "\xE6\x82\xBB", + "\xB1\xA3" => "\xE6\x82\xB5", + "\xB1\xA4" => "\xE6\x83\x9C", + "\xB1\xA5" => "\xE6\x82\xBC", + "\xB1\xA6" => "\xE6\x83\x98", + "\xB1\xA7" => "\xE6\x83\x95", + "\xB1\xA8" => "\xE6\x83\x86", + "\xB1\xA9" => "\xE6\x83\x9F", + "\xB1\xAA" => "\xE6\x82\xB8", + "\xB1\xAB" => "\xE6\x83\x9A", + "\xB1\xAC" => "\xE6\x83\x87", + "\xB1\xAD" => "\xE6\x88\x9A", + "\xB1\xAE" => "\xE6\x88\x9B", + "\xB1\xAF" => "\xE6\x89\x88", + "\xB1\xB0" => "\xE6\x8E\xA0", + "\xB1\xB1" => "\xE6\x8E\xA7", + "\xB1\xB2" => "\xE6\x8D\xB2", + "\xB1\xB3" => "\xE6\x8E\x96", + "\xB1\xB4" => "\xE6\x8E\xA2", + "\xB1\xB5" => "\xE6\x8E\xA5", + "\xB1\xB6" => "\xE6\x8D\xB7", + "\xB1\xB7" => "\xE6\x8D\xA7", + "\xB1\xB8" => "\xE6\x8E\x98", + "\xB1\xB9" => "\xE6\x8E\xAA", + "\xB1\xBA" => "\xE6\x8D\xB1", + "\xB1\xBB" => "\xE6\x8E\xA9", + "\xB1\xBC" => "\xE6\x8E\x89", + "\xB1\xBD" => "\xE6\x8E\x83", + "\xB1\xBE" => "\xE6\x8E\x9B", + "\xB1\xBF" => "\xE6\x8D\xAB", + "\xB1\xC0" => "\xE6\x8E\xA8", + "\xB1\xC1" => "\xE6\x8E\x84", + "\xB1\xC2" => "\xE6\x8E\x88", + "\xB1\xC3" => "\xE6\x8E\x99", + "\xB1\xC4" => "\xE6\x8E\xA1", + "\xB1\xC5" => "\xE6\x8E\xAC", + "\xB1\xC6" => "\xE6\x8E\x92", + "\xB1\xC7" => "\xE6\x8E\x8F", + "\xB1\xC8" => "\xE6\x8E\x80", + "\xB1\xC9" => "\xE6\x8D\xBB", + "\xB1\xCA" => "\xE6\x8D\xA9", + "\xB1\xCB" => "\xE6\x8D\xA8", + "\xB1\xCC" => "\xE6\x8D\xBA", + "\xB1\xCD" => "\xE6\x95\x9D", + "\xB1\xCE" => "\xE6\x95\x96", + "\xB1\xCF" => "\xE6\x95\x91", + "\xB1\xD0" => "\xE6\x95\x99", + "\xB1\xD1" => "\xE6\x95\x97", + "\xB1\xD2" => "\xE5\x95\x9F", + "\xB1\xD3" => "\xE6\x95\x8F", + "\xB1\xD4" => "\xE6\x95\x98", + "\xB1\xD5" => "\xE6\x95\x95", + "\xB1\xD6" => "\xE6\x95\x94", + "\xB1\xD7" => "\xE6\x96\x9C", + "\xB1\xD8" => "\xE6\x96\x9B", + "\xB1\xD9" => "\xE6\x96\xAC", + "\xB1\xDA" => "\xE6\x97\x8F", + "\xB1\xDB" => "\xE6\x97\x8B", + "\xB1\xDC" => "\xE6\x97\x8C", + "\xB1\xDD" => "\xE6\x97\x8E", + "\xB1\xDE" => "\xE6\x99\x9D", + "\xB1\xDF" => "\xE6\x99\x9A", + "\xB1\xE0" => "\xE6\x99\xA4", + "\xB1\xE1" => "\xE6\x99\xA8", + "\xB1\xE2" => "\xE6\x99\xA6", + "\xB1\xE3" => "\xE6\x99\x9E", + "\xB1\xE4" => "\xE6\x9B\xB9", + "\xB1\xE5" => "\xE5\x8B\x97", + "\xB1\xE6" => "\xE6\x9C\x9B", + "\xB1\xE7" => "\xE6\xA2\x81", + "\xB1\xE8" => "\xE6\xA2\xAF", + "\xB1\xE9" => "\xE6\xA2\xA2", + "\xB1\xEA" => "\xE6\xA2\x93", + "\xB1\xEB" => "\xE6\xA2\xB5", + "\xB1\xEC" => "\xE6\xA1\xBF", + "\xB1\xED" => "\xE6\xA1\xB6", + "\xB1\xEE" => "\xE6\xA2\xB1", + "\xB1\xEF" => "\xE6\xA2\xA7", + "\xB1\xF0" => "\xE6\xA2\x97", + "\xB1\xF1" => "\xE6\xA2\xB0", + "\xB1\xF2" => "\xE6\xA2\x83", + "\xB1\xF3" => "\xE6\xA3\x84", + "\xB1\xF4" => "\xE6\xA2\xAD", + "\xB1\xF5" => "\xE6\xA2\x86", + "\xB1\xF6" => "\xE6\xA2\x85", + "\xB1\xF7" => "\xE6\xA2\x94", + "\xB1\xF8" => "\xE6\xA2\x9D", + "\xB1\xF9" => "\xE6\xA2\xA8", + "\xB1\xFA" => "\xE6\xA2\x9F", + "\xB1\xFB" => "\xE6\xA2\xA1", + "\xB1\xFC" => "\xE6\xA2\x82", + "\xB1\xFD" => "\xE6\xAC\xB2", + "\xB1\xFE" => "\xE6\xAE\xBA", + "\xB2\x40" => "\xE6\xAF\xAB", + "\xB2\x41" => "\xE6\xAF\xAC", + "\xB2\x42" => "\xE6\xB0\xAB", + "\xB2\x43" => "\xE6\xB6\x8E", + "\xB2\x44" => "\xE6\xB6\xBC", + "\xB2\x45" => "\xE6\xB7\xB3", + "\xB2\x46" => "\xE6\xB7\x99", + "\xB2\x47" => "\xE6\xB6\xB2", + "\xB2\x48" => "\xE6\xB7\xA1", + "\xB2\x49" => "\xE6\xB7\x8C", + "\xB2\x4A" => "\xE6\xB7\xA4", + "\xB2\x4B" => "\xE6\xB7\xBB", + "\xB2\x4C" => "\xE6\xB7\xBA", + "\xB2\x4D" => "\xE6\xB8\x85", + "\xB2\x4E" => "\xE6\xB7\x87", + "\xB2\x4F" => "\xE6\xB7\x8B", + "\xB2\x50" => "\xE6\xB6\xAF", + "\xB2\x51" => "\xE6\xB7\x91", + "\xB2\x52" => "\xE6\xB6\xAE", + "\xB2\x53" => "\xE6\xB7\x9E", + "\xB2\x54" => "\xE6\xB7\xB9", + "\xB2\x55" => "\xE6\xB6\xB8", + "\xB2\x56" => "\xE6\xB7\xB7", + "\xB2\x57" => "\xE6\xB7\xB5", + "\xB2\x58" => "\xE6\xB7\x85", + "\xB2\x59" => "\xE6\xB7\x92", + "\xB2\x5A" => "\xE6\xB8\x9A", + "\xB2\x5B" => "\xE6\xB6\xB5", + "\xB2\x5C" => "\xE6\xB7\x9A", + "\xB2\x5D" => "\xE6\xB7\xAB", + "\xB2\x5E" => "\xE6\xB7\x98", + "\xB2\x5F" => "\xE6\xB7\xAA", + "\xB2\x60" => "\xE6\xB7\xB1", + "\xB2\x61" => "\xE6\xB7\xAE", + "\xB2\x62" => "\xE6\xB7\xA8", + "\xB2\x63" => "\xE6\xB7\x86", + "\xB2\x64" => "\xE6\xB7\x84", + "\xB2\x65" => "\xE6\xB6\xAA", + "\xB2\x66" => "\xE6\xB7\xAC", + "\xB2\x67" => "\xE6\xB6\xBF", + "\xB2\x68" => "\xE6\xB7\xA6", + "\xB2\x69" => "\xE7\x83\xB9", + "\xB2\x6A" => "\xE7\x84\x89", + "\xB2\x6B" => "\xE7\x84\x8A", + "\xB2\x6C" => "\xE7\x83\xBD", + "\xB2\x6D" => "\xE7\x83\xAF", + "\xB2\x6E" => "\xE7\x88\xBD", + "\xB2\x6F" => "\xE7\x89\xBD", + "\xB2\x70" => "\xE7\x8A\x81", + "\xB2\x71" => "\xE7\x8C\x9C", + "\xB2\x72" => "\xE7\x8C\x9B", + "\xB2\x73" => "\xE7\x8C\x96", + "\xB2\x74" => "\xE7\x8C\x93", + "\xB2\x75" => "\xE7\x8C\x99", + "\xB2\x76" => "\xE7\x8E\x87", + "\xB2\x77" => "\xE7\x90\x85", + "\xB2\x78" => "\xE7\x90\x8A", + "\xB2\x79" => "\xE7\x90\x83", + "\xB2\x7A" => "\xE7\x90\x86", + "\xB2\x7B" => "\xE7\x8F\xBE", + "\xB2\x7C" => "\xE7\x90\x8D", + "\xB2\x7D" => "\xE7\x93\xA0", + "\xB2\x7E" => "\xE7\x93\xB6", + "\xB2\xA1" => "\xE7\x93\xB7", + "\xB2\xA2" => "\xE7\x94\x9C", + "\xB2\xA3" => "\xE7\x94\xA2", + "\xB2\xA4" => "\xE7\x95\xA5", + "\xB2\xA5" => "\xE7\x95\xA6", + "\xB2\xA6" => "\xE7\x95\xA2", + "\xB2\xA7" => "\xE7\x95\xB0", + "\xB2\xA8" => "\xE7\x96\x8F", + "\xB2\xA9" => "\xE7\x97\x94", + "\xB2\xAA" => "\xE7\x97\x95", + "\xB2\xAB" => "\xE7\x96\xB5", + "\xB2\xAC" => "\xE7\x97\x8A", + "\xB2\xAD" => "\xE7\x97\x8D", + "\xB2\xAE" => "\xE7\x9A\x8E", + "\xB2\xAF" => "\xE7\x9B\x94", + "\xB2\xB0" => "\xE7\x9B\x92", + "\xB2\xB1" => "\xE7\x9B\x9B", + "\xB2\xB2" => "\xE7\x9C\xB7", + "\xB2\xB3" => "\xE7\x9C\xBE", + "\xB2\xB4" => "\xE7\x9C\xBC", + "\xB2\xB5" => "\xE7\x9C\xB6", + "\xB2\xB6" => "\xE7\x9C\xB8", + "\xB2\xB7" => "\xE7\x9C\xBA", + "\xB2\xB8" => "\xE7\xA1\xAB", + "\xB2\xB9" => "\xE7\xA1\x83", + "\xB2\xBA" => "\xE7\xA1\x8E", + "\xB2\xBB" => "\xE7\xA5\xA5", + "\xB2\xBC" => "\xE7\xA5\xA8", + "\xB2\xBD" => "\xE7\xA5\xAD", + "\xB2\xBE" => "\xE7\xA7\xBB", + "\xB2\xBF" => "\xE7\xAA\x92", + "\xB2\xC0" => "\xE7\xAA\x95", + "\xB2\xC1" => "\xE7\xAC\xA0", + "\xB2\xC2" => "\xE7\xAC\xA8", + "\xB2\xC3" => "\xE7\xAC\x9B", + "\xB2\xC4" => "\xE7\xAC\xAC", + "\xB2\xC5" => "\xE7\xAC\xA6", + "\xB2\xC6" => "\xE7\xAC\x99", + "\xB2\xC7" => "\xE7\xAC\x9E", + "\xB2\xC8" => "\xE7\xAC\xAE", + "\xB2\xC9" => "\xE7\xB2\x92", + "\xB2\xCA" => "\xE7\xB2\x97", + "\xB2\xCB" => "\xE7\xB2\x95", + "\xB2\xCC" => "\xE7\xB5\x86", + "\xB2\xCD" => "\xE7\xB5\x83", + "\xB2\xCE" => "\xE7\xB5\xB1", + "\xB2\xCF" => "\xE7\xB4\xAE", + "\xB2\xD0" => "\xE7\xB4\xB9", + "\xB2\xD1" => "\xE7\xB4\xBC", + "\xB2\xD2" => "\xE7\xB5\x80", + "\xB2\xD3" => "\xE7\xB4\xB0", + "\xB2\xD4" => "\xE7\xB4\xB3", + "\xB2\xD5" => "\xE7\xB5\x84", + "\xB2\xD6" => "\xE7\xB4\xAF", + "\xB2\xD7" => "\xE7\xB5\x82", + "\xB2\xD8" => "\xE7\xB4\xB2", + "\xB2\xD9" => "\xE7\xB4\xB1", + "\xB2\xDA" => "\xE7\xBC\xBD", + "\xB2\xDB" => "\xE7\xBE\x9E", + "\xB2\xDC" => "\xE7\xBE\x9A", + "\xB2\xDD" => "\xE7\xBF\x8C", + "\xB2\xDE" => "\xE7\xBF\x8E", + "\xB2\xDF" => "\xE7\xBF\x92", + "\xB2\xE0" => "\xE8\x80\x9C", + "\xB2\xE1" => "\xE8\x81\x8A", + "\xB2\xE2" => "\xE8\x81\x86", + "\xB2\xE3" => "\xE8\x84\xAF", + "\xB2\xE4" => "\xE8\x84\x96", + "\xB2\xE5" => "\xE8\x84\xA3", + "\xB2\xE6" => "\xE8\x84\xAB", + "\xB2\xE7" => "\xE8\x84\xA9", + "\xB2\xE8" => "\xE8\x84\xB0", + "\xB2\xE9" => "\xE8\x84\xA4", + "\xB2\xEA" => "\xE8\x88\x82", + "\xB2\xEB" => "\xE8\x88\xB5", + "\xB2\xEC" => "\xE8\x88\xB7", + "\xB2\xED" => "\xE8\x88\xB6", + "\xB2\xEE" => "\xE8\x88\xB9", + "\xB2\xEF" => "\xE8\x8E\x8E", + "\xB2\xF0" => "\xE8\x8E\x9E", + "\xB2\xF1" => "\xE8\x8E\x98", + "\xB2\xF2" => "\xE8\x8D\xB8", + "\xB2\xF3" => "\xE8\x8E\xA2", + "\xB2\xF4" => "\xE8\x8E\x96", + "\xB2\xF5" => "\xE8\x8E\xBD", + "\xB2\xF6" => "\xE8\x8E\xAB", + "\xB2\xF7" => "\xE8\x8E\x92", + "\xB2\xF8" => "\xE8\x8E\x8A", + "\xB2\xF9" => "\xE8\x8E\x93", + "\xB2\xFA" => "\xE8\x8E\x89", + "\xB2\xFB" => "\xE8\x8E\xA0", + "\xB2\xFC" => "\xE8\x8D\xB7", + "\xB2\xFD" => "\xE8\x8D\xBB", + "\xB2\xFE" => "\xE8\x8D\xBC", + "\xB3\x40" => "\xE8\x8E\x86", + "\xB3\x41" => "\xE8\x8E\xA7", + "\xB3\x42" => "\xE8\x99\x95", + "\xB3\x43" => "\xE5\xBD\xAA", + "\xB3\x44" => "\xE8\x9B\x87", + "\xB3\x45" => "\xE8\x9B\x80", + "\xB3\x46" => "\xE8\x9A\xB6", + "\xB3\x47" => "\xE8\x9B\x84", + "\xB3\x48" => "\xE8\x9A\xB5", + "\xB3\x49" => "\xE8\x9B\x86", + "\xB3\x4A" => "\xE8\x9B\x8B", + "\xB3\x4B" => "\xE8\x9A\xB1", + "\xB3\x4C" => "\xE8\x9A\xAF", + "\xB3\x4D" => "\xE8\x9B\x89", + "\xB3\x4E" => "\xE8\xA1\x93", + "\xB3\x4F" => "\xE8\xA2\x9E", + "\xB3\x50" => "\xE8\xA2\x88", + "\xB3\x51" => "\xE8\xA2\xAB", + "\xB3\x52" => "\xE8\xA2\x92", + "\xB3\x53" => "\xE8\xA2\x96", + "\xB3\x54" => "\xE8\xA2\x8D", + "\xB3\x55" => "\xE8\xA2\x8B", + "\xB3\x56" => "\xE8\xA6\x93", + "\xB3\x57" => "\xE8\xA6\x8F", + "\xB3\x58" => "\xE8\xA8\xAA", + "\xB3\x59" => "\xE8\xA8\x9D", + "\xB3\x5A" => "\xE8\xA8\xA3", + "\xB3\x5B" => "\xE8\xA8\xA5", + "\xB3\x5C" => "\xE8\xA8\xB1", + "\xB3\x5D" => "\xE8\xA8\xAD", + "\xB3\x5E" => "\xE8\xA8\x9F", + "\xB3\x5F" => "\xE8\xA8\x9B", + "\xB3\x60" => "\xE8\xA8\xA2", + "\xB3\x61" => "\xE8\xB1\x89", + "\xB3\x62" => "\xE8\xB1\x9A", + "\xB3\x63" => "\xE8\xB2\xA9", + "\xB3\x64" => "\xE8\xB2\xAC", + "\xB3\x65" => "\xE8\xB2\xAB", + "\xB3\x66" => "\xE8\xB2\xA8", + "\xB3\x67" => "\xE8\xB2\xAA", + "\xB3\x68" => "\xE8\xB2\xA7", + "\xB3\x69" => "\xE8\xB5\xA7", + "\xB3\x6A" => "\xE8\xB5\xA6", + "\xB3\x6B" => "\xE8\xB6\xBE", + "\xB3\x6C" => "\xE8\xB6\xBA", + "\xB3\x6D" => "\xE8\xBB\x9B", + "\xB3\x6E" => "\xE8\xBB\x9F", + "\xB3\x6F" => "\xE9\x80\x99", + "\xB3\x70" => "\xE9\x80\x8D", + "\xB3\x71" => "\xE9\x80\x9A", + "\xB3\x72" => "\xE9\x80\x97", + "\xB3\x73" => "\xE9\x80\xA3", + "\xB3\x74" => "\xE9\x80\x9F", + "\xB3\x75" => "\xE9\x80\x9D", + "\xB3\x76" => "\xE9\x80\x90", + "\xB3\x77" => "\xE9\x80\x95", + "\xB3\x78" => "\xE9\x80\x9E", + "\xB3\x79" => "\xE9\x80\xA0", + "\xB3\x7A" => "\xE9\x80\x8F", + "\xB3\x7B" => "\xE9\x80\xA2", + "\xB3\x7C" => "\xE9\x80\x96", + "\xB3\x7D" => "\xE9\x80\x9B", + "\xB3\x7E" => "\xE9\x80\x94", + "\xB3\xA1" => "\xE9\x83\xA8", + "\xB3\xA2" => "\xE9\x83\xAD", + "\xB3\xA3" => "\xE9\x83\xBD", + "\xB3\xA4" => "\xE9\x85\x97", + "\xB3\xA5" => "\xE9\x87\x8E", + "\xB3\xA6" => "\xE9\x87\xB5", + "\xB3\xA7" => "\xE9\x87\xA6", + "\xB3\xA8" => "\xE9\x87\xA3", + "\xB3\xA9" => "\xE9\x87\xA7", + "\xB3\xAA" => "\xE9\x87\xAD", + "\xB3\xAB" => "\xE9\x87\xA9", + "\xB3\xAC" => "\xE9\x96\x89", + "\xB3\xAD" => "\xE9\x99\xAA", + "\xB3\xAE" => "\xE9\x99\xB5", + "\xB3\xAF" => "\xE9\x99\xB3", + "\xB3\xB0" => "\xE9\x99\xB8", + "\xB3\xB1" => "\xE9\x99\xB0", + "\xB3\xB2" => "\xE9\x99\xB4", + "\xB3\xB3" => "\xE9\x99\xB6", + "\xB3\xB4" => "\xE9\x99\xB7", + "\xB3\xB5" => "\xE9\x99\xAC", + "\xB3\xB6" => "\xE9\x9B\x80", + "\xB3\xB7" => "\xE9\x9B\xAA", + "\xB3\xB8" => "\xE9\x9B\xA9", + "\xB3\xB9" => "\xE7\xAB\xA0", + "\xB3\xBA" => "\xE7\xAB\x9F", + "\xB3\xBB" => "\xE9\xA0\x82", + "\xB3\xBC" => "\xE9\xA0\x83", + "\xB3\xBD" => "\xE9\xAD\x9A", + "\xB3\xBE" => "\xE9\xB3\xA5", + "\xB3\xBF" => "\xE9\xB9\xB5", + "\xB3\xC0" => "\xE9\xB9\xBF", + "\xB3\xC1" => "\xE9\xBA\xA5", + "\xB3\xC2" => "\xE9\xBA\xBB", + "\xB3\xC3" => "\xE5\x82\xA2", + "\xB3\xC4" => "\xE5\x82\x8D", + "\xB3\xC5" => "\xE5\x82\x85", + "\xB3\xC6" => "\xE5\x82\x99", + "\xB3\xC7" => "\xE5\x82\x91", + "\xB3\xC8" => "\xE5\x82\x80", + "\xB3\xC9" => "\xE5\x82\x96", + "\xB3\xCA" => "\xE5\x82\x98", + "\xB3\xCB" => "\xE5\x82\x9A", + "\xB3\xCC" => "\xE6\x9C\x80", + "\xB3\xCD" => "\xE5\x87\xB1", + "\xB3\xCE" => "\xE5\x89\xB2", + "\xB3\xCF" => "\xE5\x89\xB4", + "\xB3\xD0" => "\xE5\x89\xB5", + "\xB3\xD1" => "\xE5\x89\xA9", + "\xB3\xD2" => "\xE5\x8B\x9E", + "\xB3\xD3" => "\xE5\x8B\x9D", + "\xB3\xD4" => "\xE5\x8B\x9B", + "\xB3\xD5" => "\xE5\x8D\x9A", + "\xB3\xD6" => "\xE5\x8E\xA5", + "\xB3\xD7" => "\xE5\x95\xBB", + "\xB3\xD8" => "\xE5\x96\x80", + "\xB3\xD9" => "\xE5\x96\xA7", + "\xB3\xDA" => "\xE5\x95\xBC", + "\xB3\xDB" => "\xE5\x96\x8A", + "\xB3\xDC" => "\xE5\x96\x9D", + "\xB3\xDD" => "\xE5\x96\x98", + "\xB3\xDE" => "\xE5\x96\x82", + "\xB3\xDF" => "\xE5\x96\x9C", + "\xB3\xE0" => "\xE5\x96\xAA", + "\xB3\xE1" => "\xE5\x96\x94", + "\xB3\xE2" => "\xE5\x96\x87", + "\xB3\xE3" => "\xE5\x96\x8B", + "\xB3\xE4" => "\xE5\x96\x83", + "\xB3\xE5" => "\xE5\x96\xB3", + "\xB3\xE6" => "\xE5\x96\xAE", + "\xB3\xE7" => "\xE5\x96\x9F", + "\xB3\xE8" => "\xE5\x94\xBE", + "\xB3\xE9" => "\xE5\x96\xB2", + "\xB3\xEA" => "\xE5\x96\x9A", + "\xB3\xEB" => "\xE5\x96\xBB", + "\xB3\xEC" => "\xE5\x96\xAC", + "\xB3\xED" => "\xE5\x96\xB1", + "\xB3\xEE" => "\xE5\x95\xBE", + "\xB3\xEF" => "\xE5\x96\x89", + "\xB3\xF0" => "\xE5\x96\xAB", + "\xB3\xF1" => "\xE5\x96\x99", + "\xB3\xF2" => "\xE5\x9C\x8D", + "\xB3\xF3" => "\xE5\xA0\xAF", + "\xB3\xF4" => "\xE5\xA0\xAA", + "\xB3\xF5" => "\xE5\xA0\xB4", + "\xB3\xF6" => "\xE5\xA0\xA4", + "\xB3\xF7" => "\xE5\xA0\xB0", + "\xB3\xF8" => "\xE5\xA0\xB1", + "\xB3\xF9" => "\xE5\xA0\xA1", + "\xB3\xFA" => "\xE5\xA0\x9D", + "\xB3\xFB" => "\xE5\xA0\xA0", + "\xB3\xFC" => "\xE5\xA3\xB9", + "\xB3\xFD" => "\xE5\xA3\xBA", + "\xB3\xFE" => "\xE5\xA5\xA0", + "\xB4\x40" => "\xE5\xA9\xB7", + "\xB4\x41" => "\xE5\xAA\x9A", + "\xB4\x42" => "\xE5\xA9\xBF", + "\xB4\x43" => "\xE5\xAA\x92", + "\xB4\x44" => "\xE5\xAA\x9B", + "\xB4\x45" => "\xE5\xAA\xA7", + "\xB4\x46" => "\xE5\xAD\xB3", + "\xB4\x47" => "\xE5\xAD\xB1", + "\xB4\x48" => "\xE5\xAF\x92", + "\xB4\x49" => "\xE5\xAF\x8C", + "\xB4\x4A" => "\xE5\xAF\x93", + "\xB4\x4B" => "\xE5\xAF\x90", + "\xB4\x4C" => "\xE5\xB0\x8A", + "\xB4\x4D" => "\xE5\xB0\x8B", + "\xB4\x4E" => "\xE5\xB0\xB1", + "\xB4\x4F" => "\xE5\xB5\x8C", + "\xB4\x50" => "\xE5\xB5\x90", + "\xB4\x51" => "\xE5\xB4\xB4", + "\xB4\x52" => "\xE5\xB5\x87", + "\xB4\x53" => "\xE5\xB7\xBD", + "\xB4\x54" => "\xE5\xB9\x85", + "\xB4\x55" => "\xE5\xB8\xBD", + "\xB4\x56" => "\xE5\xB9\x80", + "\xB4\x57" => "\xE5\xB9\x83", + "\xB4\x58" => "\xE5\xB9\xBE", + "\xB4\x59" => "\xE5\xBB\x8A", + "\xB4\x5A" => "\xE5\xBB\x81", + "\xB4\x5B" => "\xE5\xBB\x82", + "\xB4\x5C" => "\xE5\xBB\x84", + "\xB4\x5D" => "\xE5\xBC\xBC", + "\xB4\x5E" => "\xE5\xBD\xAD", + "\xB4\x5F" => "\xE5\xBE\xA9", + "\xB4\x60" => "\xE5\xBE\xAA", + "\xB4\x61" => "\xE5\xBE\xA8", + "\xB4\x62" => "\xE6\x83\x91", + "\xB4\x63" => "\xE6\x83\xA1", + "\xB4\x64" => "\xE6\x82\xB2", + "\xB4\x65" => "\xE6\x82\xB6", + "\xB4\x66" => "\xE6\x83\xA0", + "\xB4\x67" => "\xE6\x84\x9C", + "\xB4\x68" => "\xE6\x84\xA3", + "\xB4\x69" => "\xE6\x83\xBA", + "\xB4\x6A" => "\xE6\x84\x95", + "\xB4\x6B" => "\xE6\x83\xB0", + "\xB4\x6C" => "\xE6\x83\xBB", + "\xB4\x6D" => "\xE6\x83\xB4", + "\xB4\x6E" => "\xE6\x85\xA8", + "\xB4\x6F" => "\xE6\x83\xB1", + "\xB4\x70" => "\xE6\x84\x8E", + "\xB4\x71" => "\xE6\x83\xB6", + "\xB4\x72" => "\xE6\x84\x89", + "\xB4\x73" => "\xE6\x84\x80", + "\xB4\x74" => "\xE6\x84\x92", + "\xB4\x75" => "\xE6\x88\x9F", + "\xB4\x76" => "\xE6\x89\x89", + "\xB4\x77" => "\xE6\x8E\xA3", + "\xB4\x78" => "\xE6\x8E\x8C", + "\xB4\x79" => "\xE6\x8F\x8F", + "\xB4\x7A" => "\xE6\x8F\x80", + "\xB4\x7B" => "\xE6\x8F\xA9", + "\xB4\x7C" => "\xE6\x8F\x89", + "\xB4\x7D" => "\xE6\x8F\x86", + "\xB4\x7E" => "\xE6\x8F\x8D", + "\xB4\xA1" => "\xE6\x8F\x92", + "\xB4\xA2" => "\xE6\x8F\xA3", + "\xB4\xA3" => "\xE6\x8F\x90", + "\xB4\xA4" => "\xE6\x8F\xA1", + "\xB4\xA5" => "\xE6\x8F\x96", + "\xB4\xA6" => "\xE6\x8F\xAD", + "\xB4\xA7" => "\xE6\x8F\xAE", + "\xB4\xA8" => "\xE6\x8D\xB6", + "\xB4\xA9" => "\xE6\x8F\xB4", + "\xB4\xAA" => "\xE6\x8F\xAA", + "\xB4\xAB" => "\xE6\x8F\x9B", + "\xB4\xAC" => "\xE6\x91\x92", + "\xB4\xAD" => "\xE6\x8F\x9A", + "\xB4\xAE" => "\xE6\x8F\xB9", + "\xB4\xAF" => "\xE6\x95\x9E", + "\xB4\xB0" => "\xE6\x95\xA6", + "\xB4\xB1" => "\xE6\x95\xA2", + "\xB4\xB2" => "\xE6\x95\xA3", + "\xB4\xB3" => "\xE6\x96\x91", + "\xB4\xB4" => "\xE6\x96\x90", + "\xB4\xB5" => "\xE6\x96\xAF", + "\xB4\xB6" => "\xE6\x99\xAE", + "\xB4\xB7" => "\xE6\x99\xB0", + "\xB4\xB8" => "\xE6\x99\xB4", + "\xB4\xB9" => "\xE6\x99\xB6", + "\xB4\xBA" => "\xE6\x99\xAF", + "\xB4\xBB" => "\xE6\x9A\x91", + "\xB4\xBC" => "\xE6\x99\xBA", + "\xB4\xBD" => "\xE6\x99\xBE", + "\xB4\xBE" => "\xE6\x99\xB7", + "\xB4\xBF" => "\xE6\x9B\xBE", + "\xB4\xC0" => "\xE6\x9B\xBF", + "\xB4\xC1" => "\xE6\x9C\x9F", + "\xB4\xC2" => "\xE6\x9C\x9D", + "\xB4\xC3" => "\xE6\xA3\xBA", + "\xB4\xC4" => "\xE6\xA3\x95", + "\xB4\xC5" => "\xE6\xA3\xA0", + "\xB4\xC6" => "\xE6\xA3\x98", + "\xB4\xC7" => "\xE6\xA3\x97", + "\xB4\xC8" => "\xE6\xA4\x85", + "\xB4\xC9" => "\xE6\xA3\x9F", + "\xB4\xCA" => "\xE6\xA3\xB5", + "\xB4\xCB" => "\xE6\xA3\xAE", + "\xB4\xCC" => "\xE6\xA3\xA7", + "\xB4\xCD" => "\xE6\xA3\xB9", + "\xB4\xCE" => "\xE6\xA3\x92", + "\xB4\xCF" => "\xE6\xA3\xB2", + "\xB4\xD0" => "\xE6\xA3\xA3", + "\xB4\xD1" => "\xE6\xA3\x8B", + "\xB4\xD2" => "\xE6\xA3\x8D", + "\xB4\xD3" => "\xE6\xA4\x8D", + "\xB4\xD4" => "\xE6\xA4\x92", + "\xB4\xD5" => "\xE6\xA4\x8E", + "\xB4\xD6" => "\xE6\xA3\x89", + "\xB4\xD7" => "\xE6\xA3\x9A", + "\xB4\xD8" => "\xE6\xA5\xAE", + "\xB4\xD9" => "\xE6\xA3\xBB", + "\xB4\xDA" => "\xE6\xAC\xBE", + "\xB4\xDB" => "\xE6\xAC\xBA", + "\xB4\xDC" => "\xE6\xAC\xBD", + "\xB4\xDD" => "\xE6\xAE\x98", + "\xB4\xDE" => "\xE6\xAE\x96", + "\xB4\xDF" => "\xE6\xAE\xBC", + "\xB4\xE0" => "\xE6\xAF\xAF", + "\xB4\xE1" => "\xE6\xB0\xAE", + "\xB4\xE2" => "\xE6\xB0\xAF", + "\xB4\xE3" => "\xE6\xB0\xAC", + "\xB4\xE4" => "\xE6\xB8\xAF", + "\xB4\xE5" => "\xE6\xB8\xB8", + "\xB4\xE6" => "\xE6\xB9\x94", + "\xB4\xE7" => "\xE6\xB8\xA1", + "\xB4\xE8" => "\xE6\xB8\xB2", + "\xB4\xE9" => "\xE6\xB9\xA7", + "\xB4\xEA" => "\xE6\xB9\x8A", + "\xB4\xEB" => "\xE6\xB8\xA0", + "\xB4\xEC" => "\xE6\xB8\xA5", + "\xB4\xED" => "\xE6\xB8\xA3", + "\xB4\xEE" => "\xE6\xB8\x9B", + "\xB4\xEF" => "\xE6\xB9\x9B", + "\xB4\xF0" => "\xE6\xB9\x98", + "\xB4\xF1" => "\xE6\xB8\xA4", + "\xB4\xF2" => "\xE6\xB9\x96", + "\xB4\xF3" => "\xE6\xB9\xAE", + "\xB4\xF4" => "\xE6\xB8\xAD", + "\xB4\xF5" => "\xE6\xB8\xA6", + "\xB4\xF6" => "\xE6\xB9\xAF", + "\xB4\xF7" => "\xE6\xB8\xB4", + "\xB4\xF8" => "\xE6\xB9\x8D", + "\xB4\xF9" => "\xE6\xB8\xBA", + "\xB4\xFA" => "\xE6\xB8\xAC", + "\xB4\xFB" => "\xE6\xB9\x83", + "\xB4\xFC" => "\xE6\xB8\x9D", + "\xB4\xFD" => "\xE6\xB8\xBE", + "\xB4\xFE" => "\xE6\xBB\x8B", + "\xB5\x40" => "\xE6\xBA\x89", + "\xB5\x41" => "\xE6\xB8\x99", + "\xB5\x42" => "\xE6\xB9\x8E", + "\xB5\x43" => "\xE6\xB9\xA3", + "\xB5\x44" => "\xE6\xB9\x84", + "\xB5\x45" => "\xE6\xB9\xB2", + "\xB5\x46" => "\xE6\xB9\xA9", + "\xB5\x47" => "\xE6\xB9\x9F", + "\xB5\x48" => "\xE7\x84\x99", + "\xB5\x49" => "\xE7\x84\x9A", + "\xB5\x4A" => "\xE7\x84\xA6", + "\xB5\x4B" => "\xE7\x84\xB0", + "\xB5\x4C" => "\xE7\x84\xA1", + "\xB5\x4D" => "\xE7\x84\xB6", + "\xB5\x4E" => "\xE7\x85\xAE", + "\xB5\x4F" => "\xE7\x84\x9C", + "\xB5\x50" => "\xE7\x89\x8C", + "\xB5\x51" => "\xE7\x8A\x84", + "\xB5\x52" => "\xE7\x8A\x80", + "\xB5\x53" => "\xE7\x8C\xB6", + "\xB5\x54" => "\xE7\x8C\xA5", + "\xB5\x55" => "\xE7\x8C\xB4", + "\xB5\x56" => "\xE7\x8C\xA9", + "\xB5\x57" => "\xE7\x90\xBA", + "\xB5\x58" => "\xE7\x90\xAA", + "\xB5\x59" => "\xE7\x90\xB3", + "\xB5\x5A" => "\xE7\x90\xA2", + "\xB5\x5B" => "\xE7\x90\xA5", + "\xB5\x5C" => "\xE7\x90\xB5", + "\xB5\x5D" => "\xE7\x90\xB6", + "\xB5\x5E" => "\xE7\x90\xB4", + "\xB5\x5F" => "\xE7\x90\xAF", + "\xB5\x60" => "\xE7\x90\x9B", + "\xB5\x61" => "\xE7\x90\xA6", + "\xB5\x62" => "\xE7\x90\xA8", + "\xB5\x63" => "\xE7\x94\xA5", + "\xB5\x64" => "\xE7\x94\xA6", + "\xB5\x65" => "\xE7\x95\xAB", + "\xB5\x66" => "\xE7\x95\xAA", + "\xB5\x67" => "\xE7\x97\xA2", + "\xB5\x68" => "\xE7\x97\x9B", + "\xB5\x69" => "\xE7\x97\xA3", + "\xB5\x6A" => "\xE7\x97\x99", + "\xB5\x6B" => "\xE7\x97\x98", + "\xB5\x6C" => "\xE7\x97\x9E", + "\xB5\x6D" => "\xE7\x97\xA0", + "\xB5\x6E" => "\xE7\x99\xBB", + "\xB5\x6F" => "\xE7\x99\xBC", + "\xB5\x70" => "\xE7\x9A\x96", + "\xB5\x71" => "\xE7\x9A\x93", + "\xB5\x72" => "\xE7\x9A\xB4", + "\xB5\x73" => "\xE7\x9B\x9C", + "\xB5\x74" => "\xE7\x9D\x8F", + "\xB5\x75" => "\xE7\x9F\xAD", + "\xB5\x76" => "\xE7\xA1\x9D", + "\xB5\x77" => "\xE7\xA1\xAC", + "\xB5\x78" => "\xE7\xA1\xAF", + "\xB5\x79" => "\xE7\xA8\x8D", + "\xB5\x7A" => "\xE7\xA8\x88", + "\xB5\x7B" => "\xE7\xA8\x8B", + "\xB5\x7C" => "\xE7\xA8\x85", + "\xB5\x7D" => "\xE7\xA8\x80", + "\xB5\x7E" => "\xE7\xAA\x98", + "\xB5\xA1" => "\xE7\xAA\x97", + "\xB5\xA2" => "\xE7\xAA\x96", + "\xB5\xA3" => "\xE7\xAB\xA5", + "\xB5\xA4" => "\xE7\xAB\xA3", + "\xB5\xA5" => "\xE7\xAD\x89", + "\xB5\xA6" => "\xE7\xAD\x96", + "\xB5\xA7" => "\xE7\xAD\x86", + "\xB5\xA8" => "\xE7\xAD\x90", + "\xB5\xA9" => "\xE7\xAD\x92", + "\xB5\xAA" => "\xE7\xAD\x94", + "\xB5\xAB" => "\xE7\xAD\x8D", + "\xB5\xAC" => "\xE7\xAD\x8B", + "\xB5\xAD" => "\xE7\xAD\x8F", + "\xB5\xAE" => "\xE7\xAD\x91", + "\xB5\xAF" => "\xE7\xB2\x9F", + "\xB5\xB0" => "\xE7\xB2\xA5", + "\xB5\xB1" => "\xE7\xB5\x9E", + "\xB5\xB2" => "\xE7\xB5\x90", + "\xB5\xB3" => "\xE7\xB5\xA8", + "\xB5\xB4" => "\xE7\xB5\x95", + "\xB5\xB5" => "\xE7\xB4\xAB", + "\xB5\xB6" => "\xE7\xB5\xAE", + "\xB5\xB7" => "\xE7\xB5\xB2", + "\xB5\xB8" => "\xE7\xB5\xA1", + "\xB5\xB9" => "\xE7\xB5\xA6", + "\xB5\xBA" => "\xE7\xB5\xA2", + "\xB5\xBB" => "\xE7\xB5\xB0", + "\xB5\xBC" => "\xE7\xB5\xB3", + "\xB5\xBD" => "\xE5\x96\x84", + "\xB5\xBE" => "\xE7\xBF\x94", + "\xB5\xBF" => "\xE7\xBF\x95", + "\xB5\xC0" => "\xE8\x80\x8B", + "\xB5\xC1" => "\xE8\x81\x92", + "\xB5\xC2" => "\xE8\x82\x85", + "\xB5\xC3" => "\xE8\x85\x95", + "\xB5\xC4" => "\xE8\x85\x94", + "\xB5\xC5" => "\xE8\x85\x8B", + "\xB5\xC6" => "\xE8\x85\x91", + "\xB5\xC7" => "\xE8\x85\x8E", + "\xB5\xC8" => "\xE8\x84\xB9", + "\xB5\xC9" => "\xE8\x85\x86", + "\xB5\xCA" => "\xE8\x84\xBE", + "\xB5\xCB" => "\xE8\x85\x8C", + "\xB5\xCC" => "\xE8\x85\x93", + "\xB5\xCD" => "\xE8\x85\xB4", + "\xB5\xCE" => "\xE8\x88\x92", + "\xB5\xCF" => "\xE8\x88\x9C", + "\xB5\xD0" => "\xE8\x8F\xA9", + "\xB5\xD1" => "\xE8\x90\x83", + "\xB5\xD2" => "\xE8\x8F\xB8", + "\xB5\xD3" => "\xE8\x90\x8D", + "\xB5\xD4" => "\xE8\x8F\xA0", + "\xB5\xD5" => "\xE8\x8F\x85", + "\xB5\xD6" => "\xE8\x90\x8B", + "\xB5\xD7" => "\xE8\x8F\x81", + "\xB5\xD8" => "\xE8\x8F\xAF", + "\xB5\xD9" => "\xE8\x8F\xB1", + "\xB5\xDA" => "\xE8\x8F\xB4", + "\xB5\xDB" => "\xE8\x91\x97", + "\xB5\xDC" => "\xE8\x90\x8A", + "\xB5\xDD" => "\xE8\x8F\xB0", + "\xB5\xDE" => "\xE8\x90\x8C", + "\xB5\xDF" => "\xE8\x8F\x8C", + "\xB5\xE0" => "\xE8\x8F\xBD", + "\xB5\xE1" => "\xE8\x8F\xB2", + "\xB5\xE2" => "\xE8\x8F\x8A", + "\xB5\xE3" => "\xE8\x90\xB8", + "\xB5\xE4" => "\xE8\x90\x8E", + "\xB5\xE5" => "\xE8\x90\x84", + "\xB5\xE6" => "\xE8\x8F\x9C", + "\xB5\xE7" => "\xE8\x90\x87", + "\xB5\xE8" => "\xE8\x8F\x94", + "\xB5\xE9" => "\xE8\x8F\x9F", + "\xB5\xEA" => "\xE8\x99\x9B", + "\xB5\xEB" => "\xE8\x9B\x9F", + "\xB5\xEC" => "\xE8\x9B\x99", + "\xB5\xED" => "\xE8\x9B\xAD", + "\xB5\xEE" => "\xE8\x9B\x94", + "\xB5\xEF" => "\xE8\x9B\x9B", + "\xB5\xF0" => "\xE8\x9B\xA4", + "\xB5\xF1" => "\xE8\x9B\x90", + "\xB5\xF2" => "\xE8\x9B\x9E", + "\xB5\xF3" => "\xE8\xA1\x97", + "\xB5\xF4" => "\xE8\xA3\x81", + "\xB5\xF5" => "\xE8\xA3\x82", + "\xB5\xF6" => "\xE8\xA2\xB1", + "\xB5\xF7" => "\xE8\xA6\x83", + "\xB5\xF8" => "\xE8\xA6\x96", + "\xB5\xF9" => "\xE8\xA8\xBB", + "\xB5\xFA" => "\xE8\xA9\xA0", + "\xB5\xFB" => "\xE8\xA9\x95", + "\xB5\xFC" => "\xE8\xA9\x9E", + "\xB5\xFD" => "\xE8\xA8\xBC", + "\xB5\xFE" => "\xE8\xA9\x81", + "\xB6\x40" => "\xE8\xA9\x94", + "\xB6\x41" => "\xE8\xA9\x9B", + "\xB6\x42" => "\xE8\xA9\x90", + "\xB6\x43" => "\xE8\xA9\x86", + "\xB6\x44" => "\xE8\xA8\xB4", + "\xB6\x45" => "\xE8\xA8\xBA", + "\xB6\x46" => "\xE8\xA8\xB6", + "\xB6\x47" => "\xE8\xA9\x96", + "\xB6\x48" => "\xE8\xB1\xA1", + "\xB6\x49" => "\xE8\xB2\x82", + "\xB6\x4A" => "\xE8\xB2\xAF", + "\xB6\x4B" => "\xE8\xB2\xBC", + "\xB6\x4C" => "\xE8\xB2\xB3", + "\xB6\x4D" => "\xE8\xB2\xBD", + "\xB6\x4E" => "\xE8\xB3\x81", + "\xB6\x4F" => "\xE8\xB2\xBB", + "\xB6\x50" => "\xE8\xB3\x80", + "\xB6\x51" => "\xE8\xB2\xB4", + "\xB6\x52" => "\xE8\xB2\xB7", + "\xB6\x53" => "\xE8\xB2\xB6", + "\xB6\x54" => "\xE8\xB2\xBF", + "\xB6\x55" => "\xE8\xB2\xB8", + "\xB6\x56" => "\xE8\xB6\x8A", + "\xB6\x57" => "\xE8\xB6\x85", + "\xB6\x58" => "\xE8\xB6\x81", + "\xB6\x59" => "\xE8\xB7\x8E", + "\xB6\x5A" => "\xE8\xB7\x9D", + "\xB6\x5B" => "\xE8\xB7\x8B", + "\xB6\x5C" => "\xE8\xB7\x9A", + "\xB6\x5D" => "\xE8\xB7\x91", + "\xB6\x5E" => "\xE8\xB7\x8C", + "\xB6\x5F" => "\xE8\xB7\x9B", + "\xB6\x60" => "\xE8\xB7\x86", + "\xB6\x61" => "\xE8\xBB\xBB", + "\xB6\x62" => "\xE8\xBB\xB8", + "\xB6\x63" => "\xE8\xBB\xBC", + "\xB6\x64" => "\xE8\xBE\x9C", + "\xB6\x65" => "\xE9\x80\xAE", + "\xB6\x66" => "\xE9\x80\xB5", + "\xB6\x67" => "\xE9\x80\xB1", + "\xB6\x68" => "\xE9\x80\xB8", + "\xB6\x69" => "\xE9\x80\xB2", + "\xB6\x6A" => "\xE9\x80\xB6", + "\xB6\x6B" => "\xE9\x84\x82", + "\xB6\x6C" => "\xE9\x83\xB5", + "\xB6\x6D" => "\xE9\x84\x89", + "\xB6\x6E" => "\xE9\x83\xBE", + "\xB6\x6F" => "\xE9\x85\xA3", + "\xB6\x70" => "\xE9\x85\xA5", + "\xB6\x71" => "\xE9\x87\x8F", + "\xB6\x72" => "\xE9\x88\x94", + "\xB6\x73" => "\xE9\x88\x95", + "\xB6\x74" => "\xE9\x88\xA3", + "\xB6\x75" => "\xE9\x88\x89", + "\xB6\x76" => "\xE9\x88\x9E", + "\xB6\x77" => "\xE9\x88\x8D", + "\xB6\x78" => "\xE9\x88\x90", + "\xB6\x79" => "\xE9\x88\x87", + "\xB6\x7A" => "\xE9\x88\x91", + "\xB6\x7B" => "\xE9\x96\x94", + "\xB6\x7C" => "\xE9\x96\x8F", + "\xB6\x7D" => "\xE9\x96\x8B", + "\xB6\x7E" => "\xE9\x96\x91", + "\xB6\xA1" => "\xE9\x96\x93", + "\xB6\xA2" => "\xE9\x96\x92", + "\xB6\xA3" => "\xE9\x96\x8E", + "\xB6\xA4" => "\xE9\x9A\x8A", + "\xB6\xA5" => "\xE9\x9A\x8E", + "\xB6\xA6" => "\xE9\x9A\x8B", + "\xB6\xA7" => "\xE9\x99\xBD", + "\xB6\xA8" => "\xE9\x9A\x85", + "\xB6\xA9" => "\xE9\x9A\x86", + "\xB6\xAA" => "\xE9\x9A\x8D", + "\xB6\xAB" => "\xE9\x99\xB2", + "\xB6\xAC" => "\xE9\x9A\x84", + "\xB6\xAD" => "\xE9\x9B\x81", + "\xB6\xAE" => "\xE9\x9B\x85", + "\xB6\xAF" => "\xE9\x9B\x84", + "\xB6\xB0" => "\xE9\x9B\x86", + "\xB6\xB1" => "\xE9\x9B\x87", + "\xB6\xB2" => "\xE9\x9B\xAF", + "\xB6\xB3" => "\xE9\x9B\xB2", + "\xB6\xB4" => "\xE9\x9F\x8C", + "\xB6\xB5" => "\xE9\xA0\x85", + "\xB6\xB6" => "\xE9\xA0\x86", + "\xB6\xB7" => "\xE9\xA0\x88", + "\xB6\xB8" => "\xE9\xA3\xA7", + "\xB6\xB9" => "\xE9\xA3\xAA", + "\xB6\xBA" => "\xE9\xA3\xAF", + "\xB6\xBB" => "\xE9\xA3\xA9", + "\xB6\xBC" => "\xE9\xA3\xB2", + "\xB6\xBD" => "\xE9\xA3\xAD", + "\xB6\xBE" => "\xE9\xA6\xAE", + "\xB6\xBF" => "\xE9\xA6\xAD", + "\xB6\xC0" => "\xE9\xBB\x83", + "\xB6\xC1" => "\xE9\xBB\x8D", + "\xB6\xC2" => "\xE9\xBB\x91", + "\xB6\xC3" => "\xE4\xBA\x82", + "\xB6\xC4" => "\xE5\x82\xAD", + "\xB6\xC5" => "\xE5\x82\xB5", + "\xB6\xC6" => "\xE5\x82\xB2", + "\xB6\xC7" => "\xE5\x82\xB3", + "\xB6\xC8" => "\xE5\x83\x85", + "\xB6\xC9" => "\xE5\x82\xBE", + "\xB6\xCA" => "\xE5\x82\xAC", + "\xB6\xCB" => "\xE5\x82\xB7", + "\xB6\xCC" => "\xE5\x82\xBB", + "\xB6\xCD" => "\xE5\x82\xAF", + "\xB6\xCE" => "\xE5\x83\x87", + "\xB6\xCF" => "\xE5\x89\xBF", + "\xB6\xD0" => "\xE5\x89\xB7", + "\xB6\xD1" => "\xE5\x89\xBD", + "\xB6\xD2" => "\xE5\x8B\x9F", + "\xB6\xD3" => "\xE5\x8B\xA6", + "\xB6\xD4" => "\xE5\x8B\xA4", + "\xB6\xD5" => "\xE5\x8B\xA2", + "\xB6\xD6" => "\xE5\x8B\xA3", + "\xB6\xD7" => "\xE5\x8C\xAF", + "\xB6\xD8" => "\xE5\x97\x9F", + "\xB6\xD9" => "\xE5\x97\xA8", + "\xB6\xDA" => "\xE5\x97\x93", + "\xB6\xDB" => "\xE5\x97\xA6", + "\xB6\xDC" => "\xE5\x97\x8E", + "\xB6\xDD" => "\xE5\x97\x9C", + "\xB6\xDE" => "\xE5\x97\x87", + "\xB6\xDF" => "\xE5\x97\x91", + "\xB6\xE0" => "\xE5\x97\xA3", + "\xB6\xE1" => "\xE5\x97\xA4", + "\xB6\xE2" => "\xE5\x97\xAF", + "\xB6\xE3" => "\xE5\x97\x9A", + "\xB6\xE4" => "\xE5\x97\xA1", + "\xB6\xE5" => "\xE5\x97\x85", + "\xB6\xE6" => "\xE5\x97\x86", + "\xB6\xE7" => "\xE5\x97\xA5", + "\xB6\xE8" => "\xE5\x97\x89", + "\xB6\xE9" => "\xE5\x9C\x92", + "\xB6\xEA" => "\xE5\x9C\x93", + "\xB6\xEB" => "\xE5\xA1\x9E", + "\xB6\xEC" => "\xE5\xA1\x91", + "\xB6\xED" => "\xE5\xA1\x98", + "\xB6\xEE" => "\xE5\xA1\x97", + "\xB6\xEF" => "\xE5\xA1\x9A", + "\xB6\xF0" => "\xE5\xA1\x94", + "\xB6\xF1" => "\xE5\xA1\xAB", + "\xB6\xF2" => "\xE5\xA1\x8C", + "\xB6\xF3" => "\xE5\xA1\xAD", + "\xB6\xF4" => "\xE5\xA1\x8A", + "\xB6\xF5" => "\xE5\xA1\xA2", + "\xB6\xF6" => "\xE5\xA1\x92", + "\xB6\xF7" => "\xE5\xA1\x8B", + "\xB6\xF8" => "\xE5\xA5\xA7", + "\xB6\xF9" => "\xE5\xAB\x81", + "\xB6\xFA" => "\xE5\xAB\x89", + "\xB6\xFB" => "\xE5\xAB\x8C", + "\xB6\xFC" => "\xE5\xAA\xBE", + "\xB6\xFD" => "\xE5\xAA\xBD", + "\xB6\xFE" => "\xE5\xAA\xBC", + "\xB7\x40" => "\xE5\xAA\xB3", + "\xB7\x41" => "\xE5\xAB\x82", + "\xB7\x42" => "\xE5\xAA\xB2", + "\xB7\x43" => "\xE5\xB5\xA9", + "\xB7\x44" => "\xE5\xB5\xAF", + "\xB7\x45" => "\xE5\xB9\x8C", + "\xB7\x46" => "\xE5\xB9\xB9", + "\xB7\x47" => "\xE5\xBB\x89", + "\xB7\x48" => "\xE5\xBB\x88", + "\xB7\x49" => "\xE5\xBC\x92", + "\xB7\x4A" => "\xE5\xBD\x99", + "\xB7\x4B" => "\xE5\xBE\xAC", + "\xB7\x4C" => "\xE5\xBE\xAE", + "\xB7\x4D" => "\xE6\x84\x9A", + "\xB7\x4E" => "\xE6\x84\x8F", + "\xB7\x4F" => "\xE6\x85\x88", + "\xB7\x50" => "\xE6\x84\x9F", + "\xB7\x51" => "\xE6\x83\xB3", + "\xB7\x52" => "\xE6\x84\x9B", + "\xB7\x53" => "\xE6\x83\xB9", + "\xB7\x54" => "\xE6\x84\x81", + "\xB7\x55" => "\xE6\x84\x88", + "\xB7\x56" => "\xE6\x85\x8E", + "\xB7\x57" => "\xE6\x85\x8C", + "\xB7\x58" => "\xE6\x85\x84", + "\xB7\x59" => "\xE6\x85\x8D", + "\xB7\x5A" => "\xE6\x84\xBE", + "\xB7\x5B" => "\xE6\x84\xB4", + "\xB7\x5C" => "\xE6\x84\xA7", + "\xB7\x5D" => "\xE6\x84\x8D", + "\xB7\x5E" => "\xE6\x84\x86", + "\xB7\x5F" => "\xE6\x84\xB7", + "\xB7\x60" => "\xE6\x88\xA1", + "\xB7\x61" => "\xE6\x88\xA2", + "\xB7\x62" => "\xE6\x90\x93", + "\xB7\x63" => "\xE6\x90\xBE", + "\xB7\x64" => "\xE6\x90\x9E", + "\xB7\x65" => "\xE6\x90\xAA", + "\xB7\x66" => "\xE6\x90\xAD", + "\xB7\x67" => "\xE6\x90\xBD", + "\xB7\x68" => "\xE6\x90\xAC", + "\xB7\x69" => "\xE6\x90\x8F", + "\xB7\x6A" => "\xE6\x90\x9C", + "\xB7\x6B" => "\xE6\x90\x94", + "\xB7\x6C" => "\xE6\x90\x8D", + "\xB7\x6D" => "\xE6\x90\xB6", + "\xB7\x6E" => "\xE6\x90\x96", + "\xB7\x6F" => "\xE6\x90\x97", + "\xB7\x70" => "\xE6\x90\x86", + "\xB7\x71" => "\xE6\x95\xAC", + "\xB7\x72" => "\xE6\x96\x9F", + "\xB7\x73" => "\xE6\x96\xB0", + "\xB7\x74" => "\xE6\x9A\x97", + "\xB7\x75" => "\xE6\x9A\x89", + "\xB7\x76" => "\xE6\x9A\x87", + "\xB7\x77" => "\xE6\x9A\x88", + "\xB7\x78" => "\xE6\x9A\x96", + "\xB7\x79" => "\xE6\x9A\x84", + "\xB7\x7A" => "\xE6\x9A\x98", + "\xB7\x7B" => "\xE6\x9A\x8D", + "\xB7\x7C" => "\xE6\x9C\x83", + "\xB7\x7D" => "\xE6\xA6\x94", + "\xB7\x7E" => "\xE6\xA5\xAD", + "\xB7\xA1" => "\xE6\xA5\x9A", + "\xB7\xA2" => "\xE6\xA5\xB7", + "\xB7\xA3" => "\xE6\xA5\xA0", + "\xB7\xA4" => "\xE6\xA5\x94", + "\xB7\xA5" => "\xE6\xA5\xB5", + "\xB7\xA6" => "\xE6\xA4\xB0", + "\xB7\xA7" => "\xE6\xA6\x82", + "\xB7\xA8" => "\xE6\xA5\x8A", + "\xB7\xA9" => "\xE6\xA5\xA8", + "\xB7\xAA" => "\xE6\xA5\xAB", + "\xB7\xAB" => "\xE6\xA5\x9E", + "\xB7\xAC" => "\xE6\xA5\x93", + "\xB7\xAD" => "\xE6\xA5\xB9", + "\xB7\xAE" => "\xE6\xA6\x86", + "\xB7\xAF" => "\xE6\xA5\x9D", + "\xB7\xB0" => "\xE6\xA5\xA3", + "\xB7\xB1" => "\xE6\xA5\x9B", + "\xB7\xB2" => "\xE6\xAD\x87", + "\xB7\xB3" => "\xE6\xAD\xB2", + "\xB7\xB4" => "\xE6\xAF\x80", + "\xB7\xB5" => "\xE6\xAE\xBF", + "\xB7\xB6" => "\xE6\xAF\x93", + "\xB7\xB7" => "\xE6\xAF\xBD", + "\xB7\xB8" => "\xE6\xBA\xA2", + "\xB7\xB9" => "\xE6\xBA\xAF", + "\xB7\xBA" => "\xE6\xBB\x93", + "\xB7\xBB" => "\xE6\xBA\xB6", + "\xB7\xBC" => "\xE6\xBB\x82", + "\xB7\xBD" => "\xE6\xBA\x90", + "\xB7\xBE" => "\xE6\xBA\x9D", + "\xB7\xBF" => "\xE6\xBB\x87", + "\xB7\xC0" => "\xE6\xBB\x85", + "\xB7\xC1" => "\xE6\xBA\xA5", + "\xB7\xC2" => "\xE6\xBA\x98", + "\xB7\xC3" => "\xE6\xBA\xBC", + "\xB7\xC4" => "\xE6\xBA\xBA", + "\xB7\xC5" => "\xE6\xBA\xAB", + "\xB7\xC6" => "\xE6\xBB\x91", + "\xB7\xC7" => "\xE6\xBA\x96", + "\xB7\xC8" => "\xE6\xBA\x9C", + "\xB7\xC9" => "\xE6\xBB\x84", + "\xB7\xCA" => "\xE6\xBB\x94", + "\xB7\xCB" => "\xE6\xBA\xAA", + "\xB7\xCC" => "\xE6\xBA\xA7", + "\xB7\xCD" => "\xE6\xBA\xB4", + "\xB7\xCE" => "\xE7\x85\x8E", + "\xB7\xCF" => "\xE7\x85\x99", + "\xB7\xD0" => "\xE7\x85\xA9", + "\xB7\xD1" => "\xE7\x85\xA4", + "\xB7\xD2" => "\xE7\x85\x89", + "\xB7\xD3" => "\xE7\x85\xA7", + "\xB7\xD4" => "\xE7\x85\x9C", + "\xB7\xD5" => "\xE7\x85\xAC", + "\xB7\xD6" => "\xE7\x85\xA6", + "\xB7\xD7" => "\xE7\x85\x8C", + "\xB7\xD8" => "\xE7\x85\xA5", + "\xB7\xD9" => "\xE7\x85\x9E", + "\xB7\xDA" => "\xE7\x85\x86", + "\xB7\xDB" => "\xE7\x85\xA8", + "\xB7\xDC" => "\xE7\x85\x96", + "\xB7\xDD" => "\xE7\x88\xBA", + "\xB7\xDE" => "\xE7\x89\x92", + "\xB7\xDF" => "\xE7\x8C\xB7", + "\xB7\xE0" => "\xE7\x8D\x85", + "\xB7\xE1" => "\xE7\x8C\xBF", + "\xB7\xE2" => "\xE7\x8C\xBE", + "\xB7\xE3" => "\xE7\x91\xAF", + "\xB7\xE4" => "\xE7\x91\x9A", + "\xB7\xE5" => "\xE7\x91\x95", + "\xB7\xE6" => "\xE7\x91\x9F", + "\xB7\xE7" => "\xE7\x91\x9E", + "\xB7\xE8" => "\xE7\x91\x81", + "\xB7\xE9" => "\xE7\x90\xBF", + "\xB7\xEA" => "\xE7\x91\x99", + "\xB7\xEB" => "\xE7\x91\x9B", + "\xB7\xEC" => "\xE7\x91\x9C", + "\xB7\xED" => "\xE7\x95\xB6", + "\xB7\xEE" => "\xE7\x95\xB8", + "\xB7\xEF" => "\xE7\x98\x80", + "\xB7\xF0" => "\xE7\x97\xB0", + "\xB7\xF1" => "\xE7\x98\x81", + "\xB7\xF2" => "\xE7\x97\xB2", + "\xB7\xF3" => "\xE7\x97\xB1", + "\xB7\xF4" => "\xE7\x97\xBA", + "\xB7\xF5" => "\xE7\x97\xBF", + "\xB7\xF6" => "\xE7\x97\xB4", + "\xB7\xF7" => "\xE7\x97\xB3", + "\xB7\xF8" => "\xE7\x9B\x9E", + "\xB7\xF9" => "\xE7\x9B\x9F", + "\xB7\xFA" => "\xE7\x9D\x9B", + "\xB7\xFB" => "\xE7\x9D\xAB", + "\xB7\xFC" => "\xE7\x9D\xA6", + "\xB7\xFD" => "\xE7\x9D\x9E", + "\xB7\xFE" => "\xE7\x9D\xA3", + "\xB8\x40" => "\xE7\x9D\xB9", + "\xB8\x41" => "\xE7\x9D\xAA", + "\xB8\x42" => "\xE7\x9D\xAC", + "\xB8\x43" => "\xE7\x9D\x9C", + "\xB8\x44" => "\xE7\x9D\xA5", + "\xB8\x45" => "\xE7\x9D\xA8", + "\xB8\x46" => "\xE7\x9D\xA2", + "\xB8\x47" => "\xE7\x9F\xAE", + "\xB8\x48" => "\xE7\xA2\x8E", + "\xB8\x49" => "\xE7\xA2\xB0", + "\xB8\x4A" => "\xE7\xA2\x97", + "\xB8\x4B" => "\xE7\xA2\x98", + "\xB8\x4C" => "\xE7\xA2\x8C", + "\xB8\x4D" => "\xE7\xA2\x89", + "\xB8\x4E" => "\xE7\xA1\xBC", + "\xB8\x4F" => "\xE7\xA2\x91", + "\xB8\x50" => "\xE7\xA2\x93", + "\xB8\x51" => "\xE7\xA1\xBF", + "\xB8\x52" => "\xE7\xA5\xBA", + "\xB8\x53" => "\xE7\xA5\xBF", + "\xB8\x54" => "\xE7\xA6\x81", + "\xB8\x55" => "\xE8\x90\xAC", + "\xB8\x56" => "\xE7\xA6\xBD", + "\xB8\x57" => "\xE7\xA8\x9C", + "\xB8\x58" => "\xE7\xA8\x9A", + "\xB8\x59" => "\xE7\xA8\xA0", + "\xB8\x5A" => "\xE7\xA8\x94", + "\xB8\x5B" => "\xE7\xA8\x9F", + "\xB8\x5C" => "\xE7\xA8\x9E", + "\xB8\x5D" => "\xE7\xAA\x9F", + "\xB8\x5E" => "\xE7\xAA\xA0", + "\xB8\x5F" => "\xE7\xAD\xB7", + "\xB8\x60" => "\xE7\xAF\x80", + "\xB8\x61" => "\xE7\xAD\xA0", + "\xB8\x62" => "\xE7\xAD\xAE", + "\xB8\x63" => "\xE7\xAD\xA7", + "\xB8\x64" => "\xE7\xB2\xB1", + "\xB8\x65" => "\xE7\xB2\xB3", + "\xB8\x66" => "\xE7\xB2\xB5", + "\xB8\x67" => "\xE7\xB6\x93", + "\xB8\x68" => "\xE7\xB5\xB9", + "\xB8\x69" => "\xE7\xB6\x91", + "\xB8\x6A" => "\xE7\xB6\x81", + "\xB8\x6B" => "\xE7\xB6\x8F", + "\xB8\x6C" => "\xE7\xB5\x9B", + "\xB8\x6D" => "\xE7\xBD\xAE", + "\xB8\x6E" => "\xE7\xBD\xA9", + "\xB8\x6F" => "\xE7\xBD\xAA", + "\xB8\x70" => "\xE7\xBD\xB2", + "\xB8\x71" => "\xE7\xBE\xA9", + "\xB8\x72" => "\xE7\xBE\xA8", + "\xB8\x73" => "\xE7\xBE\xA4", + "\xB8\x74" => "\xE8\x81\x96", + "\xB8\x75" => "\xE8\x81\x98", + "\xB8\x76" => "\xE8\x82\x86", + "\xB8\x77" => "\xE8\x82\x84", + "\xB8\x78" => "\xE8\x85\xB1", + "\xB8\x79" => "\xE8\x85\xB0", + "\xB8\x7A" => "\xE8\x85\xB8", + "\xB8\x7B" => "\xE8\x85\xA5", + "\xB8\x7C" => "\xE8\x85\xAE", + "\xB8\x7D" => "\xE8\x85\xB3", + "\xB8\x7E" => "\xE8\x85\xAB", + "\xB8\xA1" => "\xE8\x85\xB9", + "\xB8\xA2" => "\xE8\x85\xBA", + "\xB8\xA3" => "\xE8\x85\xA6", + "\xB8\xA4" => "\xE8\x88\x85", + "\xB8\xA5" => "\xE8\x89\x87", + "\xB8\xA6" => "\xE8\x92\x82", + "\xB8\xA7" => "\xE8\x91\xB7", + "\xB8\xA8" => "\xE8\x90\xBD", + "\xB8\xA9" => "\xE8\x90\xB1", + "\xB8\xAA" => "\xE8\x91\xB5", + "\xB8\xAB" => "\xE8\x91\xA6", + "\xB8\xAC" => "\xE8\x91\xAB", + "\xB8\xAD" => "\xE8\x91\x89", + "\xB8\xAE" => "\xE8\x91\xAC", + "\xB8\xAF" => "\xE8\x91\x9B", + "\xB8\xB0" => "\xE8\x90\xBC", + "\xB8\xB1" => "\xE8\x90\xB5", + "\xB8\xB2" => "\xE8\x91\xA1", + "\xB8\xB3" => "\xE8\x91\xA3", + "\xB8\xB4" => "\xE8\x91\xA9", + "\xB8\xB5" => "\xE8\x91\xAD", + "\xB8\xB6" => "\xE8\x91\x86", + "\xB8\xB7" => "\xE8\x99\x9E", + "\xB8\xB8" => "\xE8\x99\x9C", + "\xB8\xB9" => "\xE8\x99\x9F", + "\xB8\xBA" => "\xE8\x9B\xB9", + "\xB8\xBB" => "\xE8\x9C\x93", + "\xB8\xBC" => "\xE8\x9C\x88", + "\xB8\xBD" => "\xE8\x9C\x87", + "\xB8\xBE" => "\xE8\x9C\x80", + "\xB8\xBF" => "\xE8\x9B\xBE", + "\xB8\xC0" => "\xE8\x9B\xBB", + "\xB8\xC1" => "\xE8\x9C\x82", + "\xB8\xC2" => "\xE8\x9C\x83", + "\xB8\xC3" => "\xE8\x9C\x86", + "\xB8\xC4" => "\xE8\x9C\x8A", + "\xB8\xC5" => "\xE8\xA1\x99", + "\xB8\xC6" => "\xE8\xA3\x9F", + "\xB8\xC7" => "\xE8\xA3\x94", + "\xB8\xC8" => "\xE8\xA3\x99", + "\xB8\xC9" => "\xE8\xA3\x9C", + "\xB8\xCA" => "\xE8\xA3\x98", + "\xB8\xCB" => "\xE8\xA3\x9D", + "\xB8\xCC" => "\xE8\xA3\xA1", + "\xB8\xCD" => "\xE8\xA3\x8A", + "\xB8\xCE" => "\xE8\xA3\x95", + "\xB8\xCF" => "\xE8\xA3\x92", + "\xB8\xD0" => "\xE8\xA6\x9C", + "\xB8\xD1" => "\xE8\xA7\xA3", + "\xB8\xD2" => "\xE8\xA9\xAB", + "\xB8\xD3" => "\xE8\xA9\xB2", + "\xB8\xD4" => "\xE8\xA9\xB3", + "\xB8\xD5" => "\xE8\xA9\xA6", + "\xB8\xD6" => "\xE8\xA9\xA9", + "\xB8\xD7" => "\xE8\xA9\xB0", + "\xB8\xD8" => "\xE8\xAA\x87", + "\xB8\xD9" => "\xE8\xA9\xBC", + "\xB8\xDA" => "\xE8\xA9\xA3", + "\xB8\xDB" => "\xE8\xAA\xA0", + "\xB8\xDC" => "\xE8\xA9\xB1", + "\xB8\xDD" => "\xE8\xAA\x85", + "\xB8\xDE" => "\xE8\xA9\xAD", + "\xB8\xDF" => "\xE8\xA9\xA2", + "\xB8\xE0" => "\xE8\xA9\xAE", + "\xB8\xE1" => "\xE8\xA9\xAC", + "\xB8\xE2" => "\xE8\xA9\xB9", + "\xB8\xE3" => "\xE8\xA9\xBB", + "\xB8\xE4" => "\xE8\xA8\xBE", + "\xB8\xE5" => "\xE8\xA9\xA8", + "\xB8\xE6" => "\xE8\xB1\xA2", + "\xB8\xE7" => "\xE8\xB2\x8A", + "\xB8\xE8" => "\xE8\xB2\x89", + "\xB8\xE9" => "\xE8\xB3\x8A", + "\xB8\xEA" => "\xE8\xB3\x87", + "\xB8\xEB" => "\xE8\xB3\x88", + "\xB8\xEC" => "\xE8\xB3\x84", + "\xB8\xED" => "\xE8\xB2\xB2", + "\xB8\xEE" => "\xE8\xB3\x83", + "\xB8\xEF" => "\xE8\xB3\x82", + "\xB8\xF0" => "\xE8\xB3\x85", + "\xB8\xF1" => "\xE8\xB7\xA1", + "\xB8\xF2" => "\xE8\xB7\x9F", + "\xB8\xF3" => "\xE8\xB7\xA8", + "\xB8\xF4" => "\xE8\xB7\xAF", + "\xB8\xF5" => "\xE8\xB7\xB3", + "\xB8\xF6" => "\xE8\xB7\xBA", + "\xB8\xF7" => "\xE8\xB7\xAA", + "\xB8\xF8" => "\xE8\xB7\xA4", + "\xB8\xF9" => "\xE8\xB7\xA6", + "\xB8\xFA" => "\xE8\xBA\xB2", + "\xB8\xFB" => "\xE8\xBC\x83", + "\xB8\xFC" => "\xE8\xBC\x89", + "\xB8\xFD" => "\xE8\xBB\xBE", + "\xB8\xFE" => "\xE8\xBC\x8A", + "\xB9\x40" => "\xE8\xBE\x9F", + "\xB9\x41" => "\xE8\xBE\xB2", + "\xB9\x42" => "\xE9\x81\x8B", + "\xB9\x43" => "\xE9\x81\x8A", + "\xB9\x44" => "\xE9\x81\x93", + "\xB9\x45" => "\xE9\x81\x82", + "\xB9\x46" => "\xE9\x81\x94", + "\xB9\x47" => "\xE9\x80\xBC", + "\xB9\x48" => "\xE9\x81\x95", + "\xB9\x49" => "\xE9\x81\x90", + "\xB9\x4A" => "\xE9\x81\x87", + "\xB9\x4B" => "\xE9\x81\x8F", + "\xB9\x4C" => "\xE9\x81\x8E", + "\xB9\x4D" => "\xE9\x81\x8D", + "\xB9\x4E" => "\xE9\x81\x91", + "\xB9\x4F" => "\xE9\x80\xBE", + "\xB9\x50" => "\xE9\x81\x81", + "\xB9\x51" => "\xE9\x84\x92", + "\xB9\x52" => "\xE9\x84\x97", + "\xB9\x53" => "\xE9\x85\xAC", + "\xB9\x54" => "\xE9\x85\xAA", + "\xB9\x55" => "\xE9\x85\xA9", + "\xB9\x56" => "\xE9\x87\x89", + "\xB9\x57" => "\xE9\x88\xB7", + "\xB9\x58" => "\xE9\x89\x97", + "\xB9\x59" => "\xE9\x88\xB8", + "\xB9\x5A" => "\xE9\x88\xBD", + "\xB9\x5B" => "\xE9\x89\x80", + "\xB9\x5C" => "\xE9\x88\xBE", + "\xB9\x5D" => "\xE9\x89\x9B", + "\xB9\x5E" => "\xE9\x89\x8B", + "\xB9\x5F" => "\xE9\x89\xA4", + "\xB9\x60" => "\xE9\x89\x91", + "\xB9\x61" => "\xE9\x88\xB4", + "\xB9\x62" => "\xE9\x89\x89", + "\xB9\x63" => "\xE9\x89\x8D", + "\xB9\x64" => "\xE9\x89\x85", + "\xB9\x65" => "\xE9\x88\xB9", + "\xB9\x66" => "\xE9\x88\xBF", + "\xB9\x67" => "\xE9\x89\x9A", + "\xB9\x68" => "\xE9\x96\x98", + "\xB9\x69" => "\xE9\x9A\x98", + "\xB9\x6A" => "\xE9\x9A\x94", + "\xB9\x6B" => "\xE9\x9A\x95", + "\xB9\x6C" => "\xE9\x9B\x8D", + "\xB9\x6D" => "\xE9\x9B\x8B", + "\xB9\x6E" => "\xE9\x9B\x89", + "\xB9\x6F" => "\xE9\x9B\x8A", + "\xB9\x70" => "\xE9\x9B\xB7", + "\xB9\x71" => "\xE9\x9B\xBB", + "\xB9\x72" => "\xE9\x9B\xB9", + "\xB9\x73" => "\xE9\x9B\xB6", + "\xB9\x74" => "\xE9\x9D\x96", + "\xB9\x75" => "\xE9\x9D\xB4", + "\xB9\x76" => "\xE9\x9D\xB6", + "\xB9\x77" => "\xE9\xA0\x90", + "\xB9\x78" => "\xE9\xA0\x91", + "\xB9\x79" => "\xE9\xA0\x93", + "\xB9\x7A" => "\xE9\xA0\x8A", + "\xB9\x7B" => "\xE9\xA0\x92", + "\xB9\x7C" => "\xE9\xA0\x8C", + "\xB9\x7D" => "\xE9\xA3\xBC", + "\xB9\x7E" => "\xE9\xA3\xB4", + "\xB9\xA1" => "\xE9\xA3\xBD", + "\xB9\xA2" => "\xE9\xA3\xBE", + "\xB9\xA3" => "\xE9\xA6\xB3", + "\xB9\xA4" => "\xE9\xA6\xB1", + "\xB9\xA5" => "\xE9\xA6\xB4", + "\xB9\xA6" => "\xE9\xAB\xA1", + "\xB9\xA7" => "\xE9\xB3\xA9", + "\xB9\xA8" => "\xE9\xBA\x82", + "\xB9\xA9" => "\xE9\xBC\x8E", + "\xB9\xAA" => "\xE9\xBC\x93", + "\xB9\xAB" => "\xE9\xBC\xA0", + "\xB9\xAC" => "\xE5\x83\xA7", + "\xB9\xAD" => "\xE5\x83\xAE", + "\xB9\xAE" => "\xE5\x83\xA5", + "\xB9\xAF" => "\xE5\x83\x96", + "\xB9\xB0" => "\xE5\x83\xAD", + "\xB9\xB1" => "\xE5\x83\x9A", + "\xB9\xB2" => "\xE5\x83\x95", + "\xB9\xB3" => "\xE5\x83\x8F", + "\xB9\xB4" => "\xE5\x83\x91", + "\xB9\xB5" => "\xE5\x83\xB1", + "\xB9\xB6" => "\xE5\x83\x8E", + "\xB9\xB7" => "\xE5\x83\xA9", + "\xB9\xB8" => "\xE5\x85\xA2", + "\xB9\xB9" => "\xE5\x87\xB3", + "\xB9\xBA" => "\xE5\x8A\x83", + "\xB9\xBB" => "\xE5\x8A\x82", + "\xB9\xBC" => "\xE5\x8C\xB1", + "\xB9\xBD" => "\xE5\x8E\xAD", + "\xB9\xBE" => "\xE5\x97\xBE", + "\xB9\xBF" => "\xE5\x98\x80", + "\xB9\xC0" => "\xE5\x98\x9B", + "\xB9\xC1" => "\xE5\x98\x97", + "\xB9\xC2" => "\xE5\x97\xBD", + "\xB9\xC3" => "\xE5\x98\x94", + "\xB9\xC4" => "\xE5\x98\x86", + "\xB9\xC5" => "\xE5\x98\x89", + "\xB9\xC6" => "\xE5\x98\x8D", + "\xB9\xC7" => "\xE5\x98\x8E", + "\xB9\xC8" => "\xE5\x97\xB7", + "\xB9\xC9" => "\xE5\x98\x96", + "\xB9\xCA" => "\xE5\x98\x9F", + "\xB9\xCB" => "\xE5\x98\x88", + "\xB9\xCC" => "\xE5\x98\x90", + "\xB9\xCD" => "\xE5\x97\xB6", + "\xB9\xCE" => "\xE5\x9C\x98", + "\xB9\xCF" => "\xE5\x9C\x96", + "\xB9\xD0" => "\xE5\xA1\xB5", + "\xB9\xD1" => "\xE5\xA1\xBE", + "\xB9\xD2" => "\xE5\xA2\x83", + "\xB9\xD3" => "\xE5\xA2\x93", + "\xB9\xD4" => "\xE5\xA2\x8A", + "\xB9\xD5" => "\xE5\xA1\xB9", + "\xB9\xD6" => "\xE5\xA2\x85", + "\xB9\xD7" => "\xE5\xA1\xBD", + "\xB9\xD8" => "\xE5\xA3\xBD", + "\xB9\xD9" => "\xE5\xA4\xA5", + "\xB9\xDA" => "\xE5\xA4\xA2", + "\xB9\xDB" => "\xE5\xA4\xA4", + "\xB9\xDC" => "\xE5\xA5\xAA", + "\xB9\xDD" => "\xE5\xA5\xA9", + "\xB9\xDE" => "\xE5\xAB\xA1", + "\xB9\xDF" => "\xE5\xAB\xA6", + "\xB9\xE0" => "\xE5\xAB\xA9", + "\xB9\xE1" => "\xE5\xAB\x97", + "\xB9\xE2" => "\xE5\xAB\x96", + "\xB9\xE3" => "\xE5\xAB\x98", + "\xB9\xE4" => "\xE5\xAB\xA3", + "\xB9\xE5" => "\xE5\xAD\xB5", + "\xB9\xE6" => "\xE5\xAF\x9E", + "\xB9\xE7" => "\xE5\xAF\xA7", + "\xB9\xE8" => "\xE5\xAF\xA1", + "\xB9\xE9" => "\xE5\xAF\xA5", + "\xB9\xEA" => "\xE5\xAF\xA6", + "\xB9\xEB" => "\xE5\xAF\xA8", + "\xB9\xEC" => "\xE5\xAF\xA2", + "\xB9\xED" => "\xE5\xAF\xA4", + "\xB9\xEE" => "\xE5\xAF\x9F", + "\xB9\xEF" => "\xE5\xB0\x8D", + "\xB9\xF0" => "\xE5\xB1\xA2", + "\xB9\xF1" => "\xE5\xB6\x84", + "\xB9\xF2" => "\xE5\xB6\x87", + "\xB9\xF3" => "\xE5\xB9\x9B", + "\xB9\xF4" => "\xE5\xB9\xA3", + "\xB9\xF5" => "\xE5\xB9\x95", + "\xB9\xF6" => "\xE5\xB9\x97", + "\xB9\xF7" => "\xE5\xB9\x94", + "\xB9\xF8" => "\xE5\xBB\x93", + "\xB9\xF9" => "\xE5\xBB\x96", + "\xB9\xFA" => "\xE5\xBC\x8A", + "\xB9\xFB" => "\xE5\xBD\x86", + "\xB9\xFC" => "\xE5\xBD\xB0", + "\xB9\xFD" => "\xE5\xBE\xB9", + "\xB9\xFE" => "\xE6\x85\x87", + "\xBA\x40" => "\xE6\x84\xBF", + "\xBA\x41" => "\xE6\x85\x8B", + "\xBA\x42" => "\xE6\x85\xB7", + "\xBA\x43" => "\xE6\x85\xA2", + "\xBA\x44" => "\xE6\x85\xA3", + "\xBA\x45" => "\xE6\x85\x9F", + "\xBA\x46" => "\xE6\x85\x9A", + "\xBA\x47" => "\xE6\x85\x98", + "\xBA\x48" => "\xE6\x85\xB5", + "\xBA\x49" => "\xE6\x88\xAA", + "\xBA\x4A" => "\xE6\x92\x87", + "\xBA\x4B" => "\xE6\x91\x98", + "\xBA\x4C" => "\xE6\x91\x94", + "\xBA\x4D" => "\xE6\x92\xA4", + "\xBA\x4E" => "\xE6\x91\xB8", + "\xBA\x4F" => "\xE6\x91\x9F", + "\xBA\x50" => "\xE6\x91\xBA", + "\xBA\x51" => "\xE6\x91\x91", + "\xBA\x52" => "\xE6\x91\xA7", + "\xBA\x53" => "\xE6\x90\xB4", + "\xBA\x54" => "\xE6\x91\xAD", + "\xBA\x55" => "\xE6\x91\xBB", + "\xBA\x56" => "\xE6\x95\xB2", + "\xBA\x57" => "\xE6\x96\xA1", + "\xBA\x58" => "\xE6\x97\x97", + "\xBA\x59" => "\xE6\x97\x96", + "\xBA\x5A" => "\xE6\x9A\xA2", + "\xBA\x5B" => "\xE6\x9A\xA8", + "\xBA\x5C" => "\xE6\x9A\x9D", + "\xBA\x5D" => "\xE6\xA6\x9C", + "\xBA\x5E" => "\xE6\xA6\xA8", + "\xBA\x5F" => "\xE6\xA6\x95", + "\xBA\x60" => "\xE6\xA7\x81", + "\xBA\x61" => "\xE6\xA6\xAE", + "\xBA\x62" => "\xE6\xA7\x93", + "\xBA\x63" => "\xE6\xA7\x8B", + "\xBA\x64" => "\xE6\xA6\x9B", + "\xBA\x65" => "\xE6\xA6\xB7", + "\xBA\x66" => "\xE6\xA6\xBB", + "\xBA\x67" => "\xE6\xA6\xAB", + "\xBA\x68" => "\xE6\xA6\xB4", + "\xBA\x69" => "\xE6\xA7\x90", + "\xBA\x6A" => "\xE6\xA7\x8D", + "\xBA\x6B" => "\xE6\xA6\xAD", + "\xBA\x6C" => "\xE6\xA7\x8C", + "\xBA\x6D" => "\xE6\xA6\xA6", + "\xBA\x6E" => "\xE6\xA7\x83", + "\xBA\x6F" => "\xE6\xA6\xA3", + "\xBA\x70" => "\xE6\xAD\x89", + "\xBA\x71" => "\xE6\xAD\x8C", + "\xBA\x72" => "\xE6\xB0\xB3", + "\xBA\x73" => "\xE6\xBC\xB3", + "\xBA\x74" => "\xE6\xBC\x94", + "\xBA\x75" => "\xE6\xBB\xBE", + "\xBA\x76" => "\xE6\xBC\x93", + "\xBA\x77" => "\xE6\xBB\xB4", + "\xBA\x78" => "\xE6\xBC\xA9", + "\xBA\x79" => "\xE6\xBC\xBE", + "\xBA\x7A" => "\xE6\xBC\xA0", + "\xBA\x7B" => "\xE6\xBC\xAC", + "\xBA\x7C" => "\xE6\xBC\x8F", + "\xBA\x7D" => "\xE6\xBC\x82", + "\xBA\x7E" => "\xE6\xBC\xA2", + "\xBA\xA1" => "\xE6\xBB\xBF", + "\xBA\xA2" => "\xE6\xBB\xAF", + "\xBA\xA3" => "\xE6\xBC\x86", + "\xBA\xA4" => "\xE6\xBC\xB1", + "\xBA\xA5" => "\xE6\xBC\xB8", + "\xBA\xA6" => "\xE6\xBC\xB2", + "\xBA\xA7" => "\xE6\xBC\xA3", + "\xBA\xA8" => "\xE6\xBC\x95", + "\xBA\xA9" => "\xE6\xBC\xAB", + "\xBA\xAA" => "\xE6\xBC\xAF", + "\xBA\xAB" => "\xE6\xBE\x88", + "\xBA\xAC" => "\xE6\xBC\xAA", + "\xBA\xAD" => "\xE6\xBB\xAC", + "\xBA\xAE" => "\xE6\xBC\x81", + "\xBA\xAF" => "\xE6\xBB\xB2", + "\xBA\xB0" => "\xE6\xBB\x8C", + "\xBA\xB1" => "\xE6\xBB\xB7", + "\xBA\xB2" => "\xE7\x86\x94", + "\xBA\xB3" => "\xE7\x86\x99", + "\xBA\xB4" => "\xE7\x85\xBD", + "\xBA\xB5" => "\xE7\x86\x8A", + "\xBA\xB6" => "\xE7\x86\x84", + "\xBA\xB7" => "\xE7\x86\x92", + "\xBA\xB8" => "\xE7\x88\xBE", + "\xBA\xB9" => "\xE7\x8A\x92", + "\xBA\xBA" => "\xE7\x8A\x96", + "\xBA\xBB" => "\xE7\x8D\x84", + "\xBA\xBC" => "\xE7\x8D\x90", + "\xBA\xBD" => "\xE7\x91\xA4", + "\xBA\xBE" => "\xE7\x91\xA3", + "\xBA\xBF" => "\xE7\x91\xAA", + "\xBA\xC0" => "\xE7\x91\xB0", + "\xBA\xC1" => "\xE7\x91\xAD", + "\xBA\xC2" => "\xE7\x94\x84", + "\xBA\xC3" => "\xE7\x96\x91", + "\xBA\xC4" => "\xE7\x98\xA7", + "\xBA\xC5" => "\xE7\x98\x8D", + "\xBA\xC6" => "\xE7\x98\x8B", + "\xBA\xC7" => "\xE7\x98\x89", + "\xBA\xC8" => "\xE7\x98\x93", + "\xBA\xC9" => "\xE7\x9B\xA1", + "\xBA\xCA" => "\xE7\x9B\xA3", + "\xBA\xCB" => "\xE7\x9E\x84", + "\xBA\xCC" => "\xE7\x9D\xBD", + "\xBA\xCD" => "\xE7\x9D\xBF", + "\xBA\xCE" => "\xE7\x9D\xA1", + "\xBA\xCF" => "\xE7\xA3\x81", + "\xBA\xD0" => "\xE7\xA2\x9F", + "\xBA\xD1" => "\xE7\xA2\xA7", + "\xBA\xD2" => "\xE7\xA2\xB3", + "\xBA\xD3" => "\xE7\xA2\xA9", + "\xBA\xD4" => "\xE7\xA2\xA3", + "\xBA\xD5" => "\xE7\xA6\x8E", + "\xBA\xD6" => "\xE7\xA6\x8F", + "\xBA\xD7" => "\xE7\xA6\x8D", + "\xBA\xD8" => "\xE7\xA8\xAE", + "\xBA\xD9" => "\xE7\xA8\xB1", + "\xBA\xDA" => "\xE7\xAA\xAA", + "\xBA\xDB" => "\xE7\xAA\xA9", + "\xBA\xDC" => "\xE7\xAB\xAD", + "\xBA\xDD" => "\xE7\xAB\xAF", + "\xBA\xDE" => "\xE7\xAE\xA1", + "\xBA\xDF" => "\xE7\xAE\x95", + "\xBA\xE0" => "\xE7\xAE\x8B", + "\xBA\xE1" => "\xE7\xAD\xB5", + "\xBA\xE2" => "\xE7\xAE\x97", + "\xBA\xE3" => "\xE7\xAE\x9D", + "\xBA\xE4" => "\xE7\xAE\x94", + "\xBA\xE5" => "\xE7\xAE\x8F", + "\xBA\xE6" => "\xE7\xAE\xB8", + "\xBA\xE7" => "\xE7\xAE\x87", + "\xBA\xE8" => "\xE7\xAE\x84", + "\xBA\xE9" => "\xE7\xB2\xB9", + "\xBA\xEA" => "\xE7\xB2\xBD", + "\xBA\xEB" => "\xE7\xB2\xBE", + "\xBA\xEC" => "\xE7\xB6\xBB", + "\xBA\xED" => "\xE7\xB6\xB0", + "\xBA\xEE" => "\xE7\xB6\x9C", + "\xBA\xEF" => "\xE7\xB6\xBD", + "\xBA\xF0" => "\xE7\xB6\xBE", + "\xBA\xF1" => "\xE7\xB6\xA0", + "\xBA\xF2" => "\xE7\xB7\x8A", + "\xBA\xF3" => "\xE7\xB6\xB4", + "\xBA\xF4" => "\xE7\xB6\xB2", + "\xBA\xF5" => "\xE7\xB6\xB1", + "\xBA\xF6" => "\xE7\xB6\xBA", + "\xBA\xF7" => "\xE7\xB6\xA2", + "\xBA\xF8" => "\xE7\xB6\xBF", + "\xBA\xF9" => "\xE7\xB6\xB5", + "\xBA\xFA" => "\xE7\xB6\xB8", + "\xBA\xFB" => "\xE7\xB6\xAD", + "\xBA\xFC" => "\xE7\xB7\x92", + "\xBA\xFD" => "\xE7\xB7\x87", + "\xBA\xFE" => "\xE7\xB6\xAC", + "\xBB\x40" => "\xE7\xBD\xB0", + "\xBB\x41" => "\xE7\xBF\xA0", + "\xBB\x42" => "\xE7\xBF\xA1", + "\xBB\x43" => "\xE7\xBF\x9F", + "\xBB\x44" => "\xE8\x81\x9E", + "\xBB\x45" => "\xE8\x81\x9A", + "\xBB\x46" => "\xE8\x82\x87", + "\xBB\x47" => "\xE8\x85\x90", + "\xBB\x48" => "\xE8\x86\x80", + "\xBB\x49" => "\xE8\x86\x8F", + "\xBB\x4A" => "\xE8\x86\x88", + "\xBB\x4B" => "\xE8\x86\x8A", + "\xBB\x4C" => "\xE8\x85\xBF", + "\xBB\x4D" => "\xE8\x86\x82", + "\xBB\x4E" => "\xE8\x87\xA7", + "\xBB\x4F" => "\xE8\x87\xBA", + "\xBB\x50" => "\xE8\x88\x87", + "\xBB\x51" => "\xE8\x88\x94", + "\xBB\x52" => "\xE8\x88\x9E", + "\xBB\x53" => "\xE8\x89\x8B", + "\xBB\x54" => "\xE8\x93\x89", + "\xBB\x55" => "\xE8\x92\xBF", + "\xBB\x56" => "\xE8\x93\x86", + "\xBB\x57" => "\xE8\x93\x84", + "\xBB\x58" => "\xE8\x92\x99", + "\xBB\x59" => "\xE8\x92\x9E", + "\xBB\x5A" => "\xE8\x92\xB2", + "\xBB\x5B" => "\xE8\x92\x9C", + "\xBB\x5C" => "\xE8\x93\x8B", + "\xBB\x5D" => "\xE8\x92\xB8", + "\xBB\x5E" => "\xE8\x93\x80", + "\xBB\x5F" => "\xE8\x93\x93", + "\xBB\x60" => "\xE8\x92\x90", + "\xBB\x61" => "\xE8\x92\xBC", + "\xBB\x62" => "\xE8\x93\x91", + "\xBB\x63" => "\xE8\x93\x8A", + "\xBB\x64" => "\xE8\x9C\xBF", + "\xBB\x65" => "\xE8\x9C\x9C", + "\xBB\x66" => "\xE8\x9C\xBB", + "\xBB\x67" => "\xE8\x9C\xA2", + "\xBB\x68" => "\xE8\x9C\xA5", + "\xBB\x69" => "\xE8\x9C\xB4", + "\xBB\x6A" => "\xE8\x9C\x98", + "\xBB\x6B" => "\xE8\x9D\x95", + "\xBB\x6C" => "\xE8\x9C\xB7", + "\xBB\x6D" => "\xE8\x9C\xA9", + "\xBB\x6E" => "\xE8\xA3\xB3", + "\xBB\x6F" => "\xE8\xA4\x82", + "\xBB\x70" => "\xE8\xA3\xB4", + "\xBB\x71" => "\xE8\xA3\xB9", + "\xBB\x72" => "\xE8\xA3\xB8", + "\xBB\x73" => "\xE8\xA3\xBD", + "\xBB\x74" => "\xE8\xA3\xA8", + "\xBB\x75" => "\xE8\xA4\x9A", + "\xBB\x76" => "\xE8\xA3\xAF", + "\xBB\x77" => "\xE8\xAA\xA6", + "\xBB\x78" => "\xE8\xAA\x8C", + "\xBB\x79" => "\xE8\xAA\x9E", + "\xBB\x7A" => "\xE8\xAA\xA3", + "\xBB\x7B" => "\xE8\xAA\x8D", + "\xBB\x7C" => "\xE8\xAA\xA1", + "\xBB\x7D" => "\xE8\xAA\x93", + "\xBB\x7E" => "\xE8\xAA\xA4", + "\xBB\xA1" => "\xE8\xAA\xAA", + "\xBB\xA2" => "\xE8\xAA\xA5", + "\xBB\xA3" => "\xE8\xAA\xA8", + "\xBB\xA4" => "\xE8\xAA\x98", + "\xBB\xA5" => "\xE8\xAA\x91", + "\xBB\xA6" => "\xE8\xAA\x9A", + "\xBB\xA7" => "\xE8\xAA\xA7", + "\xBB\xA8" => "\xE8\xB1\xAA", + "\xBB\xA9" => "\xE8\xB2\x8D", + "\xBB\xAA" => "\xE8\xB2\x8C", + "\xBB\xAB" => "\xE8\xB3\x93", + "\xBB\xAC" => "\xE8\xB3\x91", + "\xBB\xAD" => "\xE8\xB3\x92", + "\xBB\xAE" => "\xE8\xB5\xAB", + "\xBB\xAF" => "\xE8\xB6\x99", + "\xBB\xB0" => "\xE8\xB6\x95", + "\xBB\xB1" => "\xE8\xB7\xBC", + "\xBB\xB2" => "\xE8\xBC\x94", + "\xBB\xB3" => "\xE8\xBC\x92", + "\xBB\xB4" => "\xE8\xBC\x95", + "\xBB\xB5" => "\xE8\xBC\x93", + "\xBB\xB6" => "\xE8\xBE\xA3", + "\xBB\xB7" => "\xE9\x81\xA0", + "\xBB\xB8" => "\xE9\x81\x98", + "\xBB\xB9" => "\xE9\x81\x9C", + "\xBB\xBA" => "\xE9\x81\xA3", + "\xBB\xBB" => "\xE9\x81\x99", + "\xBB\xBC" => "\xE9\x81\x9E", + "\xBB\xBD" => "\xE9\x81\xA2", + "\xBB\xBE" => "\xE9\x81\x9D", + "\xBB\xBF" => "\xE9\x81\x9B", + "\xBB\xC0" => "\xE9\x84\x99", + "\xBB\xC1" => "\xE9\x84\x98", + "\xBB\xC2" => "\xE9\x84\x9E", + "\xBB\xC3" => "\xE9\x85\xB5", + "\xBB\xC4" => "\xE9\x85\xB8", + "\xBB\xC5" => "\xE9\x85\xB7", + "\xBB\xC6" => "\xE9\x85\xB4", + "\xBB\xC7" => "\xE9\x89\xB8", + "\xBB\xC8" => "\xE9\x8A\x80", + "\xBB\xC9" => "\xE9\x8A\x85", + "\xBB\xCA" => "\xE9\x8A\x98", + "\xBB\xCB" => "\xE9\x8A\x96", + "\xBB\xCC" => "\xE9\x89\xBB", + "\xBB\xCD" => "\xE9\x8A\x93", + "\xBB\xCE" => "\xE9\x8A\x9C", + "\xBB\xCF" => "\xE9\x8A\xA8", + "\xBB\xD0" => "\xE9\x89\xBC", + "\xBB\xD1" => "\xE9\x8A\x91", + "\xBB\xD2" => "\xE9\x96\xA1", + "\xBB\xD3" => "\xE9\x96\xA8", + "\xBB\xD4" => "\xE9\x96\xA9", + "\xBB\xD5" => "\xE9\x96\xA3", + "\xBB\xD6" => "\xE9\x96\xA5", + "\xBB\xD7" => "\xE9\x96\xA4", + "\xBB\xD8" => "\xE9\x9A\x99", + "\xBB\xD9" => "\xE9\x9A\x9C", + "\xBB\xDA" => "\xE9\x9A\x9B", + "\xBB\xDB" => "\xE9\x9B\x8C", + "\xBB\xDC" => "\xE9\x9B\x92", + "\xBB\xDD" => "\xE9\x9C\x80", + "\xBB\xDE" => "\xE9\x9D\xBC", + "\xBB\xDF" => "\xE9\x9E\x85", + "\xBB\xE0" => "\xE9\x9F\xB6", + "\xBB\xE1" => "\xE9\xA0\x97", + "\xBB\xE2" => "\xE9\xA0\x98", + "\xBB\xE3" => "\xE9\xA2\xAF", + "\xBB\xE4" => "\xE9\xA2\xB1", + "\xBB\xE5" => "\xE9\xA4\x83", + "\xBB\xE6" => "\xE9\xA4\x85", + "\xBB\xE7" => "\xE9\xA4\x8C", + "\xBB\xE8" => "\xE9\xA4\x89", + "\xBB\xE9" => "\xE9\xA7\x81", + "\xBB\xEA" => "\xE9\xAA\xAF", + "\xBB\xEB" => "\xE9\xAA\xB0", + "\xBB\xEC" => "\xE9\xAB\xA6", + "\xBB\xED" => "\xE9\xAD\x81", + "\xBB\xEE" => "\xE9\xAD\x82", + "\xBB\xEF" => "\xE9\xB3\xB4", + "\xBB\xF0" => "\xE9\xB3\xB6", + "\xBB\xF1" => "\xE9\xB3\xB3", + "\xBB\xF2" => "\xE9\xBA\xBC", + "\xBB\xF3" => "\xE9\xBC\xBB", + "\xBB\xF4" => "\xE9\xBD\x8A", + "\xBB\xF5" => "\xE5\x84\x84", + "\xBB\xF6" => "\xE5\x84\x80", + "\xBB\xF7" => "\xE5\x83\xBB", + "\xBB\xF8" => "\xE5\x83\xB5", + "\xBB\xF9" => "\xE5\x83\xB9", + "\xBB\xFA" => "\xE5\x84\x82", + "\xBB\xFB" => "\xE5\x84\x88", + "\xBB\xFC" => "\xE5\x84\x89", + "\xBB\xFD" => "\xE5\x84\x85", + "\xBB\xFE" => "\xE5\x87\x9C", + "\xBC\x40" => "\xE5\x8A\x87", + "\xBC\x41" => "\xE5\x8A\x88", + "\xBC\x42" => "\xE5\x8A\x89", + "\xBC\x43" => "\xE5\x8A\x8D", + "\xBC\x44" => "\xE5\x8A\x8A", + "\xBC\x45" => "\xE5\x8B\xB0", + "\xBC\x46" => "\xE5\x8E\xB2", + "\xBC\x47" => "\xE5\x98\xAE", + "\xBC\x48" => "\xE5\x98\xBB", + "\xBC\x49" => "\xE5\x98\xB9", + "\xBC\x4A" => "\xE5\x98\xB2", + "\xBC\x4B" => "\xE5\x98\xBF", + "\xBC\x4C" => "\xE5\x98\xB4", + "\xBC\x4D" => "\xE5\x98\xA9", + "\xBC\x4E" => "\xE5\x99\x93", + "\xBC\x4F" => "\xE5\x99\x8E", + "\xBC\x50" => "\xE5\x99\x97", + "\xBC\x51" => "\xE5\x99\xB4", + "\xBC\x52" => "\xE5\x98\xB6", + "\xBC\x53" => "\xE5\x98\xAF", + "\xBC\x54" => "\xE5\x98\xB0", + "\xBC\x55" => "\xE5\xA2\x80", + "\xBC\x56" => "\xE5\xA2\x9F", + "\xBC\x57" => "\xE5\xA2\x9E", + "\xBC\x58" => "\xE5\xA2\xB3", + "\xBC\x59" => "\xE5\xA2\x9C", + "\xBC\x5A" => "\xE5\xA2\xAE", + "\xBC\x5B" => "\xE5\xA2\xA9", + "\xBC\x5C" => "\xE5\xA2\xA6", + "\xBC\x5D" => "\xE5\xA5\xAD", + "\xBC\x5E" => "\xE5\xAC\x89", + "\xBC\x5F" => "\xE5\xAB\xBB", + "\xBC\x60" => "\xE5\xAC\x8B", + "\xBC\x61" => "\xE5\xAB\xB5", + "\xBC\x62" => "\xE5\xAC\x8C", + "\xBC\x63" => "\xE5\xAC\x88", + "\xBC\x64" => "\xE5\xAF\xAE", + "\xBC\x65" => "\xE5\xAF\xAC", + "\xBC\x66" => "\xE5\xAF\xA9", + "\xBC\x67" => "\xE5\xAF\xAB", + "\xBC\x68" => "\xE5\xB1\xA4", + "\xBC\x69" => "\xE5\xB1\xA5", + "\xBC\x6A" => "\xE5\xB6\x9D", + "\xBC\x6B" => "\xE5\xB6\x94", + "\xBC\x6C" => "\xE5\xB9\xA2", + "\xBC\x6D" => "\xE5\xB9\x9F", + "\xBC\x6E" => "\xE5\xB9\xA1", + "\xBC\x6F" => "\xE5\xBB\xA2", + "\xBC\x70" => "\xE5\xBB\x9A", + "\xBC\x71" => "\xE5\xBB\x9F", + "\xBC\x72" => "\xE5\xBB\x9D", + "\xBC\x73" => "\xE5\xBB\xA3", + "\xBC\x74" => "\xE5\xBB\xA0", + "\xBC\x75" => "\xE5\xBD\x88", + "\xBC\x76" => "\xE5\xBD\xB1", + "\xBC\x77" => "\xE5\xBE\xB7", + "\xBC\x78" => "\xE5\xBE\xB5", + "\xBC\x79" => "\xE6\x85\xB6", + "\xBC\x7A" => "\xE6\x85\xA7", + "\xBC\x7B" => "\xE6\x85\xAE", + "\xBC\x7C" => "\xE6\x85\x9D", + "\xBC\x7D" => "\xE6\x85\x95", + "\xBC\x7E" => "\xE6\x86\x82", + "\xBC\xA1" => "\xE6\x85\xBC", + "\xBC\xA2" => "\xE6\x85\xB0", + "\xBC\xA3" => "\xE6\x85\xAB", + "\xBC\xA4" => "\xE6\x85\xBE", + "\xBC\xA5" => "\xE6\x86\xA7", + "\xBC\xA6" => "\xE6\x86\x90", + "\xBC\xA7" => "\xE6\x86\xAB", + "\xBC\xA8" => "\xE6\x86\x8E", + "\xBC\xA9" => "\xE6\x86\xAC", + "\xBC\xAA" => "\xE6\x86\x9A", + "\xBC\xAB" => "\xE6\x86\xA4", + "\xBC\xAC" => "\xE6\x86\x94", + "\xBC\xAD" => "\xE6\x86\xAE", + "\xBC\xAE" => "\xE6\x88\xAE", + "\xBC\xAF" => "\xE6\x91\xA9", + "\xBC\xB0" => "\xE6\x91\xAF", + "\xBC\xB1" => "\xE6\x91\xB9", + "\xBC\xB2" => "\xE6\x92\x9E", + "\xBC\xB3" => "\xE6\x92\xB2", + "\xBC\xB4" => "\xE6\x92\x88", + "\xBC\xB5" => "\xE6\x92\x90", + "\xBC\xB6" => "\xE6\x92\xB0", + "\xBC\xB7" => "\xE6\x92\xA5", + "\xBC\xB8" => "\xE6\x92\x93", + "\xBC\xB9" => "\xE6\x92\x95", + "\xBC\xBA" => "\xE6\x92\xA9", + "\xBC\xBB" => "\xE6\x92\x92", + "\xBC\xBC" => "\xE6\x92\xAE", + "\xBC\xBD" => "\xE6\x92\xAD", + "\xBC\xBE" => "\xE6\x92\xAB", + "\xBC\xBF" => "\xE6\x92\x9A", + "\xBC\xC0" => "\xE6\x92\xAC", + "\xBC\xC1" => "\xE6\x92\x99", + "\xBC\xC2" => "\xE6\x92\xA2", + "\xBC\xC3" => "\xE6\x92\xB3", + "\xBC\xC4" => "\xE6\x95\xB5", + "\xBC\xC5" => "\xE6\x95\xB7", + "\xBC\xC6" => "\xE6\x95\xB8", + "\xBC\xC7" => "\xE6\x9A\xAE", + "\xBC\xC8" => "\xE6\x9A\xAB", + "\xBC\xC9" => "\xE6\x9A\xB4", + "\xBC\xCA" => "\xE6\x9A\xB1", + "\xBC\xCB" => "\xE6\xA8\xA3", + "\xBC\xCC" => "\xE6\xA8\x9F", + "\xBC\xCD" => "\xE6\xA7\xA8", + "\xBC\xCE" => "\xE6\xA8\x81", + "\xBC\xCF" => "\xE6\xA8\x9E", + "\xBC\xD0" => "\xE6\xA8\x99", + "\xBC\xD1" => "\xE6\xA7\xBD", + "\xBC\xD2" => "\xE6\xA8\xA1", + "\xBC\xD3" => "\xE6\xA8\x93", + "\xBC\xD4" => "\xE6\xA8\x8A", + "\xBC\xD5" => "\xE6\xA7\xB3", + "\xBC\xD6" => "\xE6\xA8\x82", + "\xBC\xD7" => "\xE6\xA8\x85", + "\xBC\xD8" => "\xE6\xA7\xAD", + "\xBC\xD9" => "\xE6\xA8\x91", + "\xBC\xDA" => "\xE6\xAD\x90", + "\xBC\xDB" => "\xE6\xAD\x8E", + "\xBC\xDC" => "\xE6\xAE\xA4", + "\xBC\xDD" => "\xE6\xAF\x85", + "\xBC\xDE" => "\xE6\xAF\x86", + "\xBC\xDF" => "\xE6\xBC\xBF", + "\xBC\xE0" => "\xE6\xBD\xBC", + "\xBC\xE1" => "\xE6\xBE\x84", + "\xBC\xE2" => "\xE6\xBD\x91", + "\xBC\xE3" => "\xE6\xBD\xA6", + "\xBC\xE4" => "\xE6\xBD\x94", + "\xBC\xE5" => "\xE6\xBE\x86", + "\xBC\xE6" => "\xE6\xBD\xAD", + "\xBC\xE7" => "\xE6\xBD\x9B", + "\xBC\xE8" => "\xE6\xBD\xB8", + "\xBC\xE9" => "\xE6\xBD\xAE", + "\xBC\xEA" => "\xE6\xBE\x8E", + "\xBC\xEB" => "\xE6\xBD\xBA", + "\xBC\xEC" => "\xE6\xBD\xB0", + "\xBC\xED" => "\xE6\xBD\xA4", + "\xBC\xEE" => "\xE6\xBE\x97", + "\xBC\xEF" => "\xE6\xBD\x98", + "\xBC\xF0" => "\xE6\xBB\x95", + "\xBC\xF1" => "\xE6\xBD\xAF", + "\xBC\xF2" => "\xE6\xBD\xA0", + "\xBC\xF3" => "\xE6\xBD\x9F", + "\xBC\xF4" => "\xE7\x86\x9F", + "\xBC\xF5" => "\xE7\x86\xAC", + "\xBC\xF6" => "\xE7\x86\xB1", + "\xBC\xF7" => "\xE7\x86\xA8", + "\xBC\xF8" => "\xE7\x89\x96", + "\xBC\xF9" => "\xE7\x8A\x9B", + "\xBC\xFA" => "\xE7\x8D\x8E", + "\xBC\xFB" => "\xE7\x8D\x97", + "\xBC\xFC" => "\xE7\x91\xA9", + "\xBC\xFD" => "\xE7\x92\x8B", + "\xBC\xFE" => "\xE7\x92\x83", + "\xBD\x40" => "\xE7\x91\xBE", + "\xBD\x41" => "\xE7\x92\x80", + "\xBD\x42" => "\xE7\x95\xBF", + "\xBD\x43" => "\xE7\x98\xA0", + "\xBD\x44" => "\xE7\x98\xA9", + "\xBD\x45" => "\xE7\x98\x9F", + "\xBD\x46" => "\xE7\x98\xA4", + "\xBD\x47" => "\xE7\x98\xA6", + "\xBD\x48" => "\xE7\x98\xA1", + "\xBD\x49" => "\xE7\x98\xA2", + "\xBD\x4A" => "\xE7\x9A\x9A", + "\xBD\x4B" => "\xE7\x9A\xBA", + "\xBD\x4C" => "\xE7\x9B\xA4", + "\xBD\x4D" => "\xE7\x9E\x8E", + "\xBD\x4E" => "\xE7\x9E\x87", + "\xBD\x4F" => "\xE7\x9E\x8C", + "\xBD\x50" => "\xE7\x9E\x91", + "\xBD\x51" => "\xE7\x9E\x8B", + "\xBD\x52" => "\xE7\xA3\x8B", + "\xBD\x53" => "\xE7\xA3\x85", + "\xBD\x54" => "\xE7\xA2\xBA", + "\xBD\x55" => "\xE7\xA3\x8A", + "\xBD\x56" => "\xE7\xA2\xBE", + "\xBD\x57" => "\xE7\xA3\x95", + "\xBD\x58" => "\xE7\xA2\xBC", + "\xBD\x59" => "\xE7\xA3\x90", + "\xBD\x5A" => "\xE7\xA8\xBF", + "\xBD\x5B" => "\xE7\xA8\xBC", + "\xBD\x5C" => "\xE7\xA9\x80", + "\xBD\x5D" => "\xE7\xA8\xBD", + "\xBD\x5E" => "\xE7\xA8\xB7", + "\xBD\x5F" => "\xE7\xA8\xBB", + "\xBD\x60" => "\xE7\xAA\xAF", + "\xBD\x61" => "\xE7\xAA\xAE", + "\xBD\x62" => "\xE7\xAE\xAD", + "\xBD\x63" => "\xE7\xAE\xB1", + "\xBD\x64" => "\xE7\xAF\x84", + "\xBD\x65" => "\xE7\xAE\xB4", + "\xBD\x66" => "\xE7\xAF\x86", + "\xBD\x67" => "\xE7\xAF\x87", + "\xBD\x68" => "\xE7\xAF\x81", + "\xBD\x69" => "\xE7\xAE\xA0", + "\xBD\x6A" => "\xE7\xAF\x8C", + "\xBD\x6B" => "\xE7\xB3\x8A", + "\xBD\x6C" => "\xE7\xB7\xA0", + "\xBD\x6D" => "\xE7\xB7\xB4", + "\xBD\x6E" => "\xE7\xB7\xAF", + "\xBD\x6F" => "\xE7\xB7\xBB", + "\xBD\x70" => "\xE7\xB7\x98", + "\xBD\x71" => "\xE7\xB7\xAC", + "\xBD\x72" => "\xE7\xB7\x9D", + "\xBD\x73" => "\xE7\xB7\xA8", + "\xBD\x74" => "\xE7\xB7\xA3", + "\xBD\x75" => "\xE7\xB7\x9A", + "\xBD\x76" => "\xE7\xB7\x9E", + "\xBD\x77" => "\xE7\xB7\xA9", + "\xBD\x78" => "\xE7\xB6\x9E", + "\xBD\x79" => "\xE7\xB7\x99", + "\xBD\x7A" => "\xE7\xB7\xB2", + "\xBD\x7B" => "\xE7\xB7\xB9", + "\xBD\x7C" => "\xE7\xBD\xB5", + "\xBD\x7D" => "\xE7\xBD\xB7", + "\xBD\x7E" => "\xE7\xBE\xAF", + "\xBD\xA1" => "\xE7\xBF\xA9", + "\xBD\xA2" => "\xE8\x80\xA6", + "\xBD\xA3" => "\xE8\x86\x9B", + "\xBD\xA4" => "\xE8\x86\x9C", + "\xBD\xA5" => "\xE8\x86\x9D", + "\xBD\xA6" => "\xE8\x86\xA0", + "\xBD\xA7" => "\xE8\x86\x9A", + "\xBD\xA8" => "\xE8\x86\x98", + "\xBD\xA9" => "\xE8\x94\x97", + "\xBD\xAA" => "\xE8\x94\xBD", + "\xBD\xAB" => "\xE8\x94\x9A", + "\xBD\xAC" => "\xE8\x93\xAE", + "\xBD\xAD" => "\xE8\x94\xAC", + "\xBD\xAE" => "\xE8\x94\xAD", + "\xBD\xAF" => "\xE8\x94\x93", + "\xBD\xB0" => "\xE8\x94\x91", + "\xBD\xB1" => "\xE8\x94\xA3", + "\xBD\xB2" => "\xE8\x94\xA1", + "\xBD\xB3" => "\xE8\x94\x94", + "\xBD\xB4" => "\xE8\x93\xAC", + "\xBD\xB5" => "\xE8\x94\xA5", + "\xBD\xB6" => "\xE8\x93\xBF", + "\xBD\xB7" => "\xE8\x94\x86", + "\xBD\xB8" => "\xE8\x9E\x82", + "\xBD\xB9" => "\xE8\x9D\xB4", + "\xBD\xBA" => "\xE8\x9D\xB6", + "\xBD\xBB" => "\xE8\x9D\xA0", + "\xBD\xBC" => "\xE8\x9D\xA6", + "\xBD\xBD" => "\xE8\x9D\xB8", + "\xBD\xBE" => "\xE8\x9D\xA8", + "\xBD\xBF" => "\xE8\x9D\x99", + "\xBD\xC0" => "\xE8\x9D\x97", + "\xBD\xC1" => "\xE8\x9D\x8C", + "\xBD\xC2" => "\xE8\x9D\x93", + "\xBD\xC3" => "\xE8\xA1\x9B", + "\xBD\xC4" => "\xE8\xA1\x9D", + "\xBD\xC5" => "\xE8\xA4\x90", + "\xBD\xC6" => "\xE8\xA4\x87", + "\xBD\xC7" => "\xE8\xA4\x92", + "\xBD\xC8" => "\xE8\xA4\x93", + "\xBD\xC9" => "\xE8\xA4\x95", + "\xBD\xCA" => "\xE8\xA4\x8A", + "\xBD\xCB" => "\xE8\xAA\xBC", + "\xBD\xCC" => "\xE8\xAB\x92", + "\xBD\xCD" => "\xE8\xAB\x87", + "\xBD\xCE" => "\xE8\xAB\x84", + "\xBD\xCF" => "\xE8\xAA\x95", + "\xBD\xD0" => "\xE8\xAB\x8B", + "\xBD\xD1" => "\xE8\xAB\xB8", + "\xBD\xD2" => "\xE8\xAA\xB2", + "\xBD\xD3" => "\xE8\xAB\x89", + "\xBD\xD4" => "\xE8\xAB\x82", + "\xBD\xD5" => "\xE8\xAA\xBF", + "\xBD\xD6" => "\xE8\xAA\xB0", + "\xBD\xD7" => "\xE8\xAB\x96", + "\xBD\xD8" => "\xE8\xAB\x8D", + "\xBD\xD9" => "\xE8\xAA\xB6", + "\xBD\xDA" => "\xE8\xAA\xB9", + "\xBD\xDB" => "\xE8\xAB\x9B", + "\xBD\xDC" => "\xE8\xB1\x8C", + "\xBD\xDD" => "\xE8\xB1\x8E", + "\xBD\xDE" => "\xE8\xB1\xAC", + "\xBD\xDF" => "\xE8\xB3\xA0", + "\xBD\xE0" => "\xE8\xB3\x9E", + "\xBD\xE1" => "\xE8\xB3\xA6", + "\xBD\xE2" => "\xE8\xB3\xA4", + "\xBD\xE3" => "\xE8\xB3\xAC", + "\xBD\xE4" => "\xE8\xB3\xAD", + "\xBD\xE5" => "\xE8\xB3\xA2", + "\xBD\xE6" => "\xE8\xB3\xA3", + "\xBD\xE7" => "\xE8\xB3\x9C", + "\xBD\xE8" => "\xE8\xB3\xAA", + "\xBD\xE9" => "\xE8\xB3\xA1", + "\xBD\xEA" => "\xE8\xB5\xAD", + "\xBD\xEB" => "\xE8\xB6\x9F", + "\xBD\xEC" => "\xE8\xB6\xA3", + "\xBD\xED" => "\xE8\xB8\xAB", + "\xBD\xEE" => "\xE8\xB8\x90", + "\xBD\xEF" => "\xE8\xB8\x9D", + "\xBD\xF0" => "\xE8\xB8\xA2", + "\xBD\xF1" => "\xE8\xB8\x8F", + "\xBD\xF2" => "\xE8\xB8\xA9", + "\xBD\xF3" => "\xE8\xB8\x9F", + "\xBD\xF4" => "\xE8\xB8\xA1", + "\xBD\xF5" => "\xE8\xB8\x9E", + "\xBD\xF6" => "\xE8\xBA\xBA", + "\xBD\xF7" => "\xE8\xBC\x9D", + "\xBD\xF8" => "\xE8\xBC\x9B", + "\xBD\xF9" => "\xE8\xBC\x9F", + "\xBD\xFA" => "\xE8\xBC\xA9", + "\xBD\xFB" => "\xE8\xBC\xA6", + "\xBD\xFC" => "\xE8\xBC\xAA", + "\xBD\xFD" => "\xE8\xBC\x9C", + "\xBD\xFE" => "\xE8\xBC\x9E", + "\xBE\x40" => "\xE8\xBC\xA5", + "\xBE\x41" => "\xE9\x81\xA9", + "\xBE\x42" => "\xE9\x81\xAE", + "\xBE\x43" => "\xE9\x81\xA8", + "\xBE\x44" => "\xE9\x81\xAD", + "\xBE\x45" => "\xE9\x81\xB7", + "\xBE\x46" => "\xE9\x84\xB0", + "\xBE\x47" => "\xE9\x84\xAD", + "\xBE\x48" => "\xE9\x84\xA7", + "\xBE\x49" => "\xE9\x84\xB1", + "\xBE\x4A" => "\xE9\x86\x87", + "\xBE\x4B" => "\xE9\x86\x89", + "\xBE\x4C" => "\xE9\x86\x8B", + "\xBE\x4D" => "\xE9\x86\x83", + "\xBE\x4E" => "\xE9\x8B\x85", + "\xBE\x4F" => "\xE9\x8A\xBB", + "\xBE\x50" => "\xE9\x8A\xB7", + "\xBE\x51" => "\xE9\x8B\xAA", + "\xBE\x52" => "\xE9\x8A\xAC", + "\xBE\x53" => "\xE9\x8B\xA4", + "\xBE\x54" => "\xE9\x8B\x81", + "\xBE\x55" => "\xE9\x8A\xB3", + "\xBE\x56" => "\xE9\x8A\xBC", + "\xBE\x57" => "\xE9\x8B\x92", + "\xBE\x58" => "\xE9\x8B\x87", + "\xBE\x59" => "\xE9\x8B\xB0", + "\xBE\x5A" => "\xE9\x8A\xB2", + "\xBE\x5B" => "\xE9\x96\xAD", + "\xBE\x5C" => "\xE9\x96\xB1", + "\xBE\x5D" => "\xE9\x9C\x84", + "\xBE\x5E" => "\xE9\x9C\x86", + "\xBE\x5F" => "\xE9\x9C\x87", + "\xBE\x60" => "\xE9\x9C\x89", + "\xBE\x61" => "\xE9\x9D\xA0", + "\xBE\x62" => "\xE9\x9E\x8D", + "\xBE\x63" => "\xE9\x9E\x8B", + "\xBE\x64" => "\xE9\x9E\x8F", + "\xBE\x65" => "\xE9\xA0\xA1", + "\xBE\x66" => "\xE9\xA0\xAB", + "\xBE\x67" => "\xE9\xA0\x9C", + "\xBE\x68" => "\xE9\xA2\xB3", + "\xBE\x69" => "\xE9\xA4\x8A", + "\xBE\x6A" => "\xE9\xA4\x93", + "\xBE\x6B" => "\xE9\xA4\x92", + "\xBE\x6C" => "\xE9\xA4\x98", + "\xBE\x6D" => "\xE9\xA7\x9D", + "\xBE\x6E" => "\xE9\xA7\x90", + "\xBE\x6F" => "\xE9\xA7\x9F", + "\xBE\x70" => "\xE9\xA7\x9B", + "\xBE\x71" => "\xE9\xA7\x91", + "\xBE\x72" => "\xE9\xA7\x95", + "\xBE\x73" => "\xE9\xA7\x92", + "\xBE\x74" => "\xE9\xA7\x99", + "\xBE\x75" => "\xE9\xAA\xB7", + "\xBE\x76" => "\xE9\xAB\xAE", + "\xBE\x77" => "\xE9\xAB\xAF", + "\xBE\x78" => "\xE9\xAC\xA7", + "\xBE\x79" => "\xE9\xAD\x85", + "\xBE\x7A" => "\xE9\xAD\x84", + "\xBE\x7B" => "\xE9\xAD\xB7", + "\xBE\x7C" => "\xE9\xAD\xAF", + "\xBE\x7D" => "\xE9\xB4\x86", + "\xBE\x7E" => "\xE9\xB4\x89", + "\xBE\xA1" => "\xE9\xB4\x83", + "\xBE\xA2" => "\xE9\xBA\xA9", + "\xBE\xA3" => "\xE9\xBA\xBE", + "\xBE\xA4" => "\xE9\xBB\x8E", + "\xBE\xA5" => "\xE5\xA2\xA8", + "\xBE\xA6" => "\xE9\xBD\x92", + "\xBE\xA7" => "\xE5\x84\x92", + "\xBE\xA8" => "\xE5\x84\x98", + "\xBE\xA9" => "\xE5\x84\x94", + "\xBE\xAA" => "\xE5\x84\x90", + "\xBE\xAB" => "\xE5\x84\x95", + "\xBE\xAC" => "\xE5\x86\x80", + "\xBE\xAD" => "\xE5\x86\xAA", + "\xBE\xAE" => "\xE5\x87\x9D", + "\xBE\xAF" => "\xE5\x8A\x91", + "\xBE\xB0" => "\xE5\x8A\x93", + "\xBE\xB1" => "\xE5\x8B\xB3", + "\xBE\xB2" => "\xE5\x99\x99", + "\xBE\xB3" => "\xE5\x99\xAB", + "\xBE\xB4" => "\xE5\x99\xB9", + "\xBE\xB5" => "\xE5\x99\xA9", + "\xBE\xB6" => "\xE5\x99\xA4", + "\xBE\xB7" => "\xE5\x99\xB8", + "\xBE\xB8" => "\xE5\x99\xAA", + "\xBE\xB9" => "\xE5\x99\xA8", + "\xBE\xBA" => "\xE5\x99\xA5", + "\xBE\xBB" => "\xE5\x99\xB1", + "\xBE\xBC" => "\xE5\x99\xAF", + "\xBE\xBD" => "\xE5\x99\xAC", + "\xBE\xBE" => "\xE5\x99\xA2", + "\xBE\xBF" => "\xE5\x99\xB6", + "\xBE\xC0" => "\xE5\xA3\x81", + "\xBE\xC1" => "\xE5\xA2\xBE", + "\xBE\xC2" => "\xE5\xA3\x87", + "\xBE\xC3" => "\xE5\xA3\x85", + "\xBE\xC4" => "\xE5\xA5\xAE", + "\xBE\xC5" => "\xE5\xAC\x9D", + "\xBE\xC6" => "\xE5\xAC\xB4", + "\xBE\xC7" => "\xE5\xAD\xB8", + "\xBE\xC8" => "\xE5\xAF\xB0", + "\xBE\xC9" => "\xE5\xB0\x8E", + "\xBE\xCA" => "\xE5\xBD\x8A", + "\xBE\xCB" => "\xE6\x86\xB2", + "\xBE\xCC" => "\xE6\x86\x91", + "\xBE\xCD" => "\xE6\x86\xA9", + "\xBE\xCE" => "\xE6\x86\x8A", + "\xBE\xCF" => "\xE6\x87\x8D", + "\xBE\xD0" => "\xE6\x86\xB6", + "\xBE\xD1" => "\xE6\x86\xBE", + "\xBE\xD2" => "\xE6\x87\x8A", + "\xBE\xD3" => "\xE6\x87\x88", + "\xBE\xD4" => "\xE6\x88\xB0", + "\xBE\xD5" => "\xE6\x93\x85", + "\xBE\xD6" => "\xE6\x93\x81", + "\xBE\xD7" => "\xE6\x93\x8B", + "\xBE\xD8" => "\xE6\x92\xBB", + "\xBE\xD9" => "\xE6\x92\xBC", + "\xBE\xDA" => "\xE6\x93\x9A", + "\xBE\xDB" => "\xE6\x93\x84", + "\xBE\xDC" => "\xE6\x93\x87", + "\xBE\xDD" => "\xE6\x93\x82", + "\xBE\xDE" => "\xE6\x93\x8D", + "\xBE\xDF" => "\xE6\x92\xBF", + "\xBE\xE0" => "\xE6\x93\x92", + "\xBE\xE1" => "\xE6\x93\x94", + "\xBE\xE2" => "\xE6\x92\xBE", + "\xBE\xE3" => "\xE6\x95\xB4", + "\xBE\xE4" => "\xE6\x9B\x86", + "\xBE\xE5" => "\xE6\x9B\x89", + "\xBE\xE6" => "\xE6\x9A\xB9", + "\xBE\xE7" => "\xE6\x9B\x84", + "\xBE\xE8" => "\xE6\x9B\x87", + "\xBE\xE9" => "\xE6\x9A\xB8", + "\xBE\xEA" => "\xE6\xA8\xBD", + "\xBE\xEB" => "\xE6\xA8\xB8", + "\xBE\xEC" => "\xE6\xA8\xBA", + "\xBE\xED" => "\xE6\xA9\x99", + "\xBE\xEE" => "\xE6\xA9\xAB", + "\xBE\xEF" => "\xE6\xA9\x98", + "\xBE\xF0" => "\xE6\xA8\xB9", + "\xBE\xF1" => "\xE6\xA9\x84", + "\xBE\xF2" => "\xE6\xA9\xA2", + "\xBE\xF3" => "\xE6\xA9\xA1", + "\xBE\xF4" => "\xE6\xA9\x8B", + "\xBE\xF5" => "\xE6\xA9\x87", + "\xBE\xF6" => "\xE6\xA8\xB5", + "\xBE\xF7" => "\xE6\xA9\x9F", + "\xBE\xF8" => "\xE6\xA9\x88", + "\xBE\xF9" => "\xE6\xAD\x99", + "\xBE\xFA" => "\xE6\xAD\xB7", + "\xBE\xFB" => "\xE6\xB0\x85", + "\xBE\xFC" => "\xE6\xBF\x82", + "\xBE\xFD" => "\xE6\xBE\xB1", + "\xBE\xFE" => "\xE6\xBE\xA1", + "\xBF\x40" => "\xE6\xBF\x83", + "\xBF\x41" => "\xE6\xBE\xA4", + "\xBF\x42" => "\xE6\xBF\x81", + "\xBF\x43" => "\xE6\xBE\xA7", + "\xBF\x44" => "\xE6\xBE\xB3", + "\xBF\x45" => "\xE6\xBF\x80", + "\xBF\x46" => "\xE6\xBE\xB9", + "\xBF\x47" => "\xE6\xBE\xB6", + "\xBF\x48" => "\xE6\xBE\xA6", + "\xBF\x49" => "\xE6\xBE\xA0", + "\xBF\x4A" => "\xE6\xBE\xB4", + "\xBF\x4B" => "\xE7\x86\xBE", + "\xBF\x4C" => "\xE7\x87\x89", + "\xBF\x4D" => "\xE7\x87\x90", + "\xBF\x4E" => "\xE7\x87\x92", + "\xBF\x4F" => "\xE7\x87\x88", + "\xBF\x50" => "\xE7\x87\x95", + "\xBF\x51" => "\xE7\x86\xB9", + "\xBF\x52" => "\xE7\x87\x8E", + "\xBF\x53" => "\xE7\x87\x99", + "\xBF\x54" => "\xE7\x87\x9C", + "\xBF\x55" => "\xE7\x87\x83", + "\xBF\x56" => "\xE7\x87\x84", + "\xBF\x57" => "\xE7\x8D\xA8", + "\xBF\x58" => "\xE7\x92\x9C", + "\xBF\x59" => "\xE7\x92\xA3", + "\xBF\x5A" => "\xE7\x92\x98", + "\xBF\x5B" => "\xE7\x92\x9F", + "\xBF\x5C" => "\xE7\x92\x9E", + "\xBF\x5D" => "\xE7\x93\xA2", + "\xBF\x5E" => "\xE7\x94\x8C", + "\xBF\x5F" => "\xE7\x94\x8D", + "\xBF\x60" => "\xE7\x98\xB4", + "\xBF\x61" => "\xE7\x98\xB8", + "\xBF\x62" => "\xE7\x98\xBA", + "\xBF\x63" => "\xE7\x9B\xA7", + "\xBF\x64" => "\xE7\x9B\xA5", + "\xBF\x65" => "\xE7\x9E\xA0", + "\xBF\x66" => "\xE7\x9E\x9E", + "\xBF\x67" => "\xE7\x9E\x9F", + "\xBF\x68" => "\xE7\x9E\xA5", + "\xBF\x69" => "\xE7\xA3\xA8", + "\xBF\x6A" => "\xE7\xA3\x9A", + "\xBF\x6B" => "\xE7\xA3\xAC", + "\xBF\x6C" => "\xE7\xA3\xA7", + "\xBF\x6D" => "\xE7\xA6\xA6", + "\xBF\x6E" => "\xE7\xA9\x8D", + "\xBF\x6F" => "\xE7\xA9\x8E", + "\xBF\x70" => "\xE7\xA9\x86", + "\xBF\x71" => "\xE7\xA9\x8C", + "\xBF\x72" => "\xE7\xA9\x8B", + "\xBF\x73" => "\xE7\xAA\xBA", + "\xBF\x74" => "\xE7\xAF\x99", + "\xBF\x75" => "\xE7\xB0\x91", + "\xBF\x76" => "\xE7\xAF\x89", + "\xBF\x77" => "\xE7\xAF\xA4", + "\xBF\x78" => "\xE7\xAF\x9B", + "\xBF\x79" => "\xE7\xAF\xA1", + "\xBF\x7A" => "\xE7\xAF\xA9", + "\xBF\x7B" => "\xE7\xAF\xA6", + "\xBF\x7C" => "\xE7\xB3\x95", + "\xBF\x7D" => "\xE7\xB3\x96", + "\xBF\x7E" => "\xE7\xB8\x8A", + "\xBF\xA1" => "\xE7\xB8\x91", + "\xBF\xA2" => "\xE7\xB8\x88", + "\xBF\xA3" => "\xE7\xB8\x9B", + "\xBF\xA4" => "\xE7\xB8\xA3", + "\xBF\xA5" => "\xE7\xB8\x9E", + "\xBF\xA6" => "\xE7\xB8\x9D", + "\xBF\xA7" => "\xE7\xB8\x89", + "\xBF\xA8" => "\xE7\xB8\x90", + "\xBF\xA9" => "\xE7\xBD\xB9", + "\xBF\xAA" => "\xE7\xBE\xB2", + "\xBF\xAB" => "\xE7\xBF\xB0", + "\xBF\xAC" => "\xE7\xBF\xB1", + "\xBF\xAD" => "\xE7\xBF\xAE", + "\xBF\xAE" => "\xE8\x80\xA8", + "\xBF\xAF" => "\xE8\x86\xB3", + "\xBF\xB0" => "\xE8\x86\xA9", + "\xBF\xB1" => "\xE8\x86\xA8", + "\xBF\xB2" => "\xE8\x87\xBB", + "\xBF\xB3" => "\xE8\x88\x88", + "\xBF\xB4" => "\xE8\x89\x98", + "\xBF\xB5" => "\xE8\x89\x99", + "\xBF\xB6" => "\xE8\x95\x8A", + "\xBF\xB7" => "\xE8\x95\x99", + "\xBF\xB8" => "\xE8\x95\x88", + "\xBF\xB9" => "\xE8\x95\xA8", + "\xBF\xBA" => "\xE8\x95\xA9", + "\xBF\xBB" => "\xE8\x95\x83", + "\xBF\xBC" => "\xE8\x95\x89", + "\xBF\xBD" => "\xE8\x95\xAD", + "\xBF\xBE" => "\xE8\x95\xAA", + "\xBF\xBF" => "\xE8\x95\x9E", + "\xBF\xC0" => "\xE8\x9E\x83", + "\xBF\xC1" => "\xE8\x9E\x9F", + "\xBF\xC2" => "\xE8\x9E\x9E", + "\xBF\xC3" => "\xE8\x9E\xA2", + "\xBF\xC4" => "\xE8\x9E\x8D", + "\xBF\xC5" => "\xE8\xA1\xA1", + "\xBF\xC6" => "\xE8\xA4\xAA", + "\xBF\xC7" => "\xE8\xA4\xB2", + "\xBF\xC8" => "\xE8\xA4\xA5", + "\xBF\xC9" => "\xE8\xA4\xAB", + "\xBF\xCA" => "\xE8\xA4\xA1", + "\xBF\xCB" => "\xE8\xA6\xAA", + "\xBF\xCC" => "\xE8\xA6\xA6", + "\xBF\xCD" => "\xE8\xAB\xA6", + "\xBF\xCE" => "\xE8\xAB\xBA", + "\xBF\xCF" => "\xE8\xAB\xAB", + "\xBF\xD0" => "\xE8\xAB\xB1", + "\xBF\xD1" => "\xE8\xAC\x80", + "\xBF\xD2" => "\xE8\xAB\x9C", + "\xBF\xD3" => "\xE8\xAB\xA7", + "\xBF\xD4" => "\xE8\xAB\xAE", + "\xBF\xD5" => "\xE8\xAB\xBE", + "\xBF\xD6" => "\xE8\xAC\x81", + "\xBF\xD7" => "\xE8\xAC\x82", + "\xBF\xD8" => "\xE8\xAB\xB7", + "\xBF\xD9" => "\xE8\xAB\xAD", + "\xBF\xDA" => "\xE8\xAB\xB3", + "\xBF\xDB" => "\xE8\xAB\xB6", + "\xBF\xDC" => "\xE8\xAB\xBC", + "\xBF\xDD" => "\xE8\xB1\xAB", + "\xBF\xDE" => "\xE8\xB1\xAD", + "\xBF\xDF" => "\xE8\xB2\x93", + "\xBF\xE0" => "\xE8\xB3\xB4", + "\xBF\xE1" => "\xE8\xB9\x84", + "\xBF\xE2" => "\xE8\xB8\xB1", + "\xBF\xE3" => "\xE8\xB8\xB4", + "\xBF\xE4" => "\xE8\xB9\x82", + "\xBF\xE5" => "\xE8\xB8\xB9", + "\xBF\xE6" => "\xE8\xB8\xB5", + "\xBF\xE7" => "\xE8\xBC\xBB", + "\xBF\xE8" => "\xE8\xBC\xAF", + "\xBF\xE9" => "\xE8\xBC\xB8", + "\xBF\xEA" => "\xE8\xBC\xB3", + "\xBF\xEB" => "\xE8\xBE\xA8", + "\xBF\xEC" => "\xE8\xBE\xA6", + "\xBF\xED" => "\xE9\x81\xB5", + "\xBF\xEE" => "\xE9\x81\xB4", + "\xBF\xEF" => "\xE9\x81\xB8", + "\xBF\xF0" => "\xE9\x81\xB2", + "\xBF\xF1" => "\xE9\x81\xBC", + "\xBF\xF2" => "\xE9\x81\xBA", + "\xBF\xF3" => "\xE9\x84\xB4", + "\xBF\xF4" => "\xE9\x86\x92", + "\xBF\xF5" => "\xE9\x8C\xA0", + "\xBF\xF6" => "\xE9\x8C\xB6", + "\xBF\xF7" => "\xE9\x8B\xB8", + "\xBF\xF8" => "\xE9\x8C\xB3", + "\xBF\xF9" => "\xE9\x8C\xAF", + "\xBF\xFA" => "\xE9\x8C\xA2", + "\xBF\xFB" => "\xE9\x8B\xBC", + "\xBF\xFC" => "\xE9\x8C\xAB", + "\xBF\xFD" => "\xE9\x8C\x84", + "\xBF\xFE" => "\xE9\x8C\x9A", + "\xC0\x40" => "\xE9\x8C\x90", + "\xC0\x41" => "\xE9\x8C\xA6", + "\xC0\x42" => "\xE9\x8C\xA1", + "\xC0\x43" => "\xE9\x8C\x95", + "\xC0\x44" => "\xE9\x8C\xAE", + "\xC0\x45" => "\xE9\x8C\x99", + "\xC0\x46" => "\xE9\x96\xBB", + "\xC0\x47" => "\xE9\x9A\xA7", + "\xC0\x48" => "\xE9\x9A\xA8", + "\xC0\x49" => "\xE9\x9A\xAA", + "\xC0\x4A" => "\xE9\x9B\x95", + "\xC0\x4B" => "\xE9\x9C\x8E", + "\xC0\x4C" => "\xE9\x9C\x91", + "\xC0\x4D" => "\xE9\x9C\x96", + "\xC0\x4E" => "\xE9\x9C\x8D", + "\xC0\x4F" => "\xE9\x9C\x93", + "\xC0\x50" => "\xE9\x9C\x8F", + "\xC0\x51" => "\xE9\x9D\x9B", + "\xC0\x52" => "\xE9\x9D\x9C", + "\xC0\x53" => "\xE9\x9D\xA6", + "\xC0\x54" => "\xE9\x9E\x98", + "\xC0\x55" => "\xE9\xA0\xB0", + "\xC0\x56" => "\xE9\xA0\xB8", + "\xC0\x57" => "\xE9\xA0\xBB", + "\xC0\x58" => "\xE9\xA0\xB7", + "\xC0\x59" => "\xE9\xA0\xAD", + "\xC0\x5A" => "\xE9\xA0\xB9", + "\xC0\x5B" => "\xE9\xA0\xA4", + "\xC0\x5C" => "\xE9\xA4\x90", + "\xC0\x5D" => "\xE9\xA4\xA8", + "\xC0\x5E" => "\xE9\xA4\x9E", + "\xC0\x5F" => "\xE9\xA4\x9B", + "\xC0\x60" => "\xE9\xA4\xA1", + "\xC0\x61" => "\xE9\xA4\x9A", + "\xC0\x62" => "\xE9\xA7\xAD", + "\xC0\x63" => "\xE9\xA7\xA2", + "\xC0\x64" => "\xE9\xA7\xB1", + "\xC0\x65" => "\xE9\xAA\xB8", + "\xC0\x66" => "\xE9\xAA\xBC", + "\xC0\x67" => "\xE9\xAB\xBB", + "\xC0\x68" => "\xE9\xAB\xAD", + "\xC0\x69" => "\xE9\xAC\xA8", + "\xC0\x6A" => "\xE9\xAE\x91", + "\xC0\x6B" => "\xE9\xB4\x95", + "\xC0\x6C" => "\xE9\xB4\xA3", + "\xC0\x6D" => "\xE9\xB4\xA6", + "\xC0\x6E" => "\xE9\xB4\xA8", + "\xC0\x6F" => "\xE9\xB4\x92", + "\xC0\x70" => "\xE9\xB4\x9B", + "\xC0\x71" => "\xE9\xBB\x98", + "\xC0\x72" => "\xE9\xBB\x94", + "\xC0\x73" => "\xE9\xBE\x8D", + "\xC0\x74" => "\xE9\xBE\x9C", + "\xC0\x75" => "\xE5\x84\xAA", + "\xC0\x76" => "\xE5\x84\x9F", + "\xC0\x77" => "\xE5\x84\xA1", + "\xC0\x78" => "\xE5\x84\xB2", + "\xC0\x79" => "\xE5\x8B\xB5", + "\xC0\x7A" => "\xE5\x9A\x8E", + "\xC0\x7B" => "\xE5\x9A\x80", + "\xC0\x7C" => "\xE5\x9A\x90", + "\xC0\x7D" => "\xE5\x9A\x85", + "\xC0\x7E" => "\xE5\x9A\x87", + "\xC0\xA1" => "\xE5\x9A\x8F", + "\xC0\xA2" => "\xE5\xA3\x95", + "\xC0\xA3" => "\xE5\xA3\x93", + "\xC0\xA4" => "\xE5\xA3\x91", + "\xC0\xA5" => "\xE5\xA3\x8E", + "\xC0\xA6" => "\xE5\xAC\xB0", + "\xC0\xA7" => "\xE5\xAC\xAA", + "\xC0\xA8" => "\xE5\xAC\xA4", + "\xC0\xA9" => "\xE5\xAD\xBA", + "\xC0\xAA" => "\xE5\xB0\xB7", + "\xC0\xAB" => "\xE5\xB1\xA8", + "\xC0\xAC" => "\xE5\xB6\xBC", + "\xC0\xAD" => "\xE5\xB6\xBA", + "\xC0\xAE" => "\xE5\xB6\xBD", + "\xC0\xAF" => "\xE5\xB6\xB8", + "\xC0\xB0" => "\xE5\xB9\xAB", + "\xC0\xB1" => "\xE5\xBD\x8C", + "\xC0\xB2" => "\xE5\xBE\xBD", + "\xC0\xB3" => "\xE6\x87\x89", + "\xC0\xB4" => "\xE6\x87\x82", + "\xC0\xB5" => "\xE6\x87\x87", + "\xC0\xB6" => "\xE6\x87\xA6", + "\xC0\xB7" => "\xE6\x87\x8B", + "\xC0\xB8" => "\xE6\x88\xB2", + "\xC0\xB9" => "\xE6\x88\xB4", + "\xC0\xBA" => "\xE6\x93\x8E", + "\xC0\xBB" => "\xE6\x93\x8A", + "\xC0\xBC" => "\xE6\x93\x98", + "\xC0\xBD" => "\xE6\x93\xA0", + "\xC0\xBE" => "\xE6\x93\xB0", + "\xC0\xBF" => "\xE6\x93\xA6", + "\xC0\xC0" => "\xE6\x93\xAC", + "\xC0\xC1" => "\xE6\x93\xB1", + "\xC0\xC2" => "\xE6\x93\xA2", + "\xC0\xC3" => "\xE6\x93\xAD", + "\xC0\xC4" => "\xE6\x96\x82", + "\xC0\xC5" => "\xE6\x96\x83", + "\xC0\xC6" => "\xE6\x9B\x99", + "\xC0\xC7" => "\xE6\x9B\x96", + "\xC0\xC8" => "\xE6\xAA\x80", + "\xC0\xC9" => "\xE6\xAA\x94", + "\xC0\xCA" => "\xE6\xAA\x84", + "\xC0\xCB" => "\xE6\xAA\xA2", + "\xC0\xCC" => "\xE6\xAA\x9C", + "\xC0\xCD" => "\xE6\xAB\x9B", + "\xC0\xCE" => "\xE6\xAA\xA3", + "\xC0\xCF" => "\xE6\xA9\xBE", + "\xC0\xD0" => "\xE6\xAA\x97", + "\xC0\xD1" => "\xE6\xAA\x90", + "\xC0\xD2" => "\xE6\xAA\xA0", + "\xC0\xD3" => "\xE6\xAD\x9C", + "\xC0\xD4" => "\xE6\xAE\xAE", + "\xC0\xD5" => "\xE6\xAF\x9A", + "\xC0\xD6" => "\xE6\xB0\x88", + "\xC0\xD7" => "\xE6\xBF\x98", + "\xC0\xD8" => "\xE6\xBF\xB1", + "\xC0\xD9" => "\xE6\xBF\x9F", + "\xC0\xDA" => "\xE6\xBF\xA0", + "\xC0\xDB" => "\xE6\xBF\x9B", + "\xC0\xDC" => "\xE6\xBF\xA4", + "\xC0\xDD" => "\xE6\xBF\xAB", + "\xC0\xDE" => "\xE6\xBF\xAF", + "\xC0\xDF" => "\xE6\xBE\x80", + "\xC0\xE0" => "\xE6\xBF\xAC", + "\xC0\xE1" => "\xE6\xBF\xA1", + "\xC0\xE2" => "\xE6\xBF\xA9", + "\xC0\xE3" => "\xE6\xBF\x95", + "\xC0\xE4" => "\xE6\xBF\xAE", + "\xC0\xE5" => "\xE6\xBF\xB0", + "\xC0\xE6" => "\xE7\x87\xA7", + "\xC0\xE7" => "\xE7\x87\x9F", + "\xC0\xE8" => "\xE7\x87\xAE", + "\xC0\xE9" => "\xE7\x87\xA6", + "\xC0\xEA" => "\xE7\x87\xA5", + "\xC0\xEB" => "\xE7\x87\xAD", + "\xC0\xEC" => "\xE7\x87\xAC", + "\xC0\xED" => "\xE7\x87\xB4", + "\xC0\xEE" => "\xE7\x87\xA0", + "\xC0\xEF" => "\xE7\x88\xB5", + "\xC0\xF0" => "\xE7\x89\x86", + "\xC0\xF1" => "\xE7\x8D\xB0", + "\xC0\xF2" => "\xE7\x8D\xB2", + "\xC0\xF3" => "\xE7\x92\xA9", + "\xC0\xF4" => "\xE7\x92\xB0", + "\xC0\xF5" => "\xE7\x92\xA6", + "\xC0\xF6" => "\xE7\x92\xA8", + "\xC0\xF7" => "\xE7\x99\x86", + "\xC0\xF8" => "\xE7\x99\x82", + "\xC0\xF9" => "\xE7\x99\x8C", + "\xC0\xFA" => "\xE7\x9B\xAA", + "\xC0\xFB" => "\xE7\x9E\xB3", + "\xC0\xFC" => "\xE7\x9E\xAA", + "\xC0\xFD" => "\xE7\x9E\xB0", + "\xC0\xFE" => "\xE7\x9E\xAC", + "\xC1\x40" => "\xE7\x9E\xA7", + "\xC1\x41" => "\xE7\x9E\xAD", + "\xC1\x42" => "\xE7\x9F\xAF", + "\xC1\x43" => "\xE7\xA3\xB7", + "\xC1\x44" => "\xE7\xA3\xBA", + "\xC1\x45" => "\xE7\xA3\xB4", + "\xC1\x46" => "\xE7\xA3\xAF", + "\xC1\x47" => "\xE7\xA4\x81", + "\xC1\x48" => "\xE7\xA6\xA7", + "\xC1\x49" => "\xE7\xA6\xAA", + "\xC1\x4A" => "\xE7\xA9\x97", + "\xC1\x4B" => "\xE7\xAA\xBF", + "\xC1\x4C" => "\xE7\xB0\x87", + "\xC1\x4D" => "\xE7\xB0\x8D", + "\xC1\x4E" => "\xE7\xAF\xBE", + "\xC1\x4F" => "\xE7\xAF\xB7", + "\xC1\x50" => "\xE7\xB0\x8C", + "\xC1\x51" => "\xE7\xAF\xA0", + "\xC1\x52" => "\xE7\xB3\xA0", + "\xC1\x53" => "\xE7\xB3\x9C", + "\xC1\x54" => "\xE7\xB3\x9E", + "\xC1\x55" => "\xE7\xB3\xA2", + "\xC1\x56" => "\xE7\xB3\x9F", + "\xC1\x57" => "\xE7\xB3\x99", + "\xC1\x58" => "\xE7\xB3\x9D", + "\xC1\x59" => "\xE7\xB8\xAE", + "\xC1\x5A" => "\xE7\xB8\xBE", + "\xC1\x5B" => "\xE7\xB9\x86", + "\xC1\x5C" => "\xE7\xB8\xB7", + "\xC1\x5D" => "\xE7\xB8\xB2", + "\xC1\x5E" => "\xE7\xB9\x83", + "\xC1\x5F" => "\xE7\xB8\xAB", + "\xC1\x60" => "\xE7\xB8\xBD", + "\xC1\x61" => "\xE7\xB8\xB1", + "\xC1\x62" => "\xE7\xB9\x85", + "\xC1\x63" => "\xE7\xB9\x81", + "\xC1\x64" => "\xE7\xB8\xB4", + "\xC1\x65" => "\xE7\xB8\xB9", + "\xC1\x66" => "\xE7\xB9\x88", + "\xC1\x67" => "\xE7\xB8\xB5", + "\xC1\x68" => "\xE7\xB8\xBF", + "\xC1\x69" => "\xE7\xB8\xAF", + "\xC1\x6A" => "\xE7\xBD\x84", + "\xC1\x6B" => "\xE7\xBF\xB3", + "\xC1\x6C" => "\xE7\xBF\xBC", + "\xC1\x6D" => "\xE8\x81\xB1", + "\xC1\x6E" => "\xE8\x81\xB2", + "\xC1\x6F" => "\xE8\x81\xB0", + "\xC1\x70" => "\xE8\x81\xAF", + "\xC1\x71" => "\xE8\x81\xB3", + "\xC1\x72" => "\xE8\x87\x86", + "\xC1\x73" => "\xE8\x87\x83", + "\xC1\x74" => "\xE8\x86\xBA", + "\xC1\x75" => "\xE8\x87\x82", + "\xC1\x76" => "\xE8\x87\x80", + "\xC1\x77" => "\xE8\x86\xBF", + "\xC1\x78" => "\xE8\x86\xBD", + "\xC1\x79" => "\xE8\x87\x89", + "\xC1\x7A" => "\xE8\x86\xBE", + "\xC1\x7B" => "\xE8\x87\xA8", + "\xC1\x7C" => "\xE8\x88\x89", + "\xC1\x7D" => "\xE8\x89\xB1", + "\xC1\x7E" => "\xE8\x96\xAA", + "\xC1\xA1" => "\xE8\x96\x84", + "\xC1\xA2" => "\xE8\x95\xBE", + "\xC1\xA3" => "\xE8\x96\x9C", + "\xC1\xA4" => "\xE8\x96\x91", + "\xC1\xA5" => "\xE8\x96\x94", + "\xC1\xA6" => "\xE8\x96\xAF", + "\xC1\xA7" => "\xE8\x96\x9B", + "\xC1\xA8" => "\xE8\x96\x87", + "\xC1\xA9" => "\xE8\x96\xA8", + "\xC1\xAA" => "\xE8\x96\x8A", + "\xC1\xAB" => "\xE8\x99\xA7", + "\xC1\xAC" => "\xE8\x9F\x80", + "\xC1\xAD" => "\xE8\x9F\x91", + "\xC1\xAE" => "\xE8\x9E\xB3", + "\xC1\xAF" => "\xE8\x9F\x92", + "\xC1\xB0" => "\xE8\x9F\x86", + "\xC1\xB1" => "\xE8\x9E\xAB", + "\xC1\xB2" => "\xE8\x9E\xBB", + "\xC1\xB3" => "\xE8\x9E\xBA", + "\xC1\xB4" => "\xE8\x9F\x88", + "\xC1\xB5" => "\xE8\x9F\x8B", + "\xC1\xB6" => "\xE8\xA4\xBB", + "\xC1\xB7" => "\xE8\xA4\xB6", + "\xC1\xB8" => "\xE8\xA5\x84", + "\xC1\xB9" => "\xE8\xA4\xB8", + "\xC1\xBA" => "\xE8\xA4\xBD", + "\xC1\xBB" => "\xE8\xA6\xAC", + "\xC1\xBC" => "\xE8\xAC\x8E", + "\xC1\xBD" => "\xE8\xAC\x97", + "\xC1\xBE" => "\xE8\xAC\x99", + "\xC1\xBF" => "\xE8\xAC\x9B", + "\xC1\xC0" => "\xE8\xAC\x8A", + "\xC1\xC1" => "\xE8\xAC\xA0", + "\xC1\xC2" => "\xE8\xAC\x9D", + "\xC1\xC3" => "\xE8\xAC\x84", + "\xC1\xC4" => "\xE8\xAC\x90", + "\xC1\xC5" => "\xE8\xB1\x81", + "\xC1\xC6" => "\xE8\xB0\xBF", + "\xC1\xC7" => "\xE8\xB1\xB3", + "\xC1\xC8" => "\xE8\xB3\xBA", + "\xC1\xC9" => "\xE8\xB3\xBD", + "\xC1\xCA" => "\xE8\xB3\xBC", + "\xC1\xCB" => "\xE8\xB3\xB8", + "\xC1\xCC" => "\xE8\xB3\xBB", + "\xC1\xCD" => "\xE8\xB6\xA8", + "\xC1\xCE" => "\xE8\xB9\x89", + "\xC1\xCF" => "\xE8\xB9\x8B", + "\xC1\xD0" => "\xE8\xB9\x88", + "\xC1\xD1" => "\xE8\xB9\x8A", + "\xC1\xD2" => "\xE8\xBD\x84", + "\xC1\xD3" => "\xE8\xBC\xBE", + "\xC1\xD4" => "\xE8\xBD\x82", + "\xC1\xD5" => "\xE8\xBD\x85", + "\xC1\xD6" => "\xE8\xBC\xBF", + "\xC1\xD7" => "\xE9\x81\xBF", + "\xC1\xD8" => "\xE9\x81\xBD", + "\xC1\xD9" => "\xE9\x82\x84", + "\xC1\xDA" => "\xE9\x82\x81", + "\xC1\xDB" => "\xE9\x82\x82", + "\xC1\xDC" => "\xE9\x82\x80", + "\xC1\xDD" => "\xE9\x84\xB9", + "\xC1\xDE" => "\xE9\x86\xA3", + "\xC1\xDF" => "\xE9\x86\x9E", + "\xC1\xE0" => "\xE9\x86\x9C", + "\xC1\xE1" => "\xE9\x8D\x8D", + "\xC1\xE2" => "\xE9\x8E\x82", + "\xC1\xE3" => "\xE9\x8C\xA8", + "\xC1\xE4" => "\xE9\x8D\xB5", + "\xC1\xE5" => "\xE9\x8D\x8A", + "\xC1\xE6" => "\xE9\x8D\xA5", + "\xC1\xE7" => "\xE9\x8D\x8B", + "\xC1\xE8" => "\xE9\x8C\x98", + "\xC1\xE9" => "\xE9\x8D\xBE", + "\xC1\xEA" => "\xE9\x8D\xAC", + "\xC1\xEB" => "\xE9\x8D\x9B", + "\xC1\xEC" => "\xE9\x8D\xB0", + "\xC1\xED" => "\xE9\x8D\x9A", + "\xC1\xEE" => "\xE9\x8D\x94", + "\xC1\xEF" => "\xE9\x97\x8A", + "\xC1\xF0" => "\xE9\x97\x8B", + "\xC1\xF1" => "\xE9\x97\x8C", + "\xC1\xF2" => "\xE9\x97\x88", + "\xC1\xF3" => "\xE9\x97\x86", + "\xC1\xF4" => "\xE9\x9A\xB1", + "\xC1\xF5" => "\xE9\x9A\xB8", + "\xC1\xF6" => "\xE9\x9B\x96", + "\xC1\xF7" => "\xE9\x9C\x9C", + "\xC1\xF8" => "\xE9\x9C\x9E", + "\xC1\xF9" => "\xE9\x9E\xA0", + "\xC1\xFA" => "\xE9\x9F\x93", + "\xC1\xFB" => "\xE9\xA1\x86", + "\xC1\xFC" => "\xE9\xA2\xB6", + "\xC1\xFD" => "\xE9\xA4\xB5", + "\xC1\xFE" => "\xE9\xA8\x81", + "\xC2\x40" => "\xE9\xA7\xBF", + "\xC2\x41" => "\xE9\xAE\xAE", + "\xC2\x42" => "\xE9\xAE\xAB", + "\xC2\x43" => "\xE9\xAE\xAA", + "\xC2\x44" => "\xE9\xAE\xAD", + "\xC2\x45" => "\xE9\xB4\xBB", + "\xC2\x46" => "\xE9\xB4\xBF", + "\xC2\x47" => "\xE9\xBA\x8B", + "\xC2\x48" => "\xE9\xBB\x8F", + "\xC2\x49" => "\xE9\xBB\x9E", + "\xC2\x4A" => "\xE9\xBB\x9C", + "\xC2\x4B" => "\xE9\xBB\x9D", + "\xC2\x4C" => "\xE9\xBB\x9B", + "\xC2\x4D" => "\xE9\xBC\xBE", + "\xC2\x4E" => "\xE9\xBD\x8B", + "\xC2\x4F" => "\xE5\x8F\xA2", + "\xC2\x50" => "\xE5\x9A\x95", + "\xC2\x51" => "\xE5\x9A\xAE", + "\xC2\x52" => "\xE5\xA3\x99", + "\xC2\x53" => "\xE5\xA3\x98", + "\xC2\x54" => "\xE5\xAC\xB8", + "\xC2\x55" => "\xE5\xBD\x9D", + "\xC2\x56" => "\xE6\x87\xA3", + "\xC2\x57" => "\xE6\x88\xB3", + "\xC2\x58" => "\xE6\x93\xB4", + "\xC2\x59" => "\xE6\x93\xB2", + "\xC2\x5A" => "\xE6\x93\xBE", + "\xC2\x5B" => "\xE6\x94\x86", + "\xC2\x5C" => "\xE6\x93\xBA", + "\xC2\x5D" => "\xE6\x93\xBB", + "\xC2\x5E" => "\xE6\x93\xB7", + "\xC2\x5F" => "\xE6\x96\xB7", + "\xC2\x60" => "\xE6\x9B\x9C", + "\xC2\x61" => "\xE6\x9C\xA6", + "\xC2\x62" => "\xE6\xAA\xB3", + "\xC2\x63" => "\xE6\xAA\xAC", + "\xC2\x64" => "\xE6\xAB\x83", + "\xC2\x65" => "\xE6\xAA\xBB", + "\xC2\x66" => "\xE6\xAA\xB8", + "\xC2\x67" => "\xE6\xAB\x82", + "\xC2\x68" => "\xE6\xAA\xAE", + "\xC2\x69" => "\xE6\xAA\xAF", + "\xC2\x6A" => "\xE6\xAD\x9F", + "\xC2\x6B" => "\xE6\xAD\xB8", + "\xC2\x6C" => "\xE6\xAE\xAF", + "\xC2\x6D" => "\xE7\x80\x89", + "\xC2\x6E" => "\xE7\x80\x8B", + "\xC2\x6F" => "\xE6\xBF\xBE", + "\xC2\x70" => "\xE7\x80\x86", + "\xC2\x71" => "\xE6\xBF\xBA", + "\xC2\x72" => "\xE7\x80\x91", + "\xC2\x73" => "\xE7\x80\x8F", + "\xC2\x74" => "\xE7\x87\xBB", + "\xC2\x75" => "\xE7\x87\xBC", + "\xC2\x76" => "\xE7\x87\xBE", + "\xC2\x77" => "\xE7\x87\xB8", + "\xC2\x78" => "\xE7\x8D\xB7", + "\xC2\x79" => "\xE7\x8D\xB5", + "\xC2\x7A" => "\xE7\x92\xA7", + "\xC2\x7B" => "\xE7\x92\xBF", + "\xC2\x7C" => "\xE7\x94\x95", + "\xC2\x7D" => "\xE7\x99\x96", + "\xC2\x7E" => "\xE7\x99\x98", + "\xC2\xA1" => "\xE7\x99\x92", + "\xC2\xA2" => "\xE7\x9E\xBD", + "\xC2\xA3" => "\xE7\x9E\xBF", + "\xC2\xA4" => "\xE7\x9E\xBB", + "\xC2\xA5" => "\xE7\x9E\xBC", + "\xC2\xA6" => "\xE7\xA4\x8E", + "\xC2\xA7" => "\xE7\xA6\xAE", + "\xC2\xA8" => "\xE7\xA9\xA1", + "\xC2\xA9" => "\xE7\xA9\xA2", + "\xC2\xAA" => "\xE7\xA9\xA0", + "\xC2\xAB" => "\xE7\xAB\x84", + "\xC2\xAC" => "\xE7\xAB\x85", + "\xC2\xAD" => "\xE7\xB0\xAB", + "\xC2\xAE" => "\xE7\xB0\xA7", + "\xC2\xAF" => "\xE7\xB0\xAA", + "\xC2\xB0" => "\xE7\xB0\x9E", + "\xC2\xB1" => "\xE7\xB0\xA3", + "\xC2\xB2" => "\xE7\xB0\xA1", + "\xC2\xB3" => "\xE7\xB3\xA7", + "\xC2\xB4" => "\xE7\xB9\x94", + "\xC2\xB5" => "\xE7\xB9\x95", + "\xC2\xB6" => "\xE7\xB9\x9E", + "\xC2\xB7" => "\xE7\xB9\x9A", + "\xC2\xB8" => "\xE7\xB9\xA1", + "\xC2\xB9" => "\xE7\xB9\x92", + "\xC2\xBA" => "\xE7\xB9\x99", + "\xC2\xBB" => "\xE7\xBD\x88", + "\xC2\xBC" => "\xE7\xBF\xB9", + "\xC2\xBD" => "\xE7\xBF\xBB", + "\xC2\xBE" => "\xE8\x81\xB7", + "\xC2\xBF" => "\xE8\x81\xB6", + "\xC2\xC0" => "\xE8\x87\x8D", + "\xC2\xC1" => "\xE8\x87\x8F", + "\xC2\xC2" => "\xE8\x88\x8A", + "\xC2\xC3" => "\xE8\x97\x8F", + "\xC2\xC4" => "\xE8\x96\xA9", + "\xC2\xC5" => "\xE8\x97\x8D", + "\xC2\xC6" => "\xE8\x97\x90", + "\xC2\xC7" => "\xE8\x97\x89", + "\xC2\xC8" => "\xE8\x96\xB0", + "\xC2\xC9" => "\xE8\x96\xBA", + "\xC2\xCA" => "\xE8\x96\xB9", + "\xC2\xCB" => "\xE8\x96\xA6", + "\xC2\xCC" => "\xE8\x9F\xAF", + "\xC2\xCD" => "\xE8\x9F\xAC", + "\xC2\xCE" => "\xE8\x9F\xB2", + "\xC2\xCF" => "\xE8\x9F\xA0", + "\xC2\xD0" => "\xE8\xA6\x86", + "\xC2\xD1" => "\xE8\xA6\xB2", + "\xC2\xD2" => "\xE8\xA7\xB4", + "\xC2\xD3" => "\xE8\xAC\xA8", + "\xC2\xD4" => "\xE8\xAC\xB9", + "\xC2\xD5" => "\xE8\xAC\xAC", + "\xC2\xD6" => "\xE8\xAC\xAB", + "\xC2\xD7" => "\xE8\xB1\x90", + "\xC2\xD8" => "\xE8\xB4\x85", + "\xC2\xD9" => "\xE8\xB9\x99", + "\xC2\xDA" => "\xE8\xB9\xA3", + "\xC2\xDB" => "\xE8\xB9\xA6", + "\xC2\xDC" => "\xE8\xB9\xA4", + "\xC2\xDD" => "\xE8\xB9\x9F", + "\xC2\xDE" => "\xE8\xB9\x95", + "\xC2\xDF" => "\xE8\xBB\x80", + "\xC2\xE0" => "\xE8\xBD\x89", + "\xC2\xE1" => "\xE8\xBD\x8D", + "\xC2\xE2" => "\xE9\x82\x87", + "\xC2\xE3" => "\xE9\x82\x83", + "\xC2\xE4" => "\xE9\x82\x88", + "\xC2\xE5" => "\xE9\x86\xAB", + "\xC2\xE6" => "\xE9\x86\xAC", + "\xC2\xE7" => "\xE9\x87\x90", + "\xC2\xE8" => "\xE9\x8E\x94", + "\xC2\xE9" => "\xE9\x8E\x8A", + "\xC2\xEA" => "\xE9\x8E\x96", + "\xC2\xEB" => "\xE9\x8E\xA2", + "\xC2\xEC" => "\xE9\x8E\xB3", + "\xC2\xED" => "\xE9\x8E\xAE", + "\xC2\xEE" => "\xE9\x8E\xAC", + "\xC2\xEF" => "\xE9\x8E\xB0", + "\xC2\xF0" => "\xE9\x8E\x98", + "\xC2\xF1" => "\xE9\x8E\x9A", + "\xC2\xF2" => "\xE9\x8E\x97", + "\xC2\xF3" => "\xE9\x97\x94", + "\xC2\xF4" => "\xE9\x97\x96", + "\xC2\xF5" => "\xE9\x97\x90", + "\xC2\xF6" => "\xE9\x97\x95", + "\xC2\xF7" => "\xE9\x9B\xA2", + "\xC2\xF8" => "\xE9\x9B\x9C", + "\xC2\xF9" => "\xE9\x9B\x99", + "\xC2\xFA" => "\xE9\x9B\x9B", + "\xC2\xFB" => "\xE9\x9B\x9E", + "\xC2\xFC" => "\xE9\x9C\xA4", + "\xC2\xFD" => "\xE9\x9E\xA3", + "\xC2\xFE" => "\xE9\x9E\xA6", + "\xC3\x40" => "\xE9\x9E\xAD", + "\xC3\x41" => "\xE9\x9F\xB9", + "\xC3\x42" => "\xE9\xA1\x8D", + "\xC3\x43" => "\xE9\xA1\x8F", + "\xC3\x44" => "\xE9\xA1\x8C", + "\xC3\x45" => "\xE9\xA1\x8E", + "\xC3\x46" => "\xE9\xA1\x93", + "\xC3\x47" => "\xE9\xA2\xBA", + "\xC3\x48" => "\xE9\xA4\xBE", + "\xC3\x49" => "\xE9\xA4\xBF", + "\xC3\x4A" => "\xE9\xA4\xBD", + "\xC3\x4B" => "\xE9\xA4\xAE", + "\xC3\x4C" => "\xE9\xA6\xA5", + "\xC3\x4D" => "\xE9\xA8\x8E", + "\xC3\x4E" => "\xE9\xAB\x81", + "\xC3\x4F" => "\xE9\xAC\x83", + "\xC3\x50" => "\xE9\xAC\x86", + "\xC3\x51" => "\xE9\xAD\x8F", + "\xC3\x52" => "\xE9\xAD\x8E", + "\xC3\x53" => "\xE9\xAD\x8D", + "\xC3\x54" => "\xE9\xAF\x8A", + "\xC3\x55" => "\xE9\xAF\x89", + "\xC3\x56" => "\xE9\xAF\xBD", + "\xC3\x57" => "\xE9\xAF\x88", + "\xC3\x58" => "\xE9\xAF\x80", + "\xC3\x59" => "\xE9\xB5\x91", + "\xC3\x5A" => "\xE9\xB5\x9D", + "\xC3\x5B" => "\xE9\xB5\xA0", + "\xC3\x5C" => "\xE9\xBB\xA0", + "\xC3\x5D" => "\xE9\xBC\x95", + "\xC3\x5E" => "\xE9\xBC\xAC", + "\xC3\x5F" => "\xE5\x84\xB3", + "\xC3\x60" => "\xE5\x9A\xA5", + "\xC3\x61" => "\xE5\xA3\x9E", + "\xC3\x62" => "\xE5\xA3\x9F", + "\xC3\x63" => "\xE5\xA3\xA2", + "\xC3\x64" => "\xE5\xAF\xB5", + "\xC3\x65" => "\xE9\xBE\x90", + "\xC3\x66" => "\xE5\xBB\xAC", + "\xC3\x67" => "\xE6\x87\xB2", + "\xC3\x68" => "\xE6\x87\xB7", + "\xC3\x69" => "\xE6\x87\xB6", + "\xC3\x6A" => "\xE6\x87\xB5", + "\xC3\x6B" => "\xE6\x94\x80", + "\xC3\x6C" => "\xE6\x94\x8F", + "\xC3\x6D" => "\xE6\x9B\xA0", + "\xC3\x6E" => "\xE6\x9B\x9D", + "\xC3\x6F" => "\xE6\xAB\xA5", + "\xC3\x70" => "\xE6\xAB\x9D", + "\xC3\x71" => "\xE6\xAB\x9A", + "\xC3\x72" => "\xE6\xAB\x93", + "\xC3\x73" => "\xE7\x80\x9B", + "\xC3\x74" => "\xE7\x80\x9F", + "\xC3\x75" => "\xE7\x80\xA8", + "\xC3\x76" => "\xE7\x80\x9A", + "\xC3\x77" => "\xE7\x80\x9D", + "\xC3\x78" => "\xE7\x80\x95", + "\xC3\x79" => "\xE7\x80\x98", + "\xC3\x7A" => "\xE7\x88\x86", + "\xC3\x7B" => "\xE7\x88\x8D", + "\xC3\x7C" => "\xE7\x89\x98", + "\xC3\x7D" => "\xE7\x8A\xA2", + "\xC3\x7E" => "\xE7\x8D\xB8", + "\xC3\xA1" => "\xE7\x8D\xBA", + "\xC3\xA2" => "\xE7\x92\xBD", + "\xC3\xA3" => "\xE7\x93\x8A", + "\xC3\xA4" => "\xE7\x93\xA3", + "\xC3\xA5" => "\xE7\x96\x87", + "\xC3\xA6" => "\xE7\x96\x86", + "\xC3\xA7" => "\xE7\x99\x9F", + "\xC3\xA8" => "\xE7\x99\xA1", + "\xC3\xA9" => "\xE7\x9F\x87", + "\xC3\xAA" => "\xE7\xA4\x99", + "\xC3\xAB" => "\xE7\xA6\xB1", + "\xC3\xAC" => "\xE7\xA9\xAB", + "\xC3\xAD" => "\xE7\xA9\xA9", + "\xC3\xAE" => "\xE7\xB0\xBE", + "\xC3\xAF" => "\xE7\xB0\xBF", + "\xC3\xB0" => "\xE7\xB0\xB8", + "\xC3\xB1" => "\xE7\xB0\xBD", + "\xC3\xB2" => "\xE7\xB0\xB7", + "\xC3\xB3" => "\xE7\xB1\x80", + "\xC3\xB4" => "\xE7\xB9\xAB", + "\xC3\xB5" => "\xE7\xB9\xAD", + "\xC3\xB6" => "\xE7\xB9\xB9", + "\xC3\xB7" => "\xE7\xB9\xA9", + "\xC3\xB8" => "\xE7\xB9\xAA", + "\xC3\xB9" => "\xE7\xBE\x85", + "\xC3\xBA" => "\xE7\xB9\xB3", + "\xC3\xBB" => "\xE7\xBE\xB6", + "\xC3\xBC" => "\xE7\xBE\xB9", + "\xC3\xBD" => "\xE7\xBE\xB8", + "\xC3\xBE" => "\xE8\x87\x98", + "\xC3\xBF" => "\xE8\x97\xA9", + "\xC3\xC0" => "\xE8\x97\x9D", + "\xC3\xC1" => "\xE8\x97\xAA", + "\xC3\xC2" => "\xE8\x97\x95", + "\xC3\xC3" => "\xE8\x97\xA4", + "\xC3\xC4" => "\xE8\x97\xA5", + "\xC3\xC5" => "\xE8\x97\xB7", + "\xC3\xC6" => "\xE8\x9F\xBB", + "\xC3\xC7" => "\xE8\xA0\x85", + "\xC3\xC8" => "\xE8\xA0\x8D", + "\xC3\xC9" => "\xE8\x9F\xB9", + "\xC3\xCA" => "\xE8\x9F\xBE", + "\xC3\xCB" => "\xE8\xA5\xA0", + "\xC3\xCC" => "\xE8\xA5\x9F", + "\xC3\xCD" => "\xE8\xA5\x96", + "\xC3\xCE" => "\xE8\xA5\x9E", + "\xC3\xCF" => "\xE8\xAD\x81", + "\xC3\xD0" => "\xE8\xAD\x9C", + "\xC3\xD1" => "\xE8\xAD\x98", + "\xC3\xD2" => "\xE8\xAD\x89", + "\xC3\xD3" => "\xE8\xAD\x9A", + "\xC3\xD4" => "\xE8\xAD\x8E", + "\xC3\xD5" => "\xE8\xAD\x8F", + "\xC3\xD6" => "\xE8\xAD\x86", + "\xC3\xD7" => "\xE8\xAD\x99", + "\xC3\xD8" => "\xE8\xB4\x88", + "\xC3\xD9" => "\xE8\xB4\x8A", + "\xC3\xDA" => "\xE8\xB9\xBC", + "\xC3\xDB" => "\xE8\xB9\xB2", + "\xC3\xDC" => "\xE8\xBA\x87", + "\xC3\xDD" => "\xE8\xB9\xB6", + "\xC3\xDE" => "\xE8\xB9\xAC", + "\xC3\xDF" => "\xE8\xB9\xBA", + "\xC3\xE0" => "\xE8\xB9\xB4", + "\xC3\xE1" => "\xE8\xBD\x94", + "\xC3\xE2" => "\xE8\xBD\x8E", + "\xC3\xE3" => "\xE8\xBE\xAD", + "\xC3\xE4" => "\xE9\x82\x8A", + "\xC3\xE5" => "\xE9\x82\x8B", + "\xC3\xE6" => "\xE9\x86\xB1", + "\xC3\xE7" => "\xE9\x86\xAE", + "\xC3\xE8" => "\xE9\x8F\xA1", + "\xC3\xE9" => "\xE9\x8F\x91", + "\xC3\xEA" => "\xE9\x8F\x9F", + "\xC3\xEB" => "\xE9\x8F\x83", + "\xC3\xEC" => "\xE9\x8F\x88", + "\xC3\xED" => "\xE9\x8F\x9C", + "\xC3\xEE" => "\xE9\x8F\x9D", + "\xC3\xEF" => "\xE9\x8F\x96", + "\xC3\xF0" => "\xE9\x8F\xA2", + "\xC3\xF1" => "\xE9\x8F\x8D", + "\xC3\xF2" => "\xE9\x8F\x98", + "\xC3\xF3" => "\xE9\x8F\xA4", + "\xC3\xF4" => "\xE9\x8F\x97", + "\xC3\xF5" => "\xE9\x8F\xA8", + "\xC3\xF6" => "\xE9\x97\x9C", + "\xC3\xF7" => "\xE9\x9A\xB4", + "\xC3\xF8" => "\xE9\x9B\xA3", + "\xC3\xF9" => "\xE9\x9C\xAA", + "\xC3\xFA" => "\xE9\x9C\xA7", + "\xC3\xFB" => "\xE9\x9D\xA1", + "\xC3\xFC" => "\xE9\x9F\x9C", + "\xC3\xFD" => "\xE9\x9F\xBB", + "\xC3\xFE" => "\xE9\xA1\x9E", + "\xC4\x40" => "\xE9\xA1\x98", + "\xC4\x41" => "\xE9\xA1\x9B", + "\xC4\x42" => "\xE9\xA2\xBC", + "\xC4\x43" => "\xE9\xA5\x85", + "\xC4\x44" => "\xE9\xA5\x89", + "\xC4\x45" => "\xE9\xA8\x96", + "\xC4\x46" => "\xE9\xA8\x99", + "\xC4\x47" => "\xE9\xAC\x8D", + "\xC4\x48" => "\xE9\xAF\xA8", + "\xC4\x49" => "\xE9\xAF\xA7", + "\xC4\x4A" => "\xE9\xAF\x96", + "\xC4\x4B" => "\xE9\xAF\x9B", + "\xC4\x4C" => "\xE9\xB6\x89", + "\xC4\x4D" => "\xE9\xB5\xA1", + "\xC4\x4E" => "\xE9\xB5\xB2", + "\xC4\x4F" => "\xE9\xB5\xAA", + "\xC4\x50" => "\xE9\xB5\xAC", + "\xC4\x51" => "\xE9\xBA\x92", + "\xC4\x52" => "\xE9\xBA\x97", + "\xC4\x53" => "\xE9\xBA\x93", + "\xC4\x54" => "\xE9\xBA\xB4", + "\xC4\x55" => "\xE5\x8B\xB8", + "\xC4\x56" => "\xE5\x9A\xA8", + "\xC4\x57" => "\xE5\x9A\xB7", + "\xC4\x58" => "\xE5\x9A\xB6", + "\xC4\x59" => "\xE5\x9A\xB4", + "\xC4\x5A" => "\xE5\x9A\xBC", + "\xC4\x5B" => "\xE5\xA3\xA4", + "\xC4\x5C" => "\xE5\xAD\x80", + "\xC4\x5D" => "\xE5\xAD\x83", + "\xC4\x5E" => "\xE5\xAD\xBD", + "\xC4\x5F" => "\xE5\xAF\xB6", + "\xC4\x60" => "\xE5\xB7\x89", + "\xC4\x61" => "\xE6\x87\xB8", + "\xC4\x62" => "\xE6\x87\xBA", + "\xC4\x63" => "\xE6\x94\x98", + "\xC4\x64" => "\xE6\x94\x94", + "\xC4\x65" => "\xE6\x94\x99", + "\xC4\x66" => "\xE6\x9B\xA6", + "\xC4\x67" => "\xE6\x9C\xA7", + "\xC4\x68" => "\xE6\xAB\xAC", + "\xC4\x69" => "\xE7\x80\xBE", + "\xC4\x6A" => "\xE7\x80\xB0", + "\xC4\x6B" => "\xE7\x80\xB2", + "\xC4\x6C" => "\xE7\x88\x90", + "\xC4\x6D" => "\xE7\x8D\xBB", + "\xC4\x6E" => "\xE7\x93\x8F", + "\xC4\x6F" => "\xE7\x99\xA2", + "\xC4\x70" => "\xE7\x99\xA5", + "\xC4\x71" => "\xE7\xA4\xA6", + "\xC4\x72" => "\xE7\xA4\xAA", + "\xC4\x73" => "\xE7\xA4\xAC", + "\xC4\x74" => "\xE7\xA4\xAB", + "\xC4\x75" => "\xE7\xAB\x87", + "\xC4\x76" => "\xE7\xAB\xB6", + "\xC4\x77" => "\xE7\xB1\x8C", + "\xC4\x78" => "\xE7\xB1\x83", + "\xC4\x79" => "\xE7\xB1\x8D", + "\xC4\x7A" => "\xE7\xB3\xAF", + "\xC4\x7B" => "\xE7\xB3\xB0", + "\xC4\x7C" => "\xE8\xBE\xAE", + "\xC4\x7D" => "\xE7\xB9\xBD", + "\xC4\x7E" => "\xE7\xB9\xBC", + "\xC4\xA1" => "\xE7\xBA\x82", + "\xC4\xA2" => "\xE7\xBD\x8C", + "\xC4\xA3" => "\xE8\x80\x80", + "\xC4\xA4" => "\xE8\x87\x9A", + "\xC4\xA5" => "\xE8\x89\xA6", + "\xC4\xA6" => "\xE8\x97\xBB", + "\xC4\xA7" => "\xE8\x97\xB9", + "\xC4\xA8" => "\xE8\x98\x91", + "\xC4\xA9" => "\xE8\x97\xBA", + "\xC4\xAA" => "\xE8\x98\x86", + "\xC4\xAB" => "\xE8\x98\x8B", + "\xC4\xAC" => "\xE8\x98\x87", + "\xC4\xAD" => "\xE8\x98\x8A", + "\xC4\xAE" => "\xE8\xA0\x94", + "\xC4\xAF" => "\xE8\xA0\x95", + "\xC4\xB0" => "\xE8\xA5\xA4", + "\xC4\xB1" => "\xE8\xA6\xBA", + "\xC4\xB2" => "\xE8\xA7\xB8", + "\xC4\xB3" => "\xE8\xAD\xB0", + "\xC4\xB4" => "\xE8\xAD\xAC", + "\xC4\xB5" => "\xE8\xAD\xA6", + "\xC4\xB6" => "\xE8\xAD\xAF", + "\xC4\xB7" => "\xE8\xAD\x9F", + "\xC4\xB8" => "\xE8\xAD\xAB", + "\xC4\xB9" => "\xE8\xB4\x8F", + "\xC4\xBA" => "\xE8\xB4\x8D", + "\xC4\xBB" => "\xE8\xBA\x89", + "\xC4\xBC" => "\xE8\xBA\x81", + "\xC4\xBD" => "\xE8\xBA\x85", + "\xC4\xBE" => "\xE8\xBA\x82", + "\xC4\xBF" => "\xE9\x86\xB4", + "\xC4\xC0" => "\xE9\x87\x8B", + "\xC4\xC1" => "\xE9\x90\x98", + "\xC4\xC2" => "\xE9\x90\x83", + "\xC4\xC3" => "\xE9\x8F\xBD", + "\xC4\xC4" => "\xE9\x97\xA1", + "\xC4\xC5" => "\xE9\x9C\xB0", + "\xC4\xC6" => "\xE9\xA3\x84", + "\xC4\xC7" => "\xE9\xA5\x92", + "\xC4\xC8" => "\xE9\xA5\x91", + "\xC4\xC9" => "\xE9\xA6\xA8", + "\xC4\xCA" => "\xE9\xA8\xAB", + "\xC4\xCB" => "\xE9\xA8\xB0", + "\xC4\xCC" => "\xE9\xA8\xB7", + "\xC4\xCD" => "\xE9\xA8\xB5", + "\xC4\xCE" => "\xE9\xB0\x93", + "\xC4\xCF" => "\xE9\xB0\x8D", + "\xC4\xD0" => "\xE9\xB9\xB9", + "\xC4\xD1" => "\xE9\xBA\xB5", + "\xC4\xD2" => "\xE9\xBB\xA8", + "\xC4\xD3" => "\xE9\xBC\xAF", + "\xC4\xD4" => "\xE9\xBD\x9F", + "\xC4\xD5" => "\xE9\xBD\xA3", + "\xC4\xD6" => "\xE9\xBD\xA1", + "\xC4\xD7" => "\xE5\x84\xB7", + "\xC4\xD8" => "\xE5\x84\xB8", + "\xC4\xD9" => "\xE5\x9B\x81", + "\xC4\xDA" => "\xE5\x9B\x80", + "\xC4\xDB" => "\xE5\x9B\x82", + "\xC4\xDC" => "\xE5\xA4\x94", + "\xC4\xDD" => "\xE5\xB1\xAC", + "\xC4\xDE" => "\xE5\xB7\x8D", + "\xC4\xDF" => "\xE6\x87\xBC", + "\xC4\xE0" => "\xE6\x87\xBE", + "\xC4\xE1" => "\xE6\x94\x9D", + "\xC4\xE2" => "\xE6\x94\x9C", + "\xC4\xE3" => "\xE6\x96\x95", + "\xC4\xE4" => "\xE6\x9B\xA9", + "\xC4\xE5" => "\xE6\xAB\xBB", + "\xC4\xE6" => "\xE6\xAC\x84", + "\xC4\xE7" => "\xE6\xAB\xBA", + "\xC4\xE8" => "\xE6\xAE\xB2", + "\xC4\xE9" => "\xE7\x81\x8C", + "\xC4\xEA" => "\xE7\x88\x9B", + "\xC4\xEB" => "\xE7\x8A\xA7", + "\xC4\xEC" => "\xE7\x93\x96", + "\xC4\xED" => "\xE7\x93\x94", + "\xC4\xEE" => "\xE7\x99\xA9", + "\xC4\xEF" => "\xE7\x9F\x93", + "\xC4\xF0" => "\xE7\xB1\x90", + "\xC4\xF1" => "\xE7\xBA\x8F", + "\xC4\xF2" => "\xE7\xBA\x8C", + "\xC4\xF3" => "\xE7\xBE\xBC", + "\xC4\xF4" => "\xE8\x98\x97", + "\xC4\xF5" => "\xE8\x98\xAD", + "\xC4\xF6" => "\xE8\x98\x9A", + "\xC4\xF7" => "\xE8\xA0\xA3", + "\xC4\xF8" => "\xE8\xA0\xA2", + "\xC4\xF9" => "\xE8\xA0\xA1", + "\xC4\xFA" => "\xE8\xA0\x9F", + "\xC4\xFB" => "\xE8\xA5\xAA", + "\xC4\xFC" => "\xE8\xA5\xAC", + "\xC4\xFD" => "\xE8\xA6\xBD", + "\xC4\xFE" => "\xE8\xAD\xB4", + "\xC5\x40" => "\xE8\xAD\xB7", + "\xC5\x41" => "\xE8\xAD\xBD", + "\xC5\x42" => "\xE8\xB4\x93", + "\xC5\x43" => "\xE8\xBA\x8A", + "\xC5\x44" => "\xE8\xBA\x8D", + "\xC5\x45" => "\xE8\xBA\x8B", + "\xC5\x46" => "\xE8\xBD\x9F", + "\xC5\x47" => "\xE8\xBE\xAF", + "\xC5\x48" => "\xE9\x86\xBA", + "\xC5\x49" => "\xE9\x90\xAE", + "\xC5\x4A" => "\xE9\x90\xB3", + "\xC5\x4B" => "\xE9\x90\xB5", + "\xC5\x4C" => "\xE9\x90\xBA", + "\xC5\x4D" => "\xE9\x90\xB8", + "\xC5\x4E" => "\xE9\x90\xB2", + "\xC5\x4F" => "\xE9\x90\xAB", + "\xC5\x50" => "\xE9\x97\xA2", + "\xC5\x51" => "\xE9\x9C\xB8", + "\xC5\x52" => "\xE9\x9C\xB9", + "\xC5\x53" => "\xE9\x9C\xB2", + "\xC5\x54" => "\xE9\x9F\xBF", + "\xC5\x55" => "\xE9\xA1\xA7", + "\xC5\x56" => "\xE9\xA1\xA5", + "\xC5\x57" => "\xE9\xA5\x97", + "\xC5\x58" => "\xE9\xA9\x85", + "\xC5\x59" => "\xE9\xA9\x83", + "\xC5\x5A" => "\xE9\xA9\x80", + "\xC5\x5B" => "\xE9\xA8\xBE", + "\xC5\x5C" => "\xE9\xAB\x8F", + "\xC5\x5D" => "\xE9\xAD\x94", + "\xC5\x5E" => "\xE9\xAD\x91", + "\xC5\x5F" => "\xE9\xB0\xAD", + "\xC5\x60" => "\xE9\xB0\xA5", + "\xC5\x61" => "\xE9\xB6\xAF", + "\xC5\x62" => "\xE9\xB6\xB4", + "\xC5\x63" => "\xE9\xB7\x82", + "\xC5\x64" => "\xE9\xB6\xB8", + "\xC5\x65" => "\xE9\xBA\x9D", + "\xC5\x66" => "\xE9\xBB\xAF", + "\xC5\x67" => "\xE9\xBC\x99", + "\xC5\x68" => "\xE9\xBD\x9C", + "\xC5\x69" => "\xE9\xBD\xA6", + "\xC5\x6A" => "\xE9\xBD\xA7", + "\xC5\x6B" => "\xE5\x84\xBC", + "\xC5\x6C" => "\xE5\x84\xBB", + "\xC5\x6D" => "\xE5\x9B\x88", + "\xC5\x6E" => "\xE5\x9B\x8A", + "\xC5\x6F" => "\xE5\x9B\x89", + "\xC5\x70" => "\xE5\xAD\xBF", + "\xC5\x71" => "\xE5\xB7\x94", + "\xC5\x72" => "\xE5\xB7\x92", + "\xC5\x73" => "\xE5\xBD\x8E", + "\xC5\x74" => "\xE6\x87\xBF", + "\xC5\x75" => "\xE6\x94\xA4", + "\xC5\x76" => "\xE6\xAC\x8A", + "\xC5\x77" => "\xE6\xAD\xA1", + "\xC5\x78" => "\xE7\x81\x91", + "\xC5\x79" => "\xE7\x81\x98", + "\xC5\x7A" => "\xE7\x8E\x80", + "\xC5\x7B" => "\xE7\x93\xA4", + "\xC5\x7C" => "\xE7\x96\x8A", + "\xC5\x7D" => "\xE7\x99\xAE", + "\xC5\x7E" => "\xE7\x99\xAC", + "\xC5\xA1" => "\xE7\xA6\xB3", + "\xC5\xA2" => "\xE7\xB1\xA0", + "\xC5\xA3" => "\xE7\xB1\x9F", + "\xC5\xA4" => "\xE8\x81\xBE", + "\xC5\xA5" => "\xE8\x81\xBD", + "\xC5\xA6" => "\xE8\x87\x9F", + "\xC5\xA7" => "\xE8\xA5\xB2", + "\xC5\xA8" => "\xE8\xA5\xAF", + "\xC5\xA9" => "\xE8\xA7\xBC", + "\xC5\xAA" => "\xE8\xAE\x80", + "\xC5\xAB" => "\xE8\xB4\x96", + "\xC5\xAC" => "\xE8\xB4\x97", + "\xC5\xAD" => "\xE8\xBA\x91", + "\xC5\xAE" => "\xE8\xBA\x93", + "\xC5\xAF" => "\xE8\xBD\xA1", + "\xC5\xB0" => "\xE9\x85\x88", + "\xC5\xB1" => "\xE9\x91\x84", + "\xC5\xB2" => "\xE9\x91\x91", + "\xC5\xB3" => "\xE9\x91\x92", + "\xC5\xB4" => "\xE9\x9C\xBD", + "\xC5\xB5" => "\xE9\x9C\xBE", + "\xC5\xB6" => "\xE9\x9F\x83", + "\xC5\xB7" => "\xE9\x9F\x81", + "\xC5\xB8" => "\xE9\xA1\xAB", + "\xC5\xB9" => "\xE9\xA5\x95", + "\xC5\xBA" => "\xE9\xA9\x95", + "\xC5\xBB" => "\xE9\xA9\x8D", + "\xC5\xBC" => "\xE9\xAB\x92", + "\xC5\xBD" => "\xE9\xAC\x9A", + "\xC5\xBE" => "\xE9\xB1\x89", + "\xC5\xBF" => "\xE9\xB0\xB1", + "\xC5\xC0" => "\xE9\xB0\xBE", + "\xC5\xC1" => "\xE9\xB0\xBB", + "\xC5\xC2" => "\xE9\xB7\x93", + "\xC5\xC3" => "\xE9\xB7\x97", + "\xC5\xC4" => "\xE9\xBC\xB4", + "\xC5\xC5" => "\xE9\xBD\xAC", + "\xC5\xC6" => "\xE9\xBD\xAA", + "\xC5\xC7" => "\xE9\xBE\x94", + "\xC5\xC8" => "\xE5\x9B\x8C", + "\xC5\xC9" => "\xE5\xB7\x96", + "\xC5\xCA" => "\xE6\x88\x80", + "\xC5\xCB" => "\xE6\x94\xA3", + "\xC5\xCC" => "\xE6\x94\xAB", + "\xC5\xCD" => "\xE6\x94\xAA", + "\xC5\xCE" => "\xE6\x9B\xAC", + "\xC5\xCF" => "\xE6\xAC\x90", + "\xC5\xD0" => "\xE7\x93\x9A", + "\xC5\xD1" => "\xE7\xAB\x8A", + "\xC5\xD2" => "\xE7\xB1\xA4", + "\xC5\xD3" => "\xE7\xB1\xA3", + "\xC5\xD4" => "\xE7\xB1\xA5", + "\xC5\xD5" => "\xE7\xBA\x93", + "\xC5\xD6" => "\xE7\xBA\x96", + "\xC5\xD7" => "\xE7\xBA\x94", + "\xC5\xD8" => "\xE8\x87\xA2", + "\xC5\xD9" => "\xE8\x98\xB8", + "\xC5\xDA" => "\xE8\x98\xBF", + "\xC5\xDB" => "\xE8\xA0\xB1", + "\xC5\xDC" => "\xE8\xAE\x8A", + "\xC5\xDD" => "\xE9\x82\x90", + "\xC5\xDE" => "\xE9\x82\x8F", + "\xC5\xDF" => "\xE9\x91\xA3", + "\xC5\xE0" => "\xE9\x91\xA0", + "\xC5\xE1" => "\xE9\x91\xA4", + "\xC5\xE2" => "\xE9\x9D\xA8", + "\xC5\xE3" => "\xE9\xA1\xAF", + "\xC5\xE4" => "\xE9\xA5\x9C", + "\xC5\xE5" => "\xE9\xA9\x9A", + "\xC5\xE6" => "\xE9\xA9\x9B", + "\xC5\xE7" => "\xE9\xA9\x97", + "\xC5\xE8" => "\xE9\xAB\x93", + "\xC5\xE9" => "\xE9\xAB\x94", + "\xC5\xEA" => "\xE9\xAB\x91", + "\xC5\xEB" => "\xE9\xB1\x94", + "\xC5\xEC" => "\xE9\xB1\x97", + "\xC5\xED" => "\xE9\xB1\x96", + "\xC5\xEE" => "\xE9\xB7\xA5", + "\xC5\xEF" => "\xE9\xBA\x9F", + "\xC5\xF0" => "\xE9\xBB\xB4", + "\xC5\xF1" => "\xE5\x9B\x91", + "\xC5\xF2" => "\xE5\xA3\xA9", + "\xC5\xF3" => "\xE6\x94\xAC", + "\xC5\xF4" => "\xE7\x81\x9E", + "\xC5\xF5" => "\xE7\x99\xB1", + "\xC5\xF6" => "\xE7\x99\xB2", + "\xC5\xF7" => "\xE7\x9F\x97", + "\xC5\xF8" => "\xE7\xBD\x90", + "\xC5\xF9" => "\xE7\xBE\x88", + "\xC5\xFA" => "\xE8\xA0\xB6", + "\xC5\xFB" => "\xE8\xA0\xB9", + "\xC5\xFC" => "\xE8\xA1\xA2", + "\xC5\xFD" => "\xE8\xAE\x93", + "\xC5\xFE" => "\xE8\xAE\x92", + "\xC6\x40" => "\xE8\xAE\x96", + "\xC6\x41" => "\xE8\x89\xB7", + "\xC6\x42" => "\xE8\xB4\x9B", + "\xC6\x43" => "\xE9\x87\x80", + "\xC6\x44" => "\xE9\x91\xAA", + "\xC6\x45" => "\xE9\x9D\x82", + "\xC6\x46" => "\xE9\x9D\x88", + "\xC6\x47" => "\xE9\x9D\x84", + "\xC6\x48" => "\xE9\x9F\x86", + "\xC6\x49" => "\xE9\xA1\xB0", + "\xC6\x4A" => "\xE9\xA9\x9F", + "\xC6\x4B" => "\xE9\xAC\xA2", + "\xC6\x4C" => "\xE9\xAD\x98", + "\xC6\x4D" => "\xE9\xB1\x9F", + "\xC6\x4E" => "\xE9\xB7\xB9", + "\xC6\x4F" => "\xE9\xB7\xBA", + "\xC6\x50" => "\xE9\xB9\xBC", + "\xC6\x51" => "\xE9\xB9\xBD", + "\xC6\x52" => "\xE9\xBC\x87", + "\xC6\x53" => "\xE9\xBD\xB7", + "\xC6\x54" => "\xE9\xBD\xB2", + "\xC6\x55" => "\xE5\xBB\xB3", + "\xC6\x56" => "\xE6\xAC\x96", + "\xC6\x57" => "\xE7\x81\xA3", + "\xC6\x58" => "\xE7\xB1\xAC", + "\xC6\x59" => "\xE7\xB1\xAE", + "\xC6\x5A" => "\xE8\xA0\xBB", + "\xC6\x5B" => "\xE8\xA7\x80", + "\xC6\x5C" => "\xE8\xBA\xA1", + "\xC6\x5D" => "\xE9\x87\x81", + "\xC6\x5E" => "\xE9\x91\xB2", + "\xC6\x5F" => "\xE9\x91\xB0", + "\xC6\x60" => "\xE9\xA1\xB1", + "\xC6\x61" => "\xE9\xA5\x9E", + "\xC6\x62" => "\xE9\xAB\x96", + "\xC6\x63" => "\xE9\xAC\xA3", + "\xC6\x64" => "\xE9\xBB\x8C", + "\xC6\x65" => "\xE7\x81\xA4", + "\xC6\x66" => "\xE7\x9F\x9A", + "\xC6\x67" => "\xE8\xAE\x9A", + "\xC6\x68" => "\xE9\x91\xB7", + "\xC6\x69" => "\xE9\x9F\x89", + "\xC6\x6A" => "\xE9\xA9\xA2", + "\xC6\x6B" => "\xE9\xA9\xA5", + "\xC6\x6C" => "\xE7\xBA\x9C", + "\xC6\x6D" => "\xE8\xAE\x9C", + "\xC6\x6E" => "\xE8\xBA\xAA", + "\xC6\x6F" => "\xE9\x87\x85", + "\xC6\x70" => "\xE9\x91\xBD", + "\xC6\x71" => "\xE9\x91\xBE", + "\xC6\x72" => "\xE9\x91\xBC", + "\xC6\x73" => "\xE9\xB1\xB7", + "\xC6\x74" => "\xE9\xB1\xB8", + "\xC6\x75" => "\xE9\xBB\xB7", + "\xC6\x76" => "\xE8\xB1\x94", + "\xC6\x77" => "\xE9\x91\xBF", + "\xC6\x78" => "\xE9\xB8\x9A", + "\xC6\x79" => "\xE7\x88\xA8", + "\xC6\x7A" => "\xE9\xA9\xAA", + "\xC6\x7B" => "\xE9\xAC\xB1", + "\xC6\x7C" => "\xE9\xB8\x9B", + "\xC6\x7D" => "\xE9\xB8\x9E", + "\xC6\x7E" => "\xE7\xB1\xB2", + "\xC6\xA1" => "\xE3\x83\xBE", + "\xC6\xA2" => "\xE3\x82\x9D", + "\xC6\xA3" => "\xE3\x82\x9E", + "\xC6\xA4" => "\xE3\x80\x85", + "\xC6\xA5" => "\xE3\x81\x81", + "\xC6\xA6" => "\xE3\x81\x82", + "\xC6\xA7" => "\xE3\x81\x83", + "\xC6\xA8" => "\xE3\x81\x84", + "\xC6\xA9" => "\xE3\x81\x85", + "\xC6\xAA" => "\xE3\x81\x86", + "\xC6\xAB" => "\xE3\x81\x87", + "\xC6\xAC" => "\xE3\x81\x88", + "\xC6\xAD" => "\xE3\x81\x89", + "\xC6\xAE" => "\xE3\x81\x8A", + "\xC6\xAF" => "\xE3\x81\x8B", + "\xC6\xB0" => "\xE3\x81\x8C", + "\xC6\xB1" => "\xE3\x81\x8D", + "\xC6\xB2" => "\xE3\x81\x8E", + "\xC6\xB3" => "\xE3\x81\x8F", + "\xC6\xB4" => "\xE3\x81\x90", + "\xC6\xB5" => "\xE3\x81\x91", + "\xC6\xB6" => "\xE3\x81\x92", + "\xC6\xB7" => "\xE3\x81\x93", + "\xC6\xB8" => "\xE3\x81\x94", + "\xC6\xB9" => "\xE3\x81\x95", + "\xC6\xBA" => "\xE3\x81\x96", + "\xC6\xBB" => "\xE3\x81\x97", + "\xC6\xBC" => "\xE3\x81\x98", + "\xC6\xBD" => "\xE3\x81\x99", + "\xC6\xBE" => "\xE3\x81\x9A", + "\xC6\xBF" => "\xE3\x81\x9B", + "\xC6\xC0" => "\xE3\x81\x9C", + "\xC6\xC1" => "\xE3\x81\x9D", + "\xC6\xC2" => "\xE3\x81\x9E", + "\xC6\xC3" => "\xE3\x81\x9F", + "\xC6\xC4" => "\xE3\x81\xA0", + "\xC6\xC5" => "\xE3\x81\xA1", + "\xC6\xC6" => "\xE3\x81\xA2", + "\xC6\xC7" => "\xE3\x81\xA3", + "\xC6\xC8" => "\xE3\x81\xA4", + "\xC6\xC9" => "\xE3\x81\xA5", + "\xC6\xCA" => "\xE3\x81\xA6", + "\xC6\xCB" => "\xE3\x81\xA7", + "\xC6\xCC" => "\xE3\x81\xA8", + "\xC6\xCD" => "\xE3\x81\xA9", + "\xC6\xCE" => "\xE3\x81\xAA", + "\xC6\xCF" => "\xE3\x81\xAB", + "\xC6\xD0" => "\xE3\x81\xAC", + "\xC6\xD1" => "\xE3\x81\xAD", + "\xC6\xD2" => "\xE3\x81\xAE", + "\xC6\xD3" => "\xE3\x81\xAF", + "\xC6\xD4" => "\xE3\x81\xB0", + "\xC6\xD5" => "\xE3\x81\xB1", + "\xC6\xD6" => "\xE3\x81\xB2", + "\xC6\xD7" => "\xE3\x81\xB3", + "\xC6\xD8" => "\xE3\x81\xB4", + "\xC6\xD9" => "\xE3\x81\xB5", + "\xC6\xDA" => "\xE3\x81\xB6", + "\xC6\xDB" => "\xE3\x81\xB7", + "\xC6\xDC" => "\xE3\x81\xB8", + "\xC6\xDD" => "\xE3\x81\xB9", + "\xC6\xDE" => "\xE3\x81\xBA", + "\xC6\xDF" => "\xE3\x81\xBB", + "\xC6\xE0" => "\xE3\x81\xBC", + "\xC6\xE1" => "\xE3\x81\xBD", + "\xC6\xE2" => "\xE3\x81\xBE", + "\xC6\xE3" => "\xE3\x81\xBF", + "\xC6\xE4" => "\xE3\x82\x80", + "\xC6\xE5" => "\xE3\x82\x81", + "\xC6\xE6" => "\xE3\x82\x82", + "\xC6\xE7" => "\xE3\x82\x83", + "\xC6\xE8" => "\xE3\x82\x84", + "\xC6\xE9" => "\xE3\x82\x85", + "\xC6\xEA" => "\xE3\x82\x86", + "\xC6\xEB" => "\xE3\x82\x87", + "\xC6\xEC" => "\xE3\x82\x88", + "\xC6\xED" => "\xE3\x82\x89", + "\xC6\xEE" => "\xE3\x82\x8A", + "\xC6\xEF" => "\xE3\x82\x8B", + "\xC6\xF0" => "\xE3\x82\x8C", + "\xC6\xF1" => "\xE3\x82\x8D", + "\xC6\xF2" => "\xE3\x82\x8E", + "\xC6\xF3" => "\xE3\x82\x8F", + "\xC6\xF4" => "\xE3\x82\x90", + "\xC6\xF5" => "\xE3\x82\x91", + "\xC6\xF6" => "\xE3\x82\x92", + "\xC6\xF7" => "\xE3\x82\x93", + "\xC6\xF8" => "\xE3\x82\xA1", + "\xC6\xF9" => "\xE3\x82\xA2", + "\xC6\xFA" => "\xE3\x82\xA3", + "\xC6\xFB" => "\xE3\x82\xA4", + "\xC6\xFC" => "\xE3\x82\xA5", + "\xC6\xFD" => "\xE3\x82\xA6", + "\xC6\xFE" => "\xE3\x82\xA7", + "\xC7\x40" => "\xE3\x82\xA8", + "\xC7\x41" => "\xE3\x82\xA9", + "\xC7\x42" => "\xE3\x82\xAA", + "\xC7\x43" => "\xE3\x82\xAB", + "\xC7\x44" => "\xE3\x82\xAC", + "\xC7\x45" => "\xE3\x82\xAD", + "\xC7\x46" => "\xE3\x82\xAE", + "\xC7\x47" => "\xE3\x82\xAF", + "\xC7\x48" => "\xE3\x82\xB0", + "\xC7\x49" => "\xE3\x82\xB1", + "\xC7\x4A" => "\xE3\x82\xB2", + "\xC7\x4B" => "\xE3\x82\xB3", + "\xC7\x4C" => "\xE3\x82\xB4", + "\xC7\x4D" => "\xE3\x82\xB5", + "\xC7\x4E" => "\xE3\x82\xB6", + "\xC7\x4F" => "\xE3\x82\xB7", + "\xC7\x50" => "\xE3\x82\xB8", + "\xC7\x51" => "\xE3\x82\xB9", + "\xC7\x52" => "\xE3\x82\xBA", + "\xC7\x53" => "\xE3\x82\xBB", + "\xC7\x54" => "\xE3\x82\xBC", + "\xC7\x55" => "\xE3\x82\xBD", + "\xC7\x56" => "\xE3\x82\xBE", + "\xC7\x57" => "\xE3\x82\xBF", + "\xC7\x58" => "\xE3\x83\x80", + "\xC7\x59" => "\xE3\x83\x81", + "\xC7\x5A" => "\xE3\x83\x82", + "\xC7\x5B" => "\xE3\x83\x83", + "\xC7\x5C" => "\xE3\x83\x84", + "\xC7\x5D" => "\xE3\x83\x85", + "\xC7\x5E" => "\xE3\x83\x86", + "\xC7\x5F" => "\xE3\x83\x87", + "\xC7\x60" => "\xE3\x83\x88", + "\xC7\x61" => "\xE3\x83\x89", + "\xC7\x62" => "\xE3\x83\x8A", + "\xC7\x63" => "\xE3\x83\x8B", + "\xC7\x64" => "\xE3\x83\x8C", + "\xC7\x65" => "\xE3\x83\x8D", + "\xC7\x66" => "\xE3\x83\x8E", + "\xC7\x67" => "\xE3\x83\x8F", + "\xC7\x68" => "\xE3\x83\x90", + "\xC7\x69" => "\xE3\x83\x91", + "\xC7\x6A" => "\xE3\x83\x92", + "\xC7\x6B" => "\xE3\x83\x93", + "\xC7\x6C" => "\xE3\x83\x94", + "\xC7\x6D" => "\xE3\x83\x95", + "\xC7\x6E" => "\xE3\x83\x96", + "\xC7\x6F" => "\xE3\x83\x97", + "\xC7\x70" => "\xE3\x83\x98", + "\xC7\x71" => "\xE3\x83\x99", + "\xC7\x72" => "\xE3\x83\x9A", + "\xC7\x73" => "\xE3\x83\x9B", + "\xC7\x74" => "\xE3\x83\x9C", + "\xC7\x75" => "\xE3\x83\x9D", + "\xC7\x76" => "\xE3\x83\x9E", + "\xC7\x77" => "\xE3\x83\x9F", + "\xC7\x78" => "\xE3\x83\xA0", + "\xC7\x79" => "\xE3\x83\xA1", + "\xC7\x7A" => "\xE3\x83\xA2", + "\xC7\x7B" => "\xE3\x83\xA3", + "\xC7\x7C" => "\xE3\x83\xA4", + "\xC7\x7D" => "\xE3\x83\xA5", + "\xC7\x7E" => "\xE3\x83\xA6", + "\xC7\xA1" => "\xE3\x83\xA7", + "\xC7\xA2" => "\xE3\x83\xA8", + "\xC7\xA3" => "\xE3\x83\xA9", + "\xC7\xA4" => "\xE3\x83\xAA", + "\xC7\xA5" => "\xE3\x83\xAB", + "\xC7\xA6" => "\xE3\x83\xAC", + "\xC7\xA7" => "\xE3\x83\xAD", + "\xC7\xA8" => "\xE3\x83\xAE", + "\xC7\xA9" => "\xE3\x83\xAF", + "\xC7\xAA" => "\xE3\x83\xB0", + "\xC7\xAB" => "\xE3\x83\xB1", + "\xC7\xAC" => "\xE3\x83\xB2", + "\xC7\xAD" => "\xE3\x83\xB3", + "\xC7\xAE" => "\xE3\x83\xB4", + "\xC7\xAF" => "\xE3\x83\xB5", + "\xC7\xB0" => "\xE3\x83\xB6", + "\xC7\xB1" => "\xD0\x94", + "\xC7\xB2" => "\xD0\x95", + "\xC7\xB3" => "\xD0\x81", + "\xC7\xB4" => "\xD0\x96", + "\xC7\xB5" => "\xD0\x97", + "\xC7\xB6" => "\xD0\x98", + "\xC7\xB7" => "\xD0\x99", + "\xC7\xB8" => "\xD0\x9A", + "\xC7\xB9" => "\xD0\x9B", + "\xC7\xBA" => "\xD0\x9C", + "\xC7\xBB" => "\xD0\xA3", + "\xC7\xBC" => "\xD0\xA4", + "\xC7\xBD" => "\xD0\xA5", + "\xC7\xBE" => "\xD0\xA6", + "\xC7\xBF" => "\xD0\xA7", + "\xC7\xC0" => "\xD0\xA8", + "\xC7\xC1" => "\xD0\xA9", + "\xC7\xC2" => "\xD0\xAA", + "\xC7\xC3" => "\xD0\xAB", + "\xC7\xC4" => "\xD0\xAC", + "\xC7\xC5" => "\xD0\xAD", + "\xC7\xC6" => "\xD0\xAE", + "\xC7\xC7" => "\xD0\xAF", + "\xC7\xC8" => "\xD0\xB0", + "\xC7\xC9" => "\xD0\xB1", + "\xC7\xCA" => "\xD0\xB2", + "\xC7\xCB" => "\xD0\xB3", + "\xC7\xCC" => "\xD0\xB4", + "\xC7\xCD" => "\xD0\xB5", + "\xC7\xCE" => "\xD1\x91", + "\xC7\xCF" => "\xD0\xB6", + "\xC7\xD0" => "\xD0\xB7", + "\xC7\xD1" => "\xD0\xB8", + "\xC7\xD2" => "\xD0\xB9", + "\xC7\xD3" => "\xD0\xBA", + "\xC7\xD4" => "\xD0\xBB", + "\xC7\xD5" => "\xD0\xBC", + "\xC7\xD6" => "\xD0\xBD", + "\xC7\xD7" => "\xD0\xBE", + "\xC7\xD8" => "\xD0\xBF", + "\xC7\xD9" => "\xD1\x80", + "\xC7\xDA" => "\xD1\x81", + "\xC7\xDB" => "\xD1\x82", + "\xC7\xDC" => "\xD1\x83", + "\xC7\xDD" => "\xD1\x84", + "\xC7\xDE" => "\xD1\x85", + "\xC7\xDF" => "\xD1\x86", + "\xC7\xE0" => "\xD1\x87", + "\xC7\xE1" => "\xD1\x88", + "\xC7\xE2" => "\xD1\x89", + "\xC7\xE3" => "\xD1\x8A", + "\xC7\xE4" => "\xD1\x8B", + "\xC7\xE5" => "\xD1\x8C", + "\xC7\xE6" => "\xD1\x8D", + "\xC7\xE7" => "\xD1\x8E", + "\xC7\xE8" => "\xD1\x8F", + "\xC7\xE9" => "\xE2\x91\xA0", + "\xC7\xEA" => "\xE2\x91\xA1", + "\xC7\xEB" => "\xE2\x91\xA2", + "\xC7\xEC" => "\xE2\x91\xA3", + "\xC7\xED" => "\xE2\x91\xA4", + "\xC7\xEE" => "\xE2\x91\xA5", + "\xC7\xEF" => "\xE2\x91\xA6", + "\xC7\xF0" => "\xE2\x91\xA7", + "\xC7\xF1" => "\xE2\x91\xA8", + "\xC7\xF2" => "\xE2\x91\xA9", + "\xC7\xF3" => "\xE2\x91\xB4", + "\xC7\xF4" => "\xE2\x91\xB5", + "\xC7\xF5" => "\xE2\x91\xB6", + "\xC7\xF6" => "\xE2\x91\xB7", + "\xC7\xF7" => "\xE2\x91\xB8", + "\xC7\xF8" => "\xE2\x91\xB9", + "\xC7\xF9" => "\xE2\x91\xBA", + "\xC7\xFA" => "\xE2\x91\xBB", + "\xC7\xFB" => "\xE2\x91\xBC", + "\xC7\xFC" => "\xE2\x91\xBD", + "\xC9\x40" => "\xE4\xB9\x82", + "\xC9\x41" => "\xE4\xB9\x9C", + "\xC9\x42" => "\xE5\x87\xB5", + "\xC9\x43" => "\xE5\x8C\x9A", + "\xC9\x44" => "\xE5\x8E\x82", + "\xC9\x45" => "\xE4\xB8\x87", + "\xC9\x46" => "\xE4\xB8\x8C", + "\xC9\x47" => "\xE4\xB9\x87", + "\xC9\x48" => "\xE4\xBA\x8D", + "\xC9\x49" => "\xE5\x9B\x97", + "\xC9\x4A" => "\xEF\xA8\x8C", + "\xC9\x4B" => "\xE5\xB1\xAE", + "\xC9\x4C" => "\xE5\xBD\xB3", + "\xC9\x4D" => "\xE4\xB8\x8F", + "\xC9\x4E" => "\xE5\x86\x87", + "\xC9\x4F" => "\xE4\xB8\x8E", + "\xC9\x50" => "\xE4\xB8\xAE", + "\xC9\x51" => "\xE4\xBA\x93", + "\xC9\x52" => "\xE4\xBB\x82", + "\xC9\x53" => "\xE4\xBB\x89", + "\xC9\x54" => "\xE4\xBB\x88", + "\xC9\x55" => "\xE5\x86\x98", + "\xC9\x56" => "\xE5\x8B\xBC", + "\xC9\x57" => "\xE5\x8D\xAC", + "\xC9\x58" => "\xE5\x8E\xB9", + "\xC9\x59" => "\xE5\x9C\xA0", + "\xC9\x5A" => "\xE5\xA4\x83", + "\xC9\x5B" => "\xE5\xA4\xAC", + "\xC9\x5C" => "\xE5\xB0\x90", + "\xC9\x5D" => "\xE5\xB7\xBF", + "\xC9\x5E" => "\xE6\x97\xA1", + "\xC9\x5F" => "\xE6\xAE\xB3", + "\xC9\x60" => "\xE6\xAF\x8C", + "\xC9\x61" => "\xE6\xB0\x94", + "\xC9\x62" => "\xE7\x88\xBF", + "\xC9\x63" => "\xE4\xB8\xB1", + "\xC9\x64" => "\xE4\xB8\xBC", + "\xC9\x65" => "\xE4\xBB\xA8", + "\xC9\x66" => "\xE4\xBB\x9C", + "\xC9\x67" => "\xE4\xBB\xA9", + "\xC9\x68" => "\xE4\xBB\xA1", + "\xC9\x69" => "\xE4\xBB\x9D", + "\xC9\x6A" => "\xE4\xBB\x9A", + "\xC9\x6B" => "\xE5\x88\x8C", + "\xC9\x6C" => "\xE5\x8C\x9C", + "\xC9\x6D" => "\xE5\x8D\x8C", + "\xC9\x6E" => "\xE5\x9C\xA2", + "\xC9\x6F" => "\xE5\x9C\xA3", + "\xC9\x70" => "\xE5\xA4\x97", + "\xC9\x71" => "\xE5\xA4\xAF", + "\xC9\x72" => "\xE5\xAE\x81", + "\xC9\x73" => "\xE5\xAE\x84", + "\xC9\x74" => "\xE5\xB0\x92", + "\xC9\x75" => "\xE5\xB0\xBB", + "\xC9\x76" => "\xE5\xB1\xB4", + "\xC9\x77" => "\xE5\xB1\xB3", + "\xC9\x78" => "\xE5\xB8\x84", + "\xC9\x79" => "\xE5\xBA\x80", + "\xC9\x7A" => "\xE5\xBA\x82", + "\xC9\x7B" => "\xE5\xBF\x89", + "\xC9\x7C" => "\xE6\x88\x89", + "\xC9\x7D" => "\xE6\x89\x90", + "\xC9\x7E" => "\xE6\xB0\x95", + "\xC9\xA1" => "\xE6\xB0\xB6", + "\xC9\xA2" => "\xE6\xB1\x83", + "\xC9\xA3" => "\xE6\xB0\xBF", + "\xC9\xA4" => "\xE6\xB0\xBB", + "\xC9\xA5" => "\xE7\x8A\xAE", + "\xC9\xA6" => "\xE7\x8A\xB0", + "\xC9\xA7" => "\xE7\x8E\x8A", + "\xC9\xA8" => "\xE7\xA6\xB8", + "\xC9\xA9" => "\xE8\x82\x8A", + "\xC9\xAA" => "\xE9\x98\x9E", + "\xC9\xAB" => "\xE4\xBC\x8E", + "\xC9\xAC" => "\xE4\xBC\x98", + "\xC9\xAD" => "\xE4\xBC\xAC", + "\xC9\xAE" => "\xE4\xBB\xB5", + "\xC9\xAF" => "\xE4\xBC\x94", + "\xC9\xB0" => "\xE4\xBB\xB1", + "\xC9\xB1" => "\xE4\xBC\x80", + "\xC9\xB2" => "\xE4\xBB\xB7", + "\xC9\xB3" => "\xE4\xBC\x88", + "\xC9\xB4" => "\xE4\xBC\x9D", + "\xC9\xB5" => "\xE4\xBC\x82", + "\xC9\xB6" => "\xE4\xBC\x85", + "\xC9\xB7" => "\xE4\xBC\xA2", + "\xC9\xB8" => "\xE4\xBC\x93", + "\xC9\xB9" => "\xE4\xBC\x84", + "\xC9\xBA" => "\xE4\xBB\xB4", + "\xC9\xBB" => "\xE4\xBC\x92", + "\xC9\xBC" => "\xE5\x86\xB1", + "\xC9\xBD" => "\xE5\x88\x93", + "\xC9\xBE" => "\xE5\x88\x89", + "\xC9\xBF" => "\xE5\x88\x90", + "\xC9\xC0" => "\xE5\x8A\xA6", + "\xC9\xC1" => "\xE5\x8C\xA2", + "\xC9\xC2" => "\xE5\x8C\x9F", + "\xC9\xC3" => "\xE5\x8D\x8D", + "\xC9\xC4" => "\xE5\x8E\x8A", + "\xC9\xC5" => "\xE5\x90\x87", + "\xC9\xC6" => "\xE5\x9B\xA1", + "\xC9\xC7" => "\xE5\x9B\x9F", + "\xC9\xC8" => "\xE5\x9C\xAE", + "\xC9\xC9" => "\xE5\x9C\xAA", + "\xC9\xCA" => "\xE5\x9C\xB4", + "\xC9\xCB" => "\xE5\xA4\xBC", + "\xC9\xCC" => "\xE5\xA6\x80", + "\xC9\xCD" => "\xE5\xA5\xBC", + "\xC9\xCE" => "\xE5\xA6\x85", + "\xC9\xCF" => "\xE5\xA5\xBB", + "\xC9\xD0" => "\xE5\xA5\xBE", + "\xC9\xD1" => "\xE5\xA5\xB7", + "\xC9\xD2" => "\xE5\xA5\xBF", + "\xC9\xD3" => "\xE5\xAD\x96", + "\xC9\xD4" => "\xE5\xB0\x95", + "\xC9\xD5" => "\xE5\xB0\xA5", + "\xC9\xD6" => "\xE5\xB1\xBC", + "\xC9\xD7" => "\xE5\xB1\xBA", + "\xC9\xD8" => "\xE5\xB1\xBB", + "\xC9\xD9" => "\xE5\xB1\xBE", + "\xC9\xDA" => "\xE5\xB7\x9F", + "\xC9\xDB" => "\xE5\xB9\xB5", + "\xC9\xDC" => "\xE5\xBA\x84", + "\xC9\xDD" => "\xE5\xBC\x82", + "\xC9\xDE" => "\xE5\xBC\x9A", + "\xC9\xDF" => "\xE5\xBD\xB4", + "\xC9\xE0" => "\xE5\xBF\x95", + "\xC9\xE1" => "\xE5\xBF\x94", + "\xC9\xE2" => "\xE5\xBF\x8F", + "\xC9\xE3" => "\xE6\x89\x9C", + "\xC9\xE4" => "\xE6\x89\x9E", + "\xC9\xE5" => "\xE6\x89\xA4", + "\xC9\xE6" => "\xE6\x89\xA1", + "\xC9\xE7" => "\xE6\x89\xA6", + "\xC9\xE8" => "\xE6\x89\xA2", + "\xC9\xE9" => "\xE6\x89\x99", + "\xC9\xEA" => "\xE6\x89\xA0", + "\xC9\xEB" => "\xE6\x89\x9A", + "\xC9\xEC" => "\xE6\x89\xA5", + "\xC9\xED" => "\xE6\x97\xAF", + "\xC9\xEE" => "\xE6\x97\xAE", + "\xC9\xEF" => "\xE6\x9C\xBE", + "\xC9\xF0" => "\xE6\x9C\xB9", + "\xC9\xF1" => "\xE6\x9C\xB8", + "\xC9\xF2" => "\xE6\x9C\xBB", + "\xC9\xF3" => "\xE6\x9C\xBA", + "\xC9\xF4" => "\xE6\x9C\xBF", + "\xC9\xF5" => "\xE6\x9C\xBC", + "\xC9\xF6" => "\xE6\x9C\xB3", + "\xC9\xF7" => "\xE6\xB0\x98", + "\xC9\xF8" => "\xE6\xB1\x86", + "\xC9\xF9" => "\xE6\xB1\x92", + "\xC9\xFA" => "\xE6\xB1\x9C", + "\xC9\xFB" => "\xE6\xB1\x8F", + "\xC9\xFC" => "\xE6\xB1\x8A", + "\xC9\xFD" => "\xE6\xB1\x94", + "\xC9\xFE" => "\xE6\xB1\x8B", + "\xCA\x40" => "\xE6\xB1\x8C", + "\xCA\x41" => "\xE7\x81\xB1", + "\xCA\x42" => "\xE7\x89\x9E", + "\xCA\x43" => "\xE7\x8A\xB4", + "\xCA\x44" => "\xE7\x8A\xB5", + "\xCA\x45" => "\xE7\x8E\x8E", + "\xCA\x46" => "\xE7\x94\xAA", + "\xCA\x47" => "\xE7\x99\xBF", + "\xCA\x48" => "\xE7\xA9\xB5", + "\xCA\x49" => "\xE7\xBD\x91", + "\xCA\x4A" => "\xE8\x89\xB8", + "\xCA\x4B" => "\xE8\x89\xBC", + "\xCA\x4C" => "\xE8\x8A\x80", + "\xCA\x4D" => "\xE8\x89\xBD", + "\xCA\x4E" => "\xE8\x89\xBF", + "\xCA\x4F" => "\xE8\x99\x8D", + "\xCA\x50" => "\xE8\xA5\xBE", + "\xCA\x51" => "\xE9\x82\x99", + "\xCA\x52" => "\xE9\x82\x97", + "\xCA\x53" => "\xE9\x82\x98", + "\xCA\x54" => "\xE9\x82\x9B", + "\xCA\x55" => "\xE9\x82\x94", + "\xCA\x56" => "\xE9\x98\xA2", + "\xCA\x57" => "\xE9\x98\xA4", + "\xCA\x58" => "\xE9\x98\xA0", + "\xCA\x59" => "\xE9\x98\xA3", + "\xCA\x5A" => "\xE4\xBD\x96", + "\xCA\x5B" => "\xE4\xBC\xBB", + "\xCA\x5C" => "\xE4\xBD\xA2", + "\xCA\x5D" => "\xE4\xBD\x89", + "\xCA\x5E" => "\xE4\xBD\x93", + "\xCA\x5F" => "\xE4\xBD\xA4", + "\xCA\x60" => "\xE4\xBC\xBE", + "\xCA\x61" => "\xE4\xBD\xA7", + "\xCA\x62" => "\xE4\xBD\x92", + "\xCA\x63" => "\xE4\xBD\x9F", + "\xCA\x64" => "\xE4\xBD\x81", + "\xCA\x65" => "\xE4\xBD\x98", + "\xCA\x66" => "\xE4\xBC\xAD", + "\xCA\x67" => "\xE4\xBC\xB3", + "\xCA\x68" => "\xE4\xBC\xBF", + "\xCA\x69" => "\xE4\xBD\xA1", + "\xCA\x6A" => "\xE5\x86\x8F", + "\xCA\x6B" => "\xE5\x86\xB9", + "\xCA\x6C" => "\xE5\x88\x9C", + "\xCA\x6D" => "\xE5\x88\x9E", + "\xCA\x6E" => "\xE5\x88\xA1", + "\xCA\x6F" => "\xE5\x8A\xAD", + "\xCA\x70" => "\xE5\x8A\xAE", + "\xCA\x71" => "\xE5\x8C\x89", + "\xCA\x72" => "\xE5\x8D\xA3", + "\xCA\x73" => "\xE5\x8D\xB2", + "\xCA\x74" => "\xE5\x8E\x8E", + "\xCA\x75" => "\xE5\x8E\x8F", + "\xCA\x76" => "\xE5\x90\xB0", + "\xCA\x77" => "\xE5\x90\xB7", + "\xCA\x78" => "\xE5\x90\xAA", + "\xCA\x79" => "\xE5\x91\x94", + "\xCA\x7A" => "\xE5\x91\x85", + "\xCA\x7B" => "\xE5\x90\x99", + "\xCA\x7C" => "\xE5\x90\x9C", + "\xCA\x7D" => "\xE5\x90\xA5", + "\xCA\x7E" => "\xE5\x90\x98", + "\xCA\xA1" => "\xE5\x90\xBD", + "\xCA\xA2" => "\xE5\x91\x8F", + "\xCA\xA3" => "\xE5\x91\x81", + "\xCA\xA4" => "\xE5\x90\xA8", + "\xCA\xA5" => "\xE5\x90\xA4", + "\xCA\xA6" => "\xE5\x91\x87", + "\xCA\xA7" => "\xE5\x9B\xAE", + "\xCA\xA8" => "\xE5\x9B\xA7", + "\xCA\xA9" => "\xE5\x9B\xA5", + "\xCA\xAA" => "\xE5\x9D\x81", + "\xCA\xAB" => "\xE5\x9D\x85", + "\xCA\xAC" => "\xE5\x9D\x8C", + "\xCA\xAD" => "\xE5\x9D\x89", + "\xCA\xAE" => "\xE5\x9D\x8B", + "\xCA\xAF" => "\xE5\x9D\x92", + "\xCA\xB0" => "\xE5\xA4\x86", + "\xCA\xB1" => "\xE5\xA5\x80", + "\xCA\xB2" => "\xE5\xA6\xA6", + "\xCA\xB3" => "\xE5\xA6\x98", + "\xCA\xB4" => "\xE5\xA6\xA0", + "\xCA\xB5" => "\xE5\xA6\x97", + "\xCA\xB6" => "\xE5\xA6\x8E", + "\xCA\xB7" => "\xE5\xA6\xA2", + "\xCA\xB8" => "\xE5\xA6\x90", + "\xCA\xB9" => "\xE5\xA6\x8F", + "\xCA\xBA" => "\xE5\xA6\xA7", + "\xCA\xBB" => "\xE5\xA6\xA1", + "\xCA\xBC" => "\xE5\xAE\x8E", + "\xCA\xBD" => "\xE5\xAE\x92", + "\xCA\xBE" => "\xE5\xB0\xA8", + "\xCA\xBF" => "\xE5\xB0\xAA", + "\xCA\xC0" => "\xE5\xB2\x8D", + "\xCA\xC1" => "\xE5\xB2\x8F", + "\xCA\xC2" => "\xE5\xB2\x88", + "\xCA\xC3" => "\xE5\xB2\x8B", + "\xCA\xC4" => "\xE5\xB2\x89", + "\xCA\xC5" => "\xE5\xB2\x92", + "\xCA\xC6" => "\xE5\xB2\x8A", + "\xCA\xC7" => "\xE5\xB2\x86", + "\xCA\xC8" => "\xE5\xB2\x93", + "\xCA\xC9" => "\xE5\xB2\x95", + "\xCA\xCA" => "\xE5\xB7\xA0", + "\xCA\xCB" => "\xE5\xB8\x8A", + "\xCA\xCC" => "\xE5\xB8\x8E", + "\xCA\xCD" => "\xE5\xBA\x8B", + "\xCA\xCE" => "\xE5\xBA\x89", + "\xCA\xCF" => "\xE5\xBA\x8C", + "\xCA\xD0" => "\xE5\xBA\x88", + "\xCA\xD1" => "\xE5\xBA\x8D", + "\xCA\xD2" => "\xE5\xBC\x85", + "\xCA\xD3" => "\xE5\xBC\x9D", + "\xCA\xD4" => "\xE5\xBD\xB8", + "\xCA\xD5" => "\xE5\xBD\xB6", + "\xCA\xD6" => "\xE5\xBF\x92", + "\xCA\xD7" => "\xE5\xBF\x91", + "\xCA\xD8" => "\xE5\xBF\x90", + "\xCA\xD9" => "\xE5\xBF\xAD", + "\xCA\xDA" => "\xE5\xBF\xA8", + "\xCA\xDB" => "\xE5\xBF\xAE", + "\xCA\xDC" => "\xE5\xBF\xB3", + "\xCA\xDD" => "\xE5\xBF\xA1", + "\xCA\xDE" => "\xE5\xBF\xA4", + "\xCA\xDF" => "\xE5\xBF\xA3", + "\xCA\xE0" => "\xE5\xBF\xBA", + "\xCA\xE1" => "\xE5\xBF\xAF", + "\xCA\xE2" => "\xE5\xBF\xB7", + "\xCA\xE3" => "\xE5\xBF\xBB", + "\xCA\xE4" => "\xE6\x80\x80", + "\xCA\xE5" => "\xE5\xBF\xB4", + "\xCA\xE6" => "\xE6\x88\xBA", + "\xCA\xE7" => "\xE6\x8A\x83", + "\xCA\xE8" => "\xE6\x8A\x8C", + "\xCA\xE9" => "\xE6\x8A\x8E", + "\xCA\xEA" => "\xE6\x8A\x8F", + "\xCA\xEB" => "\xE6\x8A\x94", + "\xCA\xEC" => "\xE6\x8A\x87", + "\xCA\xED" => "\xE6\x89\xB1", + "\xCA\xEE" => "\xE6\x89\xBB", + "\xCA\xEF" => "\xE6\x89\xBA", + "\xCA\xF0" => "\xE6\x89\xB0", + "\xCA\xF1" => "\xE6\x8A\x81", + "\xCA\xF2" => "\xE6\x8A\x88", + "\xCA\xF3" => "\xE6\x89\xB7", + "\xCA\xF4" => "\xE6\x89\xBD", + "\xCA\xF5" => "\xE6\x89\xB2", + "\xCA\xF6" => "\xE6\x89\xB4", + "\xCA\xF7" => "\xE6\x94\xB7", + "\xCA\xF8" => "\xE6\x97\xB0", + "\xCA\xF9" => "\xE6\x97\xB4", + "\xCA\xFA" => "\xE6\x97\xB3", + "\xCA\xFB" => "\xE6\x97\xB2", + "\xCA\xFC" => "\xE6\x97\xB5", + "\xCA\xFD" => "\xE6\x9D\x85", + "\xCA\xFE" => "\xE6\x9D\x87", + "\xCB\x40" => "\xE6\x9D\x99", + "\xCB\x41" => "\xE6\x9D\x95", + "\xCB\x42" => "\xE6\x9D\x8C", + "\xCB\x43" => "\xE6\x9D\x88", + "\xCB\x44" => "\xE6\x9D\x9D", + "\xCB\x45" => "\xE6\x9D\x8D", + "\xCB\x46" => "\xE6\x9D\x9A", + "\xCB\x47" => "\xE6\x9D\x8B", + "\xCB\x48" => "\xE6\xAF\x90", + "\xCB\x49" => "\xE6\xB0\x99", + "\xCB\x4A" => "\xE6\xB0\x9A", + "\xCB\x4B" => "\xE6\xB1\xB8", + "\xCB\x4C" => "\xE6\xB1\xA7", + "\xCB\x4D" => "\xE6\xB1\xAB", + "\xCB\x4E" => "\xE6\xB2\x84", + "\xCB\x4F" => "\xE6\xB2\x8B", + "\xCB\x50" => "\xE6\xB2\x8F", + "\xCB\x51" => "\xE6\xB1\xB1", + "\xCB\x52" => "\xE6\xB1\xAF", + "\xCB\x53" => "\xE6\xB1\xA9", + "\xCB\x54" => "\xE6\xB2\x9A", + "\xCB\x55" => "\xE6\xB1\xAD", + "\xCB\x56" => "\xE6\xB2\x87", + "\xCB\x57" => "\xE6\xB2\x95", + "\xCB\x58" => "\xE6\xB2\x9C", + "\xCB\x59" => "\xE6\xB1\xA6", + "\xCB\x5A" => "\xE6\xB1\xB3", + "\xCB\x5B" => "\xE6\xB1\xA5", + "\xCB\x5C" => "\xE6\xB1\xBB", + "\xCB\x5D" => "\xE6\xB2\x8E", + "\xCB\x5E" => "\xE7\x81\xB4", + "\xCB\x5F" => "\xE7\x81\xBA", + "\xCB\x60" => "\xE7\x89\xA3", + "\xCB\x61" => "\xE7\x8A\xBF", + "\xCB\x62" => "\xE7\x8A\xBD", + "\xCB\x63" => "\xE7\x8B\x83", + "\xCB\x64" => "\xE7\x8B\x86", + "\xCB\x65" => "\xE7\x8B\x81", + "\xCB\x66" => "\xE7\x8A\xBA", + "\xCB\x67" => "\xE7\x8B\x85", + "\xCB\x68" => "\xE7\x8E\x95", + "\xCB\x69" => "\xE7\x8E\x97", + "\xCB\x6A" => "\xE7\x8E\x93", + "\xCB\x6B" => "\xE7\x8E\x94", + "\xCB\x6C" => "\xE7\x8E\x92", + "\xCB\x6D" => "\xE7\x94\xBA", + "\xCB\x6E" => "\xE7\x94\xB9", + "\xCB\x6F" => "\xE7\x96\x94", + "\xCB\x70" => "\xE7\x96\x95", + "\xCB\x71" => "\xE7\x9A\x81", + "\xCB\x72" => "\xE7\xA4\xBD", + "\xCB\x73" => "\xE8\x80\xB4", + "\xCB\x74" => "\xE8\x82\x95", + "\xCB\x75" => "\xE8\x82\x99", + "\xCB\x76" => "\xE8\x82\x90", + "\xCB\x77" => "\xE8\x82\x92", + "\xCB\x78" => "\xE8\x82\x9C", + "\xCB\x79" => "\xE8\x8A\x90", + "\xCB\x7A" => "\xE8\x8A\x8F", + "\xCB\x7B" => "\xE8\x8A\x85", + "\xCB\x7C" => "\xE8\x8A\x8E", + "\xCB\x7D" => "\xE8\x8A\x91", + "\xCB\x7E" => "\xE8\x8A\x93", + "\xCB\xA1" => "\xE8\x8A\x8A", + "\xCB\xA2" => "\xE8\x8A\x83", + "\xCB\xA3" => "\xE8\x8A\x84", + "\xCB\xA4" => "\xE8\xB1\xB8", + "\xCB\xA5" => "\xE8\xBF\x89", + "\xCB\xA6" => "\xE8\xBE\xBF", + "\xCB\xA7" => "\xE9\x82\x9F", + "\xCB\xA8" => "\xE9\x82\xA1", + "\xCB\xA9" => "\xE9\x82\xA5", + "\xCB\xAA" => "\xE9\x82\x9E", + "\xCB\xAB" => "\xE9\x82\xA7", + "\xCB\xAC" => "\xE9\x82\xA0", + "\xCB\xAD" => "\xE9\x98\xB0", + "\xCB\xAE" => "\xE9\x98\xA8", + "\xCB\xAF" => "\xE9\x98\xAF", + "\xCB\xB0" => "\xE9\x98\xAD", + "\xCB\xB1" => "\xE4\xB8\xB3", + "\xCB\xB2" => "\xE4\xBE\x98", + "\xCB\xB3" => "\xE4\xBD\xBC", + "\xCB\xB4" => "\xE4\xBE\x85", + "\xCB\xB5" => "\xE4\xBD\xBD", + "\xCB\xB6" => "\xE4\xBE\x80", + "\xCB\xB7" => "\xE4\xBE\x87", + "\xCB\xB8" => "\xE4\xBD\xB6", + "\xCB\xB9" => "\xE4\xBD\xB4", + "\xCB\xBA" => "\xE4\xBE\x89", + "\xCB\xBB" => "\xE4\xBE\x84", + "\xCB\xBC" => "\xE4\xBD\xB7", + "\xCB\xBD" => "\xE4\xBD\x8C", + "\xCB\xBE" => "\xE4\xBE\x97", + "\xCB\xBF" => "\xE4\xBD\xAA", + "\xCB\xC0" => "\xE4\xBE\x9A", + "\xCB\xC1" => "\xE4\xBD\xB9", + "\xCB\xC2" => "\xE4\xBE\x81", + "\xCB\xC3" => "\xE4\xBD\xB8", + "\xCB\xC4" => "\xE4\xBE\x90", + "\xCB\xC5" => "\xE4\xBE\x9C", + "\xCB\xC6" => "\xE4\xBE\x94", + "\xCB\xC7" => "\xE4\xBE\x9E", + "\xCB\xC8" => "\xE4\xBE\x92", + "\xCB\xC9" => "\xE4\xBE\x82", + "\xCB\xCA" => "\xE4\xBE\x95", + "\xCB\xCB" => "\xE4\xBD\xAB", + "\xCB\xCC" => "\xE4\xBD\xAE", + "\xCB\xCD" => "\xE5\x86\x9E", + "\xCB\xCE" => "\xE5\x86\xBC", + "\xCB\xCF" => "\xE5\x86\xBE", + "\xCB\xD0" => "\xE5\x88\xB5", + "\xCB\xD1" => "\xE5\x88\xB2", + "\xCB\xD2" => "\xE5\x88\xB3", + "\xCB\xD3" => "\xE5\x89\x86", + "\xCB\xD4" => "\xE5\x88\xB1", + "\xCB\xD5" => "\xE5\x8A\xBC", + "\xCB\xD6" => "\xE5\x8C\x8A", + "\xCB\xD7" => "\xE5\x8C\x8B", + "\xCB\xD8" => "\xE5\x8C\xBC", + "\xCB\xD9" => "\xE5\x8E\x92", + "\xCB\xDA" => "\xE5\x8E\x94", + "\xCB\xDB" => "\xE5\x92\x87", + "\xCB\xDC" => "\xE5\x91\xBF", + "\xCB\xDD" => "\xE5\x92\x81", + "\xCB\xDE" => "\xE5\x92\x91", + "\xCB\xDF" => "\xE5\x92\x82", + "\xCB\xE0" => "\xE5\x92\x88", + "\xCB\xE1" => "\xE5\x91\xAB", + "\xCB\xE2" => "\xE5\x91\xBA", + "\xCB\xE3" => "\xE5\x91\xBE", + "\xCB\xE4" => "\xE5\x91\xA5", + "\xCB\xE5" => "\xE5\x91\xAC", + "\xCB\xE6" => "\xE5\x91\xB4", + "\xCB\xE7" => "\xE5\x91\xA6", + "\xCB\xE8" => "\xE5\x92\x8D", + "\xCB\xE9" => "\xE5\x91\xAF", + "\xCB\xEA" => "\xE5\x91\xA1", + "\xCB\xEB" => "\xE5\x91\xA0", + "\xCB\xEC" => "\xE5\x92\x98", + "\xCB\xED" => "\xE5\x91\xA3", + "\xCB\xEE" => "\xE5\x91\xA7", + "\xCB\xEF" => "\xE5\x91\xA4", + "\xCB\xF0" => "\xE5\x9B\xB7", + "\xCB\xF1" => "\xE5\x9B\xB9", + "\xCB\xF2" => "\xE5\x9D\xAF", + "\xCB\xF3" => "\xE5\x9D\xB2", + "\xCB\xF4" => "\xE5\x9D\xAD", + "\xCB\xF5" => "\xE5\x9D\xAB", + "\xCB\xF6" => "\xE5\x9D\xB1", + "\xCB\xF7" => "\xE5\x9D\xB0", + "\xCB\xF8" => "\xE5\x9D\xB6", + "\xCB\xF9" => "\xE5\x9E\x80", + "\xCB\xFA" => "\xE5\x9D\xB5", + "\xCB\xFB" => "\xE5\x9D\xBB", + "\xCB\xFC" => "\xE5\x9D\xB3", + "\xCB\xFD" => "\xE5\x9D\xB4", + "\xCB\xFE" => "\xE5\x9D\xA2", + "\xCC\x40" => "\xE5\x9D\xA8", + "\xCC\x41" => "\xE5\x9D\xBD", + "\xCC\x42" => "\xE5\xA4\x8C", + "\xCC\x43" => "\xE5\xA5\x85", + "\xCC\x44" => "\xE5\xA6\xB5", + "\xCC\x45" => "\xE5\xA6\xBA", + "\xCC\x46" => "\xE5\xA7\x8F", + "\xCC\x47" => "\xE5\xA7\x8E", + "\xCC\x48" => "\xE5\xA6\xB2", + "\xCC\x49" => "\xE5\xA7\x8C", + "\xCC\x4A" => "\xE5\xA7\x81", + "\xCC\x4B" => "\xE5\xA6\xB6", + "\xCC\x4C" => "\xE5\xA6\xBC", + "\xCC\x4D" => "\xE5\xA7\x83", + "\xCC\x4E" => "\xE5\xA7\x96", + "\xCC\x4F" => "\xE5\xA6\xB1", + "\xCC\x50" => "\xE5\xA6\xBD", + "\xCC\x51" => "\xE5\xA7\x80", + "\xCC\x52" => "\xE5\xA7\x88", + "\xCC\x53" => "\xE5\xA6\xB4", + "\xCC\x54" => "\xE5\xA7\x87", + "\xCC\x55" => "\xE5\xAD\xA2", + "\xCC\x56" => "\xE5\xAD\xA5", + "\xCC\x57" => "\xE5\xAE\x93", + "\xCC\x58" => "\xE5\xAE\x95", + "\xCC\x59" => "\xE5\xB1\x84", + "\xCC\x5A" => "\xE5\xB1\x87", + "\xCC\x5B" => "\xE5\xB2\xAE", + "\xCC\x5C" => "\xE5\xB2\xA4", + "\xCC\x5D" => "\xE5\xB2\xA0", + "\xCC\x5E" => "\xE5\xB2\xB5", + "\xCC\x5F" => "\xE5\xB2\xAF", + "\xCC\x60" => "\xE5\xB2\xA8", + "\xCC\x61" => "\xE5\xB2\xAC", + "\xCC\x62" => "\xE5\xB2\x9F", + "\xCC\x63" => "\xE5\xB2\xA3", + "\xCC\x64" => "\xE5\xB2\xAD", + "\xCC\x65" => "\xE5\xB2\xA2", + "\xCC\x66" => "\xE5\xB2\xAA", + "\xCC\x67" => "\xE5\xB2\xA7", + "\xCC\x68" => "\xE5\xB2\x9D", + "\xCC\x69" => "\xE5\xB2\xA5", + "\xCC\x6A" => "\xE5\xB2\xB6", + "\xCC\x6B" => "\xE5\xB2\xB0", + "\xCC\x6C" => "\xE5\xB2\xA6", + "\xCC\x6D" => "\xE5\xB8\x97", + "\xCC\x6E" => "\xE5\xB8\x94", + "\xCC\x6F" => "\xE5\xB8\x99", + "\xCC\x70" => "\xE5\xBC\xA8", + "\xCC\x71" => "\xE5\xBC\xA2", + "\xCC\x72" => "\xE5\xBC\xA3", + "\xCC\x73" => "\xE5\xBC\xA4", + "\xCC\x74" => "\xE5\xBD\x94", + "\xCC\x75" => "\xE5\xBE\x82", + "\xCC\x76" => "\xE5\xBD\xBE", + "\xCC\x77" => "\xE5\xBD\xBD", + "\xCC\x78" => "\xE5\xBF\x9E", + "\xCC\x79" => "\xE5\xBF\xA5", + "\xCC\x7A" => "\xE6\x80\xAD", + "\xCC\x7B" => "\xE6\x80\xA6", + "\xCC\x7C" => "\xE6\x80\x99", + "\xCC\x7D" => "\xE6\x80\xB2", + "\xCC\x7E" => "\xE6\x80\x8B", + "\xCC\xA1" => "\xE6\x80\xB4", + "\xCC\xA2" => "\xE6\x80\x8A", + "\xCC\xA3" => "\xE6\x80\x97", + "\xCC\xA4" => "\xE6\x80\xB3", + "\xCC\xA5" => "\xE6\x80\x9A", + "\xCC\xA6" => "\xE6\x80\x9E", + "\xCC\xA7" => "\xE6\x80\xAC", + "\xCC\xA8" => "\xE6\x80\xA2", + "\xCC\xA9" => "\xE6\x80\x8D", + "\xCC\xAA" => "\xE6\x80\x90", + "\xCC\xAB" => "\xE6\x80\xAE", + "\xCC\xAC" => "\xE6\x80\x93", + "\xCC\xAD" => "\xE6\x80\x91", + "\xCC\xAE" => "\xE6\x80\x8C", + "\xCC\xAF" => "\xE6\x80\x89", + "\xCC\xB0" => "\xE6\x80\x9C", + "\xCC\xB1" => "\xE6\x88\x94", + "\xCC\xB2" => "\xE6\x88\xBD", + "\xCC\xB3" => "\xE6\x8A\xAD", + "\xCC\xB4" => "\xE6\x8A\xB4", + "\xCC\xB5" => "\xE6\x8B\x91", + "\xCC\xB6" => "\xE6\x8A\xBE", + "\xCC\xB7" => "\xE6\x8A\xAA", + "\xCC\xB8" => "\xE6\x8A\xB6", + "\xCC\xB9" => "\xE6\x8B\x8A", + "\xCC\xBA" => "\xE6\x8A\xAE", + "\xCC\xBB" => "\xE6\x8A\xB3", + "\xCC\xBC" => "\xE6\x8A\xAF", + "\xCC\xBD" => "\xE6\x8A\xBB", + "\xCC\xBE" => "\xE6\x8A\xA9", + "\xCC\xBF" => "\xE6\x8A\xB0", + "\xCC\xC0" => "\xE6\x8A\xB8", + "\xCC\xC1" => "\xE6\x94\xBD", + "\xCC\xC2" => "\xE6\x96\xA8", + "\xCC\xC3" => "\xE6\x96\xBB", + "\xCC\xC4" => "\xE6\x98\x89", + "\xCC\xC5" => "\xE6\x97\xBC", + "\xCC\xC6" => "\xE6\x98\x84", + "\xCC\xC7" => "\xE6\x98\x92", + "\xCC\xC8" => "\xE6\x98\x88", + "\xCC\xC9" => "\xE6\x97\xBB", + "\xCC\xCA" => "\xE6\x98\x83", + "\xCC\xCB" => "\xE6\x98\x8B", + "\xCC\xCC" => "\xE6\x98\x8D", + "\xCC\xCD" => "\xE6\x98\x85", + "\xCC\xCE" => "\xE6\x97\xBD", + "\xCC\xCF" => "\xE6\x98\x91", + "\xCC\xD0" => "\xE6\x98\x90", + "\xCC\xD1" => "\xE6\x9B\xB6", + "\xCC\xD2" => "\xE6\x9C\x8A", + "\xCC\xD3" => "\xE6\x9E\x85", + "\xCC\xD4" => "\xE6\x9D\xAC", + "\xCC\xD5" => "\xE6\x9E\x8E", + "\xCC\xD6" => "\xE6\x9E\x92", + "\xCC\xD7" => "\xE6\x9D\xB6", + "\xCC\xD8" => "\xE6\x9D\xBB", + "\xCC\xD9" => "\xE6\x9E\x98", + "\xCC\xDA" => "\xE6\x9E\x86", + "\xCC\xDB" => "\xE6\x9E\x84", + "\xCC\xDC" => "\xE6\x9D\xB4", + "\xCC\xDD" => "\xE6\x9E\x8D", + "\xCC\xDE" => "\xE6\x9E\x8C", + "\xCC\xDF" => "\xE6\x9D\xBA", + "\xCC\xE0" => "\xE6\x9E\x9F", + "\xCC\xE1" => "\xE6\x9E\x91", + "\xCC\xE2" => "\xE6\x9E\x99", + "\xCC\xE3" => "\xE6\x9E\x83", + "\xCC\xE4" => "\xE6\x9D\xBD", + "\xCC\xE5" => "\xE6\x9E\x81", + "\xCC\xE6" => "\xE6\x9D\xB8", + "\xCC\xE7" => "\xE6\x9D\xB9", + "\xCC\xE8" => "\xE6\x9E\x94", + "\xCC\xE9" => "\xE6\xAC\xA5", + "\xCC\xEA" => "\xE6\xAE\x80", + "\xCC\xEB" => "\xE6\xAD\xBE", + "\xCC\xEC" => "\xE6\xAF\x9E", + "\xCC\xED" => "\xE6\xB0\x9D", + "\xCC\xEE" => "\xE6\xB2\x93", + "\xCC\xEF" => "\xE6\xB3\xAC", + "\xCC\xF0" => "\xE6\xB3\xAB", + "\xCC\xF1" => "\xE6\xB3\xAE", + "\xCC\xF2" => "\xE6\xB3\x99", + "\xCC\xF3" => "\xE6\xB2\xB6", + "\xCC\xF4" => "\xE6\xB3\x94", + "\xCC\xF5" => "\xE6\xB2\xAD", + "\xCC\xF6" => "\xE6\xB3\xA7", + "\xCC\xF7" => "\xE6\xB2\xB7", + "\xCC\xF8" => "\xE6\xB3\x90", + "\xCC\xF9" => "\xE6\xB3\x82", + "\xCC\xFA" => "\xE6\xB2\xBA", + "\xCC\xFB" => "\xE6\xB3\x83", + "\xCC\xFC" => "\xE6\xB3\x86", + "\xCC\xFD" => "\xE6\xB3\xAD", + "\xCC\xFE" => "\xE6\xB3\xB2", + "\xCD\x40" => "\xE6\xB3\x92", + "\xCD\x41" => "\xE6\xB3\x9D", + "\xCD\x42" => "\xE6\xB2\xB4", + "\xCD\x43" => "\xE6\xB2\x8A", + "\xCD\x44" => "\xE6\xB2\x9D", + "\xCD\x45" => "\xE6\xB2\x80", + "\xCD\x46" => "\xE6\xB3\x9E", + "\xCD\x47" => "\xE6\xB3\x80", + "\xCD\x48" => "\xE6\xB4\xB0", + "\xCD\x49" => "\xE6\xB3\x8D", + "\xCD\x4A" => "\xE6\xB3\x87", + "\xCD\x4B" => "\xE6\xB2\xB0", + "\xCD\x4C" => "\xE6\xB3\xB9", + "\xCD\x4D" => "\xE6\xB3\x8F", + "\xCD\x4E" => "\xE6\xB3\xA9", + "\xCD\x4F" => "\xE6\xB3\x91", + "\xCD\x50" => "\xE7\x82\x94", + "\xCD\x51" => "\xE7\x82\x98", + "\xCD\x52" => "\xE7\x82\x85", + "\xCD\x53" => "\xE7\x82\x93", + "\xCD\x54" => "\xE7\x82\x86", + "\xCD\x55" => "\xE7\x82\x84", + "\xCD\x56" => "\xE7\x82\x91", + "\xCD\x57" => "\xE7\x82\x96", + "\xCD\x58" => "\xE7\x82\x82", + "\xCD\x59" => "\xE7\x82\x9A", + "\xCD\x5A" => "\xE7\x82\x83", + "\xCD\x5B" => "\xE7\x89\xAA", + "\xCD\x5C" => "\xE7\x8B\x96", + "\xCD\x5D" => "\xE7\x8B\x8B", + "\xCD\x5E" => "\xE7\x8B\x98", + "\xCD\x5F" => "\xE7\x8B\x89", + "\xCD\x60" => "\xE7\x8B\x9C", + "\xCD\x61" => "\xE7\x8B\x92", + "\xCD\x62" => "\xE7\x8B\x94", + "\xCD\x63" => "\xE7\x8B\x9A", + "\xCD\x64" => "\xE7\x8B\x8C", + "\xCD\x65" => "\xE7\x8B\x91", + "\xCD\x66" => "\xE7\x8E\xA4", + "\xCD\x67" => "\xE7\x8E\xA1", + "\xCD\x68" => "\xE7\x8E\xAD", + "\xCD\x69" => "\xE7\x8E\xA6", + "\xCD\x6A" => "\xE7\x8E\xA2", + "\xCD\x6B" => "\xE7\x8E\xA0", + "\xCD\x6C" => "\xE7\x8E\xAC", + "\xCD\x6D" => "\xE7\x8E\x9D", + "\xCD\x6E" => "\xE7\x93\x9D", + "\xCD\x6F" => "\xE7\x93\xA8", + "\xCD\x70" => "\xE7\x94\xBF", + "\xCD\x71" => "\xE7\x95\x80", + "\xCD\x72" => "\xE7\x94\xBE", + "\xCD\x73" => "\xE7\x96\x8C", + "\xCD\x74" => "\xE7\x96\x98", + "\xCD\x75" => "\xE7\x9A\xAF", + "\xCD\x76" => "\xE7\x9B\xB3", + "\xCD\x77" => "\xE7\x9B\xB1", + "\xCD\x78" => "\xE7\x9B\xB0", + "\xCD\x79" => "\xE7\x9B\xB5", + "\xCD\x7A" => "\xE7\x9F\xB8", + "\xCD\x7B" => "\xE7\x9F\xBC", + "\xCD\x7C" => "\xE7\x9F\xB9", + "\xCD\x7D" => "\xE7\x9F\xBB", + "\xCD\x7E" => "\xE7\x9F\xBA", + "\xCD\xA1" => "\xE7\x9F\xB7", + "\xCD\xA2" => "\xE7\xA5\x82", + "\xCD\xA3" => "\xE7\xA4\xBF", + "\xCD\xA4" => "\xE7\xA7\x85", + "\xCD\xA5" => "\xE7\xA9\xB8", + "\xCD\xA6" => "\xE7\xA9\xBB", + "\xCD\xA7" => "\xE7\xAB\xBB", + "\xCD\xA8" => "\xE7\xB1\xB5", + "\xCD\xA9" => "\xE7\xB3\xBD", + "\xCD\xAA" => "\xE8\x80\xB5", + "\xCD\xAB" => "\xE8\x82\x8F", + "\xCD\xAC" => "\xE8\x82\xAE", + "\xCD\xAD" => "\xE8\x82\xA3", + "\xCD\xAE" => "\xE8\x82\xB8", + "\xCD\xAF" => "\xE8\x82\xB5", + "\xCD\xB0" => "\xE8\x82\xAD", + "\xCD\xB1" => "\xE8\x88\xA0", + "\xCD\xB2" => "\xE8\x8A\xA0", + "\xCD\xB3" => "\xE8\x8B\x80", + "\xCD\xB4" => "\xE8\x8A\xAB", + "\xCD\xB5" => "\xE8\x8A\x9A", + "\xCD\xB6" => "\xE8\x8A\x98", + "\xCD\xB7" => "\xE8\x8A\x9B", + "\xCD\xB8" => "\xE8\x8A\xB5", + "\xCD\xB9" => "\xE8\x8A\xA7", + "\xCD\xBA" => "\xE8\x8A\xAE", + "\xCD\xBB" => "\xE8\x8A\xBC", + "\xCD\xBC" => "\xE8\x8A\x9E", + "\xCD\xBD" => "\xE8\x8A\xBA", + "\xCD\xBE" => "\xE8\x8A\xB4", + "\xCD\xBF" => "\xE8\x8A\xA8", + "\xCD\xC0" => "\xE8\x8A\xA1", + "\xCD\xC1" => "\xE8\x8A\xA9", + "\xCD\xC2" => "\xE8\x8B\x82", + "\xCD\xC3" => "\xE8\x8A\xA4", + "\xCD\xC4" => "\xE8\x8B\x83", + "\xCD\xC5" => "\xE8\x8A\xB6", + "\xCD\xC6" => "\xE8\x8A\xA2", + "\xCD\xC7" => "\xE8\x99\xB0", + "\xCD\xC8" => "\xE8\x99\xAF", + "\xCD\xC9" => "\xE8\x99\xAD", + "\xCD\xCA" => "\xE8\x99\xAE", + "\xCD\xCB" => "\xE8\xB1\x96", + "\xCD\xCC" => "\xE8\xBF\x92", + "\xCD\xCD" => "\xE8\xBF\x8B", + "\xCD\xCE" => "\xE8\xBF\x93", + "\xCD\xCF" => "\xE8\xBF\x8D", + "\xCD\xD0" => "\xE8\xBF\x96", + "\xCD\xD1" => "\xE8\xBF\x95", + "\xCD\xD2" => "\xE8\xBF\x97", + "\xCD\xD3" => "\xE9\x82\xB2", + "\xCD\xD4" => "\xE9\x82\xB4", + "\xCD\xD5" => "\xE9\x82\xAF", + "\xCD\xD6" => "\xE9\x82\xB3", + "\xCD\xD7" => "\xE9\x82\xB0", + "\xCD\xD8" => "\xE9\x98\xB9", + "\xCD\xD9" => "\xE9\x98\xBD", + "\xCD\xDA" => "\xE9\x98\xBC", + "\xCD\xDB" => "\xE9\x98\xBA", + "\xCD\xDC" => "\xE9\x99\x83", + "\xCD\xDD" => "\xE4\xBF\x8D", + "\xCD\xDE" => "\xE4\xBF\x85", + "\xCD\xDF" => "\xE4\xBF\x93", + "\xCD\xE0" => "\xE4\xBE\xB2", + "\xCD\xE1" => "\xE4\xBF\x89", + "\xCD\xE2" => "\xE4\xBF\x8B", + "\xCD\xE3" => "\xE4\xBF\x81", + "\xCD\xE4" => "\xE4\xBF\x94", + "\xCD\xE5" => "\xE4\xBF\x9C", + "\xCD\xE6" => "\xE4\xBF\x99", + "\xCD\xE7" => "\xE4\xBE\xBB", + "\xCD\xE8" => "\xE4\xBE\xB3", + "\xCD\xE9" => "\xE4\xBF\x9B", + "\xCD\xEA" => "\xE4\xBF\x87", + "\xCD\xEB" => "\xE4\xBF\x96", + "\xCD\xEC" => "\xE4\xBE\xBA", + "\xCD\xED" => "\xE4\xBF\x80", + "\xCD\xEE" => "\xE4\xBE\xB9", + "\xCD\xEF" => "\xE4\xBF\xAC", + "\xCD\xF0" => "\xE5\x89\x84", + "\xCD\xF1" => "\xE5\x89\x89", + "\xCD\xF2" => "\xE5\x8B\x80", + "\xCD\xF3" => "\xE5\x8B\x82", + "\xCD\xF4" => "\xE5\x8C\xBD", + "\xCD\xF5" => "\xE5\x8D\xBC", + "\xCD\xF6" => "\xE5\x8E\x97", + "\xCD\xF7" => "\xE5\x8E\x96", + "\xCD\xF8" => "\xE5\x8E\x99", + "\xCD\xF9" => "\xE5\x8E\x98", + "\xCD\xFA" => "\xE5\x92\xBA", + "\xCD\xFB" => "\xE5\x92\xA1", + "\xCD\xFC" => "\xE5\x92\xAD", + "\xCD\xFD" => "\xE5\x92\xA5", + "\xCD\xFE" => "\xE5\x93\x8F", + "\xCE\x40" => "\xE5\x93\x83", + "\xCE\x41" => "\xE8\x8C\x8D", + "\xCE\x42" => "\xE5\x92\xB7", + "\xCE\x43" => "\xE5\x92\xAE", + "\xCE\x44" => "\xE5\x93\x96", + "\xCE\x45" => "\xE5\x92\xB6", + "\xCE\x46" => "\xE5\x93\x85", + "\xCE\x47" => "\xE5\x93\x86", + "\xCE\x48" => "\xE5\x92\xA0", + "\xCE\x49" => "\xE5\x91\xB0", + "\xCE\x4A" => "\xE5\x92\xBC", + "\xCE\x4B" => "\xE5\x92\xA2", + "\xCE\x4C" => "\xE5\x92\xBE", + "\xCE\x4D" => "\xE5\x91\xB2", + "\xCE\x4E" => "\xE5\x93\x9E", + "\xCE\x4F" => "\xE5\x92\xB0", + "\xCE\x50" => "\xE5\x9E\xB5", + "\xCE\x51" => "\xE5\x9E\x9E", + "\xCE\x52" => "\xE5\x9E\x9F", + "\xCE\x53" => "\xE5\x9E\xA4", + "\xCE\x54" => "\xE5\x9E\x8C", + "\xCE\x55" => "\xE5\x9E\x97", + "\xCE\x56" => "\xE5\x9E\x9D", + "\xCE\x57" => "\xE5\x9E\x9B", + "\xCE\x58" => "\xE5\x9E\x94", + "\xCE\x59" => "\xE5\x9E\x98", + "\xCE\x5A" => "\xE5\x9E\x8F", + "\xCE\x5B" => "\xE5\x9E\x99", + "\xCE\x5C" => "\xE5\x9E\xA5", + "\xCE\x5D" => "\xE5\x9E\x9A", + "\xCE\x5E" => "\xE5\x9E\x95", + "\xCE\x5F" => "\xE5\xA3\xB4", + "\xCE\x60" => "\xE5\xA4\x8D", + "\xCE\x61" => "\xE5\xA5\x93", + "\xCE\x62" => "\xE5\xA7\xA1", + "\xCE\x63" => "\xE5\xA7\x9E", + "\xCE\x64" => "\xE5\xA7\xAE", + "\xCE\x65" => "\xE5\xA8\x80", + "\xCE\x66" => "\xE5\xA7\xB1", + "\xCE\x67" => "\xE5\xA7\x9D", + "\xCE\x68" => "\xE5\xA7\xBA", + "\xCE\x69" => "\xE5\xA7\xBD", + "\xCE\x6A" => "\xE5\xA7\xBC", + "\xCE\x6B" => "\xE5\xA7\xB6", + "\xCE\x6C" => "\xE5\xA7\xA4", + "\xCE\x6D" => "\xE5\xA7\xB2", + "\xCE\x6E" => "\xE5\xA7\xB7", + "\xCE\x6F" => "\xE5\xA7\x9B", + "\xCE\x70" => "\xE5\xA7\xA9", + "\xCE\x71" => "\xE5\xA7\xB3", + "\xCE\x72" => "\xE5\xA7\xB5", + "\xCE\x73" => "\xE5\xA7\xA0", + "\xCE\x74" => "\xE5\xA7\xBE", + "\xCE\x75" => "\xE5\xA7\xB4", + "\xCE\x76" => "\xE5\xA7\xAD", + "\xCE\x77" => "\xE5\xAE\xA8", + "\xCE\x78" => "\xE5\xB1\x8C", + "\xCE\x79" => "\xE5\xB3\x90", + "\xCE\x7A" => "\xE5\xB3\x98", + "\xCE\x7B" => "\xE5\xB3\x8C", + "\xCE\x7C" => "\xE5\xB3\x97", + "\xCE\x7D" => "\xE5\xB3\x8B", + "\xCE\x7E" => "\xE5\xB3\x9B", + "\xCE\xA1" => "\xE5\xB3\x9E", + "\xCE\xA2" => "\xE5\xB3\x9A", + "\xCE\xA3" => "\xE5\xB3\x89", + "\xCE\xA4" => "\xE5\xB3\x87", + "\xCE\xA5" => "\xE5\xB3\x8A", + "\xCE\xA6" => "\xE5\xB3\x96", + "\xCE\xA7" => "\xE5\xB3\x93", + "\xCE\xA8" => "\xE5\xB3\x94", + "\xCE\xA9" => "\xE5\xB3\x8F", + "\xCE\xAA" => "\xE5\xB3\x88", + "\xCE\xAB" => "\xE5\xB3\x86", + "\xCE\xAC" => "\xE5\xB3\x8E", + "\xCE\xAD" => "\xE5\xB3\x9F", + "\xCE\xAE" => "\xE5\xB3\xB8", + "\xCE\xAF" => "\xE5\xB7\xB9", + "\xCE\xB0" => "\xE5\xB8\xA1", + "\xCE\xB1" => "\xE5\xB8\xA2", + "\xCE\xB2" => "\xE5\xB8\xA3", + "\xCE\xB3" => "\xE5\xB8\xA0", + "\xCE\xB4" => "\xE5\xB8\xA4", + "\xCE\xB5" => "\xE5\xBA\xB0", + "\xCE\xB6" => "\xE5\xBA\xA4", + "\xCE\xB7" => "\xE5\xBA\xA2", + "\xCE\xB8" => "\xE5\xBA\x9B", + "\xCE\xB9" => "\xE5\xBA\xA3", + "\xCE\xBA" => "\xE5\xBA\xA5", + "\xCE\xBB" => "\xE5\xBC\x87", + "\xCE\xBC" => "\xE5\xBC\xAE", + "\xCE\xBD" => "\xE5\xBD\x96", + "\xCE\xBE" => "\xE5\xBE\x86", + "\xCE\xBF" => "\xE6\x80\xB7", + "\xCE\xC0" => "\xE6\x80\xB9", + "\xCE\xC1" => "\xE6\x81\x94", + "\xCE\xC2" => "\xE6\x81\xB2", + "\xCE\xC3" => "\xE6\x81\x9E", + "\xCE\xC4" => "\xE6\x81\x85", + "\xCE\xC5" => "\xE6\x81\x93", + "\xCE\xC6" => "\xE6\x81\x87", + "\xCE\xC7" => "\xE6\x81\x89", + "\xCE\xC8" => "\xE6\x81\x9B", + "\xCE\xC9" => "\xE6\x81\x8C", + "\xCE\xCA" => "\xE6\x81\x80", + "\xCE\xCB" => "\xE6\x81\x82", + "\xCE\xCC" => "\xE6\x81\x9F", + "\xCE\xCD" => "\xE6\x80\xA4", + "\xCE\xCE" => "\xE6\x81\x84", + "\xCE\xCF" => "\xE6\x81\x98", + "\xCE\xD0" => "\xE6\x81\xA6", + "\xCE\xD1" => "\xE6\x81\xAE", + "\xCE\xD2" => "\xE6\x89\x82", + "\xCE\xD3" => "\xE6\x89\x83", + "\xCE\xD4" => "\xE6\x8B\x8F", + "\xCE\xD5" => "\xE6\x8C\x8D", + "\xCE\xD6" => "\xE6\x8C\x8B", + "\xCE\xD7" => "\xE6\x8B\xB5", + "\xCE\xD8" => "\xE6\x8C\x8E", + "\xCE\xD9" => "\xE6\x8C\x83", + "\xCE\xDA" => "\xE6\x8B\xAB", + "\xCE\xDB" => "\xE6\x8B\xB9", + "\xCE\xDC" => "\xE6\x8C\x8F", + "\xCE\xDD" => "\xE6\x8C\x8C", + "\xCE\xDE" => "\xE6\x8B\xB8", + "\xCE\xDF" => "\xE6\x8B\xB6", + "\xCE\xE0" => "\xE6\x8C\x80", + "\xCE\xE1" => "\xE6\x8C\x93", + "\xCE\xE2" => "\xE6\x8C\x94", + "\xCE\xE3" => "\xE6\x8B\xBA", + "\xCE\xE4" => "\xE6\x8C\x95", + "\xCE\xE5" => "\xE6\x8B\xBB", + "\xCE\xE6" => "\xE6\x8B\xB0", + "\xCE\xE7" => "\xE6\x95\x81", + "\xCE\xE8" => "\xE6\x95\x83", + "\xCE\xE9" => "\xE6\x96\xAA", + "\xCE\xEA" => "\xE6\x96\xBF", + "\xCE\xEB" => "\xE6\x98\xB6", + "\xCE\xEC" => "\xE6\x98\xA1", + "\xCE\xED" => "\xE6\x98\xB2", + "\xCE\xEE" => "\xE6\x98\xB5", + "\xCE\xEF" => "\xE6\x98\x9C", + "\xCE\xF0" => "\xE6\x98\xA6", + "\xCE\xF1" => "\xE6\x98\xA2", + "\xCE\xF2" => "\xE6\x98\xB3", + "\xCE\xF3" => "\xE6\x98\xAB", + "\xCE\xF4" => "\xE6\x98\xBA", + "\xCE\xF5" => "\xE6\x98\x9D", + "\xCE\xF6" => "\xE6\x98\xB4", + "\xCE\xF7" => "\xE6\x98\xB9", + "\xCE\xF8" => "\xE6\x98\xAE", + "\xCE\xF9" => "\xE6\x9C\x8F", + "\xCE\xFA" => "\xE6\x9C\x90", + "\xCE\xFB" => "\xE6\x9F\x81", + "\xCE\xFC" => "\xE6\x9F\xB2", + "\xCE\xFD" => "\xE6\x9F\x88", + "\xCE\xFE" => "\xE6\x9E\xBA", + "\xCF\x40" => "\xE6\x9F\x9C", + "\xCF\x41" => "\xE6\x9E\xBB", + "\xCF\x42" => "\xE6\x9F\xB8", + "\xCF\x43" => "\xE6\x9F\x98", + "\xCF\x44" => "\xE6\x9F\x80", + "\xCF\x45" => "\xE6\x9E\xB7", + "\xCF\x46" => "\xE6\x9F\x85", + "\xCF\x47" => "\xE6\x9F\xAB", + "\xCF\x48" => "\xE6\x9F\xA4", + "\xCF\x49" => "\xE6\x9F\x9F", + "\xCF\x4A" => "\xE6\x9E\xB5", + "\xCF\x4B" => "\xE6\x9F\x8D", + "\xCF\x4C" => "\xE6\x9E\xB3", + "\xCF\x4D" => "\xE6\x9F\xB7", + "\xCF\x4E" => "\xE6\x9F\xB6", + "\xCF\x4F" => "\xE6\x9F\xAE", + "\xCF\x50" => "\xE6\x9F\xA3", + "\xCF\x51" => "\xE6\x9F\x82", + "\xCF\x52" => "\xE6\x9E\xB9", + "\xCF\x53" => "\xE6\x9F\x8E", + "\xCF\x54" => "\xE6\x9F\xA7", + "\xCF\x55" => "\xE6\x9F\xB0", + "\xCF\x56" => "\xE6\x9E\xB2", + "\xCF\x57" => "\xE6\x9F\xBC", + "\xCF\x58" => "\xE6\x9F\x86", + "\xCF\x59" => "\xE6\x9F\xAD", + "\xCF\x5A" => "\xE6\x9F\x8C", + "\xCF\x5B" => "\xE6\x9E\xAE", + "\xCF\x5C" => "\xE6\x9F\xA6", + "\xCF\x5D" => "\xE6\x9F\x9B", + "\xCF\x5E" => "\xE6\x9F\xBA", + "\xCF\x5F" => "\xE6\x9F\x89", + "\xCF\x60" => "\xE6\x9F\x8A", + "\xCF\x61" => "\xE6\x9F\x83", + "\xCF\x62" => "\xE6\x9F\xAA", + "\xCF\x63" => "\xE6\x9F\x8B", + "\xCF\x64" => "\xE6\xAC\xA8", + "\xCF\x65" => "\xE6\xAE\x82", + "\xCF\x66" => "\xE6\xAE\x84", + "\xCF\x67" => "\xE6\xAE\xB6", + "\xCF\x68" => "\xE6\xAF\x96", + "\xCF\x69" => "\xE6\xAF\x98", + "\xCF\x6A" => "\xE6\xAF\xA0", + "\xCF\x6B" => "\xE6\xB0\xA0", + "\xCF\x6C" => "\xE6\xB0\xA1", + "\xCF\x6D" => "\xE6\xB4\xA8", + "\xCF\x6E" => "\xE6\xB4\xB4", + "\xCF\x6F" => "\xE6\xB4\xAD", + "\xCF\x70" => "\xE6\xB4\x9F", + "\xCF\x71" => "\xE6\xB4\xBC", + "\xCF\x72" => "\xE6\xB4\xBF", + "\xCF\x73" => "\xE6\xB4\x92", + "\xCF\x74" => "\xE6\xB4\x8A", + "\xCF\x75" => "\xE6\xB3\x9A", + "\xCF\x76" => "\xE6\xB4\xB3", + "\xCF\x77" => "\xE6\xB4\x84", + "\xCF\x78" => "\xE6\xB4\x99", + "\xCF\x79" => "\xE6\xB4\xBA", + "\xCF\x7A" => "\xE6\xB4\x9A", + "\xCF\x7B" => "\xE6\xB4\x91", + "\xCF\x7C" => "\xE6\xB4\x80", + "\xCF\x7D" => "\xE6\xB4\x9D", + "\xCF\x7E" => "\xE6\xB5\x82", + "\xCF\xA1" => "\xE6\xB4\x81", + "\xCF\xA2" => "\xE6\xB4\x98", + "\xCF\xA3" => "\xE6\xB4\xB7", + "\xCF\xA4" => "\xE6\xB4\x83", + "\xCF\xA5" => "\xE6\xB4\x8F", + "\xCF\xA6" => "\xE6\xB5\x80", + "\xCF\xA7" => "\xE6\xB4\x87", + "\xCF\xA8" => "\xE6\xB4\xA0", + "\xCF\xA9" => "\xE6\xB4\xAC", + "\xCF\xAA" => "\xE6\xB4\x88", + "\xCF\xAB" => "\xE6\xB4\xA2", + "\xCF\xAC" => "\xE6\xB4\x89", + "\xCF\xAD" => "\xE6\xB4\x90", + "\xCF\xAE" => "\xE7\x82\xB7", + "\xCF\xAF" => "\xE7\x82\x9F", + "\xCF\xB0" => "\xE7\x82\xBE", + "\xCF\xB1" => "\xE7\x82\xB1", + "\xCF\xB2" => "\xE7\x82\xB0", + "\xCF\xB3" => "\xE7\x82\xA1", + "\xCF\xB4" => "\xE7\x82\xB4", + "\xCF\xB5" => "\xE7\x82\xB5", + "\xCF\xB6" => "\xE7\x82\xA9", + "\xCF\xB7" => "\xE7\x89\x81", + "\xCF\xB8" => "\xE7\x89\x89", + "\xCF\xB9" => "\xE7\x89\x8A", + "\xCF\xBA" => "\xE7\x89\xAC", + "\xCF\xBB" => "\xE7\x89\xB0", + "\xCF\xBC" => "\xE7\x89\xB3", + "\xCF\xBD" => "\xE7\x89\xAE", + "\xCF\xBE" => "\xE7\x8B\x8A", + "\xCF\xBF" => "\xE7\x8B\xA4", + "\xCF\xC0" => "\xE7\x8B\xA8", + "\xCF\xC1" => "\xE7\x8B\xAB", + "\xCF\xC2" => "\xE7\x8B\x9F", + "\xCF\xC3" => "\xE7\x8B\xAA", + "\xCF\xC4" => "\xE7\x8B\xA6", + "\xCF\xC5" => "\xE7\x8B\xA3", + "\xCF\xC6" => "\xE7\x8E\x85", + "\xCF\xC7" => "\xE7\x8F\x8C", + "\xCF\xC8" => "\xE7\x8F\x82", + "\xCF\xC9" => "\xE7\x8F\x88", + "\xCF\xCA" => "\xE7\x8F\x85", + "\xCF\xCB" => "\xE7\x8E\xB9", + "\xCF\xCC" => "\xE7\x8E\xB6", + "\xCF\xCD" => "\xE7\x8E\xB5", + "\xCF\xCE" => "\xE7\x8E\xB4", + "\xCF\xCF" => "\xE7\x8F\xAB", + "\xCF\xD0" => "\xE7\x8E\xBF", + "\xCF\xD1" => "\xE7\x8F\x87", + "\xCF\xD2" => "\xE7\x8E\xBE", + "\xCF\xD3" => "\xE7\x8F\x83", + "\xCF\xD4" => "\xE7\x8F\x86", + "\xCF\xD5" => "\xE7\x8E\xB8", + "\xCF\xD6" => "\xE7\x8F\x8B", + "\xCF\xD7" => "\xE7\x93\xAC", + "\xCF\xD8" => "\xE7\x93\xAE", + "\xCF\xD9" => "\xE7\x94\xAE", + "\xCF\xDA" => "\xE7\x95\x87", + "\xCF\xDB" => "\xE7\x95\x88", + "\xCF\xDC" => "\xE7\x96\xA7", + "\xCF\xDD" => "\xE7\x96\xAA", + "\xCF\xDE" => "\xE7\x99\xB9", + "\xCF\xDF" => "\xE7\x9B\x84", + "\xCF\xE0" => "\xE7\x9C\x88", + "\xCF\xE1" => "\xE7\x9C\x83", + "\xCF\xE2" => "\xE7\x9C\x84", + "\xCF\xE3" => "\xE7\x9C\x85", + "\xCF\xE4" => "\xE7\x9C\x8A", + "\xCF\xE5" => "\xE7\x9B\xB7", + "\xCF\xE6" => "\xE7\x9B\xBB", + "\xCF\xE7" => "\xE7\x9B\xBA", + "\xCF\xE8" => "\xE7\x9F\xA7", + "\xCF\xE9" => "\xE7\x9F\xA8", + "\xCF\xEA" => "\xE7\xA0\x86", + "\xCF\xEB" => "\xE7\xA0\x91", + "\xCF\xEC" => "\xE7\xA0\x92", + "\xCF\xED" => "\xE7\xA0\x85", + "\xCF\xEE" => "\xE7\xA0\x90", + "\xCF\xEF" => "\xE7\xA0\x8F", + "\xCF\xF0" => "\xE7\xA0\x8E", + "\xCF\xF1" => "\xE7\xA0\x89", + "\xCF\xF2" => "\xE7\xA0\x83", + "\xCF\xF3" => "\xE7\xA0\x93", + "\xCF\xF4" => "\xE7\xA5\x8A", + "\xCF\xF5" => "\xE7\xA5\x8C", + "\xCF\xF6" => "\xE7\xA5\x8B", + "\xCF\xF7" => "\xE7\xA5\x85", + "\xCF\xF8" => "\xE7\xA5\x84", + "\xCF\xF9" => "\xE7\xA7\x95", + "\xCF\xFA" => "\xE7\xA7\x8D", + "\xCF\xFB" => "\xE7\xA7\x8F", + "\xCF\xFC" => "\xE7\xA7\x96", + "\xCF\xFD" => "\xE7\xA7\x8E", + "\xCF\xFE" => "\xE7\xAA\x80", + "\xD0\x40" => "\xE7\xA9\xBE", + "\xD0\x41" => "\xE7\xAB\x91", + "\xD0\x42" => "\xE7\xAC\x80", + "\xD0\x43" => "\xE7\xAC\x81", + "\xD0\x44" => "\xE7\xB1\xBA", + "\xD0\x45" => "\xE7\xB1\xB8", + "\xD0\x46" => "\xE7\xB1\xB9", + "\xD0\x47" => "\xE7\xB1\xBF", + "\xD0\x48" => "\xE7\xB2\x80", + "\xD0\x49" => "\xE7\xB2\x81", + "\xD0\x4A" => "\xE7\xB4\x83", + "\xD0\x4B" => "\xE7\xB4\x88", + "\xD0\x4C" => "\xE7\xB4\x81", + "\xD0\x4D" => "\xE7\xBD\x98", + "\xD0\x4E" => "\xE7\xBE\x91", + "\xD0\x4F" => "\xE7\xBE\x8D", + "\xD0\x50" => "\xE7\xBE\xBE", + "\xD0\x51" => "\xE8\x80\x87", + "\xD0\x52" => "\xE8\x80\x8E", + "\xD0\x53" => "\xE8\x80\x8F", + "\xD0\x54" => "\xE8\x80\x94", + "\xD0\x55" => "\xE8\x80\xB7", + "\xD0\x56" => "\xE8\x83\x98", + "\xD0\x57" => "\xE8\x83\x87", + "\xD0\x58" => "\xE8\x83\xA0", + "\xD0\x59" => "\xE8\x83\x91", + "\xD0\x5A" => "\xE8\x83\x88", + "\xD0\x5B" => "\xE8\x83\x82", + "\xD0\x5C" => "\xE8\x83\x90", + "\xD0\x5D" => "\xE8\x83\x85", + "\xD0\x5E" => "\xE8\x83\xA3", + "\xD0\x5F" => "\xE8\x83\x99", + "\xD0\x60" => "\xE8\x83\x9C", + "\xD0\x61" => "\xE8\x83\x8A", + "\xD0\x62" => "\xE8\x83\x95", + "\xD0\x63" => "\xE8\x83\x89", + "\xD0\x64" => "\xE8\x83\x8F", + "\xD0\x65" => "\xE8\x83\x97", + "\xD0\x66" => "\xE8\x83\xA6", + "\xD0\x67" => "\xE8\x83\x8D", + "\xD0\x68" => "\xE8\x87\xBF", + "\xD0\x69" => "\xE8\x88\xA1", + "\xD0\x6A" => "\xE8\x8A\x94", + "\xD0\x6B" => "\xE8\x8B\x99", + "\xD0\x6C" => "\xE8\x8B\xBE", + "\xD0\x6D" => "\xE8\x8B\xB9", + "\xD0\x6E" => "\xE8\x8C\x87", + "\xD0\x6F" => "\xE8\x8B\xA8", + "\xD0\x70" => "\xE8\x8C\x80", + "\xD0\x71" => "\xE8\x8B\x95", + "\xD0\x72" => "\xE8\x8C\xBA", + "\xD0\x73" => "\xE8\x8B\xAB", + "\xD0\x74" => "\xE8\x8B\x96", + "\xD0\x75" => "\xE8\x8B\xB4", + "\xD0\x76" => "\xE8\x8B\xAC", + "\xD0\x77" => "\xE8\x8B\xA1", + "\xD0\x78" => "\xE8\x8B\xB2", + "\xD0\x79" => "\xE8\x8B\xB5", + "\xD0\x7A" => "\xE8\x8C\x8C", + "\xD0\x7B" => "\xE8\x8B\xBB", + "\xD0\x7C" => "\xE8\x8B\xB6", + "\xD0\x7D" => "\xE8\x8B\xB0", + "\xD0\x7E" => "\xE8\x8B\xAA", + "\xD0\xA1" => "\xE8\x8B\xA4", + "\xD0\xA2" => "\xE8\x8B\xA0", + "\xD0\xA3" => "\xE8\x8B\xBA", + "\xD0\xA4" => "\xE8\x8B\xB3", + "\xD0\xA5" => "\xE8\x8B\xAD", + "\xD0\xA6" => "\xE8\x99\xB7", + "\xD0\xA7" => "\xE8\x99\xB4", + "\xD0\xA8" => "\xE8\x99\xBC", + "\xD0\xA9" => "\xE8\x99\xB3", + "\xD0\xAA" => "\xE8\xA1\x81", + "\xD0\xAB" => "\xE8\xA1\x8E", + "\xD0\xAC" => "\xE8\xA1\xA7", + "\xD0\xAD" => "\xE8\xA1\xAA", + "\xD0\xAE" => "\xE8\xA1\xA9", + "\xD0\xAF" => "\xE8\xA7\x93", + "\xD0\xB0" => "\xE8\xA8\x84", + "\xD0\xB1" => "\xE8\xA8\x87", + "\xD0\xB2" => "\xE8\xB5\xB2", + "\xD0\xB3" => "\xE8\xBF\xA3", + "\xD0\xB4" => "\xE8\xBF\xA1", + "\xD0\xB5" => "\xE8\xBF\xAE", + "\xD0\xB6" => "\xE8\xBF\xA0", + "\xD0\xB7" => "\xE9\x83\xB1", + "\xD0\xB8" => "\xE9\x82\xBD", + "\xD0\xB9" => "\xE9\x82\xBF", + "\xD0\xBA" => "\xE9\x83\x95", + "\xD0\xBB" => "\xE9\x83\x85", + "\xD0\xBC" => "\xE9\x82\xBE", + "\xD0\xBD" => "\xE9\x83\x87", + "\xD0\xBE" => "\xE9\x83\x8B", + "\xD0\xBF" => "\xE9\x83\x88", + "\xD0\xC0" => "\xE9\x87\x94", + "\xD0\xC1" => "\xE9\x87\x93", + "\xD0\xC2" => "\xE9\x99\x94", + "\xD0\xC3" => "\xE9\x99\x8F", + "\xD0\xC4" => "\xE9\x99\x91", + "\xD0\xC5" => "\xE9\x99\x93", + "\xD0\xC6" => "\xE9\x99\x8A", + "\xD0\xC7" => "\xE9\x99\x8E", + "\xD0\xC8" => "\xE5\x80\x9E", + "\xD0\xC9" => "\xE5\x80\x85", + "\xD0\xCA" => "\xE5\x80\x87", + "\xD0\xCB" => "\xE5\x80\x93", + "\xD0\xCC" => "\xE5\x80\xA2", + "\xD0\xCD" => "\xE5\x80\xB0", + "\xD0\xCE" => "\xE5\x80\x9B", + "\xD0\xCF" => "\xE4\xBF\xB5", + "\xD0\xD0" => "\xE4\xBF\xB4", + "\xD0\xD1" => "\xE5\x80\xB3", + "\xD0\xD2" => "\xE5\x80\xB7", + "\xD0\xD3" => "\xE5\x80\xAC", + "\xD0\xD4" => "\xE4\xBF\xB6", + "\xD0\xD5" => "\xE4\xBF\xB7", + "\xD0\xD6" => "\xE5\x80\x97", + "\xD0\xD7" => "\xE5\x80\x9C", + "\xD0\xD8" => "\xE5\x80\xA0", + "\xD0\xD9" => "\xE5\x80\xA7", + "\xD0\xDA" => "\xE5\x80\xB5", + "\xD0\xDB" => "\xE5\x80\xAF", + "\xD0\xDC" => "\xE5\x80\xB1", + "\xD0\xDD" => "\xE5\x80\x8E", + "\xD0\xDE" => "\xE5\x85\x9A", + "\xD0\xDF" => "\xE5\x86\x94", + "\xD0\xE0" => "\xE5\x86\x93", + "\xD0\xE1" => "\xE5\x87\x8A", + "\xD0\xE2" => "\xE5\x87\x84", + "\xD0\xE3" => "\xE5\x87\x85", + "\xD0\xE4" => "\xE5\x87\x88", + "\xD0\xE5" => "\xE5\x87\x8E", + "\xD0\xE6" => "\xE5\x89\xA1", + "\xD0\xE7" => "\xE5\x89\x9A", + "\xD0\xE8" => "\xE5\x89\x92", + "\xD0\xE9" => "\xE5\x89\x9E", + "\xD0\xEA" => "\xE5\x89\x9F", + "\xD0\xEB" => "\xE5\x89\x95", + "\xD0\xEC" => "\xE5\x89\xA2", + "\xD0\xED" => "\xE5\x8B\x8D", + "\xD0\xEE" => "\xE5\x8C\x8E", + "\xD0\xEF" => "\xE5\x8E\x9E", + "\xD0\xF0" => "\xE5\x94\xA6", + "\xD0\xF1" => "\xE5\x93\xA2", + "\xD0\xF2" => "\xE5\x94\x97", + "\xD0\xF3" => "\xE5\x94\x92", + "\xD0\xF4" => "\xE5\x93\xA7", + "\xD0\xF5" => "\xE5\x93\xB3", + "\xD0\xF6" => "\xE5\x93\xA4", + "\xD0\xF7" => "\xE5\x94\x9A", + "\xD0\xF8" => "\xE5\x93\xBF", + "\xD0\xF9" => "\xE5\x94\x84", + "\xD0\xFA" => "\xE5\x94\x88", + "\xD0\xFB" => "\xE5\x93\xAB", + "\xD0\xFC" => "\xE5\x94\x91", + "\xD0\xFD" => "\xE5\x94\x85", + "\xD0\xFE" => "\xE5\x93\xB1", + "\xD1\x40" => "\xE5\x94\x8A", + "\xD1\x41" => "\xE5\x93\xBB", + "\xD1\x42" => "\xE5\x93\xB7", + "\xD1\x43" => "\xE5\x93\xB8", + "\xD1\x44" => "\xE5\x93\xA0", + "\xD1\x45" => "\xE5\x94\x8E", + "\xD1\x46" => "\xE5\x94\x83", + "\xD1\x47" => "\xE5\x94\x8B", + "\xD1\x48" => "\xE5\x9C\x81", + "\xD1\x49" => "\xE5\x9C\x82", + "\xD1\x4A" => "\xE5\x9F\x8C", + "\xD1\x4B" => "\xE5\xA0\xB2", + "\xD1\x4C" => "\xE5\x9F\x95", + "\xD1\x4D" => "\xE5\x9F\x92", + "\xD1\x4E" => "\xE5\x9E\xBA", + "\xD1\x4F" => "\xE5\x9F\x86", + "\xD1\x50" => "\xE5\x9E\xBD", + "\xD1\x51" => "\xE5\x9E\xBC", + "\xD1\x52" => "\xE5\x9E\xB8", + "\xD1\x53" => "\xE5\x9E\xB6", + "\xD1\x54" => "\xE5\x9E\xBF", + "\xD1\x55" => "\xE5\x9F\x87", + "\xD1\x56" => "\xE5\x9F\x90", + "\xD1\x57" => "\xE5\x9E\xB9", + "\xD1\x58" => "\xE5\x9F\x81", + "\xD1\x59" => "\xE5\xA4\x8E", + "\xD1\x5A" => "\xE5\xA5\x8A", + "\xD1\x5B" => "\xE5\xA8\x99", + "\xD1\x5C" => "\xE5\xA8\x96", + "\xD1\x5D" => "\xE5\xA8\xAD", + "\xD1\x5E" => "\xE5\xA8\xAE", + "\xD1\x5F" => "\xE5\xA8\x95", + "\xD1\x60" => "\xE5\xA8\x8F", + "\xD1\x61" => "\xE5\xA8\x97", + "\xD1\x62" => "\xE5\xA8\x8A", + "\xD1\x63" => "\xE5\xA8\x9E", + "\xD1\x64" => "\xE5\xA8\xB3", + "\xD1\x65" => "\xE5\xAD\xAC", + "\xD1\x66" => "\xE5\xAE\xA7", + "\xD1\x67" => "\xE5\xAE\xAD", + "\xD1\x68" => "\xE5\xAE\xAC", + "\xD1\x69" => "\xE5\xB0\x83", + "\xD1\x6A" => "\xE5\xB1\x96", + "\xD1\x6B" => "\xE5\xB1\x94", + "\xD1\x6C" => "\xE5\xB3\xAC", + "\xD1\x6D" => "\xE5\xB3\xBF", + "\xD1\x6E" => "\xE5\xB3\xAE", + "\xD1\x6F" => "\xE5\xB3\xB1", + "\xD1\x70" => "\xE5\xB3\xB7", + "\xD1\x71" => "\xE5\xB4\x80", + "\xD1\x72" => "\xE5\xB3\xB9", + "\xD1\x73" => "\xE5\xB8\xA9", + "\xD1\x74" => "\xE5\xB8\xA8", + "\xD1\x75" => "\xE5\xBA\xA8", + "\xD1\x76" => "\xE5\xBA\xAE", + "\xD1\x77" => "\xE5\xBA\xAA", + "\xD1\x78" => "\xE5\xBA\xAC", + "\xD1\x79" => "\xE5\xBC\xB3", + "\xD1\x7A" => "\xE5\xBC\xB0", + "\xD1\x7B" => "\xE5\xBD\xA7", + "\xD1\x7C" => "\xE6\x81\x9D", + "\xD1\x7D" => "\xE6\x81\x9A", + "\xD1\x7E" => "\xE6\x81\xA7", + "\xD1\xA1" => "\xE6\x81\x81", + "\xD1\xA2" => "\xE6\x82\xA2", + "\xD1\xA3" => "\xE6\x82\x88", + "\xD1\xA4" => "\xE6\x82\x80", + "\xD1\xA5" => "\xE6\x82\x92", + "\xD1\xA6" => "\xE6\x82\x81", + "\xD1\xA7" => "\xE6\x82\x9D", + "\xD1\xA8" => "\xE6\x82\x83", + "\xD1\xA9" => "\xE6\x82\x95", + "\xD1\xAA" => "\xE6\x82\x9B", + "\xD1\xAB" => "\xE6\x82\x97", + "\xD1\xAC" => "\xE6\x82\x87", + "\xD1\xAD" => "\xE6\x82\x9C", + "\xD1\xAE" => "\xE6\x82\x8E", + "\xD1\xAF" => "\xE6\x88\x99", + "\xD1\xB0" => "\xE6\x89\x86", + "\xD1\xB1" => "\xE6\x8B\xB2", + "\xD1\xB2" => "\xE6\x8C\x90", + "\xD1\xB3" => "\xE6\x8D\x96", + "\xD1\xB4" => "\xE6\x8C\xAC", + "\xD1\xB5" => "\xE6\x8D\x84", + "\xD1\xB6" => "\xE6\x8D\x85", + "\xD1\xB7" => "\xE6\x8C\xB6", + "\xD1\xB8" => "\xE6\x8D\x83", + "\xD1\xB9" => "\xE6\x8F\xA4", + "\xD1\xBA" => "\xE6\x8C\xB9", + "\xD1\xBB" => "\xE6\x8D\x8B", + "\xD1\xBC" => "\xE6\x8D\x8A", + "\xD1\xBD" => "\xE6\x8C\xBC", + "\xD1\xBE" => "\xE6\x8C\xA9", + "\xD1\xBF" => "\xE6\x8D\x81", + "\xD1\xC0" => "\xE6\x8C\xB4", + "\xD1\xC1" => "\xE6\x8D\x98", + "\xD1\xC2" => "\xE6\x8D\x94", + "\xD1\xC3" => "\xE6\x8D\x99", + "\xD1\xC4" => "\xE6\x8C\xAD", + "\xD1\xC5" => "\xE6\x8D\x87", + "\xD1\xC6" => "\xE6\x8C\xB3", + "\xD1\xC7" => "\xE6\x8D\x9A", + "\xD1\xC8" => "\xE6\x8D\x91", + "\xD1\xC9" => "\xE6\x8C\xB8", + "\xD1\xCA" => "\xE6\x8D\x97", + "\xD1\xCB" => "\xE6\x8D\x80", + "\xD1\xCC" => "\xE6\x8D\x88", + "\xD1\xCD" => "\xE6\x95\x8A", + "\xD1\xCE" => "\xE6\x95\x86", + "\xD1\xCF" => "\xE6\x97\x86", + "\xD1\xD0" => "\xE6\x97\x83", + "\xD1\xD1" => "\xE6\x97\x84", + "\xD1\xD2" => "\xE6\x97\x82", + "\xD1\xD3" => "\xE6\x99\x8A", + "\xD1\xD4" => "\xE6\x99\x9F", + "\xD1\xD5" => "\xE6\x99\x87", + "\xD1\xD6" => "\xE6\x99\x91", + "\xD1\xD7" => "\xE6\x9C\x92", + "\xD1\xD8" => "\xE6\x9C\x93", + "\xD1\xD9" => "\xE6\xA0\x9F", + "\xD1\xDA" => "\xE6\xA0\x9A", + "\xD1\xDB" => "\xE6\xA1\x89", + "\xD1\xDC" => "\xE6\xA0\xB2", + "\xD1\xDD" => "\xE6\xA0\xB3", + "\xD1\xDE" => "\xE6\xA0\xBB", + "\xD1\xDF" => "\xE6\xA1\x8B", + "\xD1\xE0" => "\xE6\xA1\x8F", + "\xD1\xE1" => "\xE6\xA0\x96", + "\xD1\xE2" => "\xE6\xA0\xB1", + "\xD1\xE3" => "\xE6\xA0\x9C", + "\xD1\xE4" => "\xE6\xA0\xB5", + "\xD1\xE5" => "\xE6\xA0\xAB", + "\xD1\xE6" => "\xE6\xA0\xAD", + "\xD1\xE7" => "\xE6\xA0\xAF", + "\xD1\xE8" => "\xE6\xA1\x8E", + "\xD1\xE9" => "\xE6\xA1\x84", + "\xD1\xEA" => "\xE6\xA0\xB4", + "\xD1\xEB" => "\xE6\xA0\x9D", + "\xD1\xEC" => "\xE6\xA0\x92", + "\xD1\xED" => "\xE6\xA0\x94", + "\xD1\xEE" => "\xE6\xA0\xA6", + "\xD1\xEF" => "\xE6\xA0\xA8", + "\xD1\xF0" => "\xE6\xA0\xAE", + "\xD1\xF1" => "\xE6\xA1\x8D", + "\xD1\xF2" => "\xE6\xA0\xBA", + "\xD1\xF3" => "\xE6\xA0\xA5", + "\xD1\xF4" => "\xE6\xA0\xA0", + "\xD1\xF5" => "\xE6\xAC\xAC", + "\xD1\xF6" => "\xE6\xAC\xAF", + "\xD1\xF7" => "\xE6\xAC\xAD", + "\xD1\xF8" => "\xE6\xAC\xB1", + "\xD1\xF9" => "\xE6\xAC\xB4", + "\xD1\xFA" => "\xE6\xAD\xAD", + "\xD1\xFB" => "\xE8\x82\x82", + "\xD1\xFC" => "\xE6\xAE\x88", + "\xD1\xFD" => "\xE6\xAF\xA6", + "\xD1\xFE" => "\xE6\xAF\xA4", + "\xD2\x40" => "\xE6\xAF\xA8", + "\xD2\x41" => "\xE6\xAF\xA3", + "\xD2\x42" => "\xE6\xAF\xA2", + "\xD2\x43" => "\xE6\xAF\xA7", + "\xD2\x44" => "\xE6\xB0\xA5", + "\xD2\x45" => "\xE6\xB5\xBA", + "\xD2\x46" => "\xE6\xB5\xA3", + "\xD2\x47" => "\xE6\xB5\xA4", + "\xD2\x48" => "\xE6\xB5\xB6", + "\xD2\x49" => "\xE6\xB4\x8D", + "\xD2\x4A" => "\xE6\xB5\xA1", + "\xD2\x4B" => "\xE6\xB6\x92", + "\xD2\x4C" => "\xE6\xB5\x98", + "\xD2\x4D" => "\xE6\xB5\xA2", + "\xD2\x4E" => "\xE6\xB5\xAD", + "\xD2\x4F" => "\xE6\xB5\xAF", + "\xD2\x50" => "\xE6\xB6\x91", + "\xD2\x51" => "\xE6\xB6\x8D", + "\xD2\x52" => "\xE6\xB7\xAF", + "\xD2\x53" => "\xE6\xB5\xBF", + "\xD2\x54" => "\xE6\xB6\x86", + "\xD2\x55" => "\xE6\xB5\x9E", + "\xD2\x56" => "\xE6\xB5\xA7", + "\xD2\x57" => "\xE6\xB5\xA0", + "\xD2\x58" => "\xE6\xB6\x97", + "\xD2\x59" => "\xE6\xB5\xB0", + "\xD2\x5A" => "\xE6\xB5\xBC", + "\xD2\x5B" => "\xE6\xB5\x9F", + "\xD2\x5C" => "\xE6\xB6\x82", + "\xD2\x5D" => "\xE6\xB6\x98", + "\xD2\x5E" => "\xE6\xB4\xAF", + "\xD2\x5F" => "\xE6\xB5\xA8", + "\xD2\x60" => "\xE6\xB6\x8B", + "\xD2\x61" => "\xE6\xB5\xBE", + "\xD2\x62" => "\xE6\xB6\x80", + "\xD2\x63" => "\xE6\xB6\x84", + "\xD2\x64" => "\xE6\xB4\x96", + "\xD2\x65" => "\xE6\xB6\x83", + "\xD2\x66" => "\xE6\xB5\xBB", + "\xD2\x67" => "\xE6\xB5\xBD", + "\xD2\x68" => "\xE6\xB5\xB5", + "\xD2\x69" => "\xE6\xB6\x90", + "\xD2\x6A" => "\xE7\x83\x9C", + "\xD2\x6B" => "\xE7\x83\x93", + "\xD2\x6C" => "\xE7\x83\x91", + "\xD2\x6D" => "\xE7\x83\x9D", + "\xD2\x6E" => "\xE7\x83\x8B", + "\xD2\x6F" => "\xE7\xBC\xB9", + "\xD2\x70" => "\xE7\x83\xA2", + "\xD2\x71" => "\xE7\x83\x97", + "\xD2\x72" => "\xE7\x83\x92", + "\xD2\x73" => "\xE7\x83\x9E", + "\xD2\x74" => "\xE7\x83\xA0", + "\xD2\x75" => "\xE7\x83\x94", + "\xD2\x76" => "\xE7\x83\x8D", + "\xD2\x77" => "\xE7\x83\x85", + "\xD2\x78" => "\xE7\x83\x86", + "\xD2\x79" => "\xE7\x83\x87", + "\xD2\x7A" => "\xE7\x83\x9A", + "\xD2\x7B" => "\xE7\x83\x8E", + "\xD2\x7C" => "\xE7\x83\xA1", + "\xD2\x7D" => "\xE7\x89\x82", + "\xD2\x7E" => "\xE7\x89\xB8", + "\xD2\xA1" => "\xE7\x89\xB7", + "\xD2\xA2" => "\xE7\x89\xB6", + "\xD2\xA3" => "\xE7\x8C\x80", + "\xD2\xA4" => "\xE7\x8B\xBA", + "\xD2\xA5" => "\xE7\x8B\xB4", + "\xD2\xA6" => "\xE7\x8B\xBE", + "\xD2\xA7" => "\xE7\x8B\xB6", + "\xD2\xA8" => "\xE7\x8B\xB3", + "\xD2\xA9" => "\xE7\x8B\xBB", + "\xD2\xAA" => "\xE7\x8C\x81", + "\xD2\xAB" => "\xE7\x8F\x93", + "\xD2\xAC" => "\xE7\x8F\x99", + "\xD2\xAD" => "\xE7\x8F\xA5", + "\xD2\xAE" => "\xE7\x8F\x96", + "\xD2\xAF" => "\xE7\x8E\xBC", + "\xD2\xB0" => "\xE7\x8F\xA7", + "\xD2\xB1" => "\xE7\x8F\xA3", + "\xD2\xB2" => "\xE7\x8F\xA9", + "\xD2\xB3" => "\xE7\x8F\x9C", + "\xD2\xB4" => "\xE7\x8F\x92", + "\xD2\xB5" => "\xE7\x8F\x9B", + "\xD2\xB6" => "\xE7\x8F\x94", + "\xD2\xB7" => "\xE7\x8F\x9D", + "\xD2\xB8" => "\xE7\x8F\x9A", + "\xD2\xB9" => "\xE7\x8F\x97", + "\xD2\xBA" => "\xE7\x8F\x98", + "\xD2\xBB" => "\xE7\x8F\xA8", + "\xD2\xBC" => "\xE7\x93\x9E", + "\xD2\xBD" => "\xE7\x93\x9F", + "\xD2\xBE" => "\xE7\x93\xB4", + "\xD2\xBF" => "\xE7\x93\xB5", + "\xD2\xC0" => "\xE7\x94\xA1", + "\xD2\xC1" => "\xE7\x95\x9B", + "\xD2\xC2" => "\xE7\x95\x9F", + "\xD2\xC3" => "\xE7\x96\xB0", + "\xD2\xC4" => "\xE7\x97\x81", + "\xD2\xC5" => "\xE7\x96\xBB", + "\xD2\xC6" => "\xE7\x97\x84", + "\xD2\xC7" => "\xE7\x97\x80", + "\xD2\xC8" => "\xE7\x96\xBF", + "\xD2\xC9" => "\xE7\x96\xB6", + "\xD2\xCA" => "\xE7\x96\xBA", + "\xD2\xCB" => "\xE7\x9A\x8A", + "\xD2\xCC" => "\xE7\x9B\x89", + "\xD2\xCD" => "\xE7\x9C\x9D", + "\xD2\xCE" => "\xE7\x9C\x9B", + "\xD2\xCF" => "\xE7\x9C\x90", + "\xD2\xD0" => "\xE7\x9C\x93", + "\xD2\xD1" => "\xE7\x9C\x92", + "\xD2\xD2" => "\xE7\x9C\xA3", + "\xD2\xD3" => "\xE7\x9C\x91", + "\xD2\xD4" => "\xE7\x9C\x95", + "\xD2\xD5" => "\xE7\x9C\x99", + "\xD2\xD6" => "\xE7\x9C\x9A", + "\xD2\xD7" => "\xE7\x9C\xA2", + "\xD2\xD8" => "\xE7\x9C\xA7", + "\xD2\xD9" => "\xE7\xA0\xA3", + "\xD2\xDA" => "\xE7\xA0\xAC", + "\xD2\xDB" => "\xE7\xA0\xA2", + "\xD2\xDC" => "\xE7\xA0\xB5", + "\xD2\xDD" => "\xE7\xA0\xAF", + "\xD2\xDE" => "\xE7\xA0\xA8", + "\xD2\xDF" => "\xE7\xA0\xAE", + "\xD2\xE0" => "\xE7\xA0\xAB", + "\xD2\xE1" => "\xE7\xA0\xA1", + "\xD2\xE2" => "\xE7\xA0\xA9", + "\xD2\xE3" => "\xE7\xA0\xB3", + "\xD2\xE4" => "\xE7\xA0\xAA", + "\xD2\xE5" => "\xE7\xA0\xB1", + "\xD2\xE6" => "\xE7\xA5\x94", + "\xD2\xE7" => "\xE7\xA5\x9B", + "\xD2\xE8" => "\xE7\xA5\x8F", + "\xD2\xE9" => "\xE7\xA5\x9C", + "\xD2\xEA" => "\xE7\xA5\x93", + "\xD2\xEB" => "\xE7\xA5\x92", + "\xD2\xEC" => "\xE7\xA5\x91", + "\xD2\xED" => "\xE7\xA7\xAB", + "\xD2\xEE" => "\xE7\xA7\xAC", + "\xD2\xEF" => "\xE7\xA7\xA0", + "\xD2\xF0" => "\xE7\xA7\xAE", + "\xD2\xF1" => "\xE7\xA7\xAD", + "\xD2\xF2" => "\xE7\xA7\xAA", + "\xD2\xF3" => "\xE7\xA7\x9C", + "\xD2\xF4" => "\xE7\xA7\x9E", + "\xD2\xF5" => "\xE7\xA7\x9D", + "\xD2\xF6" => "\xE7\xAA\x86", + "\xD2\xF7" => "\xE7\xAA\x89", + "\xD2\xF8" => "\xE7\xAA\x85", + "\xD2\xF9" => "\xE7\xAA\x8B", + "\xD2\xFA" => "\xE7\xAA\x8C", + "\xD2\xFB" => "\xE7\xAA\x8A", + "\xD2\xFC" => "\xE7\xAA\x87", + "\xD2\xFD" => "\xE7\xAB\x98", + "\xD2\xFE" => "\xE7\xAC\x90", + "\xD3\x40" => "\xE7\xAC\x84", + "\xD3\x41" => "\xE7\xAC\x93", + "\xD3\x42" => "\xE7\xAC\x85", + "\xD3\x43" => "\xE7\xAC\x8F", + "\xD3\x44" => "\xE7\xAC\x88", + "\xD3\x45" => "\xE7\xAC\x8A", + "\xD3\x46" => "\xE7\xAC\x8E", + "\xD3\x47" => "\xE7\xAC\x89", + "\xD3\x48" => "\xE7\xAC\x92", + "\xD3\x49" => "\xE7\xB2\x84", + "\xD3\x4A" => "\xE7\xB2\x91", + "\xD3\x4B" => "\xE7\xB2\x8A", + "\xD3\x4C" => "\xE7\xB2\x8C", + "\xD3\x4D" => "\xE7\xB2\x88", + "\xD3\x4E" => "\xE7\xB2\x8D", + "\xD3\x4F" => "\xE7\xB2\x85", + "\xD3\x50" => "\xE7\xB4\x9E", + "\xD3\x51" => "\xE7\xB4\x9D", + "\xD3\x52" => "\xE7\xB4\x91", + "\xD3\x53" => "\xE7\xB4\x8E", + "\xD3\x54" => "\xE7\xB4\x98", + "\xD3\x55" => "\xE7\xB4\x96", + "\xD3\x56" => "\xE7\xB4\x93", + "\xD3\x57" => "\xE7\xB4\x9F", + "\xD3\x58" => "\xE7\xB4\x92", + "\xD3\x59" => "\xE7\xB4\x8F", + "\xD3\x5A" => "\xE7\xB4\x8C", + "\xD3\x5B" => "\xE7\xBD\x9C", + "\xD3\x5C" => "\xE7\xBD\xA1", + "\xD3\x5D" => "\xE7\xBD\x9E", + "\xD3\x5E" => "\xE7\xBD\xA0", + "\xD3\x5F" => "\xE7\xBD\x9D", + "\xD3\x60" => "\xE7\xBD\x9B", + "\xD3\x61" => "\xE7\xBE\x96", + "\xD3\x62" => "\xE7\xBE\x92", + "\xD3\x63" => "\xE7\xBF\x83", + "\xD3\x64" => "\xE7\xBF\x82", + "\xD3\x65" => "\xE7\xBF\x80", + "\xD3\x66" => "\xE8\x80\x96", + "\xD3\x67" => "\xE8\x80\xBE", + "\xD3\x68" => "\xE8\x80\xB9", + "\xD3\x69" => "\xE8\x83\xBA", + "\xD3\x6A" => "\xE8\x83\xB2", + "\xD3\x6B" => "\xE8\x83\xB9", + "\xD3\x6C" => "\xE8\x83\xB5", + "\xD3\x6D" => "\xE8\x84\x81", + "\xD3\x6E" => "\xE8\x83\xBB", + "\xD3\x6F" => "\xE8\x84\x80", + "\xD3\x70" => "\xE8\x88\x81", + "\xD3\x71" => "\xE8\x88\xAF", + "\xD3\x72" => "\xE8\x88\xA5", + "\xD3\x73" => "\xE8\x8C\xB3", + "\xD3\x74" => "\xE8\x8C\xAD", + "\xD3\x75" => "\xE8\x8D\x84", + "\xD3\x76" => "\xE8\x8C\x99", + "\xD3\x77" => "\xE8\x8D\x91", + "\xD3\x78" => "\xE8\x8C\xA5", + "\xD3\x79" => "\xE8\x8D\x96", + "\xD3\x7A" => "\xE8\x8C\xBF", + "\xD3\x7B" => "\xE8\x8D\x81", + "\xD3\x7C" => "\xE8\x8C\xA6", + "\xD3\x7D" => "\xE8\x8C\x9C", + "\xD3\x7E" => "\xE8\x8C\xA2", + "\xD3\xA1" => "\xE8\x8D\x82", + "\xD3\xA2" => "\xE8\x8D\x8E", + "\xD3\xA3" => "\xE8\x8C\x9B", + "\xD3\xA4" => "\xE8\x8C\xAA", + "\xD3\xA5" => "\xE8\x8C\x88", + "\xD3\xA6" => "\xE8\x8C\xBC", + "\xD3\xA7" => "\xE8\x8D\x8D", + "\xD3\xA8" => "\xE8\x8C\x96", + "\xD3\xA9" => "\xE8\x8C\xA4", + "\xD3\xAA" => "\xE8\x8C\xA0", + "\xD3\xAB" => "\xE8\x8C\xB7", + "\xD3\xAC" => "\xE8\x8C\xAF", + "\xD3\xAD" => "\xE8\x8C\xA9", + "\xD3\xAE" => "\xE8\x8D\x87", + "\xD3\xAF" => "\xE8\x8D\x85", + "\xD3\xB0" => "\xE8\x8D\x8C", + "\xD3\xB1" => "\xE8\x8D\x93", + "\xD3\xB2" => "\xE8\x8C\x9E", + "\xD3\xB3" => "\xE8\x8C\xAC", + "\xD3\xB4" => "\xE8\x8D\x8B", + "\xD3\xB5" => "\xE8\x8C\xA7", + "\xD3\xB6" => "\xE8\x8D\x88", + "\xD3\xB7" => "\xE8\x99\x93", + "\xD3\xB8" => "\xE8\x99\x92", + "\xD3\xB9" => "\xE8\x9A\xA2", + "\xD3\xBA" => "\xE8\x9A\xA8", + "\xD3\xBB" => "\xE8\x9A\x96", + "\xD3\xBC" => "\xE8\x9A\x8D", + "\xD3\xBD" => "\xE8\x9A\x91", + "\xD3\xBE" => "\xE8\x9A\x9E", + "\xD3\xBF" => "\xE8\x9A\x87", + "\xD3\xC0" => "\xE8\x9A\x97", + "\xD3\xC1" => "\xE8\x9A\x86", + "\xD3\xC2" => "\xE8\x9A\x8B", + "\xD3\xC3" => "\xE8\x9A\x9A", + "\xD3\xC4" => "\xE8\x9A\x85", + "\xD3\xC5" => "\xE8\x9A\xA5", + "\xD3\xC6" => "\xE8\x9A\x99", + "\xD3\xC7" => "\xE8\x9A\xA1", + "\xD3\xC8" => "\xE8\x9A\xA7", + "\xD3\xC9" => "\xE8\x9A\x95", + "\xD3\xCA" => "\xE8\x9A\x98", + "\xD3\xCB" => "\xE8\x9A\x8E", + "\xD3\xCC" => "\xE8\x9A\x9D", + "\xD3\xCD" => "\xE8\x9A\x90", + "\xD3\xCE" => "\xE8\x9A\x94", + "\xD3\xCF" => "\xE8\xA1\x83", + "\xD3\xD0" => "\xE8\xA1\x84", + "\xD3\xD1" => "\xE8\xA1\xAD", + "\xD3\xD2" => "\xE8\xA1\xB5", + "\xD3\xD3" => "\xE8\xA1\xB6", + "\xD3\xD4" => "\xE8\xA1\xB2", + "\xD3\xD5" => "\xE8\xA2\x80", + "\xD3\xD6" => "\xE8\xA1\xB1", + "\xD3\xD7" => "\xE8\xA1\xBF", + "\xD3\xD8" => "\xE8\xA1\xAF", + "\xD3\xD9" => "\xE8\xA2\x83", + "\xD3\xDA" => "\xE8\xA1\xBE", + "\xD3\xDB" => "\xE8\xA1\xB4", + "\xD3\xDC" => "\xE8\xA1\xBC", + "\xD3\xDD" => "\xE8\xA8\x92", + "\xD3\xDE" => "\xE8\xB1\x87", + "\xD3\xDF" => "\xE8\xB1\x97", + "\xD3\xE0" => "\xE8\xB1\xBB", + "\xD3\xE1" => "\xE8\xB2\xA4", + "\xD3\xE2" => "\xE8\xB2\xA3", + "\xD3\xE3" => "\xE8\xB5\xB6", + "\xD3\xE4" => "\xE8\xB5\xB8", + "\xD3\xE5" => "\xE8\xB6\xB5", + "\xD3\xE6" => "\xE8\xB6\xB7", + "\xD3\xE7" => "\xE8\xB6\xB6", + "\xD3\xE8" => "\xE8\xBB\x91", + "\xD3\xE9" => "\xE8\xBB\x93", + "\xD3\xEA" => "\xE8\xBF\xBE", + "\xD3\xEB" => "\xE8\xBF\xB5", + "\xD3\xEC" => "\xE9\x80\x82", + "\xD3\xED" => "\xE8\xBF\xBF", + "\xD3\xEE" => "\xE8\xBF\xBB", + "\xD3\xEF" => "\xE9\x80\x84", + "\xD3\xF0" => "\xE8\xBF\xBC", + "\xD3\xF1" => "\xE8\xBF\xB6", + "\xD3\xF2" => "\xE9\x83\x96", + "\xD3\xF3" => "\xE9\x83\xA0", + "\xD3\xF4" => "\xE9\x83\x99", + "\xD3\xF5" => "\xE9\x83\x9A", + "\xD3\xF6" => "\xE9\x83\xA3", + "\xD3\xF7" => "\xE9\x83\x9F", + "\xD3\xF8" => "\xE9\x83\xA5", + "\xD3\xF9" => "\xE9\x83\x98", + "\xD3\xFA" => "\xE9\x83\x9B", + "\xD3\xFB" => "\xE9\x83\x97", + "\xD3\xFC" => "\xE9\x83\x9C", + "\xD3\xFD" => "\xE9\x83\xA4", + "\xD3\xFE" => "\xE9\x85\x90", + "\xD4\x40" => "\xE9\x85\x8E", + "\xD4\x41" => "\xE9\x85\x8F", + "\xD4\x42" => "\xE9\x87\x95", + "\xD4\x43" => "\xE9\x87\xA2", + "\xD4\x44" => "\xE9\x87\x9A", + "\xD4\x45" => "\xE9\x99\x9C", + "\xD4\x46" => "\xE9\x99\x9F", + "\xD4\x47" => "\xE9\x9A\xBC", + "\xD4\x48" => "\xE9\xA3\xA3", + "\xD4\x49" => "\xE9\xAB\x9F", + "\xD4\x4A" => "\xE9\xAC\xAF", + "\xD4\x4B" => "\xE4\xB9\xBF", + "\xD4\x4C" => "\xE5\x81\xB0", + "\xD4\x4D" => "\xE5\x81\xAA", + "\xD4\x4E" => "\xE5\x81\xA1", + "\xD4\x4F" => "\xE5\x81\x9E", + "\xD4\x50" => "\xE5\x81\xA0", + "\xD4\x51" => "\xE5\x81\x93", + "\xD4\x52" => "\xE5\x81\x8B", + "\xD4\x53" => "\xE5\x81\x9D", + "\xD4\x54" => "\xE5\x81\xB2", + "\xD4\x55" => "\xE5\x81\x88", + "\xD4\x56" => "\xE5\x81\x8D", + "\xD4\x57" => "\xE5\x81\x81", + "\xD4\x58" => "\xE5\x81\x9B", + "\xD4\x59" => "\xE5\x81\x8A", + "\xD4\x5A" => "\xE5\x81\xA2", + "\xD4\x5B" => "\xE5\x80\x95", + "\xD4\x5C" => "\xE5\x81\x85", + "\xD4\x5D" => "\xE5\x81\x9F", + "\xD4\x5E" => "\xE5\x81\xA9", + "\xD4\x5F" => "\xE5\x81\xAB", + "\xD4\x60" => "\xE5\x81\xA3", + "\xD4\x61" => "\xE5\x81\xA4", + "\xD4\x62" => "\xE5\x81\x86", + "\xD4\x63" => "\xE5\x81\x80", + "\xD4\x64" => "\xE5\x81\xAE", + "\xD4\x65" => "\xE5\x81\xB3", + "\xD4\x66" => "\xE5\x81\x97", + "\xD4\x67" => "\xE5\x81\x91", + "\xD4\x68" => "\xE5\x87\x90", + "\xD4\x69" => "\xE5\x89\xAB", + "\xD4\x6A" => "\xE5\x89\xAD", + "\xD4\x6B" => "\xE5\x89\xAC", + "\xD4\x6C" => "\xE5\x89\xAE", + "\xD4\x6D" => "\xE5\x8B\x96", + "\xD4\x6E" => "\xE5\x8B\x93", + "\xD4\x6F" => "\xE5\x8C\xAD", + "\xD4\x70" => "\xE5\x8E\x9C", + "\xD4\x71" => "\xE5\x95\xB5", + "\xD4\x72" => "\xE5\x95\xB6", + "\xD4\x73" => "\xE5\x94\xBC", + "\xD4\x74" => "\xE5\x95\x8D", + "\xD4\x75" => "\xE5\x95\x90", + "\xD4\x76" => "\xE5\x94\xB4", + "\xD4\x77" => "\xE5\x94\xAA", + "\xD4\x78" => "\xE5\x95\x91", + "\xD4\x79" => "\xE5\x95\xA2", + "\xD4\x7A" => "\xE5\x94\xB6", + "\xD4\x7B" => "\xE5\x94\xB5", + "\xD4\x7C" => "\xE5\x94\xB0", + "\xD4\x7D" => "\xE5\x95\x92", + "\xD4\x7E" => "\xE5\x95\x85", + "\xD4\xA1" => "\xE5\x94\x8C", + "\xD4\xA2" => "\xE5\x94\xB2", + "\xD4\xA3" => "\xE5\x95\xA5", + "\xD4\xA4" => "\xE5\x95\x8E", + "\xD4\xA5" => "\xE5\x94\xB9", + "\xD4\xA6" => "\xE5\x95\x88", + "\xD4\xA7" => "\xE5\x94\xAD", + "\xD4\xA8" => "\xE5\x94\xBB", + "\xD4\xA9" => "\xE5\x95\x80", + "\xD4\xAA" => "\xE5\x95\x8B", + "\xD4\xAB" => "\xE5\x9C\x8A", + "\xD4\xAC" => "\xE5\x9C\x87", + "\xD4\xAD" => "\xE5\x9F\xBB", + "\xD4\xAE" => "\xE5\xA0\x94", + "\xD4\xAF" => "\xE5\x9F\xA2", + "\xD4\xB0" => "\xE5\x9F\xB6", + "\xD4\xB1" => "\xE5\x9F\x9C", + "\xD4\xB2" => "\xE5\x9F\xB4", + "\xD4\xB3" => "\xE5\xA0\x80", + "\xD4\xB4" => "\xE5\x9F\xAD", + "\xD4\xB5" => "\xE5\x9F\xBD", + "\xD4\xB6" => "\xE5\xA0\x88", + "\xD4\xB7" => "\xE5\x9F\xB8", + "\xD4\xB8" => "\xE5\xA0\x8B", + "\xD4\xB9" => "\xE5\x9F\xB3", + "\xD4\xBA" => "\xE5\x9F\x8F", + "\xD4\xBB" => "\xE5\xA0\x87", + "\xD4\xBC" => "\xE5\x9F\xAE", + "\xD4\xBD" => "\xE5\x9F\xA3", + "\xD4\xBE" => "\xE5\x9F\xB2", + "\xD4\xBF" => "\xE5\x9F\xA5", + "\xD4\xC0" => "\xE5\x9F\xAC", + "\xD4\xC1" => "\xE5\x9F\xA1", + "\xD4\xC2" => "\xE5\xA0\x8E", + "\xD4\xC3" => "\xE5\x9F\xBC", + "\xD4\xC4" => "\xE5\xA0\x90", + "\xD4\xC5" => "\xE5\x9F\xA7", + "\xD4\xC6" => "\xE5\xA0\x81", + "\xD4\xC7" => "\xE5\xA0\x8C", + "\xD4\xC8" => "\xE5\x9F\xB1", + "\xD4\xC9" => "\xE5\x9F\xA9", + "\xD4\xCA" => "\xE5\x9F\xB0", + "\xD4\xCB" => "\xE5\xA0\x8D", + "\xD4\xCC" => "\xE5\xA0\x84", + "\xD4\xCD" => "\xE5\xA5\x9C", + "\xD4\xCE" => "\xE5\xA9\xA0", + "\xD4\xCF" => "\xE5\xA9\x98", + "\xD4\xD0" => "\xE5\xA9\x95", + "\xD4\xD1" => "\xE5\xA9\xA7", + "\xD4\xD2" => "\xE5\xA9\x9E", + "\xD4\xD3" => "\xE5\xA8\xB8", + "\xD4\xD4" => "\xE5\xA8\xB5", + "\xD4\xD5" => "\xE5\xA9\xAD", + "\xD4\xD6" => "\xE5\xA9\x90", + "\xD4\xD7" => "\xE5\xA9\x9F", + "\xD4\xD8" => "\xE5\xA9\xA5", + "\xD4\xD9" => "\xE5\xA9\xAC", + "\xD4\xDA" => "\xE5\xA9\x93", + "\xD4\xDB" => "\xE5\xA9\xA4", + "\xD4\xDC" => "\xE5\xA9\x97", + "\xD4\xDD" => "\xE5\xA9\x83", + "\xD4\xDE" => "\xE5\xA9\x9D", + "\xD4\xDF" => "\xE5\xA9\x92", + "\xD4\xE0" => "\xE5\xA9\x84", + "\xD4\xE1" => "\xE5\xA9\x9B", + "\xD4\xE2" => "\xE5\xA9\x88", + "\xD4\xE3" => "\xE5\xAA\x8E", + "\xD4\xE4" => "\xE5\xA8\xBE", + "\xD4\xE5" => "\xE5\xA9\x8D", + "\xD4\xE6" => "\xE5\xA8\xB9", + "\xD4\xE7" => "\xE5\xA9\x8C", + "\xD4\xE8" => "\xE5\xA9\xB0", + "\xD4\xE9" => "\xE5\xA9\xA9", + "\xD4\xEA" => "\xE5\xA9\x87", + "\xD4\xEB" => "\xE5\xA9\x91", + "\xD4\xEC" => "\xE5\xA9\x96", + "\xD4\xED" => "\xE5\xA9\x82", + "\xD4\xEE" => "\xE5\xA9\x9C", + "\xD4\xEF" => "\xE5\xAD\xB2", + "\xD4\xF0" => "\xE5\xAD\xAE", + "\xD4\xF1" => "\xE5\xAF\x81", + "\xD4\xF2" => "\xE5\xAF\x80", + "\xD4\xF3" => "\xE5\xB1\x99", + "\xD4\xF4" => "\xE5\xB4\x9E", + "\xD4\xF5" => "\xE5\xB4\x8B", + "\xD4\xF6" => "\xE5\xB4\x9D", + "\xD4\xF7" => "\xE5\xB4\x9A", + "\xD4\xF8" => "\xE5\xB4\xA0", + "\xD4\xF9" => "\xE5\xB4\x8C", + "\xD4\xFA" => "\xE5\xB4\xA8", + "\xD4\xFB" => "\xE5\xB4\x8D", + "\xD4\xFC" => "\xE5\xB4\xA6", + "\xD4\xFD" => "\xE5\xB4\xA5", + "\xD4\xFE" => "\xE5\xB4\x8F", + "\xD5\x40" => "\xE5\xB4\xB0", + "\xD5\x41" => "\xE5\xB4\x92", + "\xD5\x42" => "\xE5\xB4\xA3", + "\xD5\x43" => "\xE5\xB4\x9F", + "\xD5\x44" => "\xE5\xB4\xAE", + "\xD5\x45" => "\xE5\xB8\xBE", + "\xD5\x46" => "\xE5\xB8\xB4", + "\xD5\x47" => "\xE5\xBA\xB1", + "\xD5\x48" => "\xE5\xBA\xB4", + "\xD5\x49" => "\xE5\xBA\xB9", + "\xD5\x4A" => "\xE5\xBA\xB2", + "\xD5\x4B" => "\xE5\xBA\xB3", + "\xD5\x4C" => "\xE5\xBC\xB6", + "\xD5\x4D" => "\xE5\xBC\xB8", + "\xD5\x4E" => "\xE5\xBE\x9B", + "\xD5\x4F" => "\xE5\xBE\x96", + "\xD5\x50" => "\xE5\xBE\x9F", + "\xD5\x51" => "\xE6\x82\x8A", + "\xD5\x52" => "\xE6\x82\x90", + "\xD5\x53" => "\xE6\x82\x86", + "\xD5\x54" => "\xE6\x82\xBE", + "\xD5\x55" => "\xE6\x82\xB0", + "\xD5\x56" => "\xE6\x82\xBA", + "\xD5\x57" => "\xE6\x83\x93", + "\xD5\x58" => "\xE6\x83\x94", + "\xD5\x59" => "\xE6\x83\x8F", + "\xD5\x5A" => "\xE6\x83\xA4", + "\xD5\x5B" => "\xE6\x83\x99", + "\xD5\x5C" => "\xE6\x83\x9D", + "\xD5\x5D" => "\xE6\x83\x88", + "\xD5\x5E" => "\xE6\x82\xB1", + "\xD5\x5F" => "\xE6\x83\x9B", + "\xD5\x60" => "\xE6\x82\xB7", + "\xD5\x61" => "\xE6\x83\x8A", + "\xD5\x62" => "\xE6\x82\xBF", + "\xD5\x63" => "\xE6\x83\x83", + "\xD5\x64" => "\xE6\x83\x8D", + "\xD5\x65" => "\xE6\x83\x80", + "\xD5\x66" => "\xE6\x8C\xB2", + "\xD5\x67" => "\xE6\x8D\xA5", + "\xD5\x68" => "\xE6\x8E\x8A", + "\xD5\x69" => "\xE6\x8E\x82", + "\xD5\x6A" => "\xE6\x8D\xBD", + "\xD5\x6B" => "\xE6\x8E\xBD", + "\xD5\x6C" => "\xE6\x8E\x9E", + "\xD5\x6D" => "\xE6\x8E\xAD", + "\xD5\x6E" => "\xE6\x8E\x9D", + "\xD5\x6F" => "\xE6\x8E\x97", + "\xD5\x70" => "\xE6\x8E\xAB", + "\xD5\x71" => "\xE6\x8E\x8E", + "\xD5\x72" => "\xE6\x8D\xAF", + "\xD5\x73" => "\xE6\x8E\x87", + "\xD5\x74" => "\xE6\x8E\x90", + "\xD5\x75" => "\xE6\x8D\xAE", + "\xD5\x76" => "\xE6\x8E\xAF", + "\xD5\x77" => "\xE6\x8D\xB5", + "\xD5\x78" => "\xE6\x8E\x9C", + "\xD5\x79" => "\xE6\x8D\xAD", + "\xD5\x7A" => "\xE6\x8E\xAE", + "\xD5\x7B" => "\xE6\x8D\xBC", + "\xD5\x7C" => "\xE6\x8E\xA4", + "\xD5\x7D" => "\xE6\x8C\xBB", + "\xD5\x7E" => "\xE6\x8E\x9F", + "\xD5\xA1" => "\xE6\x8D\xB8", + "\xD5\xA2" => "\xE6\x8E\x85", + "\xD5\xA3" => "\xE6\x8E\x81", + "\xD5\xA4" => "\xE6\x8E\x91", + "\xD5\xA5" => "\xE6\x8E\x8D", + "\xD5\xA6" => "\xE6\x8D\xB0", + "\xD5\xA7" => "\xE6\x95\x93", + "\xD5\xA8" => "\xE6\x97\x8D", + "\xD5\xA9" => "\xE6\x99\xA5", + "\xD5\xAA" => "\xE6\x99\xA1", + "\xD5\xAB" => "\xE6\x99\x9B", + "\xD5\xAC" => "\xE6\x99\x99", + "\xD5\xAD" => "\xE6\x99\x9C", + "\xD5\xAE" => "\xE6\x99\xA2", + "\xD5\xAF" => "\xE6\x9C\x98", + "\xD5\xB0" => "\xE6\xA1\xB9", + "\xD5\xB1" => "\xE6\xA2\x87", + "\xD5\xB2" => "\xE6\xA2\x90", + "\xD5\xB3" => "\xE6\xA2\x9C", + "\xD5\xB4" => "\xE6\xA1\xAD", + "\xD5\xB5" => "\xE6\xA1\xAE", + "\xD5\xB6" => "\xE6\xA2\xAE", + "\xD5\xB7" => "\xE6\xA2\xAB", + "\xD5\xB8" => "\xE6\xA5\x96", + "\xD5\xB9" => "\xE6\xA1\xAF", + "\xD5\xBA" => "\xE6\xA2\xA3", + "\xD5\xBB" => "\xE6\xA2\xAC", + "\xD5\xBC" => "\xE6\xA2\xA9", + "\xD5\xBD" => "\xE6\xA1\xB5", + "\xD5\xBE" => "\xE6\xA1\xB4", + "\xD5\xBF" => "\xE6\xA2\xB2", + "\xD5\xC0" => "\xE6\xA2\x8F", + "\xD5\xC1" => "\xE6\xA1\xB7", + "\xD5\xC2" => "\xE6\xA2\x92", + "\xD5\xC3" => "\xE6\xA1\xBC", + "\xD5\xC4" => "\xE6\xA1\xAB", + "\xD5\xC5" => "\xE6\xA1\xB2", + "\xD5\xC6" => "\xE6\xA2\xAA", + "\xD5\xC7" => "\xE6\xA2\x80", + "\xD5\xC8" => "\xE6\xA1\xB1", + "\xD5\xC9" => "\xE6\xA1\xBE", + "\xD5\xCA" => "\xE6\xA2\x9B", + "\xD5\xCB" => "\xE6\xA2\x96", + "\xD5\xCC" => "\xE6\xA2\x8B", + "\xD5\xCD" => "\xE6\xA2\xA0", + "\xD5\xCE" => "\xE6\xA2\x89", + "\xD5\xCF" => "\xE6\xA2\xA4", + "\xD5\xD0" => "\xE6\xA1\xB8", + "\xD5\xD1" => "\xE6\xA1\xBB", + "\xD5\xD2" => "\xE6\xA2\x91", + "\xD5\xD3" => "\xE6\xA2\x8C", + "\xD5\xD4" => "\xE6\xA2\x8A", + "\xD5\xD5" => "\xE6\xA1\xBD", + "\xD5\xD6" => "\xE6\xAC\xB6", + "\xD5\xD7" => "\xE6\xAC\xB3", + "\xD5\xD8" => "\xE6\xAC\xB7", + "\xD5\xD9" => "\xE6\xAC\xB8", + "\xD5\xDA" => "\xE6\xAE\x91", + "\xD5\xDB" => "\xE6\xAE\x8F", + "\xD5\xDC" => "\xE6\xAE\x8D", + "\xD5\xDD" => "\xE6\xAE\x8E", + "\xD5\xDE" => "\xE6\xAE\x8C", + "\xD5\xDF" => "\xE6\xB0\xAA", + "\xD5\xE0" => "\xE6\xB7\x80", + "\xD5\xE1" => "\xE6\xB6\xAB", + "\xD5\xE2" => "\xE6\xB6\xB4", + "\xD5\xE3" => "\xE6\xB6\xB3", + "\xD5\xE4" => "\xE6\xB9\xB4", + "\xD5\xE5" => "\xE6\xB6\xAC", + "\xD5\xE6" => "\xE6\xB7\xA9", + "\xD5\xE7" => "\xE6\xB7\xA2", + "\xD5\xE8" => "\xE6\xB6\xB7", + "\xD5\xE9" => "\xE6\xB7\xB6", + "\xD5\xEA" => "\xE6\xB7\x94", + "\xD5\xEB" => "\xE6\xB8\x80", + "\xD5\xEC" => "\xE6\xB7\x88", + "\xD5\xED" => "\xE6\xB7\xA0", + "\xD5\xEE" => "\xE6\xB7\x9F", + "\xD5\xEF" => "\xE6\xB7\x96", + "\xD5\xF0" => "\xE6\xB6\xBE", + "\xD5\xF1" => "\xE6\xB7\xA5", + "\xD5\xF2" => "\xE6\xB7\x9C", + "\xD5\xF3" => "\xE6\xB7\x9D", + "\xD5\xF4" => "\xE6\xB7\x9B", + "\xD5\xF5" => "\xE6\xB7\xB4", + "\xD5\xF6" => "\xE6\xB7\x8A", + "\xD5\xF7" => "\xE6\xB6\xBD", + "\xD5\xF8" => "\xE6\xB7\xAD", + "\xD5\xF9" => "\xE6\xB7\xB0", + "\xD5\xFA" => "\xE6\xB6\xBA", + "\xD5\xFB" => "\xE6\xB7\x95", + "\xD5\xFC" => "\xE6\xB7\x82", + "\xD5\xFD" => "\xE6\xB7\x8F", + "\xD5\xFE" => "\xE6\xB7\x89", + "\xD6\x40" => "\xE6\xB7\x90", + "\xD6\x41" => "\xE6\xB7\xB2", + "\xD6\x42" => "\xE6\xB7\x93", + "\xD6\x43" => "\xE6\xB7\xBD", + "\xD6\x44" => "\xE6\xB7\x97", + "\xD6\x45" => "\xE6\xB7\x8D", + "\xD6\x46" => "\xE6\xB7\xA3", + "\xD6\x47" => "\xE6\xB6\xBB", + "\xD6\x48" => "\xE7\x83\xBA", + "\xD6\x49" => "\xE7\x84\x8D", + "\xD6\x4A" => "\xE7\x83\xB7", + "\xD6\x4B" => "\xE7\x84\x97", + "\xD6\x4C" => "\xE7\x83\xB4", + "\xD6\x4D" => "\xE7\x84\x8C", + "\xD6\x4E" => "\xE7\x83\xB0", + "\xD6\x4F" => "\xE7\x84\x84", + "\xD6\x50" => "\xE7\x83\xB3", + "\xD6\x51" => "\xE7\x84\x90", + "\xD6\x52" => "\xE7\x83\xBC", + "\xD6\x53" => "\xE7\x83\xBF", + "\xD6\x54" => "\xE7\x84\x86", + "\xD6\x55" => "\xE7\x84\x93", + "\xD6\x56" => "\xE7\x84\x80", + "\xD6\x57" => "\xE7\x83\xB8", + "\xD6\x58" => "\xE7\x83\xB6", + "\xD6\x59" => "\xE7\x84\x8B", + "\xD6\x5A" => "\xE7\x84\x82", + "\xD6\x5B" => "\xE7\x84\x8E", + "\xD6\x5C" => "\xE7\x89\xBE", + "\xD6\x5D" => "\xE7\x89\xBB", + "\xD6\x5E" => "\xE7\x89\xBC", + "\xD6\x5F" => "\xE7\x89\xBF", + "\xD6\x60" => "\xE7\x8C\x9D", + "\xD6\x61" => "\xE7\x8C\x97", + "\xD6\x62" => "\xE7\x8C\x87", + "\xD6\x63" => "\xE7\x8C\x91", + "\xD6\x64" => "\xE7\x8C\x98", + "\xD6\x65" => "\xE7\x8C\x8A", + "\xD6\x66" => "\xE7\x8C\x88", + "\xD6\x67" => "\xE7\x8B\xBF", + "\xD6\x68" => "\xE7\x8C\x8F", + "\xD6\x69" => "\xE7\x8C\x9E", + "\xD6\x6A" => "\xE7\x8E\x88", + "\xD6\x6B" => "\xE7\x8F\xB6", + "\xD6\x6C" => "\xE7\x8F\xB8", + "\xD6\x6D" => "\xE7\x8F\xB5", + "\xD6\x6E" => "\xE7\x90\x84", + "\xD6\x6F" => "\xE7\x90\x81", + "\xD6\x70" => "\xE7\x8F\xBD", + "\xD6\x71" => "\xE7\x90\x87", + "\xD6\x72" => "\xE7\x90\x80", + "\xD6\x73" => "\xE7\x8F\xBA", + "\xD6\x74" => "\xE7\x8F\xBC", + "\xD6\x75" => "\xE7\x8F\xBF", + "\xD6\x76" => "\xE7\x90\x8C", + "\xD6\x77" => "\xE7\x90\x8B", + "\xD6\x78" => "\xE7\x8F\xB4", + "\xD6\x79" => "\xE7\x90\x88", + "\xD6\x7A" => "\xE7\x95\xA4", + "\xD6\x7B" => "\xE7\x95\xA3", + "\xD6\x7C" => "\xE7\x97\x8E", + "\xD6\x7D" => "\xE7\x97\x92", + "\xD6\x7E" => "\xE7\x97\x8F", + "\xD6\xA1" => "\xE7\x97\x8B", + "\xD6\xA2" => "\xE7\x97\x8C", + "\xD6\xA3" => "\xE7\x97\x91", + "\xD6\xA4" => "\xE7\x97\x90", + "\xD6\xA5" => "\xE7\x9A\x8F", + "\xD6\xA6" => "\xE7\x9A\x89", + "\xD6\xA7" => "\xE7\x9B\x93", + "\xD6\xA8" => "\xE7\x9C\xB9", + "\xD6\xA9" => "\xE7\x9C\xAF", + "\xD6\xAA" => "\xE7\x9C\xAD", + "\xD6\xAB" => "\xE7\x9C\xB1", + "\xD6\xAC" => "\xE7\x9C\xB2", + "\xD6\xAD" => "\xE7\x9C\xB4", + "\xD6\xAE" => "\xE7\x9C\xB3", + "\xD6\xAF" => "\xE7\x9C\xBD", + "\xD6\xB0" => "\xE7\x9C\xA5", + "\xD6\xB1" => "\xE7\x9C\xBB", + "\xD6\xB2" => "\xE7\x9C\xB5", + "\xD6\xB3" => "\xE7\xA1\x88", + "\xD6\xB4" => "\xE7\xA1\x92", + "\xD6\xB5" => "\xE7\xA1\x89", + "\xD6\xB6" => "\xE7\xA1\x8D", + "\xD6\xB7" => "\xE7\xA1\x8A", + "\xD6\xB8" => "\xE7\xA1\x8C", + "\xD6\xB9" => "\xE7\xA0\xA6", + "\xD6\xBA" => "\xE7\xA1\x85", + "\xD6\xBB" => "\xE7\xA1\x90", + "\xD6\xBC" => "\xE7\xA5\xA4", + "\xD6\xBD" => "\xE7\xA5\xA7", + "\xD6\xBE" => "\xE7\xA5\xA9", + "\xD6\xBF" => "\xE7\xA5\xAA", + "\xD6\xC0" => "\xE7\xA5\xA3", + "\xD6\xC1" => "\xE7\xA5\xAB", + "\xD6\xC2" => "\xE7\xA5\xA1", + "\xD6\xC3" => "\xE7\xA6\xBB", + "\xD6\xC4" => "\xE7\xA7\xBA", + "\xD6\xC5" => "\xE7\xA7\xB8", + "\xD6\xC6" => "\xE7\xA7\xB6", + "\xD6\xC7" => "\xE7\xA7\xB7", + "\xD6\xC8" => "\xE7\xAA\x8F", + "\xD6\xC9" => "\xE7\xAA\x94", + "\xD6\xCA" => "\xE7\xAA\x90", + "\xD6\xCB" => "\xE7\xAC\xB5", + "\xD6\xCC" => "\xE7\xAD\x87", + "\xD6\xCD" => "\xE7\xAC\xB4", + "\xD6\xCE" => "\xE7\xAC\xA5", + "\xD6\xCF" => "\xE7\xAC\xB0", + "\xD6\xD0" => "\xE7\xAC\xA2", + "\xD6\xD1" => "\xE7\xAC\xA4", + "\xD6\xD2" => "\xE7\xAC\xB3", + "\xD6\xD3" => "\xE7\xAC\x98", + "\xD6\xD4" => "\xE7\xAC\xAA", + "\xD6\xD5" => "\xE7\xAC\x9D", + "\xD6\xD6" => "\xE7\xAC\xB1", + "\xD6\xD7" => "\xE7\xAC\xAB", + "\xD6\xD8" => "\xE7\xAC\xAD", + "\xD6\xD9" => "\xE7\xAC\xAF", + "\xD6\xDA" => "\xE7\xAC\xB2", + "\xD6\xDB" => "\xE7\xAC\xB8", + "\xD6\xDC" => "\xE7\xAC\x9A", + "\xD6\xDD" => "\xE7\xAC\xA3", + "\xD6\xDE" => "\xE7\xB2\x94", + "\xD6\xDF" => "\xE7\xB2\x98", + "\xD6\xE0" => "\xE7\xB2\x96", + "\xD6\xE1" => "\xE7\xB2\xA3", + "\xD6\xE2" => "\xE7\xB4\xB5", + "\xD6\xE3" => "\xE7\xB4\xBD", + "\xD6\xE4" => "\xE7\xB4\xB8", + "\xD6\xE5" => "\xE7\xB4\xB6", + "\xD6\xE6" => "\xE7\xB4\xBA", + "\xD6\xE7" => "\xE7\xB5\x85", + "\xD6\xE8" => "\xE7\xB4\xAC", + "\xD6\xE9" => "\xE7\xB4\xA9", + "\xD6\xEA" => "\xE7\xB5\x81", + "\xD6\xEB" => "\xE7\xB5\x87", + "\xD6\xEC" => "\xE7\xB4\xBE", + "\xD6\xED" => "\xE7\xB4\xBF", + "\xD6\xEE" => "\xE7\xB5\x8A", + "\xD6\xEF" => "\xE7\xB4\xBB", + "\xD6\xF0" => "\xE7\xB4\xA8", + "\xD6\xF1" => "\xE7\xBD\xA3", + "\xD6\xF2" => "\xE7\xBE\x95", + "\xD6\xF3" => "\xE7\xBE\x9C", + "\xD6\xF4" => "\xE7\xBE\x9D", + "\xD6\xF5" => "\xE7\xBE\x9B", + "\xD6\xF6" => "\xE7\xBF\x8A", + "\xD6\xF7" => "\xE7\xBF\x8B", + "\xD6\xF8" => "\xE7\xBF\x8D", + "\xD6\xF9" => "\xE7\xBF\x90", + "\xD6\xFA" => "\xE7\xBF\x91", + "\xD6\xFB" => "\xE7\xBF\x87", + "\xD6\xFC" => "\xE7\xBF\x8F", + "\xD6\xFD" => "\xE7\xBF\x89", + "\xD6\xFE" => "\xE8\x80\x9F", + "\xD7\x40" => "\xE8\x80\x9E", + "\xD7\x41" => "\xE8\x80\x9B", + "\xD7\x42" => "\xE8\x81\x87", + "\xD7\x43" => "\xE8\x81\x83", + "\xD7\x44" => "\xE8\x81\x88", + "\xD7\x45" => "\xE8\x84\x98", + "\xD7\x46" => "\xE8\x84\xA5", + "\xD7\x47" => "\xE8\x84\x99", + "\xD7\x48" => "\xE8\x84\x9B", + "\xD7\x49" => "\xE8\x84\xAD", + "\xD7\x4A" => "\xE8\x84\x9F", + "\xD7\x4B" => "\xE8\x84\xAC", + "\xD7\x4C" => "\xE8\x84\x9E", + "\xD7\x4D" => "\xE8\x84\xA1", + "\xD7\x4E" => "\xE8\x84\x95", + "\xD7\x4F" => "\xE8\x84\xA7", + "\xD7\x50" => "\xE8\x84\x9D", + "\xD7\x51" => "\xE8\x84\xA2", + "\xD7\x52" => "\xE8\x88\x91", + "\xD7\x53" => "\xE8\x88\xB8", + "\xD7\x54" => "\xE8\x88\xB3", + "\xD7\x55" => "\xE8\x88\xBA", + "\xD7\x56" => "\xE8\x88\xB4", + "\xD7\x57" => "\xE8\x88\xB2", + "\xD7\x58" => "\xE8\x89\xB4", + "\xD7\x59" => "\xE8\x8E\x90", + "\xD7\x5A" => "\xE8\x8E\xA3", + "\xD7\x5B" => "\xE8\x8E\xA8", + "\xD7\x5C" => "\xE8\x8E\x8D", + "\xD7\x5D" => "\xE8\x8D\xBA", + "\xD7\x5E" => "\xE8\x8D\xB3", + "\xD7\x5F" => "\xE8\x8E\xA4", + "\xD7\x60" => "\xE8\x8D\xB4", + "\xD7\x61" => "\xE8\x8E\x8F", + "\xD7\x62" => "\xE8\x8E\x81", + "\xD7\x63" => "\xE8\x8E\x95", + "\xD7\x64" => "\xE8\x8E\x99", + "\xD7\x65" => "\xE8\x8D\xB5", + "\xD7\x66" => "\xE8\x8E\x94", + "\xD7\x67" => "\xE8\x8E\xA9", + "\xD7\x68" => "\xE8\x8D\xBD", + "\xD7\x69" => "\xE8\x8E\x83", + "\xD7\x6A" => "\xE8\x8E\x8C", + "\xD7\x6B" => "\xE8\x8E\x9D", + "\xD7\x6C" => "\xE8\x8E\x9B", + "\xD7\x6D" => "\xE8\x8E\xAA", + "\xD7\x6E" => "\xE8\x8E\x8B", + "\xD7\x6F" => "\xE8\x8D\xBE", + "\xD7\x70" => "\xE8\x8E\xA5", + "\xD7\x71" => "\xE8\x8E\xAF", + "\xD7\x72" => "\xE8\x8E\x88", + "\xD7\x73" => "\xE8\x8E\x97", + "\xD7\x74" => "\xE8\x8E\xB0", + "\xD7\x75" => "\xE8\x8D\xBF", + "\xD7\x76" => "\xE8\x8E\xA6", + "\xD7\x77" => "\xE8\x8E\x87", + "\xD7\x78" => "\xE8\x8E\xAE", + "\xD7\x79" => "\xE8\x8D\xB6", + "\xD7\x7A" => "\xE8\x8E\x9A", + "\xD7\x7B" => "\xE8\x99\x99", + "\xD7\x7C" => "\xE8\x99\x96", + "\xD7\x7D" => "\xE8\x9A\xBF", + "\xD7\x7E" => "\xE8\x9A\xB7", + "\xD7\xA1" => "\xE8\x9B\x82", + "\xD7\xA2" => "\xE8\x9B\x81", + "\xD7\xA3" => "\xE8\x9B\x85", + "\xD7\xA4" => "\xE8\x9A\xBA", + "\xD7\xA5" => "\xE8\x9A\xB0", + "\xD7\xA6" => "\xE8\x9B\x88", + "\xD7\xA7" => "\xE8\x9A\xB9", + "\xD7\xA8" => "\xE8\x9A\xB3", + "\xD7\xA9" => "\xE8\x9A\xB8", + "\xD7\xAA" => "\xE8\x9B\x8C", + "\xD7\xAB" => "\xE8\x9A\xB4", + "\xD7\xAC" => "\xE8\x9A\xBB", + "\xD7\xAD" => "\xE8\x9A\xBC", + "\xD7\xAE" => "\xE8\x9B\x83", + "\xD7\xAF" => "\xE8\x9A\xBD", + "\xD7\xB0" => "\xE8\x9A\xBE", + "\xD7\xB1" => "\xE8\xA1\x92", + "\xD7\xB2" => "\xE8\xA2\x89", + "\xD7\xB3" => "\xE8\xA2\x95", + "\xD7\xB4" => "\xE8\xA2\xA8", + "\xD7\xB5" => "\xE8\xA2\xA2", + "\xD7\xB6" => "\xE8\xA2\xAA", + "\xD7\xB7" => "\xE8\xA2\x9A", + "\xD7\xB8" => "\xE8\xA2\x91", + "\xD7\xB9" => "\xE8\xA2\xA1", + "\xD7\xBA" => "\xE8\xA2\x9F", + "\xD7\xBB" => "\xE8\xA2\x98", + "\xD7\xBC" => "\xE8\xA2\xA7", + "\xD7\xBD" => "\xE8\xA2\x99", + "\xD7\xBE" => "\xE8\xA2\x9B", + "\xD7\xBF" => "\xE8\xA2\x97", + "\xD7\xC0" => "\xE8\xA2\xA4", + "\xD7\xC1" => "\xE8\xA2\xAC", + "\xD7\xC2" => "\xE8\xA2\x8C", + "\xD7\xC3" => "\xE8\xA2\x93", + "\xD7\xC4" => "\xE8\xA2\x8E", + "\xD7\xC5" => "\xE8\xA6\x82", + "\xD7\xC6" => "\xE8\xA7\x96", + "\xD7\xC7" => "\xE8\xA7\x99", + "\xD7\xC8" => "\xE8\xA7\x95", + "\xD7\xC9" => "\xE8\xA8\xB0", + "\xD7\xCA" => "\xE8\xA8\xA7", + "\xD7\xCB" => "\xE8\xA8\xAC", + "\xD7\xCC" => "\xE8\xA8\x9E", + "\xD7\xCD" => "\xE8\xB0\xB9", + "\xD7\xCE" => "\xE8\xB0\xBB", + "\xD7\xCF" => "\xE8\xB1\x9C", + "\xD7\xD0" => "\xE8\xB1\x9D", + "\xD7\xD1" => "\xE8\xB1\xBD", + "\xD7\xD2" => "\xE8\xB2\xA5", + "\xD7\xD3" => "\xE8\xB5\xBD", + "\xD7\xD4" => "\xE8\xB5\xBB", + "\xD7\xD5" => "\xE8\xB5\xB9", + "\xD7\xD6" => "\xE8\xB6\xBC", + "\xD7\xD7" => "\xE8\xB7\x82", + "\xD7\xD8" => "\xE8\xB6\xB9", + "\xD7\xD9" => "\xE8\xB6\xBF", + "\xD7\xDA" => "\xE8\xB7\x81", + "\xD7\xDB" => "\xE8\xBB\x98", + "\xD7\xDC" => "\xE8\xBB\x9E", + "\xD7\xDD" => "\xE8\xBB\x9D", + "\xD7\xDE" => "\xE8\xBB\x9C", + "\xD7\xDF" => "\xE8\xBB\x97", + "\xD7\xE0" => "\xE8\xBB\xA0", + "\xD7\xE1" => "\xE8\xBB\xA1", + "\xD7\xE2" => "\xE9\x80\xA4", + "\xD7\xE3" => "\xE9\x80\x8B", + "\xD7\xE4" => "\xE9\x80\x91", + "\xD7\xE5" => "\xE9\x80\x9C", + "\xD7\xE6" => "\xE9\x80\x8C", + "\xD7\xE7" => "\xE9\x80\xA1", + "\xD7\xE8" => "\xE9\x83\xAF", + "\xD7\xE9" => "\xE9\x83\xAA", + "\xD7\xEA" => "\xE9\x83\xB0", + "\xD7\xEB" => "\xE9\x83\xB4", + "\xD7\xEC" => "\xE9\x83\xB2", + "\xD7\xED" => "\xE9\x83\xB3", + "\xD7\xEE" => "\xE9\x83\x94", + "\xD7\xEF" => "\xE9\x83\xAB", + "\xD7\xF0" => "\xE9\x83\xAC", + "\xD7\xF1" => "\xE9\x83\xA9", + "\xD7\xF2" => "\xE9\x85\x96", + "\xD7\xF3" => "\xE9\x85\x98", + "\xD7\xF4" => "\xE9\x85\x9A", + "\xD7\xF5" => "\xE9\x85\x93", + "\xD7\xF6" => "\xE9\x85\x95", + "\xD7\xF7" => "\xE9\x87\xAC", + "\xD7\xF8" => "\xE9\x87\xB4", + "\xD7\xF9" => "\xE9\x87\xB1", + "\xD7\xFA" => "\xE9\x87\xB3", + "\xD7\xFB" => "\xE9\x87\xB8", + "\xD7\xFC" => "\xE9\x87\xA4", + "\xD7\xFD" => "\xE9\x87\xB9", + "\xD7\xFE" => "\xE9\x87\xAA", + "\xD8\x40" => "\xE9\x87\xAB", + "\xD8\x41" => "\xE9\x87\xB7", + "\xD8\x42" => "\xE9\x87\xA8", + "\xD8\x43" => "\xE9\x87\xAE", + "\xD8\x44" => "\xE9\x95\xBA", + "\xD8\x45" => "\xE9\x96\x86", + "\xD8\x46" => "\xE9\x96\x88", + "\xD8\x47" => "\xE9\x99\xBC", + "\xD8\x48" => "\xE9\x99\xAD", + "\xD8\x49" => "\xE9\x99\xAB", + "\xD8\x4A" => "\xE9\x99\xB1", + "\xD8\x4B" => "\xE9\x99\xAF", + "\xD8\x4C" => "\xE9\x9A\xBF", + "\xD8\x4D" => "\xE9\x9D\xAA", + "\xD8\x4E" => "\xE9\xA0\x84", + "\xD8\x4F" => "\xE9\xA3\xA5", + "\xD8\x50" => "\xE9\xA6\x97", + "\xD8\x51" => "\xE5\x82\x9B", + "\xD8\x52" => "\xE5\x82\x95", + "\xD8\x53" => "\xE5\x82\x94", + "\xD8\x54" => "\xE5\x82\x9E", + "\xD8\x55" => "\xE5\x82\x8B", + "\xD8\x56" => "\xE5\x82\xA3", + "\xD8\x57" => "\xE5\x82\x83", + "\xD8\x58" => "\xE5\x82\x8C", + "\xD8\x59" => "\xE5\x82\x8E", + "\xD8\x5A" => "\xE5\x82\x9D", + "\xD8\x5B" => "\xE5\x81\xA8", + "\xD8\x5C" => "\xE5\x82\x9C", + "\xD8\x5D" => "\xE5\x82\x92", + "\xD8\x5E" => "\xE5\x82\x82", + "\xD8\x5F" => "\xE5\x82\x87", + "\xD8\x60" => "\xE5\x85\x9F", + "\xD8\x61" => "\xE5\x87\x94", + "\xD8\x62" => "\xE5\x8C\x92", + "\xD8\x63" => "\xE5\x8C\x91", + "\xD8\x64" => "\xE5\x8E\xA4", + "\xD8\x65" => "\xE5\x8E\xA7", + "\xD8\x66" => "\xE5\x96\x91", + "\xD8\x67" => "\xE5\x96\xA8", + "\xD8\x68" => "\xE5\x96\xA5", + "\xD8\x69" => "\xE5\x96\xAD", + "\xD8\x6A" => "\xE5\x95\xB7", + "\xD8\x6B" => "\xE5\x99\x85", + "\xD8\x6C" => "\xE5\x96\xA2", + "\xD8\x6D" => "\xE5\x96\x93", + "\xD8\x6E" => "\xE5\x96\x88", + "\xD8\x6F" => "\xE5\x96\x8F", + "\xD8\x70" => "\xE5\x96\xB5", + "\xD8\x71" => "\xE5\x96\x81", + "\xD8\x72" => "\xE5\x96\xA3", + "\xD8\x73" => "\xE5\x96\x92", + "\xD8\x74" => "\xE5\x96\xA4", + "\xD8\x75" => "\xE5\x95\xBD", + "\xD8\x76" => "\xE5\x96\x8C", + "\xD8\x77" => "\xE5\x96\xA6", + "\xD8\x78" => "\xE5\x95\xBF", + "\xD8\x79" => "\xE5\x96\x95", + "\xD8\x7A" => "\xE5\x96\xA1", + "\xD8\x7B" => "\xE5\x96\x8E", + "\xD8\x7C" => "\xE5\x9C\x8C", + "\xD8\x7D" => "\xE5\xA0\xA9", + "\xD8\x7E" => "\xE5\xA0\xB7", + "\xD8\xA1" => "\xE5\xA0\x99", + "\xD8\xA2" => "\xE5\xA0\x9E", + "\xD8\xA3" => "\xE5\xA0\xA7", + "\xD8\xA4" => "\xE5\xA0\xA3", + "\xD8\xA5" => "\xE5\xA0\xA8", + "\xD8\xA6" => "\xE5\x9F\xB5", + "\xD8\xA7" => "\xE5\xA1\x88", + "\xD8\xA8" => "\xE5\xA0\xA5", + "\xD8\xA9" => "\xE5\xA0\x9C", + "\xD8\xAA" => "\xE5\xA0\x9B", + "\xD8\xAB" => "\xE5\xA0\xB3", + "\xD8\xAC" => "\xE5\xA0\xBF", + "\xD8\xAD" => "\xE5\xA0\xB6", + "\xD8\xAE" => "\xE5\xA0\xAE", + "\xD8\xAF" => "\xE5\xA0\xB9", + "\xD8\xB0" => "\xE5\xA0\xB8", + "\xD8\xB1" => "\xE5\xA0\xAD", + "\xD8\xB2" => "\xE5\xA0\xAC", + "\xD8\xB3" => "\xE5\xA0\xBB", + "\xD8\xB4" => "\xE5\xA5\xA1", + "\xD8\xB5" => "\xE5\xAA\xAF", + "\xD8\xB6" => "\xE5\xAA\x94", + "\xD8\xB7" => "\xE5\xAA\x9F", + "\xD8\xB8" => "\xE5\xA9\xBA", + "\xD8\xB9" => "\xE5\xAA\xA2", + "\xD8\xBA" => "\xE5\xAA\x9E", + "\xD8\xBB" => "\xE5\xA9\xB8", + "\xD8\xBC" => "\xE5\xAA\xA6", + "\xD8\xBD" => "\xE5\xA9\xBC", + "\xD8\xBE" => "\xE5\xAA\xA5", + "\xD8\xBF" => "\xE5\xAA\xAC", + "\xD8\xC0" => "\xE5\xAA\x95", + "\xD8\xC1" => "\xE5\xAA\xAE", + "\xD8\xC2" => "\xE5\xA8\xB7", + "\xD8\xC3" => "\xE5\xAA\x84", + "\xD8\xC4" => "\xE5\xAA\x8A", + "\xD8\xC5" => "\xE5\xAA\x97", + "\xD8\xC6" => "\xE5\xAA\x83", + "\xD8\xC7" => "\xE5\xAA\x8B", + "\xD8\xC8" => "\xE5\xAA\xA9", + "\xD8\xC9" => "\xE5\xA9\xBB", + "\xD8\xCA" => "\xE5\xA9\xBD", + "\xD8\xCB" => "\xE5\xAA\x8C", + "\xD8\xCC" => "\xE5\xAA\x9C", + "\xD8\xCD" => "\xE5\xAA\x8F", + "\xD8\xCE" => "\xE5\xAA\x93", + "\xD8\xCF" => "\xE5\xAA\x9D", + "\xD8\xD0" => "\xE5\xAF\xAA", + "\xD8\xD1" => "\xE5\xAF\x8D", + "\xD8\xD2" => "\xE5\xAF\x8B", + "\xD8\xD3" => "\xE5\xAF\x94", + "\xD8\xD4" => "\xE5\xAF\x91", + "\xD8\xD5" => "\xE5\xAF\x8A", + "\xD8\xD6" => "\xE5\xAF\x8E", + "\xD8\xD7" => "\xE5\xB0\x8C", + "\xD8\xD8" => "\xE5\xB0\xB0", + "\xD8\xD9" => "\xE5\xB4\xB7", + "\xD8\xDA" => "\xE5\xB5\x83", + "\xD8\xDB" => "\xE5\xB5\xAB", + "\xD8\xDC" => "\xE5\xB5\x81", + "\xD8\xDD" => "\xE5\xB5\x8B", + "\xD8\xDE" => "\xE5\xB4\xBF", + "\xD8\xDF" => "\xE5\xB4\xB5", + "\xD8\xE0" => "\xE5\xB5\x91", + "\xD8\xE1" => "\xE5\xB5\x8E", + "\xD8\xE2" => "\xE5\xB5\x95", + "\xD8\xE3" => "\xE5\xB4\xB3", + "\xD8\xE4" => "\xE5\xB4\xBA", + "\xD8\xE5" => "\xE5\xB5\x92", + "\xD8\xE6" => "\xE5\xB4\xBD", + "\xD8\xE7" => "\xE5\xB4\xB1", + "\xD8\xE8" => "\xE5\xB5\x99", + "\xD8\xE9" => "\xE5\xB5\x82", + "\xD8\xEA" => "\xE5\xB4\xB9", + "\xD8\xEB" => "\xE5\xB5\x89", + "\xD8\xEC" => "\xE5\xB4\xB8", + "\xD8\xED" => "\xE5\xB4\xBC", + "\xD8\xEE" => "\xE5\xB4\xB2", + "\xD8\xEF" => "\xE5\xB4\xB6", + "\xD8\xF0" => "\xE5\xB5\x80", + "\xD8\xF1" => "\xE5\xB5\x85", + "\xD8\xF2" => "\xE5\xB9\x84", + "\xD8\xF3" => "\xE5\xB9\x81", + "\xD8\xF4" => "\xE5\xBD\x98", + "\xD8\xF5" => "\xE5\xBE\xA6", + "\xD8\xF6" => "\xE5\xBE\xA5", + "\xD8\xF7" => "\xE5\xBE\xAB", + "\xD8\xF8" => "\xE6\x83\x89", + "\xD8\xF9" => "\xE6\x82\xB9", + "\xD8\xFA" => "\xE6\x83\x8C", + "\xD8\xFB" => "\xE6\x83\xA2", + "\xD8\xFC" => "\xE6\x83\x8E", + "\xD8\xFD" => "\xE6\x83\x84", + "\xD8\xFE" => "\xE6\x84\x94", + "\xD9\x40" => "\xE6\x83\xB2", + "\xD9\x41" => "\xE6\x84\x8A", + "\xD9\x42" => "\xE6\x84\x96", + "\xD9\x43" => "\xE6\x84\x85", + "\xD9\x44" => "\xE6\x83\xB5", + "\xD9\x45" => "\xE6\x84\x93", + "\xD9\x46" => "\xE6\x83\xB8", + "\xD9\x47" => "\xE6\x83\xBC", + "\xD9\x48" => "\xE6\x83\xBE", + "\xD9\x49" => "\xE6\x83\x81", + "\xD9\x4A" => "\xE6\x84\x83", + "\xD9\x4B" => "\xE6\x84\x98", + "\xD9\x4C" => "\xE6\x84\x9D", + "\xD9\x4D" => "\xE6\x84\x90", + "\xD9\x4E" => "\xE6\x83\xBF", + "\xD9\x4F" => "\xE6\x84\x84", + "\xD9\x50" => "\xE6\x84\x8B", + "\xD9\x51" => "\xE6\x89\x8A", + "\xD9\x52" => "\xE6\x8E\x94", + "\xD9\x53" => "\xE6\x8E\xB1", + "\xD9\x54" => "\xE6\x8E\xB0", + "\xD9\x55" => "\xE6\x8F\x8E", + "\xD9\x56" => "\xE6\x8F\xA5", + "\xD9\x57" => "\xE6\x8F\xA8", + "\xD9\x58" => "\xE6\x8F\xAF", + "\xD9\x59" => "\xE6\x8F\x83", + "\xD9\x5A" => "\xE6\x92\x9D", + "\xD9\x5B" => "\xE6\x8F\xB3", + "\xD9\x5C" => "\xE6\x8F\x8A", + "\xD9\x5D" => "\xE6\x8F\xA0", + "\xD9\x5E" => "\xE6\x8F\xB6", + "\xD9\x5F" => "\xE6\x8F\x95", + "\xD9\x60" => "\xE6\x8F\xB2", + "\xD9\x61" => "\xE6\x8F\xB5", + "\xD9\x62" => "\xE6\x91\xA1", + "\xD9\x63" => "\xE6\x8F\x9F", + "\xD9\x64" => "\xE6\x8E\xBE", + "\xD9\x65" => "\xE6\x8F\x9D", + "\xD9\x66" => "\xE6\x8F\x9C", + "\xD9\x67" => "\xE6\x8F\x84", + "\xD9\x68" => "\xE6\x8F\x98", + "\xD9\x69" => "\xE6\x8F\x93", + "\xD9\x6A" => "\xE6\x8F\x82", + "\xD9\x6B" => "\xE6\x8F\x87", + "\xD9\x6C" => "\xE6\x8F\x8C", + "\xD9\x6D" => "\xE6\x8F\x8B", + "\xD9\x6E" => "\xE6\x8F\x88", + "\xD9\x6F" => "\xE6\x8F\xB0", + "\xD9\x70" => "\xE6\x8F\x97", + "\xD9\x71" => "\xE6\x8F\x99", + "\xD9\x72" => "\xE6\x94\xB2", + "\xD9\x73" => "\xE6\x95\xA7", + "\xD9\x74" => "\xE6\x95\xAA", + "\xD9\x75" => "\xE6\x95\xA4", + "\xD9\x76" => "\xE6\x95\x9C", + "\xD9\x77" => "\xE6\x95\xA8", + "\xD9\x78" => "\xE6\x95\xA5", + "\xD9\x79" => "\xE6\x96\x8C", + "\xD9\x7A" => "\xE6\x96\x9D", + "\xD9\x7B" => "\xE6\x96\x9E", + "\xD9\x7C" => "\xE6\x96\xAE", + "\xD9\x7D" => "\xE6\x97\x90", + "\xD9\x7E" => "\xE6\x97\x92", + "\xD9\xA1" => "\xE6\x99\xBC", + "\xD9\xA2" => "\xE6\x99\xAC", + "\xD9\xA3" => "\xE6\x99\xBB", + "\xD9\xA4" => "\xE6\x9A\x80", + "\xD9\xA5" => "\xE6\x99\xB1", + "\xD9\xA6" => "\xE6\x99\xB9", + "\xD9\xA7" => "\xE6\x99\xAA", + "\xD9\xA8" => "\xE6\x99\xB2", + "\xD9\xA9" => "\xE6\x9C\x81", + "\xD9\xAA" => "\xE6\xA4\x8C", + "\xD9\xAB" => "\xE6\xA3\x93", + "\xD9\xAC" => "\xE6\xA4\x84", + "\xD9\xAD" => "\xE6\xA3\x9C", + "\xD9\xAE" => "\xE6\xA4\xAA", + "\xD9\xAF" => "\xE6\xA3\xAC", + "\xD9\xB0" => "\xE6\xA3\xAA", + "\xD9\xB1" => "\xE6\xA3\xB1", + "\xD9\xB2" => "\xE6\xA4\x8F", + "\xD9\xB3" => "\xE6\xA3\x96", + "\xD9\xB4" => "\xE6\xA3\xB7", + "\xD9\xB5" => "\xE6\xA3\xAB", + "\xD9\xB6" => "\xE6\xA3\xA4", + "\xD9\xB7" => "\xE6\xA3\xB6", + "\xD9\xB8" => "\xE6\xA4\x93", + "\xD9\xB9" => "\xE6\xA4\x90", + "\xD9\xBA" => "\xE6\xA3\xB3", + "\xD9\xBB" => "\xE6\xA3\xA1", + "\xD9\xBC" => "\xE6\xA4\x87", + "\xD9\xBD" => "\xE6\xA3\x8C", + "\xD9\xBE" => "\xE6\xA4\x88", + "\xD9\xBF" => "\xE6\xA5\xB0", + "\xD9\xC0" => "\xE6\xA2\xB4", + "\xD9\xC1" => "\xE6\xA4\x91", + "\xD9\xC2" => "\xE6\xA3\xAF", + "\xD9\xC3" => "\xE6\xA3\x86", + "\xD9\xC4" => "\xE6\xA4\x94", + "\xD9\xC5" => "\xE6\xA3\xB8", + "\xD9\xC6" => "\xE6\xA3\x90", + "\xD9\xC7" => "\xE6\xA3\xBD", + "\xD9\xC8" => "\xE6\xA3\xBC", + "\xD9\xC9" => "\xE6\xA3\xA8", + "\xD9\xCA" => "\xE6\xA4\x8B", + "\xD9\xCB" => "\xE6\xA4\x8A", + "\xD9\xCC" => "\xE6\xA4\x97", + "\xD9\xCD" => "\xE6\xA3\x8E", + "\xD9\xCE" => "\xE6\xA3\x88", + "\xD9\xCF" => "\xE6\xA3\x9D", + "\xD9\xD0" => "\xE6\xA3\x9E", + "\xD9\xD1" => "\xE6\xA3\xA6", + "\xD9\xD2" => "\xE6\xA3\xB4", + "\xD9\xD3" => "\xE6\xA3\x91", + "\xD9\xD4" => "\xE6\xA4\x86", + "\xD9\xD5" => "\xE6\xA3\x94", + "\xD9\xD6" => "\xE6\xA3\xA9", + "\xD9\xD7" => "\xE6\xA4\x95", + "\xD9\xD8" => "\xE6\xA4\xA5", + "\xD9\xD9" => "\xE6\xA3\x87", + "\xD9\xDA" => "\xE6\xAC\xB9", + "\xD9\xDB" => "\xE6\xAC\xBB", + "\xD9\xDC" => "\xE6\xAC\xBF", + "\xD9\xDD" => "\xE6\xAC\xBC", + "\xD9\xDE" => "\xE6\xAE\x94", + "\xD9\xDF" => "\xE6\xAE\x97", + "\xD9\xE0" => "\xE6\xAE\x99", + "\xD9\xE1" => "\xE6\xAE\x95", + "\xD9\xE2" => "\xE6\xAE\xBD", + "\xD9\xE3" => "\xE6\xAF\xB0", + "\xD9\xE4" => "\xE6\xAF\xB2", + "\xD9\xE5" => "\xE6\xAF\xB3", + "\xD9\xE6" => "\xE6\xB0\xB0", + "\xD9\xE7" => "\xE6\xB7\xBC", + "\xD9\xE8" => "\xE6\xB9\x86", + "\xD9\xE9" => "\xE6\xB9\x87", + "\xD9\xEA" => "\xE6\xB8\x9F", + "\xD9\xEB" => "\xE6\xB9\x89", + "\xD9\xEC" => "\xE6\xBA\x88", + "\xD9\xED" => "\xE6\xB8\xBC", + "\xD9\xEE" => "\xE6\xB8\xBD", + "\xD9\xEF" => "\xE6\xB9\x85", + "\xD9\xF0" => "\xE6\xB9\xA2", + "\xD9\xF1" => "\xE6\xB8\xAB", + "\xD9\xF2" => "\xE6\xB8\xBF", + "\xD9\xF3" => "\xE6\xB9\x81", + "\xD9\xF4" => "\xE6\xB9\x9D", + "\xD9\xF5" => "\xE6\xB9\xB3", + "\xD9\xF6" => "\xE6\xB8\x9C", + "\xD9\xF7" => "\xE6\xB8\xB3", + "\xD9\xF8" => "\xE6\xB9\x8B", + "\xD9\xF9" => "\xE6\xB9\x80", + "\xD9\xFA" => "\xE6\xB9\x91", + "\xD9\xFB" => "\xE6\xB8\xBB", + "\xD9\xFC" => "\xE6\xB8\x83", + "\xD9\xFD" => "\xE6\xB8\xAE", + "\xD9\xFE" => "\xE6\xB9\x9E", + "\xDA\x40" => "\xE6\xB9\xA8", + "\xDA\x41" => "\xE6\xB9\x9C", + "\xDA\x42" => "\xE6\xB9\xA1", + "\xDA\x43" => "\xE6\xB8\xB1", + "\xDA\x44" => "\xE6\xB8\xA8", + "\xDA\x45" => "\xE6\xB9\xA0", + "\xDA\x46" => "\xE6\xB9\xB1", + "\xDA\x47" => "\xE6\xB9\xAB", + "\xDA\x48" => "\xE6\xB8\xB9", + "\xDA\x49" => "\xE6\xB8\xA2", + "\xDA\x4A" => "\xE6\xB8\xB0", + "\xDA\x4B" => "\xE6\xB9\x93", + "\xDA\x4C" => "\xE6\xB9\xA5", + "\xDA\x4D" => "\xE6\xB8\xA7", + "\xDA\x4E" => "\xE6\xB9\xB8", + "\xDA\x4F" => "\xE6\xB9\xA4", + "\xDA\x50" => "\xE6\xB9\xB7", + "\xDA\x51" => "\xE6\xB9\x95", + "\xDA\x52" => "\xE6\xB9\xB9", + "\xDA\x53" => "\xE6\xB9\x92", + "\xDA\x54" => "\xE6\xB9\xA6", + "\xDA\x55" => "\xE6\xB8\xB5", + "\xDA\x56" => "\xE6\xB8\xB6", + "\xDA\x57" => "\xE6\xB9\x9A", + "\xDA\x58" => "\xE7\x84\xA0", + "\xDA\x59" => "\xE7\x84\x9E", + "\xDA\x5A" => "\xE7\x84\xAF", + "\xDA\x5B" => "\xE7\x83\xBB", + "\xDA\x5C" => "\xE7\x84\xAE", + "\xDA\x5D" => "\xE7\x84\xB1", + "\xDA\x5E" => "\xE7\x84\xA3", + "\xDA\x5F" => "\xE7\x84\xA5", + "\xDA\x60" => "\xE7\x84\xA2", + "\xDA\x61" => "\xE7\x84\xB2", + "\xDA\x62" => "\xE7\x84\x9F", + "\xDA\x63" => "\xE7\x84\xA8", + "\xDA\x64" => "\xE7\x84\xBA", + "\xDA\x65" => "\xE7\x84\x9B", + "\xDA\x66" => "\xE7\x89\x8B", + "\xDA\x67" => "\xE7\x89\x9A", + "\xDA\x68" => "\xE7\x8A\x88", + "\xDA\x69" => "\xE7\x8A\x89", + "\xDA\x6A" => "\xE7\x8A\x86", + "\xDA\x6B" => "\xE7\x8A\x85", + "\xDA\x6C" => "\xE7\x8A\x8B", + "\xDA\x6D" => "\xE7\x8C\x92", + "\xDA\x6E" => "\xE7\x8C\x8B", + "\xDA\x6F" => "\xE7\x8C\xB0", + "\xDA\x70" => "\xE7\x8C\xA2", + "\xDA\x71" => "\xE7\x8C\xB1", + "\xDA\x72" => "\xE7\x8C\xB3", + "\xDA\x73" => "\xE7\x8C\xA7", + "\xDA\x74" => "\xE7\x8C\xB2", + "\xDA\x75" => "\xE7\x8C\xAD", + "\xDA\x76" => "\xE7\x8C\xA6", + "\xDA\x77" => "\xE7\x8C\xA3", + "\xDA\x78" => "\xE7\x8C\xB5", + "\xDA\x79" => "\xE7\x8C\x8C", + "\xDA\x7A" => "\xE7\x90\xAE", + "\xDA\x7B" => "\xE7\x90\xAC", + "\xDA\x7C" => "\xE7\x90\xB0", + "\xDA\x7D" => "\xE7\x90\xAB", + "\xDA\x7E" => "\xE7\x90\x96", + "\xDA\xA1" => "\xE7\x90\x9A", + "\xDA\xA2" => "\xE7\x90\xA1", + "\xDA\xA3" => "\xE7\x90\xAD", + "\xDA\xA4" => "\xE7\x90\xB1", + "\xDA\xA5" => "\xE7\x90\xA4", + "\xDA\xA6" => "\xE7\x90\xA3", + "\xDA\xA7" => "\xE7\x90\x9D", + "\xDA\xA8" => "\xE7\x90\xA9", + "\xDA\xA9" => "\xE7\x90\xA0", + "\xDA\xAA" => "\xE7\x90\xB2", + "\xDA\xAB" => "\xE7\x93\xBB", + "\xDA\xAC" => "\xE7\x94\xAF", + "\xDA\xAD" => "\xE7\x95\xAF", + "\xDA\xAE" => "\xE7\x95\xAC", + "\xDA\xAF" => "\xE7\x97\xA7", + "\xDA\xB0" => "\xE7\x97\x9A", + "\xDA\xB1" => "\xE7\x97\xA1", + "\xDA\xB2" => "\xE7\x97\xA6", + "\xDA\xB3" => "\xE7\x97\x9D", + "\xDA\xB4" => "\xE7\x97\x9F", + "\xDA\xB5" => "\xE7\x97\xA4", + "\xDA\xB6" => "\xE7\x97\x97", + "\xDA\xB7" => "\xE7\x9A\x95", + "\xDA\xB8" => "\xE7\x9A\x92", + "\xDA\xB9" => "\xE7\x9B\x9A", + "\xDA\xBA" => "\xE7\x9D\x86", + "\xDA\xBB" => "\xE7\x9D\x87", + "\xDA\xBC" => "\xE7\x9D\x84", + "\xDA\xBD" => "\xE7\x9D\x8D", + "\xDA\xBE" => "\xE7\x9D\x85", + "\xDA\xBF" => "\xE7\x9D\x8A", + "\xDA\xC0" => "\xE7\x9D\x8E", + "\xDA\xC1" => "\xE7\x9D\x8B", + "\xDA\xC2" => "\xE7\x9D\x8C", + "\xDA\xC3" => "\xE7\x9F\x9E", + "\xDA\xC4" => "\xE7\x9F\xAC", + "\xDA\xC5" => "\xE7\xA1\xA0", + "\xDA\xC6" => "\xE7\xA1\xA4", + "\xDA\xC7" => "\xE7\xA1\xA5", + "\xDA\xC8" => "\xE7\xA1\x9C", + "\xDA\xC9" => "\xE7\xA1\xAD", + "\xDA\xCA" => "\xE7\xA1\xB1", + "\xDA\xCB" => "\xE7\xA1\xAA", + "\xDA\xCC" => "\xE7\xA1\xAE", + "\xDA\xCD" => "\xE7\xA1\xB0", + "\xDA\xCE" => "\xE7\xA1\xA9", + "\xDA\xCF" => "\xE7\xA1\xA8", + "\xDA\xD0" => "\xE7\xA1\x9E", + "\xDA\xD1" => "\xE7\xA1\xA2", + "\xDA\xD2" => "\xE7\xA5\xB4", + "\xDA\xD3" => "\xE7\xA5\xB3", + "\xDA\xD4" => "\xE7\xA5\xB2", + "\xDA\xD5" => "\xE7\xA5\xB0", + "\xDA\xD6" => "\xE7\xA8\x82", + "\xDA\xD7" => "\xE7\xA8\x8A", + "\xDA\xD8" => "\xE7\xA8\x83", + "\xDA\xD9" => "\xE7\xA8\x8C", + "\xDA\xDA" => "\xE7\xA8\x84", + "\xDA\xDB" => "\xE7\xAA\x99", + "\xDA\xDC" => "\xE7\xAB\xA6", + "\xDA\xDD" => "\xE7\xAB\xA4", + "\xDA\xDE" => "\xE7\xAD\x8A", + "\xDA\xDF" => "\xE7\xAC\xBB", + "\xDA\xE0" => "\xE7\xAD\x84", + "\xDA\xE1" => "\xE7\xAD\x88", + "\xDA\xE2" => "\xE7\xAD\x8C", + "\xDA\xE3" => "\xE7\xAD\x8E", + "\xDA\xE4" => "\xE7\xAD\x80", + "\xDA\xE5" => "\xE7\xAD\x98", + "\xDA\xE6" => "\xE7\xAD\x85", + "\xDA\xE7" => "\xE7\xB2\xA2", + "\xDA\xE8" => "\xE7\xB2\x9E", + "\xDA\xE9" => "\xE7\xB2\xA8", + "\xDA\xEA" => "\xE7\xB2\xA1", + "\xDA\xEB" => "\xE7\xB5\x98", + "\xDA\xEC" => "\xE7\xB5\xAF", + "\xDA\xED" => "\xE7\xB5\xA3", + "\xDA\xEE" => "\xE7\xB5\x93", + "\xDA\xEF" => "\xE7\xB5\x96", + "\xDA\xF0" => "\xE7\xB5\xA7", + "\xDA\xF1" => "\xE7\xB5\xAA", + "\xDA\xF2" => "\xE7\xB5\x8F", + "\xDA\xF3" => "\xE7\xB5\xAD", + "\xDA\xF4" => "\xE7\xB5\x9C", + "\xDA\xF5" => "\xE7\xB5\xAB", + "\xDA\xF6" => "\xE7\xB5\x92", + "\xDA\xF7" => "\xE7\xB5\x94", + "\xDA\xF8" => "\xE7\xB5\xA9", + "\xDA\xF9" => "\xE7\xB5\x91", + "\xDA\xFA" => "\xE7\xB5\x9F", + "\xDA\xFB" => "\xE7\xB5\x8E", + "\xDA\xFC" => "\xE7\xBC\xBE", + "\xDA\xFD" => "\xE7\xBC\xBF", + "\xDA\xFE" => "\xE7\xBD\xA5", + "\xDB\x40" => "\xE7\xBD\xA6", + "\xDB\x41" => "\xE7\xBE\xA2", + "\xDB\x42" => "\xE7\xBE\xA0", + "\xDB\x43" => "\xE7\xBE\xA1", + "\xDB\x44" => "\xE7\xBF\x97", + "\xDB\x45" => "\xE8\x81\x91", + "\xDB\x46" => "\xE8\x81\x8F", + "\xDB\x47" => "\xE8\x81\x90", + "\xDB\x48" => "\xE8\x83\xBE", + "\xDB\x49" => "\xE8\x83\x94", + "\xDB\x4A" => "\xE8\x85\x83", + "\xDB\x4B" => "\xE8\x85\x8A", + "\xDB\x4C" => "\xE8\x85\x92", + "\xDB\x4D" => "\xE8\x85\x8F", + "\xDB\x4E" => "\xE8\x85\x87", + "\xDB\x4F" => "\xE8\x84\xBD", + "\xDB\x50" => "\xE8\x85\x8D", + "\xDB\x51" => "\xE8\x84\xBA", + "\xDB\x52" => "\xE8\x87\xA6", + "\xDB\x53" => "\xE8\x87\xAE", + "\xDB\x54" => "\xE8\x87\xB7", + "\xDB\x55" => "\xE8\x87\xB8", + "\xDB\x56" => "\xE8\x87\xB9", + "\xDB\x57" => "\xE8\x88\x84", + "\xDB\x58" => "\xE8\x88\xBC", + "\xDB\x59" => "\xE8\x88\xBD", + "\xDB\x5A" => "\xE8\x88\xBF", + "\xDB\x5B" => "\xE8\x89\xB5", + "\xDB\x5C" => "\xE8\x8C\xBB", + "\xDB\x5D" => "\xE8\x8F\x8F", + "\xDB\x5E" => "\xE8\x8F\xB9", + "\xDB\x5F" => "\xE8\x90\xA3", + "\xDB\x60" => "\xE8\x8F\x80", + "\xDB\x61" => "\xE8\x8F\xA8", + "\xDB\x62" => "\xE8\x90\x92", + "\xDB\x63" => "\xE8\x8F\xA7", + "\xDB\x64" => "\xE8\x8F\xA4", + "\xDB\x65" => "\xE8\x8F\xBC", + "\xDB\x66" => "\xE8\x8F\xB6", + "\xDB\x67" => "\xE8\x90\x90", + "\xDB\x68" => "\xE8\x8F\x86", + "\xDB\x69" => "\xE8\x8F\x88", + "\xDB\x6A" => "\xE8\x8F\xAB", + "\xDB\x6B" => "\xE8\x8F\xA3", + "\xDB\x6C" => "\xE8\x8E\xBF", + "\xDB\x6D" => "\xE8\x90\x81", + "\xDB\x6E" => "\xE8\x8F\x9D", + "\xDB\x6F" => "\xE8\x8F\xA5", + "\xDB\x70" => "\xE8\x8F\x98", + "\xDB\x71" => "\xE8\x8F\xBF", + "\xDB\x72" => "\xE8\x8F\xA1", + "\xDB\x73" => "\xE8\x8F\x8B", + "\xDB\x74" => "\xE8\x8F\x8E", + "\xDB\x75" => "\xE8\x8F\x96", + "\xDB\x76" => "\xE8\x8F\xB5", + "\xDB\x77" => "\xE8\x8F\x89", + "\xDB\x78" => "\xE8\x90\x89", + "\xDB\x79" => "\xE8\x90\x8F", + "\xDB\x7A" => "\xE8\x8F\x9E", + "\xDB\x7B" => "\xE8\x90\x91", + "\xDB\x7C" => "\xE8\x90\x86", + "\xDB\x7D" => "\xE8\x8F\x82", + "\xDB\x7E" => "\xE8\x8F\xB3", + "\xDB\xA1" => "\xE8\x8F\x95", + "\xDB\xA2" => "\xE8\x8F\xBA", + "\xDB\xA3" => "\xE8\x8F\x87", + "\xDB\xA4" => "\xE8\x8F\x91", + "\xDB\xA5" => "\xE8\x8F\xAA", + "\xDB\xA6" => "\xE8\x90\x93", + "\xDB\xA7" => "\xE8\x8F\x83", + "\xDB\xA8" => "\xE8\x8F\xAC", + "\xDB\xA9" => "\xE8\x8F\xAE", + "\xDB\xAA" => "\xE8\x8F\x84", + "\xDB\xAB" => "\xE8\x8F\xBB", + "\xDB\xAC" => "\xE8\x8F\x97", + "\xDB\xAD" => "\xE8\x8F\xA2", + "\xDB\xAE" => "\xE8\x90\x9B", + "\xDB\xAF" => "\xE8\x8F\x9B", + "\xDB\xB0" => "\xE8\x8F\xBE", + "\xDB\xB1" => "\xE8\x9B\x98", + "\xDB\xB2" => "\xE8\x9B\xA2", + "\xDB\xB3" => "\xE8\x9B\xA6", + "\xDB\xB4" => "\xE8\x9B\x93", + "\xDB\xB5" => "\xE8\x9B\xA3", + "\xDB\xB6" => "\xE8\x9B\x9A", + "\xDB\xB7" => "\xE8\x9B\xAA", + "\xDB\xB8" => "\xE8\x9B\x9D", + "\xDB\xB9" => "\xE8\x9B\xAB", + "\xDB\xBA" => "\xE8\x9B\x9C", + "\xDB\xBB" => "\xE8\x9B\xAC", + "\xDB\xBC" => "\xE8\x9B\xA9", + "\xDB\xBD" => "\xE8\x9B\x97", + "\xDB\xBE" => "\xE8\x9B\xA8", + "\xDB\xBF" => "\xE8\x9B\x91", + "\xDB\xC0" => "\xE8\xA1\x88", + "\xDB\xC1" => "\xE8\xA1\x96", + "\xDB\xC2" => "\xE8\xA1\x95", + "\xDB\xC3" => "\xE8\xA2\xBA", + "\xDB\xC4" => "\xE8\xA3\x97", + "\xDB\xC5" => "\xE8\xA2\xB9", + "\xDB\xC6" => "\xE8\xA2\xB8", + "\xDB\xC7" => "\xE8\xA3\x80", + "\xDB\xC8" => "\xE8\xA2\xBE", + "\xDB\xC9" => "\xE8\xA2\xB6", + "\xDB\xCA" => "\xE8\xA2\xBC", + "\xDB\xCB" => "\xE8\xA2\xB7", + "\xDB\xCC" => "\xE8\xA2\xBD", + "\xDB\xCD" => "\xE8\xA2\xB2", + "\xDB\xCE" => "\xE8\xA4\x81", + "\xDB\xCF" => "\xE8\xA3\x89", + "\xDB\xD0" => "\xE8\xA6\x95", + "\xDB\xD1" => "\xE8\xA6\x98", + "\xDB\xD2" => "\xE8\xA6\x97", + "\xDB\xD3" => "\xE8\xA7\x9D", + "\xDB\xD4" => "\xE8\xA7\x9A", + "\xDB\xD5" => "\xE8\xA7\x9B", + "\xDB\xD6" => "\xE8\xA9\x8E", + "\xDB\xD7" => "\xE8\xA9\x8D", + "\xDB\xD8" => "\xE8\xA8\xB9", + "\xDB\xD9" => "\xE8\xA9\x99", + "\xDB\xDA" => "\xE8\xA9\x80", + "\xDB\xDB" => "\xE8\xA9\x97", + "\xDB\xDC" => "\xE8\xA9\x98", + "\xDB\xDD" => "\xE8\xA9\x84", + "\xDB\xDE" => "\xE8\xA9\x85", + "\xDB\xDF" => "\xE8\xA9\x92", + "\xDB\xE0" => "\xE8\xA9\x88", + "\xDB\xE1" => "\xE8\xA9\x91", + "\xDB\xE2" => "\xE8\xA9\x8A", + "\xDB\xE3" => "\xE8\xA9\x8C", + "\xDB\xE4" => "\xE8\xA9\x8F", + "\xDB\xE5" => "\xE8\xB1\x9F", + "\xDB\xE6" => "\xE8\xB2\x81", + "\xDB\xE7" => "\xE8\xB2\x80", + "\xDB\xE8" => "\xE8\xB2\xBA", + "\xDB\xE9" => "\xE8\xB2\xBE", + "\xDB\xEA" => "\xE8\xB2\xB0", + "\xDB\xEB" => "\xE8\xB2\xB9", + "\xDB\xEC" => "\xE8\xB2\xB5", + "\xDB\xED" => "\xE8\xB6\x84", + "\xDB\xEE" => "\xE8\xB6\x80", + "\xDB\xEF" => "\xE8\xB6\x89", + "\xDB\xF0" => "\xE8\xB7\x98", + "\xDB\xF1" => "\xE8\xB7\x93", + "\xDB\xF2" => "\xE8\xB7\x8D", + "\xDB\xF3" => "\xE8\xB7\x87", + "\xDB\xF4" => "\xE8\xB7\x96", + "\xDB\xF5" => "\xE8\xB7\x9C", + "\xDB\xF6" => "\xE8\xB7\x8F", + "\xDB\xF7" => "\xE8\xB7\x95", + "\xDB\xF8" => "\xE8\xB7\x99", + "\xDB\xF9" => "\xE8\xB7\x88", + "\xDB\xFA" => "\xE8\xB7\x97", + "\xDB\xFB" => "\xE8\xB7\x85", + "\xDB\xFC" => "\xE8\xBB\xAF", + "\xDB\xFD" => "\xE8\xBB\xB7", + "\xDB\xFE" => "\xE8\xBB\xBA", + "\xDC\x40" => "\xE8\xBB\xB9", + "\xDC\x41" => "\xE8\xBB\xA6", + "\xDC\x42" => "\xE8\xBB\xAE", + "\xDC\x43" => "\xE8\xBB\xA5", + "\xDC\x44" => "\xE8\xBB\xB5", + "\xDC\x45" => "\xE8\xBB\xA7", + "\xDC\x46" => "\xE8\xBB\xA8", + "\xDC\x47" => "\xE8\xBB\xB6", + "\xDC\x48" => "\xE8\xBB\xAB", + "\xDC\x49" => "\xE8\xBB\xB1", + "\xDC\x4A" => "\xE8\xBB\xAC", + "\xDC\x4B" => "\xE8\xBB\xB4", + "\xDC\x4C" => "\xE8\xBB\xA9", + "\xDC\x4D" => "\xE9\x80\xAD", + "\xDC\x4E" => "\xE9\x80\xB4", + "\xDC\x4F" => "\xE9\x80\xAF", + "\xDC\x50" => "\xE9\x84\x86", + "\xDC\x51" => "\xE9\x84\xAC", + "\xDC\x52" => "\xE9\x84\x84", + "\xDC\x53" => "\xE9\x83\xBF", + "\xDC\x54" => "\xE9\x83\xBC", + "\xDC\x55" => "\xE9\x84\x88", + "\xDC\x56" => "\xE9\x83\xB9", + "\xDC\x57" => "\xE9\x83\xBB", + "\xDC\x58" => "\xE9\x84\x81", + "\xDC\x59" => "\xE9\x84\x80", + "\xDC\x5A" => "\xE9\x84\x87", + "\xDC\x5B" => "\xE9\x84\x85", + "\xDC\x5C" => "\xE9\x84\x83", + "\xDC\x5D" => "\xE9\x85\xA1", + "\xDC\x5E" => "\xE9\x85\xA4", + "\xDC\x5F" => "\xE9\x85\x9F", + "\xDC\x60" => "\xE9\x85\xA2", + "\xDC\x61" => "\xE9\x85\xA0", + "\xDC\x62" => "\xE9\x88\x81", + "\xDC\x63" => "\xE9\x88\x8A", + "\xDC\x64" => "\xE9\x88\xA5", + "\xDC\x65" => "\xE9\x88\x83", + "\xDC\x66" => "\xE9\x88\x9A", + "\xDC\x67" => "\xE9\x88\xA6", + "\xDC\x68" => "\xE9\x88\x8F", + "\xDC\x69" => "\xE9\x88\x8C", + "\xDC\x6A" => "\xE9\x88\x80", + "\xDC\x6B" => "\xE9\x88\x92", + "\xDC\x6C" => "\xE9\x87\xBF", + "\xDC\x6D" => "\xE9\x87\xBD", + "\xDC\x6E" => "\xE9\x88\x86", + "\xDC\x6F" => "\xE9\x88\x84", + "\xDC\x70" => "\xE9\x88\xA7", + "\xDC\x71" => "\xE9\x88\x82", + "\xDC\x72" => "\xE9\x88\x9C", + "\xDC\x73" => "\xE9\x88\xA4", + "\xDC\x74" => "\xE9\x88\x99", + "\xDC\x75" => "\xE9\x88\x97", + "\xDC\x76" => "\xE9\x88\x85", + "\xDC\x77" => "\xE9\x88\x96", + "\xDC\x78" => "\xE9\x95\xBB", + "\xDC\x79" => "\xE9\x96\x8D", + "\xDC\x7A" => "\xE9\x96\x8C", + "\xDC\x7B" => "\xE9\x96\x90", + "\xDC\x7C" => "\xE9\x9A\x87", + "\xDC\x7D" => "\xE9\x99\xBE", + "\xDC\x7E" => "\xE9\x9A\x88", + "\xDC\xA1" => "\xE9\x9A\x89", + "\xDC\xA2" => "\xE9\x9A\x83", + "\xDC\xA3" => "\xE9\x9A\x80", + "\xDC\xA4" => "\xE9\x9B\x82", + "\xDC\xA5" => "\xE9\x9B\x88", + "\xDC\xA6" => "\xE9\x9B\x83", + "\xDC\xA7" => "\xE9\x9B\xB1", + "\xDC\xA8" => "\xE9\x9B\xB0", + "\xDC\xA9" => "\xE9\x9D\xAC", + "\xDC\xAA" => "\xE9\x9D\xB0", + "\xDC\xAB" => "\xE9\x9D\xAE", + "\xDC\xAC" => "\xE9\xA0\x87", + "\xDC\xAD" => "\xE9\xA2\xA9", + "\xDC\xAE" => "\xE9\xA3\xAB", + "\xDC\xAF" => "\xE9\xB3\xA6", + "\xDC\xB0" => "\xE9\xBB\xB9", + "\xDC\xB1" => "\xE4\xBA\x83", + "\xDC\xB2" => "\xE4\xBA\x84", + "\xDC\xB3" => "\xE4\xBA\xB6", + "\xDC\xB4" => "\xE5\x82\xBD", + "\xDC\xB5" => "\xE5\x82\xBF", + "\xDC\xB6" => "\xE5\x83\x86", + "\xDC\xB7" => "\xE5\x82\xAE", + "\xDC\xB8" => "\xE5\x83\x84", + "\xDC\xB9" => "\xE5\x83\x8A", + "\xDC\xBA" => "\xE5\x82\xB4", + "\xDC\xBB" => "\xE5\x83\x88", + "\xDC\xBC" => "\xE5\x83\x82", + "\xDC\xBD" => "\xE5\x82\xB0", + "\xDC\xBE" => "\xE5\x83\x81", + "\xDC\xBF" => "\xE5\x82\xBA", + "\xDC\xC0" => "\xE5\x82\xB1", + "\xDC\xC1" => "\xE5\x83\x8B", + "\xDC\xC2" => "\xE5\x83\x89", + "\xDC\xC3" => "\xE5\x82\xB6", + "\xDC\xC4" => "\xE5\x82\xB8", + "\xDC\xC5" => "\xE5\x87\x97", + "\xDC\xC6" => "\xE5\x89\xBA", + "\xDC\xC7" => "\xE5\x89\xB8", + "\xDC\xC8" => "\xE5\x89\xBB", + "\xDC\xC9" => "\xE5\x89\xBC", + "\xDC\xCA" => "\xE5\x97\x83", + "\xDC\xCB" => "\xE5\x97\x9B", + "\xDC\xCC" => "\xE5\x97\x8C", + "\xDC\xCD" => "\xE5\x97\x90", + "\xDC\xCE" => "\xE5\x97\x8B", + "\xDC\xCF" => "\xE5\x97\x8A", + "\xDC\xD0" => "\xE5\x97\x9D", + "\xDC\xD1" => "\xE5\x97\x80", + "\xDC\xD2" => "\xE5\x97\x94", + "\xDC\xD3" => "\xE5\x97\x84", + "\xDC\xD4" => "\xE5\x97\xA9", + "\xDC\xD5" => "\xE5\x96\xBF", + "\xDC\xD6" => "\xE5\x97\x92", + "\xDC\xD7" => "\xE5\x96\x8D", + "\xDC\xD8" => "\xE5\x97\x8F", + "\xDC\xD9" => "\xE5\x97\x95", + "\xDC\xDA" => "\xE5\x97\xA2", + "\xDC\xDB" => "\xE5\x97\x96", + "\xDC\xDC" => "\xE5\x97\x88", + "\xDC\xDD" => "\xE5\x97\xB2", + "\xDC\xDE" => "\xE5\x97\x8D", + "\xDC\xDF" => "\xE5\x97\x99", + "\xDC\xE0" => "\xE5\x97\x82", + "\xDC\xE1" => "\xE5\x9C\x94", + "\xDC\xE2" => "\xE5\xA1\x93", + "\xDC\xE3" => "\xE5\xA1\xA8", + "\xDC\xE4" => "\xE5\xA1\xA4", + "\xDC\xE5" => "\xE5\xA1\x8F", + "\xDC\xE6" => "\xE5\xA1\x8D", + "\xDC\xE7" => "\xE5\xA1\x89", + "\xDC\xE8" => "\xE5\xA1\xAF", + "\xDC\xE9" => "\xE5\xA1\x95", + "\xDC\xEA" => "\xE5\xA1\x8E", + "\xDC\xEB" => "\xE5\xA1\x9D", + "\xDC\xEC" => "\xE5\xA1\x99", + "\xDC\xED" => "\xE5\xA1\xA5", + "\xDC\xEE" => "\xE5\xA1\x9B", + "\xDC\xEF" => "\xE5\xA0\xBD", + "\xDC\xF0" => "\xE5\xA1\xA3", + "\xDC\xF1" => "\xE5\xA1\xB1", + "\xDC\xF2" => "\xE5\xA3\xBC", + "\xDC\xF3" => "\xE5\xAB\x87", + "\xDC\xF4" => "\xE5\xAB\x84", + "\xDC\xF5" => "\xE5\xAB\x8B", + "\xDC\xF6" => "\xE5\xAA\xBA", + "\xDC\xF7" => "\xE5\xAA\xB8", + "\xDC\xF8" => "\xE5\xAA\xB1", + "\xDC\xF9" => "\xE5\xAA\xB5", + "\xDC\xFA" => "\xE5\xAA\xB0", + "\xDC\xFB" => "\xE5\xAA\xBF", + "\xDC\xFC" => "\xE5\xAB\x88", + "\xDC\xFD" => "\xE5\xAA\xBB", + "\xDC\xFE" => "\xE5\xAB\x86", + "\xDD\x40" => "\xE5\xAA\xB7", + "\xDD\x41" => "\xE5\xAB\x80", + "\xDD\x42" => "\xE5\xAB\x8A", + "\xDD\x43" => "\xE5\xAA\xB4", + "\xDD\x44" => "\xE5\xAA\xB6", + "\xDD\x45" => "\xE5\xAB\x8D", + "\xDD\x46" => "\xE5\xAA\xB9", + "\xDD\x47" => "\xE5\xAA\x90", + "\xDD\x48" => "\xE5\xAF\x96", + "\xDD\x49" => "\xE5\xAF\x98", + "\xDD\x4A" => "\xE5\xAF\x99", + "\xDD\x4B" => "\xE5\xB0\x9F", + "\xDD\x4C" => "\xE5\xB0\xB3", + "\xDD\x4D" => "\xE5\xB5\xB1", + "\xDD\x4E" => "\xE5\xB5\xA3", + "\xDD\x4F" => "\xE5\xB5\x8A", + "\xDD\x50" => "\xE5\xB5\xA5", + "\xDD\x51" => "\xE5\xB5\xB2", + "\xDD\x52" => "\xE5\xB5\xAC", + "\xDD\x53" => "\xE5\xB5\x9E", + "\xDD\x54" => "\xE5\xB5\xA8", + "\xDD\x55" => "\xE5\xB5\xA7", + "\xDD\x56" => "\xE5\xB5\xA2", + "\xDD\x57" => "\xE5\xB7\xB0", + "\xDD\x58" => "\xE5\xB9\x8F", + "\xDD\x59" => "\xE5\xB9\x8E", + "\xDD\x5A" => "\xE5\xB9\x8A", + "\xDD\x5B" => "\xE5\xB9\x8D", + "\xDD\x5C" => "\xE5\xB9\x8B", + "\xDD\x5D" => "\xE5\xBB\x85", + "\xDD\x5E" => "\xE5\xBB\x8C", + "\xDD\x5F" => "\xE5\xBB\x86", + "\xDD\x60" => "\xE5\xBB\x8B", + "\xDD\x61" => "\xE5\xBB\x87", + "\xDD\x62" => "\xE5\xBD\x80", + "\xDD\x63" => "\xE5\xBE\xAF", + "\xDD\x64" => "\xE5\xBE\xAD", + "\xDD\x65" => "\xE6\x83\xB7", + "\xDD\x66" => "\xE6\x85\x89", + "\xDD\x67" => "\xE6\x85\x8A", + "\xDD\x68" => "\xE6\x84\xAB", + "\xDD\x69" => "\xE6\x85\x85", + "\xDD\x6A" => "\xE6\x84\xB6", + "\xDD\x6B" => "\xE6\x84\xB2", + "\xDD\x6C" => "\xE6\x84\xAE", + "\xDD\x6D" => "\xE6\x85\x86", + "\xDD\x6E" => "\xE6\x84\xAF", + "\xDD\x6F" => "\xE6\x85\x8F", + "\xDD\x70" => "\xE6\x84\xA9", + "\xDD\x71" => "\xE6\x85\x80", + "\xDD\x72" => "\xE6\x88\xA0", + "\xDD\x73" => "\xE9\x85\xA8", + "\xDD\x74" => "\xE6\x88\xA3", + "\xDD\x75" => "\xE6\x88\xA5", + "\xDD\x76" => "\xE6\x88\xA4", + "\xDD\x77" => "\xE6\x8F\x85", + "\xDD\x78" => "\xE6\x8F\xB1", + "\xDD\x79" => "\xE6\x8F\xAB", + "\xDD\x7A" => "\xE6\x90\x90", + "\xDD\x7B" => "\xE6\x90\x92", + "\xDD\x7C" => "\xE6\x90\x89", + "\xDD\x7D" => "\xE6\x90\xA0", + "\xDD\x7E" => "\xE6\x90\xA4", + "\xDD\xA1" => "\xE6\x90\xB3", + "\xDD\xA2" => "\xE6\x91\x83", + "\xDD\xA3" => "\xE6\x90\x9F", + "\xDD\xA4" => "\xE6\x90\x95", + "\xDD\xA5" => "\xE6\x90\x98", + "\xDD\xA6" => "\xE6\x90\xB9", + "\xDD\xA7" => "\xE6\x90\xB7", + "\xDD\xA8" => "\xE6\x90\xA2", + "\xDD\xA9" => "\xE6\x90\xA3", + "\xDD\xAA" => "\xE6\x90\x8C", + "\xDD\xAB" => "\xE6\x90\xA6", + "\xDD\xAC" => "\xE6\x90\xB0", + "\xDD\xAD" => "\xE6\x90\xA8", + "\xDD\xAE" => "\xE6\x91\x81", + "\xDD\xAF" => "\xE6\x90\xB5", + "\xDD\xB0" => "\xE6\x90\xAF", + "\xDD\xB1" => "\xE6\x90\x8A", + "\xDD\xB2" => "\xE6\x90\x9A", + "\xDD\xB3" => "\xE6\x91\x80", + "\xDD\xB4" => "\xE6\x90\xA5", + "\xDD\xB5" => "\xE6\x90\xA7", + "\xDD\xB6" => "\xE6\x90\x8B", + "\xDD\xB7" => "\xE6\x8F\xA7", + "\xDD\xB8" => "\xE6\x90\x9B", + "\xDD\xB9" => "\xE6\x90\xAE", + "\xDD\xBA" => "\xE6\x90\xA1", + "\xDD\xBB" => "\xE6\x90\x8E", + "\xDD\xBC" => "\xE6\x95\xAF", + "\xDD\xBD" => "\xE6\x96\x92", + "\xDD\xBE" => "\xE6\x97\x93", + "\xDD\xBF" => "\xE6\x9A\x86", + "\xDD\xC0" => "\xE6\x9A\x8C", + "\xDD\xC1" => "\xE6\x9A\x95", + "\xDD\xC2" => "\xE6\x9A\x90", + "\xDD\xC3" => "\xE6\x9A\x8B", + "\xDD\xC4" => "\xE6\x9A\x8A", + "\xDD\xC5" => "\xE6\x9A\x99", + "\xDD\xC6" => "\xE6\x9A\x94", + "\xDD\xC7" => "\xE6\x99\xB8", + "\xDD\xC8" => "\xE6\x9C\xA0", + "\xDD\xC9" => "\xE6\xA5\xA6", + "\xDD\xCA" => "\xE6\xA5\x9F", + "\xDD\xCB" => "\xE6\xA4\xB8", + "\xDD\xCC" => "\xE6\xA5\x8E", + "\xDD\xCD" => "\xE6\xA5\xA2", + "\xDD\xCE" => "\xE6\xA5\xB1", + "\xDD\xCF" => "\xE6\xA4\xBF", + "\xDD\xD0" => "\xE6\xA5\x85", + "\xDD\xD1" => "\xE6\xA5\xAA", + "\xDD\xD2" => "\xE6\xA4\xB9", + "\xDD\xD3" => "\xE6\xA5\x82", + "\xDD\xD4" => "\xE6\xA5\x97", + "\xDD\xD5" => "\xE6\xA5\x99", + "\xDD\xD6" => "\xE6\xA5\xBA", + "\xDD\xD7" => "\xE6\xA5\x88", + "\xDD\xD8" => "\xE6\xA5\x89", + "\xDD\xD9" => "\xE6\xA4\xB5", + "\xDD\xDA" => "\xE6\xA5\xAC", + "\xDD\xDB" => "\xE6\xA4\xB3", + "\xDD\xDC" => "\xE6\xA4\xBD", + "\xDD\xDD" => "\xE6\xA5\xA5", + "\xDD\xDE" => "\xE6\xA3\xB0", + "\xDD\xDF" => "\xE6\xA5\xB8", + "\xDD\xE0" => "\xE6\xA4\xB4", + "\xDD\xE1" => "\xE6\xA5\xA9", + "\xDD\xE2" => "\xE6\xA5\x80", + "\xDD\xE3" => "\xE6\xA5\xAF", + "\xDD\xE4" => "\xE6\xA5\x84", + "\xDD\xE5" => "\xE6\xA5\xB6", + "\xDD\xE6" => "\xE6\xA5\x98", + "\xDD\xE7" => "\xE6\xA5\x81", + "\xDD\xE8" => "\xE6\xA5\xB4", + "\xDD\xE9" => "\xE6\xA5\x8C", + "\xDD\xEA" => "\xE6\xA4\xBB", + "\xDD\xEB" => "\xE6\xA5\x8B", + "\xDD\xEC" => "\xE6\xA4\xB7", + "\xDD\xED" => "\xE6\xA5\x9C", + "\xDD\xEE" => "\xE6\xA5\x8F", + "\xDD\xEF" => "\xE6\xA5\x91", + "\xDD\xF0" => "\xE6\xA4\xB2", + "\xDD\xF1" => "\xE6\xA5\x92", + "\xDD\xF2" => "\xE6\xA4\xAF", + "\xDD\xF3" => "\xE6\xA5\xBB", + "\xDD\xF4" => "\xE6\xA4\xBC", + "\xDD\xF5" => "\xE6\xAD\x86", + "\xDD\xF6" => "\xE6\xAD\x85", + "\xDD\xF7" => "\xE6\xAD\x83", + "\xDD\xF8" => "\xE6\xAD\x82", + "\xDD\xF9" => "\xE6\xAD\x88", + "\xDD\xFA" => "\xE6\xAD\x81", + "\xDD\xFB" => "\xE6\xAE\x9B", + "\xDD\xFC" => "\xEF\xA8\x8D", + "\xDD\xFD" => "\xE6\xAF\xBB", + "\xDD\xFE" => "\xE6\xAF\xBC", + "\xDE\x40" => "\xE6\xAF\xB9", + "\xDE\x41" => "\xE6\xAF\xB7", + "\xDE\x42" => "\xE6\xAF\xB8", + "\xDE\x43" => "\xE6\xBA\x9B", + "\xDE\x44" => "\xE6\xBB\x96", + "\xDE\x45" => "\xE6\xBB\x88", + "\xDE\x46" => "\xE6\xBA\x8F", + "\xDE\x47" => "\xE6\xBB\x80", + "\xDE\x48" => "\xE6\xBA\x9F", + "\xDE\x49" => "\xE6\xBA\x93", + "\xDE\x4A" => "\xE6\xBA\x94", + "\xDE\x4B" => "\xE6\xBA\xA0", + "\xDE\x4C" => "\xE6\xBA\xB1", + "\xDE\x4D" => "\xE6\xBA\xB9", + "\xDE\x4E" => "\xE6\xBB\x86", + "\xDE\x4F" => "\xE6\xBB\x92", + "\xDE\x50" => "\xE6\xBA\xBD", + "\xDE\x51" => "\xE6\xBB\x81", + "\xDE\x52" => "\xE6\xBA\x9E", + "\xDE\x53" => "\xE6\xBB\x89", + "\xDE\x54" => "\xE6\xBA\xB7", + "\xDE\x55" => "\xE6\xBA\xB0", + "\xDE\x56" => "\xE6\xBB\x8D", + "\xDE\x57" => "\xE6\xBA\xA6", + "\xDE\x58" => "\xE6\xBB\x8F", + "\xDE\x59" => "\xE6\xBA\xB2", + "\xDE\x5A" => "\xE6\xBA\xBE", + "\xDE\x5B" => "\xE6\xBB\x83", + "\xDE\x5C" => "\xE6\xBB\x9C", + "\xDE\x5D" => "\xE6\xBB\x98", + "\xDE\x5E" => "\xE6\xBA\x99", + "\xDE\x5F" => "\xE6\xBA\x92", + "\xDE\x60" => "\xE6\xBA\x8E", + "\xDE\x61" => "\xE6\xBA\x8D", + "\xDE\x62" => "\xE6\xBA\xA4", + "\xDE\x63" => "\xE6\xBA\xA1", + "\xDE\x64" => "\xE6\xBA\xBF", + "\xDE\x65" => "\xE6\xBA\xB3", + "\xDE\x66" => "\xE6\xBB\x90", + "\xDE\x67" => "\xE6\xBB\x8A", + "\xDE\x68" => "\xE6\xBA\x97", + "\xDE\x69" => "\xE6\xBA\xAE", + "\xDE\x6A" => "\xE6\xBA\xA3", + "\xDE\x6B" => "\xE7\x85\x87", + "\xDE\x6C" => "\xE7\x85\x94", + "\xDE\x6D" => "\xE7\x85\x92", + "\xDE\x6E" => "\xE7\x85\xA3", + "\xDE\x6F" => "\xE7\x85\xA0", + "\xDE\x70" => "\xE7\x85\x81", + "\xDE\x71" => "\xE7\x85\x9D", + "\xDE\x72" => "\xE7\x85\xA2", + "\xDE\x73" => "\xE7\x85\xB2", + "\xDE\x74" => "\xE7\x85\xB8", + "\xDE\x75" => "\xE7\x85\xAA", + "\xDE\x76" => "\xE7\x85\xA1", + "\xDE\x77" => "\xE7\x85\x82", + "\xDE\x78" => "\xE7\x85\x98", + "\xDE\x79" => "\xE7\x85\x83", + "\xDE\x7A" => "\xE7\x85\x8B", + "\xDE\x7B" => "\xE7\x85\xB0", + "\xDE\x7C" => "\xE7\x85\x9F", + "\xDE\x7D" => "\xE7\x85\x90", + "\xDE\x7E" => "\xE7\x85\x93", + "\xDE\xA1" => "\xE7\x85\x84", + "\xDE\xA2" => "\xE7\x85\x8D", + "\xDE\xA3" => "\xE7\x85\x9A", + "\xDE\xA4" => "\xE7\x89\x8F", + "\xDE\xA5" => "\xE7\x8A\x8D", + "\xDE\xA6" => "\xE7\x8A\x8C", + "\xDE\xA7" => "\xE7\x8A\x91", + "\xDE\xA8" => "\xE7\x8A\x90", + "\xDE\xA9" => "\xE7\x8A\x8E", + "\xDE\xAA" => "\xE7\x8C\xBC", + "\xDE\xAB" => "\xE7\x8D\x82", + "\xDE\xAC" => "\xE7\x8C\xBB", + "\xDE\xAD" => "\xE7\x8C\xBA", + "\xDE\xAE" => "\xE7\x8D\x80", + "\xDE\xAF" => "\xE7\x8D\x8A", + "\xDE\xB0" => "\xE7\x8D\x89", + "\xDE\xB1" => "\xE7\x91\x84", + "\xDE\xB2" => "\xE7\x91\x8A", + "\xDE\xB3" => "\xE7\x91\x8B", + "\xDE\xB4" => "\xE7\x91\x92", + "\xDE\xB5" => "\xE7\x91\x91", + "\xDE\xB6" => "\xE7\x91\x97", + "\xDE\xB7" => "\xE7\x91\x80", + "\xDE\xB8" => "\xE7\x91\x8F", + "\xDE\xB9" => "\xE7\x91\x90", + "\xDE\xBA" => "\xE7\x91\x8E", + "\xDE\xBB" => "\xE7\x91\x82", + "\xDE\xBC" => "\xE7\x91\x86", + "\xDE\xBD" => "\xE7\x91\x8D", + "\xDE\xBE" => "\xE7\x91\x94", + "\xDE\xBF" => "\xE7\x93\xA1", + "\xDE\xC0" => "\xE7\x93\xBF", + "\xDE\xC1" => "\xE7\x93\xBE", + "\xDE\xC2" => "\xE7\x93\xBD", + "\xDE\xC3" => "\xE7\x94\x9D", + "\xDE\xC4" => "\xE7\x95\xB9", + "\xDE\xC5" => "\xE7\x95\xB7", + "\xDE\xC6" => "\xE6\xA6\x83", + "\xDE\xC7" => "\xE7\x97\xAF", + "\xDE\xC8" => "\xE7\x98\x8F", + "\xDE\xC9" => "\xE7\x98\x83", + "\xDE\xCA" => "\xE7\x97\xB7", + "\xDE\xCB" => "\xE7\x97\xBE", + "\xDE\xCC" => "\xE7\x97\xBC", + "\xDE\xCD" => "\xE7\x97\xB9", + "\xDE\xCE" => "\xE7\x97\xB8", + "\xDE\xCF" => "\xE7\x98\x90", + "\xDE\xD0" => "\xE7\x97\xBB", + "\xDE\xD1" => "\xE7\x97\xB6", + "\xDE\xD2" => "\xE7\x97\xAD", + "\xDE\xD3" => "\xE7\x97\xB5", + "\xDE\xD4" => "\xE7\x97\xBD", + "\xDE\xD5" => "\xE7\x9A\x99", + "\xDE\xD6" => "\xE7\x9A\xB5", + "\xDE\xD7" => "\xE7\x9B\x9D", + "\xDE\xD8" => "\xE7\x9D\x95", + "\xDE\xD9" => "\xE7\x9D\x9F", + "\xDE\xDA" => "\xE7\x9D\xA0", + "\xDE\xDB" => "\xE7\x9D\x92", + "\xDE\xDC" => "\xE7\x9D\x96", + "\xDE\xDD" => "\xE7\x9D\x9A", + "\xDE\xDE" => "\xE7\x9D\xA9", + "\xDE\xDF" => "\xE7\x9D\xA7", + "\xDE\xE0" => "\xE7\x9D\x94", + "\xDE\xE1" => "\xE7\x9D\x99", + "\xDE\xE2" => "\xE7\x9D\xAD", + "\xDE\xE3" => "\xE7\x9F\xA0", + "\xDE\xE4" => "\xE7\xA2\x87", + "\xDE\xE5" => "\xE7\xA2\x9A", + "\xDE\xE6" => "\xE7\xA2\x94", + "\xDE\xE7" => "\xE7\xA2\x8F", + "\xDE\xE8" => "\xE7\xA2\x84", + "\xDE\xE9" => "\xE7\xA2\x95", + "\xDE\xEA" => "\xE7\xA2\x85", + "\xDE\xEB" => "\xE7\xA2\x86", + "\xDE\xEC" => "\xE7\xA2\xA1", + "\xDE\xED" => "\xE7\xA2\x83", + "\xDE\xEE" => "\xE7\xA1\xB9", + "\xDE\xEF" => "\xE7\xA2\x99", + "\xDE\xF0" => "\xE7\xA2\x80", + "\xDE\xF1" => "\xE7\xA2\x96", + "\xDE\xF2" => "\xE7\xA1\xBB", + "\xDE\xF3" => "\xE7\xA5\xBC", + "\xDE\xF4" => "\xE7\xA6\x82", + "\xDE\xF5" => "\xE7\xA5\xBD", + "\xDE\xF6" => "\xE7\xA5\xB9", + "\xDE\xF7" => "\xE7\xA8\x91", + "\xDE\xF8" => "\xE7\xA8\x98", + "\xDE\xF9" => "\xE7\xA8\x99", + "\xDE\xFA" => "\xE7\xA8\x92", + "\xDE\xFB" => "\xE7\xA8\x97", + "\xDE\xFC" => "\xE7\xA8\x95", + "\xDE\xFD" => "\xE7\xA8\xA2", + "\xDE\xFE" => "\xE7\xA8\x93", + "\xDF\x40" => "\xE7\xA8\x9B", + "\xDF\x41" => "\xE7\xA8\x90", + "\xDF\x42" => "\xE7\xAA\xA3", + "\xDF\x43" => "\xE7\xAA\xA2", + "\xDF\x44" => "\xE7\xAA\x9E", + "\xDF\x45" => "\xE7\xAB\xAB", + "\xDF\x46" => "\xE7\xAD\xA6", + "\xDF\x47" => "\xE7\xAD\xA4", + "\xDF\x48" => "\xE7\xAD\xAD", + "\xDF\x49" => "\xE7\xAD\xB4", + "\xDF\x4A" => "\xE7\xAD\xA9", + "\xDF\x4B" => "\xE7\xAD\xB2", + "\xDF\x4C" => "\xE7\xAD\xA5", + "\xDF\x4D" => "\xE7\xAD\xB3", + "\xDF\x4E" => "\xE7\xAD\xB1", + "\xDF\x4F" => "\xE7\xAD\xB0", + "\xDF\x50" => "\xE7\xAD\xA1", + "\xDF\x51" => "\xE7\xAD\xB8", + "\xDF\x52" => "\xE7\xAD\xB6", + "\xDF\x53" => "\xE7\xAD\xA3", + "\xDF\x54" => "\xE7\xB2\xB2", + "\xDF\x55" => "\xE7\xB2\xB4", + "\xDF\x56" => "\xE7\xB2\xAF", + "\xDF\x57" => "\xE7\xB6\x88", + "\xDF\x58" => "\xE7\xB6\x86", + "\xDF\x59" => "\xE7\xB6\x80", + "\xDF\x5A" => "\xE7\xB6\x8D", + "\xDF\x5B" => "\xE7\xB5\xBF", + "\xDF\x5C" => "\xE7\xB6\x85", + "\xDF\x5D" => "\xE7\xB5\xBA", + "\xDF\x5E" => "\xE7\xB6\x8E", + "\xDF\x5F" => "\xE7\xB5\xBB", + "\xDF\x60" => "\xE7\xB6\x83", + "\xDF\x61" => "\xE7\xB5\xBC", + "\xDF\x62" => "\xE7\xB6\x8C", + "\xDF\x63" => "\xE7\xB6\x94", + "\xDF\x64" => "\xE7\xB6\x84", + "\xDF\x65" => "\xE7\xB5\xBD", + "\xDF\x66" => "\xE7\xB6\x92", + "\xDF\x67" => "\xE7\xBD\xAD", + "\xDF\x68" => "\xE7\xBD\xAB", + "\xDF\x69" => "\xE7\xBD\xA7", + "\xDF\x6A" => "\xE7\xBD\xA8", + "\xDF\x6B" => "\xE7\xBD\xAC", + "\xDF\x6C" => "\xE7\xBE\xA6", + "\xDF\x6D" => "\xE7\xBE\xA5", + "\xDF\x6E" => "\xE7\xBE\xA7", + "\xDF\x6F" => "\xE7\xBF\x9B", + "\xDF\x70" => "\xE7\xBF\x9C", + "\xDF\x71" => "\xE8\x80\xA1", + "\xDF\x72" => "\xE8\x85\xA4", + "\xDF\x73" => "\xE8\x85\xA0", + "\xDF\x74" => "\xE8\x85\xB7", + "\xDF\x75" => "\xE8\x85\x9C", + "\xDF\x76" => "\xE8\x85\xA9", + "\xDF\x77" => "\xE8\x85\x9B", + "\xDF\x78" => "\xE8\x85\xA2", + "\xDF\x79" => "\xE8\x85\xB2", + "\xDF\x7A" => "\xE6\x9C\xA1", + "\xDF\x7B" => "\xE8\x85\x9E", + "\xDF\x7C" => "\xE8\x85\xB6", + "\xDF\x7D" => "\xE8\x85\xA7", + "\xDF\x7E" => "\xE8\x85\xAF", + "\xDF\xA1" => "\xE8\x85\x84", + "\xDF\xA2" => "\xE8\x85\xA1", + "\xDF\xA3" => "\xE8\x88\x9D", + "\xDF\xA4" => "\xE8\x89\x89", + "\xDF\xA5" => "\xE8\x89\x84", + "\xDF\xA6" => "\xE8\x89\x80", + "\xDF\xA7" => "\xE8\x89\x82", + "\xDF\xA8" => "\xE8\x89\x85", + "\xDF\xA9" => "\xE8\x93\xB1", + "\xDF\xAA" => "\xE8\x90\xBF", + "\xDF\xAB" => "\xE8\x91\x96", + "\xDF\xAC" => "\xE8\x91\xB6", + "\xDF\xAD" => "\xE8\x91\xB9", + "\xDF\xAE" => "\xE8\x92\x8F", + "\xDF\xAF" => "\xE8\x92\x8D", + "\xDF\xB0" => "\xE8\x91\xA5", + "\xDF\xB1" => "\xE8\x91\x91", + "\xDF\xB2" => "\xE8\x91\x80", + "\xDF\xB3" => "\xE8\x92\x86", + "\xDF\xB4" => "\xE8\x91\xA7", + "\xDF\xB5" => "\xE8\x90\xB0", + "\xDF\xB6" => "\xE8\x91\x8D", + "\xDF\xB7" => "\xE8\x91\xBD", + "\xDF\xB8" => "\xE8\x91\x9A", + "\xDF\xB9" => "\xE8\x91\x99", + "\xDF\xBA" => "\xE8\x91\xB4", + "\xDF\xBB" => "\xE8\x91\xB3", + "\xDF\xBC" => "\xE8\x91\x9D", + "\xDF\xBD" => "\xE8\x94\x87", + "\xDF\xBE" => "\xE8\x91\x9E", + "\xDF\xBF" => "\xE8\x90\xB7", + "\xDF\xC0" => "\xE8\x90\xBA", + "\xDF\xC1" => "\xE8\x90\xB4", + "\xDF\xC2" => "\xE8\x91\xBA", + "\xDF\xC3" => "\xE8\x91\x83", + "\xDF\xC4" => "\xE8\x91\xB8", + "\xDF\xC5" => "\xE8\x90\xB2", + "\xDF\xC6" => "\xE8\x91\x85", + "\xDF\xC7" => "\xE8\x90\xA9", + "\xDF\xC8" => "\xE8\x8F\x99", + "\xDF\xC9" => "\xE8\x91\x8B", + "\xDF\xCA" => "\xE8\x90\xAF", + "\xDF\xCB" => "\xE8\x91\x82", + "\xDF\xCC" => "\xE8\x90\xAD", + "\xDF\xCD" => "\xE8\x91\x9F", + "\xDF\xCE" => "\xE8\x91\xB0", + "\xDF\xCF" => "\xE8\x90\xB9", + "\xDF\xD0" => "\xE8\x91\x8E", + "\xDF\xD1" => "\xE8\x91\x8C", + "\xDF\xD2" => "\xE8\x91\x92", + "\xDF\xD3" => "\xE8\x91\xAF", + "\xDF\xD4" => "\xE8\x93\x85", + "\xDF\xD5" => "\xE8\x92\x8E", + "\xDF\xD6" => "\xE8\x90\xBB", + "\xDF\xD7" => "\xE8\x91\x87", + "\xDF\xD8" => "\xE8\x90\xB6", + "\xDF\xD9" => "\xE8\x90\xB3", + "\xDF\xDA" => "\xE8\x91\xA8", + "\xDF\xDB" => "\xE8\x91\xBE", + "\xDF\xDC" => "\xE8\x91\x84", + "\xDF\xDD" => "\xE8\x90\xAB", + "\xDF\xDE" => "\xE8\x91\xA0", + "\xDF\xDF" => "\xE8\x91\x94", + "\xDF\xE0" => "\xE8\x91\xAE", + "\xDF\xE1" => "\xE8\x91\x90", + "\xDF\xE2" => "\xE8\x9C\x8B", + "\xDF\xE3" => "\xE8\x9C\x84", + "\xDF\xE4" => "\xE8\x9B\xB7", + "\xDF\xE5" => "\xE8\x9C\x8C", + "\xDF\xE6" => "\xE8\x9B\xBA", + "\xDF\xE7" => "\xE8\x9B\x96", + "\xDF\xE8" => "\xE8\x9B\xB5", + "\xDF\xE9" => "\xE8\x9D\x8D", + "\xDF\xEA" => "\xE8\x9B\xB8", + "\xDF\xEB" => "\xE8\x9C\x8E", + "\xDF\xEC" => "\xE8\x9C\x89", + "\xDF\xED" => "\xE8\x9C\x81", + "\xDF\xEE" => "\xE8\x9B\xB6", + "\xDF\xEF" => "\xE8\x9C\x8D", + "\xDF\xF0" => "\xE8\x9C\x85", + "\xDF\xF1" => "\xE8\xA3\x96", + "\xDF\xF2" => "\xE8\xA3\x8B", + "\xDF\xF3" => "\xE8\xA3\x8D", + "\xDF\xF4" => "\xE8\xA3\x8E", + "\xDF\xF5" => "\xE8\xA3\x9E", + "\xDF\xF6" => "\xE8\xA3\x9B", + "\xDF\xF7" => "\xE8\xA3\x9A", + "\xDF\xF8" => "\xE8\xA3\x8C", + "\xDF\xF9" => "\xE8\xA3\x90", + "\xDF\xFA" => "\xE8\xA6\x85", + "\xDF\xFB" => "\xE8\xA6\x9B", + "\xDF\xFC" => "\xE8\xA7\x9F", + "\xDF\xFD" => "\xE8\xA7\xA5", + "\xDF\xFE" => "\xE8\xA7\xA4", + "\xE0\x40" => "\xE8\xA7\xA1", + "\xE0\x41" => "\xE8\xA7\xA0", + "\xE0\x42" => "\xE8\xA7\xA2", + "\xE0\x43" => "\xE8\xA7\x9C", + "\xE0\x44" => "\xE8\xA7\xA6", + "\xE0\x45" => "\xE8\xA9\xB6", + "\xE0\x46" => "\xE8\xAA\x86", + "\xE0\x47" => "\xE8\xA9\xBF", + "\xE0\x48" => "\xE8\xA9\xA1", + "\xE0\x49" => "\xE8\xA8\xBF", + "\xE0\x4A" => "\xE8\xA9\xB7", + "\xE0\x4B" => "\xE8\xAA\x82", + "\xE0\x4C" => "\xE8\xAA\x84", + "\xE0\x4D" => "\xE8\xA9\xB5", + "\xE0\x4E" => "\xE8\xAA\x83", + "\xE0\x4F" => "\xE8\xAA\x81", + "\xE0\x50" => "\xE8\xA9\xB4", + "\xE0\x51" => "\xE8\xA9\xBA", + "\xE0\x52" => "\xE8\xB0\xBC", + "\xE0\x53" => "\xE8\xB1\x8B", + "\xE0\x54" => "\xE8\xB1\x8A", + "\xE0\x55" => "\xE8\xB1\xA5", + "\xE0\x56" => "\xE8\xB1\xA4", + "\xE0\x57" => "\xE8\xB1\xA6", + "\xE0\x58" => "\xE8\xB2\x86", + "\xE0\x59" => "\xE8\xB2\x84", + "\xE0\x5A" => "\xE8\xB2\x85", + "\xE0\x5B" => "\xE8\xB3\x8C", + "\xE0\x5C" => "\xE8\xB5\xA8", + "\xE0\x5D" => "\xE8\xB5\xA9", + "\xE0\x5E" => "\xE8\xB6\x91", + "\xE0\x5F" => "\xE8\xB6\x8C", + "\xE0\x60" => "\xE8\xB6\x8E", + "\xE0\x61" => "\xE8\xB6\x8F", + "\xE0\x62" => "\xE8\xB6\x8D", + "\xE0\x63" => "\xE8\xB6\x93", + "\xE0\x64" => "\xE8\xB6\x94", + "\xE0\x65" => "\xE8\xB6\x90", + "\xE0\x66" => "\xE8\xB6\x92", + "\xE0\x67" => "\xE8\xB7\xB0", + "\xE0\x68" => "\xE8\xB7\xA0", + "\xE0\x69" => "\xE8\xB7\xAC", + "\xE0\x6A" => "\xE8\xB7\xB1", + "\xE0\x6B" => "\xE8\xB7\xAE", + "\xE0\x6C" => "\xE8\xB7\x90", + "\xE0\x6D" => "\xE8\xB7\xA9", + "\xE0\x6E" => "\xE8\xB7\xA3", + "\xE0\x6F" => "\xE8\xB7\xA2", + "\xE0\x70" => "\xE8\xB7\xA7", + "\xE0\x71" => "\xE8\xB7\xB2", + "\xE0\x72" => "\xE8\xB7\xAB", + "\xE0\x73" => "\xE8\xB7\xB4", + "\xE0\x74" => "\xE8\xBC\x86", + "\xE0\x75" => "\xE8\xBB\xBF", + "\xE0\x76" => "\xE8\xBC\x81", + "\xE0\x77" => "\xE8\xBC\x80", + "\xE0\x78" => "\xE8\xBC\x85", + "\xE0\x79" => "\xE8\xBC\x87", + "\xE0\x7A" => "\xE8\xBC\x88", + "\xE0\x7B" => "\xE8\xBC\x82", + "\xE0\x7C" => "\xE8\xBC\x8B", + "\xE0\x7D" => "\xE9\x81\x92", + "\xE0\x7E" => "\xE9\x80\xBF", + "\xE0\xA1" => "\xE9\x81\x84", + "\xE0\xA2" => "\xE9\x81\x89", + "\xE0\xA3" => "\xE9\x80\xBD", + "\xE0\xA4" => "\xE9\x84\x90", + "\xE0\xA5" => "\xE9\x84\x8D", + "\xE0\xA6" => "\xE9\x84\x8F", + "\xE0\xA7" => "\xE9\x84\x91", + "\xE0\xA8" => "\xE9\x84\x96", + "\xE0\xA9" => "\xE9\x84\x94", + "\xE0\xAA" => "\xE9\x84\x8B", + "\xE0\xAB" => "\xE9\x84\x8E", + "\xE0\xAC" => "\xE9\x85\xAE", + "\xE0\xAD" => "\xE9\x85\xAF", + "\xE0\xAE" => "\xE9\x89\x88", + "\xE0\xAF" => "\xE9\x89\x92", + "\xE0\xB0" => "\xE9\x88\xB0", + "\xE0\xB1" => "\xE9\x88\xBA", + "\xE0\xB2" => "\xE9\x89\xA6", + "\xE0\xB3" => "\xE9\x88\xB3", + "\xE0\xB4" => "\xE9\x89\xA5", + "\xE0\xB5" => "\xE9\x89\x9E", + "\xE0\xB6" => "\xE9\x8A\x83", + "\xE0\xB7" => "\xE9\x88\xAE", + "\xE0\xB8" => "\xE9\x89\x8A", + "\xE0\xB9" => "\xE9\x89\x86", + "\xE0\xBA" => "\xE9\x89\xAD", + "\xE0\xBB" => "\xE9\x89\xAC", + "\xE0\xBC" => "\xE9\x89\x8F", + "\xE0\xBD" => "\xE9\x89\xA0", + "\xE0\xBE" => "\xE9\x89\xA7", + "\xE0\xBF" => "\xE9\x89\xAF", + "\xE0\xC0" => "\xE9\x88\xB6", + "\xE0\xC1" => "\xE9\x89\xA1", + "\xE0\xC2" => "\xE9\x89\xB0", + "\xE0\xC3" => "\xE9\x88\xB1", + "\xE0\xC4" => "\xE9\x89\x94", + "\xE0\xC5" => "\xE9\x89\xA3", + "\xE0\xC6" => "\xE9\x89\x90", + "\xE0\xC7" => "\xE9\x89\xB2", + "\xE0\xC8" => "\xE9\x89\x8E", + "\xE0\xC9" => "\xE9\x89\x93", + "\xE0\xCA" => "\xE9\x89\x8C", + "\xE0\xCB" => "\xE9\x89\x96", + "\xE0\xCC" => "\xE9\x88\xB2", + "\xE0\xCD" => "\xE9\x96\x9F", + "\xE0\xCE" => "\xE9\x96\x9C", + "\xE0\xCF" => "\xE9\x96\x9E", + "\xE0\xD0" => "\xE9\x96\x9B", + "\xE0\xD1" => "\xE9\x9A\x92", + "\xE0\xD2" => "\xE9\x9A\x93", + "\xE0\xD3" => "\xE9\x9A\x91", + "\xE0\xD4" => "\xE9\x9A\x97", + "\xE0\xD5" => "\xE9\x9B\x8E", + "\xE0\xD6" => "\xE9\x9B\xBA", + "\xE0\xD7" => "\xE9\x9B\xBD", + "\xE0\xD8" => "\xE9\x9B\xB8", + "\xE0\xD9" => "\xE9\x9B\xB5", + "\xE0\xDA" => "\xE9\x9D\xB3", + "\xE0\xDB" => "\xE9\x9D\xB7", + "\xE0\xDC" => "\xE9\x9D\xB8", + "\xE0\xDD" => "\xE9\x9D\xB2", + "\xE0\xDE" => "\xE9\xA0\x8F", + "\xE0\xDF" => "\xE9\xA0\x8D", + "\xE0\xE0" => "\xE9\xA0\x8E", + "\xE0\xE1" => "\xE9\xA2\xAC", + "\xE0\xE2" => "\xE9\xA3\xB6", + "\xE0\xE3" => "\xE9\xA3\xB9", + "\xE0\xE4" => "\xE9\xA6\xAF", + "\xE0\xE5" => "\xE9\xA6\xB2", + "\xE0\xE6" => "\xE9\xA6\xB0", + "\xE0\xE7" => "\xE9\xA6\xB5", + "\xE0\xE8" => "\xE9\xAA\xAD", + "\xE0\xE9" => "\xE9\xAA\xAB", + "\xE0\xEA" => "\xE9\xAD\x9B", + "\xE0\xEB" => "\xE9\xB3\xAA", + "\xE0\xEC" => "\xE9\xB3\xAD", + "\xE0\xED" => "\xE9\xB3\xA7", + "\xE0\xEE" => "\xE9\xBA\x80", + "\xE0\xEF" => "\xE9\xBB\xBD", + "\xE0\xF0" => "\xE5\x83\xA6", + "\xE0\xF1" => "\xE5\x83\x94", + "\xE0\xF2" => "\xE5\x83\x97", + "\xE0\xF3" => "\xE5\x83\xA8", + "\xE0\xF4" => "\xE5\x83\xB3", + "\xE0\xF5" => "\xE5\x83\x9B", + "\xE0\xF6" => "\xE5\x83\xAA", + "\xE0\xF7" => "\xE5\x83\x9D", + "\xE0\xF8" => "\xE5\x83\xA4", + "\xE0\xF9" => "\xE5\x83\x93", + "\xE0\xFA" => "\xE5\x83\xAC", + "\xE0\xFB" => "\xE5\x83\xB0", + "\xE0\xFC" => "\xE5\x83\xAF", + "\xE0\xFD" => "\xE5\x83\xA3", + "\xE0\xFE" => "\xE5\x83\xA0", + "\xE1\x40" => "\xE5\x87\x98", + "\xE1\x41" => "\xE5\x8A\x80", + "\xE1\x42" => "\xE5\x8A\x81", + "\xE1\x43" => "\xE5\x8B\xA9", + "\xE1\x44" => "\xE5\x8B\xAB", + "\xE1\x45" => "\xE5\x8C\xB0", + "\xE1\x46" => "\xE5\x8E\xAC", + "\xE1\x47" => "\xE5\x98\xA7", + "\xE1\x48" => "\xE5\x98\x95", + "\xE1\x49" => "\xE5\x98\x8C", + "\xE1\x4A" => "\xE5\x98\x92", + "\xE1\x4B" => "\xE5\x97\xBC", + "\xE1\x4C" => "\xE5\x98\x8F", + "\xE1\x4D" => "\xE5\x98\x9C", + "\xE1\x4E" => "\xE5\x98\x81", + "\xE1\x4F" => "\xE5\x98\x93", + "\xE1\x50" => "\xE5\x98\x82", + "\xE1\x51" => "\xE5\x97\xBA", + "\xE1\x52" => "\xE5\x98\x9D", + "\xE1\x53" => "\xE5\x98\x84", + "\xE1\x54" => "\xE5\x97\xBF", + "\xE1\x55" => "\xE5\x97\xB9", + "\xE1\x56" => "\xE5\xA2\x89", + "\xE1\x57" => "\xE5\xA1\xBC", + "\xE1\x58" => "\xE5\xA2\x90", + "\xE1\x59" => "\xE5\xA2\x98", + "\xE1\x5A" => "\xE5\xA2\x86", + "\xE1\x5B" => "\xE5\xA2\x81", + "\xE1\x5C" => "\xE5\xA1\xBF", + "\xE1\x5D" => "\xE5\xA1\xB4", + "\xE1\x5E" => "\xE5\xA2\x8B", + "\xE1\x5F" => "\xE5\xA1\xBA", + "\xE1\x60" => "\xE5\xA2\x87", + "\xE1\x61" => "\xE5\xA2\x91", + "\xE1\x62" => "\xE5\xA2\x8E", + "\xE1\x63" => "\xE5\xA1\xB6", + "\xE1\x64" => "\xE5\xA2\x82", + "\xE1\x65" => "\xE5\xA2\x88", + "\xE1\x66" => "\xE5\xA1\xBB", + "\xE1\x67" => "\xE5\xA2\x94", + "\xE1\x68" => "\xE5\xA2\x8F", + "\xE1\x69" => "\xE5\xA3\xBE", + "\xE1\x6A" => "\xE5\xA5\xAB", + "\xE1\x6B" => "\xE5\xAB\x9C", + "\xE1\x6C" => "\xE5\xAB\xAE", + "\xE1\x6D" => "\xE5\xAB\xA5", + "\xE1\x6E" => "\xE5\xAB\x95", + "\xE1\x6F" => "\xE5\xAB\xAA", + "\xE1\x70" => "\xE5\xAB\x9A", + "\xE1\x71" => "\xE5\xAB\xAD", + "\xE1\x72" => "\xE5\xAB\xAB", + "\xE1\x73" => "\xE5\xAB\xB3", + "\xE1\x74" => "\xE5\xAB\xA2", + "\xE1\x75" => "\xE5\xAB\xA0", + "\xE1\x76" => "\xE5\xAB\x9B", + "\xE1\x77" => "\xE5\xAB\xAC", + "\xE1\x78" => "\xE5\xAB\x9E", + "\xE1\x79" => "\xE5\xAB\x9D", + "\xE1\x7A" => "\xE5\xAB\x99", + "\xE1\x7B" => "\xE5\xAB\xA8", + "\xE1\x7C" => "\xE5\xAB\x9F", + "\xE1\x7D" => "\xE5\xAD\xB7", + "\xE1\x7E" => "\xE5\xAF\xA0", + "\xE1\xA1" => "\xE5\xAF\xA3", + "\xE1\xA2" => "\xE5\xB1\xA3", + "\xE1\xA3" => "\xE5\xB6\x82", + "\xE1\xA4" => "\xE5\xB6\x80", + "\xE1\xA5" => "\xE5\xB5\xBD", + "\xE1\xA6" => "\xE5\xB6\x86", + "\xE1\xA7" => "\xE5\xB5\xBA", + "\xE1\xA8" => "\xE5\xB6\x81", + "\xE1\xA9" => "\xE5\xB5\xB7", + "\xE1\xAA" => "\xE5\xB6\x8A", + "\xE1\xAB" => "\xE5\xB6\x89", + "\xE1\xAC" => "\xE5\xB6\x88", + "\xE1\xAD" => "\xE5\xB5\xBE", + "\xE1\xAE" => "\xE5\xB5\xBC", + "\xE1\xAF" => "\xE5\xB6\x8D", + "\xE1\xB0" => "\xE5\xB5\xB9", + "\xE1\xB1" => "\xE5\xB5\xBF", + "\xE1\xB2" => "\xE5\xB9\x98", + "\xE1\xB3" => "\xE5\xB9\x99", + "\xE1\xB4" => "\xE5\xB9\x93", + "\xE1\xB5" => "\xE5\xBB\x98", + "\xE1\xB6" => "\xE5\xBB\x91", + "\xE1\xB7" => "\xE5\xBB\x97", + "\xE1\xB8" => "\xE5\xBB\x8E", + "\xE1\xB9" => "\xE5\xBB\x9C", + "\xE1\xBA" => "\xE5\xBB\x95", + "\xE1\xBB" => "\xE5\xBB\x99", + "\xE1\xBC" => "\xE5\xBB\x92", + "\xE1\xBD" => "\xE5\xBB\x94", + "\xE1\xBE" => "\xE5\xBD\x84", + "\xE1\xBF" => "\xE5\xBD\x83", + "\xE1\xC0" => "\xE5\xBD\xAF", + "\xE1\xC1" => "\xE5\xBE\xB6", + "\xE1\xC2" => "\xE6\x84\xAC", + "\xE1\xC3" => "\xE6\x84\xA8", + "\xE1\xC4" => "\xE6\x85\x81", + "\xE1\xC5" => "\xE6\x85\x9E", + "\xE1\xC6" => "\xE6\x85\xB1", + "\xE1\xC7" => "\xE6\x85\xB3", + "\xE1\xC8" => "\xE6\x85\x92", + "\xE1\xC9" => "\xE6\x85\x93", + "\xE1\xCA" => "\xE6\x85\xB2", + "\xE1\xCB" => "\xE6\x85\xAC", + "\xE1\xCC" => "\xE6\x86\x80", + "\xE1\xCD" => "\xE6\x85\xB4", + "\xE1\xCE" => "\xE6\x85\x94", + "\xE1\xCF" => "\xE6\x85\xBA", + "\xE1\xD0" => "\xE6\x85\x9B", + "\xE1\xD1" => "\xE6\x85\xA5", + "\xE1\xD2" => "\xE6\x84\xBB", + "\xE1\xD3" => "\xE6\x85\xAA", + "\xE1\xD4" => "\xE6\x85\xA1", + "\xE1\xD5" => "\xE6\x85\x96", + "\xE1\xD6" => "\xE6\x88\xA9", + "\xE1\xD7" => "\xE6\x88\xA7", + "\xE1\xD8" => "\xE6\x88\xAB", + "\xE1\xD9" => "\xE6\x90\xAB", + "\xE1\xDA" => "\xE6\x91\x8D", + "\xE1\xDB" => "\xE6\x91\x9B", + "\xE1\xDC" => "\xE6\x91\x9D", + "\xE1\xDD" => "\xE6\x91\xB4", + "\xE1\xDE" => "\xE6\x91\xB6", + "\xE1\xDF" => "\xE6\x91\xB2", + "\xE1\xE0" => "\xE6\x91\xB3", + "\xE1\xE1" => "\xE6\x91\xBD", + "\xE1\xE2" => "\xE6\x91\xB5", + "\xE1\xE3" => "\xE6\x91\xA6", + "\xE1\xE4" => "\xE6\x92\xA6", + "\xE1\xE5" => "\xE6\x91\x8E", + "\xE1\xE6" => "\xE6\x92\x82", + "\xE1\xE7" => "\xE6\x91\x9E", + "\xE1\xE8" => "\xE6\x91\x9C", + "\xE1\xE9" => "\xE6\x91\x8B", + "\xE1\xEA" => "\xE6\x91\x93", + "\xE1\xEB" => "\xE6\x91\xA0", + "\xE1\xEC" => "\xE6\x91\x90", + "\xE1\xED" => "\xE6\x91\xBF", + "\xE1\xEE" => "\xE6\x90\xBF", + "\xE1\xEF" => "\xE6\x91\xAC", + "\xE1\xF0" => "\xE6\x91\xAB", + "\xE1\xF1" => "\xE6\x91\x99", + "\xE1\xF2" => "\xE6\x91\xA5", + "\xE1\xF3" => "\xE6\x91\xB7", + "\xE1\xF4" => "\xE6\x95\xB3", + "\xE1\xF5" => "\xE6\x96\xA0", + "\xE1\xF6" => "\xE6\x9A\xA1", + "\xE1\xF7" => "\xE6\x9A\xA0", + "\xE1\xF8" => "\xE6\x9A\x9F", + "\xE1\xF9" => "\xE6\x9C\x85", + "\xE1\xFA" => "\xE6\x9C\x84", + "\xE1\xFB" => "\xE6\x9C\xA2", + "\xE1\xFC" => "\xE6\xA6\xB1", + "\xE1\xFD" => "\xE6\xA6\xB6", + "\xE1\xFE" => "\xE6\xA7\x89", + "\xE2\x40" => "\xE6\xA6\xA0", + "\xE2\x41" => "\xE6\xA7\x8E", + "\xE2\x42" => "\xE6\xA6\x96", + "\xE2\x43" => "\xE6\xA6\xB0", + "\xE2\x44" => "\xE6\xA6\xAC", + "\xE2\x45" => "\xE6\xA6\xBC", + "\xE2\x46" => "\xE6\xA6\x91", + "\xE2\x47" => "\xE6\xA6\x99", + "\xE2\x48" => "\xE6\xA6\x8E", + "\xE2\x49" => "\xE6\xA6\xA7", + "\xE2\x4A" => "\xE6\xA6\x8D", + "\xE2\x4B" => "\xE6\xA6\xA9", + "\xE2\x4C" => "\xE6\xA6\xBE", + "\xE2\x4D" => "\xE6\xA6\xAF", + "\xE2\x4E" => "\xE6\xA6\xBF", + "\xE2\x4F" => "\xE6\xA7\x84", + "\xE2\x50" => "\xE6\xA6\xBD", + "\xE2\x51" => "\xE6\xA6\xA4", + "\xE2\x52" => "\xE6\xA7\x94", + "\xE2\x53" => "\xE6\xA6\xB9", + "\xE2\x54" => "\xE6\xA7\x8A", + "\xE2\x55" => "\xE6\xA6\x9A", + "\xE2\x56" => "\xE6\xA7\x8F", + "\xE2\x57" => "\xE6\xA6\xB3", + "\xE2\x58" => "\xE6\xA6\x93", + "\xE2\x59" => "\xE6\xA6\xAA", + "\xE2\x5A" => "\xE6\xA6\xA1", + "\xE2\x5B" => "\xE6\xA6\x9E", + "\xE2\x5C" => "\xE6\xA7\x99", + "\xE2\x5D" => "\xE6\xA6\x97", + "\xE2\x5E" => "\xE6\xA6\x90", + "\xE2\x5F" => "\xE6\xA7\x82", + "\xE2\x60" => "\xE6\xA6\xB5", + "\xE2\x61" => "\xE6\xA6\xA5", + "\xE2\x62" => "\xE6\xA7\x86", + "\xE2\x63" => "\xE6\xAD\x8A", + "\xE2\x64" => "\xE6\xAD\x8D", + "\xE2\x65" => "\xE6\xAD\x8B", + "\xE2\x66" => "\xE6\xAE\x9E", + "\xE2\x67" => "\xE6\xAE\x9F", + "\xE2\x68" => "\xE6\xAE\xA0", + "\xE2\x69" => "\xE6\xAF\x83", + "\xE2\x6A" => "\xE6\xAF\x84", + "\xE2\x6B" => "\xE6\xAF\xBE", + "\xE2\x6C" => "\xE6\xBB\x8E", + "\xE2\x6D" => "\xE6\xBB\xB5", + "\xE2\x6E" => "\xE6\xBB\xB1", + "\xE2\x6F" => "\xE6\xBC\x83", + "\xE2\x70" => "\xE6\xBC\xA5", + "\xE2\x71" => "\xE6\xBB\xB8", + "\xE2\x72" => "\xE6\xBC\xB7", + "\xE2\x73" => "\xE6\xBB\xBB", + "\xE2\x74" => "\xE6\xBC\xAE", + "\xE2\x75" => "\xE6\xBC\x89", + "\xE2\x76" => "\xE6\xBD\x8E", + "\xE2\x77" => "\xE6\xBC\x99", + "\xE2\x78" => "\xE6\xBC\x9A", + "\xE2\x79" => "\xE6\xBC\xA7", + "\xE2\x7A" => "\xE6\xBC\x98", + "\xE2\x7B" => "\xE6\xBC\xBB", + "\xE2\x7C" => "\xE6\xBC\x92", + "\xE2\x7D" => "\xE6\xBB\xAD", + "\xE2\x7E" => "\xE6\xBC\x8A", + "\xE2\xA1" => "\xE6\xBC\xB6", + "\xE2\xA2" => "\xE6\xBD\xB3", + "\xE2\xA3" => "\xE6\xBB\xB9", + "\xE2\xA4" => "\xE6\xBB\xAE", + "\xE2\xA5" => "\xE6\xBC\xAD", + "\xE2\xA6" => "\xE6\xBD\x80", + "\xE2\xA7" => "\xE6\xBC\xB0", + "\xE2\xA8" => "\xE6\xBC\xBC", + "\xE2\xA9" => "\xE6\xBC\xB5", + "\xE2\xAA" => "\xE6\xBB\xAB", + "\xE2\xAB" => "\xE6\xBC\x87", + "\xE2\xAC" => "\xE6\xBC\x8E", + "\xE2\xAD" => "\xE6\xBD\x83", + "\xE2\xAE" => "\xE6\xBC\x85", + "\xE2\xAF" => "\xE6\xBB\xBD", + "\xE2\xB0" => "\xE6\xBB\xB6", + "\xE2\xB1" => "\xE6\xBC\xB9", + "\xE2\xB2" => "\xE6\xBC\x9C", + "\xE2\xB3" => "\xE6\xBB\xBC", + "\xE2\xB4" => "\xE6\xBC\xBA", + "\xE2\xB5" => "\xE6\xBC\x9F", + "\xE2\xB6" => "\xE6\xBC\x8D", + "\xE2\xB7" => "\xE6\xBC\x9E", + "\xE2\xB8" => "\xE6\xBC\x88", + "\xE2\xB9" => "\xE6\xBC\xA1", + "\xE2\xBA" => "\xE7\x86\x87", + "\xE2\xBB" => "\xE7\x86\x90", + "\xE2\xBC" => "\xE7\x86\x89", + "\xE2\xBD" => "\xE7\x86\x80", + "\xE2\xBE" => "\xE7\x86\x85", + "\xE2\xBF" => "\xE7\x86\x82", + "\xE2\xC0" => "\xE7\x86\x8F", + "\xE2\xC1" => "\xE7\x85\xBB", + "\xE2\xC2" => "\xE7\x86\x86", + "\xE2\xC3" => "\xE7\x86\x81", + "\xE2\xC4" => "\xE7\x86\x97", + "\xE2\xC5" => "\xE7\x89\x84", + "\xE2\xC6" => "\xE7\x89\x93", + "\xE2\xC7" => "\xE7\x8A\x97", + "\xE2\xC8" => "\xE7\x8A\x95", + "\xE2\xC9" => "\xE7\x8A\x93", + "\xE2\xCA" => "\xE7\x8D\x83", + "\xE2\xCB" => "\xE7\x8D\x8D", + "\xE2\xCC" => "\xE7\x8D\x91", + "\xE2\xCD" => "\xE7\x8D\x8C", + "\xE2\xCE" => "\xE7\x91\xA2", + "\xE2\xCF" => "\xE7\x91\xB3", + "\xE2\xD0" => "\xE7\x91\xB1", + "\xE2\xD1" => "\xE7\x91\xB5", + "\xE2\xD2" => "\xE7\x91\xB2", + "\xE2\xD3" => "\xE7\x91\xA7", + "\xE2\xD4" => "\xE7\x91\xAE", + "\xE2\xD5" => "\xE7\x94\x80", + "\xE2\xD6" => "\xE7\x94\x82", + "\xE2\xD7" => "\xE7\x94\x83", + "\xE2\xD8" => "\xE7\x95\xBD", + "\xE2\xD9" => "\xE7\x96\x90", + "\xE2\xDA" => "\xE7\x98\x96", + "\xE2\xDB" => "\xE7\x98\x88", + "\xE2\xDC" => "\xE7\x98\x8C", + "\xE2\xDD" => "\xE7\x98\x95", + "\xE2\xDE" => "\xE7\x98\x91", + "\xE2\xDF" => "\xE7\x98\x8A", + "\xE2\xE0" => "\xE7\x98\x94", + "\xE2\xE1" => "\xE7\x9A\xB8", + "\xE2\xE2" => "\xE7\x9E\x81", + "\xE2\xE3" => "\xE7\x9D\xBC", + "\xE2\xE4" => "\xE7\x9E\x85", + "\xE2\xE5" => "\xE7\x9E\x82", + "\xE2\xE6" => "\xE7\x9D\xAE", + "\xE2\xE7" => "\xE7\x9E\x80", + "\xE2\xE8" => "\xE7\x9D\xAF", + "\xE2\xE9" => "\xE7\x9D\xBE", + "\xE2\xEA" => "\xE7\x9E\x83", + "\xE2\xEB" => "\xE7\xA2\xB2", + "\xE2\xEC" => "\xE7\xA2\xAA", + "\xE2\xED" => "\xE7\xA2\xB4", + "\xE2\xEE" => "\xE7\xA2\xAD", + "\xE2\xEF" => "\xE7\xA2\xA8", + "\xE2\xF0" => "\xE7\xA1\xBE", + "\xE2\xF1" => "\xE7\xA2\xAB", + "\xE2\xF2" => "\xE7\xA2\x9E", + "\xE2\xF3" => "\xE7\xA2\xA5", + "\xE2\xF4" => "\xE7\xA2\xA0", + "\xE2\xF5" => "\xE7\xA2\xAC", + "\xE2\xF6" => "\xE7\xA2\xA2", + "\xE2\xF7" => "\xE7\xA2\xA4", + "\xE2\xF8" => "\xE7\xA6\x98", + "\xE2\xF9" => "\xE7\xA6\x8A", + "\xE2\xFA" => "\xE7\xA6\x8B", + "\xE2\xFB" => "\xE7\xA6\x96", + "\xE2\xFC" => "\xE7\xA6\x95", + "\xE2\xFD" => "\xE7\xA6\x94", + "\xE2\xFE" => "\xE7\xA6\x93", + "\xE3\x40" => "\xE7\xA6\x97", + "\xE3\x41" => "\xE7\xA6\x88", + "\xE3\x42" => "\xE7\xA6\x92", + "\xE3\x43" => "\xE7\xA6\x90", + "\xE3\x44" => "\xE7\xA8\xAB", + "\xE3\x45" => "\xE7\xA9\x8A", + "\xE3\x46" => "\xE7\xA8\xB0", + "\xE3\x47" => "\xE7\xA8\xAF", + "\xE3\x48" => "\xE7\xA8\xA8", + "\xE3\x49" => "\xE7\xA8\xA6", + "\xE3\x4A" => "\xE7\xAA\xA8", + "\xE3\x4B" => "\xE7\xAA\xAB", + "\xE3\x4C" => "\xE7\xAA\xAC", + "\xE3\x4D" => "\xE7\xAB\xAE", + "\xE3\x4E" => "\xE7\xAE\x88", + "\xE3\x4F" => "\xE7\xAE\x9C", + "\xE3\x50" => "\xE7\xAE\x8A", + "\xE3\x51" => "\xE7\xAE\x91", + "\xE3\x52" => "\xE7\xAE\x90", + "\xE3\x53" => "\xE7\xAE\x96", + "\xE3\x54" => "\xE7\xAE\x8D", + "\xE3\x55" => "\xE7\xAE\x8C", + "\xE3\x56" => "\xE7\xAE\x9B", + "\xE3\x57" => "\xE7\xAE\x8E", + "\xE3\x58" => "\xE7\xAE\x85", + "\xE3\x59" => "\xE7\xAE\x98", + "\xE3\x5A" => "\xE5\x8A\x84", + "\xE3\x5B" => "\xE7\xAE\x99", + "\xE3\x5C" => "\xE7\xAE\xA4", + "\xE3\x5D" => "\xE7\xAE\x82", + "\xE3\x5E" => "\xE7\xB2\xBB", + "\xE3\x5F" => "\xE7\xB2\xBF", + "\xE3\x60" => "\xE7\xB2\xBC", + "\xE3\x61" => "\xE7\xB2\xBA", + "\xE3\x62" => "\xE7\xB6\xA7", + "\xE3\x63" => "\xE7\xB6\xB7", + "\xE3\x64" => "\xE7\xB7\x82", + "\xE3\x65" => "\xE7\xB6\xA3", + "\xE3\x66" => "\xE7\xB6\xAA", + "\xE3\x67" => "\xE7\xB7\x81", + "\xE3\x68" => "\xE7\xB7\x80", + "\xE3\x69" => "\xE7\xB7\x85", + "\xE3\x6A" => "\xE7\xB6\x9D", + "\xE3\x6B" => "\xE7\xB7\x8E", + "\xE3\x6C" => "\xE7\xB7\x84", + "\xE3\x6D" => "\xE7\xB7\x86", + "\xE3\x6E" => "\xE7\xB7\x8B", + "\xE3\x6F" => "\xE7\xB7\x8C", + "\xE3\x70" => "\xE7\xB6\xAF", + "\xE3\x71" => "\xE7\xB6\xB9", + "\xE3\x72" => "\xE7\xB6\x96", + "\xE3\x73" => "\xE7\xB6\xBC", + "\xE3\x74" => "\xE7\xB6\x9F", + "\xE3\x75" => "\xE7\xB6\xA6", + "\xE3\x76" => "\xE7\xB6\xAE", + "\xE3\x77" => "\xE7\xB6\xA9", + "\xE3\x78" => "\xE7\xB6\xA1", + "\xE3\x79" => "\xE7\xB7\x89", + "\xE3\x7A" => "\xE7\xBD\xB3", + "\xE3\x7B" => "\xE7\xBF\xA2", + "\xE3\x7C" => "\xE7\xBF\xA3", + "\xE3\x7D" => "\xE7\xBF\xA5", + "\xE3\x7E" => "\xE7\xBF\x9E", + "\xE3\xA1" => "\xE8\x80\xA4", + "\xE3\xA2" => "\xE8\x81\x9D", + "\xE3\xA3" => "\xE8\x81\x9C", + "\xE3\xA4" => "\xE8\x86\x89", + "\xE3\xA5" => "\xE8\x86\x86", + "\xE3\xA6" => "\xE8\x86\x83", + "\xE3\xA7" => "\xE8\x86\x87", + "\xE3\xA8" => "\xE8\x86\x8D", + "\xE3\xA9" => "\xE8\x86\x8C", + "\xE3\xAA" => "\xE8\x86\x8B", + "\xE3\xAB" => "\xE8\x88\x95", + "\xE3\xAC" => "\xE8\x92\x97", + "\xE3\xAD" => "\xE8\x92\xA4", + "\xE3\xAE" => "\xE8\x92\xA1", + "\xE3\xAF" => "\xE8\x92\x9F", + "\xE3\xB0" => "\xE8\x92\xBA", + "\xE3\xB1" => "\xE8\x93\x8E", + "\xE3\xB2" => "\xE8\x93\x82", + "\xE3\xB3" => "\xE8\x92\xAC", + "\xE3\xB4" => "\xE8\x92\xAE", + "\xE3\xB5" => "\xE8\x92\xAB", + "\xE3\xB6" => "\xE8\x92\xB9", + "\xE3\xB7" => "\xE8\x92\xB4", + "\xE3\xB8" => "\xE8\x93\x81", + "\xE3\xB9" => "\xE8\x93\x8D", + "\xE3\xBA" => "\xE8\x92\xAA", + "\xE3\xBB" => "\xE8\x92\x9A", + "\xE3\xBC" => "\xE8\x92\xB1", + "\xE3\xBD" => "\xE8\x93\x90", + "\xE3\xBE" => "\xE8\x92\x9D", + "\xE3\xBF" => "\xE8\x92\xA7", + "\xE3\xC0" => "\xE8\x92\xBB", + "\xE3\xC1" => "\xE8\x92\xA2", + "\xE3\xC2" => "\xE8\x92\x94", + "\xE3\xC3" => "\xE8\x93\x87", + "\xE3\xC4" => "\xE8\x93\x8C", + "\xE3\xC5" => "\xE8\x92\x9B", + "\xE3\xC6" => "\xE8\x92\xA9", + "\xE3\xC7" => "\xE8\x92\xAF", + "\xE3\xC8" => "\xE8\x92\xA8", + "\xE3\xC9" => "\xE8\x93\x96", + "\xE3\xCA" => "\xE8\x92\x98", + "\xE3\xCB" => "\xE8\x92\xB6", + "\xE3\xCC" => "\xE8\x93\x8F", + "\xE3\xCD" => "\xE8\x92\xA0", + "\xE3\xCE" => "\xE8\x93\x97", + "\xE3\xCF" => "\xE8\x93\x94", + "\xE3\xD0" => "\xE8\x93\x92", + "\xE3\xD1" => "\xE8\x93\x9B", + "\xE3\xD2" => "\xE8\x92\xB0", + "\xE3\xD3" => "\xE8\x92\x91", + "\xE3\xD4" => "\xE8\x99\xA1", + "\xE3\xD5" => "\xE8\x9C\xB3", + "\xE3\xD6" => "\xE8\x9C\xA3", + "\xE3\xD7" => "\xE8\x9C\xA8", + "\xE3\xD8" => "\xE8\x9D\xAB", + "\xE3\xD9" => "\xE8\x9D\x80", + "\xE3\xDA" => "\xE8\x9C\xAE", + "\xE3\xDB" => "\xE8\x9C\x9E", + "\xE3\xDC" => "\xE8\x9C\xA1", + "\xE3\xDD" => "\xE8\x9C\x99", + "\xE3\xDE" => "\xE8\x9C\x9B", + "\xE3\xDF" => "\xE8\x9D\x83", + "\xE3\xE0" => "\xE8\x9C\xAC", + "\xE3\xE1" => "\xE8\x9D\x81", + "\xE3\xE2" => "\xE8\x9C\xBE", + "\xE3\xE3" => "\xE8\x9D\x86", + "\xE3\xE4" => "\xE8\x9C\xA0", + "\xE3\xE5" => "\xE8\x9C\xB2", + "\xE3\xE6" => "\xE8\x9C\xAA", + "\xE3\xE7" => "\xE8\x9C\xAD", + "\xE3\xE8" => "\xE8\x9C\xBC", + "\xE3\xE9" => "\xE8\x9C\x92", + "\xE3\xEA" => "\xE8\x9C\xBA", + "\xE3\xEB" => "\xE8\x9C\xB1", + "\xE3\xEC" => "\xE8\x9C\xB5", + "\xE3\xED" => "\xE8\x9D\x82", + "\xE3\xEE" => "\xE8\x9C\xA6", + "\xE3\xEF" => "\xE8\x9C\xA7", + "\xE3\xF0" => "\xE8\x9C\xB8", + "\xE3\xF1" => "\xE8\x9C\xA4", + "\xE3\xF2" => "\xE8\x9C\x9A", + "\xE3\xF3" => "\xE8\x9C\xB0", + "\xE3\xF4" => "\xE8\x9C\x91", + "\xE3\xF5" => "\xE8\xA3\xB7", + "\xE3\xF6" => "\xE8\xA3\xA7", + "\xE3\xF7" => "\xE8\xA3\xB1", + "\xE3\xF8" => "\xE8\xA3\xB2", + "\xE3\xF9" => "\xE8\xA3\xBA", + "\xE3\xFA" => "\xE8\xA3\xBE", + "\xE3\xFB" => "\xE8\xA3\xAE", + "\xE3\xFC" => "\xE8\xA3\xBC", + "\xE3\xFD" => "\xE8\xA3\xB6", + "\xE3\xFE" => "\xE8\xA3\xBB", + "\xE4\x40" => "\xE8\xA3\xB0", + "\xE4\x41" => "\xE8\xA3\xAC", + "\xE4\x42" => "\xE8\xA3\xAB", + "\xE4\x43" => "\xE8\xA6\x9D", + "\xE4\x44" => "\xE8\xA6\xA1", + "\xE4\x45" => "\xE8\xA6\x9F", + "\xE4\x46" => "\xE8\xA6\x9E", + "\xE4\x47" => "\xE8\xA7\xA9", + "\xE4\x48" => "\xE8\xA7\xAB", + "\xE4\x49" => "\xE8\xA7\xA8", + "\xE4\x4A" => "\xE8\xAA\xAB", + "\xE4\x4B" => "\xE8\xAA\x99", + "\xE4\x4C" => "\xE8\xAA\x8B", + "\xE4\x4D" => "\xE8\xAA\x92", + "\xE4\x4E" => "\xE8\xAA\x8F", + "\xE4\x4F" => "\xE8\xAA\x96", + "\xE4\x50" => "\xE8\xB0\xBD", + "\xE4\x51" => "\xE8\xB1\xA8", + "\xE4\x52" => "\xE8\xB1\xA9", + "\xE4\x53" => "\xE8\xB3\x95", + "\xE4\x54" => "\xE8\xB3\x8F", + "\xE4\x55" => "\xE8\xB3\x97", + "\xE4\x56" => "\xE8\xB6\x96", + "\xE4\x57" => "\xE8\xB8\x89", + "\xE4\x58" => "\xE8\xB8\x82", + "\xE4\x59" => "\xE8\xB7\xBF", + "\xE4\x5A" => "\xE8\xB8\x8D", + "\xE4\x5B" => "\xE8\xB7\xBD", + "\xE4\x5C" => "\xE8\xB8\x8A", + "\xE4\x5D" => "\xE8\xB8\x83", + "\xE4\x5E" => "\xE8\xB8\x87", + "\xE4\x5F" => "\xE8\xB8\x86", + "\xE4\x60" => "\xE8\xB8\x85", + "\xE4\x61" => "\xE8\xB7\xBE", + "\xE4\x62" => "\xE8\xB8\x80", + "\xE4\x63" => "\xE8\xB8\x84", + "\xE4\x64" => "\xE8\xBC\x90", + "\xE4\x65" => "\xE8\xBC\x91", + "\xE4\x66" => "\xE8\xBC\x8E", + "\xE4\x67" => "\xE8\xBC\x8D", + "\xE4\x68" => "\xE9\x84\xA3", + "\xE4\x69" => "\xE9\x84\x9C", + "\xE4\x6A" => "\xE9\x84\xA0", + "\xE4\x6B" => "\xE9\x84\xA2", + "\xE4\x6C" => "\xE9\x84\x9F", + "\xE4\x6D" => "\xE9\x84\x9D", + "\xE4\x6E" => "\xE9\x84\x9A", + "\xE4\x6F" => "\xE9\x84\xA4", + "\xE4\x70" => "\xE9\x84\xA1", + "\xE4\x71" => "\xE9\x84\x9B", + "\xE4\x72" => "\xE9\x85\xBA", + "\xE4\x73" => "\xE9\x85\xB2", + "\xE4\x74" => "\xE9\x85\xB9", + "\xE4\x75" => "\xE9\x85\xB3", + "\xE4\x76" => "\xE9\x8A\xA5", + "\xE4\x77" => "\xE9\x8A\xA4", + "\xE4\x78" => "\xE9\x89\xB6", + "\xE4\x79" => "\xE9\x8A\x9B", + "\xE4\x7A" => "\xE9\x89\xBA", + "\xE4\x7B" => "\xE9\x8A\xA0", + "\xE4\x7C" => "\xE9\x8A\x94", + "\xE4\x7D" => "\xE9\x8A\xAA", + "\xE4\x7E" => "\xE9\x8A\x8D", + "\xE4\xA1" => "\xE9\x8A\xA6", + "\xE4\xA2" => "\xE9\x8A\x9A", + "\xE4\xA3" => "\xE9\x8A\xAB", + "\xE4\xA4" => "\xE9\x89\xB9", + "\xE4\xA5" => "\xE9\x8A\x97", + "\xE4\xA6" => "\xE9\x89\xBF", + "\xE4\xA7" => "\xE9\x8A\xA3", + "\xE4\xA8" => "\xE9\x8B\xAE", + "\xE4\xA9" => "\xE9\x8A\x8E", + "\xE4\xAA" => "\xE9\x8A\x82", + "\xE4\xAB" => "\xE9\x8A\x95", + "\xE4\xAC" => "\xE9\x8A\xA2", + "\xE4\xAD" => "\xE9\x89\xBD", + "\xE4\xAE" => "\xE9\x8A\x88", + "\xE4\xAF" => "\xE9\x8A\xA1", + "\xE4\xB0" => "\xE9\x8A\x8A", + "\xE4\xB1" => "\xE9\x8A\x86", + "\xE4\xB2" => "\xE9\x8A\x8C", + "\xE4\xB3" => "\xE9\x8A\x99", + "\xE4\xB4" => "\xE9\x8A\xA7", + "\xE4\xB5" => "\xE9\x89\xBE", + "\xE4\xB6" => "\xE9\x8A\x87", + "\xE4\xB7" => "\xE9\x8A\xA9", + "\xE4\xB8" => "\xE9\x8A\x9D", + "\xE4\xB9" => "\xE9\x8A\x8B", + "\xE4\xBA" => "\xE9\x88\xAD", + "\xE4\xBB" => "\xE9\x9A\x9E", + "\xE4\xBC" => "\xE9\x9A\xA1", + "\xE4\xBD" => "\xE9\x9B\xBF", + "\xE4\xBE" => "\xE9\x9D\x98", + "\xE4\xBF" => "\xE9\x9D\xBD", + "\xE4\xC0" => "\xE9\x9D\xBA", + "\xE4\xC1" => "\xE9\x9D\xBE", + "\xE4\xC2" => "\xE9\x9E\x83", + "\xE4\xC3" => "\xE9\x9E\x80", + "\xE4\xC4" => "\xE9\x9E\x82", + "\xE4\xC5" => "\xE9\x9D\xBB", + "\xE4\xC6" => "\xE9\x9E\x84", + "\xE4\xC7" => "\xE9\x9E\x81", + "\xE4\xC8" => "\xE9\x9D\xBF", + "\xE4\xC9" => "\xE9\x9F\x8E", + "\xE4\xCA" => "\xE9\x9F\x8D", + "\xE4\xCB" => "\xE9\xA0\x96", + "\xE4\xCC" => "\xE9\xA2\xAD", + "\xE4\xCD" => "\xE9\xA2\xAE", + "\xE4\xCE" => "\xE9\xA4\x82", + "\xE4\xCF" => "\xE9\xA4\x80", + "\xE4\xD0" => "\xE9\xA4\x87", + "\xE4\xD1" => "\xE9\xA6\x9D", + "\xE4\xD2" => "\xE9\xA6\x9C", + "\xE4\xD3" => "\xE9\xA7\x83", + "\xE4\xD4" => "\xE9\xA6\xB9", + "\xE4\xD5" => "\xE9\xA6\xBB", + "\xE4\xD6" => "\xE9\xA6\xBA", + "\xE4\xD7" => "\xE9\xA7\x82", + "\xE4\xD8" => "\xE9\xA6\xBD", + "\xE4\xD9" => "\xE9\xA7\x87", + "\xE4\xDA" => "\xE9\xAA\xB1", + "\xE4\xDB" => "\xE9\xAB\xA3", + "\xE4\xDC" => "\xE9\xAB\xA7", + "\xE4\xDD" => "\xE9\xAC\xBE", + "\xE4\xDE" => "\xE9\xAC\xBF", + "\xE4\xDF" => "\xE9\xAD\xA0", + "\xE4\xE0" => "\xE9\xAD\xA1", + "\xE4\xE1" => "\xE9\xAD\x9F", + "\xE4\xE2" => "\xE9\xB3\xB1", + "\xE4\xE3" => "\xE9\xB3\xB2", + "\xE4\xE4" => "\xE9\xB3\xB5", + "\xE4\xE5" => "\xE9\xBA\xA7", + "\xE4\xE6" => "\xE5\x83\xBF", + "\xE4\xE7" => "\xE5\x84\x83", + "\xE4\xE8" => "\xE5\x84\xB0", + "\xE4\xE9" => "\xE5\x83\xB8", + "\xE4\xEA" => "\xE5\x84\x86", + "\xE4\xEB" => "\xE5\x84\x87", + "\xE4\xEC" => "\xE5\x83\xB6", + "\xE4\xED" => "\xE5\x83\xBE", + "\xE4\xEE" => "\xE5\x84\x8B", + "\xE4\xEF" => "\xE5\x84\x8C", + "\xE4\xF0" => "\xE5\x83\xBD", + "\xE4\xF1" => "\xE5\x84\x8A", + "\xE4\xF2" => "\xE5\x8A\x8B", + "\xE4\xF3" => "\xE5\x8A\x8C", + "\xE4\xF4" => "\xE5\x8B\xB1", + "\xE4\xF5" => "\xE5\x8B\xAF", + "\xE4\xF6" => "\xE5\x99\x88", + "\xE4\xF7" => "\xE5\x99\x82", + "\xE4\xF8" => "\xE5\x99\x8C", + "\xE4\xF9" => "\xE5\x98\xB5", + "\xE4\xFA" => "\xE5\x99\x81", + "\xE4\xFB" => "\xE5\x99\x8A", + "\xE4\xFC" => "\xE5\x99\x89", + "\xE4\xFD" => "\xE5\x99\x86", + "\xE4\xFE" => "\xE5\x99\x98", + "\xE5\x40" => "\xE5\x99\x9A", + "\xE5\x41" => "\xE5\x99\x80", + "\xE5\x42" => "\xE5\x98\xB3", + "\xE5\x43" => "\xE5\x98\xBD", + "\xE5\x44" => "\xE5\x98\xAC", + "\xE5\x45" => "\xE5\x98\xBE", + "\xE5\x46" => "\xE5\x98\xB8", + "\xE5\x47" => "\xE5\x98\xAA", + "\xE5\x48" => "\xE5\x98\xBA", + "\xE5\x49" => "\xE5\x9C\x9A", + "\xE5\x4A" => "\xE5\xA2\xAB", + "\xE5\x4B" => "\xE5\xA2\x9D", + "\xE5\x4C" => "\xE5\xA2\xB1", + "\xE5\x4D" => "\xE5\xA2\xA0", + "\xE5\x4E" => "\xE5\xA2\xA3", + "\xE5\x4F" => "\xE5\xA2\xAF", + "\xE5\x50" => "\xE5\xA2\xAC", + "\xE5\x51" => "\xE5\xA2\xA5", + "\xE5\x52" => "\xE5\xA2\xA1", + "\xE5\x53" => "\xE5\xA3\xBF", + "\xE5\x54" => "\xE5\xAB\xBF", + "\xE5\x55" => "\xE5\xAB\xB4", + "\xE5\x56" => "\xE5\xAB\xBD", + "\xE5\x57" => "\xE5\xAB\xB7", + "\xE5\x58" => "\xE5\xAB\xB6", + "\xE5\x59" => "\xE5\xAC\x83", + "\xE5\x5A" => "\xE5\xAB\xB8", + "\xE5\x5B" => "\xE5\xAC\x82", + "\xE5\x5C" => "\xE5\xAB\xB9", + "\xE5\x5D" => "\xE5\xAC\x81", + "\xE5\x5E" => "\xE5\xAC\x87", + "\xE5\x5F" => "\xE5\xAC\x85", + "\xE5\x60" => "\xE5\xAC\x8F", + "\xE5\x61" => "\xE5\xB1\xA7", + "\xE5\x62" => "\xE5\xB6\x99", + "\xE5\x63" => "\xE5\xB6\x97", + "\xE5\x64" => "\xE5\xB6\x9F", + "\xE5\x65" => "\xE5\xB6\x92", + "\xE5\x66" => "\xE5\xB6\xA2", + "\xE5\x67" => "\xE5\xB6\x93", + "\xE5\x68" => "\xE5\xB6\x95", + "\xE5\x69" => "\xE5\xB6\xA0", + "\xE5\x6A" => "\xE5\xB6\x9C", + "\xE5\x6B" => "\xE5\xB6\xA1", + "\xE5\x6C" => "\xE5\xB6\x9A", + "\xE5\x6D" => "\xE5\xB6\x9E", + "\xE5\x6E" => "\xE5\xB9\xA9", + "\xE5\x6F" => "\xE5\xB9\x9D", + "\xE5\x70" => "\xE5\xB9\xA0", + "\xE5\x71" => "\xE5\xB9\x9C", + "\xE5\x72" => "\xE7\xB7\xB3", + "\xE5\x73" => "\xE5\xBB\x9B", + "\xE5\x74" => "\xE5\xBB\x9E", + "\xE5\x75" => "\xE5\xBB\xA1", + "\xE5\x76" => "\xE5\xBD\x89", + "\xE5\x77" => "\xE5\xBE\xB2", + "\xE5\x78" => "\xE6\x86\x8B", + "\xE5\x79" => "\xE6\x86\x83", + "\xE5\x7A" => "\xE6\x85\xB9", + "\xE5\x7B" => "\xE6\x86\xB1", + "\xE5\x7C" => "\xE6\x86\xB0", + "\xE5\x7D" => "\xE6\x86\xA2", + "\xE5\x7E" => "\xE6\x86\x89", + "\xE5\xA1" => "\xE6\x86\x9B", + "\xE5\xA2" => "\xE6\x86\x93", + "\xE5\xA3" => "\xE6\x86\xAF", + "\xE5\xA4" => "\xE6\x86\xAD", + "\xE5\xA5" => "\xE6\x86\x9F", + "\xE5\xA6" => "\xE6\x86\x92", + "\xE5\xA7" => "\xE6\x86\xAA", + "\xE5\xA8" => "\xE6\x86\xA1", + "\xE5\xA9" => "\xE6\x86\x8D", + "\xE5\xAA" => "\xE6\x85\xA6", + "\xE5\xAB" => "\xE6\x86\xB3", + "\xE5\xAC" => "\xE6\x88\xAD", + "\xE5\xAD" => "\xE6\x91\xAE", + "\xE5\xAE" => "\xE6\x91\xB0", + "\xE5\xAF" => "\xE6\x92\x96", + "\xE5\xB0" => "\xE6\x92\xA0", + "\xE5\xB1" => "\xE6\x92\x85", + "\xE5\xB2" => "\xE6\x92\x97", + "\xE5\xB3" => "\xE6\x92\x9C", + "\xE5\xB4" => "\xE6\x92\x8F", + "\xE5\xB5" => "\xE6\x92\x8B", + "\xE5\xB6" => "\xE6\x92\x8A", + "\xE5\xB7" => "\xE6\x92\x8C", + "\xE5\xB8" => "\xE6\x92\xA3", + "\xE5\xB9" => "\xE6\x92\x9F", + "\xE5\xBA" => "\xE6\x91\xA8", + "\xE5\xBB" => "\xE6\x92\xB1", + "\xE5\xBC" => "\xE6\x92\x98", + "\xE5\xBD" => "\xE6\x95\xB6", + "\xE5\xBE" => "\xE6\x95\xBA", + "\xE5\xBF" => "\xE6\x95\xB9", + "\xE5\xC0" => "\xE6\x95\xBB", + "\xE5\xC1" => "\xE6\x96\xB2", + "\xE5\xC2" => "\xE6\x96\xB3", + "\xE5\xC3" => "\xE6\x9A\xB5", + "\xE5\xC4" => "\xE6\x9A\xB0", + "\xE5\xC5" => "\xE6\x9A\xA9", + "\xE5\xC6" => "\xE6\x9A\xB2", + "\xE5\xC7" => "\xE6\x9A\xB7", + "\xE5\xC8" => "\xE6\x9A\xAA", + "\xE5\xC9" => "\xE6\x9A\xAF", + "\xE5\xCA" => "\xE6\xA8\x80", + "\xE5\xCB" => "\xE6\xA8\x86", + "\xE5\xCC" => "\xE6\xA8\x97", + "\xE5\xCD" => "\xE6\xA7\xA5", + "\xE5\xCE" => "\xE6\xA7\xB8", + "\xE5\xCF" => "\xE6\xA8\x95", + "\xE5\xD0" => "\xE6\xA7\xB1", + "\xE5\xD1" => "\xE6\xA7\xA4", + "\xE5\xD2" => "\xE6\xA8\xA0", + "\xE5\xD3" => "\xE6\xA7\xBF", + "\xE5\xD4" => "\xE6\xA7\xAC", + "\xE5\xD5" => "\xE6\xA7\xA2", + "\xE5\xD6" => "\xE6\xA8\x9B", + "\xE5\xD7" => "\xE6\xA8\x9D", + "\xE5\xD8" => "\xE6\xA7\xBE", + "\xE5\xD9" => "\xE6\xA8\xA7", + "\xE5\xDA" => "\xE6\xA7\xB2", + "\xE5\xDB" => "\xE6\xA7\xAE", + "\xE5\xDC" => "\xE6\xA8\x94", + "\xE5\xDD" => "\xE6\xA7\xB7", + "\xE5\xDE" => "\xE6\xA7\xA7", + "\xE5\xDF" => "\xE6\xA9\x80", + "\xE5\xE0" => "\xE6\xA8\x88", + "\xE5\xE1" => "\xE6\xA7\xA6", + "\xE5\xE2" => "\xE6\xA7\xBB", + "\xE5\xE3" => "\xE6\xA8\x8D", + "\xE5\xE4" => "\xE6\xA7\xBC", + "\xE5\xE5" => "\xE6\xA7\xAB", + "\xE5\xE6" => "\xE6\xA8\x89", + "\xE5\xE7" => "\xE6\xA8\x84", + "\xE5\xE8" => "\xE6\xA8\x98", + "\xE5\xE9" => "\xE6\xA8\xA5", + "\xE5\xEA" => "\xE6\xA8\x8F", + "\xE5\xEB" => "\xE6\xA7\xB6", + "\xE5\xEC" => "\xE6\xA8\xA6", + "\xE5\xED" => "\xE6\xA8\x87", + "\xE5\xEE" => "\xE6\xA7\xB4", + "\xE5\xEF" => "\xE6\xA8\x96", + "\xE5\xF0" => "\xE6\xAD\x91", + "\xE5\xF1" => "\xE6\xAE\xA5", + "\xE5\xF2" => "\xE6\xAE\xA3", + "\xE5\xF3" => "\xE6\xAE\xA2", + "\xE5\xF4" => "\xE6\xAE\xA6", + "\xE5\xF5" => "\xE6\xB0\x81", + "\xE5\xF6" => "\xE6\xB0\x80", + "\xE5\xF7" => "\xE6\xAF\xBF", + "\xE5\xF8" => "\xE6\xB0\x82", + "\xE5\xF9" => "\xE6\xBD\x81", + "\xE5\xFA" => "\xE6\xBC\xA6", + "\xE5\xFB" => "\xE6\xBD\xBE", + "\xE5\xFC" => "\xE6\xBE\x87", + "\xE5\xFD" => "\xE6\xBF\x86", + "\xE5\xFE" => "\xE6\xBE\x92", + "\xE6\x40" => "\xE6\xBE\x8D", + "\xE6\x41" => "\xE6\xBE\x89", + "\xE6\x42" => "\xE6\xBE\x8C", + "\xE6\x43" => "\xE6\xBD\xA2", + "\xE6\x44" => "\xE6\xBD\x8F", + "\xE6\x45" => "\xE6\xBE\x85", + "\xE6\x46" => "\xE6\xBD\x9A", + "\xE6\x47" => "\xE6\xBE\x96", + "\xE6\x48" => "\xE6\xBD\xB6", + "\xE6\x49" => "\xE6\xBD\xAC", + "\xE6\x4A" => "\xE6\xBE\x82", + "\xE6\x4B" => "\xE6\xBD\x95", + "\xE6\x4C" => "\xE6\xBD\xB2", + "\xE6\x4D" => "\xE6\xBD\x92", + "\xE6\x4E" => "\xE6\xBD\x90", + "\xE6\x4F" => "\xE6\xBD\x97", + "\xE6\x50" => "\xE6\xBE\x94", + "\xE6\x51" => "\xE6\xBE\x93", + "\xE6\x52" => "\xE6\xBD\x9D", + "\xE6\x53" => "\xE6\xBC\x80", + "\xE6\x54" => "\xE6\xBD\xA1", + "\xE6\x55" => "\xE6\xBD\xAB", + "\xE6\x56" => "\xE6\xBD\xBD", + "\xE6\x57" => "\xE6\xBD\xA7", + "\xE6\x58" => "\xE6\xBE\x90", + "\xE6\x59" => "\xE6\xBD\x93", + "\xE6\x5A" => "\xE6\xBE\x8B", + "\xE6\x5B" => "\xE6\xBD\xA9", + "\xE6\x5C" => "\xE6\xBD\xBF", + "\xE6\x5D" => "\xE6\xBE\x95", + "\xE6\x5E" => "\xE6\xBD\xA3", + "\xE6\x5F" => "\xE6\xBD\xB7", + "\xE6\x60" => "\xE6\xBD\xAA", + "\xE6\x61" => "\xE6\xBD\xBB", + "\xE6\x62" => "\xE7\x86\xB2", + "\xE6\x63" => "\xE7\x86\xAF", + "\xE6\x64" => "\xE7\x86\x9B", + "\xE6\x65" => "\xE7\x86\xB0", + "\xE6\x66" => "\xE7\x86\xA0", + "\xE6\x67" => "\xE7\x86\x9A", + "\xE6\x68" => "\xE7\x86\xA9", + "\xE6\x69" => "\xE7\x86\xB5", + "\xE6\x6A" => "\xE7\x86\x9D", + "\xE6\x6B" => "\xE7\x86\xA5", + "\xE6\x6C" => "\xE7\x86\x9E", + "\xE6\x6D" => "\xE7\x86\xA4", + "\xE6\x6E" => "\xE7\x86\xA1", + "\xE6\x6F" => "\xE7\x86\xAA", + "\xE6\x70" => "\xE7\x86\x9C", + "\xE6\x71" => "\xE7\x86\xA7", + "\xE6\x72" => "\xE7\x86\xB3", + "\xE6\x73" => "\xE7\x8A\x98", + "\xE6\x74" => "\xE7\x8A\x9A", + "\xE6\x75" => "\xE7\x8D\x98", + "\xE6\x76" => "\xE7\x8D\x92", + "\xE6\x77" => "\xE7\x8D\x9E", + "\xE6\x78" => "\xE7\x8D\x9F", + "\xE6\x79" => "\xE7\x8D\xA0", + "\xE6\x7A" => "\xE7\x8D\x9D", + "\xE6\x7B" => "\xE7\x8D\x9B", + "\xE6\x7C" => "\xE7\x8D\xA1", + "\xE6\x7D" => "\xE7\x8D\x9A", + "\xE6\x7E" => "\xE7\x8D\x99", + "\xE6\xA1" => "\xE7\x8D\xA2", + "\xE6\xA2" => "\xE7\x92\x87", + "\xE6\xA3" => "\xE7\x92\x89", + "\xE6\xA4" => "\xE7\x92\x8A", + "\xE6\xA5" => "\xE7\x92\x86", + "\xE6\xA6" => "\xE7\x92\x81", + "\xE6\xA7" => "\xE7\x91\xBD", + "\xE6\xA8" => "\xE7\x92\x85", + "\xE6\xA9" => "\xE7\x92\x88", + "\xE6\xAA" => "\xE7\x91\xBC", + "\xE6\xAB" => "\xE7\x91\xB9", + "\xE6\xAC" => "\xE7\x94\x88", + "\xE6\xAD" => "\xE7\x94\x87", + "\xE6\xAE" => "\xE7\x95\xBE", + "\xE6\xAF" => "\xE7\x98\xA5", + "\xE6\xB0" => "\xE7\x98\x9E", + "\xE6\xB1" => "\xE7\x98\x99", + "\xE6\xB2" => "\xE7\x98\x9D", + "\xE6\xB3" => "\xE7\x98\x9C", + "\xE6\xB4" => "\xE7\x98\xA3", + "\xE6\xB5" => "\xE7\x98\x9A", + "\xE6\xB6" => "\xE7\x98\xA8", + "\xE6\xB7" => "\xE7\x98\x9B", + "\xE6\xB8" => "\xE7\x9A\x9C", + "\xE6\xB9" => "\xE7\x9A\x9D", + "\xE6\xBA" => "\xE7\x9A\x9E", + "\xE6\xBB" => "\xE7\x9A\x9B", + "\xE6\xBC" => "\xE7\x9E\x8D", + "\xE6\xBD" => "\xE7\x9E\x8F", + "\xE6\xBE" => "\xE7\x9E\x89", + "\xE6\xBF" => "\xE7\x9E\x88", + "\xE6\xC0" => "\xE7\xA3\x8D", + "\xE6\xC1" => "\xE7\xA2\xBB", + "\xE6\xC2" => "\xE7\xA3\x8F", + "\xE6\xC3" => "\xE7\xA3\x8C", + "\xE6\xC4" => "\xE7\xA3\x91", + "\xE6\xC5" => "\xE7\xA3\x8E", + "\xE6\xC6" => "\xE7\xA3\x94", + "\xE6\xC7" => "\xE7\xA3\x88", + "\xE6\xC8" => "\xE7\xA3\x83", + "\xE6\xC9" => "\xE7\xA3\x84", + "\xE6\xCA" => "\xE7\xA3\x89", + "\xE6\xCB" => "\xE7\xA6\x9A", + "\xE6\xCC" => "\xE7\xA6\xA1", + "\xE6\xCD" => "\xE7\xA6\xA0", + "\xE6\xCE" => "\xE7\xA6\x9C", + "\xE6\xCF" => "\xE7\xA6\xA2", + "\xE6\xD0" => "\xE7\xA6\x9B", + "\xE6\xD1" => "\xE6\xAD\xB6", + "\xE6\xD2" => "\xE7\xA8\xB9", + "\xE6\xD3" => "\xE7\xAA\xB2", + "\xE6\xD4" => "\xE7\xAA\xB4", + "\xE6\xD5" => "\xE7\xAA\xB3", + "\xE6\xD6" => "\xE7\xAE\xB7", + "\xE6\xD7" => "\xE7\xAF\x8B", + "\xE6\xD8" => "\xE7\xAE\xBE", + "\xE6\xD9" => "\xE7\xAE\xAC", + "\xE6\xDA" => "\xE7\xAF\x8E", + "\xE6\xDB" => "\xE7\xAE\xAF", + "\xE6\xDC" => "\xE7\xAE\xB9", + "\xE6\xDD" => "\xE7\xAF\x8A", + "\xE6\xDE" => "\xE7\xAE\xB5", + "\xE6\xDF" => "\xE7\xB3\x85", + "\xE6\xE0" => "\xE7\xB3\x88", + "\xE6\xE1" => "\xE7\xB3\x8C", + "\xE6\xE2" => "\xE7\xB3\x8B", + "\xE6\xE3" => "\xE7\xB7\xB7", + "\xE6\xE4" => "\xE7\xB7\x9B", + "\xE6\xE5" => "\xE7\xB7\xAA", + "\xE6\xE6" => "\xE7\xB7\xA7", + "\xE6\xE7" => "\xE7\xB7\x97", + "\xE6\xE8" => "\xE7\xB7\xA1", + "\xE6\xE9" => "\xE7\xB8\x83", + "\xE6\xEA" => "\xE7\xB7\xBA", + "\xE6\xEB" => "\xE7\xB7\xA6", + "\xE6\xEC" => "\xE7\xB7\xB6", + "\xE6\xED" => "\xE7\xB7\xB1", + "\xE6\xEE" => "\xE7\xB7\xB0", + "\xE6\xEF" => "\xE7\xB7\xAE", + "\xE6\xF0" => "\xE7\xB7\x9F", + "\xE6\xF1" => "\xE7\xBD\xB6", + "\xE6\xF2" => "\xE7\xBE\xAC", + "\xE6\xF3" => "\xE7\xBE\xB0", + "\xE6\xF4" => "\xE7\xBE\xAD", + "\xE6\xF5" => "\xE7\xBF\xAD", + "\xE6\xF6" => "\xE7\xBF\xAB", + "\xE6\xF7" => "\xE7\xBF\xAA", + "\xE6\xF8" => "\xE7\xBF\xAC", + "\xE6\xF9" => "\xE7\xBF\xA6", + "\xE6\xFA" => "\xE7\xBF\xA8", + "\xE6\xFB" => "\xE8\x81\xA4", + "\xE6\xFC" => "\xE8\x81\xA7", + "\xE6\xFD" => "\xE8\x86\xA3", + "\xE6\xFE" => "\xE8\x86\x9F", + "\xE7\x40" => "\xE8\x86\x9E", + "\xE7\x41" => "\xE8\x86\x95", + "\xE7\x42" => "\xE8\x86\xA2", + "\xE7\x43" => "\xE8\x86\x99", + "\xE7\x44" => "\xE8\x86\x97", + "\xE7\x45" => "\xE8\x88\x96", + "\xE7\x46" => "\xE8\x89\x8F", + "\xE7\x47" => "\xE8\x89\x93", + "\xE7\x48" => "\xE8\x89\x92", + "\xE7\x49" => "\xE8\x89\x90", + "\xE7\x4A" => "\xE8\x89\x8E", + "\xE7\x4B" => "\xE8\x89\x91", + "\xE7\x4C" => "\xE8\x94\xA4", + "\xE7\x4D" => "\xE8\x94\xBB", + "\xE7\x4E" => "\xE8\x94\x8F", + "\xE7\x4F" => "\xE8\x94\x80", + "\xE7\x50" => "\xE8\x94\xA9", + "\xE7\x51" => "\xE8\x94\x8E", + "\xE7\x52" => "\xE8\x94\x89", + "\xE7\x53" => "\xE8\x94\x8D", + "\xE7\x54" => "\xE8\x94\x9F", + "\xE7\x55" => "\xE8\x94\x8A", + "\xE7\x56" => "\xE8\x94\xA7", + "\xE7\x57" => "\xE8\x94\x9C", + "\xE7\x58" => "\xE8\x93\xBB", + "\xE7\x59" => "\xE8\x94\xAB", + "\xE7\x5A" => "\xE8\x93\xBA", + "\xE7\x5B" => "\xE8\x94\x88", + "\xE7\x5C" => "\xE8\x94\x8C", + "\xE7\x5D" => "\xE8\x93\xB4", + "\xE7\x5E" => "\xE8\x94\xAA", + "\xE7\x5F" => "\xE8\x93\xB2", + "\xE7\x60" => "\xE8\x94\x95", + "\xE7\x61" => "\xE8\x93\xB7", + "\xE7\x62" => "\xE8\x93\xAB", + "\xE7\x63" => "\xE8\x93\xB3", + "\xE7\x64" => "\xE8\x93\xBC", + "\xE7\x65" => "\xE8\x94\x92", + "\xE7\x66" => "\xE8\x93\xAA", + "\xE7\x67" => "\xE8\x93\xA9", + "\xE7\x68" => "\xE8\x94\x96", + "\xE7\x69" => "\xE8\x93\xBE", + "\xE7\x6A" => "\xE8\x94\xA8", + "\xE7\x6B" => "\xE8\x94\x9D", + "\xE7\x6C" => "\xE8\x94\xAE", + "\xE7\x6D" => "\xE8\x94\x82", + "\xE7\x6E" => "\xE8\x93\xBD", + "\xE7\x6F" => "\xE8\x94\x9E", + "\xE7\x70" => "\xE8\x93\xB6", + "\xE7\x71" => "\xE8\x94\xB1", + "\xE7\x72" => "\xE8\x94\xA6", + "\xE7\x73" => "\xE8\x93\xA7", + "\xE7\x74" => "\xE8\x93\xA8", + "\xE7\x75" => "\xE8\x93\xB0", + "\xE7\x76" => "\xE8\x93\xAF", + "\xE7\x77" => "\xE8\x93\xB9", + "\xE7\x78" => "\xE8\x94\x98", + "\xE7\x79" => "\xE8\x94\xA0", + "\xE7\x7A" => "\xE8\x94\xB0", + "\xE7\x7B" => "\xE8\x94\x8B", + "\xE7\x7C" => "\xE8\x94\x99", + "\xE7\x7D" => "\xE8\x94\xAF", + "\xE7\x7E" => "\xE8\x99\xA2", + "\xE7\xA1" => "\xE8\x9D\x96", + "\xE7\xA2" => "\xE8\x9D\xA3", + "\xE7\xA3" => "\xE8\x9D\xA4", + "\xE7\xA4" => "\xE8\x9D\xB7", + "\xE7\xA5" => "\xE8\x9F\xA1", + "\xE7\xA6" => "\xE8\x9D\xB3", + "\xE7\xA7" => "\xE8\x9D\x98", + "\xE7\xA8" => "\xE8\x9D\x94", + "\xE7\xA9" => "\xE8\x9D\x9B", + "\xE7\xAA" => "\xE8\x9D\x92", + "\xE7\xAB" => "\xE8\x9D\xA1", + "\xE7\xAC" => "\xE8\x9D\x9A", + "\xE7\xAD" => "\xE8\x9D\x91", + "\xE7\xAE" => "\xE8\x9D\x9E", + "\xE7\xAF" => "\xE8\x9D\xAD", + "\xE7\xB0" => "\xE8\x9D\xAA", + "\xE7\xB1" => "\xE8\x9D\x90", + "\xE7\xB2" => "\xE8\x9D\x8E", + "\xE7\xB3" => "\xE8\x9D\x9F", + "\xE7\xB4" => "\xE8\x9D\x9D", + "\xE7\xB5" => "\xE8\x9D\xAF", + "\xE7\xB6" => "\xE8\x9D\xAC", + "\xE7\xB7" => "\xE8\x9D\xBA", + "\xE7\xB8" => "\xE8\x9D\xAE", + "\xE7\xB9" => "\xE8\x9D\x9C", + "\xE7\xBA" => "\xE8\x9D\xA5", + "\xE7\xBB" => "\xE8\x9D\x8F", + "\xE7\xBC" => "\xE8\x9D\xBB", + "\xE7\xBD" => "\xE8\x9D\xB5", + "\xE7\xBE" => "\xE8\x9D\xA2", + "\xE7\xBF" => "\xE8\x9D\xA7", + "\xE7\xC0" => "\xE8\x9D\xA9", + "\xE7\xC1" => "\xE8\xA1\x9A", + "\xE7\xC2" => "\xE8\xA4\x85", + "\xE7\xC3" => "\xE8\xA4\x8C", + "\xE7\xC4" => "\xE8\xA4\x94", + "\xE7\xC5" => "\xE8\xA4\x8B", + "\xE7\xC6" => "\xE8\xA4\x97", + "\xE7\xC7" => "\xE8\xA4\x98", + "\xE7\xC8" => "\xE8\xA4\x99", + "\xE7\xC9" => "\xE8\xA4\x86", + "\xE7\xCA" => "\xE8\xA4\x96", + "\xE7\xCB" => "\xE8\xA4\x91", + "\xE7\xCC" => "\xE8\xA4\x8E", + "\xE7\xCD" => "\xE8\xA4\x89", + "\xE7\xCE" => "\xE8\xA6\xA2", + "\xE7\xCF" => "\xE8\xA6\xA4", + "\xE7\xD0" => "\xE8\xA6\xA3", + "\xE7\xD1" => "\xE8\xA7\xAD", + "\xE7\xD2" => "\xE8\xA7\xB0", + "\xE7\xD3" => "\xE8\xA7\xAC", + "\xE7\xD4" => "\xE8\xAB\x8F", + "\xE7\xD5" => "\xE8\xAB\x86", + "\xE7\xD6" => "\xE8\xAA\xB8", + "\xE7\xD7" => "\xE8\xAB\x93", + "\xE7\xD8" => "\xE8\xAB\x91", + "\xE7\xD9" => "\xE8\xAB\x94", + "\xE7\xDA" => "\xE8\xAB\x95", + "\xE7\xDB" => "\xE8\xAA\xBB", + "\xE7\xDC" => "\xE8\xAB\x97", + "\xE7\xDD" => "\xE8\xAA\xBE", + "\xE7\xDE" => "\xE8\xAB\x80", + "\xE7\xDF" => "\xE8\xAB\x85", + "\xE7\xE0" => "\xE8\xAB\x98", + "\xE7\xE1" => "\xE8\xAB\x83", + "\xE7\xE2" => "\xE8\xAA\xBA", + "\xE7\xE3" => "\xE8\xAA\xBD", + "\xE7\xE4" => "\xE8\xAB\x99", + "\xE7\xE5" => "\xE8\xB0\xBE", + "\xE7\xE6" => "\xE8\xB1\x8D", + "\xE7\xE7" => "\xE8\xB2\x8F", + "\xE7\xE8" => "\xE8\xB3\xA5", + "\xE7\xE9" => "\xE8\xB3\x9F", + "\xE7\xEA" => "\xE8\xB3\x99", + "\xE7\xEB" => "\xE8\xB3\xA8", + "\xE7\xEC" => "\xE8\xB3\x9A", + "\xE7\xED" => "\xE8\xB3\x9D", + "\xE7\xEE" => "\xE8\xB3\xA7", + "\xE7\xEF" => "\xE8\xB6\xA0", + "\xE7\xF0" => "\xE8\xB6\x9C", + "\xE7\xF1" => "\xE8\xB6\xA1", + "\xE7\xF2" => "\xE8\xB6\x9B", + "\xE7\xF3" => "\xE8\xB8\xA0", + "\xE7\xF4" => "\xE8\xB8\xA3", + "\xE7\xF5" => "\xE8\xB8\xA5", + "\xE7\xF6" => "\xE8\xB8\xA4", + "\xE7\xF7" => "\xE8\xB8\xAE", + "\xE7\xF8" => "\xE8\xB8\x95", + "\xE7\xF9" => "\xE8\xB8\x9B", + "\xE7\xFA" => "\xE8\xB8\x96", + "\xE7\xFB" => "\xE8\xB8\x91", + "\xE7\xFC" => "\xE8\xB8\x99", + "\xE7\xFD" => "\xE8\xB8\xA6", + "\xE7\xFE" => "\xE8\xB8\xA7", + "\xE8\x40" => "\xE8\xB8\x94", + "\xE8\x41" => "\xE8\xB8\x92", + "\xE8\x42" => "\xE8\xB8\x98", + "\xE8\x43" => "\xE8\xB8\x93", + "\xE8\x44" => "\xE8\xB8\x9C", + "\xE8\x45" => "\xE8\xB8\x97", + "\xE8\x46" => "\xE8\xB8\x9A", + "\xE8\x47" => "\xE8\xBC\xAC", + "\xE8\x48" => "\xE8\xBC\xA4", + "\xE8\x49" => "\xE8\xBC\x98", + "\xE8\x4A" => "\xE8\xBC\x9A", + "\xE8\x4B" => "\xE8\xBC\xA0", + "\xE8\x4C" => "\xE8\xBC\xA3", + "\xE8\x4D" => "\xE8\xBC\x96", + "\xE8\x4E" => "\xE8\xBC\x97", + "\xE8\x4F" => "\xE9\x81\xB3", + "\xE8\x50" => "\xE9\x81\xB0", + "\xE8\x51" => "\xE9\x81\xAF", + "\xE8\x52" => "\xE9\x81\xA7", + "\xE8\x53" => "\xE9\x81\xAB", + "\xE8\x54" => "\xE9\x84\xAF", + "\xE8\x55" => "\xE9\x84\xAB", + "\xE8\x56" => "\xE9\x84\xA9", + "\xE8\x57" => "\xE9\x84\xAA", + "\xE8\x58" => "\xE9\x84\xB2", + "\xE8\x59" => "\xE9\x84\xA6", + "\xE8\x5A" => "\xE9\x84\xAE", + "\xE8\x5B" => "\xE9\x86\x85", + "\xE8\x5C" => "\xE9\x86\x86", + "\xE8\x5D" => "\xE9\x86\x8A", + "\xE8\x5E" => "\xE9\x86\x81", + "\xE8\x5F" => "\xE9\x86\x82", + "\xE8\x60" => "\xE9\x86\x84", + "\xE8\x61" => "\xE9\x86\x80", + "\xE8\x62" => "\xE9\x8B\x90", + "\xE8\x63" => "\xE9\x8B\x83", + "\xE8\x64" => "\xE9\x8B\x84", + "\xE8\x65" => "\xE9\x8B\x80", + "\xE8\x66" => "\xE9\x8B\x99", + "\xE8\x67" => "\xE9\x8A\xB6", + "\xE8\x68" => "\xE9\x8B\x8F", + "\xE8\x69" => "\xE9\x8B\xB1", + "\xE8\x6A" => "\xE9\x8B\x9F", + "\xE8\x6B" => "\xE9\x8B\x98", + "\xE8\x6C" => "\xE9\x8B\xA9", + "\xE8\x6D" => "\xE9\x8B\x97", + "\xE8\x6E" => "\xE9\x8B\x9D", + "\xE8\x6F" => "\xE9\x8B\x8C", + "\xE8\x70" => "\xE9\x8B\xAF", + "\xE8\x71" => "\xE9\x8B\x82", + "\xE8\x72" => "\xE9\x8B\xA8", + "\xE8\x73" => "\xE9\x8B\x8A", + "\xE8\x74" => "\xE9\x8B\x88", + "\xE8\x75" => "\xE9\x8B\x8E", + "\xE8\x76" => "\xE9\x8B\xA6", + "\xE8\x77" => "\xE9\x8B\x8D", + "\xE8\x78" => "\xE9\x8B\x95", + "\xE8\x79" => "\xE9\x8B\x89", + "\xE8\x7A" => "\xE9\x8B\xA0", + "\xE8\x7B" => "\xE9\x8B\x9E", + "\xE8\x7C" => "\xE9\x8B\xA7", + "\xE8\x7D" => "\xE9\x8B\x91", + "\xE8\x7E" => "\xE9\x8B\x93", + "\xE8\xA1" => "\xE9\x8A\xB5", + "\xE8\xA2" => "\xE9\x8B\xA1", + "\xE8\xA3" => "\xE9\x8B\x86", + "\xE8\xA4" => "\xE9\x8A\xB4", + "\xE8\xA5" => "\xE9\x95\xBC", + "\xE8\xA6" => "\xE9\x96\xAC", + "\xE8\xA7" => "\xE9\x96\xAB", + "\xE8\xA8" => "\xE9\x96\xAE", + "\xE8\xA9" => "\xE9\x96\xB0", + "\xE8\xAA" => "\xE9\x9A\xA4", + "\xE8\xAB" => "\xE9\x9A\xA2", + "\xE8\xAC" => "\xE9\x9B\x93", + "\xE8\xAD" => "\xE9\x9C\x85", + "\xE8\xAE" => "\xE9\x9C\x88", + "\xE8\xAF" => "\xE9\x9C\x82", + "\xE8\xB0" => "\xE9\x9D\x9A", + "\xE8\xB1" => "\xE9\x9E\x8A", + "\xE8\xB2" => "\xE9\x9E\x8E", + "\xE8\xB3" => "\xE9\x9E\x88", + "\xE8\xB4" => "\xE9\x9F\x90", + "\xE8\xB5" => "\xE9\x9F\x8F", + "\xE8\xB6" => "\xE9\xA0\x9E", + "\xE8\xB7" => "\xE9\xA0\x9D", + "\xE8\xB8" => "\xE9\xA0\xA6", + "\xE8\xB9" => "\xE9\xA0\xA9", + "\xE8\xBA" => "\xE9\xA0\xA8", + "\xE8\xBB" => "\xE9\xA0\xA0", + "\xE8\xBC" => "\xE9\xA0\x9B", + "\xE8\xBD" => "\xE9\xA0\xA7", + "\xE8\xBE" => "\xE9\xA2\xB2", + "\xE8\xBF" => "\xE9\xA4\x88", + "\xE8\xC0" => "\xE9\xA3\xBA", + "\xE8\xC1" => "\xE9\xA4\x91", + "\xE8\xC2" => "\xE9\xA4\x94", + "\xE8\xC3" => "\xE9\xA4\x96", + "\xE8\xC4" => "\xE9\xA4\x97", + "\xE8\xC5" => "\xE9\xA4\x95", + "\xE8\xC6" => "\xE9\xA7\x9C", + "\xE8\xC7" => "\xE9\xA7\x8D", + "\xE8\xC8" => "\xE9\xA7\x8F", + "\xE8\xC9" => "\xE9\xA7\x93", + "\xE8\xCA" => "\xE9\xA7\x94", + "\xE8\xCB" => "\xE9\xA7\x8E", + "\xE8\xCC" => "\xE9\xA7\x89", + "\xE8\xCD" => "\xE9\xA7\x96", + "\xE8\xCE" => "\xE9\xA7\x98", + "\xE8\xCF" => "\xE9\xA7\x8B", + "\xE8\xD0" => "\xE9\xA7\x97", + "\xE8\xD1" => "\xE9\xA7\x8C", + "\xE8\xD2" => "\xE9\xAA\xB3", + "\xE8\xD3" => "\xE9\xAB\xAC", + "\xE8\xD4" => "\xE9\xAB\xAB", + "\xE8\xD5" => "\xE9\xAB\xB3", + "\xE8\xD6" => "\xE9\xAB\xB2", + "\xE8\xD7" => "\xE9\xAB\xB1", + "\xE8\xD8" => "\xE9\xAD\x86", + "\xE8\xD9" => "\xE9\xAD\x83", + "\xE8\xDA" => "\xE9\xAD\xA7", + "\xE8\xDB" => "\xE9\xAD\xB4", + "\xE8\xDC" => "\xE9\xAD\xB1", + "\xE8\xDD" => "\xE9\xAD\xA6", + "\xE8\xDE" => "\xE9\xAD\xB6", + "\xE8\xDF" => "\xE9\xAD\xB5", + "\xE8\xE0" => "\xE9\xAD\xB0", + "\xE8\xE1" => "\xE9\xAD\xA8", + "\xE8\xE2" => "\xE9\xAD\xA4", + "\xE8\xE3" => "\xE9\xAD\xAC", + "\xE8\xE4" => "\xE9\xB3\xBC", + "\xE8\xE5" => "\xE9\xB3\xBA", + "\xE8\xE6" => "\xE9\xB3\xBD", + "\xE8\xE7" => "\xE9\xB3\xBF", + "\xE8\xE8" => "\xE9\xB3\xB7", + "\xE8\xE9" => "\xE9\xB4\x87", + "\xE8\xEA" => "\xE9\xB4\x80", + "\xE8\xEB" => "\xE9\xB3\xB9", + "\xE8\xEC" => "\xE9\xB3\xBB", + "\xE8\xED" => "\xE9\xB4\x88", + "\xE8\xEE" => "\xE9\xB4\x85", + "\xE8\xEF" => "\xE9\xB4\x84", + "\xE8\xF0" => "\xE9\xBA\x83", + "\xE8\xF1" => "\xE9\xBB\x93", + "\xE8\xF2" => "\xE9\xBC\x8F", + "\xE8\xF3" => "\xE9\xBC\x90", + "\xE8\xF4" => "\xE5\x84\x9C", + "\xE8\xF5" => "\xE5\x84\x93", + "\xE8\xF6" => "\xE5\x84\x97", + "\xE8\xF7" => "\xE5\x84\x9A", + "\xE8\xF8" => "\xE5\x84\x91", + "\xE8\xF9" => "\xE5\x87\x9E", + "\xE8\xFA" => "\xE5\x8C\xB4", + "\xE8\xFB" => "\xE5\x8F\xA1", + "\xE8\xFC" => "\xE5\x99\xB0", + "\xE8\xFD" => "\xE5\x99\xA0", + "\xE8\xFE" => "\xE5\x99\xAE", + "\xE9\x40" => "\xE5\x99\xB3", + "\xE9\x41" => "\xE5\x99\xA6", + "\xE9\x42" => "\xE5\x99\xA3", + "\xE9\x43" => "\xE5\x99\xAD", + "\xE9\x44" => "\xE5\x99\xB2", + "\xE9\x45" => "\xE5\x99\x9E", + "\xE9\x46" => "\xE5\x99\xB7", + "\xE9\x47" => "\xE5\x9C\x9C", + "\xE9\x48" => "\xE5\x9C\x9B", + "\xE9\x49" => "\xE5\xA3\x88", + "\xE9\x4A" => "\xE5\xA2\xBD", + "\xE9\x4B" => "\xE5\xA3\x89", + "\xE9\x4C" => "\xE5\xA2\xBF", + "\xE9\x4D" => "\xE5\xA2\xBA", + "\xE9\x4E" => "\xE5\xA3\x82", + "\xE9\x4F" => "\xE5\xA2\xBC", + "\xE9\x50" => "\xE5\xA3\x86", + "\xE9\x51" => "\xE5\xAC\x97", + "\xE9\x52" => "\xE5\xAC\x99", + "\xE9\x53" => "\xE5\xAC\x9B", + "\xE9\x54" => "\xE5\xAC\xA1", + "\xE9\x55" => "\xE5\xAC\x94", + "\xE9\x56" => "\xE5\xAC\x93", + "\xE9\x57" => "\xE5\xAC\x90", + "\xE9\x58" => "\xE5\xAC\x96", + "\xE9\x59" => "\xE5\xAC\xA8", + "\xE9\x5A" => "\xE5\xAC\x9A", + "\xE9\x5B" => "\xE5\xAC\xA0", + "\xE9\x5C" => "\xE5\xAC\x9E", + "\xE9\x5D" => "\xE5\xAF\xAF", + "\xE9\x5E" => "\xE5\xB6\xAC", + "\xE9\x5F" => "\xE5\xB6\xB1", + "\xE9\x60" => "\xE5\xB6\xA9", + "\xE9\x61" => "\xE5\xB6\xA7", + "\xE9\x62" => "\xE5\xB6\xB5", + "\xE9\x63" => "\xE5\xB6\xB0", + "\xE9\x64" => "\xE5\xB6\xAE", + "\xE9\x65" => "\xE5\xB6\xAA", + "\xE9\x66" => "\xE5\xB6\xA8", + "\xE9\x67" => "\xE5\xB6\xB2", + "\xE9\x68" => "\xE5\xB6\xAD", + "\xE9\x69" => "\xE5\xB6\xAF", + "\xE9\x6A" => "\xE5\xB6\xB4", + "\xE9\x6B" => "\xE5\xB9\xA7", + "\xE9\x6C" => "\xE5\xB9\xA8", + "\xE9\x6D" => "\xE5\xB9\xA6", + "\xE9\x6E" => "\xE5\xB9\xAF", + "\xE9\x6F" => "\xE5\xBB\xA9", + "\xE9\x70" => "\xE5\xBB\xA7", + "\xE9\x71" => "\xE5\xBB\xA6", + "\xE9\x72" => "\xE5\xBB\xA8", + "\xE9\x73" => "\xE5\xBB\xA5", + "\xE9\x74" => "\xE5\xBD\x8B", + "\xE9\x75" => "\xE5\xBE\xBC", + "\xE9\x76" => "\xE6\x86\x9D", + "\xE9\x77" => "\xE6\x86\xA8", + "\xE9\x78" => "\xE6\x86\x96", + "\xE9\x79" => "\xE6\x87\x85", + "\xE9\x7A" => "\xE6\x86\xB4", + "\xE9\x7B" => "\xE6\x87\x86", + "\xE9\x7C" => "\xE6\x87\x81", + "\xE9\x7D" => "\xE6\x87\x8C", + "\xE9\x7E" => "\xE6\x86\xBA", + "\xE9\xA1" => "\xE6\x86\xBF", + "\xE9\xA2" => "\xE6\x86\xB8", + "\xE9\xA3" => "\xE6\x86\x8C", + "\xE9\xA4" => "\xE6\x93\x97", + "\xE9\xA5" => "\xE6\x93\x96", + "\xE9\xA6" => "\xE6\x93\x90", + "\xE9\xA7" => "\xE6\x93\x8F", + "\xE9\xA8" => "\xE6\x93\x89", + "\xE9\xA9" => "\xE6\x92\xBD", + "\xE9\xAA" => "\xE6\x92\x89", + "\xE9\xAB" => "\xE6\x93\x83", + "\xE9\xAC" => "\xE6\x93\x9B", + "\xE9\xAD" => "\xE6\x93\xB3", + "\xE9\xAE" => "\xE6\x93\x99", + "\xE9\xAF" => "\xE6\x94\xB3", + "\xE9\xB0" => "\xE6\x95\xBF", + "\xE9\xB1" => "\xE6\x95\xBC", + "\xE9\xB2" => "\xE6\x96\xA2", + "\xE9\xB3" => "\xE6\x9B\x88", + "\xE9\xB4" => "\xE6\x9A\xBE", + "\xE9\xB5" => "\xE6\x9B\x80", + "\xE9\xB6" => "\xE6\x9B\x8A", + "\xE9\xB7" => "\xE6\x9B\x8B", + "\xE9\xB8" => "\xE6\x9B\x8F", + "\xE9\xB9" => "\xE6\x9A\xBD", + "\xE9\xBA" => "\xE6\x9A\xBB", + "\xE9\xBB" => "\xE6\x9A\xBA", + "\xE9\xBC" => "\xE6\x9B\x8C", + "\xE9\xBD" => "\xE6\x9C\xA3", + "\xE9\xBE" => "\xE6\xA8\xB4", + "\xE9\xBF" => "\xE6\xA9\xA6", + "\xE9\xC0" => "\xE6\xA9\x89", + "\xE9\xC1" => "\xE6\xA9\xA7", + "\xE9\xC2" => "\xE6\xA8\xB2", + "\xE9\xC3" => "\xE6\xA9\xA8", + "\xE9\xC4" => "\xE6\xA8\xBE", + "\xE9\xC5" => "\xE6\xA9\x9D", + "\xE9\xC6" => "\xE6\xA9\xAD", + "\xE9\xC7" => "\xE6\xA9\xB6", + "\xE9\xC8" => "\xE6\xA9\x9B", + "\xE9\xC9" => "\xE6\xA9\x91", + "\xE9\xCA" => "\xE6\xA8\xA8", + "\xE9\xCB" => "\xE6\xA9\x9A", + "\xE9\xCC" => "\xE6\xA8\xBB", + "\xE9\xCD" => "\xE6\xA8\xBF", + "\xE9\xCE" => "\xE6\xA9\x81", + "\xE9\xCF" => "\xE6\xA9\xAA", + "\xE9\xD0" => "\xE6\xA9\xA4", + "\xE9\xD1" => "\xE6\xA9\x90", + "\xE9\xD2" => "\xE6\xA9\x8F", + "\xE9\xD3" => "\xE6\xA9\x94", + "\xE9\xD4" => "\xE6\xA9\xAF", + "\xE9\xD5" => "\xE6\xA9\xA9", + "\xE9\xD6" => "\xE6\xA9\xA0", + "\xE9\xD7" => "\xE6\xA8\xBC", + "\xE9\xD8" => "\xE6\xA9\x9E", + "\xE9\xD9" => "\xE6\xA9\x96", + "\xE9\xDA" => "\xE6\xA9\x95", + "\xE9\xDB" => "\xE6\xA9\x8D", + "\xE9\xDC" => "\xE6\xA9\x8E", + "\xE9\xDD" => "\xE6\xA9\x86", + "\xE9\xDE" => "\xE6\xAD\x95", + "\xE9\xDF" => "\xE6\xAD\x94", + "\xE9\xE0" => "\xE6\xAD\x96", + "\xE9\xE1" => "\xE6\xAE\xA7", + "\xE9\xE2" => "\xE6\xAE\xAA", + "\xE9\xE3" => "\xE6\xAE\xAB", + "\xE9\xE4" => "\xE6\xAF\x88", + "\xE9\xE5" => "\xE6\xAF\x87", + "\xE9\xE6" => "\xE6\xB0\x84", + "\xE9\xE7" => "\xE6\xB0\x83", + "\xE9\xE8" => "\xE6\xB0\x86", + "\xE9\xE9" => "\xE6\xBE\xAD", + "\xE9\xEA" => "\xE6\xBF\x8B", + "\xE9\xEB" => "\xE6\xBE\xA3", + "\xE9\xEC" => "\xE6\xBF\x87", + "\xE9\xED" => "\xE6\xBE\xBC", + "\xE9\xEE" => "\xE6\xBF\x8E", + "\xE9\xEF" => "\xE6\xBF\x88", + "\xE9\xF0" => "\xE6\xBD\x9E", + "\xE9\xF1" => "\xE6\xBF\x84", + "\xE9\xF2" => "\xE6\xBE\xBD", + "\xE9\xF3" => "\xE6\xBE\x9E", + "\xE9\xF4" => "\xE6\xBF\x8A", + "\xE9\xF5" => "\xE6\xBE\xA8", + "\xE9\xF6" => "\xE7\x80\x84", + "\xE9\xF7" => "\xE6\xBE\xA5", + "\xE9\xF8" => "\xE6\xBE\xAE", + "\xE9\xF9" => "\xE6\xBE\xBA", + "\xE9\xFA" => "\xE6\xBE\xAC", + "\xE9\xFB" => "\xE6\xBE\xAA", + "\xE9\xFC" => "\xE6\xBF\x8F", + "\xE9\xFD" => "\xE6\xBE\xBF", + "\xE9\xFE" => "\xE6\xBE\xB8", + "\xEA\x40" => "\xE6\xBE\xA2", + "\xEA\x41" => "\xE6\xBF\x89", + "\xEA\x42" => "\xE6\xBE\xAB", + "\xEA\x43" => "\xE6\xBF\x8D", + "\xEA\x44" => "\xE6\xBE\xAF", + "\xEA\x45" => "\xE6\xBE\xB2", + "\xEA\x46" => "\xE6\xBE\xB0", + "\xEA\x47" => "\xE7\x87\x85", + "\xEA\x48" => "\xE7\x87\x82", + "\xEA\x49" => "\xE7\x86\xBF", + "\xEA\x4A" => "\xE7\x86\xB8", + "\xEA\x4B" => "\xE7\x87\x96", + "\xEA\x4C" => "\xE7\x87\x80", + "\xEA\x4D" => "\xE7\x87\x81", + "\xEA\x4E" => "\xE7\x87\x8B", + "\xEA\x4F" => "\xE7\x87\x94", + "\xEA\x50" => "\xE7\x87\x8A", + "\xEA\x51" => "\xE7\x87\x87", + "\xEA\x52" => "\xE7\x87\x8F", + "\xEA\x53" => "\xE7\x86\xBD", + "\xEA\x54" => "\xE7\x87\x98", + "\xEA\x55" => "\xE7\x86\xBC", + "\xEA\x56" => "\xE7\x87\x86", + "\xEA\x57" => "\xE7\x87\x9A", + "\xEA\x58" => "\xE7\x87\x9B", + "\xEA\x59" => "\xE7\x8A\x9D", + "\xEA\x5A" => "\xE7\x8A\x9E", + "\xEA\x5B" => "\xE7\x8D\xA9", + "\xEA\x5C" => "\xE7\x8D\xA6", + "\xEA\x5D" => "\xE7\x8D\xA7", + "\xEA\x5E" => "\xE7\x8D\xAC", + "\xEA\x5F" => "\xE7\x8D\xA5", + "\xEA\x60" => "\xE7\x8D\xAB", + "\xEA\x61" => "\xE7\x8D\xAA", + "\xEA\x62" => "\xE7\x91\xBF", + "\xEA\x63" => "\xE7\x92\x9A", + "\xEA\x64" => "\xE7\x92\xA0", + "\xEA\x65" => "\xE7\x92\x94", + "\xEA\x66" => "\xE7\x92\x92", + "\xEA\x67" => "\xE7\x92\x95", + "\xEA\x68" => "\xE7\x92\xA1", + "\xEA\x69" => "\xE7\x94\x8B", + "\xEA\x6A" => "\xE7\x96\x80", + "\xEA\x6B" => "\xE7\x98\xAF", + "\xEA\x6C" => "\xE7\x98\xAD", + "\xEA\x6D" => "\xE7\x98\xB1", + "\xEA\x6E" => "\xE7\x98\xBD", + "\xEA\x6F" => "\xE7\x98\xB3", + "\xEA\x70" => "\xE7\x98\xBC", + "\xEA\x71" => "\xE7\x98\xB5", + "\xEA\x72" => "\xE7\x98\xB2", + "\xEA\x73" => "\xE7\x98\xB0", + "\xEA\x74" => "\xE7\x9A\xBB", + "\xEA\x75" => "\xE7\x9B\xA6", + "\xEA\x76" => "\xE7\x9E\x9A", + "\xEA\x77" => "\xE7\x9E\x9D", + "\xEA\x78" => "\xE7\x9E\xA1", + "\xEA\x79" => "\xE7\x9E\x9C", + "\xEA\x7A" => "\xE7\x9E\x9B", + "\xEA\x7B" => "\xE7\x9E\xA2", + "\xEA\x7C" => "\xE7\x9E\xA3", + "\xEA\x7D" => "\xE7\x9E\x95", + "\xEA\x7E" => "\xE7\x9E\x99", + "\xEA\xA1" => "\xE7\x9E\x97", + "\xEA\xA2" => "\xE7\xA3\x9D", + "\xEA\xA3" => "\xE7\xA3\xA9", + "\xEA\xA4" => "\xE7\xA3\xA5", + "\xEA\xA5" => "\xE7\xA3\xAA", + "\xEA\xA6" => "\xE7\xA3\x9E", + "\xEA\xA7" => "\xE7\xA3\xA3", + "\xEA\xA8" => "\xE7\xA3\x9B", + "\xEA\xA9" => "\xE7\xA3\xA1", + "\xEA\xAA" => "\xE7\xA3\xA2", + "\xEA\xAB" => "\xE7\xA3\xAD", + "\xEA\xAC" => "\xE7\xA3\x9F", + "\xEA\xAD" => "\xE7\xA3\xA0", + "\xEA\xAE" => "\xE7\xA6\xA4", + "\xEA\xAF" => "\xE7\xA9\x84", + "\xEA\xB0" => "\xE7\xA9\x88", + "\xEA\xB1" => "\xE7\xA9\x87", + "\xEA\xB2" => "\xE7\xAA\xB6", + "\xEA\xB3" => "\xE7\xAA\xB8", + "\xEA\xB4" => "\xE7\xAA\xB5", + "\xEA\xB5" => "\xE7\xAA\xB1", + "\xEA\xB6" => "\xE7\xAA\xB7", + "\xEA\xB7" => "\xE7\xAF\x9E", + "\xEA\xB8" => "\xE7\xAF\xA3", + "\xEA\xB9" => "\xE7\xAF\xA7", + "\xEA\xBA" => "\xE7\xAF\x9D", + "\xEA\xBB" => "\xE7\xAF\x95", + "\xEA\xBC" => "\xE7\xAF\xA5", + "\xEA\xBD" => "\xE7\xAF\x9A", + "\xEA\xBE" => "\xE7\xAF\xA8", + "\xEA\xBF" => "\xE7\xAF\xB9", + "\xEA\xC0" => "\xE7\xAF\x94", + "\xEA\xC1" => "\xE7\xAF\xAA", + "\xEA\xC2" => "\xE7\xAF\xA2", + "\xEA\xC3" => "\xE7\xAF\x9C", + "\xEA\xC4" => "\xE7\xAF\xAB", + "\xEA\xC5" => "\xE7\xAF\x98", + "\xEA\xC6" => "\xE7\xAF\x9F", + "\xEA\xC7" => "\xE7\xB3\x92", + "\xEA\xC8" => "\xE7\xB3\x94", + "\xEA\xC9" => "\xE7\xB3\x97", + "\xEA\xCA" => "\xE7\xB3\x90", + "\xEA\xCB" => "\xE7\xB3\x91", + "\xEA\xCC" => "\xE7\xB8\x92", + "\xEA\xCD" => "\xE7\xB8\xA1", + "\xEA\xCE" => "\xE7\xB8\x97", + "\xEA\xCF" => "\xE7\xB8\x8C", + "\xEA\xD0" => "\xE7\xB8\x9F", + "\xEA\xD1" => "\xE7\xB8\xA0", + "\xEA\xD2" => "\xE7\xB8\x93", + "\xEA\xD3" => "\xE7\xB8\x8E", + "\xEA\xD4" => "\xE7\xB8\x9C", + "\xEA\xD5" => "\xE7\xB8\x95", + "\xEA\xD6" => "\xE7\xB8\x9A", + "\xEA\xD7" => "\xE7\xB8\xA2", + "\xEA\xD8" => "\xE7\xB8\x8B", + "\xEA\xD9" => "\xE7\xB8\x8F", + "\xEA\xDA" => "\xE7\xB8\x96", + "\xEA\xDB" => "\xE7\xB8\x8D", + "\xEA\xDC" => "\xE7\xB8\x94", + "\xEA\xDD" => "\xE7\xB8\xA5", + "\xEA\xDE" => "\xE7\xB8\xA4", + "\xEA\xDF" => "\xE7\xBD\x83", + "\xEA\xE0" => "\xE7\xBD\xBB", + "\xEA\xE1" => "\xE7\xBD\xBC", + "\xEA\xE2" => "\xE7\xBD\xBA", + "\xEA\xE3" => "\xE7\xBE\xB1", + "\xEA\xE4" => "\xE7\xBF\xAF", + "\xEA\xE5" => "\xE8\x80\xAA", + "\xEA\xE6" => "\xE8\x80\xA9", + "\xEA\xE7" => "\xE8\x81\xAC", + "\xEA\xE8" => "\xE8\x86\xB1", + "\xEA\xE9" => "\xE8\x86\xA6", + "\xEA\xEA" => "\xE8\x86\xAE", + "\xEA\xEB" => "\xE8\x86\xB9", + "\xEA\xEC" => "\xE8\x86\xB5", + "\xEA\xED" => "\xE8\x86\xAB", + "\xEA\xEE" => "\xE8\x86\xB0", + "\xEA\xEF" => "\xE8\x86\xAC", + "\xEA\xF0" => "\xE8\x86\xB4", + "\xEA\xF1" => "\xE8\x86\xB2", + "\xEA\xF2" => "\xE8\x86\xB7", + "\xEA\xF3" => "\xE8\x86\xA7", + "\xEA\xF4" => "\xE8\x87\xB2", + "\xEA\xF5" => "\xE8\x89\x95", + "\xEA\xF6" => "\xE8\x89\x96", + "\xEA\xF7" => "\xE8\x89\x97", + "\xEA\xF8" => "\xE8\x95\x96", + "\xEA\xF9" => "\xE8\x95\x85", + "\xEA\xFA" => "\xE8\x95\xAB", + "\xEA\xFB" => "\xE8\x95\x8D", + "\xEA\xFC" => "\xE8\x95\x93", + "\xEA\xFD" => "\xE8\x95\xA1", + "\xEA\xFE" => "\xE8\x95\x98", + "\xEB\x40" => "\xE8\x95\x80", + "\xEB\x41" => "\xE8\x95\x86", + "\xEB\x42" => "\xE8\x95\xA4", + "\xEB\x43" => "\xE8\x95\x81", + "\xEB\x44" => "\xE8\x95\xA2", + "\xEB\x45" => "\xE8\x95\x84", + "\xEB\x46" => "\xE8\x95\x91", + "\xEB\x47" => "\xE8\x95\x87", + "\xEB\x48" => "\xE8\x95\xA3", + "\xEB\x49" => "\xE8\x94\xBE", + "\xEB\x4A" => "\xE8\x95\x9B", + "\xEB\x4B" => "\xE8\x95\xB1", + "\xEB\x4C" => "\xE8\x95\x8E", + "\xEB\x4D" => "\xE8\x95\xAE", + "\xEB\x4E" => "\xE8\x95\xB5", + "\xEB\x4F" => "\xE8\x95\x95", + "\xEB\x50" => "\xE8\x95\xA7", + "\xEB\x51" => "\xE8\x95\xA0", + "\xEB\x52" => "\xE8\x96\x8C", + "\xEB\x53" => "\xE8\x95\xA6", + "\xEB\x54" => "\xE8\x95\x9D", + "\xEB\x55" => "\xE8\x95\x94", + "\xEB\x56" => "\xE8\x95\xA5", + "\xEB\x57" => "\xE8\x95\xAC", + "\xEB\x58" => "\xE8\x99\xA3", + "\xEB\x59" => "\xE8\x99\xA5", + "\xEB\x5A" => "\xE8\x99\xA4", + "\xEB\x5B" => "\xE8\x9E\x9B", + "\xEB\x5C" => "\xE8\x9E\x8F", + "\xEB\x5D" => "\xE8\x9E\x97", + "\xEB\x5E" => "\xE8\x9E\x93", + "\xEB\x5F" => "\xE8\x9E\x92", + "\xEB\x60" => "\xE8\x9E\x88", + "\xEB\x61" => "\xE8\x9E\x81", + "\xEB\x62" => "\xE8\x9E\x96", + "\xEB\x63" => "\xE8\x9E\x98", + "\xEB\x64" => "\xE8\x9D\xB9", + "\xEB\x65" => "\xE8\x9E\x87", + "\xEB\x66" => "\xE8\x9E\xA3", + "\xEB\x67" => "\xE8\x9E\x85", + "\xEB\x68" => "\xE8\x9E\x90", + "\xEB\x69" => "\xE8\x9E\x91", + "\xEB\x6A" => "\xE8\x9E\x9D", + "\xEB\x6B" => "\xE8\x9E\x84", + "\xEB\x6C" => "\xE8\x9E\x94", + "\xEB\x6D" => "\xE8\x9E\x9C", + "\xEB\x6E" => "\xE8\x9E\x9A", + "\xEB\x6F" => "\xE8\x9E\x89", + "\xEB\x70" => "\xE8\xA4\x9E", + "\xEB\x71" => "\xE8\xA4\xA6", + "\xEB\x72" => "\xE8\xA4\xB0", + "\xEB\x73" => "\xE8\xA4\xAD", + "\xEB\x74" => "\xE8\xA4\xAE", + "\xEB\x75" => "\xE8\xA4\xA7", + "\xEB\x76" => "\xE8\xA4\xB1", + "\xEB\x77" => "\xE8\xA4\xA2", + "\xEB\x78" => "\xE8\xA4\xA9", + "\xEB\x79" => "\xE8\xA4\xA3", + "\xEB\x7A" => "\xE8\xA4\xAF", + "\xEB\x7B" => "\xE8\xA4\xAC", + "\xEB\x7C" => "\xE8\xA4\x9F", + "\xEB\x7D" => "\xE8\xA7\xB1", + "\xEB\x7E" => "\xE8\xAB\xA0", + "\xEB\xA1" => "\xE8\xAB\xA2", + "\xEB\xA2" => "\xE8\xAB\xB2", + "\xEB\xA3" => "\xE8\xAB\xB4", + "\xEB\xA4" => "\xE8\xAB\xB5", + "\xEB\xA5" => "\xE8\xAB\x9D", + "\xEB\xA6" => "\xE8\xAC\x94", + "\xEB\xA7" => "\xE8\xAB\xA4", + "\xEB\xA8" => "\xE8\xAB\x9F", + "\xEB\xA9" => "\xE8\xAB\xB0", + "\xEB\xAA" => "\xE8\xAB\x88", + "\xEB\xAB" => "\xE8\xAB\x9E", + "\xEB\xAC" => "\xE8\xAB\xA1", + "\xEB\xAD" => "\xE8\xAB\xA8", + "\xEB\xAE" => "\xE8\xAB\xBF", + "\xEB\xAF" => "\xE8\xAB\xAF", + "\xEB\xB0" => "\xE8\xAB\xBB", + "\xEB\xB1" => "\xE8\xB2\x91", + "\xEB\xB2" => "\xE8\xB2\x92", + "\xEB\xB3" => "\xE8\xB2\x90", + "\xEB\xB4" => "\xE8\xB3\xB5", + "\xEB\xB5" => "\xE8\xB3\xAE", + "\xEB\xB6" => "\xE8\xB3\xB1", + "\xEB\xB7" => "\xE8\xB3\xB0", + "\xEB\xB8" => "\xE8\xB3\xB3", + "\xEB\xB9" => "\xE8\xB5\xAC", + "\xEB\xBA" => "\xE8\xB5\xAE", + "\xEB\xBB" => "\xE8\xB6\xA5", + "\xEB\xBC" => "\xE8\xB6\xA7", + "\xEB\xBD" => "\xE8\xB8\xB3", + "\xEB\xBE" => "\xE8\xB8\xBE", + "\xEB\xBF" => "\xE8\xB8\xB8", + "\xEB\xC0" => "\xE8\xB9\x80", + "\xEB\xC1" => "\xE8\xB9\x85", + "\xEB\xC2" => "\xE8\xB8\xB6", + "\xEB\xC3" => "\xE8\xB8\xBC", + "\xEB\xC4" => "\xE8\xB8\xBD", + "\xEB\xC5" => "\xE8\xB9\x81", + "\xEB\xC6" => "\xE8\xB8\xB0", + "\xEB\xC7" => "\xE8\xB8\xBF", + "\xEB\xC8" => "\xE8\xBA\xBD", + "\xEB\xC9" => "\xE8\xBC\xB6", + "\xEB\xCA" => "\xE8\xBC\xAE", + "\xEB\xCB" => "\xE8\xBC\xB5", + "\xEB\xCC" => "\xE8\xBC\xB2", + "\xEB\xCD" => "\xE8\xBC\xB9", + "\xEB\xCE" => "\xE8\xBC\xB7", + "\xEB\xCF" => "\xE8\xBC\xB4", + "\xEB\xD0" => "\xE9\x81\xB6", + "\xEB\xD1" => "\xE9\x81\xB9", + "\xEB\xD2" => "\xE9\x81\xBB", + "\xEB\xD3" => "\xE9\x82\x86", + "\xEB\xD4" => "\xE9\x83\xBA", + "\xEB\xD5" => "\xE9\x84\xB3", + "\xEB\xD6" => "\xE9\x84\xB5", + "\xEB\xD7" => "\xE9\x84\xB6", + "\xEB\xD8" => "\xE9\x86\x93", + "\xEB\xD9" => "\xE9\x86\x90", + "\xEB\xDA" => "\xE9\x86\x91", + "\xEB\xDB" => "\xE9\x86\x8D", + "\xEB\xDC" => "\xE9\x86\x8F", + "\xEB\xDD" => "\xE9\x8C\xA7", + "\xEB\xDE" => "\xE9\x8C\x9E", + "\xEB\xDF" => "\xE9\x8C\x88", + "\xEB\xE0" => "\xE9\x8C\x9F", + "\xEB\xE1" => "\xE9\x8C\x86", + "\xEB\xE2" => "\xE9\x8C\x8F", + "\xEB\xE3" => "\xE9\x8D\xBA", + "\xEB\xE4" => "\xE9\x8C\xB8", + "\xEB\xE5" => "\xE9\x8C\xBC", + "\xEB\xE6" => "\xE9\x8C\x9B", + "\xEB\xE7" => "\xE9\x8C\xA3", + "\xEB\xE8" => "\xE9\x8C\x92", + "\xEB\xE9" => "\xE9\x8C\x81", + "\xEB\xEA" => "\xE9\x8D\x86", + "\xEB\xEB" => "\xE9\x8C\xAD", + "\xEB\xEC" => "\xE9\x8C\x8E", + "\xEB\xED" => "\xE9\x8C\x8D", + "\xEB\xEE" => "\xE9\x8B\x8B", + "\xEB\xEF" => "\xE9\x8C\x9D", + "\xEB\xF0" => "\xE9\x8B\xBA", + "\xEB\xF1" => "\xE9\x8C\xA5", + "\xEB\xF2" => "\xE9\x8C\x93", + "\xEB\xF3" => "\xE9\x8B\xB9", + "\xEB\xF4" => "\xE9\x8B\xB7", + "\xEB\xF5" => "\xE9\x8C\xB4", + "\xEB\xF6" => "\xE9\x8C\x82", + "\xEB\xF7" => "\xE9\x8C\xA4", + "\xEB\xF8" => "\xE9\x8B\xBF", + "\xEB\xF9" => "\xE9\x8C\xA9", + "\xEB\xFA" => "\xE9\x8C\xB9", + "\xEB\xFB" => "\xE9\x8C\xB5", + "\xEB\xFC" => "\xE9\x8C\xAA", + "\xEB\xFD" => "\xE9\x8C\x94", + "\xEB\xFE" => "\xE9\x8C\x8C", + "\xEC\x40" => "\xE9\x8C\x8B", + "\xEC\x41" => "\xE9\x8B\xBE", + "\xEC\x42" => "\xE9\x8C\x89", + "\xEC\x43" => "\xE9\x8C\x80", + "\xEC\x44" => "\xE9\x8B\xBB", + "\xEC\x45" => "\xE9\x8C\x96", + "\xEC\x46" => "\xE9\x96\xBC", + "\xEC\x47" => "\xE9\x97\x8D", + "\xEC\x48" => "\xE9\x96\xBE", + "\xEC\x49" => "\xE9\x96\xB9", + "\xEC\x4A" => "\xE9\x96\xBA", + "\xEC\x4B" => "\xE9\x96\xB6", + "\xEC\x4C" => "\xE9\x96\xBF", + "\xEC\x4D" => "\xE9\x96\xB5", + "\xEC\x4E" => "\xE9\x96\xBD", + "\xEC\x4F" => "\xE9\x9A\xA9", + "\xEC\x50" => "\xE9\x9B\x94", + "\xEC\x51" => "\xE9\x9C\x8B", + "\xEC\x52" => "\xE9\x9C\x92", + "\xEC\x53" => "\xE9\x9C\x90", + "\xEC\x54" => "\xE9\x9E\x99", + "\xEC\x55" => "\xE9\x9E\x97", + "\xEC\x56" => "\xE9\x9E\x94", + "\xEC\x57" => "\xE9\x9F\xB0", + "\xEC\x58" => "\xE9\x9F\xB8", + "\xEC\x59" => "\xE9\xA0\xB5", + "\xEC\x5A" => "\xE9\xA0\xAF", + "\xEC\x5B" => "\xE9\xA0\xB2", + "\xEC\x5C" => "\xE9\xA4\xA4", + "\xEC\x5D" => "\xE9\xA4\x9F", + "\xEC\x5E" => "\xE9\xA4\xA7", + "\xEC\x5F" => "\xE9\xA4\xA9", + "\xEC\x60" => "\xE9\xA6\x9E", + "\xEC\x61" => "\xE9\xA7\xAE", + "\xEC\x62" => "\xE9\xA7\xAC", + "\xEC\x63" => "\xE9\xA7\xA5", + "\xEC\x64" => "\xE9\xA7\xA4", + "\xEC\x65" => "\xE9\xA7\xB0", + "\xEC\x66" => "\xE9\xA7\xA3", + "\xEC\x67" => "\xE9\xA7\xAA", + "\xEC\x68" => "\xE9\xA7\xA9", + "\xEC\x69" => "\xE9\xA7\xA7", + "\xEC\x6A" => "\xE9\xAA\xB9", + "\xEC\x6B" => "\xE9\xAA\xBF", + "\xEC\x6C" => "\xE9\xAA\xB4", + "\xEC\x6D" => "\xE9\xAA\xBB", + "\xEC\x6E" => "\xE9\xAB\xB6", + "\xEC\x6F" => "\xE9\xAB\xBA", + "\xEC\x70" => "\xE9\xAB\xB9", + "\xEC\x71" => "\xE9\xAB\xB7", + "\xEC\x72" => "\xE9\xAC\xB3", + "\xEC\x73" => "\xE9\xAE\x80", + "\xEC\x74" => "\xE9\xAE\x85", + "\xEC\x75" => "\xE9\xAE\x87", + "\xEC\x76" => "\xE9\xAD\xBC", + "\xEC\x77" => "\xE9\xAD\xBE", + "\xEC\x78" => "\xE9\xAD\xBB", + "\xEC\x79" => "\xE9\xAE\x82", + "\xEC\x7A" => "\xE9\xAE\x93", + "\xEC\x7B" => "\xE9\xAE\x92", + "\xEC\x7C" => "\xE9\xAE\x90", + "\xEC\x7D" => "\xE9\xAD\xBA", + "\xEC\x7E" => "\xE9\xAE\x95", + "\xEC\xA1" => "\xE9\xAD\xBD", + "\xEC\xA2" => "\xE9\xAE\x88", + "\xEC\xA3" => "\xE9\xB4\xA5", + "\xEC\xA4" => "\xE9\xB4\x97", + "\xEC\xA5" => "\xE9\xB4\xA0", + "\xEC\xA6" => "\xE9\xB4\x9E", + "\xEC\xA7" => "\xE9\xB4\x94", + "\xEC\xA8" => "\xE9\xB4\xA9", + "\xEC\xA9" => "\xE9\xB4\x9D", + "\xEC\xAA" => "\xE9\xB4\x98", + "\xEC\xAB" => "\xE9\xB4\xA2", + "\xEC\xAC" => "\xE9\xB4\x90", + "\xEC\xAD" => "\xE9\xB4\x99", + "\xEC\xAE" => "\xE9\xB4\x9F", + "\xEC\xAF" => "\xE9\xBA\x88", + "\xEC\xB0" => "\xE9\xBA\x86", + "\xEC\xB1" => "\xE9\xBA\x87", + "\xEC\xB2" => "\xE9\xBA\xAE", + "\xEC\xB3" => "\xE9\xBA\xAD", + "\xEC\xB4" => "\xE9\xBB\x95", + "\xEC\xB5" => "\xE9\xBB\x96", + "\xEC\xB6" => "\xE9\xBB\xBA", + "\xEC\xB7" => "\xE9\xBC\x92", + "\xEC\xB8" => "\xE9\xBC\xBD", + "\xEC\xB9" => "\xE5\x84\xA6", + "\xEC\xBA" => "\xE5\x84\xA5", + "\xEC\xBB" => "\xE5\x84\xA2", + "\xEC\xBC" => "\xE5\x84\xA4", + "\xEC\xBD" => "\xE5\x84\xA0", + "\xEC\xBE" => "\xE5\x84\xA9", + "\xEC\xBF" => "\xE5\x8B\xB4", + "\xEC\xC0" => "\xE5\x9A\x93", + "\xEC\xC1" => "\xE5\x9A\x8C", + "\xEC\xC2" => "\xE5\x9A\x8D", + "\xEC\xC3" => "\xE5\x9A\x86", + "\xEC\xC4" => "\xE5\x9A\x84", + "\xEC\xC5" => "\xE5\x9A\x83", + "\xEC\xC6" => "\xE5\x99\xBE", + "\xEC\xC7" => "\xE5\x9A\x82", + "\xEC\xC8" => "\xE5\x99\xBF", + "\xEC\xC9" => "\xE5\x9A\x81", + "\xEC\xCA" => "\xE5\xA3\x96", + "\xEC\xCB" => "\xE5\xA3\x94", + "\xEC\xCC" => "\xE5\xA3\x8F", + "\xEC\xCD" => "\xE5\xA3\x92", + "\xEC\xCE" => "\xE5\xAC\xAD", + "\xEC\xCF" => "\xE5\xAC\xA5", + "\xEC\xD0" => "\xE5\xAC\xB2", + "\xEC\xD1" => "\xE5\xAC\xA3", + "\xEC\xD2" => "\xE5\xAC\xAC", + "\xEC\xD3" => "\xE5\xAC\xA7", + "\xEC\xD4" => "\xE5\xAC\xA6", + "\xEC\xD5" => "\xE5\xAC\xAF", + "\xEC\xD6" => "\xE5\xAC\xAE", + "\xEC\xD7" => "\xE5\xAD\xBB", + "\xEC\xD8" => "\xE5\xAF\xB1", + "\xEC\xD9" => "\xE5\xAF\xB2", + "\xEC\xDA" => "\xE5\xB6\xB7", + "\xEC\xDB" => "\xE5\xB9\xAC", + "\xEC\xDC" => "\xE5\xB9\xAA", + "\xEC\xDD" => "\xE5\xBE\xBE", + "\xEC\xDE" => "\xE5\xBE\xBB", + "\xEC\xDF" => "\xE6\x87\x83", + "\xEC\xE0" => "\xE6\x86\xB5", + "\xEC\xE1" => "\xE6\x86\xBC", + "\xEC\xE2" => "\xE6\x87\xA7", + "\xEC\xE3" => "\xE6\x87\xA0", + "\xEC\xE4" => "\xE6\x87\xA5", + "\xEC\xE5" => "\xE6\x87\xA4", + "\xEC\xE6" => "\xE6\x87\xA8", + "\xEC\xE7" => "\xE6\x87\x9E", + "\xEC\xE8" => "\xE6\x93\xAF", + "\xEC\xE9" => "\xE6\x93\xA9", + "\xEC\xEA" => "\xE6\x93\xA3", + "\xEC\xEB" => "\xE6\x93\xAB", + "\xEC\xEC" => "\xE6\x93\xA4", + "\xEC\xED" => "\xE6\x93\xA8", + "\xEC\xEE" => "\xE6\x96\x81", + "\xEC\xEF" => "\xE6\x96\x80", + "\xEC\xF0" => "\xE6\x96\xB6", + "\xEC\xF1" => "\xE6\x97\x9A", + "\xEC\xF2" => "\xE6\x9B\x92", + "\xEC\xF3" => "\xE6\xAA\x8D", + "\xEC\xF4" => "\xE6\xAA\x96", + "\xEC\xF5" => "\xE6\xAA\x81", + "\xEC\xF6" => "\xE6\xAA\xA5", + "\xEC\xF7" => "\xE6\xAA\x89", + "\xEC\xF8" => "\xE6\xAA\x9F", + "\xEC\xF9" => "\xE6\xAA\x9B", + "\xEC\xFA" => "\xE6\xAA\xA1", + "\xEC\xFB" => "\xE6\xAA\x9E", + "\xEC\xFC" => "\xE6\xAA\x87", + "\xEC\xFD" => "\xE6\xAA\x93", + "\xEC\xFE" => "\xE6\xAA\x8E", + "\xED\x40" => "\xE6\xAA\x95", + "\xED\x41" => "\xE6\xAA\x83", + "\xED\x42" => "\xE6\xAA\xA8", + "\xED\x43" => "\xE6\xAA\xA4", + "\xED\x44" => "\xE6\xAA\x91", + "\xED\x45" => "\xE6\xA9\xBF", + "\xED\x46" => "\xE6\xAA\xA6", + "\xED\x47" => "\xE6\xAA\x9A", + "\xED\x48" => "\xE6\xAA\x85", + "\xED\x49" => "\xE6\xAA\x8C", + "\xED\x4A" => "\xE6\xAA\x92", + "\xED\x4B" => "\xE6\xAD\x9B", + "\xED\x4C" => "\xE6\xAE\xAD", + "\xED\x4D" => "\xE6\xB0\x89", + "\xED\x4E" => "\xE6\xBF\x8C", + "\xED\x4F" => "\xE6\xBE\xA9", + "\xED\x50" => "\xE6\xBF\xB4", + "\xED\x51" => "\xE6\xBF\x94", + "\xED\x52" => "\xE6\xBF\xA3", + "\xED\x53" => "\xE6\xBF\x9C", + "\xED\x54" => "\xE6\xBF\xAD", + "\xED\x55" => "\xE6\xBF\xA7", + "\xED\x56" => "\xE6\xBF\xA6", + "\xED\x57" => "\xE6\xBF\x9E", + "\xED\x58" => "\xE6\xBF\xB2", + "\xED\x59" => "\xE6\xBF\x9D", + "\xED\x5A" => "\xE6\xBF\xA2", + "\xED\x5B" => "\xE6\xBF\xA8", + "\xED\x5C" => "\xE7\x87\xA1", + "\xED\x5D" => "\xE7\x87\xB1", + "\xED\x5E" => "\xE7\x87\xA8", + "\xED\x5F" => "\xE7\x87\xB2", + "\xED\x60" => "\xE7\x87\xA4", + "\xED\x61" => "\xE7\x87\xB0", + "\xED\x62" => "\xE7\x87\xA2", + "\xED\x63" => "\xE7\x8D\xB3", + "\xED\x64" => "\xE7\x8D\xAE", + "\xED\x65" => "\xE7\x8D\xAF", + "\xED\x66" => "\xE7\x92\x97", + "\xED\x67" => "\xE7\x92\xB2", + "\xED\x68" => "\xE7\x92\xAB", + "\xED\x69" => "\xE7\x92\x90", + "\xED\x6A" => "\xE7\x92\xAA", + "\xED\x6B" => "\xE7\x92\xAD", + "\xED\x6C" => "\xE7\x92\xB1", + "\xED\x6D" => "\xE7\x92\xA5", + "\xED\x6E" => "\xE7\x92\xAF", + "\xED\x6F" => "\xE7\x94\x90", + "\xED\x70" => "\xE7\x94\x91", + "\xED\x71" => "\xE7\x94\x92", + "\xED\x72" => "\xE7\x94\x8F", + "\xED\x73" => "\xE7\x96\x84", + "\xED\x74" => "\xE7\x99\x83", + "\xED\x75" => "\xE7\x99\x88", + "\xED\x76" => "\xE7\x99\x89", + "\xED\x77" => "\xE7\x99\x87", + "\xED\x78" => "\xE7\x9A\xA4", + "\xED\x79" => "\xE7\x9B\xA9", + "\xED\x7A" => "\xE7\x9E\xB5", + "\xED\x7B" => "\xE7\x9E\xAB", + "\xED\x7C" => "\xE7\x9E\xB2", + "\xED\x7D" => "\xE7\x9E\xB7", + "\xED\x7E" => "\xE7\x9E\xB6", + "\xED\xA1" => "\xE7\x9E\xB4", + "\xED\xA2" => "\xE7\x9E\xB1", + "\xED\xA3" => "\xE7\x9E\xA8", + "\xED\xA4" => "\xE7\x9F\xB0", + "\xED\xA5" => "\xE7\xA3\xB3", + "\xED\xA6" => "\xE7\xA3\xBD", + "\xED\xA7" => "\xE7\xA4\x82", + "\xED\xA8" => "\xE7\xA3\xBB", + "\xED\xA9" => "\xE7\xA3\xBC", + "\xED\xAA" => "\xE7\xA3\xB2", + "\xED\xAB" => "\xE7\xA4\x85", + "\xED\xAC" => "\xE7\xA3\xB9", + "\xED\xAD" => "\xE7\xA3\xBE", + "\xED\xAE" => "\xE7\xA4\x84", + "\xED\xAF" => "\xE7\xA6\xAB", + "\xED\xB0" => "\xE7\xA6\xA8", + "\xED\xB1" => "\xE7\xA9\x9C", + "\xED\xB2" => "\xE7\xA9\x9B", + "\xED\xB3" => "\xE7\xA9\x96", + "\xED\xB4" => "\xE7\xA9\x98", + "\xED\xB5" => "\xE7\xA9\x94", + "\xED\xB6" => "\xE7\xA9\x9A", + "\xED\xB7" => "\xE7\xAA\xBE", + "\xED\xB8" => "\xE7\xAB\x80", + "\xED\xB9" => "\xE7\xAB\x81", + "\xED\xBA" => "\xE7\xB0\x85", + "\xED\xBB" => "\xE7\xB0\x8F", + "\xED\xBC" => "\xE7\xAF\xB2", + "\xED\xBD" => "\xE7\xB0\x80", + "\xED\xBE" => "\xE7\xAF\xBF", + "\xED\xBF" => "\xE7\xAF\xBB", + "\xED\xC0" => "\xE7\xB0\x8E", + "\xED\xC1" => "\xE7\xAF\xB4", + "\xED\xC2" => "\xE7\xB0\x8B", + "\xED\xC3" => "\xE7\xAF\xB3", + "\xED\xC4" => "\xE7\xB0\x82", + "\xED\xC5" => "\xE7\xB0\x89", + "\xED\xC6" => "\xE7\xB0\x83", + "\xED\xC7" => "\xE7\xB0\x81", + "\xED\xC8" => "\xE7\xAF\xB8", + "\xED\xC9" => "\xE7\xAF\xBD", + "\xED\xCA" => "\xE7\xB0\x86", + "\xED\xCB" => "\xE7\xAF\xB0", + "\xED\xCC" => "\xE7\xAF\xB1", + "\xED\xCD" => "\xE7\xB0\x90", + "\xED\xCE" => "\xE7\xB0\x8A", + "\xED\xCF" => "\xE7\xB3\xA8", + "\xED\xD0" => "\xE7\xB8\xAD", + "\xED\xD1" => "\xE7\xB8\xBC", + "\xED\xD2" => "\xE7\xB9\x82", + "\xED\xD3" => "\xE7\xB8\xB3", + "\xED\xD4" => "\xE9\xA1\x88", + "\xED\xD5" => "\xE7\xB8\xB8", + "\xED\xD6" => "\xE7\xB8\xAA", + "\xED\xD7" => "\xE7\xB9\x89", + "\xED\xD8" => "\xE7\xB9\x80", + "\xED\xD9" => "\xE7\xB9\x87", + "\xED\xDA" => "\xE7\xB8\xA9", + "\xED\xDB" => "\xE7\xB9\x8C", + "\xED\xDC" => "\xE7\xB8\xB0", + "\xED\xDD" => "\xE7\xB8\xBB", + "\xED\xDE" => "\xE7\xB8\xB6", + "\xED\xDF" => "\xE7\xB9\x84", + "\xED\xE0" => "\xE7\xB8\xBA", + "\xED\xE1" => "\xE7\xBD\x85", + "\xED\xE2" => "\xE7\xBD\xBF", + "\xED\xE3" => "\xE7\xBD\xBE", + "\xED\xE4" => "\xE7\xBD\xBD", + "\xED\xE5" => "\xE7\xBF\xB4", + "\xED\xE6" => "\xE7\xBF\xB2", + "\xED\xE7" => "\xE8\x80\xAC", + "\xED\xE8" => "\xE8\x86\xBB", + "\xED\xE9" => "\xE8\x87\x84", + "\xED\xEA" => "\xE8\x87\x8C", + "\xED\xEB" => "\xE8\x87\x8A", + "\xED\xEC" => "\xE8\x87\x85", + "\xED\xED" => "\xE8\x87\x87", + "\xED\xEE" => "\xE8\x86\xBC", + "\xED\xEF" => "\xE8\x87\xA9", + "\xED\xF0" => "\xE8\x89\x9B", + "\xED\xF1" => "\xE8\x89\x9A", + "\xED\xF2" => "\xE8\x89\x9C", + "\xED\xF3" => "\xE8\x96\x83", + "\xED\xF4" => "\xE8\x96\x80", + "\xED\xF5" => "\xE8\x96\x8F", + "\xED\xF6" => "\xE8\x96\xA7", + "\xED\xF7" => "\xE8\x96\x95", + "\xED\xF8" => "\xE8\x96\xA0", + "\xED\xF9" => "\xE8\x96\x8B", + "\xED\xFA" => "\xE8\x96\xA3", + "\xED\xFB" => "\xE8\x95\xBB", + "\xED\xFC" => "\xE8\x96\xA4", + "\xED\xFD" => "\xE8\x96\x9A", + "\xED\xFE" => "\xE8\x96\x9E", + "\xEE\x40" => "\xE8\x95\xB7", + "\xEE\x41" => "\xE8\x95\xBC", + "\xEE\x42" => "\xE8\x96\x89", + "\xEE\x43" => "\xE8\x96\xA1", + "\xEE\x44" => "\xE8\x95\xBA", + "\xEE\x45" => "\xE8\x95\xB8", + "\xEE\x46" => "\xE8\x95\x97", + "\xEE\x47" => "\xE8\x96\x8E", + "\xEE\x48" => "\xE8\x96\x96", + "\xEE\x49" => "\xE8\x96\x86", + "\xEE\x4A" => "\xE8\x96\x8D", + "\xEE\x4B" => "\xE8\x96\x99", + "\xEE\x4C" => "\xE8\x96\x9D", + "\xEE\x4D" => "\xE8\x96\x81", + "\xEE\x4E" => "\xE8\x96\xA2", + "\xEE\x4F" => "\xE8\x96\x82", + "\xEE\x50" => "\xE8\x96\x88", + "\xEE\x51" => "\xE8\x96\x85", + "\xEE\x52" => "\xE8\x95\xB9", + "\xEE\x53" => "\xE8\x95\xB6", + "\xEE\x54" => "\xE8\x96\x98", + "\xEE\x55" => "\xE8\x96\x90", + "\xEE\x56" => "\xE8\x96\x9F", + "\xEE\x57" => "\xE8\x99\xA8", + "\xEE\x58" => "\xE8\x9E\xBE", + "\xEE\x59" => "\xE8\x9E\xAA", + "\xEE\x5A" => "\xE8\x9E\xAD", + "\xEE\x5B" => "\xE8\x9F\x85", + "\xEE\x5C" => "\xE8\x9E\xB0", + "\xEE\x5D" => "\xE8\x9E\xAC", + "\xEE\x5E" => "\xE8\x9E\xB9", + "\xEE\x5F" => "\xE8\x9E\xB5", + "\xEE\x60" => "\xE8\x9E\xBC", + "\xEE\x61" => "\xE8\x9E\xAE", + "\xEE\x62" => "\xE8\x9F\x89", + "\xEE\x63" => "\xE8\x9F\x83", + "\xEE\x64" => "\xE8\x9F\x82", + "\xEE\x65" => "\xE8\x9F\x8C", + "\xEE\x66" => "\xE8\x9E\xB7", + "\xEE\x67" => "\xE8\x9E\xAF", + "\xEE\x68" => "\xE8\x9F\x84", + "\xEE\x69" => "\xE8\x9F\x8A", + "\xEE\x6A" => "\xE8\x9E\xB4", + "\xEE\x6B" => "\xE8\x9E\xB6", + "\xEE\x6C" => "\xE8\x9E\xBF", + "\xEE\x6D" => "\xE8\x9E\xB8", + "\xEE\x6E" => "\xE8\x9E\xBD", + "\xEE\x6F" => "\xE8\x9F\x9E", + "\xEE\x70" => "\xE8\x9E\xB2", + "\xEE\x71" => "\xE8\xA4\xB5", + "\xEE\x72" => "\xE8\xA4\xB3", + "\xEE\x73" => "\xE8\xA4\xBC", + "\xEE\x74" => "\xE8\xA4\xBE", + "\xEE\x75" => "\xE8\xA5\x81", + "\xEE\x76" => "\xE8\xA5\x92", + "\xEE\x77" => "\xE8\xA4\xB7", + "\xEE\x78" => "\xE8\xA5\x82", + "\xEE\x79" => "\xE8\xA6\xAD", + "\xEE\x7A" => "\xE8\xA6\xAF", + "\xEE\x7B" => "\xE8\xA6\xAE", + "\xEE\x7C" => "\xE8\xA7\xB2", + "\xEE\x7D" => "\xE8\xA7\xB3", + "\xEE\x7E" => "\xE8\xAC\x9E", + "\xEE\xA1" => "\xE8\xAC\x98", + "\xEE\xA2" => "\xE8\xAC\x96", + "\xEE\xA3" => "\xE8\xAC\x91", + "\xEE\xA4" => "\xE8\xAC\x85", + "\xEE\xA5" => "\xE8\xAC\x8B", + "\xEE\xA6" => "\xE8\xAC\xA2", + "\xEE\xA7" => "\xE8\xAC\x8F", + "\xEE\xA8" => "\xE8\xAC\x92", + "\xEE\xA9" => "\xE8\xAC\x95", + "\xEE\xAA" => "\xE8\xAC\x87", + "\xEE\xAB" => "\xE8\xAC\x8D", + "\xEE\xAC" => "\xE8\xAC\x88", + "\xEE\xAD" => "\xE8\xAC\x86", + "\xEE\xAE" => "\xE8\xAC\x9C", + "\xEE\xAF" => "\xE8\xAC\x93", + "\xEE\xB0" => "\xE8\xAC\x9A", + "\xEE\xB1" => "\xE8\xB1\x8F", + "\xEE\xB2" => "\xE8\xB1\xB0", + "\xEE\xB3" => "\xE8\xB1\xB2", + "\xEE\xB4" => "\xE8\xB1\xB1", + "\xEE\xB5" => "\xE8\xB1\xAF", + "\xEE\xB6" => "\xE8\xB2\x95", + "\xEE\xB7" => "\xE8\xB2\x94", + "\xEE\xB8" => "\xE8\xB3\xB9", + "\xEE\xB9" => "\xE8\xB5\xAF", + "\xEE\xBA" => "\xE8\xB9\x8E", + "\xEE\xBB" => "\xE8\xB9\x8D", + "\xEE\xBC" => "\xE8\xB9\x93", + "\xEE\xBD" => "\xE8\xB9\x90", + "\xEE\xBE" => "\xE8\xB9\x8C", + "\xEE\xBF" => "\xE8\xB9\x87", + "\xEE\xC0" => "\xE8\xBD\x83", + "\xEE\xC1" => "\xE8\xBD\x80", + "\xEE\xC2" => "\xE9\x82\x85", + "\xEE\xC3" => "\xE9\x81\xBE", + "\xEE\xC4" => "\xE9\x84\xB8", + "\xEE\xC5" => "\xE9\x86\x9A", + "\xEE\xC6" => "\xE9\x86\xA2", + "\xEE\xC7" => "\xE9\x86\x9B", + "\xEE\xC8" => "\xE9\x86\x99", + "\xEE\xC9" => "\xE9\x86\x9F", + "\xEE\xCA" => "\xE9\x86\xA1", + "\xEE\xCB" => "\xE9\x86\x9D", + "\xEE\xCC" => "\xE9\x86\xA0", + "\xEE\xCD" => "\xE9\x8E\xA1", + "\xEE\xCE" => "\xE9\x8E\x83", + "\xEE\xCF" => "\xE9\x8E\xAF", + "\xEE\xD0" => "\xE9\x8D\xA4", + "\xEE\xD1" => "\xE9\x8D\x96", + "\xEE\xD2" => "\xE9\x8D\x87", + "\xEE\xD3" => "\xE9\x8D\xBC", + "\xEE\xD4" => "\xE9\x8D\x98", + "\xEE\xD5" => "\xE9\x8D\x9C", + "\xEE\xD6" => "\xE9\x8D\xB6", + "\xEE\xD7" => "\xE9\x8D\x89", + "\xEE\xD8" => "\xE9\x8D\x90", + "\xEE\xD9" => "\xE9\x8D\x91", + "\xEE\xDA" => "\xE9\x8D\xA0", + "\xEE\xDB" => "\xE9\x8D\xAD", + "\xEE\xDC" => "\xE9\x8E\x8F", + "\xEE\xDD" => "\xE9\x8D\x8C", + "\xEE\xDE" => "\xE9\x8D\xAA", + "\xEE\xDF" => "\xE9\x8D\xB9", + "\xEE\xE0" => "\xE9\x8D\x97", + "\xEE\xE1" => "\xE9\x8D\x95", + "\xEE\xE2" => "\xE9\x8D\x92", + "\xEE\xE3" => "\xE9\x8D\x8F", + "\xEE\xE4" => "\xE9\x8D\xB1", + "\xEE\xE5" => "\xE9\x8D\xB7", + "\xEE\xE6" => "\xE9\x8D\xBB", + "\xEE\xE7" => "\xE9\x8D\xA1", + "\xEE\xE8" => "\xE9\x8D\x9E", + "\xEE\xE9" => "\xE9\x8D\xA3", + "\xEE\xEA" => "\xE9\x8D\xA7", + "\xEE\xEB" => "\xE9\x8E\x80", + "\xEE\xEC" => "\xE9\x8D\x8E", + "\xEE\xED" => "\xE9\x8D\x99", + "\xEE\xEE" => "\xE9\x97\x87", + "\xEE\xEF" => "\xE9\x97\x80", + "\xEE\xF0" => "\xE9\x97\x89", + "\xEE\xF1" => "\xE9\x97\x83", + "\xEE\xF2" => "\xE9\x97\x85", + "\xEE\xF3" => "\xE9\x96\xB7", + "\xEE\xF4" => "\xE9\x9A\xAE", + "\xEE\xF5" => "\xE9\x9A\xB0", + "\xEE\xF6" => "\xE9\x9A\xAC", + "\xEE\xF7" => "\xE9\x9C\xA0", + "\xEE\xF8" => "\xE9\x9C\x9F", + "\xEE\xF9" => "\xE9\x9C\x98", + "\xEE\xFA" => "\xE9\x9C\x9D", + "\xEE\xFB" => "\xE9\x9C\x99", + "\xEE\xFC" => "\xE9\x9E\x9A", + "\xEE\xFD" => "\xE9\x9E\xA1", + "\xEE\xFE" => "\xE9\x9E\x9C", + "\xEF\x40" => "\xE9\x9E\x9E", + "\xEF\x41" => "\xE9\x9E\x9D", + "\xEF\x42" => "\xE9\x9F\x95", + "\xEF\x43" => "\xE9\x9F\x94", + "\xEF\x44" => "\xE9\x9F\xB1", + "\xEF\x45" => "\xE9\xA1\x81", + "\xEF\x46" => "\xE9\xA1\x84", + "\xEF\x47" => "\xE9\xA1\x8A", + "\xEF\x48" => "\xE9\xA1\x89", + "\xEF\x49" => "\xE9\xA1\x85", + "\xEF\x4A" => "\xE9\xA1\x83", + "\xEF\x4B" => "\xE9\xA4\xA5", + "\xEF\x4C" => "\xE9\xA4\xAB", + "\xEF\x4D" => "\xE9\xA4\xAC", + "\xEF\x4E" => "\xE9\xA4\xAA", + "\xEF\x4F" => "\xE9\xA4\xB3", + "\xEF\x50" => "\xE9\xA4\xB2", + "\xEF\x51" => "\xE9\xA4\xAF", + "\xEF\x52" => "\xE9\xA4\xAD", + "\xEF\x53" => "\xE9\xA4\xB1", + "\xEF\x54" => "\xE9\xA4\xB0", + "\xEF\x55" => "\xE9\xA6\x98", + "\xEF\x56" => "\xE9\xA6\xA3", + "\xEF\x57" => "\xE9\xA6\xA1", + "\xEF\x58" => "\xE9\xA8\x82", + "\xEF\x59" => "\xE9\xA7\xBA", + "\xEF\x5A" => "\xE9\xA7\xB4", + "\xEF\x5B" => "\xE9\xA7\xB7", + "\xEF\x5C" => "\xE9\xA7\xB9", + "\xEF\x5D" => "\xE9\xA7\xB8", + "\xEF\x5E" => "\xE9\xA7\xB6", + "\xEF\x5F" => "\xE9\xA7\xBB", + "\xEF\x60" => "\xE9\xA7\xBD", + "\xEF\x61" => "\xE9\xA7\xBE", + "\xEF\x62" => "\xE9\xA7\xBC", + "\xEF\x63" => "\xE9\xA8\x83", + "\xEF\x64" => "\xE9\xAA\xBE", + "\xEF\x65" => "\xE9\xAB\xBE", + "\xEF\x66" => "\xE9\xAB\xBD", + "\xEF\x67" => "\xE9\xAC\x81", + "\xEF\x68" => "\xE9\xAB\xBC", + "\xEF\x69" => "\xE9\xAD\x88", + "\xEF\x6A" => "\xE9\xAE\x9A", + "\xEF\x6B" => "\xE9\xAE\xA8", + "\xEF\x6C" => "\xE9\xAE\x9E", + "\xEF\x6D" => "\xE9\xAE\x9B", + "\xEF\x6E" => "\xE9\xAE\xA6", + "\xEF\x6F" => "\xE9\xAE\xA1", + "\xEF\x70" => "\xE9\xAE\xA5", + "\xEF\x71" => "\xE9\xAE\xA4", + "\xEF\x72" => "\xE9\xAE\x86", + "\xEF\x73" => "\xE9\xAE\xA2", + "\xEF\x74" => "\xE9\xAE\xA0", + "\xEF\x75" => "\xE9\xAE\xAF", + "\xEF\x76" => "\xE9\xB4\xB3", + "\xEF\x77" => "\xE9\xB5\x81", + "\xEF\x78" => "\xE9\xB5\xA7", + "\xEF\x79" => "\xE9\xB4\xB6", + "\xEF\x7A" => "\xE9\xB4\xAE", + "\xEF\x7B" => "\xE9\xB4\xAF", + "\xEF\x7C" => "\xE9\xB4\xB1", + "\xEF\x7D" => "\xE9\xB4\xB8", + "\xEF\x7E" => "\xE9\xB4\xB0", + "\xEF\xA1" => "\xE9\xB5\x85", + "\xEF\xA2" => "\xE9\xB5\x82", + "\xEF\xA3" => "\xE9\xB5\x83", + "\xEF\xA4" => "\xE9\xB4\xBE", + "\xEF\xA5" => "\xE9\xB4\xB7", + "\xEF\xA6" => "\xE9\xB5\x80", + "\xEF\xA7" => "\xE9\xB4\xBD", + "\xEF\xA8" => "\xE7\xBF\xB5", + "\xEF\xA9" => "\xE9\xB4\xAD", + "\xEF\xAA" => "\xE9\xBA\x8A", + "\xEF\xAB" => "\xE9\xBA\x89", + "\xEF\xAC" => "\xE9\xBA\x8D", + "\xEF\xAD" => "\xE9\xBA\xB0", + "\xEF\xAE" => "\xE9\xBB\x88", + "\xEF\xAF" => "\xE9\xBB\x9A", + "\xEF\xB0" => "\xE9\xBB\xBB", + "\xEF\xB1" => "\xE9\xBB\xBF", + "\xEF\xB2" => "\xE9\xBC\xA4", + "\xEF\xB3" => "\xE9\xBC\xA3", + "\xEF\xB4" => "\xE9\xBC\xA2", + "\xEF\xB5" => "\xE9\xBD\x94", + "\xEF\xB6" => "\xE9\xBE\xA0", + "\xEF\xB7" => "\xE5\x84\xB1", + "\xEF\xB8" => "\xE5\x84\xAD", + "\xEF\xB9" => "\xE5\x84\xAE", + "\xEF\xBA" => "\xE5\x9A\x98", + "\xEF\xBB" => "\xE5\x9A\x9C", + "\xEF\xBC" => "\xE5\x9A\x97", + "\xEF\xBD" => "\xE5\x9A\x9A", + "\xEF\xBE" => "\xE5\x9A\x9D", + "\xEF\xBF" => "\xE5\x9A\x99", + "\xEF\xC0" => "\xE5\xA5\xB0", + "\xEF\xC1" => "\xE5\xAC\xBC", + "\xEF\xC2" => "\xE5\xB1\xA9", + "\xEF\xC3" => "\xE5\xB1\xAA", + "\xEF\xC4" => "\xE5\xB7\x80", + "\xEF\xC5" => "\xE5\xB9\xAD", + "\xEF\xC6" => "\xE5\xB9\xAE", + "\xEF\xC7" => "\xE6\x87\x98", + "\xEF\xC8" => "\xE6\x87\x9F", + "\xEF\xC9" => "\xE6\x87\xAD", + "\xEF\xCA" => "\xE6\x87\xAE", + "\xEF\xCB" => "\xE6\x87\xB1", + "\xEF\xCC" => "\xE6\x87\xAA", + "\xEF\xCD" => "\xE6\x87\xB0", + "\xEF\xCE" => "\xE6\x87\xAB", + "\xEF\xCF" => "\xE6\x87\x96", + "\xEF\xD0" => "\xE6\x87\xA9", + "\xEF\xD1" => "\xE6\x93\xBF", + "\xEF\xD2" => "\xE6\x94\x84", + "\xEF\xD3" => "\xE6\x93\xBD", + "\xEF\xD4" => "\xE6\x93\xB8", + "\xEF\xD5" => "\xE6\x94\x81", + "\xEF\xD6" => "\xE6\x94\x83", + "\xEF\xD7" => "\xE6\x93\xBC", + "\xEF\xD8" => "\xE6\x96\x94", + "\xEF\xD9" => "\xE6\x97\x9B", + "\xEF\xDA" => "\xE6\x9B\x9A", + "\xEF\xDB" => "\xE6\x9B\x9B", + "\xEF\xDC" => "\xE6\x9B\x98", + "\xEF\xDD" => "\xE6\xAB\x85", + "\xEF\xDE" => "\xE6\xAA\xB9", + "\xEF\xDF" => "\xE6\xAA\xBD", + "\xEF\xE0" => "\xE6\xAB\xA1", + "\xEF\xE1" => "\xE6\xAB\x86", + "\xEF\xE2" => "\xE6\xAA\xBA", + "\xEF\xE3" => "\xE6\xAA\xB6", + "\xEF\xE4" => "\xE6\xAA\xB7", + "\xEF\xE5" => "\xE6\xAB\x87", + "\xEF\xE6" => "\xE6\xAA\xB4", + "\xEF\xE7" => "\xE6\xAA\xAD", + "\xEF\xE8" => "\xE6\xAD\x9E", + "\xEF\xE9" => "\xE6\xAF\x89", + "\xEF\xEA" => "\xE6\xB0\x8B", + "\xEF\xEB" => "\xE7\x80\x87", + "\xEF\xEC" => "\xE7\x80\x8C", + "\xEF\xED" => "\xE7\x80\x8D", + "\xEF\xEE" => "\xE7\x80\x81", + "\xEF\xEF" => "\xE7\x80\x85", + "\xEF\xF0" => "\xE7\x80\x94", + "\xEF\xF1" => "\xE7\x80\x8E", + "\xEF\xF2" => "\xE6\xBF\xBF", + "\xEF\xF3" => "\xE7\x80\x80", + "\xEF\xF4" => "\xE6\xBF\xBB", + "\xEF\xF5" => "\xE7\x80\xA6", + "\xEF\xF6" => "\xE6\xBF\xBC", + "\xEF\xF7" => "\xE6\xBF\xB7", + "\xEF\xF8" => "\xE7\x80\x8A", + "\xEF\xF9" => "\xE7\x88\x81", + "\xEF\xFA" => "\xE7\x87\xBF", + "\xEF\xFB" => "\xE7\x87\xB9", + "\xEF\xFC" => "\xE7\x88\x83", + "\xEF\xFD" => "\xE7\x87\xBD", + "\xEF\xFE" => "\xE7\x8D\xB6", + "\xF0\x40" => "\xE7\x92\xB8", + "\xF0\x41" => "\xE7\x93\x80", + "\xF0\x42" => "\xE7\x92\xB5", + "\xF0\x43" => "\xE7\x93\x81", + "\xF0\x44" => "\xE7\x92\xBE", + "\xF0\x45" => "\xE7\x92\xB6", + "\xF0\x46" => "\xE7\x92\xBB", + "\xF0\x47" => "\xE7\x93\x82", + "\xF0\x48" => "\xE7\x94\x94", + "\xF0\x49" => "\xE7\x94\x93", + "\xF0\x4A" => "\xE7\x99\x9C", + "\xF0\x4B" => "\xE7\x99\xA4", + "\xF0\x4C" => "\xE7\x99\x99", + "\xF0\x4D" => "\xE7\x99\x90", + "\xF0\x4E" => "\xE7\x99\x93", + "\xF0\x4F" => "\xE7\x99\x97", + "\xF0\x50" => "\xE7\x99\x9A", + "\xF0\x51" => "\xE7\x9A\xA6", + "\xF0\x52" => "\xE7\x9A\xBD", + "\xF0\x53" => "\xE7\x9B\xAC", + "\xF0\x54" => "\xE7\x9F\x82", + "\xF0\x55" => "\xE7\x9E\xBA", + "\xF0\x56" => "\xE7\xA3\xBF", + "\xF0\x57" => "\xE7\xA4\x8C", + "\xF0\x58" => "\xE7\xA4\x93", + "\xF0\x59" => "\xE7\xA4\x94", + "\xF0\x5A" => "\xE7\xA4\x89", + "\xF0\x5B" => "\xE7\xA4\x90", + "\xF0\x5C" => "\xE7\xA4\x92", + "\xF0\x5D" => "\xE7\xA4\x91", + "\xF0\x5E" => "\xE7\xA6\xAD", + "\xF0\x5F" => "\xE7\xA6\xAC", + "\xF0\x60" => "\xE7\xA9\x9F", + "\xF0\x61" => "\xE7\xB0\x9C", + "\xF0\x62" => "\xE7\xB0\xA9", + "\xF0\x63" => "\xE7\xB0\x99", + "\xF0\x64" => "\xE7\xB0\xA0", + "\xF0\x65" => "\xE7\xB0\x9F", + "\xF0\x66" => "\xE7\xB0\xAD", + "\xF0\x67" => "\xE7\xB0\x9D", + "\xF0\x68" => "\xE7\xB0\xA6", + "\xF0\x69" => "\xE7\xB0\xA8", + "\xF0\x6A" => "\xE7\xB0\xA2", + "\xF0\x6B" => "\xE7\xB0\xA5", + "\xF0\x6C" => "\xE7\xB0\xB0", + "\xF0\x6D" => "\xE7\xB9\x9C", + "\xF0\x6E" => "\xE7\xB9\x90", + "\xF0\x6F" => "\xE7\xB9\x96", + "\xF0\x70" => "\xE7\xB9\xA3", + "\xF0\x71" => "\xE7\xB9\x98", + "\xF0\x72" => "\xE7\xB9\xA2", + "\xF0\x73" => "\xE7\xB9\x9F", + "\xF0\x74" => "\xE7\xB9\x91", + "\xF0\x75" => "\xE7\xB9\xA0", + "\xF0\x76" => "\xE7\xB9\x97", + "\xF0\x77" => "\xE7\xB9\x93", + "\xF0\x78" => "\xE7\xBE\xB5", + "\xF0\x79" => "\xE7\xBE\xB3", + "\xF0\x7A" => "\xE7\xBF\xB7", + "\xF0\x7B" => "\xE7\xBF\xB8", + "\xF0\x7C" => "\xE8\x81\xB5", + "\xF0\x7D" => "\xE8\x87\x91", + "\xF0\x7E" => "\xE8\x87\x92", + "\xF0\xA1" => "\xE8\x87\x90", + "\xF0\xA2" => "\xE8\x89\x9F", + "\xF0\xA3" => "\xE8\x89\x9E", + "\xF0\xA4" => "\xE8\x96\xB4", + "\xF0\xA5" => "\xE8\x97\x86", + "\xF0\xA6" => "\xE8\x97\x80", + "\xF0\xA7" => "\xE8\x97\x83", + "\xF0\xA8" => "\xE8\x97\x82", + "\xF0\xA9" => "\xE8\x96\xB3", + "\xF0\xAA" => "\xE8\x96\xB5", + "\xF0\xAB" => "\xE8\x96\xBD", + "\xF0\xAC" => "\xE8\x97\x87", + "\xF0\xAD" => "\xE8\x97\x84", + "\xF0\xAE" => "\xE8\x96\xBF", + "\xF0\xAF" => "\xE8\x97\x8B", + "\xF0\xB0" => "\xE8\x97\x8E", + "\xF0\xB1" => "\xE8\x97\x88", + "\xF0\xB2" => "\xE8\x97\x85", + "\xF0\xB3" => "\xE8\x96\xB1", + "\xF0\xB4" => "\xE8\x96\xB6", + "\xF0\xB5" => "\xE8\x97\x92", + "\xF0\xB6" => "\xE8\x98\xA4", + "\xF0\xB7" => "\xE8\x96\xB8", + "\xF0\xB8" => "\xE8\x96\xB7", + "\xF0\xB9" => "\xE8\x96\xBE", + "\xF0\xBA" => "\xE8\x99\xA9", + "\xF0\xBB" => "\xE8\x9F\xA7", + "\xF0\xBC" => "\xE8\x9F\xA6", + "\xF0\xBD" => "\xE8\x9F\xA2", + "\xF0\xBE" => "\xE8\x9F\x9B", + "\xF0\xBF" => "\xE8\x9F\xAB", + "\xF0\xC0" => "\xE8\x9F\xAA", + "\xF0\xC1" => "\xE8\x9F\xA5", + "\xF0\xC2" => "\xE8\x9F\x9F", + "\xF0\xC3" => "\xE8\x9F\xB3", + "\xF0\xC4" => "\xE8\x9F\xA4", + "\xF0\xC5" => "\xE8\x9F\x94", + "\xF0\xC6" => "\xE8\x9F\x9C", + "\xF0\xC7" => "\xE8\x9F\x93", + "\xF0\xC8" => "\xE8\x9F\xAD", + "\xF0\xC9" => "\xE8\x9F\x98", + "\xF0\xCA" => "\xE8\x9F\xA3", + "\xF0\xCB" => "\xE8\x9E\xA4", + "\xF0\xCC" => "\xE8\x9F\x97", + "\xF0\xCD" => "\xE8\x9F\x99", + "\xF0\xCE" => "\xE8\xA0\x81", + "\xF0\xCF" => "\xE8\x9F\xB4", + "\xF0\xD0" => "\xE8\x9F\xA8", + "\xF0\xD1" => "\xE8\x9F\x9D", + "\xF0\xD2" => "\xE8\xA5\x93", + "\xF0\xD3" => "\xE8\xA5\x8B", + "\xF0\xD4" => "\xE8\xA5\x8F", + "\xF0\xD5" => "\xE8\xA5\x8C", + "\xF0\xD6" => "\xE8\xA5\x86", + "\xF0\xD7" => "\xE8\xA5\x90", + "\xF0\xD8" => "\xE8\xA5\x91", + "\xF0\xD9" => "\xE8\xA5\x89", + "\xF0\xDA" => "\xE8\xAC\xAA", + "\xF0\xDB" => "\xE8\xAC\xA7", + "\xF0\xDC" => "\xE8\xAC\xA3", + "\xF0\xDD" => "\xE8\xAC\xB3", + "\xF0\xDE" => "\xE8\xAC\xB0", + "\xF0\xDF" => "\xE8\xAC\xB5", + "\xF0\xE0" => "\xE8\xAD\x87", + "\xF0\xE1" => "\xE8\xAC\xAF", + "\xF0\xE2" => "\xE8\xAC\xBC", + "\xF0\xE3" => "\xE8\xAC\xBE", + "\xF0\xE4" => "\xE8\xAC\xB1", + "\xF0\xE5" => "\xE8\xAC\xA5", + "\xF0\xE6" => "\xE8\xAC\xB7", + "\xF0\xE7" => "\xE8\xAC\xA6", + "\xF0\xE8" => "\xE8\xAC\xB6", + "\xF0\xE9" => "\xE8\xAC\xAE", + "\xF0\xEA" => "\xE8\xAC\xA4", + "\xF0\xEB" => "\xE8\xAC\xBB", + "\xF0\xEC" => "\xE8\xAC\xBD", + "\xF0\xED" => "\xE8\xAC\xBA", + "\xF0\xEE" => "\xE8\xB1\x82", + "\xF0\xEF" => "\xE8\xB1\xB5", + "\xF0\xF0" => "\xE8\xB2\x99", + "\xF0\xF1" => "\xE8\xB2\x98", + "\xF0\xF2" => "\xE8\xB2\x97", + "\xF0\xF3" => "\xE8\xB3\xBE", + "\xF0\xF4" => "\xE8\xB4\x84", + "\xF0\xF5" => "\xE8\xB4\x82", + "\xF0\xF6" => "\xE8\xB4\x80", + "\xF0\xF7" => "\xE8\xB9\x9C", + "\xF0\xF8" => "\xE8\xB9\xA2", + "\xF0\xF9" => "\xE8\xB9\xA0", + "\xF0\xFA" => "\xE8\xB9\x97", + "\xF0\xFB" => "\xE8\xB9\x96", + "\xF0\xFC" => "\xE8\xB9\x9E", + "\xF0\xFD" => "\xE8\xB9\xA5", + "\xF0\xFE" => "\xE8\xB9\xA7", + "\xF1\x40" => "\xE8\xB9\x9B", + "\xF1\x41" => "\xE8\xB9\x9A", + "\xF1\x42" => "\xE8\xB9\xA1", + "\xF1\x43" => "\xE8\xB9\x9D", + "\xF1\x44" => "\xE8\xB9\xA9", + "\xF1\x45" => "\xE8\xB9\x94", + "\xF1\x46" => "\xE8\xBD\x86", + "\xF1\x47" => "\xE8\xBD\x87", + "\xF1\x48" => "\xE8\xBD\x88", + "\xF1\x49" => "\xE8\xBD\x8B", + "\xF1\x4A" => "\xE9\x84\xA8", + "\xF1\x4B" => "\xE9\x84\xBA", + "\xF1\x4C" => "\xE9\x84\xBB", + "\xF1\x4D" => "\xE9\x84\xBE", + "\xF1\x4E" => "\xE9\x86\xA8", + "\xF1\x4F" => "\xE9\x86\xA5", + "\xF1\x50" => "\xE9\x86\xA7", + "\xF1\x51" => "\xE9\x86\xAF", + "\xF1\x52" => "\xE9\x86\xAA", + "\xF1\x53" => "\xE9\x8E\xB5", + "\xF1\x54" => "\xE9\x8E\x8C", + "\xF1\x55" => "\xE9\x8E\x92", + "\xF1\x56" => "\xE9\x8E\xB7", + "\xF1\x57" => "\xE9\x8E\x9B", + "\xF1\x58" => "\xE9\x8E\x9D", + "\xF1\x59" => "\xE9\x8E\x89", + "\xF1\x5A" => "\xE9\x8E\xA7", + "\xF1\x5B" => "\xE9\x8E\x8E", + "\xF1\x5C" => "\xE9\x8E\xAA", + "\xF1\x5D" => "\xE9\x8E\x9E", + "\xF1\x5E" => "\xE9\x8E\xA6", + "\xF1\x5F" => "\xE9\x8E\x95", + "\xF1\x60" => "\xE9\x8E\x88", + "\xF1\x61" => "\xE9\x8E\x99", + "\xF1\x62" => "\xE9\x8E\x9F", + "\xF1\x63" => "\xE9\x8E\x8D", + "\xF1\x64" => "\xE9\x8E\xB1", + "\xF1\x65" => "\xE9\x8E\x91", + "\xF1\x66" => "\xE9\x8E\xB2", + "\xF1\x67" => "\xE9\x8E\xA4", + "\xF1\x68" => "\xE9\x8E\xA8", + "\xF1\x69" => "\xE9\x8E\xB4", + "\xF1\x6A" => "\xE9\x8E\xA3", + "\xF1\x6B" => "\xE9\x8E\xA5", + "\xF1\x6C" => "\xE9\x97\x92", + "\xF1\x6D" => "\xE9\x97\x93", + "\xF1\x6E" => "\xE9\x97\x91", + "\xF1\x6F" => "\xE9\x9A\xB3", + "\xF1\x70" => "\xE9\x9B\x97", + "\xF1\x71" => "\xE9\x9B\x9A", + "\xF1\x72" => "\xE5\xB7\x82", + "\xF1\x73" => "\xE9\x9B\x9F", + "\xF1\x74" => "\xE9\x9B\x98", + "\xF1\x75" => "\xE9\x9B\x9D", + "\xF1\x76" => "\xE9\x9C\xA3", + "\xF1\x77" => "\xE9\x9C\xA2", + "\xF1\x78" => "\xE9\x9C\xA5", + "\xF1\x79" => "\xE9\x9E\xAC", + "\xF1\x7A" => "\xE9\x9E\xAE", + "\xF1\x7B" => "\xE9\x9E\xA8", + "\xF1\x7C" => "\xE9\x9E\xAB", + "\xF1\x7D" => "\xE9\x9E\xA4", + "\xF1\x7E" => "\xE9\x9E\xAA", + "\xF1\xA1" => "\xE9\x9E\xA2", + "\xF1\xA2" => "\xE9\x9E\xA5", + "\xF1\xA3" => "\xE9\x9F\x97", + "\xF1\xA4" => "\xE9\x9F\x99", + "\xF1\xA5" => "\xE9\x9F\x96", + "\xF1\xA6" => "\xE9\x9F\x98", + "\xF1\xA7" => "\xE9\x9F\xBA", + "\xF1\xA8" => "\xE9\xA1\x90", + "\xF1\xA9" => "\xE9\xA1\x91", + "\xF1\xAA" => "\xE9\xA1\x92", + "\xF1\xAB" => "\xE9\xA2\xB8", + "\xF1\xAC" => "\xE9\xA5\x81", + "\xF1\xAD" => "\xE9\xA4\xBC", + "\xF1\xAE" => "\xE9\xA4\xBA", + "\xF1\xAF" => "\xE9\xA8\x8F", + "\xF1\xB0" => "\xE9\xA8\x8B", + "\xF1\xB1" => "\xE9\xA8\x89", + "\xF1\xB2" => "\xE9\xA8\x8D", + "\xF1\xB3" => "\xE9\xA8\x84", + "\xF1\xB4" => "\xE9\xA8\x91", + "\xF1\xB5" => "\xE9\xA8\x8A", + "\xF1\xB6" => "\xE9\xA8\x85", + "\xF1\xB7" => "\xE9\xA8\x87", + "\xF1\xB8" => "\xE9\xA8\x86", + "\xF1\xB9" => "\xE9\xAB\x80", + "\xF1\xBA" => "\xE9\xAB\x9C", + "\xF1\xBB" => "\xE9\xAC\x88", + "\xF1\xBC" => "\xE9\xAC\x84", + "\xF1\xBD" => "\xE9\xAC\x85", + "\xF1\xBE" => "\xE9\xAC\xA9", + "\xF1\xBF" => "\xE9\xAC\xB5", + "\xF1\xC0" => "\xE9\xAD\x8A", + "\xF1\xC1" => "\xE9\xAD\x8C", + "\xF1\xC2" => "\xE9\xAD\x8B", + "\xF1\xC3" => "\xE9\xAF\x87", + "\xF1\xC4" => "\xE9\xAF\x86", + "\xF1\xC5" => "\xE9\xAF\x83", + "\xF1\xC6" => "\xE9\xAE\xBF", + "\xF1\xC7" => "\xE9\xAF\x81", + "\xF1\xC8" => "\xE9\xAE\xB5", + "\xF1\xC9" => "\xE9\xAE\xB8", + "\xF1\xCA" => "\xE9\xAF\x93", + "\xF1\xCB" => "\xE9\xAE\xB6", + "\xF1\xCC" => "\xE9\xAF\x84", + "\xF1\xCD" => "\xE9\xAE\xB9", + "\xF1\xCE" => "\xE9\xAE\xBD", + "\xF1\xCF" => "\xE9\xB5\x9C", + "\xF1\xD0" => "\xE9\xB5\x93", + "\xF1\xD1" => "\xE9\xB5\x8F", + "\xF1\xD2" => "\xE9\xB5\x8A", + "\xF1\xD3" => "\xE9\xB5\x9B", + "\xF1\xD4" => "\xE9\xB5\x8B", + "\xF1\xD5" => "\xE9\xB5\x99", + "\xF1\xD6" => "\xE9\xB5\x96", + "\xF1\xD7" => "\xE9\xB5\x8C", + "\xF1\xD8" => "\xE9\xB5\x97", + "\xF1\xD9" => "\xE9\xB5\x92", + "\xF1\xDA" => "\xE9\xB5\x94", + "\xF1\xDB" => "\xE9\xB5\x9F", + "\xF1\xDC" => "\xE9\xB5\x98", + "\xF1\xDD" => "\xE9\xB5\x9A", + "\xF1\xDE" => "\xE9\xBA\x8E", + "\xF1\xDF" => "\xE9\xBA\x8C", + "\xF1\xE0" => "\xE9\xBB\x9F", + "\xF1\xE1" => "\xE9\xBC\x81", + "\xF1\xE2" => "\xE9\xBC\x80", + "\xF1\xE3" => "\xE9\xBC\x96", + "\xF1\xE4" => "\xE9\xBC\xA5", + "\xF1\xE5" => "\xE9\xBC\xAB", + "\xF1\xE6" => "\xE9\xBC\xAA", + "\xF1\xE7" => "\xE9\xBC\xA9", + "\xF1\xE8" => "\xE9\xBC\xA8", + "\xF1\xE9" => "\xE9\xBD\x8C", + "\xF1\xEA" => "\xE9\xBD\x95", + "\xF1\xEB" => "\xE5\x84\xB4", + "\xF1\xEC" => "\xE5\x84\xB5", + "\xF1\xED" => "\xE5\x8A\x96", + "\xF1\xEE" => "\xE5\x8B\xB7", + "\xF1\xEF" => "\xE5\x8E\xB4", + "\xF1\xF0" => "\xE5\x9A\xAB", + "\xF1\xF1" => "\xE5\x9A\xAD", + "\xF1\xF2" => "\xE5\x9A\xA6", + "\xF1\xF3" => "\xE5\x9A\xA7", + "\xF1\xF4" => "\xE5\x9A\xAA", + "\xF1\xF5" => "\xE5\x9A\xAC", + "\xF1\xF6" => "\xE5\xA3\x9A", + "\xF1\xF7" => "\xE5\xA3\x9D", + "\xF1\xF8" => "\xE5\xA3\x9B", + "\xF1\xF9" => "\xE5\xA4\x92", + "\xF1\xFA" => "\xE5\xAC\xBD", + "\xF1\xFB" => "\xE5\xAC\xBE", + "\xF1\xFC" => "\xE5\xAC\xBF", + "\xF1\xFD" => "\xE5\xB7\x83", + "\xF1\xFE" => "\xE5\xB9\xB0", + "\xF2\x40" => "\xE5\xBE\xBF", + "\xF2\x41" => "\xE6\x87\xBB", + "\xF2\x42" => "\xE6\x94\x87", + "\xF2\x43" => "\xE6\x94\x90", + "\xF2\x44" => "\xE6\x94\x8D", + "\xF2\x45" => "\xE6\x94\x89", + "\xF2\x46" => "\xE6\x94\x8C", + "\xF2\x47" => "\xE6\x94\x8E", + "\xF2\x48" => "\xE6\x96\x84", + "\xF2\x49" => "\xE6\x97\x9E", + "\xF2\x4A" => "\xE6\x97\x9D", + "\xF2\x4B" => "\xE6\x9B\x9E", + "\xF2\x4C" => "\xE6\xAB\xA7", + "\xF2\x4D" => "\xE6\xAB\xA0", + "\xF2\x4E" => "\xE6\xAB\x8C", + "\xF2\x4F" => "\xE6\xAB\x91", + "\xF2\x50" => "\xE6\xAB\x99", + "\xF2\x51" => "\xE6\xAB\x8B", + "\xF2\x52" => "\xE6\xAB\x9F", + "\xF2\x53" => "\xE6\xAB\x9C", + "\xF2\x54" => "\xE6\xAB\x90", + "\xF2\x55" => "\xE6\xAB\xAB", + "\xF2\x56" => "\xE6\xAB\x8F", + "\xF2\x57" => "\xE6\xAB\x8D", + "\xF2\x58" => "\xE6\xAB\x9E", + "\xF2\x59" => "\xE6\xAD\xA0", + "\xF2\x5A" => "\xE6\xAE\xB0", + "\xF2\x5B" => "\xE6\xB0\x8C", + "\xF2\x5C" => "\xE7\x80\x99", + "\xF2\x5D" => "\xE7\x80\xA7", + "\xF2\x5E" => "\xE7\x80\xA0", + "\xF2\x5F" => "\xE7\x80\x96", + "\xF2\x60" => "\xE7\x80\xAB", + "\xF2\x61" => "\xE7\x80\xA1", + "\xF2\x62" => "\xE7\x80\xA2", + "\xF2\x63" => "\xE7\x80\xA3", + "\xF2\x64" => "\xE7\x80\xA9", + "\xF2\x65" => "\xE7\x80\x97", + "\xF2\x66" => "\xE7\x80\xA4", + "\xF2\x67" => "\xE7\x80\x9C", + "\xF2\x68" => "\xE7\x80\xAA", + "\xF2\x69" => "\xE7\x88\x8C", + "\xF2\x6A" => "\xE7\x88\x8A", + "\xF2\x6B" => "\xE7\x88\x87", + "\xF2\x6C" => "\xE7\x88\x82", + "\xF2\x6D" => "\xE7\x88\x85", + "\xF2\x6E" => "\xE7\x8A\xA5", + "\xF2\x6F" => "\xE7\x8A\xA6", + "\xF2\x70" => "\xE7\x8A\xA4", + "\xF2\x71" => "\xE7\x8A\xA3", + "\xF2\x72" => "\xE7\x8A\xA1", + "\xF2\x73" => "\xE7\x93\x8B", + "\xF2\x74" => "\xE7\x93\x85", + "\xF2\x75" => "\xE7\x92\xB7", + "\xF2\x76" => "\xE7\x93\x83", + "\xF2\x77" => "\xE7\x94\x96", + "\xF2\x78" => "\xE7\x99\xA0", + "\xF2\x79" => "\xE7\x9F\x89", + "\xF2\x7A" => "\xE7\x9F\x8A", + "\xF2\x7B" => "\xE7\x9F\x84", + "\xF2\x7C" => "\xE7\x9F\xB1", + "\xF2\x7D" => "\xE7\xA4\x9D", + "\xF2\x7E" => "\xE7\xA4\x9B", + "\xF2\xA1" => "\xE7\xA4\xA1", + "\xF2\xA2" => "\xE7\xA4\x9C", + "\xF2\xA3" => "\xE7\xA4\x97", + "\xF2\xA4" => "\xE7\xA4\x9E", + "\xF2\xA5" => "\xE7\xA6\xB0", + "\xF2\xA6" => "\xE7\xA9\xA7", + "\xF2\xA7" => "\xE7\xA9\xA8", + "\xF2\xA8" => "\xE7\xB0\xB3", + "\xF2\xA9" => "\xE7\xB0\xBC", + "\xF2\xAA" => "\xE7\xB0\xB9", + "\xF2\xAB" => "\xE7\xB0\xAC", + "\xF2\xAC" => "\xE7\xB0\xBB", + "\xF2\xAD" => "\xE7\xB3\xAC", + "\xF2\xAE" => "\xE7\xB3\xAA", + "\xF2\xAF" => "\xE7\xB9\xB6", + "\xF2\xB0" => "\xE7\xB9\xB5", + "\xF2\xB1" => "\xE7\xB9\xB8", + "\xF2\xB2" => "\xE7\xB9\xB0", + "\xF2\xB3" => "\xE7\xB9\xB7", + "\xF2\xB4" => "\xE7\xB9\xAF", + "\xF2\xB5" => "\xE7\xB9\xBA", + "\xF2\xB6" => "\xE7\xB9\xB2", + "\xF2\xB7" => "\xE7\xB9\xB4", + "\xF2\xB8" => "\xE7\xB9\xA8", + "\xF2\xB9" => "\xE7\xBD\x8B", + "\xF2\xBA" => "\xE7\xBD\x8A", + "\xF2\xBB" => "\xE7\xBE\x83", + "\xF2\xBC" => "\xE7\xBE\x86", + "\xF2\xBD" => "\xE7\xBE\xB7", + "\xF2\xBE" => "\xE7\xBF\xBD", + "\xF2\xBF" => "\xE7\xBF\xBE", + "\xF2\xC0" => "\xE8\x81\xB8", + "\xF2\xC1" => "\xE8\x87\x97", + "\xF2\xC2" => "\xE8\x87\x95", + "\xF2\xC3" => "\xE8\x89\xA4", + "\xF2\xC4" => "\xE8\x89\xA1", + "\xF2\xC5" => "\xE8\x89\xA3", + "\xF2\xC6" => "\xE8\x97\xAB", + "\xF2\xC7" => "\xE8\x97\xB1", + "\xF2\xC8" => "\xE8\x97\xAD", + "\xF2\xC9" => "\xE8\x97\x99", + "\xF2\xCA" => "\xE8\x97\xA1", + "\xF2\xCB" => "\xE8\x97\xA8", + "\xF2\xCC" => "\xE8\x97\x9A", + "\xF2\xCD" => "\xE8\x97\x97", + "\xF2\xCE" => "\xE8\x97\xAC", + "\xF2\xCF" => "\xE8\x97\xB2", + "\xF2\xD0" => "\xE8\x97\xB8", + "\xF2\xD1" => "\xE8\x97\x98", + "\xF2\xD2" => "\xE8\x97\x9F", + "\xF2\xD3" => "\xE8\x97\xA3", + "\xF2\xD4" => "\xE8\x97\x9C", + "\xF2\xD5" => "\xE8\x97\x91", + "\xF2\xD6" => "\xE8\x97\xB0", + "\xF2\xD7" => "\xE8\x97\xA6", + "\xF2\xD8" => "\xE8\x97\xAF", + "\xF2\xD9" => "\xE8\x97\x9E", + "\xF2\xDA" => "\xE8\x97\xA2", + "\xF2\xDB" => "\xE8\xA0\x80", + "\xF2\xDC" => "\xE8\x9F\xBA", + "\xF2\xDD" => "\xE8\xA0\x83", + "\xF2\xDE" => "\xE8\x9F\xB6", + "\xF2\xDF" => "\xE8\x9F\xB7", + "\xF2\xE0" => "\xE8\xA0\x89", + "\xF2\xE1" => "\xE8\xA0\x8C", + "\xF2\xE2" => "\xE8\xA0\x8B", + "\xF2\xE3" => "\xE8\xA0\x86", + "\xF2\xE4" => "\xE8\x9F\xBC", + "\xF2\xE5" => "\xE8\xA0\x88", + "\xF2\xE6" => "\xE8\x9F\xBF", + "\xF2\xE7" => "\xE8\xA0\x8A", + "\xF2\xE8" => "\xE8\xA0\x82", + "\xF2\xE9" => "\xE8\xA5\xA2", + "\xF2\xEA" => "\xE8\xA5\x9A", + "\xF2\xEB" => "\xE8\xA5\x9B", + "\xF2\xEC" => "\xE8\xA5\x97", + "\xF2\xED" => "\xE8\xA5\xA1", + "\xF2\xEE" => "\xE8\xA5\x9C", + "\xF2\xEF" => "\xE8\xA5\x98", + "\xF2\xF0" => "\xE8\xA5\x9D", + "\xF2\xF1" => "\xE8\xA5\x99", + "\xF2\xF2" => "\xE8\xA6\x88", + "\xF2\xF3" => "\xE8\xA6\xB7", + "\xF2\xF4" => "\xE8\xA6\xB6", + "\xF2\xF5" => "\xE8\xA7\xB6", + "\xF2\xF6" => "\xE8\xAD\x90", + "\xF2\xF7" => "\xE8\xAD\x88", + "\xF2\xF8" => "\xE8\xAD\x8A", + "\xF2\xF9" => "\xE8\xAD\x80", + "\xF2\xFA" => "\xE8\xAD\x93", + "\xF2\xFB" => "\xE8\xAD\x96", + "\xF2\xFC" => "\xE8\xAD\x94", + "\xF2\xFD" => "\xE8\xAD\x8B", + "\xF2\xFE" => "\xE8\xAD\x95", + "\xF3\x40" => "\xE8\xAD\x91", + "\xF3\x41" => "\xE8\xAD\x82", + "\xF3\x42" => "\xE8\xAD\x92", + "\xF3\x43" => "\xE8\xAD\x97", + "\xF3\x44" => "\xE8\xB1\x83", + "\xF3\x45" => "\xE8\xB1\xB7", + "\xF3\x46" => "\xE8\xB1\xB6", + "\xF3\x47" => "\xE8\xB2\x9A", + "\xF3\x48" => "\xE8\xB4\x86", + "\xF3\x49" => "\xE8\xB4\x87", + "\xF3\x4A" => "\xE8\xB4\x89", + "\xF3\x4B" => "\xE8\xB6\xAC", + "\xF3\x4C" => "\xE8\xB6\xAA", + "\xF3\x4D" => "\xE8\xB6\xAD", + "\xF3\x4E" => "\xE8\xB6\xAB", + "\xF3\x4F" => "\xE8\xB9\xAD", + "\xF3\x50" => "\xE8\xB9\xB8", + "\xF3\x51" => "\xE8\xB9\xB3", + "\xF3\x52" => "\xE8\xB9\xAA", + "\xF3\x53" => "\xE8\xB9\xAF", + "\xF3\x54" => "\xE8\xB9\xBB", + "\xF3\x55" => "\xE8\xBB\x82", + "\xF3\x56" => "\xE8\xBD\x92", + "\xF3\x57" => "\xE8\xBD\x91", + "\xF3\x58" => "\xE8\xBD\x8F", + "\xF3\x59" => "\xE8\xBD\x90", + "\xF3\x5A" => "\xE8\xBD\x93", + "\xF3\x5B" => "\xE8\xBE\xB4", + "\xF3\x5C" => "\xE9\x85\x80", + "\xF3\x5D" => "\xE9\x84\xBF", + "\xF3\x5E" => "\xE9\x86\xB0", + "\xF3\x5F" => "\xE9\x86\xAD", + "\xF3\x60" => "\xE9\x8F\x9E", + "\xF3\x61" => "\xE9\x8F\x87", + "\xF3\x62" => "\xE9\x8F\x8F", + "\xF3\x63" => "\xE9\x8F\x82", + "\xF3\x64" => "\xE9\x8F\x9A", + "\xF3\x65" => "\xE9\x8F\x90", + "\xF3\x66" => "\xE9\x8F\xB9", + "\xF3\x67" => "\xE9\x8F\xAC", + "\xF3\x68" => "\xE9\x8F\x8C", + "\xF3\x69" => "\xE9\x8F\x99", + "\xF3\x6A" => "\xE9\x8E\xA9", + "\xF3\x6B" => "\xE9\x8F\xA6", + "\xF3\x6C" => "\xE9\x8F\x8A", + "\xF3\x6D" => "\xE9\x8F\x94", + "\xF3\x6E" => "\xE9\x8F\xAE", + "\xF3\x6F" => "\xE9\x8F\xA3", + "\xF3\x70" => "\xE9\x8F\x95", + "\xF3\x71" => "\xE9\x8F\x84", + "\xF3\x72" => "\xE9\x8F\x8E", + "\xF3\x73" => "\xE9\x8F\x80", + "\xF3\x74" => "\xE9\x8F\x92", + "\xF3\x75" => "\xE9\x8F\xA7", + "\xF3\x76" => "\xE9\x95\xBD", + "\xF3\x77" => "\xE9\x97\x9A", + "\xF3\x78" => "\xE9\x97\x9B", + "\xF3\x79" => "\xE9\x9B\xA1", + "\xF3\x7A" => "\xE9\x9C\xA9", + "\xF3\x7B" => "\xE9\x9C\xAB", + "\xF3\x7C" => "\xE9\x9C\xAC", + "\xF3\x7D" => "\xE9\x9C\xA8", + "\xF3\x7E" => "\xE9\x9C\xA6", + "\xF3\xA1" => "\xE9\x9E\xB3", + "\xF3\xA2" => "\xE9\x9E\xB7", + "\xF3\xA3" => "\xE9\x9E\xB6", + "\xF3\xA4" => "\xE9\x9F\x9D", + "\xF3\xA5" => "\xE9\x9F\x9E", + "\xF3\xA6" => "\xE9\x9F\x9F", + "\xF3\xA7" => "\xE9\xA1\x9C", + "\xF3\xA8" => "\xE9\xA1\x99", + "\xF3\xA9" => "\xE9\xA1\x9D", + "\xF3\xAA" => "\xE9\xA1\x97", + "\xF3\xAB" => "\xE9\xA2\xBF", + "\xF3\xAC" => "\xE9\xA2\xBD", + "\xF3\xAD" => "\xE9\xA2\xBB", + "\xF3\xAE" => "\xE9\xA2\xBE", + "\xF3\xAF" => "\xE9\xA5\x88", + "\xF3\xB0" => "\xE9\xA5\x87", + "\xF3\xB1" => "\xE9\xA5\x83", + "\xF3\xB2" => "\xE9\xA6\xA6", + "\xF3\xB3" => "\xE9\xA6\xA7", + "\xF3\xB4" => "\xE9\xA8\x9A", + "\xF3\xB5" => "\xE9\xA8\x95", + "\xF3\xB6" => "\xE9\xA8\xA5", + "\xF3\xB7" => "\xE9\xA8\x9D", + "\xF3\xB8" => "\xE9\xA8\xA4", + "\xF3\xB9" => "\xE9\xA8\x9B", + "\xF3\xBA" => "\xE9\xA8\xA2", + "\xF3\xBB" => "\xE9\xA8\xA0", + "\xF3\xBC" => "\xE9\xA8\xA7", + "\xF3\xBD" => "\xE9\xA8\xA3", + "\xF3\xBE" => "\xE9\xA8\x9E", + "\xF3\xBF" => "\xE9\xA8\x9C", + "\xF3\xC0" => "\xE9\xA8\x94", + "\xF3\xC1" => "\xE9\xAB\x82", + "\xF3\xC2" => "\xE9\xAC\x8B", + "\xF3\xC3" => "\xE9\xAC\x8A", + "\xF3\xC4" => "\xE9\xAC\x8E", + "\xF3\xC5" => "\xE9\xAC\x8C", + "\xF3\xC6" => "\xE9\xAC\xB7", + "\xF3\xC7" => "\xE9\xAF\xAA", + "\xF3\xC8" => "\xE9\xAF\xAB", + "\xF3\xC9" => "\xE9\xAF\xA0", + "\xF3\xCA" => "\xE9\xAF\x9E", + "\xF3\xCB" => "\xE9\xAF\xA4", + "\xF3\xCC" => "\xE9\xAF\xA6", + "\xF3\xCD" => "\xE9\xAF\xA2", + "\xF3\xCE" => "\xE9\xAF\xB0", + "\xF3\xCF" => "\xE9\xAF\x94", + "\xF3\xD0" => "\xE9\xAF\x97", + "\xF3\xD1" => "\xE9\xAF\xAC", + "\xF3\xD2" => "\xE9\xAF\x9C", + "\xF3\xD3" => "\xE9\xAF\x99", + "\xF3\xD4" => "\xE9\xAF\xA5", + "\xF3\xD5" => "\xE9\xAF\x95", + "\xF3\xD6" => "\xE9\xAF\xA1", + "\xF3\xD7" => "\xE9\xAF\x9A", + "\xF3\xD8" => "\xE9\xB5\xB7", + "\xF3\xD9" => "\xE9\xB6\x81", + "\xF3\xDA" => "\xE9\xB6\x8A", + "\xF3\xDB" => "\xE9\xB6\x84", + "\xF3\xDC" => "\xE9\xB6\x88", + "\xF3\xDD" => "\xE9\xB5\xB1", + "\xF3\xDE" => "\xE9\xB6\x80", + "\xF3\xDF" => "\xE9\xB5\xB8", + "\xF3\xE0" => "\xE9\xB6\x86", + "\xF3\xE1" => "\xE9\xB6\x8B", + "\xF3\xE2" => "\xE9\xB6\x8C", + "\xF3\xE3" => "\xE9\xB5\xBD", + "\xF3\xE4" => "\xE9\xB5\xAB", + "\xF3\xE5" => "\xE9\xB5\xB4", + "\xF3\xE6" => "\xE9\xB5\xB5", + "\xF3\xE7" => "\xE9\xB5\xB0", + "\xF3\xE8" => "\xE9\xB5\xA9", + "\xF3\xE9" => "\xE9\xB6\x85", + "\xF3\xEA" => "\xE9\xB5\xB3", + "\xF3\xEB" => "\xE9\xB5\xBB", + "\xF3\xEC" => "\xE9\xB6\x82", + "\xF3\xED" => "\xE9\xB5\xAF", + "\xF3\xEE" => "\xE9\xB5\xB9", + "\xF3\xEF" => "\xE9\xB5\xBF", + "\xF3\xF0" => "\xE9\xB6\x87", + "\xF3\xF1" => "\xE9\xB5\xA8", + "\xF3\xF2" => "\xE9\xBA\x94", + "\xF3\xF3" => "\xE9\xBA\x91", + "\xF3\xF4" => "\xE9\xBB\x80", + "\xF3\xF5" => "\xE9\xBB\xBC", + "\xF3\xF6" => "\xE9\xBC\xAD", + "\xF3\xF7" => "\xE9\xBD\x80", + "\xF3\xF8" => "\xE9\xBD\x81", + "\xF3\xF9" => "\xE9\xBD\x8D", + "\xF3\xFA" => "\xE9\xBD\x96", + "\xF3\xFB" => "\xE9\xBD\x97", + "\xF3\xFC" => "\xE9\xBD\x98", + "\xF3\xFD" => "\xE5\x8C\xB7", + "\xF3\xFE" => "\xE5\x9A\xB2", + "\xF4\x40" => "\xE5\x9A\xB5", + "\xF4\x41" => "\xE5\x9A\xB3", + "\xF4\x42" => "\xE5\xA3\xA3", + "\xF4\x43" => "\xE5\xAD\x85", + "\xF4\x44" => "\xE5\xB7\x86", + "\xF4\x45" => "\xE5\xB7\x87", + "\xF4\x46" => "\xE5\xBB\xAE", + "\xF4\x47" => "\xE5\xBB\xAF", + "\xF4\x48" => "\xE5\xBF\x80", + "\xF4\x49" => "\xE5\xBF\x81", + "\xF4\x4A" => "\xE6\x87\xB9", + "\xF4\x4B" => "\xE6\x94\x97", + "\xF4\x4C" => "\xE6\x94\x96", + "\xF4\x4D" => "\xE6\x94\x95", + "\xF4\x4E" => "\xE6\x94\x93", + "\xF4\x4F" => "\xE6\x97\x9F", + "\xF4\x50" => "\xE6\x9B\xA8", + "\xF4\x51" => "\xE6\x9B\xA3", + "\xF4\x52" => "\xE6\x9B\xA4", + "\xF4\x53" => "\xE6\xAB\xB3", + "\xF4\x54" => "\xE6\xAB\xB0", + "\xF4\x55" => "\xE6\xAB\xAA", + "\xF4\x56" => "\xE6\xAB\xA8", + "\xF4\x57" => "\xE6\xAB\xB9", + "\xF4\x58" => "\xE6\xAB\xB1", + "\xF4\x59" => "\xE6\xAB\xAE", + "\xF4\x5A" => "\xE6\xAB\xAF", + "\xF4\x5B" => "\xE7\x80\xBC", + "\xF4\x5C" => "\xE7\x80\xB5", + "\xF4\x5D" => "\xE7\x80\xAF", + "\xF4\x5E" => "\xE7\x80\xB7", + "\xF4\x5F" => "\xE7\x80\xB4", + "\xF4\x60" => "\xE7\x80\xB1", + "\xF4\x61" => "\xE7\x81\x82", + "\xF4\x62" => "\xE7\x80\xB8", + "\xF4\x63" => "\xE7\x80\xBF", + "\xF4\x64" => "\xE7\x80\xBA", + "\xF4\x65" => "\xE7\x80\xB9", + "\xF4\x66" => "\xE7\x81\x80", + "\xF4\x67" => "\xE7\x80\xBB", + "\xF4\x68" => "\xE7\x80\xB3", + "\xF4\x69" => "\xE7\x81\x81", + "\xF4\x6A" => "\xE7\x88\x93", + "\xF4\x6B" => "\xE7\x88\x94", + "\xF4\x6C" => "\xE7\x8A\xA8", + "\xF4\x6D" => "\xE7\x8D\xBD", + "\xF4\x6E" => "\xE7\x8D\xBC", + "\xF4\x6F" => "\xE7\x92\xBA", + "\xF4\x70" => "\xE7\x9A\xAB", + "\xF4\x71" => "\xE7\x9A\xAA", + "\xF4\x72" => "\xE7\x9A\xBE", + "\xF4\x73" => "\xE7\x9B\xAD", + "\xF4\x74" => "\xE7\x9F\x8C", + "\xF4\x75" => "\xE7\x9F\x8E", + "\xF4\x76" => "\xE7\x9F\x8F", + "\xF4\x77" => "\xE7\x9F\x8D", + "\xF4\x78" => "\xE7\x9F\xB2", + "\xF4\x79" => "\xE7\xA4\xA5", + "\xF4\x7A" => "\xE7\xA4\xA3", + "\xF4\x7B" => "\xE7\xA4\xA7", + "\xF4\x7C" => "\xE7\xA4\xA8", + "\xF4\x7D" => "\xE7\xA4\xA4", + "\xF4\x7E" => "\xE7\xA4\xA9", + "\xF4\xA1" => "\xE7\xA6\xB2", + "\xF4\xA2" => "\xE7\xA9\xAE", + "\xF4\xA3" => "\xE7\xA9\xAC", + "\xF4\xA4" => "\xE7\xA9\xAD", + "\xF4\xA5" => "\xE7\xAB\xB7", + "\xF4\xA6" => "\xE7\xB1\x89", + "\xF4\xA7" => "\xE7\xB1\x88", + "\xF4\xA8" => "\xE7\xB1\x8A", + "\xF4\xA9" => "\xE7\xB1\x87", + "\xF4\xAA" => "\xE7\xB1\x85", + "\xF4\xAB" => "\xE7\xB3\xAE", + "\xF4\xAC" => "\xE7\xB9\xBB", + "\xF4\xAD" => "\xE7\xB9\xBE", + "\xF4\xAE" => "\xE7\xBA\x81", + "\xF4\xAF" => "\xE7\xBA\x80", + "\xF4\xB0" => "\xE7\xBE\xBA", + "\xF4\xB1" => "\xE7\xBF\xBF", + "\xF4\xB2" => "\xE8\x81\xB9", + "\xF4\xB3" => "\xE8\x87\x9B", + "\xF4\xB4" => "\xE8\x87\x99", + "\xF4\xB5" => "\xE8\x88\x8B", + "\xF4\xB6" => "\xE8\x89\xA8", + "\xF4\xB7" => "\xE8\x89\xA9", + "\xF4\xB8" => "\xE8\x98\xA2", + "\xF4\xB9" => "\xE8\x97\xBF", + "\xF4\xBA" => "\xE8\x98\x81", + "\xF4\xBB" => "\xE8\x97\xBE", + "\xF4\xBC" => "\xE8\x98\x9B", + "\xF4\xBD" => "\xE8\x98\x80", + "\xF4\xBE" => "\xE8\x97\xB6", + "\xF4\xBF" => "\xE8\x98\x84", + "\xF4\xC0" => "\xE8\x98\x89", + "\xF4\xC1" => "\xE8\x98\x85", + "\xF4\xC2" => "\xE8\x98\x8C", + "\xF4\xC3" => "\xE8\x97\xBD", + "\xF4\xC4" => "\xE8\xA0\x99", + "\xF4\xC5" => "\xE8\xA0\x90", + "\xF4\xC6" => "\xE8\xA0\x91", + "\xF4\xC7" => "\xE8\xA0\x97", + "\xF4\xC8" => "\xE8\xA0\x93", + "\xF4\xC9" => "\xE8\xA0\x96", + "\xF4\xCA" => "\xE8\xA5\xA3", + "\xF4\xCB" => "\xE8\xA5\xA6", + "\xF4\xCC" => "\xE8\xA6\xB9", + "\xF4\xCD" => "\xE8\xA7\xB7", + "\xF4\xCE" => "\xE8\xAD\xA0", + "\xF4\xCF" => "\xE8\xAD\xAA", + "\xF4\xD0" => "\xE8\xAD\x9D", + "\xF4\xD1" => "\xE8\xAD\xA8", + "\xF4\xD2" => "\xE8\xAD\xA3", + "\xF4\xD3" => "\xE8\xAD\xA5", + "\xF4\xD4" => "\xE8\xAD\xA7", + "\xF4\xD5" => "\xE8\xAD\xAD", + "\xF4\xD6" => "\xE8\xB6\xAE", + "\xF4\xD7" => "\xE8\xBA\x86", + "\xF4\xD8" => "\xE8\xBA\x88", + "\xF4\xD9" => "\xE8\xBA\x84", + "\xF4\xDA" => "\xE8\xBD\x99", + "\xF4\xDB" => "\xE8\xBD\x96", + "\xF4\xDC" => "\xE8\xBD\x97", + "\xF4\xDD" => "\xE8\xBD\x95", + "\xF4\xDE" => "\xE8\xBD\x98", + "\xF4\xDF" => "\xE8\xBD\x9A", + "\xF4\xE0" => "\xE9\x82\x8D", + "\xF4\xE1" => "\xE9\x85\x83", + "\xF4\xE2" => "\xE9\x85\x81", + "\xF4\xE3" => "\xE9\x86\xB7", + "\xF4\xE4" => "\xE9\x86\xB5", + "\xF4\xE5" => "\xE9\x86\xB2", + "\xF4\xE6" => "\xE9\x86\xB3", + "\xF4\xE7" => "\xE9\x90\x8B", + "\xF4\xE8" => "\xE9\x90\x93", + "\xF4\xE9" => "\xE9\x8F\xBB", + "\xF4\xEA" => "\xE9\x90\xA0", + "\xF4\xEB" => "\xE9\x90\x8F", + "\xF4\xEC" => "\xE9\x90\x94", + "\xF4\xED" => "\xE9\x8F\xBE", + "\xF4\xEE" => "\xE9\x90\x95", + "\xF4\xEF" => "\xE9\x90\x90", + "\xF4\xF0" => "\xE9\x90\xA8", + "\xF4\xF1" => "\xE9\x90\x99", + "\xF4\xF2" => "\xE9\x90\x8D", + "\xF4\xF3" => "\xE9\x8F\xB5", + "\xF4\xF4" => "\xE9\x90\x80", + "\xF4\xF5" => "\xE9\x8F\xB7", + "\xF4\xF6" => "\xE9\x90\x87", + "\xF4\xF7" => "\xE9\x90\x8E", + "\xF4\xF8" => "\xE9\x90\x96", + "\xF4\xF9" => "\xE9\x90\x92", + "\xF4\xFA" => "\xE9\x8F\xBA", + "\xF4\xFB" => "\xE9\x90\x89", + "\xF4\xFC" => "\xE9\x8F\xB8", + "\xF4\xFD" => "\xE9\x90\x8A", + "\xF4\xFE" => "\xE9\x8F\xBF", + "\xF5\x40" => "\xE9\x8F\xBC", + "\xF5\x41" => "\xE9\x90\x8C", + "\xF5\x42" => "\xE9\x8F\xB6", + "\xF5\x43" => "\xE9\x90\x91", + "\xF5\x44" => "\xE9\x90\x86", + "\xF5\x45" => "\xE9\x97\x9E", + "\xF5\x46" => "\xE9\x97\xA0", + "\xF5\x47" => "\xE9\x97\x9F", + "\xF5\x48" => "\xE9\x9C\xAE", + "\xF5\x49" => "\xE9\x9C\xAF", + "\xF5\x4A" => "\xE9\x9E\xB9", + "\xF5\x4B" => "\xE9\x9E\xBB", + "\xF5\x4C" => "\xE9\x9F\xBD", + "\xF5\x4D" => "\xE9\x9F\xBE", + "\xF5\x4E" => "\xE9\xA1\xA0", + "\xF5\x4F" => "\xE9\xA1\xA2", + "\xF5\x50" => "\xE9\xA1\xA3", + "\xF5\x51" => "\xE9\xA1\x9F", + "\xF5\x52" => "\xE9\xA3\x81", + "\xF5\x53" => "\xE9\xA3\x82", + "\xF5\x54" => "\xE9\xA5\x90", + "\xF5\x55" => "\xE9\xA5\x8E", + "\xF5\x56" => "\xE9\xA5\x99", + "\xF5\x57" => "\xE9\xA5\x8C", + "\xF5\x58" => "\xE9\xA5\x8B", + "\xF5\x59" => "\xE9\xA5\x93", + "\xF5\x5A" => "\xE9\xA8\xB2", + "\xF5\x5B" => "\xE9\xA8\xB4", + "\xF5\x5C" => "\xE9\xA8\xB1", + "\xF5\x5D" => "\xE9\xA8\xAC", + "\xF5\x5E" => "\xE9\xA8\xAA", + "\xF5\x5F" => "\xE9\xA8\xB6", + "\xF5\x60" => "\xE9\xA8\xA9", + "\xF5\x61" => "\xE9\xA8\xAE", + "\xF5\x62" => "\xE9\xA8\xB8", + "\xF5\x63" => "\xE9\xA8\xAD", + "\xF5\x64" => "\xE9\xAB\x87", + "\xF5\x65" => "\xE9\xAB\x8A", + "\xF5\x66" => "\xE9\xAB\x86", + "\xF5\x67" => "\xE9\xAC\x90", + "\xF5\x68" => "\xE9\xAC\x92", + "\xF5\x69" => "\xE9\xAC\x91", + "\xF5\x6A" => "\xE9\xB0\x8B", + "\xF5\x6B" => "\xE9\xB0\x88", + "\xF5\x6C" => "\xE9\xAF\xB7", + "\xF5\x6D" => "\xE9\xB0\x85", + "\xF5\x6E" => "\xE9\xB0\x92", + "\xF5\x6F" => "\xE9\xAF\xB8", + "\xF5\x70" => "\xE9\xB1\x80", + "\xF5\x71" => "\xE9\xB0\x87", + "\xF5\x72" => "\xE9\xB0\x8E", + "\xF5\x73" => "\xE9\xB0\x86", + "\xF5\x74" => "\xE9\xB0\x97", + "\xF5\x75" => "\xE9\xB0\x94", + "\xF5\x76" => "\xE9\xB0\x89", + "\xF5\x77" => "\xE9\xB6\x9F", + "\xF5\x78" => "\xE9\xB6\x99", + "\xF5\x79" => "\xE9\xB6\xA4", + "\xF5\x7A" => "\xE9\xB6\x9D", + "\xF5\x7B" => "\xE9\xB6\x92", + "\xF5\x7C" => "\xE9\xB6\x98", + "\xF5\x7D" => "\xE9\xB6\x90", + "\xF5\x7E" => "\xE9\xB6\x9B", + "\xF5\xA1" => "\xE9\xB6\xA0", + "\xF5\xA2" => "\xE9\xB6\x94", + "\xF5\xA3" => "\xE9\xB6\x9C", + "\xF5\xA4" => "\xE9\xB6\xAA", + "\xF5\xA5" => "\xE9\xB6\x97", + "\xF5\xA6" => "\xE9\xB6\xA1", + "\xF5\xA7" => "\xE9\xB6\x9A", + "\xF5\xA8" => "\xE9\xB6\xA2", + "\xF5\xA9" => "\xE9\xB6\xA8", + "\xF5\xAA" => "\xE9\xB6\x9E", + "\xF5\xAB" => "\xE9\xB6\xA3", + "\xF5\xAC" => "\xE9\xB6\xBF", + "\xF5\xAD" => "\xE9\xB6\xA9", + "\xF5\xAE" => "\xE9\xB6\x96", + "\xF5\xAF" => "\xE9\xB6\xA6", + "\xF5\xB0" => "\xE9\xB6\xA7", + "\xF5\xB1" => "\xE9\xBA\x99", + "\xF5\xB2" => "\xE9\xBA\x9B", + "\xF5\xB3" => "\xE9\xBA\x9A", + "\xF5\xB4" => "\xE9\xBB\xA5", + "\xF5\xB5" => "\xE9\xBB\xA4", + "\xF5\xB6" => "\xE9\xBB\xA7", + "\xF5\xB7" => "\xE9\xBB\xA6", + "\xF5\xB8" => "\xE9\xBC\xB0", + "\xF5\xB9" => "\xE9\xBC\xAE", + "\xF5\xBA" => "\xE9\xBD\x9B", + "\xF5\xBB" => "\xE9\xBD\xA0", + "\xF5\xBC" => "\xE9\xBD\x9E", + "\xF5\xBD" => "\xE9\xBD\x9D", + "\xF5\xBE" => "\xE9\xBD\x99", + "\xF5\xBF" => "\xE9\xBE\x91", + "\xF5\xC0" => "\xE5\x84\xBA", + "\xF5\xC1" => "\xE5\x84\xB9", + "\xF5\xC2" => "\xE5\x8A\x98", + "\xF5\xC3" => "\xE5\x8A\x97", + "\xF5\xC4" => "\xE5\x9B\x83", + "\xF5\xC5" => "\xE5\x9A\xBD", + "\xF5\xC6" => "\xE5\x9A\xBE", + "\xF5\xC7" => "\xE5\xAD\x88", + "\xF5\xC8" => "\xE5\xAD\x87", + "\xF5\xC9" => "\xE5\xB7\x8B", + "\xF5\xCA" => "\xE5\xB7\x8F", + "\xF5\xCB" => "\xE5\xBB\xB1", + "\xF5\xCC" => "\xE6\x87\xBD", + "\xF5\xCD" => "\xE6\x94\x9B", + "\xF5\xCE" => "\xE6\xAC\x82", + "\xF5\xCF" => "\xE6\xAB\xBC", + "\xF5\xD0" => "\xE6\xAC\x83", + "\xF5\xD1" => "\xE6\xAB\xB8", + "\xF5\xD2" => "\xE6\xAC\x80", + "\xF5\xD3" => "\xE7\x81\x83", + "\xF5\xD4" => "\xE7\x81\x84", + "\xF5\xD5" => "\xE7\x81\x8A", + "\xF5\xD6" => "\xE7\x81\x88", + "\xF5\xD7" => "\xE7\x81\x89", + "\xF5\xD8" => "\xE7\x81\x85", + "\xF5\xD9" => "\xE7\x81\x86", + "\xF5\xDA" => "\xE7\x88\x9D", + "\xF5\xDB" => "\xE7\x88\x9A", + "\xF5\xDC" => "\xE7\x88\x99", + "\xF5\xDD" => "\xE7\x8D\xBE", + "\xF5\xDE" => "\xE7\x94\x97", + "\xF5\xDF" => "\xE7\x99\xAA", + "\xF5\xE0" => "\xE7\x9F\x90", + "\xF5\xE1" => "\xE7\xA4\xAD", + "\xF5\xE2" => "\xE7\xA4\xB1", + "\xF5\xE3" => "\xE7\xA4\xAF", + "\xF5\xE4" => "\xE7\xB1\x94", + "\xF5\xE5" => "\xE7\xB1\x93", + "\xF5\xE6" => "\xE7\xB3\xB2", + "\xF5\xE7" => "\xE7\xBA\x8A", + "\xF5\xE8" => "\xE7\xBA\x87", + "\xF5\xE9" => "\xE7\xBA\x88", + "\xF5\xEA" => "\xE7\xBA\x8B", + "\xF5\xEB" => "\xE7\xBA\x86", + "\xF5\xEC" => "\xE7\xBA\x8D", + "\xF5\xED" => "\xE7\xBD\x8D", + "\xF5\xEE" => "\xE7\xBE\xBB", + "\xF5\xEF" => "\xE8\x80\xB0", + "\xF5\xF0" => "\xE8\x87\x9D", + "\xF5\xF1" => "\xE8\x98\x98", + "\xF5\xF2" => "\xE8\x98\xAA", + "\xF5\xF3" => "\xE8\x98\xA6", + "\xF5\xF4" => "\xE8\x98\x9F", + "\xF5\xF5" => "\xE8\x98\xA3", + "\xF5\xF6" => "\xE8\x98\x9C", + "\xF5\xF7" => "\xE8\x98\x99", + "\xF5\xF8" => "\xE8\x98\xA7", + "\xF5\xF9" => "\xE8\x98\xAE", + "\xF5\xFA" => "\xE8\x98\xA1", + "\xF5\xFB" => "\xE8\x98\xA0", + "\xF5\xFC" => "\xE8\x98\xA9", + "\xF5\xFD" => "\xE8\x98\x9E", + "\xF5\xFE" => "\xE8\x98\xA5", + "\xF6\x40" => "\xE8\xA0\xA9", + "\xF6\x41" => "\xE8\xA0\x9D", + "\xF6\x42" => "\xE8\xA0\x9B", + "\xF6\x43" => "\xE8\xA0\xA0", + "\xF6\x44" => "\xE8\xA0\xA4", + "\xF6\x45" => "\xE8\xA0\x9C", + "\xF6\x46" => "\xE8\xA0\xAB", + "\xF6\x47" => "\xE8\xA1\x8A", + "\xF6\x48" => "\xE8\xA5\xAD", + "\xF6\x49" => "\xE8\xA5\xA9", + "\xF6\x4A" => "\xE8\xA5\xAE", + "\xF6\x4B" => "\xE8\xA5\xAB", + "\xF6\x4C" => "\xE8\xA7\xBA", + "\xF6\x4D" => "\xE8\xAD\xB9", + "\xF6\x4E" => "\xE8\xAD\xB8", + "\xF6\x4F" => "\xE8\xAD\x85", + "\xF6\x50" => "\xE8\xAD\xBA", + "\xF6\x51" => "\xE8\xAD\xBB", + "\xF6\x52" => "\xE8\xB4\x90", + "\xF6\x53" => "\xE8\xB4\x94", + "\xF6\x54" => "\xE8\xB6\xAF", + "\xF6\x55" => "\xE8\xBA\x8E", + "\xF6\x56" => "\xE8\xBA\x8C", + "\xF6\x57" => "\xE8\xBD\x9E", + "\xF6\x58" => "\xE8\xBD\x9B", + "\xF6\x59" => "\xE8\xBD\x9D", + "\xF6\x5A" => "\xE9\x85\x86", + "\xF6\x5B" => "\xE9\x85\x84", + "\xF6\x5C" => "\xE9\x85\x85", + "\xF6\x5D" => "\xE9\x86\xB9", + "\xF6\x5E" => "\xE9\x90\xBF", + "\xF6\x5F" => "\xE9\x90\xBB", + "\xF6\x60" => "\xE9\x90\xB6", + "\xF6\x61" => "\xE9\x90\xA9", + "\xF6\x62" => "\xE9\x90\xBD", + "\xF6\x63" => "\xE9\x90\xBC", + "\xF6\x64" => "\xE9\x90\xB0", + "\xF6\x65" => "\xE9\x90\xB9", + "\xF6\x66" => "\xE9\x90\xAA", + "\xF6\x67" => "\xE9\x90\xB7", + "\xF6\x68" => "\xE9\x90\xAC", + "\xF6\x69" => "\xE9\x91\x80", + "\xF6\x6A" => "\xE9\x90\xB1", + "\xF6\x6B" => "\xE9\x97\xA5", + "\xF6\x6C" => "\xE9\x97\xA4", + "\xF6\x6D" => "\xE9\x97\xA3", + "\xF6\x6E" => "\xE9\x9C\xB5", + "\xF6\x6F" => "\xE9\x9C\xBA", + "\xF6\x70" => "\xE9\x9E\xBF", + "\xF6\x71" => "\xE9\x9F\xA1", + "\xF6\x72" => "\xE9\xA1\xA4", + "\xF6\x73" => "\xE9\xA3\x89", + "\xF6\x74" => "\xE9\xA3\x86", + "\xF6\x75" => "\xE9\xA3\x80", + "\xF6\x76" => "\xE9\xA5\x98", + "\xF6\x77" => "\xE9\xA5\x96", + "\xF6\x78" => "\xE9\xA8\xB9", + "\xF6\x79" => "\xE9\xA8\xBD", + "\xF6\x7A" => "\xE9\xA9\x86", + "\xF6\x7B" => "\xE9\xA9\x84", + "\xF6\x7C" => "\xE9\xA9\x82", + "\xF6\x7D" => "\xE9\xA9\x81", + "\xF6\x7E" => "\xE9\xA8\xBA", + "\xF6\xA1" => "\xE9\xA8\xBF", + "\xF6\xA2" => "\xE9\xAB\x8D", + "\xF6\xA3" => "\xE9\xAC\x95", + "\xF6\xA4" => "\xE9\xAC\x97", + "\xF6\xA5" => "\xE9\xAC\x98", + "\xF6\xA6" => "\xE9\xAC\x96", + "\xF6\xA7" => "\xE9\xAC\xBA", + "\xF6\xA8" => "\xE9\xAD\x92", + "\xF6\xA9" => "\xE9\xB0\xAB", + "\xF6\xAA" => "\xE9\xB0\x9D", + "\xF6\xAB" => "\xE9\xB0\x9C", + "\xF6\xAC" => "\xE9\xB0\xAC", + "\xF6\xAD" => "\xE9\xB0\xA3", + "\xF6\xAE" => "\xE9\xB0\xA8", + "\xF6\xAF" => "\xE9\xB0\xA9", + "\xF6\xB0" => "\xE9\xB0\xA4", + "\xF6\xB1" => "\xE9\xB0\xA1", + "\xF6\xB2" => "\xE9\xB6\xB7", + "\xF6\xB3" => "\xE9\xB6\xB6", + "\xF6\xB4" => "\xE9\xB6\xBC", + "\xF6\xB5" => "\xE9\xB7\x81", + "\xF6\xB6" => "\xE9\xB7\x87", + "\xF6\xB7" => "\xE9\xB7\x8A", + "\xF6\xB8" => "\xE9\xB7\x8F", + "\xF6\xB9" => "\xE9\xB6\xBE", + "\xF6\xBA" => "\xE9\xB7\x85", + "\xF6\xBB" => "\xE9\xB7\x83", + "\xF6\xBC" => "\xE9\xB6\xBB", + "\xF6\xBD" => "\xE9\xB6\xB5", + "\xF6\xBE" => "\xE9\xB7\x8E", + "\xF6\xBF" => "\xE9\xB6\xB9", + "\xF6\xC0" => "\xE9\xB6\xBA", + "\xF6\xC1" => "\xE9\xB6\xAC", + "\xF6\xC2" => "\xE9\xB7\x88", + "\xF6\xC3" => "\xE9\xB6\xB1", + "\xF6\xC4" => "\xE9\xB6\xAD", + "\xF6\xC5" => "\xE9\xB7\x8C", + "\xF6\xC6" => "\xE9\xB6\xB3", + "\xF6\xC7" => "\xE9\xB7\x8D", + "\xF6\xC8" => "\xE9\xB6\xB2", + "\xF6\xC9" => "\xE9\xB9\xBA", + "\xF6\xCA" => "\xE9\xBA\x9C", + "\xF6\xCB" => "\xE9\xBB\xAB", + "\xF6\xCC" => "\xE9\xBB\xAE", + "\xF6\xCD" => "\xE9\xBB\xAD", + "\xF6\xCE" => "\xE9\xBC\x9B", + "\xF6\xCF" => "\xE9\xBC\x98", + "\xF6\xD0" => "\xE9\xBC\x9A", + "\xF6\xD1" => "\xE9\xBC\xB1", + "\xF6\xD2" => "\xE9\xBD\x8E", + "\xF6\xD3" => "\xE9\xBD\xA5", + "\xF6\xD4" => "\xE9\xBD\xA4", + "\xF6\xD5" => "\xE9\xBE\x92", + "\xF6\xD6" => "\xE4\xBA\xB9", + "\xF6\xD7" => "\xE5\x9B\x86", + "\xF6\xD8" => "\xE5\x9B\x85", + "\xF6\xD9" => "\xE5\x9B\x8B", + "\xF6\xDA" => "\xE5\xA5\xB1", + "\xF6\xDB" => "\xE5\xAD\x8B", + "\xF6\xDC" => "\xE5\xAD\x8C", + "\xF6\xDD" => "\xE5\xB7\x95", + "\xF6\xDE" => "\xE5\xB7\x91", + "\xF6\xDF" => "\xE5\xBB\xB2", + "\xF6\xE0" => "\xE6\x94\xA1", + "\xF6\xE1" => "\xE6\x94\xA0", + "\xF6\xE2" => "\xE6\x94\xA6", + "\xF6\xE3" => "\xE6\x94\xA2", + "\xF6\xE4" => "\xE6\xAC\x8B", + "\xF6\xE5" => "\xE6\xAC\x88", + "\xF6\xE6" => "\xE6\xAC\x89", + "\xF6\xE7" => "\xE6\xB0\x8D", + "\xF6\xE8" => "\xE7\x81\x95", + "\xF6\xE9" => "\xE7\x81\x96", + "\xF6\xEA" => "\xE7\x81\x97", + "\xF6\xEB" => "\xE7\x81\x92", + "\xF6\xEC" => "\xE7\x88\x9E", + "\xF6\xED" => "\xE7\x88\x9F", + "\xF6\xEE" => "\xE7\x8A\xA9", + "\xF6\xEF" => "\xE7\x8D\xBF", + "\xF6\xF0" => "\xE7\x93\x98", + "\xF6\xF1" => "\xE7\x93\x95", + "\xF6\xF2" => "\xE7\x93\x99", + "\xF6\xF3" => "\xE7\x93\x97", + "\xF6\xF4" => "\xE7\x99\xAD", + "\xF6\xF5" => "\xE7\x9A\xAD", + "\xF6\xF6" => "\xE7\xA4\xB5", + "\xF6\xF7" => "\xE7\xA6\xB4", + "\xF6\xF8" => "\xE7\xA9\xB0", + "\xF6\xF9" => "\xE7\xA9\xB1", + "\xF6\xFA" => "\xE7\xB1\x97", + "\xF6\xFB" => "\xE7\xB1\x9C", + "\xF6\xFC" => "\xE7\xB1\x99", + "\xF6\xFD" => "\xE7\xB1\x9B", + "\xF6\xFE" => "\xE7\xB1\x9A", + "\xF7\x40" => "\xE7\xB3\xB4", + "\xF7\x41" => "\xE7\xB3\xB1", + "\xF7\x42" => "\xE7\xBA\x91", + "\xF7\x43" => "\xE7\xBD\x8F", + "\xF7\x44" => "\xE7\xBE\x87", + "\xF7\x45" => "\xE8\x87\x9E", + "\xF7\x46" => "\xE8\x89\xAB", + "\xF7\x47" => "\xE8\x98\xB4", + "\xF7\x48" => "\xE8\x98\xB5", + "\xF7\x49" => "\xE8\x98\xB3", + "\xF7\x4A" => "\xE8\x98\xAC", + "\xF7\x4B" => "\xE8\x98\xB2", + "\xF7\x4C" => "\xE8\x98\xB6", + "\xF7\x4D" => "\xE8\xA0\xAC", + "\xF7\x4E" => "\xE8\xA0\xA8", + "\xF7\x4F" => "\xE8\xA0\xA6", + "\xF7\x50" => "\xE8\xA0\xAA", + "\xF7\x51" => "\xE8\xA0\xA5", + "\xF7\x52" => "\xE8\xA5\xB1", + "\xF7\x53" => "\xE8\xA6\xBF", + "\xF7\x54" => "\xE8\xA6\xBE", + "\xF7\x55" => "\xE8\xA7\xBB", + "\xF7\x56" => "\xE8\xAD\xBE", + "\xF7\x57" => "\xE8\xAE\x84", + "\xF7\x58" => "\xE8\xAE\x82", + "\xF7\x59" => "\xE8\xAE\x86", + "\xF7\x5A" => "\xE8\xAE\x85", + "\xF7\x5B" => "\xE8\xAD\xBF", + "\xF7\x5C" => "\xE8\xB4\x95", + "\xF7\x5D" => "\xE8\xBA\x95", + "\xF7\x5E" => "\xE8\xBA\x94", + "\xF7\x5F" => "\xE8\xBA\x9A", + "\xF7\x60" => "\xE8\xBA\x92", + "\xF7\x61" => "\xE8\xBA\x90", + "\xF7\x62" => "\xE8\xBA\x96", + "\xF7\x63" => "\xE8\xBA\x97", + "\xF7\x64" => "\xE8\xBD\xA0", + "\xF7\x65" => "\xE8\xBD\xA2", + "\xF7\x66" => "\xE9\x85\x87", + "\xF7\x67" => "\xE9\x91\x8C", + "\xF7\x68" => "\xE9\x91\x90", + "\xF7\x69" => "\xE9\x91\x8A", + "\xF7\x6A" => "\xE9\x91\x8B", + "\xF7\x6B" => "\xE9\x91\x8F", + "\xF7\x6C" => "\xE9\x91\x87", + "\xF7\x6D" => "\xE9\x91\x85", + "\xF7\x6E" => "\xE9\x91\x88", + "\xF7\x6F" => "\xE9\x91\x89", + "\xF7\x70" => "\xE9\x91\x86", + "\xF7\x71" => "\xE9\x9C\xBF", + "\xF7\x72" => "\xE9\x9F\xA3", + "\xF7\x73" => "\xE9\xA1\xAA", + "\xF7\x74" => "\xE9\xA1\xA9", + "\xF7\x75" => "\xE9\xA3\x8B", + "\xF7\x76" => "\xE9\xA5\x94", + "\xF7\x77" => "\xE9\xA5\x9B", + "\xF7\x78" => "\xE9\xA9\x8E", + "\xF7\x79" => "\xE9\xA9\x93", + "\xF7\x7A" => "\xE9\xA9\x94", + "\xF7\x7B" => "\xE9\xA9\x8C", + "\xF7\x7C" => "\xE9\xA9\x8F", + "\xF7\x7D" => "\xE9\xA9\x88", + "\xF7\x7E" => "\xE9\xA9\x8A", + "\xF7\xA1" => "\xE9\xA9\x89", + "\xF7\xA2" => "\xE9\xA9\x92", + "\xF7\xA3" => "\xE9\xA9\x90", + "\xF7\xA4" => "\xE9\xAB\x90", + "\xF7\xA5" => "\xE9\xAC\x99", + "\xF7\xA6" => "\xE9\xAC\xAB", + "\xF7\xA7" => "\xE9\xAC\xBB", + "\xF7\xA8" => "\xE9\xAD\x96", + "\xF7\xA9" => "\xE9\xAD\x95", + "\xF7\xAA" => "\xE9\xB1\x86", + "\xF7\xAB" => "\xE9\xB1\x88", + "\xF7\xAC" => "\xE9\xB0\xBF", + "\xF7\xAD" => "\xE9\xB1\x84", + "\xF7\xAE" => "\xE9\xB0\xB9", + "\xF7\xAF" => "\xE9\xB0\xB3", + "\xF7\xB0" => "\xE9\xB1\x81", + "\xF7\xB1" => "\xE9\xB0\xBC", + "\xF7\xB2" => "\xE9\xB0\xB7", + "\xF7\xB3" => "\xE9\xB0\xB4", + "\xF7\xB4" => "\xE9\xB0\xB2", + "\xF7\xB5" => "\xE9\xB0\xBD", + "\xF7\xB6" => "\xE9\xB0\xB6", + "\xF7\xB7" => "\xE9\xB7\x9B", + "\xF7\xB8" => "\xE9\xB7\x92", + "\xF7\xB9" => "\xE9\xB7\x9E", + "\xF7\xBA" => "\xE9\xB7\x9A", + "\xF7\xBB" => "\xE9\xB7\x8B", + "\xF7\xBC" => "\xE9\xB7\x90", + "\xF7\xBD" => "\xE9\xB7\x9C", + "\xF7\xBE" => "\xE9\xB7\x91", + "\xF7\xBF" => "\xE9\xB7\x9F", + "\xF7\xC0" => "\xE9\xB7\xA9", + "\xF7\xC1" => "\xE9\xB7\x99", + "\xF7\xC2" => "\xE9\xB7\x98", + "\xF7\xC3" => "\xE9\xB7\x96", + "\xF7\xC4" => "\xE9\xB7\xB5", + "\xF7\xC5" => "\xE9\xB7\x95", + "\xF7\xC6" => "\xE9\xB7\x9D", + "\xF7\xC7" => "\xE9\xBA\xB6", + "\xF7\xC8" => "\xE9\xBB\xB0", + "\xF7\xC9" => "\xE9\xBC\xB5", + "\xF7\xCA" => "\xE9\xBC\xB3", + "\xF7\xCB" => "\xE9\xBC\xB2", + "\xF7\xCC" => "\xE9\xBD\x82", + "\xF7\xCD" => "\xE9\xBD\xAB", + "\xF7\xCE" => "\xE9\xBE\x95", + "\xF7\xCF" => "\xE9\xBE\xA2", + "\xF7\xD0" => "\xE5\x84\xBD", + "\xF7\xD1" => "\xE5\x8A\x99", + "\xF7\xD2" => "\xE5\xA3\xA8", + "\xF7\xD3" => "\xE5\xA3\xA7", + "\xF7\xD4" => "\xE5\xA5\xB2", + "\xF7\xD5" => "\xE5\xAD\x8D", + "\xF7\xD6" => "\xE5\xB7\x98", + "\xF7\xD7" => "\xE8\xA0\xAF", + "\xF7\xD8" => "\xE5\xBD\x8F", + "\xF7\xD9" => "\xE6\x88\x81", + "\xF7\xDA" => "\xE6\x88\x83", + "\xF7\xDB" => "\xE6\x88\x84", + "\xF7\xDC" => "\xE6\x94\xA9", + "\xF7\xDD" => "\xE6\x94\xA5", + "\xF7\xDE" => "\xE6\x96\x96", + "\xF7\xDF" => "\xE6\x9B\xAB", + "\xF7\xE0" => "\xE6\xAC\x91", + "\xF7\xE1" => "\xE6\xAC\x92", + "\xF7\xE2" => "\xE6\xAC\x8F", + "\xF7\xE3" => "\xE6\xAF\x8A", + "\xF7\xE4" => "\xE7\x81\x9B", + "\xF7\xE5" => "\xE7\x81\x9A", + "\xF7\xE6" => "\xE7\x88\xA2", + "\xF7\xE7" => "\xE7\x8E\x82", + "\xF7\xE8" => "\xE7\x8E\x81", + "\xF7\xE9" => "\xE7\x8E\x83", + "\xF7\xEA" => "\xE7\x99\xB0", + "\xF7\xEB" => "\xE7\x9F\x94", + "\xF7\xEC" => "\xE7\xB1\xA7", + "\xF7\xED" => "\xE7\xB1\xA6", + "\xF7\xEE" => "\xE7\xBA\x95", + "\xF7\xEF" => "\xE8\x89\xAC", + "\xF7\xF0" => "\xE8\x98\xBA", + "\xF7\xF1" => "\xE8\x99\x80", + "\xF7\xF2" => "\xE8\x98\xB9", + "\xF7\xF3" => "\xE8\x98\xBC", + "\xF7\xF4" => "\xE8\x98\xB1", + "\xF7\xF5" => "\xE8\x98\xBB", + "\xF7\xF6" => "\xE8\x98\xBE", + "\xF7\xF7" => "\xE8\xA0\xB0", + "\xF7\xF8" => "\xE8\xA0\xB2", + "\xF7\xF9" => "\xE8\xA0\xAE", + "\xF7\xFA" => "\xE8\xA0\xB3", + "\xF7\xFB" => "\xE8\xA5\xB6", + "\xF7\xFC" => "\xE8\xA5\xB4", + "\xF7\xFD" => "\xE8\xA5\xB3", + "\xF7\xFE" => "\xE8\xA7\xBE", + "\xF8\x40" => "\xE8\xAE\x8C", + "\xF8\x41" => "\xE8\xAE\x8E", + "\xF8\x42" => "\xE8\xAE\x8B", + "\xF8\x43" => "\xE8\xAE\x88", + "\xF8\x44" => "\xE8\xB1\x85", + "\xF8\x45" => "\xE8\xB4\x99", + "\xF8\x46" => "\xE8\xBA\x98", + "\xF8\x47" => "\xE8\xBD\xA4", + "\xF8\x48" => "\xE8\xBD\xA3", + "\xF8\x49" => "\xE9\x86\xBC", + "\xF8\x4A" => "\xE9\x91\xA2", + "\xF8\x4B" => "\xE9\x91\x95", + "\xF8\x4C" => "\xE9\x91\x9D", + "\xF8\x4D" => "\xE9\x91\x97", + "\xF8\x4E" => "\xE9\x91\x9E", + "\xF8\x4F" => "\xE9\x9F\x84", + "\xF8\x50" => "\xE9\x9F\x85", + "\xF8\x51" => "\xE9\xA0\x80", + "\xF8\x52" => "\xE9\xA9\x96", + "\xF8\x53" => "\xE9\xA9\x99", + "\xF8\x54" => "\xE9\xAC\x9E", + "\xF8\x55" => "\xE9\xAC\x9F", + "\xF8\x56" => "\xE9\xAC\xA0", + "\xF8\x57" => "\xE9\xB1\x92", + "\xF8\x58" => "\xE9\xB1\x98", + "\xF8\x59" => "\xE9\xB1\x90", + "\xF8\x5A" => "\xE9\xB1\x8A", + "\xF8\x5B" => "\xE9\xB1\x8D", + "\xF8\x5C" => "\xE9\xB1\x8B", + "\xF8\x5D" => "\xE9\xB1\x95", + "\xF8\x5E" => "\xE9\xB1\x99", + "\xF8\x5F" => "\xE9\xB1\x8C", + "\xF8\x60" => "\xE9\xB1\x8E", + "\xF8\x61" => "\xE9\xB7\xBB", + "\xF8\x62" => "\xE9\xB7\xB7", + "\xF8\x63" => "\xE9\xB7\xAF", + "\xF8\x64" => "\xE9\xB7\xA3", + "\xF8\x65" => "\xE9\xB7\xAB", + "\xF8\x66" => "\xE9\xB7\xB8", + "\xF8\x67" => "\xE9\xB7\xA4", + "\xF8\x68" => "\xE9\xB7\xB6", + "\xF8\x69" => "\xE9\xB7\xA1", + "\xF8\x6A" => "\xE9\xB7\xAE", + "\xF8\x6B" => "\xE9\xB7\xA6", + "\xF8\x6C" => "\xE9\xB7\xB2", + "\xF8\x6D" => "\xE9\xB7\xB0", + "\xF8\x6E" => "\xE9\xB7\xA2", + "\xF8\x6F" => "\xE9\xB7\xAC", + "\xF8\x70" => "\xE9\xB7\xB4", + "\xF8\x71" => "\xE9\xB7\xB3", + "\xF8\x72" => "\xE9\xB7\xA8", + "\xF8\x73" => "\xE9\xB7\xAD", + "\xF8\x74" => "\xE9\xBB\x82", + "\xF8\x75" => "\xE9\xBB\x90", + "\xF8\x76" => "\xE9\xBB\xB2", + "\xF8\x77" => "\xE9\xBB\xB3", + "\xF8\x78" => "\xE9\xBC\x86", + "\xF8\x79" => "\xE9\xBC\x9C", + "\xF8\x7A" => "\xE9\xBC\xB8", + "\xF8\x7B" => "\xE9\xBC\xB7", + "\xF8\x7C" => "\xE9\xBC\xB6", + "\xF8\x7D" => "\xE9\xBD\x83", + "\xF8\x7E" => "\xE9\xBD\x8F", + "\xF8\xA1" => "\xE9\xBD\xB1", + "\xF8\xA2" => "\xE9\xBD\xB0", + "\xF8\xA3" => "\xE9\xBD\xAE", + "\xF8\xA4" => "\xE9\xBD\xAF", + "\xF8\xA5" => "\xE5\x9B\x93", + "\xF8\xA6" => "\xE5\x9B\x8D", + "\xF8\xA7" => "\xE5\xAD\x8E", + "\xF8\xA8" => "\xE5\xB1\xAD", + "\xF8\xA9" => "\xE6\x94\xAD", + "\xF8\xAA" => "\xE6\x9B\xAD", + "\xF8\xAB" => "\xE6\x9B\xAE", + "\xF8\xAC" => "\xE6\xAC\x93", + "\xF8\xAD" => "\xE7\x81\x9F", + "\xF8\xAE" => "\xE7\x81\xA1", + "\xF8\xAF" => "\xE7\x81\x9D", + "\xF8\xB0" => "\xE7\x81\xA0", + "\xF8\xB1" => "\xE7\x88\xA3", + "\xF8\xB2" => "\xE7\x93\x9B", + "\xF8\xB3" => "\xE7\x93\xA5", + "\xF8\xB4" => "\xE7\x9F\x95", + "\xF8\xB5" => "\xE7\xA4\xB8", + "\xF8\xB6" => "\xE7\xA6\xB7", + "\xF8\xB7" => "\xE7\xA6\xB6", + "\xF8\xB8" => "\xE7\xB1\xAA", + "\xF8\xB9" => "\xE7\xBA\x97", + "\xF8\xBA" => "\xE7\xBE\x89", + "\xF8\xBB" => "\xE8\x89\xAD", + "\xF8\xBC" => "\xE8\x99\x83", + "\xF8\xBD" => "\xE8\xA0\xB8", + "\xF8\xBE" => "\xE8\xA0\xB7", + "\xF8\xBF" => "\xE8\xA0\xB5", + "\xF8\xC0" => "\xE8\xA1\x8B", + "\xF8\xC1" => "\xE8\xAE\x94", + "\xF8\xC2" => "\xE8\xAE\x95", + "\xF8\xC3" => "\xE8\xBA\x9E", + "\xF8\xC4" => "\xE8\xBA\x9F", + "\xF8\xC5" => "\xE8\xBA\xA0", + "\xF8\xC6" => "\xE8\xBA\x9D", + "\xF8\xC7" => "\xE9\x86\xBE", + "\xF8\xC8" => "\xE9\x86\xBD", + "\xF8\xC9" => "\xE9\x87\x82", + "\xF8\xCA" => "\xE9\x91\xAB", + "\xF8\xCB" => "\xE9\x91\xA8", + "\xF8\xCC" => "\xE9\x91\xA9", + "\xF8\xCD" => "\xE9\x9B\xA5", + "\xF8\xCE" => "\xE9\x9D\x86", + "\xF8\xCF" => "\xE9\x9D\x83", + "\xF8\xD0" => "\xE9\x9D\x87", + "\xF8\xD1" => "\xE9\x9F\x87", + "\xF8\xD2" => "\xE9\x9F\xA5", + "\xF8\xD3" => "\xE9\xA9\x9E", + "\xF8\xD4" => "\xE9\xAB\x95", + "\xF8\xD5" => "\xE9\xAD\x99", + "\xF8\xD6" => "\xE9\xB1\xA3", + "\xF8\xD7" => "\xE9\xB1\xA7", + "\xF8\xD8" => "\xE9\xB1\xA6", + "\xF8\xD9" => "\xE9\xB1\xA2", + "\xF8\xDA" => "\xE9\xB1\x9E", + "\xF8\xDB" => "\xE9\xB1\xA0", + "\xF8\xDC" => "\xE9\xB8\x82", + "\xF8\xDD" => "\xE9\xB7\xBE", + "\xF8\xDE" => "\xE9\xB8\x87", + "\xF8\xDF" => "\xE9\xB8\x83", + "\xF8\xE0" => "\xE9\xB8\x86", + "\xF8\xE1" => "\xE9\xB8\x85", + "\xF8\xE2" => "\xE9\xB8\x80", + "\xF8\xE3" => "\xE9\xB8\x81", + "\xF8\xE4" => "\xE9\xB8\x89", + "\xF8\xE5" => "\xE9\xB7\xBF", + "\xF8\xE6" => "\xE9\xB7\xBD", + "\xF8\xE7" => "\xE9\xB8\x84", + "\xF8\xE8" => "\xE9\xBA\xA0", + "\xF8\xE9" => "\xE9\xBC\x9E", + "\xF8\xEA" => "\xE9\xBD\x86", + "\xF8\xEB" => "\xE9\xBD\xB4", + "\xF8\xEC" => "\xE9\xBD\xB5", + "\xF8\xED" => "\xE9\xBD\xB6", + "\xF8\xEE" => "\xE5\x9B\x94", + "\xF8\xEF" => "\xE6\x94\xAE", + "\xF8\xF0" => "\xE6\x96\xB8", + "\xF8\xF1" => "\xE6\xAC\x98", + "\xF8\xF2" => "\xE6\xAC\x99", + "\xF8\xF3" => "\xE6\xAC\x97", + "\xF8\xF4" => "\xE6\xAC\x9A", + "\xF8\xF5" => "\xE7\x81\xA2", + "\xF8\xF6" => "\xE7\x88\xA6", + "\xF8\xF7" => "\xE7\x8A\xAA", + "\xF8\xF8" => "\xE7\x9F\x98", + "\xF8\xF9" => "\xE7\x9F\x99", + "\xF8\xFA" => "\xE7\xA4\xB9", + "\xF8\xFB" => "\xE7\xB1\xA9", + "\xF8\xFC" => "\xE7\xB1\xAB", + "\xF8\xFD" => "\xE7\xB3\xB6", + "\xF8\xFE" => "\xE7\xBA\x9A", + "\xF9\x40" => "\xE7\xBA\x98", + "\xF9\x41" => "\xE7\xBA\x9B", + "\xF9\x42" => "\xE7\xBA\x99", + "\xF9\x43" => "\xE8\x87\xA0", + "\xF9\x44" => "\xE8\x87\xA1", + "\xF9\x45" => "\xE8\x99\x86", + "\xF9\x46" => "\xE8\x99\x87", + "\xF9\x47" => "\xE8\x99\x88", + "\xF9\x48" => "\xE8\xA5\xB9", + "\xF9\x49" => "\xE8\xA5\xBA", + "\xF9\x4A" => "\xE8\xA5\xBC", + "\xF9\x4B" => "\xE8\xA5\xBB", + "\xF9\x4C" => "\xE8\xA7\xBF", + "\xF9\x4D" => "\xE8\xAE\x98", + "\xF9\x4E" => "\xE8\xAE\x99", + "\xF9\x4F" => "\xE8\xBA\xA5", + "\xF9\x50" => "\xE8\xBA\xA4", + "\xF9\x51" => "\xE8\xBA\xA3", + "\xF9\x52" => "\xE9\x91\xAE", + "\xF9\x53" => "\xE9\x91\xAD", + "\xF9\x54" => "\xE9\x91\xAF", + "\xF9\x55" => "\xE9\x91\xB1", + "\xF9\x56" => "\xE9\x91\xB3", + "\xF9\x57" => "\xE9\x9D\x89", + "\xF9\x58" => "\xE9\xA1\xB2", + "\xF9\x59" => "\xE9\xA5\x9F", + "\xF9\x5A" => "\xE9\xB1\xA8", + "\xF9\x5B" => "\xE9\xB1\xAE", + "\xF9\x5C" => "\xE9\xB1\xAD", + "\xF9\x5D" => "\xE9\xB8\x8B", + "\xF9\x5E" => "\xE9\xB8\x8D", + "\xF9\x5F" => "\xE9\xB8\x90", + "\xF9\x60" => "\xE9\xB8\x8F", + "\xF9\x61" => "\xE9\xB8\x92", + "\xF9\x62" => "\xE9\xB8\x91", + "\xF9\x63" => "\xE9\xBA\xA1", + "\xF9\x64" => "\xE9\xBB\xB5", + "\xF9\x65" => "\xE9\xBC\x89", + "\xF9\x66" => "\xE9\xBD\x87", + "\xF9\x67" => "\xE9\xBD\xB8", + "\xF9\x68" => "\xE9\xBD\xBB", + "\xF9\x69" => "\xE9\xBD\xBA", + "\xF9\x6A" => "\xE9\xBD\xB9", + "\xF9\x6B" => "\xE5\x9C\x9E", + "\xF9\x6C" => "\xE7\x81\xA6", + "\xF9\x6D" => "\xE7\xB1\xAF", + "\xF9\x6E" => "\xE8\xA0\xBC", + "\xF9\x6F" => "\xE8\xB6\xB2", + "\xF9\x70" => "\xE8\xBA\xA6", + "\xF9\x71" => "\xE9\x87\x83", + "\xF9\x72" => "\xE9\x91\xB4", + "\xF9\x73" => "\xE9\x91\xB8", + "\xF9\x74" => "\xE9\x91\xB6", + "\xF9\x75" => "\xE9\x91\xB5", + "\xF9\x76" => "\xE9\xA9\xA0", + "\xF9\x77" => "\xE9\xB1\xB4", + "\xF9\x78" => "\xE9\xB1\xB3", + "\xF9\x79" => "\xE9\xB1\xB1", + "\xF9\x7A" => "\xE9\xB1\xB5", + "\xF9\x7B" => "\xE9\xB8\x94", + "\xF9\x7C" => "\xE9\xB8\x93", + "\xF9\x7D" => "\xE9\xBB\xB6", + "\xF9\x7E" => "\xE9\xBC\x8A", + "\xF9\xA1" => "\xE9\xBE\xA4", + "\xF9\xA2" => "\xE7\x81\xA8", + "\xF9\xA3" => "\xE7\x81\xA5", + "\xF9\xA4" => "\xE7\xB3\xB7", + "\xF9\xA5" => "\xE8\x99\xAA", + "\xF9\xA6" => "\xE8\xA0\xBE", + "\xF9\xA7" => "\xE8\xA0\xBD", + "\xF9\xA8" => "\xE8\xA0\xBF", + "\xF9\xA9" => "\xE8\xAE\x9E", + "\xF9\xAA" => "\xE8\xB2\x9C", + "\xF9\xAB" => "\xE8\xBA\xA9", + "\xF9\xAC" => "\xE8\xBB\x89", + "\xF9\xAD" => "\xE9\x9D\x8B", + "\xF9\xAE" => "\xE9\xA1\xB3", + "\xF9\xAF" => "\xE9\xA1\xB4", + "\xF9\xB0" => "\xE9\xA3\x8C", + "\xF9\xB1" => "\xE9\xA5\xA1", + "\xF9\xB2" => "\xE9\xA6\xAB", + "\xF9\xB3" => "\xE9\xA9\xA4", + "\xF9\xB4" => "\xE9\xA9\xA6", + "\xF9\xB5" => "\xE9\xA9\xA7", + "\xF9\xB6" => "\xE9\xAC\xA4", + "\xF9\xB7" => "\xE9\xB8\x95", + "\xF9\xB8" => "\xE9\xB8\x97", + "\xF9\xB9" => "\xE9\xBD\x88", + "\xF9\xBA" => "\xE6\x88\x87", + "\xF9\xBB" => "\xE6\xAC\x9E", + "\xF9\xBC" => "\xE7\x88\xA7", + "\xF9\xBD" => "\xE8\x99\x8C", + "\xF9\xBE" => "\xE8\xBA\xA8", + "\xF9\xBF" => "\xE9\x92\x82", + "\xF9\xC0" => "\xE9\x92\x80", + "\xF9\xC1" => "\xE9\x92\x81", + "\xF9\xC2" => "\xE9\xA9\xA9", + "\xF9\xC3" => "\xE9\xA9\xA8", + "\xF9\xC4" => "\xE9\xAC\xAE", + "\xF9\xC5" => "\xE9\xB8\x99", + "\xF9\xC6" => "\xE7\x88\xA9", + "\xF9\xC7" => "\xE8\x99\x8B", + "\xF9\xC8" => "\xE8\xAE\x9F", + "\xF9\xC9" => "\xE9\x92\x83", + "\xF9\xCA" => "\xE9\xB1\xB9", + "\xF9\xCB" => "\xE9\xBA\xB7", + "\xF9\xCC" => "\xE7\x99\xB5", + "\xF9\xCD" => "\xE9\xA9\xAB", + "\xF9\xCE" => "\xE9\xB1\xBA", + "\xF9\xCF" => "\xE9\xB8\x9D", + "\xF9\xD0" => "\xE7\x81\xA9", + "\xF9\xD1" => "\xE7\x81\xAA", + "\xF9\xD2" => "\xE9\xBA\xA4", + "\xF9\xD3" => "\xE9\xBD\xBE", + "\xF9\xD4" => "\xE9\xBD\x89", + "\xF9\xD5" => "\xE9\xBE\x98", + ); + return strtr($string, $transform); +} diff --git a/includes/utf/data/search_indexer_0.php b/includes/utf/data/search_indexer_0.php new file mode 100644 index 0000000..e8a087c --- /dev/null +++ b/includes/utf/data/search_indexer_0.php @@ -0,0 +1 @@ +'0',1=>'1',2=>'2',3=>'3',4=>'4',5=>'5',6=>'6',7=>'7',8=>'8',9=>'9','A'=>'a','B'=>'b','C'=>'c','D'=>'d','E'=>'e','F'=>'f','G'=>'g','H'=>'h','I'=>'i','J'=>'j','K'=>'k','L'=>'l','M'=>'m','N'=>'n','O'=>'o','P'=>'p','Q'=>'q','R'=>'r','S'=>'s','T'=>'t','U'=>'u','V'=>'v','W'=>'w','X'=>'x','Y'=>'y','Z'=>'z','a'=>'a','b'=>'b','c'=>'c','d'=>'d','e'=>'e','f'=>'f','g'=>'g','h'=>'h','i'=>'i','j'=>'j','k'=>'k','l'=>'l','m'=>'m','n'=>'n','o'=>'o','p'=>'p','q'=>'q','r'=>'r','s'=>'s','t'=>'t','u'=>'u','v'=>'v','w'=>'w','x'=>'x','y'=>'y','z'=>'z','ª'=>'ª','²'=>'2','³'=>'3','µ'=>'µ','¹'=>'1','º'=>'º','¼'=>'1/4','½'=>'1/2','¾'=>'3/4','À'=>'à','Á'=>'á','Â'=>'â','Ã'=>'ã','Ä'=>'ae','Å'=>'å','Æ'=>'ae','Ç'=>'ç','È'=>'è','É'=>'é','Ê'=>'ê','Ë'=>'ë','Ì'=>'ì','Í'=>'í','Î'=>'î','Ï'=>'ï','Ð'=>'ð','Ñ'=>'ñ','Ò'=>'ò','Ó'=>'ó','Ô'=>'ô','Õ'=>'õ','Ö'=>'oe','Ø'=>'ø','Ù'=>'ù','Ú'=>'ú','Û'=>'û','Ü'=>'ü','Ý'=>'ý','Þ'=>'þ','ß'=>'ss','à'=>'à','á'=>'á','â'=>'â','ã'=>'ã','ä'=>'ae','å'=>'å','æ'=>'ae','ç'=>'ç','è'=>'è','é'=>'é','ê'=>'ê','ë'=>'ë','ì'=>'ì','í'=>'í','î'=>'î','ï'=>'ï','ð'=>'ð','ñ'=>'ñ','ò'=>'ò','ó'=>'ó','ô'=>'ô','õ'=>'õ','ö'=>'oe','ø'=>'ø','ù'=>'ù','ú'=>'ú','û'=>'û','ü'=>'ue','ý'=>'ý','þ'=>'þ','ÿ'=>'ÿ','Ā'=>'ā','ā'=>'ā','Ă'=>'ă','ă'=>'ă','Ą'=>'ą','ą'=>'ą','Ć'=>'ć','ć'=>'ć','Ĉ'=>'ĉ','ĉ'=>'ĉ','Ċ'=>'ċ','ċ'=>'ċ','Č'=>'č','č'=>'č','Ď'=>'ď','ď'=>'ď','Đ'=>'đ','đ'=>'đ','Ē'=>'ē','ē'=>'ē','Ĕ'=>'ĕ','ĕ'=>'ĕ','Ė'=>'ė','ė'=>'ė','Ę'=>'ę','ę'=>'ę','Ě'=>'ě','ě'=>'ě','Ĝ'=>'ĝ','ĝ'=>'ĝ','Ğ'=>'ğ','ğ'=>'ğ','Ġ'=>'ġ','ġ'=>'ġ','Ģ'=>'ģ','ģ'=>'ģ','Ĥ'=>'ĥ','ĥ'=>'ĥ','Ħ'=>'ħ','ħ'=>'ħ','Ĩ'=>'ĩ','ĩ'=>'ĩ','Ī'=>'ī','ī'=>'ī','Ĭ'=>'ĭ','ĭ'=>'ĭ','Į'=>'į','į'=>'į','İ'=>'i','ı'=>'ı','IJ'=>'ij','ij'=>'ij','Ĵ'=>'ĵ','ĵ'=>'ĵ','Ķ'=>'ķ','ķ'=>'ķ','ĸ'=>'ĸ','Ĺ'=>'ĺ','ĺ'=>'ĺ','Ļ'=>'ļ','ļ'=>'ļ','Ľ'=>'ľ','ľ'=>'ľ','Ŀ'=>'ŀ','ŀ'=>'ŀ','Ł'=>'ł','ł'=>'ł','Ń'=>'ń','ń'=>'ń','Ņ'=>'ņ','ņ'=>'ņ','Ň'=>'ň','ň'=>'ň','ʼn'=>'ʼn','Ŋ'=>'ŋ','ŋ'=>'ŋ','Ō'=>'ō','ō'=>'ō','Ŏ'=>'ŏ','ŏ'=>'ŏ','Ő'=>'ő','ő'=>'ő','Œ'=>'oe','œ'=>'oe','Ŕ'=>'ŕ','ŕ'=>'ŕ','Ŗ'=>'ŗ','ŗ'=>'ŗ','Ř'=>'ř','ř'=>'ř','Ś'=>'ś','ś'=>'ś','Ŝ'=>'ŝ','ŝ'=>'ŝ','Ş'=>'ş','ş'=>'ş','Š'=>'š','š'=>'š','Ţ'=>'ţ','ţ'=>'ţ','Ť'=>'ť','ť'=>'ť','Ŧ'=>'ŧ','ŧ'=>'ŧ','Ũ'=>'ũ','ũ'=>'ũ','Ū'=>'ū','ū'=>'ū','Ŭ'=>'ŭ','ŭ'=>'ŭ','Ů'=>'ů','ů'=>'ů','Ű'=>'ű','ű'=>'ű','Ų'=>'ų','ų'=>'ų','Ŵ'=>'ŵ','ŵ'=>'ŵ','Ŷ'=>'ŷ','ŷ'=>'ŷ','Ÿ'=>'ÿ','Ź'=>'ź','ź'=>'ź','Ż'=>'ż','ż'=>'ż','Ž'=>'ž','ž'=>'ž','ſ'=>'ſ','ƀ'=>'ƀ','Ɓ'=>'ɓ','Ƃ'=>'ƃ','ƃ'=>'ƃ','Ƅ'=>'ƅ','ƅ'=>'ƅ','Ɔ'=>'ɔ','Ƈ'=>'ƈ','ƈ'=>'ƈ','Ɖ'=>'ɖ','Ɗ'=>'ɗ','Ƌ'=>'ƌ','ƌ'=>'ƌ','ƍ'=>'ƍ','Ǝ'=>'ǝ','Ə'=>'ə','Ɛ'=>'ɛ','Ƒ'=>'ƒ','ƒ'=>'ƒ','Ɠ'=>'ɠ','Ɣ'=>'ɣ','ƕ'=>'hv','Ɩ'=>'ɩ','Ɨ'=>'ɨ','Ƙ'=>'ƙ','ƙ'=>'ƙ','ƚ'=>'ƚ','ƛ'=>'ƛ','Ɯ'=>'ɯ','Ɲ'=>'ɲ','ƞ'=>'ƞ','Ɵ'=>'ɵ','Ơ'=>'ơ','ơ'=>'ơ','Ƣ'=>'oi','ƣ'=>'oi','Ƥ'=>'ƥ','ƥ'=>'ƥ','Ʀ'=>'yr','Ƨ'=>'ƨ','ƨ'=>'ƨ','Ʃ'=>'ʃ','ƪ'=>'ƪ','ƫ'=>'ƫ','Ƭ'=>'ƭ','ƭ'=>'ƭ','Ʈ'=>'ʈ','Ư'=>'ư','ư'=>'ư','Ʊ'=>'ʊ','Ʋ'=>'ʋ','Ƴ'=>'ƴ','ƴ'=>'ƴ','Ƶ'=>'ƶ','ƶ'=>'ƶ','Ʒ'=>'ʒ','Ƹ'=>'ƹ','ƹ'=>'ƹ','ƺ'=>'ƺ','ƻ'=>'ƻ','Ƽ'=>'ƽ','ƽ'=>'ƽ','ƾ'=>'ƾ','ƿ'=>'ƿ','ǀ'=>'ǀ','ǁ'=>'ǁ','ǂ'=>'ǂ','ǃ'=>'ǃ','DŽ'=>'dž','Dž'=>'dž','dž'=>'dž','LJ'=>'lj','Lj'=>'lj','lj'=>'lj','NJ'=>'nj','Nj'=>'nj','nj'=>'nj','Ǎ'=>'ǎ','ǎ'=>'ǎ','Ǐ'=>'ǐ','ǐ'=>'ǐ','Ǒ'=>'ǒ','ǒ'=>'ǒ','Ǔ'=>'ǔ','ǔ'=>'ǔ','Ǖ'=>'ǖ','ǖ'=>'ǖ','Ǘ'=>'ǘ','ǘ'=>'ǘ','Ǚ'=>'ǚ','ǚ'=>'ǚ','Ǜ'=>'ǜ','ǜ'=>'ǜ','ǝ'=>'ǝ','Ǟ'=>'ǟ','ǟ'=>'ǟ','Ǡ'=>'ǡ','ǡ'=>'ǡ','Ǣ'=>'ǣ','ǣ'=>'ǣ','Ǥ'=>'ǥ','ǥ'=>'ǥ','Ǧ'=>'ǧ','ǧ'=>'ǧ','Ǩ'=>'ǩ','ǩ'=>'ǩ','Ǫ'=>'ǫ','ǫ'=>'ǫ','Ǭ'=>'ǭ','ǭ'=>'ǭ','Ǯ'=>'ǯ','ǯ'=>'ǯ','ǰ'=>'ǰ','DZ'=>'dz','Dz'=>'dz','dz'=>'dz','Ǵ'=>'ǵ','ǵ'=>'ǵ','Ƕ'=>'ƕ','Ƿ'=>'ƿ','Ǹ'=>'ǹ','ǹ'=>'ǹ','Ǻ'=>'ǻ','ǻ'=>'ǻ','Ǽ'=>'ǽ','ǽ'=>'ǽ','Ǿ'=>'ǿ','ǿ'=>'ǿ','Ȁ'=>'ȁ','ȁ'=>'ȁ','Ȃ'=>'ȃ','ȃ'=>'ȃ','Ȅ'=>'ȅ','ȅ'=>'ȅ','Ȇ'=>'ȇ','ȇ'=>'ȇ','Ȉ'=>'ȉ','ȉ'=>'ȉ','Ȋ'=>'ȋ','ȋ'=>'ȋ','Ȍ'=>'ȍ','ȍ'=>'ȍ','Ȏ'=>'ȏ','ȏ'=>'ȏ','Ȑ'=>'ȑ','ȑ'=>'ȑ','Ȓ'=>'ȓ','ȓ'=>'ȓ','Ȕ'=>'ȕ','ȕ'=>'ȕ','Ȗ'=>'ȗ','ȗ'=>'ȗ','Ș'=>'ș','ș'=>'ș','Ț'=>'ț','ț'=>'ț','Ȝ'=>'ȝ','ȝ'=>'ȝ','Ȟ'=>'ȟ','ȟ'=>'ȟ','Ƞ'=>'ƞ','ȡ'=>'ȡ','Ȣ'=>'ou','ȣ'=>'ou','Ȥ'=>'ȥ','ȥ'=>'ȥ','Ȧ'=>'ȧ','ȧ'=>'ȧ','Ȩ'=>'ȩ','ȩ'=>'ȩ','Ȫ'=>'ȫ','ȫ'=>'ȫ','Ȭ'=>'ȭ','ȭ'=>'ȭ','Ȯ'=>'ȯ','ȯ'=>'ȯ','Ȱ'=>'ȱ','ȱ'=>'ȱ','Ȳ'=>'ȳ','ȳ'=>'ȳ','ȴ'=>'ȴ','ȵ'=>'ȵ','ȶ'=>'ȶ','ȷ'=>'ȷ','ȸ'=>'ȸ','ȹ'=>'ȹ','Ⱥ'=>'ⱥ','Ȼ'=>'ȼ','ȼ'=>'ȼ','Ƚ'=>'ƚ','Ⱦ'=>'ⱦ','ȿ'=>'ȿ','ɀ'=>'ɀ','Ɂ'=>'ɂ','ɂ'=>'ɂ','Ƀ'=>'ƀ','Ʉ'=>'ʉ','Ʌ'=>'ʌ','Ɇ'=>'ɇ','ɇ'=>'ɇ','Ɉ'=>'ɉ','ɉ'=>'ɉ','Ɋ'=>'ɋ','ɋ'=>'ɋ','Ɍ'=>'ɍ','ɍ'=>'ɍ','Ɏ'=>'ɏ','ɏ'=>'ɏ','ɐ'=>'ɐ','ɑ'=>'ɑ','ɒ'=>'ɒ','ɓ'=>'ɓ','ɔ'=>'ɔ','ɕ'=>'ɕ','ɖ'=>'ɖ','ɗ'=>'ɗ','ɘ'=>'ɘ','ə'=>'ə','ɚ'=>'ɚ','ɛ'=>'ɛ','ɜ'=>'ɜ','ɝ'=>'ɝ','ɞ'=>'ɞ','ɟ'=>'ɟ','ɠ'=>'ɠ','ɡ'=>'ɡ','ɢ'=>'ɢ','ɣ'=>'ɣ','ɤ'=>'ɤ','ɥ'=>'ɥ','ɦ'=>'ɦ','ɧ'=>'ɧ','ɨ'=>'ɨ','ɩ'=>'ɩ','ɪ'=>'ɪ','ɫ'=>'ɫ','ɬ'=>'ɬ','ɭ'=>'ɭ','ɮ'=>'ɮ','ɯ'=>'ɯ','ɰ'=>'ɰ','ɱ'=>'ɱ','ɲ'=>'ɲ','ɳ'=>'ɳ','ɴ'=>'ɴ','ɵ'=>'ɵ','ɶ'=>'ɶ','ɷ'=>'ɷ','ɸ'=>'ɸ','ɹ'=>'ɹ','ɺ'=>'ɺ','ɻ'=>'ɻ','ɼ'=>'ɼ','ɽ'=>'ɽ','ɾ'=>'ɾ','ɿ'=>'ɿ','ʀ'=>'ʀ','ʁ'=>'ʁ','ʂ'=>'ʂ','ʃ'=>'ʃ','ʄ'=>'ʄ','ʅ'=>'ʅ','ʆ'=>'ʆ','ʇ'=>'ʇ','ʈ'=>'ʈ','ʉ'=>'ʉ','ʊ'=>'ʊ','ʋ'=>'ʋ','ʌ'=>'ʌ','ʍ'=>'ʍ','ʎ'=>'ʎ','ʏ'=>'ʏ','ʐ'=>'ʐ','ʑ'=>'ʑ','ʒ'=>'ʒ','ʓ'=>'ʓ','ʔ'=>'ʔ','ʕ'=>'ʕ','ʖ'=>'ʖ','ʗ'=>'ʗ','ʘ'=>'ʘ','ʙ'=>'ʙ','ʚ'=>'ʚ','ʛ'=>'ʛ','ʜ'=>'ʜ','ʝ'=>'ʝ','ʞ'=>'ʞ','ʟ'=>'ʟ','ʠ'=>'ʠ','ʡ'=>'ʡ','ʢ'=>'ʢ','ʣ'=>'ʣ','ʤ'=>'ʤ','ʥ'=>'ʥ','ʦ'=>'ʦ','ʧ'=>'ʧ','ʨ'=>'ʨ','ʩ'=>'ʩ','ʪ'=>'ʪ','ʫ'=>'ʫ','ʬ'=>'ʬ','ʭ'=>'ʭ','ʮ'=>'ʮ','ʯ'=>'ʯ','ʰ'=>'ʰ','ʱ'=>'ʱ','ʲ'=>'ʲ','ʳ'=>'ʳ','ʴ'=>'ʴ','ʵ'=>'ʵ','ʶ'=>'ʶ','ʷ'=>'ʷ','ʸ'=>'ʸ','ʹ'=>'ʹ','ʺ'=>'ʺ','ʻ'=>'ʻ','ʼ'=>'ʼ','ʽ'=>'ʽ','ʾ'=>'ʾ','ʿ'=>'ʿ','ˀ'=>'ˀ','ˁ'=>'ˁ','ˆ'=>'ˆ','ˇ'=>'ˇ','ˈ'=>'ˈ','ˉ'=>'ˉ','ˊ'=>'ˊ','ˋ'=>'ˋ','ˌ'=>'ˌ','ˍ'=>'ˍ','ˎ'=>'ˎ','ˏ'=>'ˏ','ː'=>'ː','ˑ'=>'ˑ','ˠ'=>'ˠ','ˡ'=>'ˡ','ˢ'=>'ˢ','ˣ'=>'ˣ','ˤ'=>'ˤ','ˮ'=>'ˮ','̀'=>'̀','́'=>'́','̂'=>'̂','̃'=>'̃','̄'=>'̄','̅'=>'̅','̆'=>'̆','̇'=>'̇','̈'=>'̈','̉'=>'̉','̊'=>'̊','̋'=>'̋','̌'=>'̌','̍'=>'̍','̎'=>'̎','̏'=>'̏','̐'=>'̐','̑'=>'̑','̒'=>'̒','̓'=>'̓','̔'=>'̔','̕'=>'̕','̖'=>'̖','̗'=>'̗','̘'=>'̘','̙'=>'̙','̚'=>'̚','̛'=>'̛','̜'=>'̜','̝'=>'̝','̞'=>'̞','̟'=>'̟','̠'=>'̠','̡'=>'̡','̢'=>'̢','̣'=>'̣','̤'=>'̤','̥'=>'̥','̦'=>'̦','̧'=>'̧','̨'=>'̨','̩'=>'̩','̪'=>'̪','̫'=>'̫','̬'=>'̬','̭'=>'̭','̮'=>'̮','̯'=>'̯','̰'=>'̰','̱'=>'̱','̲'=>'̲','̳'=>'̳','̴'=>'̴','̵'=>'̵','̶'=>'̶','̷'=>'̷','̸'=>'̸','̹'=>'̹','̺'=>'̺','̻'=>'̻','̼'=>'̼','̽'=>'̽','̾'=>'̾','̿'=>'̿','̀'=>'̀','́'=>'́','͂'=>'͂','̓'=>'̓','̈́'=>'̈́','ͅ'=>'ͅ','͆'=>'͆','͇'=>'͇','͈'=>'͈','͉'=>'͉','͊'=>'͊','͋'=>'͋','͌'=>'͌','͍'=>'͍','͎'=>'͎','͏'=>'͏','͐'=>'͐','͑'=>'͑','͒'=>'͒','͓'=>'͓','͔'=>'͔','͕'=>'͕','͖'=>'͖','͗'=>'͗','͘'=>'͘','͙'=>'͙','͚'=>'͚','͛'=>'͛','͜'=>'͜','͝'=>'͝','͞'=>'͞','͟'=>'͟','͠'=>'͠','͡'=>'͡','͢'=>'͢','ͣ'=>'ͣ','ͤ'=>'ͤ','ͥ'=>'ͥ','ͦ'=>'ͦ','ͧ'=>'ͧ','ͨ'=>'ͨ','ͩ'=>'ͩ','ͪ'=>'ͪ','ͫ'=>'ͫ','ͬ'=>'ͬ','ͭ'=>'ͭ','ͮ'=>'ͮ','ͯ'=>'ͯ','ͺ'=>'ͺ','ͻ'=>'ͻ','ͼ'=>'ͼ','ͽ'=>'ͽ','Ά'=>'ά','Έ'=>'έ','Ή'=>'ή','Ί'=>'ί','Ό'=>'ό','Ύ'=>'ύ','Ώ'=>'ώ','ΐ'=>'ΐ','Α'=>'α','Β'=>'β','Γ'=>'γ','Δ'=>'δ','Ε'=>'ε','Ζ'=>'ζ','Η'=>'η','Θ'=>'θ','Ι'=>'ι','Κ'=>'κ','Λ'=>'λ','Μ'=>'μ','Ν'=>'ν','Ξ'=>'ξ','Ο'=>'ο','Π'=>'π','Ρ'=>'ρ','Σ'=>'σ','Τ'=>'τ','Υ'=>'υ','Φ'=>'φ','Χ'=>'χ','Ψ'=>'ψ','Ω'=>'ω','Ϊ'=>'ϊ','Ϋ'=>'ϋ','ά'=>'ά','έ'=>'έ','ή'=>'ή','ί'=>'ί','ΰ'=>'ΰ','α'=>'α','β'=>'β','γ'=>'γ','δ'=>'δ','ε'=>'ε','ζ'=>'ζ','η'=>'η','θ'=>'θ','ι'=>'ι','κ'=>'κ','λ'=>'λ','μ'=>'μ','ν'=>'ν','ξ'=>'ξ','ο'=>'ο','π'=>'π','ρ'=>'ρ','ς'=>'ς','σ'=>'σ','τ'=>'τ','υ'=>'υ','φ'=>'φ','χ'=>'χ','ψ'=>'ψ','ω'=>'ω','ϊ'=>'ϊ','ϋ'=>'ϋ','ό'=>'ό','ύ'=>'ύ','ώ'=>'ώ','ϐ'=>'ϐ','ϑ'=>'ϑ','ϒ'=>'ϒ','ϓ'=>'ϓ','ϔ'=>'ϔ','ϕ'=>'ϕ','ϖ'=>'ϖ','ϗ'=>'ϗ','Ϙ'=>'ϙ','ϙ'=>'ϙ','Ϛ'=>'ϛ','ϛ'=>'ϛ','Ϝ'=>'ϝ','ϝ'=>'ϝ','Ϟ'=>'ϟ','ϟ'=>'ϟ','Ϡ'=>'ϡ','ϡ'=>'ϡ','Ϣ'=>'ϣ','ϣ'=>'ϣ','Ϥ'=>'ϥ','ϥ'=>'ϥ','Ϧ'=>'ϧ','ϧ'=>'ϧ','Ϩ'=>'ϩ','ϩ'=>'ϩ','Ϫ'=>'ϫ','ϫ'=>'ϫ','Ϭ'=>'ϭ','ϭ'=>'ϭ','Ϯ'=>'ϯ','ϯ'=>'ϯ','ϰ'=>'ϰ','ϱ'=>'ϱ','ϲ'=>'ϲ','ϳ'=>'ϳ','ϴ'=>'θ','ϵ'=>'ϵ','Ϸ'=>'ϸ','ϸ'=>'ϸ','Ϲ'=>'ϲ','Ϻ'=>'ϻ','ϻ'=>'ϻ','ϼ'=>'ϼ','Ͻ'=>'ͻ','Ͼ'=>'ͼ','Ͽ'=>'ͽ','Ѐ'=>'ѐ','Ё'=>'ё','Ђ'=>'ђ','Ѓ'=>'ѓ','Є'=>'є','Ѕ'=>'ѕ','І'=>'і','Ї'=>'ї','Ј'=>'ј','Љ'=>'љ','Њ'=>'њ','Ћ'=>'ћ','Ќ'=>'ќ','Ѝ'=>'ѝ','Ў'=>'ў','Џ'=>'џ','А'=>'а','Б'=>'б','В'=>'в','Г'=>'г','Д'=>'д','Е'=>'е','Ж'=>'ж','З'=>'з','И'=>'и','Й'=>'й','К'=>'к','Л'=>'л','М'=>'м','Н'=>'н','О'=>'о','П'=>'п','Р'=>'р','С'=>'с','Т'=>'т','У'=>'у','Ф'=>'ф','Х'=>'х','Ц'=>'ц','Ч'=>'ч','Ш'=>'ш','Щ'=>'щ','Ъ'=>'ъ','Ы'=>'ы','Ь'=>'ь','Э'=>'э','Ю'=>'ю','Я'=>'я','а'=>'а','б'=>'б','в'=>'в','г'=>'г','д'=>'д','е'=>'е','ж'=>'ж','з'=>'з','и'=>'и','й'=>'й','к'=>'к','л'=>'л','м'=>'м','н'=>'н','о'=>'о','п'=>'п','р'=>'р','с'=>'с','т'=>'т','у'=>'у','ф'=>'ф','х'=>'х','ц'=>'ц','ч'=>'ч','ш'=>'ш','щ'=>'щ','ъ'=>'ъ','ы'=>'ы','ь'=>'ь','э'=>'э','ю'=>'ю','я'=>'я','ѐ'=>'ѐ','ё'=>'ё','ђ'=>'ђ','ѓ'=>'ѓ','є'=>'є','ѕ'=>'ѕ','і'=>'і','ї'=>'ї','ј'=>'ј','љ'=>'љ','њ'=>'њ','ћ'=>'ћ','ќ'=>'ќ','ѝ'=>'ѝ','ў'=>'ў','џ'=>'џ','Ѡ'=>'ѡ','ѡ'=>'ѡ','Ѣ'=>'ѣ','ѣ'=>'ѣ','Ѥ'=>'ѥ','ѥ'=>'ѥ','Ѧ'=>'ѧ','ѧ'=>'ѧ','Ѩ'=>'ѩ','ѩ'=>'ѩ','Ѫ'=>'ѫ','ѫ'=>'ѫ','Ѭ'=>'ѭ','ѭ'=>'ѭ','Ѯ'=>'ѯ','ѯ'=>'ѯ','Ѱ'=>'ѱ','ѱ'=>'ѱ','Ѳ'=>'ѳ','ѳ'=>'ѳ','Ѵ'=>'ѵ','ѵ'=>'ѵ','Ѷ'=>'ѷ','ѷ'=>'ѷ','Ѹ'=>'ѹ','ѹ'=>'ѹ','Ѻ'=>'ѻ','ѻ'=>'ѻ','Ѽ'=>'ѽ','ѽ'=>'ѽ','Ѿ'=>'ѿ','ѿ'=>'ѿ','Ҁ'=>'ҁ','ҁ'=>'ҁ','҃'=>'҃','҄'=>'҄','҅'=>'҅','҆'=>'҆','҈'=>'҈','҉'=>'҉','Ҋ'=>'ҋ','ҋ'=>'ҋ','Ҍ'=>'ҍ','ҍ'=>'ҍ','Ҏ'=>'ҏ','ҏ'=>'ҏ','Ґ'=>'ґ','ґ'=>'ґ','Ғ'=>'ғ','ғ'=>'ғ','Ҕ'=>'ҕ','ҕ'=>'ҕ','Җ'=>'җ','җ'=>'җ','Ҙ'=>'ҙ','ҙ'=>'ҙ','Қ'=>'қ','қ'=>'қ','Ҝ'=>'ҝ','ҝ'=>'ҝ','Ҟ'=>'ҟ','ҟ'=>'ҟ','Ҡ'=>'ҡ','ҡ'=>'ҡ','Ң'=>'ң','ң'=>'ң','Ҥ'=>'ҥ','ҥ'=>'ҥ','Ҧ'=>'ҧ','ҧ'=>'ҧ','Ҩ'=>'ҩ','ҩ'=>'ҩ','Ҫ'=>'ҫ','ҫ'=>'ҫ','Ҭ'=>'ҭ','ҭ'=>'ҭ','Ү'=>'ү','ү'=>'ү','Ұ'=>'ұ','ұ'=>'ұ','Ҳ'=>'ҳ','ҳ'=>'ҳ','Ҵ'=>'ҵ','ҵ'=>'ҵ','Ҷ'=>'ҷ','ҷ'=>'ҷ','Ҹ'=>'ҹ','ҹ'=>'ҹ','Һ'=>'һ','һ'=>'һ','Ҽ'=>'ҽ','ҽ'=>'ҽ','Ҿ'=>'ҿ','ҿ'=>'ҿ','Ӏ'=>'ӏ','Ӂ'=>'ӂ','ӂ'=>'ӂ','Ӄ'=>'ӄ','ӄ'=>'ӄ','Ӆ'=>'ӆ','ӆ'=>'ӆ','Ӈ'=>'ӈ','ӈ'=>'ӈ','Ӊ'=>'ӊ','ӊ'=>'ӊ','Ӌ'=>'ӌ','ӌ'=>'ӌ','Ӎ'=>'ӎ','ӎ'=>'ӎ','ӏ'=>'ӏ','Ӑ'=>'ӑ','ӑ'=>'ӑ','Ӓ'=>'ӓ','ӓ'=>'ӓ','Ӕ'=>'ӕ','ӕ'=>'ӕ','Ӗ'=>'ӗ','ӗ'=>'ӗ','Ә'=>'ә','ә'=>'ә','Ӛ'=>'ӛ','ӛ'=>'ӛ','Ӝ'=>'ӝ','ӝ'=>'ӝ','Ӟ'=>'ӟ','ӟ'=>'ӟ','Ӡ'=>'ӡ','ӡ'=>'ӡ','Ӣ'=>'ӣ','ӣ'=>'ӣ','Ӥ'=>'ӥ','ӥ'=>'ӥ','Ӧ'=>'ӧ','ӧ'=>'ӧ','Ө'=>'ө','ө'=>'ө','Ӫ'=>'ӫ','ӫ'=>'ӫ','Ӭ'=>'ӭ','ӭ'=>'ӭ','Ӯ'=>'ӯ','ӯ'=>'ӯ','Ӱ'=>'ӱ','ӱ'=>'ӱ','Ӳ'=>'ӳ','ӳ'=>'ӳ','Ӵ'=>'ӵ','ӵ'=>'ӵ','Ӷ'=>'ӷ','ӷ'=>'ӷ','Ӹ'=>'ӹ','ӹ'=>'ӹ','Ӻ'=>'ӻ','ӻ'=>'ӻ','Ӽ'=>'ӽ','ӽ'=>'ӽ','Ӿ'=>'ӿ','ӿ'=>'ӿ','Ԁ'=>'ԁ','ԁ'=>'ԁ','Ԃ'=>'ԃ','ԃ'=>'ԃ','Ԅ'=>'ԅ','ԅ'=>'ԅ','Ԇ'=>'ԇ','ԇ'=>'ԇ','Ԉ'=>'ԉ','ԉ'=>'ԉ','Ԋ'=>'ԋ','ԋ'=>'ԋ','Ԍ'=>'ԍ','ԍ'=>'ԍ','Ԏ'=>'ԏ','ԏ'=>'ԏ','Ԑ'=>'ԑ','ԑ'=>'ԑ','Ԓ'=>'ԓ','ԓ'=>'ԓ','Ա'=>'ա','Բ'=>'բ','Գ'=>'գ','Դ'=>'դ','Ե'=>'ե','Զ'=>'զ','Է'=>'է','Ը'=>'ը','Թ'=>'թ','Ժ'=>'ժ','Ի'=>'ի','Լ'=>'լ','Խ'=>'խ','Ծ'=>'ծ','Կ'=>'կ','Հ'=>'հ','Ձ'=>'ձ','Ղ'=>'ղ','Ճ'=>'ճ','Մ'=>'մ','Յ'=>'յ','Ն'=>'ն','Շ'=>'շ','Ո'=>'ո','Չ'=>'չ','Պ'=>'պ','Ջ'=>'ջ','Ռ'=>'ռ','Ս'=>'ս','Վ'=>'վ','Տ'=>'տ','Ր'=>'ր','Ց'=>'ց','Ւ'=>'ւ','Փ'=>'փ','Ք'=>'ք','Օ'=>'օ','Ֆ'=>'ֆ','ՙ'=>'ՙ','ա'=>'ա','բ'=>'բ','գ'=>'գ','դ'=>'դ','ե'=>'ե','զ'=>'զ','է'=>'է','ը'=>'ը','թ'=>'թ','ժ'=>'ժ','ի'=>'ի','լ'=>'լ','խ'=>'խ','ծ'=>'ծ','կ'=>'կ','հ'=>'հ','ձ'=>'ձ','ղ'=>'ղ','ճ'=>'ճ','մ'=>'մ','յ'=>'յ','ն'=>'ն','շ'=>'շ','ո'=>'ո','չ'=>'չ','պ'=>'պ','ջ'=>'ջ','ռ'=>'ռ','ս'=>'ս','վ'=>'վ','տ'=>'տ','ր'=>'ր','ց'=>'ց','ւ'=>'ւ','փ'=>'փ','ք'=>'ք','օ'=>'օ','ֆ'=>'ֆ','և'=>'և','֑'=>'֑','֒'=>'֒','֓'=>'֓','֔'=>'֔','֕'=>'֕','֖'=>'֖','֗'=>'֗','֘'=>'֘','֙'=>'֙','֚'=>'֚','֛'=>'֛','֜'=>'֜','֝'=>'֝','֞'=>'֞','֟'=>'֟','֠'=>'֠','֡'=>'֡','֢'=>'֢','֣'=>'֣','֤'=>'֤','֥'=>'֥','֦'=>'֦','֧'=>'֧','֨'=>'֨','֩'=>'֩','֪'=>'֪','֫'=>'֫','֬'=>'֬','֭'=>'֭','֮'=>'֮','֯'=>'֯','ְ'=>'ְ','ֱ'=>'ֱ','ֲ'=>'ֲ','ֳ'=>'ֳ','ִ'=>'ִ','ֵ'=>'ֵ','ֶ'=>'ֶ','ַ'=>'ַ','ָ'=>'ָ','ֹ'=>'ֹ','ֺ'=>'ֺ','ֻ'=>'ֻ','ּ'=>'ּ','ֽ'=>'ֽ','ֿ'=>'ֿ','ׁ'=>'ׁ','ׂ'=>'ׂ','ׄ'=>'ׄ','ׅ'=>'ׅ','ׇ'=>'ׇ','א'=>'א','ב'=>'ב','ג'=>'ג','ד'=>'ד','ה'=>'ה','ו'=>'ו','ז'=>'ז','ח'=>'ח','ט'=>'ט','י'=>'י','ך'=>'ך','כ'=>'כ','ל'=>'ל','ם'=>'ם','מ'=>'מ','ן'=>'ן','נ'=>'נ','ס'=>'ס','ע'=>'ע','ף'=>'ף','פ'=>'פ','ץ'=>'ץ','צ'=>'צ','ק'=>'ק','ר'=>'ר','ש'=>'ש','ת'=>'ת','װ'=>'װ','ױ'=>'ױ','ײ'=>'ײ','ؐ'=>'ؐ','ؑ'=>'ؑ','ؒ'=>'ؒ','ؓ'=>'ؓ','ؔ'=>'ؔ','ؕ'=>'ؕ','ء'=>'ء','آ'=>'آ','أ'=>'أ','ؤ'=>'ؤ','إ'=>'إ','ئ'=>'ئ','ا'=>'ا','ب'=>'ب','ة'=>'ة','ت'=>'ت','ث'=>'ث','ج'=>'ج','ح'=>'ح','خ'=>'خ','د'=>'د','ذ'=>'ذ','ر'=>'ر','ز'=>'ز','س'=>'س','ش'=>'ش','ص'=>'ص','ض'=>'ض','ط'=>'ط','ظ'=>'ظ','ع'=>'ع','غ'=>'غ','ـ'=>'ـ','ف'=>'ف','ق'=>'ق','ك'=>'ك','ل'=>'ل','م'=>'م','ن'=>'ن','ه'=>'ه','و'=>'و','ى'=>'ى','ي'=>'ي','ً'=>'ً','ٌ'=>'ٌ','ٍ'=>'ٍ','َ'=>'َ','ُ'=>'ُ','ِ'=>'ِ','ّ'=>'ّ','ْ'=>'ْ','ٓ'=>'ٓ','ٔ'=>'ٔ','ٕ'=>'ٕ','ٖ'=>'ٖ','ٗ'=>'ٗ','٘'=>'٘','ٙ'=>'ٙ','ٚ'=>'ٚ','ٛ'=>'ٛ','ٜ'=>'ٜ','ٝ'=>'ٝ','ٞ'=>'ٞ','٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9','ٮ'=>'ٮ','ٯ'=>'ٯ','ٰ'=>'ٰ','ٱ'=>'ٱ','ٲ'=>'ٲ','ٳ'=>'ٳ','ٴ'=>'ٴ','ٵ'=>'ٵ','ٶ'=>'ٶ','ٷ'=>'ٷ','ٸ'=>'ٸ','ٹ'=>'ٹ','ٺ'=>'ٺ','ٻ'=>'ٻ','ټ'=>'ټ','ٽ'=>'ٽ','پ'=>'پ','ٿ'=>'ٿ','ڀ'=>'ڀ','ځ'=>'ځ','ڂ'=>'ڂ','ڃ'=>'ڃ','ڄ'=>'ڄ','څ'=>'څ','چ'=>'چ','ڇ'=>'ڇ','ڈ'=>'ڈ','ډ'=>'ډ','ڊ'=>'ڊ','ڋ'=>'ڋ','ڌ'=>'ڌ','ڍ'=>'ڍ','ڎ'=>'ڎ','ڏ'=>'ڏ','ڐ'=>'ڐ','ڑ'=>'ڑ','ڒ'=>'ڒ','ړ'=>'ړ','ڔ'=>'ڔ','ڕ'=>'ڕ','ږ'=>'ږ','ڗ'=>'ڗ','ژ'=>'ژ','ڙ'=>'ڙ','ښ'=>'ښ','ڛ'=>'ڛ','ڜ'=>'ڜ','ڝ'=>'ڝ','ڞ'=>'ڞ','ڟ'=>'ڟ','ڠ'=>'ڠ','ڡ'=>'ڡ','ڢ'=>'ڢ','ڣ'=>'ڣ','ڤ'=>'ڤ','ڥ'=>'ڥ','ڦ'=>'ڦ','ڧ'=>'ڧ','ڨ'=>'ڨ','ک'=>'ک','ڪ'=>'ڪ','ګ'=>'ګ','ڬ'=>'ڬ','ڭ'=>'ڭ','ڮ'=>'ڮ','گ'=>'گ','ڰ'=>'ڰ','ڱ'=>'ڱ','ڲ'=>'ڲ','ڳ'=>'ڳ','ڴ'=>'ڴ','ڵ'=>'ڵ','ڶ'=>'ڶ','ڷ'=>'ڷ','ڸ'=>'ڸ','ڹ'=>'ڹ','ں'=>'ں','ڻ'=>'ڻ','ڼ'=>'ڼ','ڽ'=>'ڽ','ھ'=>'ھ','ڿ'=>'ڿ','ۀ'=>'ۀ','ہ'=>'ہ','ۂ'=>'ۂ','ۃ'=>'ۃ','ۄ'=>'ۄ','ۅ'=>'ۅ','ۆ'=>'ۆ','ۇ'=>'ۇ','ۈ'=>'ۈ','ۉ'=>'ۉ','ۊ'=>'ۊ','ۋ'=>'ۋ','ی'=>'ی','ۍ'=>'ۍ','ێ'=>'ێ','ۏ'=>'ۏ','ې'=>'ې','ۑ'=>'ۑ','ے'=>'ے','ۓ'=>'ۓ','ە'=>'ە','ۖ'=>'ۖ','ۗ'=>'ۗ','ۘ'=>'ۘ','ۙ'=>'ۙ','ۚ'=>'ۚ','ۛ'=>'ۛ','ۜ'=>'ۜ','۞'=>'۞','۟'=>'۟','۠'=>'۠','ۡ'=>'ۡ','ۢ'=>'ۢ','ۣ'=>'ۣ','ۤ'=>'ۤ','ۥ'=>'ۥ','ۦ'=>'ۦ','ۧ'=>'ۧ','ۨ'=>'ۨ','۪'=>'۪','۫'=>'۫','۬'=>'۬','ۭ'=>'ۭ','ۮ'=>'ۮ','ۯ'=>'ۯ','۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9','ۺ'=>'ۺ','ۻ'=>'ۻ','ۼ'=>'ۼ','ۿ'=>'ۿ','ܐ'=>'ܐ','ܑ'=>'ܑ','ܒ'=>'ܒ','ܓ'=>'ܓ','ܔ'=>'ܔ','ܕ'=>'ܕ','ܖ'=>'ܖ','ܗ'=>'ܗ','ܘ'=>'ܘ','ܙ'=>'ܙ','ܚ'=>'ܚ','ܛ'=>'ܛ','ܜ'=>'ܜ','ܝ'=>'ܝ','ܞ'=>'ܞ','ܟ'=>'ܟ','ܠ'=>'ܠ','ܡ'=>'ܡ','ܢ'=>'ܢ','ܣ'=>'ܣ','ܤ'=>'ܤ','ܥ'=>'ܥ','ܦ'=>'ܦ','ܧ'=>'ܧ','ܨ'=>'ܨ','ܩ'=>'ܩ','ܪ'=>'ܪ','ܫ'=>'ܫ','ܬ'=>'ܬ','ܭ'=>'ܭ','ܮ'=>'ܮ','ܯ'=>'ܯ','ܰ'=>'ܰ','ܱ'=>'ܱ','ܲ'=>'ܲ','ܳ'=>'ܳ','ܴ'=>'ܴ','ܵ'=>'ܵ','ܶ'=>'ܶ','ܷ'=>'ܷ','ܸ'=>'ܸ','ܹ'=>'ܹ','ܺ'=>'ܺ','ܻ'=>'ܻ','ܼ'=>'ܼ','ܽ'=>'ܽ','ܾ'=>'ܾ','ܿ'=>'ܿ','݀'=>'݀','݁'=>'݁','݂'=>'݂','݃'=>'݃','݄'=>'݄','݅'=>'݅','݆'=>'݆','݇'=>'݇','݈'=>'݈','݉'=>'݉','݊'=>'݊','ݍ'=>'ݍ','ݎ'=>'ݎ','ݏ'=>'ݏ','ݐ'=>'ݐ','ݑ'=>'ݑ','ݒ'=>'ݒ','ݓ'=>'ݓ','ݔ'=>'ݔ','ݕ'=>'ݕ','ݖ'=>'ݖ','ݗ'=>'ݗ','ݘ'=>'ݘ','ݙ'=>'ݙ','ݚ'=>'ݚ','ݛ'=>'ݛ','ݜ'=>'ݜ','ݝ'=>'ݝ','ݞ'=>'ݞ','ݟ'=>'ݟ','ݠ'=>'ݠ','ݡ'=>'ݡ','ݢ'=>'ݢ','ݣ'=>'ݣ','ݤ'=>'ݤ','ݥ'=>'ݥ','ݦ'=>'ݦ','ݧ'=>'ݧ','ݨ'=>'ݨ','ݩ'=>'ݩ','ݪ'=>'ݪ','ݫ'=>'ݫ','ݬ'=>'ݬ','ݭ'=>'ݭ','ހ'=>'ހ','ށ'=>'ށ','ނ'=>'ނ','ރ'=>'ރ','ބ'=>'ބ','ޅ'=>'ޅ','ކ'=>'ކ','އ'=>'އ','ވ'=>'ވ','މ'=>'މ','ފ'=>'ފ','ދ'=>'ދ','ތ'=>'ތ','ލ'=>'ލ','ގ'=>'ގ','ޏ'=>'ޏ','ސ'=>'ސ','ޑ'=>'ޑ','ޒ'=>'ޒ','ޓ'=>'ޓ','ޔ'=>'ޔ','ޕ'=>'ޕ','ޖ'=>'ޖ','ޗ'=>'ޗ','ޘ'=>'ޘ','ޙ'=>'ޙ','ޚ'=>'ޚ','ޛ'=>'ޛ','ޜ'=>'ޜ','ޝ'=>'ޝ','ޞ'=>'ޞ','ޟ'=>'ޟ','ޠ'=>'ޠ','ޡ'=>'ޡ','ޢ'=>'ޢ','ޣ'=>'ޣ','ޤ'=>'ޤ','ޥ'=>'ޥ','ަ'=>'ަ','ާ'=>'ާ','ި'=>'ި','ީ'=>'ީ','ު'=>'ު','ޫ'=>'ޫ','ެ'=>'ެ','ޭ'=>'ޭ','ޮ'=>'ޮ','ޯ'=>'ޯ','ް'=>'ް','ޱ'=>'ޱ','߀'=>'0','߁'=>'1','߂'=>'2','߃'=>'3','߄'=>'4','߅'=>'5','߆'=>'6','߇'=>'7','߈'=>'8','߉'=>'9','ߊ'=>'ߊ','ߋ'=>'ߋ','ߌ'=>'ߌ','ߍ'=>'ߍ','ߎ'=>'ߎ','ߏ'=>'ߏ','ߐ'=>'ߐ','ߑ'=>'ߑ','ߒ'=>'ߒ','ߓ'=>'ߓ','ߔ'=>'ߔ','ߕ'=>'ߕ','ߖ'=>'ߖ','ߗ'=>'ߗ','ߘ'=>'ߘ','ߙ'=>'ߙ','ߚ'=>'ߚ','ߛ'=>'ߛ','ߜ'=>'ߜ','ߝ'=>'ߝ','ߞ'=>'ߞ','ߟ'=>'ߟ','ߠ'=>'ߠ','ߡ'=>'ߡ','ߢ'=>'ߢ','ߣ'=>'ߣ','ߤ'=>'ߤ','ߥ'=>'ߥ','ߦ'=>'ߦ','ߧ'=>'ߧ','ߨ'=>'ߨ','ߩ'=>'ߩ','ߪ'=>'ߪ','߫'=>'߫','߬'=>'߬','߭'=>'߭','߮'=>'߮','߯'=>'߯','߰'=>'߰','߱'=>'߱','߲'=>'߲','߳'=>'߳','ߴ'=>'ߴ','ߵ'=>'ߵ','ߺ'=>'ߺ'); diff --git a/includes/utf/data/search_indexer_1.php b/includes/utf/data/search_indexer_1.php new file mode 100644 index 0000000..4dd142b --- /dev/null +++ b/includes/utf/data/search_indexer_1.php @@ -0,0 +1 @@ +'ँ','ं'=>'ं','ः'=>'ः','ऄ'=>'ऄ','अ'=>'अ','आ'=>'आ','इ'=>'इ','ई'=>'ई','उ'=>'उ','ऊ'=>'ऊ','ऋ'=>'ऋ','ऌ'=>'ऌ','ऍ'=>'ऍ','ऎ'=>'ऎ','ए'=>'ए','ऐ'=>'ऐ','ऑ'=>'ऑ','ऒ'=>'ऒ','ओ'=>'ओ','औ'=>'औ','क'=>'क','ख'=>'ख','ग'=>'ग','घ'=>'घ','ङ'=>'ङ','च'=>'च','छ'=>'छ','ज'=>'ज','झ'=>'झ','ञ'=>'ञ','ट'=>'ट','ठ'=>'ठ','ड'=>'ड','ढ'=>'ढ','ण'=>'ण','त'=>'त','थ'=>'थ','द'=>'द','ध'=>'ध','न'=>'न','ऩ'=>'ऩ','प'=>'प','फ'=>'फ','ब'=>'ब','भ'=>'भ','म'=>'म','य'=>'य','र'=>'र','ऱ'=>'ऱ','ल'=>'ल','ळ'=>'ळ','ऴ'=>'ऴ','व'=>'व','श'=>'श','ष'=>'ष','स'=>'स','ह'=>'ह','़'=>'़','ऽ'=>'ऽ','ा'=>'ा','ि'=>'ि','ी'=>'ी','ु'=>'ु','ू'=>'ू','ृ'=>'ृ','ॄ'=>'ॄ','ॅ'=>'ॅ','ॆ'=>'ॆ','े'=>'े','ै'=>'ै','ॉ'=>'ॉ','ॊ'=>'ॊ','ो'=>'ो','ौ'=>'ौ','्'=>'्','ॐ'=>'ॐ','॑'=>'॑','॒'=>'॒','॓'=>'॓','॔'=>'॔','क़'=>'क़','ख़'=>'ख़','ग़'=>'ग़','ज़'=>'ज़','ड़'=>'ड़','ढ़'=>'ढ़','फ़'=>'फ़','य़'=>'य़','ॠ'=>'ॠ','ॡ'=>'ॡ','ॢ'=>'ॢ','ॣ'=>'ॣ','०'=>'0','१'=>'1','२'=>'2','३'=>'3','४'=>'4','५'=>'5','६'=>'6','७'=>'7','८'=>'8','९'=>'9','ॻ'=>'ॻ','ॼ'=>'ॼ','ॽ'=>'ॽ','ॾ'=>'ॾ','ॿ'=>'ॿ','ঁ'=>'ঁ','ং'=>'ং','ঃ'=>'ঃ','অ'=>'অ','আ'=>'আ','ই'=>'ই','ঈ'=>'ঈ','উ'=>'উ','ঊ'=>'ঊ','ঋ'=>'ঋ','ঌ'=>'ঌ','এ'=>'এ','ঐ'=>'ঐ','ও'=>'ও','ঔ'=>'ঔ','ক'=>'ক','খ'=>'খ','গ'=>'গ','ঘ'=>'ঘ','ঙ'=>'ঙ','চ'=>'চ','ছ'=>'ছ','জ'=>'জ','ঝ'=>'ঝ','ঞ'=>'ঞ','ট'=>'ট','ঠ'=>'ঠ','ড'=>'ড','ঢ'=>'ঢ','ণ'=>'ণ','ত'=>'ত','থ'=>'থ','দ'=>'দ','ধ'=>'ধ','ন'=>'ন','প'=>'প','ফ'=>'ফ','ব'=>'ব','ভ'=>'ভ','ম'=>'ম','য'=>'য','র'=>'র','ল'=>'ল','শ'=>'শ','ষ'=>'ষ','স'=>'স','হ'=>'হ','়'=>'়','ঽ'=>'ঽ','া'=>'া','ি'=>'ি','ী'=>'ী','ু'=>'ু','ূ'=>'ূ','ৃ'=>'ৃ','ৄ'=>'ৄ','ে'=>'ে','ৈ'=>'ৈ','ো'=>'ো','ৌ'=>'ৌ','্'=>'্','ৎ'=>'ৎ','ৗ'=>'ৗ','ড়'=>'ড়','ঢ়'=>'ঢ়','য়'=>'য়','ৠ'=>'ৠ','ৡ'=>'ৡ','ৢ'=>'ৢ','ৣ'=>'ৣ','০'=>'0','১'=>'1','২'=>'2','৩'=>'3','৪'=>'4','৫'=>'5','৬'=>'6','৭'=>'7','৮'=>'8','৯'=>'9','ৰ'=>'ৰ','ৱ'=>'ৱ','৴'=>'1','৵'=>'2','৶'=>'3','৷'=>'4','৸'=>'৸','৹'=>'16','ਁ'=>'ਁ','ਂ'=>'ਂ','ਃ'=>'ਃ','ਅ'=>'ਅ','ਆ'=>'ਆ','ਇ'=>'ਇ','ਈ'=>'ਈ','ਉ'=>'ਉ','ਊ'=>'ਊ','ਏ'=>'ਏ','ਐ'=>'ਐ','ਓ'=>'ਓ','ਔ'=>'ਔ','ਕ'=>'ਕ','ਖ'=>'ਖ','ਗ'=>'ਗ','ਘ'=>'ਘ','ਙ'=>'ਙ','ਚ'=>'ਚ','ਛ'=>'ਛ','ਜ'=>'ਜ','ਝ'=>'ਝ','ਞ'=>'ਞ','ਟ'=>'ਟ','ਠ'=>'ਠ','ਡ'=>'ਡ','ਢ'=>'ਢ','ਣ'=>'ਣ','ਤ'=>'ਤ','ਥ'=>'ਥ','ਦ'=>'ਦ','ਧ'=>'ਧ','ਨ'=>'ਨ','ਪ'=>'ਪ','ਫ'=>'ਫ','ਬ'=>'ਬ','ਭ'=>'ਭ','ਮ'=>'ਮ','ਯ'=>'ਯ','ਰ'=>'ਰ','ਲ'=>'ਲ','ਲ਼'=>'ਲ਼','ਵ'=>'ਵ','ਸ਼'=>'ਸ਼','ਸ'=>'ਸ','ਹ'=>'ਹ','਼'=>'਼','ਾ'=>'ਾ','ਿ'=>'ਿ','ੀ'=>'ੀ','ੁ'=>'ੁ','ੂ'=>'ੂ','ੇ'=>'ੇ','ੈ'=>'ੈ','ੋ'=>'ੋ','ੌ'=>'ੌ','੍'=>'੍','ਖ਼'=>'ਖ਼','ਗ਼'=>'ਗ਼','ਜ਼'=>'ਜ਼','ੜ'=>'ੜ','ਫ਼'=>'ਫ਼','੦'=>'0','੧'=>'1','੨'=>'2','੩'=>'3','੪'=>'4','੫'=>'5','੬'=>'6','੭'=>'7','੮'=>'8','੯'=>'9','ੰ'=>'ੰ','ੱ'=>'ੱ','ੲ'=>'ੲ','ੳ'=>'ੳ','ੴ'=>'ੴ','ઁ'=>'ઁ','ં'=>'ં','ઃ'=>'ઃ','અ'=>'અ','આ'=>'આ','ઇ'=>'ઇ','ઈ'=>'ઈ','ઉ'=>'ઉ','ઊ'=>'ઊ','ઋ'=>'ઋ','ઌ'=>'ઌ','ઍ'=>'ઍ','એ'=>'એ','ઐ'=>'ઐ','ઑ'=>'ઑ','ઓ'=>'ઓ','ઔ'=>'ઔ','ક'=>'ક','ખ'=>'ખ','ગ'=>'ગ','ઘ'=>'ઘ','ઙ'=>'ઙ','ચ'=>'ચ','છ'=>'છ','જ'=>'જ','ઝ'=>'ઝ','ઞ'=>'ઞ','ટ'=>'ટ','ઠ'=>'ઠ','ડ'=>'ડ','ઢ'=>'ઢ','ણ'=>'ણ','ત'=>'ત','થ'=>'થ','દ'=>'દ','ધ'=>'ધ','ન'=>'ન','પ'=>'પ','ફ'=>'ફ','બ'=>'બ','ભ'=>'ભ','મ'=>'મ','ય'=>'ય','ર'=>'ર','લ'=>'લ','ળ'=>'ળ','વ'=>'વ','શ'=>'શ','ષ'=>'ષ','સ'=>'સ','હ'=>'હ','઼'=>'઼','ઽ'=>'ઽ','ા'=>'ા','િ'=>'િ','ી'=>'ી','ુ'=>'ુ','ૂ'=>'ૂ','ૃ'=>'ૃ','ૄ'=>'ૄ','ૅ'=>'ૅ','ે'=>'ે','ૈ'=>'ૈ','ૉ'=>'ૉ','ો'=>'ો','ૌ'=>'ૌ','્'=>'્','ૐ'=>'ૐ','ૠ'=>'ૠ','ૡ'=>'ૡ','ૢ'=>'ૢ','ૣ'=>'ૣ','૦'=>'0','૧'=>'1','૨'=>'2','૩'=>'3','૪'=>'4','૫'=>'5','૬'=>'6','૭'=>'7','૮'=>'8','૯'=>'9','ଁ'=>'ଁ','ଂ'=>'ଂ','ଃ'=>'ଃ','ଅ'=>'ଅ','ଆ'=>'ଆ','ଇ'=>'ଇ','ଈ'=>'ଈ','ଉ'=>'ଉ','ଊ'=>'ଊ','ଋ'=>'ଋ','ଌ'=>'ଌ','ଏ'=>'ଏ','ଐ'=>'ଐ','ଓ'=>'ଓ','ଔ'=>'ଔ','କ'=>'କ','ଖ'=>'ଖ','ଗ'=>'ଗ','ଘ'=>'ଘ','ଙ'=>'ଙ','ଚ'=>'ଚ','ଛ'=>'ଛ','ଜ'=>'ଜ','ଝ'=>'ଝ','ଞ'=>'ଞ','ଟ'=>'ଟ','ଠ'=>'ଠ','ଡ'=>'ଡ','ଢ'=>'ଢ','ଣ'=>'ଣ','ତ'=>'ତ','ଥ'=>'ଥ','ଦ'=>'ଦ','ଧ'=>'ଧ','ନ'=>'ନ','ପ'=>'ପ','ଫ'=>'ଫ','ବ'=>'ବ','ଭ'=>'ଭ','ମ'=>'ମ','ଯ'=>'ଯ','ର'=>'ର','ଲ'=>'ଲ','ଳ'=>'ଳ','ଵ'=>'ଵ','ଶ'=>'ଶ','ଷ'=>'ଷ','ସ'=>'ସ','ହ'=>'ହ','଼'=>'଼','ଽ'=>'ଽ','ା'=>'ା','ି'=>'ି','ୀ'=>'ୀ','ୁ'=>'ୁ','ୂ'=>'ୂ','ୃ'=>'ୃ','େ'=>'େ','ୈ'=>'ୈ','ୋ'=>'ୋ','ୌ'=>'ୌ','୍'=>'୍','ୖ'=>'ୖ','ୗ'=>'ୗ','ଡ଼'=>'ଡ଼','ଢ଼'=>'ଢ଼','ୟ'=>'ୟ','ୠ'=>'ୠ','ୡ'=>'ୡ','୦'=>'0','୧'=>'1','୨'=>'2','୩'=>'3','୪'=>'4','୫'=>'5','୬'=>'6','୭'=>'7','୮'=>'8','୯'=>'9','ୱ'=>'ୱ','ஂ'=>'ஂ','ஃ'=>'ஃ','அ'=>'அ','ஆ'=>'ஆ','இ'=>'இ','ஈ'=>'ஈ','உ'=>'உ','ஊ'=>'ஊ','எ'=>'எ','ஏ'=>'ஏ','ஐ'=>'ஐ','ஒ'=>'ஒ','ஓ'=>'ஓ','ஔ'=>'ஔ','க'=>'க','ங'=>'ங','ச'=>'ச','ஜ'=>'ஜ','ஞ'=>'ஞ','ட'=>'ட','ண'=>'ண','த'=>'த','ந'=>'ந','ன'=>'ன','ப'=>'ப','ம'=>'ம','ய'=>'ய','ர'=>'ர','ற'=>'ற','ல'=>'ல','ள'=>'ள','ழ'=>'ழ','வ'=>'வ','ஶ'=>'ஶ','ஷ'=>'ஷ','ஸ'=>'ஸ','ஹ'=>'ஹ','ா'=>'ா','ி'=>'ி','ீ'=>'ீ','ு'=>'ு','ூ'=>'ூ','ெ'=>'ெ','ே'=>'ே','ை'=>'ை','ொ'=>'ொ','ோ'=>'ோ','ௌ'=>'ௌ','்'=>'்','ௗ'=>'ௗ','௦'=>'0','௧'=>'1','௨'=>'2','௩'=>'3','௪'=>'4','௫'=>'5','௬'=>'6','௭'=>'7','௮'=>'8','௯'=>'9','௰'=>'10','௱'=>'100','௲'=>'1000','ఁ'=>'ఁ','ం'=>'ం','ః'=>'ః','అ'=>'అ','ఆ'=>'ఆ','ఇ'=>'ఇ','ఈ'=>'ఈ','ఉ'=>'ఉ','ఊ'=>'ఊ','ఋ'=>'ఋ','ఌ'=>'ఌ','ఎ'=>'ఎ','ఏ'=>'ఏ','ఐ'=>'ఐ','ఒ'=>'ఒ','ఓ'=>'ఓ','ఔ'=>'ఔ','క'=>'క','ఖ'=>'ఖ','గ'=>'గ','ఘ'=>'ఘ','ఙ'=>'ఙ','చ'=>'చ','ఛ'=>'ఛ','జ'=>'జ','ఝ'=>'ఝ','ఞ'=>'ఞ','ట'=>'ట','ఠ'=>'ఠ','డ'=>'డ','ఢ'=>'ఢ','ణ'=>'ణ','త'=>'త','థ'=>'థ','ద'=>'ద','ధ'=>'ధ','న'=>'న','ప'=>'ప','ఫ'=>'ఫ','బ'=>'బ','భ'=>'భ','మ'=>'మ','య'=>'య','ర'=>'ర','ఱ'=>'ఱ','ల'=>'ల','ళ'=>'ళ','వ'=>'వ','శ'=>'శ','ష'=>'ష','స'=>'స','హ'=>'హ','ా'=>'ా','ి'=>'ి','ీ'=>'ీ','ు'=>'ు','ూ'=>'ూ','ృ'=>'ృ','ౄ'=>'ౄ','ె'=>'ె','ే'=>'ే','ై'=>'ై','ొ'=>'ొ','ో'=>'ో','ౌ'=>'ౌ','్'=>'్','ౕ'=>'ౕ','ౖ'=>'ౖ','ౠ'=>'ౠ','ౡ'=>'ౡ','౦'=>'0','౧'=>'1','౨'=>'2','౩'=>'3','౪'=>'4','౫'=>'5','౬'=>'6','౭'=>'7','౮'=>'8','౯'=>'9','ಂ'=>'ಂ','ಃ'=>'ಃ','ಅ'=>'ಅ','ಆ'=>'ಆ','ಇ'=>'ಇ','ಈ'=>'ಈ','ಉ'=>'ಉ','ಊ'=>'ಊ','ಋ'=>'ಋ','ಌ'=>'ಌ','ಎ'=>'ಎ','ಏ'=>'ಏ','ಐ'=>'ಐ','ಒ'=>'ಒ','ಓ'=>'ಓ','ಔ'=>'ಔ','ಕ'=>'ಕ','ಖ'=>'ಖ','ಗ'=>'ಗ','ಘ'=>'ಘ','ಙ'=>'ಙ','ಚ'=>'ಚ','ಛ'=>'ಛ','ಜ'=>'ಜ','ಝ'=>'ಝ','ಞ'=>'ಞ','ಟ'=>'ಟ','ಠ'=>'ಠ','ಡ'=>'ಡ','ಢ'=>'ಢ','ಣ'=>'ಣ','ತ'=>'ತ','ಥ'=>'ಥ','ದ'=>'ದ','ಧ'=>'ಧ','ನ'=>'ನ','ಪ'=>'ಪ','ಫ'=>'ಫ','ಬ'=>'ಬ','ಭ'=>'ಭ','ಮ'=>'ಮ','ಯ'=>'ಯ','ರ'=>'ರ','ಱ'=>'ಱ','ಲ'=>'ಲ','ಳ'=>'ಳ','ವ'=>'ವ','ಶ'=>'ಶ','ಷ'=>'ಷ','ಸ'=>'ಸ','ಹ'=>'ಹ','಼'=>'಼','ಽ'=>'ಽ','ಾ'=>'ಾ','ಿ'=>'ಿ','ೀ'=>'ೀ','ು'=>'ು','ೂ'=>'ೂ','ೃ'=>'ೃ','ೄ'=>'ೄ','ೆ'=>'ೆ','ೇ'=>'ೇ','ೈ'=>'ೈ','ೊ'=>'ೊ','ೋ'=>'ೋ','ೌ'=>'ೌ','್'=>'್','ೕ'=>'ೕ','ೖ'=>'ೖ','ೞ'=>'ೞ','ೠ'=>'ೠ','ೡ'=>'ೡ','ೢ'=>'ೢ','ೣ'=>'ೣ','೦'=>'0','೧'=>'1','೨'=>'2','೩'=>'3','೪'=>'4','೫'=>'5','೬'=>'6','೭'=>'7','೮'=>'8','೯'=>'9','ം'=>'ം','ഃ'=>'ഃ','അ'=>'അ','ആ'=>'ആ','ഇ'=>'ഇ','ഈ'=>'ഈ','ഉ'=>'ഉ','ഊ'=>'ഊ','ഋ'=>'ഋ','ഌ'=>'ഌ','എ'=>'എ','ഏ'=>'ഏ','ഐ'=>'ഐ','ഒ'=>'ഒ','ഓ'=>'ഓ','ഔ'=>'ഔ','ക'=>'ക','ഖ'=>'ഖ','ഗ'=>'ഗ','ഘ'=>'ഘ','ങ'=>'ങ','ച'=>'ച','ഛ'=>'ഛ','ജ'=>'ജ','ഝ'=>'ഝ','ഞ'=>'ഞ','ട'=>'ട','ഠ'=>'ഠ','ഡ'=>'ഡ','ഢ'=>'ഢ','ണ'=>'ണ','ത'=>'ത','ഥ'=>'ഥ','ദ'=>'ദ','ധ'=>'ധ','ന'=>'ന','പ'=>'പ','ഫ'=>'ഫ','ബ'=>'ബ','ഭ'=>'ഭ','മ'=>'മ','യ'=>'യ','ര'=>'ര','റ'=>'റ','ല'=>'ല','ള'=>'ള','ഴ'=>'ഴ','വ'=>'വ','ശ'=>'ശ','ഷ'=>'ഷ','സ'=>'സ','ഹ'=>'ഹ','ാ'=>'ാ','ി'=>'ി','ീ'=>'ീ','ു'=>'ു','ൂ'=>'ൂ','ൃ'=>'ൃ','െ'=>'െ','േ'=>'േ','ൈ'=>'ൈ','ൊ'=>'ൊ','ോ'=>'ോ','ൌ'=>'ൌ','്'=>'്','ൗ'=>'ൗ','ൠ'=>'ൠ','ൡ'=>'ൡ','൦'=>'0','൧'=>'1','൨'=>'2','൩'=>'3','൪'=>'4','൫'=>'5','൬'=>'6','൭'=>'7','൮'=>'8','൯'=>'9','ං'=>'ං','ඃ'=>'ඃ','අ'=>'අ','ආ'=>'ආ','ඇ'=>'ඇ','ඈ'=>'ඈ','ඉ'=>'ඉ','ඊ'=>'ඊ','උ'=>'උ','ඌ'=>'ඌ','ඍ'=>'ඍ','ඎ'=>'ඎ','ඏ'=>'ඏ','ඐ'=>'ඐ','එ'=>'එ','ඒ'=>'ඒ','ඓ'=>'ඓ','ඔ'=>'ඔ','ඕ'=>'ඕ','ඖ'=>'ඖ','ක'=>'ක','ඛ'=>'ඛ','ග'=>'ග','ඝ'=>'ඝ','ඞ'=>'ඞ','ඟ'=>'ඟ','ච'=>'ච','ඡ'=>'ඡ','ජ'=>'ජ','ඣ'=>'ඣ','ඤ'=>'ඤ','ඥ'=>'ඥ','ඦ'=>'ඦ','ට'=>'ට','ඨ'=>'ඨ','ඩ'=>'ඩ','ඪ'=>'ඪ','ණ'=>'ණ','ඬ'=>'ඬ','ත'=>'ත','ථ'=>'ථ','ද'=>'ද','ධ'=>'ධ','න'=>'න','ඳ'=>'ඳ','ප'=>'ප','ඵ'=>'ඵ','බ'=>'බ','භ'=>'භ','ම'=>'ම','ඹ'=>'ඹ','ය'=>'ය','ර'=>'ර','ල'=>'ල','ව'=>'ව','ශ'=>'ශ','ෂ'=>'ෂ','ස'=>'ස','හ'=>'හ','ළ'=>'ළ','ෆ'=>'ෆ','්'=>'්','ා'=>'ා','ැ'=>'ැ','ෑ'=>'ෑ','ි'=>'ි','ී'=>'ී','ු'=>'ු','ූ'=>'ූ','ෘ'=>'ෘ','ෙ'=>'ෙ','ේ'=>'ේ','ෛ'=>'ෛ','ො'=>'ො','ෝ'=>'ෝ','ෞ'=>'ෞ','ෟ'=>'ෟ','ෲ'=>'ෲ','ෳ'=>'ෳ','ก'=>'ก','ข'=>'ข','ฃ'=>'ฃ','ค'=>'ค','ฅ'=>'ฅ','ฆ'=>'ฆ','ง'=>'ง','จ'=>'จ','ฉ'=>'ฉ','ช'=>'ช','ซ'=>'ซ','ฌ'=>'ฌ','ญ'=>'ญ','ฎ'=>'ฎ','ฏ'=>'ฏ','ฐ'=>'ฐ','ฑ'=>'ฑ','ฒ'=>'ฒ','ณ'=>'ณ','ด'=>'ด','ต'=>'ต','ถ'=>'ถ','ท'=>'ท','ธ'=>'ธ','น'=>'น','บ'=>'บ','ป'=>'ป','ผ'=>'ผ','ฝ'=>'ฝ','พ'=>'พ','ฟ'=>'ฟ','ภ'=>'ภ','ม'=>'ม','ย'=>'ย','ร'=>'ร','ฤ'=>'ฤ','ล'=>'ล','ฦ'=>'ฦ','ว'=>'ว','ศ'=>'ศ','ษ'=>'ษ','ส'=>'ส','ห'=>'ห','ฬ'=>'ฬ','อ'=>'อ','ฮ'=>'ฮ','ฯ'=>'ฯ','ะ'=>'ะ','ั'=>'ั','า'=>'า','ำ'=>'ำ','ิ'=>'ิ','ี'=>'ี','ึ'=>'ึ','ื'=>'ื','ุ'=>'ุ','ู'=>'ู','ฺ'=>'ฺ','เ'=>'เ','แ'=>'แ','โ'=>'โ','ใ'=>'ใ','ไ'=>'ไ','ๅ'=>'ๅ','ๆ'=>'ๆ','็'=>'็','่'=>'่','้'=>'้','๊'=>'๊','๋'=>'๋','์'=>'์','ํ'=>'ํ','๎'=>'๎','๐'=>'0','๑'=>'1','๒'=>'2','๓'=>'3','๔'=>'4','๕'=>'5','๖'=>'6','๗'=>'7','๘'=>'8','๙'=>'9','ກ'=>'ກ','ຂ'=>'ຂ','ຄ'=>'ຄ','ງ'=>'ງ','ຈ'=>'ຈ','ຊ'=>'ຊ','ຍ'=>'ຍ','ດ'=>'ດ','ຕ'=>'ຕ','ຖ'=>'ຖ','ທ'=>'ທ','ນ'=>'ນ','ບ'=>'ບ','ປ'=>'ປ','ຜ'=>'ຜ','ຝ'=>'ຝ','ພ'=>'ພ','ຟ'=>'ຟ','ມ'=>'ມ','ຢ'=>'ຢ','ຣ'=>'ຣ','ລ'=>'ລ','ວ'=>'ວ','ສ'=>'ສ','ຫ'=>'ຫ','ອ'=>'ອ','ຮ'=>'ຮ','ຯ'=>'ຯ','ະ'=>'ະ','ັ'=>'ັ','າ'=>'າ','ຳ'=>'ຳ','ິ'=>'ິ','ີ'=>'ີ','ຶ'=>'ຶ','ື'=>'ື','ຸ'=>'ຸ','ູ'=>'ູ','ົ'=>'ົ','ຼ'=>'ຼ','ຽ'=>'ຽ','ເ'=>'ເ','ແ'=>'ແ','ໂ'=>'ໂ','ໃ'=>'ໃ','ໄ'=>'ໄ','ໆ'=>'ໆ','່'=>'່','້'=>'້','໊'=>'໊','໋'=>'໋','໌'=>'໌','ໍ'=>'ໍ','໐'=>'0','໑'=>'1','໒'=>'2','໓'=>'3','໔'=>'4','໕'=>'5','໖'=>'6','໗'=>'7','໘'=>'8','໙'=>'9','ໜ'=>'ໜ','ໝ'=>'ໝ','ༀ'=>'ༀ','༘'=>'༘','༙'=>'༙','༠'=>'0','༡'=>'1','༢'=>'2','༣'=>'3','༤'=>'4','༥'=>'5','༦'=>'6','༧'=>'7','༨'=>'8','༩'=>'9','༪'=>'1/2','༫'=>'3/2','༬'=>'5/2','༭'=>'7/2','༮'=>'9/2','༯'=>'11/2','༰'=>'13/2','༱'=>'15/2','༲'=>'17/2','༳'=>'-1/2','༵'=>'༵','༷'=>'༷','༹'=>'༹','༾'=>'༾','༿'=>'༿','ཀ'=>'ཀ','ཁ'=>'ཁ','ག'=>'ག','གྷ'=>'གྷ','ང'=>'ང','ཅ'=>'ཅ','ཆ'=>'ཆ','ཇ'=>'ཇ','ཉ'=>'ཉ','ཊ'=>'ཊ','ཋ'=>'ཋ','ཌ'=>'ཌ','ཌྷ'=>'ཌྷ','ཎ'=>'ཎ','ཏ'=>'ཏ','ཐ'=>'ཐ','ད'=>'ད','དྷ'=>'དྷ','ན'=>'ན','པ'=>'པ','ཕ'=>'ཕ','བ'=>'བ','བྷ'=>'བྷ','མ'=>'མ','ཙ'=>'ཙ','ཚ'=>'ཚ','ཛ'=>'ཛ','ཛྷ'=>'ཛྷ','ཝ'=>'ཝ','ཞ'=>'ཞ','ཟ'=>'ཟ','འ'=>'འ','ཡ'=>'ཡ','ར'=>'ར','ལ'=>'ལ','ཤ'=>'ཤ','ཥ'=>'ཥ','ས'=>'ས','ཧ'=>'ཧ','ཨ'=>'ཨ','ཀྵ'=>'ཀྵ','ཪ'=>'ཪ','ཱ'=>'ཱ','ི'=>'ི','ཱི'=>'ཱི','ུ'=>'ུ','ཱུ'=>'ཱུ','ྲྀ'=>'ྲྀ','ཷ'=>'ཷ','ླྀ'=>'ླྀ','ཹ'=>'ཹ','ེ'=>'ེ','ཻ'=>'ཻ','ོ'=>'ོ','ཽ'=>'ཽ','ཾ'=>'ཾ','ཿ'=>'ཿ','ྀ'=>'ྀ','ཱྀ'=>'ཱྀ','ྂ'=>'ྂ','ྃ'=>'ྃ','྄'=>'྄','྆'=>'྆','྇'=>'྇','ྈ'=>'ྈ','ྉ'=>'ྉ','ྊ'=>'ྊ','ྋ'=>'ྋ','ྐ'=>'ྐ','ྑ'=>'ྑ','ྒ'=>'ྒ','ྒྷ'=>'ྒྷ','ྔ'=>'ྔ','ྕ'=>'ྕ','ྖ'=>'ྖ','ྗ'=>'ྗ','ྙ'=>'ྙ','ྚ'=>'ྚ','ྛ'=>'ྛ','ྜ'=>'ྜ','ྜྷ'=>'ྜྷ','ྞ'=>'ྞ','ྟ'=>'ྟ','ྠ'=>'ྠ','ྡ'=>'ྡ','ྡྷ'=>'ྡྷ','ྣ'=>'ྣ','ྤ'=>'ྤ','ྥ'=>'ྥ','ྦ'=>'ྦ','ྦྷ'=>'ྦྷ','ྨ'=>'ྨ','ྩ'=>'ྩ','ྪ'=>'ྪ','ྫ'=>'ྫ','ྫྷ'=>'ྫྷ','ྭ'=>'ྭ','ྮ'=>'ྮ','ྯ'=>'ྯ','ྰ'=>'ྰ','ྱ'=>'ྱ','ྲ'=>'ྲ','ླ'=>'ླ','ྴ'=>'ྴ','ྵ'=>'ྵ','ྶ'=>'ྶ','ྷ'=>'ྷ','ྸ'=>'ྸ','ྐྵ'=>'ྐྵ','ྺ'=>'ྺ','ྻ'=>'ྻ','ྼ'=>'ྼ','࿆'=>'࿆'); diff --git a/includes/utf/data/search_indexer_19.php b/includes/utf/data/search_indexer_19.php new file mode 100644 index 0000000..d10d09f --- /dev/null +++ b/includes/utf/data/search_indexer_19.php @@ -0,0 +1 @@ +'龻'); diff --git a/includes/utf/data/search_indexer_2.php b/includes/utf/data/search_indexer_2.php new file mode 100644 index 0000000..5b5f034 --- /dev/null +++ b/includes/utf/data/search_indexer_2.php @@ -0,0 +1 @@ +'က','ခ'=>'ခ','ဂ'=>'ဂ','ဃ'=>'ဃ','င'=>'င','စ'=>'စ','ဆ'=>'ဆ','ဇ'=>'ဇ','ဈ'=>'ဈ','ဉ'=>'ဉ','ည'=>'ည','ဋ'=>'ဋ','ဌ'=>'ဌ','ဍ'=>'ဍ','ဎ'=>'ဎ','ဏ'=>'ဏ','တ'=>'တ','ထ'=>'ထ','ဒ'=>'ဒ','ဓ'=>'ဓ','န'=>'န','ပ'=>'ပ','ဖ'=>'ဖ','ဗ'=>'ဗ','ဘ'=>'ဘ','မ'=>'မ','ယ'=>'ယ','ရ'=>'ရ','လ'=>'လ','ဝ'=>'ဝ','သ'=>'သ','ဟ'=>'ဟ','ဠ'=>'ဠ','အ'=>'အ','ဣ'=>'ဣ','ဤ'=>'ဤ','ဥ'=>'ဥ','ဦ'=>'ဦ','ဧ'=>'ဧ','ဩ'=>'ဩ','ဪ'=>'ဪ','ာ'=>'ာ','ိ'=>'ိ','ီ'=>'ီ','ု'=>'ု','ူ'=>'ူ','ေ'=>'ေ','ဲ'=>'ဲ','ံ'=>'ံ','့'=>'့','း'=>'း','္'=>'္','၀'=>'0','၁'=>'1','၂'=>'2','၃'=>'3','၄'=>'4','၅'=>'5','၆'=>'6','၇'=>'7','၈'=>'8','၉'=>'9','ၐ'=>'ၐ','ၑ'=>'ၑ','ၒ'=>'ၒ','ၓ'=>'ၓ','ၔ'=>'ၔ','ၕ'=>'ၕ','ၖ'=>'ၖ','ၗ'=>'ၗ','ၘ'=>'ၘ','ၙ'=>'ၙ','Ⴀ'=>'ⴀ','Ⴁ'=>'ⴁ','Ⴂ'=>'ⴂ','Ⴃ'=>'ⴃ','Ⴄ'=>'ⴄ','Ⴅ'=>'ⴅ','Ⴆ'=>'ⴆ','Ⴇ'=>'ⴇ','Ⴈ'=>'ⴈ','Ⴉ'=>'ⴉ','Ⴊ'=>'ⴊ','Ⴋ'=>'ⴋ','Ⴌ'=>'ⴌ','Ⴍ'=>'ⴍ','Ⴎ'=>'ⴎ','Ⴏ'=>'ⴏ','Ⴐ'=>'ⴐ','Ⴑ'=>'ⴑ','Ⴒ'=>'ⴒ','Ⴓ'=>'ⴓ','Ⴔ'=>'ⴔ','Ⴕ'=>'ⴕ','Ⴖ'=>'ⴖ','Ⴗ'=>'ⴗ','Ⴘ'=>'ⴘ','Ⴙ'=>'ⴙ','Ⴚ'=>'ⴚ','Ⴛ'=>'ⴛ','Ⴜ'=>'ⴜ','Ⴝ'=>'ⴝ','Ⴞ'=>'ⴞ','Ⴟ'=>'ⴟ','Ⴠ'=>'ⴠ','Ⴡ'=>'ⴡ','Ⴢ'=>'ⴢ','Ⴣ'=>'ⴣ','Ⴤ'=>'ⴤ','Ⴥ'=>'ⴥ','ა'=>'ა','ბ'=>'ბ','გ'=>'გ','დ'=>'დ','ე'=>'ე','ვ'=>'ვ','ზ'=>'ზ','თ'=>'თ','ი'=>'ი','კ'=>'კ','ლ'=>'ლ','მ'=>'მ','ნ'=>'ნ','ო'=>'ო','პ'=>'პ','ჟ'=>'ჟ','რ'=>'რ','ს'=>'ს','ტ'=>'ტ','უ'=>'უ','ფ'=>'ფ','ქ'=>'ქ','ღ'=>'ღ','ყ'=>'ყ','შ'=>'შ','ჩ'=>'ჩ','ც'=>'ც','ძ'=>'ძ','წ'=>'წ','ჭ'=>'ჭ','ხ'=>'ხ','ჯ'=>'ჯ','ჰ'=>'ჰ','ჱ'=>'ჱ','ჲ'=>'ჲ','ჳ'=>'ჳ','ჴ'=>'ჴ','ჵ'=>'ჵ','ჶ'=>'ჶ','ჷ'=>'ჷ','ჸ'=>'ჸ','ჹ'=>'ჹ','ჺ'=>'ჺ','ჼ'=>'ჼ','ᄀ'=>'ᄀ','ᄁ'=>'ᄁ','ᄂ'=>'ᄂ','ᄃ'=>'ᄃ','ᄄ'=>'ᄄ','ᄅ'=>'ᄅ','ᄆ'=>'ᄆ','ᄇ'=>'ᄇ','ᄈ'=>'ᄈ','ᄉ'=>'ᄉ','ᄊ'=>'ᄊ','ᄋ'=>'ᄋ','ᄌ'=>'ᄌ','ᄍ'=>'ᄍ','ᄎ'=>'ᄎ','ᄏ'=>'ᄏ','ᄐ'=>'ᄐ','ᄑ'=>'ᄑ','ᄒ'=>'ᄒ','ᄓ'=>'ᄓ','ᄔ'=>'ᄔ','ᄕ'=>'ᄕ','ᄖ'=>'ᄖ','ᄗ'=>'ᄗ','ᄘ'=>'ᄘ','ᄙ'=>'ᄙ','ᄚ'=>'ᄚ','ᄛ'=>'ᄛ','ᄜ'=>'ᄜ','ᄝ'=>'ᄝ','ᄞ'=>'ᄞ','ᄟ'=>'ᄟ','ᄠ'=>'ᄠ','ᄡ'=>'ᄡ','ᄢ'=>'ᄢ','ᄣ'=>'ᄣ','ᄤ'=>'ᄤ','ᄥ'=>'ᄥ','ᄦ'=>'ᄦ','ᄧ'=>'ᄧ','ᄨ'=>'ᄨ','ᄩ'=>'ᄩ','ᄪ'=>'ᄪ','ᄫ'=>'ᄫ','ᄬ'=>'ᄬ','ᄭ'=>'ᄭ','ᄮ'=>'ᄮ','ᄯ'=>'ᄯ','ᄰ'=>'ᄰ','ᄱ'=>'ᄱ','ᄲ'=>'ᄲ','ᄳ'=>'ᄳ','ᄴ'=>'ᄴ','ᄵ'=>'ᄵ','ᄶ'=>'ᄶ','ᄷ'=>'ᄷ','ᄸ'=>'ᄸ','ᄹ'=>'ᄹ','ᄺ'=>'ᄺ','ᄻ'=>'ᄻ','ᄼ'=>'ᄼ','ᄽ'=>'ᄽ','ᄾ'=>'ᄾ','ᄿ'=>'ᄿ','ᅀ'=>'ᅀ','ᅁ'=>'ᅁ','ᅂ'=>'ᅂ','ᅃ'=>'ᅃ','ᅄ'=>'ᅄ','ᅅ'=>'ᅅ','ᅆ'=>'ᅆ','ᅇ'=>'ᅇ','ᅈ'=>'ᅈ','ᅉ'=>'ᅉ','ᅊ'=>'ᅊ','ᅋ'=>'ᅋ','ᅌ'=>'ᅌ','ᅍ'=>'ᅍ','ᅎ'=>'ᅎ','ᅏ'=>'ᅏ','ᅐ'=>'ᅐ','ᅑ'=>'ᅑ','ᅒ'=>'ᅒ','ᅓ'=>'ᅓ','ᅔ'=>'ᅔ','ᅕ'=>'ᅕ','ᅖ'=>'ᅖ','ᅗ'=>'ᅗ','ᅘ'=>'ᅘ','ᅙ'=>'ᅙ','ᅟ'=>'ᅟ','ᅠ'=>'ᅠ','ᅡ'=>'ᅡ','ᅢ'=>'ᅢ','ᅣ'=>'ᅣ','ᅤ'=>'ᅤ','ᅥ'=>'ᅥ','ᅦ'=>'ᅦ','ᅧ'=>'ᅧ','ᅨ'=>'ᅨ','ᅩ'=>'ᅩ','ᅪ'=>'ᅪ','ᅫ'=>'ᅫ','ᅬ'=>'ᅬ','ᅭ'=>'ᅭ','ᅮ'=>'ᅮ','ᅯ'=>'ᅯ','ᅰ'=>'ᅰ','ᅱ'=>'ᅱ','ᅲ'=>'ᅲ','ᅳ'=>'ᅳ','ᅴ'=>'ᅴ','ᅵ'=>'ᅵ','ᅶ'=>'ᅶ','ᅷ'=>'ᅷ','ᅸ'=>'ᅸ','ᅹ'=>'ᅹ','ᅺ'=>'ᅺ','ᅻ'=>'ᅻ','ᅼ'=>'ᅼ','ᅽ'=>'ᅽ','ᅾ'=>'ᅾ','ᅿ'=>'ᅿ','ᆀ'=>'ᆀ','ᆁ'=>'ᆁ','ᆂ'=>'ᆂ','ᆃ'=>'ᆃ','ᆄ'=>'ᆄ','ᆅ'=>'ᆅ','ᆆ'=>'ᆆ','ᆇ'=>'ᆇ','ᆈ'=>'ᆈ','ᆉ'=>'ᆉ','ᆊ'=>'ᆊ','ᆋ'=>'ᆋ','ᆌ'=>'ᆌ','ᆍ'=>'ᆍ','ᆎ'=>'ᆎ','ᆏ'=>'ᆏ','ᆐ'=>'ᆐ','ᆑ'=>'ᆑ','ᆒ'=>'ᆒ','ᆓ'=>'ᆓ','ᆔ'=>'ᆔ','ᆕ'=>'ᆕ','ᆖ'=>'ᆖ','ᆗ'=>'ᆗ','ᆘ'=>'ᆘ','ᆙ'=>'ᆙ','ᆚ'=>'ᆚ','ᆛ'=>'ᆛ','ᆜ'=>'ᆜ','ᆝ'=>'ᆝ','ᆞ'=>'ᆞ','ᆟ'=>'ᆟ','ᆠ'=>'ᆠ','ᆡ'=>'ᆡ','ᆢ'=>'ᆢ','ᆨ'=>'ᆨ','ᆩ'=>'ᆩ','ᆪ'=>'ᆪ','ᆫ'=>'ᆫ','ᆬ'=>'ᆬ','ᆭ'=>'ᆭ','ᆮ'=>'ᆮ','ᆯ'=>'ᆯ','ᆰ'=>'ᆰ','ᆱ'=>'ᆱ','ᆲ'=>'ᆲ','ᆳ'=>'ᆳ','ᆴ'=>'ᆴ','ᆵ'=>'ᆵ','ᆶ'=>'ᆶ','ᆷ'=>'ᆷ','ᆸ'=>'ᆸ','ᆹ'=>'ᆹ','ᆺ'=>'ᆺ','ᆻ'=>'ᆻ','ᆼ'=>'ᆼ','ᆽ'=>'ᆽ','ᆾ'=>'ᆾ','ᆿ'=>'ᆿ','ᇀ'=>'ᇀ','ᇁ'=>'ᇁ','ᇂ'=>'ᇂ','ᇃ'=>'ᇃ','ᇄ'=>'ᇄ','ᇅ'=>'ᇅ','ᇆ'=>'ᇆ','ᇇ'=>'ᇇ','ᇈ'=>'ᇈ','ᇉ'=>'ᇉ','ᇊ'=>'ᇊ','ᇋ'=>'ᇋ','ᇌ'=>'ᇌ','ᇍ'=>'ᇍ','ᇎ'=>'ᇎ','ᇏ'=>'ᇏ','ᇐ'=>'ᇐ','ᇑ'=>'ᇑ','ᇒ'=>'ᇒ','ᇓ'=>'ᇓ','ᇔ'=>'ᇔ','ᇕ'=>'ᇕ','ᇖ'=>'ᇖ','ᇗ'=>'ᇗ','ᇘ'=>'ᇘ','ᇙ'=>'ᇙ','ᇚ'=>'ᇚ','ᇛ'=>'ᇛ','ᇜ'=>'ᇜ','ᇝ'=>'ᇝ','ᇞ'=>'ᇞ','ᇟ'=>'ᇟ','ᇠ'=>'ᇠ','ᇡ'=>'ᇡ','ᇢ'=>'ᇢ','ᇣ'=>'ᇣ','ᇤ'=>'ᇤ','ᇥ'=>'ᇥ','ᇦ'=>'ᇦ','ᇧ'=>'ᇧ','ᇨ'=>'ᇨ','ᇩ'=>'ᇩ','ᇪ'=>'ᇪ','ᇫ'=>'ᇫ','ᇬ'=>'ᇬ','ᇭ'=>'ᇭ','ᇮ'=>'ᇮ','ᇯ'=>'ᇯ','ᇰ'=>'ᇰ','ᇱ'=>'ᇱ','ᇲ'=>'ᇲ','ᇳ'=>'ᇳ','ᇴ'=>'ᇴ','ᇵ'=>'ᇵ','ᇶ'=>'ᇶ','ᇷ'=>'ᇷ','ᇸ'=>'ᇸ','ᇹ'=>'ᇹ','ሀ'=>'ሀ','ሁ'=>'ሁ','ሂ'=>'ሂ','ሃ'=>'ሃ','ሄ'=>'ሄ','ህ'=>'ህ','ሆ'=>'ሆ','ሇ'=>'ሇ','ለ'=>'ለ','ሉ'=>'ሉ','ሊ'=>'ሊ','ላ'=>'ላ','ሌ'=>'ሌ','ል'=>'ል','ሎ'=>'ሎ','ሏ'=>'ሏ','ሐ'=>'ሐ','ሑ'=>'ሑ','ሒ'=>'ሒ','ሓ'=>'ሓ','ሔ'=>'ሔ','ሕ'=>'ሕ','ሖ'=>'ሖ','ሗ'=>'ሗ','መ'=>'መ','ሙ'=>'ሙ','ሚ'=>'ሚ','ማ'=>'ማ','ሜ'=>'ሜ','ም'=>'ም','ሞ'=>'ሞ','ሟ'=>'ሟ','ሠ'=>'ሠ','ሡ'=>'ሡ','ሢ'=>'ሢ','ሣ'=>'ሣ','ሤ'=>'ሤ','ሥ'=>'ሥ','ሦ'=>'ሦ','ሧ'=>'ሧ','ረ'=>'ረ','ሩ'=>'ሩ','ሪ'=>'ሪ','ራ'=>'ራ','ሬ'=>'ሬ','ር'=>'ር','ሮ'=>'ሮ','ሯ'=>'ሯ','ሰ'=>'ሰ','ሱ'=>'ሱ','ሲ'=>'ሲ','ሳ'=>'ሳ','ሴ'=>'ሴ','ስ'=>'ስ','ሶ'=>'ሶ','ሷ'=>'ሷ','ሸ'=>'ሸ','ሹ'=>'ሹ','ሺ'=>'ሺ','ሻ'=>'ሻ','ሼ'=>'ሼ','ሽ'=>'ሽ','ሾ'=>'ሾ','ሿ'=>'ሿ','ቀ'=>'ቀ','ቁ'=>'ቁ','ቂ'=>'ቂ','ቃ'=>'ቃ','ቄ'=>'ቄ','ቅ'=>'ቅ','ቆ'=>'ቆ','ቇ'=>'ቇ','ቈ'=>'ቈ','ቊ'=>'ቊ','ቋ'=>'ቋ','ቌ'=>'ቌ','ቍ'=>'ቍ','ቐ'=>'ቐ','ቑ'=>'ቑ','ቒ'=>'ቒ','ቓ'=>'ቓ','ቔ'=>'ቔ','ቕ'=>'ቕ','ቖ'=>'ቖ','ቘ'=>'ቘ','ቚ'=>'ቚ','ቛ'=>'ቛ','ቜ'=>'ቜ','ቝ'=>'ቝ','በ'=>'በ','ቡ'=>'ቡ','ቢ'=>'ቢ','ባ'=>'ባ','ቤ'=>'ቤ','ብ'=>'ብ','ቦ'=>'ቦ','ቧ'=>'ቧ','ቨ'=>'ቨ','ቩ'=>'ቩ','ቪ'=>'ቪ','ቫ'=>'ቫ','ቬ'=>'ቬ','ቭ'=>'ቭ','ቮ'=>'ቮ','ቯ'=>'ቯ','ተ'=>'ተ','ቱ'=>'ቱ','ቲ'=>'ቲ','ታ'=>'ታ','ቴ'=>'ቴ','ት'=>'ት','ቶ'=>'ቶ','ቷ'=>'ቷ','ቸ'=>'ቸ','ቹ'=>'ቹ','ቺ'=>'ቺ','ቻ'=>'ቻ','ቼ'=>'ቼ','ች'=>'ች','ቾ'=>'ቾ','ቿ'=>'ቿ','ኀ'=>'ኀ','ኁ'=>'ኁ','ኂ'=>'ኂ','ኃ'=>'ኃ','ኄ'=>'ኄ','ኅ'=>'ኅ','ኆ'=>'ኆ','ኇ'=>'ኇ','ኈ'=>'ኈ','ኊ'=>'ኊ','ኋ'=>'ኋ','ኌ'=>'ኌ','ኍ'=>'ኍ','ነ'=>'ነ','ኑ'=>'ኑ','ኒ'=>'ኒ','ና'=>'ና','ኔ'=>'ኔ','ን'=>'ን','ኖ'=>'ኖ','ኗ'=>'ኗ','ኘ'=>'ኘ','ኙ'=>'ኙ','ኚ'=>'ኚ','ኛ'=>'ኛ','ኜ'=>'ኜ','ኝ'=>'ኝ','ኞ'=>'ኞ','ኟ'=>'ኟ','አ'=>'አ','ኡ'=>'ኡ','ኢ'=>'ኢ','ኣ'=>'ኣ','ኤ'=>'ኤ','እ'=>'እ','ኦ'=>'ኦ','ኧ'=>'ኧ','ከ'=>'ከ','ኩ'=>'ኩ','ኪ'=>'ኪ','ካ'=>'ካ','ኬ'=>'ኬ','ክ'=>'ክ','ኮ'=>'ኮ','ኯ'=>'ኯ','ኰ'=>'ኰ','ኲ'=>'ኲ','ኳ'=>'ኳ','ኴ'=>'ኴ','ኵ'=>'ኵ','ኸ'=>'ኸ','ኹ'=>'ኹ','ኺ'=>'ኺ','ኻ'=>'ኻ','ኼ'=>'ኼ','ኽ'=>'ኽ','ኾ'=>'ኾ','ዀ'=>'ዀ','ዂ'=>'ዂ','ዃ'=>'ዃ','ዄ'=>'ዄ','ዅ'=>'ዅ','ወ'=>'ወ','ዉ'=>'ዉ','ዊ'=>'ዊ','ዋ'=>'ዋ','ዌ'=>'ዌ','ው'=>'ው','ዎ'=>'ዎ','ዏ'=>'ዏ','ዐ'=>'ዐ','ዑ'=>'ዑ','ዒ'=>'ዒ','ዓ'=>'ዓ','ዔ'=>'ዔ','ዕ'=>'ዕ','ዖ'=>'ዖ','ዘ'=>'ዘ','ዙ'=>'ዙ','ዚ'=>'ዚ','ዛ'=>'ዛ','ዜ'=>'ዜ','ዝ'=>'ዝ','ዞ'=>'ዞ','ዟ'=>'ዟ','ዠ'=>'ዠ','ዡ'=>'ዡ','ዢ'=>'ዢ','ዣ'=>'ዣ','ዤ'=>'ዤ','ዥ'=>'ዥ','ዦ'=>'ዦ','ዧ'=>'ዧ','የ'=>'የ','ዩ'=>'ዩ','ዪ'=>'ዪ','ያ'=>'ያ','ዬ'=>'ዬ','ይ'=>'ይ','ዮ'=>'ዮ','ዯ'=>'ዯ','ደ'=>'ደ','ዱ'=>'ዱ','ዲ'=>'ዲ','ዳ'=>'ዳ','ዴ'=>'ዴ','ድ'=>'ድ','ዶ'=>'ዶ','ዷ'=>'ዷ','ዸ'=>'ዸ','ዹ'=>'ዹ','ዺ'=>'ዺ','ዻ'=>'ዻ','ዼ'=>'ዼ','ዽ'=>'ዽ','ዾ'=>'ዾ','ዿ'=>'ዿ','ጀ'=>'ጀ','ጁ'=>'ጁ','ጂ'=>'ጂ','ጃ'=>'ጃ','ጄ'=>'ጄ','ጅ'=>'ጅ','ጆ'=>'ጆ','ጇ'=>'ጇ','ገ'=>'ገ','ጉ'=>'ጉ','ጊ'=>'ጊ','ጋ'=>'ጋ','ጌ'=>'ጌ','ግ'=>'ግ','ጎ'=>'ጎ','ጏ'=>'ጏ','ጐ'=>'ጐ','ጒ'=>'ጒ','ጓ'=>'ጓ','ጔ'=>'ጔ','ጕ'=>'ጕ','ጘ'=>'ጘ','ጙ'=>'ጙ','ጚ'=>'ጚ','ጛ'=>'ጛ','ጜ'=>'ጜ','ጝ'=>'ጝ','ጞ'=>'ጞ','ጟ'=>'ጟ','ጠ'=>'ጠ','ጡ'=>'ጡ','ጢ'=>'ጢ','ጣ'=>'ጣ','ጤ'=>'ጤ','ጥ'=>'ጥ','ጦ'=>'ጦ','ጧ'=>'ጧ','ጨ'=>'ጨ','ጩ'=>'ጩ','ጪ'=>'ጪ','ጫ'=>'ጫ','ጬ'=>'ጬ','ጭ'=>'ጭ','ጮ'=>'ጮ','ጯ'=>'ጯ','ጰ'=>'ጰ','ጱ'=>'ጱ','ጲ'=>'ጲ','ጳ'=>'ጳ','ጴ'=>'ጴ','ጵ'=>'ጵ','ጶ'=>'ጶ','ጷ'=>'ጷ','ጸ'=>'ጸ','ጹ'=>'ጹ','ጺ'=>'ጺ','ጻ'=>'ጻ','ጼ'=>'ጼ','ጽ'=>'ጽ','ጾ'=>'ጾ','ጿ'=>'ጿ','ፀ'=>'ፀ','ፁ'=>'ፁ','ፂ'=>'ፂ','ፃ'=>'ፃ','ፄ'=>'ፄ','ፅ'=>'ፅ','ፆ'=>'ፆ','ፇ'=>'ፇ','ፈ'=>'ፈ','ፉ'=>'ፉ','ፊ'=>'ፊ','ፋ'=>'ፋ','ፌ'=>'ፌ','ፍ'=>'ፍ','ፎ'=>'ፎ','ፏ'=>'ፏ','ፐ'=>'ፐ','ፑ'=>'ፑ','ፒ'=>'ፒ','ፓ'=>'ፓ','ፔ'=>'ፔ','ፕ'=>'ፕ','ፖ'=>'ፖ','ፗ'=>'ፗ','ፘ'=>'ፘ','ፙ'=>'ፙ','ፚ'=>'ፚ','፟'=>'፟','፩'=>'1','፪'=>'2','፫'=>'3','፬'=>'4','፭'=>'5','፮'=>'6','፯'=>'7','፰'=>'8','፱'=>'9','፲'=>'10','፳'=>'20','፴'=>'30','፵'=>'40','፶'=>'50','፷'=>'60','፸'=>'70','፹'=>'80','፺'=>'90','፻'=>'100','፼'=>'10000','ᎀ'=>'ᎀ','ᎁ'=>'ᎁ','ᎂ'=>'ᎂ','ᎃ'=>'ᎃ','ᎄ'=>'ᎄ','ᎅ'=>'ᎅ','ᎆ'=>'ᎆ','ᎇ'=>'ᎇ','ᎈ'=>'ᎈ','ᎉ'=>'ᎉ','ᎊ'=>'ᎊ','ᎋ'=>'ᎋ','ᎌ'=>'ᎌ','ᎍ'=>'ᎍ','ᎎ'=>'ᎎ','ᎏ'=>'ᎏ','Ꭰ'=>'Ꭰ','Ꭱ'=>'Ꭱ','Ꭲ'=>'Ꭲ','Ꭳ'=>'Ꭳ','Ꭴ'=>'Ꭴ','Ꭵ'=>'Ꭵ','Ꭶ'=>'Ꭶ','Ꭷ'=>'Ꭷ','Ꭸ'=>'Ꭸ','Ꭹ'=>'Ꭹ','Ꭺ'=>'Ꭺ','Ꭻ'=>'Ꭻ','Ꭼ'=>'Ꭼ','Ꭽ'=>'Ꭽ','Ꭾ'=>'Ꭾ','Ꭿ'=>'Ꭿ','Ꮀ'=>'Ꮀ','Ꮁ'=>'Ꮁ','Ꮂ'=>'Ꮂ','Ꮃ'=>'Ꮃ','Ꮄ'=>'Ꮄ','Ꮅ'=>'Ꮅ','Ꮆ'=>'Ꮆ','Ꮇ'=>'Ꮇ','Ꮈ'=>'Ꮈ','Ꮉ'=>'Ꮉ','Ꮊ'=>'Ꮊ','Ꮋ'=>'Ꮋ','Ꮌ'=>'Ꮌ','Ꮍ'=>'Ꮍ','Ꮎ'=>'Ꮎ','Ꮏ'=>'Ꮏ','Ꮐ'=>'Ꮐ','Ꮑ'=>'Ꮑ','Ꮒ'=>'Ꮒ','Ꮓ'=>'Ꮓ','Ꮔ'=>'Ꮔ','Ꮕ'=>'Ꮕ','Ꮖ'=>'Ꮖ','Ꮗ'=>'Ꮗ','Ꮘ'=>'Ꮘ','Ꮙ'=>'Ꮙ','Ꮚ'=>'Ꮚ','Ꮛ'=>'Ꮛ','Ꮜ'=>'Ꮜ','Ꮝ'=>'Ꮝ','Ꮞ'=>'Ꮞ','Ꮟ'=>'Ꮟ','Ꮠ'=>'Ꮠ','Ꮡ'=>'Ꮡ','Ꮢ'=>'Ꮢ','Ꮣ'=>'Ꮣ','Ꮤ'=>'Ꮤ','Ꮥ'=>'Ꮥ','Ꮦ'=>'Ꮦ','Ꮧ'=>'Ꮧ','Ꮨ'=>'Ꮨ','Ꮩ'=>'Ꮩ','Ꮪ'=>'Ꮪ','Ꮫ'=>'Ꮫ','Ꮬ'=>'Ꮬ','Ꮭ'=>'Ꮭ','Ꮮ'=>'Ꮮ','Ꮯ'=>'Ꮯ','Ꮰ'=>'Ꮰ','Ꮱ'=>'Ꮱ','Ꮲ'=>'Ꮲ','Ꮳ'=>'Ꮳ','Ꮴ'=>'Ꮴ','Ꮵ'=>'Ꮵ','Ꮶ'=>'Ꮶ','Ꮷ'=>'Ꮷ','Ꮸ'=>'Ꮸ','Ꮹ'=>'Ꮹ','Ꮺ'=>'Ꮺ','Ꮻ'=>'Ꮻ','Ꮼ'=>'Ꮼ','Ꮽ'=>'Ꮽ','Ꮾ'=>'Ꮾ','Ꮿ'=>'Ꮿ','Ᏸ'=>'Ᏸ','Ᏹ'=>'Ᏹ','Ᏺ'=>'Ᏺ','Ᏻ'=>'Ᏻ','Ᏼ'=>'Ᏼ','ᐁ'=>'ᐁ','ᐂ'=>'ᐂ','ᐃ'=>'ᐃ','ᐄ'=>'ᐄ','ᐅ'=>'ᐅ','ᐆ'=>'ᐆ','ᐇ'=>'ᐇ','ᐈ'=>'ᐈ','ᐉ'=>'ᐉ','ᐊ'=>'ᐊ','ᐋ'=>'ᐋ','ᐌ'=>'ᐌ','ᐍ'=>'ᐍ','ᐎ'=>'ᐎ','ᐏ'=>'ᐏ','ᐐ'=>'ᐐ','ᐑ'=>'ᐑ','ᐒ'=>'ᐒ','ᐓ'=>'ᐓ','ᐔ'=>'ᐔ','ᐕ'=>'ᐕ','ᐖ'=>'ᐖ','ᐗ'=>'ᐗ','ᐘ'=>'ᐘ','ᐙ'=>'ᐙ','ᐚ'=>'ᐚ','ᐛ'=>'ᐛ','ᐜ'=>'ᐜ','ᐝ'=>'ᐝ','ᐞ'=>'ᐞ','ᐟ'=>'ᐟ','ᐠ'=>'ᐠ','ᐡ'=>'ᐡ','ᐢ'=>'ᐢ','ᐣ'=>'ᐣ','ᐤ'=>'ᐤ','ᐥ'=>'ᐥ','ᐦ'=>'ᐦ','ᐧ'=>'ᐧ','ᐨ'=>'ᐨ','ᐩ'=>'ᐩ','ᐪ'=>'ᐪ','ᐫ'=>'ᐫ','ᐬ'=>'ᐬ','ᐭ'=>'ᐭ','ᐮ'=>'ᐮ','ᐯ'=>'ᐯ','ᐰ'=>'ᐰ','ᐱ'=>'ᐱ','ᐲ'=>'ᐲ','ᐳ'=>'ᐳ','ᐴ'=>'ᐴ','ᐵ'=>'ᐵ','ᐶ'=>'ᐶ','ᐷ'=>'ᐷ','ᐸ'=>'ᐸ','ᐹ'=>'ᐹ','ᐺ'=>'ᐺ','ᐻ'=>'ᐻ','ᐼ'=>'ᐼ','ᐽ'=>'ᐽ','ᐾ'=>'ᐾ','ᐿ'=>'ᐿ','ᑀ'=>'ᑀ','ᑁ'=>'ᑁ','ᑂ'=>'ᑂ','ᑃ'=>'ᑃ','ᑄ'=>'ᑄ','ᑅ'=>'ᑅ','ᑆ'=>'ᑆ','ᑇ'=>'ᑇ','ᑈ'=>'ᑈ','ᑉ'=>'ᑉ','ᑊ'=>'ᑊ','ᑋ'=>'ᑋ','ᑌ'=>'ᑌ','ᑍ'=>'ᑍ','ᑎ'=>'ᑎ','ᑏ'=>'ᑏ','ᑐ'=>'ᑐ','ᑑ'=>'ᑑ','ᑒ'=>'ᑒ','ᑓ'=>'ᑓ','ᑔ'=>'ᑔ','ᑕ'=>'ᑕ','ᑖ'=>'ᑖ','ᑗ'=>'ᑗ','ᑘ'=>'ᑘ','ᑙ'=>'ᑙ','ᑚ'=>'ᑚ','ᑛ'=>'ᑛ','ᑜ'=>'ᑜ','ᑝ'=>'ᑝ','ᑞ'=>'ᑞ','ᑟ'=>'ᑟ','ᑠ'=>'ᑠ','ᑡ'=>'ᑡ','ᑢ'=>'ᑢ','ᑣ'=>'ᑣ','ᑤ'=>'ᑤ','ᑥ'=>'ᑥ','ᑦ'=>'ᑦ','ᑧ'=>'ᑧ','ᑨ'=>'ᑨ','ᑩ'=>'ᑩ','ᑪ'=>'ᑪ','ᑫ'=>'ᑫ','ᑬ'=>'ᑬ','ᑭ'=>'ᑭ','ᑮ'=>'ᑮ','ᑯ'=>'ᑯ','ᑰ'=>'ᑰ','ᑱ'=>'ᑱ','ᑲ'=>'ᑲ','ᑳ'=>'ᑳ','ᑴ'=>'ᑴ','ᑵ'=>'ᑵ','ᑶ'=>'ᑶ','ᑷ'=>'ᑷ','ᑸ'=>'ᑸ','ᑹ'=>'ᑹ','ᑺ'=>'ᑺ','ᑻ'=>'ᑻ','ᑼ'=>'ᑼ','ᑽ'=>'ᑽ','ᑾ'=>'ᑾ','ᑿ'=>'ᑿ','ᒀ'=>'ᒀ','ᒁ'=>'ᒁ','ᒂ'=>'ᒂ','ᒃ'=>'ᒃ','ᒄ'=>'ᒄ','ᒅ'=>'ᒅ','ᒆ'=>'ᒆ','ᒇ'=>'ᒇ','ᒈ'=>'ᒈ','ᒉ'=>'ᒉ','ᒊ'=>'ᒊ','ᒋ'=>'ᒋ','ᒌ'=>'ᒌ','ᒍ'=>'ᒍ','ᒎ'=>'ᒎ','ᒏ'=>'ᒏ','ᒐ'=>'ᒐ','ᒑ'=>'ᒑ','ᒒ'=>'ᒒ','ᒓ'=>'ᒓ','ᒔ'=>'ᒔ','ᒕ'=>'ᒕ','ᒖ'=>'ᒖ','ᒗ'=>'ᒗ','ᒘ'=>'ᒘ','ᒙ'=>'ᒙ','ᒚ'=>'ᒚ','ᒛ'=>'ᒛ','ᒜ'=>'ᒜ','ᒝ'=>'ᒝ','ᒞ'=>'ᒞ','ᒟ'=>'ᒟ','ᒠ'=>'ᒠ','ᒡ'=>'ᒡ','ᒢ'=>'ᒢ','ᒣ'=>'ᒣ','ᒤ'=>'ᒤ','ᒥ'=>'ᒥ','ᒦ'=>'ᒦ','ᒧ'=>'ᒧ','ᒨ'=>'ᒨ','ᒩ'=>'ᒩ','ᒪ'=>'ᒪ','ᒫ'=>'ᒫ','ᒬ'=>'ᒬ','ᒭ'=>'ᒭ','ᒮ'=>'ᒮ','ᒯ'=>'ᒯ','ᒰ'=>'ᒰ','ᒱ'=>'ᒱ','ᒲ'=>'ᒲ','ᒳ'=>'ᒳ','ᒴ'=>'ᒴ','ᒵ'=>'ᒵ','ᒶ'=>'ᒶ','ᒷ'=>'ᒷ','ᒸ'=>'ᒸ','ᒹ'=>'ᒹ','ᒺ'=>'ᒺ','ᒻ'=>'ᒻ','ᒼ'=>'ᒼ','ᒽ'=>'ᒽ','ᒾ'=>'ᒾ','ᒿ'=>'ᒿ','ᓀ'=>'ᓀ','ᓁ'=>'ᓁ','ᓂ'=>'ᓂ','ᓃ'=>'ᓃ','ᓄ'=>'ᓄ','ᓅ'=>'ᓅ','ᓆ'=>'ᓆ','ᓇ'=>'ᓇ','ᓈ'=>'ᓈ','ᓉ'=>'ᓉ','ᓊ'=>'ᓊ','ᓋ'=>'ᓋ','ᓌ'=>'ᓌ','ᓍ'=>'ᓍ','ᓎ'=>'ᓎ','ᓏ'=>'ᓏ','ᓐ'=>'ᓐ','ᓑ'=>'ᓑ','ᓒ'=>'ᓒ','ᓓ'=>'ᓓ','ᓔ'=>'ᓔ','ᓕ'=>'ᓕ','ᓖ'=>'ᓖ','ᓗ'=>'ᓗ','ᓘ'=>'ᓘ','ᓙ'=>'ᓙ','ᓚ'=>'ᓚ','ᓛ'=>'ᓛ','ᓜ'=>'ᓜ','ᓝ'=>'ᓝ','ᓞ'=>'ᓞ','ᓟ'=>'ᓟ','ᓠ'=>'ᓠ','ᓡ'=>'ᓡ','ᓢ'=>'ᓢ','ᓣ'=>'ᓣ','ᓤ'=>'ᓤ','ᓥ'=>'ᓥ','ᓦ'=>'ᓦ','ᓧ'=>'ᓧ','ᓨ'=>'ᓨ','ᓩ'=>'ᓩ','ᓪ'=>'ᓪ','ᓫ'=>'ᓫ','ᓬ'=>'ᓬ','ᓭ'=>'ᓭ','ᓮ'=>'ᓮ','ᓯ'=>'ᓯ','ᓰ'=>'ᓰ','ᓱ'=>'ᓱ','ᓲ'=>'ᓲ','ᓳ'=>'ᓳ','ᓴ'=>'ᓴ','ᓵ'=>'ᓵ','ᓶ'=>'ᓶ','ᓷ'=>'ᓷ','ᓸ'=>'ᓸ','ᓹ'=>'ᓹ','ᓺ'=>'ᓺ','ᓻ'=>'ᓻ','ᓼ'=>'ᓼ','ᓽ'=>'ᓽ','ᓾ'=>'ᓾ','ᓿ'=>'ᓿ','ᔀ'=>'ᔀ','ᔁ'=>'ᔁ','ᔂ'=>'ᔂ','ᔃ'=>'ᔃ','ᔄ'=>'ᔄ','ᔅ'=>'ᔅ','ᔆ'=>'ᔆ','ᔇ'=>'ᔇ','ᔈ'=>'ᔈ','ᔉ'=>'ᔉ','ᔊ'=>'ᔊ','ᔋ'=>'ᔋ','ᔌ'=>'ᔌ','ᔍ'=>'ᔍ','ᔎ'=>'ᔎ','ᔏ'=>'ᔏ','ᔐ'=>'ᔐ','ᔑ'=>'ᔑ','ᔒ'=>'ᔒ','ᔓ'=>'ᔓ','ᔔ'=>'ᔔ','ᔕ'=>'ᔕ','ᔖ'=>'ᔖ','ᔗ'=>'ᔗ','ᔘ'=>'ᔘ','ᔙ'=>'ᔙ','ᔚ'=>'ᔚ','ᔛ'=>'ᔛ','ᔜ'=>'ᔜ','ᔝ'=>'ᔝ','ᔞ'=>'ᔞ','ᔟ'=>'ᔟ','ᔠ'=>'ᔠ','ᔡ'=>'ᔡ','ᔢ'=>'ᔢ','ᔣ'=>'ᔣ','ᔤ'=>'ᔤ','ᔥ'=>'ᔥ','ᔦ'=>'ᔦ','ᔧ'=>'ᔧ','ᔨ'=>'ᔨ','ᔩ'=>'ᔩ','ᔪ'=>'ᔪ','ᔫ'=>'ᔫ','ᔬ'=>'ᔬ','ᔭ'=>'ᔭ','ᔮ'=>'ᔮ','ᔯ'=>'ᔯ','ᔰ'=>'ᔰ','ᔱ'=>'ᔱ','ᔲ'=>'ᔲ','ᔳ'=>'ᔳ','ᔴ'=>'ᔴ','ᔵ'=>'ᔵ','ᔶ'=>'ᔶ','ᔷ'=>'ᔷ','ᔸ'=>'ᔸ','ᔹ'=>'ᔹ','ᔺ'=>'ᔺ','ᔻ'=>'ᔻ','ᔼ'=>'ᔼ','ᔽ'=>'ᔽ','ᔾ'=>'ᔾ','ᔿ'=>'ᔿ','ᕀ'=>'ᕀ','ᕁ'=>'ᕁ','ᕂ'=>'ᕂ','ᕃ'=>'ᕃ','ᕄ'=>'ᕄ','ᕅ'=>'ᕅ','ᕆ'=>'ᕆ','ᕇ'=>'ᕇ','ᕈ'=>'ᕈ','ᕉ'=>'ᕉ','ᕊ'=>'ᕊ','ᕋ'=>'ᕋ','ᕌ'=>'ᕌ','ᕍ'=>'ᕍ','ᕎ'=>'ᕎ','ᕏ'=>'ᕏ','ᕐ'=>'ᕐ','ᕑ'=>'ᕑ','ᕒ'=>'ᕒ','ᕓ'=>'ᕓ','ᕔ'=>'ᕔ','ᕕ'=>'ᕕ','ᕖ'=>'ᕖ','ᕗ'=>'ᕗ','ᕘ'=>'ᕘ','ᕙ'=>'ᕙ','ᕚ'=>'ᕚ','ᕛ'=>'ᕛ','ᕜ'=>'ᕜ','ᕝ'=>'ᕝ','ᕞ'=>'ᕞ','ᕟ'=>'ᕟ','ᕠ'=>'ᕠ','ᕡ'=>'ᕡ','ᕢ'=>'ᕢ','ᕣ'=>'ᕣ','ᕤ'=>'ᕤ','ᕥ'=>'ᕥ','ᕦ'=>'ᕦ','ᕧ'=>'ᕧ','ᕨ'=>'ᕨ','ᕩ'=>'ᕩ','ᕪ'=>'ᕪ','ᕫ'=>'ᕫ','ᕬ'=>'ᕬ','ᕭ'=>'ᕭ','ᕮ'=>'ᕮ','ᕯ'=>'ᕯ','ᕰ'=>'ᕰ','ᕱ'=>'ᕱ','ᕲ'=>'ᕲ','ᕳ'=>'ᕳ','ᕴ'=>'ᕴ','ᕵ'=>'ᕵ','ᕶ'=>'ᕶ','ᕷ'=>'ᕷ','ᕸ'=>'ᕸ','ᕹ'=>'ᕹ','ᕺ'=>'ᕺ','ᕻ'=>'ᕻ','ᕼ'=>'ᕼ','ᕽ'=>'ᕽ','ᕾ'=>'ᕾ','ᕿ'=>'ᕿ','ᖀ'=>'ᖀ','ᖁ'=>'ᖁ','ᖂ'=>'ᖂ','ᖃ'=>'ᖃ','ᖄ'=>'ᖄ','ᖅ'=>'ᖅ','ᖆ'=>'ᖆ','ᖇ'=>'ᖇ','ᖈ'=>'ᖈ','ᖉ'=>'ᖉ','ᖊ'=>'ᖊ','ᖋ'=>'ᖋ','ᖌ'=>'ᖌ','ᖍ'=>'ᖍ','ᖎ'=>'ᖎ','ᖏ'=>'ᖏ','ᖐ'=>'ᖐ','ᖑ'=>'ᖑ','ᖒ'=>'ᖒ','ᖓ'=>'ᖓ','ᖔ'=>'ᖔ','ᖕ'=>'ᖕ','ᖖ'=>'ᖖ','ᖗ'=>'ᖗ','ᖘ'=>'ᖘ','ᖙ'=>'ᖙ','ᖚ'=>'ᖚ','ᖛ'=>'ᖛ','ᖜ'=>'ᖜ','ᖝ'=>'ᖝ','ᖞ'=>'ᖞ','ᖟ'=>'ᖟ','ᖠ'=>'ᖠ','ᖡ'=>'ᖡ','ᖢ'=>'ᖢ','ᖣ'=>'ᖣ','ᖤ'=>'ᖤ','ᖥ'=>'ᖥ','ᖦ'=>'ᖦ','ᖧ'=>'ᖧ','ᖨ'=>'ᖨ','ᖩ'=>'ᖩ','ᖪ'=>'ᖪ','ᖫ'=>'ᖫ','ᖬ'=>'ᖬ','ᖭ'=>'ᖭ','ᖮ'=>'ᖮ','ᖯ'=>'ᖯ','ᖰ'=>'ᖰ','ᖱ'=>'ᖱ','ᖲ'=>'ᖲ','ᖳ'=>'ᖳ','ᖴ'=>'ᖴ','ᖵ'=>'ᖵ','ᖶ'=>'ᖶ','ᖷ'=>'ᖷ','ᖸ'=>'ᖸ','ᖹ'=>'ᖹ','ᖺ'=>'ᖺ','ᖻ'=>'ᖻ','ᖼ'=>'ᖼ','ᖽ'=>'ᖽ','ᖾ'=>'ᖾ','ᖿ'=>'ᖿ','ᗀ'=>'ᗀ','ᗁ'=>'ᗁ','ᗂ'=>'ᗂ','ᗃ'=>'ᗃ','ᗄ'=>'ᗄ','ᗅ'=>'ᗅ','ᗆ'=>'ᗆ','ᗇ'=>'ᗇ','ᗈ'=>'ᗈ','ᗉ'=>'ᗉ','ᗊ'=>'ᗊ','ᗋ'=>'ᗋ','ᗌ'=>'ᗌ','ᗍ'=>'ᗍ','ᗎ'=>'ᗎ','ᗏ'=>'ᗏ','ᗐ'=>'ᗐ','ᗑ'=>'ᗑ','ᗒ'=>'ᗒ','ᗓ'=>'ᗓ','ᗔ'=>'ᗔ','ᗕ'=>'ᗕ','ᗖ'=>'ᗖ','ᗗ'=>'ᗗ','ᗘ'=>'ᗘ','ᗙ'=>'ᗙ','ᗚ'=>'ᗚ','ᗛ'=>'ᗛ','ᗜ'=>'ᗜ','ᗝ'=>'ᗝ','ᗞ'=>'ᗞ','ᗟ'=>'ᗟ','ᗠ'=>'ᗠ','ᗡ'=>'ᗡ','ᗢ'=>'ᗢ','ᗣ'=>'ᗣ','ᗤ'=>'ᗤ','ᗥ'=>'ᗥ','ᗦ'=>'ᗦ','ᗧ'=>'ᗧ','ᗨ'=>'ᗨ','ᗩ'=>'ᗩ','ᗪ'=>'ᗪ','ᗫ'=>'ᗫ','ᗬ'=>'ᗬ','ᗭ'=>'ᗭ','ᗮ'=>'ᗮ','ᗯ'=>'ᗯ','ᗰ'=>'ᗰ','ᗱ'=>'ᗱ','ᗲ'=>'ᗲ','ᗳ'=>'ᗳ','ᗴ'=>'ᗴ','ᗵ'=>'ᗵ','ᗶ'=>'ᗶ','ᗷ'=>'ᗷ','ᗸ'=>'ᗸ','ᗹ'=>'ᗹ','ᗺ'=>'ᗺ','ᗻ'=>'ᗻ','ᗼ'=>'ᗼ','ᗽ'=>'ᗽ','ᗾ'=>'ᗾ','ᗿ'=>'ᗿ','ᘀ'=>'ᘀ','ᘁ'=>'ᘁ','ᘂ'=>'ᘂ','ᘃ'=>'ᘃ','ᘄ'=>'ᘄ','ᘅ'=>'ᘅ','ᘆ'=>'ᘆ','ᘇ'=>'ᘇ','ᘈ'=>'ᘈ','ᘉ'=>'ᘉ','ᘊ'=>'ᘊ','ᘋ'=>'ᘋ','ᘌ'=>'ᘌ','ᘍ'=>'ᘍ','ᘎ'=>'ᘎ','ᘏ'=>'ᘏ','ᘐ'=>'ᘐ','ᘑ'=>'ᘑ','ᘒ'=>'ᘒ','ᘓ'=>'ᘓ','ᘔ'=>'ᘔ','ᘕ'=>'ᘕ','ᘖ'=>'ᘖ','ᘗ'=>'ᘗ','ᘘ'=>'ᘘ','ᘙ'=>'ᘙ','ᘚ'=>'ᘚ','ᘛ'=>'ᘛ','ᘜ'=>'ᘜ','ᘝ'=>'ᘝ','ᘞ'=>'ᘞ','ᘟ'=>'ᘟ','ᘠ'=>'ᘠ','ᘡ'=>'ᘡ','ᘢ'=>'ᘢ','ᘣ'=>'ᘣ','ᘤ'=>'ᘤ','ᘥ'=>'ᘥ','ᘦ'=>'ᘦ','ᘧ'=>'ᘧ','ᘨ'=>'ᘨ','ᘩ'=>'ᘩ','ᘪ'=>'ᘪ','ᘫ'=>'ᘫ','ᘬ'=>'ᘬ','ᘭ'=>'ᘭ','ᘮ'=>'ᘮ','ᘯ'=>'ᘯ','ᘰ'=>'ᘰ','ᘱ'=>'ᘱ','ᘲ'=>'ᘲ','ᘳ'=>'ᘳ','ᘴ'=>'ᘴ','ᘵ'=>'ᘵ','ᘶ'=>'ᘶ','ᘷ'=>'ᘷ','ᘸ'=>'ᘸ','ᘹ'=>'ᘹ','ᘺ'=>'ᘺ','ᘻ'=>'ᘻ','ᘼ'=>'ᘼ','ᘽ'=>'ᘽ','ᘾ'=>'ᘾ','ᘿ'=>'ᘿ','ᙀ'=>'ᙀ','ᙁ'=>'ᙁ','ᙂ'=>'ᙂ','ᙃ'=>'ᙃ','ᙄ'=>'ᙄ','ᙅ'=>'ᙅ','ᙆ'=>'ᙆ','ᙇ'=>'ᙇ','ᙈ'=>'ᙈ','ᙉ'=>'ᙉ','ᙊ'=>'ᙊ','ᙋ'=>'ᙋ','ᙌ'=>'ᙌ','ᙍ'=>'ᙍ','ᙎ'=>'ᙎ','ᙏ'=>'ᙏ','ᙐ'=>'ᙐ','ᙑ'=>'ᙑ','ᙒ'=>'ᙒ','ᙓ'=>'ᙓ','ᙔ'=>'ᙔ','ᙕ'=>'ᙕ','ᙖ'=>'ᙖ','ᙗ'=>'ᙗ','ᙘ'=>'ᙘ','ᙙ'=>'ᙙ','ᙚ'=>'ᙚ','ᙛ'=>'ᙛ','ᙜ'=>'ᙜ','ᙝ'=>'ᙝ','ᙞ'=>'ᙞ','ᙟ'=>'ᙟ','ᙠ'=>'ᙠ','ᙡ'=>'ᙡ','ᙢ'=>'ᙢ','ᙣ'=>'ᙣ','ᙤ'=>'ᙤ','ᙥ'=>'ᙥ','ᙦ'=>'ᙦ','ᙧ'=>'ᙧ','ᙨ'=>'ᙨ','ᙩ'=>'ᙩ','ᙪ'=>'ᙪ','ᙫ'=>'ᙫ','ᙬ'=>'ᙬ','ᙯ'=>'ᙯ','ᙰ'=>'ᙰ','ᙱ'=>'ᙱ','ᙲ'=>'ᙲ','ᙳ'=>'ᙳ','ᙴ'=>'ᙴ','ᙵ'=>'ᙵ','ᙶ'=>'ᙶ','ᚁ'=>'ᚁ','ᚂ'=>'ᚂ','ᚃ'=>'ᚃ','ᚄ'=>'ᚄ','ᚅ'=>'ᚅ','ᚆ'=>'ᚆ','ᚇ'=>'ᚇ','ᚈ'=>'ᚈ','ᚉ'=>'ᚉ','ᚊ'=>'ᚊ','ᚋ'=>'ᚋ','ᚌ'=>'ᚌ','ᚍ'=>'ᚍ','ᚎ'=>'ᚎ','ᚏ'=>'ᚏ','ᚐ'=>'ᚐ','ᚑ'=>'ᚑ','ᚒ'=>'ᚒ','ᚓ'=>'ᚓ','ᚔ'=>'ᚔ','ᚕ'=>'ᚕ','ᚖ'=>'ᚖ','ᚗ'=>'ᚗ','ᚘ'=>'ᚘ','ᚙ'=>'ᚙ','ᚚ'=>'ᚚ','ᚠ'=>'ᚠ','ᚡ'=>'ᚡ','ᚢ'=>'ᚢ','ᚣ'=>'ᚣ','ᚤ'=>'ᚤ','ᚥ'=>'ᚥ','ᚦ'=>'ᚦ','ᚧ'=>'ᚧ','ᚨ'=>'ᚨ','ᚩ'=>'ᚩ','ᚪ'=>'ᚪ','ᚫ'=>'ᚫ','ᚬ'=>'ᚬ','ᚭ'=>'ᚭ','ᚮ'=>'ᚮ','ᚯ'=>'ᚯ','ᚰ'=>'ᚰ','ᚱ'=>'ᚱ','ᚲ'=>'ᚲ','ᚳ'=>'ᚳ','ᚴ'=>'ᚴ','ᚵ'=>'ᚵ','ᚶ'=>'ᚶ','ᚷ'=>'ᚷ','ᚸ'=>'ᚸ','ᚹ'=>'ᚹ','ᚺ'=>'ᚺ','ᚻ'=>'ᚻ','ᚼ'=>'ᚼ','ᚽ'=>'ᚽ','ᚾ'=>'ᚾ','ᚿ'=>'ᚿ','ᛀ'=>'ᛀ','ᛁ'=>'ᛁ','ᛂ'=>'ᛂ','ᛃ'=>'ᛃ','ᛄ'=>'ᛄ','ᛅ'=>'ᛅ','ᛆ'=>'ᛆ','ᛇ'=>'ᛇ','ᛈ'=>'ᛈ','ᛉ'=>'ᛉ','ᛊ'=>'ᛊ','ᛋ'=>'ᛋ','ᛌ'=>'ᛌ','ᛍ'=>'ᛍ','ᛎ'=>'ᛎ','ᛏ'=>'ᛏ','ᛐ'=>'ᛐ','ᛑ'=>'ᛑ','ᛒ'=>'ᛒ','ᛓ'=>'ᛓ','ᛔ'=>'ᛔ','ᛕ'=>'ᛕ','ᛖ'=>'ᛖ','ᛗ'=>'ᛗ','ᛘ'=>'ᛘ','ᛙ'=>'ᛙ','ᛚ'=>'ᛚ','ᛛ'=>'ᛛ','ᛜ'=>'ᛜ','ᛝ'=>'ᛝ','ᛞ'=>'ᛞ','ᛟ'=>'ᛟ','ᛠ'=>'ᛠ','ᛡ'=>'ᛡ','ᛢ'=>'ᛢ','ᛣ'=>'ᛣ','ᛤ'=>'ᛤ','ᛥ'=>'ᛥ','ᛦ'=>'ᛦ','ᛧ'=>'ᛧ','ᛨ'=>'ᛨ','ᛩ'=>'ᛩ','ᛪ'=>'ᛪ','ᛮ'=>'17','ᛯ'=>'18','ᛰ'=>'19','ᜀ'=>'ᜀ','ᜁ'=>'ᜁ','ᜂ'=>'ᜂ','ᜃ'=>'ᜃ','ᜄ'=>'ᜄ','ᜅ'=>'ᜅ','ᜆ'=>'ᜆ','ᜇ'=>'ᜇ','ᜈ'=>'ᜈ','ᜉ'=>'ᜉ','ᜊ'=>'ᜊ','ᜋ'=>'ᜋ','ᜌ'=>'ᜌ','ᜎ'=>'ᜎ','ᜏ'=>'ᜏ','ᜐ'=>'ᜐ','ᜑ'=>'ᜑ','ᜒ'=>'ᜒ','ᜓ'=>'ᜓ','᜔'=>'᜔','ᜠ'=>'ᜠ','ᜡ'=>'ᜡ','ᜢ'=>'ᜢ','ᜣ'=>'ᜣ','ᜤ'=>'ᜤ','ᜥ'=>'ᜥ','ᜦ'=>'ᜦ','ᜧ'=>'ᜧ','ᜨ'=>'ᜨ','ᜩ'=>'ᜩ','ᜪ'=>'ᜪ','ᜫ'=>'ᜫ','ᜬ'=>'ᜬ','ᜭ'=>'ᜭ','ᜮ'=>'ᜮ','ᜯ'=>'ᜯ','ᜰ'=>'ᜰ','ᜱ'=>'ᜱ','ᜲ'=>'ᜲ','ᜳ'=>'ᜳ','᜴'=>'᜴','ᝀ'=>'ᝀ','ᝁ'=>'ᝁ','ᝂ'=>'ᝂ','ᝃ'=>'ᝃ','ᝄ'=>'ᝄ','ᝅ'=>'ᝅ','ᝆ'=>'ᝆ','ᝇ'=>'ᝇ','ᝈ'=>'ᝈ','ᝉ'=>'ᝉ','ᝊ'=>'ᝊ','ᝋ'=>'ᝋ','ᝌ'=>'ᝌ','ᝍ'=>'ᝍ','ᝎ'=>'ᝎ','ᝏ'=>'ᝏ','ᝐ'=>'ᝐ','ᝑ'=>'ᝑ','ᝒ'=>'ᝒ','ᝓ'=>'ᝓ','ᝠ'=>'ᝠ','ᝡ'=>'ᝡ','ᝢ'=>'ᝢ','ᝣ'=>'ᝣ','ᝤ'=>'ᝤ','ᝥ'=>'ᝥ','ᝦ'=>'ᝦ','ᝧ'=>'ᝧ','ᝨ'=>'ᝨ','ᝩ'=>'ᝩ','ᝪ'=>'ᝪ','ᝫ'=>'ᝫ','ᝬ'=>'ᝬ','ᝮ'=>'ᝮ','ᝯ'=>'ᝯ','ᝰ'=>'ᝰ','ᝲ'=>'ᝲ','ᝳ'=>'ᝳ','ក'=>'ក','ខ'=>'ខ','គ'=>'គ','ឃ'=>'ឃ','ង'=>'ង','ច'=>'ច','ឆ'=>'ឆ','ជ'=>'ជ','ឈ'=>'ឈ','ញ'=>'ញ','ដ'=>'ដ','ឋ'=>'ឋ','ឌ'=>'ឌ','ឍ'=>'ឍ','ណ'=>'ណ','ត'=>'ត','ថ'=>'ថ','ទ'=>'ទ','ធ'=>'ធ','ន'=>'ន','ប'=>'ប','ផ'=>'ផ','ព'=>'ព','ភ'=>'ភ','ម'=>'ម','យ'=>'យ','រ'=>'រ','ល'=>'ល','វ'=>'វ','ឝ'=>'ឝ','ឞ'=>'ឞ','ស'=>'ស','ហ'=>'ហ','ឡ'=>'ឡ','អ'=>'អ','ឣ'=>'ឣ','ឤ'=>'ឤ','ឥ'=>'ឥ','ឦ'=>'ឦ','ឧ'=>'ឧ','ឨ'=>'ឨ','ឩ'=>'ឩ','ឪ'=>'ឪ','ឫ'=>'ឫ','ឬ'=>'ឬ','ឭ'=>'ឭ','ឮ'=>'ឮ','ឯ'=>'ឯ','ឰ'=>'ឰ','ឱ'=>'ឱ','ឲ'=>'ឲ','ឳ'=>'ឳ','ា'=>'ា','ិ'=>'ិ','ី'=>'ី','ឹ'=>'ឹ','ឺ'=>'ឺ','ុ'=>'ុ','ូ'=>'ូ','ួ'=>'ួ','ើ'=>'ើ','ឿ'=>'ឿ','ៀ'=>'ៀ','េ'=>'េ','ែ'=>'ែ','ៃ'=>'ៃ','ោ'=>'ោ','ៅ'=>'ៅ','ំ'=>'ំ','ះ'=>'ះ','ៈ'=>'ៈ','៉'=>'៉','៊'=>'៊','់'=>'់','៌'=>'៌','៍'=>'៍','៎'=>'៎','៏'=>'៏','័'=>'័','៑'=>'៑','្'=>'្','៓'=>'៓','ៗ'=>'ៗ','ៜ'=>'ៜ','៝'=>'៝','០'=>'0','១'=>'1','២'=>'2','៣'=>'3','៤'=>'4','៥'=>'5','៦'=>'6','៧'=>'7','៨'=>'8','៩'=>'9','៰'=>'0','៱'=>'1','៲'=>'2','៳'=>'3','៴'=>'4','៵'=>'5','៶'=>'6','៷'=>'7','៸'=>'8','៹'=>'9'); diff --git a/includes/utf/data/search_indexer_20.php b/includes/utf/data/search_indexer_20.php new file mode 100644 index 0000000..0d2dfa6 --- /dev/null +++ b/includes/utf/data/search_indexer_20.php @@ -0,0 +1 @@ +'ꀀ','ꀁ'=>'ꀁ','ꀂ'=>'ꀂ','ꀃ'=>'ꀃ','ꀄ'=>'ꀄ','ꀅ'=>'ꀅ','ꀆ'=>'ꀆ','ꀇ'=>'ꀇ','ꀈ'=>'ꀈ','ꀉ'=>'ꀉ','ꀊ'=>'ꀊ','ꀋ'=>'ꀋ','ꀌ'=>'ꀌ','ꀍ'=>'ꀍ','ꀎ'=>'ꀎ','ꀏ'=>'ꀏ','ꀐ'=>'ꀐ','ꀑ'=>'ꀑ','ꀒ'=>'ꀒ','ꀓ'=>'ꀓ','ꀔ'=>'ꀔ','ꀕ'=>'ꀕ','ꀖ'=>'ꀖ','ꀗ'=>'ꀗ','ꀘ'=>'ꀘ','ꀙ'=>'ꀙ','ꀚ'=>'ꀚ','ꀛ'=>'ꀛ','ꀜ'=>'ꀜ','ꀝ'=>'ꀝ','ꀞ'=>'ꀞ','ꀟ'=>'ꀟ','ꀠ'=>'ꀠ','ꀡ'=>'ꀡ','ꀢ'=>'ꀢ','ꀣ'=>'ꀣ','ꀤ'=>'ꀤ','ꀥ'=>'ꀥ','ꀦ'=>'ꀦ','ꀧ'=>'ꀧ','ꀨ'=>'ꀨ','ꀩ'=>'ꀩ','ꀪ'=>'ꀪ','ꀫ'=>'ꀫ','ꀬ'=>'ꀬ','ꀭ'=>'ꀭ','ꀮ'=>'ꀮ','ꀯ'=>'ꀯ','ꀰ'=>'ꀰ','ꀱ'=>'ꀱ','ꀲ'=>'ꀲ','ꀳ'=>'ꀳ','ꀴ'=>'ꀴ','ꀵ'=>'ꀵ','ꀶ'=>'ꀶ','ꀷ'=>'ꀷ','ꀸ'=>'ꀸ','ꀹ'=>'ꀹ','ꀺ'=>'ꀺ','ꀻ'=>'ꀻ','ꀼ'=>'ꀼ','ꀽ'=>'ꀽ','ꀾ'=>'ꀾ','ꀿ'=>'ꀿ','ꁀ'=>'ꁀ','ꁁ'=>'ꁁ','ꁂ'=>'ꁂ','ꁃ'=>'ꁃ','ꁄ'=>'ꁄ','ꁅ'=>'ꁅ','ꁆ'=>'ꁆ','ꁇ'=>'ꁇ','ꁈ'=>'ꁈ','ꁉ'=>'ꁉ','ꁊ'=>'ꁊ','ꁋ'=>'ꁋ','ꁌ'=>'ꁌ','ꁍ'=>'ꁍ','ꁎ'=>'ꁎ','ꁏ'=>'ꁏ','ꁐ'=>'ꁐ','ꁑ'=>'ꁑ','ꁒ'=>'ꁒ','ꁓ'=>'ꁓ','ꁔ'=>'ꁔ','ꁕ'=>'ꁕ','ꁖ'=>'ꁖ','ꁗ'=>'ꁗ','ꁘ'=>'ꁘ','ꁙ'=>'ꁙ','ꁚ'=>'ꁚ','ꁛ'=>'ꁛ','ꁜ'=>'ꁜ','ꁝ'=>'ꁝ','ꁞ'=>'ꁞ','ꁟ'=>'ꁟ','ꁠ'=>'ꁠ','ꁡ'=>'ꁡ','ꁢ'=>'ꁢ','ꁣ'=>'ꁣ','ꁤ'=>'ꁤ','ꁥ'=>'ꁥ','ꁦ'=>'ꁦ','ꁧ'=>'ꁧ','ꁨ'=>'ꁨ','ꁩ'=>'ꁩ','ꁪ'=>'ꁪ','ꁫ'=>'ꁫ','ꁬ'=>'ꁬ','ꁭ'=>'ꁭ','ꁮ'=>'ꁮ','ꁯ'=>'ꁯ','ꁰ'=>'ꁰ','ꁱ'=>'ꁱ','ꁲ'=>'ꁲ','ꁳ'=>'ꁳ','ꁴ'=>'ꁴ','ꁵ'=>'ꁵ','ꁶ'=>'ꁶ','ꁷ'=>'ꁷ','ꁸ'=>'ꁸ','ꁹ'=>'ꁹ','ꁺ'=>'ꁺ','ꁻ'=>'ꁻ','ꁼ'=>'ꁼ','ꁽ'=>'ꁽ','ꁾ'=>'ꁾ','ꁿ'=>'ꁿ','ꂀ'=>'ꂀ','ꂁ'=>'ꂁ','ꂂ'=>'ꂂ','ꂃ'=>'ꂃ','ꂄ'=>'ꂄ','ꂅ'=>'ꂅ','ꂆ'=>'ꂆ','ꂇ'=>'ꂇ','ꂈ'=>'ꂈ','ꂉ'=>'ꂉ','ꂊ'=>'ꂊ','ꂋ'=>'ꂋ','ꂌ'=>'ꂌ','ꂍ'=>'ꂍ','ꂎ'=>'ꂎ','ꂏ'=>'ꂏ','ꂐ'=>'ꂐ','ꂑ'=>'ꂑ','ꂒ'=>'ꂒ','ꂓ'=>'ꂓ','ꂔ'=>'ꂔ','ꂕ'=>'ꂕ','ꂖ'=>'ꂖ','ꂗ'=>'ꂗ','ꂘ'=>'ꂘ','ꂙ'=>'ꂙ','ꂚ'=>'ꂚ','ꂛ'=>'ꂛ','ꂜ'=>'ꂜ','ꂝ'=>'ꂝ','ꂞ'=>'ꂞ','ꂟ'=>'ꂟ','ꂠ'=>'ꂠ','ꂡ'=>'ꂡ','ꂢ'=>'ꂢ','ꂣ'=>'ꂣ','ꂤ'=>'ꂤ','ꂥ'=>'ꂥ','ꂦ'=>'ꂦ','ꂧ'=>'ꂧ','ꂨ'=>'ꂨ','ꂩ'=>'ꂩ','ꂪ'=>'ꂪ','ꂫ'=>'ꂫ','ꂬ'=>'ꂬ','ꂭ'=>'ꂭ','ꂮ'=>'ꂮ','ꂯ'=>'ꂯ','ꂰ'=>'ꂰ','ꂱ'=>'ꂱ','ꂲ'=>'ꂲ','ꂳ'=>'ꂳ','ꂴ'=>'ꂴ','ꂵ'=>'ꂵ','ꂶ'=>'ꂶ','ꂷ'=>'ꂷ','ꂸ'=>'ꂸ','ꂹ'=>'ꂹ','ꂺ'=>'ꂺ','ꂻ'=>'ꂻ','ꂼ'=>'ꂼ','ꂽ'=>'ꂽ','ꂾ'=>'ꂾ','ꂿ'=>'ꂿ','ꃀ'=>'ꃀ','ꃁ'=>'ꃁ','ꃂ'=>'ꃂ','ꃃ'=>'ꃃ','ꃄ'=>'ꃄ','ꃅ'=>'ꃅ','ꃆ'=>'ꃆ','ꃇ'=>'ꃇ','ꃈ'=>'ꃈ','ꃉ'=>'ꃉ','ꃊ'=>'ꃊ','ꃋ'=>'ꃋ','ꃌ'=>'ꃌ','ꃍ'=>'ꃍ','ꃎ'=>'ꃎ','ꃏ'=>'ꃏ','ꃐ'=>'ꃐ','ꃑ'=>'ꃑ','ꃒ'=>'ꃒ','ꃓ'=>'ꃓ','ꃔ'=>'ꃔ','ꃕ'=>'ꃕ','ꃖ'=>'ꃖ','ꃗ'=>'ꃗ','ꃘ'=>'ꃘ','ꃙ'=>'ꃙ','ꃚ'=>'ꃚ','ꃛ'=>'ꃛ','ꃜ'=>'ꃜ','ꃝ'=>'ꃝ','ꃞ'=>'ꃞ','ꃟ'=>'ꃟ','ꃠ'=>'ꃠ','ꃡ'=>'ꃡ','ꃢ'=>'ꃢ','ꃣ'=>'ꃣ','ꃤ'=>'ꃤ','ꃥ'=>'ꃥ','ꃦ'=>'ꃦ','ꃧ'=>'ꃧ','ꃨ'=>'ꃨ','ꃩ'=>'ꃩ','ꃪ'=>'ꃪ','ꃫ'=>'ꃫ','ꃬ'=>'ꃬ','ꃭ'=>'ꃭ','ꃮ'=>'ꃮ','ꃯ'=>'ꃯ','ꃰ'=>'ꃰ','ꃱ'=>'ꃱ','ꃲ'=>'ꃲ','ꃳ'=>'ꃳ','ꃴ'=>'ꃴ','ꃵ'=>'ꃵ','ꃶ'=>'ꃶ','ꃷ'=>'ꃷ','ꃸ'=>'ꃸ','ꃹ'=>'ꃹ','ꃺ'=>'ꃺ','ꃻ'=>'ꃻ','ꃼ'=>'ꃼ','ꃽ'=>'ꃽ','ꃾ'=>'ꃾ','ꃿ'=>'ꃿ','ꄀ'=>'ꄀ','ꄁ'=>'ꄁ','ꄂ'=>'ꄂ','ꄃ'=>'ꄃ','ꄄ'=>'ꄄ','ꄅ'=>'ꄅ','ꄆ'=>'ꄆ','ꄇ'=>'ꄇ','ꄈ'=>'ꄈ','ꄉ'=>'ꄉ','ꄊ'=>'ꄊ','ꄋ'=>'ꄋ','ꄌ'=>'ꄌ','ꄍ'=>'ꄍ','ꄎ'=>'ꄎ','ꄏ'=>'ꄏ','ꄐ'=>'ꄐ','ꄑ'=>'ꄑ','ꄒ'=>'ꄒ','ꄓ'=>'ꄓ','ꄔ'=>'ꄔ','ꄕ'=>'ꄕ','ꄖ'=>'ꄖ','ꄗ'=>'ꄗ','ꄘ'=>'ꄘ','ꄙ'=>'ꄙ','ꄚ'=>'ꄚ','ꄛ'=>'ꄛ','ꄜ'=>'ꄜ','ꄝ'=>'ꄝ','ꄞ'=>'ꄞ','ꄟ'=>'ꄟ','ꄠ'=>'ꄠ','ꄡ'=>'ꄡ','ꄢ'=>'ꄢ','ꄣ'=>'ꄣ','ꄤ'=>'ꄤ','ꄥ'=>'ꄥ','ꄦ'=>'ꄦ','ꄧ'=>'ꄧ','ꄨ'=>'ꄨ','ꄩ'=>'ꄩ','ꄪ'=>'ꄪ','ꄫ'=>'ꄫ','ꄬ'=>'ꄬ','ꄭ'=>'ꄭ','ꄮ'=>'ꄮ','ꄯ'=>'ꄯ','ꄰ'=>'ꄰ','ꄱ'=>'ꄱ','ꄲ'=>'ꄲ','ꄳ'=>'ꄳ','ꄴ'=>'ꄴ','ꄵ'=>'ꄵ','ꄶ'=>'ꄶ','ꄷ'=>'ꄷ','ꄸ'=>'ꄸ','ꄹ'=>'ꄹ','ꄺ'=>'ꄺ','ꄻ'=>'ꄻ','ꄼ'=>'ꄼ','ꄽ'=>'ꄽ','ꄾ'=>'ꄾ','ꄿ'=>'ꄿ','ꅀ'=>'ꅀ','ꅁ'=>'ꅁ','ꅂ'=>'ꅂ','ꅃ'=>'ꅃ','ꅄ'=>'ꅄ','ꅅ'=>'ꅅ','ꅆ'=>'ꅆ','ꅇ'=>'ꅇ','ꅈ'=>'ꅈ','ꅉ'=>'ꅉ','ꅊ'=>'ꅊ','ꅋ'=>'ꅋ','ꅌ'=>'ꅌ','ꅍ'=>'ꅍ','ꅎ'=>'ꅎ','ꅏ'=>'ꅏ','ꅐ'=>'ꅐ','ꅑ'=>'ꅑ','ꅒ'=>'ꅒ','ꅓ'=>'ꅓ','ꅔ'=>'ꅔ','ꅕ'=>'ꅕ','ꅖ'=>'ꅖ','ꅗ'=>'ꅗ','ꅘ'=>'ꅘ','ꅙ'=>'ꅙ','ꅚ'=>'ꅚ','ꅛ'=>'ꅛ','ꅜ'=>'ꅜ','ꅝ'=>'ꅝ','ꅞ'=>'ꅞ','ꅟ'=>'ꅟ','ꅠ'=>'ꅠ','ꅡ'=>'ꅡ','ꅢ'=>'ꅢ','ꅣ'=>'ꅣ','ꅤ'=>'ꅤ','ꅥ'=>'ꅥ','ꅦ'=>'ꅦ','ꅧ'=>'ꅧ','ꅨ'=>'ꅨ','ꅩ'=>'ꅩ','ꅪ'=>'ꅪ','ꅫ'=>'ꅫ','ꅬ'=>'ꅬ','ꅭ'=>'ꅭ','ꅮ'=>'ꅮ','ꅯ'=>'ꅯ','ꅰ'=>'ꅰ','ꅱ'=>'ꅱ','ꅲ'=>'ꅲ','ꅳ'=>'ꅳ','ꅴ'=>'ꅴ','ꅵ'=>'ꅵ','ꅶ'=>'ꅶ','ꅷ'=>'ꅷ','ꅸ'=>'ꅸ','ꅹ'=>'ꅹ','ꅺ'=>'ꅺ','ꅻ'=>'ꅻ','ꅼ'=>'ꅼ','ꅽ'=>'ꅽ','ꅾ'=>'ꅾ','ꅿ'=>'ꅿ','ꆀ'=>'ꆀ','ꆁ'=>'ꆁ','ꆂ'=>'ꆂ','ꆃ'=>'ꆃ','ꆄ'=>'ꆄ','ꆅ'=>'ꆅ','ꆆ'=>'ꆆ','ꆇ'=>'ꆇ','ꆈ'=>'ꆈ','ꆉ'=>'ꆉ','ꆊ'=>'ꆊ','ꆋ'=>'ꆋ','ꆌ'=>'ꆌ','ꆍ'=>'ꆍ','ꆎ'=>'ꆎ','ꆏ'=>'ꆏ','ꆐ'=>'ꆐ','ꆑ'=>'ꆑ','ꆒ'=>'ꆒ','ꆓ'=>'ꆓ','ꆔ'=>'ꆔ','ꆕ'=>'ꆕ','ꆖ'=>'ꆖ','ꆗ'=>'ꆗ','ꆘ'=>'ꆘ','ꆙ'=>'ꆙ','ꆚ'=>'ꆚ','ꆛ'=>'ꆛ','ꆜ'=>'ꆜ','ꆝ'=>'ꆝ','ꆞ'=>'ꆞ','ꆟ'=>'ꆟ','ꆠ'=>'ꆠ','ꆡ'=>'ꆡ','ꆢ'=>'ꆢ','ꆣ'=>'ꆣ','ꆤ'=>'ꆤ','ꆥ'=>'ꆥ','ꆦ'=>'ꆦ','ꆧ'=>'ꆧ','ꆨ'=>'ꆨ','ꆩ'=>'ꆩ','ꆪ'=>'ꆪ','ꆫ'=>'ꆫ','ꆬ'=>'ꆬ','ꆭ'=>'ꆭ','ꆮ'=>'ꆮ','ꆯ'=>'ꆯ','ꆰ'=>'ꆰ','ꆱ'=>'ꆱ','ꆲ'=>'ꆲ','ꆳ'=>'ꆳ','ꆴ'=>'ꆴ','ꆵ'=>'ꆵ','ꆶ'=>'ꆶ','ꆷ'=>'ꆷ','ꆸ'=>'ꆸ','ꆹ'=>'ꆹ','ꆺ'=>'ꆺ','ꆻ'=>'ꆻ','ꆼ'=>'ꆼ','ꆽ'=>'ꆽ','ꆾ'=>'ꆾ','ꆿ'=>'ꆿ','ꇀ'=>'ꇀ','ꇁ'=>'ꇁ','ꇂ'=>'ꇂ','ꇃ'=>'ꇃ','ꇄ'=>'ꇄ','ꇅ'=>'ꇅ','ꇆ'=>'ꇆ','ꇇ'=>'ꇇ','ꇈ'=>'ꇈ','ꇉ'=>'ꇉ','ꇊ'=>'ꇊ','ꇋ'=>'ꇋ','ꇌ'=>'ꇌ','ꇍ'=>'ꇍ','ꇎ'=>'ꇎ','ꇏ'=>'ꇏ','ꇐ'=>'ꇐ','ꇑ'=>'ꇑ','ꇒ'=>'ꇒ','ꇓ'=>'ꇓ','ꇔ'=>'ꇔ','ꇕ'=>'ꇕ','ꇖ'=>'ꇖ','ꇗ'=>'ꇗ','ꇘ'=>'ꇘ','ꇙ'=>'ꇙ','ꇚ'=>'ꇚ','ꇛ'=>'ꇛ','ꇜ'=>'ꇜ','ꇝ'=>'ꇝ','ꇞ'=>'ꇞ','ꇟ'=>'ꇟ','ꇠ'=>'ꇠ','ꇡ'=>'ꇡ','ꇢ'=>'ꇢ','ꇣ'=>'ꇣ','ꇤ'=>'ꇤ','ꇥ'=>'ꇥ','ꇦ'=>'ꇦ','ꇧ'=>'ꇧ','ꇨ'=>'ꇨ','ꇩ'=>'ꇩ','ꇪ'=>'ꇪ','ꇫ'=>'ꇫ','ꇬ'=>'ꇬ','ꇭ'=>'ꇭ','ꇮ'=>'ꇮ','ꇯ'=>'ꇯ','ꇰ'=>'ꇰ','ꇱ'=>'ꇱ','ꇲ'=>'ꇲ','ꇳ'=>'ꇳ','ꇴ'=>'ꇴ','ꇵ'=>'ꇵ','ꇶ'=>'ꇶ','ꇷ'=>'ꇷ','ꇸ'=>'ꇸ','ꇹ'=>'ꇹ','ꇺ'=>'ꇺ','ꇻ'=>'ꇻ','ꇼ'=>'ꇼ','ꇽ'=>'ꇽ','ꇾ'=>'ꇾ','ꇿ'=>'ꇿ','ꈀ'=>'ꈀ','ꈁ'=>'ꈁ','ꈂ'=>'ꈂ','ꈃ'=>'ꈃ','ꈄ'=>'ꈄ','ꈅ'=>'ꈅ','ꈆ'=>'ꈆ','ꈇ'=>'ꈇ','ꈈ'=>'ꈈ','ꈉ'=>'ꈉ','ꈊ'=>'ꈊ','ꈋ'=>'ꈋ','ꈌ'=>'ꈌ','ꈍ'=>'ꈍ','ꈎ'=>'ꈎ','ꈏ'=>'ꈏ','ꈐ'=>'ꈐ','ꈑ'=>'ꈑ','ꈒ'=>'ꈒ','ꈓ'=>'ꈓ','ꈔ'=>'ꈔ','ꈕ'=>'ꈕ','ꈖ'=>'ꈖ','ꈗ'=>'ꈗ','ꈘ'=>'ꈘ','ꈙ'=>'ꈙ','ꈚ'=>'ꈚ','ꈛ'=>'ꈛ','ꈜ'=>'ꈜ','ꈝ'=>'ꈝ','ꈞ'=>'ꈞ','ꈟ'=>'ꈟ','ꈠ'=>'ꈠ','ꈡ'=>'ꈡ','ꈢ'=>'ꈢ','ꈣ'=>'ꈣ','ꈤ'=>'ꈤ','ꈥ'=>'ꈥ','ꈦ'=>'ꈦ','ꈧ'=>'ꈧ','ꈨ'=>'ꈨ','ꈩ'=>'ꈩ','ꈪ'=>'ꈪ','ꈫ'=>'ꈫ','ꈬ'=>'ꈬ','ꈭ'=>'ꈭ','ꈮ'=>'ꈮ','ꈯ'=>'ꈯ','ꈰ'=>'ꈰ','ꈱ'=>'ꈱ','ꈲ'=>'ꈲ','ꈳ'=>'ꈳ','ꈴ'=>'ꈴ','ꈵ'=>'ꈵ','ꈶ'=>'ꈶ','ꈷ'=>'ꈷ','ꈸ'=>'ꈸ','ꈹ'=>'ꈹ','ꈺ'=>'ꈺ','ꈻ'=>'ꈻ','ꈼ'=>'ꈼ','ꈽ'=>'ꈽ','ꈾ'=>'ꈾ','ꈿ'=>'ꈿ','ꉀ'=>'ꉀ','ꉁ'=>'ꉁ','ꉂ'=>'ꉂ','ꉃ'=>'ꉃ','ꉄ'=>'ꉄ','ꉅ'=>'ꉅ','ꉆ'=>'ꉆ','ꉇ'=>'ꉇ','ꉈ'=>'ꉈ','ꉉ'=>'ꉉ','ꉊ'=>'ꉊ','ꉋ'=>'ꉋ','ꉌ'=>'ꉌ','ꉍ'=>'ꉍ','ꉎ'=>'ꉎ','ꉏ'=>'ꉏ','ꉐ'=>'ꉐ','ꉑ'=>'ꉑ','ꉒ'=>'ꉒ','ꉓ'=>'ꉓ','ꉔ'=>'ꉔ','ꉕ'=>'ꉕ','ꉖ'=>'ꉖ','ꉗ'=>'ꉗ','ꉘ'=>'ꉘ','ꉙ'=>'ꉙ','ꉚ'=>'ꉚ','ꉛ'=>'ꉛ','ꉜ'=>'ꉜ','ꉝ'=>'ꉝ','ꉞ'=>'ꉞ','ꉟ'=>'ꉟ','ꉠ'=>'ꉠ','ꉡ'=>'ꉡ','ꉢ'=>'ꉢ','ꉣ'=>'ꉣ','ꉤ'=>'ꉤ','ꉥ'=>'ꉥ','ꉦ'=>'ꉦ','ꉧ'=>'ꉧ','ꉨ'=>'ꉨ','ꉩ'=>'ꉩ','ꉪ'=>'ꉪ','ꉫ'=>'ꉫ','ꉬ'=>'ꉬ','ꉭ'=>'ꉭ','ꉮ'=>'ꉮ','ꉯ'=>'ꉯ','ꉰ'=>'ꉰ','ꉱ'=>'ꉱ','ꉲ'=>'ꉲ','ꉳ'=>'ꉳ','ꉴ'=>'ꉴ','ꉵ'=>'ꉵ','ꉶ'=>'ꉶ','ꉷ'=>'ꉷ','ꉸ'=>'ꉸ','ꉹ'=>'ꉹ','ꉺ'=>'ꉺ','ꉻ'=>'ꉻ','ꉼ'=>'ꉼ','ꉽ'=>'ꉽ','ꉾ'=>'ꉾ','ꉿ'=>'ꉿ','ꊀ'=>'ꊀ','ꊁ'=>'ꊁ','ꊂ'=>'ꊂ','ꊃ'=>'ꊃ','ꊄ'=>'ꊄ','ꊅ'=>'ꊅ','ꊆ'=>'ꊆ','ꊇ'=>'ꊇ','ꊈ'=>'ꊈ','ꊉ'=>'ꊉ','ꊊ'=>'ꊊ','ꊋ'=>'ꊋ','ꊌ'=>'ꊌ','ꊍ'=>'ꊍ','ꊎ'=>'ꊎ','ꊏ'=>'ꊏ','ꊐ'=>'ꊐ','ꊑ'=>'ꊑ','ꊒ'=>'ꊒ','ꊓ'=>'ꊓ','ꊔ'=>'ꊔ','ꊕ'=>'ꊕ','ꊖ'=>'ꊖ','ꊗ'=>'ꊗ','ꊘ'=>'ꊘ','ꊙ'=>'ꊙ','ꊚ'=>'ꊚ','ꊛ'=>'ꊛ','ꊜ'=>'ꊜ','ꊝ'=>'ꊝ','ꊞ'=>'ꊞ','ꊟ'=>'ꊟ','ꊠ'=>'ꊠ','ꊡ'=>'ꊡ','ꊢ'=>'ꊢ','ꊣ'=>'ꊣ','ꊤ'=>'ꊤ','ꊥ'=>'ꊥ','ꊦ'=>'ꊦ','ꊧ'=>'ꊧ','ꊨ'=>'ꊨ','ꊩ'=>'ꊩ','ꊪ'=>'ꊪ','ꊫ'=>'ꊫ','ꊬ'=>'ꊬ','ꊭ'=>'ꊭ','ꊮ'=>'ꊮ','ꊯ'=>'ꊯ','ꊰ'=>'ꊰ','ꊱ'=>'ꊱ','ꊲ'=>'ꊲ','ꊳ'=>'ꊳ','ꊴ'=>'ꊴ','ꊵ'=>'ꊵ','ꊶ'=>'ꊶ','ꊷ'=>'ꊷ','ꊸ'=>'ꊸ','ꊹ'=>'ꊹ','ꊺ'=>'ꊺ','ꊻ'=>'ꊻ','ꊼ'=>'ꊼ','ꊽ'=>'ꊽ','ꊾ'=>'ꊾ','ꊿ'=>'ꊿ','ꋀ'=>'ꋀ','ꋁ'=>'ꋁ','ꋂ'=>'ꋂ','ꋃ'=>'ꋃ','ꋄ'=>'ꋄ','ꋅ'=>'ꋅ','ꋆ'=>'ꋆ','ꋇ'=>'ꋇ','ꋈ'=>'ꋈ','ꋉ'=>'ꋉ','ꋊ'=>'ꋊ','ꋋ'=>'ꋋ','ꋌ'=>'ꋌ','ꋍ'=>'ꋍ','ꋎ'=>'ꋎ','ꋏ'=>'ꋏ','ꋐ'=>'ꋐ','ꋑ'=>'ꋑ','ꋒ'=>'ꋒ','ꋓ'=>'ꋓ','ꋔ'=>'ꋔ','ꋕ'=>'ꋕ','ꋖ'=>'ꋖ','ꋗ'=>'ꋗ','ꋘ'=>'ꋘ','ꋙ'=>'ꋙ','ꋚ'=>'ꋚ','ꋛ'=>'ꋛ','ꋜ'=>'ꋜ','ꋝ'=>'ꋝ','ꋞ'=>'ꋞ','ꋟ'=>'ꋟ','ꋠ'=>'ꋠ','ꋡ'=>'ꋡ','ꋢ'=>'ꋢ','ꋣ'=>'ꋣ','ꋤ'=>'ꋤ','ꋥ'=>'ꋥ','ꋦ'=>'ꋦ','ꋧ'=>'ꋧ','ꋨ'=>'ꋨ','ꋩ'=>'ꋩ','ꋪ'=>'ꋪ','ꋫ'=>'ꋫ','ꋬ'=>'ꋬ','ꋭ'=>'ꋭ','ꋮ'=>'ꋮ','ꋯ'=>'ꋯ','ꋰ'=>'ꋰ','ꋱ'=>'ꋱ','ꋲ'=>'ꋲ','ꋳ'=>'ꋳ','ꋴ'=>'ꋴ','ꋵ'=>'ꋵ','ꋶ'=>'ꋶ','ꋷ'=>'ꋷ','ꋸ'=>'ꋸ','ꋹ'=>'ꋹ','ꋺ'=>'ꋺ','ꋻ'=>'ꋻ','ꋼ'=>'ꋼ','ꋽ'=>'ꋽ','ꋾ'=>'ꋾ','ꋿ'=>'ꋿ','ꌀ'=>'ꌀ','ꌁ'=>'ꌁ','ꌂ'=>'ꌂ','ꌃ'=>'ꌃ','ꌄ'=>'ꌄ','ꌅ'=>'ꌅ','ꌆ'=>'ꌆ','ꌇ'=>'ꌇ','ꌈ'=>'ꌈ','ꌉ'=>'ꌉ','ꌊ'=>'ꌊ','ꌋ'=>'ꌋ','ꌌ'=>'ꌌ','ꌍ'=>'ꌍ','ꌎ'=>'ꌎ','ꌏ'=>'ꌏ','ꌐ'=>'ꌐ','ꌑ'=>'ꌑ','ꌒ'=>'ꌒ','ꌓ'=>'ꌓ','ꌔ'=>'ꌔ','ꌕ'=>'ꌕ','ꌖ'=>'ꌖ','ꌗ'=>'ꌗ','ꌘ'=>'ꌘ','ꌙ'=>'ꌙ','ꌚ'=>'ꌚ','ꌛ'=>'ꌛ','ꌜ'=>'ꌜ','ꌝ'=>'ꌝ','ꌞ'=>'ꌞ','ꌟ'=>'ꌟ','ꌠ'=>'ꌠ','ꌡ'=>'ꌡ','ꌢ'=>'ꌢ','ꌣ'=>'ꌣ','ꌤ'=>'ꌤ','ꌥ'=>'ꌥ','ꌦ'=>'ꌦ','ꌧ'=>'ꌧ','ꌨ'=>'ꌨ','ꌩ'=>'ꌩ','ꌪ'=>'ꌪ','ꌫ'=>'ꌫ','ꌬ'=>'ꌬ','ꌭ'=>'ꌭ','ꌮ'=>'ꌮ','ꌯ'=>'ꌯ','ꌰ'=>'ꌰ','ꌱ'=>'ꌱ','ꌲ'=>'ꌲ','ꌳ'=>'ꌳ','ꌴ'=>'ꌴ','ꌵ'=>'ꌵ','ꌶ'=>'ꌶ','ꌷ'=>'ꌷ','ꌸ'=>'ꌸ','ꌹ'=>'ꌹ','ꌺ'=>'ꌺ','ꌻ'=>'ꌻ','ꌼ'=>'ꌼ','ꌽ'=>'ꌽ','ꌾ'=>'ꌾ','ꌿ'=>'ꌿ','ꍀ'=>'ꍀ','ꍁ'=>'ꍁ','ꍂ'=>'ꍂ','ꍃ'=>'ꍃ','ꍄ'=>'ꍄ','ꍅ'=>'ꍅ','ꍆ'=>'ꍆ','ꍇ'=>'ꍇ','ꍈ'=>'ꍈ','ꍉ'=>'ꍉ','ꍊ'=>'ꍊ','ꍋ'=>'ꍋ','ꍌ'=>'ꍌ','ꍍ'=>'ꍍ','ꍎ'=>'ꍎ','ꍏ'=>'ꍏ','ꍐ'=>'ꍐ','ꍑ'=>'ꍑ','ꍒ'=>'ꍒ','ꍓ'=>'ꍓ','ꍔ'=>'ꍔ','ꍕ'=>'ꍕ','ꍖ'=>'ꍖ','ꍗ'=>'ꍗ','ꍘ'=>'ꍘ','ꍙ'=>'ꍙ','ꍚ'=>'ꍚ','ꍛ'=>'ꍛ','ꍜ'=>'ꍜ','ꍝ'=>'ꍝ','ꍞ'=>'ꍞ','ꍟ'=>'ꍟ','ꍠ'=>'ꍠ','ꍡ'=>'ꍡ','ꍢ'=>'ꍢ','ꍣ'=>'ꍣ','ꍤ'=>'ꍤ','ꍥ'=>'ꍥ','ꍦ'=>'ꍦ','ꍧ'=>'ꍧ','ꍨ'=>'ꍨ','ꍩ'=>'ꍩ','ꍪ'=>'ꍪ','ꍫ'=>'ꍫ','ꍬ'=>'ꍬ','ꍭ'=>'ꍭ','ꍮ'=>'ꍮ','ꍯ'=>'ꍯ','ꍰ'=>'ꍰ','ꍱ'=>'ꍱ','ꍲ'=>'ꍲ','ꍳ'=>'ꍳ','ꍴ'=>'ꍴ','ꍵ'=>'ꍵ','ꍶ'=>'ꍶ','ꍷ'=>'ꍷ','ꍸ'=>'ꍸ','ꍹ'=>'ꍹ','ꍺ'=>'ꍺ','ꍻ'=>'ꍻ','ꍼ'=>'ꍼ','ꍽ'=>'ꍽ','ꍾ'=>'ꍾ','ꍿ'=>'ꍿ','ꎀ'=>'ꎀ','ꎁ'=>'ꎁ','ꎂ'=>'ꎂ','ꎃ'=>'ꎃ','ꎄ'=>'ꎄ','ꎅ'=>'ꎅ','ꎆ'=>'ꎆ','ꎇ'=>'ꎇ','ꎈ'=>'ꎈ','ꎉ'=>'ꎉ','ꎊ'=>'ꎊ','ꎋ'=>'ꎋ','ꎌ'=>'ꎌ','ꎍ'=>'ꎍ','ꎎ'=>'ꎎ','ꎏ'=>'ꎏ','ꎐ'=>'ꎐ','ꎑ'=>'ꎑ','ꎒ'=>'ꎒ','ꎓ'=>'ꎓ','ꎔ'=>'ꎔ','ꎕ'=>'ꎕ','ꎖ'=>'ꎖ','ꎗ'=>'ꎗ','ꎘ'=>'ꎘ','ꎙ'=>'ꎙ','ꎚ'=>'ꎚ','ꎛ'=>'ꎛ','ꎜ'=>'ꎜ','ꎝ'=>'ꎝ','ꎞ'=>'ꎞ','ꎟ'=>'ꎟ','ꎠ'=>'ꎠ','ꎡ'=>'ꎡ','ꎢ'=>'ꎢ','ꎣ'=>'ꎣ','ꎤ'=>'ꎤ','ꎥ'=>'ꎥ','ꎦ'=>'ꎦ','ꎧ'=>'ꎧ','ꎨ'=>'ꎨ','ꎩ'=>'ꎩ','ꎪ'=>'ꎪ','ꎫ'=>'ꎫ','ꎬ'=>'ꎬ','ꎭ'=>'ꎭ','ꎮ'=>'ꎮ','ꎯ'=>'ꎯ','ꎰ'=>'ꎰ','ꎱ'=>'ꎱ','ꎲ'=>'ꎲ','ꎳ'=>'ꎳ','ꎴ'=>'ꎴ','ꎵ'=>'ꎵ','ꎶ'=>'ꎶ','ꎷ'=>'ꎷ','ꎸ'=>'ꎸ','ꎹ'=>'ꎹ','ꎺ'=>'ꎺ','ꎻ'=>'ꎻ','ꎼ'=>'ꎼ','ꎽ'=>'ꎽ','ꎾ'=>'ꎾ','ꎿ'=>'ꎿ','ꏀ'=>'ꏀ','ꏁ'=>'ꏁ','ꏂ'=>'ꏂ','ꏃ'=>'ꏃ','ꏄ'=>'ꏄ','ꏅ'=>'ꏅ','ꏆ'=>'ꏆ','ꏇ'=>'ꏇ','ꏈ'=>'ꏈ','ꏉ'=>'ꏉ','ꏊ'=>'ꏊ','ꏋ'=>'ꏋ','ꏌ'=>'ꏌ','ꏍ'=>'ꏍ','ꏎ'=>'ꏎ','ꏏ'=>'ꏏ','ꏐ'=>'ꏐ','ꏑ'=>'ꏑ','ꏒ'=>'ꏒ','ꏓ'=>'ꏓ','ꏔ'=>'ꏔ','ꏕ'=>'ꏕ','ꏖ'=>'ꏖ','ꏗ'=>'ꏗ','ꏘ'=>'ꏘ','ꏙ'=>'ꏙ','ꏚ'=>'ꏚ','ꏛ'=>'ꏛ','ꏜ'=>'ꏜ','ꏝ'=>'ꏝ','ꏞ'=>'ꏞ','ꏟ'=>'ꏟ','ꏠ'=>'ꏠ','ꏡ'=>'ꏡ','ꏢ'=>'ꏢ','ꏣ'=>'ꏣ','ꏤ'=>'ꏤ','ꏥ'=>'ꏥ','ꏦ'=>'ꏦ','ꏧ'=>'ꏧ','ꏨ'=>'ꏨ','ꏩ'=>'ꏩ','ꏪ'=>'ꏪ','ꏫ'=>'ꏫ','ꏬ'=>'ꏬ','ꏭ'=>'ꏭ','ꏮ'=>'ꏮ','ꏯ'=>'ꏯ','ꏰ'=>'ꏰ','ꏱ'=>'ꏱ','ꏲ'=>'ꏲ','ꏳ'=>'ꏳ','ꏴ'=>'ꏴ','ꏵ'=>'ꏵ','ꏶ'=>'ꏶ','ꏷ'=>'ꏷ','ꏸ'=>'ꏸ','ꏹ'=>'ꏹ','ꏺ'=>'ꏺ','ꏻ'=>'ꏻ','ꏼ'=>'ꏼ','ꏽ'=>'ꏽ','ꏾ'=>'ꏾ','ꏿ'=>'ꏿ','ꐀ'=>'ꐀ','ꐁ'=>'ꐁ','ꐂ'=>'ꐂ','ꐃ'=>'ꐃ','ꐄ'=>'ꐄ','ꐅ'=>'ꐅ','ꐆ'=>'ꐆ','ꐇ'=>'ꐇ','ꐈ'=>'ꐈ','ꐉ'=>'ꐉ','ꐊ'=>'ꐊ','ꐋ'=>'ꐋ','ꐌ'=>'ꐌ','ꐍ'=>'ꐍ','ꐎ'=>'ꐎ','ꐏ'=>'ꐏ','ꐐ'=>'ꐐ','ꐑ'=>'ꐑ','ꐒ'=>'ꐒ','ꐓ'=>'ꐓ','ꐔ'=>'ꐔ','ꐕ'=>'ꐕ','ꐖ'=>'ꐖ','ꐗ'=>'ꐗ','ꐘ'=>'ꐘ','ꐙ'=>'ꐙ','ꐚ'=>'ꐚ','ꐛ'=>'ꐛ','ꐜ'=>'ꐜ','ꐝ'=>'ꐝ','ꐞ'=>'ꐞ','ꐟ'=>'ꐟ','ꐠ'=>'ꐠ','ꐡ'=>'ꐡ','ꐢ'=>'ꐢ','ꐣ'=>'ꐣ','ꐤ'=>'ꐤ','ꐥ'=>'ꐥ','ꐦ'=>'ꐦ','ꐧ'=>'ꐧ','ꐨ'=>'ꐨ','ꐩ'=>'ꐩ','ꐪ'=>'ꐪ','ꐫ'=>'ꐫ','ꐬ'=>'ꐬ','ꐭ'=>'ꐭ','ꐮ'=>'ꐮ','ꐯ'=>'ꐯ','ꐰ'=>'ꐰ','ꐱ'=>'ꐱ','ꐲ'=>'ꐲ','ꐳ'=>'ꐳ','ꐴ'=>'ꐴ','ꐵ'=>'ꐵ','ꐶ'=>'ꐶ','ꐷ'=>'ꐷ','ꐸ'=>'ꐸ','ꐹ'=>'ꐹ','ꐺ'=>'ꐺ','ꐻ'=>'ꐻ','ꐼ'=>'ꐼ','ꐽ'=>'ꐽ','ꐾ'=>'ꐾ','ꐿ'=>'ꐿ','ꑀ'=>'ꑀ','ꑁ'=>'ꑁ','ꑂ'=>'ꑂ','ꑃ'=>'ꑃ','ꑄ'=>'ꑄ','ꑅ'=>'ꑅ','ꑆ'=>'ꑆ','ꑇ'=>'ꑇ','ꑈ'=>'ꑈ','ꑉ'=>'ꑉ','ꑊ'=>'ꑊ','ꑋ'=>'ꑋ','ꑌ'=>'ꑌ','ꑍ'=>'ꑍ','ꑎ'=>'ꑎ','ꑏ'=>'ꑏ','ꑐ'=>'ꑐ','ꑑ'=>'ꑑ','ꑒ'=>'ꑒ','ꑓ'=>'ꑓ','ꑔ'=>'ꑔ','ꑕ'=>'ꑕ','ꑖ'=>'ꑖ','ꑗ'=>'ꑗ','ꑘ'=>'ꑘ','ꑙ'=>'ꑙ','ꑚ'=>'ꑚ','ꑛ'=>'ꑛ','ꑜ'=>'ꑜ','ꑝ'=>'ꑝ','ꑞ'=>'ꑞ','ꑟ'=>'ꑟ','ꑠ'=>'ꑠ','ꑡ'=>'ꑡ','ꑢ'=>'ꑢ','ꑣ'=>'ꑣ','ꑤ'=>'ꑤ','ꑥ'=>'ꑥ','ꑦ'=>'ꑦ','ꑧ'=>'ꑧ','ꑨ'=>'ꑨ','ꑩ'=>'ꑩ','ꑪ'=>'ꑪ','ꑫ'=>'ꑫ','ꑬ'=>'ꑬ','ꑭ'=>'ꑭ','ꑮ'=>'ꑮ','ꑯ'=>'ꑯ','ꑰ'=>'ꑰ','ꑱ'=>'ꑱ','ꑲ'=>'ꑲ','ꑳ'=>'ꑳ','ꑴ'=>'ꑴ','ꑵ'=>'ꑵ','ꑶ'=>'ꑶ','ꑷ'=>'ꑷ','ꑸ'=>'ꑸ','ꑹ'=>'ꑹ','ꑺ'=>'ꑺ','ꑻ'=>'ꑻ','ꑼ'=>'ꑼ','ꑽ'=>'ꑽ','ꑾ'=>'ꑾ','ꑿ'=>'ꑿ','ꒀ'=>'ꒀ','ꒁ'=>'ꒁ','ꒂ'=>'ꒂ','ꒃ'=>'ꒃ','ꒄ'=>'ꒄ','ꒅ'=>'ꒅ','ꒆ'=>'ꒆ','ꒇ'=>'ꒇ','ꒈ'=>'ꒈ','ꒉ'=>'ꒉ','ꒊ'=>'ꒊ','ꒋ'=>'ꒋ','ꒌ'=>'ꒌ','ꜗ'=>'ꜗ','ꜘ'=>'ꜘ','ꜙ'=>'ꜙ','ꜚ'=>'ꜚ'); diff --git a/includes/utf/data/search_indexer_21.php b/includes/utf/data/search_indexer_21.php new file mode 100644 index 0000000..34994b4 --- /dev/null +++ b/includes/utf/data/search_indexer_21.php @@ -0,0 +1 @@ +'ꠀ','ꠁ'=>'ꠁ','ꠂ'=>'ꠂ','ꠃ'=>'ꠃ','ꠄ'=>'ꠄ','ꠅ'=>'ꠅ','꠆'=>'꠆','ꠇ'=>'ꠇ','ꠈ'=>'ꠈ','ꠉ'=>'ꠉ','ꠊ'=>'ꠊ','ꠋ'=>'ꠋ','ꠌ'=>'ꠌ','ꠍ'=>'ꠍ','ꠎ'=>'ꠎ','ꠏ'=>'ꠏ','ꠐ'=>'ꠐ','ꠑ'=>'ꠑ','ꠒ'=>'ꠒ','ꠓ'=>'ꠓ','ꠔ'=>'ꠔ','ꠕ'=>'ꠕ','ꠖ'=>'ꠖ','ꠗ'=>'ꠗ','ꠘ'=>'ꠘ','ꠙ'=>'ꠙ','ꠚ'=>'ꠚ','ꠛ'=>'ꠛ','ꠜ'=>'ꠜ','ꠝ'=>'ꠝ','ꠞ'=>'ꠞ','ꠟ'=>'ꠟ','ꠠ'=>'ꠠ','ꠡ'=>'ꠡ','ꠢ'=>'ꠢ','ꠣ'=>'ꠣ','ꠤ'=>'ꠤ','ꠥ'=>'ꠥ','ꠦ'=>'ꠦ','ꠧ'=>'ꠧ','ꡀ'=>'ꡀ','ꡁ'=>'ꡁ','ꡂ'=>'ꡂ','ꡃ'=>'ꡃ','ꡄ'=>'ꡄ','ꡅ'=>'ꡅ','ꡆ'=>'ꡆ','ꡇ'=>'ꡇ','ꡈ'=>'ꡈ','ꡉ'=>'ꡉ','ꡊ'=>'ꡊ','ꡋ'=>'ꡋ','ꡌ'=>'ꡌ','ꡍ'=>'ꡍ','ꡎ'=>'ꡎ','ꡏ'=>'ꡏ','ꡐ'=>'ꡐ','ꡑ'=>'ꡑ','ꡒ'=>'ꡒ','ꡓ'=>'ꡓ','ꡔ'=>'ꡔ','ꡕ'=>'ꡕ','ꡖ'=>'ꡖ','ꡗ'=>'ꡗ','ꡘ'=>'ꡘ','ꡙ'=>'ꡙ','ꡚ'=>'ꡚ','ꡛ'=>'ꡛ','ꡜ'=>'ꡜ','ꡝ'=>'ꡝ','ꡞ'=>'ꡞ','ꡟ'=>'ꡟ','ꡠ'=>'ꡠ','ꡡ'=>'ꡡ','ꡢ'=>'ꡢ','ꡣ'=>'ꡣ','ꡤ'=>'ꡤ','ꡥ'=>'ꡥ','ꡦ'=>'ꡦ','ꡧ'=>'ꡧ','ꡨ'=>'ꡨ','ꡩ'=>'ꡩ','ꡪ'=>'ꡪ','ꡫ'=>'ꡫ','ꡬ'=>'ꡬ','ꡭ'=>'ꡭ','ꡮ'=>'ꡮ','ꡯ'=>'ꡯ','ꡰ'=>'ꡰ','ꡱ'=>'ꡱ','ꡲ'=>'ꡲ','ꡳ'=>'ꡳ','가'=>'가'); diff --git a/includes/utf/data/search_indexer_26.php b/includes/utf/data/search_indexer_26.php new file mode 100644 index 0000000..444ab96 --- /dev/null +++ b/includes/utf/data/search_indexer_26.php @@ -0,0 +1 @@ +'힣'); diff --git a/includes/utf/data/search_indexer_3.php b/includes/utf/data/search_indexer_3.php new file mode 100644 index 0000000..ceab762 --- /dev/null +++ b/includes/utf/data/search_indexer_3.php @@ -0,0 +1 @@ +'᠋','᠌'=>'᠌','᠍'=>'᠍','᠐'=>'0','᠑'=>'1','᠒'=>'2','᠓'=>'3','᠔'=>'4','᠕'=>'5','᠖'=>'6','᠗'=>'7','᠘'=>'8','᠙'=>'9','ᠠ'=>'ᠠ','ᠡ'=>'ᠡ','ᠢ'=>'ᠢ','ᠣ'=>'ᠣ','ᠤ'=>'ᠤ','ᠥ'=>'ᠥ','ᠦ'=>'ᠦ','ᠧ'=>'ᠧ','ᠨ'=>'ᠨ','ᠩ'=>'ᠩ','ᠪ'=>'ᠪ','ᠫ'=>'ᠫ','ᠬ'=>'ᠬ','ᠭ'=>'ᠭ','ᠮ'=>'ᠮ','ᠯ'=>'ᠯ','ᠰ'=>'ᠰ','ᠱ'=>'ᠱ','ᠲ'=>'ᠲ','ᠳ'=>'ᠳ','ᠴ'=>'ᠴ','ᠵ'=>'ᠵ','ᠶ'=>'ᠶ','ᠷ'=>'ᠷ','ᠸ'=>'ᠸ','ᠹ'=>'ᠹ','ᠺ'=>'ᠺ','ᠻ'=>'ᠻ','ᠼ'=>'ᠼ','ᠽ'=>'ᠽ','ᠾ'=>'ᠾ','ᠿ'=>'ᠿ','ᡀ'=>'ᡀ','ᡁ'=>'ᡁ','ᡂ'=>'ᡂ','ᡃ'=>'ᡃ','ᡄ'=>'ᡄ','ᡅ'=>'ᡅ','ᡆ'=>'ᡆ','ᡇ'=>'ᡇ','ᡈ'=>'ᡈ','ᡉ'=>'ᡉ','ᡊ'=>'ᡊ','ᡋ'=>'ᡋ','ᡌ'=>'ᡌ','ᡍ'=>'ᡍ','ᡎ'=>'ᡎ','ᡏ'=>'ᡏ','ᡐ'=>'ᡐ','ᡑ'=>'ᡑ','ᡒ'=>'ᡒ','ᡓ'=>'ᡓ','ᡔ'=>'ᡔ','ᡕ'=>'ᡕ','ᡖ'=>'ᡖ','ᡗ'=>'ᡗ','ᡘ'=>'ᡘ','ᡙ'=>'ᡙ','ᡚ'=>'ᡚ','ᡛ'=>'ᡛ','ᡜ'=>'ᡜ','ᡝ'=>'ᡝ','ᡞ'=>'ᡞ','ᡟ'=>'ᡟ','ᡠ'=>'ᡠ','ᡡ'=>'ᡡ','ᡢ'=>'ᡢ','ᡣ'=>'ᡣ','ᡤ'=>'ᡤ','ᡥ'=>'ᡥ','ᡦ'=>'ᡦ','ᡧ'=>'ᡧ','ᡨ'=>'ᡨ','ᡩ'=>'ᡩ','ᡪ'=>'ᡪ','ᡫ'=>'ᡫ','ᡬ'=>'ᡬ','ᡭ'=>'ᡭ','ᡮ'=>'ᡮ','ᡯ'=>'ᡯ','ᡰ'=>'ᡰ','ᡱ'=>'ᡱ','ᡲ'=>'ᡲ','ᡳ'=>'ᡳ','ᡴ'=>'ᡴ','ᡵ'=>'ᡵ','ᡶ'=>'ᡶ','ᡷ'=>'ᡷ','ᢀ'=>'ᢀ','ᢁ'=>'ᢁ','ᢂ'=>'ᢂ','ᢃ'=>'ᢃ','ᢄ'=>'ᢄ','ᢅ'=>'ᢅ','ᢆ'=>'ᢆ','ᢇ'=>'ᢇ','ᢈ'=>'ᢈ','ᢉ'=>'ᢉ','ᢊ'=>'ᢊ','ᢋ'=>'ᢋ','ᢌ'=>'ᢌ','ᢍ'=>'ᢍ','ᢎ'=>'ᢎ','ᢏ'=>'ᢏ','ᢐ'=>'ᢐ','ᢑ'=>'ᢑ','ᢒ'=>'ᢒ','ᢓ'=>'ᢓ','ᢔ'=>'ᢔ','ᢕ'=>'ᢕ','ᢖ'=>'ᢖ','ᢗ'=>'ᢗ','ᢘ'=>'ᢘ','ᢙ'=>'ᢙ','ᢚ'=>'ᢚ','ᢛ'=>'ᢛ','ᢜ'=>'ᢜ','ᢝ'=>'ᢝ','ᢞ'=>'ᢞ','ᢟ'=>'ᢟ','ᢠ'=>'ᢠ','ᢡ'=>'ᢡ','ᢢ'=>'ᢢ','ᢣ'=>'ᢣ','ᢤ'=>'ᢤ','ᢥ'=>'ᢥ','ᢦ'=>'ᢦ','ᢧ'=>'ᢧ','ᢨ'=>'ᢨ','ᢩ'=>'ᢩ','ᤀ'=>'ᤀ','ᤁ'=>'ᤁ','ᤂ'=>'ᤂ','ᤃ'=>'ᤃ','ᤄ'=>'ᤄ','ᤅ'=>'ᤅ','ᤆ'=>'ᤆ','ᤇ'=>'ᤇ','ᤈ'=>'ᤈ','ᤉ'=>'ᤉ','ᤊ'=>'ᤊ','ᤋ'=>'ᤋ','ᤌ'=>'ᤌ','ᤍ'=>'ᤍ','ᤎ'=>'ᤎ','ᤏ'=>'ᤏ','ᤐ'=>'ᤐ','ᤑ'=>'ᤑ','ᤒ'=>'ᤒ','ᤓ'=>'ᤓ','ᤔ'=>'ᤔ','ᤕ'=>'ᤕ','ᤖ'=>'ᤖ','ᤗ'=>'ᤗ','ᤘ'=>'ᤘ','ᤙ'=>'ᤙ','ᤚ'=>'ᤚ','ᤛ'=>'ᤛ','ᤜ'=>'ᤜ','ᤠ'=>'ᤠ','ᤡ'=>'ᤡ','ᤢ'=>'ᤢ','ᤣ'=>'ᤣ','ᤤ'=>'ᤤ','ᤥ'=>'ᤥ','ᤦ'=>'ᤦ','ᤧ'=>'ᤧ','ᤨ'=>'ᤨ','ᤩ'=>'ᤩ','ᤪ'=>'ᤪ','ᤫ'=>'ᤫ','ᤰ'=>'ᤰ','ᤱ'=>'ᤱ','ᤲ'=>'ᤲ','ᤳ'=>'ᤳ','ᤴ'=>'ᤴ','ᤵ'=>'ᤵ','ᤶ'=>'ᤶ','ᤷ'=>'ᤷ','ᤸ'=>'ᤸ','᤹'=>'᤹','᤺'=>'᤺','᤻'=>'᤻','᥆'=>'0','᥇'=>'1','᥈'=>'2','᥉'=>'3','᥊'=>'4','᥋'=>'5','᥌'=>'6','᥍'=>'7','᥎'=>'8','᥏'=>'9','ᥐ'=>'ᥐ','ᥑ'=>'ᥑ','ᥒ'=>'ᥒ','ᥓ'=>'ᥓ','ᥔ'=>'ᥔ','ᥕ'=>'ᥕ','ᥖ'=>'ᥖ','ᥗ'=>'ᥗ','ᥘ'=>'ᥘ','ᥙ'=>'ᥙ','ᥚ'=>'ᥚ','ᥛ'=>'ᥛ','ᥜ'=>'ᥜ','ᥝ'=>'ᥝ','ᥞ'=>'ᥞ','ᥟ'=>'ᥟ','ᥠ'=>'ᥠ','ᥡ'=>'ᥡ','ᥢ'=>'ᥢ','ᥣ'=>'ᥣ','ᥤ'=>'ᥤ','ᥥ'=>'ᥥ','ᥦ'=>'ᥦ','ᥧ'=>'ᥧ','ᥨ'=>'ᥨ','ᥩ'=>'ᥩ','ᥪ'=>'ᥪ','ᥫ'=>'ᥫ','ᥬ'=>'ᥬ','ᥭ'=>'ᥭ','ᥰ'=>'ᥰ','ᥱ'=>'ᥱ','ᥲ'=>'ᥲ','ᥳ'=>'ᥳ','ᥴ'=>'ᥴ','ᦀ'=>'ᦀ','ᦁ'=>'ᦁ','ᦂ'=>'ᦂ','ᦃ'=>'ᦃ','ᦄ'=>'ᦄ','ᦅ'=>'ᦅ','ᦆ'=>'ᦆ','ᦇ'=>'ᦇ','ᦈ'=>'ᦈ','ᦉ'=>'ᦉ','ᦊ'=>'ᦊ','ᦋ'=>'ᦋ','ᦌ'=>'ᦌ','ᦍ'=>'ᦍ','ᦎ'=>'ᦎ','ᦏ'=>'ᦏ','ᦐ'=>'ᦐ','ᦑ'=>'ᦑ','ᦒ'=>'ᦒ','ᦓ'=>'ᦓ','ᦔ'=>'ᦔ','ᦕ'=>'ᦕ','ᦖ'=>'ᦖ','ᦗ'=>'ᦗ','ᦘ'=>'ᦘ','ᦙ'=>'ᦙ','ᦚ'=>'ᦚ','ᦛ'=>'ᦛ','ᦜ'=>'ᦜ','ᦝ'=>'ᦝ','ᦞ'=>'ᦞ','ᦟ'=>'ᦟ','ᦠ'=>'ᦠ','ᦡ'=>'ᦡ','ᦢ'=>'ᦢ','ᦣ'=>'ᦣ','ᦤ'=>'ᦤ','ᦥ'=>'ᦥ','ᦦ'=>'ᦦ','ᦧ'=>'ᦧ','ᦨ'=>'ᦨ','ᦩ'=>'ᦩ','ᦰ'=>'ᦰ','ᦱ'=>'ᦱ','ᦲ'=>'ᦲ','ᦳ'=>'ᦳ','ᦴ'=>'ᦴ','ᦵ'=>'ᦵ','ᦶ'=>'ᦶ','ᦷ'=>'ᦷ','ᦸ'=>'ᦸ','ᦹ'=>'ᦹ','ᦺ'=>'ᦺ','ᦻ'=>'ᦻ','ᦼ'=>'ᦼ','ᦽ'=>'ᦽ','ᦾ'=>'ᦾ','ᦿ'=>'ᦿ','ᧀ'=>'ᧀ','ᧁ'=>'ᧁ','ᧂ'=>'ᧂ','ᧃ'=>'ᧃ','ᧄ'=>'ᧄ','ᧅ'=>'ᧅ','ᧆ'=>'ᧆ','ᧇ'=>'ᧇ','ᧈ'=>'ᧈ','ᧉ'=>'ᧉ','᧐'=>'0','᧑'=>'1','᧒'=>'2','᧓'=>'3','᧔'=>'4','᧕'=>'5','᧖'=>'6','᧗'=>'7','᧘'=>'8','᧙'=>'9','ᨀ'=>'ᨀ','ᨁ'=>'ᨁ','ᨂ'=>'ᨂ','ᨃ'=>'ᨃ','ᨄ'=>'ᨄ','ᨅ'=>'ᨅ','ᨆ'=>'ᨆ','ᨇ'=>'ᨇ','ᨈ'=>'ᨈ','ᨉ'=>'ᨉ','ᨊ'=>'ᨊ','ᨋ'=>'ᨋ','ᨌ'=>'ᨌ','ᨍ'=>'ᨍ','ᨎ'=>'ᨎ','ᨏ'=>'ᨏ','ᨐ'=>'ᨐ','ᨑ'=>'ᨑ','ᨒ'=>'ᨒ','ᨓ'=>'ᨓ','ᨔ'=>'ᨔ','ᨕ'=>'ᨕ','ᨖ'=>'ᨖ','ᨗ'=>'ᨗ','ᨘ'=>'ᨘ','ᨙ'=>'ᨙ','ᨚ'=>'ᨚ','ᨛ'=>'ᨛ','ᬀ'=>'ᬀ','ᬁ'=>'ᬁ','ᬂ'=>'ᬂ','ᬃ'=>'ᬃ','ᬄ'=>'ᬄ','ᬅ'=>'ᬅ','ᬆ'=>'ᬆ','ᬇ'=>'ᬇ','ᬈ'=>'ᬈ','ᬉ'=>'ᬉ','ᬊ'=>'ᬊ','ᬋ'=>'ᬋ','ᬌ'=>'ᬌ','ᬍ'=>'ᬍ','ᬎ'=>'ᬎ','ᬏ'=>'ᬏ','ᬐ'=>'ᬐ','ᬑ'=>'ᬑ','ᬒ'=>'ᬒ','ᬓ'=>'ᬓ','ᬔ'=>'ᬔ','ᬕ'=>'ᬕ','ᬖ'=>'ᬖ','ᬗ'=>'ᬗ','ᬘ'=>'ᬘ','ᬙ'=>'ᬙ','ᬚ'=>'ᬚ','ᬛ'=>'ᬛ','ᬜ'=>'ᬜ','ᬝ'=>'ᬝ','ᬞ'=>'ᬞ','ᬟ'=>'ᬟ','ᬠ'=>'ᬠ','ᬡ'=>'ᬡ','ᬢ'=>'ᬢ','ᬣ'=>'ᬣ','ᬤ'=>'ᬤ','ᬥ'=>'ᬥ','ᬦ'=>'ᬦ','ᬧ'=>'ᬧ','ᬨ'=>'ᬨ','ᬩ'=>'ᬩ','ᬪ'=>'ᬪ','ᬫ'=>'ᬫ','ᬬ'=>'ᬬ','ᬭ'=>'ᬭ','ᬮ'=>'ᬮ','ᬯ'=>'ᬯ','ᬰ'=>'ᬰ','ᬱ'=>'ᬱ','ᬲ'=>'ᬲ','ᬳ'=>'ᬳ','᬴'=>'᬴','ᬵ'=>'ᬵ','ᬶ'=>'ᬶ','ᬷ'=>'ᬷ','ᬸ'=>'ᬸ','ᬹ'=>'ᬹ','ᬺ'=>'ᬺ','ᬻ'=>'ᬻ','ᬼ'=>'ᬼ','ᬽ'=>'ᬽ','ᬾ'=>'ᬾ','ᬿ'=>'ᬿ','ᭀ'=>'ᭀ','ᭁ'=>'ᭁ','ᭂ'=>'ᭂ','ᭃ'=>'ᭃ','᭄'=>'᭄','ᭅ'=>'ᭅ','ᭆ'=>'ᭆ','ᭇ'=>'ᭇ','ᭈ'=>'ᭈ','ᭉ'=>'ᭉ','ᭊ'=>'ᭊ','ᭋ'=>'ᭋ','᭐'=>'0','᭑'=>'1','᭒'=>'2','᭓'=>'3','᭔'=>'4','᭕'=>'5','᭖'=>'6','᭗'=>'7','᭘'=>'8','᭙'=>'9','᭫'=>'᭫','᭬'=>'᭬','᭭'=>'᭭','᭮'=>'᭮','᭯'=>'᭯','᭰'=>'᭰','᭱'=>'᭱','᭲'=>'᭲','᭳'=>'᭳','ᴀ'=>'ᴀ','ᴁ'=>'ᴁ','ᴂ'=>'ᴂ','ᴃ'=>'ᴃ','ᴄ'=>'ᴄ','ᴅ'=>'ᴅ','ᴆ'=>'ᴆ','ᴇ'=>'ᴇ','ᴈ'=>'ᴈ','ᴉ'=>'ᴉ','ᴊ'=>'ᴊ','ᴋ'=>'ᴋ','ᴌ'=>'ᴌ','ᴍ'=>'ᴍ','ᴎ'=>'ᴎ','ᴏ'=>'ᴏ','ᴐ'=>'ᴐ','ᴑ'=>'ᴑ','ᴒ'=>'ᴒ','ᴓ'=>'ᴓ','ᴔ'=>'ᴔ','ᴕ'=>'ᴕ','ᴖ'=>'ᴖ','ᴗ'=>'ᴗ','ᴘ'=>'ᴘ','ᴙ'=>'ᴙ','ᴚ'=>'ᴚ','ᴛ'=>'ᴛ','ᴜ'=>'ᴜ','ᴝ'=>'ᴝ','ᴞ'=>'ᴞ','ᴟ'=>'ᴟ','ᴠ'=>'ᴠ','ᴡ'=>'ᴡ','ᴢ'=>'ᴢ','ᴣ'=>'ᴣ','ᴤ'=>'ᴤ','ᴥ'=>'ᴥ','ᴦ'=>'ᴦ','ᴧ'=>'ᴧ','ᴨ'=>'ᴨ','ᴩ'=>'ᴩ','ᴪ'=>'ᴪ','ᴫ'=>'ᴫ','ᴬ'=>'ᴬ','ᴭ'=>'ᴭ','ᴮ'=>'ᴮ','ᴯ'=>'ᴯ','ᴰ'=>'ᴰ','ᴱ'=>'ᴱ','ᴲ'=>'ᴲ','ᴳ'=>'ᴳ','ᴴ'=>'ᴴ','ᴵ'=>'ᴵ','ᴶ'=>'ᴶ','ᴷ'=>'ᴷ','ᴸ'=>'ᴸ','ᴹ'=>'ᴹ','ᴺ'=>'ᴺ','ᴻ'=>'ᴻ','ᴼ'=>'ᴼ','ᴽ'=>'ᴽ','ᴾ'=>'ᴾ','ᴿ'=>'ᴿ','ᵀ'=>'ᵀ','ᵁ'=>'ᵁ','ᵂ'=>'ᵂ','ᵃ'=>'ᵃ','ᵄ'=>'ᵄ','ᵅ'=>'ᵅ','ᵆ'=>'ᵆ','ᵇ'=>'ᵇ','ᵈ'=>'ᵈ','ᵉ'=>'ᵉ','ᵊ'=>'ᵊ','ᵋ'=>'ᵋ','ᵌ'=>'ᵌ','ᵍ'=>'ᵍ','ᵎ'=>'ᵎ','ᵏ'=>'ᵏ','ᵐ'=>'ᵐ','ᵑ'=>'ᵑ','ᵒ'=>'ᵒ','ᵓ'=>'ᵓ','ᵔ'=>'ᵔ','ᵕ'=>'ᵕ','ᵖ'=>'ᵖ','ᵗ'=>'ᵗ','ᵘ'=>'ᵘ','ᵙ'=>'ᵙ','ᵚ'=>'ᵚ','ᵛ'=>'ᵛ','ᵜ'=>'ᵜ','ᵝ'=>'ᵝ','ᵞ'=>'ᵞ','ᵟ'=>'ᵟ','ᵠ'=>'ᵠ','ᵡ'=>'ᵡ','ᵢ'=>'ᵢ','ᵣ'=>'ᵣ','ᵤ'=>'ᵤ','ᵥ'=>'ᵥ','ᵦ'=>'ᵦ','ᵧ'=>'ᵧ','ᵨ'=>'ᵨ','ᵩ'=>'ᵩ','ᵪ'=>'ᵪ','ᵫ'=>'ue','ᵬ'=>'ᵬ','ᵭ'=>'ᵭ','ᵮ'=>'ᵮ','ᵯ'=>'ᵯ','ᵰ'=>'ᵰ','ᵱ'=>'ᵱ','ᵲ'=>'ᵲ','ᵳ'=>'ᵳ','ᵴ'=>'ᵴ','ᵵ'=>'ᵵ','ᵶ'=>'ᵶ','ᵷ'=>'ᵷ','ᵸ'=>'ᵸ','ᵹ'=>'ᵹ','ᵺ'=>'ᵺ','ᵻ'=>'ᵻ','ᵼ'=>'ᵼ','ᵽ'=>'ᵽ','ᵾ'=>'ᵾ','ᵿ'=>'ᵿ','ᶀ'=>'ᶀ','ᶁ'=>'ᶁ','ᶂ'=>'ᶂ','ᶃ'=>'ᶃ','ᶄ'=>'ᶄ','ᶅ'=>'ᶅ','ᶆ'=>'ᶆ','ᶇ'=>'ᶇ','ᶈ'=>'ᶈ','ᶉ'=>'ᶉ','ᶊ'=>'ᶊ','ᶋ'=>'ᶋ','ᶌ'=>'ᶌ','ᶍ'=>'ᶍ','ᶎ'=>'ᶎ','ᶏ'=>'ᶏ','ᶐ'=>'ᶐ','ᶑ'=>'ᶑ','ᶒ'=>'ᶒ','ᶓ'=>'ᶓ','ᶔ'=>'ᶔ','ᶕ'=>'ᶕ','ᶖ'=>'ᶖ','ᶗ'=>'ᶗ','ᶘ'=>'ᶘ','ᶙ'=>'ᶙ','ᶚ'=>'ᶚ','ᶛ'=>'ᶛ','ᶜ'=>'ᶜ','ᶝ'=>'ᶝ','ᶞ'=>'ᶞ','ᶟ'=>'ᶟ','ᶠ'=>'ᶠ','ᶡ'=>'ᶡ','ᶢ'=>'ᶢ','ᶣ'=>'ᶣ','ᶤ'=>'ᶤ','ᶥ'=>'ᶥ','ᶦ'=>'ᶦ','ᶧ'=>'ᶧ','ᶨ'=>'ᶨ','ᶩ'=>'ᶩ','ᶪ'=>'ᶪ','ᶫ'=>'ᶫ','ᶬ'=>'ᶬ','ᶭ'=>'ᶭ','ᶮ'=>'ᶮ','ᶯ'=>'ᶯ','ᶰ'=>'ᶰ','ᶱ'=>'ᶱ','ᶲ'=>'ᶲ','ᶳ'=>'ᶳ','ᶴ'=>'ᶴ','ᶵ'=>'ᶵ','ᶶ'=>'ᶶ','ᶷ'=>'ᶷ','ᶸ'=>'ᶸ','ᶹ'=>'ᶹ','ᶺ'=>'ᶺ','ᶻ'=>'ᶻ','ᶼ'=>'ᶼ','ᶽ'=>'ᶽ','ᶾ'=>'ᶾ','ᶿ'=>'ᶿ','᷀'=>'᷀','᷁'=>'᷁','᷂'=>'᷂','᷃'=>'᷃','᷄'=>'᷄','᷅'=>'᷅','᷆'=>'᷆','᷇'=>'᷇','᷈'=>'᷈','᷉'=>'᷉','᷊'=>'᷊','᷾'=>'᷾','᷿'=>'᷿','Ḁ'=>'ḁ','ḁ'=>'ḁ','Ḃ'=>'ḃ','ḃ'=>'ḃ','Ḅ'=>'ḅ','ḅ'=>'ḅ','Ḇ'=>'ḇ','ḇ'=>'ḇ','Ḉ'=>'ḉ','ḉ'=>'ḉ','Ḋ'=>'ḋ','ḋ'=>'ḋ','Ḍ'=>'ḍ','ḍ'=>'ḍ','Ḏ'=>'ḏ','ḏ'=>'ḏ','Ḑ'=>'ḑ','ḑ'=>'ḑ','Ḓ'=>'ḓ','ḓ'=>'ḓ','Ḕ'=>'ḕ','ḕ'=>'ḕ','Ḗ'=>'ḗ','ḗ'=>'ḗ','Ḙ'=>'ḙ','ḙ'=>'ḙ','Ḛ'=>'ḛ','ḛ'=>'ḛ','Ḝ'=>'ḝ','ḝ'=>'ḝ','Ḟ'=>'ḟ','ḟ'=>'ḟ','Ḡ'=>'ḡ','ḡ'=>'ḡ','Ḣ'=>'ḣ','ḣ'=>'ḣ','Ḥ'=>'ḥ','ḥ'=>'ḥ','Ḧ'=>'ḧ','ḧ'=>'ḧ','Ḩ'=>'ḩ','ḩ'=>'ḩ','Ḫ'=>'ḫ','ḫ'=>'ḫ','Ḭ'=>'ḭ','ḭ'=>'ḭ','Ḯ'=>'ḯ','ḯ'=>'ḯ','Ḱ'=>'ḱ','ḱ'=>'ḱ','Ḳ'=>'ḳ','ḳ'=>'ḳ','Ḵ'=>'ḵ','ḵ'=>'ḵ','Ḷ'=>'ḷ','ḷ'=>'ḷ','Ḹ'=>'ḹ','ḹ'=>'ḹ','Ḻ'=>'ḻ','ḻ'=>'ḻ','Ḽ'=>'ḽ','ḽ'=>'ḽ','Ḿ'=>'ḿ','ḿ'=>'ḿ','Ṁ'=>'ṁ','ṁ'=>'ṁ','Ṃ'=>'ṃ','ṃ'=>'ṃ','Ṅ'=>'ṅ','ṅ'=>'ṅ','Ṇ'=>'ṇ','ṇ'=>'ṇ','Ṉ'=>'ṉ','ṉ'=>'ṉ','Ṋ'=>'ṋ','ṋ'=>'ṋ','Ṍ'=>'ṍ','ṍ'=>'ṍ','Ṏ'=>'ṏ','ṏ'=>'ṏ','Ṑ'=>'ṑ','ṑ'=>'ṑ','Ṓ'=>'ṓ','ṓ'=>'ṓ','Ṕ'=>'ṕ','ṕ'=>'ṕ','Ṗ'=>'ṗ','ṗ'=>'ṗ','Ṙ'=>'ṙ','ṙ'=>'ṙ','Ṛ'=>'ṛ','ṛ'=>'ṛ','Ṝ'=>'ṝ','ṝ'=>'ṝ','Ṟ'=>'ṟ','ṟ'=>'ṟ','Ṡ'=>'ṡ','ṡ'=>'ṡ','Ṣ'=>'ṣ','ṣ'=>'ṣ','Ṥ'=>'ṥ','ṥ'=>'ṥ','Ṧ'=>'ṧ','ṧ'=>'ṧ','Ṩ'=>'ṩ','ṩ'=>'ṩ','Ṫ'=>'ṫ','ṫ'=>'ṫ','Ṭ'=>'ṭ','ṭ'=>'ṭ','Ṯ'=>'ṯ','ṯ'=>'ṯ','Ṱ'=>'ṱ','ṱ'=>'ṱ','Ṳ'=>'ṳ','ṳ'=>'ṳ','Ṵ'=>'ṵ','ṵ'=>'ṵ','Ṷ'=>'ṷ','ṷ'=>'ṷ','Ṹ'=>'ṹ','ṹ'=>'ṹ','Ṻ'=>'ṻ','ṻ'=>'ṻ','Ṽ'=>'ṽ','ṽ'=>'ṽ','Ṿ'=>'ṿ','ṿ'=>'ṿ','Ẁ'=>'ẁ','ẁ'=>'ẁ','Ẃ'=>'ẃ','ẃ'=>'ẃ','Ẅ'=>'ẅ','ẅ'=>'ẅ','Ẇ'=>'ẇ','ẇ'=>'ẇ','Ẉ'=>'ẉ','ẉ'=>'ẉ','Ẋ'=>'ẋ','ẋ'=>'ẋ','Ẍ'=>'ẍ','ẍ'=>'ẍ','Ẏ'=>'ẏ','ẏ'=>'ẏ','Ẑ'=>'ẑ','ẑ'=>'ẑ','Ẓ'=>'ẓ','ẓ'=>'ẓ','Ẕ'=>'ẕ','ẕ'=>'ẕ','ẖ'=>'ẖ','ẗ'=>'ẗ','ẘ'=>'ẘ','ẙ'=>'ẙ','ẚ'=>'ẚ','ẛ'=>'ẛ','Ạ'=>'ạ','ạ'=>'ạ','Ả'=>'ả','ả'=>'ả','Ấ'=>'ấ','ấ'=>'ấ','Ầ'=>'ầ','ầ'=>'ầ','Ẩ'=>'ẩ','ẩ'=>'ẩ','Ẫ'=>'ẫ','ẫ'=>'ẫ','Ậ'=>'ậ','ậ'=>'ậ','Ắ'=>'ắ','ắ'=>'ắ','Ằ'=>'ằ','ằ'=>'ằ','Ẳ'=>'ẳ','ẳ'=>'ẳ','Ẵ'=>'ẵ','ẵ'=>'ẵ','Ặ'=>'ặ','ặ'=>'ặ','Ẹ'=>'ẹ','ẹ'=>'ẹ','Ẻ'=>'ẻ','ẻ'=>'ẻ','Ẽ'=>'ẽ','ẽ'=>'ẽ','Ế'=>'ế','ế'=>'ế','Ề'=>'ề','ề'=>'ề','Ể'=>'ể','ể'=>'ể','Ễ'=>'ễ','ễ'=>'ễ','Ệ'=>'ệ','ệ'=>'ệ','Ỉ'=>'ỉ','ỉ'=>'ỉ','Ị'=>'ị','ị'=>'ị','Ọ'=>'ọ','ọ'=>'ọ','Ỏ'=>'ỏ','ỏ'=>'ỏ','Ố'=>'ố','ố'=>'ố','Ồ'=>'ồ','ồ'=>'ồ','Ổ'=>'ổ','ổ'=>'ổ','Ỗ'=>'ỗ','ỗ'=>'ỗ','Ộ'=>'ộ','ộ'=>'ộ','Ớ'=>'ớ','ớ'=>'ớ','Ờ'=>'ờ','ờ'=>'ờ','Ở'=>'ở','ở'=>'ở','Ỡ'=>'ỡ','ỡ'=>'ỡ','Ợ'=>'ợ','ợ'=>'ợ','Ụ'=>'ụ','ụ'=>'ụ','Ủ'=>'ủ','ủ'=>'ủ','Ứ'=>'ứ','ứ'=>'ứ','Ừ'=>'ừ','ừ'=>'ừ','Ử'=>'ử','ử'=>'ử','Ữ'=>'ữ','ữ'=>'ữ','Ự'=>'ự','ự'=>'ự','Ỳ'=>'ỳ','ỳ'=>'ỳ','Ỵ'=>'ỵ','ỵ'=>'ỵ','Ỷ'=>'ỷ','ỷ'=>'ỷ','Ỹ'=>'ỹ','ỹ'=>'ỹ','ἀ'=>'ἀ','ἁ'=>'ἁ','ἂ'=>'ἂ','ἃ'=>'ἃ','ἄ'=>'ἄ','ἅ'=>'ἅ','ἆ'=>'ἆ','ἇ'=>'ἇ','Ἀ'=>'ἀ','Ἁ'=>'ἁ','Ἂ'=>'ἂ','Ἃ'=>'ἃ','Ἄ'=>'ἄ','Ἅ'=>'ἅ','Ἆ'=>'ἆ','Ἇ'=>'ἇ','ἐ'=>'ἐ','ἑ'=>'ἑ','ἒ'=>'ἒ','ἓ'=>'ἓ','ἔ'=>'ἔ','ἕ'=>'ἕ','Ἐ'=>'ἐ','Ἑ'=>'ἑ','Ἒ'=>'ἒ','Ἓ'=>'ἓ','Ἔ'=>'ἔ','Ἕ'=>'ἕ','ἠ'=>'ἠ','ἡ'=>'ἡ','ἢ'=>'ἢ','ἣ'=>'ἣ','ἤ'=>'ἤ','ἥ'=>'ἥ','ἦ'=>'ἦ','ἧ'=>'ἧ','Ἠ'=>'ἠ','Ἡ'=>'ἡ','Ἢ'=>'ἢ','Ἣ'=>'ἣ','Ἤ'=>'ἤ','Ἥ'=>'ἥ','Ἦ'=>'ἦ','Ἧ'=>'ἧ','ἰ'=>'ἰ','ἱ'=>'ἱ','ἲ'=>'ἲ','ἳ'=>'ἳ','ἴ'=>'ἴ','ἵ'=>'ἵ','ἶ'=>'ἶ','ἷ'=>'ἷ','Ἰ'=>'ἰ','Ἱ'=>'ἱ','Ἲ'=>'ἲ','Ἳ'=>'ἳ','Ἴ'=>'ἴ','Ἵ'=>'ἵ','Ἶ'=>'ἶ','Ἷ'=>'ἷ','ὀ'=>'ὀ','ὁ'=>'ὁ','ὂ'=>'ὂ','ὃ'=>'ὃ','ὄ'=>'ὄ','ὅ'=>'ὅ','Ὀ'=>'ὀ','Ὁ'=>'ὁ','Ὂ'=>'ὂ','Ὃ'=>'ὃ','Ὄ'=>'ὄ','Ὅ'=>'ὅ','ὐ'=>'ὐ','ὑ'=>'ὑ','ὒ'=>'ὒ','ὓ'=>'ὓ','ὔ'=>'ὔ','ὕ'=>'ὕ','ὖ'=>'ὖ','ὗ'=>'ὗ','Ὑ'=>'ὑ','Ὓ'=>'ὓ','Ὕ'=>'ὕ','Ὗ'=>'ὗ','ὠ'=>'ὠ','ὡ'=>'ὡ','ὢ'=>'ὢ','ὣ'=>'ὣ','ὤ'=>'ὤ','ὥ'=>'ὥ','ὦ'=>'ὦ','ὧ'=>'ὧ','Ὠ'=>'ὠ','Ὡ'=>'ὡ','Ὢ'=>'ὢ','Ὣ'=>'ὣ','Ὤ'=>'ὤ','Ὥ'=>'ὥ','Ὦ'=>'ὦ','Ὧ'=>'ὧ','ὰ'=>'ὰ','ά'=>'ά','ὲ'=>'ὲ','έ'=>'έ','ὴ'=>'ὴ','ή'=>'ή','ὶ'=>'ὶ','ί'=>'ί','ὸ'=>'ὸ','ό'=>'ό','ὺ'=>'ὺ','ύ'=>'ύ','ὼ'=>'ὼ','ώ'=>'ώ','ᾀ'=>'ᾀ','ᾁ'=>'ᾁ','ᾂ'=>'ᾂ','ᾃ'=>'ᾃ','ᾄ'=>'ᾄ','ᾅ'=>'ᾅ','ᾆ'=>'ᾆ','ᾇ'=>'ᾇ','ᾈ'=>'ᾀ','ᾉ'=>'ᾁ','ᾊ'=>'ᾂ','ᾋ'=>'ᾃ','ᾌ'=>'ᾄ','ᾍ'=>'ᾅ','ᾎ'=>'ᾆ','ᾏ'=>'ᾇ','ᾐ'=>'ᾐ','ᾑ'=>'ᾑ','ᾒ'=>'ᾒ','ᾓ'=>'ᾓ','ᾔ'=>'ᾔ','ᾕ'=>'ᾕ','ᾖ'=>'ᾖ','ᾗ'=>'ᾗ','ᾘ'=>'ᾐ','ᾙ'=>'ᾑ','ᾚ'=>'ᾒ','ᾛ'=>'ᾓ','ᾜ'=>'ᾔ','ᾝ'=>'ᾕ','ᾞ'=>'ᾖ','ᾟ'=>'ᾗ','ᾠ'=>'ᾠ','ᾡ'=>'ᾡ','ᾢ'=>'ᾢ','ᾣ'=>'ᾣ','ᾤ'=>'ᾤ','ᾥ'=>'ᾥ','ᾦ'=>'ᾦ','ᾧ'=>'ᾧ','ᾨ'=>'ᾠ','ᾩ'=>'ᾡ','ᾪ'=>'ᾢ','ᾫ'=>'ᾣ','ᾬ'=>'ᾤ','ᾭ'=>'ᾥ','ᾮ'=>'ᾦ','ᾯ'=>'ᾧ','ᾰ'=>'ᾰ','ᾱ'=>'ᾱ','ᾲ'=>'ᾲ','ᾳ'=>'ᾳ','ᾴ'=>'ᾴ','ᾶ'=>'ᾶ','ᾷ'=>'ᾷ','Ᾰ'=>'ᾰ','Ᾱ'=>'ᾱ','Ὰ'=>'ὰ','Ά'=>'ά','ᾼ'=>'ᾳ','ι'=>'ι','ῂ'=>'ῂ','ῃ'=>'ῃ','ῄ'=>'ῄ','ῆ'=>'ῆ','ῇ'=>'ῇ','Ὲ'=>'ὲ','Έ'=>'έ','Ὴ'=>'ὴ','Ή'=>'ή','ῌ'=>'ῃ','ῐ'=>'ῐ','ῑ'=>'ῑ','ῒ'=>'ῒ','ΐ'=>'ΐ','ῖ'=>'ῖ','ῗ'=>'ῗ','Ῐ'=>'ῐ','Ῑ'=>'ῑ','Ὶ'=>'ὶ','Ί'=>'ί','ῠ'=>'ῠ','ῡ'=>'ῡ','ῢ'=>'ῢ','ΰ'=>'ΰ','ῤ'=>'ῤ','ῥ'=>'ῥ','ῦ'=>'ῦ','ῧ'=>'ῧ','Ῠ'=>'ῠ','Ῡ'=>'ῡ','Ὺ'=>'ὺ','Ύ'=>'ύ','Ῥ'=>'ῥ','ῲ'=>'ῲ','ῳ'=>'ῳ','ῴ'=>'ῴ','ῶ'=>'ῶ','ῷ'=>'ῷ','Ὸ'=>'ὸ','Ό'=>'ό','Ὼ'=>'ὼ','Ώ'=>'ώ','ῼ'=>'ῳ'); diff --git a/includes/utf/data/search_indexer_31.php b/includes/utf/data/search_indexer_31.php new file mode 100644 index 0000000..85961d3 --- /dev/null +++ b/includes/utf/data/search_indexer_31.php @@ -0,0 +1 @@ +'豈','更'=>'更','車'=>'車','賈'=>'賈','滑'=>'滑','串'=>'串','句'=>'句','龜'=>'龜','龜'=>'龜','契'=>'契','金'=>'金','喇'=>'喇','奈'=>'奈','懶'=>'懶','癩'=>'癩','羅'=>'羅','蘿'=>'蘿','螺'=>'螺','裸'=>'裸','邏'=>'邏','樂'=>'樂','洛'=>'洛','烙'=>'烙','珞'=>'珞','落'=>'落','酪'=>'酪','駱'=>'駱','亂'=>'亂','卵'=>'卵','欄'=>'欄','爛'=>'爛','蘭'=>'蘭','鸞'=>'鸞','嵐'=>'嵐','濫'=>'濫','藍'=>'藍','襤'=>'襤','拉'=>'拉','臘'=>'臘','蠟'=>'蠟','廊'=>'廊','朗'=>'朗','浪'=>'浪','狼'=>'狼','郎'=>'郎','來'=>'來','冷'=>'冷','勞'=>'勞','擄'=>'擄','櫓'=>'櫓','爐'=>'爐','盧'=>'盧','老'=>'老','蘆'=>'蘆','虜'=>'虜','路'=>'路','露'=>'露','魯'=>'魯','鷺'=>'鷺','碌'=>'碌','祿'=>'祿','綠'=>'綠','菉'=>'菉','錄'=>'錄','鹿'=>'鹿','論'=>'論','壟'=>'壟','弄'=>'弄','籠'=>'籠','聾'=>'聾','牢'=>'牢','磊'=>'磊','賂'=>'賂','雷'=>'雷','壘'=>'壘','屢'=>'屢','樓'=>'樓','淚'=>'淚','漏'=>'漏','累'=>'累','縷'=>'縷','陋'=>'陋','勒'=>'勒','肋'=>'肋','凜'=>'凜','凌'=>'凌','稜'=>'稜','綾'=>'綾','菱'=>'菱','陵'=>'陵','讀'=>'讀','拏'=>'拏','樂'=>'樂','諾'=>'諾','丹'=>'丹','寧'=>'寧','怒'=>'怒','率'=>'率','異'=>'異','北'=>'北','磻'=>'磻','便'=>'便','復'=>'復','不'=>'不','泌'=>'泌','數'=>'數','索'=>'索','參'=>'參','塞'=>'塞','省'=>'省','葉'=>'葉','說'=>'說','殺'=>'殺','辰'=>'辰','沈'=>'沈','拾'=>'拾','若'=>'若','掠'=>'掠','略'=>'略','亮'=>'亮','兩'=>'兩','凉'=>'凉','梁'=>'梁','糧'=>'糧','良'=>'良','諒'=>'諒','量'=>'量','勵'=>'勵','呂'=>'呂','女'=>'女','廬'=>'廬','旅'=>'旅','濾'=>'濾','礪'=>'礪','閭'=>'閭','驪'=>'驪','麗'=>'麗','黎'=>'黎','力'=>'力','曆'=>'曆','歷'=>'歷','轢'=>'轢','年'=>'年','憐'=>'憐','戀'=>'戀','撚'=>'撚','漣'=>'漣','煉'=>'煉','璉'=>'璉','秊'=>'秊','練'=>'練','聯'=>'聯','輦'=>'輦','蓮'=>'蓮','連'=>'連','鍊'=>'鍊','列'=>'列','劣'=>'劣','咽'=>'咽','烈'=>'烈','裂'=>'裂','說'=>'說','廉'=>'廉','念'=>'念','捻'=>'捻','殮'=>'殮','簾'=>'簾','獵'=>'獵','令'=>'令','囹'=>'囹','寧'=>'寧','嶺'=>'嶺','怜'=>'怜','玲'=>'玲','瑩'=>'瑩','羚'=>'羚','聆'=>'聆','鈴'=>'鈴','零'=>'零','靈'=>'靈','領'=>'領','例'=>'例','禮'=>'禮','醴'=>'醴','隸'=>'隸','惡'=>'惡','了'=>'了','僚'=>'僚','寮'=>'寮','尿'=>'尿','料'=>'料','樂'=>'樂','燎'=>'燎','療'=>'療','蓼'=>'蓼','遼'=>'遼','龍'=>'龍','暈'=>'暈','阮'=>'阮','劉'=>'劉','杻'=>'杻','柳'=>'柳','流'=>'流','溜'=>'溜','琉'=>'琉','留'=>'留','硫'=>'硫','紐'=>'紐','類'=>'類','六'=>'六','戮'=>'戮','陸'=>'陸','倫'=>'倫','崙'=>'崙','淪'=>'淪','輪'=>'輪','律'=>'律','慄'=>'慄','栗'=>'栗','率'=>'率','隆'=>'隆','利'=>'利','吏'=>'吏','履'=>'履','易'=>'易','李'=>'李','梨'=>'梨','泥'=>'泥','理'=>'理','痢'=>'痢','罹'=>'罹','裏'=>'裏','裡'=>'裡','里'=>'里','離'=>'離','匿'=>'匿','溺'=>'溺','吝'=>'吝','燐'=>'燐','璘'=>'璘','藺'=>'藺','隣'=>'隣','鱗'=>'鱗','麟'=>'麟','林'=>'林','淋'=>'淋','臨'=>'臨','立'=>'立','笠'=>'笠','粒'=>'粒','狀'=>'狀','炙'=>'炙','識'=>'識','什'=>'什','茶'=>'茶','刺'=>'刺','切'=>'切','度'=>'度','拓'=>'拓','糖'=>'糖','宅'=>'宅','洞'=>'洞','暴'=>'暴','輻'=>'輻','行'=>'行','降'=>'降','見'=>'見','廓'=>'廓','兀'=>'兀','嗀'=>'嗀','﨎'=>'﨎','﨏'=>'﨏','塚'=>'塚','﨑'=>'﨑','晴'=>'晴','﨓'=>'﨓','﨔'=>'﨔','凞'=>'凞','猪'=>'猪','益'=>'益','礼'=>'礼','神'=>'神','祥'=>'祥','福'=>'福','靖'=>'靖','精'=>'精','羽'=>'羽','﨟'=>'﨟','蘒'=>'蘒','﨡'=>'﨡','諸'=>'諸','﨣'=>'﨣','﨤'=>'﨤','逸'=>'逸','都'=>'都','﨧'=>'﨧','﨨'=>'﨨','﨩'=>'﨩','飯'=>'飯','飼'=>'飼','館'=>'館','鶴'=>'鶴','侮'=>'侮','僧'=>'僧','免'=>'免','勉'=>'勉','勤'=>'勤','卑'=>'卑','喝'=>'喝','嘆'=>'嘆','器'=>'器','塀'=>'塀','墨'=>'墨','層'=>'層','屮'=>'屮','悔'=>'悔','慨'=>'慨','憎'=>'憎','懲'=>'懲','敏'=>'敏','既'=>'既','暑'=>'暑','梅'=>'梅','海'=>'海','渚'=>'渚','漢'=>'漢','煮'=>'煮','爫'=>'爫','琢'=>'琢','碑'=>'碑','社'=>'社','祉'=>'祉','祈'=>'祈','祐'=>'祐','祖'=>'祖','祝'=>'祝','禍'=>'禍','禎'=>'禎','穀'=>'穀','突'=>'突','節'=>'節','練'=>'練','縉'=>'縉','繁'=>'繁','署'=>'署','者'=>'者','臭'=>'臭','艹'=>'艹','艹'=>'艹','著'=>'著','褐'=>'褐','視'=>'視','謁'=>'謁','謹'=>'謹','賓'=>'賓','贈'=>'贈','辶'=>'辶','逸'=>'逸','難'=>'難','響'=>'響','頻'=>'頻','並'=>'並','况'=>'况','全'=>'全','侀'=>'侀','充'=>'充','冀'=>'冀','勇'=>'勇','勺'=>'勺','喝'=>'喝','啕'=>'啕','喙'=>'喙','嗢'=>'嗢','塚'=>'塚','墳'=>'墳','奄'=>'奄','奔'=>'奔','婢'=>'婢','嬨'=>'嬨','廒'=>'廒','廙'=>'廙','彩'=>'彩','徭'=>'徭','惘'=>'惘','慎'=>'慎','愈'=>'愈','憎'=>'憎','慠'=>'慠','懲'=>'懲','戴'=>'戴','揄'=>'揄','搜'=>'搜','摒'=>'摒','敖'=>'敖','晴'=>'晴','朗'=>'朗','望'=>'望','杖'=>'杖','歹'=>'歹','殺'=>'殺','流'=>'流','滛'=>'滛','滋'=>'滋','漢'=>'漢','瀞'=>'瀞','煮'=>'煮','瞧'=>'瞧','爵'=>'爵','犯'=>'犯','猪'=>'猪','瑱'=>'瑱','甆'=>'甆','画'=>'画','瘝'=>'瘝','瘟'=>'瘟','益'=>'益','盛'=>'盛','直'=>'直','睊'=>'睊','着'=>'着','磌'=>'磌','窱'=>'窱','節'=>'節','类'=>'类','絛'=>'絛','練'=>'練','缾'=>'缾','者'=>'者','荒'=>'荒','華'=>'華','蝹'=>'蝹','襁'=>'襁','覆'=>'覆','視'=>'視','調'=>'調','諸'=>'諸','請'=>'請','謁'=>'謁','諾'=>'諾','諭'=>'諭','謹'=>'謹','變'=>'變','贈'=>'贈','輸'=>'輸','遲'=>'遲','醙'=>'醙','鉶'=>'鉶','陼'=>'陼','難'=>'難','靖'=>'靖','韛'=>'韛','響'=>'響','頋'=>'頋','頻'=>'頻','鬒'=>'鬒','龜'=>'龜','𢡊'=>'𢡊','𢡄'=>'𢡄','𣏕'=>'𣏕','㮝'=>'㮝','䀘'=>'䀘','䀹'=>'䀹','𥉉'=>'𥉉','𥳐'=>'𥳐','𧻓'=>'𧻓','齃'=>'齃','龎'=>'龎','ff'=>'ff','fi'=>'fi','fl'=>'fl','ffi'=>'ffi','ffl'=>'ffl','ſt'=>'ſt','st'=>'st','ﬓ'=>'ﬓ','ﬔ'=>'ﬔ','ﬕ'=>'ﬕ','ﬖ'=>'ﬖ','ﬗ'=>'ﬗ','יִ'=>'יִ','ﬞ'=>'ﬞ','ײַ'=>'ײַ','ﬠ'=>'ﬠ','ﬡ'=>'ﬡ','ﬢ'=>'ﬢ','ﬣ'=>'ﬣ','ﬤ'=>'ﬤ','ﬥ'=>'ﬥ','ﬦ'=>'ﬦ','ﬧ'=>'ﬧ','ﬨ'=>'ﬨ','שׁ'=>'שׁ','שׂ'=>'שׂ','שּׁ'=>'שּׁ','שּׂ'=>'שּׂ','אַ'=>'אַ','אָ'=>'אָ','אּ'=>'אּ','בּ'=>'בּ','גּ'=>'גּ','דּ'=>'דּ','הּ'=>'הּ','וּ'=>'וּ','זּ'=>'זּ','טּ'=>'טּ','יּ'=>'יּ','ךּ'=>'ךּ','כּ'=>'כּ','לּ'=>'לּ','מּ'=>'מּ','נּ'=>'נּ','סּ'=>'סּ','ףּ'=>'ףּ','פּ'=>'פּ','צּ'=>'צּ','קּ'=>'קּ','רּ'=>'רּ','שּ'=>'שּ','תּ'=>'תּ','וֹ'=>'וֹ','בֿ'=>'בֿ','כֿ'=>'כֿ','פֿ'=>'פֿ','ﭏ'=>'ﭏ','ﭐ'=>'ﭐ','ﭑ'=>'ﭑ','ﭒ'=>'ﭒ','ﭓ'=>'ﭓ','ﭔ'=>'ﭔ','ﭕ'=>'ﭕ','ﭖ'=>'ﭖ','ﭗ'=>'ﭗ','ﭘ'=>'ﭘ','ﭙ'=>'ﭙ','ﭚ'=>'ﭚ','ﭛ'=>'ﭛ','ﭜ'=>'ﭜ','ﭝ'=>'ﭝ','ﭞ'=>'ﭞ','ﭟ'=>'ﭟ','ﭠ'=>'ﭠ','ﭡ'=>'ﭡ','ﭢ'=>'ﭢ','ﭣ'=>'ﭣ','ﭤ'=>'ﭤ','ﭥ'=>'ﭥ','ﭦ'=>'ﭦ','ﭧ'=>'ﭧ','ﭨ'=>'ﭨ','ﭩ'=>'ﭩ','ﭪ'=>'ﭪ','ﭫ'=>'ﭫ','ﭬ'=>'ﭬ','ﭭ'=>'ﭭ','ﭮ'=>'ﭮ','ﭯ'=>'ﭯ','ﭰ'=>'ﭰ','ﭱ'=>'ﭱ','ﭲ'=>'ﭲ','ﭳ'=>'ﭳ','ﭴ'=>'ﭴ','ﭵ'=>'ﭵ','ﭶ'=>'ﭶ','ﭷ'=>'ﭷ','ﭸ'=>'ﭸ','ﭹ'=>'ﭹ','ﭺ'=>'ﭺ','ﭻ'=>'ﭻ','ﭼ'=>'ﭼ','ﭽ'=>'ﭽ','ﭾ'=>'ﭾ','ﭿ'=>'ﭿ','ﮀ'=>'ﮀ','ﮁ'=>'ﮁ','ﮂ'=>'ﮂ','ﮃ'=>'ﮃ','ﮄ'=>'ﮄ','ﮅ'=>'ﮅ','ﮆ'=>'ﮆ','ﮇ'=>'ﮇ','ﮈ'=>'ﮈ','ﮉ'=>'ﮉ','ﮊ'=>'ﮊ','ﮋ'=>'ﮋ','ﮌ'=>'ﮌ','ﮍ'=>'ﮍ','ﮎ'=>'ﮎ','ﮏ'=>'ﮏ','ﮐ'=>'ﮐ','ﮑ'=>'ﮑ','ﮒ'=>'ﮒ','ﮓ'=>'ﮓ','ﮔ'=>'ﮔ','ﮕ'=>'ﮕ','ﮖ'=>'ﮖ','ﮗ'=>'ﮗ','ﮘ'=>'ﮘ','ﮙ'=>'ﮙ','ﮚ'=>'ﮚ','ﮛ'=>'ﮛ','ﮜ'=>'ﮜ','ﮝ'=>'ﮝ','ﮞ'=>'ﮞ','ﮟ'=>'ﮟ','ﮠ'=>'ﮠ','ﮡ'=>'ﮡ','ﮢ'=>'ﮢ','ﮣ'=>'ﮣ','ﮤ'=>'ﮤ','ﮥ'=>'ﮥ','ﮦ'=>'ﮦ','ﮧ'=>'ﮧ','ﮨ'=>'ﮨ','ﮩ'=>'ﮩ','ﮪ'=>'ﮪ','ﮫ'=>'ﮫ','ﮬ'=>'ﮬ','ﮭ'=>'ﮭ','ﮮ'=>'ﮮ','ﮯ'=>'ﮯ','ﮰ'=>'ﮰ','ﮱ'=>'ﮱ','ﯓ'=>'ﯓ','ﯔ'=>'ﯔ','ﯕ'=>'ﯕ','ﯖ'=>'ﯖ','ﯗ'=>'ﯗ','ﯘ'=>'ﯘ','ﯙ'=>'ﯙ','ﯚ'=>'ﯚ','ﯛ'=>'ﯛ','ﯜ'=>'ﯜ','ﯝ'=>'ﯝ','ﯞ'=>'ﯞ','ﯟ'=>'ﯟ','ﯠ'=>'ﯠ','ﯡ'=>'ﯡ','ﯢ'=>'ﯢ','ﯣ'=>'ﯣ','ﯤ'=>'ﯤ','ﯥ'=>'ﯥ','ﯦ'=>'ﯦ','ﯧ'=>'ﯧ','ﯨ'=>'ﯨ','ﯩ'=>'ﯩ','ﯪ'=>'ﯪ','ﯫ'=>'ﯫ','ﯬ'=>'ﯬ','ﯭ'=>'ﯭ','ﯮ'=>'ﯮ','ﯯ'=>'ﯯ','ﯰ'=>'ﯰ','ﯱ'=>'ﯱ','ﯲ'=>'ﯲ','ﯳ'=>'ﯳ','ﯴ'=>'ﯴ','ﯵ'=>'ﯵ','ﯶ'=>'ﯶ','ﯷ'=>'ﯷ','ﯸ'=>'ﯸ','ﯹ'=>'ﯹ','ﯺ'=>'ﯺ','ﯻ'=>'ﯻ','ﯼ'=>'ﯼ','ﯽ'=>'ﯽ','ﯾ'=>'ﯾ','ﯿ'=>'ﯿ','ﰀ'=>'ﰀ','ﰁ'=>'ﰁ','ﰂ'=>'ﰂ','ﰃ'=>'ﰃ','ﰄ'=>'ﰄ','ﰅ'=>'ﰅ','ﰆ'=>'ﰆ','ﰇ'=>'ﰇ','ﰈ'=>'ﰈ','ﰉ'=>'ﰉ','ﰊ'=>'ﰊ','ﰋ'=>'ﰋ','ﰌ'=>'ﰌ','ﰍ'=>'ﰍ','ﰎ'=>'ﰎ','ﰏ'=>'ﰏ','ﰐ'=>'ﰐ','ﰑ'=>'ﰑ','ﰒ'=>'ﰒ','ﰓ'=>'ﰓ','ﰔ'=>'ﰔ','ﰕ'=>'ﰕ','ﰖ'=>'ﰖ','ﰗ'=>'ﰗ','ﰘ'=>'ﰘ','ﰙ'=>'ﰙ','ﰚ'=>'ﰚ','ﰛ'=>'ﰛ','ﰜ'=>'ﰜ','ﰝ'=>'ﰝ','ﰞ'=>'ﰞ','ﰟ'=>'ﰟ','ﰠ'=>'ﰠ','ﰡ'=>'ﰡ','ﰢ'=>'ﰢ','ﰣ'=>'ﰣ','ﰤ'=>'ﰤ','ﰥ'=>'ﰥ','ﰦ'=>'ﰦ','ﰧ'=>'ﰧ','ﰨ'=>'ﰨ','ﰩ'=>'ﰩ','ﰪ'=>'ﰪ','ﰫ'=>'ﰫ','ﰬ'=>'ﰬ','ﰭ'=>'ﰭ','ﰮ'=>'ﰮ','ﰯ'=>'ﰯ','ﰰ'=>'ﰰ','ﰱ'=>'ﰱ','ﰲ'=>'ﰲ','ﰳ'=>'ﰳ','ﰴ'=>'ﰴ','ﰵ'=>'ﰵ','ﰶ'=>'ﰶ','ﰷ'=>'ﰷ','ﰸ'=>'ﰸ','ﰹ'=>'ﰹ','ﰺ'=>'ﰺ','ﰻ'=>'ﰻ','ﰼ'=>'ﰼ','ﰽ'=>'ﰽ','ﰾ'=>'ﰾ','ﰿ'=>'ﰿ','ﱀ'=>'ﱀ','ﱁ'=>'ﱁ','ﱂ'=>'ﱂ','ﱃ'=>'ﱃ','ﱄ'=>'ﱄ','ﱅ'=>'ﱅ','ﱆ'=>'ﱆ','ﱇ'=>'ﱇ','ﱈ'=>'ﱈ','ﱉ'=>'ﱉ','ﱊ'=>'ﱊ','ﱋ'=>'ﱋ','ﱌ'=>'ﱌ','ﱍ'=>'ﱍ','ﱎ'=>'ﱎ','ﱏ'=>'ﱏ','ﱐ'=>'ﱐ','ﱑ'=>'ﱑ','ﱒ'=>'ﱒ','ﱓ'=>'ﱓ','ﱔ'=>'ﱔ','ﱕ'=>'ﱕ','ﱖ'=>'ﱖ','ﱗ'=>'ﱗ','ﱘ'=>'ﱘ','ﱙ'=>'ﱙ','ﱚ'=>'ﱚ','ﱛ'=>'ﱛ','ﱜ'=>'ﱜ','ﱝ'=>'ﱝ','ﱞ'=>'ﱞ','ﱟ'=>'ﱟ','ﱠ'=>'ﱠ','ﱡ'=>'ﱡ','ﱢ'=>'ﱢ','ﱣ'=>'ﱣ','ﱤ'=>'ﱤ','ﱥ'=>'ﱥ','ﱦ'=>'ﱦ','ﱧ'=>'ﱧ','ﱨ'=>'ﱨ','ﱩ'=>'ﱩ','ﱪ'=>'ﱪ','ﱫ'=>'ﱫ','ﱬ'=>'ﱬ','ﱭ'=>'ﱭ','ﱮ'=>'ﱮ','ﱯ'=>'ﱯ','ﱰ'=>'ﱰ','ﱱ'=>'ﱱ','ﱲ'=>'ﱲ','ﱳ'=>'ﱳ','ﱴ'=>'ﱴ','ﱵ'=>'ﱵ','ﱶ'=>'ﱶ','ﱷ'=>'ﱷ','ﱸ'=>'ﱸ','ﱹ'=>'ﱹ','ﱺ'=>'ﱺ','ﱻ'=>'ﱻ','ﱼ'=>'ﱼ','ﱽ'=>'ﱽ','ﱾ'=>'ﱾ','ﱿ'=>'ﱿ','ﲀ'=>'ﲀ','ﲁ'=>'ﲁ','ﲂ'=>'ﲂ','ﲃ'=>'ﲃ','ﲄ'=>'ﲄ','ﲅ'=>'ﲅ','ﲆ'=>'ﲆ','ﲇ'=>'ﲇ','ﲈ'=>'ﲈ','ﲉ'=>'ﲉ','ﲊ'=>'ﲊ','ﲋ'=>'ﲋ','ﲌ'=>'ﲌ','ﲍ'=>'ﲍ','ﲎ'=>'ﲎ','ﲏ'=>'ﲏ','ﲐ'=>'ﲐ','ﲑ'=>'ﲑ','ﲒ'=>'ﲒ','ﲓ'=>'ﲓ','ﲔ'=>'ﲔ','ﲕ'=>'ﲕ','ﲖ'=>'ﲖ','ﲗ'=>'ﲗ','ﲘ'=>'ﲘ','ﲙ'=>'ﲙ','ﲚ'=>'ﲚ','ﲛ'=>'ﲛ','ﲜ'=>'ﲜ','ﲝ'=>'ﲝ','ﲞ'=>'ﲞ','ﲟ'=>'ﲟ','ﲠ'=>'ﲠ','ﲡ'=>'ﲡ','ﲢ'=>'ﲢ','ﲣ'=>'ﲣ','ﲤ'=>'ﲤ','ﲥ'=>'ﲥ','ﲦ'=>'ﲦ','ﲧ'=>'ﲧ','ﲨ'=>'ﲨ','ﲩ'=>'ﲩ','ﲪ'=>'ﲪ','ﲫ'=>'ﲫ','ﲬ'=>'ﲬ','ﲭ'=>'ﲭ','ﲮ'=>'ﲮ','ﲯ'=>'ﲯ','ﲰ'=>'ﲰ','ﲱ'=>'ﲱ','ﲲ'=>'ﲲ','ﲳ'=>'ﲳ','ﲴ'=>'ﲴ','ﲵ'=>'ﲵ','ﲶ'=>'ﲶ','ﲷ'=>'ﲷ','ﲸ'=>'ﲸ','ﲹ'=>'ﲹ','ﲺ'=>'ﲺ','ﲻ'=>'ﲻ','ﲼ'=>'ﲼ','ﲽ'=>'ﲽ','ﲾ'=>'ﲾ','ﲿ'=>'ﲿ','ﳀ'=>'ﳀ','ﳁ'=>'ﳁ','ﳂ'=>'ﳂ','ﳃ'=>'ﳃ','ﳄ'=>'ﳄ','ﳅ'=>'ﳅ','ﳆ'=>'ﳆ','ﳇ'=>'ﳇ','ﳈ'=>'ﳈ','ﳉ'=>'ﳉ','ﳊ'=>'ﳊ','ﳋ'=>'ﳋ','ﳌ'=>'ﳌ','ﳍ'=>'ﳍ','ﳎ'=>'ﳎ','ﳏ'=>'ﳏ','ﳐ'=>'ﳐ','ﳑ'=>'ﳑ','ﳒ'=>'ﳒ','ﳓ'=>'ﳓ','ﳔ'=>'ﳔ','ﳕ'=>'ﳕ','ﳖ'=>'ﳖ','ﳗ'=>'ﳗ','ﳘ'=>'ﳘ','ﳙ'=>'ﳙ','ﳚ'=>'ﳚ','ﳛ'=>'ﳛ','ﳜ'=>'ﳜ','ﳝ'=>'ﳝ','ﳞ'=>'ﳞ','ﳟ'=>'ﳟ','ﳠ'=>'ﳠ','ﳡ'=>'ﳡ','ﳢ'=>'ﳢ','ﳣ'=>'ﳣ','ﳤ'=>'ﳤ','ﳥ'=>'ﳥ','ﳦ'=>'ﳦ','ﳧ'=>'ﳧ','ﳨ'=>'ﳨ','ﳩ'=>'ﳩ','ﳪ'=>'ﳪ','ﳫ'=>'ﳫ','ﳬ'=>'ﳬ','ﳭ'=>'ﳭ','ﳮ'=>'ﳮ','ﳯ'=>'ﳯ','ﳰ'=>'ﳰ','ﳱ'=>'ﳱ','ﳲ'=>'ﳲ','ﳳ'=>'ﳳ','ﳴ'=>'ﳴ','ﳵ'=>'ﳵ','ﳶ'=>'ﳶ','ﳷ'=>'ﳷ','ﳸ'=>'ﳸ','ﳹ'=>'ﳹ','ﳺ'=>'ﳺ','ﳻ'=>'ﳻ','ﳼ'=>'ﳼ','ﳽ'=>'ﳽ','ﳾ'=>'ﳾ','ﳿ'=>'ﳿ','ﴀ'=>'ﴀ','ﴁ'=>'ﴁ','ﴂ'=>'ﴂ','ﴃ'=>'ﴃ','ﴄ'=>'ﴄ','ﴅ'=>'ﴅ','ﴆ'=>'ﴆ','ﴇ'=>'ﴇ','ﴈ'=>'ﴈ','ﴉ'=>'ﴉ','ﴊ'=>'ﴊ','ﴋ'=>'ﴋ','ﴌ'=>'ﴌ','ﴍ'=>'ﴍ','ﴎ'=>'ﴎ','ﴏ'=>'ﴏ','ﴐ'=>'ﴐ','ﴑ'=>'ﴑ','ﴒ'=>'ﴒ','ﴓ'=>'ﴓ','ﴔ'=>'ﴔ','ﴕ'=>'ﴕ','ﴖ'=>'ﴖ','ﴗ'=>'ﴗ','ﴘ'=>'ﴘ','ﴙ'=>'ﴙ','ﴚ'=>'ﴚ','ﴛ'=>'ﴛ','ﴜ'=>'ﴜ','ﴝ'=>'ﴝ','ﴞ'=>'ﴞ','ﴟ'=>'ﴟ','ﴠ'=>'ﴠ','ﴡ'=>'ﴡ','ﴢ'=>'ﴢ','ﴣ'=>'ﴣ','ﴤ'=>'ﴤ','ﴥ'=>'ﴥ','ﴦ'=>'ﴦ','ﴧ'=>'ﴧ','ﴨ'=>'ﴨ','ﴩ'=>'ﴩ','ﴪ'=>'ﴪ','ﴫ'=>'ﴫ','ﴬ'=>'ﴬ','ﴭ'=>'ﴭ','ﴮ'=>'ﴮ','ﴯ'=>'ﴯ','ﴰ'=>'ﴰ','ﴱ'=>'ﴱ','ﴲ'=>'ﴲ','ﴳ'=>'ﴳ','ﴴ'=>'ﴴ','ﴵ'=>'ﴵ','ﴶ'=>'ﴶ','ﴷ'=>'ﴷ','ﴸ'=>'ﴸ','ﴹ'=>'ﴹ','ﴺ'=>'ﴺ','ﴻ'=>'ﴻ','ﴼ'=>'ﴼ','ﴽ'=>'ﴽ','ﵐ'=>'ﵐ','ﵑ'=>'ﵑ','ﵒ'=>'ﵒ','ﵓ'=>'ﵓ','ﵔ'=>'ﵔ','ﵕ'=>'ﵕ','ﵖ'=>'ﵖ','ﵗ'=>'ﵗ','ﵘ'=>'ﵘ','ﵙ'=>'ﵙ','ﵚ'=>'ﵚ','ﵛ'=>'ﵛ','ﵜ'=>'ﵜ','ﵝ'=>'ﵝ','ﵞ'=>'ﵞ','ﵟ'=>'ﵟ','ﵠ'=>'ﵠ','ﵡ'=>'ﵡ','ﵢ'=>'ﵢ','ﵣ'=>'ﵣ','ﵤ'=>'ﵤ','ﵥ'=>'ﵥ','ﵦ'=>'ﵦ','ﵧ'=>'ﵧ','ﵨ'=>'ﵨ','ﵩ'=>'ﵩ','ﵪ'=>'ﵪ','ﵫ'=>'ﵫ','ﵬ'=>'ﵬ','ﵭ'=>'ﵭ','ﵮ'=>'ﵮ','ﵯ'=>'ﵯ','ﵰ'=>'ﵰ','ﵱ'=>'ﵱ','ﵲ'=>'ﵲ','ﵳ'=>'ﵳ','ﵴ'=>'ﵴ','ﵵ'=>'ﵵ','ﵶ'=>'ﵶ','ﵷ'=>'ﵷ','ﵸ'=>'ﵸ','ﵹ'=>'ﵹ','ﵺ'=>'ﵺ','ﵻ'=>'ﵻ','ﵼ'=>'ﵼ','ﵽ'=>'ﵽ','ﵾ'=>'ﵾ','ﵿ'=>'ﵿ','ﶀ'=>'ﶀ','ﶁ'=>'ﶁ','ﶂ'=>'ﶂ','ﶃ'=>'ﶃ','ﶄ'=>'ﶄ','ﶅ'=>'ﶅ','ﶆ'=>'ﶆ','ﶇ'=>'ﶇ','ﶈ'=>'ﶈ','ﶉ'=>'ﶉ','ﶊ'=>'ﶊ','ﶋ'=>'ﶋ','ﶌ'=>'ﶌ','ﶍ'=>'ﶍ','ﶎ'=>'ﶎ','ﶏ'=>'ﶏ','ﶒ'=>'ﶒ','ﶓ'=>'ﶓ','ﶔ'=>'ﶔ','ﶕ'=>'ﶕ','ﶖ'=>'ﶖ','ﶗ'=>'ﶗ','ﶘ'=>'ﶘ','ﶙ'=>'ﶙ','ﶚ'=>'ﶚ','ﶛ'=>'ﶛ','ﶜ'=>'ﶜ','ﶝ'=>'ﶝ','ﶞ'=>'ﶞ','ﶟ'=>'ﶟ','ﶠ'=>'ﶠ','ﶡ'=>'ﶡ','ﶢ'=>'ﶢ','ﶣ'=>'ﶣ','ﶤ'=>'ﶤ','ﶥ'=>'ﶥ','ﶦ'=>'ﶦ','ﶧ'=>'ﶧ','ﶨ'=>'ﶨ','ﶩ'=>'ﶩ','ﶪ'=>'ﶪ','ﶫ'=>'ﶫ','ﶬ'=>'ﶬ','ﶭ'=>'ﶭ','ﶮ'=>'ﶮ','ﶯ'=>'ﶯ','ﶰ'=>'ﶰ','ﶱ'=>'ﶱ','ﶲ'=>'ﶲ','ﶳ'=>'ﶳ','ﶴ'=>'ﶴ','ﶵ'=>'ﶵ','ﶶ'=>'ﶶ','ﶷ'=>'ﶷ','ﶸ'=>'ﶸ','ﶹ'=>'ﶹ','ﶺ'=>'ﶺ','ﶻ'=>'ﶻ','ﶼ'=>'ﶼ','ﶽ'=>'ﶽ','ﶾ'=>'ﶾ','ﶿ'=>'ﶿ','ﷀ'=>'ﷀ','ﷁ'=>'ﷁ','ﷂ'=>'ﷂ','ﷃ'=>'ﷃ','ﷄ'=>'ﷄ','ﷅ'=>'ﷅ','ﷆ'=>'ﷆ','ﷇ'=>'ﷇ','ﷰ'=>'ﷰ','ﷱ'=>'ﷱ','ﷲ'=>'ﷲ','ﷳ'=>'ﷳ','ﷴ'=>'ﷴ','ﷵ'=>'ﷵ','ﷶ'=>'ﷶ','ﷷ'=>'ﷷ','ﷸ'=>'ﷸ','ﷹ'=>'ﷹ','ﷺ'=>'ﷺ','ﷻ'=>'ﷻ','︀'=>'︀','︁'=>'︁','︂'=>'︂','︃'=>'︃','︄'=>'︄','︅'=>'︅','︆'=>'︆','︇'=>'︇','︈'=>'︈','︉'=>'︉','︊'=>'︊','︋'=>'︋','︌'=>'︌','︍'=>'︍','︎'=>'︎','️'=>'️','︠'=>'︠','︡'=>'︡','︢'=>'︢','︣'=>'︣','ﹰ'=>'ﹰ','ﹱ'=>'ﹱ','ﹲ'=>'ﹲ','ﹳ'=>'ﹳ','ﹴ'=>'ﹴ','ﹶ'=>'ﹶ','ﹷ'=>'ﹷ','ﹸ'=>'ﹸ','ﹹ'=>'ﹹ','ﹺ'=>'ﹺ','ﹻ'=>'ﹻ','ﹼ'=>'ﹼ','ﹽ'=>'ﹽ','ﹾ'=>'ﹾ','ﹿ'=>'ﹿ','ﺀ'=>'ﺀ','ﺁ'=>'ﺁ','ﺂ'=>'ﺂ','ﺃ'=>'ﺃ','ﺄ'=>'ﺄ','ﺅ'=>'ﺅ','ﺆ'=>'ﺆ','ﺇ'=>'ﺇ','ﺈ'=>'ﺈ','ﺉ'=>'ﺉ','ﺊ'=>'ﺊ','ﺋ'=>'ﺋ','ﺌ'=>'ﺌ','ﺍ'=>'ﺍ','ﺎ'=>'ﺎ','ﺏ'=>'ﺏ','ﺐ'=>'ﺐ','ﺑ'=>'ﺑ','ﺒ'=>'ﺒ','ﺓ'=>'ﺓ','ﺔ'=>'ﺔ','ﺕ'=>'ﺕ','ﺖ'=>'ﺖ','ﺗ'=>'ﺗ','ﺘ'=>'ﺘ','ﺙ'=>'ﺙ','ﺚ'=>'ﺚ','ﺛ'=>'ﺛ','ﺜ'=>'ﺜ','ﺝ'=>'ﺝ','ﺞ'=>'ﺞ','ﺟ'=>'ﺟ','ﺠ'=>'ﺠ','ﺡ'=>'ﺡ','ﺢ'=>'ﺢ','ﺣ'=>'ﺣ','ﺤ'=>'ﺤ','ﺥ'=>'ﺥ','ﺦ'=>'ﺦ','ﺧ'=>'ﺧ','ﺨ'=>'ﺨ','ﺩ'=>'ﺩ','ﺪ'=>'ﺪ','ﺫ'=>'ﺫ','ﺬ'=>'ﺬ','ﺭ'=>'ﺭ','ﺮ'=>'ﺮ','ﺯ'=>'ﺯ','ﺰ'=>'ﺰ','ﺱ'=>'ﺱ','ﺲ'=>'ﺲ','ﺳ'=>'ﺳ','ﺴ'=>'ﺴ','ﺵ'=>'ﺵ','ﺶ'=>'ﺶ','ﺷ'=>'ﺷ','ﺸ'=>'ﺸ','ﺹ'=>'ﺹ','ﺺ'=>'ﺺ','ﺻ'=>'ﺻ','ﺼ'=>'ﺼ','ﺽ'=>'ﺽ','ﺾ'=>'ﺾ','ﺿ'=>'ﺿ','ﻀ'=>'ﻀ','ﻁ'=>'ﻁ','ﻂ'=>'ﻂ','ﻃ'=>'ﻃ','ﻄ'=>'ﻄ','ﻅ'=>'ﻅ','ﻆ'=>'ﻆ','ﻇ'=>'ﻇ','ﻈ'=>'ﻈ','ﻉ'=>'ﻉ','ﻊ'=>'ﻊ','ﻋ'=>'ﻋ','ﻌ'=>'ﻌ','ﻍ'=>'ﻍ','ﻎ'=>'ﻎ','ﻏ'=>'ﻏ','ﻐ'=>'ﻐ','ﻑ'=>'ﻑ','ﻒ'=>'ﻒ','ﻓ'=>'ﻓ','ﻔ'=>'ﻔ','ﻕ'=>'ﻕ','ﻖ'=>'ﻖ','ﻗ'=>'ﻗ','ﻘ'=>'ﻘ','ﻙ'=>'ﻙ','ﻚ'=>'ﻚ','ﻛ'=>'ﻛ','ﻜ'=>'ﻜ','ﻝ'=>'ﻝ','ﻞ'=>'ﻞ','ﻟ'=>'ﻟ','ﻠ'=>'ﻠ','ﻡ'=>'ﻡ','ﻢ'=>'ﻢ','ﻣ'=>'ﻣ','ﻤ'=>'ﻤ','ﻥ'=>'ﻥ','ﻦ'=>'ﻦ','ﻧ'=>'ﻧ','ﻨ'=>'ﻨ','ﻩ'=>'ﻩ','ﻪ'=>'ﻪ','ﻫ'=>'ﻫ','ﻬ'=>'ﻬ','ﻭ'=>'ﻭ','ﻮ'=>'ﻮ','ﻯ'=>'ﻯ','ﻰ'=>'ﻰ','ﻱ'=>'ﻱ','ﻲ'=>'ﻲ','ﻳ'=>'ﻳ','ﻴ'=>'ﻴ','ﻵ'=>'ﻵ','ﻶ'=>'ﻶ','ﻷ'=>'ﻷ','ﻸ'=>'ﻸ','ﻹ'=>'ﻹ','ﻺ'=>'ﻺ','ﻻ'=>'ﻻ','ﻼ'=>'ﻼ','0'=>'0','1'=>'1','2'=>'2','3'=>'3','4'=>'4','5'=>'5','6'=>'6','7'=>'7','8'=>'8','9'=>'9','A'=>'a','B'=>'b','C'=>'c','D'=>'d','E'=>'e','F'=>'f','G'=>'g','H'=>'h','I'=>'i','J'=>'j','K'=>'k','L'=>'l','M'=>'m','N'=>'n','O'=>'o','P'=>'p','Q'=>'q','R'=>'r','S'=>'s','T'=>'t','U'=>'u','V'=>'v','W'=>'w','X'=>'x','Y'=>'y','Z'=>'z','a'=>'a','b'=>'b','c'=>'c','d'=>'d','e'=>'e','f'=>'f','g'=>'g','h'=>'h','i'=>'i','j'=>'j','k'=>'k','l'=>'l','m'=>'m','n'=>'n','o'=>'o','p'=>'p','q'=>'q','r'=>'r','s'=>'s','t'=>'t','u'=>'u','v'=>'v','w'=>'w','x'=>'x','y'=>'y','z'=>'z','ヲ'=>'ヲ','ァ'=>'ァ','ィ'=>'ィ','ゥ'=>'ゥ','ェ'=>'ェ','ォ'=>'ォ','ャ'=>'ャ','ュ'=>'ュ','ョ'=>'ョ','ッ'=>'ッ','ー'=>'ー','ア'=>'ア','イ'=>'イ','ウ'=>'ウ','エ'=>'エ','オ'=>'オ','カ'=>'カ','キ'=>'キ','ク'=>'ク','ケ'=>'ケ','コ'=>'コ','サ'=>'サ','シ'=>'シ','ス'=>'ス','セ'=>'セ','ソ'=>'ソ','タ'=>'タ','チ'=>'チ','ツ'=>'ツ','テ'=>'テ','ト'=>'ト','ナ'=>'ナ','ニ'=>'ニ','ヌ'=>'ヌ','ネ'=>'ネ','ノ'=>'ノ','ハ'=>'ハ','ヒ'=>'ヒ','フ'=>'フ','ヘ'=>'ヘ','ホ'=>'ホ','マ'=>'マ','ミ'=>'ミ','ム'=>'ム','メ'=>'メ','モ'=>'モ','ヤ'=>'ヤ','ユ'=>'ユ','ヨ'=>'ヨ','ラ'=>'ラ','リ'=>'リ','ル'=>'ル','レ'=>'レ','ロ'=>'ロ','ワ'=>'ワ','ン'=>'ン','゙'=>'゙','゚'=>'゚','ᅠ'=>'ᅠ','ᄀ'=>'ᄀ','ᄁ'=>'ᄁ','ᆪ'=>'ᆪ','ᄂ'=>'ᄂ','ᆬ'=>'ᆬ','ᆭ'=>'ᆭ','ᄃ'=>'ᄃ','ᄄ'=>'ᄄ','ᄅ'=>'ᄅ','ᆰ'=>'ᆰ','ᆱ'=>'ᆱ','ᆲ'=>'ᆲ','ᆳ'=>'ᆳ','ᆴ'=>'ᆴ','ᆵ'=>'ᆵ','ᄚ'=>'ᄚ','ᄆ'=>'ᄆ','ᄇ'=>'ᄇ','ᄈ'=>'ᄈ','ᄡ'=>'ᄡ','ᄉ'=>'ᄉ','ᄊ'=>'ᄊ','ᄋ'=>'ᄋ','ᄌ'=>'ᄌ','ᄍ'=>'ᄍ','ᄎ'=>'ᄎ','ᄏ'=>'ᄏ','ᄐ'=>'ᄐ','ᄑ'=>'ᄑ','ᄒ'=>'ᄒ','ᅡ'=>'ᅡ','ᅢ'=>'ᅢ','ᅣ'=>'ᅣ','ᅤ'=>'ᅤ','ᅥ'=>'ᅥ','ᅦ'=>'ᅦ','ᅧ'=>'ᅧ','ᅨ'=>'ᅨ','ᅩ'=>'ᅩ','ᅪ'=>'ᅪ','ᅫ'=>'ᅫ','ᅬ'=>'ᅬ','ᅭ'=>'ᅭ','ᅮ'=>'ᅮ','ᅯ'=>'ᅯ','ᅰ'=>'ᅰ','ᅱ'=>'ᅱ','ᅲ'=>'ᅲ','ᅳ'=>'ᅳ','ᅴ'=>'ᅴ','ᅵ'=>'ᅵ'); diff --git a/includes/utf/data/search_indexer_32.php b/includes/utf/data/search_indexer_32.php new file mode 100644 index 0000000..cea5eba --- /dev/null +++ b/includes/utf/data/search_indexer_32.php @@ -0,0 +1 @@ +'𐀀','𐀁'=>'𐀁','𐀂'=>'𐀂','𐀃'=>'𐀃','𐀄'=>'𐀄','𐀅'=>'𐀅','𐀆'=>'𐀆','𐀇'=>'𐀇','𐀈'=>'𐀈','𐀉'=>'𐀉','𐀊'=>'𐀊','𐀋'=>'𐀋','𐀍'=>'𐀍','𐀎'=>'𐀎','𐀏'=>'𐀏','𐀐'=>'𐀐','𐀑'=>'𐀑','𐀒'=>'𐀒','𐀓'=>'𐀓','𐀔'=>'𐀔','𐀕'=>'𐀕','𐀖'=>'𐀖','𐀗'=>'𐀗','𐀘'=>'𐀘','𐀙'=>'𐀙','𐀚'=>'𐀚','𐀛'=>'𐀛','𐀜'=>'𐀜','𐀝'=>'𐀝','𐀞'=>'𐀞','𐀟'=>'𐀟','𐀠'=>'𐀠','𐀡'=>'𐀡','𐀢'=>'𐀢','𐀣'=>'𐀣','𐀤'=>'𐀤','𐀥'=>'𐀥','𐀦'=>'𐀦','𐀨'=>'𐀨','𐀩'=>'𐀩','𐀪'=>'𐀪','𐀫'=>'𐀫','𐀬'=>'𐀬','𐀭'=>'𐀭','𐀮'=>'𐀮','𐀯'=>'𐀯','𐀰'=>'𐀰','𐀱'=>'𐀱','𐀲'=>'𐀲','𐀳'=>'𐀳','𐀴'=>'𐀴','𐀵'=>'𐀵','𐀶'=>'𐀶','𐀷'=>'𐀷','𐀸'=>'𐀸','𐀹'=>'𐀹','𐀺'=>'𐀺','𐀼'=>'𐀼','𐀽'=>'𐀽','𐀿'=>'𐀿','𐁀'=>'𐁀','𐁁'=>'𐁁','𐁂'=>'𐁂','𐁃'=>'𐁃','𐁄'=>'𐁄','𐁅'=>'𐁅','𐁆'=>'𐁆','𐁇'=>'𐁇','𐁈'=>'𐁈','𐁉'=>'𐁉','𐁊'=>'𐁊','𐁋'=>'𐁋','𐁌'=>'𐁌','𐁍'=>'𐁍','𐁐'=>'𐁐','𐁑'=>'𐁑','𐁒'=>'𐁒','𐁓'=>'𐁓','𐁔'=>'𐁔','𐁕'=>'𐁕','𐁖'=>'𐁖','𐁗'=>'𐁗','𐁘'=>'𐁘','𐁙'=>'𐁙','𐁚'=>'𐁚','𐁛'=>'𐁛','𐁜'=>'𐁜','𐁝'=>'𐁝','𐂀'=>'𐂀','𐂁'=>'𐂁','𐂂'=>'𐂂','𐂃'=>'𐂃','𐂄'=>'𐂄','𐂅'=>'𐂅','𐂆'=>'𐂆','𐂇'=>'𐂇','𐂈'=>'𐂈','𐂉'=>'𐂉','𐂊'=>'𐂊','𐂋'=>'𐂋','𐂌'=>'𐂌','𐂍'=>'𐂍','𐂎'=>'𐂎','𐂏'=>'𐂏','𐂐'=>'𐂐','𐂑'=>'𐂑','𐂒'=>'𐂒','𐂓'=>'𐂓','𐂔'=>'𐂔','𐂕'=>'𐂕','𐂖'=>'𐂖','𐂗'=>'𐂗','𐂘'=>'𐂘','𐂙'=>'𐂙','𐂚'=>'𐂚','𐂛'=>'𐂛','𐂜'=>'𐂜','𐂝'=>'𐂝','𐂞'=>'𐂞','𐂟'=>'𐂟','𐂠'=>'𐂠','𐂡'=>'𐂡','𐂢'=>'𐂢','𐂣'=>'𐂣','𐂤'=>'𐂤','𐂥'=>'𐂥','𐂦'=>'𐂦','𐂧'=>'𐂧','𐂨'=>'𐂨','𐂩'=>'𐂩','𐂪'=>'𐂪','𐂫'=>'𐂫','𐂬'=>'𐂬','𐂭'=>'𐂭','𐂮'=>'𐂮','𐂯'=>'𐂯','𐂰'=>'𐂰','𐂱'=>'𐂱','𐂲'=>'𐂲','𐂳'=>'𐂳','𐂴'=>'𐂴','𐂵'=>'𐂵','𐂶'=>'𐂶','𐂷'=>'𐂷','𐂸'=>'𐂸','𐂹'=>'𐂹','𐂺'=>'𐂺','𐂻'=>'𐂻','𐂼'=>'𐂼','𐂽'=>'𐂽','𐂾'=>'𐂾','𐂿'=>'𐂿','𐃀'=>'𐃀','𐃁'=>'𐃁','𐃂'=>'𐃂','𐃃'=>'𐃃','𐃄'=>'𐃄','𐃅'=>'𐃅','𐃆'=>'𐃆','𐃇'=>'𐃇','𐃈'=>'𐃈','𐃉'=>'𐃉','𐃊'=>'𐃊','𐃋'=>'𐃋','𐃌'=>'𐃌','𐃍'=>'𐃍','𐃎'=>'𐃎','𐃏'=>'𐃏','𐃐'=>'𐃐','𐃑'=>'𐃑','𐃒'=>'𐃒','𐃓'=>'𐃓','𐃔'=>'𐃔','𐃕'=>'𐃕','𐃖'=>'𐃖','𐃗'=>'𐃗','𐃘'=>'𐃘','𐃙'=>'𐃙','𐃚'=>'𐃚','𐃛'=>'𐃛','𐃜'=>'𐃜','𐃝'=>'𐃝','𐃞'=>'𐃞','𐃟'=>'𐃟','𐃠'=>'𐃠','𐃡'=>'𐃡','𐃢'=>'𐃢','𐃣'=>'𐃣','𐃤'=>'𐃤','𐃥'=>'𐃥','𐃦'=>'𐃦','𐃧'=>'𐃧','𐃨'=>'𐃨','𐃩'=>'𐃩','𐃪'=>'𐃪','𐃫'=>'𐃫','𐃬'=>'𐃬','𐃭'=>'𐃭','𐃮'=>'𐃮','𐃯'=>'𐃯','𐃰'=>'𐃰','𐃱'=>'𐃱','𐃲'=>'𐃲','𐃳'=>'𐃳','𐃴'=>'𐃴','𐃵'=>'𐃵','𐃶'=>'𐃶','𐃷'=>'𐃷','𐃸'=>'𐃸','𐃹'=>'𐃹','𐃺'=>'𐃺','𐄇'=>'1','𐄈'=>'2','𐄉'=>'3','𐄊'=>'4','𐄋'=>'5','𐄌'=>'6','𐄍'=>'7','𐄎'=>'8','𐄏'=>'9','𐄐'=>'10','𐄑'=>'20','𐄒'=>'30','𐄓'=>'40','𐄔'=>'50','𐄕'=>'60','𐄖'=>'70','𐄗'=>'80','𐄘'=>'90','𐄙'=>'100','𐄚'=>'200','𐄛'=>'300','𐄜'=>'400','𐄝'=>'500','𐄞'=>'600','𐄟'=>'700','𐄠'=>'800','𐄡'=>'900','𐄢'=>'1000','𐄣'=>'2000','𐄤'=>'3000','𐄥'=>'4000','𐄦'=>'5000','𐄧'=>'6000','𐄨'=>'7000','𐄩'=>'8000','𐄪'=>'9000','𐄫'=>'10000','𐄬'=>'20000','𐄭'=>'30000','𐄮'=>'40000','𐄯'=>'50000','𐄰'=>'60000','𐄱'=>'70000','𐄲'=>'80000','𐄳'=>'90000','𐅀'=>'1/4','𐅁'=>'1/2','𐅂'=>'1','𐅃'=>'5','𐅄'=>'50','𐅅'=>'500','𐅆'=>'5000','𐅇'=>'50000','𐅈'=>'5','𐅉'=>'10','𐅊'=>'50','𐅋'=>'100','𐅌'=>'500','𐅍'=>'1000','𐅎'=>'5000','𐅏'=>'5','𐅐'=>'10','𐅑'=>'50','𐅒'=>'100','𐅓'=>'500','𐅔'=>'1000','𐅕'=>'10000','𐅖'=>'50000','𐅗'=>'10','𐅘'=>'1','𐅙'=>'1','𐅚'=>'1','𐅛'=>'2','𐅜'=>'2','𐅝'=>'2','𐅞'=>'2','𐅟'=>'5','𐅠'=>'10','𐅡'=>'10','𐅢'=>'10','𐅣'=>'10','𐅤'=>'10','𐅥'=>'30','𐅦'=>'50','𐅧'=>'50','𐅨'=>'50','𐅩'=>'50','𐅪'=>'100','𐅫'=>'300','𐅬'=>'500','𐅭'=>'500','𐅮'=>'500','𐅯'=>'500','𐅰'=>'500','𐅱'=>'1000','𐅲'=>'5000','𐅳'=>'5','𐅴'=>'50','𐅵'=>'1/2','𐅶'=>'1/2','𐅷'=>'2/3','𐅸'=>'3/4','𐆊'=>'0','𐌀'=>'𐌀','𐌁'=>'𐌁','𐌂'=>'𐌂','𐌃'=>'𐌃','𐌄'=>'𐌄','𐌅'=>'𐌅','𐌆'=>'𐌆','𐌇'=>'𐌇','𐌈'=>'𐌈','𐌉'=>'𐌉','𐌊'=>'𐌊','𐌋'=>'𐌋','𐌌'=>'𐌌','𐌍'=>'𐌍','𐌎'=>'𐌎','𐌏'=>'𐌏','𐌐'=>'𐌐','𐌑'=>'𐌑','𐌒'=>'𐌒','𐌓'=>'𐌓','𐌔'=>'𐌔','𐌕'=>'𐌕','𐌖'=>'𐌖','𐌗'=>'𐌗','𐌘'=>'𐌘','𐌙'=>'𐌙','𐌚'=>'𐌚','𐌛'=>'𐌛','𐌜'=>'𐌜','𐌝'=>'𐌝','𐌞'=>'𐌞','𐌠'=>'1','𐌡'=>'5','𐌢'=>'10','𐌣'=>'50','𐌰'=>'𐌰','𐌱'=>'𐌱','𐌲'=>'𐌲','𐌳'=>'𐌳','𐌴'=>'𐌴','𐌵'=>'𐌵','𐌶'=>'𐌶','𐌷'=>'𐌷','𐌸'=>'𐌸','𐌹'=>'𐌹','𐌺'=>'𐌺','𐌻'=>'𐌻','𐌼'=>'𐌼','𐌽'=>'𐌽','𐌾'=>'𐌾','𐌿'=>'𐌿','𐍀'=>'𐍀','𐍁'=>'90','𐍂'=>'𐍂','𐍃'=>'𐍃','𐍄'=>'𐍄','𐍅'=>'𐍅','𐍆'=>'𐍆','𐍇'=>'𐍇','𐍈'=>'𐍈','𐍉'=>'𐍉','𐍊'=>'900','𐎀'=>'𐎀','𐎁'=>'𐎁','𐎂'=>'𐎂','𐎃'=>'𐎃','𐎄'=>'𐎄','𐎅'=>'𐎅','𐎆'=>'𐎆','𐎇'=>'𐎇','𐎈'=>'𐎈','𐎉'=>'𐎉','𐎊'=>'𐎊','𐎋'=>'𐎋','𐎌'=>'𐎌','𐎍'=>'𐎍','𐎎'=>'𐎎','𐎏'=>'𐎏','𐎐'=>'𐎐','𐎑'=>'𐎑','𐎒'=>'𐎒','𐎓'=>'𐎓','𐎔'=>'𐎔','𐎕'=>'𐎕','𐎖'=>'𐎖','𐎗'=>'𐎗','𐎘'=>'𐎘','𐎙'=>'𐎙','𐎚'=>'𐎚','𐎛'=>'𐎛','𐎜'=>'𐎜','𐎝'=>'𐎝','𐎠'=>'𐎠','𐎡'=>'𐎡','𐎢'=>'𐎢','𐎣'=>'𐎣','𐎤'=>'𐎤','𐎥'=>'𐎥','𐎦'=>'𐎦','𐎧'=>'𐎧','𐎨'=>'𐎨','𐎩'=>'𐎩','𐎪'=>'𐎪','𐎫'=>'𐎫','𐎬'=>'𐎬','𐎭'=>'𐎭','𐎮'=>'𐎮','𐎯'=>'𐎯','𐎰'=>'𐎰','𐎱'=>'𐎱','𐎲'=>'𐎲','𐎳'=>'𐎳','𐎴'=>'𐎴','𐎵'=>'𐎵','𐎶'=>'𐎶','𐎷'=>'𐎷','𐎸'=>'𐎸','𐎹'=>'𐎹','𐎺'=>'𐎺','𐎻'=>'𐎻','𐎼'=>'𐎼','𐎽'=>'𐎽','𐎾'=>'𐎾','𐎿'=>'𐎿','𐏀'=>'𐏀','𐏁'=>'𐏁','𐏂'=>'𐏂','𐏃'=>'𐏃','𐏈'=>'𐏈','𐏉'=>'𐏉','𐏊'=>'𐏊','𐏋'=>'𐏋','𐏌'=>'𐏌','𐏍'=>'𐏍','𐏎'=>'𐏎','𐏏'=>'𐏏','𐏑'=>'1','𐏒'=>'2','𐏓'=>'10','𐏔'=>'20','𐏕'=>'100','𐐀'=>'𐐨','𐐁'=>'𐐩','𐐂'=>'𐐪','𐐃'=>'𐐫','𐐄'=>'𐐬','𐐅'=>'𐐭','𐐆'=>'𐐮','𐐇'=>'𐐯','𐐈'=>'𐐰','𐐉'=>'𐐱','𐐊'=>'𐐲','𐐋'=>'𐐳','𐐌'=>'𐐴','𐐍'=>'𐐵','𐐎'=>'𐐶','𐐏'=>'𐐷','𐐐'=>'𐐸','𐐑'=>'𐐹','𐐒'=>'𐐺','𐐓'=>'𐐻','𐐔'=>'𐐼','𐐕'=>'𐐽','𐐖'=>'𐐾','𐐗'=>'𐐿','𐐘'=>'𐑀','𐐙'=>'𐑁','𐐚'=>'𐑂','𐐛'=>'𐑃','𐐜'=>'𐑄','𐐝'=>'𐑅','𐐞'=>'𐑆','𐐟'=>'𐑇','𐐠'=>'𐑈','𐐡'=>'𐑉','𐐢'=>'𐑊','𐐣'=>'𐑋','𐐤'=>'𐑌','𐐥'=>'𐑍','𐐦'=>'𐑎','𐐧'=>'𐑏','𐐨'=>'𐐨','𐐩'=>'𐐩','𐐪'=>'𐐪','𐐫'=>'𐐫','𐐬'=>'𐐬','𐐭'=>'𐐭','𐐮'=>'𐐮','𐐯'=>'𐐯','𐐰'=>'𐐰','𐐱'=>'𐐱','𐐲'=>'𐐲','𐐳'=>'𐐳','𐐴'=>'𐐴','𐐵'=>'𐐵','𐐶'=>'𐐶','𐐷'=>'𐐷','𐐸'=>'𐐸','𐐹'=>'𐐹','𐐺'=>'𐐺','𐐻'=>'𐐻','𐐼'=>'𐐼','𐐽'=>'𐐽','𐐾'=>'𐐾','𐐿'=>'𐐿','𐑀'=>'𐑀','𐑁'=>'𐑁','𐑂'=>'𐑂','𐑃'=>'𐑃','𐑄'=>'𐑄','𐑅'=>'𐑅','𐑆'=>'𐑆','𐑇'=>'𐑇','𐑈'=>'𐑈','𐑉'=>'𐑉','𐑊'=>'𐑊','𐑋'=>'𐑋','𐑌'=>'𐑌','𐑍'=>'𐑍','𐑎'=>'𐑎','𐑏'=>'𐑏','𐑐'=>'𐑐','𐑑'=>'𐑑','𐑒'=>'𐑒','𐑓'=>'𐑓','𐑔'=>'𐑔','𐑕'=>'𐑕','𐑖'=>'𐑖','𐑗'=>'𐑗','𐑘'=>'𐑘','𐑙'=>'𐑙','𐑚'=>'𐑚','𐑛'=>'𐑛','𐑜'=>'𐑜','𐑝'=>'𐑝','𐑞'=>'𐑞','𐑟'=>'𐑟','𐑠'=>'𐑠','𐑡'=>'𐑡','𐑢'=>'𐑢','𐑣'=>'𐑣','𐑤'=>'𐑤','𐑥'=>'𐑥','𐑦'=>'𐑦','𐑧'=>'𐑧','𐑨'=>'𐑨','𐑩'=>'𐑩','𐑪'=>'𐑪','𐑫'=>'𐑫','𐑬'=>'𐑬','𐑭'=>'𐑭','𐑮'=>'𐑮','𐑯'=>'𐑯','𐑰'=>'𐑰','𐑱'=>'𐑱','𐑲'=>'𐑲','𐑳'=>'𐑳','𐑴'=>'𐑴','𐑵'=>'𐑵','𐑶'=>'𐑶','𐑷'=>'𐑷','𐑸'=>'𐑸','𐑹'=>'𐑹','𐑺'=>'𐑺','𐑻'=>'𐑻','𐑼'=>'𐑼','𐑽'=>'𐑽','𐑾'=>'𐑾','𐑿'=>'𐑿','𐒀'=>'𐒀','𐒁'=>'𐒁','𐒂'=>'𐒂','𐒃'=>'𐒃','𐒄'=>'𐒄','𐒅'=>'𐒅','𐒆'=>'𐒆','𐒇'=>'𐒇','𐒈'=>'𐒈','𐒉'=>'𐒉','𐒊'=>'𐒊','𐒋'=>'𐒋','𐒌'=>'𐒌','𐒍'=>'𐒍','𐒎'=>'𐒎','𐒏'=>'𐒏','𐒐'=>'𐒐','𐒑'=>'𐒑','𐒒'=>'𐒒','𐒓'=>'𐒓','𐒔'=>'𐒔','𐒕'=>'𐒕','𐒖'=>'𐒖','𐒗'=>'𐒗','𐒘'=>'𐒘','𐒙'=>'𐒙','𐒚'=>'𐒚','𐒛'=>'𐒛','𐒜'=>'𐒜','𐒝'=>'𐒝','𐒠'=>'0','𐒡'=>'1','𐒢'=>'2','𐒣'=>'3','𐒤'=>'4','𐒥'=>'5','𐒦'=>'6','𐒧'=>'7','𐒨'=>'8','𐒩'=>'9'); diff --git a/includes/utf/data/search_indexer_33.php b/includes/utf/data/search_indexer_33.php new file mode 100644 index 0000000..3b77ca2 --- /dev/null +++ b/includes/utf/data/search_indexer_33.php @@ -0,0 +1 @@ +'𐠀','𐠁'=>'𐠁','𐠂'=>'𐠂','𐠃'=>'𐠃','𐠄'=>'𐠄','𐠅'=>'𐠅','𐠈'=>'𐠈','𐠊'=>'𐠊','𐠋'=>'𐠋','𐠌'=>'𐠌','𐠍'=>'𐠍','𐠎'=>'𐠎','𐠏'=>'𐠏','𐠐'=>'𐠐','𐠑'=>'𐠑','𐠒'=>'𐠒','𐠓'=>'𐠓','𐠔'=>'𐠔','𐠕'=>'𐠕','𐠖'=>'𐠖','𐠗'=>'𐠗','𐠘'=>'𐠘','𐠙'=>'𐠙','𐠚'=>'𐠚','𐠛'=>'𐠛','𐠜'=>'𐠜','𐠝'=>'𐠝','𐠞'=>'𐠞','𐠟'=>'𐠟','𐠠'=>'𐠠','𐠡'=>'𐠡','𐠢'=>'𐠢','𐠣'=>'𐠣','𐠤'=>'𐠤','𐠥'=>'𐠥','𐠦'=>'𐠦','𐠧'=>'𐠧','𐠨'=>'𐠨','𐠩'=>'𐠩','𐠪'=>'𐠪','𐠫'=>'𐠫','𐠬'=>'𐠬','𐠭'=>'𐠭','𐠮'=>'𐠮','𐠯'=>'𐠯','𐠰'=>'𐠰','𐠱'=>'𐠱','𐠲'=>'𐠲','𐠳'=>'𐠳','𐠴'=>'𐠴','𐠵'=>'𐠵','𐠷'=>'𐠷','𐠸'=>'𐠸','𐠼'=>'𐠼','𐠿'=>'𐠿','𐤀'=>'𐤀','𐤁'=>'𐤁','𐤂'=>'𐤂','𐤃'=>'𐤃','𐤄'=>'𐤄','𐤅'=>'𐤅','𐤆'=>'𐤆','𐤇'=>'𐤇','𐤈'=>'𐤈','𐤉'=>'𐤉','𐤊'=>'𐤊','𐤋'=>'𐤋','𐤌'=>'𐤌','𐤍'=>'𐤍','𐤎'=>'𐤎','𐤏'=>'𐤏','𐤐'=>'𐤐','𐤑'=>'𐤑','𐤒'=>'𐤒','𐤓'=>'𐤓','𐤔'=>'𐤔','𐤕'=>'𐤕','𐤖'=>'1','𐤗'=>'10','𐤘'=>'20','𐤙'=>'100','𐨀'=>'𐨀','𐨁'=>'𐨁','𐨂'=>'𐨂','𐨃'=>'𐨃','𐨅'=>'𐨅','𐨆'=>'𐨆','𐨌'=>'𐨌','𐨍'=>'𐨍','𐨎'=>'𐨎','𐨏'=>'𐨏','𐨐'=>'𐨐','𐨑'=>'𐨑','𐨒'=>'𐨒','𐨓'=>'𐨓','𐨕'=>'𐨕','𐨖'=>'𐨖','𐨗'=>'𐨗','𐨙'=>'𐨙','𐨚'=>'𐨚','𐨛'=>'𐨛','𐨜'=>'𐨜','𐨝'=>'𐨝','𐨞'=>'𐨞','𐨟'=>'𐨟','𐨠'=>'𐨠','𐨡'=>'𐨡','𐨢'=>'𐨢','𐨣'=>'𐨣','𐨤'=>'𐨤','𐨥'=>'𐨥','𐨦'=>'𐨦','𐨧'=>'𐨧','𐨨'=>'𐨨','𐨩'=>'𐨩','𐨪'=>'𐨪','𐨫'=>'𐨫','𐨬'=>'𐨬','𐨭'=>'𐨭','𐨮'=>'𐨮','𐨯'=>'𐨯','𐨰'=>'𐨰','𐨱'=>'𐨱','𐨲'=>'𐨲','𐨳'=>'𐨳','𐨸'=>'𐨸','𐨹'=>'𐨹','𐨺'=>'𐨺','𐨿'=>'𐨿','𐩀'=>'1','𐩁'=>'2','𐩂'=>'3','𐩃'=>'4','𐩄'=>'10','𐩅'=>'20','𐩆'=>'100','𐩇'=>'1000'); diff --git a/includes/utf/data/search_indexer_36.php b/includes/utf/data/search_indexer_36.php new file mode 100644 index 0000000..a970fa2 --- /dev/null +++ b/includes/utf/data/search_indexer_36.php @@ -0,0 +1 @@ +'𒀀','𒀁'=>'𒀁','𒀂'=>'𒀂','𒀃'=>'𒀃','𒀄'=>'𒀄','𒀅'=>'𒀅','𒀆'=>'𒀆','𒀇'=>'𒀇','𒀈'=>'𒀈','𒀉'=>'𒀉','𒀊'=>'𒀊','𒀋'=>'𒀋','𒀌'=>'𒀌','𒀍'=>'𒀍','𒀎'=>'𒀎','𒀏'=>'𒀏','𒀐'=>'𒀐','𒀑'=>'𒀑','𒀒'=>'𒀒','𒀓'=>'𒀓','𒀔'=>'𒀔','𒀕'=>'𒀕','𒀖'=>'𒀖','𒀗'=>'𒀗','𒀘'=>'𒀘','𒀙'=>'𒀙','𒀚'=>'𒀚','𒀛'=>'𒀛','𒀜'=>'𒀜','𒀝'=>'𒀝','𒀞'=>'𒀞','𒀟'=>'𒀟','𒀠'=>'𒀠','𒀡'=>'𒀡','𒀢'=>'𒀢','𒀣'=>'𒀣','𒀤'=>'𒀤','𒀥'=>'𒀥','𒀦'=>'𒀦','𒀧'=>'𒀧','𒀨'=>'𒀨','𒀩'=>'𒀩','𒀪'=>'𒀪','𒀫'=>'𒀫','𒀬'=>'𒀬','𒀭'=>'𒀭','𒀮'=>'𒀮','𒀯'=>'𒀯','𒀰'=>'𒀰','𒀱'=>'𒀱','𒀲'=>'𒀲','𒀳'=>'𒀳','𒀴'=>'𒀴','𒀵'=>'𒀵','𒀶'=>'𒀶','𒀷'=>'𒀷','𒀸'=>'𒀸','𒀹'=>'𒀹','𒀺'=>'𒀺','𒀻'=>'𒀻','𒀼'=>'𒀼','𒀽'=>'𒀽','𒀾'=>'𒀾','𒀿'=>'𒀿','𒁀'=>'𒁀','𒁁'=>'𒁁','𒁂'=>'𒁂','𒁃'=>'𒁃','𒁄'=>'𒁄','𒁅'=>'𒁅','𒁆'=>'𒁆','𒁇'=>'𒁇','𒁈'=>'𒁈','𒁉'=>'𒁉','𒁊'=>'𒁊','𒁋'=>'𒁋','𒁌'=>'𒁌','𒁍'=>'𒁍','𒁎'=>'𒁎','𒁏'=>'𒁏','𒁐'=>'𒁐','𒁑'=>'𒁑','𒁒'=>'𒁒','𒁓'=>'𒁓','𒁔'=>'𒁔','𒁕'=>'𒁕','𒁖'=>'𒁖','𒁗'=>'𒁗','𒁘'=>'𒁘','𒁙'=>'𒁙','𒁚'=>'𒁚','𒁛'=>'𒁛','𒁜'=>'𒁜','𒁝'=>'𒁝','𒁞'=>'𒁞','𒁟'=>'𒁟','𒁠'=>'𒁠','𒁡'=>'𒁡','𒁢'=>'𒁢','𒁣'=>'𒁣','𒁤'=>'𒁤','𒁥'=>'𒁥','𒁦'=>'𒁦','𒁧'=>'𒁧','𒁨'=>'𒁨','𒁩'=>'𒁩','𒁪'=>'𒁪','𒁫'=>'𒁫','𒁬'=>'𒁬','𒁭'=>'𒁭','𒁮'=>'𒁮','𒁯'=>'𒁯','𒁰'=>'𒁰','𒁱'=>'𒁱','𒁲'=>'𒁲','𒁳'=>'𒁳','𒁴'=>'𒁴','𒁵'=>'𒁵','𒁶'=>'𒁶','𒁷'=>'𒁷','𒁸'=>'𒁸','𒁹'=>'𒁹','𒁺'=>'𒁺','𒁻'=>'𒁻','𒁼'=>'𒁼','𒁽'=>'𒁽','𒁾'=>'𒁾','𒁿'=>'𒁿','𒂀'=>'𒂀','𒂁'=>'𒂁','𒂂'=>'𒂂','𒂃'=>'𒂃','𒂄'=>'𒂄','𒂅'=>'𒂅','𒂆'=>'𒂆','𒂇'=>'𒂇','𒂈'=>'𒂈','𒂉'=>'𒂉','𒂊'=>'𒂊','𒂋'=>'𒂋','𒂌'=>'𒂌','𒂍'=>'𒂍','𒂎'=>'𒂎','𒂏'=>'𒂏','𒂐'=>'𒂐','𒂑'=>'𒂑','𒂒'=>'𒂒','𒂓'=>'𒂓','𒂔'=>'𒂔','𒂕'=>'𒂕','𒂖'=>'𒂖','𒂗'=>'𒂗','𒂘'=>'𒂘','𒂙'=>'𒂙','𒂚'=>'𒂚','𒂛'=>'𒂛','𒂜'=>'𒂜','𒂝'=>'𒂝','𒂞'=>'𒂞','𒂟'=>'𒂟','𒂠'=>'𒂠','𒂡'=>'𒂡','𒂢'=>'𒂢','𒂣'=>'𒂣','𒂤'=>'𒂤','𒂥'=>'𒂥','𒂦'=>'𒂦','𒂧'=>'𒂧','𒂨'=>'𒂨','𒂩'=>'𒂩','𒂪'=>'𒂪','𒂫'=>'𒂫','𒂬'=>'𒂬','𒂭'=>'𒂭','𒂮'=>'𒂮','𒂯'=>'𒂯','𒂰'=>'𒂰','𒂱'=>'𒂱','𒂲'=>'𒂲','𒂳'=>'𒂳','𒂴'=>'𒂴','𒂵'=>'𒂵','𒂶'=>'𒂶','𒂷'=>'𒂷','𒂸'=>'𒂸','𒂹'=>'𒂹','𒂺'=>'𒂺','𒂻'=>'𒂻','𒂼'=>'𒂼','𒂽'=>'𒂽','𒂾'=>'𒂾','𒂿'=>'𒂿','𒃀'=>'𒃀','𒃁'=>'𒃁','𒃂'=>'𒃂','𒃃'=>'𒃃','𒃄'=>'𒃄','𒃅'=>'𒃅','𒃆'=>'𒃆','𒃇'=>'𒃇','𒃈'=>'𒃈','𒃉'=>'𒃉','𒃊'=>'𒃊','𒃋'=>'𒃋','𒃌'=>'𒃌','𒃍'=>'𒃍','𒃎'=>'𒃎','𒃏'=>'𒃏','𒃐'=>'𒃐','𒃑'=>'𒃑','𒃒'=>'𒃒','𒃓'=>'𒃓','𒃔'=>'𒃔','𒃕'=>'𒃕','𒃖'=>'𒃖','𒃗'=>'𒃗','𒃘'=>'𒃘','𒃙'=>'𒃙','𒃚'=>'𒃚','𒃛'=>'𒃛','𒃜'=>'𒃜','𒃝'=>'𒃝','𒃞'=>'𒃞','𒃟'=>'𒃟','𒃠'=>'𒃠','𒃡'=>'𒃡','𒃢'=>'𒃢','𒃣'=>'𒃣','𒃤'=>'𒃤','𒃥'=>'𒃥','𒃦'=>'𒃦','𒃧'=>'𒃧','𒃨'=>'𒃨','𒃩'=>'𒃩','𒃪'=>'𒃪','𒃫'=>'𒃫','𒃬'=>'𒃬','𒃭'=>'𒃭','𒃮'=>'𒃮','𒃯'=>'𒃯','𒃰'=>'𒃰','𒃱'=>'𒃱','𒃲'=>'𒃲','𒃳'=>'𒃳','𒃴'=>'𒃴','𒃵'=>'𒃵','𒃶'=>'𒃶','𒃷'=>'𒃷','𒃸'=>'𒃸','𒃹'=>'𒃹','𒃺'=>'𒃺','𒃻'=>'𒃻','𒃼'=>'𒃼','𒃽'=>'𒃽','𒃾'=>'𒃾','𒃿'=>'𒃿','𒄀'=>'𒄀','𒄁'=>'𒄁','𒄂'=>'𒄂','𒄃'=>'𒄃','𒄄'=>'𒄄','𒄅'=>'𒄅','𒄆'=>'𒄆','𒄇'=>'𒄇','𒄈'=>'𒄈','𒄉'=>'𒄉','𒄊'=>'𒄊','𒄋'=>'𒄋','𒄌'=>'𒄌','𒄍'=>'𒄍','𒄎'=>'𒄎','𒄏'=>'𒄏','𒄐'=>'𒄐','𒄑'=>'𒄑','𒄒'=>'𒄒','𒄓'=>'𒄓','𒄔'=>'𒄔','𒄕'=>'𒄕','𒄖'=>'𒄖','𒄗'=>'𒄗','𒄘'=>'𒄘','𒄙'=>'𒄙','𒄚'=>'𒄚','𒄛'=>'𒄛','𒄜'=>'𒄜','𒄝'=>'𒄝','𒄞'=>'𒄞','𒄟'=>'𒄟','𒄠'=>'𒄠','𒄡'=>'𒄡','𒄢'=>'𒄢','𒄣'=>'𒄣','𒄤'=>'𒄤','𒄥'=>'𒄥','𒄦'=>'𒄦','𒄧'=>'𒄧','𒄨'=>'𒄨','𒄩'=>'𒄩','𒄪'=>'𒄪','𒄫'=>'𒄫','𒄬'=>'𒄬','𒄭'=>'𒄭','𒄮'=>'𒄮','𒄯'=>'𒄯','𒄰'=>'𒄰','𒄱'=>'𒄱','𒄲'=>'𒄲','𒄳'=>'𒄳','𒄴'=>'𒄴','𒄵'=>'𒄵','𒄶'=>'𒄶','𒄷'=>'𒄷','𒄸'=>'𒄸','𒄹'=>'𒄹','𒄺'=>'𒄺','𒄻'=>'𒄻','𒄼'=>'𒄼','𒄽'=>'𒄽','𒄾'=>'𒄾','𒄿'=>'𒄿','𒅀'=>'𒅀','𒅁'=>'𒅁','𒅂'=>'𒅂','𒅃'=>'𒅃','𒅄'=>'𒅄','𒅅'=>'𒅅','𒅆'=>'𒅆','𒅇'=>'𒅇','𒅈'=>'𒅈','𒅉'=>'𒅉','𒅊'=>'𒅊','𒅋'=>'𒅋','𒅌'=>'𒅌','𒅍'=>'𒅍','𒅎'=>'𒅎','𒅏'=>'𒅏','𒅐'=>'𒅐','𒅑'=>'𒅑','𒅒'=>'𒅒','𒅓'=>'𒅓','𒅔'=>'𒅔','𒅕'=>'𒅕','𒅖'=>'𒅖','𒅗'=>'𒅗','𒅘'=>'𒅘','𒅙'=>'𒅙','𒅚'=>'𒅚','𒅛'=>'𒅛','𒅜'=>'𒅜','𒅝'=>'𒅝','𒅞'=>'𒅞','𒅟'=>'𒅟','𒅠'=>'𒅠','𒅡'=>'𒅡','𒅢'=>'𒅢','𒅣'=>'𒅣','𒅤'=>'𒅤','𒅥'=>'𒅥','𒅦'=>'𒅦','𒅧'=>'𒅧','𒅨'=>'𒅨','𒅩'=>'𒅩','𒅪'=>'𒅪','𒅫'=>'𒅫','𒅬'=>'𒅬','𒅭'=>'𒅭','𒅮'=>'𒅮','𒅯'=>'𒅯','𒅰'=>'𒅰','𒅱'=>'𒅱','𒅲'=>'𒅲','𒅳'=>'𒅳','𒅴'=>'𒅴','𒅵'=>'𒅵','𒅶'=>'𒅶','𒅷'=>'𒅷','𒅸'=>'𒅸','𒅹'=>'𒅹','𒅺'=>'𒅺','𒅻'=>'𒅻','𒅼'=>'𒅼','𒅽'=>'𒅽','𒅾'=>'𒅾','𒅿'=>'𒅿','𒆀'=>'𒆀','𒆁'=>'𒆁','𒆂'=>'𒆂','𒆃'=>'𒆃','𒆄'=>'𒆄','𒆅'=>'𒆅','𒆆'=>'𒆆','𒆇'=>'𒆇','𒆈'=>'𒆈','𒆉'=>'𒆉','𒆊'=>'𒆊','𒆋'=>'𒆋','𒆌'=>'𒆌','𒆍'=>'𒆍','𒆎'=>'𒆎','𒆏'=>'𒆏','𒆐'=>'𒆐','𒆑'=>'𒆑','𒆒'=>'𒆒','𒆓'=>'𒆓','𒆔'=>'𒆔','𒆕'=>'𒆕','𒆖'=>'𒆖','𒆗'=>'𒆗','𒆘'=>'𒆘','𒆙'=>'𒆙','𒆚'=>'𒆚','𒆛'=>'𒆛','𒆜'=>'𒆜','𒆝'=>'𒆝','𒆞'=>'𒆞','𒆟'=>'𒆟','𒆠'=>'𒆠','𒆡'=>'𒆡','𒆢'=>'𒆢','𒆣'=>'𒆣','𒆤'=>'𒆤','𒆥'=>'𒆥','𒆦'=>'𒆦','𒆧'=>'𒆧','𒆨'=>'𒆨','𒆩'=>'𒆩','𒆪'=>'𒆪','𒆫'=>'𒆫','𒆬'=>'𒆬','𒆭'=>'𒆭','𒆮'=>'𒆮','𒆯'=>'𒆯','𒆰'=>'𒆰','𒆱'=>'𒆱','𒆲'=>'𒆲','𒆳'=>'𒆳','𒆴'=>'𒆴','𒆵'=>'𒆵','𒆶'=>'𒆶','𒆷'=>'𒆷','𒆸'=>'𒆸','𒆹'=>'𒆹','𒆺'=>'𒆺','𒆻'=>'𒆻','𒆼'=>'𒆼','𒆽'=>'𒆽','𒆾'=>'𒆾','𒆿'=>'𒆿','𒇀'=>'𒇀','𒇁'=>'𒇁','𒇂'=>'𒇂','𒇃'=>'𒇃','𒇄'=>'𒇄','𒇅'=>'𒇅','𒇆'=>'𒇆','𒇇'=>'𒇇','𒇈'=>'𒇈','𒇉'=>'𒇉','𒇊'=>'𒇊','𒇋'=>'𒇋','𒇌'=>'𒇌','𒇍'=>'𒇍','𒇎'=>'𒇎','𒇏'=>'𒇏','𒇐'=>'𒇐','𒇑'=>'𒇑','𒇒'=>'𒇒','𒇓'=>'𒇓','𒇔'=>'𒇔','𒇕'=>'𒇕','𒇖'=>'𒇖','𒇗'=>'𒇗','𒇘'=>'𒇘','𒇙'=>'𒇙','𒇚'=>'𒇚','𒇛'=>'𒇛','𒇜'=>'𒇜','𒇝'=>'𒇝','𒇞'=>'𒇞','𒇟'=>'𒇟','𒇠'=>'𒇠','𒇡'=>'𒇡','𒇢'=>'𒇢','𒇣'=>'𒇣','𒇤'=>'𒇤','𒇥'=>'𒇥','𒇦'=>'𒇦','𒇧'=>'𒇧','𒇨'=>'𒇨','𒇩'=>'𒇩','𒇪'=>'𒇪','𒇫'=>'𒇫','𒇬'=>'𒇬','𒇭'=>'𒇭','𒇮'=>'𒇮','𒇯'=>'𒇯','𒇰'=>'𒇰','𒇱'=>'𒇱','𒇲'=>'𒇲','𒇳'=>'𒇳','𒇴'=>'𒇴','𒇵'=>'𒇵','𒇶'=>'𒇶','𒇷'=>'𒇷','𒇸'=>'𒇸','𒇹'=>'𒇹','𒇺'=>'𒇺','𒇻'=>'𒇻','𒇼'=>'𒇼','𒇽'=>'𒇽','𒇾'=>'𒇾','𒇿'=>'𒇿','𒈀'=>'𒈀','𒈁'=>'𒈁','𒈂'=>'𒈂','𒈃'=>'𒈃','𒈄'=>'𒈄','𒈅'=>'𒈅','𒈆'=>'𒈆','𒈇'=>'𒈇','𒈈'=>'𒈈','𒈉'=>'𒈉','𒈊'=>'𒈊','𒈋'=>'𒈋','𒈌'=>'𒈌','𒈍'=>'𒈍','𒈎'=>'𒈎','𒈏'=>'𒈏','𒈐'=>'𒈐','𒈑'=>'𒈑','𒈒'=>'𒈒','𒈓'=>'𒈓','𒈔'=>'𒈔','𒈕'=>'𒈕','𒈖'=>'𒈖','𒈗'=>'𒈗','𒈘'=>'𒈘','𒈙'=>'𒈙','𒈚'=>'𒈚','𒈛'=>'𒈛','𒈜'=>'𒈜','𒈝'=>'𒈝','𒈞'=>'𒈞','𒈟'=>'𒈟','𒈠'=>'𒈠','𒈡'=>'𒈡','𒈢'=>'𒈢','𒈣'=>'𒈣','𒈤'=>'𒈤','𒈥'=>'𒈥','𒈦'=>'𒈦','𒈧'=>'𒈧','𒈨'=>'𒈨','𒈩'=>'𒈩','𒈪'=>'𒈪','𒈫'=>'𒈫','𒈬'=>'𒈬','𒈭'=>'𒈭','𒈮'=>'𒈮','𒈯'=>'𒈯','𒈰'=>'𒈰','𒈱'=>'𒈱','𒈲'=>'𒈲','𒈳'=>'𒈳','𒈴'=>'𒈴','𒈵'=>'𒈵','𒈶'=>'𒈶','𒈷'=>'𒈷','𒈸'=>'𒈸','𒈹'=>'𒈹','𒈺'=>'𒈺','𒈻'=>'𒈻','𒈼'=>'𒈼','𒈽'=>'𒈽','𒈾'=>'𒈾','𒈿'=>'𒈿','𒉀'=>'𒉀','𒉁'=>'𒉁','𒉂'=>'𒉂','𒉃'=>'𒉃','𒉄'=>'𒉄','𒉅'=>'𒉅','𒉆'=>'𒉆','𒉇'=>'𒉇','𒉈'=>'𒉈','𒉉'=>'𒉉','𒉊'=>'𒉊','𒉋'=>'𒉋','𒉌'=>'𒉌','𒉍'=>'𒉍','𒉎'=>'𒉎','𒉏'=>'𒉏','𒉐'=>'𒉐','𒉑'=>'𒉑','𒉒'=>'𒉒','𒉓'=>'𒉓','𒉔'=>'𒉔','𒉕'=>'𒉕','𒉖'=>'𒉖','𒉗'=>'𒉗','𒉘'=>'𒉘','𒉙'=>'𒉙','𒉚'=>'𒉚','𒉛'=>'𒉛','𒉜'=>'𒉜','𒉝'=>'𒉝','𒉞'=>'𒉞','𒉟'=>'𒉟','𒉠'=>'𒉠','𒉡'=>'𒉡','𒉢'=>'𒉢','𒉣'=>'𒉣','𒉤'=>'𒉤','𒉥'=>'𒉥','𒉦'=>'𒉦','𒉧'=>'𒉧','𒉨'=>'𒉨','𒉩'=>'𒉩','𒉪'=>'𒉪','𒉫'=>'𒉫','𒉬'=>'𒉬','𒉭'=>'𒉭','𒉮'=>'𒉮','𒉯'=>'𒉯','𒉰'=>'𒉰','𒉱'=>'𒉱','𒉲'=>'𒉲','𒉳'=>'𒉳','𒉴'=>'𒉴','𒉵'=>'𒉵','𒉶'=>'𒉶','𒉷'=>'𒉷','𒉸'=>'𒉸','𒉹'=>'𒉹','𒉺'=>'𒉺','𒉻'=>'𒉻','𒉼'=>'𒉼','𒉽'=>'𒉽','𒉾'=>'𒉾','𒉿'=>'𒉿','𒊀'=>'𒊀','𒊁'=>'𒊁','𒊂'=>'𒊂','𒊃'=>'𒊃','𒊄'=>'𒊄','𒊅'=>'𒊅','𒊆'=>'𒊆','𒊇'=>'𒊇','𒊈'=>'𒊈','𒊉'=>'𒊉','𒊊'=>'𒊊','𒊋'=>'𒊋','𒊌'=>'𒊌','𒊍'=>'𒊍','𒊎'=>'𒊎','𒊏'=>'𒊏','𒊐'=>'𒊐','𒊑'=>'𒊑','𒊒'=>'𒊒','𒊓'=>'𒊓','𒊔'=>'𒊔','𒊕'=>'𒊕','𒊖'=>'𒊖','𒊗'=>'𒊗','𒊘'=>'𒊘','𒊙'=>'𒊙','𒊚'=>'𒊚','𒊛'=>'𒊛','𒊜'=>'𒊜','𒊝'=>'𒊝','𒊞'=>'𒊞','𒊟'=>'𒊟','𒊠'=>'𒊠','𒊡'=>'𒊡','𒊢'=>'𒊢','𒊣'=>'𒊣','𒊤'=>'𒊤','𒊥'=>'𒊥','𒊦'=>'𒊦','𒊧'=>'𒊧','𒊨'=>'𒊨','𒊩'=>'𒊩','𒊪'=>'𒊪','𒊫'=>'𒊫','𒊬'=>'𒊬','𒊭'=>'𒊭','𒊮'=>'𒊮','𒊯'=>'𒊯','𒊰'=>'𒊰','𒊱'=>'𒊱','𒊲'=>'𒊲','𒊳'=>'𒊳','𒊴'=>'𒊴','𒊵'=>'𒊵','𒊶'=>'𒊶','𒊷'=>'𒊷','𒊸'=>'𒊸','𒊹'=>'𒊹','𒊺'=>'𒊺','𒊻'=>'𒊻','𒊼'=>'𒊼','𒊽'=>'𒊽','𒊾'=>'𒊾','𒊿'=>'𒊿','𒋀'=>'𒋀','𒋁'=>'𒋁','𒋂'=>'𒋂','𒋃'=>'𒋃','𒋄'=>'𒋄','𒋅'=>'𒋅','𒋆'=>'𒋆','𒋇'=>'𒋇','𒋈'=>'𒋈','𒋉'=>'𒋉','𒋊'=>'𒋊','𒋋'=>'𒋋','𒋌'=>'𒋌','𒋍'=>'𒋍','𒋎'=>'𒋎','𒋏'=>'𒋏','𒋐'=>'𒋐','𒋑'=>'𒋑','𒋒'=>'𒋒','𒋓'=>'𒋓','𒋔'=>'𒋔','𒋕'=>'𒋕','𒋖'=>'𒋖','𒋗'=>'𒋗','𒋘'=>'𒋘','𒋙'=>'𒋙','𒋚'=>'𒋚','𒋛'=>'𒋛','𒋜'=>'𒋜','𒋝'=>'𒋝','𒋞'=>'𒋞','𒋟'=>'𒋟','𒋠'=>'𒋠','𒋡'=>'𒋡','𒋢'=>'𒋢','𒋣'=>'𒋣','𒋤'=>'𒋤','𒋥'=>'𒋥','𒋦'=>'𒋦','𒋧'=>'𒋧','𒋨'=>'𒋨','𒋩'=>'𒋩','𒋪'=>'𒋪','𒋫'=>'𒋫','𒋬'=>'𒋬','𒋭'=>'𒋭','𒋮'=>'𒋮','𒋯'=>'𒋯','𒋰'=>'𒋰','𒋱'=>'𒋱','𒋲'=>'𒋲','𒋳'=>'𒋳','𒋴'=>'𒋴','𒋵'=>'𒋵','𒋶'=>'𒋶','𒋷'=>'𒋷','𒋸'=>'𒋸','𒋹'=>'𒋹','𒋺'=>'𒋺','𒋻'=>'𒋻','𒋼'=>'𒋼','𒋽'=>'𒋽','𒋾'=>'𒋾','𒋿'=>'𒋿','𒌀'=>'𒌀','𒌁'=>'𒌁','𒌂'=>'𒌂','𒌃'=>'𒌃','𒌄'=>'𒌄','𒌅'=>'𒌅','𒌆'=>'𒌆','𒌇'=>'𒌇','𒌈'=>'𒌈','𒌉'=>'𒌉','𒌊'=>'𒌊','𒌋'=>'𒌋','𒌌'=>'𒌌','𒌍'=>'𒌍','𒌎'=>'𒌎','𒌏'=>'𒌏','𒌐'=>'𒌐','𒌑'=>'𒌑','𒌒'=>'𒌒','𒌓'=>'𒌓','𒌔'=>'𒌔','𒌕'=>'𒌕','𒌖'=>'𒌖','𒌗'=>'𒌗','𒌘'=>'𒌘','𒌙'=>'𒌙','𒌚'=>'𒌚','𒌛'=>'𒌛','𒌜'=>'𒌜','𒌝'=>'𒌝','𒌞'=>'𒌞','𒌟'=>'𒌟','𒌠'=>'𒌠','𒌡'=>'𒌡','𒌢'=>'𒌢','𒌣'=>'𒌣','𒌤'=>'𒌤','𒌥'=>'𒌥','𒌦'=>'𒌦','𒌧'=>'𒌧','𒌨'=>'𒌨','𒌩'=>'𒌩','𒌪'=>'𒌪','𒌫'=>'𒌫','𒌬'=>'𒌬','𒌭'=>'𒌭','𒌮'=>'𒌮','𒌯'=>'𒌯','𒌰'=>'𒌰','𒌱'=>'𒌱','𒌲'=>'𒌲','𒌳'=>'𒌳','𒌴'=>'𒌴','𒌵'=>'𒌵','𒌶'=>'𒌶','𒌷'=>'𒌷','𒌸'=>'𒌸','𒌹'=>'𒌹','𒌺'=>'𒌺','𒌻'=>'𒌻','𒌼'=>'𒌼','𒌽'=>'𒌽','𒌾'=>'𒌾','𒌿'=>'𒌿','𒍀'=>'𒍀','𒍁'=>'𒍁','𒍂'=>'𒍂','𒍃'=>'𒍃','𒍄'=>'𒍄','𒍅'=>'𒍅','𒍆'=>'𒍆','𒍇'=>'𒍇','𒍈'=>'𒍈','𒍉'=>'𒍉','𒍊'=>'𒍊','𒍋'=>'𒍋','𒍌'=>'𒍌','𒍍'=>'𒍍','𒍎'=>'𒍎','𒍏'=>'𒍏','𒍐'=>'𒍐','𒍑'=>'𒍑','𒍒'=>'𒍒','𒍓'=>'𒍓','𒍔'=>'𒍔','𒍕'=>'𒍕','𒍖'=>'𒍖','𒍗'=>'𒍗','𒍘'=>'𒍘','𒍙'=>'𒍙','𒍚'=>'𒍚','𒍛'=>'𒍛','𒍜'=>'𒍜','𒍝'=>'𒍝','𒍞'=>'𒍞','𒍟'=>'𒍟','𒍠'=>'𒍠','𒍡'=>'𒍡','𒍢'=>'𒍢','𒍣'=>'𒍣','𒍤'=>'𒍤','𒍥'=>'𒍥','𒍦'=>'𒍦','𒍧'=>'𒍧','𒍨'=>'𒍨','𒍩'=>'𒍩','𒍪'=>'𒍪','𒍫'=>'𒍫','𒍬'=>'𒍬','𒍭'=>'𒍭','𒍮'=>'𒍮','𒐀'=>'2','𒐁'=>'3','𒐂'=>'4','𒐃'=>'5','𒐄'=>'6','𒐅'=>'7','𒐆'=>'8','𒐇'=>'9','𒐈'=>'3','𒐉'=>'4','𒐊'=>'5','𒐋'=>'6','𒐌'=>'7','𒐍'=>'8','𒐎'=>'9','𒐏'=>'4','𒐐'=>'5','𒐑'=>'6','𒐒'=>'7','𒐓'=>'8','𒐔'=>'9','𒐕'=>'1','𒐖'=>'2','𒐗'=>'3','𒐘'=>'4','𒐙'=>'5','𒐚'=>'6','𒐛'=>'7','𒐜'=>'8','𒐝'=>'9','𒐞'=>'1','𒐟'=>'2','𒐠'=>'3','𒐡'=>'4','𒐢'=>'5','𒐣'=>'2','𒐤'=>'3','𒐥'=>'3','𒐦'=>'4','𒐧'=>'5','𒐨'=>'6','𒐩'=>'7','𒐪'=>'8','𒐫'=>'9','𒐬'=>'1','𒐭'=>'2','𒐮'=>'3','𒐯'=>'3','𒐰'=>'4','𒐱'=>'5','𒐲'=>'𒐲','𒐳'=>'𒐳','𒐴'=>'1','𒐵'=>'2','𒐶'=>'3','𒐷'=>'3','𒐸'=>'4','𒐹'=>'5','𒐺'=>'3','𒐻'=>'3','𒐼'=>'4','𒐽'=>'4','𒐾'=>'4','𒐿'=>'4','𒑀'=>'6','𒑁'=>'7','𒑂'=>'7','𒑃'=>'7','𒑄'=>'8','𒑅'=>'8','𒑆'=>'9','𒑇'=>'9','𒑈'=>'9','𒑉'=>'9','𒑊'=>'2','𒑋'=>'3','𒑌'=>'4','𒑍'=>'5','𒑎'=>'6','𒑏'=>'1','𒑐'=>'2','𒑑'=>'3','𒑒'=>'4','𒑓'=>'4','𒑔'=>'5','𒑕'=>'5','𒑖'=>'𒑖','𒑗'=>'𒑗','𒑘'=>'1','𒑙'=>'2','𒑚'=>'1/3','𒑛'=>'2/3','𒑜'=>'5/6','𒑝'=>'1/3','𒑞'=>'2/3','𒑟'=>'1/8','𒑠'=>'1/4','𒑡'=>'1/6','𒑢'=>'1/4'); diff --git a/includes/utf/data/search_indexer_4.php b/includes/utf/data/search_indexer_4.php new file mode 100644 index 0000000..6697501 --- /dev/null +++ b/includes/utf/data/search_indexer_4.php @@ -0,0 +1 @@ +'0','ⁱ'=>'ⁱ','⁴'=>'4','⁵'=>'5','⁶'=>'6','⁷'=>'7','⁸'=>'8','⁹'=>'9','ⁿ'=>'ⁿ','₀'=>'0','₁'=>'1','₂'=>'2','₃'=>'3','₄'=>'4','₅'=>'5','₆'=>'6','₇'=>'7','₈'=>'8','₉'=>'9','ₐ'=>'ₐ','ₑ'=>'ₑ','ₒ'=>'ₒ','ₓ'=>'ₓ','ₔ'=>'ₔ','⃐'=>'⃐','⃑'=>'⃑','⃒'=>'⃒','⃓'=>'⃓','⃔'=>'⃔','⃕'=>'⃕','⃖'=>'⃖','⃗'=>'⃗','⃘'=>'⃘','⃙'=>'⃙','⃚'=>'⃚','⃛'=>'⃛','⃜'=>'⃜','⃝'=>'⃝','⃞'=>'⃞','⃟'=>'⃟','⃠'=>'⃠','⃡'=>'⃡','⃢'=>'⃢','⃣'=>'⃣','⃤'=>'⃤','⃥'=>'⃥','⃦'=>'⃦','⃧'=>'⃧','⃨'=>'⃨','⃩'=>'⃩','⃪'=>'⃪','⃫'=>'⃫','⃬'=>'⃬','⃭'=>'⃭','⃮'=>'⃮','⃯'=>'⃯','ℂ'=>'ℂ','ℇ'=>'ℇ','ℊ'=>'ℊ','ℋ'=>'ℋ','ℌ'=>'ℌ','ℍ'=>'ℍ','ℎ'=>'ℎ','ℏ'=>'ℏ','ℐ'=>'ℐ','ℑ'=>'ℑ','ℒ'=>'ℒ','ℓ'=>'ℓ','ℕ'=>'ℕ','ℙ'=>'ℙ','ℚ'=>'ℚ','ℛ'=>'ℛ','ℜ'=>'ℜ','ℝ'=>'ℝ','ℤ'=>'ℤ','Ω'=>'ω','ℨ'=>'ℨ','K'=>'k','Å'=>'å','ℬ'=>'ℬ','ℭ'=>'ℭ','ℯ'=>'ℯ','ℰ'=>'ℰ','ℱ'=>'ℱ','Ⅎ'=>'ⅎ','ℳ'=>'ℳ','ℴ'=>'ℴ','ℵ'=>'ℵ','ℶ'=>'ℶ','ℷ'=>'ℷ','ℸ'=>'ℸ','ℹ'=>'ℹ','ℼ'=>'ℼ','ℽ'=>'ℽ','ℾ'=>'ℾ','ℿ'=>'ℿ','ⅅ'=>'ⅅ','ⅆ'=>'ⅆ','ⅇ'=>'ⅇ','ⅈ'=>'ⅈ','ⅉ'=>'ⅉ','ⅎ'=>'ⅎ','⅓'=>'1/3','⅔'=>'2/3','⅕'=>'1/5','⅖'=>'2/5','⅗'=>'3/5','⅘'=>'4/5','⅙'=>'1/6','⅚'=>'5/6','⅛'=>'1/8','⅜'=>'3/8','⅝'=>'5/8','⅞'=>'7/8','⅟'=>'1','Ⅰ'=>'1','Ⅱ'=>'2','Ⅲ'=>'3','Ⅳ'=>'4','Ⅴ'=>'5','Ⅵ'=>'6','Ⅶ'=>'7','Ⅷ'=>'8','Ⅸ'=>'9','Ⅹ'=>'10','Ⅺ'=>'11','Ⅻ'=>'12','Ⅼ'=>'50','Ⅽ'=>'100','Ⅾ'=>'500','Ⅿ'=>'1000','ⅰ'=>'1','ⅱ'=>'2','ⅲ'=>'3','ⅳ'=>'4','ⅴ'=>'5','ⅵ'=>'6','ⅶ'=>'7','ⅷ'=>'8','ⅸ'=>'9','ⅹ'=>'10','ⅺ'=>'11','ⅻ'=>'12','ⅼ'=>'50','ⅽ'=>'100','ⅾ'=>'500','ⅿ'=>'1000','ↀ'=>'1000','ↁ'=>'5000','ↂ'=>'10000','Ↄ'=>'ↄ','ↄ'=>'ↄ','①'=>'1','②'=>'2','③'=>'3','④'=>'4','⑤'=>'5','⑥'=>'6','⑦'=>'7','⑧'=>'8','⑨'=>'9','⑩'=>'10','⑪'=>'11','⑫'=>'12','⑬'=>'13','⑭'=>'14','⑮'=>'15','⑯'=>'16','⑰'=>'17','⑱'=>'18','⑲'=>'19','⑳'=>'20','⑴'=>'1','⑵'=>'2','⑶'=>'3','⑷'=>'4','⑸'=>'5','⑹'=>'6','⑺'=>'7','⑻'=>'8','⑼'=>'9','⑽'=>'10','⑾'=>'11','⑿'=>'12','⒀'=>'13','⒁'=>'14','⒂'=>'15','⒃'=>'16','⒄'=>'17','⒅'=>'18','⒆'=>'19','⒇'=>'20','⒈'=>'1','⒉'=>'2','⒊'=>'3','⒋'=>'4','⒌'=>'5','⒍'=>'6','⒎'=>'7','⒏'=>'8','⒐'=>'9','⒑'=>'10','⒒'=>'11','⒓'=>'12','⒔'=>'13','⒕'=>'14','⒖'=>'15','⒗'=>'16','⒘'=>'17','⒙'=>'18','⒚'=>'19','⒛'=>'20','⓪'=>'0','⓫'=>'11','⓬'=>'12','⓭'=>'13','⓮'=>'14','⓯'=>'15','⓰'=>'16','⓱'=>'17','⓲'=>'18','⓳'=>'19','⓴'=>'20','⓵'=>'1','⓶'=>'2','⓷'=>'3','⓸'=>'4','⓹'=>'5','⓺'=>'6','⓻'=>'7','⓼'=>'8','⓽'=>'9','⓾'=>'10','⓿'=>'0','❶'=>'1','❷'=>'2','❸'=>'3','❹'=>'4','❺'=>'5','❻'=>'6','❼'=>'7','❽'=>'8','❾'=>'9','❿'=>'10','➀'=>'1','➁'=>'2','➂'=>'3','➃'=>'4','➄'=>'5','➅'=>'6','➆'=>'7','➇'=>'8','➈'=>'9','➉'=>'10','➊'=>'1','➋'=>'2','➌'=>'3','➍'=>'4','➎'=>'5','➏'=>'6','➐'=>'7','➑'=>'8','➒'=>'9','➓'=>'10'); diff --git a/includes/utf/data/search_indexer_448.php b/includes/utf/data/search_indexer_448.php new file mode 100644 index 0000000..6cbf664 --- /dev/null +++ b/includes/utf/data/search_indexer_448.php @@ -0,0 +1 @@ +'󠄀','󠄁'=>'󠄁','󠄂'=>'󠄂','󠄃'=>'󠄃','󠄄'=>'󠄄','󠄅'=>'󠄅','󠄆'=>'󠄆','󠄇'=>'󠄇','󠄈'=>'󠄈','󠄉'=>'󠄉','󠄊'=>'󠄊','󠄋'=>'󠄋','󠄌'=>'󠄌','󠄍'=>'󠄍','󠄎'=>'󠄎','󠄏'=>'󠄏','󠄐'=>'󠄐','󠄑'=>'󠄑','󠄒'=>'󠄒','󠄓'=>'󠄓','󠄔'=>'󠄔','󠄕'=>'󠄕','󠄖'=>'󠄖','󠄗'=>'󠄗','󠄘'=>'󠄘','󠄙'=>'󠄙','󠄚'=>'󠄚','󠄛'=>'󠄛','󠄜'=>'󠄜','󠄝'=>'󠄝','󠄞'=>'󠄞','󠄟'=>'󠄟','󠄠'=>'󠄠','󠄡'=>'󠄡','󠄢'=>'󠄢','󠄣'=>'󠄣','󠄤'=>'󠄤','󠄥'=>'󠄥','󠄦'=>'󠄦','󠄧'=>'󠄧','󠄨'=>'󠄨','󠄩'=>'󠄩','󠄪'=>'󠄪','󠄫'=>'󠄫','󠄬'=>'󠄬','󠄭'=>'󠄭','󠄮'=>'󠄮','󠄯'=>'󠄯','󠄰'=>'󠄰','󠄱'=>'󠄱','󠄲'=>'󠄲','󠄳'=>'󠄳','󠄴'=>'󠄴','󠄵'=>'󠄵','󠄶'=>'󠄶','󠄷'=>'󠄷','󠄸'=>'󠄸','󠄹'=>'󠄹','󠄺'=>'󠄺','󠄻'=>'󠄻','󠄼'=>'󠄼','󠄽'=>'󠄽','󠄾'=>'󠄾','󠄿'=>'󠄿','󠅀'=>'󠅀','󠅁'=>'󠅁','󠅂'=>'󠅂','󠅃'=>'󠅃','󠅄'=>'󠅄','󠅅'=>'󠅅','󠅆'=>'󠅆','󠅇'=>'󠅇','󠅈'=>'󠅈','󠅉'=>'󠅉','󠅊'=>'󠅊','󠅋'=>'󠅋','󠅌'=>'󠅌','󠅍'=>'󠅍','󠅎'=>'󠅎','󠅏'=>'󠅏','󠅐'=>'󠅐','󠅑'=>'󠅑','󠅒'=>'󠅒','󠅓'=>'󠅓','󠅔'=>'󠅔','󠅕'=>'󠅕','󠅖'=>'󠅖','󠅗'=>'󠅗','󠅘'=>'󠅘','󠅙'=>'󠅙','󠅚'=>'󠅚','󠅛'=>'󠅛','󠅜'=>'󠅜','󠅝'=>'󠅝','󠅞'=>'󠅞','󠅟'=>'󠅟','󠅠'=>'󠅠','󠅡'=>'󠅡','󠅢'=>'󠅢','󠅣'=>'󠅣','󠅤'=>'󠅤','󠅥'=>'󠅥','󠅦'=>'󠅦','󠅧'=>'󠅧','󠅨'=>'󠅨','󠅩'=>'󠅩','󠅪'=>'󠅪','󠅫'=>'󠅫','󠅬'=>'󠅬','󠅭'=>'󠅭','󠅮'=>'󠅮','󠅯'=>'󠅯','󠅰'=>'󠅰','󠅱'=>'󠅱','󠅲'=>'󠅲','󠅳'=>'󠅳','󠅴'=>'󠅴','󠅵'=>'󠅵','󠅶'=>'󠅶','󠅷'=>'󠅷','󠅸'=>'󠅸','󠅹'=>'󠅹','󠅺'=>'󠅺','󠅻'=>'󠅻','󠅼'=>'󠅼','󠅽'=>'󠅽','󠅾'=>'󠅾','󠅿'=>'󠅿','󠆀'=>'󠆀','󠆁'=>'󠆁','󠆂'=>'󠆂','󠆃'=>'󠆃','󠆄'=>'󠆄','󠆅'=>'󠆅','󠆆'=>'󠆆','󠆇'=>'󠆇','󠆈'=>'󠆈','󠆉'=>'󠆉','󠆊'=>'󠆊','󠆋'=>'󠆋','󠆌'=>'󠆌','󠆍'=>'󠆍','󠆎'=>'󠆎','󠆏'=>'󠆏','󠆐'=>'󠆐','󠆑'=>'󠆑','󠆒'=>'󠆒','󠆓'=>'󠆓','󠆔'=>'󠆔','󠆕'=>'󠆕','󠆖'=>'󠆖','󠆗'=>'󠆗','󠆘'=>'󠆘','󠆙'=>'󠆙','󠆚'=>'󠆚','󠆛'=>'󠆛','󠆜'=>'󠆜','󠆝'=>'󠆝','󠆞'=>'󠆞','󠆟'=>'󠆟','󠆠'=>'󠆠','󠆡'=>'󠆡','󠆢'=>'󠆢','󠆣'=>'󠆣','󠆤'=>'󠆤','󠆥'=>'󠆥','󠆦'=>'󠆦','󠆧'=>'󠆧','󠆨'=>'󠆨','󠆩'=>'󠆩','󠆪'=>'󠆪','󠆫'=>'󠆫','󠆬'=>'󠆬','󠆭'=>'󠆭','󠆮'=>'󠆮','󠆯'=>'󠆯','󠆰'=>'󠆰','󠆱'=>'󠆱','󠆲'=>'󠆲','󠆳'=>'󠆳','󠆴'=>'󠆴','󠆵'=>'󠆵','󠆶'=>'󠆶','󠆷'=>'󠆷','󠆸'=>'󠆸','󠆹'=>'󠆹','󠆺'=>'󠆺','󠆻'=>'󠆻','󠆼'=>'󠆼','󠆽'=>'󠆽','󠆾'=>'󠆾','󠆿'=>'󠆿','󠇀'=>'󠇀','󠇁'=>'󠇁','󠇂'=>'󠇂','󠇃'=>'󠇃','󠇄'=>'󠇄','󠇅'=>'󠇅','󠇆'=>'󠇆','󠇇'=>'󠇇','󠇈'=>'󠇈','󠇉'=>'󠇉','󠇊'=>'󠇊','󠇋'=>'󠇋','󠇌'=>'󠇌','󠇍'=>'󠇍','󠇎'=>'󠇎','󠇏'=>'󠇏','󠇐'=>'󠇐','󠇑'=>'󠇑','󠇒'=>'󠇒','󠇓'=>'󠇓','󠇔'=>'󠇔','󠇕'=>'󠇕','󠇖'=>'󠇖','󠇗'=>'󠇗','󠇘'=>'󠇘','󠇙'=>'󠇙','󠇚'=>'󠇚','󠇛'=>'󠇛','󠇜'=>'󠇜','󠇝'=>'󠇝','󠇞'=>'󠇞','󠇟'=>'󠇟','󠇠'=>'󠇠','󠇡'=>'󠇡','󠇢'=>'󠇢','󠇣'=>'󠇣','󠇤'=>'󠇤','󠇥'=>'󠇥','󠇦'=>'󠇦','󠇧'=>'󠇧','󠇨'=>'󠇨','󠇩'=>'󠇩','󠇪'=>'󠇪','󠇫'=>'󠇫','󠇬'=>'󠇬','󠇭'=>'󠇭','󠇮'=>'󠇮','󠇯'=>'󠇯'); diff --git a/includes/utf/data/search_indexer_5.php b/includes/utf/data/search_indexer_5.php new file mode 100644 index 0000000..cef8fe9 --- /dev/null +++ b/includes/utf/data/search_indexer_5.php @@ -0,0 +1 @@ +'ⰰ','Ⰱ'=>'ⰱ','Ⰲ'=>'ⰲ','Ⰳ'=>'ⰳ','Ⰴ'=>'ⰴ','Ⰵ'=>'ⰵ','Ⰶ'=>'ⰶ','Ⰷ'=>'ⰷ','Ⰸ'=>'ⰸ','Ⰹ'=>'ⰹ','Ⰺ'=>'ⰺ','Ⰻ'=>'ⰻ','Ⰼ'=>'ⰼ','Ⰽ'=>'ⰽ','Ⰾ'=>'ⰾ','Ⰿ'=>'ⰿ','Ⱀ'=>'ⱀ','Ⱁ'=>'ⱁ','Ⱂ'=>'ⱂ','Ⱃ'=>'ⱃ','Ⱄ'=>'ⱄ','Ⱅ'=>'ⱅ','Ⱆ'=>'ⱆ','Ⱇ'=>'ⱇ','Ⱈ'=>'ⱈ','Ⱉ'=>'ⱉ','Ⱊ'=>'ⱊ','Ⱋ'=>'ⱋ','Ⱌ'=>'ⱌ','Ⱍ'=>'ⱍ','Ⱎ'=>'ⱎ','Ⱏ'=>'ⱏ','Ⱐ'=>'ⱐ','Ⱑ'=>'ⱑ','Ⱒ'=>'ⱒ','Ⱓ'=>'ⱓ','Ⱔ'=>'ⱔ','Ⱕ'=>'ⱕ','Ⱖ'=>'ⱖ','Ⱗ'=>'ⱗ','Ⱘ'=>'ⱘ','Ⱙ'=>'ⱙ','Ⱚ'=>'ⱚ','Ⱛ'=>'ⱛ','Ⱜ'=>'ⱜ','Ⱝ'=>'ⱝ','Ⱞ'=>'ⱞ','ⰰ'=>'ⰰ','ⰱ'=>'ⰱ','ⰲ'=>'ⰲ','ⰳ'=>'ⰳ','ⰴ'=>'ⰴ','ⰵ'=>'ⰵ','ⰶ'=>'ⰶ','ⰷ'=>'ⰷ','ⰸ'=>'ⰸ','ⰹ'=>'ⰹ','ⰺ'=>'ⰺ','ⰻ'=>'ⰻ','ⰼ'=>'ⰼ','ⰽ'=>'ⰽ','ⰾ'=>'ⰾ','ⰿ'=>'ⰿ','ⱀ'=>'ⱀ','ⱁ'=>'ⱁ','ⱂ'=>'ⱂ','ⱃ'=>'ⱃ','ⱄ'=>'ⱄ','ⱅ'=>'ⱅ','ⱆ'=>'ⱆ','ⱇ'=>'ⱇ','ⱈ'=>'ⱈ','ⱉ'=>'ⱉ','ⱊ'=>'ⱊ','ⱋ'=>'ⱋ','ⱌ'=>'ⱌ','ⱍ'=>'ⱍ','ⱎ'=>'ⱎ','ⱏ'=>'ⱏ','ⱐ'=>'ⱐ','ⱑ'=>'ⱑ','ⱒ'=>'ⱒ','ⱓ'=>'ⱓ','ⱔ'=>'ⱔ','ⱕ'=>'ⱕ','ⱖ'=>'ⱖ','ⱗ'=>'ⱗ','ⱘ'=>'ⱘ','ⱙ'=>'ⱙ','ⱚ'=>'ⱚ','ⱛ'=>'ⱛ','ⱜ'=>'ⱜ','ⱝ'=>'ⱝ','ⱞ'=>'ⱞ','Ⱡ'=>'ⱡ','ⱡ'=>'ⱡ','Ɫ'=>'ɫ','Ᵽ'=>'ᵽ','Ɽ'=>'ɽ','ⱥ'=>'ⱥ','ⱦ'=>'ⱦ','Ⱨ'=>'ⱨ','ⱨ'=>'ⱨ','Ⱪ'=>'ⱪ','ⱪ'=>'ⱪ','Ⱬ'=>'ⱬ','ⱬ'=>'ⱬ','ⱴ'=>'ⱴ','Ⱶ'=>'ⱶ','ⱶ'=>'ⱶ','ⱷ'=>'ⱷ','Ⲁ'=>'ⲁ','ⲁ'=>'ⲁ','Ⲃ'=>'ⲃ','ⲃ'=>'ⲃ','Ⲅ'=>'ⲅ','ⲅ'=>'ⲅ','Ⲇ'=>'ⲇ','ⲇ'=>'ⲇ','Ⲉ'=>'ⲉ','ⲉ'=>'ⲉ','Ⲋ'=>'ⲋ','ⲋ'=>'ⲋ','Ⲍ'=>'ⲍ','ⲍ'=>'ⲍ','Ⲏ'=>'ⲏ','ⲏ'=>'ⲏ','Ⲑ'=>'ⲑ','ⲑ'=>'ⲑ','Ⲓ'=>'ⲓ','ⲓ'=>'ⲓ','Ⲕ'=>'ⲕ','ⲕ'=>'ⲕ','Ⲗ'=>'ⲗ','ⲗ'=>'ⲗ','Ⲙ'=>'ⲙ','ⲙ'=>'ⲙ','Ⲛ'=>'ⲛ','ⲛ'=>'ⲛ','Ⲝ'=>'ⲝ','ⲝ'=>'ⲝ','Ⲟ'=>'ⲟ','ⲟ'=>'ⲟ','Ⲡ'=>'ⲡ','ⲡ'=>'ⲡ','Ⲣ'=>'ⲣ','ⲣ'=>'ⲣ','Ⲥ'=>'ⲥ','ⲥ'=>'ⲥ','Ⲧ'=>'ⲧ','ⲧ'=>'ⲧ','Ⲩ'=>'ⲩ','ⲩ'=>'ⲩ','Ⲫ'=>'ⲫ','ⲫ'=>'ⲫ','Ⲭ'=>'ⲭ','ⲭ'=>'ⲭ','Ⲯ'=>'ⲯ','ⲯ'=>'ⲯ','Ⲱ'=>'ⲱ','ⲱ'=>'ⲱ','Ⲳ'=>'ⲳ','ⲳ'=>'ⲳ','Ⲵ'=>'ⲵ','ⲵ'=>'ⲵ','Ⲷ'=>'ⲷ','ⲷ'=>'ⲷ','Ⲹ'=>'ⲹ','ⲹ'=>'ⲹ','Ⲻ'=>'ⲻ','ⲻ'=>'ⲻ','Ⲽ'=>'ⲽ','ⲽ'=>'ⲽ','Ⲿ'=>'ⲿ','ⲿ'=>'ⲿ','Ⳁ'=>'ⳁ','ⳁ'=>'ⳁ','Ⳃ'=>'ⳃ','ⳃ'=>'ⳃ','Ⳅ'=>'ⳅ','ⳅ'=>'ⳅ','Ⳇ'=>'ⳇ','ⳇ'=>'ⳇ','Ⳉ'=>'ⳉ','ⳉ'=>'ⳉ','Ⳋ'=>'ⳋ','ⳋ'=>'ⳋ','Ⳍ'=>'ⳍ','ⳍ'=>'ⳍ','Ⳏ'=>'ⳏ','ⳏ'=>'ⳏ','Ⳑ'=>'ⳑ','ⳑ'=>'ⳑ','Ⳓ'=>'ⳓ','ⳓ'=>'ⳓ','Ⳕ'=>'ⳕ','ⳕ'=>'ⳕ','Ⳗ'=>'ⳗ','ⳗ'=>'ⳗ','Ⳙ'=>'ⳙ','ⳙ'=>'ⳙ','Ⳛ'=>'ⳛ','ⳛ'=>'ⳛ','Ⳝ'=>'ⳝ','ⳝ'=>'ⳝ','Ⳟ'=>'ⳟ','ⳟ'=>'ⳟ','Ⳡ'=>'ⳡ','ⳡ'=>'ⳡ','Ⳣ'=>'ⳣ','ⳣ'=>'ⳣ','ⳤ'=>'ⳤ','⳽'=>'1/2','ⴀ'=>'ⴀ','ⴁ'=>'ⴁ','ⴂ'=>'ⴂ','ⴃ'=>'ⴃ','ⴄ'=>'ⴄ','ⴅ'=>'ⴅ','ⴆ'=>'ⴆ','ⴇ'=>'ⴇ','ⴈ'=>'ⴈ','ⴉ'=>'ⴉ','ⴊ'=>'ⴊ','ⴋ'=>'ⴋ','ⴌ'=>'ⴌ','ⴍ'=>'ⴍ','ⴎ'=>'ⴎ','ⴏ'=>'ⴏ','ⴐ'=>'ⴐ','ⴑ'=>'ⴑ','ⴒ'=>'ⴒ','ⴓ'=>'ⴓ','ⴔ'=>'ⴔ','ⴕ'=>'ⴕ','ⴖ'=>'ⴖ','ⴗ'=>'ⴗ','ⴘ'=>'ⴘ','ⴙ'=>'ⴙ','ⴚ'=>'ⴚ','ⴛ'=>'ⴛ','ⴜ'=>'ⴜ','ⴝ'=>'ⴝ','ⴞ'=>'ⴞ','ⴟ'=>'ⴟ','ⴠ'=>'ⴠ','ⴡ'=>'ⴡ','ⴢ'=>'ⴢ','ⴣ'=>'ⴣ','ⴤ'=>'ⴤ','ⴥ'=>'ⴥ','ⴰ'=>'ⴰ','ⴱ'=>'ⴱ','ⴲ'=>'ⴲ','ⴳ'=>'ⴳ','ⴴ'=>'ⴴ','ⴵ'=>'ⴵ','ⴶ'=>'ⴶ','ⴷ'=>'ⴷ','ⴸ'=>'ⴸ','ⴹ'=>'ⴹ','ⴺ'=>'ⴺ','ⴻ'=>'ⴻ','ⴼ'=>'ⴼ','ⴽ'=>'ⴽ','ⴾ'=>'ⴾ','ⴿ'=>'ⴿ','ⵀ'=>'ⵀ','ⵁ'=>'ⵁ','ⵂ'=>'ⵂ','ⵃ'=>'ⵃ','ⵄ'=>'ⵄ','ⵅ'=>'ⵅ','ⵆ'=>'ⵆ','ⵇ'=>'ⵇ','ⵈ'=>'ⵈ','ⵉ'=>'ⵉ','ⵊ'=>'ⵊ','ⵋ'=>'ⵋ','ⵌ'=>'ⵌ','ⵍ'=>'ⵍ','ⵎ'=>'ⵎ','ⵏ'=>'ⵏ','ⵐ'=>'ⵐ','ⵑ'=>'ⵑ','ⵒ'=>'ⵒ','ⵓ'=>'ⵓ','ⵔ'=>'ⵔ','ⵕ'=>'ⵕ','ⵖ'=>'ⵖ','ⵗ'=>'ⵗ','ⵘ'=>'ⵘ','ⵙ'=>'ⵙ','ⵚ'=>'ⵚ','ⵛ'=>'ⵛ','ⵜ'=>'ⵜ','ⵝ'=>'ⵝ','ⵞ'=>'ⵞ','ⵟ'=>'ⵟ','ⵠ'=>'ⵠ','ⵡ'=>'ⵡ','ⵢ'=>'ⵢ','ⵣ'=>'ⵣ','ⵤ'=>'ⵤ','ⵥ'=>'ⵥ','ⵯ'=>'ⵯ','ⶀ'=>'ⶀ','ⶁ'=>'ⶁ','ⶂ'=>'ⶂ','ⶃ'=>'ⶃ','ⶄ'=>'ⶄ','ⶅ'=>'ⶅ','ⶆ'=>'ⶆ','ⶇ'=>'ⶇ','ⶈ'=>'ⶈ','ⶉ'=>'ⶉ','ⶊ'=>'ⶊ','ⶋ'=>'ⶋ','ⶌ'=>'ⶌ','ⶍ'=>'ⶍ','ⶎ'=>'ⶎ','ⶏ'=>'ⶏ','ⶐ'=>'ⶐ','ⶑ'=>'ⶑ','ⶒ'=>'ⶒ','ⶓ'=>'ⶓ','ⶔ'=>'ⶔ','ⶕ'=>'ⶕ','ⶖ'=>'ⶖ','ⶠ'=>'ⶠ','ⶡ'=>'ⶡ','ⶢ'=>'ⶢ','ⶣ'=>'ⶣ','ⶤ'=>'ⶤ','ⶥ'=>'ⶥ','ⶦ'=>'ⶦ','ⶨ'=>'ⶨ','ⶩ'=>'ⶩ','ⶪ'=>'ⶪ','ⶫ'=>'ⶫ','ⶬ'=>'ⶬ','ⶭ'=>'ⶭ','ⶮ'=>'ⶮ','ⶰ'=>'ⶰ','ⶱ'=>'ⶱ','ⶲ'=>'ⶲ','ⶳ'=>'ⶳ','ⶴ'=>'ⶴ','ⶵ'=>'ⶵ','ⶶ'=>'ⶶ','ⶸ'=>'ⶸ','ⶹ'=>'ⶹ','ⶺ'=>'ⶺ','ⶻ'=>'ⶻ','ⶼ'=>'ⶼ','ⶽ'=>'ⶽ','ⶾ'=>'ⶾ','ⷀ'=>'ⷀ','ⷁ'=>'ⷁ','ⷂ'=>'ⷂ','ⷃ'=>'ⷃ','ⷄ'=>'ⷄ','ⷅ'=>'ⷅ','ⷆ'=>'ⷆ','ⷈ'=>'ⷈ','ⷉ'=>'ⷉ','ⷊ'=>'ⷊ','ⷋ'=>'ⷋ','ⷌ'=>'ⷌ','ⷍ'=>'ⷍ','ⷎ'=>'ⷎ','ⷐ'=>'ⷐ','ⷑ'=>'ⷑ','ⷒ'=>'ⷒ','ⷓ'=>'ⷓ','ⷔ'=>'ⷔ','ⷕ'=>'ⷕ','ⷖ'=>'ⷖ','ⷘ'=>'ⷘ','ⷙ'=>'ⷙ','ⷚ'=>'ⷚ','ⷛ'=>'ⷛ','ⷜ'=>'ⷜ','ⷝ'=>'ⷝ','ⷞ'=>'ⷞ'); diff --git a/includes/utf/data/search_indexer_58.php b/includes/utf/data/search_indexer_58.php new file mode 100644 index 0000000..ec86a4b --- /dev/null +++ b/includes/utf/data/search_indexer_58.php @@ -0,0 +1 @@ +'𝅥','𝅦'=>'𝅦','𝅧'=>'𝅧','𝅨'=>'𝅨','𝅩'=>'𝅩','𝅭'=>'𝅭','𝅮'=>'𝅮','𝅯'=>'𝅯','𝅰'=>'𝅰','𝅱'=>'𝅱','𝅲'=>'𝅲','𝅻'=>'𝅻','𝅼'=>'𝅼','𝅽'=>'𝅽','𝅾'=>'𝅾','𝅿'=>'𝅿','𝆀'=>'𝆀','𝆁'=>'𝆁','𝆂'=>'𝆂','𝆅'=>'𝆅','𝆆'=>'𝆆','𝆇'=>'𝆇','𝆈'=>'𝆈','𝆉'=>'𝆉','𝆊'=>'𝆊','𝆋'=>'𝆋','𝆪'=>'𝆪','𝆫'=>'𝆫','𝆬'=>'𝆬','𝆭'=>'𝆭','𝉂'=>'𝉂','𝉃'=>'𝉃','𝉄'=>'𝉄','𝍠'=>'1','𝍡'=>'2','𝍢'=>'3','𝍣'=>'4','𝍤'=>'5','𝍥'=>'6','𝍦'=>'7','𝍧'=>'8','𝍨'=>'9','𝍩'=>'10','𝍪'=>'20','𝍫'=>'30','𝍬'=>'40','𝍭'=>'50','𝍮'=>'60','𝍯'=>'70','𝍰'=>'80','𝍱'=>'90','𝐀'=>'𝐀','𝐁'=>'𝐁','𝐂'=>'𝐂','𝐃'=>'𝐃','𝐄'=>'𝐄','𝐅'=>'𝐅','𝐆'=>'𝐆','𝐇'=>'𝐇','𝐈'=>'𝐈','𝐉'=>'𝐉','𝐊'=>'𝐊','𝐋'=>'𝐋','𝐌'=>'𝐌','𝐍'=>'𝐍','𝐎'=>'𝐎','𝐏'=>'𝐏','𝐐'=>'𝐐','𝐑'=>'𝐑','𝐒'=>'𝐒','𝐓'=>'𝐓','𝐔'=>'𝐔','𝐕'=>'𝐕','𝐖'=>'𝐖','𝐗'=>'𝐗','𝐘'=>'𝐘','𝐙'=>'𝐙','𝐚'=>'𝐚','𝐛'=>'𝐛','𝐜'=>'𝐜','𝐝'=>'𝐝','𝐞'=>'𝐞','𝐟'=>'𝐟','𝐠'=>'𝐠','𝐡'=>'𝐡','𝐢'=>'𝐢','𝐣'=>'𝐣','𝐤'=>'𝐤','𝐥'=>'𝐥','𝐦'=>'𝐦','𝐧'=>'𝐧','𝐨'=>'𝐨','𝐩'=>'𝐩','𝐪'=>'𝐪','𝐫'=>'𝐫','𝐬'=>'𝐬','𝐭'=>'𝐭','𝐮'=>'𝐮','𝐯'=>'𝐯','𝐰'=>'𝐰','𝐱'=>'𝐱','𝐲'=>'𝐲','𝐳'=>'𝐳','𝐴'=>'𝐴','𝐵'=>'𝐵','𝐶'=>'𝐶','𝐷'=>'𝐷','𝐸'=>'𝐸','𝐹'=>'𝐹','𝐺'=>'𝐺','𝐻'=>'𝐻','𝐼'=>'𝐼','𝐽'=>'𝐽','𝐾'=>'𝐾','𝐿'=>'𝐿','𝑀'=>'𝑀','𝑁'=>'𝑁','𝑂'=>'𝑂','𝑃'=>'𝑃','𝑄'=>'𝑄','𝑅'=>'𝑅','𝑆'=>'𝑆','𝑇'=>'𝑇','𝑈'=>'𝑈','𝑉'=>'𝑉','𝑊'=>'𝑊','𝑋'=>'𝑋','𝑌'=>'𝑌','𝑍'=>'𝑍','𝑎'=>'𝑎','𝑏'=>'𝑏','𝑐'=>'𝑐','𝑑'=>'𝑑','𝑒'=>'𝑒','𝑓'=>'𝑓','𝑔'=>'𝑔','𝑖'=>'𝑖','𝑗'=>'𝑗','𝑘'=>'𝑘','𝑙'=>'𝑙','𝑚'=>'𝑚','𝑛'=>'𝑛','𝑜'=>'𝑜','𝑝'=>'𝑝','𝑞'=>'𝑞','𝑟'=>'𝑟','𝑠'=>'𝑠','𝑡'=>'𝑡','𝑢'=>'𝑢','𝑣'=>'𝑣','𝑤'=>'𝑤','𝑥'=>'𝑥','𝑦'=>'𝑦','𝑧'=>'𝑧','𝑨'=>'𝑨','𝑩'=>'𝑩','𝑪'=>'𝑪','𝑫'=>'𝑫','𝑬'=>'𝑬','𝑭'=>'𝑭','𝑮'=>'𝑮','𝑯'=>'𝑯','𝑰'=>'𝑰','𝑱'=>'𝑱','𝑲'=>'𝑲','𝑳'=>'𝑳','𝑴'=>'𝑴','𝑵'=>'𝑵','𝑶'=>'𝑶','𝑷'=>'𝑷','𝑸'=>'𝑸','𝑹'=>'𝑹','𝑺'=>'𝑺','𝑻'=>'𝑻','𝑼'=>'𝑼','𝑽'=>'𝑽','𝑾'=>'𝑾','𝑿'=>'𝑿','𝒀'=>'𝒀','𝒁'=>'𝒁','𝒂'=>'𝒂','𝒃'=>'𝒃','𝒄'=>'𝒄','𝒅'=>'𝒅','𝒆'=>'𝒆','𝒇'=>'𝒇','𝒈'=>'𝒈','𝒉'=>'𝒉','𝒊'=>'𝒊','𝒋'=>'𝒋','𝒌'=>'𝒌','𝒍'=>'𝒍','𝒎'=>'𝒎','𝒏'=>'𝒏','𝒐'=>'𝒐','𝒑'=>'𝒑','𝒒'=>'𝒒','𝒓'=>'𝒓','𝒔'=>'𝒔','𝒕'=>'𝒕','𝒖'=>'𝒖','𝒗'=>'𝒗','𝒘'=>'𝒘','𝒙'=>'𝒙','𝒚'=>'𝒚','𝒛'=>'𝒛','𝒜'=>'𝒜','𝒞'=>'𝒞','𝒟'=>'𝒟','𝒢'=>'𝒢','𝒥'=>'𝒥','𝒦'=>'𝒦','𝒩'=>'𝒩','𝒪'=>'𝒪','𝒫'=>'𝒫','𝒬'=>'𝒬','𝒮'=>'𝒮','𝒯'=>'𝒯','𝒰'=>'𝒰','𝒱'=>'𝒱','𝒲'=>'𝒲','𝒳'=>'𝒳','𝒴'=>'𝒴','𝒵'=>'𝒵','𝒶'=>'𝒶','𝒷'=>'𝒷','𝒸'=>'𝒸','𝒹'=>'𝒹','𝒻'=>'𝒻','𝒽'=>'𝒽','𝒾'=>'𝒾','𝒿'=>'𝒿','𝓀'=>'𝓀','𝓁'=>'𝓁','𝓂'=>'𝓂','𝓃'=>'𝓃','𝓅'=>'𝓅','𝓆'=>'𝓆','𝓇'=>'𝓇','𝓈'=>'𝓈','𝓉'=>'𝓉','𝓊'=>'𝓊','𝓋'=>'𝓋','𝓌'=>'𝓌','𝓍'=>'𝓍','𝓎'=>'𝓎','𝓏'=>'𝓏','𝓐'=>'𝓐','𝓑'=>'𝓑','𝓒'=>'𝓒','𝓓'=>'𝓓','𝓔'=>'𝓔','𝓕'=>'𝓕','𝓖'=>'𝓖','𝓗'=>'𝓗','𝓘'=>'𝓘','𝓙'=>'𝓙','𝓚'=>'𝓚','𝓛'=>'𝓛','𝓜'=>'𝓜','𝓝'=>'𝓝','𝓞'=>'𝓞','𝓟'=>'𝓟','𝓠'=>'𝓠','𝓡'=>'𝓡','𝓢'=>'𝓢','𝓣'=>'𝓣','𝓤'=>'𝓤','𝓥'=>'𝓥','𝓦'=>'𝓦','𝓧'=>'𝓧','𝓨'=>'𝓨','𝓩'=>'𝓩','𝓪'=>'𝓪','𝓫'=>'𝓫','𝓬'=>'𝓬','𝓭'=>'𝓭','𝓮'=>'𝓮','𝓯'=>'𝓯','𝓰'=>'𝓰','𝓱'=>'𝓱','𝓲'=>'𝓲','𝓳'=>'𝓳','𝓴'=>'𝓴','𝓵'=>'𝓵','𝓶'=>'𝓶','𝓷'=>'𝓷','𝓸'=>'𝓸','𝓹'=>'𝓹','𝓺'=>'𝓺','𝓻'=>'𝓻','𝓼'=>'𝓼','𝓽'=>'𝓽','𝓾'=>'𝓾','𝓿'=>'𝓿','𝔀'=>'𝔀','𝔁'=>'𝔁','𝔂'=>'𝔂','𝔃'=>'𝔃','𝔄'=>'𝔄','𝔅'=>'𝔅','𝔇'=>'𝔇','𝔈'=>'𝔈','𝔉'=>'𝔉','𝔊'=>'𝔊','𝔍'=>'𝔍','𝔎'=>'𝔎','𝔏'=>'𝔏','𝔐'=>'𝔐','𝔑'=>'𝔑','𝔒'=>'𝔒','𝔓'=>'𝔓','𝔔'=>'𝔔','𝔖'=>'𝔖','𝔗'=>'𝔗','𝔘'=>'𝔘','𝔙'=>'𝔙','𝔚'=>'𝔚','𝔛'=>'𝔛','𝔜'=>'𝔜','𝔞'=>'𝔞','𝔟'=>'𝔟','𝔠'=>'𝔠','𝔡'=>'𝔡','𝔢'=>'𝔢','𝔣'=>'𝔣','𝔤'=>'𝔤','𝔥'=>'𝔥','𝔦'=>'𝔦','𝔧'=>'𝔧','𝔨'=>'𝔨','𝔩'=>'𝔩','𝔪'=>'𝔪','𝔫'=>'𝔫','𝔬'=>'𝔬','𝔭'=>'𝔭','𝔮'=>'𝔮','𝔯'=>'𝔯','𝔰'=>'𝔰','𝔱'=>'𝔱','𝔲'=>'𝔲','𝔳'=>'𝔳','𝔴'=>'𝔴','𝔵'=>'𝔵','𝔶'=>'𝔶','𝔷'=>'𝔷','𝔸'=>'𝔸','𝔹'=>'𝔹','𝔻'=>'𝔻','𝔼'=>'𝔼','𝔽'=>'𝔽','𝔾'=>'𝔾','𝕀'=>'𝕀','𝕁'=>'𝕁','𝕂'=>'𝕂','𝕃'=>'𝕃','𝕄'=>'𝕄','𝕆'=>'𝕆','𝕊'=>'𝕊','𝕋'=>'𝕋','𝕌'=>'𝕌','𝕍'=>'𝕍','𝕎'=>'𝕎','𝕏'=>'𝕏','𝕐'=>'𝕐','𝕒'=>'𝕒','𝕓'=>'𝕓','𝕔'=>'𝕔','𝕕'=>'𝕕','𝕖'=>'𝕖','𝕗'=>'𝕗','𝕘'=>'𝕘','𝕙'=>'𝕙','𝕚'=>'𝕚','𝕛'=>'𝕛','𝕜'=>'𝕜','𝕝'=>'𝕝','𝕞'=>'𝕞','𝕟'=>'𝕟','𝕠'=>'𝕠','𝕡'=>'𝕡','𝕢'=>'𝕢','𝕣'=>'𝕣','𝕤'=>'𝕤','𝕥'=>'𝕥','𝕦'=>'𝕦','𝕧'=>'𝕧','𝕨'=>'𝕨','𝕩'=>'𝕩','𝕪'=>'𝕪','𝕫'=>'𝕫','𝕬'=>'𝕬','𝕭'=>'𝕭','𝕮'=>'𝕮','𝕯'=>'𝕯','𝕰'=>'𝕰','𝕱'=>'𝕱','𝕲'=>'𝕲','𝕳'=>'𝕳','𝕴'=>'𝕴','𝕵'=>'𝕵','𝕶'=>'𝕶','𝕷'=>'𝕷','𝕸'=>'𝕸','𝕹'=>'𝕹','𝕺'=>'𝕺','𝕻'=>'𝕻','𝕼'=>'𝕼','𝕽'=>'𝕽','𝕾'=>'𝕾','𝕿'=>'𝕿','𝖀'=>'𝖀','𝖁'=>'𝖁','𝖂'=>'𝖂','𝖃'=>'𝖃','𝖄'=>'𝖄','𝖅'=>'𝖅','𝖆'=>'𝖆','𝖇'=>'𝖇','𝖈'=>'𝖈','𝖉'=>'𝖉','𝖊'=>'𝖊','𝖋'=>'𝖋','𝖌'=>'𝖌','𝖍'=>'𝖍','𝖎'=>'𝖎','𝖏'=>'𝖏','𝖐'=>'𝖐','𝖑'=>'𝖑','𝖒'=>'𝖒','𝖓'=>'𝖓','𝖔'=>'𝖔','𝖕'=>'𝖕','𝖖'=>'𝖖','𝖗'=>'𝖗','𝖘'=>'𝖘','𝖙'=>'𝖙','𝖚'=>'𝖚','𝖛'=>'𝖛','𝖜'=>'𝖜','𝖝'=>'𝖝','𝖞'=>'𝖞','𝖟'=>'𝖟','𝖠'=>'𝖠','𝖡'=>'𝖡','𝖢'=>'𝖢','𝖣'=>'𝖣','𝖤'=>'𝖤','𝖥'=>'𝖥','𝖦'=>'𝖦','𝖧'=>'𝖧','𝖨'=>'𝖨','𝖩'=>'𝖩','𝖪'=>'𝖪','𝖫'=>'𝖫','𝖬'=>'𝖬','𝖭'=>'𝖭','𝖮'=>'𝖮','𝖯'=>'𝖯','𝖰'=>'𝖰','𝖱'=>'𝖱','𝖲'=>'𝖲','𝖳'=>'𝖳','𝖴'=>'𝖴','𝖵'=>'𝖵','𝖶'=>'𝖶','𝖷'=>'𝖷','𝖸'=>'𝖸','𝖹'=>'𝖹','𝖺'=>'𝖺','𝖻'=>'𝖻','𝖼'=>'𝖼','𝖽'=>'𝖽','𝖾'=>'𝖾','𝖿'=>'𝖿','𝗀'=>'𝗀','𝗁'=>'𝗁','𝗂'=>'𝗂','𝗃'=>'𝗃','𝗄'=>'𝗄','𝗅'=>'𝗅','𝗆'=>'𝗆','𝗇'=>'𝗇','𝗈'=>'𝗈','𝗉'=>'𝗉','𝗊'=>'𝗊','𝗋'=>'𝗋','𝗌'=>'𝗌','𝗍'=>'𝗍','𝗎'=>'𝗎','𝗏'=>'𝗏','𝗐'=>'𝗐','𝗑'=>'𝗑','𝗒'=>'𝗒','𝗓'=>'𝗓','𝗔'=>'𝗔','𝗕'=>'𝗕','𝗖'=>'𝗖','𝗗'=>'𝗗','𝗘'=>'𝗘','𝗙'=>'𝗙','𝗚'=>'𝗚','𝗛'=>'𝗛','𝗜'=>'𝗜','𝗝'=>'𝗝','𝗞'=>'𝗞','𝗟'=>'𝗟','𝗠'=>'𝗠','𝗡'=>'𝗡','𝗢'=>'𝗢','𝗣'=>'𝗣','𝗤'=>'𝗤','𝗥'=>'𝗥','𝗦'=>'𝗦','𝗧'=>'𝗧','𝗨'=>'𝗨','𝗩'=>'𝗩','𝗪'=>'𝗪','𝗫'=>'𝗫','𝗬'=>'𝗬','𝗭'=>'𝗭','𝗮'=>'𝗮','𝗯'=>'𝗯','𝗰'=>'𝗰','𝗱'=>'𝗱','𝗲'=>'𝗲','𝗳'=>'𝗳','𝗴'=>'𝗴','𝗵'=>'𝗵','𝗶'=>'𝗶','𝗷'=>'𝗷','𝗸'=>'𝗸','𝗹'=>'𝗹','𝗺'=>'𝗺','𝗻'=>'𝗻','𝗼'=>'𝗼','𝗽'=>'𝗽','𝗾'=>'𝗾','𝗿'=>'𝗿','𝘀'=>'𝘀','𝘁'=>'𝘁','𝘂'=>'𝘂','𝘃'=>'𝘃','𝘄'=>'𝘄','𝘅'=>'𝘅','𝘆'=>'𝘆','𝘇'=>'𝘇','𝘈'=>'𝘈','𝘉'=>'𝘉','𝘊'=>'𝘊','𝘋'=>'𝘋','𝘌'=>'𝘌','𝘍'=>'𝘍','𝘎'=>'𝘎','𝘏'=>'𝘏','𝘐'=>'𝘐','𝘑'=>'𝘑','𝘒'=>'𝘒','𝘓'=>'𝘓','𝘔'=>'𝘔','𝘕'=>'𝘕','𝘖'=>'𝘖','𝘗'=>'𝘗','𝘘'=>'𝘘','𝘙'=>'𝘙','𝘚'=>'𝘚','𝘛'=>'𝘛','𝘜'=>'𝘜','𝘝'=>'𝘝','𝘞'=>'𝘞','𝘟'=>'𝘟','𝘠'=>'𝘠','𝘡'=>'𝘡','𝘢'=>'𝘢','𝘣'=>'𝘣','𝘤'=>'𝘤','𝘥'=>'𝘥','𝘦'=>'𝘦','𝘧'=>'𝘧','𝘨'=>'𝘨','𝘩'=>'𝘩','𝘪'=>'𝘪','𝘫'=>'𝘫','𝘬'=>'𝘬','𝘭'=>'𝘭','𝘮'=>'𝘮','𝘯'=>'𝘯','𝘰'=>'𝘰','𝘱'=>'𝘱','𝘲'=>'𝘲','𝘳'=>'𝘳','𝘴'=>'𝘴','𝘵'=>'𝘵','𝘶'=>'𝘶','𝘷'=>'𝘷','𝘸'=>'𝘸','𝘹'=>'𝘹','𝘺'=>'𝘺','𝘻'=>'𝘻','𝘼'=>'𝘼','𝘽'=>'𝘽','𝘾'=>'𝘾','𝘿'=>'𝘿','𝙀'=>'𝙀','𝙁'=>'𝙁','𝙂'=>'𝙂','𝙃'=>'𝙃','𝙄'=>'𝙄','𝙅'=>'𝙅','𝙆'=>'𝙆','𝙇'=>'𝙇','𝙈'=>'𝙈','𝙉'=>'𝙉','𝙊'=>'𝙊','𝙋'=>'𝙋','𝙌'=>'𝙌','𝙍'=>'𝙍','𝙎'=>'𝙎','𝙏'=>'𝙏','𝙐'=>'𝙐','𝙑'=>'𝙑','𝙒'=>'𝙒','𝙓'=>'𝙓','𝙔'=>'𝙔','𝙕'=>'𝙕','𝙖'=>'𝙖','𝙗'=>'𝙗','𝙘'=>'𝙘','𝙙'=>'𝙙','𝙚'=>'𝙚','𝙛'=>'𝙛','𝙜'=>'𝙜','𝙝'=>'𝙝','𝙞'=>'𝙞','𝙟'=>'𝙟','𝙠'=>'𝙠','𝙡'=>'𝙡','𝙢'=>'𝙢','𝙣'=>'𝙣','𝙤'=>'𝙤','𝙥'=>'𝙥','𝙦'=>'𝙦','𝙧'=>'𝙧','𝙨'=>'𝙨','𝙩'=>'𝙩','𝙪'=>'𝙪','𝙫'=>'𝙫','𝙬'=>'𝙬','𝙭'=>'𝙭','𝙮'=>'𝙮','𝙯'=>'𝙯','𝙰'=>'𝙰','𝙱'=>'𝙱','𝙲'=>'𝙲','𝙳'=>'𝙳','𝙴'=>'𝙴','𝙵'=>'𝙵','𝙶'=>'𝙶','𝙷'=>'𝙷','𝙸'=>'𝙸','𝙹'=>'𝙹','𝙺'=>'𝙺','𝙻'=>'𝙻','𝙼'=>'𝙼','𝙽'=>'𝙽','𝙾'=>'𝙾','𝙿'=>'𝙿','𝚀'=>'𝚀','𝚁'=>'𝚁','𝚂'=>'𝚂','𝚃'=>'𝚃','𝚄'=>'𝚄','𝚅'=>'𝚅','𝚆'=>'𝚆','𝚇'=>'𝚇','𝚈'=>'𝚈','𝚉'=>'𝚉','𝚊'=>'𝚊','𝚋'=>'𝚋','𝚌'=>'𝚌','𝚍'=>'𝚍','𝚎'=>'𝚎','𝚏'=>'𝚏','𝚐'=>'𝚐','𝚑'=>'𝚑','𝚒'=>'𝚒','𝚓'=>'𝚓','𝚔'=>'𝚔','𝚕'=>'𝚕','𝚖'=>'𝚖','𝚗'=>'𝚗','𝚘'=>'𝚘','𝚙'=>'𝚙','𝚚'=>'𝚚','𝚛'=>'𝚛','𝚜'=>'𝚜','𝚝'=>'𝚝','𝚞'=>'𝚞','𝚟'=>'𝚟','𝚠'=>'𝚠','𝚡'=>'𝚡','𝚢'=>'𝚢','𝚣'=>'𝚣','𝚤'=>'𝚤','𝚥'=>'𝚥','𝚨'=>'𝚨','𝚩'=>'𝚩','𝚪'=>'𝚪','𝚫'=>'𝚫','𝚬'=>'𝚬','𝚭'=>'𝚭','𝚮'=>'𝚮','𝚯'=>'𝚯','𝚰'=>'𝚰','𝚱'=>'𝚱','𝚲'=>'𝚲','𝚳'=>'𝚳','𝚴'=>'𝚴','𝚵'=>'𝚵','𝚶'=>'𝚶','𝚷'=>'𝚷','𝚸'=>'𝚸','𝚹'=>'𝚹','𝚺'=>'𝚺','𝚻'=>'𝚻','𝚼'=>'𝚼','𝚽'=>'𝚽','𝚾'=>'𝚾','𝚿'=>'𝚿','𝛀'=>'𝛀','𝛂'=>'𝛂','𝛃'=>'𝛃','𝛄'=>'𝛄','𝛅'=>'𝛅','𝛆'=>'𝛆','𝛇'=>'𝛇','𝛈'=>'𝛈','𝛉'=>'𝛉','𝛊'=>'𝛊','𝛋'=>'𝛋','𝛌'=>'𝛌','𝛍'=>'𝛍','𝛎'=>'𝛎','𝛏'=>'𝛏','𝛐'=>'𝛐','𝛑'=>'𝛑','𝛒'=>'𝛒','𝛓'=>'𝛓','𝛔'=>'𝛔','𝛕'=>'𝛕','𝛖'=>'𝛖','𝛗'=>'𝛗','𝛘'=>'𝛘','𝛙'=>'𝛙','𝛚'=>'𝛚','𝛜'=>'𝛜','𝛝'=>'𝛝','𝛞'=>'𝛞','𝛟'=>'𝛟','𝛠'=>'𝛠','𝛡'=>'𝛡','𝛢'=>'𝛢','𝛣'=>'𝛣','𝛤'=>'𝛤','𝛥'=>'𝛥','𝛦'=>'𝛦','𝛧'=>'𝛧','𝛨'=>'𝛨','𝛩'=>'𝛩','𝛪'=>'𝛪','𝛫'=>'𝛫','𝛬'=>'𝛬','𝛭'=>'𝛭','𝛮'=>'𝛮','𝛯'=>'𝛯','𝛰'=>'𝛰','𝛱'=>'𝛱','𝛲'=>'𝛲','𝛳'=>'𝛳','𝛴'=>'𝛴','𝛵'=>'𝛵','𝛶'=>'𝛶','𝛷'=>'𝛷','𝛸'=>'𝛸','𝛹'=>'𝛹','𝛺'=>'𝛺','𝛼'=>'𝛼','𝛽'=>'𝛽','𝛾'=>'𝛾','𝛿'=>'𝛿','𝜀'=>'𝜀','𝜁'=>'𝜁','𝜂'=>'𝜂','𝜃'=>'𝜃','𝜄'=>'𝜄','𝜅'=>'𝜅','𝜆'=>'𝜆','𝜇'=>'𝜇','𝜈'=>'𝜈','𝜉'=>'𝜉','𝜊'=>'𝜊','𝜋'=>'𝜋','𝜌'=>'𝜌','𝜍'=>'𝜍','𝜎'=>'𝜎','𝜏'=>'𝜏','𝜐'=>'𝜐','𝜑'=>'𝜑','𝜒'=>'𝜒','𝜓'=>'𝜓','𝜔'=>'𝜔','𝜖'=>'𝜖','𝜗'=>'𝜗','𝜘'=>'𝜘','𝜙'=>'𝜙','𝜚'=>'𝜚','𝜛'=>'𝜛','𝜜'=>'𝜜','𝜝'=>'𝜝','𝜞'=>'𝜞','𝜟'=>'𝜟','𝜠'=>'𝜠','𝜡'=>'𝜡','𝜢'=>'𝜢','𝜣'=>'𝜣','𝜤'=>'𝜤','𝜥'=>'𝜥','𝜦'=>'𝜦','𝜧'=>'𝜧','𝜨'=>'𝜨','𝜩'=>'𝜩','𝜪'=>'𝜪','𝜫'=>'𝜫','𝜬'=>'𝜬','𝜭'=>'𝜭','𝜮'=>'𝜮','𝜯'=>'𝜯','𝜰'=>'𝜰','𝜱'=>'𝜱','𝜲'=>'𝜲','𝜳'=>'𝜳','𝜴'=>'𝜴','𝜶'=>'𝜶','𝜷'=>'𝜷','𝜸'=>'𝜸','𝜹'=>'𝜹','𝜺'=>'𝜺','𝜻'=>'𝜻','𝜼'=>'𝜼','𝜽'=>'𝜽','𝜾'=>'𝜾','𝜿'=>'𝜿','𝝀'=>'𝝀','𝝁'=>'𝝁','𝝂'=>'𝝂','𝝃'=>'𝝃','𝝄'=>'𝝄','𝝅'=>'𝝅','𝝆'=>'𝝆','𝝇'=>'𝝇','𝝈'=>'𝝈','𝝉'=>'𝝉','𝝊'=>'𝝊','𝝋'=>'𝝋','𝝌'=>'𝝌','𝝍'=>'𝝍','𝝎'=>'𝝎','𝝐'=>'𝝐','𝝑'=>'𝝑','𝝒'=>'𝝒','𝝓'=>'𝝓','𝝔'=>'𝝔','𝝕'=>'𝝕','𝝖'=>'𝝖','𝝗'=>'𝝗','𝝘'=>'𝝘','𝝙'=>'𝝙','𝝚'=>'𝝚','𝝛'=>'𝝛','𝝜'=>'𝝜','𝝝'=>'𝝝','𝝞'=>'𝝞','𝝟'=>'𝝟','𝝠'=>'𝝠','𝝡'=>'𝝡','𝝢'=>'𝝢','𝝣'=>'𝝣','𝝤'=>'𝝤','𝝥'=>'𝝥','𝝦'=>'𝝦','𝝧'=>'𝝧','𝝨'=>'𝝨','𝝩'=>'𝝩','𝝪'=>'𝝪','𝝫'=>'𝝫','𝝬'=>'𝝬','𝝭'=>'𝝭','𝝮'=>'𝝮','𝝰'=>'𝝰','𝝱'=>'𝝱','𝝲'=>'𝝲','𝝳'=>'𝝳','𝝴'=>'𝝴','𝝵'=>'𝝵','𝝶'=>'𝝶','𝝷'=>'𝝷','𝝸'=>'𝝸','𝝹'=>'𝝹','𝝺'=>'𝝺','𝝻'=>'𝝻','𝝼'=>'𝝼','𝝽'=>'𝝽','𝝾'=>'𝝾','𝝿'=>'𝝿','𝞀'=>'𝞀','𝞁'=>'𝞁','𝞂'=>'𝞂','𝞃'=>'𝞃','𝞄'=>'𝞄','𝞅'=>'𝞅','𝞆'=>'𝞆','𝞇'=>'𝞇','𝞈'=>'𝞈','𝞊'=>'𝞊','𝞋'=>'𝞋','𝞌'=>'𝞌','𝞍'=>'𝞍','𝞎'=>'𝞎','𝞏'=>'𝞏','𝞐'=>'𝞐','𝞑'=>'𝞑','𝞒'=>'𝞒','𝞓'=>'𝞓','𝞔'=>'𝞔','𝞕'=>'𝞕','𝞖'=>'𝞖','𝞗'=>'𝞗','𝞘'=>'𝞘','𝞙'=>'𝞙','𝞚'=>'𝞚','𝞛'=>'𝞛','𝞜'=>'𝞜','𝞝'=>'𝞝','𝞞'=>'𝞞','𝞟'=>'𝞟','𝞠'=>'𝞠','𝞡'=>'𝞡','𝞢'=>'𝞢','𝞣'=>'𝞣','𝞤'=>'𝞤','𝞥'=>'𝞥','𝞦'=>'𝞦','𝞧'=>'𝞧','𝞨'=>'𝞨','𝞪'=>'𝞪','𝞫'=>'𝞫','𝞬'=>'𝞬','𝞭'=>'𝞭','𝞮'=>'𝞮','𝞯'=>'𝞯','𝞰'=>'𝞰','𝞱'=>'𝞱','𝞲'=>'𝞲','𝞳'=>'𝞳','𝞴'=>'𝞴','𝞵'=>'𝞵','𝞶'=>'𝞶','𝞷'=>'𝞷','𝞸'=>'𝞸','𝞹'=>'𝞹','𝞺'=>'𝞺','𝞻'=>'𝞻','𝞼'=>'𝞼','𝞽'=>'𝞽','𝞾'=>'𝞾','𝞿'=>'𝞿','𝟀'=>'𝟀','𝟁'=>'𝟁','𝟂'=>'𝟂','𝟄'=>'𝟄','𝟅'=>'𝟅','𝟆'=>'𝟆','𝟇'=>'𝟇','𝟈'=>'𝟈','𝟉'=>'𝟉','𝟊'=>'𝟊','𝟋'=>'𝟋','𝟎'=>'0','𝟏'=>'1','𝟐'=>'2','𝟑'=>'3','𝟒'=>'4','𝟓'=>'5','𝟔'=>'6','𝟕'=>'7','𝟖'=>'8','𝟗'=>'9','𝟘'=>'0','𝟙'=>'1','𝟚'=>'2','𝟛'=>'3','𝟜'=>'4','𝟝'=>'5','𝟞'=>'6','𝟟'=>'7','𝟠'=>'8','𝟡'=>'9','𝟢'=>'0','𝟣'=>'1','𝟤'=>'2','𝟥'=>'3','𝟦'=>'4','𝟧'=>'5','𝟨'=>'6','𝟩'=>'7','𝟪'=>'8','𝟫'=>'9','𝟬'=>'0','𝟭'=>'1','𝟮'=>'2','𝟯'=>'3','𝟰'=>'4','𝟱'=>'5','𝟲'=>'6','𝟳'=>'7','𝟴'=>'8','𝟵'=>'9','𝟶'=>'0','𝟷'=>'1','𝟸'=>'2','𝟹'=>'3','𝟺'=>'4','𝟻'=>'5','𝟼'=>'6','𝟽'=>'7','𝟾'=>'8','𝟿'=>'9'); diff --git a/includes/utf/data/search_indexer_6.php b/includes/utf/data/search_indexer_6.php new file mode 100644 index 0000000..1ccce03 --- /dev/null +++ b/includes/utf/data/search_indexer_6.php @@ -0,0 +1 @@ +'々','〆'=>'〆','〇'=>'0','〡'=>'1','〢'=>'2','〣'=>'3','〤'=>'4','〥'=>'5','〦'=>'6','〧'=>'7','〨'=>'8','〩'=>'9','〪'=>'〪','〫'=>'〫','〬'=>'〬','〭'=>'〭','〮'=>'〮','〯'=>'〯','〱'=>'〱','〲'=>'〲','〳'=>'〳','〴'=>'〴','〵'=>'〵','〸'=>'10','〹'=>'20','〺'=>'30','〻'=>'〻','〼'=>'〼','ぁ'=>'ぁ','あ'=>'あ','ぃ'=>'ぃ','い'=>'い','ぅ'=>'ぅ','う'=>'う','ぇ'=>'ぇ','え'=>'え','ぉ'=>'ぉ','お'=>'お','か'=>'か','が'=>'が','き'=>'き','ぎ'=>'ぎ','く'=>'く','ぐ'=>'ぐ','け'=>'け','げ'=>'げ','こ'=>'こ','ご'=>'ご','さ'=>'さ','ざ'=>'ざ','し'=>'し','じ'=>'じ','す'=>'す','ず'=>'ず','せ'=>'せ','ぜ'=>'ぜ','そ'=>'そ','ぞ'=>'ぞ','た'=>'た','だ'=>'だ','ち'=>'ち','ぢ'=>'ぢ','っ'=>'っ','つ'=>'つ','づ'=>'づ','て'=>'て','で'=>'で','と'=>'と','ど'=>'ど','な'=>'な','に'=>'に','ぬ'=>'ぬ','ね'=>'ね','の'=>'の','は'=>'は','ば'=>'ば','ぱ'=>'ぱ','ひ'=>'ひ','び'=>'び','ぴ'=>'ぴ','ふ'=>'ふ','ぶ'=>'ぶ','ぷ'=>'ぷ','へ'=>'へ','べ'=>'べ','ぺ'=>'ぺ','ほ'=>'ほ','ぼ'=>'ぼ','ぽ'=>'ぽ','ま'=>'ま','み'=>'み','む'=>'む','め'=>'め','も'=>'も','ゃ'=>'ゃ','や'=>'や','ゅ'=>'ゅ','ゆ'=>'ゆ','ょ'=>'ょ','よ'=>'よ','ら'=>'ら','り'=>'り','る'=>'る','れ'=>'れ','ろ'=>'ろ','ゎ'=>'ゎ','わ'=>'わ','ゐ'=>'ゐ','ゑ'=>'ゑ','を'=>'を','ん'=>'ん','ゔ'=>'ゔ','ゕ'=>'ゕ','ゖ'=>'ゖ','゙'=>'゙','゚'=>'゚','ゝ'=>'ゝ','ゞ'=>'ゞ','ゟ'=>'ゟ','ァ'=>'ァ','ア'=>'ア','ィ'=>'ィ','イ'=>'イ','ゥ'=>'ゥ','ウ'=>'ウ','ェ'=>'ェ','エ'=>'エ','ォ'=>'ォ','オ'=>'オ','カ'=>'カ','ガ'=>'ガ','キ'=>'キ','ギ'=>'ギ','ク'=>'ク','グ'=>'グ','ケ'=>'ケ','ゲ'=>'ゲ','コ'=>'コ','ゴ'=>'ゴ','サ'=>'サ','ザ'=>'ザ','シ'=>'シ','ジ'=>'ジ','ス'=>'ス','ズ'=>'ズ','セ'=>'セ','ゼ'=>'ゼ','ソ'=>'ソ','ゾ'=>'ゾ','タ'=>'タ','ダ'=>'ダ','チ'=>'チ','ヂ'=>'ヂ','ッ'=>'ッ','ツ'=>'ツ','ヅ'=>'ヅ','テ'=>'テ','デ'=>'デ','ト'=>'ト','ド'=>'ド','ナ'=>'ナ','ニ'=>'ニ','ヌ'=>'ヌ','ネ'=>'ネ','ノ'=>'ノ','ハ'=>'ハ','バ'=>'バ','パ'=>'パ','ヒ'=>'ヒ','ビ'=>'ビ','ピ'=>'ピ','フ'=>'フ','ブ'=>'ブ','プ'=>'プ','ヘ'=>'ヘ','ベ'=>'ベ','ペ'=>'ペ','ホ'=>'ホ','ボ'=>'ボ','ポ'=>'ポ','マ'=>'マ','ミ'=>'ミ','ム'=>'ム','メ'=>'メ','モ'=>'モ','ャ'=>'ャ','ヤ'=>'ヤ','ュ'=>'ュ','ユ'=>'ユ','ョ'=>'ョ','ヨ'=>'ヨ','ラ'=>'ラ','リ'=>'リ','ル'=>'ル','レ'=>'レ','ロ'=>'ロ','ヮ'=>'ヮ','ワ'=>'ワ','ヰ'=>'ヰ','ヱ'=>'ヱ','ヲ'=>'ヲ','ン'=>'ン','ヴ'=>'ヴ','ヵ'=>'ヵ','ヶ'=>'ヶ','ヷ'=>'ヷ','ヸ'=>'ヸ','ヹ'=>'ヹ','ヺ'=>'ヺ','ー'=>'ー','ヽ'=>'ヽ','ヾ'=>'ヾ','ヿ'=>'ヿ','ㄅ'=>'ㄅ','ㄆ'=>'ㄆ','ㄇ'=>'ㄇ','ㄈ'=>'ㄈ','ㄉ'=>'ㄉ','ㄊ'=>'ㄊ','ㄋ'=>'ㄋ','ㄌ'=>'ㄌ','ㄍ'=>'ㄍ','ㄎ'=>'ㄎ','ㄏ'=>'ㄏ','ㄐ'=>'ㄐ','ㄑ'=>'ㄑ','ㄒ'=>'ㄒ','ㄓ'=>'ㄓ','ㄔ'=>'ㄔ','ㄕ'=>'ㄕ','ㄖ'=>'ㄖ','ㄗ'=>'ㄗ','ㄘ'=>'ㄘ','ㄙ'=>'ㄙ','ㄚ'=>'ㄚ','ㄛ'=>'ㄛ','ㄜ'=>'ㄜ','ㄝ'=>'ㄝ','ㄞ'=>'ㄞ','ㄟ'=>'ㄟ','ㄠ'=>'ㄠ','ㄡ'=>'ㄡ','ㄢ'=>'ㄢ','ㄣ'=>'ㄣ','ㄤ'=>'ㄤ','ㄥ'=>'ㄥ','ㄦ'=>'ㄦ','ㄧ'=>'ㄧ','ㄨ'=>'ㄨ','ㄩ'=>'ㄩ','ㄪ'=>'ㄪ','ㄫ'=>'ㄫ','ㄬ'=>'ㄬ','ㄱ'=>'ㄱ','ㄲ'=>'ㄲ','ㄳ'=>'ㄳ','ㄴ'=>'ㄴ','ㄵ'=>'ㄵ','ㄶ'=>'ㄶ','ㄷ'=>'ㄷ','ㄸ'=>'ㄸ','ㄹ'=>'ㄹ','ㄺ'=>'ㄺ','ㄻ'=>'ㄻ','ㄼ'=>'ㄼ','ㄽ'=>'ㄽ','ㄾ'=>'ㄾ','ㄿ'=>'ㄿ','ㅀ'=>'ㅀ','ㅁ'=>'ㅁ','ㅂ'=>'ㅂ','ㅃ'=>'ㅃ','ㅄ'=>'ㅄ','ㅅ'=>'ㅅ','ㅆ'=>'ㅆ','ㅇ'=>'ㅇ','ㅈ'=>'ㅈ','ㅉ'=>'ㅉ','ㅊ'=>'ㅊ','ㅋ'=>'ㅋ','ㅌ'=>'ㅌ','ㅍ'=>'ㅍ','ㅎ'=>'ㅎ','ㅏ'=>'ㅏ','ㅐ'=>'ㅐ','ㅑ'=>'ㅑ','ㅒ'=>'ㅒ','ㅓ'=>'ㅓ','ㅔ'=>'ㅔ','ㅕ'=>'ㅕ','ㅖ'=>'ㅖ','ㅗ'=>'ㅗ','ㅘ'=>'ㅘ','ㅙ'=>'ㅙ','ㅚ'=>'ㅚ','ㅛ'=>'ㅛ','ㅜ'=>'ㅜ','ㅝ'=>'ㅝ','ㅞ'=>'ㅞ','ㅟ'=>'ㅟ','ㅠ'=>'ㅠ','ㅡ'=>'ㅡ','ㅢ'=>'ㅢ','ㅣ'=>'ㅣ','ㅤ'=>'ㅤ','ㅥ'=>'ㅥ','ㅦ'=>'ㅦ','ㅧ'=>'ㅧ','ㅨ'=>'ㅨ','ㅩ'=>'ㅩ','ㅪ'=>'ㅪ','ㅫ'=>'ㅫ','ㅬ'=>'ㅬ','ㅭ'=>'ㅭ','ㅮ'=>'ㅮ','ㅯ'=>'ㅯ','ㅰ'=>'ㅰ','ㅱ'=>'ㅱ','ㅲ'=>'ㅲ','ㅳ'=>'ㅳ','ㅴ'=>'ㅴ','ㅵ'=>'ㅵ','ㅶ'=>'ㅶ','ㅷ'=>'ㅷ','ㅸ'=>'ㅸ','ㅹ'=>'ㅹ','ㅺ'=>'ㅺ','ㅻ'=>'ㅻ','ㅼ'=>'ㅼ','ㅽ'=>'ㅽ','ㅾ'=>'ㅾ','ㅿ'=>'ㅿ','ㆀ'=>'ㆀ','ㆁ'=>'ㆁ','ㆂ'=>'ㆂ','ㆃ'=>'ㆃ','ㆄ'=>'ㆄ','ㆅ'=>'ㆅ','ㆆ'=>'ㆆ','ㆇ'=>'ㆇ','ㆈ'=>'ㆈ','ㆉ'=>'ㆉ','ㆊ'=>'ㆊ','ㆋ'=>'ㆋ','ㆌ'=>'ㆌ','ㆍ'=>'ㆍ','ㆎ'=>'ㆎ','㆒'=>'1','㆓'=>'2','㆔'=>'3','㆕'=>'4','ㆠ'=>'ㆠ','ㆡ'=>'ㆡ','ㆢ'=>'ㆢ','ㆣ'=>'ㆣ','ㆤ'=>'ㆤ','ㆥ'=>'ㆥ','ㆦ'=>'ㆦ','ㆧ'=>'ㆧ','ㆨ'=>'ㆨ','ㆩ'=>'ㆩ','ㆪ'=>'ㆪ','ㆫ'=>'ㆫ','ㆬ'=>'ㆬ','ㆭ'=>'ㆭ','ㆮ'=>'ㆮ','ㆯ'=>'ㆯ','ㆰ'=>'ㆰ','ㆱ'=>'ㆱ','ㆲ'=>'ㆲ','ㆳ'=>'ㆳ','ㆴ'=>'ㆴ','ㆵ'=>'ㆵ','ㆶ'=>'ㆶ','ㆷ'=>'ㆷ','ㇰ'=>'ㇰ','ㇱ'=>'ㇱ','ㇲ'=>'ㇲ','ㇳ'=>'ㇳ','ㇴ'=>'ㇴ','ㇵ'=>'ㇵ','ㇶ'=>'ㇶ','ㇷ'=>'ㇷ','ㇸ'=>'ㇸ','ㇹ'=>'ㇹ','ㇺ'=>'ㇺ','ㇻ'=>'ㇻ','ㇼ'=>'ㇼ','ㇽ'=>'ㇽ','ㇾ'=>'ㇾ','ㇿ'=>'ㇿ','㈠'=>'1','㈡'=>'2','㈢'=>'3','㈣'=>'4','㈤'=>'5','㈥'=>'6','㈦'=>'7','㈧'=>'8','㈨'=>'9','㈩'=>'10','㉑'=>'21','㉒'=>'22','㉓'=>'23','㉔'=>'24','㉕'=>'25','㉖'=>'26','㉗'=>'27','㉘'=>'28','㉙'=>'29','㉚'=>'30','㉛'=>'31','㉜'=>'32','㉝'=>'33','㉞'=>'34','㉟'=>'35','㊀'=>'1','㊁'=>'2','㊂'=>'3','㊃'=>'4','㊄'=>'5','㊅'=>'6','㊆'=>'7','㊇'=>'8','㊈'=>'9','㊉'=>'10','㊱'=>'36','㊲'=>'37','㊳'=>'38','㊴'=>'39','㊵'=>'40','㊶'=>'41','㊷'=>'42','㊸'=>'43','㊹'=>'44','㊺'=>'45','㊻'=>'46','㊼'=>'47','㊽'=>'48','㊾'=>'49','㊿'=>'50','㐀'=>'㐀'); diff --git a/includes/utf/data/search_indexer_64.php b/includes/utf/data/search_indexer_64.php new file mode 100644 index 0000000..b5002d4 --- /dev/null +++ b/includes/utf/data/search_indexer_64.php @@ -0,0 +1 @@ +'𠀀'); diff --git a/includes/utf/data/search_indexer_84.php b/includes/utf/data/search_indexer_84.php new file mode 100644 index 0000000..c038215 --- /dev/null +++ b/includes/utf/data/search_indexer_84.php @@ -0,0 +1 @@ +'𪛖'); diff --git a/includes/utf/data/search_indexer_9.php b/includes/utf/data/search_indexer_9.php new file mode 100644 index 0000000..a358f78 --- /dev/null +++ b/includes/utf/data/search_indexer_9.php @@ -0,0 +1 @@ +'䶵','一'=>'一'); diff --git a/includes/utf/data/search_indexer_95.php b/includes/utf/data/search_indexer_95.php new file mode 100644 index 0000000..63d27fb --- /dev/null +++ b/includes/utf/data/search_indexer_95.php @@ -0,0 +1 @@ +'丽','丸'=>'丸','乁'=>'乁','𠄢'=>'𠄢','你'=>'你','侮'=>'侮','侻'=>'侻','倂'=>'倂','偺'=>'偺','備'=>'備','僧'=>'僧','像'=>'像','㒞'=>'㒞','𠘺'=>'𠘺','免'=>'免','兔'=>'兔','兤'=>'兤','具'=>'具','𠔜'=>'𠔜','㒹'=>'㒹','內'=>'內','再'=>'再','𠕋'=>'𠕋','冗'=>'冗','冤'=>'冤','仌'=>'仌','冬'=>'冬','况'=>'况','𩇟'=>'𩇟','凵'=>'凵','刃'=>'刃','㓟'=>'㓟','刻'=>'刻','剆'=>'剆','割'=>'割','剷'=>'剷','㔕'=>'㔕','勇'=>'勇','勉'=>'勉','勤'=>'勤','勺'=>'勺','包'=>'包','匆'=>'匆','北'=>'北','卉'=>'卉','卑'=>'卑','博'=>'博','即'=>'即','卽'=>'卽','卿'=>'卿','卿'=>'卿','卿'=>'卿','𠨬'=>'𠨬','灰'=>'灰','及'=>'及','叟'=>'叟','𠭣'=>'𠭣','叫'=>'叫','叱'=>'叱','吆'=>'吆','咞'=>'咞','吸'=>'吸','呈'=>'呈','周'=>'周','咢'=>'咢','哶'=>'哶','唐'=>'唐','啓'=>'啓','啣'=>'啣','善'=>'善','善'=>'善','喙'=>'喙','喫'=>'喫','喳'=>'喳','嗂'=>'嗂','圖'=>'圖','嘆'=>'嘆','圗'=>'圗','噑'=>'噑','噴'=>'噴','切'=>'切','壮'=>'壮','城'=>'城','埴'=>'埴','堍'=>'堍','型'=>'型','堲'=>'堲','報'=>'報','墬'=>'墬','𡓤'=>'𡓤','売'=>'売','壷'=>'壷','夆'=>'夆','多'=>'多','夢'=>'夢','奢'=>'奢','𡚨'=>'𡚨','𡛪'=>'𡛪','姬'=>'姬','娛'=>'娛','娧'=>'娧','姘'=>'姘','婦'=>'婦','㛮'=>'㛮','㛼'=>'㛼','嬈'=>'嬈','嬾'=>'嬾','嬾'=>'嬾','𡧈'=>'𡧈','寃'=>'寃','寘'=>'寘','寧'=>'寧','寳'=>'寳','𡬘'=>'𡬘','寿'=>'寿','将'=>'将','当'=>'当','尢'=>'尢','㞁'=>'㞁','屠'=>'屠','屮'=>'屮','峀'=>'峀','岍'=>'岍','𡷤'=>'𡷤','嵃'=>'嵃','𡷦'=>'𡷦','嵮'=>'嵮','嵫'=>'嵫','嵼'=>'嵼','巡'=>'巡','巢'=>'巢','㠯'=>'㠯','巽'=>'巽','帨'=>'帨','帽'=>'帽','幩'=>'幩','㡢'=>'㡢','𢆃'=>'𢆃','㡼'=>'㡼','庰'=>'庰','庳'=>'庳','庶'=>'庶','廊'=>'廊','𪎒'=>'𪎒','廾'=>'廾','𢌱'=>'𢌱','𢌱'=>'𢌱','舁'=>'舁','弢'=>'弢','弢'=>'弢','㣇'=>'㣇','𣊸'=>'𣊸','𦇚'=>'𦇚','形'=>'形','彫'=>'彫','㣣'=>'㣣','徚'=>'徚','忍'=>'忍','志'=>'志','忹'=>'忹','悁'=>'悁','㤺'=>'㤺','㤜'=>'㤜','悔'=>'悔','𢛔'=>'𢛔','惇'=>'惇','慈'=>'慈','慌'=>'慌','慎'=>'慎','慌'=>'慌','慺'=>'慺','憎'=>'憎','憲'=>'憲','憤'=>'憤','憯'=>'憯','懞'=>'懞','懲'=>'懲','懶'=>'懶','成'=>'成','戛'=>'戛','扝'=>'扝','抱'=>'抱','拔'=>'拔','捐'=>'捐','𢬌'=>'𢬌','挽'=>'挽','拼'=>'拼','捨'=>'捨','掃'=>'掃','揤'=>'揤','𢯱'=>'𢯱','搢'=>'搢','揅'=>'揅','掩'=>'掩','㨮'=>'㨮','摩'=>'摩','摾'=>'摾','撝'=>'撝','摷'=>'摷','㩬'=>'㩬','敏'=>'敏','敬'=>'敬','𣀊'=>'𣀊','旣'=>'旣','書'=>'書','晉'=>'晉','㬙'=>'㬙','暑'=>'暑','㬈'=>'㬈','㫤'=>'㫤','冒'=>'冒','冕'=>'冕','最'=>'最','暜'=>'暜','肭'=>'肭','䏙'=>'䏙','朗'=>'朗','望'=>'望','朡'=>'朡','杞'=>'杞','杓'=>'杓','𣏃'=>'𣏃','㭉'=>'㭉','柺'=>'柺','枅'=>'枅','桒'=>'桒','梅'=>'梅','𣑭'=>'𣑭','梎'=>'梎','栟'=>'栟','椔'=>'椔','㮝'=>'㮝','楂'=>'楂','榣'=>'榣','槪'=>'槪','檨'=>'檨','𣚣'=>'𣚣','櫛'=>'櫛','㰘'=>'㰘','次'=>'次','𣢧'=>'𣢧','歔'=>'歔','㱎'=>'㱎','歲'=>'歲','殟'=>'殟','殺'=>'殺','殻'=>'殻','𣪍'=>'𣪍','𡴋'=>'𡴋','𣫺'=>'𣫺','汎'=>'汎','𣲼'=>'𣲼','沿'=>'沿','泍'=>'泍','汧'=>'汧','洖'=>'洖','派'=>'派','海'=>'海','流'=>'流','浩'=>'浩','浸'=>'浸','涅'=>'涅','𣴞'=>'𣴞','洴'=>'洴','港'=>'港','湮'=>'湮','㴳'=>'㴳','滋'=>'滋','滇'=>'滇','𣻑'=>'𣻑','淹'=>'淹','潮'=>'潮','𣽞'=>'𣽞','𣾎'=>'𣾎','濆'=>'濆','瀹'=>'瀹','瀞'=>'瀞','瀛'=>'瀛','㶖'=>'㶖','灊'=>'灊','災'=>'災','灷'=>'灷','炭'=>'炭','𠔥'=>'𠔥','煅'=>'煅','𤉣'=>'𤉣','熜'=>'熜','𤎫'=>'𤎫','爨'=>'爨','爵'=>'爵','牐'=>'牐','𤘈'=>'𤘈','犀'=>'犀','犕'=>'犕','𤜵'=>'𤜵','𤠔'=>'𤠔','獺'=>'獺','王'=>'王','㺬'=>'㺬','玥'=>'玥','㺸'=>'㺸','㺸'=>'㺸','瑇'=>'瑇','瑜'=>'瑜','瑱'=>'瑱','璅'=>'璅','瓊'=>'瓊','㼛'=>'㼛','甤'=>'甤','𤰶'=>'𤰶','甾'=>'甾','𤲒'=>'𤲒','異'=>'異','𢆟'=>'𢆟','瘐'=>'瘐','𤾡'=>'𤾡','𤾸'=>'𤾸','𥁄'=>'𥁄','㿼'=>'㿼','䀈'=>'䀈','直'=>'直','𥃳'=>'𥃳','𥃲'=>'𥃲','𥄙'=>'𥄙','𥄳'=>'𥄳','眞'=>'眞','真'=>'真','真'=>'真','睊'=>'睊','䀹'=>'䀹','瞋'=>'瞋','䁆'=>'䁆','䂖'=>'䂖','𥐝'=>'𥐝','硎'=>'硎','碌'=>'碌','磌'=>'磌','䃣'=>'䃣','𥘦'=>'𥘦','祖'=>'祖','𥚚'=>'𥚚','𥛅'=>'𥛅','福'=>'福','秫'=>'秫','䄯'=>'䄯','穀'=>'穀','穊'=>'穊','穏'=>'穏','𥥼'=>'𥥼','𥪧'=>'𥪧','𥪧'=>'𥪧','竮'=>'竮','䈂'=>'䈂','𥮫'=>'𥮫','篆'=>'篆','築'=>'築','䈧'=>'䈧','𥲀'=>'𥲀','糒'=>'糒','䊠'=>'䊠','糨'=>'糨','糣'=>'糣','紀'=>'紀','𥾆'=>'𥾆','絣'=>'絣','䌁'=>'䌁','緇'=>'緇','縂'=>'縂','繅'=>'繅','䌴'=>'䌴','𦈨'=>'𦈨','𦉇'=>'𦉇','䍙'=>'䍙','𦋙'=>'𦋙','罺'=>'罺','𦌾'=>'𦌾','羕'=>'羕','翺'=>'翺','者'=>'者','𦓚'=>'𦓚','𦔣'=>'𦔣','聠'=>'聠','𦖨'=>'𦖨','聰'=>'聰','𣍟'=>'𣍟','䏕'=>'䏕','育'=>'育','脃'=>'脃','䐋'=>'䐋','脾'=>'脾','媵'=>'媵','𦞧'=>'𦞧','𦞵'=>'𦞵','𣎓'=>'𣎓','𣎜'=>'𣎜','舁'=>'舁','舄'=>'舄','辞'=>'辞','䑫'=>'䑫','芑'=>'芑','芋'=>'芋','芝'=>'芝','劳'=>'劳','花'=>'花','芳'=>'芳','芽'=>'芽','苦'=>'苦','𦬼'=>'𦬼','若'=>'若','茝'=>'茝','荣'=>'荣','莭'=>'莭','茣'=>'茣','莽'=>'莽','菧'=>'菧','著'=>'著','荓'=>'荓','菊'=>'菊','菌'=>'菌','菜'=>'菜','𦰶'=>'𦰶','𦵫'=>'𦵫','𦳕'=>'𦳕','䔫'=>'䔫','蓱'=>'蓱','蓳'=>'蓳','蔖'=>'蔖','𧏊'=>'𧏊','蕤'=>'蕤','𦼬'=>'𦼬','䕝'=>'䕝','䕡'=>'䕡','𦾱'=>'𦾱','𧃒'=>'𧃒','䕫'=>'䕫','虐'=>'虐','虜'=>'虜','虧'=>'虧','虩'=>'虩','蚩'=>'蚩','蚈'=>'蚈','蜎'=>'蜎','蛢'=>'蛢','蝹'=>'蝹','蜨'=>'蜨','蝫'=>'蝫','螆'=>'螆','䗗'=>'䗗','蟡'=>'蟡','蠁'=>'蠁','䗹'=>'䗹','衠'=>'衠','衣'=>'衣','𧙧'=>'𧙧','裗'=>'裗','裞'=>'裞','䘵'=>'䘵','裺'=>'裺','㒻'=>'㒻','𧢮'=>'𧢮','𧥦'=>'𧥦','䚾'=>'䚾','䛇'=>'䛇','誠'=>'誠','諭'=>'諭','變'=>'變','豕'=>'豕','𧲨'=>'𧲨','貫'=>'貫','賁'=>'賁','贛'=>'贛','起'=>'起','𧼯'=>'𧼯','𠠄'=>'𠠄','跋'=>'跋','趼'=>'趼','跰'=>'跰','𠣞'=>'𠣞','軔'=>'軔','輸'=>'輸','𨗒'=>'𨗒','𨗭'=>'𨗭','邔'=>'邔','郱'=>'郱','鄑'=>'鄑','𨜮'=>'𨜮','鄛'=>'鄛','鈸'=>'鈸','鋗'=>'鋗','鋘'=>'鋘','鉼'=>'鉼','鏹'=>'鏹','鐕'=>'鐕','𨯺'=>'𨯺','開'=>'開','䦕'=>'䦕','閷'=>'閷','𨵷'=>'𨵷','䧦'=>'䧦','雃'=>'雃','嶲'=>'嶲','霣'=>'霣','𩅅'=>'𩅅','𩈚'=>'𩈚','䩮'=>'䩮','䩶'=>'䩶','韠'=>'韠','𩐊'=>'𩐊','䪲'=>'䪲','𩒖'=>'𩒖','頋'=>'頋','頋'=>'頋','頩'=>'頩','𩖶'=>'𩖶','飢'=>'飢','䬳'=>'䬳','餩'=>'餩','馧'=>'馧','駂'=>'駂','駾'=>'駾','䯎'=>'䯎','𩬰'=>'𩬰','鬒'=>'鬒','鱀'=>'鱀','鳽'=>'鳽','䳎'=>'䳎','䳭'=>'䳭','鵧'=>'鵧','𪃎'=>'𪃎','䳸'=>'䳸','𪄅'=>'𪄅','𪈎'=>'𪈎','𪊑'=>'𪊑','麻'=>'麻','䵖'=>'䵖','黹'=>'黹','黾'=>'黾','鼅'=>'鼅','鼏'=>'鼏','鼖'=>'鼖','鼻'=>'鼻','𪘀'=>'𪘀'); diff --git a/includes/utf/utf_tools.php b/includes/utf/utf_tools.php new file mode 100644 index 0000000..89de454 --- /dev/null +++ b/includes/utf/utf_tools.php @@ -0,0 +1,1450 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +// Enforce ASCII only string handling +setlocale(LC_CTYPE, 'C'); + +/** +* Setup the UTF-8 portability layer +*/ +Patchwork\Utf8\Bootup::initUtf8Encode(); +Patchwork\Utf8\Bootup::initMbstring(); +Patchwork\Utf8\Bootup::initIntl(); + +/** +* UTF-8 tools +* +* Whenever possible, these functions will try to use PHP's built-in functions or +* extensions, otherwise they will default to custom routines. +* +*/ + +/** +* UTF-8 aware alternative to strrpos +* @ignore +*/ +function utf8_strrpos($str, $needle, $offset = null) +{ + // Emulate behaviour of strrpos rather than raising warning + if (empty($str)) + { + return false; + } + + if (is_null($offset)) + { + return mb_strrpos($str, $needle); + } + else + { + return mb_strrpos($str, $needle, $offset); + } +} + +/** +* UTF-8 aware alternative to strpos +* @ignore +*/ +function utf8_strpos($str, $needle, $offset = null) +{ + if (is_null($offset)) + { + return mb_strpos($str, $needle); + } + else + { + return mb_strpos($str, $needle, $offset); + } +} + +/** +* UTF-8 aware alternative to strtolower +* @ignore +*/ +function utf8_strtolower($str) +{ + return mb_strtolower($str); +} + +/** +* UTF-8 aware alternative to strtoupper +* @ignore +*/ +function utf8_strtoupper($str) +{ + return mb_strtoupper($str); +} + +/** +* UTF-8 aware alternative to substr +* @ignore +*/ +function utf8_substr($str, $offset, $length = null) +{ + if (is_null($length)) + { + return mb_substr($str, $offset); + } + else + { + return mb_substr($str, $offset, $length); + } +} + +/** +* Return the length (in characters) of a UTF-8 string +* @ignore +*/ +function utf8_strlen($text) +{ + return mb_strlen($text, 'utf-8'); +} + +/** +* UTF-8 aware alternative to str_split +* Convert a string to an array +* +* @author Harry Fuecks +* @param string $str UTF-8 encoded +* @param int $split_len number to characters to split string by +* @return array characters in string reverses +*/ +function utf8_str_split($str, $split_len = 1) +{ + if (!is_int($split_len) || $split_len < 1) + { + return false; + } + + $len = utf8_strlen($str); + if ($len <= $split_len) + { + return array($str); + } + + preg_match_all('/.{' . $split_len . '}|[^\x00]{1,' . $split_len . '}$/us', $str, $ar); + return $ar[0]; +} + +/** +* UTF-8 aware alternative to strspn +* Find length of initial segment matching the mask +* +* @author Harry Fuecks +*/ +function utf8_strspn($str, $mask, $start = null, $length = null) +{ + if ($start !== null || $length !== null) + { + $str = utf8_substr($str, $start, $length); + } + + preg_match('/^[' . $mask . ']+/u', $str, $matches); + + if (isset($matches[0])) + { + return utf8_strlen($matches[0]); + } + + return 0; +} + +/** +* UTF-8 aware alternative to ucfirst +* Make a string's first character uppercase +* +* @author Harry Fuecks +* @param string +* @return string with first character as upper case (if applicable) +*/ +function utf8_ucfirst($str) +{ + switch (utf8_strlen($str)) + { + case 0: + return ''; + break; + + case 1: + return utf8_strtoupper($str); + break; + + default: + preg_match('/^(.{1})(.*)$/us', $str, $matches); + return utf8_strtoupper($matches[1]) . $matches[2]; + break; + } +} + +/** +* Recode a string to UTF-8 +* +* If the encoding is not supported, the string is returned as-is +* +* @param string $string Original string +* @param string $encoding Original encoding (lowered) +* @return string The string, encoded in UTF-8 +*/ +function utf8_recode($string, $encoding) +{ + $encoding = strtolower($encoding); + + if ($encoding == 'utf-8' || !is_string($string) || empty($string)) + { + return $string; + } + + // we force iso-8859-1 to be cp1252 + if ($encoding == 'iso-8859-1') + { + $encoding = 'cp1252'; + } + // convert iso-8859-8-i to iso-8859-8 + else if ($encoding == 'iso-8859-8-i') + { + $encoding = 'iso-8859-8'; + $string = hebrev($string); + } + + // First, try iconv() + if (function_exists('iconv')) + { + $ret = @iconv($encoding, 'utf-8', $string); + + if (!empty($ret)) + { + return $ret; + } + } + + // Try the mb_string extension + if (function_exists('mb_convert_encoding')) + { + // mbstring is nasty on PHP4, we must make *sure* that we send a good encoding + switch ($encoding) + { + case 'iso-8859-1': + case 'iso-8859-2': + case 'iso-8859-4': + case 'iso-8859-7': + case 'iso-8859-9': + case 'iso-8859-15': + case 'windows-1251': + case 'windows-1252': + case 'cp1252': + case 'shift_jis': + case 'euc-kr': + case 'big5': + case 'gb2312': + $ret = @mb_convert_encoding($string, 'utf-8', $encoding); + + if (!empty($ret)) + { + return $ret; + } + } + } + + // Try the recode extension + if (function_exists('recode_string')) + { + $ret = @recode_string($encoding . '..utf-8', $string); + + if (!empty($ret)) + { + return $ret; + } + } + + // If nothing works, check if we have a custom transcoder available + if (!preg_match('#^[a-z0-9_ \\-]+$#', $encoding)) + { + // Make sure the encoding name is alphanumeric, we don't want it to be abused into loading arbitrary files + trigger_error('Unknown encoding: ' . $encoding, E_USER_ERROR); + } + + global $phpbb_root_path, $phpEx; + + // iso-8859-* character encoding + if (preg_match('/iso[_ -]?8859[_ -]?(\\d+)/', $encoding, $array)) + { + switch ($array[1]) + { + case '1': + case '2': + case '4': + case '7': + case '8': + case '9': + case '15': + if (!function_exists('iso_8859_' . $array[1])) + { + if (!file_exists($phpbb_root_path . 'includes/utf/data/recode_basic.' . $phpEx)) + { + trigger_error('Basic reencoder file is missing', E_USER_ERROR); + } + include($phpbb_root_path . 'includes/utf/data/recode_basic.' . $phpEx); + } + return call_user_func('iso_8859_' . $array[1], $string); + break; + + default: + trigger_error('Unknown encoding: ' . $encoding, E_USER_ERROR); + break; + } + } + + // CP/WIN character encoding + if (preg_match('/(?:cp|windows)[_\- ]?(\\d+)/', $encoding, $array)) + { + switch ($array[1]) + { + case '932': + break; + case '1250': + case '1251': + case '1252': + case '1254': + case '1255': + case '1256': + case '1257': + case '874': + if (!function_exists('cp' . $array[1])) + { + if (!file_exists($phpbb_root_path . 'includes/utf/data/recode_basic.' . $phpEx)) + { + trigger_error('Basic reencoder file is missing', E_USER_ERROR); + } + include($phpbb_root_path . 'includes/utf/data/recode_basic.' . $phpEx); + } + return call_user_func('cp' . $array[1], $string); + break; + + default: + trigger_error('Unknown encoding: ' . $encoding, E_USER_ERROR); + break; + } + } + + // TIS-620 + if (preg_match('/tis[_ -]?620/', $encoding)) + { + if (!function_exists('tis_620')) + { + if (!file_exists($phpbb_root_path . 'includes/utf/data/recode_basic.' . $phpEx)) + { + trigger_error('Basic reencoder file is missing', E_USER_ERROR); + } + include($phpbb_root_path . 'includes/utf/data/recode_basic.' . $phpEx); + } + return tis_620($string); + } + + // SJIS + if (preg_match('/sjis(?:[_ -]?win)?|(?:cp|ibm)[_ -]?932|shift[_ -]?jis/', $encoding)) + { + if (!function_exists('sjis')) + { + if (!file_exists($phpbb_root_path . 'includes/utf/data/recode_cjk.' . $phpEx)) + { + trigger_error('CJK reencoder file is missing', E_USER_ERROR); + } + include($phpbb_root_path . 'includes/utf/data/recode_cjk.' . $phpEx); + } + return sjis($string); + } + + // EUC_KR + if (preg_match('/euc[_ -]?kr/', $encoding)) + { + if (!function_exists('euc_kr')) + { + if (!file_exists($phpbb_root_path . 'includes/utf/data/recode_cjk.' . $phpEx)) + { + trigger_error('CJK reencoder file is missing', E_USER_ERROR); + } + include($phpbb_root_path . 'includes/utf/data/recode_cjk.' . $phpEx); + } + return euc_kr($string); + } + + // BIG-5 + if (preg_match('/big[_ -]?5/', $encoding)) + { + if (!function_exists('big5')) + { + if (!file_exists($phpbb_root_path . 'includes/utf/data/recode_cjk.' . $phpEx)) + { + trigger_error('CJK reencoder file is missing', E_USER_ERROR); + } + include($phpbb_root_path . 'includes/utf/data/recode_cjk.' . $phpEx); + } + return big5($string); + } + + // GB2312 + if (preg_match('/gb[_ -]?2312/', $encoding)) + { + if (!function_exists('gb2312')) + { + if (!file_exists($phpbb_root_path . 'includes/utf/data/recode_cjk.' . $phpEx)) + { + trigger_error('CJK reencoder file is missing', E_USER_ERROR); + } + include($phpbb_root_path . 'includes/utf/data/recode_cjk.' . $phpEx); + } + return gb2312($string); + } + + // Trigger an error?! Fow now just give bad data :-( + trigger_error('Unknown encoding: ' . $encoding, E_USER_ERROR); +} + +/** +* Replace all UTF-8 chars that are not in ASCII with their NCR +* +* @param string $text UTF-8 string in NFC +* @return string ASCII string using NCRs for non-ASCII chars +*/ +function utf8_encode_ncr($text) +{ + return preg_replace_callback('#[\\xC2-\\xF4][\\x80-\\xBF]{1,3}#', 'utf8_encode_ncr_callback', $text); +} + +/** +* Callback used in encode_ncr() +* +* Takes a UTF-8 char and replaces it with its NCR. Attention, $m is an array +* +* @param array $m 0-based numerically indexed array passed by preg_replace_callback() +* @return string A HTML NCR if the character is valid, or the original string otherwise +*/ +function utf8_encode_ncr_callback($m) +{ + return '&#' . utf8_ord($m[0]) . ';'; +} + +/** +* Converts a UTF-8 char to an NCR +* +* @param string $chr UTF-8 char +* @return integer UNICODE code point +*/ +function utf8_ord($chr) +{ + switch (strlen($chr)) + { + case 1: + return ord($chr); + break; + + case 2: + return ((ord($chr[0]) & 0x1F) << 6) | (ord($chr[1]) & 0x3F); + break; + + case 3: + return ((ord($chr[0]) & 0x0F) << 12) | ((ord($chr[1]) & 0x3F) << 6) | (ord($chr[2]) & 0x3F); + break; + + case 4: + return ((ord($chr[0]) & 0x07) << 18) | ((ord($chr[1]) & 0x3F) << 12) | ((ord($chr[2]) & 0x3F) << 6) | (ord($chr[3]) & 0x3F); + break; + + default: + return $chr; + } +} + +/** +* Converts an NCR to a UTF-8 char +* +* @param int $cp UNICODE code point +* @return string UTF-8 char +*/ +function utf8_chr($cp) +{ + if ($cp > 0xFFFF) + { + return chr(0xF0 | ($cp >> 18)) . chr(0x80 | (($cp >> 12) & 0x3F)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F)); + } + else if ($cp > 0x7FF) + { + return chr(0xE0 | ($cp >> 12)) . chr(0x80 | (($cp >> 6) & 0x3F)) . chr(0x80 | ($cp & 0x3F)); + } + else if ($cp > 0x7F) + { + return chr(0xC0 | ($cp >> 6)) . chr(0x80 | ($cp & 0x3F)); + } + else + { + return chr($cp); + } +} + +/** +* Convert Numeric Character References to UTF-8 chars +* +* Notes: +* - we do not convert NCRs recursively, if you pass &#38; it will return & +* - we DO NOT check for the existence of the Unicode characters, therefore an entity may be converted to an inexistent codepoint +* +* @param string $text String to convert, encoded in UTF-8 (no normal form required) +* @return string UTF-8 string where NCRs have been replaced with the actual chars +*/ +function utf8_decode_ncr($text) +{ + return preg_replace_callback('/&#([0-9]{1,6}|x[0-9A-F]{1,5});/i', 'utf8_decode_ncr_callback', $text); +} + +/** +* Callback used in decode_ncr() +* +* Takes a NCR (in decimal or hexadecimal) and returns a UTF-8 char. Attention, $m is an array. +* It will ignore most of invalid NCRs, but not all! +* +* @param array $m 0-based numerically indexed array passed by preg_replace_callback() +* @return string UTF-8 char +*/ +function utf8_decode_ncr_callback($m) +{ + $cp = (strncasecmp($m[1], 'x', 1)) ? $m[1] : hexdec(substr($m[1], 1)); + + return utf8_chr($cp); +} + +/** +* Case folds a unicode string as per Unicode 5.0, section 3.13 +* +* @param string $text text to be case folded +* @param string $option determines how we will fold the cases +* @return string case folded text +*/ +function utf8_case_fold($text, $option = 'full') +{ + static $uniarray = array(); + global $phpbb_root_path, $phpEx; + + // common is always set + if (!isset($uniarray['c'])) + { + $uniarray['c'] = include($phpbb_root_path . 'includes/utf/data/case_fold_c.' . $phpEx); + } + + // only set full if we need to + if ($option === 'full' && !isset($uniarray['f'])) + { + $uniarray['f'] = include($phpbb_root_path . 'includes/utf/data/case_fold_f.' . $phpEx); + } + + // only set simple if we need to + if ($option !== 'full' && !isset($uniarray['s'])) + { + $uniarray['s'] = include($phpbb_root_path . 'includes/utf/data/case_fold_s.' . $phpEx); + } + + // common is always replaced + $text = strtr($text, $uniarray['c']); + + if ($option === 'full') + { + // full replaces a character with multiple characters + $text = strtr($text, $uniarray['f']); + } + else + { + // simple replaces a character with another character + $text = strtr($text, $uniarray['s']); + } + + return $text; +} + +/** +* Takes the input and does a "special" case fold. It does minor normalization +* and returns NFKC compatable text +* +* @param string $text text to be case folded +* @param string $option determines how we will fold the cases +* @return string case folded text +*/ +function utf8_case_fold_nfkc($text, $option = 'full') +{ + static $fc_nfkc_closure = array( + "\xCD\xBA" => "\x20\xCE\xB9", + "\xCF\x92" => "\xCF\x85", + "\xCF\x93" => "\xCF\x8D", + "\xCF\x94" => "\xCF\x8B", + "\xCF\xB2" => "\xCF\x83", + "\xCF\xB9" => "\xCF\x83", + "\xE1\xB4\xAC" => "\x61", + "\xE1\xB4\xAD" => "\xC3\xA6", + "\xE1\xB4\xAE" => "\x62", + "\xE1\xB4\xB0" => "\x64", + "\xE1\xB4\xB1" => "\x65", + "\xE1\xB4\xB2" => "\xC7\x9D", + "\xE1\xB4\xB3" => "\x67", + "\xE1\xB4\xB4" => "\x68", + "\xE1\xB4\xB5" => "\x69", + "\xE1\xB4\xB6" => "\x6A", + "\xE1\xB4\xB7" => "\x6B", + "\xE1\xB4\xB8" => "\x6C", + "\xE1\xB4\xB9" => "\x6D", + "\xE1\xB4\xBA" => "\x6E", + "\xE1\xB4\xBC" => "\x6F", + "\xE1\xB4\xBD" => "\xC8\xA3", + "\xE1\xB4\xBE" => "\x70", + "\xE1\xB4\xBF" => "\x72", + "\xE1\xB5\x80" => "\x74", + "\xE1\xB5\x81" => "\x75", + "\xE1\xB5\x82" => "\x77", + "\xE2\x82\xA8" => "\x72\x73", + "\xE2\x84\x82" => "\x63", + "\xE2\x84\x83" => "\xC2\xB0\x63", + "\xE2\x84\x87" => "\xC9\x9B", + "\xE2\x84\x89" => "\xC2\xB0\x66", + "\xE2\x84\x8B" => "\x68", + "\xE2\x84\x8C" => "\x68", + "\xE2\x84\x8D" => "\x68", + "\xE2\x84\x90" => "\x69", + "\xE2\x84\x91" => "\x69", + "\xE2\x84\x92" => "\x6C", + "\xE2\x84\x95" => "\x6E", + "\xE2\x84\x96" => "\x6E\x6F", + "\xE2\x84\x99" => "\x70", + "\xE2\x84\x9A" => "\x71", + "\xE2\x84\x9B" => "\x72", + "\xE2\x84\x9C" => "\x72", + "\xE2\x84\x9D" => "\x72", + "\xE2\x84\xA0" => "\x73\x6D", + "\xE2\x84\xA1" => "\x74\x65\x6C", + "\xE2\x84\xA2" => "\x74\x6D", + "\xE2\x84\xA4" => "\x7A", + "\xE2\x84\xA8" => "\x7A", + "\xE2\x84\xAC" => "\x62", + "\xE2\x84\xAD" => "\x63", + "\xE2\x84\xB0" => "\x65", + "\xE2\x84\xB1" => "\x66", + "\xE2\x84\xB3" => "\x6D", + "\xE2\x84\xBB" => "\x66\x61\x78", + "\xE2\x84\xBE" => "\xCE\xB3", + "\xE2\x84\xBF" => "\xCF\x80", + "\xE2\x85\x85" => "\x64", + "\xE3\x89\x90" => "\x70\x74\x65", + "\xE3\x8B\x8C" => "\x68\x67", + "\xE3\x8B\x8E" => "\x65\x76", + "\xE3\x8B\x8F" => "\x6C\x74\x64", + "\xE3\x8D\xB1" => "\x68\x70\x61", + "\xE3\x8D\xB3" => "\x61\x75", + "\xE3\x8D\xB5" => "\x6F\x76", + "\xE3\x8D\xBA" => "\x69\x75", + "\xE3\x8E\x80" => "\x70\x61", + "\xE3\x8E\x81" => "\x6E\x61", + "\xE3\x8E\x82" => "\xCE\xBC\x61", + "\xE3\x8E\x83" => "\x6D\x61", + "\xE3\x8E\x84" => "\x6B\x61", + "\xE3\x8E\x85" => "\x6B\x62", + "\xE3\x8E\x86" => "\x6D\x62", + "\xE3\x8E\x87" => "\x67\x62", + "\xE3\x8E\x8A" => "\x70\x66", + "\xE3\x8E\x8B" => "\x6E\x66", + "\xE3\x8E\x8C" => "\xCE\xBC\x66", + "\xE3\x8E\x90" => "\x68\x7A", + "\xE3\x8E\x91" => "\x6B\x68\x7A", + "\xE3\x8E\x92" => "\x6D\x68\x7A", + "\xE3\x8E\x93" => "\x67\x68\x7A", + "\xE3\x8E\x94" => "\x74\x68\x7A", + "\xE3\x8E\xA9" => "\x70\x61", + "\xE3\x8E\xAA" => "\x6B\x70\x61", + "\xE3\x8E\xAB" => "\x6D\x70\x61", + "\xE3\x8E\xAC" => "\x67\x70\x61", + "\xE3\x8E\xB4" => "\x70\x76", + "\xE3\x8E\xB5" => "\x6E\x76", + "\xE3\x8E\xB6" => "\xCE\xBC\x76", + "\xE3\x8E\xB7" => "\x6D\x76", + "\xE3\x8E\xB8" => "\x6B\x76", + "\xE3\x8E\xB9" => "\x6D\x76", + "\xE3\x8E\xBA" => "\x70\x77", + "\xE3\x8E\xBB" => "\x6E\x77", + "\xE3\x8E\xBC" => "\xCE\xBC\x77", + "\xE3\x8E\xBD" => "\x6D\x77", + "\xE3\x8E\xBE" => "\x6B\x77", + "\xE3\x8E\xBF" => "\x6D\x77", + "\xE3\x8F\x80" => "\x6B\xCF\x89", + "\xE3\x8F\x81" => "\x6D\xCF\x89", + "\xE3\x8F\x83" => "\x62\x71", + "\xE3\x8F\x86" => "\x63\xE2\x88\x95\x6B\x67", + "\xE3\x8F\x87" => "\x63\x6F\x2E", + "\xE3\x8F\x88" => "\x64\x62", + "\xE3\x8F\x89" => "\x67\x79", + "\xE3\x8F\x8B" => "\x68\x70", + "\xE3\x8F\x8D" => "\x6B\x6B", + "\xE3\x8F\x8E" => "\x6B\x6D", + "\xE3\x8F\x97" => "\x70\x68", + "\xE3\x8F\x99" => "\x70\x70\x6D", + "\xE3\x8F\x9A" => "\x70\x72", + "\xE3\x8F\x9C" => "\x73\x76", + "\xE3\x8F\x9D" => "\x77\x62", + "\xE3\x8F\x9E" => "\x76\xE2\x88\x95\x6D", + "\xE3\x8F\x9F" => "\x61\xE2\x88\x95\x6D", + "\xF0\x9D\x90\x80" => "\x61", + "\xF0\x9D\x90\x81" => "\x62", + "\xF0\x9D\x90\x82" => "\x63", + "\xF0\x9D\x90\x83" => "\x64", + "\xF0\x9D\x90\x84" => "\x65", + "\xF0\x9D\x90\x85" => "\x66", + "\xF0\x9D\x90\x86" => "\x67", + "\xF0\x9D\x90\x87" => "\x68", + "\xF0\x9D\x90\x88" => "\x69", + "\xF0\x9D\x90\x89" => "\x6A", + "\xF0\x9D\x90\x8A" => "\x6B", + "\xF0\x9D\x90\x8B" => "\x6C", + "\xF0\x9D\x90\x8C" => "\x6D", + "\xF0\x9D\x90\x8D" => "\x6E", + "\xF0\x9D\x90\x8E" => "\x6F", + "\xF0\x9D\x90\x8F" => "\x70", + "\xF0\x9D\x90\x90" => "\x71", + "\xF0\x9D\x90\x91" => "\x72", + "\xF0\x9D\x90\x92" => "\x73", + "\xF0\x9D\x90\x93" => "\x74", + "\xF0\x9D\x90\x94" => "\x75", + "\xF0\x9D\x90\x95" => "\x76", + "\xF0\x9D\x90\x96" => "\x77", + "\xF0\x9D\x90\x97" => "\x78", + "\xF0\x9D\x90\x98" => "\x79", + "\xF0\x9D\x90\x99" => "\x7A", + "\xF0\x9D\x90\xB4" => "\x61", + "\xF0\x9D\x90\xB5" => "\x62", + "\xF0\x9D\x90\xB6" => "\x63", + "\xF0\x9D\x90\xB7" => "\x64", + "\xF0\x9D\x90\xB8" => "\x65", + "\xF0\x9D\x90\xB9" => "\x66", + "\xF0\x9D\x90\xBA" => "\x67", + "\xF0\x9D\x90\xBB" => "\x68", + "\xF0\x9D\x90\xBC" => "\x69", + "\xF0\x9D\x90\xBD" => "\x6A", + "\xF0\x9D\x90\xBE" => "\x6B", + "\xF0\x9D\x90\xBF" => "\x6C", + "\xF0\x9D\x91\x80" => "\x6D", + "\xF0\x9D\x91\x81" => "\x6E", + "\xF0\x9D\x91\x82" => "\x6F", + "\xF0\x9D\x91\x83" => "\x70", + "\xF0\x9D\x91\x84" => "\x71", + "\xF0\x9D\x91\x85" => "\x72", + "\xF0\x9D\x91\x86" => "\x73", + "\xF0\x9D\x91\x87" => "\x74", + "\xF0\x9D\x91\x88" => "\x75", + "\xF0\x9D\x91\x89" => "\x76", + "\xF0\x9D\x91\x8A" => "\x77", + "\xF0\x9D\x91\x8B" => "\x78", + "\xF0\x9D\x91\x8C" => "\x79", + "\xF0\x9D\x91\x8D" => "\x7A", + "\xF0\x9D\x91\xA8" => "\x61", + "\xF0\x9D\x91\xA9" => "\x62", + "\xF0\x9D\x91\xAA" => "\x63", + "\xF0\x9D\x91\xAB" => "\x64", + "\xF0\x9D\x91\xAC" => "\x65", + "\xF0\x9D\x91\xAD" => "\x66", + "\xF0\x9D\x91\xAE" => "\x67", + "\xF0\x9D\x91\xAF" => "\x68", + "\xF0\x9D\x91\xB0" => "\x69", + "\xF0\x9D\x91\xB1" => "\x6A", + "\xF0\x9D\x91\xB2" => "\x6B", + "\xF0\x9D\x91\xB3" => "\x6C", + "\xF0\x9D\x91\xB4" => "\x6D", + "\xF0\x9D\x91\xB5" => "\x6E", + "\xF0\x9D\x91\xB6" => "\x6F", + "\xF0\x9D\x91\xB7" => "\x70", + "\xF0\x9D\x91\xB8" => "\x71", + "\xF0\x9D\x91\xB9" => "\x72", + "\xF0\x9D\x91\xBA" => "\x73", + "\xF0\x9D\x91\xBB" => "\x74", + "\xF0\x9D\x91\xBC" => "\x75", + "\xF0\x9D\x91\xBD" => "\x76", + "\xF0\x9D\x91\xBE" => "\x77", + "\xF0\x9D\x91\xBF" => "\x78", + "\xF0\x9D\x92\x80" => "\x79", + "\xF0\x9D\x92\x81" => "\x7A", + "\xF0\x9D\x92\x9C" => "\x61", + "\xF0\x9D\x92\x9E" => "\x63", + "\xF0\x9D\x92\x9F" => "\x64", + "\xF0\x9D\x92\xA2" => "\x67", + "\xF0\x9D\x92\xA5" => "\x6A", + "\xF0\x9D\x92\xA6" => "\x6B", + "\xF0\x9D\x92\xA9" => "\x6E", + "\xF0\x9D\x92\xAA" => "\x6F", + "\xF0\x9D\x92\xAB" => "\x70", + "\xF0\x9D\x92\xAC" => "\x71", + "\xF0\x9D\x92\xAE" => "\x73", + "\xF0\x9D\x92\xAF" => "\x74", + "\xF0\x9D\x92\xB0" => "\x75", + "\xF0\x9D\x92\xB1" => "\x76", + "\xF0\x9D\x92\xB2" => "\x77", + "\xF0\x9D\x92\xB3" => "\x78", + "\xF0\x9D\x92\xB4" => "\x79", + "\xF0\x9D\x92\xB5" => "\x7A", + "\xF0\x9D\x93\x90" => "\x61", + "\xF0\x9D\x93\x91" => "\x62", + "\xF0\x9D\x93\x92" => "\x63", + "\xF0\x9D\x93\x93" => "\x64", + "\xF0\x9D\x93\x94" => "\x65", + "\xF0\x9D\x93\x95" => "\x66", + "\xF0\x9D\x93\x96" => "\x67", + "\xF0\x9D\x93\x97" => "\x68", + "\xF0\x9D\x93\x98" => "\x69", + "\xF0\x9D\x93\x99" => "\x6A", + "\xF0\x9D\x93\x9A" => "\x6B", + "\xF0\x9D\x93\x9B" => "\x6C", + "\xF0\x9D\x93\x9C" => "\x6D", + "\xF0\x9D\x93\x9D" => "\x6E", + "\xF0\x9D\x93\x9E" => "\x6F", + "\xF0\x9D\x93\x9F" => "\x70", + "\xF0\x9D\x93\xA0" => "\x71", + "\xF0\x9D\x93\xA1" => "\x72", + "\xF0\x9D\x93\xA2" => "\x73", + "\xF0\x9D\x93\xA3" => "\x74", + "\xF0\x9D\x93\xA4" => "\x75", + "\xF0\x9D\x93\xA5" => "\x76", + "\xF0\x9D\x93\xA6" => "\x77", + "\xF0\x9D\x93\xA7" => "\x78", + "\xF0\x9D\x93\xA8" => "\x79", + "\xF0\x9D\x93\xA9" => "\x7A", + "\xF0\x9D\x94\x84" => "\x61", + "\xF0\x9D\x94\x85" => "\x62", + "\xF0\x9D\x94\x87" => "\x64", + "\xF0\x9D\x94\x88" => "\x65", + "\xF0\x9D\x94\x89" => "\x66", + "\xF0\x9D\x94\x8A" => "\x67", + "\xF0\x9D\x94\x8D" => "\x6A", + "\xF0\x9D\x94\x8E" => "\x6B", + "\xF0\x9D\x94\x8F" => "\x6C", + "\xF0\x9D\x94\x90" => "\x6D", + "\xF0\x9D\x94\x91" => "\x6E", + "\xF0\x9D\x94\x92" => "\x6F", + "\xF0\x9D\x94\x93" => "\x70", + "\xF0\x9D\x94\x94" => "\x71", + "\xF0\x9D\x94\x96" => "\x73", + "\xF0\x9D\x94\x97" => "\x74", + "\xF0\x9D\x94\x98" => "\x75", + "\xF0\x9D\x94\x99" => "\x76", + "\xF0\x9D\x94\x9A" => "\x77", + "\xF0\x9D\x94\x9B" => "\x78", + "\xF0\x9D\x94\x9C" => "\x79", + "\xF0\x9D\x94\xB8" => "\x61", + "\xF0\x9D\x94\xB9" => "\x62", + "\xF0\x9D\x94\xBB" => "\x64", + "\xF0\x9D\x94\xBC" => "\x65", + "\xF0\x9D\x94\xBD" => "\x66", + "\xF0\x9D\x94\xBE" => "\x67", + "\xF0\x9D\x95\x80" => "\x69", + "\xF0\x9D\x95\x81" => "\x6A", + "\xF0\x9D\x95\x82" => "\x6B", + "\xF0\x9D\x95\x83" => "\x6C", + "\xF0\x9D\x95\x84" => "\x6D", + "\xF0\x9D\x95\x86" => "\x6F", + "\xF0\x9D\x95\x8A" => "\x73", + "\xF0\x9D\x95\x8B" => "\x74", + "\xF0\x9D\x95\x8C" => "\x75", + "\xF0\x9D\x95\x8D" => "\x76", + "\xF0\x9D\x95\x8E" => "\x77", + "\xF0\x9D\x95\x8F" => "\x78", + "\xF0\x9D\x95\x90" => "\x79", + "\xF0\x9D\x95\xAC" => "\x61", + "\xF0\x9D\x95\xAD" => "\x62", + "\xF0\x9D\x95\xAE" => "\x63", + "\xF0\x9D\x95\xAF" => "\x64", + "\xF0\x9D\x95\xB0" => "\x65", + "\xF0\x9D\x95\xB1" => "\x66", + "\xF0\x9D\x95\xB2" => "\x67", + "\xF0\x9D\x95\xB3" => "\x68", + "\xF0\x9D\x95\xB4" => "\x69", + "\xF0\x9D\x95\xB5" => "\x6A", + "\xF0\x9D\x95\xB6" => "\x6B", + "\xF0\x9D\x95\xB7" => "\x6C", + "\xF0\x9D\x95\xB8" => "\x6D", + "\xF0\x9D\x95\xB9" => "\x6E", + "\xF0\x9D\x95\xBA" => "\x6F", + "\xF0\x9D\x95\xBB" => "\x70", + "\xF0\x9D\x95\xBC" => "\x71", + "\xF0\x9D\x95\xBD" => "\x72", + "\xF0\x9D\x95\xBE" => "\x73", + "\xF0\x9D\x95\xBF" => "\x74", + "\xF0\x9D\x96\x80" => "\x75", + "\xF0\x9D\x96\x81" => "\x76", + "\xF0\x9D\x96\x82" => "\x77", + "\xF0\x9D\x96\x83" => "\x78", + "\xF0\x9D\x96\x84" => "\x79", + "\xF0\x9D\x96\x85" => "\x7A", + "\xF0\x9D\x96\xA0" => "\x61", + "\xF0\x9D\x96\xA1" => "\x62", + "\xF0\x9D\x96\xA2" => "\x63", + "\xF0\x9D\x96\xA3" => "\x64", + "\xF0\x9D\x96\xA4" => "\x65", + "\xF0\x9D\x96\xA5" => "\x66", + "\xF0\x9D\x96\xA6" => "\x67", + "\xF0\x9D\x96\xA7" => "\x68", + "\xF0\x9D\x96\xA8" => "\x69", + "\xF0\x9D\x96\xA9" => "\x6A", + "\xF0\x9D\x96\xAA" => "\x6B", + "\xF0\x9D\x96\xAB" => "\x6C", + "\xF0\x9D\x96\xAC" => "\x6D", + "\xF0\x9D\x96\xAD" => "\x6E", + "\xF0\x9D\x96\xAE" => "\x6F", + "\xF0\x9D\x96\xAF" => "\x70", + "\xF0\x9D\x96\xB0" => "\x71", + "\xF0\x9D\x96\xB1" => "\x72", + "\xF0\x9D\x96\xB2" => "\x73", + "\xF0\x9D\x96\xB3" => "\x74", + "\xF0\x9D\x96\xB4" => "\x75", + "\xF0\x9D\x96\xB5" => "\x76", + "\xF0\x9D\x96\xB6" => "\x77", + "\xF0\x9D\x96\xB7" => "\x78", + "\xF0\x9D\x96\xB8" => "\x79", + "\xF0\x9D\x96\xB9" => "\x7A", + "\xF0\x9D\x97\x94" => "\x61", + "\xF0\x9D\x97\x95" => "\x62", + "\xF0\x9D\x97\x96" => "\x63", + "\xF0\x9D\x97\x97" => "\x64", + "\xF0\x9D\x97\x98" => "\x65", + "\xF0\x9D\x97\x99" => "\x66", + "\xF0\x9D\x97\x9A" => "\x67", + "\xF0\x9D\x97\x9B" => "\x68", + "\xF0\x9D\x97\x9C" => "\x69", + "\xF0\x9D\x97\x9D" => "\x6A", + "\xF0\x9D\x97\x9E" => "\x6B", + "\xF0\x9D\x97\x9F" => "\x6C", + "\xF0\x9D\x97\xA0" => "\x6D", + "\xF0\x9D\x97\xA1" => "\x6E", + "\xF0\x9D\x97\xA2" => "\x6F", + "\xF0\x9D\x97\xA3" => "\x70", + "\xF0\x9D\x97\xA4" => "\x71", + "\xF0\x9D\x97\xA5" => "\x72", + "\xF0\x9D\x97\xA6" => "\x73", + "\xF0\x9D\x97\xA7" => "\x74", + "\xF0\x9D\x97\xA8" => "\x75", + "\xF0\x9D\x97\xA9" => "\x76", + "\xF0\x9D\x97\xAA" => "\x77", + "\xF0\x9D\x97\xAB" => "\x78", + "\xF0\x9D\x97\xAC" => "\x79", + "\xF0\x9D\x97\xAD" => "\x7A", + "\xF0\x9D\x98\x88" => "\x61", + "\xF0\x9D\x98\x89" => "\x62", + "\xF0\x9D\x98\x8A" => "\x63", + "\xF0\x9D\x98\x8B" => "\x64", + "\xF0\x9D\x98\x8C" => "\x65", + "\xF0\x9D\x98\x8D" => "\x66", + "\xF0\x9D\x98\x8E" => "\x67", + "\xF0\x9D\x98\x8F" => "\x68", + "\xF0\x9D\x98\x90" => "\x69", + "\xF0\x9D\x98\x91" => "\x6A", + "\xF0\x9D\x98\x92" => "\x6B", + "\xF0\x9D\x98\x93" => "\x6C", + "\xF0\x9D\x98\x94" => "\x6D", + "\xF0\x9D\x98\x95" => "\x6E", + "\xF0\x9D\x98\x96" => "\x6F", + "\xF0\x9D\x98\x97" => "\x70", + "\xF0\x9D\x98\x98" => "\x71", + "\xF0\x9D\x98\x99" => "\x72", + "\xF0\x9D\x98\x9A" => "\x73", + "\xF0\x9D\x98\x9B" => "\x74", + "\xF0\x9D\x98\x9C" => "\x75", + "\xF0\x9D\x98\x9D" => "\x76", + "\xF0\x9D\x98\x9E" => "\x77", + "\xF0\x9D\x98\x9F" => "\x78", + "\xF0\x9D\x98\xA0" => "\x79", + "\xF0\x9D\x98\xA1" => "\x7A", + "\xF0\x9D\x98\xBC" => "\x61", + "\xF0\x9D\x98\xBD" => "\x62", + "\xF0\x9D\x98\xBE" => "\x63", + "\xF0\x9D\x98\xBF" => "\x64", + "\xF0\x9D\x99\x80" => "\x65", + "\xF0\x9D\x99\x81" => "\x66", + "\xF0\x9D\x99\x82" => "\x67", + "\xF0\x9D\x99\x83" => "\x68", + "\xF0\x9D\x99\x84" => "\x69", + "\xF0\x9D\x99\x85" => "\x6A", + "\xF0\x9D\x99\x86" => "\x6B", + "\xF0\x9D\x99\x87" => "\x6C", + "\xF0\x9D\x99\x88" => "\x6D", + "\xF0\x9D\x99\x89" => "\x6E", + "\xF0\x9D\x99\x8A" => "\x6F", + "\xF0\x9D\x99\x8B" => "\x70", + "\xF0\x9D\x99\x8C" => "\x71", + "\xF0\x9D\x99\x8D" => "\x72", + "\xF0\x9D\x99\x8E" => "\x73", + "\xF0\x9D\x99\x8F" => "\x74", + "\xF0\x9D\x99\x90" => "\x75", + "\xF0\x9D\x99\x91" => "\x76", + "\xF0\x9D\x99\x92" => "\x77", + "\xF0\x9D\x99\x93" => "\x78", + "\xF0\x9D\x99\x94" => "\x79", + "\xF0\x9D\x99\x95" => "\x7A", + "\xF0\x9D\x99\xB0" => "\x61", + "\xF0\x9D\x99\xB1" => "\x62", + "\xF0\x9D\x99\xB2" => "\x63", + "\xF0\x9D\x99\xB3" => "\x64", + "\xF0\x9D\x99\xB4" => "\x65", + "\xF0\x9D\x99\xB5" => "\x66", + "\xF0\x9D\x99\xB6" => "\x67", + "\xF0\x9D\x99\xB7" => "\x68", + "\xF0\x9D\x99\xB8" => "\x69", + "\xF0\x9D\x99\xB9" => "\x6A", + "\xF0\x9D\x99\xBA" => "\x6B", + "\xF0\x9D\x99\xBB" => "\x6C", + "\xF0\x9D\x99\xBC" => "\x6D", + "\xF0\x9D\x99\xBD" => "\x6E", + "\xF0\x9D\x99\xBE" => "\x6F", + "\xF0\x9D\x99\xBF" => "\x70", + "\xF0\x9D\x9A\x80" => "\x71", + "\xF0\x9D\x9A\x81" => "\x72", + "\xF0\x9D\x9A\x82" => "\x73", + "\xF0\x9D\x9A\x83" => "\x74", + "\xF0\x9D\x9A\x84" => "\x75", + "\xF0\x9D\x9A\x85" => "\x76", + "\xF0\x9D\x9A\x86" => "\x77", + "\xF0\x9D\x9A\x87" => "\x78", + "\xF0\x9D\x9A\x88" => "\x79", + "\xF0\x9D\x9A\x89" => "\x7A", + "\xF0\x9D\x9A\xA8" => "\xCE\xB1", + "\xF0\x9D\x9A\xA9" => "\xCE\xB2", + "\xF0\x9D\x9A\xAA" => "\xCE\xB3", + "\xF0\x9D\x9A\xAB" => "\xCE\xB4", + "\xF0\x9D\x9A\xAC" => "\xCE\xB5", + "\xF0\x9D\x9A\xAD" => "\xCE\xB6", + "\xF0\x9D\x9A\xAE" => "\xCE\xB7", + "\xF0\x9D\x9A\xAF" => "\xCE\xB8", + "\xF0\x9D\x9A\xB0" => "\xCE\xB9", + "\xF0\x9D\x9A\xB1" => "\xCE\xBA", + "\xF0\x9D\x9A\xB2" => "\xCE\xBB", + "\xF0\x9D\x9A\xB3" => "\xCE\xBC", + "\xF0\x9D\x9A\xB4" => "\xCE\xBD", + "\xF0\x9D\x9A\xB5" => "\xCE\xBE", + "\xF0\x9D\x9A\xB6" => "\xCE\xBF", + "\xF0\x9D\x9A\xB7" => "\xCF\x80", + "\xF0\x9D\x9A\xB8" => "\xCF\x81", + "\xF0\x9D\x9A\xB9" => "\xCE\xB8", + "\xF0\x9D\x9A\xBA" => "\xCF\x83", + "\xF0\x9D\x9A\xBB" => "\xCF\x84", + "\xF0\x9D\x9A\xBC" => "\xCF\x85", + "\xF0\x9D\x9A\xBD" => "\xCF\x86", + "\xF0\x9D\x9A\xBE" => "\xCF\x87", + "\xF0\x9D\x9A\xBF" => "\xCF\x88", + "\xF0\x9D\x9B\x80" => "\xCF\x89", + "\xF0\x9D\x9B\x93" => "\xCF\x83", + "\xF0\x9D\x9B\xA2" => "\xCE\xB1", + "\xF0\x9D\x9B\xA3" => "\xCE\xB2", + "\xF0\x9D\x9B\xA4" => "\xCE\xB3", + "\xF0\x9D\x9B\xA5" => "\xCE\xB4", + "\xF0\x9D\x9B\xA6" => "\xCE\xB5", + "\xF0\x9D\x9B\xA7" => "\xCE\xB6", + "\xF0\x9D\x9B\xA8" => "\xCE\xB7", + "\xF0\x9D\x9B\xA9" => "\xCE\xB8", + "\xF0\x9D\x9B\xAA" => "\xCE\xB9", + "\xF0\x9D\x9B\xAB" => "\xCE\xBA", + "\xF0\x9D\x9B\xAC" => "\xCE\xBB", + "\xF0\x9D\x9B\xAD" => "\xCE\xBC", + "\xF0\x9D\x9B\xAE" => "\xCE\xBD", + "\xF0\x9D\x9B\xAF" => "\xCE\xBE", + "\xF0\x9D\x9B\xB0" => "\xCE\xBF", + "\xF0\x9D\x9B\xB1" => "\xCF\x80", + "\xF0\x9D\x9B\xB2" => "\xCF\x81", + "\xF0\x9D\x9B\xB3" => "\xCE\xB8", + "\xF0\x9D\x9B\xB4" => "\xCF\x83", + "\xF0\x9D\x9B\xB5" => "\xCF\x84", + "\xF0\x9D\x9B\xB6" => "\xCF\x85", + "\xF0\x9D\x9B\xB7" => "\xCF\x86", + "\xF0\x9D\x9B\xB8" => "\xCF\x87", + "\xF0\x9D\x9B\xB9" => "\xCF\x88", + "\xF0\x9D\x9B\xBA" => "\xCF\x89", + "\xF0\x9D\x9C\x8D" => "\xCF\x83", + "\xF0\x9D\x9C\x9C" => "\xCE\xB1", + "\xF0\x9D\x9C\x9D" => "\xCE\xB2", + "\xF0\x9D\x9C\x9E" => "\xCE\xB3", + "\xF0\x9D\x9C\x9F" => "\xCE\xB4", + "\xF0\x9D\x9C\xA0" => "\xCE\xB5", + "\xF0\x9D\x9C\xA1" => "\xCE\xB6", + "\xF0\x9D\x9C\xA2" => "\xCE\xB7", + "\xF0\x9D\x9C\xA3" => "\xCE\xB8", + "\xF0\x9D\x9C\xA4" => "\xCE\xB9", + "\xF0\x9D\x9C\xA5" => "\xCE\xBA", + "\xF0\x9D\x9C\xA6" => "\xCE\xBB", + "\xF0\x9D\x9C\xA7" => "\xCE\xBC", + "\xF0\x9D\x9C\xA8" => "\xCE\xBD", + "\xF0\x9D\x9C\xA9" => "\xCE\xBE", + "\xF0\x9D\x9C\xAA" => "\xCE\xBF", + "\xF0\x9D\x9C\xAB" => "\xCF\x80", + "\xF0\x9D\x9C\xAC" => "\xCF\x81", + "\xF0\x9D\x9C\xAD" => "\xCE\xB8", + "\xF0\x9D\x9C\xAE" => "\xCF\x83", + "\xF0\x9D\x9C\xAF" => "\xCF\x84", + "\xF0\x9D\x9C\xB0" => "\xCF\x85", + "\xF0\x9D\x9C\xB1" => "\xCF\x86", + "\xF0\x9D\x9C\xB2" => "\xCF\x87", + "\xF0\x9D\x9C\xB3" => "\xCF\x88", + "\xF0\x9D\x9C\xB4" => "\xCF\x89", + "\xF0\x9D\x9D\x87" => "\xCF\x83", + "\xF0\x9D\x9D\x96" => "\xCE\xB1", + "\xF0\x9D\x9D\x97" => "\xCE\xB2", + "\xF0\x9D\x9D\x98" => "\xCE\xB3", + "\xF0\x9D\x9D\x99" => "\xCE\xB4", + "\xF0\x9D\x9D\x9A" => "\xCE\xB5", + "\xF0\x9D\x9D\x9B" => "\xCE\xB6", + "\xF0\x9D\x9D\x9C" => "\xCE\xB7", + "\xF0\x9D\x9D\x9D" => "\xCE\xB8", + "\xF0\x9D\x9D\x9E" => "\xCE\xB9", + "\xF0\x9D\x9D\x9F" => "\xCE\xBA", + "\xF0\x9D\x9D\xA0" => "\xCE\xBB", + "\xF0\x9D\x9D\xA1" => "\xCE\xBC", + "\xF0\x9D\x9D\xA2" => "\xCE\xBD", + "\xF0\x9D\x9D\xA3" => "\xCE\xBE", + "\xF0\x9D\x9D\xA4" => "\xCE\xBF", + "\xF0\x9D\x9D\xA5" => "\xCF\x80", + "\xF0\x9D\x9D\xA6" => "\xCF\x81", + "\xF0\x9D\x9D\xA7" => "\xCE\xB8", + "\xF0\x9D\x9D\xA8" => "\xCF\x83", + "\xF0\x9D\x9D\xA9" => "\xCF\x84", + "\xF0\x9D\x9D\xAA" => "\xCF\x85", + "\xF0\x9D\x9D\xAB" => "\xCF\x86", + "\xF0\x9D\x9D\xAC" => "\xCF\x87", + "\xF0\x9D\x9D\xAD" => "\xCF\x88", + "\xF0\x9D\x9D\xAE" => "\xCF\x89", + "\xF0\x9D\x9E\x81" => "\xCF\x83", + "\xF0\x9D\x9E\x90" => "\xCE\xB1", + "\xF0\x9D\x9E\x91" => "\xCE\xB2", + "\xF0\x9D\x9E\x92" => "\xCE\xB3", + "\xF0\x9D\x9E\x93" => "\xCE\xB4", + "\xF0\x9D\x9E\x94" => "\xCE\xB5", + "\xF0\x9D\x9E\x95" => "\xCE\xB6", + "\xF0\x9D\x9E\x96" => "\xCE\xB7", + "\xF0\x9D\x9E\x97" => "\xCE\xB8", + "\xF0\x9D\x9E\x98" => "\xCE\xB9", + "\xF0\x9D\x9E\x99" => "\xCE\xBA", + "\xF0\x9D\x9E\x9A" => "\xCE\xBB", + "\xF0\x9D\x9E\x9B" => "\xCE\xBC", + "\xF0\x9D\x9E\x9C" => "\xCE\xBD", + "\xF0\x9D\x9E\x9D" => "\xCE\xBE", + "\xF0\x9D\x9E\x9E" => "\xCE\xBF", + "\xF0\x9D\x9E\x9F" => "\xCF\x80", + "\xF0\x9D\x9E\xA0" => "\xCF\x81", + "\xF0\x9D\x9E\xA1" => "\xCE\xB8", + "\xF0\x9D\x9E\xA2" => "\xCF\x83", + "\xF0\x9D\x9E\xA3" => "\xCF\x84", + "\xF0\x9D\x9E\xA4" => "\xCF\x85", + "\xF0\x9D\x9E\xA5" => "\xCF\x86", + "\xF0\x9D\x9E\xA6" => "\xCF\x87", + "\xF0\x9D\x9E\xA7" => "\xCF\x88", + "\xF0\x9D\x9E\xA8" => "\xCF\x89", + "\xF0\x9D\x9E\xBB" => "\xCF\x83", + "\xF0\x9D\x9F\x8A" => "\xCF\x9D", + ); + + // do the case fold + $text = utf8_case_fold($text, $option); + + // convert to NFKC + Normalizer::normalize($text, Normalizer::NFKC); + + // FC_NFKC_Closure, http://www.unicode.org/Public/5.0.0/ucd/DerivedNormalizationProps.txt + $text = strtr($text, $fc_nfkc_closure); + + return $text; +} + +/** +* Assume the input is NFC: +* Takes the input and does a "special" case fold. It does minor normalization as well. +* +* @param string $text text to be case folded +* @param string $option determines how we will fold the cases +* @return string case folded text +*/ +function utf8_case_fold_nfc($text, $option = 'full') +{ + static $uniarray = array(); + static $ypogegrammeni = array( + "\xCD\xBA" => "\x20\xCD\x85", + "\xE1\xBE\x80" => "\xE1\xBC\x80\xCD\x85", + "\xE1\xBE\x81" => "\xE1\xBC\x81\xCD\x85", + "\xE1\xBE\x82" => "\xE1\xBC\x82\xCD\x85", + "\xE1\xBE\x83" => "\xE1\xBC\x83\xCD\x85", + "\xE1\xBE\x84" => "\xE1\xBC\x84\xCD\x85", + "\xE1\xBE\x85" => "\xE1\xBC\x85\xCD\x85", + "\xE1\xBE\x86" => "\xE1\xBC\x86\xCD\x85", + "\xE1\xBE\x87" => "\xE1\xBC\x87\xCD\x85", + "\xE1\xBE\x88" => "\xE1\xBC\x88\xCD\x85", + "\xE1\xBE\x89" => "\xE1\xBC\x89\xCD\x85", + "\xE1\xBE\x8A" => "\xE1\xBC\x8A\xCD\x85", + "\xE1\xBE\x8B" => "\xE1\xBC\x8B\xCD\x85", + "\xE1\xBE\x8C" => "\xE1\xBC\x8C\xCD\x85", + "\xE1\xBE\x8D" => "\xE1\xBC\x8D\xCD\x85", + "\xE1\xBE\x8E" => "\xE1\xBC\x8E\xCD\x85", + "\xE1\xBE\x8F" => "\xE1\xBC\x8F\xCD\x85", + "\xE1\xBE\x90" => "\xE1\xBC\xA0\xCD\x85", + "\xE1\xBE\x91" => "\xE1\xBC\xA1\xCD\x85", + "\xE1\xBE\x92" => "\xE1\xBC\xA2\xCD\x85", + "\xE1\xBE\x93" => "\xE1\xBC\xA3\xCD\x85", + "\xE1\xBE\x94" => "\xE1\xBC\xA4\xCD\x85", + "\xE1\xBE\x95" => "\xE1\xBC\xA5\xCD\x85", + "\xE1\xBE\x96" => "\xE1\xBC\xA6\xCD\x85", + "\xE1\xBE\x97" => "\xE1\xBC\xA7\xCD\x85", + "\xE1\xBE\x98" => "\xE1\xBC\xA8\xCD\x85", + "\xE1\xBE\x99" => "\xE1\xBC\xA9\xCD\x85", + "\xE1\xBE\x9A" => "\xE1\xBC\xAA\xCD\x85", + "\xE1\xBE\x9B" => "\xE1\xBC\xAB\xCD\x85", + "\xE1\xBE\x9C" => "\xE1\xBC\xAC\xCD\x85", + "\xE1\xBE\x9D" => "\xE1\xBC\xAD\xCD\x85", + "\xE1\xBE\x9E" => "\xE1\xBC\xAE\xCD\x85", + "\xE1\xBE\x9F" => "\xE1\xBC\xAF\xCD\x85", + "\xE1\xBE\xA0" => "\xE1\xBD\xA0\xCD\x85", + "\xE1\xBE\xA1" => "\xE1\xBD\xA1\xCD\x85", + "\xE1\xBE\xA2" => "\xE1\xBD\xA2\xCD\x85", + "\xE1\xBE\xA3" => "\xE1\xBD\xA3\xCD\x85", + "\xE1\xBE\xA4" => "\xE1\xBD\xA4\xCD\x85", + "\xE1\xBE\xA5" => "\xE1\xBD\xA5\xCD\x85", + "\xE1\xBE\xA6" => "\xE1\xBD\xA6\xCD\x85", + "\xE1\xBE\xA7" => "\xE1\xBD\xA7\xCD\x85", + "\xE1\xBE\xA8" => "\xE1\xBD\xA8\xCD\x85", + "\xE1\xBE\xA9" => "\xE1\xBD\xA9\xCD\x85", + "\xE1\xBE\xAA" => "\xE1\xBD\xAA\xCD\x85", + "\xE1\xBE\xAB" => "\xE1\xBD\xAB\xCD\x85", + "\xE1\xBE\xAC" => "\xE1\xBD\xAC\xCD\x85", + "\xE1\xBE\xAD" => "\xE1\xBD\xAD\xCD\x85", + "\xE1\xBE\xAE" => "\xE1\xBD\xAE\xCD\x85", + "\xE1\xBE\xAF" => "\xE1\xBD\xAF\xCD\x85", + "\xE1\xBE\xB2" => "\xE1\xBD\xB0\xCD\x85", + "\xE1\xBE\xB3" => "\xCE\xB1\xCD\x85", + "\xE1\xBE\xB4" => "\xCE\xAC\xCD\x85", + "\xE1\xBE\xB7" => "\xE1\xBE\xB6\xCD\x85", + "\xE1\xBE\xBC" => "\xCE\x91\xCD\x85", + "\xE1\xBF\x82" => "\xE1\xBD\xB4\xCD\x85", + "\xE1\xBF\x83" => "\xCE\xB7\xCD\x85", + "\xE1\xBF\x84" => "\xCE\xAE\xCD\x85", + "\xE1\xBF\x87" => "\xE1\xBF\x86\xCD\x85", + "\xE1\xBF\x8C" => "\xCE\x97\xCD\x85", + "\xE1\xBF\xB2" => "\xE1\xBD\xBC\xCD\x85", + "\xE1\xBF\xB3" => "\xCF\x89\xCD\x85", + "\xE1\xBF\xB4" => "\xCF\x8E\xCD\x85", + "\xE1\xBF\xB7" => "\xE1\xBF\xB6\xCD\x85", + "\xE1\xBF\xBC" => "\xCE\xA9\xCD\x85", + ); + + // perform a small trick, avoid further normalization on composed points that contain U+0345 in their decomposition + $text = strtr($text, $ypogegrammeni); + + // do the case fold + $text = utf8_case_fold($text, $option); + + return $text; +} + +/** +* wrapper around PHP's native normalizer from intl +* previously a PECL extension, included in the core since PHP 5.3.0 +* http://php.net/manual/en/normalizer.normalize.php +* +* @param mixed $strings a string or an array of strings to normalize +* @return mixed the normalized content, preserving array keys if array given. +*/ +function utf8_normalize_nfc($strings) +{ + if (empty($strings)) + { + return $strings; + } + + if (!is_array($strings)) + { + if (Normalizer::isNormalized($strings)) + { + return $strings; + } + return (string) Normalizer::normalize($strings); + } + else + { + foreach ($strings as $key => $string) + { + if (is_array($string)) + { + foreach ($string as $_key => $_string) + { + if (Normalizer::isNormalized($strings[$key][$_key])) + { + continue; + } + $strings[$key][$_key] = (string) Normalizer::normalize($strings[$key][$_key]); + } + } + else + { + if (Normalizer::isNormalized($strings[$key])) + { + continue; + } + $strings[$key] = (string) Normalizer::normalize($strings[$key]); + } + } + } + + return $strings; +} + +/** +* This function is used to generate a "clean" version of a string. +* Clean means that it is a case insensitive form (case folding) and that it is normalized (NFC). +* Additionally a homographs of one character are transformed into one specific character (preferably ASCII +* if it is an ASCII character). +* +* Please be aware that if you change something within this function or within +* functions used here you need to rebuild/update the username_clean column in the users table. And all other +* columns that store a clean string otherwise you will break this functionality. +* +* @param string $text An unclean string, mabye user input (has to be valid UTF-8!) +* @return string Cleaned up version of the input string +*/ +function utf8_clean_string($text) +{ + global $phpbb_root_path, $phpEx; + + static $homographs = array(); + if (empty($homographs)) + { + $homographs = include($phpbb_root_path . 'includes/utf/data/confusables.' . $phpEx); + } + + $text = utf8_case_fold_nfkc($text); + $text = strtr($text, $homographs); + // Other control characters + $text = preg_replace('#(?:[\x00-\x1F\x7F]+|(?:\xC2[\x80-\x9F])+)#', '', $text); + + // we need to reduce multiple spaces to a single one + $text = preg_replace('# {2,}#', ' ', $text); + + // we can use trim here as all the other space characters should have been turned + // into normal ASCII spaces by now + return trim($text); +} + +/** +* A wrapper for htmlspecialchars($value, ENT_COMPAT, 'UTF-8') +*/ +function utf8_htmlspecialchars($value) +{ + return htmlspecialchars($value, ENT_COMPAT, 'UTF-8'); +} + +/** +* Trying to convert returned system message to utf8 +* +* PHP assumes such messages are ISO-8859-1 so we'll do that too +* and if it breaks messages we'll blame it on them ;-) +*/ +function utf8_convert_message($message) +{ + // First of all check if conversion is neded at all, as there is no point + // in converting ASCII messages from ISO-8859-1 to UTF-8 + if (!preg_match('/[\x80-\xFF]/', $message)) + { + return utf8_htmlspecialchars($message); + } + + // else we need to convert some part of the message + return utf8_htmlspecialchars(utf8_recode($message, 'ISO-8859-1')); +} + +/** +* UTF8-compatible wordwrap replacement +* +* @param string $string The input string +* @param int $width The column width. Defaults to 75. +* @param string $break The line is broken using the optional break parameter. Defaults to '\n'. +* @param bool $cut If the cut is set to TRUE, the string is always wrapped at the specified width. So if you have a word that is larger than the given width, it is broken apart. +* +* @return string the given string wrapped at the specified column. +* +*/ +function utf8_wordwrap($string, $width = 75, $break = "\n", $cut = false) +{ + // We first need to explode on $break, not destroying existing (intended) breaks + $lines = explode($break, $string); + $new_lines = array(0 => ''); + $index = 0; + + foreach ($lines as $line) + { + $words = explode(' ', $line); + + for ($i = 0, $size = count($words); $i < $size; $i++) + { + $word = $words[$i]; + + // If cut is true we need to cut the word if it is > width chars + if ($cut && utf8_strlen($word) > $width) + { + $words[$i] = utf8_substr($word, $width); + $word = utf8_substr($word, 0, $width); + $i--; + } + + if (utf8_strlen($new_lines[$index] . $word) > $width) + { + $new_lines[$index] = substr($new_lines[$index], 0, -1); + $index++; + $new_lines[$index] = ''; + } + + $new_lines[$index] .= $word . ' '; + } + + $new_lines[$index] = substr($new_lines[$index], 0, -1); + $index++; + $new_lines[$index] = ''; + } + + unset($new_lines[$index]); + return implode($break, $new_lines); +} + +/** +* UTF8-safe basename() function +* +* basename() has some limitations and is dependent on the locale setting +* according to the PHP manual. Therefore we provide our own locale independent +* basename function. +* +* @param string $filename The filename basename() should be applied to +* @return string The basenamed filename +*/ +function utf8_basename($filename) +{ + // We always check for forward slash AND backward slash + // because they could be mixed or "sneaked" in. ;) + // You know, never trust user input... + if (strpos($filename, '/') !== false) + { + $filename = utf8_substr($filename, utf8_strrpos($filename, '/') + 1); + } + + if (strpos($filename, '\\') !== false) + { + $filename = utf8_substr($filename, utf8_strrpos($filename, '\\') + 1); + } + + return $filename; +} diff --git a/index.php b/index.php new file mode 100644 index 0000000..5eee772 --- /dev/null +++ b/index.php @@ -0,0 +1,260 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + +// Start session management +$user->session_begin(); +$auth->acl($user->data); +$user->setup('viewforum'); + +// Mark notifications read +if (($mark_notification = $request->variable('mark_notification', 0))) +{ + if ($user->data['user_id'] == ANONYMOUS) + { + if ($request->is_ajax()) + { + trigger_error('LOGIN_REQUIRED'); + } + login_box('', $user->lang['LOGIN_REQUIRED']); + } + + if (check_link_hash($request->variable('hash', ''), 'mark_notification_read')) + { + /* @var $phpbb_notifications \phpbb\notification\manager */ + $phpbb_notifications = $phpbb_container->get('notification_manager'); + + $notification = $phpbb_notifications->load_notifications('notification.method.board', array( + 'notification_id' => $mark_notification, + )); + + if (isset($notification['notifications'][$mark_notification])) + { + $notification = $notification['notifications'][$mark_notification]; + + $notification->mark_read(); + + /** + * You can use this event to perform additional tasks or redirect user elsewhere. + * + * @event core.index_mark_notification_after + * @var int mark_notification Notification ID + * @var \phpbb\notification\type\type_interface notification Notification instance + * @since 3.2.6-RC1 + */ + $vars = array('mark_notification', 'notification'); + extract($phpbb_dispatcher->trigger_event('core.index_mark_notification_after', compact($vars))); + + if ($request->is_ajax()) + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'success' => true, + )); + } + + if (($redirect = $request->variable('redirect', ''))) + { + redirect(append_sid($phpbb_root_path . $redirect)); + } + + redirect($notification->get_redirect_url()); + } + } +} + +display_forums('', $config['load_moderators']); + +$order_legend = ($config['legend_sort_groupname']) ? 'group_name' : 'group_legend'; +// Grab group details for legend display +if ($auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) +{ + $sql = 'SELECT group_id, group_name, group_colour, group_type, group_legend + FROM ' . GROUPS_TABLE . ' + WHERE group_legend > 0 + ORDER BY ' . $order_legend . ' ASC'; +} +else +{ + $sql = 'SELECT g.group_id, g.group_name, g.group_colour, g.group_type, g.group_legend + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . USER_GROUP_TABLE . ' ug + ON ( + g.group_id = ug.group_id + AND ug.user_id = ' . $user->data['user_id'] . ' + AND ug.user_pending = 0 + ) + WHERE g.group_legend > 0 + AND (g.group_type <> ' . GROUP_HIDDEN . ' OR ug.user_id = ' . $user->data['user_id'] . ') + ORDER BY g.' . $order_legend . ' ASC'; +} +$result = $db->sql_query($sql); + +/** @var \phpbb\group\helper $group_helper */ +$group_helper = $phpbb_container->get('group_helper'); + +$legend = array(); +while ($row = $db->sql_fetchrow($result)) +{ + $colour_text = ($row['group_colour']) ? ' style="color:#' . $row['group_colour'] . '"' : ''; + $group_name = $group_helper->get_name($row['group_name']); + + if ($row['group_name'] == 'BOTS' || ($user->data['user_id'] != ANONYMOUS && !$auth->acl_get('u_viewprofile'))) + { + $legend[] = '' . $group_name . '
'; + } + else + { + $legend[] = '' . $group_name . ''; + } +} +$db->sql_freeresult($result); + +$legend = implode($user->lang['COMMA_SEPARATOR'], $legend); + +// Generate birthday list if required ... +$show_birthdays = ($config['load_birthdays'] && $config['allow_birthdays'] && $auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')); + +$birthdays = $birthday_list = array(); +if ($show_birthdays) +{ + $time = $user->create_datetime(); + $now = phpbb_gmgetdate($time->getTimestamp() + $time->getOffset()); + + // Display birthdays of 29th february on 28th february in non-leap-years + $leap_year_birthdays = ''; + if ($now['mday'] == 28 && $now['mon'] == 2 && !$time->format('L')) + { + $leap_year_birthdays = " OR u.user_birthday LIKE '" . $db->sql_escape(sprintf('%2d-%2d-', 29, 2)) . "%'"; + } + + $sql_ary = array( + 'SELECT' => 'u.user_id, u.username, u.user_colour, u.user_birthday', + 'FROM' => array( + USERS_TABLE => 'u', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(BANLIST_TABLE => 'b'), + 'ON' => 'u.user_id = b.ban_userid', + ), + ), + 'WHERE' => "(b.ban_id IS NULL OR b.ban_exclude = 1) + AND (u.user_birthday LIKE '" . $db->sql_escape(sprintf('%2d-%2d-', $now['mday'], $now['mon'])) . "%' $leap_year_birthdays) + AND u.user_type IN (" . USER_NORMAL . ', ' . USER_FOUNDER . ')', + ); + + /** + * Event to modify the SQL query to get birthdays data + * + * @event core.index_modify_birthdays_sql + * @var array now The assoc array with the 'now' local timestamp data + * @var array sql_ary The SQL array to get the birthdays data + * @var object time The user related Datetime object + * @since 3.1.7-RC1 + */ + $vars = array('now', 'sql_ary', 'time'); + extract($phpbb_dispatcher->trigger_event('core.index_modify_birthdays_sql', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + $rows = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + foreach ($rows as $row) + { + $birthday_username = get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']); + $birthday_year = (int) substr($row['user_birthday'], -4); + $birthday_age = ($birthday_year) ? max(0, $now['year'] - $birthday_year) : ''; + + $birthdays[] = array( + 'USERNAME' => $birthday_username, + 'AGE' => $birthday_age, + ); + + // For 3.0 compatibility + $birthday_list[] = $birthday_username . (($birthday_age) ? " ({$birthday_age})" : ''); + } + + /** + * Event to modify the birthdays list + * + * @event core.index_modify_birthdays_list + * @var array birthdays Array with the users birthdays data + * @var array rows Array with the birthdays SQL query result + * @since 3.1.7-RC1 + */ + $vars = array('birthdays', 'rows'); + extract($phpbb_dispatcher->trigger_event('core.index_modify_birthdays_list', compact($vars))); + + $template->assign_block_vars_array('birthdays', $birthdays); +} + +// Add form token for login box +add_form_key('login', '_LOGIN'); + +// Assign index specific vars +$template->assign_vars(array( + 'TOTAL_POSTS' => $user->lang('TOTAL_POSTS_COUNT', (int) $config['num_posts']), + 'TOTAL_TOPICS' => $user->lang('TOTAL_TOPICS', (int) $config['num_topics']), + 'TOTAL_USERS' => $user->lang('TOTAL_USERS', (int) $config['num_users']), + 'NEWEST_USER' => $user->lang('NEWEST_USER', get_username_string('full', $config['newest_user_id'], $config['newest_username'], $config['newest_user_colour'])), + + 'LEGEND' => $legend, + 'BIRTHDAY_LIST' => (empty($birthday_list)) ? '' : implode($user->lang['COMMA_SEPARATOR'], $birthday_list), + + 'FORUM_IMG' => $user->img('forum_read', 'NO_UNREAD_POSTS'), + 'FORUM_UNREAD_IMG' => $user->img('forum_unread', 'UNREAD_POSTS'), + 'FORUM_LOCKED_IMG' => $user->img('forum_read_locked', 'NO_UNREAD_POSTS_LOCKED'), + 'FORUM_UNREAD_LOCKED_IMG' => $user->img('forum_unread_locked', 'UNREAD_POSTS_LOCKED'), + + 'S_LOGIN_ACTION' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'), + 'U_SEND_PASSWORD' => ($config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') : '', + 'S_DISPLAY_BIRTHDAY_LIST' => $show_birthdays, + 'S_INDEX' => true, + + 'U_MARK_FORUMS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}index.$phpEx", 'hash=' . generate_link_hash('global') . '&mark=forums&mark_time=' . time()) : '', + 'U_MCP' => ($auth->acl_get('m_') || $auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main&mode=front', true, $user->session_id) : '') +); + +$page_title = ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['INDEX']; + +/** +* You can use this event to modify the page title and load data for the index +* +* @event core.index_modify_page_title +* @var string page_title Title of the index page +* @since 3.1.0-a1 +*/ +$vars = array('page_title'); +extract($phpbb_dispatcher->trigger_event('core.index_modify_page_title', compact($vars))); + +// Output page +page_header($page_title, true); + +$template->set_filenames(array( + 'body' => 'index_body.html') +); + +page_footer(); diff --git a/install/app.php b/install/app.php new file mode 100644 index 0000000..710f495 --- /dev/null +++ b/install/app.php @@ -0,0 +1,63 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +/** + * @ignore + */ +define('IN_PHPBB', true); +define('IN_INSTALL', true); +define('PHPBB_ENVIRONMENT', 'production'); +$phpbb_root_path = '../'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); + +if (version_compare(PHP_VERSION, '5.4.7', '<') || version_compare(PHP_VERSION, '7.3-dev', '>=')) +{ + die('You are running an unsupported PHP version. Please upgrade to PHP equal to or greater than 5.4.7 but less than 7.3-dev in order to install or update to phpBB 3.2'); +} + +$startup_new_path = $phpbb_root_path . 'install/update/update/new/install/startup.' . $phpEx; +$startup_path = (file_exists($startup_new_path)) ? $startup_new_path : $phpbb_root_path . 'install/startup.' . $phpEx; +require($startup_path); + +/** @var \phpbb\filesystem\filesystem $phpbb_filesystem */ +$phpbb_filesystem = $phpbb_installer_container->get('filesystem'); + +/** @var \phpbb\template\template $template */ +$template = $phpbb_installer_container->get('template'); + +// Path to templates +$paths = array($phpbb_root_path . 'install/update/new/adm/style', $phpbb_admin_path . 'style'); +$paths = array_filter($paths, 'is_dir'); + +$template->set_custom_style(array( + array( + 'name' => 'adm', + 'ext_path' => 'adm/style/', + ), +), $paths); + +/** @var $phpbb_dispatcher \phpbb\event\dispatcher */ +$phpbb_dispatcher = $phpbb_installer_container->get('dispatcher'); + +/** @var \phpbb\language\language $language */ +$language = $phpbb_installer_container->get('language'); +$language->add_lang(array('common', 'acp/common', 'acp/board', 'install', 'posting')); + +/** @var $http_kernel \Symfony\Component\HttpKernel\HttpKernel */ +$http_kernel = $phpbb_installer_container->get('http_kernel'); + +/** @var $symfony_request \phpbb\symfony_request */ +$symfony_request = $phpbb_installer_container->get('symfony_request'); +$response = $http_kernel->handle($symfony_request); +$response->send(); +$http_kernel->terminate($symfony_request, $response); diff --git a/install/convert/controller/convertor.php b/install/convert/controller/convertor.php new file mode 100644 index 0000000..3639b10 --- /dev/null +++ b/install/convert/controller/convertor.php @@ -0,0 +1,865 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\convert\controller; + +use phpbb\cache\driver\driver_interface; +use phpbb\exception\http_exception; +use phpbb\install\controller\helper; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\database; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\iohandler\factory; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\navigation\navigation_provider; +use phpbb\language\language; +use phpbb\request\request_interface; +use phpbb\template\template; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Controller for forum convertors + * + * WARNING: This file did not meant to be present in a production environment, so moving + * this file to a location which is accessible after board installation might + * lead to security issues. + */ +class convertor +{ + /** + * @var driver_interface + */ + protected $cache; + + /** + * @var driver_interface + */ + protected $installer_cache; + + /** + * @var \phpbb\config\db + */ + protected $config; + + /** + * @var \phpbb\config_php_file + */ + protected $config_php_file; + + /** + * @var string + */ + protected $config_table; + + /** + * @var helper + */ + protected $controller_helper; + + /** + * @var database + */ + protected $db_helper; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var install_helper + */ + protected $install_helper; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var language + */ + protected $language; + + /** + * @var navigation_provider + */ + protected $navigation_provider; + + /** + * @var request_interface + */ + protected $request; + + /** + * @var string + */ + protected $session_keys_table; + + /** + * @var string + */ + protected $session_table; + + /** + * @var template + */ + protected $template; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param driver_interface $cache + * @param container_factory $container + * @param database $db_helper + * @param helper $controller_helper + * @param install_helper $install_helper + * @param factory $iohandler + * @param language $language + * @param navigation_provider $nav + * @param request_interface $request + * @param template $template + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(driver_interface $cache, container_factory $container, database $db_helper, helper $controller_helper, install_helper $install_helper, factory $iohandler, language $language, navigation_provider $nav, request_interface $request, template $template, $phpbb_root_path, $php_ext) + { + $this->installer_cache = $cache; + $this->controller_helper = $controller_helper; + $this->db_helper = $db_helper; + $this->install_helper = $install_helper; + $this->language = $language; + $this->navigation_provider = $nav; + $this->request = $request; + $this->template = $template; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $iohandler->set_environment('ajax'); + $this->iohandler = $iohandler->get(); + + if (!$this->install_helper->is_phpbb_installed() || !defined('IN_INSTALL')) + { + throw new http_exception(403, 'INSTALL_PHPBB_NOT_INSTALLED'); + } + + $this->controller_helper->handle_language_select(); + + $this->cache = $container->get('cache.driver'); + $this->config = $container->get('config'); + $this->config_php_file = new \phpbb\config_php_file($this->phpbb_root_path, $this->php_ext); + $this->db = $container->get('dbal.conn.driver'); + + $this->config_table = $container->get_parameter('tables.config'); + $this->session_keys_table = $container->get_parameter('tables.sessions_keys'); + $this->session_table = $container->get_parameter('tables.sessions'); + } + + /** + * Render the intro page + * + * @param bool|int $start_new Whether or not to force to start a new convertor + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function intro($start_new) + { + $this->setup_navigation('intro'); + + if ($start_new) + { + if ($this->request->is_ajax()) + { + $response = new StreamedResponse(); + $iohandler = $this->iohandler; + $url = $this->controller_helper->route('phpbb_convert_intro', array('start_new' => 'new')); + $response->setCallback(function() use ($iohandler, $url) { + $iohandler->redirect($url); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $this->config['convert_progress'] = ''; + $this->config['convert_db_server'] = ''; + $this->config['convert_db_user'] = ''; + $this->db->sql_query('DELETE FROM ' . $this->config_table . " + WHERE config_name = 'convert_progress' + OR config_name = 'convert_db_server' + OR config_name = 'convert_db_user'" + ); + } + + // Let's see if there is a conversion in the works... + $options = array(); + if (!empty($this->config['convert_progress']) && + !empty($this->config['convert_db_server']) && + !empty($this->config['convert_db_user']) && + !empty($this->config['convert_options'])) + { + $options = unserialize($this->config['convert_progress']); + $options = array_merge($options, + unserialize($this->config['convert_db_server']), + unserialize($this->config['convert_db_user']), + unserialize($this->config['convert_options']) + ); + } + + // This information should have already been checked once, but do it again for safety + if (!empty($options) && !empty($options['tag']) && + isset($options['dbms']) && + isset($options['dbhost']) && + isset($options['dbport']) && + isset($options['dbuser']) && + isset($options['dbpasswd']) && + isset($options['dbname']) && + isset($options['table_prefix'])) + { + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('CONTINUE_CONVERT'), + 'BODY' => $this->language->lang('CONTINUE_CONVERT_BODY'), + 'S_CONTINUE' => true, + 'U_NEW_ACTION' => $this->controller_helper->route('phpbb_convert_intro', array('start_new' => 'new')), + 'U_CONTINUE_ACTION' => $this->controller_helper->route('phpbb_convert_convert', array('converter' => $options['tag'])), + )); + + return $this->controller_helper->render('installer_convert.html', 'CONTINUE_CONVERT', true); + } + + return $this->render_convert_list(); + } + + /** + * Obtain convertor settings + * + * @param string $converter Name of the convertor + * + * @return \Symfony\Component\HttpFoundation\Response|StreamedResponse + */ + public function settings($converter) + { + $this->setup_navigation('settings'); + + require_once ($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); + require_once ($this->phpbb_root_path . 'includes/functions_convert.' . $this->php_ext); + + // Include convertor if available + $convertor_file_path = $this->phpbb_root_path . 'install/convertors/convert_' . $converter . '.' . $this->php_ext; + if (!file_exists($convertor_file_path)) + { + if ($this->request->is_ajax()) + { + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref) { + $ref->render_error('CONVERT_NOT_EXIST'); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $this->render_error('CONVERT_NOT_EXIST'); + return $this->controller_helper->render('installer_convert.html', 'STAGE_SETTINGS', true); + } + + $get_info = true; + $phpbb_root_path = $this->phpbb_root_path; // These globals are required + $phpEx = $this->php_ext; // See above + include_once ($convertor_file_path); + + // The test_file is a file that should be present in the location of the old board. + if (!isset($test_file)) + { + if ($this->request->is_ajax()) + { + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref) { + $ref->render_error('DEV_NO_TEST_FILE'); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $this->render_error('DEV_NO_TEST_FILE'); + return $this->controller_helper->render('installer_convert.html', 'STAGE_SETTINGS', true); + } + + if ($this->request->variable('submit', false)) + { + // It must be an AJAX request at this point + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref, $converter) { + $ref->proccess_settings_form($converter); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + else + { + $this->template->assign_vars(array( + 'U_ACTION' => $this->controller_helper->route('phpbb_convert_settings', array( + 'converter' => $converter, + )) + )); + + if ($this->request->is_ajax()) + { + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref) { + $ref->render_settings_form(); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $this->render_settings_form(); + } + + return $this->controller_helper->render('installer_convert.html', 'STAGE_SETTINGS', true); + } + + /** + * Run conversion + */ + public function convert($converter) + { + $this->setup_navigation('convert'); + + if ($this->request->is_ajax()) + { + $route = $this->controller_helper->route('phpbb_convert_convert', array('converter' => $converter)); + $response = new StreamedResponse(); + $ref = $this; + $response->setCallback(function() use ($ref, $route) { + $ref->redirect_to_html($route); + }); + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + + $convertor = new \phpbb\convert\convertor($this->template, $this->controller_helper); + $convertor->convert_data($converter); + + return $this->controller_helper->render('installer_convert.html', 'STAGE_IN_PROGRESS'); + } + + /** + * Render the final page of the convertor + */ + public function finish() + { + $this->setup_navigation('finish'); + + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('CONVERT_COMPLETE'), + 'BODY' => $this->language->lang('CONVERT_COMPLETE_EXPLAIN'), + )); + + // If we reached this step (conversion completed) we want to purge the cache and log the user out. + // This is for making sure the session get not screwed due to the 3.0.x users table being completely new. + $this->cache->purge(); + $this->installer_cache->purge(); + + require_once($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); + require_once($this->phpbb_root_path . 'includes/functions_convert.' . $this->php_ext); + + $sql = 'SELECT config_value + FROM ' . $this->config_table . ' + WHERE config_name = \'search_type\''; + $result = $this->db->sql_query($sql); + + if ($this->db->sql_fetchfield('config_value') != 'fulltext_mysql') + { + $this->template->assign_vars(array( + 'S_ERROR_BOX' => true, + 'ERROR_TITLE' => $this->language->lang('SEARCH_INDEX_UNCONVERTED'), + 'ERROR_MSG' => $this->language->lang('SEARCH_INDEX_UNCONVERTED_EXPLAIN'), + )); + } + + $this->db->sql_freeresult($result); + + switch ($this->db->get_sql_layer()) + { + case 'sqlite3': + $this->db->sql_query('DELETE FROM ' . $this->session_keys_table); + $this->db->sql_query('DELETE FROM ' . $this->session_table); + break; + + default: + $this->db->sql_query('TRUNCATE TABLE ' . $this->session_keys_table); + $this->db->sql_query('TRUNCATE TABLE ' . $this->session_table); + break; + } + + return $this->controller_helper->render('installer_convert.html', 'CONVERT_COMPLETE'); + } + + /** + * Validates settings form + * + * @param string $convertor + */ + public function proccess_settings_form($convertor) + { + global $phpbb_root_path, $phpEx, $get_info; + + $phpbb_root_path = $this->phpbb_root_path; + $phpEx = $this->php_ext; + $get_info = true; + + require_once($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); + require_once($this->phpbb_root_path . 'includes/functions_convert.' . $this->php_ext); + + // Include convertor if available + $convertor_file_path = $this->phpbb_root_path . 'install/convertors/convert_' . $convertor . '.' . $this->php_ext; + include ($convertor_file_path); + + // We expect to have an AJAX request here + $src_dbms = $this->request->variable('src_dbms', $convertor_data['dbms']); + $src_dbhost = $this->request->variable('src_dbhost', $convertor_data['dbhost']); + $src_dbport = $this->request->variable('src_dbport', $convertor_data['dbport']); + $src_dbuser = $this->request->variable('src_dbuser', $convertor_data['dbuser']); + $src_dbpasswd = $this->request->variable('src_dbpasswd', $convertor_data['dbpasswd']); + $src_dbname = $this->request->variable('src_dbname', $convertor_data['dbname']); + $src_table_prefix = $this->request->variable('src_table_prefix', $convertor_data['table_prefix']); + $forum_path = $this->request->variable('forum_path', $convertor_data['forum_path']); + $refresh = $this->request->variable('refresh', 1); + + // Default URL of the old board + // @todo Are we going to use this for attempting to convert URL references in posts, or should we remove it? + // -> We should convert old urls to the new relative urls format + // $src_url = $request->variable('src_url', 'Not in use at the moment'); + + // strip trailing slash from old forum path + $forum_path = (strlen($forum_path) && $forum_path[strlen($forum_path) - 1] == '/') ? substr($forum_path, 0, -1) : $forum_path; + + $error = array(); + if (!file_exists($this->phpbb_root_path . $forum_path . '/' . $test_file)) + { + $error[] = $this->language->lang('COULD_NOT_FIND_PATH', $forum_path); + } + + $connect_test = false; + $available_dbms = $this->db_helper->get_available_dbms(false, true, true); + if (!isset($available_dbms[$src_dbms]) || !$available_dbms[$src_dbms]['AVAILABLE']) + { + $error[] = $this->language->lang('INST_ERR_NO_DB'); + } + else + { + $connect_test = $this->db_helper->check_database_connection($src_dbms, $src_dbhost, $src_dbport, $src_dbuser, $src_dbpasswd, $src_dbname, $src_table_prefix); + } + + extract($this->config_php_file->get_all()); + + // The forum prefix of the old and the new forum can only be the same if two different databases are used. + if ($src_table_prefix === $table_prefix && $src_dbms === $dbms && $src_dbhost === $dbhost && $src_dbport === $dbport && $src_dbname === $dbname) + { + $error[] = $this->language->lang('TABLE_PREFIX_SAME', $src_table_prefix); + } + + if (!$connect_test) + { + $error[] = $this->language->lang('INST_ERR_DB_CONNECT'); + } + + $src_dbms = $this->config_php_file->convert_30_dbms_to_31($src_dbms); + + // Check table prefix + if (empty($error)) + { + // initiate database connection to old db if old and new db differ + global $src_db, $same_db; + $src_db = $same_db = false; + + if ($src_dbms != $dbms || $src_dbhost != $dbhost || $src_dbport != $dbport || $src_dbname != $dbname || $src_dbuser != $dbuser) + { + /** @var \phpbb\db\driver\driver_interface $src_db */ + $src_db = new $src_dbms(); + $src_db->sql_connect($src_dbhost, $src_dbuser, htmlspecialchars_decode($src_dbpasswd), $src_dbname, $src_dbport, false, true); + $same_db = false; + } + else + { + $src_db = $this->db; + $same_db = true; + } + + $src_db->sql_return_on_error(true); + $this->db->sql_return_on_error(true); + + // Try to select one row from the first table to see if the prefix is OK + $result = $src_db->sql_query_limit('SELECT * FROM ' . $src_table_prefix . $tables[0], 1); + + if (!$result) + { + $prefixes = array(); + + $db_tools_factory = new \phpbb\db\tools\factory(); + $db_tools = $db_tools_factory->get($src_db); + $tables_existing = $db_tools->sql_list_tables(); + $tables_existing = array_map('strtolower', $tables_existing); + foreach ($tables_existing as $table_name) + { + compare_table($tables, $table_name, $prefixes); + } + unset($tables_existing); + + foreach ($prefixes as $prefix => $count) + { + if ($count >= count($tables)) + { + $possible_prefix = $prefix; + break; + } + } + + $msg = ''; + if (!empty($convertor_data['table_prefix'])) + { + $msg .= $this->language->lang_array('DEFAULT_PREFIX_IS', array($convertor_data['forum_name'], $convertor_data['table_prefix'])); + } + + if (!empty($possible_prefix)) + { + $msg .= '
'; + $msg .= ($possible_prefix == '*') ? $this->language->lang('BLANK_PREFIX_FOUND') : $this->language->lang_array('PREFIX_FOUND', array($possible_prefix)); + $src_table_prefix = ($possible_prefix == '*') ? '' : $possible_prefix; + } + + $error[] = $msg; + } + + $src_db->sql_freeresult($result); + $src_db->sql_return_on_error(false); + } + + if (empty($error)) + { + // Save convertor Status + $this->config->set('convert_progress', serialize(array( + 'step' => '', + 'table_prefix' => $src_table_prefix, + 'tag' => $convertor, + )), false); + $this->config->set('convert_db_server', serialize(array( + 'dbms' => $src_dbms, + 'dbhost' => $src_dbhost, + 'dbport' => $src_dbport, + 'dbname' => $src_dbname, + )), false); + $this->config->set('convert_db_user', serialize(array( + 'dbuser' => $src_dbuser, + 'dbpasswd' => $src_dbpasswd, + )), false); + + // Save options + $this->config->set('convert_options', serialize(array( + 'forum_path' => $this->phpbb_root_path . $forum_path, + 'refresh' => $refresh + )), false); + + $url = $this->controller_helper->route('phpbb_convert_convert', array('converter' => $convertor)); + $this->iohandler->redirect($url); + $this->iohandler->send_response(true); + } + else + { + $this->render_settings_form($error); + } + } + + /** + * Renders settings form + * + * @param array $error Array of errors + */ + public function render_settings_form($error = array()) + { + foreach ($error as $msg) + { + $this->iohandler->add_error_message($msg); + } + + $dbms_options = array(); + foreach ($this->db_helper->get_available_dbms() as $dbms_key => $dbms_array) + { + $dbms_options[] = array( + 'value' => $dbms_key, + 'label' => 'DB_OPTION_' . strtoupper($dbms_key), + ); + } + + $form_title = 'SPECIFY_OPTIONS'; + $form_data = array( + 'src_dbms' => array( + 'label' => 'DBMS', + 'type' => 'select', + 'options' => $dbms_options, + ), + 'src_dbhost' => array( + 'label' => 'DB_HOST', + 'description' => 'DB_HOST_EXPLAIN', + 'type' => 'text', + ), + 'src_dbport' => array( + 'label' => 'DB_PORT', + 'description' => 'DB_PORT_EXPLAIN', + 'type' => 'text', + ), + 'src_dbname' => array( + 'label' => 'DB_NAME', + 'type' => 'text', + ), + 'src_dbuser' => array( + 'label' => 'DB_USERNAME', + 'type' => 'text', + ), + 'src_dbpasswd' => array( + 'label' => 'DB_PASSWORD', + 'type' => 'password', + ), + 'src_table_prefix' => array( + 'label' => 'TABLE_PREFIX', + 'description' => 'TABLE_PREFIX_EXPLAIN', + 'type' => 'text', + ), + 'forum_path' => array( + 'label' => 'FORUM_PATH', + 'description' => 'FORUM_PATH_EXPLAIN', + 'type' => 'text', + ), + 'refresh' => array( + 'label' => 'REFRESH_PAGE', + 'description' => 'REFRESH_PAGE_EXPLAIN', + 'type' => 'radio', + 'options' => array( + array( + 'value' => 0, + 'label' => 'NO', + 'selected' => true, + ), + array( + 'value' => 1, + 'label' => 'YES', + 'selected' => false, + ), + ), + ), + 'submit' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + ); + + if ($this->request->is_ajax()) + { + $this->iohandler->add_user_form_group($form_title, $form_data); + $this->iohandler->send_response(true); + } + else + { + $rendered_form = $this->iohandler->generate_form_render_data($form_title, $form_data); + + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('STAGE_SETTINGS'), + 'CONTENT' => $rendered_form, + )); + } + } + + /** + * Render the list of available convertors + * + * @return \Symfony\Component\HttpFoundation\Response + */ + protected function render_convert_list() + { + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('CONVERT_INTRO'), + 'BODY' => $this->language->lang('CONVERT_INTRO_BODY'), + 'S_LIST' => true, + )); + + $convertors = $sort = array(); + $get_info = true; // Global flag + + $handle = @opendir($this->phpbb_root_path . 'install/convertors/'); + + if (!$handle) + { + die('Unable to access the convertors directory'); + } + + while ($entry = readdir($handle)) + { + if (preg_match('/^convert_([a-z0-9_]+).' . $this->php_ext . '$/i', $entry, $m)) + { + $phpbb_root_path = $this->phpbb_root_path; // These globals are required + $phpEx = $this->php_ext; // See above + include_once($this->phpbb_root_path . 'install/convertors/' . $entry); + if (isset($convertor_data)) + { + $sort[strtolower($convertor_data['forum_name'])] = count($convertors); + + $convertors[] = array( + 'tag' => $m[1], + 'forum_name' => $convertor_data['forum_name'], + 'version' => $convertor_data['version'], + 'dbms' => $convertor_data['dbms'], + 'dbhost' => $convertor_data['dbhost'], + 'dbport' => $convertor_data['dbport'], + 'dbuser' => $convertor_data['dbuser'], + 'dbpasswd' => $convertor_data['dbpasswd'], + 'dbname' => $convertor_data['dbname'], + 'table_prefix' => $convertor_data['table_prefix'], + 'author' => $convertor_data['author'] + ); + } + unset($convertor_data); + } + } + closedir($handle); + + @ksort($sort); + + foreach ($sort as $void => $index) + { + $this->template->assign_block_vars('convertors', array( + 'AUTHOR' => $convertors[$index]['author'], + 'SOFTWARE' => $convertors[$index]['forum_name'], + 'VERSION' => $convertors[$index]['version'], + + 'U_CONVERT' => $this->controller_helper->route('phpbb_convert_settings', array('converter' => $convertors[$index]['tag'])), + )); + } + + return $this->controller_helper->render('installer_convert.html', 'SUB_INTRO', true); + } + + /** + * Renders an error form + * + * @param string $msg + * @param string|bool $desc + */ + public function render_error($msg, $desc = false) + { + if ($this->request->is_ajax()) + { + $this->iohandler->add_error_message($msg, $desc); + $this->iohandler->send_response(true); + } + else + { + $this->template->assign_vars(array( + 'S_ERROR_BOX' => true, + 'ERROR_TITLE' => $this->language->lang($msg), + )); + + if ($desc) + { + $this->template->assign_var('ERROR_MSG', $this->language->lang($desc)); + } + } + } + + /** + * Redirects an AJAX request to a non-JS version + * + * @param string $url URL to redirect to + */ + public function redirect_to_html($url) + { + $this->iohandler->redirect($url); + $this->iohandler->send_response(true); + } + + private function setup_navigation($stage) + { + $active = true; + $completed = false; + + switch ($stage) + { + case 'finish': + $this->navigation_provider->set_nav_property( + array('convert', 0, 'finish'), + array( + 'selected' => $active, + 'completed' => $completed, + ) + ); + + $active = false; + $completed = true; + // no break; + + case 'convert': + $this->navigation_provider->set_nav_property( + array('convert', 0, 'convert'), + array( + 'selected' => $active, + 'completed' => $completed, + ) + ); + + $active = false; + $completed = true; + // no break; + + case 'settings': + $this->navigation_provider->set_nav_property( + array('convert', 0, 'settings'), + array( + 'selected' => $active, + 'completed' => $completed, + ) + ); + + $active = false; + $completed = true; + // no break; + + case 'intro': + $this->navigation_provider->set_nav_property( + array('convert', 0, 'intro'), + array( + 'selected' => $active, + 'completed' => $completed, + ) + ); + break; + } + } +} diff --git a/install/convert/convert.php b/install/convert/convert.php new file mode 100644 index 0000000..3e9e562 --- /dev/null +++ b/install/convert/convert.php @@ -0,0 +1,60 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\convert; + +/** + * Class holding all convertor-specific details. + * + * WARNING: This file did not meant to be present in a production environment, so moving this file to a location which + * is accessible after board installation might lead to security issues. + */ +class convert +{ + var $options = array(); + + var $convertor_tag = ''; + var $src_dbms = ''; + var $src_dbhost = ''; + var $src_dbport = ''; + var $src_dbuser = ''; + var $src_dbpasswd = ''; + var $src_dbname = ''; + var $src_table_prefix = ''; + + var $convertor_data = array(); + var $tables = array(); + var $config_schema = array(); + var $convertor = array(); + var $src_truncate_statement = 'DELETE FROM '; + var $truncate_statement = 'DELETE FROM '; + + var $fulltext_search; + + // Batch size, can be adjusted by the conversion file + // For big boards a value of 6000 seems to be optimal + var $batch_size = 2000; + // Number of rows to be inserted at once (extended insert) if supported + // For installations having enough memory a value of 60 may be good. + var $num_wait_rows = 20; + + // Mysqls internal recoding engine messing up with our (better) functions? We at least support more encodings than mysql so should use it in favor. + var $mysql_convert = false; + + var $p_master; + + function __construct($p_master) + { + $this->p_master = $p_master; + } +} diff --git a/install/convert/convertor.php b/install/convert/convertor.php new file mode 100644 index 0000000..5118651 --- /dev/null +++ b/install/convert/convertor.php @@ -0,0 +1,1614 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\convert; + +use phpbb\install\controller\helper; +use phpbb\template\template; + +/** + * Convertor backend class + * + * WARNING: This file did not meant to be present in a production environment, so moving this file to a location which + * is accessible after board installation might lead to security issues. + */ +class convertor +{ + /** + * @var helper + */ + protected $controller_helper; + + /** + * @var \phpbb\filesystem\filesystem + */ + protected $filesystem; + + /** + * @var \phpbb\template\template + */ + protected $template; + + /** + * Constructor + * + * @param template $template + * @param helper $controller_helper + */ + public function __construct(template $template, helper $controller_helper) + { + global $convert, $phpbb_filesystem; + + $this->template = $template; + $this->filesystem = $phpbb_filesystem; + $this->controller_helper = $controller_helper; + + $convert = new convert($this); + } + + /** + * The function which does the actual work (or dispatches it to the relevant places) + */ + function convert_data($converter) + { + global $user, $phpbb_root_path, $phpEx, $db, $lang, $config, $cache, $auth; + global $convert, $convert_row, $message_parser, $skip_rows, $language; + global $request, $phpbb_dispatcher; + + $phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx); + extract($phpbb_config_php_file->get_all()); + + require_once($phpbb_root_path . 'includes/constants.' . $phpEx); + require_once($phpbb_root_path . 'includes/functions_convert.' . $phpEx); + + $dbms = $phpbb_config_php_file->convert_30_dbms_to_31($dbms); + + /** @var \phpbb\db\driver\driver_interface $db */ + $db = new $dbms(); + $db->sql_connect($dbhost, $dbuser, $dbpasswd, $dbname, $dbport, false, true); + unset($dbpasswd); + + // We need to fill the config to let internal functions correctly work + $config = new \phpbb\config\db($db, new \phpbb\cache\driver\dummy, CONFIG_TABLE); + + // Override a couple of config variables for the duration + $config['max_quote_depth'] = 0; + + // @todo Need to confirm that max post length in source is <= max post length in destination or there may be interesting formatting issues + $config['max_post_chars'] = $config['min_post_chars'] = 0; + + // Set up a user as well. We _should_ have enough of a database here at this point to do this + // and it helps for any core code we call + $user->session_begin(); + $user->page = $user->extract_current_page($phpbb_root_path); + + $convert->options = array(); + if (isset($config['convert_progress'])) + { + $convert->options = unserialize($config['convert_progress']); + $convert->options = array_merge($convert->options, unserialize($config['convert_db_server']), unserialize($config['convert_db_user']), unserialize($config['convert_options'])); + } + + // This information should have already been checked once, but do it again for safety + if (empty($convert->options) || empty($convert->options['tag']) || + !isset($convert->options['dbms']) || + !isset($convert->options['dbhost']) || + !isset($convert->options['dbport']) || + !isset($convert->options['dbuser']) || + !isset($convert->options['dbpasswd']) || + !isset($convert->options['dbname']) || + !isset($convert->options['table_prefix'])) + { + $this->error($user->lang['NO_CONVERT_SPECIFIED'], __LINE__, __FILE__); + } + + $this->template->assign_var('S_CONV_IN_PROGRESS', true); + + // Make some short variables accessible, for easier referencing + $convert->convertor_tag = basename($convert->options['tag']); + $convert->src_dbms = $convert->options['dbms']; + $convert->src_dbhost = $convert->options['dbhost']; + $convert->src_dbport = $convert->options['dbport']; + $convert->src_dbuser = $convert->options['dbuser']; + $convert->src_dbpasswd = $convert->options['dbpasswd']; + $convert->src_dbname = $convert->options['dbname']; + $convert->src_table_prefix = $convert->options['table_prefix']; + + // initiate database connection to old db if old and new db differ + global $src_db, $same_db; + $src_db = $same_db = null; + if ($convert->src_dbms != $dbms || $convert->src_dbhost != $dbhost || $convert->src_dbport != $dbport || $convert->src_dbname != $dbname || $convert->src_dbuser != $dbuser) + { + $dbms = $convert->src_dbms; + /** @var \phpbb\db\driver\driver $src_db */ + $src_db = new $dbms(); + $src_db->sql_connect($convert->src_dbhost, $convert->src_dbuser, htmlspecialchars_decode($convert->src_dbpasswd), $convert->src_dbname, $convert->src_dbport, false, true); + $same_db = false; + } + else + { + $src_db = $db; + $same_db = true; + } + + $convert->mysql_convert = false; + switch ($src_db->sql_layer) + { + case 'sqlite3': + $convert->src_truncate_statement = 'DELETE FROM '; + break; + + // Thanks MySQL, for silently converting... + case 'mysql': + case 'mysql4': + if (version_compare($src_db->sql_server_info(true, false), '4.1.3', '>=')) + { + $convert->mysql_convert = true; + } + $convert->src_truncate_statement = 'TRUNCATE TABLE '; + break; + + case 'mysqli': + $convert->mysql_convert = true; + $convert->src_truncate_statement = 'TRUNCATE TABLE '; + break; + + default: + $convert->src_truncate_statement = 'TRUNCATE TABLE '; + break; + } + + if ($convert->mysql_convert && !$same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + switch ($db->get_sql_layer()) + { + case 'sqlite3': + $convert->truncate_statement = 'DELETE FROM '; + break; + + default: + $convert->truncate_statement = 'TRUNCATE TABLE '; + break; + } + + $get_info = false; + + // check security implications of direct inclusion + if (!file_exists('./convertors/convert_' . $convert->convertor_tag . '.' . $phpEx)) + { + $this->error($user->lang['CONVERT_NOT_EXIST'], __LINE__, __FILE__); + } + + if (file_exists('./convertors/functions_' . $convert->convertor_tag . '.' . $phpEx)) + { + include_once('./convertors/functions_' . $convert->convertor_tag . '.' . $phpEx); + } + + $get_info = true; + include('./convertors/convert_' . $convert->convertor_tag . '.' . $phpEx); + + // Map some variables... + $convert->convertor_data = $convertor_data; + $convert->tables = $tables; + $convert->config_schema = $config_schema; + + // Now include the real data + $get_info = false; + include('./convertors/convert_' . $convert->convertor_tag . '.' . $phpEx); + + $convert->convertor_data = $convertor_data; + $convert->tables = $tables; + $convert->config_schema = $config_schema; + $convert->convertor = $convertor; + + // The test_file is a file that should be present in the location of the old board. + if (!file_exists($convert->options['forum_path'] . '/' . $test_file)) + { + $this->error(sprintf($user->lang['COULD_NOT_FIND_PATH'], $convert->options['forum_path']), __LINE__, __FILE__); + } + + $search_type = $config['search_type']; + + // For conversions we are a bit less strict and set to a search backend we know exist... + if (!class_exists($search_type)) + { + $search_type = '\phpbb\search\fulltext_native'; + $config->set('search_type', $search_type); + } + + if (!class_exists($search_type)) + { + trigger_error('NO_SUCH_SEARCH_MODULE'); + } + + $error = false; + $convert->fulltext_search = new $search_type($error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher); + + if ($error) + { + trigger_error($error); + } + + include_once($phpbb_root_path . 'includes/message_parser.' . $phpEx); + $message_parser = new \parse_message(); + + $jump = $request->variable('jump', 0); + $final_jump = $request->variable('final_jump', 0); + $sync_batch = $request->variable('sync_batch', -1); + $last_statement = $request->variable('last', 0); + + // We are running sync... + if ($sync_batch >= 0) + { + $this->sync_forums($converter, $sync_batch); + return; + } + + if ($jump) + { + $this->jump($converter, $jump, $last_statement); + return; + } + + if ($final_jump) + { + $this->final_jump($final_jump); + return; + } + + $current_table = $request->variable('current_table', 0); + $old_current_table = min(-1, $current_table - 1); + $skip_rows = $request->variable('skip_rows', 0); + + if (!$current_table && !$skip_rows) + { + if (!$request->variable('confirm', false)) + { + // If avatars / ranks / smilies folders are specified make sure they are writable + $bad_folders = array(); + + $local_paths = array( + 'avatar_path' => path($config['avatar_path']), + 'avatar_gallery_path' => path($config['avatar_gallery_path']), + 'icons_path' => path($config['icons_path']), + 'ranks_path' => path($config['ranks_path']), + 'smilies_path' => path($config['smilies_path']) + ); + + foreach ($local_paths as $folder => $local_path) + { + if (isset($convert->convertor[$folder])) + { + if (empty($convert->convertor['test_file'])) + { + // test_file is mandantory at the moment so this should never be reached, but just in case... + $this->error($user->lang['DEV_NO_TEST_FILE'], __LINE__, __FILE__); + } + + if (!$local_path || !$this->filesystem->is_writable($phpbb_root_path . $local_path)) + { + if (!$local_path) + { + $bad_folders[] = sprintf($user->lang['CONFIG_PHPBB_EMPTY'], $folder); + } + else + { + $bad_folders[] = $local_path; + } + } + } + } + + if (count($bad_folders)) + { + $msg = (count($bad_folders) == 1) ? $user->lang['MAKE_FOLDER_WRITABLE'] : $user->lang['MAKE_FOLDERS_WRITABLE']; + sort($bad_folders); + $this->error(sprintf($msg, implode('
', $bad_folders)), __LINE__, __FILE__, true); + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['INSTALL_TEST'], + 'U_ACTION' => $this->controller_helper->route('phpbb_convert_convert', array('converter' => $converter)), + )); + return; + } + + // Grab all the tables used in convertor + $missing_tables = $tables_list = $aliases = array(); + + foreach ($convert->convertor['schema'] as $schema) + { + // Skip those not used (because of addons/plugins not detected) + if (!$schema['target']) + { + continue; + } + + foreach ($schema as $key => $val) + { + // we're dealing with an array like: + // array('forum_status', 'forums.forum_status', 'is_item_locked') + if (is_int($key) && !empty($val[1])) + { + $temp_data = $val[1]; + if (!is_array($temp_data)) + { + $temp_data = array($temp_data); + } + + foreach ($temp_data as $value) + { + if (preg_match('/([a-z0-9_]+)\.([a-z0-9_]+)\)* ?A?S? ?([a-z0-9_]*?)\.?([a-z0-9_]*)$/i', $value, $m)) + { + $table = $convert->src_table_prefix . $m[1]; + $tables_list[$table] = $table; + + if (!empty($m[3])) + { + $aliases[] = $convert->src_table_prefix . $m[3]; + } + } + } + } + // 'left_join' => 'topics LEFT JOIN vote_desc ON topics.topic_id = vote_desc.topic_id AND topics.topic_vote = 1' + else if ($key == 'left_join') + { + // Convert the value if it wasn't an array already. + if (!is_array($val)) + { + $val = array($val); + } + + for ($j = 0, $size = count($val); $j < $size; ++$j) + { + if (preg_match('/LEFT JOIN ([a-z0-9_]+) AS ([a-z0-9_]+)/i', $val[$j], $m)) + { + $table = $convert->src_table_prefix . $m[1]; + $tables_list[$table] = $table; + + if (!empty($m[2])) + { + $aliases[] = $convert->src_table_prefix . $m[2]; + } + } + } + } + } + } + + // Remove aliased tables from $tables_list + foreach ($aliases as $alias) + { + unset($tables_list[$alias]); + } + + // Check if the tables that we need exist + $src_db->sql_return_on_error(true); + foreach ($tables_list as $table => $null) + { + $sql = 'SELECT 1 FROM ' . $table; + $_result = $src_db->sql_query_limit($sql, 1); + + if (!$_result) + { + $missing_tables[] = $table; + } + $src_db->sql_freeresult($_result); + } + $src_db->sql_return_on_error(false); + + // Throw an error if some tables are missing + // We used to do some guessing here, but since we have a suggestion of possible values earlier, I don't see it adding anything here to do it again + + if (count($missing_tables) == count($tables_list)) + { + $this->error($user->lang['NO_TABLES_FOUND'] . ' ' . $user->lang['CHECK_TABLE_PREFIX'], __LINE__, __FILE__); + } + else if (count($missing_tables)) + { + $this->error(sprintf($user->lang['TABLES_MISSING'], implode($user->lang['COMMA_SEPARATOR'], $missing_tables)) . '

' . $user->lang['CHECK_TABLE_PREFIX'], __LINE__, __FILE__); + } + + $url = $this->save_convert_progress($converter, 'confirm=1'); + $msg = $user->lang['PRE_CONVERT_COMPLETE']; + + if ($convert->convertor_data['author_notes']) + { + $msg .= '

' . sprintf($user->lang['AUTHOR_NOTES'], $convert->convertor_data['author_notes']); + } + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['CONTINUE_CONVERT'], + 'BODY' => $msg, + 'U_ACTION' => $url, + )); + + return; + } // if (!$request->variable('confirm', false))) + + $this->template->assign_block_vars('checks', array( + 'S_LEGEND' => true, + 'LEGEND' => $user->lang['STARTING_CONVERT'], + )); + + // Convert the config table and load the settings of the old board + if (!empty($convert->config_schema)) + { + restore_config($convert->config_schema); + + // Override a couple of config variables for the duration + $config['max_quote_depth'] = 0; + + // @todo Need to confirm that max post length in source is <= max post length in destination or there may be interesting formatting issues + $config['max_post_chars'] = $config['min_post_chars'] = 0; + } + + $this->template->assign_block_vars('checks', array( + 'TITLE' => $user->lang['CONFIG_CONVERT'], + 'RESULT' => $user->lang['DONE'], + )); + + // Now process queries and execute functions that have to be executed prior to the conversion + if (!empty($convert->convertor['execute_first'])) + { + // @codingStandardsIgnoreStart + eval($convert->convertor['execute_first']); + // @codingStandardsIgnoreEnd + } + + if (!empty($convert->convertor['query_first'])) + { + if (!is_array($convert->convertor['query_first'])) + { + $convert->convertor['query_first'] = array('target', array($convert->convertor['query_first'])); + } + else if (!is_array($convert->convertor['query_first'][0])) + { + $convert->convertor['query_first'] = array(array($convert->convertor['query_first'][0], $convert->convertor['query_first'][1])); + } + + foreach ($convert->convertor['query_first'] as $query_first) + { + if ($query_first[0] == 'src') + { + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + $src_db->sql_query($query_first[1]); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + } + else + { + $db->sql_query($query_first[1]); + } + } + } + + $this->template->assign_block_vars('checks', array( + 'TITLE' => $user->lang['PREPROCESS_STEP'], + 'RESULT' => $user->lang['DONE'], + )); + } // if (!$current_table && !$skip_rows) + + $this->template->assign_block_vars('checks', array( + 'S_LEGEND' => true, + 'LEGEND' => $user->lang['FILLING_TABLES'], + )); + + // This loop takes one target table and processes it + while ($current_table < count($convert->convertor['schema'])) + { + $schema = $convert->convertor['schema'][$current_table]; + + // The target table isn't set, this can be because a module (for example the attachement mod) is taking care of this. + if (empty($schema['target'])) + { + $current_table++; + continue; + } + + $this->template->assign_block_vars('checks', array( + 'TITLE' => sprintf($user->lang['FILLING_TABLE'], $schema['target']), + )); + + // This is only the case when we first start working on the tables. + if (!$skip_rows) + { + // process execute_first and query_first for this table... + if (!empty($schema['execute_first'])) + { + // @codingStandardsIgnoreStart + eval($schema['execute_first']); + // @codingStandardsIgnoreEnd + } + + if (!empty($schema['query_first'])) + { + if (!is_array($schema['query_first'])) + { + $schema['query_first'] = array('target', array($schema['query_first'])); + } + else if (!is_array($schema['query_first'][0])) + { + $schema['query_first'] = array(array($schema['query_first'][0], $schema['query_first'][1])); + } + + foreach ($schema['query_first'] as $query_first) + { + if ($query_first[0] == 'src') + { + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + $src_db->sql_query($query_first[1]); + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + } + else + { + $db->sql_query($query_first[1]); + } + } + } + + if (!empty($schema['autoincrement'])) + { + switch ($db->get_sql_layer()) + { + case 'postgres': + $db->sql_query("SELECT SETVAL('" . $schema['target'] . "_seq',(select case when max(" . $schema['autoincrement'] . ")>0 then max(" . $schema['autoincrement'] . ")+1 else 1 end from " . $schema['target'] . '));'); + break; + + case 'oracle': + $result = $db->sql_query('SELECT MAX(' . $schema['autoincrement'] . ') as max_id FROM ' . $schema['target']); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $largest_id = (int) $row['max_id']; + + if ($largest_id) + { + $db->sql_query('DROP SEQUENCE ' . $schema['target'] . '_seq'); + $db->sql_query('CREATE SEQUENCE ' . $schema['target'] . '_seq START WITH ' . ($largest_id + 1)); + } + break; + } + } + } + + // Process execute_always for this table + // This is for code which needs to be executed on every pass of this table if + // it gets split because of time restrictions + if (!empty($schema['execute_always'])) + { + // @codingStandardsIgnoreStart + eval($schema['execute_always']); + // @codingStandardsIgnoreEnd + } + + // + // Set up some variables + // + // $waiting_rows holds rows for multirows insertion (MySQL only) + // $src_tables holds unique tables with aliases to select from + // $src_fields will quickly refer source fields (or aliases) corresponding to the current index + // $select_fields holds the names of the fields to retrieve + // + + $sql_data = array( + 'source_fields' => array(), + 'target_fields' => array(), + 'source_tables' => array(), + 'select_fields' => array(), + ); + + // This statement is building the keys for later insertion. + $insert_query = $this->build_insert_query($schema, $sql_data, $current_table); + + // If no source table is affected, we skip the table + if (empty($sql_data['source_tables'])) + { + $skip_rows = 0; + $current_table++; + continue; + } + + $distinct = (!empty($schema['distinct'])) ? 'DISTINCT ' : ''; + + $sql = 'SELECT ' . $distinct . implode(', ', $sql_data['select_fields']) . " \nFROM " . implode(', ', $sql_data['source_tables']); + + // Where + $sql .= (!empty($schema['where'])) ? "\nWHERE (" . $schema['where'] . ')' : ''; + + // Group By + if (!empty($schema['group_by'])) + { + $schema['group_by'] = array($schema['group_by']); + foreach ($sql_data['select_fields'] as $select) + { + $alias = strpos(strtolower($select), ' as '); + $select = ($alias) ? substr($select, 0, $alias) : $select; + if (!in_array($select, $schema['group_by'])) + { + $schema['group_by'][] = $select; + } + } + } + $sql .= (!empty($schema['group_by'])) ? "\nGROUP BY " . implode(', ', $schema['group_by']) : ''; + + // Having + $sql .= (!empty($schema['having'])) ? "\nHAVING " . $schema['having'] : ''; + + // Order By + if (empty($schema['order_by']) && !empty($schema['primary'])) + { + $schema['order_by'] = $schema['primary']; + } + $sql .= (!empty($schema['order_by'])) ? "\nORDER BY " . $schema['order_by'] : ''; + + // Counting basically holds the amount of rows processed. + $counting = -1; + $batch_time = 0; + + while ($counting === -1 || ($counting >= $convert->batch_size && still_on_time())) + { + $old_current_table = $current_table; + + $rows = ''; + $waiting_rows = array(); + + if (!empty($batch_time)) + { + $mtime = explode(' ', microtime()); + $mtime = $mtime[0] + $mtime[1]; + $rows = ceil($counting/($mtime - $batch_time)) . " rows/s ($counting rows) | "; + } + + $this->template->assign_block_vars('checks', array( + 'TITLE' => "skip_rows = $skip_rows", + 'RESULT' => $rows . ((defined('DEBUG') && function_exists('memory_get_usage')) ? ceil(memory_get_usage()/1024) . ' ' . $user->lang['KIB'] : ''), + )); + + $mtime = explode(' ', microtime()); + $batch_time = $mtime[0] + $mtime[1]; + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + // Take skip rows into account and only fetch batch_size amount of rows + $___result = $src_db->sql_query_limit($sql, $convert->batch_size, $skip_rows); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + + // This loop processes each row + $counting = 0; + + $convert->row = $convert_row = array(); + + if (!empty($schema['autoincrement'])) + { + switch ($db->get_sql_layer()) + { + case 'mssql_odbc': + case 'mssqlnative': + $db->sql_query('SET IDENTITY_INSERT ' . $schema['target'] . ' ON'); + break; + } + } + + // Now handle the rows until time is over or no more rows to process... + while ($counting === 0 || still_on_time()) + { + $convert_row = $src_db->sql_fetchrow($___result); + + if (!$convert_row) + { + // move to the next batch or table + break; + } + + // With this we are able to always save the last state + $convert->row = $convert_row; + + // Increment the counting variable, it stores the number of rows we have processed + $counting++; + + $insert_values = array(); + + $sql_flag = $this->process_row($schema, $sql_data, $insert_values); + + if ($sql_flag === true) + { + switch ($db->get_sql_layer()) + { + // If MySQL, we'll wait to have num_wait_rows rows to submit at once + case 'mysql': + case 'mysql4': + case 'mysqli': + $waiting_rows[] = '(' . implode(', ', $insert_values) . ')'; + + if (count($waiting_rows) >= $convert->num_wait_rows) + { + $errored = false; + + $db->sql_return_on_error(true); + + if (!$db->sql_query($insert_query . implode(', ', $waiting_rows))) + { + $errored = true; + } + $db->sql_return_on_error(false); + + if ($errored) + { + $db->sql_return_on_error(true); + + // Because it errored out we will try to insert the rows one by one... most of the time this + // is caused by duplicate entries - but we also do not want to miss one... + foreach ($waiting_rows as $waiting_sql) + { + if (!$db->sql_query($insert_query . $waiting_sql)) + { + $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_query . $waiting_sql) . '

' . htmlspecialchars(print_r($db->_sql_error(), true)), __LINE__, __FILE__, true); + } + } + + $db->sql_return_on_error(false); + } + + $waiting_rows = array(); + } + + break; + + default: + $insert_sql = $insert_query . '(' . implode(', ', $insert_values) . ')'; + + $db->sql_return_on_error(true); + + if (!$db->sql_query($insert_sql)) + { + $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_sql) . '

' . htmlspecialchars(print_r($db->_sql_error(), true)), __LINE__, __FILE__, true); + } + $db->sql_return_on_error(false); + + $waiting_rows = array(); + + break; + } + } + + $skip_rows++; + } + $src_db->sql_freeresult($___result); + + // We might still have some rows waiting + if (count($waiting_rows)) + { + $errored = false; + $db->sql_return_on_error(true); + + if (!$db->sql_query($insert_query . implode(', ', $waiting_rows))) + { + $errored = true; + } + $db->sql_return_on_error(false); + + if ($errored) + { + $db->sql_return_on_error(true); + + // Because it errored out we will try to insert the rows one by one... most of the time this + // is caused by duplicate entries - but we also do not want to miss one... + foreach ($waiting_rows as $waiting_sql) + { + $db->sql_query($insert_query . $waiting_sql); + $this->db_error($user->lang['DB_ERR_INSERT'], htmlspecialchars($insert_query . $waiting_sql) . '

' . htmlspecialchars(print_r($db->_sql_error(), true)), __LINE__, __FILE__, true); + } + + $db->sql_return_on_error(false); + } + + $waiting_rows = array(); + } + + if (!empty($schema['autoincrement'])) + { + switch ($db->get_sql_layer()) + { + case 'mssql_odbc': + case 'mssqlnative': + $db->sql_query('SET IDENTITY_INSERT ' . $schema['target'] . ' OFF'); + break; + + case 'postgres': + $db->sql_query("SELECT SETVAL('" . $schema['target'] . "_seq',(select case when max(" . $schema['autoincrement'] . ")>0 then max(" . $schema['autoincrement'] . ")+1 else 1 end from " . $schema['target'] . '));'); + break; + + case 'oracle': + $result = $db->sql_query('SELECT MAX(' . $schema['autoincrement'] . ') as max_id FROM ' . $schema['target']); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $largest_id = (int) $row['max_id']; + + if ($largest_id) + { + $db->sql_query('DROP SEQUENCE ' . $schema['target'] . '_seq'); + $db->sql_query('CREATE SEQUENCE ' . $schema['target'] . '_seq START WITH ' . ($largest_id + 1)); + } + break; + } + } + } + + // When we reach this point, either the current table has been processed or we're running out of time. + if (still_on_time() && $counting < $convert->batch_size/* && !defined('DEBUG')*/) + { + $skip_rows = 0; + $current_table++; + } + else + {/* + if (still_on_time() && $counting < $convert->batch_size) + { + $skip_rows = 0; + $current_table++; + }*/ + + // Looks like we ran out of time. + $url = $this->save_convert_progress($converter, 'current_table=' . $current_table . '&skip_rows=' . $skip_rows); + + $current_table++; +// $percentage = ($skip_rows == 0) ? 0 : floor(100 / ($total_rows / $skip_rows)); + + $msg = sprintf($user->lang['STEP_PERCENT_COMPLETED'], $current_table, count($convert->convertor['schema'])); + + $this->template->assign_vars(array( + 'BODY' => $msg, + 'L_SUBMIT' => $user->lang['CONTINUE_CONVERT'], + 'U_ACTION' => $url, + )); + + $this->meta_refresh($url); + return; + } + } + + // Process execute_last then we'll be done + $url = $this->save_convert_progress($converter, 'jump=1'); + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['FINAL_STEP'], + 'U_ACTION' => $url, + )); + + $this->meta_refresh($url); + return; + } + + /** + * Sync function being executed at the middle, some functions need to be executed after a successful sync. + */ + function sync_forums($converter, $sync_batch) + { + global $user, $db, $phpbb_root_path, $phpEx, $config, $cache; + global $convert; + + include_once ($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + + $this->template->assign_block_vars('checks', array( + 'S_LEGEND' => true, + 'LEGEND' => $user->lang['SYNC_TOPICS'], + )); + + $batch_size = $convert->batch_size; + + $sql = 'SELECT MIN(topic_id) as min_value, MAX(topic_id) AS max_value + FROM ' . TOPICS_TABLE; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Set values of minimum/maximum primary value for this table. + $primary_min = $row['min_value']; + $primary_max = $row['max_value']; + + if ($sync_batch == 0) + { + $sync_batch = (int) $primary_min; + } + + if ($sync_batch == 0) + { + $sync_batch = 1; + } + + // Fetch a batch of rows, process and insert them. + while ($sync_batch <= $primary_max && still_on_time()) + { + $end = ($sync_batch + $batch_size - 1); + + // Sync all topics in batch mode... + sync('topic', 'range', 'topic_id BETWEEN ' . $sync_batch . ' AND ' . $end, true, true); + + $this->template->assign_block_vars('checks', array( + 'TITLE' => sprintf($user->lang['SYNC_TOPIC_ID'], $sync_batch, ($sync_batch + $batch_size)) . ((defined('DEBUG') && function_exists('memory_get_usage')) ? ' [' . ceil(memory_get_usage()/1024) . ' ' . $user->lang['KIB'] . ']' : ''), + 'RESULT' => $user->lang['DONE'], + )); + + $sync_batch += $batch_size; + } + + if ($sync_batch >= $primary_max) + { + $url = $this->save_convert_progress($converter, 'final_jump=1'); + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['CONTINUE_CONVERT'], + 'U_ACTION' => $url, + )); + + $this->meta_refresh($url); + return; + } + else + { + $sync_batch--; + } + + $url = $this->save_convert_progress($converter, 'sync_batch=' . $sync_batch); + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['CONTINUE_CONVERT'], + 'U_ACTION' => $url, + )); + + $this->meta_refresh($url); + return; + } + + /** + * Save the convertor status + */ + function save_convert_progress($convertor_tag, $step) + { + global $config, $convert, $language; + + // Save convertor Status + $config->set('convert_progress', serialize(array( + 'step' => $step, + 'table_prefix' => $convert->src_table_prefix, + 'tag' => $convert->convertor_tag, + )), false); + + $config->set('convert_db_server', serialize(array( + 'dbms' => $convert->src_dbms, + 'dbhost' => $convert->src_dbhost, + 'dbport' => $convert->src_dbport, + 'dbname' => $convert->src_dbname, + )), false); + + $config->set('convert_db_user', serialize(array( + 'dbuser' => $convert->src_dbuser, + 'dbpasswd' => $convert->src_dbpasswd, + )), false); + + return $this->controller_helper->route('phpbb_convert_convert', array('converter' => $convertor_tag)) . '?' . $step; + } + + /** + * Finish conversion, the last function to be called. + */ + function finish_conversion() + { + global $db, $phpbb_root_path, $phpEx, $convert, $config, $language, $user; + global $cache, $auth, $phpbb_container, $phpbb_log; + + include_once ($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + + $db->sql_query('DELETE FROM ' . CONFIG_TABLE . " + WHERE config_name = 'convert_progress' + OR config_name = 'convert_options' + OR config_name = 'convert_db_server' + OR config_name = 'convert_db_user'"); + $db->sql_query('DELETE FROM ' . SESSIONS_TABLE); + + @unlink($phpbb_container->getParameter('core.cache_dir') . 'data_global.' . $phpEx); + phpbb_cache_moderators($db, $cache, $auth); + + // And finally, add a note to the log + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_INSTALL_CONVERTED', false, array($convert->convertor_data['forum_name'], $config['version'])); + + $url = $this->controller_helper->route('phpbb_convert_finish'); + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['FINAL_STEP'], + 'U_ACTION' => $url, + )); + + $this->meta_refresh($url); + return; + } + + /** + * This function marks the steps after syncing + */ + function final_jump($final_jump) + { + global $user, $src_db, $same_db, $db, $phpbb_root_path, $phpEx, $config, $cache; + global $convert; + + $this->template->assign_block_vars('checks', array( + 'S_LEGEND' => true, + 'LEGEND' => $user->lang['PROCESS_LAST'], + )); + + if ($final_jump == 1) + { + $db->sql_return_on_error(true); + + update_topics_posted(); + + $this->template->assign_block_vars('checks', array( + 'TITLE' => $user->lang['UPDATE_TOPICS_POSTED'], + 'RESULT' => $user->lang['DONE'], + )); + + if ($db->get_sql_error_triggered()) + { + $this->template->assign_vars(array( + 'S_ERROR_BOX' => true, + 'ERROR_TITLE' => $user->lang['UPDATE_TOPICS_POSTED'], + 'ERROR_MSG' => $user->lang['UPDATE_TOPICS_POSTED_ERR'], + )); + } + $db->sql_return_on_error(false); + + $this->finish_conversion(); + return; + } + } + + /** + * This function marks the steps before syncing (jump=1) + */ + function jump($converter, $jump, $last_statement) + { + /** @var \phpbb\db\driver\driver_interface $src_db */ + /** @var \phpbb\cache\driver\driver_interface $cache */ + global $user, $src_db, $same_db, $db, $phpbb_root_path, $phpEx, $config, $cache; + global $convert; + + include_once ($phpbb_root_path . 'includes/functions_admin.' . $phpEx); + + $this->template->assign_block_vars('checks', array( + 'S_LEGEND' => true, + 'LEGEND' => $user->lang['PROCESS_LAST'], + )); + + if ($jump == 1) + { + // Execute 'last' statements/queries + if (!empty($convert->convertor['execute_last'])) + { + if (!is_array($convert->convertor['execute_last'])) + { + // @codingStandardsIgnoreStart + eval($convert->convertor['execute_last']); + // @codingStandardsIgnoreEnd + } + else + { + while ($last_statement < count($convert->convertor['execute_last'])) + { + // @codingStandardsIgnoreStart + eval($convert->convertor['execute_last'][$last_statement]); + // @codingStandardsIgnoreEnd + + $this->template->assign_block_vars('checks', array( + 'TITLE' => $convert->convertor['execute_last'][$last_statement], + 'RESULT' => $user->lang['DONE'], + )); + + $last_statement++; + $url = $this->save_convert_progress($converter, 'jump=1&last=' . $last_statement); + + $percentage = ($last_statement == 0) ? 0 : floor(100 / (count($convert->convertor['execute_last']) / $last_statement)); + $msg = sprintf($user->lang['STEP_PERCENT_COMPLETED'], $last_statement, count($convert->convertor['execute_last']), $percentage); + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['CONTINUE_LAST'], + 'BODY' => $msg, + 'U_ACTION' => $url, + )); + + $this->meta_refresh($url); + return; + } + } + } + + if (!empty($convert->convertor['query_last'])) + { + if (!is_array($convert->convertor['query_last'])) + { + $convert->convertor['query_last'] = array('target', array($convert->convertor['query_last'])); + } + else if (!is_array($convert->convertor['query_last'][0])) + { + $convert->convertor['query_last'] = array(array($convert->convertor['query_last'][0], $convert->convertor['query_last'][1])); + } + + foreach ($convert->convertor['query_last'] as $query_last) + { + if ($query_last[0] == 'src') + { + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + $src_db->sql_query($query_last[1]); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + } + else + { + $db->sql_query($query_last[1]); + } + } + } + + // Sanity check + $db->sql_return_on_error(false); + $src_db->sql_return_on_error(false); + + fix_empty_primary_groups(); + + $sql = 'SELECT MIN(user_regdate) AS board_startdate + FROM ' . USERS_TABLE; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!isset($config['board_startdate']) || ($row['board_startdate'] < $config['board_startdate'] && $row['board_startdate'] > 0)) + { + $config->set('board_startdate', $row['board_startdate']); + $db->sql_query('UPDATE ' . USERS_TABLE . ' SET user_regdate = ' . $row['board_startdate'] . ' WHERE user_id = ' . ANONYMOUS); + } + + update_dynamic_config(); + + $this->template->assign_block_vars('checks', array( + 'TITLE' => $user->lang['CLEAN_VERIFY'], + 'RESULT' => $user->lang['DONE'], + )); + + $url = $this->save_convert_progress($converter, 'jump=2'); + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['CONTINUE_CONVERT'], + 'U_ACTION' => $url, + )); + + $this->meta_refresh($url); + return; + } + + if ($jump == 2) + { + $db->sql_query('UPDATE ' . USERS_TABLE . " SET user_permissions = ''"); + + // TODO: sync() is likely going to bomb out on forums with a considerable amount of topics. + // TODO: the sync function is able to handle FROM-TO values, we should use them here (batch processing) + sync('forum', '', '', false, true); + $cache->destroy('sql', FORUMS_TABLE); + + $this->template->assign_block_vars('checks', array( + 'TITLE' => $user->lang['SYNC_FORUMS'], + 'RESULT' => $user->lang['DONE'], + )); + + // Continue with synchronizing the forums... + $url = $this->save_convert_progress($converter, 'sync_batch=0'); + + $this->template->assign_vars(array( + 'L_SUBMIT' => $user->lang['CONTINUE_CONVERT'], + 'U_ACTION' => $url, + )); + + $this->meta_refresh($url); + return; + } + } + + function build_insert_query(&$schema, &$sql_data, $current_table) + { + global $db, $user; + global $convert; + + // Can we use IGNORE with this DBMS? + $sql_ignore = (strpos($db->get_sql_layer(), 'mysql') === 0 && !defined('DEBUG')) ? 'IGNORE ' : ''; + $insert_query = 'INSERT ' . $sql_ignore . 'INTO ' . $schema['target'] . ' ('; + + $aliases = array(); + + $sql_data = array( + 'source_fields' => array(), + 'target_fields' => array(), + 'source_tables' => array(), + 'select_fields' => array(), + ); + + foreach ($schema as $key => $val) + { + // Example: array('group_name', 'extension_groups.group_name', 'htmlspecialchars'), + if (is_int($key)) + { + if (!empty($val[0])) + { + // Target fields + $sql_data['target_fields'][$val[0]] = $key; + $insert_query .= $val[0] . ', '; + } + + if (!is_array($val[1])) + { + $val[1] = array($val[1]); + } + + foreach ($val[1] as $valkey => $value_1) + { + // This should cover about any case: + // + // table.field => SELECT table.field FROM table + // table.field AS alias => SELECT table.field AS alias FROM table + // table.field AS table2.alias => SELECT table2.field AS alias FROM table table2 + // table.field AS table2.field => SELECT table2.field FROM table table2 + // + if (preg_match('/^([a-z0-9_]+)\.([a-z0-9_]+)( +AS +(([a-z0-9_]+?)\.)?([a-z0-9_]+))?$/i', $value_1, $m)) + { + // There is 'AS ...' in the field names + if (!empty($m[3])) + { + $value_1 = ($m[2] == $m[6]) ? $m[1] . '.' . $m[2] : $m[1] . '.' . $m[2] . ' AS ' . $m[6]; + + // Table alias: store it then replace the source table with it + if (!empty($m[5]) && $m[5] != $m[1]) + { + $aliases[$m[5]] = $m[1]; + $value_1 = str_replace($m[1] . '.' . $m[2], $m[5] . '.' . $m[2], $value_1); + } + } + else + { + // No table alias + $sql_data['source_tables'][$m[1]] = (empty($convert->src_table_prefix)) ? $m[1] : $convert->src_table_prefix . $m[1] . ' ' . $m[1]; + } + + $sql_data['select_fields'][$value_1] = $value_1; + $sql_data['source_fields'][$key][$valkey] = (!empty($m[6])) ? $m[6] : $m[2]; + } + } + } + else if ($key == 'where' || $key == 'group_by' || $key == 'order_by' || $key == 'having') + { + if (@preg_match_all('/([a-z0-9_]+)\.([a-z0-9_]+)/i', $val, $m)) + { + foreach ($m[1] as $value) + { + $sql_data['source_tables'][$value] = (empty($convert->src_table_prefix)) ? $value : $convert->src_table_prefix . $value . ' ' . $value; + } + } + } + } + + // Add the aliases to the list of tables + foreach ($aliases as $alias => $table) + { + $sql_data['source_tables'][$alias] = $convert->src_table_prefix . $table . ' ' . $alias; + } + + // 'left_join' => 'forums LEFT JOIN forum_prune ON forums.forum_id = forum_prune.forum_id', + if (!empty($schema['left_join'])) + { + if (!is_array($schema['left_join'])) + { + $schema['left_join'] = array($schema['left_join']); + } + + foreach ($schema['left_join'] as $left_join) + { + // This won't handle concatened LEFT JOINs + if (!preg_match('/([a-z0-9_]+) LEFT JOIN ([a-z0-9_]+) A?S? ?([a-z0-9_]*?) ?(ON|USING)(.*)/i', $left_join, $m)) + { + $this->error(sprintf($user->lang['NOT_UNDERSTAND'], 'LEFT JOIN', $left_join, $current_table, $schema['target']), __LINE__, __FILE__); + } + + if (!empty($aliases[$m[2]])) + { + if (!empty($m[3])) + { + $this->error(sprintf($user->lang['NAMING_CONFLICT'], $m[2], $m[3], $schema['left_join']), __LINE__, __FILE__); + } + + $m[2] = $aliases[$m[2]]; + $m[3] = $m[2]; + } + + $right_table = $convert->src_table_prefix . $m[2]; + if (!empty($m[3])) + { + unset($sql_data['source_tables'][$m[3]]); + } + else if ($m[2] != $m[1]) + { + unset($sql_data['source_tables'][$m[2]]); + } + + if (strpos($sql_data['source_tables'][$m[1]], "\nLEFT JOIN") !== false) + { + $sql_data['source_tables'][$m[1]] = '(' . $sql_data['source_tables'][$m[1]] . ")\nLEFT JOIN $right_table"; + } + else + { + $sql_data['source_tables'][$m[1]] .= "\nLEFT JOIN $right_table"; + } + + if (!empty($m[3])) + { + unset($sql_data['source_tables'][$m[3]]); + $sql_data['source_tables'][$m[1]] .= ' AS ' . $m[3]; + } + else if (!empty($convert->src_table_prefix)) + { + $sql_data['source_tables'][$m[1]] .= ' AS ' . $m[2]; + } + $sql_data['source_tables'][$m[1]] .= ' ' . $m[4] . $m[5]; + } + } + + // Remove ", " from the end of the insert query + $insert_query = substr($insert_query, 0, -2) . ') VALUES '; + + return $insert_query; + } + + /** + * Function for processing the currently handled row + */ + function process_row(&$schema, &$sql_data, &$insert_values) + { + global $user, $phpbb_root_path, $phpEx, $db, $lang, $config, $cache; + global $convert, $convert_row; + + $sql_flag = false; + + foreach ($schema as $key => $fields) + { + // We are only interested in the lines with: + // array('comment', 'attachments_desc.comment', 'htmlspecialchars'), + if (is_int($key)) + { + if (!is_array($fields[1])) + { + $fields[1] = array($fields[1]); + } + + $firstkey_set = false; + $firstkey = 0; + + foreach ($fields[1] as $inner_key => $inner_value) + { + if (!$firstkey_set) + { + $firstkey = $inner_key; + $firstkey_set = true; + } + + $src_field = isset($sql_data['source_fields'][$key][$inner_key]) ? $sql_data['source_fields'][$key][$inner_key] : ''; + + if (!empty($src_field)) + { + $fields[1][$inner_key] = $convert->row[$src_field]; + } + } + + if (!empty($fields[0])) + { + // We have a target field, if we haven't set $sql_flag yet it will be set to TRUE. + // If a function has already set it to FALSE it won't change it. + if ($sql_flag === false) + { + $sql_flag = true; + } + + // No function assigned? + if (empty($fields[2])) + { + $value = $fields[1][$firstkey]; + } + else if (is_array($fields[2]) && !is_callable($fields[2])) + { + // Execute complex function/eval/typecast + $value = $fields[1]; + + foreach ($fields[2] as $type => $execution) + { + if (strpos($type, 'typecast') === 0) + { + if (!is_array($value)) + { + $value = array($value); + } + $value = $value[0]; + settype($value, $execution); + } + else if (strpos($type, 'function') === 0) + { + if (!is_array($value)) + { + $value = array($value); + } + + $value = call_user_func_array($execution, $value); + } + else if (strpos($type, 'execute') === 0) + { + if (!is_array($value)) + { + $value = array($value); + } + + $execution = str_replace('{RESULT}', '$value', $execution); + $execution = str_replace('{VALUE}', '$value', $execution); + // @codingStandardsIgnoreStart + eval($execution); + // @codingStandardsIgnoreEnd + } + } + } + else + { + $value = call_user_func_array($fields[2], $fields[1]); + } + + if (is_null($value)) + { + $value = ''; + } + + $insert_values[] = $db->_sql_validate_value($value); + } + else if (!empty($fields[2])) + { + if (is_array($fields[2])) + { + // Execute complex function/eval/typecast + $value = ''; + + foreach ($fields[2] as $type => $execution) + { + if (strpos($type, 'typecast') === 0) + { + $value = settype($value, $execution); + } + else if (strpos($type, 'function') === 0) + { + if (!is_array($value)) + { + $value = array($value); + } + + $value = call_user_func_array($execution, $value); + } + else if (strpos($type, 'execute') === 0) + { + if (!is_array($value)) + { + $value = array($value); + } + + $execution = str_replace('{RESULT}', '$value', $execution); + $execution = str_replace('{VALUE}', '$value', $execution); + // @codingStandardsIgnoreStart + eval($execution); + // @codingStandardsIgnoreEnd + } + } + } + else + { + call_user_func_array($fields[2], $fields[1]); + } + } + } + } + + return $sql_flag; + } + + /** + * Own meta refresh function to be able to change the global time used + */ + function meta_refresh($url) + { + global $convert; + + if ($convert->options['refresh']) + { + // Because we should not rely on correct settings, we simply use the relative path here directly. + $this->template->assign_vars(array( + 'S_REFRESH' => true, + 'META' => '') + ); + } + } + + /** + * Error handler function + * + * This function needs to be kept for BC + * + * @param $error + * @param $line + * @param $file + * @param bool|false $skip + */ + public function error($error, $line, $file, $skip = false) + { + $this->template->assign_block_vars('errors', array( + 'TITLE' => $error, + 'DESCRIPTION' => 'In ' . $file . ' on line ' . $line, + )); + } + + /** + * Database error handler function + * + * This function needs to be kept for BC + * + * @param $error + * @param $sql + * @param $line + * @param $file + * @param bool|false $skip + */ + public function db_error($error, $sql, $line, $file, $skip = false) + { + $this->template->assign_block_vars('errors', array( + 'TITLE' => $error, + 'DESCRIPTION' => 'In ' . $file . ' on line ' . $line . '

SQL: ' . $sql, + )); + } +} diff --git a/install/convertors/convert_phpbb20.php b/install/convertors/convert_phpbb20.php new file mode 100644 index 0000000..230b999 --- /dev/null +++ b/install/convertors/convert_phpbb20.php @@ -0,0 +1,974 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* NOTE to potential convertor authors. Please use this file to get +* familiar with the structure since we added some bare explanations here. +* +* Since this file gets included more than once on one page you are not able to add functions to it. +* Instead use a functions_ file. +* +* @ignore +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +$phpbb_config_php_file = new \phpbb\config_php_file($phpbb_root_path, $phpEx); +extract($phpbb_config_php_file->get_all()); +unset($dbpasswd); + +$dbms = $phpbb_config_php_file->convert_30_dbms_to_31($dbms); + +/** +* $convertor_data provides some basic information about this convertor which is +* used on the initial list of convertors and to populate the default settings +*/ +$convertor_data = array( + 'forum_name' => 'phpBB 2.0.x', + 'version' => '1.0.3', + 'phpbb_version' => '3.2.7', + 'author' => 'phpBB Limited', + 'dbms' => $dbms, + 'dbhost' => $dbhost, + 'dbport' => $dbport, + 'dbuser' => $dbuser, + 'dbpasswd' => '', + 'dbname' => $dbname, + 'table_prefix' => 'phpbb_', + 'forum_path' => '../forums', + 'author_notes' => '', +); + +/** +* $tables is a list of the tables (minus prefix) which we expect to find in the +* source forum. It is used to guess the prefix if the specified prefix is incorrect +*/ +$tables = array( + 'auth_access', + 'banlist', + 'categories', + 'disallow', + 'forum_prune', + 'forums', + 'groups', + 'posts', + 'posts_text', + 'privmsgs', + 'privmsgs_text', + 'ranks', + 'smilies', + 'topics', + 'topics_watch', + 'user_group', + 'users', + 'vote_desc', + 'vote_results', + 'vote_voters', + 'words' +); + +/** +* $config_schema details how the board configuration information is stored in the source forum. +* +* 'table_format' can take the value 'file' to indicate a config file. In this case array_name +* is set to indicate the name of the array the config values are stored in +* Example of using a file: +* $config_schema = array( +* 'table_format' => 'file', +* 'filename' => 'NAME OF FILE', // If the file is not in the root directory, the path needs to be added with no leading slash +* 'array_name' => 'NAME OF ARRAY', // Only used if the configuration file stores the setting in an array. +* 'settings' => array( +* 'board_email' => 'SUPPORT_EMAIL', // target config name => source target name +* ) +* ); +* 'table_format' can be an array if the values are stored in a table which is an assosciative array +* (as per phpBB 2.0.x) +* If left empty, values are assumed to be stored in a table where each config setting is +* a column (as per phpBB 1.x) +* +* In either of the latter cases 'table_name' indicates the name of the table in the database +* +* 'settings' is an array which maps the name of the config directive in the source forum +* to the config directive in phpBB3. It can either be a direct mapping or use a function. +* Please note that the contents of the old config value are passed to the function, therefore +* an in-built function requiring the variable passed by reference is not able to be used. Since +* empty() is such a function we created the function is_empty() to be used instead. +*/ +$config_schema = array( + 'table_name' => 'config', + 'table_format' => array('config_name' => 'config_value'), + 'settings' => array( + 'allow_bbcode' => 'allow_bbcode', + 'allow_smilies' => 'allow_smilies', + 'allow_sig' => 'allow_sig', + 'allow_namechange' => 'allow_namechange', + 'allow_avatar_local' => 'allow_avatar_local', + 'allow_avatar_remote' => 'allow_avatar_remote', + 'allow_avatar_upload' => 'allow_avatar_upload', + 'board_disable' => 'board_disable', + 'sitename' => 'phpbb_set_encoding(sitename)', + 'site_desc' => 'phpbb_set_encoding(site_desc)', + 'session_length' => 'session_length', + 'board_email_sig' => 'phpbb_set_encoding(board_email_sig)', + 'posts_per_page' => 'posts_per_page', + 'topics_per_page' => 'topics_per_page', + 'enable_confirm' => 'enable_confirm', + 'board_email_form' => 'board_email_form', + 'override_user_style' => 'override_user_style', + 'hot_threshold' => 'hot_threshold', + 'max_poll_options' => 'max_poll_options', + 'max_sig_chars' => 'max_sig_chars', + 'pm_max_msgs' => 'max_inbox_privmsgs', + 'smtp_delivery' => 'smtp_delivery', + 'smtp_host' => 'smtp_host', + 'smtp_username' => 'smtp_username', + 'smtp_password' => 'smtp_password', + 'require_activation' => 'require_activation', + 'flood_interval' => 'flood_interval', + 'avatar_filesize' => 'avatar_filesize', + 'avatar_max_width' => 'avatar_max_width', + 'avatar_max_height' => 'avatar_max_height', + 'default_dateformat' => 'phpbb_set_encoding(default_dateformat)', + 'board_timezone' => 'phpbb_convert_timezone(board_timezone)', + 'allow_privmsg' => 'not(privmsg_disable)', + 'gzip_compress' => 'gzip_compress', + 'coppa_enable' => '!is_empty(coppa_mail)', + 'coppa_fax' => 'coppa_fax', + 'coppa_mail' => 'coppa_mail', + 'record_online_users' => 'record_online_users', + 'record_online_date' => 'record_online_date', + 'board_startdate' => 'board_startdate', + ) +); + +/** +* $test_file is the name of a file which is present on the source +* forum which can be used to check that the path specified by the +* user was correct +*/ +$test_file = 'modcp.php'; + +/** +* If this is set then we are not generating the first page of information but getting the conversion information. +*/ +if (!$get_info) +{ + // Test to see if the birthday MOD is installed on the source forum + // Niels' birthday mod + if (get_config_value('birthday_required') !== false || get_config_value('bday_require') !== false) + { + define('MOD_BIRTHDAY', true); + } + + // TerraFrost's validated birthday mod + if (get_config_value('bday_require') !== false) + { + define('MOD_BIRTHDAY_TERRA', true); + } + + // Test to see if the attachment MOD is installed on the source forum + // If it is, we will convert this data as well + $src_db->sql_return_on_error(true); + + $sql = "SELECT config_value + FROM {$convert->src_table_prefix}attachments_config + WHERE config_name = 'upload_dir'"; + $result = $src_db->sql_query($sql); + + if ($result && $row = $src_db->sql_fetchrow($result)) + { + // Here the constant is defined + define('MOD_ATTACHMENT', true); + + // Here i add more tables to be checked in the old forum + $tables += array( + 'attachments', + 'attachments_desc', + 'extensions', + 'extension_groups' + ); + + $src_db->sql_freeresult($result); + } + else if ($result) + { + $src_db->sql_freeresult($result); + } + + + /** + * Tests for further MODs can be included here. + * Please use constants for this, prefixing them with MOD_ + */ + + $src_db->sql_return_on_error(false); + + // Now let us set a temporary config variable for user id incrementing + $sql = "SELECT user_id + FROM {$convert->src_table_prefix}users + WHERE user_id = 1"; + $result = $src_db->sql_query($sql); + $user_id = (int) $src_db->sql_fetchfield('user_id'); + $src_db->sql_freeresult($result); + + // If there is a user id 1, we need to increment user ids. :/ + if ($user_id === 1) + { + // Try to get the maximum user id possible... + $sql = "SELECT MAX(user_id) AS max_user_id + FROM {$convert->src_table_prefix}users"; + $result = $src_db->sql_query($sql); + $user_id = (int) $src_db->sql_fetchfield('max_user_id'); + $src_db->sql_freeresult($result); + + $config->set('increment_user_id', ($user_id + 1), false); + } + else + { + $config->set('increment_user_id', 0, false); + } + + // Overwrite maximum avatar width/height + @define('DEFAULT_AVATAR_X_CUSTOM', get_config_value('avatar_max_width')); + @define('DEFAULT_AVATAR_Y_CUSTOM', get_config_value('avatar_max_height')); + + // additional table used only during conversion + @define('USERCONV_TABLE', $table_prefix . 'userconv'); + +/** +* Description on how to use the convertor framework. +* +* 'schema' Syntax Description +* -> 'target' => Target Table. If not specified the next table will be handled +* -> 'primary' => Primary Key. If this is specified then this table is processed in batches +* -> 'query_first' => array('target' or 'src', Query to execute before beginning the process +* (if more than one then specified as array)) +* -> 'function_first' => Function to execute before beginning the process (if more than one then specified as array) +* (This is mostly useful if variables need to be given to the converting process) +* -> 'test_file' => This is not used at the moment but should be filled with a file from the old installation +* +* // DB Functions +* 'distinct' => Add DISTINCT to the select query +* 'where' => Add WHERE to the select query +* 'group_by' => Add GROUP BY to the select query +* 'left_join' => Add LEFT JOIN to the select query (if more than one joins specified as array) +* 'having' => Add HAVING to the select query +* +* // DB INSERT array +* This one consist of three parameters +* First Parameter: +* The key need to be filled within the target table +* If this is empty, the target table gets not assigned the source value +* Second Parameter: +* Source value. If the first parameter is specified, it will be assigned this value. +* If the first parameter is empty, this only gets added to the select query +* Third Parameter: +* Custom Function. Function to execute while storing source value into target table. +* The functions return value get stored. +* The function parameter consist of the value of the second parameter. +* +* types: +* - empty string == execute nothing +* - string == function to execute +* - array == complex execution instructions +* +* Complex execution instructions: +* @todo test complex execution instructions - in theory they will work fine +* +* By defining an array as the third parameter you are able to define some statements to be executed. The key +* is defining what to execute, numbers can be appended... +* +* 'function' => execute function +* 'execute' => run code, whereby all occurrences of {VALUE} get replaced by the last returned value. +* The result *must* be assigned/stored to {RESULT}. +* 'typecast' => typecast value +* +* The returned variables will be made always available to the next function to continue to work with. +* +* example (variable inputted is an integer of 1): +* +* array( +* 'function1' => 'increment_by_one', // returned variable is 2 +* 'typecast' => 'string', // typecast variable to be a string +* 'execute' => '{RESULT} = {VALUE} . ' is good';', // returned variable is '2 is good' +* 'function2' => 'replace_good_with_bad', // returned variable is '2 is bad' +* ), +* +*/ + + $convertor = array( + 'test_file' => 'viewtopic.php', + + 'avatar_path' => get_config_value('avatar_path') . '/', + 'avatar_gallery_path' => get_config_value('avatar_gallery_path') . '/', + 'smilies_path' => get_config_value('smilies_path') . '/', + 'upload_path' => (defined('MOD_ATTACHMENT')) ? phpbb_get_files_dir() . '/' : '', + 'thumbnails' => (defined('MOD_ATTACHMENT')) ? array('thumbs/', 't_') : '', + 'ranks_path' => false, // phpBB 2.0.x had no config value for a ranks path + + // We empty some tables to have clean data available + 'query_first' => array( + array('target', $convert->truncate_statement . SEARCH_RESULTS_TABLE), + array('target', $convert->truncate_statement . SEARCH_WORDLIST_TABLE), + array('target', $convert->truncate_statement . SEARCH_WORDMATCH_TABLE), + array('target', $convert->truncate_statement . LOG_TABLE), + ), + +// with this you are able to import all attachment files on the fly. For large boards this is not an option, therefore commented out by default. +// Instead every file gets copied while processing the corresponding attachment entry. +// if (defined("MOD_ATTACHMENT")) { import_attachment_files(); phpbb_copy_thumbnails(); } + + // phpBB2 allowed some similar usernames to coexist which would have the same + // username_clean in phpBB3 which is not possible, so we'll give the admin a list + // of user ids and usernames and let him deicde what he wants to do with them + 'execute_first' => ' + phpbb_create_userconv_table(); + import_avatar_gallery(); + if (defined("MOD_ATTACHMENT")) phpbb_import_attach_config(); + phpbb_insert_forums(); + ', + + 'execute_last' => array(' + add_bots(); + ', ' + update_folder_pm_count(); + ', ' + update_unread_count(); + ', (defined('MOD_ATTACHMENT')) ? ' + phpbb_attachment_extension_group_name(); + ' : ' + ', ' + phpbb_convert_authentication(\'start\'); + ', ' + phpbb_convert_authentication(\'first\'); + ', ' + phpbb_convert_authentication(\'second\'); + ', ' + phpbb_convert_authentication(\'third\'); + '), + + 'schema' => array( + array( + 'target' => USERCONV_TABLE, + 'query_first' => array('target', $convert->truncate_statement . USERCONV_TABLE), + + + array('user_id', 'users.user_id', ''), + array('username_clean', 'users.username', array('function1' => 'phpbb_set_encoding', 'function2' => 'utf8_clean_string')), + ), + + array( + 'target' => (defined('MOD_ATTACHMENT')) ? ATTACHMENTS_TABLE : '', + 'primary' => 'attachments.attach_id', + 'query_first' => (defined('MOD_ATTACHMENT')) ? array('target', $convert->truncate_statement . ATTACHMENTS_TABLE) : '', + 'autoincrement' => 'attach_id', + + array('attach_id', 'attachments.attach_id', ''), + array('post_msg_id', 'attachments.post_id', ''), + array('topic_id', 'posts.topic_id', ''), + array('in_message', 0, ''), + array('is_orphan', 0, ''), + array('poster_id', 'attachments.user_id_1 AS poster_id', 'phpbb_user_id'), + array('physical_filename', 'attachments_desc.physical_filename', 'import_attachment'), + array('real_filename', 'attachments_desc.real_filename', 'phpbb_set_encoding'), + array('download_count', 'attachments_desc.download_count', ''), + array('attach_comment', 'attachments_desc.comment', array('function1' => 'phpbb_set_encoding', 'function2' => 'utf8_htmlspecialchars')), + array('extension', 'attachments_desc.extension', ''), + array('mimetype', 'attachments_desc.mimetype', ''), + array('filesize', 'attachments_desc.filesize', ''), + array('filetime', 'attachments_desc.filetime', ''), + array('thumbnail', 'attachments_desc.thumbnail', ''), + + 'where' => 'attachments_desc.attach_id = attachments.attach_id AND attachments.privmsgs_id = 0 AND posts.post_id = attachments.post_id', + 'group_by' => 'attachments.attach_id' + ), + + array( + 'target' => (defined('MOD_ATTACHMENT')) ? ATTACHMENTS_TABLE : '', + 'primary' => 'attachments.attach_id', + 'autoincrement' => 'attach_id', + + array('attach_id', 'attachments.attach_id', ''), + array('post_msg_id', 'attachments.privmsgs_id', ''), + array('topic_id', 0, ''), + array('in_message', 1, ''), + array('is_orphan', 0, ''), + array('poster_id', 'attachments.user_id_1 AS poster_id', 'phpbb_user_id'), + array('physical_filename', 'attachments_desc.physical_filename', 'import_attachment'), + array('real_filename', 'attachments_desc.real_filename', 'phpbb_set_encoding'), + array('download_count', 'attachments_desc.download_count', ''), + array('attach_comment', 'attachments_desc.comment', array('function1' => 'phpbb_set_encoding', 'function2' => 'utf8_htmlspecialchars')), + array('extension', 'attachments_desc.extension', ''), + array('mimetype', 'attachments_desc.mimetype', ''), + array('filesize', 'attachments_desc.filesize', ''), + array('filetime', 'attachments_desc.filetime', ''), + array('thumbnail', 'attachments_desc.thumbnail', ''), + + 'where' => 'attachments_desc.attach_id = attachments.attach_id AND attachments.post_id = 0', + 'group_by' => 'attachments.attach_id' + ), + + array( + 'target' => (defined('MOD_ATTACHMENT')) ? EXTENSIONS_TABLE : '', + 'query_first' => (defined('MOD_ATTACHMENT')) ? array('target', $convert->truncate_statement . EXTENSIONS_TABLE) : '', + 'autoincrement' => 'extension_id', + + array('extension_id', 'extensions.ext_id', ''), + array('group_id', 'extensions.group_id', ''), + array('extension', 'extensions.extension', ''), + ), + + array( + 'target' => (defined('MOD_ATTACHMENT')) ? EXTENSION_GROUPS_TABLE : '', + 'query_first' => (defined('MOD_ATTACHMENT')) ? array('target', $convert->truncate_statement . EXTENSION_GROUPS_TABLE) : '', + 'autoincrement' => 'group_id', + + array('group_id', 'extension_groups.group_id', ''), + array('group_name', 'extension_groups.group_name', array('function1' => 'phpbb_set_encoding', 'function2' => 'utf8_htmlspecialchars')), + array('cat_id', 'extension_groups.cat_id', 'phpbb_attachment_category'), + array('allow_group', 'extension_groups.allow_group', ''), + array('download_mode', 1, ''), + array('upload_icon', '', ''), + array('max_filesize', 'extension_groups.max_filesize', ''), + array('allowed_forums', 'extension_groups.forum_permissions', 'phpbb_attachment_forum_perms'), + array('allow_in_pm', 1, ''), + ), + + array( + 'target' => BANLIST_TABLE, + 'execute_first' => 'phpbb_check_username_collisions();', + 'query_first' => array('target', $convert->truncate_statement . BANLIST_TABLE), + + array('ban_ip', 'banlist.ban_ip', 'decode_ban_ip'), + array('ban_userid', 'banlist.ban_userid', 'phpbb_user_id'), + array('ban_email', 'banlist.ban_email', ''), + array('ban_reason', '', ''), + array('ban_give_reason', '', ''), + + 'where' => "banlist.ban_ip NOT LIKE '%.%'", + ), + + array( + 'target' => BANLIST_TABLE, + + array('ban_ip', 'banlist.ban_ip', ''), + array('ban_userid', 0, ''), + array('ban_email', '', ''), + array('ban_reason', '', ''), + array('ban_give_reason', '', ''), + + 'where' => "banlist.ban_ip LIKE '%.%'", + ), + + array( + 'target' => DISALLOW_TABLE, + 'query_first' => array('target', $convert->truncate_statement . DISALLOW_TABLE), + + array('disallow_username', 'disallow.disallow_username', 'phpbb_disallowed_username'), + ), + + array( + 'target' => RANKS_TABLE, + 'query_first' => array('target', $convert->truncate_statement . RANKS_TABLE), + 'autoincrement' => 'rank_id', + + array('rank_id', 'ranks.rank_id', ''), + array('rank_title', 'ranks.rank_title', array('function1' => 'phpbb_set_default_encoding', 'function2' => 'utf8_htmlspecialchars')), + array('rank_min', 'ranks.rank_min', array('typecast' => 'int', 'execute' => '{RESULT} = ({VALUE}[0] < 0) ? 0 : {VALUE}[0];')), + array('rank_special', 'ranks.rank_special', ''), + array('rank_image', 'ranks.rank_image', 'import_rank'), + ), + + array( + 'target' => TOPICS_TABLE, + 'query_first' => array('target', $convert->truncate_statement . TOPICS_TABLE), + 'primary' => 'topics.topic_id', + 'autoincrement' => 'topic_id', + + array('topic_id', 'topics.topic_id', ''), + array('forum_id', 'topics.forum_id', ''), + array('icon_id', 0, ''), + array('topic_poster', 'topics.topic_poster AS poster_id', 'phpbb_user_id'), + array('topic_attachment', ((defined('MOD_ATTACHMENT')) ? 'topics.topic_attachment' : 0), ''), + array('topic_title', 'topics.topic_title', 'phpbb_set_encoding'), + array('topic_time', 'topics.topic_time', ''), + array('topic_views', 'topics.topic_views', ''), + array('topic_posts_approved', 'topics.topic_replies', 'phpbb_topic_replies_to_posts'), + array('topic_posts_unapproved', 0, ''), + array('topic_posts_softdeleted',0, ''), + array('topic_last_post_id', 'topics.topic_last_post_id', ''), + array('topic_status', 'topics.topic_status', 'is_topic_locked'), + array('topic_moved_id', 0, ''), + array('topic_type', 'topics.topic_type', 'phpbb_convert_topic_type'), + array('topic_first_post_id', 'topics.topic_first_post_id', ''), + array('topic_last_view_time', 'posts.post_time', 'intval'), + array('topic_visibility', ITEM_APPROVED, ''), + + array('poll_title', 'vote_desc.vote_text', array('function1' => 'null_to_str', 'function2' => 'phpbb_set_encoding', 'function3' => 'htmlspecialchars_decode', 'function4' => 'utf8_htmlspecialchars')), + array('poll_start', 'vote_desc.vote_start', 'null_to_zero'), + array('poll_length', 'vote_desc.vote_length', 'null_to_zero'), + array('poll_max_options', 1, ''), + array('poll_vote_change', 0, ''), + + 'left_join' => array ( 'topics LEFT JOIN vote_desc ON topics.topic_id = vote_desc.topic_id AND topics.topic_vote = 1', + 'topics LEFT JOIN posts ON topics.topic_last_post_id = posts.post_id', + ), + 'where' => 'topics.topic_moved_id = 0', + ), + + array( + 'target' => TOPICS_TABLE, + 'primary' => 'topics.topic_id', + 'autoincrement' => 'topic_id', + + array('topic_id', 'topics.topic_id', ''), + array('forum_id', 'topics.forum_id', ''), + array('icon_id', 0, ''), + array('topic_poster', 'topics.topic_poster AS poster_id', 'phpbb_user_id'), + array('topic_attachment', ((defined('MOD_ATTACHMENT')) ? 'topics.topic_attachment' : 0), ''), + array('topic_title', 'topics.topic_title', 'phpbb_set_encoding'), + array('topic_time', 'topics.topic_time', ''), + array('topic_views', 'topics.topic_views', ''), + array('topic_posts_approved', 'topics.topic_replies', 'phpbb_topic_replies_to_posts'), + array('topic_posts_unapproved', 0, ''), + array('topic_posts_softdeleted',0, ''), + array('topic_last_post_id', 'topics.topic_last_post_id', ''), + array('topic_status', ITEM_MOVED, ''), + array('topic_moved_id', 'topics.topic_moved_id', ''), + array('topic_type', 'topics.topic_type', 'phpbb_convert_topic_type'), + array('topic_first_post_id', 'topics.topic_first_post_id', ''), + array('topic_visibility', ITEM_APPROVED, ''), + + array('poll_title', 'vote_desc.vote_text', array('function1' => 'null_to_str', 'function2' => 'phpbb_set_encoding', 'function3' => 'htmlspecialchars_decode', 'function4' => 'utf8_htmlspecialchars')), + array('poll_start', 'vote_desc.vote_start', 'null_to_zero'), + array('poll_length', 'vote_desc.vote_length', 'null_to_zero'), + array('poll_max_options', 1, ''), + array('poll_vote_change', 0, ''), + + 'left_join' => 'topics LEFT JOIN vote_desc ON topics.topic_id = vote_desc.topic_id AND topics.topic_vote = 1', + 'where' => 'topics.topic_moved_id <> 0', + ), + + array( + 'target' => TOPICS_WATCH_TABLE, + 'primary' => 'topics_watch.topic_id', + 'query_first' => array('target', $convert->truncate_statement . TOPICS_WATCH_TABLE), + + array('topic_id', 'topics_watch.topic_id', ''), + array('user_id', 'topics_watch.user_id', 'phpbb_user_id'), + array('notify_status', 'topics_watch.notify_status', ''), + ), + + array( + 'target' => SMILIES_TABLE, + 'query_first' => array('target', $convert->truncate_statement . SMILIES_TABLE), + 'autoincrement' => 'smiley_id', + + array('smiley_id', 'smilies.smilies_id', ''), + array('code', 'smilies.code', array('function1' => 'phpbb_smilie_html_decode', 'function2' => 'phpbb_set_encoding', 'function3' => 'utf8_htmlspecialchars')), + array('emotion', 'smilies.emoticon', 'phpbb_set_encoding'), + array('smiley_url', 'smilies.smile_url', 'import_smiley'), + array('smiley_width', 'smilies.smile_url', 'get_smiley_width'), + array('smiley_height', 'smilies.smile_url', 'get_smiley_height'), + array('smiley_order', 'smilies.smilies_id', ''), + array('display_on_posting', 'smilies.smilies_id', 'get_smiley_display'), + + 'order_by' => 'smilies.smilies_id ASC', + ), + + array( + 'target' => POLL_OPTIONS_TABLE, + 'primary' => 'vote_results.vote_option_id', + 'query_first' => array('target', $convert->truncate_statement . POLL_OPTIONS_TABLE), + + array('poll_option_id', 'vote_results.vote_option_id', ''), + array('topic_id', 'vote_desc.topic_id', ''), + array('', 'topics.topic_poster AS poster_id', 'phpbb_user_id'), + array('poll_option_text', 'vote_results.vote_option_text', array('function1' => 'phpbb_set_encoding', 'function2' => 'htmlspecialchars_decode', 'function3' => 'utf8_htmlspecialchars')), + array('poll_option_total', 'vote_results.vote_result', ''), + + 'where' => 'vote_results.vote_id = vote_desc.vote_id', + 'left_join' => 'vote_desc LEFT JOIN topics ON topics.topic_id = vote_desc.topic_id', + ), + + array( + 'target' => POLL_VOTES_TABLE, + 'primary' => 'vote_desc.topic_id', + 'query_first' => array('target', $convert->truncate_statement . POLL_VOTES_TABLE), + + array('poll_option_id', VOTE_CONVERTED, ''), + array('topic_id', 'vote_desc.topic_id', ''), + array('vote_user_id', 'vote_voters.vote_user_id', 'phpbb_user_id'), + array('vote_user_ip', 'vote_voters.vote_user_ip', 'decode_ip'), + + 'where' => 'vote_voters.vote_id = vote_desc.vote_id', + ), + + array( + 'target' => WORDS_TABLE, + 'primary' => 'words.word_id', + 'query_first' => array('target', $convert->truncate_statement . WORDS_TABLE), + 'autoincrement' => 'word_id', + + array('word_id', 'words.word_id', ''), + array('word', 'words.word', 'phpbb_set_encoding'), + array('replacement', 'words.replacement', 'phpbb_set_encoding'), + ), + + array( + 'target' => POSTS_TABLE, + 'primary' => 'posts.post_id', + 'autoincrement' => 'post_id', + 'query_first' => array('target', $convert->truncate_statement . POSTS_TABLE), + 'execute_first' => ' + $config["max_post_chars"] = 0; + $config["min_post_chars"] = 0; + $config["max_quote_depth"] = 0; + ', + + array('post_id', 'posts.post_id', ''), + array('topic_id', 'posts.topic_id', ''), + array('forum_id', 'posts.forum_id', ''), + array('poster_id', 'posts.poster_id', 'phpbb_user_id'), + array('icon_id', 0, ''), + array('poster_ip', 'posts.poster_ip', 'decode_ip'), + array('post_time', 'posts.post_time', ''), + array('enable_bbcode', 'posts.enable_bbcode', ''), + array('', 'posts.enable_html', ''), + array('enable_smilies', 'posts.enable_smilies', ''), + array('enable_sig', 'posts.enable_sig', ''), + array('enable_magic_url', 1, ''), + array('post_username', 'posts.post_username', 'phpbb_set_encoding'), + array('post_subject', 'posts_text.post_subject', 'phpbb_set_encoding'), + array('post_attachment', ((defined('MOD_ATTACHMENT')) ? 'posts.post_attachment' : 0), ''), + array('post_edit_time', 'posts.post_edit_time', array('typecast' => 'int')), + array('post_edit_count', 'posts.post_edit_count', ''), + array('post_edit_reason', '', ''), + array('post_edit_user', '', 'phpbb_post_edit_user'), + array('post_visibility', ITEM_APPROVED, ''), + + array('bbcode_uid', 'posts.post_time', 'make_uid'), + array('post_text', 'posts_text.post_text', 'phpbb_prepare_message'), + array('', 'posts_text.bbcode_uid AS old_bbcode_uid', ''), + array('bbcode_bitfield', '', 'get_bbcode_bitfield'), + array('post_checksum', '', ''), + + // Commented out inline search indexing, this takes up a LOT of time. :D + // @todo We either need to enable this or call the rebuild search functionality post convert +/* array('', '', 'search_indexing'), + array('', 'posts_text.post_text AS message', ''), + array('', 'posts_text.post_subject AS title', ''),*/ + + 'where' => 'posts.post_id = posts_text.post_id' + ), + + array( + 'target' => PRIVMSGS_TABLE, + 'primary' => 'privmsgs.privmsgs_id', + 'autoincrement' => 'msg_id', + 'query_first' => array( + array('target', $convert->truncate_statement . PRIVMSGS_TABLE), + array('target', $convert->truncate_statement . PRIVMSGS_RULES_TABLE), + ), + + 'execute_first' => ' + $config["max_post_chars"] = 0; + $config["min_post_chars"] = 0; + $config["max_quote_depth"] = 0; + ', + + array('msg_id', 'privmsgs.privmsgs_id', ''), + array('root_level', 0, ''), + array('author_id', 'privmsgs.privmsgs_from_userid AS poster_id', 'phpbb_user_id'), + array('icon_id', 0, ''), + array('author_ip', 'privmsgs.privmsgs_ip', 'decode_ip'), + array('message_time', 'privmsgs.privmsgs_date', ''), + array('enable_bbcode', 'privmsgs.privmsgs_enable_bbcode AS enable_bbcode', ''), + array('', 'privmsgs.privmsgs_enable_html AS enable_html', ''), + array('enable_smilies', 'privmsgs.privmsgs_enable_smilies AS enable_smilies', ''), + array('enable_magic_url', 1, ''), + array('enable_sig', 'privmsgs.privmsgs_attach_sig', ''), + array('message_subject', 'privmsgs.privmsgs_subject', 'phpbb_set_encoding'), // Already specialchared in 2.0.x + array('message_attachment', ((defined('MOD_ATTACHMENT')) ? 'privmsgs.privmsgs_attachment' : 0), ''), + array('message_edit_reason', '', ''), + array('message_edit_user', 0, ''), + array('message_edit_time', 0, ''), + array('message_edit_count', 0, ''), + + array('bbcode_uid', 'privmsgs.privmsgs_date AS post_time', 'make_uid'), + array('message_text', 'privmsgs_text.privmsgs_text', 'phpbb_prepare_message'), + array('', 'privmsgs_text.privmsgs_bbcode_uid AS old_bbcode_uid', ''), + array('bbcode_bitfield', '', 'get_bbcode_bitfield'), + array('to_address', 'privmsgs.privmsgs_to_userid', 'phpbb_privmsgs_to_userid'), + array('bcc_address', '', ''), + + 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id' + ), + + array( + 'target' => PRIVMSGS_FOLDER_TABLE, + 'primary' => 'users.user_id', + 'query_first' => array('target', $convert->truncate_statement . PRIVMSGS_FOLDER_TABLE), + + array('user_id', 'users.user_id', 'phpbb_user_id'), + array('folder_name', $user->lang['CONV_SAVED_MESSAGES'], ''), + array('pm_count', 0, ''), + + 'where' => 'users.user_id <> -1', + ), + + // Inbox + array( + 'target' => PRIVMSGS_TO_TABLE, + 'primary' => 'privmsgs.privmsgs_id', + 'query_first' => array('target', $convert->truncate_statement . PRIVMSGS_TO_TABLE), + + array('msg_id', 'privmsgs.privmsgs_id', ''), + array('user_id', 'privmsgs.privmsgs_to_userid', 'phpbb_user_id'), + array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), + array('pm_deleted', 0, ''), + array('pm_new', 'privmsgs.privmsgs_type', 'phpbb_new_pm'), + array('pm_unread', 'privmsgs.privmsgs_type', 'phpbb_unread_pm'), + array('pm_replied', 0, ''), + array('pm_marked', 0, ''), + array('pm_forwarded', 0, ''), + array('folder_id', PRIVMSGS_INBOX, ''), + + 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id + AND (privmsgs.privmsgs_type = 0 OR privmsgs.privmsgs_type = 1 OR privmsgs.privmsgs_type = 5)', + ), + + // Outbox + array( + 'target' => PRIVMSGS_TO_TABLE, + 'primary' => 'privmsgs.privmsgs_id', + + array('msg_id', 'privmsgs.privmsgs_id', ''), + array('user_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), + array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), + array('pm_deleted', 0, ''), + array('pm_new', 0, ''), + array('pm_unread', 0, ''), + array('pm_replied', 0, ''), + array('pm_marked', 0, ''), + array('pm_forwarded', 0, ''), + array('folder_id', PRIVMSGS_OUTBOX, ''), + + 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id + AND (privmsgs.privmsgs_type = 1 OR privmsgs.privmsgs_type = 5)', + ), + + // Sentbox + array( + 'target' => PRIVMSGS_TO_TABLE, + 'primary' => 'privmsgs.privmsgs_id', + + array('msg_id', 'privmsgs.privmsgs_id', ''), + array('user_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), + array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), + array('pm_deleted', 0, ''), + array('pm_new', 'privmsgs.privmsgs_type', 'phpbb_new_pm'), + array('pm_unread', 'privmsgs.privmsgs_type', 'phpbb_unread_pm'), + array('pm_replied', 0, ''), + array('pm_marked', 0, ''), + array('pm_forwarded', 0, ''), + array('folder_id', PRIVMSGS_SENTBOX, ''), + + 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id + AND privmsgs.privmsgs_type = 2', + ), + + // Savebox (SAVED IN) + array( + 'target' => PRIVMSGS_TO_TABLE, + 'primary' => 'privmsgs.privmsgs_id', + + array('msg_id', 'privmsgs.privmsgs_id', ''), + array('user_id', 'privmsgs.privmsgs_to_userid', 'phpbb_user_id'), + array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), + array('pm_deleted', 0, ''), + array('pm_new', 'privmsgs.privmsgs_type', 'phpbb_new_pm'), + array('pm_unread', 'privmsgs.privmsgs_type', 'phpbb_unread_pm'), + array('pm_replied', 0, ''), + array('pm_marked', 0, ''), + array('pm_forwarded', 0, ''), + array('folder_id', 'privmsgs.privmsgs_to_userid', 'phpbb_get_savebox_id'), + + 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id + AND privmsgs.privmsgs_type = 3', + ), + + // Savebox (SAVED OUT) + array( + 'target' => PRIVMSGS_TO_TABLE, + 'primary' => 'privmsgs.privmsgs_id', + + array('msg_id', 'privmsgs.privmsgs_id', ''), + array('user_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), + array('author_id', 'privmsgs.privmsgs_from_userid', 'phpbb_user_id'), + array('pm_deleted', 0, ''), + array('pm_new', 'privmsgs.privmsgs_type', 'phpbb_new_pm'), + array('pm_unread', 'privmsgs.privmsgs_type', 'phpbb_unread_pm'), + array('pm_replied', 0, ''), + array('pm_marked', 0, ''), + array('pm_forwarded', 0, ''), + array('folder_id', 'privmsgs.privmsgs_from_userid', 'phpbb_get_savebox_id'), + + 'where' => 'privmsgs.privmsgs_id = privmsgs_text.privmsgs_text_id + AND privmsgs.privmsgs_type = 4', + ), + + array( + 'target' => GROUPS_TABLE, + 'autoincrement' => 'group_id', + 'query_first' => array( + array('target', $convert->truncate_statement . GROUPS_TABLE), + array('target', $convert->truncate_statement . TEAMPAGE_TABLE), + ), + + array('group_id', 'groups.group_id', ''), + array('group_type', 'groups.group_type', 'phpbb_convert_group_type'), + array('group_display', 0, ''), + array('group_legend', 0, ''), + array('group_name', 'groups.group_name', 'phpbb_convert_group_name'), // phpbb_set_encoding called in phpbb_convert_group_name + array('group_desc', 'groups.group_description', 'phpbb_set_encoding'), + + 'where' => 'groups.group_single_user = 0', + ), + + array( + 'target' => USER_GROUP_TABLE, + 'query_first' => array('target', $convert->truncate_statement . USER_GROUP_TABLE), + 'execute_first' => ' + add_default_groups(); + add_groups_to_teampage(); + ', + + array('group_id', 'groups.group_id', ''), + array('user_id', 'groups.group_moderator', 'phpbb_user_id'), + array('group_leader', 1, ''), + array('user_pending', 0, ''), + + 'where' => 'groups.group_single_user = 0 AND groups.group_moderator <> 0', + ), + + array( + 'target' => USER_GROUP_TABLE, + + array('group_id', 'user_group.group_id', ''), + array('user_id', 'user_group.user_id', 'phpbb_user_id'), + array('group_leader', 0, ''), + array('user_pending', 'user_group.user_pending', ''), + + 'where' => 'user_group.group_id = groups.group_id AND groups.group_single_user = 0 AND groups.group_moderator <> user_group.user_id', + ), + + array( + 'target' => USERS_TABLE, + 'primary' => 'users.user_id', + 'autoincrement' => 'user_id', + 'query_first' => array( + array('target', 'DELETE FROM ' . USERS_TABLE . ' WHERE user_id <> ' . ANONYMOUS), + array('target', $convert->truncate_statement . BOTS_TABLE), + array('target', $convert->truncate_statement . USER_NOTIFICATIONS_TABLE), + ), + + 'execute_last' => ' + remove_invalid_users(); + ', + + array('user_id', 'users.user_id', 'phpbb_user_id'), + array('', 'users.user_id AS poster_id', 'phpbb_user_id'), + array('user_type', 'users.user_active', 'set_user_type'), + array('group_id', 'users.user_level', 'phpbb_set_primary_group'), + array('user_regdate', 'users.user_regdate', ''), + array('username', 'users.username', 'phpbb_set_default_encoding'), // recode to utf8 with default lang + array('username_clean', 'users.username', array('function1' => 'phpbb_set_default_encoding', 'function2' => 'utf8_clean_string')), + array('user_password', 'users.user_password', 'phpbb_convert_password_hash'), + array('user_posts', 'users.user_posts', 'intval'), + array('user_email', 'users.user_email', 'strtolower'), + array('user_email_hash', 'users.user_email', 'gen_email_hash'), + array('user_birthday', ((defined('MOD_BIRTHDAY')) ? 'users.user_birthday' : ''), 'phpbb_get_birthday'), + array('user_lastvisit', 'users.user_lastvisit', 'intval'), + array('user_lastmark', 'users.user_lastvisit', 'intval'), + array('user_lang', $config['default_lang'], ''), + array('', 'users.user_lang', ''), + array('user_timezone', 'users.user_timezone', 'phpbb_convert_timezone'), + array('user_dateformat', 'users.user_dateformat', array('function1' => 'phpbb_set_encoding', 'function2' => 'fill_dateformat')), + array('user_inactive_reason', '', 'phpbb_inactive_reason'), + array('user_inactive_time', '', 'phpbb_inactive_time'), + + array('user_jabber', '', ''), + array('user_rank', 'users.user_rank', 'intval'), + array('user_permissions', '', ''), + + array('user_avatar', 'users.user_avatar', 'phpbb_import_avatar'), + array('user_avatar_type', 'users.user_avatar_type', 'phpbb_avatar_type'), + array('user_avatar_width', 'users.user_avatar', 'phpbb_get_avatar_width'), + array('user_avatar_height', 'users.user_avatar', 'phpbb_get_avatar_height'), + + array('user_new_privmsg', 'users.user_new_privmsg', ''), + array('user_unread_privmsg', 0, ''), //'users.user_unread_privmsg' + array('user_last_privmsg', 'users.user_last_privmsg', 'intval'), + array('user_emailtime', 'users.user_emailtime', 'null_to_zero'), + array('user_notify', 'users.user_notify', 'intval'), + array('user_notify_pm', 'users.user_notify_pm', 'intval'), + array('user_notify_type', NOTIFY_EMAIL, ''), + array('user_allow_pm', 'users.user_allow_pm', 'intval'), + array('user_allow_viewonline', 'users.user_allow_viewonline', 'intval'), + array('user_allow_viewemail', 'users.user_viewemail', 'intval'), + array('user_actkey', 'users.user_actkey', ''), + array('user_newpasswd', '', ''), // Users need to re-request their password... + array('user_style', $config['default_style'], ''), + + array('user_options', '', 'set_user_options'), + array('', 'users.user_popup_pm AS popuppm', ''), + array('', 'users.user_allowhtml AS html', ''), + array('', 'users.user_allowbbcode AS bbcode', ''), + array('', 'users.user_allowsmile AS smile', ''), + array('', 'users.user_attachsig AS attachsig',''), + + array('user_sig_bbcode_uid', 'users.user_regdate', 'make_uid'), + array('user_sig', 'users.user_sig', 'phpbb_prepare_message'), + array('', 'users.user_sig_bbcode_uid AS old_bbcode_uid', ''), + array('user_sig_bbcode_bitfield', '', 'get_bbcode_bitfield'), + array('', 'users.user_regdate AS post_time', ''), + + array('', 'users.user_notify_pm', 'phpbb_add_notification_options'), + + 'where' => 'users.user_id <> -1', + ), + + array( + 'target' => PROFILE_FIELDS_DATA_TABLE, + 'primary' => 'users.user_id', + 'query_first' => array( + array('target', $convert->truncate_statement . PROFILE_FIELDS_DATA_TABLE), + ), + + array('user_id', 'users.user_id', 'phpbb_user_id'), + array('pf_phpbb_occupation', 'users.user_occ', array('function1' => 'phpbb_set_encoding')), + array('pf_phpbb_interests', 'users.user_interests', array('function1' => 'phpbb_set_encoding')), + array('pf_phpbb_location', 'users.user_from', array('function1' => 'phpbb_set_encoding')), + array('pf_phpbb_icq', 'users.user_icq', array('function1' => 'phpbb_set_encoding')), + array('pf_phpbb_yahoo', 'users.user_yim', array('function1' => 'phpbb_set_encoding')), + array('pf_phpbb_aol', 'users.user_aim', array('function1' => 'phpbb_set_encoding')), + array('pf_phpbb_website', 'users.user_website', 'validate_website'), + + 'where' => 'users.user_id <> -1', + ), + ), + ); +} diff --git a/install/convertors/functions_phpbb20.php b/install/convertors/functions_phpbb20.php new file mode 100644 index 0000000..2da901d --- /dev/null +++ b/install/convertors/functions_phpbb20.php @@ -0,0 +1,1970 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* Helper functions for phpBB 2.0.x to phpBB 3.1.x conversion +*/ + +/** +* Set forum flags - only prune old polls by default +*/ +function phpbb_forum_flags() +{ + // Set forum flags + $forum_flags = 0; + + // FORUM_FLAG_LINK_TRACK + $forum_flags += 0; + + // FORUM_FLAG_PRUNE_POLL + $forum_flags += FORUM_FLAG_PRUNE_POLL; + + // FORUM_FLAG_PRUNE_ANNOUNCE + $forum_flags += 0; + + // FORUM_FLAG_PRUNE_STICKY + $forum_flags += 0; + + // FORUM_FLAG_ACTIVE_TOPICS + $forum_flags += 0; + + // FORUM_FLAG_POST_REVIEW + $forum_flags += FORUM_FLAG_POST_REVIEW; + + return $forum_flags; +} + +/** +* Insert/Convert forums +*/ +function phpbb_insert_forums() +{ + global $db, $src_db, $same_db, $convert, $user; + + $db->sql_query($convert->truncate_statement . FORUMS_TABLE); + + // Determine the highest id used within the old forums table (we add the categories after the forum ids) + $sql = 'SELECT MAX(forum_id) AS max_forum_id + FROM ' . $convert->src_table_prefix . 'forums'; + $result = $src_db->sql_query($sql); + $max_forum_id = (int) $src_db->sql_fetchfield('max_forum_id'); + $src_db->sql_freeresult($result); + + $max_forum_id++; + + // pruning disabled globally? + $sql = "SELECT config_value + FROM {$convert->src_table_prefix}config + WHERE config_name = 'prune_enable'"; + $result = $src_db->sql_query($sql); + $prune_enabled = (int) $src_db->sql_fetchfield('config_value'); + $src_db->sql_freeresult($result); + + // Insert categories + $sql = 'SELECT cat_id, cat_title + FROM ' . $convert->src_table_prefix . 'categories + ORDER BY cat_order'; + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + $result = $src_db->sql_query($sql); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + + switch ($db->get_sql_layer()) + { + case 'mssql_odbc': + case 'mssqlnative': + $db->sql_query('SET IDENTITY_INSERT ' . FORUMS_TABLE . ' ON'); + break; + } + + $cats_added = array(); + while ($row = $src_db->sql_fetchrow($result)) + { + $sql_ary = array( + 'forum_id' => (int) $max_forum_id, + 'forum_name' => ($row['cat_title']) ? htmlspecialchars(phpbb_set_default_encoding($row['cat_title']), ENT_COMPAT, 'UTF-8') : $user->lang['CATEGORY'], + 'parent_id' => 0, + 'forum_parents' => '', + 'forum_desc' => '', + 'forum_type' => FORUM_CAT, + 'forum_status' => ITEM_UNLOCKED, + 'forum_rules' => '', + ); + + $sql = 'SELECT MAX(right_id) AS right_id + FROM ' . FORUMS_TABLE; + $_result = $db->sql_query($sql); + $cat_row = $db->sql_fetchrow($_result); + $db->sql_freeresult($_result); + + $sql_ary['left_id'] = (int) ($cat_row['right_id'] + 1); + $sql_ary['right_id'] = (int) ($cat_row['right_id'] + 2); + + $sql = 'INSERT INTO ' . FORUMS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + + $cats_added[$row['cat_id']] = $max_forum_id; + $max_forum_id++; + } + $src_db->sql_freeresult($result); + + // There may be installations having forums with non-existant category ids. + // We try to catch them and add them to an "unknown" category instead of leaving them out. + $sql = 'SELECT cat_id + FROM ' . $convert->src_table_prefix . 'forums + GROUP BY cat_id'; + $result = $src_db->sql_query($sql); + + $unknown_cat_id = false; + while ($row = $src_db->sql_fetchrow($result)) + { + // Catch those categories not been added before + if (!isset($cats_added[$row['cat_id']])) + { + $unknown_cat_id = true; + } + } + $src_db->sql_freeresult($result); + + // Is there at least one category not known? + if ($unknown_cat_id === true) + { + $unknown_cat_id = 'ghost'; + + $sql_ary = array( + 'forum_id' => (int) $max_forum_id, + 'forum_name' => (string) $user->lang['CATEGORY'], + 'parent_id' => 0, + 'forum_parents' => '', + 'forum_desc' => '', + 'forum_type' => FORUM_CAT, + 'forum_status' => ITEM_UNLOCKED, + 'forum_rules' => '', + ); + + $sql = 'SELECT MAX(right_id) AS right_id + FROM ' . FORUMS_TABLE; + $_result = $db->sql_query($sql); + $cat_row = $db->sql_fetchrow($_result); + $db->sql_freeresult($_result); + + $sql_ary['left_id'] = (int) ($cat_row['right_id'] + 1); + $sql_ary['right_id'] = (int) ($cat_row['right_id'] + 2); + + $sql = 'INSERT INTO ' . FORUMS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + + $cats_added[$unknown_cat_id] = $max_forum_id; + } + + // Now insert the forums + $sql = 'SELECT f.forum_id, f.forum_name, f.cat_id, f.forum_desc, f.forum_status, f.prune_enable, f.prune_next, fp.prune_days, fp.prune_freq FROM ' . $convert->src_table_prefix . 'forums f + LEFT JOIN ' . $convert->src_table_prefix . 'forum_prune fp ON f.forum_id = fp.forum_id + GROUP BY f.forum_id, f.forum_name, f.cat_id, f.forum_desc, f.forum_status, f.prune_enable, f.prune_next, f.forum_order, fp.prune_days, fp.prune_freq + ORDER BY f.cat_id, f.forum_order'; + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + $result = $src_db->sql_query($sql); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + + while ($row = $src_db->sql_fetchrow($result)) + { + // Some might have forums here with an id not being "possible"... + // To be somewhat friendly we "change" the category id for those to a previously created ghost category + if (!isset($cats_added[$row['cat_id']]) && $unknown_cat_id !== false) + { + $row['cat_id'] = $unknown_cat_id; + } + + if (!isset($cats_added[$row['cat_id']])) + { + continue; + } + + // Define the new forums sql ary + $sql_ary = array( + 'forum_id' => (int) $row['forum_id'], + 'forum_name' => htmlspecialchars(phpbb_set_default_encoding($row['forum_name']), ENT_COMPAT, 'UTF-8'), + 'parent_id' => (int) $cats_added[$row['cat_id']], + 'forum_parents' => '', + 'forum_desc' => htmlspecialchars(phpbb_set_default_encoding($row['forum_desc']), ENT_COMPAT, 'UTF-8'), + 'forum_type' => FORUM_POST, + 'forum_status' => is_item_locked($row['forum_status']), + 'enable_prune' => ($prune_enabled) ? (int) $row['prune_enable'] : 0, + 'prune_next' => (int) null_to_zero($row['prune_next']), + 'prune_days' => (int) null_to_zero($row['prune_days']), + 'prune_viewed' => 0, + 'prune_freq' => (int) null_to_zero($row['prune_freq']), + + 'forum_flags' => phpbb_forum_flags(), + 'forum_options' => 0, + + // Default values + 'forum_desc_bitfield' => '', + 'forum_desc_options' => 7, + 'forum_desc_uid' => '', + 'forum_link' => '', + 'forum_password' => '', + 'forum_style' => 0, + 'forum_image' => '', + 'forum_rules' => '', + 'forum_rules_link' => '', + 'forum_rules_bitfield' => '', + 'forum_rules_options' => 7, + 'forum_rules_uid' => '', + 'forum_topics_per_page' => 0, + 'forum_posts_approved' => 0, + 'forum_posts_unapproved' => 0, + 'forum_posts_softdeleted' => 0, + 'forum_topics_approved' => 0, + 'forum_topics_unapproved' => 0, + 'forum_topics_softdeleted' => 0, + 'forum_last_post_id' => 0, + 'forum_last_poster_id' => 0, + 'forum_last_post_subject' => '', + 'forum_last_post_time' => 0, + 'forum_last_poster_name' => '', + 'forum_last_poster_colour' => '', + 'display_on_index' => 1, + 'enable_indexing' => 1, + 'enable_icons' => 0, + ); + + // Now add the forums with proper left/right ids + $sql = 'SELECT left_id, right_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $cats_added[$row['cat_id']]; + $_result = $db->sql_query($sql); + $cat_row = $db->sql_fetchrow($_result); + $db->sql_freeresult($_result); + + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE left_id > ' . $cat_row['right_id']; + $db->sql_query($sql); + + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET right_id = right_id + 2 + WHERE ' . $cat_row['left_id'] . ' BETWEEN left_id AND right_id'; + $db->sql_query($sql); + + $sql_ary['left_id'] = (int) $cat_row['right_id']; + $sql_ary['right_id'] = (int) ($cat_row['right_id'] + 1); + + $sql = 'INSERT INTO ' . FORUMS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + } + $src_db->sql_freeresult($result); + + switch ($db->get_sql_layer()) + { + case 'postgres': + $db->sql_query("SELECT SETVAL('" . FORUMS_TABLE . "_seq',(select case when max(forum_id)>0 then max(forum_id)+1 else 1 end from " . FORUMS_TABLE . '));'); + break; + + case 'mssql_odbc': + case 'mssqlnative': + $db->sql_query('SET IDENTITY_INSERT ' . FORUMS_TABLE . ' OFF'); + break; + + case 'oracle': + $result = $db->sql_query('SELECT MAX(forum_id) as max_id FROM ' . FORUMS_TABLE); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $largest_id = (int) $row['max_id']; + + if ($largest_id) + { + $db->sql_query('DROP SEQUENCE ' . FORUMS_TABLE . '_seq'); + $db->sql_query('CREATE SEQUENCE ' . FORUMS_TABLE . '_seq START WITH ' . ($largest_id + 1)); + } + break; + } +} + +/** +* Function for recoding text with the default language +* +* @param string $text text to recode to utf8 +* @param bool $grab_user_lang if set to true the function tries to use $convert_row['user_lang'] (and falls back to $convert_row['poster_id']) instead of the boards default language +*/ +function phpbb_set_encoding($text, $grab_user_lang = true) +{ + global $lang_enc_array, $convert_row; + global $convert, $phpEx; + + /*static $lang_enc_array = array( + 'korean' => 'euc-kr', + 'serbian' => 'windows-1250', + 'polish' => 'iso-8859-2', + 'kurdish' => 'windows-1254', + 'slovak' => 'Windows-1250', + 'russian' => 'windows-1251', + 'estonian' => 'iso-8859-4', + 'chinese_simplified' => 'gb2312', + 'macedonian' => 'windows-1251', + 'azerbaijani' => 'UTF-8', + 'romanian' => 'iso-8859-2', + 'romanian_diacritice' => 'iso-8859-2', + 'lithuanian' => 'windows-1257', + 'turkish' => 'iso-8859-9', + 'ukrainian' => 'windows-1251', + 'japanese' => 'shift_jis', + 'hungarian' => 'ISO-8859-2', + 'romanian_no_diacritics' => 'iso-8859-2', + 'mongolian' => 'UTF-8', + 'slovenian' => 'windows-1250', + 'bosnian' => 'windows-1250', + 'czech' => 'Windows-1250', + 'farsi' => 'Windows-1256', + 'croatian' => 'windows-1250', + 'greek' => 'iso-8859-7', + 'russian_tu' => 'windows-1251', + 'sakha' => 'UTF-8', + 'serbian_cyrillic' => 'windows-1251', + 'bulgarian' => 'windows-1251', + 'chinese_traditional_taiwan' => 'big5', + 'chinese_traditional' => 'big5', + 'arabic' => 'windows-1256', + 'hebrew' => 'WINDOWS-1255', + 'thai' => 'windows-874', + //'chinese_traditional_taiwan' => 'utf-8' // custom modified, we may have to do an include :-( + );*/ + + if (empty($lang_enc_array)) + { + $lang_enc_array = array(); + } + + $get_lang = trim(get_config_value('default_lang')); + + // Do we need the users language encoding? + if ($grab_user_lang && !empty($convert_row)) + { + if (!empty($convert_row['user_lang'])) + { + $get_lang = trim($convert_row['user_lang']); + } + else if (!empty($convert_row['poster_id'])) + { + global $src_db, $same_db; + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + $sql = 'SELECT user_lang + FROM ' . $convert->src_table_prefix . 'users + WHERE user_id = ' . (int) $convert_row['poster_id']; + $result = $src_db->sql_query($sql); + $get_lang = (string) $src_db->sql_fetchfield('user_lang'); + $src_db->sql_freeresult($result); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + + $get_lang = (!trim($get_lang)) ? trim(get_config_value('default_lang')) : trim($get_lang); + } + } + + if (!isset($lang_enc_array[$get_lang])) + { + $filename = $convert->options['forum_path'] . '/language/lang_' . $get_lang . '/lang_main.' . $phpEx; + + if (!file_exists($filename)) + { + $get_lang = trim(get_config_value('default_lang')); + } + + if (!isset($lang_enc_array[$get_lang])) + { + include($convert->options['forum_path'] . '/language/lang_' . $get_lang . '/lang_main.' . $phpEx); + $lang_enc_array[$get_lang] = $lang['ENCODING']; + unset($lang); + } + } + + return utf8_recode($text, $lang_enc_array[$get_lang]); +} + +/** +* Same as phpbb_set_encoding, but forcing boards default language +*/ +function phpbb_set_default_encoding($text) +{ + return phpbb_set_encoding($text, false); +} + +/** +* Convert Birthday from Birthday MOD to phpBB Format +*/ +function phpbb_get_birthday($birthday = '') +{ + if (defined('MOD_BIRTHDAY_TERRA')) + { + $birthday = (string) $birthday; + + // stored as month, day, year + if (!$birthday) + { + return ' 0- 0- 0'; + } + + // We use the original mod code to retrieve the birthday (not ideal) + preg_match('/(..)(..)(....)/', sprintf('%08d', $birthday), $birthday_parts); + + $month = $birthday_parts[1]; + $day = $birthday_parts[2]; + $year = $birthday_parts[3]; + + return sprintf('%2d-%2d-%4d', $day, $month, $year); + } + else + { + $birthday = (int) $birthday; + + if (!$birthday || $birthday == 999999) + { + return ' 0- 0- 0'; + } + + // The birthday mod from niels is using this code to transform to day/month/year + return sprintf('%2d-%2d-%4d', gmdate('j', $birthday * 86400 + 1), gmdate('n', $birthday * 86400 + 1), gmdate('Y', $birthday * 86400 + 1)); + } +} + +/** +* Return correct user id value +* Everyone's id will be one higher to allow the guest/anonymous user to have a positive id as well +*/ +function phpbb_user_id($user_id) +{ + global $config; + + // Increment user id if the old forum is having a user with the id 1 + if (!isset($config['increment_user_id'])) + { + global $src_db, $same_db, $convert; + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + // Now let us set a temporary config variable for user id incrementing + $sql = "SELECT user_id + FROM {$convert->src_table_prefix}users + WHERE user_id = 1"; + $result = $src_db->sql_query($sql); + $id = (int) $src_db->sql_fetchfield('user_id'); + $src_db->sql_freeresult($result); + + // Try to get the maximum user id possible... + $sql = "SELECT MAX(user_id) AS max_user_id + FROM {$convert->src_table_prefix}users"; + $result = $src_db->sql_query($sql); + $max_id = (int) $src_db->sql_fetchfield('max_user_id'); + $src_db->sql_freeresult($result); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + + // If there is a user id 1, we need to increment user ids. :/ + if ($id === 1) + { + $config->set('increment_user_id', ($max_id + 1), false); + $config['increment_user_id'] = $max_id + 1; + } + else + { + $config->set('increment_user_id', 0, false); + $config['increment_user_id'] = 0; + } + } + + // If the old user id is -1 in 2.0.x it is the anonymous user... + if ($user_id == -1) + { + return ANONYMOUS; + } + + if (!empty($config['increment_user_id']) && $user_id == 1) + { + return $config['increment_user_id']; + } + + // A user id of 0 can happen, for example within the ban table if no user is banned... + // Within the posts and topics table this can be "dangerous" but is the fault of the user + // having mods installed (a poster id of 0 is not possible in 2.0.x). + // Therefore, we return the user id "as is". + + return (int) $user_id; +} + +/** +* Return correct user id value +* Everyone's id will be one higher to allow the guest/anonymous user to have a positive id as well +*/ +function phpbb_topic_replies_to_posts($num_replies) +{ + return (int) $num_replies + 1; +} + +/* Copy additional table fields from old forum to new forum if user wants this (for Mod compatibility for example) +function phpbb_copy_table_fields() +{ +} +*/ + +/** +* Convert authentication +* user, group and forum table has to be filled in order to work +*/ +function phpbb_convert_authentication($mode) +{ + global $db, $src_db, $same_db, $convert, $config; + + if ($mode == 'start') + { + $db->sql_query($convert->truncate_statement . ACL_USERS_TABLE); + $db->sql_query($convert->truncate_statement . ACL_GROUPS_TABLE); + + // What we will do is handling all 2.0.x admins as founder to replicate what is common in 2.0.x. + // After conversion the main admin need to make sure he is removing permissions and the founder status if wanted. + + // Grab user ids of users with user_level of ADMIN + $sql = "SELECT user_id + FROM {$convert->src_table_prefix}users + WHERE user_level = 1 + ORDER BY user_regdate ASC"; + $result = $src_db->sql_query($sql); + + while ($row = $src_db->sql_fetchrow($result)) + { + $user_id = (int) phpbb_user_id($row['user_id']); + // Set founder admin... + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_type = ' . USER_FOUNDER . " + WHERE user_id = $user_id"; + $db->sql_query($sql); + } + $src_db->sql_freeresult($result); + + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $db->sql_escape('BOTS') . "'"; + $result = $db->sql_query($sql); + $bot_group_id = (int) $db->sql_fetchfield('group_id'); + $db->sql_freeresult($result); + } + + // Grab forum auth information + $sql = "SELECT * + FROM {$convert->src_table_prefix}forums"; + $result = $src_db->sql_query($sql); + + $forum_access = array(); + while ($row = $src_db->sql_fetchrow($result)) + { + $forum_access[$row['forum_id']] = $row; + } + $src_db->sql_freeresult($result); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + // Grab user auth information from 2.0.x board + $sql = "SELECT ug.user_id, aa.* + FROM {$convert->src_table_prefix}auth_access aa, {$convert->src_table_prefix}user_group ug, {$convert->src_table_prefix}groups g, {$convert->src_table_prefix}forums f + WHERE g.group_id = aa.group_id + AND g.group_single_user = 1 + AND ug.group_id = g.group_id + AND f.forum_id = aa.forum_id"; + $result = $src_db->sql_query($sql); + + $user_access = array(); + while ($row = $src_db->sql_fetchrow($result)) + { + $user_access[$row['forum_id']][] = $row; + } + $src_db->sql_freeresult($result); + + // Grab group auth information + $sql = "SELECT g.group_id, aa.* + FROM {$convert->src_table_prefix}auth_access aa, {$convert->src_table_prefix}groups g + WHERE g.group_id = aa.group_id + AND g.group_single_user <> 1"; + $result = $src_db->sql_query($sql); + + $group_access = array(); + while ($row = $src_db->sql_fetchrow($result)) + { + $group_access[$row['forum_id']][] = $row; + } + $src_db->sql_freeresult($result); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + + // Add Forum Access List + $auth_map = array( + 'auth_view' => array('f_', 'f_list'), + 'auth_read' => array('f_read', 'f_search'), + 'auth_post' => array('f_post', 'f_bbcode', 'f_smilies', 'f_img', 'f_sigs', 'f_postcount', 'f_report', 'f_subscribe', 'f_print', 'f_email'), + 'auth_reply' => 'f_reply', + 'auth_edit' => 'f_edit', + 'auth_delete' => 'f_delete', + 'auth_pollcreate' => 'f_poll', + 'auth_vote' => 'f_vote', + 'auth_announce' => array('f_announce', 'f_announce_global'), + 'auth_sticky' => 'f_sticky', + 'auth_attachments' => array('f_attach', 'f_download'), + 'auth_download' => 'f_download', + ); + + // Define the ACL constants used in 2.0 to make the code slightly more readable + define('AUTH_ALL', 0); + define('AUTH_REG', 1); + define('AUTH_ACL', 2); + define('AUTH_MOD', 3); + define('AUTH_ADMIN', 5); + + // A mapping of the simple permissions used by 2.0 + $simple_auth_ary = array( + 'public' => array( + 'auth_view' => AUTH_ALL, + 'auth_read' => AUTH_ALL, + 'auth_post' => AUTH_ALL, + 'auth_reply' => AUTH_ALL, + 'auth_edit' => AUTH_REG, + 'auth_delete' => AUTH_REG, + 'auth_sticky' => AUTH_MOD, + 'auth_announce' => AUTH_MOD, + 'auth_vote' => AUTH_REG, + 'auth_pollcreate' => AUTH_REG, + ), + 'registered' => array( + 'auth_view' => AUTH_ALL, + 'auth_read' => AUTH_ALL, + 'auth_post' => AUTH_REG, + 'auth_reply' => AUTH_REG, + 'auth_edit' => AUTH_REG, + 'auth_delete' => AUTH_REG, + 'auth_sticky' => AUTH_MOD, + 'auth_announce' => AUTH_MOD, + 'auth_vote' => AUTH_REG, + 'auth_pollcreate' => AUTH_REG, + ), + 'registered_hidden' => array( + 'auth_view' => AUTH_REG, + 'auth_read' => AUTH_REG, + 'auth_post' => AUTH_REG, + 'auth_reply' => AUTH_REG, + 'auth_edit' => AUTH_REG, + 'auth_delete' => AUTH_REG, + 'auth_sticky' => AUTH_MOD, + 'auth_announce' => AUTH_MOD, + 'auth_vote' => AUTH_REG, + 'auth_pollcreate' => AUTH_REG, + ), + 'private' => array( + 'auth_view' => AUTH_ALL, + 'auth_read' => AUTH_ACL, + 'auth_post' => AUTH_ACL, + 'auth_reply' => AUTH_ACL, + 'auth_edit' => AUTH_ACL, + 'auth_delete' => AUTH_ACL, + 'auth_sticky' => AUTH_ACL, + 'auth_announce' => AUTH_MOD, + 'auth_vote' => AUTH_ACL, + 'auth_pollcreate' => AUTH_ACL, + ), + 'private_hidden' => array( + 'auth_view' => AUTH_ACL, + 'auth_read' => AUTH_ACL, + 'auth_post' => AUTH_ACL, + 'auth_reply' => AUTH_ACL, + 'auth_edit' => AUTH_ACL, + 'auth_delete' => AUTH_ACL, + 'auth_sticky' => AUTH_ACL, + 'auth_announce' => AUTH_MOD, + 'auth_vote' => AUTH_ACL, + 'auth_pollcreate' => AUTH_ACL, + ), + 'moderator' => array( + 'auth_view' => AUTH_ALL, + 'auth_read' => AUTH_MOD, + 'auth_post' => AUTH_MOD, + 'auth_reply' => AUTH_MOD, + 'auth_edit' => AUTH_MOD, + 'auth_delete' => AUTH_MOD, + 'auth_sticky' => AUTH_MOD, + 'auth_announce' => AUTH_MOD, + 'auth_vote' => AUTH_MOD, + 'auth_pollcreate' => AUTH_MOD, + ), + 'moderator_hidden' => array( + 'auth_view' => AUTH_MOD, + 'auth_read' => AUTH_MOD, + 'auth_post' => AUTH_MOD, + 'auth_reply' => AUTH_MOD, + 'auth_edit' => AUTH_MOD, + 'auth_delete' => AUTH_MOD, + 'auth_sticky' => AUTH_MOD, + 'auth_announce' => AUTH_MOD, + 'auth_vote' => AUTH_MOD, + 'auth_pollcreate' => AUTH_MOD, + ), + ); + + if ($mode == 'start') + { + user_group_auth('guests', 'SELECT user_id, {GUESTS} FROM ' . USERS_TABLE . ' WHERE user_id = ' . ANONYMOUS, false); + user_group_auth('registered', 'SELECT user_id, {REGISTERED} FROM ' . USERS_TABLE . ' WHERE user_id <> ' . ANONYMOUS . " AND group_id <> $bot_group_id", false); + + // Selecting from old table + if (!empty($config['increment_user_id'])) + { + $auth_sql = 'SELECT user_id, {ADMINISTRATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1 AND user_id <> 1'; + user_group_auth('administrators', $auth_sql, true); + + $auth_sql = 'SELECT ' . $config['increment_user_id'] . ' as user_id, {ADMINISTRATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1 AND user_id = 1'; + user_group_auth('administrators', $auth_sql, true); + } + else + { + $auth_sql = 'SELECT user_id, {ADMINISTRATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1'; + user_group_auth('administrators', $auth_sql, true); + } + + if (!empty($config['increment_user_id'])) + { + $auth_sql = 'SELECT user_id, {GLOBAL_MODERATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1 AND user_id <> 1'; + user_group_auth('global_moderators', $auth_sql, true); + + $auth_sql = 'SELECT ' . $config['increment_user_id'] . ' as user_id, {GLOBAL_MODERATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1 AND user_id = 1'; + user_group_auth('global_moderators', $auth_sql, true); + } + else + { + $auth_sql = 'SELECT user_id, {GLOBAL_MODERATORS} FROM ' . $convert->src_table_prefix . 'users WHERE user_level = 1'; + user_group_auth('global_moderators', $auth_sql, true); + } + } + else if ($mode == 'first') + { + // Go through all 2.0.x forums + foreach ($forum_access as $forum) + { + $new_forum_id = (int) $forum['forum_id']; + + // Administrators have full access to all forums whatever happens + mass_auth('group_role', $new_forum_id, 'administrators', 'FORUM_FULL'); + + $matched_type = ''; + foreach ($simple_auth_ary as $key => $auth_levels) + { + $matched = 1; + foreach ($auth_levels as $k => $level) + { + if ($forum[$k] != $auth_levels[$k]) + { + $matched = 0; + } + } + + if ($matched) + { + $matched_type = $key; + break; + } + } + + switch ($matched_type) + { + case 'public': + mass_auth('group_role', $new_forum_id, 'guests', 'FORUM_LIMITED'); + mass_auth('group_role', $new_forum_id, 'registered', 'FORUM_LIMITED_POLLS'); + mass_auth('group_role', $new_forum_id, 'bots', 'FORUM_BOT'); + break; + + case 'registered': + mass_auth('group_role', $new_forum_id, 'guests', 'FORUM_READONLY'); + mass_auth('group_role', $new_forum_id, 'bots', 'FORUM_BOT'); + + // no break; + + case 'registered_hidden': + mass_auth('group_role', $new_forum_id, 'registered', 'FORUM_POLLS'); + break; + + case 'private': + case 'private_hidden': + case 'moderator': + case 'moderator_hidden': + default: + // The permissions don't match a simple set, so we're going to have to map them directly + + // No post approval for all, in 2.0.x this feature does not exist + mass_auth('group', $new_forum_id, 'guests', 'f_noapprove', ACL_YES); + mass_auth('group', $new_forum_id, 'registered', 'f_noapprove', ACL_YES); + + // Go through authentication map + foreach ($auth_map as $old_auth_key => $new_acl) + { + // If old authentication key does not exist we continue + // This is helpful for mods adding additional authentication fields, we need to add them to the auth_map array + if (!isset($forum[$old_auth_key])) + { + continue; + } + + // Now set the new ACL correctly + switch ($forum[$old_auth_key]) + { + // AUTH_ALL + case AUTH_ALL: + mass_auth('group', $new_forum_id, 'guests', $new_acl, ACL_YES); + mass_auth('group', $new_forum_id, 'bots', $new_acl, ACL_YES); + mass_auth('group', $new_forum_id, 'registered', $new_acl, ACL_YES); + break; + + // AUTH_REG + case AUTH_REG: + mass_auth('group', $new_forum_id, 'registered', $new_acl, ACL_YES); + break; + + // AUTH_ACL + case AUTH_ACL: + // Go through the old group access list for this forum + if (isset($group_access[$forum['forum_id']])) + { + foreach ($group_access[$forum['forum_id']] as $index => $access) + { + // We only check for ACL_YES equivalence entry + if (isset($access[$old_auth_key]) && $access[$old_auth_key] == 1) + { + mass_auth('group', $new_forum_id, (int) $access['group_id'], $new_acl, ACL_YES); + } + } + } + + if (isset($user_access[$forum['forum_id']])) + { + foreach ($user_access[$forum['forum_id']] as $index => $access) + { + // We only check for ACL_YES equivalence entry + if (isset($access[$old_auth_key]) && $access[$old_auth_key] == 1) + { + mass_auth('user', $new_forum_id, (int) phpbb_user_id($access['user_id']), $new_acl, ACL_YES); + } + } + } + break; + + // AUTH_MOD + case AUTH_MOD: + if (isset($group_access[$forum['forum_id']])) + { + foreach ($group_access[$forum['forum_id']] as $index => $access) + { + // We only check for ACL_YES equivalence entry + if (isset($access[$old_auth_key]) && $access[$old_auth_key] == 1) + { + mass_auth('group', $new_forum_id, (int) $access['group_id'], $new_acl, ACL_YES); + } + } + } + + if (isset($user_access[$forum['forum_id']])) + { + foreach ($user_access[$forum['forum_id']] as $index => $access) + { + // We only check for ACL_YES equivalence entry + if (isset($access[$old_auth_key]) && $access[$old_auth_key] == 1) + { + mass_auth('user', $new_forum_id, (int) phpbb_user_id($access['user_id']), $new_acl, ACL_YES); + } + } + } + break; + } + } + break; + } + } + } + else if ($mode == 'second') + { + // Assign permission roles and other default permissions + + // guests having u_download and u_search ability + $db->sql_query('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) SELECT ' . get_group_id('guests') . ', 0, auth_option_id, 0, 1 FROM ' . ACL_OPTIONS_TABLE . " WHERE auth_option IN ('u_', 'u_download', 'u_search')"); + + // administrators/global mods having full user features + mass_auth('group_role', 0, 'administrators', 'USER_FULL'); + mass_auth('group_role', 0, 'global_moderators', 'USER_FULL'); + + // By default all converted administrators are given full access + mass_auth('group_role', 0, 'administrators', 'ADMIN_FULL'); + + // All registered users are assigned the standard user role + mass_auth('group_role', 0, 'registered', 'USER_STANDARD'); + mass_auth('group_role', 0, 'registered_coppa', 'USER_STANDARD'); + + // Instead of administrators being global moderators we give the MOD_FULL role to global mods (admins already assigned to this group) + mass_auth('group_role', 0, 'global_moderators', 'MOD_FULL'); + + // And now those who have had their avatar rights removed get assigned a more restrictive role + $sql = 'SELECT user_id FROM ' . $convert->src_table_prefix . 'users + WHERE user_allowavatar = 0 + AND user_id > 0'; + $result = $src_db->sql_query($sql); + + while ($row = $src_db->sql_fetchrow($result)) + { + mass_auth('user_role', 0, (int) phpbb_user_id($row['user_id']), 'USER_NOAVATAR'); + } + $src_db->sql_freeresult($result); + + // And the same for those who have had their PM rights removed + $sql = 'SELECT user_id FROM ' . $convert->src_table_prefix . 'users + WHERE user_allow_pm = 0 + AND user_id > 0'; + $result = $src_db->sql_query($sql); + + while ($row = $src_db->sql_fetchrow($result)) + { + mass_auth('user_role', 0, (int) phpbb_user_id($row['user_id']), 'USER_NOPM'); + } + $src_db->sql_freeresult($result); + } + else if ($mode == 'third') + { + // And now the moderators + // We make sure that they have at least standard access to the forums they moderate in addition to the moderating permissions + + $mod_post_map = array( + 'auth_announce' => array('f_announce', 'f_announce_global'), + 'auth_sticky' => 'f_sticky' + ); + + foreach ($user_access as $forum_id => $access_map) + { + $forum_id = (int) $forum_id; + + foreach ($access_map as $access) + { + if (isset($access['auth_mod']) && $access['auth_mod'] == 1) + { + mass_auth('user_role', $forum_id, (int) phpbb_user_id($access['user_id']), 'MOD_STANDARD'); + mass_auth('user_role', $forum_id, (int) phpbb_user_id($access['user_id']), 'FORUM_STANDARD'); + foreach ($mod_post_map as $old => $new) + { + if (isset($forum_access[$forum_id]) && isset($forum_access[$forum_id][$old]) && $forum_access[$forum_id][$old] == AUTH_MOD) + { + mass_auth('user', $forum_id, (int) phpbb_user_id($access['user_id']), $new, ACL_YES); + } + } + } + } + } + + foreach ($group_access as $forum_id => $access_map) + { + $forum_id = (int) $forum_id; + + foreach ($access_map as $access) + { + if (isset($access['auth_mod']) && $access['auth_mod'] == 1) + { + mass_auth('group_role', $forum_id, (int) $access['group_id'], 'MOD_STANDARD'); + mass_auth('group_role', $forum_id, (int) $access['group_id'], 'FORUM_STANDARD'); + foreach ($mod_post_map as $old => $new) + { + if (isset($forum_access[$forum_id]) && isset($forum_access[$forum_id][$old]) && $forum_access[$forum_id][$old] == AUTH_MOD) + { + mass_auth('group', $forum_id, (int) $access['group_id'], $new, ACL_YES); + } + } + } + } + } + + // We grant everyone readonly access to the categories to ensure that the forums are visible + $sql = 'SELECT forum_id, forum_name, parent_id, left_id, right_id + FROM ' . FORUMS_TABLE . ' + ORDER BY left_id ASC'; + $result = $db->sql_query($sql); + + $parent_forums = $forums = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['parent_id'] == 0) + { + mass_auth('group_role', $row['forum_id'], 'administrators', 'FORUM_FULL'); + mass_auth('group_role', $row['forum_id'], 'global_moderators', 'FORUM_FULL'); + $parent_forums[] = $row; + } + else + { + $forums[] = $row; + } + } + $db->sql_freeresult($result); + + global $auth; + + // Let us see which groups have access to these forums... + foreach ($parent_forums as $row) + { + // Get the children + $branch = $forum_ids = array(); + + foreach ($forums as $key => $_row) + { + if ($_row['left_id'] > $row['left_id'] && $_row['left_id'] < $row['right_id']) + { + $branch[] = $_row; + $forum_ids[] = $_row['forum_id']; + continue; + } + } + + if (count($forum_ids)) + { + // Now make sure the user is able to read these forums + $hold_ary = $auth->acl_group_raw_data(false, 'f_list', $forum_ids); + + if (empty($hold_ary)) + { + continue; + } + + foreach ($hold_ary as $g_id => $f_id_ary) + { + $set_group = false; + + foreach ($f_id_ary as $f_id => $auth_ary) + { + foreach ($auth_ary as $auth_option => $setting) + { + if ($setting == ACL_YES) + { + $set_group = true; + break 2; + } + } + } + + if ($set_group) + { + mass_auth('group', $row['forum_id'], $g_id, 'f_list', ACL_YES); + } + } + } + } + } +} + +/** +* Set primary group. +* Really simple and only based on user_level (remaining groups will be assigned later) +*/ +function phpbb_set_primary_group($user_level) +{ + global $convert_row; + + if ($user_level == 1) + { + return get_group_id('administrators'); + } +/* else if ($user_level == 2) + { + return get_group_id('global_moderators'); + } + else if ($user_level == 0 && $convert_row['user_active'])*/ + else if ($convert_row['user_active']) + { + return get_group_id('registered'); + } + + return 0; +} + +/** +* Convert the group name, making sure to avoid conflicts with 3.0 special groups +*/ +function phpbb_convert_group_name($group_name) +{ + $default_groups = array( + 'GUESTS', + 'REGISTERED', + 'REGISTERED_COPPA', + 'GLOBAL_MODERATORS', + 'ADMINISTRATORS', + 'BOTS', + ); + + if (in_array(strtoupper($group_name), $default_groups)) + { + return 'phpBB2 - ' . $group_name; + } + + return phpbb_set_default_encoding($group_name); +} + +/** +* Convert the group type constants +*/ +function phpbb_convert_group_type($group_type) +{ + switch ($group_type) + { + case 0: + return GROUP_OPEN; + break; + + case 1: + return GROUP_CLOSED; + break; + + case 2: + return GROUP_HIDDEN; + break; + } + + // Never return GROUP_SPECIAL here, because only phpBB3's default groups are allowed to have this type set. + return GROUP_HIDDEN; +} + +/** +* Convert the topic type constants +*/ +function phpbb_convert_topic_type($topic_type) +{ + switch ($topic_type) + { + case 0: + return POST_NORMAL; + break; + + case 1: + return POST_STICKY; + break; + + case 2: + return POST_ANNOUNCE; + break; + + case 3: + return POST_GLOBAL; + break; + } + + return POST_NORMAL; +} + +function phpbb_replace_size($matches) +{ + return '[size=' . min(200, ceil(100.0 * (((double) $matches[1])/12.0))) . ':' . $matches[2] . ']'; +} + +/** +* Reparse the message stripping out the bbcode_uid values and adding new ones and setting the bitfield +* @todo What do we want to do about HTML in messages - currently it gets converted to the entities, but there may be some objections to this +*/ +function phpbb_prepare_message($message) +{ + global $convert, $user, $convert_row, $message_parser; + + if (!$message) + { + $convert->row['mp_bbcode_bitfield'] = $convert_row['mp_bbcode_bitfield'] = 0; + return ''; + } + + // Decode phpBB 2.0.x Message + if (isset($convert->row['old_bbcode_uid']) && $convert->row['old_bbcode_uid'] != '') + { + // Adjust size... + if (strpos($message, '[size=') !== false) + { + $message = preg_replace_callback('/\[size=(\d*):(' . $convert->row['old_bbcode_uid'] . ')\]/', 'phpbb_replace_size', $message); + } + + $message = preg_replace('/\:(([a-z0-9]:)?)' . $convert->row['old_bbcode_uid'] . '/s', '', $message); + } + + if (strpos($message, '[quote=') !== false) + { + $message = preg_replace('/\[quote="(.*?)"\]/s', '[quote="\1"]', $message); + $message = preg_replace('/\[quote=\\\"(.*?)\\\"\]/s', '[quote="\1"]', $message); + + // let's hope that this solves more problems than it causes. Deal with escaped quotes. + $message = str_replace('\"', '"', $message); + $message = str_replace('\"', '"', $message); + } + + $message = str_replace('
', "\n", $message); + $message = str_replace('<', '<', $message); + $message = str_replace('>', '>', $message); + + // make the post UTF-8 + $message = phpbb_set_encoding($message); + + $message_parser->warn_msg = array(); // Reset the errors from the previous message + $message_parser->bbcode_uid = make_uid($convert->row['post_time']); + $message_parser->message = $message; + unset($message); + + // Make sure options are set. +// $enable_html = (!isset($row['enable_html'])) ? false : $row['enable_html']; + $enable_bbcode = (!isset($convert->row['enable_bbcode'])) ? true : $convert->row['enable_bbcode']; + $enable_smilies = (!isset($convert->row['enable_smilies'])) ? true : $convert->row['enable_smilies']; + $enable_magic_url = (!isset($convert->row['enable_magic_url'])) ? true : $convert->row['enable_magic_url']; + + // parse($allow_bbcode, $allow_magic_url, $allow_smilies, $allow_img_bbcode = true, $allow_flash_bbcode = true, $allow_quote_bbcode = true, $allow_url_bbcode = true, $update_this_message = true, $mode = 'post') + $message_parser->parse($enable_bbcode, $enable_magic_url, $enable_smilies); + + if (count($message_parser->warn_msg)) + { + $msg_id = isset($convert->row['post_id']) ? $convert->row['post_id'] : $convert->row['privmsgs_id']; + $convert->p_master->error('' . $user->lang['POST_ID'] . ': ' . $msg_id . ' ' . $user->lang['CONV_ERROR_MESSAGE_PARSER'] . ':

' . implode('
', $message_parser->warn_msg), __LINE__, __FILE__, true); + } + + $convert->row['mp_bbcode_bitfield'] = $convert_row['mp_bbcode_bitfield'] = $message_parser->bbcode_bitfield; + + $message = $message_parser->message; + unset($message_parser->message); + + return $message; +} + +/** +* Return the bitfield calculated by the previous function +*/ +function get_bbcode_bitfield() +{ + global $convert_row; + + return $convert_row['mp_bbcode_bitfield']; +} + +/** +* Determine the last user to edit a post +* In practice we only tracked edits by the original poster in 2.0.x so this will only be set if they had edited their own post +*/ +function phpbb_post_edit_user() +{ + global $convert_row; + + if (isset($convert_row['post_edit_count'])) + { + return phpbb_user_id($convert_row['poster_id']); + } + + return 0; +} + +/** +* Obtain the path to uploaded files on the 2.0.x forum +* This is only used if the Attachment MOD was installed +*/ +function phpbb_get_files_dir() +{ + if (!defined('MOD_ATTACHMENT')) + { + return; + } + + global $src_db, $same_db, $convert, $user; + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + $sql = 'SELECT config_value AS upload_dir + FROM ' . $convert->src_table_prefix . "attachments_config + WHERE config_name = 'upload_dir'"; + $result = $src_db->sql_query($sql); + $upload_path = $src_db->sql_fetchfield('upload_dir'); + $src_db->sql_freeresult($result); + + $sql = 'SELECT config_value AS ftp_upload + FROM ' . $convert->src_table_prefix . "attachments_config + WHERE config_name = 'allow_ftp_upload'"; + $result = $src_db->sql_query($sql); + $ftp_upload = (int) $src_db->sql_fetchfield('ftp_upload'); + $src_db->sql_freeresult($result); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + + if ($ftp_upload) + { + $convert->p_master->error($user->lang['CONV_ERROR_ATTACH_FTP_DIR'], __LINE__, __FILE__); + } + + return $upload_path; +} + +/** +* Copy thumbnails of uploaded images from the 2.0.x forum +* This is only used if the Attachment MOD was installed +*/ +function phpbb_copy_thumbnails() +{ + global $convert, $config, $phpbb_root_path; + + $src_path = $convert->options['forum_path'] . '/' . phpbb_get_files_dir() . '/thumbs/'; + + if ($handle = @opendir($src_path)) + { + while ($entry = readdir($handle)) + { + if ($entry[0] == '.') + { + continue; + } + + if (is_dir($src_path . $entry)) + { + continue; + } + else + { + copy_file($src_path . $entry, $config['upload_path'] . '/' . preg_replace('/^t_/', 'thumb_', $entry)); + @unlink($phpbb_root_path . $config['upload_path'] . '/thumbs/' . $entry); + } + } + closedir($handle); + } +} + +/** +* Convert the attachment category constants +* This is only used if the Attachment MOD was installed +*/ +function phpbb_attachment_category($cat_id) +{ + switch ($cat_id) + { + case 1: + return ATTACHMENT_CATEGORY_IMAGE; + break; + + case 2: + return ATTACHMENT_CATEGORY_WM; + break; + + case 3: + return ATTACHMENT_CATEGORY_FLASH; + break; + } + + return ATTACHMENT_CATEGORY_NONE; +} + +/** +* Convert the attachment extension names +* This is only used if the Attachment MOD was installed +*/ +function phpbb_attachment_extension_group_name() +{ + global $db, $phpbb_root_path, $phpEx; + + // Update file extension group names to use language strings. + $sql = 'SELECT lang_dir + FROM ' . LANG_TABLE; + $result = $db->sql_query($sql); + + $extension_groups_updated = array(); + while ($lang_dir = $db->sql_fetchfield('lang_dir')) + { + $lang_dir = basename($lang_dir); + $lang_file = $phpbb_root_path . 'language/' . $lang_dir . '/acp/attachments.' . $phpEx; + + if (!file_exists($lang_file)) + { + continue; + } + + $lang = array(); + include($lang_file); + + foreach ($lang as $lang_key => $lang_val) + { + if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0) + { + continue; + } + + $sql_ary = array( + 'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_' + ); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . " + WHERE group_name = '" . $db->sql_escape($lang_val) . "'"; + $db->sql_query($sql); + + $extension_groups_updated[$lang_key] = true; + } + } + $db->sql_freeresult($result); +} + +/** +* Obtain list of forums in which different attachment categories can be used +*/ +function phpbb_attachment_forum_perms($forum_permissions) +{ + if (empty($forum_permissions)) + { + return ''; + } + + // Decode forum permissions + $forum_ids = array(); + + $one_char_encoding = '#'; + $two_char_encoding = '.'; + + $auth_len = 1; + for ($pos = 0; $pos < strlen($forum_permissions); $pos += $auth_len) + { + $forum_auth = substr($forum_permissions, $pos, 1); + if ($forum_auth == $one_char_encoding) + { + $auth_len = 1; + continue; + } + else if ($forum_auth == $two_char_encoding) + { + $auth_len = 2; + $pos--; + continue; + } + + $forum_auth = substr($forum_permissions, $pos, $auth_len); + $forum_id = base64_unpack($forum_auth); + + $forum_ids[] = (int) $forum_id; + } + + if (count($forum_ids)) + { + return attachment_forum_perms($forum_ids); + } + + return ''; +} + +/** +* Convert the avatar type constants +*/ +function phpbb_avatar_type($type) +{ + switch ($type) + { + case 1: + return AVATAR_UPLOAD; + break; + + case 2: + return AVATAR_REMOTE; + break; + + case 3: + return AVATAR_GALLERY; + break; + } + + return 0; +} + + +/** +* Just undos the replacing of '<' and '>' +*/ +function phpbb_smilie_html_decode($code) +{ + $code = str_replace('<', '<', $code); + return str_replace('>', '>', $code); +} + +/** +* Transfer avatars, copying the image if it was uploaded +*/ +function phpbb_import_avatar($user_avatar) +{ + global $convert_row; + + if (!$convert_row['user_avatar_type']) + { + return ''; + } + else if ($convert_row['user_avatar_type'] == 1) + { + // Uploaded avatar + return import_avatar($user_avatar, false, $convert_row['user_id']); + } + else if ($convert_row['user_avatar_type'] == 2) + { + // Remote avatar + return $user_avatar; + } + else if ($convert_row['user_avatar_type'] == 3) + { + // Gallery avatar + return $user_avatar; + } + + return ''; +} + + +/** +* Find out about the avatar's dimensions +*/ +function phpbb_get_avatar_height($user_avatar) +{ + global $convert_row; + + if (empty($convert_row['user_avatar_type'])) + { + return 0; + } + return get_avatar_height($user_avatar, 'phpbb_avatar_type', $convert_row['user_avatar_type']); +} + + +/** +* Find out about the avatar's dimensions +*/ +function phpbb_get_avatar_width($user_avatar) +{ + global $convert_row; + + if (empty($convert_row['user_avatar_type'])) + { + return 0; + } + + return get_avatar_width($user_avatar, 'phpbb_avatar_type', $convert_row['user_avatar_type']); +} + + +/** +* Calculate the correct to_address field for private messages +*/ +function phpbb_privmsgs_to_userid($to_userid) +{ + return 'u_' . phpbb_user_id($to_userid); +} + +/** +* Calculate whether a private message was unread using the bitfield +*/ +function phpbb_unread_pm($pm_type) +{ + return ($pm_type == 5) ? 1 : 0; +} + +/** +* Calculate whether a private message was new using the bitfield +*/ +function phpbb_new_pm($pm_type) +{ + return ($pm_type == 1) ? 1 : 0; +} + +/** +* Obtain the folder_id for the custom folder created to replace the savebox from 2.0.x (used to store saved private messages) +*/ +function phpbb_get_savebox_id($user_id) +{ + global $db; + + $user_id = phpbb_user_id($user_id); + + // Only one custom folder, check only one + $sql = 'SELECT folder_id + FROM ' . PRIVMSGS_FOLDER_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query_limit($sql, 1); + $folder_id = (int) $db->sql_fetchfield('folder_id'); + $db->sql_freeresult($result); + + return $folder_id; +} + +/** +* Transfer attachment specific configuration options +* These were not stored in the main config table on 2.0.x +* This is only used if the Attachment MOD was installed +*/ +function phpbb_import_attach_config() +{ + global $src_db, $same_db, $convert, $config; + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'binary'"); + } + + $sql = 'SELECT * + FROM ' . $convert->src_table_prefix . 'attachments_config'; + $result = $src_db->sql_query($sql); + + if ($convert->mysql_convert && $same_db) + { + $src_db->sql_query("SET NAMES 'utf8'"); + } + + $attach_config = array(); + while ($row = $src_db->sql_fetchrow($result)) + { + $attach_config[$row['config_name']] = $row['config_value']; + } + $src_db->sql_freeresult($result); + + $config->set('allow_attachments', 1); + + // old attachment mod? Must be very old if this entry do not exist... + if (!empty($attach_config['display_order'])) + { + $config->set('display_order', $attach_config['display_order']); + } + $config->set('max_filesize', $attach_config['max_filesize']); + $config->set('max_filesize_pm', $attach_config['max_filesize_pm']); + $config->set('attachment_quota', $attach_config['attachment_quota']); + $config->set('max_attachments', $attach_config['max_attachments']); + $config->set('max_attachments_pm', $attach_config['max_attachments_pm']); + $config->set('allow_pm_attach', $attach_config['allow_pm_attach']); + + $config->set('img_display_inlined', $attach_config['img_display_inlined']); + $config->set('img_max_width', $attach_config['img_max_width']); + $config->set('img_max_height', $attach_config['img_max_height']); + $config->set('img_link_width', $attach_config['img_link_width']); + $config->set('img_link_height', $attach_config['img_link_height']); + $config->set('img_create_thumbnail', $attach_config['img_create_thumbnail']); + $config->set('img_max_thumb_width', 400); + $config->set('img_min_thumb_filesize', $attach_config['img_min_thumb_filesize']); +} + +/** +* Calculate the date a user became inactive +*/ +function phpbb_inactive_time() +{ + global $convert_row; + + if ($convert_row['user_active']) + { + return 0; + } + + if ($convert_row['user_lastvisit']) + { + return $convert_row['user_lastvisit']; + } + + return $convert_row['user_regdate']; +} + +/** +* Calculate the reason a user became inactive +* We can't actually tell the difference between a manual deactivation and one for profile changes +* from the data available to assume the latter +*/ +function phpbb_inactive_reason() +{ + global $convert_row; + + if ($convert_row['user_active']) + { + return 0; + } + + if ($convert_row['user_lastvisit']) + { + return INACTIVE_PROFILE; + } + + return INACTIVE_REGISTER; +} + +/** +* Adjust 2.0.x disallowed names to 3.0.x format +*/ +function phpbb_disallowed_username($username) +{ + // Replace * with % + $username = phpbb_set_default_encoding(str_replace('*', '%', $username)); + return utf8_htmlspecialchars($username); +} + +/** +* Checks whether there are any usernames on the old board that would map to the same +* username_clean on phpBB3. Prints out a list if any exist and exits. +*/ +function phpbb_create_userconv_table() +{ + global $db; + + switch ($db->get_sql_layer()) + { + case 'mysql': + $map_dbms = 'mysql_40'; + break; + + case 'mysql4': + if (version_compare($db->sql_server_info(true), '4.1.3', '>=')) + { + $map_dbms = 'mysql_41'; + } + else + { + $map_dbms = 'mysql_40'; + } + break; + + case 'mysqli': + $map_dbms = 'mysql_41'; + break; + + case 'mssql_odbc': + case 'mssqlnative': + $map_dbms = 'mssql'; + break; + + default: + $map_dbms = $db->get_sql_layer(); + break; + } + + // create a temporary table in which we store the clean usernames + $drop_sql = 'DROP TABLE ' . USERCONV_TABLE; + switch ($map_dbms) + { + case 'mssql': + $create_sql = 'CREATE TABLE [' . USERCONV_TABLE . '] ( + [user_id] [int] NOT NULL , + [username_clean] [varchar] (255) DEFAULT (\'\') NOT NULL + )'; + break; + + case 'mysql_40': + $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( + user_id mediumint(8) NOT NULL, + username_clean blob NOT NULL + )'; + break; + + case 'mysql_41': + $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( + user_id mediumint(8) NOT NULL, + username_clean varchar(255) DEFAULT \'\' NOT NULL + ) CHARACTER SET `utf8` COLLATE `utf8_bin`'; + break; + + case 'oracle': + $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( + user_id number(8) NOT NULL, + username_clean varchar2(255) DEFAULT \'\' + )'; + break; + + case 'postgres': + $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( + user_id INT4 DEFAULT \'0\', + username_clean varchar_ci DEFAULT \'\' NOT NULL + )'; + break; + + case 'sqlite3': + $create_sql = 'CREATE TABLE ' . USERCONV_TABLE . ' ( + user_id INTEGER NOT NULL DEFAULT \'0\', + username_clean varchar(255) NOT NULL DEFAULT \'\' + )'; + break; + } + + $db->sql_return_on_error(true); + $db->sql_query($drop_sql); + $db->sql_return_on_error(false); + $db->sql_query($create_sql); +} + +function phpbb_check_username_collisions() +{ + global $db, $src_db, $convert, $user, $lang; + + // now find the clean version of the usernames that collide + $sql = 'SELECT username_clean + FROM ' . USERCONV_TABLE .' + GROUP BY username_clean + HAVING COUNT(user_id) > 1'; + $result = $db->sql_query($sql); + + $colliding_names = array(); + while ($row = $db->sql_fetchrow($result)) + { + $colliding_names[] = $row['username_clean']; + } + $db->sql_freeresult($result); + + // there was at least one collision, the admin will have to solve it before conversion can continue + if (count($colliding_names)) + { + $sql = 'SELECT user_id, username_clean + FROM ' . USERCONV_TABLE . ' + WHERE ' . $db->sql_in_set('username_clean', $colliding_names); + $result = $db->sql_query($sql); + unset($colliding_names); + + $colliding_user_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $colliding_user_ids[(int) $row['user_id']] = $row['username_clean']; + } + $db->sql_freeresult($result); + + $sql = 'SELECT username, user_id, user_posts + FROM ' . $convert->src_table_prefix . 'users + WHERE ' . $src_db->sql_in_set('user_id', array_keys($colliding_user_ids)); + $result = $src_db->sql_query($sql); + + $colliding_users = array(); + while ($row = $src_db->sql_fetchrow($result)) + { + $row['user_id'] = (int) $row['user_id']; + if (isset($colliding_user_ids[$row['user_id']])) + { + $colliding_users[$colliding_user_ids[$row['user_id']]][] = $row; + } + } + $src_db->sql_freeresult($result); + unset($colliding_user_ids); + + $list = ''; + foreach ($colliding_users as $username_clean => $users) + { + $list .= sprintf($user->lang['COLLIDING_CLEAN_USERNAME'], $username_clean) . "
\n"; + foreach ($users as $i => $row) + { + $list .= sprintf($user->lang['COLLIDING_USER'], $row['user_id'], phpbb_set_default_encoding($row['username']), $row['user_posts']) . "
\n"; + } + } + + $lang['INST_ERR_FATAL'] = $user->lang['CONV_ERR_FATAL']; + $convert->p_master->error('' . $user->lang['COLLIDING_USERNAMES_FOUND'] . '

' . $list . '', __LINE__, __FILE__); + } + + $drop_sql = 'DROP TABLE ' . USERCONV_TABLE; + $db->sql_query($drop_sql); +} + +function phpbb_convert_timezone($timezone) +{ + global $config, $db, $phpbb_root_path, $phpEx, $table_prefix; + + $factory = new \phpbb\db\tools\factory(); + $timezone_migration = new \phpbb\db\migration\data\v310\timezone($config, $db, $factory->get($db), $phpbb_root_path, $phpEx, $table_prefix); + return $timezone_migration->convert_phpbb30_timezone($timezone, 0); +} + +function phpbb_add_notification_options($user_notify_pm) +{ + global $convert_row, $db; + + $user_id = phpbb_user_id($convert_row['user_id']); + if ($user_id == ANONYMOUS) + { + return; + } + + $rows = array(); + + $rows[] = array( + 'item_type' => 'post', + 'item_id' => 0, + 'user_id' => (int) $user_id, + 'notify' => 1, + 'method' => 'email', + ); + $rows[] = array( + 'item_type' => 'topic', + 'item_id' => 0, + 'user_id' => (int) $user_id, + 'notify' => 1, + 'method' => 'email', + ); + if ($user_notify_pm) + { + $rows[] = array( + 'item_type' => 'pm', + 'item_id' => 0, + 'user_id' => (int) $user_id, + 'notify' => 1, + 'method' => 'email', + ); + } + + $db->sql_multi_insert(USER_NOTIFICATIONS_TABLE, $rows); +} + +function phpbb_convert_password_hash($hash) +{ + global $phpbb_container; + + /* @var $manager \phpbb\passwords\manager */ + $manager = $phpbb_container->get('passwords.manager'); + $hash = $manager->hash($hash, '$H$'); + + return '$CP$' . $hash; +} diff --git a/install/index.html b/install/index.html new file mode 100644 index 0000000..8f8bfb4 --- /dev/null +++ b/install/index.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/install/phpbbcli.php b/install/phpbbcli.php new file mode 100644 index 0000000..217f1df --- /dev/null +++ b/install/phpbbcli.php @@ -0,0 +1,52 @@ +#!/usr/bin/env php + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +use Symfony\Component\Console\Input\ArgvInput; + +if (php_sapi_name() !== 'cli') +{ + echo 'This program must be run from the command line.' . PHP_EOL; + exit(1); +} + +define('IN_PHPBB', true); +define('IN_INSTALL', true); +define('PHPBB_ENVIRONMENT', 'production'); +define('PHPBB_VERSION', '3.2.7'); +$phpbb_root_path = __DIR__ . '/../'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); + +// +// Let's do the common.php logic +// +$startup_new_path = $phpbb_root_path . 'install/update/update/new/install/startup.' . $phpEx; +$startup_path = (file_exists($startup_new_path)) ? $startup_new_path : $phpbb_root_path . 'install/startup.' . $phpEx; +require($startup_path); + +$input = new ArgvInput(); + +// Enable superglobals for cli support +$phpbb_installer_container->get('request')->enable_super_globals(); + +/** @var \phpbb\filesystem\filesystem $phpbb_filesystem */ +$phpbb_filesystem = $phpbb_installer_container->get('filesystem'); + +/** @var \phpbb\language\language $language */ +$language = $phpbb_installer_container->get('language'); +$language->add_lang(array('common', 'acp/common', 'acp/board', 'install', 'posting', 'cli')); + +$application = new \phpbb\console\application('phpBB Installer', PHPBB_VERSION, $language); +$application->setDispatcher($phpbb_installer_container->get('dispatcher')); +$application->register_container_commands($phpbb_installer_container->get('console.installer.command_collection')); +$application->run($input); diff --git a/install/phpinfo.php b/install/phpinfo.php new file mode 100644 index 0000000..28c3b9f --- /dev/null +++ b/install/phpinfo.php @@ -0,0 +1,14 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +phpinfo(); diff --git a/install/schemas/index.htm b/install/schemas/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/install/schemas/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/install/schemas/oracle_schema.sql b/install/schemas/oracle_schema.sql new file mode 100644 index 0000000..2473d31 --- /dev/null +++ b/install/schemas/oracle_schema.sql @@ -0,0 +1,37 @@ +/* + This first section is optional, however its probably the best method + of running phpBB on Oracle. If you already have a tablespace and user created + for phpBB you can leave this section commented out! + + The first set of statements create a phpBB tablespace and a phpBB user, + make sure you change the password of the phpBB user before you run this script!! +*/ + +/* +CREATE TABLESPACE "PHPBB" + LOGGING + DATAFILE 'E:\ORACLE\ORADATA\LOCAL\PHPBB.ora' + SIZE 10M + AUTOEXTEND ON NEXT 10M + MAXSIZE 100M; + +CREATE USER "PHPBB" + PROFILE "DEFAULT" + IDENTIFIED BY "phpbb_password" + DEFAULT TABLESPACE "PHPBB" + QUOTA UNLIMITED ON "PHPBB" + ACCOUNT UNLOCK; + +GRANT ANALYZE ANY TO "PHPBB"; +GRANT CREATE SEQUENCE TO "PHPBB"; +GRANT CREATE SESSION TO "PHPBB"; +GRANT CREATE TABLE TO "PHPBB"; +GRANT CREATE TRIGGER TO "PHPBB"; +GRANT CREATE VIEW TO "PHPBB"; +GRANT "CONNECT" TO "PHPBB"; + +COMMIT; +DISCONNECT; + +CONNECT phpbb/phpbb_password; +*/ diff --git a/install/schemas/postgres_schema.sql b/install/schemas/postgres_schema.sql new file mode 100644 index 0000000..65caba8 --- /dev/null +++ b/install/schemas/postgres_schema.sql @@ -0,0 +1,80 @@ + +BEGIN; + +/* + Domain definition +*/ +CREATE DOMAIN varchar_ci AS varchar(255) NOT NULL DEFAULT ''::character varying; + +/* + Operation Functions +*/ +CREATE FUNCTION _varchar_ci_equal(varchar_ci, varchar_ci) RETURNS boolean AS 'SELECT LOWER($1) = LOWER($2)' LANGUAGE SQL STRICT; +CREATE FUNCTION _varchar_ci_not_equal(varchar_ci, varchar_ci) RETURNS boolean AS 'SELECT LOWER($1) != LOWER($2)' LANGUAGE SQL STRICT; +CREATE FUNCTION _varchar_ci_less_than(varchar_ci, varchar_ci) RETURNS boolean AS 'SELECT LOWER($1) < LOWER($2)' LANGUAGE SQL STRICT; +CREATE FUNCTION _varchar_ci_less_equal(varchar_ci, varchar_ci) RETURNS boolean AS 'SELECT LOWER($1) <= LOWER($2)' LANGUAGE SQL STRICT; +CREATE FUNCTION _varchar_ci_greater_than(varchar_ci, varchar_ci) RETURNS boolean AS 'SELECT LOWER($1) > LOWER($2)' LANGUAGE SQL STRICT; +CREATE FUNCTION _varchar_ci_greater_equals(varchar_ci, varchar_ci) RETURNS boolean AS 'SELECT LOWER($1) >= LOWER($2)' LANGUAGE SQL STRICT; + +/* + Operators +*/ +CREATE OPERATOR <( + PROCEDURE = _varchar_ci_less_than, + LEFTARG = varchar_ci, + RIGHTARG = varchar_ci, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); + +CREATE OPERATOR <=( + PROCEDURE = _varchar_ci_less_equal, + LEFTARG = varchar_ci, + RIGHTARG = varchar_ci, + COMMUTATOR = >=, + NEGATOR = >, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel); + +CREATE OPERATOR >( + PROCEDURE = _varchar_ci_greater_than, + LEFTARG = varchar_ci, + RIGHTARG = varchar_ci, + COMMUTATOR = <, + NEGATOR = <=, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); + +CREATE OPERATOR >=( + PROCEDURE = _varchar_ci_greater_equals, + LEFTARG = varchar_ci, + RIGHTARG = varchar_ci, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel); + +CREATE OPERATOR <>( + PROCEDURE = _varchar_ci_not_equal, + LEFTARG = varchar_ci, + RIGHTARG = varchar_ci, + COMMUTATOR = <>, + NEGATOR = =, + RESTRICT = neqsel, + JOIN = neqjoinsel); + +CREATE OPERATOR =( + PROCEDURE = _varchar_ci_equal, + LEFTARG = varchar_ci, + RIGHTARG = varchar_ci, + COMMUTATOR = =, + NEGATOR = <>, + RESTRICT = eqsel, + JOIN = eqjoinsel, + HASHES, + MERGES, + SORT1= <); + +COMMIT; + diff --git a/install/schemas/schema.json b/install/schemas/schema.json new file mode 100644 index 0000000..d407786 --- /dev/null +++ b/install/schemas/schema.json @@ -0,0 +1,3437 @@ +{ + "phpbb_acl_groups": { + "COLUMNS": { + "group_id": [ + "UINT", + 0 + ], + "forum_id": [ + "UINT", + 0 + ], + "auth_option_id": [ + "UINT", + 0 + ], + "auth_role_id": [ + "UINT", + 0 + ], + "auth_setting": [ + "TINT:2", + 0 + ] + }, + "KEYS": { + "group_id": [ + "INDEX", + "group_id" + ], + "auth_opt_id": [ + "INDEX", + "auth_option_id" + ], + "auth_role_id": [ + "INDEX", + "auth_role_id" + ] + } + }, + "phpbb_acl_options": { + "COLUMNS": { + "auth_option_id": [ + "UINT", + null, + "auto_increment" + ], + "auth_option": [ + "VCHAR:50", + "" + ], + "is_global": [ + "BOOL", + 0 + ], + "is_local": [ + "BOOL", + 0 + ], + "founder_only": [ + "BOOL", + 0 + ] + }, + "PRIMARY_KEY": "auth_option_id", + "KEYS": { + "auth_option": [ + "UNIQUE", + [ + "auth_option" + ] + ] + } + }, + "phpbb_acl_roles": { + "COLUMNS": { + "role_id": [ + "UINT", + null, + "auto_increment" + ], + "role_name": [ + "VCHAR_UNI", + "" + ], + "role_description": [ + "TEXT_UNI", + "" + ], + "role_type": [ + "VCHAR:10", + "" + ], + "role_order": [ + "USINT", + 0 + ] + }, + "PRIMARY_KEY": "role_id", + "KEYS": { + "role_type": [ + "INDEX", + "role_type" + ], + "role_order": [ + "INDEX", + "role_order" + ] + } + }, + "phpbb_acl_roles_data": { + "COLUMNS": { + "role_id": [ + "UINT", + 0 + ], + "auth_option_id": [ + "UINT", + 0 + ], + "auth_setting": [ + "TINT:2", + 0 + ] + }, + "PRIMARY_KEY": [ + "role_id", + "auth_option_id" + ], + "KEYS": { + "ath_op_id": [ + "INDEX", + "auth_option_id" + ] + } + }, + "phpbb_acl_users": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "forum_id": [ + "UINT", + 0 + ], + "auth_option_id": [ + "UINT", + 0 + ], + "auth_role_id": [ + "UINT", + 0 + ], + "auth_setting": [ + "TINT:2", + 0 + ] + }, + "KEYS": { + "user_id": [ + "INDEX", + "user_id" + ], + "auth_option_id": [ + "INDEX", + "auth_option_id" + ], + "auth_role_id": [ + "INDEX", + "auth_role_id" + ] + } + }, + "phpbb_attachments": { + "COLUMNS": { + "attach_id": [ + "ULINT", + null, + "auto_increment" + ], + "post_msg_id": [ + "ULINT", + 0 + ], + "topic_id": [ + "ULINT", + 0 + ], + "in_message": [ + "BOOL", + 0 + ], + "poster_id": [ + "ULINT", + 0 + ], + "is_orphan": [ + "BOOL", + 1 + ], + "physical_filename": [ + "VCHAR", + "" + ], + "real_filename": [ + "VCHAR", + "" + ], + "download_count": [ + "UINT", + 0 + ], + "attach_comment": [ + "TEXT_UNI", + "" + ], + "extension": [ + "VCHAR:100", + "" + ], + "mimetype": [ + "VCHAR:100", + "" + ], + "filesize": [ + "UINT:20", + 0 + ], + "filetime": [ + "TIMESTAMP", + 0 + ], + "thumbnail": [ + "BOOL", + 0 + ] + }, + "PRIMARY_KEY": "attach_id", + "KEYS": { + "filetime": [ + "INDEX", + "filetime" + ], + "post_msg_id": [ + "INDEX", + "post_msg_id" + ], + "topic_id": [ + "INDEX", + "topic_id" + ], + "poster_id": [ + "INDEX", + "poster_id" + ], + "is_orphan": [ + "INDEX", + "is_orphan" + ] + } + }, + "phpbb_banlist": { + "COLUMNS": { + "ban_id": [ + "ULINT", + null, + "auto_increment" + ], + "ban_userid": [ + "ULINT", + 0 + ], + "ban_ip": [ + "VCHAR:40", + "" + ], + "ban_email": [ + "VCHAR_UNI:100", + "" + ], + "ban_start": [ + "TIMESTAMP", + 0 + ], + "ban_end": [ + "TIMESTAMP", + 0 + ], + "ban_exclude": [ + "BOOL", + 0 + ], + "ban_reason": [ + "VCHAR_UNI", + "" + ], + "ban_give_reason": [ + "VCHAR_UNI", + "" + ] + }, + "PRIMARY_KEY": "ban_id", + "KEYS": { + "ban_end": [ + "INDEX", + "ban_end" + ], + "ban_user": [ + "INDEX", + [ + "ban_userid", + "ban_exclude" + ] + ], + "ban_email": [ + "INDEX", + [ + "ban_email", + "ban_exclude" + ] + ], + "ban_ip": [ + "INDEX", + [ + "ban_ip", + "ban_exclude" + ] + ] + } + }, + "phpbb_bbcodes": { + "COLUMNS": { + "bbcode_id": [ + "USINT", + 0 + ], + "bbcode_tag": [ + "VCHAR:16", + "" + ], + "bbcode_helpline": [ + "VCHAR_UNI", + "" + ], + "display_on_posting": [ + "BOOL", + 0 + ], + "bbcode_match": [ + "TEXT_UNI", + "" + ], + "bbcode_tpl": [ + "MTEXT_UNI", + "" + ], + "first_pass_match": [ + "MTEXT_UNI", + "" + ], + "first_pass_replace": [ + "MTEXT_UNI", + "" + ], + "second_pass_match": [ + "MTEXT_UNI", + "" + ], + "second_pass_replace": [ + "MTEXT_UNI", + "" + ] + }, + "PRIMARY_KEY": "bbcode_id", + "KEYS": { + "display_on_post": [ + "INDEX", + "display_on_posting" + ] + } + }, + "phpbb_bookmarks": { + "COLUMNS": { + "topic_id": [ + "ULINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ] + }, + "PRIMARY_KEY": [ + "topic_id", + "user_id" + ] + }, + "phpbb_bots": { + "COLUMNS": { + "bot_id": [ + "ULINT", + null, + "auto_increment" + ], + "bot_active": [ + "BOOL", + 1 + ], + "bot_name": [ + "STEXT_UNI", + "" + ], + "user_id": [ + "ULINT", + 0 + ], + "bot_agent": [ + "VCHAR", + "" + ], + "bot_ip": [ + "VCHAR", + "" + ] + }, + "PRIMARY_KEY": "bot_id", + "KEYS": { + "bot_active": [ + "INDEX", + "bot_active" + ] + } + }, + "phpbb_config": { + "COLUMNS": { + "config_name": [ + "VCHAR", + "" + ], + "config_value": [ + "VCHAR_UNI", + "" + ], + "is_dynamic": [ + "BOOL", + 0 + ] + }, + "PRIMARY_KEY": "config_name", + "KEYS": { + "is_dynamic": [ + "INDEX", + "is_dynamic" + ] + } + }, + "phpbb_config_text": { + "COLUMNS": { + "config_name": [ + "VCHAR", + "" + ], + "config_value": [ + "MTEXT", + "" + ] + }, + "PRIMARY_KEY": "config_name" + }, + "phpbb_confirm": { + "COLUMNS": { + "confirm_id": [ + "CHAR:32", + "" + ], + "session_id": [ + "CHAR:32", + "" + ], + "confirm_type": [ + "TINT:3", + 0 + ], + "code": [ + "VCHAR:8", + "" + ], + "seed": [ + "UINT:10", + 0 + ], + "attempts": [ + "UINT", + 0 + ] + }, + "PRIMARY_KEY": [ + "session_id", + "confirm_id" + ], + "KEYS": { + "confirm_type": [ + "INDEX", + "confirm_type" + ] + } + }, + "phpbb_disallow": { + "COLUMNS": { + "disallow_id": [ + "UINT", + null, + "auto_increment" + ], + "disallow_username": [ + "VCHAR_UNI:255", + "" + ] + }, + "PRIMARY_KEY": "disallow_id" + }, + "phpbb_drafts": { + "COLUMNS": { + "draft_id": [ + "ULINT", + null, + "auto_increment" + ], + "user_id": [ + "ULINT", + 0 + ], + "topic_id": [ + "ULINT", + 0 + ], + "forum_id": [ + "UINT", + 0 + ], + "save_time": [ + "TIMESTAMP", + 0 + ], + "draft_subject": [ + "STEXT_UNI", + "" + ], + "draft_message": [ + "MTEXT_UNI", + "" + ] + }, + "PRIMARY_KEY": "draft_id", + "KEYS": { + "save_time": [ + "INDEX", + "save_time" + ] + } + }, + "phpbb_ext": { + "COLUMNS": { + "ext_name": [ + "VCHAR", + "" + ], + "ext_active": [ + "BOOL", + 0 + ], + "ext_state": [ + "TEXT", + "" + ] + }, + "KEYS": { + "ext_name": [ + "UNIQUE", + "ext_name" + ] + } + }, + "phpbb_extension_groups": { + "COLUMNS": { + "group_id": [ + "UINT", + null, + "auto_increment" + ], + "group_name": [ + "VCHAR_UNI", + "" + ], + "cat_id": [ + "TINT:2", + 0 + ], + "allow_group": [ + "BOOL", + 0 + ], + "download_mode": [ + "BOOL", + 1 + ], + "upload_icon": [ + "VCHAR", + "" + ], + "max_filesize": [ + "UINT:20", + 0 + ], + "allowed_forums": [ + "TEXT", + "" + ], + "allow_in_pm": [ + "BOOL", + 0 + ] + }, + "PRIMARY_KEY": "group_id" + }, + "phpbb_extensions": { + "COLUMNS": { + "extension_id": [ + "UINT", + null, + "auto_increment" + ], + "group_id": [ + "UINT", + 0 + ], + "extension": [ + "VCHAR:100", + "" + ] + }, + "PRIMARY_KEY": "extension_id" + }, + "phpbb_forums": { + "COLUMNS": { + "forum_id": [ + "UINT", + null, + "auto_increment" + ], + "parent_id": [ + "UINT", + 0 + ], + "left_id": [ + "UINT", + 0 + ], + "right_id": [ + "UINT", + 0 + ], + "forum_parents": [ + "MTEXT", + "" + ], + "forum_name": [ + "STEXT_UNI", + "" + ], + "forum_desc": [ + "TEXT_UNI", + "" + ], + "forum_desc_bitfield": [ + "VCHAR:255", + "" + ], + "forum_desc_options": [ + "UINT:11", + 7 + ], + "forum_desc_uid": [ + "VCHAR:8", + "" + ], + "forum_link": [ + "VCHAR_UNI", + "" + ], + "forum_password": [ + "VCHAR:255", + "" + ], + "forum_style": [ + "UINT", + 0 + ], + "forum_image": [ + "VCHAR", + "" + ], + "forum_rules": [ + "TEXT_UNI", + "" + ], + "forum_rules_link": [ + "VCHAR_UNI", + "" + ], + "forum_rules_bitfield": [ + "VCHAR:255", + "" + ], + "forum_rules_options": [ + "UINT:11", + 7 + ], + "forum_rules_uid": [ + "VCHAR:8", + "" + ], + "forum_topics_per_page": [ + "USINT", + 0 + ], + "forum_type": [ + "TINT:4", + 0 + ], + "forum_status": [ + "TINT:4", + 0 + ], + "forum_last_post_id": [ + "ULINT", + 0 + ], + "forum_last_poster_id": [ + "ULINT", + 0 + ], + "forum_last_post_subject": [ + "STEXT_UNI", + "" + ], + "forum_last_post_time": [ + "TIMESTAMP", + 0 + ], + "forum_last_poster_name": [ + "VCHAR_UNI", + "" + ], + "forum_last_poster_colour": [ + "VCHAR:6", + "" + ], + "forum_flags": [ + "TINT:4", + 32 + ], + "display_on_index": [ + "BOOL", + 1 + ], + "enable_indexing": [ + "BOOL", + 1 + ], + "enable_icons": [ + "BOOL", + 1 + ], + "enable_prune": [ + "BOOL", + 0 + ], + "prune_next": [ + "TIMESTAMP", + 0 + ], + "prune_days": [ + "UINT", + 0 + ], + "prune_viewed": [ + "UINT", + 0 + ], + "prune_freq": [ + "UINT", + 0 + ], + "display_subforum_list": [ + "BOOL", + 1 + ], + "forum_options": [ + "UINT:20", + 0 + ], + "enable_shadow_prune": [ + "BOOL", + 0 + ], + "prune_shadow_days": [ + "UINT", + 7 + ], + "prune_shadow_freq": [ + "UINT", + 1 + ], + "prune_shadow_next": [ + "INT:11", + 0 + ], + "forum_posts_approved": [ + "UINT", + 0 + ], + "forum_posts_unapproved": [ + "UINT", + 0 + ], + "forum_posts_softdeleted": [ + "UINT", + 0 + ], + "forum_topics_approved": [ + "UINT", + 0 + ], + "forum_topics_unapproved": [ + "UINT", + 0 + ], + "forum_topics_softdeleted": [ + "UINT", + 0 + ] + }, + "PRIMARY_KEY": "forum_id", + "KEYS": { + "left_right_id": [ + "INDEX", + [ + "left_id", + "right_id" + ] + ], + "forum_lastpost_id": [ + "INDEX", + "forum_last_post_id" + ] + } + }, + "phpbb_forums_access": { + "COLUMNS": { + "forum_id": [ + "UINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "session_id": [ + "CHAR:32", + "" + ] + }, + "PRIMARY_KEY": [ + "forum_id", + "user_id", + "session_id" + ] + }, + "phpbb_forums_track": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "forum_id": [ + "UINT", + 0 + ], + "mark_time": [ + "TIMESTAMP", + 0 + ] + }, + "PRIMARY_KEY": [ + "user_id", + "forum_id" + ] + }, + "phpbb_forums_watch": { + "COLUMNS": { + "forum_id": [ + "UINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "notify_status": [ + "BOOL", + 0 + ] + }, + "KEYS": { + "forum_id": [ + "INDEX", + "forum_id" + ], + "user_id": [ + "INDEX", + "user_id" + ], + "notify_stat": [ + "INDEX", + "notify_status" + ] + } + }, + "phpbb_groups": { + "COLUMNS": { + "group_id": [ + "UINT", + null, + "auto_increment" + ], + "group_type": [ + "TINT:4", + 1 + ], + "group_founder_manage": [ + "BOOL", + 0 + ], + "group_skip_auth": [ + "BOOL", + 0 + ], + "group_name": [ + "VCHAR_CI", + "" + ], + "group_desc": [ + "TEXT_UNI", + "" + ], + "group_desc_bitfield": [ + "VCHAR:255", + "" + ], + "group_desc_options": [ + "UINT:11", + 7 + ], + "group_desc_uid": [ + "VCHAR:8", + "" + ], + "group_display": [ + "BOOL", + 0 + ], + "group_avatar": [ + "VCHAR", + "" + ], + "group_avatar_type": [ + "VCHAR:255", + "" + ], + "group_avatar_width": [ + "USINT", + 0 + ], + "group_avatar_height": [ + "USINT", + 0 + ], + "group_rank": [ + "UINT", + 0 + ], + "group_colour": [ + "VCHAR:6", + "" + ], + "group_sig_chars": [ + "UINT", + 0 + ], + "group_receive_pm": [ + "BOOL", + 0 + ], + "group_message_limit": [ + "UINT", + 0 + ], + "group_legend": [ + "UINT", + 0 + ], + "group_max_recipients": [ + "UINT", + 0 + ] + }, + "PRIMARY_KEY": "group_id", + "KEYS": { + "group_legend_name": [ + "INDEX", + [ + "group_legend", + "group_name" + ] + ] + } + }, + "phpbb_icons": { + "COLUMNS": { + "icons_id": [ + "UINT", + null, + "auto_increment" + ], + "icons_url": [ + "VCHAR", + "" + ], + "icons_width": [ + "TINT:4", + 0 + ], + "icons_height": [ + "TINT:4", + 0 + ], + "icons_alt": [ + "VCHAR", + "" + ], + "icons_order": [ + "UINT", + 0 + ], + "display_on_posting": [ + "BOOL", + 1 + ] + }, + "PRIMARY_KEY": "icons_id", + "KEYS": { + "display_on_posting": [ + "INDEX", + "display_on_posting" + ] + } + }, + "phpbb_lang": { + "COLUMNS": { + "lang_id": [ + "TINT:4", + null, + "auto_increment" + ], + "lang_iso": [ + "VCHAR:30", + "" + ], + "lang_dir": [ + "VCHAR:30", + "" + ], + "lang_english_name": [ + "VCHAR_UNI:100", + "" + ], + "lang_local_name": [ + "VCHAR_UNI:255", + "" + ], + "lang_author": [ + "VCHAR_UNI:255", + "" + ] + }, + "PRIMARY_KEY": "lang_id", + "KEYS": { + "lang_iso": [ + "INDEX", + "lang_iso" + ] + } + }, + "phpbb_log": { + "COLUMNS": { + "log_id": [ + "ULINT", + null, + "auto_increment" + ], + "log_type": [ + "TINT:4", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "forum_id": [ + "UINT", + 0 + ], + "topic_id": [ + "ULINT", + 0 + ], + "post_id": [ + "ULINT", + 0 + ], + "reportee_id": [ + "ULINT", + 0 + ], + "log_ip": [ + "VCHAR:40", + "" + ], + "log_time": [ + "TIMESTAMP", + 0 + ], + "log_operation": [ + "TEXT_UNI", + "" + ], + "log_data": [ + "MTEXT_UNI", + "" + ] + }, + "PRIMARY_KEY": "log_id", + "KEYS": { + "log_type": [ + "INDEX", + "log_type" + ], + "forum_id": [ + "INDEX", + "forum_id" + ], + "topic_id": [ + "INDEX", + "topic_id" + ], + "reportee_id": [ + "INDEX", + "reportee_id" + ], + "user_id": [ + "INDEX", + "user_id" + ], + "log_time": [ + "INDEX", + [ + "log_time" + ] + ] + } + }, + "phpbb_login_attempts": { + "COLUMNS": { + "attempt_ip": [ + "VCHAR:40", + "" + ], + "attempt_browser": [ + "VCHAR:150", + "" + ], + "attempt_forwarded_for": [ + "VCHAR:255", + "" + ], + "attempt_time": [ + "TIMESTAMP", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "username": [ + "VCHAR_UNI:255", + 0 + ], + "username_clean": [ + "VCHAR_CI", + 0 + ] + }, + "KEYS": { + "att_ip": [ + "INDEX", + [ + "attempt_ip", + "attempt_time" + ] + ], + "att_for": [ + "INDEX", + [ + "attempt_forwarded_for", + "attempt_time" + ] + ], + "att_time": [ + "INDEX", + [ + "attempt_time" + ] + ], + "user_id": [ + "INDEX", + "user_id" + ] + } + }, + "phpbb_migrations": { + "COLUMNS": { + "migration_name": [ + "VCHAR", + "" + ], + "migration_depends_on": [ + "TEXT", + "" + ], + "migration_schema_done": [ + "BOOL", + 0 + ], + "migration_data_done": [ + "BOOL", + 0 + ], + "migration_data_state": [ + "TEXT", + "" + ], + "migration_start_time": [ + "TIMESTAMP", + 0 + ], + "migration_end_time": [ + "TIMESTAMP", + 0 + ] + }, + "PRIMARY_KEY": "migration_name" + }, + "phpbb_moderator_cache": { + "COLUMNS": { + "forum_id": [ + "UINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "username": [ + "VCHAR_UNI:255", + "" + ], + "group_id": [ + "UINT", + 0 + ], + "group_name": [ + "VCHAR_UNI", + "" + ], + "display_on_index": [ + "BOOL", + 1 + ] + }, + "KEYS": { + "disp_idx": [ + "INDEX", + "display_on_index" + ], + "forum_id": [ + "INDEX", + "forum_id" + ] + } + }, + "phpbb_modules": { + "COLUMNS": { + "module_id": [ + "UINT", + null, + "auto_increment" + ], + "module_enabled": [ + "BOOL", + 1 + ], + "module_display": [ + "BOOL", + 1 + ], + "module_basename": [ + "VCHAR", + "" + ], + "module_class": [ + "VCHAR:10", + "" + ], + "parent_id": [ + "UINT", + 0 + ], + "left_id": [ + "UINT", + 0 + ], + "right_id": [ + "UINT", + 0 + ], + "module_langname": [ + "VCHAR", + "" + ], + "module_mode": [ + "VCHAR", + "" + ], + "module_auth": [ + "VCHAR", + "" + ] + }, + "PRIMARY_KEY": "module_id", + "KEYS": { + "left_right_id": [ + "INDEX", + [ + "left_id", + "right_id" + ] + ], + "module_enabled": [ + "INDEX", + "module_enabled" + ], + "class_left_id": [ + "INDEX", + [ + "module_class", + "left_id" + ] + ] + } + }, + "phpbb_notification_types": { + "COLUMNS": { + "notification_type_id": [ + "USINT", + null, + "auto_increment" + ], + "notification_type_name": [ + "VCHAR:255", + "" + ], + "notification_type_enabled": [ + "BOOL", + 1 + ] + }, + "PRIMARY_KEY": [ + "notification_type_id" + ], + "KEYS": { + "type": [ + "UNIQUE", + [ + "notification_type_name" + ] + ] + } + }, + "phpbb_notifications": { + "COLUMNS": { + "notification_id": [ + "UINT:10", + null, + "auto_increment" + ], + "notification_type_id": [ + "USINT", + 0 + ], + "item_id": [ + "UINT", + 0 + ], + "item_parent_id": [ + "UINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "notification_read": [ + "BOOL", + 0 + ], + "notification_time": [ + "TIMESTAMP", + 1 + ], + "notification_data": [ + "TEXT_UNI", + "" + ] + }, + "PRIMARY_KEY": "notification_id", + "KEYS": { + "item_ident": [ + "INDEX", + [ + "notification_type_id", + "item_id" + ] + ], + "user": [ + "INDEX", + [ + "user_id", + "notification_read" + ] + ] + } + }, + "phpbb_oauth_accounts": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "provider": [ + "VCHAR", + "" + ], + "oauth_provider_id": [ + "TEXT_UNI", + "" + ] + }, + "PRIMARY_KEY": [ + "user_id", + "provider" + ] + }, + "phpbb_oauth_states": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "session_id": [ + "CHAR:32", + "" + ], + "provider": [ + "VCHAR", + "" + ], + "oauth_state": [ + "VCHAR", + "" + ] + }, + "KEYS": { + "user_id": [ + "INDEX", + "user_id" + ], + "provider": [ + "INDEX", + "provider" + ] + } + }, + "phpbb_oauth_tokens": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "session_id": [ + "CHAR:32", + "" + ], + "provider": [ + "VCHAR", + "" + ], + "oauth_token": [ + "MTEXT", + "" + ] + }, + "KEYS": { + "user_id": [ + "INDEX", + "user_id" + ], + "provider": [ + "INDEX", + "provider" + ] + } + }, + "phpbb_poll_options": { + "COLUMNS": { + "poll_option_id": [ + "TINT:4", + 0 + ], + "topic_id": [ + "ULINT", + 0 + ], + "poll_option_text": [ + "TEXT_UNI", + "" + ], + "poll_option_total": [ + "UINT", + 0 + ] + }, + "KEYS": { + "poll_opt_id": [ + "INDEX", + "poll_option_id" + ], + "topic_id": [ + "INDEX", + "topic_id" + ] + } + }, + "phpbb_poll_votes": { + "COLUMNS": { + "topic_id": [ + "ULINT", + 0 + ], + "poll_option_id": [ + "TINT:4", + 0 + ], + "vote_user_id": [ + "ULINT", + 0 + ], + "vote_user_ip": [ + "VCHAR:40", + "" + ] + }, + "KEYS": { + "topic_id": [ + "INDEX", + "topic_id" + ], + "vote_user_id": [ + "INDEX", + "vote_user_id" + ], + "vote_user_ip": [ + "INDEX", + "vote_user_ip" + ] + } + }, + "phpbb_posts": { + "COLUMNS": { + "post_id": [ + "ULINT", + null, + "auto_increment" + ], + "topic_id": [ + "ULINT", + 0 + ], + "forum_id": [ + "UINT", + 0 + ], + "poster_id": [ + "ULINT", + 0 + ], + "icon_id": [ + "UINT", + 0 + ], + "poster_ip": [ + "VCHAR:40", + "" + ], + "post_time": [ + "TIMESTAMP", + 0 + ], + "post_reported": [ + "BOOL", + 0 + ], + "enable_bbcode": [ + "BOOL", + 1 + ], + "enable_smilies": [ + "BOOL", + 1 + ], + "enable_magic_url": [ + "BOOL", + 1 + ], + "enable_sig": [ + "BOOL", + 1 + ], + "post_username": [ + "VCHAR_UNI:255", + "" + ], + "post_subject": [ + "STEXT_UNI", + "", + "true_sort" + ], + "post_text": [ + "MTEXT_UNI", + "" + ], + "post_checksum": [ + "VCHAR:32", + "" + ], + "post_attachment": [ + "BOOL", + 0 + ], + "bbcode_bitfield": [ + "VCHAR:255", + "" + ], + "bbcode_uid": [ + "VCHAR:8", + "" + ], + "post_postcount": [ + "BOOL", + 1 + ], + "post_edit_time": [ + "TIMESTAMP", + 0 + ], + "post_edit_reason": [ + "STEXT_UNI", + "" + ], + "post_edit_user": [ + "ULINT", + 0 + ], + "post_edit_count": [ + "USINT", + 0 + ], + "post_edit_locked": [ + "BOOL", + 0 + ], + "post_visibility": [ + "TINT:3", + 0 + ], + "post_delete_time": [ + "TIMESTAMP", + 0 + ], + "post_delete_reason": [ + "STEXT_UNI", + "" + ], + "post_delete_user": [ + "ULINT", + 0 + ] + }, + "PRIMARY_KEY": "post_id", + "KEYS": { + "forum_id": [ + "INDEX", + "forum_id" + ], + "topic_id": [ + "INDEX", + "topic_id" + ], + "poster_ip": [ + "INDEX", + "poster_ip" + ], + "poster_id": [ + "INDEX", + "poster_id" + ], + "tid_post_time": [ + "INDEX", + [ + "topic_id", + "post_time" + ] + ], + "post_username": [ + "INDEX", + [ + "post_username:255" + ] + ], + "post_visibility": [ + "INDEX", + [ + "post_visibility" + ] + ] + } + }, + "phpbb_privmsgs": { + "COLUMNS": { + "msg_id": [ + "ULINT", + null, + "auto_increment" + ], + "root_level": [ + "UINT", + 0 + ], + "author_id": [ + "ULINT", + 0 + ], + "icon_id": [ + "UINT", + 0 + ], + "author_ip": [ + "VCHAR:40", + "" + ], + "message_time": [ + "TIMESTAMP", + 0 + ], + "enable_bbcode": [ + "BOOL", + 1 + ], + "enable_smilies": [ + "BOOL", + 1 + ], + "enable_magic_url": [ + "BOOL", + 1 + ], + "enable_sig": [ + "BOOL", + 1 + ], + "message_subject": [ + "STEXT_UNI", + "" + ], + "message_text": [ + "MTEXT_UNI", + "" + ], + "message_edit_reason": [ + "STEXT_UNI", + "" + ], + "message_edit_user": [ + "ULINT", + 0 + ], + "message_attachment": [ + "BOOL", + 0 + ], + "bbcode_bitfield": [ + "VCHAR:255", + "" + ], + "bbcode_uid": [ + "VCHAR:8", + "" + ], + "message_edit_time": [ + "TIMESTAMP", + 0 + ], + "message_edit_count": [ + "USINT", + 0 + ], + "to_address": [ + "TEXT_UNI", + "" + ], + "bcc_address": [ + "TEXT_UNI", + "" + ], + "message_reported": [ + "BOOL", + 0 + ] + }, + "PRIMARY_KEY": "msg_id", + "KEYS": { + "author_ip": [ + "INDEX", + "author_ip" + ], + "message_time": [ + "INDEX", + "message_time" + ], + "author_id": [ + "INDEX", + "author_id" + ], + "root_level": [ + "INDEX", + "root_level" + ] + } + }, + "phpbb_privmsgs_folder": { + "COLUMNS": { + "folder_id": [ + "UINT", + null, + "auto_increment" + ], + "user_id": [ + "ULINT", + 0 + ], + "folder_name": [ + "VCHAR_UNI", + "" + ], + "pm_count": [ + "UINT", + 0 + ] + }, + "PRIMARY_KEY": "folder_id", + "KEYS": { + "user_id": [ + "INDEX", + "user_id" + ] + } + }, + "phpbb_privmsgs_rules": { + "COLUMNS": { + "rule_id": [ + "UINT", + null, + "auto_increment" + ], + "user_id": [ + "ULINT", + 0 + ], + "rule_check": [ + "UINT", + 0 + ], + "rule_connection": [ + "UINT", + 0 + ], + "rule_string": [ + "VCHAR_UNI", + "" + ], + "rule_user_id": [ + "ULINT", + 0 + ], + "rule_group_id": [ + "UINT", + 0 + ], + "rule_action": [ + "UINT", + 0 + ], + "rule_folder_id": [ + "INT:11", + 0 + ] + }, + "PRIMARY_KEY": "rule_id", + "KEYS": { + "user_id": [ + "INDEX", + "user_id" + ] + } + }, + "phpbb_privmsgs_to": { + "COLUMNS": { + "msg_id": [ + "ULINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "author_id": [ + "ULINT", + 0 + ], + "pm_deleted": [ + "BOOL", + 0 + ], + "pm_new": [ + "BOOL", + 1 + ], + "pm_unread": [ + "BOOL", + 1 + ], + "pm_replied": [ + "BOOL", + 0 + ], + "pm_marked": [ + "BOOL", + 0 + ], + "pm_forwarded": [ + "BOOL", + 0 + ], + "folder_id": [ + "INT:11", + 0 + ] + }, + "KEYS": { + "msg_id": [ + "INDEX", + "msg_id" + ], + "author_id": [ + "INDEX", + "author_id" + ], + "usr_flder_id": [ + "INDEX", + [ + "user_id", + "folder_id" + ] + ] + } + }, + "phpbb_profile_fields": { + "COLUMNS": { + "field_id": [ + "UINT", + null, + "auto_increment" + ], + "field_name": [ + "VCHAR_UNI", + "" + ], + "field_type": [ + "VCHAR:100", + "" + ], + "field_ident": [ + "VCHAR:20", + "" + ], + "field_length": [ + "VCHAR:20", + "" + ], + "field_minlen": [ + "VCHAR", + "" + ], + "field_maxlen": [ + "VCHAR", + "" + ], + "field_novalue": [ + "VCHAR_UNI", + "" + ], + "field_default_value": [ + "VCHAR_UNI", + "" + ], + "field_validation": [ + "VCHAR_UNI:64", + "" + ], + "field_required": [ + "BOOL", + 0 + ], + "field_show_on_reg": [ + "BOOL", + 0 + ], + "field_hide": [ + "BOOL", + 0 + ], + "field_no_view": [ + "BOOL", + 0 + ], + "field_active": [ + "BOOL", + 0 + ], + "field_order": [ + "UINT", + 0 + ], + "field_show_profile": [ + "BOOL", + 0 + ], + "field_show_on_vt": [ + "BOOL", + 0 + ], + "field_show_novalue": [ + "BOOL", + 0 + ], + "field_show_on_pm": [ + "BOOL", + 0 + ], + "field_show_on_ml": [ + "BOOL", + 0 + ], + "field_is_contact": [ + "BOOL", + 0 + ], + "field_contact_desc": [ + "VCHAR", + "" + ], + "field_contact_url": [ + "VCHAR", + "" + ] + }, + "PRIMARY_KEY": "field_id", + "KEYS": { + "fld_type": [ + "INDEX", + "field_type" + ], + "fld_ordr": [ + "INDEX", + "field_order" + ] + } + }, + "phpbb_profile_fields_data": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "pf_phpbb_interests": [ + "MTEXT", + "" + ], + "pf_phpbb_occupation": [ + "MTEXT", + "" + ], + "pf_phpbb_facebook": [ + "VCHAR", + "" + ], + "pf_phpbb_googleplus": [ + "VCHAR", + "" + ], + "pf_phpbb_icq": [ + "VCHAR", + "" + ], + "pf_phpbb_location": [ + "VCHAR", + "" + ], + "pf_phpbb_skype": [ + "VCHAR", + "" + ], + "pf_phpbb_twitter": [ + "VCHAR", + "" + ], + "pf_phpbb_website": [ + "VCHAR", + "" + ], + "pf_phpbb_yahoo": [ + "VCHAR", + "" + ], + "pf_phpbb_youtube": [ + "VCHAR", + "" + ], + "pf_phpbb_aol": [ + "VCHAR", + "" + ] + }, + "PRIMARY_KEY": "user_id" + }, + "phpbb_profile_fields_lang": { + "COLUMNS": { + "field_id": [ + "UINT", + 0 + ], + "lang_id": [ + "UINT", + 0 + ], + "option_id": [ + "UINT", + 0 + ], + "field_type": [ + "VCHAR:100", + "" + ], + "lang_value": [ + "VCHAR_UNI", + "" + ] + }, + "PRIMARY_KEY": [ + "field_id", + "lang_id", + "option_id" + ] + }, + "phpbb_profile_lang": { + "COLUMNS": { + "field_id": [ + "UINT", + 0 + ], + "lang_id": [ + "UINT", + 0 + ], + "lang_name": [ + "VCHAR_UNI", + "" + ], + "lang_explain": [ + "TEXT_UNI", + "" + ], + "lang_default_value": [ + "VCHAR_UNI", + "" + ] + }, + "PRIMARY_KEY": [ + "field_id", + "lang_id" + ] + }, + "phpbb_ranks": { + "COLUMNS": { + "rank_id": [ + "UINT", + null, + "auto_increment" + ], + "rank_title": [ + "VCHAR_UNI", + "" + ], + "rank_min": [ + "UINT", + 0 + ], + "rank_special": [ + "BOOL", + 0 + ], + "rank_image": [ + "VCHAR", + "" + ] + }, + "PRIMARY_KEY": "rank_id" + }, + "phpbb_reports": { + "COLUMNS": { + "report_id": [ + "ULINT", + null, + "auto_increment" + ], + "reason_id": [ + "USINT", + 0 + ], + "post_id": [ + "ULINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "user_notify": [ + "BOOL", + 0 + ], + "report_closed": [ + "BOOL", + 0 + ], + "report_time": [ + "TIMESTAMP", + 0 + ], + "report_text": [ + "MTEXT_UNI", + "" + ], + "pm_id": [ + "ULINT", + 0 + ], + "reported_post_enable_bbcode": [ + "BOOL", + 1 + ], + "reported_post_enable_smilies": [ + "BOOL", + 1 + ], + "reported_post_enable_magic_url": [ + "BOOL", + 1 + ], + "reported_post_text": [ + "MTEXT_UNI", + "" + ], + "reported_post_uid": [ + "VCHAR:8", + "" + ], + "reported_post_bitfield": [ + "VCHAR:255", + "" + ] + }, + "PRIMARY_KEY": "report_id", + "KEYS": { + "post_id": [ + "INDEX", + [ + "post_id" + ] + ], + "pm_id": [ + "INDEX", + [ + "pm_id" + ] + ] + } + }, + "phpbb_reports_reasons": { + "COLUMNS": { + "reason_id": [ + "USINT", + null, + "auto_increment" + ], + "reason_title": [ + "VCHAR_UNI", + "" + ], + "reason_description": [ + "MTEXT_UNI", + "" + ], + "reason_order": [ + "USINT", + 0 + ] + }, + "PRIMARY_KEY": "reason_id" + }, + "phpbb_search_results": { + "COLUMNS": { + "search_key": [ + "VCHAR:32", + "" + ], + "search_time": [ + "TIMESTAMP", + 0 + ], + "search_keywords": [ + "MTEXT_UNI", + "" + ], + "search_authors": [ + "MTEXT", + "" + ] + }, + "PRIMARY_KEY": "search_key" + }, + "phpbb_search_wordlist": { + "COLUMNS": { + "word_id": [ + "ULINT", + null, + "auto_increment" + ], + "word_text": [ + "VCHAR_UNI", + "" + ], + "word_common": [ + "BOOL", + 0 + ], + "word_count": [ + "UINT", + 0 + ] + }, + "PRIMARY_KEY": "word_id", + "KEYS": { + "wrd_txt": [ + "UNIQUE", + "word_text" + ], + "wrd_cnt": [ + "INDEX", + "word_count" + ] + } + }, + "phpbb_search_wordmatch": { + "COLUMNS": { + "post_id": [ + "ULINT", + 0 + ], + "word_id": [ + "ULINT", + 0 + ], + "title_match": [ + "BOOL", + 0 + ] + }, + "KEYS": { + "word_id": [ + "INDEX", + "word_id" + ], + "post_id": [ + "INDEX", + "post_id" + ], + "un_mtch": [ + "UNIQUE", + [ + "word_id", + "post_id", + "title_match" + ] + ] + } + }, + "phpbb_sessions": { + "COLUMNS": { + "session_id": [ + "CHAR:32", + "" + ], + "session_user_id": [ + "ULINT", + 0 + ], + "session_last_visit": [ + "TIMESTAMP", + 0 + ], + "session_start": [ + "TIMESTAMP", + 0 + ], + "session_time": [ + "TIMESTAMP", + 0 + ], + "session_ip": [ + "VCHAR:40", + "" + ], + "session_browser": [ + "VCHAR:150", + "" + ], + "session_forwarded_for": [ + "VCHAR:255", + "" + ], + "session_page": [ + "VCHAR_UNI", + "" + ], + "session_viewonline": [ + "BOOL", + 1 + ], + "session_autologin": [ + "BOOL", + 0 + ], + "session_admin": [ + "BOOL", + 0 + ], + "session_forum_id": [ + "UINT", + 0 + ] + }, + "PRIMARY_KEY": "session_id", + "KEYS": { + "session_time": [ + "INDEX", + "session_time" + ], + "session_user_id": [ + "INDEX", + "session_user_id" + ], + "session_fid": [ + "INDEX", + [ + "session_forum_id" + ] + ] + } + }, + "phpbb_sessions_keys": { + "COLUMNS": { + "key_id": [ + "CHAR:32", + "" + ], + "user_id": [ + "ULINT", + 0 + ], + "last_ip": [ + "VCHAR:40", + "" + ], + "last_login": [ + "TIMESTAMP", + 0 + ] + }, + "PRIMARY_KEY": [ + "key_id", + "user_id" + ], + "KEYS": { + "last_login": [ + "INDEX", + "last_login" + ] + } + }, + "phpbb_sitelist": { + "COLUMNS": { + "site_id": [ + "UINT", + null, + "auto_increment" + ], + "site_ip": [ + "VCHAR:40", + "" + ], + "site_hostname": [ + "VCHAR", + "" + ], + "ip_exclude": [ + "BOOL", + 0 + ] + }, + "PRIMARY_KEY": "site_id" + }, + "phpbb_smilies": { + "COLUMNS": { + "smiley_id": [ + "UINT", + null, + "auto_increment" + ], + "code": [ + "VCHAR_UNI:50", + "" + ], + "emotion": [ + "VCHAR_UNI", + "" + ], + "smiley_url": [ + "VCHAR:50", + "" + ], + "smiley_width": [ + "USINT", + 0 + ], + "smiley_height": [ + "USINT", + 0 + ], + "smiley_order": [ + "UINT", + 0 + ], + "display_on_posting": [ + "BOOL", + 1 + ] + }, + "PRIMARY_KEY": "smiley_id", + "KEYS": { + "display_on_post": [ + "INDEX", + "display_on_posting" + ] + } + }, + "phpbb_styles": { + "COLUMNS": { + "style_id": [ + "UINT", + null, + "auto_increment" + ], + "style_name": [ + "VCHAR_UNI:255", + "" + ], + "style_copyright": [ + "VCHAR_UNI", + "" + ], + "style_active": [ + "BOOL", + 1 + ], + "style_path": [ + "VCHAR:100", + "" + ], + "bbcode_bitfield": [ + "VCHAR:255", + "kNg=" + ], + "style_parent_id": [ + "UINT:4", + 0 + ], + "style_parent_tree": [ + "TEXT", + "" + ] + }, + "PRIMARY_KEY": "style_id", + "KEYS": { + "style_name": [ + "UNIQUE", + "style_name" + ] + } + }, + "phpbb_teampage": { + "COLUMNS": { + "teampage_id": [ + "UINT", + null, + "auto_increment" + ], + "group_id": [ + "UINT", + 0 + ], + "teampage_name": [ + "VCHAR_UNI:255", + "" + ], + "teampage_position": [ + "UINT", + 0 + ], + "teampage_parent": [ + "UINT", + 0 + ] + }, + "PRIMARY_KEY": "teampage_id" + }, + "phpbb_topics": { + "COLUMNS": { + "topic_id": [ + "ULINT", + null, + "auto_increment" + ], + "forum_id": [ + "UINT", + 0 + ], + "icon_id": [ + "UINT", + 0 + ], + "topic_attachment": [ + "BOOL", + 0 + ], + "topic_reported": [ + "BOOL", + 0 + ], + "topic_title": [ + "STEXT_UNI", + "", + "true_sort" + ], + "topic_poster": [ + "ULINT", + 0 + ], + "topic_time": [ + "TIMESTAMP", + 0 + ], + "topic_time_limit": [ + "TIMESTAMP", + 0 + ], + "topic_views": [ + "UINT", + 0 + ], + "topic_status": [ + "TINT:3", + 0 + ], + "topic_type": [ + "TINT:3", + 0 + ], + "topic_first_post_id": [ + "ULINT", + 0 + ], + "topic_first_poster_name": [ + "VCHAR_UNI:255", + "", + "true_sort" + ], + "topic_first_poster_colour": [ + "VCHAR:6", + "" + ], + "topic_last_post_id": [ + "ULINT", + 0 + ], + "topic_last_poster_id": [ + "ULINT", + 0 + ], + "topic_last_poster_name": [ + "VCHAR_UNI", + "" + ], + "topic_last_poster_colour": [ + "VCHAR:6", + "" + ], + "topic_last_post_subject": [ + "STEXT_UNI", + "" + ], + "topic_last_post_time": [ + "TIMESTAMP", + 0 + ], + "topic_last_view_time": [ + "TIMESTAMP", + 0 + ], + "topic_moved_id": [ + "ULINT", + 0 + ], + "topic_bumped": [ + "BOOL", + 0 + ], + "topic_bumper": [ + "UINT", + 0 + ], + "poll_title": [ + "STEXT_UNI", + "" + ], + "poll_start": [ + "TIMESTAMP", + 0 + ], + "poll_length": [ + "TIMESTAMP", + 0 + ], + "poll_max_options": [ + "TINT:4", + 1 + ], + "poll_last_vote": [ + "TIMESTAMP", + 0 + ], + "poll_vote_change": [ + "BOOL", + 0 + ], + "topic_visibility": [ + "TINT:3", + 0 + ], + "topic_delete_time": [ + "TIMESTAMP", + 0 + ], + "topic_delete_reason": [ + "STEXT_UNI", + "" + ], + "topic_delete_user": [ + "ULINT", + 0 + ], + "topic_posts_approved": [ + "UINT", + 0 + ], + "topic_posts_unapproved": [ + "UINT", + 0 + ], + "topic_posts_softdeleted": [ + "UINT", + 0 + ] + }, + "PRIMARY_KEY": "topic_id", + "KEYS": { + "forum_id": [ + "INDEX", + "forum_id" + ], + "forum_id_type": [ + "INDEX", + [ + "forum_id", + "topic_type" + ] + ], + "last_post_time": [ + "INDEX", + "topic_last_post_time" + ], + "fid_time_moved": [ + "INDEX", + [ + "forum_id", + "topic_last_post_time", + "topic_moved_id" + ] + ], + "topic_visibility": [ + "INDEX", + [ + "topic_visibility" + ] + ], + "forum_vis_last": [ + "INDEX", + [ + "forum_id", + "topic_visibility", + "topic_last_post_id" + ] + ], + "latest_topics": [ + "INDEX", + [ + "forum_id", + "topic_last_post_time", + "topic_last_post_id", + "topic_moved_id" + ] + ] + } + }, + "phpbb_topics_posted": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "topic_id": [ + "ULINT", + 0 + ], + "topic_posted": [ + "BOOL", + 0 + ] + }, + "PRIMARY_KEY": [ + "user_id", + "topic_id" + ] + }, + "phpbb_topics_track": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "topic_id": [ + "ULINT", + 0 + ], + "forum_id": [ + "UINT", + 0 + ], + "mark_time": [ + "TIMESTAMP", + 0 + ] + }, + "PRIMARY_KEY": [ + "user_id", + "topic_id" + ], + "KEYS": { + "forum_id": [ + "INDEX", + "forum_id" + ], + "topic_id": [ + "INDEX", + [ + "topic_id" + ] + ] + } + }, + "phpbb_topics_watch": { + "COLUMNS": { + "topic_id": [ + "ULINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "notify_status": [ + "BOOL", + 0 + ] + }, + "KEYS": { + "topic_id": [ + "INDEX", + "topic_id" + ], + "user_id": [ + "INDEX", + "user_id" + ], + "notify_stat": [ + "INDEX", + "notify_status" + ] + } + }, + "phpbb_user_group": { + "COLUMNS": { + "group_id": [ + "UINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "group_leader": [ + "BOOL", + 0 + ], + "user_pending": [ + "BOOL", + 1 + ] + }, + "KEYS": { + "group_id": [ + "INDEX", + "group_id" + ], + "user_id": [ + "INDEX", + "user_id" + ], + "group_leader": [ + "INDEX", + "group_leader" + ] + } + }, + "phpbb_user_notifications": { + "COLUMNS": { + "item_type": [ + "VCHAR:165", + "" + ], + "item_id": [ + "ULINT", + 0 + ], + "user_id": [ + "ULINT", + 0 + ], + "method": [ + "VCHAR:165", + "" + ], + "notify": [ + "BOOL", + 1 + ] + }, + "KEYS": { + "user_id": [ + "INDEX", + [ + "user_id" + ] + ], + "uid_itm_id": [ + "INDEX", + [ + "user_id", + "item_id" + ] + ], + "usr_itm_tpe": [ + "INDEX", + [ + "user_id", + "item_type", + "item_id" + ] + ], + "itm_usr_mthd": [ + "UNIQUE", + [ + "item_type", + "item_id", + "user_id", + "method" + ] + ] + } + }, + "phpbb_users": { + "COLUMNS": { + "user_id": [ + "ULINT", + null, + "auto_increment" + ], + "user_type": [ + "TINT:2", + 0 + ], + "group_id": [ + "UINT", + 3 + ], + "user_permissions": [ + "MTEXT", + "" + ], + "user_perm_from": [ + "UINT", + 0 + ], + "user_ip": [ + "VCHAR:40", + "" + ], + "user_regdate": [ + "TIMESTAMP", + 0 + ], + "username": [ + "VCHAR_CI", + "" + ], + "username_clean": [ + "VCHAR_CI", + "" + ], + "user_password": [ + "VCHAR:255", + "" + ], + "user_passchg": [ + "TIMESTAMP", + 0 + ], + "user_email": [ + "VCHAR_UNI:100", + "" + ], + "user_email_hash": [ + "BINT", + 0 + ], + "user_birthday": [ + "VCHAR:10", + "" + ], + "user_lastvisit": [ + "TIMESTAMP", + 0 + ], + "user_lastmark": [ + "TIMESTAMP", + 0 + ], + "user_lastpost_time": [ + "TIMESTAMP", + 0 + ], + "user_lastpage": [ + "VCHAR_UNI:200", + "" + ], + "user_last_confirm_key": [ + "VCHAR:10", + "" + ], + "user_last_search": [ + "TIMESTAMP", + 0 + ], + "user_warnings": [ + "TINT:4", + 0 + ], + "user_last_warning": [ + "TIMESTAMP", + 0 + ], + "user_login_attempts": [ + "TINT:4", + 0 + ], + "user_inactive_reason": [ + "TINT:2", + 0 + ], + "user_inactive_time": [ + "TIMESTAMP", + 0 + ], + "user_posts": [ + "UINT", + 0 + ], + "user_lang": [ + "VCHAR:30", + "" + ], + "user_timezone": [ + "VCHAR:100", + "" + ], + "user_dateformat": [ + "VCHAR_UNI:64", + "d M Y H:i" + ], + "user_style": [ + "UINT", + 0 + ], + "user_rank": [ + "UINT", + 0 + ], + "user_colour": [ + "VCHAR:6", + "" + ], + "user_new_privmsg": [ + "INT:4", + 0 + ], + "user_unread_privmsg": [ + "INT:4", + 0 + ], + "user_last_privmsg": [ + "TIMESTAMP", + 0 + ], + "user_message_rules": [ + "BOOL", + 0 + ], + "user_full_folder": [ + "INT:11", + -3 + ], + "user_emailtime": [ + "TIMESTAMP", + 0 + ], + "user_topic_show_days": [ + "USINT", + 0 + ], + "user_topic_sortby_type": [ + "VCHAR:1", + "t" + ], + "user_topic_sortby_dir": [ + "VCHAR:1", + "d" + ], + "user_post_show_days": [ + "USINT", + 0 + ], + "user_post_sortby_type": [ + "VCHAR:1", + "t" + ], + "user_post_sortby_dir": [ + "VCHAR:1", + "a" + ], + "user_notify": [ + "BOOL", + 0 + ], + "user_notify_pm": [ + "BOOL", + 1 + ], + "user_notify_type": [ + "TINT:4", + 0 + ], + "user_allow_pm": [ + "BOOL", + 1 + ], + "user_allow_viewonline": [ + "BOOL", + 1 + ], + "user_allow_viewemail": [ + "BOOL", + 1 + ], + "user_allow_massemail": [ + "BOOL", + 1 + ], + "user_options": [ + "UINT:11", + 230271 + ], + "user_avatar": [ + "VCHAR", + "" + ], + "user_avatar_type": [ + "VCHAR:255", + "" + ], + "user_avatar_width": [ + "USINT", + 0 + ], + "user_avatar_height": [ + "USINT", + 0 + ], + "user_sig": [ + "MTEXT_UNI", + "" + ], + "user_sig_bbcode_uid": [ + "VCHAR:8", + "" + ], + "user_sig_bbcode_bitfield": [ + "VCHAR:255", + "" + ], + "user_jabber": [ + "VCHAR_UNI", + "" + ], + "user_actkey": [ + "VCHAR:32", + "" + ], + "user_newpasswd": [ + "VCHAR:255", + "" + ], + "user_form_salt": [ + "VCHAR_UNI:32", + "" + ], + "user_new": [ + "BOOL", + 1 + ], + "user_reminded": [ + "TINT:4", + 0 + ], + "user_reminded_time": [ + "TIMESTAMP", + 0 + ] + }, + "PRIMARY_KEY": "user_id", + "KEYS": { + "user_birthday": [ + "INDEX", + "user_birthday" + ], + "user_email_hash": [ + "INDEX", + "user_email_hash" + ], + "user_type": [ + "INDEX", + "user_type" + ], + "username_clean": [ + "UNIQUE", + "username_clean" + ] + } + }, + "phpbb_warnings": { + "COLUMNS": { + "warning_id": [ + "UINT", + null, + "auto_increment" + ], + "user_id": [ + "ULINT", + 0 + ], + "post_id": [ + "ULINT", + 0 + ], + "log_id": [ + "ULINT", + 0 + ], + "warning_time": [ + "TIMESTAMP", + 0 + ] + }, + "PRIMARY_KEY": "warning_id" + }, + "phpbb_words": { + "COLUMNS": { + "word_id": [ + "ULINT", + null, + "auto_increment" + ], + "word": [ + "VCHAR_UNI", + "" + ], + "replacement": [ + "VCHAR_UNI", + "" + ] + }, + "PRIMARY_KEY": "word_id" + }, + "phpbb_zebra": { + "COLUMNS": { + "user_id": [ + "ULINT", + 0 + ], + "zebra_id": [ + "ULINT", + 0 + ], + "friend": [ + "BOOL", + 0 + ], + "foe": [ + "BOOL", + 0 + ] + }, + "PRIMARY_KEY": [ + "user_id", + "zebra_id" + ] + } +} \ No newline at end of file diff --git a/install/schemas/schema_data.sql b/install/schemas/schema_data.sql new file mode 100644 index 0000000..174acc2 --- /dev/null +++ b/install/schemas/schema_data.sql @@ -0,0 +1,812 @@ +# +# $Id$ +# + +# POSTGRES BEGIN # + +# -- Config +INSERT INTO phpbb_config (config_name, config_value) VALUES ('active_sessions', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_attachments', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_autologin', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_avatar', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_avatar_gravatar', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_avatar_local', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_avatar_remote', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_avatar_upload', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_avatar_remote_upload', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_bbcode', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_birthdays', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_bookmarks', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_cdn', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_emailreuse', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_password_reset', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_forum_notify', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_live_searches', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_mass_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_name_chars', 'USERNAME_CHARS_ANY'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_namechange', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_nocensors', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_pm_attach', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_pm_report', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_post_flash', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_post_links', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_privmsg', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_quick_reply', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_sig', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_sig_bbcode', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_sig_flash', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_sig_img', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_sig_links', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_sig_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_sig_smilies', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_smilies', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_topic_notify', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allowed_schemes_links', 'http,https,ftp'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('assets_version', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('attachment_quota', '52428800'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('auth_bbcode_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('auth_flash_pm', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('auth_img_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('auth_method', 'db'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('auth_smilies_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_filesize', '6144'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_gallery_path', 'images/avatars/gallery'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_max_height', '90'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_max_width', '90'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_min_height', '20'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_min_width', '20'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_path', 'images/avatars/upload'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('avatar_salt', 'phpbb_avatar'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_contact', 'contact@yourdomain.tld'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_contact_name', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_disable', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_disable_msg', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_email', 'address@yourdomain.tld'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_email_form', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_email_sig', '{L_CONFIG_BOARD_EMAIL_SIG}'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_hide_emails', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_index_text', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('board_timezone', 'UTC'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('browser_check', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('bump_interval', '10'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('bump_type', 'd'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('cache_gc', '7200'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_plugin', 'core.captcha.plugins.nogd'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_foreground_noise', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_x_grid', '25'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_y_grid', '25'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_wave', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_3d_noise', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('captcha_gd_fonts', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('confirm_refresh', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('check_attachment_content', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('check_dnsbl', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('chg_passforce', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('contact_admin_form_enable', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('cookie_domain', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('cookie_name', 'phpbb3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('cookie_path', '/'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('cookie_secure', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('coppa_enable', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('coppa_fax', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('coppa_mail', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('database_gc', '604800'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('dbms_version', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('default_dateformat', 'D M d, Y g:i a'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('default_style', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('display_last_edited', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('display_last_subject', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('display_order', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('edit_time', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('extension_force_unstable', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('delete_time', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('email_check_mx', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('email_enable', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('email_force_sender', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('email_max_chunk_size', '50'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('email_package_size', '20'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('enable_accurate_pm_button', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('enable_confirm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('enable_mod_rewrite', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('allow_board_notifications', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('enable_pm_icons', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('enable_post_confirm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_enable', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_http_auth', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_limit_post', '15'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_limit_topic', '10'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_overall_forums', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_overall', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_forum', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_topic', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_topics_new', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_topics_active', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('feed_item_statistics', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('flood_interval', '15'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('force_server_vars', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('form_token_lifetime', '7200'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('form_token_mintime', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('form_token_sid_guests', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('forward_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('forwarded_for_check', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('full_folder_action', '2'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_mysql_max_word_len', '254'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_mysql_min_word_len', '4'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_native_common_thres', '5'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_native_load_upd', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_native_max_chars', '14'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_native_min_chars', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_max_word_len', '254'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_min_word_len', '4'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_postgres_ts_name', 'simple'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_sphinx_indexer_mem_limit', '512'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('fulltext_sphinx_stopwords', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('gzip_compress', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('help_send_statistics', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('help_send_statistics_time', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('hot_threshold', '25'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('icons_path', 'images/icons'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_create_thumbnail', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_display_inlined', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_link_height', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_link_width', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_max_height', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_max_thumb_width', '400'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_max_width', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('img_min_thumb_filesize', '12000'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ip_check', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ip_login_limit_max', '50'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ip_login_limit_time', '21600'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ip_login_limit_use_forwarded', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_enable', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_host', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_password', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_package_size', '20'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_port', '5222'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_use_ssl', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('jab_username', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ldap_base_dn', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ldap_email', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ldap_password', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ldap_port', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ldap_server', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ldap_uid', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ldap_user', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ldap_user_filter', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('legend_sort_groupname', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('limit_load', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('limit_search_load', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_anon_lastread', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_birthdays', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_memberlist', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_viewprofile', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_cpf_viewtopic', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_db_lastread', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_db_track', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_jumpbox', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_moderators', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_notifications', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_online', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_online_guests', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_online_time', '5'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_onlinetrack', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_search', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_tplcompile', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_unreads_search', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_user_activity', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('load_user_activity_limit', '5000'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_attachments', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_attachments_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_autologin_time', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_filesize', '262144'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_filesize_pm', '262144'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_login_attempts', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_name_chars', '20'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_num_search_keywords', '10'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_pass_chars', '100'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_poll_options', '10'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_post_chars', '60000'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_post_font_size', '200'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_post_img_height', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_post_img_width', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_post_smilies', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_post_urls', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_quote_depth', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_reg_attempts', '5'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_chars', '255'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_font_size', '200'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_img_height', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_img_width', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_smilies', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('max_sig_urls', '5'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_name_chars', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_pass_chars', '6'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_post_chars', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('min_search_author_chars', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('new_member_post_limit', '3'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('new_member_group_default', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('override_user_style', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('pass_complex', 'PASS_TYPE_ANY'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('plupload_salt', 'phpbb_plupload'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('pm_edit_time', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('pm_max_boxes', '4'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('pm_max_msgs', '50'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('pm_max_recipients', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('posts_per_page', '10'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('print_pm', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('queue_interval', '60'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('ranks_path', 'images/ranks'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('read_notification_expire_days', '30'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('read_notification_gc', '86400'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('remote_upload_verify', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('require_activation', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('referer_validation', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('script_path', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('search_block_size', '250'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('search_gc', '7200'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('search_interval', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('search_anonymous_interval', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('search_type', '\phpbb\search\fulltext_native'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('search_store_results', '1800'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('secure_allow_deny', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('secure_allow_empty_referer', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('secure_downloads', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('server_name', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('server_port', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('server_protocol', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('session_gc', '3600'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('session_length', '3600'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('site_desc', '{L_CONFIG_SITE_DESC}'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('site_home_url', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('site_home_text', ''); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('sitename', '{L_CONFIG_SITENAME}'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('smilies_path', 'images/smilies'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('smilies_per_page', '50'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('smtp_auth_method', 'PLAIN'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('smtp_delivery', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('smtp_host', ''); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('smtp_password', '', 1); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('smtp_port', '25'); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('smtp_username', '', 1); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('teampage_memberships', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('teampage_forums', '1'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('topics_per_page', '25'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('tpl_allow_php', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_icons_path', 'images/upload_icons'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('upload_path', 'files'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('use_system_cron', '0'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('version', '3.2.7'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_expire_days', '90'); +INSERT INTO phpbb_config (config_name, config_value) VALUES ('warnings_gc', '14400'); + +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cache_last_gc', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('cron_lock', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('database_last_gc', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('last_queue_run', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('newest_user_colour', 'AA0000', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('newest_user_id', '2', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('newest_username', '', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('num_files', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('num_posts', '1', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('num_topics', '1', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('num_users', '1', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('plupload_last_gc', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('rand_seed', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('rand_seed_last_update', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('read_notification_last_gc', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('record_online_date', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('record_online_users', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('search_indexing_state', '', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('search_last_gc', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('session_last_gc', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('upload_dir_size', '0', 1); +INSERT INTO phpbb_config (config_name, config_value, is_dynamic) VALUES ('warnings_last_gc', '0', 1); + +# Config text +INSERT INTO phpbb_config_text (config_name, config_value) VALUES ('contact_admin_info', ''); +INSERT INTO phpbb_config_text (config_name, config_value) VALUES ('contact_admin_info_uid', ''); +INSERT INTO phpbb_config_text (config_name, config_value) VALUES ('contact_admin_info_bitfield', ''); +INSERT INTO phpbb_config_text (config_name, config_value) VALUES ('contact_admin_info_flags', '7'); + +# -- Forum related auth options +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_announce', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_announce_global', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_attach', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_bbcode', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_bump', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_delete', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_download', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_edit', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_email', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_flash', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_icons', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_ignoreflood', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_img', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_list', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_list_topics', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_noapprove', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_poll', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_post', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_postcount', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_print', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_read', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_reply', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_report', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_search', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_sigs', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_smilies', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_sticky', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_subscribe', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_user_lock', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_vote', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_votechg', 1); +INSERT INTO phpbb_acl_options (auth_option, is_local) VALUES ('f_softdelete', 1); + +# -- Moderator related auth options +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_approve', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_chgposter', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_delete', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_edit', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_info', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_lock', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_merge', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_move', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_report', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_split', 1, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_softdelete', 1, 1); + +# -- Global moderator auth option (not a local option) +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_ban', 0, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_pm_report', 0, 1); +INSERT INTO phpbb_acl_options (auth_option, is_local, is_global) VALUES ('m_warn', 0, 1); + +# -- Admin related auth options +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_aauth', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_attach', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_authgroups', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_authusers', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_backup', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_ban', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_bbcode', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_board', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_bots', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_clearlogs', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_email', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_extensions', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_fauth', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_forum', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_forumadd', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_forumdel', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_group', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_groupadd', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_groupdel', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_icons', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_jabber', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_language', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_mauth', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_modules', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_names', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_phpinfo', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_profile', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_prune', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_ranks', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_reasons', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_roles', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_search', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_server', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_styles', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_switchperm', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_uauth', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_user', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_userdel', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_viewauth', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_viewlogs', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('a_words', 1); + +# -- User related auth options +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_attach', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgavatar', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgcensors', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgemail', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chggrp', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgname', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgpasswd', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_chgprofileinfo', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_download', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_hideonline', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_ignoreflood', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_masspm', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_masspm_group', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_attach', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_bbcode', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_delete', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_download', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_edit', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_emailpm', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_flash', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_forward', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_img', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_printpm', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_pm_smilies', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_readpm', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_savedrafts', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_search', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_sendemail', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_sendim', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_sendpm', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_sig', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_viewonline', 1); +INSERT INTO phpbb_acl_options (auth_option, is_global) VALUES ('u_viewprofile', 1); + + +# -- standard auth roles +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_ADMIN_STANDARD', 'ROLE_DESCRIPTION_ADMIN_STANDARD', 'a_', 1); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_ADMIN_FORUM', 'ROLE_DESCRIPTION_ADMIN_FORUM', 'a_', 3); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_ADMIN_USERGROUP', 'ROLE_DESCRIPTION_ADMIN_USERGROUP', 'a_', 4); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_ADMIN_FULL', 'ROLE_DESCRIPTION_ADMIN_FULL', 'a_', 2); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_FULL', 'ROLE_DESCRIPTION_USER_FULL', 'u_', 3); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_STANDARD', 'ROLE_DESCRIPTION_USER_STANDARD', 'u_', 1); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_LIMITED', 'ROLE_DESCRIPTION_USER_LIMITED', 'u_', 2); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NOPM', 'ROLE_DESCRIPTION_USER_NOPM', 'u_', 4); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NOAVATAR', 'ROLE_DESCRIPTION_USER_NOAVATAR', 'u_', 5); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_MOD_FULL', 'ROLE_DESCRIPTION_MOD_FULL', 'm_', 3); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_MOD_STANDARD', 'ROLE_DESCRIPTION_MOD_STANDARD', 'm_', 1); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_MOD_SIMPLE', 'ROLE_DESCRIPTION_MOD_SIMPLE', 'm_', 2); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_MOD_QUEUE', 'ROLE_DESCRIPTION_MOD_QUEUE', 'm_', 4); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_FULL', 'ROLE_DESCRIPTION_FORUM_FULL', 'f_', 7); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_STANDARD', 'ROLE_DESCRIPTION_FORUM_STANDARD', 'f_', 5); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NOACCESS', 'ROLE_DESCRIPTION_FORUM_NOACCESS', 'f_', 1); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_READONLY', 'ROLE_DESCRIPTION_FORUM_READONLY', 'f_', 2); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_LIMITED', 'ROLE_DESCRIPTION_FORUM_LIMITED', 'f_', 3); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_BOT', 'ROLE_DESCRIPTION_FORUM_BOT', 'f_', 9); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_ONQUEUE', 'ROLE_DESCRIPTION_FORUM_ONQUEUE', 'f_', 8); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_POLLS', 'ROLE_DESCRIPTION_FORUM_POLLS', 'f_', 6); +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_LIMITED_POLLS', 'ROLE_DESCRIPTION_FORUM_LIMITED_POLLS', 'f_', 4); + +# 23 +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', 6); + +# 24 +INSERT INTO phpbb_acl_roles (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', 10); + +# -- phpbb_styles +INSERT INTO phpbb_styles (style_name, style_copyright, style_active, style_path, bbcode_bitfield, style_parent_id, style_parent_tree) VALUES ('prosilver', '© phpBB Limited', 1, 'prosilver', '//g=', 0, ''); + +# -- Forums +INSERT INTO phpbb_forums (forum_name, forum_desc, left_id, right_id, parent_id, forum_type, forum_posts_approved, forum_posts_unapproved, forum_posts_softdeleted, forum_topics_approved, forum_topics_unapproved, forum_topics_softdeleted, forum_last_post_id, forum_last_poster_id, forum_last_poster_name, forum_last_poster_colour, forum_last_post_time, forum_link, forum_password, forum_image, forum_rules, forum_rules_link, forum_rules_uid, forum_desc_uid, prune_days, prune_viewed, forum_parents) VALUES ('{L_FORUMS_FIRST_CATEGORY}', '', 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 'Admin', 'AA0000', 972086460, '', '', '', '', '', '', '', 0, 0, ''); + +INSERT INTO phpbb_forums (forum_name, forum_desc, left_id, right_id, parent_id, forum_type, forum_posts_approved, forum_posts_unapproved, forum_posts_softdeleted, forum_topics_approved, forum_topics_unapproved, forum_topics_softdeleted, forum_last_post_id, forum_last_poster_id, forum_last_poster_name, forum_last_poster_colour, forum_last_post_subject, forum_last_post_time, forum_link, forum_password, forum_image, forum_rules, forum_rules_link, forum_rules_uid, forum_desc_uid, prune_days, prune_viewed, forum_parents, forum_flags) VALUES ('{L_FORUMS_TEST_FORUM_TITLE}', '{L_FORUMS_TEST_FORUM_DESC}', 2, 3, 1, 1, 1, 0, 0, 1, 0, 0, 1, 2, 'Admin', 'AA0000', '{L_TOPICS_TOPIC_TITLE}', 972086460, '', '', '', '', '', '', '', 0, 0, '', 48); + +# -- Users / Anonymous user +INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_jabber, user_actkey, user_newpasswd, user_allow_massemail) VALUES (2, 1, 'Anonymous', 'anonymous', 0, '', '', 'en', 1, 0, '', 0, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', '', '', 0); + +# -- username: Admin password: admin (change this or remove it once everything is working!) +INSERT INTO phpbb_users (user_type, group_id, username, username_clean, user_regdate, user_password, user_email, user_lang, user_style, user_rank, user_colour, user_posts, user_permissions, user_ip, user_birthday, user_lastpage, user_last_confirm_key, user_post_sortby_type, user_post_sortby_dir, user_topic_sortby_type, user_topic_sortby_dir, user_avatar, user_sig, user_sig_bbcode_uid, user_jabber, user_actkey, user_newpasswd) VALUES (3, 5, 'Admin', 'admin', 0, '21232f297a57a5a743894a0e4a801fc3', 'admin@yourdomain.com', 'en', 1, 1, 'AA0000', 1, '', '', '', '', '', 't', 'a', 't', 'd', '', '', '', '', '', ''); + +# -- Groups +INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('GUESTS', 3, 0, '', 0, '', '', '', 5); +INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('REGISTERED', 3, 0, '', 0, '', '', '', 5); +INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('REGISTERED_COPPA', 3, 0, '', 0, '', '', '', 5); +INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('GLOBAL_MODERATORS', 3, 0, '00AA00', 2, '', '', '', 0); +INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('ADMINISTRATORS', 3, 1, 'AA0000', 1, '', '', '', 0); +INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('BOTS', 3, 0, '9E8DA7', 0, '', '', '', 5); +INSERT INTO phpbb_groups (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5); + +# -- Teampage +INSERT INTO phpbb_teampage (group_id, teampage_name, teampage_position, teampage_parent) VALUES (5, '', 1, 0); +INSERT INTO phpbb_teampage (group_id, teampage_name, teampage_position, teampage_parent) VALUES (4, '', 2, 0); + +# -- User -> Group +INSERT INTO phpbb_user_group (group_id, user_id, user_pending, group_leader) VALUES (1, 1, 0, 0); +INSERT INTO phpbb_user_group (group_id, user_id, user_pending, group_leader) VALUES (2, 2, 0, 0); +INSERT INTO phpbb_user_group (group_id, user_id, user_pending, group_leader) VALUES (4, 2, 0, 0); +INSERT INTO phpbb_user_group (group_id, user_id, user_pending, group_leader) VALUES (5, 2, 0, 1); + +# -- Ranks +INSERT INTO phpbb_ranks (rank_title, rank_min, rank_special, rank_image) VALUES ('{L_RANKS_SITE_ADMIN_TITLE}', 0, 1, ''); + +# -- Roles data + +# Standard Admin (a_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 1, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'a_%' AND auth_option NOT IN ('a_switchperm', 'a_jabber', 'a_phpinfo', 'a_server', 'a_backup', 'a_styles', 'a_clearlogs', 'a_modules', 'a_language', 'a_email', 'a_bots', 'a_search', 'a_aauth', 'a_roles'); + +# Forum admin (a_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 2, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'a_%' AND auth_option IN ('a_', 'a_authgroups', 'a_authusers', 'a_fauth', 'a_forum', 'a_forumadd', 'a_forumdel', 'a_mauth', 'a_prune', 'a_uauth', 'a_viewauth', 'a_viewlogs'); + +# User and Groups Admin (a_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 3, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'a_%' AND auth_option IN ('a_', 'a_authgroups', 'a_authusers', 'a_ban', 'a_group', 'a_groupadd', 'a_groupdel', 'a_ranks', 'a_uauth', 'a_user', 'a_viewauth', 'a_viewlogs'); + +# Full Admin (a_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 4, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'a_%'; + +# All Features (u_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 5, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%'; + +# Standard Features (u_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 6, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_flash', 'u_pm_forward'); + +# Limited Features (u_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 7, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_attach', 'u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_attach', 'u_pm_emailpm', 'u_pm_flash', 'u_savedrafts', 'u_search', 'u_sendemail', 'u_sendim', 'u_masspm', 'u_masspm_group'); + +# No Private Messages (u_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 8, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_', 'u_chgavatar', 'u_chgcensors', 'u_chgemail', 'u_chgpasswd', 'u_download', 'u_hideonline', 'u_sig', 'u_viewprofile'); +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 8, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_readpm', 'u_sendpm', 'u_masspm', 'u_masspm_group'); + +# No Avatar (u_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 9, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option NOT IN ('u_attach', 'u_chgavatar', 'u_viewonline', 'u_chggrp', 'u_chgname', 'u_ignoreflood', 'u_pm_attach', 'u_pm_emailpm', 'u_pm_flash', 'u_savedrafts', 'u_search', 'u_sendemail', 'u_sendim', 'u_masspm', 'u_masspm_group'); +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 9, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_chgavatar'); + +# Full Moderator (m_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 10, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'm_%'; + +# Standard Moderator (m_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 11, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'm_%' AND auth_option NOT IN ('m_ban', 'm_chgposter'); + +# Simple Moderator (m_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 12, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'm_%' AND auth_option IN ('m_', 'm_delete', 'm_softdelete', 'm_edit', 'm_info', 'm_report', 'm_pm_report'); + +# Queue Moderator (m_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 13, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'm_%' AND auth_option IN ('m_', 'm_approve', 'm_edit'); + +# Full Access (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 14, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%'; + +# Standard Access (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 15, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option NOT IN ('f_announce', 'f_announce_global', 'f_flash', 'f_ignoreflood', 'f_poll', 'f_sticky', 'f_user_lock'); + +# No Access (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 16, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option = 'f_'; + +# Read Only Access (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 17, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_', 'f_download', 'f_list', 'f_list_topics', 'f_read', 'f_search', 'f_subscribe', 'f_print'); + +# Limited Access (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 18, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option NOT IN ('f_announce', 'f_announce_global', 'f_attach', 'f_bump', 'f_delete', 'f_flash', 'f_icons', 'f_ignoreflood', 'f_poll', 'f_sticky', 'f_user_lock', 'f_votechg'); + +# Bot Access (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 19, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_', 'f_download', 'f_list', 'f_list_topics', 'f_read', 'f_print'); + +# On Moderation Queue (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 20, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option NOT IN ('f_announce', 'f_announce_global', 'f_bump', 'f_delete', 'f_flash', 'f_icons', 'f_ignoreflood', 'f_poll', 'f_sticky', 'f_user_lock', 'f_votechg', 'f_noapprove'); +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 20, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove'); + +# Standard Access + Polls (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 21, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option NOT IN ('f_announce', 'f_announce_global', 'f_flash', 'f_ignoreflood', 'f_sticky', 'f_user_lock'); + +# Limited Access + Polls (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 22, auth_option_id, 1 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option NOT IN ('f_announce', 'f_announce_global', 'f_attach', 'f_bump', 'f_delete', 'f_flash', 'f_icons', 'f_ignoreflood', 'f_sticky', 'f_user_lock', 'f_votechg'); + +# New Member (u_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 23, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group', 'u_chgprofileinfo'); + +# New Member (f_) +INSERT INTO phpbb_acl_roles_data (role_id, auth_option_id, auth_setting) SELECT 24, auth_option_id, 0 FROM phpbb_acl_options WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove'); + + +# Permissions + +# GUESTS - u_download and u_search ability +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) SELECT 1, 0, auth_option_id, 0, 1 FROM phpbb_acl_options WHERE auth_option IN ('u_', 'u_download', 'u_search'); + +# Admin user - full user features +INSERT INTO phpbb_acl_users (user_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (2, 0, 0, 5, 0); + +# ADMINISTRATOR Group - full user features +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (5, 0, 0, 5, 0); + +# ADMINISTRATOR Group - standard admin +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (5, 0, 0, 1, 0); + +# REGISTERED and REGISTERED_COPPA having standard user features +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (2, 0, 0, 6, 0); +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (3, 0, 0, 6, 0); + +# GLOBAL_MODERATORS having full user features +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (4, 0, 0, 5, 0); + +# GLOBAL_MODERATORS having full global moderator access +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (4, 0, 0, 10, 0); + +# Giving all groups read only access to the first category +# since administrators and moderators are already within the registered users group we do not need to set them here +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (1, 1, 0, 17, 0); +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (2, 1, 0, 17, 0); +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (3, 1, 0, 17, 0); +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (6, 1, 0, 17, 0); + +# Giving access to the first forum + +# guests having read only access +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (1, 2, 0, 17, 0); + +# registered and registered_coppa having standard access +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (2, 2, 0, 15, 0); +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (3, 2, 0, 15, 0); + +# global moderators having standard access + polls +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (4, 2, 0, 21, 0); + +# administrators having full forum and full moderator access +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (5, 2, 0, 14, 0); +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (5, 2, 0, 10, 0); + +# Bots having bot access +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (6, 2, 0, 19, 0); + +# NEW MEMBERS are not allowed to send private messages +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (7, 0, 0, 23, 0); + +# NEW MEMBERS on the queue +INSERT INTO phpbb_acl_groups (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (7, 2, 0, 24, 0); + + +# -- Demo Topic +INSERT INTO phpbb_topics (topic_title, topic_poster, topic_time, topic_views, topic_posts_approved, topic_posts_unapproved, topic_posts_softdeleted, forum_id, topic_status, topic_type, topic_first_post_id, topic_first_poster_name, topic_first_poster_colour, topic_last_post_id, topic_last_poster_id, topic_last_poster_name, topic_last_poster_colour, topic_last_post_subject, topic_last_post_time, topic_last_view_time, poll_title, topic_visibility) VALUES ('{L_TOPICS_TOPIC_TITLE}', 2, 972086460, 0, 1, 0, 0, 2, 0, 0, 1, 'Admin', 'AA0000', 1, 2, 'Admin', 'AA0000', '{L_TOPICS_TOPIC_TITLE}', 972086460, 972086460, '', 1); + +# -- Demo Post +INSERT INTO phpbb_posts (topic_id, forum_id, poster_id, icon_id, post_time, post_username, poster_ip, post_subject, post_text, post_checksum, bbcode_uid, post_visibility) VALUES (1, 2, 2, 0, 972086460, '', '127.0.0.1', '{L_TOPICS_TOPIC_TITLE}', '{L_DEFAULT_INSTALL_POST}', '5dd683b17f641daf84c040bfefc58ce9', '', 1); + +# -- Admin posted to the demo topic +INSERT INTO phpbb_topics_posted (user_id, topic_id, topic_posted) VALUES (2, 1, 1); + +# -- Smilies +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':D', 'icon_e_biggrin.gif', '{L_SMILIES_VERY_HAPPY}', 15, 17, 1); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-D', 'icon_e_biggrin.gif', '{L_SMILIES_VERY_HAPPY}', 15, 17, 2); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':grin:', 'icon_e_biggrin.gif', '{L_SMILIES_VERY_HAPPY}', 15, 17, 3); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':)', 'icon_e_smile.gif', '{L_SMILIES_SMILE}', 15, 17, 4); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-)', 'icon_e_smile.gif', '{L_SMILIES_SMILE}', 15, 17, 5); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':smile:', 'icon_e_smile.gif', '{L_SMILIES_SMILE}', 15, 17, 6); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (';)', 'icon_e_wink.gif', '{L_SMILIES_WINK}', 15, 17, 7); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (';-)', 'icon_e_wink.gif', '{L_SMILIES_WINK}', 15, 17, 8); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':wink:', 'icon_e_wink.gif', '{L_SMILIES_WINK}', 15, 17, 9); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':(', 'icon_e_sad.gif', '{L_SMILIES_SAD}', 15, 17, 10); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-(', 'icon_e_sad.gif', '{L_SMILIES_SAD}', 15, 17, 11); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':sad:', 'icon_e_sad.gif', '{L_SMILIES_SAD}', 15, 17, 12); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':o', 'icon_e_surprised.gif', '{L_SMILIES_SURPRISED}', 15, 17, 13); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-o', 'icon_e_surprised.gif', '{L_SMILIES_SURPRISED}', 15, 17, 14); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':eek:', 'icon_e_surprised.gif', '{L_SMILIES_SURPRISED}', 15, 17, 15); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':shock:', 'icon_eek.gif', '{L_SMILIES_SHOCKED}', 15, 17, 16); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':?', 'icon_e_confused.gif', '{L_SMILIES_CONFUSED}', 15, 17, 17); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-?', 'icon_e_confused.gif', '{L_SMILIES_CONFUSED}', 15, 17, 18); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':???:', 'icon_e_confused.gif', '{L_SMILIES_CONFUSED}', 15, 17, 19); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES ('8-)', 'icon_cool.gif', '{L_SMILIES_COOL}', 15, 17, 20); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':cool:', 'icon_cool.gif', '{L_SMILIES_COOL}', 15, 17, 21); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':lol:', 'icon_lol.gif', '{L_SMILIES_LAUGHING}', 15, 17, 22); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':x', 'icon_mad.gif', '{L_SMILIES_MAD}', 15, 17, 23); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-x', 'icon_mad.gif', '{L_SMILIES_MAD}', 15, 17, 24); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':mad:', 'icon_mad.gif', '{L_SMILIES_MAD}', 15, 17, 25); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':P', 'icon_razz.gif', '{L_SMILIES_RAZZ}', 15, 17, 26); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-P', 'icon_razz.gif', '{L_SMILIES_RAZZ}', 15, 17, 27); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':razz:', 'icon_razz.gif', '{L_SMILIES_RAZZ}', 15, 17, 28); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':oops:', 'icon_redface.gif', '{L_SMILIES_EMARRASSED}', 15, 17, 29); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':cry:', 'icon_cry.gif', '{L_SMILIES_CRYING}', 15, 17, 30); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':evil:', 'icon_evil.gif', '{L_SMILIES_EVIL}', 15, 17, 31); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':twisted:', 'icon_twisted.gif', '{L_SMILIES_TWISTED_EVIL}', 15, 17, 32); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':roll:', 'icon_rolleyes.gif', '{L_SMILIES_ROLLING_EYES}', 15, 17, 33); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':!:', 'icon_exclaim.gif', '{L_SMILIES_EXCLAMATION}', 15, 17, 34); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':?:', 'icon_question.gif', '{L_SMILIES_QUESTION}', 15, 17, 35); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':idea:', 'icon_idea.gif', '{L_SMILIES_IDEA}', 15, 17, 36); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':arrow:', 'icon_arrow.gif', '{L_SMILIES_ARROW}', 15, 17, 37); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':|', 'icon_neutral.gif', '{L_SMILIES_NEUTRAL}', 15, 17, 38); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':-|', 'icon_neutral.gif', '{L_SMILIES_NEUTRAL}', 15, 17, 39); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':mrgreen:', 'icon_mrgreen.gif', '{L_SMILIES_MR_GREEN}', 15, 17, 40); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':geek:', 'icon_e_geek.gif', '{L_SMILIES_GEEK}', 17, 17, 41); +INSERT INTO phpbb_smilies (code, smiley_url, emotion, smiley_width, smiley_height, smiley_order) VALUES (':ugeek:', 'icon_e_ugeek.gif', '{L_SMILIES_UBER_GEEK}', 17, 18, 42); + +# -- icons +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/fire.gif', 16, 16, 1, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/redface.gif', 16, 16, 9, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/mrgreen.gif', 16, 16, 10, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/heart.gif', 16, 16, 4, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/star.gif', 16, 16, 2, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/radioactive.gif', 16, 16, 3, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('misc/thinking.gif', 16, 16, 5, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/info.gif', 16, 16, 8, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/question.gif', 16, 16, 6, 1); +INSERT INTO phpbb_icons (icons_url, icons_width, icons_height, icons_order, display_on_posting) VALUES ('smile/alert.gif', 16, 16, 7, 1); + +# -- reasons +INSERT INTO phpbb_reports_reasons (reason_title, reason_description, reason_order) VALUES ('warez', '{L_REPORT_WAREZ}', 1); +INSERT INTO phpbb_reports_reasons (reason_title, reason_description, reason_order) VALUES ('spam', '{L_REPORT_SPAM}', 2); +INSERT INTO phpbb_reports_reasons (reason_title, reason_description, reason_order) VALUES ('off_topic', '{L_REPORT_OFF_TOPIC}', 3); +INSERT INTO phpbb_reports_reasons (reason_title, reason_description, reason_order) VALUES ('other', '{L_REPORT_OTHER}', 4); + +# -- extension_groups +INSERT INTO phpbb_extension_groups (group_name, cat_id, allow_group, download_mode, upload_icon, max_filesize, allowed_forums) VALUES ('IMAGES', 1, 1, 1, '', 0, ''); +INSERT INTO phpbb_extension_groups (group_name, cat_id, allow_group, download_mode, upload_icon, max_filesize, allowed_forums) VALUES ('ARCHIVES', 0, 1, 1, '', 0, ''); +INSERT INTO phpbb_extension_groups (group_name, cat_id, allow_group, download_mode, upload_icon, max_filesize, allowed_forums) VALUES ('PLAIN_TEXT', 0, 0, 1, '', 0, ''); +INSERT INTO phpbb_extension_groups (group_name, cat_id, allow_group, download_mode, upload_icon, max_filesize, allowed_forums) VALUES ('DOCUMENTS', 0, 0, 1, '', 0, ''); +INSERT INTO phpbb_extension_groups (group_name, cat_id, allow_group, download_mode, upload_icon, max_filesize, allowed_forums) VALUES ('FLASH_FILES', 5, 0, 1, '', 0, ''); +INSERT INTO phpbb_extension_groups (group_name, cat_id, allow_group, download_mode, upload_icon, max_filesize, allowed_forums) VALUES ('DOWNLOADABLE_FILES', 0, 0, 1, '', 0, ''); + +# -- extensions +INSERT INTO phpbb_extensions (group_id, extension) VALUES (1, 'gif'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (1, 'png'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (1, 'jpeg'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (1, 'jpg'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (1, 'tif'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (1, 'tiff'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (1, 'tga'); + +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'gtar'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'gz'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'tar'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'zip'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'rar'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'ace'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'torrent'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'tgz'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, 'bz2'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (2, '7z'); + +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'txt'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'c'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'h'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'cpp'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'hpp'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'diz'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'csv'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'ini'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'log'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'js'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (3, 'xml'); + +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'xls'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'xlsx'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'xlsm'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'xlsb'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'doc'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'docx'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'docm'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'dot'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'dotx'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'dotm'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'pdf'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'ai'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'ps'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'ppt'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'pptx'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'pptm'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'odg'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'odp'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'ods'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'odt'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (4, 'rtf'); + +INSERT INTO phpbb_extensions (group_id, extension) VALUES (5, 'swf'); + +INSERT INTO phpbb_extensions (group_id, extension) VALUES (6, 'mp3'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (6, 'mpeg'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (6, 'mpg'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (6, 'ogg'); +INSERT INTO phpbb_extensions (group_id, extension) VALUES (6, 'ogm'); + +# Add default profile fields +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_location', 'profilefields.type.string', 'phpbb_location', '20', '2', '100', '', '', '.*', 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, '', ''); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_website', 'profilefields.type.url', 'phpbb_website', '40', '12', '255', '', '', '', 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 2, 1, 'VISIT_WEBSITE', '%s'); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_interests', 'profilefields.type.text', 'phpbb_interests', '3|30', '2', '500', '', '', '.*', 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, '', ''); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_occupation', 'profilefields.type.text', 'phpbb_occupation', '3|30', '2', '500', '', '', '.*', 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, '', ''); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_aol', 'profilefields.type.string', 'phpbb_aol', '40', '5', '255', '', '', '.*', 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 5, 1, '', ''); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_icq', 'profilefields.type.string', 'phpbb_icq', '20', '3', '15', '', '', '[0-9]+', 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 6, 1, 'SEND_ICQ_MESSAGE', 'https://www.icq.com/people/%s/'); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_yahoo', 'profilefields.type.string', 'phpbb_yahoo', '40', '5', '255', '', '', '.*', 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 8, 1, 'SEND_YIM_MESSAGE', 'ymsgr:sendim?%s'); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_facebook', 'profilefields.type.string', 'phpbb_facebook', '20', '5', '50', '', '', '[\w.]+', 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 9, 1, 'VIEW_FACEBOOK_PROFILE', 'http://facebook.com/%s/'); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_twitter', 'profilefields.type.string', 'phpbb_twitter', '20', '1', '15', '', '', '[\w_]+', 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 10, 1, 'VIEW_TWITTER_PROFILE', 'http://twitter.com/%s'); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_skype', 'profilefields.type.string', 'phpbb_skype', '20', '6', '32', '', '', '[a-zA-Z][\w\.,\-_]+', 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 11, 1, 'VIEW_SKYPE_PROFILE', 'skype:%s?userinfo'); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_youtube', 'profilefields.type.string', 'phpbb_youtube', '20', '3', '60', '', '', '[a-zA-Z][\w\.,\-_]+', 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 12, 1, 'VIEW_YOUTUBE_CHANNEL', 'http://youtube.com/user/%s'); +INSERT INTO phpbb_profile_fields (field_name, field_type, field_ident, field_length, field_minlen, field_maxlen, field_novalue, field_default_value, field_validation, field_required, field_show_novalue, field_show_on_reg, field_show_on_pm, field_show_on_vt, field_show_on_ml, field_show_profile, field_hide, field_no_view, field_active, field_order, field_is_contact, field_contact_desc, field_contact_url) VALUES ('phpbb_googleplus', 'profilefields.type.googleplus', 'phpbb_googleplus', '20', '3', '255', '', '', '[\w]+', 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 13, 1, 'VIEW_GOOGLEPLUS_PROFILE', 'http://plus.google.com/%s'); + +# User Notification Options (for first user) +INSERT INTO phpbb_user_notifications (item_type, item_id, user_id, method) VALUES('notification.type.post', 0, 2, 'notification.method.board'); +INSERT INTO phpbb_user_notifications (item_type, item_id, user_id, method) VALUES('notification.type.post', 0, 2, 'notification.method.email'); +INSERT INTO phpbb_user_notifications (item_type, item_id, user_id, method) VALUES('notification.type.topic', 0, 2, 'notification.method.board'); +INSERT INTO phpbb_user_notifications (item_type, item_id, user_id, method) VALUES('notification.type.topic', 0, 2, 'notification.method.email'); + +# POSTGRES COMMIT # diff --git a/install/startup.php b/install/startup.php new file mode 100644 index 0000000..9a4f9bf --- /dev/null +++ b/install/startup.php @@ -0,0 +1,143 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +/** @ignore */ +if (!defined('IN_PHPBB') || !defined('IN_INSTALL')) +{ + exit; +} + +function phpbb_require_updated($path, $phpbb_root_path, $optional = false) +{ + $new_path = $phpbb_root_path . 'install/update/new/' . $path; + $old_path = $phpbb_root_path . $path; + + if (file_exists($new_path)) + { + require($new_path); + } + else if (!$optional || file_exists($old_path)) + { + require($old_path); + } +} + +function phpbb_include_updated($path, $phpbb_root_path, $optional = false) +{ + $new_path = $phpbb_root_path . 'install/update/new/' . $path; + $old_path = $phpbb_root_path . $path; + + if (file_exists($new_path)) + { + include($new_path); + } + else if (!$optional || file_exists($old_path)) + { + include($old_path); + } +} + +function installer_msg_handler($errno, $msg_text, $errfile, $errline) +{ + global $phpbb_installer_container; + + if (error_reporting() == 0) + { + return true; + } + + switch ($errno) + { + case E_NOTICE: + case E_WARNING: + case E_USER_WARNING: + case E_USER_NOTICE: + $msg = '[phpBB Debug] "' . $msg_text . '" in file ' . $errfile . ' on line ' . $errline; + + if (!empty($phpbb_installer_container)) + { + try + { + /** @var \phpbb\install\helper\iohandler\iohandler_interface $iohandler */ + $iohandler = $phpbb_installer_container->get('installer.helper.iohandler'); + $iohandler->add_warning_message($msg); + } + catch (\phpbb\install\helper\iohandler\exception\iohandler_not_implemented_exception $e) + { + print($msg); + } + } + else + { + print($msg); + } + + return; + break; + case E_USER_ERROR: + $msg = 'General Error:
' . $msg_text . '
in file ' . $errfile . ' on line ' . $errline; + + $backtrace = get_backtrace(); + if ($backtrace) + { + $msg .= '

BACKTRACE
' . $backtrace; + } + + throw new \phpbb\exception\runtime_exception($msg); + break; + case E_DEPRECATED: + return true; + break; + } + + return false; +} + +phpbb_require_updated('includes/startup.' . $phpEx, $phpbb_root_path); +phpbb_require_updated('phpbb/class_loader.' . $phpEx, $phpbb_root_path); + +$phpbb_class_loader_new = new \phpbb\class_loader('phpbb\\', "{$phpbb_root_path}install/update/new/phpbb/", $phpEx); +$phpbb_class_loader_new->register(); +$phpbb_class_loader = new \phpbb\class_loader('phpbb\\', "{$phpbb_root_path}phpbb/", $phpEx); +$phpbb_class_loader->register(); +$phpbb_class_loader = new \phpbb\class_loader('phpbb\\convert\\', "{$phpbb_root_path}install/convert/", $phpEx); +$phpbb_class_loader->register(); +$phpbb_class_loader_ext = new \phpbb\class_loader('\\', "{$phpbb_root_path}ext/", $phpEx); +$phpbb_class_loader_ext->register(); + +// In case $phpbb_adm_relative_path is not set (in case of an update), use the default. +$phpbb_adm_relative_path = (isset($phpbb_adm_relative_path)) ? $phpbb_adm_relative_path : 'adm/'; +$phpbb_admin_path = (defined('PHPBB_ADMIN_PATH')) ? PHPBB_ADMIN_PATH : $phpbb_root_path . $phpbb_adm_relative_path; + +// Include files +phpbb_require_updated('includes/functions.' . $phpEx, $phpbb_root_path); +phpbb_require_updated('includes/functions_content.' . $phpEx, $phpbb_root_path); +phpbb_include_updated('includes/functions_compatibility.' . $phpEx, $phpbb_root_path); +phpbb_require_updated('includes/functions_user.' . $phpEx, $phpbb_root_path); +phpbb_require_updated('includes/utf/utf_tools.' . $phpEx, $phpbb_root_path); + +// Set PHP error handler to ours +set_error_handler(defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'installer_msg_handler'); + +$phpbb_installer_container_builder = new \phpbb\di\container_builder($phpbb_root_path, $phpEx); +$phpbb_installer_container_builder + ->with_environment('installer') + ->without_extensions(); + +$other_config_path = $phpbb_root_path . 'install/update/new/config'; +$config_path = (file_exists($other_config_path . '/installer/config.yml')) ? $other_config_path : $phpbb_root_path . 'config'; + +$phpbb_installer_container = $phpbb_installer_container_builder + ->with_config_path($config_path) + ->with_custom_parameters(array('cache.driver.class' => 'phpbb\cache\driver\file')) + ->get_container(); diff --git a/language/en/acp/attachments.php b/language/en/acp/attachments.php new file mode 100644 index 0000000..86430f4 --- /dev/null +++ b/language/en/acp/attachments.php @@ -0,0 +1,169 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACP_ATTACHMENT_SETTINGS_EXPLAIN' => 'Here you can configure the main settings for attachments and the associated special categories.', + 'ACP_EXTENSION_GROUPS_EXPLAIN' => 'Here you can add, delete, modify or disable your extension groups. Further options include the assignment of a special category to them, changing the download mechanism and defining an upload icon which will be displayed in front of the attachment which belongs to the group.', + 'ACP_MANAGE_EXTENSIONS_EXPLAIN' => 'Here you can manage your allowed extensions. To activate your extensions, please refer to the extension groups management panel. We strongly recommend not to allow scripting extensions (such as php, php3, php4, phtml, pl, cgi, py, rb, asp, aspx, and so forth…).', + 'ACP_ORPHAN_ATTACHMENTS_EXPLAIN' => 'Here you are able to see orphaned files. This happens mostly if users are attaching files but not submitting the post. You are able to delete the files or attach them to existing posts. Attaching to posts requires a valid post ID, you have to determine this ID by yourself. This will assign the already uploaded attachment to the post you entered.', + 'ADD_EXTENSION' => 'Add extension', + 'ADD_EXTENSION_GROUP' => 'Add extension group', + 'ADMIN_UPLOAD_ERROR' => 'Errors while trying to attach file: “%s”.', + 'ALLOWED_FORUMS' => 'Allowed forums', + 'ALLOWED_FORUMS_EXPLAIN' => 'Able to post the assigned extensions at the selected (or all if selected) forums.', + 'ALLOWED_IN_PM_POST' => 'Allowed', + 'ALLOW_ATTACHMENTS' => 'Allow attachments', + 'ALLOW_ALL_FORUMS' => 'Allow all forums', + 'ALLOW_IN_PM' => 'Allowed in private messaging', + 'ALLOW_PM_ATTACHMENTS' => 'Allow attachments in private messages', + 'ALLOW_SELECTED_FORUMS' => 'Only forums selected below', + 'ASSIGNED_EXTENSIONS' => 'Assigned extensions', + 'ASSIGNED_GROUP' => 'Assigned extension group', + 'ATTACH_EXTENSIONS_URL' => 'Extensions', + 'ATTACH_EXT_GROUPS_URL' => 'Extension groups', + 'ATTACH_ID' => 'ID', + 'ATTACH_MAX_FILESIZE' => 'Maximum file size', + 'ATTACH_MAX_FILESIZE_EXPLAIN' => 'Maximum size of each file. If this value is 0, the uploadable filesize is only limited by your PHP configuration.', + 'ATTACH_MAX_PM_FILESIZE' => 'Maximum file size messaging', + 'ATTACH_MAX_PM_FILESIZE_EXPLAIN' => 'Maximum size of each file, with 0 being unlimited, attached to a private message.', + 'ATTACH_ORPHAN_URL' => 'Orphan attachments', + 'ATTACH_POST_ID' => 'Post ID', + 'ATTACH_POST_TYPE' => 'Post type', + 'ATTACH_QUOTA' => 'Total attachment quota', + 'ATTACH_QUOTA_EXPLAIN' => 'Maximum drive space available for attachments for the whole board, with 0 being unlimited.', + 'ATTACH_TO_POST' => 'Attach file to post', + + 'CAT_FLASH_FILES' => 'Flash files', + 'CAT_IMAGES' => 'Images', + 'CHECK_CONTENT' => 'Check attachment files', + 'CHECK_CONTENT_EXPLAIN' => 'Some browsers can be tricked to assume an incorrect mimetype for uploaded files. This option ensures that such files likely to cause this are rejected.', + 'CREATE_GROUP' => 'Create new group', + 'CREATE_THUMBNAIL' => 'Create thumbnail', + 'CREATE_THUMBNAIL_EXPLAIN' => 'Create a thumbnail in all possible situations.', + + 'DEFINE_ALLOWED_IPS' => 'Define allowed IPs/hostnames', + 'DEFINE_DISALLOWED_IPS' => 'Define disallowed IPs/hostnames', + 'DOWNLOAD_ADD_IPS_EXPLAIN' => 'To specify several different IPs or hostnames enter each on a new line. To specify a range of IP addresses separate the start and end with a hyphen (-), to specify a wildcard use “*”.', + 'DOWNLOAD_REMOVE_IPS_EXPLAIN' => 'You can remove (or un-exclude) multiple IP addresses in one go using the appropriate combination of mouse and keyboard for your computer and browser. Excluded IPs have a blue background.', + 'DISPLAY_INLINED' => 'Display images inline', + 'DISPLAY_INLINED_EXPLAIN' => 'If set to No image attachments will show as a link.', + 'DISPLAY_ORDER' => 'Attachment display order', + 'DISPLAY_ORDER_EXPLAIN' => 'Display attachments ordered by time.', + + 'EDIT_EXTENSION_GROUP' => 'Edit extension group', + 'EXCLUDE_ENTERED_IP' => 'Enable this to exclude the entered IP/hostname.', + 'EXCLUDE_FROM_ALLOWED_IP' => 'Exclude IP from allowed IPs/hostnames', + 'EXCLUDE_FROM_DISALLOWED_IP' => 'Exclude IP from disallowed IPs/hostnames', + 'EXTENSIONS_UPDATED' => 'Extensions successfully updated.', + 'EXTENSION_EXIST' => 'The extension %s already exists.', + 'EXTENSION_GROUP' => 'Extension group', + 'EXTENSION_GROUPS' => 'Extension groups', + 'EXTENSION_GROUP_DELETED' => 'Extension group successfully deleted.', + 'EXTENSION_GROUP_EXIST' => 'The extension group %s already exists.', + + 'EXT_GROUP_ARCHIVES' => 'Archives', + 'EXT_GROUP_DOCUMENTS' => 'Documents', + 'EXT_GROUP_DOWNLOADABLE_FILES' => 'Downloadable Files', + 'EXT_GROUP_FLASH_FILES' => 'Flash Files', + 'EXT_GROUP_IMAGES' => 'Images', + 'EXT_GROUP_PLAIN_TEXT' => 'Plain Text', + + 'FILES_GONE' => 'Some of the attachments you selected for deletion do not exist. They may have been already deleted. Attachments that did exist were deleted.', + 'FILES_STATS_WRONG' => 'Your file statistics are likely inaccurate and need to be resynchronised. Actual values: number of attachments = %1$d, total size of attachments = %2$s.
Click %3$shere%4$s to resynchronise them.', + + 'GO_TO_EXTENSIONS' => 'Go to extension management screen', + 'GROUP_NAME' => 'Group name', + + 'IMAGE_LINK_SIZE' => 'Image link dimensions', + 'IMAGE_LINK_SIZE_EXPLAIN' => 'Display image attachment as an inline text link if image is larger than this. To disable this behaviour, set the values to 0px by 0px.', + + 'MAX_ATTACHMENTS' => 'Maximum number of attachments per post', + 'MAX_ATTACHMENTS_PM' => 'Maximum number of attachments per private message', + 'MAX_EXTGROUP_FILESIZE' => 'Maximum file size', + 'MAX_IMAGE_SIZE' => 'Maximum image dimensions', + 'MAX_IMAGE_SIZE_EXPLAIN' => 'Maximum size of image attachments. Set both values to 0px by 0px to disable dimension checking.', + 'MAX_THUMB_WIDTH' => 'Maximum thumbnail width/height in pixel', + 'MAX_THUMB_WIDTH_EXPLAIN' => 'A generated thumbnail will not exceed the width set here.', + 'MIN_THUMB_FILESIZE' => 'Minimum thumbnail file size', + 'MIN_THUMB_FILESIZE_EXPLAIN' => 'Do not create a thumbnail for images smaller than this.', + 'MODE_INLINE' => 'Inline', + 'MODE_PHYSICAL' => 'Physical', + + 'NOT_ALLOWED_IN_PM' => 'Only allowed in posts', + 'NOT_ALLOWED_IN_PM_POST' => 'Not allowed', + 'NOT_ASSIGNED' => 'Not assigned', + 'NO_ATTACHMENTS' => 'No attachments found for this period.', + 'NO_EXT_GROUP' => 'None', + 'NO_EXT_GROUP_NAME' => 'No group name entered', + 'NO_EXT_GROUP_SPECIFIED' => 'No extension group specified.', + 'NO_FILE_CAT' => 'None', + 'NO_IMAGE' => 'No image', + 'NO_UPLOAD_DIR' => 'The upload directory you specified does not exist.', + 'NO_WRITE_UPLOAD' => 'The upload directory you specified cannot be written to. Please alter the permissions to allow the webserver to write to it.', + + 'ONLY_ALLOWED_IN_PM' => 'Only allowed in private messages', + 'ORDER_ALLOW_DENY' => 'Allow', + 'ORDER_DENY_ALLOW' => 'Deny', + + 'REMOVE_ALLOWED_IPS' => 'Remove or un-exclude allowed IPs/hostnames', + 'REMOVE_DISALLOWED_IPS' => 'Remove or un-exclude disallowed IPs/hostnames', + 'RESYNC_FILES_STATS_CONFIRM' => 'Are you sure you wish to resynchronise file statistics?', + + 'SECURE_ALLOW_DENY' => 'Allow/Deny list', + 'SECURE_ALLOW_DENY_EXPLAIN' => 'Change the default behaviour when secure downloads are enabled of the Allow/Deny list to that of a whitelist (Allow) or a blacklist (Deny).', + 'SECURE_DOWNLOADS' => 'Enable secure downloads', + 'SECURE_DOWNLOADS_EXPLAIN' => 'With this option enabled, downloads are limited to IP’s/hostnames you define.', + 'SECURE_DOWNLOAD_NOTICE' => 'Secure Downloads are not enabled. The settings below will be applied after enabling secure downloads.', + 'SECURE_DOWNLOAD_UPDATE_SUCCESS'=> 'The IP list has been updated successfully.', + 'SECURE_EMPTY_REFERRER' => 'Allow empty referrer', + 'SECURE_EMPTY_REFERRER_EXPLAIN' => 'Secure downloads are based on referrers. Do you want to allow downloads for those omitting the referrer?', + 'SETTINGS_CAT_IMAGES' => 'Image category settings', + 'SPECIAL_CATEGORY' => 'Special category', + 'SPECIAL_CATEGORY_EXPLAIN' => 'Special categories differ between the way presented within posts.', + 'SUCCESSFULLY_UPLOADED' => 'Successfully uploaded.', + 'SUCCESS_EXTENSION_GROUP_ADD' => 'Extension group successfully added.', + 'SUCCESS_EXTENSION_GROUP_EDIT' => 'Extension group successfully updated.', + + 'UPLOADING_FILES' => 'Uploading files', + 'UPLOADING_FILE_TO' => 'Uploading file “%1$s” to post number %2$d…', + 'UPLOAD_DENIED_FORUM' => 'You do not have the permission to upload files to forum “%s”.', + 'UPLOAD_DIR' => 'Upload directory', + 'UPLOAD_DIR_EXPLAIN' => 'Storage path for attachments. Please note that if you change this directory while already having uploaded attachments you need to manually copy the files to their new location.', + 'UPLOAD_ICON' => 'Upload icon', + 'UPLOAD_NOT_DIR' => 'The upload location you specified does not appear to be a directory.', +)); diff --git a/language/en/acp/ban.php b/language/en/acp/ban.php new file mode 100644 index 0000000..93d5cf9 --- /dev/null +++ b/language/en/acp/ban.php @@ -0,0 +1,84 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Banning +$lang = array_merge($lang, array( + '1_HOUR' => '1 hour', + '30_MINS' => '30 minutes', + '6_HOURS' => '6 hours', + + 'ACP_BAN_EXPLAIN' => 'Here you can control the banning of users by name, IP or email address. These methods prevent a user reaching any part of the board. You can give a short (maximum 3000 characters) reason for the ban if you wish. This will be displayed in the admin log. The duration of a ban can also be specified. If you want the ban to end on a specific date rather than after a set time period select Until -> for the ban length and enter a date in YYYY-MM-DD format.', + + 'BAN_EXCLUDE' => 'Exclude from banning', + 'BAN_LENGTH' => 'Length of ban', + 'BAN_REASON' => 'Reason for ban', + 'BAN_GIVE_REASON' => 'Reason shown to the banned', + 'BAN_UPDATE_SUCCESSFUL' => 'The banlist has been updated successfully.', + 'BANNED_UNTIL_DATE' => 'until %s', // Example: "until Mon 13.Jul.2009, 14:44" + 'BANNED_UNTIL_DURATION' => '%1$s (until %2$s)', // Example: "7 days (until Tue 14.Jul.2009, 14:44)" + + 'EMAIL_BAN' => 'Ban one or more email addresses', + 'EMAIL_BAN_EXCLUDE_EXPLAIN' => 'Enable this to exclude the entered email address from all current bans.', + 'EMAIL_BAN_EXPLAIN' => 'To specify more than one email address enter each on a new line. To match partial addresses use * as the wildcard, e.g. *@hotmail.com, *@*.domain.tld, etc.', + 'EMAIL_NO_BANNED' => 'No banned email addresses', + 'EMAIL_UNBAN' => 'Un-ban or un-exclude emails', + 'EMAIL_UNBAN_EXPLAIN' => 'You can unban (or un-exclude) multiple email addresses in one go using the appropriate combination of mouse and keyboard for your computer and browser. Excluded email addresses are emphasised.', + + 'IP_BAN' => 'Ban one or more IPs', + 'IP_BAN_EXCLUDE_EXPLAIN' => 'Enable this to exclude the entered IP from all current bans.', + 'IP_BAN_EXPLAIN' => 'To specify several different IPs or hostnames enter each on a new line. To specify a range of IP addresses separate the start and end with a hyphen (-), to specify a wildcard use “*”.', + 'IP_HOSTNAME' => 'IP addresses or hostnames', + 'IP_NO_BANNED' => 'No banned IP addresses', + 'IP_UNBAN' => 'Un-ban or un-exclude IPs', + 'IP_UNBAN_EXPLAIN' => 'You can unban (or un-exclude) multiple IP addresses in one go using the appropriate combination of mouse and keyboard for your computer and browser. Excluded IPs are emphasised.', + + 'LENGTH_BAN_INVALID' => 'The date has to be formatted YYYY-MM-DD.', + + 'OPTIONS_BANNED' => 'Banned', + 'OPTIONS_EXCLUDED' => 'Excluded', + + 'PERMANENT' => 'Permanent', + + 'UNTIL' => 'Until', + 'USER_BAN' => 'Ban one or more users by username', + 'USER_BAN_EXCLUDE_EXPLAIN' => 'Enable this to exclude the entered users from all current bans.', + 'USER_BAN_EXPLAIN' => 'You can ban multiple users in one go by entering each name on a new line. Use the Find a member facility to look up and add one or more users automatically.', + 'USER_NO_BANNED' => 'No banned usernames', + 'USER_UNBAN' => 'Un-ban or un-exclude users by username', + 'USER_UNBAN_EXPLAIN' => 'You can unban (or un-exclude) multiple users in one go using the appropriate combination of mouse and keyboard for your computer and browser. Excluded users are emphasised.', +)); diff --git a/language/en/acp/board.php b/language/en/acp/board.php new file mode 100644 index 0000000..9b45ffa --- /dev/null +++ b/language/en/acp/board.php @@ -0,0 +1,631 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Board Settings +$lang = array_merge($lang, array( + 'ACP_BOARD_SETTINGS_EXPLAIN' => 'Here you can determine the basic operation of your board, give it a fitting name and description, and among other settings adjust the default values for timezone and language.', + 'BOARD_INDEX_TEXT' => 'Board index text', + 'BOARD_INDEX_TEXT_EXPLAIN' => 'This text is displayed as the board index in the board’s breadcrumbs. If not specified, it will default to “Board index”.', + 'BOARD_STYLE' => 'Board style', + 'CUSTOM_DATEFORMAT' => 'Custom…', + 'DEFAULT_DATE_FORMAT' => 'Date format', + 'DEFAULT_DATE_FORMAT_EXPLAIN' => 'The date format is the same as the PHP date function.', + 'DEFAULT_LANGUAGE' => 'Default language', + 'DEFAULT_STYLE' => 'Default style', + 'DEFAULT_STYLE_EXPLAIN' => 'The default style for new users.', + 'DISABLE_BOARD' => 'Disable board', + 'DISABLE_BOARD_EXPLAIN' => 'This will make the board unavailable to users who are neither administrators nor moderators. You can also enter a short (255 character) message to display if you wish.', + 'DISPLAY_LAST_SUBJECT' => 'Display subject of last added post on forum list', + 'DISPLAY_LAST_SUBJECT_EXPLAIN' => 'The subject of the last added post will be displayed in the forum list with a hyperlink to the post. Subjects from password protected forums and forums in which user doesn’t have read access are not shown.', + 'GUEST_STYLE' => 'Guest style', + 'GUEST_STYLE_EXPLAIN' => 'The board style for guests.', + 'OVERRIDE_STYLE' => 'Override user style', + 'OVERRIDE_STYLE_EXPLAIN' => 'Replaces user’s (and guest’s) style with the style as defined under "Default style".', + 'SITE_DESC' => 'Site description', + 'SITE_HOME_TEXT' => 'Main website text', + 'SITE_HOME_TEXT_EXPLAIN' => 'This text will be displayed as a link to your website homepage in the board’s breadcrumbs. If not specified, it will default to “Home”.', + 'SITE_HOME_URL' => 'Main website URL', + 'SITE_HOME_URL_EXPLAIN' => 'If specified, a link to this URL will be prepended to your board’s breadcrumbs and the board logo will link to this URL instead of the forum index. An absolute URL is required, e.g. http://www.phpbb.com.', + 'SITE_NAME' => 'Site name', + 'SYSTEM_TIMEZONE' => 'Guest timezone', + 'SYSTEM_TIMEZONE_EXPLAIN' => 'Timezone to use for displaying times to users who are not logged in (guests, bots). Logged in users set their timezone during registration and can change it in their user control panel.', + 'WARNINGS_EXPIRE' => 'Warning duration', + 'WARNINGS_EXPIRE_EXPLAIN' => 'Number of days that will elapse before a warning will automatically expire from a user’s record. Set this value to 0 to make warnings permanent.', +)); + +// Board Features +$lang = array_merge($lang, array( + 'ACP_BOARD_FEATURES_EXPLAIN' => 'Here you can enable/disable several board features.', + + 'ALLOW_ATTACHMENTS' => 'Allow attachments', + 'ALLOW_BIRTHDAYS' => 'Allow birthdays', + 'ALLOW_BIRTHDAYS_EXPLAIN' => 'Allow birthdays to be entered and age being displayed in profiles. Please note the birthday list within the board index is controlled by a separate load setting.', + 'ALLOW_BOOKMARKS' => 'Allow bookmarking topics', + 'ALLOW_BOOKMARKS_EXPLAIN' => 'User is able to store personal bookmarks.', + 'ALLOW_BBCODE' => 'Allow BBCode', + 'ALLOW_FORUM_NOTIFY' => 'Allow subscribing to forums', + 'ALLOW_NAME_CHANGE' => 'Allow username changes', + 'ALLOW_NO_CENSORS' => 'Allow disabling of word censoring', + 'ALLOW_NO_CENSORS_EXPLAIN' => 'Users can choose to disable the automatic word censoring of posts and private messages.', + 'ALLOW_PM_ATTACHMENTS' => 'Allow attachments in private messages', + 'ALLOW_PM_REPORT' => 'Allow users to report private messages', + 'ALLOW_PM_REPORT_EXPLAIN' => 'If this setting is enabled, users have the option of reporting a private message they have received or sent to the board’s moderators. These private messages will then be visible in the Moderator Control Panel.', + 'ALLOW_QUICK_REPLY' => 'Allow quick reply', + 'ALLOW_QUICK_REPLY_EXPLAIN' => 'This switch allows for the quick reply to be disabled board-wide. When enabled, forum specific settings will be used to determine whether the quick reply is displayed in individual forums.', + 'ALLOW_QUICK_REPLY_BUTTON' => 'Submit and enable quick reply in all forums', + 'ALLOW_SIG' => 'Allow signatures', + 'ALLOW_SIG_BBCODE' => 'Allow BBCode in user signatures', + 'ALLOW_SIG_FLASH' => 'Allow use of [FLASH] BBCode tag in user signatures', + 'ALLOW_SIG_IMG' => 'Allow use of [IMG] BBCode tag in user signatures', + 'ALLOW_SIG_LINKS' => 'Allow use of links in user signatures', + 'ALLOW_SIG_LINKS_EXPLAIN' => 'If disallowed the [URL] BBCode tag and automatic/magic URLs are disabled.', + 'ALLOW_SIG_SMILIES' => 'Allow use of smilies in user signatures', + 'ALLOW_SMILIES' => 'Allow smilies', + 'ALLOW_TOPIC_NOTIFY' => 'Allow subscribing to topics', + 'BOARD_PM' => 'Private messaging', + 'BOARD_PM_EXPLAIN' => 'Enable private messaging for all users.', + 'ALLOW_BOARD_NOTIFICATIONS' => 'Allow board notifications', +)); + +// Avatar Settings +$lang = array_merge($lang, array( + 'ACP_AVATAR_SETTINGS_EXPLAIN' => 'Avatars are generally small, unique images a user can associate with themselves. Depending on the style they are usually displayed below the username when viewing topics. Here you can determine how users can define their avatars. Please note that in order to upload avatars you need to have created the directory you name below and ensure it can be written to by the web server. Please also note that file size limits are only imposed on uploaded avatars, they do not apply to remotely linked images.', + + 'ALLOW_AVATARS' => 'Enable avatars', + 'ALLOW_AVATARS_EXPLAIN' => 'Allow general usage of avatars;
If you disable avatars in general or avatars of a certain mode, the disabled avatars will no longer be shown on the board, but users will still be able to download their own avatars in the User Control Panel.', + 'ALLOW_GRAVATAR' => 'Enable gravatar avatars', + 'ALLOW_LOCAL' => 'Enable gallery avatars', + 'ALLOW_REMOTE' => 'Enable remote avatars', + 'ALLOW_REMOTE_EXPLAIN' => 'Avatars linked to from another website.
Warning: Enabling this feature might allow users to check for the existence of files and services that are only accessible on the local network.', + 'ALLOW_REMOTE_UPLOAD' => 'Enable remote avatar uploading', + 'ALLOW_REMOTE_UPLOAD_EXPLAIN' => 'Allow uploading of avatars from another website.
Warning: Enabling this feature might allow users to check for the existence of files and services that are only accessible on the local network.', + 'ALLOW_UPLOAD' => 'Enable avatar uploading', + 'AVATAR_GALLERY_PATH' => 'Avatar gallery path', + 'AVATAR_GALLERY_PATH_EXPLAIN' => 'Path under your phpBB root directory for pre-loaded images, e.g. images/avatars/gallery.
Double dots like ../ will be stripped from the path for security reasons.', + 'AVATAR_STORAGE_PATH' => 'Avatar storage path', + 'AVATAR_STORAGE_PATH_EXPLAIN' => 'Path under your phpBB root directory, e.g. images/avatars/upload.
Avatar uploading will not be available if this path is not writable.
Double dots like ../ will be stripped from the path for security reasons.', + 'MAX_AVATAR_SIZE' => 'Maximum avatar dimensions', + 'MAX_AVATAR_SIZE_EXPLAIN' => 'Width x Height in pixels.', + 'MAX_FILESIZE' => 'Maximum avatar file size', + 'MAX_FILESIZE_EXPLAIN' => 'For uploaded avatar files. If this value is 0, the uploaded filesize is only limited by your PHP configuration.', + 'MIN_AVATAR_SIZE' => 'Minimum avatar dimensions', + 'MIN_AVATAR_SIZE_EXPLAIN' => 'Width x Height in pixels.', +)); + +// Message Settings +$lang = array_merge($lang, array( + 'ACP_MESSAGE_SETTINGS_EXPLAIN' => 'Here you can set all default settings for private messaging.', + + 'ALLOW_BBCODE_PM' => 'Allow BBCode in private messages', + 'ALLOW_FLASH_PM' => 'Allow use of [FLASH] BBCode tag', + 'ALLOW_FLASH_PM_EXPLAIN' => 'Note that the ability to use flash in private messages, if enabled here, also depends on the permissions.', + 'ALLOW_FORWARD_PM' => 'Allow forwarding of private messages', + 'ALLOW_IMG_PM' => 'Allow use of [IMG] BBCode tag', + 'ALLOW_MASS_PM' => 'Allow sending of private messages to multiple users and groups', + 'ALLOW_MASS_PM_EXPLAIN' => 'Sending to groups can be adjusted per group within the group settings page.', + 'ALLOW_PRINT_PM' => 'Allow print view in private messaging', + 'ALLOW_QUOTE_PM' => 'Allow quotes in private messages', + 'ALLOW_SIG_PM' => 'Allow signature in private messages', + 'ALLOW_SMILIES_PM' => 'Allow smilies in private messages', + 'BOXES_LIMIT' => 'Maximum private messages per box', + 'BOXES_LIMIT_EXPLAIN' => 'Users may receive no more than this many messages in each of their private message boxes. Set this value to 0 to allow unlimited messages.', + 'BOXES_MAX' => 'Maximum private message folders', + 'BOXES_MAX_EXPLAIN' => 'By default users may create this many personal folders for private messages.', + 'ENABLE_PM_ICONS' => 'Enable use of topic icons in private messages', + 'FULL_FOLDER_ACTION' => 'Full folder default action', + 'FULL_FOLDER_ACTION_EXPLAIN'=> 'Default action to take if a user’s folder is full assuming the user’s folder action, if set at all, is not applicable. The only exception is for the “Sent messages” folder where the default action is always to delete old messages.', + 'HOLD_NEW_MESSAGES' => 'Hold new messages', + 'PM_EDIT_TIME' => 'Limit editing time', + 'PM_EDIT_TIME_EXPLAIN' => 'Limits the time available to edit a private message not already delivered. Setting the value to 0 disables this behaviour.', + 'PM_MAX_RECIPIENTS' => 'Maximum number of allowed recipients', + 'PM_MAX_RECIPIENTS_EXPLAIN' => 'The maximum number of allowed recipients in a private message. If 0 is entered, an unlimited number is allowed. This setting can be adjusted for every group within the group settings page.', +)); + +// Post Settings +$lang = array_merge($lang, array( + 'ACP_POST_SETTINGS_EXPLAIN' => 'Here you can set all default settings for posting.', + 'ALLOW_POST_LINKS' => 'Allow links in posts/private messages', + 'ALLOW_POST_LINKS_EXPLAIN' => 'If disallowed the [URL] BBCode tag and automatic/magic URLs are disabled.', + 'ALLOWED_SCHEMES_LINKS' => 'Allowed schemes in links', + 'ALLOWED_SCHEMES_LINKS_EXPLAIN' => 'Users can only post schemeless URLs or one of the comma-separated list of allowed schemes.', + 'ALLOW_POST_FLASH' => 'Allow use of [FLASH] BBCode tag in posts', + 'ALLOW_POST_FLASH_EXPLAIN' => 'If disallowed the [FLASH] BBCode tag is disabled in posts. Otherwise the permission system controls which users can use the [FLASH] BBCode tag.', + + 'BUMP_INTERVAL' => 'Bump interval', + 'BUMP_INTERVAL_EXPLAIN' => 'Number of minutes, hours or days between the last post to a topic and the ability to bump that topic. Setting the value to 0 disables bumping entirely.', + 'CHAR_LIMIT' => 'Maximum characters per post/message', + 'CHAR_LIMIT_EXPLAIN' => 'The number of characters allowed within a post/private message. Set to 0 for unlimited characters.', + 'DELETE_TIME' => 'Limit deleting time', + 'DELETE_TIME_EXPLAIN' => 'Limits the time available to delete a new post. Setting the value to 0 disables this behaviour.', + 'DISPLAY_LAST_EDITED' => 'Display last edited time information', + 'DISPLAY_LAST_EDITED_EXPLAIN' => 'Choose if the last edited by information to be displayed on posts.', + 'EDIT_TIME' => 'Limit editing time', + 'EDIT_TIME_EXPLAIN' => 'Limits the time available to edit a new post. Setting the value to 0 disables this behaviour.', + 'FLOOD_INTERVAL' => 'Flood interval', + 'FLOOD_INTERVAL_EXPLAIN' => 'Number of seconds a user must wait between posting new messages. To enable users to ignore this alter their permissions.', + 'HOT_THRESHOLD' => 'Popular topic threshold', + 'HOT_THRESHOLD_EXPLAIN' => 'Posts per topic threshold required for the popular topic annotation. Set to 0 to disable popular topics.', + 'MAX_POLL_OPTIONS' => 'Maximum number of poll options', + 'MAX_POST_FONT_SIZE' => 'Maximum font size per post', + 'MAX_POST_FONT_SIZE_EXPLAIN' => 'Maximum font size allowed in a post. Set to 0 for unlimited font size.', + 'MAX_POST_IMG_HEIGHT' => 'Maximum image height per post', + 'MAX_POST_IMG_HEIGHT_EXPLAIN' => 'Maximum height of an image/flash file in postings. Set to 0 for unlimited size.', + 'MAX_POST_IMG_WIDTH' => 'Maximum image width per post', + 'MAX_POST_IMG_WIDTH_EXPLAIN' => 'Maximum width of an image/flash file in postings. Set to 0 for unlimited size.', + 'MAX_POST_URLS' => 'Maximum links per post', + 'MAX_POST_URLS_EXPLAIN' => 'Maximum number of URLs in a post. Set to 0 for unlimited links.', + 'MIN_CHAR_LIMIT' => 'Minimum characters per post/message', + 'MIN_CHAR_LIMIT_EXPLAIN' => 'The minimum number of characters the user need to enter within a post/private message. The minimum for this setting is 1.', + 'POSTING' => 'Posting', + 'POSTS_PER_PAGE' => 'Posts per page', + 'QUOTE_DEPTH_LIMIT' => 'Maximum nesting depth for quotes', + 'QUOTE_DEPTH_LIMIT_EXPLAIN' => 'Maximum quote nesting depth in a post. Set to 0 for unlimited depth.', + 'SMILIES_LIMIT' => 'Maximum smilies per post', + 'SMILIES_LIMIT_EXPLAIN' => 'Maximum number of smilies in a post. Set to 0 for unlimited smilies.', + 'SMILIES_PER_PAGE' => 'Smilies per page', + 'TOPICS_PER_PAGE' => 'Topics per page', +)); + +// Signature Settings +$lang = array_merge($lang, array( + 'ACP_SIGNATURE_SETTINGS_EXPLAIN' => 'Here you can set all default settings for signatures.', + + 'MAX_SIG_FONT_SIZE' => 'Maximum signature font size', + 'MAX_SIG_FONT_SIZE_EXPLAIN' => 'Maximum font size allowed in user signatures. Set to 0 for unlimited size.', + 'MAX_SIG_IMG_HEIGHT' => 'Maximum signature image height', + 'MAX_SIG_IMG_HEIGHT_EXPLAIN' => 'Maximum height of an image/flash file in user signatures. Set to 0 for unlimited height.', + 'MAX_SIG_IMG_WIDTH' => 'Maximum signature image width', + 'MAX_SIG_IMG_WIDTH_EXPLAIN' => 'Maximum width of an image/flash file in user signatures. Set to 0 for unlimited width.', + 'MAX_SIG_LENGTH' => 'Maximum signature length', + 'MAX_SIG_LENGTH_EXPLAIN' => 'Maximum number of characters in user signatures.', + 'MAX_SIG_SMILIES' => 'Maximum smilies per signature', + 'MAX_SIG_SMILIES_EXPLAIN' => 'Maximum smilies allowed in user signatures. Set to 0 for unlimited smilies.', + 'MAX_SIG_URLS' => 'Maximum signature links', + 'MAX_SIG_URLS_EXPLAIN' => 'Maximum number of links in user signatures. Set to 0 for unlimited links.', +)); + +// Registration Settings +$lang = array_merge($lang, array( + 'ACP_REGISTER_SETTINGS_EXPLAIN' => 'Here you are able to define registration and profile related settings.', + + 'ACC_ACTIVATION' => 'Account activation', + 'ACC_ACTIVATION_EXPLAIN' => 'This determines whether users have immediate access to the board or if confirmation is required. You can also completely disable new registrations. “Board-wide email” must be enabled in order to use user or admin activation.', + 'ACC_ACTIVATION_WARNING' => 'Please note that the currently selected activation method requires emails to be enabled, otherwise registration will be disabled. We recommend to either select a different activation method or reenable emails.', + 'NEW_MEMBER_POST_LIMIT' => 'New member post limit', + 'NEW_MEMBER_POST_LIMIT_EXPLAIN' => 'New members are within the Newly Registered Users group until they reach this number of posts. You can use this group to keep them from using the PM system or to review their posts. A value of 0 disables this feature.', + 'NEW_MEMBER_GROUP_DEFAULT' => 'Set Newly Registered Users group to default', + 'NEW_MEMBER_GROUP_DEFAULT_EXPLAIN' => 'If set to yes, and a new member post limit is specified, newly registered users will not only be put into the Newly Registered Users group, but this group will also be their default one. This may come in handy if you want to assign a group default rank and/or avatar the user then inherits.', + + 'ACC_ADMIN' => 'By admin', + 'ACC_DISABLE' => 'Disable registration', + 'ACC_NONE' => 'No activation (immediate access)', + 'ACC_USER' => 'By user (email verification)', +// 'ACC_USER_ADMIN' => 'User + Admin', + 'ALLOW_EMAIL_REUSE' => 'Allow email address re-use', + 'ALLOW_EMAIL_REUSE_EXPLAIN' => 'Different users can register with the same email address.', + 'COPPA' => 'COPPA', + 'COPPA_FAX' => 'COPPA fax number', + 'COPPA_MAIL' => 'COPPA mailing address', + 'COPPA_MAIL_EXPLAIN' => 'This is the mailing address where parents will send COPPA registration forms.', + 'ENABLE_COPPA' => 'Enable COPPA', + 'ENABLE_COPPA_EXPLAIN' => 'This requires users to declare whether they are 13 or over for compliance with the U.S. COPPA. If this is disabled the COPPA specific groups will no longer be displayed.', + 'MAX_CHARS' => 'Max', + 'MIN_CHARS' => 'Min', + 'NO_AUTH_PLUGIN' => 'No suitable auth plugin found.', + 'PASSWORD_LENGTH' => 'Password length', + 'PASSWORD_LENGTH_EXPLAIN' => 'Minimum and maximum number of characters in passwords.', + 'REG_LIMIT' => 'Registration attempts', + 'REG_LIMIT_EXPLAIN' => 'Number of attempts users can make at solving the anti-spambot task before being locked out of that session.', + 'USERNAME_ALPHA_ONLY' => 'Alphanumeric only', + 'USERNAME_ALPHA_SPACERS' => 'Alphanumeric and spacers', + 'USERNAME_ASCII' => 'ASCII (no international unicode)', + 'USERNAME_LETTER_NUM' => 'Any letter and number', + 'USERNAME_LETTER_NUM_SPACERS' => 'Any letter, number, and spacer', + 'USERNAME_CHARS' => 'Limit username chars', + 'USERNAME_CHARS_ANY' => 'Any character', + 'USERNAME_CHARS_EXPLAIN' => 'Restrict type of characters that may be used in usernames, spacers are: space, -, +, _, [ and ].', + 'USERNAME_LENGTH' => 'Username length', + 'USERNAME_LENGTH_EXPLAIN' => 'Minimum and maximum number of characters in usernames.', +)); + +// Feeds +$lang = array_merge($lang, array( + 'ACP_FEED_MANAGEMENT' => 'General syndication feeds settings', + 'ACP_FEED_MANAGEMENT_EXPLAIN' => 'This module makes available various ATOM feeds, parsing any BBCode in posts to make them readable in external feeds.', + + 'ACP_FEED_GENERAL' => 'General feed settings', + 'ACP_FEED_POST_BASED' => 'Post-based feed settings', + 'ACP_FEED_TOPIC_BASED' => 'Topic-based feed settings', + 'ACP_FEED_SETTINGS_OTHER' => 'Other feeds and settings', + + 'ACP_FEED_ENABLE' => 'Enable feeds', + 'ACP_FEED_ENABLE_EXPLAIN' => 'Turns on or off ATOM feeds for the entire board.
Disabling this switches off all feeds, no matter how the options below are set.', + 'ACP_FEED_LIMIT' => 'Number of items', + 'ACP_FEED_LIMIT_EXPLAIN' => 'The maximum number of feed items to display.', + + 'ACP_FEED_OVERALL' => 'Enable board-wide feed', + 'ACP_FEED_OVERALL_EXPLAIN' => 'Board-wide new posts.', + 'ACP_FEED_FORUM' => 'Enable per-forum feeds', + 'ACP_FEED_FORUM_EXPLAIN' => 'Single forum and subforums new posts.', + 'ACP_FEED_TOPIC' => 'Enable per-topic feeds', + 'ACP_FEED_TOPIC_EXPLAIN' => 'Single topics new posts.', + + 'ACP_FEED_TOPICS_NEW' => 'Enable new topics feed', + 'ACP_FEED_TOPICS_NEW_EXPLAIN' => 'Enables the “New Topics” feed, which displays the last created topics including the first post.', + 'ACP_FEED_TOPICS_ACTIVE' => 'Enable active topics feed', + 'ACP_FEED_TOPICS_ACTIVE_EXPLAIN' => 'Enables the “Active Topics” feed, which displays the last active topics including the last post.', + 'ACP_FEED_NEWS' => 'News feed', + 'ACP_FEED_NEWS_EXPLAIN' => 'Pull the first post from these forums. Select no forums to disable news feed.
Select multiple forums by holding CTRL and clicking.', + + 'ACP_FEED_OVERALL_FORUMS' => 'Enable forums feed', + 'ACP_FEED_OVERALL_FORUMS_EXPLAIN' => 'Enables the “All forums” feed, which displays a list of forums.', + + 'ACP_FEED_HTTP_AUTH' => 'Allow HTTP Authentication', + 'ACP_FEED_HTTP_AUTH_EXPLAIN' => 'Enables HTTP authentication, which allows users to receive content that is hidden to guest users by adding the auth=http parameter to the feed URL. Please note that some PHP setups require additional changes to the .htaccess file. Instructions can be found in that file.', + 'ACP_FEED_ITEM_STATISTICS' => 'Item statistics', + 'ACP_FEED_ITEM_STATISTICS_EXPLAIN' => 'Display individual statistics underneath feed items
(e.g. posted by, date and time, replies, views)', + 'ACP_FEED_EXCLUDE_ID' => 'Exclude these forums', + 'ACP_FEED_EXCLUDE_ID_EXPLAIN' => 'Content from these will be not included in feeds. Select no forum to pull data from all forums.
Select/Deselect multiple forums by holding CTRL and clicking.', +)); + +// Visual Confirmation Settings +$lang = array_merge($lang, array( + 'ACP_VC_SETTINGS_EXPLAIN' => 'Here you can select and configure plugins, which are designed to block automated form submissions by spambots. These plugins typically work by challenging the user with a CAPTCHA, a test which is designed to be difficult for computers to solve.', + 'ACP_VC_EXT_GET_MORE' => 'For additional (and possibly better) anti-spam plugins, visit the phpBB.com Extensions Database. For more information on preventing spam on your board, visit the phpBB.com Knowledge Base.', + 'AVAILABLE_CAPTCHAS' => 'Available plugins', + 'CAPTCHA_UNAVAILABLE' => 'The plugin cannot be selected as its requirements are not met.', + 'CAPTCHA_GD' => 'GD image', + 'CAPTCHA_GD_3D' => 'GD 3D image', + 'CAPTCHA_GD_FOREGROUND_NOISE' => 'Foreground noise', + 'CAPTCHA_GD_EXPLAIN' => 'Uses GD to make a more advanced anti-spambot image.', + 'CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN' => 'Use foreground noise to make the image harder to read.', + 'CAPTCHA_GD_X_GRID' => 'Background noise x-axis', + 'CAPTCHA_GD_X_GRID_EXPLAIN' => 'Use lower settings of this to make the image harder to read. 0 will disable x-axis background noise.', + 'CAPTCHA_GD_Y_GRID' => 'Background noise y-axis', + 'CAPTCHA_GD_Y_GRID_EXPLAIN' => 'Use lower settings of this to make the image harder to read. 0 will disable y-axis background noise.', + 'CAPTCHA_GD_WAVE' => 'Wave distortion', + 'CAPTCHA_GD_WAVE_EXPLAIN' => 'This applies a wave distortion to the image.', + 'CAPTCHA_GD_3D_NOISE' => 'Add 3D-noise objects', + 'CAPTCHA_GD_3D_NOISE_EXPLAIN' => 'This adds additional objects to the image, over the letters.', + 'CAPTCHA_GD_FONTS' => 'Use different fonts', + 'CAPTCHA_GD_FONTS_EXPLAIN' => 'This setting controls how many different letter shapes are used. You can just use the default shapes or introduce altered letters. Adding lowercase letters is also possible.', + 'CAPTCHA_FONT_DEFAULT' => 'Default', + 'CAPTCHA_FONT_NEW' => 'New Shapes', + 'CAPTCHA_FONT_LOWER' => 'Also use lowercase', + 'CAPTCHA_NO_GD' => 'Simple image', + 'CAPTCHA_PREVIEW_MSG' => 'Your changes have not been saved, this is just a preview.', + 'CAPTCHA_PREVIEW_EXPLAIN' => 'The plugin as it would look like using the current selection.', + + 'CAPTCHA_SELECT' => 'Installed plugins', + 'CAPTCHA_SELECT_EXPLAIN' => 'The dropdown holds the plugins recognised by the board. Grey entries are not available right now and might need configuration prior to use.', + 'CAPTCHA_CONFIGURE' => 'Configure plugins', + 'CAPTCHA_CONFIGURE_EXPLAIN' => 'Change the settings for the selected plugin.', + 'CONFIGURE' => 'Configure', + 'CAPTCHA_NO_OPTIONS' => 'This plugin has no configuration options.', + + 'VISUAL_CONFIRM_POST' => 'Enable spambot countermeasures for guest postings', + 'VISUAL_CONFIRM_POST_EXPLAIN' => 'Requires guest users to pass the anti-spambot task to help prevent automated postings.', + 'VISUAL_CONFIRM_REG' => 'Enable spambot countermeasures for registrations', + 'VISUAL_CONFIRM_REG_EXPLAIN' => 'Requires new users to pass the anti-spambot task to help prevent automated registrations.', + 'VISUAL_CONFIRM_REFRESH' => 'Allow users to refresh the anti-spambot task', + 'VISUAL_CONFIRM_REFRESH_EXPLAIN' => 'Allows users to request a new anti-spambot task if they are unable to solve the current task during registration. Some plugins might not support this option.', +)); + +// Cookie Settings +$lang = array_merge($lang, array( + 'ACP_COOKIE_SETTINGS_EXPLAIN' => 'These details define the data used to send cookies to your users browsers. In most cases the default values for the cookie settings should be sufficient. If you do need to change any do so with care, incorrect settings can prevent users logging in. If you have problems with users staying logging in to your board, visit the phpBB.com Knowledge Base - Fixing incorrect cookie settings.', + + 'COOKIE_DOMAIN' => 'Cookie domain', + 'COOKIE_DOMAIN_EXPLAIN' => 'In most cases the cookie domain is optional. Leave it blank if you are unsure.

In the case where you have a board integrated with other software or have multiple domains, then to determine the cookie domain you need to do the following. If you have something like example.com and forums.example.com, or perhaps forums.example.com and blog.example.com. Remove the subdomains until you find the common domain, example.com. Now add a dot in front of the common domain and you would enter .example.com (note the dot at the beginning).', + 'COOKIE_NAME' => 'Cookie name', + 'COOKIE_NAME_EXPLAIN' => 'This can be anything what you want, make it original. Whenever the cookie settings are changed the name of the cookie should be changed.', + 'COOKIE_NOTICE' => 'Cookie notice', + 'COOKIE_NOTICE_EXPLAIN' => 'If enabled a cookie notice will be displayed to users when visiting your board. This might be required by law depending on the content of your board and enabled extensions.', + 'COOKIE_PATH' => 'Cookie path', + 'COOKIE_PATH_EXPLAIN' => 'This will usually be the same as your script path or simply a slash to make the cookie accessible across the site domain.', + 'COOKIE_SECURE' => 'Cookie secure', + 'COOKIE_SECURE_EXPLAIN' => 'If your server is running via SSL set this to enabled else leave as disabled. Having this enabled and not running via SSL will result in server errors during redirects.', + 'ONLINE_LENGTH' => 'View online time span', + 'ONLINE_LENGTH_EXPLAIN' => 'Number of minutes after which inactive users will not appear in “Who is online” listings. The higher this value the greater is the processing required to generate the listing.', + 'SESSION_LENGTH' => 'Session length', + 'SESSION_LENGTH_EXPLAIN' => 'Sessions will expire after this time, in seconds.', +)); + +// Contact Settings +$lang = array_merge($lang, array( + 'ACP_CONTACT_SETTINGS_EXPLAIN' => 'Here you can enable and disable the contact page and also add a text that is displayed on the page.', + + 'CONTACT_US_ENABLE' => 'Enable contact page', + 'CONTACT_US_ENABLE_EXPLAIN' => 'This page allows users to send emails to board administrators. Please note that board-wide emails option must be enabled as well. You can find this option in General > Client Communication > Email settings.', + + 'CONTACT_US_INFO' => 'Contact information', + 'CONTACT_US_INFO_EXPLAIN' => 'The message is displayed on the contact page', + 'CONTACT_US_INFO_PREVIEW' => 'Contact page information - Preview', + 'CONTACT_US_INFO_UPDATED' => 'Contact page information has been updated.', +)); + +// Load Settings +$lang = array_merge($lang, array( + 'ACP_LOAD_SETTINGS_EXPLAIN' => 'Here you can enable and disable certain board functions to reduce the amount of processing required. On most servers there is no need to disable any functions. However on certain systems or in shared hosting environments it may be beneficial to disable capabilities you do not really need. You can also specify limits for system load and active sessions beyond which the board will go offline.', + + 'ALLOW_CDN' => 'Allow usage of third party content delivery networks', + 'ALLOW_CDN_EXPLAIN' => 'If this setting is enabled, some files will be served from external third party servers instead of your server. This reduces the network bandwidth required by your server, but may present a privacy issue for some board administrators. In a default phpBB installation, this includes loading “jQuery” and the font “Open Sans” from Google’s content delivery network.', + 'ALLOW_LIVE_SEARCHES' => 'Allow live searches', + 'ALLOW_LIVE_SEARCHES_EXPLAIN' => 'If this setting is enabled, users are provided with keyword suggestions as they type in certain fields throughout the board.', + 'CUSTOM_PROFILE_FIELDS' => 'Custom profile fields', + 'LIMIT_LOAD' => 'Limit system load', + 'LIMIT_LOAD_EXPLAIN' => 'If the system’s 1-minute load average exceeds this value the board will automatically go offline. A value of 1.0 equals ~100% utilisation of one processor. This only functions on UNIX based servers and where this information is accessible. The value here resets itself to 0 if phpBB was unable to get the load limit.', + 'LIMIT_SESSIONS' => 'Limit sessions', + 'LIMIT_SESSIONS_EXPLAIN' => 'If the number of sessions exceeds this value within a one minute period the board will go offline. Set to 0 for unlimited sessions.', + 'LOAD_CPF_MEMBERLIST' => 'Allow styles to display custom profile fields in memberlist', + 'LOAD_CPF_PM' => 'Display custom profile fields in private messages', + 'LOAD_CPF_VIEWPROFILE' => 'Display custom profile fields in user profiles', + 'LOAD_CPF_VIEWTOPIC' => 'Display custom profile fields on topic pages', + 'LOAD_USER_ACTIVITY' => 'Show user’s activity', + 'LOAD_USER_ACTIVITY_EXPLAIN' => 'Displays active topic/forum in user profiles and user control panel. It is recommended to disable this on boards with more than one million posts.', + 'LOAD_USER_ACTIVITY_LIMIT' => 'User’s activity post limit', + 'LOAD_USER_ACTIVITY_LIMIT_EXPLAIN' => 'The active topic/forum won’t be shown for users having more than this number of posts. Set to 0 to disable the limit.', + 'READ_NOTIFICATION_EXPIRE_DAYS' => 'Read Notification Expiration', + 'READ_NOTIFICATION_EXPIRE_DAYS_EXPLAIN' => 'Number of days that will elapse before a read notification will automatically be deleted. Set this value to 0 to make notifications permanent.', + 'RECOMPILE_STYLES' => 'Recompile stale style components', + 'RECOMPILE_STYLES_EXPLAIN' => 'Check for updated style components on filesystem and recompile.', + 'YES_ACCURATE_PM_BUTTON' => 'Enable permission specific PM button in topic pages', + 'YES_ACCURATE_PM_BUTTON_EXPLAIN' => 'If this setting is enabled, only post profiles of users who are permitted to read private messages will have a private message button.', + 'YES_ANON_READ_MARKING' => 'Enable topic marking for guests', + 'YES_ANON_READ_MARKING_EXPLAIN' => 'Stores read/unread status information for guests. If disabled, posts are always marked read for guests.', + 'YES_BIRTHDAYS' => 'Enable birthday listing', + 'YES_BIRTHDAYS_EXPLAIN' => 'If disabled the birthday listing is no longer displayed. To let this setting take effect the birthday feature needs to be enabled too.', + 'YES_JUMPBOX' => 'Enable display of jumpbox', + 'YES_MODERATORS' => 'Enable display of moderators', + 'YES_ONLINE' => 'Enable online user listings', + 'YES_ONLINE_EXPLAIN' => 'Display online user information on index, forum and topic pages.', + 'YES_ONLINE_GUESTS' => 'Enable online guest listings in viewonline', + 'YES_ONLINE_GUESTS_EXPLAIN' => 'Allow display of guest user information in viewonline.', + 'YES_ONLINE_TRACK' => 'Enable display of user online/offline information', + 'YES_ONLINE_TRACK_EXPLAIN' => 'Display online information for user in profiles and topic pages.', + 'YES_POST_MARKING' => 'Enable dotted topics', + 'YES_POST_MARKING_EXPLAIN' => 'Indicates whether user has posted to a topic.', + 'YES_READ_MARKING' => 'Enable server-side topic marking', + 'YES_READ_MARKING_EXPLAIN' => 'Stores read/unread status information in the database rather than a cookie.', + 'YES_UNREAD_SEARCH' => 'Enable search for unread posts', +)); + +// Auth settings +$lang = array_merge($lang, array( + 'ACP_AUTH_SETTINGS_EXPLAIN' => 'phpBB supports authentication plug-ins, or modules. These allow you determine how users are authenticated when they log into the board. By default four plug-ins are provided: DB, LDAP, Apache, and OAuth. Not all methods require additional information so only fill out fields if they are relevant to the selected method.', + + 'AUTH_METHOD' => 'Select an authentication method', + + 'AUTH_PROVIDER_OAUTH_ERROR_ELEMENT_MISSING' => 'Both the key and secret of each enabled OAuth service provider must be provided. Only one was provided for an OAuth service provider.', + 'AUTH_PROVIDER_OAUTH_EXPLAIN' => 'Each OAuth provider requires a unique secret and key in order to authenticate with the external server. These should be supplied by the OAuth service when you register your website with them and should be entered exactly as provided to you.
Any service that does not have both a key and a secret entered here will not be available for use by the forum users. Also note, that user can still register and login using the DB authentication plug-in.', + 'AUTH_PROVIDER_OAUTH_KEY' => 'Key', + 'AUTH_PROVIDER_OAUTH_TITLE' => 'OAuth', + 'AUTH_PROVIDER_OAUTH_SECRET' => 'Secret', + + 'APACHE_SETUP_BEFORE_USE' => 'You have to setup apache authentication before you switch phpBB to this authentication method. Keep in mind that the username you use for apache authentication has to be the same as your phpBB username. Apache authentication can only be used with mod_php (not with a CGI version) and safe_mode disabled.', + + 'LDAP' => 'LDAP', + 'LDAP_DN' => 'LDAP base dn', + 'LDAP_DN_EXPLAIN' => 'This is the Distinguished Name, locating the user information, e.g. o=My Company,c=US.', + 'LDAP_EMAIL' => 'LDAP email attribute', + 'LDAP_EMAIL_EXPLAIN' => 'Set this to the name of your user entry email attribute (if one exists) in order to automatically set the email address for new users. Leaving this empty results in empty email address for users who log in for the first time.', + 'LDAP_INCORRECT_USER_PASSWORD' => 'Binding to LDAP server failed with specified user/password.', + 'LDAP_NO_EMAIL' => 'The specified email attribute does not exist.', + 'LDAP_NO_IDENTITY' => 'Could not find a login identity for %s.', + 'LDAP_PASSWORD' => 'LDAP password', + 'LDAP_PASSWORD_EXPLAIN' => 'Leave blank to use anonymous binding, otherwise fill in the password for the above user. Required for Active Directory Servers.
Warning: This password will be stored as plain text in the database, visible to everybody who can access your database or who can view this configuration page.', + 'LDAP_PORT' => 'LDAP server port', + 'LDAP_PORT_EXPLAIN' => 'Optionally you can specify a port which should be used to connect to the LDAP server instead of the default port 389.', + 'LDAP_SERVER' => 'LDAP server name', + 'LDAP_SERVER_EXPLAIN' => 'If using LDAP this is the hostname or IP address of the LDAP server. Alternatively you can specify an URL like ldap://hostname:port/', + 'LDAP_UID' => 'LDAP uid', + 'LDAP_UID_EXPLAIN' => 'This is the key under which to search for a given login identity, e.g. uid, sn, etc.', + 'LDAP_USER' => 'LDAP user dn', + 'LDAP_USER_EXPLAIN' => 'Leave blank to use anonymous binding. If filled in phpBB uses the specified distinguished name on login attempts to find the correct user, e.g. uid=Username,ou=MyUnit,o=MyCompany,c=US. Required for Active Directory Servers.', + 'LDAP_USER_FILTER' => 'LDAP user filter', + 'LDAP_USER_FILTER_EXPLAIN' => 'Optionally you can further limit the searched objects with additional filters. For example objectClass=posixGroup would result in the use of (&(uid=$username)(objectClass=posixGroup))', +)); + +// Server Settings +$lang = array_merge($lang, array( + 'ACP_SERVER_SETTINGS_EXPLAIN' => 'Here you define server and domain dependent settings. Please ensure the data you enter is accurate, errors will result in emails containing incorrect information. When entering the domain name remember it does include http:// or other protocol term. Only alter the port number if you know your server uses a different value, port 80 is correct in most cases.', + + 'ENABLE_GZIP' => 'Enable GZip compression', + 'ENABLE_GZIP_EXPLAIN' => 'Generated content will be compressed prior to sending it to the user. This can reduce network traffic but will also increase CPU usage on both server and client side. Requires zlib PHP extension to be loaded.', + 'FORCE_SERVER_VARS' => 'Force server URL settings', + 'FORCE_SERVER_VARS_EXPLAIN' => 'If set to yes the server settings defined here will be used in favour of the automatically determined values.', + 'ICONS_PATH' => 'Post icons storage path', + 'ICONS_PATH_EXPLAIN' => 'Path under your phpBB root directory, e.g. images/icons.', + 'MOD_REWRITE_ENABLE' => 'Enable URL Rewriting', + 'MOD_REWRITE_ENABLE_EXPLAIN' => 'When enabled, URLs containing ’app.php’ will be rewritten to remove the filename (i.e. app.php/foo will become /foo). Apache server’s mod_rewrite module is required for this functionality to work; if this option is enabled without mod_rewrite support, URLs on your board may be broken.', + 'MOD_REWRITE_DISABLED' => 'The mod_rewrite module on your Apache web server is disabled. Enable the module or contact your web hosting provider if you wish to enable this feature.', + 'MOD_REWRITE_INFORMATION_UNAVAILABLE' => 'We are unable to determine whether or not this server supports URL rewriting. This setting may be enabled but if URL rewriting is not available, paths generated by this board (such as for use in links) may be broken. Contact your web hosting provider if you are unsure whether or not you can safely enable this feature.', + 'PATH_SETTINGS' => 'Path settings', + 'RANKS_PATH' => 'Rank image storage path', + 'RANKS_PATH_EXPLAIN' => 'Path under your phpBB root directory, e.g. images/ranks.', + 'SCRIPT_PATH' => 'Script path', + 'SCRIPT_PATH_EXPLAIN' => 'The path where phpBB is located relative to the domain name, e.g. /phpBB3.', + 'SERVER_NAME' => 'Domain name', + 'SERVER_NAME_EXPLAIN' => 'The domain name this board runs from (for example: www.example.com).', + 'SERVER_PORT' => 'Server port', + 'SERVER_PORT_EXPLAIN' => 'The port your server is running on, usually 80, only change if different.', + 'SERVER_PROTOCOL' => 'Server protocol', + 'SERVER_PROTOCOL_EXPLAIN' => 'This is used as the server protocol if these settings are forced. If empty or not forced the protocol is determined by the cookie secure settings (http:// or https://).', + 'SERVER_URL_SETTINGS' => 'Server URL settings', + 'SMILIES_PATH' => 'Smilies storage path', + 'SMILIES_PATH_EXPLAIN' => 'Path under your phpBB root directory, e.g. images/smilies.', + 'UPLOAD_ICONS_PATH' => 'Extension group icons storage path', + 'UPLOAD_ICONS_PATH_EXPLAIN' => 'Path under your phpBB root directory, e.g. images/upload_icons.', + 'USE_SYSTEM_CRON' => 'Run periodic tasks from system cron', + 'USE_SYSTEM_CRON_EXPLAIN' => 'When off, phpBB will arrange for periodic tasks to be run automatically. When on, phpBB will not schedule any periodic tasks by itself; a system administrator must arrange for bin/phpbbcli.php cron:run to be run by the system cron facility at regular intervals (e.g. every 5 minutes).', +)); + +// Security Settings +$lang = array_merge($lang, array( + 'ACP_SECURITY_SETTINGS_EXPLAIN' => 'Here you are able to define session and login related settings.', + + 'ALL' => 'All', + 'ALLOW_AUTOLOGIN' => 'Allow "Remember Me" logins', + 'ALLOW_AUTOLOGIN_EXPLAIN' => 'Determines whether users are given "Remember Me" option when they visit the board.', + 'ALLOW_PASSWORD_RESET' => 'Allow password reset ("Forgot Password")', + 'ALLOW_PASSWORD_RESET_EXPLAIN' => 'Determines whether or not users are able to use the "I forgot my password" link on the login page to recover their account. If you use an external authentication mechanism you may wish to disable this feature.', + 'AUTOLOGIN_LENGTH' => '"Remember Me" login key expiration length (in days)', + 'AUTOLOGIN_LENGTH_EXPLAIN' => 'Number of days after which "Remember Me" login keys are removed or zero to disable.', + 'BROWSER_VALID' => 'Validate browser', + 'BROWSER_VALID_EXPLAIN' => 'Enables browser validation for each session improving security.', + 'CHECK_DNSBL' => 'Check IP against DNS Blackhole List', + 'CHECK_DNSBL_EXPLAIN' => 'If enabled the user’s IP address is checked against the following DNSBL services on registration and posting: spamcop.net and www.spamhaus.org. This lookup may take a while, depending on the server’s configuration. If slowdowns are experienced or too many false positives reported it is recommended to disable this check.', + 'CLASS_B' => 'A.B', + 'CLASS_C' => 'A.B.C', + 'EMAIL_CHECK_MX' => 'Check email domain for valid MX record', + 'EMAIL_CHECK_MX_EXPLAIN' => 'If enabled, the email domain provided on registration and profile changes is checked for a valid MX record.', + 'FORCE_PASS_CHANGE' => 'Force password change', + 'FORCE_PASS_CHANGE_EXPLAIN' => 'Require user to change their password after a set number of days. Setting this value to 0 disables this behaviour.', + 'FORM_TIME_MAX' => 'Maximum time to submit forms', + 'FORM_TIME_MAX_EXPLAIN' => 'The time a user has to submit a form. Use -1 to disable. Note that a form might become invalid if the session expires, regardless of this setting.', + 'FORM_SID_GUESTS' => 'Tie forms to guest sessions', + 'FORM_SID_GUESTS_EXPLAIN' => 'If enabled, the form token issued to guests will be session-exclusive. This can cause problems with some ISPs.', + 'FORWARDED_FOR_VALID' => 'Validate X_FORWARDED_FOR header', + 'FORWARDED_FOR_VALID_EXPLAIN' => 'Sessions will only be continued if the sent X_FORWARDED_FOR header equals the one sent with the previous request. Bans will be checked against IPs in X_FORWARDED_FOR too.', + 'IP_VALID' => 'Session IP validation', + 'IP_VALID_EXPLAIN' => 'Determines how much of the users IP is used to validate a session; All compares the complete address, A.B.C the first x.x.x, A.B the first x.x, None disables checking. On IPv6 addresses A.B.C compares the first 4 blocks and A.B the first 3 blocks.', + 'IP_LOGIN_LIMIT_MAX' => 'Maximum number of login attempts per IP address', + 'IP_LOGIN_LIMIT_MAX_EXPLAIN' => 'The threshold of login attempts allowed from a single IP address before an anti-spambot task is triggered. Enter 0 to prevent the anti-spambot task from being triggered by IP addresses.', + 'IP_LOGIN_LIMIT_TIME' => 'IP address login attempt expiration time', + 'IP_LOGIN_LIMIT_TIME_EXPLAIN' => 'Login attempts expire after this period.', + 'IP_LOGIN_LIMIT_USE_FORWARDED' => 'Limit login attempts by X_FORWARDED_FOR header', + 'IP_LOGIN_LIMIT_USE_FORWARDED_EXPLAIN' => 'Instead of limiting login attempts by IP address they are limited by X_FORWARDED_FOR values.
Warning: Only enable this if you are operating a proxy server that sets X_FORWARDED_FOR to trustworthy values.', + 'MAX_LOGIN_ATTEMPTS' => 'Maximum number of login attempts per username', + 'MAX_LOGIN_ATTEMPTS_EXPLAIN' => 'The number of login attempts allowed for a single account before the anti-spambot task is triggered. Enter 0 to prevent the anti-spambot task from being triggered for distinct user accounts.', + 'NO_IP_VALIDATION' => 'None', + 'NO_REF_VALIDATION' => 'None', + 'PASSWORD_TYPE' => 'Password complexity', + 'PASSWORD_TYPE_EXPLAIN' => 'Determines how complex a password needs to be when set or altered, subsequent options include the previous ones.', + 'PASS_TYPE_ALPHA' => 'Must contain letters and numbers', + 'PASS_TYPE_ANY' => 'No requirements', + 'PASS_TYPE_CASE' => 'Must be mixed case', + 'PASS_TYPE_SYMBOL' => 'Must contain symbols', + 'REF_HOST' => 'Only validate host', + 'REF_PATH' => 'Also validate path', + 'REFERRER_VALID' => 'Validate Referrer', + 'REFERRER_VALID_EXPLAIN' => 'If enabled, the referrer of POST requests will be checked against the host/script path settings. This may cause issues with boards using several domains and or external logins.', + 'TPL_ALLOW_PHP' => 'Allow php in templates', + 'TPL_ALLOW_PHP_EXPLAIN' => 'If this option is enabled, PHP and INCLUDEPHP statements will be recognised and parsed in templates.', + 'UPLOAD_CERT_VALID' => 'Validate upload certificate', + 'UPLOAD_CERT_VALID_EXPLAIN' => 'If enabled, certificates of remote uploads will be validated. This requires the CA bundle to be defined by the openssl.cafile or curl.cainfo setting in your php.ini.', +)); + +// Email Settings +$lang = array_merge($lang, array( + 'ACP_EMAIL_SETTINGS_EXPLAIN' => 'This information is used when the board sends emails to your users. Please ensure the email address you specify is valid, any bounced or undeliverable messages will likely be sent to that address. If your host does not provide a native (PHP based) email service you can instead send messages directly using SMTP. This requires the address of an appropriate server (ask your provider if necessary). If the server requires authentication (and only if it does) enter the necessary username, password and authentication method.', + + 'ADMIN_EMAIL' => 'From email address', + 'ADMIN_EMAIL_EXPLAIN' => 'This will be used as the from address on all emails, the technical contact email address. It will always be used as the Sender address in emails.', + 'BOARD_EMAIL_FORM' => 'Users send email via board', + 'BOARD_EMAIL_FORM_EXPLAIN' => 'Instead of showing the users email address users are able to send emails via the board.', + 'BOARD_HIDE_EMAILS' => 'Hide email addresses', + 'BOARD_HIDE_EMAILS_EXPLAIN' => 'This function keeps email addresses completely private.', + 'CONTACT_EMAIL' => 'Contact email address', + 'CONTACT_EMAIL_EXPLAIN' => 'This address will be used whenever a specific contact point is needed, e.g. spam, error output, etc. It will always be used as the From and Reply-To address in emails.', + 'CONTACT_EMAIL_NAME' => 'Contact name', + 'CONTACT_EMAIL_NAME_EXPLAIN' => 'This is the contact name that e-mail recipients will see. If you don’t want to have a contact name, leave this field empty.', + 'EMAIL_FORCE_SENDER' => 'Force from email address', + 'EMAIL_FORCE_SENDER_EXPLAIN' => 'This will set the Return-Path to the from email address instead of using the local user and hostname of the server. This setting does not apply when using SMTP.
Warning: Requires the user that the webserver runs as to be added as trusted user to the sendmail configuration.', + 'EMAIL_PACKAGE_SIZE' => 'Email package size', + 'EMAIL_PACKAGE_SIZE_EXPLAIN' => 'This is the number of maximum emails sent out in one package. This setting is applied to the internal message queue; set this value to 0 if you have problems with non-delivered notification emails.', + 'EMAIL_SIG' => 'Email signature', + 'EMAIL_SIG_EXPLAIN' => 'This text will be attached to all emails the board sends.', + 'ENABLE_EMAIL' => 'Enable board-wide emails', + 'ENABLE_EMAIL_EXPLAIN' => 'If this is set to disabled no emails will be sent by the board at all. Note the user and admin account activation settings require this setting to be enabled. If currently using “user” or “admin” activation in the activation settings, disabling this setting will disable registration.', + 'SEND_TEST_EMAIL' => 'Send a test email', + 'SEND_TEST_EMAIL_EXPLAIN' => 'This will send a test email to the address defined in your account.', + 'SMTP_ALLOW_SELF_SIGNED' => 'Allow self-signed SSL certificates', + 'SMTP_ALLOW_SELF_SIGNED_EXPLAIN'=> 'Allow connections to SMTP server with self-signed SSL certificate.
Warning: Allowing self-signed SSL certificates may cause security implications.', + 'SMTP_AUTH_METHOD' => 'Authentication method for SMTP', + 'SMTP_AUTH_METHOD_EXPLAIN' => 'Only used if a username/password is set, ask your provider if you are unsure which method to use.', + 'SMTP_CRAM_MD5' => 'CRAM-MD5', + 'SMTP_DIGEST_MD5' => 'DIGEST-MD5', + 'SMTP_LOGIN' => 'LOGIN', + 'SMTP_PASSWORD' => 'SMTP password', + 'SMTP_PASSWORD_EXPLAIN' => 'Only enter a password if your SMTP server requires it.
Warning: This password will be stored as plain text in the database, visible to everybody who can access your database or who can view this configuration page.', + 'SMTP_PLAIN' => 'PLAIN', + 'SMTP_POP_BEFORE_SMTP' => 'POP-BEFORE-SMTP', + 'SMTP_PORT' => 'SMTP server port', + 'SMTP_PORT_EXPLAIN' => 'Only change this if you know your SMTP server is on a different port.', + 'SMTP_SERVER' => 'SMTP server address and protocol', + 'SMTP_SERVER_EXPLAIN' => 'Note that you have to provide the protocol that your server uses. If you are using SSL, this has to be "ssl://your.mailserver.com"', + 'SMTP_SETTINGS' => 'SMTP settings', + 'SMTP_USERNAME' => 'SMTP username', + 'SMTP_USERNAME_EXPLAIN' => 'Only enter a username if your SMTP server requires it.', + 'SMTP_VERIFY_PEER' => 'Verify SSL certificate', + 'SMTP_VERIFY_PEER_EXPLAIN' => 'Require verification of SSL certificate used by SMTP server.
Warning: Connecting peers with unverified SSL certificates may cause security implications.', + 'SMTP_VERIFY_PEER_NAME' => 'Verify SMTP peer name', + 'SMTP_VERIFY_PEER_NAME_EXPLAIN' => 'Require verification of peer name for SMTP servers using SSL / TLS connections.
Warning: Connecting to unverified peers may cause security implications.', + 'TEST_EMAIL_SENT' => 'The test email has been sent.
If you don’t receive it, please check your emails configuration.

If you require assistance, please visit the phpBB support forums.', + + 'USE_SMTP' => 'Use SMTP server for email', + 'USE_SMTP_EXPLAIN' => 'Select “Yes” if you want or have to send email via a named server instead of the local mail function.', +)); + +// Jabber settings +$lang = array_merge($lang, array( + 'ACP_JABBER_SETTINGS_EXPLAIN' => 'Here you can enable and control the use of Jabber for instant messaging and board notifications. Jabber is an open source protocol and therefore available for use by anyone. Some Jabber servers include gateways or transports which allow you to contact users on other networks. Not all servers offer all transports and changes in protocols can prevent transports from operating. Please be sure to enter already registered account details - phpBB will use the details you enter here as is.', + + 'JAB_ALLOW_SELF_SIGNED' => 'Allow self-signed SSL certificates', + 'JAB_ALLOW_SELF_SIGNED_EXPLAIN' => 'Allow connections to Jabber server with self-signed SSL certificate.
Warning: Allowing self-signed SSL certificates may cause security implications.', + 'JAB_ENABLE' => 'Enable Jabber', + 'JAB_ENABLE_EXPLAIN' => 'Enables use of Jabber messaging and notifications.', + 'JAB_GTALK_NOTE' => 'Please note that GTalk will not work because the dns_get_record function could not be found. This function is not available in PHP4, and is not implemented on Windows platforms. It currently does not work on BSD-based systems, including Mac OS.', + 'JAB_PACKAGE_SIZE' => 'Jabber package size', + 'JAB_PACKAGE_SIZE_EXPLAIN' => 'This is the number of messages sent in one package. If set to 0 the message is sent immediately and will not be queued for later sending.', + 'JAB_PASSWORD' => 'Jabber password', + 'JAB_PASSWORD_EXPLAIN' => 'Warning: This password will be stored as plain text in the database, visible to everybody who can access your database or who can view this configuration page.', + 'JAB_PORT' => 'Jabber port', + 'JAB_PORT_EXPLAIN' => 'Leave blank unless you know it is not port 5222.', + 'JAB_SERVER' => 'Jabber server', + 'JAB_SERVER_EXPLAIN' => 'See %sjabber.org%s for a list of servers.', + 'JAB_SETTINGS_CHANGED' => 'Jabber settings changed successfully.', + 'JAB_USE_SSL' => 'Use SSL to connect', + 'JAB_USE_SSL_EXPLAIN' => 'If enabled a secure connection is tried to be established. The Jabber port will be modified to 5223 if port 5222 is specified.', + 'JAB_USERNAME' => 'Jabber username or JID', + 'JAB_USERNAME_EXPLAIN' => 'Specify a registered username or a valid JID. The username will not be checked for validity. If you only specify a username, then your JID will be the username and the server you specified above. Else, specify a valid JID, for example user@jabber.org.', + 'JAB_VERIFY_PEER' => 'Verify SSL certificate', + 'JAB_VERIFY_PEER_EXPLAIN' => 'Require verification of SSL certificate used by Jabber server.
Warning: Connecting peers with unverified SSL certificates may cause security implications.', + 'JAB_VERIFY_PEER_NAME' => 'Verify Jabber peer name', + 'JAB_VERIFY_PEER_NAME_EXPLAIN' => 'Require verification of peer name for Jabber servers using SSL / TLS connections.
Warning: Connecting to unverified peers may cause security implications.', +)); diff --git a/language/en/acp/bots.php b/language/en/acp/bots.php new file mode 100644 index 0000000..1429228 --- /dev/null +++ b/language/en/acp/bots.php @@ -0,0 +1,72 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Bot settings +$lang = array_merge($lang, array( + 'BOTS' => 'Manage bots', + 'BOTS_EXPLAIN' => '“Bots”, “spiders” or “crawlers” are automated agents most commonly used by search engines to update their databases. Since they rarely make proper use of sessions they can distort visitor counts, increase load and sometimes fail to index sites correctly. Here you can define a special type of user to overcome these problems.', + 'BOT_ACTIVATE' => 'Activate', + 'BOT_ACTIVE' => 'Bot active', + 'BOT_ADD' => 'Add bot', + 'BOT_ADDED' => 'New bot successfully added.', + 'BOT_AGENT' => 'Agent match', + 'BOT_AGENT_EXPLAIN' => 'A string matching the bots browser agent, partial matches are allowed.', + 'BOT_DEACTIVATE' => 'Deactivate', + 'BOT_DELETED' => 'Bot deleted successfully.', + 'BOT_EDIT' => 'Edit bots', + 'BOT_EDIT_EXPLAIN' => 'Here you can add or edit an existing bot entry. You may define an agent string and/or one or more IP addresses (or range of addresses) to match. Be careful when defining matching agent strings or addresses. You may also specify a style and language that the bot will view the board using. This may allow you to reduce bandwidth use by setting a simple style for bots. Remember to set appropriate permissions for the special Bot usergroup.', + 'BOT_LANG' => 'Bot language', + 'BOT_LANG_EXPLAIN' => 'The language presented to the bot as it browses.', + 'BOT_LAST_VISIT' => 'Last visit', + 'BOT_IP' => 'Bot IP address', + 'BOT_IP_EXPLAIN' => 'Partial matches are allowed, separate addresses with a comma.', + 'BOT_NAME' => 'Bot name', + 'BOT_NAME_EXPLAIN' => 'Used only for your own information.', + 'BOT_NAME_TAKEN' => 'The name is already in use on your board and can’t be used for the Bot.', + 'BOT_NEVER' => 'Never', + 'BOT_STYLE' => 'Bot style', + 'BOT_STYLE_EXPLAIN' => 'The style used for the board by the bot.', + 'BOT_UPDATED' => 'Existing bot updated successfully.', + + 'ERR_BOT_AGENT_MATCHES_UA' => 'The bot agent you supplied is similar to the one you are currently using. Please adjust the agent for this bot.', + 'ERR_BOT_NO_IP' => 'The IP addresses you supplied were invalid or the hostname could not be resolved.', + 'ERR_BOT_NO_MATCHES' => 'You must supply at least one of an agent or IP for this bot match.', + + 'NO_BOT' => 'Found no bot with the specified ID.', + 'NO_BOT_GROUP' => 'Unable to find special bot group.', +)); diff --git a/language/en/acp/common.php b/language/en/acp/common.php new file mode 100644 index 0000000..1c22535 --- /dev/null +++ b/language/en/acp/common.php @@ -0,0 +1,833 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Common +$lang = array_merge($lang, array( + 'ACP_ADMINISTRATORS' => 'Administrators', + 'ACP_ADMIN_LOGS' => 'Admin log', + 'ACP_ADMIN_ROLES' => 'Admin roles', + 'ACP_ATTACHMENTS' => 'Attachments', + 'ACP_ATTACHMENT_SETTINGS' => 'Attachment settings', + 'ACP_AUTH_SETTINGS' => 'Authentication', + 'ACP_AUTOMATION' => 'Automation', + 'ACP_AVATAR_SETTINGS' => 'Avatar settings', + + 'ACP_BACKUP' => 'Backup', + 'ACP_BAN' => 'Banning', + 'ACP_BAN_EMAILS' => 'Ban emails', + 'ACP_BAN_IPS' => 'Ban IPs', + 'ACP_BAN_USERNAMES' => 'Ban users', + 'ACP_BBCODES' => 'BBCodes', + 'ACP_BOARD_CONFIGURATION' => 'Board configuration', + 'ACP_BOARD_FEATURES' => 'Board features', + 'ACP_BOARD_MANAGEMENT' => 'Board management', + 'ACP_BOARD_SETTINGS' => 'Board settings', + 'ACP_BOTS' => 'Spiders/Robots', + + 'ACP_CAPTCHA' => 'CAPTCHA', + + 'ACP_CAT_CUSTOMISE' => 'Customise', + 'ACP_CAT_DATABASE' => 'Database', + 'ACP_CAT_DOT_MODS' => 'Extensions', + 'ACP_CAT_FORUMS' => 'Forums', + 'ACP_CAT_GENERAL' => 'General', + 'ACP_CAT_MAINTENANCE' => 'Maintenance', + 'ACP_CAT_PERMISSIONS' => 'Permissions', + 'ACP_CAT_POSTING' => 'Posting', + 'ACP_CAT_STYLES' => 'Styles', + 'ACP_CAT_SYSTEM' => 'System', + 'ACP_CAT_USERGROUP' => 'Users and Groups', + 'ACP_CAT_USERS' => 'Users', + 'ACP_CLIENT_COMMUNICATION' => 'Client communication', + 'ACP_COOKIE_SETTINGS' => 'Cookie settings', + 'ACP_CONTACT' => 'Contact page', + 'ACP_CONTACT_SETTINGS' => 'Contact page settings', + 'ACP_CRITICAL_LOGS' => 'Error log', + 'ACP_CUSTOM_PROFILE_FIELDS' => 'Custom profile fields', + + 'ACP_DATABASE' => 'Database management', + 'ACP_DISALLOW' => 'Disallow', + 'ACP_DISALLOW_USERNAMES' => 'Disallow usernames', + + 'ACP_EMAIL_SETTINGS' => 'Email settings', + 'ACP_EXTENSION_GROUPS' => 'Manage attachment extension groups', + 'ACP_EXTENSION_MANAGEMENT' => 'Extension management', + 'ACP_EXTENSIONS' => 'Manage extensions', + + 'ACP_FORUM_BASED_PERMISSIONS' => 'Forum based permissions', + 'ACP_FORUM_LOGS' => 'Forum logs', + 'ACP_FORUM_MANAGEMENT' => 'Forum management', + 'ACP_FORUM_MODERATORS' => 'Forum moderators', + 'ACP_FORUM_PERMISSIONS' => 'Forum permissions', + 'ACP_FORUM_PERMISSIONS_COPY' => 'Copy forum permissions', + 'ACP_FORUM_ROLES' => 'Forum roles', + + 'ACP_GENERAL_CONFIGURATION' => 'General configuration', + 'ACP_GENERAL_TASKS' => 'General tasks', + 'ACP_GLOBAL_MODERATORS' => 'Global moderators', + 'ACP_GLOBAL_PERMISSIONS' => 'Global permissions', + 'ACP_GROUPS' => 'Groups', + 'ACP_GROUPS_FORUM_PERMISSIONS' => 'Group forum permissions', + 'ACP_GROUPS_MANAGE' => 'Manage groups', + 'ACP_GROUPS_MANAGEMENT' => 'Group management', + 'ACP_GROUPS_PERMISSIONS' => 'Group permissions', + 'ACP_GROUPS_POSITION' => 'Manage group positions', + + 'ACP_HELP_PHPBB' => 'Help support phpBB', + + 'ACP_ICONS' => 'Topic icons', + 'ACP_ICONS_SMILIES' => 'Topic icons/smilies', + 'ACP_INACTIVE_USERS' => 'Inactive users', + 'ACP_INDEX' => 'ACP index', + + 'ACP_JABBER_SETTINGS' => 'Jabber settings', + + 'ACP_LANGUAGE' => 'Language management', + 'ACP_LANGUAGE_PACKS' => 'Language packs', + 'ACP_LOAD_SETTINGS' => 'Load settings', + 'ACP_LOGGING' => 'Logging', + + 'ACP_MAIN' => 'ACP index', + + 'ACP_MANAGE_ATTACHMENTS' => 'Manage attachments', + 'ACP_MANAGE_ATTACHMENTS_EXPLAIN' => 'Here you can list and delete files attached to posts and private messages.', + + 'ACP_MANAGE_EXTENSIONS' => 'Manage attachment extensions', + 'ACP_MANAGE_FORUMS' => 'Manage forums', + 'ACP_MANAGE_RANKS' => 'Manage ranks', + 'ACP_MANAGE_REASONS' => 'Manage report/denial reasons', + 'ACP_MANAGE_USERS' => 'Manage users', + 'ACP_MASS_EMAIL' => 'Mass email', + 'ACP_MESSAGES' => 'Messages', + 'ACP_MESSAGE_SETTINGS' => 'Private message settings', + 'ACP_MODULE_MANAGEMENT' => 'Module management', + 'ACP_MOD_LOGS' => 'Moderator log', + 'ACP_MOD_ROLES' => 'Moderator roles', + + 'ACP_NO_ITEMS' => 'There are no items yet.', + + 'ACP_ORPHAN_ATTACHMENTS' => 'Orphaned attachments', + + 'ACP_PERMISSIONS' => 'Permissions', + 'ACP_PERMISSION_MASKS' => 'Permission masks', + 'ACP_PERMISSION_ROLES' => 'Permission roles', + 'ACP_PERMISSION_TRACE' => 'Permission trace', + 'ACP_PHP_INFO' => 'PHP information', + 'ACP_POST_SETTINGS' => 'Post settings', + 'ACP_PRUNE_FORUMS' => 'Prune forums', + 'ACP_PRUNE_USERS' => 'Prune users', + 'ACP_PRUNING' => 'Pruning', + + 'ACP_QUICK_ACCESS' => 'Quick access', + + 'ACP_RANKS' => 'Ranks', + 'ACP_REASONS' => 'Report/denial reasons', + 'ACP_REGISTER_SETTINGS' => 'User registration settings', + + 'ACP_RESTORE' => 'Restore', + + 'ACP_FEED' => 'Feed management', + 'ACP_FEED_SETTINGS' => 'Feed settings', + + 'ACP_SEARCH' => 'Search configuration', + 'ACP_SEARCH_INDEX' => 'Search index', + 'ACP_SEARCH_SETTINGS' => 'Search settings', + + 'ACP_SECURITY_SETTINGS' => 'Security settings', + 'ACP_SERVER_CONFIGURATION' => 'Server configuration', + 'ACP_SERVER_SETTINGS' => 'Server settings', + 'ACP_SIGNATURE_SETTINGS' => 'Signature settings', + 'ACP_SMILIES' => 'Smilies', + 'ACP_STYLE_MANAGEMENT' => 'Style management', + 'ACP_STYLES' => 'Styles', + 'ACP_STYLES_CACHE' => 'Purge Cache', + 'ACP_STYLES_INSTALL' => 'Install Styles', + + 'ACP_SUBMIT_CHANGES' => 'Submit changes', + + 'ACP_TEMPLATES' => 'Templates', + 'ACP_THEMES' => 'Themes', + + 'ACP_UPDATE' => 'Updating', + 'ACP_USERS_FORUM_PERMISSIONS' => 'User forum permissions', + 'ACP_USERS_LOGS' => 'User logs', + 'ACP_USERS_PERMISSIONS' => 'User permissions', + 'ACP_USER_ATTACH' => 'Attachments', + 'ACP_USER_AVATAR' => 'Avatar', + 'ACP_USER_FEEDBACK' => 'Feedback', + 'ACP_USER_GROUPS' => 'Groups', + 'ACP_USER_MANAGEMENT' => 'User management', + 'ACP_USER_OVERVIEW' => 'Overview', + 'ACP_USER_PERM' => 'Permissions', + 'ACP_USER_PREFS' => 'Preferences', + 'ACP_USER_PROFILE' => 'Profile', + 'ACP_USER_RANK' => 'Rank', + 'ACP_USER_ROLES' => 'User roles', + 'ACP_USER_SECURITY' => 'User security', + 'ACP_USER_SIG' => 'Signature', + 'ACP_USER_WARNINGS' => 'Warnings', + + 'ACP_VC_SETTINGS' => 'Spambot countermeasures', + 'ACP_VC_CAPTCHA_DISPLAY' => 'CAPTCHA image preview', + 'ACP_VERSION_CHECK' => 'Check for updates', + 'ACP_VIEW_ADMIN_PERMISSIONS' => 'View administrative permissions', + 'ACP_VIEW_FORUM_MOD_PERMISSIONS' => 'View forum moderation permissions', + 'ACP_VIEW_FORUM_PERMISSIONS' => 'View forum-based permissions', + 'ACP_VIEW_GLOBAL_MOD_PERMISSIONS' => 'View global moderation permissions', + 'ACP_VIEW_USER_PERMISSIONS' => 'View user-based permissions', + + 'ACP_WORDS' => 'Word censoring', + + 'ACTION' => 'Action', + 'ACTIONS' => 'Actions', + 'ACTIVATE' => 'Activate', + 'ADD' => 'Add', + 'ADMIN' => 'Administration', + 'ADMIN_INDEX' => 'Admin index', + 'ADMIN_PANEL' => 'Administration Control Panel', + + 'ADM_LOGOUT' => 'ACP Logout', + 'ADM_LOGGED_OUT' => 'Successfully logged out from Administration Control Panel', + + 'BACK' => 'Back', + + 'CANNOT_CHANGE_FILE_GROUP' => 'Unable to change file group', + 'CANNOT_CHANGE_FILE_PERMISSIONS' => 'Unable to change file permissions', + 'CANNOT_COPY_FILES' => 'Unable to copy files', + 'CANNOT_CREATE_SYMLINK' => 'Unable to create a symlink', + 'CANNOT_DELETE_FILES' => 'Unable to delete files from the system', + 'CANNOT_DUMP_FILE' => 'Unable to dump file', + 'CANNOT_MIRROR_DIRECTORY' => 'Unable to mirror directory', + 'CANNOT_RENAME_FILE' => 'Unable to rename a file from the system', + 'CANNOT_TOUCH_FILES' => 'Unable to determine if the file exists', + + 'CONTAINER_EXCEPTION' => 'phpBB encountered an error building the container due to an installed extension. For this reason, all extensions have been temporarily disabled. Please try purging your forum cache. All extensions will automatically be re-enabled once the container error is resolved. If this error continues, please visit phpBB.com for support.', + 'EXCEPTION' => 'Exception', + + 'COLOUR_SWATCH' => 'Web-safe colour swatch', + 'CONFIG_UPDATED' => 'Configuration updated successfully.', + 'CRON_LOCK_ERROR' => 'Could not obtain cron lock.', + 'CRON_NO_SUCH_TASK' => 'Could not find cron task “%s”.', + 'CRON_NO_TASK' => 'No cron tasks need to be run right now.', + 'CRON_NO_TASKS' => 'No cron tasks could be found.', + 'CURRENT_VERSION' => 'Current version', + + 'DEACTIVATE' => 'Deactivate', + 'DIRECTORY_DOES_NOT_EXIST' => 'The entered path “%s” does not exist.', + 'DIRECTORY_NOT_DIR' => 'The entered path “%s” is not a directory.', + 'DIRECTORY_NOT_WRITABLE' => 'The entered path “%s” is not writable.', + 'DISABLE' => 'Disable', + 'DOWNLOAD' => 'Download', + 'DOWNLOAD_AS' => 'Download as', + 'DOWNLOAD_STORE' => 'Download or store file', + 'DOWNLOAD_STORE_EXPLAIN' => 'You may directly download the file or save it in your store/ folder.', + 'DOWNLOADS' => 'Downloads', + + 'EDIT' => 'Edit', + 'ENABLE' => 'Enable', + 'EXPORT_DOWNLOAD' => 'Download', + 'EXPORT_STORE' => 'Store', + + 'GENERAL_OPTIONS' => 'General options', + 'GENERAL_SETTINGS' => 'General settings', + 'GLOBAL_MASK' => 'Global permission mask', + + 'INSTALL' => 'Install', + 'IP' => 'User IP', + 'IP_HOSTNAME' => 'IP addresses or hostnames', + + 'LATEST_VERSION' => 'Latest version', + 'LOAD_NOTIFICATIONS' => 'Display Notifications', + 'LOAD_NOTIFICATIONS_EXPLAIN' => 'Display the notifications list on every page (typically in the header).', + 'LOGGED_IN_AS' => 'You are logged in as:', + 'LOGIN_ADMIN' => 'To administer the board you must be an authenticated user.', + 'LOGIN_ADMIN_CONFIRM' => 'To administer the board you must re-authenticate yourself.', + 'LOGIN_ADMIN_SUCCESS' => 'You have successfully authenticated and will now be redirected to the Administration Control Panel.', + 'LOOK_UP_FORUM' => 'Select a forum', + 'LOOK_UP_FORUMS_EXPLAIN'=> 'You are able to select more than one forum.', + + 'MANAGE' => 'Manage', + 'MENU_TOGGLE' => 'Hide or display the side menu', + 'MORE' => 'More', // Not used at the moment + 'MORE_INFORMATION' => 'More information »', + 'MOVE_DOWN' => 'Move down', + 'MOVE_UP' => 'Move up', + + 'NOTIFY' => 'Notification', + 'NO_ADMIN' => 'You are not authorised to administer this board.', + 'NO_EMAILS_DEFINED' => 'No valid email addresses found.', + 'NO_FILES_TO_DELETE' => 'Attachments you selected for deletion do not exist.', + 'NO_PASSWORD_SUPPLIED' => 'You need to enter your password to access the Administration Control Panel.', + + 'OFF' => 'Off', + 'ON' => 'On', + + 'PARSE_BBCODE' => 'Parse BBCode', + 'PARSE_SMILIES' => 'Parse smilies', + 'PARSE_URLS' => 'Parse links', + 'PERMISSIONS_TRANSFERRED' => 'Permissions transferred', + 'PERMISSIONS_TRANSFERRED_EXPLAIN' => 'You currently have the permissions from %1$s. You are able to browse the board with this user’s permissions, but not access the administration control panel since admin permissions were not transferred. You can revert to your permission set at any time.', + 'PROCEED_TO_ACP' => '%sProceed to the ACP%s', + + 'RELEASE_ANNOUNCEMENT' => 'Announcement', + 'REMIND' => 'Remind', + 'REPARSE_LOCK_ERROR' => 'Reparsing is already in progress by another process.', + 'RESYNC' => 'Resynchronise', + + 'RUNNING_TASK' => 'Running task: %s.', + 'SELECT_ANONYMOUS' => 'Select anonymous user', + 'SELECT_OPTION' => 'Select option', + + 'SETTING_TOO_LOW' => 'The provided value for the setting “%1$s” is too low. The minimum acceptable value is %2$d.', + 'SETTING_TOO_BIG' => 'The provided value for the setting “%1$s” is too high. The maximum acceptable value is %2$d.', + 'SETTING_TOO_LONG' => 'The provided value for the setting “%1$s” is too long. The maximum acceptable length is %2$d.', + 'SETTING_TOO_SHORT' => 'The provided value for the setting “%1$s” is too short. The minimum acceptable length is %2$d.', + + 'SHOW_ALL_OPERATIONS' => 'Show all operations', + + 'TASKS_NOT_READY' => 'Not ready tasks:', + 'TASKS_READY' => 'Ready tasks:', + 'TOTAL_SIZE' => 'Total size', + + 'UCP' => 'User Control Panel', + 'URL_INVALID' => 'The provided URL for the setting “%1$s” is invalid.', + 'USERNAMES_EXPLAIN' => 'Place each username on a separate line.', + 'USER_CONTROL_PANEL' => 'User Control Panel', + + 'UPDATE_NEEDED' => 'The board is not up to date.', + 'UPDATE_NOT_NEEDED' => 'The board is up to date.', + 'UPDATES_AVAILABLE' => 'Updates available:', + + 'WARNING' => 'Warning', +)); + +// PHP info +$lang = array_merge($lang, array( + 'ACP_PHP_INFO_EXPLAIN' => 'This page lists information on the version of PHP installed on this server. It includes details of loaded modules, available variables and default settings. This information may be useful when diagnosing problems. Please be aware that some hosting companies will limit what information is displayed here for security reasons. You are advised to not give out any details on this page except when asked by official team members on the support forums.', + + 'NO_PHPINFO_AVAILABLE' => 'Information about your PHP configuration is unable to be determined. Phpinfo() has been disabled for security reasons.', +)); + +// Logs +$lang = array_merge($lang, array( + 'ACP_ADMIN_LOGS_EXPLAIN' => 'This lists all the actions carried out by board administrators. You can sort by username, date, IP or action. If you have appropriate permissions you can also clear individual operations or the log as a whole.', + 'ACP_CRITICAL_LOGS_EXPLAIN' => 'This lists the actions carried out by the board itself. This log provides you with information you are able to use for solving specific problems, for example non-delivery of emails. You can sort by username, date, IP or action. If you have appropriate permissions you can also clear individual operations or the log as a whole.', + 'ACP_MOD_LOGS_EXPLAIN' => 'This lists all actions done on forums, topics and posts as well as actions carried out on users by moderators, including banning. You can sort by username, date, IP or action. If you have appropriate permissions you can also clear individual operations or the log as a whole.', + 'ACP_USERS_LOGS_EXPLAIN' => 'This lists all actions carried out by users or on users (reports, warnings and user notes).', + 'ALL_ENTRIES' => 'All entries', + + 'DISPLAY_LOG' => 'Display entries from previous', + + 'NO_ENTRIES' => 'No log entries for this period.', + + 'SORT_IP' => 'IP address', + 'SORT_DATE' => 'Date', + 'SORT_ACTION' => 'Log action', +)); + +// Index page +$lang = array_merge($lang, array( + 'ADMIN_INTRO' => 'Thank you for choosing phpBB as your board solution. This screen will give you a quick overview of all the various statistics of your board. The links on the left hand side of this screen allow you to control every aspect of your board experience. Each page will have instructions on how to use the tools.', + 'ADMIN_LOG' => 'Logged administrator actions', + 'ADMIN_LOG_INDEX_EXPLAIN' => 'This gives an overview of the last five actions carried out by board administrators. A full copy of the log can be viewed from the appropriate menu item or following the link below.', + 'AVATAR_DIR_SIZE' => 'Avatar directory size', + + 'BOARD_STARTED' => 'Board started', + 'BOARD_VERSION' => 'Board version', + + 'DATABASE_SERVER_INFO' => 'Database server', + 'DATABASE_SIZE' => 'Database size', + + // Enviroment configuration checks, mbstring related + 'ERROR_MBSTRING_FUNC_OVERLOAD' => 'Function overloading is improperly configured', + 'ERROR_MBSTRING_FUNC_OVERLOAD_EXPLAIN' => 'mbstring.func_overload must be set to either 0 or 4. You can check the current value on the PHP information page.', + 'ERROR_MBSTRING_ENCODING_TRANSLATION' => 'Transparent character encoding is improperly configured', + 'ERROR_MBSTRING_ENCODING_TRANSLATION_EXPLAIN' => 'mbstring.encoding_translation must be set to 0. You can check the current value on the PHP information page.', + 'ERROR_MBSTRING_HTTP_INPUT' => 'HTTP input character conversion is improperly configured', + 'ERROR_MBSTRING_HTTP_INPUT_EXPLAIN' => 'mbstring.http_input must be set to pass. You can check the current value on the PHP information page.', + 'ERROR_MBSTRING_HTTP_OUTPUT' => 'HTTP output character conversion is improperly configured', + 'ERROR_MBSTRING_HTTP_OUTPUT_EXPLAIN' => 'mbstring.http_output must be set to pass. You can check the current value on the PHP information page.', + + 'FILES_PER_DAY' => 'Attachments per day', + 'FORUM_STATS' => 'Board statistics', + + 'GZIP_COMPRESSION' => 'GZip compression', + + 'NO_SEARCH_INDEX' => 'The selected search backend does not have a search index.
Please create the index for “%1$s” in the %2$ssearch index%3$s section.', + 'NOT_AVAILABLE' => 'Not available', + 'NUMBER_FILES' => 'Number of attachments', + 'NUMBER_POSTS' => 'Number of posts', + 'NUMBER_TOPICS' => 'Number of topics', + 'NUMBER_USERS' => 'Number of users', + 'NUMBER_ORPHAN' => 'Orphan attachments', + + 'PHP_VERSION' => 'PHP version', + 'PHP_VERSION_OLD' => 'The version of PHP on this server (%1$s) will no longer be supported by future versions of phpBB. The minimum required version will be PHP %2$s. %3$sDetails%4$s', + + 'POSTS_PER_DAY' => 'Posts per day', + + 'PURGE_CACHE' => 'Purge the cache', + 'PURGE_CACHE_CONFIRM' => 'Are you sure you wish to purge the cache?', + 'PURGE_CACHE_EXPLAIN' => 'Purge all cache related items, this includes any cached template files or queries.', + 'PURGE_CACHE_SUCCESS' => 'Cache successfully purged.', + + 'PURGE_SESSIONS' => 'Purge all sessions', + 'PURGE_SESSIONS_CONFIRM' => 'Are you sure you wish to purge all sessions? This will log out all users.', + 'PURGE_SESSIONS_EXPLAIN' => 'Purge all sessions. This will log out all users by truncating the session table.', + 'PURGE_SESSIONS_SUCCESS' => 'Sessions successfully purged.', + + 'RESET_DATE' => 'Reset board’s start date', + 'RESET_DATE_CONFIRM' => 'Are you sure you wish to reset the board’s start date?', + 'RESET_DATE_SUCCESS' => 'Board’s start date reset', + 'RESET_ONLINE' => 'Reset most users ever online', + 'RESET_ONLINE_CONFIRM' => 'Are you sure you wish to reset the most users ever online counter?', + 'RESET_ONLINE_SUCCESS' => 'Most users ever online reset', + 'RESYNC_POSTCOUNTS' => 'Resynchronise post counts', + 'RESYNC_POSTCOUNTS_EXPLAIN' => 'Only existing posts will be taken into consideration. Pruned posts will not be counted.', + 'RESYNC_POSTCOUNTS_CONFIRM' => 'Are you sure you wish to resynchronise post counts?', + 'RESYNC_POSTCOUNTS_SUCCESS' => 'Resynchronised post counts', + 'RESYNC_POST_MARKING' => 'Resynchronise dotted topics', + 'RESYNC_POST_MARKING_CONFIRM' => 'Are you sure you wish to resynchronise dotted topics?', + 'RESYNC_POST_MARKING_EXPLAIN' => 'First unmarks all topics and then correctly marks topics that have seen any activity during the past six months.', + 'RESYNC_POST_MARKING_SUCCESS' => 'Resynchronised dotted topics', + 'RESYNC_STATS' => 'Resynchronise statistics', + 'RESYNC_STATS_CONFIRM' => 'Are you sure you wish to resynchronise statistics?', + 'RESYNC_STATS_EXPLAIN' => 'Recalculates the total number of posts, topics, users and files.', + 'RESYNC_STATS_SUCCESS' => 'Resynchronised statistics', + 'RUN' => 'Run now', + + 'STATISTIC' => 'Statistic', + 'STATISTIC_RESYNC_OPTIONS' => 'Resynchronise or reset statistics', + + 'TIMEZONE_INVALID' => 'The timezone you selected is invalid.', + 'TIMEZONE_SELECTED' => '(currently selected)', + 'TOPICS_PER_DAY' => 'Topics per day', + + 'UPLOAD_DIR_SIZE' => 'Size of posted attachments', + 'USERS_PER_DAY' => 'Users per day', + + 'VALUE' => 'Value', + 'VERSIONCHECK_FAIL' => 'Failed to obtain latest version information.', + 'VERSIONCHECK_FORCE_UPDATE' => 'Re-Check version', + 'VERSION_CHECK' => 'Version check', + 'VERSION_CHECK_EXPLAIN' => 'Checks to see if your phpBB installation is up to date.', + 'VERSIONCHECK_INVALID_ENTRY' => 'Latest version information contains an unsupported entry.', + 'VERSIONCHECK_INVALID_URL' => 'Latest version information contains invalid URL.', + 'VERSIONCHECK_INVALID_VERSION' => 'Latest version information contains an invalid version.', + 'VERSION_NOT_UP_TO_DATE_ACP' => 'Your phpBB installation is not up to date.
Below is a link to the release announcement, which contains more information as well as instructions on updating.', + 'VERSION_NOT_UP_TO_DATE_TITLE' => 'Your phpBB installation is not up to date.', + 'VERSION_UP_TO_DATE_ACP' => 'Your phpBB installation is up to date. There are no updates available at this time.', + 'VIEW_ADMIN_LOG' => 'View administrator log', + 'VIEW_INACTIVE_USERS' => 'View inactive users', + + 'WELCOME_PHPBB' => 'Welcome to phpBB', + 'WRITABLE_CONFIG' => 'Your config file (config.php) is currently world-writable. We strongly encourage you to change the permissions to 640 or at least to 644 (for example: chmod 640 config.php).', +)); + +// Inactive Users +$lang = array_merge($lang, array( + 'INACTIVE_DATE' => 'Inactive date', + 'INACTIVE_REASON' => 'Reason', + 'INACTIVE_REASON_MANUAL' => 'Account deactivated by administrator', + 'INACTIVE_REASON_PROFILE' => 'Profile details changed', + 'INACTIVE_REASON_REGISTER' => 'Newly registered account', + 'INACTIVE_REASON_REMIND' => 'Forced user account reactivation', + 'INACTIVE_REASON_UNKNOWN' => 'Unknown', + 'INACTIVE_USERS' => 'Inactive users', + 'INACTIVE_USERS_EXPLAIN' => 'This is a list of users who have registered but whose accounts are inactive. You can activate, delete or remind (by sending an email) these users if you wish.', + 'INACTIVE_USERS_EXPLAIN_INDEX' => 'This is a list of the last 10 registered users who have inactive accounts. Accounts are inactive either because account activation was enabled in user registration settings and these users’ accounts have not yet been activated, or because these accounts have been deactivated. A full list is available by following the link below from where you can activate, delete or remind (by sending an email) these users if you wish.', + + 'NO_INACTIVE_USERS' => 'No inactive users', + + 'SORT_INACTIVE' => 'Inactive date', + 'SORT_LAST_VISIT' => 'Last visit', + 'SORT_REASON' => 'Reason', + 'SORT_REG_DATE' => 'Registration date', + 'SORT_LAST_REMINDER'=> 'Last reminded', + 'SORT_REMINDER' => 'Reminder sent', + + 'USER_IS_INACTIVE' => 'User is inactive', +)); + +// Help support phpBB page +$lang = array_merge($lang, array( + 'EXPLAIN_SEND_STATISTICS' => 'Please send information about your server and board configurations to phpBB for statistical analysis. All information that could identify you or your website has been removed - the data is entirely anonymous. We base decisions about future phpBB versions on this information. The statistics are made available publically. We also share this data with the PHP project, the programming language phpBB is made with.', + 'EXPLAIN_SHOW_STATISTICS' => 'Using the button below you can preview all variables that will be transmitted.', + 'DONT_SEND_STATISTICS' => 'Return to the ACP if you do not wish to send statistical information to phpBB.', + 'GO_ACP_MAIN' => 'Go to the ACP start page', + 'HIDE_STATISTICS' => 'Hide details', + 'SEND_STATISTICS' => 'Send statistics', + 'SEND_STATISTICS_LONG' => 'Send statistical information', + 'SHOW_STATISTICS' => 'Show details', + 'THANKS_SEND_STATISTICS' => 'Thank you for submitting your information.', + 'FAIL_SEND_STATISTICS' => 'phpBB was unable to send statistics', +)); + +// Log Entries +$lang = array_merge($lang, array( + 'LOG_ACL_ADD_USER_GLOBAL_U_' => 'Added or edited users’ user permissions
» %s', + 'LOG_ACL_ADD_GROUP_GLOBAL_U_' => 'Added or edited groups’ user permissions
» %s', + 'LOG_ACL_ADD_USER_GLOBAL_M_' => 'Added or edited users’ global moderator permissions
» %s', + 'LOG_ACL_ADD_GROUP_GLOBAL_M_' => 'Added or edited groups’ global moderator permissions
» %s', + 'LOG_ACL_ADD_USER_GLOBAL_A_' => 'Added or edited users’ administrator permissions
» %s', + 'LOG_ACL_ADD_GROUP_GLOBAL_A_' => 'Added or edited groups’ administrator permissions
» %s', + + 'LOG_ACL_ADD_ADMIN_GLOBAL_A_' => 'Added or edited Administrators
» %s', + 'LOG_ACL_ADD_MOD_GLOBAL_M_' => 'Added or edited Global Moderators
» %s', + + 'LOG_ACL_ADD_USER_LOCAL_F_' => 'Added or edited users’ forum access from %1$s
» %2$s', + 'LOG_ACL_ADD_USER_LOCAL_M_' => 'Added or edited users’ forum moderator access from %1$s
» %2$s', + 'LOG_ACL_ADD_GROUP_LOCAL_F_' => 'Added or edited groups’ forum access from %1$s
» %2$s', + 'LOG_ACL_ADD_GROUP_LOCAL_M_' => 'Added or edited groups’ forum moderator access from %1$s
» %2$s', + + 'LOG_ACL_ADD_MOD_LOCAL_M_' => 'Added or edited Moderators from %1$s
» %2$s', + 'LOG_ACL_ADD_FORUM_LOCAL_F_' => 'Added or edited forum permissions from %1$s
» %2$s', + + 'LOG_ACL_DEL_ADMIN_GLOBAL_A_' => 'Removed Administrators
» %s', + 'LOG_ACL_DEL_MOD_GLOBAL_M_' => 'Removed Global Moderators
» %s', + 'LOG_ACL_DEL_MOD_LOCAL_M_' => 'Removed Moderators from %1$s
» %2$s', + 'LOG_ACL_DEL_FORUM_LOCAL_F_' => 'Removed User/Group forum permissions from %1$s
» %2$s', + + 'LOG_ACL_TRANSFER_PERMISSIONS' => 'Permissions transferred from
» %s', + 'LOG_ACL_RESTORE_PERMISSIONS' => 'Own permissions restored after using permissions from
» %s', + + 'LOG_ADMIN_AUTH_FAIL' => 'Failed administration login attempt', + 'LOG_ADMIN_AUTH_SUCCESS' => 'Successful administration login', + + 'LOG_ATTACHMENTS_DELETED' => 'Removed user attachments
» %s', + + 'LOG_ATTACH_EXT_ADD' => 'Added or edited attachment extension
» %s', + 'LOG_ATTACH_EXT_DEL' => 'Removed attachment extension
» %s', + 'LOG_ATTACH_EXT_UPDATE' => 'Updated attachment extension
» %s', + 'LOG_ATTACH_EXTGROUP_ADD' => 'Added extension group
» %s', + 'LOG_ATTACH_EXTGROUP_EDIT' => 'Edited extension group
» %s', + 'LOG_ATTACH_EXTGROUP_DEL' => 'Removed extension group
» %s', + 'LOG_ATTACH_FILEUPLOAD' => 'Orphan File uploaded to Post
» ID %1$d - %2$s', + 'LOG_ATTACH_ORPHAN_DEL' => 'Orphan Files deleted
» %s', + + 'LOG_BAN_EXCLUDE_USER' => 'Excluded user from ban for reason “%1$s
» %2$s', + 'LOG_BAN_EXCLUDE_IP' => 'Excluded IP from ban for reason “%1$s
» %2$s', + 'LOG_BAN_EXCLUDE_EMAIL' => 'Excluded email from ban for reason “%1$s
» %2$s', + 'LOG_BAN_USER' => 'Banned user for reason “%1$s
» %2$s', + 'LOG_BAN_IP' => 'Banned IP for reason “%1$s
» %2$s', + 'LOG_BAN_EMAIL' => 'Banned email for reason “%1$s
» %2$s', + 'LOG_UNBAN_USER' => 'Unbanned user
» %s', + 'LOG_UNBAN_IP' => 'Unbanned IP
» %s', + 'LOG_UNBAN_EMAIL' => 'Unbanned email
» %s', + + 'LOG_BBCODE_ADD' => 'Added new BBCode
» %s', + 'LOG_BBCODE_EDIT' => 'Edited BBCode
» %s', + 'LOG_BBCODE_DELETE' => 'Deleted BBCode
» %s', + 'LOG_BBCODE_CONFIGURATION_ERROR' => 'Error while configuring BBCode: %1$s
» %2$s', + + 'LOG_BOT_ADDED' => 'New bot added
» %s', + 'LOG_BOT_DELETE' => 'Deleted bot
» %s', + 'LOG_BOT_UPDATED' => 'Existing bot updated
» %s', + + 'LOG_CLEAR_ADMIN' => 'Cleared admin log', + 'LOG_CLEAR_CRITICAL' => 'Cleared error log', + 'LOG_CLEAR_MOD' => 'Cleared moderator log', + 'LOG_CLEAR_USER' => 'Cleared user log
» %s', + 'LOG_CLEAR_USERS' => 'Cleared user logs', + + 'LOG_CONFIG_ATTACH' => 'Altered attachment settings', + 'LOG_CONFIG_AUTH' => 'Altered authentication settings', + 'LOG_CONFIG_AVATAR' => 'Altered avatar settings', + 'LOG_CONFIG_COOKIE' => 'Altered cookie settings', + 'LOG_CONFIG_EMAIL' => 'Altered email settings', + 'LOG_CONFIG_FEATURES' => 'Altered board features', + 'LOG_CONFIG_LOAD' => 'Altered load settings', + 'LOG_CONFIG_MESSAGE' => 'Altered private message settings', + 'LOG_CONFIG_POST' => 'Altered post settings', + 'LOG_CONFIG_REGISTRATION' => 'Altered user registration settings', + 'LOG_CONFIG_FEED' => 'Altered syndication feeds settings', + 'LOG_CONFIG_SEARCH' => 'Altered search settings', + 'LOG_CONFIG_SECURITY' => 'Altered security settings', + 'LOG_CONFIG_SERVER' => 'Altered server settings', + 'LOG_CONFIG_SETTINGS' => 'Altered board settings', + 'LOG_CONFIG_SIGNATURE' => 'Altered signature settings', + 'LOG_CONFIG_VISUAL' => 'Altered anti-spambot settings', + + 'LOG_APPROVE_TOPIC' => 'Approved topic
» %s', + 'LOG_BUMP_TOPIC' => 'User bumped topic
» %s', + 'LOG_DELETE_POST' => 'Deleted post “%1$s” written by “%2$s” for the following reason
» %3$s', + 'LOG_DELETE_SHADOW_TOPIC' => 'Deleted shadow topic
» %s', + 'LOG_DELETE_TOPIC' => 'Deleted topic “%1$s” written by “%2$s” for the following reason
» %3$s', + 'LOG_FORK' => 'Copied topic
» from %s', + 'LOG_LOCK' => 'Locked topic
» %s', + 'LOG_LOCK_POST' => 'Locked post
» %s', + 'LOG_MERGE' => 'Merged posts into topic
» %s', + 'LOG_MOVE' => 'Moved topic
» from %1$s to %2$s', + 'LOG_MOVED_TOPIC' => 'Moved topic
» %s', + 'LOG_PM_REPORT_CLOSED' => 'Closed PM report
» %s', + 'LOG_PM_REPORT_DELETED' => 'Deleted PM report
» %s', + 'LOG_POST_APPROVED' => 'Approved post
» %s', + 'LOG_POST_DISAPPROVED' => 'Disapproved post “%1$s” written by “%3$s” for the following reason
» %2$s', + 'LOG_POST_EDITED' => 'Edited post “%1$s” written by “%2$s” for the following reason
» %3$s', + 'LOG_POST_RESTORED' => 'Restored post
» %s', + 'LOG_REPORT_CLOSED' => 'Closed report
» %s', + 'LOG_REPORT_DELETED' => 'Deleted report
» %s', + 'LOG_RESTORE_TOPIC' => 'Restored topic “%1$s” written by
» %2$s', + 'LOG_SOFTDELETE_POST' => 'Soft deleted post “%1$s” written by “%2$s” for the following reason
» %3$s', + 'LOG_SOFTDELETE_TOPIC' => 'Soft deleted topic “%1$s” written by “%2$s” for the following reason
» %3$s', + 'LOG_SPLIT_DESTINATION' => 'Moved split posts
» to %s', + 'LOG_SPLIT_SOURCE' => 'Split posts
» from %s', + + 'LOG_TOPIC_APPROVED' => 'Approved topic
» %s', + 'LOG_TOPIC_RESTORED' => 'Restored topic
» %s', + 'LOG_TOPIC_DISAPPROVED' => 'Disapproved topic “%1$s” written by “%3$s” for the following reason
» %2$s', + 'LOG_TOPIC_RESYNC' => 'Resynchronised topic counters
» %s', + 'LOG_TOPIC_TYPE_CHANGED' => 'Changed topic type
» %s', + 'LOG_UNLOCK' => 'Unlocked topic
» %s', + 'LOG_UNLOCK_POST' => 'Unlocked post
» %s', + + 'LOG_DISALLOW_ADD' => 'Added disallowed username
» %s', + 'LOG_DISALLOW_DELETE' => 'Deleted disallowed username', + + 'LOG_DB_BACKUP' => 'Database backup', + 'LOG_DB_DELETE' => 'Deleted database backup', + 'LOG_DB_RESTORE' => 'Restored database backup', + + 'LOG_DOWNLOAD_EXCLUDE_IP' => 'Excluded IP/hostname from download list
» %s', + 'LOG_DOWNLOAD_IP' => 'Added IP/hostname to download list
» %s', + 'LOG_DOWNLOAD_REMOVE_IP' => 'Removed IP/hostname from download list
» %s', + + 'LOG_ERROR_JABBER' => 'Jabber error
» %s', + 'LOG_ERROR_EMAIL' => 'Email error
» %s', + 'LOG_ERROR_CAPTCHA' => 'CAPTCHA error
» %s', + + 'LOG_FORUM_ADD' => 'Created new forum
» %s', + 'LOG_FORUM_COPIED_PERMISSIONS' => 'Copied forum permissions from %1$s
» %2$s', + 'LOG_FORUM_DEL_FORUM' => 'Deleted forum
» %s', + 'LOG_FORUM_DEL_FORUMS' => 'Deleted forum and its subforums
» %s', + 'LOG_FORUM_DEL_MOVE_FORUMS' => 'Deleted forum and moved subforums to %1$s
» %2$s', + 'LOG_FORUM_DEL_MOVE_POSTS' => 'Deleted forum and moved posts to %1$s
» %2$s', + 'LOG_FORUM_DEL_MOVE_POSTS_FORUMS' => 'Deleted forum and its subforums, moved posts to %1$s
» %2$s', + 'LOG_FORUM_DEL_MOVE_POSTS_MOVE_FORUMS' => 'Deleted forum, moved posts to %1$s and subforums to %2$s
» %3$s', + 'LOG_FORUM_DEL_POSTS' => 'Deleted forum and its posts
» %s', + 'LOG_FORUM_DEL_POSTS_FORUMS' => 'Deleted forum, its posts and subforums
» %s', + 'LOG_FORUM_DEL_POSTS_MOVE_FORUMS' => 'Deleted forum and its posts, moved subforums to %1$s
» %2$s', + 'LOG_FORUM_EDIT' => 'Edited forum details
» %s', + 'LOG_FORUM_MOVE_DOWN' => 'Moved forum %1$s below %2$s', + 'LOG_FORUM_MOVE_UP' => 'Moved forum %1$s above %2$s', + 'LOG_FORUM_SYNC' => 'Re-synchronised forum
» %s', + + 'LOG_GENERAL_ERROR' => 'A general error occurred: %1$s
» %2$s', + + 'LOG_GROUP_CREATED' => 'New usergroup created
» %s', + 'LOG_GROUP_DEFAULTS' => 'Group “%1$s” made default for members
» %2$s', + 'LOG_GROUP_DELETE' => 'Usergroup deleted
» %s', + 'LOG_GROUP_DEMOTED' => 'Leaders demoted in usergroup %1$s
» %2$s', + 'LOG_GROUP_PROMOTED' => 'Members promoted to leader in usergroup %1$s
» %2$s', + 'LOG_GROUP_REMOVE' => 'Members removed from usergroup %1$s
» %2$s', + 'LOG_GROUP_UPDATED' => 'Usergroup details updated
» %s', + 'LOG_MODS_ADDED' => 'Added new leaders to usergroup %1$s
» %2$s', + 'LOG_USERS_ADDED' => 'Added new members to usergroup %1$s
» %2$s', + 'LOG_USERS_APPROVED' => 'Users approved in usergroup %1$s
» %2$s', + 'LOG_USERS_PENDING' => 'Users requested to join group “%1$s” and need to be approved
» %2$s', + + 'LOG_IMAGE_GENERATION_ERROR' => 'Error while creating image
» Error in %1$s on line %2$s: %3$s', + + 'LOG_INACTIVE_ACTIVATE' => 'Activated inactive users
» %s', + 'LOG_INACTIVE_DELETE' => 'Deleted inactive users
» %s', + 'LOG_INACTIVE_REMIND' => 'Sent reminder emails to inactive users
» %s', + 'LOG_INSTALL_CONVERTED' => 'Converted from %1$s to phpBB %2$s', + 'LOG_INSTALL_INSTALLED' => 'Installed phpBB %s', + + 'LOG_IP_BROWSER_FORWARDED_CHECK' => 'Session IP/browser/X_FORWARDED_FOR check failed
»User IP “%1$s” checked against session IP “%2$s”, user browser string “%3$s” checked against session browser string “%4$s” and user X_FORWARDED_FOR string “%5$s” checked against session X_FORWARDED_FOR string “%6$s”.', + + 'LOG_JAB_CHANGED' => 'Jabber account changed', + 'LOG_JAB_PASSCHG' => 'Jabber password changed', + 'LOG_JAB_REGISTER' => 'Jabber account registered', + 'LOG_JAB_SETTINGS_CHANGED' => 'Jabber settings changed', + + 'LOG_LANGUAGE_PACK_DELETED' => 'Deleted language pack
» %s', + 'LOG_LANGUAGE_PACK_INSTALLED' => 'Installed language pack
» %s', + 'LOG_LANGUAGE_PACK_UPDATED' => 'Updated language pack details
» %s', + 'LOG_LANGUAGE_FILE_REPLACED' => 'Replaced language file
» %s', + 'LOG_LANGUAGE_FILE_SUBMITTED' => 'Submitted language file and placed in store folder
» %s', + + 'LOG_MASS_EMAIL' => 'Sent mass email
» %s', + + 'LOG_MCP_CHANGE_POSTER' => 'Changed poster in topic “%1$s”
» from %2$s to %3$s', + + 'LOG_MODULE_DISABLE' => 'Module disabled
» %s', + 'LOG_MODULE_ENABLE' => 'Module enabled
» %s', + 'LOG_MODULE_MOVE_DOWN' => 'Module moved down
» %1$s below %2$s', + 'LOG_MODULE_MOVE_UP' => 'Module moved up
» %1$s above %2$s', + 'LOG_MODULE_REMOVED' => 'Module removed
» %s', + 'LOG_MODULE_ADD' => 'Module added
» %s', + 'LOG_MODULE_EDIT' => 'Module edited
» %s', + + 'LOG_A_ROLE_ADD' => 'Admin role added
» %s', + 'LOG_A_ROLE_EDIT' => 'Admin role edited
» %s', + 'LOG_A_ROLE_REMOVED' => 'Admin role removed
» %s', + 'LOG_F_ROLE_ADD' => 'Forum role added
» %s', + 'LOG_F_ROLE_EDIT' => 'Forum role edited
» %s', + 'LOG_F_ROLE_REMOVED' => 'Forum role removed
» %s', + 'LOG_M_ROLE_ADD' => 'Moderator role added
» %s', + 'LOG_M_ROLE_EDIT' => 'Moderator role edited
» %s', + 'LOG_M_ROLE_REMOVED' => 'Moderator role removed
» %s', + 'LOG_U_ROLE_ADD' => 'User role added
» %s', + 'LOG_U_ROLE_EDIT' => 'User role edited
» %s', + 'LOG_U_ROLE_REMOVED' => 'User role removed
» %s', + + 'LOG_PLUPLOAD_TIDY_FAILED' => 'Unable to open %1$s for tidying, check permissions.
Exception: %2$s
Trace: %3$s', + + 'LOG_PROFILE_FIELD_ACTIVATE' => 'Profile field activated
» %s', + 'LOG_PROFILE_FIELD_CREATE' => 'Profile field added
» %s', + 'LOG_PROFILE_FIELD_DEACTIVATE' => 'Profile field deactivated
» %s', + 'LOG_PROFILE_FIELD_EDIT' => 'Profile field changed
» %s', + 'LOG_PROFILE_FIELD_REMOVED' => 'Profile field removed
» %s', + + 'LOG_PRUNE' => 'Pruned forums
» %s', + 'LOG_AUTO_PRUNE' => 'Auto-pruned forums
» %s', + 'LOG_PRUNE_SHADOW' => 'Auto-pruned shadow topics
» %s', + 'LOG_PRUNE_USER_DEAC' => 'Users deactivated
» %s', + 'LOG_PRUNE_USER_DEL_DEL' => 'Users pruned and posts deleted
» %s', + 'LOG_PRUNE_USER_DEL_ANON' => 'Users pruned and posts retained
» %s', + + 'LOG_PURGE_CACHE' => 'Purged cache', + 'LOG_PURGE_SESSIONS' => 'Purged sessions', + + 'LOG_RANK_ADDED' => 'Added new rank
» %s', + 'LOG_RANK_REMOVED' => 'Removed rank
» %s', + 'LOG_RANK_UPDATED' => 'Updated rank
» %s', + + 'LOG_REASON_ADDED' => 'Added report/denial reason
» %s', + 'LOG_REASON_REMOVED' => 'Removed report/denial reason
» %s', + 'LOG_REASON_UPDATED' => 'Updated report/denial reason
» %s', + + 'LOG_REFERER_INVALID' => 'Referrer validation failed
»Referrer was “%1$s”. The request was rejected and the session killed.', + 'LOG_RESET_DATE' => 'Board start date reset', + 'LOG_RESET_ONLINE' => 'Most users online reset', + 'LOG_RESYNC_FILES_STATS' => 'File statistics resynchronised', + 'LOG_RESYNC_POSTCOUNTS' => 'User post counts resynchronised', + 'LOG_RESYNC_POST_MARKING' => 'Dotted topics resynchronised', + 'LOG_RESYNC_STATS' => 'Post, topic and user statistics resynchronised', + + 'LOG_SEARCH_INDEX_CREATED' => 'Created search index for
» %s', + 'LOG_SEARCH_INDEX_REMOVED' => 'Removed search index for
» %s', + 'LOG_SPHINX_ERROR' => 'Sphinx Error
» %s', + 'LOG_STYLE_ADD' => 'Added new style
» %s', + 'LOG_STYLE_DELETE' => 'Deleted style
» %s', + 'LOG_STYLE_EDIT_DETAILS' => 'Edited style
» %s', + 'LOG_STYLE_EXPORT' => 'Exported style
» %s', + + // @deprecated 3.1 + 'LOG_TEMPLATE_ADD_DB' => 'Added new template set to database
» %s', + // @deprecated 3.1 + 'LOG_TEMPLATE_ADD_FS' => 'Add new template set on filesystem
» %s', + 'LOG_TEMPLATE_CACHE_CLEARED' => 'Deleted cached versions of template files in template set %1$s
» %2$s', + 'LOG_TEMPLATE_DELETE' => 'Deleted template set
» %s', + 'LOG_TEMPLATE_EDIT' => 'Edited template set %1$s
» %2$s', + 'LOG_TEMPLATE_EDIT_DETAILS' => 'Edited template details
» %s', + 'LOG_TEMPLATE_EXPORT' => 'Exported template set
» %s', + // @deprecated 3.1 + 'LOG_TEMPLATE_REFRESHED' => 'Refreshed template set
» %s', + + // @deprecated 3.1 + 'LOG_THEME_ADD_DB' => 'Added new theme to database
» %s', + // @deprecated 3.1 + 'LOG_THEME_ADD_FS' => 'Add new theme on filesystem
» %s', + 'LOG_THEME_DELETE' => 'Theme deleted
» %s', + 'LOG_THEME_EDIT_DETAILS' => 'Edited theme details
» %s', + 'LOG_THEME_EDIT' => 'Edited theme %1$s', + 'LOG_THEME_EDIT_FILE' => 'Edited theme %1$s
» Modified file %2$s', + 'LOG_THEME_EXPORT' => 'Exported theme
» %s', + // @deprecated 3.1 + 'LOG_THEME_REFRESHED' => 'Refreshed theme
» %s', + + 'LOG_UPDATE_DATABASE' => 'Updated Database from version %1$s to version %2$s', + 'LOG_UPDATE_PHPBB' => 'Updated phpBB from version %1$s to version %2$s', + + 'LOG_USER_ACTIVE' => 'User activated
» %s', + 'LOG_USER_BAN_USER' => 'Banned User via user management for reason “%1$s
» %2$s', + 'LOG_USER_BAN_IP' => 'Banned IP via user management for reason “%1$s
» %2$s', + 'LOG_USER_BAN_EMAIL' => 'Banned email via user management for reason “%1$s
» %2$s', + 'LOG_USER_DELETED' => 'Deleted user
» %s', + 'LOG_USER_DEL_ATTACH' => 'Removed all attachments made by the user
» %s', + 'LOG_USER_DEL_AVATAR' => 'Removed user avatar
» %s', + 'LOG_USER_DEL_OUTBOX' => 'Emptied user outbox
» %s', + 'LOG_USER_DEL_POSTS' => 'Removed all posts made by the user
» %s', + 'LOG_USER_DEL_SIG' => 'Removed user signature
» %s', + 'LOG_USER_INACTIVE' => 'User deactivated
» %s', + 'LOG_USER_MOVE_POSTS' => 'Moved user posts
» posts by “%1$s” to forum “%2$s”', + 'LOG_USER_NEW_PASSWORD' => 'Changed user password
» %s', + 'LOG_USER_REACTIVATE' => 'Forced user account reactivation
» %s', + 'LOG_USER_REMOVED_NR' => 'Removed newly registered flag from user
» %s', + + 'LOG_USER_UPDATE_EMAIL' => 'User “%1$s” changed email
» from “%2$s” to “%3$s”', + 'LOG_USER_UPDATE_NAME' => 'Changed username
» from “%1$s” to “%2$s”', + 'LOG_USER_USER_UPDATE' => 'Updated user details
» %s', + + 'LOG_USER_ACTIVE_USER' => 'User account activated', + 'LOG_USER_DEL_AVATAR_USER' => 'User avatar removed', + 'LOG_USER_DEL_SIG_USER' => 'User signature removed', + 'LOG_USER_FEEDBACK' => 'Added user feedback
» %s', + 'LOG_USER_GENERAL' => 'Entry added:
» %s', + 'LOG_USER_INACTIVE_USER' => 'User account de-activated', + 'LOG_USER_LOCK' => 'User locked own topic
» %s', + 'LOG_USER_MOVE_POSTS_USER' => 'Moved all posts to forum» %s', + 'LOG_USER_REACTIVATE_USER' => 'Forced user account reactivation', + 'LOG_USER_UNLOCK' => 'User unlocked own topic
» %s', + 'LOG_USER_WARNING' => 'Added user warning
» %s', + 'LOG_USER_WARNING_BODY' => 'The following warning was issued to this user
» %s', + + 'LOG_USER_GROUP_CHANGE' => 'User changed default group
» %s', + 'LOG_USER_GROUP_DEMOTE' => 'User demoted as leaders from usergroup
» %s', + 'LOG_USER_GROUP_JOIN' => 'User joined group
» %s', + 'LOG_USER_GROUP_JOIN_PENDING' => 'User joined group and needs to be approved
» %s', + 'LOG_USER_GROUP_RESIGN' => 'User resigned membership from group
» %s', + + 'LOG_WARNING_DELETED' => 'Deleted user warning
» %s', + 'LOG_WARNINGS_DELETED' => array( + 1 => 'Deleted user warning
» %1$s', + 2 => 'Deleted %2$d user warnings
» %1$s', // Example: 'Deleted 2 user warnings
» username' + ), + 'LOG_WARNINGS_DELETED_ALL' => 'Deleted all user warnings
» %s', + + 'LOG_WORD_ADD' => 'Added word censor
» %s', + 'LOG_WORD_DELETE' => 'Deleted word censor
» %s', + 'LOG_WORD_EDIT' => 'Edited word censor
» %s', + + 'LOG_EXT_ENABLE' => 'Extension enabled
» %s', + 'LOG_EXT_DISABLE' => 'Extension disabled
» %s', + 'LOG_EXT_PURGE' => 'Extension’s data deleted
» %s', + 'LOG_EXT_UPDATE' => 'Extension updated
» %s', +)); diff --git a/language/en/acp/database.php b/language/en/acp/database.php new file mode 100644 index 0000000..302aaee --- /dev/null +++ b/language/en/acp/database.php @@ -0,0 +1,77 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Database Backup/Restore +$lang = array_merge($lang, array( + 'ACP_BACKUP_EXPLAIN' => 'Here you can backup all your phpBB related data. The resulting archive will be stored in your store/ folder. Depending on your server configuration you may be able to compress the file in a number of formats.', + 'ACP_RESTORE_EXPLAIN' => 'This will perform a full restore of all phpBB tables from a saved file. If your server supports it you may use a gzip or bzip2 compressed text file and it will automatically be decompressed. WARNING This will overwrite any existing data. The restore may take a long time to process please do not move from this page till it is complete. Backups are stored in the store/ folder and are assumed to be generated by phpBB’s backup functionality. Restoring backups that were not created by the built in system may or may not work.', + + 'BACKUP_DELETE' => 'The backup file has been deleted successfully.', + 'BACKUP_INVALID' => 'The selected file to backup is invalid.', + 'BACKUP_NOT_SUPPORTED' => 'The selected backup is not supported', + 'BACKUP_OPTIONS' => 'Backup options', + 'BACKUP_SUCCESS' => 'The backup file has been created successfully.', + 'BACKUP_TYPE' => 'Backup type', + + 'DATABASE' => 'Database utilities', + 'DATA_ONLY' => 'Data only', + 'DELETE_BACKUP' => 'Delete backup', + 'DELETE_SELECTED_BACKUP' => 'Are you sure you want to delete the selected backup?', + 'DESELECT_ALL' => 'Deselect all', + 'DOWNLOAD_BACKUP' => 'Download backup', + + 'FILE_TYPE' => 'File type', + 'FILE_WRITE_FAIL' => 'Unable to write file to storage folder.', + 'FULL_BACKUP' => 'Full', + + 'RESTORE_FAILURE' => 'The backup file may be corrupt.', + 'RESTORE_OPTIONS' => 'Restore options', + 'RESTORE_SELECTED_BACKUP' => 'Are you sure you want to restore the selected backup?', + 'RESTORE_SUCCESS' => 'The database has been successfully restored.

Your board should be back to the state it was when the backup was made.', + + 'SELECT_ALL' => 'Select all', + 'SELECT_FILE' => 'Select a file', + 'START_BACKUP' => 'Start backup', + 'START_RESTORE' => 'Start restore', + 'STORE_AND_DOWNLOAD' => 'Store and download', + 'STORE_LOCAL' => 'Store file locally', + 'STRUCTURE_ONLY' => 'Structure only', + + 'TABLE_SELECT' => 'Table select', + 'TABLE_SELECT_ERROR'=> 'You must select at least one table.', +)); diff --git a/language/en/acp/email.php b/language/en/acp/email.php new file mode 100644 index 0000000..0d47e37 --- /dev/null +++ b/language/en/acp/email.php @@ -0,0 +1,68 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Email settings +$lang = array_merge($lang, array( + 'ACP_MASS_EMAIL_EXPLAIN' => 'Here you can email a message to either all of your users or all users of a specific group having the option to receive mass emails enabled. To achieve this an email will be sent out to the administrative email address supplied, with a blind carbon copy sent to all recipients. The default setting is to only include 20 recipients in such an email, for more recipients more emails will be sent. If you are emailing a large group of people please be patient after submitting and do not stop the page halfway through. It is normal for a mass emailing to take a long time, you will be notified when the script has completed.', + 'ALL_USERS' => 'All users', + + 'COMPOSE' => 'Compose', + + 'EMAIL_SEND_ERROR' => 'There were one or more errors while sending the email. Please check the %sError log%s for detailed error messages.', + 'EMAIL_SENT' => 'This message has been sent.', + 'EMAIL_SENT_QUEUE' => 'This message has been queued for sending.', + + 'LOG_SESSION' => 'Log mail session to critical log', + + 'SEND_IMMEDIATELY' => 'Send immediately', + 'SEND_TO_GROUP' => 'Send to group', + 'SEND_TO_USERS' => 'Send to users', + 'SEND_TO_USERS_EXPLAIN' => 'Entering names here will override any group selected above. Enter each username on a new line.', + + 'MAIL_BANNED' => 'Mail banned users', + 'MAIL_BANNED_EXPLAIN' => 'When sending a mass email to a group you can select here whether banned users will also receive the email.', + 'MAIL_HIGH_PRIORITY' => 'High', + 'MAIL_LOW_PRIORITY' => 'Low', + 'MAIL_NORMAL_PRIORITY' => 'Normal', + 'MAIL_PRIORITY' => 'Mail priority', + 'MASS_MESSAGE' => 'Your message', + 'MASS_MESSAGE_EXPLAIN' => 'Please note that you may enter only plain text. All markup will be removed before sending.', + + 'NO_EMAIL_MESSAGE' => 'You must enter a message.', + 'NO_EMAIL_SUBJECT' => 'You must specify a subject for your message.', +)); diff --git a/language/en/acp/extensions.php b/language/en/acp/extensions.php new file mode 100644 index 0000000..a96a7a2 --- /dev/null +++ b/language/en/acp/extensions.php @@ -0,0 +1,133 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* DO NOT CHANGE +*/ +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'EXTENSION' => 'Extension', + 'EXTENSIONS' => 'Extensions', + 'EXTENSIONS_ADMIN' => 'Extensions Manager', + 'EXTENSIONS_EXPLAIN' => 'The Extensions Manager is a tool in your phpBB Board which allows you to manage all of your extensions statuses and view information about them.', + 'EXTENSION_INVALID_LIST' => 'The “%s” extension is not valid.
%s

', + 'EXTENSION_NOT_AVAILABLE' => 'The selected extension is not available for this board, please verify your phpBB and PHP versions are allowed (see the details page).', + 'EXTENSION_DIR_INVALID' => 'The selected extension has an invalid directory structure and cannot be enabled.', + 'EXTENSION_NOT_ENABLEABLE' => 'The selected extension cannot be enabled, please verify the extension’s requirements.', + 'EXTENSION_NOT_INSTALLED' => 'The extension %s is not available. Please check that you have installed it correctly.', + + 'DETAILS' => 'Details', + + 'EXTENSIONS_DISABLED' => 'Disabled Extensions', + 'EXTENSIONS_ENABLED' => 'Enabled Extensions', + + 'EXTENSION_DELETE_DATA' => 'Delete data', + 'EXTENSION_DISABLE' => 'Disable', + 'EXTENSION_ENABLE' => 'Enable', + + 'EXTENSION_DELETE_DATA_EXPLAIN' => 'Deleting an extension’s data removes all of its data and settings. The extension files are retained so it can be enabled again.', + 'EXTENSION_DISABLE_EXPLAIN' => 'Disabling an extension retains its files, data and settings but removes any functionality added by the extension.', + 'EXTENSION_ENABLE_EXPLAIN' => 'Enabling an extension allows you to use it on your board.', + + 'EXTENSION_DELETE_DATA_IN_PROGRESS' => 'The extension’s data is currently being deleted. Please do not leave or refresh this page until it is completed.', + 'EXTENSION_DISABLE_IN_PROGRESS' => 'The extension is currently being disabled. Please do not leave or refresh this page until it is completed.', + 'EXTENSION_ENABLE_IN_PROGRESS' => 'The extension is currently being enabled. Please do not leave or refresh this page until it is completed.', + + 'EXTENSION_DELETE_DATA_SUCCESS' => 'The extension’s data was deleted successfully', + 'EXTENSION_DISABLE_SUCCESS' => 'The extension was disabled successfully', + 'EXTENSION_ENABLE_SUCCESS' => 'The extension was enabled successfully', + + 'EXTENSION_NAME' => 'Extension Name', + 'EXTENSION_ACTIONS' => 'Actions', + 'EXTENSION_OPTIONS' => 'Options', + 'EXTENSION_INSTALL_HEADLINE'=> 'Installing an extension', + 'EXTENSION_INSTALL_EXPLAIN' => '

    +
  1. Download an extension from phpBB’s extensions database
  2. +
  3. Unzip the extension and upload it to the ext/ directory of your phpBB board
  4. +
  5. Enable the extension, here in the Extensions manager
  6. +
', + 'EXTENSION_UPDATE_HEADLINE' => 'Updating an extension', + 'EXTENSION_UPDATE_EXPLAIN' => '
    +
  1. Disable the extension
  2. +
  3. Delete the extension’s files from the filesystem
  4. +
  5. Upload the new files
  6. +
  7. Enable the extension
  8. +
', + 'EXTENSION_REMOVE_HEADLINE' => 'Completely removing an extension from your board', + 'EXTENSION_REMOVE_EXPLAIN' => '
    +
  1. Disable the extension
  2. +
  3. Delete the extension’s data
  4. +
  5. Delete the extension’s files from the filesystem
  6. +
', + + 'EXTENSION_DELETE_DATA_CONFIRM' => 'Are you sure that you wish to delete the data associated with “%s”?

This removes all of its data and settings and cannot be undone!', + 'EXTENSION_DISABLE_CONFIRM' => 'Are you sure that you wish to disable the “%s” extension?', + 'EXTENSION_ENABLE_CONFIRM' => 'Are you sure that you wish to enable the “%s” extension?', + 'EXTENSION_FORCE_UNSTABLE_CONFIRM' => 'Are you sure that you wish to force the use of unstable version?', + + 'RETURN_TO_EXTENSION_LIST' => 'Return to the extension list', + + 'EXT_DETAILS' => 'Extension Details', + 'DISPLAY_NAME' => 'Display Name', + 'CLEAN_NAME' => 'Clean Name', + 'TYPE' => 'Type', + 'DESCRIPTION' => 'Description', + 'VERSION' => 'Version', + 'HOMEPAGE' => 'Homepage', + 'PATH' => 'File Path', + 'TIME' => 'Release Time', + 'LICENSE' => 'Licence', + + 'REQUIREMENTS' => 'Requirements', + 'PHPBB_VERSION' => 'phpBB Version', + 'PHP_VERSION' => 'PHP Version', + 'AUTHOR_INFORMATION' => 'Author Information', + 'AUTHOR_NAME' => 'Name', + 'AUTHOR_EMAIL' => 'Email', + 'AUTHOR_HOMEPAGE' => 'Homepage', + 'AUTHOR_ROLE' => 'Role', + + 'NOT_UP_TO_DATE' => '%s is not up to date', + 'UP_TO_DATE' => '%s is up to date', + 'ANNOUNCEMENT_TOPIC' => 'Release Announcement', + 'DOWNLOAD_LATEST' => 'Download Version', + 'NO_VERSIONCHECK' => 'No version check information given.', + + 'VERSIONCHECK_FORCE_UPDATE_ALL' => 'Re-Check all versions', + 'FORCE_UNSTABLE' => 'Always check for unstable versions', + 'EXTENSIONS_VERSION_CHECK_SETTINGS' => 'Version check settings', + + 'BROWSE_EXTENSIONS_DATABASE' => 'Browse extensions database', + + 'META_FIELD_NOT_SET' => 'Required meta field %s has not been set.', + 'META_FIELD_INVALID' => 'Meta field %s is invalid.', +)); diff --git a/language/en/acp/forums.php b/language/en/acp/forums.php new file mode 100644 index 0000000..7a71763 --- /dev/null +++ b/language/en/acp/forums.php @@ -0,0 +1,165 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Forum Admin +$lang = array_merge($lang, array( + 'AUTO_PRUNE_DAYS' => 'Auto-prune post age', + 'AUTO_PRUNE_DAYS_EXPLAIN' => 'Number of days since last post after which topic is removed.', + 'AUTO_PRUNE_FREQ' => 'Auto-prune frequency', + 'AUTO_PRUNE_FREQ_EXPLAIN' => 'Time in days between pruning events.', + 'AUTO_PRUNE_VIEWED' => 'Auto-prune post viewed age', + 'AUTO_PRUNE_VIEWED_EXPLAIN' => 'Number of days since topic was viewed after which topic is removed.', + 'AUTO_PRUNE_SHADOW_FREQ' => 'Auto-prune shadow topics frequency', + 'AUTO_PRUNE_SHADOW_DAYS' => 'Auto-prune shadow topics age', + 'AUTO_PRUNE_SHADOW_DAYS_EXPLAIN' => 'Number of days after which shadow topic is removed.', + 'AUTO_PRUNE_SHADOW_FREQ_EXPLAIN' => 'Time in days between pruning events.', + + 'CONTINUE' => 'Continue', + 'COPY_PERMISSIONS' => 'Copy permissions from', + 'COPY_PERMISSIONS_EXPLAIN' => 'To ease up the permission setup for your new forum, you can copy the permissions of an existing forum.', + 'COPY_PERMISSIONS_ADD_EXPLAIN' => 'Once created, the forum will have the same permissions as the one you select here. If no forum is selected the newly created forum will not be visible until permissions had been set.', + 'COPY_PERMISSIONS_EDIT_EXPLAIN' => 'If you select to copy permissions, the forum will have the same permissions as the one you select here. This will overwrite any permissions you have previously set for this forum with the permissions of the forum you select here. If no forum is selected the current permissions will be kept.', + 'COPY_TO_ACL' => 'Alternatively, you are also able to %sset up new permissions%s for this forum.', + 'CREATE_FORUM' => 'Create new forum', + + 'DECIDE_MOVE_DELETE_CONTENT' => 'Delete content or move to forum', + 'DECIDE_MOVE_DELETE_SUBFORUMS' => 'Delete subforums or move to forum', + 'DEFAULT_STYLE' => 'Default style', + 'DELETE_ALL_POSTS' => 'Delete posts', + 'DELETE_SUBFORUMS' => 'Delete subforums and posts', + 'DISPLAY_ACTIVE_TOPICS' => 'Enable active topics', + 'DISPLAY_ACTIVE_TOPICS_EXPLAIN' => 'If set to yes active topics in selected subforums will be displayed under this category.', + + 'EDIT_FORUM' => 'Edit forum', + 'ENABLE_INDEXING' => 'Enable search indexing', + 'ENABLE_INDEXING_EXPLAIN' => 'If set to yes posts made to this forum will be indexed for searching.', + 'ENABLE_POST_REVIEW' => 'Enable post review', + 'ENABLE_POST_REVIEW_EXPLAIN' => 'If set to yes users are able to review their post if new posts were made to the topic while users wrote theirs. This should be disabled for chat forums.', + 'ENABLE_QUICK_REPLY' => 'Enable quick reply', + 'ENABLE_QUICK_REPLY_EXPLAIN' => 'Enables the quick reply in this forum. This setting is not considered if the quick reply is disabled board wide. The quick reply will only be displayed for users who have permission to post in this forum.', + 'ENABLE_RECENT' => 'Display active topics', + 'ENABLE_RECENT_EXPLAIN' => 'If set to yes topics made to this forum will be shown in the active topics list.', + 'ENABLE_TOPIC_ICONS' => 'Enable topic icons', + + 'FORUM_ADMIN' => 'Forum administration', + 'FORUM_ADMIN_EXPLAIN' => 'In phpBB3 everything is forum based. A category is just a special type of forum. Each forum can have an unlimited number of sub-forums and you can determine whether each may be posted to or not (i.e. whether it acts like an old category). Here you can add, edit, delete, lock, unlock individual forums as well as set certain additional controls. If your posts and topics have got out of sync you can also resynchronise a forum. You need to copy or set appropriate permissions for newly created forums to have them displayed.', + 'FORUM_AUTO_PRUNE' => 'Enable auto-pruning', + 'FORUM_AUTO_PRUNE_EXPLAIN' => 'Prunes the forum of topics, set the frequency/age parameters below.', + 'FORUM_CREATED' => 'Forum created successfully.', + 'FORUM_DATA_NEGATIVE' => 'Pruning parameters cannot be negative.', + 'FORUM_DESC_TOO_LONG' => 'The forum description is too long, it must be less than 4000 characters.', + 'FORUM_DELETE' => 'Delete forum', + 'FORUM_DELETE_EXPLAIN' => 'The form below will allow you to delete a forum. If the forum is postable you are able to decide where you want to put all topics (or forums) it contained.', + 'FORUM_DELETED' => 'Forum successfully deleted.', + 'FORUM_DESC' => 'Description', + 'FORUM_DESC_EXPLAIN' => 'Any HTML markup entered here will be displayed as is. If the selected forum type is a category the description is not used.', + 'FORUM_EDIT_EXPLAIN' => 'The form below will allow you to customise this forum. Please note that moderation and post count controls are set via forum permissions for each user or usergroup.', + 'FORUM_IMAGE' => 'Forum image', + 'FORUM_IMAGE_EXPLAIN' => 'Location, relative to the phpBB root directory, of an additional image to associate with this forum.', + 'FORUM_IMAGE_NO_EXIST' => 'The specified forum image does not exist', + 'FORUM_LINK_EXPLAIN' => 'Full URL (including the protocol, i.e.: http://) to the destination location that clicking this forum will take the user, e.g.: http://www.phpbb.com/.', + 'FORUM_LINK_TRACK' => 'Track link redirects', + 'FORUM_LINK_TRACK_EXPLAIN' => 'Records the number of times a forum link was clicked.', + 'FORUM_NAME' => 'Forum name', + 'FORUM_NAME_EMPTY' => 'You must enter a name for this forum.', + 'FORUM_PARENT' => 'Parent forum', + 'FORUM_PASSWORD' => 'Forum password', + 'FORUM_PASSWORD_CONFIRM' => 'Confirm forum password', + 'FORUM_PASSWORD_CONFIRM_EXPLAIN' => 'Only needs to be set if a forum password is entered.', + 'FORUM_PASSWORD_EXPLAIN' => 'Defines a password for this forum, use the permission system in preference.', + 'FORUM_PASSWORD_UNSET' => 'Remove forum password', + 'FORUM_PASSWORD_UNSET_EXPLAIN' => 'Check here if you want to remove the forum password.', + 'FORUM_PASSWORD_OLD' => 'The forum password is using an old hashing method and should be changed.', + 'FORUM_PASSWORD_MISMATCH' => 'The passwords you entered did not match.', + 'FORUM_PRUNE_SETTINGS' => 'Forum prune settings', + 'FORUM_PRUNE_SHADOW' => 'Enable auto-pruning of shadow topics', + 'FORUM_PRUNE_SHADOW_EXPLAIN' => 'Prunes the forum of shadow topics, set the frequency/age parameters below.', + 'FORUM_RESYNCED' => 'Forum “%s” successfully resynced', + 'FORUM_RULES_EXPLAIN' => 'Forum rules are displayed at any page within the given forum.', + 'FORUM_RULES_LINK' => 'Link to forum rules', + 'FORUM_RULES_LINK_EXPLAIN' => 'You are able to enter the URL of the page/post containing your forum rules here. This setting will override the forum rules text you specified.', + 'FORUM_RULES_PREVIEW' => 'Forum rules preview', + 'FORUM_RULES_TOO_LONG' => 'The forum rules must be less than 4000 characters.', + 'FORUM_SETTINGS' => 'Forum settings', + 'FORUM_STATUS' => 'Forum status', + 'FORUM_STYLE' => 'Forum style', + 'FORUM_TOPICS_PAGE' => 'Topics per page', + 'FORUM_TOPICS_PAGE_EXPLAIN' => 'If non-zero this value will override the default topics per page setting.', + 'FORUM_TYPE' => 'Forum type', + 'FORUM_UPDATED' => 'Forum information updated successfully.', + + 'FORUM_WITH_SUBFORUMS_NOT_TO_LINK' => 'You want to change a postable forum having subforums to a link. Please move all subforums out of this forum before you proceed, because after changing to a link you are no longer able to see the subforums currently connected to this forum.', + + 'GENERAL_FORUM_SETTINGS' => 'General forum settings', + + 'LINK' => 'Link', + 'LIST_INDEX' => 'List subforum in parent-forum’s legend', + 'LIST_INDEX_EXPLAIN' => 'Displays this forum on the index and elsewhere as a link within the legend of its parent-forum if the parent-forum’s “List subforums in legend” option is enabled.', + 'LIST_SUBFORUMS' => 'List subforums in legend', + 'LIST_SUBFORUMS_EXPLAIN' => 'Displays this forum’s subforums on the index and elsewhere as a link within the legend if their “List subforum in parent-forum’s legend” option is enabled.', + 'LOCKED' => 'Locked', + + 'MOVE_POSTS_NO_POSTABLE_FORUM' => 'The forum you selected for moving the posts to is not postable. Please select a postable forum.', + 'MOVE_POSTS_TO' => 'Move posts to', + 'MOVE_SUBFORUMS_TO' => 'Move subforums to', + + 'NO_DESTINATION_FORUM' => 'You have not specified a forum to move content to.', + 'NO_FORUM_ACTION' => 'No action defined for what happens with the forum content.', + 'NO_PARENT' => 'No parent', + 'NO_PERMISSIONS' => 'Do not copy permissions', + 'NO_PERMISSION_FORUM_ADD' => 'You do not have the necessary permissions to add forums.', + 'NO_PERMISSION_FORUM_DELETE' => 'You do not have the necessary permissions to delete forums.', + + 'PARENT_IS_LINK_FORUM' => 'The parent you specified is a forum link. Link forums are not able to hold other forums, please specify a category or forum as the parent forum.', + 'PARENT_NOT_EXIST' => 'Parent does not exist.', + 'PRUNE_ANNOUNCEMENTS' => 'Prune announcements', + 'PRUNE_STICKY' => 'Prune stickies', + 'PRUNE_OLD_POLLS' => 'Prune old polls', + 'PRUNE_OLD_POLLS_EXPLAIN' => 'Removes topics with polls not voted in for post age days.', + + 'REDIRECT_ACL' => 'Now you are able to %sset permissions%s for this forum.', + + 'SYNC_IN_PROGRESS' => 'Synchronizing forum', + 'SYNC_IN_PROGRESS_EXPLAIN' => 'Currently resyncing topic range %1$d/%2$d.', + + 'TYPE_CAT' => 'Category', + 'TYPE_FORUM' => 'Forum', + 'TYPE_LINK' => 'Link', + + 'UNLOCKED' => 'Unlocked', +)); diff --git a/language/en/acp/groups.php b/language/en/acp/groups.php new file mode 100644 index 0000000..5d68132 --- /dev/null +++ b/language/en/acp/groups.php @@ -0,0 +1,158 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACP_GROUPS_MANAGE_EXPLAIN' => 'From this panel you can administer all your usergroups. You can delete, create and edit existing groups. Furthermore, you may choose group leaders, toggle open/hidden/closed group status and set the group name and description.', + 'ADD_GROUP_CATEGORY' => 'Add category', + 'ADD_USERS' => 'Add users', + 'ADD_USERS_EXPLAIN' => 'Here you can add new users to the group. You may select whether this group becomes the new default for the selected users. Additionally you can define them as group leaders. Please enter each username on a separate line.', + + 'COPY_PERMISSIONS' => 'Copy permissions from', + 'COPY_PERMISSIONS_EXPLAIN' => 'Once created, the group will have the same permissions as the one you select here.', + 'CREATE_GROUP' => 'Create new group', + + 'GROUPS_NO_MEMBERS' => 'This group has no members', + 'GROUPS_NO_MODS' => 'No group leaders defined', + + 'GROUP_APPROVE' => 'Approve member', + 'GROUP_APPROVED' => 'Approved members', + 'GROUP_AVATAR' => 'Group avatar', + 'GROUP_AVATAR_EXPLAIN' => 'This image will be displayed in the Group Control Panel.', + 'GROUP_CATEGORY_NAME' => 'Category name', + 'GROUP_CLOSED' => 'Closed', + 'GROUP_COLOR' => 'Group colour', + 'GROUP_COLOR_EXPLAIN' => 'Defines the colour members’ usernames will appear in, leave blank for user default.', + 'GROUP_CONFIRM_ADD_USERS' => array( + 1 => 'Are you sure that you want to add the user %2$s to the group?', + 2 => 'Are you sure that you want to add the users %2$s to the group?', + ), + 'GROUP_CREATED' => 'Group has been created successfully.', + 'GROUP_DEFAULT' => 'Make group default for member', + 'GROUP_DEFS_UPDATED' => 'Default group set for all selected members.', + 'GROUP_DELETE' => 'Remove member from group', + 'GROUP_DELETED' => 'Group deleted and user default groups set successfully.', + 'GROUP_DEMOTE' => 'Demote group leader', + 'GROUP_DESC' => 'Group description', + 'GROUP_DETAILS' => 'Group details', + 'GROUP_EDIT_EXPLAIN' => 'Here you can edit an existing group. You can change its name, description and type (open, closed, etc.). You can also set certain group wide options such as colouration, rank, etc. Changes made here override users’ current settings. Please note that group members can override group-avatar settings, unless you set appropriate user permissions.', + 'GROUP_ERR_USERS_EXIST' => 'The specified users are already members of this group.', + 'GROUP_FOUNDER_MANAGE' => 'Founder manage only', + 'GROUP_FOUNDER_MANAGE_EXPLAIN' => 'Restrict management of this group to founders only. Users having group permissions are still able to see this group as well as this group’s members.', + 'GROUP_HIDDEN' => 'Hidden', + 'GROUP_LANG' => 'Group language', + 'GROUP_LEAD' => 'Group leaders', + 'GROUP_LEADERS_ADDED' => 'New leaders added to group successfully.', + 'GROUP_LEGEND' => 'Display group in legend', + 'GROUP_LIST' => 'Current members', + 'GROUP_LIST_EXPLAIN' => 'This is a complete list of all the current users with membership of this group. You can delete members (except in certain special groups) or add new ones as you see fit.', + 'GROUP_MEMBERS' => 'Group members', + 'GROUP_MEMBERS_EXPLAIN' => 'This is a complete listing of all the members of this usergroup. It includes separate sections for leaders, pending and existing members. From here you can manage all aspects of who has membership of this group and what their role is. To remove a leader but keep them in the group use Demote rather than delete. Similarly use Promote to make an existing member a leader.', + 'GROUP_MESSAGE_LIMIT' => 'Group private message limit per folder', + 'GROUP_MESSAGE_LIMIT_EXPLAIN' => 'This setting overrides the per-user folder message limit. The maximum for all groups of the user is used to determine the actual value.
Set this value to 0 to overwrite the setting for all users of this group with the board-wide setting.', + 'GROUP_MODS_ADDED' => 'New group leaders added successfully.', + 'GROUP_MODS_DEMOTED' => 'Group leaders demoted successfully.', + 'GROUP_MODS_PROMOTED' => 'Group members promoted successfully.', + 'GROUP_NAME' => 'Group name', + 'GROUP_NAME_TAKEN' => 'The group name you entered is already in use, please select an alternative.', + 'GROUP_OPEN' => 'Open', + 'GROUP_PENDING' => 'Pending members', + 'GROUP_MAX_RECIPIENTS' => 'Maximum number of allowed recipients per private message', + 'GROUP_MAX_RECIPIENTS_EXPLAIN' => 'The maximum number of allowed recipients in a private message. The maximum for all groups of the user is used to determine the actual value.
Set this value to 0 to overwrite the setting for all users of this group with the board-wide setting.', + 'GROUP_OPTIONS_SAVE' => 'Group wide options', + 'GROUP_PROMOTE' => 'Promote to group leader', + 'GROUP_RANK' => 'Group rank', + 'GROUP_RECEIVE_PM' => 'Group able to receive private messages', + 'GROUP_RECEIVE_PM_EXPLAIN' => 'Please note that hidden groups are not able to be messaged, regardless of this setting.', + 'GROUP_REQUEST' => 'Request', + 'GROUP_SETTINGS_SAVE' => 'Group wide settings', + 'GROUP_SKIP_AUTH' => 'Exempt group leader from permissions', + 'GROUP_SKIP_AUTH_EXPLAIN' => 'If enabled group leader no longer inherit permissions from the group.', + 'GROUP_SPECIAL' => 'Pre-defined', + 'GROUP_TEAMPAGE' => 'Display group on teampage', + 'GROUP_TYPE' => 'Group type', + 'GROUP_TYPE_EXPLAIN' => 'This determines which users can join or view this group.', + 'GROUP_UPDATED' => 'Group preferences updated successfully.', + + 'GROUP_USERS_ADDED' => 'New users added to group successfully.', + 'GROUP_USERS_EXIST' => 'The selected users are already members.', + 'GROUP_USERS_REMOVE' => 'Users removed from group and new defaults set successfully.', + 'GROUP_USERS_INVALID' => 'No users were added to the group as the following usernames do not exist: %s', + + 'LEGEND_EXPLAIN' => 'These are the groups which are displayed in the group legend:', + 'LEGEND_SETTINGS' => 'Legend settings', + 'LEGEND_SORT_GROUPNAME' => 'Sort legend by group name', + 'LEGEND_SORT_GROUPNAME_EXPLAIN' => 'The order below is ignored when this option is enabled.', + + 'MANAGE_LEGEND' => 'Manage group legend', + 'MANAGE_TEAMPAGE' => 'Manage teampage', + 'MAKE_DEFAULT_FOR_ALL' => 'Make default group for every member', + 'MEMBERS' => 'Members', + + 'NO_GROUP' => 'No group specified.', + 'NO_GROUPS_ADDED' => 'No groups added yet.', + 'NO_GROUPS_CREATED' => 'No groups created yet.', + 'NO_PERMISSIONS' => 'Do not copy permissions', + 'NO_USERS' => 'You haven’t entered any users.', + 'NO_USERS_ADDED' => 'No users were added to the group.', + 'NO_VALID_USERS' => 'You haven’t entered any users eligible for that action.', + + 'PENDING_MEMBERS' => 'Pending', + + 'SELECT_GROUP' => 'Select a group', + 'SPECIAL_GROUPS' => 'Pre-defined groups', + 'SPECIAL_GROUPS_EXPLAIN' => 'Pre-defined groups are special groups, they cannot be deleted or directly modified. However you can still add users and alter basic settings.', + + 'TEAMPAGE' => 'Teampage', + 'TEAMPAGE_DISP_ALL' => 'All memberships', + 'TEAMPAGE_DISP_DEFAULT' => 'User’s default group only', + 'TEAMPAGE_DISP_FIRST' => 'First membership only', + 'TEAMPAGE_EXPLAIN' => 'These are the groups which are displayed on the teampage:', + 'TEAMPAGE_FORUMS' => 'Display moderated forums', + 'TEAMPAGE_FORUMS_EXPLAIN' => 'If set to yes, moderators will have a list with all of the forums where they have moderator permissions displayed in their row. This can be very database intensive for big boards.', + 'TEAMPAGE_MEMBERSHIPS' => 'Display user memberships', + 'TEAMPAGE_SETTINGS' => 'Teampage settings', + 'TOTAL_MEMBERS' => 'Members', + + 'USERS_APPROVED' => 'Users approved successfully.', + 'USER_DEFAULT' => 'User default', + 'USER_DEF_GROUPS' => 'User defined groups', + 'USER_DEF_GROUPS_EXPLAIN' => 'These are groups created by you or another admin on this board. You can manage memberships as well as edit group properties or even delete the group.', + 'USER_GROUP_DEFAULT' => 'Set as default group', + 'USER_GROUP_DEFAULT_EXPLAIN' => 'Saying yes here will set this group as the default group for the added users.', + 'USER_GROUP_LEADER' => 'Set as group leader', +)); diff --git a/language/en/acp/index.htm b/language/en/acp/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/language/en/acp/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/language/en/acp/language.php b/language/en/acp/language.php new file mode 100644 index 0000000..d14491a --- /dev/null +++ b/language/en/acp/language.php @@ -0,0 +1,78 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACP_FILES' => 'Admin language files', + 'ACP_LANGUAGE_PACKS_EXPLAIN' => 'Here you are able to install/remove language packs. The default language pack is marked with an asterisk (*).', + + 'DELETE_LANGUAGE_CONFIRM' => 'Are you sure you wish to delete “%s”?', + + 'INSTALLED_LANGUAGE_PACKS' => 'Installed language packs', + + 'LANGUAGE_DETAILS_UPDATED' => 'Language details successfully updated.', + 'LANGUAGE_PACK_ALREADY_INSTALLED' => 'This language pack is already installed.', + 'LANGUAGE_PACK_DELETED' => 'The language pack “%s” has been removed successfully. All users using this language have been reset to the board’s default language.', + 'LANGUAGE_PACK_DETAILS' => 'Language pack details', + 'LANGUAGE_PACK_INSTALLED' => 'The language pack “%s” has been successfully installed.', + 'LANGUAGE_PACK_CPF_UPDATE' => 'The custom profile fields’ language strings were copied from the default language. Please change them if necessary.', + 'LANGUAGE_PACK_ISO' => 'ISO', + 'LANGUAGE_PACK_LOCALNAME' => 'Local name', + 'LANGUAGE_PACK_NAME' => 'Name', + 'LANGUAGE_PACK_NOT_EXIST' => 'The selected language pack does not exist.', + 'LANGUAGE_PACK_USED_BY' => 'Used by (including robots)', + 'LANGUAGE_VARIABLE' => 'Language variable', + 'LANG_AUTHOR' => 'Language pack author', + 'LANG_ENGLISH_NAME' => 'English name', + 'LANG_ISO_CODE' => 'ISO code', + 'LANG_LOCAL_NAME' => 'Local name', + + 'MISSING_LANG_FILES' => 'Missing language files', + 'MISSING_LANG_VARIABLES' => 'Missing language variables', + + 'NO_FILE_SELECTED' => 'You haven’t specified a language file.', + 'NO_LANG_ID' => 'You haven’t specified a language pack.', + 'NO_REMOVE_DEFAULT_LANG' => 'You are not able to remove the default language pack.
If you want to remove this language pack, change your board’s default language first.', + 'NO_UNINSTALLED_LANGUAGE_PACKS' => 'No uninstalled language packs', + + 'THOSE_MISSING_LANG_FILES' => 'The following language files are missing from the “%s” language folder', + 'THOSE_MISSING_LANG_VARIABLES' => 'The following language variables are missing from the “%s” language pack', + + 'UNINSTALLED_LANGUAGE_PACKS' => 'Uninstalled language packs', + + 'BROWSE_LANGUAGE_PACKS_DATABASE' => 'Browse language packs database', +)); diff --git a/language/en/acp/modules.php b/language/en/acp/modules.php new file mode 100644 index 0000000..1213641 --- /dev/null +++ b/language/en/acp/modules.php @@ -0,0 +1,83 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACP_MODULE_MANAGEMENT_EXPLAIN' => 'Here you are able to manage all kind of modules. Please note that the ACP has a three-level menu structure (Category -> Category -> Module) whereby the others having a two-level menu structure (Category -> Module) which must be kept. Please also be aware that you may lock out yourself if you disable or delete the modules responsible for the module management itself.', + 'ADD_MODULE' => 'Add module', + 'ADD_MODULE_CONFIRM' => 'Are you sure you want to add the selected module with the selected mode?', + 'ADD_MODULE_TITLE' => 'Add module', + + 'CANNOT_REMOVE_MODULE' => 'Unable to remove module, it has assigned children. Please remove or move all children before performing this action.', + 'CATEGORY' => 'Category', + 'CHOOSE_MODE' => 'Choose module mode', + 'CHOOSE_MODE_EXPLAIN' => 'Choose the modules mode being used.', + 'CHOOSE_MODULE' => 'Choose module', + 'CHOOSE_MODULE_EXPLAIN' => 'Choose the file being called by this module.', + 'CREATE_MODULE' => 'Create new module', + + 'DEACTIVATED_MODULE' => 'Deactivated module', + 'DELETE_MODULE' => 'Delete module', + 'DELETE_MODULE_CONFIRM' => 'Are you sure you want to remove this module?', + + 'EDIT_MODULE' => 'Edit module', + 'EDIT_MODULE_EXPLAIN' => 'Here you are able to enter module specific settings.', + + 'HIDDEN_MODULE' => 'Hidden module', + + 'MODULE' => 'Module', + 'MODULE_ADDED' => 'Module successfully added.', + 'MODULE_DELETED' => 'Module successfully removed.', + 'MODULE_DISPLAYED' => 'Module displayed', + 'MODULE_DISPLAYED_EXPLAIN' => 'If you do not wish to display this module, but want to use it, set this to no.', + 'MODULE_EDITED' => 'Module successfully edited.', + 'MODULE_ENABLED' => 'Module enabled', + 'MODULE_LANGNAME' => 'Module language name', + 'MODULE_LANGNAME_EXPLAIN' => 'Enter the displayed module name. Use language constant if name is served from language file.', + 'MODULE_TYPE' => 'Module type', + + 'NO_CATEGORY_TO_MODULE' => 'Unable to turn category into module. Please remove/move all children before performing this action.', + 'NO_MODULE' => 'No module found.', + 'NO_MODULE_ID' => 'No module id specified.', + 'NO_MODULE_LANGNAME' => 'No module language name specified.', + 'NO_PARENT' => 'No Parent', + + 'PARENT' => 'Parent', + 'PARENT_NO_EXIST' => 'Parent does not exist.', + + 'SELECT_MODULE' => 'Select a module', +)); diff --git a/language/en/acp/permissions.php b/language/en/acp/permissions.php new file mode 100644 index 0000000..54d968c --- /dev/null +++ b/language/en/acp/permissions.php @@ -0,0 +1,287 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACP_PERMISSIONS_EXPLAIN' => ' +

Permissions are highly granular and grouped into four major sections, which are:

+ +

Global Permissions

+

These are used to control access on a global level and apply to the entire bulletin board. They are further divided into User Permissions, Group Permissions, Administrators and Global Moderators.

+ +

Forum Based Permissions

+

These are used to control access on a per forum basis. They are further divided into Forum Permissions, Forum Moderators, User Forum Permissions and Group Forum Permissions.

+ +

Permission Roles

+

These are used to create different sets of permissions for the different permission types later being able to be assigned on a role-based basis. The default roles should cover the administration of bulletin boards large and small, though within each of the four divisions, you can add/edit/delete roles as you see fit.

+ +

Permission Masks

+

These are used to view the effective permissions assigned to Users, Moderators (Local and Global), Administrators or Forums.

+ +
+ +

For further information on setting up and managing permissions on your phpBB3 board, please see the section on Setting permissions of our Quick Start Guide.

+ ', + + 'ACL_NEVER' => 'Never', + 'ACL_SET' => 'Setting permissions', + 'ACL_SET_EXPLAIN' => 'Permissions are based on a simple YES/NO system. Setting an option to NEVER for a user or usergroup overrides any other value assigned to it. If you do not wish to assign a value for an option for this user or group select NO. If values are assigned for this option elsewhere they will be used in preference, else NEVER is assumed. All objects marked (with the checkbox in front of them) will copy the permission set you defined.', + 'ACL_SETTING' => 'Setting', + + 'ACL_TYPE_A_' => 'Administrative permissions', + 'ACL_TYPE_F_' => 'Forum permissions', + 'ACL_TYPE_M_' => 'Moderative permissions', + 'ACL_TYPE_U_' => 'User permissions', + + 'ACL_TYPE_GLOBAL_A_' => 'Administrative permissions', + 'ACL_TYPE_GLOBAL_U_' => 'User permissions', + 'ACL_TYPE_GLOBAL_M_' => 'Global Moderator permissions', + 'ACL_TYPE_LOCAL_M_' => 'Forum Moderator permissions', + 'ACL_TYPE_LOCAL_F_' => 'Forum permissions', + + 'ACL_NO' => 'No', + 'ACL_VIEW' => 'Viewing permissions', + 'ACL_VIEW_EXPLAIN' => 'Here you can see the effective permissions the user/group is having. A red square indicates that the user/group does not have the permission, a green square indicates that the user/group does have the permission.', + 'ACL_YES' => 'Yes', + + 'ACP_ADMINISTRATORS_EXPLAIN' => 'Here you can assign administrator permissions to users or groups. All users with administrator permissions can view the administration control panel.', + 'ACP_FORUM_MODERATORS_EXPLAIN' => 'Here you can assign users and groups as forum moderators. To assign users access to forums, to define global moderative permissions or administrators please use the appropriate page.', + 'ACP_FORUM_PERMISSIONS_EXPLAIN' => 'Here you can alter which users and groups can access which forums. To assign moderators or define administrators please use the appropriate page.', + 'ACP_FORUM_PERMISSIONS_COPY_EXPLAIN' => 'Here you can copy forum permissions from one forum to one or more other forums.', + 'ACP_GLOBAL_MODERATORS_EXPLAIN' => 'Here you can assign global moderator permissions to users or groups. These moderators are like ordinary moderators except they have access to every forum on your board.', + 'ACP_GROUPS_FORUM_PERMISSIONS_EXPLAIN' => 'Here you can assign forum permissions to groups.', + 'ACP_GROUPS_PERMISSIONS_EXPLAIN' => 'Here you can assign global permissions to groups - user permissions, global moderator permissions and administrator permissions. User permissions include capabilities such as the use of avatars, sending private messages, et cetera; global moderator permissions such as approving posts, manage topics, manage bans, et cetera and lastly administrator permissions such as altering permissions, define custom BBCodes, manage forums, et cetera. Individual user permissions should only be changed in rare occasions, the preferred method is putting users in groups and assigning the group permissions.', + 'ACP_ADMIN_ROLES_EXPLAIN' => 'Here you are able to manage the roles for administrative permissions. Roles are effective permissions, if you change a role the items having this role assigned will change its permissions too.', + 'ACP_FORUM_ROLES_EXPLAIN' => 'Here you are able to manage the roles for forum permissions. Roles are effective permissions, if you change a role the items having this role assigned will change its permissions too.', + 'ACP_MOD_ROLES_EXPLAIN' => 'Here you are able to manage the roles for moderative permissions. Roles are effective permissions, if you change a role the items having this role assigned will change its permissions too.', + 'ACP_USER_ROLES_EXPLAIN' => 'Here you are able to manage the roles for user permissions. Roles are effective permissions, if you change a role the items having this role assigned will change its permissions too.', + 'ACP_USERS_FORUM_PERMISSIONS_EXPLAIN' => 'Here you can assign forum permissions to users.', + 'ACP_USERS_PERMISSIONS_EXPLAIN' => 'Here you can assign global permissions to users - user permissions, global moderator permissions and administrator permissions. User permissions include capabilities such as the use of avatars, sending private messages, et cetera; global moderator permissions such as approving posts, manage topics, manage bans, et cetera and lastly administrator permissions such as altering permissions, define custom BBCodes, manage forums, et cetera. To alter these settings for large numbers of users the Group permissions system is the preferred method. User permissions should only be changed in rare occasions, the preferred method is putting users in groups and assigning the group permissions.', + 'ACP_VIEW_ADMIN_PERMISSIONS_EXPLAIN' => 'Here you can view the effective administrative permissions assigned to the selected users/groups.', + 'ACP_VIEW_GLOBAL_MOD_PERMISSIONS_EXPLAIN' => 'Here you can view the global moderative permissions assigned to the selected users/groups.', + 'ACP_VIEW_FORUM_PERMISSIONS_EXPLAIN' => 'Here you can view the forum permissions assigned to the selected users/groups and forums.', + 'ACP_VIEW_FORUM_MOD_PERMISSIONS_EXPLAIN' => 'Here you can view the forum moderator permissions assigned to the selected users/groups and forums.', + 'ACP_VIEW_USER_PERMISSIONS_EXPLAIN' => 'Here you can view the effective user permissions assigned to the selected users/groups.', + + 'ADD_GROUPS' => 'Add groups', + 'ADD_PERMISSIONS' => 'Add permissions', + 'ADD_USERS' => 'Add users', + 'ADVANCED_PERMISSIONS' => 'Advanced Permissions', + 'ALL_GROUPS' => 'Select all groups', + 'ALL_NEVER' => 'All NEVER', + 'ALL_NO' => 'All NO', + 'ALL_USERS' => 'Select all users', + 'ALL_YES' => 'All YES', + 'APPLY_ALL_PERMISSIONS' => 'Apply all permissions', + 'APPLY_PERMISSIONS' => 'Apply permissions', + 'APPLY_PERMISSIONS_EXPLAIN' => 'The permissions and role defined for this item will only be applied to this item and all checked items.', + 'AUTH_UPDATED' => 'Permissions have been updated.', + + 'COPY_PERMISSIONS_CONFIRM' => 'Are you sure you wish to carry out this operation? Please be aware that this will overwrite any existing permissions on the selected targets.', + 'COPY_PERMISSIONS_FORUM_FROM_EXPLAIN' => 'The source forum you want to copy permissions from.', + 'COPY_PERMISSIONS_FORUM_TO_EXPLAIN' => 'The destination forums you want the copied permissions applied to.', + 'COPY_PERMISSIONS_FROM' => 'Copy permissions from', + 'COPY_PERMISSIONS_TO' => 'Apply permissions to', + + 'CREATE_ROLE' => 'Create role', + 'CREATE_ROLE_FROM' => 'Use settings from…', + 'CUSTOM' => 'Custom…', + + 'DEFAULT' => 'Default', + 'DELETE_ROLE' => 'Delete role', + 'DELETE_ROLE_CONFIRM' => 'Are you sure you want to remove this role? Items having this role assigned will not lose their permission settings.', + 'DISPLAY_ROLE_ITEMS' => 'View items using this role', + + 'EDIT_PERMISSIONS' => 'Edit permissions', + 'EDIT_ROLE' => 'Edit role', + + 'GROUPS_NOT_ASSIGNED' => 'No group assigned to this role', + + 'LOOK_UP_GROUP' => 'Look up usergroup', + 'LOOK_UP_USER' => 'Look up user', + + 'MANAGE_GROUPS' => 'Manage groups', + 'MANAGE_USERS' => 'Manage users', + + 'NO_AUTH_SETTING_FOUND' => 'Permission settings not defined.', + 'NO_ROLE_ASSIGNED' => 'No role assigned…', + 'NO_ROLE_ASSIGNED_EXPLAIN' => 'Setting to this role does not change permissions on the right. If you want to unset/remove all permissions you should use the “All NO” link.', + 'NO_ROLE_AVAILABLE' => 'No role available', + 'NO_ROLE_NAME_SPECIFIED' => 'Please give the role a name.', + 'NO_ROLE_SELECTED' => 'Role could not be found.', + 'NO_USER_GROUP_SELECTED' => 'You haven’t selected any user or group.', + + 'ONLY_FORUM_DEFINED' => 'You only defined forums in your selection. Please also select at least one user or one group.', + + 'PERMISSION_APPLIED_TO_ALL' => 'Permissions and role will also be applied to all checked objects', + 'PLUS_SUBFORUMS' => '+Subforums', + + 'REMOVE_PERMISSIONS' => 'Remove permissions', + 'REMOVE_ROLE' => 'Remove role', + 'RESULTING_PERMISSION' => 'Resulting permission', + 'ROLE' => 'Role', + 'ROLE_ADD_SUCCESS' => 'Role successfully added.', + 'ROLE_ASSIGNED_TO' => 'Users/Groups assigned to %s', + 'ROLE_DELETED' => 'Role successfully removed.', + 'ROLE_DESCRIPTION' => 'Role description', + + 'ROLE_ADMIN_FORUM' => 'Forum Admin', + 'ROLE_ADMIN_FULL' => 'Full Admin', + 'ROLE_ADMIN_STANDARD' => 'Standard Admin', + 'ROLE_ADMIN_USERGROUP' => 'User and Groups Admin', + 'ROLE_FORUM_BOT' => 'Bot Access', + 'ROLE_FORUM_FULL' => 'Full Access', + 'ROLE_FORUM_LIMITED' => 'Limited Access', + 'ROLE_FORUM_LIMITED_POLLS' => 'Limited Access + Polls', + 'ROLE_FORUM_NOACCESS' => 'No Access', + 'ROLE_FORUM_ONQUEUE' => 'On Moderation Queue', + 'ROLE_FORUM_POLLS' => 'Standard Access + Polls', + 'ROLE_FORUM_READONLY' => 'Read Only Access', + 'ROLE_FORUM_STANDARD' => 'Standard Access', + 'ROLE_FORUM_NEW_MEMBER' => 'Newly Registered User Access', + 'ROLE_MOD_FULL' => 'Full Moderator', + 'ROLE_MOD_QUEUE' => 'Queue Moderator', + 'ROLE_MOD_SIMPLE' => 'Simple Moderator', + 'ROLE_MOD_STANDARD' => 'Standard Moderator', + 'ROLE_USER_FULL' => 'All Features', + 'ROLE_USER_LIMITED' => 'Limited Features', + 'ROLE_USER_NOAVATAR' => 'No Avatar', + 'ROLE_USER_NOPM' => 'No Private Messages', + 'ROLE_USER_STANDARD' => 'Standard Features', + 'ROLE_USER_NEW_MEMBER' => 'Newly Registered User Features', + + 'ROLE_DESCRIPTION_ADMIN_FORUM' => 'Can access the forum management and forum permission settings.', + 'ROLE_DESCRIPTION_ADMIN_FULL' => 'Has access to all administrative functions of this board.
Not recommended.', + 'ROLE_DESCRIPTION_ADMIN_STANDARD' => 'Has access to most administrative features but is not allowed to use server or system related tools.', + 'ROLE_DESCRIPTION_ADMIN_USERGROUP' => 'Can manage groups and users: Able to change permissions, settings, manage bans, and manage ranks.', + 'ROLE_DESCRIPTION_FORUM_BOT' => 'This role is recommended for bots and search spiders.', + 'ROLE_DESCRIPTION_FORUM_FULL' => 'Can use all forum features, including posting of announcements and stickies. Can also ignore the flood limit.
Not recommended for normal users.', + 'ROLE_DESCRIPTION_FORUM_LIMITED' => 'Can use some forum features, but cannot attach files or use post icons.', + 'ROLE_DESCRIPTION_FORUM_LIMITED_POLLS' => 'As per Limited Access but can also create polls.', + 'ROLE_DESCRIPTION_FORUM_NOACCESS' => 'Can neither see nor access the forum.', + 'ROLE_DESCRIPTION_FORUM_ONQUEUE' => 'Can use most forum features including attachments, but posts and topics need to be approved by a moderator.', + 'ROLE_DESCRIPTION_FORUM_POLLS' => 'Like Standard Access but can also create polls.', + 'ROLE_DESCRIPTION_FORUM_READONLY' => 'Can read the forum, but cannot create new topics or reply to posts.', + 'ROLE_DESCRIPTION_FORUM_STANDARD' => 'Can use most forum features including attachments and deleting own topics, but cannot lock own topics, and cannot create polls.', + 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER' => 'A role for members of the special newly registered users group; contains NEVER permissions to lock features for new users.', + 'ROLE_DESCRIPTION_MOD_FULL' => 'Can use all moderating features, including banning.', + 'ROLE_DESCRIPTION_MOD_QUEUE' => 'Can use the Moderation Queue to validate and edit posts, but nothing else.', + 'ROLE_DESCRIPTION_MOD_SIMPLE' => 'Can only use basic topic actions. Cannot send warnings or use moderation queue.', + 'ROLE_DESCRIPTION_MOD_STANDARD' => 'Can use most moderating tools, but cannot ban users or change the post author.', + 'ROLE_DESCRIPTION_USER_FULL' => 'Can use all available forum features for users, including changing the user name or ignoring the flood limit.
Not recommended.', + 'ROLE_DESCRIPTION_USER_LIMITED' => 'Can access some of the user features. Attachments, emails, or instant messages are not allowed.', + 'ROLE_DESCRIPTION_USER_NOAVATAR' => 'Has a limited feature set and is not allowed to use the Avatar feature.', + 'ROLE_DESCRIPTION_USER_NOPM' => 'Has a limited feature set, and is not allowed to use Private Messages.', + 'ROLE_DESCRIPTION_USER_STANDARD' => 'Can access most but not all user features. Cannot change user name or ignore the flood limit, for instance.', + 'ROLE_DESCRIPTION_USER_NEW_MEMBER' => 'A role for members of the special newly registered users group; contains NEVER permissions to lock features for new users.', + + 'ROLE_DESCRIPTION_EXPLAIN' => 'You are able to enter a short explanation of what the role is doing or for what it is meant for. The text you enter here will be displayed within the permissions screens too.', + 'ROLE_DESCRIPTION_LONG' => 'The role description is too long, please limit it to 4000 characters.', + 'ROLE_DETAILS' => 'Role details', + 'ROLE_EDIT_SUCCESS' => 'Role successfully edited.', + 'ROLE_NAME' => 'Role name', + 'ROLE_NAME_ALREADY_EXIST' => 'A role named %s already exist for the specified permission type.', + 'ROLE_NOT_ASSIGNED' => 'Role has not been assigned yet.', + + 'SELECTED_FORUM_NOT_EXIST' => 'The selected forum(s) do not exist.', + 'SELECTED_GROUP_NOT_EXIST' => 'The selected group(s) do not exist.', + 'SELECTED_USER_NOT_EXIST' => 'The selected user(s) do not exist.', + 'SELECT_FORUM_SUBFORUM_EXPLAIN' => 'The forum you select here will include all subforums into the selection.', + 'SELECT_ROLE' => 'Select role…', + 'SELECT_TYPE' => 'Select type', + 'SET_PERMISSIONS' => 'Set permissions', + 'SET_ROLE_PERMISSIONS' => 'Set role permissions', + 'SET_USERS_PERMISSIONS' => 'Set user permissions', + 'SET_USERS_FORUM_PERMISSIONS' => 'Set user forum permissions', + + 'TRACE_DEFAULT' => 'By default every permission is NO (unset). So the permission can be overwritten by other settings.', + 'TRACE_FOR' => 'Trace for', + 'TRACE_GLOBAL_SETTING' => '%s (global)', + 'TRACE_GROUP_NEVER_TOTAL_NEVER' => 'This group’s permission is set to NEVER like the total result so the old result is kept.', + 'TRACE_GROUP_NEVER_TOTAL_NEVER_LOCAL' => 'This group’s permission for this forum is set to NEVER like the total result so the old result is kept.', + 'TRACE_GROUP_NEVER_TOTAL_NO' => 'This group’s permission is set to NEVER which becomes the new total value because it wasn’t set yet (set to NO).', + 'TRACE_GROUP_NEVER_TOTAL_NO_LOCAL' => 'This group’s permission for this forum is set to NEVER which becomes the new total value because it wasn’t set yet (set to NO).', + 'TRACE_GROUP_NEVER_TOTAL_YES' => 'This group’s permission is set to NEVER which overwrites the total YES to a NEVER for this user.', + 'TRACE_GROUP_NEVER_TOTAL_YES_LOCAL' => 'This group’s permission for this forum is set to NEVER which overwrites the total YES to a NEVER for this user.', + 'TRACE_GROUP_NO' => 'The permission is NO for this group so the old total value is kept.', + 'TRACE_GROUP_NO_LOCAL' => 'The permission is NO for this group within this forum so the old total value is kept.', + 'TRACE_GROUP_YES_TOTAL_NEVER' => 'This group’s permission is set to YES but the total NEVER cannot be overwritten.', + 'TRACE_GROUP_YES_TOTAL_NEVER_LOCAL' => 'This group’s permission for this forum is set to YES but the total NEVER cannot be overwritten.', + 'TRACE_GROUP_YES_TOTAL_NO' => 'This group’s permission is set to YES which becomes the new total value because it wasn’t set yet (set to NO).', + 'TRACE_GROUP_YES_TOTAL_NO_LOCAL' => 'This group’s permission for this forum is set to YES which becomes the new total value because it wasn’t set yet (set to NO).', + 'TRACE_GROUP_YES_TOTAL_YES' => 'This group’s permission is set to YES and the total permission is already set to YES, so the total result is kept.', + 'TRACE_GROUP_YES_TOTAL_YES_LOCAL' => 'This group’s permission for this forum is set to YES and the total permission is already set to YES, so the total result is kept.', + 'TRACE_PERMISSION' => 'Trace permission - %s', + 'TRACE_RESULT' => 'Trace result', + 'TRACE_SETTING' => 'Trace setting', + + 'TRACE_USER_GLOBAL_YES_TOTAL_YES' => 'The forum independent user permission evaluates to YES but the total permission is already set to YES, so the total result is kept. %sTrace global permission%s', + 'TRACE_USER_GLOBAL_YES_TOTAL_NEVER' => 'The forum independent user permission evaluates to YES which overwrites the current local result NEVER. %sTrace global permission%s', + 'TRACE_USER_GLOBAL_NEVER_TOTAL_KEPT' => 'The forum independent user permission evaluates to NEVER which doesn’t influence the local permission. %sTrace global permission%s', + + 'TRACE_USER_FOUNDER' => 'The user is a founder, therefore admin permissions are always set to YES.', + 'TRACE_USER_KEPT' => 'The user’s permission is NO so the old total value is kept.', + 'TRACE_USER_KEPT_LOCAL' => 'The user’s permission for this forum is NO so the old total value is kept.', + 'TRACE_USER_NEVER_TOTAL_NEVER' => 'The user’s permission is set to NEVER and the total value is set to NEVER, so nothing is changed.', + 'TRACE_USER_NEVER_TOTAL_NEVER_LOCAL' => 'The user’s permission for this forum is set to NEVER and the total value is set to NEVER, so nothing is changed.', + 'TRACE_USER_NEVER_TOTAL_NO' => 'The user’s permission is set to NEVER which becomes the total value because it was set to NO.', + 'TRACE_USER_NEVER_TOTAL_NO_LOCAL' => 'The user’s permission for this forum is set to NEVER which becomes the total value because it was set to NO.', + 'TRACE_USER_NEVER_TOTAL_YES' => 'The user’s permission is set to NEVER and overwrites the previous YES.', + 'TRACE_USER_NEVER_TOTAL_YES_LOCAL' => 'The user’s permission for this forum is set to NEVER and overwrites the previous YES.', + 'TRACE_USER_NO_TOTAL_NO' => 'The user’s permission is NO and the total value was set to NO so it defaults to NEVER.', + 'TRACE_USER_NO_TOTAL_NO_LOCAL' => 'The user’s permission for this forum is NO and the total value was set to NO so it defaults to NEVER.', + 'TRACE_USER_YES_TOTAL_NEVER' => 'The user’s permission is set to YES but the total NEVER cannot be overwritten.', + 'TRACE_USER_YES_TOTAL_NEVER_LOCAL' => 'The user’s permission for this forum is set to YES but the total NEVER cannot be overwritten.', + 'TRACE_USER_YES_TOTAL_NO' => 'The user’s permission is set to YES which becomes the total value because it was set to NO.', + 'TRACE_USER_YES_TOTAL_NO_LOCAL' => 'The user’s permission for this forum is set to YES which becomes the total value because it was set to NO.', + 'TRACE_USER_YES_TOTAL_YES' => 'The user’s permission is set to YES and the total value is set to YES, so nothing is changed.', + 'TRACE_USER_YES_TOTAL_YES_LOCAL' => 'The user’s permission for this forum is set to YES and the total value is set to YES, so nothing is changed.', + 'TRACE_WHO' => 'Who', + 'TRACE_TOTAL' => 'Total', + + 'USERS_NOT_ASSIGNED' => 'No users are assigned to this role', + 'USER_IS_MEMBER_OF_DEFAULT' => 'is a member of the following pre-defined groups', + 'USER_IS_MEMBER_OF_CUSTOM' => 'is a member of the following user defined groups', + + 'VIEW_ASSIGNED_ITEMS' => 'View assigned items', + 'VIEW_LOCAL_PERMS' => 'Local permissions', + 'VIEW_GLOBAL_PERMS' => 'Global permissions', + 'VIEW_PERMISSIONS' => 'View permissions', + + 'WRONG_PERMISSION_TYPE' => 'Wrong permission type selected.', + 'WRONG_PERMISSION_SETTING_FORMAT' => 'The permission settings are in a wrong format, phpBB is not able to process them correctly.', +)); diff --git a/language/en/acp/permissions_phpbb.php b/language/en/acp/permissions_phpbb.php new file mode 100644 index 0000000..64740b3 --- /dev/null +++ b/language/en/acp/permissions_phpbb.php @@ -0,0 +1,214 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +/** +* EXTENSION-DEVELOPERS PLEASE NOTE +* +* You are able to put your permission sets into your extension. +* The permissions logic should be added via the 'core.permissions' event. +* You can easily add new permission categories, types and permissions, by +* simply merging them into the respective arrays. +* The respective language strings should be added into a language file, that +* start with 'permissions_', so they are automatically loaded within the ACP. +*/ + +$lang = array_merge($lang, array( + 'ACL_CAT_ACTIONS' => 'Actions', + 'ACL_CAT_CONTENT' => 'Content', + 'ACL_CAT_FORUMS' => 'Forums', + 'ACL_CAT_MISC' => 'Misc', + 'ACL_CAT_PERMISSIONS' => 'Permissions', + 'ACL_CAT_PM' => 'Private messages', + 'ACL_CAT_POLLS' => 'Polls', + 'ACL_CAT_POST' => 'Post', + 'ACL_CAT_POST_ACTIONS' => 'Post actions', + 'ACL_CAT_POSTING' => 'Posting', + 'ACL_CAT_PROFILE' => 'Profile', + 'ACL_CAT_SETTINGS' => 'Settings', + 'ACL_CAT_TOPIC_ACTIONS' => 'Topic actions', + 'ACL_CAT_USER_GROUP' => 'Users & Groups', +)); + +// User Permissions +$lang = array_merge($lang, array( + 'ACL_U_VIEWPROFILE' => 'Can view profiles, memberlist and online list', + 'ACL_U_CHGNAME' => 'Can change username', + 'ACL_U_CHGPASSWD' => 'Can change password', + 'ACL_U_CHGEMAIL' => 'Can change email address', + 'ACL_U_CHGAVATAR' => 'Can change avatar', + 'ACL_U_CHGGRP' => 'Can change default usergroup', + 'ACL_U_CHGPROFILEINFO' => 'Can change profile field information', + + 'ACL_U_ATTACH' => 'Can attach files', + 'ACL_U_DOWNLOAD' => 'Can download files', + 'ACL_U_SAVEDRAFTS' => 'Can save drafts', + 'ACL_U_CHGCENSORS' => 'Can disable word censors', + 'ACL_U_SIG' => 'Can use signature', + + 'ACL_U_SENDPM' => 'Can send private messages', + 'ACL_U_MASSPM' => 'Can send messages to multiple users', + 'ACL_U_MASSPM_GROUP'=> 'Can send messages to groups', + 'ACL_U_READPM' => 'Can read private messages', + 'ACL_U_PM_EDIT' => 'Can edit own private messages', + 'ACL_U_PM_DELETE' => 'Can remove private messages from own folder', + 'ACL_U_PM_FORWARD' => 'Can forward private messages', + 'ACL_U_PM_EMAILPM' => 'Can email private messages', + 'ACL_U_PM_PRINTPM' => 'Can print private messages', + 'ACL_U_PM_ATTACH' => 'Can attach files in private messages', + 'ACL_U_PM_DOWNLOAD' => 'Can download files in private messages', + 'ACL_U_PM_BBCODE' => 'Can use BBCode in private messages', + 'ACL_U_PM_SMILIES' => 'Can use smilies in private messages', + 'ACL_U_PM_IMG' => 'Can use [img] BBCode tag in private messages', + 'ACL_U_PM_FLASH' => 'Can use [flash] BBCode tag in private messages', + + 'ACL_U_SENDEMAIL' => 'Can send emails', + 'ACL_U_SENDIM' => 'Can send instant messages', + 'ACL_U_IGNOREFLOOD' => 'Can ignore flood limit', + 'ACL_U_HIDEONLINE' => 'Can hide online status', + 'ACL_U_VIEWONLINE' => 'Can view hidden online users', + 'ACL_U_SEARCH' => 'Can search board', +)); + +// Forum Permissions +$lang = array_merge($lang, array( + 'ACL_F_LIST' => 'Can see forum', + 'ACL_F_LIST_TOPICS' => 'Can see topics', + 'ACL_F_READ' => 'Can read forum', + 'ACL_F_SEARCH' => 'Can search the forum', + 'ACL_F_SUBSCRIBE' => 'Can subscribe forum', + 'ACL_F_PRINT' => 'Can print topics', + 'ACL_F_EMAIL' => 'Can email topics', + 'ACL_F_BUMP' => 'Can bump topics', + 'ACL_F_USER_LOCK' => 'Can lock own topics', + 'ACL_F_DOWNLOAD' => 'Can download files', + 'ACL_F_REPORT' => 'Can report posts', + + 'ACL_F_POST' => 'Can start new topics', + 'ACL_F_STICKY' => 'Can post stickies', + 'ACL_F_ANNOUNCE' => 'Can post announcements', + 'ACL_F_ANNOUNCE_GLOBAL' => 'Can post global announcements', + 'ACL_F_REPLY' => 'Can reply to topics', + 'ACL_F_EDIT' => 'Can edit own posts', + 'ACL_F_DELETE' => 'Can permanently delete own posts', + 'ACL_F_SOFTDELETE' => 'Can soft delete own posts
Moderators, who have the approve posts permission, can restore soft deleted posts.', + 'ACL_F_IGNOREFLOOD' => 'Can ignore flood limit', + 'ACL_F_POSTCOUNT' => 'Increment post counter
Please note that this setting only affects new posts.', + 'ACL_F_NOAPPROVE' => 'Can post without approval', + + 'ACL_F_ATTACH' => 'Can attach files', + 'ACL_F_ICONS' => 'Can use topic/post icons', + 'ACL_F_BBCODE' => 'Can use BBCode', + 'ACL_F_FLASH' => 'Can use [flash] BBCode tag', + 'ACL_F_IMG' => 'Can use [img] BBCode tag', + 'ACL_F_SIGS' => 'Can use signatures', + 'ACL_F_SMILIES' => 'Can use smilies', + + 'ACL_F_POLL' => 'Can create polls', + 'ACL_F_VOTE' => 'Can vote in polls', + 'ACL_F_VOTECHG' => 'Can change existing vote', +)); + +// Moderator Permissions +$lang = array_merge($lang, array( + 'ACL_M_EDIT' => 'Can edit posts', + 'ACL_M_DELETE' => 'Can permanently delete posts', + 'ACL_M_SOFTDELETE' => 'Can soft delete posts
Moderators, who have the approve posts permission, can restore soft deleted posts.', + 'ACL_M_APPROVE' => 'Can approve and restore posts', + 'ACL_M_REPORT' => 'Can close and delete reports', + 'ACL_M_CHGPOSTER' => 'Can change post author', + + 'ACL_M_MOVE' => 'Can move topics', + 'ACL_M_LOCK' => 'Can lock topics', + 'ACL_M_SPLIT' => 'Can split topics', + 'ACL_M_MERGE' => 'Can merge topics', + + 'ACL_M_INFO' => 'Can view post details', + 'ACL_M_WARN' => 'Can issue warnings
This setting is only assigned globally. It is not forum based.', // This moderator setting is only global (and not local) + 'ACL_M_PM_REPORT' => 'Can close and delete reports of private messages
This setting is only assigned globally. It is not forum based.', // This moderator setting is only global (and not local) + 'ACL_M_BAN' => 'Can manage bans
This setting is only assigned globally. It is not forum based.', // This moderator setting is only global (and not local) +)); + +// Admin Permissions +$lang = array_merge($lang, array( + 'ACL_A_BOARD' => 'Can alter board settings/check for updates', + 'ACL_A_SERVER' => 'Can alter server/communication settings', + 'ACL_A_JABBER' => 'Can alter Jabber settings', + 'ACL_A_PHPINFO' => 'Can view php settings', + + 'ACL_A_FORUM' => 'Can manage forums', + 'ACL_A_FORUMADD' => 'Can add new forums', + 'ACL_A_FORUMDEL' => 'Can delete forums', + 'ACL_A_PRUNE' => 'Can prune forums', + + 'ACL_A_ICONS' => 'Can alter topic/post icons and smilies', + 'ACL_A_WORDS' => 'Can alter word censors', + 'ACL_A_BBCODE' => 'Can define BBCode tags', + 'ACL_A_ATTACH' => 'Can alter attachment related settings', + + 'ACL_A_USER' => 'Can manage users
This also includes seeing the users browser agent within the viewonline list.', + 'ACL_A_USERDEL' => 'Can delete/prune users', + 'ACL_A_GROUP' => 'Can manage groups', + 'ACL_A_GROUPADD' => 'Can add new groups', + 'ACL_A_GROUPDEL' => 'Can delete groups', + 'ACL_A_RANKS' => 'Can manage ranks', + 'ACL_A_PROFILE' => 'Can manage custom profile fields', + 'ACL_A_NAMES' => 'Can manage disallowed names', + 'ACL_A_BAN' => 'Can manage bans', + + 'ACL_A_VIEWAUTH' => 'Can view permission masks', + 'ACL_A_AUTHGROUPS' => 'Can alter permissions for individual groups', + 'ACL_A_AUTHUSERS' => 'Can alter permissions for individual users', + 'ACL_A_FAUTH' => 'Can alter forum permission class', + 'ACL_A_MAUTH' => 'Can alter moderator permission class', + 'ACL_A_AAUTH' => 'Can alter admin permission class', + 'ACL_A_UAUTH' => 'Can alter user permission class', + 'ACL_A_ROLES' => 'Can manage roles', + 'ACL_A_SWITCHPERM' => 'Can use others permissions', + + 'ACL_A_STYLES' => 'Can manage styles', + 'ACL_A_EXTENSIONS' => 'Can manage extensions', + 'ACL_A_VIEWLOGS' => 'Can view logs', + 'ACL_A_CLEARLOGS' => 'Can clear logs', + 'ACL_A_MODULES' => 'Can manage modules', + 'ACL_A_LANGUAGE' => 'Can manage language packs', + 'ACL_A_EMAIL' => 'Can send mass email', + 'ACL_A_BOTS' => 'Can manage bots', + 'ACL_A_REASONS' => 'Can manage report/denial reasons', + 'ACL_A_BACKUP' => 'Can backup/restore database', + 'ACL_A_SEARCH' => 'Can manage search backends and settings', +)); diff --git a/language/en/acp/posting.php b/language/en/acp/posting.php new file mode 100644 index 0000000..119ad2d --- /dev/null +++ b/language/en/acp/posting.php @@ -0,0 +1,291 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// BBCodes +// Note to translators: you can translate everything but what's between { and } +$lang = array_merge($lang, array( + 'ACP_BBCODES_EXPLAIN' => 'BBCode is a special implementation of HTML offering greater control over what and how something is displayed. From this page you can add, remove and edit custom BBCodes.', + 'ADD_BBCODE' => 'Add a new BBCode', + + 'BBCODE_DANGER' => 'The BBCode you are trying to add seems to use a {TEXT} token inside a HTML attribute. This is a possible XSS security issue. Try using the more restrictive {SIMPLETEXT} or {INTTEXT} types instead. Only proceed if you understand the risks involved and you consider the use of {TEXT} absolutely unavoidable.', + 'BBCODE_DANGER_PROCEED' => 'Proceed', //'I understand the risk', + + 'BBCODE_ADDED' => 'BBCode added successfully.', + 'BBCODE_EDITED' => 'BBCode edited successfully.', + 'BBCODE_DELETED' => 'The BBCode has been removed successfully.', + 'BBCODE_NOT_EXIST' => 'The BBCode you selected does not exist.', + 'BBCODE_HELPLINE' => 'Help line', + 'BBCODE_HELPLINE_EXPLAIN' => 'This field contains the mouse over text of the BBCode.', + 'BBCODE_HELPLINE_TEXT' => 'Help line text', + 'BBCODE_HELPLINE_TOO_LONG' => 'The help line you entered is too long.', + + 'BBCODE_INVALID_TAG_NAME' => 'The BBCode tag name that you selected already exists.', + 'BBCODE_INVALID' => 'Your BBCode is constructed in an invalid form.', + 'BBCODE_OPEN_ENDED_TAG' => 'Your custom BBCode must contain both an opening and a closing tag.', + 'BBCODE_TAG' => 'Tag', + 'BBCODE_TAG_TOO_LONG' => 'The tag name you selected is too long.', + 'BBCODE_TAG_DEF_TOO_LONG' => 'The tag definition that you have entered is too long, please shorten your tag definition.', + 'BBCODE_USAGE' => 'BBCode usage', + 'BBCODE_USAGE_EXAMPLE' => '[highlight={COLOR}]{TEXT}[/highlight]

[font={SIMPLETEXT1}]{SIMPLETEXT2}[/font]', + 'BBCODE_USAGE_EXPLAIN' => 'Here you define how to use the BBCode. Replace any variable input by the corresponding token (%ssee below%s).', + + 'EXAMPLE' => 'Example:', + 'EXAMPLES' => 'Examples:', + + 'HTML_REPLACEMENT' => 'HTML replacement', + 'HTML_REPLACEMENT_EXAMPLE' => '<span style="background-color: {COLOR};">{TEXT}</span>

<span style="font-family: {SIMPLETEXT1};">{SIMPLETEXT2}</span>', + 'HTML_REPLACEMENT_EXPLAIN' => 'Here you define the default HTML replacement. Do not forget to put back tokens you used above!', + + 'TOKEN' => 'Token', + 'TOKENS' => 'Tokens', + 'TOKENS_EXPLAIN' => 'Tokens are placeholders for user input. The input will be validated only if it matches the corresponding definition. If needed, you can number them by adding a number as the last character between the braces, e.g. {TEXT1}, {TEXT2}.

Within the HTML replacement you can also use any language string present in your language/ directory like this: {L_<STRINGNAME>} where <STRINGNAME> is the name of the translated string you want to add. For example, {L_WROTE} will be displayed as “wrote” or its translation according to user’s locale.

Please note that only tokens listed below are able to be used within custom BBCodes.', + 'TOKEN_DEFINITION' => 'What can it be?', + 'TOO_MANY_BBCODES' => 'You cannot create any more BBCodes. Please remove one or more BBCodes then try again.', + + 'tokens' => array( + 'TEXT' => 'Any text, including foreign characters, numbers, etc… You should not use this token in HTML tags. Instead try to use IDENTIFIER, INTTEXT or SIMPLETEXT.', + 'SIMPLETEXT' => 'Characters from the latin alphabet (A-Z), numbers, spaces, commas, dots, minus, plus, hyphen and underscore', + 'INTTEXT' => 'Unicode letter characters, numbers, spaces, commas, dots, minus, plus, hyphen, underscore and whitespaces.', + 'IDENTIFIER' => 'Characters from the latin alphabet (A-Z), numbers, hyphen and underscore', + 'NUMBER' => 'Any series of digits', + 'EMAIL' => 'A valid email address', + 'URL' => 'A valid URL using any protocol (http, ftp, etc… cannot be used for javascript exploits). If none is given, “http://” is prefixed to the string.', + 'LOCAL_URL' => 'A local URL. The URL must be relative to the topic page and cannot contain a server name or protocol, as links are prefixed with “%s”', + 'RELATIVE_URL' => 'A relative URL. You can use this to match parts of a URL, but be careful: a full URL is a valid relative URL. When you want to use relative URLs of your board, use the LOCAL_URL token.', + 'COLOR' => 'A HTML colour, can be either in the numeric form #FF1234 or a CSS colour keyword such as fuchsia or InactiveBorder', + ), +)); + +// Smilies and topic icons +$lang = array_merge($lang, array( + 'ACP_ICONS_EXPLAIN' => 'From this page you can add, remove and edit the icons users may add to their topics or posts. These icons are generally displayed next to topic titles on the forum listing, or the post subjects in topic listings. You can also install and create new packages of icons.', + 'ACP_SMILIES_EXPLAIN' => 'Smilies or emoticons are typically small, sometimes animated images used to convey an emotion or feeling. From this page you can add, remove and edit the emoticons users can use in their posts and private messages. You can also install and create new packages of smilies.', + 'ADD_SMILIES' => 'Add multiple smilies', + 'ADD_SMILEY_CODE' => 'Add additional smiley code', + 'ADD_ICONS' => 'Add multiple icons', + 'AFTER_ICONS' => 'After %s', + 'AFTER_SMILIES' => 'After %s', + + 'CODE' => 'Code', + 'CURRENT_ICONS' => 'Current icons', + 'CURRENT_ICONS_EXPLAIN' => 'Choose what to do with the currently installed icons.', + 'CURRENT_SMILIES' => 'Current smilies', + 'CURRENT_SMILIES_EXPLAIN' => 'Choose what to do with the currently installed smilies.', + + 'DISPLAY_ON_POSTING' => 'Display on posting page', + 'DISPLAY_POSTING' => 'On posting page', + 'DISPLAY_POSTING_NO' => 'Not on posting page', + + 'EDIT_ICONS' => 'Edit icons', + 'EDIT_SMILIES' => 'Edit smilies', + 'EMOTION' => 'Emotion', + 'EXPORT_ICONS' => 'Export and download icons.pak', + 'EXPORT_ICONS_EXPLAIN' => '%sOn clicking this link, the configuration for your installed icons will be packaged into icons.pak which once downloaded can be used to create a .zip or .tgz file containing all of your icons plus this icons.pak configuration file%s.', + 'EXPORT_SMILIES' => 'Export and download smilies.pak', + 'EXPORT_SMILIES_EXPLAIN' => '%sOn clicking this link, the configuration for your installed smilies will be packaged into smilies.pak which once downloaded can be used to create a .zip or .tgz file containing all of your smilies plus this smilies.pak configuration file%s.', + + 'FIRST' => 'First', + + 'ICONS_ADD' => 'Add a new icon', + 'ICONS_ADDED' => array( + 0 => 'No icons were added.', + 1 => 'The icon has been added successfully.', + 2 => 'The icons have been added successfully.', + ), + 'ICONS_CONFIG' => 'Icon configuration', + 'ICONS_DELETED' => 'The icon has been removed successfully.', + 'ICONS_EDIT' => 'Edit icon', + 'ICONS_EDITED' => array( + 0 => 'No icons were updated.', + 1 => 'The icon has been updated successfully.', + 2 => 'The icons have been updated successfully.', + ), + 'ICONS_HEIGHT' => 'Icon height', + 'ICONS_IMAGE' => 'Icon image', + 'ICONS_IMPORTED' => 'The icons pack has been installed successfully.', + 'ICONS_IMPORT_SUCCESS' => 'The icons pack was imported successfully.', + 'ICONS_LOCATION' => 'Icon location', + 'ICONS_NOT_DISPLAYED' => 'The following icons are not displayed on the posting page', + 'ICONS_ORDER' => 'Icon order', + 'ICONS_URL' => 'Icon image file', + 'ICONS_WIDTH' => 'Icon width', + 'IMPORT_ICONS' => 'Install icons package', + 'IMPORT_SMILIES' => 'Install smilies package', + + 'KEEP_ALL' => 'Keep all', + + 'MASS_ADD_SMILIES' => 'Add multiple smilies', + + 'NO_ICONS_ADD' => 'There are no icons available for adding.', + 'NO_ICONS_EDIT' => 'There are no icons available for modifying.', + 'NO_ICONS_EXPORT' => 'You have no icons with which to create a package.', + 'NO_ICONS_PAK' => 'No icon packages found.', + 'NO_SMILIES_ADD' => 'There are no smilies available for adding.', + 'NO_SMILIES_EDIT' => 'There are no smilies available for modifying.', + 'NO_SMILIES_EXPORT' => 'You have no smilies with which to create a package.', + 'NO_SMILIES_PAK' => 'No smiley packages found.', + + 'PAK_FILE_NOT_READABLE' => 'Could not read .pak file.', + + 'REPLACE_MATCHES' => 'Replace matches', + + 'SELECT_PACKAGE' => 'Select a package file', + 'SMILIES_ADD' => 'Add a new smiley', + 'SMILIES_ADDED' => array( + 0 => 'No smilies were added.', + 1 => 'The smiley has been added successfully.', + 2 => 'The smilies have been added successfully.', + ), + 'SMILIES_CODE' => 'Smiley code', + 'SMILIES_CONFIG' => 'Smiley configuration', + 'SMILIES_DELETED' => 'The smiley has been removed successfully.', + 'SMILIES_EDIT' => 'Edit smiley', + 'SMILIE_NO_CODE' => 'The smiley “%s” was ignored, as there was no code entered.', + 'SMILIE_NO_EMOTION' => 'The smiley “%s” was ignored, as there was no emotion entered.', + 'SMILIE_NO_FILE' => 'The smiley “%s” was ignored, as the file is missing.', + 'SMILIES_EDITED' => array( + 0 => 'No smilies were updated.', + 1 => 'The smiley has been updated successfully.', + 2 => 'The smilies have been updated successfully.', + ), + 'SMILIES_EMOTION' => 'Emotion', + 'SMILIES_HEIGHT' => 'Smiley height', + 'SMILIES_IMAGE' => 'Smiley image', + 'SMILIES_IMPORTED' => 'The smilies pack has been installed successfully.', + 'SMILIES_IMPORT_SUCCESS' => 'The smilies pack was imported successfully.', + 'SMILIES_LOCATION' => 'Smiley location', + 'SMILIES_NOT_DISPLAYED' => 'The following smilies are not displayed on the posting page', + 'SMILIES_ORDER' => 'Smiley order', + 'SMILIES_URL' => 'Smiley image file', + 'SMILIES_WIDTH' => 'Smiley width', + + 'TOO_MANY_SMILIES' => array( + 1 => 'Limit of %d smiley reached.', + 2 => 'Limit of %d smilies reached.', + ), + + 'WRONG_PAK_TYPE' => 'The specified package does not contain the appropriate data.', +)); + +// Word censors +$lang = array_merge($lang, array( + 'ACP_WORDS_EXPLAIN' => 'From this control panel you can add, edit, and remove words that will be automatically censored on your forums. People are still allowed to register with usernames containing these words. Wildcards (*) are accepted in the word field, e.g. *test* will match detestable, test* would match testing, *test would match detest.', + 'ADD_WORD' => 'Add new word', + + 'EDIT_WORD' => 'Edit word censor', + 'ENTER_WORD' => 'You must enter a word and its replacement.', + + 'NO_WORD' => 'No word selected for editing.', + + 'REPLACEMENT' => 'Replacement', + + 'UPDATE_WORD' => 'Update word censor', + + 'WORD' => 'Word', + 'WORD_ADDED' => 'The word censor has been successfully added.', + 'WORD_REMOVED' => 'The selected word censor has been successfully removed.', + 'WORD_UPDATED' => 'The selected word censor has been successfully updated.', +)); + +// Ranks +$lang = array_merge($lang, array( + 'ACP_RANKS_EXPLAIN' => 'Using this form you can add, edit, view and delete ranks. You can also create special ranks which can be applied to a user via the user management facility.', + 'ADD_RANK' => 'Add new rank', + + 'MUST_SELECT_RANK' => 'You must select a rank.', + + 'NO_ASSIGNED_RANK' => 'No special rank assigned.', + 'NO_RANK_TITLE' => 'You haven’t specified a title for the rank.', + 'NO_UPDATE_RANKS' => 'The rank was successfully deleted. However user accounts using this rank were not updated. You will need to manually reset the rank on these accounts.', + + 'RANK_ADDED' => 'The rank was successfully added.', + 'RANK_IMAGE' => 'Rank image', + 'RANK_IMAGE_EXPLAIN' => 'Use this to define a small image associated with the rank. The path is relative to the root phpBB directory.', + 'RANK_IMAGE_IN_USE' => '(In use)', + 'RANK_MINIMUM' => 'Minimum posts', + 'RANK_REMOVED' => 'The rank was successfully deleted.', + 'RANK_SPECIAL' => 'Set as special rank', + 'RANK_TITLE' => 'Rank title', + 'RANK_UPDATED' => 'The rank was successfully updated.', +)); + +// Disallow Usernames +$lang = array_merge($lang, array( + 'ACP_DISALLOW_EXPLAIN' => 'Here you can control usernames which will not be allowed to be used. Disallowed usernames are allowed to contain a wildcard character of *.', + 'ADD_DISALLOW_EXPLAIN' => 'You can disallow a username using the wildcard character * to match any character.', + 'ADD_DISALLOW_TITLE' => 'Add a disallowed username', + + 'DELETE_DISALLOW_EXPLAIN' => 'You can remove a disallowed username by selecting the username from this list and clicking submit.', + 'DELETE_DISALLOW_TITLE' => 'Remove a disallowed username', + 'DISALLOWED_ALREADY' => 'The name you entered is already disallowed.', + 'DISALLOWED_DELETED' => 'The disallowed username has been successfully removed.', + 'DISALLOW_SUCCESSFUL' => 'The disallowed username has been successfully added.', + + 'NO_DISALLOWED' => 'No disallowed usernames', + 'NO_USERNAME_SPECIFIED' => 'You haven’t selected or entered a username to operate with.', +)); + +// Reasons +$lang = array_merge($lang, array( + 'ACP_REASONS_EXPLAIN' => 'Here you can manage the reasons used in reports and denial messages when disapproving posts. There is one default reason (marked with a *) you are not able to remove, this reason is normally used for custom messages if no reason fits.', + 'ADD_NEW_REASON' => 'Add new reason', + 'AVAILABLE_TITLES' => 'Available localised reason titles', + + 'IS_NOT_TRANSLATED' => 'Reason has not been localised.', + 'IS_NOT_TRANSLATED_EXPLAIN' => 'Reason has not been localised. If you want to provide the localised form, specify the correct key from the language files report reasons section.', + 'IS_TRANSLATED' => 'Reason has been localised.', + 'IS_TRANSLATED_EXPLAIN' => 'Reason has been localised. If the title you enter here is specified within the language files report reasons section, the localised form of the title and description will be used.', + + 'NO_REASON' => 'Reason could not be found.', + 'NO_REASON_INFO' => 'You have to specify a title and a description for this reason.', + 'NO_REMOVE_DEFAULT_REASON' => 'You are not able to remove the default reason “Other”.', + + 'REASON_ADD' => 'Add report/denial reason', + 'REASON_ADDED' => 'Report/denial reason successfully added.', + 'REASON_ALREADY_EXIST' => 'A reason with this title already exist, please enter another title for this reason.', + 'REASON_DESCRIPTION' => 'Reason description', + 'REASON_DESC_TRANSLATED' => 'Displayed reason description', + 'REASON_EDIT' => 'Edit report/denial reason', + 'REASON_EDIT_EXPLAIN' => 'Here you are able to add or edit a reason. If the reason is translated the localised version is used instead of the description entered here.', + 'REASON_REMOVED' => 'Report/denial reason successfully removed.', + 'REASON_TITLE' => 'Reason title', + 'REASON_TITLE_TRANSLATED' => 'Displayed reason title', + 'REASON_UPDATED' => 'Report/denial reason successfully updated.', + + 'USED_IN_REPORTS' => 'Used in reports', +)); diff --git a/language/en/acp/profile.php b/language/en/acp/profile.php new file mode 100644 index 0000000..41cbd9c --- /dev/null +++ b/language/en/acp/profile.php @@ -0,0 +1,176 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Custom profile fields +$lang = array_merge($lang, array( + 'ADDED_PROFILE_FIELD' => 'Successfully added custom profile field.', + 'ALPHA_DOTS' => 'Alphanumeric and dots (periods)', + 'ALPHA_ONLY' => 'Alphanumeric only', + 'ALPHA_SPACERS' => 'Alphanumeric and spacers', + 'ALPHA_UNDERSCORE' => 'Alphanumeric and underscores', + 'ALPHA_PUNCTUATION' => 'Alphanumeric with comma, dots, underscore and dashes beginning with a letter', + 'ALWAYS_TODAY' => 'Always the current date', + + 'BOOL_ENTRIES_EXPLAIN' => 'Enter your options now', + 'BOOL_TYPE_EXPLAIN' => 'Define the type, either a checkbox or radio buttons. A checkbox will only be displayed if it is checked for a given user. In that case the second language option will be used. Radio buttons will display regardless of their value.', + + 'CHANGED_PROFILE_FIELD' => 'Successfully changed profile field.', + 'CHARS_ANY' => 'Any character', + 'CHECKBOX' => 'Checkbox', + 'COLUMNS' => 'Columns', + 'CP_LANG_DEFAULT_VALUE' => 'Default value', + 'CP_LANG_EXPLAIN' => 'Field description', + 'CP_LANG_EXPLAIN_EXPLAIN' => 'The explanation for this field presented to the user.', + 'CP_LANG_NAME' => 'Field name/title presented to the user', + 'CP_LANG_OPTIONS' => 'Options', + 'CREATE_NEW_FIELD' => 'Create new field', + 'CUSTOM_FIELDS_NOT_TRANSLATED' => 'At least one custom profile field has not yet been translated. Please enter the required information by clicking on the “Translate” link.', + + 'DEFAULT_ISO_LANGUAGE' => 'Default language [%s]', + 'DEFAULT_LANGUAGE_NOT_FILLED' => 'The language entries for the default language are not filled for this profile field.', + 'DEFAULT_VALUE' => 'Default value', + 'DELETE_PROFILE_FIELD' => 'Remove profile field', + 'DELETE_PROFILE_FIELD_CONFIRM' => 'Are you sure you want to delete this profile field?', + 'DISPLAY_AT_PROFILE' => 'Display in user control panel', + 'DISPLAY_AT_PROFILE_EXPLAIN' => 'The user is able to change this profile field within the user control panel.', + 'DISPLAY_AT_REGISTER' => 'Display on registration screen', + 'DISPLAY_AT_REGISTER_EXPLAIN' => 'If this option is enabled, the field will be displayed on registration.', + 'DISPLAY_ON_MEMBERLIST' => 'Display on memberlist screen', + 'DISPLAY_ON_MEMBERLIST_EXPLAIN' => 'If this option is enabled, the field will be displayed in the user rows on the memberlist screen.', + 'DISPLAY_ON_PM' => 'Display on view private message screen', + 'DISPLAY_ON_PM_EXPLAIN' => 'If this option is enabled, the field will be displayed in the mini-profile on the private message screen.', + 'DISPLAY_ON_VT' => 'Display on viewtopic screen', + 'DISPLAY_ON_VT_EXPLAIN' => 'If this option is enabled, the field will be displayed in the mini-profile on the topic screen.', + 'DISPLAY_PROFILE_FIELD' => 'Publicly display profile field', + 'DISPLAY_PROFILE_FIELD_EXPLAIN' => 'The profile field will be shown in all locations allowed within the load settings. Setting this to “no” will hide the field from topic pages, profiles and the memberlist.', + 'DROPDOWN_ENTRIES_EXPLAIN' => 'Enter your options now, every option in one line.', + + 'EDIT_DROPDOWN_LANG_EXPLAIN' => 'Please note that you are able to change your options text and also able to add new options to the end. It is not advised to add new options between existing options - this could result in wrong options assigned to your users. This can also happen if you remove options in-between. Removing options from the end result in users having assigned this item now reverting back to the default one.', + 'EMPTY_FIELD_IDENT' => 'Empty field identification', + 'EMPTY_USER_FIELD_NAME' => 'Please enter a field name/title', + 'ENTRIES' => 'Entries', + 'EVERYTHING_OK' => 'Everything OK', + + 'FIELD_BOOL' => 'Boolean (Yes/No)', + 'FIELD_CONTACT_DESC' => 'Contact description', + 'FIELD_CONTACT_URL' => 'Contact link', + 'FIELD_DATE' => 'Date', + 'FIELD_DESCRIPTION' => 'Field description', + 'FIELD_DESCRIPTION_EXPLAIN' => 'The explanation for this field presented to the user.', + 'FIELD_DROPDOWN' => 'Dropdown box', + 'FIELD_GOOGLEPLUS' => 'Google+', + 'FIELD_IDENT' => 'Field identification', + 'FIELD_IDENT_ALREADY_EXIST' => 'The chosen field identification already exist. Please choose another name.', + 'FIELD_IDENT_EXPLAIN' => 'The field identification is a name to identify the profile field within the database and the templates.', + 'FIELD_INT' => 'Numbers', + 'FIELD_IS_CONTACT' => 'Display field as a contact field', + 'FIELD_IS_CONTACT_EXPLAIN' => 'Contact fields are displayed within the contact section of the user profile and are displayed differently in the mini profile next to posts and private messages. You can use %s as a placeholder variable which will be replaced by a value provided by the user.', + 'FIELD_LENGTH' => 'Length of input box', + 'FIELD_NOT_FOUND' => 'Profile field not found.', + 'FIELD_STRING' => 'Single text field', + 'FIELD_TEXT' => 'Textarea', + 'FIELD_TYPE' => 'Field type', + 'FIELD_TYPE_EXPLAIN' => 'You are not able to change the field type later.', + 'FIELD_URL' => 'URL (Link)', + 'FIELD_VALIDATION' => 'Field validation', + 'FIRST_OPTION' => 'First option', + + 'HIDE_PROFILE_FIELD' => 'Hide profile field', + 'HIDE_PROFILE_FIELD_EXPLAIN' => 'Hide the profile field from all other users except the user, administrators and moderators who are still able to see this field. If the Display in user control panel option is disabled, the user will not be able to see or change this field and the field can only be changed by administrators.', + + 'INVALID_CHARS_FIELD_IDENT' => 'Field identification can only contain lowercase a-z and _', + 'INVALID_FIELD_IDENT_LEN' => 'Field identification can only be 17 characters long', + 'ISO_LANGUAGE' => 'Language [%s]', + + 'LANG_SPECIFIC_OPTIONS' => 'Language specific options [%s]', + + 'LETTER_NUM_DOTS' => 'Any letters, numbers and dots (periods)', + 'LETTER_NUM_ONLY' => 'Any letters and numbers', + 'LETTER_NUM_PUNCTUATION' => 'Any letters, numbers, comma, dots, underscores and dashes beginning with any letter', + 'LETTER_NUM_SPACERS' => 'Any letters, numbers and spacers', + 'LETTER_NUM_UNDERSCORE' => 'Any letters, numbers and underscores', + + 'MAX_FIELD_CHARS' => 'Maximum number of characters', + 'MAX_FIELD_NUMBER' => 'Highest allowed number', + 'MIN_FIELD_CHARS' => 'Minimum number of characters', + 'MIN_FIELD_NUMBER' => 'Lowest allowed number', + + 'NO_FIELD_ENTRIES' => 'No entries defined', + 'NO_FIELD_ID' => 'No field id specified.', + 'NO_FIELD_TYPE' => 'No Field type specified.', + 'NO_VALUE_OPTION' => 'Option equal to non entered value', + 'NO_VALUE_OPTION_EXPLAIN' => 'Value for a non-entry. If the field is required, the user gets an error if he choose the option selected here.', + 'NUMBERS_ONLY' => 'Only numbers (0-9)', + + 'PROFILE_BASIC_OPTIONS' => 'Basic options', + 'PROFILE_FIELD_ACTIVATED' => 'Profile field successfully activated.', + 'PROFILE_FIELD_DEACTIVATED' => 'Profile field successfully deactivated.', + 'PROFILE_LANG_OPTIONS' => 'Language specific options', + 'PROFILE_TYPE_OPTIONS' => 'Profile type specific options', + + 'RADIO_BUTTONS' => 'Radio buttons', + 'REMOVED_PROFILE_FIELD' => 'Successfully removed profile field.', + 'REQUIRED_FIELD' => 'Required field', + 'REQUIRED_FIELD_EXPLAIN' => 'Force profile field to be filled out or specified by user or administrator. If display at registration screen option is disabled, the field will only be required when the user edits their profile.', + 'ROWS' => 'Rows', + + 'SAVE' => 'Save', + 'SECOND_OPTION' => 'Second option', + 'SHOW_NOVALUE_FIELD' => 'Show field if no value was selected', + 'SHOW_NOVALUE_FIELD_EXPLAIN' => 'Determines if the profile field should be displayed if no value was selected for optional fields or if no value has been selected yet for required fields.', + 'STEP_1_EXPLAIN_CREATE' => 'Here you can enter the first basic parameters of your new profile field. This information is needed for the second step where you’ll be able to set remaining options and tweak your profile field further.', + 'STEP_1_EXPLAIN_EDIT' => 'Here you can change the basic parameters of your profile field. The relevant options are re-calculated within the second step.', + 'STEP_1_TITLE_CREATE' => 'Add profile field', + 'STEP_1_TITLE_EDIT' => 'Edit profile field', + 'STEP_2_EXPLAIN_CREATE' => 'Here you are able to define some common options you may want to adjust.', + 'STEP_2_EXPLAIN_EDIT' => 'Here you are able to change some common options.
Please note that changes to profile fields will not affect existing profile fields entered by your users.', + 'STEP_2_TITLE_CREATE' => 'Profile type specific options', + 'STEP_2_TITLE_EDIT' => 'Profile type specific options', + 'STEP_3_EXPLAIN_CREATE' => 'Since you have more than one board language installed, you have to fill out the remaining language items too. If you don’t, then default language setting for this custom profile field will be used, you are able to fill out the remaining language items later too.', + 'STEP_3_EXPLAIN_EDIT' => 'Since you have more than one board language installed, you now can change or add the remaining language items too. If you don’t, then default language setting for this custom profile field will be used.', + 'STEP_3_TITLE_CREATE' => 'Remaining language definitions', + 'STEP_3_TITLE_EDIT' => 'Language definitions', + 'STRING_DEFAULT_VALUE_EXPLAIN' => 'Enter a default phrase to be displayed, a default value. Leave empty if you want to show it empty at the first place.', + + 'TEXT_DEFAULT_VALUE_EXPLAIN' => 'Enter a default text to be displayed, a default value. Leave empty if you want to show it empty at the first place.', + 'TRANSLATE' => 'Translate', + + 'USER_FIELD_NAME' => 'Field name/title presented to the user', + + 'VISIBILITY_OPTION' => 'Visibility options', +)); diff --git a/language/en/acp/prune.php b/language/en/acp/prune.php new file mode 100644 index 0000000..130d1db --- /dev/null +++ b/language/en/acp/prune.php @@ -0,0 +1,95 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// User pruning +$lang = array_merge($lang, array( + 'ACP_PRUNE_USERS_EXPLAIN' => 'This section allows you to delete or deactivate users on your board. Accounts can be filtered in a variety of ways; by post count, most recent activity, etc. Criteria may be combined to narrow down which accounts are affected. For example, you can prune users with fewer than 10 posts, who were also inactive after 2002-01-01. Use * as a wildcard for text fields. Alternatively, you may skip the criteria selection completely by entering a list of users (each on a separate line) into the text field. Take care with this facility! Once a user is deleted, there is no way to reverse the action.', + + 'CRITERIA' => 'Criteria', + + 'DEACTIVATE_DELETE' => 'Deactivate or delete', + 'DEACTIVATE_DELETE_EXPLAIN' => 'Choose whether to deactivate users or delete them entirely. Please note that deleted users cannot be restored!', + 'DELETE_USERS' => 'Delete', + 'DELETE_USER_POSTS' => 'Delete pruned user posts', + 'DELETE_USER_POSTS_EXPLAIN' => 'Removes posts made by deleted users, has no effect if users are deactivated.', + + 'JOINED_EXPLAIN' => 'Enter a date in YYYY-MM-DD format. You may use both fields to specify an interval, or leave one blank for an open date range.', + + 'LAST_ACTIVE_EXPLAIN' => 'Enter a date in YYYY-MM-DD format. Enter 0000-00-00 to prune users who never logged in, Before and After conditions will be ignored.', + + 'POSTS_ON_QUEUE' => 'Posts Awaiting Approval', + 'PRUNE_USERS_GROUP_EXPLAIN' => 'Limit to users within the selected group.', + 'PRUNE_USERS_GROUP_NONE' => 'All groups', + 'PRUNE_USERS_LIST' => 'Users to be pruned', + 'PRUNE_USERS_LIST_DELETE' => 'With the selected critera for pruning users the following accounts will be removed. You can remove individual users from the deletion list by unchecking the box next to their username.', + 'PRUNE_USERS_LIST_DEACTIVATE' => 'With the selected critera for pruning users the following accounts will be deactivated. You can remove individual users from the deactivation list by unchecking the box next to their username.', + + 'SELECT_USERS_EXPLAIN' => 'Enter specific usernames here. They will be used in preference to the criteria above. Founders cannot be pruned.', + + 'USER_DEACTIVATE_SUCCESS' => 'The selected users have been deactivated successfully.', + 'USER_DELETE_SUCCESS' => 'The selected users have been deleted successfully.', + 'USER_PRUNE_FAILURE' => 'No users fit the selected criteria.', + + 'WRONG_ACTIVE_JOINED_DATE' => 'The date entered is wrong, it is expected in YYYY-MM-DD format.', +)); + +// Forum Pruning +$lang = array_merge($lang, array( + 'ACP_PRUNE_FORUMS_EXPLAIN' => 'This will delete any topic which has not been posted to or viewed within the number of days you select. If you do not enter a number then all topics will be deleted. By default, it will not remove topics in which polls are still running nor will it remove stickies and announcements.', + + 'FORUM_PRUNE' => 'Forum prune', + + 'NO_PRUNE' => 'No forums pruned.', + + 'SELECTED_FORUM' => 'Selected forum', + 'SELECTED_FORUMS' => 'Selected forums', + + 'POSTS_PRUNED' => 'Posts pruned', + 'PRUNE_ANNOUNCEMENTS' => 'Prune announcements', + 'PRUNE_FINISHED_POLLS' => 'Prune closed polls', + 'PRUNE_FINISHED_POLLS_EXPLAIN' => 'Removes topics with polls which have ended.', + 'PRUNE_FORUM_CONFIRM' => 'Are you sure you want to prune the selected forums with the settings specified? Once removed, there is no way to recover the pruned posts and topics.', + 'PRUNE_NOT_POSTED' => 'Days since last posted', + 'PRUNE_NOT_VIEWED' => 'Days since last viewed', + 'PRUNE_OLD_POLLS' => 'Prune old polls', + 'PRUNE_OLD_POLLS_EXPLAIN' => 'Removes topics with polls not voted in for post age days.', + 'PRUNE_STICKY' => 'Prune stickies', + 'PRUNE_SUCCESS' => 'Pruning of forums was successful.', + + 'TOPICS_PRUNED' => 'Topics pruned', +)); diff --git a/language/en/acp/search.php b/language/en/acp/search.php new file mode 100644 index 0000000..443dbb7 --- /dev/null +++ b/language/en/acp/search.php @@ -0,0 +1,141 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACP_SEARCH_INDEX_EXPLAIN' => 'Here you can manage the search backend’s indexes. Since you normally use only one backend you should delete all indexes that you do not make use of. After altering some of the search settings (e.g. the number of minimum/maximum chars) it might be worth recreating the index so it reflects those changes.', + 'ACP_SEARCH_SETTINGS_EXPLAIN' => 'Here you can define what search backend will be used for indexing posts and performing searches. You can set various options that can influence how much processing these actions require. Some of these settings are the same for all search engine backends.', + + 'COMMON_WORD_THRESHOLD' => 'Common word threshold', + 'COMMON_WORD_THRESHOLD_EXPLAIN' => 'Words which are contained in a greater percentage of all posts will be regarded as common. Common words are ignored in search queries. Set to zero to disable. Only takes effect if there are more than 100 posts. If you want words that are currently regarded as common to be reconsidered you have to recreate the index.', + 'CONFIRM_SEARCH_BACKEND' => 'Are you sure you wish to switch to a different search backend? After changing the search backend you will have to create an index for the new search backend. If you don’t plan on switching back to the old search backend you can also delete the old backend’s index in order to free system resources.', + 'CONTINUE_DELETING_INDEX' => 'Continue previous index removal process', + 'CONTINUE_DELETING_INDEX_EXPLAIN' => 'An index removal process has been started. In order to access the search index page you will have to complete it or cancel it.', + 'CONTINUE_INDEXING' => 'Continue previous indexing process', + 'CONTINUE_INDEXING_EXPLAIN' => 'An indexing process has been started. In order to access the search index page you will have to complete it or cancel it.', + 'CREATE_INDEX' => 'Create index', + + 'DELETE_INDEX' => 'Delete index', + 'DELETING_INDEX_IN_PROGRESS' => 'Deleting the index in progress', + 'DELETING_INDEX_IN_PROGRESS_EXPLAIN' => 'The search backend is currently cleaning its index. This can take a few minutes.', + + 'FULLTEXT_MYSQL_INCOMPATIBLE_DATABASE' => 'The MySQL fulltext backend can only be used with MySQL4 and above.', + 'FULLTEXT_MYSQL_NOT_SUPPORTED' => 'MySQL fulltext indexes can only be used with MyISAM or InnoDB tables. MySQL 5.6.8 or later is required for fulltext indexes on InnoDB tables.', + 'FULLTEXT_MYSQL_TOTAL_POSTS' => 'Total number of indexed posts', + 'FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN' => 'Words with at least this many characters will be indexed for searching. You or your host can only change this setting by changing the mysql configuration.', + 'FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN' => 'Words with no more than this many characters will be indexed for searching. You or your host can only change this setting by changing the mysql configuration.', + + 'FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE' => 'The PostgreSQL fulltext backend can only be used with PostgreSQL.', + 'FULLTEXT_POSTGRES_TOTAL_POSTS' => 'Total number of indexed posts', + 'FULLTEXT_POSTGRES_VERSION_CHECK' => 'PostgreSQL version', + 'FULLTEXT_POSTGRES_TS_NAME' => 'Text search Configuration Profile:', + 'FULLTEXT_POSTGRES_MIN_WORD_LEN' => 'Minimum word length for keywords', + 'FULLTEXT_POSTGRES_MAX_WORD_LEN' => 'Maximum word length for keywords', + 'FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN' => 'This search backend requires PostgreSQL version 8.3 and above.', + 'FULLTEXT_POSTGRES_TS_NAME_EXPLAIN' => 'The Text search configuration profile used to determine the parser and dictionary.', + 'FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN' => 'Words with at least this many characters will be included in the query to the database.', + 'FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN' => 'Words with no more than this many characters will be included in the query to the database.', + + 'FULLTEXT_SPHINX_CONFIGURE' => 'Configure the following settings to generate sphinx config file', + 'FULLTEXT_SPHINX_DATA_PATH' => 'Path to data directory', + 'FULLTEXT_SPHINX_DATA_PATH_EXPLAIN' => 'It will be used to store the indexes and log files. You should create this directory outside the web accessible directories. (should have a trailing slash)', + 'FULLTEXT_SPHINX_DELTA_POSTS' => 'Number of posts in frequently updated delta index', + 'FULLTEXT_SPHINX_HOST' => 'Sphinx search daemon host', + 'FULLTEXT_SPHINX_HOST_EXPLAIN' => 'Host on which the sphinx search daemon (searchd) listens. Leave empty to use the default localhost', + 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT' => 'Indexer memory limit', + 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN' => 'This number should at all times be lower than the RAM available on your machine. If you experience periodic performance problems this might be due to the indexer consuming too many resources. It might help to lower the amount of memory available to the indexer.', + 'FULLTEXT_SPHINX_MAIN_POSTS' => 'Number of posts in main index', + 'FULLTEXT_SPHINX_PORT' => 'Sphinx search daemon port', + 'FULLTEXT_SPHINX_PORT_EXPLAIN' => 'Port on which the sphinx search daemon (searchd) listens. Leave empty to use the default Sphinx API port 9312', + 'FULLTEXT_SPHINX_WRONG_DATABASE' => 'The sphinx search for phpBB supports MySQL and PostgreSQL only.', + 'FULLTEXT_SPHINX_CONFIG_FILE' => 'Sphinx config file', + 'FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN' => 'The generated content of the sphinx config file. This data needs to be pasted into the sphinx.conf which is used by sphinx search daemon. Replace the [dbuser] and [dbpassword] placeholders with your database credentials.', + 'FULLTEXT_SPHINX_NO_CONFIG_DATA' => 'The sphinx data directory path is not defined. Please define the path and submit to generate the config file.', + + 'GENERAL_SEARCH_SETTINGS' => 'General search settings', + 'GO_TO_SEARCH_INDEX' => 'Go to search index page', + + 'INDEX_STATS' => 'Index statistics', + 'INDEXING_IN_PROGRESS' => 'Indexing in progress', + 'INDEXING_IN_PROGRESS_EXPLAIN' => 'The search backend is currently indexing all posts on the board. This can take from a few minutes to a few hours depending on your board’s size.', + + 'LIMIT_SEARCH_LOAD' => 'Search page system load limit', + 'LIMIT_SEARCH_LOAD_EXPLAIN' => 'If the 1 minute system load exceeds this value the search page will go offline, 1.0 equals ~100% utilisation of one processor. This only functions on UNIX based servers.', + + 'MAX_SEARCH_CHARS' => 'Max characters indexed by search', + 'MAX_SEARCH_CHARS_EXPLAIN' => 'Words with no more than this many characters will be indexed for searching.', + 'MAX_NUM_SEARCH_KEYWORDS' => 'Maximum number of allowed keywords', + 'MAX_NUM_SEARCH_KEYWORDS_EXPLAIN' => 'Maximum number of words the user is able to search for. A value of 0 allows an unlimited number of words.', + 'MIN_SEARCH_CHARS' => 'Min characters indexed by search', + 'MIN_SEARCH_CHARS_EXPLAIN' => 'Words with at least this many characters will be indexed for searching.', + 'MIN_SEARCH_AUTHOR_CHARS' => 'Min author name characters', + 'MIN_SEARCH_AUTHOR_CHARS_EXPLAIN' => 'Users have to enter at least this many characters of the name when performing a wildcard author search. If the author’s username is shorter than this number you can still search for the author’s posts by entering the complete username.', + + 'PROGRESS_BAR' => 'Progress bar', + + 'SEARCH_GUEST_INTERVAL' => 'Guest search flood interval', + 'SEARCH_GUEST_INTERVAL_EXPLAIN' => 'Number of seconds guests must wait between searches. If one guest searches all others have to wait until the time interval passed.', + 'SEARCH_INDEX_CREATE_REDIRECT' => array( + 2 => 'All posts up to post id %2$d have now been indexed, of which %1$d posts were within this step.
', + ), + 'SEARCH_INDEX_CREATE_REDIRECT_RATE' => array( + 2 => 'The current rate of indexing is approximately %1$.1f posts per second.
Indexing in progress…', + ), + 'SEARCH_INDEX_DELETE_REDIRECT' => array( + 2 => 'All posts up to post id %2$d have been removed from the search index, of which %1$d posts were within this step.
', + ), + 'SEARCH_INDEX_DELETE_REDIRECT_RATE' => array( + 2 => 'The current rate of deleting is approximately %1$.1f posts per second.
Deleting in progress…', + ), + 'SEARCH_INDEX_CREATED' => 'Successfully indexed all posts in the board database.', + 'SEARCH_INDEX_REMOVED' => 'Successfully deleted the search index for this backend.', + 'SEARCH_INTERVAL' => 'User search flood interval', + 'SEARCH_INTERVAL_EXPLAIN' => 'Number of seconds users must wait between searches. This interval is checked independently for each user.', + 'SEARCH_STORE_RESULTS' => 'Search result cache length', + 'SEARCH_STORE_RESULTS_EXPLAIN' => 'Cached search results will expire after this time, in seconds. Set to 0 if you want to disable search cache.', + 'SEARCH_TYPE' => 'Search backend', + 'SEARCH_TYPE_EXPLAIN' => 'phpBB allows you to choose the backend that is used for searching text in post contents. By default the search will use phpBB’s own fulltext search.', + 'SWITCHED_SEARCH_BACKEND' => 'You switched the search backend. In order to use the new search backend you should make sure that there is an index for the backend you chose.', + + 'TOTAL_WORDS' => 'Total number of indexed words', + 'TOTAL_MATCHES' => 'Total number of word to post relations indexed', + + 'YES_SEARCH' => 'Enable search facilities', + 'YES_SEARCH_EXPLAIN' => 'Enables user facing search functionality including member search.', + 'YES_SEARCH_UPDATE' => 'Enable fulltext updating', + 'YES_SEARCH_UPDATE_EXPLAIN' => 'Updating of fulltext indexes when posting, overridden if search is disabled.', +)); diff --git a/language/en/acp/styles.php b/language/en/acp/styles.php new file mode 100644 index 0000000..ab85d9d --- /dev/null +++ b/language/en/acp/styles.php @@ -0,0 +1,90 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACP_STYLES_EXPLAIN' => 'Here you can manage the available styles on your board. You may alter existing styles, delete, deactivate, reactivate, install new ones. You can also see what a style will look like using the preview function. Also listed is the total user count for each style, note that overriding user styles will not be reflected here.', + + 'CANNOT_BE_INSTALLED' => 'Cannot be installed', + 'CONFIRM_UNINSTALL_STYLES' => 'Are you sure you wish to uninstall selected styles?', + 'COPYRIGHT' => 'Copyright', + + 'DEACTIVATE_DEFAULT' => 'You cannot deactivate the default style.', + 'DELETE_FROM_FS' => 'Delete from filesystem', + 'DELETE_STYLE_FILES_FAILED' => 'Error deleting files for style "%s".', + 'DELETE_STYLE_FILES_SUCCESS' => 'Files for style "%s" have been deleted.', + 'DETAILS' => 'Details', + + 'INHERITING_FROM' => 'Inherits from', + 'INSTALL_STYLE' => 'Install style', + 'INSTALL_STYLES' => 'Install styles', + 'INSTALL_STYLES_EXPLAIN' => 'Here you can install new styles.
If you cannot find a specific style in list below, check to make sure style is already installed. If it is not installed, check if it was uploaded correctly.', + 'INVALID_STYLE_ID' => 'Invalid style ID.', + + 'NO_MATCHING_STYLES_FOUND' => 'No styles match your query.', + 'NO_UNINSTALLED_STYLE' => 'No uninstalled styles detected.', + + 'PURGED_CACHE' => 'Cache was purged.', + + 'REQUIRES_STYLE' => 'This style requires the style "%s" to be installed.', + + 'STYLE_ACTIVATE' => 'Activate', + 'STYLE_ACTIVE' => 'Active', + 'STYLE_DEACTIVATE' => 'Deactivate', + 'STYLE_DEFAULT' => 'Make default style', + 'STYLE_DEFAULT_CHANGE_INACTIVE' => 'You must activate style before making it default style.', + 'STYLE_ERR_INVALID_PARENT' => 'Invalid parent style.', + 'STYLE_ERR_NAME_EXIST' => 'A style with that name already exists.', + 'STYLE_ERR_STYLE_NAME' => 'You must supply a name for this style.', + 'STYLE_INSTALLED' => 'Style "%s" has been installed.', + 'STYLE_INSTALLED_RETURN_INSTALLED_STYLES' => 'Return to installed styles list', + 'STYLE_INSTALLED_RETURN_UNINSTALLED_STYLES' => 'Install more styles', + 'STYLE_NAME' => 'Style name', + 'STYLE_NAME_RESERVED' => 'Style "%s" can not be installed, because the name is reserved.', + 'STYLE_NOT_INSTALLED' => 'Style "%s" was not installed.', + 'STYLE_PATH' => 'Style path', + 'STYLE_UNINSTALL' => 'Uninstall', + 'STYLE_UNINSTALL_DEPENDENT' => 'Style "%s" cannot be uninstalled because it has one or more child styles.', + 'STYLE_UNINSTALLED' => 'Style "%s" uninstalled successfully.', + 'STYLE_PHPBB_VERSION' => 'phpBB Version', + 'STYLE_USED_BY' => 'Used by (including robots)', + 'STYLE_VERSION' => 'Style version', + + 'UNINSTALL_DEFAULT' => 'You cannot uninstall the default style.', + + 'BROWSE_STYLES_DATABASE' => 'Browse styles database', +)); diff --git a/language/en/acp/users.php b/language/en/acp/users.php new file mode 100644 index 0000000..980e73a --- /dev/null +++ b/language/en/acp/users.php @@ -0,0 +1,143 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ADMIN_SIG_PREVIEW' => 'Signature preview', + 'AT_LEAST_ONE_FOUNDER' => 'You are not able to change this founder to a normal user. There needs to be at least one founder enabled for this board. If you want to change this users founder status, promote another user to be a founder first.', + + 'BAN_ALREADY_ENTERED' => 'The ban had been previously entered successfully. The ban list has not been updated.', + 'BAN_SUCCESSFUL' => 'Ban entered successfully.', + + 'CANNOT_BAN_ANONYMOUS' => 'You are not allowed to ban the anonymous account. Permissions for anonymous users can be set under the Permissions tab.', + 'CANNOT_BAN_FOUNDER' => 'You are not allowed to ban founder accounts.', + 'CANNOT_BAN_YOURSELF' => 'You are not allowed to ban yourself.', + 'CANNOT_DEACTIVATE_BOT' => 'You are not allowed to deactivate bot accounts. Please deactivate the bot within the bots page instead.', + 'CANNOT_DEACTIVATE_FOUNDER' => 'You are not allowed to deactivate founder accounts.', + 'CANNOT_DEACTIVATE_YOURSELF' => 'You are not allowed to deactivate your own account.', + 'CANNOT_FORCE_REACT_BOT' => 'You are not allowed to force reactivation on bot accounts. Please reactivate the bot within the bots page instead.', + 'CANNOT_FORCE_REACT_FOUNDER' => 'You are not allowed to force reactivation on founder accounts.', + 'CANNOT_FORCE_REACT_YOURSELF' => 'You are not allowed to force reactivation of your own account.', + 'CANNOT_REMOVE_ANONYMOUS' => 'You are not able to remove the guest user account.', + 'CANNOT_REMOVE_FOUNDER' => 'You are not allowed to remove founder accounts.', + 'CANNOT_REMOVE_YOURSELF' => 'You are not allowed to remove your own user account.', + 'CANNOT_SET_FOUNDER_IGNORED' => 'You are not able to promote ignored users to be founders.', + 'CANNOT_SET_FOUNDER_INACTIVE' => 'You need to activate users before you promote them to founders, only activated users are able to be promoted.', + 'CONFIRM_EMAIL_EXPLAIN' => 'You only need to specify this if you are changing the users email address.', + + 'DELETE_POSTS' => 'Delete posts', + 'DELETE_USER' => 'Delete user', + 'DELETE_USER_EXPLAIN' => 'Please note that deleting a user is final, they cannot be recovered. Unread private messages sent by this user will be deleted and will not be available to their recipients.', + + 'FORCE_REACTIVATION_SUCCESS' => 'Successfully forced reactivation.', + 'FOUNDER' => 'Founder', + 'FOUNDER_EXPLAIN' => 'Founders have all administrative permissions and can never be banned, deleted or altered by non-founder members.', + + 'GROUP_APPROVE' => 'Approve member', + 'GROUP_DEFAULT' => 'Make group default for member', + 'GROUP_DELETE' => 'Remove member from group', + 'GROUP_DEMOTE' => 'Demote group leader', + 'GROUP_PROMOTE' => 'Promote to group leader', + + 'IP_WHOIS_FOR' => 'IP whois for %s', + + 'LAST_ACTIVE' => 'Last active', + + 'MOVE_POSTS_EXPLAIN' => 'Please select the forum to which you wish to move all the posts this user has made.', + + 'NO_SPECIAL_RANK' => 'No special rank assigned', + 'NO_WARNINGS' => 'No warnings.', + 'NOT_MANAGE_FOUNDER' => 'You tried to manage a user with founder status. Only founders are allowed to manage other founders.', + + 'QUICK_TOOLS' => 'Quick tools', + + 'REGISTERED' => 'Registered', + 'REGISTERED_IP' => 'Registered from IP', + 'RETAIN_POSTS' => 'Retain posts', + + 'SELECT_FORM' => 'Select form', + 'SELECT_USER' => 'Select user', + + 'USER_ADMIN' => 'User administration', + 'USER_ADMIN_ACTIVATE' => 'Activate account', + 'USER_ADMIN_ACTIVATED' => 'User activated successfully.', + 'USER_ADMIN_AVATAR_REMOVED' => 'Successfully removed avatar from user account.', + 'USER_ADMIN_BAN_EMAIL' => 'Ban by email', + 'USER_ADMIN_BAN_EMAIL_REASON' => 'Email address banned via user management', + 'USER_ADMIN_BAN_IP' => 'Ban by IP', + 'USER_ADMIN_BAN_IP_REASON' => 'IP banned via user management', + 'USER_ADMIN_BAN_NAME_REASON' => 'Username banned via user management', + 'USER_ADMIN_BAN_USER' => 'Ban by username', + 'USER_ADMIN_DEACTIVATE' => 'Deactivate account', + 'USER_ADMIN_DEACTIVED' => 'User deactivated successfully.', + 'USER_ADMIN_DEL_ATTACH' => 'Delete all attachments', + 'USER_ADMIN_DEL_AVATAR' => 'Delete avatar', + 'USER_ADMIN_DEL_OUTBOX' => 'Empty PM outbox', + 'USER_ADMIN_DEL_POSTS' => 'Delete all posts', + 'USER_ADMIN_DEL_SIG' => 'Delete signature', + 'USER_ADMIN_EXPLAIN' => 'Here you can change your users information and certain specific options.', + 'USER_ADMIN_FORCE' => 'Force reactivation', + 'USER_ADMIN_LEAVE_NR' => 'Remove from Newly Registered', + 'USER_ADMIN_MOVE_POSTS' => 'Move all posts', + 'USER_ADMIN_SIG_REMOVED' => 'Successfully removed signature from user account.', + 'USER_ATTACHMENTS_REMOVED' => 'Successfully removed all attachments made by this user.', + 'USER_AVATAR_NOT_ALLOWED' => 'The avatar cannot be displayed because avatars have been disallowed.', + 'USER_AVATAR_UPDATED' => 'Successfully updated user avatars details.', + 'USER_AVATAR_TYPE_NOT_ALLOWED' => 'The current avatar cannot be displayed because its type has been disallowed.', + 'USER_CUSTOM_PROFILE_FIELDS' => 'Custom profile fields', + 'USER_DELETED' => 'User deleted successfully.', + 'USER_GROUP_ADD' => 'Add user to group', + 'USER_GROUP_NORMAL' => 'User defined groups user is a member of', + 'USER_GROUP_PENDING' => 'Groups user is in pending mode', + 'USER_GROUP_SPECIAL' => 'Pre-defined groups user is a member of', + 'USER_LIFTED_NR' => 'Successfully removed the user’s newly registered status.', + 'USER_NO_ATTACHMENTS' => 'There are no attached files to display.', + 'USER_NO_POSTS_TO_DELETE' => 'The user has no posts to retain or delete.', + 'USER_OUTBOX_EMPTIED' => 'Successfully emptied user’s private message outbox.', + 'USER_OUTBOX_EMPTY' => 'The user’s private message outbox was already empty.', + 'USER_OVERVIEW_UPDATED' => 'User details updated.', + 'USER_POSTS_DELETED' => 'Successfully removed all posts made by this user.', + 'USER_POSTS_MOVED' => 'Successfully moved users posts to target forum.', + 'USER_PREFS_UPDATED' => 'User preferences updated.', + 'USER_PROFILE' => 'User profile', + 'USER_PROFILE_UPDATED' => 'User profile updated.', + 'USER_RANK' => 'User rank', + 'USER_RANK_UPDATED' => 'User rank updated.', + 'USER_SIG_UPDATED' => 'User signature successfully updated.', + 'USER_WARNING_LOG_DELETED' => 'No information available. Possibly the log entry has been deleted.', + 'USER_TOOLS' => 'Basic tools', +)); diff --git a/language/en/app.php b/language/en/app.php new file mode 100644 index 0000000..39c4065 --- /dev/null +++ b/language/en/app.php @@ -0,0 +1,48 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine +// +// Some characters you may want to copy&paste: +// ’ » “ ” … +// + +$lang = array_merge($lang, array( + 'CONTROLLER_ARGUMENT_VALUE_MISSING' => 'Missing value for argument #%1$s: %3$s in class %2$s', + 'CONTROLLER_NOT_SPECIFIED' => 'No controller has been specified.', + 'CONTROLLER_METHOD_NOT_SPECIFIED' => 'No method was specified for the controller.', + 'CONTROLLER_SERVICE_UNDEFINED' => 'The service for controller “%s” is not defined in ./config/services.yml.', +)); diff --git a/language/en/captcha_qa.php b/language/en/captcha_qa.php new file mode 100644 index 0000000..637c4e0 --- /dev/null +++ b/language/en/captcha_qa.php @@ -0,0 +1,64 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'CAPTCHA_QA' => 'Q&A', + 'CONFIRM_QUESTION_EXPLAIN' => 'This question is a means of preventing automated form submissions by spambots.', + 'CONFIRM_QUESTION_WRONG' => 'You have provided an invalid answer to the question.', + 'CONFIRM_QUESTION_MISSING' => 'Questions for the captcha could not be retrieved. Please contact a board administrator.', + + 'QUESTION_ANSWERS' => 'Answers', + 'ANSWERS_EXPLAIN' => 'Please enter valid answers to the question, one per line.', + 'CONFIRM_QUESTION' => 'Question', + + 'ANSWER' => 'Answer', + 'EDIT_QUESTION' => 'Edit Question', + 'QUESTIONS' => 'Questions', + 'QUESTIONS_EXPLAIN' => 'For every form submission where you have enabled the Q&A plugin, users will be asked one of the questions specified here. To use this plugin at least one question must be set in the default language. These questions should be easy for your target audience to answer but beyond the ability of a bot capable of running a Google™ search. Only a single proper question is necessary. If you start receiving spam registrations, the question should be changed. Enable the strict setting if your question relies on mixed case, punctuation or whitespace.', + 'QUESTION_DELETED' => 'Question deleted', + 'QUESTION_LANG' => 'Language', + 'QUESTION_LANG_EXPLAIN' => 'The language this question and its answers are written in.', + 'QUESTION_STRICT' => 'Strict check', + 'QUESTION_STRICT_EXPLAIN' => 'Enable to enforce mixed case, punctuation and whitespace.', + + 'QUESTION_TEXT' => 'Question', + 'QUESTION_TEXT_EXPLAIN' => 'The question presented to the user.', + + 'QA_ERROR_MSG' => 'Please fill in all fields and enter at least one answer.', + 'QA_LAST_QUESTION' => 'You cannot delete all questions while the plugin is active.', +)); diff --git a/language/en/captcha_recaptcha.php b/language/en/captcha_recaptcha.php new file mode 100644 index 0000000..dde2a4b --- /dev/null +++ b/language/en/captcha_recaptcha.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'RECAPTCHA_LANG' => 'en-GB', // Find the language/country code on https://developers.google.com/recaptcha/docs/language - If no code exists for your language you can use "en" or leave the string empty + 'RECAPTCHA_NOT_AVAILABLE' => 'In order to use reCaptcha, you must create an account on www.google.com/recaptcha.', + 'CAPTCHA_RECAPTCHA' => 'reCaptcha', + 'RECAPTCHA_INCORRECT' => 'The solution you provided was incorrect', + 'RECAPTCHA_NOSCRIPT' => 'Please enable JavaScript in your browser to load the challenge.', + + 'RECAPTCHA_PUBLIC' => 'Public reCaptcha key', + 'RECAPTCHA_PUBLIC_EXPLAIN' => 'Your public reCaptcha key. Keys can be obtained on www.google.com/recaptcha.', + 'RECAPTCHA_PRIVATE' => 'Private reCaptcha key', + 'RECAPTCHA_PRIVATE_EXPLAIN' => 'Your private reCaptcha key. Keys can be obtained on www.google.com/recaptcha.', + + 'RECAPTCHA_EXPLAIN' => 'In an effort to prevent automatic submissions, we require that you complete the following challenge.', +)); diff --git a/language/en/cli.php b/language/en/cli.php new file mode 100644 index 0000000..505d12e --- /dev/null +++ b/language/en/cli.php @@ -0,0 +1,178 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +if (!defined('IN_PHPBB')) +{ + exit; +} + +/** +* DO NOT CHANGE +*/ +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'CLI_CONFIG_CANNOT_CACHED' => 'Set this option if the configuration option changes too frequently to be efficiently cached.', + 'CLI_CONFIG_CURRENT' => 'Current configuration value, use 0 and 1 to specify boolean values', + 'CLI_CONFIG_DELETE_SUCCESS' => 'Successfully deleted config %s.', + 'CLI_CONFIG_NEW' => 'New configuration value, use 0 and 1 to specify boolean values', + 'CLI_CONFIG_NOT_EXISTS' => 'Config %s does not exist', + 'CLI_CONFIG_OPTION_NAME' => 'The configuration option’s name', + 'CLI_CONFIG_PRINT_WITHOUT_NEWLINE' => 'Set this option if the value should be printed without a new line at the end.', + 'CLI_CONFIG_INCREMENT_BY' => 'Amount to increment by', + 'CLI_CONFIG_INCREMENT_SUCCESS' => 'Successfully incremented config %s', + 'CLI_CONFIG_SET_FAILURE' => 'Could not set config %s', + 'CLI_CONFIG_SET_SUCCESS' => 'Successfully set config %s', + + 'CLI_DESCRIPTION_CRON_LIST' => 'Prints a list of ready and unready cron jobs.', + 'CLI_DESCRIPTION_CRON_RUN' => 'Runs all ready cron tasks.', + 'CLI_DESCRIPTION_CRON_RUN_ARGUMENT_1' => 'Name of the task to be run', + 'CLI_DESCRIPTION_DB_LIST' => 'List all installed and available migrations.', + 'CLI_DESCRIPTION_DB_MIGRATE' => 'Updates the database by applying migrations.', + 'CLI_DESCRIPTION_DB_REVERT' => 'Revert a migration.', + 'CLI_DESCRIPTION_DELETE_CONFIG' => 'Deletes a configuration option', + 'CLI_DESCRIPTION_DISABLE_EXTENSION' => 'Disables the specified extension.', + 'CLI_DESCRIPTION_ENABLE_EXTENSION' => 'Enables the specified extension.', + 'CLI_DESCRIPTION_FIND_MIGRATIONS' => 'Finds migrations that are not depended upon.', + 'CLI_DESCRIPTION_FIX_LEFT_RIGHT_IDS' => 'Repairs the tree structure of the forums and modules.', + 'CLI_DESCRIPTION_GET_CONFIG' => 'Gets a configuration option’s value', + 'CLI_DESCRIPTION_INCREMENT_CONFIG' => 'Increments a configuration option’s integer value', + 'CLI_DESCRIPTION_LIST_EXTENSIONS' => 'Lists all extensions in the database and on the filesystem.', + + 'CLI_DESCRIPTION_OPTION_ENV' => 'The Environment name.', + 'CLI_DESCRIPTION_OPTION_SAFE_MODE' => 'Run in Safe Mode (without extensions).', + 'CLI_DESCRIPTION_OPTION_SHELL' => 'Launch the shell.', + + 'CLI_DESCRIPTION_PURGE_EXTENSION' => 'Purges the specified extension.', + + 'CLI_DESCRIPTION_REPARSER_LIST' => 'Lists the types of text that can be reparsed.', + 'CLI_DESCRIPTION_REPARSER_AVAILABLE' => 'Available reparsers:', + 'CLI_DESCRIPTION_REPARSER_REPARSE' => 'Reparses stored text with the current text_formatter services.', + 'CLI_DESCRIPTION_REPARSER_REPARSE_ARG_1' => 'Type of text to reparse. Leave blank to reparse everything.', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_DRY_RUN' => 'Do not save any changes; just print what would happen', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MIN' => 'Lowest record ID to process', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MAX' => 'Highest record ID to process', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_SIZE' => 'Approximate number of records to process at a time', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RESUME' => 'Start reparsing where the last execution stopped', + + 'CLI_DESCRIPTION_RECALCULATE_EMAIL_HASH' => 'Recalculates the user_email_hash column of the users table.', + + 'CLI_DESCRIPTION_SET_ATOMIC_CONFIG' => 'Sets a configuration option’s value only if the old matches the current value', + 'CLI_DESCRIPTION_SET_CONFIG' => 'Sets a configuration option’s value', + + 'CLI_DESCRIPTION_THUMBNAIL_DELETE' => 'Delete all existing thumbnails.', + 'CLI_DESCRIPTION_THUMBNAIL_GENERATE' => 'Generate all missing thumbnails.', + 'CLI_DESCRIPTION_THUMBNAIL_RECREATE' => 'Recreate all thumbnails.', + + 'CLI_DESCRIPTION_UPDATE_CHECK' => 'Check if the board is up to date.', + 'CLI_DESCRIPTION_UPDATE_CHECK_ARGUMENT_1' => 'Name of the extension to check (if all, checks all the extensions)', + 'CLI_DESCRIPTION_UPDATE_CHECK_OPTION_CACHE' => 'Run check command with cache.', + 'CLI_DESCRIPTION_UPDATE_CHECK_OPTION_STABILITY' => 'Run command choosing to check only stable or unstable versions.', + + 'CLI_DESCRIPTION_UPDATE_HASH_BCRYPT' => 'Updates outdated password hashes to be hashed with bcrypt.', + + 'CLI_ERROR_INVALID_STABILITY' => '"%s" needs to be set to "stable" or "unstable".', + + 'CLI_DESCRIPTION_USER_ACTIVATE' => 'Activate (or deactivate) a user account.', + 'CLI_DESCRIPTION_USER_ACTIVATE_USERNAME' => 'Username of the account to activate.', + 'CLI_DESCRIPTION_USER_ACTIVATE_DEACTIVATE' => 'Deactivate the user’s account', + 'CLI_DESCRIPTION_USER_ACTIVATE_ACTIVE' => 'The user is already active.', + 'CLI_DESCRIPTION_USER_ACTIVATE_INACTIVE' => 'The user is already inactive.', + 'CLI_DESCRIPTION_USER_ADD' => 'Add a new user.', + 'CLI_DESCRIPTION_USER_ADD_OPTION_USERNAME' => 'Username of the new user', + 'CLI_DESCRIPTION_USER_ADD_OPTION_PASSWORD' => 'Password of the new user', + 'CLI_DESCRIPTION_USER_ADD_OPTION_EMAIL' => 'E-mail address of the new user', + 'CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY' => 'Send account activation email to the new user (not sent by default)', + 'CLI_DESCRIPTION_USER_DELETE' => 'Delete a user account.', + 'CLI_DESCRIPTION_USER_DELETE_USERNAME' => 'Username of the user to delete', + 'CLI_DESCRIPTION_USER_DELETE_OPTION_POSTS' => 'Delete all posts by the user. Without this option, the user’s posts will be retained.', + 'CLI_DESCRIPTION_USER_RECLEAN' => 'Re-clean usernames.', + + 'CLI_EXTENSION_DISABLE_FAILURE' => 'Could not disable extension %s', + 'CLI_EXTENSION_DISABLE_SUCCESS' => 'Successfully disabled extension %s', + 'CLI_EXTENSION_DISABLED' => 'Extension %s is not enabled', + 'CLI_EXTENSION_ENABLE_FAILURE' => 'Could not enable extension %s', + 'CLI_EXTENSION_ENABLE_SUCCESS' => 'Successfully enabled extension %s', + 'CLI_EXTENSION_ENABLED' => 'Extension %s is already enabled', + 'CLI_EXTENSION_NOT_EXIST' => 'Extension %s does not exist', + 'CLI_EXTENSION_NAME' => 'Name of the extension', + 'CLI_EXTENSION_PURGE_FAILURE' => 'Could not purge extension %s', + 'CLI_EXTENSION_PURGE_SUCCESS' => 'Successfully purged extension %s', + 'CLI_EXTENSION_UPDATE_FAILURE' => 'Could not update extension %s', + 'CLI_EXTENSION_UPDATE_SUCCESS' => 'Successfully updated extension %s', + 'CLI_EXTENSION_NOT_FOUND' => 'No extensions were found.', + 'CLI_EXTENSION_NOT_ENABLEABLE' => 'Extension %s is not enableable.', + 'CLI_EXTENSIONS_AVAILABLE' => 'Available', + 'CLI_EXTENSIONS_DISABLED' => 'Disabled', + 'CLI_EXTENSIONS_ENABLED' => 'Enabled', + + 'CLI_FIXUP_FIX_LEFT_RIGHT_IDS_SUCCESS' => 'Successfully repaired the tree structure of the forums and modules.', + 'CLI_FIXUP_RECALCULATE_EMAIL_HASH_SUCCESS' => 'Successfully recalculated all email hashes.', + 'CLI_FIXUP_UPDATE_HASH_BCRYPT_SUCCESS' => 'Successfully updated outdated password hashes to bcrypt.', + + 'CLI_MIGRATION_NAME' => 'Migration name, including the namespace (use forward slashes instead of backslashes to avoid problems).', + 'CLI_MIGRATIONS_AVAILABLE' => 'Available migrations', + 'CLI_MIGRATIONS_INSTALLED' => 'Installed migrations', + 'CLI_MIGRATIONS_ONLY_AVAILABLE' => 'Show only available migrations', + 'CLI_MIGRATIONS_EMPTY' => 'No migrations.', + + 'CLI_REPARSER_REPARSE_REPARSING' => 'Reparsing %1$s (range %2$d..%3$d)', + 'CLI_REPARSER_REPARSE_REPARSING_START' => 'Reparsing %s...', + 'CLI_REPARSER_REPARSE_SUCCESS' => 'Reparsing ended with success', + + // In all the case %1$s is the logical name of the file and %2$s the real name on the filesystem + // eg: big_image.png (2_a51529ae7932008cf8454a95af84cacd) generated. + 'CLI_THUMBNAIL_DELETED' => '%1$s (%2$s) deleted.', + 'CLI_THUMBNAIL_DELETING' => 'Deleting thumbnails', + 'CLI_THUMBNAIL_SKIPPED' => '%1$s (%2$s) skipped.', + 'CLI_THUMBNAIL_GENERATED' => '%1$s (%2$s) generated.', + 'CLI_THUMBNAIL_GENERATING' => 'Generating thumbnails', + 'CLI_THUMBNAIL_GENERATING_DONE' => 'All thumbnails have been regenerated.', + 'CLI_THUMBNAIL_DELETING_DONE' => 'All thumbnails have been deleted.', + + 'CLI_THUMBNAIL_NOTHING_TO_GENERATE' => 'No thumbnails to generate.', + 'CLI_THUMBNAIL_NOTHING_TO_DELETE' => 'No thumbnails to delete.', + + 'CLI_USER_ADD_SUCCESS' => 'Successfully added user %s.', + 'CLI_USER_DELETE_CONFIRM' => 'Are you sure you want to delete ‘%s’? [y/N]', + 'CLI_USER_RECLEAN_START' => 'Re-cleaning usernames', + 'CLI_USER_RECLEAN_DONE' => [ + 0 => 'Re-cleaning complete. No usernames needed to be cleaned.', + 1 => 'Re-cleaning complete. %d username was cleaned.', + 2 => 'Re-cleaning complete. %d usernames were cleaned.', + ], +)); + +// Additional help for commands. +$lang = array_merge($lang, array( + 'CLI_HELP_CRON_RUN' => $lang['CLI_DESCRIPTION_CRON_RUN'] . ' Optionally you can specify a cron task name to run only the specified cron task.', + 'CLI_HELP_USER_ACTIVATE' => 'Activate a user account, or deactivate an account using the --deactivate option. +To optionally send an activation email to the user, use the --send-email option.', + 'CLI_HELP_USER_ADD' => 'The %command.name% command adds a new user: +If this command is run without options, you will be prompted to enter them. +To optionally send an email to the new user, use the --send-email option.', + 'CLI_HELP_USER_RECLEAN' => 'Re-clean usernames will check all stored usernames and ensure clean versions are also stored. Cleaned usernames are a case insensitive form, NFC normalized and transformed to ASCII.', +)); diff --git a/language/en/common.php b/language/en/common.php new file mode 100644 index 0000000..8350307 --- /dev/null +++ b/language/en/common.php @@ -0,0 +1,1438 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine +// +// Some characters you may want to copy&paste: +// ’ » “ ” … +// + +$lang = array_merge($lang, array( + 'TRANSLATION_INFO' => '', + 'DIRECTION' => 'ltr', + 'DATE_FORMAT' => '|d M Y|', // 01 Jan 2007 (with Relative days enabled) + 'DATETIME_FORMAT' => '|d M Y, H:i|', // 01 Jan 2007, 13:37 (with Relative days enabled) + 'USER_LANG' => 'en-gb', + + // You can define different rules for the determination of plural forms here. + // See https://area51.phpbb.com/docs/dev/32x/language/plurals.html for more information + // or ask the translation manager for help. + 'PLURAL_RULE' => 1, + + '1_DAY' => '1 day', + '1_MONTH' => '1 month', + '1_YEAR' => '1 year', + '2_WEEKS' => '2 weeks', + '3_MONTHS' => '3 months', + '6_MONTHS' => '6 months', + '7_DAYS' => '7 days', + + 'ACCOUNT_ALREADY_ACTIVATED' => 'Your account has already been activated.', + 'ACCOUNT_DEACTIVATED' => 'Your account has been manually deactivated and is only able to be reactivated by an administrator.', + 'ACP' => 'Administration Control Panel', + 'ACP_SHORT' => 'ACP', + 'ACTIVE' => 'active', + 'ACTIVE_ERROR' => 'The specified username is currently inactive. If you have problems activating your account, please contact a board administrator.', + 'ADMINISTRATOR' => 'Administrator', + 'ADMINISTRATORS' => 'Administrators', + 'AGE' => 'Age', + 'AIM' => 'AIM', + 'AJAX_ERROR_TITLE' => 'AJAX error', + 'AJAX_ERROR_TEXT' => 'Something went wrong when processing your request.', + 'AJAX_ERROR_TEXT_ABORT' => 'User aborted request.', + 'AJAX_ERROR_TEXT_TIMEOUT' => 'Your request timed out; please try again.', + 'AJAX_ERROR_TEXT_PARSERERROR' => 'Something went wrong with the request and the server returned an invalid reply.', + 'ALLOWED' => 'Allowed', + 'ALL_FILES' => 'All files', + 'ALL_FORUMS' => 'All forums', + 'ALL_MESSAGES' => 'All messages', + 'ALL_POSTS' => 'All posts', + 'ALL_TIMES' => 'All times are %1$s', + 'ALL_TOPICS' => 'All Topics', + 'ALT_TEXT' => 'Alternative text', + 'AND' => 'And', + 'ARE_WATCHING_FORUM' => 'You have subscribed to be notified of new posts in this forum.', + 'ARE_WATCHING_TOPIC' => 'You have subscribed to be notified of new posts in this topic.', + 'ASCENDING' => 'Ascending', + 'ATTACHMENTS' => 'Attachments', + 'ATTACHED_IMAGE_NOT_IMAGE' => 'The image file you tried to attach is invalid.', + 'AUTHOR' => 'Author', + 'AUTH_NO_PROFILE_CREATED' => 'The creation of a user profile was unsuccessful.', + 'AUTH_PROVIDER_OAUTH_ERROR_INVALID_ENTRY' => 'Invalid database entry.', + 'AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE' => 'Invalid service type provided to OAuth service handler.', + 'AUTH_PROVIDER_OAUTH_ERROR_SERVICE_NOT_CREATED' => 'OAuth service not created', + 'AUTH_PROVIDER_OAUTH_SERVICE_BITLY' => 'Bitly', + 'AUTH_PROVIDER_OAUTH_SERVICE_FACEBOOK' => 'Facebook', + 'AUTH_PROVIDER_OAUTH_SERVICE_GOOGLE' => 'Google', + 'AUTH_PROVIDER_OAUTH_SERVICE_TWITTER' => 'Twitter', + 'AUTH_PROVIDER_OAUTH_TOKEN_ERROR_NOT_STORED' => 'OAuth token not stored.', + 'AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED' => 'OAuth token incorrectly stored.', + 'AVATAR_DISALLOWED_CONTENT' => 'The upload was rejected because the uploaded file was identified as a possible attack vector.', + 'AVATAR_DISALLOWED_EXTENSION' => 'This file cannot be displayed because the extension %s is not allowed.', + 'AVATAR_EMPTY_REMOTE_DATA' => 'The specified avatar could not be uploaded because the remote data appears to be invalid or corrupted.', + 'AVATAR_EMPTY_FILEUPLOAD' => 'The uploaded avatar file is empty.', + 'AVATAR_INVALID_FILENAME' => '%s is an invalid filename.', + 'AVATAR_NOT_UPLOADED' => 'Avatar could not be uploaded.', + 'AVATAR_NO_TEMP_DIR' => 'Temporary folder could not be found or is not writable.', + 'AVATAR_NO_SIZE' => 'The width or height of the linked avatar could not be determined. Please enter them manually.', + 'AVATAR_PARTIAL_UPLOAD' => 'The specified file was only partially uploaded.', + 'AVATAR_PHP_SIZE_NA' => 'The avatar’s filesize is too large.
The maximum allowed filesize set in php.ini could not be determined.', + 'AVATAR_PHP_SIZE_OVERRUN' => 'The avatar’s filesize is too large. The maximum allowed upload size is %1$d %2$s.
Please note this is set in php.ini and cannot be overridden.', + 'AVATAR_REMOTE_UPLOAD_TIMEOUT' => 'The specified avatar could not be uploaded because the request timed out.', + 'AVATAR_PHP_UPLOAD_STOPPED' => 'A PHP extension has stopped the file upload.', + 'AVATAR_URL_INVALID' => 'The URL you specified is invalid.', + 'AVATAR_URL_NOT_FOUND' => 'The file specified could not be found.', + 'AVATAR_WRONG_FILESIZE' => 'The avatar’s filesize must be between 0 and %1$d %2$s.', + 'AVATAR_WRONG_SIZE' => 'The submitted avatar is %5$s wide and %6$s high. Avatars must be at least %1$s wide and %2$s high, but no larger than %3$s wide and %4$s high.', + + 'BACK_TO_TOP' => 'Top', + 'BACK_TO_PREV' => 'Back to previous page', + 'BAN_TRIGGERED_BY_EMAIL'=> 'A ban has been issued on your email address.', + 'BAN_TRIGGERED_BY_IP' => 'A ban has been issued on your IP address.', + 'BAN_TRIGGERED_BY_USER' => 'A ban has been issued on your username.', + 'BBCODE_GUIDE' => 'BBCode guide', + 'BCC' => 'BCC', + 'BIRTHDAYS' => 'Birthdays', + 'BOARD_BAN_PERM' => 'You have been permanently banned from this board.

Please contact the %2$sBoard Administrator%3$s for more information.', + 'BOARD_BAN_REASON' => 'Reason given for ban: %s', + 'BOARD_BAN_TIME' => 'You have been banned from this board until %1$s.

Please contact the %2$sBoard Administrator%3$s for more information.', + 'BOARD_DISABLE' => 'Sorry but this board is currently unavailable.', + 'BOARD_DISABLED' => 'This board is currently disabled.', + 'BOARD_UNAVAILABLE' => 'Sorry but the board is temporarily unavailable, please try again in a few minutes.', + 'BROWSING_FORUM' => 'Users browsing this forum: %1$s', + 'BROWSING_FORUM_GUESTS' => array( + 1 => 'Users browsing this forum: %2$s and %1$d guest', + 2 => 'Users browsing this forum: %2$s and %1$d guests', + ), + 'BUTTON_DELETE' => 'Delete', + 'BUTTON_EDIT' => 'Edit', + 'BUTTON_FORUM_LOCKED' => 'Locked', + 'BUTTON_INFORMATION' => 'Information', + 'BUTTON_NEW_TOPIC' => 'New Topic', + 'BUTTON_PM' => 'PM', + 'BUTTON_PM_FORWARD' => 'Forward', + 'BUTTON_PM_NEW' => 'New PM', + 'BUTTON_PM_REPLY' => 'Send Reply', + 'BUTTON_PM_REPLY_ALL' => 'Reply All', + 'BUTTON_POST_REPLY' => 'Post Reply', + 'BUTTON_QUOTE' => 'Quote', + 'BUTTON_REPORT' => 'Report', + 'BUTTON_TOPIC_LOCKED' => 'Locked', + 'BUTTON_WARN' => 'Warn', + 'BYTES' => 'Bytes', + 'BYTES_SHORT' => 'B', + + 'CANCEL' => 'Cancel', + 'CHANGE' => 'Change', + 'CHANGE_FONT_SIZE' => 'Change font size', + 'CHANGING_PREFERENCES' => 'Changing board preferences', + 'CHANGING_PROFILE' => 'Changing profile settings', + 'CHARACTERS' => array( + 1 => '%d character', + 2 => '%d characters', + ), + 'COLLAPSE_VIEW' => 'Collapse view', + 'CLOSE_WINDOW' => 'Close window', + 'COLOUR_SWATCH' => 'Colour swatch', + 'COLON' => ':', + 'COMMA_SEPARATOR' => ', ', // Comma used to join lists into a single string, use localised comma if appropriate, eg: Ideographic or Arabic + 'CONFIRM' => 'Confirm', + 'CONFIRM_CODE' => 'Confirmation code', + 'CONFIRM_CODE_EXPLAIN' => 'Enter the code exactly as it appears. All letters are case insensitive.', + 'CONFIRM_CODE_WRONG' => 'The confirmation code you entered was incorrect.', + 'CONFIRM_OPERATION' => 'Are you sure you wish to carry out this operation?', + 'CONFIRM_AVATAR_DELETE' => 'Are you sure you wish to delete this avatar?', + 'CONGRATULATIONS' => 'Congratulations to', + 'CONNECTION_FAILED' => 'Connection failed.', + 'CONNECTION_SUCCESS' => 'Connection was successful!', + 'CONTACT' => 'Contact', + 'CONTACT_USER' => 'Contact %s', + 'CONTACT_US' => 'Contact us', + 'COOKIE_CONSENT_INFO' => 'Learn more', + 'COOKIE_CONSENT_MSG' => 'This website uses cookies to ensure you get the best experience on our website.', + 'COOKIE_CONSENT_OK' => 'Got it!', + 'COOKIE_CONSENT_HREF' => 'http://cookiesandyou.com', + 'COOKIES_DELETED' => 'All board cookies successfully deleted.', + 'CURRENT_TIME' => 'It is currently %s', + + 'DAY' => 'Day', + 'DAYS' => 'Days', + 'DELETE' => 'Delete', + 'DELETE_ALL' => 'Delete all', + 'DELETE_COOKIES' => 'Delete cookies', + 'DELETE_MARKED' => 'Delete marked', + 'DELETE_POST' => 'Delete post', + 'DELIMITER' => 'Delimiter', + 'DESCENDING' => 'Descending', + 'DISABLED' => 'Disabled', + 'DISPLAY' => 'Display', + 'DISPLAY_GUESTS' => 'Display guests', + 'DISPLAY_MESSAGES' => 'Display messages from previous', + 'DISPLAY_POSTS' => 'Display posts from previous', + 'DISPLAY_TOPICS' => 'Display topics from previous', + 'DOWNLOADED' => 'Downloaded', + 'DOWNLOADING_FILE' => 'Downloading file', + 'DOWNLOAD_COUNTS' => array( + 0 => 'Not downloaded yet', + 1 => 'Downloaded %d time', + 2 => 'Downloaded %d times', + ), + + 'EDIT_POST' => 'Edit post', + 'ELLIPSIS' => '…', + 'EMAIL' => 'Email', // Short form for EMAIL_ADDRESS + 'EMAIL_ADDRESS' => 'Email address', + 'EMAIL_INVALID_EMAIL' => 'The email address you entered is invalid.', + 'EMAIL_SMTP_ERROR_RESPONSE' => 'Ran into problems sending email at Line %1$s. Response: %2$s.', + 'EMPTY_SUBJECT' => 'You must specify a subject when posting a new topic.', + 'EMPTY_MESSAGE_SUBJECT' => 'You must specify a subject when composing a new message.', + 'ENABLED' => 'Enabled', + 'ENCLOSURE' => 'Enclosure', + 'ENTER_USERNAME' => 'Enter username', + 'ERR_CHANGING_DIRECTORY' => 'Unable to change directory.', + 'ERR_CONNECTING_SERVER' => 'Error connecting to the server.', + 'ERR_JAB_AUTH' => 'Could not authorise on Jabber server.', + 'ERR_JAB_CONNECT' => 'Could not connect to Jabber server.', + 'ERR_UNABLE_TO_LOGIN' => 'The specified username or password is incorrect.', + 'ERR_UNWATCHING' => 'An error occurred while trying to unsubscribe.', + 'ERR_WATCHING' => 'An error occurred while trying to subscribe.', + 'ERR_WRONG_PATH_TO_PHPBB' => 'The phpBB path specified appears to be invalid.', + 'ERROR' => 'Error', + 'EXPAND_VIEW' => 'Expand view', + 'EXTENSION' => 'Extension', + 'EXTENSION_DISABLED' => 'The extension %s is not enabled.', + 'EXTENSION_DISABLED_AFTER_POSTING' => 'The extension %s has been deactivated and can no longer be displayed.', + 'EXTENSION_DOES_NOT_EXIST' => 'The extension %s does not exist.', + + 'FACEBOOK' => 'Facebook', + 'FAQ' => 'FAQ', + 'FAQ_EXPLAIN' => 'Frequently Asked Questions', + 'FEATURE_NOT_AVAILABLE' => 'The requested feature is not available on this board.', + 'FILENAME' => 'Filename', + 'FILESIZE' => 'File size', + 'FILEDATE' => 'File date', + 'FILE_COMMENT' => 'File comment', + 'FILE_CONTENT_ERR' => 'Could not read the contents of file: %s', + 'FILE_JSON_DECODE_ERR' => 'Failed to decode json file: %s', + 'FILE_NOT_FOUND' => 'The requested file could not be found: %s', + 'FIND_USERNAME' => 'Find a member', + 'FOLDER' => 'Folder', + 'FORGOT_PASS' => 'I forgot my password', + 'FORM_INVALID' => 'The submitted form was invalid. Try submitting again.', + 'FORUM' => 'Forum', + 'FORUMS' => 'Forums', + 'FORUMS_MARKED' => 'Forums have been marked read.', + 'FORUM_CAT' => 'Forum category', + 'FORUM_INDEX' => 'Board index', + 'FORUM_LINK' => 'Forum link', + 'FORUM_LOCATION' => 'Forum location', + 'FORUM_LOCKED' => 'Forum locked', + 'FORUM_RULES' => 'Forum rules', + 'FORUM_RULES_LINK' => 'Please click here to view the forum rules', + 'FROM' => 'from', + 'FSOCK_DISABLED' => 'The operation could not be completed because the fsockopen function has been disabled or the server being queried could not be found.', + 'FSOCK_TIMEOUT' => 'A timeout occurred while reading from the network stream.', + + 'FTP_FSOCK_HOST' => 'FTP host', + 'FTP_FSOCK_HOST_EXPLAIN' => 'FTP server used to connect your site.', + 'FTP_FSOCK_PASSWORD' => 'FTP password', + 'FTP_FSOCK_PASSWORD_EXPLAIN' => 'Password for your FTP username.', + 'FTP_FSOCK_PORT' => 'FTP port', + 'FTP_FSOCK_PORT_EXPLAIN' => 'Port used to connect to your server.', + 'FTP_FSOCK_ROOT_PATH' => 'Path to phpBB', + 'FTP_FSOCK_ROOT_PATH_EXPLAIN' => 'Path from the root to your phpBB board.', + 'FTP_FSOCK_TIMEOUT' => 'FTP timeout', + 'FTP_FSOCK_TIMEOUT_EXPLAIN' => 'The amount of time, in seconds, that the system will wait for a reply from your server.', + 'FTP_FSOCK_USERNAME' => 'FTP username', + 'FTP_FSOCK_USERNAME_EXPLAIN' => 'Username used to connect to your server.', + + 'FTP_HOST' => 'FTP host', + 'FTP_HOST_EXPLAIN' => 'FTP server used to connect your site.', + 'FTP_PASSWORD' => 'FTP password', + 'FTP_PASSWORD_EXPLAIN' => 'Password for your FTP username.', + 'FTP_PORT' => 'FTP port', + 'FTP_PORT_EXPLAIN' => 'Port used to connect to your server.', + 'FTP_ROOT_PATH' => 'Path to phpBB', + 'FTP_ROOT_PATH_EXPLAIN' => 'Path from the root to your phpBB board.', + 'FTP_TIMEOUT' => 'FTP timeout', + 'FTP_TIMEOUT_EXPLAIN' => 'The amount of time, in seconds, that the system will wait for a reply from your server.', + 'FTP_USERNAME' => 'FTP username', + 'FTP_USERNAME_EXPLAIN' => 'Username used to connect to your server.', + + 'GENERAL_ERROR' => 'General Error', + 'GB' => 'GB', + 'GIB' => 'GiB', + 'GO' => 'Go', + 'GOOGLEPLUS' => 'Google+', + 'GOTO_FIRST_POST' => 'Go to first post', + 'GOTO_LAST_POST' => 'Go to last post', + 'GOTO_PAGE' => 'Go to page', + 'GROUP' => 'Group', + 'GROUPS' => 'Groups', + 'GROUP_ERR_TYPE' => 'Inappropriate group type specified.', + 'GROUP_ERR_USERNAME' => 'No group name specified.', + 'GROUP_ERR_USER_LONG' => 'Group names cannot exceed 60 characters. The specified group name is too long.', + 'GUEST' => 'Guest', + 'GUEST_USERS_ONLINE' => array( + 1 => 'There is %d guest user online', + 2 => 'There are %d guest users online', + ), + 'GUEST_USERS_TOTAL' => array( + 1 => '%d guest', + 2 => '%d guests', + ), + 'G_ADMINISTRATORS' => 'Administrators', + 'G_BOTS' => 'Bots', + 'G_GUESTS' => 'Guests', + 'G_REGISTERED' => 'Registered users', + 'G_REGISTERED_COPPA' => 'Registered COPPA users', + 'G_GLOBAL_MODERATORS' => 'Global moderators', + 'G_NEWLY_REGISTERED' => 'Newly registered users', + + 'HIDDEN_USERS_ONLINE' => array( + 1 => '%d hidden user', + 2 => '%d hidden users', + ), + 'HIDDEN_USERS_TOTAL' => array( + 1 => '%d hidden', + 2 => '%d hidden', + ), + 'HIDE_GUESTS' => 'Hide guests', + 'HIDE_ME' => 'Hide my online status this session', + 'HOURS' => 'Hours', + 'HOME' => 'Home', + + 'ICQ' => 'ICQ', + 'IF' => 'If', + 'IMAGE' => 'Image', + 'IMAGE_FILETYPE_INVALID' => 'Image file type %d for mimetype %s not supported.', + 'IMAGE_FILETYPE_MISMATCH' => 'Image file type mismatch: expected extension %1$s but extension %2$s given.', + 'IN' => 'in', + 'INACTIVE' => 'Inactive', + 'INDEX' => 'Index page', + 'INFORMATION' => 'Information', + 'INSECURE_REDIRECT' => 'Tried to redirect to potentially insecure url.', + 'INTERESTS' => 'Interests', + 'INVALID_DIGEST_CHALLENGE' => 'Invalid digest challenge.', + 'INVALID_EMAIL_LOG' => '%s possibly an invalid email address?', + 'INVALID_FEED_ATTACHMENTS' => 'The selected feed tried fetching attachments with invalid constraints.', + 'INVALID_PLURAL_RULE' => 'The chosen plural rule is invalid. Valid values are integers between 0 and 15.', + 'IP' => 'IP', + 'IP_BLACKLISTED' => 'Your IP %1$s has been blocked because it is blacklisted. For details please see %2$s.', + + 'JABBER' => 'Jabber', + 'JOINED' => 'Joined', + 'JUMP_PAGE' => 'Enter the page number you wish to go to', + 'JUMP_TO' => 'Jump to', + 'JUMP_TO_PAGE' => 'Jump to page', + 'JUMP_TO_PAGE_CLICK' => 'Click to jump to page…', + + 'KB' => 'KB', + 'KIB' => 'KiB', + + 'LAST_POST' => 'Last post', + 'LAST_UPDATED' => 'Last updated', + 'LAST_VISIT' => 'Last visit', + 'LDAP_NO_LDAP_EXTENSION' => 'LDAP extension not available.', + 'LDAP_NO_SERVER_CONNECTION' => 'Could not connect to LDAP server.', + 'LDAP_SEARCH_FAILED' => 'An error occurred while searching the LDAP directory.', + 'LEGEND' => 'Legend', + 'LIVE_SEARCHES_NOT_ALLOWED' => 'Live searches are not allowed.', + 'LOADING' => 'Loading', + 'LOCATION' => 'Location', + 'LOCK_POST' => 'Lock post', + 'LOCK_POST_EXPLAIN' => 'Prevent editing', + 'LOCK_TOPIC' => 'Lock topic', + 'LOGIN' => 'Login', + 'LOGIN_CHECK_PM' => 'Log in to check your private messages.', + 'LOGIN_CONFIRMATION' => 'Confirmation of login', + 'LOGIN_CONFIRM_EXPLAIN' => 'To prevent brute forcing accounts the board requires you to enter a confirmation code after a maximum amount of failed logins. The code is displayed in the image you should see below. If you are visually impaired or cannot otherwise read this code please contact the %sBoard Administrator%s.', // unused + 'LOGIN_ERROR_ATTEMPTS' => 'You exceeded the maximum allowed number of login attempts. In addition to your username and password you now also have to solve the CAPTCHA below.', + 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE' => 'You have not been authenticated by Apache.', + 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST' => 'A non-existant OAuth service has been requested.', + 'LOGIN_ERROR_PASSWORD' => 'You have specified an incorrect password. Please check your password and try again. If you continue to have problems please contact the %sBoard Administrator%s.', + 'LOGIN_ERROR_PASSWORD_CONVERT' => 'It was not possible to convert your password when updating this bulletin board’s software. Please %srequest a new password%s. If you continue to have problems please contact the %sBoard Administrator%s.', + 'LOGIN_ERROR_USERNAME' => 'You have specified an incorrect username. Please check your username and try again. If you continue to have problems please contact the %sBoard Administrator%s.', + 'LOGIN_FORUM' => 'To view or post in this forum you must enter its password.', + 'LOGIN_INFO' => 'In order to login you must be registered. Registering takes only a few moments but gives you increased capabilities. The board administrator may also grant additional permissions to registered users. Before you register please ensure you are familiar with our terms of use and related policies. Please ensure you read any forum rules as you navigate around the board.', + 'LOGIN_VIEWFORUM' => 'The board requires you to be registered and logged in to view this forum.', + 'LOGIN_EXPLAIN_EDIT' => 'In order to edit posts in this forum you have to be registered and logged in.', + 'LOGIN_EXPLAIN_VIEWONLINE' => 'In order to view the online list you have to be registered and logged in.', + 'LOGIN_REQUIRED' => 'You need to login to perform this action.', + 'LOGOUT' => 'Logout', + 'LOGOUT_USER' => 'Logout [ %s ]', + 'LOG_ME_IN' => 'Remember me', + + 'MAIN' => 'Main', + 'MARK' => 'Mark', + 'MARK_ALL' => 'Mark all', + 'MARK_ALL_READ' => 'Mark all read', + 'MARK_FORUMS_READ' => 'Mark forums read', + 'MARK_READ' => 'Mark read', + 'MARK_SUBFORUMS_READ' => 'Mark subforums read', + 'MB' => 'MB', + 'MIB' => 'MiB', + 'MCP' => 'Moderator Control Panel', + 'MCP_SHORT' => 'MCP', + 'MEMBERLIST' => 'Members', + 'MEMBERLIST_EXPLAIN' => 'View complete list of members', + 'MERGE' => 'Merge', + 'MERGE_POSTS' => 'Move posts', + 'MERGE_TOPIC' => 'Merge topic', + 'MESSAGE' => 'Message', + 'MESSAGES' => 'Messages', + 'MESSAGES_COUNT' => array( + 1 => '%d message', + 2 => '%d messages', + ), + 'MESSAGE_BODY' => 'Message body', + 'MINUTES' => 'Minutes', + 'MODERATE' => 'Moderate', + 'MODERATOR' => 'Moderator', + 'MODERATORS' => 'Moderators', + 'MODULE_NOT_ACCESS' => 'Module not accessible', + 'MODULE_NOT_FIND' => 'Cannot find module %s', + 'MODULE_FILE_INCORRECT_CLASS' => 'Module file %s does not contain correct class [%s]', + 'MONTH' => 'Month', + 'MOVE' => 'Move', + + 'NA' => 'N/A', + 'NEWEST_USER' => 'Our newest member %s', + 'NEW_MESSAGE' => 'New message', + 'NEW_MESSAGES' => 'New messages', + 'NEW_POST' => 'New post', // Not used anymore + 'NEW_POSTS' => 'New posts', // Not used anymore + 'NEXT' => 'Next', // Used in pagination + 'NEXT_STEP' => 'Next', + 'NEVER' => 'Never', + 'NO' => 'No', + 'NO_NOTIFICATIONS' => 'You have no notifications', + 'NOT_ALLOWED_MANAGE_GROUP' => 'You are not allowed to manage this group.', + 'NOT_AUTHORISED' => 'You are not authorised to access this area.', + 'NOT_WATCHING_FORUM' => 'You are no longer subscribed to updates on this forum.', + 'NOT_WATCHING_TOPIC' => 'You are no longer subscribed to this topic.', + 'NOTIFICATIONS' => 'Notifications', + // This applies for NOTIFICATION_BOOKMARK and NOTIFICATION_POST. + // %1$s will return a list of users that's concatenated using "," and "and" - see STRING_LIST + // Once the user count reaches 5 users or more, the list is trimmed using NOTIFICATION_X_OTHERS + // Once the user count reaches 20 users or more, the list is trimmed using NOTIFICATION_MANY_OTHERS + // Examples: + // A replied... + // A and B replied... + // A, B and C replied... + // A, B, C and 2 others replied... + // A, B, C and others replied... + 'NOTIFICATION_BOOKMARK' => array( + 1 => 'Reply from %1$s in bookmarked topic:', + ), + 'NOTIFICATION_FORUM' => 'Forum: %1$s', + 'NOTIFICATION_GROUP_REQUEST' => 'Group request from %1$s to join the group %2$s.', + 'NOTIFICATION_GROUP_REQUEST_APPROVED' => 'Group request approved to join the group %1$s.', + 'NOTIFICATION_METHOD_INVALID' => 'The method "%s" does not refer to a valid notification method.', + 'NOTIFICATION_PM' => 'Private Message from %1$s:', + 'NOTIFICATION_POST' => array( + 1 => 'Reply from %1$s in topic:', + ), + 'NOTIFICATION_POST_APPROVED' => 'Post approved:', + 'NOTIFICATION_POST_DISAPPROVED' => 'Post disapproved:', + 'NOTIFICATION_POST_IN_QUEUE' => 'Post approval request by %1$s:', + 'NOTIFICATION_QUOTE' => array( + 1 => 'Quoted by %1$s in:', + ), + 'NOTIFICATION_REFERENCE' => '"%1$s"', + 'NOTIFICATION_REASON' => 'Reason: %1$s.', + 'NOTIFICATION_REPORT_PM' => 'Private Message reported by %1$s:', + 'NOTIFICATION_REPORT_POST' => 'Post reported by %1$s:', + 'NOTIFICATION_REPORT_CLOSED' => 'Report closed by %1$s for:', + 'NOTIFICATION_TOPIC' => 'New topic by %1$s:', + 'NOTIFICATION_TOPIC_APPROVED' => 'Topic approved:', + 'NOTIFICATION_TOPIC_DISAPPROVED' => 'Topic disapproved:', + 'NOTIFICATION_TOPIC_IN_QUEUE' => 'Topic approval request by %1$s:', + 'NOTIFICATION_TYPE_NOT_EXIST' => 'The notification type "%s" is missing from the file system.', + 'NOTIFICATION_ADMIN_ACTIVATE_USER' => 'Activation required for deactivated or newly registered user: “%1$s”', + // Used in conjuction with NOTIFICATION_BOOKMARK and NOTIFICATION_POST. + 'NOTIFICATION_MANY_OTHERS' => 'others', + 'NOTIFICATION_X_OTHERS' => array( + 2 => '%d others', + ), + 'NOTIFY_ADMIN' => 'Please notify the board administrator or webmaster.', + 'NOTIFY_ADMIN_EMAIL' => 'Please notify the board administrator or webmaster: %1$s', + 'NO_ACCESS_ATTACHMENT' => 'You are not allowed to access this file.', + 'NO_ACTION' => 'No action specified.', + 'NO_ADMINISTRATORS' => 'There are no administrators.', + 'NO_AUTH_ADMIN' => 'Access to the Administration Control Panel is not allowed as you do not have administrative permissions.', + 'NO_AUTH_ADMIN_USER_DIFFER' => 'You are not able to re-authenticate as a different user.', + 'NO_AUTH_OPERATION' => 'You do not have the necessary permissions to complete this operation.', + 'NO_AVATARS' => 'No avatars currently available', + 'NO_CONNECT_TO_SMTP_HOST' => 'Could not connect to smtp host : %1$s : %2$s', + 'NO_BIRTHDAYS' => 'No birthdays today', + 'NO_EMAIL_MESSAGE' => 'Email message was blank.', + 'NO_EMAIL_RESPONSE_CODE' => 'Could not get mail server response codes.', + 'NO_EMAIL_SUBJECT' => 'No email subject specified.', + 'NO_FORUM' => 'The forum you selected does not exist.', + 'NO_FORUMS' => 'This board has no forums.', + 'NO_GROUP' => 'The requested usergroup does not exist.', + 'NO_GROUP_MEMBERS' => 'This group currently has no members.', + 'NO_IPS_DEFINED' => 'No IP addresses or hostnames defined', + 'NO_MEMBERS' => 'No members found for this search criterion.', + 'NO_MESSAGES' => 'No messages', + 'NO_MODE' => 'No mode specified.', + 'NO_MODERATORS' => 'There are no moderators.', + 'NO_NEW_MESSAGES' => 'No new messages', + 'NO_NEW_POSTS' => 'No new posts', // Not used anymore + 'NO_ONLINE_USERS' => 'No registered users', + 'NO_POSTS' => 'No posts', + 'NO_POSTS_TIME_FRAME' => 'No posts exist inside this topic for the selected time frame.', + 'NO_FEED_ENABLED' => 'Feeds are not available on this board.', + 'NO_FEED' => 'The requested feed is not available.', + 'NO_STYLE_DATA' => 'Could not get style data', + 'NO_SUBJECT' => 'No subject specified', // Used for posts having no subject defined but displayed within management pages. + 'NO_SUCH_SEARCH_MODULE' => 'The specified search backend doesn’t exist.', + 'NO_SUPPORTED_AUTH_METHODS' => 'No supported authentication methods.', + 'NO_TOPIC' => 'The requested topic does not exist.', + 'NO_TOPIC_FORUM' => 'The topic or forum no longer exists.', + 'NO_TOPICS' => 'There are no topics or posts in this forum.', + 'NO_TOPICS_TIME_FRAME' => 'No topics exist inside this forum for the selected time frame.', + 'NO_UNREAD_POSTS' => 'No unread posts', + 'NO_UPLOAD_FORM_FOUND' => 'Upload initiated but no valid file upload form found.', + 'NO_USER' => 'The requested user does not exist.', + 'NO_USERS' => 'The requested users do not exist.', + 'NO_USER_SPECIFIED' => 'No username was specified.', + + // Nullar/Singular/Plural language entry. The key numbers define the number range in which a certain grammatical expression is valid. + 'NUM_ATTACHMENTS' => array( + 1 => '%d attachment', + 2 => '%d attachments', + ), + 'NUM_POSTS_IN_QUEUE' => array( + 0 => 'No posts in queue', // 0 + 1 => '1 post in queue', // 1 + 2 => '%d posts in queue', // 2+ + ), + + 'OCCUPATION' => 'Occupation', + 'OFFLINE' => 'Offline', + 'ONLINE' => 'Online', + 'ONLINE_BUDDIES' => 'Online friends', + // "... :: x registered and y hidden" + 'ONLINE_USERS_TOTAL' => array( + 1 => 'In total there is %1$d user online :: %2$s and %3$s', + 2 => 'In total there are %1$d users online :: %2$s and %3$s', + ), + // "... :: x registered, y hidden and z guests" + 'ONLINE_USERS_TOTAL_GUESTS' => array( + 1 => 'In total there is %1$d user online :: %2$s, %3$s and %4$s', + 2 => 'In total there are %1$d users online :: %2$s, %3$s and %4$s', + ), + 'OPTIONS' => 'Options', + + 'PAGE_NOT_FOUND' => 'The requested page could not be found.', + 'PAGE_OF' => 'Page %1$d of %2$d', + 'PAGE_TITLE_NUMBER' => 'Page %s', + 'PASSWORD' => 'Password', + 'PIXEL' => 'px', + 'PIXELS' => array( + 1 => '%d pixel', + 2 => '%d pixels', + ), + 'PLEASE_WAIT' => 'Please wait.', + 'PM' => 'PM', + 'PM_REPORTED' => 'Click to view report', + 'POSTING_MESSAGE' => 'Posting message in %s', + 'POSTING_PRIVATE_MESSAGE' => 'Composing private message', + 'POST' => 'Post', + 'POST_ANNOUNCEMENT' => 'Announce', + 'POST_STICKY' => 'Sticky', + 'POSTED' => 'Posted', + 'POSTED_IN_FORUM' => 'in', + 'POSTED_ON_DATE' => 'on', + 'POSTS' => 'Posts', + 'POSTS_UNAPPROVED' => 'At least one post in this topic has not been approved.', + 'POSTS_UNAPPROVED_FORUM'=> 'At least one post in this forum has not been approved.', + 'POST_BY_AUTHOR' => 'by', + 'POST_BY_FOE' => '%1$s, who is currently on your ignore list, made this post.', + 'POST_DISPLAY' => '%1$sDisplay this post%2$s.', + 'POST_DAY' => '%.2f posts per day', + 'POST_DELETED_ACTION' => 'Deleted post:', + 'POST_DELETED' => 'This post has been deleted.', + 'POST_DELETED_BY' => '%2$s deleted the post by %1$s on %3$s.', + 'POST_DELETED_BY_REASON'=> '%2$s deleted the post by %1$s on %3$s for the following reason: %4$s', + 'POST_DETAILS' => 'Post details', + 'POST_NEW_TOPIC' => 'Post new topic', + 'POST_PCT' => '%.2f%% of all posts', + 'POST_PCT_ACTIVE' => '%.2f%% of user’s posts', + 'POST_PCT_ACTIVE_OWN' => '%.2f%% of your posts', + 'POST_REPLY' => 'Post a reply', + 'POST_REPORTED' => 'Click to view report', + 'POST_SUBJECT' => 'Post subject', + 'POST_TIME' => 'Post time', + 'POST_TOPIC' => 'Post a new topic', + 'POST_UNAPPROVED_ACTION' => 'Post awaiting approval:', + 'POST_UNAPPROVED' => 'This post has not been approved.', + 'POWERED_BY' => 'Powered by %s', + 'PREVIEW' => 'Preview', + 'PREVIOUS' => 'Previous', // Used in pagination + 'PREVIOUS_STEP' => 'Previous', + 'PRIVACY' => 'Privacy policy', + 'PRIVACY_LINK' => 'Privacy', + 'PRIVATE_MESSAGE' => 'Private message', + 'PRIVATE_MESSAGES' => 'Private messages', + 'PRIVATE_MESSAGING' => 'Private messaging', + 'PROFILE' => 'User Control Panel', + + 'QUICK_LINKS' => 'Quick links', + + 'RANK' => 'Rank', + 'READING_FORUM' => 'Viewing topics in %s', + 'READING_GLOBAL_ANNOUNCE' => 'Reading global announcement', + 'READING_LINK' => 'Following forum link %s', + 'READING_TOPIC' => 'Reading topic in %s', + 'READ_PROFILE' => 'Profile', + 'REASON' => 'Reason', + 'RECORD_ONLINE_USERS' => 'Most users ever online was %1$s on %2$s', + 'REDIRECT' => 'Redirect', + 'REDIRECTS' => 'Total redirects', + 'REGISTER' => 'Register', + 'REGISTERED_USERS' => 'Registered users:', + // "... and 2 hidden users online" + 'REG_USERS_ONLINE' => array( + 1 => 'There is %1$d registered user and %2$s online', + 2 => 'There are %1$d registered users and %2$s online', + ), + 'REG_USERS_TOTAL' => array( + 1 => '%d registered', + 2 => '%d registered', + ), + 'REMOVE' => 'Remove', + 'REMOVE_INSTALL' => 'Please delete, move or rename the install directory before you use your board. If this directory is still present, only the Administration Control Panel (ACP) will be accessible.', + 'REPLIES' => 'Replies', + 'REPLY_WITH_QUOTE' => 'Reply with quote', + 'REPLYING_GLOBAL_ANNOUNCE' => 'Replying to global announcement', + 'REPLYING_MESSAGE' => 'Replying to message in %s', + 'REPORT_BY' => 'Report by', + 'REPORT_POST' => 'Report this post', + 'REPORTING_POST' => 'Reporting post', + 'RESEND_ACTIVATION' => 'Resend activation email', + 'RESET' => 'Reset', + 'RESTORE_PERMISSIONS' => 'Restore permissions', + 'RETURN_INDEX' => '%sReturn to the index page%s', + 'RETURN_FORUM' => '%sReturn to the forum last visited%s', + 'RETURN_PAGE' => '%sReturn to the previous page%s', + 'RETURN_TOPIC' => '%sReturn to the topic last visited%s', + 'RETURN_TO' => 'Return to “%s”', + 'RETURN_TO_INDEX' => 'Return to Board Index', + 'FEED' => 'Feed', + 'FEED_NEWS' => 'News', + 'FEED_TOPICS_ACTIVE' => 'Active Topics', + 'FEED_TOPICS_NEW' => 'New Topics', + 'RULES_ATTACH_CAN' => 'You can post attachments in this forum', + 'RULES_ATTACH_CANNOT' => 'You cannot post attachments in this forum', + 'RULES_DELETE_CAN' => 'You can delete your posts in this forum', + 'RULES_DELETE_CANNOT' => 'You cannot delete your posts in this forum', + 'RULES_DOWNLOAD_CAN' => 'You can download attachments in this forum', + 'RULES_DOWNLOAD_CANNOT' => 'You cannot download attachments in this forum', + 'RULES_EDIT_CAN' => 'You can edit your posts in this forum', + 'RULES_EDIT_CANNOT' => 'You cannot edit your posts in this forum', + 'RULES_LOCK_CAN' => 'You can lock your topics in this forum', + 'RULES_LOCK_CANNOT' => 'You cannot lock your topics in this forum', + 'RULES_POST_CAN' => 'You can post new topics in this forum', + 'RULES_POST_CANNOT' => 'You cannot post new topics in this forum', + 'RULES_REPLY_CAN' => 'You can reply to topics in this forum', + 'RULES_REPLY_CANNOT' => 'You cannot reply to topics in this forum', + 'RULES_VOTE_CAN' => 'You can vote in polls in this forum', + 'RULES_VOTE_CANNOT' => 'You cannot vote in polls in this forum', + + 'SEARCH' => 'Search', + 'SEARCH_MINI' => 'Search…', + 'SEARCH_ADV' => 'Advanced search', + 'SEARCH_ADV_EXPLAIN' => 'View the advanced search options', + 'SEARCH_KEYWORDS' => 'Search for keywords', + 'SEARCHING_FORUMS' => 'Searching forums', + 'SEARCH_ACTIVE_TOPICS' => 'Active topics', + 'SEARCH_FOR' => 'Search for', + 'SEARCH_FORUM' => 'Search this forum…', + 'SEARCH_NEW' => 'New posts', + 'SEARCH_POSTS_BY' => 'Search posts by', + 'SEARCH_SELF' => 'Your posts', + 'SEARCH_TOPIC' => 'Search this topic…', + 'SEARCH_UNANSWERED' => 'Unanswered topics', + 'SEARCH_UNREAD' => 'Unread posts', + 'SEARCH_USER_POSTS' => 'Search user’s posts', + 'SECONDS' => 'Seconds', + 'SEE_ALL' => 'See All', + 'SELECT' => 'Select', + 'SELECT_ALL_CODE' => 'Select all', + 'SELECT_DESTINATION_FORUM' => 'Please select a destination forum', + 'SELECT_FORUM' => 'Select a forum', + 'SEND_EMAIL' => 'Send email', // Used for submit buttons + 'SEND_EMAIL_USER' => 'Send email to %s', + 'SEND_PRIVATE_MESSAGE' => 'Send private message', + 'SETTINGS' => 'Settings', + 'SIGNATURE' => 'Signature', + 'SKIP' => 'Skip to content', + 'SKYPE' => 'Skype', + 'SMTP_NO_AUTH_SUPPORT' => 'SMTP server does not support authentication.', + 'SORRY_AUTH_READ' => 'You are not authorised to read this forum.', + 'SORRY_AUTH_READ_TOPIC' => 'You are not authorised to read this topic.', + 'SORRY_AUTH_VIEW_ATTACH' => 'You are not authorised to download this attachment.', + 'SORT_BY' => 'Sort by', + 'SORT_DIRECTION' => 'Direction', + 'SORT_JOINED' => 'Joined date', + 'SORT_LOCATION' => 'Location', + 'SORT_OPTIONS' => 'Display and sorting options', + 'SORT_RANK' => 'Rank', + 'SORT_POSTS' => 'Posts', + 'SORT_TOPIC_TITLE' => 'Topic title', + 'SORT_USERNAME' => 'Username', + 'SPLIT_TOPIC' => 'Split topic', + 'SQL_ERROR_OCCURRED' => 'An SQL error occurred while fetching this page. Please contact the %sBoard Administrator%s if this problem persists.', + 'STATISTICS' => 'Statistics', + 'START_WATCHING_FORUM' => 'Subscribe forum', + 'START_WATCHING_TOPIC' => 'Subscribe topic', + 'STOP_WATCHING_FORUM' => 'Unsubscribe forum', + 'STOP_WATCHING_TOPIC' => 'Unsubscribe topic', + 'STRING_LIST_MULTI' => '%1$s, and %2$s', + 'STRING_LIST_SIMPLE' => '%1$s and %2$s', + 'SUBFORUM' => 'Subforum', + 'SUBFORUMS' => 'Subforums', + 'SUBJECT' => 'Subject', + 'SUBMIT' => 'Submit', + + 'TB' => 'TB', + 'TERMS_LINK' => 'Terms', + 'TERMS_USE' => 'Terms of use', + 'TEST_CONNECTION' => 'Test connection', + 'THE_TEAM' => 'The team', + 'TIB' => 'TiB', + 'TIME' => 'Time', + 'TIMEOUT_PROCESSING_REQ' => 'Request timed out.', + + 'TOO_LARGE' => 'The value you entered is too large.', + 'TOO_LARGE_MAX_RECIPIENTS' => 'The value of Maximum number of allowed recipients per private message setting you entered is too large.', + + 'TOO_LONG' => 'The value you entered is too long.', + + 'TOO_LONG_CONFIRM_CODE' => 'The confirm code you entered is too long.', + 'TOO_LONG_DATEFORMAT' => 'The date format you entered is too long.', + 'TOO_LONG_JABBER' => 'The Jabber account name you entered is too long.', + 'TOO_LONG_NEW_PASSWORD' => 'The password you entered is too long.', + 'TOO_LONG_PASSWORD_CONFIRM' => 'The password confirmation you entered is too long.', + 'TOO_LONG_USER_PASSWORD' => 'The password you entered is too long.', + 'TOO_LONG_USERNAME' => 'The username you entered is too long.', + 'TOO_LONG_EMAIL' => 'The email address you entered is too long.', + + 'TOO_MANY_VOTE_OPTIONS' => 'You have tried to vote for too many options.', + + 'TOO_SHORT' => 'The value you entered is too short.', + + 'TOO_SHORT_CONFIRM_CODE' => 'The confirm code you entered is too short.', + 'TOO_SHORT_DATEFORMAT' => 'The date format you entered is too short.', + 'TOO_SHORT_JABBER' => 'The Jabber account name you entered is too short.', + 'TOO_SHORT_NEW_PASSWORD' => 'The password you entered is too short.', + 'TOO_SHORT_PASSWORD_CONFIRM' => 'The password confirmation you entered is too short.', + 'TOO_SHORT_USER_PASSWORD' => 'The password you entered is too short.', + 'TOO_SHORT_USERNAME' => 'The username you entered is too short.', + 'TOO_SHORT_EMAIL' => 'The email address you entered is too short.', + 'TOO_SHORT_EMAIL_CONFIRM' => 'The email address confirmation you entered is too short.', + 'TOO_SMALL' => 'The value you entered is too small.', + 'TOO_SMALL_MAX_RECIPIENTS' => 'The value of Maximum number of allowed recipients per private message setting you entered is too small.', + + 'TOPIC' => 'Topic', + 'TOPICS' => 'Topics', + 'TOPICS_UNAPPROVED' => 'At least one topic in this forum has not been approved.', + 'TOPIC_ICON' => 'Topic icon', + 'TOPIC_LOCKED' => 'This topic is locked, you cannot edit posts or make further replies.', + 'TOPIC_LOCKED_SHORT'=> 'Topic locked', + 'TOPIC_MOVED' => 'Moved topic', + 'TOPIC_REVIEW' => 'Topic review', + 'TOPIC_TITLE' => 'Topic title', + 'TOPIC_UNAPPROVED' => 'This topic has not been approved.', + 'TOPIC_UNAPPROVED_FORUM' => array( + 1 => 'Topic awaiting approval', + 2 => 'Topics awaiting approval', + ), + 'TOPIC_DELETED' => 'This topic has been deleted.', + 'TOTAL_ATTACHMENTS' => 'Attachment(s)', + 'TOTAL_LOGS' => array( + 1 => '%d log', + 2 => '%d logs', + ), + 'TOTAL_PMS' => array( + 1 => '%d private message in total', + 2 => '%d private messages in total', + ), + 'TOPIC_POLL' => 'This topic has a poll.', + 'TOTAL_POSTS' => 'Total posts', + 'TOTAL_POSTS_COUNT' => array( + 2 => 'Total posts %d', + ), + 'TOPIC_REPORTED' => 'This topic has been reported', + 'TOTAL_TOPICS' => array( + 2 => 'Total topics %d', + ), + 'TOTAL_USERS' => array( + 2 => 'Total members %d', + ), + 'TRACKED_PHP_ERROR' => 'Tracked PHP errors: %s', + 'TWITTER' => 'Twitter', + + 'UNABLE_GET_IMAGE_SIZE' => 'It was not possible to determine the dimensions of the image. Please verify that the URL you entered is correct.', + 'UNABLE_TO_DELIVER_FILE'=> 'Unable to deliver file.', + 'UNKNOWN_BROWSER' => 'Unknown browser', + 'UNMARK_ALL' => 'Unmark all', + 'UNREAD_MESSAGES' => 'Unread messages', + 'UNREAD_POST' => 'Unread post', + 'UNREAD_POSTS' => 'Unread posts', + 'UNWATCH_FORUM_CONFIRM' => 'Are you sure you wish to unsubscribe from this forum?', + 'UNWATCH_FORUM_DETAILED' => 'Are you sure you wish to unsubscribe from the forum “%s”?', + 'UNWATCH_TOPIC_CONFIRM' => 'Are you sure you wish to unsubscribe from this topic?', + 'UNWATCH_TOPIC_DETAILED' => 'Are you sure you wish to unsubscribe from the topic “%s”?', + 'UNWATCHED_FORUMS' => 'You are no longer subscribed to the selected forums.', + 'UNWATCHED_TOPICS' => 'You are no longer subscribed to the selected topics.', + 'UNWATCHED_FORUMS_TOPICS' => 'You are no longer subscribed to the selected entries.', + 'UPDATE' => 'Update', + 'UPLOAD_IN_PROGRESS' => 'The upload is currently in progress.', + 'URL_REDIRECT' => 'If your browser does not support meta redirection %splease click HERE to be redirected%s.', + 'USERGROUPS' => 'Groups', + 'USERNAME' => 'Username', + 'USERNAMES' => 'Usernames', + 'USER_AVATAR' => 'User avatar', + 'USER_CANNOT_READ' => 'You cannot read posts in this forum.', + 'USER_POSTS' => array( + 1 => '%d Post', + 2 => '%d Posts', + ), + 'USERS' => 'Users', + 'USE_PERMISSIONS' => 'Test out user’s permissions', + + 'USER_NEW_PERMISSION_DISALLOWED' => 'We are sorry, but you are not authorised to use this feature. You may have just registered here and may need to participate more in discussions to be able to use this feature.', + + 'VARIANT_DATE_SEPARATOR' => ' / ', // Used in date format dropdown, eg: "Today, 13:37 / 01 Jan 2007, 13:37" ... to join a relative date with calendar date + 'VIEWED' => 'Viewed', + 'VIEWED_COUNTS' => array( + 0 => 'Not viewed yet', + 1 => 'Viewed %d time', + 2 => 'Viewed %d times', + ), + 'VIEWING_CONTACT_ADMIN' => 'Viewing contact page', + 'VIEWING_FAQ' => 'Viewing FAQ', + 'VIEWING_MEMBERS' => 'Viewing member details', + 'VIEWING_ONLINE' => 'Viewing who is online', + 'VIEWING_MCP' => 'Viewing moderator control panel', + 'VIEWING_MEMBER_PROFILE' => 'Viewing member profile', + 'VIEWING_PRIVATE_MESSAGES' => 'Viewing private messages', + 'VIEWING_REGISTER' => 'Registering account', + 'VIEWING_UCP' => 'Viewing user control panel', + 'VIEWS' => 'Views', + 'VIEW_BOOKMARKS' => 'View bookmarks', + 'VIEW_FORUM_LOGS' => 'View Logs', + 'VIEW_LATEST_POST' => 'View the latest post', + 'VIEW_NEWEST_POST' => 'View first unread post', + 'VIEW_NOTES' => 'View user notes', + 'VIEW_ONLINE_TIMES' => array( + 1 => 'based on users active over the past %d minute', + 2 => 'based on users active over the past %d minutes', + ), + 'VIEW_TOPIC' => 'View topic', + 'VIEW_TOPIC_ANNOUNCEMENT' => 'Announcement: ', + 'VIEW_TOPIC_GLOBAL' => 'Global Announcement: ', + 'VIEW_TOPIC_LOCKED' => 'Locked: ', + 'VIEW_TOPIC_LOGS' => 'View logs', + 'VIEW_TOPIC_MOVED' => 'Moved: ', + 'VIEW_TOPIC_POLL' => 'Poll: ', + 'VIEW_TOPIC_STICKY' => 'Sticky: ', + 'VISIT_WEBSITE' => 'Visit website', + + 'WARNINGS' => 'Warnings', + 'WARN_USER' => 'Warn user', + 'WATCH_FORUM_CONFIRM' => 'Are you sure you wish to subscribe to this forum?', + 'WATCH_FORUM_DETAILED' => 'Are you sure you wish to subscribe to the forum “%s”?', + 'WATCH_TOPIC_CONFIRM' => 'Are you sure you wish to subscribe to this topic?', + 'WATCH_TOPIC_DETAILED' => 'Are you sure you wish to subscribe to the topic “%s”?', + 'WELCOME_SUBJECT' => 'Welcome to %s forums', + 'WEBSITE' => 'Website', + 'WHOIS' => 'Whois', + 'WHO_IS_ONLINE' => 'Who is online', + 'WRONG_PASSWORD' => 'You entered an incorrect password.', + + 'WRONG_DATA_COLOUR' => 'The colour value you entered is invalid.', + 'WRONG_DATA_JABBER' => 'The name you entered is not a valid Jabber account name.', + 'WRONG_DATA_LANG' => 'The language you specified is not valid.', + 'WRONG_DATA_POST_SD' => 'The post sort direction you specified is not valid.', + 'WRONG_DATA_POST_SK' => 'The post sort option you specified is not valid.', + 'WRONG_DATA_TOPIC_SD' => 'The topic sort direction you specified is not valid.', + 'WRONG_DATA_TOPIC_SK' => 'The topic sort option you specified is not valid.', + 'WROTE' => 'wrote', + + 'YAHOO' => 'Yahoo Messenger', + 'YOUTUBE' => 'YouTube', + 'YEAR' => 'Year', + 'YEAR_MONTH_DAY' => '(YYYY-MM-DD)', + 'YES' => 'Yes', + 'YOU_LAST_VISIT' => 'Last visit was: %s', + + 'datetime' => array( + 'TODAY' => 'Today', + 'TOMORROW' => 'Tomorrow', + 'YESTERDAY' => 'Yesterday', + 'AGO' => array( + 0 => 'less than a minute ago', + 1 => '%d minute ago', + 2 => '%d minutes ago', + ), + + 'Sunday' => 'Sunday', + 'Monday' => 'Monday', + 'Tuesday' => 'Tuesday', + 'Wednesday' => 'Wednesday', + 'Thursday' => 'Thursday', + 'Friday' => 'Friday', + 'Saturday' => 'Saturday', + + 'Sun' => 'Sun', + 'Mon' => 'Mon', + 'Tue' => 'Tue', + 'Wed' => 'Wed', + 'Thu' => 'Thu', + 'Fri' => 'Fri', + 'Sat' => 'Sat', + + 'January' => 'January', + 'February' => 'February', + 'March' => 'March', + 'April' => 'April', + 'May' => 'May', + 'June' => 'June', + 'July' => 'July', + 'August' => 'August', + 'September' => 'September', + 'October' => 'October', + 'November' => 'November', + 'December' => 'December', + + 'Jan' => 'Jan', + 'Feb' => 'Feb', + 'Mar' => 'Mar', + 'Apr' => 'Apr', + 'May_short' => 'May', // Short representation of "May". May_short used because in English the short and long date are the same for May. + 'Jun' => 'Jun', + 'Jul' => 'Jul', + 'Aug' => 'Aug', + 'Sep' => 'Sep', + 'Oct' => 'Oct', + 'Nov' => 'Nov', + 'Dec' => 'Dec', + ), + + // Timezones can be translated. We use this for the Etc/GMT timezones here, + // because they are named invers to their offset. + 'timezones' => array( + 'UTC' => 'UTC', + 'UTC_OFFSET' => 'UTC%1$s', + 'UTC_OFFSET_CURRENT' => 'UTC%1$s - %2$s', + + 'Etc/GMT-12' => 'UTC+12', + 'Etc/GMT-11' => 'UTC+11', + 'Etc/GMT-10' => 'UTC+10', + 'Etc/GMT-9' => 'UTC+9', + 'Etc/GMT-8' => 'UTC+8', + 'Etc/GMT-7' => 'UTC+7', + 'Etc/GMT-6' => 'UTC+6', + 'Etc/GMT-5' => 'UTC+5', + 'Etc/GMT-4' => 'UTC+4', + 'Etc/GMT-3' => 'UTC+3', + 'Etc/GMT-2' => 'UTC+2', + 'Etc/GMT-1' => 'UTC+1', + 'Etc/GMT+1' => 'UTC-1', + 'Etc/GMT+2' => 'UTC-2', + 'Etc/GMT+3' => 'UTC-3', + 'Etc/GMT+4' => 'UTC-4', + 'Etc/GMT+5' => 'UTC-5', + 'Etc/GMT+6' => 'UTC-6', + 'Etc/GMT+7' => 'UTC-7', + 'Etc/GMT+8' => 'UTC-8', + 'Etc/GMT+9' => 'UTC-9', + 'Etc/GMT+10' => 'UTC-10', + 'Etc/GMT+11' => 'UTC-11', + 'Etc/GMT+12' => 'UTC-12', + + 'Africa/Abidjan' => 'Africa/Abidjan', + 'Africa/Accra' => 'Africa/Accra', + 'Africa/Addis_Ababa' => 'Africa/Addis Ababa', + 'Africa/Algiers' => 'Africa/Algiers', + 'Africa/Asmara' => 'Africa/Asmara', + 'Africa/Bamako' => 'Africa/Bamako', + 'Africa/Bangui' => 'Africa/Bangui', + 'Africa/Banjul' => 'Africa/Banjul', + 'Africa/Bissau' => 'Africa/Bissau', + 'Africa/Blantyre' => 'Africa/Blantyre', + 'Africa/Brazzaville' => 'Africa/Brazzaville', + 'Africa/Bujumbura' => 'Africa/Bujumbura', + 'Africa/Cairo' => 'Africa/Cairo', + 'Africa/Casablanca' => 'Africa/Casablanca', + 'Africa/Ceuta' => 'Africa/Ceuta', + 'Africa/Conakry' => 'Africa/Conakry', + 'Africa/Dakar' => 'Africa/Dakar', + 'Africa/Dar_es_Salaam' => 'Africa/Dar es Salaam', + 'Africa/Djibouti' => 'Africa/Djibouti', + 'Africa/Douala' => 'Africa/Douala', + 'Africa/El_Aaiun' => 'Africa/El Aaiun', + 'Africa/Freetown' => 'Africa/Freetown', + 'Africa/Gaborone' => 'Africa/Gaborone', + 'Africa/Harare' => 'Africa/Harare', + 'Africa/Johannesburg' => 'Africa/Johannesburg', + 'Africa/Juba' => 'Africa/Juba', + 'Africa/Kampala' => 'Africa/Kampala', + 'Africa/Khartoum' => 'Africa/Khartoum', + 'Africa/Kigali' => 'Africa/Kigali', + 'Africa/Kinshasa' => 'Africa/Kinshasa', + 'Africa/Lagos' => 'Africa/Lagos', + 'Africa/Libreville' => 'Africa/Libreville', + 'Africa/Lome' => 'Africa/Lome', + 'Africa/Luanda' => 'Africa/Luanda', + 'Africa/Lubumbashi' => 'Africa/Lubumbashi', + 'Africa/Lusaka' => 'Africa/Lusaka', + 'Africa/Malabo' => 'Africa/Malabo', + 'Africa/Maputo' => 'Africa/Maputo', + 'Africa/Maseru' => 'Africa/Maseru', + 'Africa/Mbabane' => 'Africa/Mbabane', + 'Africa/Mogadishu' => 'Africa/Mogadishu', + 'Africa/Monrovia' => 'Africa/Monrovia', + 'Africa/Nairobi' => 'Africa/Nairobi', + 'Africa/Ndjamena' => 'Africa/Ndjamena', + 'Africa/Niamey' => 'Africa/Niamey', + 'Africa/Nouakchott' => 'Africa/Nouakchott', + 'Africa/Ouagadougou' => 'Africa/Ouagadougou', + 'Africa/Porto-Novo' => 'Africa/Porto-Novo', + 'Africa/Sao_Tome' => 'Africa/Sao Tome', + 'Africa/Tripoli' => 'Africa/Tripoli', + 'Africa/Tunis' => 'Africa/Tunis', + 'Africa/Windhoek' => 'Africa/Windhoek', + + 'America/Adak' => 'America/Adak', + 'America/Anchorage' => 'America/Anchorage', + 'America/Anguilla' => 'America/Anguilla', + 'America/Antigua' => 'America/Antigua', + 'America/Araguaina' => 'America/Araguaina', + + 'America/Argentina/Buenos_Aires' => 'America/Argentina/Buenos Aires', + 'America/Argentina/Catamarca' => 'America/Argentina/Catamarca', + 'America/Argentina/Cordoba' => 'America/Argentina/Cordoba', + 'America/Argentina/Jujuy' => 'America/Argentina/Jujuy', + 'America/Argentina/La_Rioja' => 'America/Argentina/La Rioja', + 'America/Argentina/Mendoza' => 'America/Argentina/Mendoza', + 'America/Argentina/Rio_Gallegos' => 'America/Argentina/Rio Gallegos', + 'America/Argentina/Salta' => 'America/Argentina/Salta', + 'America/Argentina/San_Juan' => 'America/Argentina/San Juan', + 'America/Argentina/San_Luis' => 'America/Argentina/San Luis', + 'America/Argentina/Tucuman' => 'America/Argentina/Tucuman', + 'America/Argentina/Ushuaia' => 'America/Argentina/Ushuaia', + + 'America/Aruba' => 'America/Aruba', + 'America/Asuncion' => 'America/Asuncion', + 'America/Atikokan' => 'America/Atikokan', + 'America/Bahia' => 'America/Bahia', + 'America/Bahia_Banderas' => 'America/Bahia Banderas', + 'America/Barbados' => 'America/Barbados', + 'America/Belem' => 'America/Belem', + 'America/Belize' => 'America/Belize', + 'America/Blanc-Sablon' => 'America/Blanc-Sablon', + 'America/Boa_Vista' => 'America/Boa Vista', + 'America/Bogota' => 'America/Bogota', + 'America/Boise' => 'America/Boise', + 'America/Cambridge_Bay' => 'America/Cambridge Bay', + 'America/Campo_Grande' => 'America/Campo Grande', + 'America/Cancun' => 'America/Cancun', + 'America/Caracas' => 'America/Caracas', + 'America/Cayenne' => 'America/Cayenne', + 'America/Cayman' => 'America/Cayman', + 'America/Chicago' => 'America/Chicago', + 'America/Chihuahua' => 'America/Chihuahua', + 'America/Costa_Rica' => 'America/Costa Rica', + 'America/Creston' => 'America/Creston', + 'America/Cuiaba' => 'America/Cuiaba', + 'America/Curacao' => 'America/Curacao', + 'America/Danmarkshavn' => 'America/Danmarkshavn', + 'America/Dawson' => 'America/Dawson', + 'America/Dawson_Creek' => 'America/Dawson Creek', + 'America/Denver' => 'America/Denver', + 'America/Detroit' => 'America/Detroit', + 'America/Dominica' => 'America/Dominica', + 'America/Edmonton' => 'America/Edmonton', + 'America/Eirunepe' => 'America/Eirunepe', + 'America/El_Salvador' => 'America/El Salvador', + 'America/Fortaleza' => 'America/Fortaleza', + 'America/Glace_Bay' => 'America/Glace Bay', + 'America/Godthab' => 'America/Godthab', + 'America/Goose_Bay' => 'America/Goose Bay', + 'America/Grand_Turk' => 'America/Grand Turk', + 'America/Grenada' => 'America/Grenada', + 'America/Guadeloupe' => 'America/Guadeloupe', + 'America/Guatemala' => 'America/Guatemala', + 'America/Guayaquil' => 'America/Guayaquil', + 'America/Guyana' => 'America/Guyana', + 'America/Halifax' => 'America/Halifax', + 'America/Havana' => 'America/Havana', + 'America/Hermosillo' => 'America/Hermosillo', + 'America/Indiana/Indianapolis' => 'America/Indiana/Indianapolis', + 'America/Indiana/Knox' => 'America/Indiana/Knox', + 'America/Indiana/Marengo' => 'America/Indiana/Marengo', + 'America/Indiana/Petersburg' => 'America/Indiana/Petersburg', + 'America/Indiana/Tell_City' => 'America/Indiana/Tell City', + 'America/Indiana/Vevay' => 'America/Indiana/Vevay', + 'America/Indiana/Vincennes' => 'America/Indiana/Vincennes', + 'America/Indiana/Winamac' => 'America/Indiana/Winamac', + 'America/Inuvik' => 'America/Inuvik', + 'America/Iqaluit' => 'America/Iqaluit', + 'America/Jamaica' => 'America/Jamaica', + 'America/Juneau' => 'America/Juneau', + 'America/Kentucky/Louisville' => 'America/Kentucky/Louisville', + 'America/Kentucky/Monticello' => 'America/Kentucky/Monticello', + 'America/Kralendijk' => 'America/Kralendijk', + 'America/La_Paz' => 'America/La Paz', + 'America/Lima' => 'America/Lima', + 'America/Los_Angeles' => 'America/Los Angeles', + 'America/Lower_Princes' => 'America/Lower Princes', + 'America/Maceio' => 'America/Maceio', + 'America/Managua' => 'America/Managua', + 'America/Manaus' => 'America/Manaus', + 'America/Marigot' => 'America/Marigot', + 'America/Martinique' => 'America/Martinique', + 'America/Matamoros' => 'America/Matamoros', + 'America/Mazatlan' => 'America/Mazatlan', + 'America/Menominee' => 'America/Menominee', + 'America/Merida' => 'America/Merida', + 'America/Metlakatla' => 'America/Metlakatla', + 'America/Mexico_City' => 'America/Mexico City', + 'America/Miquelon' => 'America/Miquelon', + 'America/Moncton' => 'America/Moncton', + 'America/Monterrey' => 'America/Monterrey', + 'America/Montevideo' => 'America/Montevideo', + 'America/Montreal' => 'America/Montreal', + 'America/Montserrat' => 'America/Montserrat', + 'America/Nassau' => 'America/Nassau', + 'America/New_York' => 'America/New York', + 'America/Nipigon' => 'America/Nipigon', + 'America/Nome' => 'America/Nome', + 'America/Noronha' => 'America/Noronha', + 'America/North_Dakota/Beulah' => 'America/North Dakota/Beulah', + 'America/North_Dakota/Center' => 'America/North Dakota/Center', + 'America/North_Dakota/New_Salem' => 'America/North Dakota/New Salem', + 'America/Ojinaga' => 'America/Ojinaga', + 'America/Panama' => 'America/Panama', + 'America/Pangnirtung' => 'America/Pangnirtung', + 'America/Paramaribo' => 'America/Paramaribo', + 'America/Phoenix' => 'America/Phoenix', + 'America/Port-au-Prince' => 'America/Port-au-Prince', + 'America/Port_of_Spain' => 'America/Port of Spain', + 'America/Porto_Velho' => 'America/Porto Velho', + 'America/Puerto_Rico' => 'America/Puerto Rico', + 'America/Rainy_River' => 'America/Rainy River', + 'America/Rankin_Inlet' => 'America/Rankin Inlet', + 'America/Recife' => 'America/Recife', + 'America/Regina' => 'America/Regina', + 'America/Resolute' => 'America/Resolute', + 'America/Rio_Branco' => 'America/Rio Branco', + 'America/Santa_Isabel' => 'America/Santa Isabel', + 'America/Santarem' => 'America/Santarem', + 'America/Santiago' => 'America/Santiago', + 'America/Santo_Domingo' => 'America/Santo Domingo', + 'America/Sao_Paulo' => 'America/Sao Paulo', + 'America/Scoresbysund' => 'America/Scoresbysund', + 'America/Shiprock' => 'America/Shiprock', + 'America/Sitka' => 'America/Sitka', + 'America/St_Barthelemy' => 'America/St. Barthelemy', + 'America/St_Johns' => 'America/St. Johns', + 'America/St_Kitts' => 'America/St. Kitts', + 'America/St_Lucia' => 'America/St. Lucia', + 'America/St_Thomas' => 'America/St. Thomas', + 'America/St_Vincent' => 'America/St. Vincent', + 'America/Swift_Current' => 'America/Swift Current', + 'America/Tegucigalpa' => 'America/Tegucigalpa', + 'America/Thule' => 'America/Thule', + 'America/Thunder_Bay' => 'America/Thunder Bay', + 'America/Tijuana' => 'America/Tijuana', + 'America/Toronto' => 'America/Toronto', + 'America/Tortola' => 'America/Tortola', + 'America/Vancouver' => 'America/Vancouver', + 'America/Whitehorse' => 'America/Whitehorse', + 'America/Winnipeg' => 'America/Winnipeg', + 'America/Yakutat' => 'America/Yakutat', + 'America/Yellowknife' => 'America/Yellowknife', + + 'Antarctica/Casey' => 'Antarctica/Casey', + 'Antarctica/Davis' => 'Antarctica/Davis', + 'Antarctica/DumontDUrville' => 'Antarctica/DumontDUrville', + 'Antarctica/Macquarie' => 'Antarctica/Macquarie', + 'Antarctica/Mawson' => 'Antarctica/Mawson', + 'Antarctica/McMurdo' => 'Antarctica/McMurdo', + 'Antarctica/Palmer' => 'Antarctica/Palmer', + 'Antarctica/Rothera' => 'Antarctica/Rothera', + 'Antarctica/South_Pole' => 'Antarctica/South Pole', + 'Antarctica/Syowa' => 'Antarctica/Syowa', + 'Antarctica/Vostok' => 'Antarctica/Vostok', + + 'Arctic/Longyearbyen' => 'Arctic/Longyearbyen', + + 'Asia/Aden' => 'Asia/Aden', + 'Asia/Almaty' => 'Asia/Almaty', + 'Asia/Amman' => 'Asia/Amman', + 'Asia/Anadyr' => 'Asia/Anadyr', + 'Asia/Aqtau' => 'Asia/Aqtau', + 'Asia/Aqtobe' => 'Asia/Aqtobe', + 'Asia/Ashgabat' => 'Asia/Ashgabat', + 'Asia/Baghdad' => 'Asia/Baghdad', + 'Asia/Bahrain' => 'Asia/Bahrain', + 'Asia/Baku' => 'Asia/Baku', + 'Asia/Bangkok' => 'Asia/Bangkok', + 'Asia/Beirut' => 'Asia/Beirut', + 'Asia/Bishkek' => 'Asia/Bishkek', + 'Asia/Brunei' => 'Asia/Brunei', + 'Asia/Choibalsan' => 'Asia/Choibalsan', + 'Asia/Chongqing' => 'Asia/Chongqing', + 'Asia/Colombo' => 'Asia/Colombo', + 'Asia/Damascus' => 'Asia/Damascus', + 'Asia/Dhaka' => 'Asia/Dhaka', + 'Asia/Dili' => 'Asia/Dili', + 'Asia/Dubai' => 'Asia/Dubai', + 'Asia/Dushanbe' => 'Asia/Dushanbe', + 'Asia/Gaza' => 'Asia/Gaza', + 'Asia/Harbin' => 'Asia/Harbin', + 'Asia/Hebron' => 'Asia/Hebron', + 'Asia/Ho_Chi_Minh' => 'Asia/Ho Chi Minh', + 'Asia/Hong_Kong' => 'Asia/Hong Kong', + 'Asia/Hovd' => 'Asia/Hovd', + 'Asia/Irkutsk' => 'Asia/Irkutsk', + 'Asia/Jakarta' => 'Asia/Jakarta', + 'Asia/Jayapura' => 'Asia/Jayapura', + 'Asia/Jerusalem' => 'Asia/Jerusalem', + 'Asia/Kabul' => 'Asia/Kabul', + 'Asia/Kamchatka' => 'Asia/Kamchatka', + 'Asia/Karachi' => 'Asia/Karachi', + 'Asia/Kashgar' => 'Asia/Kashgar', + 'Asia/Kathmandu' => 'Asia/Kathmandu', + 'Asia/Khandyga' => 'Asia/Khandyga', + 'Asia/Kolkata' => 'Asia/Kolkata', + 'Asia/Krasnoyarsk' => 'Asia/Krasnoyarsk', + 'Asia/Kuala_Lumpur' => 'Asia/Kuala Lumpur', + 'Asia/Kuching' => 'Asia/Kuching', + 'Asia/Kuwait' => 'Asia/Kuwait', + 'Asia/Macau' => 'Asia/Macau', + 'Asia/Magadan' => 'Asia/Magadan', + 'Asia/Makassar' => 'Asia/Makassar', + 'Asia/Manila' => 'Asia/Manila', + 'Asia/Muscat' => 'Asia/Muscat', + 'Asia/Nicosia' => 'Asia/Nicosia', + 'Asia/Novokuznetsk' => 'Asia/Novokuznetsk', + 'Asia/Novosibirsk' => 'Asia/Novosibirsk', + 'Asia/Omsk' => 'Asia/Omsk', + 'Asia/Oral' => 'Asia/Oral', + 'Asia/Phnom_Penh' => 'Asia/Phnom Penh', + 'Asia/Pontianak' => 'Asia/Pontianak', + 'Asia/Pyongyang' => 'Asia/Pyongyang', + 'Asia/Qatar' => 'Asia/Qatar', + 'Asia/Qyzylorda' => 'Asia/Qyzylorda', + 'Asia/Rangoon' => 'Asia/Rangoon', + 'Asia/Riyadh' => 'Asia/Riyadh', + 'Asia/Sakhalin' => 'Asia/Sakhalin', + 'Asia/Samarkand' => 'Asia/Samarkand', + 'Asia/Seoul' => 'Asia/Seoul', + 'Asia/Shanghai' => 'Asia/Shanghai', + 'Asia/Singapore' => 'Asia/Singapore', + 'Asia/Taipei' => 'Asia/Taipei', + 'Asia/Tashkent' => 'Asia/Tashkent', + 'Asia/Tbilisi' => 'Asia/Tbilisi', + 'Asia/Tehran' => 'Asia/Tehran', + 'Asia/Thimphu' => 'Asia/Thimphu', + 'Asia/Tokyo' => 'Asia/Tokyo', + 'Asia/Ulaanbaatar' => 'Asia/Ulaanbaatar', + 'Asia/Urumqi' => 'Asia/Urumqi', + 'Asia/Ust-Nera' => 'Asia/Ust-Nera', + 'Asia/Vientiane' => 'Asia/Vientiane', + 'Asia/Vladivostok' => 'Asia/Vladivostok', + 'Asia/Yakutsk' => 'Asia/Yakutsk', + 'Asia/Yekaterinburg' => 'Asia/Yekaterinburg', + 'Asia/Yerevan' => 'Asia/Yerevan', + + 'Atlantic/Azores' => 'Atlantic/Azores', + 'Atlantic/Bermuda' => 'Atlantic/Bermuda', + 'Atlantic/Canary' => 'Atlantic/Canary', + 'Atlantic/Cape_Verde' => 'Atlantic/Cape Verde', + 'Atlantic/Faroe' => 'Atlantic/Faroe', + 'Atlantic/Madeira' => 'Atlantic/Madeira', + 'Atlantic/Reykjavik' => 'Atlantic/Reykjavik', + 'Atlantic/South_Georgia' => 'Atlantic/South Georgia', + 'Atlantic/St_Helena' => 'Atlantic/St. Helena', + 'Atlantic/Stanley' => 'Atlantic/Stanley', + + 'Australia/Adelaide' => 'Australia/Adelaide', + 'Australia/Brisbane' => 'Australia/Brisbane', + 'Australia/Broken_Hill' => 'Australia/Broken Hill', + 'Australia/Currie' => 'Australia/Currie', + 'Australia/Darwin' => 'Australia/Darwin', + 'Australia/Eucla' => 'Australia/Eucla', + 'Australia/Hobart' => 'Australia/Hobart', + 'Australia/Lindeman' => 'Australia/Lindeman', + 'Australia/Lord_Howe' => 'Australia/Lord Howe', + 'Australia/Melbourne' => 'Australia/Melbourne', + 'Australia/Perth' => 'Australia/Perth', + 'Australia/Sydney' => 'Australia/Sydney', + + 'Europe/Amsterdam' => 'Europe/Amsterdam', + 'Europe/Andorra' => 'Europe/Andorra', + 'Europe/Athens' => 'Europe/Athens', + 'Europe/Belgrade' => 'Europe/Belgrade', + 'Europe/Berlin' => 'Europe/Berlin', + 'Europe/Bratislava' => 'Europe/Bratislava', + 'Europe/Brussels' => 'Europe/Brussels', + 'Europe/Bucharest' => 'Europe/Bucharest', + 'Europe/Budapest' => 'Europe/Budapest', + 'Europe/Busingen' => 'Europe/Busingen', + 'Europe/Chisinau' => 'Europe/Chisinau', + 'Europe/Copenhagen' => 'Europe/Copenhagen', + 'Europe/Dublin' => 'Europe/Dublin', + 'Europe/Gibraltar' => 'Europe/Gibraltar', + 'Europe/Guernsey' => 'Europe/Guernsey', + 'Europe/Helsinki' => 'Europe/Helsinki', + 'Europe/Isle_of_Man' => 'Europe/Isle of Man', + 'Europe/Istanbul' => 'Europe/Istanbul', + 'Europe/Jersey' => 'Europe/Jersey', + 'Europe/Kaliningrad' => 'Europe/Kaliningrad', + 'Europe/Kiev' => 'Europe/Kiev', + 'Europe/Lisbon' => 'Europe/Lisbon', + 'Europe/Ljubljana' => 'Europe/Ljubljana', + 'Europe/London' => 'Europe/London', + 'Europe/Luxembourg' => 'Europe/Luxembourg', + 'Europe/Madrid' => 'Europe/Madrid', + 'Europe/Malta' => 'Europe/Malta', + 'Europe/Mariehamn' => 'Europe/Mariehamn', + 'Europe/Minsk' => 'Europe/Minsk', + 'Europe/Monaco' => 'Europe/Monaco', + 'Europe/Moscow' => 'Europe/Moscow', + 'Europe/Oslo' => 'Europe/Oslo', + 'Europe/Paris' => 'Europe/Paris', + 'Europe/Podgorica' => 'Europe/Podgorica', + 'Europe/Prague' => 'Europe/Prague', + 'Europe/Riga' => 'Europe/Riga', + 'Europe/Rome' => 'Europe/Rome', + 'Europe/Samara' => 'Europe/Samara', + 'Europe/San_Marino' => 'Europe/San Marino', + 'Europe/Sarajevo' => 'Europe/Sarajevo', + 'Europe/Simferopol' => 'Europe/Simferopol', + 'Europe/Skopje' => 'Europe/Skopje', + 'Europe/Sofia' => 'Europe/Sofia', + 'Europe/Stockholm' => 'Europe/Stockholm', + 'Europe/Tallinn' => 'Europe/Tallinn', + 'Europe/Tirane' => 'Europe/Tirane', + 'Europe/Uzhgorod' => 'Europe/Uzhgorod', + 'Europe/Vaduz' => 'Europe/Vaduz', + 'Europe/Vatican' => 'Europe/Vatican', + 'Europe/Vienna' => 'Europe/Vienna', + 'Europe/Vilnius' => 'Europe/Vilnius', + 'Europe/Volgograd' => 'Europe/Volgograd', + 'Europe/Warsaw' => 'Europe/Warsaw', + 'Europe/Zagreb' => 'Europe/Zagreb', + 'Europe/Zaporozhye' => 'Europe/Zaporozhye', + 'Europe/Zurich' => 'Europe/Zurich', + + 'Indian/Antananarivo' => 'Indian/Antananarivo', + 'Indian/Chagos' => 'Indian/Chagos', + 'Indian/Christmas' => 'Indian/Christmas', + 'Indian/Cocos' => 'Indian/Cocos', + 'Indian/Comoro' => 'Indian/Comoro', + 'Indian/Kerguelen' => 'Indian/Kerguelen', + 'Indian/Mahe' => 'Indian/Mahe', + 'Indian/Maldives' => 'Indian/Maldives', + 'Indian/Mauritius' => 'Indian/Mauritius', + 'Indian/Mayotte' => 'Indian/Mayotte', + 'Indian/Reunion' => 'Indian/Reunion', + + 'Pacific/Apia' => 'Pacific/Apia', + 'Pacific/Auckland' => 'Pacific/Auckland', + 'Pacific/Chatham' => 'Pacific/Chatham', + 'Pacific/Chuuk' => 'Pacific/Chuuk', + 'Pacific/Easter' => 'Pacific/Easter', + 'Pacific/Efate' => 'Pacific/Efate', + 'Pacific/Enderbury' => 'Pacific/Enderbury', + 'Pacific/Fakaofo' => 'Pacific/Fakaofo', + 'Pacific/Fiji' => 'Pacific/Fiji', + 'Pacific/Funafuti' => 'Pacific/Funafuti', + 'Pacific/Galapagos' => 'Pacific/Galapagos', + 'Pacific/Gambier' => 'Pacific/Gambier', + 'Pacific/Guadalcanal' => 'Pacific/Guadalcanal', + 'Pacific/Guam' => 'Pacific/Guam', + 'Pacific/Honolulu' => 'Pacific/Honolulu', + 'Pacific/Johnston' => 'Pacific/Johnston', + 'Pacific/Kiritimati' => 'Pacific/Kiritimati', + 'Pacific/Kosrae' => 'Pacific/Kosrae', + 'Pacific/Kwajalein' => 'Pacific/Kwajalein', + 'Pacific/Majuro' => 'Pacific/Majuro', + 'Pacific/Marquesas' => 'Pacific/Marquesas', + 'Pacific/Midway' => 'Pacific/Midway', + 'Pacific/Nauru' => 'Pacific/Nauru', + 'Pacific/Niue' => 'Pacific/Niue', + 'Pacific/Norfolk' => 'Pacific/Norfolk', + 'Pacific/Noumea' => 'Pacific/Noumea', + 'Pacific/Pago_Pago' => 'Pacific/Pago Pago', + 'Pacific/Palau' => 'Pacific/Palau', + 'Pacific/Pitcairn' => 'Pacific/Pitcairn', + 'Pacific/Pohnpei' => 'Pacific/Pohnpei', + 'Pacific/Port_Moresby' => 'Pacific/Port Moresby', + 'Pacific/Rarotonga' => 'Pacific/Rarotonga', + 'Pacific/Saipan' => 'Pacific/Saipan', + 'Pacific/Tahiti' => 'Pacific/Tahiti', + 'Pacific/Tarawa' => 'Pacific/Tarawa', + 'Pacific/Tongatapu' => 'Pacific/Tongatapu', + 'Pacific/Wake' => 'Pacific/Wake', + 'Pacific/Wallis' => 'Pacific/Wallis', + ), + + // The value is only an example and will get replaced by the current time on view + 'dateformats' => array( + 'd M Y, H:i' => '01 Jan 2007, 13:37', + 'd M Y H:i' => '01 Jan 2007 13:37', + 'M jS, \'y, H:i' => 'Jan 1st, \'07, 13:37', + 'D M d, Y g:i a' => 'Mon Jan 01, 2007 1:37 pm', + 'F jS, Y, g:i a' => 'January 1st, 2007, 1:37 pm', + '|d M Y|, H:i' => 'Today, 13:37 / 01 Jan 2007, 13:37', + '|F jS, Y|, g:i a' => 'Today, 1:37 pm / January 1st, 2007, 1:37 pm', + ), + + // The default dateformat which will be used on new installs in this language + // Translators should change this if a the usual date format is different + 'default_dateformat' => 'D M d, Y g:i a', // Mon Jan 01, 2007 1:37 pm + +)); diff --git a/language/en/email/admin_activate.txt b/language/en/email/admin_activate.txt new file mode 100644 index 0000000..a53ab12 --- /dev/null +++ b/language/en/email/admin_activate.txt @@ -0,0 +1,13 @@ +Subject: Activate user account + +Hello, + +The account owned by "{USERNAME}" has been deactivated or newly created, you should check the details of this user (if required) and handle it appropriately. + +Use this link to view the user's profile: +{U_USER_DETAILS} + +Use this link to activate the account: +{U_ACTIVATE} + +{EMAIL_SIG} diff --git a/language/en/email/admin_send_email.txt b/language/en/email/admin_send_email.txt new file mode 100644 index 0000000..7345a04 --- /dev/null +++ b/language/en/email/admin_send_email.txt @@ -0,0 +1,13 @@ + +The following is an email sent to you by an administrator of "{SITENAME}". If this message is spam, contains abusive or other comments you find offensive please contact the webmaster of the board at the following address: + +{CONTACT_EMAIL} + +Include this full email (particularly the headers). + +Message sent to you follows: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{MESSAGE} + +{EMAIL_SIG} diff --git a/language/en/email/admin_welcome_activated.txt b/language/en/email/admin_welcome_activated.txt new file mode 100644 index 0000000..2e136ac --- /dev/null +++ b/language/en/email/admin_welcome_activated.txt @@ -0,0 +1,9 @@ +Subject: Account activated + +Hello {USERNAME}, + +Your account on "{SITENAME}" has been activated by an administrator, you may login now. + +Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +{EMAIL_SIG} diff --git a/language/en/email/admin_welcome_inactive.txt b/language/en/email/admin_welcome_inactive.txt new file mode 100644 index 0000000..f9a57b9 --- /dev/null +++ b/language/en/email/admin_welcome_inactive.txt @@ -0,0 +1,19 @@ +Subject: Welcome to "{SITENAME}" + +{WELCOME_MSG} + +Please keep this email for your records. Your account information is as follows: + +---------------------------- +Username: {USERNAME} + +Board URL: {U_BOARD} +---------------------------- + +Your account is currently inactive and will need to be approved by an administrator before you can log in. Another email will be sent when this has occurred. + +Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +Thank you for registering. + +{EMAIL_SIG} diff --git a/language/en/email/bookmark.txt b/language/en/email/bookmark.txt new file mode 100644 index 0000000..95f17b5 --- /dev/null +++ b/language/en/email/bookmark.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because a topic you bookmarked, "{TOPIC_TITLE}" at "{SITENAME}", has received a reply since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. + +If you want to view the newest post made since your last visit, click the following link: +{U_NEWEST_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/language/en/email/contact_admin.txt b/language/en/email/contact_admin.txt new file mode 100644 index 0000000..c895c4d --- /dev/null +++ b/language/en/email/contact_admin.txt @@ -0,0 +1,23 @@ + +Hello {TO_USERNAME}, + +The following is an e-mail sent to you through the administration contact page on "{SITENAME}". + + +The message has been sent from an account on the site. +Username: {FROM_USERNAME} +E-mail address: {FROM_EMAIL_ADDRESS} +IP Address: {FROM_IP_ADDRESS} +Profile: {U_FROM_PROFILE} + +The message was sent from a guest who specified the following contact information: +Name: {FROM_USERNAME} +E-mail address: {FROM_EMAIL_ADDRESS} +IP Address: {FROM_IP_ADDRESS} + + + +Message sent to you follows +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{MESSAGE} diff --git a/language/en/email/coppa_resend_inactive.txt b/language/en/email/coppa_resend_inactive.txt new file mode 100644 index 0000000..915534a --- /dev/null +++ b/language/en/email/coppa_resend_inactive.txt @@ -0,0 +1,42 @@ +Subject: Welcome to "{SITENAME}" + +{WELCOME_MSG} + +In compliance with the COPPA, your account is currently inactive. + +Please print this message and have your parent or guardian sign and date it. Then fax it to: + +{FAX_INFO} + +OR mail it to: + +{MAIL_INFO} + +------------------------------ CUT HERE ------------------------------ +Permission to participate at "{SITENAME}" - {U_BOARD} + +Username: {USERNAME} +Email: {EMAIL_ADDRESS} + +I HAVE REVIEWED THE INFORMATION PROVIDED BY MY CHILD AND HEREBY GRANT PERMISSION TO "{SITENAME}" TO STORE THIS INFORMATION. +I UNDERSTAND THIS INFORMATION CAN BE CHANGED AT ANY TIME BY ENTERING A PASSWORD. +I UNDERSTAND THAT I MAY REQUEST FOR THIS INFORMATION TO BE REMOVED FROM "{SITENAME}" AT ANY TIME. + + +Parent or guardian +(print your name here): _____________________ + +(sign here): __________________ + +Date: _______________ + +------------------------------ CUT HERE ------------------------------ + + +Once the administrator has received the above form via fax or regular mail, your account will be activated. + +Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +Thank you for registering. + +{EMAIL_SIG} diff --git a/language/en/email/coppa_welcome_inactive.txt b/language/en/email/coppa_welcome_inactive.txt new file mode 100644 index 0000000..915534a --- /dev/null +++ b/language/en/email/coppa_welcome_inactive.txt @@ -0,0 +1,42 @@ +Subject: Welcome to "{SITENAME}" + +{WELCOME_MSG} + +In compliance with the COPPA, your account is currently inactive. + +Please print this message and have your parent or guardian sign and date it. Then fax it to: + +{FAX_INFO} + +OR mail it to: + +{MAIL_INFO} + +------------------------------ CUT HERE ------------------------------ +Permission to participate at "{SITENAME}" - {U_BOARD} + +Username: {USERNAME} +Email: {EMAIL_ADDRESS} + +I HAVE REVIEWED THE INFORMATION PROVIDED BY MY CHILD AND HEREBY GRANT PERMISSION TO "{SITENAME}" TO STORE THIS INFORMATION. +I UNDERSTAND THIS INFORMATION CAN BE CHANGED AT ANY TIME BY ENTERING A PASSWORD. +I UNDERSTAND THAT I MAY REQUEST FOR THIS INFORMATION TO BE REMOVED FROM "{SITENAME}" AT ANY TIME. + + +Parent or guardian +(print your name here): _____________________ + +(sign here): __________________ + +Date: _______________ + +------------------------------ CUT HERE ------------------------------ + + +Once the administrator has received the above form via fax or regular mail, your account will be activated. + +Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +Thank you for registering. + +{EMAIL_SIG} diff --git a/language/en/email/email_notify.txt b/language/en/email/email_notify.txt new file mode 100644 index 0000000..43c9098 --- /dev/null +++ b/language/en/email/email_notify.txt @@ -0,0 +1,17 @@ +Subject: "{SITENAME}" - Email a friend + +Hello {TO_USERNAME}, + +This email was sent from "{SITENAME}" by {FROM_USERNAME} who thought you may be interested in the following topic: + +{TOPIC_NAME} + +You can find it at: + +{U_TOPIC} + +A message from {FROM_USERNAME} may also be included below. Please note that this message has not been seen or approved by the board administrators. If you wish to complain about having received this email please contact the board administrator at {BOARD_CONTACT}. Please quote the message headers when contacting this address. + +---------- + +{MESSAGE} diff --git a/language/en/email/forum_notify.txt b/language/en/email/forum_notify.txt new file mode 100644 index 0000000..1dfe8c6 --- /dev/null +++ b/language/en/email/forum_notify.txt @@ -0,0 +1,20 @@ +Subject: Forum post notification - "{FORUM_NAME}" +List-Unsubscribe: <{U_STOP_WATCHING_FORUM}> + +Hello {USERNAME}, + +You are receiving this notification because you are watching the forum "{FORUM_NAME}" at "{SITENAME}". This forum has received a new reply to the topic "{TOPIC_TITLE}" by {AUTHOR_NAME} since your last visit. You can use the following link to view the last unread reply, no more notifications will be sent until you visit the topic. + +{U_NEWEST_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to watch this forum you can either click the "Unsubscribe forum" link found in the forum above, or by clicking the following link: + +{U_STOP_WATCHING_FORUM} + +{EMAIL_SIG} diff --git a/language/en/email/group_added.txt b/language/en/email/group_added.txt new file mode 100644 index 0000000..9464aae --- /dev/null +++ b/language/en/email/group_added.txt @@ -0,0 +1,11 @@ +Subject: You have been added to this usergroup + +Congratulations, + +You have been added to the "{GROUP_NAME}" group on "{SITENAME}". +This action was done by a group leader or the site administrator, contact them for more information. + +You can view your groups information here: +{U_GROUP} + +{EMAIL_SIG} diff --git a/language/en/email/group_request.txt b/language/en/email/group_request.txt new file mode 100644 index 0000000..7584083 --- /dev/null +++ b/language/en/email/group_request.txt @@ -0,0 +1,10 @@ +Subject: A request to join your group has been made + +Dear {USERNAME}, + +The user "{REQUEST_USERNAME}" has requested to join the group "{GROUP_NAME}" you moderate on "{SITENAME}". +To approve or deny this request for group membership please visit the following link: + +{U_PENDING} + +{EMAIL_SIG} diff --git a/language/en/email/index.htm b/language/en/email/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/language/en/email/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/language/en/email/installed.txt b/language/en/email/installed.txt new file mode 100644 index 0000000..9344458 --- /dev/null +++ b/language/en/email/installed.txt @@ -0,0 +1,19 @@ +Subject: phpBB installed + +Congratulations, + +You have successfully installed phpBB on your server. + +This email contains important information regarding your installation and should be kept for reference. Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +---------------------------- +Username: {USERNAME} + +Board URL: {U_BOARD} +---------------------------- + +Useful information regarding the phpBB software can be found in the docs folder of your installation and on phpBB.com's support page - https://www.phpbb.com/support/ + +In order to keep your board safe and secure, we highly recommended keeping current with software releases. For your convenience, a mailing list is available at the page referenced above. + +{EMAIL_SIG} diff --git a/language/en/email/newtopic_notify.txt b/language/en/email/newtopic_notify.txt new file mode 100644 index 0000000..0dfc9e4 --- /dev/null +++ b/language/en/email/newtopic_notify.txt @@ -0,0 +1,18 @@ +Subject: New topic notification - "{FORUM_NAME}" +List-Unsubscribe: <{U_STOP_WATCHING_FORUM}> + +Hello {USERNAME}, + +You are receiving this notification because you are watching the forum "{FORUM_NAME}" at "{SITENAME}". This forum has received a new topic by {AUTHOR_NAME} since your last visit, "{TOPIC_TITLE}". You can use the following link to view the forum, no more notifications will be sent until you visit the forum. + +{U_FORUM} + +To see new topic directly, visit the following link: + +{U_TOPIC} + +If you no longer wish to watch this forum you can either click the "Unsubscribe forum" link found in the forum above, or by clicking the following link: + +{U_STOP_WATCHING_FORUM} + +{EMAIL_SIG} diff --git a/language/en/email/pm_report_closed.txt b/language/en/email/pm_report_closed.txt new file mode 100644 index 0000000..0202b9d --- /dev/null +++ b/language/en/email/pm_report_closed.txt @@ -0,0 +1,7 @@ +Subject: Report closed - "{PM_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because the report you filed regarding the private message "{PM_SUBJECT}" at "{SITENAME}" has been tended to by a moderator or administrator. The report is now closed. If you have further questions, please contact {CLOSER_NAME} by private message. + +{EMAIL_SIG} diff --git a/language/en/email/pm_report_deleted.txt b/language/en/email/pm_report_deleted.txt new file mode 100644 index 0000000..991ed59 --- /dev/null +++ b/language/en/email/pm_report_deleted.txt @@ -0,0 +1,7 @@ +Subject: Report deleted - "{PM_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because the report you filed regarding the private message "{PM_SUBJECT}" at "{SITENAME}" was deleted by a moderator or administrator. + +{EMAIL_SIG} diff --git a/language/en/email/post_approved.txt b/language/en/email/post_approved.txt new file mode 100644 index 0000000..854d785 --- /dev/null +++ b/language/en/email/post_approved.txt @@ -0,0 +1,13 @@ +Subject: Post approved - "{POST_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because your post "{POST_SUBJECT}" at "{SITENAME}" was approved by a moderator or administrator. + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + +{EMAIL_SIG} diff --git a/language/en/email/post_disapproved.txt b/language/en/email/post_disapproved.txt new file mode 100644 index 0000000..9b2ee64 --- /dev/null +++ b/language/en/email/post_disapproved.txt @@ -0,0 +1,11 @@ +Subject: Post disapproved - "{POST_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because your post "{POST_SUBJECT}" at "{SITENAME}" was disapproved by a moderator or administrator. + +The following reason was given for the disapproval: + +{REASON} + +{EMAIL_SIG} diff --git a/language/en/email/post_in_queue.txt b/language/en/email/post_in_queue.txt new file mode 100644 index 0000000..941f070 --- /dev/null +++ b/language/en/email/post_in_queue.txt @@ -0,0 +1,13 @@ +Subject: Post moderation notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the post "{POST_SUBJECT}" at "{SITENAME}" needs approval. + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +{EMAIL_SIG} diff --git a/language/en/email/privmsg_notify.txt b/language/en/email/privmsg_notify.txt new file mode 100644 index 0000000..41fdbb7 --- /dev/null +++ b/language/en/email/privmsg_notify.txt @@ -0,0 +1,15 @@ +Subject: New private message has arrived + +Hello {USERNAME}, + +You have received a new private message from "{AUTHOR_NAME}" to your account on "{SITENAME}" with the following subject: + +{SUBJECT} + +You can view your new message by clicking on the following link: + +{U_VIEW_MESSAGE} + +You have requested that you be notified on this event, remember that you can always choose not to be notified of new messages by changing the appropriate setting in your profile. + +{EMAIL_SIG} diff --git a/language/en/email/profile_send_email.txt b/language/en/email/profile_send_email.txt new file mode 100644 index 0000000..3e63777 --- /dev/null +++ b/language/en/email/profile_send_email.txt @@ -0,0 +1,13 @@ + +Hello {TO_USERNAME}, + +The following is an email sent to you by {FROM_USERNAME} via your account on "{SITENAME}". If this message is spam, contains abusive or other comments you find offensive please contact the webmaster of the board at the following address: + +{BOARD_CONTACT} + +Include this full email (particularly the headers). Please note that the reply address to this email has been set to that of {FROM_USERNAME}. + +Message sent to you follows +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{MESSAGE} diff --git a/language/en/email/profile_send_im.txt b/language/en/email/profile_send_im.txt new file mode 100644 index 0000000..02c820c --- /dev/null +++ b/language/en/email/profile_send_im.txt @@ -0,0 +1,13 @@ + +Hello {TO_USERNAME}, + +The following is a message sent to you by {FROM_USERNAME} via your account on "{SITENAME}". If this message is spam, contains abusive or other comments you find offensive please contact the webmaster of the board at the following address: + +{BOARD_CONTACT} + +Include this full message. Please note that the sender address has been set to the boards IM account. + +Message sent to you follows +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{MESSAGE} diff --git a/language/en/email/quote.txt b/language/en/email/quote.txt new file mode 100644 index 0000000..2b95258 --- /dev/null +++ b/language/en/email/quote.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because "{AUTHOR_NAME}" quoted you in the topic "{TOPIC_TITLE}" at "{SITENAME}". You can use the following link to view the reply made. + +If you want to view the quoted post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies quoting you, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/language/en/email/report_closed.txt b/language/en/email/report_closed.txt new file mode 100644 index 0000000..f248018 --- /dev/null +++ b/language/en/email/report_closed.txt @@ -0,0 +1,7 @@ +Subject: Report closed - "{POST_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because the report you filed on the post "{POST_SUBJECT}" in "{TOPIC_TITLE}" at "{SITENAME}" was handled by a moderator or by an administrator. The report was afterwards closed. If you have further questions contact {CLOSER_NAME} with a personal message. + +{EMAIL_SIG} diff --git a/language/en/email/report_deleted.txt b/language/en/email/report_deleted.txt new file mode 100644 index 0000000..9a30ea2 --- /dev/null +++ b/language/en/email/report_deleted.txt @@ -0,0 +1,7 @@ +Subject: Report deleted - "{POST_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because the report you filed on the post "{POST_SUBJECT}" in "{TOPIC_TITLE}" at "{SITENAME}" was deleted by a moderator or by an administrator. + +{EMAIL_SIG} diff --git a/language/en/email/report_pm.txt b/language/en/email/report_pm.txt new file mode 100644 index 0000000..a6b8086 --- /dev/null +++ b/language/en/email/report_pm.txt @@ -0,0 +1,10 @@ +Subject: Private Message report - "{SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because a Private Message titled "{SUBJECT}" by "{AUTHOR_NAME}" at "{SITENAME}" was reported. + +If you want to view the report, click the following link: +{U_VIEW_REPORT} + +{EMAIL_SIG} diff --git a/language/en/email/report_post.txt b/language/en/email/report_post.txt new file mode 100644 index 0000000..8eb24ec --- /dev/null +++ b/language/en/email/report_post.txt @@ -0,0 +1,13 @@ +Subject: Post report - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the post "{POST_SUBJECT}" at "{SITENAME}" was reported. + +If you want to view the report, click the following link: +{U_VIEW_REPORT} + +If you want to view the post, click the following link: +{U_VIEW_POST} + +{EMAIL_SIG} diff --git a/language/en/email/short/bookmark.txt b/language/en/email/short/bookmark.txt new file mode 100644 index 0000000..95f17b5 --- /dev/null +++ b/language/en/email/short/bookmark.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because a topic you bookmarked, "{TOPIC_TITLE}" at "{SITENAME}", has received a reply since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. + +If you want to view the newest post made since your last visit, click the following link: +{U_NEWEST_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies to bookmarks, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/language/en/email/short/newtopic_notify.txt b/language/en/email/short/newtopic_notify.txt new file mode 100644 index 0000000..b9416d8 --- /dev/null +++ b/language/en/email/short/newtopic_notify.txt @@ -0,0 +1,13 @@ +Subject: New topic notification - "{FORUM_NAME}" + +Hello {USERNAME}, + +You are receiving this notification because you are watching the forum "{FORUM_NAME}" at "{SITENAME}". This forum has received a new topic by {AUTHOR_NAME} since your last visit, "{TOPIC_TITLE}". You can use the following link to view the forum, no more notifications will be sent until you visit the forum. + +{U_FORUM} + +If you no longer wish to watch this forum you can either click the "Unsubscribe forum" link found in the forum above, or by clicking the following link: + +{U_STOP_WATCHING_FORUM} + +{EMAIL_SIG} diff --git a/language/en/email/short/post_approved.txt b/language/en/email/short/post_approved.txt new file mode 100644 index 0000000..854d785 --- /dev/null +++ b/language/en/email/short/post_approved.txt @@ -0,0 +1,13 @@ +Subject: Post approved - "{POST_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because your post "{POST_SUBJECT}" at "{SITENAME}" was approved by a moderator or administrator. + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + +{EMAIL_SIG} diff --git a/language/en/email/short/post_disapproved.txt b/language/en/email/short/post_disapproved.txt new file mode 100644 index 0000000..9b2ee64 --- /dev/null +++ b/language/en/email/short/post_disapproved.txt @@ -0,0 +1,11 @@ +Subject: Post disapproved - "{POST_SUBJECT}" + +Hello {USERNAME}, + +You are receiving this notification because your post "{POST_SUBJECT}" at "{SITENAME}" was disapproved by a moderator or administrator. + +The following reason was given for the disapproval: + +{REASON} + +{EMAIL_SIG} diff --git a/language/en/email/short/post_in_queue.txt b/language/en/email/short/post_in_queue.txt new file mode 100644 index 0000000..941f070 --- /dev/null +++ b/language/en/email/short/post_in_queue.txt @@ -0,0 +1,13 @@ +Subject: Post moderation notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the post "{POST_SUBJECT}" at "{SITENAME}" needs approval. + +If you want to view the post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +{EMAIL_SIG} diff --git a/language/en/email/short/privmsg_notify.txt b/language/en/email/short/privmsg_notify.txt new file mode 100644 index 0000000..41fdbb7 --- /dev/null +++ b/language/en/email/short/privmsg_notify.txt @@ -0,0 +1,15 @@ +Subject: New private message has arrived + +Hello {USERNAME}, + +You have received a new private message from "{AUTHOR_NAME}" to your account on "{SITENAME}" with the following subject: + +{SUBJECT} + +You can view your new message by clicking on the following link: + +{U_VIEW_MESSAGE} + +You have requested that you be notified on this event, remember that you can always choose not to be notified of new messages by changing the appropriate setting in your profile. + +{EMAIL_SIG} diff --git a/language/en/email/short/quote.txt b/language/en/email/short/quote.txt new file mode 100644 index 0000000..2b95258 --- /dev/null +++ b/language/en/email/short/quote.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because "{AUTHOR_NAME}" quoted you in the topic "{TOPIC_TITLE}" at "{SITENAME}". You can use the following link to view the reply made. + +If you want to view the quoted post, click the following link: +{U_VIEW_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to receive updates about replies quoting you, please update your notification settings here: + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/language/en/email/short/report_pm.txt b/language/en/email/short/report_pm.txt new file mode 100644 index 0000000..a101a01 --- /dev/null +++ b/language/en/email/short/report_pm.txt @@ -0,0 +1,10 @@ +Subject: Private Message report - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because a Private Message titled "{SUBJECT}" by "{AUTHOR_NAME}" at "{SITENAME}" was reported. + +If you want to view the report, click the following link: +{U_VIEW_REPORT} + +{EMAIL_SIG} diff --git a/language/en/email/short/report_post.txt b/language/en/email/short/report_post.txt new file mode 100644 index 0000000..8eb24ec --- /dev/null +++ b/language/en/email/short/report_post.txt @@ -0,0 +1,13 @@ +Subject: Post report - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the post "{POST_SUBJECT}" at "{SITENAME}" was reported. + +If you want to view the report, click the following link: +{U_VIEW_REPORT} + +If you want to view the post, click the following link: +{U_VIEW_POST} + +{EMAIL_SIG} diff --git a/language/en/email/short/topic_approved.txt b/language/en/email/short/topic_approved.txt new file mode 100644 index 0000000..60c7ef4 --- /dev/null +++ b/language/en/email/short/topic_approved.txt @@ -0,0 +1,10 @@ +Subject: Topic approved - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because your topic "{TOPIC_TITLE}" at "{SITENAME}" was approved by a moderator or administrator. + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + +{EMAIL_SIG} diff --git a/language/en/email/short/topic_disapproved.txt b/language/en/email/short/topic_disapproved.txt new file mode 100644 index 0000000..9c821e2 --- /dev/null +++ b/language/en/email/short/topic_disapproved.txt @@ -0,0 +1,11 @@ +Subject: Topic disapproved - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because your topic "{TOPIC_TITLE}" at "{SITENAME}" was disapproved by a moderator or administrator. + +The following reason was given for the disapproval: + +{REASON} + +{EMAIL_SIG} diff --git a/language/en/email/short/topic_in_queue.txt b/language/en/email/short/topic_in_queue.txt new file mode 100644 index 0000000..706dddf --- /dev/null +++ b/language/en/email/short/topic_in_queue.txt @@ -0,0 +1,13 @@ +Subject: Topic moderation notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the topic "{TOPIC_TITLE}" at "{SITENAME}" needs approval. + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +{EMAIL_SIG} diff --git a/language/en/email/short/topic_notify.txt b/language/en/email/short/topic_notify.txt new file mode 100644 index 0000000..b1ed657 --- /dev/null +++ b/language/en/email/short/topic_notify.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because you are watching the topic "{TOPIC_TITLE}" at "{SITENAME}". This topic has received a reply by {AUTHOR_NAME} since your last visit. You can use the following link to view the replies made, no more notifications will be sent until you visit the topic. + +If you want to view the newest post made since your last visit, click the following link: +{U_NEWEST_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to watch this topic you can either click the "Unsubscribe topic" link found at the bottom of the topic above, or by clicking the following link: + +{U_STOP_WATCHING_TOPIC} + +{EMAIL_SIG} diff --git a/language/en/email/test.txt b/language/en/email/test.txt new file mode 100644 index 0000000..91a7372 --- /dev/null +++ b/language/en/email/test.txt @@ -0,0 +1,9 @@ +Subject: phpBB is correctly configured to send emails + +Hello {USERNAME}, + +Congratulations. If you received this email, phpBB is correctly configured to send emails. + +In case you require assistance, please visit the phpBB support forums - https://www.phpbb.com/community/ + +{EMAIL_SIG} diff --git a/language/en/email/topic_approved.txt b/language/en/email/topic_approved.txt new file mode 100644 index 0000000..60c7ef4 --- /dev/null +++ b/language/en/email/topic_approved.txt @@ -0,0 +1,10 @@ +Subject: Topic approved - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because your topic "{TOPIC_TITLE}" at "{SITENAME}" was approved by a moderator or administrator. + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + +{EMAIL_SIG} diff --git a/language/en/email/topic_disapproved.txt b/language/en/email/topic_disapproved.txt new file mode 100644 index 0000000..9c821e2 --- /dev/null +++ b/language/en/email/topic_disapproved.txt @@ -0,0 +1,11 @@ +Subject: Topic disapproved - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because your topic "{TOPIC_TITLE}" at "{SITENAME}" was disapproved by a moderator or administrator. + +The following reason was given for the disapproval: + +{REASON} + +{EMAIL_SIG} diff --git a/language/en/email/topic_in_queue.txt b/language/en/email/topic_in_queue.txt new file mode 100644 index 0000000..706dddf --- /dev/null +++ b/language/en/email/topic_in_queue.txt @@ -0,0 +1,13 @@ +Subject: Topic moderation notification - "{TOPIC_TITLE}" + +Hello {USERNAME}, + +You are receiving this notification because the topic "{TOPIC_TITLE}" at "{SITENAME}" needs approval. + +If you want to view the topic, click the following link: +{U_VIEW_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +{EMAIL_SIG} diff --git a/language/en/email/topic_notify.txt b/language/en/email/topic_notify.txt new file mode 100644 index 0000000..92bf858 --- /dev/null +++ b/language/en/email/topic_notify.txt @@ -0,0 +1,20 @@ +Subject: Topic reply notification - "{TOPIC_TITLE}" +List-Unsubscribe: <{U_STOP_WATCHING_TOPIC}> + +Hello {USERNAME}, + +You are receiving this notification because you are watching the topic "{TOPIC_TITLE}" at "{SITENAME}". This topic has received a reply by {AUTHOR_NAME} since your last visit. No more notifications will be sent until you visit the topic. + +If you want to view the newest post made since your last visit, click the following link: +{U_NEWEST_POST} + +If you want to view the topic, click the following link: +{U_TOPIC} + +If you want to view the forum, click the following link: +{U_FORUM} + +If you no longer wish to watch this topic you can either click the "Unsubscribe topic" link found at the bottom of the topic above, or by clicking the following link: +{U_STOP_WATCHING_TOPIC} + +{EMAIL_SIG} diff --git a/language/en/email/user_activate.txt b/language/en/email/user_activate.txt new file mode 100644 index 0000000..b949b68 --- /dev/null +++ b/language/en/email/user_activate.txt @@ -0,0 +1,9 @@ +Subject: Reactivate your account + +Hello {USERNAME}, + +Your account on "{SITENAME}" has been deactivated, most likely due to changes made to your profile. In order to reactivate your account you must click on the link below: + +{U_ACTIVATE} + +{EMAIL_SIG} diff --git a/language/en/email/user_activate_inactive.txt b/language/en/email/user_activate_inactive.txt new file mode 100644 index 0000000..7743420 --- /dev/null +++ b/language/en/email/user_activate_inactive.txt @@ -0,0 +1,7 @@ +Subject: Your account has been deactivated + +Hello {USERNAME}, + +Your account on "{SITENAME}" has been deactivated, most likely due to changes made to your profile. The administrator of the board will need to activate it before you can log in. You will receive another notification when this has occurred. + +{EMAIL_SIG} diff --git a/language/en/email/user_activate_passwd.txt b/language/en/email/user_activate_passwd.txt new file mode 100644 index 0000000..965d5a5 --- /dev/null +++ b/language/en/email/user_activate_passwd.txt @@ -0,0 +1,17 @@ +Subject: New password activation + +Hello {USERNAME} + +You are receiving this notification because you have (or someone pretending to be you has) requested a new password be sent for your account on "{SITENAME}". If you did not request this notification then please ignore it, if you keep receiving it please contact the board administrator. + +To use the new password you need to activate it. To do this click the link provided below. + +{U_ACTIVATE} + +If successful you will be able to login using the following password: + +Password: {PASSWORD} + +You can of course change this password yourself via the profile page. If you have any difficulties please contact the board administrator. + +{EMAIL_SIG} diff --git a/language/en/email/user_reactivate_account.txt b/language/en/email/user_reactivate_account.txt new file mode 100644 index 0000000..7564b0a --- /dev/null +++ b/language/en/email/user_reactivate_account.txt @@ -0,0 +1,18 @@ +Subject: Reactivate your account on "{SITENAME}" + +A board administrator requested that your account be reactivated. Your account is currently inactive. +Please follow the steps listed here to reactivate your account. + +Please keep this email for your records. Your account information is as follows: + +---------------------------- +Username: {USERNAME} +---------------------------- + +Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +Please visit the following link to reactivate your account: + +{U_ACTIVATE} + +{EMAIL_SIG} diff --git a/language/en/email/user_remind_inactive.txt b/language/en/email/user_remind_inactive.txt new file mode 100644 index 0000000..1f136b5 --- /dev/null +++ b/language/en/email/user_remind_inactive.txt @@ -0,0 +1,11 @@ +Subject: Inactive account reminder + +Hello {USERNAME}, + +This notification is a reminder that your account at "{SITENAME}", created on {REGISTER_DATE}, remains inactive. If you would like to activate this account, please visit the following link: + +{U_ACTIVATE} + +Thank you for registering at "{SITENAME}", we look forward to your participation. + +{EMAIL_SIG} diff --git a/language/en/email/user_resend_inactive.txt b/language/en/email/user_resend_inactive.txt new file mode 100644 index 0000000..d975a07 --- /dev/null +++ b/language/en/email/user_resend_inactive.txt @@ -0,0 +1,19 @@ +Subject: Welcome to "{SITENAME}" + +{WELCOME_MSG} + +Please keep this email for your records. Your account information is as follows: + +---------------------------- +Username: {USERNAME} +---------------------------- + +Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +Please visit the following link in order to activate your account: + +{U_ACTIVATE} + +Thank you for registering. + +{EMAIL_SIG} diff --git a/language/en/email/user_welcome.txt b/language/en/email/user_welcome.txt new file mode 100644 index 0000000..f299b7b --- /dev/null +++ b/language/en/email/user_welcome.txt @@ -0,0 +1,17 @@ +Subject: Welcome to "{SITENAME}" + +{WELCOME_MSG} + +Please keep this email for your records. Your account information is as follows: + +---------------------------- +Username: {USERNAME} + +Board URL: {U_BOARD} +---------------------------- + +Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +Thank you for registering. + +{EMAIL_SIG} diff --git a/language/en/email/user_welcome_inactive.txt b/language/en/email/user_welcome_inactive.txt new file mode 100644 index 0000000..b4300d0 --- /dev/null +++ b/language/en/email/user_welcome_inactive.txt @@ -0,0 +1,21 @@ +Subject: Welcome to "{SITENAME}" + +{WELCOME_MSG} + +Please keep this email for your records. Your account information is as follows: + +---------------------------- +Username: {USERNAME} + +Board URL: {U_BOARD} +---------------------------- + +Please visit the following link in order to activate your account: + +{U_ACTIVATE} + +Your password has been securely stored in our database and cannot be retrieved. In the event that it is forgotten, you will be able to reset it using the email address associated with your account. + +Thank you for registering. + +{EMAIL_SIG} diff --git a/language/en/groups.php b/language/en/groups.php new file mode 100644 index 0000000..6d25fea --- /dev/null +++ b/language/en/groups.php @@ -0,0 +1,95 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ALREADY_DEFAULT_GROUP' => 'The selected group is already your default group.', + 'ALREADY_IN_GROUP' => 'You are already a member of the selected group.', + 'ALREADY_IN_GROUP_PENDING' => 'You already requested joining the selected group.', + + 'CANNOT_JOIN_GROUP' => 'You are not able to join this group. You are only able to join open and freely open groups.', + 'CANNOT_RESIGN_GROUP' => 'You are not able to resign from this group. You are only able to resign from open and freely open groups.', + 'CHANGED_DEFAULT_GROUP' => 'Successfully changed default group.', + + 'GROUP_AVATAR' => 'Group avatar', + 'GROUP_CHANGE_DEFAULT' => 'Are you sure you want to change your default membership to the group “%s”?', + 'GROUP_CLOSED' => 'Closed', + 'GROUP_DESC' => 'Group description', + 'GROUP_HIDDEN' => 'Hidden', + 'GROUP_INFORMATION' => 'Usergroup information', + 'GROUP_IS_CLOSED' => 'This is a closed group, new members can only join upon invitation of a group leader.', + 'GROUP_IS_FREE' => 'This is a freely open group, all new members are welcome.', + 'GROUP_IS_HIDDEN' => 'This is a hidden group, only members of this group can view its membership.', + 'GROUP_IS_OPEN' => 'This is an open group, members can apply to join.', + 'GROUP_IS_SPECIAL' => 'This is a special group, special groups are managed by the board administrators.', + 'GROUP_JOIN' => 'Join group', + 'GROUP_JOIN_CONFIRM' => 'Are you sure you want to join the selected group?', + 'GROUP_JOIN_PENDING' => 'Request to join group', + 'GROUP_JOIN_PENDING_CONFIRM' => 'Are you sure you want to request joining the selected group?', + 'GROUP_JOINED' => 'Successfully joined selected group.', + 'GROUP_JOINED_PENDING' => 'Successfully requested group membership. Please wait for a group leader to approve your membership.', + 'GROUP_LIST' => 'Manage users', + 'GROUP_MEMBERS' => 'Group members', + 'GROUP_NAME' => 'Group name', + 'GROUP_OPEN' => 'Open', + 'GROUP_RANK' => 'Group rank', + 'GROUP_RESIGN_MEMBERSHIP' => 'Resign group membership', + 'GROUP_RESIGN_MEMBERSHIP_CONFIRM' => 'Are you sure you want to resign your membership from the selected group?', + 'GROUP_RESIGN_PENDING' => 'Resign a pending group membership', + 'GROUP_RESIGN_PENDING_CONFIRM' => 'Are you sure you want to resign your pending membership from the selected group?', + 'GROUP_RESIGNED_MEMBERSHIP' => 'You were successfully removed from the selected group.', + 'GROUP_RESIGNED_PENDING' => 'Your pending membership was successfully removed from the selected group.', + 'GROUP_TYPE' => 'Group type', + 'GROUP_UNDISCLOSED' => 'Hidden group', + 'FORUM_UNDISCLOSED' => 'Moderating hidden forum(s)', + + 'LOGIN_EXPLAIN_GROUP' => 'You need to login to view group details.', + + 'NO_LEADERS' => 'You are not a leader of any group.', + 'NOT_LEADER_OF_GROUP' => 'The requested operation cannot be taken because you are not a leader of the selected group.', + 'NOT_MEMBER_OF_GROUP' => 'The requested operation cannot be taken because you are not a member of the selected group or your membership has not been approved yet.', + 'NOT_RESIGN_FROM_DEFAULT_GROUP' => 'You are not allowed to resign from your default group.', + + 'PRIMARY_GROUP' => 'Primary group', + + 'REMOVE_SELECTED' => 'Remove selected', + + 'USER_GROUP_CHANGE' => 'From “%1$s” group to “%2$s”', + 'USER_GROUP_DEMOTE' => 'Demote leadership', + 'USER_GROUP_DEMOTE_CONFIRM' => 'Are you sure you want to demote as group leader from the selected group?', + 'USER_GROUP_DEMOTED' => 'Successfully demoted your leadership.', +)); diff --git a/language/en/help/bbcode.php b/language/en/help/bbcode.php new file mode 100644 index 0000000..aa5bc16 --- /dev/null +++ b/language/en/help/bbcode.php @@ -0,0 +1,66 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +$lang = array_merge($lang, array( + 'HELP_BBCODE_BLOCK_IMAGES' => 'Showing images in posts', + 'HELP_BBCODE_BLOCK_INTRO' => 'Introduction', + 'HELP_BBCODE_BLOCK_LINKS' => 'Creating Links', + 'HELP_BBCODE_BLOCK_LISTS' => 'Generating lists', + 'HELP_BBCODE_BLOCK_OTHERS' => 'Other matters', + 'HELP_BBCODE_BLOCK_QUOTES' => 'Quoting and outputting fixed-width text', + 'HELP_BBCODE_BLOCK_TEXT' => 'Text Formatting', + + 'HELP_BBCODE_IMAGES_ATTACHMENT_ANSWER' => 'Attachments can now be placed in any part of a post by using the new [attachment=][/attachment] BBCode, if the attachments functionality has been enabled by a board administrator and if you are given the appropriate permissions to create attachments. Within the posting screen is a drop-down box (respectively a button) for placing attachments inline.', + 'HELP_BBCODE_IMAGES_ATTACHMENT_QUESTION' => 'Adding attachments into a post', + 'HELP_BBCODE_IMAGES_BASIC_ANSWER' => 'phpBB BBCode incorporates a tag for including images in your posts. Two very important things to remember when using this tag are: many users do not appreciate lots of images being shown in posts and secondly the image you display must already be available on the internet (it cannot exist only on your computer for example, unless you run a webserver!). To display an image you must surround the URL pointing to the image with [img][/img] tags. For example:

[img]https://www.phpbb.com/theme/images/logos/blue/160x52.png[/img]

As noted in the URL section above you can wrap an image in a [url][/url] tag if you wish, e.g.

[url=https://www.phpbb.com/][img]https://www.phpbb.com/theme/images/logos/blue/160x52.png[/img][/url]

would generate:

', + 'HELP_BBCODE_IMAGES_BASIC_QUESTION' => 'Adding an image to a post', + + 'HELP_BBCODE_INTRO_BBCODE_ANSWER' => 'BBCode is a special implementation of HTML. Whether you can actually use BBCode in your posts on the forum is determined by the administrator. In addition you can disable BBCode on a per post basis via the posting form. BBCode itself is similar in style to HTML, tags are enclosed in square brackets [ and ] rather than < and > and it offers greater control over what and how something is displayed. Depending on the template you are using you may find adding BBCode to your posts is made much easier through a clickable interface above the message area on the posting form. Even with this you may find the following guide useful.', + 'HELP_BBCODE_INTRO_BBCODE_QUESTION' => 'What is BBCode?', + + 'HELP_BBCODE_LINKS_BASIC_ANSWER' => 'phpBB BBCode supports a number of ways of creating URIs (Uniform Resource Indicators) better known as URLs.
  • The first of these uses the [url=][/url] tag, whatever you type after the = sign will cause the contents of that tag to act as a URL. For example to link to phpBB.com you could use:

    [url=https://www.phpbb.com/]Visit phpBB![/url]

    This would generate the following link, Visit phpBB! Please notice that the link opens in the same window or a new window depending on the users browser preferences.
  • If you want the URL itself displayed as the link you can do this by simply using:

    [url]https://www.phpbb.com/[/url]

    This would generate the following link, https://www.phpbb.com/
  • Additionally, phpBB features something called Magic Links, this will turn any syntactically correct URL into a link without you needing to specify any tags or even the leading http://. For example typing www.phpbb.com into your message will automatically lead to www.phpbb.com being output when you view the message.
  • The same thing applies equally to email addresses, you can either specify an address explicitly for example:

    [email]no.one@domain.adr[/email]

    which will output no.one@domain.adr or you can just type no.one@domain.adr into your message and it will be automatically converted when you view.
As with all the BBCode tags you can wrap URLs around any of the other tags such as [img][/img] (see next entry), [b][/b], etc. As with the formatting tags it is up to you to ensure the correct open and close order is following, for example:

[url=https://www.phpbb.com/][img]https://www.phpbb.com/theme/images/logos/blue/160x52.png[/url][/img]

is not correct which may lead to your post being deleted so take care.', + 'HELP_BBCODE_LINKS_BASIC_QUESTION' => 'Linking to another site', + + 'HELP_BBCODE_LISTS_ORDERER_ANSWER' => 'The second type of list, an ordered list, gives you control over what is output before each item. To create an ordered list you use [list=1][/list] to create a numbered list or alternatively [list=a][/list] for an alphabetical list. As with the unordered list, items are specified using [*]. For example:

[list=1]
[*]Go to the shops
[*]Buy a new computer
[*]Swear at computer when it crashes
[/list]

will generate the following:
  1. Go to the shops
  2. Buy a new computer
  3. Swear at computer when it crashes
Whereas for an alphabetical list you would use:

[list=a]
[*]The first possible answer
[*]The second possible answer
[*]The third possible answer
[/list]

giving
  1. The first possible answer
  2. The second possible answer
  3. The third possible answer

[list=A]
[*]The first possible answer
[*]The second possible answer
[*]The third possible answer
[/list]

giving
  1. The first possible answer
  2. The second possible answer
  3. The third possible answer

[list=i]
[*]The first possible answer
[*]The second possible answer
[*]The third possible answer
[/list]

giving
  1. The first possible answer
  2. The second possible answer
  3. The third possible answer

[list=I]
[*]The first possible answer
[*]The second possible answer
[*]The third possible answer
[/list]

giving
  1. The first possible answer
  2. The second possible answer
  3. The third possible answer
', + 'HELP_BBCODE_LISTS_ORDERER_QUESTION' => 'Creating an Ordered list', + 'HELP_BBCODE_LISTS_UNORDERER_ANSWER' => 'BBCode supports two types of lists, unordered and ordered. They are essentially the same as their HTML equivalents. An unordered list outputs each item in your list sequentially one after the other indenting each with a bullet character. To create an unordered list you use [list][/list] and define each item within the list using [*]. For example to list your favourite colours you could use:

[list]
[*]Red
[*]Blue
[*]Yellow
[/list]

This would generate the following list:
  • Red
  • Blue
  • Yellow

Alternatively you can specify the list’s bullet style using [list=disc][/list], [list=circle][/list], or [list=square][/list].', + 'HELP_BBCODE_LISTS_UNORDERER_QUESTION' => 'Creating an Unordered list', + + 'HELP_BBCODE_OTHERS_CUSTOM_ANSWER' => 'If you are an administrator on this board and have the proper permissions, you can add further BBCodes through the Custom BBCodes section.', + 'HELP_BBCODE_OTHERS_CUSTOM_QUESTION' => 'Can I add my own tags?', + + 'HELP_BBCODE_QUOTES_CODE_ANSWER' => 'If you want to output a piece of code or in fact anything that requires a fixed width, e.g. Courier type font you should enclose the text in [code][/code] tags, e.g.

[code]echo "This is some code";[/code]

All formatting used within [code][/code] tags is retained when you later view it.', + 'HELP_BBCODE_QUOTES_CODE_QUESTION' => 'Outputting code or fixed width data', + 'HELP_BBCODE_QUOTES_TEXT_ANSWER' => 'There are two ways you can quote text, with a reference or without.
  • When you utilise the Quote function to reply to a post on the board you should notice that the post text is added to the message window enclosed in a [quote=""][/quote] block. This method allows you to quote with a reference to a person or whatever else you choose to put! For example to quote a piece of text Mr. Blobby wrote you would enter:

    [quote="Mr. Blobby"]The text Mr. Blobby wrote would go here[/quote]

    The resulting output will automatically add "Mr. Blobby wrote:" before the actual text. Remember you must include the quotation marks "" around the name you are quoting, they are not optional.
  • The second method allows you to blindly quote something. To utilise this enclose the text in [quote][/quote] tags. When you view the message it will simply show the text within a quotation block.
', + 'HELP_BBCODE_QUOTES_TEXT_QUESTION' => 'Quoting text in replies', + + 'HELP_BBCODE_TEXT_BASIC_ANSWER' => 'BBCode includes tags to allow you to quickly change the basic style of your text. This is achieved in the following ways:
  • To make a piece of text bold enclose it in [b][/b], e.g.

    [b]Hello[/b]

    will become Hello
  • For underlining use [u][/u], for example:

    [u]Good Morning[/u]

    becomes Good Morning
  • To italicise text use [i][/i], e.g.

    This is [i]Great![/i]

    would give This is Great!
', + 'HELP_BBCODE_TEXT_BASIC_QUESTION' => 'How to create bold, italic and underlined text', + 'HELP_BBCODE_TEXT_COLOR_ANSWER' => 'To alter the colour or size of your text the following tags can be used. Keep in mind that how the output appears will depend on the viewers browser and system:
  • Changing the colour of text is achieved by wrapping it in [color=][/color]. You can specify either a recognised colour name (eg. red, blue, yellow, etc.) or the hexadecimal triplet alternative, e.g. #FFFFFF, #000000. For example, to create red text you could use:

    [color=red]Hello![/color]

    or

    [color=#FF0000]Hello![/color]

    Both will output Hello!
  • Changing the text size is achieved in a similar way using [size=][/size]. This tag is dependent on the template the user has selected but the recommended format is a numerical value representing the text size in percent, starting at 20 (very small) through to 200 (very large) by default. For example:

    [size=30]SMALL[/size]

    will generally be SMALL

    whereas:

    [size=200]HUGE![/size]

    will be HUGE!
', + 'HELP_BBCODE_TEXT_COLOR_QUESTION' => 'How to change the text colour or size', + 'HELP_BBCODE_TEXT_COMBINE_ANSWER' => 'Yes, of course you can, for example to get someones attention you may write:

[size=200][color=red][b]LOOK AT ME![/b][/color][/size]

this would output LOOK AT ME!

We don’t recommend you output lots of text that looks like this though! Remember it is up to you, the poster, to ensure tags are closed correctly. For example the following is incorrect:

[b][u]This is wrong[/b][/u]', + 'HELP_BBCODE_TEXT_COMBINE_QUESTION' => 'Can I combine formatting tags?', +)); diff --git a/language/en/help/faq.php b/language/en/help/faq.php new file mode 100644 index 0000000..6b165da --- /dev/null +++ b/language/en/help/faq.php @@ -0,0 +1,186 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +$lang = array_merge($lang, array( + 'HELP_FAQ_ATTACHMENTS_ALLOWED_ANSWER' => 'Each board administrator can allow or disallow certain attachment types. If you are unsure what is allowed to be uploaded, contact the board administrator for assistance.', + 'HELP_FAQ_ATTACHMENTS_ALLOWED_QUESTION' => 'What attachments are allowed on this board?', + 'HELP_FAQ_ATTACHMENTS_OWN_ANSWER' => 'To find your list of attachments that you have uploaded, go to your User Control Panel and follow the links to the attachments section.', + 'HELP_FAQ_ATTACHMENTS_OWN_QUESTION' => 'How do I find all my attachments?', + + 'HELP_FAQ_BLOCK_ATTACHMENTS' => 'Attachments', + 'HELP_FAQ_BLOCK_BOOKMARKS' => 'Subscriptions and Bookmarks', + 'HELP_FAQ_BLOCK_FORMATTING' => 'Formatting and Topic Types', + 'HELP_FAQ_BLOCK_FRIENDS' => 'Friends and Foes', + 'HELP_FAQ_BLOCK_GROUPS' => 'User Levels and Groups', + 'HELP_FAQ_BLOCK_ISSUES' => 'phpBB Issues', + 'HELP_FAQ_BLOCK_LOGIN' => 'Login and Registration Issues', + 'HELP_FAQ_BLOCK_PMS' => 'Private Messaging', + 'HELP_FAQ_BLOCK_POSTING' => 'Posting Issues', + 'HELP_FAQ_BLOCK_SEARCH' => 'Searching the Forums', + 'HELP_FAQ_BLOCK_USERSETTINGS' => 'User Preferences and settings', + + 'HELP_FAQ_BOOKMARKS_DIFFERENCE_ANSWER' => 'In phpBB 3.0, bookmarking topics worked much like bookmarking in a web browser. You were not alerted when there was an update. As of phpBB 3.1, bookmarking is more like subscribing to a topic. You can be notified when a bookmarked topic is updated. Subscribing, however, will notify you when there is an update to a topic or forum on the board. Notification options for bookmarks and subscriptions can be configured in the User Control Panel, under “Board preferences”.', + 'HELP_FAQ_BOOKMARKS_DIFFERENCE_QUESTION' => 'What is the difference between bookmarking and subscribing?', + 'HELP_FAQ_BOOKMARKS_FORUM_ANSWER' => 'To subscribe to a specific forum, click the “Subscribe forum” link, at the bottom of page, upon entering the forum.', + 'HELP_FAQ_BOOKMARKS_FORUM_QUESTION' => 'How do I subscribe to specific forums?', + 'HELP_FAQ_BOOKMARKS_REMOVE_ANSWER' => 'To remove your subscriptions, go to your User Control Panel and follow the links to your subscriptions.', + 'HELP_FAQ_BOOKMARKS_REMOVE_QUESTION' => 'How do I remove my subscriptions?', + 'HELP_FAQ_BOOKMARKS_TOPIC_ANSWER' => 'You can bookmark or subscribe to a specific topic by clicking the appropriate link in the “Topic tools” menu, conveniently located near the top and bottom of a topic discussion.
Replying to a topic with the “Notify me when a reply is posted” option checked will also subscribe you to the topic.', + 'HELP_FAQ_BOOKMARKS_TOPIC_QUESTION' => 'How do I bookmark or subscribe to specific topics?', + + 'HELP_FAQ_FORMATTING_ANNOUNCEMENT_ANSWER' => 'Announcements often contain important information for the forum you are currently reading and you should read them whenever possible. Announcements appear at the top of every page in the forum to which they are posted. As with global announcements, announcement permissions are granted by the board administrator.', + 'HELP_FAQ_FORMATTING_ANNOUNCEMENT_QUESTION' => 'What are announcements?', + 'HELP_FAQ_FORMATTING_BBOCDE_ANSWER' => 'BBCode is a special implementation of HTML, offering great formatting control on particular objects in a post. The use of BBCode is granted by the administrator, but it can also be disabled on a per post basis from the posting form. BBCode itself is similar in style to HTML, but tags are enclosed in square brackets [ and ] rather than < and >. For more information on BBCode see the guide which can be accessed from the posting page.', + 'HELP_FAQ_FORMATTING_BBOCDE_QUESTION' => 'What is BBCode?', + 'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_ANSWER' => 'Global announcements contain important information and you should read them whenever possible. They will appear at the top of every forum and within your User Control Panel. Global announcement permissions are granted by the board administrator.', + 'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_QUESTION' => 'What are global announcements?', + 'HELP_FAQ_FORMATTING_HTML_ANSWER' => 'No. It is not possible to post HTML on this board and have it rendered as HTML. Most formatting which can be carried out using HTML can be applied using BBCode instead.', + 'HELP_FAQ_FORMATTING_HTML_QUESTION' => 'Can I use HTML?', + 'HELP_FAQ_FORMATTING_ICONS_ANSWER' => 'Topic icons are author chosen images associated with posts to indicate their content. The ability to use topic icons depends on the permissions set by the board administrator.', + 'HELP_FAQ_FORMATTING_ICONS_QUESTION' => 'What are topic icons?', + 'HELP_FAQ_FORMATTING_IMAGES_ANSWER' => 'Yes, images can be shown in your posts. If the administrator has allowed attachments, you may be able to upload the image to the board. Otherwise, you must link to an image stored on a publicly accessible web server, e.g. http://www.example.com/my-picture.gif. You cannot link to pictures stored on your own PC (unless it is a publicly accessible server) nor images stored behind authentication mechanisms, e.g. hotmail or yahoo mailboxes, password protected sites, etc. To display the image use the BBCode [img] tag.', + 'HELP_FAQ_FORMATTING_IMAGES_QUESTION' => 'Can I post images?', + 'HELP_FAQ_FORMATTING_LOCKED_ANSWER' => 'Locked topics are topics where users can no longer reply and any poll it contained was automatically ended. Topics may be locked for many reasons and were set this way by either the forum moderator or board administrator. You may also be able to lock your own topics depending on the permissions you are granted by the board administrator.', + 'HELP_FAQ_FORMATTING_LOCKED_QUESTION' => 'What are locked topics?', + 'HELP_FAQ_FORMATTING_SMILIES_ANSWER' => 'Smilies, or Emoticons, are small images which can be used to express a feeling using a short code, e.g. :) denotes happy, while :( denotes sad. The full list of emoticons can be seen in the posting form. Try not to overuse smilies, however, as they can quickly render a post unreadable and a moderator may edit them out or remove the post altogether. The board administrator may also have set a limit to the number of smilies you may use within a post.', + 'HELP_FAQ_FORMATTING_SMILIES_QUESTION' => 'What are Smilies?', + 'HELP_FAQ_FORMATTING_STICKIES_ANSWER' => 'Sticky topics within the forum appear below announcements and only on the first page. They are often quite important so you should read them whenever possible. As with announcements and global announcements, sticky topic permissions are granted by the board administrator.', + 'HELP_FAQ_FORMATTING_STICKIES_QUESTION' => 'What are sticky topics?', + + 'HELP_FAQ_FRIENDS_BASIC_ANSWER' => 'You can use these lists to organise other members of the board. Members added to your friends list will be listed within your User Control Panel for quick access to see their online status and to send them private messages. Subject to template support, posts from these users may also be highlighted. If you add a user to your foes list, any posts they make will be hidden by default.', + 'HELP_FAQ_FRIENDS_BASIC_QUESTION' => 'What are my Friends and Foes lists?', + 'HELP_FAQ_FRIENDS_MANAGE_ANSWER' => 'You can add users to your list in two ways. Within each user’s profile, there is a link to add them to either your Friend or Foe list. Alternatively, from your User Control Panel, you can directly add users by entering their member name. You may also remove users from your list using the same page.', + 'HELP_FAQ_FRIENDS_MANAGE_QUESTION' => 'How can I add / remove users to my Friends or Foes list?', + + 'HELP_FAQ_GROUPS_ADMINISTRATORS_ANSWER' => 'Administrators are members assigned with the highest level of control over the entire board. These members can control all facets of board operation, including setting permissions, banning users, creating usergroups or moderators, etc., dependent upon the board founder and what permissions he or she has given the other administrators. They may also have full moderator capabilities in all forums, depending on the settings put forth by the board founder.', + 'HELP_FAQ_GROUPS_ADMINISTRATORS_QUESTION' => 'What are Administrators?', + 'HELP_FAQ_GROUPS_COLORS_ANSWER' => 'It is possible for the board administrator to assign a colour to the members of a usergroup to make it easy to identify the members of this group.', + 'HELP_FAQ_GROUPS_COLORS_QUESTION' => 'Why do some usergroups appear in a different colour?', + 'HELP_FAQ_GROUPS_DEFAULT_ANSWER' => 'If you are a member of more than one usergroup, your default is used to determine which group colour and group rank should be shown for you by default. The board administrator may grant you permission to change your default usergroup via your User Control Panel.', + 'HELP_FAQ_GROUPS_DEFAULT_QUESTION' => 'What is a “Default usergroup”?', + 'HELP_FAQ_GROUPS_MODERATORS_ANSWER' => 'Moderators are individuals (or groups of individuals) who look after the forums from day to day. They have the authority to edit or delete posts and lock, unlock, move, delete and split topics in the forum they moderate. Generally, moderators are present to prevent users from going off-topic or posting abusive or offensive material.', + 'HELP_FAQ_GROUPS_MODERATORS_QUESTION' => 'What are Moderators?', + 'HELP_FAQ_GROUPS_TEAM_ANSWER' => 'This page provides you with a list of board staff, including board administrators and moderators and other details such as the forums they moderate.', + 'HELP_FAQ_GROUPS_TEAM_QUESTION' => 'What is “The team” link?', + 'HELP_FAQ_GROUPS_USERGROUPS_ANSWER' => 'Usergroups are groups of users that divide the community into manageable sections board administrators can work with. Each user can belong to several groups and each group can be assigned individual permissions. This provides an easy way for administrators to change permissions for many users at once, such as changing moderator permissions or granting users access to a private forum.', + 'HELP_FAQ_GROUPS_USERGROUPS_JOIN_ANSWER' => 'You can view all usergroups via the “Usergroups” link within your User Control Panel. If you would like to join one, proceed by clicking the appropriate button. Not all groups have open access, however. Some may require approval to join, some may be closed and some may even have hidden memberships. If the group is open, you can join it by clicking the appropriate button. If a group requires approval to join you may request to join by clicking the appropriate button. The user group leader will need to approve your request and may ask why you want to join the group. Please do not harass a group leader if they reject your request; they will have their reasons.', + 'HELP_FAQ_GROUPS_USERGROUPS_JOIN_QUESTION' => 'Where are the usergroups and how do I join one?', + 'HELP_FAQ_GROUPS_USERGROUPS_LEAD_ANSWER' => 'A usergroup leader is usually assigned when usergroups are initially created by a board administrator. If you are interested in creating a usergroup, your first point of contact should be an administrator; try sending a private message.', + 'HELP_FAQ_GROUPS_USERGROUPS_LEAD_QUESTION' => 'How do I become a usergroup leader?', + 'HELP_FAQ_GROUPS_USERGROUPS_QUESTION' => 'What are usergroups?', + + 'HELP_FAQ_ISSUES_ADMIN_ANSWER' => 'All users of the board can use the “Contact us” form, if the option was enabled by the board administrator.
Members of the board can also use the “The team” link.', + 'HELP_FAQ_ISSUES_ADMIN_QUESTION' => 'How do I contact a board administrator?', + 'HELP_FAQ_ISSUES_FEATURE_ANSWER' => 'This software was written by and licensed through phpBB Limited. If you believe a feature needs to be added please visit the phpBB Ideas Centre, where you can upvote existing ideas or suggest new features.', + 'HELP_FAQ_ISSUES_FEATURE_QUESTION' => 'Why isn’t X feature available?', + 'HELP_FAQ_ISSUES_LEGAL_ANSWER' => 'Any of the administrators listed on the “The team” page should be an appropriate point of contact for your complaints. If this still gets no response then you should contact the owner of the domain (do a whois lookup) or, if this is running on a free service (e.g. Yahoo!, free.fr, f2s.com, etc.), the management or abuse department of that service. Please note that the phpBB Limited has absolutely no jurisdiction and cannot in any way be held liable over how, where or by whom this board is used. Do not contact the phpBB Limited in relation to any legal (cease and desist, liable, defamatory comment, etc.) matter not directly related to the phpBB.com website or the discrete software of phpBB itself. If you do email phpBB Limited about any third party use of this software then you should expect a terse response or no response at all.', + 'HELP_FAQ_ISSUES_LEGAL_QUESTION' => 'Who do I contact about abusive and/or legal matters related to this board?', + 'HELP_FAQ_ISSUES_WHOIS_PHPBB_ANSWER' => 'This software (in its unmodified form) is produced, released and is copyright phpBB Limited. It is made available under the GNU General Public License, version 2 (GPL-2.0) and may be freely distributed. See About phpBB for more details.', + 'HELP_FAQ_ISSUES_WHOIS_PHPBB_QUESTION' => 'Who wrote this bulletin board?', + + 'HELP_FAQ_LOGIN_AUTO_LOGOUT_ANSWER' => 'If you do not check the Remember me box when you login, the board will only keep you logged in for a preset time. This prevents misuse of your account by anyone else. To stay logged in, check the Remember me box during login. This is not recommended if you access the board from a shared computer, e.g. library, internet cafe, university computer lab, etc. If you do not see this checkbox, it means a board administrator has disabled this feature.', + 'HELP_FAQ_LOGIN_AUTO_LOGOUT_QUESTION' => 'Why do I get logged off automatically?', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANSWER' => 'There are several reasons why this could occur. First, ensure your username and password are correct. If they are, contact a board administrator to make sure you haven’t been banned. It is also possible the website owner has a configuration error on their end, and they would need to fix it.', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_ANSWER' => 'It is possible an administrator has deactivated or deleted your account for some reason. Also, many boards periodically remove users who have not posted for a long time to reduce the size of the database. If this has happened, try registering again and being more involved in discussions.', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_QUESTION' => 'I registered in the past but cannot login any more?!', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_QUESTION' => 'Why can’t I login?', + 'HELP_FAQ_LOGIN_CANNOT_REGISTER_ANSWER' => 'It is possible a board administrator has disabled registration to prevent new visitors from signing up. A board administrator could have also banned your IP address or disallowed the username you are attempting to register. Contact a board administrator for assistance.', + 'HELP_FAQ_LOGIN_CANNOT_REGISTER_QUESTION' => 'Why can’t I register?', + 'HELP_FAQ_LOGIN_COPPA_ANSWER' => 'COPPA, or the Children’s Online Privacy Protection Act of 1998, is a law in the United States requiring websites which can potentially collect information from minors under the age of 13 to have written parental consent or some other method of legal guardian acknowledgment, allowing the collection of personally identifiable information from a minor under the age of 13. If you are unsure if this applies to you as someone trying to register or to the website you are trying to register on, contact legal counsel for assistance. Please note that phpBB Limited and the owners of this board cannot provide legal advice and is not a point of contact for legal concerns of any kind, except as outlined in question “Who do I contact about abusive and/or legal matters related to this board?”.', + 'HELP_FAQ_LOGIN_COPPA_QUESTION' => 'What is COPPA?', + 'HELP_FAQ_LOGIN_DELETE_COOKIES_ANSWER' => '“Delete cookies” deletes the cookies created by phpBB which keep you authenticated and logged into the board. Cookies also provide functions such as read tracking if they have been enabled by a board administrator. If you are having login or logout problems, deleting board cookies may help.', + 'HELP_FAQ_LOGIN_DELETE_COOKIES_QUESTION' => 'What does the “Delete cookies” do?', + 'HELP_FAQ_LOGIN_LOST_PASSWORD_ANSWER' => 'Don’t panic! While your password cannot be retrieved, it can easily be reset. Visit the login page and click I forgot my password. Follow the instructions and you should be able to log in again shortly.
However, if you are not able to reset your password, contact a board administrator.', + 'HELP_FAQ_LOGIN_LOST_PASSWORD_QUESTION' => 'I’ve lost my password!', + 'HELP_FAQ_LOGIN_REGISTER_ANSWER' => 'You may not have to, it is up to the administrator of the board as to whether you need to register in order to post messages. However; registration will give you access to additional features not available to guest users such as definable avatar images, private messaging, emailing of fellow users, usergroup subscription, etc. It only takes a few moments to register so it is recommended you do so.', + 'HELP_FAQ_LOGIN_REGISTER_CONFIRM_ANSWER' => 'First, check your username and password. If they are correct, then one of two things may have happened. If COPPA support is enabled and you specified being under 13 years old during registration, you will have to follow the instructions you received. Some boards will also require new registrations to be activated, either by yourself or by an administrator before you can logon; this information was present during registration. If you were sent an email, follow the instructions. If you did not receive an email, you may have provided an incorrect email address or the email may have been picked up by a spam filer. If you are sure the email address you provided is correct, try contacting an administrator.', + 'HELP_FAQ_LOGIN_REGISTER_CONFIRM_QUESTION' => 'I registered but cannot login!', + 'HELP_FAQ_LOGIN_REGISTER_QUESTION' => 'Why do I need to register?', + + 'HELP_FAQ_PMS_CANNOT_SEND_ANSWER' => 'There are three reasons for this; you are not registered and/or not logged on, the board administrator has disabled private messaging for the entire board, or the board administrator has prevented you from sending messages. Contact a board administrator for more information.', + 'HELP_FAQ_PMS_CANNOT_SEND_QUESTION' => 'I cannot send private messages!', + 'HELP_FAQ_PMS_SPAM_ANSWER' => 'We are sorry to hear that. The email form feature of this board includes safeguards to try and track users who send such posts, so email the board administrator with a full copy of the email you received. It is very important that this includes the headers that contain the details of the user that sent the email. The board administrator can then take action.', + 'HELP_FAQ_PMS_SPAM_QUESTION' => 'I have received a spamming or abusive email from someone on this board!', + 'HELP_FAQ_PMS_UNWANTED_ANSWER' => 'You can automatically delete private messages from a user by using message rules within your User Control Panel. If you are receiving abusive private messages from a particular user, report the messages to the moderators; they have the power to prevent a user from sending private messages.', + 'HELP_FAQ_PMS_UNWANTED_QUESTION' => 'I keep getting unwanted private messages!', + + 'HELP_FAQ_POSTING_BUMP_ANSWER' => 'By clicking the “Bump topic” link when you are viewing it, you can “bump” the topic to the top of the forum on the first page. However, if you do not see this, then topic bumping may be disabled or the time allowance between bumps has not yet been reached. It is also possible to bump the topic simply by replying to it, however, be sure to follow the board rules when doing so.', + 'HELP_FAQ_POSTING_BUMP_QUESTION' => 'How do I bump my topic?', + 'HELP_FAQ_POSTING_CREATE_ANSWER' => 'To post a new topic in a forum, click "New Topic". To post a reply to a topic, click "Post Reply". You may need to register before you can post a message. A list of your permissions in each forum is available at the bottom of the forum and topic screens. Example: You can post new topics, You can post attachments, etc.', + 'HELP_FAQ_POSTING_CREATE_QUESTION' => 'How do I create a new topic or post a reply?', + 'HELP_FAQ_POSTING_DRAFT_ANSWER' => 'This allows you to save drafts to be completed and submitted at a later date. To reload a saved draft, visit the User Control Panel.', + 'HELP_FAQ_POSTING_DRAFT_QUESTION' => 'What is the “Save” button for in topic posting?', + 'HELP_FAQ_POSTING_EDIT_DELETE_ANSWER' => 'Unless you are a board administrator or moderator, you can only edit or delete your own posts. You can edit a post by clicking the edit button for the relevant post, sometimes for only a limited time after the post was made. If someone has already replied to the post, you will find a small piece of text output below the post when you return to the topic which lists the number of times you edited it along with the date and time. This will only appear if someone has made a reply; it will not appear if a moderator or administrator edited the post, though they may leave a note as to why they’ve edited the post at their own discretion. Please note that normal users cannot delete a post once someone has replied.', + 'HELP_FAQ_POSTING_EDIT_DELETE_QUESTION' => 'How do I edit or delete a post?', + 'HELP_FAQ_POSTING_FORUM_RESTRICTED_ANSWER' => 'Some forums may be limited to certain users or groups. To view, read, post or perform another action you may need special permissions. Contact a moderator or board administrator to grant you access.', + 'HELP_FAQ_POSTING_FORUM_RESTRICTED_QUESTION' => 'Why can’t I access a forum?', + 'HELP_FAQ_POSTING_NO_ATTACHMENTS_ANSWER' => 'Attachment permissions are granted on a per forum, per group, or per user basis. The board administrator may not have allowed attachments to be added for the specific forum you are posting in, or perhaps only certain groups can post attachments. Contact the board administrator if you are unsure about why you are unable to add attachments.', + 'HELP_FAQ_POSTING_NO_ATTACHMENTS_QUESTION' => 'Why can’t I add attachments?', + 'HELP_FAQ_POSTING_POLL_ADD_ANSWER' => 'The limit for poll options is set by the board administrator. If you feel you need to add more options to your poll than the allowed amount, contact the board administrator.', + 'HELP_FAQ_POSTING_POLL_ADD_QUESTION' => 'Why can’t I add more poll options?', + 'HELP_FAQ_POSTING_POLL_CREATE_ANSWER' => 'When posting a new topic or editing the first post of a topic, click the “Poll creation” tab below the main posting form; if you cannot see this, you do not have appropriate permissions to create polls. Enter a title and at least two options in the appropriate fields, making sure each option is on a separate line in the textarea. You can also set the number of options users may select during voting under “Options per user”, a time limit in days for the poll (0 for infinite duration) and lastly the option to allow users to amend their votes.', + 'HELP_FAQ_POSTING_POLL_CREATE_QUESTION' => 'How do I create a poll?', + 'HELP_FAQ_POSTING_POLL_EDIT_ANSWER' => 'As with posts, polls can only be edited by the original poster, a moderator or an administrator. To edit a poll, click to edit the first post in the topic; this always has the poll associated with it. If no one has cast a vote, users can delete the poll or edit any poll option. However, if members have already placed votes, only moderators or administrators can edit or delete it. This prevents the poll’s options from being changed mid-way through a poll.', + 'HELP_FAQ_POSTING_POLL_EDIT_QUESTION' => 'How do I edit or delete a poll?', + 'HELP_FAQ_POSTING_QUEUE_ANSWER' => 'The board administrator may have decided that posts in the forum you are posting to require review before submission. It is also possible that the administrator has placed you in a group of users whose posts require review before submission. Please contact the board administrator for further details.', + 'HELP_FAQ_POSTING_QUEUE_QUESTION' => 'Why does my post need to be approved?', + 'HELP_FAQ_POSTING_REPORT_ANSWER' => 'If the board administrator has allowed it, you should see a button for reporting posts next to the post you wish to report. Clicking this will walk you through the steps necessary to report the post.', + 'HELP_FAQ_POSTING_REPORT_QUESTION' => 'How can I report posts to a moderator?', + 'HELP_FAQ_POSTING_SIGNATURE_ANSWER' => 'To add a signature to a post you must first create one via your User Control Panel. Once created, you can check the Attach a signature box on the posting form to add your signature. You can also add a signature by default to all your posts by checking the appropriate radio button in the User Control Panel. If you do so, you can still prevent a signature being added to individual posts by un-checking the add signature box within the posting form.', + 'HELP_FAQ_POSTING_SIGNATURE_QUESTION' => 'How do I add a signature to my post?', + 'HELP_FAQ_POSTING_WARNING_ANSWER' => 'Each board administrator has their own set of rules for their site. If you have broken a rule, you may be issued a warning. Please note that this is the board administrator’s decision, and the phpBB Limited has nothing to do with the warnings on the given site. Contact the board administrator if you are unsure about why you were issued a warning.', + 'HELP_FAQ_POSTING_WARNING_QUESTION' => 'Why did I receive a warning?', + + 'HELP_FAQ_SEARCH_BLANK_ANSWER' => 'Your search returned too many results for the webserver to handle. Use “Advanced search” and be more specific in the terms used and forums that are to be searched.', + 'HELP_FAQ_SEARCH_BLANK_QUESTION' => 'Why does my search return a blank page!?', + 'HELP_FAQ_SEARCH_FORUM_ANSWER' => 'Enter a search term in the search box located on the index, forum or topic pages. Advanced search can be accessed by clicking the “Advance Search” link which is available on all pages on the forum. How to access the search may depend on the style used.', + 'HELP_FAQ_SEARCH_FORUM_QUESTION' => 'How can I search a forum or forums?', + 'HELP_FAQ_SEARCH_MEMBERS_ANSWER' => 'Visit to the “Members” page and click the “Find a member” link.', + 'HELP_FAQ_SEARCH_MEMBERS_QUESTION' => 'How do I search for members?', + 'HELP_FAQ_SEARCH_NO_RESULT_ANSWER' => 'Your search was probably too vague and included many common terms which are not indexed by phpBB. Be more specific and use the options available within Advanced search.', + 'HELP_FAQ_SEARCH_NO_RESULT_QUESTION' => 'Why does my search return no results?', + 'HELP_FAQ_SEARCH_OWN_ANSWER' => 'Your own posts can be retrieved either by clicking the “Show your posts” link within the User Control Panel or by clicking the “Search user’s posts” link via your own profile page or by clicking the “Quick links” menu at the top of the board. To search for your topics, use the Advanced search page and fill in the various options appropriately.', + 'HELP_FAQ_SEARCH_OWN_QUESTION' => 'How can I find my own posts and topics?', + + 'HELP_FAQ_USERSETTINGS_AVATAR_ANSWER' => 'There are two images which may appear along with a username when viewing posts. One of them may be an image associated with your rank, generally in the form of stars, blocks or dots, indicating how many posts you have made or your status on the board. Another, usually larger, image is known as an avatar and is generally unique or personal to each user.', + 'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_ANSWER' => 'Within your User Control Panel, under “Profile” you can add an avatar by using one of the four following methods: Gravatar, Gallery, Remote or Upload. It is up to the board administrator to enable avatars and to choose the way in which avatars can be made available. If you are unable to use avatars, contact a board administrator.', + 'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_QUESTION' => 'How do I display an avatar?', + 'HELP_FAQ_USERSETTINGS_AVATAR_QUESTION' => 'What are the images next to my username?', + 'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_ANSWER' => 'If you are a registered user, all your settings are stored in the board database. To alter them, visit your User Control Panel; a link can usually be found by clicking on your username at the top of board pages. This system will allow you to change all your settings and preferences.', + 'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_QUESTION' => 'How do I change my settings?', + 'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_ANSWER' => 'Only registered users can send email to other users via the built-in email form, and only if the administrator has enabled this feature. This is to prevent malicious use of the email system by anonymous users.', + 'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_QUESTION' => 'When I click the email link for a user it asks me to login?', + 'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_ANSWER' => 'Within your User Control Panel, under “Board preferences”, you will find the option Hide your online status. Enable this option and you will only appear to the administrators, moderators and yourself. You will be counted as a hidden user.', + 'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_QUESTION' => 'How do I prevent my username appearing in the online user listings?', + 'HELP_FAQ_USERSETTINGS_LANGUAGE_ANSWER' => 'Either the administrator has not installed your language or nobody has translated this board into your language. Try asking a board administrator if they can install the language pack you need. If the language pack does not exist, feel free to create a new translation. More information can be found at the phpBB® website.', + 'HELP_FAQ_USERSETTINGS_LANGUAGE_QUESTION' => 'My language is not in the list!', + 'HELP_FAQ_USERSETTINGS_RANK_ANSWER' => 'Ranks, which appear below your username, indicate the number of posts you have made or identify certain users, e.g. moderators and administrators. In general, you cannot directly change the wording of any board ranks as they are set by the board administrator. Please do not abuse the board by posting unnecessarily just to increase your rank. Most boards will not tolerate this and the moderator or administrator will simply lower your post count.', + 'HELP_FAQ_USERSETTINGS_RANK_QUESTION' => 'What is my rank and how do I change it?', + 'HELP_FAQ_USERSETTINGS_SERVERTIME_ANSWER' => 'If you are sure you have set the timezone correctly and the time is still incorrect, then the time stored on the server clock is incorrect. Please notify an administrator to correct the problem.', + 'HELP_FAQ_USERSETTINGS_SERVERTIME_QUESTION' => 'I changed the timezone and the time is still wrong!', + 'HELP_FAQ_USERSETTINGS_TIMEZONE_ANSWER' => 'It is possible the time displayed is from a timezone different from the one you are in. If this is the case, visit your User Control Panel and change your timezone to match your particular area, e.g. London, Paris, New York, Sydney, etc. Please note that changing the timezone, like most settings, can only be done by registered users. If you are not registered, this is a good time to do so.', + 'HELP_FAQ_USERSETTINGS_TIMEZONE_QUESTION' => 'The times are not correct!', +)); diff --git a/language/en/index.htm b/language/en/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/language/en/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/language/en/install.php b/language/en/install.php new file mode 100644 index 0000000..9bceecc --- /dev/null +++ b/language/en/install.php @@ -0,0 +1,599 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Common installer pages +$lang = array_merge($lang, array( + 'INSTALL_PANEL' => 'Installation Panel', + 'SELECT_LANG' => 'Select language', + + 'STAGE_INSTALL' => 'Installing phpBB', + + // Introduction page + 'INTRODUCTION_TITLE' => 'Introduction', + 'INTRODUCTION_BODY' => 'Welcome to phpBB3!

phpBB® is the most widely used open source bulletin board solution in the world. phpBB3 is the latest installment in a package line started in 2000. Like its predecessors, phpBB3 is feature-rich, user-friendly, and fully supported by the phpBB Team. phpBB3 greatly improves on what made phpBB2 popular, and adds commonly requested features that were not present in previous versions. We hope it exceeds your expectations.

This installation system will guide you through installing phpBB3, updating to the latest version of phpBB3 from past releases, as well as converting to phpBB3 from a different discussion board system (including phpBB2). For more information, we encourage you to read the installation guide.

To read the phpBB3 license or learn about obtaining support and our stance on it, please select the respective options from the side menu. To continue, please select the appropriate tab above.', + + // Support page + 'SUPPORT_TITLE' => 'Support', + 'SUPPORT_BODY' => 'Full support will be provided for the current stable release of phpBB3, free of charge. This includes:

  • installation
  • configuration
  • technical questions
  • problems relating to potential bugs in the software
  • updating from Release Candidate (RC) versions to the latest stable version
  • converting from phpBB 2.0.x to phpBB3
  • converting from other discussion board software to phpBB3 (please see the Convertors Forum)

We encourage users still running beta versions of phpBB3 to replace their installation with a fresh copy of the latest version.

Extensions / Styles

For issues relating to Extensions, please post in the appropriate Extensions Forum.
For issues relating to styles, templates and themes, please post in the appropriate Styles Forum.

If your question relates to a specific package, please post directly in the topic dedicated to the package.

Obtaining Support

Support Section
Quick Start Guide

To ensure you stay up to date with the latest news and releases, follow us on Twitter and Facebook

', + + // License + 'LICENSE_TITLE' => 'General Public License', + + // Install page + 'INSTALL_INTRO' => 'Welcome to Installation', + 'INSTALL_INTRO_BODY' => 'With this option, it is possible to install phpBB3 onto your server.

In order to proceed, you will need your database settings. If you do not know your database settings, please contact your host and ask for them. You will not be able to continue without them. You need:

+ +
    +
  • The Database Type - the database you will be using.
  • +
  • The Database server hostname or DSN - the address of the database server.
  • +
  • The Database server port - the port of the database server (most of the time this is not needed).
  • +
  • The Database name - the name of the database on the server.
  • +
  • The Database username and Database password - the login data to access the database.
  • +
+ +

Note: if you are installing using SQLite, you should enter the full path to your database file in the DSN field and leave the username and password fields blank. For security reasons, you should make sure that the database file is not stored in a location accessible from the web.

+ +

phpBB3 supports the following databases:

+
    +
  • MySQL 3.23 or above (MySQLi supported)
  • +
  • PostgreSQL 8.3+
  • +
  • SQLite 3.6.15+
  • +
  • MS SQL Server 2000 or above (directly or via ODBC)
  • +
  • MS SQL Server 2005 or above (native)
  • +
  • Oracle
  • +
+ +

Only those databases supported on your server will be displayed.', + + 'ACP_LINK' => 'Take me to the ACP', + + 'INSTALL_PHPBB_INSTALLED' => 'phpBB is already installed.', + 'INSTALL_PHPBB_NOT_INSTALLED' => 'phpBB is not installed yet.', +)); + +// Requirements translation +$lang = array_merge($lang, array( + // Filesystem requirements + 'FILE_NOT_EXISTS' => 'File does not exist', + 'FILE_NOT_EXISTS_EXPLAIN' => 'To be able to install phpBB the %1$s file needs to exist.', + 'FILE_NOT_EXISTS_EXPLAIN_OPTIONAL' => 'It is recommended that the %1$s file exist for a better forum user experience.', + 'FILE_NOT_WRITABLE' => 'File is not writable', + 'FILE_NOT_WRITABLE_EXPLAIN' => 'To be able to install phpBB the %1$s file needs to be writable.', + 'FILE_NOT_WRITABLE_EXPLAIN_OPTIONAL' => 'It is recommended that the %1$s file be writable for a better forum user experience.', + + 'DIRECTORY_NOT_EXISTS' => 'Directory does not exist', + 'DIRECTORY_NOT_EXISTS_EXPLAIN' => 'To be able to install phpBB the %1$s directory needs to exist.', + 'DIRECTORY_NOT_EXISTS_EXPLAIN_OPTIONAL' => 'It is recommended that the %1$s directory exist for a better forum user experience.', + 'DIRECTORY_NOT_WRITABLE' => 'Directory is not writable', + 'DIRECTORY_NOT_WRITABLE_EXPLAIN' => 'To be able to install phpBB the %1$s directory needs to be writable.', + 'DIRECTORY_NOT_WRITABLE_EXPLAIN_OPTIONAL' => 'It is recommended that the %1$s directory be writable for a better forum user experience.', + + // Server requirements + 'PHP_VERSION_REQD' => 'PHP version', + 'PHP_VERSION_REQD_EXPLAIN' => 'phpBB requires PHP version 5.4.0 or higher.', + 'PHP_GETIMAGESIZE_SUPPORT' => 'PHP getimagesize() function is required', + 'PHP_GETIMAGESIZE_SUPPORT_EXPLAIN' => 'In order for phpBB to function correctly, the getimagesize function needs to be available.', + 'PCRE_UTF_SUPPORT' => 'PCRE UTF-8 support', + 'PCRE_UTF_SUPPORT_EXPLAIN' => 'phpBB will not run if your PHP installation is not compiled with UTF-8 support in the PCRE extension.', + 'PHP_JSON_SUPPORT' => 'PHP JSON support', + 'PHP_JSON_SUPPORT_EXPLAIN' => 'In order for phpBB to function correctly, the PHP JSON extension needs to be available.', + 'PHP_XML_SUPPORT' => 'PHP XML/DOM support', + 'PHP_XML_SUPPORT_EXPLAIN' => 'In order for phpBB to function correctly, the PHP XML/DOM extension needs to be available.', + 'PHP_SUPPORTED_DB' => 'Supported databases', + 'PHP_SUPPORTED_DB_EXPLAIN' => 'You must have support for at least one compatible database within PHP. If no database modules are shown as available you should contact your hosting provider or review the relevant PHP installation documentation for advice.', + + 'RETEST_REQUIREMENTS' => 'Retest requirements', + + 'STAGE_REQUIREMENTS' => 'Check requirements', +)); + +// General error messages +$lang = array_merge($lang, array( + 'INST_ERR_MISSING_DATA' => 'You must fill out all fields in this block.', + + 'TIMEOUT_DETECTED_TITLE' => 'The installer detected a timeout', + 'TIMEOUT_DETECTED_MESSAGE' => 'The installer has detected a timeout, you may try to refresh the page, which may lead to data corruption. We suggest that you either increase your timeout settings or try to use the CLI.', +)); + +// Data obtaining translations +$lang = array_merge($lang, array( + 'STAGE_OBTAIN_DATA' => 'Set installation data', + + // + // Admin data + // + 'STAGE_ADMINISTRATOR' => 'Administrator details', + + // Form labels + 'ADMIN_CONFIG' => 'Administrator configuration', + 'ADMIN_PASSWORD' => 'Administrator password', + 'ADMIN_PASSWORD_CONFIRM' => 'Confirm administrator password', + 'ADMIN_PASSWORD_EXPLAIN' => 'Please enter a password between 6 and 30 characters in length.', + 'ADMIN_USERNAME' => 'Administrator username', + 'ADMIN_USERNAME_EXPLAIN' => 'Please enter a username between 3 and 20 characters in length.', + + // Errors + 'INST_ERR_EMAIL_INVALID' => 'The email address you entered is invalid.', + 'INST_ERR_PASSWORD_MISMATCH' => 'The passwords you entered did not match.', + 'INST_ERR_PASSWORD_TOO_LONG' => 'The password you entered is too long. The maximum length is 30 characters.', + 'INST_ERR_PASSWORD_TOO_SHORT' => 'The password you entered is too short. The minimum length is 6 characters.', + 'INST_ERR_USER_TOO_LONG' => 'The username you entered is too long. The maximum length is 20 characters.', + 'INST_ERR_USER_TOO_SHORT' => 'The username you entered is too short. The minimum length is 3 characters.', + + // + // Board data + // + // Form labels + 'BOARD_CONFIG' => 'Bulletin board configuration', + 'DEFAULT_LANGUAGE' => 'Default language', + 'BOARD_NAME' => 'Title of the board', + 'BOARD_DESCRIPTION' => 'Short description of the board', + + // + // Database data + // + 'STAGE_DATABASE' => 'Database settings', + + // Form labels + 'DB_CONFIG' => 'Database configuration', + 'DBMS' => 'Database type', + 'DB_HOST' => 'Database server hostname or DSN', + 'DB_HOST_EXPLAIN' => 'DSN stands for Data Source Name and is relevant only for ODBC installs. On PostgreSQL, use localhost to connect to the local server via UNIX domain socket and 127.0.0.1 to connect via TCP. For SQLite, enter the full path to your database file.', + 'DB_PORT' => 'Database server port', + 'DB_PORT_EXPLAIN' => 'Leave this blank unless you know the server operates on a non-standard port.', + 'DB_PASSWORD' => 'Database password', + 'DB_NAME' => 'Database name', + 'DB_USERNAME' => 'Database username', + 'DATABASE_VERSION' => 'Database version', + 'TABLE_PREFIX' => 'Prefix for tables in database', + 'TABLE_PREFIX_EXPLAIN' => 'The prefix must start with a letter and must only contain letters, numbers and underscores.', + + // Database options + 'DB_OPTION_MSSQL_ODBC' => 'MSSQL Server 2000+ via ODBC', + 'DB_OPTION_MSSQLNATIVE' => 'MSSQL Server 2005+ [ Native ]', + 'DB_OPTION_MYSQL' => 'MySQL', + 'DB_OPTION_MYSQLI' => 'MySQL with MySQLi Extension', + 'DB_OPTION_ORACLE' => 'Oracle', + 'DB_OPTION_POSTGRES' => 'PostgreSQL', + 'DB_OPTION_SQLITE3' => 'SQLite 3', + + // Errors + 'INST_ERR_DB' => 'Database installation error', + 'INST_ERR_NO_DB' => 'Cannot load the PHP module for the selected database type.', + 'INST_ERR_DB_INVALID_PREFIX' => 'The prefix you entered is invalid. It must start with a letter and must only contain letters, numbers and underscores.', + 'INST_ERR_PREFIX_TOO_LONG' => 'The table prefix you have specified is too long. The maximum length is %d characters.', + 'INST_ERR_DB_NO_NAME' => 'No database name specified.', + 'INST_ERR_DB_FORUM_PATH' => 'The database file specified is within your board directory tree. You should put this file in a non web-accessible location.', + 'INST_ERR_DB_CONNECT' => 'Could not connect to the database, see error message below.', + 'INST_ERR_DB_NO_WRITABLE' => 'Both the database and the directory containing it must be writable.', + 'INST_ERR_DB_NO_ERROR' => 'No error message given.', + 'INST_ERR_PREFIX' => 'Tables with the specified prefix already exist, please choose an alternative.', + 'INST_ERR_DB_NO_MYSQLI' => 'The version of MySQL installed on this machine is incompatible with the “MySQL with MySQLi Extension” option you have selected. Please try the “MySQL” option instead.', + 'INST_ERR_DB_NO_SQLITE3' => 'The version of the SQLite extension you have installed is too old, it must be upgraded to at least 3.6.15.', + 'INST_ERR_DB_NO_ORACLE' => 'The version of Oracle installed on this machine requires you to set the NLS_CHARACTERSET parameter to UTF8. Either upgrade your installation to 9.2+ or change the parameter.', + 'INST_ERR_DB_NO_POSTGRES' => 'The database you have selected was not created in UNICODE or UTF8 encoding. Try installing with a database in UNICODE or UTF8 encoding.', + 'INST_SCHEMA_FILE_NOT_WRITABLE' => 'The schema file is not writable', + + // + // Email data + // + 'EMAIL_CONFIG' => 'E-mail configuration', + + // Package info + 'PACKAGE_VERSION' => 'Package version installed', + 'UPDATE_INCOMPLETE' => 'Your phpBB installation has not been correctly updated.', + 'UPDATE_INCOMPLETE_MORE' => 'Please read the information below in order to fix this error.', + 'UPDATE_INCOMPLETE_EXPLAIN' => '

Incomplete update

+ +

We noticed that the last update of your phpBB installation hasn’t been completed. Visit the database updater, ensure Update database only is selected and click on Submit. Don\'t forget to delete the "install"-directory after you have updated the database sucessfully.

', + + // + // Server data + // + // Form labels + 'UPGRADE_INSTRUCTIONS' => 'A new feature release %1$s is available. Please read the release announcement to learn about what it has to offer, and how to upgrade.', + 'SERVER_CONFIG' => 'Server configuration', + 'SCRIPT_PATH' => 'Script path', + 'SCRIPT_PATH_EXPLAIN' => 'The path where phpBB is located relative to the domain name, e.g. /phpBB3.', +)); + +// Default database schema entries... +$lang = array_merge($lang, array( + 'CONFIG_BOARD_EMAIL_SIG' => 'Thanks, The Management', + 'CONFIG_SITE_DESC' => 'A short text to describe your forum', + 'CONFIG_SITENAME' => 'yourdomain.com', + + 'DEFAULT_INSTALL_POST' => 'This is an example post in your phpBB3 installation. Everything seems to be working. You may delete this post if you like and continue to set up your board. During the installation process your first category and your first forum are assigned an appropriate set of permissions for the predefined usergroups administrators, bots, global moderators, guests, registered users and registered COPPA users. If you also choose to delete your first category and your first forum, do not forget to assign permissions for all these usergroups for all new categories and forums you create. It is recommended to rename your first category and your first forum and copy permissions from these while creating new categories and forums. Have fun!', + + 'FORUMS_FIRST_CATEGORY' => 'Your first category', + 'FORUMS_TEST_FORUM_DESC' => 'Description of your first forum.', + 'FORUMS_TEST_FORUM_TITLE' => 'Your first forum', + + 'RANKS_SITE_ADMIN_TITLE' => 'Site Admin', + 'REPORT_WAREZ' => 'The post contains links to illegal or pirated software.', + 'REPORT_SPAM' => 'The reported post has the only purpose to advertise for a website or another product.', + 'REPORT_OFF_TOPIC' => 'The reported post is off topic.', + 'REPORT_OTHER' => 'The reported post does not fit into any other category, please use the further information field.', + + 'SMILIES_ARROW' => 'Arrow', + 'SMILIES_CONFUSED' => 'Confused', + 'SMILIES_COOL' => 'Cool', + 'SMILIES_CRYING' => 'Crying or Very Sad', + 'SMILIES_EMARRASSED' => 'Embarrassed', + 'SMILIES_EVIL' => 'Evil or Very Mad', + 'SMILIES_EXCLAMATION' => 'Exclamation', + 'SMILIES_GEEK' => 'Geek', + 'SMILIES_IDEA' => 'Idea', + 'SMILIES_LAUGHING' => 'Laughing', + 'SMILIES_MAD' => 'Mad', + 'SMILIES_MR_GREEN' => 'Mr. Green', + 'SMILIES_NEUTRAL' => 'Neutral', + 'SMILIES_QUESTION' => 'Question', + 'SMILIES_RAZZ' => 'Razz', + 'SMILIES_ROLLING_EYES' => 'Rolling Eyes', + 'SMILIES_SAD' => 'Sad', + 'SMILIES_SHOCKED' => 'Shocked', + 'SMILIES_SMILE' => 'Smile', + 'SMILIES_SURPRISED' => 'Surprised', + 'SMILIES_TWISTED_EVIL' => 'Twisted Evil', + 'SMILIES_UBER_GEEK' => 'Uber Geek', + 'SMILIES_VERY_HAPPY' => 'Very Happy', + 'SMILIES_WINK' => 'Wink', + + 'TOPICS_TOPIC_TITLE' => 'Welcome to phpBB3', +)); + +// Common navigation items' translation +$lang = array_merge($lang, array( + 'MENU_OVERVIEW' => 'Overview', + 'MENU_INTRO' => 'Introduction', + 'MENU_LICENSE' => 'License', + 'MENU_SUPPORT' => 'Support', +)); + +// Task names +$lang = array_merge($lang, array( + // Install filesystem + 'TASK_CREATE_CONFIG_FILE' => 'Creating configuration file', + + // Install database + 'TASK_ADD_CONFIG_SETTINGS' => 'Adding configuration settings', + 'TASK_ADD_DEFAULT_DATA' => 'Adding default settings to the database', + 'TASK_CREATE_DATABASE_SCHEMA_FILE' => 'Creating database schema file', + 'TASK_SETUP_DATABASE' => 'Setting up database', + 'TASK_CREATE_TABLES' => 'Creating tables', + + // Install data + 'TASK_ADD_BOTS' => 'Registering bots', + 'TASK_ADD_LANGUAGES' => 'Installing available languages', + 'TASK_ADD_MODULES' => 'Installing modules', + 'TASK_CREATE_SEARCH_INDEX' => 'Creating search index', + + // Install finish tasks + 'TASK_INSTALL_EXTENSIONS' => 'Installing packaged extensions', + 'TASK_NOTIFY_USER' => 'Sending notification e-mail', + 'TASK_POPULATE_MIGRATIONS' => 'Populating migrations', + + // Installer general progress messages + 'INSTALLER_FINISHED' => 'The installer has finished successfully', +)); + +// Installer's general messages +$lang = array_merge($lang, array( + 'MODULE_NOT_FOUND' => 'Module not found', + 'MODULE_NOT_FOUND_DESCRIPTION' => 'A module could not be found because the service, %s, is undefined.', + + 'TASK_NOT_FOUND' => 'Task not found', + 'TASK_NOT_FOUND_DESCRIPTION' => 'A task could not be found because the service, %s, is undefined.', + + 'SKIP_MODULE' => 'Skip “%s” module', + 'SKIP_TASK' => 'Skip “%s” task', + + 'TASK_SERVICE_INSTALLER_MISSING' => 'All installer task services should start with “installer”', + 'TASK_CLASS_NOT_FOUND' => 'Installer task service definition is invalid. Service name “%1$s” given, the expected class namespace is “%2$s” for that. For more information please see the documentation of task_interface.', + + 'INSTALLER_CONFIG_NOT_WRITABLE' => 'The installer config file is not writable.', +)); + +// CLI messages +$lang = array_merge($lang, array( + 'CLI_INSTALL_BOARD' => 'Install phpBB', + 'CLI_UPDATE_BOARD' => 'Update phpBB', + 'CLI_INSTALL_SHOW_CONFIG' => 'Show the configuration which will be used', + 'CLI_INSTALL_VALIDATE_CONFIG' => 'Validate a configuration file', + 'CLI_CONFIG_FILE' => 'Config file to use', + 'MISSING_FILE' => 'Unable to access file %1$s', + 'MISSING_DATA' => 'Config file is missing data or might contain invalid settings.', + 'INVALID_YAML_FILE' => 'Could not parse YAML file %1$s', + 'CONFIGURATION_VALID' => 'The configuration file is valid', +)); + +// Common updater messages +$lang = array_merge($lang, array( + 'UPDATE_INSTALLATION' => 'Update phpBB installation', + 'UPDATE_INSTALLATION_EXPLAIN' => 'With this option, it is possible to update your phpBB installation to the latest version.
During the process all of your files will be checked for their integrity. You are able to review all differences and files before the update.

The file update itself can be done in two different ways.

Manual Update

With this update you only download your personal set of changed files to make sure you do not lose your file modifications you may have done. After you downloaded this package you need to manually upload the files to their correct position under your phpBB root directory. Once done, you are able to do the file check stage again to see if you moved the files to their correct location.

Automatic Update with FTP

This method is similar to the first one but without the need to download the changed files and uploading them on your own. This will be done for you. In order to use this method you need to know your FTP login details since you will be asked for them. Once finished you will be redirected to the file check again to make sure everything got updated correctly.

', + 'UPDATE_INSTRUCTIONS' => ' + +

Release announcement

+ +

Please read the release announcement for the latest version before you continue your update process, it may contain useful information. It also contains full download links as well as the change log.

+ +
+ +

How to update your installation with the Full Package

+ +

The recommended way of updating your installation is using the full package. If core phpBB files have been modified in your installation you may wish to use the automatic update package in order to not lose these changes. You are also able to update your installation using the other methods listed within the INSTALL.html document. The steps for updating phpBB3 using the full package are:

+ +
    +
  1. Backup all board files and the database.
  2. +
  3. Go to the phpBB.com downloads page and download the latest "Full Package" archive.
  4. +
  5. Unpack the archive.
  6. +
  7. Remove (delete) the config.php file, and the /images, /store and /files folders from the package (not your site).
  8. +
  9. Go to the ACP, Board settings, and make sure prosilver is set as the default style. If not, set it to prosilver.
  10. +
  11. Delete the /vendor and /cache folders from the board’s root folder on the host.
  12. +
  13. Via FTP or SSH upload the remaining files and folders (that is, the remaining CONTENTS of the phpBB3 folder) to the root folder of your board installation on the server, overwriting the existing files. (Note: take care not to delete any extensions in your /ext folder when uploading the new phpBB3 contents.)
  14. +
  15. Now start the update process by pointing your browser to the install folder.
  16. +
  17. Follow the steps to update the database and let that run to completion.
  18. +
  19. Via FTP or SSH delete the /install folder from the root of your board installation.

  20. +
+ +

You now have a new up to date board containing all your users and posts. Follow up tasks:

+
    +
  • Update your language pack
  • +
  • Update your style

  • +
+ +

How to update your installation with the Automatic Update Package

+ +

The automatic update package is only recommended in case core phpBB files have been modified in your installation. You are also able to update your installation using the methods listed within the INSTALL.html document. The steps for updating phpBB3 using the automatic update package are:

+ +
    +
  1. Go to the phpBB.com downloads page and download the "Automatic Update Package" archive.
  2. +
  3. Unpack the archive.
  4. +
  5. Upload the complete uncompressed "install" and "vendor" folders to your phpBB root directory (where your config.php file is).

  6. +
+ +

Once uploaded your board will be offline for normal users due to the install directory you uploaded now being present.

+ Now start the update process by pointing your browser to the install folder.
+
+ You will then be guided through the update process. You will be notified once the update is complete. +

+ ', +)); + +// Updater forms +$lang = array_merge($lang, array( + // Updater types + 'UPDATE_TYPE' => 'Type of update to run', + + 'UPDATE_TYPE_ALL' => 'Update filesystem and database', + 'UPDATE_TYPE_DB_ONLY' => 'Update database only', + + // File updater methods + 'UPDATE_FILE_METHOD_TITLE' => 'File updater methods', + + 'UPDATE_FILE_METHOD' => 'File updater method', + 'UPDATE_FILE_METHOD_DOWNLOAD' => 'Download modified files in an archive', + 'UPDATE_FILE_METHOD_FTP' => 'Update files via FTP (Automatic)', + 'UPDATE_FILE_METHOD_FILESYSTEM' => 'Update files via direct file access (Automatic)', + + // File updater archives + 'SELECT_DOWNLOAD_FORMAT' => 'Select download archive format', + + // FTP settings + 'FTP_SETTINGS' => 'FTP settings', +)); + +// Requirements messages +$lang = array_merge($lang, array( + 'UPDATE_FILES_NOT_FOUND' => 'No valid update directory was found, please make sure you uploaded the relevant files.', + + 'NO_UPDATE_FILES_UP_TO_DATE' => 'Your version is up to date. There is no need to run the update tool. If you want to make an integrity check on your files make sure you uploaded the correct update files.', + 'OLD_UPDATE_FILES' => 'Update files are out of date. The update files found are for updating from phpBB %1$s to phpBB %2$s but the latest version of phpBB is %3$s.', + 'INCOMPATIBLE_UPDATE_FILES' => 'The update files found are incompatible with your installed version. Your installed version is %1$s and the update file is for updating phpBB %2$s to %3$s.', +)); + +// Update files +$lang = array_merge($lang, array( + 'STAGE_UPDATE_FILES' => 'Update files', + + // Check files + 'UPDATE_CHECK_FILES' => 'Check files to update', + + // Update file differ + 'FILE_DIFFER_ERROR_FILE_CANNOT_BE_READ' => 'The file differ failed to open %s.', + + 'UPDATE_FILE_DIFF' => 'Diffing changed files', + 'ALL_FILES_DIFFED' => 'All modified files has been diffed.', + + // File status + 'UPDATE_CONTINUE_FILE_UPDATE' => 'Update files', + + 'DOWNLOAD' => 'Download', + 'DOWNLOAD_CONFLICTS' => 'Download merge conflicts archive', + 'DOWNLOAD_CONFLICTS_EXPLAIN' => 'Search for <<< to spot conflicts', + 'DOWNLOAD_UPDATE_METHOD' => 'Download modified files archive', + 'DOWNLOAD_UPDATE_METHOD_EXPLAIN' => 'Once downloaded you should unpack the archive. You will find the modified files you need to upload to your phpBB root directory within it. Please upload the files to their respective locations then. After you have uploaded all files, you may continue with the update process.', + + 'FILE_ALREADY_UP_TO_DATE' => 'File is already up to date.', + 'FILE_DIFF_NOT_ALLOWED' => 'File not allowed to be diffed.', + 'FILE_USED' => 'Information used from', // Single file + 'FILES_CONFLICT' => 'Conflict files', + 'FILES_CONFLICT_EXPLAIN' => 'The following files are modified and do not represent the original files from the old version. phpBB determined that these files create conflicts if they are tried to be merged. Please investigate the conflicts and try to manually resolve them or continue the update choosing the preferred merging method. If you resolve the conflicts manually check the files again after you modified them. You are also able to choose between the preferred merge method for every file. The first one will result in a file where the conflicting lines from your old file will be lost, the other one will result in losing the changes from the newer file.', + 'FILES_DELETED' => 'Deleted files', + 'FILES_DELETED_EXPLAIN' => 'The following files do not exist in the new version. These files have to be deleted from your installation.', + 'FILES_MODIFIED' => 'Modified files', + 'FILES_MODIFIED_EXPLAIN' => 'The following files are modified and do not represent the original files from the old version. The updated file will be a merge between your modifications and the new file.', + 'FILES_NEW' => 'New files', + 'FILES_NEW_EXPLAIN' => 'The following files currently do not exist within your installation. These files will be added to your installation.', + 'FILES_NEW_CONFLICT' => 'New conflicting files', + 'FILES_NEW_CONFLICT_EXPLAIN' => 'The following files are new within the latest version but it has been determined that there is already a file with the same name within the same position. This file will be overwritten by the new file.', + 'FILES_NOT_MODIFIED' => 'Not modified files', + 'FILES_NOT_MODIFIED_EXPLAIN' => 'The following files are not modified and represent the original phpBB files from the version you want to update from.', + 'FILES_UP_TO_DATE' => 'Already updated files', + 'FILES_UP_TO_DATE_EXPLAIN' => 'The following files are already up to date and do not need to be updated.', + 'FILES_VERSION' => 'Files Version', + 'TOGGLE_DISPLAY' => 'View/Hide file list', + + // File updater + 'UPDATE_UPDATING_FILES' => 'Updating files', + + 'UPDATE_FILE_UPDATER_HAS_FAILED' => 'File updater “%1$s“ has failed. The installer will try to fallback to “%2$s“.', + 'UPDATE_FILE_UPDATERS_HAVE_FAILED' => 'The file updater failed. No further fallback methods are available.', + + 'UPDATE_CONTINUE_UPDATE_PROCESS' => 'Continue update process', + 'UPDATE_RECHECK_UPDATE_FILES' => 'Check files again', +)); + +// Update database +$lang = array_merge($lang, array( + 'STAGE_UPDATE_DATABASE' => 'Update database', + + 'INLINE_UPDATE_SUCCESSFUL' => 'The database update was successful.', + + 'TASK_UPDATE_EXTENSIONS' => 'Updating extensions', +)); + +// Converter +$lang = array_merge($lang, array( + // Common converter messages + 'CONVERT_NOT_EXIST' => 'The specified convertor does not exist.', + 'DEV_NO_TEST_FILE' => 'No value has been specified for the test_file variable in the convertor. If you are a user of this convertor, you should not be seeing this error, please report this message to the convertor author. If you are a convertor author, you must specify the name of a file which exists in the source board to allow the path to it to be verified.', + 'COULD_NOT_FIND_PATH' => 'Could not find path to your former board. Please check your settings and try again.
» %s was specified as the source path.', + 'CONFIG_PHPBB_EMPTY' => 'The phpBB3 config variable for “%s” is empty.', + + 'MAKE_FOLDER_WRITABLE' => 'Please make sure that this folder exists and is writable by the webserver then try again:
»%s.', + 'MAKE_FOLDERS_WRITABLE' => 'Please make sure that these folders exist and are writable by the webserver then try again:
»%s.', + + 'INSTALL_TEST' => 'Test again', + + 'NO_TABLES_FOUND' => 'No tables found.', + 'TABLES_MISSING' => 'Could not find these tables
» %s.', + 'CHECK_TABLE_PREFIX' => 'Please check your table prefix and try again.', + + // Conversion in progress + 'CONTINUE_CONVERT' => 'Continue conversion', + 'CONTINUE_CONVERT_BODY' => 'A previous conversion attempt has been determined. You are now able to choose between starting a new conversion or continuing the conversion.', + 'CONVERT_NEW_CONVERSION' => 'New conversion', + 'CONTINUE_OLD_CONVERSION' => 'Continue previously started conversion', + + // Start conversion + 'SUB_INTRO' => 'Introduction', + 'CONVERT_INTRO' => 'Welcome to the phpBB Unified Convertor Framework', + 'CONVERT_INTRO_BODY' => 'From here, you are able to import data from other (installed) board systems. The list below shows all the conversion modules currently available. If there is no convertor shown in this list for the board software you wish to convert from, please check our website where further conversion modules may be available for download.', + 'AVAILABLE_CONVERTORS' => 'Available convertors', + 'NO_CONVERTORS' => 'No convertors are available for use.', + 'CONVERT_OPTIONS' => 'Options', + 'SOFTWARE' => 'Board software', + 'VERSION' => 'Version', + 'CONVERT' => 'Convert', + + // Settings + 'STAGE_SETTINGS' => 'Settings', + 'TABLE_PREFIX_SAME' => 'The table prefix needs to be the one used by the software you are converting from.
» Specified table prefix was %s.', + 'DEFAULT_PREFIX_IS' => 'The convertor was not able to find tables with the specified prefix. Please make sure you have entered the correct details for the board you are converting from. The default table prefix for %1$s is %2$s.', + 'SPECIFY_OPTIONS' => 'Specify conversion options', + 'FORUM_PATH' => 'Board path', + 'FORUM_PATH_EXPLAIN' => 'This is the relative path on disk to your former board from the root of this phpBB3 installation.', + 'REFRESH_PAGE' => 'Refresh page to continue conversion', + 'REFRESH_PAGE_EXPLAIN' => 'If set to yes, the convertor will refresh the page to continue the conversion after having finished a step. If this is your first conversion for testing purposes and to determine any errors in advance, we suggest to set this to No.', + + // Conversion + 'STAGE_IN_PROGRESS' => 'Conversion in progress', + + 'AUTHOR_NOTES' => 'Author notes
» %s', + 'STARTING_CONVERT' => 'Starting conversion process', + 'CONFIG_CONVERT' => 'Converting the configuration', + 'DONE' => 'Done', + 'PREPROCESS_STEP' => 'Executing pre-processing functions/queries', + 'FILLING_TABLE' => 'Filling table %s', + 'FILLING_TABLES' => 'Filling tables', + 'DB_ERR_INSERT' => 'Error while processing INSERT query.', + 'DB_ERR_LAST' => 'Error while processing query_last.', + 'DB_ERR_QUERY_FIRST' => 'Error while executing query_first.', + 'DB_ERR_QUERY_FIRST_TABLE' => 'Error while executing query_first, %s (“%s”).', + 'DB_ERR_SELECT' => 'Error while running SELECT query.', + 'STEP_PERCENT_COMPLETED' => 'Step %d of %d', + 'FINAL_STEP' => 'Process final step', + 'SYNC_FORUMS' => 'Starting to synchronise forums', + 'SYNC_POST_COUNT' => 'Synchronising post_counts', + 'SYNC_POST_COUNT_ID' => 'Synchronising post_counts from entry %1$s to %2$s.', + 'SYNC_TOPICS' => 'Starting to synchronise topics', + 'SYNC_TOPIC_ID' => 'Synchronising topics from topic_id %1$s to %2$s.', + 'PROCESS_LAST' => 'Processing last statements', + 'UPDATE_TOPICS_POSTED' => 'Generating topics posted information', + 'UPDATE_TOPICS_POSTED_ERR' => 'An error occurred while generating topics posted information. You can retry this step in the ACP after the conversion process is completed.', + 'CONTINUE_LAST' => 'Continue last statements', + 'CLEAN_VERIFY' => 'Cleaning up and verifying the final structure', + 'NOT_UNDERSTAND' => 'Could not understand %s #%d, table %s (“%s”)', + 'NAMING_CONFLICT' => 'Naming conflict: %s and %s are both aliases

%s', + + // Finish conversion + 'CONVERT_COMPLETE' => 'Conversion completed', + 'CONVERT_COMPLETE_EXPLAIN' => 'You have now successfully converted your board to phpBB 3.2. You can now login and access your board. Please ensure that the settings were transferred correctly before enabling your board by deleting the install directory. Remember that help on using phpBB is available online via the Documentation and the support forums.', + + 'CONV_ERROR_ATTACH_FTP_DIR' => 'FTP upload for attachments is enabled at the old board. Please disable the FTP upload option and make sure a valid upload directory is specified, then copy all attachment files to this new web accessible directory. Once you have done this, restart the convertor.', + 'CONV_ERROR_CONFIG_EMPTY' => 'There is no configuration information available for the conversion.', + 'CONV_ERROR_FORUM_ACCESS' => 'Unable to get forum access information.', + 'CONV_ERROR_GET_CATEGORIES' => 'Unable to get categories.', + 'CONV_ERROR_GET_CONFIG' => 'Could not retrieve your board configuration.', + 'CONV_ERROR_COULD_NOT_READ' => 'Unable to access/read “%s”.', + 'CONV_ERROR_GROUP_ACCESS' => 'Unable to get group authentication information.', + 'CONV_ERROR_INCONSISTENT_GROUPS' => 'Inconsistency in groups table detected in add_bots() - you need to add all special groups if you do it manually.', + 'CONV_ERROR_INSERT_BOT' => 'Unable to insert bot into users table.', + 'CONV_ERROR_INSERT_BOTGROUP' => 'Unable to insert bot into bots table.', + 'CONV_ERROR_INSERT_USER_GROUP' => 'Unable to insert user into user_group table.', + 'CONV_ERROR_MESSAGE_PARSER' => 'Message parser error', + 'CONV_ERROR_NO_AVATAR_PATH' => 'Note to developer: you must specify $convertor[\'avatar_path\'] to use %s.', + 'CONV_ERROR_NO_FORUM_PATH' => 'The relative path to the source board has not been specified.', + 'CONV_ERROR_NO_GALLERY_PATH' => 'Note to developer: you must specify $convertor[\'avatar_gallery_path\'] to use %s.', + 'CONV_ERROR_NO_GROUP' => 'Group “%1$s” could not be found in %2$s.', + 'CONV_ERROR_NO_RANKS_PATH' => 'Note to developer: you must specify $convertor[\'ranks_path\'] to use %s.', + 'CONV_ERROR_NO_SMILIES_PATH' => 'Note to developer: you must specify $convertor[\'smilies_path\'] to use %s.', + 'CONV_ERROR_NO_UPLOAD_DIR' => 'Note to developer: you must specify $convertor[\'upload_path\'] to use %s.', + 'CONV_ERROR_PERM_SETTING' => 'Unable to insert/update permission setting.', + 'CONV_ERROR_PM_COUNT' => 'Unable to select folder pm count.', + 'CONV_ERROR_REPLACE_CATEGORY' => 'Unable to insert new forum replacing old category.', + 'CONV_ERROR_REPLACE_FORUM' => 'Unable to insert new forum replacing old forum.', + 'CONV_ERROR_USER_ACCESS' => 'Unable to get user authentication information.', + 'CONV_ERROR_WRONG_GROUP' => 'Wrong group “%1$s” defined in %2$s.', + 'CONV_OPTIONS_BODY' => 'This page collects the data required to access the source board. Enter the database details of your former board; the converter will not change anything in the database given below. The source board should be disabled to allow a consistent conversion.', + 'CONV_SAVED_MESSAGES' => 'Saved messages', + + 'PRE_CONVERT_COMPLETE' => 'All pre-conversion steps have successfully been completed. You may now begin the actual conversion process. Please note that you may have to manually do and adjust several things. After conversion, especially check the permissions assigned, rebuild your search index which is not converted and also make sure files got copied correctly, for example avatars and smilies.', +)); diff --git a/language/en/iso.txt b/language/en/iso.txt new file mode 100644 index 0000000..2e45cc5 --- /dev/null +++ b/language/en/iso.txt @@ -0,0 +1,3 @@ +British English +British English +phpBB Limited \ No newline at end of file diff --git a/language/en/mcp.php b/language/en/mcp.php new file mode 100644 index 0000000..b196a1d --- /dev/null +++ b/language/en/mcp.php @@ -0,0 +1,434 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACTION' => 'Action', + 'ACTION_NOTE' => 'Action/Note', + 'ADD_FEEDBACK' => 'Add feedback', + 'ADD_FEEDBACK_EXPLAIN' => 'If you would like to add a report on this please fill out the following form. Only use plain text; HTML, BBCode, etc. are not permitted.', + 'ADD_WARNING' => 'Add warning', + 'ADD_WARNING_EXPLAIN' => 'To send a warning to this user please fill out the following form. Only use plain text; HTML, BBCode, etc. are not permitted.', + 'ALL_ENTRIES' => 'All entries', + 'ALL_NOTES_DELETED' => 'Successfully removed all user notes.', + 'ALL_REPORTS' => 'All reports', + 'ALREADY_REPORTED' => 'This post has already been reported.', + 'ALREADY_REPORTED_PM' => 'This private message has already been reported.', + 'ALREADY_WARNED' => 'A warning has already been issued for this post.', + 'APPROVE' => 'Approve', + 'APPROVE_POST' => 'Approve post', + 'APPROVE_POST_CONFIRM' => 'Are you sure you want to approve this post?', + 'APPROVE_POSTS' => 'Approve posts', + 'APPROVE_POSTS_CONFIRM' => 'Are you sure you want to approve the selected posts?', + 'APPROVE_TOPIC' => 'Approve topic', + 'APPROVE_TOPIC_CONFIRM' => 'Are you sure you want to approve this topic?', + 'APPROVE_TOPICS' => 'Approve topics', + 'APPROVE_TOPICS_CONFIRM'=> 'Are you sure you want to approve the selected topics?', + + 'CANNOT_MOVE_SAME_FORUM'=> 'You cannot move a topic to the forum it’s already in.', + 'CANNOT_WARN_ANONYMOUS' => 'You cannot warn unregistered guest users.', + 'CANNOT_WARN_SELF' => 'You cannot warn yourself.', + 'CAN_LEAVE_BLANK' => 'This can be left blank.', + 'CHANGE_POSTER' => 'Change poster', + 'CLOSE_PM_REPORT' => 'Close PM report', + 'CLOSE_PM_REPORT_CONFIRM' => 'Are you sure you want to close the selected PM report?', + 'CLOSE_PM_REPORTS' => 'Close PM reports', + 'CLOSE_PM_REPORTS_CONFIRM' => 'Are you sure you want to close the selected PM reports?', + 'CLOSE_REPORT' => 'Close report', + 'CLOSE_REPORT_CONFIRM' => 'Are you sure you want to close the selected report?', + 'CLOSE_REPORTS' => 'Close reports', + 'CLOSE_REPORTS_CONFIRM' => 'Are you sure you want to close the selected reports?', + + 'DELETE_PM_REPORT' => 'Delete PM report', + 'DELETE_PM_REPORT_CONFIRM' => 'Are you sure you want to delete the selected PM report?', + 'DELETE_PM_REPORTS' => 'Delete PM reports', + 'DELETE_PM_REPORTS_CONFIRM' => 'Are you sure you want to delete the selected PM reports?', + 'DELETE_POSTS' => 'Delete posts', + 'DELETE_REPORT' => 'Delete report', + 'DELETE_REPORT_CONFIRM' => 'Are you sure you want to delete the selected report?', + 'DELETE_REPORTS' => 'Delete reports', + 'DELETE_REPORTS_CONFIRM' => 'Are you sure you want to delete the selected reports?', + 'DELETE_SHADOW_TOPIC' => 'Delete shadow topic', + 'DELETE_TOPICS' => 'Delete selected topics', + 'DISAPPROVE' => 'Disapprove', + 'DISAPPROVE_REASON' => 'Reason for disapproval', + 'DISAPPROVE_POST' => 'Disapprove post', + 'DISAPPROVE_POST_CONFIRM' => 'Are you sure you want to disapprove this post?', + 'DISAPPROVE_POSTS' => 'Disapprove posts', + 'DISAPPROVE_POSTS_CONFIRM' => 'Are you sure you want to disapprove the selected posts?', + 'DISPLAY_LOG' => 'Display entries from previous', + 'DISPLAY_OPTIONS' => 'Display options', + + 'EMPTY_REPORT' => 'You must enter a description when selecting this reason.', + 'EMPTY_TOPICS_REMOVED_WARNING' => 'Please note that one or several topics have been removed from the database because they were or become empty.', + + 'FEEDBACK' => 'Feedback', + 'FORK' => 'Copy', + 'FORK_TOPIC' => 'Copy topic', + 'FORK_TOPIC_CONFIRM' => 'Are you sure you want to copy this topic?', + 'FORK_TOPICS' => 'Copy selected topics', + 'FORK_TOPICS_CONFIRM' => 'Are you sure you want to copy the selected topics?', + 'FORUM_DESC' => 'Description', + 'FORUM_NAME' => 'Forum name', + 'FORUM_NOT_EXIST' => 'The forum you selected does not exist.', + 'FORUM_NOT_POSTABLE' => 'The forum you selected cannot be posted to.', + 'FORUM_STATUS' => 'Forum status', + 'FORUM_STYLE' => 'Forum style', + + 'GLOBAL_ANNOUNCEMENT' => 'Global announcement', + + 'IP_INFO' => 'IP address information', + 'IPS_POSTED_FROM' => 'IP addresses this user has posted from', + + 'LATEST_LOGS' => 'Latest 5 logged actions', + 'LATEST_REPORTED' => 'Latest 5 reports', + 'LATEST_REPORTED_PMS' => 'Latest 5 PM reports', + 'LATEST_UNAPPROVED' => 'Latest 5 posts awaiting approval', + 'LATEST_WARNING_TIME' => 'Latest warning issued', + 'LATEST_WARNINGS' => 'Latest 5 warnings', + 'LEAVE_SHADOW' => 'Leave shadow topic in place', + 'LIST_REPORTS' => array( + 1 => '%d report', + 2 => '%d reports', + ), + 'LOCK' => 'Lock', + 'LOCK_POST_POST' => 'Lock post', + 'LOCK_POST_POST_CONFIRM' => 'Are you sure you want to prevent editing this post?', + 'LOCK_POST_POSTS' => 'Lock selected posts', + 'LOCK_POST_POSTS_CONFIRM' => 'Are you sure you want to prevent editing the selected posts?', + 'LOCK_TOPIC_CONFIRM' => 'Are you sure you want to lock this topic?', + 'LOCK_TOPICS' => 'Lock selected topics', + 'LOCK_TOPICS_CONFIRM' => 'Are you sure you want to lock all selected topics?', + 'LOGS_CURRENT_TOPIC' => 'Currently viewing logs of:', + 'LOGIN_EXPLAIN_MCP' => 'To moderate this forum you must login.', + 'LOGVIEW_VIEWPOST' => 'View post', + 'LOGVIEW_VIEWTOPIC' => 'View topic', + 'LOGVIEW_VIEWLOGS' => 'View topic log', + 'LOGVIEW_VIEWFORUM' => 'View forum', + 'LOOKUP_ALL' => 'Look up all IPs', + 'LOOKUP_IP' => 'Look up IP', + + 'MARKED_NOTES_DELETED' => 'Successfully removed all marked user notes.', + + 'MCP_ADD' => 'Add a warning', + + 'MCP_BAN' => 'Banning', + 'MCP_BAN_EMAILS' => 'Ban emails', + 'MCP_BAN_IPS' => 'Ban IPs', + 'MCP_BAN_USERNAMES' => 'Ban Usernames', + + 'MCP_LOGS' => 'Moderator logs', + 'MCP_LOGS_FRONT' => 'Front page', + 'MCP_LOGS_FORUM_VIEW' => 'Forum logs', + 'MCP_LOGS_TOPIC_VIEW' => 'Topic logs', + + 'MCP_MAIN' => 'Main', + 'MCP_MAIN_FORUM_VIEW' => 'View forum', + 'MCP_MAIN_FRONT' => 'Front page', + 'MCP_MAIN_POST_DETAILS' => 'Post details', + 'MCP_MAIN_TOPIC_VIEW' => 'View topic', + 'MCP_MAKE_ANNOUNCEMENT' => 'Modify to “Announcement”', + 'MCP_MAKE_ANNOUNCEMENT_CONFIRM' => 'Are you sure you want to change this topic to an “Announcement”?', + 'MCP_MAKE_ANNOUNCEMENTS' => 'Modify to “Announcements”', + 'MCP_MAKE_ANNOUNCEMENTS_CONFIRM'=> 'Are you sure you want to change the selected topics to “Announcements”?', + 'MCP_MAKE_GLOBAL' => 'Modify to “Global announcement”', + 'MCP_MAKE_GLOBAL_CONFIRM' => 'Are you sure you want to change this topic to a “Global announcement”?', + 'MCP_MAKE_GLOBALS' => 'Modify to “Global announcements”', + 'MCP_MAKE_GLOBALS_CONFIRM' => 'Are you sure you want to change the selected topics to “Global announcements”?', + 'MCP_MAKE_STICKY' => 'Modify to “Sticky”', + 'MCP_MAKE_STICKY_CONFIRM' => 'Are you sure you want to change this topic to a “Sticky”?', + 'MCP_MAKE_STICKIES' => 'Modify to “Stickies”', + 'MCP_MAKE_STICKIES_CONFIRM' => 'Are you sure you want to change the selected topics to “Stickies”?', + 'MCP_MAKE_NORMAL' => 'Modify to “Standard Topic”', + 'MCP_MAKE_NORMAL_CONFIRM' => 'Are you sure you want to change this topic to a “Standard Topic”?', + 'MCP_MAKE_NORMALS' => 'Modify to “Standard Topics”', + 'MCP_MAKE_NORMALS_CONFIRM' => 'Are you sure you want to change the selected topics to “Standard Topics”?', + + 'MCP_NOTES' => 'User notes', + 'MCP_NOTES_FRONT' => 'Front page', + 'MCP_NOTES_USER' => 'User details', + + 'MCP_POST_REPORTS' => 'Reports issued on this post', + + 'MCP_PM_REPORTS' => 'Reported PMs', + 'MCP_PM_REPORT_DETAILS' => 'PM Report details', + 'MCP_PM_REPORTS_CLOSED' => 'Closed PM reports', + 'MCP_PM_REPORTS_CLOSED_EXPLAIN' => 'This is a list of all reports about private messages which have previously been resolved.', + 'MCP_PM_REPORTS_OPEN' => 'Open PM reports', + 'MCP_PM_REPORTS_OPEN_EXPLAIN' => 'This is a list of all reported private messages which are still to be handled.', + + 'MCP_REPORTS' => 'Reported messages', + 'MCP_REPORT_DETAILS' => 'Report details', + 'MCP_REPORTS_CLOSED' => 'Closed reports', + 'MCP_REPORTS_CLOSED_EXPLAIN' => 'This is a list of all reports about posts which have previously been resolved.', + 'MCP_REPORTS_OPEN' => 'Open reports', + 'MCP_REPORTS_OPEN_EXPLAIN' => 'This is a list of all reported posts which are still to be handled.', + + 'MCP_QUEUE' => 'Moderation queue', + 'MCP_QUEUE_APPROVE_DETAILS' => 'Approve details', + 'MCP_QUEUE_UNAPPROVED_POSTS' => 'Posts awaiting approval', + 'MCP_QUEUE_UNAPPROVED_POSTS_EXPLAIN' => 'This is a list of all posts which require approving before they will be visible to users.', + 'MCP_QUEUE_UNAPPROVED_TOPICS' => 'Topics awaiting approval', + 'MCP_QUEUE_UNAPPROVED_TOPICS_EXPLAIN' => 'This is a list of all topics which require approving before they will be visible to users.', + 'MCP_QUEUE_DELETED_POSTS' => 'Deleted posts', + 'MCP_QUEUE_DELETED_POSTS_EXPLAIN' => 'This is a list of all soft deleted posts. You can restore or permanently delete the posts from this screen.', + 'MCP_QUEUE_DELETED_TOPICS' => 'Deleted topics', + 'MCP_QUEUE_DELETED_TOPICS_EXPLAIN' => 'This is a list of all soft deleted topics. You can restore or permanently delete the topics from this screen.', + + 'MCP_VIEW_USER' => 'View warnings for a specific user', + + 'MCP_WARN' => 'Warnings', + 'MCP_WARN_FRONT' => 'Front page', + 'MCP_WARN_LIST' => 'List warnings', + 'MCP_WARN_POST' => 'Warn for specific post', + 'MCP_WARN_USER' => 'Warn user', + + 'MERGE_POSTS_CONFIRM' => 'Are you sure you want to move the selected posts?', + 'MERGE_TOPIC_EXPLAIN' => 'Using the form below you can move selected posts into another topic. The posts will be split from this topic and merged into the other topic. These posts will not be reordered and will appear as if the users posted them to the new topic.
Please enter the destination topic id or click on “Select topic” to search for one.', + 'MERGE_TOPIC_ID' => 'Destination topic identification number', + 'MERGE_TOPICS' => 'Merge topics', + 'MERGE_TOPICS_CONFIRM' => 'Are you sure you want to merge the selected topics?', + 'MODERATE_FORUM' => 'Moderate forum', + 'MODERATE_TOPIC' => 'Moderate topic', + 'MODERATE_POST' => 'Moderate post', + 'MOD_OPTIONS' => 'Moderator options', + 'MORE_INFO' => 'Further information', + 'MOST_WARNINGS' => 'Users with most warnings', + 'MOVE_TOPIC_CONFIRM' => 'Are you sure you want to move the topic into a new forum?', + 'MOVE_TOPICS' => 'Move selected topics', + 'MOVE_TOPICS_CONFIRM' => 'Are you sure you want to move the selected topics into a new forum?', + + 'NOTIFY_POSTER_APPROVAL' => 'Notify poster about approval?', + 'NOTIFY_POSTER_DISAPPROVAL' => 'Notify poster about disapproval?', + 'NOTIFY_USER_WARN' => 'Notify user about warning?', + 'NOT_MODERATOR' => 'You are not a moderator of this forum.', + 'NO_DESTINATION_FORUM' => 'Please select a forum for destination.', + 'NO_DESTINATION_FORUM_FOUND' => 'There is no destination forum available.', + 'NO_ENTRIES' => 'No log entries.', + 'NO_FEEDBACK' => 'No feedback exists for this user.', + 'NO_FINAL_TOPIC_SELECTED' => 'You have to select a destination topic for merging posts.', + 'NO_MATCHES_FOUND' => 'No matches found.', + 'NO_POST' => 'You have to select a post in order to warn the user for a post.', + 'NO_POST_REPORT' => 'This post was not reported.', + 'NO_POST_SELECTED' => 'You must select at least one post to perform this action.', + 'NO_POSTS_DELETED' => 'There are no deleted posts.', + 'NO_POSTS_QUEUE' => 'There are no posts waiting for approval.', + 'NO_REASON_DISAPPROVAL' => 'Please give an appropriate reason for disapproval.', + 'NO_REPORT' => 'No report found', + 'NO_REPORTS' => 'No reports found', + 'NO_REPORT_SELECTED' => 'You must select at least one report to perform this action.', + 'NO_TOPIC_ICON' => 'None', + 'NO_TOPIC_SELECTED' => 'You must select at least one topic to perform this action.', + 'NO_TOPICS_DELETED' => 'There are no deleted topics.', + 'NO_TOPICS_QUEUE' => 'There are no topics waiting for approval.', + + 'ONLY_TOPIC' => 'Only topic “%s”', + 'OTHER_USERS' => 'Other users posting from this IP', + + 'QUICKMOD_ACTION_NOT_ALLOWED' => "%s not allowed as quickmod", + + 'PM_REPORT_CLOSED_SUCCESS' => 'The selected PM report has been closed successfully.', + 'PM_REPORT_DELETED_SUCCESS' => 'The selected PM report has been deleted successfully.', + 'PM_REPORTED_SUCCESS' => 'This private message has been successfully reported.', + 'PM_REPORTS_CLOSED_SUCCESS' => 'The selected PM reports have been closed successfully.', + 'PM_REPORTS_DELETED_SUCCESS'=> 'The selected PM reports have been deleted successfully.', + 'PM_REPORTS_TOTAL' => array( + 0 => 'There are no PM reports to review.', + 1 => 'In total there is 1 PM report to review.', + 2 => 'In total there are %d PM reports to review.', + ), + 'PM_REPORT_DETAILS' => 'Private message report details', + 'POSTER' => 'Poster', + 'POSTS_APPROVED_SUCCESS' => 'The selected posts have been approved.', + 'POSTS_DELETED_SUCCESS' => 'The selected posts have been successfully removed from the database.', + 'POSTS_DISAPPROVED_SUCCESS' => 'The selected posts have been disapproved.', + 'POSTS_LOCKED_SUCCESS' => 'The selected posts have been locked successfully.', + 'POSTS_MERGED_SUCCESS' => 'The selected posts have been merged.', + 'POSTS_PER_PAGE' => 'Posts per page', + 'POSTS_PER_PAGE_EXPLAIN' => '(Set to 0 to view all posts.)', + 'POSTS_RESTORED_SUCCESS' => 'The selected posts have been restored successfully.', + 'POSTS_UNLOCKED_SUCCESS' => 'The selected posts have been unlocked successfully.', + 'POST_APPROVED_SUCCESS' => 'The selected post has been approved.', + 'POST_DELETED_SUCCESS' => 'The selected post has been successfully removed from the database.', + 'POST_DISAPPROVED_SUCCESS' => 'The selected post has been disapproved.', + 'POST_LOCKED_SUCCESS' => 'Post locked successfully.', + 'POST_NOT_EXIST' => 'The post you requested does not exist.', + 'POST_REPORTED_SUCCESS' => 'This post has been successfully reported.', + 'POST_RESTORED_SUCCESS' => 'This post has been restored successfully.', + 'POST_UNLOCKED_SUCCESS' => 'Post unlocked successfully.', + + 'READ_USERNOTES' => 'User notes', + 'READ_WARNINGS' => 'User warnings', + 'REPORTER' => 'Reporter', + 'REPORTED' => 'Reported', + 'REPORTED_BY' => 'Reported by', + 'REPORTED_ON_DATE' => 'on', + 'REPORTS_CLOSED_SUCCESS' => 'The selected reports have been closed successfully.', + 'REPORTS_DELETED_SUCCESS' => 'The selected reports have been deleted successfully.', + 'REPORTS_TOTAL' => array( + 0 => 'There are no reports to review.', + 1 => 'In total there is 1 report to review.', + 2 => 'In total there are %d reports to review.', + ), + 'REPORT_CLOSED' => 'This report has already been closed.', + 'REPORT_CLOSED_SUCCESS' => 'The selected report has been closed successfully.', + 'REPORT_DELETED_SUCCESS' => 'The selected report has been deleted successfully.', + 'REPORT_DETAILS' => 'Report details', + 'REPORT_MESSAGE' => 'Report this message', + 'REPORT_MESSAGE_EXPLAIN' => 'Use this form to report the selected private message. Reporting should generally be used only if the message breaks forum rules. Reporting a private message will make its contents visible to all moderators.', + 'REPORT_NOTIFY' => 'Notify me', + 'REPORT_NOTIFY_EXPLAIN' => 'Informs you when your report is dealt with.', + 'REPORT_POST_EXPLAIN' => 'Use this form to report the selected post to the forum moderators and board administrators. Reporting should generally be used only if the post breaks forum rules.', + 'REPORT_REASON' => 'Report reason', + 'REPORT_TIME' => 'Report time', + 'RESTORE' => 'Restore', + 'RESTORE_POST' => 'Restore post', + 'RESTORE_POST_CONFIRM' => 'Are you sure you want to restore this post?', + 'RESTORE_POSTS' => 'Restore posts', + 'RESTORE_POSTS_CONFIRM' => 'Are you sure you want to restore the selected posts?', + 'RESTORE_TOPIC' => 'Restore topic', + 'RESTORE_TOPIC_CONFIRM' => 'Are you sure you want to restore this topic?', + 'RESTORE_TOPICS' => 'Restore topics', + 'RESTORE_TOPICS_CONFIRM' => 'Are you sure you want to restore the selected topics?', + 'RESYNC' => 'Resync', + 'RETURN_MESSAGE' => '%sReturn to the message%s', + 'RETURN_NEW_FORUM' => '%sGo to the new forum%s', + 'RETURN_NEW_TOPIC' => '%sGo to the new topic%s', + 'RETURN_PM' => '%sReturn to the private message%s', + 'RETURN_POST' => '%sReturn to the post%s', + 'RETURN_QUEUE' => '%sReturn to the queue%s', + 'RETURN_REPORTS' => '%sReturn to the reports%s', + 'RETURN_TOPIC_SIMPLE' => '%sReturn to the topic%s', + + 'SEARCH_POSTS_BY_USER' => 'Search posts by', + 'SELECT_ACTION' => 'Select desired action', + 'SELECT_FORUM_GLOBAL_ANNOUNCEMENT' => 'Please select the forum you wish this global announcement to be displayed.', + 'SELECT_FORUM_GLOBAL_ANNOUNCEMENTS' => 'One or more of the selected topics are global announcements. Please select the forum you wish these to be displayed.', + 'SELECT_MERGE' => 'Select for merge', + 'SELECT_TOPICS_FROM' => 'Select topics from', + 'SELECT_TOPIC' => 'Select topic', + 'SELECT_USER' => 'Select user', + 'SORT_ACTION' => 'Log action', + 'SORT_DATE' => 'Date', + 'SORT_IP' => 'IP address', + 'SORT_WARNINGS' => 'Warnings', + 'SPLIT_AFTER' => 'Split topic from selected post onwards', + 'SPLIT_FORUM' => 'Forum for new topic', + 'SPLIT_POSTS' => 'Split selected posts', + 'SPLIT_SUBJECT' => 'New topic title', + 'SPLIT_TOPIC_ALL' => 'Split topic from selected posts', + 'SPLIT_TOPIC_ALL_CONFIRM' => 'Are you sure you want to split this topic?', + 'SPLIT_TOPIC_BEYOND' => 'Split topic at selected post', + 'SPLIT_TOPIC_BEYOND_CONFIRM' => 'Are you sure you want to split this topic at the selected post?', + 'SPLIT_TOPIC_EXPLAIN' => 'Using the form below you can split a topic in two, either by selecting the posts individually or by splitting at a selected post.', + + 'THIS_PM_IP' => 'IP for this private message', + 'THIS_POST_IP' => 'IP for this post', + 'TOPICS_APPROVED_SUCCESS' => 'The selected topics have been approved.', + 'TOPICS_DELETED_SUCCESS' => 'The selected topics have been successfully removed from the database.', + 'TOPICS_DISAPPROVED_SUCCESS'=> 'The selected topics have been disapproved.', + 'TOPICS_FORKED_SUCCESS' => 'The selected topics have been copied successfully.', + 'TOPICS_LOCKED_SUCCESS' => 'The selected topics have been locked.', + 'TOPICS_MOVED_SUCCESS' => 'The selected topics have been moved successfully.', + 'TOPICS_RESTORED_SUCCESS' => 'The selected topics have been restored successfully.', + 'TOPICS_RESYNC_SUCCESS' => 'The selected topics have been resynchronised.', + 'TOPICS_TYPE_CHANGED' => 'Topic types changed successfully.', + 'TOPICS_UNLOCKED_SUCCESS' => 'The selected topics have been unlocked.', + 'TOPIC_APPROVED_SUCCESS' => 'The selected topic has been approved.', + 'TOPIC_DELETED_SUCCESS' => 'The selected topic has been successfully removed from the database.', + 'TOPIC_DISAPPROVED_SUCCESS' => 'The selected topic has been disapproved.', + 'TOPIC_FORKED_SUCCESS' => 'The selected topic has been copied successfully.', + 'TOPIC_LOCKED_SUCCESS' => 'The selected topic has been locked.', + 'TOPIC_MOVED_SUCCESS' => 'The selected topic has been moved successfully.', + 'TOPIC_NOT_EXIST' => 'The topic you selected does not exist.', + 'TOPIC_RESTORED_SUCCESS' => 'The selected topic has been restored successfully.', + 'TOPIC_RESYNC_SUCCESS' => 'The selected topic has been resynchronised.', + 'TOPIC_SPLIT_SUCCESS' => 'The selected topic has been split successfully.', + 'TOPIC_TIME' => 'Topic time', + 'TOPIC_TYPE_CHANGED' => 'Topic type changed successfully.', + 'TOPIC_UNLOCKED_SUCCESS' => 'The selected topic has been unlocked.', + 'TOTAL_WARNINGS' => 'Total Warnings', + + 'UNAPPROVED_POSTS_TOTAL' => array( + 0 => 'There are no posts waiting for approval.', + 1 => 'In total there is 1 post waiting for approval.', + 2 => 'In total there are %d posts waiting for approval.', + ), + 'UNLOCK' => 'Unlock', + 'UNLOCK_POST' => 'Unlock post', + 'UNLOCK_POST_EXPLAIN' => 'Allow editing', + 'UNLOCK_POST_POST' => 'Unlock post', + 'UNLOCK_POST_POST_CONFIRM' => 'Are you sure you want to allow editing this post?', + 'UNLOCK_POST_POSTS' => 'Unlock selected posts', + 'UNLOCK_POST_POSTS_CONFIRM' => 'Are you sure you want to allow editing the selected posts?', + 'UNLOCK_TOPIC' => 'Unlock topic', + 'UNLOCK_TOPIC_CONFIRM' => 'Are you sure you want to unlock this topic?', + 'UNLOCK_TOPICS' => 'Unlock selected topics', + 'UNLOCK_TOPICS_CONFIRM' => 'Are you sure you want to unlock all selected topics?', + 'USER_CANNOT_POST' => 'You cannot post in this forum.', + 'USER_CANNOT_REPORT' => 'You cannot report posts in this forum.', + 'USER_FEEDBACK_ADDED' => 'User feedback added successfully.', + 'USER_WARNING_ADDED' => 'User warned successfully.', + + 'VIEW_DETAILS' => 'View details', + 'VIEW_PM' => 'View private message', + 'VIEW_POST' => 'View post', + + 'WARNED_USERS' => 'Warned users', + 'WARNED_USERS_EXPLAIN' => 'This is a list of users with unexpired warnings issued to them.', + 'WARNING_PM_BODY' => 'The following is a warning which has been issued to you by an administrator or moderator of this site.[quote]%s[/quote]', + 'WARNING_PM_SUBJECT' => 'Board warning issued', + 'WARNING_POST_DEFAULT' => 'This is a warning regarding the following post made by you: %s .', + 'NO_WARNINGS' => 'No warnings exist.', + + 'YOU_SELECTED_TOPIC' => 'You selected topic number %d: %s.', + + 'report_reasons' => array( + 'TITLE' => array( + 'WAREZ' => 'Warez', + 'SPAM' => 'Spam', + 'OFF_TOPIC' => 'Off-topic', + 'OTHER' => 'Other', + ), + 'DESCRIPTION' => array( + 'WAREZ' => 'The message contains links to illegal or pirated software.', + 'SPAM' => 'The reported message has the only purpose to advertise for a website or another product.', + 'OFF_TOPIC' => 'The reported message is off topic.', + 'OTHER' => 'The reported message does not fit into any other category, please use the further information field.', + ), + ), +)); diff --git a/language/en/memberlist.php b/language/en/memberlist.php new file mode 100644 index 0000000..c7b2bf5 --- /dev/null +++ b/language/en/memberlist.php @@ -0,0 +1,154 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ABOUT_USER' => 'Profile', + 'ACTIVE_IN_FORUM' => 'Most active forum', + 'ACTIVE_IN_TOPIC' => 'Most active topic', + 'ADD_FOE' => 'Add foe', + 'ADD_FRIEND' => 'Add friend', + 'AFTER' => 'After', + + 'ALL' => 'All', + + 'BEFORE' => 'Before', + + 'CC_SENDER' => 'Send a copy of this email to yourself.', + 'CONTACT_ADMIN' => 'Contact a Board Administrator', + + 'DEST_LANG' => 'Language', + 'DEST_LANG_EXPLAIN' => 'Select an appropriate language (if available) for the recipient of this message.', + + 'EDIT_PROFILE' => 'Edit profile', + + 'EMAIL_BODY_EXPLAIN' => 'This message will be sent as plain text, do not include any HTML or BBCode. The return address for this message will be set to your email address.', + 'EMAIL_DISABLED' => 'Sorry but all email related functions have been disabled.', + 'EMAIL_SENT' => 'The email has been sent.', + 'EMAIL_TOPIC_EXPLAIN' => 'This message will be sent as plain text, do not include any HTML or BBCode. Please note that the topic information is already included in the message. The return address for this message will be set to your email address.', + 'EMPTY_ADDRESS_EMAIL' => 'You must provide a valid email address for the recipient.', + 'EMPTY_MESSAGE_EMAIL' => 'You must enter a message to be emailed.', + 'EMPTY_MESSAGE_IM' => 'You must enter a message to be send.', + 'EMPTY_NAME_EMAIL' => 'You must enter the real name of the recipient.', + 'EMPTY_SENDER_EMAIL' => 'You must provide a valid email address.', + 'EMPTY_SENDER_NAME' => 'You must provide a name.', + 'EMPTY_SUBJECT_EMAIL' => 'You must specify a subject for the email.', + 'EQUAL_TO' => 'Equal to', + + 'FIND_USERNAME_EXPLAIN' => 'Use this form to search for specific members. You do not need to fill out all fields. To match partial data use * as a wildcard. When entering dates use the format YYYY-MM-DD, e.g. 2004-02-29. Use the mark checkboxes to select one or more usernames (several usernames may be accepted depending on the form itself) and click the Select Marked button to return to the previous form.', + 'FLOOD_EMAIL_LIMIT' => 'You cannot send another email at this time. Please try again later.', + + 'GROUP_LEADER' => 'Group leader', + + 'HIDE_MEMBER_SEARCH' => 'Hide member search', + + 'IM_ADD_CONTACT' => 'Add Contact', + 'IM_DOWNLOAD_APP' => 'Download application', + 'IM_JABBER' => 'Please note that users may have selected to not receive unsolicited instant messages.', + 'IM_JABBER_SUBJECT' => 'This is an automated message please do not reply! Message from user %1$s at %2$s.', + 'IM_MESSAGE' => 'Your message', + 'IM_NAME' => 'Your Name', + 'IM_NO_DATA' => 'There is no suitable contact information for this user.', + 'IM_NO_JABBER' => 'Sorry, direct messaging of Jabber users is not supported on this board. You will need a Jabber client installed on your system to contact the recipient above.', + 'IM_RECIPIENT' => 'Recipient', + 'IM_SEND' => 'Send message', + 'IM_SEND_MESSAGE' => 'Send message', + 'IM_SENT_JABBER' => 'Your message to %1$s has been sent successfully.', + 'IM_USER' => 'Send an instant message', + + 'LAST_ACTIVE' => 'Last active', + 'LESS_THAN' => 'Less than', + 'LIST_USERS' => array( + 1 => '%d user', + 2 => '%d users', + ), + 'LOGIN_EXPLAIN_TEAM' => 'The board requires you to be registered and logged in to view the team listing.', + 'LOGIN_EXPLAIN_MEMBERLIST' => 'The board requires you to be registered and logged in to access the memberlist.', + 'LOGIN_EXPLAIN_SEARCHUSER' => 'The board requires you to be registered and logged in to search users.', + 'LOGIN_EXPLAIN_VIEWPROFILE' => 'The board requires you to be registered and logged in to view profiles.', + + 'MANAGE_GROUP' => 'Manage Group', + 'MORE_THAN' => 'More than', + + 'NO_CONTACT_FORM' => 'The board administrator contact form has been disabled.', + 'NO_CONTACT_PAGE' => 'The board administrator contact page has been disabled.', + 'NO_EMAIL' => 'You are not permitted to send email to this user.', + 'NO_VIEW_USERS' => 'You are not authorised to view the member list or profiles.', + + 'ORDER' => 'Order', + 'OTHER' => 'Other', + + 'POST_IP' => 'Posted from IP/domain', + + 'REAL_NAME' => 'Recipient name', + 'RECIPIENT' => 'Recipient', + 'REMOVE_FOE' => 'Remove foe', + 'REMOVE_FRIEND' => 'Remove friend', + + 'SELECT_MARKED' => 'Select marked', + 'SELECT_SORT_METHOD' => 'Select sort method', + 'SENDER_EMAIL_ADDRESS' => 'Your email address', + 'SENDER_NAME' => 'Your name', + 'SEND_ICQ_MESSAGE' => 'Send ICQ message', + 'SEND_IM' => 'Instant messaging', + 'SEND_JABBER_MESSAGE' => 'Send Jabber message', + 'SEND_MESSAGE' => 'Message', + 'SEND_YIM_MESSAGE' => 'Send YIM message', + 'SORT_EMAIL' => 'Email', + 'SORT_LAST_ACTIVE' => 'Last active', + 'SORT_POST_COUNT' => 'Post count', + + 'USERNAME_BEGINS_WITH' => 'Username begins with', + 'USER_ADMIN' => 'Administer user', + 'USER_BAN' => 'Banning', + 'USER_FORUM' => 'User statistics', + 'USER_LAST_REMINDED' => array( + 0 => 'No reminder sent at this time', + 1 => '%1$d reminder sent
» %2$s', + 2 => '%1$d reminder sent
» %2$s', + ), + 'USER_ONLINE' => 'Online', + 'USER_PRESENCE' => 'Board presence', + 'USERS_PER_PAGE' => 'Users per page', + + 'VIEWING_PROFILE' => 'Viewing profile - %s', + 'VIEW_FACEBOOK_PROFILE' => 'View Facebook Profile', + 'VIEW_SKYPE_PROFILE' => 'View Skype Profile', + 'VIEW_TWITTER_PROFILE' => 'View Twitter Profile', + 'VIEW_YOUTUBE_CHANNEL' => 'View YouTube Channel', + 'VIEW_GOOGLEPLUS_PROFILE' => 'View Google+ Profile', +)); diff --git a/language/en/migrator.php b/language/en/migrator.php new file mode 100644 index 0000000..8a82d40 --- /dev/null +++ b/language/en/migrator.php @@ -0,0 +1,81 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'CONFIG_NOT_EXIST' => 'The config setting "%s" unexpectedly does not exist.', + + 'GROUP_NOT_EXIST' => 'The group "%s" unexpectedly does not exist.', + + 'MIGRATION_APPLY_DEPENDENCIES' => 'Apply dependencies of %s.', + 'MIGRATION_DATA_DONE' => 'Installed Data: %1$s; Time: %2$.2f seconds', + 'MIGRATION_DATA_IN_PROGRESS' => 'Installing Data: %1$s; Time: %2$.2f seconds', + 'MIGRATION_DATA_RUNNING' => 'Installing Data: %s.', + 'MIGRATION_EFFECTIVELY_INSTALLED' => 'Migration already effectively installed (skipped): %s', + 'MIGRATION_EXCEPTION_ERROR' => 'Something went wrong during the request and an exception was thrown. The changes made before the error occurred were reversed to the best of our abilities, but you should check the board for errors.', + 'MIGRATION_NOT_FULFILLABLE' => 'The migration "%1$s" is not fulfillable, missing migration "%2$s".', + 'MIGRATION_NOT_INSTALLED' => 'The migration "%s" is not installed.', + 'MIGRATION_NOT_VALID' => '%s is not a valid migration.', + 'MIGRATION_SCHEMA_DONE' => 'Installed Schema: %1$s; Time: %2$.2f seconds', + 'MIGRATION_SCHEMA_IN_PROGRESS' => 'Installing Schema: %1$s; Time: %2$.2f seconds', + 'MIGRATION_SCHEMA_RUNNING' => 'Installing Schema: %s.', + + 'MIGRATION_REVERT_DATA_DONE' => 'Reverted Data: %1$s; Time: %2$.2f seconds', + 'MIGRATION_REVERT_DATA_IN_PROGRESS' => 'Reverting Data: %1$s; Time: %2$.2f seconds', + 'MIGRATION_REVERT_DATA_RUNNING' => 'Reverting Data: %s.', + 'MIGRATION_REVERT_SCHEMA_DONE' => 'Reverted Schema: %1$s; Time: %2$.2f seconds', + 'MIGRATION_REVERT_SCHEMA_IN_PROGRESS' => 'Reverting Schema: %1$s; Time: %2$.2f seconds', + 'MIGRATION_REVERT_SCHEMA_RUNNING' => 'Reverting Schema: %s.', + + 'MIGRATION_INVALID_DATA_MISSING_CONDITION' => 'A migration is invalid. An if statement helper is missing a condition.', + 'MIGRATION_INVALID_DATA_MISSING_STEP' => 'A migration is invalid. An if statement helper is missing a valid call to a migration step.', + 'MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE' => 'A migration is invalid. A custom callable function could not be called.', + 'MIGRATION_INVALID_DATA_UNKNOWN_TYPE' => 'A migration is invalid. An unknown migration tool type was encountered.', + 'MIGRATION_INVALID_DATA_UNDEFINED_TOOL' => 'A migration is invalid. An undefined migration tool was encountered.', + 'MIGRATION_INVALID_DATA_UNDEFINED_METHOD' => 'A migration is invalid. An undefined migration tool method was encountered.', + + 'MODULE_ERROR' => 'An error occurred while creating a module: %s', + 'MODULE_EXISTS' => 'A module already exists: %s', + 'MODULE_EXIST_MULTIPLE' => 'Several modules with the given parent module langname already exist: %s. Try using before/after keys to clarify the module placement.', + 'MODULE_INFO_FILE_NOT_EXIST' => 'A required module info file is missing: %2$s', + 'MODULE_NOT_EXIST' => 'A required module does not exist: %s', + + 'PARENT_MODULE_FIND_ERROR' => 'Unable to determine the parent module identifier: %s', + 'PERMISSION_NOT_EXIST' => 'The permission setting "%s" unexpectedly does not exist.', + + 'ROLE_NOT_EXIST' => 'The permission role "%s" unexpectedly does not exist.', +)); diff --git a/language/en/plupload.php b/language/en/plupload.php new file mode 100644 index 0000000..15c3955 --- /dev/null +++ b/language/en/plupload.php @@ -0,0 +1,79 @@ + +* @copyright (c) 2010-2013 Moxiecode Systems AB +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'PLUPLOAD_ADD_FILES' => 'Add files', + 'PLUPLOAD_ADD_FILES_TO_QUEUE' => 'Add files to the upload queue and click the start button.', + 'PLUPLOAD_ALREADY_QUEUED' => '%s already present in the queue.', + 'PLUPLOAD_CLOSE' => 'Close', + 'PLUPLOAD_DRAG' => 'Drag files here.', + 'PLUPLOAD_DUPLICATE_ERROR' => 'Duplicate file error.', + 'PLUPLOAD_DRAG_TEXTAREA' => 'You may also attach files by dragging and dropping them in the message box.', + 'PLUPLOAD_ERR_INPUT' => 'Failed to open input stream.', + 'PLUPLOAD_ERR_MOVE_UPLOADED' => 'Failed to move uploaded file.', + 'PLUPLOAD_ERR_OUTPUT' => 'Failed to open output stream.', + 'PLUPLOAD_ERR_FILE_TOO_LARGE' => 'File too large:', + 'PLUPLOAD_ERR_FILE_COUNT' => 'File count error.', + 'PLUPLOAD_ERR_FILE_INVALID_EXT' => 'Invalid file extension:', + 'PLUPLOAD_ERR_RUNTIME_MEMORY' => 'Runtime ran out of available memory.', + 'PLUPLOAD_ERR_UPLOAD_URL' => 'Upload URL might be wrong or does not exist.', + 'PLUPLOAD_EXTENSION_ERROR' => 'File extension error.', + 'PLUPLOAD_FILE' => 'File: %s', + 'PLUPLOAD_FILE_DETAILS' => 'File: %s, size: %d, max file size: %d', + 'PLUPLOAD_FILENAME' => 'Filename', + 'PLUPLOAD_FILES_QUEUED' => '%d files queued', + 'PLUPLOAD_GENERIC_ERROR' => 'Generic error.', + 'PLUPLOAD_HTTP_ERROR' => 'HTTP error.', + 'PLUPLOAD_IMAGE_FORMAT' => 'Image format either wrong or not supported.', + 'PLUPLOAD_INIT_ERROR' => 'Init error.', + 'PLUPLOAD_IO_ERROR' => 'IO error.', + 'PLUPLOAD_NOT_APPLICABLE' => 'N/A', + 'PLUPLOAD_SECURITY_ERROR' => 'Security error.', + 'PLUPLOAD_SELECT_FILES' => 'Select files', + 'PLUPLOAD_SIZE' => 'Size', + 'PLUPLOAD_SIZE_ERROR' => 'File size error.', + 'PLUPLOAD_STATUS' => 'Status', + 'PLUPLOAD_START_UPLOAD' => 'Start upload', + 'PLUPLOAD_START_CURRENT_UPLOAD' => 'Start uploading queue', + 'PLUPLOAD_STOP_UPLOAD' => 'Stop upload', + 'PLUPLOAD_STOP_CURRENT_UPLOAD' => 'Stop current upload', + // Note: This string is formatted independently by plupload and so does not + // use the same formatting rules as normal phpBB translation strings + 'PLUPLOAD_UPLOADED' => 'Uploaded %d/%d files', +)); diff --git a/language/en/posting.php b/language/en/posting.php new file mode 100644 index 0000000..11ea648 --- /dev/null +++ b/language/en/posting.php @@ -0,0 +1,287 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ADD_ATTACHMENT' => 'Upload attachment', + 'ADD_ATTACHMENT_EXPLAIN' => 'If you wish to attach one or more files enter the details below.', + 'ADD_FILE' => 'Add the file', + 'ADD_POLL' => 'Poll creation', + 'ADD_POLL_EXPLAIN' => 'If you do not want to add a poll to your topic leave the fields blank.', + 'ALREADY_DELETED' => 'Sorry but this message is already deleted.', + 'ATTACH_DISK_FULL' => 'There is not enough free disk space to post this attachment.', + 'ATTACH_QUOTA_REACHED' => 'Sorry, the board attachment quota has been reached.', + 'ATTACH_SIG' => 'Attach a signature (signatures can be altered via the UCP)', + + 'BBCODE_A_HELP' => 'Inline uploaded attachment: [attachment=]filename.ext[/attachment]', + 'BBCODE_B_HELP' => 'Bold text: [b]text[/b]', + 'BBCODE_C_HELP' => 'Code display: [code]code[/code]', + 'BBCODE_D_HELP' => 'Flash: [flash=width,height]http://url[/flash]', + 'BBCODE_F_HELP' => 'Font size: [size=85]small text[/size]', + 'BBCODE_IS_OFF' => '%sBBCode%s is OFF', + 'BBCODE_IS_ON' => '%sBBCode%s is ON', + 'BBCODE_I_HELP' => 'Italic text: [i]text[/i]', + 'BBCODE_L_HELP' => 'List: [list][*]text[/list]', + 'BBCODE_LISTITEM_HELP' => 'List item: [*]text', + 'BBCODE_O_HELP' => 'Ordered list: e.g. [list=1][*]First point[/list] or [list=a][*]Point a[/list]', + 'BBCODE_P_HELP' => 'Insert image: [img]http://image_url[/img]', + 'BBCODE_Q_HELP' => 'Quote text: [quote]text[/quote]', + 'BBCODE_S_HELP' => 'Font colour: [color=red]text[/color] or [color=#FF0000]text[/color]', + 'BBCODE_U_HELP' => 'Underline text: [u]text[/u]', + 'BBCODE_W_HELP' => 'Insert URL: [url]http://url[/url] or [url=http://url]URL text[/url]', + 'BBCODE_Y_HELP' => 'List: Add list element', + 'BUMP_ERROR' => 'You cannot bump this topic so soon after the last post.', + + 'CANNOT_DELETE_REPLIED' => 'Sorry but you may only delete posts which have not been replied to.', + 'CANNOT_EDIT_POST_LOCKED' => 'This post has been locked. You can no longer edit that post.', + 'CANNOT_EDIT_TIME' => 'You can no longer edit or delete that post.', + 'CANNOT_POST_ANNOUNCE' => 'Sorry but you cannot post announcements.', + 'CANNOT_POST_STICKY' => 'Sorry but you cannot post sticky topics.', + 'CHANGE_TOPIC_TO' => 'Change topic type to', + 'CHARS_POST_CONTAINS' => array( + 1 => 'Your message contains %1$d character.', + 2 => 'Your message contains %1$d characters.', + ), + 'CHARS_SIG_CONTAINS' => array( + 1 => 'Your signature contains %1$d character.', + 2 => 'Your signature contains %1$d characters.', + ), + 'CLOSE_TAGS' => 'Close tags', + 'CURRENT_TOPIC' => 'Current topic', + + 'DELETE_FILE' => 'Delete file', + 'DELETE_MESSAGE' => 'Delete message', + 'DELETE_MESSAGE_CONFIRM' => 'Are you sure you want to delete this message?', + 'DELETE_OWN_POSTS' => 'Sorry but you can only delete your own posts.', + 'DELETE_PERMANENTLY' => 'Delete permanently', + 'DELETE_POST_CONFIRM' => 'Are you sure you want to delete this post?', + 'DELETE_POST_PERMANENTLY_CONFIRM' => 'Are you sure you want to permanently delete this post?', + 'DELETE_POST_PERMANENTLY' => array( + 1 => 'Permanently delete this post so it can not be recovered', + 2 => 'Permanently delete %1$d posts so they can not be recovered', + ), + 'DELETE_POSTS_CONFIRM' => 'Are you sure you want to delete these posts?', + 'DELETE_POSTS_PERMANENTLY_CONFIRM' => 'Are you sure you want to permanently delete these posts?', + 'DELETE_REASON' => 'Reason for deletion', + 'DELETE_REASON_EXPLAIN' => 'The specified reason for deletion will be visible to moderators.', + 'DELETE_POST_WARN' => 'Delete this post', + 'DELETE_TOPIC_CONFIRM' => 'Are you sure you want to delete this topic?', + 'DELETE_TOPIC_PERMANENTLY' => array( + 1 => 'Permanently delete this topic so it can not be recovered', + 2 => 'Permanently delete %1$d topics so they can not be recovered', + ), + 'DELETE_TOPIC_PERMANENTLY_CONFIRM' => 'Are you sure you want to permanently delete this topic?', + 'DELETE_TOPICS_CONFIRM' => 'Are you sure you want to delete these topics?', + 'DELETE_TOPICS_PERMANENTLY_CONFIRM' => 'Are you sure you want to permanently delete these topics?', + 'DISABLE_BBCODE' => 'Disable BBCode', + 'DISABLE_MAGIC_URL' => 'Do not automatically parse URLs', + 'DISABLE_SMILIES' => 'Disable smilies', + 'DISALLOWED_CONTENT' => 'The upload was rejected because the uploaded file was identified as a possible attack vector.', + 'DISALLOWED_EXTENSION' => 'The extension %s is not allowed.', + 'DRAFT_LOADED' => 'Draft loaded into posting area, you may want to finish your post now.
Your draft will be deleted after submitting this post.', + 'DRAFT_LOADED_PM' => 'Draft loaded into message area, you may want to finish your private message now.
Your draft will be deleted after submitting this private message.', + 'DRAFT_SAVED' => 'Draft successfully saved.', + 'DRAFT_TITLE' => 'Draft title', + + 'EDIT_REASON' => 'Reason for editing this post', + 'EMPTY_FILEUPLOAD' => 'The uploaded file is empty.', + 'EMPTY_MESSAGE' => 'You must enter a message when posting.', + 'EMPTY_REMOTE_DATA' => 'File could not be uploaded, please try uploading the file manually.', + + 'FLASH_IS_OFF' => '[flash] is OFF', + 'FLASH_IS_ON' => '[flash] is ON', + 'FLOOD_ERROR' => 'You cannot make another post so soon after your last.', + 'FONT_COLOR' => 'Font colour', + 'FONT_COLOR_HIDE' => 'Hide font colour', + 'FONT_HUGE' => 'Huge', + 'FONT_LARGE' => 'Large', + 'FONT_NORMAL' => 'Normal', + 'FONT_SIZE' => 'Font size', + 'FONT_SMALL' => 'Small', + 'FONT_TINY' => 'Tiny', + + 'GENERAL_UPLOAD_ERROR' => 'Could not upload attachment to %s.', + + 'IMAGES_ARE_OFF' => '[img] is OFF', + 'IMAGES_ARE_ON' => '[img] is ON', + 'INVALID_FILENAME' => '%s is an invalid filename.', + + 'LOAD' => 'Load', + 'LOAD_DRAFT' => 'Load draft', + 'LOAD_DRAFT_EXPLAIN' => 'Here you are able to select the draft you want to continue writing. Your current post will be cancelled, all current post contents will be deleted. View, edit and delete drafts within your User Control Panel.', + 'LOGIN_EXPLAIN_BUMP' => 'You need to login in order to bump topics within this forum.', + 'LOGIN_EXPLAIN_DELETE' => 'You need to login in order to delete posts within this forum.', + 'LOGIN_EXPLAIN_SOFT_DELETE' => 'You need to login in order to soft-delete posts within this forum.', + 'LOGIN_EXPLAIN_POST' => 'You need to login in order to post within this forum.', + 'LOGIN_EXPLAIN_QUOTE' => 'You need to login in order to quote posts within this forum.', + 'LOGIN_EXPLAIN_REPLY' => 'You need to login in order to reply to topics within this forum.', + + 'MAX_FONT_SIZE_EXCEEDED' => 'You may only use fonts up to size %d.', + 'MAX_FLASH_HEIGHT_EXCEEDED' => array( + 1 => 'Your flash files may only be up to %d pixel high.', + 2 => 'Your flash files may only be up to %d pixels high.', + ), + 'MAX_FLASH_WIDTH_EXCEEDED' => array( + 1 => 'Your flash files may only be up to %d pixel wide.', + 2 => 'Your flash files may only be up to %d pixels wide.', + ), + 'MAX_IMG_HEIGHT_EXCEEDED' => array( + 1 => 'Your images may only be up to %1$d pixel high.', + 2 => 'Your images may only be up to %1$d pixels high.', + ), + 'MAX_IMG_WIDTH_EXCEEDED' => array( + 1 => 'Your images may only be up to %d pixel wide.', + 2 => 'Your images may only be up to %d pixels wide.', + ), + + 'MESSAGE_BODY_EXPLAIN' => array( + 0 => '', // zero means no limit, so we don't view a message here. + 1 => 'Enter your message here, it may contain no more than %d character.', + 2 => 'Enter your message here, it may contain no more than %d characters.', + ), + 'MESSAGE_DELETED' => 'This message has been deleted successfully.', + 'MORE_SMILIES' => 'View more smilies', + + 'NOTIFY_REPLY' => 'Notify me when a reply is posted', + 'NOT_UPLOADED' => 'File could not be uploaded.', + 'NO_DELETE_POLL_OPTIONS' => 'You cannot delete existing poll options.', + 'NO_PM_ICON' => 'No PM icon', + 'NO_POLL_TITLE' => 'You have to enter a poll title.', + 'NO_POST' => 'The requested post does not exist.', + 'NO_POST_MODE' => 'No post mode specified.', + 'NO_TEMP_DIR' => 'Temporary folder could not be found or is not writable.', + + 'PARTIAL_UPLOAD' => 'The uploaded file was only partially uploaded.', + 'PHP_UPLOAD_STOPPED' => 'A PHP extension has stopped the file upload.', + 'PHP_SIZE_NA' => 'The attachment’s file size is too large.
Could not determine the maximum size defined by PHP in php.ini.', + 'PHP_SIZE_OVERRUN' => 'The attachment’s file size is too large, the maximum upload size is %1$d %2$s.
Please note this is set in php.ini and cannot be overridden.', + 'PLACE_INLINE' => 'Place inline', + 'POLL_DELETE' => 'Delete poll', + 'POLL_FOR' => 'Run poll for', + 'POLL_FOR_EXPLAIN' => 'Enter 0 for a never ending poll.', + 'POLL_MAX_OPTIONS' => 'Options per user', + 'POLL_MAX_OPTIONS_EXPLAIN' => 'This is the number of options each user may select when voting.', + 'POLL_OPTIONS' => 'Poll options', + 'POLL_OPTIONS_EXPLAIN' => array( + 1 => 'Place each option on a new line. You may enter %d option.', + 2 => 'Place each option on a new line. You may enter up to %d options.', + ), + 'POLL_OPTIONS_EDIT_EXPLAIN' => array( + 1 => 'Place each option on a new line. You may enter %d option. If you remove or add options all previous votes will be reset.', + 2 => 'Place each option on a new line. You may enter up to %d options. If you remove or add options all previous votes will be reset.', + ), + 'POLL_QUESTION' => 'Poll question', + 'POLL_TITLE_TOO_LONG' => 'The poll title must contain fewer than 100 characters.', + 'POLL_TITLE_COMP_TOO_LONG' => 'The parsed size of your poll title is too large, consider removing BBCodes or smilies.', + 'POLL_VOTE_CHANGE' => 'Allow re-voting', + 'POLL_VOTE_CHANGE_EXPLAIN' => 'If enabled users are able to change their vote.', + 'POSTED_ATTACHMENTS' => 'Posted attachments', + 'POST_APPROVAL_NOTIFY' => 'You will be notified when your post has been approved.', + 'POST_CONFIRMATION' => 'Confirmation of post', + 'POST_CONFIRM_EXPLAIN' => 'To prevent automated posts the board requires you to enter a confirmation code. The code is displayed in the image you should see below. If you are visually impaired or cannot otherwise read this code please contact the %sBoard Administrator%s.', + 'POST_DELETED' => 'This post has been deleted successfully.', + 'POST_EDITED' => 'This post has been edited successfully.', + 'POST_EDITED_MOD' => 'This post has been edited successfully, but it will need to be approved by a moderator before it is publicly viewable.', + 'POST_GLOBAL' => 'Global', + 'POST_ICON' => 'Post icon', + 'POST_NORMAL' => 'Normal', + 'POST_REVIEW' => 'Post review', + 'POST_REVIEW_EDIT' => 'Post review', + 'POST_REVIEW_EDIT_EXPLAIN' => 'This post has been altered by another user while you were editing it. You may wish to review the current version of this post and adjust your edits.', + 'POST_REVIEW_EXPLAIN' => 'At least one new post has been made to this topic. You may wish to review your post in light of this.', + 'POST_STORED' => 'This message has been posted successfully.', + 'POST_STORED_MOD' => 'This message has been submitted successfully, but it will need to be approved by a moderator before it is publicly viewable.', + 'POST_TOPIC_AS' => 'Post topic as', + 'PROGRESS_BAR' => 'Progress bar', + + 'QUOTE_DEPTH_EXCEEDED' => array( + 1 => 'You may embed only %d quote within each other.', + 2 => 'You may embed only %d quotes within each other.', + ), + 'QUOTE_NO_NESTING' => 'You may not embed quotes within each other.', + + 'REMOTE_UPLOAD_TIMEOUT' => 'The specified file could not be uploaded because the request timed out.', + 'SAVE' => 'Save', + 'SAVE_DATE' => 'Saved at', + 'SAVE_DRAFT' => 'Save draft', + 'SAVE_DRAFT_CONFIRM' => 'Please note that saved drafts only include the subject and the message, any other element will be removed. Do you want to save your draft now?', + 'SMILIES' => 'Smilies', + 'SMILIES_ARE_OFF' => 'Smilies are OFF', + 'SMILIES_ARE_ON' => 'Smilies are ON', + 'STICKY_ANNOUNCE_TIME_LIMIT'=> 'Sticky/Announcement/Global time limit', + 'STICK_TOPIC_FOR' => 'Stick topic for', + 'STICK_TOPIC_FOR_EXPLAIN' => 'Enter 0 for a never ending Sticky/Announcement/Global. Please note that this number is relative to the date of the post.', + 'STYLES_TIP' => 'Tip: Styles can be applied quickly to selected text.', + + 'TOO_FEW_CHARS' => 'Your message contains too few characters.', + 'TOO_FEW_CHARS_LIMIT' => array( + 1 => 'You need to enter at least %1$d character.', + 2 => 'You need to enter at least %1$d characters.', + ), + 'TOO_FEW_POLL_OPTIONS' => 'You must enter at least two poll options.', + 'TOO_MANY_ATTACHMENTS' => 'Cannot add another attachment, %d is the maximum.', + 'TOO_MANY_CHARS' => 'Your message contains too many characters.', + 'TOO_MANY_CHARS_LIMIT' => array( + 2 => 'The maximum number of allowed characters is %1$d.', + ), + 'TOO_MANY_POLL_OPTIONS' => 'You have tried to enter too many poll options.', + 'TOO_MANY_SMILIES' => 'Your message contains too many smilies. The maximum number of smilies allowed is %d.', + 'TOO_MANY_URLS' => 'Your message contains too many URLs. The maximum number of URLs allowed is %d.', + 'TOO_MANY_USER_OPTIONS' => 'You cannot specify more options per user than existing poll options.', + 'TOPIC_BUMPED' => 'Topic has been bumped successfully.', + + 'UNAUTHORISED_BBCODE' => 'You cannot use certain BBCodes: %s.', + 'UNGLOBALISE_EXPLAIN' => 'To switch this topic back from being global to a normal topic, you need to select the forum you wish this topic to be displayed.', + 'UNSUPPORTED_CHARACTERS_MESSAGE' => 'Your message contains the following unsupported characters:
%s', + 'UNSUPPORTED_CHARACTERS_SUBJECT' => 'Your subject contains the following unsupported characters:
%s', + 'UPDATE_COMMENT' => 'Update comment', + 'URL_INVALID' => 'The URL you specified is invalid.', + 'URL_NOT_FOUND' => 'The file specified could not be found.', + 'URL_IS_OFF' => '[url] is OFF', + 'URL_IS_ON' => '[url] is ON', + 'USER_CANNOT_BUMP' => 'You cannot bump topics in this forum.', + 'USER_CANNOT_DELETE' => 'You cannot delete posts in this forum.', + 'USER_CANNOT_EDIT' => 'You cannot edit posts in this forum.', + 'USER_CANNOT_REPLY' => 'You cannot reply in this forum.', + 'USER_CANNOT_FORUM_POST' => 'You are not able to do posting operations on this forum due to the forum type not supporting it.', + + 'VIEW_MESSAGE' => '%sView your submitted message%s', + 'VIEW_PRIVATE_MESSAGE' => '%sView your submitted private message%s', + + 'WRONG_FILESIZE' => 'The file is too big, maximum allowed size is %1$d %2$s.', + 'WRONG_SIZE' => 'The image must be at least %1$s wide, %2$s high and at most %3$s wide and %4$s high. The submitted image is %5$s wide and %6$s high.', +)); diff --git a/language/en/search.php b/language/en/search.php new file mode 100644 index 0000000..13e5bf7 --- /dev/null +++ b/language/en/search.php @@ -0,0 +1,124 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ALL_AVAILABLE' => 'All available', + 'ALL_RESULTS' => 'All results', + + 'DISPLAY_RESULTS' => 'Display results as', + + 'FOUND_SEARCH_MATCHES' => array( + 1 => 'Search found %d match', + 2 => 'Search found %d matches', + ), + 'FOUND_MORE_SEARCH_MATCHES' => array( + 1 => 'Search found more than %d match', + 2 => 'Search found more than %d matches', + ), + + 'GLOBAL' => 'Global announcement', + + 'IGNORED_TERMS' => 'ignored', + 'IGNORED_TERMS_EXPLAIN' => 'The following words in your search query were ignored because they are too common words: %s.', + + 'JUMP_TO_POST' => 'Jump to post', + + 'LOGIN_EXPLAIN_EGOSEARCH' => 'The board requires you to be registered and logged in to view your own posts.', + 'LOGIN_EXPLAIN_UNREADSEARCH'=> 'The board requires you to be registered and logged in to view your unread posts.', + 'LOGIN_EXPLAIN_NEWPOSTS' => 'The board requires you to be registered and logged in to view new posts since your last visit.', + + 'MAX_NUM_SEARCH_KEYWORDS_REFINE' => array( + 1 => 'You specified too many words to search for. Please do not enter more than %1$d word.', + 2 => 'You specified too many words to search for. Please do not enter more than %1$d words.', + ), + + 'NO_KEYWORDS' => 'You must specify at least one word to search for. Each word must consist of at least %s and must not contain more than %s excluding wildcards.', + 'NO_RECENT_SEARCHES' => 'No searches have been carried out recently.', + 'NO_SEARCH' => 'Sorry but you are not permitted to use the search system.', + 'NO_SEARCH_RESULTS' => 'No suitable matches were found.', + 'NO_SEARCH_LOAD' => 'Sorry but you cannot use search at this time. The server has high load. Please try again later.', + 'NO_SEARCH_TIME' => array( + 1 => 'Sorry but you cannot use search at this time. Please try again in %d second.', + 2 => 'Sorry but you cannot use search at this time. Please try again in %d seconds.', + ), + 'NO_SEARCH_UNREADS' => 'Sorry but searching for unread posts has been disabled on this board.', + 'WORD_IN_NO_POST' => 'No posts were found because the word %s is not contained in any post.', + 'WORDS_IN_NO_POST' => 'No posts were found because the words %s are not contained in any post.', + + 'POST_CHARACTERS' => 'characters of posts', + 'PHRASE_SEARCH_DISABLED' => 'Searching by exact phrase is not supported on this board.', + + 'RECENT_SEARCHES' => 'Recent searches', + 'RESULT_DAYS' => 'Limit results to previous', + 'RESULT_SORT' => 'Sort results by', + 'RETURN_FIRST' => 'Return first', + 'GO_TO_SEARCH_ADV' => 'Go to advanced search', + + 'SEARCHED_FOR' => 'Search term used', + 'SEARCHED_TOPIC' => 'Searched topic', + 'SEARCHED_QUERY' => 'Searched query', + 'SEARCH_ALL_TERMS' => 'Search for all terms or use query as entered', + 'SEARCH_ANY_TERMS' => 'Search for any terms', + 'SEARCH_AUTHOR' => 'Search for author', + 'SEARCH_AUTHOR_EXPLAIN' => 'Use * as a wildcard for partial matches.', + 'SEARCH_FIRST_POST' => 'First post of topics only', + 'SEARCH_FORUMS' => 'Search in forums', + 'SEARCH_FORUMS_EXPLAIN' => 'Select the forum or forums you wish to search in. Subforums are searched automatically if you do not disable “search subforums“ below.', + 'SEARCH_IN_RESULTS' => 'Search these results', + 'SEARCH_KEYWORDS_EXPLAIN' => 'Place + in front of a word which must be found and - in front of a word which must not be found. Put a list of words separated by | into brackets if only one of the words must be found. Use * as a wildcard for partial matches.', + 'SEARCH_MSG_ONLY' => 'Message text only', + 'SEARCH_OPTIONS' => 'Search options', + 'SEARCH_QUERY' => 'Search query', + 'SEARCH_SUBFORUMS' => 'Search subforums', + 'SEARCH_TITLE_MSG' => 'Post subjects and message text', + 'SEARCH_TITLE_ONLY' => 'Topic titles only', + 'SEARCH_WITHIN' => 'Search within', + 'SORT_ASCENDING' => 'Ascending', + 'SORT_AUTHOR' => 'Author', + 'SORT_DESCENDING' => 'Descending', + 'SORT_FORUM' => 'Forum', + 'SORT_POST_SUBJECT' => 'Post subject', + 'SORT_TIME' => 'Post time', + 'SPHINX_SEARCH_FAILED' => 'Search failed: %s', + 'SPHINX_SEARCH_FAILED_LOG' => 'Sorry, search could not be performed. More information about this failure has been logged in the error log.', + + 'TOO_FEW_AUTHOR_CHARS' => array( + 1 => 'You must specify at least %d character of the authors name.', + 2 => 'You must specify at least %d characters of the authors name.', + ), +)); diff --git a/language/en/ucp.php b/language/en/ucp.php new file mode 100644 index 0000000..2622fb5 --- /dev/null +++ b/language/en/ucp.php @@ -0,0 +1,662 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Privacy policy and T&C +$lang = array_merge($lang, array( + 'TERMS_OF_USE_CONTENT' => 'By accessing “%1$s” (hereinafter “we”, “us”, “our”, “%1$s”, “%2$s”), you agree to be legally bound by the following terms. If you do not agree to be legally bound by all of the following terms then please do not access and/or use “%1$s”. We may change these at any time and we’ll do our utmost in informing you, though it would be prudent to review this regularly yourself as your continued usage of “%1$s” after changes mean you agree to be legally bound by these terms as they are updated and/or amended.
+
+ Our forums are powered by phpBB (hereinafter “they”, “them”, “their”, “phpBB software”, “www.phpbb.com”, “phpBB Limited”, “phpBB Teams”) which is a bulletin board solution released under the “GNU General Public License v2” (hereinafter “GPL”) and can be downloaded from www.phpbb.com. The phpBB software only facilitates internet based discussions; phpBB Limited is not responsible for what we allow and/or disallow as permissible content and/or conduct. For further information about phpBB, please see: https://www.phpbb.com/.
+
+ You agree not to post any abusive, obscene, vulgar, slanderous, hateful, threatening, sexually-orientated or any other material that may violate any laws be it of your country, the country where “%1$s” is hosted or International Law. Doing so may lead to you being immediately and permanently banned, with notification of your Internet Service Provider if deemed required by us. The IP address of all posts are recorded to aid in enforcing these conditions. You agree that “%1$s” have the right to remove, edit, move or close any topic at any time should we see fit. As a user you agree to any information you have entered to being stored in a database. While this information will not be disclosed to any third party without your consent, neither “%1$s” nor phpBB shall be held responsible for any hacking attempt that may lead to the data being compromised. + ', + + 'PRIVACY_POLICY' => 'This policy explains in detail how “%1$s” along with its affiliated companies (hereinafter “we”, “us”, “our”, “%1$s”, “%2$s”) and phpBB (hereinafter “they”, “them”, “their”, “phpBB software”, “www.phpbb.com”, “phpBB Limited”, “phpBB Teams”) use any information collected during any session of usage by you (hereinafter “your information”).
+
+ Your information is collected via two ways. Firstly, by browsing “%1$s” will cause the phpBB software to create a number of cookies, which are small text files that are downloaded on to your computer’s web browser temporary files. The first two cookies just contain a user identifier (hereinafter “user-id”) and an anonymous session identifier (hereinafter “session-id”), automatically assigned to you by the phpBB software. A third cookie will be created once you have browsed topics within “%1$s” and is used to store which topics have been read, thereby improving your user experience.
+
+ We may also create cookies external to the phpBB software whilst browsing “%1$s”, though these are outside the scope of this document which is intended to only cover the pages created by the phpBB software. The second way in which we collect your information is by what you submit to us. This can be, and is not limited to: posting as an anonymous user (hereinafter “anonymous posts”), registering on “%1$s” (hereinafter “your account”) and posts submitted by you after registration and whilst logged in (hereinafter “your posts”).
+
+ Your account will at a bare minimum contain a uniquely identifiable name (hereinafter “your user name”), a personal password used for logging into your account (hereinafter “your password”) and a personal, valid email address (hereinafter “your email”). Your information for your account at “%1$s” is protected by data-protection laws applicable in the country that hosts us. Any information beyond your user name, your password, and your email address required by “%1$s” during the registration process is either mandatory or optional, at the discretion of “%1$s”. In all cases, you have the option of what information in your account is publicly displayed. Furthermore, within your account, you have the option to opt-in or opt-out of automatically generated emails from the phpBB software.
+
+ Your password is ciphered (a one-way hash) so that it is secure. However, it is recommended that you do not reuse the same password across a number of different websites. Your password is the means of accessing your account at “%1$s”, so please guard it carefully and under no circumstance will anyone affiliated with “%1$s”, phpBB or another 3rd party, legitimately ask you for your password. Should you forget your password for your account, you can use the “I forgot my password” feature provided by the phpBB software. This process will ask you to submit your user name and your email, then the phpBB software will generate a new password to reclaim your account.
+ ', +)); + +// Common language entries +$lang = array_merge($lang, array( + 'ACCOUNT_ACTIVE' => 'Your account has now been activated. Thank you for registering.', + 'ACCOUNT_ACTIVE_ADMIN' => 'The account has now been activated.', + 'ACCOUNT_ACTIVE_PROFILE' => 'Your account has now been successfully reactivated.', + 'ACCOUNT_ADDED' => 'Thank you for registering, your account has been created. You may now login with your username and password.', + 'ACCOUNT_COPPA' => 'Your account has been created but has to be approved, please check your email for details.', + 'ACCOUNT_EMAIL_CHANGED' => 'Your account has been updated. However, this board requires account reactivation on email changes. An activation key has been sent to the new email address you provided. Please check your email for further information.', + 'ACCOUNT_EMAIL_CHANGED_ADMIN' => 'Your account has been updated. However, this board requires account reactivation by the administrators on email changes. An email has been sent to them and you will be informed when your account has been reactivated.', + 'ACCOUNT_INACTIVE' => 'Your account has been created. However, this board requires account activation. An activation key has been sent to the email address you provided. Please check your email for further information and also be sure to check your junk mail box. It may take a while to get the email depending on your email provider.', + 'ACCOUNT_INACTIVE_ADMIN' => 'Your account has been created. However, this board requires account activation by the administrator group. An email has been sent to them and you will be informed when your account has been activated.', + 'ACTIVATION_EMAIL_SENT' => 'The activation email has been sent to your email address.', + 'ACTIVATION_EMAIL_SENT_ADMIN' => 'The activation email has been sent to the administrators email addresses.', + 'ADD' => 'Add', + 'ADD_BCC' => 'Add [BCC]', + 'ADD_FOES' => 'Add new foes', + 'ADD_FOES_EXPLAIN' => 'You may enter several usernames each on a different line.', + 'ADD_FOLDER' => 'Add folder', + 'ADD_FRIENDS' => 'Add new friends', + 'ADD_FRIENDS_EXPLAIN' => 'You may enter several usernames each on a different line.', + 'ADD_NEW_RULE' => 'Add new rule', + 'ADD_RULE' => 'Add rule', + 'ADD_TO' => 'Add [To]', + 'ADD_USERS_UCP_EXPLAIN' => 'Here you can add new users to the group. You may select whether this group becomes the new default for the selected users. Please enter each username on a separate line.', + 'ADMIN_EMAIL' => 'Administrators can email me information', + 'AGREE' => 'I agree to these terms', + 'ALLOW_PM' => 'Allow users to send you private messages', + 'ALLOW_PM_EXPLAIN' => 'Note that administrators and moderators will always be able to send you messages.', + 'ALREADY_ACTIVATED' => 'You have already activated your account.', + 'ATTACHMENTS_EXPLAIN' => 'This is a list of attachments you have made in posts to this board.', + 'ATTACHMENTS_DELETED' => 'Attachments successfully deleted.', + 'ATTACHMENT_DELETED' => 'Attachment successfully deleted.', + 'AUTOLOGIN_SESSION_KEYS_DELETED'=> 'The selected "Remember Me" login keys were successfully deleted.', + 'AVATAR_CATEGORY' => 'Category', + 'AVATAR_DRIVER_GRAVATAR_TITLE' => 'Gravatar', + 'AVATAR_DRIVER_GRAVATAR_EXPLAIN'=> 'Gravatar is a service that allows you to maintain the same avatar across multiple websites. Visit Gravatar for more information.', + 'AVATAR_DRIVER_LOCAL_TITLE' => 'Gallery avatar', + 'AVATAR_DRIVER_LOCAL_EXPLAIN' => 'You can choose your avatar from a locally available set of avatars.', + 'AVATAR_DRIVER_REMOTE_TITLE' => 'Remote avatar', + 'AVATAR_DRIVER_REMOTE_EXPLAIN' => 'Link to avatar images from another website.', + 'AVATAR_DRIVER_UPLOAD_TITLE' => 'Upload avatar', + 'AVATAR_DRIVER_UPLOAD_EXPLAIN' => 'Upload your own custom avatar.', + 'AVATAR_EXPLAIN' => 'Maximum dimensions; width: %1$s, height: %2$s, file size: %3$.2f KiB.', + 'AVATAR_EXPLAIN_NO_FILESIZE' => 'Maximum dimensions; width: %1$s, height: %2$s.', + 'AVATAR_FEATURES_DISABLED' => 'The avatar functionality is currently disabled.', + 'AVATAR_GALLERY' => 'Local gallery', + 'AVATAR_GENERAL_UPLOAD_ERROR' => 'Could not upload avatar to %s.', + 'AVATAR_NOT_ALLOWED' => 'Your avatar cannot be displayed because avatars have been disallowed.', + 'AVATAR_PAGE' => 'Page', + 'AVATAR_SELECT' => 'Select your avatar', + 'AVATAR_TYPE' => 'Avatar type', + 'AVATAR_TYPE_NOT_ALLOWED' => 'Your current avatar cannot be displayed because its type has been disallowed.', + + 'BACK_TO_DRAFTS' => 'Back to saved drafts', + 'BACK_TO_LOGIN' => 'Back to login screen', + 'BIRTHDAY' => 'Birthday', + 'BIRTHDAY_EXPLAIN' => 'Setting a year will list your age when it is your birthday.', + 'BOARD_DATE_FORMAT' => 'My date format', + 'BOARD_DATE_FORMAT_EXPLAIN' => 'The syntax used is identical to the PHP date() function.', + 'BOARD_LANGUAGE' => 'My language', + 'BOARD_STYLE' => 'My board style', + 'BOARD_TIMEZONE' => 'My timezone', + 'BOOKMARKS' => 'Bookmarks', + 'BOOKMARKS_EXPLAIN' => 'You can bookmark topics for future reference. Select the checkbox for any bookmark you wish to delete, then press the Remove marked bookmarks button.', + 'BOOKMARKS_DISABLED' => 'Bookmarks are disabled on this board.', + 'BOOKMARKS_REMOVED' => 'Bookmarks removed successfully.', + + 'CANNOT_EDIT_MESSAGE_TIME' => 'You can no longer edit or delete that message.', + 'CANNOT_MOVE_TO_SAME_FOLDER'=> 'Messages cannot be moved to the folder you want to remove.', + 'CANNOT_MOVE_FROM_SPECIAL' => 'Messages cannot be moved from the outbox.', + 'CANNOT_RENAME_FOLDER' => 'This folder cannot be renamed.', + 'CANNOT_REMOVE_FOLDER' => 'This folder cannot be removed.', + 'CHANGE_DEFAULT_GROUP' => 'Change default group', + 'CHANGE_PASSWORD' => 'Change password', + 'CLICK_GOTO_FOLDER' => '%1$sGo to your “%3$s” folder%2$s', + 'CLICK_RETURN_FOLDER' => '%1$sReturn to your “%3$s” folder%2$s', + 'CONFIRMATION' => 'Confirmation of registration', + 'CONFIRM_CHANGES' => 'Confirm changes', + 'CONFIRM_EXPLAIN' => 'To prevent automated registrations the board requires you to enter a confirmation code. The code is displayed in the image you should see below. If you are visually impaired or cannot otherwise read this code please contact the %sBoard Administrator%s.', + 'VC_REFRESH' => 'Refresh confirmation code', + 'VC_REFRESH_EXPLAIN' => 'If you cannot read the code you can request a new one by clicking the button.', + + 'CONFIRM_PASSWORD' => 'Confirm password', + 'CONFIRM_PASSWORD_EXPLAIN' => 'You only need to confirm your password if you changed it above.', + 'COPPA_BIRTHDAY' => 'To continue with the registration procedure please tell us when you were born.', + 'COPPA_COMPLIANCE' => 'COPPA compliance', + 'COPPA_EXPLAIN' => 'Please note that clicking submit will create your account. However it cannot be activated until a parent or guardian approves your registration. You will be emailed a copy of the necessary form with details of where to send it.', + 'CREATE_FOLDER' => 'Add folder…', + 'CURRENT_IMAGE' => 'Current image', + 'CURRENT_PASSWORD' => 'Current password', + 'CURRENT_PASSWORD_EXPLAIN' => 'You must enter your current password if you wish to alter your email address or username.', + 'CURRENT_CHANGE_PASSWORD_EXPLAIN' => 'To change your password, your email address, or your username, you must enter your current password.', + 'CUR_PASSWORD_EMPTY' => 'You did not enter your current password.', + 'CUR_PASSWORD_ERROR' => 'The current password you entered is incorrect.', + 'CUSTOM_DATEFORMAT' => 'Custom…', + + 'DEFAULT_ACTION' => 'Default action', + 'DEFAULT_ACTION_EXPLAIN' => 'This action will be triggered if none of the above is applicable.', + 'DEFAULT_ADD_SIG' => 'Attach my signature by default', + 'DEFAULT_BBCODE' => 'Enable BBCode by default', + 'DEFAULT_NOTIFY' => 'Notify me upon replies by default', + 'DEFAULT_SMILIES' => 'Enable smilies by default', + 'DEFINED_RULES' => 'Defined rules', + 'DELETED_TOPIC' => 'Topic has been removed.', + 'DELETE_ATTACHMENT' => 'Delete attachment', + 'DELETE_ATTACHMENTS' => 'Delete attachments', + 'DELETE_ATTACHMENT_CONFIRM' => 'Are you sure you want to delete this attachment?', + 'DELETE_ATTACHMENTS_CONFIRM'=> 'Are you sure you want to delete these attachments?', + 'DELETE_AVATAR' => 'Delete image', + 'DELETE_COOKIES_CONFIRM' => 'Are you sure you want to delete all cookies set by this board?', + 'DELETE_MARKED_PM' => 'Delete marked messages', + 'DELETE_MARKED_PM_CONFIRM' => 'Are you sure you want to delete all marked messages?', + 'DELETE_OLDEST_MESSAGES' => 'Delete oldest messages', + 'DELETE_MESSAGE' => 'Delete message', + 'DELETE_MESSAGE_CONFIRM' => 'Are you sure you want to delete this private message?', + 'DELETE_MESSAGES_IN_FOLDER' => 'Delete all messages within removed folder', + 'DELETE_RULE' => 'Delete rule', + 'DELETE_RULE_CONFIRM' => 'Are you sure you want to delete this rule?', + 'DEMOTE_SELECTED' => 'Demote selected', + 'DISABLE_CENSORS' => 'Enable word censoring', + 'DISPLAY_GALLERY' => 'Display gallery', + 'DOMAIN_NO_MX_RECORD_EMAIL' => 'The entered email domain has no valid MX record.', + 'DOWNLOADS' => 'Downloads', + 'DRAFTS_DELETED' => 'All selected drafts were successfully deleted.', + 'DRAFTS_EXPLAIN' => 'Here you can view, edit and delete your saved drafts.', + 'DRAFT_UPDATED' => 'Draft successfully updated.', + + 'EDIT_DRAFT_EXPLAIN' => 'Here you are able to edit your draft. Drafts do not contain attachment and poll information.', + 'EMAIL_BANNED_EMAIL' => 'The email address you entered is not allowed to be used.', + 'EMAIL_REMIND' => 'This must be the email address associated with your account. If you have not changed this via your user control panel then it is the email address you registered your account with.', + 'EMAIL_TAKEN_EMAIL' => 'The entered email address is already in use.', + 'EMPTY_DRAFT' => 'You must enter a message to submit your changes.', + 'EMPTY_DRAFT_TITLE' => 'You must enter a draft title.', + 'EXPORT_AS_XML' => 'Export as XML', + 'EXPORT_AS_CSV' => 'Export as CSV', + 'EXPORT_AS_CSV_EXCEL' => 'Export as CSV (Excel)', + 'EXPORT_AS_TXT' => 'Export as TXT', + 'EXPORT_AS_MSG' => 'Export as MSG', + 'EXPORT_FOLDER' => 'Export this view', + + 'FIELD_REQUIRED' => 'The field “%s” must be completed.', + 'FIELD_TOO_SHORT' => array( + 1 => 'The field “%2$s” is too short, a minimum of %1$d character is required.', + 2 => 'The field “%2$s” is too short, a minimum of %1$d characters is required.', + ), + 'FIELD_TOO_LONG' => array( + 1 => 'The field “%2$s” is too long, a maximum of %1$d character is allowed.', + 2 => 'The field “%2$s” is too long, a maximum of %1$d characters is allowed.', + ), + 'FIELD_TOO_SMALL' => 'The value of “%2$s” is too small, a minimum value of %1$d is required.', + 'FIELD_TOO_LARGE' => 'The value of “%2$s” is too large, a maximum value of %1$d is allowed.', + 'FIELD_INVALID_CHARS_INVALID' => 'The field “%s” has invalid characters.', + 'FIELD_INVALID_CHARS_NUMBERS_ONLY' => 'The field “%s” has invalid characters, only numbers are allowed.', + 'FIELD_INVALID_CHARS_ALPHA_DOTS' => 'The field “%s” has invalid characters, only alphanumeric or . characters are allowed.', + 'FIELD_INVALID_CHARS_ALPHA_ONLY' => 'The field “%s” has invalid characters, only alphanumeric characters are allowed.', + 'FIELD_INVALID_CHARS_ALPHA_PUNCTUATION' => 'The field “%s” has invalid characters, only alphanumeric or _,-. characters are allowed and the first character must be alphabetic.', + 'FIELD_INVALID_CHARS_ALPHA_SPACERS' => 'The field “%s” has invalid characters, only alphanumeric, space or -+_[] characters are allowed.', + 'FIELD_INVALID_CHARS_ALPHA_UNDERSCORE' => 'The field “%s” has invalid characters, only alphanumeric or _ characters are allowed.', + 'FIELD_INVALID_CHARS_LETTER_NUM_DOTS' => 'The field “%s” has invalid characters, only letter, number or . characters are allowed.', + 'FIELD_INVALID_CHARS_LETTER_NUM_ONLY' => 'The field “%s” has invalid characters, only letter and number characters are allowed.', + 'FIELD_INVALID_CHARS_LETTER_NUM_PUNCTUATION' => 'The field “%s” has invalid characters, only letter, number or _,-. characters are allowed and the first character must be alphabetic.', + 'FIELD_INVALID_CHARS_LETTER_NUM_SPACERS' => 'The field “%s” has invalid characters, only letter, number, space or -+_[] characters are allowed.', + 'FIELD_INVALID_CHARS_LETTER_NUM_UNDERSCORE' => 'The field “%s” has invalid characters, only letter, number or _ characters are allowed.', + 'FIELD_INVALID_DATE' => 'The field “%s” has an invalid date.', + 'FIELD_INVALID_URL' => 'The field “%s” has an invalid url.', + 'FIELD_INVALID_VALUE' => 'The field “%s” has an invalid value.', + + 'FOE_MESSAGE' => 'Message from foe', + 'FOES_EXPLAIN' => 'Foes are users which will be ignored by default. Posts by these users will not be fully visible. Personal messages from foes are still permitted. Please note that you cannot ignore moderators or administrators.', + 'FOES_UPDATED' => 'Your foes list has been updated successfully.', + 'FOLDER_ADDED' => 'Folder successfully added.', + 'FOLDER_MESSAGE_STATUS' => array( + 1 => '%2$d out of %1$s stored', + 2 => '%2$d out of %1$s stored', + ), + 'FOLDER_NAME_EMPTY' => 'You must enter a name for this folder.', + 'FOLDER_NAME_EXIST' => 'Folder %s already exists.', + 'FOLDER_OPTIONS' => 'Folder options', + 'FOLDER_RENAMED' => 'Folder successfully renamed.', + 'FOLDER_REMOVED' => 'Folder successfully removed.', + 'FOLDER_STATUS_MSG' => array( + 1 => 'Folder is %3$d%% full (%2$d out of %1$s stored)', + 2 => 'Folder is %3$d%% full (%2$d out of %1$s stored)', + ), + 'FORWARD_PM' => 'Forward PM', + 'FORCE_PASSWORD_EXPLAIN' => 'Before you may continue browsing the board you are required to change your password.', + 'FRIEND_MESSAGE' => 'Message from friend', + 'FRIENDS' => 'Friends', + 'FRIENDS_EXPLAIN' => 'Friends enable you quick access to members you communicate with frequently. If the template has relevant support any posts made by a friend may be highlighted.', + 'FRIENDS_OFFLINE' => 'Offline', + 'FRIENDS_ONLINE' => 'Online', + 'FRIENDS_UPDATED' => 'Your friends list has been updated successfully.', + 'FULL_FOLDER_OPTION_CHANGED'=> 'The action to take when a folder is full has been changed successfully.', + 'FWD_ORIGINAL_MESSAGE' => '-------- Original Message --------', + 'FWD_SUBJECT' => 'Subject: %s', + 'FWD_DATE' => 'Date: %s', + 'FWD_FROM' => 'From: %s', + 'FWD_TO' => 'To: %s', + + 'GLOBAL_ANNOUNCEMENT' => 'Global announcement', + + 'GRAVATAR_AVATAR_EMAIL' => 'Gravatar email', + 'GRAVATAR_AVATAR_EMAIL_EXPLAIN' => 'Enter the email address you used for registering your account on Gravatar.', + 'GRAVATAR_AVATAR_SIZE' => 'Avatar dimensions', + 'GRAVATAR_AVATAR_SIZE_EXPLAIN' => 'Specify the width and height of the avatar, leave blank to attempt automatic verification.', + + 'HIDE_ONLINE' => 'Hide my online status', + 'HIDE_ONLINE_EXPLAIN' => 'Changing this setting won’t become effective until your next visit to the board.', + 'HOLD_NEW_MESSAGES' => 'Do not accept new messages (New messages will be held back until enough space is available)', + 'HOLD_NEW_MESSAGES_SHORT' => 'New messages will be held back', + + 'IF_FOLDER_FULL' => 'If folder is full', + 'IMPORTANT_NEWS' => 'Important announcements', + 'INVALID_USER_BIRTHDAY' => 'The entered birthday is not a valid date.', + 'INVALID_CHARS_USERNAME' => 'The username contains forbidden characters.', + 'INVALID_EMOJIS_USERNAME' => 'The username contains forbidden characters (Emoji).', + 'INVALID_CHARS_NEW_PASSWORD'=> 'The password does not contain the required characters.', + 'ITEMS_REQUIRED' => 'The items marked with * are required profile fields and need to be filled out.', + + 'JOIN_SELECTED' => 'Join selected', + + 'LANGUAGE' => 'Language', + 'LINK_REMOTE_AVATAR' => 'Link off-site', + 'LINK_REMOTE_AVATAR_EXPLAIN'=> 'Enter the URL of the location containing the avatar image you wish to link to.', + 'LINK_REMOTE_SIZE' => 'Avatar dimensions', + 'LINK_REMOTE_SIZE_EXPLAIN' => 'Specify the width and height of the avatar, leave blank to attempt automatic verification.', + 'LOGIN_EXPLAIN_UCP' => 'Please login in order to access the User Control Panel.', + 'LOGIN_LINK' => 'Link or register your account on an external service with your board account', + 'LOGIN_LINK_EXPLAIN' => 'You have attempted to login with an external service that is not yet connected to an account on this board. You must now either link this account to an existing account or create a new account.', + 'LOGIN_LINK_MISSING_DATA' => 'Data that is necessary to link your account with an external service is not available. Please restart the login process.', + 'LOGIN_LINK_NO_DATA_PROVIDED' => 'No data has been provided to this page to link an external account to a forum account. Please contact the board administrator if you continue to experience problems.', + 'LOGIN_KEY' => 'Login Key', + 'LOGIN_TIME' => 'Login Time', + 'LOGIN_REDIRECT' => 'You have been successfully logged in.', + 'LOGOUT_FAILED' => 'You were not logged out, as the request did not match your session. Please contact the board administrator if you continue to experience problems.', + 'LOGOUT_REDIRECT' => 'You have been successfully logged out.', + + 'MARK_IMPORTANT' => 'Mark/Unmark as important', + 'MARKED_MESSAGE' => 'Marked message', + 'MAX_FOLDER_REACHED' => 'Maximum number of allowed user defined folders reached.', + 'MESSAGE_BY_AUTHOR' => 'by', + 'MESSAGE_COLOURS' => 'Message colours', + 'MESSAGE_DELETED' => 'Message successfully deleted.', + 'MESSAGE_EDITED' => 'Message successfully edited.', + 'MESSAGE_HISTORY' => 'Message history', + 'MESSAGE_REMOVED_FROM_OUTBOX' => 'This message was deleted by its author.', + 'MESSAGE_REPORTED_MESSAGE' => 'Reported message', + 'MESSAGE_SENT_ON' => 'on', + 'MESSAGE_STORED' => 'This message has been sent successfully.', + 'MESSAGE_TO' => 'To', + 'MESSAGES_DELETED' => 'Messages successfully deleted', + 'MOVE_DELETED_MESSAGES_TO' => 'Move messages from removed folder to', + 'MOVE_DOWN' => 'Move down', + 'MOVE_MARKED_TO_FOLDER' => 'Move marked to %s', + 'MOVE_PM_ERROR' => array( + 1 => 'An error occurred while moving the messages to the new folder, only %2$d out of %1$s was moved.', + 2 => 'An error occurred while moving the messages to the new folder, only %2$d out of %1$s were moved.', + ), + 'MOVE_TO_FOLDER' => 'Move to folder', + 'MOVE_UP' => 'Move up', + + 'NEW_FOLDER_NAME' => 'New folder name', + 'NEW_PASSWORD' => 'New password', + 'NEW_PASSWORD_CONFIRM_EMPTY' => 'You did not enter a confirm password.', + 'NEW_PASSWORD_ERROR' => 'The passwords you entered do not match.', + + 'NOTIFICATIONS_MARK_ALL_READ' => 'Mark all notifications read', + 'NOTIFICATIONS_MARK_ALL_READ_CONFIRM' => 'Are you sure you want to mark all notifications read?', + 'NOTIFICATIONS_MARK_ALL_READ_SUCCESS' => 'All notifications have been marked read.', + 'NOTIFICATION_GROUP_MISCELLANEOUS' => 'Miscellaneous Notifications', + 'NOTIFICATION_GROUP_MODERATION' => 'Moderation Notifications', + 'NOTIFICATION_GROUP_ADMINISTRATION' => 'Administration Notifications', + 'NOTIFICATION_GROUP_POSTING' => 'Posting Notifications', + 'NOTIFICATION_METHOD_BOARD' => 'Notifications', + 'NOTIFICATION_METHOD_EMAIL' => 'Email', + 'NOTIFICATION_METHOD_JABBER' => 'Jabber', + 'NOTIFICATION_TYPE' => 'Notification type', + 'NOTIFICATION_TYPE_BOOKMARK' => 'Someone replies to a topic you have bookmarked', + 'NOTIFICATION_TYPE_GROUP_REQUEST' => 'Someone requests to join a group you lead', + 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE' => 'A post or topic needs approval', + 'NOTIFICATION_TYPE_MODERATION_QUEUE' => 'Your topics/posts are approved or disapproved by a moderator', + 'NOTIFICATION_TYPE_PM' => 'Someone sends you a private message', + 'NOTIFICATION_TYPE_POST' => 'Someone replies to a topic to which you are subscribed', + 'NOTIFICATION_TYPE_QUOTE' => 'Someone quotes you in a post', + 'NOTIFICATION_TYPE_REPORT' => 'Someone reports a post', + 'NOTIFICATION_TYPE_TOPIC' => 'Someone creates a topic in a forum to which you are subscribed', + 'NOTIFICATION_TYPE_ADMIN_ACTIVATE_USER' => 'User requiring activation', + + 'NOTIFY_METHOD' => 'Notification method', + 'NOTIFY_METHOD_BOTH' => 'Both', + 'NOTIFY_METHOD_EMAIL' => 'Email only', + 'NOTIFY_METHOD_EXPLAIN' => 'Method for sending messages sent via this board.', + 'NOTIFY_METHOD_IM' => 'Jabber only', + 'NOTIFY_ON_PM' => 'Notify me on new private messages', + 'NOT_ADDED_FRIENDS_ANONYMOUS' => 'You cannot add the anonymous user to your friends list.', + 'NOT_ADDED_FRIENDS_BOTS' => 'You cannot add bots to your friends list.', + 'NOT_ADDED_FRIENDS_FOES' => 'You cannot add users to your friends list who are on your foes list.', + 'NOT_ADDED_FRIENDS_SELF' => 'You cannot add yourself to the friends list.', + 'NOT_ADDED_FOES_MOD_ADMIN' => 'You cannot add administrators and moderators to your foes list.', + 'NOT_ADDED_FOES_ANONYMOUS' => 'You cannot add the anonymous user to your foes list.', + 'NOT_ADDED_FOES_BOTS' => 'You cannot add bots to your foes list.', + 'NOT_ADDED_FOES_FRIENDS' => 'You cannot add users to your foes list who are on your friends list.', + 'NOT_ADDED_FOES_SELF' => 'You cannot add yourself to the foes list.', + 'NOT_AGREE' => 'I do not agree to these terms', + 'NOT_ENOUGH_SPACE_FOLDER' => 'The destination folder “%s” seems to be full. The requested action has not been taken.', + 'NOT_MOVED_MESSAGES' => array( + 1 => 'You have %d private message currently on hold because of full folder.', + 2 => 'You have %d private messages currently on hold because of full folder.', + ), + 'NO_ACTION_MODE' => 'No message action specified.', + 'NO_AUTHOR' => 'No author defined for this message', + 'NO_AVATAR' => 'No avatar selected', + 'NO_AVATAR_CATEGORY' => 'None', + + 'NO_AUTH_DELETE_MESSAGE' => 'You are not authorised to delete private messages.', + 'NO_AUTH_EDIT_MESSAGE' => 'You are not authorised to edit private messages.', + 'NO_AUTH_FORWARD_MESSAGE' => 'You are not authorised to forward private messages.', + 'NO_AUTH_GROUP_MESSAGE' => 'You are not authorised to send private messages to groups.', + 'NO_AUTH_PROFILEINFO' => 'You are not authorised to change your profile information.', + 'NO_AUTH_READ_HOLD_MESSAGE' => 'You are not authorised to read private messages that are on hold.', + 'NO_AUTH_READ_MESSAGE' => 'You are not authorised to read private messages.', + 'NO_AUTH_PRINT_MESSAGE' => 'You are not authorised to print private messages.', + 'NO_AUTH_READ_REMOVED_MESSAGE' => 'You are not able to read this message because it was removed by the author.', + 'NO_AUTH_SEND_MESSAGE' => 'You are not authorised to send private messages.', + 'NO_AUTH_SIGNATURE' => 'You are not authorised to define a signature.', + + 'NO_BCC_RECIPIENT' => 'None', + 'NO_BOOKMARKS' => 'You have no bookmarks.', + 'NO_BOOKMARKS_SELECTED' => 'You have selected no bookmarks.', + 'NO_EDIT_READ_MESSAGE' => 'Private message cannot be edited because it has already been read.', + 'NO_EMAIL_USER' => 'The email/username information submitted could not be found.', + 'EMAIL_NOT_UNIQUE' => 'Email you specified is used by multiple users. You must specify username as well.', + 'NO_FOES' => 'No foes currently defined', + 'NO_FRIENDS' => 'No friends currently defined', + 'NO_FRIENDS_OFFLINE' => 'No friends offline', + 'NO_FRIENDS_ONLINE' => 'No friends online', + 'NO_GROUP_SELECTED' => 'No group specified.', + 'NO_IMPORTANT_NEWS' => 'No important announcements present.', + 'NO_MESSAGE' => 'Private message could not be found.', + 'NO_NEW_FOLDER_NAME' => 'You have to specify a new folder name.', + 'NO_NEWER_PM' => 'No newer messages.', + 'NO_OLDER_PM' => 'No older messages.', + 'NO_PASSWORD_SUPPLIED' => 'You cannot login without a password.', + 'NO_RECIPIENT' => 'No recipient defined.', + 'NO_RULES_DEFINED' => 'No rules defined.', + 'NO_SAVED_DRAFTS' => 'No drafts saved.', + 'NO_TO_RECIPIENT' => 'None', + 'NO_WATCHED_FORUMS' => 'You are not subscribed to any forums.', + 'NO_WATCHED_SELECTED' => 'You have not selected any subscribed topics or forums.', + 'NO_WATCHED_TOPICS' => 'You are not subscribed to any topics.', + + 'PASS_TYPE_ALPHA_EXPLAIN' => 'Password must be between %1$s and %2$s long, must contain letters in mixed case and must contain numbers.', + 'PASS_TYPE_ANY_EXPLAIN' => 'Must be between %1$s and %2$s.', + 'PASS_TYPE_CASE_EXPLAIN' => 'Password must be between %1$s and %2$s long and must contain letters in mixed case.', + 'PASS_TYPE_SYMBOL_EXPLAIN' => 'Password must be between %1$s and %2$s long, must contain letters in mixed case, must contain numbers and must contain symbols.', + 'PASSWORD' => 'Password', + 'PASSWORD_ACTIVATED' => 'Your new password has been activated.', + 'PASSWORD_UPDATED_IF_EXISTED' => 'If your account exists, a new password was sent to your registered email address. If you do not receive an email, it may be because you are banned, your account is not activated, or you are not allowed to change your password. Contact admin if any of those reasons apply. Also, check your spam filter.', + 'PERMISSIONS_RESTORED' => 'Successfully restored original permissions.', + 'PERMISSIONS_TRANSFERRED' => 'Successfully transferred permissions from %s, you are now able to browse the board with this user’s permissions.
Please note that admin permissions were not transferred. You are able to revert to your permission set at any time.', + 'PM_DISABLED' => 'Private messaging has been disabled on this board.', + 'PM_FROM' => 'From', + 'PM_FROM_REMOVED_AUTHOR' => 'This message was sent by a user no longer registered.', + 'PM_ICON' => 'PM icon', + 'PM_INBOX' => 'Inbox', + 'PM_MARK_ALL_READ' => 'Mark all messages read', + 'PM_MARK_ALL_READ_SUCCESS' => 'All private messages in this folder have been marked read', + 'PM_NO_USERS' => 'The requested users to be added do not exist.', + 'PM_OUTBOX' => 'Outbox', + 'PM_SENTBOX' => 'Sent messages', + 'PM_SUBJECT' => 'Message subject', + 'PM_TO' => 'Send to', + 'PM_TOOLS' => 'Message tools', + 'PM_USERS_REMOVED_NO_PERMISSION' => 'Some users couldn’t be added as they do not have permission to read private messages.', + 'PM_USERS_REMOVED_NO_PM' => 'Some users couldn’t be added as they have disabled private message receipt.', + 'POST_EDIT_PM' => 'Edit message', + 'POST_FORWARD_PM' => 'Forward message', + 'POST_NEW_PM' => 'Compose message', + 'POST_PM_LOCKED' => 'Private messaging is locked.', + 'POST_PM_POST' => 'Quote post', + 'POST_QUOTE_PM' => 'Quote message', + 'POST_REPLY_PM' => 'Reply to message', + 'PRINT_PM' => 'Print view', + 'PREFERENCES_UPDATED' => 'Your preferences have been updated.', + 'PROFILE_INFO_NOTICE' => 'Please note that this information may be viewable to other members. Be careful when including any personal details. Any fields marked with a * must be completed.', + 'PROFILE_UPDATED' => 'Your profile has been updated.', + 'PROFILE_AUTOLOGIN_KEYS' => 'The "Remember Me" login keys automatically log you in when you visit the board. If you logout, the remember me login key is deleted only on the computer you are using to logout. Here you can see remember login keys created on other computers you used to access this site.', + 'PROFILE_NO_AUTOLOGIN_KEYS' => 'There are no saved "Remember Me" login keys.', + + 'RECIPIENT' => 'Recipient', + 'RECIPIENTS' => 'Recipients', + 'REGISTRATION' => 'Registration', + 'RELEASE_MESSAGES' => '%sRelease all on-hold messages%s… they will be re-sorted into the appropriate folder if enough space is made available.', + 'REMOVE_ADDRESS' => 'Remove address', + 'REMOVE_SELECTED_BOOKMARKS' => 'Remove selected bookmarks', + 'REMOVE_SELECTED_BOOKMARKS_CONFIRM' => 'Are you sure you want to delete all selected bookmarks?', + 'REMOVE_BOOKMARK_MARKED' => 'Remove marked bookmarks', + 'REMOVE_FOLDER' => 'Remove folder', + 'REMOVE_FOLDER_CONFIRM' => 'Are you sure you want to remove this folder?', + 'RENAME' => 'Rename', + 'RENAME_FOLDER' => 'Rename folder', + 'REPLIED_MESSAGE' => 'Replied to message', + 'REPLY_TO_ALL' => 'Reply to sender and all recipients.', + 'REPORT_PM' => 'Report private message', + 'RESIGN_SELECTED' => 'Resign selected', + 'RETURN_FOLDER' => '%1$sReturn to previous folder%2$s', + 'RETURN_UCP' => '%sReturn to the User Control Panel%s', + 'RULE_ADDED' => 'Rule successfully added.', + 'RULE_ALREADY_DEFINED' => 'This rule was defined previously.', + 'RULE_DELETED' => 'Rule successfully removed.', + 'RULE_LIMIT_REACHED' => 'You cannot add more PM rules. You have reached the maximum number of rules.', + 'RULE_NOT_DEFINED' => 'Rule not correctly specified.', + 'RULE_REMOVED_MESSAGES' => array( + 1 => '%d private message was removed due to private message filters.', + 2 => '%d private messages were removed due to private message filters.', + ), + + 'SAME_PASSWORD_ERROR' => 'The new password you entered is the same as your current password.', + 'SEARCH_YOUR_POSTS' => 'Show your posts', + 'SEND_PASSWORD' => 'Send password', + 'SENT_AT' => 'Sent', // Used before dates in private messages + 'SHOW_EMAIL' => 'Users can contact me by email', + 'SIGNATURE_EXPLAIN' => 'This is a block of text that can be added to posts you make. There is a %d character limit.', + 'SIGNATURE_PREVIEW' => 'Your signature will appear like this in posts', + 'SIGNATURE_TOO_LONG' => 'Your signature is too long.', + 'SELECT_CURRENT_TIME' => 'Select current time', + 'SELECT_TIMEZONE' => 'Select timezone', + 'SORT' => 'Sort', + 'SORT_COMMENT' => 'File comment', + 'SORT_DOWNLOADS' => 'Downloads', + 'SORT_EXTENSION' => 'Extension', + 'SORT_FILENAME' => 'Filename', + 'SORT_POST_TIME' => 'Post time', + 'SORT_SIZE' => 'File size', + + 'TIMEZONE' => 'Timezone', + 'TIMEZONE_DATE_SUGGESTION' => 'Suggestion: %s', + 'TIMEZONE_INVALID' => 'The timezone you selected is invalid.', + 'TO' => 'Recipient', + 'TO_MASS' => 'Recipients', + 'TO_ADD' => 'Add recipient', + 'TO_ADD_MASS' => 'Add recipients', + 'TO_ADD_GROUPS' => 'Add groups', + 'TOO_MANY_RECIPIENTS' => 'You tried to send a private message to too many recipients.', + 'TOO_MANY_REGISTERS' => 'You have exceeded the maximum number of registration attempts for this session. Please try again later.', + + 'UCP' => 'User Control Panel', + 'UCP_ACTIVATE' => 'Activate account', + 'UCP_ADMIN_ACTIVATE' => 'Please note that you will need to enter a valid email address before your account is activated. The administrator will review your account and if approved you will receive an email at the address you specified.', + 'UCP_ATTACHMENTS' => 'Attachments', + 'UCP_AUTH_LINK' => 'External accounts', + 'UCP_AUTH_LINK_ASK' => 'You currently have no account associated with this external service. Click the button below to link your board account to an account with this external service.', + 'UCP_AUTH_LINK_ID' => 'Unique identifier', + 'UCP_AUTH_LINK_LINK' => 'Link', + 'UCP_AUTH_LINK_MANAGE' => 'Manage external account associations', + 'UCP_AUTH_LINK_NOT_SUPPORTED' => 'Linking board accounts to external services is not supported by this board’s current authentication method.', + 'UCP_AUTH_LINK_TITLE' => 'Manage your external account associations', + 'UCP_AUTH_LINK_UNLINK' => 'Unlink', + 'UCP_COPPA_BEFORE' => 'Before %s', + 'UCP_COPPA_ON_AFTER' => 'On or after %s', + 'UCP_EMAIL_ACTIVATE' => 'Please note that you will need to enter a valid email address before your account is activated. You will receive an email at the address you provide that contains an account activation link.', + 'UCP_JABBER' => 'Jabber address', + 'UCP_LOGIN_LINK' => 'Set up an external account association', + + 'UCP_MAIN' => 'Overview', + 'UCP_MAIN_ATTACHMENTS' => 'Manage attachments', + 'UCP_MAIN_BOOKMARKS' => 'Manage bookmarks', + 'UCP_MAIN_DRAFTS' => 'Manage drafts', + 'UCP_MAIN_FRONT' => 'Front page', + 'UCP_MAIN_SUBSCRIBED' => 'Manage subscriptions', + + 'UCP_NO_ATTACHMENTS' => 'You have posted no files.', + + 'UCP_NOTIFICATION_LIST' => 'Manage notifications', + 'UCP_NOTIFICATION_LIST_EXPLAIN' => 'Here you may view all past notifications.', + 'UCP_NOTIFICATION_OPTIONS' => 'Edit notification options', + 'UCP_NOTIFICATION_OPTIONS_EXPLAIN' => 'Here you can set your preferred notification methods for the board.', + + 'UCP_PREFS' => 'Board preferences', + 'UCP_PREFS_PERSONAL' => 'Edit global settings', + 'UCP_PREFS_POST' => 'Edit posting defaults', + 'UCP_PREFS_VIEW' => 'Edit display options', + + 'UCP_PM' => 'Private messages', + 'UCP_PM_COMPOSE' => 'Compose message', + 'UCP_PM_DRAFTS' => 'Manage PM drafts', + 'UCP_PM_OPTIONS' => 'Rules, folders & settings', + 'UCP_PM_UNREAD' => 'Unread messages', + 'UCP_PM_VIEW' => 'View messages', + + 'UCP_PROFILE' => 'Profile', + 'UCP_PROFILE_AVATAR' => 'Edit avatar', + 'UCP_PROFILE_PROFILE_INFO' => 'Edit profile', + 'UCP_PROFILE_REG_DETAILS' => 'Edit account settings', + 'UCP_PROFILE_SIGNATURE' => 'Edit signature', + 'UCP_PROFILE_AUTOLOGIN_KEYS'=> 'Manage “Remember Me” login keys', + + 'UCP_USERGROUPS' => 'Usergroups', + 'UCP_USERGROUPS_MEMBER' => 'Edit memberships', + 'UCP_USERGROUPS_MANAGE' => 'Manage groups', + + 'UCP_PASSWORD_RESET_DISABLED' => 'The password reset functionality has been disabled. If you need help accessing your account, please contact the %sBoard Administrator%s', + 'UCP_REGISTER_DISABLE' => 'Creating a new account is currently not possible.', + 'UCP_REMIND' => 'Send password', + 'UCP_RESEND' => 'Send activation email', + 'UCP_WELCOME' => 'Welcome to the User Control Panel. From here you can monitor, view and update your profile, preferences, subscribed forums and topics. You can also send messages to other users (if permitted). Please ensure you read any announcements before continuing.', + 'UCP_ZEBRA' => 'Friends & Foes', + 'UCP_ZEBRA_FOES' => 'Manage foes', + 'UCP_ZEBRA_FRIENDS' => 'Manage friends', + 'UNDISCLOSED_RECIPIENT' => 'Undisclosed Recipient', + 'UNKNOWN_FOLDER' => 'Unknown folder', + 'UNWATCH_MARKED' => 'Unwatch marked', + 'UPLOAD_AVATAR_FILE' => 'Upload from your machine', + 'UPLOAD_AVATAR_URL' => 'Upload from a URL', + 'UPLOAD_AVATAR_URL_EXPLAIN' => 'Enter the URL of the location containing the image. The image will be copied to this site.', + 'USERNAME_ALPHA_ONLY_EXPLAIN' => 'Username must be between %1$s and %2$s long and use only alphanumeric characters.', + 'USERNAME_ALPHA_SPACERS_EXPLAIN'=> 'Username must be between %1$s and %2$s long and use alphanumeric, space or -+_[] characters.', + 'USERNAME_ASCII_EXPLAIN' => 'Username must be between %1$s and %2$s long and use only ASCII characters, so no special symbols.', + 'USERNAME_LETTER_NUM_EXPLAIN' => 'Username must be between %1$s and %2$s long and use only letter or number characters.', + 'USERNAME_LETTER_NUM_SPACERS_EXPLAIN'=> 'Username must be between %1$s and %2$s long and use letter, number, space or -+_[] characters.', + 'USERNAME_CHARS_ANY_EXPLAIN' => 'Length must be between %1$s and %2$s.', + 'USERNAME_TAKEN_USERNAME' => 'The username you entered is already in use, please select an alternative.', + 'USERNAME_DISALLOWED_USERNAME' => 'The username you entered has been disallowed or contains a disallowed word. Please choose a different name.', + 'USER_NOT_FOUND_OR_INACTIVE' => 'The usernames you specified could either not be found or are not activated users.', + + 'VIEW_AVATARS' => 'Display avatars', + 'VIEW_EDIT' => 'View/Edit', + 'VIEW_FLASH' => 'Display Flash animations', + 'VIEW_IMAGES' => 'Display images within posts', + 'VIEW_NEXT_HISTORY' => 'Next PM in history', + 'VIEW_NEXT_PM' => 'Next PM', + 'VIEW_PM' => 'View message', + 'VIEW_PM_INFO' => 'Message details', + 'VIEW_PM_MESSAGES' => array( + 1 => '%d message', + 2 => '%d messages', + ), + 'VIEW_PREVIOUS_HISTORY' => 'Previous PM in history', + 'VIEW_PREVIOUS_PM' => 'Previous PM', + 'VIEW_PROFILE' => 'View profile', + 'VIEW_SIGS' => 'Display signatures', + 'VIEW_SMILIES' => 'Display smilies as images', + 'VIEW_TOPICS_DAYS' => 'Display topics from previous days', + 'VIEW_TOPICS_DIR' => 'Display topic order direction', + 'VIEW_TOPICS_KEY' => 'Display topics ordering by', + 'VIEW_POSTS_DAYS' => 'Display posts from previous days', + 'VIEW_POSTS_DIR' => 'Display post order direction', + 'VIEW_POSTS_KEY' => 'Display posts ordering by', + + 'WATCHED_EXPLAIN' => 'Below is a list of forums and topics you are subscribed to. You will be notified of new posts in either. To unsubscribe mark the forum or topic and then press the Unwatch marked button.', + 'WATCHED_FORUMS' => 'Watched forums', + 'WATCHED_TOPICS' => 'Watched topics', + 'WRONG_ACTIVATION' => 'The activation key you supplied does not match any in the database.', + + 'YOUR_DETAILS' => 'Your activity', + 'YOUR_FOES' => 'Your foes', + 'YOUR_FOES_EXPLAIN' => 'To remove usernames select them and click submit.', + 'YOUR_FRIENDS' => 'Your friends', + 'YOUR_FRIENDS_EXPLAIN' => 'To remove usernames select them and click submit.', + 'YOUR_WARNINGS' => 'Your warning level', + + 'PM_ACTION' => array( + 'PLACE_INTO_FOLDER' => 'Place into folder', + 'MARK_AS_READ' => 'Mark as read', + 'MARK_AS_IMPORTANT' => 'Mark message', + 'DELETE_MESSAGE' => 'Delete message', + ), + 'PM_CHECK' => array( + 'SUBJECT' => 'Subject', + 'SENDER' => 'Sender', + 'MESSAGE' => 'Message', + 'STATUS' => 'Message status', + 'TO' => 'Sent To', + ), + 'PM_RULE' => array( + 'IS_LIKE' => 'is like', + 'IS_NOT_LIKE' => 'is not like', + 'IS' => 'is', + 'IS_NOT' => 'is not', + 'BEGINS_WITH' => 'begins with', + 'ENDS_WITH' => 'ends with', + 'IS_FRIEND' => 'is friend', + 'IS_FOE' => 'is foe', + 'IS_USER' => 'is user', + 'IS_GROUP' => 'is in usergroup', + 'ANSWERED' => 'answered', + 'FORWARDED' => 'forwarded', + 'TO_GROUP' => 'to my default usergroup', + 'TO_ME' => 'to me', + ), + + 'GROUPS_EXPLAIN' => 'Usergroups enable board admins to better administer users. By default you will be placed in a specific group, this is your default group. This group defines how you may appear to other users, for example your username colouration, avatar, rank, etc. Depending on whether the administrator allows it you may be allowed to change your default group. You may also be placed in or allowed to join other groups. Some groups may give you additional permissions to view content or increase your capabilities in other areas.', + 'GROUP_LEADER' => 'Leaderships', + 'GROUP_MEMBER' => 'Memberships', + 'GROUP_PENDING' => 'Pending memberships', + 'GROUP_NONMEMBER' => 'Non-memberships', + 'GROUP_DETAILS' => 'Group details', + + 'NO_LEADER' => 'No group leaderships', + 'NO_MEMBER' => 'No group memberships', + 'NO_PENDING' => 'No pending memberships', + 'NO_NONMEMBER' => 'No non-member groups', +)); diff --git a/language/en/viewforum.php b/language/en/viewforum.php new file mode 100644 index 0000000..e2a6e2a --- /dev/null +++ b/language/en/viewforum.php @@ -0,0 +1,73 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'ACTIVE_TOPICS' => 'Active topics', + 'ANNOUNCEMENTS' => 'Announcements', + + 'FORUM_PERMISSIONS' => 'Forum permissions', + + 'ICON_ANNOUNCEMENT' => 'Announcement', + 'ICON_STICKY' => 'Sticky', + + 'LOGIN_NOTIFY_FORUM' => 'You have been notified about this forum, please login to view it.', + + 'MARK_TOPICS_READ' => 'Mark topics read', + + 'NEW_POSTS_HOT' => 'New posts [ Popular ]', // Not used anymore + 'NEW_POSTS_LOCKED' => 'New posts [ Locked ]', // Not used anymore + 'NO_NEW_POSTS_HOT' => 'No new posts [ Popular ]', // Not used anymore + 'NO_NEW_POSTS_LOCKED' => 'No new posts [ Locked ]', // Not used anymore + 'NO_READ_ACCESS' => 'You do not have the required permissions to view or read topics within this forum.', + 'NO_FORUMS_IN_CATEGORY' => 'This category has no forums.', + 'NO_UNREAD_POSTS_HOT' => 'No unread posts [ Popular ]', + 'NO_UNREAD_POSTS_LOCKED' => 'No unread posts [ Locked ]', + + 'POST_FORUM_LOCKED' => 'Forum is locked', + + 'TOPICS_MARKED' => 'The topics for this forum have now been marked read.', + + 'UNREAD_POSTS_HOT' => 'Unread posts [ Popular ]', + 'UNREAD_POSTS_LOCKED' => 'Unread posts [ Locked ]', + + 'VIEW_FORUM' => 'View forum', + 'VIEW_FORUM_TOPICS' => array( + 1 => '%d topic', + 2 => '%d topics', + ), +)); diff --git a/language/en/viewtopic.php b/language/en/viewtopic.php new file mode 100644 index 0000000..5d127ac --- /dev/null +++ b/language/en/viewtopic.php @@ -0,0 +1,127 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* DO NOT CHANGE +*/ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = array(); +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, array( + 'APPROVE' => 'Approve', + 'ATTACHMENT' => 'Attachment', + 'ATTACHMENT_FUNCTIONALITY_DISABLED' => 'The attachments feature has been disabled.', + + 'BOOKMARK_ADDED' => 'Bookmarked topic successfully.', + 'BOOKMARK_ERR' => 'Bookmarking the topic failed. Please try again.', + 'BOOKMARK_REMOVED' => 'Removed bookmarked topic successfully.', + 'BOOKMARK_TOPIC' => 'Bookmark topic', + 'BOOKMARK_TOPIC_REMOVE' => 'Remove from bookmarks', + 'BUMPED_BY' => 'Last bumped by %1$s on %2$s.', + 'BUMP_TOPIC' => 'Bump topic', + + 'CODE' => 'Code', + + 'DELETE_TOPIC' => 'Delete topic', + 'DELETED_INFORMATION' => 'Deleted by %1$s on %2$s', + 'DISAPPROVE' => 'Disapprove', + 'DOWNLOAD_NOTICE' => 'You do not have the required permissions to view the files attached to this post.', + + 'EDITED_TIMES_TOTAL' => array( + 1 => 'Last edited by %2$s on %3$s, edited %1$d time in total.', + 2 => 'Last edited by %2$s on %3$s, edited %1$d times in total.', + ), + 'EMAIL_TOPIC' => 'Email topic', + 'ERROR_NO_ATTACHMENT' => 'The selected attachment does not exist anymore.', + + 'FILE_NOT_FOUND_404' => 'The file %s does not exist.', + 'FORK_TOPIC' => 'Copy topic', + 'FULL_EDITOR' => 'Full Editor & Preview', + + 'LINKAGE_FORBIDDEN' => 'You are not authorised to view, download or link from/to this site.', + 'LOGIN_NOTIFY_TOPIC' => 'You have been notified about this topic, please login to view it.', + 'LOGIN_VIEWTOPIC' => 'The board requires you to be registered and logged in to view this topic.', + + 'MAKE_ANNOUNCE' => 'Change to “Announcement”', + 'MAKE_GLOBAL' => 'Change to “Global”', + 'MAKE_NORMAL' => 'Change to “Standard Topic”', + 'MAKE_STICKY' => 'Change to “Sticky”', + 'MAX_OPTIONS_SELECT' => array( + 1 => 'You may select %d option', + 2 => 'You may select up to %d options', + ), + 'MISSING_INLINE_ATTACHMENT' => 'The attachment %s is no longer available', + 'MOVE_TOPIC' => 'Move topic', + + 'NO_ATTACHMENT_SELECTED'=> 'You haven’t selected an attachment to download or view.', + 'NO_NEWER_TOPICS' => 'There are no newer topics in this forum.', + 'NO_OLDER_TOPICS' => 'There are no older topics in this forum.', + 'NO_UNREAD_POSTS' => 'There are no new unread posts for this topic.', + 'NO_VOTE_OPTION' => 'You must specify an option when voting.', + 'NO_VOTES' => 'No votes', + 'NO_AUTH_PRINT_TOPIC' => 'You are not authorised to print topics.', + + 'POLL_ENDED_AT' => 'Poll ended at %s', + 'POLL_RUN_TILL' => 'Poll runs till %s', + 'POLL_VOTED_OPTION' => 'You voted for this option', + 'POST_DELETED_RESTORE' => 'This post has been deleted. It can be restored.', + 'PRINT_TOPIC' => 'Print view', + + 'QUICK_MOD' => 'Quick-mod tools', + 'QUICKREPLY' => 'Quick Reply', + 'QUOTE' => 'Quote', + + 'REPLY_TO_TOPIC' => 'Reply to topic', + 'RESTORE' => 'Restore', + 'RESTORE_TOPIC' => 'Restore topic', + 'RETURN_POST' => '%sReturn to the post%s', + + 'SUBMIT_VOTE' => 'Submit vote', + + 'TOPIC_TOOLS' => 'Topic tools', + 'TOTAL_VOTES' => 'Total votes', + + 'UNLOCK_TOPIC' => 'Unlock topic', + + 'VIEW_INFO' => 'Post details', + 'VIEW_NEXT_TOPIC' => 'Next topic', + 'VIEW_PREVIOUS_TOPIC' => 'Previous topic', + 'VIEW_RESULTS' => 'View results', + 'VIEW_TOPIC_POSTS' => array( + 1 => '%d post', + 2 => '%d posts', + ), + 'VIEW_UNREAD_POST' => 'First unread post', + 'VOTE_SUBMITTED' => 'Your vote has been cast.', + 'VOTE_CONVERTED' => 'Changing votes is not supported for converted polls.', + +)); diff --git a/language/fr/LICENSE b/language/fr/LICENSE new file mode 100644 index 0000000..ce992b2 --- /dev/null +++ b/language/fr/LICENSE @@ -0,0 +1,281 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + diff --git a/language/fr/acp/attachments.php b/language/fr/acp/attachments.php new file mode 100644 index 0000000..e39c5d6 --- /dev/null +++ b/language/fr/acp/attachments.php @@ -0,0 +1,168 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_ATTACHMENT_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez configurer les paramètres relatifs aux pièces jointes et à leurs catégories spéciales.', + 'ACP_EXTENSION_GROUPS_EXPLAIN' => 'Depuis cette page, vous pouvez ajouter, supprimer, modifier ou désactiver les groupes d’extensions relatifs aux pièces jointes. Vous pouvez attribuer des catégories spéciales, modifier le mécanisme de téléchargement et définir une icône de transfert qui sera affichée devant les pièces jointes appartenant à un groupe d’extensions spécifique.', + 'ACP_MANAGE_EXTENSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les extensions des pièces jointes autorisées à être transférées et téléchargées sur votre forum. Si vous souhaitez activer une extension, vous devez vous rendre sur le panneau de gestion des groupes d’extensions. Pour des raisons de sécurité, nous vous recommandons de ne pas autoriser les extensions de langages de programmation (telles que « php », « php3 », « php4 », « phtml », « pl », « cgi », « py », « rb », « asp », « aspx », etc.).', + 'ACP_ORPHAN_ATTACHMENTS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter les fichiers orphelins, qui correspondent aux pièces jointes qui ont été transférées et insérées à des messages qui n’ont pas été envoyés par leur auteur respectif. Vous pouvez supprimer ces fichiers ou les insérer à des messages existants en saisissant leurs identifiants.', + 'ADD_EXTENSION' => 'Ajouter une extension', + 'ADD_EXTENSION_GROUP' => 'Ajouter un groupe d’extensions', + 'ADMIN_UPLOAD_ERROR' => 'Des erreurs sont survenues lors de l’insertion du fichier « %s ».', + 'ALLOWED_FORUMS' => 'Forums autorisés', + 'ALLOWED_FORUMS_EXPLAIN' => 'Les extensions assignées seront autorisées dans les forums sélectionnés.', + 'ALLOWED_IN_PM_POST' => 'Autorisé', + 'ALLOW_ATTACHMENTS' => 'Activer les pièces jointes', + 'ALLOW_ALL_FORUMS' => 'Autoriser dans tous les forums', + 'ALLOW_IN_PM' => 'Autorisé dans la messagerie privée', + 'ALLOW_PM_ATTACHMENTS' => 'Autoriser le transfert de pièces jointes dans les messages privés', + 'ALLOW_SELECTED_FORUMS' => 'Autoriser uniquement dans les forums sélectionnés ci-dessous', + 'ASSIGNED_EXTENSIONS' => 'Extensions assignées', + 'ASSIGNED_GROUP' => 'Groupe d’extensions assignées', + 'ATTACH_EXTENSIONS_URL' => 'Extensions', + 'ATTACH_EXT_GROUPS_URL' => 'Groupes d’extensions', + 'ATTACH_ID' => 'Identifiant', + 'ATTACH_MAX_FILESIZE' => 'Taille maximale des pièces jointes', + 'ATTACH_MAX_FILESIZE_EXPLAIN' => 'La taille maximale des pièces jointes. Si cette valeur est sur « 0 », la taille ne sera limitée que par votre configuration de PHP.', + 'ATTACH_MAX_PM_FILESIZE' => 'Taille maximale des pièces jointes dans les messages privés', + 'ATTACH_MAX_PM_FILESIZE_EXPLAIN' => 'La taille maximale des pièces jointes insérées dans les messages privés. Réglez cette valeur sur « 0 » si vous ne souhaitez pas limiter cette taille.', + 'ATTACH_ORPHAN_URL' => 'Pièces jointes orphelines', + 'ATTACH_POST_ID' => 'Identifiant du message', + 'ATTACH_POST_TYPE' => 'Type de message', + 'ATTACH_QUOTA' => 'Taille maximale de l’espace de stockage des pièces jointes', + 'ATTACH_QUOTA_EXPLAIN' => 'La taille maximale de l’espace de stockage qui sera alloué à la totalité des pièces jointes transférées sur le forum. Réglez cette valeur sur « 0 » si vous ne souhaitez pas limiter la taille de cet espace.', + 'ATTACH_TO_POST' => 'Insérer au message', + + 'CAT_FLASH_FILES' => 'Fichiers Flash', + 'CAT_IMAGES' => 'Images', + 'CHECK_CONTENT' => 'Vérifier les pièces jointes', + 'CHECK_CONTENT_EXPLAIN' => 'Certains navigateurs internet peuvent faire erreur en attribuant un type MIME erroné aux fichiers transférés. Cette option permet de rejeter les fichiers qui présentent un risque de provoquer cette erreur.', + 'CREATE_GROUP' => 'Créer un nouveau groupe', + 'CREATE_THUMBNAIL' => 'Générer des miniatures', + 'CREATE_THUMBNAIL_EXPLAIN' => 'Génère des miniatures dans toutes les situations possibles.', + + 'DEFINE_ALLOWED_IPS' => 'Spécifier les adresses IP et les noms d’hôtes autorisés', + 'DEFINE_DISALLOWED_IPS' => 'Spécifier les adresses IP et les noms d’hôtes interdits', + 'DOWNLOAD_ADD_IPS_EXPLAIN' => 'Veuillez saisir chaque adresse IP et nom d’hôte sur une nouvelle ligne. Si vous souhaitez spécifier une plage d’adresses IP, séparez le début et la fin par un tiret « - » et utilisez un astérisque « * » comme métacaractère passe-partout.', + 'DOWNLOAD_REMOVE_IPS_EXPLAIN' => 'Vous pouvez supprimer ou lever l’exclusion de plusieurs adresses IP en une seule fois en utilisant la combinaison appropriée de la souris et du clavier de votre ordinateur et de votre navigateur internet. Les adresses IP et les noms d’hôtes exclus apparaissent sur fond bleu.', + 'DISPLAY_INLINED' => 'Afficher les images des pièces jointes dans les messages', + 'DISPLAY_INLINED_EXPLAIN' => 'Si cette option est désactivée, les images transférées comme pièces jointes ne s’afficheront que sous forme de lien.', + 'DISPLAY_ORDER' => 'Ordre d’affichage des pièces jointes', + 'DISPLAY_ORDER_EXPLAIN' => 'Les pièces jointes transférées seront affichées dans l’ordre chronologique sélectionné dans ce menu.', + + 'EDIT_EXTENSION_GROUP' => 'Modifier le groupe d’extensions', + 'EXCLUDE_ENTERED_IP' => 'Activez cette option afin de retirer de la liste les éléments sélectionnés.', + 'EXCLUDE_FROM_ALLOWED_IP' => 'Retirer la sélection des adresses IP et des noms d’hôtes autorisés', + 'EXCLUDE_FROM_DISALLOWED_IP' => 'Retirer la sélection des adresses IP et des noms d’hôtes interdits', + 'EXTENSIONS_UPDATED' => 'Les extensions ont été mises à jour.', + 'EXTENSION_EXIST' => 'L’extension « %s » existe déjà.', + 'EXTENSION_GROUP' => 'Groupe d’extensions', + 'EXTENSION_GROUPS' => 'Groupes d’extensions', + 'EXTENSION_GROUP_DELETED' => 'Le groupe d’extensions a été supprimé.', + 'EXTENSION_GROUP_EXIST' => 'Le groupe d’extensions « %s » existe déjà.', + + 'EXT_GROUP_ARCHIVES' => 'Archives', + 'EXT_GROUP_DOCUMENTS' => 'Documents', + 'EXT_GROUP_DOWNLOADABLE_FILES' => 'Fichiers téléchargeables', + 'EXT_GROUP_FLASH_FILES' => 'Fichiers Flash', + 'EXT_GROUP_IMAGES' => 'Images', + 'EXT_GROUP_PLAIN_TEXT' => 'Texte brut', + + 'FILES_GONE' => 'Certaines pièces jointes que vous souhaitez supprimer sont introuvables. Elles ont peut-être déjà été supprimées. Les pièces jointes détectées ont été supprimées.', + 'FILES_STATS_WRONG' => 'Les fichiers statistiques semblent contenir des informations erronées et doivent être resynchronisés. Les valeurs actuelles mentionnent que le nombre de pièces jointes est à %1$d et que leur taille est de %2$s.
Veuillez cliquer %3$sici%4$s afin de resynchroniser les fichiers statistiques.', + + 'GO_TO_EXTENSIONS' => 'Aller sur la page de gestion des extensions', + 'GROUP_NAME' => 'Nom du groupe d’extensions', + + 'IMAGE_LINK_SIZE' => 'Dimensions maximales des images avant la transformation en lien', + 'IMAGE_LINK_SIZE_EXPLAIN' => 'Les images transférées en pièces jointes seront affichées comme des liens si elles dépassent les valeurs spécifiées dans ces champs. Réglez ces deux valeurs sur « 0 » si vous souhaitez désactiver ce comportement.', + + 'MAX_ATTACHMENTS' => 'Limite de pièces jointes par message', + 'MAX_ATTACHMENTS_PM' => 'Limite de pièces jointes par message privé', + 'MAX_EXTGROUP_FILESIZE' => 'Taille maximale des fichiers', + 'MAX_IMAGE_SIZE' => 'Dimensions maximales des images', + 'MAX_IMAGE_SIZE_EXPLAIN' => 'La largeur et la hauteur maximale des images transférées comme pièces jointes. Réglez ces deux valeurs sur « 0 » si vous souhaitez désactiver la vérification des dimensions des images.', + 'MAX_THUMB_WIDTH' => 'Taille maximale des miniatures', + 'MAX_THUMB_WIDTH_EXPLAIN' => 'Les miniatures générées ne dépasseront pas la dimension spécifiée dans ce champ.', + 'MIN_THUMB_FILESIZE' => 'Taille minimale des miniatures', + 'MIN_THUMB_FILESIZE_EXPLAIN' => 'Les images ne seront pas miniaturisées si la taille des images est inférieure à la valeur spécifiée dans ce champ.', + 'MODE_INLINE' => 'Dans la ligne', + 'MODE_PHYSICAL' => 'Physique', + + 'NOT_ALLOWED_IN_PM' => 'Autorisé uniquement dans les messages', + 'NOT_ALLOWED_IN_PM_POST' => 'Interdit', + 'NOT_ASSIGNED' => 'Non assigné', + 'NO_ATTACHMENTS' => 'Aucune pièce jointe.', + 'NO_EXT_GROUP' => 'Aucun', + 'NO_EXT_GROUP_NAME' => 'Vous n’avez spécifié aucun nom de groupe', + 'NO_EXT_GROUP_SPECIFIED' => 'Vous n’avez spécifié aucun groupe d’extensions.', + 'NO_FILE_CAT' => 'Aucune', + 'NO_IMAGE' => 'Aucune image', + 'NO_UPLOAD_DIR' => 'Le répertoire de transfert que vous avez spécifié est introuvable.', + 'NO_WRITE_UPLOAD' => 'Le répertoire de transfert que vous avez spécifié est en lecture seule. Veuillez modifier ses droits d’accès pour écriture par votre serveur.', + + 'ONLY_ALLOWED_IN_PM' => 'Autorisé uniquement dans les messages privés', + 'ORDER_ALLOW_DENY' => 'Autoriser', + 'ORDER_DENY_ALLOW' => 'Interdire', + + 'REMOVE_ALLOWED_IPS' => 'Supprimer ou lever l’exclusion des adresses IP ou des noms d’hôtes autorisés', + 'REMOVE_DISALLOWED_IPS' => 'Supprimer ou lever l’exclusion des adresses IP ou des noms d’hôtes interdits', + 'RESYNC_FILES_STATS_CONFIRM' => 'Êtes-vous sûr de vouloir resynchroniser les fichiers statistiques ?', + + 'SECURE_ALLOW_DENY' => 'Comportement des téléchargements sécurisés', + 'SECURE_ALLOW_DENY_EXPLAIN' => 'Si les téléchargements sécurisés sont activés, cette option vous permet de modifier le comportement par défaut en autorisant, sous forme de liste blanche, ou en interdisant, sous forme de liste noire, les adresses IP et les noms d’hôtes spécifiés à télécharger les pièces jointes de votre forum.', + 'SECURE_DOWNLOADS' => 'Activer les téléchargements sécurisés', + 'SECURE_DOWNLOADS_EXPLAIN' => 'Si cette option est activée, les téléchargements ne seront limités qu’aux adresses IP et aux noms d’hôtes spécifiés.', + 'SECURE_DOWNLOAD_NOTICE' => 'Les téléchargements sécurisés sont désactivés. Les paramètres ci-dessous ne seront appliqués que lorsque les téléchargements sécurisés sont activés.', + 'SECURE_DOWNLOAD_UPDATE_SUCCESS' => 'La liste des adresses IP a été mise à jour.', + 'SECURE_EMPTY_REFERRER' => 'Autoriser les téléchargements aux référants vides', + 'SECURE_EMPTY_REFERRER_EXPLAIN' => 'Les téléchargements sécurisés sont basés sur les référants. Souhaitez-vous autoriser les téléchargements aux utilisateurs qui ne divulguent pas leur référant ?', + 'SETTINGS_CAT_IMAGES' => 'Paramètres des catégories d’images', + 'SPECIAL_CATEGORY' => 'Catégorie spéciale', + 'SPECIAL_CATEGORY_EXPLAIN' => 'Les catégories spéciales modifient la présentation des messages.', + 'SUCCESSFULLY_UPLOADED' => 'Transféré.', + 'SUCCESS_EXTENSION_GROUP_ADD' => 'Le groupe d’extensions a été ajouté.', + 'SUCCESS_EXTENSION_GROUP_EDIT' => 'Les groupes d’extensions ont été mis à jour.', + + 'UPLOADING_FILES' => 'Transfert de fichiers', + 'UPLOADING_FILE_TO' => 'Transfert du fichier « %1$s » au message numéro %2$d…', + 'UPLOAD_DENIED_FORUM' => 'Vous ne pouvez pas transférer de fichiers vers le forum « %s ».', + 'UPLOAD_DIR' => 'Répertoire de transfert', + 'UPLOAD_DIR_EXPLAIN' => 'Le répertoire de stockage des pièces jointes. Veuillez noter que si vous modifiez ce répertoire, vous devrez transférer manuellement toutes les pièces jointes existantes vers ce nouvel emplacement.', + 'UPLOAD_ICON' => 'Icône de transfert', + 'UPLOAD_NOT_DIR' => 'Le répertoire de transfert que vous avez spécifié est invalide.', +]); diff --git a/language/fr/acp/ban.php b/language/fr/acp/ban.php new file mode 100644 index 0000000..5b57eb3 --- /dev/null +++ b/language/fr/acp/ban.php @@ -0,0 +1,83 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Banning +$lang = array_merge($lang, [ + '1_HOUR' => '1 heure', + '30_MINS' => '30 minutes', + '6_HOURS' => '6 heures', + + 'ACP_BAN_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les bannissements par noms d’utilisateurs, adresses IP ou adresses de courriel. Ces différentes méthodes empêchent qu’un utilisateur indésirable puisse participer à l’ensemble des discussions de votre forum. Si vous le souhaitez, vous pouvez fournir une raison de 3000 caractères au maximum qui sera affichée à l’utilisateur banni. Cette raison sera également affichée dans l’historique des administrateurs. La durée des bannissements peut également être spécifiée. Si vous souhaitez qu’un bannissement expire à une date spécifique plutôt qu’après avoir passé un certain délai, sélectionnez « Jusqu’au » et saisissez une date au format « AAAA-MM-JJ ».', + + 'BAN_EXCLUDE' => 'Annuler le bannissement', + 'BAN_LENGTH' => 'Durée du bannissement', + 'BAN_REASON' => 'Raison du bannissement', + 'BAN_GIVE_REASON' => 'Raison affichée à l’utilisateur banni', + 'BAN_UPDATE_SUCCESSFUL' => 'La liste des bannissements a été mise à jour.', + 'BANNED_UNTIL_DATE' => 'jusqu’au %s', // Example: "until Mon 13.Jul.2009, 14:44" + 'BANNED_UNTIL_DURATION' => '%1$s (jusqu’au %2$s)', // Example: "7 days (until Tue 14.Jul.2009, 14:44)" + + 'EMAIL_BAN' => 'Bannir des adresses de courriel', + 'EMAIL_BAN_EXCLUDE_EXPLAIN' => 'Si cette option est activée, les adresses de courriel saisies seront exclues de tous les bannissements actuels.', + 'EMAIL_BAN_EXPLAIN' => 'Vous pouvez bannir plusieurs adresses de courriel en les saisissant sur une nouvelle ligne. Pour utiliser des adresses partielles, utilisez un astérisque « * » comme métacaractère passe-partout. Par exemple, « *@outlook.com », « *@*.domaine.tld », etc.', + 'EMAIL_NO_BANNED' => 'Aucune adresse de courriel n’a été bannie.', + 'EMAIL_UNBAN' => 'Annuler le bannissement des adresses de courriel', + 'EMAIL_UNBAN_EXPLAIN' => 'Vous pouvez annuler le bannissement de plusieurs adresses de courriel en une seule fois en utilisant la combinaison appropriée de la souris et du clavier de votre ordinateur et de votre navigateur. Les adresses de courriel exclues apparaissent soulignées.', + + 'IP_BAN' => 'Bannir des adresses IP', + 'IP_BAN_EXCLUDE_EXPLAIN' => 'Si cette option est activée, les adresses IP saisies seront exclues de tous les bannissements actuels.', + 'IP_BAN_EXPLAIN' => 'Vous pouvez bannir plusieurs adresses IP ou noms d’hôtes en les saisissant sur une nouvelle ligne. Pour spécifier une plage d’adresses IP, séparez le début et la fin par un tiret « - » et utilisez un astérisque « * » comme métacaractère passe-partout.', + 'IP_HOSTNAME' => 'Adresses IP ou noms d’hôtes', + 'IP_NO_BANNED' => 'Aucune adresse IP n’a été bannie.', + 'IP_UNBAN' => 'Annuler le bannissement des adresses IP', + 'IP_UNBAN_EXPLAIN' => 'Vous pouvez annuler le bannissement de plusieurs adresses IP en une seule fois en utilisant la combinaison appropriée de la souris et du clavier de votre ordinateur et de votre navigateur. Les adresses IP exclues apparaissent soulignées.', + + 'LENGTH_BAN_INVALID' => 'La date doit être au format « AAAA-MM-JJ ».', + + 'OPTIONS_BANNED' => 'Banni', + 'OPTIONS_EXCLUDED' => 'Exclu', + + 'PERMANENT' => 'Permanent', + + 'UNTIL' => 'Jusqu’au', + 'USER_BAN' => 'Bannir des utilisateurs par leur nom d’utilisateurs', + 'USER_BAN_EXCLUDE_EXPLAIN' => 'Si cette option est activée, les utilisateurs saisis seront exclus de tous les bannissements actuels.', + 'USER_BAN_EXPLAIN' => 'Vous pouvez bannir plusieurs noms d’utilisateurs en les saisissant sur une nouvelle ligne. Utilisez la fonctionnalité « Trouver un membre » afin de rechercher et d’ajouter un ou plusieurs utilisateurs.', + 'USER_NO_BANNED' => 'Aucun nom d’utilisateur n’a été banni.', + 'USER_UNBAN' => 'Annuler le bannissement des utilisateurs par leur nom d’utilisateurs', + 'USER_UNBAN_EXPLAIN' => 'Vous pouvez annuler le bannissement de plusieurs utilisateurs en une seule fois en utilisant la combinaison appropriée de la souris et du clavier de votre ordinateur et de votre navigateur. Les utilisateurs exclus apparaissent soulignés.', +]); diff --git a/language/fr/acp/board.php b/language/fr/acp/board.php new file mode 100644 index 0000000..ca1b3c1 --- /dev/null +++ b/language/fr/acp/board.php @@ -0,0 +1,630 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Board Settings +$lang = array_merge($lang, [ + 'ACP_BOARD_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez effectuer les opérations basiques de votre forum, comme lui attribuer un nom et une description, régler son fuseau horaire ou encore définir sa langue par défaut.', + 'BOARD_INDEX_TEXT' => 'Texte de l’accueil du forum', + 'BOARD_INDEX_TEXT_EXPLAIN' => 'Ce texte sera affiché comme accueil du forum dans le chemin de navigation du forum. Si aucun texte n’est spécifié, « Accueil du forum » sera affiché par défaut.', + 'BOARD_STYLE' => 'Style du forum', + 'CUSTOM_DATEFORMAT' => 'Personnaliser…', + 'DEFAULT_DATE_FORMAT' => 'Format de la date', + 'DEFAULT_DATE_FORMAT_EXPLAIN' => 'Le format de la date est similaire au format utilisé par la fonction « date() » de PHP.', + 'DEFAULT_LANGUAGE' => 'Langue par défaut', + 'DEFAULT_STYLE' => 'Style par défaut', + 'DEFAULT_STYLE_EXPLAIN' => 'Le style par défaut affiché aux utilisateurs nouvellement inscrits.', + 'DISABLE_BOARD' => 'Désactiver le forum', + 'DISABLE_BOARD_EXPLAIN' => 'Le forum sera inaccessible aux utilisateurs qui ne sont ni administrateurs ni modérateurs. Si vous le souhaitez, vous pouvez également fournir une explication, limitée à 255 caractères au maximum, qui sera affichée aux utilisateurs.', + 'DISPLAY_LAST_SUBJECT' => 'Afficher le sujet du dernier message publié dans la liste des forums', + 'DISPLAY_LAST_SUBJECT_EXPLAIN' => 'Le sujet du dernier message qui a été publié dans un forum sera affiché dans la liste des forums sous la forme d’un lien redirigeant vers le dernier message. Les sujets publiés dans les forums protégés par un mot de passe ou en lecture seule ne sont pas affichés.', + 'GUEST_STYLE' => 'Style des invités', + 'GUEST_STYLE_EXPLAIN' => 'Le style affiché aux utilisateurs qui ne sont pas connectés à un compte.', + 'OVERRIDE_STYLE' => 'Remplacer le style des utilisateurs', + 'OVERRIDE_STYLE_EXPLAIN' => 'Le style des utilisateurs et des invités sera remplacé par celui qui a été sélectionné dans le menu « Style par défaut ».', + 'SITE_DESC' => 'Description du forum', + 'SITE_HOME_TEXT' => 'Texte du lien du site internet principal', + 'SITE_HOME_TEXT_EXPLAIN' => 'Ce texte, affiché sous forme de lien redirigeant vers votre site internet, sera inséré dans les liens de navigation de votre forum. Si aucun texte n’est spécifié dans ce champ, « Page d’accueil » sera alors affiché par défaut.', + 'SITE_HOME_URL' => 'Lien du site internet principal', + 'SITE_HOME_URL_EXPLAIN' => 'Si vous saisissez un lien, il sera inséré dans les liens de navigation de votre forum et remplacera par la destination souhaitée le lien présent sur le logo du forum qui redirige par défaut les utilisateurs sur l’accueil du forum. Veuillez saisir un lien absolu, tel que « https://www.phpbb.com ».', + 'SITE_NAME' => 'Nom du forum', + 'SYSTEM_TIMEZONE' => 'Fuseau horaire affiché aux invités', + 'SYSTEM_TIMEZONE_EXPLAIN' => 'Le fuseau horaire de ce menu ne concerne que les utilisateurs qui ne sont pas connectés à un compte, tels que les invités et les robots. Les utilisateurs connectés à un compte peuvent régler leur fuseau horaire lors de leur inscription ou depuis le panneau de contrôle de l’utilisateur.', + 'WARNINGS_EXPIRE' => 'Durée des avertissements', + 'WARNINGS_EXPIRE_EXPLAIN' => 'Le nombre de jours qui s’écouleront avant qu’un avertissement n’expire automatiquement. Réglez cette valeur sur « 0 » afin de ne pas faire expirer les avertissements dans le temps.', +]); + +// Board Features +$lang = array_merge($lang, [ + 'ACP_BOARD_FEATURES_EXPLAIN' => 'Depuis cette page, vous pouvez activer et désactiver les diverses fonctionnalités du forum.', + + 'ALLOW_ATTACHMENTS' => 'Activer les pièces jointes', + 'ALLOW_BIRTHDAYS' => 'Activer les anniversaires', + 'ALLOW_BIRTHDAYS_EXPLAIN' => 'Les utilisateurs pourront saisir leur date de naissance afin que leur âge soit affiché sur leur profil. Veuillez noter que la liste des anniversaires affichée sur l’accueil du forum ne dépend pas de ce paramètre.', + 'ALLOW_BOOKMARKS' => 'Autoriser l’ajout de sujets aux favoris', + 'ALLOW_BOOKMARKS_EXPLAIN' => 'Les utilisateurs pourront ajouter des sujets à leurs favoris.', + 'ALLOW_BBCODE' => 'Activer les BBCodes', + 'ALLOW_FORUM_NOTIFY' => 'Autoriser l’abonnement aux forums', + 'ALLOW_NAME_CHANGE' => 'Autoriser la modification des noms d’utilisateurs', + 'ALLOW_NO_CENSORS' => 'Autoriser la désactivation de la censure de mots', + 'ALLOW_NO_CENSORS_EXPLAIN' => 'Les utilisateurs pourront désactiver la censure automatique de mots dans le contenu des messages et des messages privés.', + 'ALLOW_PM_ATTACHMENTS' => 'Autoriser le transfert de pièces jointes dans les messages privés', + 'ALLOW_PM_REPORT' => 'Autoriser les utilisateurs à rapporter des messages privés', + 'ALLOW_PM_REPORT_EXPLAIN' => 'Si cette option est activée, les utilisateurs pourront rapporter aux modérateurs du forum les messages privés qu’ils ont reçus ou qu’ils ont envoyés. Les messages privés rapportés seront alors visibles depuis le panneau de contrôle du modérateur.', + 'ALLOW_QUICK_REPLY' => 'Activer la réponse rapide', + 'ALLOW_QUICK_REPLY_EXPLAIN' => 'La réponse rapide doit également être autorisée dans les paramètres spécifiques de chaque forum afin que les utilisateurs puissent utiliser cette fonctionnalité.', + 'ALLOW_QUICK_REPLY_BUTTON' => 'Activer et autoriser la réponse rapide dans tous les forums', + 'ALLOW_SIG' => 'Activer les signatures', + 'ALLOW_SIG_BBCODE' => 'Autoriser les BBCodes dans les signatures', + 'ALLOW_SIG_FLASH' => 'Autoriser la balise BBCode « [flash] » dans les signatures', + 'ALLOW_SIG_IMG' => 'Autoriser la balise BBCode « [img] » dans les signatures', + 'ALLOW_SIG_LINKS' => 'Autoriser les liens dans les signatures', + 'ALLOW_SIG_LINKS_EXPLAIN' => 'Si cette option est désactivée, l’utilisation de la balise BBCode « [url] » et la transformation automatique de texte en lien sera désactivée.', + 'ALLOW_SIG_SMILIES' => 'Autoriser les émoticônes dans les signatures', + 'ALLOW_SMILIES' => 'Activer les émoticônes', + 'ALLOW_TOPIC_NOTIFY' => 'Autoriser l’abonnement aux sujets', + 'BOARD_PM' => 'Activer la messagerie privée', + 'BOARD_PM_EXPLAIN' => 'La messagerie privée pourra être utilisée par les utilisateurs.', + 'ALLOW_BOARD_NOTIFICATIONS' => 'Autoriser les notifications du forum', +]); + +// Avatar Settings +$lang = array_merge($lang, [ + 'ACP_AVATAR_SETTINGS_EXPLAIN' => 'Les avatars sont généralement de petites images uniques qu’un utilisateur peut associer à sa personnalité. Les avatars sont habituellement affichés sous le nom d’utilisateur lors de la consultation de sujets, bien que cette apparence puisse être différente selon le style utilisé sur le forum. Depuis cette page, vous pouvez déterminer quels avatars pourront être utilisés par les utilisateurs de votre forum. Veuillez noter que pour transférer des avatars, vous devez avoir préalablement créé le répertoire que vous indiquerez ci-dessous et vous assurer que ses droits d’accès soient disponibles pour écriture par votre serveur. Veuillez également noter que les limitations de taille ne sont imposées qu’aux avatars transférés mais ne s’appliquent pas aux images distantes.', + + 'ALLOW_AVATARS' => 'Activer les avatars', + 'ALLOW_AVATARS_EXPLAIN' => 'Les utilisateurs pourront afficher un avatar.
Si les avatars sont désactivés, ils ne seront plus affichés sur le forum, mais les utilisateurs pourront cependant toujours télécharger leur avatar depuis le panneau de contrôle de l’utilisateur.', + 'ALLOW_GRAVATAR' => 'Autoriser les images de Gravatar', + 'ALLOW_LOCAL' => 'Autoriser les images de la galerie d’avatars', + 'ALLOW_REMOTE' => 'Autoriser les images distantes', + 'ALLOW_REMOTE_EXPLAIN' => 'Les avatars situés sur un site internet externe.
Attention : l’activation de cette fonctionnalité peut permettre aux utilisateurs de vérifier l’existence de fichiers et de services qui ne sont accessibles que sur le réseau local.', + 'ALLOW_REMOTE_UPLOAD' => 'Autoriser le transfert d’images distantes', + 'ALLOW_REMOTE_UPLOAD_EXPLAIN' => 'Les utilisateurs du forum pourront transférer sur votre serveur des images hébergées sur des sites internet externes et les utiliser comme avatars.
Attention : l’activation de cette fonctionnalité peut permettre aux utilisateurs de vérifier l’existence de fichiers et de services qui ne sont accessibles que sur le réseau local', + 'ALLOW_UPLOAD' => 'Autoriser le transfert d’images locales', + 'AVATAR_GALLERY_PATH' => 'Chemin vers la galerie d’avatars', + 'AVATAR_GALLERY_PATH_EXPLAIN' => 'Le chemin relatif à la racine du répertoire de votre forum, tel que « images/avatars/gallery ».
Pour des raisons de sécurité, les deux points, tels que « ../ », seront retirés du chemin.', + 'AVATAR_STORAGE_PATH' => 'Chemin vers le répertoire de stockage des avatars', + 'AVATAR_STORAGE_PATH_EXPLAIN' => 'Le chemin relatif à la racine du répertoire de votre forum, tel que « images/avatars/upload ».
Le transfert des avatars ne sera pas disponible si le répertoire spécifié est en lecture seule.
Pour des raisons de sécurité, les deux points, tels que « ../ », seront retirés du chemin.', + 'MAX_AVATAR_SIZE' => 'Dimensions maximales des avatars', + 'MAX_AVATAR_SIZE_EXPLAIN' => 'La largeur et la hauteur maximale, en pixels, des avatars.', + 'MAX_FILESIZE' => 'Taille maximale des avatars', + 'MAX_FILESIZE_EXPLAIN' => 'Cette limitation ne concerne que les avatars qui seront transférés sur le serveur par les utilisateurs de votre forum. Si cette valeur est sur « 0 », la taille ne sera limitée que par votre configuration de PHP.', + 'MIN_AVATAR_SIZE' => 'Dimensions minimales des avatars', + 'MIN_AVATAR_SIZE_EXPLAIN' => 'La largeur et la hauteur minimale, en pixels, des avatars.', +]); + +// Message Settings +$lang = array_merge($lang, [ + 'ACP_MESSAGE_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez configurer tous les paramètres relatifs aux messages privés.', + + 'ALLOW_BBCODE_PM' => 'Autoriser les BBCodes dans les messages privés', + 'ALLOW_FLASH_PM' => 'Autoriser la balise BBCode « [flash] »', + 'ALLOW_FLASH_PM_EXPLAIN' => 'Veuillez noter que l’utilisation du Flash dans les messages privés dépend également des permissions.', + 'ALLOW_FORWARD_PM' => 'Autoriser le transfert de messages privés', + 'ALLOW_IMG_PM' => 'Autoriser la balise BBCode « [img] »', + 'ALLOW_MASS_PM' => 'Autoriser l’envoi de messages privés de masse', + 'ALLOW_MASS_PM_EXPLAIN' => 'Les utilisateurs pourront envoyer en une seule fois un message privé à plusieurs utilisateurs et groupes d’utilisateurs. Vous pouvez restreindre l’envoi de messages privés à chaque groupe d’utilisateurs depuis les paramètres relatifs aux groupes.', + 'ALLOW_PRINT_PM' => 'Autoriser l’aperçu avant impression dans les messages privés', + 'ALLOW_QUOTE_PM' => 'Autoriser les citations dans les messages privés', + 'ALLOW_SIG_PM' => 'Autoriser les signatures dans les messages privés', + 'ALLOW_SMILIES_PM' => 'Autoriser les émoticônes dans les messages privés', + 'BOXES_LIMIT' => 'Nombre maximal de messages privés par boîte', + 'BOXES_LIMIT_EXPLAIN' => 'Si leur boîte est pleine, les utilisateurs ne pourront pas recevoir davantage de messages privés que la valeur spécifiée dans ce champ. Réglez cette valeur sur « 0 » afin de ne pas limiter le nombre de messages privés par boîte.', + 'BOXES_MAX' => 'Nombre maximal de boîtes de messages privés', + 'BOXES_MAX_EXPLAIN' => 'Le nombre maximal de boîtes de messages privés que les utilisateurs pourront créer et utiliser.', + 'ENABLE_PM_ICONS' => 'Activer les icônes de sujet dans les messages privés', + 'FULL_FOLDER_ACTION' => 'Opération à effectuer par défaut lorsqu’une boîte est pleine', + 'FULL_FOLDER_ACTION_EXPLAIN' => 'L’opération à effectuer par défaut lorsque la boîte d’un utilisateur est pleine et si l’opération spécifiée par l’utilisateur est impossible. La seule exception s’applique à la boîte des messages envoyés, où les messages les plus anciens seront automatiquement supprimés par défaut.', + 'HOLD_NEW_MESSAGES' => 'Mettre en attente les nouveaux messages', + 'PM_EDIT_TIME' => 'Limite de la durée de modificiation', + 'PM_EDIT_TIME_EXPLAIN' => 'Limite la durée de modification des messages privés qui n’ont pas encore été consultés par leurs destinataires. Réglez cette valeur sur « 0 » afin de ne pas limiter cette durée.', + 'PM_MAX_RECIPIENTS' => 'Nombre maximal de destinataires par message privé', + 'PM_MAX_RECIPIENTS_EXPLAIN' => 'Le nombre maximal de destinataires autorisés par message privé. Réglez cette valeur sur « 0 » afin de ne pas limiter ce nombre. Vous pouvez imposer un nombre différent à chaque groupe d’utilisateurs depuis les paramètres relatifs aux groupes.', +]); + +// Post Settings +$lang = array_merge($lang, [ + 'ACP_POST_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez configurer tous les paramètres relatifs à la publication.', + 'ALLOW_POST_LINKS' => 'Autoriser les liens dans les messages et les messages privés', + 'ALLOW_POST_LINKS_EXPLAIN' => 'Si vous n’autorisez pas cette fonctionnalité, l’utilisation de la balise BBCode « [url] » et la transformation automatique de texte en lien sera désactivée.', + 'ALLOWED_SCHEMES_LINKS' => 'Systèmes de liens autorisés', + 'ALLOWED_SCHEMES_LINKS_EXPLAIN' => 'Les utilisateurs ne peuvent publier que les liens sans système de liens ou les liens avec des systèmes de liens autorisés dans ce champ, en les séparant par une virgule.', + 'ALLOW_POST_FLASH' => 'Autoriser la balise BBCode « [flash] » dans les messages', + 'ALLOW_POST_FLASH_EXPLAIN' => 'Si vous n’autorisez pas cette fonctionnalité, l’utilisation de la balise BBCode « [flash] » sera désactivée dans tous les messages. Dans le cas contraire, vous pourrez déterminer depuis le système de permissions quels utilisateurs pourront, ou non, utiliser cette balise.', + + 'BUMP_INTERVAL' => 'Intervalle de remontée des sujets', + 'BUMP_INTERVAL_EXPLAIN' => 'Le nombre de minutes, d’heures ou de jours qui s’écouleront entre le dernier message d’un sujet et la possibilité de remonter ce même sujet. Réglez cette valeur sur « 0 » afin de désactiver ce comportement.', + 'CHAR_LIMIT' => 'Nombre maximal de caractères par message et message privé', + 'CHAR_LIMIT_EXPLAIN' => 'Le nombre maximal de caractères que les utilisateurs peuvent saisir dans un message et dans un message privé. Réglez cette valeur sur « 0 » afin de ne pas limiter le nombre de caractères.', + 'DELETE_TIME' => 'Limite de la durée de suppression', + 'DELETE_TIME_EXPLAIN' => 'La limite de la durée de suppression des messages publiés. Réglez cette valeur sur « 0 » afin de ne pas limiter la durée de suppression.', + 'DISPLAY_LAST_EDITED' => 'Afficher la date de la dernière modification', + 'DISPLAY_LAST_EDITED_EXPLAIN' => 'La date de la dernière modification des messages sera affichée.', + 'EDIT_TIME' => 'Limite de la durée de modification', + 'EDIT_TIME_EXPLAIN' => 'La limite de la durée de modification des messages publiés. Réglez cette valeur sur « 0 » afin de ne pas limiter la durée de modification.', + 'FLOOD_INTERVAL' => 'Intervalle de publication', + 'FLOOD_INTERVAL_EXPLAIN' => 'Le nombre de secondes qui s’écouleront avant qu’un utilisateur puisse de nouveau avoir la possibilité de publier de nouveaux messages. Si vous souhaitez autoriser les utilisateurs à ignorer cet intervalle, vous devrez modifier leurs permissions.', + 'HOT_THRESHOLD' => 'Seuil de popularité des sujets', + 'HOT_THRESHOLD_EXPLAIN' => 'Le nombre de messages par sujet à atteindre afin qu’un sujet soit considéré comme populaire. Réglez cette valeur sur « 0 » afin de désactiver ce comportement.', + 'MAX_POLL_OPTIONS' => 'Nombre maximal d’options par sondage', + 'MAX_POST_FONT_SIZE' => 'Taille maximale de la police de caractères dans les messages et messages privés', + 'MAX_POST_FONT_SIZE_EXPLAIN' => 'La taille maximale de la police de caractères autorisée dans un message et dans un message privé. Réglez cette valeur sur « 0 » afin de ne pas limiter la taille de la police de caractères.', + 'MAX_POST_IMG_HEIGHT' => 'Hauteur maximale des images dans les messages et les messages privés', + 'MAX_POST_IMG_HEIGHT_EXPLAIN' => 'La hauteur maximale des images et des fichiers Flash dans les messages et les messages privés. Réglez cette valeur sur « 0 » afin de ne pas limiter la hauteur des images et des fichiers Flash.', + 'MAX_POST_IMG_WIDTH' => 'Largeur maximale des images dans les messages et les messages privés', + 'MAX_POST_IMG_WIDTH_EXPLAIN' => 'La largeur maximale des images et des fichiers Flash dans les messages et les messages privés. Réglez cette valeur sur « 0 » afin de ne limiter la largeur des images et des fichiers Flash.', + 'MAX_POST_URLS' => 'Nombre maximal de liens par message et message privé', + 'MAX_POST_URLS_EXPLAIN' => 'Le nombre maximal de liens que les utilisateurs peuvent insérer dans un message et dans un message privé. Réglez cette valeur sur « 0 » afin de ne pas limiter le nombre de liens.', + 'MIN_CHAR_LIMIT' => 'Nombre minimal de caractères par message et message privé', + 'MIN_CHAR_LIMIT_EXPLAIN' => 'Le nombre minimal de caractères que les utilisateurs doivent saisir afin de pouvoir publier un message ou un message privé. La valeur minimale de cette option est de 1.', + 'POSTING' => 'Publication', + 'POSTS_PER_PAGE' => 'Nombre de messages par page', + 'QUOTE_DEPTH_LIMIT' => 'Nombre maximal de citations imbriquées', + 'QUOTE_DEPTH_LIMIT_EXPLAIN' => 'Le nombre maximal de citations imbriquées dans un message et dans un message privé. Réglez cette valeur sur « 0 » afin de ne pas limiter le nombre de citations imbriquées.', + 'SMILIES_LIMIT' => 'Nombre maximal d’émoticônes par message et message privé', + 'SMILIES_LIMIT_EXPLAIN' => 'Le nombre maximal d’émoticônes que les utilisateurs peuvent insérer dans un message et dans un message privé. Réglez cette valeur sur « 0 » afin de ne pas limiter le nombre d’émoticônes.', + 'SMILIES_PER_PAGE' => 'Nombre d’émoticônes par page', + 'TOPICS_PER_PAGE' => 'Nombre de sujets par page', +]); + +// Signature Settings +$lang = array_merge($lang, [ + 'ACP_SIGNATURE_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez configurer tous les paramètres relatifs aux signatures.', + + 'MAX_SIG_FONT_SIZE' => 'Taille maximale de la police de caractères dans les signatures', + 'MAX_SIG_FONT_SIZE_EXPLAIN' => 'La taille maximale de la police de caractères que les utilisateurs pourront spécifier dans leur signature. Réglez cette valeur sur « 0 » afin de ne pas limiter la taille de la police de caractères.', + 'MAX_SIG_IMG_HEIGHT' => 'Hauteur maximale des images dans les signatures', + 'MAX_SIG_IMG_HEIGHT_EXPLAIN' => 'La hauteur maximale des images et des fichiers Flash que les utilisateurs pourront insérer dans leur signature. Réglez cette valeur sur « 0 » afin de ne pas limiter la hauteur des images et des fichiers Flash.', + 'MAX_SIG_IMG_WIDTH' => 'Largeur maximale des images dans les signatures', + 'MAX_SIG_IMG_WIDTH_EXPLAIN' => 'La largeur maximale des images et des fichiers Flash que les utilisateurs pourront insérer dans leur signature. Réglez cette valeur sur « 0 » afin de ne pas limiter la largeur des images et des fichiers Flash.', + 'MAX_SIG_LENGTH' => 'Nombre maximal de caractères dans les signatures', + 'MAX_SIG_LENGTH_EXPLAIN' => 'Le nombre maximal de caractères que les utilisateurs pourront saisir dans leur signature.', + 'MAX_SIG_SMILIES' => 'Nombre maximal d’émoticônes dans les signatures', + 'MAX_SIG_SMILIES_EXPLAIN' => 'Le nombre maximal d’émoticônes que les utilisateurs pourront insérer dans leur signature. Réglez cette valeur sur « 0 » afin de ne pas limiter le nombre d’émoticônes.', + 'MAX_SIG_URLS' => 'Nombre maximal de liens dans les signatures', + 'MAX_SIG_URLS_EXPLAIN' => 'Le nombre maximal de liens que les utilisateurs pourront insérer dans leur signature. Réglez cette valeur sur « 0 » afin de ne pas limiter le nombre de liens.', +]); + +// Registration Settings +$lang = array_merge($lang, [ + 'ACP_REGISTER_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez configurer tous les paramètres relatifs aux inscriptions et aux profils des utilisateurs.', + + 'ACC_ACTIVATION' => 'Méthode d’activation des comptes', + 'ACC_ACTIVATION_EXPLAIN' => 'Détermine quels utilisateurs bénéficieront d’un accès immédiat au forum ou si une confirmation leur sera demandée. Vous pouvez également désactiver entièrement les nouvelles inscriptions. Veuillez noter que l’option « Envoi des courriels depuis le forum » doit être activée afin d’être en mesure de pouvoir utiliser l’activation par un administrateur ou par l’utilisateur lui-même.', + 'ACC_ACTIVATION_WARNING' => 'Veuillez noter que la méthode d’activation actuellement sélectionnée nécessite l’activation de la messagerie électronique, sinon l’inscription sera désactivée. Nous vous recommandons d’activer la messagerie électronique ou de sélectionner une autre méthode d’activation.', + 'NEW_MEMBER_POST_LIMIT' => 'Limite de messages des nouveaux membres', + 'NEW_MEMBER_POST_LIMIT_EXPLAIN' => 'Les nouveaux membres seront ajoutés au groupe des utilisateurs nouvellement inscrits jusqu’à ce qu’ils atteignent ce nombre de messages. Vous pouvez utiliser ce groupe afin d’empêcher ces membres à utiliser la messagerie privée ou de contrôler leurs messages. Réglez cette valeur sur « 0 » afin de désactiver ce comportement.', + 'NEW_MEMBER_GROUP_DEFAULT' => 'Instaurer le groupe des utilisateurs nouvellement inscrits en tant que groupe par défaut', + 'NEW_MEMBER_GROUP_DEFAULT_EXPLAIN' => 'Si cette option est activée et qu’une nouvelle limite de messages des nouveaux membres est spécifiée, les utilisateurs nouvellement inscrits ne seront pas uniquement membres du groupe des utilisateurs nouvellement inscrits, mais ce groupe sera également leur groupe par défaut. Cela peut être utile si vous souhaitez attribuer un rang ou un avatar par défaut afin que tous les utilisateurs en bénéficient.', + + 'ACC_ADMIN' => 'Par un administrateur', + 'ACC_DISABLE' => 'Désactiver les inscriptions', + 'ACC_NONE' => 'Aucune activation (accès immédiat)', + 'ACC_USER' => 'Par l’utilisateur (courriel)', +// 'ACC_USER_ADMIN' => 'Par l’utilisateur et un administrateur', + 'ALLOW_EMAIL_REUSE' => 'Autoriser les adresses de courriel identiques', + 'ALLOW_EMAIL_REUSE_EXPLAIN' => 'Les utilisateurs pourront s’inscrire sur le forum en renseignant une adresse de courriel identique à celle utilisée par un autre utilisateur.', + 'COPPA' => 'COPPA', + 'COPPA_FAX' => 'Numéro de télécopie de la COPPA', + 'COPPA_MAIL' => 'Adresse de courriel de la COPPA', + 'COPPA_MAIL_EXPLAIN' => 'L’adresse de courriel qui recevra les formulaires d’inscription de la COPPA que les tuteurs légaux devront envoyer.', + 'ENABLE_COPPA' => 'Activer la COPPA', + 'ENABLE_COPPA_EXPLAIN' => 'Les utilisateurs devront déclarer qu’ils ont 13 ans ou plus afin de se conformer à la COPPA, loi des États-Unis d’Amérique visant à protéger la vie privée des enfants sur internet. Si cette option est désactivée, les groupes d’utilisateurs liés à la COPPA ne seront pas affichés.', + 'MAX_CHARS' => 'max', + 'MIN_CHARS' => 'min', + 'NO_AUTH_PLUGIN' => 'Aucun module d’extension d’authentification n’a été trouvé.', + 'PASSWORD_LENGTH' => 'Longueur des mots de passe', + 'PASSWORD_LENGTH_EXPLAIN' => 'Le nombre de caractères minimaux et maximaux qui doivent être saisis dans les mots de passe.', + 'REG_LIMIT' => 'Nombre maximal de tentatives d’inscription par session', + 'REG_LIMIT_EXPLAIN' => 'Le nombre maximal de tentatives d’inscription autorisé à partir d’une session avant que cette dernière expire et soit soumise à la saisie d’un code issu d’une mesure de lutte contre les robots indésirables.', + 'USERNAME_ALPHA_ONLY' => 'Lettres (sans accent) et chiffres uniquement', + 'USERNAME_ALPHA_SPACERS' => 'Lettres (sans accent), chiffres et séparateurs uniquement', + 'USERNAME_ASCII' => 'ASCII (aucun caractère international)', + 'USERNAME_LETTER_NUM' => 'Lettres et chiffres', + 'USERNAME_LETTER_NUM_SPACERS' => 'Lettres, chiffres et séparateurs', + 'USERNAME_CHARS' => 'Restriction du contenu des noms d’utilisateurs', + 'USERNAME_CHARS_ANY' => 'Tout contenu', + 'USERNAME_CHARS_EXPLAIN' => 'Le contenu des noms d’utilisateurs. Les séparateurs comprennent les espaces, les tirets bas, les crochets et les signes plus et moins.', + 'USERNAME_LENGTH' => 'Longueur des noms d’utilisateurs', + 'USERNAME_LENGTH_EXPLAIN' => 'Le nombre de caractères minimaux et maximaux qui doivent être saisis dans les noms d’utilisateurs.', +]); + +// Feeds +$lang = array_merge($lang, [ + 'ACP_FEED_MANAGEMENT' => 'Paramètres généraux des flux de syndication', + 'ACP_FEED_MANAGEMENT_EXPLAIN' => 'Ce module met à disponibilité de multiples flux ATOM en décomposant les balises BBCodes contenues dans les messages afin de les rendre lisibles dans les flux externes.', + + 'ACP_FEED_GENERAL' => 'Paramètres généraux des flux', + 'ACP_FEED_POST_BASED' => 'Paramètres des flux de messages', + 'ACP_FEED_TOPIC_BASED' => 'Paramètres des flux de sujets', + 'ACP_FEED_SETTINGS_OTHER' => 'Autres paramètres de flux', + + 'ACP_FEED_ENABLE' => 'Activer les flux', + 'ACP_FEED_ENABLE_EXPLAIN' => 'Active ou désactive les flux ATOM dans la totalité du forum.
Si cette option est désactivée, tous les flux seront désactivés, sans prendre en compte les paramètres ci-dessous.', + 'ACP_FEED_LIMIT' => 'Nombre d’éléments', + 'ACP_FEED_LIMIT_EXPLAIN' => 'Le nombre maximal d’éléments à afficher dans les flux.', + + 'ACP_FEED_OVERALL' => 'Activer le flux général', + 'ACP_FEED_OVERALL_EXPLAIN' => 'Le flux général affiche tous les messages de l’ensemble du forum.', + 'ACP_FEED_FORUM' => 'Activer les flux par forum', + 'ACP_FEED_FORUM_EXPLAIN' => 'Le flux par forum affiche tous les messages d’un forum et de ses sous-forums.', + 'ACP_FEED_TOPIC' => 'Activer les flux par sujet', + 'ACP_FEED_TOPIC_EXPLAIN' => 'Le flux par sujet affiche tous les messages d’un sujet.', + + 'ACP_FEED_TOPICS_NEW' => 'Activer le flux des nouveaux sujets', + 'ACP_FEED_TOPICS_NEW_EXPLAIN' => 'Le flux des nouveaux sujets affiche tous les derniers sujets, en incluant leur premier message.', + 'ACP_FEED_TOPICS_ACTIVE' => 'Activer le flux des sujets actifs', + 'ACP_FEED_TOPICS_ACTIVE_EXPLAIN' => 'Le flux des sujet actifs affiche tous les sujets actifs, en incluant leur premier message.', + 'ACP_FEED_NEWS' => 'Flux des nouveautés', + 'ACP_FEED_NEWS_EXPLAIN' => 'Le flux des nouveautés affiche le dernier message des forums sélectionnés. Si vous ne souhaitez pas activer ce flux, ne sélectionnez aucun forum.
Vous pouvez sélectionner ou désélectionner plusieurs forums en maintenant appuyé la touche « CTRL » du clavier de votre ordinateur et en cliquant.', + + 'ACP_FEED_OVERALL_FORUMS' => 'Activer le flux des forums', + 'ACP_FEED_OVERALL_FORUMS_EXPLAIN' => 'Le flux des forums affiche une liste de tous les forums.', + + 'ACP_FEED_HTTP_AUTH' => 'Authentification HTTP', + 'ACP_FEED_HTTP_AUTH_EXPLAIN' => 'L’authentification HTTP permet aux utilisateurs de recevoir un contenu caché aux invités en ajoutant « auth=http » à l’adresse du flux. Veuillez noter que pour fonctionner correctement, certaines installations de PHP peuvent nécessiter des modifications additionnelles dans le fichier « .htaccess ».', + 'ACP_FEED_ITEM_STATISTICS' => 'Statistiques des éléments', + 'ACP_FEED_ITEM_STATISTICS_EXPLAIN' => 'Les statistiques individuelles sont affichées sous les articles du flux.
Les statistiques affichent l’auteur de la publication, la date et l’heure de la publication, le nombre de réponses, le nombre de vues, etc.', + 'ACP_FEED_EXCLUDE_ID' => 'Ignorer ces forums', + 'ACP_FEED_EXCLUDE_ID_EXPLAIN' => 'Le contenu de ces forums ne sera pas affiché dans les flux. Si vous souhaitez extraire les données de tous les forums, ne sélectionnez aucun forum.
Vous pouvez sélectionner ou désélectionner plusieurs forums en maintenant appuyé la touche « CTRL » du clavier de votre ordinateur et en cliquant.', +]); + +// Visual Confirmation Settings +$lang = array_merge($lang, [ + 'ACP_VC_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez sélectionner et configurer tous les modules d’extension qui permettent de se prémunir contre les soumissions automatisées et intensives effectuées par des robots malveillants. Ces modules d’extension demandent en général aux utilisateur de résoudre un CAPTCHA, qui est un outil permettant de différencier les humains aux robots.', + 'ACP_VC_EXT_GET_MORE' => 'Vous pouvez obtenir d’autres modules d’extension contre les soumissions automatisées et intensives, qui seront probablement plus efficaces, en visitant la base de données des extensions (en anglais). Pour plus d’informations concernant la prévention des soumissions automatisées et intensives sur votre forum, veuillez visiter la base de connaissances (en anglais).', + 'AVAILABLE_CAPTCHAS' => 'Modules d’extension disponibles', + 'CAPTCHA_UNAVAILABLE' => 'Le module d’extension ne peut pas être sélectionné tant que ses prérequis ne sont pas respectés.', + 'CAPTCHA_GD' => 'Bruit « GD » de l’image', + 'CAPTCHA_GD_3D' => 'Bruit « GD » de l’image en 3D', + 'CAPTCHA_GD_FOREGROUND_NOISE' => 'Bruit de premier plan', + 'CAPTCHA_GD_EXPLAIN' => 'Utilise la bibliothèque graphique « GD » afin de rendre l’image plus difficilement déchiffrable, permettant de se prémunir contre les soumissions automatisées et intensives effectuées par des robots malveillants.', + 'CAPTCHA_GD_FOREGROUND_NOISE_EXPLAIN' => 'Utilise un bruit de premier plan afin de rendre l’image plus difficilement déchiffrable.', + 'CAPTCHA_GD_X_GRID' => 'Bruit de fond sur l’axe des abscisses', + 'CAPTCHA_GD_X_GRID_EXPLAIN' => 'Utilisez ici des valeurs faibles afin de rendre l’image plus difficilement déchiffrable. Réglez cette valeur sur « 0 » afin de désactiver le bruit de fond sur l’axe des abscisses.', + 'CAPTCHA_GD_Y_GRID' => 'Bruit de fond sur l’axe des ordonnées', + 'CAPTCHA_GD_Y_GRID_EXPLAIN' => 'Utilisez ici des valeurs faibles afin de rendre l’image plus difficilement déchiffrable. Réglez cette valeur sur « 0 » afin de désactiver le bruit de fond sur l’axe des ordonnées.', + 'CAPTCHA_GD_WAVE' => 'Distorsion en forme de vagues', + 'CAPTCHA_GD_WAVE_EXPLAIN' => 'Cela appliquera sur l’image une distorsion en forme de vagues.', + 'CAPTCHA_GD_3D_NOISE' => 'Ajouter des éléments de bruit en 3D', + 'CAPTCHA_GD_3D_NOISE_EXPLAIN' => 'Cela ajoutera sur l’image des éléments supplémentaires, par-dessus les lettres.', + 'CAPTCHA_GD_FONTS' => 'Utiliser des polices de caractères différentes', + 'CAPTCHA_GD_FONTS_EXPLAIN' => 'Cette option contrôle le nombre de différentes formes de lettres qui seront utilisées. Vous pouvez seulement utiliser les formes présentes par défaut ou introduire de nouvelles lettres. Il est également possible d’ajouter des lettres en minuscule.', + 'CAPTCHA_FONT_DEFAULT' => 'Par défaut', + 'CAPTCHA_FONT_NEW' => 'Nouvelles formes', + 'CAPTCHA_FONT_LOWER' => 'Utiliser également des lettres en minuscule', + 'CAPTCHA_NO_GD' => 'Image simple', + 'CAPTCHA_PREVIEW_MSG' => 'Vos modifications n’ont pas été enregistrées, ceci n’est qu’une prévisualisation.', + 'CAPTCHA_PREVIEW_EXPLAIN' => 'Le module d’extension tel qu’il ressemblera lors de son utilisation.', + + 'CAPTCHA_SELECT' => 'Modules d’extension installés', + 'CAPTCHA_SELECT_EXPLAIN' => 'La liste déroulante contient les modules d’extension reconnus par le forum. Les éléments grisés correspondent aux modules d’extension qui ne sont pas encore disponibles et qui peuvent nécessiter une configuration avant leur utilisation.', + 'CAPTCHA_CONFIGURE' => 'Configurer les modules d’extension', + 'CAPTCHA_CONFIGURE_EXPLAIN' => 'Vous permet de configurer le module d’extension sélectionné dans la liste déroulante.', + 'CONFIGURE' => 'Configurer', + 'CAPTCHA_NO_OPTIONS' => 'Ce module d’extension n’a aucune option de configuration.', + + 'VISUAL_CONFIRM_POST' => 'Activer les mesures de lutte contre les soumissions automatisées et intensives aux invités', + 'VISUAL_CONFIRM_POST_EXPLAIN' => 'Les utilisateurs qui ne sont pas connectés seront invités à compléter des mesures permettant de se prémunir contre les publications automatisées et intensives effectuées par des robots malveillants.', + 'VISUAL_CONFIRM_REG' => 'Activer les mesures de lutte contre les soumissions automatisées et intensives lors des inscriptions', + 'VISUAL_CONFIRM_REG_EXPLAIN' => 'Les utilisateurs seront invités à compléter des mesures permettant de se prémunir contre les soumissions automatisées et intensives effectuées par des robots malveillants lors de leurs inscriptions.', + 'VISUAL_CONFIRM_REFRESH' => 'Autoriser les utilisateurs à rafraîchir les mesures de lutte contre les soumissions automatisées et intensives', + 'VISUAL_CONFIRM_REFRESH_EXPLAIN' => 'Les utilisateurs, s’ils sont incapables de déchiffrer la mesure actuelle, pourront demander une nouvelle mesure de lutte contre les soumissions automatisées et intensives effectuées par des robots malveillants. Certains modules d’extension peuvent ne pas prendre en charge cette fonctionnalité.', +]); + +// Cookie Settings +$lang = array_merge($lang, [ + 'ACP_COOKIE_SETTINGS_EXPLAIN' => 'Les informations contenues dans cette page permettent de définir les données qui seront utilisées afin d’envoyer des cookies au navigateur internet de vos utilisateurs. Dans la plupart des cas, les valeurs par défaut sont suffisantes. Si vous avez besoin de les modifier, faites-le avec précaution, une mauvaise configuration peut empêcher les utilisateurs de se connecter à votre forum. Si vous rencontrez des problèmes d’utilisateurs qui restent constamment connectés à votre forum, veuillez consulter l’article « Corriger des paramètres incorrects de cookies » (en anglais) de notre base de connaissances.', + + 'COOKIE_DOMAIN' => 'Domaine du cookie', + 'COOKIE_DOMAIN_EXPLAIN' => 'Dans la plupart des cas, le domaine du cookie est facultatif. Laissez ce champ vide si vous avez des doutes.

Cependant, dans le cas où votre forum est intégré à un autre logiciel ou est hébergé sur plusieurs domaines, vous devrez alors déterminer le domaine du cookie. Si votre domaine ressemble à « exemple.com », « forums.exemple.com », « forums.exemple.com » ou encore « blog.exemple.com », supprimez les sous-domaines pour ne conserver que le domaine commun, tel que « exemple.com ». Il vous suffit alors d’ajouter un point avant ce domaine commun, et le domaine du cookie ressemblera alors à « .exemple.com » (notez le point au début).', + 'COOKIE_NAME' => 'Nom du cookie', + 'COOKIE_NAME_EXPLAIN' => 'Le choix du nom est entièrement libre, essayez d’être original. À chaque fois que les paramètres du cookie sont modifiés, il est recommandé de modifier également le nom du cookie.', + 'COOKIE_NOTICE' => 'Information sur les cookies', + 'COOKIE_NOTICE_EXPLAIN' => 'Si cette option est activée, une information concernant l’utilisation des cookies sera affichée aux utilisateurs lors de leur navigation sur votre forum. Cette information est une obligation légale dans certains pays, selon le contenu et les extensions activées sur votre forum.', + 'COOKIE_PATH' => 'Chemin du cookie', + 'COOKIE_PATH_EXPLAIN' => 'Le chemin est généralement le même que celui menant vers le script ou est simplement une barre oblique, ce qui permet au cookie d’être accessible sur l’ensemble du domaine du site.', + 'COOKIE_SECURE' => 'Cookie sécurisé', + 'COOKIE_SECURE_EXPLAIN' => 'Si votre serveur est sécurisé avec un certificat SSL, vous devez activer cette option. Si vous activez cette option alors que votre serveur n’est pas sécurisé avec un certificat SSL, il est probable que des erreurs surviennent lors des redirections.', + 'ONLINE_LENGTH' => 'Durée d’apparition dans la liste des utilisateurs en ligne', + 'ONLINE_LENGTH_EXPLAIN' => 'Le nombre de minutes qui s’écouleront avant que les utilisateurs inactifs n’apparaissent plus dans la liste des utilisateurs en ligne. Plus la valeur est élevée, plus le temps de génération de la liste sera long.', + 'SESSION_LENGTH' => 'Durée des sessions', + 'SESSION_LENGTH_EXPLAIN' => 'Le nombre de secondes qui s’écouleront avant que les sessions expirent.', +]); + +// Contact Settings +$lang = array_merge($lang, [ + 'ACP_CONTACT_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez activer, modifier et désactiver la page de contact.', + + 'CONTACT_US_ENABLE' => 'Activer la page de contact', + 'CONTACT_US_ENABLE_EXPLAIN' => 'Cette page permet aux utilisateurs d’envoyer des courriels aux administrateurs du forum. Veuillez noter que le paramètre de l’envoi des courriels sur le forum doit être activé. Vous trouverez ce paramètre dans « Général > Communication > Paramètres des courriels ».', + + 'CONTACT_US_INFO' => 'Informations de la page de contact', + 'CONTACT_US_INFO_EXPLAIN' => 'Le message est affiché sur la page de contact', + 'CONTACT_US_INFO_PREVIEW' => 'Prévisualisation des informations de la page de contact', + 'CONTACT_US_INFO_UPDATED' => 'Les informations de la page de contact ont été mises à jour.', +]); + +// Load Settings +$lang = array_merge($lang, [ + 'ACP_LOAD_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez activer et désactiver certaines fonctionnalités du forum qui pourront réduire la quantité de processus actifs. Sur la plupart des serveurs, il n’est pas nécessaire de désactiver ces fonctionnalités. Cependant, sur certains systèmes d’exploitation ou offres d’hébergements mutualisés, il est préférable de désactiver certaines fonctionnalités dont vous n’avez pas réellement besoin. Vous pouvez également limiter certaines options relatives à la charge du système et aux sessions actives qui désactiveront automatiquement votre forum si les valeurs sont dépassées.', + + 'ALLOW_CDN' => 'Autoriser l’utilisation de contenu hébergé sur des serveurs externes', + 'ALLOW_CDN_EXPLAIN' => 'Si cette option est activée, certains fichiers seront distribués par des serveurs externes au lieu de votre serveur. Cette fonctionnalité réduira la bande passante de votre serveur mais peut poser des problèmes de confidentialité pour les administrateurs soucieux de l’anonymat du contenu de leur forum. Dans une installation par défaut, la librairie « jQuery » et la police de caractères « Open Sans » sont distribuées par le réseau de distribution de contenu de Google.', + 'ALLOW_LIVE_SEARCHES' => 'Autoriser les prédictions de recherche', + 'ALLOW_LIVE_SEARCHES_EXPLAIN' => 'Si cette option est activée, les utilisateurs bénéficieront de suggestions de mots-clés lors de la rédaction de texte dans certains champs du forum.', + 'CUSTOM_PROFILE_FIELDS' => 'Champs de profil personnalisés', + 'LIMIT_LOAD' => 'Limite de la charge du système', + 'LIMIT_LOAD_EXPLAIN' => 'Si la charge du système dépasse cette valeur durant une minute, le forum sera automatiquement désactivé. Une valeur de « 1.0 » correspond à 100 % de l’utilisation des processus d’un processeur. Cette fonctionnalité ne fonctionne que sur les serveurs basés sous UNIX et dans lesquels cette option est accessible. Cette valeur se réinitialise sur « 0 » si le logiciel n’arrive pas à charger la limite de la charge du système.', + 'LIMIT_SESSIONS' => 'Limiter le nombre de sessions', + 'LIMIT_SESSIONS_EXPLAIN' => 'Si le nombre de sessions dépasse cette valeur durant une minute, le forum sera automatiquement désactivé. Réglez cette valeur sur « 0 » si vous ne souhaitez pas limiter le nombre de sessions.', + 'LOAD_CPF_MEMBERLIST' => 'Afficher les champs de profil personnalisés dans la liste des membres', + 'LOAD_CPF_PM' => 'Afficher les champs de profil personnalisés dans les messages privés', + 'LOAD_CPF_VIEWPROFILE' => 'Afficher les champs de profil personnalisés dans le profil des utilisateurs', + 'LOAD_CPF_VIEWTOPIC' => 'Afficher les champs de profil personnalisés dans les pages de sujets', + 'LOAD_USER_ACTIVITY' => 'Afficher l’activité des utilisateurs', + 'LOAD_USER_ACTIVITY_EXPLAIN' => 'La liste des sujets et des forums où les utilisateurs sont actifs sera affichée sur leur profil et sur le panneau de contrôle de l’utilisateur. Il est recommandé de désactiver cette fonctionnalité sur les forums comportant plus d’un million de messages.', + 'LOAD_USER_ACTIVITY_LIMIT' => 'Limite de messages pour l’activité des utilisateurs', + 'LOAD_USER_ACTIVITY_LIMIT_EXPLAIN' => 'Les forums et les sujets où les utilisateurs sont actifs ne seront pas affichés aux utilisateurs qui ont publiés plus que ce nombre de messages. Réglez cette valeur sur « 0 » afin de désactiver ce comportement.', + 'READ_NOTIFICATION_EXPIRE_DAYS' => 'Délai d’expiration des notifications consultées', + 'READ_NOTIFICATION_EXPIRE_DAYS_EXPLAIN' => 'Le nombre de jours qui s’écouleront avant que les notifications consultées soient supprimées. Réglez cette valeur sur « 0 » si vous ne souhaitez pas limiter la durée des notifications consultées.', + 'RECOMPILE_STYLES' => 'Recompiler les composants obsolètes des styles', + 'RECOMPILE_STYLES_EXPLAIN' => 'Les composants obsolètes des styles installés sur votre forum seront vérifiés, mis à jour et recompilés.', + 'YES_ACCURATE_PM_BUTTON' => 'Activer l’affichage spécifique du bouton relatif aux messages privés', + 'YES_ACCURATE_PM_BUTTON_EXPLAIN' => 'Si cette option est activée, le bouton relatif aux messages privés ne sera affiché que sur les profils des utilisateurs autorisés à lire les messages privés.', + 'YES_ANON_READ_MARKING' => 'Activer l’indicateur de lecture des sujets pour les invités', + 'YES_ANON_READ_MARKING_EXPLAIN' => 'Les sujets pourront apparaître comme non lus pour les invités. Si cette option est désactivée, les messages apparaîtront toujours comme lus pour les invités.', + 'YES_BIRTHDAYS' => 'Afficher les anniversaires', + 'YES_BIRTHDAYS_EXPLAIN' => 'Si cette option est désactivée, les anniversaires ne seront plus affichés. Les anniversaires doivent préalablement être activés afin de pouvoir être affichés.', + 'YES_JUMPBOX' => 'Afficher la boîte d’accès rapide aux forums', + 'YES_MODERATORS' => 'Afficher les modérateurs', + 'YES_ONLINE' => 'Afficher la liste des utilisateurs en ligne', + 'YES_ONLINE_EXPLAIN' => 'La liste des utilisateurs en ligne parcourant l’index, les forums et les pages de sujets du forum.', + 'YES_ONLINE_GUESTS' => 'Afficher les invités dans la liste des utilisateurs en ligne', + 'YES_ONLINE_GUESTS_EXPLAIN' => 'Les invités apparaîtront dans la liste des utilisateurs en ligne parcourant le forum.', + 'YES_ONLINE_TRACK' => 'Afficher le statut de connexion des utilisateurs', + 'YES_ONLINE_TRACK_EXPLAIN' => 'Les utilisateurs seront, selon le statut de leur connexion sur le forum, affichés comme étant en ligne ou hors-ligne dans leur profil et sur les pages de sujets.', + 'YES_POST_MARKING' => 'Activer les sujets pointés', + 'YES_POST_MARKING_EXPLAIN' => 'Les sujets pointés indiquent aux utilisateurs s’ils ont publié un message dans un sujet.', + 'YES_READ_MARKING' => 'Activer l’indicateur de lecture par le serveur', + 'YES_READ_MARKING_EXPLAIN' => 'Les sujets seront marqués comme lus ou non lus dans la base de données de votre serveur au lieu de stocker cette information dans un cookie.', + 'YES_UNREAD_SEARCH' => 'Activer la recherche des messages non lus', +]); + +// Auth settings +$lang = array_merge($lang, [ + 'ACP_AUTH_SETTINGS_EXPLAIN' => 'Les modules d’extension d’authentification vous permettent de déterminer la manière dont les utilisateurs s’authentifient lors de leur connexion au forum. Quatre modules sont disponibles par défaut : la base de données (DB), LDAP, Apache et OAuth. Toutes les méthodes d’authentification ne nécessitent pas forcément d’informations complémentaires, ne remplissez que les champs utiles à la méthode sélectionnée.', + + 'AUTH_METHOD' => 'Sélectionner une méthode d’authentification', + + 'AUTH_PROVIDER_OAUTH_ERROR_ELEMENT_MISSING' => 'Vous devez renseigner la clé et le secret pour chaque fournisseur de service « OAuth ». Seule une de ces données a été renseignée dans le cas d’un fournisseur de service OAuth.', + 'AUTH_PROVIDER_OAUTH_EXPLAIN' => 'Chaque fournisseur de service OAuth doit être renseigné par une clé et un secret unique afin de pouvoir se faire authentifier par le serveur externe. Ces données sont renseignées par le service OAuth lors de l’inscription de votre site internet à leurs services et doivent être saisies telles quelles.
Chaque service qui n’est pas renseigné de ces deux données ne sera pas accessible aux utilisateurs du forum. Veuillez noter que les utilisateurs pourront toujours s’inscrire et se connecter en utilisant le module d’extension d’authentification de la base de données.', + 'AUTH_PROVIDER_OAUTH_KEY' => 'Clé', + 'AUTH_PROVIDER_OAUTH_TITLE' => 'OAuth', + 'AUTH_PROVIDER_OAUTH_SECRET' => 'Secret', + + 'APACHE_SETUP_BEFORE_USE' => 'Vous devez configurer l’authentification par Apache avant de pouvoir faire fonctionner phpBB sous cette méthode d’authentification. Le nom d’utilisateur que vous utilisez afin de vous authentifier par Apache doit être identique à votre nom d’utilisateur sur le forum. L’authentification par Apache ne peut être utilisé que si « mod_php » (qui ne doit pas être avec une version CGI) et « safe_mode » sont désactivés.', + + 'LDAP' => 'LDAP', + 'LDAP_DN' => 'Base LDAP vers « dn »', + 'LDAP_DN_EXPLAIN' => 'Le nom absolu menant aux informations des utilisateurs, tel que « o=MaCompagnie,c=FR ».', + 'LDAP_EMAIL' => 'Attribut LDAP des adresses de courriel', + 'LDAP_EMAIL_EXPLAIN' => 'Le nom de l’attribut de l’adresse de courriel de vos utilisateurs, s’il existe, qui permet de compléter automatiquement l’adresse de courriel de tous vos nouveaux utilisateurs. Ne remplissez pas ce champ si vous souhaitez que le champ correspondant à l’adresse de courriel soit vide pour les utilisateurs qui se connectent pour la première fois.', + 'LDAP_INCORRECT_USER_PASSWORD' => 'La connexion au serveur LDAP a échoué car le nom d’utilisateur ou le mot de passe est incorrect.', + 'LDAP_NO_EMAIL' => 'L’attribut de l’adresse de courriel que vous avez spécifié est introuvable.', + 'LDAP_NO_IDENTITY' => 'L’identifiant de connexion concernant %s est introuvable.', + 'LDAP_PASSWORD' => 'Mot de passe LDAP', + 'LDAP_PASSWORD_EXPLAIN' => 'Si vous souhaitez utiliser une connexion anonyme, ne remplissez pas ce champ. Dans le cas contraire, vous pouvez indiquer le mot de passe lié à l’utilisateur que vous avez spécifié ci-dessus. Cette information est obligatoire pour les serveurs aux répertoires dits actifs.
Attention : ce mot de passe sera archivé en texte brut dans la base de données et sera donc visible à tous ceux qui ont accès à votre base de données et à cette page de configuration.', + 'LDAP_PORT' => 'Port du serveur LDAP', + 'LDAP_PORT_EXPLAIN' => 'Si vous le souhaitez, vous pouvez saisir un port différent au port 389 qui est utilisé par défaut.', + 'LDAP_SERVER' => 'Nom du serveur LDAP', + 'LDAP_SERVER_EXPLAIN' => 'Si vous utilisez LDAP, ce champ devra comporter le nom d’hôte ou l’adresse IP du serveur LDAP. Vous pouvez également saisir un lien, tel que « ldap://nomdhote:port/ ».', + 'LDAP_UID' => 'Clé « uid » de LDAP', + 'LDAP_UID_EXPLAIN' => 'La clé avec laquelle vous pouvez rechercher un identifiant de connexion, tel que « uid », « sn », etc.', + 'LDAP_USER' => 'Utilisateur « dn » de LDAP', + 'LDAP_USER_EXPLAIN' => 'Si vous souhaitez utiliser une connexion anonyme, ne remplissez pas ce champ. Dans le cas contraire, si cela a été renseigné dans phpBB, vous pouvez utiliser le nom absolu que vous avez spécifié lors de vos tentatives de connexion pour retrouver le bon utilisateur, tel que « uid=Nom,ou=MonUnité,o=MaCompagnie,c=FR ». Cette information est obligatoire pour les serveurs aux répertoires dits actifs.', + 'LDAP_USER_FILTER' => 'Filtre de l’utilisateur LDAP', + 'LDAP_USER_FILTER_EXPLAIN' => 'Si vous le souhaitez, vous pouvez limiter les éléments recherchés grâce à des filtres additionnels. Par exemple, « objectClass=posixGroup » sera utilisé en tant que « (&(uid=$username)(objectClass=posixGroup)) ».', +]); + +// Server Settings +$lang = array_merge($lang, [ + 'ACP_SERVER_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez configurer les paramètres relatifs au serveur et au domaine. Veuillez vous assurer de la validité des données présentes ci-dessous car vos courriels pourraient contenir des informations erronées si des données invalides étaient présentes. Lorsque vous saisissez le nom de domaine, n’oubliez pas d’inclure le protocole, tel que « http:// » ou « https:// ». Ne modifiez le port que si votre serveur fonctionne sous un port différent dont vous avez connaissance, mais sachez néanmoins que le port 80 convient dans la plupart des cas.', + + 'ENABLE_GZIP' => 'Activer la compression GZip', + 'ENABLE_GZIP_EXPLAIN' => 'Le contenu généré sera compressé avant d’être envoyé aux utilisateurs. Cela peut réduire le trafic du réseau mais augmentera en contrepartie l’utilisation du processeur de votre serveur et de vos utilisateurs. L’extension PHP « zlib » doit être chargée afin que la compression fonctionne.', + 'FORCE_SERVER_VARS' => 'Forcer les paramètres de la redirection du serveur', + 'FORCE_SERVER_VARS_EXPLAIN' => 'Si cette option est activée, les paramètres de la redirection du serveur spécifiés ci-dessous seront utilisés à la place des valeurs déterminées automatiquement.', + 'ICONS_PATH' => 'Chemin vers le répertoire de stockage des icônes de message', + 'ICONS_PATH_EXPLAIN' => 'Le chemin relatif à la racine du répertoire de votre forum, tel que « images/icons ».', + 'MOD_REWRITE_ENABLE' => 'Activer la réécriture des liens', + 'MOD_REWRITE_ENABLE_EXPLAIN' => 'Si cette option est activée, les liens contenant « app.php » seront réécrits afin de supprimer le nom du fichier (par exemple, « app.php/foo » deviendra « /foo »). Le module « mod_rewrite » du serveur Apache doit être activé afin que la réécriture fonctionne correctement. Si cette option est activée alors que « mod_rewrite » n’est pas activé, les liens de votre forum peuvent ne plus fonctionner.', + 'MOD_REWRITE_DISABLED' => 'Le module « mod_rewrite » de votre serveur Apache est désactivé. Si vous souhaitez activer cette fonctionnalité, vous devez activer ce module ou demander à votre hébergeur internet de le faire.', + 'MOD_REWRITE_INFORMATION_UNAVAILABLE' => 'Nous sommes dans l’incapacité à déterminer si votre serveur est compatible avec cette fonctionnalité. Vous pouvez essayer d’activer la réecriture des liens, mais si votre serveur n’est pas compatible avec cette dernière, les chemins générés par le forum peuvent ne plus fonctionner correctement. Veuillez contacter votre hébergeur internet afin de lui demander si vous pouvez activer en toute sécurité cette fonctionnalité.', + 'PATH_SETTINGS' => 'Paramètres du chemin', + 'RANKS_PATH' => 'Chemin vers le répertoire de stockage des images de rangs', + 'RANKS_PATH_EXPLAIN' => 'Le chemin relatif à la racine du répertoire de votre forum, tel que « images/ranks ».', + 'SCRIPT_PATH' => 'Chemin du forum', + 'SCRIPT_PATH_EXPLAIN' => 'Le chemin menant à votre forum par rapport au nom de domaine, tel que « /phpBB3 ».', + 'SERVER_NAME' => 'Nom de domaine', + 'SERVER_NAME_EXPLAIN' => 'Le nom de domaine qui est utilisé sur votre serveur, tel que « www.exemple.com ».', + 'SERVER_PORT' => 'Port du serveur', + 'SERVER_PORT_EXPLAIN' => 'Le port qui sert à vous connecter à votre serveur, généralement le 80.', + 'SERVER_PROTOCOL' => 'Protocole du serveur', + 'SERVER_PROTOCOL_EXPLAIN' => 'Le protocole du serveur qui sera utilisé si les paramètres de la redirection du serveur sont forcés. Si ces derniers ne sont pas forcés ou que ce champ est vide, le protocole sera alors déterminé selon le paramètre du cookie sécurisé, qui choisira entre utiliser « http:// » ou « https:// ».', + 'SERVER_URL_SETTINGS' => 'Paramètres de la redirection du serveur', + 'SMILIES_PATH' => 'Chemin vers le répertoire de stockage des émoticônes', + 'SMILIES_PATH_EXPLAIN' => 'Le chemin relatif à la racine du répertoire de votre forum, tel que « images/smilies ».', + 'UPLOAD_ICONS_PATH' => 'Chemin vers le répertoire de stockage des icônes de groupes d’extensions', + 'UPLOAD_ICONS_PATH_EXPLAIN' => 'Le chemin relatif à la racine du répertoire de votre forum, tel que « images/upload_icons ».', + 'USE_SYSTEM_CRON' => 'Exécuter les tâches périodiques à partir du service « cron »', + 'USE_SYSTEM_CRON_EXPLAIN' => 'Si cette option est désactivée, phpBB s’assurera que les tâches périodiques soient exécutées automatiquement. Si cette option est activée, phpBB ne planifiera aucune tâche périodique de lui-même. Un administrateur système devra alors s’assurer de faire exécuter « bin/phpbbcli.php cron:run » à des intervalles réguliers (toutes les 5 minutes par exemple) par le programme « cron ».', +]); + +// Security Settings +$lang = array_merge($lang, [ + 'ACP_SECURITY_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez configurer tous les paramètres relatifs aux sessions et aux connexions.', + + 'ALL' => 'Tout', + 'ALLOW_AUTOLOGIN' => 'Activer la fonctionnalité « Se souvenir de moi »', + 'ALLOW_AUTOLOGIN_EXPLAIN' => 'Les utilisateurs pourront se connecter automatiquement lors de leurs visites sur le forum.', + 'ALLOW_PASSWORD_RESET' => 'Autoriser la réinitialisation des mots de passe (« J’ai oublié mon mot de passe »)', + 'ALLOW_PASSWORD_RESET_EXPLAIN' => 'Les utilisateurs pourront utiliser le lien « J’ai oublié mon mot de passe » situé sur la page de connexion afin de réinitialiser leur mot de passe et recommencer à se connecter sur le forum. Si vous utilisez un système d’authentification externe, vous devriez désactiver cette fonctionnalité.', + 'AUTOLOGIN_LENGTH' => 'Délai d’expiration des clés de connexions automatiques', + 'AUTOLOGIN_LENGTH_EXPLAIN' => 'Le nombre de jours qui s’écouleront avant que les clés de connexions automatiques soient supprimées. Réglez cette valeur sur « 0 » si vous ne souhaitez pas limiter la durée des clés de connexions automatiques.', + 'BROWSER_VALID' => 'Valider les navigateurs internet', + 'BROWSER_VALID_EXPLAIN' => 'Les navigateurs internet seront validés lors de chaque session, ce qui améliore la sécurité.', + 'CHECK_DNSBL' => 'Vérifier les adresses IP sur les listes des systèmes de noms de domaine', + 'CHECK_DNSBL_EXPLAIN' => 'Si cette option est activée, l’adresse IP des utilisateurs sera vérifiée par les services DNSBL SpamCop et Spamhaus (en anglais) lors des inscriptions et des publications de messages. Cette vérification peut durer un certain temps, selon la configuration du serveur sélectionné. Désactivez cette fonctionnalité si vous constatez des ralentissements ou des erreurs d’appréciation.', + 'CLASS_B' => 'A.B', + 'CLASS_C' => 'A.B.C', + 'EMAIL_CHECK_MX' => 'Vérifier l’enregistrement MX des adresses de courriel', + 'EMAIL_CHECK_MX_EXPLAIN' => 'Si cette option est activée, le domaine des adresses de courriel spécifiées lors des inscriptions et des modifications de profil sera vérifié afin de s’assurer de la validité de l’enregistrement MX.', + 'FORCE_PASS_CHANGE' => 'Forcer la modification du mot de passe', + 'FORCE_PASS_CHANGE_EXPLAIN' => 'Les utilisateurs devront modifier leur mot de passe après que le nombre de jours saisi dans ce champ ne soit écoulé. Réglez cette valeur sur « 0 » afin de désactiver ce comportement.', + 'FORM_TIME_MAX' => 'Durée maximale de l’envoi des formulaires', + 'FORM_TIME_MAX_EXPLAIN' => 'La durée maximale qu’un utilisateur ne doit pas dépasser afin d’envoyer un formulaire. Réglez cette valeur sur « -1 » afin de désactiver ce comportement. Veuillez noter qu’un formulaire peut devenir invalide si la session d’un utilisateur expire, mais cela ne dépend pas de cette option.', + 'FORM_SID_GUESTS' => 'Lier les formulaires aux sessions des invités', + 'FORM_SID_GUESTS_EXPLAIN' => 'Si cette option est activée, les formulaires émis aux invités seront exclusifs à leur session. Cela peut cependant entraîner quelques problèmes avec certains fournisseurs d’accès à internet.', + 'FORWARDED_FOR_VALID' => 'Valider l’en-tête « X_FORWARDED_FOR »', + 'FORWARDED_FOR_VALID_EXPLAIN' => 'Les sessions des utilisateurs n’expireront pas tant que l’en-tête « X_FORWARDED_FOR » envoyé équivaudra à l’en-tête envoyé précédemment. L’en-tête « X_FORWARDED_FOR » vérifiera également que les adresses IP n’ont pas été bannies entre chaque session.', + 'IP_VALID' => 'Valider les sessions', + 'IP_VALID_EXPLAIN' => 'Les parties des adresses IP des utilisateurs qui seront analysées afin de valider leur session. « Tout » analysera l’intégralité des adresses IP, « A.B.C » analysera les trois premières parties des adresses IP, « A.B » analysera les deux premières parties des adresses IP et « Rien » désactivera cette fonctionnalité. Concernant les adresses IPv6, « A.B.C » concerne les quatre premiers blocs et « A.B » concerne les trois premiers blocs.', + 'IP_LOGIN_LIMIT_MAX' => 'Nombre maximal de tentatives de connexion par adresse IP', + 'IP_LOGIN_LIMIT_MAX_EXPLAIN' => 'Le nombre de tentatives de connexion autorisées à partir d’une adresse IP unique avant que la saisie d’un code de confirmation de lutte contre les robots indésirables ne soit requise. Réglez cette valeur sur « 0 » afin de ne pas demander la saisie de ce code.', + 'IP_LOGIN_LIMIT_TIME' => 'Délai d’expiration des tentatives de connexion par adresse IP', + 'IP_LOGIN_LIMIT_TIME_EXPLAIN' => 'Le nombre de secondes qui s’écouleront avant que les tentatives de connexion expirent.', + 'IP_LOGIN_LIMIT_USE_FORWARDED' => 'Limiter les tentatives de connexion par en-tête « X_FORWARDED_FOR »', + 'IP_LOGIN_LIMIT_USE_FORWARDED_EXPLAIN' => 'Les tentatives de connexion seront limitées par les valeurs de « X_FORWARDED_FOR » au lieu d’être limitées par l’adresse IP.
Attention : vous ne devez activer cette option que si vous utilisez un serveur proxy qui règle correctement les valeurs de « X_FORWARDED_FOR ».', + 'MAX_LOGIN_ATTEMPTS' => 'Nombre maximal de tentatives de connexion par nom d’utilisateur', + 'MAX_LOGIN_ATTEMPTS_EXPLAIN' => 'Le nombre maximal de tentatives de connexion autorisé à partir d’un nom d’utilisateur avant que ce dernier soit soumis à la saisie d’un code issu d’une mesure de lutte contre les robots indésirables. Réglez cette valeur sur « 0 » afin de désactiver ce comportement.', + 'NO_IP_VALIDATION' => 'Rien', + 'NO_REF_VALIDATION' => 'Ne rien valider', + 'PASSWORD_TYPE' => 'Restriction de la complexité des mots de passe', + 'PASSWORD_TYPE_EXPLAIN' => 'La complexité des mots de passe des utilisateurs. Chaque option inclut les options précédentes.', + 'PASS_TYPE_ALPHA' => 'Doit contenir des lettres (sans accent) et des chiffres', + 'PASS_TYPE_ANY' => 'Aucune restriction', + 'PASS_TYPE_CASE' => 'Doit contenir des majuscules et des minuscules', + 'PASS_TYPE_SYMBOL' => 'Doit contenir des symboles', + 'REF_HOST' => 'Valider uniquement l’hôte', + 'REF_PATH' => 'Valider également le chemin', + 'REFERRER_VALID' => 'Valider le référant', + 'REFERRER_VALID_EXPLAIN' => 'Si cette option est activée, le référant des requêtes « POST » sera vérifié et pourra servir à valider l’hôte et le chemin du forum. Si votre forum est accessibles sur plusieurs domaines ou utilise des connexions externes, vous ne devez pas autoriser le référant à valider le chemin afin d’éviter tout conflit.', + 'TPL_ALLOW_PHP' => 'Autoriser le code PHP dans les modèles', + 'TPL_ALLOW_PHP_EXPLAIN' => 'Si cette option est activée, les instructions « PHP » et « INCLUDEPHP » seront reconnues et analysées dans les modèles.', + 'UPLOAD_CERT_VALID' => 'Valider les certificats transférés', + 'UPLOAD_CERT_VALID_EXPLAIN' => 'Si cette option est activée, les certificats des transferts à distance seront validés. Le paquet de l’autorité de certification devra être défini en configurant « openssl.cafile » ou « curl.cainfo » dans le fichier « php.ini ».', +]); + +// Email Settings +$lang = array_merge($lang, [ + 'ACP_EMAIL_SETTINGS_EXPLAIN' => 'Ces informations sont utilisées lors de l’envoi des courriels à vos utilisateurs. Veuillez vous assurer que l’adresse de courriel spécifiée est correcte car les messages refusés ou échoués seront probablement retournés à cette adresse. Si votre hébergeur ne propose pas de service PHP d’envoi de courriels, vous pouvez envoyer directement des messages en utilisant le protocole SMTP. Cela nécessite l’adresse d’un serveur approprié (si besoin, demandez cette information à votre hébergeur internet). Si le serveur exige une authentification (et seulement dans ce cas), saisissez le nom d’utilisateur, le mot de passe et la méthode d’authentification nécessaire.', + + 'ADMIN_EMAIL' => 'Adresse de courriel d’expédition', + 'ADMIN_EMAIL_EXPLAIN' => 'Cette adresse sera utilisée comme l’adresse d’expédition de tous les courriels. Cela correspond à l’adresse de courriel du contact technique. Elle sera toujours utilisée comme l’adresse de courriel de l’expéditeur dans tous les messages.', + 'BOARD_EMAIL_FORM' => 'Envoi des courriels depuis le forum', + 'BOARD_EMAIL_FORM_EXPLAIN' => 'Si cette option est activée, les utilisateurs pourront, au lieu d’afficher les adresses de courriel, envoyer des courriels depuis le forum.', + 'BOARD_HIDE_EMAILS' => 'Masquer les adresses de courriel', + 'BOARD_HIDE_EMAILS_EXPLAIN' => 'Les adresses de courriel peuvent être masquées afin qu’elles demeurent confidentielles.', + 'CONTACT_EMAIL' => 'Adresse de courriel de contact', + 'CONTACT_EMAIL_EXPLAIN' => 'Cette adresse sera utilisée lorsqu’un contact particulier est nécessaire, comme en cas de message indésirable, d’erreur survenue, etc. Elle sera toujours utilisée comme l’adresse du chemin de l’envoi et considérée comme celle du destinataire dans les courriels.', + 'CONTACT_EMAIL_NAME' => 'Nom du contact', + 'CONTACT_EMAIL_NAME_EXPLAIN' => 'Le nom du contact qui s’affichera aux destinataires des courriels. Laissez ce champ vide si vous ne souhaitez pas afficher de nom de contact.', + 'EMAIL_FORCE_SENDER' => 'Forcer l’adresse de courriel d’expédition', + 'EMAIL_FORCE_SENDER_EXPLAIN' => 'L’adresse de courriel d’expédition utilisera la fonction « Return-Path » au lieu d’utiliser l’utilisateur et le nom d’hôte local du serveur. Veuillez noter que ce paramètre est ignoré si vous utilisez le protocole SMTP.
Attention : vous devez vous assurer que le serveur soit ajouté en tant qu’utilisateur de confiance dans la configuration du service d’envoi de courriels.', + 'EMAIL_PACKAGE_SIZE' => 'Nombre de courriels envoyés en une seule fois', + 'EMAIL_PACKAGE_SIZE_EXPLAIN' => 'Le nombre de courriels envoyés en une seule fois. Ce paramètre est appliqué à la file d’attente de la messagerie électronique interne. Si vous rencontrez des difficultés lors de l’envoi de courriels alors que cette option est activée, nous vous conseillons de régler cette valeur sur « 0 ».', + 'EMAIL_SIG' => 'Signature des courriels', + 'EMAIL_SIG_EXPLAIN' => 'Ce texte sera inséré en bas de tous les courriels envoyés à partir du forum.', + 'ENABLE_EMAIL' => 'Envoi des courriels à partir du forum', + 'ENABLE_EMAIL_EXPLAIN' => 'Si cette option est désactivée, aucun courriel ne pourra être envoyé à partir du forum. Veuillez noter que cette option doit être activée afin que l’activation par l’utilisateur ou par un administrateur soit fonctionnelle. Si vous utilisez actuellement l’activation par l’utilisateur ou par un administrateur et que vous n’activez pas cette option, les inscriptions seront désactivées.', + 'SEND_TEST_EMAIL' => 'Envoyer un courriel de test', + 'SEND_TEST_EMAIL_EXPLAIN' => 'Cela enverra un courriel de test à l’adresse de courriel spécifiée dans votre compte.', + 'SMTP_ALLOW_SELF_SIGNED' => 'Autoriser les certificats SSL auto-signés', + 'SMTP_ALLOW_SELF_SIGNED_EXPLAIN' => 'Autoriser les connexions au serveur SMTP avec un certificat SSL auto-signé.
Attention : les certificats SSL auto-signés peuvent avoir des répercutions sur la sécurité.', + 'SMTP_AUTH_METHOD' => 'Méthode d’authentification du protocole SMTP', + 'SMTP_AUTH_METHOD_EXPLAIN' => 'Cette méthode n’est utilisée que si un nom d’utilisateur et un mot de passe SMTP ont été renseignés. Veuillez demander cette information à votre hébergeur internet si vous n’êtes pas certain de la méthode à utiliser.', + 'SMTP_CRAM_MD5' => 'CRAM-MD5', + 'SMTP_DIGEST_MD5' => 'DIGEST-MD5', + 'SMTP_LOGIN' => 'LOGIN', + 'SMTP_PASSWORD' => 'Mot de passe SMTP', + 'SMTP_PASSWORD_EXPLAIN' => 'Ne saisissez un mot de passe que si votre serveur SMTP le demande.
Attention : ce mot de passe sera stocké en texte brut dans la base de données et sera visible à tous ceux qui ont accès à votre base de données et à cette page de configuration.', + 'SMTP_PLAIN' => 'PLAIN', + 'SMTP_POP_BEFORE_SMTP' => 'POP-BEFORE-SMTP', + 'SMTP_PORT' => 'Port du serveur SMTP', + 'SMTP_PORT_EXPLAIN' => 'Ne modifiez ce dernier que si votre serveur SMTP utilise un port différent dont vous avez connaissance.', + 'SMTP_SERVER' => 'Adresse et protocole du serveur SMTP', + 'SMTP_SERVER_EXPLAIN' => 'Veuillez noter que vous devez renseigner le protocole utilisé par le serveur. Si vous utilisez SSL, cela ressemblera à « ssl://exemple.com ».', + 'SMTP_SETTINGS' => 'Paramètres du protocole SMTP', + 'SMTP_USERNAME' => 'Nom d’utilisateur SMTP', + 'SMTP_USERNAME_EXPLAIN' => 'Ne saisissez un nom d’utilisateur que si votre serveur SMTP le demande.', + 'SMTP_VERIFY_PEER' => 'Vérifier le certificat SSL', + 'SMTP_VERIFY_PEER_EXPLAIN' => 'Force la vérification du certificat SSL utilisé par les serveurs SMTP.
Attention : la connexion de pairs avec des certificats SSL qui ne sont pas vérifiés peut avoir des répercutions sur la sécurité.', + 'SMTP_VERIFY_PEER_NAME' => 'Vérifier le nom de pair SMTP', + 'SMTP_VERIFY_PEER_NAME_EXPLAIN' => 'Force la vérification du nom de pair des serveurs SMTP qui utilisent des connexions SSL/TLS.
Attention : la connexion à des pairs qui ne sont pas vérifiés peut avoir des répercutions sur la sécurité.', + 'TEST_EMAIL_SENT' => 'Le courriel de test a été envoyé.
Si vous ne le recevez pas, veuillez vérifier votre configuration des courriels.

Si vous avez besoin d’aide, veuillez vous rendre sur nos forums d’assistance (en anglais).', + + 'USE_SMTP' => 'Envoi des courriels à partir d’un serveur SMTP', + 'USE_SMTP_EXPLAIN' => 'Activez cette option si vous souhaitez envoyer les courriels à partir d’un serveur SMTP au lieu d’utiliser la fonction locale de la messagerie électronique.', +]); + +// Jabber settings +$lang = array_merge($lang, [ + 'ACP_JABBER_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez activer et contrôler l’utilisation de Jabber afin d’envoyer des messages instantanés et des notifications sur le forum. Jabber est un protocole libre et gratuit utilisable par tous. Certains serveurs Jabber incluent des passerelles qui vous permettent de contacter les utilisateurs d’autres réseaux, mais tous les serveurs n’offrent pas cette possibilité. Veuillez vous assurer d’avoir saisi les informations du compte que vous avez inscrit, ces informations seront utilisées telles quelles.', + + 'JAB_ALLOW_SELF_SIGNED' => 'Autoriser les certificats SSL auto-signés', + 'JAB_ALLOW_SELF_SIGNED_EXPLAIN' => 'Autorise les connexions au serveur Jabber avec des certificats SSL auto-signés.
Attention : les certificats SSL auto-signés peuvent avoir des répercutions sur la sécurité.', + 'JAB_ENABLE' => 'Activer Jabber', + 'JAB_ENABLE_EXPLAIN' => 'La messagerie et les notifications de Jabber seront activées.', + 'JAB_GTALK_NOTE' => 'Veuillez noter que GTalk ne fonctionnera pas car la fonction « dns_get_record » est introuvable. Cette fonction n’est pas implémentée dans PHP 4, sur les plates-formes Windows et sur les systèmes basés sous BSD, incluant Mac OS.', + 'JAB_PACKAGE_SIZE' => 'Taille du paquet Jabber', + 'JAB_PACKAGE_SIZE_EXPLAIN' => 'Le nombre de messages envoyés en un seul paquet. Si cette valeur est sur « 0 », les messages seront envoyés en temps réel et ne seront pas retardés.', + 'JAB_PASSWORD' => 'Mot de passe Jabber', + 'JAB_PASSWORD_EXPLAIN' => 'Attention : ce mot de passe sera archivé en texte brut dans la base de données et sera visible à tous ceux qui ont accès à votre base de données et à cette page de configuration.', + 'JAB_PORT' => 'Port de Jabber', + 'JAB_PORT_EXPLAIN' => 'Laissez ce champ vide sauf si le serveur utilise un port différent du 5222 dont vous avez connaissance.', + 'JAB_SERVER' => 'Serveur Jabber', + 'JAB_SERVER_EXPLAIN' => 'La liste des serveurs est disponible sur %sle site internet de Jabber%s.', + 'JAB_SETTINGS_CHANGED' => 'Les paramètres de Jabber ont été modifiés.', + 'JAB_USE_SSL' => 'Utiliser une connexion SSL', + 'JAB_USE_SSL_EXPLAIN' => 'Si cette option est activée, une connexion sécurisée essaiera d’être établie. Le port de Jabber sera modifié en 5223 si le port 5222 est utilisé.', + 'JAB_USERNAME' => 'Nom d’utilisateur Jabber ou JID', + 'JAB_USERNAME_EXPLAIN' => 'Saisissez un nom d’utilisateur inscrit ou un JID correct. La validité de ce nom d’utilisateur ne sera cependant pas vérifiée. Si vous ne saisissez qu’un nom d’utilisateur, votre JID correspondra à votre nom d’utilisateur et au serveur que vous avez spécifié ci-dessous. Dans le cas contraire, veuillez saisir un JID correct, tel que « utilisateur@jabber.org ».', + 'JAB_VERIFY_PEER' => 'Vérifier le certificat SSL', + 'JAB_VERIFY_PEER_EXPLAIN' => 'Vérifie le certificat SSL utilisé par le serveur Jabber.
Attention : les connexions aux pairs avec des certificats SSL qui n’ont pas été vérifiés peuvent avoir des répercutions sur la sécurité.', + 'JAB_VERIFY_PEER_NAME' => 'Vérifier le nom de pair Jabber', + 'JAB_VERIFY_PEER_NAME_EXPLAIN' => 'Vérifie le nom de pair des serveurs Jabber utilisant des connexions SSL et TLS.
Attention : les connexions à des pairs qui n’ont pas été vérifiés peuvent avoir des répercutions sur la sécurité.', +]); diff --git a/language/fr/acp/bots.php b/language/fr/acp/bots.php new file mode 100644 index 0000000..a2342a5 --- /dev/null +++ b/language/fr/acp/bots.php @@ -0,0 +1,71 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Bot settings +$lang = array_merge($lang, [ + 'BOTS' => 'Gérer les robots', + 'BOTS_EXPLAIN' => 'Les robots sont des agents automatisés qui sont dans la plupart des cas utilisés par les moteurs de recherche afin d’alimenter leur base de données. Les robots n’utilisent que très rarement les sessions de manière appropriée. Il est donc fréquent qu’ils augmentent inutilement la charge des serveurs et qu’ils n’indexent pas correctement certaines pages. Vous pouvez définir ici un type spécial d’utilisateurs qui vous permettra de résoudre ces problèmes.', + 'BOT_ACTIVATE' => 'Activer', + 'BOT_ACTIVE' => 'Activer le robot', + 'BOT_ADD' => 'Ajouter un robot', + 'BOT_ADDED' => 'Le nouveau robot a été ajouté.', + 'BOT_AGENT' => 'Agent utilisateur', + 'BOT_AGENT_EXPLAIN' => 'La chaîne correspondante à l’agent utilisateur du robot. Les correspondances partielles sont autorisées.', + 'BOT_DEACTIVATE' => 'Désactiver', + 'BOT_DELETED' => 'Le robot a été supprimé.', + 'BOT_EDIT' => 'Modifier un robot', + 'BOT_EDIT_EXPLAIN' => 'Depuis cette page, vous pouvez ajouter ou modifier un robot. Vous devez définir une chaîne correspondante à l’agent utilisateur du robot ou définir au moins une de ses adresses IP. Configurez ces paramètres avec précaution. Vous pouvez également sélectionner le style et la langue que le robot utilisera lors de sa navigation sur le forum. Cela vous permet de spécifier un style léger qui réduirera alors de façon significative la bande passante consommée par votre serveur. N’oubliez pas de configurer également les permissions correspondantes au groupe d’utilisateurs des robots.', + 'BOT_LANG' => 'Langue du robot', + 'BOT_LANG_EXPLAIN' => 'Le robot utilisera cette langue lors de sa navigation sur le forum.', + 'BOT_LAST_VISIT' => 'Dernière visite', + 'BOT_IP' => 'Adresse IP du robot', + 'BOT_IP_EXPLAIN' => 'Les adresses partielles sont autorisées. Chaque adresse doit être séparée par une virgule.', + 'BOT_NAME' => 'Nom du robot', + 'BOT_NAME_EXPLAIN' => 'Le robot sera identifié par ce nom.', + 'BOT_NAME_TAKEN' => 'Le nom du robot que vous avez spécifié existe déjà. Veuillez en saisir un autre.', + 'BOT_NEVER' => 'Jamais', + 'BOT_STYLE' => 'Style du robot', + 'BOT_STYLE_EXPLAIN' => 'Le robot utilisera ce style lors de sa navigation sur le forum.', + 'BOT_UPDATED' => 'Le robot a été mis à jour.', + + 'ERR_BOT_AGENT_MATCHES_UA' => 'L’agent utilisateur que vous avez spécifié pour le robot est identique à celui que vous utilisez actuellement. Veuillez en saisir un autre.', + 'ERR_BOT_NO_IP' => 'Les adresses IP que vous avez spécifiées sont invalides ou le nom d’hôte est indisponible.', + 'ERR_BOT_NO_MATCHES' => 'Vous devez saisir l’agent utilisateur du robot ou une de ses adresses IP.', + + 'NO_BOT' => 'Aucun robot correspondant à l’identifiant spécifié n’a été trouvé.', + 'NO_BOT_GROUP' => 'Le groupe d’utilisateurs des robots est introuvable.', +]); diff --git a/language/fr/acp/common.php b/language/fr/acp/common.php new file mode 100644 index 0000000..0b1d7ce --- /dev/null +++ b/language/fr/acp/common.php @@ -0,0 +1,832 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Common +$lang = array_merge($lang, [ + 'ACP_ADMINISTRATORS' => 'Administrateurs', + 'ACP_ADMIN_LOGS' => 'Historique des administrateurs', + 'ACP_ADMIN_ROLES' => 'Rôles des administrateurs', + 'ACP_ATTACHMENTS' => 'Pièces jointes', + 'ACP_ATTACHMENT_SETTINGS' => 'Paramètres des pièces jointes', + 'ACP_AUTH_SETTINGS' => 'Authentification', + 'ACP_AUTOMATION' => 'Automatisation', + 'ACP_AVATAR_SETTINGS' => 'Paramètres des avatars', + + 'ACP_BACKUP' => 'Sauvegarder', + 'ACP_BAN' => 'Bannissement', + 'ACP_BAN_EMAILS' => 'Bannir des adresses de courriel', + 'ACP_BAN_IPS' => 'Bannir des adresses IP', + 'ACP_BAN_USERNAMES' => 'Bannir des noms d’utilisateurs', + 'ACP_BBCODES' => 'BBCode', + 'ACP_BOARD_CONFIGURATION' => 'Configuration du forum', + 'ACP_BOARD_FEATURES' => 'Fonctionnalités du forum', + 'ACP_BOARD_MANAGEMENT' => 'Gestion du forum', + 'ACP_BOARD_SETTINGS' => 'Paramètres du forum', + 'ACP_BOTS' => 'Robots', + + 'ACP_CAPTCHA' => 'CAPTCHA', + + 'ACP_CAT_CUSTOMISE' => 'Personnalisation', + 'ACP_CAT_DATABASE' => 'Base de données', + 'ACP_CAT_DOT_MODS' => 'Extensions', + 'ACP_CAT_FORUMS' => 'Forums', + 'ACP_CAT_GENERAL' => 'Général', + 'ACP_CAT_MAINTENANCE' => 'Maintenance', + 'ACP_CAT_PERMISSIONS' => 'Permissions', + 'ACP_CAT_POSTING' => 'Publication', + 'ACP_CAT_STYLES' => 'Styles', + 'ACP_CAT_SYSTEM' => 'Système', + 'ACP_CAT_USERGROUP' => 'Utilisateurs et groupes', + 'ACP_CAT_USERS' => 'Utilisateurs', + 'ACP_CLIENT_COMMUNICATION' => 'Communication', + 'ACP_COOKIE_SETTINGS' => 'Paramètres des cookies', + 'ACP_CONTACT' => 'Page de contact', + 'ACP_CONTACT_SETTINGS' => 'Paramètres de la page de contact', + 'ACP_CRITICAL_LOGS' => 'Historique des erreurs', + 'ACP_CUSTOM_PROFILE_FIELDS' => 'Champs de profil personnalisés', + + 'ACP_DATABASE' => 'Gestion de la base de données', + 'ACP_DISALLOW' => 'Interdire', + 'ACP_DISALLOW_USERNAMES' => 'Interdire des noms d’utilisateurs', + + 'ACP_EMAIL_SETTINGS' => 'Paramètres des courriels', + 'ACP_EXTENSION_GROUPS' => 'Gérer les groupes d’extensions des pièces jointes', + 'ACP_EXTENSION_MANAGEMENT' => 'Gestion des extensions', + 'ACP_EXTENSIONS' => 'Gérer les extensions', + + 'ACP_FORUM_BASED_PERMISSIONS' => 'Permissions liées aux forums', + 'ACP_FORUM_LOGS' => 'Historique du forum', + 'ACP_FORUM_MANAGEMENT' => 'Gestion des forums', + 'ACP_FORUM_MODERATORS' => 'Permissions des forums aux modérateurs', + 'ACP_FORUM_PERMISSIONS' => 'Permissions des forums', + 'ACP_FORUM_PERMISSIONS_COPY' => 'Copie des permissions des forums', + 'ACP_FORUM_ROLES' => 'Rôles des forums', + + 'ACP_GENERAL_CONFIGURATION' => 'Configuration générale', + 'ACP_GENERAL_TASKS' => 'Tâches générales', + 'ACP_GLOBAL_MODERATORS' => 'Modérateurs généraux', + 'ACP_GLOBAL_PERMISSIONS' => 'Permissions générales', + 'ACP_GROUPS' => 'Groupes', + 'ACP_GROUPS_FORUM_PERMISSIONS' => 'Permissions des forums aux groupes', + 'ACP_GROUPS_MANAGE' => 'Gérer les groupes', + 'ACP_GROUPS_MANAGEMENT' => 'Gestion des groupes', + 'ACP_GROUPS_PERMISSIONS' => 'Permissions des groupes', + 'ACP_GROUPS_POSITION' => 'Gérer la position des groupes', + + 'ACP_HELP_PHPBB' => 'Aider et soutenir phpBB', + + 'ACP_ICONS' => 'Icônes de sujet', + 'ACP_ICONS_SMILIES' => 'Icônes de sujet et émoticônes', + 'ACP_INACTIVE_USERS' => 'Utilisateurs inactifs', + 'ACP_INDEX' => 'Accueil du PCA', + + 'ACP_JABBER_SETTINGS' => 'Paramètres de Jabber', + + 'ACP_LANGUAGE' => 'Gestion des langues', + 'ACP_LANGUAGE_PACKS' => 'Langues', + 'ACP_LOAD_SETTINGS' => 'Paramètres de la charge', + 'ACP_LOGGING' => 'Connexion', + + 'ACP_MAIN' => 'Accueil du PCA', + + 'ACP_MANAGE_ATTACHMENTS' => 'Gérer les pièces jointes', + 'ACP_MANAGE_ATTACHMENTS_EXPLAIN' => 'Depuis cette page, vous pouvez lister et supprimer les fichiers insérés aux messages et aux messages privés.', + + 'ACP_MANAGE_EXTENSIONS' => 'Gérer les extensions des pièces jointes', + 'ACP_MANAGE_FORUMS' => 'Gérer les forums', + 'ACP_MANAGE_RANKS' => 'Gérer les rangs', + 'ACP_MANAGE_REASONS' => 'Gérer les rapports et raisons', + 'ACP_MANAGE_USERS' => 'Gérer les utilisateurs', + 'ACP_MASS_EMAIL' => 'Courriel de masse', + 'ACP_MESSAGES' => 'Messages', + 'ACP_MESSAGE_SETTINGS' => 'Paramètres des messages privés', + 'ACP_MODULE_MANAGEMENT' => 'Gestion des modules', + 'ACP_MOD_LOGS' => 'Historique des modérateurs', + 'ACP_MOD_ROLES' => 'Rôles des modérateurs', + + 'ACP_NO_ITEMS' => 'Aucun élément.', + + 'ACP_ORPHAN_ATTACHMENTS' => 'Pièces jointes orphelines', + + 'ACP_PERMISSIONS' => 'Permissions', + 'ACP_PERMISSION_MASKS' => 'Masques des permissions', + 'ACP_PERMISSION_ROLES' => 'Rôles des permissions', + 'ACP_PERMISSION_TRACE' => 'Trace de permission', + 'ACP_PHP_INFO' => 'Informations sur PHP', + 'ACP_POST_SETTINGS' => 'Paramètres de la publication', + 'ACP_PRUNE_FORUMS' => 'Délester des forums', + 'ACP_PRUNE_USERS' => 'Délester des utilisateurs', + 'ACP_PRUNING' => 'Délestage', + + 'ACP_QUICK_ACCESS' => 'Accès rapide', + + 'ACP_RANKS' => 'Rangs', + 'ACP_REASONS' => 'Rapports et raisons', + 'ACP_REGISTER_SETTINGS' => 'Paramètres des inscriptions', + + 'ACP_RESTORE' => 'Restaurer', + + 'ACP_FEED' => 'Gestion des flux', + 'ACP_FEED_SETTINGS' => 'Paramètres des flux', + + 'ACP_SEARCH' => 'Configuration de la recherche', + 'ACP_SEARCH_INDEX' => 'Indexation de la recherche', + 'ACP_SEARCH_SETTINGS' => 'Paramètres de la recherche', + + 'ACP_SECURITY_SETTINGS' => 'Paramètres de sécurité', + 'ACP_SERVER_CONFIGURATION' => 'Configuration du serveur', + 'ACP_SERVER_SETTINGS' => 'Paramètres du serveur', + 'ACP_SIGNATURE_SETTINGS' => 'Paramètres des signatures', + 'ACP_SMILIES' => 'Émoticônes', + 'ACP_STYLE_MANAGEMENT' => 'Gestion des styles', + 'ACP_STYLES' => 'Styles', + 'ACP_STYLES_CACHE' => 'Vider le cache', + 'ACP_STYLES_INSTALL' => 'Installer des styles', + + 'ACP_SUBMIT_CHANGES' => 'Sauvegarder les modifications', + + 'ACP_TEMPLATES' => 'Modèles', + 'ACP_THEMES' => 'Thèmes', + + 'ACP_UPDATE' => 'Mise à jour', + 'ACP_USERS_FORUM_PERMISSIONS' => 'Permissions des forums aux utilisateurs', + 'ACP_USERS_LOGS' => 'Historique des utilisateurs', + 'ACP_USERS_PERMISSIONS' => 'Permissions des utilisateurs', + 'ACP_USER_ATTACH' => 'Pièces jointes', + 'ACP_USER_AVATAR' => 'Avatar', + 'ACP_USER_FEEDBACK' => 'Remarque', + 'ACP_USER_GROUPS' => 'Groupes', + 'ACP_USER_MANAGEMENT' => 'Gestion des utilisateurs', + 'ACP_USER_OVERVIEW' => 'Vue d’ensemble', + 'ACP_USER_PERM' => 'Permissions', + 'ACP_USER_PREFS' => 'Préférences', + 'ACP_USER_PROFILE' => 'Profil', + 'ACP_USER_RANK' => 'Rang', + 'ACP_USER_ROLES' => 'Rôles des utilisateurs', + 'ACP_USER_SECURITY' => 'Sécurité des utilisateurs', + 'ACP_USER_SIG' => 'Signature', + 'ACP_USER_WARNINGS' => 'Avertissements', + + 'ACP_VC_SETTINGS' => 'Mesures de lutte contre les robots indésirables', + 'ACP_VC_CAPTCHA_DISPLAY' => 'Prévisualisation de l’image du CAPTCHA', + 'ACP_VERSION_CHECK' => 'Vérifier les mises à jour', + 'ACP_VIEW_ADMIN_PERMISSIONS' => 'Masques des administrateurs', + 'ACP_VIEW_FORUM_MOD_PERMISSIONS' => 'Masques des modérateurs', + 'ACP_VIEW_FORUM_PERMISSIONS' => 'Masques des forums', + 'ACP_VIEW_GLOBAL_MOD_PERMISSIONS' => 'Masques des modérateurs généraux', + 'ACP_VIEW_USER_PERMISSIONS' => 'Masques des utilisateurs', + + 'ACP_WORDS' => 'Censure de mots', + + 'ACTION' => 'Opération', + 'ACTIONS' => 'Opérations', + 'ACTIVATE' => 'Activer', + 'ADD' => 'Ajouter', + 'ADMIN' => 'Administration', + 'ADMIN_INDEX' => 'Accueil de l’administration', + 'ADMIN_PANEL' => 'Panneau de contrôle d’administration', + + 'ADM_LOGOUT' => 'Déconnexion du PCA', + 'ADM_LOGGED_OUT' => 'Vous êtes à présent déconnecté du panneau de contrôle d’administration.', + + 'BACK' => 'Retour', + + 'CANNOT_CHANGE_FILE_GROUP' => 'Impossible de modifier le groupe du fichier', + 'CANNOT_CHANGE_FILE_PERMISSIONS' => 'Impossible de modifier les permissions du fichier', + 'CANNOT_COPY_FILES' => 'Impossible de copier les fichiers', + 'CANNOT_CREATE_SYMLINK' => 'Impossible de créer un lien symbolique', + 'CANNOT_DELETE_FILES' => 'Impossible de supprimer les fichiers du système', + 'CANNOT_DUMP_FILE' => 'Impossible de vider le fichier', + 'CANNOT_MIRROR_DIRECTORY' => 'Impossible de répliquer le répertoire', + 'CANNOT_RENAME_FILE' => 'Impossible de renommer un fichier du système', + 'CANNOT_TOUCH_FILES' => 'Impossible de déterminer si le fichier existe', + + 'CONTAINER_EXCEPTION' => 'Une erreur est survenue lors de la construction du conteneur en raison d’une extension installée. Par mesure de sécurité, toutes les extensions ont été temporairement désactivées. Veuillez essayer de vider le cache de votre forum. Toutes les extensions seront automatiquement réactivées lorsque cette erreur de conteneur sera corrigée. Si cette erreur persiste, veuillez demander une assistance sur notre site internet (en anglais).', + 'EXCEPTION' => 'Exception', + + 'COLOUR_SWATCH' => 'Palette de couleurs internet', + 'CONFIG_UPDATED' => 'La configuration a été mise à jour.', + 'CRON_LOCK_ERROR' => 'Impossible d’obtenir le verrouillage de « cron ».', + 'CRON_NO_SUCH_TASK' => 'La tâche cron « %s » est introuvable.', + 'CRON_NO_TASK' => 'Aucune tâche « cron » ne doit être exécutée.', + 'CRON_NO_TASKS' => 'Aucune tâche « cron » n’est disponible.', + 'CURRENT_VERSION' => 'Version actuelle', + + 'DEACTIVATE' => 'Désactiver', + 'DIRECTORY_DOES_NOT_EXIST' => 'Le chemin « %s » que vous avez spécifié est introuvable.', + 'DIRECTORY_NOT_DIR' => 'Le chemin « %s » que vous avez spécifié n’est pas un répertoire.', + 'DIRECTORY_NOT_WRITABLE' => 'Le répertoire du chemin « %s » que vous avez spécifié est en lecture seule.', + 'DISABLE' => 'Désactiver', + 'DOWNLOAD' => 'Télécharger', + 'DOWNLOAD_AS' => 'Télécharger sous', + 'DOWNLOAD_STORE' => 'Télécharger ou stocker le fichier', + 'DOWNLOAD_STORE_EXPLAIN' => 'Vous pouvez télécharger directement le fichier ou le sauvegarder dans votre répertoire « store/ ».', + 'DOWNLOADS' => 'Téléchargements', + + 'EDIT' => 'Modifier', + 'ENABLE' => 'Activer', + 'EXPORT_DOWNLOAD' => 'Télécharger', + 'EXPORT_STORE' => 'Stocker', + + 'GENERAL_OPTIONS' => 'Options générales', + 'GENERAL_SETTINGS' => 'Paramètres généraux', + 'GLOBAL_MASK' => 'Masque de permission générale', + + 'INSTALL' => 'Installer', + 'IP' => 'Adresse IP de l’utilisateur', + 'IP_HOSTNAME' => 'Adresses IP ou noms d’hôtes', + + 'LATEST_VERSION' => 'Dernière version', + 'LOAD_NOTIFICATIONS' => 'Activer les notifications', + 'LOAD_NOTIFICATIONS_EXPLAIN' => 'La liste des notifications sera affichée sur toutes les pages du forum (généralement en haut des pages).', + 'LOGGED_IN_AS' => 'Connecté en tant que :', + 'LOGIN_ADMIN' => 'Vous devez vous authentifier afin d’administrer le forum.', + 'LOGIN_ADMIN_CONFIRM' => 'Vous devez vous authentifier de nouveau afin d’administrer le forum.', + 'LOGIN_ADMIN_SUCCESS' => 'Vous êtes à présent authentifié et allez être redirigé vers le panneau de contrôle d’administration.', + 'LOOK_UP_FORUM' => 'Sélectionner un forum', + 'LOOK_UP_FORUMS_EXPLAIN' => 'Vous pouvez sélectionner plusieurs forums.', + + 'MANAGE' => 'Gérer', + 'MENU_TOGGLE' => 'Masquer ou afficher le menu latéral', + 'MORE' => 'Plus', // Not used at the moment + 'MORE_INFORMATION' => 'Plus d’informations…', + 'MOVE_DOWN' => 'Descendre', + 'MOVE_UP' => 'Monter', + + 'NOTIFY' => 'Notification', + 'NO_ADMIN' => 'Vous ne pouvez pas administrer ce forum.', + 'NO_EMAILS_DEFINED' => 'Aucune adresse de courriel valide n’a été spécifiée.', + 'NO_FILES_TO_DELETE' => 'Les pièces jointes que vous souhaitez supprimer sont introuvables.', + 'NO_PASSWORD_SUPPLIED' => 'Vous devez saisir votre mot de passe afin d’accéder au panneau de contrôle d’administration.', + + 'OFF' => 'Désactivé', + 'ON' => 'Activé', + + 'PARSE_BBCODE' => 'Analyser la syntaxe du BBCode', + 'PARSE_SMILIES' => 'Analyser la syntaxe des émoticônes', + 'PARSE_URLS' => 'Analyser la syntaxe des liens', + 'PERMISSIONS_TRANSFERRED' => 'Permissions transférées', + 'PERMISSIONS_TRANSFERRED_EXPLAIN' => 'Vous détenez actuellement les permissions de %1$s. Vous pouvez parcourir le forum avec les permissions de cet utilisateur mais vous ne pouvez pas accéder au panneau de contrôle d’administration car les permissions des administrateurs ne sont pas transférées. Vous pouvez réinitialiser la configuration de vos permissions à tout moment.', + 'PROCEED_TO_ACP' => '%sAller au PCA%s', + + 'RELEASE_ANNOUNCEMENT' => 'Annonce', + 'REMIND' => 'Rappel', + 'REPARSE_LOCK_ERROR' => 'L’analyse est déjà en cours par un autre processus.', + 'RESYNC' => 'Resynchroniser', + + 'RUNNING_TASK' => 'Exécution de la tâche : %s.', + 'SELECT_ANONYMOUS' => 'Sélectionner un utilisateur anonyme', + 'SELECT_OPTION' => 'Sélectionner une option', + + 'SETTING_TOO_LOW' => 'La valeur que vous avez spécifiée concernant « %1$s » est trop faible. Cette valeur doit être supérieure ou égale à %2$d.', + 'SETTING_TOO_BIG' => 'La valeur que vous avez spécifiée concernant « %1$s » est trop élevée. Cette valeur doit être inférieure ou égale à %2$d.', + 'SETTING_TOO_LONG' => 'La valeur que vous avez spécifiée concernant « %1$s » est trop longue. Cette valeur doit être inférieure ou égale à %2$d.', + 'SETTING_TOO_SHORT' => 'La valeur que vous avez spécifiée concernant « %1$s » est trop courte. Cette valeur doit être supérieure ou égale à %2$d.', + + 'SHOW_ALL_OPERATIONS' => 'Afficher toutes les opérations', + + 'TASKS_NOT_READY' => 'Tâches non prêtes :', + 'TASKS_READY' => 'Tâches prêtes :', + 'TOTAL_SIZE' => 'Taille totale', + + 'UCP' => 'Panneau de contrôle de l’utilisateur', + 'URL_INVALID' => 'L’adresse spécifiée concernant le paramètre « %1$s » est invalide.', + 'USERNAMES_EXPLAIN' => 'Veuillez insérer chaque nom d’utilisateur sur une nouvelle ligne.', + 'USER_CONTROL_PANEL' => 'Panneau de contrôle de l’utilisateur', + + 'UPDATE_NEEDED' => 'Le forum n’est pas à jour.', + 'UPDATE_NOT_NEEDED' => 'Le forum est à jour.', + 'UPDATES_AVAILABLE' => 'Mises à jour disponibles :', + + 'WARNING' => 'Avertissement', +]); + +// PHP info +$lang = array_merge($lang, [ + 'ACP_PHP_INFO_EXPLAIN' => 'Depuis cette page, vous pouvez consulter les informations sur la version de PHP installée sur votre serveur. Ces informations comprennent l’ensemble des données sur les modules chargés, les variables disponibles et les paramètres par défaut. Ces informations peuvent être utiles afin de diagnostiquer les problèmes présents sur votre serveur. Veuillez noter que certains hébergeurs peuvent limiter l’affichage de ces informations pour des raisons de sécurité. Il est déconseillé de divulguer les informations disponibles sur cette page, sauf si elles sont explicitement demandées par un des membres de notre équipe (en anglais) sur les forums d’assistance.', + + 'NO_PHPINFO_AVAILABLE' => 'Impossible d’afficher les informations concernant votre configuration de PHP. La fonction « phpinfo() » a été désactivée pour des raisons de sécurité.', +]); + +// Logs +$lang = array_merge($lang, [ + 'ACP_ADMIN_LOGS_EXPLAIN' => 'Cette liste vous permet de consulter toutes les opérations qui ont été effectuées par les administrateurs du forum. Vous pouvez les classer par nom d’utilisateur, date, adresse IP ou opération. Si vous en avez les permissions, vous pouvez également effacer intégralement ou individuellement ces opérations.', + 'ACP_CRITICAL_LOGS_EXPLAIN' => 'Cette liste vous permet de consulter toutes les opérations qui ont été effectuées par le forum. Cet historique vous informe des problèmes qui se sont produits, comme des courriels qui n’ont pas été correctement transférés. Vous pouvez les classer par nom d’utilisateur, date, adresse IP ou opération. Si vous en avez les permissions, vous pouvez également effacer intégralement ou individuellement ces opérations.', + 'ACP_MOD_LOGS_EXPLAIN' => 'Cette liste vous permet de consulter toutes les opérations qui ont été effectuées sur les forums, les sujets et les messages, ainsi que celles effectuées par les modérateurs sur des utilisateurs, comme les bannissements. Vous pouvez les classer par nom d’utilisateur, date, adresse IP ou opération. Si vous en avez les permissions, vous pouvez également effacer intégralement ou individuellement ces opérations.', + 'ACP_USERS_LOGS_EXPLAIN' => 'Cette liste vous permet de consulter toutes les opérations qui ont été effectuées par les utilisateurs ou sur des utilisateurs (comme les rapports, les avertissements et les remarques).', + 'ALL_ENTRIES' => 'Tous les éléments', + + 'DISPLAY_LOG' => 'Afficher les éléments antérieurs', + + 'NO_ENTRIES' => 'Aucun historique.', + + 'SORT_IP' => 'Adresse IP', + 'SORT_DATE' => 'Date', + 'SORT_ACTION' => 'Opération', +]); + +// Index page +$lang = array_merge($lang, [ + 'ADMIN_INTRO' => 'Nous vous remercions d’utiliser le logiciel phpBB comme solution pour votre forum de discussions. Cette page vous offre un aperçu des nombreuses statistiques de votre forum. Les liens situés sur le volet à gauche de cette page vous permettent de personnaliser tous les aspects de votre forum. Chaque page contient des instructions vous informant sur l’utilisation des outils disponibles.', + 'ADMIN_LOG' => 'Historique des dernières opérations des administrateurs', + 'ADMIN_LOG_INDEX_EXPLAIN' => 'Cet historique vous affiche les cinq dernières opérations effectuées par les administrateurs de ce forum. Vous pouvez consulter la totalité de cet historique depuis le menu approprié ou en cliquant sur le lien disponible ci-dessous.', + 'AVATAR_DIR_SIZE' => 'Taille du répertoire des avatars', + + 'BOARD_STARTED' => 'Date d’ouverture du forum', + 'BOARD_VERSION' => 'Version du forum', + + 'DATABASE_SERVER_INFO' => 'Serveur de la base de données', + 'DATABASE_SIZE' => 'Taille de la base de données', + + // Enviroment configuration checks, mbstring related + 'ERROR_MBSTRING_FUNC_OVERLOAD' => 'La fonction de surcharge n’est pas correctement configurée', + 'ERROR_MBSTRING_FUNC_OVERLOAD_EXPLAIN' => 'La variable « mbstring.func_overload » doit être réglée sur « 0 » ou « 4 ». Vous pouvez consulter la valeur actuelle sur la page « Informations sur PHP ».', + 'ERROR_MBSTRING_ENCODING_TRANSLATION' => 'L’encodage des caractères transparents n’est pas correctement configuré', + 'ERROR_MBSTRING_ENCODING_TRANSLATION_EXPLAIN' => 'La variable « mbstring.encoding_translation » doit être réglée sur « 0 ». Vous pouvez consulter la valeur actuelle sur la page « Informations sur PHP ».', + 'ERROR_MBSTRING_HTTP_INPUT' => 'La conversion des caractères d’entrée HTTP n’est pas correctement configurée', + 'ERROR_MBSTRING_HTTP_INPUT_EXPLAIN' => 'La variable « mbstring.http_input » doit être paramétrée sur « pass ». Vous pouvez consulter la valeur actuelle sur la page « Informations sur PHP ».', + 'ERROR_MBSTRING_HTTP_OUTPUT' => 'La conversion des caractères de sortie HTTP n’est pas correctement configurée', + 'ERROR_MBSTRING_HTTP_OUTPUT_EXPLAIN' => 'La variable « mbstring.http_output » doit être paramétrée sur « pass ». Vous pouvez consulter la valeur actuelle sur la page « Informations sur PHP ».', + + 'FILES_PER_DAY' => 'Moyenne de pièces jointes par jour', + 'FORUM_STATS' => 'Statistiques du forum', + + 'GZIP_COMPRESSION' => 'Compression GZip', + + 'NO_SEARCH_INDEX' => 'La méthode d’indexation de la recherche sélectionnée n’est pas associée à un index de recherche.
Veuillez créer l’index associé à « %1$s » dans la section « %2$sIndex de recherche%3$s ».', + 'NOT_AVAILABLE' => 'Non disponible', + 'NUMBER_FILES' => 'Nombre de pièces jointes', + 'NUMBER_POSTS' => 'Nombre de messages', + 'NUMBER_TOPICS' => 'Nombre de sujets', + 'NUMBER_USERS' => 'Nombre d’utilisateurs', + 'NUMBER_ORPHAN' => 'Nombre de pièces jointes orphelines', + + 'PHP_VERSION' => 'Version de PHP', + 'PHP_VERSION_OLD' => 'La version de PHP de ce serveur (%1$s) ne sera plus compatible avec les futures versions de phpBB. La version minimale requise sera PHP %2$s. %3$sEn savoir plus…%4$s', + + 'POSTS_PER_DAY' => 'Moyenne de messages par jour', + + 'PURGE_CACHE' => 'Vider le cache', + 'PURGE_CACHE_CONFIRM' => 'Êtes-vous sûr de vouloir vider le cache ?', + 'PURGE_CACHE_EXPLAIN' => 'Les fichiers mis en cache, tels que les fichiers des modèles et les requêtes, seront supprimés.', + 'PURGE_CACHE_SUCCESS' => 'La cache a été vidé.', + + 'PURGE_SESSIONS' => 'Vider toutes les sessions', + 'PURGE_SESSIONS_CONFIRM' => 'Êtes-vous sûr de vouloir vider toutes les sessions ? Tous les utilisateurs seront déconnectés.', + 'PURGE_SESSIONS_EXPLAIN' => 'Toutes les sessions seront vidées et tous les utilisateurs seront déconnectés.', + 'PURGE_SESSIONS_SUCCESS' => 'Les sessions ont été vidées.', + + 'RESET_DATE' => 'Réinitialiser la date d’ouverture du forum', + 'RESET_DATE_CONFIRM' => 'Êtes-vous sûr de vouloir réinitialiser la date d’ouverture du forum ?', + 'RESET_DATE_SUCCESS' => 'La date d’ouverture du forum a été réinitialisée', + 'RESET_ONLINE' => 'Réinitialiser le compteur du nombre maximal d’utilisateurs en ligne simultanément', + 'RESET_ONLINE_CONFIRM' => 'Êtes-vous sûr de vouloir réinitialiser le compteur du nombre maximal d’utilisateurs en ligne simultanément ?', + 'RESET_ONLINE_SUCCESS' => 'Le compteur du nombre maximal d’utilisateurs en ligne simultanément a été réinitialisé', + 'RESYNC_POSTCOUNTS' => 'Resynchroniser le compteur de messages', + 'RESYNC_POSTCOUNTS_EXPLAIN' => 'Tous les messages actuels seront recomptabilisés. Les messages délestés ne seront pas pris en compte.', + 'RESYNC_POSTCOUNTS_CONFIRM' => 'Êtes-vous sûr de vouloir resynchroniser le compteur de messages ?', + 'RESYNC_POSTCOUNTS_SUCCESS' => 'Le compteur de messages a été resynchronisé', + 'RESYNC_POST_MARKING' => 'Resynchroniser les sujets pointés', + 'RESYNC_POST_MARKING_CONFIRM' => 'Êtes-vous sûr de vouloir resynchroniser les sujets pointés ?', + 'RESYNC_POST_MARKING_EXPLAIN' => 'Tous les sujets seront décomptabilisés afin que seuls les sujets actifs des six derniers mois soient recomptabilisés.', + 'RESYNC_POST_MARKING_SUCCESS' => 'Les sujets pointés ont été resynchronisés', + 'RESYNC_STATS' => 'Resynchroniser les statistiques', + 'RESYNC_STATS_CONFIRM' => 'Êtes-vous sûr de vouloir resynchroniser les statistiques ?', + 'RESYNC_STATS_EXPLAIN' => 'Les statistiques sur le nombre total de messages, de sujets, d’utilisateurs et de fichiers seront recomptabilisées.', + 'RESYNC_STATS_SUCCESS' => 'Les statistiques ont été resynchronisées', + 'RUN' => 'Exécuter maintenant', + + 'STATISTIC' => 'Statistiques', + 'STATISTIC_RESYNC_OPTIONS' => 'Resynchroniser ou réinitialiser les statistiques', + + 'TIMEZONE_INVALID' => 'Le fuseau horaire que vous avez spécifié est invalide.', + 'TIMEZONE_SELECTED' => '(actuellement sélectionné)', + 'TOPICS_PER_DAY' => 'Moyenne de sujets par jour', + + 'UPLOAD_DIR_SIZE' => 'Taille des pièces jointes publiées', + 'USERS_PER_DAY' => 'Moyenne d’utilisateurs par jour', + + 'VALUE' => 'Valeur', + 'VERSIONCHECK_FAIL' => 'Aucune information concernant la dernière version stable n’est disponible.', + 'VERSIONCHECK_FORCE_UPDATE' => 'Vérifier de nouveau les mises à jour', + 'VERSION_CHECK' => 'Vérifier les mises à jour', + 'VERSION_CHECK_EXPLAIN' => 'Depuis cette page, vous pouvez vérifier si le logiciel de votre forum est à jour avec la dernière version actuellement disponible.', + 'VERSIONCHECK_INVALID_ENTRY' => 'Les informations concernant la dernière version contiennent un élément non pris en charge.', + 'VERSIONCHECK_INVALID_URL' => 'Les informations concernant la dernière version contiennent un lien invalide.', + 'VERSIONCHECK_INVALID_VERSION' => 'Les informations concernant la dernière version contiennent une version invalide.', + 'VERSION_NOT_UP_TO_DATE_ACP' => 'Le logiciel de votre forum n’est pas à jour.
Vous trouverez ci-dessous un lien vers l’annonce de sortie, qui contient de plus amples informations et les instructions de mise à jour.', + 'VERSION_NOT_UP_TO_DATE_TITLE' => 'Le logiciel de votre forum n’est pas à jour.', + 'VERSION_UP_TO_DATE_ACP' => 'Le logiciel de votre forum est à jour. Aucune mise à jour n’est disponible pour le moment.', + 'VIEW_ADMIN_LOG' => 'Consulter l’historique des administrateurs', + 'VIEW_INACTIVE_USERS' => 'Consulter la liste des utilisateurs inactifs', + + 'WELCOME_PHPBB' => 'Bienvenue sur phpBB', + 'WRITABLE_CONFIG' => 'Les droits d’accès pour écriture de votre fichier de configuration « config.php » sont actuellement publics. Nous vous encourageons fortement à modifier vos permissions en 640, en exécutant la commande « chmod 640 config.php », ou, le cas échéant, en 644.', +]); + +// Inactive Users +$lang = array_merge($lang, [ + 'INACTIVE_DATE' => 'Date d’inactivité', + 'INACTIVE_REASON' => 'Raison', + 'INACTIVE_REASON_MANUAL' => 'Le compte a été désactivé par un administrateur', + 'INACTIVE_REASON_PROFILE' => 'Les informations du profil ont été modifiées', + 'INACTIVE_REASON_REGISTER' => 'Le compte appartient à un utilisateur nouvellement inscrit', + 'INACTIVE_REASON_REMIND' => 'Le compte doit être réactivé', + 'INACTIVE_REASON_UNKNOWN' => 'Inconnue', + 'INACTIVE_USERS' => 'Utilisateurs inactifs', + 'INACTIVE_USERS_EXPLAIN' => 'Cette liste vous affiche les utilisateurs inscrits dont le compte est actuellement inactif. Vous pouvez activer, supprimer ou rappeler (en envoyant un courriel) ces utilisateurs.', + 'INACTIVE_USERS_EXPLAIN_INDEX' => 'Cette liste vous affiche les dix derniers utilisateurs inscrits dont le compte est actuellement inactif. Les comptes d’utilisateurs peuvent être inactifs lorsqu’une activation est requise dans les paramètres des inscriptions mais que certains comptes d’utilisateurs n’ont pas encore été activés. Les comptes d’utilisateurs désactivés sont également affichés dans cette liste. Vous pouvez consulter la totalité de la liste des utilisateurs inactifs depuis le menu approprié ou en cliquant sur le lien ci-dessous d’où vous pourrez activer, supprimer ou rappeler (en envoyant un courriel) ces utilisateurs.', + + 'NO_INACTIVE_USERS' => 'Aucun utilisateur inactif', + + 'SORT_INACTIVE' => 'Date d’inactivité', + 'SORT_LAST_VISIT' => 'Dernière visite', + 'SORT_REASON' => 'Raison', + 'SORT_REG_DATE' => 'Date d’inscription', + 'SORT_LAST_REMINDER' => 'Dernier rappel', + 'SORT_REMINDER' => 'Rappel envoyé', + + 'USER_IS_INACTIVE' => 'L’utilisateur est inactif', +]); + +// Help support phpBB page +$lang = array_merge($lang, [ + 'EXPLAIN_SEND_STATISTICS' => 'Vous pouvez nous soumettre des statistiques sur votre serveur et sur la configuration de votre forum. Veuillez noter que les données sont entièrement anonymes, toutes les informations pouvant vous identifier ou identifier votre site internet ont été supprimées. Nous utilisons ces informations afin de mieux adapter nos décisions sur les futures versions de notre logiciel. Les statistiques sont rendues publiques. Nous partageons également ces données avec le projet PHP, qui est l’auteur du langage de programmation du même nom, majoritairement utilisé dans phpBB.', + 'EXPLAIN_SHOW_STATISTICS' => 'En cliquant sur le bouton ci-dessous, vous pouvez prévisualiser toutes les variables qui nous seront transmises.', + 'DONT_SEND_STATISTICS' => 'Revenir au panneau de contrôle d’administration sans envoyer les statistiques.', + 'GO_ACP_MAIN' => 'Aller sur la page de démarrage du PCA', + 'HIDE_STATISTICS' => 'Masquer les informations', + 'SEND_STATISTICS' => 'Envoyer les statistiques', + 'SEND_STATISTICS_LONG' => 'Envoyer les statistiques', + 'SHOW_STATISTICS' => 'Afficher les informations', + 'THANKS_SEND_STATISTICS' => 'Nous vous remercions d’avoir partagé vos informations.', + 'FAIL_SEND_STATISTICS' => 'Une erreur est survenue lors de l’envoi des statistiquues', +]); + +// Log Entries +$lang = array_merge($lang, [ + 'LOG_ACL_ADD_USER_GLOBAL_U_' => 'Ajout ou modification des permissions d’un utilisateur
» %s', + 'LOG_ACL_ADD_GROUP_GLOBAL_U_' => 'Ajout ou modification des permissions d’un groupe d’utilisateurs
» %s', + 'LOG_ACL_ADD_USER_GLOBAL_M_' => 'Ajout ou modification des permissions des modérateurs généraux
» %s', + 'LOG_ACL_ADD_GROUP_GLOBAL_M_' => 'Ajout ou modification des permissions d’un groupe de modérateurs généraux
» %s', + 'LOG_ACL_ADD_USER_GLOBAL_A_' => 'Ajout ou modification des permissions d’administrateurs
» %s', + 'LOG_ACL_ADD_GROUP_GLOBAL_A_' => 'Ajout ou modification des permissions d’un groupe d’administrateurs
» %s', + + 'LOG_ACL_ADD_ADMIN_GLOBAL_A_' => 'Ajout ou modification d’administrateurs
» %s', + 'LOG_ACL_ADD_MOD_GLOBAL_M_' => 'Ajout ou modification de modérateurs généraux
» %s', + + 'LOG_ACL_ADD_USER_LOCAL_F_' => 'Ajout ou modification de l’accès des utilisateurs à un forum de %1$s
» %2$s', + 'LOG_ACL_ADD_USER_LOCAL_M_' => 'Ajout ou modification de l’accès des modérateurs à un forum de %1$s
» %2$s', + 'LOG_ACL_ADD_GROUP_LOCAL_F_' => 'Ajout ou modification de l’accès des groupes d’utilisateurs à un forum de %1$s
» %2$s', + 'LOG_ACL_ADD_GROUP_LOCAL_M_' => 'Ajout ou modification de l’accès des groupes de modérateurs à un forum de %1$s
» %2$s', + + 'LOG_ACL_ADD_MOD_LOCAL_M_' => 'Ajout ou modification de modérateurs de %1$s
» %2$s', + 'LOG_ACL_ADD_FORUM_LOCAL_F_' => 'Ajout ou modification des permissions d’un forum de %1$s
» %2$s', + + 'LOG_ACL_DEL_ADMIN_GLOBAL_A_' => 'Suppression d’administrateurs
» %s', + 'LOG_ACL_DEL_MOD_GLOBAL_M_' => 'Suppression de modérateurs généraux
» %s', + 'LOG_ACL_DEL_MOD_LOCAL_M_' => 'Suppression de modérateurs de %1$s
» %2$s', + 'LOG_ACL_DEL_FORUM_LOCAL_F_' => 'Suppression des permissions de groupes d’utilisateurs ou d’utilisateurs d’un forum de %1$s
» %2$s', + + 'LOG_ACL_TRANSFER_PERMISSIONS' => 'Utilisation des permissions d’un utilisateur
» %s', + 'LOG_ACL_RESTORE_PERMISSIONS' => 'Restauration des permissions originales après utilisation des permissions d’un utilisateur
» %s', + + 'LOG_ADMIN_AUTH_FAIL' => 'Échec d’une tentative de connexion au panneau de contrôle d’administration', + 'LOG_ADMIN_AUTH_SUCCESS' => 'Connexion au panneau de contrôle d’administration', + + 'LOG_ATTACHMENTS_DELETED' => 'Suppression des pièces jointes d’un utilisateur
» %s', + + 'LOG_ATTACH_EXT_ADD' => 'Ajout ou modification d’une extension de pièce jointe
» %s', + 'LOG_ATTACH_EXT_DEL' => 'Suppression d’une extension de pièce jointe
» %s', + 'LOG_ATTACH_EXT_UPDATE' => 'Mise à jour d’une extension de pièce jointe
» %s', + 'LOG_ATTACH_EXTGROUP_ADD' => 'Ajout d’un groupe d’extensions
» %s', + 'LOG_ATTACH_EXTGROUP_EDIT' => 'Modification d’un groupe d’extensions
» %s', + 'LOG_ATTACH_EXTGROUP_DEL' => 'Suppression d’un groupe d’extensions
» %s', + 'LOG_ATTACH_FILEUPLOAD' => 'Transfert d’une pièce jointe orpheline vers un message
» ID %1$d – %2$s', + 'LOG_ATTACH_ORPHAN_DEL' => 'Suppression d’une pièce jointe orpheline
» %s', + + 'LOG_BAN_EXCLUDE_USER' => 'Exclusion d’un nom d’utilisateur d’un bannissement en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_BAN_EXCLUDE_IP' => 'Exclusion d’une adresse IP du bannissement en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_BAN_EXCLUDE_EMAIL' => 'Exclusion d’une adresse de courriel du bannissement en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_BAN_USER' => 'Bannissement d’un nom d’utilisateur en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_BAN_IP' => 'Bannissement d’une adresse IP en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_BAN_EMAIL' => 'Bannissement d’une adresse de courriel en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_UNBAN_USER' => 'Annulation du bannissement d’un nom d’utilisateur
» %s', + 'LOG_UNBAN_IP' => 'Annulation du bannissement d’une adresse IP
» %s', + 'LOG_UNBAN_EMAIL' => 'Annulation du bannissement d’une adresse de courriel
» %s', + + 'LOG_BBCODE_ADD' => 'Ajout d’un nouveau BBCode
» %s', + 'LOG_BBCODE_EDIT' => 'Modification d’un BBCode
» %s', + 'LOG_BBCODE_DELETE' => 'Suppression d’un BBCode
» %s', + 'LOG_BBCODE_CONFIGURATION_ERROR' => 'Une erreur est survenue lors de la configuration du BBCode : %1$s
» %2$s', + + 'LOG_BOT_ADDED' => 'Ajout d’un nouveau robot
» %s', + 'LOG_BOT_DELETE' => 'Suppression d’un robot
» %s', + 'LOG_BOT_UPDATED' => 'Mise à jour d’un robot existant
» %s', + + 'LOG_CLEAR_ADMIN' => 'Nettoyage de l’historique des administrateurs', + 'LOG_CLEAR_CRITICAL' => 'Nettoyage de l’historique des erreurs', + 'LOG_CLEAR_MOD' => 'Nettoyage de l’historique des modérateurs', + 'LOG_CLEAR_USER' => 'Nettoyage de l’historique d’un utilisateur
» %s', + 'LOG_CLEAR_USERS' => 'Nettoyage de l’historique des utilisateurs', + + 'LOG_CONFIG_ATTACH' => 'Modification des paramètres des pièces jointes', + 'LOG_CONFIG_AUTH' => 'Modification des paramètres d’authentification', + 'LOG_CONFIG_AVATAR' => 'Modification des paramètres des avatars', + 'LOG_CONFIG_COOKIE' => 'Modification des paramètres des cookies', + 'LOG_CONFIG_EMAIL' => 'Modification des paramètres de la messagerie électronique', + 'LOG_CONFIG_FEATURES' => 'Modification des fonctionnalités du forum', + 'LOG_CONFIG_LOAD' => 'Modification des paramètres de la charge du système', + 'LOG_CONFIG_MESSAGE' => 'Modification des paramètres de la messagerie privée', + 'LOG_CONFIG_POST' => 'Modification des paramètres de la publication', + 'LOG_CONFIG_REGISTRATION' => 'Modification des paramètres des inscriptions', + 'LOG_CONFIG_FEED' => 'Modification des paramètres des flux de syndication', + 'LOG_CONFIG_SEARCH' => 'Modification des paramètres de la recherche', + 'LOG_CONFIG_SECURITY' => 'Modification des paramètres de sécurité', + 'LOG_CONFIG_SERVER' => 'Modification des paramètres du serveur', + 'LOG_CONFIG_SETTINGS' => 'Modification des paramètres du forum', + 'LOG_CONFIG_SIGNATURE' => 'Modification des paramètres des signatures', + 'LOG_CONFIG_VISUAL' => 'Modification des mesures de lutte contre les robots indésirables', + + 'LOG_APPROVE_TOPIC' => 'Approbation d’un sujet
» %s', + 'LOG_BUMP_TOPIC' => 'Un utilisateur a remonté un sujet
» %s', + 'LOG_DELETE_POST' => 'Suppression du message « %1$s » publié par %2$s en spécifiant une raison
» %3$s', + 'LOG_DELETE_SHADOW_TOPIC' => 'Suppression d’une redirection de sujet
» %s', + 'LOG_DELETE_TOPIC' => 'Suppression du sujet « %1$s » publié par %2$s en spécifiant une raison
» %3$s', + 'LOG_FORK' => 'Copie d’un sujet
» de %s', + 'LOG_LOCK' => 'Verrouillage d’un sujet
» %s', + 'LOG_LOCK_POST' => 'Verrouillage d’un message
» %s', + 'LOG_MERGE' => 'Fusion de messages dans le sujet
» %s', + 'LOG_MOVE' => 'Déplacement d’un sujet
» de %1$s vers %2$s', + 'LOG_MOVED_TOPIC' => 'Déplacement d’un sujet
» %s', + 'LOG_PM_REPORT_CLOSED' => 'Clôture d’un rapport de MP
» %s', + 'LOG_PM_REPORT_DELETED' => 'Suppression d’un rapport de MP
» %s', + 'LOG_POST_APPROVED' => 'Approbation d’un message
» %s', + 'LOG_POST_DISAPPROVED' => 'Désapprobation du message « %1$s » publié par %3$s en spécifiant une raison
» %2$s', + 'LOG_POST_EDITED' => 'Modification du message « %1$s » publié par %2$s en spécifiant une raison
» %3$s', + 'LOG_POST_RESTORED' => 'Restauration d’un message
» %s', + 'LOG_REPORT_CLOSED' => 'Clôture d’un rapport
» %s', + 'LOG_REPORT_DELETED' => 'Suppression d’un rapport
» %s', + 'LOG_RESTORE_TOPIC' => 'Restauration du sujet « %1$s » publié par
» %2$s', + 'LOG_SOFTDELETE_POST' => 'Suppression du message « %1$s » publié par %2$s en spécifiant une raison
» %3$s', + 'LOG_SOFTDELETE_TOPIC' => 'Suppression du sujet « %1$s » publié par %2$s en spécifiant une raison
» %3$s', + 'LOG_SPLIT_DESTINATION' => 'Déplacement de messages divisés
» vers %s', + 'LOG_SPLIT_SOURCE' => 'Division de messages
» de %s', + + 'LOG_TOPIC_APPROVED' => 'Approbation d’un sujet
» %s', + 'LOG_TOPIC_RESTORED' => 'Restauration d’un sujet
» %s', + 'LOG_TOPIC_DISAPPROVED' => 'Désapprobation du sujet « %1$s » publié par %3$s en spécifiant une raison
%2$s', + 'LOG_TOPIC_RESYNC' => 'Resynchronisation du compteur de sujets
» %s', + 'LOG_TOPIC_TYPE_CHANGED' => 'Modification du type d’un sujet
» %s', + 'LOG_UNLOCK' => 'Déverrouillage d’un sujet
» %s', + 'LOG_UNLOCK_POST' => 'Déverrouillage d’un message
» %s', + + 'LOG_DISALLOW_ADD' => 'Interdiction d’un nom d’utilisateur
» %s', + 'LOG_DISALLOW_DELETE' => 'Autorisation d’un nom d’utilisateur', + + 'LOG_DB_BACKUP' => 'Sauvegarde de la base de données', + 'LOG_DB_DELETE' => 'Suppression de la sauvegarde de la base de données', + 'LOG_DB_RESTORE' => 'Restauration de la sauvegarde de la base de données', + + 'LOG_DOWNLOAD_EXCLUDE_IP' => 'Exclusion d’un nom d’hôte ou d’une adresse IP de la liste des téléchargements
» %s', + 'LOG_DOWNLOAD_IP' => 'Ajout d’un nom d’hôte ou d’une adresse IP dans la liste des téléchargements
» %s', + 'LOG_DOWNLOAD_REMOVE_IP' => 'Suppression d’un nom d’hôte ou d’une adresse IP de la liste des téléchargements
» %s', + + 'LOG_ERROR_JABBER' => 'Une erreur de Jabber est survenue
» %s', + 'LOG_ERROR_EMAIL' => 'Une erreur de courriel est survenue
» %s', + 'LOG_ERROR_CAPTCHA' => 'Une erreur de CAPTCHA est survenue
» %s', + + 'LOG_FORUM_ADD' => 'Création d’un nouveau forum
» %s', + 'LOG_FORUM_COPIED_PERMISSIONS' => 'Copie des permissions d’un forum de %1$s
» %2$s', + 'LOG_FORUM_DEL_FORUM' => 'Suppression d’un forum
» %s', + 'LOG_FORUM_DEL_FORUMS' => 'Suppression d’un forum et de ses sous-forums
» %s', + 'LOG_FORUM_DEL_MOVE_FORUMS' => 'Suppression d’un forum et déplacement de ses sous-forums vers %1$s
» %2$s', + 'LOG_FORUM_DEL_MOVE_POSTS' => 'Suppression d’un forum et déplacement de ses messages vers %1$s
» %2$s', + 'LOG_FORUM_DEL_MOVE_POSTS_FORUMS' => 'Suppression d’un forum et de ses sous-forums, puis déplacement des messages vers %1$s
» %2$s', + 'LOG_FORUM_DEL_MOVE_POSTS_MOVE_FORUMS' => 'Suppression d’un forum et déplacement des messages vers %1$s et des sous-forums vers %2$s
» %3$s', + 'LOG_FORUM_DEL_POSTS' => 'Suppression d’un forum et de ses messages
» %s', + 'LOG_FORUM_DEL_POSTS_FORUMS' => 'Suppression d’un forum, de ses messages et de ses sous-forums
» %s', + 'LOG_FORUM_DEL_POSTS_MOVE_FORUMS' => 'Suppression d’un forum et de ses messages, puis déplacement des sous-forums vers %1$s
» %2$s', + 'LOG_FORUM_EDIT' => 'Modification des informations d’un forum
» %s', + 'LOG_FORUM_MOVE_DOWN' => 'Déplacement du forum %1$s en dessous de %2$s', + 'LOG_FORUM_MOVE_UP' => 'Déplacement du forum %1$s au-dessus de %2$s', + 'LOG_FORUM_SYNC' => 'Resynchronisation d’un forum
» %s', + + 'LOG_GENERAL_ERROR' => 'Une erreur générale est survenue : %1$s
» %2$s', + + 'LOG_GROUP_CREATED' => 'Création d’un nouveau groupe d’utilisateurs
» %s', + 'LOG_GROUP_DEFAULTS' => 'Le groupe « %1$s » a été défini comme groupe par défaut pour les membres
» %2$s', + 'LOG_GROUP_DELETE' => 'Suppression d’un groupe d’utilisateurs
» %s', + 'LOG_GROUP_DEMOTED' => 'Rétrogradation de responsables dans le groupe d’utilisateurs %1$s
» %2$s', + 'LOG_GROUP_PROMOTED' => 'Promotion de membres en tant que responsables dans le groupe d’utilisateurs %1$s
» %2$s', + 'LOG_GROUP_REMOVE' => 'Suppression de membres dans le groupe d’utilisateurs %1$s
» %2$s', + 'LOG_GROUP_UPDATED' => 'Mise à jour des informations d’un groupe d’utilisateurs
» %s', + 'LOG_MODS_ADDED' => 'Ajout de nouveaux responsables dans le groupe d’utilisateurs %1$s
» %2$s', + 'LOG_USERS_ADDED' => 'Ajout de nouveaux membres dans le groupe d’utilisateurs %1$s
» %2$s', + 'LOG_USERS_APPROVED' => 'Approbation d’utilisateurs dans le groupe d’utilisateurs %1$s
» %2$s', + 'LOG_USERS_PENDING' => 'Un ou plusieurs utilisateurs ont demandé à rejoindre le groupe d’utilisateurs « %1$s » et attendent d’être approuvés
» %2$s', + + 'LOG_IMAGE_GENERATION_ERROR' => 'Une erreur est survenue lors de la création d’une image
» Erreur dans %1$s sur la ligne %2$s : %3$s', + + 'LOG_INACTIVE_ACTIVATE' => 'Activation d’utilisateurs inactifs
» %s', + 'LOG_INACTIVE_DELETE' => 'Suppression d’utilisateurs inactifs
» %s', + 'LOG_INACTIVE_REMIND' => 'Envoi d’un courriel de rappel aux utilisateurs inactifs
» %s', + 'LOG_INSTALL_CONVERTED' => 'Conversion de phpBB %1$s vers phpBB %2$s', + 'LOG_INSTALL_INSTALLED' => 'Installation de phpBB %s', + + 'LOG_IP_BROWSER_FORWARDED_CHECK' => 'Échec de la vérification de la session IP, du navigateur ou de « X_FORWARDED_FOR »
»L’adresse IP de l’utilisateur %1$s a été comparée à la session IP « %2$s », la chaîne du navigateur de l’utilisateur %3$s a été comparée à la chaîne de la session du navigateur « %4$s » et la chaîne X_FORWARDED_FOR de l’utilisateur %5$s a été comparée à la chaîne X_FORWARDED_FOR de la session « %6$s ».', + + 'LOG_JAB_CHANGED' => 'Modification d’un compte Jabber', + 'LOG_JAB_PASSCHG' => 'Modification d’un mot de passe de Jabber', + 'LOG_JAB_REGISTER' => 'Inscription d’un compte Jabber', + 'LOG_JAB_SETTINGS_CHANGED' => 'Modification des paramètres de Jabber', + + 'LOG_LANGUAGE_PACK_DELETED' => 'Suppression d’une langue
» %s', + 'LOG_LANGUAGE_PACK_INSTALLED' => 'Installation d’une langue
» %s', + 'LOG_LANGUAGE_PACK_UPDATED' => 'Mise à jour des informations d’une langue
» %s', + 'LOG_LANGUAGE_FILE_REPLACED' => 'Remplacement d’un fichier de langue
» %s', + 'LOG_LANGUAGE_FILE_SUBMITTED' => 'Envoi et déplacement dans le répertoire de stockage d’un fichier de langue
» %s', + + 'LOG_MASS_EMAIL' => 'Envoi d’un courriel de masse
» %s', + + 'LOG_MCP_CHANGE_POSTER' => 'Modification de l’auteur du sujet « %1$s »
» de %2$s vers %3$s', + + 'LOG_MODULE_DISABLE' => 'Désactivation d’un module
» %s', + 'LOG_MODULE_ENABLE' => 'Activation d’un module
» %s', + 'LOG_MODULE_MOVE_DOWN' => 'Abaissement d’un module
» %1$s en dessous de %2$s', + 'LOG_MODULE_MOVE_UP' => 'Rehaussement d’un module
» %1$s au-dessus de %2$s', + 'LOG_MODULE_REMOVED' => 'Suppression d’un module
» %s', + 'LOG_MODULE_ADD' => 'Ajout d’un module
» %s', + 'LOG_MODULE_EDIT' => 'Modification d’un module
» %s', + + 'LOG_A_ROLE_ADD' => 'Ajout d’un rôle d’administrateur
» %s', + 'LOG_A_ROLE_EDIT' => 'Modification d’un rôle d’administrateur
» %s', + 'LOG_A_ROLE_REMOVED' => 'Suppression d’un rôle d’administrateur
» %s', + 'LOG_F_ROLE_ADD' => 'Ajout d’un rôle d’un forum
» %s', + 'LOG_F_ROLE_EDIT' => 'Modification d’un rôle d’un forum
» %s', + 'LOG_F_ROLE_REMOVED' => 'Suppression d’un rôle d’un forum
» %s', + 'LOG_M_ROLE_ADD' => 'Ajout d’un rôle de modérateur
» %s', + 'LOG_M_ROLE_EDIT' => 'Modification d’un rôle de modérateur
» %s', + 'LOG_M_ROLE_REMOVED' => 'Suppression d’un rôle de modérateur
» %s', + 'LOG_U_ROLE_ADD' => 'Ajout d’un rôle d’utilisateur
» %s', + 'LOG_U_ROLE_EDIT' => 'Modification d’un rôle d’utilisateur
» %s', + 'LOG_U_ROLE_REMOVED' => 'Suppression d’un rôle d’utilisateur
» %s', + + 'LOG_PLUPLOAD_TIDY_FAILED' => 'Le rangement a échoué car il n’a pas été possible d’ouvrir %1$s. Veuillez vérifier les permissions.
Exception : %2$s
Localisation : %3$s', + + 'LOG_PROFILE_FIELD_ACTIVATE' => 'Activation d’un champ de profil
» %s', + 'LOG_PROFILE_FIELD_CREATE' => 'Ajout d’un champ de profil
» %s', + 'LOG_PROFILE_FIELD_DEACTIVATE' => 'Désactivation d’un champ de profil
» %s', + 'LOG_PROFILE_FIELD_EDIT' => 'Modification d’un champ de profil
» %s', + 'LOG_PROFILE_FIELD_REMOVED' => 'Suppression d’un champ de profil
» %s', + + 'LOG_PRUNE' => 'Délestage de forums
» %s', + 'LOG_AUTO_PRUNE' => 'Délestage automatique de forums
» %s', + 'LOG_PRUNE_SHADOW' => 'Délestage automatique de redirections de sujet
» %s', + 'LOG_PRUNE_USER_DEAC' => 'Désactivation d’utilisateurs
» %s', + 'LOG_PRUNE_USER_DEL_DEL' => 'Délestage d’utilisateurs et suppression de leurs messages
» %s', + 'LOG_PRUNE_USER_DEL_ANON' => 'Délestage d’utilisateurs et préservation de leurs messages
» %s', + + 'LOG_PURGE_CACHE' => 'Purge du cache', + 'LOG_PURGE_SESSIONS' => 'Purge des sessions', + + 'LOG_RANK_ADDED' => 'Ajout d’un nouveau rang
» %s', + 'LOG_RANK_REMOVED' => 'Suppression d’un rang
» %s', + 'LOG_RANK_UPDATED' => 'Mise à jour d’un rang
» %s', + + 'LOG_REASON_ADDED' => 'Ajout d’un rapport ou d’une raison
» %s', + 'LOG_REASON_REMOVED' => 'Suppression d’un rapport ou d’une raison
» %s', + 'LOG_REASON_UPDATED' => 'Mise à jour d’un rapport ou d’une raison
» %s', + + 'LOG_REFERER_INVALID' => 'Échec de la validation du référant
»Le référant était « %1$s ». La requête a été rejetée et la session a été interrompue.', + 'LOG_RESET_DATE' => 'Réinitialisation de la date d’ouverture du forum', + 'LOG_RESET_ONLINE' => 'Réinitialisation du nombre maximal d’utilisateurs en ligne', + 'LOG_RESYNC_FILES_STATS' => 'Resynchronisation des fichiers statistiques', + 'LOG_RESYNC_POSTCOUNTS' => 'Réinitialisation du compteur de messages des utilisateurs', + 'LOG_RESYNC_POST_MARKING' => 'Resynchronisation des sujets pointés', + 'LOG_RESYNC_STATS' => 'Resynchronisation des messages, des sujets et des statistiques d’un utilisateur', + + 'LOG_SEARCH_INDEX_CREATED' => 'Création d’un index de recherche
» %s', + 'LOG_SEARCH_INDEX_REMOVED' => 'Suppression d’un index de recherche
» %s', + 'LOG_SPHINX_ERROR' => 'Une erreur de Sphinx est survenue
» %s', + 'LOG_STYLE_ADD' => 'Ajout d’un nouveau style
» %s', + 'LOG_STYLE_DELETE' => 'Suppression d’un style
» %s', + 'LOG_STYLE_EDIT_DETAILS' => 'Modification d’un style
» %s', + 'LOG_STYLE_EXPORT' => 'Exportation d’un style
» %s', + + // @deprecated 3.1 + 'LOG_TEMPLATE_ADD_DB' => 'Ajout d’un nouvel ensemble de modèles à la base de données
» %s', + // @deprecated 3.1 + 'LOG_TEMPLATE_ADD_FS' => 'Ajout d’un nouvel ensemble de modèles au système de fichiers
» %s', + 'LOG_TEMPLATE_CACHE_CLEARED' => 'Rafraîchissement de l’ensemble de modèles de %1$s
» %2$s', + 'LOG_TEMPLATE_DELETE' => 'Suppression d’un ensemble de modèles
» %s', + 'LOG_TEMPLATE_EDIT' => 'Modification de l’ensemble de modèles de %1$s
» %2$s', + 'LOG_TEMPLATE_EDIT_DETAILS' => 'Modification des informations d’un ensemble de modèles
» %s', + 'LOG_TEMPLATE_EXPORT' => 'Exportation d’un ensemble de modèles
» %s', + // @deprecated 3.1 + 'LOG_TEMPLATE_REFRESHED' => 'Rafraîchissement d’un ensemble de modèles
» %s', + + // @deprecated 3.1 + 'LOG_THEME_ADD_DB' => 'Ajout d’un nouvel ensemble de thèmes à la base de données
» %s', + // @deprecated 3.1 + 'LOG_THEME_ADD_FS' => 'Ajout d’un nouveau ensemble de thèmes au système de fichiers
» %s', + 'LOG_THEME_DELETE' => 'Suppression d’un ensemble de thèmes
» %s', + 'LOG_THEME_EDIT_DETAILS' => 'Modification des informations d’un ensemble de thèmes
» %s', + 'LOG_THEME_EDIT' => 'Modification de l’ensemble de thèmes de %1$s', + 'LOG_THEME_EDIT_FILE' => 'Modification de l’ensemble de thèmes de %1$s
» Le fichier modifié est %2$s', + 'LOG_THEME_EXPORT' => 'Exportation d’un ensemble de thèmes
» %s', + // @deprecated 3.1 + 'LOG_THEME_REFRESHED' => 'Rafraîchissement d’un ensemble de thèmes
» %s', + + 'LOG_UPDATE_DATABASE' => 'Mise à jour de la base de données de la version %1$s à la version %2$s', + 'LOG_UPDATE_PHPBB' => 'Mise à jour de phpBB de la version %1$s à la version %2$s', + + 'LOG_USER_ACTIVE' => 'Activation du compte d’un utilisateur
» %s', + 'LOG_USER_BAN_USER' => 'Bannissement du compte d’un utilisateur depuis la gestion des utilisateurs en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_USER_BAN_IP' => 'Bannissement d’une adresse IP depuis la gestion des utilisateurs en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_USER_BAN_EMAIL' => 'Bannissement d’une adresse de courriel depuis la gestion des utilisateurs en spécifiant comme raison « %1$s »
» %2$s', + 'LOG_USER_DELETED' => 'Suppression du compte d’un utilisateur
» %s', + 'LOG_USER_DEL_ATTACH' => 'Suppression de toutes les pièces jointes d’un utilisateur
» %s', + 'LOG_USER_DEL_AVATAR' => 'Suppression de l’avatar d’un utilisateur
» %s', + 'LOG_USER_DEL_OUTBOX' => 'Nettoyage de la boîte d’envoi d’un utilisateur
» %s', + 'LOG_USER_DEL_POSTS' => 'Suppression de tous les messages d’un utilisateur
» %s', + 'LOG_USER_DEL_SIG' => 'Suppression de la signature d’un utilisateur
» %s', + 'LOG_USER_INACTIVE' => 'Désactivation du compte d’un utilisateur
» %s', + 'LOG_USER_MOVE_POSTS' => 'Déplacement des messages d’un utilisateur
» messages de %1$s vers le forum « %2$s »', + 'LOG_USER_NEW_PASSWORD' => 'Modification du mot de passe d’un utilisateur
» %s', + 'LOG_USER_REACTIVATE' => 'Réactivation forcée du compte d’un utilisateur
» %s', + 'LOG_USER_REMOVED_NR' => 'Suppression du statut d’utilisateur nouvellement inscrit du compte d’un utilisateur
» %s', + + 'LOG_USER_UPDATE_EMAIL' => 'Modification de l’adresse de courriel de l’utilisateur %1$s
» de « %2$s » à « %3$s »', + 'LOG_USER_UPDATE_NAME' => 'Modification d’un nom d’utilisateur
» de « %1$s » à « %2$s »', + 'LOG_USER_USER_UPDATE' => 'Mise à jour des informations du compte d’un utilisateur
» %s', + + 'LOG_USER_ACTIVE_USER' => 'Activation du compte d’un utilisateur', + 'LOG_USER_DEL_AVATAR_USER' => 'Suppression de l’avatar d’un utilisateur', + 'LOG_USER_DEL_SIG_USER' => 'Suppression de la signature d’un utilisateur', + 'LOG_USER_FEEDBACK' => 'Ajout d’une remarque concernant un utilisateur
» %s', + 'LOG_USER_GENERAL' => 'Ajout d’un élément :
» %s', + 'LOG_USER_INACTIVE_USER' => 'Désactivation du compte d’un utilisateur', + 'LOG_USER_LOCK' => 'Verrouillage du propre sujet d’un utilisateur
» %s', + 'LOG_USER_MOVE_POSTS_USER' => 'Déplacement de tous les messages vers un forum» %s', + 'LOG_USER_REACTIVATE_USER' => 'Réactivation forcée du compte d’un utilisateur', + 'LOG_USER_UNLOCK' => 'Déverrouillage du propre sujet d’un utilisateur
» %s', + 'LOG_USER_WARNING' => 'Avertissement prononcé à l’encontre d’un utilisateur
» %s', + 'LOG_USER_WARNING_BODY' => 'L’avertissement suivant a été prononcé à l’encontre de cet utilisateur
» %s', + + 'LOG_USER_GROUP_CHANGE' => 'Modification par un utilisateur de son groupe d’utilisateurs par défaut
» %s', + 'LOG_USER_GROUP_DEMOTE' => 'Rétrogradation par un utilisateur de son statut de responsable d’un groupe d’utilisateurs
» %s', + 'LOG_USER_GROUP_JOIN' => 'Adhésion d’un utilisateur à un groupe d’utilisateurs
» %s', + 'LOG_USER_GROUP_JOIN_PENDING' => 'Adhésion en attente d’approbation d’un utilisateur à un groupe d’utilisateurs
» %s', + 'LOG_USER_GROUP_RESIGN' => 'Désinscription d’un utilisateur d’un groupe d’utilisateurs
» %s', + + 'LOG_WARNING_DELETED' => 'Suppression d’un avertissement prononcé à l’encontre d’un utilisateur
» %s', + 'LOG_WARNINGS_DELETED' => [ + 1 => 'Suppression d’un avertissement prononcé à l’encontre d’un utilisateur
» %1$s', + 2 => 'Suppression de %2$d avertissements prononcés à l’encontre d’un utilisateur
» %1$s', // Example: 'Deleted 2 user warnings
» username' + ], + 'LOG_WARNINGS_DELETED_ALL' => 'Suppression de tous les avertissements prononcés à l’encontre d’un utilisateur
» %s', + + 'LOG_WORD_ADD' => 'Ajout d’une censure de mot
» %s', + 'LOG_WORD_DELETE' => 'Suppression d’une censure de mot
» %s', + 'LOG_WORD_EDIT' => 'Modification d’une censure de mot
» %s', + + 'LOG_EXT_ENABLE' => 'Activation d’une extension
» %s', + 'LOG_EXT_DISABLE' => 'Désactivation d’une extension
» %s', + 'LOG_EXT_PURGE' => 'Suppression des données d’une extension
» %s', + 'LOG_EXT_UPDATE' => 'Mise à jour d’une extension
» %s', +]); diff --git a/language/fr/acp/database.php b/language/fr/acp/database.php new file mode 100644 index 0000000..3a208f6 --- /dev/null +++ b/language/fr/acp/database.php @@ -0,0 +1,76 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Database Backup/Restore +$lang = array_merge($lang, [ + 'ACP_BACKUP_EXPLAIN' => 'Depuis cette page, vous pouvez sauvegarder toutes les données relatives à votre forum. L’archive de sauvegarde sera stockée dans votre répertoire « store/ ». Selon la configuration de votre serveur, vous pourrez compresser cette archive sous plusieurs formats.', + 'ACP_RESTORE_EXPLAIN' => 'Cette opération effectuera une restauration complète de toutes les tables de phpBB à partir d’un fichier de sauvegarde. Si votre serveur est compatible avec cette fonctionnalité, vous pouvez utiliser un fichier texte compressé sous le format GZip ou BZip2 qui sera automatiquement décompressé. Veuillez noter que cette opération remplacera toutes les données existantes. La restauration est un processus qui peut durer un certain temps, veillez à ne pas vous déplacer sur une autre page tant que l’opération n’est pas terminée. Les sauvegardes sont stockées dans le répertoire « store/ » et sont supposées être générées par l’outil de restauration présent par défaut dans le logiciel phpBB. Il est possible que la restauration des bases de données qui n’ont pas été sauvegardées avec cet outil ne fonctionnent pas.', + + 'BACKUP_DELETE' => 'Le fichier de sauvegarde a été supprimé.', + 'BACKUP_INVALID' => 'Le fichier de sauvegarde que vous avez sélectionné est invalide.', + 'BACKUP_NOT_SUPPORTED' => 'Le fichier de sauvegarde que vous avez sélectionné n’est pas supporté.', + 'BACKUP_OPTIONS' => 'Options de sauvegarde', + 'BACKUP_SUCCESS' => 'Le fichier de sauvegarde a été créé.', + 'BACKUP_TYPE' => 'Type de sauvegarde', + + 'DATABASE' => 'Utilitaires de la base de données', + 'DATA_ONLY' => 'Données uniquement', + 'DELETE_BACKUP' => 'Supprimer la sauvegarde', + 'DELETE_SELECTED_BACKUP' => 'Êtes-vous sûr de vouloir supprimer cette sauvegarde ?', + 'DESELECT_ALL' => 'Tout désélectionner', + 'DOWNLOAD_BACKUP' => 'Télécharger la sauvegarde', + + 'FILE_TYPE' => 'Type de fichier', + 'FILE_WRITE_FAIL' => 'Le répertoire de stockage est en lecture seule. Veuillez modifier ses droits d’accès pour écriture par votre serveur.', + 'FULL_BACKUP' => 'Complète', + + 'RESTORE_FAILURE' => 'Le fichier de sauvegarde semble corrompu.', + 'RESTORE_OPTIONS' => 'Options de restauration', + 'RESTORE_SELECTED_BACKUP' => 'Êtes-vous sûr de vouloir restaurer cette sauvegarde ?', + 'RESTORE_SUCCESS' => 'La base de données a été restaurée.

Votre forum devrait être tel qu’il était lors de la dernière sauvegarde.', + + 'SELECT_ALL' => 'Tout sélectionner', + 'SELECT_FILE' => 'Sélectionner un fichier', + 'START_BACKUP' => 'Démarrer la sauvegarde', + 'START_RESTORE' => 'Démarrer la restauration', + 'STORE_AND_DOWNLOAD' => 'Stocker et télécharger', + 'STORE_LOCAL' => 'Stocker le fichier en local', + 'STRUCTURE_ONLY' => 'Structure uniquement', + + 'TABLE_SELECT' => 'Sélection de table', + 'TABLE_SELECT_ERROR' => 'Vous devez sélectionner au moins une table.', +]); diff --git a/language/fr/acp/email.php b/language/fr/acp/email.php new file mode 100644 index 0000000..a8452a5 --- /dev/null +++ b/language/fr/acp/email.php @@ -0,0 +1,67 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Email settings +$lang = array_merge($lang, [ + 'ACP_MASS_EMAIL_EXPLAIN' => 'Depuis cette page, vous pouvez envoyer un courriel à la totalité des utilisateurs ou aux utilisateurs d’un groupe d’utilisateurs spécifique qui acceptent la réception de courriels de masse. Pour ce faire, un courriel sera envoyé à l’adresse de courriel renseignée par les administrateurs et une copie sera adressée à tous les destinataires. La configuration par défaut est limitée à 20 destinataires par courriel, mais si ce nombre est dépassé, des courriels supplémentaires seront envoyés. Sachez également que plus les destinataires sont nombreux, plus le délai d’exécution est important. Il est normal que l’envoi d’un courriel de masse prenne un certain temps, veillez à ne pas vous déplacer sur une autre page tant que l’opération n’est pas totalement terminée.', + 'ALL_USERS' => 'Tous les utilisateurs', + + 'COMPOSE' => 'Rédiger', + + 'EMAIL_SEND_ERROR' => 'Une ou plusieurs erreurs sont survenues lors de l’envoi du courriel. Pour plus d’informations, veuillez consulter l’%shistorique des erreurs%s.', + 'EMAIL_SENT' => 'Le message a été envoyé.', + 'EMAIL_SENT_QUEUE' => 'Le message est en file d’attente et sera envoyé ultérieurement.', + + 'LOG_SESSION' => 'Historique de la session de la messagerie électronique vers l’historique des erreurs critiques', + + 'SEND_IMMEDIATELY' => 'Envoyer en temps réel', + 'SEND_TO_GROUP' => 'Envoyer à un groupe d’utilisateurs', + 'SEND_TO_USERS' => 'Envoyer à des utilisateurs', + 'SEND_TO_USERS_EXPLAIN' => 'Les noms d’utilisateurs spécifiés dans ce champ remplaceront le groupe d’utilisateurs sélectionné dans le menu déroulant ci-dessus. Veuillez saisir chaque nom d’utilisateur sur une nouvelle ligne.', + + 'MAIL_BANNED' => 'Envoyer le courriel aux utilisateurs bannis', + 'MAIL_BANNED_EXPLAIN' => 'Les membres bannis d’un groupe d’utilisateurs destinataire d’un courriel de masse recevront également le courriel.', + 'MAIL_HIGH_PRIORITY' => 'Élevée', + 'MAIL_LOW_PRIORITY' => 'Faible', + 'MAIL_NORMAL_PRIORITY' => 'Normale', + 'MAIL_PRIORITY' => 'Priorité du courriel', + 'MASS_MESSAGE' => 'Message', + 'MASS_MESSAGE_EXPLAIN' => 'Le message ne doit contenir que du texte brut. Toutes les balises seront automatiquement supprimées.', + + 'NO_EMAIL_MESSAGE' => 'Vous devez saisir un message.', + 'NO_EMAIL_SUBJECT' => 'Vous devez saisir le sujet de votre message.', +]); diff --git a/language/fr/acp/extensions.php b/language/fr/acp/extensions.php new file mode 100644 index 0000000..ac86307 --- /dev/null +++ b/language/fr/acp/extensions.php @@ -0,0 +1,134 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'EXTENSION' => 'Extension', + 'EXTENSIONS' => 'Extensions', + 'EXTENSIONS_ADMIN' => 'Gestionnaire d’extensions', + 'EXTENSIONS_EXPLAIN' => 'Le gestionnaire d’extensions est un outil vous permettant de gérer tous les statuts de vos extensions et de consulter les informations associées.', + 'EXTENSION_INVALID_LIST' => 'L’extension « %s » est invalide.
%s

', + 'EXTENSION_NOT_AVAILABLE' => 'Cette extension n’est pas disponible sur ce forum. Veuillez vérifier la compatibilité de vos versions de phpBB et de PHP.', + 'EXTENSION_DIR_INVALID' => 'Cette extension est construite avec une structure de répertoire invalide et ne peut donc pas être activée.', + 'EXTENSION_NOT_ENABLEABLE' => 'Cette extension ne peut pas être activée. Veuillez vérifier les prérequis de l’extension.', + 'EXTENSION_NOT_INSTALLED' => 'L’extension « %s » n’est pas disponible. Veuillez vérifier si elle a été correctement installée.', + + 'DETAILS' => 'Informations', + + 'EXTENSIONS_DISABLED' => 'Extensions désactivées', + 'EXTENSIONS_ENABLED' => 'Extensions activées', + + 'EXTENSION_DELETE_DATA' => 'Supprimer les données', + 'EXTENSION_DISABLE' => 'Désactiver', + 'EXTENSION_ENABLE' => 'Activer', + + 'EXTENSION_DELETE_DATA_EXPLAIN' => 'La suppression des données d’une extension supprime toutes ses données et sa configuration. Les fichiers de l’extension sont conservés afin de pouvoir la réactiver ultérieurement.', + 'EXTENSION_DISABLE_EXPLAIN' => 'La désactivation d’une extension conserve ses fichiers, ses données et sa configuration mais supprime toutes les fonctionnalités qu’elle ajoute.', + 'EXTENSION_ENABLE_EXPLAIN' => 'L’activation d’une extension vous permet de l’utiliser sur votre forum.', + + 'EXTENSION_DELETE_DATA_IN_PROGRESS' => 'Les données de l’extension sont en cours de suppression. Veillez à ne pas quitter ou rafraîchir la page avant la fin de toutes les opérations.', + 'EXTENSION_DISABLE_IN_PROGRESS' => 'L’extension est en cours de désactivation. Veillez à ne pas quitter ou rafraîchir la page avant la fin de toutes les opérations.', + 'EXTENSION_ENABLE_IN_PROGRESS' => 'L’extension est en cours d’activation. Veillez à ne pas quitter ou rafraîchir la page avant la fin de toutes les opérations.', + + 'EXTENSION_DELETE_DATA_SUCCESS' => 'L’extension a été supprimée', + 'EXTENSION_DISABLE_SUCCESS' => 'L’extension a été désactivée', + 'EXTENSION_ENABLE_SUCCESS' => 'L’extension a été activée', + + 'EXTENSION_NAME' => 'Nom de l’extension', + 'EXTENSION_ACTIONS' => 'Opérations', + 'EXTENSION_OPTIONS' => 'Options', + 'EXTENSION_INSTALL_HEADLINE' => 'Installer une extension', + 'EXTENSION_INSTALL_EXPLAIN' => '
    +
  1. Téléchargez une extension à partir de la base de données des extensions de phpBB
  2. +
  3. Décompressez et transférez l’extension dans le répertoire « ext/ » de votre forum
  4. +
  5. Activez l’extension à partir du gestionnaire d’extensions
  6. +
', + 'EXTENSION_UPDATE_HEADLINE' => 'Mettre à jour une extension', + 'EXTENSION_UPDATE_EXPLAIN' => '
    +
  1. Désactivez l’extension
  2. +
  3. Supprimez les fichiers de l’extension de votre serveur
  4. +
  5. Transférez les nouveaux fichiers de l’extension sur votre serveur
  6. +
  7. Activez l’extension
  8. +
', + 'EXTENSION_REMOVE_HEADLINE' => 'Supprimer totalement une extension', + 'EXTENSION_REMOVE_EXPLAIN' => '
    +
  1. Désactivez l’extension
  2. +
  3. Supprimez les données de l’extension
  4. +
  5. Supprimez les fichiers de l’extension de votre serveur
  6. +
', + + 'EXTENSION_DELETE_DATA_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer les données de « %s » ?

Cela supprimera toutes les données et la configuration de l’extension qui ne pourront pas être restaurés !', + 'EXTENSION_DISABLE_CONFIRM' => 'Êtes-vous sûr de vouloir désactiver l’extension « %s » ?', + 'EXTENSION_ENABLE_CONFIRM' => 'Êtes-vous sûr de vouloir activer l’extension « %s » ?', + 'EXTENSION_FORCE_UNSTABLE_CONFIRM' => 'Êtes-vous sûr de vouloir forcer l’utilisation d’une version instable ?', + + 'RETURN_TO_EXTENSION_LIST' => 'Revenir à la liste des extensions', + + 'EXT_DETAILS' => 'Informations sur l’extension', + 'DISPLAY_NAME' => 'Afficher le nom', + 'CLEAN_NAME' => 'Effacer le nom', + 'TYPE' => 'Type', + 'DESCRIPTION' => 'Description', + 'VERSION' => 'Version', + 'HOMEPAGE' => 'Page d’accueil', + 'PATH' => 'Chemin du fichier', + 'TIME' => 'Date de sortie', + 'LICENSE' => 'Licence', + + 'REQUIREMENTS' => 'Prérequis', + 'PHPBB_VERSION' => 'Version de phpBB', + 'PHP_VERSION' => 'Version de PHP', + 'AUTHOR_INFORMATION' => 'Informations sur l’auteur', + 'AUTHOR_NAME' => 'Nom', + 'AUTHOR_EMAIL' => 'Adresse de courriel', + 'AUTHOR_HOMEPAGE' => 'Page d’accueil', + 'AUTHOR_ROLE' => 'Rôle', + + 'NOT_UP_TO_DATE' => '« %s » n’est pas à jour', + 'UP_TO_DATE' => '« %s » est à jour', + 'ANNOUNCEMENT_TOPIC' => 'Annonce de sortie', + 'DOWNLOAD_LATEST' => 'Télécharger', + 'NO_VERSIONCHECK' => 'Aucune information concernant la vérification de mise à jour n’est disponible.', + + 'VERSIONCHECK_FORCE_UPDATE_ALL' => 'Vérifier de nouveau les mises à jour', + 'FORCE_UNSTABLE' => 'Toujours vérifier la disponibilité de versions instables', + 'EXTENSIONS_VERSION_CHECK_SETTINGS' => 'Paramètres de la vérification des mises à jour', + + 'BROWSE_EXTENSIONS_DATABASE' => 'Parcourir la base de données des extensions', + + 'META_FIELD_NOT_SET' => 'Le méta-champ « %s » est requis et doit être configuré.', + 'META_FIELD_INVALID' => 'Le méta-champ « %s » est invalide.', +]); diff --git a/language/fr/acp/forums.php b/language/fr/acp/forums.php new file mode 100644 index 0000000..741355d --- /dev/null +++ b/language/fr/acp/forums.php @@ -0,0 +1,164 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Forum Admin +$lang = array_merge($lang, [ + 'AUTO_PRUNE_DAYS' => 'Intervalle du délestage automatique des sujets inactifs', + 'AUTO_PRUNE_DAYS_EXPLAIN' => 'Le nombre de jours qui s’écouleront entre la dernière publication d’un message dans un sujet et la suppression du sujet.', + 'AUTO_PRUNE_FREQ' => 'Fréquence du délestage automatique', + 'AUTO_PRUNE_FREQ_EXPLAIN' => 'Le nombre de jours qui s’écouleront entre les délestages automatiques.', + 'AUTO_PRUNE_VIEWED' => 'Intervalle du délestage automatique des sujets impopulaires', + 'AUTO_PRUNE_VIEWED_EXPLAIN' => 'Le nombre de jours qui s’écouleront entre la dernière consultation d’un sujet et la suppression du sujet.', + 'AUTO_PRUNE_SHADOW_FREQ' => 'Fréquence du délestage automatique des redirections de sujet', + 'AUTO_PRUNE_SHADOW_DAYS' => 'Intervalle du délestage automatique des redirections de sujet', + 'AUTO_PRUNE_SHADOW_DAYS_EXPLAIN' => 'Le nombre de jours qui s’écouleront entre la dernière visite d’une redirection de sujet et la suppression de la redirection de sujet.', + 'AUTO_PRUNE_SHADOW_FREQ_EXPLAIN' => 'Le nombre de jours qui s’écouleront entre les délestages automatiques.', + + 'CONTINUE' => 'Continuer', + 'COPY_PERMISSIONS' => 'Copier les permissions', + 'COPY_PERMISSIONS_EXPLAIN' => 'Pour faciliter la configuration des permissions de votre nouveau forum, vous pouvez copier les permissions d’un forum déjà existant.', + 'COPY_PERMISSIONS_ADD_EXPLAIN' => 'Une fois créé, le forum détiendra les mêmes permissions que celles du forum que vous avez sélectionné. Si vous ne sélectionnez aucun forum, le forum nouvellement créé ne sera pas visible tant que ses permissions ne soient pas définies.', + 'COPY_PERMISSIONS_EDIT_EXPLAIN' => 'Si vous copiez des permissions, ce forum détiendra les mêmes permissions que celles du forum que vous avez sélectionné. Toutes les permissions définies antérieurement dans ce forum seront remplacées par les permissions du forum sélectionné. Si vous ne sélectionnez aucun forum, les permissions actuelles seront conservées.', + 'COPY_TO_ACL' => 'Si vous le souhaitez, vous pouvez également %sconfigurer les nouvelles permissions%s de ce forum.', + 'CREATE_FORUM' => 'Créer un nouveau forum', + + 'DECIDE_MOVE_DELETE_CONTENT' => 'Supprimer le contenu ou déplacer ce dernier vers un forum', + 'DECIDE_MOVE_DELETE_SUBFORUMS' => 'Supprimer les sous-forums ou déplacer ces derniers vers un forum', + 'DEFAULT_STYLE' => 'Style par défaut', + 'DELETE_ALL_POSTS' => 'Supprimer les messages', + 'DELETE_SUBFORUMS' => 'Supprimer les sous-forums et les messages', + 'DISPLAY_ACTIVE_TOPICS' => 'Activer les sujets actifs', + 'DISPLAY_ACTIVE_TOPICS_EXPLAIN' => 'Si cette option est activée, les sujets actifs des sous-forums sélectionnés seront affichés sous cette catégorie.', + + 'EDIT_FORUM' => 'Modifier le forum', + 'ENABLE_INDEXING' => 'Activer l’indexation de la recherche', + 'ENABLE_INDEXING_EXPLAIN' => 'Si cette option est activée, les messages publiés dans ce forum seront indexés afin qu’ils puissent être pris en compte lors des recherches.', + 'ENABLE_POST_REVIEW' => 'Autoriser la révision des messages', + 'ENABLE_POST_REVIEW_EXPLAIN' => 'Si cette option est activée, les utilisateurs pourront modifier leurs messages malgré que d’autres utilisateurs aient répondu au sujet. Il n’est pas conseillé d’activer cette option sur les forums de discussions.', + 'ENABLE_QUICK_REPLY' => 'Autoriser la réponse rapide', + 'ENABLE_QUICK_REPLY_EXPLAIN' => 'Si cette option est activée, les utilisateurs pourront utiliser la réponse rapide sur ce forum. Cette option n’est pas prise en compte si la réponse rapide a été désactivée dans les paramètres de la publication. Le champ de la réponse rapide ne sera visible qu’aux utilisateurs autorisés à publier dans ce forum.', + 'ENABLE_RECENT' => 'Afficher les sujets actifs', + 'ENABLE_RECENT_EXPLAIN' => 'Si cette option est activée, les sujets publiés dans ce forum seront affichés dans la liste des sujets actifs.', + 'ENABLE_TOPIC_ICONS' => 'Activer les icônes de sujet', + + 'FORUM_ADMIN' => 'Administration du forum', + 'FORUM_ADMIN_EXPLAIN' => 'Dans phpBB 3.2, tout est basé sur la notion de forum. Ainsi, même les catégories ne sont plus qu’un type de forum. Chaque forum peut contenir un nombre illimité de sous-forums et vous pouvez spécifier s’ils peuvent contenir ou non des messages (comme c’était le cas avec les anciennes catégories). Depuis cette page, vous pouvez, de manière individuelle, ajouter, modifier, supprimer, verrouiller ou déverrouiller les forums et définir certaines permissions additionnelles. Si certains messages et sujets ne sont plus synchronisés, vous pouvez également exécuter une resynchronisation. Si vous souhaitez que les forums que vous créez soient visibles publiquement, n’oubliez pas de copier ou de définir les permissions de chaque nouveau forum.', + 'FORUM_AUTO_PRUNE' => 'Activer le délestage automatique', + 'FORUM_AUTO_PRUNE_EXPLAIN' => 'Si cette option est activée, les sujets de ce forum seront délestés selon les paramètres de fréquence et d’intervalles du délestage automatique spécifiés ci-dessous.', + 'FORUM_CREATED' => 'Le forum a été créé.', + 'FORUM_DATA_NEGATIVE' => 'Les paramètres de délestage ne peuvent pas être négatifs.', + 'FORUM_DESC_TOO_LONG' => 'La description du forum est trop longue, elle doit être inférieure à 4000 caractères.', + 'FORUM_DELETE' => 'Supprimer un forum', + 'FORUM_DELETE_EXPLAIN' => 'Le formulaire ci-dessous vous permet de supprimer un forum. Si le forum contient des sous-forums, des sujets ou des messages, vous pouvez sélectionner l’emplacement où vous souhaitez déplacer ces derniers.', + 'FORUM_DELETED' => 'Le forum a été supprimé.', + 'FORUM_DESC' => 'Description', + 'FORUM_DESC_EXPLAIN' => 'Les balises HTML saisies dans ce champ seront affichées comme telles. Si le type de forum sélectionné correspond à une catégorie, la description ne sera pas affichée.', + 'FORUM_EDIT_EXPLAIN' => 'Le formulaire ci-dessous vous permet de personnaliser le forum sélectionné. Veuillez noter que la modération et que les paramètres relatifs aux compteurs de messages sont disponibles depuis les permissions des forums aux utilisateurs et aux groupes d’utilisateurs.', + 'FORUM_IMAGE' => 'Image du forum', + 'FORUM_IMAGE_EXPLAIN' => 'L’image associée à ce forum. L’emplacement doit être relatif à la racine du répertoire de votre forum.', + 'FORUM_IMAGE_NO_EXIST' => 'L’image du forum que vous avez spécifiée est introuvable', + 'FORUM_LINK_EXPLAIN' => 'Le lien complet, incluant le protocole « http:// » ou « https:// », qui redirigera les utilisateurs vers la destination souhaitée, tel que « https://www.phpbb.com/ ».', + 'FORUM_LINK_TRACK' => 'Afficher le nombre de redirections', + 'FORUM_LINK_TRACK_EXPLAIN' => 'Le nombre de redirections effectués à partir du forum-lien seront comptabilisées et affichées.', + 'FORUM_NAME' => 'Nom du forum', + 'FORUM_NAME_EMPTY' => 'Vous devez saisir le nom de ce forum.', + 'FORUM_PARENT' => 'Forum parent', + 'FORUM_PASSWORD' => 'Mot de passe du forum', + 'FORUM_PASSWORD_CONFIRM' => 'Confirmer le mot de passe du forum', + 'FORUM_PASSWORD_CONFIRM_EXPLAIN' => 'Ce champ ne doit être renseigné que si un mot de passe a été spécifié ci-dessus.', + 'FORUM_PASSWORD_EXPLAIN' => 'Le mot de passe sera demandé afin d’accéder à ce forum. Il est préférable d’utiliser le système de permissions.', + 'FORUM_PASSWORD_UNSET' => 'Supprimer le mot de passe du forum', + 'FORUM_PASSWORD_UNSET_EXPLAIN' => 'Ne cochez cette case que si vous souhaitez supprimer le mot de passe du forum.', + 'FORUM_PASSWORD_OLD' => 'Le mot de passe du forum utilise une méthode de hachage obsolète que vous devriez remplacer.', + 'FORUM_PASSWORD_MISMATCH' => 'Les mots de passe que vous avez saisis ne concordent pas.', + 'FORUM_PRUNE_SETTINGS' => 'Paramètres du délestage de forum', + 'FORUM_PRUNE_SHADOW' => 'Activer le délestage automatique des redirections de sujet', + 'FORUM_PRUNE_SHADOW_EXPLAIN' => 'Les redirections de sujet seront supprimées du forum. Indiquez ci-dessous les paramètres de fréquence et d’ancienneté.', + 'FORUM_RESYNCED' => 'Le forum « %s » a été resynchronisé.', + 'FORUM_RULES_EXPLAIN' => 'Les règles du forum seront affichées sur toutes les pages de ce forum.', + 'FORUM_RULES_LINK' => 'Lien vers les règles du forum', + 'FORUM_RULES_LINK_EXPLAIN' => 'Le lien vers la page ou le message contenant les règles de votre forum. Le lien remplacera les règles du forum qui ont été spécifiées dans le champ ci-dessous.', + 'FORUM_RULES_PREVIEW' => 'Prévisualisation des règles du forum', + 'FORUM_RULES_TOO_LONG' => 'Les règles du forum ne doivent pas dépasser 4000 caractères.', + 'FORUM_SETTINGS' => 'Paramètres du forum', + 'FORUM_STATUS' => 'Statut du forum', + 'FORUM_STYLE' => 'Style du forum', + 'FORUM_TOPICS_PAGE' => 'Nombre de sujets par page', + 'FORUM_TOPICS_PAGE_EXPLAIN' => 'Si cette valeur est autre que « 0 », elle remplacera la configuration par défaut du nombre de sujets par page.', + 'FORUM_TYPE' => 'Type de forum', + 'FORUM_UPDATED' => 'Les informations du forum ont été mises à jour.', + + 'FORUM_WITH_SUBFORUMS_NOT_TO_LINK' => 'Vous souhaitez modifier un forum contenant des sous-forums dans lesquels vous pouvez rédiger des messages en un forum-lien. Avant de procéder à cette opération, veuillez déplacer tous les sous-forums hors de ce forum. Une fois que le forum sera modifié en forum-lien, vous ne pourrez plus consulter ses sous-forums.', + + 'GENERAL_FORUM_SETTINGS' => 'Paramètres généraux du forum', + + 'LINK' => 'Lien', + 'LIST_INDEX' => 'Lister ce forum dans la légende du forum parent', + 'LIST_INDEX_EXPLAIN' => 'Si cette option est activée, ce forum sera listé sur l’index du forum et sur divers endroits dans un lien disponible dans la légende du forum parent.', + 'LIST_SUBFORUMS' => 'Lister les sous-forums dans la légende de ce forum', + 'LIST_SUBFORUMS_EXPLAIN' => 'Si cette option est activée, les sous-forums de ce forum seront listés sur l’index du forum et sur divers endroits dans un lien disponible dans la légende de ce forum.', + 'LOCKED' => 'Verrouillé', + + 'MOVE_POSTS_NO_POSTABLE_FORUM' => 'Ce forum ne peut pas recevoir le contenu que vous souhaitez déplacer. Veuillez sélectionner un forum dans lequel il est possible de publier des messages.', + 'MOVE_POSTS_TO' => 'Déplacer les messages dans', + 'MOVE_SUBFORUMS_TO' => 'Déplacer les sous-forums dans', + + 'NO_DESTINATION_FORUM' => 'Vous devez sélectionner un forum destiné à recevoir le contenu.', + 'NO_FORUM_ACTION' => 'Vous devez sélectionner une action à effectuer avec le contenu du forum.', + 'NO_PARENT' => 'Aucun parent', + 'NO_PERMISSIONS' => 'Ne pas copier de permissions', + 'NO_PERMISSION_FORUM_ADD' => 'Vous ne pouvez pas ajouter de forum.', + 'NO_PERMISSION_FORUM_DELETE' => 'Vous ne pouvez pas supprimer de forum.', + + 'PARENT_IS_LINK_FORUM' => 'Ce forum parent est un forum-lien et ne peut pas contenir d’autres forums. Veuillez sélectionner une catégorie ou un forum comme forum parent.', + 'PARENT_NOT_EXIST' => 'Le forum parent est introuvable.', + 'PRUNE_ANNOUNCEMENTS' => 'Délester les annonces', + 'PRUNE_STICKY' => 'Délester les notes', + 'PRUNE_OLD_POLLS' => 'Délester les sondages expirés', + 'PRUNE_OLD_POLLS_EXPLAIN' => 'Si cette option est activée, les sujets contenant des sondages expirés seront délestés.', + + 'REDIRECT_ACL' => 'Vous pouvez à présent %sdéfinir les permissions%s de ce forum.', + + 'SYNC_IN_PROGRESS' => 'Synchronisation du forum', + 'SYNC_IN_PROGRESS_EXPLAIN' => 'Resynchronisation des sujets %1$d/%2$d.', + + 'TYPE_CAT' => 'Catégorie', + 'TYPE_FORUM' => 'Forum', + 'TYPE_LINK' => 'Lien', + + 'UNLOCKED' => 'Déverrouillé', +]); diff --git a/language/fr/acp/groups.php b/language/fr/acp/groups.php new file mode 100644 index 0000000..de21364 --- /dev/null +++ b/language/fr/acp/groups.php @@ -0,0 +1,157 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_GROUPS_MANAGE_EXPLAIN' => 'Depuis cette page, vous pouvez gérer tous les groupes d’utilisateurs de votre forum. Vous pouvez créer, modifier et supprimer des groupes d’utilisateurs, sélectionner leur statut (restreint, privé ou invisible) et saisir leur nom et leur description. De plus, vous pouvez nommer les responsables des différents groupes d’utilisateurs.', + 'ADD_GROUP_CATEGORY' => 'Ajouter une catégorie', + 'ADD_USERS' => 'Ajouter des utilisateurs', + 'ADD_USERS_EXPLAIN' => 'Vous pouvez ajouter ici de nouveaux utilisateurs à un groupe d’utilisateurs. Vous pouvez également attribuer un groupe d’utilisateurs par défaut aux utilisateurs sélectionnés. De plus, vous pouvez promouvoir certains membres d’un groupe d’utilisateurs comme responsables de ce dernier. Veuillez saisir chaque nom d’utilisateur sur une nouvelle ligne.', + + 'COPY_PERMISSIONS' => 'Copier les permissions de', + 'COPY_PERMISSIONS_EXPLAIN' => 'Une fois que ce groupe d’utilisateurs sera créé, ses permissions seront identiques à celles du groupe d’utilisateurs sélectionné.', + 'CREATE_GROUP' => 'Créer un nouveau groupe d’utilisateurs', + + 'GROUPS_NO_MEMBERS' => 'Ce groupe d’utilisateurs n’a aucun membre', + 'GROUPS_NO_MODS' => 'Ce groupe d’utilisateurs n’a aucun responsable', + + 'GROUP_APPROVE' => 'Approuver le membre', + 'GROUP_APPROVED' => 'Membres approuvés', + 'GROUP_AVATAR' => 'Avatar du groupe', + 'GROUP_AVATAR_EXPLAIN' => 'Cette image sera affichée dans le panneau de contrôle des groupes d’utilisateurs.', + 'GROUP_CATEGORY_NAME' => 'Nom de la catégorie', + 'GROUP_CLOSED' => 'Privé', + 'GROUP_COLOR' => 'Couleur du groupe', + 'GROUP_COLOR_EXPLAIN' => 'La couleur dans laquelle les noms d’utilisateurs des membres du groupe apparaîtront. Laissez ce champ vide si vous souhaitez conserver la couleur des membres par défaut.', + 'GROUP_CONFIRM_ADD_USERS' => [ + 1 => 'Êtes-vous sûr de vouloir ajouter l’utilisateur %2$s au groupe d’utilisateurs ?', + 2 => 'Êtes-vous sûr de vouloir ajouter les utilisateurs %2$s au groupe d’utilisateurs ?', + ], + 'GROUP_CREATED' => 'Le groupe d’utilisateurs a été créé.', + 'GROUP_DEFAULT' => 'Définir comme groupe d’utilisateurs par défaut pour les membres', + 'GROUP_DEFS_UPDATED' => 'Le groupe d’utilisateurs a été défini comme groupe d’utilisateurs par défaut pour tous les membres sélectionnés.', + 'GROUP_DELETE' => 'Supprimer le membre du groupe', + 'GROUP_DELETED' => 'Le groupe d’utilisateurs a été supprimé et tous ses membres ont été transférés dans le groupe d’utilisateurs par défaut.', + 'GROUP_DEMOTE' => 'Rétrograder le responsable du groupe', + 'GROUP_DESC' => 'Description du groupe', + 'GROUP_DETAILS' => 'Informations sur le groupe', + 'GROUP_EDIT_EXPLAIN' => 'Depuis cette page, vous pouvez modifier un groupe d’utilisateurs déjà existant. Vous pouvez modifier son nom, sa description et son type (restreint, privé, etc.). Vous pouvez également modifier certains aspects visuels comme la couleur, le rang, etc. Les modifications effectuées sur cette page remplaceront les paramètres individuels des utilisateurs. Veuillez noter que si les permissions des utilisateurs le permettent, les membres d’un groupe d’utilisateurs pourront outrepasser la configuration de l’avatar en sélectionnant eux-mêmes un avatar personnalisé.', + 'GROUP_ERR_USERS_EXIST' => 'Les utilisateurs que vous avez spécifiés sont déjà membres de ce groupe d’utilisateurs.', + 'GROUP_FOUNDER_MANAGE' => 'Limiter la gestion du groupe aux fondateurs uniquement', + 'GROUP_FOUNDER_MANAGE_EXPLAIN' => 'Le groupe d’utilisateurs ne pourra être géré que par les fondateurs du forum. Les membres du groupe d’utilisateurs peuvent cependant consulter ce dernier.', + 'GROUP_HIDDEN' => 'Invisible', + 'GROUP_LANG' => 'Langue du groupe', + 'GROUP_LEAD' => 'Responsables du groupe', + 'GROUP_LEADERS_ADDED' => 'Les nouveaux responsables du groupe d’utilisateurs ont été ajoutés.', + 'GROUP_LEGEND' => 'Afficher le groupe dans la légende', + 'GROUP_LIST' => 'Membres actuels', + 'GROUP_LIST_EXPLAIN' => 'Ceci correspond à la liste complète de tous les membres actuels de ce groupe d’utilisateurs. Vous pouvez supprimer ses membres (excepté dans certains groupes spéciaux) ou en ajouter de nouveaux.', + 'GROUP_MEMBERS' => 'Membres du groupe', + 'GROUP_MEMBERS_EXPLAIN' => 'Ceci correspond à la liste complète de tous les membres de ce groupe d’utilisateurs. Ces derniers sont divisés en trois sections, les responsables, les membres en attente et les membres déjà existants. Vous pouvez gérer ici tous les aspects des membres de ce groupe d’utilisateurs en définissant leurs rôles et leurs responsabilités. Pour supprimer un responsable tout en le conservant dans le groupe d’utilisateurs, utilisez la rétrogradation au lieu de le suppression. De même, si vous souhaitez promouvoir un membre en responsable, utilisez la promotion.', + 'GROUP_MESSAGE_LIMIT' => 'Limite de messages privés par boîte des membres du groupe', + 'GROUP_MESSAGE_LIMIT_EXPLAIN' => 'La limite des messages privés par boîte des utilisateurs sera remplacée par cette valeur. La valeur maximale de tous les groupes de l’utilisateur est utilisée afin de déterminer la valeur actuelle.
Réglez cette valeur sur « 0 » afin de remplacer les paramètres de tous les utilisateurs de ce groupe par les paramètres généraux du forum.', + 'GROUP_MODS_ADDED' => 'Les nouveaux responsables du groupe ont été ajoutés.', + 'GROUP_MODS_DEMOTED' => 'Les responsables du groupe d’utilisateurs ont été rétrogradés.', + 'GROUP_MODS_PROMOTED' => 'Les membres du groupe d’utilisateurs ont été promus.', + 'GROUP_NAME' => 'Nom du groupe', + 'GROUP_NAME_TAKEN' => 'Ce nom de groupe est déjà utilisé. Veuillez en saisir un autre.', + 'GROUP_OPEN' => 'Public', + 'GROUP_PENDING' => 'Membres en attente', + 'GROUP_MAX_RECIPIENTS' => 'Nombre maximal de destinataires autorisés dans un message privé', + 'GROUP_MAX_RECIPIENTS_EXPLAIN' => 'Le nombre maximal de destinataires autorisés dans un message privé. La valeur maximale de tous les groupes de l’utilisateur est utilisée afin de déterminer la valeur actuelle.
Réglez cette valeur sur « 0 » afin de remplacer les paramètres de tous les utilisateurs de ce groupe par les paramètres généraux du forum.', + 'GROUP_OPTIONS_SAVE' => 'Options générales du groupe', + 'GROUP_PROMOTE' => 'Promouvoir en responsable du groupe', + 'GROUP_RANK' => 'Rang du groupe', + 'GROUP_RECEIVE_PM' => 'Autoriser le groupe à recevoir des messages privés', + 'GROUP_RECEIVE_PM_EXPLAIN' => 'Les groupes invisibles ne seront tout de même pas autorisés à recevoir des messages privés.', + 'GROUP_REQUEST' => 'Restreint', + 'GROUP_SETTINGS_SAVE' => 'Paramètres généraux du groupe', + 'GROUP_SKIP_AUTH' => 'Exempter le responsable des permissions du groupe', + 'GROUP_SKIP_AUTH_EXPLAIN' => 'Si cette option est activée, le responsable du groupe d’utilisateurs n’héritera pas des permissions de ce dernier.', + 'GROUP_SPECIAL' => 'Prédéfini', + 'GROUP_TEAMPAGE' => 'Afficher le groupe sur la page de l’équipe', + 'GROUP_TYPE' => 'Type de groupe', + 'GROUP_TYPE_EXPLAIN' => 'Détermine quels utilisateurs peuvent rejoindre ou consulter ce groupe.', + 'GROUP_UPDATED' => 'Les préférences du groupe ont été mises à jour.', + + 'GROUP_USERS_ADDED' => 'Les nouveaux utilisateurs ont été ajoutés au groupe d’utilisateurs.', + 'GROUP_USERS_EXIST' => 'Les utilisateurs sont déjà membres de ce groupe d’utilisateurs.', + 'GROUP_USERS_REMOVE' => 'Les utilisateurs ont été supprimés de ce groupe d’utilisateurs. Ils ont été transférés dans le groupe d’utilisateurs par défaut.', + 'GROUP_USERS_INVALID' => 'Aucun utilisateur n’a été ajouté au groupe d’utilisateurs étant donné que les noms d’utilisateurs suivants n’existent pas : %s', + + 'LEGEND_EXPLAIN' => 'Cette liste correspond aux groupes d’utilisateurs qui sont affichés dans la légende des groupes :', + 'LEGEND_SETTINGS' => 'Paramètres de la légende', + 'LEGEND_SORT_GROUPNAME' => 'Trier la légende par le nom des groupes', + 'LEGEND_SORT_GROUPNAME_EXPLAIN' => 'Si cette option est activée, la position des groupes d’utilisateurs qui est affiché ci-dessous sera ignorée.', + + 'MANAGE_LEGEND' => 'Gérer la légende des groupes', + 'MANAGE_TEAMPAGE' => 'Gérer la page de l’équipe', + 'MAKE_DEFAULT_FOR_ALL' => 'Définir comme groupe par défaut pour tous les membres', + 'MEMBERS' => 'Membres', + + 'NO_GROUP' => 'Aucun groupe n’a été spécifié.', + 'NO_GROUPS_ADDED' => 'Aucun groupe n’a été ajouté.', + 'NO_GROUPS_CREATED' => 'Aucun groupe n’a été créé.', + 'NO_PERMISSIONS' => 'Ne copier aucune permission', + 'NO_USERS' => 'Aucun utilisateur n’a été spécifié.', + 'NO_USERS_ADDED' => 'Aucun utilisateur n’a été ajouté au groupe d’utilisateurs.', + 'NO_VALID_USERS' => 'Aucun utilisateur ne peut réaliser cette opération.', + + 'PENDING_MEMBERS' => 'En attente', + + 'SELECT_GROUP' => 'Sélectionner un groupe', + 'SPECIAL_GROUPS' => 'Groupes prédéfinis', + 'SPECIAL_GROUPS_EXPLAIN' => 'Les groupes prédéfinis sont des groupes spéciaux. Ils ne peuvent ni être supprimés, ni être directement modifiés. Cependant, vous pouvez gérer leurs membres et configurer certains de leurs paramètres.', + + 'TEAMPAGE' => 'Page de l’équipe', + 'TEAMPAGE_DISP_ALL' => 'Toutes les adhésions', + 'TEAMPAGE_DISP_DEFAULT' => 'Adhésions par défaut', + 'TEAMPAGE_DISP_FIRST' => 'Premières adhésions', + 'TEAMPAGE_EXPLAIN' => 'Cette liste correspond aux groupes d’utilisateurs qui sont affichés dans la page de l’équipe :', + 'TEAMPAGE_FORUMS' => 'Afficher les forums modérés', + 'TEAMPAGE_FORUMS_EXPLAIN' => 'Si cette option est activée, les modérateurs pourront consulter dans leur ligne la liste de tous les forums où ils détiennent des permissions de modérateur. Veuillez noter que cette option peut provoquer des communications intensives avec la base de données dans le cas des forums populaires.', + 'TEAMPAGE_MEMBERSHIPS' => 'Méthode d’affichage des adhésions des utilisateurs', + 'TEAMPAGE_SETTINGS' => 'Paramètres de la page de l’équipe', + 'TOTAL_MEMBERS' => 'Membres', + + 'USERS_APPROVED' => 'Les utilisateurs ont été approuvés.', + 'USER_DEFAULT' => 'Utilisateur par défaut', + 'USER_DEF_GROUPS' => 'Groupes définis par un administrateur', + 'USER_DEF_GROUPS_EXPLAIN' => 'Les groupes définis par un administrateur sont des groupes d’utilisateurs créés par vous-même ou par un autre administrateur du forum. Vous pouvez gérer leurs membres, configurer leurs paramètres ou encore les supprimer.', + 'USER_GROUP_DEFAULT' => 'Définir comme groupe par défaut', + 'USER_GROUP_DEFAULT_EXPLAIN' => 'Si cette option est activée, ce groupe d’utilisateurs sera considéré comme le groupe d’utilisateurs par défaut de tous les utilisateurs.', + 'USER_GROUP_LEADER' => 'Promouvoir en responsable du groupe', +]); diff --git a/language/fr/acp/index.htm b/language/fr/acp/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/language/fr/acp/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/language/fr/acp/language.php b/language/fr/acp/language.php new file mode 100644 index 0000000..383960a --- /dev/null +++ b/language/fr/acp/language.php @@ -0,0 +1,77 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_FILES' => 'Fichiers de langue de l’administration', + 'ACP_LANGUAGE_PACKS_EXPLAIN' => 'Depuis cette page, vous pouvez installer, modifier et supprimer les langues de votre forum. La langue par défaut est signalée par un astérisque « * ».', + + 'DELETE_LANGUAGE_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer la langue « %s » ?', + + 'INSTALLED_LANGUAGE_PACKS' => 'Langues installées', + + 'LANGUAGE_DETAILS_UPDATED' => 'Les informations concernant la langue ont été mises à jour.', + 'LANGUAGE_PACK_ALREADY_INSTALLED' => 'Cette langue est déjà installée.', + 'LANGUAGE_PACK_DELETED' => 'La langue « %s » a été supprimée. Tous les utilisateurs qui utilisaient cette langue utilisent à présent celle par défaut.', + 'LANGUAGE_PACK_DETAILS' => 'Informations sur la langue', + 'LANGUAGE_PACK_INSTALLED' => 'La langue « %s » a été installée.', + 'LANGUAGE_PACK_CPF_UPDATE' => 'Les chaînes de langue des champs de profil personnalisés ont été copiées à partir de la langue par défaut. Veuillez les modifier si nécessaire.', + 'LANGUAGE_PACK_ISO' => 'ISO', + 'LANGUAGE_PACK_LOCALNAME' => 'Nom local', + 'LANGUAGE_PACK_NAME' => 'Nom', + 'LANGUAGE_PACK_NOT_EXIST' => 'La langue est introuvable.', + 'LANGUAGE_PACK_USED_BY' => 'Utilisée par (incluant les robots)', + 'LANGUAGE_VARIABLE' => 'Variable de langue', + 'LANG_AUTHOR' => 'Auteur de la langue', + 'LANG_ENGLISH_NAME' => 'Nom en anglais', + 'LANG_ISO_CODE' => 'Code ISO', + 'LANG_LOCAL_NAME' => 'Nom local', + + 'MISSING_LANG_FILES' => 'Fichiers de langue manquants', + 'MISSING_LANG_VARIABLES' => 'Variables de langue manquantes', + + 'NO_FILE_SELECTED' => 'Aucun fichier de langue n’a été spécifié.', + 'NO_LANG_ID' => 'Aucune langue n’a été spécifiée.', + 'NO_REMOVE_DEFAULT_LANG' => 'Vous ne pouvez pas supprimer la langue par défaut.
Si vous souhaitez supprimer cette langue, veuillez modifier préalablement la langue par défaut de votre forum.', + 'NO_UNINSTALLED_LANGUAGE_PACKS' => 'Aucune langue non installée', + + 'THOSE_MISSING_LANG_FILES' => 'Les fichiers de langue suivants sont manquants du répertoire de langue « %s »', + 'THOSE_MISSING_LANG_VARIABLES' => 'Les variables de langue suivantes sont manquantes de la langue « %s ».', + + 'UNINSTALLED_LANGUAGE_PACKS' => 'Langues non installées', + + 'BROWSE_LANGUAGE_PACKS_DATABASE' => 'Parcourir la base de données des langues', +]); diff --git a/language/fr/acp/modules.php b/language/fr/acp/modules.php new file mode 100644 index 0000000..26e33dd --- /dev/null +++ b/language/fr/acp/modules.php @@ -0,0 +1,82 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_MODULE_MANAGEMENT_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les modules. Veuillez noter que le panneau de contrôle d’administration est divisé en structure à trois niveaux de menu (Catégorie → Catégorie → Module) alors que les autres panneaux de contrôle ne sont divisés qu’en structure à deux niveaux de menu (Catégorie → Module). Cette structure doit être conservée. Veuillez noter également que vous pouvez vous bloquer si vous désactivez ou supprimez les modules responsables de la gestion des modules.', + 'ADD_MODULE' => 'Ajouter un module', + 'ADD_MODULE_CONFIRM' => 'Êtes-vous sûr de vouloir ajouter ce module ?', + 'ADD_MODULE_TITLE' => 'Ajouter un module', + + 'CANNOT_REMOVE_MODULE' => 'Le module n’a pas pu être supprimé car il contient des sous-modules. Veuillez supprimer ou déplacer tous les sous-modules avant d’effectuer cette opération.', + 'CATEGORY' => 'Catégorie', + 'CHOOSE_MODE' => 'Sélectionner un mode de module', + 'CHOOSE_MODE_EXPLAIN' => 'Sélectionnez le mode qui sera utilisé par les modules.', + 'CHOOSE_MODULE' => 'Sélectionner un module', + 'CHOOSE_MODULE_EXPLAIN' => 'Sélectionnez le fichier qui sera utilisé par ce module.', + 'CREATE_MODULE' => 'Créer un nouveau module', + + 'DEACTIVATED_MODULE' => 'Module désactivé', + 'DELETE_MODULE' => 'Supprimer le module', + 'DELETE_MODULE_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce module ?', + + 'EDIT_MODULE' => 'Modifier le module', + 'EDIT_MODULE_EXPLAIN' => 'Depuis cette page, vous pouvez configurer les paramètres relatifs au module.', + + 'HIDDEN_MODULE' => 'Module masqué', + + 'MODULE' => 'Module', + 'MODULE_ADDED' => 'Le module a été ajouté.', + 'MODULE_DELETED' => 'Le module a été supprimé.', + 'MODULE_DISPLAYED' => 'Module affiché', + 'MODULE_DISPLAYED_EXPLAIN' => 'Si vous ne souhaitez pas afficher ce module mais que vous souhaitez l’utiliser, désactivez cette option.', + 'MODULE_EDITED' => 'Le module a été modifié.', + 'MODULE_ENABLED' => 'Module activé', + 'MODULE_LANGNAME' => 'Nom de la langue du module', + 'MODULE_LANGNAME_EXPLAIN' => 'Le nom du module qui sera affiché. Saisissez une variable de langue si le nom souhaité est présent dans un fichier de langue.', + 'MODULE_TYPE' => 'Type de module', + + 'NO_CATEGORY_TO_MODULE' => 'Impossible de modifier cette catégorie en module. Veuillez supprimer ou déplacer tous les sous-modules avant d’effectuer cette opération.', + 'NO_MODULE' => 'Aucun module n’a été trouvé.', + 'NO_MODULE_ID' => 'L’identifiant du module n’a pas été spécifié.', + 'NO_MODULE_LANGNAME' => 'Le nom de langue du module n’a pas été spécifié.', + 'NO_PARENT' => 'Aucun parent', + + 'PARENT' => 'Parent', + 'PARENT_NO_EXIST' => 'Le parent est introuvable.', + + 'SELECT_MODULE' => 'Sélectionner un module', +]); diff --git a/language/fr/acp/permissions.php b/language/fr/acp/permissions.php new file mode 100644 index 0000000..7ba9103 --- /dev/null +++ b/language/fr/acp/permissions.php @@ -0,0 +1,286 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_PERMISSIONS_EXPLAIN' => ' +

Les permissions sont nombreuses et regroupées en quatre sections majeures :

+ +

Les permissions générales

+

Elles sont utilisées afin de contrôler l’accès à l’ensemble du forum. Elles sont divisées en permissions des utilisateurs, permissions des groupes, permissions des administrateurs et permissions des modérateurs généraux.

+ +

Les permissions liées aux forums

+

Elles sont utilisées afin de contrôler l’accès à chaque forum. Elles sont divisées en permissions des forums, permissions des forums aux modérateurs, permissions des forums aux utilisateurs et permissions des forums aux groupes.

+ +

Les rôles des permissions

+

Ils sont utilisés afin de créer différents ensembles de permissions concernant les différents types de permissions qui peuvent être assignés ultérieurement aux rôles. Les rôles par défaut devraient couvrir l’administration des forums étant donné que dans chacune des quatre divisions, vous pouvez ajouter, modifier et supprimer des rôles selon vos besoins.

+ +

Les masques des permissions

+

Ils sont utilisés afin de consulter les permissions effectives assignées aux utilisateurs, aux modérateurs (locaux et généraux), aux administrateurs et aux forums.

+ +
+ +

Pour plus d’informations concernant la configuration des permissions sur votre forum, veuillez consulter la section sur la configuration des permissions de notre guide de démarrage rapide (en anglais).

+ ', + + 'ACL_NEVER' => 'Jamais', + 'ACL_SET' => 'Paramètre des permissions', + 'ACL_SET_EXPLAIN' => 'Les permissions sont basées sur un système simpliste de « OUI » et de « NON ». Sélectionnez « JAMAIS » si vous souhaitez remplacer les valeurs assignées dans un autre système de permissions. Si vous ne souhaitez pas assigner de valeur à une option par ce système de permissions, sélectionnez « NON ». Si des valeurs sont assignées dans un autre système de permissions, elles seront alors utilisées de préférence, sauf dans le cas où « JAMAIS » est sélectionné. Tous les forums cochés grâce à la boîte de sélection située à côté du nom qui leur a été attribué copieront les paramètres des permissions que vous avez spécifiées.', + 'ACL_SETTING' => 'Paramètre', + + 'ACL_TYPE_A_' => 'Permissions des administrateurs', + 'ACL_TYPE_F_' => 'Permissions des forums', + 'ACL_TYPE_M_' => 'Permissions des modérateurs', + 'ACL_TYPE_U_' => 'Permissions des utilisateurs', + + 'ACL_TYPE_GLOBAL_A_' => 'Permissions des administrateurs', + 'ACL_TYPE_GLOBAL_U_' => 'Permissions des utilisateurs', + 'ACL_TYPE_GLOBAL_M_' => 'Permissions des modérateurs généraux', + 'ACL_TYPE_LOCAL_M_' => 'Permissions des modérateurs du forum', + 'ACL_TYPE_LOCAL_F_' => 'Permissions des forums', + + 'ACL_NO' => 'Non', + 'ACL_VIEW' => 'Consultation des permissions', + 'ACL_VIEW_EXPLAIN' => 'Depuis cette page, vous pouvez consulter toutes les permissions effectives de l’utilisateur ou du groupe d’utilisateurs sélectionné. Un rectangle rouge indique les permissions qu’il ne détient pas, un rectangle vert indique les permissions qu’il détient.', + 'ACL_YES' => 'Oui', + + 'ACP_ADMINISTRATORS_EXPLAIN' => 'Depuis cette page, vous pouvez attribuer les permissions des administrateurs à des utilisateurs et des groupes d’utilisateurs, qui pourront alors accéder au panneau de contrôle d’administration.', + 'ACP_FORUM_MODERATORS_EXPLAIN' => 'Depuis cette page, vous pouvez attribuer les permissions des modérateurs sur certains forums spécifiques à des utilisateurs et des groupes d’utilisateurs. Veuillez vous rendre sur la page appropriée afin d’attribuer l’accès des utilisateurs aux forums et définir des permissions de modérateurs ou d’administrateurs à ces derniers.', + 'ACP_FORUM_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez spécifier les utilisateurs et les groupes d’utilisateurs qui pourront accéder à certains forums. Veuillez vous rendre sur les pages appropriées si vous souhaitez définir les permissions des administrateurs et des modérateurs.', + 'ACP_FORUM_PERMISSIONS_COPY_EXPLAIN' => 'Depuis cette page, vous pouvez copier les permissions d’un forum à un ou plusieurs autres forums.', + 'ACP_GLOBAL_MODERATORS_EXPLAIN' => 'Depuis cette page, vous pouvez attribuer les permissions des modérateurs généraux à des utilisateurs et des groupes d’utilisateurs. Ces modérateurs sont des modérateurs ordinaires, mis à part le fait qu’ils peuvent modérer la totalité des forums.', + 'ACP_GROUPS_FORUM_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez attribuer les permissions des forums à des groupes d’utilisateurs.', + 'ACP_GROUPS_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez attribuer les permissions générales à des groupes d’utilisateurs, comme les permissions des utilisateurs, les permissions des modérateurs généraux et les permissions des administrateurs. Les permissions des utilisateurs incluent des fonctionnalités comme l’utilisation d’avatars, l’envoi de messages privés, etc. Les permissions des modérateurs généraux incluent des fonctionnalités comme l’approbation de messages, la gestion de sujets, la gestion des bannissements, etc. Enfin, les permissions des administrateurs incluent des fonctionnalités comme la modification de permissions, la gestion des BBCodes personnalisés, la gestion de forums, etc. Les permissions individuelles des utilisateurs ne devraient être modifiées que dans de rares occasions, il est préférable de les ajouter à un groupe d’utilisateurs, puis de définir les permissions de ce dernier.', + 'ACP_ADMIN_ROLES_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les rôles de permissions des administrateurs. Les rôles sont des permissions effectives. Si vous modifiez un rôle, les éléments assignés à ce rôle modifieront également leurs permissions.', + 'ACP_FORUM_ROLES_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les rôles des permissions des forums. Les rôles sont des permissions effectives. Si vous modifiez un rôle, les éléments assignés à ce rôle modifieront également leurs permissions.', + 'ACP_MOD_ROLES_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les rôles des permissions des modérateurs. Les rôles sont des permissions effectives. Si vous modifiez un rôle, les éléments assignés à ce rôle modifieront également leurs permissions.', + 'ACP_USER_ROLES_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les rôles des permissions des utilisateurs. Les rôles sont des permissions effectives. Si vous modifiez un rôle, les éléments assignés à ce rôle modifieront également leurs permissions.', + 'ACP_USERS_FORUM_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez attribuer les permissions des forums à des utilisateurs.', + 'ACP_USERS_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez attribuer les permissions générales à des utilisateurs, comme les permissions des utilisateurs, les permissions des modérateurs généraux et les permissions des administrateurs. Les permissions des utilisateurs incluent des fonctionnalités comme l’utilisation d’avatars, l’envoi de messages privés, etc. Les permissions des modérateurs généraux incluent des fonctionnalités comme l’approbation de messages, la gestion de sujets, la gestion des bannissements, etc. Enfin, les permissions des administrateurs incluent des fonctionnalités comme la modification de permissions, la gestion des BBCodes personnalisés, la gestion de forums, etc. Pour partager les mêmes paramètres à un grand nombre d’utilisateurs, il est préférable d’utiliser le système des permissions des groupes. Les permissions individuelles des utilisateurs ne devraient être modifiées que dans de rares occasions, il est préférable de les ajouter à un groupe d’utilisateurs, puis de définir les permissions de ce dernier.', + 'ACP_VIEW_ADMIN_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter les permissions effectives des administrateurs assignées aux utilisateurs et aux groupes d’utilisateurs sélectionnés.', + 'ACP_VIEW_GLOBAL_MOD_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter les permissions effectives des modérateurs généraux assignées aux utilisateurs et aux groupes d’utilisateurs sélectionnés.', + 'ACP_VIEW_FORUM_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter les permissions effectives des forums assignées aux utilisateurs et aux groupes d’utilisateurs sélectionnés.', + 'ACP_VIEW_FORUM_MOD_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter les permissions effectives des modérateurs de forum assignées aux utilisateurs, aux groupes d’utilisateurs et aux forums sélectionnés.', + 'ACP_VIEW_USER_PERMISSIONS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter les permissions effectives des utilisateurs assignées aux utilisateurs et aux groupes d’utilisateurs sélectionnés.', + + 'ADD_GROUPS' => 'Ajouter des groupes d’utilisateurs', + 'ADD_PERMISSIONS' => 'Ajouter des permissions', + 'ADD_USERS' => 'Ajouter des utilisateurs', + 'ADVANCED_PERMISSIONS' => 'Permissions avancées', + 'ALL_GROUPS' => 'Sélectionner tous les groupes d’utilisateurs', + 'ALL_NEVER' => 'Tous sur « JAMAIS »', + 'ALL_NO' => 'Tous sur « NON »', + 'ALL_USERS' => 'Sélectionner tous les utilisateurs', + 'ALL_YES' => 'Tous sur « OUI »', + 'APPLY_ALL_PERMISSIONS' => 'Appliquer toutes les permissions', + 'APPLY_PERMISSIONS' => 'Appliquer les permissions', + 'APPLY_PERMISSIONS_EXPLAIN' => 'Les permissions et les rôles de cet élément ne seront appliqués qu’aux éléments cochés et à ce dernier.', + 'AUTH_UPDATED' => 'Les permissions ont été mises à jour.', + + 'COPY_PERMISSIONS_CONFIRM' => 'Êtes-vous sûr de vouloir effectuer cette opération ? Cela remplacera toutes les permissions existantes des forums sélectionnés.', + 'COPY_PERMISSIONS_FORUM_FROM_EXPLAIN' => 'Le forum source à partir duquel vous souhaitez copier les permissions.', + 'COPY_PERMISSIONS_FORUM_TO_EXPLAIN' => 'Le ou les forums où les permissions que vous avez copiées seront appliquées.', + 'COPY_PERMISSIONS_FROM' => 'Copier les permissions de', + 'COPY_PERMISSIONS_TO' => 'Appliquer les permissions sur', + + 'CREATE_ROLE' => 'Créer un rôle', + 'CREATE_ROLE_FROM' => 'Utiliser les paramètres de…', + 'CUSTOM' => 'Personnaliser…', + + 'DEFAULT' => 'Par défaut', + 'DELETE_ROLE' => 'Supprimer le rôle', + 'DELETE_ROLE_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce rôle ? Les éléments assignés à ce rôle ne perdront pas leurs paramètres de permissions.', + 'DISPLAY_ROLE_ITEMS' => 'Consulter les éléments assignés à ce rôle', + + 'EDIT_PERMISSIONS' => 'Modifier les permissions', + 'EDIT_ROLE' => 'Modifier le rôle', + + 'GROUPS_NOT_ASSIGNED' => 'Aucun groupe d’utilisateurs n’est assigné à ce rôle', + + 'LOOK_UP_GROUP' => 'Sélectionner un groupe d’utilisateurs', + 'LOOK_UP_USER' => 'Sélectionner un utilisateur', + + 'MANAGE_GROUPS' => 'Gérer les groupes d’utilisateurs', + 'MANAGE_USERS' => 'Gérer les utilisateurs', + + 'NO_AUTH_SETTING_FOUND' => 'Paramètres de permissions non définis.', + 'NO_ROLE_ASSIGNED' => 'Aucun rôle n’est assigné…', + 'NO_ROLE_ASSIGNED_EXPLAIN' => 'La configuration de ce rôle ne modifie pas les permissions de droite. Si vous souhaitez supprimer toutes les permissions, vous devez utiliser le lien « Tous sur NON ».', + 'NO_ROLE_AVAILABLE' => 'Aucun rôle n’est disponible', + 'NO_ROLE_NAME_SPECIFIED' => 'Veuillez saisir le nom de ce rôle.', + 'NO_ROLE_SELECTED' => 'Le rôle est introuvable.', + 'NO_USER_GROUP_SELECTED' => 'Vous n’avez sélectionné aucun utilisateur ou groupe d’utilisateurs.', + + 'ONLY_FORUM_DEFINED' => 'Vous n’avez sélectionné que des forums. Veuillez également sélectionner au moins un utilisateur ou groupe d’utilisateurs.', + + 'PERMISSION_APPLIED_TO_ALL' => 'Les permissions et le rôle seront également appliqués à tous les éléments que vous avez cochés', + 'PLUS_SUBFORUMS' => '+Sous-forums', + + 'REMOVE_PERMISSIONS' => 'Supprimer des permissions', + 'REMOVE_ROLE' => 'Supprimer un rôle', + 'RESULTING_PERMISSION' => 'Permission résultante', + 'ROLE' => 'Rôle', + 'ROLE_ADD_SUCCESS' => 'Le rôle a été ajouté.', + 'ROLE_ASSIGNED_TO' => 'Utilisateurs et groupes d’utilisateurs assignés à « %s »', + 'ROLE_DELETED' => 'Le rôle a été supprimé.', + 'ROLE_DESCRIPTION' => 'Description du rôle', + + 'ROLE_ADMIN_FORUM' => 'Administrateur du forum', + 'ROLE_ADMIN_FULL' => 'Administrateur aux pleins pouvoirs', + 'ROLE_ADMIN_STANDARD' => 'Administrateur standard', + 'ROLE_ADMIN_USERGROUP' => 'Administrateur des utilisateurs et des groupes', + 'ROLE_FORUM_BOT' => 'Accès des robots', + 'ROLE_FORUM_FULL' => 'Accès total', + 'ROLE_FORUM_LIMITED' => 'Accès limité', + 'ROLE_FORUM_LIMITED_POLLS' => 'Accès limité + sondages', + 'ROLE_FORUM_NOACCESS' => 'Aucun accès', + 'ROLE_FORUM_ONQUEUE' => 'File d’attente de modération', + 'ROLE_FORUM_POLLS' => 'Accès standard + sondages', + 'ROLE_FORUM_READONLY' => 'Accès en lecture seule', + 'ROLE_FORUM_STANDARD' => 'Accès standard', + 'ROLE_FORUM_NEW_MEMBER' => 'Accès des utilisateurs nouvellement inscrits', + 'ROLE_MOD_FULL' => 'Modérateur aux pleins pouvoirs', + 'ROLE_MOD_QUEUE' => 'Modérateur suppléant', + 'ROLE_MOD_SIMPLE' => 'Modérateur simple', + 'ROLE_MOD_STANDARD' => 'Modérateur standard', + 'ROLE_USER_FULL' => 'Toutes les fonctionnalités', + 'ROLE_USER_LIMITED' => 'Fonctionnalités limitées', + 'ROLE_USER_NOAVATAR' => 'Privé d’avatar', + 'ROLE_USER_NOPM' => 'Privé de messagerie privée', + 'ROLE_USER_STANDARD' => 'Fonctionnalités standards', + 'ROLE_USER_NEW_MEMBER' => 'Fonctionnalités des utilisateurs nouvellement inscrits', + + 'ROLE_DESCRIPTION_ADMIN_FORUM' => 'Peut accéder à la gestion du forum et aux paramètres de ses permissions.', + 'ROLE_DESCRIPTION_ADMIN_FULL' => 'Peut accéder à toutes les fonctionnalités des administrateurs.
Il n’est pas recommandé d’attribuer ce rôle.', + 'ROLE_DESCRIPTION_ADMIN_STANDARD' => 'Peut accéder à la plupart des fonctionnalités des administrateurs mais ne peut pas modifier les options relatives au système et au serveur.', + 'ROLE_DESCRIPTION_ADMIN_USERGROUP' => 'Peut gérer les utilisateurs et les groupes d’utilisateurs (gestion des permissions, des paramètres, des bannissements et des rangs).', + 'ROLE_DESCRIPTION_FORUM_BOT' => 'Il recommandé d’attribuer ce rôle aux robots des moteurs de recherche.', + 'ROLE_DESCRIPTION_FORUM_FULL' => 'Peut utiliser toutes les fonctionnalités du forum, dont la publication d’annonces et de notes. Peut également ignorer la limitation de flood.
Il n’est pas recommandé d’attribuer ce rôle aux utilisateurs ordinaires.', + 'ROLE_DESCRIPTION_FORUM_LIMITED' => 'Peut utiliser certaines fonctionnalités du forum mais ne peut pas transférer de pièces jointes et insérer des icônes aux messages.', + 'ROLE_DESCRIPTION_FORUM_LIMITED_POLLS' => 'Similaire à l’accès limité, mais peut également créer des sondages.', + 'ROLE_DESCRIPTION_FORUM_NOACCESS' => 'Ne peut ni consulter ni accéder au forum.', + 'ROLE_DESCRIPTION_FORUM_ONQUEUE' => 'Peut utiliser la plupart des fonctionnalités du forum, dont la possibilité de transférer des pièces jointes, mais les messages et les sujets doivent être approuvés par un modérateur avant leur publication.', + 'ROLE_DESCRIPTION_FORUM_POLLS' => 'Similaire à l’accès standard, mais peut également créer des sondages.', + 'ROLE_DESCRIPTION_FORUM_READONLY' => 'Peut consulter le forum mais ne peut pas créer de nouveaux sujets et répondre aux messages.', + 'ROLE_DESCRIPTION_FORUM_STANDARD' => 'Peut utiliser la plupart des fonctionnalités du forum, dont la possibilité de transférer des pièces jointes et de supprimer ses propres sujets, mais ne peut pas verrouiller ses propres sujets et ne peut pas créer de sondages.', + 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER' => 'Ce rôle est destiné aux membres du groupe des utilisateurs nouvellement inscrits. Il contient des permissions « JAMAIS » qui permettent de limiter certaines fonctionnalités aux nouveaux membres.', + 'ROLE_DESCRIPTION_MOD_FULL' => 'Peut utiliser toutes les fonctionnalités liées à la modération, dont le bannissement.', + 'ROLE_DESCRIPTION_MOD_QUEUE' => 'Ne peut utiliser que la file d’attente de modération afin de valider et modifier des messages.', + 'ROLE_DESCRIPTION_MOD_SIMPLE' => 'Ne peut effectuer que les opérations basiques liées aux sujets mais ne peut pas envoyer d’avertissements et accéder à la file d’attente de modération.', + 'ROLE_DESCRIPTION_MOD_STANDARD' => 'Peut utiliser la plupart des outils liées à la modération mais ne peut pas bannir d’utilisateurs et modifier l’auteur d’un message.', + 'ROLE_DESCRIPTION_USER_FULL' => 'Peut utiliser toutes les fonctionnalités du forum qui sont disponibles aux utilisateurs, dont la possibilité de modifier son nom d’utilisateur et d’ignorer la limitation de flood.
Il n’est pas recommandé d’attribuer ce rôle.', + 'ROLE_DESCRIPTION_USER_LIMITED' => 'Peut accéder à la plupart des fonctionnalités des utilisateurs mais les pièces jointes, les courriels et les messages instantanés ne sont pas autorisés.', + 'ROLE_DESCRIPTION_USER_NOAVATAR' => 'Ne peut accéder qu’à un ensemble de fonctionnalités limité et n’est pas autorisé à utiliser la fonctionnalité des avatars.', + 'ROLE_DESCRIPTION_USER_NOPM' => 'Ne peut accéder qu’à un ensemble de fonctionnalités limité et n’est pas autorisé à utiliser les fonctionnalités de la messagerie privée.', + 'ROLE_DESCRIPTION_USER_STANDARD' => 'Peut accéder à la plupart des fonctionnalités des utilisateurs mais ne peut pas modifier son nom d’utilisateur et ignorer la limitation de flood.', + 'ROLE_DESCRIPTION_USER_NEW_MEMBER' => 'Ce rôle est destiné aux membres du groupe des utilisateurs nouvellement inscrits. Il contient des permissions « JAMAIS » qui permettent de limiter certaines fonctionnalités aux nouveaux membres.', + + 'ROLE_DESCRIPTION_EXPLAIN' => 'Vous pouvez saisir une brève explication de ce que fait le rôle ou de ce qu’il veut signifier. Le texte que vous saisissez ici sera également affiché sur la page des permissions.', + 'ROLE_DESCRIPTION_LONG' => 'La description du rôle est trop longue. Veuillez ne pas dépasser 4000 caractères.', + 'ROLE_DETAILS' => 'Informations sur le rôle', + 'ROLE_EDIT_SUCCESS' => 'Le rôle a été modifié.', + 'ROLE_NAME' => 'Nom du rôle', + 'ROLE_NAME_ALREADY_EXIST' => 'Un rôle portant le nom de « %s » existe déjà. Veuillez en saisir un autre.', + 'ROLE_NOT_ASSIGNED' => 'Le rôle n’a pas encore été attribué.', + + 'SELECTED_FORUM_NOT_EXIST' => 'Le forum que vous avez spécifié est introuvable.', + 'SELECTED_GROUP_NOT_EXIST' => 'Le groupe d’utilisateurs que vous avez spécifié est introuvable.', + 'SELECTED_USER_NOT_EXIST' => 'L’utilisateur que vous avez spécifié est introuvable.', + 'SELECT_FORUM_SUBFORUM_EXPLAIN' => 'Le forum que vous sélectionnez dans la liste déroulante comprendra également tous ses sous-forums.', + 'SELECT_ROLE' => 'Sélectionner un rôle…', + 'SELECT_TYPE' => 'Sélectionner un type', + 'SET_PERMISSIONS' => 'Définir les permissions', + 'SET_ROLE_PERMISSIONS' => 'Définir les permissions du rôle', + 'SET_USERS_PERMISSIONS' => 'Définir les permissions des utilisateurs', + 'SET_USERS_FORUM_PERMISSIONS' => 'Définir les permissions des forums aux utilisateurs', + + 'TRACE_DEFAULT' => 'Par défaut, toutes les permissions sont configurées sur « NON ». La permission peut alors être remplacée par d’autres paramètres.', + 'TRACE_FOR' => 'Suivre pour', + 'TRACE_GLOBAL_SETTING' => '%s (général)', + 'TRACE_GROUP_NEVER_TOTAL_NEVER' => 'Cette permission de groupe est configurée sur « JAMAIS » tout comme le résultat total. L’ancien résultat est donc conservé.', + 'TRACE_GROUP_NEVER_TOTAL_NEVER_LOCAL' => 'Cette permission de groupe concernant ce forum est configurée sur « JAMAIS » tout comme le résultat total. L’ancien résultat est donc conservé.', + 'TRACE_GROUP_NEVER_TOTAL_NO' => 'Cette permission de groupe est configurée sur « JAMAIS » ce qui devient la nouvelle valeur totale car elle n’était pas encore définie (configurée sur « NON »).', + 'TRACE_GROUP_NEVER_TOTAL_NO_LOCAL' => 'Cette permission de groupe concernant ce forum est configurée sur « JAMAIS » ce qui devient la nouvelle valeur totale car elle n’était pas encore définie (configurée sur « NON »).', + 'TRACE_GROUP_NEVER_TOTAL_YES' => 'Cette permission de groupe est configurée sur « JAMAIS » ce qui remplace le total de « OUI » par « JAMAIS » concernant cet utilisateur.', + 'TRACE_GROUP_NEVER_TOTAL_YES_LOCAL' => 'Cette permission de groupe concernant ce forum est configurée sur « JAMAIS » ce qui remplace le total de « OUI » par « JAMAIS » concernant cet utilisateur.', + 'TRACE_GROUP_NO' => 'La permission est configurée sur « NON » concernant ce groupe. L’ancienne valeur totale est donc conservée.', + 'TRACE_GROUP_NO_LOCAL' => 'La permission est configurée sur « NON » concernant ce groupe dans ce forum. L’ancienne valeur totale est donc conservée.', + 'TRACE_GROUP_YES_TOTAL_NEVER' => 'Cette permission de groupe est configurée sur « OUI » mais le total de « JAMAIS » ne peut pas être remplacé.', + 'TRACE_GROUP_YES_TOTAL_NEVER_LOCAL' => 'Cette permission de groupe concernant ce forum est configurée sur « OUI » mais le total de « JAMAIS » ne peut pas être remplacé.', + 'TRACE_GROUP_YES_TOTAL_NO' => 'Cette permission de groupe est configurée sur « OUI » ce qui devient la nouvelle valeur totale car elle n’était pas encore définie (configurée sur « NON »).', + 'TRACE_GROUP_YES_TOTAL_NO_LOCAL' => 'Cette permission de groupe concernant ce forum est configurée sur « OUI » ce qui devient la nouvelle valeur totale car elle n’était pas encore définie (configurée sur « NON »).', + 'TRACE_GROUP_YES_TOTAL_YES' => 'Cette permission de groupe est configurée sur « OUI » mais la permission totale est déjà configurée sur « OUI ». Le résultat total est donc conservé.', + 'TRACE_GROUP_YES_TOTAL_YES_LOCAL' => 'Cette permission de groupe concernant ce forum est configurée sur « OUI » mais la permission totale est déjà configurée sur « OUI ». Le résultat total est donc conservé.', + 'TRACE_PERMISSION' => 'Suivi de la permission – %s', + 'TRACE_RESULT' => 'Résultat du suivi', + 'TRACE_SETTING' => 'Paramètre du suivi', + + 'TRACE_USER_GLOBAL_YES_TOTAL_YES' => 'La permission indépendante de l’utilisateur dans ce forum est configurée sur « OUI » mais la permission totale est déjà configurée sur « OUI ». Le résultat total est donc conservé. %sSuivre la permission générale%s', + 'TRACE_USER_GLOBAL_YES_TOTAL_NEVER' => 'La permission indépendante de l’utilisateur dans ce le forum est configurée sur « OUI » ce qui remplace l’actuel résultat local « JAMAIS ». %sSuivre la permission générale%s', + 'TRACE_USER_GLOBAL_NEVER_TOTAL_KEPT' => 'La permission indépendante de l’utilisateur dans ce forum est configurée sur « JAMAIS » ce qui n’influence pas la permission locale. %sSuivre la permission générale%s', + + 'TRACE_USER_FOUNDER' => 'L’utilisateur est un fondateur. Les permissions d’administrateurs sont donc toujours définies sur « OUI ».', + 'TRACE_USER_KEPT' => 'La permission de l’utilisateur est configurée sur « NON ». L’ancienne valeur totale est donc conservée.', + 'TRACE_USER_KEPT_LOCAL' => 'La permission de l’utilisateur concernant ce forum est configurée sur « NON ». L’ancienne valeur totale est donc conservée.', + 'TRACE_USER_NEVER_TOTAL_NEVER' => 'La permission de l’utilisateur est configurée sur « JAMAIS » et la valeur totale est configurée sur « JAMAIS ». Il n’y a donc aucune modification.', + 'TRACE_USER_NEVER_TOTAL_NEVER_LOCAL' => 'La permission de l’utilisateur concernant ce forum est configurée sur « JAMAIS » et la valeur totale est configurée sur « JAMAIS ». Il n’y a donc aucune modification.', + 'TRACE_USER_NEVER_TOTAL_NO' => 'La permission de l’utilisateur est configurée sur « JAMAIS » ce qui devient la valeur totale car elle était configurée sur « NON ».', + 'TRACE_USER_NEVER_TOTAL_NO_LOCAL' => 'La permission de l’utilisateur concernant ce forum est configurée sur « JAMAIS » ce qui devient la valeur totale car elle était configurée sur « NON ».', + 'TRACE_USER_NEVER_TOTAL_YES' => 'La permission de l’utilisateur est configurée sur « JAMAIS » et remplace la précédente permission « OUI ».', + 'TRACE_USER_NEVER_TOTAL_YES_LOCAL' => 'La permission de l’utilisateur concernant ce forum est configurée sur « JAMAIS » et remplace la précédente permission « OUI ».', + 'TRACE_USER_NO_TOTAL_NO' => 'La permission de l’utilisateur est configurée sur « NON » et la valeur totale était configurée sur « NON », donc sur « JAMAIS » par défaut.', + 'TRACE_USER_NO_TOTAL_NO_LOCAL' => 'La permission de l’utilisateur concernant ce forum est configurée sur « NON » et la valeur totale était configurée sur « NON », donc sur « JAMAIS » par défaut.', + 'TRACE_USER_YES_TOTAL_NEVER' => 'La permission de l’utilisateur est configurée sur « OUI » mais le total de « JAMAIS » ne peut pas être remplacé.', + 'TRACE_USER_YES_TOTAL_NEVER_LOCAL' => 'La permission de l’utilisateur concernant ce forum est configurée sur « OUI » mais le total de « JAMAIS » ne peut pas être remplacé.', + 'TRACE_USER_YES_TOTAL_NO' => 'La permission de l’utilisateur est configurée sur « OUI » ce qui devient la valeur totale car elle était configurée sur « NON ».', + 'TRACE_USER_YES_TOTAL_NO_LOCAL' => 'La permission de l’utilisateur concernant ce forum est configurée sur « OUI » ce qui devient la valeur totale car elle était configurée sur « NON ».', + 'TRACE_USER_YES_TOTAL_YES' => 'La permission de l’utilisateur est configurée sur « OUI » et la valeur totale est configurée sur « OUI ». Il n’y a donc aucune modification.', + 'TRACE_USER_YES_TOTAL_YES_LOCAL' => 'La permission de l’utilisateur concernant ce forum est configurée sur « OUI » et la valeur totale est configurée sur « OUI ». Il n’y a donc aucune modification.', + 'TRACE_WHO' => 'Qui', + 'TRACE_TOTAL' => 'Total', + + 'USERS_NOT_ASSIGNED' => 'Aucun utilisateur n’est assigné à ce rôle', + 'USER_IS_MEMBER_OF_DEFAULT' => 'est un membre des groupes d’utilisateurs prédéfinis suivants', + 'USER_IS_MEMBER_OF_CUSTOM' => 'est un membre des groupes d’utilisateurs suivants', + + 'VIEW_ASSIGNED_ITEMS' => 'Consulter les éléments assignés', + 'VIEW_LOCAL_PERMS' => 'Permissions locales', + 'VIEW_GLOBAL_PERMS' => 'Permissions générales', + 'VIEW_PERMISSIONS' => 'Consulter les permissions', + + 'WRONG_PERMISSION_TYPE' => 'Le type de permission que vous avez spécifié est incorrect.', + 'WRONG_PERMISSION_SETTING_FORMAT' => 'Une erreur est survenue lors du traitement des paramètres des permissions car elles sont enregistrés dans un mauvais format.', +]); diff --git a/language/fr/acp/permissions_phpbb.php b/language/fr/acp/permissions_phpbb.php new file mode 100644 index 0000000..31b91f5 --- /dev/null +++ b/language/fr/acp/permissions_phpbb.php @@ -0,0 +1,211 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// EXTENSION-DEVELOPERS PLEASE NOTE +// +// You are able to put your permission sets into your extension. +// The permissions logic should be added via the 'core.permissions' event. +// You can easily add new permission categories, types and permissions, by +// simply merging them into the respective arrays. +// The respective language strings should be added into a language file, that +// start with 'permissions_', so they are automatically loaded within the ACP. + +$lang = array_merge($lang, [ + 'ACL_CAT_ACTIONS' => 'Opérations', + 'ACL_CAT_CONTENT' => 'Contenu', + 'ACL_CAT_FORUMS' => 'Forums', + 'ACL_CAT_MISC' => 'Divers', + 'ACL_CAT_PERMISSIONS' => 'Permissions', + 'ACL_CAT_PM' => 'Messages privés', + 'ACL_CAT_POLLS' => 'Sondages', + 'ACL_CAT_POST' => 'Message', + 'ACL_CAT_POST_ACTIONS' => 'Opérations des messages', + 'ACL_CAT_POSTING' => 'Publication', + 'ACL_CAT_PROFILE' => 'Profil', + 'ACL_CAT_SETTINGS' => 'Paramètres', + 'ACL_CAT_TOPIC_ACTIONS' => 'Opérations des sujets', + 'ACL_CAT_USER_GROUP' => 'Utilisateurs et groupes', +]); + +// User Permissions +$lang = array_merge($lang, [ + 'ACL_U_VIEWPROFILE' => 'Peut consulter les profils, la liste des membres et la liste des utilisateurs en ligne', + 'ACL_U_CHGNAME' => 'Peut modifier son nom d’utilisateur', + 'ACL_U_CHGPASSWD' => 'Peut modifier son mot de passe', + 'ACL_U_CHGEMAIL' => 'Peut modifier son adresse de courriel', + 'ACL_U_CHGAVATAR' => 'Peut modifier son avatar', + 'ACL_U_CHGGRP' => 'Peut modifier son groupe d’utilisateurs par défaut', + 'ACL_U_CHGPROFILEINFO' => 'Peut modifier les champs d’information de son profil', + + 'ACL_U_ATTACH' => 'Peut transférer des pièces jointes', + 'ACL_U_DOWNLOAD' => 'Peut télécharger les pièces jointes', + 'ACL_U_SAVEDRAFTS' => 'Peut enregistrer des messages comme brouillons', + 'ACL_U_CHGCENSORS' => 'Peut désactiver la censure de mots', + 'ACL_U_SIG' => 'Peut insérer une signature', + + 'ACL_U_SENDPM' => 'Peut envoyer des messages privés', + 'ACL_U_MASSPM' => 'Peut envoyer des messages privés à plusieurs utilisateurs', + 'ACL_U_MASSPM_GROUP' => 'Peut envoyer des messages privés aux groupes d’utilisateurs', + 'ACL_U_READPM' => 'Peut lire ses messages privés', + 'ACL_U_PM_EDIT' => 'Peut modifier ses messages privés', + 'ACL_U_PM_DELETE' => 'Peut supprimer les messages privés de ses boîtes', + 'ACL_U_PM_FORWARD' => 'Peut transférer les messages privés', + 'ACL_U_PM_EMAILPM' => 'Peut envoyer les messages privés par courriel', + 'ACL_U_PM_PRINTPM' => 'Peut imprimer les messages privés', + 'ACL_U_PM_ATTACH' => 'Peut transférer des pièces jointes dans ses messages privés', + 'ACL_U_PM_DOWNLOAD' => 'Peut télécharger les pièces jointes transférées à des messages privés', + 'ACL_U_PM_BBCODE' => 'Peut utiliser du BBCode dans les messages privés', + 'ACL_U_PM_SMILIES' => 'Peut insérer des émoticônes dans les messages privés', + 'ACL_U_PM_IMG' => 'Peut utiliser la balise BBCode [img] dans les messages privés', + 'ACL_U_PM_FLASH' => 'Peut utiliser la balise BBCode [flash] dans les messages privés', + + 'ACL_U_SENDEMAIL' => 'Peut envoyer des courriels', + 'ACL_U_SENDIM' => 'Peut envoyer des messages instantanés', + 'ACL_U_IGNOREFLOOD' => 'Peut ignorer la limite de flood', + 'ACL_U_HIDEONLINE' => 'Peut masquer son statut en ligne', + 'ACL_U_VIEWONLINE' => 'Peut voir les utilisateurs invisibles en ligne', + 'ACL_U_SEARCH' => 'Peut rechercher sur le forum', +]); + +// Forum Permissions +$lang = array_merge($lang, [ + 'ACL_F_LIST' => 'Peut voir le forum', + 'ACL_F_LIST_TOPICS' => 'Peut voir les sujets', + 'ACL_F_READ' => 'Peut consulter les messages du forum', + 'ACL_F_SEARCH' => 'Peut effectuer des recherches sur le forum', + 'ACL_F_SUBSCRIBE' => 'Peut s’abonner au forum', + 'ACL_F_PRINT' => 'Peut imprimer les sujets', + 'ACL_F_EMAIL' => 'Peut envoyer les sujets par courriel', + 'ACL_F_BUMP' => 'Peut remonter des sujets', + 'ACL_F_USER_LOCK' => 'Peut verrouiller ses sujets', + 'ACL_F_DOWNLOAD' => 'Peut télécharger les pièces jointes', + 'ACL_F_REPORT' => 'Peut rapporter les messages', + + 'ACL_F_POST' => 'Peut publier de nouveaux sujets', + 'ACL_F_STICKY' => 'Peut publier des notes', + 'ACL_F_ANNOUNCE' => 'Peut publier des annonces', + 'ACL_F_ANNOUNCE_GLOBAL' => 'Peut publier des annonces générales', + 'ACL_F_REPLY' => 'Peut répondre aux sujets', + 'ACL_F_EDIT' => 'Peut modifier ses messages', + 'ACL_F_DELETE' => 'Peut supprimer définitivement ses messages', + 'ACL_F_SOFTDELETE' => 'Peut supprimer ses messages
Veuillez noter que les modérateurs qui peuvent approuver des messages pourront également restaurer les messages supprimés.', + 'ACL_F_IGNOREFLOOD' => 'Peut ignorer la limite de flood', + 'ACL_F_POSTCOUNT' => 'Peut augmenter son compteur de messages
Veuillez noter que cette option n’affecte que les nouveaux messages.', + 'ACL_F_NOAPPROVE' => 'Peut publier des messages sans approbation', + + 'ACL_F_ATTACH' => 'Peut transférer des pièces jointes', + 'ACL_F_ICONS' => 'Peut insérer des icônes de sujet et de message', + 'ACL_F_BBCODE' => 'Peut utiliser du BBCode', + 'ACL_F_FLASH' => 'Peut utiliser la balise BBCode [flash]', + 'ACL_F_IMG' => 'Peut utiliser la balise BBCode [img]', + 'ACL_F_SIGS' => 'Peut insérer une signature', + 'ACL_F_SMILIES' => 'Peut insérer des émoticônes', + + 'ACL_F_POLL' => 'Peut créer des sondages', + 'ACL_F_VOTE' => 'Peut voter aux sondages', + 'ACL_F_VOTECHG' => 'Peut modifier ses votes', +]); + +// Moderator Permissions +$lang = array_merge($lang, [ + 'ACL_M_EDIT' => 'Peut modifier les messages', + 'ACL_M_DELETE' => 'Peut supprimer définitivement les messages', + 'ACL_M_SOFTDELETE' => 'Peut supprimer les messages
Veuillez noter que les modérateurs qui peuvent approuver des messages pourront également restaurer les messages supprimés.', + 'ACL_M_APPROVE' => 'Peut approuver et restaurer les messages', + 'ACL_M_REPORT' => 'Peut fermer et supprimer les rapports', + 'ACL_M_CHGPOSTER' => 'Peut modifier les auteurs des messages', + + 'ACL_M_MOVE' => 'Peut déplacer les sujets', + 'ACL_M_LOCK' => 'Peut verrouiller les sujets', + 'ACL_M_SPLIT' => 'Peut diviser les sujets', + 'ACL_M_MERGE' => 'Peut fusionner les sujets', + + 'ACL_M_INFO' => 'Peut consulter les informations sur les messages', + 'ACL_M_WARN' => 'Peut distribuer des avertissements
Cette option n’est assignée que généralement.', // This moderator setting is only global (and not local) + 'ACL_M_PM_REPORT' => 'Peut fermer et supprimer les rapports de messages privés
Cette option n’est assignée que généralement.', // This moderator setting is only global (and not local) + 'ACL_M_BAN' => 'Peut gérer les bannissements
Cette option n’est assignée que généralement.', // This moderator setting is only global (and not local) +]); + +// Admin Permissions +$lang = array_merge($lang, [ + 'ACL_A_BOARD' => 'Peut modifier les paramètres du forum et rechercher les mises à jour', + 'ACL_A_SERVER' => 'Peut modifier les paramètres du serveur et de la communication', + 'ACL_A_JABBER' => 'Peut modifier les paramètres de Jabber', + 'ACL_A_PHPINFO' => 'Peut consulter les paramètres de PHP', + + 'ACL_A_FORUM' => 'Peut gérer les forums', + 'ACL_A_FORUMADD' => 'Peut ajouter de nouveaux forums', + 'ACL_A_FORUMDEL' => 'Peut supprimer les forums', + 'ACL_A_PRUNE' => 'Peut délester les forums', + + 'ACL_A_ICONS' => 'Peut modifier les icônes de sujet et de message et les émoticônes', + 'ACL_A_WORDS' => 'Peut modifier la censure de mots', + 'ACL_A_BBCODE' => 'Peut définir les balises BBCodes', + 'ACL_A_ATTACH' => 'Peut modifier les paramètres relatifs aux pièces jointes', + + 'ACL_A_USER' => 'Peut gérer les utilisateurs
Cela inclut également la possibilité de pouvoir consulter l’agent du navigateur des utilisateurs dans la liste des utilisateurs en ligne.', + 'ACL_A_USERDEL' => 'Peut supprimer et délester les utilisateurs', + 'ACL_A_GROUP' => 'Peut gérer les groupes d’utilisateurs', + 'ACL_A_GROUPADD' => 'Peut ajouter de nouveaux groupes d’utilisateurs', + 'ACL_A_GROUPDEL' => 'Peut supprimer les groupes d’utilisateurs', + 'ACL_A_RANKS' => 'Peut gérer les rangs', + 'ACL_A_PROFILE' => 'Peut gérer les champs de profil personnalisés', + 'ACL_A_NAMES' => 'Peut autoriser ou interdire les noms d’utilisateurs', + 'ACL_A_BAN' => 'Peut gérer les bannissements', + + 'ACL_A_VIEWAUTH' => 'Peut consulter les masques de permission', + 'ACL_A_AUTHGROUPS' => 'Peut modifier les permissions des groupes individuels', + 'ACL_A_AUTHUSERS' => 'Peut modifier les permissions des utilisateurs individuels', + 'ACL_A_FAUTH' => 'Peut modifier les permissions des forums', + 'ACL_A_MAUTH' => 'Peut modifier les permissions des modérateurs', + 'ACL_A_AAUTH' => 'Peut modifier les permissions des administrateurs', + 'ACL_A_UAUTH' => 'Peut modifier les permissions des utilisateurs', + 'ACL_A_ROLES' => 'Peut gérer les rôles', + 'ACL_A_SWITCHPERM' => 'Peut utiliser d’autres permissions', + + 'ACL_A_STYLES' => 'Peut gérer les styles', + 'ACL_A_EXTENSIONS' => 'Peut gérer les extensions', + 'ACL_A_VIEWLOGS' => 'Peut consulter les historiques', + 'ACL_A_CLEARLOGS' => 'Peut effacer les historiques', + 'ACL_A_MODULES' => 'Peut gérer les modules', + 'ACL_A_LANGUAGE' => 'Peut gérer les langues', + 'ACL_A_EMAIL' => 'Peut envoyer des courriels de masse', + 'ACL_A_BOTS' => 'Peut gérer les robots', + 'ACL_A_REASONS' => 'Peut gérer les rapports et les raisons', + 'ACL_A_BACKUP' => 'Peut sauvegarder et restaurer la base de données', + 'ACL_A_SEARCH' => 'Peut gérer l’indexation et les paramètres de la recherche', +]); diff --git a/language/fr/acp/posting.php b/language/fr/acp/posting.php new file mode 100644 index 0000000..4a8044a --- /dev/null +++ b/language/fr/acp/posting.php @@ -0,0 +1,290 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// BBCodes +// Note to translators: you can translate everything but what's between { and } +$lang = array_merge($lang, [ + 'ACP_BBCODES_EXPLAIN' => 'Le BBCode est une implémentation spéciale du code HTML qui offre un meilleur contrôle sur la mise en forme. De cette page, vous pouvez ajouter, supprimer et modifier des BBCodes personnalisés.', + 'ADD_BBCODE' => 'Ajouter un nouveau BBCode', + + 'BBCODE_DANGER' => 'Le BBCode que vous essayez d’ajouter semble utiliser une chaîne de symboles « {TEXT} » dans un attribut HTML. Cela peut présenter une éventuelle faille de sécurité XSS. Veuillez plutôt essayer d’utiliser un type plus restrictif, tel que « {SIMPLETEXT} » ou « {INTTEXT} ». Ne procédez à cette opération que si vous comprenez les risque encourus et que vous considérez que l’utilisation de « {TEXT} » est absolumement inévitable.', + 'BBCODE_DANGER_PROCEED' => 'Procéder', //'I understand the risk', + + 'BBCODE_ADDED' => 'Le BBCode a été ajouté.', + 'BBCODE_EDITED' => 'Le BBCode a été modifié.', + 'BBCODE_DELETED' => 'Le BBCode a été supprimé.', + 'BBCODE_NOT_EXIST' => 'Le BBCode que vous avez spécifié est introuvable.', + 'BBCODE_HELPLINE' => 'Ligne d’aide', + 'BBCODE_HELPLINE_EXPLAIN' => 'Vous pouvez saisir dans ce champ le texte qui sera affiché lors du passage de la souris sur le bouton du BBCode.', + 'BBCODE_HELPLINE_TEXT' => 'Texte de la ligne d’aide', + 'BBCODE_HELPLINE_TOO_LONG' => 'La ligne d’aide que vous avez saisie est trop longue.', + + 'BBCODE_INVALID_TAG_NAME' => 'Le nom de la balise BBCode que vous avez spécifié existe déjà.', + 'BBCODE_INVALID' => 'Votre BBCode est construit dans une architecture invalide.', + 'BBCODE_OPEN_ENDED_TAG' => 'Votre BBCode personnalisé doit contenir une balise d’ouverture et de fermeture.', + 'BBCODE_TAG' => 'Balise', + 'BBCODE_TAG_TOO_LONG' => 'Le nom de la balise que vous avez spécifié est trop long.', + 'BBCODE_TAG_DEF_TOO_LONG' => 'La définition de la balise que vous avez spécifiée est trop longue, veuillez la raccourcir.', + 'BBCODE_USAGE' => 'Structure du BBCode', + 'BBCODE_USAGE_EXAMPLE' => '[highlight={COLOR}]{TEXT}[/highlight]

[font={SIMPLETEXT1}]{SIMPLETEXT2}[/font]', + 'BBCODE_USAGE_EXPLAIN' => 'Vous pouvez saisir dans ce champ la structure du BBCode que vos utilisateurs devront respecter lors de son utilisation. Remplacez les variables des éléments par les chaînes de symboles correspondantes (%svoir ci-dessous%s).', + + 'EXAMPLE' => 'Exemple :', + 'EXAMPLES' => 'Exemples :', + + 'HTML_REPLACEMENT' => 'Code HTML', + 'HTML_REPLACEMENT_EXAMPLE' => '<span style="background-color: {COLOR};">{TEXT}</span>

<span style="font-family: {SIMPLETEXT1};">{SIMPLETEXT2}</span>', + 'HTML_REPLACEMENT_EXPLAIN' => 'Vous pouvez saisir dans ce champ le code HTML qui sera affiché lors de l’utilisation du BBCode saisi ci-dessus. N’oubliez pas d’utiliser toutes les chaînes de symboles que vous avez utilisées dans votre BBCode !', + + 'TOKEN' => 'Chaîne de symboles', + 'TOKENS' => 'Chaînes de symboles', + 'TOKENS_EXPLAIN' => 'Les chaînes de symboles sont des marques substitutives pour les entrées des utilisateurs. Les entrées ne seront validées que si elles trouvent la définition correspondante. Si besoin, vous pouvez les numéroter en ajoutant un nombre entre des accolades comme dernier caractère, tel que « {TEXT1} », « {TEXT2} », etc.

Dans le code HTML, vous pouvez également utiliser une chaîne de langue dans votre répertoire « language/ » tel que « {L_« NOMDELACHAÎNE »} » où « NOMDELACHAÎNE » est le nom de la chaîne traduite que vous souhaitez ajouter. Par exemple, « {L_WROTE} » sera affiché en tant que « a écrit » ou sa traduction selon la langue locale de l’utilisateur.

Veuillez noter que seules les chaînes listées ci-dessous sont autorisées à être utilisées dans les BBCodes personnalisés.', + 'TOKEN_DEFINITION' => 'Qu’est-ce que c’est ?', + 'TOO_MANY_BBCODES' => 'Vous ne pouvez pas créer d’autres BBCodes. Veuillez supprimer un ou plusieurs BBCodes et réessayer.', + + 'tokens' => [ + 'TEXT' => 'Du texte, comprenant les caractères étrangers, les chiffres, etc. Vous ne devriez pas utiliser cette chaîne de symboles dans les balises HTML. Essayez d’utiliser à la place « IDENTIFIER », « INTTEXT » ou « SIMPLETEXT ».', + 'SIMPLETEXT' => 'Des caractères de l’alphabet latin (A–Z), des chiffres, des espaces, des virgules, des points, des tirets, des tirets bas et des signes plus et moins.', + 'INTTEXT' => 'Des lettres de caractères Unicode, des chiffres, des espaces, des virgules, des points, des tirets bas et des espaces insécables et des signes plus et moins.', + 'IDENTIFIER' => 'Des caractères de l’alphabet latin (A–Z), des chiffres, des tirets et des tirets bas.', + 'NUMBER' => 'Une série de chiffres.', + 'EMAIL' => 'Une adresse de courriel correcte.', + 'URL' => 'Une adresse universelle correcte utilisant n’importe quel protocole (HTTP, FTP, etc.) ne pouvant pas être utilisée pour des injections de Javascript. Si aucun protocole n’est renseigné, « http:// » sera utilisé par défaut.', + 'LOCAL_URL' => 'Une adresse universelle locale. L’adresse doit être relative à la page du sujet et ne doit pas contenir de nom de serveur ou de protocole, tels que des liens qui sont précédés de « %s »', + 'RELATIVE_URL' => 'Une adresse universelle relative. Vous pouvez utiliser des résultats partiels d’une adresse universelle, mais soyez vigilant, car une adresse universelle complète est une adresse universelle relative valide. Lorsque vous souhaitez utiliser les adresses universelles relatives de votre forum, utilisez la variable « LOCAL_URL ».', + 'COLOR' => 'Une couleur HTML qui peut être dans une forme hexadécimale « #FF1234 » ou un nom de couleur CSS (en anglais), tel que « black » ou « fuchsia ».', + ], +]); + +// Smilies and topic icons +$lang = array_merge($lang, [ + 'ACP_ICONS_EXPLAIN' => 'Depuis cette page, vous pouvez ajouter, supprimer ou modifier les icônes que les utilisateurs peuvent insérer aux sujets et aux messages. Ces icônes sont généralement affichées à côté des titres des sujets sur la liste des forums ou à côté des titres des messages sur la liste des sujets. Vous pouvez également installer et créer de nouvelles archives d’icônes.', + 'ACP_SMILIES_EXPLAIN' => 'Les émoticônes sont généralement de petites images, parfois animées, qui sont utilisées afin d’exprimer une émotion ou un sentiment. Depuis cette page, vous pouvez ajouter, supprimer et modifier les émoticônes que les utilisateurs peuvent insérer dans leurs messages et leurs messages privés. Vous pouvez également installer et créer de nouvelles archives d’émoticônes.', + 'ADD_SMILIES' => 'Ajouter plusieurs émoticônes', + 'ADD_SMILEY_CODE' => 'Ajouter un code d’émoticône additionnel', + 'ADD_ICONS' => 'Ajouter plusieurs icônes', + 'AFTER_ICONS' => 'Après %s', + 'AFTER_SMILIES' => 'Après %s', + + 'CODE' => 'Code', + 'CURRENT_ICONS' => 'Icônes actuelles', + 'CURRENT_ICONS_EXPLAIN' => 'Choisissez ce que vous souhaitez faire des icônes actuellement installées.', + 'CURRENT_SMILIES' => 'Émoticônes actuelles', + 'CURRENT_SMILIES_EXPLAIN' => 'Choisissez ce que vous souhaitez faire des émoticônes actuellement installées.', + + 'DISPLAY_ON_POSTING' => 'Afficher sur la page de rédaction', + 'DISPLAY_POSTING' => 'Sur la page de rédaction', + 'DISPLAY_POSTING_NO' => 'Pas sur la page de rédaction', + + 'EDIT_ICONS' => 'Modifier les icônes', + 'EDIT_SMILIES' => 'Modifier les émoticônes', + 'EMOTION' => 'Émotion', + 'EXPORT_ICONS' => 'Exporter et télécharger « icons.pak »', + 'EXPORT_ICONS_EXPLAIN' => '%sEn cliquant sur ce lien, la configuration des icônes installées sera archivée dans « icons.pak » qui, une fois téléchargé, pourra être utilisé afin de créer un fichier compressé au format « .zip » ou « .tgz » qui contiendra toutes vos icônes, ainsi que le fichier de configuration « icons.pak »%s.', + 'EXPORT_SMILIES' => 'Exporter et télécharger « smilies.pak »', + 'EXPORT_SMILIES_EXPLAIN' => '%sEn cliquant sur ce lien, la configuration des émoticônes installées sera archivée dans « smilies.pak » qui, une fois téléchargé, pourra être utilisé afin de créer un fichier compressé au format « .zip » ou « .tgz » qui contiendra toutes vos émoticônes, ainsi que le fichier de configuration « smilies.pak »%s.', + + 'FIRST' => 'Premier', + + 'ICONS_ADD' => 'Ajouter une nouvelle icône', + 'ICONS_ADDED' => [ + 0 => 'Aucune icône n’a été ajoutée.', + 1 => 'L’icône a été ajoutée.', + 2 => 'Les icônes ont été ajoutées.', + ], + 'ICONS_CONFIG' => 'Configuration de l’icône', + 'ICONS_DELETED' => 'L’icône a été supprimée.', + 'ICONS_EDIT' => 'Modifier l’icône', + 'ICONS_EDITED' => [ + 0 => 'Aucune icône n’a été mise à jour.', + 1 => 'L’icône a été mise à jour.', + 2 => 'Les icônes ont été mises à jour.', + ], + 'ICONS_HEIGHT' => 'Hauteur de l’icône', + 'ICONS_IMAGE' => 'Image de l’icône', + 'ICONS_IMPORTED' => 'L’archive d’icônes a été installée.', + 'ICONS_IMPORT_SUCCESS' => 'L’archive d’icônes a été importée.', + 'ICONS_LOCATION' => 'Emplacement de l’icône', + 'ICONS_NOT_DISPLAYED' => 'Les icônes suivantes ne sont pas affichées sur la page de rédaction', + 'ICONS_ORDER' => 'Position de l’icône', + 'ICONS_URL' => 'Image de l’icône', + 'ICONS_WIDTH' => 'Largeur de l’icône', + 'IMPORT_ICONS' => 'Installer une archive d’icônes', + 'IMPORT_SMILIES' => 'Installer une archive d’émoticônes', + + 'KEEP_ALL' => 'Tout conserver', + + 'MASS_ADD_SMILIES' => 'Ajouter plusieurs émoticônes', + + 'NO_ICONS_ADD' => 'Impossible d’ajouter une icône car aucune icône n’est disponible.', + 'NO_ICONS_EDIT' => 'Impossible de modifier une icône car aucune icône n’est disponible.', + 'NO_ICONS_EXPORT' => 'Aucune icône permettant de créer une archive n’est disponible.', + 'NO_ICONS_PAK' => 'Aucune archive d’icônes n’a été trouvée.', + 'NO_SMILIES_ADD' => 'Impossible d’ajouter une émoticône car aucune émoticône n’est disponible.', + 'NO_SMILIES_EDIT' => 'Impossible de modifier une émoticône car aucune émoticône n’est disponible.', + 'NO_SMILIES_EXPORT' => 'Aucune émoticône permettant de créer une archive n’est disponible.', + 'NO_SMILIES_PAK' => 'Aucune archive d’émoticônes n’a été trouvée.', + + 'PAK_FILE_NOT_READABLE' => 'Impossible de lire le fichier « .pak ».', + + 'REPLACE_MATCHES' => 'Remplacer les résultats', + + 'SELECT_PACKAGE' => 'Sélectionner une archive', + 'SMILIES_ADD' => 'Ajouter une nouvelle émoticône', + 'SMILIES_ADDED' => [ + 0 => 'Aucune émoticône n’a été ajoutée.', + 1 => 'L’émoticône a été ajoutée.', + 2 => 'Les émoticônes ont été ajoutées.', + ], + 'SMILIES_CODE' => 'Code de l’émoticône', + 'SMILIES_CONFIG' => 'Configuration de l’émoticône', + 'SMILIES_DELETED' => 'L’émoticône a été supprimée.', + 'SMILIES_EDIT' => 'Modifier l’émoticône', + 'SMILIE_NO_CODE' => 'L’émoticône « %s » a été ignorée car aucun code n’a été spécifié.', + 'SMILIE_NO_EMOTION' => 'L’émoticône « %s » a été ignorée car aucune émoticône n’a été spécifiée.', + 'SMILIE_NO_FILE' => 'L’émoticône « %s » a été ignorée car le fichier est manquant.', + 'SMILIES_EDITED' => [ + 0 => 'Aucune émoticône n’a été mise à jour.', + 1 => 'L’émoticône a été mise à jour.', + 2 => 'Les émoticônes ont été mises à jour.', + ], + 'SMILIES_EMOTION' => 'Émotion', + 'SMILIES_HEIGHT' => 'Hauteur de l’émoticône', + 'SMILIES_IMAGE' => 'Image de l’émoticône', + 'SMILIES_IMPORTED' => 'L’archive d’émoticônes a été installée.', + 'SMILIES_IMPORT_SUCCESS' => 'L’archive d’émoticônes a été importée.', + 'SMILIES_LOCATION' => 'Emplacement de l’émoticône', + 'SMILIES_NOT_DISPLAYED' => 'Les émoticônes suivantes ne sont pas affichées sur la page de rédaction', + 'SMILIES_ORDER' => 'Position de l’émoticône', + 'SMILIES_URL' => 'Image de l’émoticône', + 'SMILIES_WIDTH' => 'Largeur de l’émoticône', + + 'TOO_MANY_SMILIES' => [ + 1 => 'La limite de %d émoticône a été atteinte.', + 2 => 'La limite de %d émoticônes a été atteinte.', + ], + + 'WRONG_PAK_TYPE' => 'L’archive ne contient pas les données appropriées.', +]); + +// Word censors +$lang = array_merge($lang, [ + 'ACP_WORDS_EXPLAIN' => 'Depuis cette page, vous pouvez ajouter, modifier et supprimer les mots qui seront automatiquement censurés sur votre forum. Les mots censurés seront alors remplacés par les substitutions qui leurs sont assignées. Les visiteurs pourront tout de même s’inscrire avec des noms d’utilisateurs contenant ces mots. Les astérisques « * » utilisés comme métacaractère passe-partout sont acceptés dans les champs. Par exemple, « *test* » censurera « détestable », « test* » censurera « testament », « *test » censurera « alcootest ».', + 'ADD_WORD' => 'Ajouter un nouveau mot', + + 'EDIT_WORD' => 'Modifier la censure de mot', + 'ENTER_WORD' => 'Vous devez saisir un mot et sa substitution.', + + 'NO_WORD' => 'Vous n’avez sélectionné aucun mot à modifier.', + + 'REPLACEMENT' => 'Substitution', + + 'UPDATE_WORD' => 'Mettre à jour la censure de mot', + + 'WORD' => 'Mot', + 'WORD_ADDED' => 'La censure de mot a été ajoutée.', + 'WORD_REMOVED' => 'La censure de mot a été supprimée.', + 'WORD_UPDATED' => 'La censure de mot a été mise à jour.', +]); + +// Ranks +$lang = array_merge($lang, [ + 'ACP_RANKS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter, ajouter, modifier et supprimer les rangs des utilisateurs de votre forum. Vous pouvez également créer des rangs personnalisés qui peuvent être mis en place sur certains de vos utilisateurs depuis la page de gestion des utilisateurs.', + 'ADD_RANK' => 'Ajouter un nouveau rang', + + 'MUST_SELECT_RANK' => 'Vous devez sélectionner un rang.', + + 'NO_ASSIGNED_RANK' => 'Aucun rang spécial n’a été spécifié.', + 'NO_RANK_TITLE' => 'Vous n’avez pas spécifié le titre du rang.', + 'NO_UPDATE_RANKS' => 'Le rang a été supprimé. Cependant, les comptes des utilisateurs qui utilisent ce rang n’ont pas été mis à jour. Vous devrez réinitialiser manuellement le rang de ces comptes.', + + 'RANK_ADDED' => 'Le rang a été ajouté.', + 'RANK_IMAGE' => 'Image du rang', + 'RANK_IMAGE_EXPLAIN' => 'Vous permet de définir une petite image à associer au rang. Le chemin est relatif à la racine du répertoire de votre forum.', + 'RANK_IMAGE_IN_USE' => '(en cours d’utilisation)', + 'RANK_MINIMUM' => 'Nombre minimal de messages', + 'RANK_REMOVED' => 'Le rang a été supprimé.', + 'RANK_SPECIAL' => 'Définir comme rang spécial', + 'RANK_TITLE' => 'Titre du rang', + 'RANK_UPDATED' => 'Le rang a été mis à jour.', +]); + +// Disallow Usernames +$lang = array_merge($lang, [ + 'ACP_DISALLOW_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les noms d’utilisateurs qui ne sont pas autorisés à être utilisés. Les noms d’utilisateurs interdits peuvent contenir un astérisque « * » comme métacaractère passe-partout.', + 'ADD_DISALLOW_EXPLAIN' => 'Vous pouvez interdire un nom d’utilisateur en utilisant un astérisque « * » comme métacaractère passe-partout qui remplacera alors n’importe quel caractère.', + 'ADD_DISALLOW_TITLE' => 'Interdire un nom d’utilisateur', + + 'DELETE_DISALLOW_EXPLAIN' => 'Vous pouvez autoriser un nom d’utilisateur en sélectionnant dans cette liste le nom d’utilisateur interdit souhaité et en cliquant sur « Envoyer ».', + 'DELETE_DISALLOW_TITLE' => 'Autoriser un nom d’utilisateur', + 'DISALLOWED_ALREADY' => 'Ce nom d’utilisateur est déjà interdit.', + 'DISALLOWED_DELETED' => 'Le nom d’utilisateur a été autorisé.', + 'DISALLOW_SUCCESSFUL' => 'Le nom d’utilisateur a été interdit.', + + 'NO_DISALLOWED' => 'Aucun nom d’utilisateur n’a été interdit.', + 'NO_USERNAME_SPECIFIED' => 'Vous devez sélectionner un nom d’utilisateur.', +]); + +// Reasons +$lang = array_merge($lang, [ + 'ACP_REASONS_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les raisons utilisées dans les rapports lors de la désapprobation de messages. La raison par défaut, signalée par un astérisque « * », ne peut pas être supprimée car elle est réservée aux messages personnalisés quand aucune raison n’est spécifiée.', + 'ADD_NEW_REASON' => 'Ajouter une nouvelle raison', + 'AVAILABLE_TITLES' => 'Titres des raisons traduites disponibles', + + 'IS_NOT_TRANSLATED' => 'La raison n’a pas été traduite.', + 'IS_NOT_TRANSLATED_EXPLAIN' => 'La raison n’a pas été traduite. Si vous souhaitez renseigner le formulaire, saisissez la clé des fichiers de langue dans la section des raisons de rapports.', + 'IS_TRANSLATED' => 'La raison a été traduite.', + 'IS_TRANSLATED_EXPLAIN' => 'La raison a été traduite. Si le titre que vous avez saisi ici est spécifié dans les fichiers de langue situés dans la section des raisons de rapports, le formulaire du titre et de la description traduit sera utilisé.', + + 'NO_REASON' => 'La raison est introuvable.', + 'NO_REASON_INFO' => 'Vous devez saisir le titre et la description de cette raison.', + 'NO_REMOVE_DEFAULT_REASON' => 'Vous ne pouvez pas supprimer la raison par défaut « Divers ».', + + 'REASON_ADD' => 'Ajouter une raison', + 'REASON_ADDED' => 'La raison a été ajoutée.', + 'REASON_ALREADY_EXIST' => 'Une raison portant ce titre existe déjà. Veuillez en saisir un autre.', + 'REASON_DESCRIPTION' => 'Description de la raison', + 'REASON_DESC_TRANSLATED' => 'Description de la raison affichée', + 'REASON_EDIT' => 'Modifier la raison', + 'REASON_EDIT_EXPLAIN' => 'Depuis cette page, vous pouvez ajouter ou modifier une raison. Si la raison est traduite, la traduction sera utilisée à la place de la description spécifiée dans le champ correspondant.', + 'REASON_REMOVED' => 'La raison a été supprimée.', + 'REASON_TITLE' => 'Titre de la raison', + 'REASON_TITLE_TRANSLATED' => 'Titre de la raison affiché', + 'REASON_UPDATED' => 'La raison a été mise à jour.', + + 'USED_IN_REPORTS' => 'Nombre d’utilisations', +]); diff --git a/language/fr/acp/profile.php b/language/fr/acp/profile.php new file mode 100644 index 0000000..7a836f6 --- /dev/null +++ b/language/fr/acp/profile.php @@ -0,0 +1,175 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Custom profile fields +$lang = array_merge($lang, [ + 'ADDED_PROFILE_FIELD' => 'Le champ de profil personnalisé a été ajouté.', + 'ALPHA_DOTS' => 'Lettres (sans accent), chiffres et points', + 'ALPHA_ONLY' => 'Lettres (sans accent) et chiffres uniquement', + 'ALPHA_SPACERS' => 'Lettres (sans accent), chiffres et séparateurs', + 'ALPHA_UNDERSCORE' => 'Lettres (sans accent), chiffres et tirets bas', + 'ALPHA_PUNCTUATION' => 'Lettres (sans accent), chiffres, virgules, points, tirets bas et tirets commençant par une lettre', + 'ALWAYS_TODAY' => 'Toujours la date actuelle', + + 'BOOL_ENTRIES_EXPLAIN' => 'Saisissez à présent vos options', + 'BOOL_TYPE_EXPLAIN' => 'Détermine le type, soit une case à cocher, soit un bouton radio. Les cases à cocher ne seront affichées que si cela est coché pour un utilisateur spécifique. Dans ce cas, la seconde option de langue sera utilisée. Les boutons radios seront affichés sans prendre en compte leur valeur.', + + 'CHANGED_PROFILE_FIELD' => 'Le champ de profil a été modifié.', + 'CHARS_ANY' => 'N’importe quel caractère', + 'CHECKBOX' => 'Case à cocher', + 'COLUMNS' => 'Colonnes', + 'CP_LANG_DEFAULT_VALUE' => 'Valeur par défaut', + 'CP_LANG_EXPLAIN' => 'Description du champ', + 'CP_LANG_EXPLAIN_EXPLAIN' => 'La description de ce champ est affichée aux utilisateurs.', + 'CP_LANG_NAME' => 'Nom ou titre du champ affiché aux utilisateurs', + 'CP_LANG_OPTIONS' => 'Options', + 'CREATE_NEW_FIELD' => 'Créer un nouveau champ', + 'CUSTOM_FIELDS_NOT_TRANSLATED' => 'Au moins un champ de profil personnalisé n’a pas encore été traduit. Veuillez compléter l’information demandée en cliquant sur le lien « Traduire ».', + + 'DEFAULT_ISO_LANGUAGE' => 'Langue par défaut [%s]', + 'DEFAULT_LANGUAGE_NOT_FILLED' => 'Les éléments de langue de la langue par défaut ne sont pas spécifiés concernant ce champ de profil.', + 'DEFAULT_VALUE' => 'Valeur par défaut', + 'DELETE_PROFILE_FIELD' => 'Supprimer le champ de profil', + 'DELETE_PROFILE_FIELD_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce champ de profil ?', + 'DISPLAY_AT_PROFILE' => 'Afficher dans le panneau de contrôle de l’utilisateur', + 'DISPLAY_AT_PROFILE_EXPLAIN' => 'L’utilisateur pourra modifier ce champ de profil depuis le panneau de contrôle de l’utilisateur.', + 'DISPLAY_AT_REGISTER' => 'Afficher lors de l’inscription', + 'DISPLAY_AT_REGISTER_EXPLAIN' => 'Si cette option est activée, le champ sera affiché lors de l’inscription.', + 'DISPLAY_ON_MEMBERLIST' => 'Afficher sur la page de la liste des membres', + 'DISPLAY_ON_MEMBERLIST_EXPLAIN' => 'Si cette option est activée, le champ sera affiché dans la colonne des utilisateurs sur la page de la liste des membres.', + 'DISPLAY_ON_PM' => 'Afficher sur la page de consultation des messages privés', + 'DISPLAY_ON_PM_EXPLAIN' => 'Si cette option est activée, le champ sera affiché dans le profil miniature de la page des messages privés.', + 'DISPLAY_ON_VT' => 'Afficher sur la page de la liste des sujets', + 'DISPLAY_ON_VT_EXPLAIN' => 'Si cette option est activée, le champ sera affiché dans le profil miniature de la page de la liste des sujets.', + 'DISPLAY_PROFILE_FIELD' => 'Afficher publiquement le champ de profil', + 'DISPLAY_PROFILE_FIELD_EXPLAIN' => 'Le champ de profil sera affiché dans tous les endroits autorisés par les paramètres de la charge. Veuillez désactiver cette option si vous souhaitez masquer le champ sur les pages des sujets, les profils et la liste des membres.', + 'DROPDOWN_ENTRIES_EXPLAIN' => 'Saisissez à présent vos options. Chaque option doit être saisie sur une nouvelle ligne.', + + 'EDIT_DROPDOWN_LANG_EXPLAIN' => 'Veuillez noter que vous pourrez modifier les options du texte et ajouter de nouvelles options ultérieurement. Il n’est pas conseillé d’ajouter de nouvelles options entre des options déjà existantes car cela pourrait attribuer des options erronées à vos utilisateurs. Il en est de même si vous supprimez une option que des utilisateurs utilisent. Ces derniers seront alors redirigés vers l’élément par défaut.', + 'EMPTY_FIELD_IDENT' => 'Identification du champ vide', + 'EMPTY_USER_FIELD_NAME' => 'Veuillez saisir le nom ou le titre du champ', + 'ENTRIES' => 'Éléments', + 'EVERYTHING_OK' => 'Tout est correct', + + 'FIELD_BOOL' => 'Booléen (oui/non)', + 'FIELD_CONTACT_DESC' => 'Description du contact', + 'FIELD_CONTACT_URL' => 'Lien du contact', + 'FIELD_DATE' => 'Date', + 'FIELD_DESCRIPTION' => 'Description du champ', + 'FIELD_DESCRIPTION_EXPLAIN' => 'La description de ce champ est affiché aux utilisateurs.', + 'FIELD_DROPDOWN' => 'Liste déroulante', + 'FIELD_GOOGLEPLUS' => 'Google+', + 'FIELD_IDENT' => 'Identification du champ', + 'FIELD_IDENT_ALREADY_EXIST' => 'Cette identification de champ existe déjà. Veuillez en saisir une autre.', + 'FIELD_IDENT_EXPLAIN' => 'L’identification du champ correspond au nom qui permet d’identifier le champ de profil dans la base de données et les modèles.', + 'FIELD_INT' => 'Chiffres', + 'FIELD_IS_CONTACT' => 'Afficher le champ comme un champ de contact', + 'FIELD_IS_CONTACT_EXPLAIN' => 'Les champs de contact sont affichés dans la section de contact sur le profil des utilisateurs et sont affichés différemment dans le profil miniature situé à côté des messages et des messages privés. Vous pouvez utiliser « %s » comme une variable de marque substitutive qui sera remplacée par la valeur que les utilisateurs auront saisi.', + 'FIELD_LENGTH' => 'Largeur de la boîte de saisie', + 'FIELD_NOT_FOUND' => 'Le champ de profil est introuvable.', + 'FIELD_STRING' => 'Champ de texte simple', + 'FIELD_TEXT' => 'Zone de texte', + 'FIELD_TYPE' => 'Type de champ', + 'FIELD_TYPE_EXPLAIN' => 'Le type de champ ne pourra pas être modifié ultérieurement.', + 'FIELD_URL' => 'Lien', + 'FIELD_VALIDATION' => 'Validation du champ', + 'FIRST_OPTION' => 'Première option', + + 'HIDE_PROFILE_FIELD' => 'Masquer le champ de profil', + 'HIDE_PROFILE_FIELD_EXPLAIN' => 'Le champ de profil sera invisible à tous les utilisateurs, seuls les administrateurs et les modérateurs pourront toujours le voir. Si l’affichage est désactivé dans le panneau de contrôle de l’utilisateur, l’utilisateur ne pourra pas voir ou modifier ce champ et ce dernier ne pourra être modifié que par un administrateur.', + + 'INVALID_CHARS_FIELD_IDENT' => 'L’identification du champ ne peut contenir que des lettres minuscules situées entre A et E et des tirets bas', + 'INVALID_FIELD_IDENT_LEN' => 'L’identification du champ est limitée à 17 caractères', + 'ISO_LANGUAGE' => 'Langue [%s]', + + 'LANG_SPECIFIC_OPTIONS' => 'Options spécifiques à la langue [%s]', + + 'LETTER_NUM_DOTS' => 'Tout chiffre et point', + 'LETTER_NUM_ONLY' => 'Tout chiffre et lettre', + 'LETTER_NUM_PUNCTUATION' => 'Tout chiffre, lettre, virgule, point, tiret bas et tiret commençant avec une lettre', + 'LETTER_NUM_SPACERS' => 'Tout chiffre, lettre et espace', + 'LETTER_NUM_UNDERSCORE' => 'Tout chiffre, lettre et tiret bas', + + 'MAX_FIELD_CHARS' => 'Nombre maximal de caractères', + 'MAX_FIELD_NUMBER' => 'Nombre le plus élevé autorisé', + 'MIN_FIELD_CHARS' => 'Nombre minimal de caractères', + 'MIN_FIELD_NUMBER' => 'Nombre le plus faible autorisé', + + 'NO_FIELD_ENTRIES' => 'Aucun élément n’a été spécifié', + 'NO_FIELD_ID' => 'Aucune identification de champ n’a été spécifiée.', + 'NO_FIELD_TYPE' => 'Aucun type de champ n’a été spécifié.', + 'NO_VALUE_OPTION' => 'Option égale à la valeur de non-saisie', + 'NO_VALUE_OPTION_EXPLAIN' => 'Valeur de non-saisie. Si ce champ est obligatoire et si cette option est sélectionnée, les utilisateurs obtiendront une erreur.', + 'NUMBERS_ONLY' => 'Chiffres uniquement (0–9)', + + 'PROFILE_BASIC_OPTIONS' => 'Options basiques', + 'PROFILE_FIELD_ACTIVATED' => 'Le champ de profil a été activé.', + 'PROFILE_FIELD_DEACTIVATED' => 'Le champ de profil a été désactivé.', + 'PROFILE_LANG_OPTIONS' => 'Options spécifiques à la langue', + 'PROFILE_TYPE_OPTIONS' => 'Options spécifiques au type de profil', + + 'RADIO_BUTTONS' => 'Boutons radios', + 'REMOVED_PROFILE_FIELD' => 'Le champ de profil a été supprimé.', + 'REQUIRED_FIELD' => 'Champ obligatoire', + 'REQUIRED_FIELD_EXPLAIN' => 'Les utilisateurs devront obligatoirement renseigner le champ de profil. Si l’affichage est désactivé sur la page d’inscription, le champ ne devra être renseigné que dans le cas où les utilisateurs modifient leur profil.', + 'ROWS' => 'Lignes', + + 'SAVE' => 'Enregistrer', + 'SECOND_OPTION' => 'Seconde option', + 'SHOW_NOVALUE_FIELD' => 'Afficher le champ même si aucune valeur n’a été saisie', + 'SHOW_NOVALUE_FIELD_EXPLAIN' => 'Le champ de profil sera affiché si aucune valeur n’a été saisie dans les champs optionnels ou si les champs obligatoires n’ont pas encore été remplis.', + 'STEP_1_EXPLAIN_CREATE' => 'Depuis cette page, vous pouvez renseigner les options basiques de votre champ de profil. Ces informations sont nécessaires afin de poursuivre à la seconde étape où vous pourrez définir les dernières options, prévisualiser votre champ de profil et l’améliorer si nécessaire.', + 'STEP_1_EXPLAIN_EDIT' => 'Depuis cette page, vous pouvez modifier les options basiques de votre champ de profil. Les options correspondantes sont recalculées à la seconde étape.', + 'STEP_1_TITLE_CREATE' => 'Ajouter un champ de profil', + 'STEP_1_TITLE_EDIT' => 'Modifier le champ de profil', + 'STEP_2_EXPLAIN_CREATE' => 'Depuis cette page, vous pouvez renseigner les options spécifiques de votre champ de profil.', + 'STEP_2_EXPLAIN_EDIT' => 'Depuis cette page, vous pouvez modifier les options spécifiques de votre champ de profil.
Veuillez noter que les modifications apportées n’affecteront pas les champs de profil déjà existants et complétés par vos utilisateurs.', + 'STEP_2_TITLE_CREATE' => 'Options spécifiques au type de profil', + 'STEP_2_TITLE_EDIT' => 'Options spécifiques au type de profil', + 'STEP_3_EXPLAIN_CREATE' => 'Plusieurs langues sont installées sur votre forum. Vous devez renseigner les éléments restants associés à la langue. Si vous ne le faites pas, le paramètre de langue par défaut sera utilisé dans ce champ de profil. Vous pourrez compléter ultérieurement les éléments restants associés à la langue.', + 'STEP_3_EXPLAIN_EDIT' => 'Plusieurs langues sont installées sur votre forum. Vous pouvez modifier ou ajouter les éléments restants associés à la langue. Si vous ne le faites pas, le paramètre de langue par défaut sera utilisé dans ce champ de profil.', + 'STEP_3_TITLE_CREATE' => 'Définitions de langue restantes', + 'STEP_3_TITLE_EDIT' => 'Définitions de langue', + 'STRING_DEFAULT_VALUE_EXPLAIN' => 'Veuillez saisir une phrase ou une valeur qui sera affichée par défaut. Laissez cette option vide si vous souhaitez ne rien afficher.', + + 'TEXT_DEFAULT_VALUE_EXPLAIN' => 'Veuillez saisir un texte ou une valeur qui sera affiché par défaut. Laissez cette option vide si vous souhaitez ne rien afficher.', + 'TRANSLATE' => 'Traduire', + + 'USER_FIELD_NAME' => 'Nom ou titre du champ affiché aux utilisateurs', + + 'VISIBILITY_OPTION' => 'Options de visibilité', +]); diff --git a/language/fr/acp/prune.php b/language/fr/acp/prune.php new file mode 100644 index 0000000..330e1e3 --- /dev/null +++ b/language/fr/acp/prune.php @@ -0,0 +1,94 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// User pruning +$lang = array_merge($lang, [ + 'ACP_PRUNE_USERS_EXPLAIN' => 'Depuis cette page, vous pouvez supprimer et désactiver des utilisateurs de votre forum. Les comptes peuvent être filtrés de différentes manières ; par le nombre de messages, par l’activité la plus récente, etc. Des critères peuvent être combinés afin de restreindre les comptes qui sont affectés. Par exemple, vous pouvez délester les utilisateurs qui ont publiés moins de 10 messages et qui sont inactifs depuis le 01/01/2002, ou encore utiliser un astérisque « * » comme métacaractère passe-partout dans les champs de texte. De même, vous pouvez ignorer complètement la sélection des critères en saisissant directement une liste d’utilisateurs dans le champ de texte, en prenant le soin de séparer chaque nom d’utilisateurs sur une nouvelle ligne. Soyez prudent lorsque vous utilisez cette fonctionnalité ! Une fois qu’un utilisateur a été supprimé, il ne peut pas être restauré.', + + 'CRITERIA' => 'Critère', + + 'DEACTIVATE_DELETE' => 'Désactiver ou supprimer', + 'DEACTIVATE_DELETE_EXPLAIN' => 'Choisissez si vous souhaitez désactiver ou supprimer entièrement les utilisateurs. Veuillez noter que les utilisateurs supprimés ne peuvent pas être restaurés !', + 'DELETE_USERS' => 'Supprimer', + 'DELETE_USER_POSTS' => 'Supprimer les messages des utilisateurs délestés', + 'DELETE_USER_POSTS_EXPLAIN' => 'Supprime les messages des utilisateurs délestés. Cela ne fonctionne pas avec les utilisateurs désactivés.', + + 'JOINED_EXPLAIN' => 'Saisissez une date au format « AAAA-MM-JJ ». Vous pouvez utiliser les deux champs afin de sélectionner un intervalle, ou laisser un champ vide afin de spécifier une période de temps ouverte.', + + 'LAST_ACTIVE_EXPLAIN' => 'Saisissez une date au format « AAAA-MM-JJ ». Saisissez « 0000-00-00 » afin de délester les utilisateurs qui ne se sont jamais connectés, les conditions « Avant » et « Après » seront ignorées.', + + 'POSTS_ON_QUEUE' => 'Messages en attente d’approbation', + 'PRUNE_USERS_GROUP_EXPLAIN' => 'Détermine une limite aux utilisateurs de ce groupe.', + 'PRUNE_USERS_GROUP_NONE' => 'Tous les groupes', + 'PRUNE_USERS_LIST' => 'Utilisateurs à délester', + 'PRUNE_USERS_LIST_DELETE' => 'En accord avec le critère sélectionné concernant le délestage des utilisateurs, les comptes suivants seront supprimés. Vous pouvez supprimer individuellement des utilisateurs de la liste de suppression en décochant la case à côté de leur nom d’utilisateur.', + 'PRUNE_USERS_LIST_DEACTIVATE' => 'En accord avec le critère sélectionné concernant le délestage des utilisateurs, les comptes suivants seront désactivés. Vous pouvez supprimer individuellement des utilisateurs de la liste de désactivation en décochant la case à côté de leur nom d’utilisateur.', + + 'SELECT_USERS_EXPLAIN' => 'Veuillez saisir ici des noms d’utilisateurs spécifiques. Ils seront utilisés de préférence en tenant compte du critère sélectionné ci-dessus. Les fondateurs ne peuvent pas être délestés.', + + 'USER_DEACTIVATE_SUCCESS' => 'Les utilisateurs ont été désactivés.', + 'USER_DELETE_SUCCESS' => 'Les utilisateurs ont été supprimés.', + 'USER_PRUNE_FAILURE' => 'Aucun utilisateur ne correspond au critère sélectionné.', + + 'WRONG_ACTIVE_JOINED_DATE' => 'La date que vous avez spécifiée est incorrecte, elle doit obligatoirement respecter le format « AAAA-MM-JJ »', +]); + +// Forum Pruning +$lang = array_merge($lang, [ + 'ACP_PRUNE_FORUMS_EXPLAIN' => 'Le délestage vous permet de supprimer tous les sujets considérés inactifs ou impopulaires du fait qu’ils n’ont pas été consultés depuis un certain nombre de jours. Si vous ne saisissez pas de nombre de jours, tous les sujets seront alors supprimés. Par défaut, les annonces, les notes et les sujets dans lesquels un sondage est en cours ne seront pas supprimés.', + + 'FORUM_PRUNE' => 'Délester le forum', + + 'NO_PRUNE' => 'Aucun forum n’a été délesté.', + + 'SELECTED_FORUM' => 'Forum sélectionné', + 'SELECTED_FORUMS' => 'Forums sélectionnés', + + 'POSTS_PRUNED' => 'Messages délestés', + 'PRUNE_ANNOUNCEMENTS' => 'Délester les annonces', + 'PRUNE_FINISHED_POLLS' => 'Délester les sondages expirés', + 'PRUNE_FINISHED_POLLS_EXPLAIN' => 'Les sujets dans lesquels les sondages sont expirés seront supprimés.', + 'PRUNE_FORUM_CONFIRM' => 'Êtes-vous sûr de vouloir délester ces forums ? Une fois que les messages et les sujets délestés auront été supprimés, ils ne pourront pas être restaurés.', + 'PRUNE_NOT_POSTED' => 'Nombre de jours depuis la dernière publication', + 'PRUNE_NOT_VIEWED' => 'Nombre de jours depuis la dernière consultation', + 'PRUNE_OLD_POLLS' => 'Délester les sondages obsolètes', + 'PRUNE_OLD_POLLS_EXPLAIN' => 'Les sujets dans lesquels les sondages en cours sont inactifs depuis un certains temps seront supprimés.', + 'PRUNE_STICKY' => 'Délester les notes', + 'PRUNE_SUCCESS' => 'Les forums ont été délestés.', + + 'TOPICS_PRUNED' => 'Sujets délestés', +]); diff --git a/language/fr/acp/search.php b/language/fr/acp/search.php new file mode 100644 index 0000000..8e0b48d --- /dev/null +++ b/language/fr/acp/search.php @@ -0,0 +1,144 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_SEARCH_INDEX_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les méthodes d’indexation de la recherche. Le moteur de recherche n’utilisant qu’une seule méthode d’indexation, vous devriez supprimer toutes les indexations inutilisées. Vous devriez également, après toute modification sur la plupart des paramètres de la recherche comme le nombre minimal et maximal de caractères, recréer l’index afin qu’il prenne en compte ces modifications.', + 'ACP_SEARCH_SETTINGS_EXPLAIN' => 'Depuis cette page, vous pouvez définir la méthode d’indexation de la recherche qui sera utilisée lors de l’indexation des messages et l’exécution des recherches. Vous pouvez paramétrer différentes options qui peuvent influencer sur le nombre d’exécutions de ces opérations. Certains de ces paramètres sont les mêmes que toutes les autres méthodes d’indexation du moteur de recherche.', + + 'COMMON_WORD_THRESHOLD' => 'Seuil de mot commun', + 'COMMON_WORD_THRESHOLD_EXPLAIN' => 'Les mots contenus dans la majorité des messages seront considérés comme communs. Les mots communs sont ignorés des recherches. Réglez cette valeur sur « 0 » afin de désactiver ce comportement. Les mots ne deviennent communs que s’ils ont été utilisés dans au moins 100 messages. Si vous souhaitez que les mots communs soient reconsidérés, vous devez recréer un index.', + 'CONFIRM_SEARCH_BACKEND' => 'Êtes-vous sûr de vouloir modifier votre méthode d’indexation pour une méthode différente ? Après la modification de la méthode d’indexation, vous devez créer un index. Si vous ne prévoyez pas de restaurer l’ancienne méthode d’indexation, vous pouvez également la supprimer afin de libérer des ressources systèmes.', + 'CONTINUE_DELETING_INDEX' => 'Continuer le processus de suppression de l’ancien index', + 'CONTINUE_DELETING_INDEX_EXPLAIN' => 'Un processus de suppression de l’ancien index a été commencé. Vous devez terminer ou annuler ce dernier afin d’accéder à la page de l’index de la recherche.', + 'CONTINUE_INDEXING' => 'Continuer l’ancien processus d’indexation', + 'CONTINUE_INDEXING_EXPLAIN' => 'Un processus d’indexation de l’ancien index a été commencé. Vous devez terminer ou annuler ce dernier afin d’accéder à la page de l’index de la recherche.', + 'CREATE_INDEX' => 'Créer un index', + + 'DELETE_INDEX' => 'Supprimer un index', + 'DELETING_INDEX_IN_PROGRESS' => 'Suppression de l’index.', + 'DELETING_INDEX_IN_PROGRESS_EXPLAIN' => 'La méthode d’indexation de la recherche est actuellement en train de vider son index. Cela peut prendre quelques minutes.', + + 'FULLTEXT_MYSQL_INCOMPATIBLE_DATABASE' => 'L’indexation plein texte de MySQL ne peut être utilisée qu’avec MySQL4 ou ses versions plus récentes.', + 'FULLTEXT_MYSQL_NOT_SUPPORTED' => 'Les indexations plein texte de MySQL ne peuvent être utilisées qu’avec les tables MyISAM ou InnoDB. MySQL 5.6.8 ou ses versions plus récentes sont obligatoires si vous souhaitez utiliser des indexations plein texte sur des tables InnoDB.', + 'FULLTEXT_MYSQL_TOTAL_POSTS' => 'Nombre total de messages indexés', + 'FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN' => 'Les mots ne contenant pas moins de caractères que ce nombre seront indexés. Seul vous et votre hébergeur internet êtes en mesure de modifier cette option par la configuration de MySQL.', + 'FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN' => 'Les mots ne contenant pas plus de caractères que ce nombre seront indexés. Seul vous et votre hébergeur internet êtes en mesure de modifier cette option par la configuration de MySQL.', + + 'FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE' => 'L’indexation plein texte de PostgreSQL ne peut être utilisée qu’avec PostgreSQL.', + 'FULLTEXT_POSTGRES_TOTAL_POSTS' => 'Nombre total de messages indexés', + 'FULLTEXT_POSTGRES_VERSION_CHECK' => 'Version de PostgreSQL :', + 'FULLTEXT_POSTGRES_TS_NAME' => 'Profil de configuration de la recherche de texte :', + 'FULLTEXT_POSTGRES_MIN_WORD_LEN' => 'Longueur minimale des mots-clés', + 'FULLTEXT_POSTGRES_MAX_WORD_LEN' => 'Longueur maximale des mots-clés', + 'FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN' => 'Cette information ne peut être affichée que sur PostgreSQL 8.3 et ses versions plus récentes.', + 'FULLTEXT_POSTGRES_TS_NAME_EXPLAIN' => 'Le profil de configuration de la recherche de texte détermine l’analyseur syntaxique et le dictionnaire.', + 'FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN' => 'Les mots contenant au moins ce nombre de caractères seront indexés.', + 'FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN' => 'Les mots ne contenant pas plus de caractères que ce nombre seront indexés.', + + 'FULLTEXT_SPHINX_CONFIGURE' => 'Configurez les paramètres suivants afin de générer le fichier de configuration de Sphinx', + 'FULLTEXT_SPHINX_DATA_PATH' => 'Chemin vers le répertoire de données', + 'FULLTEXT_SPHINX_DATA_PATH_EXPLAIN' => 'Le répertoire de données sera utilisé afin de stocker les index et les fichiers contenant les historiques. Vous devriez créer ce répertoire en prenant soin qu’il ne soit pas accessible en ligne. Veuillez vous assurer d’ajouter une barre oblique à la fin du nom du répertoire.', + 'FULLTEXT_SPHINX_DELTA_POSTS' => 'Nombre de messages dans l’index « delta » fréquemment mis à jour', + 'FULLTEXT_SPHINX_HOST' => 'Hôte du programme en attente de recherche Sphinx', + 'FULLTEXT_SPHINX_HOST_EXPLAIN' => 'Le programme en attente de recherche Sphinx (« searchd ») se connectera sur cet hôte. Si vous laissez ce champ vide, l’hôte par défaut (qui est « localhost ») sera utilisé.', + 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT' => 'Mémoire limite de l’indexeur', + 'FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN' => 'Ce nombre doit être inférieur au nombre de mémoire vive dont dispose votre serveur. Si vous rencontrez périodiquement des problèmes de performance, il est probable qu’il s’agisse de l’indexeur qui consomme trop de ressources. Baissez alors le montant de mémoire alloué à l’indexeur.', + 'FULLTEXT_SPHINX_MAIN_POSTS' => 'Nombre de messages dans l’index principal', + 'FULLTEXT_SPHINX_PORT' => 'Port du programme en attente de recherche Sphinx', + 'FULLTEXT_SPHINX_PORT_EXPLAIN' => 'Le programme en attente de recherche Sphinx (« searchd ») se connectera sur ce port. Si vous laissez ce champ vide, le port par défaut de l’interface de programmation de Sphinx (qui est 9312) sera utilisé.', + 'FULLTEXT_SPHINX_WRONG_DATABASE' => 'La recherche Sphinx pour phpBB ne fonctionne qu’avec MySQL et PostgreSQL.', + 'FULLTEXT_SPHINX_CONFIG_FILE' => 'Fichier de configuration de Sphinx', + 'FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN' => 'Le contenu du fichier de configuration de Sphinx. Ce contenu doit être retranscrit dans le fichier « sphinx.conf » qui est utilisé par le programme en attente de recherche Sphinx. Veuillez remplacer les marques substitutives « [dbuser] » et « [dbpassword] » par vos identifiants de base de données.', + 'FULLTEXT_SPHINX_NO_CONFIG_DATA' => 'Le chemin vers le répertoire de données de Sphinx n’a pas été renseigné. Veuillez spécifier et envoyer le chemin afin de générer le fichier de configuration.', + + 'GENERAL_SEARCH_SETTINGS' => 'Paramètres généraux de la recherche', + 'GO_TO_SEARCH_INDEX' => 'Aller sur la page d’index de la recherche', + + 'INDEX_STATS' => 'Statistiques de l’index', + 'INDEXING_IN_PROGRESS' => 'Indexation en cours', + 'INDEXING_IN_PROGRESS_EXPLAIN' => 'La méthode d’indexation de la recherche est actuellement en train d’indexer tous les messages du forum. Ce processus peut prendre un certain temps selon la taille de votre forum.', + + 'LIMIT_SEARCH_LOAD' => 'Limite de la charge du système de la page de recherche', + 'LIMIT_SEARCH_LOAD_EXPLAIN' => 'Si la charge du système dépasse cette valeur durant une minute, la page de recherche sera automatiquement désactivée. Une valeur de « 1.0 » correspond à 100 % de l’utilisation des processus d’un processeur. Cette fonctionnalité ne fonctionne que sur les serveurs basés sous UNIX.', + + 'MAX_SEARCH_CHARS' => 'Nombre maximal de caractères indexés dans la recherche', + 'MAX_SEARCH_CHARS_EXPLAIN' => 'Les mots contenant moins de caractères que ce nombre seront indexés.', + 'MAX_NUM_SEARCH_KEYWORDS' => 'Nombre maximal de mots clés', + 'MAX_NUM_SEARCH_KEYWORDS_EXPLAIN' => 'Le nombre maximal de mots clés que les utilisateurs seront capables de rechercher. Réglez cette valeur sur « 0 » si vous ne souhaitez pas limiter le nombre de mot clé.', + 'MIN_SEARCH_CHARS' => 'Nombre minimal de caractères indexés dans la recherche', + 'MIN_SEARCH_CHARS_EXPLAIN' => 'Les mots contenant plus de caractères que ce nombre seront indexés.', + 'MIN_SEARCH_AUTHOR_CHARS' => 'Nombre minimal de caractères des noms d’auteur', + 'MIN_SEARCH_AUTHOR_CHARS_EXPLAIN' => 'Le nombre minimal de caractères que les utilisateurs devront saisir lorsqu’ils effectuent une recherche d’auteurs en utilisant un astérisque « * » comme métacaractère passe-partout. Si les noms d’auteur sont plus court que la valeur saisie dans ce champ, les utilisateurs devront saisir les noms d’utilisateur complets.', + + 'PROGRESS_BAR' => 'Barre de progression', + + 'SEARCH_GUEST_INTERVAL' => 'Intervalle d’affluence de la recherche des invités', + 'SEARCH_GUEST_INTERVAL_EXPLAIN' => 'Le nombre de secondes qui s’écouleront avant qu’un invité puisse de nouveau effectuer une recherche. Si un invité est en train d’effectuer une recherche, tous les autres invités devront patienter le temps que cette durée soit écoulée avant de pouvoir effectuer eux-mêmes une recherche.', + 'SEARCH_INDEX_CREATE_REDIRECT' => [ + 1 => 'Tous les messages allant jusqu’au message dont l’ID est %2$d sont à présent indexés, ce qui correspond à %1$d message.
', + 2 => 'Tous les messages allant jusqu’au message dont l’ID est %2$d sont à présent indexés, ce qui correspond à %1$d messages.
', + ], + 'SEARCH_INDEX_CREATE_REDIRECT_RATE' => [ + 1 => 'Le taux d’indexation actuel est d’approximativement %1$.1f message par seconde.
Indexation en cours…', + 2 => 'Le taux d’indexation actuel est d’approximativement %1$.1f messages par seconde.
Indexation en cours…', + ], + 'SEARCH_INDEX_DELETE_REDIRECT' => [ + 1 => 'Tous les messages allant jusqu’au message dont l’ID est %2$d sont à présent supprimés de l’index de recherche, ce qui correspond à %1$d message.
', + 2 => 'Tous les messages allant jusqu’au message dont l’ID est %2$d sont à présent supprimés de l’index de recherche, ce qui correspond à %1$d messages.
', + ], + 'SEARCH_INDEX_DELETE_REDIRECT_RATE' => [ + 1 => 'Le taux de suppression actuel est d’approximativement %1$.1f message par seconde.
Suppression en cours…', + 2 => 'Le taux de suppression actuel est d’approximativement %1$.1f messages par seconde.
Suppression en cours…', + ], + 'SEARCH_INDEX_CREATED' => 'Tous les messages ont été indexés dans la base de données du forum.', + 'SEARCH_INDEX_REMOVED' => 'L’index de recherche a été supprimé de cette méthode d’indexation.', + 'SEARCH_INTERVAL' => 'Intervalle d’affluence de la recherche des utilisateurs', + 'SEARCH_INTERVAL_EXPLAIN' => 'Le nombre de secondes qui s’écouleront avant qu’un utilisateur puisse de nouveau effectuer une recherche. Cet intervalle est vérifié indépendamment pour chaque utilisateur.', + 'SEARCH_STORE_RESULTS' => 'Durée des résultats de recherche mis en cache', + 'SEARCH_STORE_RESULTS_EXPLAIN' => 'Le nombre de secondes qui s’écouleront avant que les résultats de recherche mis en cache soient supprimés. Réglez cette valeur sur « 0 » si vous souhaitez pas mettre en cache les résultats de recherche.', + 'SEARCH_TYPE' => 'Méthode d’indexation de la recherche', + 'SEARCH_TYPE_EXPLAIN' => 'Le logiciel phpBB vous permet de choisir la méthode d’indexation qui sera utilisée lors des recherches de texte effectuées dans le contenu des messages. Par défaut, la recherche utilisera la méthode « phpBB Native Fulltext ».', + 'SWITCHED_SEARCH_BACKEND' => 'La méthode d’indexation de la recherche a été modifiée. Vous devriez à présent recréer un index.', + + 'TOTAL_WORDS' => 'Nombre total de mots indexés', + 'TOTAL_MATCHES' => 'Nombre total de mots indexés en relation avec les sujets', + + 'YES_SEARCH' => 'Activer la recherche', + 'YES_SEARCH_EXPLAIN' => 'Les utilisateurs pourront effectuer des recherches sur votre forum, dont la recherche de membres.', + 'YES_SEARCH_UPDATE' => 'Activer la mise à jour plein texte', + 'YES_SEARCH_UPDATE_EXPLAIN' => 'Les index plein texte seront mis à jour au moment des publications. Cette option ne sera pas prise en compte si la recherche n’est pas activée.', +]); diff --git a/language/fr/acp/styles.php b/language/fr/acp/styles.php new file mode 100644 index 0000000..b36ef7a --- /dev/null +++ b/language/fr/acp/styles.php @@ -0,0 +1,89 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACP_STYLES_EXPLAIN' => 'Depuis cette page, vous pouvez gérer les styles disponibles sur votre forum. Vous pouvez installer de nouveaux styles et modifier, supprimer, désactiver et réactiver les styles déjà installés. Vous pouvez également voir à quoi ressemble un style en utilisant la fonctionnalité de prévisualisation. De même, vous pouvez consulter le compteur du nombre total d’utilisateurs de chaque style. Veuillez cependant noter que lorsque vous remplacez un style, la comptabilisation du nombre total d’utilisateurs ne sera pas répercutée sur le style qui le remplacera.', + + 'CANNOT_BE_INSTALLED' => 'Ne peut pas être installé', + 'CONFIRM_UNINSTALL_STYLES' => 'Êtes-vous sûr de vouloir désinstaller ces styles ?', + 'COPYRIGHT' => 'Copyright', + + 'DEACTIVATE_DEFAULT' => 'Vous ne pouvez pas désactiver le style par défaut.', + 'DELETE_FROM_FS' => 'Supprimer du système de fichiers', + 'DELETE_STYLE_FILES_FAILED' => 'Une erreur est survenue lors de la suppression des fichiers du style « %s ».', + 'DELETE_STYLE_FILES_SUCCESS' => 'Les fichiers du style « %s » ont été supprimés.', + 'DETAILS' => 'Informations', + + 'INHERITING_FROM' => 'Hérite de', + 'INSTALL_STYLE' => 'Installer un style', + 'INSTALL_STYLES' => 'Installer des styles', + 'INSTALL_STYLES_EXPLAIN' => 'Depuis cette page, vous pouvez installer de nouveaux styles.
Si vous ne retrouvez pas dans la liste ci-dessous le style que vous avez transféré, assurez-vous que ce dernier ne soit pas déjà installé sur votre forum. Si ce n’est pas le cas, essayez de le transférer de nouveau.', + 'INVALID_STYLE_ID' => 'L’identifiant du style est invalide.', + + 'NO_MATCHING_STYLES_FOUND' => 'Aucun style ne correspond à votre requête.', + 'NO_UNINSTALLED_STYLE' => 'Aucun style n’a été désinstallé.', + + 'PURGED_CACHE' => 'Le cache a été vidé.', + + 'REQUIRES_STYLE' => 'Ce style ne fonctionne que si le style « %s » est également installé.', + + 'STYLE_ACTIVATE' => 'Activer', + 'STYLE_ACTIVE' => 'Actif', + 'STYLE_DEACTIVATE' => 'Désactiver', + 'STYLE_DEFAULT' => 'En faire le style par défaut', + 'STYLE_DEFAULT_CHANGE_INACTIVE' => 'Vous devez activer le style avant de pouvoir en faire le style par défaut.', + 'STYLE_ERR_INVALID_PARENT' => 'Le style parent est invalide.', + 'STYLE_ERR_NAME_EXIST' => 'Un style portant ce nom existe déjà.', + 'STYLE_ERR_STYLE_NAME' => 'Vous devez saisir le nom de ce style.', + 'STYLE_INSTALLED' => 'Le style « %s » a été installé.', + 'STYLE_INSTALLED_RETURN_INSTALLED_STYLES' => 'Revenir à la liste des styles installés', + 'STYLE_INSTALLED_RETURN_UNINSTALLED_STYLES' => 'Installer de nouveaux styles', + 'STYLE_NAME' => 'Nom du style', + 'STYLE_NAME_RESERVED' => 'Le style « %s » ne peut pas être installé car le nom est réservé.', + 'STYLE_NOT_INSTALLED' => 'Le style « %s » n’a pas été installé.', + 'STYLE_PATH' => 'Chemin du style', + 'STYLE_UNINSTALL' => 'Désinstaller', + 'STYLE_UNINSTALL_DEPENDENT' => 'Le style « %s » ne peut pas être désinstallé car un ou plusieurs styles sont dépendants de ce dernier.', + 'STYLE_UNINSTALLED' => 'Le style « %s » a été désinstallé.', + 'STYLE_PHPBB_VERSION' => 'Version de phpBB', + 'STYLE_USED_BY' => 'Utilisé par (incluant les robots)', + 'STYLE_VERSION' => 'Version du style', + + 'UNINSTALL_DEFAULT' => 'Vous ne pouvez pas désinstaller le style par défaut.', + + 'BROWSE_STYLES_DATABASE' => 'Parcourir la base de données des styles', +]); diff --git a/language/fr/acp/users.php b/language/fr/acp/users.php new file mode 100644 index 0000000..2b78637 --- /dev/null +++ b/language/fr/acp/users.php @@ -0,0 +1,142 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ADMIN_SIG_PREVIEW' => 'Prévisualiser la signature', + 'AT_LEAST_ONE_FOUNDER' => 'Vous ne pouvez pas modifier ce fondateur en utilisateur ordinaire. Il est nécessaire d’avoir au moins un fondateur sur le forum. Si vous souhaitez modifier le statut de ce fondateur, vous devez tout d’abord promouvoir un autre utilisateur en fondateur.', + + 'BAN_ALREADY_ENTERED' => 'Le bannissement a déjà été effectué. La liste des bannissements n’a pas été mise à jour.', + 'BAN_SUCCESSFUL' => 'Le bannissement a été effectué.', + + 'CANNOT_BAN_ANONYMOUS' => 'Vous ne pouvez pas bannir les comptes d’anonymes. Les permissions agissant sur les utilisateurs anonymes peuvent être définies sous l’onglet « Permissions ».', + 'CANNOT_BAN_FOUNDER' => 'Vous ne pouvez pas bannir les comptes des fondateurs.', + 'CANNOT_BAN_YOURSELF' => 'Vous ne pouvez pas bannir votre propre compte.', + 'CANNOT_DEACTIVATE_BOT' => 'Vous ne pouvez pas désactiver les comptes des robots. Veuillez plutôt désactiver le robot à partir de la page des robots.', + 'CANNOT_DEACTIVATE_FOUNDER' => 'Vous ne pouvez pas désactiver les comptes des fondateurs.', + 'CANNOT_DEACTIVATE_YOURSELF' => 'Vous ne pouvez pas désactiver votre propre compte.', + 'CANNOT_FORCE_REACT_BOT' => 'Vous ne pouvez pas forcer la réactivation des comptes des robots. Veuillez plutôt réactiver le robot à partir de la page des robots.', + 'CANNOT_FORCE_REACT_FOUNDER' => 'Vous ne pouvez pas forcer la réactivation des comptes des fondateurs.', + 'CANNOT_FORCE_REACT_YOURSELF' => 'Vous ne pouvez pas forcer la réactivation de votre propre compte.', + 'CANNOT_REMOVE_ANONYMOUS' => 'Vous ne pouvez pas supprimer le compte d’un invité.', + 'CANNOT_REMOVE_FOUNDER' => 'Vous ne pouvez pas supprimer les comptes des fondateurs.', + 'CANNOT_REMOVE_YOURSELF' => 'Vous ne pouvez pas supprimer votre propre compte.', + 'CANNOT_SET_FOUNDER_IGNORED' => 'Vous ne pouvez pas promouvoir des utilisateurs ignorés en fondateurs.', + 'CANNOT_SET_FOUNDER_INACTIVE' => 'Vous devez activer les utilisateurs avant de les promouvoir en fondateurs. Seuls des utilisateurs activés peuvent être promus.', + 'CONFIRM_EMAIL_EXPLAIN' => 'Vous n’avez besoin de confirmer que si vous modifiez les adresses de courriel des utilisateurs.', + + 'DELETE_POSTS' => 'Supprimer les messages', + 'DELETE_USER' => 'Supprimer l’utilisateur', + 'DELETE_USER_EXPLAIN' => 'Veuillez noter que la suppression d’un utilisateur est irréversible, sa restauration est impossible. Les messages non lus envoyés par cet utilisateur seront supprimés et les destinataires ne pourront pas les consulter.', + + 'FORCE_REACTIVATION_SUCCESS' => 'La réactivation a été forcée.', + 'FOUNDER' => 'Fondateur', + 'FOUNDER_EXPLAIN' => 'Les fondateurs détiennent toutes les permissions des administrateurs et ne peuvent pas être bannis, supprimés ou modifiés par des membres qui ne sont pas eux-mêmes des fondateurs.', + + 'GROUP_APPROVE' => 'Approuver le membre', + 'GROUP_DEFAULT' => 'Définir comme le groupe par défaut du membre', + 'GROUP_DELETE' => 'Supprimer le membre du groupe', + 'GROUP_DEMOTE' => 'Rétrograder le responsable du groupe', + 'GROUP_PROMOTE' => 'Promouvoir en responsable du groupe', + + 'IP_WHOIS_FOR' => 'À qui appartient l’IP pour « %s »', + + 'LAST_ACTIVE' => 'Dernière visite', + + 'MOVE_POSTS_EXPLAIN' => 'Veuillez sélectionner le forum dans lequel vous souhaitez déplacer tous les messages de cet utilisateur.', + + 'NO_SPECIAL_RANK' => 'Aucun rang spécial n’a été spécifié', + 'NO_WARNINGS' => 'Aucun avertissement.', + 'NOT_MANAGE_FOUNDER' => 'Vous avez essayé de modifier un utilisateur qui détient le statut de fondateur. Seuls les fondateurs sont autorisés à gérer les autres fondateurs.', + + 'QUICK_TOOLS' => 'Actions rapides', + + 'REGISTERED' => 'Inscription', + 'REGISTERED_IP' => 'Adresse IP lors de l’inscription', + 'RETAIN_POSTS' => 'Conserver ses messages', + + 'SELECT_FORM' => 'Sélectionner un formulaire', + 'SELECT_USER' => 'Sélectionner un utilisateur', + + 'USER_ADMIN' => 'Administration de l’utilisateur', + 'USER_ADMIN_ACTIVATE' => 'Activer le compte', + 'USER_ADMIN_ACTIVATED' => 'L’utilisateur a été activé.', + 'USER_ADMIN_AVATAR_REMOVED' => 'L’avatar associé au compte de l’utilisateur a été supprimé.', + 'USER_ADMIN_BAN_EMAIL' => 'Bannir par adresse de courriel', + 'USER_ADMIN_BAN_EMAIL_REASON' => 'L’adresse de courriel a été bannie depuis la gestion des utilisateurs', + 'USER_ADMIN_BAN_IP' => 'Bannir par adresse IP', + 'USER_ADMIN_BAN_IP_REASON' => 'L’adresse IP a été bannie depuis la gestion des utilisateurs', + 'USER_ADMIN_BAN_NAME_REASON' => 'Le nom d’utilisateur a été banni depuis la gestion des utilisateurs', + 'USER_ADMIN_BAN_USER' => 'Bannir par nom d’utilisateur', + 'USER_ADMIN_DEACTIVATE' => 'Désactiver le compte', + 'USER_ADMIN_DEACTIVED' => 'L’utilisateur a été désactivé.', + 'USER_ADMIN_DEL_ATTACH' => 'Supprimer toutes les pièces jointes', + 'USER_ADMIN_DEL_AVATAR' => 'Supprimer l’avatar', + 'USER_ADMIN_DEL_OUTBOX' => 'Vider la boîte des messages privés envoyés', + 'USER_ADMIN_DEL_POSTS' => 'Supprimer tous les messages', + 'USER_ADMIN_DEL_SIG' => 'Supprimer la signature', + 'USER_ADMIN_EXPLAIN' => 'Depuis cette page, vous pouvez modifier les informations et les données de vos utilisateurs et gérer certaines de leurs fonctionnalités.', + 'USER_ADMIN_FORCE' => 'Forcer la réactivation', + 'USER_ADMIN_LEAVE_NR' => 'Supprimer des nouvellement inscrits', + 'USER_ADMIN_MOVE_POSTS' => 'Déplacer tous les messages', + 'USER_ADMIN_SIG_REMOVED' => 'La signature associée au compte de l’utilisateur a été supprimée.', + 'USER_ATTACHMENTS_REMOVED' => 'Toutes les pièces jointes insérées par cet utilisateur ont été supprimées.', + 'USER_AVATAR_NOT_ALLOWED' => 'L’avatar ne peut pas être affiché car les avatars ne sont pas autorisés.', + 'USER_AVATAR_UPDATED' => 'Les informations liées aux avatars de l’utilisateur ont été mises à jour.', + 'USER_AVATAR_TYPE_NOT_ALLOWED' => 'L’avatar actuel ne peut pas être affiché car son type n’est pas autorisé.', + 'USER_CUSTOM_PROFILE_FIELDS' => 'Champs de profil personnalisés', + 'USER_DELETED' => 'L’utilisateur a été supprimé.', + 'USER_GROUP_ADD' => 'Ajouter l’utilisateur au groupe', + 'USER_GROUP_NORMAL' => 'L’utilisateur est membre des groupes normaux', + 'USER_GROUP_PENDING' => 'L’utilisateur est en attente d’adhésion aux groupes', + 'USER_GROUP_SPECIAL' => 'L’utilisateur est membre des groupes prédéfinis', + 'USER_LIFTED_NR' => 'Le statut d’utilisateur nouvellement inscrit a été supprimé.', + 'USER_NO_ATTACHMENTS' => 'Aucune pièce jointe.', + 'USER_NO_POSTS_TO_DELETE' => 'L’utilisateur n’a aucun message à conserver ou à supprimer.', + 'USER_OUTBOX_EMPTIED' => 'La boîte des messages privés envoyés par l’utilisateur a été vidée.', + 'USER_OUTBOX_EMPTY' => 'La boîte des messages privés envoyés par l’utilisateur est déjà vide.', + 'USER_OVERVIEW_UPDATED' => 'Les informations de l’utilisateur ont été mises à jour.', + 'USER_POSTS_DELETED' => 'Tous les messages publiés par cet utilisateur ont été supprimés.', + 'USER_POSTS_MOVED' => 'Les messages des utilisateurs ont été déplacés dans le forum que vous avez spécifié.', + 'USER_PREFS_UPDATED' => 'Les préférences de l’utilisateur ont été mises à jour.', + 'USER_PROFILE' => 'Profil de l’utilisateur', + 'USER_PROFILE_UPDATED' => 'Le profil de l’utilisateur a été mis à jour.', + 'USER_RANK' => 'Rang de l’utilisateur', + 'USER_RANK_UPDATED' => 'Le rang de l’utilisateur a été mis à jour.', + 'USER_SIG_UPDATED' => 'La signature de l’utilisateur a été mise à jour.', + 'USER_WARNING_LOG_DELETED' => 'Aucune information n’est disponible. L’entrée a été probablement supprimée de l’historique.', + 'USER_TOOLS' => 'Outils basiques', +]); diff --git a/language/fr/app.php b/language/fr/app.php new file mode 100644 index 0000000..5c9444c --- /dev/null +++ b/language/fr/app.php @@ -0,0 +1,43 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'CONTROLLER_ARGUMENT_VALUE_MISSING' => 'Valeur manquante concernant l’argument #%1$s : « %3$s » dans la classe « %2$s »', + 'CONTROLLER_NOT_SPECIFIED' => 'Aucun contrôleur n’a été spécifié.', + 'CONTROLLER_METHOD_NOT_SPECIFIED' => 'Aucune méthode concernant le contrôleur n’a été spécifiée.', + 'CONTROLLER_SERVICE_UNDEFINED' => 'Le service du contrôleur « %s » n’est pas défini dans « ./config/services.yml ».', +]); diff --git a/language/fr/captcha_qa.php b/language/fr/captcha_qa.php new file mode 100644 index 0000000..1a657a8 --- /dev/null +++ b/language/fr/captcha_qa.php @@ -0,0 +1,63 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'CAPTCHA_QA' => 'Questions-Réponses', + 'CONFIRM_QUESTION_EXPLAIN' => 'Cette question vous permet de vous prémunir contre les soumissions automatisées et intensives effectuées par des robots malveillants.', + 'CONFIRM_QUESTION_WRONG' => 'Vous n’avez pas répondu correctement à la question.', + 'CONFIRM_QUESTION_MISSING' => 'Les questions relatives au CAPTCHA sont introuvables. Veuillez contacter un administrateur du forum.', + + 'QUESTION_ANSWERS' => 'Réponses', + 'ANSWERS_EXPLAIN' => 'Veuillez répondre correctement à la question. Chaque réponse doit être saisie sur une nouvelle ligne.', + 'CONFIRM_QUESTION' => 'Question', + + 'ANSWER' => 'Réponse', + 'EDIT_QUESTION' => 'Modifier la question', + 'QUESTIONS' => 'Questions', + 'QUESTIONS_EXPLAIN' => 'Lors de chaque soumission de formulaires où le module d’extension des questions-réponses est activé, les utilisateurs seront invités à répondre à une des questions spécifiées ici. Pour utiliser ce module d’extension, au moins une des questions devra être rédigée dans la langue par défaut. Il est recommandé de cibler ces questions selon votre audience, qui devrait être capable de répondre plus facilement que des robots malveillants capables d’exécuter des requêtes sur les moteurs de recherche. Seule une unique question bien formulée est nécessaire. Si votre forum commence à recevoir des soumissions automatisées et intensives effectuées par des robots malveillants, vous devriez remplacer votre question par une nouvelle plus complexe. Activez la vérification stricte si une des réponses à votre question contient des majuscules, des minuscules, des signes de ponctuation ou des espaces.', + 'QUESTION_DELETED' => 'Question supprimée', + 'QUESTION_LANG' => 'Langue', + 'QUESTION_LANG_EXPLAIN' => 'La langue dans laquelle cette question et ses réponses sont rédigées.', + 'QUESTION_STRICT' => 'Vérification stricte', + 'QUESTION_STRICT_EXPLAIN' => 'Activez cette option afin de prendre en compte la sensibilité à la casse, les signes de ponctuation et les espaces.', + + 'QUESTION_TEXT' => 'Question', + 'QUESTION_TEXT_EXPLAIN' => 'La question qui sera affichée aux utilisateurs.', + + 'QA_ERROR_MSG' => 'Veuillez renseigner tous les champs et saisir au moins une réponse.', + 'QA_LAST_QUESTION' => 'Vous ne pouvez pas supprimer toutes les questions lorsque le module d’extension est actif.', +]); diff --git a/language/fr/captcha_recaptcha.php b/language/fr/captcha_recaptcha.php new file mode 100644 index 0000000..befb452 --- /dev/null +++ b/language/fr/captcha_recaptcha.php @@ -0,0 +1,51 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'RECAPTCHA_LANG' => 'fr', // Find the language/country code on https://developers.google.com/recaptcha/docs/language - If no code exists for your language you can use "en" or leave the string empty + 'RECAPTCHA_NOT_AVAILABLE' => 'Vous devez créer un compte sur le site de reCAPTCHA (en anglais) afin de pouvoir utiliser reCAPTCHA.', + 'CAPTCHA_RECAPTCHA' => 'reCAPTCHA', + 'RECAPTCHA_INCORRECT' => 'La vérification a échoué', + 'RECAPTCHA_NOSCRIPT' => 'Vous devez activer JavaScript dans votre navigateur afin de charger le formulaire.', + + 'RECAPTCHA_PUBLIC' => 'Clé publique de reCAPTCHA', + 'RECAPTCHA_PUBLIC_EXPLAIN' => 'Votre clé publique de reCAPTCHA. Les clés sont disponibles sur le site de reCAPTCHA (en anglais).', + 'RECAPTCHA_PRIVATE' => 'Clé privée de reCAPTCHA', + 'RECAPTCHA_PRIVATE_EXPLAIN' => 'Votre clé privée de reCAPTCHA. Les clés sont disponibles sur le site de reCAPTCHA (en anglais).', + + 'RECAPTCHA_EXPLAIN' => 'Veuillez compléter la vérification suivante afin de nous permettre de nous prémunir contre les soumissions automatisées et intensives effectuées par des robots malveillants.', +]); diff --git a/language/fr/cli.php b/language/fr/cli.php new file mode 100644 index 0000000..234d4c3 --- /dev/null +++ b/language/fr/cli.php @@ -0,0 +1,179 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'CLI_CONFIG_CANNOT_CACHED' => 'Modifiez ce paramètre si l’option de configuration change trop fréquemment. Elle pourra alors être plus efficacement mise en cache.', + 'CLI_CONFIG_CURRENT' => 'La valeur de configuration actuelle. Utilisez « 0 » et « 1 » afin de spécifier des valeurs booléennes', + 'CLI_CONFIG_DELETE_SUCCESS' => 'La configuration « %s » a été supprimée.', + 'CLI_CONFIG_NEW' => 'La nouvelle valeur de configuration. Utilisez « 0 » et « 1 » afin de spécifier des valeurs booléennes', + 'CLI_CONFIG_NOT_EXISTS' => 'La configuration « %s » est introuvable', + 'CLI_CONFIG_OPTION_NAME' => 'Le nom de l’option de configuration', + 'CLI_CONFIG_PRINT_WITHOUT_NEWLINE' => 'Modifiez cette option si la valeur doit être affichée sans effectuer de retour à la ligne.', + 'CLI_CONFIG_INCREMENT_BY' => 'Valeur de l’incrément', + 'CLI_CONFIG_INCREMENT_SUCCESS' => 'La valeur de la configuration « %s » a été incrémentée', + 'CLI_CONFIG_SET_FAILURE' => 'Impossible de paramétrer la configuration « %s »', + 'CLI_CONFIG_SET_SUCCESS' => 'La configuration « %s » a été paramétrée', + + 'CLI_DESCRIPTION_CRON_LIST' => 'Affiche une liste de tâches cron prêtes et non prêtes.', + 'CLI_DESCRIPTION_CRON_RUN' => 'Exécute toutes les tâches cron prêtes.', + 'CLI_DESCRIPTION_CRON_RUN_ARGUMENT_1' => 'Nom de la tâche à exécuter', + 'CLI_DESCRIPTION_DB_LIST' => 'Liste toutes les migrations installées et disponibles.', + 'CLI_DESCRIPTION_DB_MIGRATE' => 'Met à jour la base de données en appliquant les migrations.', + 'CLI_DESCRIPTION_DB_REVERT' => 'Restaurer une migration.', + 'CLI_DESCRIPTION_DELETE_CONFIG' => 'Supprime une option de configuration', + 'CLI_DESCRIPTION_DISABLE_EXTENSION' => 'Désactive l’extension spécifiée.', + 'CLI_DESCRIPTION_ENABLE_EXTENSION' => 'Active l’extension spécifiée.', + 'CLI_DESCRIPTION_FIND_MIGRATIONS' => 'Détecte les migrations indépendantes.', + 'CLI_DESCRIPTION_FIX_LEFT_RIGHT_IDS' => 'Répare l’arborescence des forums et des modules.', + 'CLI_DESCRIPTION_GET_CONFIG' => 'Obtient une valeur de l’option de configuration', + 'CLI_DESCRIPTION_INCREMENT_CONFIG' => 'Incrémente une valeur entière de l’option de configuration', + 'CLI_DESCRIPTION_LIST_EXTENSIONS' => 'Liste toutes les extensions présentes dans la base de données et le système de fichiers.', + + 'CLI_DESCRIPTION_OPTION_ENV' => 'Le nom de l’environnement.', + 'CLI_DESCRIPTION_OPTION_SAFE_MODE' => 'Exécuter en mode sans échec (sans extension).', + 'CLI_DESCRIPTION_OPTION_SHELL' => 'Lancer la console.', + + 'CLI_DESCRIPTION_PURGE_EXTENSION' => 'Purge l’extension spécifiée.', + + 'CLI_DESCRIPTION_REPARSER_LIST' => 'Liste les types de textes qui peuvent être réanalysés.', + 'CLI_DESCRIPTION_REPARSER_AVAILABLE' => 'Réanalyseurs syntaxiques disponibles :', + 'CLI_DESCRIPTION_REPARSER_REPARSE' => 'Réanalyse le texte stocké avec les services « text_formatter » actuels.', + 'CLI_DESCRIPTION_REPARSER_REPARSE_ARG_1' => 'Le type de texte à réanalyser. Laissez ce champ vide afin de tout réanalyser.', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_DRY_RUN' => 'Ne sauvegarder aucune modification et afficher uniquement ce qui se passerait', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MIN' => 'Plus faible identifiant d’enregistrement à traiter', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MAX' => 'Plus important identifiant d’enregistrement à traiter', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_SIZE' => 'Nombre approximatif d’enregistrements à traiter à la fois', + 'CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RESUME' => 'Démarrer la réanalyse syntaxique où la dernière exécution s’est arrêtée', + + 'CLI_DESCRIPTION_RECALCULATE_EMAIL_HASH' => 'Recalcule la colonne « user_email_hash » de la table des utilisateurs.', + + 'CLI_DESCRIPTION_SET_ATOMIC_CONFIG' => 'Configure une valeur de l’option de configuration uniquement lorsque l’ancienne valeur est identique à la valeur actuelle', + 'CLI_DESCRIPTION_SET_CONFIG' => 'Configure une valeur de l’option de configuration', + + 'CLI_DESCRIPTION_THUMBNAIL_DELETE' => 'Supprimer toutes les vignettes existantes.', + 'CLI_DESCRIPTION_THUMBNAIL_GENERATE' => 'Générer toutes les vignettes manquantes.', + 'CLI_DESCRIPTION_THUMBNAIL_RECREATE' => 'Recréer toutes les vignettes.', + + 'CLI_DESCRIPTION_UPDATE_CHECK' => 'Vérifier si le forum est à jour.', + 'CLI_DESCRIPTION_UPDATE_CHECK_ARGUMENT_1' => 'Nom de l’extension à vérifier (si « all » est sélectionné, toutes les extensions seront vérifiées)', + 'CLI_DESCRIPTION_UPDATE_CHECK_OPTION_CACHE' => 'Exécuter la commande en ne vérifiant que les versions mises en cache.', + 'CLI_DESCRIPTION_UPDATE_CHECK_OPTION_STABILITY' => 'Exécuter la commande en ne vérifiant que les versions stables ou instables.', + + 'CLI_DESCRIPTION_UPDATE_HASH_BCRYPT' => 'Met à jour les hachages des mots de passe obsolètes afin de les encoder avec « bcrypt ».', + + 'CLI_ERROR_INVALID_STABILITY' => '« %s » doit être paramétré sur « stable » ou « instable ».', + + 'CLI_DESCRIPTION_USER_ACTIVATE' => 'Activer (ou désactiver) le compte d’un utilisateur.', + 'CLI_DESCRIPTION_USER_ACTIVATE_USERNAME' => 'Nom d’utilisateur du compte à activer.', + 'CLI_DESCRIPTION_USER_ACTIVATE_DEACTIVATE' => 'Désactiver le compte de l’utilisateur', + 'CLI_DESCRIPTION_USER_ACTIVATE_ACTIVE' => 'L’utilisateur est déjà actif.', + 'CLI_DESCRIPTION_USER_ACTIVATE_INACTIVE' => 'L’utilisateur est déjà inactif.', + 'CLI_DESCRIPTION_USER_ADD' => 'Ajouter un nouvel utilisateur.', + 'CLI_DESCRIPTION_USER_ADD_OPTION_USERNAME' => 'Nom d’utilisateur du nouvel utilisateur', + 'CLI_DESCRIPTION_USER_ADD_OPTION_PASSWORD' => 'Mot de passe du nouvel utilisateur', + 'CLI_DESCRIPTION_USER_ADD_OPTION_EMAIL' => 'Adresse de courriel du nouvel utilisateur', + 'CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY' => 'Envoyer le courriel d’activation du compte au nouvel utilisateur (non envoyé par défaut)', + 'CLI_DESCRIPTION_USER_DELETE' => 'Supprimer le compte d’un utilisateur.', + 'CLI_DESCRIPTION_USER_DELETE_USERNAME' => 'Nom d’utilisateur du compte à supprimer', + 'CLI_DESCRIPTION_USER_DELETE_OPTION_POSTS' => 'Supprimer tous les messages de cet utilisateur. Les messages de l’utilisateur seront conservés si cette option n’est pas utilisée.', + 'CLI_DESCRIPTION_USER_RECLEAN' => 'Renettoyer les noms d’utilisateurs.', + + 'CLI_EXTENSION_DISABLE_FAILURE' => 'Impossible de désactiver l’extension « %s »', + 'CLI_EXTENSION_DISABLE_SUCCESS' => 'L’extension « %s » a été désactivée', + 'CLI_EXTENSION_DISABLED' => 'L’extension « %s » n’est pas activée', + 'CLI_EXTENSION_ENABLE_FAILURE' => 'Impossible d’activer l’extension « %s »', + 'CLI_EXTENSION_ENABLE_SUCCESS' => 'L’extension « %s » a été activée', + 'CLI_EXTENSION_ENABLED' => 'L’extension « %s » est déjà activée', + 'CLI_EXTENSION_NOT_EXIST' => 'L’extension « %s » n’existe pas', + 'CLI_EXTENSION_NAME' => 'Nom de l’extension', + 'CLI_EXTENSION_PURGE_FAILURE' => 'Impossible de purger l’extension « %s »', + 'CLI_EXTENSION_PURGE_SUCCESS' => 'L’extension « %s » a été purgée', + 'CLI_EXTENSION_UPDATE_FAILURE' => 'Impossible de mettre à jour l’extension « %s »', + 'CLI_EXTENSION_UPDATE_SUCCESS' => 'L’extension « %s » a été mise à jour', + 'CLI_EXTENSION_NOT_FOUND' => 'Aucune extension.', + 'CLI_EXTENSION_NOT_ENABLEABLE' => 'L’extension « %s » n’est pas activable.', + 'CLI_EXTENSIONS_AVAILABLE' => 'Disponible', + 'CLI_EXTENSIONS_DISABLED' => 'Désactivée', + 'CLI_EXTENSIONS_ENABLED' => 'Activée', + + 'CLI_FIXUP_FIX_LEFT_RIGHT_IDS_SUCCESS' => 'L’arborescence des forums et des modules a été réparée.', + 'CLI_FIXUP_RECALCULATE_EMAIL_HASH_SUCCESS' => 'Tous les hachages des courriels ont été recalculés.', + 'CLI_FIXUP_UPDATE_HASH_BCRYPT_SUCCESS' => 'Les hachages des mots de passe obsolètes ont été encodés avec « bcrypt ».', + + 'CLI_MIGRATION_NAME' => 'Le nom de la migration, espaces de noms inclus (veuillez utiliser des barres obliques à la place de barres obliques inversées).', + 'CLI_MIGRATIONS_AVAILABLE' => 'Migrations disponibles', + 'CLI_MIGRATIONS_INSTALLED' => 'Migrations installées', + 'CLI_MIGRATIONS_ONLY_AVAILABLE' => 'N’afficher que les migrations disponibles', + 'CLI_MIGRATIONS_EMPTY' => 'Aucune migration.', + + 'CLI_REPARSER_REPARSE_REPARSING' => 'Réanalyse de « %1$s » (rangée %2$d sur %3$d)', + 'CLI_REPARSER_REPARSE_REPARSING_START' => 'Réanalyse de « %s »…', + 'CLI_REPARSER_REPARSE_SUCCESS' => 'Réanalyse terminée', + + // In all the case %1$s is the logical name of the file and %2$s the real name on the filesystem + // eg: big_image.png (2_a51529ae7932008cf8454a95af84cacd) generated. + 'CLI_THUMBNAIL_DELETED' => '%1$s (%2$s) a été supprimée.', + 'CLI_THUMBNAIL_DELETING' => 'Suppression des vignettes', + 'CLI_THUMBNAIL_SKIPPED' => '%1$s (%2$s) a été ignorée.', + 'CLI_THUMBNAIL_GENERATED' => '%1$s (%2$s) a été générée.', + 'CLI_THUMBNAIL_GENERATING' => 'Génération des vignettes', + 'CLI_THUMBNAIL_GENERATING_DONE' => 'Toutes les vignettes ont été générées.', + 'CLI_THUMBNAIL_DELETING_DONE' => 'Toutes les vignettes ont été supprimées.', + + 'CLI_THUMBNAIL_NOTHING_TO_GENERATE' => 'Aucune vignette à générer.', + 'CLI_THUMBNAIL_NOTHING_TO_DELETE' => 'Aucune vignette à supprimer.', + + 'CLI_USER_ADD_SUCCESS' => 'L’utilisateur « %s » a été ajouté.', + 'CLI_USER_DELETE_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer « %s » ? Saisissez « y » pour confirmer ou « N » pour annuler.', + 'CLI_USER_RECLEAN_START' => 'Renettoyage des noms d’utilisateurs', + 'CLI_USER_RECLEAN_DONE' => [ + 0 => 'Renettoyage terminé. Aucun nom d’utilisateur n’a nécessité d’être nettoyé.', + 1 => 'Renettoyage terminé. %d nom d’utilisateur a été nettoyé.', + 2 => 'Renettoyage terminé. %d noms d’utilisateurs ont été nettoyés.', + ], +]); + +// Additional help for commands. +$lang = array_merge($lang, [ + 'CLI_HELP_CRON_RUN' => $lang['CLI_DESCRIPTION_CRON_RUN'].'Vous pouvez éventuellement spécifier un nom de tâche cron afin d’exécuter uniquement cette tâche.', + 'CLI_HELP_USER_ACTIVATE' => 'L’option « --deactivate » vous permet d’activer ou de désactiver le compte d’un utilisateur. +Si vous souhaitez envoyer un courriel d’activation à l’utilisateur, veuillez utiliser l’option « --send-email ».', + 'CLI_HELP_USER_ADD' => 'La commande « %command.name% » vous permet d’ajouter un nouvel utilisateur : +Si cette commande est exécutée sans option, vous serez invité à les saisir. +Si vous souhaitez envoyer un courriel au nouvel utilisateur, veuillez utiliser l’option « --send-email ».', + 'CLI_HELP_USER_RECLEAN' => 'Renettoyer les noms d’utilisateurs vous permet de vérifier tous les noms d’utilisateurs en vous assurant que les versions propres soient également stockées. Les noms d’utilisateurs propres ne sont pas sensibles à la casse, sont normalisés avec NFC et sont transformés en ASCII.', +]); diff --git a/language/fr/common.php b/language/fr/common.php new file mode 100644 index 0000000..3dfff7e --- /dev/null +++ b/language/fr/common.php @@ -0,0 +1,1439 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'TRANSLATION_INFO' => 'Traduction française officielle © Miles Cellar', + 'DIRECTION' => 'ltr', + 'DATE_FORMAT' => '|d F Y|', // 01 January 2007 (with Relative days enabled) + 'DATETIME_FORMAT' => '|d F Y, H:i|', // 01 January 2007, 13:37 (with Relative days enabled) + 'USER_LANG' => 'fr', + + // You can define different rules for the determination of plural forms here. + // See https://area51.phpbb.com/docs/dev/32x/language/plurals.html for more information + // or ask the translation manager for help. + 'PLURAL_RULE' => 2, + + '1_DAY' => '1 jour', + '1_MONTH' => '1 mois', + '1_YEAR' => '1 an', + '2_WEEKS' => '2 semaines', + '3_MONTHS' => '3 mois', + '6_MONTHS' => '6 mois', + '7_DAYS' => '7 jours', + + 'ACCOUNT_ALREADY_ACTIVATED' => 'Votre compte a déjà été activé.', + 'ACCOUNT_DEACTIVATED' => 'Votre compte a été désactivé manuellement et ne peut être réactivé que par un administrateur.', + 'ACP' => 'Panneau de contrôle d’administration', + 'ACP_SHORT' => 'PCA', + 'ACTIVE' => 'actif', + 'ACTIVE_ERROR' => 'Le nom d’utilisateur que vous avez spécifié est actuellement inactif. Si vous rencontrez des difficultés lors de l’activation de votre compte, veuillez contacter un administrateur du forum.', + 'ADMINISTRATOR' => 'Administrateur', + 'ADMINISTRATORS' => 'Administrateurs', + 'AGE' => 'Âge', + 'AIM' => 'AIM', + 'AJAX_ERROR_TITLE' => 'Erreur AJAX', + 'AJAX_ERROR_TEXT' => 'Une erreur est survenue lors du traitement de votre requête.', + 'AJAX_ERROR_TEXT_ABORT' => 'La requête a été interrompue par l’utilisateur.', + 'AJAX_ERROR_TEXT_TIMEOUT' => 'Votre requête a expiré prématurément. Veuillez réessayer.', + 'AJAX_ERROR_TEXT_PARSERERROR' => 'Une erreur est survenue lors du traitement de votre requête et le serveur a renvoyé une réponse invalide.', + 'ALLOWED' => 'Autorisé', + 'ALL_FILES' => 'Tous les fichiers', + 'ALL_FORUMS' => 'Tous les forums', + 'ALL_MESSAGES' => 'Tous les messages privés', + 'ALL_POSTS' => 'Tous les messages', + 'ALL_TIMES' => 'Fuseau horaire sur %1$s', + 'ALL_TOPICS' => 'Tous les sujets', + 'ALT_TEXT' => 'Texte alternatif', + 'AND' => 'et', + 'ARE_WATCHING_FORUM' => 'Vous êtes à présent abonné à ce forum. Vous recevrez une notification lorsqu’un nouveau sujet sera publié.', + 'ARE_WATCHING_TOPIC' => 'Vous êtes à présent abonné à ce sujet. Vous recevrez une notification lorsqu’un nouveau message sera publié.', + 'ASCENDING' => 'Croissant', + 'ATTACHMENTS' => 'Pièces jointes', + 'ATTACHED_IMAGE_NOT_IMAGE' => 'L’image que vous avez souhaité transférer est invalide.', + 'AUTHOR' => 'Auteur', + 'AUTH_NO_PROFILE_CREATED' => 'Une erreur est survenue lors de la création du profil de l’utilisateur.', + 'AUTH_PROVIDER_OAUTH_ERROR_INVALID_ENTRY' => 'L’entrée de la base de données est invalide.', + 'AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE' => 'Le type de service spécifié dans le gestionnaire de service OAuth est invalide.', + 'AUTH_PROVIDER_OAUTH_ERROR_SERVICE_NOT_CREATED' => 'Le service OAuth n’a pas été créé', + 'AUTH_PROVIDER_OAUTH_SERVICE_BITLY' => 'Bitly', + 'AUTH_PROVIDER_OAUTH_SERVICE_FACEBOOK' => 'Facebook', + 'AUTH_PROVIDER_OAUTH_SERVICE_GOOGLE' => 'Google', + 'AUTH_PROVIDER_OAUTH_SERVICE_TWITTER' => 'Twitter', + 'AUTH_PROVIDER_OAUTH_TOKEN_ERROR_NOT_STORED' => 'Le jeton OAuth n’a pas été stocké.', + 'AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED' => 'Le jeton OAuth n’a pas été stocké correctement.', + 'AVATAR_DISALLOWED_CONTENT' => 'Le transfert a été interrompu. Le fichier que vous avez souhaité transférer a été identifié comme une vulnérabilité potentielle.', + 'AVATAR_DISALLOWED_EXTENSION' => 'Ce fichier ne peut pas être affiché car l’extension « %s » n’est pas autorisée.', + 'AVATAR_EMPTY_REMOTE_DATA' => 'L’avatar que vous avez spécifié n’a pas pu être transféré car les données distantes semblent corrompues ou invalides.', + 'AVATAR_EMPTY_FILEUPLOAD' => 'L’avatar que vous avez transféré est vide.', + 'AVATAR_INVALID_FILENAME' => 'Le nom de fichier « %s » est invalide.', + 'AVATAR_NOT_UPLOADED' => 'Une erreur est survenue lors du transfert de l’avatar.', + 'AVATAR_NO_TEMP_DIR' => 'Le répertoire temporaire est introuvable ou est en lecture seule.', + 'AVATAR_NO_SIZE' => 'La taille de l’avatar lié n’a pas pu être détectée automatiquement. Veuillez la saisir manuellement.', + 'AVATAR_PARTIAL_UPLOAD' => 'Le fichier que vous avez spécifié n’a pas été totalement transféré.', + 'AVATAR_PHP_SIZE_NA' => 'La taille de l’avatar est trop importante.
La taille maximale autorisée, située dans le fichier « php.ini », n’a pas pu être détectée.', + 'AVATAR_PHP_SIZE_OVERRUN' => 'La taille de l’avatar est trop importante. La taille maximale autorisée est de %1$d %2$s.
Veuillez noter que cette valeur est située dans le fichier « php.ini » et qu’elle ne peut pas être remplacée.', + 'AVATAR_REMOTE_UPLOAD_TIMEOUT' => 'L’avatar que vous avez spécifié n’a pu être transféré car la requête a expiré prématurément.', + 'AVATAR_PHP_UPLOAD_STOPPED' => 'Une extension PHP a interrompu le transfert du fichier.', + 'AVATAR_URL_INVALID' => 'Le lien que vous avez spécifié est invalide.', + 'AVATAR_URL_NOT_FOUND' => 'Le fichier que vous avez spécifié est introuvable.', + 'AVATAR_WRONG_FILESIZE' => 'La taille de l’avatar doit être comprise entre 0 et %1$d %2$s.', + 'AVATAR_WRONG_SIZE' => 'La taille de l’avatar que vous avez transféré mesure %5$s de large et %6$s de haut. La taille des avatars doit mesurer entre %1$s de large et %2$s de haut mais ne doit pas dépasser %3$s de large et %4$s de haut.', + + 'BACK_TO_TOP' => 'Haut', + 'BACK_TO_PREV' => 'Revenir à la page précédente', + 'BAN_TRIGGERED_BY_EMAIL' => 'Votre adresse de courriel a été bannie.', + 'BAN_TRIGGERED_BY_IP' => 'Votre adresse IP a été bannie.', + 'BAN_TRIGGERED_BY_USER' => 'Votre nom d’utilisateur a été banni.', + 'BBCODE_GUIDE' => 'Guide du BBCode', + 'BCC' => 'Cci', + 'BIRTHDAYS' => 'Anniversaires', + 'BOARD_BAN_PERM' => 'Vous avez été définitivement banni de ce forum.

Pour plus d’informations, veuillez contacter un %2$sadministrateur du forum%3$s.', + 'BOARD_BAN_REASON' => 'Raison du bannissement : %s', + 'BOARD_BAN_TIME' => 'Vous avez été banni de ce forum jusqu’au %1$s.

Pour plus d’informations, veuillez contacter un %2$sadministrateur du forum%3$s.', + 'BOARD_DISABLE' => 'Le forum est actuellement indisponible.', + 'BOARD_DISABLED' => 'Le forum est actuellement désactivé.', + 'BOARD_UNAVAILABLE' => 'Le forum est temporairement indisponible. Veuillez revenir ultérieurement.', + 'BROWSING_FORUM' => 'Utilisateurs parcourant ce forum : %1$s', + 'BROWSING_FORUM_GUESTS' => [ + 1 => 'Utilisateurs parcourant ce forum : %2$s et %1$d invité', + 2 => 'Utilisateurs parcourant ce forum : %2$s et %1$d invités', + ], + 'BUTTON_DELETE' => 'Supprimer', + 'BUTTON_EDIT' => 'Modifier', + 'BUTTON_FORUM_LOCKED' => 'Verrouillé', + 'BUTTON_INFORMATION' => 'Information', + 'BUTTON_NEW_TOPIC' => 'Nouveau sujet', + 'BUTTON_PM' => 'MP', + 'BUTTON_PM_FORWARD' => 'Transférer', + 'BUTTON_PM_NEW' => 'Nouveau message privé', + 'BUTTON_PM_REPLY' => 'Répondre', + 'BUTTON_PM_REPLY_ALL' => 'Répondre à tous', + 'BUTTON_POST_REPLY' => 'Répondre', + 'BUTTON_QUOTE' => 'Citer', + 'BUTTON_REPORT' => 'Rapporter', + 'BUTTON_TOPIC_LOCKED' => 'Verrouillé', + 'BUTTON_WARN' => 'Avertir', + 'BYTES' => 'octets', + 'BYTES_SHORT' => 'o', + + 'CANCEL' => 'Annuler', + 'CHANGE' => 'Modifier', + 'CHANGE_FONT_SIZE' => 'Modifier la taille de la police de caractères', + 'CHANGING_PREFERENCES' => 'Modifie les préférences du forum', + 'CHANGING_PROFILE' => 'Modifie les paramètres du profil', + 'CHARACTERS' => [ + 1 => '%d caractère', + 2 => '%d caractères', + ], + 'COLLAPSE_VIEW' => 'Réduire', + 'CLOSE_WINDOW' => 'Fermer la fenêtre', + 'COLOUR_SWATCH' => 'Palette de couleurs', + 'COLON' => ' :', + 'COMMA_SEPARATOR' => ', ', // Comma used to join lists into a single string, use localised comma if appropriate, eg: Ideographic or Arabic + 'CONFIRM' => 'Confirmer', + 'CONFIRM_CODE' => 'Code de confirmation', + 'CONFIRM_CODE_EXPLAIN' => 'Veuillez saisir le code tel qu’il apparaît dans l’image. Les lettres ne sont pas sensibles à la casse.', + 'CONFIRM_CODE_WRONG' => 'Le code de confirmation que vous avez spécifié est incorrect.', + 'CONFIRM_OPERATION' => 'Êtes-vous sûr de vouloir effectuer cette opération ?', + 'CONFIRM_AVATAR_DELETE' => 'Êtes-vous sûr de vouloir supprimer cet avatar ?', + 'CONGRATULATIONS' => 'Félicitations à', + 'CONNECTION_FAILED' => 'La connexion a échoué.', + 'CONNECTION_SUCCESS' => 'Vous êtes à présent connecté !', + 'CONTACT' => 'Contact', + 'CONTACT_USER' => 'Contacter %s', + 'CONTACT_US' => 'Nous contacter', + 'COOKIE_CONSENT_INFO' => 'En savoir plus…', + 'COOKIE_CONSENT_MSG' => 'En poursuivant votre navigation sur ce site, vous acceptez l’utilisation de cookies vous permettant de bénéficier d’une expérience de navigation optimale.', + 'COOKIE_CONSENT_OK' => 'J’accepte', + 'COOKIE_CONSENT_HREF' => 'https://www.cnil.fr/fr/site-web-cookies-et-autres-traceurs', + 'COOKIES_DELETED' => 'Les cookies du forum ont été supprimés.', + 'CURRENT_TIME' => 'Nous sommes le %s', + + 'DAY' => 'jour', + 'DAYS' => 'jours', + 'DELETE' => 'Supprimer', + 'DELETE_ALL' => 'Tout supprimer', + 'DELETE_COOKIES' => 'Supprimer les cookies', + 'DELETE_MARKED' => 'Supprimer la sélection', + 'DELETE_POST' => 'Supprimer le message', + 'DELIMITER' => 'Séparateur', + 'DESCENDING' => 'Décroissant', + 'DISABLED' => 'Désactivé', + 'DISPLAY' => 'Afficher', + 'DISPLAY_GUESTS' => 'Afficher les invités', + 'DISPLAY_MESSAGES' => 'Afficher les messages privés publiés depuis', + 'DISPLAY_POSTS' => 'Afficher les messages publiés depuis', + 'DISPLAY_TOPICS' => 'Afficher les sujets publiés depuis', + 'DOWNLOADED' => 'Téléchargé', + 'DOWNLOADING_FILE' => 'Téléchargement du fichier', + 'DOWNLOAD_COUNTS' => [ + 0 => 'Téléchargé 0 fois', + 1 => 'Téléchargé %d fois', + 2 => 'Téléchargé %d fois', + ], + + 'EDIT_POST' => 'Modifier le message', + 'ELLIPSIS' => '…', + 'EMAIL' => 'Courriel', // Short form for EMAIL_ADDRESS + 'EMAIL_ADDRESS' => 'Adresse de courriel', + 'EMAIL_INVALID_EMAIL' => 'L’adresse de courriel que vous avez spécifiée est invalide.', + 'EMAIL_SMTP_ERROR_RESPONSE' => 'Une erreur est survenue lors de l’envoi du courriel à la ligne %1$s. Réponse : %2$s.', + 'EMPTY_SUBJECT' => 'Veuillez saisir le titre du nouveau sujet.', + 'EMPTY_MESSAGE_SUBJECT' => 'Veuillez saisir le titre du nouveau message privé.', + 'ENABLED' => 'Activé', + 'ENCLOSURE' => 'Pièce jointe', + 'ENTER_USERNAME' => 'Veuillez saisir un nom d’utilisateur', + 'ERR_CHANGING_DIRECTORY' => 'Une erreur est survenue lors de la modification du répertoire.', + 'ERR_CONNECTING_SERVER' => 'Une erreur est survenue lors de la connexion au serveur.', + 'ERR_JAB_AUTH' => 'Une erreur est survenue lors de l’authentification au serveur Jabber.', + 'ERR_JAB_CONNECT' => 'Une erreur est survenue lors de la connexion au serveur Jabber.', + 'ERR_UNABLE_TO_LOGIN' => 'Le nom d’utilisateur ou le mot de passe que vous avez spécifié est invalide.', + 'ERR_UNWATCHING' => 'Une erreur est survenue lors du désabonnement.', + 'ERR_WATCHING' => 'Une erreur est survenue lors de l’abonnement.', + 'ERR_WRONG_PATH_TO_PHPBB' => 'Le chemin que vous avez spécifié est invalide.', + 'ERROR' => 'Erreur', + 'EXPAND_VIEW' => 'Agrandir', + 'EXTENSION' => 'Extension', + 'EXTENSION_DISABLED' => 'L’extension « %s » est désactivée.', + 'EXTENSION_DISABLED_AFTER_POSTING' => 'L’extension « %s » a été désactivée et ne peut plus être affichée.', + 'EXTENSION_DOES_NOT_EXIST' => 'L’extension « %s » est introuvable.', + + 'FACEBOOK' => 'Facebook', + 'FAQ' => 'FAQ', + 'FAQ_EXPLAIN' => 'Foire aux questions', + 'FEATURE_NOT_AVAILABLE' => 'Cette fonctionnalité n’est pas disponible sur ce forum.', + 'FILENAME' => 'Nom du fichier', + 'FILESIZE' => 'Taille du fichier', + 'FILEDATE' => 'Date du fichier', + 'FILE_COMMENT' => 'Description du fichier', + 'FILE_CONTENT_ERR' => 'Impossible de lire le contenu de ce fichier : %s', + 'FILE_JSON_DECODE_ERR' => 'Impossible de déchiffrer le fichier JSON : %s', + 'FILE_NOT_FOUND' => 'Le fichier que vous recherchez est introuvable : %s', + 'FIND_USERNAME' => 'Trouver un membre', + 'FOLDER' => 'Boîte', + 'FORGOT_PASS' => 'J’ai oublié mon mot de passe', + 'FORM_INVALID' => 'Une erreur est survenue lors de l’envoi du formulaire. Veuillez réessayer.', + 'FORUM' => 'Forum', + 'FORUMS' => 'Forums', + 'FORUMS_MARKED' => 'Les forums ont été marqués comme lus.', + 'FORUM_CAT' => 'Catégorie du forum', + 'FORUM_INDEX' => 'Accueil du forum', + 'FORUM_LINK' => 'Forum-lien', + 'FORUM_LOCATION' => 'Emplacement du forum', + 'FORUM_LOCKED' => 'Forum verrouillé', + 'FORUM_RULES' => 'Règles du forum', + 'FORUM_RULES_LINK' => 'Veuillez cliquer ici afin de consulter les règles du forum', + 'FROM' => 'de', + 'FSOCK_DISABLED' => 'L’opération a échoué car la fonction « fsockopen » est désactivée ou le serveur interrogé est introuvable.', + 'FSOCK_TIMEOUT' => 'Le délai de réponse a expiré lors de la lecture du flux réseau.', + + 'FTP_FSOCK_HOST' => 'Hôte FTP', + 'FTP_FSOCK_HOST_EXPLAIN' => 'Le serveur FTP qui est utilisé afin de vous connecter à votre site.', + 'FTP_FSOCK_PASSWORD' => 'Mot de passe FTP', + 'FTP_FSOCK_PASSWORD_EXPLAIN' => 'Le mot de passe qui est affilié à votre nom d’utilisateur FTP.', + 'FTP_FSOCK_PORT' => 'Port FTP', + 'FTP_FSOCK_PORT_EXPLAIN' => 'Le port qui est utilisé afin de vous connecter à votre serveur.', + 'FTP_FSOCK_ROOT_PATH' => 'Chemin vers le forum', + 'FTP_FSOCK_ROOT_PATH_EXPLAIN' => 'Le chemin relatif à la racine de votre serveur.', + 'FTP_FSOCK_TIMEOUT' => 'Délai d’attente FTP', + 'FTP_FSOCK_TIMEOUT_EXPLAIN' => 'Le nombre de secondes que le système attendra afin d’obtenir une réponse de votre serveur.', + 'FTP_FSOCK_USERNAME' => 'Nom d’utilisateur FTP', + 'FTP_FSOCK_USERNAME_EXPLAIN' => 'Le nom d’utilisateur qui est utilisé afin de vous connecter à votre serveur.', + + 'FTP_HOST' => 'Hôte FTP', + 'FTP_HOST_EXPLAIN' => 'Le serveur FTP qui est utilisé afin de vous connecter à votre site.', + 'FTP_PASSWORD' => 'Mot de passe FTP', + 'FTP_PASSWORD_EXPLAIN' => 'Le mot de passe qui est affilié à votre nom d’utilisateur FTP.', + 'FTP_PORT' => 'Port FTP', + 'FTP_PORT_EXPLAIN' => 'Le port qui est utilisé afin de vous connecter à votre serveur.', + 'FTP_ROOT_PATH' => 'Chemin vers le forum', + 'FTP_ROOT_PATH_EXPLAIN' => 'Le chemin relatif à la racine de votre serveur.', + 'FTP_TIMEOUT' => 'Délai d’attente FTP', + 'FTP_TIMEOUT_EXPLAIN' => 'Le nombre de secondes que le système attendra afin d’obtenir une réponse de votre serveur.', + 'FTP_USERNAME' => 'Nom d’utilisateur FTP', + 'FTP_USERNAME_EXPLAIN' => 'Le nom d’utilisateur qui est utilisé afin de vous connecter à votre serveur.', + + 'GENERAL_ERROR' => 'Erreur générale', + 'GB' => 'Go', + 'GIB' => 'Gio', + 'GO' => 'Aller', + 'GOOGLEPLUS' => 'Google+', + 'GOTO_FIRST_POST' => 'Aller au premier message', + 'GOTO_LAST_POST' => 'Aller au dernier message', + 'GOTO_PAGE' => 'Aller à la page', + 'GROUP' => 'Groupe', + 'GROUPS' => 'Groupes', + 'GROUP_ERR_TYPE' => 'Le type de groupe que vous avez spécifié est invalide.', + 'GROUP_ERR_USERNAME' => 'Vous devez saisir le nom du groupe.', + 'GROUP_ERR_USER_LONG' => 'Le nom de groupe que vous avez saisi est trop long. Les noms de groupe ne doivent pas dépasser 60 caractères.', + 'GUEST' => 'Invité', + 'GUEST_USERS_ONLINE' => [ + 1 => 'Il y a %d invité en ligne', + 2 => 'Il y a %d invités en ligne', + ], + 'GUEST_USERS_TOTAL' => [ + 1 => '%d invité', + 2 => '%d invités', + ], + 'G_ADMINISTRATORS' => 'Administrateurs', + 'G_BOTS' => 'Robots', + 'G_GUESTS' => 'Invités', + 'G_REGISTERED' => 'Utilisateurs inscrits', + 'G_REGISTERED_COPPA' => 'Utilisateurs COPPA inscrits', + 'G_GLOBAL_MODERATORS' => 'Modérateurs généraux', + 'G_NEWLY_REGISTERED' => 'Utilisateurs nouvellement inscrits', + + 'HIDDEN_USERS_ONLINE' => [ + 1 => '%d utilisateur invisible', + 2 => '%d utilisateurs invisibles', + ], + 'HIDDEN_USERS_TOTAL' => [ + 1 => '%d invisible', + 2 => '%d invisibles', + ], + 'HIDE_GUESTS' => 'Masquer les invités', + 'HIDE_ME' => 'Masquer ma présence lors de cette session', + 'HOURS' => 'Heures', + 'HOME' => 'Accueil', + + 'ICQ' => 'ICQ', + 'IF' => 'Si', + 'IMAGE' => 'Image', + 'IMAGE_FILETYPE_INVALID' => 'Le type d’image « %d » du standard MIME « %s » n’est pas pris en charge.', + 'IMAGE_FILETYPE_MISMATCH' => 'Le type d’image ne correspond pas. L’extension « %1$s » était attendue mais l’extension « %2$s » a été transférée.', + 'IN' => 'dans', + 'INACTIVE' => 'Inactif', + 'INDEX' => 'Page d’accueil', + 'INFORMATION' => 'Information', + 'INSECURE_REDIRECT' => 'Redirection vers un lien potentiellement insécurisé.', + 'INTERESTS' => 'Centres d’intérêt', + 'INVALID_DIGEST_CHALLENGE' => 'Le Digest Challenge est invalide.', + 'INVALID_EMAIL_LOG' => 'L’adresse de courriel « %s » est potentiellement invalide.', + 'INVALID_FEED_ATTACHMENTS' => 'Le flux sélectionné a tenté d’extraire des pièces jointes contenant des contraintes invalides.', + 'INVALID_PLURAL_RULE' => 'Cette règle de pluriel est invalide. Seuls les nombres entiers compris entre 0 et 15 sont autorisés.', + 'IP' => 'IP', + 'IP_BLACKLISTED' => 'Votre adresse IP %1$s a été bloquée car elle est présente dans la liste noire. Pour plus d’informations, veuillez consulter %2$s.', + + 'JABBER' => 'Jabber', + 'JOINED' => 'Inscription', + 'JUMP_PAGE' => 'Saisissez le numéro de la page que vous souhaitez atteindre', + 'JUMP_TO' => 'Aller', + 'JUMP_TO_PAGE' => 'Aller sur la page', + 'JUMP_TO_PAGE_CLICK' => 'Cliquez ici afin d’atteindre une page…', + + 'KB' => 'Ko', + 'KIB' => 'Kio', + + 'LAST_POST' => 'Dernier message', + 'LAST_UPDATED' => 'Dernière mise à jour', + 'LAST_VISIT' => 'Dernière visite', + 'LDAP_NO_LDAP_EXTENSION' => 'L’extension LDAP est indisponible.', + 'LDAP_NO_SERVER_CONNECTION' => 'Une erreur est survenue lors de la connexion au serveur LDAP.', + 'LDAP_SEARCH_FAILED' => 'Le répertoire LDAP est introuvable.', + 'LEGEND' => 'Légende', + 'LIVE_SEARCHES_NOT_ALLOWED' => 'Les prédictions de recherche ne sont pas autorisées.', + 'LOADING' => 'Chargement', + 'LOCATION' => 'Localisation', + 'LOCK_POST' => 'Verrouiller le message', + 'LOCK_POST_EXPLAIN' => 'Il ne pourra plus être modifié', + 'LOCK_TOPIC' => 'Verrouiller le sujet', + 'LOGIN' => 'Connexion', + 'LOGIN_CHECK_PM' => 'Connectez-vous afin de consulter vos messages privés.', + 'LOGIN_CONFIRMATION' => 'Confirmation de connexion', + 'LOGIN_CONFIRM_EXPLAIN' => 'Pour des raisons de sécurité, vous devez saisir un code de confirmation après avoir échoué plusieurs fois lors de vos tentatives de connexion. Le code est affiché dans l’image affichée ci-dessous. Si vous éprouvez des difficultés à lire ce code correctement, veuillez contacter un %sadministrateur du forum%s.', // Unused + 'LOGIN_ERROR_ATTEMPTS' => 'Vous avez dépassé le nombre maximal de tentatives de connexion autorisées. En plus de votre nom d’utilisateur et de votre mot de passe, vous devez à présent résoudre le CAPTCHA affiché ci-dessous.', + 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE' => 'Une erreur est survenue lors de votre authentification par Apache.', + 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST' => 'Le service OAuth est introuvable.', + 'LOGIN_ERROR_PASSWORD' => 'Le mot de passe que vous avez spécifié est incorrect. Veuillez vérifier votre mot de passe et réessayer. Si ce problème persiste, veuillez contacter un %sadministrateur du forum%s.', + 'LOGIN_ERROR_PASSWORD_CONVERT' => 'Il n’a pas été possible de convertir votre mot de passe lors de la mise à jour du logiciel de ce forum de discussions. Veuillez %sdemander un nouveau mot de passe%s. Si ce problème persiste, veuillez contacter un %sadministrateur du forum%s.', + 'LOGIN_ERROR_USERNAME' => 'Le nom d’utilisateur que vous avez spécifié est incorrect. Veuillez vérifier votre nom d’utilisateur et réessayer. Si ce problème persiste, veuillez contacter un %sadministrateur du forum%s.', + 'LOGIN_FORUM' => 'Vous devez saisir le mot de passe de ce forum afin de consulter et de publier des sujets et des messages.', + 'LOGIN_INFO' => 'Vous devez être inscrit avant de pouvoir vous connecter. L’inscription est rapide et vous offre de nombreux avantages. Les administrateurs du forum peuvent accorder des fonctionnalités supplémentaires aux utilisateurs inscrits. Avant de vous inscrire, assurez-vous d’avoir pris connaissance de nos conditions d’utilisation et de notre politique de confidentialité. Veuillez également prendre le temps de consulter attentivement toutes les règles du forum lors de votre navigation.', + 'LOGIN_VIEWFORUM' => 'Vous devez être inscrit et connecté afin de pouvoir consulter ce forum.', + 'LOGIN_EXPLAIN_EDIT' => 'Vous devez être inscrit et connecté afin de pouvoir modifier les messages de ce forum.', + 'LOGIN_EXPLAIN_VIEWONLINE' => 'Vous devez être inscrit et connecté afin de pouvoir consulter la liste des utilisateurs en ligne.', + 'LOGIN_REQUIRED' => 'Vous devez être connecté afin d’effectuer cette opération.', + 'LOGOUT' => 'Déconnexion', + 'LOGOUT_USER' => 'Déconnexion [ %s ]', + 'LOG_ME_IN' => 'Se souvenir de moi', + + 'MAIN' => 'Général', + 'MARK' => 'Cocher', + 'MARK_ALL' => 'Tout cocher', + 'MARK_ALL_READ' => 'Tout marquer comme lu', + 'MARK_FORUMS_READ' => 'Marquer tous les forums comme lus', + 'MARK_READ' => 'Marquer comme lu', + 'MARK_SUBFORUMS_READ' => 'Marquer tous les sous-forums comme lus', + 'MB' => 'Mo', + 'MIB' => 'Mio', + 'MCP' => 'Panneau de contrôle du modérateur', + 'MCP_SHORT' => 'PCM', + 'MEMBERLIST' => 'Membres', + 'MEMBERLIST_EXPLAIN' => 'La liste complète des membres du forum', + 'MERGE' => 'Fusionner', + 'MERGE_POSTS' => 'Déplacer les messages', + 'MERGE_TOPIC' => 'Fusionner le sujet', + 'MESSAGE' => 'Message', + 'MESSAGES' => 'Messages', + 'MESSAGES_COUNT' => [ + 1 => '%d message', + 2 => '%d messages', + ], + 'MESSAGE_BODY' => 'Corps du message', + 'MINUTES' => 'minutes', + 'MODERATE' => 'Modérer', + 'MODERATOR' => 'Modérateur', + 'MODERATORS' => 'Modérateurs', + 'MODULE_NOT_ACCESS' => 'Le module est inaccessible', + 'MODULE_NOT_FIND' => 'Le module « %s » est introuvable', + 'MODULE_FILE_INCORRECT_CLASS' => 'Le fichier du module « %s » ne contient pas la classe « %s »', + 'MONTH' => 'Mois', + 'MOVE' => 'Déplacer', + + 'NA' => 'ND', + 'NEWEST_USER' => 'Notre membre le plus récent est %s', + 'NEW_MESSAGE' => 'Nouveau message', + 'NEW_MESSAGES' => 'Nouveaux messages', + 'NEW_POST' => 'Nouveau message', // Not used anymore + 'NEW_POSTS' => 'Nouveaux messages', // Not used anymore + 'NEXT' => 'Suivant', // Used in pagination + 'NEXT_STEP' => 'Suivant', + 'NEVER' => 'Jamais', + 'NO' => 'Non', + 'NO_NOTIFICATIONS' => 'Aucune notification.', + 'NOT_ALLOWED_MANAGE_GROUP' => 'Vous ne pouvez pas gérer ce groupe.', + 'NOT_AUTHORISED' => 'Vous ne pouvez pas accéder à cette page.', + 'NOT_WATCHING_FORUM' => 'Vous n’êtes à présent plus abonné à ce forum. Vous ne recevrez plus de notification lorsqu’un nouveau sujet sera publié.', + 'NOT_WATCHING_TOPIC' => 'Vous n’êtes à présent plus abonné à ce sujet. Vous ne recevrez plus de notification lorsqu’un nouveau message sera publié.', + 'NOTIFICATIONS' => 'Notifications', + // This applies for NOTIFICATION_BOOKMARK and NOTIFICATION_POST. + // %1$s will return a list of users that's concatenated using "," and "and" - see STRING_LIST + // Once the user count reaches 5 users or more, the list is trimmed using NOTIFICATION_X_OTHERS + // Once the user count reaches 20 users or more, the list is trimmed using NOTIFICATION_MANY_OTHERS + // Examples: + // A replied... + // A and B replied... + // A, B and C replied... + // A, B, C and 2 others replied... + // A, B, C and others replied... + 'NOTIFICATION_BOOKMARK' => [ + 1 => 'Publication d’un message par %1$s dans le sujet ajouté aux favoris :', + 2 => 'Publication de messages par %1$s dans le sujet ajouté aux favoris :', + ], + 'NOTIFICATION_FORUM' => 'Forum : %1$s', + 'NOTIFICATION_GROUP_REQUEST' => 'Demande d’invitation par %1$s afin de rejoindre le groupe %2$s.', + 'NOTIFICATION_GROUP_REQUEST_APPROVED' => 'Demande d’invitation approuvée afin de rejoindre le groupe %1$s.', + 'NOTIFICATION_METHOD_INVALID' => 'La méthode « %s » ne fait pas référence à une méthode de notification valide.', + 'NOTIFICATION_PM' => 'Réception d’un message privé par %1$s :', + 'NOTIFICATION_POST' => [ + 1 => 'Publication d’un message par %1$s dans le sujet :', + 2 => 'Publication de messages par %1$s dans le sujet :', + ], + 'NOTIFICATION_POST_APPROVED' => 'Approbation d’un message :', + 'NOTIFICATION_POST_DISAPPROVED' => 'Désapprobation d’un message :', + 'NOTIFICATION_POST_IN_QUEUE' => 'Demande d’approbation d’un message par %1$s :', + 'NOTIFICATION_QUOTE' => [ + 1 => 'Citation par %1$s dans :', + 2 => 'Citations par %1$s dans :', + ], + 'NOTIFICATION_REFERENCE' => '« %1$s »', + 'NOTIFICATION_REASON' => 'Raison : %1$s.', + 'NOTIFICATION_REPORT_PM' => 'Rapport d’un message privé par %1$s :', + 'NOTIFICATION_REPORT_POST' => 'Rapport d’un message par %1$s :', + 'NOTIFICATION_REPORT_CLOSED' => 'Clôture d’un rapport par %1$s pour :', + 'NOTIFICATION_TOPIC' => 'Publication d’un sujet par %1$s :', + 'NOTIFICATION_TOPIC_APPROVED' => 'Approbation d’un sujet :', + 'NOTIFICATION_TOPIC_DISAPPROVED' => 'Désapprobation d’un sujet :', + 'NOTIFICATION_TOPIC_IN_QUEUE' => 'Demande d’approbation d’un sujet par %1$s :', + 'NOTIFICATION_TYPE_NOT_EXIST' => 'Le type de notification « %s » est introuvable dans le système de fichiers.', + 'NOTIFICATION_ADMIN_ACTIVATE_USER' => 'Demande d’activation par un utilisateur désactivé ou nouvellement inscrit : « %1$s »', + // Used in conjuction with NOTIFICATION_BOOKMARK and NOTIFICATION_POST. + 'NOTIFICATION_MANY_OTHERS' => 'plusieurs autres utilisateurs', + 'NOTIFICATION_X_OTHERS' => [ + 1 => '%d autre utilisateur', + 2 => '%d autres utilisateurs', + ], + 'NOTIFY_ADMIN' => 'Veuillez contacter un administrateur du forum.', + 'NOTIFY_ADMIN_EMAIL' => 'Veuillez contacter un administrateur du forum : %1$s', + 'NO_ACCESS_ATTACHMENT' => 'Vous ne pouvez pas accéder à ce fichier.', + 'NO_ACTION' => 'Aucune opération n’a été sélectionnée.', + 'NO_ADMINISTRATORS' => 'Aucun administrateur.', + 'NO_AUTH_ADMIN' => 'Vous ne pouvez pas accéder au panneau de contrôle d’administration.', + 'NO_AUTH_ADMIN_USER_DIFFER' => 'Vous ne pouvez pas vous authentifier de nouveau avec un compte d’utilisateur différent du vôtre.', + 'NO_AUTH_OPERATION' => 'Vous ne pouvez pas effectuer cette opération.', + 'NO_AVATARS' => 'Aucun avatar n’est disponible', + 'NO_CONNECT_TO_SMTP_HOST' => 'Une erreur est survenue lors de la connexion à l’hôte du protocole SMTP : %1$s : %2$s', + 'NO_BIRTHDAYS' => 'Aucun membre ne fête son anniversaire aujourd’hui.', + 'NO_EMAIL_MESSAGE' => 'Ce courriel ne contient aucun contenu.', + 'NO_EMAIL_RESPONSE_CODE' => 'Une erreur est survenue lors de la récupération des codes de réponse du serveur de la messagerie électronique.', + 'NO_EMAIL_SUBJECT' => 'Vous devez saisir le sujet du courriel.', + 'NO_FORUM' => 'Le forum que vous souhaitez consulter est introuvable.', + 'NO_FORUMS' => 'Aucun forum.', + 'NO_GROUP' => 'Le groupe d’utilisateurs que vous souhaitez consulter est introuvable.', + 'NO_GROUP_MEMBERS' => 'Ce groupe d’utilisateurs ne contient aucun membre.', + 'NO_IPS_DEFINED' => 'Aucun nom d’hôte ou adresse IP n’a été spécifié.', + 'NO_MEMBERS' => 'Aucun membre ne correspond à ce critère.', + 'NO_MESSAGES' => 'Aucun message', + 'NO_MODE' => 'Aucun mode n’a été sélectionné.', + 'NO_MODERATORS' => 'Aucun modérateur.', + 'NO_NEW_MESSAGES' => 'Aucun nouveau message', + 'NO_NEW_POSTS' => 'Aucun nouveau message', // Not used anymore + 'NO_ONLINE_USERS' => 'Aucun utilisateur inscrit', + 'NO_POSTS' => 'Aucun message', + 'NO_POSTS_TIME_FRAME' => 'Aucun message n’a été publié dans ce sujet lors de cette période.', + 'NO_FEED_ENABLED' => 'Les flux ne sont pas disponibles sur ce forum.', + 'NO_FEED' => 'Le flux que vous souhaitez consulter n’est pas disponible.', + 'NO_STYLE_DATA' => 'Impossible d’accéder aux données du style', + 'NO_SUBJECT' => 'Aucun sujet n’a été spécifié', // Used for posts having no subject defined but displayed within management pages. + 'NO_SUCH_SEARCH_MODULE' => 'Le module de recherche que vous avez spécifié est introuvable.', + 'NO_SUPPORTED_AUTH_METHODS' => 'Aucune méthode d’authentification n’est prise en charge.', + 'NO_TOPIC' => 'Le sujet que vous souhaitez consulter est introuvable.', + 'NO_TOPIC_FORUM' => 'Le forum ou le sujet que vous souhaitez consulter est introuvable.', + 'NO_TOPICS' => 'Aucun sujet n’a été publié dans ce forum.', + 'NO_TOPICS_TIME_FRAME' => 'Aucun sujet n’a été publié dans ce forum lors de cette période.', + 'NO_UNREAD_POSTS' => 'Aucun message non lu', + 'NO_UPLOAD_FORM_FOUND' => 'Le transfert a démarré mais le formulaire de transfert de fichiers est introuvable.', + 'NO_USER' => 'L’utilisateur que vous souhaitez consulter est introuvable.', + 'NO_USERS' => 'Les utilisateurs que vous souhaitez consulter sont introuvables.', + 'NO_USER_SPECIFIED' => 'Aucun nom d’utilisateur n’a été spécifié.', + + // Nullar/Singular/Plural language entry. The key numbers define the number range in which a certain grammatical expression is valid. + 'NUM_ATTACHMENTS' => [ + 1 => '%d pièce jointe', + 2 => '%d pièces jointes', + ], + 'NUM_POSTS_IN_QUEUE' => [ + 0 => 'Aucun message en attente', // 0 + 1 => '1 message en attente', // 1 + 2 => '%d messages en attente', // 2+ + ], + + 'OCCUPATION' => 'Profession', + 'OFFLINE' => 'Hors-ligne', + 'ONLINE' => 'En ligne', + 'ONLINE_BUDDIES' => 'Amis en ligne', + // "... :: x registered and y hidden" + 'ONLINE_USERS_TOTAL' => [ + 1 => 'Au total, il y a %1$d utilisateur en ligne :: %2$s et %3$s', + 2 => 'Au total, il y a %1$d utilisateurs en ligne :: %2$s et %3$s', + ], + // "... :: x registered, y hidden and z guests" + 'ONLINE_USERS_TOTAL_GUESTS' => [ + 1 => 'Au total, il y a %1$d utilisateur en ligne :: %2$s, %3$s et %4$s', + 2 => 'Au total, il y a %1$d utilisateurs en ligne :: %2$s, %3$s et %4$s', + ], + 'OPTIONS' => 'Options', + + 'PAGE_NOT_FOUND' => 'La page demandée est introuvable.', + 'PAGE_OF' => 'Page %1$d sur %2$d', + 'PAGE_TITLE_NUMBER' => 'Page %s', + 'PASSWORD' => 'Mot de passe', + 'PIXEL' => 'px', + 'PIXELS' => [ + 1 => '%d pixel', + 2 => '%d pixels', + ], + 'PLEASE_WAIT' => 'Veuillez patienter.', + 'PM' => 'MP', + 'PM_REPORTED' => 'Cliquez ici afin de consulter le rapport', + 'POSTING_MESSAGE' => 'Rédige un message dans %s', + 'POSTING_PRIVATE_MESSAGE' => 'Rédige un message privé', + 'POST' => 'Message', + 'POST_ANNOUNCEMENT' => 'Annonce', + 'POST_STICKY' => 'Note', + 'POSTED' => 'Publié', + 'POSTED_IN_FORUM' => 'dans', + 'POSTED_ON_DATE' => 'le', + 'POSTS' => 'Messages', + 'POSTS_UNAPPROVED' => 'Au moins un message de ce sujet n’a pas été approuvé.', + 'POSTS_UNAPPROVED_FORUM' => 'Au moins un message de ce forum n’a pas été approuvé.', + 'POST_BY_AUTHOR' => 'par', + 'POST_BY_FOE' => '%1$s, qui est actuellement dans votre liste d’utilisateurs ignorés, a publié ce message.', + 'POST_DISPLAY' => '%1$sAfficher le message%2$s.', + 'POST_DAY' => '%.2f messages par jour', + 'POST_DELETED_ACTION' => 'Message supprimé :', + 'POST_DELETED' => 'Ce message a été supprimé.', + 'POST_DELETED_BY' => '%2$s a supprimé le message publié par %1$s le %3$s.', + 'POST_DELETED_BY_REASON' => '%2$s a supprimé le message publié par %1$s le %3$s pour la raison suivante : %4$s', + 'POST_DETAILS' => 'Informations sur le message', + 'POST_NEW_TOPIC' => 'Publier un nouveau sujet', + 'POST_PCT' => '%.2f %% de tous les messages', + 'POST_PCT_ACTIVE' => '%.2f %% de tous les messages de l’utilisateur', + 'POST_PCT_ACTIVE_OWN' => '%.2f %% de tous vos messages', + 'POST_REPLY' => 'Répondre', + 'POST_REPORTED' => 'Cliquez ici afin de consulter le rapport', + 'POST_SUBJECT' => 'Sujet du message', + 'POST_TIME' => 'Date du message', + 'POST_TOPIC' => 'Publier un nouveau sujet', + 'POST_UNAPPROVED_ACTION' => 'Message en attente d’approbation :', + 'POST_UNAPPROVED' => 'Ce message n’a pas encore été approuvé.', + 'POWERED_BY' => 'Développé par %s', + 'PREVIEW' => 'Prévisualiser', + 'PREVIOUS' => 'Précédent', // Used in pagination + 'PREVIOUS_STEP' => 'Précédent', + 'PRIVACY' => 'Politique de confidentialité', + 'PRIVACY_LINK' => 'Confidentialité', + 'PRIVATE_MESSAGE' => 'Message privé', + 'PRIVATE_MESSAGES' => 'Messages privés', + 'PRIVATE_MESSAGING' => 'Messagerie privée', + 'PROFILE' => 'Panneau de contrôle de l’utilisateur', + + 'QUICK_LINKS' => 'Raccourcis', + + 'RANK' => 'Rang', + 'READING_FORUM' => 'Consulte des sujets dans %s', + 'READING_GLOBAL_ANNOUNCE' => 'Consulte une annonce générale', + 'READING_LINK' => 'Visite le forum-lien %s', + 'READING_TOPIC' => 'Consulte un sujet dans %s', + 'READ_PROFILE' => 'Profil', + 'REASON' => 'Raison', + 'RECORD_ONLINE_USERS' => 'Le nombre maximal d’utilisateurs en ligne simultanément a été de %1$s le %2$s', + 'REDIRECT' => 'Redirection', + 'REDIRECTS' => 'Nombre total de redirections', + 'REGISTER' => 'Inscription', + 'REGISTERED_USERS' => 'Utilisateurs inscrits :', + // "... and 2 hidden users online" + 'REG_USERS_ONLINE' => [ + 1 => 'Il y a %1$d utilisateur inscrit et %2$s en ligne', + 2 => 'Il y a %1$d utilisateurs inscrits et %2$s en ligne', + ], + 'REG_USERS_TOTAL' => [ + 1 => '%d inscrit', + 2 => '%d inscrits', + ], + 'REMOVE' => 'Supprimer', + 'REMOVE_INSTALL' => 'Veuillez supprimer, déplacer ou renommer le répertoire « install/ » de votre serveur. Seul le panneau de contrôle d’administration sera accessible tant que ce répertoire est présent.', + 'REPLIES' => 'Réponses', + 'REPLY_WITH_QUOTE' => 'Répondre en citant le message', + 'REPLYING_GLOBAL_ANNOUNCE' => 'Répond à une annonce générale', + 'REPLYING_MESSAGE' => 'Répond à un message dans %s', + 'REPORT_BY' => 'Rapporté par', + 'REPORT_POST' => 'Rapporter le message', + 'REPORTING_POST' => 'Rapporte un message', + 'RESEND_ACTIVATION' => 'Renvoyer le courriel d’activation', + 'RESET' => 'Réinitialiser', + 'RESTORE_PERMISSIONS' => 'Restaurer les permissions', + 'RETURN_INDEX' => '%sRevenir à la page d’accueil%s', + 'RETURN_FORUM' => '%sRevenir au dernier forum visité%s', + 'RETURN_PAGE' => '%sRevenir à la page précédente%s', + 'RETURN_TOPIC' => '%sRevenir au dernier sujet visité%s', + 'RETURN_TO' => 'Revenir à « %s »', + 'RETURN_TO_INDEX' => 'Revenir à l’accueil du forum', + 'FEED' => 'Flux', + 'FEED_NEWS' => 'Nouveautés', + 'FEED_TOPICS_ACTIVE' => 'Sujets actifs', + 'FEED_TOPICS_NEW' => 'Nouveaux sujets', + 'RULES_ATTACH_CAN' => 'Vous pouvez transférer des pièces jointes dans ce forum', + 'RULES_ATTACH_CANNOT' => 'Vous ne pouvez pas transférer de pièces jointes dans ce forum', + 'RULES_DELETE_CAN' => 'Vous pouvez supprimer vos messages dans ce forum', + 'RULES_DELETE_CANNOT' => 'Vous ne pouvez pas supprimer vos messages dans ce forum', + 'RULES_DOWNLOAD_CAN' => 'Vous pouvez télécharger des pièces jointes dans ce forum', + 'RULES_DOWNLOAD_CANNOT' => 'Vous ne pouvez pas télécharger de pièces jointes dans ce forum', + 'RULES_EDIT_CAN' => 'Vous pouvez modifier vos messages dans ce forum', + 'RULES_EDIT_CANNOT' => 'Vous ne pouvez pas modifier vos messages dans ce forum', + 'RULES_LOCK_CAN' => 'Vous pouvez verrouiller vos sujets dans ce forum', + 'RULES_LOCK_CANNOT' => 'Vous ne pouvez pas verrouiller vos sujets dans ce forum', + 'RULES_POST_CAN' => 'Vous pouvez publier de nouveaux sujets dans ce forum', + 'RULES_POST_CANNOT' => 'Vous ne pouvez pas publier de nouveaux sujets dans ce forum', + 'RULES_REPLY_CAN' => 'Vous pouvez répondre aux sujets dans ce forum', + 'RULES_REPLY_CANNOT' => 'Vous ne pouvez pas répondre aux sujets dans ce forum', + 'RULES_VOTE_CAN' => 'Vous pouvez voter aux sondages dans ce forum', + 'RULES_VOTE_CANNOT' => 'Vous ne pouvez pas voter aux sondages dans ce forum', + + 'SEARCH' => 'Rechercher', + 'SEARCH_MINI' => 'Rechercher…', + 'SEARCH_ADV' => 'Recherche avancée', + 'SEARCH_ADV_EXPLAIN' => 'Accéder aux paramètres avancés de la recherche', + 'SEARCH_KEYWORDS' => 'Rechercher par mots-clés', + 'SEARCHING_FORUMS' => 'Recherche dans les forums', + 'SEARCH_ACTIVE_TOPICS' => 'Sujets actifs', + 'SEARCH_FOR' => 'Rechercher', + 'SEARCH_FORUM' => 'Rechercher…', + 'SEARCH_NEW' => 'Nouveaux messages', + 'SEARCH_POSTS_BY' => 'Rechercher les messages de', + 'SEARCH_SELF' => 'Vos messages', + 'SEARCH_TOPIC' => 'Rechercher…', + 'SEARCH_UNANSWERED' => 'Sujets sans réponse', + 'SEARCH_UNREAD' => 'Messages non lus', + 'SEARCH_USER_POSTS' => 'Rechercher les messages de l’utilisateur', + 'SECONDS' => 'secondes', + 'SEE_ALL' => 'Tout consulter', + 'SELECT' => 'Sélectionner', + 'SELECT_ALL_CODE' => 'Tout sélectionner', + 'SELECT_DESTINATION_FORUM' => 'Veuillez sélectionner un forum de destination', + 'SELECT_FORUM' => 'Sélectionner un forum', + 'SEND_EMAIL' => 'Envoyer un courriel', // Used for submit buttons + 'SEND_EMAIL_USER' => 'Envoyer un courriel à %s', + 'SEND_PRIVATE_MESSAGE' => 'Envoyer un message privé', + 'SETTINGS' => 'Paramètres', + 'SIGNATURE' => 'Signature', + 'SKIP' => 'Accéder au contenu', + 'SKYPE' => 'Skype', + 'SMTP_NO_AUTH_SUPPORT' => 'Une erreur est survenue lors de l’authentification par le serveur SMTP.', + 'SORRY_AUTH_READ' => 'Vous ne pouvez pas consulter ce forum.', + 'SORRY_AUTH_READ_TOPIC' => 'Vous ne pouvez pas consulter ce sujet.', + 'SORRY_AUTH_VIEW_ATTACH' => 'Vous ne pouvez pas télécharger cette pièce jointe.', + 'SORT_BY' => 'Trier par', + 'SORT_DIRECTION' => 'Ordre', + 'SORT_JOINED' => 'Date d’inscription', + 'SORT_LOCATION' => 'Localisation', + 'SORT_OPTIONS' => 'Options d’affichage et de tri', + 'SORT_RANK' => 'Rang', + 'SORT_POSTS' => 'Messages', + 'SORT_TOPIC_TITLE' => 'Titre du sujet', + 'SORT_USERNAME' => 'Nom d’utilisateur', + 'SPLIT_TOPIC' => 'Diviser le sujet', + 'SQL_ERROR_OCCURRED' => 'Une erreur SQL est survenue lors du chargement de la page. Si ce problème persiste, veuillez contacter un %sadministrateur du forum%s.', + 'STATISTICS' => 'Statistiques', + 'START_WATCHING_FORUM' => 'S’abonner au forum', + 'START_WATCHING_TOPIC' => 'S’abonner au sujet', + 'STOP_WATCHING_FORUM' => 'Se désabonner du forum', + 'STOP_WATCHING_TOPIC' => 'Se désabonner du sujet', + 'STRING_LIST_MULTI' => '%1$s, et %2$s', + 'STRING_LIST_SIMPLE' => '%1$s et %2$s', + 'SUBFORUM' => 'Sous-forum', + 'SUBFORUMS' => 'Sous-forums', + 'SUBJECT' => 'Sujet', + 'SUBMIT' => 'Envoyer', + + 'TB' => 'To', + 'TERMS_LINK' => 'Conditions', + 'TERMS_USE' => 'Conditions d’utilisation', + 'TEST_CONNECTION' => 'Tester la connexion', + 'THE_TEAM' => 'L’équipe', + 'TIB' => 'Tio', + 'TIME' => 'Date', + 'TIMEOUT_PROCESSING_REQ' => 'Le délai a été dépassé.', + + 'TOO_LARGE' => 'La valeur que vous avez spécifiée est trop importante.', + 'TOO_LARGE_MAX_RECIPIENTS' => 'La valeur du nombre maximal de destinataires autorisés par message privé que vous avez spécifiée est trop importante.', + + 'TOO_LONG' => 'La valeur que vous avez spécifiée est trop longue.', + + 'TOO_LONG_CONFIRM_CODE' => 'Le code de confirmation que vous avez spécifié est trop long.', + 'TOO_LONG_DATEFORMAT' => 'Le format de date que vous avez spécifié est trop long.', + 'TOO_LONG_JABBER' => 'Le nom du compte Jabber que vous avez spécifié est trop long.', + 'TOO_LONG_NEW_PASSWORD' => 'Le mot de passe que vous avez spécifié est trop long.', + 'TOO_LONG_PASSWORD_CONFIRM' => 'Le mot de passe de confirmation que vous avez spécifié est trop long.', + 'TOO_LONG_USER_PASSWORD' => 'Le mot de passe que vous avez spécifié est trop long.', + 'TOO_LONG_USERNAME' => 'Le nom d’utilisateur que vous avez spécifié est trop long.', + 'TOO_LONG_EMAIL' => 'L’adresse de courriel que vous avez spécifiée est trop longue.', + + 'TOO_MANY_VOTE_OPTIONS' => 'Vous avez voté pour un trop grand nombre d’options.', + + 'TOO_SHORT' => 'La valeur que vous avez spécifiée est trop courte.', + + 'TOO_SHORT_CONFIRM_CODE' => 'Le code de confirmation que vous avez spécifié est trop court.', + 'TOO_SHORT_DATEFORMAT' => 'Le format de date que vous avez spécifié est trop court.', + 'TOO_SHORT_JABBER' => 'Le nom du compte Jabber que vous avez spécifié est trop court.', + 'TOO_SHORT_NEW_PASSWORD' => 'Le mot de passe que vous avez spécifié est trop court.', + 'TOO_SHORT_PASSWORD_CONFIRM' => 'Le mot de passe de confirmation que vous avez spécifié est trop court.', + 'TOO_SHORT_USER_PASSWORD' => 'Le mot de passe que vous avez spécifié est trop court.', + 'TOO_SHORT_USERNAME' => 'Le nom d’utilisateur que vous avez spécifié est trop court.', + 'TOO_SHORT_EMAIL' => 'L’adresse de courriel que vous avez spécifiée est trop courte.', + 'TOO_SHORT_EMAIL_CONFIRM' => 'L’adresse de courriel de confirmation que vous avez spécifiée est trop courte.', + 'TOO_SMALL' => 'La valeur que vous avez spécifiée est trop faible.', + 'TOO_SMALL_MAX_RECIPIENTS' => 'La valeur du nombre maximal de destinataires autorisés par message privé que vous avez spécifiée est trop faible.', + + 'TOPIC' => 'Sujet', + 'TOPICS' => 'Sujets', + 'TOPICS_UNAPPROVED' => 'Au moins un des sujets de ce forum n’a pas été approuvé.', + 'TOPIC_ICON' => 'Icône de sujet', + 'TOPIC_LOCKED' => 'Ce sujet est verrouillé. Vous ne pouvez pas publier ou modifier de messages.', + 'TOPIC_LOCKED_SHORT' => 'Sujet verrouillé', + 'TOPIC_MOVED' => 'Sujet déplacé', + 'TOPIC_REVIEW' => 'Relecture du sujet', + 'TOPIC_TITLE' => 'Titre du sujet', + 'TOPIC_UNAPPROVED' => 'Ce sujet n’a pas encore été approuvé.', + 'TOPIC_UNAPPROVED_FORUM' => [ + 1 => 'Sujet en attente d’approbation', + 2 => 'Sujets en attente d’approbation', + ], + 'TOPIC_DELETED' => 'Ce sujet a été supprimé.', + 'TOTAL_ATTACHMENTS' => 'Pièces jointes', + 'TOTAL_LOGS' => [ + 1 => '%d historique', + 2 => '%d historiques', + ], + 'TOTAL_PMS' => [ + 1 => '%d message privé', + 2 => '%d messages privés', + ], + 'TOPIC_POLL' => 'Ce sujet contient un sondage.', + 'TOTAL_POSTS' => 'Nombre total de messages', + 'TOTAL_POSTS_COUNT' => [ + 1 => '%d message', + 2 => '%d messages', + ], + 'TOPIC_REPORTED' => 'Ce sujet a été rapporté', + 'TOTAL_TOPICS' => [ + 1 => '%d sujet', + 2 => '%d sujets', + ], + 'TOTAL_USERS' => [ + 1 => '%d membre', + 2 => '%d membres', + ], + 'TRACKED_PHP_ERROR' => 'Erreurs PHP survenues : %s', + 'TWITTER' => 'Twitter', + + 'UNABLE_GET_IMAGE_SIZE' => 'Les dimensions de l’image n’ont pu être détectées automatiquement. Veuillez vérifier que le lien que vous avez saisi est valide.', + 'UNABLE_TO_DELIVER_FILE' => 'Une erreur est survenue lors du transfert du fichier.', + 'UNKNOWN_BROWSER' => 'Navigateur inconnu', + 'UNMARK_ALL' => 'Tout décocher', + 'UNREAD_MESSAGES' => 'Messages non lus', + 'UNREAD_POST' => 'Message non lu', + 'UNREAD_POSTS' => 'Messages non lus', + 'UNWATCH_FORUM_CONFIRM' => 'Êtes-vous sûr de vouloir vous désabonner de ce forum ?', + 'UNWATCH_FORUM_DETAILED' => 'Êtes-vous sûr de vouloir vous désabonner du forum « %s » ?', + 'UNWATCH_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir vous désabonner de ce sujet ?', + 'UNWATCH_TOPIC_DETAILED' => 'Êtes-vous sûr de vouloir vous désabonner du sujet « %s » ?', + 'UNWATCHED_FORUMS' => 'Vous n’êtes à présent plus abonné à ces forums.', + 'UNWATCHED_TOPICS' => 'Vous n’êtes à présent plus abonné à ces sujets.', + 'UNWATCHED_FORUMS_TOPICS' => 'Vous n’êtes à présent plus abonné à ces éléments.', + 'UPDATE' => 'Mettre à jour', + 'UPLOAD_IN_PROGRESS' => 'Transfert en cours.', + 'URL_REDIRECT' => 'Si votre navigateur ne prend pas en charge les redirections automatiques, veuillez %scliquer ici%s afin d’être redirigé.', + 'USERGROUPS' => 'Groupes', + 'USERNAME' => 'Nom d’utilisateur', + 'USERNAMES' => 'Noms d’utilisateurs', + 'USER_AVATAR' => 'Avatar de l’utilisateur', + 'USER_CANNOT_READ' => 'Vous ne pouvez pas consulter les messages de ce forum.', + 'USER_POSTS' => [ + 1 => '%d message', + 2 => '%d messages', + ], + 'USERS' => 'Utilisateurs', + 'USE_PERMISSIONS' => 'Tester les permissions de l’utilisateur', + + 'USER_NEW_PERMISSION_DISALLOWED' => 'Vous ne pouvez pas utiliser cette fonctionnalité. Vous devez vous inscrire et participer activement aux discussions du forum.', + + 'VARIANT_DATE_SEPARATOR' => ' / ', // Used in date format dropdown, eg: "Today, 13:37 / 01 Jan 2007, 13:37" ... to join a relative date with calendar date + 'VIEWED' => 'Consulté', + 'VIEWED_COUNTS' => [ + 0 => 'Consulté 0 fois', + 1 => 'Consulté %d fois', + 2 => 'Consulté %d fois', + ], + 'VIEWING_CONTACT_ADMIN' => 'Consulte la page de contact', + 'VIEWING_FAQ' => 'Consulte la foire aux questions', + 'VIEWING_MEMBERS' => 'Consulte les informations d’un membre', + 'VIEWING_ONLINE' => 'Consulte la liste des utilisateurs en ligne', + 'VIEWING_MCP' => 'Consulte le panneau de contrôle du modérateur', + 'VIEWING_MEMBER_PROFILE' => 'Consulte le profil d’un membre', + 'VIEWING_PRIVATE_MESSAGES' => 'Consulte ses messages privés', + 'VIEWING_REGISTER' => 'Crée un compte', + 'VIEWING_UCP' => 'Consulte le panneau de contrôle de l’utilisateur', + 'VIEWS' => 'Vues', + 'VIEW_BOOKMARKS' => 'Consulter les favoris', + 'VIEW_FORUM_LOGS' => 'Consulter l’historique', + 'VIEW_LATEST_POST' => 'Consulter le dernier message', + 'VIEW_NEWEST_POST' => 'Consulter le message non lu le plus récent', + 'VIEW_NOTES' => 'Consulter les remarques sur l’utilisateur', + 'VIEW_ONLINE_TIMES' => [ + 1 => 'selon le nombre d’utilisateurs actifs de la dernière minute', + 2 => 'selon le nombre d’utilisateurs actifs des %d dernières minutes', + ], + 'VIEW_TOPIC' => 'Consulter le sujet', + 'VIEW_TOPIC_ANNOUNCEMENT' => 'Annonce : ', + 'VIEW_TOPIC_GLOBAL' => 'Annonce générale : ', + 'VIEW_TOPIC_LOCKED' => 'Verrouillé : ', + 'VIEW_TOPIC_LOGS' => 'Consulter l’historique', + 'VIEW_TOPIC_MOVED' => 'Déplacé : ', + 'VIEW_TOPIC_POLL' => 'Sondage : ', + 'VIEW_TOPIC_STICKY' => 'Note : ', + 'VISIT_WEBSITE' => 'Visiter le site internet', + + 'WARNINGS' => 'Avertissements', + 'WARN_USER' => 'Avertir l’utilisateur', + 'WATCH_FORUM_CONFIRM' => 'Êtes-vous sûr de vouloir vous abonner à ce forum ?', + 'WATCH_FORUM_DETAILED' => 'Êtes-vous sûr de vouloir vous abonner au forum « %s » ?', + 'WATCH_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir vous abonner à ce sujet ?', + 'WATCH_TOPIC_DETAILED' => 'Êtes-vous sûr de vouloir vous abonner au sujet « %s » ?', + 'WELCOME_SUBJECT' => 'Bienvenue sur les forums de %s', + 'WEBSITE' => 'Site internet', + 'WHOIS' => 'Qui est-ce ?', + 'WHO_IS_ONLINE' => 'Qui est en ligne ?', + 'WRONG_PASSWORD' => 'Le mot de passe que vous avez spécifié est incorrect.', + + 'WRONG_DATA_COLOUR' => 'La valeur de couleur que vous avez spécifiée est invalide.', + 'WRONG_DATA_JABBER' => 'Le nom du compte Jabber que vous avez spécifié est invalide.', + 'WRONG_DATA_LANG' => 'La langue que vous avez spécifiée est invalide.', + 'WRONG_DATA_POST_SD' => 'L’ordre de tri des messages que vous avez spécifié est invalide.', + 'WRONG_DATA_POST_SK' => 'L’option de tri des messages que vous avez spécifiée est invalide.', + 'WRONG_DATA_TOPIC_SD' => 'L’ordre de tri des sujets que vous avez spécifié est invalide.', + 'WRONG_DATA_TOPIC_SK' => 'L’option de tri des sujets que vous avez spécifiée est invalide.', + 'WROTE' => 'a écrit', + + 'YAHOO' => 'Yahoo Messenger', + 'YOUTUBE' => 'YouTube', + 'YEAR' => 'Année', + 'YEAR_MONTH_DAY' => '(AAAA-MM-JJ)', + 'YES' => 'Oui', + 'YOU_LAST_VISIT' => 'Dernière visite : %s', + + 'datetime' => [ + 'TODAY' => 'Aujourd’hui', + 'TOMORROW' => 'Demain', + 'YESTERDAY' => 'Hier', + 'AGO' => [ + 0 => 'il y a moins d’une minute', + 1 => 'il y a %d minute', + 2 => 'il y a %d minutes', + ], + + 'Sunday' => 'dimanche', + 'Monday' => 'lundi', + 'Tuesday' => 'mardi', + 'Wednesday' => 'mercredi', + 'Thursday' => 'jeudi', + 'Friday' => 'vendredi', + 'Saturday' => 'samedi', + + 'Sun' => 'dim.', + 'Mon' => 'lun.', + 'Tue' => 'mar.', + 'Wed' => 'mer.', + 'Thu' => 'jeu.', + 'Fri' => 'ven.', + 'Sat' => 'sam.', + + 'January' => 'janvier', + 'February' => 'février', + 'March' => 'mars', + 'April' => 'avril', + 'May' => 'mai', + 'June' => 'juin', + 'July' => 'juillet', + 'August' => 'août', + 'September' => 'septembre', + 'October' => 'octobre', + 'November' => 'novembre', + 'December' => 'décembre', + + 'Jan' => 'janv.', + 'Feb' => 'févr.', + 'Mar' => 'mars', + 'Apr' => 'avr.', + 'May_short' => 'mai', // Short representation of "May". May_short used because in English the short and long date are the same for May. + 'Jun' => 'juin', + 'Jul' => 'juil.', + 'Aug' => 'août', + 'Sep' => 'sept.', + 'Oct' => 'oct.', + 'Nov' => 'nov.', + 'Dec' => 'déc.', + ], + + // Timezones can be translated. We use this for the Etc/GMT timezones here, + // because they are named invers to their offset. + 'timezones' => [ + 'UTC' => 'UTC', + 'UTC_OFFSET' => 'UTC%1$s', + 'UTC_OFFSET_CURRENT' => 'UTC%1$s – %2$s', + + 'Etc/GMT-12' => 'UTC+12', + 'Etc/GMT-11' => 'UTC+11', + 'Etc/GMT-10' => 'UTC+10', + 'Etc/GMT-9' => 'UTC+9', + 'Etc/GMT-8' => 'UTC+8', + 'Etc/GMT-7' => 'UTC+7', + 'Etc/GMT-6' => 'UTC+6', + 'Etc/GMT-5' => 'UTC+5', + 'Etc/GMT-4' => 'UTC+4', + 'Etc/GMT-3' => 'UTC+3', + 'Etc/GMT-2' => 'UTC+2', + 'Etc/GMT-1' => 'UTC+1', + 'Etc/GMT+1' => 'UTC-1', + 'Etc/GMT+2' => 'UTC-2', + 'Etc/GMT+3' => 'UTC-3', + 'Etc/GMT+4' => 'UTC-4', + 'Etc/GMT+5' => 'UTC-5', + 'Etc/GMT+6' => 'UTC-6', + 'Etc/GMT+7' => 'UTC-7', + 'Etc/GMT+8' => 'UTC-8', + 'Etc/GMT+9' => 'UTC-9', + 'Etc/GMT+10' => 'UTC-10', + 'Etc/GMT+11' => 'UTC-11', + 'Etc/GMT+12' => 'UTC-12', + + 'Africa/Abidjan' => 'Afrique/Abidjan', + 'Africa/Accra' => 'Afrique/Accra', + 'Africa/Addis_Ababa' => 'Afrique/Addis-Abeba', + 'Africa/Algiers' => 'Afrique/Alger', + 'Africa/Asmara' => 'Afrique/Asmara', + 'Africa/Bamako' => 'Afrique/Bamako', + 'Africa/Bangui' => 'Afrique/Bangui', + 'Africa/Banjul' => 'Afrique/Banjul', + 'Africa/Bissau' => 'Afrique/Bissau', + 'Africa/Blantyre' => 'Afrique/Blantyre', + 'Africa/Brazzaville' => 'Afrique/Brazzaville', + 'Africa/Bujumbura' => 'Afrique/Bujumbura', + 'Africa/Cairo' => 'Afrique/Le Caire', + 'Africa/Casablanca' => 'Afrique/Casablanca', + 'Africa/Ceuta' => 'Afrique/Ceuta', + 'Africa/Conakry' => 'Afrique/Conakry', + 'Africa/Dakar' => 'Afrique/Dakar', + 'Africa/Dar_es_Salaam' => 'Afrique/Dar es Salam', + 'Africa/Djibouti' => 'Afrique/Djibouti', + 'Africa/Douala' => 'Afrique/Douala', + 'Africa/El_Aaiun' => 'Afrique/Laâyoune', + 'Africa/Freetown' => 'Afrique/Freetown', + 'Africa/Gaborone' => 'Afrique/Gaborone', + 'Africa/Harare' => 'Afrique/Harare', + 'Africa/Johannesburg' => 'Afrique/Johannesburg', + 'Africa/Juba' => 'Afrique/Juba', + 'Africa/Kampala' => 'Afrique/Kampala', + 'Africa/Khartoum' => 'Afrique/Khartoum', + 'Africa/Kigali' => 'Afrique/Kigali', + 'Africa/Kinshasa' => 'Afrique/Kinshasa', + 'Africa/Lagos' => 'Afrique/Lagos', + 'Africa/Libreville' => 'Afrique/Libreville', + 'Africa/Lome' => 'Afrique/Lomé', + 'Africa/Luanda' => 'Afrique/Luanda', + 'Africa/Lubumbashi' => 'Afrique/Lubumbashi', + 'Africa/Lusaka' => 'Afrique/Lusaka', + 'Africa/Malabo' => 'Afrique/Malabo', + 'Africa/Maputo' => 'Afrique/Maputo', + 'Africa/Maseru' => 'Afrique/Maseru', + 'Africa/Mbabane' => 'Afrique/Mbabane', + 'Africa/Mogadishu' => 'Afrique/Mogadiscio', + 'Africa/Monrovia' => 'Afrique/Monrovia', + 'Africa/Nairobi' => 'Afrique/Nairobi', + 'Africa/Ndjamena' => 'Afrique/N’Djamena', + 'Africa/Niamey' => 'Afrique/Niamey', + 'Africa/Nouakchott' => 'Afrique/Nouakchott', + 'Africa/Ouagadougou' => 'Afrique/Ouagadougou', + 'Africa/Porto-Novo' => 'Afrique/Porto-Novo', + 'Africa/Sao_Tome' => 'Afrique/Sao Tomé-et-Principe', + 'Africa/Tripoli' => 'Afrique/Tripoli', + 'Africa/Tunis' => 'Afrique/Tunis', + 'Africa/Windhoek' => 'Afrique/Windhoek', + + 'America/Adak' => 'Amérique/Adak', + 'America/Anchorage' => 'Amérique/Anchorage', + 'America/Anguilla' => 'Amérique/Anguilla', + 'America/Antigua' => 'Amérique/Antigua-et-Barbuda', + 'America/Araguaina' => 'Amérique/Araguaína', + + 'America/Argentina/Buenos_Aires' => 'Amérique/Argentine/Buenos Aires', + 'America/Argentina/Catamarca' => 'Amérique/Argentine/Catamarca', + 'America/Argentina/Cordoba' => 'Amérique/Argentine/Córdoba', + 'America/Argentina/Jujuy' => 'Amérique/Argentine/Jujuy', + 'America/Argentina/La_Rioja' => 'Amérique/Argentine/La Rioja', + 'America/Argentina/Mendoza' => 'Amérique/Argentine/Mendoza', + 'America/Argentina/Rio_Gallegos' => 'Amérique/Argentine/Río Gallegos', + 'America/Argentina/Salta' => 'Amérique/Argentine/Salta', + 'America/Argentina/San_Juan' => 'Amérique/Argentine/San Juan', + 'America/Argentina/San_Luis' => 'Amérique/Argentine/San Luis', + 'America/Argentina/Tucuman' => 'Amérique/Argentine/Tucumán', + 'America/Argentina/Ushuaia' => 'Amérique/Argentine/Ushuaïa', + + 'America/Aruba' => 'Amérique/Aruba', + 'America/Asuncion' => 'Amérique/Asuncion', + 'America/Atikokan' => 'Amérique/Atikokan', + 'America/Bahia' => 'Amérique/Bahia', + 'America/Bahia_Banderas' => 'Amérique/Bahía de Banderas', + 'America/Barbados' => 'Amérique/Barbade', + 'America/Belem' => 'Amérique/Belem', + 'America/Belize' => 'Amérique/Belize', + 'America/Blanc-Sablon' => 'Amérique/Blanc-Sablon', + 'America/Boa_Vista' => 'Amérique/Boa Vista', + 'America/Bogota' => 'Amérique/Bogota', + 'America/Boise' => 'Amérique/Boise', + 'America/Cambridge_Bay' => 'Amérique/Ikaluktutiak', + 'America/Campo_Grande' => 'Amérique/Campo Grande', + 'America/Cancun' => 'Amérique/Cancún', + 'America/Caracas' => 'Amérique/Caracas', + 'America/Cayenne' => 'Amérique/Cayenne', + 'America/Cayman' => 'Amérique/Îles Caïmans', + 'America/Chicago' => 'Amérique/Chicago', + 'America/Chihuahua' => 'Amérique/Chihuahua', + 'America/Costa_Rica' => 'Amérique/Costa Rica', + 'America/Creston' => 'Amérique/Creston', + 'America/Cuiaba' => 'Amérique/Cuiabá', + 'America/Curacao' => 'Amérique/Curaçao', + 'America/Danmarkshavn' => 'Amérique/Danmarkshavn', + 'America/Dawson' => 'Amérique/Dawson', + 'America/Dawson_Creek' => 'Amérique/Dawson Creek', + 'America/Denver' => 'Amérique/Denver', + 'America/Detroit' => 'Amérique/Détroit', + 'America/Dominica' => 'Amérique/Dominique', + 'America/Edmonton' => 'Amérique/Edmonton', + 'America/Eirunepe' => 'Amérique/Eirunepé', + 'America/El_Salvador' => 'Amérique/Salvador', + 'America/Fortaleza' => 'Amérique/Fortaleza', + 'America/Glace_Bay' => 'Amérique/Glace Bay', + 'America/Godthab' => 'Amérique/Nuuk', + 'America/Goose_Bay' => 'Amérique/Happy Valley-Goose Bay', + 'America/Grand_Turk' => 'Amérique/Grand Turk', + 'America/Grenada' => 'Amérique/Grenade', + 'America/Guadeloupe' => 'Amérique/Guadeloupe', + 'America/Guatemala' => 'Amérique/Guatemala', + 'America/Guayaquil' => 'Amérique/Guayaquil', + 'America/Guyana' => 'Amérique/Guyana', + 'America/Halifax' => 'Amérique/Halifax', + 'America/Havana' => 'Amérique/La Havane', + 'America/Hermosillo' => 'Amérique/Hermosillo', + 'America/Indiana/Indianapolis' => 'Amérique/Indiana/Indianapolis', + 'America/Indiana/Knox' => 'Amérique/Indiana/Knox', + 'America/Indiana/Marengo' => 'Amérique/Indiana/Marengo', + 'America/Indiana/Petersburg' => 'Amérique/Indiana/Petersburg', + 'America/Indiana/Tell_City' => 'Amérique/Indiana/Tell City', + 'America/Indiana/Vevay' => 'Amérique/Indiana/Vevay', + 'America/Indiana/Vincennes' => 'Amérique/Indiana/Vincennes', + 'America/Indiana/Winamac' => 'Amérique/Indiana/Winamac', + 'America/Inuvik' => 'Amérique/Inuvik', + 'America/Iqaluit' => 'Amérique/Iqaluit', + 'America/Jamaica' => 'Amérique/Jamaïque', + 'America/Juneau' => 'Amérique/Juneau', + 'America/Kentucky/Louisville' => 'Amérique/Kentucky/Louisville', + 'America/Kentucky/Monticello' => 'Amérique/Kentucky/Monticello', + 'America/Kralendijk' => 'Amérique/Kralendijk', + 'America/La_Paz' => 'Amérique/La Paz', + 'America/Lima' => 'Amérique/Lima', + 'America/Los_Angeles' => 'Amérique/Los Angeles', + 'America/Lower_Princes' => 'Amérique/Lower Prince’s Quarter', + 'America/Maceio' => 'Amérique/Maceió', + 'America/Managua' => 'Amérique/Managua', + 'America/Manaus' => 'Amérique/Manaus', + 'America/Marigot' => 'Amérique/Marigot', + 'America/Martinique' => 'Amérique/Martinique', + 'America/Matamoros' => 'Amérique/Matamoros', + 'America/Mazatlan' => 'Amérique/Mazatlán', + 'America/Menominee' => 'Amérique/Menominee', + 'America/Merida' => 'Amérique/Mérida', + 'America/Metlakatla' => 'Amérique/Metlakatla', + 'America/Mexico_City' => 'Amérique/Mexico', + 'America/Miquelon' => 'Amérique/Saint-Pierre-et-Miquelon', + 'America/Moncton' => 'Amérique/Moncton', + 'America/Monterrey' => 'Amérique/Monterrey', + 'America/Montevideo' => 'Amérique/Montevideo', + 'America/Montreal' => 'Amérique/Montréal', + 'America/Montserrat' => 'Amérique/Montserrat', + 'America/Nassau' => 'Amérique/Nassau', + 'America/New_York' => 'Amérique/New York', + 'America/Nipigon' => 'Amérique/Nipigon', + 'America/Nome' => 'Amérique/Nome', + 'America/Noronha' => 'Amérique/Fernando de Noronha', + 'America/North_Dakota/Beulah' => 'Amérique/Dakota du Nord/Beulah', + 'America/North_Dakota/Center' => 'Amérique/Dakota du Nord/Center', + 'America/North_Dakota/New_Salem' => 'Amérique/Dakota du Nord/New Salem', + 'America/Ojinaga' => 'Amérique/Ojinaga', + 'America/Panama' => 'Amérique/Panama', + 'America/Pangnirtung' => 'Amérique/Pangnirtung', + 'America/Paramaribo' => 'Amérique/Paramaribo', + 'America/Phoenix' => 'Amérique/Phoenix', + 'America/Port-au-Prince' => 'Amérique/Port-au-Prince', + 'America/Port_of_Spain' => 'Amérique/Port-d’Espagne', + 'America/Porto_Velho' => 'Amérique/Porto Velho', + 'America/Puerto_Rico' => 'Amérique/Porto Rico', + 'America/Rainy_River' => 'Amérique/Rainy River', + 'America/Rankin_Inlet' => 'Amérique/Rankin Inlet', + 'America/Recife' => 'Amérique/Recife', + 'America/Regina' => 'Amérique/Regina', + 'America/Resolute' => 'Amérique/Resolute', + 'America/Rio_Branco' => 'Amérique/Rio Branco', + 'America/Santa_Isabel' => 'Amérique/Santa Isabel', + 'America/Santarem' => 'Amérique/Santarém', + 'America/Santiago' => 'Amérique/Santiago', + 'America/Santo_Domingo' => 'Amérique/Saint-Domingue', + 'America/Sao_Paulo' => 'Amérique/São Paulo', + 'America/Scoresbysund' => 'Amérique/Scoresby Sund', + 'America/Shiprock' => 'Amérique/Shiprock', + 'America/Sitka' => 'Amérique/Sitka', + 'America/St_Barthelemy' => 'Amérique/Saint-Barthélemy', + 'America/St_Johns' => 'Amérique/Saint John’s', + 'America/St_Kitts' => 'Amérique/Saint-Christophe', + 'America/St_Lucia' => 'Amérique/Sainte-Lucie', + 'America/St_Thomas' => 'Amérique/Saint Thomas', + 'America/St_Vincent' => 'Amérique/Saint-Vincent', + 'America/Swift_Current' => 'Amérique/Swift Current', + 'America/Tegucigalpa' => 'Amérique/Tegucigalpa', + 'America/Thule' => 'Amérique/Thulé', + 'America/Thunder_Bay' => 'Amérique/Thunder Bay', + 'America/Tijuana' => 'Amérique/Tijuana', + 'America/Toronto' => 'Amérique/Toronto', + 'America/Tortola' => 'Amérique/Tortola', + 'America/Vancouver' => 'Amérique/Vancouver', + 'America/Whitehorse' => 'Amérique/Whitehorse', + 'America/Winnipeg' => 'Amérique/Winnipeg', + 'America/Yakutat' => 'Amérique/Yakutat', + 'America/Yellowknife' => 'Amérique/Yellowknife', + + 'Antarctica/Casey' => 'Antarctique/Casey', + 'Antarctica/Davis' => 'Antarctique/Davis', + 'Antarctica/DumontDUrville' => 'Antarctique/Dumont d’Urville', + 'Antarctica/Macquarie' => 'Antarctique/Île Macquarie', + 'Antarctica/Mawson' => 'Antarctique/Mawson', + 'Antarctica/McMurdo' => 'Antarctique/McMurdo', + 'Antarctica/Palmer' => 'Antarctique/Palmer', + 'Antarctica/Rothera' => 'Antarctique/Rothera', + 'Antarctica/South_Pole' => 'Antarctique/Amundsen-Scott', + 'Antarctica/Syowa' => 'Antarctique/Syowa', + 'Antarctica/Vostok' => 'Antarctique/Vostok', + + 'Arctic/Longyearbyen' => 'Arctique/Longyearbyen', + + 'Asia/Aden' => 'Asie/Aden', + 'Asia/Almaty' => 'Asie/Almaty', + 'Asia/Amman' => 'Asie/Amman', + 'Asia/Anadyr' => 'Asie/Anadyr', + 'Asia/Aqtau' => 'Asie/Aktaou', + 'Asia/Aqtobe' => 'Asie/Aktioubé', + 'Asia/Ashgabat' => 'Asie/Achgabat', + 'Asia/Baghdad' => 'Asie/Bagdad', + 'Asia/Bahrain' => 'Asie/Bahreïn', + 'Asia/Baku' => 'Asie/Bakou', + 'Asia/Bangkok' => 'Asie/Bangkok', + 'Asia/Beirut' => 'Asie/Beyrouth', + 'Asia/Bishkek' => 'Asie/Bichkek', + 'Asia/Brunei' => 'Asie/Brunei', + 'Asia/Choibalsan' => 'Asie/Choybalsan', + 'Asia/Chongqing' => 'Asie/Chongqing', + 'Asia/Colombo' => 'Asie/Colombo', + 'Asia/Damascus' => 'Asie/Damas', + 'Asia/Dhaka' => 'Asie/Dacca', + 'Asia/Dili' => 'Asie/Dili', + 'Asia/Dubai' => 'Asie/Dubaï', + 'Asia/Dushanbe' => 'Asie/Douchanbé', + 'Asia/Gaza' => 'Asie/Gaza', + 'Asia/Harbin' => 'Asie/Harbin', + 'Asia/Hebron' => 'Asie/Hébron', + 'Asia/Ho_Chi_Minh' => 'Asie/Hô-Chi-Minh-Ville', + 'Asia/Hong_Kong' => 'Asie/Hong Kong', + 'Asia/Hovd' => 'Asie/Hovd', + 'Asia/Irkutsk' => 'Asie/Irkoutsk', + 'Asia/Jakarta' => 'Asie/Jakarta', + 'Asia/Jayapura' => 'Asie/Jayapura', + 'Asia/Jerusalem' => 'Asie/Jérusalem', + 'Asia/Kabul' => 'Asie/Kaboul', + 'Asia/Kamchatka' => 'Asie/Kamtchatka', + 'Asia/Karachi' => 'Asie/Karachi', + 'Asia/Kashgar' => 'Asie/Kachgar', + 'Asia/Kathmandu' => 'Asie/Katmandou', + 'Asia/Khandyga' => 'Asie/Khandyga', + 'Asia/Kolkata' => 'Asie/Calcutta', + 'Asia/Krasnoyarsk' => 'Asie/Krasnoïarsk', + 'Asia/Kuala_Lumpur' => 'Asie/Kuala Lumpur', + 'Asia/Kuching' => 'Asie/Kuching', + 'Asia/Kuwait' => 'Asie/Koweït', + 'Asia/Macau' => 'Asie/Macao', + 'Asia/Magadan' => 'Asie/Magadan', + 'Asia/Makassar' => 'Asie/Makassar', + 'Asia/Manila' => 'Asie/Manille', + 'Asia/Muscat' => 'Asie/Mascate', + 'Asia/Nicosia' => 'Asie/Nicosia', + 'Asia/Novokuznetsk' => 'Asie/Novokouznetsk', + 'Asia/Novosibirsk' => 'Asie/Novossibirsk', + 'Asia/Omsk' => 'Asie/Omsk', + 'Asia/Oral' => 'Asie/Orel', + 'Asia/Phnom_Penh' => 'Asie/Phnom Penh', + 'Asia/Pontianak' => 'Asie/Pontianak', + 'Asia/Pyongyang' => 'Asie/Pyongyang', + 'Asia/Qatar' => 'Asie/Qatar', + 'Asia/Qyzylorda' => 'Asie/Kyzylorda', + 'Asia/Rangoon' => 'Asie/Rangoun', + 'Asia/Riyadh' => 'Asie/Riyad', + 'Asia/Sakhalin' => 'Asie/Sakhaline', + 'Asia/Samarkand' => 'Asie/Samarcande', + 'Asia/Seoul' => 'Asie/Séoul', + 'Asia/Shanghai' => 'Asie/Shanghai', + 'Asia/Singapore' => 'Asie/Singapour', + 'Asia/Taipei' => 'Asie/Taipei', + 'Asia/Tashkent' => 'Asie/Tachkent', + 'Asia/Tbilisi' => 'Asie/Tbilissi', + 'Asia/Tehran' => 'Asie/Téhéran', + 'Asia/Thimphu' => 'Asie/Thimphou', + 'Asia/Tokyo' => 'Asie/Tokyo', + 'Asia/Ulaanbaatar' => 'Asie/Oulan-Bator', + 'Asia/Urumqi' => 'Asie/Ürümqi', + 'Asia/Ust-Nera' => 'Asie/Ust-Nera', + 'Asia/Vientiane' => 'Asie/Vientiane', + 'Asia/Vladivostok' => 'Asie/Vladivostok', + 'Asia/Yakutsk' => 'Asie/Iakoutsk', + 'Asia/Yekaterinburg' => 'Asie/Iekaterinbourg', + 'Asia/Yerevan' => 'Asie/Erevan', + + 'Atlantic/Azores' => 'Océan Atlantique/Açores', + 'Atlantic/Bermuda' => 'Océan Atlantique/Bermudes', + 'Atlantic/Canary' => 'Océan Atlantique/Îles Canaries', + 'Atlantic/Cape_Verde' => 'Océan Atlantique/Cap-Vert', + 'Atlantic/Faroe' => 'Océan Atlantique/Îles Féroé', + 'Atlantic/Madeira' => 'Océan Atlantique/Madère', + 'Atlantic/Reykjavik' => 'Océan Atlantique/Reykjavik', + 'Atlantic/South_Georgia' => 'Océan Atlantique/Géorgie du Sud-et-les Îles Sandwich du Sud', + 'Atlantic/St_Helena' => 'Océan Atlantique/Sainte-Hélène', + 'Atlantic/Stanley' => 'Océan Atlantique/Port Stanley', + + 'Australia/Adelaide' => 'Australie/Adélaïde', + 'Australia/Brisbane' => 'Australie/Brisbane', + 'Australia/Broken_Hill' => 'Australie/Broken Hill', + 'Australia/Currie' => 'Australie/Currie', + 'Australia/Darwin' => 'Australie/Darwin', + 'Australia/Eucla' => 'Australie/Eucla', + 'Australia/Hobart' => 'Australie/Hobart', + 'Australia/Lindeman' => 'Australie/Îles Whitsunday', + 'Australia/Lord_Howe' => 'Australie/Île Lord Howe', + 'Australia/Melbourne' => 'Australie/Melbourne', + 'Australia/Perth' => 'Australie/Perth', + 'Australia/Sydney' => 'Australie/Sydney', + + 'Europe/Amsterdam' => 'Europe/Amsterdam', + 'Europe/Andorra' => 'Europe/Andorre', + 'Europe/Athens' => 'Europe/Athènes', + 'Europe/Belgrade' => 'Europe/Belgrade', + 'Europe/Berlin' => 'Europe/Berlin', + 'Europe/Bratislava' => 'Europe/Bratislava', + 'Europe/Brussels' => 'Europe/Bruxelles', + 'Europe/Bucharest' => 'Europe/Bucarest', + 'Europe/Budapest' => 'Europe/Budapest', + 'Europe/Busingen' => 'Europe/Büsingen am Hochrhein', + 'Europe/Chisinau' => 'Europe/Chișinău', + 'Europe/Copenhagen' => 'Europe/Copenhague', + 'Europe/Dublin' => 'Europe/Dublin', + 'Europe/Gibraltar' => 'Europe/Gibraltar', + 'Europe/Guernsey' => 'Europe/Guernesey', + 'Europe/Helsinki' => 'Europe/Helsinki', + 'Europe/Isle_of_Man' => 'Europe/Île de Man', + 'Europe/Istanbul' => 'Europe/Istanbul', + 'Europe/Jersey' => 'Europe/Jersey', + 'Europe/Kaliningrad' => 'Europe/Kaliningrad', + 'Europe/Kiev' => 'Europe/Kiev', + 'Europe/Lisbon' => 'Europe/Lisbonne', + 'Europe/Ljubljana' => 'Europe/Ljubljana', + 'Europe/London' => 'Europe/Londres', + 'Europe/Luxembourg' => 'Europe/Luxembourg', + 'Europe/Madrid' => 'Europe/Madrid', + 'Europe/Malta' => 'Europe/Malte', + 'Europe/Mariehamn' => 'Europe/Mariehamn', + 'Europe/Minsk' => 'Europe/Minsk', + 'Europe/Monaco' => 'Europe/Monaco', + 'Europe/Moscow' => 'Europe/Moscou', + 'Europe/Oslo' => 'Europe/Oslo', + 'Europe/Paris' => 'Europe/Paris', + 'Europe/Podgorica' => 'Europe/Podgorica', + 'Europe/Prague' => 'Europe/Prague', + 'Europe/Riga' => 'Europe/Riga', + 'Europe/Rome' => 'Europe/Rome', + 'Europe/Samara' => 'Europe/Samara', + 'Europe/San_Marino' => 'Europe/Saint-Marin', + 'Europe/Sarajevo' => 'Europe/Sarajevo', + 'Europe/Simferopol' => 'Europe/Simferopol', + 'Europe/Skopje' => 'Europe/Skopje', + 'Europe/Sofia' => 'Europe/Sofia', + 'Europe/Stockholm' => 'Europe/Stockholm', + 'Europe/Tallinn' => 'Europe/Tallinn', + 'Europe/Tirane' => 'Europe/Tirana', + 'Europe/Uzhgorod' => 'Europe/Oujhorod', + 'Europe/Vaduz' => 'Europe/Vaduz', + 'Europe/Vatican' => 'Europe/Vatican', + 'Europe/Vienna' => 'Europe/Vienne', + 'Europe/Vilnius' => 'Europe/Vilnius', + 'Europe/Volgograd' => 'Europe/Volgograd', + 'Europe/Warsaw' => 'Europe/Varsovie', + 'Europe/Zagreb' => 'Europe/Zagreb', + 'Europe/Zaporozhye' => 'Europe/Zaporijia', + 'Europe/Zurich' => 'Europe/Zurich', + + 'Indian/Antananarivo' => 'Océan Indien/Antananarivo', + 'Indian/Chagos' => 'Océan Indien/Archipel des Chagos', + 'Indian/Christmas' => 'Océan Indien/Île Christmas', + 'Indian/Cocos' => 'Océan Indien/Îles Cocos', + 'Indian/Comoro' => 'Océan Indien/Comores', + 'Indian/Kerguelen' => 'Océan Indien/Îles Kerguelen', + 'Indian/Mahe' => 'Océan Indien/Mahé', + 'Indian/Maldives' => 'Océan Indien/Maldives', + 'Indian/Mauritius' => 'Océan Indien/Maurice', + 'Indian/Mayotte' => 'Océan Indien/Mayotte', + 'Indian/Reunion' => 'Océan Indien/La Réunion', + + 'Pacific/Apia' => 'Océan Pacifique/Apia', + 'Pacific/Auckland' => 'Océan Pacifique/Auckland', + 'Pacific/Chatham' => 'Océan Pacifique/Chatham', + 'Pacific/Chuuk' => 'Océan Pacifique/Chuuk', + 'Pacific/Easter' => 'Océan Pacifique/Île de Pâques', + 'Pacific/Efate' => 'Océan Pacifique/Éfaté', + 'Pacific/Enderbury' => 'Océan Pacifique/Enderbury', + 'Pacific/Fakaofo' => 'Océan Pacifique/Fakaofo', + 'Pacific/Fiji' => 'Océan Pacifique/Fidji', + 'Pacific/Funafuti' => 'Océan Pacifique/Funafuti', + 'Pacific/Galapagos' => 'Océan Pacifique/Îles Galápagos', + 'Pacific/Gambier' => 'Océan Pacifique/Îles Gambier', + 'Pacific/Guadalcanal' => 'Océan Pacifique/Guadalcanal', + 'Pacific/Guam' => 'Océan Pacifique/Guam', + 'Pacific/Honolulu' => 'Océan Pacifique/Honolulu', + 'Pacific/Johnston' => 'Océan Pacifique/Atoll Johnston', + 'Pacific/Kiritimati' => 'Océan Pacifique/Île Christmas', + 'Pacific/Kosrae' => 'Océan Pacifique/Kosrae', + 'Pacific/Kwajalein' => 'Océan Pacifique/Kwajalein', + 'Pacific/Majuro' => 'Océan Pacifique/Majuro', + 'Pacific/Marquesas' => 'Océan Pacifique/Îles Marquises', + 'Pacific/Midway' => 'Océan Pacifique/Îles Midway', + 'Pacific/Nauru' => 'Océan Pacifique/Nauru', + 'Pacific/Niue' => 'Océan Pacifique/Niue', + 'Pacific/Norfolk' => 'Océan Pacifique/Norfolk', + 'Pacific/Noumea' => 'Océan Pacifique/Nouméa', + 'Pacific/Pago_Pago' => 'Océan Pacifique/Pago Pago', + 'Pacific/Palau' => 'Océan Pacifique/Palaos', + 'Pacific/Pitcairn' => 'Océan Pacifique/Îles Pitcairn', + 'Pacific/Pohnpei' => 'Océan Pacifique/Pohnpei', + 'Pacific/Port_Moresby' => 'Océan Pacifique/Port Moresby', + 'Pacific/Rarotonga' => 'Océan Pacifique/Rarotonga', + 'Pacific/Saipan' => 'Océan Pacifique/Saipan', + 'Pacific/Tahiti' => 'Océan Pacifique/Tahiti', + 'Pacific/Tarawa' => 'Océan Pacifique/Tarawa', + 'Pacific/Tongatapu' => 'Océan Pacifique/Tongatapu', + 'Pacific/Wake' => 'Océan Pacifique/Wake', + 'Pacific/Wallis' => 'Océan Pacifique/Wallis-et-Futuna', + ], + + // The value is only an example and will get replaced by the current time on view + 'dateformats' => [ + 'd M Y, H:i' => '01 janv. 2007, 13:37', + 'd M Y H:i' => '01 janv. 2007 13:37', + 'M jS, \'y, H:i' => 'janv. 1er, ’07, 13:37', + 'D M d, Y g:i a' => 'lun. janv. 01, 2007 1:37 pm', + 'F jS, Y, g:i a' => 'janvier 1er, 2007, 1:37 pm', + '|d M Y|, H:i' => 'Aujourd’hui, 13:37 / 01 janv. 2007, 13:37', + '|F jS, Y|, g:i a' => 'Aujourd’hui, 1:37 pm / janv. 1er, 2007, 1:37 pm', + ], + + // The default dateformat which will be used on new installs in this language + // Translators should change this if a the usual date format is different + 'default_dateformat' => 'd F Y, H:i', // 01 January 2007, 13:37 +]); diff --git a/language/fr/email/admin_activate.txt b/language/fr/email/admin_activate.txt new file mode 100644 index 0000000..051a0b5 --- /dev/null +++ b/language/fr/email/admin_activate.txt @@ -0,0 +1,13 @@ +Subject: Activation du compte d’un utilisateur + +Bonjour, + +Le compte de « {USERNAME} » a été désactivé ou nouvellement créé. Vous devriez, si nécessaire, gérer et vérifier les informations concernant cet utilisateur. + +Si vous souhaitez consulter le profil de cet utilisateur, veuillez cliquer sur le lien suivant : +{U_USER_DETAILS} + +Si vous souhaitez activer le compte de cet utilisateur, veuillez cliquer sur le lien suivant : +{U_ACTIVATE} + +{EMAIL_SIG} diff --git a/language/fr/email/admin_send_email.txt b/language/fr/email/admin_send_email.txt new file mode 100644 index 0000000..a153c18 --- /dev/null +++ b/language/fr/email/admin_send_email.txt @@ -0,0 +1,13 @@ + +Ce qui suit est un courriel envoyé par un administrateur de « {SITENAME} ». Si ce message est un pourriel ou qu’il contient des commentaires que vous trouvez déplacés, veuillez contacter un administrateur du forum à l’adresse suivante : + +{CONTACT_EMAIL} + +Incluez ce courriel en entier (particulièrement les en-têtes). + +Message qui vous a été envoyé : +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{MESSAGE} + +{EMAIL_SIG} diff --git a/language/fr/email/admin_welcome_activated.txt b/language/fr/email/admin_welcome_activated.txt new file mode 100644 index 0000000..682b92e --- /dev/null +++ b/language/fr/email/admin_welcome_activated.txt @@ -0,0 +1,9 @@ +Subject: Votre compte a été activé + +Bonjour {USERNAME}, + +Votre compte sur « {SITENAME} » a été activé par un administrateur, vous pouvez à présent vous connecter. + +Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +{EMAIL_SIG} diff --git a/language/fr/email/admin_welcome_inactive.txt b/language/fr/email/admin_welcome_inactive.txt new file mode 100644 index 0000000..a7cfb39 --- /dev/null +++ b/language/fr/email/admin_welcome_inactive.txt @@ -0,0 +1,19 @@ +Subject: Bienvenue sur « {SITENAME} » + +{WELCOME_MSG} + +Veuillez conserver ce courriel dans vos archives. Les informations concernant votre compte sont les suivantes : + +---------------------------- +Nom d’utilisateur : {USERNAME} + +Lien du forum : {U_BOARD} +---------------------------- + +Votre compte est actuellement inactif et devra être approuvé par un administrateur avant que vous ne puissiez vous connecter. Un autre courriel sera envoyé lorsque cela aura été effectué. + +Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +Nous vous remercions de votre inscription. + +{EMAIL_SIG} diff --git a/language/fr/email/bookmark.txt b/language/fr/email/bookmark.txt new file mode 100644 index 0000000..40a62a2 --- /dev/null +++ b/language/fr/email/bookmark.txt @@ -0,0 +1,20 @@ +Subject: Notification de réponse à un sujet – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le sujet que vous avez ajouté aux favoris, « {TOPIC_TITLE} » sur « {SITENAME} », a reçu une réponse depuis votre dernière visite. Vous pouvez utiliser le lien suivant afin de consulter les réponses qui ont été publiées, aucune nouvelle notification ne vous sera envoyée jusqu’à ce que vous visitiez le sujet. + +Si vous souhaitez consulter le nouveau message publié depuis votre dernière visite, veuillez cliquer sur le lien suivant : +{U_NEWEST_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +Si vous ne souhaitez plus recevoir de notifications lorsqu’un utilisateur répond à un sujet que vous avez ajouté aux favoris, vous pouvez configurer les paramètres de notification sur le lien suivant : + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/language/fr/email/contact_admin.txt b/language/fr/email/contact_admin.txt new file mode 100644 index 0000000..9dbf573 --- /dev/null +++ b/language/fr/email/contact_admin.txt @@ -0,0 +1,23 @@ + +Bonjour {TO_USERNAME}, + +Ce qui suit est un courriel qui vous a été envoyé depuis la page de contact d’administration sur « {SITENAME} ». + + +Le message a été envoyé d’un compte du forum. +Nom d’utilisateur : {FROM_USERNAME} +Adresse de courriel : {FROM_EMAIL_ADDRESS} +Adresse IP : {FROM_IP_ADDRESS} +Profil : {U_FROM_PROFILE} + +Le message a été envoyé par un invité qui a spécifié les informations de contact suivantes : +Nom : {FROM_USERNAME} +Adresse de courriel : {FROM_EMAIL_ADDRESS} +Adresse IP : {FROM_IP_ADDRESS} + + + +Le message qui vous a été envoyé est le suivant : +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{MESSAGE} diff --git a/language/fr/email/coppa_resend_inactive.txt b/language/fr/email/coppa_resend_inactive.txt new file mode 100644 index 0000000..0fbe6ae --- /dev/null +++ b/language/fr/email/coppa_resend_inactive.txt @@ -0,0 +1,42 @@ +Subject: Bienvenue sur « {SITENAME} » + +{WELCOME_MSG} + +En accord avec la COPPA, votre compte est actuellement inactif. + +Veuillez imprimer ce message et le faire signer et dater par vos parents ou vos tuteurs. Puis, faxez-le à : + +{FAX_INFO} + +Ou envoyez-le par courriel à : + +{MAIL_INFO} + +------------------------------ COUPEZ ICI ------------------------------ +Permission de participer à « {SITENAME} » – {U_BOARD} + +Nom d’utilisateur : {USERNAME} +Adresse de courriel : {EMAIL_ADDRESS} + +J’AI VERIFIÉ L’INFORMATION RENSEIGNÉE PAR MON ENFANT ET DONNE LA PERMISSION À « {SITENAME} » DE STOCKER CETTE INFORMATION. +JE CONÇOIS QUE CETTE INFORMATION PEUT ÊTRE MODIFIÉE A TOUT MOMENT EN ENTRANT UN MOT DE PASSE. +JE SAIS QUE JE PEUX DEMANDER LE RETRAIT DE CETTE INFORMATION SUR « {SITENAME} » À TOUT MOMENT. + + +Parent ou tuteur +(inscrivez votre nom ici) : _____________________ + +(signez ici) : __________________ + +Date : _______________ + +------------------------------ COUPEZ ICI ------------------------------ + + +Une fois qu’un administrateur aura reçu le texte ci-dessus par courriel, télécopie ou courrier postal, votre compte sera activé. + +Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +Nous vous remercions de votre inscription. + +{EMAIL_SIG} diff --git a/language/fr/email/coppa_welcome_inactive.txt b/language/fr/email/coppa_welcome_inactive.txt new file mode 100644 index 0000000..0fbe6ae --- /dev/null +++ b/language/fr/email/coppa_welcome_inactive.txt @@ -0,0 +1,42 @@ +Subject: Bienvenue sur « {SITENAME} » + +{WELCOME_MSG} + +En accord avec la COPPA, votre compte est actuellement inactif. + +Veuillez imprimer ce message et le faire signer et dater par vos parents ou vos tuteurs. Puis, faxez-le à : + +{FAX_INFO} + +Ou envoyez-le par courriel à : + +{MAIL_INFO} + +------------------------------ COUPEZ ICI ------------------------------ +Permission de participer à « {SITENAME} » – {U_BOARD} + +Nom d’utilisateur : {USERNAME} +Adresse de courriel : {EMAIL_ADDRESS} + +J’AI VERIFIÉ L’INFORMATION RENSEIGNÉE PAR MON ENFANT ET DONNE LA PERMISSION À « {SITENAME} » DE STOCKER CETTE INFORMATION. +JE CONÇOIS QUE CETTE INFORMATION PEUT ÊTRE MODIFIÉE A TOUT MOMENT EN ENTRANT UN MOT DE PASSE. +JE SAIS QUE JE PEUX DEMANDER LE RETRAIT DE CETTE INFORMATION SUR « {SITENAME} » À TOUT MOMENT. + + +Parent ou tuteur +(inscrivez votre nom ici) : _____________________ + +(signez ici) : __________________ + +Date : _______________ + +------------------------------ COUPEZ ICI ------------------------------ + + +Une fois qu’un administrateur aura reçu le texte ci-dessus par courriel, télécopie ou courrier postal, votre compte sera activé. + +Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +Nous vous remercions de votre inscription. + +{EMAIL_SIG} diff --git a/language/fr/email/email_notify.txt b/language/fr/email/email_notify.txt new file mode 100644 index 0000000..03ec710 --- /dev/null +++ b/language/fr/email/email_notify.txt @@ -0,0 +1,17 @@ +Subject: « {SITENAME} » – Envoi d’un courriel par un ami + +Bonjour {TO_USERNAME}, + +Ce courriel a été envoyé de « {SITENAME} » par {FROM_USERNAME}, qui a pensé que vous pourriez être intéressé par le sujet suivant : + +{TOPIC_NAME} + +Vous pouvez retrouver ce sujet en cliquant sur ce lien : + +{U_TOPIC} + +Un message de la part de {FROM_USERNAME} peut également être inclus ci-dessous. Veuillez noter que ce message n’a pas été consulté ou approuvé par les administrateurs du forum. Si vous souhaitez vous plaindre à propos de la réception de ce courriel, veuillez contacter un administrateur du forum sur {BOARD_CONTACT}. Veuillez citer les en-têtes du message lorsque vous contacterez cette adresse. + +---------- + +{MESSAGE} diff --git a/language/fr/email/forum_notify.txt b/language/fr/email/forum_notify.txt new file mode 100644 index 0000000..06bca82 --- /dev/null +++ b/language/fr/email/forum_notify.txt @@ -0,0 +1,20 @@ +Subject: Notification de publication d’un message dans un forum – « {FORUM_NAME} » +List-Unsubscribe: <{U_STOP_WATCHING_FORUM}> + +Bonjour {USERNAME}, + +Vous recevez cette notification car vous surveillez le forum « {FORUM_NAME} » sur « {SITENAME} ». Depuis votre dernière visite, un nouveau sujet, « {TOPIC_TITLE} », a été publié dans ce forum par {AUTHOR_NAME}. Vous pouvez utiliser le lien suivant afin de consulter le forum, aucune nouvelle notification ne vous sera envoyée jusqu’à ce que vous visitiez ce dernier. + +{U_NEWEST_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +Si vous ne souhaitez plus surveiller ce forum, vous pouvez cliquer sur le lien « Ne plus surveiller le forum », présent dans le forum accessible ci-dessus, ou cliquer sur le lien suivant : + +{U_STOP_WATCHING_FORUM} + +{EMAIL_SIG} diff --git a/language/fr/email/group_added.txt b/language/fr/email/group_added.txt new file mode 100644 index 0000000..697ea3b --- /dev/null +++ b/language/fr/email/group_added.txt @@ -0,0 +1,11 @@ +Subject: Vous avez été ajouté à un groupe d’utilisateurs + +Félicitations, + +Vous avez été ajouté au groupe « {GROUP_NAME} » sur « {SITENAME} ». +Cette opération a été réalisée par le responsable du groupe ou par un administrateur du forum, contactez-les pour plus d’informations. + +Vous pouvez consulter les informations de votre groupe en cliquant sur ce lien : +{U_GROUP} + +{EMAIL_SIG} diff --git a/language/fr/email/group_request.txt b/language/fr/email/group_request.txt new file mode 100644 index 0000000..9457ca5 --- /dev/null +++ b/language/fr/email/group_request.txt @@ -0,0 +1,10 @@ +Subject: Demande à rejoindre votre groupe + +Cher {USERNAME}, + +L’utilisateur « {REQUEST_USERNAME} » a demandé à rejoindre le groupe « {GROUP_NAME} » que vous modérez sur « {SITENAME} ». +Veuillez vous rendre sur le lien suivant afin d’approuver ou de refuser cette demande d’adhésion au groupe : + +{U_PENDING} + +{EMAIL_SIG} diff --git a/language/fr/email/index.htm b/language/fr/email/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/language/fr/email/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/language/fr/email/installed.txt b/language/fr/email/installed.txt new file mode 100644 index 0000000..1c1e94b --- /dev/null +++ b/language/fr/email/installed.txt @@ -0,0 +1,19 @@ +Subject: phpBB a été installé + +Félicitations, + +Vous avez correctement installé phpBB sur votre serveur. + +Ce courriel contient des informations importantes concernant votre installation et devrait être conservé précieusement. Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +---------------------------- +Nom d’utilisateur : {USERNAME} + +Lien du forum : {U_BOARD} +---------------------------- + +Des informations utiles concernant le logiciel phpBB peuvent être trouvées dans le répertoire « docs/ » de votre installation et sur notre page d’assistance – https://www.phpbb.com/support/ (en anglais) + +Afin de garantir la sécurité de votre forum, nous vous recommandons fortement de le maintenir à jour avec la dernière version stable du logiciel. Pour votre confort, une liste de diffusion est disponible à la page référencée ci-dessus. + +{EMAIL_SIG} diff --git a/language/fr/email/newtopic_notify.txt b/language/fr/email/newtopic_notify.txt new file mode 100644 index 0000000..0245589 --- /dev/null +++ b/language/fr/email/newtopic_notify.txt @@ -0,0 +1,18 @@ +Subject: Notification de publication d’un nouveau sujet – « {FORUM_NAME} » +List-Unsubscribe: <{U_STOP_WATCHING_FORUM}> + +Bonjour {USERNAME}, + +Vous recevez cette notification car vous surveillez le forum « {FORUM_NAME} » sur « {SITENAME} ». Depuis votre dernière visite, un nouveau sujet, « {TOPIC_TITLE} », a été publié dans ce forum par {AUTHOR_NAME}. Vous pouvez utiliser le lien suivant afin de consulter le forum, aucune nouvelle notification ne vous sera envoyée jusqu’à ce que vous visitiez ce dernier. + +{U_FORUM} + +Pour consulter directement ce nouveau sujet, vous pouvez cliquer sur le lien suivant : + +{U_TOPIC} + +Si vous ne souhaitez plus surveiller ce forum, vous pouvez cliquer sur le lien « Ne plus surveiller le forum », présent dans le forum accessible ci-dessus, ou cliquer sur le lien suivant : + +{U_STOP_WATCHING_FORUM} + +{EMAIL_SIG} diff --git a/language/fr/email/pm_report_closed.txt b/language/fr/email/pm_report_closed.txt new file mode 100644 index 0000000..c6526ea --- /dev/null +++ b/language/fr/email/pm_report_closed.txt @@ -0,0 +1,7 @@ +Subject: Votre rapport a été clôturé – « {PM_SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre rapport concernant le message privé « {PM_SUBJECT} » sur « {SITENAME} » a été traité par un modérateur ou un administrateur. Le rapport est à présent clôturé. Si vous souhaitez obtenir plus d’informations, veuillez contacter {CLOSER_NAME} par message privé. + +{EMAIL_SIG} diff --git a/language/fr/email/pm_report_deleted.txt b/language/fr/email/pm_report_deleted.txt new file mode 100644 index 0000000..8905747 --- /dev/null +++ b/language/fr/email/pm_report_deleted.txt @@ -0,0 +1,7 @@ +Subject: Votre rapport a été supprimé – « {PM_SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre rapport concernant le message privé « {PM_SUBJECT} » sur « {SITENAME} » a été supprimé par un modérateur ou un administrateur. + +{EMAIL_SIG} diff --git a/language/fr/email/post_approved.txt b/language/fr/email/post_approved.txt new file mode 100644 index 0000000..a426467 --- /dev/null +++ b/language/fr/email/post_approved.txt @@ -0,0 +1,13 @@ +Subject: Votre message a été approuvé – « {POST_SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre message « {POST_SUBJECT} » sur « {SITENAME} » a été approuvé par un modérateur ou un administrateur. + +Si vous souhaitez consulter le message, veuillez cliquer sur le lien suivant : +{U_VIEW_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_VIEW_TOPIC} + +{EMAIL_SIG} diff --git a/language/fr/email/post_disapproved.txt b/language/fr/email/post_disapproved.txt new file mode 100644 index 0000000..ea63180 --- /dev/null +++ b/language/fr/email/post_disapproved.txt @@ -0,0 +1,11 @@ +Subject: Votre message a été désapprouvé – « {POST_SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre message « {POST_SUBJECT} » sur « {SITENAME} » a été désapprouvé par un modérateur ou un administrateur. + +La raison suivante a été prononcée concernant la désapprobation : + +{REASON} + +{EMAIL_SIG} diff --git a/language/fr/email/post_in_queue.txt b/language/fr/email/post_in_queue.txt new file mode 100644 index 0000000..a3e5255 --- /dev/null +++ b/language/fr/email/post_in_queue.txt @@ -0,0 +1,13 @@ +Subject: Notification de modération d’un message – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le message « {POST_SUBJECT} » sur « {SITENAME} » est en attente d’approbation. + +Si vous souhaitez consulter le message, veuillez cliquer sur le lien suivant : +{U_VIEW_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +{EMAIL_SIG} diff --git a/language/fr/email/privmsg_notify.txt b/language/fr/email/privmsg_notify.txt new file mode 100644 index 0000000..ce718bc --- /dev/null +++ b/language/fr/email/privmsg_notify.txt @@ -0,0 +1,15 @@ +Subject: Vous avez reçu un nouveau message privé + +Bonjour {USERNAME}, + +Vous avez reçu un nouveau message privé de la part de « {AUTHOR_NAME} » sur votre compte sur « {SITENAME} » dont l’intitulé est le suivant : + +{SUBJECT} + +Vous pouvez consulter votre nouveau message en cliquant sur le lien suivant : + +{U_VIEW_MESSAGE} + +Vous avez demandé à être notifié lors de cet événement. Sachez que vous pouvez choisir de ne plus recevoir de notifications lors de la réception de nouveaux messages privés en modifiant les paramètres de votre profil. + +{EMAIL_SIG} diff --git a/language/fr/email/profile_send_email.txt b/language/fr/email/profile_send_email.txt new file mode 100644 index 0000000..aa7f225 --- /dev/null +++ b/language/fr/email/profile_send_email.txt @@ -0,0 +1,13 @@ + +Bonjour {TO_USERNAME}, + +Ce qui suit est un courriel qui vous a été envoyé par {FROM_USERNAME} depuis votre compte sur « {SITENAME} ». Si ce message est un pourriel ou qu’il contient des commentaires que vous trouvez déplacés, veuillez contacter un administrateur du forum à l’adresse suivante : + +{BOARD_CONTACT} + +Incluez ce courriel en entier (particulièrement les en-têtes). Veuillez noter que l’adresse de réponse à ce courriel a été définie comme celle de {FROM_USERNAME}. + +Le message qui vous a été envoyé est le suivant : +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{MESSAGE} diff --git a/language/fr/email/profile_send_im.txt b/language/fr/email/profile_send_im.txt new file mode 100644 index 0000000..7d54ec8 --- /dev/null +++ b/language/fr/email/profile_send_im.txt @@ -0,0 +1,13 @@ + +Bonjour {TO_USERNAME}, + +Ce qui suit est un message qui vous a été envoyé par {FROM_USERNAME} depuis votre compte sur « {SITENAME} ». Si ce message est un pourriel ou qu’il contient des commentaires que vous trouvez déplacés, veuillez contacter un administrateur du forum à l’adresse suivante : + +{BOARD_CONTACT} + +Incluez ce courriel en entier. Veuillez noter que l’adresse de l’expéditeur a été définie sur le compte IM des forums. + +Le message qui vous a été envoyé est le suivant : +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +{MESSAGE} diff --git a/language/fr/email/quote.txt b/language/fr/email/quote.txt new file mode 100644 index 0000000..968c16d --- /dev/null +++ b/language/fr/email/quote.txt @@ -0,0 +1,20 @@ +Subject: Notification de réponse à un sujet – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car « {AUTHOR_NAME} » vous a cité dans le sujet « {TOPIC_TITLE} » sur « {SITENAME} ». Vous pouvez utiliser le lien suivant afin de consulter la réponse rédigée. + +Si vous souhaitez consulter le message cité, veuillez cliquer sur le lien suivant : +{U_VIEW_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +Si vous ne souhaitez plus recevoir de notifications lorsqu’un utilisateur vous cite, vous pouvez configurer les paramètres de notification sur le lien suivant : + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/language/fr/email/report_closed.txt b/language/fr/email/report_closed.txt new file mode 100644 index 0000000..1ceda96 --- /dev/null +++ b/language/fr/email/report_closed.txt @@ -0,0 +1,7 @@ +Subject: Votre rapport a été clôturé – « {POST_SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le rapport que vous avez rempli sur le message « {POST_SUBJECT} » dans « {TOPIC_TITLE} » sur « {SITENAME} » a été vérifié par un modérateur ou un administrateur. Le rapport a alors été clôturé. Si vous souhaitez avoir plus d’informations à ce sujet, veuillez contacter {CLOSER_NAME} par message privé. + +{EMAIL_SIG} diff --git a/language/fr/email/report_deleted.txt b/language/fr/email/report_deleted.txt new file mode 100644 index 0000000..cf2430e --- /dev/null +++ b/language/fr/email/report_deleted.txt @@ -0,0 +1,7 @@ +Subject: Un rapport a été supprimé – « {POST_SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le rapport du message « {POST_SUBJECT} » dans « {TOPIC_TITLE} » sur « {SITENAME} » a été supprimé par un modérateur ou un administrateur. + +{EMAIL_SIG} diff --git a/language/fr/email/report_pm.txt b/language/fr/email/report_pm.txt new file mode 100644 index 0000000..15dd9f2 --- /dev/null +++ b/language/fr/email/report_pm.txt @@ -0,0 +1,10 @@ +Subject: Rapport de message privé – « {SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car un message privé intitulé « {SUBJECT} » publié par « {AUTHOR_NAME} » sur « {SITENAME} » a été rapporté. + +Si vous souhaitez consulter le rapport, veuillez cliquer sur le lien suivant : +{U_VIEW_REPORT} + +{EMAIL_SIG} diff --git a/language/fr/email/report_post.txt b/language/fr/email/report_post.txt new file mode 100644 index 0000000..11ace88 --- /dev/null +++ b/language/fr/email/report_post.txt @@ -0,0 +1,13 @@ +Subject: Rapport de message – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le message « {POST_SUBJECT} » sur « {SITENAME} » a été rapporté. + +Si vous souhaitez consulter le rapport, veuillez cliquer sur le lien suivant : +{U_VIEW_REPORT} + +Si vous souhaitez consulter le message, veuillez cliquer sur le lien suivant : +{U_VIEW_POST} + +{EMAIL_SIG} diff --git a/language/fr/email/short/bookmark.txt b/language/fr/email/short/bookmark.txt new file mode 100644 index 0000000..40a62a2 --- /dev/null +++ b/language/fr/email/short/bookmark.txt @@ -0,0 +1,20 @@ +Subject: Notification de réponse à un sujet – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le sujet que vous avez ajouté aux favoris, « {TOPIC_TITLE} » sur « {SITENAME} », a reçu une réponse depuis votre dernière visite. Vous pouvez utiliser le lien suivant afin de consulter les réponses qui ont été publiées, aucune nouvelle notification ne vous sera envoyée jusqu’à ce que vous visitiez le sujet. + +Si vous souhaitez consulter le nouveau message publié depuis votre dernière visite, veuillez cliquer sur le lien suivant : +{U_NEWEST_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +Si vous ne souhaitez plus recevoir de notifications lorsqu’un utilisateur répond à un sujet que vous avez ajouté aux favoris, vous pouvez configurer les paramètres de notification sur le lien suivant : + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/language/fr/email/short/newtopic_notify.txt b/language/fr/email/short/newtopic_notify.txt new file mode 100644 index 0000000..2612806 --- /dev/null +++ b/language/fr/email/short/newtopic_notify.txt @@ -0,0 +1,13 @@ +Subject: Notification de publication d’un nouveau sujet – « {FORUM_NAME} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car vous surveillez le forum « {FORUM_NAME} » sur « {SITENAME} ». Depuis votre dernière visite, un nouveau sujet, « {TOPIC_TITLE} », a été publié dans ce forum par {AUTHOR_NAME}. Vous pouvez utiliser le lien suivant afin de consulter le forum, aucune nouvelle notification ne vous sera envoyée jusqu’à ce que vous visitiez ce dernier. + +{U_FORUM} + +Si vous ne souhaitez plus surveiller ce forum, vous pouvez cliquer sur le lien « Ne plus surveiller le forum », présent dans le forum accessible ci-dessus, ou cliquer sur le lien suivant : + +{U_STOP_WATCHING_FORUM} + +{EMAIL_SIG} diff --git a/language/fr/email/short/post_approved.txt b/language/fr/email/short/post_approved.txt new file mode 100644 index 0000000..a426467 --- /dev/null +++ b/language/fr/email/short/post_approved.txt @@ -0,0 +1,13 @@ +Subject: Votre message a été approuvé – « {POST_SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre message « {POST_SUBJECT} » sur « {SITENAME} » a été approuvé par un modérateur ou un administrateur. + +Si vous souhaitez consulter le message, veuillez cliquer sur le lien suivant : +{U_VIEW_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_VIEW_TOPIC} + +{EMAIL_SIG} diff --git a/language/fr/email/short/post_disapproved.txt b/language/fr/email/short/post_disapproved.txt new file mode 100644 index 0000000..ea63180 --- /dev/null +++ b/language/fr/email/short/post_disapproved.txt @@ -0,0 +1,11 @@ +Subject: Votre message a été désapprouvé – « {POST_SUBJECT} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre message « {POST_SUBJECT} » sur « {SITENAME} » a été désapprouvé par un modérateur ou un administrateur. + +La raison suivante a été prononcée concernant la désapprobation : + +{REASON} + +{EMAIL_SIG} diff --git a/language/fr/email/short/post_in_queue.txt b/language/fr/email/short/post_in_queue.txt new file mode 100644 index 0000000..a3e5255 --- /dev/null +++ b/language/fr/email/short/post_in_queue.txt @@ -0,0 +1,13 @@ +Subject: Notification de modération d’un message – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le message « {POST_SUBJECT} » sur « {SITENAME} » est en attente d’approbation. + +Si vous souhaitez consulter le message, veuillez cliquer sur le lien suivant : +{U_VIEW_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +{EMAIL_SIG} diff --git a/language/fr/email/short/privmsg_notify.txt b/language/fr/email/short/privmsg_notify.txt new file mode 100644 index 0000000..ce718bc --- /dev/null +++ b/language/fr/email/short/privmsg_notify.txt @@ -0,0 +1,15 @@ +Subject: Vous avez reçu un nouveau message privé + +Bonjour {USERNAME}, + +Vous avez reçu un nouveau message privé de la part de « {AUTHOR_NAME} » sur votre compte sur « {SITENAME} » dont l’intitulé est le suivant : + +{SUBJECT} + +Vous pouvez consulter votre nouveau message en cliquant sur le lien suivant : + +{U_VIEW_MESSAGE} + +Vous avez demandé à être notifié lors de cet événement. Sachez que vous pouvez choisir de ne plus recevoir de notifications lors de la réception de nouveaux messages privés en modifiant les paramètres de votre profil. + +{EMAIL_SIG} diff --git a/language/fr/email/short/quote.txt b/language/fr/email/short/quote.txt new file mode 100644 index 0000000..968c16d --- /dev/null +++ b/language/fr/email/short/quote.txt @@ -0,0 +1,20 @@ +Subject: Notification de réponse à un sujet – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car « {AUTHOR_NAME} » vous a cité dans le sujet « {TOPIC_TITLE} » sur « {SITENAME} ». Vous pouvez utiliser le lien suivant afin de consulter la réponse rédigée. + +Si vous souhaitez consulter le message cité, veuillez cliquer sur le lien suivant : +{U_VIEW_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +Si vous ne souhaitez plus recevoir de notifications lorsqu’un utilisateur vous cite, vous pouvez configurer les paramètres de notification sur le lien suivant : + +{U_NOTIFICATION_SETTINGS} + +{EMAIL_SIG} diff --git a/language/fr/email/short/report_pm.txt b/language/fr/email/short/report_pm.txt new file mode 100644 index 0000000..05d73f6 --- /dev/null +++ b/language/fr/email/short/report_pm.txt @@ -0,0 +1,10 @@ +Subject: Rapport de message privé – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car un message privé intitulé « {SUBJECT} » publié par « {AUTHOR_NAME} » sur « {SITENAME} » a été rapporté. + +Si vous souhaitez consulter le rapport, veuillez cliquer sur le lien suivant : +{U_VIEW_REPORT} + +{EMAIL_SIG} diff --git a/language/fr/email/short/report_post.txt b/language/fr/email/short/report_post.txt new file mode 100644 index 0000000..11ace88 --- /dev/null +++ b/language/fr/email/short/report_post.txt @@ -0,0 +1,13 @@ +Subject: Rapport de message – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le message « {POST_SUBJECT} » sur « {SITENAME} » a été rapporté. + +Si vous souhaitez consulter le rapport, veuillez cliquer sur le lien suivant : +{U_VIEW_REPORT} + +Si vous souhaitez consulter le message, veuillez cliquer sur le lien suivant : +{U_VIEW_POST} + +{EMAIL_SIG} diff --git a/language/fr/email/short/topic_approved.txt b/language/fr/email/short/topic_approved.txt new file mode 100644 index 0000000..4c7b315 --- /dev/null +++ b/language/fr/email/short/topic_approved.txt @@ -0,0 +1,10 @@ +Subject: Votre sujet a été approuvé – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre sujet « {TOPIC_TITLE} » sur « {SITENAME} » a été approuvé par un modérateur ou un administrateur. + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_VIEW_TOPIC} + +{EMAIL_SIG} diff --git a/language/fr/email/short/topic_disapproved.txt b/language/fr/email/short/topic_disapproved.txt new file mode 100644 index 0000000..a4f7721 --- /dev/null +++ b/language/fr/email/short/topic_disapproved.txt @@ -0,0 +1,11 @@ +Subject: Votre sujet a été désapprouvé – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre sujet « {TOPIC_TITLE} » sur « {SITENAME} » a été désapprouvé par un modérateur ou un administrateur. + +La raison suivante a été prononcée concernant la désapprobation : + +{REASON} + +{EMAIL_SIG} diff --git a/language/fr/email/short/topic_in_queue.txt b/language/fr/email/short/topic_in_queue.txt new file mode 100644 index 0000000..b1a61a1 --- /dev/null +++ b/language/fr/email/short/topic_in_queue.txt @@ -0,0 +1,13 @@ +Subject: Notification de modération d’un sujet – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le sujet « {TOPIC_TITLE} » sur « {SITENAME} » est en attente d’approbation. + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_VIEW_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +{EMAIL_SIG} diff --git a/language/fr/email/short/topic_notify.txt b/language/fr/email/short/topic_notify.txt new file mode 100644 index 0000000..07114d0 --- /dev/null +++ b/language/fr/email/short/topic_notify.txt @@ -0,0 +1,20 @@ +Subject: Notification de réponse à un sujet – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car vous surveillez le sujet « {TOPIC_TITLE} » sur « {SITENAME} ». Ce sujet a reçu une nouvelle réponse de {AUTHOR_NAME} depuis votre dernière visite. Vous pouvez utiliser le lien suivant afin de consulter la réponse, aucune nouvelle notification ne vous sera envoyée jusqu’à ce que vous visitiez le sujet. + +Si vous souhaitez consulter le dernier message publié depuis votre dernière visite, veuillez cliquer sur le lien suivant : +{U_NEWEST_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +Si vous ne souhaitez plus surveiller ce sujet, vous pouvez cliquer sur le lien « Ne plus surveiller le sujet », présent en bas de page du sujet accessible ci-dessus, ou cliquer sur le lien suivant : + +{U_STOP_WATCHING_TOPIC} + +{EMAIL_SIG} diff --git a/language/fr/email/test.txt b/language/fr/email/test.txt new file mode 100644 index 0000000..184ea53 --- /dev/null +++ b/language/fr/email/test.txt @@ -0,0 +1,9 @@ +Subject: phpBB est correctement configuré pour envoyer des courriels + +Bonjour {USERNAME}, + +Félicitations. La réception de ce courriel signifie que la messagerie électronique de phpBB est correctement configurée. + +Dans le cas où vous auriez besoin d’aide, veuillez vous rendre sur nos forums d’assistance – https://www.phpbb.com/community/ (en anglais) + +{EMAIL_SIG} diff --git a/language/fr/email/topic_approved.txt b/language/fr/email/topic_approved.txt new file mode 100644 index 0000000..4c7b315 --- /dev/null +++ b/language/fr/email/topic_approved.txt @@ -0,0 +1,10 @@ +Subject: Votre sujet a été approuvé – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre sujet « {TOPIC_TITLE} » sur « {SITENAME} » a été approuvé par un modérateur ou un administrateur. + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_VIEW_TOPIC} + +{EMAIL_SIG} diff --git a/language/fr/email/topic_disapproved.txt b/language/fr/email/topic_disapproved.txt new file mode 100644 index 0000000..a4f7721 --- /dev/null +++ b/language/fr/email/topic_disapproved.txt @@ -0,0 +1,11 @@ +Subject: Votre sujet a été désapprouvé – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car votre sujet « {TOPIC_TITLE} » sur « {SITENAME} » a été désapprouvé par un modérateur ou un administrateur. + +La raison suivante a été prononcée concernant la désapprobation : + +{REASON} + +{EMAIL_SIG} diff --git a/language/fr/email/topic_in_queue.txt b/language/fr/email/topic_in_queue.txt new file mode 100644 index 0000000..b1a61a1 --- /dev/null +++ b/language/fr/email/topic_in_queue.txt @@ -0,0 +1,13 @@ +Subject: Notification de modération d’un sujet – « {TOPIC_TITLE} » + +Bonjour {USERNAME}, + +Vous recevez cette notification car le sujet « {TOPIC_TITLE} » sur « {SITENAME} » est en attente d’approbation. + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_VIEW_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +{EMAIL_SIG} diff --git a/language/fr/email/topic_notify.txt b/language/fr/email/topic_notify.txt new file mode 100644 index 0000000..01e658b --- /dev/null +++ b/language/fr/email/topic_notify.txt @@ -0,0 +1,20 @@ +Subject: Notification de réponse à un sujet – « {TOPIC_TITLE} » +List-Unsubscribe: <{U_STOP_WATCHING_TOPIC}> + +Bonjour {USERNAME}, + +Vous recevez cette notification car vous surveillez le sujet « {TOPIC_TITLE} » sur « {SITENAME} ». Ce sujet a reçu une nouvelle réponse de {AUTHOR_NAME} depuis votre dernière visite. Aucune nouvelle notification ne vous sera envoyée jusqu’à ce que vous visitiez le sujet. + +Si vous souhaitez consulter le dernier message publié depuis votre dernière visite, veuillez cliquer sur le lien suivant : +{U_NEWEST_POST} + +Si vous souhaitez consulter le sujet, veuillez cliquer sur le lien suivant : +{U_TOPIC} + +Si vous souhaitez consulter le forum, veuillez cliquer sur le lien suivant : +{U_FORUM} + +Si vous ne souhaitez plus surveiller ce sujet, vous pouvez cliquer sur le lien « Ne plus surveiller le sujet », présent en bas de page du sujet accessible ci-dessus, ou cliquer sur le lien suivant : +{U_STOP_WATCHING_TOPIC} + +{EMAIL_SIG} diff --git a/language/fr/email/user_activate.txt b/language/fr/email/user_activate.txt new file mode 100644 index 0000000..836175e --- /dev/null +++ b/language/fr/email/user_activate.txt @@ -0,0 +1,9 @@ +Subject: Réactivez votre compte + +Bonjour {USERNAME}, + +Votre compte sur « {SITENAME} » a été désactivé, sûrement à cause de modifications effectuées sur votre profil. Afin de réactiver votre compte, vous devez cliquer sur le lien ci-dessous : + +{U_ACTIVATE} + +{EMAIL_SIG} diff --git a/language/fr/email/user_activate_inactive.txt b/language/fr/email/user_activate_inactive.txt new file mode 100644 index 0000000..12647b0 --- /dev/null +++ b/language/fr/email/user_activate_inactive.txt @@ -0,0 +1,7 @@ +Subject: Votre compte a été désactivé + +Bonjour {USERNAME}, + +Votre compte sur « {SITENAME} » a été désactivé, sûrement à cause de modifications effectuées sur votre profil. Un administrateur du forum doit réactiver votre compte avant que vous ne puissiez vous connecter à nouveau. Vous recevrez une autre notification lorsque cela aura été effectué. + +{EMAIL_SIG} diff --git a/language/fr/email/user_activate_passwd.txt b/language/fr/email/user_activate_passwd.txt new file mode 100644 index 0000000..739d00d --- /dev/null +++ b/language/fr/email/user_activate_passwd.txt @@ -0,0 +1,17 @@ +Subject: Activation d’un nouveau mot de passe + +Bonjour {USERNAME}, + +Vous recevez cette notification car vous (ou quelqu’un se faisant passer pour vous) avez demandé qu’un nouveau mot de passe vous soit envoyé pour votre compte sur « {SITENAME} ». Si vous n’avez pas demandé cette modification, veuillez alors l’ignorer. Si vous continuez à recevoir cette notification, veuillez contacter un administrateur du forum. + +Pour utiliser le nouveau mot de passe, vous avez besoin de l’activer. Pour cela, veuillez cliquer sur le lien ci-dessous : + +{U_ACTIVATE} + +Si cela a fonctionné, vous pourrez vous connecter en utilisant le mot de passe suivant : + +Mot de passe : {PASSWORD} + +Vous pouvez modifier ce mot de passe dans le panneau de contrôle de l’utilisateur. Si vous éprouvez une quelconque difficulté durant ces étapes, veuillez contacter un administrateur du forum. + +{EMAIL_SIG} diff --git a/language/fr/email/user_reactivate_account.txt b/language/fr/email/user_reactivate_account.txt new file mode 100644 index 0000000..a197f2f --- /dev/null +++ b/language/fr/email/user_reactivate_account.txt @@ -0,0 +1,18 @@ +Subject: Réactivez votre compte sur « {SITENAME} » + +Un administrateur du forum demande à ce que votre compte soit réactivé. Votre compte est actuellement inactif. +Veuillez lire les informations ci-dessous afin de réactiver votre compte. + +Veuillez conserver ce courriel dans vos archives. L’information de votre compte est la suivante : + +---------------------------- +Nom d’utilisateur : {USERNAME} +---------------------------- + +Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +Veuillez cliquer sur le lien suivant afin de réactiver votre compte : + +{U_ACTIVATE} + +{EMAIL_SIG} diff --git a/language/fr/email/user_remind_inactive.txt b/language/fr/email/user_remind_inactive.txt new file mode 100644 index 0000000..4f35672 --- /dev/null +++ b/language/fr/email/user_remind_inactive.txt @@ -0,0 +1,11 @@ +Subject: Compte inactif + +Bonjour {USERNAME}, + +Cette notification est un rappel car votre compte sur « {SITENAME} », créé le {REGISTER_DATE}, demeure inactif. Si vous souhaitez activer ce compte, veuillez vous rendre sur le lien suivant : + +{U_ACTIVATE} + +Nous vous remercions de votre inscription sur « {SITENAME} » et attendons avec impatience votre participation. + +{EMAIL_SIG} diff --git a/language/fr/email/user_resend_inactive.txt b/language/fr/email/user_resend_inactive.txt new file mode 100644 index 0000000..6221400 --- /dev/null +++ b/language/fr/email/user_resend_inactive.txt @@ -0,0 +1,19 @@ +Subject: Bienvenue sur « {SITENAME} » + +{WELCOME_MSG} + +Veuillez conserver ce courriel dans vos archives. L’information concernant votre compte est la suivante : + +---------------------------- +Nom d’utilisateur : {USERNAME} +---------------------------- + +Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +Veuillez cliquer sur le lien suivant afin d’activer votre compte : + +{U_ACTIVATE} + +Nous vous remercions de votre inscription. + +{EMAIL_SIG} diff --git a/language/fr/email/user_welcome.txt b/language/fr/email/user_welcome.txt new file mode 100644 index 0000000..507948e --- /dev/null +++ b/language/fr/email/user_welcome.txt @@ -0,0 +1,17 @@ +Subject: Bienvenue sur « {SITENAME} » + +{WELCOME_MSG} + +Veuillez conserver ce courriel dans vos archives. Les informations concernant votre compte sont les suivantes : + +---------------------------- +Nom d’utilisateur : {USERNAME} + +Lien du forum : {U_BOARD} +---------------------------- + +Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +Nous vous remercions de votre inscription. + +{EMAIL_SIG} diff --git a/language/fr/email/user_welcome_inactive.txt b/language/fr/email/user_welcome_inactive.txt new file mode 100644 index 0000000..fbd77d6 --- /dev/null +++ b/language/fr/email/user_welcome_inactive.txt @@ -0,0 +1,21 @@ +Subject: Bienvenue sur « {SITENAME} » + +{WELCOME_MSG} + +Veuillez conserver ce courriel dans vos archives. Les informations concernant votre compte sont les suivantes : + +---------------------------- +Nom d’utilisateur : {USERNAME} + +Lien du forum : {U_BOARD} +---------------------------- + +Veuillez cliquer sur le lien suivant afin d’activer votre compte : + +{U_ACTIVATE} + +Votre mot de passe a été stocké en toute sécurité dans notre base de données et ne pourra pas être retrouvé. Dans le cas où vous l’oubliez, vous pourrez le réinitialiser en utilisant l’adresse de courriel associée à votre compte. + +Nous vous remercions de votre inscription. + +{EMAIL_SIG} diff --git a/language/fr/groups.php b/language/fr/groups.php new file mode 100644 index 0000000..2831b12 --- /dev/null +++ b/language/fr/groups.php @@ -0,0 +1,94 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ALREADY_DEFAULT_GROUP' => 'Ce groupe est déjà votre groupe par défaut.', + 'ALREADY_IN_GROUP' => 'Vous êtes déjà membre de ce groupe.', + 'ALREADY_IN_GROUP_PENDING' => 'Vous avez déjà demandé à rejoindre ce groupe.', + + 'CANNOT_JOIN_GROUP' => 'Vous ne pouvez pas rejoindre ce groupe. Vous ne pouvez rejoindre que les groupes publics ou restreints.', + 'CANNOT_RESIGN_GROUP' => 'Vous ne pouvez pas quitter ce groupe. Vous ne pouvez quitter que les groupes publics ou restreints.', + 'CHANGED_DEFAULT_GROUP' => 'Le groupe par défaut a été modifié.', + + 'GROUP_AVATAR' => 'Avatar du groupe', + 'GROUP_CHANGE_DEFAULT' => 'Êtes-vous sûr de vouloir modifier votre groupe par défaut par le groupe « %s » ?', + 'GROUP_CLOSED' => 'Privé', + 'GROUP_DESC' => 'Description du groupe', + 'GROUP_HIDDEN' => 'Invisible', + 'GROUP_INFORMATION' => 'Informations sur le groupe', + 'GROUP_IS_CLOSED' => 'Ce groupe est un groupe privé. Les utilisateurs ne peuvent le rejoindre que sur invitation du responsable du groupe.', + 'GROUP_IS_FREE' => 'Ce groupe est un groupe public. Tous les utilisateurs peuvent le rejoindre sans aucune restriction.', + 'GROUP_IS_HIDDEN' => 'Ce groupe est un groupe invisible. Seuls les membres de ce groupe peuvent voir ses adhérents.', + 'GROUP_IS_OPEN' => 'Ce groupe est un groupe restreint. Les utilisateurs peuvent le rejoindre sur demande qui sera soumise à approbation.', + 'GROUP_IS_SPECIAL' => 'Ce groupe est un groupe spécial. Il est géré par les administrateurs du forum.', + 'GROUP_JOIN' => 'Rejoindre le groupe', + 'GROUP_JOIN_CONFIRM' => 'Êtes-vous sûr de vouloir rejoindre ce groupe ?', + 'GROUP_JOIN_PENDING' => 'Demander à rejoindre le groupe', + 'GROUP_JOIN_PENDING_CONFIRM' => 'Êtes-vous sûr de vouloir demander à rejoindre ce groupe ?', + 'GROUP_JOINED' => 'Vous avez rejoint ce groupe.', + 'GROUP_JOINED_PENDING' => 'Votre demande d’adhésion a bien été prise en compte. Veuillez patienter le temps que le responsable du groupe examine votre demande.', + 'GROUP_LIST' => 'Gérer les utilisateurs', + 'GROUP_MEMBERS' => 'Membres du groupe', + 'GROUP_NAME' => 'Nom du groupe', + 'GROUP_OPEN' => 'Restreint', + 'GROUP_RANK' => 'Rang du groupe', + 'GROUP_RESIGN_MEMBERSHIP' => 'Quitter le groupe', + 'GROUP_RESIGN_MEMBERSHIP_CONFIRM' => 'Êtes-vous sûr de vouloir quitter ce groupe ?', + 'GROUP_RESIGN_PENDING' => 'Retirer ma demande d’adhésion au groupe', + 'GROUP_RESIGN_PENDING_CONFIRM' => 'Êtes-vous sûr de vouloir retirer votre demande d’adhésion à ce groupe ?', + 'GROUP_RESIGNED_MEMBERSHIP' => 'Vous avez quitté ce groupe.', + 'GROUP_RESIGNED_PENDING' => 'Vous avez retiré votre demande d’adhésion à ce groupe.', + 'GROUP_TYPE' => 'Type de groupe', + 'GROUP_UNDISCLOSED' => 'Groupe invisible', + 'FORUM_UNDISCLOSED' => 'Modération des forums invisibles', + + 'LOGIN_EXPLAIN_GROUP' => 'Vous devez vous connecter afin de pouvoir consulter les informations sur ce groupe.', + + 'NO_LEADERS' => 'Vous n’êtes responsable d’aucun groupe.', + 'NOT_LEADER_OF_GROUP' => 'Cette opération n’a pas pu aboutir car vous n’êtes pas responsable de ce groupe.', + 'NOT_MEMBER_OF_GROUP' => 'Cette opération n’a pas pu aboutir car vous n’êtes pas membre de ce groupe ou votre demande d’adhésion n’a pas encore été approuvée.', + 'NOT_RESIGN_FROM_DEFAULT_GROUP' => 'Vous ne pouvez pas vous retirer de votre groupe par défaut.', + + 'PRIMARY_GROUP' => 'Groupe principal', + + 'REMOVE_SELECTED' => 'Supprimer la sélection', + + 'USER_GROUP_CHANGE' => 'De « %1$s » au groupe « %2$s »', + 'USER_GROUP_DEMOTE' => 'Quitter la fonction de responsable', + 'USER_GROUP_DEMOTE_CONFIRM' => 'Êtes-vous sûr de vouloir quitter votre fonction de responsable de ce groupe ?', + 'USER_GROUP_DEMOTED' => 'Vous n’êtes plus responsable de ce groupe.', +]); diff --git a/language/fr/help/bbcode.php b/language/fr/help/bbcode.php new file mode 100644 index 0000000..3215f44 --- /dev/null +++ b/language/fr/help/bbcode.php @@ -0,0 +1,65 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +$lang = array_merge($lang, [ + 'HELP_BBCODE_BLOCK_IMAGES' => 'Affichage d’images dans les messages', + 'HELP_BBCODE_BLOCK_INTRO' => 'Introduction', + 'HELP_BBCODE_BLOCK_LINKS' => 'Insertion de liens', + 'HELP_BBCODE_BLOCK_LISTS' => 'Genèse de listes', + 'HELP_BBCODE_BLOCK_OTHERS' => 'Divers', + 'HELP_BBCODE_BLOCK_QUOTES' => 'Citation et données de largeur fixe', + 'HELP_BBCODE_BLOCK_TEXT' => 'Mise en forme du texte', + + 'HELP_BBCODE_IMAGES_ATTACHMENT_ANSWER' => 'Les pièces jointes peuvent dès à présent être transférées dans les messages en utilisant le nouveau BBCode « [attachment=][/attachment] », si cette fonctionnalité a été activée par les administrateurs du forum et si vous en avez les permissions appropriées. Pour transférer des pièces jointes en ligne, cliquez sur le bouton respectif après votre sélection depuis la liste déroulante dans la page de rédaction.', + 'HELP_BBCODE_IMAGES_ATTACHMENT_QUESTION' => 'Transfert de pièces jointes dans un message', + 'HELP_BBCODE_IMAGES_BASIC_ANSWER' => 'Le BBCode de phpBB intègre une balise qui vous permet d’insérer des images dans vos messages. Les deux choses importantes à garder à l’esprit lors de l’utilisation des balises d’images sont que l’excès d’images dans les messages n’est pas apprécié par la plupart des d’utilisateurs et que l’image que vous souhaitez afficher doit être déjà disponible en ligne (elle ne peut pas exister seulement sur votre ordinateur, à moins que vous travailliez directement depuis un serveur internet !). Pour afficher une image, vous devez insérer entre les balises « [img][/img] » le lien pointant vers l’image. Par exemple :

[img]https://www.phpbb.com/theme/images/logos/blue/160x52.png[/img]

Comme noté précédemment dans la section des liens située ci-dessus, vous pouvez intégrer une image entre des balises « [url][/url] ». Par exemple :

[url=https://www.phpbb.com/][img]https://www.phpbb.com/theme/images/logos/blue/160x52.png[/img][/url]

Deviendra :

', + 'HELP_BBCODE_IMAGES_BASIC_QUESTION' => 'Ajout d’une image à un message', + + 'HELP_BBCODE_INTRO_BBCODE_ANSWER' => 'Le BBCode est une implémentation spéciale du code HTML. Vous pouvez utiliser le BBCode dans vos messages sur le forum sous réserve que cette fonctionnalité ait été autorisée par un administrateur. De plus, vous pouvez désactiver les BBCodes sur chacun de vos messages depuis le formulaire de rédaction. Le BBCode est similaire à l’architecture du code HTML, les balises sont contenues entre des crochets « [ » et « ] » à la place des chevrons « < » et « > », et il offre un meilleur contrôle sur la mise en forme. Selon le style que vous utilisez, vous pouvez trouver et utiliser très facilement du BBCode dans vos messages depuis une interface cliquable située juste au-dessus du formulaire de rédaction. Si ces brèves explications ne vous conviennent pas, vous pouvez consulter le guide suivant qui s’avère très explicite.', + 'HELP_BBCODE_INTRO_BBCODE_QUESTION' => 'Qu’est-ce que le BBCode ?', + + 'HELP_BBCODE_LINKS_BASIC_ANSWER' => 'Le BBCode de phpBB propose plusieurs manières de créer des liens.
  • La première emploie la balise « [url=][/url] », où ce qui est inséré après le signe égal « = » fera agir le contenu de cette balise en tant que lien. Par exemple, pour insérer un lien vers phpBB.com, vous pouvez utiliser :

    [url=https://www.phpbb.com/]Visitez phpBB.com ![/url]

    Ceci génèrera le lien suivant : Visitez phpBB.com ! Veuillez noter que le lien peut s’ouvrir dans la même fenêtre ou dans une nouvelle fenêtre, selon les préférences du navigateur des utilisateurs.
  • Si vous souhaitez que le lien soit affiché comme un lien basique, vous pouvez employer simplement ceci :

    [url]https://www.phpbb.com/[/url]

    Ceci générera le lien suivant : https://www.phpbb.com/
  • De plus, une des fonctionnalités de phpBB permet d’insérer les liens magiques, ce qui transformera n’importe quel lien en lien syntaxiquement correct, sans que vous n’ayez besoin d’indiquer des balises ou même encore d’ajouter « https:// ». Par exemple, en écrivant « www.phpbb.com » dans votre message, ceci se complètera automatiquement en « www.phpbb.com » lorsque vous verrez votre message par la suite.
  • La même chose est valable pour les adresses de courrier électronique, vous pouvez cependant indiquer une adresse explicite, par exemple :

    [email]inconnu@domaine.adr[/email]

    Ce qui deviendra : inconnu@domaine.adr, ou vous pouvez simplement écrire « inconnu@domaine.adr » dans votre message et celui-ci sera automatiquement converti lors de la visualisation.
Comme avec toutes les balises BBCode, vous pouvez insérer des liens autour d’autres balises comme « [img][/img] » (consultez la prochaine question), « [b][/b] », etc. Comme pour les balises de mise en forme, il vous appartient d’en assurer l’usage correct d’ouverture et de fermeture des balises, et ceci dans le bon ordre, comme par exemple :

[url=https://www.phpbb.com/][img]https://www.phpbb.com/theme/images/logos/blue/160x52.png[/url][/img]

N’est pas correct, ce qui peut mener à la suppression de votre message, soyez donc vigilant.', + 'HELP_BBCODE_LINKS_BASIC_QUESTION' => 'Insérer un lien vers un site internet', + + 'HELP_BBCODE_LISTS_ORDERER_ANSWER' => 'Le second type de liste, la liste ordonnée, vous donne le contrôle sur ce qui est affiché devant chaque article. Vous devez utiliser les balises « [list=1][/list] » afin de créer une liste numérotée, ou bien encore les balises « [list=a][/list] » afin de créer une liste alphabétique. Comme pour la liste non-ordonnée, les articles doivent être définis en utilisant la balise « [*] ». Par exemple :

[list=1]
[*]Faire les boutiques
[*]Acheter un nouvel ordinateur
[*]Râler contre l’ordinateur lorsqu’il est bloqué
[/list]

Deviendra :
  1. Faire les boutiques
  2. Acheter un nouvel ordinateur
  3. Râler contre l’ordinateur lorsqu’il est bloqué
Pour créer une liste alphabétique, vous devez cependant utiliser :

[list=a]
[*]La première réponse possible
[*]La seconde réponse possible
[*]La troisième réponse possible
[/list]

Ce qui deviendra :
  1. La première réponse possible
  2. La seconde réponse possible
  3. La troisième réponse possible

[list=A]
[*]La première réponse possible
[*]La seconde réponse possible
[*]La troisième réponse possible
[/list]

Deviendra :
  1. La première réponse possible
  2. La seconde réponse possible
  3. La troisième réponse possible

[list=i]
[*]La première réponse possible
[*]La seconde réponse possible
[*]La troisième réponse possible
[/list]

Deviendra :
  1. La première réponse possible
  2. La seconde réponse possible
  3. La troisième réponse possible

[list=I]
[*]La première réponse possible
[*]La seconde réponse possible
[*]La troisième réponse possible
[/list]

Deviendra :
  1. La première réponse possible
  2. La seconde réponse possible
  3. La troisième réponse possible
', + 'HELP_BBCODE_LISTS_ORDERER_QUESTION' => 'Création d’une liste ordonnée', + 'HELP_BBCODE_LISTS_UNORDERER_ANSWER' => 'Le BBCode prend en charge deux types de liste, la liste ordonnée et la liste non-ordonnée. Elles sont pratiquement identiques par rapport à leur équivalent en HTML. Une liste non-ordonnée publie chaque article l’un après l’autre, en utilisant le caractère étoile. Pour créer une liste non-ordonnée, vous devez utiliser les balises « [list][/list] » et définir chaque article dans la liste en utilisant les balises « [*] ». Par exemple, pour lister vos couleurs favorites, vous pouvez utiliser :

[list]
[*]Rouge
[*]Bleu
[*]Jaune
[/list]

Ce qui deviendra la liste :
  • Rouge
  • Bleu
  • Jaune

Vous pouvez également créer une liste à puces en utilisant « [list=disc][/list] », « [list=circle][/list] » ou « [list=square][/list] ».', + 'HELP_BBCODE_LISTS_UNORDERER_QUESTION' => 'Création d’une liste non-ordonnée', + + 'HELP_BBCODE_OTHERS_CUSTOM_ANSWER' => 'Si vous êtes un administrateur de ce forum et que vous en avez les permissions appropriées, vous pouvez ajouter davantage de BBCodes dans la section « Personnaliser les BBCodes ».', + 'HELP_BBCODE_OTHERS_CUSTOM_QUESTION' => 'Puis-je ajouter mes propres balises ?', + + 'HELP_BBCODE_QUOTES_CODE_ANSWER' => 'Si vous souhaitez insérer un fragment de code ou quelque chose qui demande une largeur fixe, comme la police de caractères « Courier », vous devez enfermer le texte entre les balises « [code][/code] ». Par exemple :

[code]echo "Ceci est un fragment de code";[/code]

Toutes les balises de mise en forme présentes entre les balises « [code][/code] » sont conservées telles quelles lorsque vous consulterez le message ultérieurement.', + 'HELP_BBCODE_QUOTES_CODE_QUESTION' => 'Mise en forme de code ou de données de largeur fixe', + 'HELP_BBCODE_QUOTES_TEXT_ANSWER' => 'Il y a deux manières de citer un texte, avec ou sans référence :
  • Lorsque vous utilisez la fonction de citation afin de répondre à un message sur le forum, vous devriez noter que le texte qui est ajouté à la fenêtre du message est inséré entre les balises « [quote=""][/quote] ». Cette méthode vous permet de citer, comme référence, une personne ou autre chose que vous choisissez de commenter ! Par exemple, pour citer une partie d’un texte rédigé par M. Blobby, vous devez saisir :

    [quote="M. Blobby"]Le texte que M. Blobby a rédigé sera situé ici[/quote]

    Le rendu final, « M. Blobby a écrit », sera ajouté automatiquement avant le texte actuel. Gardez à l’esprit que vous devez obligatoirement insérer les guillemets anglais « " " » autour du nom que vous citez, ils ne sont pas optionnels.
  • La seconde méthode vous permet de citer quelque chose de manière anonyme. Pour l’utiliser, enfermez le texte entre les balises « [quote][/quote] ». Lorsque vous consulterez le message ultérieurement, il affichera simplement le texte dans un bloc de citation.
', + 'HELP_BBCODE_QUOTES_TEXT_QUESTION' => 'Citation de texte dans les réponses', + + 'HELP_BBCODE_TEXT_BASIC_ANSWER' => 'Le BBCode inclut des balises qui vous permettent de modifier rapidement le style de votre texte brut. Ceci peut être effectué de différentes manières :
  • Pour mettre une partie d’un texte en gras, il faut l’enfermer entre les balises « [b][/b] ». Par exemple :

    [b]Salut[/b]

    Deviendra : Salut
  • Pour souligner une partie d’un texte, utilisez les balises « [u][/u] ». Par exemple :

    [u]Bonjour[/u]

    Deviendra : Bonjour
  • Pour mettre une partie d’un texte en italique, utilisez les balises « [i][/i] ». Par exemple :

    C’est [i]super ![/i]

    Deviendra : C’est super !
', + 'HELP_BBCODE_TEXT_BASIC_QUESTION' => 'Comment puis-je mettre du texte en gras, en italique et en souligné ?', + 'HELP_BBCODE_TEXT_COLOR_ANSWER' => 'Pour modifier la taille ou la couleur du texte, vous pourrez utiliser les balises suivantes. Cependant, gardez à l’esprit que le rendu visuel final dépendra également du navigateur internet et du système d’exploitation de l’utilisateur :
  • Pour insérer une couleur sur une partie d’un texte, il faut l’enfermer entre les balises « [color=][/color] ». Vous pouvez saisir le nom d’une couleur reconnue, comme par exemple « red », « blue » ou « yellow », ou saisir directement son code hexadécimal, comme « #FFFFFF » ou « #000000 ». Par exemple, pour mettre une partie d’un texte en rouge, vous pouvez utiliser :

    [color=red]Salut ![/color]

    Ou encore :

    [color=#FF0000]Salut ![/color]

    Ce qui, dans les deux cas, deviendra : Salut !
  • Pour modifier la taille de la police de caractères d’une partie d’un texte, utilisez les balises « [size=][/size] ». Les balises dépendent du style que l’utilisateur a sélectionné, mais le format recommandé est une valeur numérique représentant la taille du texte en pourcentage, commençant de 50 (minuscule) et allant jusqu’à 200 (énorme). Par exemple :

    [size=30]MINUSCULE[/size]

    Deviendra : MINUSCULE

    Alors que :

    [size=200]ÉNORME ![/size]

    Deviendra : ÉNORME !
', + 'HELP_BBCODE_TEXT_COLOR_QUESTION' => 'Comment puis-je modifier la taille ou la couleur du texte ?', + 'HELP_BBCODE_TEXT_COMBINE_ANSWER' => 'Bien sûr ! Par exemple, pour obtenir l’attention de tout le monde, vous pouvez écrire :

[size=200][color=red][b]REGARDEZ-MOI ![/b][/color][/size]

Ce qui deviendra : REGARDEZ-MOI !

Cependant, nous vous déconseillons fortement d’utiliser fréquemment ce genre de mise en forme ! Gardez à l’esprit qu’il faut simplement fermer les balises correctement et dans le bon ordre. Par exemple, ce qui suit est incorrect :

[b][u]Ceci est incorrect[/b][/u]', + 'HELP_BBCODE_TEXT_COMBINE_QUESTION' => 'Puis-je combiner des balises de mise en forme ?', +]); diff --git a/language/fr/help/faq.php b/language/fr/help/faq.php new file mode 100644 index 0000000..2e9326d --- /dev/null +++ b/language/fr/help/faq.php @@ -0,0 +1,185 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +$lang = array_merge($lang, [ + 'HELP_FAQ_ATTACHMENTS_ALLOWED_ANSWER' => 'Chaque administrateur peut autoriser ou interdire certains types de pièces jointes. Si vous n’êtes pas certain de savoir ce qui est autorisé ou non, nous vous invitons à contacter un administrateur du forum.', + 'HELP_FAQ_ATTACHMENTS_ALLOWED_QUESTION' => 'Quelles pièces jointes sont autorisées sur ce forum ?', + 'HELP_FAQ_ATTACHMENTS_OWN_ANSWER' => 'Pour retrouver la liste des pièces jointes que vous avez transférées, veuillez vous rendre dans le panneau de contrôle de l’utilisateur et suivre les liens vers la section des pièces jointes.', + 'HELP_FAQ_ATTACHMENTS_OWN_QUESTION' => 'Comment puis-je retrouver toutes mes pièces jointes ?', + + 'HELP_FAQ_BLOCK_ATTACHMENTS' => 'Pièces jointes', + 'HELP_FAQ_BLOCK_BOOKMARKS' => 'Favoris et abonnements', + 'HELP_FAQ_BLOCK_FORMATTING' => 'Mise en forme et types de sujets', + 'HELP_FAQ_BLOCK_FRIENDS' => 'Amis et ignorés', + 'HELP_FAQ_BLOCK_GROUPS' => 'Niveaux des utilisateurs et groupes d’utilisateurs', + 'HELP_FAQ_BLOCK_ISSUES' => 'À propos de phpBB', + 'HELP_FAQ_BLOCK_LOGIN' => 'Problèmes de connexion et d’inscription', + 'HELP_FAQ_BLOCK_PMS' => 'Messagerie privée', + 'HELP_FAQ_BLOCK_POSTING' => 'Problèmes de publication', + 'HELP_FAQ_BLOCK_SEARCH' => 'Recherche dans les forums', + 'HELP_FAQ_BLOCK_USERSETTINGS' => 'Préférences et paramètres des utilisateurs', + + 'HELP_FAQ_BOOKMARKS_DIFFERENCE_ANSWER' => 'Dans phpBB 3.0, la fonctionnalité qui vous permettait d’ajouter un sujet aux favoris était similaire à celle présente dans votre navigateur internet. Vous ne receviez aucune notification lorsqu’un sujet ajouté aux favoris était mis à jour. Dans phpBB 3.2, les favoris sont davantage similaires aux abonnements. Vous pouvez à présent recevoir une notification lorsqu’un sujet ajouté aux favoris est mis à jour. L’abonnement, quant à lui, vous préviendra de la mise à jour d’un forum ou d’un sujet auquel vous êtes abonné. Les options de notification des favoris et des abonnements peuvent être modifiés depuis le panneau de contrôle de l’utilisateur, sous les « Préférences du forum ».', + 'HELP_FAQ_BOOKMARKS_DIFFERENCE_QUESTION' => 'Quelle est la différence entre les favoris et les abonnements ?', + 'HELP_FAQ_BOOKMARKS_FORUM_ANSWER' => 'Vous pouvez vous abonner à un forum spécifique en cliquant sur le lien « S’abonner au forum » situé en bas de la page du forum.', + 'HELP_FAQ_BOOKMARKS_FORUM_QUESTION' => 'Comment puis-je m’abonner à un forum spécifique ?', + 'HELP_FAQ_BOOKMARKS_REMOVE_ANSWER' => 'Pour résilier vos abonnements, veuillez vous rendre dans le panneau de contrôle de l’utilisateur et suivre le lien vers vos abonnements.', + 'HELP_FAQ_BOOKMARKS_REMOVE_QUESTION' => 'Comment puis-je résilier mes abonnements ?', + 'HELP_FAQ_BOOKMARKS_TOPIC_ANSWER' => 'Vous pouvez ajouter aux favoris ou vous abonner à un sujet spécifique en cliquant sur le lien approprié dans le menu « Outils du sujet », situé en haut et en bas des sujets et parfois illustré par une image.
Répondre à un sujet tout en cochant la case « Recevoir une notification lorsqu’une réponse est publiée » équivaut à vous abonner à ce sujet.', + 'HELP_FAQ_BOOKMARKS_TOPIC_QUESTION' => 'Comment puis-je ajouter aux favoris ou m’abonner à un sujet spécifique ?', + + 'HELP_FAQ_FORMATTING_ANNOUNCEMENT_ANSWER' => 'Les annonces contiennent souvent des informations importantes sur le forum dans lequel vous naviguez. Les annonces apparaissent en haut de chaque page du forum dans lequel elles ont été publiées. Tout comme les annonces générales, les permissions concernant les annonces sont définies par les administrateurs du forum.', + 'HELP_FAQ_FORMATTING_ANNOUNCEMENT_QUESTION' => 'Que sont les annonces ?', + 'HELP_FAQ_FORMATTING_BBOCDE_ANSWER' => 'Le BBCode est une implémentation spéciale du code HTML, vous offrant un meilleur contrôle sur la mise en forme d’un message. L’utilisation du BBCode est déterminée par les administrateurs, mais il vous est également possible de la désactiver sur chaque message depuis le formulaire de rédaction. Le BBCode est similaire à l’architecture du code HTML, les balises sont contenues entre des crochets « [ » et « ] » à la place des chevrons « < » et « > ». Pour plus d’informations à propos du BBCode, veuillez consulter le guide qui est accessible depuis la page de rédaction.', + 'HELP_FAQ_FORMATTING_BBOCDE_QUESTION' => 'Qu’est-ce que le BBCode ?', + 'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_ANSWER' => 'Les annonces générales contiennent des informations très importantes que vous devriez consulter en priorité. Elles apparaissent en haut de chaque forum et dans le panneau de contrôle de l’utilisateur. Les permissions concernant les annonces générales sont définies par les administrateurs du forum.', + 'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_QUESTION' => 'Que sont les annonces générales ?', + 'HELP_FAQ_FORMATTING_HTML_ANSWER' => 'Par mesure de sécurité, il n’est pas possible d’insérer du code HTML sur ce forum. La majeure partie de la mise en forme qui peut être générée par du code HTML peut être remplacée par du BBCode.', + 'HELP_FAQ_FORMATTING_HTML_QUESTION' => 'Puis-je insérer du code HTML ?', + 'HELP_FAQ_FORMATTING_ICONS_ANSWER' => 'Les icônes de sujet sont de petites images que l’auteur peut insérer afin d’illustrer le contenu de son sujet. Les administrateurs peuvent désactiver cette fonctionnalité.', + 'HELP_FAQ_FORMATTING_ICONS_QUESTION' => 'Que sont les icônes de sujet ?', + 'HELP_FAQ_FORMATTING_IMAGES_ANSWER' => 'Oui, vous pouvez insérer des images à vos messages. Si les administrateurs du forum ont autorisé l’insertion de pièces jointes, vous pourrez transférer des images sur le forum. Dans le cas contraire, vous devrez insérer un lien vers une image distante, hébergée sur un serveur internet public, comme par exemple « http://www.exemple.com/mon-image.gif ». Vous ne pourrez cependant ni insérer de lien vers des images présentes sur votre propre ordinateur (à moins, bien évidemment, que celui-ci soit en lui-même un serveur internet), ni insérer de lien vers des images hébergées derrière un quelconque système d’authentification, comme par exemple les services de messagerie électronique de Outlook ou de Yahoo, les sites protégés par un mot de passe, etc. Pour insérer une image, utilisez la balise BBCode « [img] ».', + 'HELP_FAQ_FORMATTING_IMAGES_QUESTION' => 'Puis-je insérer des images ?', + 'HELP_FAQ_FORMATTING_LOCKED_ANSWER' => 'Les sujets verrouillés sont des sujets dans lesquels les utilisateurs ne peuvent plus répondre et dans lesquels les sondages sont automatiquement expirés. Les sujets peuvent être verrouillés par un administrateur ou un modérateur du forum pour de multiples raisons. Vous pouvez également verrouiller vos propres sujets, si cela a été autorisé par les administrateurs.', + 'HELP_FAQ_FORMATTING_LOCKED_QUESTION' => 'Que sont les sujets verrouillés ?', + 'HELP_FAQ_FORMATTING_SMILIES_ANSWER' => 'Les émoticônes sont de petites images qui peuvent être utilisées grâce à un code court et qui permettent d’exprimer des sentiments. Par exemple, « :) » exprime la joie, alors qu’au contraire, « :( » exprime la tristesse. Vous pouvez consulter la liste complète des émoticônes depuis le formulaire de rédaction. Essayez cependant de ne pas abuser des émoticônes, elles peuvent rapidement rendre un message illisible et un modérateur pourrait décider de le modifier ou de le supprimer complètement. Les administrateurs du forum peuvent également limiter le nombre d’émoticônes qu’il est possible d’insérer à un message.', + 'HELP_FAQ_FORMATTING_SMILIES_QUESTION' => 'Que sont les émoticônes ?', + 'HELP_FAQ_FORMATTING_STICKIES_ANSWER' => 'Les notes apparaissent en dessous des annonces et seulement sur la première page du forum concerné. Elles sont souvent assez importantes et il est recommandé de les consulter dès que vous en avez la possibilité. Tout comme les annonces et les annonces générales, les permissions concernant les notes sont définies par les administrateurs du forum.', + 'HELP_FAQ_FORMATTING_STICKIES_QUESTION' => 'Que sont les notes ?', + + 'HELP_FAQ_FRIENDS_BASIC_ANSWER' => 'Vous pouvez utiliser ces listes afin d’organiser et trier certains utilisateurs du forum. Les membres ajoutés à votre liste d’amis seront listés dans le panneau de contrôle de l’utilisateur afin de consulter rapidement leur statut en ligne et leur envoyer des messages privés. Selon le style utilisé, les messages publiés par ces utilisateurs peuvent éventuellement être mis en surbrillance. Si vous ajoutez un utilisateur à votre liste d’ignorés, tous les messages qu’il publiera seront masqués par défaut.', + 'HELP_FAQ_FRIENDS_BASIC_QUESTION' => 'À quoi sert ma liste d’amis et d’ignorés ?', + 'HELP_FAQ_FRIENDS_MANAGE_ANSWER' => 'Dans chaque profil d’utilisateurs, un lien vous permet de les ajouter à votre liste d’amis ou d’ignorés. De même, vous pouvez ajouter directement des utilisateurs depuis le panneau de contrôle de l’utilisateur en saisissant leur nom d’utilisateur. Vous pouvez également les supprimer de vos listes depuis cette même page.', + 'HELP_FAQ_FRIENDS_MANAGE_QUESTION' => 'Comment puis-je ajouter ou supprimer des utilisateurs de ma liste d’amis et d’ignorés ?', + + 'HELP_FAQ_GROUPS_ADMINISTRATORS_ANSWER' => 'Les administrateurs sont les membres possédant le plus haut niveau de contrôle sur le forum. Ces utilisateurs peuvent contrôler toutes les opérations du forum, telles que les paramètres des permissions, le bannissement d’utilisateurs, la création de groupes d’utilisateurs ou de modérateurs, etc. Ils peuvent également être habilités à modérer l’ensemble des forums. Tout ceci dépend de la configuration effectuée par le fondateur du forum.', + 'HELP_FAQ_GROUPS_ADMINISTRATORS_QUESTION' => 'Que sont les administrateurs ?', + 'HELP_FAQ_GROUPS_COLORS_ANSWER' => 'Les administrateurs du forum peuvent assigner une couleur aux membres d’un groupe d’utilisateurs afin de faciliter leur identification.', + 'HELP_FAQ_GROUPS_COLORS_QUESTION' => 'Pourquoi certains groupes d’utilisateurs apparaissent dans une couleur différente ?', + 'HELP_FAQ_GROUPS_DEFAULT_ANSWER' => 'Si vous êtes membre de plus d’un groupe d’utilisateurs, votre groupe d’utilisateurs par défaut est utilisé afin de déterminer quelle sera la couleur et le rang qui vous sera assigné par défaut. Les administrateurs du forum peuvent vous autoriser à modifier vous-même votre groupe d’utilisateurs par défaut depuis le panneau de contrôle de l’utilisateur.', + 'HELP_FAQ_GROUPS_DEFAULT_QUESTION' => 'Qu’est-ce qu’un « groupe d’utilisateurs par défaut » ?', + 'HELP_FAQ_GROUPS_MODERATORS_ANSWER' => 'Les modérateurs sont des utilisateurs individuels (ou des groupes d’utilisateurs individuels) qui surveillent régulièrement les forums. Ils ont la possibilité de modifier ou de supprimer les sujets, les verrouiller, les déverrouiller, les déplacer, les fusionner et les diviser dans le forum qu’ils modèrent. En règle générale, les modérateurs sont présents pour que les utilisateurs respectent les règles imposées sur le forum.', + 'HELP_FAQ_GROUPS_MODERATORS_QUESTION' => 'Que sont les modérateurs ?', + 'HELP_FAQ_GROUPS_TEAM_ANSWER' => 'Cette page liste les membres de l’équipe du forum que sont les administrateurs et les modérateurs, en plus de quelques informations supplémentaires tels que les forums qu’ils modèrent.', + 'HELP_FAQ_GROUPS_TEAM_QUESTION' => 'Qu’est-ce que le lien « L’équipe » ?', + 'HELP_FAQ_GROUPS_USERGROUPS_ANSWER' => 'Les groupes d’utilisateurs sont une façon pour les administrateurs du forum de regrouper plusieurs utilisateurs. Chaque utilisateur peut appartenir à plusieurs groupes et chaque groupe peut détenir des permissions individuelles. Ceci facilite les tâches aux administrateurs qui pourront modifier les permissions de plusieurs utilisateurs en une seule fois, ou encore leur accorder des pouvoirs de modération, ou bien leur donner accès à un forum privé.', + 'HELP_FAQ_GROUPS_USERGROUPS_JOIN_ANSWER' => 'Vous pouvez consulter tous les groupes d’utilisateurs en cliquant sur le lien « Groupes d’utilisateurs » depuis le panneau de contrôle de l’utilisateur. Si vous souhaitez en rejoindre un, cliquez sur le bouton approprié. Cependant, tous les groupes d’utilisateurs ne sont pas ouverts aux nouvelles adhésions. Certains peuvent nécessiter une approbation, d’autres peuvent être privés et d’autres peuvent même être invisibles. Si le groupe est public, vous pouvez le rejoindre en cliquant sur le bouton dédié. Si le groupe est restreint et nécessite une approbation, vous devez cliquer également sur le bouton approprié. Le responsable du groupe d’utilisateurs devra alors approuver votre requête et pourra vous demander la raison de votre requête. Merci de ne pas harceler un responsable de groupe s’il refuse votre demande.', + 'HELP_FAQ_GROUPS_USERGROUPS_JOIN_QUESTION' => 'Où sont les groupes d’utilisateurs et comment puis-je en rejoindre un ?', + 'HELP_FAQ_GROUPS_USERGROUPS_LEAD_ANSWER' => 'Le responsable d’un groupe d’utilisateurs est généralement assigné lorsque les groupes d’utilisateurs sont initialement créés par un administrateur du forum. Si vous êtes intéressé par la création d’un groupe d’utilisateurs, votre premier contact devrait être un administrateur. Essayez de le contacter en lui envoyant un message privé.', + 'HELP_FAQ_GROUPS_USERGROUPS_LEAD_QUESTION' => 'Comment puis-je devenir le responsable d’un groupe d’utilisateurs ?', + 'HELP_FAQ_GROUPS_USERGROUPS_QUESTION' => 'Que sont les groupes d’utilisateurs ?', + + 'HELP_FAQ_ISSUES_ADMIN_ANSWER' => 'Tous les utilisateurs du forum peuvent utiliser le formulaire disponible sur le lien « Nous contacter » si cette fonctionnalité a été activée par les administrateurs du forum.
Les membres du forum peuvent également utiliser le lien « L’équipe ».', + 'HELP_FAQ_ISSUES_ADMIN_QUESTION' => 'Comment puis-je contacter un administrateur du forum ?', + 'HELP_FAQ_ISSUES_FEATURE_ANSWER' => 'Ce programme a été développé et mis sous licence par phpBB Limited. Si vous souhaitez proposer l’intégration d’une nouvelle fonctionnalité, veuillez vous rendre sur notre centre d’idées (en anglais) où vous pourrez voter pour les idées soumises par d’autres utilisateurs et suggérer les vôtres.', + 'HELP_FAQ_ISSUES_FEATURE_QUESTION' => 'Pourquoi la fonctionnalité X n’est pas disponible ?', + 'HELP_FAQ_ISSUES_LEGAL_ANSWER' => 'Tous les administrateurs listés sur la page « L’équipe » devraient être un contact approprié concernant ces problèmes. Si vous n’obtenez aucune réponse de leur part, vous devriez alors contacter le propriétaire du domaine (dont les informations sont disponibles grâce à une requête WHOIS), ou, si celui-ci fonctionne sur un service gratuit (comme Yahoo, Free, etc.), le service de gestion des abus. Veuillez noter que phpBB Limited n’a absolument aucune juridiction et ne peut en aucun cas être tenu comme responsable de comment, où et par qui ce forum est utilisé. Ne contactez pas phpBB Limited pour tout problème d’ordre légal (commentaire incessant, insultant, diffamatoire, etc.) qui ne sont pas directement reliés avec le site internet de phpBB.com ou le logiciel phpBB en lui-même. Si vous envoyez un courrier électronique à phpBB Limited à propos d’une utilisation de tierce partie de ce logiciel, attendez-vous à une réponse laconique ou à ne pas recevoir de réponse.', + 'HELP_FAQ_ISSUES_LEGAL_QUESTION' => 'Qui dois-je contacter à propos de problèmes d’abus ou d’ordres légaux liés à ce forum ?', + 'HELP_FAQ_ISSUES_WHOIS_PHPBB_ANSWER' => 'Ce programme (dans sa forme non modifiée) est produit et distribué par phpBB Limited, qui en est le légitime propriétaire. Il est rendu accessible sous la « Licence Publique Générale GNU version 2 (GPL-2.0) » et peut être distribué gratuitement. Pour plus d’informations, veuillez consulter la rubrique « À propos de phpBB » (en anglais).', + 'HELP_FAQ_ISSUES_WHOIS_PHPBB_QUESTION' => 'Qui a développé ce logiciel de forum de discussions ?', + + 'HELP_FAQ_LOGIN_AUTO_LOGOUT_ANSWER' => 'Si vous ne cochez pas la case « Se souvenir de moi » lors de votre connexion au forum, vous ne resterez connecté que pour une période prédéfinie. Cela permet d’éviter que votre compte soit utilisé par quelqu’un d’autre. Pour rester connecté, veuillez cocher la case « Se souvenir de moi » lors de votre connexion au forum. Ceci n’est pas recommandé si vous accédez au forum depuis un ordinateur public, comme une librairie, un cybercafé, une université, etc. Si vous n’arrivez pas à trouver cette case à cocher, il est probable qu’un administrateur du forum ait désactivé cette fonctionnalité.', + 'HELP_FAQ_LOGIN_AUTO_LOGOUT_QUESTION' => 'Pourquoi suis-je déconnecté automatiquement ?', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANSWER' => 'Plusieurs raisons peuvent en être la cause. Assurez-vous avant tout que votre nom d’utilisateur et votre mot de passe soient corrects. Si tel est le cas, contactez un administrateur du forum afin de vous assurer de ne pas avoir été banni. Il est également possible que le propriétaire du site internet ait un problème de configuration et qu’il soit nécessaire de la corriger.', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_ANSWER' => 'Il est possible qu’un administrateur ait désactivé ou supprimé votre compte pour une quelconque raison. De plus, beaucoup de forums suppriment périodiquement les utilisateurs inactifs afin de réduire la taille de leur base de données. Si tel était le cas, inscrivez-vous de nouveau et essayez de participer plus activement aux discussions du forum.', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_QUESTION' => 'Je m’étais déjà inscrit par le passé mais ne peux à présent plus me connecter ?!', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_QUESTION' => 'Pourquoi ne puis-je pas me connecter ?', + 'HELP_FAQ_LOGIN_CANNOT_REGISTER_ANSWER' => 'Il est possible qu’un administrateur du forum ait désactivé les inscriptions afin d’empêcher les nouveaux visiteurs de s’inscrire. De même, il est également possible qu’un administrateur du forum ait banni votre adresse IP ou interdit l’utilisation du nom d’utilisateur que vous souhaitez utiliser. Pour plus d’informations, veuillez contacter un administrateur du forum.', + 'HELP_FAQ_LOGIN_CANNOT_REGISTER_QUESTION' => 'Pourquoi ne puis-je pas m’inscrire ?', + 'HELP_FAQ_LOGIN_COPPA_ANSWER' => 'La COPPA (pour « Child Online Privacy and Protection Act ») est une loi des États-Unis d’Amérique qui demande aux sites internet collectant potentiellement des informations sur les mineurs âgés de moins de 13 ans un consentement écrit des parents ou des tuteurs légaux des mineurs concernés. Si vous ne savez pas si cette loi s’applique également aux mineurs âgés de moins de 13 ans inscrits sur votre forum, nous vous conseillons de contacter un conseiller juridique qui pourra vous renseigner. Veuillez noter que phpBB Limited et que les propriétaires de ce forum ne peuvent pas vous proposer d’assistance légale et ne doivent donc pas être contactés à ce sujet, excepté lorsque l’assistance porte sur la question « Qui dois-je contacter à propos de problèmes d’abus ou d’ordres légaux liés à ce forum ? ».', + 'HELP_FAQ_LOGIN_COPPA_QUESTION' => 'Qu’est-ce que la COPPA ?', + 'HELP_FAQ_LOGIN_DELETE_COOKIES_ANSWER' => 'Cette option vous permet d’effacer tous les cookies générés par phpBB 3.2 qui conservent votre authentification et votre connexion au forum. Les cookies permettent également d’enregistrer le statut des messages (s’ils sont lus ou non lus) dans le cas où cette fonctionnalité a été activée par un administrateur du forum. Si vous rencontrez des problèmes récurrents de connexion et de déconnexion au forum, essayez de supprimer les cookies.', + 'HELP_FAQ_LOGIN_DELETE_COOKIES_QUESTION' => 'À quoi sert « Supprimer les cookies » ?', + 'HELP_FAQ_LOGIN_LOST_PASSWORD_ANSWER' => 'Pas de panique ! Bien que votre mot de passe ne puisse pas être récupéré, il peut facilement être réinitialisé. Veuillez vous rendre sur la page de connexion et cliquer sur « J’ai perdu mon mot de passe ». Suivez les instructions et vous devriez être en mesure de pouvoir vous connecter de nouveau rapidement.
Cependant, si vous ne pouvez pas réinitialiser votre mot de passe, nous vous invitons à contacter un administrateur du forum.', + 'HELP_FAQ_LOGIN_LOST_PASSWORD_QUESTION' => 'J’ai perdu mon mot de passe !', + 'HELP_FAQ_LOGIN_REGISTER_ANSWER' => 'Vous n’êtes pas dans l’obligation de le faire, mais les administrateurs du forum peuvent limiter la publication de messages aux utilisateurs inscrits. En vous inscrivant, vous pouvez également avoir accès à des fonctionnalités supplémentaires qui ne sont pas disponibles aux visiteurs, tels que l’affichage d’avatars personnalisés, l’utilisation de la messagerie privée, l’envoi de courriers électroniques aux autres utilisateurs, l’adhésion à un groupe d’utilisateurs, etc. L’inscription ne vous prend qu’un court instant, c’est pourquoi nous vous recommandons de le faire.', + 'HELP_FAQ_LOGIN_REGISTER_CONFIRM_ANSWER' => 'Vérifiez en premier lieu que votre nom d’utilisateur et votre mot de passe soient corrects. Si la fonctionnalité de la COPPA est activée et que vous avez spécifié avoir en dessous de 13 ans pendant l’inscription, vous devrez suivre les instructions que vous avez reçues. Certains forums exigeront également que les nouvelles inscriptions doivent être activées, soit par vous-même ou soit par un administrateur, avant que vous puissiez ouvrir une session ; cette information était présente lors de votre inscription. Si vous aviez reçu un courrier électronique, consultez les instructions. Si vous ne recevez pas de courrier électronique, vous avez probablement spécifié une mauvaise adresse de courrier électronique ou le courrier électronique a été filtré en tant que pourriel. Si vous êtes certain que l’adresse de courrier électronique que vous avez spécifiée était correcte, essayez de contacter un administrateur du forum.', + 'HELP_FAQ_LOGIN_REGISTER_CONFIRM_QUESTION' => 'Je suis inscrit mais je ne peux pas me connecter !', + 'HELP_FAQ_LOGIN_REGISTER_QUESTION' => 'Pourquoi ai-je besoin de m’inscrire ?', + + 'HELP_FAQ_PMS_CANNOT_SEND_ANSWER' => 'Soit vous n’êtes pas inscrit et connecté, soit un administrateur a désactivé entièrement la messagerie privée sur le forum, soit un administrateur ou un modérateur a décidé de vous empêcher d’envoyer des messages privés. Pour plus d’informations, veuillez contacter un administrateur du forum.', + 'HELP_FAQ_PMS_CANNOT_SEND_QUESTION' => 'Je ne peux pas envoyer de messages privés !', + 'HELP_FAQ_PMS_SPAM_ANSWER' => 'Nous en sommes navrés. Le formulaire d’envoi de courriers électroniques de ce forum possède des protections qui essaient de repérer les utilisateurs envoyant de tels messages. Vous devriez envoyer par courrier électronique une copie complète du courrier électronique que vous avez reçu à un administrateur du forum. Il est très important d’y inclure les en-têtes contenant des informations sur l’auteur du courrier électronique. Il pourra alors agir en conséquence.', + 'HELP_FAQ_PMS_SPAM_QUESTION' => 'J’ai reçu un courrier électronique indésirable de la part de quelqu’un sur ce forum !', + 'HELP_FAQ_PMS_UNWANTED_ANSWER' => 'Vous pouvez supprimer automatiquement les messages privés d’un utilisateur en utilisant les règles de messages depuis le panneau de contrôle de l’utilisateur. Si vous recevez des messages privés de manière abusive de la part d’un autre utilisateur, rapportez ces messages aux modérateurs. Ils peuvent empêcher un utilisateur d’envoyer des messages privés.', + 'HELP_FAQ_PMS_UNWANTED_QUESTION' => 'Je continue à recevoir des messages privés non sollicités !', + + 'HELP_FAQ_POSTING_BUMP_ANSWER' => 'En cliquant sur le lien « Remonter le sujet » lorsque vous êtes en train de consulter un sujet, vous pouvez remonter celui-ci en haut de la liste des sujets, à la première page du forum. Cependant, si vous ne voyez pas ce lien, cette fonctionnalité a peut-être été désactivée ou le temps d’attente nécessaire entre les remontées n’a peut-être pas encore été atteint. Il est également possible de remonter le sujet simplement en y répondant, mais assurez-vous de le faire tout en respectant les règles du forum.', + 'HELP_FAQ_POSTING_BUMP_QUESTION' => 'Comment puis-je remonter mes sujets ?', + 'HELP_FAQ_POSTING_CREATE_ANSWER' => 'Pour publier un nouveau sujet dans un forum, cliquez sur le bouton « Nouveau sujet ». Pour publier une réponse à un sujet ou un message, cliquez sur le bouton « Répondre ». Il se peut que vous ayez besoin d’être inscrit avant de pouvoir rédiger un message. Sur chaque forum, une liste de vos permissions est affichée en bas de l’écran du forum ou du sujet. Par exemple : vous pouvez publier de nouveaux sujets dans ce forum, vous pouvez transférer des pièces jointes dans ce forum, etc.', + 'HELP_FAQ_POSTING_CREATE_QUESTION' => 'Comment puis-je publier un nouveau sujet ou une réponse ?', + 'HELP_FAQ_POSTING_DRAFT_ANSWER' => 'Il vous permet d’enregistrer comme brouillons les messages que vous souhaitez finaliser et publier ultérieurement. Vous pouvez reprendre les messages enregistrés comme brouillons depuis le panneau de contrôle de l’utilisateur.', + 'HELP_FAQ_POSTING_DRAFT_QUESTION' => 'À quoi sert le bouton « Enregistrer comme brouillon » affiché lors de la rédaction d’un sujet ?', + 'HELP_FAQ_POSTING_EDIT_DELETE_ANSWER' => 'À moins que vous ne soyez un administrateur ou un modérateur du forum, vous ne pouvez modifier ou supprimer que vos propres messages. Vous pouvez modifier un de vos messages en cliquant le bouton adéquat, parfois dans une limite de temps après que le message initial ait été publié. Si quelqu’un a déjà répondu à votre message, un petit texte situé en dessous du message affichera le nombre de fois que vous l’avez modifié, contenant la date et l’heure de la modification. Ce petit texte n’apparaîtra pas s’il s’agit d’une modification effectuée par un modérateur ou un administrateur, bien qu’ils puissent rédiger une raison discrète concernant leur modification. Veuillez noter que les utilisateurs normaux ne peuvent pas supprimer leur propre message si une réponse a été publiée.', + 'HELP_FAQ_POSTING_EDIT_DELETE_QUESTION' => 'Comment puis-je modifier ou supprimer un message ?', + 'HELP_FAQ_POSTING_FORUM_RESTRICTED_ANSWER' => 'Certains forums peuvent être limités à certains utilisateurs ou groupes d’utilisateurs. Pour consulter, rédiger, publier ou réaliser n’importe quelle autre action, vous avez besoin des permissions adéquates. Essayez de contacter un modérateur ou un administrateur du forum afin de lui demander un accès.', + 'HELP_FAQ_POSTING_FORUM_RESTRICTED_QUESTION' => 'Pourquoi ne puis-je pas accéder à un forum ?', + 'HELP_FAQ_POSTING_NO_ATTACHMENTS_ANSWER' => 'Les permissions permettant de transférer des pièces jointes sont accordées par forum, par groupe ou par utilisateur. Les administrateurs du forum ont peut-être désactivé le transfert de pièces jointes dans le forum concerné, ou seuls certains groupes d’utilisateurs détiennent cette autorisation. Pour plus d’informations, veuillez contacter un administrateur du forum.', + 'HELP_FAQ_POSTING_NO_ATTACHMENTS_QUESTION' => 'Pourquoi ne puis-je pas transférer de pièces jointes ?', + 'HELP_FAQ_POSTING_POLL_ADD_ANSWER' => 'La limite d’options d’un sondage est décidée par les administrateurs du forum. Si le nombre d’options que vous pouvez ajouter à un sondage vous semble trop restreint, essayez de demander à un administrateur du forum s’il est possible de l’augmenter.', + 'HELP_FAQ_POSTING_POLL_ADD_QUESTION' => 'Pourquoi ne puis-je pas ajouter plus d’options à un sondage ?', + 'HELP_FAQ_POSTING_POLL_CREATE_ANSWER' => 'Lorsque vous rédigez un nouveau sujet ou modifiez le premier message d’un sujet, cliquez sur l’onglet « Créer un sondage » situé en-dessous du formulaire principal de rédaction. Si cet onglet n’est pas disponible, il est probable que vous n’ayez pas la permission de créer des sondages. Saisissez le titre du sondage en incluant au moins deux options dans les champs adéquats, chaque option devant être insérée sur une nouvelle ligne. Vous pouvez définir le nombre d’options que les utilisateurs peuvent insérer en modifiant, lors du vote, le nombre des « Options par utilisateur ». Vous pouvez également spécifier une limite de temps en jours et autoriser ou non les utilisateurs à modifier leurs votes.', + 'HELP_FAQ_POSTING_POLL_CREATE_QUESTION' => 'Comment puis-je créer un sondage ?', + 'HELP_FAQ_POSTING_POLL_EDIT_ANSWER' => 'Comme pour les messages, les sondages ne peuvent être modifiés que par leur auteur, les modérateurs et les administrateurs. Pour modifier un sondage, modifiez tout simplement le premier message du sujet car le sondage est obligatoirement associé à ce dernier. Si personne n’a encore voté, il est possible de supprimer le sondage ou de modifier ses options. Cependant, si des votes ont été exprimés, seuls les modérateurs et les administrateurs peuvent modifier ou supprimer le sondage. Cela empêche de modifier les options d’un sondage en cours.', + 'HELP_FAQ_POSTING_POLL_EDIT_QUESTION' => 'Comment puis-je modifier ou supprimer un sondage ?', + 'HELP_FAQ_POSTING_QUEUE_ANSWER' => 'Les administrateurs du forum peuvent décider de soumettre à des vérifications les messages que vous rédigez sur le forum. Il est également possible que vous ayez été placé dans un groupe d’utilisateurs aux permissions limitées. Pour plus d’informations, veuillez contacter un administrateur du forum.', + 'HELP_FAQ_POSTING_QUEUE_QUESTION' => 'Pourquoi mon message a-t-il besoin d’être approuvé ?', + 'HELP_FAQ_POSTING_REPORT_ANSWER' => 'Si les administrateurs du forum ont activé cette fonctionnalité, un bouton dédié devrait être affiché à côté du message que vous souhaitez rapporter. En cliquant sur celui-ci, vous trouverez toutes les étapes nécessaires afin de rapporter le message.', + 'HELP_FAQ_POSTING_REPORT_QUESTION' => 'Comment puis-je rapporter des messages à un modérateur ?', + 'HELP_FAQ_POSTING_SIGNATURE_ANSWER' => 'Pour insérer une signature à un de vos messages, vous devez tout d’abord en créer une depuis le panneau de contrôle de l’utilisateur. Une fois créée, vous pouvez cocher la case « Insérer une signature » depuis le formulaire de rédaction afin d’insérer votre signature. Vous pouvez également ajouter une signature qui sera insérée à tous vos messages en cochant la case appropriée dans le panneau de contrôle de l’utilisateur. Si vous choisissez cette dernière option, il ne vous sera plus utile de spécifier sur chaque message votre souhait d’insérer votre signature.', + 'HELP_FAQ_POSTING_SIGNATURE_QUESTION' => 'Comment puis-je insérer une signature à mon message ?', + 'HELP_FAQ_POSTING_WARNING_ANSWER' => 'Chaque forum a son propre ensemble de règles. Si vous ne respectez pas une de ces règles, vous recevrez un avertissement. Veuillez noter que cette décision n’appartient qu’aux administrateurs du forum, phpBB Limited n’est en aucun cas responsable du règlement instauré sur cet espace. Pour plus d’informations, veuillez contacter un administrateur du forum.', + 'HELP_FAQ_POSTING_WARNING_QUESTION' => 'Pourquoi ai-je reçu un avertissement ?', + + 'HELP_FAQ_SEARCH_BLANK_ANSWER' => 'Votre recherche a renvoyé trop de résultats pour que le serveur puisse les afficher. Utilisez la recherche avancée et essayez d’être plus précis dans les termes employés et dans la sélection des forums dans lesquels vous souhaitez effectuer une recherche.', + 'HELP_FAQ_SEARCH_BLANK_QUESTION' => 'Pourquoi ma recherche renvoie à une page blanche ?!', + 'HELP_FAQ_SEARCH_FORUM_ANSWER' => 'Saisissez un terme dans la boîte de recherche située sur l’index, les pages des forums ou les pages de sujets. La recherche avancée est accessible en cliquant sur le lien « Recherche avancée » disponible sur toutes les pages du forum. L’accès à la recherche dépend du style utilisé.', + 'HELP_FAQ_SEARCH_FORUM_QUESTION' => 'Comment puis-je effectuer une recherche dans un ou des forums ?', + 'HELP_FAQ_SEARCH_MEMBERS_ANSWER' => 'Veuillez vous rendre sur la page « Membres » puis cliquer sur le lien « Trouver un membre ».', + 'HELP_FAQ_SEARCH_MEMBERS_QUESTION' => 'Comment puis-je rechercher des membres ?', + 'HELP_FAQ_SEARCH_NO_RESULT_ANSWER' => 'Votre recherche était probablement trop vague ou incluait trop de termes communs qui ne sont pas indexés par phpBB. Essayez d’être plus précis et d’utiliser les différents filtres disponibles dans la recherche avancée.', + 'HELP_FAQ_SEARCH_NO_RESULT_QUESTION' => 'Pourquoi ma recherche ne renvoie aucun résultat ?', + 'HELP_FAQ_SEARCH_OWN_ANSWER' => 'Vos propres messages peuvent être affichés soit en cliquant sur le lien « Afficher vos messages » dans le panneau de contrôle de l’utilisateur, soit en cliquant sur le lien « Rechercher les messages de l’utilisateur » sur la page de votre propre profil ou soit en cliquant sur le menu « Raccourcis » situé sur la partie supérieure du forum. Pour effectuer une recherche de vos propres sujets, utilisez la recherche avancée et remplissez convenablement les options qui vous sont disponibles.', + 'HELP_FAQ_SEARCH_OWN_QUESTION' => 'Comment puis-je retrouver mes propres messages et sujets ?', + + 'HELP_FAQ_USERSETTINGS_AVATAR_ANSWER' => 'Deux images peuvent apparaître à côté de votre nom d’utilisateur lorsque vous consultez un sujet. Une d’elles peut être une image associée à votre rang, généralement représentée par des étoiles, des carrés ou des ronds. Elle permet d’indiquer votre activité selon le nombre de messages que vous avez publié, ou permet de différencier votre statut particulier sur le forum. L’autre image, généralement plus grande, est une image connue sous le nom d’avatar qui est bien souvent unique et personnelle à chaque utilisateur.', + 'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_ANSWER' => 'Dans le panneau de contrôle de l’utilisateur, sous « Profil », vous pouvez ajouter un avatar en utilisant une des quatre méthodes suivantes : le service « Gravatar », la galerie d’avatars, les images distantes ou le transfert d’images locales. Les administrateurs du forum peuvent activer ou non la fonctionnalité des avatars et des méthodes qu’ils veuillent rendre disponible aux utilisateurs. Si vous ne pouvez pas utiliser d’avatars, nous vous invitons à contacter un administrateur du forum.', + 'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_QUESTION' => 'Comment puis-je afficher un avatar ?', + 'HELP_FAQ_USERSETTINGS_AVATAR_QUESTION' => 'Que signifient les images situées à côté de mon nom d’utilisateur ?', + 'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_ANSWER' => 'Si vous êtes un utilisateur inscrit, tous vos paramètres sont stockés dans la base de données du forum. Vous pouvez les modifier depuis le panneau de contrôle de l’utilisateur. Le lien vers ce dernier se trouve généralement en cliquant sur votre nom d’utilisateur situé en haut des pages du forum. Ce système vous permettra de modifier tous vos paramètres et toutes vos préférences.', + 'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_QUESTION' => 'Comment puis-je modifier mes paramètres ?', + 'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_ANSWER' => 'Si les administrateurs ont activé cette fonctionnalité, seuls les utilisateurs inscrits peuvent envoyer des courriers électroniques aux autres utilisateurs depuis un formulaire dédié. Cela permet d’empêcher une utilisation abusive du système de messagerie électronique par des utilisateurs malveillants ou des robots.', + 'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_QUESTION' => 'Pourquoi m’est-il demandé de me connecter lorsque je clique sur le lien de courrier électronique d’un utilisateur ?', + 'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_ANSWER' => 'Dans le panneau de contrôle de l’utilisateur, sous « Préférences du forum », vous trouverez l’option « Masquer mon statut en ligne ». Si vous activez cette option, vous ne serez visible que des administrateurs, des modérateurs et de vous-même. Vous serez alors comptabilisé comme étant un utilisateur invisible.', + 'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_QUESTION' => 'Comment puis-je masquer mon nom d’utilisateur de la liste des utilisateurs en ligne ?', + 'HELP_FAQ_USERSETTINGS_LANGUAGE_ANSWER' => 'Soit les administrateurs n’ont pas installé votre langue sur le forum, soit le logiciel n’a pas encore été traduit dans votre langue. Essayez de demander à un administrateur du forum s’il est possible qu’il puisse installer la langue que vous souhaitez. Si la traduction désirée n’existe pas, vous êtes libre de vous porter volontaire et commencer une nouvelle traduction. Pour plus d’informations, veuillez vous rendre sur le site internet de phpBB® (en anglais).', + 'HELP_FAQ_USERSETTINGS_LANGUAGE_QUESTION' => 'Ma langue n’apparaît pas dans la liste !', + 'HELP_FAQ_USERSETTINGS_RANK_ANSWER' => 'Les rangs, qui apparaissent en dessous de votre nom d’utilisateur, indiquent votre activité selon le nombre de messages que vous avez publié ou identifient certains utilisateurs spécifiques, comme les administrateurs et les modérateurs. Dans la plupart des cas, seul un administrateur du forum peut modifier le texte des rangs du forum. Merci de ne pas abuser de ce système en publiant inutilement des messages afin d’augmenter votre rang sur le forum. Beaucoup de forums ne toléreront pas ce procédé et un administrateur ou un modérateur pourra vous sanctionner en abaissant votre compteur de messages.', + 'HELP_FAQ_USERSETTINGS_RANK_QUESTION' => 'Quel est mon rang et comment puis-je le modifier ?', + 'HELP_FAQ_USERSETTINGS_SERVERTIME_ANSWER' => 'Si vous êtes certain d’avoir correctement réglé le fuseau horaire mais que l’heure n’est toujours pas correcte, il est probable que l’horloge du serveur soit erronée. Veuillez contacter un administrateur afin de lui communiquer ce problème.', + 'HELP_FAQ_USERSETTINGS_SERVERTIME_QUESTION' => 'J’ai réglé le fuseau horaire mais l’heure n’est toujours pas correcte !', + 'HELP_FAQ_USERSETTINGS_TIMEZONE_ANSWER' => 'Il est possible que l’heure affichée soit réglée sur un fuseau horaire différent du vôtre. Si tel était le cas, veuillez vous rendre dans le panneau de contrôle de l’utilisateur et régler le fuseau horaire afin de trouver votre zone adéquate, par exemple Londres, Paris, New York, Sydney, etc. Veuillez noter que le réglage du fuseau horaire, comme la plupart des autres paramètres, n’est accessible qu’aux utilisateurs inscrits. Si vous n’êtes pas inscrit, c’est l’occasion idéale de le faire.', + 'HELP_FAQ_USERSETTINGS_TIMEZONE_QUESTION' => 'L’heure n’est pas correcte !', +]); diff --git a/language/fr/index.htm b/language/fr/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/language/fr/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/language/fr/install.php b/language/fr/install.php new file mode 100644 index 0000000..e232291 --- /dev/null +++ b/language/fr/install.php @@ -0,0 +1,596 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Common installer pages +$lang = array_merge($lang, [ + 'INSTALL_PANEL' => 'Panneau d’installation', + 'SELECT_LANG' => 'Sélectionner la langue', + + 'STAGE_INSTALL' => 'Installation de phpBB', + + // Introduction page + 'INTRODUCTION_TITLE' => 'Introduction', + 'INTRODUCTION_BODY' => 'Bienvenue sur phpBB 3.2 !

phpBB® est le logiciel de forum de discussions libre et gratuit le plus populaire dans le monde. Il est l’aboutissement d’un développement qui a débuté en 2000. Tout comme ses versions précédentes, phpBB 3.2 est riche en fonctionnalités, facile d’accès et entièrement pris en charge par phpBB Limited. Il améliore considérablement ce qui a rendu phpBB 2.0 populaire et ajoute des fonctionnalités demandées qui manquaient aux versions antérieures. Nous espérons avoir répondu convenablement à vos attentes.

Cet assistant vous guidera au cours de l’installation et de la mise à jour du logiciel, ainsi que la conversion d’un logiciel de forum de discussions (dont phpBB 2.0) vers phpBB 3.2. Pour plus d’informations, veuillez consulter le guide d’installation (en anglais).

Si vous souhaitez vous renseigner sur la licence du logiciel ou sur l’assistance mise à votre disposition, veuillez sélectionner les éléments appropriés situés sur le menu latéral de gauche. Pour continuer, veuillez sélectionner l’onglet approprié situé en haut de cette page.', + + // Support page + 'SUPPORT_TITLE' => 'Assistance', + 'SUPPORT_BODY' => 'La dernière version stable de phpBB 3 vous permet de bénéficier d’une assistance complète et gratuite comprenant :

  • l’installation
  • la configuration
  • les questions techniques
  • les problèmes en relation avec de potentiels bogues du logiciel
  • la mise à jour d’une version « Release Candidate » (RC) vers la dernière version stable
  • la conversion de phpBB 2.0 vers phpBB 3.2
  • la conversion d’un logiciel de forum de discussions vers phpBB 3.2 (veuillez consulter le forum relatif aux convertisseurs (en anglais))

Nous encourageons vivement les utilisateurs des versions « Beta » de phpBB 3.2 à procéder à une nouvelle installation vers la dernière version stable.

Extensions et styles

Concernant les problèmes relatifs aux extensions, veuillez publier vos questions dans le forum relatif aux extensions (en anglais).
Concernant les problèmes relatifs aux styles, aux modèles et aux thèmes, veuillez publier vos questions dans le forum relatif aux styles (en anglais).

Si votre question est spécifique à une archive, veuillez publier directement votre demande dans le sujet dédié à cette archive.

Assistance


La section d’assistance (en anglais)
Le guide de démarrage rapide (en anglais)

Si vous souhaitez vous assurer d’être tenu au courant des dernières versions et nouveautés de notre logiciel, suivez-nous Twitter (en anglais) et Facebook (en anglais).

', + + // License + 'LICENSE_TITLE' => 'Licence publique générale', + + // Install page + 'INSTALL_INTRO' => 'Bienvenue sur l’assistant d’installation', + 'INSTALL_INTRO_BODY' => 'Cet assistant va vous permettre d’installer phpBB 3.2 sur votre serveur.

Avant de continuer, assurez-vous d’avoir à disposition les informations concernant votre base de données. Si vous ne les connaissez pas, veuillez les demander à votre hébergeur. Vous aurez besoin des informations suivantes :

+ +
    +
  • Le type de votre base de données
  • +
  • Le nom d’hôte du serveur de votre base de données ou le DSN (l’adresse du serveur de votre base de données)
  • +
  • Le port du serveur de votre base de données (dans la plupart des cas, il est facultatif)
  • +
  • Le nom de votre base de données
  • +
  • Le nom d’utilisateur et le mot de passe de votre base de données (qui vous permettent de vous connecter pour accéder à votre base de données)
  • +
+ +

Attention : si vous procédez à une installation en utilisant SQLite, vous devrez saisir le chemin complet de votre fichier de base de données dans le champ DSN et laisser les champs du nom d’utilisateur et du mot de passe vides. Pour des raisons de sécurité, vous devriez vous assurer que le fichier de base de données ne soit pas situé dans un endroit accessible sur internet.

+ +

phpBB 3.2 est compatible avec les bases de données suivantes :

+
    +
  • MySQL 3.23+ (MySQLi pris en charge)
  • +
  • PostgreSQL 8.3+
  • +
  • SQLite 3.6.15+
  • +
  • MS SQL Server 2000+ (directement ou depuis ODBC)
  • +
  • MS SQL Server 2005+ (en natif)
  • +
  • Oracle
  • +
+ +

Seules les bases de données qui sont compatibles avec votre serveur seront proposées.', + + 'ACP_LINK' => 'Me rendre sur le PCA', + + 'INSTALL_PHPBB_INSTALLED' => 'phpBB est déjà installé.', + 'INSTALL_PHPBB_NOT_INSTALLED' => 'phpBB n’est pas encore installé.', +]); + +// Requirements translation +$lang = array_merge($lang, [ + // Filesystem requirements + 'FILE_NOT_EXISTS' => 'Le fichier est introuvable', + 'FILE_NOT_EXISTS_EXPLAIN' => 'Assurez-vous que le fichier « %1$s » existe afin d’installer phpBB.', + 'FILE_NOT_EXISTS_EXPLAIN_OPTIONAL' => 'Il est recommandé que le fichier « %1$s » existe afin de bénéficier d’une meilleure expérience utilisateur.', + 'FILE_NOT_WRITABLE' => 'Le fichier est en lecture seule', + 'FILE_NOT_WRITABLE_EXPLAIN' => 'Les droits d’accès du fichier « %1$s » doivent être modifiés pour écriture par votre serveur afin d’installer phpBB.', + 'FILE_NOT_WRITABLE_EXPLAIN_OPTIONAL' => 'Il est recommandé de modifier les droits d’accès du fichier « %1$s » pour écriture par votre serveur afin de bénéficier d’une meilleure expérience utilisateur.', + + 'DIRECTORY_NOT_EXISTS' => 'Le répertoire est introuvable', + 'DIRECTORY_NOT_EXISTS_EXPLAIN' => 'Assurez-vous que le répertoire « %1$s » existe afin d’installer phpBB.', + 'DIRECTORY_NOT_EXISTS_EXPLAIN_OPTIONAL' => 'Il est recommandé que le répertoire « %1$s » existe afin de bénéficier d’une meilleure expérience utilisateur.', + 'DIRECTORY_NOT_WRITABLE' => 'Le répertoire est en lecture seule', + 'DIRECTORY_NOT_WRITABLE_EXPLAIN' => 'Les droits d’accès du répertoire « %1$s » doivent être modifiés pour écriture par votre serveur afin d’installer phpBB.', + 'DIRECTORY_NOT_WRITABLE_EXPLAIN_OPTIONAL' => 'Il est recommandé de modifier les droits d’accès du répertoire « %1$s » pour écriture par votre serveur afin de bénéficier d’une meilleure expérience utilisateur.', + + // Server requirements + 'PHP_VERSION_REQD' => 'Version de PHP', + 'PHP_VERSION_REQD_EXPLAIN' => 'phpBB requiert la version 5.4.0 ou supérieure de PHP.', + 'PHP_GETIMAGESIZE_SUPPORT' => 'La fonction PHP « getimagesize() » est requise.', + 'PHP_GETIMAGESIZE_SUPPORT_EXPLAIN' => 'La fonction PHP « getimagesize() » est disponible.', + 'PCRE_UTF_SUPPORT' => 'Support de PCRE UTF-8', + 'PCRE_UTF_SUPPORT_EXPLAIN' => 'Votre installation PHP doit être compilée avec la prise en charge d’UTF-8 dans l’extension PCRE.', + 'PHP_JSON_SUPPORT' => 'Support de PHP JSON', + 'PHP_JSON_SUPPORT_EXPLAIN' => 'L’extension PHP « JSON » est requise.', + 'PHP_XML_SUPPORT' => 'Support de PHP XML/DOM', + 'PHP_XML_SUPPORT_EXPLAIN' => 'L’extension PHP « XML/DOM » est requise.', + 'PHP_SUPPORTED_DB' => 'Base de données prises en charge', + 'PHP_SUPPORTED_DB_EXPLAIN' => 'Votre serveur doit prendre en charge au moins une base de données compatible avec PHP. Si aucun module de base de données n’est disponible, veuillez contacter votre hébergeur ou vous renseigner en consultant la documentation sur PHP.', + + 'RETEST_REQUIREMENTS' => 'Vérifier de nouveau les prérequis', + + 'STAGE_REQUIREMENTS' => 'Vérification des prérequis', +]); + +// General error messages +$lang = array_merge($lang, [ + 'INST_ERR_MISSING_DATA' => 'Vous devez remplir tous les champs de ce bloc.', + + 'TIMEOUT_DETECTED_TITLE' => 'Le délai d’attente de l’assistant d’installation a expiré', + 'TIMEOUT_DETECTED_MESSAGE' => 'Le délai d’attente de l’assistant d’installation a expiré. Vous pouvez essayer d’actualiser la page mais cela pourrait corrompre les données. Nous vous conseillons d’augmenter le délai d’attente de votre serveur ou d’utiliser l’interface en ligne de commande.', +]); + +// Data obtaining translations +$lang = array_merge($lang, [ + 'STAGE_OBTAIN_DATA' => 'Collecte des données d’installation', + + // + // Admin data + // + 'STAGE_ADMINISTRATOR' => 'Informations sur l’administrateur', + + // Form labels + 'ADMIN_CONFIG' => 'Configuration de l’administrateur', + 'ADMIN_PASSWORD' => 'Mot de passe de l’administrateur', + 'ADMIN_PASSWORD_CONFIRM' => 'Confirmer le mot de passe de l’administrateur', + 'ADMIN_PASSWORD_EXPLAIN' => 'Veuillez saisir un mot de passe entre 6 et 30 caractères de long.', + 'ADMIN_USERNAME' => 'Nom d’utilisateur de l’administrateur', + 'ADMIN_USERNAME_EXPLAIN' => 'Veuillez saisir un nom d’utilisateur entre 3 et 20 caractères de long.', + + // Errors + 'INST_ERR_EMAIL_INVALID' => 'L’adresse de courriel saisie est invalide.', + 'INST_ERR_PASSWORD_MISMATCH' => 'Les mots de passe saisis ne correspondent pas.', + 'INST_ERR_PASSWORD_TOO_LONG' => 'Le mot de passe est trop long. Il ne doit pas dépasser 30 caractères.', + 'INST_ERR_PASSWORD_TOO_SHORT' => 'Le mot de passe est trop court. Il doit contenir au moins 6 caractères.', + 'INST_ERR_USER_TOO_LONG' => 'Le nom d’utilisateur est trop long. Il ne doit pas dépasser 20 caractères.', + 'INST_ERR_USER_TOO_SHORT' => 'Le nom d’utilisateur est trop court. Il doit contenir au moins 3 caractères.', + + // + // Board data + // + // Form labels + 'BOARD_CONFIG' => 'Configuration de votre forum', + 'DEFAULT_LANGUAGE' => 'Langue par défaut', + 'BOARD_NAME' => 'Titre de votre forum', + 'BOARD_DESCRIPTION' => 'Courte description de votre forum', + + // + // Database data + // + 'STAGE_DATABASE' => 'Paramètres de la base de données', + + // Form labels + 'DB_CONFIG' => 'Configuration de la base de données', + 'DBMS' => 'Type de base de données', + 'DB_HOST' => 'Nom d’hôte du serveur de votre base de données ou DSN', + 'DB_HOST_EXPLAIN' => 'Le DSN est réservé aux installations de type ODBC. Sur PostgreSQL, saisissez « localhost » afin de vous connecter sur le serveur local depuis le connecteur du domaine UNIX ou saisissez « 127.0.0.1 » afin de vous connecter depuis TCP. Sur SQLite, saisissez le chemin complet vers le fichier de votre base de données.', + 'DB_PORT' => 'Port du serveur de votre base de données', + 'DB_PORT_EXPLAIN' => 'Laissez ce champ vide à moins que votre serveur utilise un port différent dont vous avez connaissance.', + 'DB_PASSWORD' => 'Mot de passe de votre base de données', + 'DB_NAME' => 'Nom de votre base de données', + 'DB_USERNAME' => 'Nom d’utilisateur de votre base de données', + 'DATABASE_VERSION' => 'Version de votre base de données', + 'TABLE_PREFIX' => 'Préfixe des tables de votre base de données', + 'TABLE_PREFIX_EXPLAIN' => 'Le préfixe doit démarrer par une lettre et ne doit contenir que des lettres, des nombres et des tirets bas.', + + // Database options + 'DB_OPTION_MSSQL_ODBC' => 'MSSQL Server 2000+ via ODBC', + 'DB_OPTION_MSSQLNATIVE' => 'MSSQL Server 2005+ [ Natif ]', + 'DB_OPTION_MYSQL' => 'MySQL', + 'DB_OPTION_MYSQLI' => 'MySQL avec l’extension MySQLi', + 'DB_OPTION_ORACLE' => 'Oracle', + 'DB_OPTION_POSTGRES' => 'PostgreSQL', + 'DB_OPTION_SQLITE3' => 'SQLite 3', + + // Errors + 'INST_ERR_DB' => 'Une erreur est survenue lors de l’installation de la base de données', + 'INST_ERR_NO_DB' => 'Impossible de charger le module PHP concernant ce type de base de données.', + 'INST_ERR_DB_INVALID_PREFIX' => 'Ce préfixe est invalide. Il doit démarrer par une lettre et ne doit contenir que des lettres, des nombres et des tirets bas.', + 'INST_ERR_PREFIX_TOO_LONG' => 'Ce préfixe est trop long. Il ne doit pas dépasser %d caractères.', + 'INST_ERR_DB_NO_NAME' => 'Le nom de la base de données doit être spécifié.', + 'INST_ERR_DB_FORUM_PATH' => 'Le fichier de la base de données est situé à la racine du répertoire de votre forum. Vous devriez déplacer ce fichier à un endroit qui ne soit pas accessible sur internet.', + 'INST_ERR_DB_CONNECT' => 'Impossible de se connecter à la base de données. Veuillez consulter le message d’erreur ci-dessous.', + 'INST_ERR_DB_NO_WRITABLE' => 'La base de données et le répertoire sont tous deux en lecture seule.', + 'INST_ERR_DB_NO_ERROR' => 'Aucune erreur n’est survenue.', + 'INST_ERR_PREFIX' => 'Des tables contenant ce préfixe existent déjà. Veuillez le renommer.', + 'INST_ERR_DB_NO_MYSQLI' => 'La version de MySQL installée sur votre serveur est incompatible avec l’option « MySQL avec l’extension MySQLi ». Veuillez sélectionner l’option « MySQL ».', + 'INST_ERR_DB_NO_SQLITE3' => 'La version de l’extension SQLite installée sur votre serveur est obsolète. Elle doit être mise à jour vers la version 3.6.15 ou supérieure.', + 'INST_ERR_DB_NO_ORACLE' => 'La version d’Oracle installée sur votre serveur nécessite de définir « NLS_CHARACTERSET » sur « UTF8 ». Veuillez modifier cette configuration ou mettre à jour votre version vers la version 9.2 ou supérieure.', + 'INST_ERR_DB_NO_POSTGRES' => 'La base de données sélectionnée n’est pas encodée en « UNICODE » ou en « UTF8 ». Veuillez sélectionner une base de données prenant en charge au moins un de ces encodages.', + 'INST_SCHEMA_FILE_NOT_WRITABLE' => 'Le fichier du schéma de votre base de données est en lecture seule', + + // + // Email data + // + 'EMAIL_CONFIG' => 'Configuration de la messagerie électronique', + + // Package info + 'PACKAGE_VERSION' => 'Version de l’archive installée', + 'UPDATE_INCOMPLETE' => 'Votre installation de phpBB n’a pas été correctement mise à jour.', + 'UPDATE_INCOMPLETE_MORE' => 'Veuillez consulter les informations ci-dessous afin de corriger cette erreur.', + 'UPDATE_INCOMPLETE_EXPLAIN' => '

Mise à jour incomplète

+ +

La dernière mise à jour de votre installation de phpBB ne semble pas complète. Veuillez vous rendre sur la page de mise à jour de la base de données, vous assurer que « Mettre à jour uniquement la base de données » soit coché et cliquer sur « Envoyer ». Assurez-vous également de supprimer le répertoire « install/ » après avoir mis à jour votre base de données.

', + + // + // Server data + // + // Form labels + 'UPGRADE_INSTRUCTIONS' => 'La version %1$s, proposant de nouvelles fonctionnalités, est disponible. Veuillez consulter l’annonce de sortie (en anglais) afin de vous renseigner sur les modifications apportées et consulter les instructions pour mettre à jour votre forum.', + 'SERVER_CONFIG' => 'Configuration du serveur', + 'SCRIPT_PATH' => 'Chemin du forum', + 'SCRIPT_PATH_EXPLAIN' => 'Le chemin menant à votre forum par rapport au nom de domaine, tel que « /phpBB3 ».', +]); + +// Default database schema entries... +$lang = array_merge($lang, [ + 'CONFIG_BOARD_EMAIL_SIG' => 'Cordialement. L’équipe du forum.', + 'CONFIG_SITE_DESC' => 'Une courte description de votre forum', + 'CONFIG_SITENAME' => 'votredomaine.com', + + 'DEFAULT_INSTALL_POST' => 'Ceci est un exemple de message de votre nouvelle installation de phpBB 3.2. Tout semble fonctionner correctement ! Si vous le souhaitez, vous pouvez supprimer ce message et continuer à configurer votre forum. Lors du processus d’installation, votre première catégorie et votre premier forum ont été assignés à un ensemble de permissions relatif aux groupes d’utilisateurs que sont les administrateurs, les modérateurs généraux, les utilisateurs inscrits, les utilisateurs COPPA inscrits, les invités et les robots. Si vous souhaitez supprimer également cette première catégorie et ce premier forum, n’oubliez pas d’assigner les permissions appropriées à tous les groupes d’utilisateurs pour chaque nouvelle catégorie et nouveau forum que vous créez. Il est recommandé de renommer cette première catégorie et ce premier forum, puis de copier leurs permissions sur chaque nouvelle catégorie et nouveau forum lors de leur création. Bonne continuation !', + + 'FORUMS_FIRST_CATEGORY' => 'Votre première catégorie', + 'FORUMS_TEST_FORUM_DESC' => 'La description de votre premier forum.', + 'FORUMS_TEST_FORUM_TITLE' => 'Votre premier forum', + + 'RANKS_SITE_ADMIN_TITLE' => 'Administrateur', + 'REPORT_WAREZ' => 'Le message rapporté contient du contenu portant atteinte au droit d’auteur, au droit des marques, au secret industriel ou à d’autres législations.', + 'REPORT_SPAM' => 'Le message rapporté contient du contenu publicitaire indésirable dont le but est de promouvoir une marque, un produit, une entreprise ou un site internet.', + 'REPORT_OFF_TOPIC' => 'Le message rapporté est hors-sujet.', + 'REPORT_OTHER' => 'Le message rapporté ne correspond à aucune catégorie. Veuillez utiliser le champ d’information complémentaire.', + + 'SMILIES_ARROW' => 'Flèche', + 'SMILIES_CONFUSED' => 'Confus', + 'SMILIES_COOL' => 'Décontracté', + 'SMILIES_CRYING' => 'En pleurs', + 'SMILIES_EMARRASSED' => 'Embarrassé', + 'SMILIES_EVIL' => 'Diable en colère', + 'SMILIES_EXCLAMATION' => 'Exclamation', + 'SMILIES_GEEK' => 'Geek', + 'SMILIES_IDEA' => 'Idée', + 'SMILIES_LAUGHING' => 'Riant', + 'SMILIES_MAD' => 'En colère', + 'SMILIES_MR_GREEN' => 'M. Vert', + 'SMILIES_NEUTRAL' => 'Neutre', + 'SMILIES_QUESTION' => 'Interrogation', + 'SMILIES_RAZZ' => 'Tirant la langue', + 'SMILIES_ROLLING_EYES' => 'Roulant des yeux', + 'SMILIES_SAD' => 'Triste', + 'SMILIES_SHOCKED' => 'Scandalisé', + 'SMILIES_SMILE' => 'Souriant', + 'SMILIES_SURPRISED' => 'Étonné', + 'SMILIES_TWISTED_EVIL' => 'Diable souriant', + 'SMILIES_UBER_GEEK' => 'Super geek', + 'SMILIES_VERY_HAPPY' => 'Heureux', + 'SMILIES_WINK' => 'Clin d’œil', + + 'TOPICS_TOPIC_TITLE' => 'Bienvenue sur phpBB 3.2', +]); + +// Common navigation items' translation +$lang = array_merge($lang, [ + 'MENU_OVERVIEW' => 'Général', + 'MENU_INTRO' => 'Introduction', + 'MENU_LICENSE' => 'Licence', + 'MENU_SUPPORT' => 'Assistance', +]); + +// Task names +$lang = array_merge($lang, [ + // Install filesystem + 'TASK_CREATE_CONFIG_FILE' => 'Création du fichier de configuration', + + // Install database + 'TASK_ADD_CONFIG_SETTINGS' => 'Ajout des paramètres de configuration', + 'TASK_ADD_DEFAULT_DATA' => 'Ajout des paramètres par défaut dans la base de données', + 'TASK_CREATE_DATABASE_SCHEMA_FILE' => 'Création du fichier de shéma de la base de données', + 'TASK_SETUP_DATABASE' => 'Configuration de la base de données', + 'TASK_CREATE_TABLES' => 'Création des tables', + + // Install data + 'TASK_ADD_BOTS' => 'Inscription des robots', + 'TASK_ADD_LANGUAGES' => 'Installation des langues disponibles', + 'TASK_ADD_MODULES' => 'Installation des modules', + 'TASK_CREATE_SEARCH_INDEX' => 'Création de l’index de recherche', + + // Install finish tasks + 'TASK_INSTALL_EXTENSIONS' => 'Installation de la collection d’extensions', + 'TASK_NOTIFY_USER' => 'Envoi du courriel de notification', + 'TASK_POPULATE_MIGRATIONS' => 'Remplissage des migrations', + + // Installer general progress messages + 'INSTALLER_FINISHED' => 'L’assistant d’installation a terminé toutes ses opérations', +]); + +// Installer's general messages +$lang = array_merge($lang, [ + 'MODULE_NOT_FOUND' => 'Module introuvable', + 'MODULE_NOT_FOUND_DESCRIPTION' => 'Un module n’a pas pu être trouvé car le service « %s » n’a pas été défini.', + + 'TASK_NOT_FOUND' => 'Tâche introuvable', + 'TASK_NOT_FOUND_DESCRIPTION' => 'Une tâche n’a pas pu être trouvée car le service « %s » n’a pas été défini.', + + 'SKIP_MODULE' => 'Le module « %s » a été ignoré.', + 'SKIP_TASK' => 'La tâche « %s » a été ignorée.', + + 'TASK_SERVICE_INSTALLER_MISSING' => 'Tous les services de tâches du programme d’installation doivent commencer par « installer »', + 'TASK_CLASS_NOT_FOUND' => 'La définition du service de tâches du programme d’installation est invalide. Le nom de service « %1$s » a été spécifié alors que l’espace de nom de classe attendu est « %2$s ». Pour plus d’informations, veuillez consulter la documentation sur « task_interface ».', + + 'INSTALLER_CONFIG_NOT_WRITABLE' => 'Le fichier de configuration de l’assistant d’installation est en lecture seule.', +]); + +// CLI messages +$lang = array_merge($lang, [ + 'CLI_INSTALL_BOARD' => 'Installer phpBB', + 'CLI_UPDATE_BOARD' => 'Mettre à jour phpBB', + 'CLI_INSTALL_SHOW_CONFIG' => 'Afficher la configuration qui sera utilisée', + 'CLI_INSTALL_VALIDATE_CONFIG' => 'Valider un fichier de configuration', + 'CLI_CONFIG_FILE' => 'Fichier de configuration à utiliser', + 'MISSING_FILE' => 'Impossible d’accéder au fichier « %1$s »', + 'MISSING_DATA' => 'Le fichier de configuration ne contient pas toutes les données requises ou celles-ci sont invalides.', + 'INVALID_YAML_FILE' => 'Impossible d’analyser le fichier YAML « %1$s »', + 'CONFIGURATION_VALID' => 'Le fichier de configuration est valide', +]); + +// Common updater messages +$lang = array_merge($lang, [ + 'UPDATE_INSTALLATION' => 'Mettre à jour phpBB', + 'UPDATE_INSTALLATION_EXPLAIN' => 'Cet assistant va vous permettre de mettre à jour le logiciel de votre forum vers la dernière version stable.
Durant le processus, tous vos fichiers seront vérifiés dans leur intégralité. Vous pourrez prévisualiser toutes les différences et tous les fichiers avant d’exécuter la mise à jour.

Le fichier de mise à jour peut se générer de deux manières différentes.

Mise à jour manuelle

Cette mise à jour vous permet de ne télécharger que les paramètres des fichiers modifiés afin de vous assurer de ne perdre aucune modification. Après avoir téléchargé cette archive, vous devrez mettre à jour manuellement les fichiers à leur emplacement respectif, selon la racine du répertoire de votre forum. Une fois la mise à jour terminée, vous pourrez recommencer l’étape de vérification des fichiers afin de vous assurer d’avoir déplacé les fichiers à leur emplacement respectif.

Mise à jour automatique par FTP

Cette mise à jour est similaire à la première mais ne nécessitera pas de télécharger et de transférer manuellement les fichiers modifiés, tout sera effectué automatiquement. Vous devrez vous munir des informations concernant votre connexion FTP car elles vous seront demandées. Une fois la mise à jour terminée, vous serez redirigé une fois de plus à la vérification des fichiers afin de vous assurer du bon déroulement de la mise à jour.

', + 'UPDATE_INSTRUCTIONS' => ' + +

Annonce de sortie

+ +

Veuillez consulter l’annonce relative à la sortie de la dernière version avant de continuer le processus de mise à jour. Elle contient des informations qui pourraient vous intéresser, telles que les différents liens de téléchargement et les modifications apportées.

+ +
+ +

Mettre à jour votre installation avec l’archive complète

+

Il est recommandé de mettre à jour votre forum en utilisant l’archive complète. Néanmoins, si les fichiers systèmes de phpBB ont été modifiés sur votre installation, il est préférable d’utiliser l’archive de mise à jour automatique afin de préserver les modifications apportées. Vous pouvez également mettre à jour votre installation en utilisant des méthodes alternatives listées dans le document « INSTALL.html ». Les étapes pour mettre à jour phpBB 3.2 en utilisant l’archive complète sont :

+
    +
  1. Sauvegardez les fichiers et la base de données de votre forum.
  2. +
  3. Accédez à la page des téléchargements (en anglais) et téléchargez la dernière version de l’archive complète.
  4. +
  5. Décompressez l’archive.
  6. +
  7. Supprimez de l’archive (et pas de votre serveur !) le fichier « config.php » ainsi que les répertoires « images/ », « store/ » et « files/ ».
  8. +
  9. Accédez aux paramètres du forum dans le panneau de contrôle de l’administration et assurez-vous que « prosilver » soit bien le style par défaut de votre forum.
  10. +
  11. Supprimez les répertoires « vendor/ » et « cache/ » de la racine de votre forum.
  12. +
  13. Transférez par FTP ou SSH les fichiers et les répertoires restants (que contient le répertoire « phpBB3/ » de l’archive) à la racine du forum sur votre serveur, en vous assurant d’écraser les fichiers existants. Veuillez vous assurer de ne pas supprimer les extensions contenues dans le répertoire « ext/ » lorsque vous transférerez le nouveau contenu.
  14. +
  15. Démarrez à présent le processus de mise à jour en dirigeant votre navigateur vers le répertoire « install/ ».
  16. +
  17. Suivez les étapes jusqu’à mettre à jour votre base de données.
  18. +
  19. Supprimez par FTP ou SSH le répertoire « install/ » de la racine de votre forum.

  20. +
+ +

Profitez dès à présent de votre forum fraîchement mis à jour contenant tous vos utilisateurs et vos messages. Si vous le souhaitez, il ne vous reste plus qu’à :

+
    +
  • Mettre à jour votre traduction
  • +
  • Mettre à jour votre style

  • +
+ +

Mettre à jour votre installation avec l’archive de mise à jour automatique

+ +

Il n’est recommandé de mettre à jour votre forum en utilisant l’archive de mise à jour automatique que dans le cas où les fichiers systèmes de phpBB ont été modifiés sur votre installation. Vous pouvez également mettre à jour votre installation en utilisant des méthodes alternatives listées dans le document « INSTALL.html ». Les étapes pour mettre à jour phpBB 3.2 en utilisant l’archive de mise à jour automatique sont :

+ +
    +
  1. Accédez à la page des téléchargements (en anglais) et téléchargez la dernière version de l’archive de mise à jour automatique.

  2. +
  3. Décompressez l’archive.

  4. +
  5. Transférez les répertoires décompressés « install/ » et « vendor/ » à la racine de votre forum (où se trouve le fichier « config.php »).

  6. +
+ +

Pour des raisons de sécurité, une fois que les répertoires et leur contenu ont été transférés, votre forum apparaîtra hors-ligne aux utilisateurs.

+ Démarrez à présent le processus de mise à jour en dirigeant votre navigateur vers le répertoire « install/ ».
+
+ Vous serez alors guidé par l’assistant de mise à jour qui vous informera lorsque la mise à jour sera entièrement finalisée. +

+ ', +]); + +// Updater forms +$lang = array_merge($lang, [ + // Updater types + 'UPDATE_TYPE' => 'Type de mise à jour à exécuter', + + 'UPDATE_TYPE_ALL' => 'Mettre à jour les fichiers et la base de données', + 'UPDATE_TYPE_DB_ONLY' => 'Mettre à jour uniquement la base de données', + + // File updater methods + 'UPDATE_FILE_METHOD_TITLE' => 'Méthodes de l’outil de mise à jour des fichiers', + + 'UPDATE_FILE_METHOD' => 'Méthode de l’outil de mise à jour des fichiers', + 'UPDATE_FILE_METHOD_DOWNLOAD' => 'Télécharger les fichiers modifiés dans une archive', + 'UPDATE_FILE_METHOD_FTP' => 'Mettre à jour les fichiers par FTP (automatique)', + 'UPDATE_FILE_METHOD_FILESYSTEM' => 'Mettre à jour les fichiers par accès direct aux fichiers (automatique)', + + // File updater archives + 'SELECT_DOWNLOAD_FORMAT' => 'Sélectionner le format de l’archive à télécharger', + + // FTP settings + 'FTP_SETTINGS' => 'Paramètres FTP', +]); + +// Requirements messages +$lang = array_merge($lang, [ + 'UPDATE_FILES_NOT_FOUND' => 'Aucun répertoire de mise à jour n’a été trouvé. Veuillez vous assurer d’avoir transféré les fichiers nécessaires.', + + 'NO_UPDATE_FILES_UP_TO_DATE' => 'Votre version est à jour. Il n’est pas nécessaire d’exécuter cet assistant de mise à jour. Si vous souhaitez vérifier intégralement tous vos fichiers, veuillez vous assurer d’avoir transféré les fichiers de mise à jour nécessaires.', + 'OLD_UPDATE_FILES' => 'Les fichiers de mise à jour sont obsolètes. Les fichiers de mise à jour trouvés sont prévus pour la mise à jour de phpBB %1$s à phpBB %2$s, alors que la dernière version est phpBB %3$s.', + 'INCOMPATIBLE_UPDATE_FILES' => 'Les fichiers de mise à jour trouvés sont incompatibles avec la version actuellement installée. La version actuellement installée est phpBB %1$s alors que le fichier de mise à jour est prévu pour la mise à jour de phpBB %2$s à phpBB %3$s.', +]); + +// Update files +$lang = array_merge($lang, [ + 'STAGE_UPDATE_FILES' => 'Mettre à jour les fichiers', + + // Check files + 'UPDATE_CHECK_FILES' => 'Vérifier les fichiers à mettre à jour', + + // Update file differ + 'FILE_DIFFER_ERROR_FILE_CANNOT_BE_READ' => 'Le comparateur de fichier n’a pas pu ouvrir « %s ».', + + 'UPDATE_FILE_DIFF' => 'Comparaison des fichiers modifiés', + 'ALL_FILES_DIFFED' => 'Tous les fichiers modifiés ont été comparés.', + + // File status + 'UPDATE_CONTINUE_FILE_UPDATE' => 'Mettre à jour les fichiers', + + 'DOWNLOAD' => 'Télécharger', + 'DOWNLOAD_CONFLICTS' => 'Télécharger l’archive de fusion des conflits', + 'DOWNLOAD_CONFLICTS_EXPLAIN' => 'Rechercher <<< afin de détecter les conflits', + 'DOWNLOAD_UPDATE_METHOD' => 'Télécharger l’archive des fichiers modifiés', + 'DOWNLOAD_UPDATE_METHOD_EXPLAIN' => 'Après avoir téléchargé cette archive, vous devez la décompresser. Elle comporte les fichiers modifiés que vous devez transférer à la racine du répertoire de votre forum. Veuillez vous assurer de transférer ces fichiers à leur emplacement respectif. Vous pourrez continuer le processus de mise à jour après avoir transféré ces fichiers.', + + 'FILE_ALREADY_UP_TO_DATE' => 'Le fichier est déjà à jour.', + 'FILE_DIFF_NOT_ALLOWED' => 'Le fichier ne peut pas être comparé.', + 'FILE_USED' => 'Information utilisée depuis', // Single file + 'FILES_CONFLICT' => 'Fichiers conflictuels', + 'FILES_CONFLICT_EXPLAIN' => 'Les fichiers suivants ont été modifiés par rapport aux fichiers originaux présents dans l’ancienne version. Ils ne pourront pas être fusionnés sans provoquer des conflits. Veuillez vérifier ces fichiers afin de détecter et résoudre manuellement les conflits ou continuez le processus de mise à jour en sélectionnant la méthode de fusion souhaitée. Si vous résolvez les conflits manuellement, vérifiez de nouveau les fichiers une fois que les modifications ont été apportées. Vous pouvez également sélectionner pour chaque fichier la méthode de fusion qui vous semble la plus adaptée. La première méthode produira un fichier où les lignes conflictuelles présentes dans votre ancien fichier seront perdues. La seconde méthode ignorera toutes les modifications qui ont été apportées au nouveau fichier.', + 'FILES_DELETED' => 'Fichiers supprimés', + 'FILES_DELETED_EXPLAIN' => 'Les fichiers suivants ne sont plus présents dans la nouvelle version. Ils doivent être supprimés de votre installation actuelle.', + 'FILES_MODIFIED' => 'Fichiers modifiés', + 'FILES_MODIFIED_EXPLAIN' => 'Les fichiers suivants ont été modifiés par rapport aux fichiers originaux présents dans l’ancienne version. Le fichier mis à jour correspondra à une fusion de vos modifications et du nouveau fichier.', + 'FILES_NEW' => 'Nouveaux fichiers', + 'FILES_NEW_EXPLAIN' => 'Les fichiers suivants sont introuvables dans votre installation actuelle. Ils seront ajoutés à votre installation.', + 'FILES_NEW_CONFLICT' => 'Nouveaux fichiers conflictuels', + 'FILES_NEW_CONFLICT_EXPLAIN' => 'Les fichiers suivants ont été ajoutés dans la dernière version stable du logiciel, mais certains fichiers du même nom, situés dans le même emplacement, existent déjà. Ils seront remplacés par les nouveaux fichiers.', + 'FILES_NOT_MODIFIED' => 'Fichiers inchangés', + 'FILES_NOT_MODIFIED_EXPLAIN' => 'Les fichiers suivants n’ont pas été modifiés par rapport aux fichiers originaux de la version que vous souhaitez mettre à jour.', + 'FILES_UP_TO_DATE' => 'Fichiers déjà à jour', + 'FILES_UP_TO_DATE_EXPLAIN' => 'Les fichiers suivants sont déjà à jour et ne nécessitent donc pas d’être mis à jour.', + 'FILES_VERSION' => 'Version des fichiers', + 'TOGGLE_DISPLAY' => 'Afficher ou masquer la liste des fichiers', + + // File updater + 'UPDATE_UPDATING_FILES' => 'Mise à jour des fichiers', + + 'UPDATE_FILE_UPDATER_HAS_FAILED' => 'L’assistant de mise à jour des fichiers « %1$s » a rencontré un problème. L’installateur va essayer de revenir à « %2$s ».', + 'UPDATE_FILE_UPDATERS_HAVE_FAILED' => 'L’assistant de mise à jour des fichiers a rencontré un problème. Il n’est pas possible de revenir en arrière.', + + 'UPDATE_CONTINUE_UPDATE_PROCESS' => 'Continuer la mise à jour', + 'UPDATE_RECHECK_UPDATE_FILES' => 'Vérifier à nouveau les fichier', +]); + +// Update database +$lang = array_merge($lang, [ + 'STAGE_UPDATE_DATABASE' => 'Mettre à jour la base de données', + + 'INLINE_UPDATE_SUCCESSFUL' => 'La base de données a été mise à jour.', + + 'TASK_UPDATE_EXTENSIONS' => 'Mise à jour des extensions', +]); + +// Converter +$lang = array_merge($lang, [ + // Common converter messages + 'CONVERT_NOT_EXIST' => 'Le convertisseur que vous avez spécifié est introuvable.', + 'DEV_NO_TEST_FILE' => 'Aucune valeur n’a été spécifiée dans le convertisseur concernant la variable test_file. Si vous êtes un utilisateur de ce convertisseur, vous ne devriez pas être en mesure de voir cette erreur. Veuillez rapporter ce message à l’auteur du convertisseur. Si vous êtes l’auteur du convertisseur, vous devez spécifier le nom d’un fichier existant dans le forum source afin de permettre la validation du chemin vers ce dernier.', + 'COULD_NOT_FIND_PATH' => 'Le chemin menant à votre ancien forum est introuvable. Veuillez vérifier vos paramètres et réessayer.
» Le chemin source spédifié était « %s ».', + 'CONFIG_PHPBB_EMPTY' => 'La variable de configuration « %s » de phpBB 3.2 est vide.', + + 'MAKE_FOLDER_WRITABLE' => 'Veuillez vous assurer que ce répertoire existe et que ses droits d’accès soient disponibles pour écriture par votre serveur, puis réessayez :
»%s.', + 'MAKE_FOLDERS_WRITABLE' => 'Veuillez vous assurer que ces répertoires existent et que leurs droits d’accès soient disponibles pour écriture par votre serveur, puis réessayez :
»%s.', + + 'INSTALL_TEST' => 'Tester à nouveau', + + 'NO_TABLES_FOUND' => 'Aucun table n’a été trouvée.', + 'TABLES_MISSING' => 'Les tables suivantes sont introuvables :
» %s.', + 'CHECK_TABLE_PREFIX' => 'Veuillez vérifier votre préfixe de table et réessayer.', + + // Conversion in progress + 'CONTINUE_CONVERT' => 'Continuer la conversion', + 'CONTINUE_CONVERT_BODY' => 'Une précédente tentative de conversion a été trouvée. Vous pouvez à présent choisir entre démarrer une nouvelle conversion ou continuer la conversion précédente.', + 'CONVERT_NEW_CONVERSION' => 'Nouvelle conversion', + 'CONTINUE_OLD_CONVERSION' => 'Continuer la conversion précédente', + + // Start conversion + 'SUB_INTRO' => 'Introduction', + 'CONVERT_INTRO' => 'Bienvenue sur le framework de conversion unifié de phpBB', + 'CONVERT_INTRO_BODY' => 'Vous pouvez importer ici les données provenant d’autres logiciels de forum de discussions. La liste ci-dessous vous permet de consulter tous les modules de conversion actuellement disponibles. Si vous ne trouvez pas dans cette liste le logiciel de forum que vous souhaitez convertir, veuillez vérifier sur notre site internet où davantage modules de conversion peuvent être disponibles.', + 'AVAILABLE_CONVERTORS' => 'Convertisseurs disponibles', + 'NO_CONVERTORS' => 'Aucun convertisseur n’est disponible.', + 'CONVERT_OPTIONS' => 'Options', + 'SOFTWARE' => 'Logiciel de forum', + 'VERSION' => 'Version', + 'CONVERT' => 'Convertir', + + // Settings + 'STAGE_SETTINGS' => 'Paramètres', + 'TABLE_PREFIX_SAME' => 'Le préfixe de table doit être celui qui est utilisé par le logiciel que vous souhaitez convertir.
» Le préfixe de table spécifié était « %s ».', + 'DEFAULT_PREFIX_IS' => 'Le convertisseur n’a pas été en mesure de trouver de tables comportant ce préfixe. Veuillez vous assurer d’avoir correctement spécifié les informations sur le forum que vous souhaitez convertir. Le préfixe de table par défaut concernant « %1$s » est « %2$s ».', + 'SPECIFY_OPTIONS' => 'Spécifier les options de conversion', + 'FORUM_PATH' => 'Chemin du forum', + 'FORUM_PATH_EXPLAIN' => 'Le chemin relatif sur le disque vers votre ancien forum depuis la racine de cette installation de phpBB 3.2.', + 'REFRESH_PAGE' => 'Actualiser la page pour continuer la conversion', + 'REFRESH_PAGE_EXPLAIN' => 'Si cette option est activée, le convertisseur actualisera à chaque étape la page pour continuer la conversion. Nous vous conseillons de ne pas activer cette option s’il s’agit de votre première conversion, en particulier si votre but est de tester cet outil et de déterminer en avance les possibles erreurs.', + + // Conversion + 'STAGE_IN_PROGRESS' => 'Conversion en cours', + + 'AUTHOR_NOTES' => 'Notes de l’auteur
» %s', + 'STARTING_CONVERT' => 'Démarrage du processus de conversion', + 'CONFIG_CONVERT' => 'Conversion de la configuration', + 'DONE' => 'Effectué', + 'PREPROCESS_STEP' => 'Exécution des fonctions et des requêtes de prétraitement', + 'FILLING_TABLE' => 'Alimentation de la table « %s »', + 'FILLING_TABLES' => 'Alimentation des tables', + 'DB_ERR_INSERT' => 'Une erreur est survenue lors du traitement de la requête « INSERT ».', + 'DB_ERR_LAST' => 'Une erreur est survenue lors du traitement de « query_last ».', + 'DB_ERR_QUERY_FIRST' => 'Une erreur est survenue lors de l’exécution de « query_first ».', + 'DB_ERR_QUERY_FIRST_TABLE' => 'Une erreur est survenue lors de l’exécution de « query_first », %s (« %s »).', + 'DB_ERR_SELECT' => 'Une erreur est survenue lors de l’exécution de la requête « SELECT ».', + 'STEP_PERCENT_COMPLETED' => 'Étape %d sur %d', + 'FINAL_STEP' => 'Étape finale', + 'SYNC_FORUMS' => 'Démarrage de la synchronisation des forums', + 'SYNC_POST_COUNT' => 'Synchronisation de post_counts', + 'SYNC_POST_COUNT_ID' => 'Synchronisation de post_counts à partir de « entry » %1$s vers %2$s.', + 'SYNC_TOPICS' => 'Démarrage de la synchronisation des sujets', + 'SYNC_TOPIC_ID' => 'Synchronisation des sujets à partir de « topic_id » %1$s vers %2$s.', + 'PROCESS_LAST' => 'Traitement des dernières opérations', + 'UPDATE_TOPICS_POSTED' => 'Génération des informations de publication des sujets', + 'UPDATE_TOPICS_POSTED_ERR' => 'Une erreur est survenue lors de la génération des informations de publication des sujets. Vous pouvez réessayer cette étape depuis le panneau de contrôle d’administration une fois le processus de conversion terminé.', + 'CONTINUE_LAST' => 'Continuer les dernières opérations', + 'CLEAN_VERIFY' => 'Nettoyage et vérification de la structure finale', + 'NOT_UNDERSTAND' => 'Incompréhension de %s #%d, table %s (« %s »)', + 'NAMING_CONFLICT' => 'Conflit d’appelation : « %s » et « %s » sont tous deux des alias

%s', + + // Finish conversion + 'CONVERT_COMPLETE' => 'Conversion terminée', + 'CONVERT_COMPLETE_EXPLAIN' => 'Félicitations ! Vous avez converti votre forum à phpBB 3.2. Vous pouvez à présent vous connecter et accéder à votre forum. Veuillez vous assurer que les paramètres ont été correctement transférés avant de mettre en ligne votre forum en supprimant le répertoire d’installation. Il vous est possible d’obtenir une assistance sur l’utilisation de phpBB grâce à sa documentation (en anglais) et ses forums d’assistance (en anglais).', + + 'CONV_ERROR_ATTACH_FTP_DIR' => 'Le transfert de pièces jointes par FTP est activé sur l’ancien forum. Veuillez désactiver cette option et vous assurer qu’un répertoire valide de transfert a bien été spécifié, puis copiez toutes les pièces jointes dans ce nouveau répertoire en ligne. Une fois cette opération effectuée, redémarrez le convertisseur.', + 'CONV_ERROR_CONFIG_EMPTY' => 'Aucune information de configuration sur ce convertisseur n’est disponible.', + 'CONV_ERROR_FORUM_ACCESS' => 'Impossible d’obtenir l’information d’accès au forum.', + 'CONV_ERROR_GET_CATEGORIES' => 'Impossible d’obtenir les catégories.', + 'CONV_ERROR_GET_CONFIG' => 'Impossible de retrouver la configuration de votre forum.', + 'CONV_ERROR_COULD_NOT_READ' => 'Impossible d’accéder et de lire « %s ».', + 'CONV_ERROR_GROUP_ACCESS' => 'Impossiblee d’obtenir l’information d’authentification des groupes.', + 'CONV_ERROR_INCONSISTENT_GROUPS' => 'Incohérence dans le tableau des groupes détecté dans « add_bots() ». Vous devez ajouter tous les groupes spéciaux si vous le faites manuellement.', + 'CONV_ERROR_INSERT_BOT' => 'Impossible d’insérer le robot dans la table des utilisateurs.', + 'CONV_ERROR_INSERT_BOTGROUP' => 'Impossible d’insérer le robot dans la table des robots.', + 'CONV_ERROR_INSERT_USER_GROUP' => 'Impossible d’insérer l’utilisateurs dans la table « user_group ».', + 'CONV_ERROR_MESSAGE_PARSER' => 'Message d’erreur de l’analyseur', + 'CONV_ERROR_NO_AVATAR_PATH' => 'Note au développeur : vous devez spécifier « $convertor[\'avatar_path\'] » pour utiliser « %s ».', + 'CONV_ERROR_NO_FORUM_PATH' => 'Le chemin relatif au forum source n’a pas été spécifié.', + 'CONV_ERROR_NO_GALLERY_PATH' => 'Note au développeur : vous devez spécifier « $convertor[\'avatar_gallery_path\'] » pour utiliser « %s ».', + 'CONV_ERROR_NO_GROUP' => 'Le groupe « %1$s » est introuvable dans « %2$s ».', + 'CONV_ERROR_NO_RANKS_PATH' => 'Note au développeur : vous devez spécifier « $convertor[\'ranks_path\'] » pour utiliser « %s ».', + 'CONV_ERROR_NO_SMILIES_PATH' => 'Note au développeur : vous devez spécifier « $convertor[\'smilies_path\'] » pour utiliser « %s ».', + 'CONV_ERROR_NO_UPLOAD_DIR' => 'Note au développeur : vous devez spécifier « $convertor[\'upload_path\'] » pour utiliser « %s ».', + 'CONV_ERROR_PERM_SETTING' => 'Impossible d’insérer et de mettre à jour le paramètre de permission.', + 'CONV_ERROR_PM_COUNT' => 'Impossible de sélectionner le dossier du compteur des messages privés.', + 'CONV_ERROR_REPLACE_CATEGORY' => 'Impossible d’insérer le nouveau forum en remplacement de l’ancienne catégorie.', + 'CONV_ERROR_REPLACE_FORUM' => 'Impossible d’insérer le nouveau forum en remplacement de l’ancien forum.', + 'CONV_ERROR_USER_ACCESS' => 'Impossible d’obtenir l’information sur l’authentification de l’utilisateur.', + 'CONV_ERROR_WRONG_GROUP' => 'Définition du mauvais groupe « %1$s » dans « %2$s ».', + 'CONV_OPTIONS_BODY' => 'Cette page collecte les données requises pour accéder au forum source. Veuillez saisir les informations sur la base de données de votre ancien forum. Le convertisseur ne modifiera rien dans la base de données affichée ci-dessous. Le forum source devrait être désactivé pour permettre une conversion complète.', + 'CONV_SAVED_MESSAGES' => 'Messages sauvegardés', + + 'PRE_CONVERT_COMPLETE' => 'Toutes les étapes de préconversion sont terminées. Vous pouvez à présent commencer le processus de conversion actuel. Veuillez noter que vous pourrez être amené à saisir et à effectuer manuellement quelques opérations. Après la conversion, vérifiez en particulier les permissions assignées, reconstruisez votre index de recherche qui ne sera pas converti et assurez-vous que les fichiers ont été correctement copiés, comme par exemple les avatars et les émoticônes.', +]); diff --git a/language/fr/iso.txt b/language/fr/iso.txt new file mode 100644 index 0000000..e3da218 --- /dev/null +++ b/language/fr/iso.txt @@ -0,0 +1,3 @@ +French +Français +Miles Cellar \ No newline at end of file diff --git a/language/fr/mcp.php b/language/fr/mcp.php new file mode 100644 index 0000000..7f86688 --- /dev/null +++ b/language/fr/mcp.php @@ -0,0 +1,433 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACTION' => 'Opération', + 'ACTION_NOTE' => 'Opération/Remarque', + 'ADD_FEEDBACK' => 'Ajouter une remarque', + 'ADD_FEEDBACK_EXPLAIN' => 'Veuillez remplir le formulaire suivant pour ajouter une remarque sur cet utilisateur. Ne saisissez que du texte brut. Les balises HTML, BBCode, etc. ne sont pas autorisés.', + 'ADD_WARNING' => 'Ajouter un avertissement', + 'ADD_WARNING_EXPLAIN' => 'Veuillez remplir le formulaire suivant pour envoyer un avertissement à cet utilisateur. Ne saisissez que du texte brut. Les balises HTML, BBCode, etc. ne sont pas autorisés.', + 'ALL_ENTRIES' => 'Tous les éléments', + 'ALL_NOTES_DELETED' => 'Toutes les remarques sur cet utilisateur ont été supprimées.', + 'ALL_REPORTS' => 'Tous les rapports', + 'ALREADY_REPORTED' => 'Ce message a déjà été rapporté.', + 'ALREADY_REPORTED_PM' => 'Ce message privé a déjà été rapporté.', + 'ALREADY_WARNED' => 'Un avertissement a déjà été émis à l’encontre de ce message.', + 'APPROVE' => 'Approuver', + 'APPROVE_POST' => 'Approuver le message', + 'APPROVE_POST_CONFIRM' => 'Êtes-vous sûr de vouloir approuver ce message ?', + 'APPROVE_POSTS' => 'Approuver les messages', + 'APPROVE_POSTS_CONFIRM' => 'Êtes-vous sûr de vouloir approuver ces messages ?', + 'APPROVE_TOPIC' => 'Approuver le sujet', + 'APPROVE_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir approuver ce sujet ?', + 'APPROVE_TOPICS' => 'Approuver les sujets', + 'APPROVE_TOPICS_CONFIRM' => 'Êtes-vous sûr de vouloir approuver ces sujets ?', + + 'CANNOT_MOVE_SAME_FORUM' => 'Le sujet se trouve déjà dans ce forum.', + 'CANNOT_WARN_ANONYMOUS' => 'Vous ne pouvez pas émettre un avertissement à l’encontre des visiteurs.', + 'CANNOT_WARN_SELF' => 'Vous ne pouvez pas émettre un avertissement à votre encontre.', + 'CAN_LEAVE_BLANK' => 'Ce champ peut être laissé vide.', + 'CHANGE_POSTER' => 'Modifier l’auteur', + 'CLOSE_PM_REPORT' => 'Clore le rapport de MP', + 'CLOSE_PM_REPORT_CONFIRM' => 'Êtes-vous sûr de vouloir clore ce rapport de MP ?', + 'CLOSE_PM_REPORTS' => 'Clore les rapports de MP', + 'CLOSE_PM_REPORTS_CONFIRM' => 'Êtes-vous sûr de vouloir clore ces rapports de MP ?', + 'CLOSE_REPORT' => 'Clore le rapport', + 'CLOSE_REPORT_CONFIRM' => 'Êtes-vous sûr de vouloir clore ce rapport ?', + 'CLOSE_REPORTS' => 'Clore les rapports', + 'CLOSE_REPORTS_CONFIRM' => 'Êtes-vous sûr de vouloir clore ces rapports ?', + + 'DELETE_PM_REPORT' => 'Supprimer le rapport de MP', + 'DELETE_PM_REPORT_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce rapport de MP ?', + 'DELETE_PM_REPORTS' => 'Supprimer les rapports de MP', + 'DELETE_PM_REPORTS_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ces rapports de MP ?', + 'DELETE_POSTS' => 'Supprimer les messages', + 'DELETE_REPORT' => 'Supprimer le rapport', + 'DELETE_REPORT_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce rapport ?', + 'DELETE_REPORTS' => 'Supprimer les rapports', + 'DELETE_REPORTS_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ces rapports ?', + 'DELETE_SHADOW_TOPIC' => 'Supprimer la redirection du sujet', + 'DELETE_TOPICS' => 'Supprimer les sujets sélectionnés', + 'DISAPPROVE' => 'Désapprouver', + 'DISAPPROVE_REASON' => 'Raison de la désapprobation', + 'DISAPPROVE_POST' => 'Désapprouver le message', + 'DISAPPROVE_POST_CONFIRM' => 'Êtes-vous sûr de vouloir désapprouver ce message ?', + 'DISAPPROVE_POSTS' => 'Désapprouver les messages', + 'DISAPPROVE_POSTS_CONFIRM' => 'Êtes-vous sûr de vouloir désapprouver ces messages ?', + 'DISPLAY_LOG' => 'Afficher les éléments précédents', + 'DISPLAY_OPTIONS' => 'Options d’affichage', + + 'EMPTY_REPORT' => 'Vous devez renseigner une description lorsque vous sélectionnez cette raison.', + 'EMPTY_TOPICS_REMOVED_WARNING' => 'Veuillez noter qu’un ou plusieurs sujets ont été supprimés de la base de données car ils étaient ou devenaient vides.', + + 'FEEDBACK' => 'Remarque', + 'FORK' => 'Copier', + 'FORK_TOPIC' => 'Copier le sujet', + 'FORK_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir copier ce sujet ?', + 'FORK_TOPICS' => 'Copier les sujets sélectionnés', + 'FORK_TOPICS_CONFIRM' => 'Êtes-vous sûr de vouloir copier ces sujets ?', + 'FORUM_DESC' => 'Description', + 'FORUM_NAME' => 'Nom du forum', + 'FORUM_NOT_EXIST' => 'Le forum que vous souhaitez consulter est introuvable.', + 'FORUM_NOT_POSTABLE' => 'Vous ne pouvez pas publier de messages dans ce forum.', + 'FORUM_STATUS' => 'Statut du forum', + 'FORUM_STYLE' => 'Style du forum', + + 'GLOBAL_ANNOUNCEMENT' => 'Annonce générale', + + 'IP_INFO' => 'Informations sur l’adresse IP', + 'IPS_POSTED_FROM' => 'Adresses IP attribuées à cet utilisateur', + + 'LATEST_LOGS' => 'Les 5 derniers événements', + 'LATEST_REPORTED' => 'Les 5 derniers rapports', + 'LATEST_REPORTED_PMS' => 'Les 5 derniers rapports de MP', + 'LATEST_UNAPPROVED' => 'Les 5 derniers messages en attente d’approbation', + 'LATEST_WARNING_TIME' => 'Le dernier avertissement publié', + 'LATEST_WARNINGS' => 'Les 5 derniers avertissements', + 'LEAVE_SHADOW' => 'Conserver sur place une redirection vers le sujet', + 'LIST_REPORTS' => [ + 1 => '%d rapport', + 2 => '%d rapports', + ], + 'LOCK' => 'Verrouiller', + 'LOCK_POST_POST' => 'Verrouiller le message', + 'LOCK_POST_POST_CONFIRM' => 'Êtes-vous sûr de vouloir empêcher la modification de ce message ?', + 'LOCK_POST_POSTS' => 'Verrouiller les messages sélectionnés', + 'LOCK_POST_POSTS_CONFIRM' => 'Êtes-vous sûr de vouloir empêcher la modification de ces messages ?', + 'LOCK_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir verrouiller ce sujet ?', + 'LOCK_TOPICS' => 'Verrouiller les sujets sélectionnés', + 'LOCK_TOPICS_CONFIRM' => 'Êtes-vous sûr de vouloir verrouiller ces sujets ?', + 'LOGS_CURRENT_TOPIC' => 'Consulte actuellement l’historique de :', + 'LOGIN_EXPLAIN_MCP' => 'Vous devez vous connecter afin de modérer ce forum.', + 'LOGVIEW_VIEWPOST' => 'Consulter le message', + 'LOGVIEW_VIEWTOPIC' => 'Consulter le sujet', + 'LOGVIEW_VIEWLOGS' => 'Consulter l’historique du sujet', + 'LOGVIEW_VIEWFORUM' => 'Consulter le forum', + 'LOOKUP_ALL' => 'Rechercher toutes les adresses IP', + 'LOOKUP_IP' => 'Rechercher une adresse IP', + + 'MARKED_NOTES_DELETED' => 'Les remarques sur cet utilisateur ont été supprimées.', + + 'MCP_ADD' => 'Ajouter un avertissement', + + 'MCP_BAN' => 'Bannissement', + 'MCP_BAN_EMAILS' => 'Bannir des adresses de courriel', + 'MCP_BAN_IPS' => 'Bannir des adresses IP', + 'MCP_BAN_USERNAMES' => 'Bannir des noms d’utilisateurs', + + 'MCP_LOGS' => 'Historique de modération', + 'MCP_LOGS_FRONT' => 'Page principale', + 'MCP_LOGS_FORUM_VIEW' => 'Historique des forums', + 'MCP_LOGS_TOPIC_VIEW' => 'Historique des sujets', + + 'MCP_MAIN' => 'Principal', + 'MCP_MAIN_FORUM_VIEW' => 'Consulter le forum', + 'MCP_MAIN_FRONT' => 'Page principale', + 'MCP_MAIN_POST_DETAILS' => 'Informations sur le message', + 'MCP_MAIN_TOPIC_VIEW' => 'Consulter le sujet', + 'MCP_MAKE_ANNOUNCEMENT' => 'Modifier en annonce', + 'MCP_MAKE_ANNOUNCEMENT_CONFIRM' => 'Êtes-vous sûr de vouloir modifier ce sujet en annonce ?', + 'MCP_MAKE_ANNOUNCEMENTS' => 'Modifier en annonces', + 'MCP_MAKE_ANNOUNCEMENTS_CONFIRM' => 'Êtes-vous sûr de vouloir modifier ces sujets en annonces ?', + 'MCP_MAKE_GLOBAL' => 'Modifier en annonce générale', + 'MCP_MAKE_GLOBAL_CONFIRM' => 'Êtes-vous sûr de vouloir modifier ce sujet en annonce générale ?', + 'MCP_MAKE_GLOBALS' => 'Modifier en annonces générales', + 'MCP_MAKE_GLOBALS_CONFIRM' => 'Êtes-vous sûr de vouloir modifier ces sujets en annonces générales ?', + 'MCP_MAKE_STICKY' => 'Modifier en note', + 'MCP_MAKE_STICKY_CONFIRM' => 'Êtes-vous sûr de vouloir modifier ce sujet en note ?', + 'MCP_MAKE_STICKIES' => 'Modifier en notes', + 'MCP_MAKE_STICKIES_CONFIRM' => 'Êtes-vous sûr de vouloir modifier ces sujets en notes ?', + 'MCP_MAKE_NORMAL' => 'Modifier en sujet standard', + 'MCP_MAKE_NORMAL_CONFIRM' => 'Êtes-vous sûr de vouloir modifier ce sujet en sujet standard ?', + 'MCP_MAKE_NORMALS' => 'Modifier en sujets standards', + 'MCP_MAKE_NORMALS_CONFIRM' => 'Êtes-vous sûr de vouloir modifier ces sujets en sujets standards ?', + + 'MCP_NOTES' => 'Remarques sur les utilisateurs', + 'MCP_NOTES_FRONT' => 'Page principale', + 'MCP_NOTES_USER' => 'Remarques sur l’utilisateur', + + 'MCP_POST_REPORTS' => 'Rapports émis sur ce message', + + 'MCP_PM_REPORTS' => 'MP rapportés', + 'MCP_PM_REPORT_DETAILS' => 'Informations sur le rapport de MP', + 'MCP_PM_REPORTS_CLOSED' => 'Rapports de MP clos', + 'MCP_PM_REPORTS_CLOSED_EXPLAIN' => 'Cette page vous affiche la liste de tous les messages privés qui ont été rapportés et qui ont déjà été traités.', + 'MCP_PM_REPORTS_OPEN' => 'Rapports de MP ouverts', + 'MCP_PM_REPORTS_OPEN_EXPLAIN' => 'Cette page vous affiche la liste de tous les messages privés qui ont été rapportés et qui sont en attente de traitement.', + + 'MCP_REPORTS' => 'Messages rapportés', + 'MCP_REPORT_DETAILS' => 'Informations sur le rapport', + 'MCP_REPORTS_CLOSED' => 'Rapports clos', + 'MCP_REPORTS_CLOSED_EXPLAIN' => 'Cette page vous affiche la liste de tous les messages qui ont été rapportés et qui ont déjà été traités.', + 'MCP_REPORTS_OPEN' => 'Rapports ouverts', + 'MCP_REPORTS_OPEN_EXPLAIN' => 'Cette page vous affiche la liste de tous les messages qui ont été rapportés et qui sont en attente de traitement.', + + 'MCP_QUEUE' => 'File d’attente de modération', + 'MCP_QUEUE_APPROVE_DETAILS' => 'Approuver les informations', + 'MCP_QUEUE_UNAPPROVED_POSTS' => 'Messages en attente d’approbation', + 'MCP_QUEUE_UNAPPROVED_POSTS_EXPLAIN' => 'Cette page vous affiche la liste de tous les messages qui nécessitent d’être approuvés afin qu’ils soient visibles aux utilisateurs.', + 'MCP_QUEUE_UNAPPROVED_TOPICS' => 'Sujets en attente d’approbation', + 'MCP_QUEUE_UNAPPROVED_TOPICS_EXPLAIN' => 'Cette page vous affiche la liste de tous les sujets qui nécessitent d’être approuvés afin qu’ils soient visibles aux utilisateurs.', + 'MCP_QUEUE_DELETED_POSTS' => 'Messages supprimés', + 'MCP_QUEUE_DELETED_POSTS_EXPLAIN' => 'Cette page vous affiche la liste de tous les messages supprimés. Vous pouvez restaurer ou supprimer définitivement les messages à partir de cette page.', + 'MCP_QUEUE_DELETED_TOPICS' => 'Sujets supprimés', + 'MCP_QUEUE_DELETED_TOPICS_EXPLAIN' => 'Cette page vous affiche la liste de tous les sujets supprimés. Vous pouvez restaurer ou supprimer définitivement les sujets à partir de cette page.', + + 'MCP_VIEW_USER' => 'Consulter les avertissements émis à l’encontre d’un utilisateur spécifique', + + 'MCP_WARN' => 'Avertissements', + 'MCP_WARN_FRONT' => 'Page principale', + 'MCP_WARN_LIST' => 'Liste des avertissements', + 'MCP_WARN_POST' => 'Avertir pour un message spécifique', + 'MCP_WARN_USER' => 'Avertir l’utilisateur', + + 'MERGE_POSTS_CONFIRM' => 'Êtes-vous sûr de vouloir déplacer ces messages ?', + 'MERGE_TOPIC_EXPLAIN' => 'Utilisez ce formulaire afin de déplacer les messages sélectionnés dans un autre sujet. Les messages seront retirés de ce sujet afin d’être fusionnés dans un autre sujet. Ces messages ne seront pas réordonnés et apparaîtront comme si les utilisateurs les avaient publiés dans le nouveau sujet.
Veuillez saisir l’identifiant du sujet de destination ou cliquer sur « Sélectionner le sujet » afin d’en rechercher un.', + 'MERGE_TOPIC_ID' => 'Identifiant du sujet de destination', + 'MERGE_TOPICS' => 'Fusionner les sujets', + 'MERGE_TOPICS_CONFIRM' => 'Êtes-vous sûr de vouloir fusionner ces sujets ?', + 'MODERATE_FORUM' => 'Modérer le forum', + 'MODERATE_TOPIC' => 'Modérer le sujet', + 'MODERATE_POST' => 'Modérer le message', + 'MOD_OPTIONS' => 'Options de modération', + 'MORE_INFO' => 'Informations supplémentaires', + 'MOST_WARNINGS' => 'Utilisateurs avec le plus grand nombre d’avertissements', + 'MOVE_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir déplacer le sujet vers un nouveau forum ?', + 'MOVE_TOPICS' => 'Déplacer les sujets sélectionnés', + 'MOVE_TOPICS_CONFIRM' => 'Êtes-vous sûr de vouloir déplacer ces sujets vers un nouveau forum ?', + + 'NOTIFY_POSTER_APPROVAL' => 'Notifier l’approbation à l’auteur du message ?', + 'NOTIFY_POSTER_DISAPPROVAL' => 'Notifier la désapprobation à l’auteur du message ?', + 'NOTIFY_USER_WARN' => 'Notifier l’avertissement à l’utilisateur ?', + 'NOT_MODERATOR' => 'Vous n’êtes pas modérateur de ce forum.', + 'NO_DESTINATION_FORUM' => 'Veuillez sélectionner un forum de destination.', + 'NO_DESTINATION_FORUM_FOUND' => 'Aucun forum de destination n’est disponible.', + 'NO_ENTRIES' => 'Aucun historique.', + 'NO_FEEDBACK' => 'Aucune remarque.', + 'NO_FINAL_TOPIC_SELECTED' => 'Vous devez sélectionner un sujet de destination afin de fusionner les messages.', + 'NO_MATCHES_FOUND' => 'Aucun résultat.', + 'NO_POST' => 'Vous devez sélectionner un message avant d’avertir un utilisateur concernant un de ses messages.', + 'NO_POST_REPORT' => 'Le message n’a pas été rapporté.', + 'NO_POST_SELECTED' => 'Vous devez sélectionner au moins un message afin d’effectuer cette opération.', + 'NO_POSTS_DELETED' => 'Aucun message n’a été supprimé.', + 'NO_POSTS_QUEUE' => 'Aucun message n’est en attente d’approbation.', + 'NO_REASON_DISAPPROVAL' => 'Veuillez saisir la raison de la désapprobation.', + 'NO_REPORT' => 'Aucun rapport.', + 'NO_REPORTS' => 'Aucun rapport.', + 'NO_REPORT_SELECTED' => 'Vous devez sélectionner au moins un rapport afin d’effectuer cette opération.', + 'NO_TOPIC_ICON' => 'Aucune', + 'NO_TOPIC_SELECTED' => 'Vous devez sélectionner au moins un sujet afin d’effectuer cette opération.', + 'NO_TOPICS_DELETED' => 'Aucun sujet n’a été supprimé.', + 'NO_TOPICS_QUEUE' => 'Aucun sujet n’est en attente d’approbation.', + + 'ONLY_TOPIC' => 'Le sujet « %s » uniquement', + 'OTHER_USERS' => 'Autres utilisateurs ayant publiés avec cette adresse IP', + + 'QUICKMOD_ACTION_NOT_ALLOWED' => '%s ne peut pas être une action rapide de modération', + + 'PM_REPORT_CLOSED_SUCCESS' => 'Le rapport de MP a été clos.', + 'PM_REPORT_DELETED_SUCCESS' => 'Le rapport de MP a été supprimé.', + 'PM_REPORTED_SUCCESS' => 'Le message privé a été rapporté.', + 'PM_REPORTS_CLOSED_SUCCESS' => 'Les rapports de MP ont été clos.', + 'PM_REPORTS_DELETED_SUCCESS' => 'Les rapport de MP ont été supprimés.', + 'PM_REPORTS_TOTAL' => [ + 0 => 'Aucun rapport de MP.', + 1 => 'Au total, il y a 1 rapport de MP à traiter.', + 2 => 'Au total, il y a %d rapports de MP à traiter.', + ], + 'PM_REPORT_DETAILS' => 'Informations sur le rapport de message privé', + 'POSTER' => 'Auteur', + 'POSTS_APPROVED_SUCCESS' => 'Les messages ont été approuvés.', + 'POSTS_DELETED_SUCCESS' => 'Les messages ont été supprimés de la base de données.', + 'POSTS_DISAPPROVED_SUCCESS' => 'Les messages ont été désapprouvés.', + 'POSTS_LOCKED_SUCCESS' => 'Les messages ont été verrouillés.', + 'POSTS_MERGED_SUCCESS' => 'Les messages ont été fusionnés.', + 'POSTS_PER_PAGE' => 'Messages par page', + 'POSTS_PER_PAGE_EXPLAIN' => 'Réglez cette valeur sur « 0 » afin de consulter tous les messages.', + 'POSTS_RESTORED_SUCCESS' => 'Les messages ont été restaurés.', + 'POSTS_UNLOCKED_SUCCESS' => 'Les messages ont été déverrouillés.', + 'POST_APPROVED_SUCCESS' => 'Le message a été approuvé.', + 'POST_DELETED_SUCCESS' => 'Le message a été supprimé de la base de données.', + 'POST_DISAPPROVED_SUCCESS' => 'Le message a été désapprouvé.', + 'POST_LOCKED_SUCCESS' => 'Le message a été verrouillé.', + 'POST_NOT_EXIST' => 'Le message que vous souhaitez consulter est introuvable.', + 'POST_REPORTED_SUCCESS' => 'Le message a été rapporté.', + 'POST_RESTORED_SUCCESS' => 'Le message a été restauré.', + 'POST_UNLOCKED_SUCCESS' => 'Le message a été déverrouillé.', + + 'READ_USERNOTES' => 'Remarques sur l’utilisateur', + 'READ_WARNINGS' => 'Avertissements sur l’utilisateur', + 'REPORTER' => 'Auteur du rapport', + 'REPORTED' => 'Rapporté', + 'REPORTED_BY' => 'Rapporté par', + 'REPORTED_ON_DATE' => 'le', + 'REPORTS_CLOSED_SUCCESS' => 'Les rapports ont été clos.', + 'REPORTS_DELETED_SUCCESS' => 'Les rapports ont été supprimés.', + 'REPORTS_TOTAL' => [ + 0 => 'Aucun rapport.', + 1 => 'Au total, il y a 1 rapport à traiter.', + 2 => 'Au total, il y a %d rapports à traiter.', + ], + 'REPORT_CLOSED' => 'Ce rapport a déjà été clos.', + 'REPORT_CLOSED_SUCCESS' => 'Le rapport a été clos.', + 'REPORT_DELETED_SUCCESS' => 'Le rapport a été supprimé.', + 'REPORT_DETAILS' => 'Informations sur le rapport', + 'REPORT_MESSAGE' => 'Rapporter le message', + 'REPORT_MESSAGE_EXPLAIN' => 'Utilisez ce formulaire afin de rapporter ce message privé. Seuls les messages ne respectant pas les règles du forum devraient être rapportés. Veuillez noter que lorsqu’un message privé est rapporté, son contenu est alors rendu visible à tous les modérateurs.', + 'REPORT_NOTIFY' => 'M’envoyer une notification', + 'REPORT_NOTIFY_EXPLAIN' => 'Vous enverra une notification lorsque votre rapport sera traité.', + 'REPORT_POST_EXPLAIN' => 'Utilisez ce formulaire afin de rapporter ce message aux modérateurs et aux administrateurs du forum. Seuls les messages ne respectant pas les règles du forum devraient être rapportés.', + 'REPORT_REASON' => 'Raison du rapport', + 'REPORT_TIME' => 'Date du rapport', + 'RESTORE' => 'Restaurer', + 'RESTORE_POST' => 'Restaurer le message', + 'RESTORE_POST_CONFIRM' => 'Êtes-vous sûr de vouloir restaurer ce message ?', + 'RESTORE_POSTS' => 'Restaurer les messages', + 'RESTORE_POSTS_CONFIRM' => 'Êtes-vous sûr de vouloir restaurer ces messages ?', + 'RESTORE_TOPIC' => 'Restaurer le sujet', + 'RESTORE_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir restaurer ce sujet ?', + 'RESTORE_TOPICS' => 'Restaurer les sujets', + 'RESTORE_TOPICS_CONFIRM' => 'Êtes-vous sûr de vouloir restaurer ces sujets ?', + 'RESYNC' => 'Resynchroniser', + 'RETURN_MESSAGE' => '%sRevenir au message%s', + 'RETURN_NEW_FORUM' => '%sAller sur le nouveau forum%s', + 'RETURN_NEW_TOPIC' => '%sAller sur le nouveau sujet%s', + 'RETURN_PM' => '%sRevenir au message privé%s', + 'RETURN_POST' => '%sRevenir au message%s', + 'RETURN_QUEUE' => '%sRevenir à la file d’attente%s', + 'RETURN_REPORTS' => '%sRevenir aux rapports%s', + 'RETURN_TOPIC_SIMPLE' => '%sRevenir au sujet%s', + + 'SEARCH_POSTS_BY_USER' => 'Rechercher les messages par', + 'SELECT_ACTION' => 'Sélectionner une opération souhaitée', + 'SELECT_FORUM_GLOBAL_ANNOUNCEMENT' => 'Veuillez sélectionner le forum dans lequel vous souhaitez afficher cette annonce générale.', + 'SELECT_FORUM_GLOBAL_ANNOUNCEMENTS' => 'Un ou plusieurs sujets sélectionnés sont des annonces générales. Veuillez sélectionner le forum dans lequel vous souhaitez que ces annonces soient affichées.', + 'SELECT_MERGE' => 'Sélectionner pour la fusion', + 'SELECT_TOPICS_FROM' => 'Sélectionner les sujets de', + 'SELECT_TOPIC' => 'Sélectionner le sujet', + 'SELECT_USER' => 'Sélectionner l’utilisateur', + 'SORT_ACTION' => 'Historique', + 'SORT_DATE' => 'Date', + 'SORT_IP' => 'Adresse IP', + 'SORT_WARNINGS' => 'Avertissements', + 'SPLIT_AFTER' => 'Diviser le sujet à partir du message sélectionné', + 'SPLIT_FORUM' => 'Forum du nouveau sujet', + 'SPLIT_POSTS' => 'Diviser les messages sélectionnés', + 'SPLIT_SUBJECT' => 'Titre du nouveau sujet', + 'SPLIT_TOPIC_ALL' => 'Diviser le sujet à partir des messages sélectionnés', + 'SPLIT_TOPIC_ALL_CONFIRM' => 'Êtes-vous sûr de vouloir diviser ce sujet ?', + 'SPLIT_TOPIC_BEYOND' => 'Diviser le sujet à partir du message sélectionné', + 'SPLIT_TOPIC_BEYOND_CONFIRM' => 'Êtes-vous sûr de vouloir diviser ce sujet à partir du message sélectionné ?', + 'SPLIT_TOPIC_EXPLAIN' => 'Utilisez ce formulaire afin de diviser un sujet en deux. Pour ce faire, sélectionnez individuellement chaque sujet ou divisez le sujet à partir d’un message.', + + 'THIS_PM_IP' => 'Adresse IP du message privé', + 'THIS_POST_IP' => 'Adresse IP du message', + 'TOPICS_APPROVED_SUCCESS' => 'Les sujets ont été approuvés.', + 'TOPICS_DELETED_SUCCESS' => 'Les sujets ont été supprimés de la base de données.', + 'TOPICS_DISAPPROVED_SUCCESS' => 'Les sujets ont été désapprouvés.', + 'TOPICS_FORKED_SUCCESS' => 'Les sujets ont été copiés.', + 'TOPICS_LOCKED_SUCCESS' => 'Les sujets ont été verrouillés.', + 'TOPICS_MOVED_SUCCESS' => 'Les sujets ont été déplacés.', + 'TOPICS_RESTORED_SUCCESS' => 'Les sujets ont été restaurés.', + 'TOPICS_RESYNC_SUCCESS' => 'Les sujets ont été resynchronisés.', + 'TOPICS_TYPE_CHANGED' => 'Les types de sujet ont été modifiés.', + 'TOPICS_UNLOCKED_SUCCESS' => 'Les sujets ont été déverrouillés.', + 'TOPIC_APPROVED_SUCCESS' => 'Le sujet a été approuvé.', + 'TOPIC_DELETED_SUCCESS' => 'Le sujet a été supprimé de la base de données.', + 'TOPIC_DISAPPROVED_SUCCESS' => 'Le sujet a été désapprouvé.', + 'TOPIC_FORKED_SUCCESS' => 'Le sujet a été copié.', + 'TOPIC_LOCKED_SUCCESS' => 'Le sujet a été verrouillé.', + 'TOPIC_MOVED_SUCCESS' => 'Le sujet a été déplacé.', + 'TOPIC_NOT_EXIST' => 'Le sujet que vous souhaitez consulter est introuvable.', + 'TOPIC_RESTORED_SUCCESS' => 'Le sujet a été restauré.', + 'TOPIC_RESYNC_SUCCESS' => 'Le sujet a été resynchronisé.', + 'TOPIC_SPLIT_SUCCESS' => 'Le sujet a été divisé.', + 'TOPIC_TIME' => 'Heure du sujet', + 'TOPIC_TYPE_CHANGED' => 'Le type de sujet a été modifié.', + 'TOPIC_UNLOCKED_SUCCESS' => 'Le sujet a été déverrouillé.', + 'TOTAL_WARNINGS' => 'Nombre total d’avertissements', + + 'UNAPPROVED_POSTS_TOTAL' => [ + 0 => 'Aucun message n’est en attente d’approbation.', + 1 => 'Au total, il y a 1 message en attente d’approbation.', + 2 => 'Au total, il y a %d messages en attente d’approbation.', + ], + 'UNLOCK' => 'Déverrouiller', + 'UNLOCK_POST' => 'Déverrouiller le message', + 'UNLOCK_POST_EXPLAIN' => 'Autorise la modification', + 'UNLOCK_POST_POST' => 'Déverrouiller le message', + 'UNLOCK_POST_POST_CONFIRM' => 'Êtes-vous sûr de vouloir autoriser la modification de ce message ?', + 'UNLOCK_POST_POSTS' => 'Déverrouiller les messages sélectionnés', + 'UNLOCK_POST_POSTS_CONFIRM' => 'Êtes-vous sûr de vouloir autoriser la modification de ces messages ?', + 'UNLOCK_TOPIC' => 'Déverrouiller le sujet', + 'UNLOCK_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir déverrouiller ce sujet ?', + 'UNLOCK_TOPICS' => 'Déverrouiller les sujets sélectionnés', + 'UNLOCK_TOPICS_CONFIRM' => 'Êtes-vous sûr de vouloir déverrouiller ces sujets ?', + 'USER_CANNOT_POST' => 'Vous ne pouvez pas publier de messages dans ce forum.', + 'USER_CANNOT_REPORT' => 'Vous ne pouvez pas rapporter les messages de ce forum.', + 'USER_FEEDBACK_ADDED' => 'La remarque a été ajoutée sur l’utilisateur.', + 'USER_WARNING_ADDED' => 'L’utilisateur a été averti.', + + 'VIEW_DETAILS' => 'Consulter les informations', + 'VIEW_PM' => 'Consulter le message privé', + 'VIEW_POST' => 'Consulter le message', + + 'WARNED_USERS' => 'Utilisateurs avertis', + 'WARNED_USERS_EXPLAIN' => 'Cette page vous affiche la liste des utilisateurs qui ont été avertis et dont la période d’avertissement n’a pas encore expirée.', + 'WARNING_PM_BODY' => 'Ce qui suit est un avertissement émis à votre encontre par un administrateur ou un modérateur de ce forum.[quote]%s[/quote]', + 'WARNING_PM_SUBJECT' => 'Avertissement', + 'WARNING_POST_DEFAULT' => 'Ce qui suit est un avertissement émis à l’encontre de votre message suivant : %s .', + 'NO_WARNINGS' => 'Aucun avertissement.', + + 'YOU_SELECTED_TOPIC' => 'Vous avez sélectionné le sujet numéro %d : %s.', + + 'report_reasons' => [ + 'TITLE' => [ + 'WAREZ' => 'Contenu illicite', + 'SPAM' => 'Publicité indésirable', + 'OFF_TOPIC' => 'Hors-sujet', + 'OTHER' => 'Autre', + ], + 'DESCRIPTION' => [ + 'WAREZ' => 'Le message rapporté contient du contenu portant atteinte au droit d’auteur, au droit des marques, au secret industriel ou à d’autres législations.', + 'SPAM' => 'Le message rapporté contient du contenu publicitaire indésirable dont le but est de promouvoir une marque, un produit, une entreprise ou un site internet.', + 'OFF_TOPIC' => 'Le message rapporté est hors-sujet.', + 'OTHER' => 'Le message rapporté ne correspond à aucune catégorie. Veuillez utiliser le champ d’information complémentaire.', + ], + ], +]); diff --git a/language/fr/memberlist.php b/language/fr/memberlist.php new file mode 100644 index 0000000..8409bc4 --- /dev/null +++ b/language/fr/memberlist.php @@ -0,0 +1,153 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ABOUT_USER' => 'Profil', + 'ACTIVE_IN_FORUM' => 'Forum le plus actif', + 'ACTIVE_IN_TOPIC' => 'Sujet le plus actif', + 'ADD_FOE' => 'Ajouter en ignoré', + 'ADD_FRIEND' => 'Ajouter en ami', + 'AFTER' => 'Après', + + 'ALL' => 'Tous', + + 'BEFORE' => 'Avant', + + 'CC_SENDER' => 'M’envoyer une copie de ce courriel.', + 'CONTACT_ADMIN' => 'Contacter un administrateur du forum', + + 'DEST_LANG' => 'Langue', + 'DEST_LANG_EXPLAIN' => 'Sélectionnez une langue appropriée, si disponible, pour le destinataire de ce message.', + + 'EDIT_PROFILE' => 'Modifier le profil', + + 'EMAIL_BODY_EXPLAIN' => 'Le message sera envoyé en texte brut, sans balise HTML ou BBCode. L’adresse de retour du message correspond à votre adresse de courriel.', + 'EMAIL_DISABLED' => 'Toutes les fonctionnalités liées à la messagerie électronique ont été désactivées.', + 'EMAIL_SENT' => 'Le courriel a été envoyé.', + 'EMAIL_TOPIC_EXPLAIN' => 'Le message sera envoyé en texte brut, sans balise HTML ou BBCode. Veuillez noter que le contenu du sujet est déjà intégré au message. L’adresse de retour du message correspond à votre adresse de courriel.', + 'EMPTY_ADDRESS_EMAIL' => 'Vous devez saisir une adresse de courriel valide concernant le destinataire.', + 'EMPTY_MESSAGE_EMAIL' => 'Vous devez saisir un message afin d’envoyer un courriel.', + 'EMPTY_MESSAGE_IM' => 'Vous devez saisir le contenu du message.', + 'EMPTY_NAME_EMAIL' => 'Vous devez saisir le nom réel du destinataire.', + 'EMPTY_SENDER_EMAIL' => 'Vous devez saisir une adresse de courriel valide.', + 'EMPTY_SENDER_NAME' => 'Vous devez spécifier un nom.', + 'EMPTY_SUBJECT_EMAIL' => 'Vous devez saisir le sujet du courriel.', + 'EQUAL_TO' => 'Égal à', + + 'FIND_USERNAME_EXPLAIN' => 'Utilisez ce formulaire afin de rechercher des membres spécifiques. Vous n’avez pas besoin de remplir tous les champs. Pour utiliser partiellement une donnée, utilisez un astérisque « * » comme métacaractère passe-partout. Lorsque vous saisissez une date, utilisez le format « AAAA-MM-JJ », comme par exemple « 2004-02-29 ». Utilisez les cases à cocher afin de sélectionner un ou plusieurs noms d’utilisateurs et cliquez sur le bouton « Sélectionner la sélection » afin de revenir au formulaire précédent.', + 'FLOOD_EMAIL_LIMIT' => 'Vous ne pouvez pas envoyer d’autres courriels pour le moment. Veuillez réessayer ultérieurement.', + + 'GROUP_LEADER' => 'Responsable du groupe', + + 'HIDE_MEMBER_SEARCH' => 'Masquer la recherche des membres', + + 'IM_ADD_CONTACT' => 'Ajouter un contact', + 'IM_DOWNLOAD_APP' => 'Télécharger l’application', + 'IM_JABBER' => 'Veuillez noter que certains utilisateurs aient pu choisir de ne pas vouloir recevoir de messages instantanés non sollicités.', + 'IM_JABBER_SUBJECT' => 'Ceci est un message automatique, merci de ne pas y répondre. Message de l’utilisateur %1$s le %2$s.', + 'IM_MESSAGE' => 'Votre message', + 'IM_NAME' => 'Votre nom', + 'IM_NO_DATA' => 'Il n’y a aucune information de contact concernant cet utilisateur.', + 'IM_NO_JABBER' => 'La transmission de messages instantanés vers des utilisateurs de Jabber n’est pas prise en charge sur ce forum. Vous devez installer un client Jabber sur votre système afin de contacter le destinataire ci-dessus.', + 'IM_RECIPIENT' => 'Destinataire', + 'IM_SEND' => 'Envoyer un message', + 'IM_SEND_MESSAGE' => 'Envoyer un message', + 'IM_SENT_JABBER' => 'Votre message destiné à %1$s a été envoyé.', + 'IM_USER' => 'Envoyer un message instantané', + + 'LAST_ACTIVE' => 'Dernière visite', + 'LESS_THAN' => 'Moins que', + 'LIST_USERS' => [ + 1 => '%d utilisateur', + 2 => '%d utilisateurs', + ], + 'LOGIN_EXPLAIN_TEAM' => 'Vous devez vous inscrire et vous connecter afin de pouvoir consulter la liste des membres de l’équipe.', + 'LOGIN_EXPLAIN_MEMBERLIST' => 'Vous devez vous inscrire et vous connecter afin de pouvoir consulter la liste des membres.', + 'LOGIN_EXPLAIN_SEARCHUSER' => 'Vous devez vous inscrire et vous connecter afin de pouvoir rechercher des utilisateurs.', + 'LOGIN_EXPLAIN_VIEWPROFILE' => 'Vous devez vous inscrire et vous connecter afin de pouvoir consulter le profil des utilisateurs.', + + 'MANAGE_GROUP' => 'Gérer le groupe', + 'MORE_THAN' => 'Plus que', + + 'NO_CONTACT_FORM' => 'Le formulaire de contact des administrateurs du forum a été désactivé.', + 'NO_CONTACT_PAGE' => 'La page de contact des administrateurs du forum a été désactivée.', + 'NO_EMAIL' => 'Vous ne pouvez pas envoyer de courriel à cet utilisateur.', + 'NO_VIEW_USERS' => 'Vous ne pouvez pas consulter la liste des membres et le profil des utilisateurs.', + + 'ORDER' => 'Ordre', + 'OTHER' => 'Autre', + + 'POST_IP' => 'Publié depuis le domaine/IP', + + 'REAL_NAME' => 'Nom du destinataire', + 'RECIPIENT' => 'Destinataire', + 'REMOVE_FOE' => 'Retirer des ignorés', + 'REMOVE_FRIEND' => 'Retirer des amis', + + 'SELECT_MARKED' => 'Valider la sélection', + 'SELECT_SORT_METHOD' => 'Sélectionner la méthode de tri', + 'SENDER_EMAIL_ADDRESS' => 'Votre adresse de courriel', + 'SENDER_NAME' => 'Votre nom', + 'SEND_ICQ_MESSAGE' => 'Envoyer un message ICQ', + 'SEND_IM' => 'Messagerie instantanée', + 'SEND_JABBER_MESSAGE' => 'Envoyer un message Jabber', + 'SEND_MESSAGE' => 'Message', + 'SEND_YIM_MESSAGE' => 'Envoyer un message YIM', + 'SORT_EMAIL' => 'Adresse de courriel', + 'SORT_LAST_ACTIVE' => 'Dernière activité', + 'SORT_POST_COUNT' => 'Nombre de messages', + + 'USERNAME_BEGINS_WITH' => 'Noms d’utilisateurs commençant par', + 'USER_ADMIN' => 'Administrer l’utilisateur', + 'USER_BAN' => 'Bannissement', + 'USER_FORUM' => 'Statistiques de l’utilisateur', + 'USER_LAST_REMINDED' => [ + 0 => 'Aucun rappel n’a été envoyé pour le moment', + 1 => '%1$d rappel a été envoyé
» %2$s', + 2 => '%1$d rappels ont été envoyés
» %2$s', + ], + 'USER_ONLINE' => 'En ligne', + 'USER_PRESENCE' => 'Présence sur le forum', + 'USERS_PER_PAGE' => 'Utilisateurs par page', + + 'VIEWING_PROFILE' => 'Consulte le profil de %s', + 'VIEW_FACEBOOK_PROFILE' => 'Consulter le profil Facebook', + 'VIEW_SKYPE_PROFILE' => 'Consulter le profil Skype', + 'VIEW_TWITTER_PROFILE' => 'Consulter le profil Twitter', + 'VIEW_YOUTUBE_CHANNEL' => 'Consulter la chaîne YouTube', + 'VIEW_GOOGLEPLUS_PROFILE' => 'Consulter le profil Google+', +]); diff --git a/language/fr/migrator.php b/language/fr/migrator.php new file mode 100644 index 0000000..2b9fd34 --- /dev/null +++ b/language/fr/migrator.php @@ -0,0 +1,80 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'CONFIG_NOT_EXIST' => 'La configuration « %s » est introuvable.', + + 'GROUP_NOT_EXIST' => 'Le groupe « %s » est introuvable.', + + 'MIGRATION_APPLY_DEPENDENCIES' => 'Appliquer les dépendances de « %s ».', + 'MIGRATION_DATA_DONE' => 'Données installées : %1$s ; durée : %2$.2f secondes', + 'MIGRATION_DATA_IN_PROGRESS' => 'Installation des données : %1$s ; durée : %2$.2f secondes', + 'MIGRATION_DATA_RUNNING' => 'Installation des données : %s.', + 'MIGRATION_EFFECTIVELY_INSTALLED' => 'Migration déjà installée (mais ignorée) : %s', + 'MIGRATION_EXCEPTION_ERROR' => 'Une erreur est survenue lors de la requête et une exception a été exécutée. Les modifications effectuées avant que l’erreur ne survienne ont été restaurées au mieux mais vous devriez vérifier que votre forum fonctionne correctement.', + 'MIGRATION_NOT_FULFILLABLE' => 'La migration « %1$s » n’est pas exécutable car la migration « %2$s » est manquante.', + 'MIGRATION_NOT_INSTALLED' => 'La migration « %s » n’est pas installée.', + 'MIGRATION_NOT_VALID' => '%s n’est pas une migration valide.', + 'MIGRATION_SCHEMA_DONE' => 'Schéma installé : %1$s ; durée : %2$.2f secondes', + 'MIGRATION_SCHEMA_IN_PROGRESS' => 'Installation du schéma : %1$s ; durée : %2$.2f secondes', + 'MIGRATION_SCHEMA_RUNNING' => 'Installation du schéma : %s.', + + 'MIGRATION_REVERT_DATA_DONE' => 'Données récupérées : %1$s ; durée : %2$.2f secondes', + 'MIGRATION_REVERT_DATA_IN_PROGRESS' => 'Récupération des données : %1$s ; durée : %2$.2f secondes', + 'MIGRATION_REVERT_DATA_RUNNING' => 'Récupération des données : %s.', + 'MIGRATION_REVERT_SCHEMA_DONE' => 'Schéma récupéré : %1$s ; durée : %2$.2f secondes', + 'MIGRATION_REVERT_SCHEMA_IN_PROGRESS' => 'Récupération du schéma : %1$s ; durée : %2$.2f secondes', + 'MIGRATION_REVERT_SCHEMA_RUNNING' => 'Récupération du schéma : %s.', + + 'MIGRATION_INVALID_DATA_MISSING_CONDITION' => 'Une migration n’est pas valide. Une condition dans une instruction d’aide « IF » est manquant.', + 'MIGRATION_INVALID_DATA_MISSING_STEP' => 'Une migration n’est pas valide. Un rappel valide à une étape de migration dans une instruction d’aide « IF » est manquant.', + 'MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE' => 'Une migration n’est pas valide. Une fonction de rappel personnalisée n’a pas pu être rappelée.', + 'MIGRATION_INVALID_DATA_UNKNOWN_TYPE' => 'Une migration n’est pas valide. Un type d’outil de migration inconnu a été découvert.', + 'MIGRATION_INVALID_DATA_UNDEFINED_TOOL' => 'Une migration n’est pas valide. Un outil de migration indéfini a été découvert.', + 'MIGRATION_INVALID_DATA_UNDEFINED_METHOD' => 'Une migration n’est pas valide. Une méthode d’outil de migration indéfini a été découvert.', + + 'MODULE_ERROR' => 'Une erreur est survenue lors de la création d’un module : %s', + 'MODULE_EXISTS' => 'Le module « %s » existe déjà', + 'MODULE_EXIST_MULTIPLE' => 'Plusieurs modules portant ce nom de langue existent déjà dans le module parent « %s ». Veuillez essayer d’utiliser des clés « Before » (avant) et « After » (après) afin de positionner l’emplacement du module.', + 'MODULE_INFO_FILE_NOT_EXIST' => 'Un fichier d’information du module est manquant : %2$s', + 'MODULE_NOT_EXIST' => 'Un module nécessaire est manquant : %s', + + 'PARENT_MODULE_FIND_ERROR' => 'Impossible de déterminer l’identifiant du module parent « %s »', + 'PERMISSION_NOT_EXIST' => 'Le paramètre de permission « %s » est introuvable.', + + 'ROLE_NOT_EXIST' => 'Le rôle de permission « %s » est introuvable.', +]); diff --git a/language/fr/plupload.php b/language/fr/plupload.php new file mode 100644 index 0000000..58c5a73 --- /dev/null +++ b/language/fr/plupload.php @@ -0,0 +1,78 @@ + + * @copyright (c) Moxiecode Systems AB + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'PLUPLOAD_ADD_FILES' => 'Ajouter des fichiers', + 'PLUPLOAD_ADD_FILES_TO_QUEUE' => 'Ajoutez des fichiers à la file d’attente de transfert et cliquez sur le bouton de démarrage afin de les transférer.', + 'PLUPLOAD_ALREADY_QUEUED' => '%s fichiers déjà présents dans la file d’attente.', + 'PLUPLOAD_CLOSE' => 'Fermer', + 'PLUPLOAD_DRAG' => 'Glissez et déposez ici les fichiers que vous souhaitez transférer.', + 'PLUPLOAD_DUPLICATE_ERROR' => 'Erreur de duplicata de fichier.', + 'PLUPLOAD_DRAG_TEXTAREA' => 'Vous pouvez également transférer des fichiers en les glissant et en les déposant dans la boîte de texte.', + 'PLUPLOAD_ERR_INPUT' => 'Impossible d’ouvrir le flux d’entrée.', + 'PLUPLOAD_ERR_MOVE_UPLOADED' => 'Impossible de déplacer le fichier transféré.', + 'PLUPLOAD_ERR_OUTPUT' => 'Impossible d’ouvrir le flux de sortie.', + 'PLUPLOAD_ERR_FILE_TOO_LARGE' => 'La taille du fichier est trop importante :', + 'PLUPLOAD_ERR_FILE_COUNT' => 'Erreur sur le nombre de fichiers.', + 'PLUPLOAD_ERR_FILE_INVALID_EXT' => 'L’extension du fichier est invalide :', + 'PLUPLOAD_ERR_RUNTIME_MEMORY' => 'La mémoire disponible est insuffisante.', + 'PLUPLOAD_ERR_UPLOAD_URL' => 'Le lien de transfert est introuvable ou invalide.', + 'PLUPLOAD_EXTENSION_ERROR' => 'Erreur d’extension de fichier.', + 'PLUPLOAD_FILE' => 'Fichier : %s', + 'PLUPLOAD_FILE_DETAILS' => 'Fichier : %s ; taille : %d ; taille maximale autorisée : %d', + 'PLUPLOAD_FILENAME' => 'Nom', + 'PLUPLOAD_FILES_QUEUED' => '%d fichiers en attente', + 'PLUPLOAD_GENERIC_ERROR' => 'Erreur générique.', + 'PLUPLOAD_HTTP_ERROR' => 'Erreur HTTP.', + 'PLUPLOAD_IMAGE_FORMAT' => 'Le format de l’image est invalide ou n’est pas pris en charge.', + 'PLUPLOAD_INIT_ERROR' => 'Erreur d’initialisation.', + 'PLUPLOAD_IO_ERROR' => 'Erreur E/S.', + 'PLUPLOAD_NOT_APPLICABLE' => 'ND', + 'PLUPLOAD_SECURITY_ERROR' => 'Erreur de sécurité.', + 'PLUPLOAD_SELECT_FILES' => 'Sélectionner des fichiers', + 'PLUPLOAD_SIZE' => 'Taille', + 'PLUPLOAD_SIZE_ERROR' => 'Erreur de taille de fichier.', + 'PLUPLOAD_STATUS' => 'Statut', + 'PLUPLOAD_START_UPLOAD' => 'Démarrer le transfert', + 'PLUPLOAD_START_CURRENT_UPLOAD' => 'Démarrer le transfert de la file', + 'PLUPLOAD_STOP_UPLOAD' => 'Interrompre le transfert', + 'PLUPLOAD_STOP_CURRENT_UPLOAD' => 'Interrompre le transfert actuel', + // Note: This string is formatted independently by plupload and so does not + // use the same formatting rules as normal phpBB translation strings + 'PLUPLOAD_UPLOADED' => '%d/%d fichiers transférés', +]); diff --git a/language/fr/posting.php b/language/fr/posting.php new file mode 100644 index 0000000..ab54885 --- /dev/null +++ b/language/fr/posting.php @@ -0,0 +1,287 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ADD_ATTACHMENT' => 'Insérer une pièce jointe', + 'ADD_ATTACHMENT_EXPLAIN' => 'Si vous souhaitez insérer un ou plusieurs fichiers, veuillez renseigner les informations demandées ci-dessous.', + 'ADD_FILE' => 'Ajouter le fichier', + 'ADD_POLL' => 'Créer un sondage', + 'ADD_POLL_EXPLAIN' => 'Laissez ces champs vides si vous ne souhaitez pas insérer de sondage à votre sujet.', + 'ALREADY_DELETED' => 'Le message a déjà été supprimé.', + 'ATTACH_DISK_FULL' => 'Vous ne pouvez pas transférer cette pièce jointe car l’espace disque est insuffisant.', + 'ATTACH_QUOTA_REACHED' => 'La limite du nombre de pièces jointes sur le forum a été atteinte.', + 'ATTACH_SIG' => 'Insérer une signature (personnalisable dans le panneau de contrôle de l’utilisateur)', + + 'BBCODE_A_HELP' => 'Insérer une pièce jointe dans une ligne : [attachment=]nomdufichier.ext[/attachment]', + 'BBCODE_B_HELP' => 'Texte en gras : [b]texte[/b]', + 'BBCODE_C_HELP' => 'Afficher du code : [code]code[/code]', + 'BBCODE_D_HELP' => 'Flash : [flash=largeur,hauteur]http://lien[/flash]', + 'BBCODE_F_HELP' => 'Taille de la police : [size=85]texte de petite taille[/size]', + 'BBCODE_IS_OFF' => 'Le %sBBCode%s est désactivé', + 'BBCODE_IS_ON' => 'Le %sBBCode%s est activé', + 'BBCODE_I_HELP' => 'Texte en italique : [i]texte[/i]', + 'BBCODE_L_HELP' => 'Liste : [list][*]texte[/list]', + 'BBCODE_LISTITEM_HELP' => 'Objet d’une liste : [*]texte', + 'BBCODE_O_HELP' => 'Liste ordonnée : [list=1][*]Premier point[/list] ou [list=a][*]Point A[/list]', + 'BBCODE_P_HELP' => 'Insérer une image : [img]http://lien_de_l_image[/img]', + 'BBCODE_Q_HELP' => 'Citer du texte : [quote]texte[/quote]', + 'BBCODE_S_HELP' => 'Couleur de la police : [color=red]texte[/color] ou [color=#FF0000]texte[/color]', + 'BBCODE_U_HELP' => 'Souligner du texte : [u]texte[/u]', + 'BBCODE_W_HELP' => 'Insérer un lien : [url]http://lien[/url] ou [url=http://lien]texte du lien[/url]', + 'BBCODE_Y_HELP' => 'Liste : ajouter une liste d’éléments', + 'BUMP_ERROR' => 'Vous devez patienter quelques instants avant de remonter ce sujet.', + + 'CANNOT_DELETE_REPLIED' => 'Vous ne pouvez pas supprimer les messages qui ont fait l’objet d’une ou plusieurs réponses.', + 'CANNOT_EDIT_POST_LOCKED' => 'Le message a été verrouillé, vous ne pouvez désormais plus le modifier.', + 'CANNOT_EDIT_TIME' => 'Vous ne pouvez désormais plus modifier ou supprimer ce message.', + 'CANNOT_POST_ANNOUNCE' => 'Vous ne pouvez pas publier d’annonces.', + 'CANNOT_POST_STICKY' => 'Vous ne pouvez pas publier de notes.', + 'CHANGE_TOPIC_TO' => 'Modifier le type de sujet en', + 'CHARS_POST_CONTAINS' => [ + 1 => 'Votre message contient %1$d caractère.', + 2 => 'Votre message contient %1$d caractères.', + ], + 'CHARS_SIG_CONTAINS' => [ + 1 => 'Votre signature contient %1$d caractère.', + 2 => 'Votre signature contient %1$d caractères.', + ], + 'CLOSE_TAGS' => 'Fermer les balises', + 'CURRENT_TOPIC' => 'Sujet actuel', + + 'DELETE_FILE' => 'Supprimer le fichier', + 'DELETE_MESSAGE' => 'Supprimer le message', + 'DELETE_MESSAGE_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce message ?', + 'DELETE_OWN_POSTS' => 'Vous ne pouvez supprimer que vos propres messages.', + 'DELETE_PERMANENTLY' => 'Supprimer définitivement', + 'DELETE_POST_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce message ?', + 'DELETE_POST_PERMANENTLY_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer définitivement ce message ?', + 'DELETE_POST_PERMANENTLY' => [ + 1 => 'Supprimer définitivement ce message qui ne pourra pas être restauré', + 2 => 'Supprimer définitivement %1$d messages qui ne pourront pas être restaurés', + ], + 'DELETE_POSTS_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ces messages ?', + 'DELETE_POSTS_PERMANENTLY_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer définitivement ces messages ?', + 'DELETE_REASON' => 'Raison de la suppression', + 'DELETE_REASON_EXPLAIN' => 'La raison de la suppression sera affichée aux modérateurs.', + 'DELETE_POST_WARN' => 'Supprimer ce message', + 'DELETE_TOPIC_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce sujet ?', + 'DELETE_TOPIC_PERMANENTLY' => [ + 1 => 'Supprimer définitivement ce sujet qui ne pourra pas être restauré', + 2 => 'Supprimer définitivement %1$d sujets qui ne pourront pas être restaurés', + ], + 'DELETE_TOPIC_PERMANENTLY_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer définitivement ce sujet ?', + 'DELETE_TOPICS_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ces sujets ?', + 'DELETE_TOPICS_PERMANENTLY_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer définitivement ces sujets ?', + 'DISABLE_BBCODE' => 'Désactiver les BBCodes', + 'DISABLE_MAGIC_URL' => 'Ne pas analyser automatiquement la syntaxe des liens', + 'DISABLE_SMILIES' => 'Désactiver les émoticônes', + 'DISALLOWED_CONTENT' => 'Le transfert a été interrompu car le fichier que vous avez souhaité transférer a été identifié comme une vulnérabilité potentielle.', + 'DISALLOWED_EXTENSION' => 'L’extension « %s » n’est pas autorisée.', + 'DRAFT_LOADED' => 'Le brouillon a été chargé dans la zone de rédaction, vous pouvez à présent finaliser votre message.
Votre brouillon sera supprimé après l’envoi du message.', + 'DRAFT_LOADED_PM' => 'Le brouillon a été chargé dans la zone de rédaction, vous pouvez à présent finaliser votre message privé.
Votre brouillon sera supprimé après l’envoi du message privé.', + 'DRAFT_SAVED' => 'Le brouillon a été enregistré.', + 'DRAFT_TITLE' => 'Titre du brouillon', + + 'EDIT_REASON' => 'Raison de la modification', + 'EMPTY_FILEUPLOAD' => 'Le fichier transféré ne contient aucune donnée.', + 'EMPTY_MESSAGE' => 'Vous devez saisir le contenu de votre message.', + 'EMPTY_REMOTE_DATA' => 'Le fichier n’a pas pu être transféré, veuillez essayer de le transférer manuellement.', + + 'FLASH_IS_OFF' => 'La balise [flash] est désactivée', + 'FLASH_IS_ON' => 'La balise [flash] est activée', + 'FLOOD_ERROR' => 'Vous devez patienter quelques instants avant de publier un nouveau message.', + 'FONT_COLOR' => 'Couleur de la police', + 'FONT_COLOR_HIDE' => 'Masquer la couleur de la police', + 'FONT_HUGE' => 'Énorme', + 'FONT_LARGE' => 'Grande', + 'FONT_NORMAL' => 'Normale', + 'FONT_SIZE' => 'Taille de la police', + 'FONT_SMALL' => 'Petite', + 'FONT_TINY' => 'Minuscule', + + 'GENERAL_UPLOAD_ERROR' => 'Impossible de transférer la pièce jointe sur %s.', + + 'IMAGES_ARE_OFF' => 'La balise [img] est désactivée', + 'IMAGES_ARE_ON' => 'La balise [img] est activée', + 'INVALID_FILENAME' => '%s est un nom de fichier invalide.', + + 'LOAD' => 'Charger', + 'LOAD_DRAFT' => 'Charger un brouillon', + 'LOAD_DRAFT_EXPLAIN' => 'Vous pouvez sélectionner ici le brouillon que vous souhaitez continuer à rédiger. Votre message actuel sera annulé et tout son contenu sera supprimé. Vous pouvez consulter, modifier et supprimer vos brouillons depuis le panneau de contrôle de l’utilisateur.', + 'LOGIN_EXPLAIN_BUMP' => 'Vous devez vous connecter afin de pouvoir remonter les sujets de ce forum.', + 'LOGIN_EXPLAIN_DELETE' => 'Vous devez vous connecter afin de pouvoir supprimer définitivement les messages de ce forum.', + 'LOGIN_EXPLAIN_SOFT_DELETE' => 'Vous devez vous connecter afin de pouvoir supprimer les messages de ce forum.', + 'LOGIN_EXPLAIN_POST' => 'Vous devez vous connecter afin de pouvoir publier des messages dans ce forum.', + 'LOGIN_EXPLAIN_QUOTE' => 'Vous devez vous connecter afin de pouvoir citer les messages de ce forum.', + 'LOGIN_EXPLAIN_REPLY' => 'Vous devez vous connecter afin de pouvoir répondre aux sujets de ce forum.', + + 'MAX_FONT_SIZE_EXCEEDED' => 'Vous ne devez utiliser que les polices de caractères dont la taille est inférieure à %1$d.', + 'MAX_FLASH_HEIGHT_EXCEEDED' => [ + 1 => 'Vos fichiers Flash ne doivent pas dépasser %d pixel de haut.', + 2 => 'Vos fichiers Flash ne doivent pas dépasser %d pixels de haut.', + ], + 'MAX_FLASH_WIDTH_EXCEEDED' => [ + 1 => 'Vos fichiers Flash ne doivent pas dépasser %d pixel de large.', + 2 => 'Vos fichiers Flash ne doivent pas dépasser %d pixels de large.', + ], + 'MAX_IMG_HEIGHT_EXCEEDED' => [ + 1 => 'Vos images ne doivent pas dépasser %1$d pixel de haut.', + 2 => 'Vos images ne doivent pas dépasser %1$d pixels de haut.', + ], + 'MAX_IMG_WIDTH_EXCEEDED' => [ + 1 => 'Vos images ne doivent pas dépasser %d pixel de large.', + 2 => 'Vos images ne doivent pas dépasser %d pixels de large.', + ], + + 'MESSAGE_BODY_EXPLAIN' => [ + 0 => '', // zero means no limit, so we don't view a message here. + 1 => 'Veuillez saisir votre message ici. Il ne doit pas contenir plus de %d caractère.', + 2 => 'Veuillez saisir votre message ici. Il ne doit pas contenir plus de %d caractères.', + ], + 'MESSAGE_DELETED' => 'Le message a été supprimé.', + 'MORE_SMILIES' => 'Plus d’émoticônes', + + 'NOTIFY_REPLY' => 'Recevoir une notification lorsqu’une réponse est publiée', + 'NOT_UPLOADED' => 'Une erreur est survenue lors du transfert du fichier.', + 'NO_DELETE_POLL_OPTIONS' => 'Vous ne pouvez pas supprimer les options d’un sondage qui existent déjà.', + 'NO_PM_ICON' => 'Aucune', + 'NO_POLL_TITLE' => 'Vous devez saisir le titre du sondage.', + 'NO_POST' => 'Le message que vous souhaitez consulter est introuvable.', + 'NO_POST_MODE' => 'Aucun mode de message n’a été sélectionné.', + 'NO_TEMP_DIR' => 'Le répertoire temporaire est introuvable ou est en lecture seule.', + + 'PARTIAL_UPLOAD' => 'Le fichier n’a été que partiellement transféré.', + 'PHP_UPLOAD_STOPPED' => 'Une extension PHP a interrompu le transfert du fichier.', + 'PHP_SIZE_NA' => 'La taille de la pièce jointe est trop importante.
La taille maximale autorisée, située dans le fichier « php.ini », n’a pas pu être déterminée.', + 'PHP_SIZE_OVERRUN' => 'La taille de la pièce jointe est trop importante, la taille maximale autorisée est de %1$d %2$s.
Veuillez noter que cette valeur est située dans le fichier « php.ini » et qu’elle ne peut pas être remplacée.', + 'PLACE_INLINE' => 'Insérer dans la ligne', + 'POLL_DELETE' => 'Supprimer le sondage', + 'POLL_FOR' => 'Durée du sondage', + 'POLL_FOR_EXPLAIN' => 'Laissez cette valeur à 0 si vous ne souhaitez pas limiter la durée du sondage.', + 'POLL_MAX_OPTIONS' => 'Options par utilisateur', + 'POLL_MAX_OPTIONS_EXPLAIN' => 'Cette valeur correspond au nombre d’options que les utilisateurs peuvent sélectionner lors d’un vote.', + 'POLL_OPTIONS' => 'Options du sondage', + 'POLL_OPTIONS_EXPLAIN' => [ + 1 => 'Insérez chaque option sur une nouvelle ligne. Vous pouvez saisir %d option.', + 2 => 'Insérez chaque option sur une nouvelle ligne. Vous pouvez saisir jusqu’à %d options.', + ], + 'POLL_OPTIONS_EDIT_EXPLAIN' => [ + 1 => 'Insérez chaque option sur une nouvelle ligne. Vous pouvez saisir %d option. Si vous supprimez ou ajoutez une ou plusieurs options, tous les votes effectués seront réinitialisés.', + 2 => 'Insérez chaque option sur une nouvelle ligne. Vous pouvez saisir jusqu’à %d options. Si vous supprimez ou ajoutez une ou plusieurs options, tous les votes effectués seront réinitialisés.', + ], + 'POLL_QUESTION' => 'Question du sondage', + 'POLL_TITLE_TOO_LONG' => 'Le titre du sondage ne doit pas dépasser 100 caractères.', + 'POLL_TITLE_COMP_TOO_LONG' => 'La taille du titre de votre sondage est trop importante. Veuillez noter que les BBCodes et les émoticônes se sont pas pris en charge dans les titres.', + 'POLL_VOTE_CHANGE' => 'Votes multiples', + 'POLL_VOTE_CHANGE_EXPLAIN' => 'Si cette option est activée, les utilisateurs pourront modifier leur vote.', + 'POSTED_ATTACHMENTS' => 'Pièces jointes transférées', + 'POST_APPROVAL_NOTIFY' => 'Vous recevrez une notification lorsque votre message sera approuvé.', + 'POST_CONFIRMATION' => 'Confirmation du message', + 'POST_CONFIRM_EXPLAIN' => 'Vous devez saisir un code de confirmation afin de nous permettre de nous prémunir contre les soumissions automatisées et intensives effectuées par des robots malveillants. Le code est affiché dans l’image ci-dessous. Si vous éprouvez des difficultés à lire ce code correctement, veuillez contacter un %sadministrateur du forum%s.', + 'POST_DELETED' => 'Le message a été supprimé.', + 'POST_EDITED' => 'Le message a été modifié.', + 'POST_EDITED_MOD' => 'Le message a été modifié mais il doit être approuvé par un modérateur avant de pouvoir être visible publiquement.', + 'POST_GLOBAL' => 'Annonce générale', + 'POST_ICON' => 'Icône de message', + 'POST_NORMAL' => 'Normal', + 'POST_REVIEW' => 'Prévisualisation du message', + 'POST_REVIEW_EDIT' => 'Prévisualisation du message', + 'POST_REVIEW_EDIT_EXPLAIN' => 'Le message a été modifié par un autre utilisateur lorsque vous étiez vous-même en train de le modifier. Vous pouvez prévisualiser la version actuelle du message et ajuster vos modifications.', + 'POST_REVIEW_EXPLAIN' => 'Au moins un nouveau message a été publié à partir de ce sujet. Vous pouvez consulter votre message qui apparaîtra en surbrillance.', + 'POST_STORED' => 'Le message a été publié.', + 'POST_STORED_MOD' => 'Le message a été envoyé mais il doit être approuvé par un modérateur avant de pouvoir être visible publiquement.', + 'POST_TOPIC_AS' => 'Publier le sujet comme', + 'PROGRESS_BAR' => 'Barre de progression', + + 'QUOTE_DEPTH_EXCEEDED' => [ + 1 => 'Vous ne pouvez imbriquer que %d citation.', + 2 => 'Vous ne pouvez imbriquer que %d citations.', + ], + 'QUOTE_NO_NESTING' => 'Vous ne pouvez pas imbriquer de citations.', + + 'REMOTE_UPLOAD_TIMEOUT' => 'Le fichier que vous avez spécifié n’a pu être transféré car le délai d’attente de la requête a expiré.', + 'SAVE' => 'Enregistrer', + 'SAVE_DATE' => 'Enregistré le', + 'SAVE_DRAFT' => 'Enregistrer comme brouillon', + 'SAVE_DRAFT_CONFIRM' => 'Veuillez noter que les messages enregistrés comme brouillons ne comportent que le sujet et le texte du message, tout autre élément sera supprimé. Souhaitez-vous enregistrer votre message comme brouillon ?', + 'SMILIES' => 'Émoticônes', + 'SMILIES_ARE_OFF' => 'Les émoticônes sont désactivées', + 'SMILIES_ARE_ON' => 'Les émoticônes sont activées', + 'STICKY_ANNOUNCE_TIME_LIMIT' => 'Durée limite de la note, annonce ou annonce générale', + 'STICK_TOPIC_FOR' => 'Durée limite de la note', + 'STICK_TOPIC_FOR_EXPLAIN' => 'Laissez cette valeur à 0 si vous ne souhaitez pas que la note, l’annonce ou l’annonce générale expire et se transforme en sujet normal. Veuillez noter que le nombre de jours est relatif à la date de publication du message.', + 'STYLES_TIP' => 'Astuce : les mises en forme peuvent rapidement être appliquées en sélectionnant le texte.', + + 'TOO_FEW_CHARS' => 'Votre message ne contient pas assez de caractères.', + 'TOO_FEW_CHARS_LIMIT' => [ + 1 => 'Vous devez saisir au moins %1$d caractère.', + 2 => 'Vous devez saisir au moins %1$d caractères.', + ], + 'TOO_FEW_POLL_OPTIONS' => 'Vous devez saisir au moins deux options afin de créer un sondage.', + 'TOO_MANY_ATTACHMENTS' => 'Impossible de transférer une nouvelle pièce jointe. La taille maximale de l’espace de stockage des pièces jointes est limitée à %d.', + 'TOO_MANY_CHARS' => 'Votre message contient trop de caractères.', + 'TOO_MANY_CHARS_LIMIT' => [ + 1 => 'Le nombre maximal de caractères autorisés est de %1$d.', + 2 => 'Le nombre maximal de caractères autorisés est de %1$d.', + ], + 'TOO_MANY_POLL_OPTIONS' => 'Vous avez essayé de voter pour un trop grand nombre d’options.', + 'TOO_MANY_SMILIES' => 'Votre message contient trop d’émoticônes. La limite maximale d’émoticônes est de %d.', + 'TOO_MANY_URLS' => 'Votre message contient trop de liens. La limite maximale de liens est de %d.', + 'TOO_MANY_USER_OPTIONS' => 'Vous ne pouvez pas définir autant d’options par utilisateur car le sondage en contient moins.', + 'TOPIC_BUMPED' => 'Le sujet a été remonté.', + + 'UNAUTHORISED_BBCODE' => 'Vous ne pouvez pas utiliser certains BBCodes : %s.', + 'UNGLOBALISE_EXPLAIN' => 'Pour rétablir cette annonce générale en sujet standard, vous devez sélectionner le forum dans lequel vous souhaitez voir apparaître ce sujet.', + 'UNSUPPORTED_CHARACTERS_MESSAGE' => 'Votre message contient les caractères non pris en charge suivants :
%s', + 'UNSUPPORTED_CHARACTERS_SUBJECT' => 'Votre sujet contient les caractères non pris en charge suivants :
%s', + 'UPDATE_COMMENT' => 'Mettre à jour la description', + 'URL_INVALID' => 'Le lien que vous avez spécifié est invalide.', + 'URL_NOT_FOUND' => 'Le fichier que vous avez spécifié est introuvable.', + 'URL_IS_OFF' => 'La balise [url] est désactivée', + 'URL_IS_ON' => 'La balise [url] est activée', + 'USER_CANNOT_BUMP' => 'Vous ne pouvez pas remonter les sujets de ce forum.', + 'USER_CANNOT_DELETE' => 'Vous ne pouvez pas supprimer les messages de ce forum.', + 'USER_CANNOT_EDIT' => 'Vous ne pouvez pas modifier les messages de ce forum.', + 'USER_CANNOT_REPLY' => 'Vous ne pouvez pas répondre aux messages de ce forum.', + 'USER_CANNOT_FORUM_POST' => 'Vous ne pouvez pas publier de messages dans ce type de forum.', + + 'VIEW_MESSAGE' => '%sConsulter le message%s', + 'VIEW_PRIVATE_MESSAGE' => '%sConsulter le message privé%s', + + 'WRONG_FILESIZE' => 'Le fichier est trop lourd, la taille maximale autorisée est de %1$d %2$s.', + 'WRONG_SIZE' => 'La taille de l’image doit mesurer moins de %1$s de large et %2$s de haut et plus de %3$s de large et %4$s de haut. Votre image mesure %5$s de large et %6$s de haut.', +]); diff --git a/language/fr/search.php b/language/fr/search.php new file mode 100644 index 0000000..c5cbb3a --- /dev/null +++ b/language/fr/search.php @@ -0,0 +1,123 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ALL_AVAILABLE' => 'Tous disponibles', + 'ALL_RESULTS' => 'Tous les résultats', + + 'DISPLAY_RESULTS' => 'Afficher les résultats sous forme de', + + 'FOUND_SEARCH_MATCHES' => [ + 1 => 'La recherche a retourné %d résultat', + 2 => 'La recherche a retourné %d résultats', + ], + 'FOUND_MORE_SEARCH_MATCHES' => [ + 1 => 'La recherche a retourné plus de %d résultat', + 2 => 'La recherche a retourné plus de %d résultats', + ], + + 'GLOBAL' => 'Annonce générale', + + 'IGNORED_TERMS' => 'ignorés', + 'IGNORED_TERMS_EXPLAIN' => 'Les mots suivants ont été ignorés car ils sont considérés comme trop communs : « %s ».', + + 'JUMP_TO_POST' => 'Aller au message', + + 'LOGIN_EXPLAIN_EGOSEARCH' => 'Vous devez vous inscrire et vous connecter afin de consulter vos messages.', + 'LOGIN_EXPLAIN_UNREADSEARCH' => 'Vous devez vous inscrire et vous connecter afin de consulter vos messages non lus.', + 'LOGIN_EXPLAIN_NEWPOSTS' => 'Vous devez vous inscrire et vous connecter afin de consulter les nouveaux messages qui ont été publiés depuis votre dernière visite.', + + 'MAX_NUM_SEARCH_KEYWORDS_REFINE' => [ + 1 => 'Vous avez spécifié un trop grand nombre de mots à rechercher. Veuillez ne pas saisir plus de %1$d mot.', + 2 => 'Vous avez spécifié un trop grand nombre de mots à rechercher. Veuillez ne pas saisir plus de %1$d mots.', + ], + + 'NO_KEYWORDS' => 'Vous devez saisir au moins un mot afin d’effectuer une recherche. Chaque mot doit être composé d’au moins %s caractères et ne doit pas en contenir plus de %s, en excluant en utilisant les astérisques « * » utilisés comme métacaractères passe-partout.', + 'NO_RECENT_SEARCHES' => 'Aucune recherche n’a été effectuée récemment.', + 'NO_SEARCH' => 'Vous ne pouvez pas effectuer de recherche.', + 'NO_SEARCH_RESULTS' => 'Aucun résultat ne correspond aux termes que vous avez spécifiés.', + 'NO_SEARCH_LOAD' => 'Vous ne pouvez pas effectuer de recherche pour le moment car le serveur est en surcharge. Veuillez réessayer ultérieurement.', + 'NO_SEARCH_TIME' => [ + 1 => 'Vous ne pouvez pas effectuer de recherche pour le moment. Veuillez réessayer dans %d seconde.', + 2 => 'Vous ne pouvez pas effectuer de recherche pour le moment. Veuillez réessayer dans %d secondes.', + ], + 'NO_SEARCH_UNREADS' => 'Le système de recherche des messages non lus a été désactivé sur ce forum.', + 'WORD_IN_NO_POST' => 'Le mot « %s » n’est présent dans aucun message.', + 'WORDS_IN_NO_POST' => 'Les mots « %s » ne sont présents dans aucun message.', + + 'POST_CHARACTERS' => 'caractères des messages', + 'PHRASE_SEARCH_DISABLED' => 'La recherche par phrase exacte n’est pas prise en charge sur ce forum.', + + 'RECENT_SEARCHES' => 'Recherches récentes', + 'RESULT_DAYS' => 'Limiter les résultats selon leur ancienneté', + 'RESULT_SORT' => 'Trier les résultats par', + 'RETURN_FIRST' => 'Afficher seulement les premiers', + 'GO_TO_SEARCH_ADV' => 'Aller sur la recherche avancée', + + 'SEARCHED_FOR' => 'Rechercher les termes utilisés', + 'SEARCHED_TOPIC' => 'Sujet recherché', + 'SEARCHED_QUERY' => 'Requête recherchée', + 'SEARCH_ALL_TERMS' => 'Rechercher tous les termes ou utiliser une question comme élément', + 'SEARCH_ANY_TERMS' => 'Rechercher n’importe quel de ces termes', + 'SEARCH_AUTHOR' => 'Rechercher par auteur', + 'SEARCH_AUTHOR_EXPLAIN' => 'Utilisez un astérisque « * » comme métacaractère passe-partout si vous souhaitez effectuer des recherches partielles.', + 'SEARCH_FIRST_POST' => 'Le premier message des sujets uniquement', + 'SEARCH_FORUMS' => 'Rechercher dans les forums', + 'SEARCH_FORUMS_EXPLAIN' => 'Sélectionnez le ou les forums dans lesquels vous souhaitez effectuer une recherche. Les sous-forums seront automatiquement inclus dans la recherche si vous ne désactivez pas l’option « Rechercher dans les sous-forums » affichée ci-dessous.', + 'SEARCH_IN_RESULTS' => 'Rechercher dans ces résultats', + 'SEARCH_KEYWORDS_EXPLAIN' => 'Insérez le caractère « + » devant un mot qui doit être trouvé et « - » devant un mot qui doit être ignoré. Insérez une liste de mots séparés entre des barres verticales discontinues « | » si seul un des mots doit être trouvé. Utilisez un astérisque « * » comme métacaractère passe-partout si vous souhaitez effectuer des recherches partielles.', + 'SEARCH_MSG_ONLY' => 'Le contenu des messages uniquement', + 'SEARCH_OPTIONS' => 'Préférences de la recherche', + 'SEARCH_QUERY' => 'Requête de la recherche', + 'SEARCH_SUBFORUMS' => 'Rechercher dans les sous-forums', + 'SEARCH_TITLE_MSG' => 'Le titre des sujets et le contenu des messages', + 'SEARCH_TITLE_ONLY' => 'Le titre des sujets uniquement', + 'SEARCH_WITHIN' => 'Rechercher dans', + 'SORT_ASCENDING' => 'Croissant', + 'SORT_AUTHOR' => 'Auteur', + 'SORT_DESCENDING' => 'Décroissant', + 'SORT_FORUM' => 'Forum', + 'SORT_POST_SUBJECT' => 'Sujet du message', + 'SORT_TIME' => 'Date du message', + 'SPHINX_SEARCH_FAILED' => 'Une erreur est survenue lors de la recherche : %s', + 'SPHINX_SEARCH_FAILED_LOG' => 'Une erreur est survenue lors de la recherche. Pour plus d’informations, veuillez consulter l’historique des erreurs.', + + 'TOO_FEW_AUTHOR_CHARS' => [ + 1 => 'Vous devez saisir au moins %d caractère du nom des auteurs.', + 2 => 'Vous devez saisir au moins %d caractères du nom des auteurs.', + ], +]); diff --git a/language/fr/ucp.php b/language/fr/ucp.php new file mode 100644 index 0000000..76addfd --- /dev/null +++ b/language/fr/ucp.php @@ -0,0 +1,661 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +// Privacy policy and T&C +$lang = array_merge($lang, [ + 'TERMS_OF_USE_CONTENT' => 'En accédant à « %1$s » (désigné ci-après par « nous », « notre », « nos », « %1$s » et « %2$s »), vous acceptez d’être légalement responsable des conditions suivantes. Si vous n’acceptez pas d’être légalement responsable de toutes les conditions suivantes, veuillez ne pas utiliser et accéder à « %1$s ». Nous pouvons modifier ces conditions à n’importe quel moment et nous essaierons de vous informer de ces modifications, bien que nous vous conseillons de vérifier régulièrement par vous-même. En effet, si vous continuez à participer à « %1$s » après que des modifications aient été effectuées, vous acceptez d’être légalement responsable des conditions modifiées et mises à jour.
+
+ Nos forums sont développés par phpBB (désignés ci-après par « logiciel phpBB » et « phpBB Limited ») qui est un logiciel de forum de discussions déclaré sous la « licence publique générale GNU 2.0 » et qui peut être téléchargé sur le site de phpBB (en anglais). Le logiciel phpBB a pour seul but de faciliter les discussions sur internet et phpBB Limited ne peut en aucun cas être tenu comme responsable de la conduite et du contenu que nous acceptons et que nous n’acceptons pas. Pour plus d’informations concernant phpBB, veuillez consulter le site de phpBB (en anglais).
+
+ Vous acceptez de ne publier aucun contenu à caractère abusif, obscène, vulgaire, diffamatoire, choquant, menaçant, pornographique, etc. qui pourrait transgresser la législation de votre pays, du pays dans lequel le serveur de « %1$s » est hébergé ou encore la loi internationale. Si vous ne respectez pas ces dispositions, vous vous exposez à un bannissement immédiat et définitif et nous nous réservons le droit d’avertir votre fournisseur d’accès à internet et les autorités officielles. L’adresse IP de tous les messages est enregistrée afin d’aider au renforcement de ces conditions. Vous acceptez le fait que « %1$s » ait le droit de supprimer, de modifier, de déplacer ou de verrouiller n’importe quel sujet et message à n’importe quel moment si nous estimons cela nécessaire. En tant qu’utilisateur, vous acceptez que toutes les informations que vous avez renseignées soient enregistrées dans notre base de données. Bien que ces informations ne seront pas diffusées à une tierce partie sans votre consentement, ni « %1$s », ni phpBB, ne pourront être tenus comme responsables en cas de tentative de piratage informatique visant à compromettre vos données. + ', + + 'PRIVACY_POLICY' => 'Cette politique de confidentialité explique en détail comment « %1$s » et ses partenaires affiliés (désignés ci-après par « nous », « notre », « nos », « %1$s » et « %2$s ») et phpBB (désigné ci-après par « logiciel phpBB » et « phpBB Limited ») utilisent toutes les informations collectées lors des sessions d’utilisation de votre part (désignées ci-après par « vos informations »).
+
+ Vos informations sont collectées de deux manières différentes. Premièrement, en naviguant sur « %1$s », le logiciel phpBB génèrera un certain nombre de cookies qui sont de petits fichiers téléchargés temporairement par le navigateur internet de votre ordinateur. Les deux premiers cookies ne contiennent qu’un identifiant utilisateur et un identifiant anonyme de session qui vous sont automatiquement assignés par le logiciel phpBB. Un troisième cookie sera créé lors de votre navigation sur les sujets de « %1$s », archivant de ce fait tous les sujets que vous avez consultés et permettant d’améliorer votre confort de navigation en tant qu’utilisateur.
+
+ Lors de votre navigation sur « %1$s », nous pouvons également créer une quatrième sorte de cookies, externes au document qui est prévu pour couvrir uniquement les pages créées par le logiciel phpBB. La seconde manière est de récupérer les informations que vous nous envoyez et que nous collectons. Ceci peut correspondre — mais n’est pas limité à — la publication de messages en tant qu’utilisateur anonyme, l’inscription sur « %1$s » (désignée ci-après par « votre compte ») et les messages que vous publiez après votre inscription et lors de votre connexion (désignés ci-après par « vos messages »).
+
+ Votre compte contiendra au minimum un identifiant unique (désigné ci-après par « votre nom d’utilisateur ») et un mot de passe personnel vous permettant de vous connecter à votre compte (désigné ci-après par « votre mot de passe ») et une adresse de courriel personnelle. Les informations de votre compte sur « %1$s » sont protégées par les lois de protection des données applicables dans le pays qui héberge notre serveur. Toutes les informations, en-dehors de votre nom d’utilisateur, de votre mot de passe et de votre adresse de courriel requis par « %1$s » durant votre inscription, sont obligatoires ou facultatives, à la seule discrétion de « %1$s ». Dans tous les cas, vous pouvez contrôler quelles informations de votre compte vous souhaitez rendre publiques ou non. De plus, vous pouvez décider de vous abonner ou non à la liste de diffusion du logiciel phpBB depuis une option disponible sur votre compte.
+
+ Votre mot de passe est chiffré (par un chiffrage à sens unique) afin qu’il soit sécurisé. Cependant, il est recommandé de ne pas utiliser le même mot de passe sur plusieurs sites internet différents. Votre mot de passe est le moyen d’accès à votre compte sur « %1$s », veillez donc à le conservez précieusement. En aucun cas une personne affiliée à « %1$s », à phpBB ou à un site de tierce partie ne peut vous demander légitimement votre mot de passe. Si vous oubliez le mot de passe de votre compte, vous pouvez utiliser la fonction « J’ai perdu mon mot de passe » qui est proposée par défaut sur le logiciel phpBB. Cette fonctionnalité vous demandera de spécifier votre nom d’utilisateur et votre adresse de courriel et le logiciel phpBB générera alors un nouveau mot de passe afin que vous puissiez reprendre le contrôle de votre compte.
+ ', +]); + +// Common language entries +$lang = array_merge($lang, [ + 'ACCOUNT_ACTIVE' => 'Votre compte est à présent activé. Nous vous remercions de votre inscription.', + 'ACCOUNT_ACTIVE_ADMIN' => 'Le compte est à présent activé.', + 'ACCOUNT_ACTIVE_PROFILE' => 'Votre compte est à présent réactivé.', + 'ACCOUNT_ADDED' => 'Votre compte a été créé, nous vous remercions de votre inscription. Vous pouvez à présent vous connecter avec votre nom d’utilisateur et votre mot de passe.', + 'ACCOUNT_COPPA' => 'Votre compte a été créé mais nécessite d’être activé. Pour plus d’informations, veuillez consulter votre messagerie.', + 'ACCOUNT_EMAIL_CHANGED' => 'Votre compte a été mis à jour. Cependant, vous devez le réactiver car vous avez modifié votre adresse de courriel. Une clé d’activation a été envoyée sur la nouvelle adresse de courriel que vous avez spécifiée. Pour plus d’informations, veuillez consulter votre messagerie.', + 'ACCOUNT_EMAIL_CHANGED_ADMIN' => 'Votre compte a été mis à jour. Cependant, un administrateur doit le réactiver car vous avez modifié votre adresse de courriel. Vous serez informé lors de la réactivation de votre compte.', + 'ACCOUNT_INACTIVE' => 'Votre compte a été créé. Cependant, vous devez activer votre compte. Une clé d’activation a été envoyée sur l’adresse de courriel que vous avez spécifiée. Pour plus d’informations, veuillez consulter votre messagerie. Si vous n’avez pas reçu le courriel, assurez-vous que celui-ci ne soit pas présent dans votre boîte de courriels indésirables. Selon le fournisseur de messagerie, il se peut également que la réception nécessite un certain temps.', + 'ACCOUNT_INACTIVE_ADMIN' => 'Votre compte a été créé. Cependant, un administrateur doit activer votre compte. Vous serez informé lors de la réactivation de votre compte.', + 'ACTIVATION_EMAIL_SENT' => 'Le courriel d’activation a été envoyé à votre adresse de courrier électronique.', + 'ACTIVATION_EMAIL_SENT_ADMIN' => 'Le courriel d’activation a été envoyé aux adresses de courriel des administrateurs.', + 'ADD' => 'Ajouter', + 'ADD_BCC' => 'Ajouter [Cci]', + 'ADD_FOES' => 'Ajouter des ignorés', + 'ADD_FOES_EXPLAIN' => 'Vous devez saisir chaque nom d’utilisateur sur une nouvelle ligne.', + 'ADD_FOLDER' => 'Ajouter une boîte', + 'ADD_FRIENDS' => 'Ajouter des amis', + 'ADD_FRIENDS_EXPLAIN' => 'Vous devez saisir chaque nom d’utilisateur sur une nouvelle ligne.', + 'ADD_NEW_RULE' => 'Ajouter une nouvelle règle', + 'ADD_RULE' => 'Ajouter une règle', + 'ADD_TO' => 'Ajouter [À]', + 'ADD_USERS_UCP_EXPLAIN' => 'Vous pouvez ajouter ici de nouveaux utilisateurs à des groupes. Vous pouvez décider que ce groupe deviendra le nouveau groupe par défaut de ces utilisateurs. Veuillez saisir chaque nom d’utilisateur sur une nouvelle ligne.', + 'ADMIN_EMAIL' => 'Autoriser les administrateurs à me contacter par courriel', + 'AGREE' => 'J’accepte ces conditions', + 'ALLOW_PM' => 'Autoriser les utilisateurs à me contacter par message privé', + 'ALLOW_PM_EXPLAIN' => 'Veuillez noter que malgré la désactivation de cette option, les administrateurs et les modérateurs pourront toujours vous envoyer des messages privés.', + 'ALREADY_ACTIVATED' => 'Vous avez déjà activé votre compte.', + 'ATTACHMENTS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter la liste des pièces jointes que vous avez transférées et insérées à vos messages sur le forum.', + 'ATTACHMENTS_DELETED' => 'Les pièces jointes ont été supprimées.', + 'ATTACHMENT_DELETED' => 'La pièce jointe a été supprimée.', + 'AUTOLOGIN_SESSION_KEYS_DELETED' => 'Les clés de connexions automatiques ont été supprimées.', + 'AVATAR_CATEGORY' => 'Catégorie', + 'AVATAR_DRIVER_GRAVATAR_TITLE' => 'Avatar de Gravatar', + 'AVATAR_DRIVER_GRAVATAR_EXPLAIN' => 'Gravatar est un service internet qui vous permet d’associer un avatar qui sera utilisé sur plusieurs sites internet. Pour plus d’informations, veuillez consulter le site internet de Gravatar.', + 'AVATAR_DRIVER_LOCAL_TITLE' => 'Avatar de la galerie', + 'AVATAR_DRIVER_LOCAL_EXPLAIN' => 'Sélectionnez votre avatar parmi une galerie d’images mise à votre disposition.', + 'AVATAR_DRIVER_REMOTE_TITLE' => 'Avatar externe', + 'AVATAR_DRIVER_REMOTE_EXPLAIN' => 'Saisissez le lien d’une image présente sur un site internet externe afin d’en faire votre avatar.', + 'AVATAR_DRIVER_UPLOAD_TITLE' => 'Avatar local', + 'AVATAR_DRIVER_UPLOAD_EXPLAIN' => 'Transférez une image présente sur votre ordinateur afin d’en faire votre avatar.', + 'AVATAR_EXPLAIN' => 'Dimensions maximales : %1$s de large et %2$s de haut ; %3$.2f Kio maximum.', + 'AVATAR_EXPLAIN_NO_FILESIZE' => 'Dimensions maximales : %1$s de large et %2$s de haut.', + 'AVATAR_FEATURES_DISABLED' => 'Les avatars sont actuellement désactivés.', + 'AVATAR_GALLERY' => 'Galerie locale', + 'AVATAR_GENERAL_UPLOAD_ERROR' => 'Impossible de transférer l’avatar vers %s.', + 'AVATAR_NOT_ALLOWED' => 'Votre avatar ne peut pas être affiché car les avatars n’ont pas été autorisés par les administrateurs de ce forum.', + 'AVATAR_PAGE' => 'Page', + 'AVATAR_SELECT' => 'Sélectionnez votre avatar', + 'AVATAR_TYPE' => 'Type d’avatar', + 'AVATAR_TYPE_NOT_ALLOWED' => 'Votre avatar actuel ne peut pas être affiché car ce type d’avatar n’a pas été autorisé par les administrateurs de ce forum.', + + 'BACK_TO_DRAFTS' => 'Revenir aux brouillons enregistrés', + 'BACK_TO_LOGIN' => 'Revenir à l’écran de connexion', + 'BIRTHDAY' => 'Anniversaire', + 'BIRTHDAY_EXPLAIN' => 'Si vous saisissez une année, votre âge sera affiché lors de votre anniversaire.', + 'BOARD_DATE_FORMAT' => 'Mon format de date', + 'BOARD_DATE_FORMAT_EXPLAIN' => 'La syntaxe qui est utilisée est identique à la fonction PHP « date() ».', + 'BOARD_LANGUAGE' => 'Ma langue', + 'BOARD_STYLE' => 'Mon style sur le forum', + 'BOARD_TIMEZONE' => 'Mon fuseau horaire', + 'BOOKMARKS' => 'Favoris', + 'BOOKMARKS_EXPLAIN' => 'Vous pouvez ajouter des sujets aux favoris afin de les consulter ultérieurement. Si vous souhaitez supprimer des sujets de vos favoris, cochez les cases correspondantes et cliquez sur le bouton « Supprimer les sujets sélectionnés des favoris ».', + 'BOOKMARKS_DISABLED' => 'Les favoris ont été désactivés sur ce forum.', + 'BOOKMARKS_REMOVED' => 'Les sujets ont été supprimés de vos favoris.', + + 'CANNOT_EDIT_MESSAGE_TIME' => 'Vous ne pouvez désormais plus modifier ou supprimer ce message.', + 'CANNOT_MOVE_TO_SAME_FOLDER' => 'Les messages ne peuvent pas être déplacés dans la boîte que vous souhaitez supprimer.', + 'CANNOT_MOVE_FROM_SPECIAL' => 'Les messages ne peuvent pas être déplacés de la boîte d’envoi.', + 'CANNOT_RENAME_FOLDER' => 'Cette boîte ne peut pas être renommée.', + 'CANNOT_REMOVE_FOLDER' => 'Cette boîte ne peut pas être supprimée.', + 'CHANGE_DEFAULT_GROUP' => 'Modifier le groupe par défaut', + 'CHANGE_PASSWORD' => 'Modifier le mot de passe', + 'CLICK_GOTO_FOLDER' => '%1$sAller à votre boîte « %3$s »%2$s', + 'CLICK_RETURN_FOLDER' => '%1$sRevenir sur votre boîte « %3$s »%2$s', + 'CONFIRMATION' => 'Confirmation d’inscription', + 'CONFIRM_CHANGES' => 'Confirmer les modifications', + 'CONFIRM_EXPLAIN' => 'Vous devez saisir un code de confirmation afin de nous permettre de nous prémunir contre les soumissions automatisées et intensives effectuées par des robots malveillants. Le code est affiché dans l’image ci-dessous. Si vous éprouvez des difficultés à lire ce code correctement, veuillez contacter un %sadministrateur du forum%s.', + 'VC_REFRESH' => 'Générer un nouveau code de confirmation', + 'VC_REFRESH_EXPLAIN' => 'Si vous éprouvez des difficultés à lire ce code correctement, vous pouvez générer un nouveau code en cliquant sur le bouton suivant.', + + 'CONFIRM_PASSWORD' => 'Confirmer le mot de passe', + 'CONFIRM_PASSWORD_EXPLAIN' => 'Vous ne devez confirmer votre mot de passe que si vous avez modifié ce dernier ci-dessus.', + 'COPPA_BIRTHDAY' => 'Veuillez renseigner votre date de naissance afin de continuer votre inscription.', + 'COPPA_COMPLIANCE' => 'Conformité à la loi COPPA', + 'COPPA_EXPLAIN' => 'Veuillez noter que si vous envoyez le formulaire, cela créera votre compte. Cependant, sachez que celui-ci ne peut être activé que si un de vos parents ou un tuteur légal approuve votre inscription. Vous recevrez une copie du formulaire contenant les renseignements à envoyer.', + 'CREATE_FOLDER' => 'Ajouter une boîte…', + 'CURRENT_IMAGE' => 'Image actuelle', + 'CURRENT_PASSWORD' => 'Mot de passe actuel', + 'CURRENT_PASSWORD_EXPLAIN' => 'Vous devez saisir votre mot de passe actuel si vous souhaitez modifier votre mot de passe, votre adresse de courriel ou votre nom d’utilisateur.', + 'CURRENT_CHANGE_PASSWORD_EXPLAIN' => 'Par mesure de sécurité, vous devez confirmer votre mot de passe actuel afin de modifier votre mot de passe, votre adresse de courriel ou votre nom d’utilisateur.', + 'CUR_PASSWORD_EMPTY' => 'Vous n’avez pas renseigné votre mot de passe actuel.', + 'CUR_PASSWORD_ERROR' => 'Le mot de passe que vous avez spécifié est incorrect.', + 'CUSTOM_DATEFORMAT' => 'Personnaliser…', + + 'DEFAULT_ACTION' => 'Opération par défaut', + 'DEFAULT_ACTION_EXPLAIN' => 'Cette opération sera effectuée si les opérations précédentes sont impossibles.', + 'DEFAULT_ADD_SIG' => 'Insérer ma signature par défaut', + 'DEFAULT_BBCODE' => 'Activer les BBCodes par défaut', + 'DEFAULT_NOTIFY' => 'Recevoir une notification par défaut lors de la réception d’un nouveau message', + 'DEFAULT_SMILIES' => 'Activer les émoticônes par défaut', + 'DEFINED_RULES' => 'Règles définies', + 'DELETED_TOPIC' => 'Le sujet a été supprimé.', + 'DELETE_ATTACHMENT' => 'Supprimer la pièce jointe', + 'DELETE_ATTACHMENTS' => 'Supprimer les pièces jointes', + 'DELETE_ATTACHMENT_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer cette pièce jointe ?', + 'DELETE_ATTACHMENTS_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ces pièces jointes ?', + 'DELETE_AVATAR' => 'Supprimer l’image', + 'DELETE_COOKIES_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer les cookies de ce forum ?', + 'DELETE_MARKED_PM' => 'Supprimer les messages sélectionnés', + 'DELETE_MARKED_PM_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer les messages sélectionnés ?', + 'DELETE_OLDEST_MESSAGES' => 'Supprimer les messages les plus anciens', + 'DELETE_MESSAGE' => 'Supprimer le message', + 'DELETE_MESSAGE_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ce message privé ?', + 'DELETE_MESSAGES_IN_FOLDER' => 'Supprimer tous les messages de la boîte que vous souhaitez supprimer', + 'DELETE_RULE' => 'Supprimer la règle', + 'DELETE_RULE_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer cette règle ?', + 'DEMOTE_SELECTED' => 'Rétrograder la sélection', + 'DISABLE_CENSORS' => 'Activer la censure de mots', + 'DISPLAY_GALLERY' => 'Afficher la galerie', + 'DOMAIN_NO_MX_RECORD_EMAIL' => 'Le domaine de cette adresse de courriel ne contient pas d’enregistrement MX valide.', + 'DOWNLOADS' => 'Téléchargements', + 'DRAFTS_DELETED' => 'Les brouillons ont été supprimés.', + 'DRAFTS_EXPLAIN' => 'Depuis cette page, vous pouvez consulter, modifier et supprimer les brouillons que vous avez préalablement enregistrés.', + 'DRAFT_UPDATED' => 'Le brouillon a été mis à jour.', + + 'EDIT_DRAFT_EXPLAIN' => 'Vous pouvez modifier ici votre brouillon. Les brouillons ne peuvent pas contenir de pièces jointes et de sondages.', + 'EMAIL_BANNED_EMAIL' => 'Cette adresse de courriel a été bannie de ce forum.', + 'EMAIL_REMIND' => 'Cette adresse doit correspondre à l’adresse de courriel associée à votre compte. Si vous ne l’avez jamais modifié depuis le panneau de contrôle de l’utilisateur, il s’agit de l’adresse de courriel que vous avez spécifiée lors de votre inscription.', + 'EMAIL_TAKEN_EMAIL' => 'Cette adresse de courriel est déjà utilisée.', + 'EMPTY_DRAFT' => 'Vous devez saisir un message avant d’envoyer vos modifications.', + 'EMPTY_DRAFT_TITLE' => 'Vous devez saisir le titre du brouillon.', + 'EXPORT_AS_XML' => 'Exporter en XML', + 'EXPORT_AS_CSV' => 'Exporter en CSV', + 'EXPORT_AS_CSV_EXCEL' => 'Exporter en CSV (Excel)', + 'EXPORT_AS_TXT' => 'Exporter en TXT', + 'EXPORT_AS_MSG' => 'Exporter en MSG', + 'EXPORT_FOLDER' => 'Exporter la boîte', + + 'FIELD_REQUIRED' => 'Le champ « %s » doit être renseigné.', + 'FIELD_TOO_SHORT' => [ + 1 => 'Le champ « %2$s » est trop court, vous devez saisir au moins %1$d caractère.', + 2 => 'Le champ « %2$s » est trop court, vous devez saisir au moins %1$d caractères.', + ], + 'FIELD_TOO_LONG' => [ + 1 => 'Le champ « %2$s » est trop long, vous ne pouvez saisir que %1$d caractère au maximum.', + 2 => 'Le champ « %2$s » est trop long, vous ne pouvez saisir que %1$d caractères au maximum.', + ], + 'FIELD_TOO_SMALL' => 'La valeur de « %2$s » est trop faible, la valeur minimale est de %1$d.', + 'FIELD_TOO_LARGE' => 'La valeur de « %2$s » est trop importante, la valeur maximale est de %1$d.', + 'FIELD_INVALID_CHARS_INVALID' => 'Le champ « %s » contient des caractères invalides.', + 'FIELD_INVALID_CHARS_NUMBERS_ONLY' => 'Le champ « %s » contient des caractères invalides. Seuls les chiffres sont autorisés.', + 'FIELD_INVALID_CHARS_ALPHA_DOTS' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (sans accent), les chiffres et les points sont autorisés.', + 'FIELD_INVALID_CHARS_ALPHA_ONLY' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (sans accent) et les chiffres sont autorisés.', + 'FIELD_INVALID_CHARS_ALPHA_PUNCTUATION' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (sans accent), les chiffres, les tirets bas, les tirets, les virgules et les points sont autorisés. De plus, le premier caractère doit être une lettre.', + 'FIELD_INVALID_CHARS_ALPHA_SPACERS' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (sans accent), les chiffres, les espaces, les tirets bas, les crochets et les signes plus et moins sont autorisés.', + 'FIELD_INVALID_CHARS_ALPHA_UNDERSCORE' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (sans accent), les chiffres et les tirets bas sont autorisés.', + 'FIELD_INVALID_CHARS_LETTER_NUM_DOTS' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (avec ou sans accent), les chiffres et les points sont autorisés.', + 'FIELD_INVALID_CHARS_LETTER_NUM_ONLY' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (avec ou sans accent) et les chiffres sont autorisés.', + 'FIELD_INVALID_CHARS_LETTER_NUM_PUNCTUATION' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (avec ou sans accent), les chiffres, les tirets bas, les tirets et les points sont autorisés. De plus, le premier caractère doit être une lettre.', + 'FIELD_INVALID_CHARS_LETTER_NUM_SPACERS' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (avec ou sans accent), les chiffres, les espaces, les tirets bas, les crochets et les signes plus et moins sont autorisés.', + 'FIELD_INVALID_CHARS_LETTER_NUM_UNDERSCORE' => 'Le champ « %s » contient des caractères invalides. Seuls les lettres (avec ou sans accent), les chiffres et les tirets bas sont autorisés.', + 'FIELD_INVALID_DATE' => 'Le champ « %s » contient une date invalide.', + 'FIELD_INVALID_URL' => 'Le champ « %s » contient un lien invalide.', + 'FIELD_INVALID_VALUE' => 'Le champ « %s » contient une valeur invalide.', + + 'FOE_MESSAGE' => 'Message d’un ignoré', + 'FOES_EXPLAIN' => 'Les ignorés sont les utilisateurs dont vous ignorerez par défaut le contenu dont ils sont les auteurs. Les messages que ces utilisateurs publient ne vous seront pas entièrement visibles. Les ignorés peuvent néanmoins vous envoyer des messages privés. Veuillez noter que vous ne pouvez pas ignorer les administrateurs et les modérateurs.', + 'FOES_UPDATED' => 'Votre liste d’ignorés a été mise à jour.', + 'FOLDER_ADDED' => 'La boîte a été ajoutée.', + 'FOLDER_MESSAGE_STATUS' => [ + 1 => '%2$d sur %1$s', + 2 => '%2$d sur %1$s', + ], + 'FOLDER_NAME_EMPTY' => 'Vous devez saisir le nom de cette boîte.', + 'FOLDER_NAME_EXIST' => 'La boîte « %s » existe déjà.', + 'FOLDER_OPTIONS' => 'Options des boîtes', + 'FOLDER_RENAMED' => 'La boîte a été renommée.', + 'FOLDER_REMOVED' => 'La boîte a été supprimée.', + 'FOLDER_STATUS_MSG' => [ + 1 => 'La boîte est utilisée à %3$d %% (%2$d sur %1$s)', + 2 => 'La boîte est utilisée à %3$d %% (%2$d sur %1$s)', + ], + 'FORWARD_PM' => 'Transférer le MP', + 'FORCE_PASSWORD_EXPLAIN' => 'Vous devez modifier votre mot de passe afin de poursuivre votre navigation sur ce forum.', + 'FRIEND_MESSAGE' => 'Message d’un ami', + 'FRIENDS' => 'Amis', + 'FRIENDS_EXPLAIN' => 'Les amis vous permettent d’accéder plus rapidement aux membres avec qui vous communiquez fréquemment. Si le style que vous utilisez est compatible avec cette fonctionnalité, les messages de vos amis apparaîtront en surbrillance.', + 'FRIENDS_OFFLINE' => 'Hors-ligne', + 'FRIENDS_ONLINE' => 'En ligne', + 'FRIENDS_UPDATED' => 'Votre liste d’amis a été mise à jour.', + 'FULL_FOLDER_OPTION_CHANGED' => 'L’opération à effectuer lorsqu’une boîte est pleine a été modifiée.', + 'FWD_ORIGINAL_MESSAGE' => '-------- Message original --------', + 'FWD_SUBJECT' => 'Sujet : %s', + 'FWD_DATE' => 'Date : %s', + 'FWD_FROM' => 'Par : %s', + 'FWD_TO' => 'À : %s', + + 'GLOBAL_ANNOUNCEMENT' => 'Annonce générale', + + 'GRAVATAR_AVATAR_EMAIL' => 'Adresse de courriel Gravatar', + 'GRAVATAR_AVATAR_EMAIL_EXPLAIN' => 'Saisissez l’adresse de courriel que vous utilisez sur votre compte Gravatar.', + 'GRAVATAR_AVATAR_SIZE' => 'Dimensions de l’avatar', + 'GRAVATAR_AVATAR_SIZE_EXPLAIN' => 'Saisissez la hauteur et la largeur de votre avatar. Laissez ces champs vides si vous souhaitez que cela soit détecté automatiquement.', + + 'HIDE_ONLINE' => 'Masquer ma présence', + 'HIDE_ONLINE_EXPLAIN' => 'La modification de cette option ne sera prise en compte que lors de votre prochaine visite sur le forum.', + 'HOLD_NEW_MESSAGES' => 'Refuser les nouveaux messages (ils seront mis en attente jusqu’à ce que vous libériez de l’espace)', + 'HOLD_NEW_MESSAGES_SHORT' => 'Mettre en attente les nouveaux messages', + + 'IF_FOLDER_FULL' => 'Si la boîte est pleine', + 'IMPORTANT_NEWS' => 'Annonces importantes', + 'INVALID_USER_BIRTHDAY' => 'La date d’anniversaire que vous avez spécifiée est invalide.', + 'INVALID_CHARS_USERNAME' => 'Le nom d’utilisateur que vous avez spécifié contient des caractères qui ne sont pas autorisés.', + 'INVALID_EMOJIS_USERNAME' => 'Le nom d’utilisateur que vous avez spécifié contient des caractères qui ne sont pas autorisés (émojis).', + 'INVALID_CHARS_NEW_PASSWORD' => 'Le mot de passe que vous avez spécifié ne contient pas les caractères requis.', + 'ITEMS_REQUIRED' => 'Les champs marqués par « * » sont obligatoires.', + + 'JOIN_SELECTED' => 'Rejoindre la sélection', + + 'LANGUAGE' => 'Langue', + 'LINK_REMOTE_AVATAR' => 'Lien externe', + 'LINK_REMOTE_AVATAR_EXPLAIN' => 'Saisissez le lien où se situe l’image de l’avatar que vous souhaitez insérer.', + 'LINK_REMOTE_SIZE' => 'Dimensions de l’avatar', + 'LINK_REMOTE_SIZE_EXPLAIN' => 'Saisissez la largeur et la hauteur de l’avatar. Laissez ces champs vides si vous souhaitez que cela soit détecté automatiquement.', + 'LOGIN_EXPLAIN_UCP' => 'Veuillez vous connecter afin d’accéder au panneau de contrôle de l’utilisateur.', + 'LOGIN_LINK' => 'Associer ou inscrire votre compte provenant d’un service externe à celui de ce forum', + 'LOGIN_LINK_EXPLAIN' => 'Vous avez essayé de vous connecter avec un service externe qui n’est associé à aucun compte de ce forum. Vous devez à présent associer ce compte à un compte du forum ou en créer un nouveau.', + 'LOGIN_LINK_MISSING_DATA' => 'Les données qui sont nécessaires afin d’associer votre compte à celui d’un service externe ne sont pas disponibles. Veuillez recommencer la procédure de connexion.', + 'LOGIN_LINK_NO_DATA_PROVIDED' => 'Aucune donnée n’a été transmise à cette page afin d’associer votre compte à celui d’un service externe. Si le problème persiste, veuillez contacter un administrateur du forum.', + 'LOGIN_KEY' => 'Clé de connexion', + 'LOGIN_TIME' => 'Date de connexion', + 'LOGIN_REDIRECT' => 'Vous êtes à présent connecté.', + 'LOGOUT_FAILED' => 'Vous n’avez pas été déconnecté car la requête n’a pas fonctionné sur votre session. Si le problème persiste, veuillez contacter un administrateur du forum.', + 'LOGOUT_REDIRECT' => 'Vous êtes à présent déconnecté.', + + 'MARK_IMPORTANT' => '(Dé)cocher comme important', + 'MARKED_MESSAGE' => 'Message coché', + 'MAX_FOLDER_REACHED' => 'Le nombre maximal de boîtes autorisé par utilisateur a été atteint.', + 'MESSAGE_BY_AUTHOR' => 'par', + 'MESSAGE_COLOURS' => 'Légende de couleurs des messages', + 'MESSAGE_DELETED' => 'Le message a été supprimé.', + 'MESSAGE_EDITED' => 'Le message a été modifié.', + 'MESSAGE_HISTORY' => 'Historique du message', + 'MESSAGE_REMOVED_FROM_OUTBOX' => 'Le message a été supprimé par son auteur.', + 'MESSAGE_REPORTED_MESSAGE' => 'Message rapporté', + 'MESSAGE_SENT_ON' => 'le', + 'MESSAGE_STORED' => 'Le message a été envoyé.', + 'MESSAGE_TO' => 'À', + 'MESSAGES_DELETED' => 'Les messages ont été supprimés', + 'MOVE_DELETED_MESSAGES_TO' => 'Déplacer les messages de la boîte que vous avez supprimée vers', + 'MOVE_DOWN' => 'Descendre', + 'MOVE_MARKED_TO_FOLDER' => 'Déplacer la sélection vers %s', + 'MOVE_PM_ERROR' => [ + 1 => 'Une erreur est survenue lors du déplacement des messages vers la nouvelle boîte. Seul %2$d message sur %1$s a été déplacé.', + 2 => 'Une erreur est survenue lors du déplacement des messages vers la nouvelle boîte. Seuls %2$d messages sur %1$s ont été déplacés.', + ], + 'MOVE_TO_FOLDER' => 'Déplacer vers la boîte', + 'MOVE_UP' => 'Monter', + + 'NEW_FOLDER_NAME' => 'Nom de la nouvelle boîte', + 'NEW_PASSWORD' => 'Nouveau mot de passe', + 'NEW_PASSWORD_CONFIRM_EMPTY' => 'Vous devez spécifier le mot de passe de confirmation.', + 'NEW_PASSWORD_ERROR' => 'Les mots de passe que vous avez spécifiés ne concordent pas.', + + 'NOTIFICATIONS_MARK_ALL_READ' => 'Marquer les notifications comme lues', + 'NOTIFICATIONS_MARK_ALL_READ_CONFIRM' => 'Êtes-vous sûr de vouloir marquer les notifications comme lues ?', + 'NOTIFICATIONS_MARK_ALL_READ_SUCCESS' => 'Les notifications ont été marquées comme lues.', + 'NOTIFICATION_GROUP_MISCELLANEOUS' => 'Notifications diverses', + 'NOTIFICATION_GROUP_MODERATION' => 'Notifications de modération', + 'NOTIFICATION_GROUP_ADMINISTRATION' => 'Notifications d’administration', + 'NOTIFICATION_GROUP_POSTING' => 'Notifications de publication', + 'NOTIFICATION_METHOD_BOARD' => 'Notifications', + 'NOTIFICATION_METHOD_EMAIL' => 'Courriel', + 'NOTIFICATION_METHOD_JABBER' => 'Jabber', + 'NOTIFICATION_TYPE' => 'Type de notification', + 'NOTIFICATION_TYPE_BOOKMARK' => 'Quelqu’un a répondu à un sujet que vous avez ajouté aux favoris', + 'NOTIFICATION_TYPE_GROUP_REQUEST' => 'Quelqu’un a demandé à rejoindre un groupe dont vous êtes responsable', + 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE' => 'Un sujet ou un message est en attente d’approbation', + 'NOTIFICATION_TYPE_MODERATION_QUEUE' => 'Un de vos sujets ou de vos messages ont été traités par un modérateur', + 'NOTIFICATION_TYPE_PM' => 'Quelqu’un vous a envoyé un message privé', + 'NOTIFICATION_TYPE_POST' => 'Quelqu’un a répondu à un sujet auquel vous êtes abonné', + 'NOTIFICATION_TYPE_QUOTE' => 'Quelqu’un vous a cité dans un message', + 'NOTIFICATION_TYPE_REPORT' => 'Quelqu’un a rapporté un message', + 'NOTIFICATION_TYPE_TOPIC' => 'Quelqu’un a créé un sujet dans un forum auquel vous êtes abonné', + 'NOTIFICATION_TYPE_ADMIN_ACTIVATE_USER' => 'Un utilisateur est en attente d’activation', + + 'NOTIFY_METHOD' => 'Méthode de notification', + 'NOTIFY_METHOD_BOTH' => 'Tous les deux', + 'NOTIFY_METHOD_EMAIL' => 'Courriel uniquement', + 'NOTIFY_METHOD_EXPLAIN' => 'La méthode d’envoi des messages qui sont envoyés depuis ce forum.', + 'NOTIFY_METHOD_IM' => 'Jabber uniquement', + 'NOTIFY_ON_PM' => 'M’envoyer une notification lors de la réception d’un nouveau message privé', + 'NOT_ADDED_FRIENDS_ANONYMOUS' => 'Vous ne pouvez pas ajouter en ami des utilisateurs anonymes.', + 'NOT_ADDED_FRIENDS_BOTS' => 'Vous ne pouvez pas ajouter en ami des robots.', + 'NOT_ADDED_FRIENDS_FOES' => 'Vous ne pouvez pas ajouter en ami des utilisateurs ignorés.', + 'NOT_ADDED_FRIENDS_SELF' => 'Vous ne pouvez pas vous ajouter en ami.', + 'NOT_ADDED_FOES_MOD_ADMIN' => 'Vous ne pouvez pas ignorer des administrateurs et des modérateurs.', + 'NOT_ADDED_FOES_ANONYMOUS' => 'Vous ne pouvez pas ignorer des utilisateurs anonymes.', + 'NOT_ADDED_FOES_BOTS' => 'Vous ne pouvez pas ignorer des robots.', + 'NOT_ADDED_FOES_FRIENDS' => 'Vous ne pouvez pas ignorer des utilisateurs amis.', + 'NOT_ADDED_FOES_SELF' => 'Vous ne pouvez pas vous ignorer.', + 'NOT_AGREE' => 'Je refuse ces conditions', + 'NOT_ENOUGH_SPACE_FOLDER' => 'La boîte de destination « %s » semble être pleine. L’opération n’a pas été prise en compte.', + 'NOT_MOVED_MESSAGES' => [ + 1 => 'Vous avez actuellement %d message privé en attente car la boîte de réception est pleine.', + 2 => 'Vous avez actuellement %d messages privés en attente car la boîte de réception est pleine.', + ], + 'NO_ACTION_MODE' => 'Aucune opération n’a été sélectionnée.', + 'NO_AUTHOR' => 'Aucun auteur n’a été spécifié', + 'NO_AVATAR' => 'Aucun avatar n’a été sélectionné', + 'NO_AVATAR_CATEGORY' => 'Aucune', + + 'NO_AUTH_DELETE_MESSAGE' => 'Vous ne pouvez pas supprimer les messages privés.', + 'NO_AUTH_EDIT_MESSAGE' => 'Vous ne pouvez pas modifier les messages privés.', + 'NO_AUTH_FORWARD_MESSAGE' => 'Vous ne pouvez pas transférer les messages privés.', + 'NO_AUTH_GROUP_MESSAGE' => 'Vous ne pouvez pas envoyer de messages privés aux groupes.', + 'NO_AUTH_PROFILEINFO' => 'Vous ne pouvez pas modifier les informations de votre profil.', + 'NO_AUTH_READ_HOLD_MESSAGE' => 'Vous ne pouvez pas consulter les messages privés qui sont en liste d’attente.', + 'NO_AUTH_READ_MESSAGE' => 'Vous ne pouvez pas consulter les messages privés.', + 'NO_AUTH_PRINT_MESSAGE' => 'Vous ne pouvez pas imprimer les messages privés.', + 'NO_AUTH_READ_REMOVED_MESSAGE' => 'Vous ne pouvez pas consulter ce message car il a été supprimé par son auteur.', + 'NO_AUTH_SEND_MESSAGE' => 'Vous ne pouvez pas envoyer de messages privés.', + 'NO_AUTH_SIGNATURE' => 'Vous ne pouvez pas définir une signature.', + + 'NO_BCC_RECIPIENT' => 'Aucun', + 'NO_BOOKMARKS' => 'Aucun favori n’a été ajouté.', + 'NO_BOOKMARKS_SELECTED' => 'Aucun favori n’a été sélectionné.', + 'NO_EDIT_READ_MESSAGE' => 'Le message privé ne peut plus être modifié car il a été consulté.', + 'NO_EMAIL_USER' => 'L’adresse de courriel ou le nom d’utilisateur souhaité est introuvable.', + 'EMAIL_NOT_UNIQUE' => 'L’adresse de courriel que vous avez spécifiée est utilisée par plusieurs utilisateurs. De même, vous devez également spécifier un nom d’utilisateur.', + 'NO_FOES' => 'Aucun ignoré', + 'NO_FRIENDS' => 'Aucun ami', + 'NO_FRIENDS_OFFLINE' => 'Aucun ami n’est hors-ligne', + 'NO_FRIENDS_ONLINE' => 'Aucun ami n’est en ligne', + 'NO_GROUP_SELECTED' => 'Aucun groupe n’a été spécifié.', + 'NO_IMPORTANT_NEWS' => 'Aucune annonce importante n’a été publiée.', + 'NO_MESSAGE' => 'Le message privé est introuvable.', + 'NO_NEW_FOLDER_NAME' => 'Vous devez saisir un nouveau nom de boîte.', + 'NO_NEWER_PM' => 'Aucun nouveau message.', + 'NO_OLDER_PM' => 'Aucun ancien message.', + 'NO_PASSWORD_SUPPLIED' => 'Vous ne pouvez pas vous connecter sans saisir de mot de passe.', + 'NO_RECIPIENT' => 'Aucun destinataire n’a été spécifié.', + 'NO_RULES_DEFINED' => 'Aucune règle n’a été spécifiée.', + 'NO_SAVED_DRAFTS' => 'Aucun brouillon n’a été enregistré.', + 'NO_TO_RECIPIENT' => 'Aucun', + 'NO_WATCHED_FORUMS' => 'Aucun abonnement à un forum.', + 'NO_WATCHED_SELECTED' => 'Aucun abonnement à un forum ou un sujet n’a été sélectionné.', + 'NO_WATCHED_TOPICS' => 'Aucun abonnement à un sujet.', + + 'PASS_TYPE_ALPHA_EXPLAIN' => 'Le mot de passe doit être compris entre %1$s et %2$s de long, doit contenir des lettres (sans accent) en majuscules et en minuscules et doit contenir des chiffres.', + 'PASS_TYPE_ANY_EXPLAIN' => 'Doit être compris entre %1$s et %2$s.', + 'PASS_TYPE_CASE_EXPLAIN' => 'Le mot de passe doit être compris entre %1$s et %2$s de long et doit contenir des lettres en majuscules et en minuscules.', + 'PASS_TYPE_SYMBOL_EXPLAIN' => 'Le mot de passe doit être compris entre %1$s et %2$s de long, doit contenir des lettres en majuscules et en minuscules, doit contenir des chiffres et doit contenir des symboles.', + 'PASSWORD' => 'Mot de passe', + 'PASSWORD_ACTIVATED' => 'Votre nouveau mot de passe a été activé.', + 'PASSWORD_UPDATED_IF_EXISTED' => 'Si votre compte existe, un nouveau mot de passe a été envoyé sur l’adresse de courriel associée. Si vous ne recevez pas de courriel, il se peut que vous ayez été banni, que votre compte ne soit pas activé ou que vous n’avez pas l’autorisation de modifier votre mot de passe. Pour plus d’informations, veuillez contacter un administrateur. De même, n’oubliez pas de vérifier que le courriel ne soit pas considéré comme courriel indésirable par votre service de messagerie électronique.', + 'PERMISSIONS_RESTORED' => 'Les permissions par défaut ont été restaurées.', + 'PERMISSIONS_TRANSFERRED' => 'Les permissions de « %s » ont été transférées. Vous pouvez à présent parcourir le forum avec les permissions de cet utilisateur.
Veuillez noter que les permissions d’administration ne sont pas transférées. Vous pouvez restaurer vos permissions à tout moment.', + 'PM_DISABLED' => 'La messagerie privée a été désactivée sur ce forum.', + 'PM_FROM' => 'Par', + 'PM_FROM_REMOVED_AUTHOR' => 'Le message privé a été envoyé par un utilisateur qui n’est désormais plus inscrit.', + 'PM_ICON' => 'Icône de MP', + 'PM_INBOX' => 'Boîte de réception', + 'PM_MARK_ALL_READ' => 'Marquer les messages privés comme lus', + 'PM_MARK_ALL_READ_SUCCESS' => 'Les messages privés de ce dossier ont été marqués comme lus', + 'PM_NO_USERS' => 'Les utilisateurs que vous souhaitez ajouter sont introuvables.', + 'PM_OUTBOX' => 'Boîte d’envoi', + 'PM_SENTBOX' => 'Messages envoyés', + 'PM_SUBJECT' => 'Sujet du message', + 'PM_TO' => 'Envoyer à', + 'PM_TOOLS' => 'Outils du message', + 'PM_USERS_REMOVED_NO_PERMISSION' => 'Certains utilisateurs ne peuvent pas être ajoutés car ils ne sont pas autorisés à consulter les messages privés.', + 'PM_USERS_REMOVED_NO_PM' => 'Certains utilisateurs ne peuvent pas être ajoutés car ils ont désactivés la réception de messages privés.', + 'POST_EDIT_PM' => 'Modifier le message', + 'POST_FORWARD_PM' => 'Transférer le message', + 'POST_NEW_PM' => 'Rédiger un message', + 'POST_PM_LOCKED' => 'La messagerie privée est verrouillée.', + 'POST_PM_POST' => 'Citer le message', + 'POST_QUOTE_PM' => 'Citer le message', + 'POST_REPLY_PM' => 'Répondre au message', + 'PRINT_PM' => 'Aperçu avant impression', + 'PREFERENCES_UPDATED' => 'Vos préférences ont été mises à jour.', + 'PROFILE_INFO_NOTICE' => 'Veuillez noter que ces informations peuvent être visibles aux autres membres. Soyez prudent lors de la saisie d’informations personnelles. Tous les champs marqués par « * » sont obligatoires.', + 'PROFILE_UPDATED' => 'Votre profil a été mis à jour.', + 'PROFILE_AUTOLOGIN_KEYS' => 'Les clés de connexions automatiques vous permettent de vous connecter automatiquement lors de vos différentes visites sur le forum. Si vous vous déconnectez, la clé de connexion automatique ne sera supprimée que de l’ordinateur que vous utilisiez au moment de votre déconnexion. Depuis cette page, vous pouvez consulter les clés de connexions automatiques générées sur les ordinateurs qui ont été utilisés afin d’accéder à ce forum.', + 'PROFILE_NO_AUTOLOGIN_KEYS' => 'Aucune clé de connexion automatique n’a été sauvegardée.', + + 'RECIPIENT' => 'Destinataire', + 'RECIPIENTS' => 'Destinataires', + 'REGISTRATION' => 'Inscription', + 'RELEASE_MESSAGES' => '%sRendre disponible tous les messages en liste d’attente%s… Ils seront triés de nouveau dans les boîtes appropriées si assez d’espace a été libéré.', + 'REMOVE_ADDRESS' => 'Supprimer l’adresse', + 'REMOVE_SELECTED_BOOKMARKS' => 'Supprimer les sujets sélectionnés des favoris', + 'REMOVE_SELECTED_BOOKMARKS_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer ces sujets de vos favoris ?', + 'REMOVE_BOOKMARK_MARKED' => 'Supprimer les sujets sélectionnés des favoris', + 'REMOVE_FOLDER' => 'Supprimer la boîte', + 'REMOVE_FOLDER_CONFIRM' => 'Êtes-vous sûr de vouloir supprimer cette boîte ?', + 'RENAME' => 'Renommer', + 'RENAME_FOLDER' => 'Renommer la boîte', + 'REPLIED_MESSAGE' => 'Réponse au message', + 'REPLY_TO_ALL' => 'Répondre à l’expéditeur et à tous les destinataires.', + 'REPORT_PM' => 'Rapporter le message privé', + 'RESIGN_SELECTED' => 'Décocher la sélection', + 'RETURN_FOLDER' => '%1$sRevenir sur la boîte précédente%2$s', + 'RETURN_UCP' => '%sRevenir sur le panneau de contrôle de l’utilisateur%s', + 'RULE_ADDED' => 'La règle a été ajoutée.', + 'RULE_ALREADY_DEFINED' => 'Cette règle a déjà été spécifiée.', + 'RULE_DELETED' => 'La règle a été supprimée.', + 'RULE_LIMIT_REACHED' => 'Vous ne pouvez pas ajouter plus de règles de MP car vous avez atteint la limite.', + 'RULE_NOT_DEFINED' => 'La règle n’est pas correctement spécifiée.', + 'RULE_REMOVED_MESSAGES' => [ + 1 => '%d message privé a été supprimé par les filtres de la messagerie privée.', + 2 => '%d messages privés ont été supprimés par les filtres de la messagerie privée.', + ], + + 'SAME_PASSWORD_ERROR' => 'Le mot de passe que vous avez spécifié est identique à votre mot de passe actuel.', + 'SEARCH_YOUR_POSTS' => 'Afficher vos messages', + 'SEND_PASSWORD' => 'Envoyer le mot de passe', + 'SENT_AT' => 'Date d’envoi', // Used before dates in private messages + 'SHOW_EMAIL' => 'Autoriser les utilisateurs à me contacter par courriel', + 'SIGNATURE_EXPLAIN' => 'La signature est un bloc de texte qui sera inséré au pied de tous vos messages. Elle est limitée à %d caractères.', + 'SIGNATURE_PREVIEW' => 'Votre signature apparaîtra comme ceci dans tous vos messages', + 'SIGNATURE_TOO_LONG' => 'Votre signature est trop longue.', + 'SELECT_CURRENT_TIME' => 'Régler l’heure actuelle', + 'SELECT_TIMEZONE' => 'Régler le fuseau horaire', + 'SORT' => 'Trier par', + 'SORT_COMMENT' => 'Description du fichier', + 'SORT_DOWNLOADS' => 'Téléchargements', + 'SORT_EXTENSION' => 'Extension', + 'SORT_FILENAME' => 'Nom du fichier', + 'SORT_POST_TIME' => 'Date de publication', + 'SORT_SIZE' => 'Taille du fichier', + + 'TIMEZONE' => 'Fuseau horaire', + 'TIMEZONE_DATE_SUGGESTION' => 'Suggestion : %s', + 'TIMEZONE_INVALID' => 'Le fuseau horaire que vous avez spécifié est invalide.', + 'TO' => 'Destinataire', + 'TO_MASS' => 'Destinataires', + 'TO_ADD' => 'Ajouter un destinataire', + 'TO_ADD_MASS' => 'Ajouter des destinataires', + 'TO_ADD_GROUPS' => 'Ajouter des groupes d’utilisateurs', + 'TOO_MANY_RECIPIENTS' => 'Vous ne pouvez pas envoyer de message privé à autant de destinataires.', + 'TOO_MANY_REGISTERS' => 'Vous avez dépassé la limite de tentatives d’inscriptions lors de cette session. Veuillez réessayer ultérieurement.', + + 'UCP' => 'Panneau de contrôle de l’utilisateur', + 'UCP_ACTIVATE' => 'Activer le compte', + 'UCP_ADMIN_ACTIVATE' => 'Veuillez noter que vous devez saisir une adresse de courriel valide afin que votre compte soit activé. Un administrateur vérifiera votre compte. S’il est approuvé, vous recevrez un courriel à l’adresse de courriel que vous avez spécifiée.', + 'UCP_ATTACHMENTS' => 'Pièces jointes', + 'UCP_AUTH_LINK' => 'Comptes externes', + 'UCP_AUTH_LINK_ASK' => 'Aucun compte n’est associé à ce service externe. Veuillez cliquer sur le bouton ci-dessous afin d’associer votre compte de ce forum à un compte de ce service externe.', + 'UCP_AUTH_LINK_ID' => 'Identifiant unique', + 'UCP_AUTH_LINK_LINK' => 'Associer', + 'UCP_AUTH_LINK_MANAGE' => 'Gérer les associations aux comptes externes', + 'UCP_AUTH_LINK_NOT_SUPPORTED' => 'L’association de comptes du forum à des services externes n’est pas prise en charge par la méthode d’authentification de ce forum.', + 'UCP_AUTH_LINK_TITLE' => 'Gérer vos associations aux comptes externes', + 'UCP_AUTH_LINK_UNLINK' => 'Dissocier', + 'UCP_COPPA_BEFORE' => 'Avant le %s', + 'UCP_COPPA_ON_AFTER' => 'Le ou après le %s', + 'UCP_EMAIL_ACTIVATE' => 'Veuillez noter que vous devez saisir une adresse de courriel valide afin que votre compte soit activé. Vous recevrez un courriel qui contiendra le lien d’activation à l’adresse de courriel que vous avez spécifiée.', + 'UCP_JABBER' => 'Adresse Jabber', + 'UCP_LOGIN_LINK' => 'Associer un compte externe', + + 'UCP_MAIN' => 'Vue d’ensemble', + 'UCP_MAIN_ATTACHMENTS' => 'Gérer les pièces jointes', + 'UCP_MAIN_BOOKMARKS' => 'Gérer les favoris', + 'UCP_MAIN_DRAFTS' => 'Gérer les brouillons', + 'UCP_MAIN_FRONT' => 'Page principale', + 'UCP_MAIN_SUBSCRIBED' => 'Gérer les abonnements', + + 'UCP_NO_ATTACHMENTS' => 'Aucune pièce jointe n’a été insérée.', + + 'UCP_NOTIFICATION_LIST' => 'Gérer les notifications', + 'UCP_NOTIFICATION_LIST_EXPLAIN' => 'Depuis cette page, vous pouvez consulter l’historique de vos notifications.', + 'UCP_NOTIFICATION_OPTIONS' => 'Modifier les méthodes de notification', + 'UCP_NOTIFICATION_OPTIONS_EXPLAIN' => 'Depuis cette page, vous pouvez définir les méthodes de notification du forum.', + + 'UCP_PREFS' => 'Préférences du forum', + 'UCP_PREFS_PERSONAL' => 'Modifier les paramètres généraux', + 'UCP_PREFS_POST' => 'Modifier les préférences de publication par défaut', + 'UCP_PREFS_VIEW' => 'Modifier les options d’affichage', + + 'UCP_PM' => 'Messages privés', + 'UCP_PM_COMPOSE' => 'Rédiger un message', + 'UCP_PM_DRAFTS' => 'Gérer les brouillons de MP', + 'UCP_PM_OPTIONS' => 'Boîtes, règles et paramètres', + 'UCP_PM_UNREAD' => 'Messages non lus', + 'UCP_PM_VIEW' => 'Consulter les messages', + + 'UCP_PROFILE' => 'Profil', + 'UCP_PROFILE_AVATAR' => 'Modifier l’avatar', + 'UCP_PROFILE_PROFILE_INFO' => 'Modifier le profil', + 'UCP_PROFILE_REG_DETAILS' => 'Modifier les paramètres du compte', + 'UCP_PROFILE_SIGNATURE' => 'Modifier la signature', + 'UCP_PROFILE_AUTOLOGIN_KEYS' => 'Gérer les clés de connexions automatiques', + + 'UCP_USERGROUPS' => 'Groupes d’utilisateurs', + 'UCP_USERGROUPS_MEMBER' => 'Modifier les adhésions', + 'UCP_USERGROUPS_MANAGE' => 'Gérer les groupes', + + 'UCP_PASSWORD_RESET_DISABLED' => 'La réinitialisation des mots de passe a été désactivée. Si vous souhaitez obtenir de l’aide afin d’accéder à votre compte, veuillez contacter un %sadministrateur du forum%s', + 'UCP_REGISTER_DISABLE' => 'L’inscription de nouveaux comptes est désactivée.', + 'UCP_REMIND' => 'Envoyer le mot de passe', + 'UCP_RESEND' => 'Envoyer le courriel d’activation', + 'UCP_WELCOME' => 'Bienvenue sur le panneau de contrôle de l’utilisateur. Dans ce dernier, vous pouvez consulter et mettre à jour votre profil, vos préférences et vos abonnements aux forums et aux sujets. Si les administrateurs du forum ont activé la messagerie privée, vous pouvez également envoyer des messages aux autres utilisateurs. Veuillez vous assurer d’avoir consulté toutes les annonces avant de continuer.', + 'UCP_ZEBRA' => 'Amis et ignorés', + 'UCP_ZEBRA_FOES' => 'Gérer les ignorés', + 'UCP_ZEBRA_FRIENDS' => 'Gérer les amis', + 'UNDISCLOSED_RECIPIENT' => 'Destinataire confidentiel', + 'UNKNOWN_FOLDER' => 'Boîte inconnue', + 'UNWATCH_MARKED' => 'Ne plus surveiller la sélection', + 'UPLOAD_AVATAR_FILE' => 'Transférer depuis votre ordinateur', + 'UPLOAD_AVATAR_URL' => 'Transférer depuis un lien', + 'UPLOAD_AVATAR_URL_EXPLAIN' => 'Saisissez le lien vers l’image. L’image sera ensuite copiée sur ce forum.', + 'USERNAME_ALPHA_ONLY_EXPLAIN' => 'Le nom d’utilisateur doit être compris entre %1$s et %2$s de long et ne doit contenir que des lettres (sans accent) et des chiffres.', + 'USERNAME_ALPHA_SPACERS_EXPLAIN' => 'Le nom d’utilisateur doit être compris entre %1$s et %2$s de long et ne doit contenir que des lettres (sans accent), des chiffres, des espaces, des tirets bas, des crochets et des signes plus et moins.', + 'USERNAME_ASCII_EXPLAIN' => 'Le nom d’utilisateur doit être compris entre %1$s et %2$s de long et ne doit contenir aucun symbole spécial, seulement des caractères ASCII.', + 'USERNAME_LETTER_NUM_EXPLAIN' => 'Le nom d’utilisateur doit être compris entre %1$s et %2$s de long et ne doit contenir que des lettres et des chiffres.', + 'USERNAME_LETTER_NUM_SPACERS_EXPLAIN' => 'Le nom d’utilisateur doit être compris entre %1$s et %2$s de long et ne doit contenir que des lettres, des chiffres, des espaces, des tirets bas, des crochets et des signes plus et moins.', + 'USERNAME_CHARS_ANY_EXPLAIN' => 'Doit être compris entre %1$s et %2$s.', + 'USERNAME_TAKEN_USERNAME' => 'Le nom d’utilisateur que vous avez spécifié est déjà utilisé. Veuillez en sélectionner un autre.', + 'USERNAME_DISALLOWED_USERNAME' => 'Le nom d’utilisateur que vous avez spécifié a été interdit ou contient un mot interdit. Veuillez en sélectionner un autre.', + 'USER_NOT_FOUND_OR_INACTIVE' => 'Les noms d’utilisateurs que vous avez spécifiés sont introuvables ou ces utilisateurs ne sont pas encore activés.', + + 'VIEW_AVATARS' => 'Afficher les avatars', + 'VIEW_EDIT' => 'Consulter et modifier', + 'VIEW_FLASH' => 'Afficher les animations Flash', + 'VIEW_IMAGES' => 'Afficher les images dans les messages', + 'VIEW_NEXT_HISTORY' => 'MP suivant dans l’ordre chronologique', + 'VIEW_NEXT_PM' => 'MP suivant', + 'VIEW_PM' => 'Consulter le message', + 'VIEW_PM_INFO' => 'Informations sur le message', + 'VIEW_PM_MESSAGES' => [ + 1 => '%d message', + 2 => '%d messages', + ], + 'VIEW_PREVIOUS_HISTORY' => 'MP précédent dans l’ordre chronologique', + 'VIEW_PREVIOUS_PM' => 'MP précédent', + 'VIEW_PROFILE' => 'Consulter le profil', + 'VIEW_SIGS' => 'Afficher les signatures', + 'VIEW_SMILIES' => 'Afficher les émoticônes comme des images', + 'VIEW_TOPICS_DAYS' => 'Afficher les sujets triés selon leur ancienneté', + 'VIEW_TOPICS_DIR' => 'Afficher les sujets triés par ordre', + 'VIEW_TOPICS_KEY' => 'Afficher les sujets triés par', + 'VIEW_POSTS_DAYS' => 'Afficher les messages triés selon leur ancienneté', + 'VIEW_POSTS_DIR' => 'Afficher les messages triés par ordre', + 'VIEW_POSTS_KEY' => 'Afficher les messages triés par', + + 'WATCHED_EXPLAIN' => 'La liste ci-dessous vous affiche les forums et les sujets auxquels vous êtes abonné. Vous recevrez une notification à chaque fois qu’un nouveau message est publié dans un de ces derniers. Si vous souhaitez vous désabonner d’un forum ou d’un sujet, sélectionnez-le puis cliquez sur le bouton « Ne plus surveiller la sélection ».', + 'WATCHED_FORUMS' => 'Forums auxquels vous êtes abonné', + 'WATCHED_TOPICS' => 'Sujets auxquels vous êtes abonné', + 'WRONG_ACTIVATION' => 'La clé d’activation que vous avez spécifiée est inconnue de notre base de données.', + + 'YOUR_DETAILS' => 'Votre activité', + 'YOUR_FOES' => 'Vos ignorés', + 'YOUR_FOES_EXPLAIN' => 'Pour supprimer des noms d’utilisateurs, sélectionnez-les et envoyez les données.', + 'YOUR_FRIENDS' => 'Vos amis', + 'YOUR_FRIENDS_EXPLAIN' => 'Pour supprimer des noms d’utilisateurs, sélectionnez-les et envoyez les données.', + 'YOUR_WARNINGS' => 'Votre niveau d’avertissement', + + 'PM_ACTION' => [ + 'PLACE_INTO_FOLDER' => 'Placer dans la boîte', + 'MARK_AS_READ' => 'Marquer comme lu', + 'MARK_AS_IMPORTANT' => 'Marquer le message', + 'DELETE_MESSAGE' => 'Supprimer le message', + ], + 'PM_CHECK' => [ + 'SUBJECT' => 'Sujet', + 'SENDER' => 'Expéditeur', + 'MESSAGE' => 'Message', + 'STATUS' => 'Statut du message', + 'TO' => 'Envoyé à', + ], + 'PM_RULE' => [ + 'IS_LIKE' => 'est comme', + 'IS_NOT_LIKE' => 'n’est pas comme', + 'IS' => 'est', + 'IS_NOT' => 'n’est pas', + 'BEGINS_WITH' => 'commence par', + 'ENDS_WITH' => 'termine par', + 'IS_FRIEND' => 'est un ami', + 'IS_FOE' => 'est un ignoré', + 'IS_USER' => 'est un utilisateur', + 'IS_GROUP' => 'est dans un groupe d’utilisateurs', + 'ANSWERED' => 'répondu', + 'FORWARDED' => 'transféré', + 'TO_GROUP' => 'à mon groupe d’utilisateurs par défaut', + 'TO_ME' => 'à moi', + ], + + 'GROUPS_EXPLAIN' => 'Les groupes d’utilisateurs permettent aux administrateurs de gérer plus facilement les utilisateurs. Dès votre inscription, vous êtes automatiquement membre d’un groupe d’utilisateurs par défaut. Ce groupe spécifie votre apparence auprès des autres utilisateurs, grâce à, par exemple, une colorisation de votre nom d’utilisateur, un avatar, un rang, etc. Selon la politique des administrateurs du forum, vous pouvez être autorisé à modifier votre groupe d’utilisateurs par défaut, à être membre de plusieurs groupes d’utilisateurs et à rejoindre de nouveaux groupes d’utilisateurs. Certains groupes d’utilisateurs peuvent vous procurer des permissions supplémentaires qui vous permettront de consulter du contenu exclusif ou de profiter de nouvelles fonctionnalités dans d’autres domaines.', + 'GROUP_LEADER' => 'Responsable', + 'GROUP_MEMBER' => 'Membre', + 'GROUP_PENDING' => 'Membre en attente', + 'GROUP_NONMEMBER' => 'Non-membre', + 'GROUP_DETAILS' => 'Informations sur le groupe', + + 'NO_LEADER' => 'Aucun responsable du groupe', + 'NO_MEMBER' => 'Aucun membre du groupe', + 'NO_PENDING' => 'Aucun membre en attente', + 'NO_NONMEMBER' => 'Aucun non-membre du groupe', +]); diff --git a/language/fr/viewforum.php b/language/fr/viewforum.php new file mode 100644 index 0000000..61faf39 --- /dev/null +++ b/language/fr/viewforum.php @@ -0,0 +1,72 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'ACTIVE_TOPICS' => 'Sujets actifs', + 'ANNOUNCEMENTS' => 'Annonces', + + 'FORUM_PERMISSIONS' => 'Permissions du forum', + + 'ICON_ANNOUNCEMENT' => 'Annonce', + 'ICON_STICKY' => 'Note', + + 'LOGIN_NOTIFY_FORUM' => 'Vous avez reçu une notification concernant un message de ce forum. Veuillez vous connecter afin de le consulter.', + + 'MARK_TOPICS_READ' => 'Marquer les sujets comme lus', + + 'NEW_POSTS_HOT' => 'Nouveaux messages [ Populaires ]', // Not used anymore + 'NEW_POSTS_LOCKED' => 'Nouveaux messages [ Verrouillés ]', // Not used anymore + 'NO_NEW_POSTS_HOT' => 'Aucun nouveau message [ Populaire ]', // Not used anymore + 'NO_NEW_POSTS_LOCKED' => 'Aucun nouveau message [ Verrouillé ]', // Not used anymore + 'NO_READ_ACCESS' => 'Vous ne pouvez pas voir ou consulter les sujets de ce forum.', + 'NO_FORUMS_IN_CATEGORY' => 'Cette catégorie ne contient aucun forum.', + 'NO_UNREAD_POSTS_HOT' => 'Aucun message non lu [ Populaire ]', + 'NO_UNREAD_POSTS_LOCKED' => 'Aucun message non lu [ Verrouillé ]', + + 'POST_FORUM_LOCKED' => 'Le forum est verrouillé', + + 'TOPICS_MARKED' => 'Les sujets de ce forum ont été marqués comme lus.', + + 'UNREAD_POSTS_HOT' => 'Messages non lus [ Populaires ]', + 'UNREAD_POSTS_LOCKED' => 'Messages non lus [ Verrouillés ]', + + 'VIEW_FORUM' => 'Consulter le forum', + 'VIEW_FORUM_TOPICS' => [ + 1 => '%d sujet', + 2 => '%d sujets', + ], +]); diff --git a/language/fr/viewtopic.php b/language/fr/viewtopic.php new file mode 100644 index 0000000..a705255 --- /dev/null +++ b/language/fr/viewtopic.php @@ -0,0 +1,125 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For more information about the language pack, please visit + * https://www.phpbb.com/customise/db/translation/french/ + */ + +/** + * DO NOT CHANGE + */ +if (!defined('IN_PHPBB')) +{ + exit; +} + +if (empty($lang) || !is_array($lang)) +{ + $lang = []; +} + +// DEVELOPERS PLEASE NOTE +// +// All language files should use UTF-8 as their encoding and the files must not contain a BOM. +// +// Placeholders can now contain order information, e.g. instead of +// 'Page %s of %s' you can (and should) write 'Page %1$s of %2$s', this allows +// translators to re-order the output of data while ensuring it remains correct +// +// You do not need this where single placeholders are used, e.g. 'Message %d' is fine +// equally where a string contains only two placeholders which are used to wrap text +// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine + +$lang = array_merge($lang, [ + 'APPROVE' => 'Approuver', + 'ATTACHMENT' => 'Pièce jointe', + 'ATTACHMENT_FUNCTIONALITY_DISABLED' => 'Les pièces jointes ont été désactivées.', + + 'BOOKMARK_ADDED' => 'Le sujet a été ajouté aux favoris.', + 'BOOKMARK_ERR' => 'Le sujet n’a pas pu être ajouté aux favoris. Veuillez réessayer ultérieurement.', + 'BOOKMARK_REMOVED' => 'Le sujet a été supprimé de vos favoris.', + 'BOOKMARK_TOPIC' => 'Ajouter ce sujet aux favoris', + 'BOOKMARK_TOPIC_REMOVE' => 'Supprimer ce sujet de vos favoris', + 'BUMPED_BY' => 'Dernière remontée par %1$s le %2$s.', + 'BUMP_TOPIC' => 'Remonter le sujet', + + 'CODE' => 'Code', + + 'DELETE_TOPIC' => 'Supprimer le sujet', + 'DELETED_INFORMATION' => 'Supprimé par %1$s le %2$s', + 'DISAPPROVE' => 'Désapprouver', + 'DOWNLOAD_NOTICE' => 'Vous ne pouvez pas consulter les pièces jointes insérées à ce message.', + + 'EDITED_TIMES_TOTAL' => [ + 1 => 'Dernière modification par %2$s le %3$s, modifié %1$d fois.', + 2 => 'Dernière modification par %2$s le %3$s, modifié %1$d fois.', + ], + 'EMAIL_TOPIC' => 'Envoyer le sujet par courriel', + 'ERROR_NO_ATTACHMENT' => 'La pièce jointe est introuvable.', + + 'FILE_NOT_FOUND_404' => 'Le fichier « %s » est introuvable.', + 'FORK_TOPIC' => 'Copier le sujet', + 'FULL_EDITOR' => 'Éditeur avancé et prévisualisation', + + 'LINKAGE_FORBIDDEN' => 'Vous ne pouvez pas consulter, télécharger ou insérer de liens vers ce site.', + 'LOGIN_NOTIFY_TOPIC' => 'Vous avez reçu une notification concernant un message de ce sujet. Veuillez vous connecter afin de le consulter.', + 'LOGIN_VIEWTOPIC' => 'Vous devez être inscrit et connecté afin de consulter ce sujet.', + + 'MAKE_ANNOUNCE' => 'Modifier en annonce', + 'MAKE_GLOBAL' => 'Modifier en annonce générale', + 'MAKE_NORMAL' => 'Modifier en sujet standard', + 'MAKE_STICKY' => 'Modifier en note', + 'MAX_OPTIONS_SELECT' => [ + 1 => 'Vous pouvez sélectionner %d option', + 2 => 'Vous pouvez sélectionner %d options', + ], + 'MISSING_INLINE_ATTACHMENT' => 'La pièce jointe « %s » n’est plus disponible', + 'MOVE_TOPIC' => 'Déplacer le sujet', + + 'NO_ATTACHMENT_SELECTED' => 'Vous n’avez sélectionné aucune pièce jointe à télécharger ou à consulter.', + 'NO_NEWER_TOPICS' => 'Aucun nouveau sujet n’a été publié dans ce forum.', + 'NO_OLDER_TOPICS' => 'Aucun ancien sujet n’a été publié dans ce forum.', + 'NO_UNREAD_POSTS' => 'Aucun message non lu n’a été publié dans ce sujet.', + 'NO_VOTE_OPTION' => 'Vous devez sélectionner une option afin de voter.', + 'NO_VOTES' => 'Aucun vote', + 'NO_AUTH_PRINT_TOPIC' => 'Vous ne pouvez pas imprimer les sujets.', + + 'POLL_ENDED_AT' => 'Le sondage est terminé depuis le %s', + 'POLL_RUN_TILL' => 'Le sondage est actif jusqu’au %s', + 'POLL_VOTED_OPTION' => 'Vous avez voté pour cette option', + 'POST_DELETED_RESTORE' => 'Le message a été supprimé. Il peut être restauré.', + 'PRINT_TOPIC' => 'Aperçu avant impression', + + 'QUICK_MOD' => 'Actions rapides de modération', + 'QUICKREPLY' => 'Réponse rapide', + 'QUOTE' => 'Citation', + + 'REPLY_TO_TOPIC' => 'Répondre au sujet', + 'RESTORE' => 'Restaurer', + 'RESTORE_TOPIC' => 'Restaurer le sujet', + 'RETURN_POST' => '%sRevenir au message%s', + + 'SUBMIT_VOTE' => 'Voter', + + 'TOPIC_TOOLS' => 'Outils du sujet', + 'TOTAL_VOTES' => 'Nombre total de votes', + + 'UNLOCK_TOPIC' => 'Déverrouiller le sujet', + + 'VIEW_INFO' => 'Informations sur le message', + 'VIEW_NEXT_TOPIC' => 'Sujet suivant', + 'VIEW_PREVIOUS_TOPIC' => 'Sujet précédent', + 'VIEW_RESULTS' => 'Consulter les résultats', + 'VIEW_TOPIC_POSTS' => [ + 1 => '%d message', + 2 => '%d messages', + ], + 'VIEW_UNREAD_POST' => 'Premier message non lu', + 'VOTE_SUBMITTED' => 'Votre vote a bien été comptabilisé.', + 'VOTE_CONVERTED' => 'Les votes d’un sondage qui a été converti ne peuvent pas être modifiés.', +]); diff --git a/language/index.htm b/language/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/language/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/mcp.php b/mcp.php new file mode 100644 index 0000000..c4a8a66 --- /dev/null +++ b/mcp.php @@ -0,0 +1,326 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/functions_admin.' . $phpEx); +include($phpbb_root_path . 'includes/functions_mcp.' . $phpEx); +require($phpbb_root_path . 'includes/functions_module.' . $phpEx); + +// Start session management +$user->session_begin(); +$auth->acl($user->data); +$user->setup('mcp'); + +$module = new p_master(); + +// Setting a variable to let the style designer know where he is... +$template->assign_var('S_IN_MCP', true); + +// Basic parameter data +$id = $request->variable('i', ''); + +$mode = $request->variable('mode', array('')); +$mode = count($mode) ? array_shift($mode) : $request->variable('mode', ''); + +// Only Moderators can go beyond this point +if (!$user->data['is_registered']) +{ + if ($user->data['is_bot']) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + login_box('', $user->lang['LOGIN_EXPLAIN_MCP']); +} + +$quickmod = (isset($_REQUEST['quickmod'])) ? true : false; +$action = $request->variable('action', ''); +$action_ary = $request->variable('action', array('' => 0)); + +$forum_action = $request->variable('forum_action', ''); +if ($forum_action !== '' && $request->variable('sort', false, false, \phpbb\request\request_interface::POST)) +{ + $action = $forum_action; +} + +if (count($action_ary)) +{ + list($action, ) = each($action_ary); +} +unset($action_ary); + +if ($mode == 'topic_logs') +{ + $id = 'logs'; + $quickmod = false; +} + +$post_id = $request->variable('p', 0); +$topic_id = $request->variable('t', 0); +$forum_id = $request->variable('f', 0); +$report_id = $request->variable('r', 0); +$user_id = $request->variable('u', 0); +$username = $request->variable('username', '', true); + +if ($post_id) +{ + // We determine the topic and forum id here, to make sure the moderator really has moderative rights on this post + $sql = 'SELECT topic_id, forum_id + FROM ' . POSTS_TABLE . " + WHERE post_id = $post_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $topic_id = (int) $row['topic_id']; + $forum_id = (int) $row['forum_id']; +} +else if ($topic_id) +{ + $sql = 'SELECT forum_id + FROM ' . TOPICS_TABLE . " + WHERE topic_id = $topic_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $forum_id = (int) $row['forum_id']; +} + +// If the user doesn't have any moderator powers (globally or locally) he can't access the mcp +if (!$auth->acl_getf_global('m_')) +{ + // Except he is using one of the quickmod tools for users + $user_quickmod_actions = array( + 'lock' => 'f_user_lock', + 'make_sticky' => 'f_sticky', + 'make_announce' => 'f_announce', + 'make_global' => 'f_announce_global', + 'make_normal' => array('f_announce', 'f_announce_global', 'f_sticky') + ); + + $allow_user = false; + if ($quickmod && isset($user_quickmod_actions[$action]) && $user->data['is_registered'] && $auth->acl_gets($user_quickmod_actions[$action], $forum_id)) + { + $topic_info = phpbb_get_topic_data(array($topic_id)); + if ($topic_info[$topic_id]['topic_poster'] == $user->data['user_id']) + { + $allow_user = true; + } + } + + if (!$allow_user) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } +} + +// if the user cannot read the forum he tries to access then we won't allow mcp access either +if ($forum_id && !$auth->acl_get('f_read', $forum_id)) +{ + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); +} + +/** +* Allow applying additional permissions to MCP access besides f_read +* +* @event core.mcp_global_f_read_auth_after +* @var string action The action the user tried to execute +* @var int forum_id The forum the user tried to access +* @var string mode The MCP module the user is trying to access +* @var p_master module Module system class +* @var bool quickmod True if the user is accessing using quickmod tools +* @var int topic_id The topic the user tried to access +* @since 3.1.3-RC1 +*/ +$vars = array( + 'action', + 'forum_id', + 'mode', + 'module', + 'quickmod', + 'topic_id', +); +extract($phpbb_dispatcher->trigger_event('core.mcp_global_f_read_auth_after', compact($vars))); + +if ($forum_id) +{ + $module->acl_forum_id = $forum_id; +} + +// Instantiate module system and generate list of available modules +$module->list_modules('mcp'); + +if ($quickmod) +{ + $mode = 'quickmod'; + + switch ($action) + { + case 'lock': + case 'unlock': + case 'lock_post': + case 'unlock_post': + case 'make_sticky': + case 'make_announce': + case 'make_global': + case 'make_normal': + case 'fork': + case 'move': + case 'delete_post': + case 'delete_topic': + case 'restore_topic': + $module->load('mcp', 'main', 'quickmod'); + return; + break; + + case 'topic_logs': + // Reset start parameter if we jumped from the quickmod dropdown + if ($request->variable('start', 0)) + { + $request->overwrite('start', 0); + } + + $module->set_active('logs', 'topic_logs'); + break; + + case 'merge_topic': + $module->set_active('main', 'forum_view'); + break; + + case 'split': + case 'merge': + $module->set_active('main', 'topic_view'); + break; + + default: + // If needed, the flag can be set to true within event listener + // to indicate that the action was handled properly + // and to pass by the trigger_error() call below + $is_valid_action = false; + + /** + * This event allows you to add custom quickmod options + * + * @event core.modify_quickmod_options + * @var object module Instance of module system class + * @var string action Quickmod option + * @var bool is_valid_action Flag indicating if the action was handled properly + * @since 3.1.0-a4 + */ + $vars = array('module', 'action', 'is_valid_action'); + extract($phpbb_dispatcher->trigger_event('core.modify_quickmod_options', compact($vars))); + + if (!$is_valid_action) + { + trigger_error($user->lang('QUICKMOD_ACTION_NOT_ALLOWED', $action), E_USER_ERROR); + } + break; + } +} +else +{ + // Select the active module + $module->set_active($id, $mode); +} + +// Hide some of the options if we don't have the relevant information to use them +if (!$post_id) +{ + $module->set_display('main', 'post_details', false); + $module->set_display('warn', 'warn_post', false); +} + +if ($mode == '' || $mode == 'unapproved_topics' || $mode == 'unapproved_posts' || $mode == 'deleted_topics' || $mode == 'deleted_posts') +{ + $module->set_display('queue', 'approve_details', false); +} + +if ($mode == '' || $mode == 'reports' || $mode == 'reports_closed' || $mode == 'pm_reports' || $mode == 'pm_reports_closed' || $mode == 'pm_report_details') +{ + $module->set_display('reports', 'report_details', false); +} + +if ($mode == '' || $mode == 'reports' || $mode == 'reports_closed' || $mode == 'pm_reports' || $mode == 'pm_reports_closed' || $mode == 'report_details') +{ + $module->set_display('pm_reports', 'pm_report_details', false); +} + +if (!$topic_id) +{ + $module->set_display('main', 'topic_view', false); + $module->set_display('logs', 'topic_logs', false); +} + +if (!$forum_id) +{ + $module->set_display('main', 'forum_view', false); + $module->set_display('logs', 'forum_logs', false); +} + +if (!$user_id && $username == '') +{ + $module->set_display('notes', 'user_notes', false); + $module->set_display('warn', 'warn_user', false); +} + +/** +* This event allows you to set display option for custom MCP modules +* +* @event core.modify_mcp_modules_display_option +* @var p_master module Module system class +* @var string mode MCP mode +* @var int user_id User id +* @var int forum_id Forum id +* @var int topic_id Topic id +* @var int post_id Post id +* @var string username User name +* @var int id Parent module id +* @since 3.1.0-b2 +*/ +$vars = array( + 'module', + 'mode', + 'user_id', + 'forum_id', + 'topic_id', + 'post_id', + 'username', + 'id', +); +extract($phpbb_dispatcher->trigger_event('core.modify_mcp_modules_display_option', compact($vars))); + +// Load and execute the relevant module +$module->load_active(); + +// Assign data to the template engine for the list of modules +$module->assign_tpl_vars(append_sid("{$phpbb_root_path}mcp.$phpEx")); + +// Generate urls for letting the moderation control panel being accessed in different modes +$template->assign_vars(array( + 'U_MCP' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=main'), + 'U_MCP_FORUM' => ($forum_id) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&mode=forum_view&f=$forum_id") : '', + 'U_MCP_TOPIC' => ($forum_id && $topic_id) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&mode=topic_view&t=$topic_id") : '', + 'U_MCP_POST' => ($forum_id && $topic_id && $post_id) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&mode=post_details&t=$topic_id&p=$post_id") : '', +)); + +// Generate the page, do not display/query online list +$module->display($module->get_page_title()); diff --git a/memberlist.php b/memberlist.php new file mode 100644 index 0000000..b26d7c8 --- /dev/null +++ b/memberlist.php @@ -0,0 +1,1776 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + +$mode = $request->variable('mode', ''); + +if ($mode === 'contactadmin') +{ + define('SKIP_CHECK_BAN', true); + define('SKIP_CHECK_DISABLED', true); +} + +// Start session management +$user->session_begin(); +$auth->acl($user->data); +$user->setup(array('memberlist', 'groups')); + +// Setting a variable to let the style designer know where he is... +$template->assign_var('S_IN_MEMBERLIST', true); + +// Grab data +$action = $request->variable('action', ''); +$user_id = $request->variable('u', ANONYMOUS); +$username = $request->variable('un', '', true); +$group_id = $request->variable('g', 0); +$topic_id = $request->variable('t', 0); + +// Redirect when old mode is used +if ($mode == 'leaders') +{ + send_status_line(301, 'Moved Permanently'); + redirect(append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team')); +} + +// Check our mode... +if (!in_array($mode, array('', 'group', 'viewprofile', 'email', 'contact', 'contactadmin', 'searchuser', 'team', 'livesearch'))) +{ + trigger_error('NO_MODE'); +} + +switch ($mode) +{ + case 'email': + case 'contactadmin': + break; + + case 'livesearch': + if (!$config['allow_live_searches']) + { + trigger_error('LIVE_SEARCHES_NOT_ALLOWED'); + } + // No break + + default: + // Can this user view profiles/memberlist? + if (!$auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) + { + if ($user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_VIEW_USERS'); + } + + login_box('', ((isset($user->lang['LOGIN_EXPLAIN_' . strtoupper($mode)])) ? $user->lang['LOGIN_EXPLAIN_' . strtoupper($mode)] : $user->lang['LOGIN_EXPLAIN_MEMBERLIST'])); + } + break; +} + +/** @var \phpbb\group\helper $group_helper */ +$group_helper = $phpbb_container->get('group_helper'); + +$start = $request->variable('start', 0); +$submit = (isset($_POST['submit'])) ? true : false; + +$default_key = 'c'; +$sort_key = $request->variable('sk', $default_key); +$sort_dir = $request->variable('sd', 'a'); + +$user_types = array(USER_NORMAL, USER_FOUNDER); +if ($auth->acl_get('a_user')) +{ + $user_types[] = USER_INACTIVE; +} + +// What do you want to do today? ... oops, I think that line is taken ... +switch ($mode) +{ + case 'team': + // Display a listing of board admins, moderators + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $page_title = $user->lang['THE_TEAM']; + $template_html = 'memberlist_team.html'; + + $sql = 'SELECT * + FROM ' . TEAMPAGE_TABLE . ' + ORDER BY teampage_position ASC'; + $result = $db->sql_query($sql, 3600); + $teampage_data = $db->sql_fetchrowset($result); + $db->sql_freeresult($result); + + $sql_ary = array( + 'SELECT' => 'g.group_id, g.group_name, g.group_colour, g.group_type, ug.user_id as ug_user_id, t.teampage_id', + + 'FROM' => array(GROUPS_TABLE => 'g'), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(TEAMPAGE_TABLE => 't'), + 'ON' => 't.group_id = g.group_id', + ), + array( + 'FROM' => array(USER_GROUP_TABLE => 'ug'), + 'ON' => 'ug.group_id = g.group_id AND ug.user_pending = 0 AND ug.user_id = ' . (int) $user->data['user_id'], + ), + ), + ); + + $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); + + $group_ids = $groups_ary = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['group_type'] == GROUP_HIDDEN && !$auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel') && $row['ug_user_id'] != $user->data['user_id']) + { + $row['group_name'] = $user->lang['GROUP_UNDISCLOSED']; + $row['u_group'] = ''; + } + else + { + $row['group_name'] = $group_helper->get_name($row['group_name']); + $row['u_group'] = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group&g=' . $row['group_id']); + } + + if ($row['teampage_id']) + { + // Only put groups into the array we want to display. + // We are fetching all groups, to ensure we got all data for default groups. + $group_ids[] = (int) $row['group_id']; + } + $groups_ary[(int) $row['group_id']] = $row; + } + $db->sql_freeresult($result); + + $sql_ary = array( + 'SELECT' => 'u.user_id, u.group_id as default_group, u.username, u.username_clean, u.user_colour, u.user_type, u.user_rank, u.user_posts, u.user_allow_pm, g.group_id', + + 'FROM' => array( + USER_GROUP_TABLE => 'ug', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(USERS_TABLE => 'u'), + 'ON' => 'ug.user_id = u.user_id', + ), + array( + 'FROM' => array(GROUPS_TABLE => 'g'), + 'ON' => 'ug.group_id = g.group_id', + ), + ), + + 'WHERE' => $db->sql_in_set('g.group_id', $group_ids, false, true) . ' AND ug.user_pending = 0', + + 'ORDER_BY' => 'u.username_clean ASC', + ); + + /** + * Modify the query used to get the users for the team page + * + * @event core.memberlist_team_modify_query + * @var array sql_ary Array containing the query + * @var array group_ids Array of group ids + * @var array teampage_data The teampage data + * @since 3.1.3-RC1 + */ + $vars = array( + 'sql_ary', + 'group_ids', + 'teampage_data', + ); + extract($phpbb_dispatcher->trigger_event('core.memberlist_team_modify_query', compact($vars))); + + $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); + + $user_ary = $user_ids = $group_users = array(); + while ($row = $db->sql_fetchrow($result)) + { + $row['forums'] = ''; + $row['forums_ary'] = array(); + $user_ary[(int) $row['user_id']] = $row; + $user_ids[] = (int) $row['user_id']; + $group_users[(int) $row['group_id']][] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + $user_ids = array_unique($user_ids); + + if (!empty($user_ids) && $config['teampage_forums']) + { + $template->assign_var('S_DISPLAY_MODERATOR_FORUMS', true); + // Get all moderators + $perm_ary = $auth->acl_get_list($user_ids, array('m_'), false); + + foreach ($perm_ary as $forum_id => $forum_ary) + { + foreach ($forum_ary as $auth_option => $id_ary) + { + foreach ($id_ary as $id) + { + if (!$forum_id) + { + $user_ary[$id]['forums'] = $user->lang['ALL_FORUMS']; + } + else + { + $user_ary[$id]['forums_ary'][] = $forum_id; + } + } + } + } + + $sql = 'SELECT forum_id, forum_name + FROM ' . FORUMS_TABLE; + $result = $db->sql_query($sql); + + $forums = array(); + while ($row = $db->sql_fetchrow($result)) + { + $forums[$row['forum_id']] = $row['forum_name']; + } + $db->sql_freeresult($result); + + foreach ($user_ary as $user_id => $user_data) + { + if (!$user_data['forums']) + { + foreach ($user_data['forums_ary'] as $forum_id) + { + $user_ary[$user_id]['forums_options'] = true; + if (isset($forums[$forum_id])) + { + if ($auth->acl_get('f_list', $forum_id)) + { + $user_ary[$user_id]['forums'] .= ''; + } + } + } + } + } + } + + $parent_team = 0; + foreach ($teampage_data as $team_data) + { + // If this team entry has no group, it's a category + if (!$team_data['group_id']) + { + $template->assign_block_vars('group', array( + 'GROUP_NAME' => $team_data['teampage_name'], + )); + + $parent_team = (int) $team_data['teampage_id']; + continue; + } + + $group_data = $groups_ary[(int) $team_data['group_id']]; + $group_id = (int) $team_data['group_id']; + + if (!$team_data['teampage_parent']) + { + // If the group does not have a parent category, we display the groupname as category + $template->assign_block_vars('group', array( + 'GROUP_NAME' => $group_data['group_name'], + 'GROUP_COLOR' => $group_data['group_colour'], + 'U_GROUP' => $group_data['u_group'], + )); + } + + // Display group members. + if (!empty($group_users[$group_id])) + { + foreach ($group_users[$group_id] as $user_id) + { + if (isset($user_ary[$user_id])) + { + $row = $user_ary[$user_id]; + if ($config['teampage_memberships'] == 1 && ($group_id != $groups_ary[$row['default_group']]['group_id']) && $groups_ary[$row['default_group']]['teampage_id']) + { + // Display users in their primary group, instead of the first group, when it is displayed on the teampage. + continue; + } + + $user_rank_data = phpbb_get_user_rank($row, (($row['user_id'] == ANONYMOUS) ? false : $row['user_posts'])); + + $template_vars = array( + 'USER_ID' => $row['user_id'], + 'FORUMS' => $row['forums'], + 'FORUM_OPTIONS' => (isset($row['forums_options'])) ? true : false, + 'RANK_TITLE' => $user_rank_data['title'], + + 'GROUP_NAME' => $groups_ary[$row['default_group']]['group_name'], + 'GROUP_COLOR' => $groups_ary[$row['default_group']]['group_colour'], + 'U_GROUP' => $groups_ary[$row['default_group']]['u_group'], + + 'RANK_IMG' => $user_rank_data['img'], + 'RANK_IMG_SRC' => $user_rank_data['img_src'], + + 'S_INACTIVE' => $row['user_type'] == USER_INACTIVE, + + 'U_PM' => ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && ($row['user_allow_pm'] || $auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_'))) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&u=' . $row['user_id']) : '', + + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'USERNAME' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), + 'USER_COLOR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), + 'U_VIEW_PROFILE' => get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']), + ); + + /** + * Modify the template vars for displaying the user in the groups on the teampage + * + * @event core.memberlist_team_modify_template_vars + * @var array template_vars Array containing the query + * @var array row Array containing the action user row + * @var array groups_ary Array of groups with all users that should be displayed + * @since 3.1.3-RC1 + */ + $vars = array( + 'template_vars', + 'row', + 'groups_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.memberlist_team_modify_template_vars', compact($vars))); + + $template->assign_block_vars('group.user', $template_vars); + + if ($config['teampage_memberships'] != 2) + { + unset($user_ary[$user_id]); + } + } + } + } + } + + $template->assign_vars(array( + 'PM_IMG' => $user->img('icon_contact_pm', $user->lang['SEND_PRIVATE_MESSAGE'])) + ); + break; + + case 'contact': + + $page_title = $user->lang['IM_USER']; + $template_html = 'memberlist_im.html'; + + if (!$auth->acl_get('u_sendim')) + { + send_status_line(403, 'Forbidden'); + trigger_error('NOT_AUTHORISED'); + } + + $presence_img = ''; + switch ($action) + { + case 'jabber': + $lang = 'JABBER'; + $sql_field = 'user_jabber'; + $s_select = (@extension_loaded('xml') && $config['jab_enable']) ? 'S_SEND_JABBER' : 'S_NO_SEND_JABBER'; + $s_action = append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=contact&action=$action&u=$user_id"); + break; + + default: + trigger_error('NO_MODE', E_USER_ERROR); + break; + } + + // Grab relevant data + $sql = "SELECT user_id, username, user_email, user_lang, $sql_field + FROM " . USERS_TABLE . " + WHERE user_id = $user_id + AND user_type IN (" . USER_NORMAL . ', ' . USER_FOUNDER . ')'; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_USER'); + } + else if (empty($row[$sql_field])) + { + trigger_error('IM_NO_DATA'); + } + + // Post data grab actions + switch ($action) + { + case 'jabber': + add_form_key('memberlist_messaging'); + + if ($submit && @extension_loaded('xml') && $config['jab_enable']) + { + if (check_form_key('memberlist_messaging')) + { + + include_once($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + + $subject = sprintf($user->lang['IM_JABBER_SUBJECT'], $user->data['username'], $config['server_name']); + $message = $request->variable('message', '', true); + + if (empty($message)) + { + trigger_error('EMPTY_MESSAGE_IM'); + } + + $messenger = new messenger(false); + + $messenger->template('profile_send_im', $row['user_lang']); + $messenger->subject(htmlspecialchars_decode($subject)); + + $messenger->replyto($user->data['user_email']); + $messenger->set_addresses($row); + + $messenger->assign_vars(array( + 'BOARD_CONTACT' => phpbb_get_board_contact($config, $phpEx), + 'FROM_USERNAME' => htmlspecialchars_decode($user->data['username']), + 'TO_USERNAME' => htmlspecialchars_decode($row['username']), + 'MESSAGE' => htmlspecialchars_decode($message)) + ); + + $messenger->send(NOTIFY_IM); + + $s_select = 'S_SENT_JABBER'; + } + else + { + trigger_error('FORM_INVALID'); + } + } + break; + } + + // Send vars to the template + $template->assign_vars(array( + 'IM_CONTACT' => $row[$sql_field], + 'A_IM_CONTACT' => addslashes($row[$sql_field]), + + 'USERNAME' => $row['username'], + 'CONTACT_NAME' => $row[$sql_field], + 'SITENAME' => $config['sitename'], + + 'PRESENCE_IMG' => $presence_img, + + 'L_SEND_IM_EXPLAIN' => $user->lang['IM_' . $lang], + 'L_IM_SENT_JABBER' => sprintf($user->lang['IM_SENT_JABBER'], $row['username']), + + $s_select => true, + 'S_IM_ACTION' => $s_action) + ); + + break; + + case 'viewprofile': + // Display a profile + if ($user_id == ANONYMOUS && !$username) + { + trigger_error('NO_USER'); + } + + // Get user... + $sql_array = array( + 'SELECT' => 'u.*', + 'FROM' => array( + USERS_TABLE => 'u' + ), + 'WHERE' => (($username) ? "u.username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'" : "u.user_id = $user_id"), + ); + + /** + * Modify user data SQL before member profile row is created + * + * @event core.memberlist_modify_viewprofile_sql + * @var int user_id The user ID + * @var string username The username + * @var array sql_array Array containing the main query + * @since 3.2.6-RC1 + */ + $vars = array( + 'user_id', + 'username', + 'sql_array', + ); + extract($phpbb_dispatcher->trigger_event('core.memberlist_modify_viewprofile_sql', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + $member = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$member) + { + trigger_error('NO_USER'); + } + + // a_user admins and founder are able to view inactive users and bots to be able to manage them more easily + // Normal users are able to see at least users having only changed their profile settings but not yet reactivated. + if (!$auth->acl_get('a_user') && $user->data['user_type'] != USER_FOUNDER) + { + if ($member['user_type'] == USER_IGNORE) + { + trigger_error('NO_USER'); + } + else if ($member['user_type'] == USER_INACTIVE && $member['user_inactive_reason'] != INACTIVE_PROFILE) + { + trigger_error('NO_USER'); + } + } + + $user_id = (int) $member['user_id']; + + // Get group memberships + // Also get visiting user's groups to determine hidden group memberships if necessary. + $auth_hidden_groups = ($user_id === (int) $user->data['user_id'] || $auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) ? true : false; + $sql_uid_ary = ($auth_hidden_groups) ? array($user_id) : array($user_id, (int) $user->data['user_id']); + + // Do the SQL thang + $sql_ary = [ + 'SELECT' => 'g.group_id, g.group_name, g.group_type, ug.user_id', + + 'FROM' => [ + GROUPS_TABLE => 'g', + ], + + 'LEFT_JOIN' => [ + [ + 'FROM' => [USER_GROUP_TABLE => 'ug'], + 'ON' => 'g.group_id = ug.group_id', + ], + ], + + 'WHERE' => $db->sql_in_set('ug.user_id', $sql_uid_ary) . ' + AND ug.user_pending = 0', + ]; + + /** + * Modify the query used to get the group data + * + * @event core.modify_memberlist_viewprofile_group_sql + * @var array sql_ary Array containing the query + * @since 3.2.6-RC1 + */ + $vars = array( + 'sql_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.modify_memberlist_viewprofile_group_sql', compact($vars))); + + $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); + + // Divide data into profile data and current user data + $profile_groups = $user_groups = array(); + while ($row = $db->sql_fetchrow($result)) + { + $row['user_id'] = (int) $row['user_id']; + $row['group_id'] = (int) $row['group_id']; + + if ($row['user_id'] == $user_id) + { + $profile_groups[] = $row; + } + else + { + $user_groups[$row['group_id']] = $row['group_id']; + } + } + $db->sql_freeresult($result); + + // Filter out hidden groups and sort groups by name + $group_data = $group_sort = array(); + foreach ($profile_groups as $row) + { + if (!$auth_hidden_groups && $row['group_type'] == GROUP_HIDDEN && !isset($user_groups[$row['group_id']])) + { + // Skip over hidden groups the user cannot see + continue; + } + + $row['group_name'] = $group_helper->get_name($row['group_name']); + + $group_sort[$row['group_id']] = utf8_clean_string($row['group_name']); + $group_data[$row['group_id']] = $row; + } + unset($profile_groups); + unset($user_groups); + asort($group_sort); + + /** + * Modify group data before options is created and data is unset + * + * @event core.modify_memberlist_viewprofile_group_data + * @var array group_data Array containing the group data + * @var array group_sort Array containing the sorted group data + * @since 3.2.6-RC1 + */ + $vars = array( + 'group_data', + 'group_sort', + ); + extract($phpbb_dispatcher->trigger_event('core.modify_memberlist_viewprofile_group_data', compact($vars))); + + $group_options = ''; + foreach ($group_sort as $group_id => $null) + { + $row = $group_data[$group_id]; + + $group_options .= ''; + } + unset($group_data); + unset($group_sort); + + // What colour is the zebra + $sql = 'SELECT friend, foe + FROM ' . ZEBRA_TABLE . " + WHERE zebra_id = $user_id + AND user_id = {$user->data['user_id']}"; + + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $foe = ($row['foe']) ? true : false; + $friend = ($row['friend']) ? true : false; + $db->sql_freeresult($result); + + if ($config['load_onlinetrack']) + { + $sql = 'SELECT MAX(session_time) AS session_time, MIN(session_viewonline) AS session_viewonline + FROM ' . SESSIONS_TABLE . " + WHERE session_user_id = $user_id"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $member['session_time'] = (isset($row['session_time'])) ? $row['session_time'] : 0; + $member['session_viewonline'] = (isset($row['session_viewonline'])) ? $row['session_viewonline'] : 0; + unset($row); + } + + if ($config['load_user_activity']) + { + display_user_activity($member); + } + + // Do the relevant calculations + $memberdays = max(1, round((time() - $member['user_regdate']) / 86400)); + $posts_per_day = $member['user_posts'] / $memberdays; + $percentage = ($config['num_posts']) ? min(100, ($member['user_posts'] / $config['num_posts']) * 100) : 0; + + + if ($member['user_sig']) + { + $parse_flags = ($member['user_sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $member['user_sig'] = generate_text_for_display($member['user_sig'], $member['user_sig_bbcode_uid'], $member['user_sig_bbcode_bitfield'], $parse_flags, true); + } + + // We need to check if the modules 'zebra' ('friends' & 'foes' mode), 'notes' ('user_notes' mode) and 'warn' ('warn_user' mode) are accessible to decide if we can display appropriate links + $zebra_enabled = $friends_enabled = $foes_enabled = $user_notes_enabled = $warn_user_enabled = false; + + // Only check if the user is logged in + if ($user->data['is_registered']) + { + if (!class_exists('p_master')) + { + include($phpbb_root_path . 'includes/functions_module.' . $phpEx); + } + $module = new p_master(); + + $module->list_modules('ucp'); + $module->list_modules('mcp'); + + $user_notes_enabled = ($module->loaded('mcp_notes', 'user_notes')) ? true : false; + $warn_user_enabled = ($module->loaded('mcp_warn', 'warn_user')) ? true : false; + $zebra_enabled = ($module->loaded('ucp_zebra')) ? true : false; + $friends_enabled = ($module->loaded('ucp_zebra', 'friends')) ? true : false; + $foes_enabled = ($module->loaded('ucp_zebra', 'foes')) ? true : false; + + unset($module); + } + + // Custom Profile Fields + $profile_fields = array(); + if ($config['load_cpf_viewprofile']) + { + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + $profile_fields = $cp->grab_profile_fields_data($user_id); + $profile_fields = (isset($profile_fields[$user_id])) ? $cp->generate_profile_fields_template_data($profile_fields[$user_id]) : array(); + } + + /** + * Modify user data before we display the profile + * + * @event core.memberlist_view_profile + * @var array member Array with user's data + * @var bool user_notes_enabled Is the mcp user notes module enabled? + * @var bool warn_user_enabled Is the mcp warnings module enabled? + * @var bool zebra_enabled Is the ucp zebra module enabled? + * @var bool friends_enabled Is the ucp friends module enabled? + * @var bool foes_enabled Is the ucp foes module enabled? + * @var bool friend Is the user friend? + * @var bool foe Is the user foe? + * @var array profile_fields Array with user's profile field data + * @since 3.1.0-a1 + * @changed 3.1.0-b2 Added friend and foe status + * @changed 3.1.0-b3 Added profile fields data + */ + $vars = array( + 'member', + 'user_notes_enabled', + 'warn_user_enabled', + 'zebra_enabled', + 'friends_enabled', + 'foes_enabled', + 'friend', + 'foe', + 'profile_fields', + ); + extract($phpbb_dispatcher->trigger_event('core.memberlist_view_profile', compact($vars))); + + $template->assign_vars(phpbb_show_profile($member, $user_notes_enabled, $warn_user_enabled)); + + // If the user has m_approve permission or a_user permission, then list then display unapproved posts + if ($auth->acl_getf_global('m_approve') || $auth->acl_get('a_user')) + { + $sql = 'SELECT COUNT(post_id) as posts_in_queue + FROM ' . POSTS_TABLE . ' + WHERE poster_id = ' . $user_id . ' + AND ' . $db->sql_in_set('post_visibility', array(ITEM_UNAPPROVED, ITEM_REAPPROVE)); + $result = $db->sql_query($sql); + $member['posts_in_queue'] = (int) $db->sql_fetchfield('posts_in_queue'); + $db->sql_freeresult($result); + } + else + { + $member['posts_in_queue'] = 0; + } + + // Define the main array of vars to assign to memberlist_view.html + $template_ary = array( + 'L_POSTS_IN_QUEUE' => $user->lang('NUM_POSTS_IN_QUEUE', $member['posts_in_queue']), + + 'POSTS_DAY' => $user->lang('POST_DAY', $posts_per_day), + 'POSTS_PCT' => $user->lang('POST_PCT', $percentage), + + 'SIGNATURE' => $member['user_sig'], + 'POSTS_IN_QUEUE' => $member['posts_in_queue'], + + 'PM_IMG' => $user->img('icon_contact_pm', $user->lang['SEND_PRIVATE_MESSAGE']), + 'L_SEND_EMAIL_USER' => $user->lang('SEND_EMAIL_USER', $member['username']), + 'EMAIL_IMG' => $user->img('icon_contact_email', $user->lang['EMAIL']), + 'JABBER_IMG' => $user->img('icon_contact_jabber', $user->lang['JABBER']), + 'SEARCH_IMG' => $user->img('icon_user_search', $user->lang['SEARCH']), + + 'S_PROFILE_ACTION' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=group'), + 'S_GROUP_OPTIONS' => $group_options, + 'S_CUSTOM_FIELDS' => (isset($profile_fields['row']) && count($profile_fields['row'])) ? true : false, + + 'U_USER_ADMIN' => ($auth->acl_get('a_user')) ? append_sid("{$phpbb_admin_path}index.$phpEx", 'i=users&mode=overview&u=' . $user_id, true, $user->session_id) : '', + 'U_USER_BAN' => ($auth->acl_get('m_ban') && $user_id != $user->data['user_id']) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=ban&mode=user&u=' . $user_id, true, $user->session_id) : '', + 'U_MCP_QUEUE' => ($auth->acl_getf_global('m_approve')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue', true, $user->session_id) : '', + + 'U_SWITCH_PERMISSIONS' => ($auth->acl_get('a_switchperm') && $user->data['user_id'] != $user_id) ? append_sid("{$phpbb_root_path}ucp.$phpEx", "mode=switch_perm&u={$user_id}&hash=" . generate_link_hash('switchperm')) : '', + 'U_EDIT_SELF' => ($user_id == $user->data['user_id'] && $auth->acl_get('u_chgprofileinfo')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_profile&mode=profile_info') : '', + + 'S_USER_NOTES' => ($user_notes_enabled) ? true : false, + 'S_WARN_USER' => ($warn_user_enabled) ? true : false, + 'S_ZEBRA' => ($user->data['user_id'] != $user_id && $user->data['is_registered'] && $zebra_enabled) ? true : false, + 'U_ADD_FRIEND' => (!$friend && !$foe && $friends_enabled) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=zebra&add=' . urlencode(htmlspecialchars_decode($member['username']))) : '', + 'U_ADD_FOE' => (!$friend && !$foe && $foes_enabled) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=zebra&mode=foes&add=' . urlencode(htmlspecialchars_decode($member['username']))) : '', + 'U_REMOVE_FRIEND' => ($friend && $friends_enabled) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=zebra&remove=1&usernames[]=' . $user_id) : '', + 'U_REMOVE_FOE' => ($foe && $foes_enabled) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=zebra&remove=1&mode=foes&usernames[]=' . $user_id) : '', + + 'U_CANONICAL' => generate_board_url() . '/' . append_sid("memberlist.$phpEx", 'mode=viewprofile&u=' . $user_id, true, ''), + ); + + /** + * Modify user's template vars before we display the profile + * + * @event core.memberlist_modify_view_profile_template_vars + * @var array template_ary Array with user's template vars + * @since 3.2.6-RC1 + */ + $vars = array( + 'template_ary', + ); + extract($phpbb_dispatcher->trigger_event('core.memberlist_modify_view_profile_template_vars', compact($vars))); + + // Assign vars to memberlist_view.html + $template->assign_vars($template_ary); + + if (!empty($profile_fields['row'])) + { + $template->assign_vars($profile_fields['row']); + } + + if (!empty($profile_fields['blockrow'])) + { + foreach ($profile_fields['blockrow'] as $field_data) + { + $template->assign_block_vars('custom_fields', $field_data); + } + } + + // Inactive reason/account? + if ($member['user_type'] == USER_INACTIVE) + { + $user->add_lang('acp/common'); + + $inactive_reason = $user->lang['INACTIVE_REASON_UNKNOWN']; + + switch ($member['user_inactive_reason']) + { + case INACTIVE_REGISTER: + $inactive_reason = $user->lang['INACTIVE_REASON_REGISTER']; + break; + + case INACTIVE_PROFILE: + $inactive_reason = $user->lang['INACTIVE_REASON_PROFILE']; + break; + + case INACTIVE_MANUAL: + $inactive_reason = $user->lang['INACTIVE_REASON_MANUAL']; + break; + + case INACTIVE_REMIND: + $inactive_reason = $user->lang['INACTIVE_REASON_REMIND']; + break; + } + + $template->assign_vars(array( + 'S_USER_INACTIVE' => true, + 'USER_INACTIVE_REASON' => $inactive_reason) + ); + } + + // Now generate page title + $page_title = sprintf($user->lang['VIEWING_PROFILE'], $member['username']); + $template_html = 'memberlist_view.html'; + + break; + + case 'contactadmin': + case 'email': + if (!class_exists('messenger')) + { + include($phpbb_root_path . 'includes/functions_messenger.' . $phpEx); + } + + $user_id = $request->variable('u', 0); + $topic_id = $request->variable('t', 0); + + if ($user_id) + { + $form_name = 'user'; + } + else if ($topic_id) + { + $form_name = 'topic'; + } + else if ($mode === 'contactadmin') + { + $form_name = 'admin'; + } + else + { + trigger_error('NO_EMAIL'); + } + + /** @var $form \phpbb\message\form */ + $form = $phpbb_container->get('message.form.' . $form_name); + + $form->bind($request); + $error = $form->check_allow(); + if ($error) + { + trigger_error($error); + } + + if ($request->is_set_post('submit')) + { + $messenger = new messenger(false); + $form->submit($messenger); + } + + $page_title = $form->get_page_title(); + $template_html = $form->get_template_file(); + $form->render($template); + + break; + + case 'livesearch': + + $username_chars = $request->variable('username', '', true); + + $sql = 'SELECT username, user_id, user_colour + FROM ' . USERS_TABLE . ' + WHERE ' . $db->sql_in_set('user_type', $user_types) . ' + AND username_clean ' . $db->sql_like_expression(utf8_clean_string($username_chars) . $db->get_any_char()); + $result = $db->sql_query_limit($sql, 10); + $user_list = array(); + + while ($row = $db->sql_fetchrow($result)) + { + $user_list[] = array( + 'user_id' => (int) $row['user_id'], + 'result' => $row['username'], + 'username_full' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']), + 'display' => get_username_string('no_profile', $row['user_id'], $row['username'], $row['user_colour']), + ); + } + $db->sql_freeresult($result); + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'keyword' => $username_chars, + 'results' => $user_list, + )); + + break; + + case 'group': + default: + // The basic memberlist + $page_title = $user->lang['MEMBERLIST']; + $template_html = 'memberlist_body.html'; + + /* @var $pagination \phpbb\pagination */ + $pagination = $phpbb_container->get('pagination'); + + // Sorting + $sort_key_text = array('a' => $user->lang['SORT_USERNAME'], 'c' => $user->lang['SORT_JOINED'], 'd' => $user->lang['SORT_POST_COUNT']); + $sort_key_sql = array('a' => 'u.username_clean', 'c' => 'u.user_regdate', 'd' => 'u.user_posts'); + + if ($config['jab_enable']) + { + $sort_key_text['k'] = $user->lang['JABBER']; + $sort_key_sql['k'] = 'u.user_jabber'; + } + + if ($auth->acl_get('a_user')) + { + $sort_key_text['e'] = $user->lang['SORT_EMAIL']; + $sort_key_sql['e'] = 'u.user_email'; + } + + if ($auth->acl_get('u_viewonline')) + { + $sort_key_text['l'] = $user->lang['SORT_LAST_ACTIVE']; + $sort_key_sql['l'] = 'u.user_lastvisit'; + } + + $sort_key_text['m'] = $user->lang['SORT_RANK']; + $sort_key_sql['m'] = 'u.user_rank'; + + $sort_dir_text = array('a' => $user->lang['ASCENDING'], 'd' => $user->lang['DESCENDING']); + + $s_sort_key = ''; + foreach ($sort_key_text as $key => $value) + { + $selected = ($sort_key == $key) ? ' selected="selected"' : ''; + $s_sort_key .= ''; + } + + $s_sort_dir = ''; + foreach ($sort_dir_text as $key => $value) + { + $selected = ($sort_dir == $key) ? ' selected="selected"' : ''; + $s_sort_dir .= ''; + } + + // Additional sorting options for user search ... if search is enabled, if not + // then only admins can make use of this (for ACP functionality) + $sql_select = $sql_where_data = $sql_from = $sql_where = $order_by = ''; + + + $form = $request->variable('form', ''); + $field = $request->variable('field', ''); + $select_single = $request->variable('select_single', false); + + // Search URL parameters, if any of these are in the URL we do a search + $search_params = array('username', 'email', 'jabber', 'search_group_id', 'joined_select', 'active_select', 'count_select', 'joined', 'active', 'count', 'ip'); + + // We validate form and field here, only id/class allowed + $form = (!preg_match('/^[a-z0-9_-]+$/i', $form)) ? '' : $form; + $field = (!preg_match('/^[a-z0-9_-]+$/i', $field)) ? '' : $field; + if ((($mode == '' || $mode == 'searchuser') || count(array_intersect($request->variable_names(\phpbb\request\request_interface::GET), $search_params)) > 0) && ($config['load_search'] || $auth->acl_get('a_'))) + { + $username = $request->variable('username', '', true); + $email = strtolower($request->variable('email', '')); + $jabber = $request->variable('jabber', ''); + $search_group_id = $request->variable('search_group_id', 0); + + // when using these, make sure that we actually have values defined in $find_key_match + $joined_select = $request->variable('joined_select', 'lt'); + $active_select = $request->variable('active_select', 'lt'); + $count_select = $request->variable('count_select', 'eq'); + + $joined = explode('-', $request->variable('joined', '')); + $active = explode('-', $request->variable('active', '')); + $count = ($request->variable('count', '') !== '') ? $request->variable('count', 0) : ''; + $ipdomain = $request->variable('ip', ''); + + $find_key_match = array('lt' => '<', 'gt' => '>', 'eq' => '='); + + $find_count = array('lt' => $user->lang['LESS_THAN'], 'eq' => $user->lang['EQUAL_TO'], 'gt' => $user->lang['MORE_THAN']); + $s_find_count = ''; + foreach ($find_count as $key => $value) + { + $selected = ($count_select == $key) ? ' selected="selected"' : ''; + $s_find_count .= ''; + } + + $find_time = array('lt' => $user->lang['BEFORE'], 'gt' => $user->lang['AFTER']); + $s_find_join_time = ''; + foreach ($find_time as $key => $value) + { + $selected = ($joined_select == $key) ? ' selected="selected"' : ''; + $s_find_join_time .= ''; + } + + $s_find_active_time = ''; + foreach ($find_time as $key => $value) + { + $selected = ($active_select == $key) ? ' selected="selected"' : ''; + $s_find_active_time .= ''; + } + + $sql_where .= ($username) ? ' AND u.username_clean ' . $db->sql_like_expression(str_replace('*', $db->get_any_char(), utf8_clean_string($username))) : ''; + $sql_where .= ($auth->acl_get('a_user') && $email) ? ' AND u.user_email ' . $db->sql_like_expression(str_replace('*', $db->get_any_char(), $email)) . ' ' : ''; + $sql_where .= ($jabber) ? ' AND u.user_jabber ' . $db->sql_like_expression(str_replace('*', $db->get_any_char(), $jabber)) . ' ' : ''; + $sql_where .= (is_numeric($count) && isset($find_key_match[$count_select])) ? ' AND u.user_posts ' . $find_key_match[$count_select] . ' ' . (int) $count . ' ' : ''; + + if (isset($find_key_match[$joined_select]) && count($joined) == 3) + { + $joined_time = gmmktime(0, 0, 0, (int) $joined[1], (int) $joined[2], (int) $joined[0]); + + if ($joined_time !== false) + { + $sql_where .= " AND u.user_regdate " . $find_key_match[$joined_select] . ' ' . $joined_time; + } + } + + if (isset($find_key_match[$active_select]) && count($active) == 3 && $auth->acl_get('u_viewonline')) + { + $active_time = gmmktime(0, 0, 0, (int) $active[1], (int) $active[2], (int) $active[0]); + + if ($active_time !== false) + { + $sql_where .= " AND u.user_lastvisit " . $find_key_match[$active_select] . ' ' . $active_time; + } + } + + $sql_where .= ($search_group_id) ? " AND u.user_id = ug.user_id AND ug.group_id = $search_group_id AND ug.user_pending = 0 " : ''; + + if ($search_group_id) + { + $sql_from = ', ' . USER_GROUP_TABLE . ' ug '; + } + + if ($ipdomain && $auth->acl_getf_global('m_info')) + { + if (strspn($ipdomain, 'abcdefghijklmnopqrstuvwxyz')) + { + $hostnames = gethostbynamel($ipdomain); + + if ($hostnames !== false) + { + $ips = "'" . implode('\', \'', array_map(array($db, 'sql_escape'), preg_replace('#([0-9]{1,3}\.[0-9]{1,3}[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})#', "\\1", gethostbynamel($ipdomain)))) . "'"; + } + else + { + $ips = false; + } + } + else + { + $ips = "'" . str_replace('*', '%', $db->sql_escape($ipdomain)) . "'"; + } + + if ($ips === false) + { + // A minor fudge but it does the job :D + $sql_where .= " AND u.user_id = 0"; + } + else + { + $ip_forums = array_keys($auth->acl_getf('m_info', true)); + + $sql = 'SELECT DISTINCT poster_id + FROM ' . POSTS_TABLE . ' + WHERE poster_ip ' . ((strpos($ips, '%') !== false) ? 'LIKE' : 'IN') . " ($ips) + AND " . $db->sql_in_set('forum_id', $ip_forums); + + /** + * Modify sql query for members search by ip address / hostname + * + * @event core.memberlist_modify_ip_search_sql_query + * @var string ipdomain The host name + * @var string ips IP address list for the given host name + * @var string sql The SQL query for searching members by IP address + * @since 3.1.7-RC1 + */ + $vars = array( + 'ipdomain', + 'ips', + 'sql', + ); + extract($phpbb_dispatcher->trigger_event('core.memberlist_modify_ip_search_sql_query', compact($vars))); + + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $ip_sql = array(); + do + { + $ip_sql[] = $row['poster_id']; + } + while ($row = $db->sql_fetchrow($result)); + + $sql_where .= ' AND ' . $db->sql_in_set('u.user_id', $ip_sql); + } + else + { + // A minor fudge but it does the job :D + $sql_where .= " AND u.user_id = 0"; + } + unset($ip_forums); + + $db->sql_freeresult($result); + } + } + } + + $first_char = $request->variable('first_char', ''); + + if ($first_char == 'other') + { + for ($i = 97; $i < 123; $i++) + { + $sql_where .= ' AND u.username_clean NOT ' . $db->sql_like_expression(chr($i) . $db->get_any_char()); + } + } + else if ($first_char) + { + $sql_where .= ' AND u.username_clean ' . $db->sql_like_expression(substr($first_char, 0, 1) . $db->get_any_char()); + } + + // Are we looking at a usergroup? If so, fetch additional info + // and further restrict the user info query + if ($mode == 'group') + { + // We JOIN here to save a query for determining membership for hidden groups. ;) + $sql = 'SELECT g.*, ug.user_id, ug.group_leader + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . USER_GROUP_TABLE . ' ug ON (ug.user_pending = 0 AND ug.user_id = ' . $user->data['user_id'] . " AND ug.group_id = $group_id) + WHERE g.group_id = $group_id"; + $result = $db->sql_query($sql); + $group_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$group_row) + { + trigger_error('NO_GROUP'); + } + + switch ($group_row['group_type']) + { + case GROUP_OPEN: + $group_row['l_group_type'] = 'OPEN'; + break; + + case GROUP_CLOSED: + $group_row['l_group_type'] = 'CLOSED'; + break; + + case GROUP_HIDDEN: + $group_row['l_group_type'] = 'HIDDEN'; + + // Check for membership or special permissions + if (!$auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel') && $group_row['user_id'] != $user->data['user_id']) + { + trigger_error('NO_GROUP'); + } + break; + + case GROUP_SPECIAL: + $group_row['l_group_type'] = 'SPECIAL'; + break; + + case GROUP_FREE: + $group_row['l_group_type'] = 'FREE'; + break; + } + + $avatar_img = phpbb_get_group_avatar($group_row); + + // ... same for group rank + $user_rank_data = array( + 'title' => null, + 'img' => null, + 'img_src' => null, + ); + if ($group_row['group_rank']) + { + $user_rank_data = phpbb_get_user_rank($group_row, false); + + if ($user_rank_data['img']) + { + $user_rank_data['img'] .= '
'; + } + } + // include modules for manage groups link display or not + // need to ensure the module is active + $can_manage_group = false; + if ($user->data['is_registered'] && $group_row['group_leader']) + { + if (!class_exists('p_master')) + { + include($phpbb_root_path . 'includes/functions_module.' . $phpEx); + } + $module = new p_master; + $module->list_modules('ucp'); + + if ($module->is_active('ucp_groups', 'manage')) + { + $can_manage_group = true; + } + unset($module); + } + + $template->assign_vars(array( + 'GROUP_DESC' => generate_text_for_display($group_row['group_desc'], $group_row['group_desc_uid'], $group_row['group_desc_bitfield'], $group_row['group_desc_options']), + 'GROUP_NAME' => $group_helper->get_name($group_row['group_name']), + 'GROUP_COLOR' => $group_row['group_colour'], + 'GROUP_TYPE' => $user->lang['GROUP_IS_' . $group_row['l_group_type']], + 'GROUP_RANK' => $user_rank_data['title'], + + 'AVATAR_IMG' => $avatar_img, + 'RANK_IMG' => $user_rank_data['img'], + 'RANK_IMG_SRC' => $user_rank_data['img_src'], + + 'U_PM' => ($auth->acl_get('u_sendpm') && $auth->acl_get('u_masspm_group') && $group_row['group_receive_pm'] && $config['allow_privmsg'] && $config['allow_mass_pm']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&g=' . $group_id) : '', + 'U_MANAGE' => ($can_manage_group) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_groups&mode=manage') : false,) + ); + + $sql_select = ', ug.group_leader'; + $sql_from = ', ' . USER_GROUP_TABLE . ' ug '; + $order_by = 'ug.group_leader DESC, '; + + $sql_where .= " AND ug.user_pending = 0 AND u.user_id = ug.user_id AND ug.group_id = $group_id"; + $sql_where_data = " AND u.user_id = ug.user_id AND ug.group_id = $group_id"; + } + + // Sorting and order + if (!isset($sort_key_sql[$sort_key])) + { + $sort_key = $default_key; + } + + $order_by .= $sort_key_sql[$sort_key] . ' ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'); + + // Unfortunately we must do this here for sorting by rank, else the sort order is applied wrongly + if ($sort_key == 'm') + { + $order_by .= ', u.user_posts DESC'; + } + + /** + * Modify sql query data for members search + * + * @event core.memberlist_modify_sql_query_data + * @var string order_by SQL ORDER BY clause condition + * @var string sort_dir The sorting direction + * @var string sort_key The sorting key + * @var array sort_key_sql Arraty with the sorting conditions data + * @var string sql_from SQL FROM clause condition + * @var string sql_select SQL SELECT fields list + * @var string sql_where SQL WHERE clause condition + * @var string sql_where_data SQL WHERE clause additional conditions data + * @since 3.1.7-RC1 + */ + $vars = array( + 'order_by', + 'sort_dir', + 'sort_key', + 'sort_key_sql', + 'sql_from', + 'sql_select', + 'sql_where', + 'sql_where_data', + ); + extract($phpbb_dispatcher->trigger_event('core.memberlist_modify_sql_query_data', compact($vars))); + + // Count the users ... + $sql = 'SELECT COUNT(u.user_id) AS total_users + FROM ' . USERS_TABLE . " u$sql_from + WHERE " . $db->sql_in_set('u.user_type', $user_types) . " + $sql_where"; + $result = $db->sql_query($sql); + $total_users = (int) $db->sql_fetchfield('total_users'); + $db->sql_freeresult($result); + + // Build a relevant pagination_url + $params = $sort_params = array(); + + // We do not use $request->variable() here directly to save some calls (not all variables are set) + $check_params = array( + 'g' => array('g', 0), + 'sk' => array('sk', $default_key), + 'sd' => array('sd', 'a'), + 'form' => array('form', ''), + 'field' => array('field', ''), + 'select_single' => array('select_single', $select_single), + 'username' => array('username', '', true), + 'email' => array('email', ''), + 'jabber' => array('jabber', ''), + 'search_group_id' => array('search_group_id', 0), + 'joined_select' => array('joined_select', 'lt'), + 'active_select' => array('active_select', 'lt'), + 'count_select' => array('count_select', 'eq'), + 'joined' => array('joined', ''), + 'active' => array('active', ''), + 'count' => ($request->variable('count', '') !== '') ? array('count', 0) : array('count', ''), + 'ip' => array('ip', ''), + 'first_char' => array('first_char', ''), + ); + + $u_first_char_params = array(); + foreach ($check_params as $key => $call) + { + if (!isset($_REQUEST[$key])) + { + continue; + } + + $param = call_user_func_array(array($request, 'variable'), $call); + // Encode strings, convert everything else to int in order to prevent empty parameters. + $param = urlencode($key) . '=' . ((is_string($param)) ? urlencode($param) : (int) $param); + $params[] = $param; + + if ($key != 'first_char') + { + $u_first_char_params[] = $param; + } + if ($key != 'sk' && $key != 'sd') + { + $sort_params[] = $param; + } + } + + $u_hide_find_member = append_sid("{$phpbb_root_path}memberlist.$phpEx", "start=$start" . (!empty($params) ? '&' . implode('&', $params) : '')); + + if ($mode) + { + $params[] = "mode=$mode"; + $u_first_char_params[] = "mode=$mode"; + } + $sort_params[] = "mode=$mode"; + + $u_first_char_params = implode('&', $u_first_char_params); + $u_first_char_params .= ($u_first_char_params) ? '&' : ''; + + $first_characters = array(); + $first_characters[''] = $user->lang['ALL']; + for ($i = 97; $i < 123; $i++) + { + $first_characters[chr($i)] = chr($i - 32); + } + $first_characters['other'] = $user->lang['OTHER']; + + $first_char_block_vars = []; + + foreach ($first_characters as $char => $desc) + { + $first_char_block_vars[] = [ + 'DESC' => $desc, + 'VALUE' => $char, + 'S_SELECTED' => ($first_char == $char) ? true : false, + 'U_SORT' => append_sid("{$phpbb_root_path}memberlist.$phpEx", $u_first_char_params . 'first_char=' . $char) . '#memberlist', + ]; + } + + /** + * Modify memberlist sort and pagination parameters + * + * @event core.memberlist_modify_sort_pagination_params + * @var array sort_params Array with URL parameters for sorting + * @var array params Array with URL parameters for pagination + * @var array first_characters Array that maps each letter in a-z, 'other' and the empty string to their display representation + * @var string u_first_char_params Concatenated URL parameters for first character search links + * @var array first_char_block_vars Template block variables for each first character + * @var int total_users Total number of users found in this search + * @since 3.2.6-RC1 + */ + $vars = [ + 'sort_params', + 'params', + 'first_characters', + 'u_first_char_params', + 'first_char_block_vars', + 'total_users', + ]; + extract($phpbb_dispatcher->trigger_event('core.memberlist_modify_sort_pagination_params', compact($vars))); + + $template->assign_block_vars_array('first_char', $first_char_block_vars); + + $pagination_url = append_sid("{$phpbb_root_path}memberlist.$phpEx", implode('&', $params)); + $sort_url = append_sid("{$phpbb_root_path}memberlist.$phpEx", implode('&', $sort_params)); + + unset($search_params, $sort_params); + + // Some search user specific data + if (($mode == '' || $mode == 'searchuser') && ($config['load_search'] || $auth->acl_get('a_'))) + { + $group_selected = $request->variable('search_group_id', 0); + $s_group_select = ''; + $group_ids = array(); + + /** + * @todo add this to a separate function (function is responsible for returning the groups the user is able to see based on the users group membership) + */ + + if ($auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) + { + $sql = 'SELECT group_id, group_name, group_type + FROM ' . GROUPS_TABLE; + + if (!$config['coppa_enable']) + { + $sql .= " WHERE group_name <> 'REGISTERED_COPPA'"; + } + + $sql .= ' ORDER BY group_name ASC'; + } + else + { + $sql = 'SELECT g.group_id, g.group_name, g.group_type + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . USER_GROUP_TABLE . ' ug + ON ( + g.group_id = ug.group_id + AND ug.user_id = ' . $user->data['user_id'] . ' + AND ug.user_pending = 0 + ) + WHERE (g.group_type <> ' . GROUP_HIDDEN . ' OR ug.user_id = ' . $user->data['user_id'] . ')'; + + if (!$config['coppa_enable']) + { + $sql .= " AND g.group_name <> 'REGISTERED_COPPA'"; + } + + $sql .= ' ORDER BY g.group_name ASC'; + } + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $group_ids[] = $row['group_id']; + $s_group_select .= ''; + } + $db->sql_freeresult($result); + + if ($group_selected !== 0 && !in_array($group_selected, $group_ids)) + { + trigger_error('NO_GROUP'); + } + + $template->assign_vars(array( + 'USERNAME' => $username, + 'EMAIL' => $email, + 'JABBER' => $jabber, + 'JOINED' => implode('-', $joined), + 'ACTIVE' => implode('-', $active), + 'COUNT' => $count, + 'IP' => $ipdomain, + + 'S_IP_SEARCH_ALLOWED' => ($auth->acl_getf_global('m_info')) ? true : false, + 'S_EMAIL_SEARCH_ALLOWED'=> ($auth->acl_get('a_user')) ? true : false, + 'S_JABBER_ENABLED' => $config['jab_enable'], + 'S_IN_SEARCH_POPUP' => ($form && $field) ? true : false, + 'S_SEARCH_USER' => ($mode == 'searchuser' || ($mode == '' && $submit)), + 'S_FORM_NAME' => $form, + 'S_FIELD_NAME' => $field, + 'S_SELECT_SINGLE' => $select_single, + 'S_COUNT_OPTIONS' => $s_find_count, + 'S_SORT_OPTIONS' => $s_sort_key, + 'S_JOINED_TIME_OPTIONS' => $s_find_join_time, + 'S_ACTIVE_TIME_OPTIONS' => $s_find_active_time, + 'S_GROUP_SELECT' => $s_group_select, + 'S_USER_SEARCH_ACTION' => append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=searchuser&form=$form&field=$field")) + ); + } + + $start = $pagination->validate_start($start, $config['topics_per_page'], $total_users); + + // Get us some users :D + $sql = "SELECT u.user_id + FROM " . USERS_TABLE . " u + $sql_from + WHERE " . $db->sql_in_set('u.user_type', $user_types) . " + $sql_where + ORDER BY $order_by"; + $result = $db->sql_query_limit($sql, $config['topics_per_page'], $start); + + $user_list = array(); + while ($row = $db->sql_fetchrow($result)) + { + $user_list[] = (int) $row['user_id']; + } + $db->sql_freeresult($result); + + // Load custom profile fields + if ($config['load_cpf_memberlist']) + { + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + + $cp_row = $cp->generate_profile_fields_template_headlines('field_show_on_ml'); + foreach ($cp_row as $profile_field) + { + $template->assign_block_vars('custom_fields', $profile_field); + } + } + + $leaders_set = false; + // So, did we get any users? + if (count($user_list)) + { + // Session time?! Session time... + $sql = 'SELECT session_user_id, MAX(session_time) AS session_time + FROM ' . SESSIONS_TABLE . ' + WHERE session_time >= ' . (time() - $config['session_length']) . ' + AND ' . $db->sql_in_set('session_user_id', $user_list) . ' + GROUP BY session_user_id'; + $result = $db->sql_query($sql); + + $session_times = array(); + while ($row = $db->sql_fetchrow($result)) + { + $session_times[$row['session_user_id']] = $row['session_time']; + } + $db->sql_freeresult($result); + + // Do the SQL thang + if ($mode == 'group') + { + $sql_from_ary = explode(',', $sql_from); + $extra_tables = []; + foreach ($sql_from_ary as $entry) + { + $table_data = explode(' ', trim($entry)); + + if (empty($table_data[0]) || empty($table_data[1])) + { + continue; + } + + $extra_tables[$table_data[0]] = $table_data[1]; + } + + $sql_array = array( + 'SELECT' => 'u.*' . $sql_select, + 'FROM' => array_merge([USERS_TABLE => 'u'], $extra_tables), + 'WHERE' => $db->sql_in_set('u.user_id', $user_list) . $sql_where_data . '', + ); + } + else + { + $sql_array = array( + 'SELECT' => 'u.*', + 'FROM' => array( + USERS_TABLE => 'u' + ), + 'WHERE' => $db->sql_in_set('u.user_id', $user_list), + ); + } + + /** + * Modify user data SQL before member row is created + * + * @event core.memberlist_modify_memberrow_sql + * @var string mode Memberlist mode + * @var string sql_select Additional select statement + * @var string sql_from Additional from statement + * @var array sql_array Array containing the main query + * @var array user_list Array containing list of users + * @since 3.2.6-RC1 + */ + $vars = array( + 'mode', + 'sql_select', + 'sql_from', + 'sql_array', + 'user_list', + ); + extract($phpbb_dispatcher->trigger_event('core.memberlist_modify_memberrow_sql', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + + $id_cache = array(); + while ($row = $db->sql_fetchrow($result)) + { + $row['session_time'] = (!empty($session_times[$row['user_id']])) ? $session_times[$row['user_id']] : 0; + $row['last_visit'] = (!empty($row['session_time'])) ? $row['session_time'] : $row['user_lastvisit']; + + $id_cache[$row['user_id']] = $row; + } + + $db->sql_freeresult($result); + + // Load custom profile fields if required + if ($config['load_cpf_memberlist']) + { + // Grab all profile fields from users in id cache for later use - similar to the poster cache + $profile_fields_cache = $cp->grab_profile_fields_data($user_list); + + // Filter the fields we don't want to show + foreach ($profile_fields_cache as $user_id => $user_profile_fields) + { + foreach ($user_profile_fields as $field_ident => $profile_field) + { + if (!$profile_field['data']['field_show_on_ml']) + { + unset($profile_fields_cache[$user_id][$field_ident]); + } + } + } + } + + // If we sort by last active date we need to adjust the id cache due to user_lastvisit not being the last active date... + if ($sort_key == 'l') + { +// uasort($id_cache, create_function('$first, $second', "return (\$first['last_visit'] == \$second['last_visit']) ? 0 : ((\$first['last_visit'] < \$second['last_visit']) ? $lesser_than : ($lesser_than * -1));")); + usort($user_list, 'phpbb_sort_last_active'); + } + + // do we need to display contact fields as such + $use_contact_fields = false; + + /** + * Modify list of users before member row is created + * + * @event core.memberlist_memberrow_before + * @var array user_list Array containing list of users + * @var bool use_contact_fields Should we display contact fields as such? + * @since 3.1.7-RC1 + */ + $vars = array('user_list', 'use_contact_fields'); + extract($phpbb_dispatcher->trigger_event('core.memberlist_memberrow_before', compact($vars))); + + for ($i = 0, $end = count($user_list); $i < $end; ++$i) + { + $user_id = $user_list[$i]; + $row = $id_cache[$user_id]; + $is_leader = (isset($row['group_leader']) && $row['group_leader']) ? true : false; + $leaders_set = ($leaders_set || $is_leader); + + $cp_row = array(); + if ($config['load_cpf_memberlist']) + { + $cp_row = (isset($profile_fields_cache[$user_id])) ? $cp->generate_profile_fields_template_data($profile_fields_cache[$user_id], $use_contact_fields) : array(); + } + + $memberrow = array_merge(phpbb_show_profile($row, false, false, false), array( + 'ROW_NUMBER' => $i + ($start + 1), + + 'S_CUSTOM_PROFILE' => (isset($cp_row['row']) && count($cp_row['row'])) ? true : false, + 'S_GROUP_LEADER' => $is_leader, + 'S_INACTIVE' => $row['user_type'] == USER_INACTIVE, + + 'U_VIEW_PROFILE' => get_username_string('profile', $user_id, $row['username']), + )); + + if (isset($cp_row['row']) && count($cp_row['row'])) + { + $memberrow = array_merge($memberrow, $cp_row['row']); + } + + $template->assign_block_vars('memberrow', $memberrow); + + if (isset($cp_row['blockrow']) && count($cp_row['blockrow'])) + { + foreach ($cp_row['blockrow'] as $field_data) + { + $template->assign_block_vars('memberrow.custom_fields', $field_data); + } + } + + unset($id_cache[$user_id]); + } + } + + $pagination->generate_template_pagination($pagination_url, 'pagination', 'start', $total_users, $config['topics_per_page'], $start); + + // Generate page + $template_vars = array( + 'TOTAL_USERS' => $user->lang('LIST_USERS', (int) $total_users), + + 'PROFILE_IMG' => $user->img('icon_user_profile', $user->lang['PROFILE']), + 'PM_IMG' => $user->img('icon_contact_pm', $user->lang['SEND_PRIVATE_MESSAGE']), + 'EMAIL_IMG' => $user->img('icon_contact_email', $user->lang['EMAIL']), + 'JABBER_IMG' => $user->img('icon_contact_jabber', $user->lang['JABBER']), + 'SEARCH_IMG' => $user->img('icon_user_search', $user->lang['SEARCH']), + + 'U_FIND_MEMBER' => ($config['load_search'] || $auth->acl_get('a_')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser' . (($start) ? "&start=$start" : '') . (!empty($params) ? '&' . implode('&', $params) : '')) : '', + 'U_HIDE_FIND_MEMBER' => ($mode == 'searchuser' || ($mode == '' && $submit)) ? $u_hide_find_member : '', + 'U_LIVE_SEARCH' => ($config['allow_live_searches']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=livesearch') : false, + 'U_SORT_USERNAME' => $sort_url . '&sk=a&sd=' . (($sort_key == 'a' && $sort_dir == 'a') ? 'd' : 'a'), + 'U_SORT_JOINED' => $sort_url . '&sk=c&sd=' . (($sort_key == 'c' && $sort_dir == 'd') ? 'a' : 'd'), + 'U_SORT_POSTS' => $sort_url . '&sk=d&sd=' . (($sort_key == 'd' && $sort_dir == 'd') ? 'a' : 'd'), + 'U_SORT_EMAIL' => $sort_url . '&sk=e&sd=' . (($sort_key == 'e' && $sort_dir == 'd') ? 'a' : 'd'), + 'U_SORT_ACTIVE' => ($auth->acl_get('u_viewonline')) ? $sort_url . '&sk=l&sd=' . (($sort_key == 'l' && $sort_dir == 'd') ? 'a' : 'd') : '', + 'U_SORT_RANK' => $sort_url . '&sk=m&sd=' . (($sort_key == 'm' && $sort_dir == 'd') ? 'a' : 'd'), + 'U_LIST_CHAR' => $sort_url . '&sk=a&sd=' . (($sort_key == 'l' && $sort_dir == 'd') ? 'a' : 'd'), + + 'S_SHOW_GROUP' => ($mode == 'group') ? true : false, + 'S_VIEWONLINE' => $auth->acl_get('u_viewonline'), + 'S_LEADERS_SET' => $leaders_set, + 'S_MODE_SELECT' => $s_sort_key, + 'S_ORDER_SELECT' => $s_sort_dir, + 'S_MODE_ACTION' => $pagination_url, + ); + + /** + * Modify memberlist page template vars + * + * @event core.memberlist_modify_template_vars + * @var array params Array containing URL parameters + * @var string sort_url Sorting URL base + * @var array template_vars Array containing template vars + * @since 3.2.2-RC1 + */ + $vars = array('params', 'sort_url', 'template_vars'); + extract($phpbb_dispatcher->trigger_event('core.memberlist_modify_template_vars', compact($vars))); + + $template->assign_vars($template_vars); +} + +// Output the page +page_header($page_title); + +$template->set_filenames(array( + 'body' => $template_html) +); +make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx")); + +page_footer(); diff --git a/phpbb/attachment/delete.php b/phpbb/attachment/delete.php new file mode 100644 index 0000000..3c98e21 --- /dev/null +++ b/phpbb/attachment/delete.php @@ -0,0 +1,480 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\attachment; + +use \phpbb\config\config; +use \phpbb\db\driver\driver_interface; +use \phpbb\event\dispatcher; +use \phpbb\filesystem\filesystem; + +/** + * Attachment delete class + */ +class delete +{ + /** @var config */ + protected $config; + + /** @var driver_interface */ + protected $db; + + /** @var dispatcher */ + protected $dispatcher; + + /** @var filesystem */ + protected $filesystem; + + /** @var resync */ + protected $resync; + + /** @var string phpBB root path */ + protected $phpbb_root_path; + + /** @var array Attachement IDs */ + protected $ids; + + /** @var string SQL ID string */ + private $sql_id; + + /** @var string SQL where string */ + private $sql_where = ''; + + /** @var int Number of deleted items */ + private $num_deleted; + + /** @var array Post IDs */ + private $post_ids = array(); + + /** @var array Message IDs */ + private $message_ids = array(); + + /** @var array Topic IDs */ + private $topic_ids = array(); + + /** @var array Info of physical file */ + private $physical = array(); + + /** + * Attachment delete class constructor + * + * @param config $config + * @param driver_interface $db + * @param dispatcher $dispatcher + * @param filesystem $filesystem + * @param resync $resync + * @param string $phpbb_root_path + */ + public function __construct(config $config, driver_interface $db, dispatcher $dispatcher, filesystem $filesystem, resync $resync, $phpbb_root_path) + { + $this->config = $config; + $this->db = $db; + $this->dispatcher = $dispatcher; + $this->filesystem = $filesystem; + $this->resync = $resync; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Delete Attachments + * + * @param string $mode can be: post|message|topic|attach|user + * @param mixed $ids can be: post_ids, message_ids, topic_ids, attach_ids, user_ids + * @param bool $resync set this to false if you are deleting posts or topics + * + * @return int|bool Number of deleted attachments or false if something + * went wrong during attachment deletion + */ + public function delete($mode, $ids, $resync = true) + { + if (!$this->set_attachment_ids($ids)) + { + return false; + } + + $this->set_sql_constraints($mode); + + $sql_id = $this->sql_id; + + /** + * Perform additional actions before collecting data for attachment(s) deletion + * + * @event core.delete_attachments_collect_data_before + * @var string mode Variable containing attachments deletion mode, can be: post|message|topic|attach|user + * @var mixed ids Array or comma separated list of ids corresponding to the mode + * @var bool resync Flag indicating if posts/messages/topics should be synchronized + * @var string sql_id The field name to collect/delete data for depending on the mode + * @since 3.1.7-RC1 + */ + $vars = array( + 'mode', + 'ids', + 'resync', + 'sql_id', + ); + extract($this->dispatcher->trigger_event('core.delete_attachments_collect_data_before', compact($vars))); + + $this->sql_id = $sql_id; + unset($sql_id); + + // Collect post and topic ids for later use if we need to touch remaining entries (if resync is enabled) + $this->collect_attachment_info($resync); + + // Delete attachments from database + $this->delete_attachments_from_db($mode, $ids, $resync); + + $sql_id = $this->sql_id; + $post_ids = $this->post_ids; + $topic_ids = $this->topic_ids; + $message_ids = $this->message_ids; + $physical = $this->physical; + $num_deleted = $this->num_deleted; + + /** + * Perform additional actions after attachment(s) deletion from the database + * + * @event core.delete_attachments_from_database_after + * @var string mode Variable containing attachments deletion mode, can be: post|message|topic|attach|user + * @var mixed ids Array or comma separated list of ids corresponding to the mode + * @var bool resync Flag indicating if posts/messages/topics should be synchronized + * @var string sql_id The field name to collect/delete data for depending on the mode + * @var array post_ids Array with post ids for deleted attachment(s) + * @var array topic_ids Array with topic ids for deleted attachment(s) + * @var array message_ids Array with private message ids for deleted attachment(s) + * @var array physical Array with deleted attachment(s) physical file(s) data + * @var int num_deleted The number of deleted attachment(s) from the database + * @since 3.1.7-RC1 + */ + $vars = array( + 'mode', + 'ids', + 'resync', + 'sql_id', + 'post_ids', + 'topic_ids', + 'message_ids', + 'physical', + 'num_deleted', + ); + extract($this->dispatcher->trigger_event('core.delete_attachments_from_database_after', compact($vars))); + + $this->sql_id = $sql_id; + $this->post_ids = $post_ids; + $this->topic_ids = $topic_ids; + $this->message_ids = $message_ids; + $this->physical = $physical; + $this->num_deleted = $num_deleted; + unset($sql_id, $post_ids, $topic_ids, $message_ids, $physical, $num_deleted); + + if (!$this->num_deleted) + { + return 0; + } + + // Delete attachments from filesystem + $this->remove_from_filesystem($mode, $ids, $resync); + + // If we do not resync, we do not need to adjust any message, post, topic or user entries + if (!$resync) + { + return $this->num_deleted; + } + + // No more use for the original ids + unset($ids); + + // Update post indicators for posts now no longer having attachments + $this->resync->resync('post', $this->post_ids); + + // Update message table if messages are affected + $this->resync->resync('message', $this->message_ids); + + // Now update the topics. This is a bit trickier, because there could be posts still having attachments within the topic + $this->resync->resync('topic', $this->topic_ids); + + return $this->num_deleted; + } + + /** + * Set attachment IDs + * + * @param mixed $ids ID or array of IDs + * + * @return bool True if attachment IDs were set, false if not + */ + protected function set_attachment_ids($ids) + { + // 0 is as bad as an empty array + if (empty($ids)) + { + return false; + } + + if (is_array($ids)) + { + $ids = array_unique($ids); + $this->ids = array_map('intval', $ids); + } + else + { + $this->ids = array((int) $ids); + } + + return true; + } + + /** + * Set SQL constraints based on mode + * + * @param string $mode Delete mode; can be: post|message|topic|attach|user + */ + private function set_sql_constraints($mode) + { + switch ($mode) + { + case 'post': + case 'message': + $this->sql_id = 'post_msg_id'; + $this->sql_where = ' AND in_message = ' . ($mode == 'message' ? 1 : 0); + break; + + case 'topic': + $this->sql_id = 'topic_id'; + break; + + case 'user': + $this->sql_id = 'poster_id'; + break; + + case 'attach': + default: + $this->sql_id = 'attach_id'; + break; + } + } + + /** + * Collect info about attachment IDs + * + * @param bool $resync Whether topics/posts should be resynced after delete + */ + protected function collect_attachment_info($resync) + { + // Collect post and topic ids for later use if we need to touch remaining entries (if resync is enabled) + $sql = 'SELECT post_msg_id, topic_id, in_message, physical_filename, thumbnail, filesize, is_orphan + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $this->db->sql_in_set($this->sql_id, $this->ids); + + $sql .= $this->sql_where; + + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // We only need to store post/message/topic ids if resync is enabled and the file is not orphaned + if ($resync && !$row['is_orphan']) + { + if (!$row['in_message']) + { + $this->post_ids[] = $row['post_msg_id']; + $this->topic_ids[] = $row['topic_id']; + } + else + { + $this->message_ids[] = $row['post_msg_id']; + } + } + + $this->physical[] = array('filename' => $row['physical_filename'], 'thumbnail' => $row['thumbnail'], 'filesize' => $row['filesize'], 'is_orphan' => $row['is_orphan']); + } + $this->db->sql_freeresult($result); + + // IDs should be unique + $this->post_ids = array_unique($this->post_ids); + $this->message_ids = array_unique($this->message_ids); + $this->topic_ids = array_unique($this->topic_ids); + } + + /** + * Delete attachments from database table + */ + protected function delete_attachments_from_db($mode, $ids, $resync) + { + $sql_id = $this->sql_id; + $post_ids = $this->post_ids; + $topic_ids = $this->topic_ids; + $message_ids = $this->message_ids; + $physical = $this->physical; + + /** + * Perform additional actions before attachment(s) deletion + * + * @event core.delete_attachments_before + * @var string mode Variable containing attachments deletion mode, can be: post|message|topic|attach|user + * @var mixed ids Array or comma separated list of ids corresponding to the mode + * @var bool resync Flag indicating if posts/messages/topics should be synchronized + * @var string sql_id The field name to collect/delete data for depending on the mode + * @var array post_ids Array with post ids for deleted attachment(s) + * @var array topic_ids Array with topic ids for deleted attachment(s) + * @var array message_ids Array with private message ids for deleted attachment(s) + * @var array physical Array with deleted attachment(s) physical file(s) data + * @since 3.1.7-RC1 + */ + $vars = array( + 'mode', + 'ids', + 'resync', + 'sql_id', + 'post_ids', + 'topic_ids', + 'message_ids', + 'physical', + ); + extract($this->dispatcher->trigger_event('core.delete_attachments_before', compact($vars))); + + $this->sql_id = $sql_id; + $this->post_ids = $post_ids; + $this->topic_ids = $topic_ids; + $this->message_ids = $message_ids; + $this->physical = $physical; + unset($sql_id, $post_ids, $topic_ids, $message_ids, $physical); + + // Delete attachments + $sql = 'DELETE FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $this->db->sql_in_set($this->sql_id, $this->ids); + + $sql .= $this->sql_where; + + $this->db->sql_query($sql); + $this->num_deleted = $this->db->sql_affectedrows(); + } + + /** + * Delete attachments from filesystem + */ + protected function remove_from_filesystem($mode, $ids, $resync) + { + $space_removed = $files_removed = 0; + + foreach ($this->physical as $file_ary) + { + if ($this->unlink_attachment($file_ary['filename'], 'file', true) && !$file_ary['is_orphan']) + { + // Only non-orphaned files count to the file size + $space_removed += $file_ary['filesize']; + $files_removed++; + } + + if ($file_ary['thumbnail']) + { + $this->unlink_attachment($file_ary['filename'], 'thumbnail', true); + } + } + + $sql_id = $this->sql_id; + $post_ids = $this->post_ids; + $topic_ids = $this->topic_ids; + $message_ids = $this->message_ids; + $physical = $this->physical; + $num_deleted = $this->num_deleted; + + /** + * Perform additional actions after attachment(s) deletion from the filesystem + * + * @event core.delete_attachments_from_filesystem_after + * @var string mode Variable containing attachments deletion mode, can be: post|message|topic|attach|user + * @var mixed ids Array or comma separated list of ids corresponding to the mode + * @var bool resync Flag indicating if posts/messages/topics should be synchronized + * @var string sql_id The field name to collect/delete data for depending on the mode + * @var array post_ids Array with post ids for deleted attachment(s) + * @var array topic_ids Array with topic ids for deleted attachment(s) + * @var array message_ids Array with private message ids for deleted attachment(s) + * @var array physical Array with deleted attachment(s) physical file(s) data + * @var int num_deleted The number of deleted attachment(s) from the database + * @var int space_removed The size of deleted files(s) from the filesystem + * @var int files_removed The number of deleted file(s) from the filesystem + * @since 3.1.7-RC1 + */ + $vars = array( + 'mode', + 'ids', + 'resync', + 'sql_id', + 'post_ids', + 'topic_ids', + 'message_ids', + 'physical', + 'num_deleted', + 'space_removed', + 'files_removed', + ); + extract($this->dispatcher->trigger_event('core.delete_attachments_from_filesystem_after', compact($vars))); + + $this->sql_id = $sql_id; + $this->post_ids = $post_ids; + $this->topic_ids = $topic_ids; + $this->message_ids = $message_ids; + $this->physical = $physical; + $this->num_deleted = $num_deleted; + unset($sql_id, $post_ids, $topic_ids, $message_ids, $physical, $num_deleted); + + if ($space_removed || $files_removed) + { + $this->config->increment('upload_dir_size', $space_removed * (-1), false); + $this->config->increment('num_files', $files_removed * (-1), false); + } + } + + /** + * Delete attachment from filesystem + * + * @param string $filename Filename of attachment + * @param string $mode Delete mode + * @param bool $entry_removed Whether entry was removed. Defaults to false + * @return bool True if file was removed, false if not + */ + public function unlink_attachment($filename, $mode = 'file', $entry_removed = false) + { + // Because of copying topics or modifications a physical filename could be assigned more than once. If so, do not remove the file itself. + $sql = 'SELECT COUNT(attach_id) AS num_entries + FROM ' . ATTACHMENTS_TABLE . " + WHERE physical_filename = '" . $this->db->sql_escape(utf8_basename($filename)) . "'"; + $result = $this->db->sql_query($sql); + $num_entries = (int) $this->db->sql_fetchfield('num_entries'); + $this->db->sql_freeresult($result); + + // Do not remove file if at least one additional entry with the same name exist. + if (($entry_removed && $num_entries > 0) || (!$entry_removed && $num_entries > 1)) + { + return false; + } + + $filename = ($mode == 'thumbnail') ? 'thumb_' . utf8_basename($filename) : utf8_basename($filename); + $filepath = $this->phpbb_root_path . $this->config['upload_path'] . '/' . $filename; + + try + { + if ($this->filesystem->exists($filepath)) + { + $this->filesystem->remove($this->phpbb_root_path . $this->config['upload_path'] . '/' . $filename); + return true; + } + } + catch (\phpbb\filesystem\exception\filesystem_exception $exception) + { + // Fail is covered by return statement below + } + + return false; + } +} diff --git a/phpbb/attachment/manager.php b/phpbb/attachment/manager.php new file mode 100644 index 0000000..3c47171 --- /dev/null +++ b/phpbb/attachment/manager.php @@ -0,0 +1,99 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\attachment; + +/** + * Attachment manager + */ +class manager +{ + /** @var delete Attachment delete class */ + protected $delete; + + /** @var resync Attachment resync class */ + protected $resync; + + /** @var upload Attachment upload class */ + protected $upload; + + /** + * Constructor for attachment manager + * + * @param delete $delete Attachment delete class + * @param resync $resync Attachment resync class + * @param upload $upload Attachment upload class + */ + public function __construct(delete $delete, resync $resync, upload $upload) + { + $this->delete = $delete; + $this->resync = $resync; + $this->upload = $upload; + } + + /** + * Wrapper method for deleting attachments + * + * @param string $mode can be: post|message|topic|attach|user + * @param mixed $ids can be: post_ids, message_ids, topic_ids, attach_ids, user_ids + * @param bool $resync set this to false if you are deleting posts or topics + * + * @return int|bool Number of deleted attachments or false if something + * went wrong during attachment deletion + */ + public function delete($mode, $ids, $resync = true) + { + return $this->delete->delete($mode, $ids, $resync); + } + + /** + * Wrapper method for deleting attachments from filesystem + * + * @param string $filename Filename of attachment + * @param string $mode Delete mode + * @param bool $entry_removed Whether entry was removed. Defaults to false + * @return bool True if file was removed, false if not + */ + public function unlink($filename, $mode = 'file', $entry_removed = false) + { + return $this->delete->unlink_attachment($filename, $mode, $entry_removed); + } + + /** + * Wrapper method for resyncing specified type + * + * @param string $type Type of resync + * @param array $ids IDs to resync + */ + public function resync($type, $ids) + { + $this->resync->resync($type, $ids); + } + + /** + * Wrapper method for uploading attachment + * + * @param string $form_name The form name of the file upload input + * @param int $forum_id The id of the forum + * @param bool $local Whether the file is local or not + * @param string $local_storage The path to the local file + * @param bool $is_message Whether it is a PM or not + * @param array $local_filedata An file data object created for the local file + * + * @return array File data array + */ + public function upload($form_name, $forum_id, $local = false, $local_storage = '', $is_message = false, $local_filedata = []) + { + return $this->upload->upload($form_name, $forum_id, $local, $local_storage, $is_message, $local_filedata); + } +} diff --git a/phpbb/attachment/resync.php b/phpbb/attachment/resync.php new file mode 100644 index 0000000..aeacf82 --- /dev/null +++ b/phpbb/attachment/resync.php @@ -0,0 +1,124 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\attachment; + +use \phpbb\db\driver\driver_interface; + +/** + * Attachment resync class + */ +class resync +{ + /** @var driver_interface */ + protected $db; + + /** @var string Attachment table SQL ID */ + private $attach_sql_id; + + /** @var string Resync table SQL ID */ + private $resync_sql_id; + + /** @var string Resync SQL table */ + private $resync_table; + + /** @var string SQL where statement */ + private $sql_where; + + /** + * Constructor for attachment resync class + * + * @param driver_interface $db Database driver + */ + public function __construct(driver_interface $db) + { + $this->db = $db; + } + + /** + * Set type constraints for attachment resync + * + * @param string $type Type of resync; can be: message|post|topic + */ + protected function set_type_constraints($type) + { + switch ($type) + { + case 'message': + $this->attach_sql_id = 'post_msg_id'; + $this->sql_where = ' AND in_message = 1 + AND is_orphan = 0'; + $this->resync_table = PRIVMSGS_TABLE; + $this->resync_sql_id = 'msg_id'; + break; + + case 'post': + $this->attach_sql_id = 'post_msg_id'; + $this->sql_where = ' AND in_message = 0 + AND is_orphan = 0'; + $this->resync_table = POSTS_TABLE; + $this->resync_sql_id = 'post_id'; + break; + + case 'topic': + $this->attach_sql_id = 'topic_id'; + $this->sql_where = ' AND is_orphan = 0'; + $this->resync_table = TOPICS_TABLE; + $this->resync_sql_id = 'topic_id'; + break; + } + } + + /** + * Resync specified type + * + * @param string $type Type of resync + * @param array $ids IDs to resync + */ + public function resync($type, $ids) + { + if (empty($type) || !is_array($ids) || !count($ids) || !in_array($type, array('post', 'topic', 'message'))) + { + return; + } + + $this->set_type_constraints($type); + + // Just check which elements are still having an assigned attachment + // not orphaned by querying the attachments table + $sql = 'SELECT ' . $this->attach_sql_id . ' + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $this->db->sql_in_set($this->attach_sql_id, $ids) + . $this->sql_where; + $result = $this->db->sql_query($sql); + + $remaining_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $remaining_ids[] = $row[$this->attach_sql_id]; + } + $this->db->sql_freeresult($result); + + // Now only unset those ids remaining + $ids = array_diff($ids, $remaining_ids); + + if (count($ids)) + { + $sql = 'UPDATE ' . $this->resync_table . ' + SET ' . $type . '_attachment = 0 + WHERE ' . $this->db->sql_in_set($this->resync_sql_id, $ids); + $this->db->sql_query($sql); + } + } + +} diff --git a/phpbb/attachment/upload.php b/phpbb/attachment/upload.php new file mode 100644 index 0000000..b9d3205 --- /dev/null +++ b/phpbb/attachment/upload.php @@ -0,0 +1,334 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\attachment; + +use phpbb\auth\auth; +use \phpbb\cache\service; +use \phpbb\config\config; +use \phpbb\event\dispatcher; +use \phpbb\language\language; +use \phpbb\mimetype\guesser; +use \phpbb\plupload\plupload; +use \phpbb\user; + +/** + * Attachment upload class + */ +class upload +{ + /** @var auth */ + protected $auth; + + /** @var service */ + protected $cache; + + /** @var config */ + protected $config; + + /** @var \phpbb\files\upload Upload class */ + protected $files_upload; + + /** @var language */ + protected $language; + + /** @var guesser Mimetype guesser */ + protected $mimetype_guesser; + + /** @var dispatcher */ + protected $phpbb_dispatcher; + + /** @var plupload Plupload */ + protected $plupload; + + /** @var user */ + protected $user; + + /** @var \phpbb\files\filespec Current filespec instance */ + private $file; + + /** @var array File data */ + private $file_data = array( + 'error' => array() + ); + + /** @var array Extensions array */ + private $extensions; + + /** + * Constructor for attachments upload class + * + * @param auth $auth + * @param service $cache + * @param config $config + * @param \phpbb\files\upload $files_upload + * @param language $language + * @param guesser $mimetype_guesser + * @param dispatcher $phpbb_dispatcher + * @param plupload $plupload + * @param user $user + * @param $phpbb_root_path + */ + public function __construct(auth $auth, service $cache, config $config, \phpbb\files\upload $files_upload, language $language, guesser $mimetype_guesser, dispatcher $phpbb_dispatcher, plupload $plupload, user $user, $phpbb_root_path) + { + $this->auth = $auth; + $this->cache = $cache; + $this->config = $config; + $this->files_upload = $files_upload; + $this->language = $language; + $this->mimetype_guesser = $mimetype_guesser; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->plupload = $plupload; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Upload Attachment - filedata is generated here + * Uses upload class + * + * @param string $form_name The form name of the file upload input + * @param int $forum_id The id of the forum + * @param bool $local Whether the file is local or not + * @param string $local_storage The path to the local file + * @param bool $is_message Whether it is a PM or not + * @param array $local_filedata An file data object created for the local file + * + * @return array File data array + */ + public function upload($form_name, $forum_id, $local = false, $local_storage = '', $is_message = false, $local_filedata = array()) + { + $this->init_files_upload($forum_id, $is_message); + + $this->file_data['post_attach'] = $local || $this->files_upload->is_valid($form_name); + + if (!$this->file_data['post_attach']) + { + $this->file_data['error'][] = $this->language->lang('NO_UPLOAD_FORM_FOUND'); + return $this->file_data; + } + + $this->file = ($local) ? $this->files_upload->handle_upload('files.types.local', $local_storage, $local_filedata) : $this->files_upload->handle_upload('files.types.form', $form_name); + + if ($this->file->init_error()) + { + $this->file_data['post_attach'] = false; + return $this->file_data; + } + + // Whether the uploaded file is in the image category + $is_image = (isset($this->extensions[$this->file->get('extension')]['display_cat'])) ? $this->extensions[$this->file->get('extension')]['display_cat'] == ATTACHMENT_CATEGORY_IMAGE : false; + + if (!$this->auth->acl_get('a_') && !$this->auth->acl_get('m_', $forum_id)) + { + // Check Image Size, if it is an image + if ($is_image) + { + $this->file->upload->set_allowed_dimensions(0, 0, $this->config['img_max_width'], $this->config['img_max_height']); + } + + // Admins and mods are allowed to exceed the allowed filesize + if (!empty($this->extensions[$this->file->get('extension')]['max_filesize'])) + { + $allowed_filesize = $this->extensions[$this->file->get('extension')]['max_filesize']; + } + else + { + $allowed_filesize = ($is_message) ? $this->config['max_filesize_pm'] : $this->config['max_filesize']; + } + + $this->file->upload->set_max_filesize($allowed_filesize); + } + + $this->file->clean_filename('unique', $this->user->data['user_id'] . '_'); + + // Are we uploading an image *and* this image being within the image category? + // Only then perform additional image checks. + $this->file->move_file($this->config['upload_path'], false, !$is_image); + + // Do we have to create a thumbnail? + $this->file_data['thumbnail'] = ($is_image && $this->config['img_create_thumbnail']) ? 1 : 0; + + // Make sure the image category only holds valid images... + $this->check_image($is_image); + + if (count($this->file->error)) + { + $this->file->remove(); + $this->file_data['error'] = array_merge($this->file_data['error'], $this->file->error); + $this->file_data['post_attach'] = false; + + return $this->file_data; + } + + $this->fill_file_data(); + + $filedata = $this->file_data; + + /** + * Event to modify uploaded file before submit to the post + * + * @event core.modify_uploaded_file + * @var array filedata Array containing uploaded file data + * @var bool is_image Flag indicating if the file is an image + * @since 3.1.0-RC3 + */ + $vars = array( + 'filedata', + 'is_image', + ); + extract($this->phpbb_dispatcher->trigger_event('core.modify_uploaded_file', compact($vars))); + $this->file_data = $filedata; + unset($filedata); + + // Check for attachment quota and free space + if (!$this->check_attach_quota() || !$this->check_disk_space()) + { + return $this->file_data; + } + + // Create Thumbnail + $this->create_thumbnail(); + + return $this->file_data; + } + + /** + * Create thumbnail for file if necessary + * + * @return array Updated $filedata + */ + protected function create_thumbnail() + { + if ($this->file_data['thumbnail']) + { + $source = $this->file->get('destination_file'); + $destination = $this->file->get('destination_path') . '/thumb_' . $this->file->get('realname'); + + if (!create_thumbnail($source, $destination, $this->file->get('mimetype'))) + { + $this->file_data['thumbnail'] = 0; + } + } + } + + /** + * Init files upload class + * + * @param int $forum_id Forum ID + * @param bool $is_message Whether attachment is inside PM or not + */ + protected function init_files_upload($forum_id, $is_message) + { + if ($this->config['check_attachment_content'] && isset($this->config['mime_triggers'])) + { + $this->files_upload->set_disallowed_content(explode('|', $this->config['mime_triggers'])); + } + else if (!$this->config['check_attachment_content']) + { + $this->files_upload->set_disallowed_content(array()); + } + + $this->extensions = $this->cache->obtain_attach_extensions((($is_message) ? false : (int) $forum_id)); + $this->files_upload->set_allowed_extensions(array_keys($this->extensions['_allowed_'])); + } + + /** + * Check if uploaded file is really an image + * + * @param bool $is_image Whether file is image + */ + protected function check_image($is_image) + { + // Make sure the image category only holds valid images... + if ($is_image && !$this->file->is_image()) + { + $this->file->remove(); + + if ($this->plupload && $this->plupload->is_active()) + { + $this->plupload->emit_error(104, 'ATTACHED_IMAGE_NOT_IMAGE'); + } + + // If this error occurs a user tried to exploit an IE Bug by renaming extensions + // Since the image category is displaying content inline we need to catch this. + $this->file->set_error($this->language->lang('ATTACHED_IMAGE_NOT_IMAGE')); + } + } + + /** + * Check if attachment quota was reached + * + * @return bool False if attachment quota was reached, true if not + */ + protected function check_attach_quota() + { + if ($this->config['attachment_quota']) + { + if (intval($this->config['upload_dir_size']) + $this->file->get('filesize') > $this->config['attachment_quota']) + { + $this->file_data['error'][] = $this->language->lang('ATTACH_QUOTA_REACHED'); + $this->file_data['post_attach'] = false; + + $this->file->remove(); + + return false; + } + } + + return true; + } + + /** + * Check if there is enough free space available on disk + * + * @return bool True if disk space is available, false if not + */ + protected function check_disk_space() + { + if ($free_space = @disk_free_space($this->phpbb_root_path . $this->config['upload_path'])) + { + if ($free_space <= $this->file->get('filesize')) + { + if ($this->auth->acl_get('a_')) + { + $this->file_data['error'][] = $this->language->lang('ATTACH_DISK_FULL'); + } + else + { + $this->file_data['error'][] = $this->language->lang('ATTACH_QUOTA_REACHED'); + } + $this->file_data['post_attach'] = false; + + $this->file->remove(); + + return false; + } + } + + return true; + } + + /** + * Fills file data with file information and current time as filetime + */ + protected function fill_file_data() + { + $this->file_data['filesize'] = $this->file->get('filesize'); + $this->file_data['mimetype'] = $this->file->get('mimetype'); + $this->file_data['extension'] = $this->file->get('extension'); + $this->file_data['physical_filename'] = $this->file->get('realname'); + $this->file_data['real_filename'] = $this->file->get('uploadname'); + $this->file_data['filetime'] = time(); + } +} diff --git a/phpbb/auth/auth.php b/phpbb/auth/auth.php new file mode 100644 index 0000000..f46a21a --- /dev/null +++ b/phpbb/auth/auth.php @@ -0,0 +1,1122 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth; + +/** +* Permission/Auth class +*/ +class auth +{ + var $acl = array(); + var $cache = array(); + var $acl_options = array(); + var $acl_forum_ids = false; + + /** + * Init permissions + */ + function acl(&$userdata) + { + global $db, $cache; + + $this->acl = $this->cache = $this->acl_options = array(); + $this->acl_forum_ids = false; + + if (($this->acl_options = $cache->get('_acl_options')) === false) + { + $sql = 'SELECT auth_option_id, auth_option, is_global, is_local + FROM ' . ACL_OPTIONS_TABLE . ' + ORDER BY auth_option_id'; + $result = $db->sql_query($sql); + + $global = $local = 0; + $this->acl_options = array(); + while ($row = $db->sql_fetchrow($result)) + { + if ($row['is_global']) + { + $this->acl_options['global'][$row['auth_option']] = $global++; + } + + if ($row['is_local']) + { + $this->acl_options['local'][$row['auth_option']] = $local++; + } + + $this->acl_options['id'][$row['auth_option']] = (int) $row['auth_option_id']; + $this->acl_options['option'][(int) $row['auth_option_id']] = $row['auth_option']; + } + $db->sql_freeresult($result); + + $cache->put('_acl_options', $this->acl_options); + } + + if (!trim($userdata['user_permissions'])) + { + $this->acl_cache($userdata); + } + + // Fill ACL array + $this->_fill_acl($userdata['user_permissions']); + + // Verify bitstring length with options provided... + $renew = false; + $global_length = count($this->acl_options['global']); + $local_length = count($this->acl_options['local']); + + // Specify comparing length (bitstring is padded to 31 bits) + $global_length = ($global_length % 31) ? ($global_length - ($global_length % 31) + 31) : $global_length; + $local_length = ($local_length % 31) ? ($local_length - ($local_length % 31) + 31) : $local_length; + + // You thought we are finished now? Noooo... now compare them. + foreach ($this->acl as $forum_id => $bitstring) + { + if (($forum_id && strlen($bitstring) != $local_length) || (!$forum_id && strlen($bitstring) != $global_length)) + { + $renew = true; + break; + } + } + + // If a bitstring within the list does not match the options, we have a user with incorrect permissions set and need to renew them + if ($renew) + { + $this->acl_cache($userdata); + $this->_fill_acl($userdata['user_permissions']); + } + + return; + } + + /** + * Retrieves data wanted by acl function from the database for the + * specified user. + * + * @param int $user_id User ID + * @return array User attributes + */ + public function obtain_user_data($user_id) + { + global $db; + + $sql = 'SELECT user_id, username, user_permissions, user_type + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query($sql); + $user_data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + return $user_data; + } + + /** + * Fill ACL array with relevant bitstrings from user_permissions column + * @access private + */ + function _fill_acl($user_permissions) + { + $seq_cache = array(); + $this->acl = array(); + $user_permissions = explode("\n", $user_permissions); + + foreach ($user_permissions as $f => $seq) + { + if ($seq) + { + $i = 0; + + if (!isset($this->acl[$f])) + { + $this->acl[$f] = ''; + } + + while ($subseq = substr($seq, $i, 6)) + { + if (isset($seq_cache[$subseq])) + { + $converted = $seq_cache[$subseq]; + } + else + { + $converted = $seq_cache[$subseq] = str_pad(base_convert($subseq, 36, 2), 31, 0, STR_PAD_LEFT); + } + + // We put the original bitstring into the acl array + $this->acl[$f] .= $converted; + $i += 6; + } + } + } + } + + /** + * Look up an option + * if the option is prefixed with !, then the result becomes negated + * + * If a forum id is specified the local option will be combined with a global option if one exist. + * If a forum id is not specified, only the global option will be checked. + */ + function acl_get($opt, $f = 0) + { + $negate = false; + + if (strpos($opt, '!') === 0) + { + $negate = true; + $opt = substr($opt, 1); + } + + if (!isset($this->cache[$f][$opt])) + { + // We combine the global/local option with an OR because some options are global and local. + // If the user has the global permission the local one is true too and vice versa + $this->cache[$f][$opt] = false; + + // Is this option a global permission setting? + if (isset($this->acl_options['global'][$opt])) + { + if (isset($this->acl[0])) + { + $this->cache[$f][$opt] = $this->acl[0][$this->acl_options['global'][$opt]]; + } + } + + // Is this option a local permission setting? + // But if we check for a global option only, we won't combine the options... + if ($f != 0 && isset($this->acl_options['local'][$opt])) + { + if (isset($this->acl[$f]) && isset($this->acl[$f][$this->acl_options['local'][$opt]])) + { + $this->cache[$f][$opt] |= $this->acl[$f][$this->acl_options['local'][$opt]]; + } + } + } + + // Founder always has all global options set to true... + return ($negate) ? !$this->cache[$f][$opt] : $this->cache[$f][$opt]; + } + + /** + * Get forums with the specified permission setting + * + * @param string $opt The permission name to lookup. If prefixed with !, the result is negated. + * @param bool $clean set to true if only values needs to be returned which are set/unset + * + * @return array Contains the forum ids with the specified permission set to true. + This is a nested array: array => forum_id => permission => true + */ + function acl_getf($opt, $clean = false) + { + $acl_f = array(); + $negate = false; + + if (strpos($opt, '!') === 0) + { + $negate = true; + $opt = substr($opt, 1); + } + + // If we retrieve a list of forums not having permissions in, we need to get every forum_id + if ($negate) + { + if ($this->acl_forum_ids === false) + { + global $db; + + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE; + + if (count($this->acl)) + { + $sql .= ' WHERE ' . $db->sql_in_set('forum_id', array_keys($this->acl), true); + } + $result = $db->sql_query($sql); + + $this->acl_forum_ids = array(); + while ($row = $db->sql_fetchrow($result)) + { + $this->acl_forum_ids[] = $row['forum_id']; + } + $db->sql_freeresult($result); + } + } + + if (isset($this->acl_options['local'][$opt])) + { + foreach ($this->acl as $f => $bitstring) + { + // Skip global settings + if (!$f) + { + continue; + } + + $allowed = (!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt]; + + if (!$clean) + { + $acl_f[$f][$opt] = ($negate) ? !$allowed : $allowed; + } + else + { + if (($negate && !$allowed) || (!$negate && $allowed)) + { + $acl_f[$f][$opt] = 1; + } + } + } + } + + // If we get forum_ids not having this permission, we need to fill the remaining parts + if ($negate && count($this->acl_forum_ids)) + { + foreach ($this->acl_forum_ids as $f) + { + $acl_f[$f][$opt] = 1; + } + } + + return $acl_f; + } + + /** + * Get local permission state for any forum. + * + * Returns true if user has the permission in one or more forums, false if in no forum. + * If global option is checked it returns the global state (same as acl_get($opt)) + * Local option has precedence... + */ + function acl_getf_global($opt) + { + if (is_array($opt)) + { + // evaluates to true as soon as acl_getf_global is true for one option + foreach ($opt as $check_option) + { + if ($this->acl_getf_global($check_option)) + { + return true; + } + } + + return false; + } + + if (isset($this->acl_options['local'][$opt])) + { + foreach ($this->acl as $f => $bitstring) + { + // Skip global settings + if (!$f) + { + continue; + } + + // as soon as the user has any permission we're done so return true + if ((!isset($this->cache[$f][$opt])) ? $this->acl_get($opt, $f) : $this->cache[$f][$opt]) + { + return true; + } + } + } + else if (isset($this->acl_options['global'][$opt])) + { + return $this->acl_get($opt); + } + + return false; + } + + /** + * Get permission settings (more than one) + */ + function acl_gets() + { + $args = func_get_args(); + $f = array_pop($args); + + if (!is_numeric($f)) + { + $args[] = $f; + $f = 0; + } + + // alternate syntax: acl_gets(array('m_', 'a_'), $forum_id) + if (is_array($args[0])) + { + $args = $args[0]; + } + + $acl = 0; + foreach ($args as $opt) + { + $acl |= $this->acl_get($opt, $f); + } + + return $acl; + } + + /** + * Get permission listing based on user_id/options/forum_ids + * + * Be careful when using this function with permissions a_, m_, u_ and f_ ! + * It may not work correctly. When a user group grants an a_* permission, + * e.g. a_foo, but the user's a_foo permission is set to "Never", then + * the user does not in fact have the a_ permission. + * But the user will still be listed as having the a_ permission. + * + * For more information see: http://tracker.phpbb.com/browse/PHPBB3-10252 + */ + function acl_get_list($user_id = false, $opts = false, $forum_id = false) + { + if ($user_id !== false && !is_array($user_id) && $opts === false && $forum_id === false) + { + $hold_ary = array($user_id => $this->acl_raw_data_single_user($user_id)); + } + else + { + $hold_ary = $this->acl_raw_data($user_id, $opts, $forum_id); + } + + $auth_ary = array(); + foreach ($hold_ary as $user_id => $forum_ary) + { + foreach ($forum_ary as $forum_id => $auth_option_ary) + { + foreach ($auth_option_ary as $auth_option => $auth_setting) + { + if ($auth_setting) + { + $auth_ary[$forum_id][$auth_option][] = $user_id; + } + } + } + } + + return $auth_ary; + } + + /** + * Cache data to user_permissions row + */ + function acl_cache(&$userdata) + { + global $db; + + // Empty user_permissions + $userdata['user_permissions'] = ''; + + $hold_ary = $this->acl_raw_data_single_user($userdata['user_id']); + + // Key 0 in $hold_ary are global options, all others are forum_ids + + // If this user is founder we're going to force fill the admin options ... + if ($userdata['user_type'] == USER_FOUNDER) + { + foreach ($this->acl_options['global'] as $opt => $id) + { + if (strpos($opt, 'a_') === 0) + { + $hold_ary[0][$this->acl_options['id'][$opt]] = ACL_YES; + } + } + } + + $hold_str = $this->build_bitstring($hold_ary); + + if ($hold_str) + { + $userdata['user_permissions'] = $hold_str; + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_permissions = '" . $db->sql_escape($userdata['user_permissions']) . "', + user_perm_from = 0 + WHERE user_id = " . $userdata['user_id']; + $db->sql_query($sql); + } + + return; + } + + /** + * Build bitstring from permission set + */ + function build_bitstring(&$hold_ary) + { + $hold_str = ''; + + if (count($hold_ary)) + { + ksort($hold_ary); + + $last_f = 0; + + foreach ($hold_ary as $f => $auth_ary) + { + $ary_key = (!$f) ? 'global' : 'local'; + + $bitstring = array(); + foreach ($this->acl_options[$ary_key] as $opt => $id) + { + if (isset($auth_ary[$this->acl_options['id'][$opt]])) + { + $bitstring[$id] = $auth_ary[$this->acl_options['id'][$opt]]; + + $option_key = substr($opt, 0, strpos($opt, '_') + 1); + + // If one option is allowed, the global permission for this option has to be allowed too + // example: if the user has the a_ permission this means he has one or more a_* permissions + if ($auth_ary[$this->acl_options['id'][$opt]] == ACL_YES && (!isset($bitstring[$this->acl_options[$ary_key][$option_key]]) || $bitstring[$this->acl_options[$ary_key][$option_key]] == ACL_NEVER)) + { + $bitstring[$this->acl_options[$ary_key][$option_key]] = ACL_YES; + } + } + else + { + $bitstring[$id] = ACL_NEVER; + } + } + + // Now this bitstring defines the permission setting for the current forum $f (or global setting) + $bitstring = implode('', $bitstring); + + // The line number indicates the id, therefore we have to add empty lines for those ids not present + $hold_str .= str_repeat("\n", $f - $last_f); + + // Convert bitstring for storage - we do not use binary/bytes because PHP's string functions are not fully binary safe + for ($i = 0, $bit_length = strlen($bitstring); $i < $bit_length; $i += 31) + { + $hold_str .= str_pad(base_convert(str_pad(substr($bitstring, $i, 31), 31, 0, STR_PAD_RIGHT), 2, 36), 6, 0, STR_PAD_LEFT); + } + + $last_f = $f; + } + unset($bitstring); + + $hold_str = rtrim($hold_str); + } + + return $hold_str; + } + + /** + * Clear one or all users cached permission settings + */ + function acl_clear_prefetch($user_id = false) + { + global $db, $cache, $phpbb_dispatcher; + + // Rebuild options cache + $cache->destroy('_role_cache'); + + $sql = 'SELECT * + FROM ' . ACL_ROLES_DATA_TABLE . ' + ORDER BY role_id ASC'; + $result = $db->sql_query($sql); + + $this->role_cache = array(); + while ($row = $db->sql_fetchrow($result)) + { + $this->role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting']; + } + $db->sql_freeresult($result); + + foreach ($this->role_cache as $role_id => $role_options) + { + $this->role_cache[$role_id] = serialize($role_options); + } + + $cache->put('_role_cache', $this->role_cache); + + // Now empty user permissions + $where_sql = ''; + + if ($user_id !== false) + { + $user_id = (!is_array($user_id)) ? $user_id = array((int) $user_id) : array_map('intval', $user_id); + $where_sql = ' WHERE ' . $db->sql_in_set('user_id', $user_id); + } + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_permissions = '', + user_perm_from = 0 + $where_sql"; + $db->sql_query($sql); + + /** + * Event is triggered after user(s) permission settings cache has been cleared + * + * @event core.acl_clear_prefetch_after + * @var mixed user_id User ID(s) + * @since 3.1.11-RC1 + */ + $vars = array('user_id'); + extract($phpbb_dispatcher->trigger_event('core.acl_clear_prefetch_after', compact($vars))); + + return; + } + + /** + * Get assigned roles + */ + function acl_role_data($user_type, $role_type, $ug_id = false, $forum_id = false) + { + global $db; + + $roles = array(); + + $sql_id = ($user_type == 'user') ? 'user_id' : 'group_id'; + + $sql_ug = ($ug_id !== false) ? ((!is_array($ug_id)) ? "AND a.$sql_id = $ug_id" : 'AND ' . $db->sql_in_set("a.$sql_id", $ug_id)) : ''; + $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? "AND a.forum_id = $forum_id" : 'AND ' . $db->sql_in_set('a.forum_id', $forum_id)) : ''; + + // Grab assigned roles... + $sql = 'SELECT a.auth_role_id, a.' . $sql_id . ', a.forum_id + FROM ' . (($user_type == 'user') ? ACL_USERS_TABLE : ACL_GROUPS_TABLE) . ' a, ' . ACL_ROLES_TABLE . " r + WHERE a.auth_role_id = r.role_id + AND r.role_type = '" . $db->sql_escape($role_type) . "' + $sql_ug + $sql_forum + ORDER BY r.role_order ASC"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $roles[$row[$sql_id]][$row['forum_id']] = $row['auth_role_id']; + } + $db->sql_freeresult($result); + + return $roles; + } + + /** + * Get raw acl data based on user/option/forum + */ + function acl_raw_data($user_id = false, $opts = false, $forum_id = false) + { + global $db; + + $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : ''; + $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; + + $sql_opts = $sql_opts_select = $sql_opts_from = ''; + $hold_ary = array(); + + if ($opts !== false) + { + $sql_opts_select = ', ao.auth_option'; + $sql_opts_from = ', ' . ACL_OPTIONS_TABLE . ' ao'; + $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); + } + + $sql_ary = array(); + + // Grab non-role settings - user-specific + $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . ' + FROM ' . ACL_USERS_TABLE . ' a' . $sql_opts_from . ' + WHERE a.auth_role_id = 0 ' . + (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') . + (($sql_user) ? 'AND a.' . $sql_user : '') . " + $sql_forum + $sql_opts"; + + // Now the role settings - user-specific + $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . ' + FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . ' + WHERE a.auth_role_id = r.role_id ' . + (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') . + (($sql_user) ? 'AND a.' . $sql_user : '') . " + $sql_forum + $sql_opts"; + + foreach ($sql_ary as $sql) + { + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']]; + $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting']; + } + $db->sql_freeresult($result); + } + + $sql_ary = array(); + + // Now grab group settings - non-role specific... + $sql_ary[] = 'SELECT ug.user_id, a.forum_id, a.auth_setting, a.auth_option_id' . $sql_opts_select . ' + FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g' . $sql_opts_from . ' + WHERE a.auth_role_id = 0 ' . + (($sql_opts_from) ? 'AND a.auth_option_id = ao.auth_option_id ' : '') . ' + AND a.group_id = ug.group_id + AND g.group_id = ug.group_id + AND ug.user_pending = 0 + AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) + ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . " + $sql_forum + $sql_opts"; + + // Now grab group settings - role specific... + $sql_ary[] = 'SELECT ug.user_id, a.forum_id, r.auth_setting, r.auth_option_id' . $sql_opts_select . ' + FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g, ' . ACL_ROLES_DATA_TABLE . ' r' . $sql_opts_from . ' + WHERE a.auth_role_id = r.role_id ' . + (($sql_opts_from) ? 'AND r.auth_option_id = ao.auth_option_id ' : '') . ' + AND a.group_id = ug.group_id + AND g.group_id = ug.group_id + AND ug.user_pending = 0 + AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) + ' . (($sql_user) ? 'AND ug.' . $sql_user : '') . " + $sql_forum + $sql_opts"; + + foreach ($sql_ary as $sql) + { + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $option = ($sql_opts_select) ? $row['auth_option'] : $this->acl_options['option'][$row['auth_option_id']]; + + if (!isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) || (isset($hold_ary[$row['user_id']][$row['forum_id']][$option]) && $hold_ary[$row['user_id']][$row['forum_id']][$option] != ACL_NEVER)) + { + $hold_ary[$row['user_id']][$row['forum_id']][$option] = $row['auth_setting']; + + // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again) + if ($row['auth_setting'] == ACL_NEVER) + { + $flag = substr($option, 0, strpos($option, '_') + 1); + + if (isset($hold_ary[$row['user_id']][$row['forum_id']][$flag]) && $hold_ary[$row['user_id']][$row['forum_id']][$flag] == ACL_YES) + { + unset($hold_ary[$row['user_id']][$row['forum_id']][$flag]); + +/* if (in_array(ACL_YES, $hold_ary[$row['user_id']][$row['forum_id']])) + { + $hold_ary[$row['user_id']][$row['forum_id']][$flag] = ACL_YES; + } +*/ + } + } + } + } + $db->sql_freeresult($result); + } + + return $hold_ary; + } + + /** + * Get raw user based permission settings + */ + function acl_user_raw_data($user_id = false, $opts = false, $forum_id = false) + { + global $db; + + $sql_user = ($user_id !== false) ? ((!is_array($user_id)) ? 'user_id = ' . (int) $user_id : $db->sql_in_set('user_id', array_map('intval', $user_id))) : ''; + $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; + + $sql_opts = ''; + $hold_ary = $sql_ary = array(); + + if ($opts !== false) + { + $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); + } + + // Grab user settings - non-role specific... + $sql_ary[] = 'SELECT a.user_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option + FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao + WHERE a.auth_role_id = 0 + AND a.auth_option_id = ao.auth_option_id ' . + (($sql_user) ? 'AND a.' . $sql_user : '') . " + $sql_forum + $sql_opts + ORDER BY a.forum_id, ao.auth_option"; + + // Now the role settings - user-specific + $sql_ary[] = 'SELECT a.user_id, a.forum_id, r.auth_option_id, r.auth_setting, r.auth_option_id, ao.auth_option + FROM ' . ACL_USERS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao + WHERE a.auth_role_id = r.role_id + AND r.auth_option_id = ao.auth_option_id ' . + (($sql_user) ? 'AND a.' . $sql_user : '') . " + $sql_forum + $sql_opts + ORDER BY a.forum_id, ao.auth_option"; + + foreach ($sql_ary as $sql) + { + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $hold_ary[$row['user_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + } + + return $hold_ary; + } + + /** + * Get raw group based permission settings + */ + function acl_group_raw_data($group_id = false, $opts = false, $forum_id = false) + { + global $db; + + $sql_group = ($group_id !== false) ? ((!is_array($group_id)) ? 'group_id = ' . (int) $group_id : $db->sql_in_set('group_id', array_map('intval', $group_id))) : ''; + $sql_forum = ($forum_id !== false) ? ((!is_array($forum_id)) ? 'AND a.forum_id = ' . (int) $forum_id : 'AND ' . $db->sql_in_set('a.forum_id', array_map('intval', $forum_id))) : ''; + + $sql_opts = ''; + $hold_ary = $sql_ary = array(); + + if ($opts !== false) + { + $this->build_auth_option_statement('ao.auth_option', $opts, $sql_opts); + } + + // Grab group settings - non-role specific... + $sql_ary[] = 'SELECT a.group_id, a.forum_id, a.auth_setting, a.auth_option_id, ao.auth_option + FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_OPTIONS_TABLE . ' ao + WHERE a.auth_role_id = 0 + AND a.auth_option_id = ao.auth_option_id ' . + (($sql_group) ? 'AND a.' . $sql_group : '') . " + $sql_forum + $sql_opts + ORDER BY a.forum_id, ao.auth_option"; + + // Now grab group settings - role specific... + $sql_ary[] = 'SELECT a.group_id, a.forum_id, r.auth_setting, r.auth_option_id, ao.auth_option + FROM ' . ACL_GROUPS_TABLE . ' a, ' . ACL_ROLES_DATA_TABLE . ' r, ' . ACL_OPTIONS_TABLE . ' ao + WHERE a.auth_role_id = r.role_id + AND r.auth_option_id = ao.auth_option_id ' . + (($sql_group) ? 'AND a.' . $sql_group : '') . " + $sql_forum + $sql_opts + ORDER BY a.forum_id, ao.auth_option"; + + foreach ($sql_ary as $sql) + { + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $hold_ary[$row['group_id']][$row['forum_id']][$row['auth_option']] = $row['auth_setting']; + } + $db->sql_freeresult($result); + } + + return $hold_ary; + } + + /** + * Get raw acl data based on user for caching user_permissions + * This function returns the same data as acl_raw_data(), but without the user id as the first key within the array. + */ + function acl_raw_data_single_user($user_id) + { + global $db, $cache; + + // Check if the role-cache is there + if (($this->role_cache = $cache->get('_role_cache')) === false) + { + $this->role_cache = array(); + + // We pre-fetch roles + $sql = 'SELECT * + FROM ' . ACL_ROLES_DATA_TABLE . ' + ORDER BY role_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $this->role_cache[$row['role_id']][$row['auth_option_id']] = (int) $row['auth_setting']; + } + $db->sql_freeresult($result); + + foreach ($this->role_cache as $role_id => $role_options) + { + $this->role_cache[$role_id] = serialize($role_options); + } + + $cache->put('_role_cache', $this->role_cache); + } + + $hold_ary = array(); + + // Grab user-specific permission settings + $sql = 'SELECT forum_id, auth_option_id, auth_role_id, auth_setting + FROM ' . ACL_USERS_TABLE . ' + WHERE user_id = ' . $user_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + // If a role is assigned, assign all options included within this role. Else, only set this one option. + if ($row['auth_role_id']) + { + $hold_ary[$row['forum_id']] = (empty($hold_ary[$row['forum_id']])) ? unserialize($this->role_cache[$row['auth_role_id']]) : $hold_ary[$row['forum_id']] + unserialize($this->role_cache[$row['auth_role_id']]); + } + else + { + $hold_ary[$row['forum_id']][$row['auth_option_id']] = $row['auth_setting']; + } + } + $db->sql_freeresult($result); + + // Now grab group-specific permission settings + $sql = 'SELECT a.forum_id, a.auth_option_id, a.auth_role_id, a.auth_setting + FROM ' . ACL_GROUPS_TABLE . ' a, ' . USER_GROUP_TABLE . ' ug, ' . GROUPS_TABLE . ' g + WHERE a.group_id = ug.group_id + AND g.group_id = ug.group_id + AND ug.user_pending = 0 + AND NOT (ug.group_leader = 1 AND g.group_skip_auth = 1) + AND ug.user_id = ' . $user_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (!$row['auth_role_id']) + { + $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $row['auth_option_id'], $row['auth_setting']); + } + else if (!empty($this->role_cache[$row['auth_role_id']])) + { + foreach (unserialize($this->role_cache[$row['auth_role_id']]) as $option_id => $setting) + { + $this->_set_group_hold_ary($hold_ary[$row['forum_id']], $option_id, $setting); + } + } + } + $db->sql_freeresult($result); + + return $hold_ary; + } + + /** + * Private function snippet for setting a specific piece of the hold_ary + */ + function _set_group_hold_ary(&$hold_ary, $option_id, $setting) + { + if (!isset($hold_ary[$option_id]) || (isset($hold_ary[$option_id]) && $hold_ary[$option_id] != ACL_NEVER)) + { + $hold_ary[$option_id] = $setting; + + // If we detect ACL_NEVER, we will unset the flag option (within building the bitstring it is correctly set again) + if ($setting == ACL_NEVER) + { + $flag = substr($this->acl_options['option'][$option_id], 0, strpos($this->acl_options['option'][$option_id], '_') + 1); + $flag = (int) $this->acl_options['id'][$flag]; + + if (isset($hold_ary[$flag]) && $hold_ary[$flag] == ACL_YES) + { + unset($hold_ary[$flag]); + +/* This is uncommented, because i suspect this being slightly wrong due to mixed permission classes being possible + if (in_array(ACL_YES, $hold_ary)) + { + $hold_ary[$flag] = ACL_YES; + }*/ + } + } + } + } + + /** + * Authentication plug-ins is largely down to Sergey Kanareykin, our thanks to him. + */ + function login($username, $password, $autologin = false, $viewonline = 1, $admin = 0) + { + global $db, $user, $phpbb_root_path, $phpEx, $phpbb_container; + global $phpbb_dispatcher; + + /* @var $provider_collection \phpbb\auth\provider_collection */ + $provider_collection = $phpbb_container->get('auth.provider_collection'); + + $provider = $provider_collection->get_provider(); + if ($provider) + { + $login = $provider->login($username, $password); + + // If the auth module wants us to create an empty profile do so and then treat the status as LOGIN_SUCCESS + if ($login['status'] == LOGIN_SUCCESS_CREATE_PROFILE) + { + // we are going to use the user_add function so include functions_user.php if it wasn't defined yet + if (!function_exists('user_add')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + user_add($login['user_row'], (isset($login['cp_data'])) ? $login['cp_data'] : false); + + $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $db->sql_escape(utf8_clean_string($username)) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'AUTH_NO_PROFILE_CREATED', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $login = array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + + // If the auth provider wants us to link an empty account do so and redirect + if ($login['status'] == LOGIN_SUCCESS_LINK_PROFILE) + { + // If this status exists a fourth field is in the $login array called 'redirect_data' + // This data is passed along as GET data to the next page allow the account to be linked + + $params = array('mode' => 'login_link'); + $url = append_sid($phpbb_root_path . 'ucp.' . $phpEx, array_merge($params, $login['redirect_data'])); + + redirect($url); + } + + /** + * Event is triggered after checking for valid username and password, and before the actual session creation. + * + * @event core.auth_login_session_create_before + * @var array login Variable containing login array + * @var bool admin Boolean variable whether user is logging into the ACP + * @var string username Username of user to log in + * @var bool autologin Boolean variable signaling whether login is triggered via auto login + * @since 3.1.7-RC1 + */ + $vars = array( + 'login', + 'admin', + 'username', + 'autologin', + ); + extract($phpbb_dispatcher->trigger_event('core.auth_login_session_create_before', compact($vars))); + + // If login succeeded, we will log the user in... else we pass the login array through... + if ($login['status'] == LOGIN_SUCCESS) + { + $old_session_id = $user->session_id; + + if ($admin) + { + global $SID, $_SID; + + $cookie_expire = time() - 31536000; + $user->set_cookie('u', '', $cookie_expire); + $user->set_cookie('sid', '', $cookie_expire); + unset($cookie_expire); + + $SID = '?sid='; + $user->session_id = $_SID = ''; + } + + $result = $user->session_create($login['user_row']['user_id'], $admin, $autologin, $viewonline); + + // Successful session creation + if ($result === true) + { + // If admin re-authentication we remove the old session entry because a new one has been created... + if ($admin) + { + // the login array is used because the user ids do not differ for re-authentication + $sql = 'DELETE FROM ' . SESSIONS_TABLE . " + WHERE session_id = '" . $db->sql_escape($old_session_id) . "' + AND session_user_id = {$login['user_row']['user_id']}"; + $db->sql_query($sql); + } + + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $login['user_row'], + ); + } + + return array( + 'status' => LOGIN_BREAK, + 'error_msg' => $result, + 'user_row' => $login['user_row'], + ); + } + + return $login; + } + + trigger_error('Authentication method not found', E_USER_ERROR); + } + + /** + * Fill auth_option statement for later querying based on the supplied options + */ + function build_auth_option_statement($key, $auth_options, &$sql_opts) + { + global $db; + + if (!is_array($auth_options)) + { + if (strpos($auth_options, '%') !== false) + { + $sql_opts = "AND $key " . $db->sql_like_expression(str_replace('%', $db->get_any_char(), $auth_options)); + } + else + { + $sql_opts = "AND $key = '" . $db->sql_escape($auth_options) . "'"; + } + } + else + { + $is_like_expression = false; + + foreach ($auth_options as $option) + { + if (strpos($option, '%') !== false) + { + $is_like_expression = true; + } + } + + if (!$is_like_expression) + { + $sql_opts = 'AND ' . $db->sql_in_set($key, $auth_options); + } + else + { + $sql = array(); + + foreach ($auth_options as $option) + { + if (strpos($option, '%') !== false) + { + $sql[] = $key . ' ' . $db->sql_like_expression(str_replace('%', $db->get_any_char(), $option)); + } + else + { + $sql[] = $key . " = '" . $db->sql_escape($option) . "'"; + } + } + + $sql_opts = 'AND (' . implode(' OR ', $sql) . ')'; + } + } + } +} diff --git a/phpbb/auth/index.htm b/phpbb/auth/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/phpbb/auth/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/phpbb/auth/provider/apache.php b/phpbb/auth/provider/apache.php new file mode 100644 index 0000000..aa5bf64 --- /dev/null +++ b/phpbb/auth/provider/apache.php @@ -0,0 +1,264 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider; + +/** +* Apache authentication provider for phpBB3 +*/ +class apache extends \phpbb\auth\provider\base +{ + /** + * phpBB passwords manager + * + * @var \phpbb\passwords\manager + */ + protected $passwords_manager; + + /** + * Apache Authentication Constructor + * + * @param \phpbb\db\driver\driver_interface $db Database object + * @param \phpbb\config\config $config Config object + * @param \phpbb\passwords\manager $passwords_manager Passwords Manager object + * @param \phpbb\request\request $request Request object + * @param \phpbb\user $user User object + * @param string $phpbb_root_path Relative path to phpBB root + * @param string $php_ext PHP file extension + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request $request, \phpbb\user $user, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->config = $config; + $this->passwords_manager = $passwords_manager; + $this->request = $request; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function init() + { + if (!$this->request->is_set('PHP_AUTH_USER', \phpbb\request\request_interface::SERVER) || $this->user->data['username'] !== htmlspecialchars_decode($this->request->server('PHP_AUTH_USER'))) + { + return $this->user->lang['APACHE_SETUP_BEFORE_USE']; + } + return false; + } + + /** + * {@inheritdoc} + */ + public function login($username, $password) + { + // do not allow empty password + if (!$password) + { + return array( + 'status' => LOGIN_ERROR_PASSWORD, + 'error_msg' => 'NO_PASSWORD_SUPPLIED', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!$username) + { + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!$this->request->is_set('PHP_AUTH_USER', \phpbb\request\request_interface::SERVER)) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $php_auth_user = htmlspecialchars_decode($this->request->server('PHP_AUTH_USER')); + $php_auth_pw = htmlspecialchars_decode($this->request->server('PHP_AUTH_PW')); + + if (!empty($php_auth_user) && !empty($php_auth_pw)) + { + if ($php_auth_user !== $username) + { + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type + FROM ' . USERS_TABLE . " + WHERE username = '" . $this->db->sql_escape($php_auth_user) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + // User inactive... + if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) + { + return array( + 'status' => LOGIN_ERROR_ACTIVE, + 'error_msg' => 'ACTIVE_ERROR', + 'user_row' => $row, + ); + } + + // Successful login... + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + + // this is the user's first login so create an empty profile + return array( + 'status' => LOGIN_SUCCESS_CREATE_PROFILE, + 'error_msg' => false, + 'user_row' => $this->user_row($php_auth_user, $php_auth_pw), + ); + } + + // Not logged into apache + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LOGIN_ERROR_EXTERNAL_AUTH_APACHE', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + /** + * {@inheritdoc} + */ + public function autologin() + { + if (!$this->request->is_set('PHP_AUTH_USER', \phpbb\request\request_interface::SERVER)) + { + return array(); + } + + $php_auth_user = htmlspecialchars_decode($this->request->server('PHP_AUTH_USER')); + $php_auth_pw = htmlspecialchars_decode($this->request->server('PHP_AUTH_PW')); + + if (!empty($php_auth_user) && !empty($php_auth_pw)) + { + set_var($php_auth_user, $php_auth_user, 'string', true); + set_var($php_auth_pw, $php_auth_pw, 'string', true); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . " + WHERE username = '" . $this->db->sql_escape($php_auth_user) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + return ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) ? array() : $row; + } + + if (!function_exists('user_add')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + // create the user if he does not exist yet + user_add($this->user_row($php_auth_user, $php_auth_pw)); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($php_auth_user)) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + return $row; + } + } + + return array(); + } + + /** + * This function generates an array which can be passed to the user_add + * function in order to create a user + * + * @param string $username The username of the new user. + * @param string $password The password of the new user. + * @return array Contains data that can be passed directly to + * the user_add function. + */ + private function user_row($username, $password) + { + // first retrieve default group id + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' + AND group_type = " . GROUP_SPECIAL; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_GROUP'); + } + + // generate user account data + return array( + 'username' => $username, + 'user_password' => $this->passwords_manager->hash($password), + 'user_email' => '', + 'group_id' => (int) $row['group_id'], + 'user_type' => USER_NORMAL, + 'user_ip' => $this->user->ip, + 'user_new' => ($this->config['new_member_post_limit']) ? 1 : 0, + ); + } + + /** + * {@inheritdoc} + */ + public function validate_session($user) + { + // Check if PHP_AUTH_USER is set and handle this case + if ($this->request->is_set('PHP_AUTH_USER', \phpbb\request\request_interface::SERVER)) + { + $php_auth_user = $this->request->server('PHP_AUTH_USER'); + + return ($php_auth_user === $user['username']) ? true : false; + } + + // PHP_AUTH_USER is not set. A valid session is now determined by the user type (anonymous/bot or not) + if ($user['user_type'] == USER_IGNORE) + { + return true; + } + + return false; + } +} diff --git a/phpbb/auth/provider/base.php b/phpbb/auth/provider/base.php new file mode 100644 index 0000000..dea27cc --- /dev/null +++ b/phpbb/auth/provider/base.php @@ -0,0 +1,108 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider; + +/** +* Base authentication provider class that all other providers should implement +*/ +abstract class base implements \phpbb\auth\provider\provider_interface +{ + /** + * {@inheritdoc} + */ + public function init() + { + return; + } + + /** + * {@inheritdoc} + */ + public function autologin() + { + return; + } + + /** + * {@inheritdoc} + */ + public function acp() + { + return; + } + + /** + * {@inheritdoc} + */ + public function get_acp_template($new_config) + { + return; + } + + /** + * {@inheritdoc} + */ + public function get_login_data() + { + return; + } + + /** + * {@inheritdoc} + */ + public function get_auth_link_data($user_id = 0) + { + return; + } + + /** + * {@inheritdoc} + */ + public function logout($data, $new_session) + { + return; + } + + /** + * {@inheritdoc} + */ + public function validate_session($user) + { + return; + } + + /** + * {@inheritdoc} + */ + public function login_link_has_necessary_data($login_link_data) + { + return; + } + + /** + * {@inheritdoc} + */ + public function link_account(array $link_data) + { + return; + } + + /** + * {@inheritdoc} + */ + public function unlink_account(array $link_data) + { + return; + } +} diff --git a/phpbb/auth/provider/db.php b/phpbb/auth/provider/db.php new file mode 100644 index 0000000..1adf85e --- /dev/null +++ b/phpbb/auth/provider/db.php @@ -0,0 +1,240 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider; + +/** + * Database authentication provider for phpBB3 + * This is for authentication via the integrated user table + */ +class db extends \phpbb\auth\provider\base +{ + /** + * phpBB passwords manager + * + * @var \phpbb\passwords\manager + */ + protected $passwords_manager; + + /** + * DI container + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $phpbb_container; + + /** + * Database Authentication Constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\config\config $config + * @param \phpbb\passwords\manager $passwords_manager + * @param \phpbb\request\request $request + * @param \phpbb\user $user + * @param \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container DI container + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request $request, \phpbb\user $user, \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->config = $config; + $this->passwords_manager = $passwords_manager; + $this->request = $request; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->phpbb_container = $phpbb_container; + } + + /** + * {@inheritdoc} + */ + public function login($username, $password) + { + // Auth plugins get the password untrimmed. + // For compatibility we trim() here. + $password = trim($password); + + // do not allow empty password + if (!$password) + { + return array( + 'status' => LOGIN_ERROR_PASSWORD, + 'error_msg' => 'NO_PASSWORD_SUPPLIED', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!$username) + { + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $username_clean = utf8_clean_string($username); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape($username_clean) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (($this->user->ip && !$this->config['ip_login_limit_use_forwarded']) || + ($this->user->forwarded_for && $this->config['ip_login_limit_use_forwarded'])) + { + $sql = 'SELECT COUNT(*) AS attempts + FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE attempt_time > ' . (time() - (int) $this->config['ip_login_limit_time']); + if ($this->config['ip_login_limit_use_forwarded']) + { + $sql .= " AND attempt_forwarded_for = '" . $this->db->sql_escape($this->user->forwarded_for) . "'"; + } + else + { + $sql .= " AND attempt_ip = '" . $this->db->sql_escape($this->user->ip) . "' "; + } + + $result = $this->db->sql_query($sql); + $attempts = (int) $this->db->sql_fetchfield('attempts'); + $this->db->sql_freeresult($result); + + $attempt_data = array( + 'attempt_ip' => $this->user->ip, + 'attempt_browser' => trim(substr($this->user->browser, 0, 149)), + 'attempt_forwarded_for' => $this->user->forwarded_for, + 'attempt_time' => time(), + 'user_id' => ($row) ? (int) $row['user_id'] : 0, + 'username' => $username, + 'username_clean' => $username_clean, + ); + $sql = 'INSERT INTO ' . LOGIN_ATTEMPT_TABLE . $this->db->sql_build_array('INSERT', $attempt_data); + $this->db->sql_query($sql); + } + else + { + $attempts = 0; + } + + if (!$row) + { + if ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max']) + { + return array( + 'status' => LOGIN_ERROR_ATTEMPTS, + 'error_msg' => 'LOGIN_ERROR_ATTEMPTS', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $show_captcha = ($this->config['max_login_attempts'] && $row['user_login_attempts'] >= $this->config['max_login_attempts']) || + ($this->config['ip_login_limit_max'] && $attempts >= $this->config['ip_login_limit_max']); + + // If there are too many login attempts, we need to check for a confirm image + // Every auth module is able to define what to do by itself... + if ($show_captcha) + { + /* @var $captcha_factory \phpbb\captcha\factory */ + $captcha_factory = $this->phpbb_container->get('captcha.factory'); + $captcha = $captcha_factory->get_instance($this->config['captcha_plugin']); + $captcha->init(CONFIRM_LOGIN); + $vc_response = $captcha->validate($row); + if ($vc_response) + { + return array( + 'status' => LOGIN_ERROR_ATTEMPTS, + 'error_msg' => 'LOGIN_ERROR_ATTEMPTS', + 'user_row' => $row, + ); + } + else + { + $captcha->reset(); + } + + } + + // Check password ... + if ($this->passwords_manager->check($password, $row['user_password'], $row)) + { + // Check for old password hash... + if ($this->passwords_manager->convert_flag || strlen($row['user_password']) == 32) + { + $hash = $this->passwords_manager->hash($password); + + // Update the password in the users table to the new format + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_password = '" . $this->db->sql_escape($hash) . "' + WHERE user_id = {$row['user_id']}"; + $this->db->sql_query($sql); + + $row['user_password'] = $hash; + } + + $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE user_id = ' . $row['user_id']; + $this->db->sql_query($sql); + + if ($row['user_login_attempts'] != 0) + { + // Successful, reset login attempts (the user passed all stages) + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_login_attempts = 0 + WHERE user_id = ' . $row['user_id']; + $this->db->sql_query($sql); + } + + // User inactive... + if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) + { + return array( + 'status' => LOGIN_ERROR_ACTIVE, + 'error_msg' => 'ACTIVE_ERROR', + 'user_row' => $row, + ); + } + + // Successful login... set user_login_attempts to zero... + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + + // Password incorrect - increase login attempts + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_login_attempts = user_login_attempts + 1 + WHERE user_id = ' . (int) $row['user_id'] . ' + AND user_login_attempts < ' . LOGIN_ATTEMPTS_MAX; + $this->db->sql_query($sql); + + // Give status about wrong password... + return array( + 'status' => ($show_captcha) ? LOGIN_ERROR_ATTEMPTS : LOGIN_ERROR_PASSWORD, + 'error_msg' => 'LOGIN_ERROR_PASSWORD', + 'user_row' => $row, + ); + } +} diff --git a/phpbb/auth/provider/index.htm b/phpbb/auth/provider/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/phpbb/auth/provider/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/phpbb/auth/provider/ldap.php b/phpbb/auth/provider/ldap.php new file mode 100644 index 0000000..0789a62 --- /dev/null +++ b/phpbb/auth/provider/ldap.php @@ -0,0 +1,348 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider; + +/** + * Database authentication provider for phpBB3 + * This is for authentication via the integrated user table + */ +class ldap extends \phpbb\auth\provider\base +{ + /** + * phpBB passwords manager + * + * @var \phpbb\passwords\manager + */ + protected $passwords_manager; + + /** + * LDAP Authentication Constructor + * + * @param \phpbb\db\driver\driver_interface $db Database object + * @param \phpbb\config\config $config Config object + * @param \phpbb\passwords\manager $passwords_manager Passwords manager object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\user $user) + { + $this->db = $db; + $this->config = $config; + $this->passwords_manager = $passwords_manager; + $this->user = $user; + } + + /** + * {@inheritdoc} + */ + public function init() + { + if (!@extension_loaded('ldap')) + { + return $this->user->lang['LDAP_NO_LDAP_EXTENSION']; + } + + $this->config['ldap_port'] = (int) $this->config['ldap_port']; + if ($this->config['ldap_port']) + { + $ldap = @ldap_connect($this->config['ldap_server'], $this->config['ldap_port']); + } + else + { + $ldap = @ldap_connect($this->config['ldap_server']); + } + + if (!$ldap) + { + return $this->user->lang['LDAP_NO_SERVER_CONNECTION']; + } + + @ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); + @ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + + if ($this->config['ldap_user'] || $this->config['ldap_password']) + { + if (!@ldap_bind($ldap, htmlspecialchars_decode($this->config['ldap_user']), htmlspecialchars_decode($this->config['ldap_password']))) + { + return $this->user->lang['LDAP_INCORRECT_USER_PASSWORD']; + } + } + + // ldap_connect only checks whether the specified server is valid, so the connection might still fail + $search = @ldap_search( + $ldap, + htmlspecialchars_decode($this->config['ldap_base_dn']), + $this->ldap_user_filter($this->user->data['username']), + (empty($this->config['ldap_email'])) ? + array(htmlspecialchars_decode($this->config['ldap_uid'])) : + array(htmlspecialchars_decode($this->config['ldap_uid']), htmlspecialchars_decode($this->config['ldap_email'])), + 0, + 1 + ); + + if ($search === false) + { + return $this->user->lang['LDAP_SEARCH_FAILED']; + } + + $result = @ldap_get_entries($ldap, $search); + + @ldap_close($ldap); + + if (!is_array($result) || count($result) < 2) + { + return sprintf($this->user->lang['LDAP_NO_IDENTITY'], $this->user->data['username']); + } + + if (!empty($this->config['ldap_email']) && !isset($result[0][htmlspecialchars_decode($this->config['ldap_email'])])) + { + return $this->user->lang['LDAP_NO_EMAIL']; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function login($username, $password) + { + // do not allow empty password + if (!$password) + { + return array( + 'status' => LOGIN_ERROR_PASSWORD, + 'error_msg' => 'NO_PASSWORD_SUPPLIED', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!$username) + { + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + if (!@extension_loaded('ldap')) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LDAP_NO_LDAP_EXTENSION', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + $this->config['ldap_port'] = (int) $this->config['ldap_port']; + if ($this->config['ldap_port']) + { + $ldap = @ldap_connect($this->config['ldap_server'], $this->config['ldap_port']); + } + else + { + $ldap = @ldap_connect($this->config['ldap_server']); + } + + if (!$ldap) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LDAP_NO_SERVER_CONNECTION', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + @ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3); + @ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0); + + if ($this->config['ldap_user'] || $this->config['ldap_password']) + { + if (!@ldap_bind($ldap, htmlspecialchars_decode($this->config['ldap_user']), htmlspecialchars_decode($this->config['ldap_password']))) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LDAP_NO_SERVER_CONNECTION', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + } + + $search = @ldap_search( + $ldap, + htmlspecialchars_decode($this->config['ldap_base_dn']), + $this->ldap_user_filter($username), + (empty($this->config['ldap_email'])) ? + array(htmlspecialchars_decode($this->config['ldap_uid'])) : + array(htmlspecialchars_decode($this->config['ldap_uid']), htmlspecialchars_decode($this->config['ldap_email'])), + 0, + 1 + ); + + $ldap_result = @ldap_get_entries($ldap, $search); + + if (is_array($ldap_result) && count($ldap_result) > 1) + { + if (@ldap_bind($ldap, $ldap_result[0]['dn'], htmlspecialchars_decode($password))) + { + @ldap_close($ldap); + + $sql ='SELECT user_id, username, user_password, user_passchg, user_email, user_type + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + unset($ldap_result); + + // User inactive... + if ($row['user_type'] == USER_INACTIVE || $row['user_type'] == USER_IGNORE) + { + return array( + 'status' => LOGIN_ERROR_ACTIVE, + 'error_msg' => 'ACTIVE_ERROR', + 'user_row' => $row, + ); + } + + // Successful login... set user_login_attempts to zero... + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + else + { + // retrieve default group id + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' + AND group_type = " . GROUP_SPECIAL; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + trigger_error('NO_GROUP'); + } + + // generate user account data + $ldap_user_row = array( + 'username' => $username, + 'user_password' => $this->passwords_manager->hash($password), + 'user_email' => (!empty($this->config['ldap_email'])) ? utf8_htmlspecialchars($ldap_result[0][htmlspecialchars_decode($this->config['ldap_email'])][0]) : '', + 'group_id' => (int) $row['group_id'], + 'user_type' => USER_NORMAL, + 'user_ip' => $this->user->ip, + 'user_new' => ($this->config['new_member_post_limit']) ? 1 : 0, + ); + + unset($ldap_result); + + // this is the user's first login so create an empty profile + return array( + 'status' => LOGIN_SUCCESS_CREATE_PROFILE, + 'error_msg' => false, + 'user_row' => $ldap_user_row, + ); + } + } + else + { + unset($ldap_result); + @ldap_close($ldap); + + // Give status about wrong password... + return array( + 'status' => LOGIN_ERROR_PASSWORD, + 'error_msg' => 'LOGIN_ERROR_PASSWORD', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + } + + @ldap_close($ldap); + + return array( + 'status' => LOGIN_ERROR_USERNAME, + 'error_msg' => 'LOGIN_ERROR_USERNAME', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + /** + * {@inheritdoc} + */ + public function acp() + { + // These are fields required in the config table + return array( + 'ldap_server', 'ldap_port', 'ldap_base_dn', 'ldap_uid', 'ldap_user_filter', 'ldap_email', 'ldap_user', 'ldap_password', + ); + } + + /** + * {@inheritdoc} + */ + public function get_acp_template($new_config) + { + return array( + 'TEMPLATE_FILE' => 'auth_provider_ldap.html', + 'TEMPLATE_VARS' => array( + 'AUTH_LDAP_BASE_DN' => $new_config['ldap_base_dn'], + 'AUTH_LDAP_EMAIL' => $new_config['ldap_email'], + 'AUTH_LDAP_PASSORD' => $new_config['ldap_password'] !== '' ? '********' : '', + 'AUTH_LDAP_PORT' => $new_config['ldap_port'], + 'AUTH_LDAP_SERVER' => $new_config['ldap_server'], + 'AUTH_LDAP_UID' => $new_config['ldap_uid'], + 'AUTH_LDAP_USER' => $new_config['ldap_user'], + 'AUTH_LDAP_USER_FILTER' => $new_config['ldap_user_filter'], + ), + ); + } + + /** + * Generates a filter string for ldap_search to find a user + * + * @param $username string Username identifying the searched user + * + * @return string A filter string for ldap_search + */ + private function ldap_user_filter($username) + { + $filter = '(' . $this->config['ldap_uid'] . '=' . $this->ldap_escape(htmlspecialchars_decode($username)) . ')'; + if ($this->config['ldap_user_filter']) + { + $_filter = ($this->config['ldap_user_filter'][0] == '(' && substr($this->config['ldap_user_filter'], -1) == ')') ? $this->config['ldap_user_filter'] : "({$this->config['ldap_user_filter']})"; + $filter = "(&{$filter}{$_filter})"; + } + return $filter; + } + + /** + * Escapes an LDAP AttributeValue + * + * @param string $string The string to be escaped + * @return string The escaped string + */ + private function ldap_escape($string) + { + return str_replace(array('*', '\\', '(', ')'), array('\\*', '\\\\', '\\(', '\\)'), $string); + } +} diff --git a/phpbb/auth/provider/oauth/oauth.php b/phpbb/auth/provider/oauth/oauth.php new file mode 100644 index 0000000..93419d2 --- /dev/null +++ b/phpbb/auth/provider/oauth/oauth.php @@ -0,0 +1,747 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth; + +use OAuth\Common\Consumer\Credentials; + +/** +* OAuth authentication provider for phpBB3 +*/ +class oauth extends \phpbb\auth\provider\base +{ + /** + * Database driver + * + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * phpBB config + * + * @var \phpbb\config\config + */ + protected $config; + + /** + * phpBB passwords manager + * + * @var \phpbb\passwords\manager + */ + protected $passwords_manager; + + /** + * phpBB request object + * + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * phpBB user + * + * @var \phpbb\user + */ + protected $user; + + /** + * OAuth token table + * + * @var string + */ + protected $auth_provider_oauth_token_storage_table; + + /** + * OAuth state table + * + * @var string + */ + protected $auth_provider_oauth_state_table; + + /** + * OAuth account association table + * + * @var string + */ + protected $auth_provider_oauth_token_account_assoc; + + /** + * All OAuth service providers + * + * @var \phpbb\di\service_collection Contains \phpbb\auth\provider\oauth\service_interface + */ + protected $service_providers; + + /** + * Users table + * + * @var string + */ + protected $users_table; + + /** + * Cached current uri object + * + * @var \OAuth\Common\Http\Uri\UriInterface|null + */ + protected $current_uri; + + /** + * DI container + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $phpbb_container; + + /** + * phpBB event dispatcher + * + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * phpBB root path + * + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP file extension + * + * @var string + */ + protected $php_ext; + + /** + * OAuth Authentication Constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\config\config $config + * @param \phpbb\passwords\manager $passwords_manager + * @param \phpbb\request\request_interface $request + * @param \phpbb\user $user + * @param string $auth_provider_oauth_token_storage_table + * @param string $auth_provider_oauth_state_table + * @param string $auth_provider_oauth_token_account_assoc + * @param \phpbb\di\service_collection $service_providers Contains \phpbb\auth\provider\oauth\service_interface + * @param string $users_table + * @param \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container DI container + * @param \phpbb\event\dispatcher_interface $dispatcher phpBB event dispatcher + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\passwords\manager $passwords_manager, \phpbb\request\request_interface $request, \phpbb\user $user, $auth_provider_oauth_token_storage_table, $auth_provider_oauth_state_table, $auth_provider_oauth_token_account_assoc, \phpbb\di\service_collection $service_providers, $users_table, \Symfony\Component\DependencyInjection\ContainerInterface $phpbb_container, \phpbb\event\dispatcher_interface $dispatcher, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->config = $config; + $this->passwords_manager = $passwords_manager; + $this->request = $request; + $this->user = $user; + $this->auth_provider_oauth_token_storage_table = $auth_provider_oauth_token_storage_table; + $this->auth_provider_oauth_state_table = $auth_provider_oauth_state_table; + $this->auth_provider_oauth_token_account_assoc = $auth_provider_oauth_token_account_assoc; + $this->service_providers = $service_providers; + $this->users_table = $users_table; + $this->phpbb_container = $phpbb_container; + $this->dispatcher = $dispatcher; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function init() + { + // This does not test whether or not the key and secret provided are valid. + foreach ($this->service_providers as $service_provider) + { + $credentials = $service_provider->get_service_credentials(); + + if (($credentials['key'] && !$credentials['secret']) || (!$credentials['key'] && $credentials['secret'])) + { + return $this->user->lang['AUTH_PROVIDER_OAUTH_ERROR_ELEMENT_MISSING']; + } + } + return false; + } + + /** + * {@inheritdoc} + */ + public function login($username, $password) + { + // Temporary workaround for only having one authentication provider available + if (!$this->request->is_set('oauth_service')) + { + $provider = new \phpbb\auth\provider\db($this->db, $this->config, $this->passwords_manager, $this->request, $this->user, $this->phpbb_container, $this->phpbb_root_path, $this->php_ext); + return $provider->login($username, $password); + } + + // Request the name of the OAuth service + $service_name_original = $this->request->variable('oauth_service', '', false); + $service_name = 'auth.provider.oauth.service.' . strtolower($service_name_original); + if ($service_name_original === '' || !array_key_exists($service_name, $this->service_providers)) + { + return array( + 'status' => LOGIN_ERROR_EXTERNAL_AUTH, + 'error_msg' => 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST', + 'user_row' => array('user_id' => ANONYMOUS), + ); + } + + // Get the service credentials for the given service + $service_credentials = $this->service_providers[$service_name]->get_service_credentials(); + + $storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table); + $query = 'mode=login&login=external&oauth_service=' . $service_name_original; + $service = $this->get_service($service_name_original, $storage, $service_credentials, $query, $this->service_providers[$service_name]->get_auth_scope()); + + if (($service::OAUTH_VERSION === 2 && $this->request->is_set('code', \phpbb\request\request_interface::GET)) + || ($service::OAUTH_VERSION === 1 && $this->request->is_set('oauth_token', \phpbb\request\request_interface::GET))) + { + $this->service_providers[$service_name]->set_external_service_provider($service); + $unique_id = $this->service_providers[$service_name]->perform_auth_login(); + + // Check to see if this provider is already assosciated with an account + $data = array( + 'provider' => $service_name_original, + 'oauth_provider_id' => $unique_id + ); + + $sql = 'SELECT user_id FROM ' . $this->auth_provider_oauth_token_account_assoc . ' + WHERE ' . $this->db->sql_build_array('SELECT', $data); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $redirect_data = array( + 'auth_provider' => 'oauth', + 'login_link_oauth_service' => $service_name_original, + ); + + /** + * Event is triggered before check if provider is already associated with an account + * + * @event core.oauth_login_after_check_if_provider_id_has_match + * @var array row User row + * @var array data Provider data + * @var array redirect_data Data to be appended to the redirect url + * @var \OAuth\Common\Service\ServiceInterface service OAuth service + * @since 3.2.3-RC1 + * @changed 3.2.6-RC1 Added redirect_data + */ + $vars = array( + 'row', + 'data', + 'redirect_data', + 'service', + ); + extract($this->dispatcher->trigger_event('core.oauth_login_after_check_if_provider_id_has_match', compact($vars))); + + if (!$row) + { + // The user does not yet exist, ask to link or create profile + return array( + 'status' => LOGIN_SUCCESS_LINK_PROFILE, + 'error_msg' => 'LOGIN_OAUTH_ACCOUNT_NOT_LINKED', + 'user_row' => array(), + 'redirect_data' => $redirect_data, + ); + } + + // Retrieve the user's account + $sql = 'SELECT user_id, username, user_password, user_passchg, user_email, user_type, user_login_attempts + FROM ' . $this->users_table . ' + WHERE user_id = ' . (int) $row['user_id']; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + throw new \Exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_ENTRY'); + } + + /** + * Check if the user is banned. + * The fourth parameter, return, has to be true, + * otherwise the OAuth login is still called and + * an uncaught exception is thrown as there is no + * token stored in the database. + */ + $ban = $this->user->check_ban($row['user_id'], $row['user_ip'], $row['user_email'], true); + if (!empty($ban)) + { + $till_date = !empty($ban['ban_end']) ? $this->user->format_date($ban['ban_end']) : ''; + $message = !empty($ban['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; + + $contact_link = phpbb_get_board_contact_link($this->config, $this->phpbb_root_path, $this->php_ext); + $message = $this->user->lang($message, $till_date, '', ''); + $message .= !empty($ban['ban_give_reason']) ? '

' . $this->user->lang('BOARD_BAN_REASON', $ban['ban_give_reason']) : ''; + $message .= !empty($ban['ban_triggered_by']) ? '

' . $this->user->lang('BAN_TRIGGERED_BY_' . strtoupper($ban['ban_triggered_by'])) . '' : ''; + + return array( + 'status' => LOGIN_BREAK, + 'error_msg' => $message, + 'user_row' => $row, + ); + } + + // Update token storage to store the user_id + $storage->set_user_id($row['user_id']); + + /** + * Event is triggered after user is successfully logged in via OAuth. + * + * @event core.auth_oauth_login_after + * @var array row User row + * @since 3.1.11-RC1 + */ + $vars = array( + 'row', + ); + extract($this->dispatcher->trigger_event('core.auth_oauth_login_after', compact($vars))); + + // The user is now authenticated and can be logged in + return array( + 'status' => LOGIN_SUCCESS, + 'error_msg' => false, + 'user_row' => $row, + ); + } + else + { + if ($service::OAUTH_VERSION === 1) + { + $token = $service->requestRequestToken(); + $url = $service->getAuthorizationUri(array('oauth_token' => $token->getRequestToken())); + } + else + { + $url = $service->getAuthorizationUri(); + } + header('Location: ' . $url); + } + } + + /** + * Returns the cached current_uri object or creates and caches it if it is + * not already created. In each case the query string is updated based on + * the $query parameter. + * + * @param string $service_name The name of the service + * @param string $query The query string of the current_uri + * used in redirects + * @return \OAuth\Common\Http\Uri\UriInterface + */ + protected function get_current_uri($service_name, $query) + { + if ($this->current_uri) + { + $this->current_uri->setQuery($query); + return $this->current_uri; + } + + $uri_factory = new \OAuth\Common\Http\Uri\UriFactory(); + $super_globals = $this->request->get_super_global(\phpbb\request\request_interface::SERVER); + if (!empty($super_globals['HTTP_X_FORWARDED_PROTO']) && $super_globals['HTTP_X_FORWARDED_PROTO'] === 'https') + { + $super_globals['HTTPS'] = 'on'; + $super_globals['SERVER_PORT'] = 443; + } + $current_uri = $uri_factory->createFromSuperGlobalArray($super_globals); + $current_uri->setQuery($query); + + $this->current_uri = $current_uri; + return $current_uri; + } + + /** + * Returns a new service object + * + * @param string $service_name The name of the service + * @param \phpbb\auth\provider\oauth\token_storage $storage + * @param array $service_credentials {@see \phpbb\auth\provider\oauth\oauth::get_service_credentials} + * @param string $query The query string of the + * current_uri used in redirection + * @param array $scopes The scope of the request against + * the api. + * @return \OAuth\Common\Service\ServiceInterface + * @throws \Exception + */ + protected function get_service($service_name, \phpbb\auth\provider\oauth\token_storage $storage, array $service_credentials, $query, array $scopes = array()) + { + $current_uri = $this->get_current_uri($service_name, $query); + + // Setup the credentials for the requests + $credentials = new Credentials( + $service_credentials['key'], + $service_credentials['secret'], + $current_uri->getAbsoluteUri() + ); + + $service_factory = new \OAuth\ServiceFactory(); + $service = $service_factory->createService($service_name, $credentials, $storage, $scopes); + + if (!$service) + { + throw new \Exception('AUTH_PROVIDER_OAUTH_ERROR_SERVICE_NOT_CREATED'); + } + + return $service; + } + + /** + * {@inheritdoc} + */ + public function get_login_data() + { + $login_data = array( + 'TEMPLATE_FILE' => 'login_body_oauth.html', + 'BLOCK_VAR_NAME' => 'oauth', + 'BLOCK_VARS' => array(), + ); + + foreach ($this->service_providers as $service_name => $service_provider) + { + // Only include data if the credentials are set + $credentials = $service_provider->get_service_credentials(); + if ($credentials['key'] && $credentials['secret']) + { + $actual_name = str_replace('auth.provider.oauth.service.', '', $service_name); + $redirect_url = build_url(false) . '&login=external&oauth_service=' . $actual_name; + $login_data['BLOCK_VARS'][$service_name] = array( + 'REDIRECT_URL' => redirect($redirect_url, true), + 'SERVICE_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)], + ); + } + } + + return $login_data; + } + + /** + * {@inheritdoc} + */ + public function acp() + { + $ret = array(); + + foreach ($this->service_providers as $service_name => $service_provider) + { + $actual_name = str_replace('auth.provider.oauth.service.', '', $service_name); + $ret[] = 'auth_oauth_' . $actual_name . '_key'; + $ret[] = 'auth_oauth_' . $actual_name . '_secret'; + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function get_acp_template($new_config) + { + $ret = array( + 'BLOCK_VAR_NAME' => 'oauth_services', + 'BLOCK_VARS' => array(), + 'TEMPLATE_FILE' => 'auth_provider_oauth.html', + 'TEMPLATE_VARS' => array(), + ); + + foreach ($this->service_providers as $service_name => $service_provider) + { + $actual_name = str_replace('auth.provider.oauth.service.', '', $service_name); + $ret['BLOCK_VARS'][$actual_name] = array( + 'ACTUAL_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)], + 'KEY' => $new_config['auth_oauth_' . $actual_name . '_key'], + 'NAME' => $actual_name, + 'SECRET' => $new_config['auth_oauth_' . $actual_name . '_secret'], + ); + } + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function login_link_has_necessary_data($login_link_data) + { + if (empty($login_link_data)) + { + return 'LOGIN_LINK_NO_DATA_PROVIDED'; + } + + if (!array_key_exists('oauth_service', $login_link_data) || !$login_link_data['oauth_service'] || + !array_key_exists('link_method', $login_link_data) || !$login_link_data['link_method']) + { + return 'LOGIN_LINK_MISSING_DATA'; + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function link_account(array $link_data) + { + // Check for a valid link method (auth_link or login_link) + if (!array_key_exists('link_method', $link_data) || + !in_array($link_data['link_method'], array( + 'auth_link', + 'login_link', + ))) + { + return 'LOGIN_LINK_MISSING_DATA'; + } + + // We must have an oauth_service listed, check for it two ways + if (!array_key_exists('oauth_service', $link_data) || !$link_data['oauth_service']) + { + $link_data['oauth_service'] = $this->request->variable('oauth_service', ''); + + if (!$link_data['oauth_service']) + { + return 'LOGIN_LINK_MISSING_DATA'; + } + } + + $service_name = 'auth.provider.oauth.service.' . strtolower($link_data['oauth_service']); + if (!array_key_exists($service_name, $this->service_providers)) + { + return 'LOGIN_ERROR_OAUTH_SERVICE_DOES_NOT_EXIST'; + } + + switch ($link_data['link_method']) + { + case 'auth_link': + return $this->link_account_auth_link($link_data, $service_name); + case 'login_link': + return $this->link_account_login_link($link_data, $service_name); + } + } + + /** + * Performs the account linking for login_link + * + * @param array $link_data The same variable given to {@see \phpbb\auth\provider\provider_interface::link_account} + * @param string $service_name The name of the service being used in + * linking. + * @return string|null Returns a language constant (string) if an error is + * encountered, or null on success. + */ + protected function link_account_login_link(array $link_data, $service_name) + { + $storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table); + + // Check for an access token, they should have one + if (!$storage->has_access_token_by_session($service_name)) + { + return 'LOGIN_LINK_ERROR_OAUTH_NO_ACCESS_TOKEN'; + } + + // Prepare the query string + $query = 'mode=login_link&login_link_oauth_service=' . strtolower($link_data['oauth_service']); + + // Prepare for an authentication request + $service_credentials = $this->service_providers[$service_name]->get_service_credentials(); + $scopes = $this->service_providers[$service_name]->get_auth_scope(); + $service = $this->get_service(strtolower($link_data['oauth_service']), $storage, $service_credentials, $query, $scopes); + $this->service_providers[$service_name]->set_external_service_provider($service); + + // The user has already authenticated successfully, request to authenticate again + $unique_id = $this->service_providers[$service_name]->perform_token_auth(); + + // Insert into table, they will be able to log in after this + $data = array( + 'user_id' => $link_data['user_id'], + 'provider' => strtolower($link_data['oauth_service']), + 'oauth_provider_id' => $unique_id, + ); + + $this->link_account_perform_link($data); + // Update token storage to store the user_id + $storage->set_user_id($link_data['user_id']); + } + + /** + * Performs the account linking for auth_link + * + * @param array $link_data The same variable given to {@see \phpbb\auth\provider\provider_interface::link_account} + * @param string $service_name The name of the service being used in + * linking. + * @return string|null Returns a language constant (string) if an error is + * encountered, or null on success. + */ + protected function link_account_auth_link(array $link_data, $service_name) + { + $storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table); + $query = 'i=ucp_auth_link&mode=auth_link&link=1&oauth_service=' . strtolower($link_data['oauth_service']); + $service_credentials = $this->service_providers[$service_name]->get_service_credentials(); + $scopes = $this->service_providers[$service_name]->get_auth_scope(); + $service = $this->get_service(strtolower($link_data['oauth_service']), $storage, $service_credentials, $query, $scopes); + + if (($service::OAUTH_VERSION === 2 && $this->request->is_set('code', \phpbb\request\request_interface::GET)) + || ($service::OAUTH_VERSION === 1 && $this->request->is_set('oauth_token', \phpbb\request\request_interface::GET))) + { + $this->service_providers[$service_name]->set_external_service_provider($service); + $unique_id = $this->service_providers[$service_name]->perform_auth_login(); + + // Insert into table, they will be able to log in after this + $data = array( + 'user_id' => $this->user->data['user_id'], + 'provider' => strtolower($link_data['oauth_service']), + 'oauth_provider_id' => $unique_id, + ); + + $this->link_account_perform_link($data); + } + else + { + if ($service::OAUTH_VERSION === 1) + { + $token = $service->requestRequestToken(); + $url = $service->getAuthorizationUri(array('oauth_token' => $token->getRequestToken())); + } + else + { + $url = $service->getAuthorizationUri(); + } + header('Location: ' . $url); + } + } + + /** + * Performs the query that inserts an account link + * + * @param array $data This array is passed to db->sql_build_array + */ + protected function link_account_perform_link(array $data) + { + $sql = 'INSERT INTO ' . $this->auth_provider_oauth_token_account_assoc . ' + ' . $this->db->sql_build_array('INSERT', $data); + $this->db->sql_query($sql); + + /** + * Event is triggered after user links account. + * + * @event core.auth_oauth_link_after + * @var array data User row + * @since 3.1.11-RC1 + */ + $vars = array( + 'data', + ); + extract($this->dispatcher->trigger_event('core.auth_oauth_link_after', compact($vars))); + } + + /** + * {@inheritdoc} + */ + public function logout($data, $new_session) + { + // Clear all tokens belonging to the user + $storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table); + $storage->clearAllTokens(); + + return; + } + + /** + * {@inheritdoc} + */ + public function get_auth_link_data($user_id = 0) + { + $block_vars = array(); + + // Get all external accounts tied to the current user + $data = array( + 'user_id' => ($user_id <= 0) ? (int) $this->user->data['user_id'] : (int) $user_id, + ); + $sql = 'SELECT oauth_provider_id, provider FROM ' . $this->auth_provider_oauth_token_account_assoc . ' + WHERE ' . $this->db->sql_build_array('SELECT', $data); + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + $oauth_user_ids = array(); + + if ($rows !== false && count($rows)) + { + foreach ($rows as $row) + { + $oauth_user_ids[$row['provider']] = $row['oauth_provider_id']; + } + } + unset($rows); + + foreach ($this->service_providers as $service_name => $service_provider) + { + // Only include data if the credentials are set + $credentials = $service_provider->get_service_credentials(); + if ($credentials['key'] && $credentials['secret']) + { + $actual_name = str_replace('auth.provider.oauth.service.', '', $service_name); + + $block_vars[$service_name] = array( + 'HIDDEN_FIELDS' => array( + 'link' => (!isset($oauth_user_ids[$actual_name])), + 'oauth_service' => $actual_name, + ), + + 'SERVICE_ID' => $actual_name, + 'SERVICE_NAME' => $this->user->lang['AUTH_PROVIDER_OAUTH_SERVICE_' . strtoupper($actual_name)], + 'UNIQUE_ID' => (isset($oauth_user_ids[$actual_name])) ? $oauth_user_ids[$actual_name] : null, + ); + } + } + + return array( + 'BLOCK_VAR_NAME' => 'oauth', + 'BLOCK_VARS' => $block_vars, + + 'TEMPLATE_FILE' => 'ucp_auth_link_oauth.html', + ); + } + + /** + * {@inheritdoc} + */ + public function unlink_account(array $link_data) + { + if (!array_key_exists('oauth_service', $link_data) || !$link_data['oauth_service']) + { + return 'LOGIN_LINK_MISSING_DATA'; + } + + // Remove user specified in $link_data if possible + $user_id = isset($link_data['user_id']) ? $link_data['user_id'] : $this->user->data['user_id']; + + // Remove the link + $sql = 'DELETE FROM ' . $this->auth_provider_oauth_token_account_assoc . " + WHERE provider = '" . $this->db->sql_escape($link_data['oauth_service']) . "' + AND user_id = " . (int) $user_id; + $this->db->sql_query($sql); + + // Clear all tokens belonging to the user on this service + $service_name = 'auth.provider.oauth.service.' . strtolower($link_data['oauth_service']); + $storage = new \phpbb\auth\provider\oauth\token_storage($this->db, $this->user, $this->auth_provider_oauth_token_storage_table, $this->auth_provider_oauth_state_table); + $storage->clearToken($service_name); + } +} diff --git a/phpbb/auth/provider/oauth/service/base.php b/phpbb/auth/provider/oauth/service/base.php new file mode 100644 index 0000000..6adf64a --- /dev/null +++ b/phpbb/auth/provider/oauth/service/base.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth\service; + +/** +* Base OAuth abstract class that all OAuth services should implement +*/ +abstract class base implements \phpbb\auth\provider\oauth\service\service_interface +{ + /** + * External OAuth service provider + * + * @var \OAuth\Common\Service\ServiceInterface + */ + protected $service_provider; + + /** + * {@inheritdoc} + */ + public function get_external_service_provider() + { + return $this->service_provider; + } + + /** + * {@inheritdoc} + */ + public function get_auth_scope() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function set_external_service_provider(\OAuth\Common\Service\ServiceInterface $service_provider) + { + $this->service_provider = $service_provider; + } +} diff --git a/phpbb/auth/provider/oauth/service/bitly.php b/phpbb/auth/provider/oauth/service/bitly.php new file mode 100644 index 0000000..25e731a --- /dev/null +++ b/phpbb/auth/provider/oauth/service/bitly.php @@ -0,0 +1,94 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth\service; + +/** +* Bitly OAuth service +*/ +class bitly extends \phpbb\auth\provider\oauth\service\base +{ + /** + * phpBB config + * + * @var \phpbb\config\config + */ + protected $config; + + /** + * phpBB request + * + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * Constructor + * + * @param \phpbb\config\config $config + * @param \phpbb\request\request_interface $request + */ + public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request) + { + $this->config = $config; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function get_service_credentials() + { + return array( + 'key' => $this->config['auth_oauth_bitly_key'], + 'secret' => $this->config['auth_oauth_bitly_secret'], + ); + } + + /** + * {@inheritdoc} + */ + public function perform_auth_login() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Bitly)) + { + throw new \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // This was a callback request from bitly, get the token + $this->service_provider->requestAccessToken($this->request->variable('code', '')); + + // Send a request with it + $result = json_decode($this->service_provider->request('user/info'), true); + + // Return the unique identifier returned from bitly + return $result['data']['login']; + } + + /** + * {@inheritdoc} + */ + public function perform_token_auth() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Bitly)) + { + throw new \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // Send a request with it + $result = json_decode($this->service_provider->request('user/info'), true); + + // Return the unique identifier returned from bitly + return $result['data']['login']; + } +} diff --git a/phpbb/auth/provider/oauth/service/exception.php b/phpbb/auth/provider/oauth/service/exception.php new file mode 100644 index 0000000..d3e95be --- /dev/null +++ b/phpbb/auth/provider/oauth/service/exception.php @@ -0,0 +1,21 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth\service; + +/** +* OAuth service exception class +*/ +class exception extends \RuntimeException +{ +} diff --git a/phpbb/auth/provider/oauth/service/facebook.php b/phpbb/auth/provider/oauth/service/facebook.php new file mode 100644 index 0000000..bb98835 --- /dev/null +++ b/phpbb/auth/provider/oauth/service/facebook.php @@ -0,0 +1,94 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth\service; + +/** +* Facebook OAuth service +*/ +class facebook extends base +{ + /** + * phpBB config + * + * @var \phpbb\config\config + */ + protected $config; + + /** + * phpBB request + * + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * Constructor + * + * @param \phpbb\config\config $config + * @param \phpbb\request\request_interface $request + */ + public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request) + { + $this->config = $config; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function get_service_credentials() + { + return array( + 'key' => $this->config['auth_oauth_facebook_key'], + 'secret' => $this->config['auth_oauth_facebook_secret'], + ); + } + + /** + * {@inheritdoc} + */ + public function perform_auth_login() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Facebook)) + { + throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // This was a callback request, get the token + $this->service_provider->requestAccessToken($this->request->variable('code', '')); + + // Send a request with it + $result = json_decode($this->service_provider->request('/me'), true); + + // Return the unique identifier + return $result['id']; + } + + /** + * {@inheritdoc} + */ + public function perform_token_auth() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Facebook)) + { + throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // Send a request with it + $result = json_decode($this->service_provider->request('/me'), true); + + // Return the unique identifier + return $result['id']; + } +} diff --git a/phpbb/auth/provider/oauth/service/google.php b/phpbb/auth/provider/oauth/service/google.php new file mode 100644 index 0000000..cb9f83a --- /dev/null +++ b/phpbb/auth/provider/oauth/service/google.php @@ -0,0 +1,105 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth\service; + +/** +* Google OAuth service +*/ +class google extends base +{ + /** + * phpBB config + * + * @var \phpbb\config\config + */ + protected $config; + + /** + * phpBB request + * + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * Constructor + * + * @param \phpbb\config\config $config + * @param \phpbb\request\request_interface $request + */ + public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request) + { + $this->config = $config; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function get_auth_scope() + { + return array( + 'userinfo_email', + 'userinfo_profile', + ); + } + + /** + * {@inheritdoc} + */ + public function get_service_credentials() + { + return array( + 'key' => $this->config['auth_oauth_google_key'], + 'secret' => $this->config['auth_oauth_google_secret'], + ); + } + + /** + * {@inheritdoc} + */ + public function perform_auth_login() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Google)) + { + throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // This was a callback request, get the token + $this->service_provider->requestAccessToken($this->request->variable('code', '')); + + // Send a request with it + $result = json_decode($this->service_provider->request('https://www.googleapis.com/oauth2/v1/userinfo'), true); + + // Return the unique identifier + return $result['id']; + } + + /** + * {@inheritdoc} + */ + public function perform_token_auth() + { + if (!($this->service_provider instanceof \OAuth\OAuth2\Service\Google)) + { + throw new exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // Send a request with it + $result = json_decode($this->service_provider->request('https://www.googleapis.com/oauth2/v1/userinfo'), true); + + // Return the unique identifier + return $result['id']; + } +} diff --git a/phpbb/auth/provider/oauth/service/service_interface.php b/phpbb/auth/provider/oauth/service/service_interface.php new file mode 100644 index 0000000..e84eb24 --- /dev/null +++ b/phpbb/auth/provider/oauth/service/service_interface.php @@ -0,0 +1,73 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth\service; + +/** +* OAuth service interface +*/ +interface service_interface +{ + /** + * Returns an array of the scopes necessary for auth + * + * @return array An array of the required scopes + */ + public function get_auth_scope(); + + /** + * Returns the external library service provider once it has been set + * + * @param \OAuth\Common\Service\ServiceInterface|null + */ + public function get_external_service_provider(); + + /** + * Returns an array containing the service credentials belonging to requested + * service. + * + * @return array An array containing the 'key' and the 'secret' of the + * service in the form: + * array( + * 'key' => string + * 'secret' => string + * ) + */ + public function get_service_credentials(); + + /** + * Returns the results of the authentication in json format + * + * @throws \phpbb\auth\provider\oauth\service\exception + * @return string The unique identifier returned by the service provider + * that is used to authenticate the user with phpBB. + */ + public function perform_auth_login(); + + /** + * Returns the results of the authentication in json format + * Use this function when the user already has an access token + * + * @throws \phpbb\auth\provider\oauth\service\exception + * @return string The unique identifier returned by the service provider + * that is used to authenticate the user with phpBB. + */ + public function perform_token_auth(); + + /** + * Sets the external library service provider + * + * @param \OAuth\Common\Service\ServiceInterface $service_provider + */ + public function set_external_service_provider(\OAuth\Common\Service\ServiceInterface $service_provider); +} diff --git a/phpbb/auth/provider/oauth/service/twitter.php b/phpbb/auth/provider/oauth/service/twitter.php new file mode 100644 index 0000000..06beac5 --- /dev/null +++ b/phpbb/auth/provider/oauth/service/twitter.php @@ -0,0 +1,102 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth\service; + +/** +* Twitter OAuth service +*/ +class twitter extends \phpbb\auth\provider\oauth\service\base +{ + /** + * phpBB config + * + * @var \phpbb\config\config + */ + protected $config; + + /** + * phpBB request + * + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * Constructor + * + * @param \phpbb\config\config $config + * @param \phpbb\request\request_interface $request + */ + public function __construct(\phpbb\config\config $config, \phpbb\request\request_interface $request) + { + $this->config = $config; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function get_service_credentials() + { + return array( + 'key' => $this->config['auth_oauth_twitter_key'], + 'secret' => $this->config['auth_oauth_twitter_secret'], + ); + } + + /** + * {@inheritdoc} + */ + public function perform_auth_login() + { + if (!($this->service_provider instanceof \OAuth\OAuth1\Service\Twitter)) + { + throw new \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + $storage = $this->service_provider->getStorage(); + $token = $storage->retrieveAccessToken('Twitter'); + $tokensecret = $token->getRequestTokenSecret(); + + // This was a callback request from twitter, get the token + $this->service_provider->requestAccessToken( + $this->request->variable('oauth_token', ''), + $this->request->variable('oauth_verifier', ''), + $tokensecret + ); + + // Send a request with it + $result = json_decode($this->service_provider->request('account/verify_credentials.json'), true); + + // Return the unique identifier returned from twitter + return $result['id']; + } + + /** + * {@inheritdoc} + */ + public function perform_token_auth() + { + if (!($this->service_provider instanceof \OAuth\OAuth1\Service\Twitter)) + { + throw new \phpbb\auth\provider\oauth\service\exception('AUTH_PROVIDER_OAUTH_ERROR_INVALID_SERVICE_TYPE'); + } + + // Send a request with it + $result = json_decode($this->service_provider->request('account/verify_credentials.json'), true); + + // Return the unique identifier returned from twitter + return $result['id']; + } +} diff --git a/phpbb/auth/provider/oauth/token_storage.php b/phpbb/auth/provider/oauth/token_storage.php new file mode 100644 index 0000000..b0c2fd0 --- /dev/null +++ b/phpbb/auth/provider/oauth/token_storage.php @@ -0,0 +1,592 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider\oauth; + +use OAuth\OAuth1\Token\StdOAuth1Token; +use OAuth\Common\Token\TokenInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Storage\Exception\TokenNotFoundException; +use OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException; + +/** +* OAuth storage wrapper for phpbb's cache +*/ +class token_storage implements TokenStorageInterface +{ + /** + * Cache driver. + * + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * phpBB user + * + * @var \phpbb\user + */ + protected $user; + + /** + * OAuth token table + * + * @var string + */ + protected $oauth_token_table; + + /** + * OAuth state table + * + * @var string + */ + protected $oauth_state_table; + + /** + * @var object|TokenInterface + */ + protected $cachedToken; + + /** + * @var string + */ + protected $cachedState; + + /** + * Creates token storage for phpBB. + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\user $user + * @param string $oauth_token_table + * @param string $oauth_state_table + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\user $user, $oauth_token_table, $oauth_state_table) + { + $this->db = $db; + $this->user = $user; + $this->oauth_token_table = $oauth_token_table; + $this->oauth_state_table = $oauth_state_table; + } + + /** + * {@inheritdoc} + */ + public function retrieveAccessToken($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedToken instanceof TokenInterface) + { + return $this->cachedToken; + } + + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + ); + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $data['session_id'] = $this->user->data['session_id']; + } + + return $this->_retrieve_access_token($data); + } + + /** + * {@inheritdoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + $service = $this->get_service_name_for_db($service); + + $this->cachedToken = $token; + + $data = array( + 'oauth_token' => $this->json_encode_token($token), + ); + + $sql = 'UPDATE ' . $this->oauth_token_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE user_id = ' . (int) $this->user->data['user_id'] . ' + ' . ((int) $this->user->data['user_id'] === ANONYMOUS ? "AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'" : '') . " + AND provider = '" . $this->db->sql_escape($service) . "'"; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + 'oauth_token' => $this->json_encode_token($token), + 'session_id' => $this->user->data['session_id'], + ); + + $sql = 'INSERT INTO ' . $this->oauth_token_table . $this->db->sql_build_array('INSERT', $data); + + $this->db->sql_query($sql); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasAccessToken($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedToken) + { + return true; + } + + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + ); + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $data['session_id'] = $this->user->data['session_id']; + } + + return $this->_has_acess_token($data); + } + + /** + * {@inheritdoc} + */ + public function clearToken($service) + { + $service = $this->get_service_name_for_db($service); + + $this->cachedToken = null; + + $sql = 'DELETE FROM ' . $this->oauth_token_table . ' + WHERE user_id = ' . (int) $this->user->data['user_id'] . " + AND provider = '" . $this->db->sql_escape($service) . "'"; + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; + } + + $this->db->sql_query($sql); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function clearAllTokens() + { + $this->cachedToken = null; + + $sql = 'DELETE FROM ' . $this->oauth_token_table . ' + WHERE user_id = ' . (int) $this->user->data['user_id']; + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; + } + + $this->db->sql_query($sql); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function storeAuthorizationState($service, $state) + { + $service = $this->get_service_name_for_db($service); + + $this->cachedState = $state; + + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + 'oauth_state' => $state, + 'session_id' => $this->user->data['session_id'], + ); + + $sql = 'INSERT INTO ' . $this->oauth_state_table . ' + ' . $this->db->sql_build_array('INSERT', $data); + $this->db->sql_query($sql); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function hasAuthorizationState($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedState) + { + return true; + } + + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + ); + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $data['session_id'] = $this->user->data['session_id']; + } + + return (bool) $this->get_state_row($data); + } + + /** + * {@inheritdoc} + */ + public function retrieveAuthorizationState($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedState) + { + return $this->cachedState; + } + + $data = array( + 'user_id' => (int) $this->user->data['user_id'], + 'provider' => $service, + ); + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $data['session_id'] = $this->user->data['session_id']; + } + + return $this->get_state_row($data); + } + + /** + * {@inheritdoc} + */ + public function clearAuthorizationState($service) + { + $service = $this->get_service_name_for_db($service); + + $this->cachedState = null; + + $sql = 'DELETE FROM ' . $this->oauth_state_table . ' + WHERE user_id = ' . (int) $this->user->data['user_id'] . " + AND provider = '" . $this->db->sql_escape($service) . "'"; + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; + } + + $this->db->sql_query($sql); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function clearAllAuthorizationStates() + { + $this->cachedState = null; + + $sql = 'DELETE FROM ' . $this->oauth_state_table . ' + WHERE user_id = ' . (int) $this->user->data['user_id']; + + if ((int) $this->user->data['user_id'] === ANONYMOUS) + { + $sql .= " AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; + } + + $this->db->sql_query($sql); + + return $this; + } + + /** + * Updates the user_id field in the database assosciated with the token + * + * @param int $user_id + */ + public function set_user_id($user_id) + { + if (!$this->cachedToken) + { + return; + } + + $sql = 'UPDATE ' . $this->oauth_token_table . ' + SET ' . $this->db->sql_build_array('UPDATE', array( + 'user_id' => (int) $user_id + )) . ' + WHERE user_id = ' . (int) $this->user->data['user_id'] . " + AND session_id = '" . $this->db->sql_escape($this->user->data['session_id']) . "'"; + $this->db->sql_query($sql); + } + + /** + * Checks to see if an access token exists solely by the session_id of the user + * + * @param string $service The name of the OAuth service + * @return bool true if they have token, false if they don't + */ + public function has_access_token_by_session($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedToken) + { + return true; + } + + $data = array( + 'session_id' => $this->user->data['session_id'], + 'provider' => $service, + ); + + return $this->_has_acess_token($data); + } + + /** + * Checks to see if a state exists solely by the session_id of the user + * + * @param string $service The name of the OAuth service + * @return bool true if they have state, false if they don't + */ + public function has_state_by_session($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedState) + { + return true; + } + + $data = array( + 'session_id' => $this->user->data['session_id'], + 'provider' => $service, + ); + + return (bool) $this->get_state_row($data); + } + + /** + * A helper function that performs the query for has access token functions + * + * @param array $data + * @return bool + */ + protected function _has_acess_token($data) + { + return (bool) $this->get_access_token_row($data); + } + + public function retrieve_access_token_by_session($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedToken instanceof TokenInterface) + { + return $this->cachedToken; + } + + $data = array( + 'session_id' => $this->user->data['session_id'], + 'provider' => $service, + ); + + return $this->_retrieve_access_token($data); + } + + public function retrieve_state_by_session($service) + { + $service = $this->get_service_name_for_db($service); + + if ($this->cachedState) + { + return $this->cachedState; + } + + $data = array( + 'session_id' => $this->user->data['session_id'], + 'provider' => $service, + ); + + return $this->_retrieve_state($data); + } + + /** + * A helper function that performs the query for retrieve access token functions + * Also checks if the token is a valid token + * + * @param array $data + * @return mixed + * @throws \OAuth\Common\Storage\Exception\TokenNotFoundException + */ + protected function _retrieve_access_token($data) + { + $row = $this->get_access_token_row($data); + + if (!$row) + { + throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_NOT_STORED'); + } + + $token = $this->json_decode_token($row['oauth_token']); + + // Ensure that the token was serialized/unserialized correctly + if (!($token instanceof TokenInterface)) + { + $this->clearToken($data['provider']); + throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED'); + } + + $this->cachedToken = $token; + return $token; + } + + /** + * A helper function that performs the query for retrieve state functions + * + * @param array $data + * @return mixed + * @throws \OAuth\Common\Storage\Exception\AuthorizationStateNotFoundException + */ + protected function _retrieve_state($data) + { + $row = $this->get_state_row($data); + + if (!$row) + { + throw new AuthorizationStateNotFoundException(); + } + + $this->cachedState = $row['oauth_state']; + return $this->cachedState; + } + + /** + * A helper function that performs the query for retrieving an access token + * + * @param array $data + * @return mixed + */ + protected function get_access_token_row($data) + { + $sql = 'SELECT oauth_token FROM ' . $this->oauth_token_table . ' + WHERE ' . $this->db->sql_build_array('SELECT', $data); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $row; + } + + /** + * A helper function that performs the query for retrieving a state + * + * @param array $data + * @return mixed + */ + protected function get_state_row($data) + { + $sql = 'SELECT oauth_state FROM ' . $this->oauth_state_table . ' + WHERE ' . $this->db->sql_build_array('SELECT', $data); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $row; + } + + public function json_encode_token(TokenInterface $token) + { + $members = array( + 'accessToken' => $token->getAccessToken(), + 'endOfLife' => $token->getEndOfLife(), + 'extraParams' => $token->getExtraParams(), + 'refreshToken' => $token->getRefreshToken(), + + 'token_class' => get_class($token), + ); + + // Handle additional data needed for OAuth1 tokens + if ($token instanceof StdOAuth1Token) + { + $members['requestToken'] = $token->getRequestToken(); + $members['requestTokenSecret'] = $token->getRequestTokenSecret(); + $members['accessTokenSecret'] = $token->getAccessTokenSecret(); + } + + return json_encode($members); + } + + public function json_decode_token($json) + { + $token_data = json_decode($json, true); + + if ($token_data === null) + { + throw new TokenNotFoundException('AUTH_PROVIDER_OAUTH_TOKEN_ERROR_INCORRECTLY_STORED'); + } + + $token_class = $token_data['token_class']; + $access_token = $token_data['accessToken']; + $refresh_token = $token_data['refreshToken']; + $endOfLife = $token_data['endOfLife']; + $extra_params = $token_data['extraParams']; + + // Create the token + $token = new $token_class($access_token, $refresh_token, TokenInterface::EOL_NEVER_EXPIRES, $extra_params); + $token->setEndOfLife($endOfLife); + + // Handle OAuth 1.0 specific elements + if ($token instanceof StdOAuth1Token) + { + $token->setRequestToken($token_data['requestToken']); + $token->setRequestTokenSecret($token_data['requestTokenSecret']); + $token->setAccessTokenSecret($token_data['accessTokenSecret']); + } + + return $token; + } + + /** + * Returns the name of the service as it must be stored in the database. + * + * @param string $service The name of the OAuth service + * @return string The name of the OAuth service as it needs to be stored + * in the database. + */ + protected function get_service_name_for_db($service) + { + // Enforce the naming convention for oauth services + if (strpos($service, 'auth.provider.oauth.service.') !== 0) + { + $service = 'auth.provider.oauth.service.' . strtolower($service); + } + + return $service; + } +} diff --git a/phpbb/auth/provider/provider_interface.php b/phpbb/auth/provider/provider_interface.php new file mode 100644 index 0000000..35e0f55 --- /dev/null +++ b/phpbb/auth/provider/provider_interface.php @@ -0,0 +1,197 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth\provider; + +/** +* The interface authentication provider classes have to implement. +*/ +interface provider_interface +{ + /** + * Checks whether the user is currently identified to the authentication + * provider. + * Called in acp_board while setting authentication plugins. + * Changing to an authentication provider will not be permitted in acp_board + * if there is an error. + * + * @return boolean|string False if the user is identified, otherwise an + * error message, or null if not implemented. + */ + public function init(); + + /** + * Performs login. + * + * @param string $username The name of the user being authenticated. + * @param string $password The password of the user. + * @return array An associative array of the format: + * array( + * 'status' => status constant + * 'error_msg' => string + * 'user_row' => array + * ) + * A fourth key of the array may be present: + * 'redirect_data' This key is only used when 'status' is + * equal to LOGIN_SUCCESS_LINK_PROFILE and its value is an + * associative array that is turned into GET variables on + * the redirect url. + */ + public function login($username, $password); + + /** + * Autologin function + * + * @return array|null containing the user row, empty if no auto login + * should take place, or null if not impletmented. + */ + public function autologin(); + + /** + * This function is used to output any required fields in the authentication + * admin panel. It also defines any required configuration table fields. + * + * @return array|null Returns null if not implemented or an array of the + * configuration fields of the provider. + */ + public function acp(); + + /** + * This function updates the template with variables related to the acp + * options with whatever configuraton values are passed to it as an array. + * It then returns the name of the acp file related to this authentication + * provider. + * @param array $new_config Contains the new configuration values that + * have been set in acp_board. + * @return array|null Returns null if not implemented or an array with + * the template file name and an array of the vars + * that the template needs that must conform to the + * following example: + * array( + * 'TEMPLATE_FILE' => string, + * 'TEMPLATE_VARS' => array(...), + * ) + * An optional third element may be added to this + * array: 'BLOCK_VAR_NAME'. If this is present, + * then its value should be a string that is used + * to designate the name of the loop used in the + * ACP template file. When this is present, an + * additional key named 'BLOCK_VARS' is required. + * This must be an array containing at least one + * array of variables that will be assigned during + * the loop in the template. An example of this is + * presented below: + * array( + * 'BLOCK_VAR_NAME' => string, + * 'BLOCK_VARS' => array( + * 'KEY IS UNIMPORTANT' => array(...), + * ), + * 'TEMPLATE_FILE' => string, + * 'TEMPLATE_VARS' => array(...), + * ) + */ + public function get_acp_template($new_config); + + /** + * Returns an array of data necessary to build custom elements on the login + * form. + * + * @return array|null If this function is not implemented on an auth + * provider then it returns null. If it is implemented + * it will return an array of up to four elements of + * which only 'TEMPLATE_FILE'. If 'BLOCK_VAR_NAME' is + * present then 'BLOCK_VARS' must also be present in + * the array. The fourth element 'VARS' is also + * optional. The array, with all four elements present + * looks like the following: + * array( + * 'TEMPLATE_FILE' => string, + * 'BLOCK_VAR_NAME' => string, + * 'BLOCK_VARS' => array(...), + * 'VARS' => array(...), + * ) + */ + public function get_login_data(); + + /** + * Performs additional actions during logout. + * + * @param array $data An array corresponding to + * \phpbb\session::data + * @param boolean $new_session True for a new session, false for no new + * session. + */ + public function logout($data, $new_session); + + /** + * The session validation function checks whether the user is still logged + * into phpBB. + * + * @param array $user + * @return boolean true if the given user is authenticated, false if the + * session should be closed, or null if not implemented. + */ + public function validate_session($user); + + /** + * Checks to see if $login_link_data contains all information except for the + * user_id of an account needed to successfully link an external account to + * a forum account. + * + * @param array $login_link_data Any data needed to link a phpBB account to + * an external account. + * @return string|null Returns a string with a language constant if there + * is data missing or null if there is no error. + */ + public function login_link_has_necessary_data($login_link_data); + + /** + * Links an external account to a phpBB account. + * + * @param array $link_data Any data needed to link a phpBB account to + * an external account. + */ + public function link_account(array $link_data); + + /** + * Returns an array of data necessary to build the ucp_auth_link page + * + * @param int $user_id User ID for whom the data should be retrieved. + * defaults to 0, which is not a valid ID. The method + * should fall back to the current user's ID in this + * case. + * @return array|null If this function is not implemented on an auth + * provider then it returns null. If it is implemented + * it will return an array of up to four elements of + * which only 'TEMPLATE_FILE'. If 'BLOCK_VAR_NAME' is + * present then 'BLOCK_VARS' must also be present in + * the array. The fourth element 'VARS' is also + * optional. The array, with all four elements present + * looks like the following: + * array( + * 'TEMPLATE_FILE' => string, + * 'BLOCK_VAR_NAME' => string, + * 'BLOCK_VARS' => array(...), + * 'VARS' => array(...), + * ) + */ + public function get_auth_link_data($user_id = 0); + + /** + * Unlinks an external account from a phpBB account. + * + * @param array $link_data Any data needed to unlink a phpBB account + * from a phpbb account. + */ + public function unlink_account(array $link_data); +} diff --git a/phpbb/auth/provider_collection.php b/phpbb/auth/provider_collection.php new file mode 100644 index 0000000..8e7e9e2 --- /dev/null +++ b/phpbb/auth/provider_collection.php @@ -0,0 +1,67 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\auth; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** +* Collection of auth providers to be configured at container compile time. +*/ +class provider_collection extends \phpbb\di\service_collection +{ + /** @var \phpbb\config\config phpBB Config */ + protected $config; + + /** + * Constructor + * + * @param ContainerInterface $container Container object + * @param \phpbb\config\config $config phpBB config + */ + public function __construct(ContainerInterface $container, \phpbb\config\config $config) + { + $this->container = $container; + $this->config = $config; + } + + /** + * Get an auth provider. + * + * @param string $provider_name The name of the auth provider + * @return object Default auth provider selected in config if it + * does exist. Otherwise the standard db auth + * provider. + * @throws \RuntimeException If neither the auth provider that + * is specified by the phpBB config nor the db + * auth provider exist. The db auth provider + * should always exist in a phpBB installation. + */ + public function get_provider($provider_name = '') + { + $provider_name = ($provider_name !== '') ? $provider_name : basename(trim($this->config['auth_method'])); + if ($this->offsetExists('auth.provider.' . $provider_name)) + { + return $this->offsetGet('auth.provider.' . $provider_name); + } + // Revert to db auth provider if selected method does not exist + else if ($this->offsetExists('auth.provider.db')) + { + return $this->offsetGet('auth.provider.db'); + } + else + { + throw new \RuntimeException(sprintf('The authentication provider for the authentication method "%1$s" does not exist. It was not possible to recover from this by reverting to the database authentication provider.', $this->config['auth_method'])); + } + } +} diff --git a/phpbb/avatar/driver/driver.php b/phpbb/avatar/driver/driver.php new file mode 100644 index 0000000..45681f3 --- /dev/null +++ b/phpbb/avatar/driver/driver.php @@ -0,0 +1,152 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\avatar\driver; + +/** +* Base class for avatar drivers +*/ +abstract class driver implements \phpbb\avatar\driver\driver_interface +{ + /** + * Avatar driver name + * @var string + */ + protected $name; + + /** + * Current board configuration + * @var \phpbb\config\config + */ + protected $config; + + /** @var \FastImageSize\FastImageSize */ + protected $imagesize; + + /** + * Current $phpbb_root_path + * @var string + */ + protected $phpbb_root_path; + + /** + * Current $php_ext + * @var string + */ + protected $php_ext; + + /** + * Path Helper + * @var \phpbb\path_helper + */ + protected $path_helper; + + /** + * Cache driver + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * Array of allowed avatar image extensions + * Array is used for setting the allowed extensions in the fileupload class + * and as a base for a regex of allowed extensions, which will be formed by + * imploding the array with a "|". + * + * @var array + */ + protected $allowed_extensions = array( + 'gif', + 'jpg', + 'jpeg', + 'png', + ); + + /** + * Construct a driver object + * + * @param \phpbb\config\config $config phpBB configuration + * @param \FastImageSize\FastImageSize $imagesize FastImageSize class + * @param string $phpbb_root_path Path to the phpBB root + * @param string $php_ext PHP file extension + * @param \phpbb\path_helper $path_helper phpBB path helper + * @param \phpbb\cache\driver\driver_interface $cache Cache driver + */ + public function __construct(\phpbb\config\config $config, \FastImageSize\FastImageSize $imagesize, $phpbb_root_path, $php_ext, \phpbb\path_helper $path_helper, \phpbb\cache\driver\driver_interface $cache = null) + { + $this->config = $config; + $this->imagesize = $imagesize; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->path_helper = $path_helper; + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function get_custom_html($user, $row, $alt = '') + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function prepare_form_acp($user) + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function delete($row) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function get_config_name() + { + return preg_replace('#^phpbb\\\\avatar\\\\driver\\\\#', '', get_class($this)); + } + + /** + * {@inheritdoc} + */ + public function get_acp_template_name() + { + return 'acp_avatar_options_' . $this->get_config_name() . '.html'; + } + + /** + * Sets the name of the driver. + * + * @param string $name Driver name + */ + public function set_name($name) + { + $this->name = $name; + } +} diff --git a/phpbb/avatar/driver/driver_interface.php b/phpbb/avatar/driver/driver_interface.php new file mode 100644 index 0000000..7d6c2cf --- /dev/null +++ b/phpbb/avatar/driver/driver_interface.php @@ -0,0 +1,127 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\avatar\driver; + +/** +* Interface for avatar drivers +*/ +interface driver_interface +{ + /** + * Returns the name of the driver. + * + * @return string Name of driver. + */ + public function get_name(); + + /** + * Returns the config name of the driver. To be used in accessing the CONFIG variables. + * + * @return string Config name of driver. + */ + public function get_config_name(); + + /** + * Get the avatar url and dimensions + * + * @param array $row User data or group data that has been cleaned with + * \phpbb\avatar\manager::clean_row + * @return array Avatar data, must have keys src, width and height, e.g. + * ['src' => '', 'width' => 0, 'height' => 0] + */ + public function get_data($row); + + /** + * Returns custom html if it is needed for displaying this avatar + * + * @param \phpbb\user $user phpBB user object + * @param array $row User data or group data that has been cleaned with + * \phpbb\avatar\manager::clean_row + * @param string $alt Alternate text for avatar image + * + * @return string HTML + */ + public function get_custom_html($user, $row, $alt = ''); + + /** + * Prepare form for changing the settings of this avatar + * + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + * @param array $row User data or group data that has been cleaned with + * \phpbb\avatar\manager::clean_row + * @param array &$error Reference to an error array that is filled by this + * function. Key values can either be a string with a language key or + * an array that will be passed to vsprintf() with the language key in + * the first array key. + * + * @return bool True if form has been successfully prepared + */ + public function prepare_form($request, $template, $user, $row, &$error); + + /** + * Prepare form for changing the acp settings of this avatar + * + * @param \phpbb\user $user phpBB user object + * + * @return array Array of configuration options as consumed by acp_board. + * The setting for enabling/disabling the avatar will be handled by + * the avatar manager. + */ + public function prepare_form_acp($user); + + /** + * Process form data + * + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + * @param array $row User data or group data that has been cleaned with + * \phpbb\avatar\manager::clean_row + * @param array &$error Reference to an error array that is filled by this + * function. Key values can either be a string with a language key or + * an array that will be passed to vsprintf() with the language key in + * the first array key. + * + * @return array Array containing the avatar data as follows: + * ['avatar'], ['avatar_width'], ['avatar_height'] + */ + public function process_form($request, $template, $user, $row, &$error); + + /** + * Delete avatar + * + * @param array $row User data or group data that has been cleaned with + * \phpbb\avatar\manager::clean_row + * + * @return bool True if avatar has been deleted or there is no need to delete, + * i.e. when the avatar is not hosted locally. + */ + public function delete($row); + + /** + * Get the avatar driver's template name + * + * @return string Avatar driver's template name + */ + public function get_template_name(); + + /** + * Get the avatar driver's template name (ACP) + * + * @return string Avatar driver's template name + */ + public function get_acp_template_name(); +} diff --git a/phpbb/avatar/driver/gravatar.php b/phpbb/avatar/driver/gravatar.php new file mode 100644 index 0000000..3e4e7ff --- /dev/null +++ b/phpbb/avatar/driver/gravatar.php @@ -0,0 +1,198 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\avatar\driver; + +/** +* Handles avatars hosted at gravatar.com +*/ +class gravatar extends \phpbb\avatar\driver\driver +{ + /** + * The URL for the gravatar service + */ + const GRAVATAR_URL = '//secure.gravatar.com/avatar/'; + + /** + * {@inheritdoc} + */ + public function get_data($row) + { + return array( + 'src' => $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + } + + /** + * {@inheritdoc} + */ + public function get_custom_html($user, $row, $alt = '') + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function prepare_form($request, $template, $user, $row, &$error) + { + $template->assign_vars(array( + 'AVATAR_GRAVATAR_WIDTH' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_width']) ? $row['avatar_width'] : $request->variable('avatar_gravatar_width', ''), + 'AVATAR_GRAVATAR_HEIGHT' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_gravatar_width', ''), + 'AVATAR_GRAVATAR_EMAIL' => (($row['avatar_type'] == $this->get_name() || $row['avatar_type'] == 'gravatar') && $row['avatar']) ? $row['avatar'] : '', + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function process_form($request, $template, $user, $row, &$error) + { + $row['avatar'] = $request->variable('avatar_gravatar_email', ''); + $row['avatar_width'] = $request->variable('avatar_gravatar_width', 0); + $row['avatar_height'] = $request->variable('avatar_gravatar_height', 0); + + if (empty($row['avatar'])) + { + return false; + } + + if (!function_exists('validate_data')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $validate_array = validate_data( + array( + 'email' => $row['avatar'], + ), + array( + 'email' => array( + array('string', false, 6, 60), + array('email'), + ), + ) + ); + + $error = array_merge($error, $validate_array); + + if (!empty($error)) + { + return false; + } + + // Get image dimensions if they are not set + if ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) + { + /** + * default to the minimum of the maximum allowed avatar size if the size + * is not or only partially entered + */ + $row['avatar_width'] = $row['avatar_height'] = min($this->config['avatar_max_width'], $this->config['avatar_max_height']); + $url = $this->get_gravatar_url($row); + + if (($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) && (($image_data = $this->imagesize->getImageSize($url)) === false)) + { + $error[] = 'UNABLE_GET_IMAGE_SIZE'; + return false; + } + + if (!empty($image_data) && ($image_data['width'] <= 0 || $image_data['width'] <= 0)) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + $row['avatar_width'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_width'] : $image_data['width']; + $row['avatar_height'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_height'] : $image_data['height']; + } + + if ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + if ($this->config['avatar_max_width'] || $this->config['avatar_max_height']) + { + if ($row['avatar_width'] > $this->config['avatar_max_width'] || $row['avatar_height'] > $this->config['avatar_max_height']) + { + $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $row['avatar_width'], $row['avatar_height']); + return false; + } + } + + if ($this->config['avatar_min_width'] || $this->config['avatar_min_height']) + { + if ($row['avatar_width'] < $this->config['avatar_min_width'] || $row['avatar_height'] < $this->config['avatar_min_height']) + { + $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $row['avatar_width'], $row['avatar_height']); + return false; + } + } + + return array( + 'avatar' => $row['avatar'], + 'avatar_width' => $row['avatar_width'], + 'avatar_height' => $row['avatar_height'], + ); + } + + /** + * {@inheritdoc} + */ + public function get_template_name() + { + return 'ucp_avatar_options_gravatar.html'; + } + + /** + * Build gravatar URL for output on page + * + * @param array $row User data or group data that has been cleaned with + * \phpbb\avatar\manager::clean_row + * @return string Gravatar URL + */ + protected function get_gravatar_url($row) + { + global $phpbb_dispatcher; + + $url = self::GRAVATAR_URL; + $url .= md5(strtolower(trim($row['avatar']))); + + if ($row['avatar_width'] || $row['avatar_height']) + { + $url .= '?s=' . max($row['avatar_width'], $row['avatar_height']); + } + + /** + * Modify gravatar url + * + * @event core.get_gravatar_url_after + * @var string row User data or group data + * @var string url Gravatar URL + * @since 3.1.7-RC1 + */ + $vars = array('row', 'url'); + extract($phpbb_dispatcher->trigger_event('core.get_gravatar_url_after', compact($vars))); + + return $url; + } +} diff --git a/phpbb/avatar/driver/local.php b/phpbb/avatar/driver/local.php new file mode 100644 index 0000000..4b84e42 --- /dev/null +++ b/phpbb/avatar/driver/local.php @@ -0,0 +1,207 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\avatar\driver; + +/** +* Handles avatars selected from the board gallery +*/ +class local extends \phpbb\avatar\driver\driver +{ + /** + * {@inheritdoc} + */ + public function get_data($row) + { + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $this->path_helper->get_web_root_path(); + + return array( + 'src' => $root_path . $this->config['avatar_gallery_path'] . '/' . $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + } + + /** + * {@inheritdoc} + */ + public function prepare_form($request, $template, $user, $row, &$error) + { + $avatar_list = $this->get_avatar_list($user); + $category = $request->variable('avatar_local_cat', key($avatar_list)); + + foreach ($avatar_list as $cat => $null) + { + if (!empty($avatar_list[$cat])) + { + $template->assign_block_vars('avatar_local_cats', array( + 'NAME' => $cat, + 'SELECTED' => ($cat == $category), + )); + } + + if ($cat != $category) + { + unset($avatar_list[$cat]); + } + } + + if (!empty($avatar_list[$category])) + { + $template->assign_vars(array( + 'AVATAR_LOCAL_SHOW' => true, + )); + + $table_cols = isset($row['avatar_gallery_cols']) ? $row['avatar_gallery_cols'] : 4; + $row_count = $col_count = $avatar_pos = 0; + $avatar_count = count($avatar_list[$category]); + + reset($avatar_list[$category]); + + while ($avatar_pos < $avatar_count) + { + $img = current($avatar_list[$category]); + next($avatar_list[$category]); + + if ($col_count == 0) + { + ++$row_count; + $template->assign_block_vars('avatar_local_row', array( + )); + } + + $template->assign_block_vars('avatar_local_row.avatar_local_col', array( + 'AVATAR_IMAGE' => $this->phpbb_root_path . $this->config['avatar_gallery_path'] . '/' . $img['file'], + 'AVATAR_NAME' => $img['name'], + 'AVATAR_FILE' => $img['filename'], + 'CHECKED' => $img['file'] === $row['avatar'], + )); + + $template->assign_block_vars('avatar_local_row.avatar_local_option', array( + 'AVATAR_FILE' => $img['filename'], + 'S_OPTIONS_AVATAR' => $img['filename'], + 'CHECKED' => $img['file'] === $row['avatar'], + )); + + $col_count = ($col_count + 1) % $table_cols; + + ++$avatar_pos; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function prepare_form_acp($user) + { + return array( + 'avatar_gallery_path' => array('lang' => 'AVATAR_GALLERY_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true), + ); + } + + /** + * {@inheritdoc} + */ + public function process_form($request, $template, $user, $row, &$error) + { + $avatar_list = $this->get_avatar_list($user); + $category = $request->variable('avatar_local_cat', ''); + + $file = $request->variable('avatar_local_file', ''); + + if (empty($category) || empty($file)) + { + return false; + } + + if (!isset($avatar_list[$category][urldecode($file)])) + { + $error[] = 'AVATAR_URL_NOT_FOUND'; + return false; + } + + return array( + 'avatar' => ($category != $user->lang['NO_AVATAR_CATEGORY']) ? $category . '/' . $file : $file, + 'avatar_width' => $avatar_list[$category][urldecode($file)]['width'], + 'avatar_height' => $avatar_list[$category][urldecode($file)]['height'], + ); + } + + /** + * {@inheritdoc} + */ + public function get_template_name() + { + return 'ucp_avatar_options_local.html'; + } + + /** + * Get a list of avatars that are locally available + * Results get cached for 24 hours (86400 seconds) + * + * @param \phpbb\user $user User object + * + * @return array Array containing the locally available avatars + */ + protected function get_avatar_list($user) + { + $avatar_list = ($this->cache == null) ? false : $this->cache->get('_avatar_local_list_' . $user->data['user_lang']); + + if ($avatar_list === false) + { + $avatar_list = array(); + $path = $this->phpbb_root_path . $this->config['avatar_gallery_path']; + + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS), \RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $file_info) + { + $file_path = $file_info->getPath(); + $image = $file_info->getFilename(); + + // Match all images in the gallery folder + if (preg_match('#^[^&\'"<>]+\.(?:' . implode('|', $this->allowed_extensions) . ')$#i', $image) && is_file($file_path . '/' . $image)) + { + $dims = $this->imagesize->getImageSize($file_path . '/' . $image); + + if ($dims === false) + { + $dims = array(0, 0); + } + else + { + $dims = array($dims['width'], $dims['height']); + } + $cat = ($path == $file_path) ? $user->lang['NO_AVATAR_CATEGORY'] : str_replace("$path/", '', $file_path); + $avatar_list[$cat][$image] = array( + 'file' => ($cat != $user->lang['NO_AVATAR_CATEGORY']) ? str_replace('%2F', '/', rawurlencode($cat)) . '/' . rawurlencode($image) : rawurlencode($image), + 'filename' => rawurlencode($image), + 'name' => ucfirst(str_replace('_', ' ', preg_replace('#^(.*)\..*$#', '\1', $image))), + 'width' => $dims[0], + 'height' => $dims[1], + ); + } + } + ksort($avatar_list); + + if ($this->cache != null) + { + $this->cache->put('_avatar_local_list_' . $user->data['user_lang'], $avatar_list, 86400); + } + } + + return $avatar_list; + } +} diff --git a/phpbb/avatar/driver/remote.php b/phpbb/avatar/driver/remote.php new file mode 100644 index 0000000..efc4f5e --- /dev/null +++ b/phpbb/avatar/driver/remote.php @@ -0,0 +1,216 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\avatar\driver; + +/** +* Handles avatars hosted remotely +*/ +class remote extends \phpbb\avatar\driver\driver +{ + /** + * {@inheritdoc} + */ + public function get_data($row) + { + return array( + 'src' => $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + } + + /** + * {@inheritdoc} + */ + public function prepare_form($request, $template, $user, $row, &$error) + { + $template->assign_vars(array( + 'AVATAR_REMOTE_WIDTH' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar_width']) ? $row['avatar_width'] : $request->variable('avatar_remote_width', ''), + 'AVATAR_REMOTE_HEIGHT' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar_height']) ? $row['avatar_height'] : $request->variable('avatar_remote_width', ''), + 'AVATAR_REMOTE_URL' => ((in_array($row['avatar_type'], array(AVATAR_REMOTE, $this->get_name(), 'remote'))) && $row['avatar']) ? $row['avatar'] : '', + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function process_form($request, $template, $user, $row, &$error) + { + $url = $request->variable('avatar_remote_url', ''); + $width = $request->variable('avatar_remote_width', 0); + $height = $request->variable('avatar_remote_height', 0); + + if (empty($url)) + { + return false; + } + + if (!preg_match('#^(http|https|ftp)://#i', $url)) + { + $url = 'http://' . $url; + } + + if (!function_exists('validate_data')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $validate_array = validate_data( + array( + 'url' => $url, + ), + array( + 'url' => array('string', true, 5, 255), + ) + ); + + $error = array_merge($error, $validate_array); + + if (!empty($error)) + { + return false; + } + + // Check if this url looks alright + // Do not allow specifying the port (see RFC 3986) or IP addresses + if (!preg_match('#^(http|https|ftp)://(?:(.*?\.)*?[a-z0-9\-]+?\.[a-z]{2,4}|(?:\d{1,3}\.){3,5}\d{1,3}):?([0-9]*?).*?\.('. implode('|', $this->allowed_extensions) . ')$#i', $url) || + preg_match('@^(http|https|ftp)://[^/:?#]+:[0-9]+[/:?#]@i', $url) || + preg_match('#^(http|https|ftp)://(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])#i', $url) || + preg_match('#^(http|https|ftp)://(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){0,5}(?:[\dA-F]{1,4}(?::[\dA-F]{1,4})?|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:)|(?:::))#i', $url)) + { + $error[] = 'AVATAR_URL_INVALID'; + return false; + } + + // Get image dimensions + if (($width <= 0 || $height <= 0) && (($image_data = $this->imagesize->getImageSize($url)) === false)) + { + $error[] = 'UNABLE_GET_IMAGE_SIZE'; + return false; + } + + if (!empty($image_data) && ($image_data['width'] <= 0 || $image_data['height'] <= 0)) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + $width = ($width && $height) ? $width : $image_data['width']; + $height = ($width && $height) ? $height : $image_data['height']; + + if ($width <= 0 || $height <= 0) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + $types = \phpbb\files\upload::image_types(); + $extension = strtolower(\phpbb\files\filespec::get_extension($url)); + + // Check if this is actually an image + if ($file_stream = @fopen($url, 'r')) + { + // Timeout after 1 second + stream_set_timeout($file_stream, 1); + // read some data to ensure headers are present + fread($file_stream, 1024); + $meta = stream_get_meta_data($file_stream); + + if (isset($meta['wrapper_data']['headers']) && is_array($meta['wrapper_data']['headers'])) + { + $headers = $meta['wrapper_data']['headers']; + } + else if (isset($meta['wrapper_data']) && is_array($meta['wrapper_data'])) + { + $headers = $meta['wrapper_data']; + } + else + { + $headers = array(); + } + + foreach ($headers as $header) + { + $header = preg_split('/ /', $header, 2); + if (strtr(strtolower(trim($header[0], ':')), '_', '-') === 'content-type') + { + if (strpos($header[1], 'image/') !== 0) + { + $error[] = 'AVATAR_URL_INVALID'; + fclose($file_stream); + return false; + } + else + { + fclose($file_stream); + break; + } + } + } + } + else + { + $error[] = 'AVATAR_URL_INVALID'; + return false; + } + + if (!empty($image_data) && (!isset($types[$image_data['type']]) || !in_array($extension, $types[$image_data['type']]))) + { + if (!isset($types[$image_data['type']])) + { + $error[] = 'UNABLE_GET_IMAGE_SIZE'; + } + else + { + $error[] = array('IMAGE_FILETYPE_MISMATCH', $types[$image_data['type']][0], $extension); + } + + return false; + } + + if ($this->config['avatar_max_width'] || $this->config['avatar_max_height']) + { + if ($width > $this->config['avatar_max_width'] || $height > $this->config['avatar_max_height']) + { + $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $width, $height); + return false; + } + } + + if ($this->config['avatar_min_width'] || $this->config['avatar_min_height']) + { + if ($width < $this->config['avatar_min_width'] || $height < $this->config['avatar_min_height']) + { + $error[] = array('AVATAR_WRONG_SIZE', $this->config['avatar_min_width'], $this->config['avatar_min_height'], $this->config['avatar_max_width'], $this->config['avatar_max_height'], $width, $height); + return false; + } + } + + return array( + 'avatar' => $url, + 'avatar_width' => $width, + 'avatar_height' => $height, + ); + } + + /** + * {@inheritdoc} + */ + public function get_template_name() + { + return 'ucp_avatar_options_remote.html'; + } +} diff --git a/phpbb/avatar/driver/upload.php b/phpbb/avatar/driver/upload.php new file mode 100644 index 0000000..a012bb1 --- /dev/null +++ b/phpbb/avatar/driver/upload.php @@ -0,0 +1,331 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\avatar\driver; + +/** +* Handles avatars uploaded to the board +*/ +class upload extends \phpbb\avatar\driver\driver +{ + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * @var \phpbb\files\factory + */ + protected $files_factory; + + /** + * Construct a driver object + * + * @param \phpbb\config\config $config phpBB configuration + * @param string $phpbb_root_path Path to the phpBB root + * @param string $php_ext PHP file extension + * @param \phpbb\filesystem\filesystem_interface $filesystem phpBB filesystem helper + * @param \phpbb\path_helper $path_helper phpBB path helper + * @param \phpbb\event\dispatcher_interface $dispatcher phpBB Event dispatcher object + * @param \phpbb\files\factory $files_factory File classes factory + * @param \phpbb\cache\driver\driver_interface $cache Cache driver + */ + public function __construct(\phpbb\config\config $config, $phpbb_root_path, $php_ext, \phpbb\filesystem\filesystem_interface $filesystem, \phpbb\path_helper $path_helper, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\files\factory $files_factory, \phpbb\cache\driver\driver_interface $cache = null) + { + $this->config = $config; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->filesystem = $filesystem; + $this->path_helper = $path_helper; + $this->dispatcher = $dispatcher; + $this->files_factory = $files_factory; + $this->cache = $cache; + } + + /** + * {@inheritdoc} + */ + public function get_data($row) + { + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $this->path_helper->get_web_root_path(); + + return array( + 'src' => $root_path . 'download/file.' . $this->php_ext . '?avatar=' . $row['avatar'], + 'width' => $row['avatar_width'], + 'height' => $row['avatar_height'], + ); + } + + /** + * {@inheritdoc} + */ + public function prepare_form($request, $template, $user, $row, &$error) + { + if (!$this->can_upload()) + { + return false; + } + + $template->assign_vars(array( + 'S_UPLOAD_AVATAR_URL' => ($this->config['allow_avatar_remote_upload']) ? true : false, + 'AVATAR_UPLOAD_SIZE' => $this->config['avatar_filesize'], + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function process_form($request, $template, $user, $row, &$error) + { + if (!$this->can_upload()) + { + return false; + } + + /** @var \phpbb\files\upload $upload */ + $upload = $this->files_factory->get('upload') + ->set_error_prefix('AVATAR_') + ->set_allowed_extensions($this->allowed_extensions) + ->set_max_filesize($this->config['avatar_filesize']) + ->set_allowed_dimensions( + $this->config['avatar_min_width'], + $this->config['avatar_min_height'], + $this->config['avatar_max_width'], + $this->config['avatar_max_height']) + ->set_disallowed_content((isset($this->config['mime_triggers']) ? explode('|', $this->config['mime_triggers']) : false)); + + $url = $request->variable('avatar_upload_url', ''); + $upload_file = $request->file('avatar_upload_file'); + + if (!empty($upload_file['name'])) + { + $file = $upload->handle_upload('files.types.form', 'avatar_upload_file'); + } + else if (!empty($this->config['allow_avatar_remote_upload']) && !empty($url)) + { + if (!preg_match('#^(http|https|ftp)://#i', $url)) + { + $url = 'http://' . $url; + } + + if (!function_exists('validate_data')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $validate_array = validate_data( + array( + 'url' => $url, + ), + array( + 'url' => array('string', true, 5, 255), + ) + ); + + $error = array_merge($error, $validate_array); + + if (!empty($error)) + { + return false; + } + + // Do not allow specifying the port (see RFC 3986) or IP addresses + // remote_upload() will do its own check for allowed filetypes + if (!preg_match('#^(http|https|ftp)://(?:(.*?\.)*?[a-z0-9\-]+?\.[a-z]{2,4}|(?:\d{1,3}\.){3,5}\d{1,3}):?([0-9]*?).*?\.('. implode('|', $this->allowed_extensions) . ')$#i', $url) || + preg_match('@^(http|https|ftp)://[^/:?#]+:[0-9]+[/:?#]@i', $url) || + preg_match('#^(http|https|ftp)://(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])#i', $url) || + preg_match('#^(http|https|ftp)://(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){0,5}(?:[\dA-F]{1,4}(?::[\dA-F]{1,4})?|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:)|(?:::))#i', $url)) + { + $error[] = 'AVATAR_URL_INVALID'; + return false; + } + + $file = $upload->handle_upload('files.types.remote', $url); + } + else + { + return false; + } + + $prefix = $this->config['avatar_salt'] . '_'; + $file->clean_filename('avatar', $prefix, $row['id']); + + // If there was an error during upload, then abort operation + if (count($file->error)) + { + $file->remove(); + $error = $file->error; + return false; + } + + // Calculate new destination + $destination = $this->config['avatar_path']; + + // Adjust destination path (no trailing slash) + if (substr($destination, -1, 1) == '/' || substr($destination, -1, 1) == '\\') + { + $destination = substr($destination, 0, -1); + } + + $destination = str_replace(array('../', '..\\', './', '.\\'), '', $destination); + if ($destination && ($destination[0] == '/' || $destination[0] == "\\")) + { + $destination = ''; + } + + $filedata = array( + 'filename' => $file->get('filename'), + 'filesize' => $file->get('filesize'), + 'mimetype' => $file->get('mimetype'), + 'extension' => $file->get('extension'), + 'physical_filename' => $file->get('realname'), + 'real_filename' => $file->get('uploadname'), + ); + + /** + * Before moving new file in place (and eventually overwriting the existing avatar with the newly uploaded avatar) + * + * @event core.avatar_driver_upload_move_file_before + * @var array filedata Array containing uploaded file data + * @var \phpbb\files\filespec file Instance of filespec class + * @var string destination Destination directory where the file is going to be moved + * @var string prefix Prefix for the avatar filename + * @var array row Array with avatar row data + * @var array error Array of errors, if filled in by this event file will not be moved + * @since 3.1.6-RC1 + * @changed 3.1.9-RC1 Added filedata + * @changed 3.2.3-RC1 Added file + */ + $vars = array( + 'filedata', + 'file', + 'destination', + 'prefix', + 'row', + 'error', + ); + extract($this->dispatcher->trigger_event('core.avatar_driver_upload_move_file_before', compact($vars))); + + unset($filedata); + + if (!count($error)) + { + // Move file and overwrite any existing image + $file->move_file($destination, true); + } + + // If there was an error during move, then clean up leftovers + $error = array_merge($error, $file->error); + if (count($error)) + { + $file->remove(); + return false; + } + + // Delete current avatar if not overwritten + $ext = substr(strrchr($row['avatar'], '.'), 1); + if ($ext && $ext !== $file->get('extension')) + { + $this->delete($row); + } + + return array( + 'avatar' => $row['id'] . '_' . time() . '.' . $file->get('extension'), + 'avatar_width' => $file->get('width'), + 'avatar_height' => $file->get('height'), + ); + } + + /** + * {@inheritdoc} + */ + public function prepare_form_acp($user) + { + return array( + 'allow_avatar_remote_upload'=> array('lang' => 'ALLOW_REMOTE_UPLOAD', 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + 'avatar_filesize' => array('lang' => 'MAX_FILESIZE', 'validate' => 'int:0', 'type' => 'number:0', 'explain' => true, 'append' => ' ' . $user->lang['BYTES']), + 'avatar_path' => array('lang' => 'AVATAR_STORAGE_PATH', 'validate' => 'rpath', 'type' => 'text:20:255', 'explain' => true), + ); + } + + /** + * {@inheritdoc} + */ + public function delete($row) + { + + $error = array(); + $destination = $this->config['avatar_path']; + $prefix = $this->config['avatar_salt'] . '_'; + $ext = substr(strrchr($row['avatar'], '.'), 1); + $filename = $this->phpbb_root_path . $destination . '/' . $prefix . $row['id'] . '.' . $ext; + + /** + * Before deleting an existing avatar + * + * @event core.avatar_driver_upload_delete_before + * @var string destination Destination directory where the file is going to be deleted + * @var string prefix Prefix for the avatar filename + * @var array row Array with avatar row data + * @var array error Array of errors, if filled in by this event file will not be deleted + * @since 3.1.6-RC1 + */ + $vars = array( + 'destination', + 'prefix', + 'row', + 'error', + ); + extract($this->dispatcher->trigger_event('core.avatar_driver_upload_delete_before', compact($vars))); + + if (!count($error) && $this->filesystem->exists($filename)) + { + try + { + $this->filesystem->remove($filename); + return true; + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Fail is covered by return statement below + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function get_template_name() + { + return 'ucp_avatar_options_upload.html'; + } + + /** + * Check if user is able to upload an avatar + * + * @return bool True if user can upload, false if not + */ + protected function can_upload() + { + return ($this->filesystem->exists($this->phpbb_root_path . $this->config['avatar_path']) && $this->filesystem->is_writable($this->phpbb_root_path . $this->config['avatar_path']) && (@ini_get('file_uploads') || strtolower(@ini_get('file_uploads')) == 'on')); + } +} diff --git a/phpbb/avatar/manager.php b/phpbb/avatar/manager.php new file mode 100644 index 0000000..a909a91 --- /dev/null +++ b/phpbb/avatar/manager.php @@ -0,0 +1,375 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\avatar; + +class manager +{ + /** + * phpBB configuration + * @var \phpbb\config\config + */ + protected $config; + + /** + * phpBB event dispatcher + * @var \phpbb\event\dispatcher_interface + */ + protected $phpbb_dispatcher; + + /** + * Array that contains a list of enabled drivers + * @var array + */ + static protected $enabled_drivers = false; + + /** + * Array that contains all available avatar drivers which are passed via the + * service container + * @var array + */ + protected $avatar_drivers; + + /** + * Default avatar data row + * @var array + */ + static protected $default_row = array( + 'avatar' => '', + 'avatar_type' => '', + 'avatar_width' => 0, + 'avatar_height' => 0, + ); + + /** + * Construct an avatar manager object + * + * @param \phpbb\config\config $config phpBB configuration + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher phpBB event dispatcher + * @param array $avatar_drivers Avatar drivers passed via the service container + */ + public function __construct(\phpbb\config\config $config, \phpbb\event\dispatcher_interface $phpbb_dispatcher, $avatar_drivers) + { + $this->config = $config; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->register_avatar_drivers($avatar_drivers); + } + + /** + * Register avatar drivers + * + * @param array $avatar_drivers Service collection of avatar drivers + */ + protected function register_avatar_drivers($avatar_drivers) + { + if (!empty($avatar_drivers)) + { + foreach ($avatar_drivers as $driver) + { + $this->avatar_drivers[$driver->get_name()] = $driver; + } + } + } + + /** + * Get the driver object specified by the avatar type + * + * @param string $avatar_type Avatar type; by default an avatar's service container name + * @param bool $load_enabled Load only enabled avatars + * + * @return object Avatar driver object + */ + public function get_driver($avatar_type, $load_enabled = true) + { + if (self::$enabled_drivers === false) + { + $this->load_enabled_drivers(); + } + + $avatar_drivers = ($load_enabled) ? self::$enabled_drivers : $this->get_all_drivers(); + + // Legacy stuff... + switch ($avatar_type) + { + case AVATAR_GALLERY: + $avatar_type = 'avatar.driver.local'; + break; + case AVATAR_UPLOAD: + $avatar_type = 'avatar.driver.upload'; + break; + case AVATAR_REMOTE: + $avatar_type = 'avatar.driver.remote'; + break; + } + + if (!isset($avatar_drivers[$avatar_type])) + { + return null; + } + + /* + * There is no need to handle invalid avatar types as the following code + * will cause a ServiceNotFoundException if the type does not exist + */ + $driver = $this->avatar_drivers[$avatar_type]; + + return $driver; + } + + /** + * Load the list of enabled drivers + * This is executed once and fills self::$enabled_drivers + */ + protected function load_enabled_drivers() + { + if (!empty($this->avatar_drivers)) + { + self::$enabled_drivers = array(); + foreach ($this->avatar_drivers as $driver) + { + if ($this->is_enabled($driver)) + { + self::$enabled_drivers[$driver->get_name()] = $driver->get_name(); + } + } + asort(self::$enabled_drivers); + } + } + + /** + * Get a list of all avatar drivers + * + * As this function will only be called in the ACP avatar settings page, it + * doesn't make much sense to cache the list of all avatar drivers like the + * list of the enabled drivers. + * + * @return array Array containing a list of all avatar drivers + */ + public function get_all_drivers() + { + $drivers = array(); + + if (!empty($this->avatar_drivers)) + { + foreach ($this->avatar_drivers as $driver) + { + $drivers[$driver->get_name()] = $driver->get_name(); + } + asort($drivers); + } + + return $drivers; + } + + /** + * Get a list of enabled avatar drivers + * + * @return array Array containing a list of the enabled avatar drivers + */ + public function get_enabled_drivers() + { + if (self::$enabled_drivers === false) + { + $this->load_enabled_drivers(); + } + + return self::$enabled_drivers; + } + + /** + * Strip out user_, group_, or other prefixes from array keys + * + * @param array $row User data or group data + * @param string $prefix Prefix of data keys (e.g. user), should not include the trailing underscore + * + * @return array User or group data with keys that have been + * stripped from the preceding "user_" or "group_" + * Also the group id is prefixed with g, when the prefix group is removed. + */ + static public function clean_row($row, $prefix = '') + { + // Upon creation of a user/group $row might be empty + if (empty($row)) + { + return self::$default_row; + } + + $output = array(); + foreach ($row as $key => $value) + { + $key = preg_replace("#^(?:{$prefix}_)#", '', $key); + $output[$key] = $value; + } + + if ($prefix === 'group' && isset($output['id'])) + { + $output['id'] = 'g' . $output['id']; + } + + return $output; + } + + /** + * Clean driver names that are returned from template files + * Underscores are replaced with dots + * + * @param string $name Driver name + * + * @return string Cleaned driver name + */ + static public function clean_driver_name($name) + { + return str_replace(array('\\', '_'), '.', $name); + } + + /** + * Prepare driver names for use in template files + * Dots are replaced with underscores + * + * @param string $name Clean driver name + * + * @return string Prepared driver name + */ + static public function prepare_driver_name($name) + { + return str_replace('.', '_', $name); + } + + /** + * Check if avatar is enabled + * + * @param object $driver Avatar driver object + * + * @return bool True if avatar is enabled, false if it's disabled + */ + public function is_enabled($driver) + { + $config_name = $driver->get_config_name(); + + return $this->config["allow_avatar_{$config_name}"]; + } + + /** + * Get the settings array for enabling/disabling an avatar driver + * + * @param object $driver Avatar driver object + * + * @return array Array of configuration options as consumed by acp_board + */ + public function get_avatar_settings($driver) + { + $config_name = $driver->get_config_name(); + + return array( + 'allow_avatar_' . $config_name => array('lang' => 'ALLOW_' . strtoupper(str_replace('\\', '_', $config_name)), 'validate' => 'bool', 'type' => 'radio:yes_no', 'explain' => true), + ); + } + + /** + * Replace "error" strings with their real, localized form + * + * @param \phpbb\user phpBB User object + * @param array $error Array containing error strings + * Key values can either be a string with a language key or an array + * that will be passed to vsprintf() with the language key in the + * first array key. + * + * @return array Array containing the localized error strings + */ + public function localize_errors(\phpbb\user $user, $error) + { + foreach ($error as $key => $lang) + { + if (is_array($lang)) + { + $lang_key = array_shift($lang); + $error[$key] = vsprintf($user->lang($lang_key), $lang); + } + else + { + $error[$key] = $user->lang("$lang"); + } + } + + return $error; + } + + /** + * Handle deleting avatars + * + * @param \phpbb\db\driver\driver_interface $db phpBB dbal + * @param \phpbb\user $user phpBB user object + * @param array $avatar_data Cleaned user data containing the user's + * avatar data + * @param string $table Database table from which the avatar should be deleted + * @param string $prefix Prefix of user data columns in database + * @return null + */ + public function handle_avatar_delete(\phpbb\db\driver\driver_interface $db, \phpbb\user $user, $avatar_data, $table, $prefix) + { + if ($driver = $this->get_driver($avatar_data['avatar_type'])) + { + $driver->delete($avatar_data); + } + + $result = $this->prefix_avatar_columns($prefix, self::$default_row); + + $sql = 'UPDATE ' . $table . ' + SET ' . $db->sql_build_array('UPDATE', $result) . ' + WHERE ' . $prefix . 'id = ' . (int) $avatar_data['id']; + $db->sql_query($sql); + + // Make sure we also delete this avatar from the users + if ($prefix === 'group_') + { + $result = $this->prefix_avatar_columns('user_', self::$default_row); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $result) . " + WHERE user_avatar = '" . $db->sql_escape($avatar_data['avatar']) . "'"; + $db->sql_query($sql); + } + + /** + * Event is triggered after user avatar has been deleted + * + * @event core.avatar_manager_avatar_delete_after + * @var \phpbb\user user phpBB user object + * @var array avatar_data Normalised avatar-related user data + * @var string table Table to delete avatar from + * @var string prefix Column prefix to delete avatar from + * @since 3.2.4-RC1 + */ + $vars = array('user', 'avatar_data', 'table', 'prefix'); + extract($this->phpbb_dispatcher->trigger_event('core.avatar_manager_avatar_delete_after', compact($vars))); + } + + /** + * Prefix avatar columns + * + * @param string $prefix Column prefix + * @param array $data Column data + * + * @return array Column data with prefixed column names + */ + public function prefix_avatar_columns($prefix, $data) + { + foreach ($data as $key => $value) + { + $data[$prefix . $key] = $value; + unset($data[$key]); + } + + return $data; + } +} diff --git a/phpbb/cache/driver/apc.php b/phpbb/cache/driver/apc.php new file mode 100644 index 0000000..521d5d4 --- /dev/null +++ b/phpbb/cache/driver/apc.php @@ -0,0 +1,70 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* ACM for APC +*/ +class apc extends \phpbb\cache\driver\memory +{ + var $extension = 'apc'; + + /** + * {@inheritDoc} + */ + function purge() + { + apc_clear_cache('user'); + + parent::purge(); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + return apc_fetch($this->key_prefix . $var); + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + return apc_store($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return apc_delete($this->key_prefix . $var); + } +} diff --git a/phpbb/cache/driver/apcu.php b/phpbb/cache/driver/apcu.php new file mode 100644 index 0000000..c96cf0d --- /dev/null +++ b/phpbb/cache/driver/apcu.php @@ -0,0 +1,74 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* ACM for APCU +*/ +class apcu extends \phpbb\cache\driver\memory +{ + var $extension = 'apcu'; + + /** + * {@inheritDoc} + */ + function purge() + { + /* + * Use an iterator to selectively delete our cache entries without disturbing + * any other cache users (e.g. other phpBB boards hosted on this server) + */ + apcu_delete(new \APCUIterator('#^' . $this->key_prefix . '#')); + + parent::purge(); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + return apcu_fetch($this->key_prefix . $var); + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + return apcu_store($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return apcu_delete($this->key_prefix . $var); + } +} diff --git a/phpbb/cache/driver/base.php b/phpbb/cache/driver/base.php new file mode 100644 index 0000000..3eca521 --- /dev/null +++ b/phpbb/cache/driver/base.php @@ -0,0 +1,234 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +abstract class base implements \phpbb\cache\driver\driver_interface +{ + var $vars = array(); + var $is_modified = false; + + var $sql_rowset = array(); + var $sql_row_pointer = array(); + var $cache_dir = ''; + + /** + * {@inheritDoc} + */ + function purge() + { + // Purge all phpbb cache files + try + { + $iterator = new \DirectoryIterator($this->cache_dir); + } + catch (\Exception $e) + { + return; + } + + foreach ($iterator as $fileInfo) + { + if ($fileInfo->isDot()) + { + continue; + } + $filename = $fileInfo->getFilename(); + if ($fileInfo->isDir()) + { + $this->remove_dir($fileInfo->getPathname()); + } + else if (strpos($filename, 'container_') === 0 || + strpos($filename, 'autoload_') === 0 || + strpos($filename, 'url_matcher') === 0 || + strpos($filename, 'url_generator') === 0 || + strpos($filename, 'sql_') === 0 || + strpos($filename, 'data_') === 0) + { + $this->remove_file($fileInfo->getPathname()); + } + } + + unset($this->vars); + unset($this->sql_rowset); + unset($this->sql_row_pointer); + + if (function_exists('opcache_reset')) + { + @opcache_reset(); + } + + $this->vars = array(); + $this->sql_rowset = array(); + $this->sql_row_pointer = array(); + + $this->is_modified = false; + } + + /** + * {@inheritDoc} + */ + function unload() + { + $this->save(); + unset($this->vars); + unset($this->sql_rowset); + unset($this->sql_row_pointer); + + $this->vars = array(); + $this->sql_rowset = array(); + $this->sql_row_pointer = array(); + } + + /** + * {@inheritDoc} + */ + function sql_load($query) + { + // Remove extra spaces and tabs + $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); + $query_id = md5($query); + + if (($result = $this->_read('sql_' . $query_id)) === false) + { + return false; + } + + $this->sql_rowset[$query_id] = $result; + $this->sql_row_pointer[$query_id] = 0; + + return $query_id; + } + + /** + * {@inheritDoc} + */ + function sql_exists($query_id) + { + return isset($this->sql_rowset[$query_id]); + } + + /** + * {@inheritDoc} + */ + function sql_fetchrow($query_id) + { + if ($this->sql_row_pointer[$query_id] < count($this->sql_rowset[$query_id])) + { + return $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++]; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchfield($query_id, $field) + { + if ($this->sql_row_pointer[$query_id] < count($this->sql_rowset[$query_id])) + { + return (isset($this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]][$field])) ? $this->sql_rowset[$query_id][$this->sql_row_pointer[$query_id]++][$field] : false; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_rowseek($rownum, $query_id) + { + if ($rownum >= count($this->sql_rowset[$query_id])) + { + return false; + } + + $this->sql_row_pointer[$query_id] = $rownum; + return true; + } + + /** + * {@inheritDoc} + */ + function sql_freeresult($query_id) + { + if (!isset($this->sql_rowset[$query_id])) + { + return false; + } + + unset($this->sql_rowset[$query_id]); + unset($this->sql_row_pointer[$query_id]); + + return true; + } + + /** + * Removes/unlinks file + * + * @param string $filename Filename to remove + * @param bool $check Check file permissions + * @return bool True if the file was successfully removed, otherwise false + */ + function remove_file($filename, $check = false) + { + global $phpbb_filesystem; + + if ($check && !$phpbb_filesystem->is_writable($this->cache_dir)) + { + // E_USER_ERROR - not using language entry - intended. + trigger_error('Unable to remove files within ' . $this->cache_dir . '. Please check directory permissions.', E_USER_ERROR); + } + + return @unlink($filename); + } + + /** + * Remove directory + * + * @param string $dir Directory to remove + * + * @return null + */ + protected function remove_dir($dir) + { + try + { + $iterator = new \DirectoryIterator($dir); + } + catch (\Exception $e) + { + return; + } + + foreach ($iterator as $fileInfo) + { + if ($fileInfo->isDot()) + { + continue; + } + + if ($fileInfo->isDir()) + { + $this->remove_dir($fileInfo->getPathname()); + } + else + { + $this->remove_file($fileInfo->getPathname()); + } + } + + @rmdir($dir); + } +} diff --git a/phpbb/cache/driver/driver_interface.php b/phpbb/cache/driver/driver_interface.php new file mode 100644 index 0000000..9ac9ca0 --- /dev/null +++ b/phpbb/cache/driver/driver_interface.php @@ -0,0 +1,167 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* An interface that all cache drivers must implement +*/ +interface driver_interface +{ + /** + * Load global cache + * + * @return mixed False if an error was encountered, otherwise the data type of the cached data + */ + public function load(); + + /** + * Unload cache object + * + * @return null + */ + public function unload(); + + /** + * Save modified objects + * + * @return null + */ + public function save(); + + /** + * Tidy cache + * + * @return null + */ + public function tidy(); + + /** + * Get saved cache object + * + * @param string $var_name Cache key + * @return mixed False if an error was encountered, otherwise the saved cached object + */ + public function get($var_name); + + /** + * Put data into cache + * + * @param string $var_name Cache key + * @param mixed $var Cached data to store + * @param int $ttl Time-to-live of cached data + * @return null + */ + public function put($var_name, $var, $ttl = 0); + + /** + * Purge cache data + * + * @return null + */ + public function purge(); + + /** + * Destroy cache data + * + * @param string $var_name Cache key + * @param string $table Table name + * @return null + */ + public function destroy($var_name, $table = ''); + + /** + * Check if a given cache entry exists + * + * @param string $var_name Cache key + * + * @return bool True if cache file exists and has not expired. + * False otherwise. + */ + public function _exists($var_name); + + /** + * Load result of an SQL query from cache. + * + * @param string $query SQL query + * + * @return int|bool Query ID (integer) if cache contains a rowset + * for the specified query. + * False otherwise. + */ + public function sql_load($query); + + /** + * Save result of an SQL query in cache. + * + * In persistent cache stores, this function stores the query + * result to persistent storage. In other words, there is no need + * to call save() afterwards. + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param string $query SQL query, should be used for generating storage key + * @param mixed $query_result The result from \dbal::sql_query, to be passed to + * \dbal::sql_fetchrow to get all rows and store them + * in cache. + * @param int $ttl Time to live, after this timeout the query should + * expire from the cache. + * @return int|mixed If storing in cache succeeded, an integer $query_id + * representing the query should be returned. Otherwise + * the original $query_result should be returned. + */ + public function sql_save(\phpbb\db\driver\driver_interface $db, $query, $query_result, $ttl); + + /** + * Check if result for a given SQL query exists in cache. + * + * @param int $query_id + * @return bool + */ + public function sql_exists($query_id); + + /** + * Fetch row from cache (database) + * + * @param int $query_id + * @return array|bool The query result if found in the cache, otherwise + * false. + */ + public function sql_fetchrow($query_id); + + /** + * Fetch a field from the current row of a cached database result (database) + * + * @param int $query_id + * @param string $field The name of the column. + * @return string|bool The field of the query result if found in the cache, + * otherwise false. + */ + public function sql_fetchfield($query_id, $field); + + /** + * Seek a specific row in an a cached database result (database) + * + * @param int $rownum Row to seek to. + * @param int $query_id + * @return bool + */ + public function sql_rowseek($rownum, $query_id); + + /** + * Free memory used for a cached database result (database) + * + * @param int $query_id + * @return bool + */ + public function sql_freeresult($query_id); +} diff --git a/phpbb/cache/driver/dummy.php b/phpbb/cache/driver/dummy.php new file mode 100644 index 0000000..1f74f6d --- /dev/null +++ b/phpbb/cache/driver/dummy.php @@ -0,0 +1,153 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* ACM dummy Caching +*/ +class dummy extends \phpbb\cache\driver\base +{ + /** + * Set cache path + */ + function __construct() + { + } + + /** + * {@inheritDoc} + */ + function load() + { + return true; + } + + /** + * {@inheritDoc} + */ + function unload() + { + } + + /** + * {@inheritDoc} + */ + function save() + { + } + + /** + * {@inheritDoc} + */ + function tidy() + { + global $config; + + // This cache always has a tidy room. + $config->set('cache_last_gc', time(), false); + } + + /** + * {@inheritDoc} + */ + function get($var_name) + { + return false; + } + + /** + * {@inheritDoc} + */ + function put($var_name, $var, $ttl = 0) + { + } + + /** + * {@inheritDoc} + */ + function purge() + { + } + + /** + * {@inheritDoc} + */ + function destroy($var_name, $table = '') + { + } + + /** + * {@inheritDoc} + */ + function _exists($var_name) + { + return false; + } + + /** + * {@inheritDoc} + */ + function sql_load($query) + { + return false; + } + + /** + * {@inheritDoc} + */ + function sql_save(\phpbb\db\driver\driver_interface $db, $query, $query_result, $ttl) + { + return $query_result; + } + + /** + * {@inheritDoc} + */ + function sql_exists($query_id) + { + return false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchrow($query_id) + { + return false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchfield($query_id, $field) + { + return false; + } + + /** + * {@inheritDoc} + */ + function sql_rowseek($rownum, $query_id) + { + return false; + } + + /** + * {@inheritDoc} + */ + function sql_freeresult($query_id) + { + return false; + } +} diff --git a/phpbb/cache/driver/eaccelerator.php b/phpbb/cache/driver/eaccelerator.php new file mode 100644 index 0000000..7408551 --- /dev/null +++ b/phpbb/cache/driver/eaccelerator.php @@ -0,0 +1,107 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* ACM for eAccelerator +* @todo Missing locks from destroy() talk with David +*/ +class eaccelerator extends \phpbb\cache\driver\memory +{ + var $extension = 'eaccelerator'; + var $function = 'eaccelerator_get'; + + var $serialize_header = '#phpbb-serialized#'; + + /** + * {@inheritDoc} + */ + function purge() + { + foreach (eaccelerator_list_keys() as $var) + { + // @todo Check why the substr() + // @todo Only unset vars matching $this->key_prefix + eaccelerator_rm(substr($var['name'], 1)); + } + + parent::purge(); + } + + /** + * {@inheritDoc} + */ + function tidy() + { + global $config; + + eaccelerator_gc(); + + $config->set('cache_last_gc', time(), false); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + $result = eaccelerator_get($this->key_prefix . $var); + + if ($result === null) + { + return false; + } + + // Handle serialized objects + if (is_string($result) && strpos($result, $this->serialize_header . 'O:') === 0) + { + $result = unserialize(substr($result, strlen($this->serialize_header))); + } + + return $result; + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + // Serialize objects and make them easy to detect + $data = (is_object($data)) ? $this->serialize_header . serialize($data) : $data; + + return eaccelerator_put($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return eaccelerator_rm($this->key_prefix . $var); + } +} diff --git a/phpbb/cache/driver/file.php b/phpbb/cache/driver/file.php new file mode 100644 index 0000000..de6f444 --- /dev/null +++ b/phpbb/cache/driver/file.php @@ -0,0 +1,613 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* ACM File Based Caching +*/ +class file extends \phpbb\cache\driver\base +{ + var $var_expires = array(); + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * Set cache path + * + * @param string $cache_dir Define the path to the cache directory (default: $phpbb_root_path . 'cache/') + */ + function __construct($cache_dir = null) + { + global $phpbb_container; + + $this->cache_dir = !is_null($cache_dir) ? $cache_dir : $phpbb_container->getParameter('core.cache_dir'); + $this->filesystem = new \phpbb\filesystem\filesystem(); + + if (!is_dir($this->cache_dir)) + { + @mkdir($this->cache_dir, 0777, true); + } + } + + /** + * {@inheritDoc} + */ + function load() + { + return $this->_read('data_global'); + } + + /** + * {@inheritDoc} + */ + function unload() + { + parent::unload(); + unset($this->var_expires); + $this->var_expires = array(); + } + + /** + * {@inheritDoc} + */ + function save() + { + if (!$this->is_modified) + { + return; + } + + global $phpEx; + + if (!$this->_write('data_global')) + { + // Now, this occurred how often? ... phew, just tell the user then... + if (!$this->filesystem->is_writable($this->cache_dir)) + { + // We need to use die() here, because else we may encounter an infinite loop (the message handler calls $cache->unload()) + die('Fatal: ' . $this->cache_dir . ' is NOT writable.'); + exit; + } + + die('Fatal: Not able to open ' . $this->cache_dir . 'data_global.' . $phpEx); + exit; + } + + $this->is_modified = false; + } + + /** + * {@inheritDoc} + */ + function tidy() + { + global $config, $phpEx; + + $dir = @opendir($this->cache_dir); + + if (!$dir) + { + return; + } + + $time = time(); + + while (($entry = readdir($dir)) !== false) + { + if (!preg_match('/^(sql_|data_(?!global))/', $entry)) + { + continue; + } + + if (!($handle = @fopen($this->cache_dir . $entry, 'rb'))) + { + continue; + } + + // Skip the PHP header + fgets($handle); + + // Skip expiration + $expires = (int) fgets($handle); + + fclose($handle); + + if ($time >= $expires) + { + $this->remove_file($this->cache_dir . $entry); + } + } + closedir($dir); + + if (file_exists($this->cache_dir . 'data_global.' . $phpEx)) + { + if (!count($this->vars)) + { + $this->load(); + } + + foreach ($this->var_expires as $var_name => $expires) + { + if ($time >= $expires) + { + $this->destroy($var_name); + } + } + } + + $config->set('cache_last_gc', time(), false); + } + + /** + * {@inheritDoc} + */ + function get($var_name) + { + if ($var_name[0] == '_') + { + if (!$this->_exists($var_name)) + { + return false; + } + + return $this->_read('data' . $var_name); + } + else + { + return ($this->_exists($var_name)) ? $this->vars[$var_name] : false; + } + } + + /** + * {@inheritDoc} + */ + function put($var_name, $var, $ttl = 31536000) + { + if ($var_name[0] == '_') + { + $this->_write('data' . $var_name, $var, time() + $ttl); + } + else + { + $this->vars[$var_name] = $var; + $this->var_expires[$var_name] = time() + $ttl; + $this->is_modified = true; + } + } + + /** + * {@inheritDoc} + */ + function purge() + { + parent::purge(); + $this->var_expires = array(); + } + + /** + * {@inheritDoc} + */ + function destroy($var_name, $table = '') + { + global $phpEx; + + if ($var_name == 'sql' && !empty($table)) + { + if (!is_array($table)) + { + $table = array($table); + } + + $dir = @opendir($this->cache_dir); + + if (!$dir) + { + return; + } + + while (($entry = readdir($dir)) !== false) + { + if (strpos($entry, 'sql_') !== 0) + { + continue; + } + + if (!($handle = @fopen($this->cache_dir . $entry, 'rb'))) + { + continue; + } + + // Skip the PHP header + fgets($handle); + + // Skip expiration + fgets($handle); + + // Grab the query, remove the LF + $query = substr(fgets($handle), 0, -1); + + fclose($handle); + + foreach ($table as $check_table) + { + // Better catch partial table names than no table names. ;) + if (strpos($query, $check_table) !== false) + { + $this->remove_file($this->cache_dir . $entry); + break; + } + } + } + closedir($dir); + + return; + } + + if (!$this->_exists($var_name)) + { + return; + } + + if ($var_name[0] == '_') + { + $this->remove_file($this->cache_dir . 'data' . $var_name . ".$phpEx", true); + } + else if (isset($this->vars[$var_name])) + { + $this->is_modified = true; + unset($this->vars[$var_name]); + unset($this->var_expires[$var_name]); + + // We save here to let the following cache hits succeed + $this->save(); + } + } + + /** + * {@inheritDoc} + */ + function _exists($var_name) + { + if ($var_name[0] == '_') + { + global $phpEx; + $var_name = $this->clean_varname($var_name); + return file_exists($this->cache_dir . 'data' . $var_name . ".$phpEx"); + } + else + { + if (!count($this->vars)) + { + $this->load(); + } + + if (!isset($this->var_expires[$var_name])) + { + return false; + } + + return (time() > $this->var_expires[$var_name]) ? false : isset($this->vars[$var_name]); + } + } + + /** + * {@inheritDoc} + */ + function sql_save(\phpbb\db\driver\driver_interface $db, $query, $query_result, $ttl) + { + // Remove extra spaces and tabs + $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); + + $query_id = md5($query); + $this->sql_rowset[$query_id] = array(); + $this->sql_row_pointer[$query_id] = 0; + + while ($row = $db->sql_fetchrow($query_result)) + { + $this->sql_rowset[$query_id][] = $row; + } + $db->sql_freeresult($query_result); + + if ($this->_write('sql_' . $query_id, $this->sql_rowset[$query_id], $ttl + time(), $query)) + { + return $query_id; + } + + return $query_result; + } + + /** + * Read cached data from a specified file + * + * @access private + * @param string $filename Filename to write + * @return mixed False if an error was encountered, otherwise the data type of the cached data + */ + function _read($filename) + { + global $phpEx; + + $filename = $this->clean_varname($filename); + $file = "{$this->cache_dir}$filename.$phpEx"; + + $type = substr($filename, 0, strpos($filename, '_')); + + if (!file_exists($file)) + { + return false; + } + + if (!($handle = @fopen($file, 'rb'))) + { + return false; + } + + // Skip the PHP header + fgets($handle); + + if ($filename == 'data_global') + { + $this->vars = $this->var_expires = array(); + + $time = time(); + + while (($expires = (int) fgets($handle)) && !feof($handle)) + { + // Number of bytes of data + $bytes = substr(fgets($handle), 0, -1); + + if (!is_numeric($bytes) || ($bytes = (int) $bytes) === 0) + { + // We cannot process the file without a valid number of bytes + // so we discard it + fclose($handle); + + $this->vars = $this->var_expires = array(); + $this->is_modified = false; + + $this->remove_file($file); + + return false; + } + + if ($time >= $expires) + { + fseek($handle, $bytes, SEEK_CUR); + + continue; + } + + $var_name = substr(fgets($handle), 0, -1); + + // Read the length of bytes that consists of data. + $data = fread($handle, $bytes - strlen($var_name)); + $data = @unserialize($data); + + // Don't use the data if it was invalid + if ($data !== false) + { + $this->vars[$var_name] = $data; + $this->var_expires[$var_name] = $expires; + } + + // Absorb the LF + fgets($handle); + } + + fclose($handle); + + $this->is_modified = false; + + return true; + } + else + { + $data = false; + $line = 0; + + while (($buffer = fgets($handle)) && !feof($handle)) + { + $buffer = substr($buffer, 0, -1); // Remove the LF + + // $buffer is only used to read integers + // if it is non numeric we have an invalid + // cache file, which we will now remove. + if (!is_numeric($buffer)) + { + break; + } + + if ($line == 0) + { + $expires = (int) $buffer; + + if (time() >= $expires) + { + break; + } + + if ($type == 'sql') + { + // Skip the query + fgets($handle); + } + } + else if ($line == 1) + { + $bytes = (int) $buffer; + + // Never should have 0 bytes + if (!$bytes) + { + break; + } + + // Grab the serialized data + $data = fread($handle, $bytes); + + // Read 1 byte, to trigger EOF + fread($handle, 1); + + if (!feof($handle)) + { + // Somebody tampered with our data + $data = false; + } + break; + } + else + { + // Something went wrong + break; + } + $line++; + } + fclose($handle); + + // unserialize if we got some data + $data = ($data !== false) ? @unserialize($data) : $data; + + if ($data === false) + { + $this->remove_file($file); + return false; + } + + return $data; + } + } + + /** + * Write cache data to a specified file + * + * 'data_global' is a special case and the generated format is different for this file: + * + * + * (expiration) + * (length of var and serialised data) + * (var) + * (serialised data) + * ... (repeat) + * + * + * The other files have a similar format: + * + * + * (expiration) + * (query) [SQL files only] + * (length of serialised data) + * (serialised data) + * + * + * @access private + * @param string $filename Filename to write + * @param mixed $data Data to store + * @param int $expires Timestamp when the data expires + * @param string $query Query when caching SQL queries + * @return bool True if the file was successfully created, otherwise false + */ + function _write($filename, $data = null, $expires = 0, $query = '') + { + global $phpEx; + + $filename = $this->clean_varname($filename); + $file = "{$this->cache_dir}$filename.$phpEx"; + + $lock = new \phpbb\lock\flock($file); + $lock->acquire(); + + if ($handle = @fopen($file, 'wb')) + { + // File header + fwrite($handle, '<' . '?php exit; ?' . '>'); + + if ($filename == 'data_global') + { + // Global data is a different format + foreach ($this->vars as $var => $data) + { + if (strpos($var, "\r") !== false || strpos($var, "\n") !== false) + { + // CR/LF would cause fgets() to read the cache file incorrectly + // do not cache test entries, they probably won't be read back + // the cache keys should really be alphanumeric with a few symbols. + continue; + } + $data = serialize($data); + + // Write out the expiration time + fwrite($handle, "\n" . $this->var_expires[$var] . "\n"); + + // Length of the remaining data for this var (ignoring two LF's) + fwrite($handle, strlen($data . $var) . "\n"); + fwrite($handle, $var . "\n"); + fwrite($handle, $data); + } + } + else + { + fwrite($handle, "\n" . $expires . "\n"); + + if (strpos($filename, 'sql_') === 0) + { + fwrite($handle, $query . "\n"); + } + $data = serialize($data); + + fwrite($handle, strlen($data) . "\n"); + fwrite($handle, $data); + } + + fclose($handle); + + if (function_exists('opcache_invalidate')) + { + @opcache_invalidate($file); + } + + try + { + $this->filesystem->phpbb_chmod($file, CHMOD_READ | CHMOD_WRITE); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + + $return_value = true; + } + else + { + $return_value = false; + } + + $lock->release(); + + return $return_value; + } + + /** + * Replace slashes in the file name + * + * @param string $varname name of a cache variable + * @return string $varname name that is safe to use as a filename + */ + protected function clean_varname($varname) + { + return str_replace(array('/', '\\'), '-', $varname); + } +} diff --git a/phpbb/cache/driver/memcache.php b/phpbb/cache/driver/memcache.php new file mode 100644 index 0000000..57f138f --- /dev/null +++ b/phpbb/cache/driver/memcache.php @@ -0,0 +1,122 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +if (!defined('PHPBB_ACM_MEMCACHE_PORT')) +{ + define('PHPBB_ACM_MEMCACHE_PORT', 11211); +} + +if (!defined('PHPBB_ACM_MEMCACHE_COMPRESS')) +{ + define('PHPBB_ACM_MEMCACHE_COMPRESS', false); +} + +if (!defined('PHPBB_ACM_MEMCACHE_HOST')) +{ + define('PHPBB_ACM_MEMCACHE_HOST', 'localhost'); +} + +if (!defined('PHPBB_ACM_MEMCACHE')) +{ + //can define multiple servers with host1/port1,host2/port2 format + define('PHPBB_ACM_MEMCACHE', PHPBB_ACM_MEMCACHE_HOST . '/' . PHPBB_ACM_MEMCACHE_PORT); +} + +/** +* ACM for Memcached +*/ +class memcache extends \phpbb\cache\driver\memory +{ + var $extension = 'memcache'; + + var $memcache; + var $flags = 0; + + function __construct() + { + // Call the parent constructor + parent::__construct(); + + $this->memcache = new \Memcache; + foreach (explode(',', PHPBB_ACM_MEMCACHE) as $u) + { + preg_match('#(.*)/(\d+)#', $u, $parts); + $this->memcache->addServer(trim($parts[1]), (int) trim($parts[2])); + } + $this->flags = (PHPBB_ACM_MEMCACHE_COMPRESS) ? MEMCACHE_COMPRESSED : 0; + } + + /** + * {@inheritDoc} + */ + function unload() + { + parent::unload(); + + $this->memcache->close(); + } + + /** + * {@inheritDoc} + */ + function purge() + { + $this->memcache->flush(); + + parent::purge(); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + return $this->memcache->get($this->key_prefix . $var); + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + if (!$this->memcache->replace($this->key_prefix . $var, $data, $this->flags, $ttl)) + { + return $this->memcache->set($this->key_prefix . $var, $data, $this->flags, $ttl); + } + return true; + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return $this->memcache->delete($this->key_prefix . $var); + } +} diff --git a/phpbb/cache/driver/memcached.php b/phpbb/cache/driver/memcached.php new file mode 100644 index 0000000..7d66759 --- /dev/null +++ b/phpbb/cache/driver/memcached.php @@ -0,0 +1,134 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +if (!defined('PHPBB_ACM_MEMCACHED_PORT')) +{ + define('PHPBB_ACM_MEMCACHED_PORT', 11211); +} + +if (!defined('PHPBB_ACM_MEMCACHED_COMPRESS')) +{ + define('PHPBB_ACM_MEMCACHED_COMPRESS', true); +} + +if (!defined('PHPBB_ACM_MEMCACHED_HOST')) +{ + define('PHPBB_ACM_MEMCACHED_HOST', 'localhost'); +} + +if (!defined('PHPBB_ACM_MEMCACHED')) +{ + //can define multiple servers with host1/port1,host2/port2 format + define('PHPBB_ACM_MEMCACHED', PHPBB_ACM_MEMCACHED_HOST . '/' . PHPBB_ACM_MEMCACHED_PORT); +} + +/** +* ACM for Memcached +*/ +class memcached extends \phpbb\cache\driver\memory +{ + /** @var string Extension to use */ + protected $extension = 'memcached'; + + /** @var \Memcached Memcached class */ + protected $memcached; + + /** @var int Flags */ + protected $flags = 0; + + /** + * Memcached constructor + */ + public function __construct() + { + // Call the parent constructor + parent::__construct(); + + $this->memcached = new \Memcached(); + $this->memcached->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + // Memcached defaults to using compression, disable if we don't want + // to use it + if (!PHPBB_ACM_MEMCACHED_COMPRESS) + { + $this->memcached->setOption(\Memcached::OPT_COMPRESSION, false); + } + + foreach (explode(',', PHPBB_ACM_MEMCACHED) as $u) + { + preg_match('#(.*)/(\d+)#', $u, $parts); + $this->memcached->addServer(trim($parts[1]), (int) trim($parts[2])); + } + } + + /** + * {@inheritDoc} + */ + public function unload() + { + parent::unload(); + + unset($this->memcached); + } + + /** + * {@inheritDoc} + */ + public function purge() + { + $this->memcached->flush(); + + parent::purge(); + } + + /** + * Fetch an item from the cache + * + * @param string $var Cache key + * + * @return mixed Cached data + */ + protected function _read($var) + { + return $this->memcached->get($this->key_prefix . $var); + } + + /** + * Store data in the cache + * + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + protected function _write($var, $data, $ttl = 2592000) + { + if (!$this->memcached->replace($this->key_prefix . $var, $data, $ttl)) + { + return $this->memcached->set($this->key_prefix . $var, $data, $ttl); + } + return true; + } + + /** + * Remove an item from the cache + * + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + protected function _delete($var) + { + return $this->memcached->delete($this->key_prefix . $var); + } +} diff --git a/phpbb/cache/driver/memory.php b/phpbb/cache/driver/memory.php new file mode 100644 index 0000000..eba9549 --- /dev/null +++ b/phpbb/cache/driver/memory.php @@ -0,0 +1,282 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* ACM Abstract Memory Class +*/ +abstract class memory extends \phpbb\cache\driver\base +{ + var $key_prefix; + + /** + * Set cache path + */ + function __construct() + { + global $phpbb_root_path, $dbname, $table_prefix, $phpbb_container; + + $this->cache_dir = $phpbb_container->getParameter('core.cache_dir'); + $this->key_prefix = substr(md5($dbname . $table_prefix), 0, 8) . '_'; + + if (!isset($this->extension) || !extension_loaded($this->extension)) + { + global $acm_type; + + trigger_error("Could not find required extension [{$this->extension}] for the ACM module $acm_type.", E_USER_ERROR); + } + + if (isset($this->function) && !function_exists($this->function)) + { + global $acm_type; + + trigger_error("The required function [{$this->function}] is not available for the ACM module $acm_type.", E_USER_ERROR); + } + } + + /** + * {@inheritDoc} + */ + function load() + { + // grab the global cache + $data = $this->_read('global'); + + if ($data !== false) + { + $this->vars = $data; + return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function save() + { + if (!$this->is_modified) + { + return; + } + + $this->_write('global', $this->vars, 2592000); + + $this->is_modified = false; + } + + /** + * {@inheritDoc} + */ + function tidy() + { + global $config; + + // cache has auto GC, no need to have any code here :) + $config->set('cache_last_gc', time(), false); + } + + /** + * {@inheritDoc} + */ + function get($var_name) + { + if ($var_name[0] == '_') + { + if (!$this->_exists($var_name)) + { + return false; + } + + return $this->_read($var_name); + } + else + { + return ($this->_exists($var_name)) ? $this->vars[$var_name] : false; + } + } + + /** + * {@inheritDoc} + */ + function put($var_name, $var, $ttl = 2592000) + { + if ($var_name[0] == '_') + { + $this->_write($var_name, $var, $ttl); + } + else + { + $this->vars[$var_name] = $var; + $this->is_modified = true; + } + } + + /** + * {@inheritDoc} + */ + function destroy($var_name, $table = '') + { + if ($var_name == 'sql' && !empty($table)) + { + if (!is_array($table)) + { + $table = array($table); + } + + foreach ($table as $table_name) + { + // gives us the md5s that we want + $temp = $this->_read('sql_' . $table_name); + + if ($temp === false) + { + continue; + } + + // delete each query ref + foreach ($temp as $md5_id => $void) + { + $this->_delete('sql_' . $md5_id); + } + + // delete the table ref + $this->_delete('sql_' . $table_name); + } + + return; + } + + if (!$this->_exists($var_name)) + { + return; + } + + if ($var_name[0] == '_') + { + $this->_delete($var_name); + } + else if (isset($this->vars[$var_name])) + { + $this->is_modified = true; + unset($this->vars[$var_name]); + + // We save here to let the following cache hits succeed + $this->save(); + } + } + + /** + * {@inheritDoc} + */ + function _exists($var_name) + { + if ($var_name[0] == '_') + { + return $this->_isset($var_name); + } + else + { + if (!count($this->vars)) + { + $this->load(); + } + + return isset($this->vars[$var_name]); + } + } + + /** + * {@inheritDoc} + */ + function sql_save(\phpbb\db\driver\driver_interface $db, $query, $query_result, $ttl) + { + // Remove extra spaces and tabs + $query = preg_replace('/[\n\r\s\t]+/', ' ', $query); + $query_id = md5($query); + + // determine which tables this query belongs to + // Some queries use backticks, namely the get_database_size() query + // don't check for conformity, the SQL would error and not reach here. + if (!preg_match_all('/(?:FROM \\(?(`?\\w+`?(?: \\w+)?(?:, ?`?\\w+`?(?: \\w+)?)*)\\)?)|(?:JOIN (`?\\w+`?(?: \\w+)?))/', $query, $regs, PREG_SET_ORDER)) + { + // Bail out if the match fails. + return $query_result; + } + + $tables = array(); + foreach ($regs as $match) + { + if ($match[0][0] == 'F') + { + $tables = array_merge($tables, array_map('trim', explode(',', $match[1]))); + } + else + { + $tables[] = $match[2]; + } + } + + foreach ($tables as $table_name) + { + // Remove backticks + $table_name = ($table_name[0] == '`') ? substr($table_name, 1, -1) : $table_name; + + if (($pos = strpos($table_name, ' ')) !== false) + { + $table_name = substr($table_name, 0, $pos); + } + + $temp = $this->_read('sql_' . $table_name); + + if ($temp === false) + { + $temp = array(); + } + + $temp[$query_id] = true; + + // This must never expire + $this->_write('sql_' . $table_name, $temp, 0); + } + + // store them in the right place + $this->sql_rowset[$query_id] = array(); + $this->sql_row_pointer[$query_id] = 0; + + while ($row = $db->sql_fetchrow($query_result)) + { + $this->sql_rowset[$query_id][] = $row; + } + $db->sql_freeresult($query_result); + + $this->_write('sql_' . $query_id, $this->sql_rowset[$query_id], $ttl); + + return $query_id; + } + + /** + * Check if a cache var exists + * + * @access protected + * @param string $var Cache key + * @return bool True if it exists, otherwise false + */ + function _isset($var) + { + // Most caches don't need to check + return true; + } +} diff --git a/phpbb/cache/driver/redis.php b/phpbb/cache/driver/redis.php new file mode 100644 index 0000000..eaeb529 --- /dev/null +++ b/phpbb/cache/driver/redis.php @@ -0,0 +1,162 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +if (!defined('PHPBB_ACM_REDIS_PORT')) +{ + define('PHPBB_ACM_REDIS_PORT', 6379); +} + +if (!defined('PHPBB_ACM_REDIS_HOST')) +{ + define('PHPBB_ACM_REDIS_HOST', 'localhost'); +} + +/** +* ACM for Redis +* +* Compatible with the php extension phpredis available +* at https://github.com/nicolasff/phpredis +* +*/ +class redis extends \phpbb\cache\driver\memory +{ + var $extension = 'redis'; + + var $redis; + + /** + * Creates a redis cache driver. + * + * The following global constants affect operation: + * + * PHPBB_ACM_REDIS_HOST + * PHPBB_ACM_REDIS_PORT + * PHPBB_ACM_REDIS_PASSWORD + * PHPBB_ACM_REDIS_DB + * + * There are no publicly documented constructor parameters. + */ + function __construct() + { + // Call the parent constructor + parent::__construct(); + + $this->redis = new \Redis(); + + $args = func_get_args(); + if (!empty($args)) + { + $ok = call_user_func_array(array($this->redis, 'connect'), $args); + } + else + { + $ok = $this->redis->connect(PHPBB_ACM_REDIS_HOST, PHPBB_ACM_REDIS_PORT); + } + + if (!$ok) + { + trigger_error('Could not connect to redis server'); + } + + if (defined('PHPBB_ACM_REDIS_PASSWORD')) + { + if (!$this->redis->auth(PHPBB_ACM_REDIS_PASSWORD)) + { + global $acm_type; + + trigger_error("Incorrect password for the ACM module $acm_type.", E_USER_ERROR); + } + } + + $this->redis->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_PHP); + $this->redis->setOption(\Redis::OPT_PREFIX, $this->key_prefix); + + if (defined('PHPBB_ACM_REDIS_DB')) + { + if (!$this->redis->select(PHPBB_ACM_REDIS_DB)) + { + global $acm_type; + + trigger_error("Incorrect database for the ACM module $acm_type.", E_USER_ERROR); + } + } + } + + /** + * {@inheritDoc} + */ + function unload() + { + parent::unload(); + + $this->redis->close(); + } + + /** + * {@inheritDoc} + */ + function purge() + { + $this->redis->flushDB(); + + parent::purge(); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + return $this->redis->get($var); + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + if ($ttl == 0) + { + return $this->redis->set($var, $data); + } + return $this->redis->setex($var, $ttl, $data); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + if ($this->redis->delete($var) > 0) + { + return true; + } + return false; + } +} diff --git a/phpbb/cache/driver/wincache.php b/phpbb/cache/driver/wincache.php new file mode 100644 index 0000000..632b534 --- /dev/null +++ b/phpbb/cache/driver/wincache.php @@ -0,0 +1,73 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* ACM for WinCache +*/ +class wincache extends \phpbb\cache\driver\memory +{ + var $extension = 'wincache'; + + /** + * {@inheritDoc} + */ + function purge() + { + wincache_ucache_clear(); + + parent::purge(); + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + $success = false; + $result = wincache_ucache_get($this->key_prefix . $var, $success); + + return ($success) ? $result : false; + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + return wincache_ucache_set($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return wincache_ucache_delete($this->key_prefix . $var); + } +} diff --git a/phpbb/cache/driver/xcache.php b/phpbb/cache/driver/xcache.php new file mode 100644 index 0000000..0c845a6 --- /dev/null +++ b/phpbb/cache/driver/xcache.php @@ -0,0 +1,107 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache\driver; + +/** +* ACM for XCache +* +* To use this module you need ini_get() enabled and the following INI settings configured as follows: +* - xcache.var_size > 0 +* - xcache.admin.enable_auth = off (or xcache.admin.user and xcache.admin.password set) +* +*/ +class xcache extends \phpbb\cache\driver\memory +{ + var $extension = 'XCache'; + + function __construct() + { + parent::__construct(); + + if (!function_exists('ini_get') || (int) ini_get('xcache.var_size') <= 0) + { + trigger_error('Increase xcache.var_size setting above 0 or enable ini_get() to use this ACM module.', E_USER_ERROR); + } + } + + /** + * {@inheritDoc} + */ + function purge() + { + // Run before for XCache, if admin functions are disabled it will terminate execution + parent::purge(); + + // If the admin authentication is enabled but not set up, this will cause a nasty error. + // Not much we can do about it though. + $n = xcache_count(XC_TYPE_VAR); + + for ($i = 0; $i < $n; $i++) + { + xcache_clear_cache(XC_TYPE_VAR, $i); + } + } + + /** + * Fetch an item from the cache + * + * @access protected + * @param string $var Cache key + * @return mixed Cached data + */ + function _read($var) + { + $result = xcache_get($this->key_prefix . $var); + + return ($result !== null) ? $result : false; + } + + /** + * Store data in the cache + * + * @access protected + * @param string $var Cache key + * @param mixed $data Data to store + * @param int $ttl Time-to-live of cached data + * @return bool True if the operation succeeded + */ + function _write($var, $data, $ttl = 2592000) + { + return xcache_set($this->key_prefix . $var, $data, $ttl); + } + + /** + * Remove an item from the cache + * + * @access protected + * @param string $var Cache key + * @return bool True if the operation succeeded + */ + function _delete($var) + { + return xcache_unset($this->key_prefix . $var); + } + + /** + * Check if a cache var exists + * + * @access protected + * @param string $var Cache key + * @return bool True if it exists, otherwise false + */ + function _isset($var) + { + return xcache_isset($this->key_prefix . $var); + } +} diff --git a/phpbb/cache/service.php b/phpbb/cache/service.php new file mode 100644 index 0000000..502ae27 --- /dev/null +++ b/phpbb/cache/service.php @@ -0,0 +1,390 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cache; + +/** +* Class for grabbing/handling cached entries +*/ +class service +{ + /** + * Cache driver. + * + * @var \phpbb\cache\driver\driver_interface + */ + protected $driver; + + /** + * The config. + * + * @var \phpbb\config\config + */ + protected $config; + + /** + * Database connection. + * + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * Root path. + * + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP file extension. + * + * @var string + */ + protected $php_ext; + + /** + * Creates a cache service around a cache driver + * + * @param \phpbb\cache\driver\driver_interface $driver The cache driver + * @param \phpbb\config\config $config The config + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param string $phpbb_root_path Root path + * @param string $php_ext PHP file extension + */ + public function __construct(\phpbb\cache\driver\driver_interface $driver, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, $phpbb_root_path, $php_ext) + { + $this->set_driver($driver); + $this->config = $config; + $this->db = $db; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Returns the cache driver used by this cache service. + * + * @return \phpbb\cache\driver\driver_interface The cache driver + */ + public function get_driver() + { + return $this->driver; + } + + /** + * Replaces the cache driver used by this cache service. + * + * @param \phpbb\cache\driver\driver_interface $driver The cache driver + */ + public function set_driver(\phpbb\cache\driver\driver_interface $driver) + { + $this->driver = $driver; + } + + public function __call($method, $arguments) + { + return call_user_func_array(array($this->driver, $method), $arguments); + } + + /** + * Obtain list of naughty words and build preg style replacement arrays for use by the + * calling script + */ + function obtain_word_list() + { + if (($censors = $this->driver->get('_word_censors')) === false) + { + $sql = 'SELECT word, replacement + FROM ' . WORDS_TABLE; + $result = $this->db->sql_query($sql); + + $censors = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $censors['match'][] = get_censor_preg_expression($row['word']); + $censors['replace'][] = $row['replacement']; + } + $this->db->sql_freeresult($result); + + $this->driver->put('_word_censors', $censors); + } + + return $censors; + } + + /** + * Obtain currently listed icons + */ + function obtain_icons() + { + if (($icons = $this->driver->get('_icons')) === false) + { + // Topic icons + $sql = 'SELECT * + FROM ' . ICONS_TABLE . ' + ORDER BY icons_order'; + $result = $this->db->sql_query($sql); + + $icons = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $icons[$row['icons_id']]['img'] = $row['icons_url']; + $icons[$row['icons_id']]['width'] = (int) $row['icons_width']; + $icons[$row['icons_id']]['height'] = (int) $row['icons_height']; + $icons[$row['icons_id']]['alt'] = ($row['icons_alt']) ? $row['icons_alt'] : ''; + $icons[$row['icons_id']]['display'] = (bool) $row['display_on_posting']; + } + $this->db->sql_freeresult($result); + + $this->driver->put('_icons', $icons); + } + + return $icons; + } + + /** + * Obtain ranks + */ + function obtain_ranks() + { + if (($ranks = $this->driver->get('_ranks')) === false) + { + $sql = 'SELECT * + FROM ' . RANKS_TABLE . ' + ORDER BY rank_min DESC'; + $result = $this->db->sql_query($sql); + + $ranks = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['rank_special']) + { + unset($row['rank_min']); + $ranks['special'][$row['rank_id']] = $row; + } + else + { + $ranks['normal'][$row['rank_id']] = $row; + } + } + $this->db->sql_freeresult($result); + + $this->driver->put('_ranks', $ranks); + } + + return $ranks; + } + + /** + * Obtain allowed extensions + * + * @param mixed $forum_id If false then check for private messaging, if int then check for forum id. If true, then only return extension informations. + * + * @return array allowed extensions array. + */ + function obtain_attach_extensions($forum_id) + { + if (($extensions = $this->driver->get('_extensions')) === false) + { + $extensions = array( + '_allowed_post' => array(), + '_allowed_pm' => array(), + ); + + // The rule is to only allow those extensions defined. ;) + $sql = 'SELECT e.extension, g.* + FROM ' . EXTENSIONS_TABLE . ' e, ' . EXTENSION_GROUPS_TABLE . ' g + WHERE e.group_id = g.group_id + AND (g.allow_group = 1 OR g.allow_in_pm = 1)'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $extension = strtolower(trim($row['extension'])); + + $extensions[$extension] = array( + 'display_cat' => (int) $row['cat_id'], + 'download_mode' => (int) $row['download_mode'], + 'upload_icon' => trim($row['upload_icon']), + 'max_filesize' => (int) $row['max_filesize'], + 'allow_group' => $row['allow_group'], + 'allow_in_pm' => $row['allow_in_pm'], + 'group_name' => $row['group_name'], + ); + + $allowed_forums = ($row['allowed_forums']) ? unserialize(trim($row['allowed_forums'])) : array(); + + // Store allowed extensions forum wise + if ($row['allow_group']) + { + $extensions['_allowed_post'][$extension] = (!count($allowed_forums)) ? 0 : $allowed_forums; + } + + if ($row['allow_in_pm']) + { + $extensions['_allowed_pm'][$extension] = 0; + } + } + $this->db->sql_freeresult($result); + + $this->driver->put('_extensions', $extensions); + } + + // Forum post + if ($forum_id === false) + { + // We are checking for private messages, therefore we only need to get the pm extensions... + $return = array('_allowed_' => array()); + + foreach ($extensions['_allowed_pm'] as $extension => $check) + { + $return['_allowed_'][$extension] = 0; + $return[$extension] = $extensions[$extension]; + } + + $extensions = $return; + } + else if ($forum_id === true) + { + return $extensions; + } + else + { + $forum_id = (int) $forum_id; + $return = array('_allowed_' => array()); + + foreach ($extensions['_allowed_post'] as $extension => $check) + { + // Check for allowed forums + if (is_array($check)) + { + $allowed = (!in_array($forum_id, $check)) ? false : true; + } + else + { + $allowed = true; + } + + if ($allowed) + { + $return['_allowed_'][$extension] = 0; + $return[$extension] = $extensions[$extension]; + } + } + + $extensions = $return; + } + + if (!isset($extensions['_allowed_'])) + { + $extensions['_allowed_'] = array(); + } + + return $extensions; + } + + /** + * Obtain active bots + */ + function obtain_bots() + { + if (($bots = $this->driver->get('_bots')) === false) + { + switch ($this->db->get_sql_layer()) + { + case 'mssql_odbc': + case 'mssqlnative': + $sql = 'SELECT user_id, bot_agent, bot_ip + FROM ' . BOTS_TABLE . ' + WHERE bot_active = 1 + ORDER BY LEN(bot_agent) DESC'; + break; + + // LENGTH supported by MySQL, IBM DB2 and Oracle for sure... + default: + $sql = 'SELECT user_id, bot_agent, bot_ip + FROM ' . BOTS_TABLE . ' + WHERE bot_active = 1 + ORDER BY LENGTH(bot_agent) DESC'; + break; + } + $result = $this->db->sql_query($sql); + + $bots = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $bots[] = $row; + } + $this->db->sql_freeresult($result); + + $this->driver->put('_bots', $bots); + } + + return $bots; + } + + /** + * Obtain cfg file data + */ + function obtain_cfg_items($style) + { + $parsed_array = $this->driver->get('_cfg_' . $style['style_path']); + + if ($parsed_array === false) + { + $parsed_array = array(); + } + + $filename = $this->phpbb_root_path . 'styles/' . $style['style_path'] . '/style.cfg'; + + if (!file_exists($filename)) + { + return $parsed_array; + } + + if (!isset($parsed_array['filetime']) || (($this->config['load_tplcompile'] && @filemtime($filename) > $parsed_array['filetime']))) + { + // Re-parse cfg file + $parsed_array = parse_cfg_file($filename); + $parsed_array['filetime'] = @filemtime($filename); + + $this->driver->put('_cfg_' . $style['style_path'], $parsed_array); + } + + return $parsed_array; + } + + /** + * Obtain disallowed usernames + */ + function obtain_disallowed_usernames() + { + if (($usernames = $this->driver->get('_disallowed_usernames')) === false) + { + $sql = 'SELECT disallow_username + FROM ' . DISALLOW_TABLE; + $result = $this->db->sql_query($sql); + + $usernames = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $usernames[] = str_replace('%', '.*?', preg_quote(utf8_clean_string($row['disallow_username']), '#')); + } + $this->db->sql_freeresult($result); + + $this->driver->put('_disallowed_usernames', $usernames); + } + + return $usernames; + } +} diff --git a/phpbb/captcha/char_cube3d.php b/phpbb/captcha/char_cube3d.php new file mode 100644 index 0000000..0255259 --- /dev/null +++ b/phpbb/captcha/char_cube3d.php @@ -0,0 +1,277 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha; + +class char_cube3d +{ + var $bitmap; + var $bitmap_width; + var $bitmap_height; + + var $basis_matrix = array(array(1, 0, 0), array(0, 1, 0), array(0, 0, 1)); + var $abs_x = array(1, 0); + var $abs_y = array(0, 1); + var $x = 0; + var $y = 1; + var $z = 2; + var $letter = ''; + + /** + */ + function __construct(&$bitmaps, $letter) + { + $this->bitmap = $bitmaps['data'][$letter]; + $this->bitmap_width = $bitmaps['width']; + $this->bitmap_height = $bitmaps['height']; + + $this->basis_matrix[0][0] = mt_rand(-600, 600); + $this->basis_matrix[0][1] = mt_rand(-600, 600); + $this->basis_matrix[0][2] = (mt_rand(0, 1) * 2000) - 1000; + $this->basis_matrix[1][0] = mt_rand(-1000, 1000); + $this->basis_matrix[1][1] = mt_rand(-1000, 1000); + $this->basis_matrix[1][2] = mt_rand(-1000, 1000); + + $this->normalize($this->basis_matrix[0]); + $this->normalize($this->basis_matrix[1]); + $this->basis_matrix[2] = $this->cross_product($this->basis_matrix[0], $this->basis_matrix[1]); + $this->normalize($this->basis_matrix[2]); + + // $this->basis_matrix[1] might not be (probably isn't) orthogonal to $basis_matrix[0] + $this->basis_matrix[1] = $this->cross_product($this->basis_matrix[0], $this->basis_matrix[2]); + $this->normalize($this->basis_matrix[1]); + + // Make sure our cube is facing into the canvas (assuming +z == in) + for ($i = 0; $i < 3; ++$i) + { + if ($this->basis_matrix[$i][2] < 0) + { + $this->basis_matrix[$i][0] *= -1; + $this->basis_matrix[$i][1] *= -1; + $this->basis_matrix[$i][2] *= -1; + } + } + + // Force our "z" basis vector to be the one with greatest absolute z value + $this->x = 0; + $this->y = 1; + $this->z = 2; + + // Swap "y" with "z" + if ($this->basis_matrix[1][2] > $this->basis_matrix[2][2]) + { + $this->z = 1; + $this->y = 2; + } + + // Swap "x" with "z" + if ($this->basis_matrix[0][2] > $this->basis_matrix[$this->z][2]) + { + $this->x = $this->z; + $this->z = 0; + } + + // Still need to determine which of $x,$y are which. + // wrong orientation if y's y-component is less than it's x-component + // likewise if x's x-component is less than it's y-component + // if they disagree, go with the one with the greater weight difference. + // rotate if positive + $weight = (abs($this->basis_matrix[$this->x][1]) - abs($this->basis_matrix[$this->x][0])) + (abs($this->basis_matrix[$this->y][0]) - abs($this->basis_matrix[$this->y][1])); + + // Swap "x" with "y" + if ($weight > 0) + { + list($this->x, $this->y) = array($this->y, $this->x); + } + + $this->abs_x = array($this->basis_matrix[$this->x][0], $this->basis_matrix[$this->x][1]); + $this->abs_y = array($this->basis_matrix[$this->y][0], $this->basis_matrix[$this->y][1]); + + if ($this->abs_x[0] < 0) + { + $this->abs_x[0] *= -1; + $this->abs_x[1] *= -1; + } + + if ($this->abs_y[1] > 0) + { + $this->abs_y[0] *= -1; + $this->abs_y[1] *= -1; + } + + $this->letter = $letter; + } + + /** + * Draw a character + */ + function drawchar($scale, $xoff, $yoff, $img, $background, $colours) + { + $width = $this->bitmap_width; + $height = $this->bitmap_height; + $bitmap = $this->bitmap; + + $colour1 = $colours[array_rand($colours)]; + $colour2 = $colours[array_rand($colours)]; + + $swapx = ($this->basis_matrix[$this->x][0] > 0); + $swapy = ($this->basis_matrix[$this->y][1] < 0); + + for ($y = 0; $y < $height; ++$y) + { + for ($x = 0; $x < $width; ++$x) + { + $xp = ($swapx) ? ($width - $x - 1) : $x; + $yp = ($swapy) ? ($height - $y - 1) : $y; + + if ($bitmap[$height - $yp - 1][$xp]) + { + $dx = $this->scale($this->abs_x, ($xp - ($swapx ? ($width / 2) : ($width / 2) - 1)) * $scale); + $dy = $this->scale($this->abs_y, ($yp - ($swapy ? ($height / 2) : ($height / 2) - 1)) * $scale); + $xo = $xoff + $dx[0] + $dy[0]; + $yo = $yoff + $dx[1] + $dy[1]; + + $origin = array(0, 0, 0); + $xvec = $this->scale($this->basis_matrix[$this->x], $scale); + $yvec = $this->scale($this->basis_matrix[$this->y], $scale); + $face_corner = $this->sum2($xvec, $yvec); + + $zvec = $this->scale($this->basis_matrix[$this->z], $scale); + $x_corner = $this->sum2($xvec, $zvec); + $y_corner = $this->sum2($yvec, $zvec); + + imagefilledpolygon($img, $this->gen_poly($xo, $yo, $origin, $xvec, $x_corner,$zvec), 4, $colour1); + imagefilledpolygon($img, $this->gen_poly($xo, $yo, $origin, $yvec, $y_corner,$zvec), 4, $colour2); + + $face = $this->gen_poly($xo, $yo, $origin, $xvec, $face_corner, $yvec); + + imagefilledpolygon($img, $face, 4, $background); + imagepolygon($img, $face, 4, $colour1); + } + } + } + } + + /* + * return a roughly acceptable range of sizes for rendering with this texttype + */ + function range() + { + return array(3, 4); + } + + /** + * Vector length + */ + function vectorlen($vector) + { + return sqrt(pow($vector[0], 2) + pow($vector[1], 2) + pow($vector[2], 2)); + } + + /** + * Normalize + */ + function normalize(&$vector, $length = 1) + { + $length = (( $length < 1) ? 1 : $length); + $length /= $this->vectorlen($vector); + $vector[0] *= $length; + $vector[1] *= $length; + $vector[2] *= $length; + } + + /** + */ + function cross_product($vector1, $vector2) + { + $retval = array(0, 0, 0); + $retval[0] = (($vector1[1] * $vector2[2]) - ($vector1[2] * $vector2[1])); + $retval[1] = -(($vector1[0] * $vector2[2]) - ($vector1[2] * $vector2[0])); + $retval[2] = (($vector1[0] * $vector2[1]) - ($vector1[1] * $vector2[0])); + + return $retval; + } + + /** + */ + function sum($vector1, $vector2) + { + return array($vector1[0] + $vector2[0], $vector1[1] + $vector2[1], $vector1[2] + $vector2[2]); + } + + /** + */ + function sum2($vector1, $vector2) + { + return array($vector1[0] + $vector2[0], $vector1[1] + $vector2[1]); + } + + /** + */ + function scale($vector, $length) + { + if (count($vector) == 2) + { + return array($vector[0] * $length, $vector[1] * $length); + } + + return array($vector[0] * $length, $vector[1] * $length, $vector[2] * $length); + } + + /** + */ + function gen_poly($xoff, $yoff, &$vec1, &$vec2, &$vec3, &$vec4) + { + $poly = array(); + $poly[0] = $xoff + $vec1[0]; + $poly[1] = $yoff + $vec1[1]; + $poly[2] = $xoff + $vec2[0]; + $poly[3] = $yoff + $vec2[1]; + $poly[4] = $xoff + $vec3[0]; + $poly[5] = $yoff + $vec3[1]; + $poly[6] = $xoff + $vec4[0]; + $poly[7] = $yoff + $vec4[1]; + + return $poly; + } + + /** + * dimensions + */ + function dimensions($size) + { + $xn = $this->scale($this->basis_matrix[$this->x], -($this->bitmap_width / 2) * $size); + $xp = $this->scale($this->basis_matrix[$this->x], ($this->bitmap_width / 2) * $size); + $yn = $this->scale($this->basis_matrix[$this->y], -($this->bitmap_height / 2) * $size); + $yp = $this->scale($this->basis_matrix[$this->y], ($this->bitmap_height / 2) * $size); + + $p = array(); + $p[0] = $this->sum2($xn, $yn); + $p[1] = $this->sum2($xp, $yn); + $p[2] = $this->sum2($xp, $yp); + $p[3] = $this->sum2($xn, $yp); + + $min_x = $max_x = $p[0][0]; + $min_y = $max_y = $p[0][1]; + + for ($i = 1; $i < 4; ++$i) + { + $min_x = ($min_x > $p[$i][0]) ? $p[$i][0] : $min_x; + $min_y = ($min_y > $p[$i][1]) ? $p[$i][1] : $min_y; + $max_x = ($max_x < $p[$i][0]) ? $p[$i][0] : $max_x; + $max_y = ($max_y < $p[$i][1]) ? $p[$i][1] : $max_y; + } + + return array($min_x, $min_y, $max_x, $max_y); + } +} diff --git a/phpbb/captcha/colour_manager.php b/phpbb/captcha/colour_manager.php new file mode 100644 index 0000000..82332da --- /dev/null +++ b/phpbb/captcha/colour_manager.php @@ -0,0 +1,527 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha; + +class colour_manager +{ + var $img; + var $mode; + var $colours; + var $named_colours; + + /** + * Create the colour manager, link it to the image resource + */ + function __construct($img, $background = false, $mode = 'ahsv') + { + $this->img = $img; + $this->mode = $mode; + $this->colours = array(); + $this->named_colours = array(); + + if ($background !== false) + { + $bg = $this->allocate_named('background', $background); + imagefill($this->img, 0, 0, $bg); + } + } + + /** + * Lookup a named colour resource + */ + function get_resource($named_colour) + { + if (isset($this->named_colours[$named_colour])) + { + return $this->named_colours[$named_colour]; + } + + if (isset($this->named_rgb[$named_colour])) + { + return $this->allocate_named($named_colour, $this->named_rgb[$named_colour], 'rgb'); + } + + return false; + } + + /** + * Assign a name to a colour resource + */ + function name_colour($name, $resource) + { + $this->named_colours[$name] = $resource; + } + + /** + * names and allocates a colour resource + */ + function allocate_named($name, $colour, $mode = false) + { + $resource = $this->allocate($colour, $mode); + + if ($resource !== false) + { + $this->name_colour($name, $resource); + } + return $resource; + } + + /** + * allocates a specified colour into the image + */ + function allocate($colour, $mode = false) + { + if ($mode === false) + { + $mode = $this->mode; + } + + if (!is_array($colour)) + { + if (isset($this->named_rgb[$colour])) + { + return $this->allocate_named($colour, $this->named_rgb[$colour], 'rgb'); + } + + if (!is_int($colour)) + { + return false; + } + + $mode = 'rgb'; + $colour = array(255 & ($colour >> 16), 255 & ($colour >> 8), 255 & $colour); + } + + if (isset($colour['mode'])) + { + $mode = $colour['mode']; + unset($colour['mode']); + } + + if (isset($colour['random'])) + { + unset($colour['random']); + // everything else is params + return $this->random_colour($colour, $mode); + } + + $rgb = $this->model_convert($colour, $mode, 'rgb'); + $store = ($this->mode == 'rgb') ? $rgb : $this->model_convert($colour, $mode, $this->mode); + $resource = imagecolorallocate($this->img, $rgb[0], $rgb[1], $rgb[2]); + $this->colours[$resource] = $store; + + return $resource; + } + + /** + * randomly generates a colour, with optional params + */ + function random_colour($params = array(), $mode = false) + { + if ($mode === false) + { + $mode = $this->mode; + } + + switch ($mode) + { + case 'rgb': + // @TODO random rgb generation. do we intend to do this, or is it just too tedious? + break; + + case 'ahsv': + case 'hsv': + default: + + $default_params = array( + 'hue_bias' => false, // degree / 'r'/'g'/'b'/'c'/'m'/'y' /'o' + 'hue_range' => false, // if hue bias, then difference range +/- from bias + 'min_saturation' => 30, // 0 - 100 + 'max_saturation' => 80, // 0 - 100 + 'min_value' => 30, // 0 - 100 + 'max_value' => 80, // 0 - 100 + ); + + $alt = ($mode == 'ahsv') ? true : false; + $params = array_merge($default_params, $params); + + $min_hue = 0; + $max_hue = 359; + $min_saturation = max(0, $params['min_saturation']); + $max_saturation = min(100, $params['max_saturation']); + $min_value = max(0, $params['min_value']); + $max_value = min(100, $params['max_value']); + + if ($params['hue_bias'] !== false) + { + if (is_numeric($params['hue_bias'])) + { + $h = intval($params['hue_bias']) % 360; + } + else + { + switch ($params['hue_bias']) + { + case 'o': + $h = $alt ? 60 : 30; + break; + + case 'y': + $h = $alt ? 120 : 60; + break; + + case 'g': + $h = $alt ? 180 : 120; + break; + + case 'c': + $h = $alt ? 210 : 180; + break; + + case 'b': + $h = 240; + break; + + case 'm': + $h = 300; + break; + + case 'r': + default: + $h = 0; + break; + } + } + + $min_hue = $h + 360; + $max_hue = $h + 360; + + if ($params['hue_range']) + { + $min_hue -= min(180, $params['hue_range']); + $max_hue += min(180, $params['hue_range']); + } + } + + $h = mt_rand($min_hue, $max_hue); + $s = mt_rand($min_saturation, $max_saturation); + $v = mt_rand($min_value, $max_value); + + return $this->allocate(array($h, $s, $v), $mode); + + break; + } + } + + /** + */ + function colour_scheme($resource, $include_original = true) + { + $mode = 'hsv'; + + if (($pre = $this->get_resource($resource)) !== false) + { + $resource = $pre; + } + + $colour = $this->model_convert($this->colours[$resource], $this->mode, $mode); + $results = ($include_original) ? array($resource) : array(); + $colour2 = $colour3 = $colour4 = $colour; + $colour2[0] += 150; + $colour3[0] += 180; + $colour4[0] += 210; + + $results[] = $this->allocate($colour2, $mode); + $results[] = $this->allocate($colour3, $mode); + $results[] = $this->allocate($colour4, $mode); + + return $results; + } + + /** + */ + function mono_range($resource, $count = 5, $include_original = true) + { + if (is_array($resource)) + { + $results = array(); + for ($i = 0, $size = count($resource); $i < $size; ++$i) + { + $results = array_merge($results, $this->mono_range($resource[$i], $count, $include_original)); + } + return $results; + } + + $mode = (in_array($this->mode, array('hsv', 'ahsv'), true) ? $this->mode : 'ahsv'); + if (($pre = $this->get_resource($resource)) !== false) + { + $resource = $pre; + } + + $colour = $this->model_convert($this->colours[$resource], $this->mode, $mode); + + $results = array(); + if ($include_original) + { + $results[] = $resource; + $count--; + } + + // This is a hard problem. I chicken out and try to maintain readability at the cost of less randomness. + + while ($count > 0) + { + $colour[1] = ($colour[1] + mt_rand(40,60)) % 99; + $colour[2] = ($colour[2] + mt_rand(40,60)); + $results[] = $this->allocate($colour, $mode); + $count--; + } + return $results; + } + + /** + * Convert from one colour model to another + */ + function model_convert($colour, $from_model, $to_model) + { + if ($from_model == $to_model) + { + return $colour; + } + + switch ($to_model) + { + case 'hsv': + + switch ($from_model) + { + case 'ahsv': + return $this->ah2h($colour); + break; + + case 'rgb': + return $this->rgb2hsv($colour); + break; + } + break; + + case 'ahsv': + + switch ($from_model) + { + case 'hsv': + return $this->h2ah($colour); + break; + + case 'rgb': + return $this->h2ah($this->rgb2hsv($colour)); + break; + } + break; + + case 'rgb': + switch ($from_model) + { + case 'hsv': + return $this->hsv2rgb($colour); + break; + + case 'ahsv': + return $this->hsv2rgb($this->ah2h($colour)); + break; + } + break; + } + return false; + } + + /** + * Slightly altered from wikipedia's algorithm + */ + function hsv2rgb($hsv) + { + $this->normalize_hue($hsv[0]); + + $h = $hsv[0]; + $s = min(1, max(0, $hsv[1] / 100)); + $v = min(1, max(0, $hsv[2] / 100)); + + // calculate hue sector + $hi = floor($hsv[0] / 60); + + // calculate opposite colour + $p = $v * (1 - $s); + + // calculate distance between hex vertices + $f = ($h / 60) - $hi; + + // coming in or going out? + if (!($hi & 1)) + { + $f = 1 - $f; + } + + // calculate adjacent colour + $q = $v * (1 - ($f * $s)); + + switch ($hi) + { + case 0: + $rgb = array($v, $q, $p); + break; + + case 1: + $rgb = array($q, $v, $p); + break; + + case 2: + $rgb = array($p, $v, $q); + break; + + case 3: + $rgb = array($p, $q, $v); + break; + + case 4: + $rgb = array($q, $p, $v); + break; + + case 5: + $rgb = array($v, $p, $q); + break; + + default: + return array(0, 0, 0); + break; + } + + return array(255 * $rgb[0], 255 * $rgb[1], 255 * $rgb[2]); + } + + /** + * (more than) Slightly altered from wikipedia's algorithm + */ + function rgb2hsv($rgb) + { + $r = min(255, max(0, $rgb[0])); + $g = min(255, max(0, $rgb[1])); + $b = min(255, max(0, $rgb[2])); + $max = max($r, $g, $b); + $min = min($r, $g, $b); + + $v = $max / 255; + $s = (!$max) ? 0 : 1 - ($min / $max); + + // if max - min is 0, we want hue to be 0 anyway. + $h = $max - $min; + + if ($h) + { + switch ($max) + { + case $g: + $h = 120 + (60 * ($b - $r) / $h); + break; + + case $b: + $h = 240 + (60 * ($r - $g) / $h); + break; + + case $r: + $h = 360 + (60 * ($g - $b) / $h); + break; + } + } + $this->normalize_hue($h); + + return array($h, $s * 100, $v * 100); + } + + /** + */ + function normalize_hue(&$hue) + { + $hue %= 360; + + if ($hue < 0) + { + $hue += 360; + } + } + + /** + * Alternate hue to hue + */ + function ah2h($ahue) + { + if (is_array($ahue)) + { + $ahue[0] = $this->ah2h($ahue[0]); + return $ahue; + } + $this->normalize_hue($ahue); + + // blue through red is already ok + if ($ahue >= 240) + { + return $ahue; + } + + // ahue green is at 180 + if ($ahue >= 180) + { + // return (240 - (2 * (240 - $ahue))); + return (2 * $ahue) - 240; // equivalent + } + + // ahue yellow is at 120 (RYB rather than RGB) + if ($ahue >= 120) + { + return $ahue - 60; + } + + return $ahue / 2; + } + + /** + * hue to Alternate hue + */ + function h2ah($hue) + { + if (is_array($hue)) + { + $hue[0] = $this->h2ah($hue[0]); + return $hue; + } + $this->normalize_hue($hue); + + // blue through red is already ok + if ($hue >= 240) + { + return $hue; + } + else if ($hue <= 60) + { + return $hue * 2; + } + else if ($hue <= 120) + { + return $hue + 60; + } + else + { + return ($hue + 240) / 2; + } + } +} diff --git a/phpbb/captcha/factory.php b/phpbb/captcha/factory.php new file mode 100644 index 0000000..dd44aca --- /dev/null +++ b/phpbb/captcha/factory.php @@ -0,0 +1,88 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha; + +class factory +{ + /** + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + private $container; + + /** + * @var \phpbb\di\service_collection + */ + private $plugins; + + /** + * Constructor + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + * @param \phpbb\di\service_collection $plugins + */ + public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container, \phpbb\di\service_collection $plugins) + { + $this->container = $container; + $this->plugins = $plugins; + } + + /** + * Return a new instance of a given plugin + * + * @param $name + * @return object + */ + public function get_instance($name) + { + return $this->container->get($name); + } + + /** + * Call the garbage collector + * + * @param string $name The name to the captcha service. + */ + function garbage_collect($name) + { + $captcha = $this->get_instance($name); + $captcha->garbage_collect(0); + } + + /** + * Return a list of all registered CAPTCHA plugins + * + * @returns array + */ + function get_captcha_types() + { + $captchas = array( + 'available' => array(), + 'unavailable' => array(), + ); + + foreach ($this->plugins as $plugin => $plugin_instance) + { + if ($plugin_instance->is_available()) + { + $captchas['available'][$plugin] = $plugin_instance->get_name(); + } + else + { + $captchas['unavailable'][$plugin] = $plugin_instance->get_name(); + } + } + + return $captchas; + } +} diff --git a/phpbb/captcha/gd.php b/phpbb/captcha/gd.php new file mode 100644 index 0000000..91b2f89 --- /dev/null +++ b/phpbb/captcha/gd.php @@ -0,0 +1,1844 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha; + +class gd +{ + var $width = 360; + var $height = 96; + + /** + * Create the image containing $code with a seed of $seed + */ + function execute($code, $seed) + { + global $config; + + mt_srand($seed); + + // Create image + $img = imagecreatetruecolor($this->width, $this->height); + + // Generate colours + $colour = new colour_manager($img, array( + 'random' => true, + 'min_value' => 60, + ), 'hsv'); + + $scheme = $colour->colour_scheme('background', false); + $scheme = $colour->mono_range($scheme, 10, false); + shuffle($scheme); + + $bg_colours = array_splice($scheme, mt_rand(6, 12)); + + // Generate code characters + $characters = $sizes = $bounding_boxes = $noise = array(); + $width_avail = $this->width - 15; + $code_len = strlen($code); + $captcha_bitmaps = $this->captcha_bitmaps(); + + for ($i = 0; $i < $code_len; ++$i) + { + $characters[$i] = new char_cube3d($captcha_bitmaps, $code[$i]); + + list($min, $max) = $characters[$i]->range(); + $sizes[$i] = mt_rand($min, $max); + + $box = $characters[$i]->dimensions($sizes[$i]); + $width_avail -= ($box[2] - $box[0]); + $bounding_boxes[$i] = $box; + } + + // Redistribute leftover x-space + $offset = array(); + for ($i = 0; $i < $code_len; ++$i) + { + $denom = ($code_len - $i); + $denom = max(1.3, $denom); + $offset[$i] = phpbb_mt_rand(0, (int) round((1.5 * $width_avail) / $denom)); + $width_avail -= $offset[$i]; + } + + if ($config['captcha_gd_x_grid']) + { + $grid = (int) $config['captcha_gd_x_grid']; + for ($y = 0; $y < $this->height; $y += mt_rand($grid - 2, $grid + 2)) + { + $current_colour = $scheme[array_rand($scheme)]; + imageline($img, mt_rand(0,4), mt_rand($y - 3, $y), mt_rand($this->width - 5, $this->width), mt_rand($y - 3, $y), $current_colour); + } + } + + if ($config['captcha_gd_y_grid']) + { + $grid = (int) $config['captcha_gd_y_grid']; + for ($x = 0; $x < $this->width; $x += mt_rand($grid - 2, $grid + 2)) + { + $current_colour = $scheme[array_rand($scheme)]; + imagedashedline($img, mt_rand($x -3, $x + 3), mt_rand(0, 4), mt_rand($x -3, $x + 3), mt_rand($this->height - 5, $this->height), $current_colour); + } + } + + if ($config['captcha_gd_wave'] && ($config['captcha_gd_y_grid'] || $config['captcha_gd_y_grid'])) + { + $this->wave($img); + } + + if ($config['captcha_gd_3d_noise']) + { + $noise_bitmaps = $this->captcha_noise_bg_bitmaps(); + for ($i = 0; $i < $code_len; ++$i) + { + $noise[$i] = new char_cube3d($noise_bitmaps, mt_rand(1, count($noise_bitmaps['data']))); + + $noise[$i]->range(); + //$box = $noise[$i]->dimensions($sizes[$i]); + } + $xoffset = 0; + for ($i = 0; $i < $code_len; ++$i) + { + $dimm = $bounding_boxes[$i]; + $xoffset += ($offset[$i] - $dimm[0]); + $yoffset = mt_rand(-$dimm[1], $this->height - $dimm[3]); + + $noise[$i]->drawchar($sizes[$i], $xoffset, $yoffset, $img, $colour->get_resource('background'), $scheme); + $xoffset += $dimm[2]; + } + } + + $xoffset = 5; + for ($i = 0; $i < $code_len; ++$i) + { + $dimm = $bounding_boxes[$i]; + $xoffset += ($offset[$i] - $dimm[0]); + $yoffset = mt_rand(-$dimm[1], $this->height - $dimm[3]); + + $characters[$i]->drawchar($sizes[$i], $xoffset, $yoffset, $img, $colour->get_resource('background'), $scheme); + $xoffset += $dimm[2]; + } + + if ($config['captcha_gd_wave']) + { + $this->wave($img); + } + + if ($config['captcha_gd_foreground_noise']) + { + $this->noise_line($img, 0, 0, $this->width, $this->height, $colour->get_resource('background'), $scheme, $bg_colours); + } + + // Send image + header('Content-Type: image/png'); + header('Cache-control: no-cache, no-store'); + imagepng($img); + imagedestroy($img); + } + + /** + * Sinus + */ + function wave($img) + { + $period_x = mt_rand(12,18); + $period_y = mt_rand(7,14); + $amp_x = mt_rand(5,10); + $amp_y = mt_rand(2,4); + $socket = mt_rand(0,100); + + $dampen_x = mt_rand($this->width/5, $this->width/2); + $dampen_y = mt_rand($this->height/5, $this->height/2); + $direction_x = (mt_rand (0, 1)); + $direction_y = (mt_rand (0, 1)); + + for ($i = 0; $i < $this->width; $i++) + { + $dir = ($direction_x) ? $i : ($this->width - $i); + imagecopy($img, $img, $i-1, sin($socket+ $i/($period_x + $dir/$dampen_x)) * $amp_x, $i, 0, 1, $this->height); + } + $socket = mt_rand(0,100); + for ($i = 0; $i < $this->height; $i++) + { + $dir = ($direction_y) ? $i : ($this->height - $i); + imagecopy($img, $img ,sin($socket + $i/($period_y + ($dir)/$dampen_y)) * $amp_y, $i-1, 0, $i, $this->width, 1); + } + return $img; + } + + /** + * Noise line + */ + function noise_line($img, $min_x, $min_y, $max_x, $max_y, $bg, $font, $non_font) + { + imagesetthickness($img, 2); + + $x1 = $min_x; + $x2 = $max_x; + $y1 = $min_y; + $y2 = $min_y; + + do + { + $line = array_merge( + array_fill(0, mt_rand(30, 60), $non_font[array_rand($non_font)]), + array_fill(0, mt_rand(30, 60), $bg) + ); + + imagesetstyle($img, $line); + imageline($img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED); + + $y1 += mt_rand(12, 35); + $y2 += mt_rand(12, 35); + } + while ($y1 < $max_y && $y2 < $max_y); + + $x1 = $min_x; + $x2 = $min_x; + $y1 = $min_y; + $y2 = $max_y; + + do + { + $line = array_merge( + array_fill(0, mt_rand(30, 60), $non_font[array_rand($non_font)]), + array_fill(0, mt_rand(30, 60), $bg) + ); + + imagesetstyle($img, $line); + imageline($img, $x1, $y1, $x2, $y2, IMG_COLOR_STYLED); + + $x1 += mt_rand(20, 35); + $x2 += mt_rand(20, 35); + } + while ($x1 < $max_x && $x2 < $max_x); + imagesetthickness($img, 1); + } + + function captcha_noise_bg_bitmaps() + { + return array( + 'width' => 15, + 'height' => 5, + 'data' => array( + + 1 => array( + array(1,0,0,0,1,0,0,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,1,0,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,1,0,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0,0,0,1,0,0,0), + ), + 2 => array( + array(1,1,mt_rand(0,1),1,0,1,1,1,1,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,1,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,1,1,0,1,1,1), + ), + 3 => array( + array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0,0,0,0,0,1,0), + array(0,0,0,0,1,0,0,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,1), + ), + 4 => array( + array(1,0,1,0,1,0,0,1,1,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,1,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(1,0,1,0,0,0,0,0,0,0,0,0,0,0,0), + ), + 5 => array( + array(1,1,1,1,0,0,0,1,1,1,0,0,1,0,1), + array(0,0,0,0,0,0,0,1,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(1,0,1,0,0,0,0,0,0,0,0,0,0,0,0), + ), + 6 => array( + array(mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),0,mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),mt_rand(0,1),0,mt_rand(0,1),mt_rand(0,1),mt_rand(0,1)), + array(0,0,0,0,0,0,0,mt_rand(0,1),0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(mt_rand(0,1),0,mt_rand(0,1),0,0,0,0,0,0,0,0,0,0,0,0), + ), + 7 => array( + array(0,0,0,0,0,0,0,0,0,0,1,1,0,1,1), + array(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + array(0,0,1,1,0,0,0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,1,0,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0,0,0,0,0,0,0), + ), + )); + } + + /** + * Return bitmaps + */ + function captcha_bitmaps() + { + global $config; + + $chars = array( + 'A' => array( + array( + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,1,1,1,1,1,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,1,1,0,1,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,1,1,1,1,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,0,0,0,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,1,0,0), + array(0,1,1,0,0,0,1,1,0), + array(1,1,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,1), + array(0,0,0,0,0,1,1,1,1), + array(0,0,0,1,1,1,0,0,1), + array(0,1,1,1,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,1,0,0,0,0,1,1,1), + array(0,1,1,1,1,1,1,0,1), + ), + ), + 'B' => array( + array( + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + ), + array( + array(1,1,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + ), + array( + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,1,1,1,1,0,0), + ), + ), + 'C' => array( + array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + array( + array(0,0,1,1,1,1,1,0,1), + array(0,1,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,1), + array(0,0,1,1,1,1,1,0,1), + ), + ), + 'D' => array( + array( + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + ), + array( + array(1,1,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,1,1,1,1,1,0,1), + array(0,1,1,0,0,0,1,1,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,1,0,0,0,1,1,1), + array(0,0,1,1,1,1,1,0,1), + ), + ), + 'E' => array( + array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,1,1,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,1,1,1), + ), + array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,1), + array(1,1,1,1,1,1,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,1,0,0), + array(0,1,1,0,0,0,1,1,0), + array(1,1,0,0,0,0,0,1,1), + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,1), + array(1,1,0,0,0,0,0,1,1), + array(0,1,1,1,1,1,1,1,0), + ), + ), + 'F' => array( + array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + ), + array( + array(0,1,1,1,1,1,1,1,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(1,1,1,0,0,0,0,0,0), + ), + array( + array(0,0,0,1,1,0,0,0,0), + array(0,0,1,1,0,0,0,0,0), + array(0,1,1,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(1,1,1,1,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + ), + ), + 'G' => array( + array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,1,1,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + array( + array(0,0,1,1,1,1,1,0,1), + array(0,1,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,1,1,1,1,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,1), + array(0,0,1,1,1,1,1,0,1), + ), + array( + array(0,0,1,1,1,1,1,0,1), + array(0,1,1,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,1,1,0,0,0,0,0,1), + array(0,0,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,1), + array(1,1,1,1,1,1,1,1,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + ), + 'H' => array( + array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + ), + array( + array(1,1,1,0,0,0,1,1,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,1,1,1,1,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,0,0,0,1,1,1), + ), + array( + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,1,1,1,0,0,0), + array(1,1,1,1,0,1,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + ), + ), + 'I' => array( + array( + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(1,1,1,1,1,1,1,1,1), + ), + array( + array(0,0,0,1,1,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,1,1,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,1,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,1,1,0,0,0), + ), + ), + 'J' => array( + array( + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(0,1,0,0,1,0,0,0,0), + array(0,0,1,1,0,0,0,0,0), + ), + array( + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,1,0,0,1,0,0,0,0), + array(1,0,1,1,0,0,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(0,1,0,0,1,0,0,0,0), + array(0,0,1,1,0,0,0,0,0), + ), + ), + 'K' => array( + array( // New 'K', supplied by NeoThermic + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,1,0,0,0,0), + array(1,0,0,1,0,0,0,0,0), + array(1,0,1,0,0,0,0,0,0), + array(1,1,0,0,0,0,0,0,0), + array(1,0,1,0,0,0,0,0,0), + array(1,0,0,1,0,0,0,0,0), + array(1,0,0,0,1,0,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + ), + array( + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,1,0,0), + array(0,1,0,0,0,1,0,0,0), + array(0,1,0,0,1,0,0,0,0), + array(0,1,0,1,0,0,0,0,0), + array(0,1,1,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,1,0,0,0,0,0,0), + array(0,1,0,1,0,0,0,0,0), + array(0,1,0,0,1,0,0,0,0), + array(0,1,0,0,0,1,0,0,0), + array(0,1,0,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,0,0,0,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,1,0,0,0), + array(0,1,0,0,1,0,0,0,0), + array(0,1,0,1,0,0,0,0,0), + array(0,1,1,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,1,0,0,0,0,0,0), + array(0,1,0,1,0,0,0,0,0), + array(0,1,0,0,1,0,0,0,0), + array(0,1,0,0,0,1,0,0,0), + array(0,1,0,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + ), + ), + 'L' => array( + array( + array(0,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,1), + array(1,1,1,1,1,1,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,1,0,0,0,0,0,0), + array(0,0,1,1,1,0,0,0,0), + ), + ), + 'M' => array( + array( + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,0,1,0,0,0,1,0,1), + array(1,0,1,0,0,0,1,0,1), + array(1,0,1,0,0,0,1,0,1), + array(1,0,0,1,0,1,0,0,1), + array(1,0,0,1,0,1,0,0,1), + array(1,0,0,1,0,1,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,0,0,0,1,1,0), + array(0,1,1,0,0,0,1,1,0), + array(0,1,1,0,0,0,1,1,0), + array(0,1,0,1,0,1,0,1,0), + array(0,1,0,1,0,1,0,1,0), + array(0,1,0,1,0,1,0,1,0), + array(0,1,0,0,1,0,0,1,0), + array(0,1,0,0,1,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,0,0,0,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,1,1,1,0,1,1,1,0), + array(1,1,0,1,1,1,0,1,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + ), + ), + 'N' => array( + array( + array(1,1,0,0,0,0,0,0,1), + array(1,1,0,0,0,0,0,0,1), + array(1,0,1,0,0,0,0,0,1), + array(1,0,1,0,0,0,0,0,1), + array(1,0,0,1,0,0,0,0,1), + array(1,0,0,1,0,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,0,1,0,0,1), + array(1,0,0,0,0,1,0,0,1), + array(1,0,0,0,0,0,1,0,1), + array(1,0,0,0,0,0,1,0,1), + array(1,0,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,0,0,0,0,1,0), + array(0,1,1,0,0,0,0,1,0), + array(0,1,1,0,0,0,0,1,0), + array(0,1,0,1,0,0,0,1,0), + array(0,1,0,1,0,0,0,1,0), + array(0,1,0,1,0,0,0,1,0), + array(0,1,0,0,1,0,0,1,0), + array(0,1,0,0,1,1,0,1,0), + array(0,1,0,0,0,1,0,1,0), + array(0,1,0,0,0,1,1,1,0), + array(0,1,0,0,0,0,1,1,0), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,0,0,0,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(1,0,1,1,1,1,0,0,0), + array(1,1,1,0,0,1,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + ), + ), + 'O' => array( + array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,1,1,1,1,1,0,0,0), + array(1,1,1,0,0,1,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,1,0,0,0,1,1,0,0), + array(0,1,1,1,1,1,0,0,0), + ), + ), + 'P' => array( + array( + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + ), + array( + array(1,1,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(1,1,1,0,0,0,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,1,1,0,0,0,0,0), + array(1,1,0,1,1,0,0,0,0), + array(1,0,0,0,1,0,0,0,0), + array(1,0,0,0,1,0,0,0,0), + array(1,0,0,1,1,0,0,0,0), + array(1,1,1,1,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + ), + ), + 'Q' => array( + array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,1,0,0,1), + array(1,0,0,0,0,0,1,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,1), + ), + array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,1,0,0,1,1,0,1,1), + array(0,1,1,1,1,1,1,1,0), + array(0,0,0,0,0,0,1,1,0), + array(0,0,0,0,0,0,0,1,1), + array(0,0,0,0,0,0,0,0,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,1,1,1,1), + array(0,0,0,0,1,1,0,0,1), + array(0,0,0,0,1,0,0,0,1), + array(0,0,0,0,1,0,0,0,1), + array(0,0,0,0,1,1,0,1,1), + array(0,0,0,0,0,1,1,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + ), + ), + 'R' => array( + array( + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + array(1,1,1,0,0,0,0,0,0), + array(1,0,0,1,0,0,0,0,0), + array(1,0,0,0,1,0,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + ), + array( + array(1,1,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + array(0,1,1,0,0,0,0,0,0), + array(0,1,1,1,0,0,0,0,0), + array(0,1,0,1,1,0,0,0,0), + array(0,1,0,0,1,1,0,0,0), + array(0,1,0,0,0,1,1,0,0), + array(0,1,0,0,0,0,1,1,0), + array(1,1,1,0,0,0,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,0,0,0,0), + array(1,1,0,0,1,1,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + ), + ), + 'S' => array( + array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + array( + array(0,0,1,1,1,1,1,0,1), + array(0,1,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,1,0,0,0,0,0,1,0), + array(1,0,1,1,1,1,1,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,1,1,1,1,0,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,0,0,0,0,0,0,0), + array(0,1,1,1,1,0,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(1,0,0,0,1,1,0,0,0), + array(0,1,1,1,1,0,0,0,0), + ), + ), + 'T' => array( + array( + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + ), + array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,1,0,0,0,1), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,1,1,0,0,0), + ), + array( + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,1,1,1,1,1,1,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,1,0,0,0), + array(0,0,0,0,0,1,1,1,0), + ), + ), + 'U' => array( + array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + array( + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,0,0,0,1,1,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,0,0,0,1,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,1,0,0,0,0,0,1), + array(0,0,1,0,0,0,0,0,1), + array(0,0,1,0,0,0,0,0,1), + array(0,0,1,0,0,0,0,0,1), + array(0,0,1,0,0,0,0,0,1), + array(0,0,1,0,0,0,0,1,1), + array(0,0,1,1,0,0,1,1,1), + array(0,0,0,1,1,1,1,0,1), + ), + ), + 'V' => array( + array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(1,1,1,0,0,0,1,1,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + ), + ), + 'W' => array( + array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,1,0,1,0,0,1), + array(1,0,0,1,0,1,0,0,1), + array(1,0,0,1,0,1,0,0,1), + array(1,0,1,0,0,0,1,0,1), + array(1,0,1,0,0,0,1,0,1), + array(1,0,1,0,0,0,1,0,1), + array(1,1,0,0,0,0,0,1,1), + array(1,1,0,0,0,0,0,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(1,1,1,0,0,0,1,1,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,1,0,0,1,0), + array(0,1,0,0,1,0,0,1,0), + array(0,1,0,1,1,1,0,1,0), + array(0,1,0,1,0,1,0,1,0), + array(0,1,1,1,0,1,1,1,0), + array(0,1,1,0,0,0,1,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,1,0,0,1,0), + array(0,1,0,0,1,0,0,1,0), + array(0,1,0,1,1,1,0,1,0), + array(0,1,0,1,0,1,0,1,0), + array(0,1,1,1,0,1,1,1,0), + array(0,1,1,0,0,0,1,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,0), + ), + ), + 'X' => array( + array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,1,0,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(1,1,1,0,0,0,1,1,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,1,0,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,1,1,0,0,0,1,1,1), + array(0,0,0,0,0,0,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,0,0,0,1,1,0), + array(0,0,1,1,0,1,1,0,0), + array(0,0,0,1,1,1,0,0,0), + array(0,0,0,1,1,1,0,0,0), + array(0,0,1,1,0,1,1,0,0), + array(0,1,1,0,0,0,1,1,0), + array(0,0,0,0,0,0,0,0,0), + ), + ), + 'Y' => array( + array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(1,1,1,0,0,0,1,1,1), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,1,1,0,0,0), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,1,0,0,0,0,1), + array(0,0,0,1,1,0,0,0,1), + array(0,0,0,0,1,0,0,1,1), + array(0,0,0,0,1,1,0,1,0), + array(0,0,0,0,0,1,1,1,0), + array(0,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,1,1,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,1,1,0,0,0), + array(0,0,1,1,1,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + ), + 'Z' => array( + array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,1,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,1), + array(1,1,1,1,1,1,1,1,1), + ), + array( + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,1,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,1,1,1), + ), + array( + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,1,1,1,1,1,1,1,0), + array(0,0,0,0,0,1,1,0,0), + array(0,0,0,0,1,1,0,0,0), + array(0,0,0,1,1,0,0,0,0), + array(0,0,1,1,0,0,0,0,0), + array(0,0,1,0,0,0,0,0,0), + array(0,1,1,1,1,1,1,1,0), + ), + ), + ); + return array( + 'width' => 9, + 'height' => 15, + 'data' => array( + + 'A' => $chars['A'][mt_rand(0, min(count($chars['A']), $config['captcha_gd_fonts']) -1)], + 'B' => $chars['B'][mt_rand(0, min(count($chars['B']), $config['captcha_gd_fonts']) -1)], + 'C' => $chars['C'][mt_rand(0, min(count($chars['C']), $config['captcha_gd_fonts']) -1)], + 'D' => $chars['D'][mt_rand(0, min(count($chars['D']), $config['captcha_gd_fonts']) -1)], + 'E' => $chars['E'][mt_rand(0, min(count($chars['E']), $config['captcha_gd_fonts']) -1)], + 'F' => $chars['F'][mt_rand(0, min(count($chars['F']), $config['captcha_gd_fonts']) -1)], + 'G' => $chars['G'][mt_rand(0, min(count($chars['G']), $config['captcha_gd_fonts']) -1)], + 'H' => $chars['H'][mt_rand(0, min(count($chars['H']), $config['captcha_gd_fonts']) -1)], + 'I' => $chars['I'][mt_rand(0, min(count($chars['I']), $config['captcha_gd_fonts']) -1)], + 'J' => $chars['J'][mt_rand(0, min(count($chars['J']), $config['captcha_gd_fonts']) -1)], + 'K' => $chars['K'][mt_rand(0, min(count($chars['K']), $config['captcha_gd_fonts']) -1)], + 'L' => $chars['L'][mt_rand(0, min(count($chars['L']), $config['captcha_gd_fonts']) -1)], + 'M' => $chars['M'][mt_rand(0, min(count($chars['M']), $config['captcha_gd_fonts']) -1)], + 'N' => $chars['N'][mt_rand(0, min(count($chars['N']), $config['captcha_gd_fonts']) -1)], + 'O' => $chars['O'][mt_rand(0, min(count($chars['O']), $config['captcha_gd_fonts']) -1)], + 'P' => $chars['P'][mt_rand(0, min(count($chars['P']), $config['captcha_gd_fonts']) -1)], + 'Q' => $chars['Q'][mt_rand(0, min(count($chars['Q']), $config['captcha_gd_fonts']) -1)], + 'R' => $chars['R'][mt_rand(0, min(count($chars['R']), $config['captcha_gd_fonts']) -1)], + 'S' => $chars['S'][mt_rand(0, min(count($chars['S']), $config['captcha_gd_fonts']) -1)], + 'T' => $chars['T'][mt_rand(0, min(count($chars['T']), $config['captcha_gd_fonts']) -1)], + 'U' => $chars['U'][mt_rand(0, min(count($chars['U']), $config['captcha_gd_fonts']) -1)], + 'V' => $chars['V'][mt_rand(0, min(count($chars['V']), $config['captcha_gd_fonts']) -1)], + 'W' => $chars['W'][mt_rand(0, min(count($chars['W']), $config['captcha_gd_fonts']) -1)], + 'X' => $chars['X'][mt_rand(0, min(count($chars['X']), $config['captcha_gd_fonts']) -1)], + 'Y' => $chars['Y'][mt_rand(0, min(count($chars['Y']), $config['captcha_gd_fonts']) -1)], + 'Z' => $chars['Z'][mt_rand(0, min(count($chars['Z']), $config['captcha_gd_fonts']) -1)], + + '1' => array( + array(0,0,0,1,1,0,0,0,0), + array(0,0,1,0,1,0,0,0,0), + array(0,1,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,1,1,1,1,1,1,1,0), + ), + '2' => array( // New '2' supplied by Anon + array(0,0,0,1,1,1,0,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,1,0,0,0,0,1,1,0), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,1,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,0,0), + ), + '3' => array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,1,1,0,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + '4' => array( + array(0,0,0,0,0,0,1,1,0), + array(0,0,0,0,0,1,0,1,0), + array(0,0,0,0,1,0,0,1,0), + array(0,0,0,1,0,0,0,1,0), + array(0,0,1,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + ), + '5' => array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + '6' => array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,1,1,1,1,0,0), + array(1,0,1,0,0,0,0,1,0), + array(1,1,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + '7' => array( + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,1,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + ), + '8' => array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + '9' => array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,1,1), + array(0,1,0,0,0,0,1,0,1), + array(0,0,1,1,1,1,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + ), + ) + ); + } +} diff --git a/phpbb/captcha/gd_wave.php b/phpbb/captcha/gd_wave.php new file mode 100644 index 0000000..f2ec413 --- /dev/null +++ b/phpbb/captcha/gd_wave.php @@ -0,0 +1,842 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha; + +/** +* Wave3D CAPTCHA +*/ +class gd_wave +{ + var $width = 360; + var $height = 96; + + function execute($code, $seed) + { + // seed the random generator + mt_srand($seed); + + // set height and width + $img_x = $this->width; + $img_y = $this->height; + + // Generate image + $img = imagecreatetruecolor($img_x, $img_y); + $x_grid = mt_rand(6, 10); + $y_grid = mt_rand(6, 10); + + // Ok, so lets cut to the chase. We could accurately represent this in 3d and + // do all the appropriate linear transforms. my questions is... why bother? + // The computational overhead is unnecessary when you consider the simple fact: + // we're not here to accurately represent a model, but to just show off some random-ish + // polygons + + // Conceive of 3 spaces. + // 1) planar-space (discrete "pixel" grid) + // 2) 3-space. (planar-space with z/height aspect) + // 3) image space (pixels on the screen) + // resolution of the planar-space we're embedding the text code in + $plane_x = 100; + $plane_y = 30; + + $subdivision_factor = 3; + + // $box is the 4 points in img_space that correspond to the corners of the plane in 3-space + $box = array( + 'upper_left' => array( + 'x' => mt_rand(5, 15), + 'y' => mt_rand(10, 15) + ), + 'upper_right' => array( + 'x' => mt_rand($img_x - 35, $img_x - 19), + 'y' => mt_rand(10, 17) + ), + 'lower_left' => array( + 'x' => mt_rand($img_x - 45, $img_x - 5), + 'y' => mt_rand($img_y - 15, $img_y - 0), + ), + ); + + $box['lower_right'] = array( + 'x' => $box['lower_left']['x'] + $box['upper_left']['x'] - $box['upper_right']['x'], + 'y' => $box['lower_left']['y'] + $box['upper_left']['y'] - $box['upper_right']['y'], + ); + + // TODO + $background = imagecolorallocate($img, mt_rand(155, 255), mt_rand(155, 255), mt_rand(155, 255)); + imagefill($img, 0, 0, $background); + + $random = array(); + $fontcolors = array(); + + for ($i = 0; $i < 15; ++$i) + { + $random[$i] = imagecolorallocate($img, mt_rand(120, 255), mt_rand(120, 255), mt_rand(120, 255)); + } + + $fontcolors[0] = imagecolorallocate($img, mt_rand(0, 120), mt_rand(0, 120), mt_rand(0, 120)); + + $colors = array(); + + $minr = mt_rand(20, 30); + $ming = mt_rand(20, 30); + $minb = mt_rand(20, 30); + + $maxr = mt_rand(150, 230); + $maxg = mt_rand(150, 230); + $maxb = mt_rand(150, 230); + + for ($i = -30; $i <= 30; ++$i) + { + $coeff1 = ($i + 12) / 45; + $coeff2 = 1 - $coeff1; + $colors[$i] = imagecolorallocate($img, ($coeff2 * $maxr) + ($coeff1 * $minr), ($coeff2 * $maxg) + ($coeff1 * $ming), ($coeff2 * $maxb) + ($coeff1 * $minb)); + } + + // $img_buffer is the last row of 3-space positions (converted to img-space), cached + // (using this means we don't need to recalculate all 4 positions for each new polygon, + // merely the newest point that we're adding, which is then cached. + $img_buffer = array(array(), array()); + + // In image-space, the x- and y-offset necessary to move one unit in the x-direction in planar-space + $dxx = ($box['upper_right']['x'] - $box['upper_left']['x']) / ($subdivision_factor * $plane_x); + $dxy = ($box['upper_right']['y'] - $box['upper_left']['y']) / ($subdivision_factor * $plane_x); + + // In image-space, the x- and y-offset necessary to move one unit in the y-direction in planar-space + $dyx = ($box['lower_right']['x'] - $box['upper_left']['x']) / ($subdivision_factor * $plane_y); + $dyy = ($box['lower_right']['y'] - $box['upper_left']['y']) / ($subdivision_factor * $plane_y); + + // Initial captcha-letter offset in planar-space + $plane_offset_x = mt_rand(3, 8); + $plane_offset_y = mt_rand( 12, 15); + + // character map + $map = $this->captcha_bitmaps(); + + // matrix + $plane = array(); + + // for each character, we'll silkscreen it into our boolean pixel plane + for ($c = 0, $code_num = strlen($code); $c < $code_num; ++$c) + { + $letter = $code[$c]; + + for ($x = $map['width'] - 1; $x >= 0; --$x) + { + for ($y = $map['height'] - 1; $y >= 0; --$y) + { + if ($map['data'][$letter][$y][$x]) + { + $plane[$y + $plane_offset_y + (($c & 1) ? 1 : -1)][$x + $plane_offset_x] = true; + } + } + } + $plane_offset_x += 11; + } + + // calculate our first buffer, we can't actually draw polys with these yet + // img_pos_prev == screen x,y location to our immediate left. + // img_pos_cur == current screen x,y location + // we calculate screen position of our + // current cell based on the difference from the previous cell + // rather than recalculating from absolute coordinates + // What we cache into the $img_buffer contains the raised text coordinates. + $img_pos_prev = $img_buffer[0][0] = array($box['upper_left']['x'], $box['upper_left']['y']); + $prev_height = $this->wave_height(0, 0, $subdivision_factor); + $full_x = $plane_x * $subdivision_factor; + $full_y = $plane_y * $subdivision_factor; + + for ($x = 1; $x <= $full_x; ++$x) + { + $cur_height = $this->wave_height($x, 0, $subdivision_factor); + $offset = $cur_height - $prev_height; + $img_pos_cur = array($img_pos_prev[0] + $dxx, $img_pos_prev[1] + $dxy + $offset); + + $img_buffer[0][$x] = $img_pos_cur; + $img_pos_prev = $img_pos_cur; + $prev_height = $cur_height; + } + + for ($y = 1; $y <= $full_y; ++$y) + { + // swap buffers + $buffer_cur = $y & 1; + $buffer_prev = 1 - $buffer_cur; + + $prev_height = $this->wave_height(0, $y, $subdivision_factor); + $offset = $prev_height - $this->wave_height(0, $y - 1, $subdivision_factor); + $img_pos_cur = array($img_buffer[$buffer_prev][0][0] + $dyx, min($img_buffer[$buffer_prev][0][1] + $dyy + $offset, $img_y - 1)); + + // make sure we don't try to write off the page + $img_pos_prev = $img_pos_cur; + + $img_buffer[$buffer_cur][0] = $img_pos_cur; + + for ($x = 1; $x <= $full_x; ++$x) + { + $cur_height = $this->wave_height($x, $y, $subdivision_factor) + $this->grid_height($x, $y, $x_grid, $y_grid, 1); + + // height is a z-factor, not a y-factor + $offset = $cur_height - $prev_height; + $img_pos_cur = array($img_pos_prev[0] + $dxx, $img_pos_prev[1] + $dxy + $offset); + + // height is float, index it to an int, get closest color + $color = $colors[intval($cur_height)]; + $img_pos_prev = $img_pos_cur; + $prev_height = $cur_height; + + $y_index_old = intval(($y - 1) / $subdivision_factor); + $y_index_new = intval($y / $subdivision_factor); + $x_index_old = intval(($x - 1) / $subdivision_factor); + $x_index_new = intval($x / $subdivision_factor); + + if (!empty($plane[$y_index_new][$x_index_new])) + { + $img_pos_cur[1] += $this->wave_height($x, $y, $subdivision_factor, 1) - 30 - $cur_height; + $color = $colors[20]; + } + $img_pos_cur[1] = min($img_pos_cur[1], $img_y - 1); + $img_buffer[$buffer_cur][$x] = $img_pos_cur; + + // Smooth the edges as much as possible by having not more than one low<->high traingle per square + // Otherwise, just + $diag_down = (empty($plane[$y_index_old][$x_index_old]) == empty($plane[$y_index_new][$x_index_new])); + $diag_up = (empty($plane[$y_index_old][$x_index_new]) == empty($plane[$y_index_new][$x_index_old])); + + // natural switching + $mode = ($x + $y) & 1; + + // override if it requires it + if ($diag_down != $diag_up) + { + $mode = $diag_up; + } + + if ($mode) + { + // +-/ / + // 1 |/ 2 /| + // / /-+ + $poly1 = array_merge($img_buffer[$buffer_cur][$x - 1], $img_buffer[$buffer_prev][$x - 1], $img_buffer[$buffer_prev][$x]); + $poly2 = array_merge($img_buffer[$buffer_cur][$x - 1], $img_buffer[$buffer_cur][$x], $img_buffer[$buffer_prev][$x]); + } + else + { + // \ \-+ + // 1 |\ 2 \| + // +-\ \ + $poly1 = array_merge($img_buffer[$buffer_cur][$x - 1], $img_buffer[$buffer_prev][$x - 1], $img_buffer[$buffer_cur][$x]); + $poly2 = array_merge($img_buffer[$buffer_prev][$x - 1], $img_buffer[$buffer_prev][$x], $img_buffer[$buffer_cur][$x]); + } + + imagefilledpolygon($img, $poly1, 3, $color); + imagefilledpolygon($img, $poly2, 3, $color); + } + } + + // Output image + header('Content-Type: image/png'); + header('Cache-control: no-cache, no-store'); + //$mtime = explode(' ', microtime()); + //$totaltime = $mtime[0] + $mtime[1] - $starttime; + + //echo $totaltime . "
\n"; + //echo memory_get_usage() - $tmp; + imagepng($img); + imagedestroy($img); + } + + function wave_height($x, $y, $factor = 1, $tweak = 0.7) + { + // stretch the wave. TODO: pretty it up + $x = $x/5 + 180; + $y = $y/4; + return ((sin($x / (3 * $factor)) + sin($y / (3 * $factor))) * 10 * $tweak); + } + + function grid_height($x, $y, $x_grid, $y_grid, $factor = 1) + { + return ((!($x % ($x_grid * $factor)) || !($y % ($y_grid * $factor))) ? 3 : 0); + } + + function captcha_bitmaps() + { + return array( + 'width' => 9, + 'height' => 13, + 'data' => array( + 'A' => array( + array(0,0,1,1,1,1,0,0,0), + array(0,1,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'B' => array( + array(1,1,1,1,1,1,0,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,1,0,0), + array(1,1,1,1,1,1,0,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,1,0,0), + array(1,1,1,1,1,1,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'C' => array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'D' => array( + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'E' => array( + array(0,0,1,1,1,1,1,1,1), + array(0,1,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'F' => array( + array(0,0,1,1,1,1,1,1,0), + array(0,1,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,1,1,1,1,1,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'G' => array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'H' => array( + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,1,1,1,1,1,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'I' => array( + array(0,1,1,1,1,1,1,1,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,1,1,1,1,1,1,1,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'J' => array( + array(0,0,0,0,0,0,1,1,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,0,1,0,0,0,0,1,0), + array(0,0,0,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'K' => array( + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,1,0,0,0,0), + array(1,0,0,1,0,0,0,0,0), + array(1,0,1,0,0,0,0,0,0), + array(1,1,0,0,0,0,0,0,0), + array(1,0,1,0,0,0,0,0,0), + array(1,0,0,1,0,0,0,0,0), + array(1,0,0,0,1,0,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'L' => array( + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'M' => array( + array(0,1,0,0,0,0,0,1,0), + array(0,1,1,0,0,0,1,1,0), + array(0,1,0,1,0,1,0,1,0), + array(0,1,0,0,1,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'N' => array( + array(1,0,0,0,0,0,0,0,1), + array(1,1,0,0,0,0,0,0,1), + array(1,0,1,0,0,0,0,0,1), + array(1,0,0,1,0,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,0,0,1,0,0,1), + array(1,0,0,0,0,0,1,0,1), + array(1,0,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'O' => array( + array(0,0,0,1,1,1,0,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,1,1,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'P' => array( + array(1,1,1,1,1,1,0,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,1,0,0), + array(1,1,1,1,1,1,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'Q' => array( + array(0,0,1,1,1,1,0,0,0), + array(0,1,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,1,0,0,1,0), + array(1,0,0,0,0,1,0,1,0), + array(0,1,0,0,0,0,1,0,0), + array(0,0,1,1,1,1,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'R' => array( + array(1,1,1,1,1,1,0,0,0), + array(1,0,0,0,0,0,1,0,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,1,0,0), + array(1,1,1,1,1,1,0,0,0), + array(1,0,1,0,0,0,0,0,0), + array(1,0,0,1,0,0,0,0,0), + array(1,0,0,0,1,0,0,0,0), + array(1,0,0,0,0,1,0,0,0), + array(1,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'S' => array( + array(0,0,1,1,1,1,1,1,1), + array(0,1,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(1,1,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'T' => array( + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'U' => array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'V' => array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'W' => array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,1,0,0,0,1), + array(1,0,0,1,0,1,0,0,1), + array(1,0,1,0,0,0,1,0,1), + array(1,1,0,0,0,0,0,1,1), + array(1,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'X' => array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'Y' => array( + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,0,0,0,1,0,0), + array(0,0,0,1,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + 'Z' => array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,1,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,1), + array(1,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + '1' => array( + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,1,0,0,0,0), + array(0,0,1,0,1,0,0,0,0), + array(0,1,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,1,1,1,1,1,1,1,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + '2' => array( + array(0,0,0,1,1,1,0,0,0), + array(0,0,1,0,0,0,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,1,0,0,0,0,0), + array(0,0,1,0,0,0,0,0,0), + array(0,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,0,0), + ), + '3' => array( + array(0,0,0,1,1,1,1,0,0), + array(0,0,1,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,1,1,0,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,0,1,0,0,0,0,1,0), + array(0,0,0,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + '4' => array( + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,1,1,0), + array(0,0,0,0,0,1,0,1,0), + array(0,0,0,0,1,0,0,1,0), + array(0,0,0,1,0,0,0,1,0), + array(0,0,1,0,0,0,0,1,0), + array(0,1,1,1,1,1,1,1,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + '5' => array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(0,1,0,0,0,0,0,0,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + '6' => array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,0,0,0,0,0,0), + array(1,0,0,1,1,1,1,0,0), + array(1,0,1,0,0,0,0,1,0), + array(1,1,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + '7' => array( + array(1,1,1,1,1,1,1,1,1), + array(1,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,1,0), + array(0,0,0,0,0,0,1,0,0), + array(0,0,0,0,0,1,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,1,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + '8' => array( + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + array(0,1,0,0,0,0,0,1,0), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(1,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,0), + array(0,0,1,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + '9' => array( + array(0,0,0,1,1,1,1,0,0), + array(0,0,1,0,0,0,0,1,0), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,1,1), + array(0,0,1,1,1,1,1,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,0,0,0,0,0,0,0,1), + array(0,1,0,0,0,0,0,0,1), + array(0,0,1,0,0,0,0,1,0), + array(0,0,0,1,1,1,1,0,0), + array(0,0,0,0,0,0,0,0,0), + array(0,0,0,0,0,0,0,0,0), + ), + ) + ); + } +} diff --git a/phpbb/captcha/non_gd.php b/phpbb/captcha/non_gd.php new file mode 100644 index 0000000..3818672 --- /dev/null +++ b/phpbb/captcha/non_gd.php @@ -0,0 +1,386 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha; + +/** +* Main non-gd captcha class +* @ignore +*/ +class non_gd +{ + var $filtered_pngs; + var $width = 320; + var $height = 50; + + /** + * Define filtered pngs on init + */ + function __construct() + { + // If we can we will generate a single filtered png, we avoid nastiness via emulation of some Zlib stuff + $this->define_filtered_pngs(); + } + + /** + * Create the image containing $code with a seed of $seed + */ + function execute($code, $seed) + { + $img_height = $this->height - 10; + $img_width = 0; + + mt_srand($seed); + + $char_widths = $hold_chars = array(); + $code_len = strlen($code); + + for ($i = 0; $i < $code_len; $i++) + { + $char = $code[$i]; + + $width = mt_rand(0, 4); + $raw_width = $this->filtered_pngs[$char]['width']; + $char_widths[$i] = $width; + $img_width += $raw_width - $width; + + // Split the char into chunks of $raw_width + 1 length + if (empty($hold_chars[$char])) + { + $hold_chars[$char] = str_split(base64_decode($this->filtered_pngs[$char]['data']), $raw_width + 1); + } + } + + $offset_x = mt_rand(0, $this->width - $img_width); + $offset_y = mt_rand(0, $this->height - $img_height); + + $image = ''; + for ($i = 0; $i < $this->height; $i++) + { + $image .= chr(0); + + if ($i > $offset_y && $i < $offset_y + $img_height) + { + for ($j = 0; $j < $offset_x; $j++) + { + $image .= chr(mt_rand(140, 255)); + } + + for ($j = 0; $j < $code_len; $j++) + { + $image .= $this->randomise(substr($hold_chars[$code{$j}][$i - $offset_y - 1], 1), $char_widths[$j]); + } + + for ($j = $offset_x + $img_width; $j < $this->width; $j++) + { + $image .= chr(mt_rand(140, 255)); + } + } + else + { + for ($j = 0; $j < $this->width; $j++) + { + $image .= chr(mt_rand(140, 255)); + } + } + } + unset($hold_chars); + + $image = $this->create_png($image, $this->width, $this->height); + + // Output image + header('Content-Type: image/png'); + header('Cache-control: no-cache, no-store'); + echo $image; + exit; + } + + /** + * This is designed to randomise the pixels of the image data within + * certain limits so as to keep it readable. It also varies the image + * width a little + */ + function randomise($scanline, $width) + { + $new_line = ''; + + $end = strlen($scanline) - ceil($width/2); + for ($i = (int) floor($width / 2); $i < $end; $i++) + { + $pixel = ord($scanline{$i}); + + if ($pixel < 190) + { + $new_line .= chr(mt_rand(0, 205)); + } + else if ($pixel > 190) + { + $new_line .= chr(mt_rand(145, 255)); + } + else + { + $new_line .= $scanline{$i}; + } + } + + return $new_line; + } + + /** + * This creates a chunk of the given type, with the given data + * of the given length adding the relevant crc + */ + function png_chunk($length, $type, $data) + { + $raw = $type . $data; + + return pack('N', $length) . $raw . pack('N', crc32($raw)); + } + + /** + * Creates greyscale 8bit png - The PNG spec can be found at + * http://www.libpng.org/pub/png/spec/PNG-Contents.html we use + * png because it's a fully recognised open standard and supported + * by practically all modern browsers and OSs + */ + function create_png($raw_image, $width, $height) + { + // SIG + $image = pack('C8', 137, 80, 78, 71, 13, 10, 26, 10); + + // IHDR + $raw = pack('N2', $width, $height); + $raw .= pack('C5', 8, 0, 0, 0, 0); + $image .= $this->png_chunk(13, 'IHDR', $raw); + + // IDAT + if (@extension_loaded('zlib')) + { + $raw_image = gzcompress($raw_image); + $length = strlen($raw_image); + } + else + { + // The total length of this image, uncompressed, is just a calculation of pixels + $length = ($width + 1) * $height; + + // Adler-32 hash generation + // Note: The hash is _backwards_ so we must reverse it + + if (@extension_loaded('hash')) + { + $adler_hash = strrev(hash('adler32', $raw_image, true)); + } + else if (@extension_loaded('mhash')) + { + $adler_hash = strrev(mhash(MHASH_ADLER32, $raw_image)); + } + else + { + // Optimized Adler-32 loop ported from the GNU Classpath project + $temp_length = $length; + $s1 = 1; + $s2 = $index = 0; + + while ($temp_length > 0) + { + // We can defer the modulo operation: + // s1 maximally grows from 65521 to 65521 + 255 * 3800 + // s2 maximally grows by 3800 * median(s1) = 2090079800 < 2^31 + $substract_value = ($temp_length < 3800) ? $temp_length : 3800; + $temp_length -= $substract_value; + + while (--$substract_value >= 0) + { + $s1 += ord($raw_image[$index]); + $s2 += $s1; + + $index++; + } + + $s1 %= 65521; + $s2 %= 65521; + } + $adler_hash = pack('N', ($s2 << 16) | $s1); + } + + // This is the same thing as gzcompress($raw_image, 0) but does not need zlib + $raw_image = pack('C3v2', 0x78, 0x01, 0x01, $length, ~$length) . $raw_image . $adler_hash; + + // The Zlib header + Adler hash make us add on 11 + $length += 11; + } + + // IDAT + $image .= $this->png_chunk($length, 'IDAT', $raw_image); + + // IEND + $image .= $this->png_chunk(0, 'IEND', ''); + + return $image; + } + + /** + * png image data + * Each 'data' element is base64_encoded uncompressed IDAT + */ + function define_filtered_pngs() + { + $this->filtered_pngs = array( + '0' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A///////////////////olFAkBAAAGDyA4P///M31/////////////wD////////////////0dAgAAAAAAAAAAAAEcPipFGHn////////////AP//////////////6DAAAAAAAAAAAAAAAAAALSEAN+T///////////8A//////////////xAAAAAAAAAAAAAAAAAAAAAACPA/////////////wD/////////////oAAAAAAAAAAAAAAAAAAAAAAAev//////////////AP////////////8oAAAAAAAAPNj/zDAAAAAAAABD//////////////8A////////////1AAAAAAAABjw////5BAAAAAAAADo/////////////wD///////////+QAAAAAAAAbP//////QgAAAAAAAKj/////////////AP///////////1wAAAAAAACs/////8AXAAAAAAAAcP////////////8A////////////OAAAAAAAAND////dNwAAAAAAAABI/////////////wD///////////8gAAAAAAAA4P//7koACwAAAAAAACT/////////////AP///////////wgAAAAAAAD///VqAwaPAAAAAAAAEP////////////8A////////////AAAAAAAAAP/8kQYDavUAAAAAAAAA/////////////wD///////////8AAAAAAAAA/6kNAEru/wAAAAAAAAD/////////////AP///////////wAAAAAAAADAIwA33f//AAAAAAAAAP////////////8A////////////FAAAAAAAADYAI8D///8AAAAAAAAQ/////////////wD///////////8kAAAAAAAAAA2p////5AAAAAAAACD/////////////AP///////////0gAAAAAAAAFkfz////UAAAAAAAAQP////////////8A////////////cAAAAAAAAET1/////7AAAAAAAABo/////////////wD///////////+oAAAAAAAAXfX/////sAAAAAAAAGj/////////////AAAAALgAAAAAAAAwAAAAAAAAAAAAAAD////////////oAAAAAAAACOT////oEAAAAAAAAOD/////////////AP////////////8+AAAAAAAAKMz/zDQAAAAAAAA0//////////////8A////////////7jgAAAAAAAAAAAAAAAAAAAAAAKT//////////////wD///////////VqAwIAAAAAAAAAAAAAAAAAAAA8////////////////AP//////////rQcDaVEAAAAAAAAAAAAAAAAAKOj///////////////8A///////////nblnu/IAIAAAAAAAAAAAAAFzw/////////////////wD////////////79////+iITCAAAAAgSITg////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////w==', + 'width' => 40 + ), + '1' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////8BAAAAAAAP//////////////////AP////////////////////////9sAAAAAAAA//////////////////8A////////////////////////pAAAAAAAAAD//////////////////wD//////////////////////6wEAAAAAAAAAP//////////////////AP////////////////////h4AAAAAAAAAAAA//////////////////8A//////////////////ygJAAAAAAAAAAAAAD//////////////////wD//////////////9x8HAAAAAAAAAAAAAAAAP//////////////////AP//////////////AAAAAAAAAAAAAAAAAAAA//////////////////8A//////////////8AAAAAAAAAAAAAAAAAAAD//////////////////wD//////////////wAAAAAAAAR4AAAAAAAAAP//////////////////AP//////////////AAAAAAA4zP8AAAAAAAAA//////////////////8A//////////////8AAAA4sP///wAAAAAAAAD//////////////////wD//////////////yR80P//////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////AAAAAAAAAP//////////////////AP////////////////////////8AAAAAAAAA//////////////////8A/////////////////////////wAAAAAAAAD//////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + '2' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP/////////////////okFAkCAAABCBIfNT///////////////////8A///////////////8hAgAAAAAAAAAAAAAAFTo/////////////////wD//////////////1QAAAAAAAAAAAAAAAAAACjo////////////////AP////////////+MAAAAAAAAAAAAAAAAAAAAADj///////////////8A////////////9BAAAAAAAAAAAAAAAAAAAAAAALD//////////////wD///////////+gAAAAAAAAAHjs+KwMAAAAAAAAVP//////////////AP///////////1gAAAAAAABM/////6QAAAAAAAAU//////////////8A////////////KAAAAAAAALj/////+AAAAAAAAAD//////////////wD///////////+MfGBMOCAI8P/////wAAAAAAAACP//////////////AP///////////////////////////5wAAAAAAAAw//////////////8A///////////////////////////oFAAAAAAAAHz//////////////wD/////////////////////////6CgAAAAAAAAE3P//////////////AP///////////////////////9ggAAAAAAAAAHT///////////////8A//////////////////////+0DAAAAAAAAAA8+P///////////////wD/////////////////////gAAAAAAAAAAAKOj/////////////////AP//////////////////9FAAAAAAAAAAADzw//////////////////8A/////////////////+g4AAAAAAAAAABk/P///////////////////wD////////////////oKAAAAAAAAAAMqP//////////////////////AP//////////////6CgAAAAAAAAAMNz///////////////////////8A//////////////g4AAAAAAAAAFT0/////////////////////////wD/////////////bAAAAAAAAABU/P//////////////////////////AP///////////8wAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A////////////SAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////9wAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////hAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////9AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////xAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + '3' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD////////////////8sGg0FAAAACA4cLz8////////////////////AP//////////////rBgAAAAAAAAAAAAAACTA//////////////////8A/////////////3QAAAAAAAAAAAAAAAAAAASs/////////////////wD///////////+YAAAAAAAAAAAAAAAAAAAAAAjc////////////////AP//////////6AwAAAAAAAAAAAAAAAAAAAAAAGT///////////////8A//////////94AAAAAAAABJDw/8g4AAAAAAAAHP///////////////wD//////////yAAAAAAAACE/////9gAAAAAAAAA////////////////AP///////////NSwiGQ4FOT//////AAAAAAAABD///////////////8A//////////////////////////+YAAAAAAAAVP///////////////wD//////////////////////P/ggAQAAAAAAATM////////////////AP////////////////////9gAAAAAAAAAAAElP////////////////8A/////////////////////0AAAAAAAAAAHLj//////////////////wD/////////////////////OAAAAAAAAAAwkPj/////////////////AP////////////////////8gAAAAAAAAAAAAINj///////////////8A/////////////////////xAAAAAAAAAAAAAAIPD//////////////wD/////////////////////uOz/4HgEAAAAAAAAhP//////////////AP///////////////////////////3wAAAAAAAAw//////////////8A////////////////////////////6AAAAAAAAAj//////////////wD/////////////////////////////AAAAAAAAAP//////////////AP//////////tJh8YEQoDNz//////+AAAAAAAAAY//////////////8A//////////88AAAAAAAAaP//////dAAAAAAAAEz//////////////wD//////////6QAAAAAAAAAdOD/5HQAAAAAAAAApP//////////////AP///////////CgAAAAAAAAAAAAAAAAAAAAAACD4//////////////8A////////////yAQAAAAAAAAAAAAAAAAAAAAEuP///////////////wD/////////////rAQAAAAAAAAAAAAAAAAABJD/////////////////AP//////////////zDQAAAAAAAAAAAAAACTA//////////////////8A/////////////////8BwOCAAAAAUNGi0/P///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + '4' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////////////////nAAAAAAAAAD///////////////8A/////////////////////////8AEAAAAAAAAAP///////////////wD////////////////////////gGAAAAAAAAAAA////////////////AP//////////////////////9DAAAAAAAAAAAAD///////////////8A//////////////////////9UAAAAAAAAAAAAAP///////////////wD/////////////////////hAAAAAAAAAAAAAAA////////////////AP///////////////////7QAAAAAAAAAAAAAAAD///////////////8A///////////////////UDAAAAAAUAAAAAAAAAP///////////////wD/////////////////7CQAAAAABMAAAAAAAAAA////////////////AP////////////////xEAAAAAACU/wAAAAAAAAD///////////////8A////////////////cAAAAAAAZP//AAAAAAAAAP///////////////wD//////////////6AAAAAAADz8//8AAAAAAAAA////////////////AP/////////////IBAAAAAAc6P///wAAAAAAAAD///////////////8A////////////5BgAAAAADMz/////AAAAAAAAAP///////////////wD///////////g0AAAAAACk//////8AAAAAAAAA////////////////AP//////////XAAAAAAAfP///////wAAAAAAAAD///////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A////////////////////////////AAAAAAAAAP///////////////wD///////////////////////////8AAAAAAAAA////////////////AP///////////////////////////wAAAAAAAAD///////////////8A////////////////////////////AAAAAAAAAP///////////////wD///////////////////////////8AAAAAAAAA////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + '5' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////8AAAAAAAAAAAAAAAAAAAAAAA//////////////8A///////////////MAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////////6wAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////////iAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////////9kAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////////0QAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////////IAAAAAAAYP////////////////////////////8A//////////////wAAAAAAAB8/////////////////////////////wD/////////////3AAAAAAAAIj/////////////////////////////AP////////////+4AAAAAAAAoLRYHAAEKGTE//////////////////8A/////////////5QAAAAAAAAQAAAAAAAAAABY9P///////////////wD/////////////dAAAAAAAAAAAAAAAAAAAAAA89P//////////////AP////////////9QAAAAAAAAAAAAAAAAAAAAAABg//////////////8A/////////////zAAAAAAAAAAAAAAAAAAAAAAAADQ/////////////wD/////////////IAAAAAAAAGjY/+h4BAAAAAAAAGz/////////////AP//////////////9NS0lHSc//////90AAAAAAAALP////////////8A/////////////////////////////9QAAAAAAAAE/////////////wD//////////////////////////////wAAAAAAAAD/////////////AP/////////////////////////////8AAAAAAAAEP////////////8A////////////pIRwWEAgDOD//////8wAAAAAAAA8/////////////wD///////////9EAAAAAAAAaP//////ZAAAAAAAAHz/////////////AP///////////6QAAAAAAAAAaOD/4GQAAAAAAAAE4P////////////8A/////////////CQAAAAAAAAAAAAAAAAAAAAAAGD//////////////wD/////////////yAQAAAAAAAAAAAAAAAAAAAAc7P//////////////AP//////////////rAwAAAAAAAAAAAAAAAAAGNj///////////////8A////////////////0EAAAAAAAAAAAAAAAFTo/////////////////wD//////////////////8h4QCAAAAAcQHzU////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + '6' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////////////+0ZCwMAAAUNGjI////////////////////AP/////////////////EMAAAAAAAAAAAAABM6P////////////////8A////////////////lAQAAAAAAAAAAAAAAAAo6P///////////////wD//////////////6wAAAAAAAAAAAAAAAAAAABI////////////////AP/////////////oEAAAAAAAAAAAAAAAAAAAAACw//////////////8A/////////////3AAAAAAAAAoxP/YPAAAAAAAAEj//////////////wD////////////4EAAAAAAACOD////YDCBAVGiAoP//////////////AP///////////7gAAAAAAABY//////////////////////////////8A////////////eAAAAAAAAJT//////////////////////////////wD///////////9MAAAAAAAAvP/IXBgABCx03P//////////////////AP///////////ygAAAAAAADcdAAAAAAAAAAEiP////////////////8A////////////FAAAAAAAAFAAAAAAAAAAAAAAcP///////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAlP//////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAQ8P////////////8A////////////AAAAAAAAAABAyP/kZAAAAAAAAACQ/////////////wD///////////8MAAAAAAAALPj/////WAAAAAAAAET/////////////AP///////////yQAAAAAAACY///////MAAAAAAAAFP////////////8A////////////SAAAAAAAAMD///////wAAAAAAAAA/////////////wD///////////9wAAAAAAAAvP///////wAAAAAAAAD/////////////AP///////////7QAAAAAAACI///////UAAAAAAAAJP////////////8A////////////+AwAAAAAACDw/////2wAAAAAAABY/////////////wD/////////////cAAAAAAAADC8/Ox4AAAAAAAAAKj/////////////AP/////////////oEAAAAAAAAAAAAAAAAAAAAAAk/P////////////8A//////////////+oAAAAAAAAAAAAAAAAAAAABLj//////////////wD///////////////+QAAAAAAAAAAAAAAAAAACQ////////////////AP////////////////+0JAAAAAAAAAAAAAAkuP////////////////8A///////////////////8sGg0FAAADCxgqPz//////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + '7' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAABP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAy4/////////////wD//////////////////////////+QUAAAAAAAEuP//////////////AP/////////////////////////8QAAAAAAAAKT///////////////8A/////////////////////////4wAAAAAAAB0/////////////////wD////////////////////////cCAAAAAAANPz/////////////////AP///////////////////////0QAAAAAAATY//////////////////8A//////////////////////+0AAAAAAAAeP///////////////////wD//////////////////////CQAAAAAABTw////////////////////AP////////////////////+gAAAAAAAAkP////////////////////8A/////////////////////ywAAAAAABDw/////////////////////wD///////////////////+4AAAAAAAAbP//////////////////////AP///////////////////1wAAAAAAADQ//////////////////////8A///////////////////4DAAAAAAAMP///////////////////////wD//////////////////7QAAAAAAAB8////////////////////////AP//////////////////aAAAAAAAAMj///////////////////////8A//////////////////8oAAAAAAAM/P///////////////////////wD/////////////////8AAAAAAAAET/////////////////////////AP////////////////+0AAAAAAAAcP////////////////////////8A/////////////////4wAAAAAAACY/////////////////////////wD/////////////////WAAAAAAAAMD/////////////////////////AP////////////////80AAAAAAAA4P////////////////////////8A/////////////////xAAAAAAAAD4/////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + '8' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD////////////////////IdDQUAAAEIEiA1P//////////////////AP/////////////////gRAAAAAAAAAAAAAAAROD///////////////8A////////////////0BgAAAAAAAAAAAAAAAAAEMj//////////////wD///////////////AcAAAAAAAAAAAAAAAAAAAAHPD/////////////AP//////////////hAAAAAAAAAAAAAAAAAAAAAAAhP////////////8A//////////////8sAAAAAAAAKMz/zCgAAAAAAAAs/////////////wD//////////////wAAAAAAAADM////zAAAAAAAAAD/////////////AP//////////////BAAAAAAAAP//////AAAAAAAABP////////////8A//////////////8sAAAAAAAAzP///9QAAAAAAAAw/////////////wD//////////////3wAAAAAAAAoyP/YNAAAAAAAAIT/////////////AP//////////////7BgAAAAAAAAAAAAAAAAAAAAc8P////////////8A////////////////xBgAAAAAAAAAAAAAAAAAGNj//////////////wD/////////////////tAQAAAAAAAAAAAAAAACo////////////////AP///////////////HAAAAAAAAAAAAAAAAAAAAB8//////////////8A//////////////9gAAAAAAAAAAAAAAAAAAAAAAB8/////////////wD/////////////wAAAAAAAAABk4P/UWAAAAAAAAATQ////////////AP////////////9UAAAAAAAAaP//////XAAAAAAAAGT///////////8A/////////////xgAAAAAAADg///////cAAAAAAAAJP///////////wD/////////////AAAAAAAAAP////////8AAAAAAAAA////////////AP////////////8AAAAAAAAA4P//////3AAAAAAAAAT///////////8A/////////////ygAAAAAAABg//////9cAAAAAAAALP///////////wD/////////////ZAAAAAAAAABY1P/cXAAAAAAAAABw////////////AP/////////////QAAAAAAAAAAAAAAAAAAAAAAAABNz///////////8A//////////////9gAAAAAAAAAAAAAAAAAAAAAAB0/////////////wD///////////////Q8AAAAAAAAAAAAAAAAAAAAUPz/////////////AP////////////////x4CAAAAAAAAAAAAAAAEIT8//////////////8A///////////////////smFQwGAAAABg0ZKT0/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + '9' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////////////ysYCwMAAAUNGiw/P//////////////////AP////////////////+4JAAAAAAAAAAAAAAkuP////////////////8A////////////////lAQAAAAAAAAAAAAAAAAAkP///////////////wD//////////////8AEAAAAAAAAAAAAAAAAAAAAqP//////////////AP/////////////8JAAAAAAAAAAAAAAAAAAAAAAQ7P////////////8A/////////////6wAAAAAAAAAfOz8vCwAAAAAAABw/////////////wD/////////////WAAAAAAAAHD/////7BgAAAAAAAz4////////////AP////////////8kAAAAAAAA1P//////hAAAAAAAALT///////////8A/////////////wAAAAAAAAD///////+4AAAAAAAAcP///////////wD/////////////AAAAAAAAAPz//////8AAAAAAAABI////////////AP////////////8UAAAAAAAAzP//////lAAAAAAAACT///////////8A/////////////0QAAAAAAABY//////gsAAAAAAAADP///////////wD/////////////kAAAAAAAAABw5P/IPAAAAAAAAAAA////////////AP/////////////wEAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A//////////////+UAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD///////////////9wAAAAAAAAAAAAAFAAAAAAAAAU////////////AP////////////////+IBAAAAAAAAABw3AAAAAAAACj///////////8A///////////////////cdCwEABhcxP+8AAAAAAAATP///////////wD//////////////////////////////5AAAAAAAAB4////////////AP//////////////////////////////UAAAAAAAALj///////////8A//////////////+kgGxUQCAM2P///+AIAAAAAAAQ+P///////////wD//////////////0gAAAAAAAA42P/EKAAAAAAAAHD/////////////AP//////////////sAAAAAAAAAAAAAAAAAAAAAAQ6P////////////8A////////////////TAAAAAAAAAAAAAAAAAAAAKz//////////////wD////////////////oKAAAAAAAAAAAAAAAAASU////////////////AP/////////////////sUAAAAAAAAAAAAAAwxP////////////////8A////////////////////yHA0FAAADCxktP///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'A' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////////////////+QAAAAAAAAAAAAAAOT/////////////////AP//////////////////kAAAAAAAAAAAAAAAkP////////////////8A//////////////////88AAAAAAAAAAAAAAA8/////////////////wD/////////////////5AAAAAAAAAAAAAAAAADk////////////////AP////////////////+QAAAAAAAAAAAAAAAAAJD///////////////8A/////////////////zwAAAAAAAAAAAAAAAAAPP///////////////wD////////////////kAAAAAAAAAAgAAAAAAAAA5P//////////////AP///////////////5AAAAAAAAAAgAAAAAAAAACQ//////////////8A////////////////PAAAAAAAAAz8HAAAAAAAADz//////////////wD//////////////+QAAAAAAAAAWP9kAAAAAAAAANz/////////////AP//////////////kAAAAAAAAACk/7wAAAAAAAAAhP////////////8A//////////////88AAAAAAAABOz//BQAAAAAAAAw/////////////wD/////////////4AAAAAAAAAA8////ZAAAAAAAAADc////////////AP////////////+EAAAAAAAAAIj///+8AAAAAAAAAIT///////////8A/////////////zAAAAAAAAAA2P////wQAAAAAAAAMP///////////wD////////////cAAAAAAAAACT//////1wAAAAAAAAA3P//////////AP///////////4QAAAAAAAAAAAAAAAAAAAAAAAAAAACE//////////8A////////////MAAAAAAAAAAAAAAAAAAAAAAAAAAAADD//////////wD//////////9wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANz/////////AP//////////hAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhP////////8A//////////8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAw/////////wD/////////3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADc////////AP////////+EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIT///////8A/////////zAAAAAAAAAAhP///////////2QAAAAAAAAAMP///////wD////////cAAAAAAAAAADM////////////vAAAAAAAAAAA3P//////AP///////4QAAAAAAAAAHP/////////////4DAAAAAAAAACE//////8A////////MAAAAAAAAABk//////////////9cAAAAAAAAADD//////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'B' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAEDh83P///////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAEhP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAeP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAxP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAABY////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAABT///////////8A//////////8AAAAAAAAAAP/////4zEwAAAAAAAAAAP///////////wD//////////wAAAAAAAAAA////////7AAAAAAAAAAQ////////////AP//////////AAAAAAAAAAD////////sAAAAAAAAAEj///////////8A//////////8AAAAAAAAAAP/////4zEQAAAAAAAAAtP///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAFz/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAiA/P////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAIjPj//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAGKz/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAJT///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAABNz//////////wD//////////wAAAAAAAAAA///////sqCAAAAAAAAAAbP//////////AP//////////AAAAAAAAAAD/////////yAAAAAAAAAAs//////////8A//////////8AAAAAAAAAAP//////////AAAAAAAAAAT//////////wD//////////wAAAAAAAAAA/////////7wAAAAAAAAAAP//////////AP//////////AAAAAAAAAAD//////+ikGAAAAAAAAAAY//////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFT//////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsP//////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAADj///////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAc6P///////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAATOj/////////////AP//////////AAAAAAAAAAAAAAAAAAAEIEBkkNj///////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'C' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////////////5JRULBAAAAgkTIDQ//////////////////8A////////////////1FAAAAAAAAAAAAAAAABAyP///////////////wD//////////////4gEAAAAAAAAAAAAAAAAAAAElP//////////////AP////////////9wAAAAAAAAAAAAAAAAAAAAAAAAlP////////////8A////////////kAAAAAAAAAAAAAAAAAAAAAAAAAAEyP///////////wD//////////9wIAAAAAAAAAAAAAAAAAAAAAAAAAAAw////////////AP//////////WAAAAAAAAAAAWMz/8JwQAAAAAAAAAACw//////////8A/////////+wEAAAAAAAAAID//////9QMAAAAAAAAAET//////////wD/////////nAAAAAAAAAAo/P///////3wAAAAABDBspP//////////AP////////9gAAAAAAAAAIz/////////3BxQjMT0//////////////8A/////////zQAAAAAAAAAzP///////////////////////////////wD/////////GAAAAAAAAADo////////////////////////////////AP////////8AAAAAAAAAAP////////////////////////////////8A/////////wAAAAAAAAAA/////////////////////////////////wD/////////AAAAAAAAAAD/////////////////////////////////AP////////8cAAAAAAAAAOj///////////////////////////////8A/////////zgAAAAAAAAA0P/////////kIGio7P///////////////wD/////////bAAAAAAAAACg/////////5wAAAAAMHS49P//////////AP////////+oAAAAAAAAAEz/////////PAAAAAAAAAAc//////////8A//////////QIAAAAAAAAALz//////6QAAAAAAAAAAGT//////////wD//////////3AAAAAAAAAADIzo/+SEBAAAAAAAAAAAyP//////////AP//////////7BAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A////////////rAAAAAAAAAAAAAAAAAAAAAAAAAAE0P///////////wD/////////////fAAAAAAAAAAAAAAAAAAAAAAAAJz/////////////AP//////////////iAQAAAAAAAAAAAAAAAAAAASY//////////////8A////////////////yEAAAAAAAAAAAAAAAAA8yP///////////////wD//////////////////9yIUCwQAAAAIEB4yP//////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'D' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////////8AAAAAAAAAAAAAAAAADChQkOT/////////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAABGjw//////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAACDY/////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAABjk////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAKj//////////wD///////////8AAAAAAAAAAP///+isSAAAAAAAAAAANP//////////AP///////////wAAAAAAAAAA////////hAAAAAAAAAAA2P////////8A////////////AAAAAAAAAAD/////////MAAAAAAAAACQ/////////wD///////////8AAAAAAAAAAP////////+MAAAAAAAAAFj/////////AP///////////wAAAAAAAAAA/////////8gAAAAAAAAAMP////////8A////////////AAAAAAAAAAD/////////5AAAAAAAAAAY/////////wD///////////8AAAAAAAAAAP//////////AAAAAAAAAAD/////////AP///////////wAAAAAAAAAA//////////8AAAAAAAAAAP////////8A////////////AAAAAAAAAAD//////////wAAAAAAAAAA/////////wD///////////8AAAAAAAAAAP/////////wAAAAAAAAABD/////////AP///////////wAAAAAAAAAA/////////9QAAAAAAAAAJP////////8A////////////AAAAAAAAAAD/////////qAAAAAAAAABI/////////wD///////////8AAAAAAAAAAP////////9QAAAAAAAAAHj/////////AP///////////wAAAAAAAAAA////////uAAAAAAAAAAAvP////////8A////////////AAAAAAAAAAD////w0HwEAAAAAAAAACT8/////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAADz8//////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAY6P///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAKNz/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAACHT0//////////////8A////////////AAAAAAAAAAAAAAAAABg4bKj0/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'E' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAD///////////////////////////////8A//////////8AAAAAAAAAAP///////////////////////////////wD//////////wAAAAAAAAAA////////////////////////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'F' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'G' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////////////////MB8TCgQAAAACCA4YJzs////////////////AP///////////////JQcAAAAAAAAAAAAAAAAAAhw8P////////////8A/////////////9gwAAAAAAAAAAAAAAAAAAAAAAAk2P///////////wD////////////EDAAAAAAAAAAAAAAAAAAAAAAAAAAc7P//////////AP//////////2AwAAAAAAAAAAAAAAAAAAAAAAAAAAABY//////////8A//////////wwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQ/////////wD/////////kAAAAAAAAAAAEHzQ/P/gmCAAAAAAAAAAAFz/////////AP////////wcAAAAAAAAACjg////////8CwAAAAAAAAgWP////////8A////////vAAAAAAAAAAI2P//////////yBRAcJjI8P///////////wD///////94AAAAAAAAAGD/////////////////////////////////AP///////0AAAAAAAAAAsP////////////////////////////////8A////////IAAAAAAAAADc/////////////////////////////////wD///////8AAAAAAAAAAP///////wAAAAAAAAAAAAAAAAD/////////AP///////wAAAAAAAAAA////////AAAAAAAAAAAAAAAAAP////////8A////////AAAAAAAAAAD///////8AAAAAAAAAAAAAAAAA/////////wD///////8gAAAAAAAAAOD//////wAAAAAAAAAAAAAAAAD/////////AP///////0AAAAAAAAAAtP//////AAAAAAAAAAAAAAAAAP////////8A////////cAAAAAAAAABw//////8AAAAAAAAAAAAAAAAA/////////wD///////+8AAAAAAAAABDs////////////AAAAAAAAAAD/////////AP////////wYAAAAAAAAADz0//////////AAAAAAAAAAAP////////8A/////////5AAAAAAAAAAACCY4P//3KhcCAAAAAAAAAAA/////////wD/////////+CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////AP//////////xAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIP////////8A////////////rAQAAAAAAAAAAAAAAAAAAAAAAAAAAGTw/////////wD/////////////vBQAAAAAAAAAAAAAAAAAAAAAADjI////////////AP//////////////8HAQAAAAAAAAAAAAAAAAAEiw//////////////8A//////////////////iwcEAgBAAABCA4aKDk/////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'H' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'I' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAP///////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAA////////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAD///////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'J' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAD//////////////wD///////////////////////////8AAAAAAAAAAP//////////////AP///////////////////////////wAAAAAAAAAA//////////////8A////////////////////////////AAAAAAAAAAj//////////////wD//////////+zMrIxwUDAQ//////wAAAAAAAAAIP//////////////AP//////////DAAAAAAAAADo////2AAAAAAAAAA0//////////////8A//////////8wAAAAAAAAAKj///+YAAAAAAAAAFj//////////////wD//////////2gAAAAAAAAAIND/yBgAAAAAAAAAkP//////////////AP//////////vAAAAAAAAAAAAAAAAAAAAAAAAADc//////////////8A////////////MAAAAAAAAAAAAAAAAAAAAAAAUP///////////////wD////////////EBAAAAAAAAAAAAAAAAAAAABjk////////////////AP////////////+sBAAAAAAAAAAAAAAAAAAY2P////////////////8A///////////////EMAAAAAAAAAAAAAAAVOj//////////////////wD/////////////////vHBAIAAAABg8fNT/////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'K' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////8AAAAAAAAAAP//////////wAQAAAAAAAAAAABw////////AP///////wAAAAAAAAAA/////////9AMAAAAAAAAAAAAcP////////8A////////AAAAAAAAAAD////////cGAAAAAAAAAAAAHD//////////wD///////8AAAAAAAAAAP//////6CgAAAAAAAAAAABs////////////AP///////wAAAAAAAAAA//////Q0AAAAAAAAAAAAVPz///////////8A////////AAAAAAAAAAD////8RAAAAAAAAAAAAFT8/////////////wD///////8AAAAAAAAAAP///1gAAAAAAAAAAABU/P//////////////AP///////wAAAAAAAAAA//9wAAAAAAAAAAAASPz///////////////8A////////AAAAAAAAAAD/jAAAAAAAAAAAADz0/////////////////wD///////8AAAAAAAAAAKQAAAAAAAAAAAA89P//////////////////AP///////wAAAAAAAAAABAAAAAAAAAAAFPT///////////////////8A////////AAAAAAAAAAAAAAAAAAAAAAAApP///////////////////wD///////8AAAAAAAAAAAAAAAAAAAAAAAAU8P//////////////////AP///////wAAAAAAAAAAAAAAAAAAAAAAAABk//////////////////8A////////AAAAAAAAAAAAAAAAAAAAAAAAAADE/////////////////wD///////8AAAAAAAAAAAAAAAAoEAAAAAAAACz8////////////////AP///////wAAAAAAAAAAAAAAGNiAAAAAAAAAAIj///////////////8A////////AAAAAAAAAAAAABjY//gYAAAAAAAACOD//////////////wD///////8AAAAAAAAAAAAY2P///5wAAAAAAAAASP//////////////AP///////wAAAAAAAAAAGNj//////CgAAAAAAAAAqP////////////8A////////AAAAAAAAAADI////////sAAAAAAAAAAc8P///////////wD///////8AAAAAAAAAAP//////////QAAAAAAAAABs////////////AP///////wAAAAAAAAAA///////////IAAAAAAAAAATI//////////8A////////AAAAAAAAAAD///////////9YAAAAAAAAADD8/////////wD///////8AAAAAAAAAAP///////////9wEAAAAAAAAAJD/////////AP///////wAAAAAAAAAA/////////////3AAAAAAAAAADOT///////8A////////AAAAAAAAAAD/////////////7BAAAAAAAAAAUP///////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'L' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAD/////////////////////////////AP////////////8AAAAAAAAAAP////////////////////////////8A/////////////wAAAAAAAAAA/////////////////////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////////AAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'M' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////8AAAAAAAAAAAAAAHz//////3wAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAAAAAATP//////UAAAAAAAAAAAAAAA////////AP//////AAAAAAAAAAAAAAAc//////8cAAAAAAAAAAAAAAD///////8A//////8AAAAAAAAAAAAAAADw////8AAAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAAAAAAALz////AAAAAAAAAAAAAAAAA////////AP//////AAAAAAAAAAAAAAAAkP///5AAAAAAAAAAAAAAAAD///////8A//////8AAAAAAAAAAAAAAABc////ZAAAAAAAAAAAAAAAAP///////wD//////wAAAAAAAAAoAAAAADD///8wAAAAACQAAAAAAAAA////////AP//////AAAAAAAAAFwAAAAABPz//AgAAAAAXAAAAAAAAAD///////8A//////8AAAAAAAAAkAAAAAAA0P/UAAAAAACQAAAAAAAAAP///////wD//////wAAAAAAAADMAAAAAACg/6gAAAAAAMQAAAAAAAAA////////AP//////AAAAAAAAAPgEAAAAAHD/dAAAAAAE+AAAAAAAAAD///////8A//////8AAAAAAAAA/zQAAAAAQP9IAAAAADD/AAAAAAAAAP///////wD//////wAAAAAAAAD/bAAAAAAQ/xQAAAAAaP8AAAAAAAAA////////AP//////AAAAAAAAAP+gAAAAAADQAAAAAACc/wAAAAAAAAD///////8A//////8AAAAAAAAA/9QAAAAAAGgAAAAAAND/AAAAAAAAAP///////wD//////wAAAAAAAAD//wwAAAAAFAAAAAAM/P8AAAAAAAAA////////AP//////AAAAAAAAAP//RAAAAAAAAAAAADz//wAAAAAAAAD///////8A//////8AAAAAAAAA//94AAAAAAAAAAAAcP//AAAAAAAAAP///////wD//////wAAAAAAAAD//7AAAAAAAAAAAACo//8AAAAAAAAA////////AP//////AAAAAAAAAP//5AAAAAAAAAAAANz//wAAAAAAAAD///////8A//////8AAAAAAAAA////HAAAAAAAAAAQ////AAAAAAAAAP///////wD//////wAAAAAAAAD///9QAAAAAAAAAEz///8AAAAAAAAA////////AP//////AAAAAAAAAP///4gAAAAAAAAAfP///wAAAAAAAAD///////8A//////8AAAAAAAAA////vAAAAAAAAACw////AAAAAAAAAP///////wD//////wAAAAAAAAD////wAAAAAAAAAOz///8AAAAAAAAA////////AP//////AAAAAAAAAP////8sAAAAAAAc/////wAAAAAAAAD///////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'N' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////AAAAAAAAALD/////////////AAAAAAAAAP//////////AP////////8AAAAAAAAAFOj///////////8AAAAAAAAA//////////8A/////////wAAAAAAAAAASP///////////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAkP//////////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAI1P////////8AAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAw+P///////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAABw////////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAC8//////8AAAAAAAAA//////////8A/////////wAAAAAAAAAAAAAAABzs/////wAAAAAAAAD//////////wD/////////AAAAAAAAAAAAAAAAAFD/////AAAAAAAAAP//////////AP////////8AAAAAAAAAAAAAAAAAAJz///8AAAAAAAAA//////////8A/////////wAAAAAAAAAUAAAAAAAADNz//wAAAAAAAAD//////////wD/////////AAAAAAAAALQAAAAAAAAANPz/AAAAAAAAAP//////////AP////////8AAAAAAAAA/2wAAAAAAAAAfP8AAAAAAAAA//////////8A/////////wAAAAAAAAD/+CwAAAAAAAAExAAAAAAAAAD//////////wD/////////AAAAAAAAAP//0AQAAAAAAAAgAAAAAAAAAP//////////AP////////8AAAAAAAAA////jAAAAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD/////RAAAAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP/////kFAAAAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAA//////+sAAAAAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD///////9kAAAAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP////////QkAAAAAAAAAAAAAP//////////AP////////8AAAAAAAAA/////////8wEAAAAAAAAAAAA//////////8A/////////wAAAAAAAAD//////////4QAAAAAAAAAAAD//////////wD/////////AAAAAAAAAP///////////DwAAAAAAAAAAP//////////AP////////8AAAAAAAAA////////////4BAAAAAAAAAA//////////8A/////////wAAAAAAAAD/////////////qAAAAAAAAAD//////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'O' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A///////////////////0qGw4HAAAABw4aKT0/////////////////wD////////////////wcAwAAAAAAAAAAAAAAAho6P//////////////AP//////////////uBQAAAAAAAAAAAAAAAAAAAAMoP////////////8A/////////////6AEAAAAAAAAAAAAAAAAAAAAAAAAkP///////////wD///////////+4BAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP//////////8BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAM5P////////8A//////////9wAAAAAAAAAAAsrPD/7KQsAAAAAAAAAABg/////////wD/////////+BAAAAAAAAAAUPj///////hQAAAAAAAAAAjs////////AP////////+sAAAAAAAAABDw//////////AYAAAAAAAAAKD///////8A/////////2wAAAAAAAAAdP///////////3wAAAAAAAAAYP///////wD/////////OAAAAAAAAAC4////////////xAAAAAAAAAAw////////AP////////8cAAAAAAAAAOD////////////oAAAAAAAAABT///////8A/////////wAAAAAAAAAA//////////////8AAAAAAAAAAP///////wD/////////AAAAAAAAAAD//////////////wAAAAAAAAAA////////AP////////8AAAAAAAAAAP/////////////8AAAAAAAAAAD///////8A/////////xwAAAAAAAAA5P///////////+AAAAAAAAAAHP///////wD/////////NAAAAAAAAAC8////////////uAAAAAAAAAA4////////AP////////9oAAAAAAAAAHj///////////98AAAAAAAAAGT///////8A/////////6gAAAAAAAAAGPD/////////+BgAAAAAAAAApP///////wD/////////9AwAAAAAAAAAUPz///////xcAAAAAAAAAAjs////////AP//////////cAAAAAAAAAAALKjs//CwOAAAAAAAAAAAYP////////8A///////////wFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzk/////////wD///////////+4BAAAAAAAAAAAAAAAAAAAAAAAAAAAoP//////////AP////////////+QAAAAAAAAAAAAAAAAAAAAAAAAAJD///////////8A//////////////+sEAAAAAAAAAAAAAAAAAAAAAyg/////////////wD////////////////oZAgAAAAAAAAAAAAAAARg4P//////////////AP//////////////////9KhsOCAAAAAUMFyc7P////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'P' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////////wAAAAAAAAAAAAAAAAAACCxguP////////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAOOD//////////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAGOD/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAARP////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAxP///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAABo////////////AP///////////wAAAAAAAAAA////6JwMAAAAAAAAADD///////////8A////////////AAAAAAAAAAD//////6AAAAAAAAAADP///////////wD///////////8AAAAAAAAAAP//////9AAAAAAAAAAA////////////AP///////////wAAAAAAAAAA///////0AAAAAAAAAAD///////////8A////////////AAAAAAAAAAD//////5gAAAAAAAAAHP///////////wD///////////8AAAAAAAAAAP///9iICAAAAAAAAABI////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAJD///////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAI6P///////////wD///////////8AAAAAAAAAAAAAAAAAAAAAAAAAAIT/////////////AP///////////wAAAAAAAAAAAAAAAAAAAAAAAABU/P////////////8A////////////AAAAAAAAAAAAAAAAAAAAAAAIhPz//////////////wD///////////8AAAAAAAAAAAAAAAAABCRMkOz/////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP///////////wAAAAAAAAAA//////////////////////////////8A////////////AAAAAAAAAAD//////////////////////////////wD///////////8AAAAAAAAAAP//////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'Q' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////SoaDQcAAAAHDhoqPT///////////////////8A//////////////BwDAAAAAAAAAAAAAAACHDo/////////////////wD///////////+4FAAAAAAAAAAAAAAAAAAAABCo////////////////AP//////////nAQAAAAAAAAAAAAAAAAAAAAAAACQ//////////////8A/////////7gEAAAAAAAAAAAAAAAAAAAAAAAAAACg/////////////wD////////wFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzo////////////AP///////3AAAAAAAAAAACyo8P/sqCwAAAAAAAAAAGT///////////8A///////4EAAAAAAAAABM+P///////FQAAAAAAAAACPT//////////wD//////7AAAAAAAAAAFPD/////////9BgAAAAAAAAApP//////////AP//////bAAAAAAAAAB4////////////fAAAAAAAAABk//////////8A//////84AAAAAAAAALz///////////+8AAAAAAAAADT//////////wD//////xwAAAAAAAAA6P///////////+QAAAAAAAAAHP//////////AP//////AAAAAAAAAAD//////////////wAAAAAAAAAA//////////8A//////8AAAAAAAAAAP//////////////AAAAAAAAAAD//////////wD//////wAAAAAAAAAA/P////////////8AAAAAAAAAAP//////////AP//////GAAAAAAAAADg////////////4AAAAAAAAAAc//////////8A//////84AAAAAAAAALT////MJHTo//+8AAAAAAAAADT//////////wD//////2wAAAAAAAAAdP///2AAABCg/3wAAAAAAAAAZP//////////AP//////rAAAAAAAAAAY9P/sCAAAAABMGAAAAAAAAACk//////////8A///////4EAAAAAAAAABU/P+0OAAAAAAAAAAAAAAACPT//////////wD///////94AAAAAAAAAAA4sPD/gAAAAAAAAAAAAABk////////////AP////////AcAAAAAAAAAAAAAAAAAAAAAAAAAAAADOT///////////8A/////////7wEAAAAAAAAAAAAAAAAAAAAAAAAAACQ/////////////wD//////////6wEAAAAAAAAAAAAAAAAAAAAAAAAABSs////////////AP///////////7gUAAAAAAAAAAAAAAAAAAAAAAAAAABAwP////////8A//////////////BwDAAAAAAAAAAAAAAABAgAAAAAAAA8/////////wD////////////////0qGg0GAAAABgwXJjkxBgAAAAAALD/////////AP//////////////////////////////////5DQAAAAk/P////////8A////////////////////////////////////+GwAAJD//////////wD//////////////////////////////////////8A49P//////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'R' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////wAAAAAAAAAAAAAAAAAAAAQgOGSk+P///////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAcuP//////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAEsP////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ6P///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADD///////////8A/////////wAAAAAAAAAA///////svDgAAAAAAAAACP///////////wD/////////AAAAAAAAAAD/////////7AAAAAAAAAAA////////////AP////////8AAAAAAAAAAP/////////cAAAAAAAAABD///////////8A/////////wAAAAAAAAAA//////DQoCQAAAAAAAAAQP///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAACU////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAIPj///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAzU/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAA02P//////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAxctPz///////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAEDY/////////////////wD/////////AAAAAAAAAAD/9LAsAAAAAAAAAAzc////////////////AP////////8AAAAAAAAAAP///+wkAAAAAAAAADD8//////////////8A/////////wAAAAAAAAAA/////8QAAAAAAAAAAJD//////////////wD/////////AAAAAAAAAAD//////1QAAAAAAAAAFPD/////////////AP////////8AAAAAAAAAAP//////3AQAAAAAAAAAgP////////////8A/////////wAAAAAAAAAA////////aAAAAAAAAAAM6P///////////wD/////////AAAAAAAAAAD////////oCAAAAAAAAABs////////////AP////////8AAAAAAAAAAP////////+AAAAAAAAAAATc//////////8A/////////wAAAAAAAAAA//////////AUAAAAAAAAAFj//////////wD/////////AAAAAAAAAAD//////////5AAAAAAAAAAAND/////////AP////////8AAAAAAAAAAP//////////+CQAAAAAAAAAQP////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'S' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP/////////////////8vHBEIAgAAAQgQHC8/P////////////////8A////////////////pCQAAAAAAAAAAAAAAAAcoP///////////////wD//////////////FwAAAAAAAAAAAAAAAAAAAAAXP//////////////AP////////////9oAAAAAAAAAAAAAAAAAAAAAAAAhP////////////8A////////////zAAAAAAAAAAAAAAAAAAAAAAAAAAI6P///////////wD///////////9cAAAAAAAAAAAAAAAAAAAAAAAAAACA////////////AP///////////xgAAAAAAAAAUOD/8KwkAAAAAAAAADj///////////8A////////////AAAAAAAAAAD0/////8wABCAgICxASP///////////wD///////////8MAAAAAAAAAMz/////////////////////////////AP///////////0AAAAAAAAAACFiQxPT///////////////////////8A////////////oAAAAAAAAAAAAAAAADBwtPT//////////////////wD////////////8QAAAAAAAAAAAAAAAAAAACFTA////////////////AP/////////////oOAAAAAAAAAAAAAAAAAAAAABM6P////////////8A///////////////4fAgAAAAAAAAAAAAAAAAAAAAY2P///////////wD/////////////////7IwwAAAAAAAAAAAAAAAAAAAo+P//////////AP/////////////////////koGw0BAAAAAAAAAAAAACU//////////8A///////////////////////////4uFgAAAAAAAAAADz//////////wD//////////2BgSEA0IBwA6P///////5QAAAAAAAAADP//////////AP//////////JAAAAAAAAACc/////////AAAAAAAAAAA//////////8A//////////9YAAAAAAAAACDo///////AAAAAAAAAABT//////////wD//////////6QAAAAAAAAAACCk7P/snBQAAAAAAAAAUP//////////AP//////////+BAAAAAAAAAAAAAAAAAAAAAAAAAAAACs//////////8A////////////kAAAAAAAAAAAAAAAAAAAAAAAAAAAOP///////////wD////////////8RAAAAAAAAAAAAAAAAAAAAAAAABjc////////////AP/////////////0PAAAAAAAAAAAAAAAAAAAAAAg2P////////////8A///////////////8hBQAAAAAAAAAAAAAAAAMdPT//////////////wD/////////////////+LRwSCAMAAAAHDhoqPT/////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'T' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD///////////////////8AAAAAAAAAAP//////////////////////AP///////////////////wAAAAAAAAAA//////////////////////8A////////////////////AAAAAAAAAAD//////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'U' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////AAAAAAAAAAD///////////8AAAAAAAAAAP//////////AP////////8AAAAAAAAAAP///////////wAAAAAAAAAA//////////8A/////////wAAAAAAAAAA////////////AAAAAAAAAAD//////////wD/////////JAAAAAAAAADk/////////+gAAAAAAAAAHP//////////AP////////9MAAAAAAAAAJz/////////nAAAAAAAAABE//////////8A/////////4gAAAAAAAAAHOj//////+ggAAAAAAAAAHz//////////wD/////////0AAAAAAAAAAAIJzs/+ykIAAAAAAAAAAA0P//////////AP//////////QAAAAAAAAAAAAAAAAAAAAAAAAAAAAED///////////8A///////////IBAAAAAAAAAAAAAAAAAAAAAAAAAAE0P///////////wD///////////+YAAAAAAAAAAAAAAAAAAAAAAAAAJj/////////////AP////////////+UBAAAAAAAAAAAAAAAAAAAAASU//////////////8A///////////////IPAAAAAAAAAAAAAAAAAAwyP///////////////wD/////////////////0IxYOCAIAAAEIEiAyP//////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'V' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD//////zAAAAAAAAAAYP//////////////ZAAAAAAAAAAw////////AP//////kAAAAAAAAAAU/P////////////8UAAAAAAAAAJD///////8A///////oBAAAAAAAAADE////////////xAAAAAAAAAAE7P///////wD///////9MAAAAAAAAAHD///////////94AAAAAAAAAEz/////////AP///////6gAAAAAAAAAJP///////////yQAAAAAAAAArP////////8A////////+BAAAAAAAAAA1P/////////YAAAAAAAAABT4/////////wD/////////aAAAAAAAAACE/////////4QAAAAAAAAAbP//////////AP/////////EAAAAAAAAADT/////////OAAAAAAAAADM//////////8A//////////8kAAAAAAAAAOT//////+QAAAAAAAAAKP///////////wD//////////4QAAAAAAAAAmP//////nAAAAAAAAACI////////////AP//////////5AAAAAAAAABE//////9EAAAAAAAABOT///////////8A////////////QAAAAAAAAAT0////9AgAAAAAAABI/////////////wD///////////+gAAAAAAAAAKT///+kAAAAAAAAAKj/////////////AP////////////QIAAAAAAAAXP///1wAAAAAAAAM+P////////////8A/////////////1wAAAAAAAAM+P/8DAAAAAAAAGT//////////////wD/////////////vAAAAAAAAAC8/7wAAAAAAAAAxP//////////////AP//////////////HAAAAAAAAGj/aAAAAAAAACT///////////////8A//////////////94AAAAAAAAHP8cAAAAAAAAhP///////////////wD//////////////9gAAAAAAAAAkAAAAAAAAADk////////////////AP///////////////zgAAAAAAAAQAAAAAAAAQP////////////////8A////////////////lAAAAAAAAAAAAAAAAACg/////////////////wD////////////////sCAAAAAAAAAAAAAAADPT/////////////////AP////////////////9QAAAAAAAAAAAAAABg//////////////////8A/////////////////7AAAAAAAAAAAAAAAMD//////////////////wD//////////////////BQAAAAAAAAAAAAc////////////////////AP//////////////////cAAAAAAAAAAAAHz///////////////////8A///////////////////MAAAAAAAAAAAA3P///////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'W' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//8cAAAAAAAAALz/////4AAAAAAAAAAA6P////+8AAAAAAAAABz//wD//1QAAAAAAAAAjP////+gAAAAAAAAAACo/////4wAAAAAAAAAUP//AP//jAAAAAAAAABU/////2AAAAAAAAAAAGj/////VAAAAAAAAACM//8A///EAAAAAAAAACT/////IAAAAAAAAAAAKP////8kAAAAAAAAAMT//wD///gEAAAAAAAAAPD//+AAAAAAAAAAAAAA6P//8AAAAAAAAAAE9P//AP///zAAAAAAAAAAvP//oAAAAAAAAAAAAACo//+8AAAAAAAAADD///8A////bAAAAAAAAACM//9gAAAAAAAAAAAAAGT//4wAAAAAAAAAaP///wD///+kAAAAAAAAAFT//yAAAAAAAAAAAAAAIP//VAAAAAAAAACc////AP///9gAAAAAAAAAJP/gAAAAAAAAAAAAAAAA4P8kAAAAAAAAANT///8A/////xAAAAAAAAAA8KAAAAAAAAAAAAAAAACg8AAAAAAAAAAQ/////wD/////TAAAAAAAAAC8YAAAAAAAAAAAAAAAAGC8AAAAAAAAAET/////AP////+AAAAAAAAAAIwgAAAAAAAAAAAAAAAAIIwAAAAAAAAAfP////8A/////7gAAAAAAAAANAAAAAAAACwwAAAAAAAANAAAAAAAAACw/////wD/////8AAAAAAAAAAAAAAAAAAAdHgAAAAAAAAAAAAAAAAAAOz/////AP//////KAAAAAAAAAAAAAAAAAC4vAAAAAAAAAAAAAAAAAAg//////8A//////9gAAAAAAAAAAAAAAAACPj4CAAAAAAAAAAAAAAAAFj//////wD//////5QAAAAAAAAAAAAAAABE//9IAAAAAAAAAAAAAAAAkP//////AP//////0AAAAAAAAAAAAAAAAIj//4wAAAAAAAAAAAAAAADI//////8A///////8DAAAAAAAAAAAAAAAzP//1AAAAAAAAAAAAAAABPj//////wD///////88AAAAAAAAAAAAABT/////GAAAAAAAAAAAAAA0////////AP///////3QAAAAAAAAAAAAAWP////9gAAAAAAAAAAAAAHD///////8A////////sAAAAAAAAAAAAACg/////6QAAAAAAAAAAAAApP///////wD////////kAAAAAAAAAAAAAOT/////6AAAAAAAAAAAAADc////////AP////////8cAAAAAAAAAAAo////////MAAAAAAAAAAAEP////////8A/////////1QAAAAAAAAAAHD///////94AAAAAAAAAABM/////////wD/////////jAAAAAAAAAAAtP///////7wAAAAAAAAAAID/////////AP/////////EAAAAAAAAAAT0////////+AgAAAAAAAAAuP////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'X' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD///////9UAAAAAAAAAKz///////////+sAAAAAAAAAFD/////////AP///////+QQAAAAAAAAFOT/////////8BwAAAAAAAAM5P////////8A/////////5gAAAAAAAAATP////////9kAAAAAAAAAJD//////////wD//////////0AAAAAAAAAAoP//////wAAAAAAAAAA0/P//////////AP//////////2AgAAAAAAAAQ4P////gkAAAAAAAABMz///////////8A////////////iAAAAAAAAABA////dAAAAAAAAABw/////////////wD////////////8MAAAAAAAAACU/9AEAAAAAAAAHPD/////////////AP/////////////IBAAAAAAAAAzYMAAAAAAAAACs//////////////8A//////////////90AAAAAAAAABAAAAAAAAAATP///////////////wD///////////////QgAAAAAAAAAAAAAAAAAAzg////////////////AP///////////////7wAAAAAAAAAAAAAAAAAjP////////////////8A/////////////////2AAAAAAAAAAAAAAADD8/////////////////wD/////////////////7BQAAAAAAAAAAAAEyP//////////////////AP/////////////////gDAAAAAAAAAAAAAjY//////////////////8A/////////////////0AAAAAAAAAAAAAAADj8/////////////////wD///////////////+UAAAAAAAAAAAAAAAAAJD/////////////////AP//////////////4AwAAAAAAAAAAAAAAAAADOD///////////////8A//////////////9AAAAAAAAAAAAAAAAAAAAAQP///////////////wD/////////////nAAAAAAAAAAAWAAAAAAAAAAAlP//////////////AP///////////+QQAAAAAAAAAGD/YAAAAAAAAAAM4P////////////8A////////////TAAAAAAAAAAs9P/0LAAAAAAAAABM/////////////wD//////////6AAAAAAAAAADNT////UDAAAAAAAAACg////////////AP/////////kEAAAAAAAAACg//////+gAAAAAAAAABDk//////////8A/////////0wAAAAAAAAAYP////////9gAAAAAAAAAEz//////////wD///////+oAAAAAAAAACz0//////////QsAAAAAAAAAKT/////////AP//////7BQAAAAAAAAM1P///////////9QMAAAAAAAAFOz///////8A//////9UAAAAAAAAAKD//////////////6AAAAAAAAAAVP///////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'Y' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP///////1QAAAAAAAAAAGj//////////2gAAAAAAAAAAFT///////8A////////5BAAAAAAAAAAAMT////////EAAAAAAAAAAAQ5P///////wD/////////mAAAAAAAAAAAKPj/////+CgAAAAAAAAAAJj/////////AP//////////PAAAAAAAAAAAgP////+AAAAAAAAAAAA8//////////8A///////////YCAAAAAAAAAAE2P//2AQAAAAAAAAACNj//////////wD///////////+AAAAAAAAAAAA4//84AAAAAAAAAACA////////////AP////////////woAAAAAAAAAACUlAAAAAAAAAAAKPz///////////8A/////////////8gAAAAAAAAAABAQAAAAAAAAAADI/////////////wD//////////////2wAAAAAAAAAAAAAAAAAAAAAbP//////////////AP//////////////8BwAAAAAAAAAAAAAAAAAABzw//////////////8A////////////////tAAAAAAAAAAAAAAAAAAAtP///////////////wD/////////////////VAAAAAAAAAAAAAAAAFT/////////////////AP/////////////////oEAAAAAAAAAAAAAAQ6P////////////////8A//////////////////+cAAAAAAAAAAAAAJz//////////////////wD///////////////////9AAAAAAAAAAABA////////////////////AP///////////////////9gAAAAAAAAAANj///////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////8AAAAAAAAAAP////////////////////8A/////////////////////wAAAAAAAAAA/////////////////////wD/////////////////////AAAAAAAAAAD/////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + 'Z' => array( + 'data' => 'AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////////8A//////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAD//////////////wD//////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////////AP//////////AAAAAAAAAAAAAAAAAAAAAAAAAAAQ//////////////8A/////////////////////////1AAAAAAAAAABLz//////////////wD///////////////////////98AAAAAAAAAACY////////////////AP//////////////////////pAAAAAAAAAAAaP////////////////8A/////////////////////8QIAAAAAAAAAET8/////////////////wD////////////////////gGAAAAAAAAAAo9P//////////////////AP//////////////////9CwAAAAAAAAAFNz///////////////////8A//////////////////xMAAAAAAAAAATA/////////////////////wD/////////////////eAAAAAAAAAAAnP//////////////////////AP///////////////5wAAAAAAAAAAHT///////////////////////8A///////////////ABAAAAAAAAABM/P///////////////////////wD/////////////3BQAAAAAAAAALPT/////////////////////////AP////////////QoAAAAAAAAABjg//////////////////////////8A///////////8SAAAAAAAAAAExP///////////////////////////wD//////////2wAAAAAAAAAAKD/////////////////////////////AP////////+YAAAAAAAAAAB8//////////////////////////////8A/////////wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////wD/////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////////////AP////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8A/////////////////////////////////////////////////////wD/////////////////////////////////////////////////////AP////////////////////////////////////////////////////8=', + 'width' => 40 + ), + ); + } +} diff --git a/phpbb/captcha/plugins/captcha_abstract.php b/phpbb/captcha/plugins/captcha_abstract.php new file mode 100644 index 0000000..b508767 --- /dev/null +++ b/phpbb/captcha/plugins/captcha_abstract.php @@ -0,0 +1,390 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha\plugins; + +/** +* This class holds the code shared by the two default 3.0.x CAPTCHAs. +*/ +abstract class captcha_abstract +{ + var $confirm_id; + var $confirm_code; + var $code; + var $seed; + var $attempts = 0; + var $type; + var $solved = 0; + var $captcha_vars = false; + + /** + * @var string name of the service. + */ + protected $service_name; + + function init($type) + { + global $config, $request; + + // read input + $this->confirm_id = $request->variable('confirm_id', ''); + $this->confirm_code = $request->variable('confirm_code', ''); + $refresh = $request->variable('refresh_vc', false) && $config['confirm_refresh']; + + $this->type = (int) $type; + + if (!strlen($this->confirm_id) || !$this->load_code()) + { + // we have no confirm ID, better get ready to display something + $this->generate_code(); + } + else if ($refresh) + { + $this->regenerate_code(); + } + } + + function execute_demo() + { + $this->code = gen_rand_string_friendly(mt_rand(CAPTCHA_MIN_CHARS, CAPTCHA_MAX_CHARS)); + $this->seed = hexdec(substr(unique_id(), 4, 10)); + + // compute $seed % 0x7fffffff + $this->seed -= 0x7fffffff * floor($this->seed / 0x7fffffff); + + $generator = $this->get_generator_class(); + $captcha = new $generator(); + define('IMAGE_OUTPUT', 1); + $captcha->execute($this->code, $this->seed); + } + + function execute() + { + if (empty($this->code)) + { + if (!$this->load_code()) + { + // invalid request, bail out + return false; + } + } + $generator = $this->get_generator_class(); + $captcha = new $generator(); + define('IMAGE_OUTPUT', 1); + $captcha->execute($this->code, $this->seed); + } + + function get_template() + { + global $config, $user, $template, $phpEx, $phpbb_root_path; + + if ($this->is_solved()) + { + return false; + } + else + { + $link = append_sid($phpbb_root_path . 'ucp.' . $phpEx, 'mode=confirm&confirm_id=' . $this->confirm_id . '&type=' . $this->type); + $contact_link = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx); + $explain = $user->lang(($this->type != CONFIRM_POST) ? 'CONFIRM_EXPLAIN' : 'POST_CONFIRM_EXPLAIN', '', ''); + + $template->assign_vars(array( + 'CONFIRM_IMAGE_LINK' => $link, + 'CONFIRM_IMAGE' => '', + 'CONFIRM_IMG' => '', + 'CONFIRM_ID' => $this->confirm_id, + 'S_CONFIRM_CODE' => true, + 'S_TYPE' => $this->type, + 'S_CONFIRM_REFRESH' => ($config['enable_confirm'] && $config['confirm_refresh'] && $this->type == CONFIRM_REG) ? true : false, + 'L_CONFIRM_EXPLAIN' => $explain, + )); + + return 'captcha_default.html'; + } + } + + function get_demo_template($id) + { + global $config, $template, $request, $phpbb_admin_path, $phpEx; + + $variables = ''; + + if (is_array($this->captcha_vars)) + { + foreach ($this->captcha_vars as $captcha_var => $template_var) + { + $variables .= '&' . rawurlencode($captcha_var) . '=' . $request->variable($captcha_var, (int) $config[$captcha_var]); + } + } + + // acp_captcha has a delivery function; let's use it + $template->assign_vars(array( + 'CONFIRM_IMAGE' => append_sid($phpbb_admin_path . 'index.' . $phpEx, 'captcha_demo=1&mode=visual&i=' . $id . '&select_captcha=' . $this->get_service_name()) . $variables, + 'CONFIRM_ID' => $this->confirm_id, + )); + + return 'captcha_default_acp_demo.html'; + } + + function get_hidden_fields() + { + $hidden_fields = array(); + + // this is required for posting.php - otherwise we would forget about the captcha being already solved + if ($this->solved) + { + $hidden_fields['confirm_code'] = $this->confirm_code; + } + $hidden_fields['confirm_id'] = $this->confirm_id; + return $hidden_fields; + } + + function garbage_collect($type) + { + global $db; + + $sql = 'SELECT DISTINCT c.session_id + FROM ' . CONFIRM_TABLE . ' c + LEFT JOIN ' . SESSIONS_TABLE . ' s ON (c.session_id = s.session_id) + WHERE s.session_id IS NULL' . + ((empty($type)) ? '' : ' AND c.confirm_type = ' . (int) $type); + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $sql_in = array(); + do + { + $sql_in[] = (string) $row['session_id']; + } + while ($row = $db->sql_fetchrow($result)); + + if (count($sql_in)) + { + $sql = 'DELETE FROM ' . CONFIRM_TABLE . ' + WHERE ' . $db->sql_in_set('session_id', $sql_in); + $db->sql_query($sql); + } + } + $db->sql_freeresult($result); + } + + function uninstall() + { + $this->garbage_collect(0); + } + + function install() + { + return; + } + + function validate() + { + global $user; + + if (!$user->is_setup()) + { + $user->setup(); + } + + $error = ''; + if (!$this->confirm_id) + { + $error = $user->lang['CONFIRM_CODE_WRONG']; + } + else + { + if ($this->check_code()) + { + $this->solved = true; + } + else + { + $error = $user->lang['CONFIRM_CODE_WRONG']; + } + } + + if (strlen($error)) + { + // okay, incorrect answer. Let's ask a new question. + $this->new_attempt(); + return $error; + } + else + { + return false; + } + } + + /** + * The old way to generate code, suitable for GD and non-GD. Resets the internal state. + */ + function generate_code() + { + global $db, $user; + + $this->code = gen_rand_string_friendly(mt_rand(CAPTCHA_MIN_CHARS, CAPTCHA_MAX_CHARS)); + $this->confirm_id = md5(unique_id($user->ip)); + $this->seed = hexdec(substr(unique_id(), 4, 10)); + $this->solved = 0; + // compute $seed % 0x7fffffff + $this->seed -= 0x7fffffff * floor($this->seed / 0x7fffffff); + + $sql = 'INSERT INTO ' . CONFIRM_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'confirm_id' => (string) $this->confirm_id, + 'session_id' => (string) $user->session_id, + 'confirm_type' => (int) $this->type, + 'code' => (string) $this->code, + 'seed' => (int) $this->seed) + ); + $db->sql_query($sql); + } + + /** + * New Question, if desired. + */ + function regenerate_code() + { + global $db, $user; + + $this->code = gen_rand_string_friendly(mt_rand(CAPTCHA_MIN_CHARS, CAPTCHA_MAX_CHARS)); + $this->seed = hexdec(substr(unique_id(), 4, 10)); + $this->solved = 0; + // compute $seed % 0x7fffffff + $this->seed -= 0x7fffffff * floor($this->seed / 0x7fffffff); + + $sql = 'UPDATE ' . CONFIRM_TABLE . ' SET ' . $db->sql_build_array('UPDATE', array( + 'code' => (string) $this->code, + 'seed' => (int) $this->seed)) . ' + WHERE + confirm_id = \'' . $db->sql_escape($this->confirm_id) . '\' + AND session_id = \'' . $db->sql_escape($user->session_id) . '\''; + $db->sql_query($sql); + } + + /** + * New Question, if desired. + */ + function new_attempt() + { + global $db, $user; + + $this->code = gen_rand_string_friendly(mt_rand(CAPTCHA_MIN_CHARS, CAPTCHA_MAX_CHARS)); + $this->seed = hexdec(substr(unique_id(), 4, 10)); + $this->solved = 0; + // compute $seed % 0x7fffffff + $this->seed -= 0x7fffffff * floor($this->seed / 0x7fffffff); + + $sql = 'UPDATE ' . CONFIRM_TABLE . ' SET ' . $db->sql_build_array('UPDATE', array( + 'code' => (string) $this->code, + 'seed' => (int) $this->seed)) . ' + , attempts = attempts + 1 + WHERE + confirm_id = \'' . $db->sql_escape($this->confirm_id) . '\' + AND session_id = \'' . $db->sql_escape($user->session_id) . '\''; + $db->sql_query($sql); + } + + /** + * Look up everything we need for painting&checking. + */ + function load_code() + { + global $db, $user; + + $sql = 'SELECT code, seed, attempts + FROM ' . CONFIRM_TABLE . " + WHERE confirm_id = '" . $db->sql_escape($this->confirm_id) . "' + AND session_id = '" . $db->sql_escape($user->session_id) . "' + AND confirm_type = " . $this->type; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $this->code = $row['code']; + $this->seed = $row['seed']; + $this->attempts = $row['attempts']; + return true; + } + + return false; + } + + function check_code() + { + return (strcasecmp($this->code, $this->confirm_code) === 0); + } + + function get_attempt_count() + { + return $this->attempts; + } + + function reset() + { + global $db, $user; + + $sql = 'DELETE FROM ' . CONFIRM_TABLE . " + WHERE session_id = '" . $db->sql_escape($user->session_id) . "' + AND confirm_type = " . (int) $this->type; + $db->sql_query($sql); + + // we leave the class usable by generating a new question + $this->generate_code(); + } + + function is_solved() + { + global $request; + + if ($request->variable('confirm_code', false) && $this->solved === 0) + { + $this->validate(); + } + return (bool) $this->solved; + } + + /** + * API function + */ + function has_config() + { + return false; + } + + /** + * @return string the name of the service corresponding to the plugin + */ + function get_service_name() + { + return $this->service_name; + } + + /** + * Set the name of the plugin + * + * @param string $name + */ + public function set_name($name) + { + $this->service_name = $name; + } + + /** + * @return string the name of the class used to generate the captcha + */ + abstract function get_generator_class(); +} diff --git a/phpbb/captcha/plugins/gd.php b/phpbb/captcha/plugins/gd.php new file mode 100644 index 0000000..6d3c9bb --- /dev/null +++ b/phpbb/captcha/plugins/gd.php @@ -0,0 +1,123 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha\plugins; + +class gd extends captcha_abstract +{ + var $captcha_vars = array( + 'captcha_gd_x_grid' => 'CAPTCHA_GD_X_GRID', + 'captcha_gd_y_grid' => 'CAPTCHA_GD_Y_GRID', + 'captcha_gd_foreground_noise' => 'CAPTCHA_GD_FOREGROUND_NOISE', +// 'captcha_gd' => 'CAPTCHA_GD_PREVIEWED', + 'captcha_gd_wave' => 'CAPTCHA_GD_WAVE', + 'captcha_gd_3d_noise' => 'CAPTCHA_GD_3D_NOISE', + 'captcha_gd_fonts' => 'CAPTCHA_GD_FONTS', + ); + + public function is_available() + { + return @extension_loaded('gd'); + } + + /** + * @return string the name of the class used to generate the captcha + */ + function get_generator_class() + { + return '\\phpbb\\captcha\\gd'; + } + + /** + * API function + */ + function has_config() + { + return true; + } + + public function get_name() + { + return 'CAPTCHA_GD'; + } + + function acp_page($id, $module) + { + global $user, $template, $phpbb_log, $request; + global $config; + + $user->add_lang('acp/board'); + + $module->tpl_name = 'captcha_gd_acp'; + $module->page_title = 'ACP_VC_SETTINGS'; + $form_key = 'acp_captcha'; + add_form_key($form_key); + + $submit = $request->variable('submit', ''); + + if ($submit && check_form_key($form_key)) + { + $captcha_vars = array_keys($this->captcha_vars); + foreach ($captcha_vars as $captcha_var) + { + $value = $request->variable($captcha_var, 0); + if ($value >= 0) + { + $config->set($captcha_var, $value); + } + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($module->u_action)); + } + else if ($submit) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($module->u_action)); + } + else + { + foreach ($this->captcha_vars as $captcha_var => $template_var) + { + $var = (isset($_REQUEST[$captcha_var])) ? $request->variable($captcha_var, 0) : $config[$captcha_var]; + $template->assign_var($template_var, $var); + } + + $template->assign_vars(array( + 'CAPTCHA_PREVIEW' => $this->get_demo_template($id), + 'CAPTCHA_NAME' => $this->get_service_name(), + 'U_ACTION' => $module->u_action, + )); + } + } + + function execute_demo() + { + global $config, $request; + + $config_old = $config; + + $config = new \phpbb\config\config(array()); + foreach ($config_old as $key => $value) + { + $config->set($key, $value); + } + + foreach ($this->captcha_vars as $captcha_var => $template_var) + { + $config->set($captcha_var, $request->variable($captcha_var, (int) $config[$captcha_var])); + } + parent::execute_demo(); + $config = $config_old; + } + +} diff --git a/phpbb/captcha/plugins/gd_wave.php b/phpbb/captcha/plugins/gd_wave.php new file mode 100644 index 0000000..4ac26ed --- /dev/null +++ b/phpbb/captcha/plugins/gd_wave.php @@ -0,0 +1,42 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha\plugins; + +class gd_wave extends captcha_abstract +{ + public function is_available() + { + return @extension_loaded('gd'); + } + + public function get_name() + { + return 'CAPTCHA_GD_3D'; + } + + /** + * @return string the name of the class used to generate the captcha + */ + function get_generator_class() + { + return '\\phpbb\\captcha\\gd_wave'; + } + + function acp_page($id, $module) + { + global $user; + + trigger_error($user->lang['CAPTCHA_NO_OPTIONS'] . adm_back_link($module->u_action)); + } +} diff --git a/phpbb/captcha/plugins/nogd.php b/phpbb/captcha/plugins/nogd.php new file mode 100644 index 0000000..da67cd2 --- /dev/null +++ b/phpbb/captcha/plugins/nogd.php @@ -0,0 +1,42 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha\plugins; + +class nogd extends captcha_abstract +{ + public function is_available() + { + return true; + } + + public function get_name() + { + return 'CAPTCHA_NO_GD'; + } + + /** + * @return string the name of the class used to generate the captcha + */ + function get_generator_class() + { + return '\\phpbb\\captcha\\non_gd'; + } + + function acp_page($id, $module) + { + global $user; + + trigger_error($user->lang['CAPTCHA_NO_OPTIONS'] . adm_back_link($module->u_action)); + } +} diff --git a/phpbb/captcha/plugins/qa.php b/phpbb/captcha/plugins/qa.php new file mode 100644 index 0000000..70b3f72 --- /dev/null +++ b/phpbb/captcha/plugins/qa.php @@ -0,0 +1,1040 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha\plugins; + +/** +* And now to something completely different. Let's make a captcha without extending the abstract class. +* QA CAPTCHA sample implementation +*/ +class qa +{ + var $confirm_id; + var $answer; + var $question_ids; + var $question_text; + var $question_lang; + var $question_strict; + var $attempts = 0; + var $type; + // dirty trick: 0 is false, but can still encode that the captcha is not yet validated + var $solved = 0; + + protected $table_captcha_questions; + protected $table_captcha_answers; + protected $table_qa_confirm; + + /** + * @var string name of the service. + */ + protected $service_name; + + /** + * Constructor + * + * @param string $table_captcha_questions + * @param string $table_captcha_answers + * @param string $table_qa_confirm + */ + function __construct($table_captcha_questions, $table_captcha_answers, $table_qa_confirm) + { + $this->table_captcha_questions = $table_captcha_questions; + $this->table_captcha_answers = $table_captcha_answers; + $this->table_qa_confirm = $table_qa_confirm; + } + + /** + * @param int $type as per the CAPTCHA API docs, the type + */ + function init($type) + { + global $config, $db, $user, $request; + + // load our language file + $user->add_lang('captcha_qa'); + + // read input + $this->confirm_id = $request->variable('qa_confirm_id', ''); + $this->answer = $request->variable('qa_answer', '', true); + + $this->type = (int) $type; + $this->question_lang = $user->lang_name; + + // we need all defined questions - shouldn't be too many, so we can just grab them + // try the user's lang first + $sql = 'SELECT question_id + FROM ' . $this->table_captcha_questions . " + WHERE lang_iso = '" . $db->sql_escape($user->lang_name) . "'"; + $result = $db->sql_query($sql, 3600); + + while ($row = $db->sql_fetchrow($result)) + { + $this->question_ids[$row['question_id']] = $row['question_id']; + } + $db->sql_freeresult($result); + + // fallback to the board default lang + if (!count($this->question_ids)) + { + $this->question_lang = $config['default_lang']; + + $sql = 'SELECT question_id + FROM ' . $this->table_captcha_questions . " + WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; + $result = $db->sql_query($sql, 7200); + + while ($row = $db->sql_fetchrow($result)) + { + $this->question_ids[$row['question_id']] = $row['question_id']; + } + $db->sql_freeresult($result); + } + + // final fallback to any language + if (!count($this->question_ids)) + { + $this->question_lang = ''; + + $sql = 'SELECT q.question_id, q.lang_iso + FROM ' . $this->table_captcha_questions . ' q, ' . $this->table_captcha_answers . ' a + WHERE q.question_id = a.question_id'; + $result = $db->sql_query($sql, 7200); + + while ($row = $db->sql_fetchrow($result)) + { + if (empty($this->question_lang)) + { + $this->question_lang = $row['lang_iso']; + } + $this->question_ids[$row['question_id']] = $row['question_id']; + } + $db->sql_freeresult($result); + } + + // okay, if there is a confirm_id, we try to load that confirm's state. If not, we try to find one + if (!$this->load_answer() && (!$this->load_confirm_id() || !$this->load_answer())) + { + // we have no valid confirm ID, better get ready to ask something + $this->select_question(); + } + } + + /** + * See if the captcha has created its tables. + */ + public function is_installed() + { + global $phpbb_container; + + $db_tool = $phpbb_container->get('dbal.tools'); + + return $db_tool->sql_table_exists($this->table_captcha_questions); + } + + /** + * API function - for the captcha to be available, it must have installed itself and there has to be at least one question in the board's default lang + */ + public function is_available() + { + global $config, $db, $user; + + // load language file for pretty display in the ACP dropdown + $user->add_lang('captcha_qa'); + + if (!$this->is_installed()) + { + return false; + } + + $sql = 'SELECT COUNT(question_id) AS question_count + FROM ' . $this->table_captcha_questions . " + WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + return ((bool) $row['question_count']); + } + + /** + * API function + */ + function has_config() + { + return true; + } + + /** + * API function + */ + static public function get_name() + { + return 'CAPTCHA_QA'; + } + + /** + * @return string the name of the service corresponding to the plugin + */ + function get_service_name() + { + return $this->service_name; + } + + /** + * Set the name of the plugin + * + * @param string $name + */ + public function set_name($name) + { + $this->service_name = $name; + } + + /** + * API function - not needed as we don't display an image + */ + function execute_demo() + { + } + + /** + * API function - not needed as we don't display an image + */ + function execute() + { + } + + /** + * API function - send the question to the template + */ + function get_template() + { + global $phpbb_log, $template, $user; + + if ($this->is_solved()) + { + return false; + } + else if (empty($this->question_text) || !count($this->question_ids)) + { + /** @var \phpbb\log\log_interface $phpbb_log */ + $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_ERROR_CAPTCHA', time(), array($user->lang('CONFIRM_QUESTION_MISSING'))); + return false; + } + else + { + $template->assign_vars(array( + 'QA_CONFIRM_QUESTION' => $this->question_text, + 'QA_CONFIRM_ID' => $this->confirm_id, + 'S_CONFIRM_CODE' => true, + 'S_TYPE' => $this->type, + )); + + return 'captcha_qa.html'; + } + } + + /** + * API function - we just display a mockup so that the captcha doesn't need to be installed + */ + function get_demo_template() + { + global $config, $db, $template; + + if ($this->is_available()) + { + $sql = 'SELECT question_text + FROM ' . $this->table_captcha_questions . " + WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "'"; + $result = $db->sql_query_limit($sql, 1); + if ($row = $db->sql_fetchrow($result)) + { + $template->assign_vars(array( + 'QA_CONFIRM_QUESTION' => $row['question_text'], + )); + } + $db->sql_freeresult($result); + } + return 'captcha_qa_acp_demo.html'; + } + + /** + * API function + */ + function get_hidden_fields() + { + $hidden_fields = array(); + + // this is required - otherwise we would forget about the captcha being already solved + if ($this->solved) + { + $hidden_fields['qa_answer'] = $this->answer; + } + $hidden_fields['qa_confirm_id'] = $this->confirm_id; + + return $hidden_fields; + } + + /** + * API function + */ + function garbage_collect($type = 0) + { + global $db; + + $sql = 'SELECT c.confirm_id + FROM ' . $this->table_qa_confirm . ' c + LEFT JOIN ' . SESSIONS_TABLE . ' s + ON (c.session_id = s.session_id) + WHERE s.session_id IS NULL' . + ((empty($type)) ? '' : ' AND c.confirm_type = ' . (int) $type); + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $sql_in = array(); + + do + { + $sql_in[] = (string) $row['confirm_id']; + } + while ($row = $db->sql_fetchrow($result)); + + if (count($sql_in)) + { + $sql = 'DELETE FROM ' . $this->table_qa_confirm . ' + WHERE ' . $db->sql_in_set('confirm_id', $sql_in); + $db->sql_query($sql); + } + } + $db->sql_freeresult($result); + } + + /** + * API function - we don't drop the tables here, as that would cause the loss of all entered questions. + */ + function uninstall() + { + $this->garbage_collect(0); + } + + /** + * API function - set up shop + */ + function install() + { + global $phpbb_container; + + $db_tool = $phpbb_container->get('dbal.tools'); + $schemas = array( + $this->table_captcha_questions => array ( + 'COLUMNS' => array( + 'question_id' => array('UINT', null, 'auto_increment'), + 'strict' => array('BOOL', 0), + 'lang_id' => array('UINT', 0), + 'lang_iso' => array('VCHAR:30', ''), + 'question_text' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'question_id', + 'KEYS' => array( + 'lang' => array('INDEX', 'lang_iso'), + ), + ), + $this->table_captcha_answers => array ( + 'COLUMNS' => array( + 'question_id' => array('UINT', 0), + 'answer_text' => array('STEXT_UNI', ''), + ), + 'KEYS' => array( + 'qid' => array('INDEX', 'question_id'), + ), + ), + $this->table_qa_confirm => array ( + 'COLUMNS' => array( + 'session_id' => array('CHAR:32', ''), + 'confirm_id' => array('CHAR:32', ''), + 'lang_iso' => array('VCHAR:30', ''), + 'question_id' => array('UINT', 0), + 'attempts' => array('UINT', 0), + 'confirm_type' => array('USINT', 0), + ), + 'KEYS' => array( + 'session_id' => array('INDEX', 'session_id'), + 'lookup' => array('INDEX', array('confirm_id', 'session_id', 'lang_iso')), + ), + 'PRIMARY_KEY' => 'confirm_id', + ), + ); + + foreach ($schemas as $table => $schema) + { + if (!$db_tool->sql_table_exists($table)) + { + $db_tool->sql_create_table($table, $schema); + } + } + } + + /** + * API function - see what has to be done to validate + */ + function validate() + { + global $phpbb_log, $user; + + $error = ''; + + if (!count($this->question_ids)) + { + /** @var \phpbb\log\log_interface $phpbb_log */ + $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_ERROR_CAPTCHA', time(), array($user->lang('CONFIRM_QUESTION_MISSING'))); + return $user->lang('CONFIRM_QUESTION_MISSING'); + } + + if (!$this->confirm_id) + { + $error = $user->lang['CONFIRM_QUESTION_WRONG']; + } + else + { + if ($this->check_answer()) + { + $this->solved = true; + } + else + { + $error = $user->lang['CONFIRM_QUESTION_WRONG']; + } + } + + if (strlen($error)) + { + // okay, incorrect answer. Let's ask a new question. + $this->new_attempt(); + $this->solved = false; + + return $error; + } + else + { + return false; + } + } + + /** + * Select a question + */ + function select_question() + { + global $db, $user; + + if (!count($this->question_ids)) + { + return; + } + $this->confirm_id = md5(unique_id($user->ip)); + $this->question = (int) array_rand($this->question_ids); + + $sql = 'INSERT INTO ' . $this->table_qa_confirm . ' ' . $db->sql_build_array('INSERT', array( + 'confirm_id' => (string) $this->confirm_id, + 'session_id' => (string) $user->session_id, + 'lang_iso' => (string) $this->question_lang, + 'confirm_type' => (int) $this->type, + 'question_id' => (int) $this->question, + )); + $db->sql_query($sql); + + $this->load_answer(); + } + + /** + * New Question, if desired. + */ + function reselect_question() + { + global $db, $user; + + if (!count($this->question_ids)) + { + return; + } + + $this->question = (int) array_rand($this->question_ids); + $this->solved = 0; + + $sql = 'UPDATE ' . $this->table_qa_confirm . ' + SET question_id = ' . (int) $this->question . " + WHERE confirm_id = '" . $db->sql_escape($this->confirm_id) . "' + AND session_id = '" . $db->sql_escape($user->session_id) . "'"; + $db->sql_query($sql); + + $this->load_answer(); + } + + /** + * Wrong answer, so we increase the attempts and use a different question. + */ + function new_attempt() + { + global $db, $user; + + // yah, I would prefer a stronger rand, but this should work + $this->question = (int) array_rand($this->question_ids); + $this->solved = 0; + + $sql = 'UPDATE ' . $this->table_qa_confirm . ' + SET question_id = ' . (int) $this->question . ", + attempts = attempts + 1 + WHERE confirm_id = '" . $db->sql_escape($this->confirm_id) . "' + AND session_id = '" . $db->sql_escape($user->session_id) . "'"; + $db->sql_query($sql); + + $this->load_answer(); + } + + + /** + * See if there is already an entry for the current session. + */ + function load_confirm_id() + { + global $db, $user; + + $sql = 'SELECT confirm_id + FROM ' . $this->table_qa_confirm . " + WHERE + session_id = '" . $db->sql_escape($user->session_id) . "' + AND lang_iso = '" . $db->sql_escape($this->question_lang) . "' + AND confirm_type = " . $this->type; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $this->confirm_id = $row['confirm_id']; + return true; + } + return false; + } + + /** + * Look up everything we need and populate the instance variables. + */ + function load_answer() + { + global $db, $user; + + if (!strlen($this->confirm_id) || !count($this->question_ids)) + { + return false; + } + + $sql = 'SELECT con.question_id, attempts, question_text, strict + FROM ' . $this->table_qa_confirm . ' con, ' . $this->table_captcha_questions . " qes + WHERE con.question_id = qes.question_id + AND confirm_id = '" . $db->sql_escape($this->confirm_id) . "' + AND session_id = '" . $db->sql_escape($user->session_id) . "' + AND qes.lang_iso = '" . $db->sql_escape($this->question_lang) . "' + AND confirm_type = " . $this->type; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $this->question = $row['question_id']; + + $this->attempts = $row['attempts']; + $this->question_strict = $row['strict']; + $this->question_text = $row['question_text']; + + return true; + } + + return false; + } + + /** + * The actual validation + */ + function check_answer() + { + global $db, $request; + + $answer = ($this->question_strict) ? $request->variable('qa_answer', '', true) : utf8_clean_string($request->variable('qa_answer', '', true)); + + $sql = 'SELECT answer_text + FROM ' . $this->table_captcha_answers . ' + WHERE question_id = ' . (int) $this->question; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $solution = ($this->question_strict) ? $row['answer_text'] : utf8_clean_string($row['answer_text']); + + if ($solution === $answer) + { + $this->solved = true; + + break; + } + } + $db->sql_freeresult($result); + + return $this->solved; + } + + /** + * API function + */ + function get_attempt_count() + { + return $this->attempts; + } + + /** + * API function + */ + function reset() + { + global $db, $user; + + $sql = 'DELETE FROM ' . $this->table_qa_confirm . " + WHERE session_id = '" . $db->sql_escape($user->session_id) . "' + AND confirm_type = " . (int) $this->type; + $db->sql_query($sql); + + // we leave the class usable by generating a new question + $this->select_question(); + } + + /** + * API function + */ + function is_solved() + { + global $request; + + if ($request->variable('qa_answer', false) && $this->solved === 0) + { + $this->validate(); + } + + return (bool) $this->solved; + } + + /** + * API function - The ACP backend, this marks the end of the easy methods + */ + function acp_page($id, $module) + { + global $config, $request, $phpbb_log, $template, $user; + + $user->add_lang('acp/board'); + $user->add_lang('captcha_qa'); + + if (!self::is_installed()) + { + $this->install(); + } + + $module->tpl_name = 'captcha_qa_acp'; + $module->page_title = 'ACP_VC_SETTINGS'; + $form_key = 'acp_captcha'; + add_form_key($form_key); + + $submit = $request->variable('submit', false); + $question_id = $request->variable('question_id', 0); + $action = $request->variable('action', ''); + + // we have two pages, so users might want to navigate from one to the other + $list_url = $module->u_action . "&configure=1&select_captcha=" . $this->get_service_name(); + + $template->assign_vars(array( + 'U_ACTION' => $module->u_action, + 'QUESTION_ID' => $question_id , + 'CLASS' => $this->get_service_name(), + )); + + // show the list? + if (!$question_id && $action != 'add') + { + $this->acp_question_list($module); + } + else if ($question_id && $action == 'delete') + { + if ($this->get_service_name() !== $config['captcha_plugin'] || !$this->acp_is_last($question_id)) + { + if (confirm_box(true)) + { + $this->acp_delete_question($question_id); + + trigger_error($user->lang['QUESTION_DELETED'] . adm_back_link($list_url)); + } + else + { + confirm_box(false, $user->lang['CONFIRM_OPERATION'], build_hidden_fields(array( + 'question_id' => $question_id, + 'action' => $action, + 'configure' => 1, + 'select_captcha' => $this->get_service_name(), + )) + ); + } + } + else + { + trigger_error($user->lang['QA_LAST_QUESTION'] . adm_back_link($list_url), E_USER_WARNING); + } + } + else + { + // okay, show the editor + $question_input = $this->acp_get_question_input(); + $langs = $this->get_languages(); + + foreach ($langs as $lang => $entry) + { + $template->assign_block_vars('langs', array( + 'ISO' => $lang, + 'NAME' => $entry['name'], + )); + } + + $template->assign_vars(array( + 'U_LIST' => $list_url, + )); + + if ($question_id) + { + if ($question = $this->acp_get_question_data($question_id)) + { + $template->assign_vars(array( + 'QUESTION_TEXT' => ($question_input['question_text']) ? $question_input['question_text'] : $question['question_text'], + 'LANG_ISO' => ($question_input['lang_iso']) ? $question_input['lang_iso'] : $question['lang_iso'], + 'STRICT' => (isset($_REQUEST['strict'])) ? $question_input['strict'] : $question['strict'], + 'ANSWERS' => implode("\n", $question['answers']), + )); + } + else + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($list_url)); + } + } + else + { + $template->assign_vars(array( + 'QUESTION_TEXT' => $question_input['question_text'], + 'LANG_ISO' => $question_input['lang_iso'], + 'STRICT' => $question_input['strict'], + 'ANSWERS' => (is_array($question_input['answers'])) ? implode("\n", $question_input['answers']) : '', + )); + } + + if ($submit && check_form_key($form_key)) + { + if (!$this->validate_input($question_input)) + { + $template->assign_vars(array( + 'S_ERROR' => true, + )); + } + else + { + if ($question_id) + { + $this->acp_update_question($question_input, $question_id); + } + else + { + $this->acp_add_question($question_input); + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($list_url)); + } + } + else if ($submit) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($list_url), E_USER_WARNING); + } + } + } + + /** + * This handles the list overview + */ + function acp_question_list($module) + { + global $db, $template; + + $sql = 'SELECT * + FROM ' . $this->table_captcha_questions; + $result = $db->sql_query($sql); + + $template->assign_vars(array( + 'S_LIST' => true, + )); + + while ($row = $db->sql_fetchrow($result)) + { + $url = $module->u_action . "&question_id={$row['question_id']}&configure=1&select_captcha=" . $this->get_service_name() . '&'; + + $template->assign_block_vars('questions', array( + 'QUESTION_TEXT' => $row['question_text'], + 'QUESTION_ID' => $row['question_id'], + 'QUESTION_LANG' => $row['lang_iso'], + 'U_DELETE' => "{$url}action=delete", + 'U_EDIT' => "{$url}action=edit", + )); + } + $db->sql_freeresult($result); + } + + /** + * Grab a question and bring it into a format the editor understands + */ + function acp_get_question_data($question_id) + { + global $db; + + if ($question_id) + { + $sql = 'SELECT * + FROM ' . $this->table_captcha_questions . ' + WHERE question_id = ' . $question_id; + $result = $db->sql_query($sql); + $question = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$question) + { + return false; + } + + $question['answers'] = array(); + + $sql = 'SELECT * + FROM ' . $this->table_captcha_answers . ' + WHERE question_id = ' . $question_id; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $question['answers'][] = $row['answer_text']; + } + $db->sql_freeresult($result); + + return $question; + } + + return false; + } + + /** + * Grab a question from input and bring it into a format the editor understands + */ + function acp_get_question_input() + { + global $request; + + $answers = $request->variable('answers', '', true); + + // Convert answers into array and filter if answers are set + if (strlen($answers)) + { + $answers = array_filter(array_map('trim', explode("\n", $answers)), function ($value) { + return $value !== ''; + }); + } + + $question = array( + 'question_text' => $request->variable('question_text', '', true), + 'strict' => $request->variable('strict', false), + 'lang_iso' => $request->variable('lang_iso', ''), + 'answers' => $answers, + ); + return $question; + } + + /** + * Update a question. + * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data + */ + function acp_update_question($data, $question_id) + { + global $db, $cache; + + // easier to delete all answers than to figure out which to update + $sql = 'DELETE FROM ' . $this->table_captcha_answers . " WHERE question_id = $question_id"; + $db->sql_query($sql); + + $langs = $this->get_languages(); + $question_ary = $data; + $question_ary['lang_id'] = $langs[$question_ary['lang_iso']]['id']; + unset($question_ary['answers']); + + $sql = 'UPDATE ' . $this->table_captcha_questions . ' + SET ' . $db->sql_build_array('UPDATE', $question_ary) . " + WHERE question_id = $question_id"; + $db->sql_query($sql); + + $this->acp_insert_answers($data, $question_id); + + $cache->destroy('sql', $this->table_captcha_questions); + } + + /** + * Insert a question. + * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data + */ + function acp_add_question($data) + { + global $db, $cache; + + $langs = $this->get_languages(); + $question_ary = $data; + + $question_ary['lang_id'] = $langs[$data['lang_iso']]['id']; + unset($question_ary['answers']); + + $sql = 'INSERT INTO ' . $this->table_captcha_questions . ' ' . $db->sql_build_array('INSERT', $question_ary); + $db->sql_query($sql); + + $question_id = $db->sql_nextid(); + + $this->acp_insert_answers($data, $question_id); + + $cache->destroy('sql', $this->table_captcha_questions); + } + + /** + * Insert the answers. + * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data + */ + function acp_insert_answers($data, $question_id) + { + global $db, $cache; + + foreach ($data['answers'] as $answer) + { + $answer_ary = array( + 'question_id' => $question_id, + 'answer_text' => $answer, + ); + + $sql = 'INSERT INTO ' . $this->table_captcha_answers . ' ' . $db->sql_build_array('INSERT', $answer_ary); + $db->sql_query($sql); + } + + $cache->destroy('sql', $this->table_captcha_answers); + } + + /** + * Delete a question. + */ + function acp_delete_question($question_id) + { + global $db, $cache; + + $tables = array($this->table_captcha_questions, $this->table_captcha_answers); + + foreach ($tables as $table) + { + $sql = "DELETE FROM $table + WHERE question_id = $question_id"; + $db->sql_query($sql); + } + + $cache->destroy('sql', $tables); + } + + /** + * Check if the entered data can be inserted/used + * param mixed $data : an array as created from acp_get_question_input or acp_get_question_data + */ + function validate_input($question_data) + { + $langs = $this->get_languages(); + + if (!isset($question_data['lang_iso']) || + !isset($question_data['question_text']) || + !isset($question_data['strict']) || + !isset($question_data['answers'])) + { + return false; + } + + if (!isset($langs[$question_data['lang_iso']]) || + !strlen($question_data['question_text']) || + !count($question_data['answers']) || + !is_array($question_data['answers'])) + { + return false; + } + + return true; + } + + /** + * List the installed language packs + */ + function get_languages() + { + global $db; + + $sql = 'SELECT * + FROM ' . LANG_TABLE; + $result = $db->sql_query($sql); + + $langs = array(); + while ($row = $db->sql_fetchrow($result)) + { + $langs[$row['lang_iso']] = array( + 'name' => $row['lang_local_name'], + 'id' => (int) $row['lang_id'], + ); + } + $db->sql_freeresult($result); + + return $langs; + } + + + + /** + * See if there is a question other than the one we have + */ + function acp_is_last($question_id) + { + global $config, $db; + + if ($question_id) + { + $sql = 'SELECT question_id + FROM ' . $this->table_captcha_questions . " + WHERE lang_iso = '" . $db->sql_escape($config['default_lang']) . "' + AND question_id <> " . (int) $question_id; + $result = $db->sql_query_limit($sql, 1); + $question = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$question) + { + return true; + } + return false; + } + } +} diff --git a/phpbb/captcha/plugins/recaptcha.php b/phpbb/captcha/plugins/recaptcha.php new file mode 100644 index 0000000..b7c0b5f --- /dev/null +++ b/phpbb/captcha/plugins/recaptcha.php @@ -0,0 +1,225 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\captcha\plugins; + +class recaptcha extends captcha_abstract +{ + var $recaptcha_server = 'http://www.google.com/recaptcha/api'; + var $recaptcha_server_secure = 'https://www.google.com/recaptcha/api'; // class constants :( + + var $response; + + /** + * Constructor + */ + public function __construct() + { + global $request; + $this->recaptcha_server = $request->is_secure() ? $this->recaptcha_server_secure : $this->recaptcha_server; + } + + function init($type) + { + global $user, $request; + + $user->add_lang('captcha_recaptcha'); + parent::init($type); + $this->response = $request->variable('g-recaptcha-response', ''); + } + + public function is_available() + { + global $config, $user; + $user->add_lang('captcha_recaptcha'); + return (isset($config['recaptcha_pubkey']) && !empty($config['recaptcha_pubkey'])); + } + + /** + * API function + */ + function has_config() + { + return true; + } + + static public function get_name() + { + return 'CAPTCHA_RECAPTCHA'; + } + + /** + * This function is implemented because required by the upper class, but is never used for reCaptcha. + */ + function get_generator_class() + { + throw new \Exception('No generator class given.'); + } + + function acp_page($id, $module) + { + global $config, $template, $user, $phpbb_log, $request; + + $captcha_vars = array( + 'recaptcha_pubkey' => 'RECAPTCHA_PUBKEY', + 'recaptcha_privkey' => 'RECAPTCHA_PRIVKEY', + ); + + $module->tpl_name = 'captcha_recaptcha_acp'; + $module->page_title = 'ACP_VC_SETTINGS'; + $form_key = 'acp_captcha'; + add_form_key($form_key); + + $submit = $request->variable('submit', ''); + + if ($submit && check_form_key($form_key)) + { + $captcha_vars = array_keys($captcha_vars); + foreach ($captcha_vars as $captcha_var) + { + $value = $request->variable($captcha_var, ''); + if ($value) + { + $config->set($captcha_var, $value); + } + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_CONFIG_VISUAL'); + trigger_error($user->lang['CONFIG_UPDATED'] . adm_back_link($module->u_action)); + } + else if ($submit) + { + trigger_error($user->lang['FORM_INVALID'] . adm_back_link($module->u_action)); + } + else + { + foreach ($captcha_vars as $captcha_var => $template_var) + { + $var = (isset($_REQUEST[$captcha_var])) ? $request->variable($captcha_var, '') : ((isset($config[$captcha_var])) ? $config[$captcha_var] : ''); + $template->assign_var($template_var, $var); + } + + $template->assign_vars(array( + 'CAPTCHA_PREVIEW' => $this->get_demo_template($id), + 'CAPTCHA_NAME' => $this->get_service_name(), + 'U_ACTION' => $module->u_action, + )); + + } + } + + // not needed + function execute_demo() + { + } + + // not needed + function execute() + { + } + + function get_template() + { + global $config, $user, $template, $phpbb_root_path, $phpEx; + + if ($this->is_solved()) + { + return false; + } + else + { + $contact_link = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx); + $explain = $user->lang(($this->type != CONFIRM_POST) ? 'CONFIRM_EXPLAIN' : 'POST_CONFIRM_EXPLAIN', '', ''); + + $template->assign_vars(array( + 'RECAPTCHA_SERVER' => $this->recaptcha_server, + 'RECAPTCHA_PUBKEY' => isset($config['recaptcha_pubkey']) ? $config['recaptcha_pubkey'] : '', + 'S_RECAPTCHA_AVAILABLE' => self::is_available(), + 'S_CONFIRM_CODE' => true, + 'S_TYPE' => $this->type, + 'L_CONFIRM_EXPLAIN' => $explain, + )); + + return 'captcha_recaptcha.html'; + } + } + + function get_demo_template($id) + { + return $this->get_template(); + } + + function get_hidden_fields() + { + $hidden_fields = array(); + + // this is required for posting.php - otherwise we would forget about the captcha being already solved + if ($this->solved) + { + $hidden_fields['confirm_code'] = $this->code; + } + $hidden_fields['confirm_id'] = $this->confirm_id; + return $hidden_fields; + } + + function uninstall() + { + $this->garbage_collect(0); + } + + function install() + { + return; + } + + function validate() + { + if (!parent::validate()) + { + return false; + } + else + { + return $this->recaptcha_check_answer(); + } + } + + /** + * Calls an HTTP POST function to verify if the user's guess was correct + * + * @return bool|string Returns false on success or error string on failure. + */ + function recaptcha_check_answer() + { + global $config, $user; + + //discard spam submissions + if ($this->response == null || strlen($this->response) == 0) + { + return $user->lang['RECAPTCHA_INCORRECT']; + } + + $recaptcha = new \ReCaptcha\ReCaptcha($config['recaptcha_privkey']); + $result = $recaptcha->verify($this->response, $user->ip); + + if ($result->isSuccess()) + { + $this->solved = true; + return false; + } + else + { + return $user->lang['RECAPTCHA_INCORRECT']; + } + } +} diff --git a/phpbb/class_loader.php b/phpbb/class_loader.php new file mode 100644 index 0000000..cfdcc2a --- /dev/null +++ b/phpbb/class_loader.php @@ -0,0 +1,164 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** +* The class loader resolves class names to file system paths and loads them if +* necessary. +* +* Classes have to be of the form phpbb_(dir_)*(classpart_)*, so directory names +* must never contain underscores. Example: phpbb_dir_subdir_class_name is a +* valid class name, while phpbb_dir_sub_dir_class_name is not. +* +* If every part of the class name is a directory, the last directory name is +* also used as the filename, e.g. phpbb_dir would resolve to dir/dir.php. +*/ +class class_loader +{ + private $namespace; + private $path; + private $php_ext; + private $cache; + + /** + * A map of looked up class names to paths relative to $this->path. + * This map is stored in cache and looked up if the cache is available. + * + * @var array + */ + private $cached_paths = array(); + + /** + * Creates a new \phpbb\class_loader, which loads files with the given + * file extension from the given path. + * + * @param string $namespace Required namespace for files to be loaded + * @param string $path Directory to load files from + * @param string $php_ext The file extension for PHP files + * @param \phpbb\cache\driver\driver_interface $cache An implementation of the phpBB cache interface. + */ + public function __construct($namespace, $path, $php_ext = 'php', \phpbb\cache\driver\driver_interface $cache = null) + { + if ($namespace[0] !== '\\') + { + $namespace = '\\' . $namespace; + } + + $this->namespace = $namespace; + $this->path = $path; + $this->php_ext = $php_ext; + + $this->set_cache($cache); + } + + /** + * Provide the class loader with a cache to store paths. If set to null, the + * the class loader will resolve paths by checking for the existance of every + * directory in the class name every time. + * + * @param \phpbb\cache\driver\driver_interface $cache An implementation of the phpBB cache interface. + */ + public function set_cache(\phpbb\cache\driver\driver_interface $cache = null) + { + if ($cache) + { + $this->cached_paths = $cache->get('class_loader_' . str_replace('\\', '__', $this->namespace)); + + if ($this->cached_paths === false) + { + $this->cached_paths = array(); + } + } + + $this->cache = $cache; + } + + /** + * Registers the class loader as an autoloader using SPL. + */ + public function register() + { + spl_autoload_register(array($this, 'load_class')); + } + + /** + * Removes the class loader from the SPL autoloader stack. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'load_class')); + } + + /** + * Resolves a phpBB class name to a relative path which can be included. + * + * @param string $class The class name to resolve, must be in the + * namespace the loader was constructed with. + * Has to begin with \ + * @return string|bool A relative path to the file containing the + * class or false if looking it up failed. + */ + public function resolve_path($class) + { + if (isset($this->cached_paths[$class])) + { + return $this->path . $this->cached_paths[$class] . '.' . $this->php_ext; + } + + if (!preg_match('/^' . preg_quote($this->namespace, '/') . '[a-zA-Z0-9_\\\\]+$/', $class)) + { + return false; + } + + $relative_path = str_replace('\\', '/', substr($class, strlen($this->namespace))); + + if (!file_exists($this->path . $relative_path . '.' . $this->php_ext)) + { + return false; + } + + if ($this->cache) + { + $this->cached_paths[$class] = $relative_path; + $this->cache->put('class_loader_' . str_replace('\\', '__', $this->namespace), $this->cached_paths); + } + + return $this->path . $relative_path . '.' . $this->php_ext; + } + + /** + * Resolves a class name to a path and then includes it. + * + * @param string $class The class name which is being loaded. + */ + public function load_class($class) + { + // In general $class is not supposed to contain a leading backslash, + // but sometimes it does. See tickets PHP-50731 and HHVM-1840. + if ($class[0] !== '\\') + { + $class = '\\' . $class; + } + + if (substr($class, 0, strlen($this->namespace)) === $this->namespace) + { + $path = $this->resolve_path($class); + + if ($path) + { + require $path; + } + } + } +} diff --git a/phpbb/composer.json b/phpbb/composer.json new file mode 100644 index 0000000..0b6299d --- /dev/null +++ b/phpbb/composer.json @@ -0,0 +1,32 @@ +{ + "name": "phpbb/phpbb-core", + "description": "Collection of core phpBB libraries", + "type": "library", + "keywords": ["phpbb", "forum"], + "homepage": "https://www.phpbb.com", + "license": "GPL-2.0", + "authors": [ + { + "name": "phpBB Limited", + "email": "operations@phpbb.com", + "homepage": "https://www.phpbb.com/go/authors" + } + ], + "support": { + "issues": "https://tracker.phpbb.com", + "forum": "https://www.phpbb.com/community/", + "wiki": "https://wiki.phpbb.com", + "irc": "irc://irc.freenode.org/phpbb" + }, + "autoload": { + "classmap": [""] + }, + "require": { + "php": ">=5.4" + }, + "extra": { + "branch-alias": { + "dev-master": "3.3.x-dev" + } + } +} diff --git a/phpbb/config/config.php b/phpbb/config/config.php new file mode 100644 index 0000000..aaad333 --- /dev/null +++ b/phpbb/config/config.php @@ -0,0 +1,167 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\config; + +/** +* Configuration container class +*/ +class config implements \ArrayAccess, \IteratorAggregate, \Countable +{ + /** + * The configuration data + * @var array(string => string) + */ + protected $config; + + /** + * Creates a configuration container with a default set of values + * + * @param array(string => string) $config The configuration data. + */ + public function __construct(array $config) + { + $this->config = $config; + } + + /** + * Retrieves an ArrayIterator over the configuration values. + * + * @return \ArrayIterator An iterator over all config data + */ + public function getIterator() + { + return new \ArrayIterator($this->config); + } + + /** + * Checks if the specified config value exists. + * + * @param string $key The configuration option's name. + * @return bool Whether the configuration option exists. + */ + public function offsetExists($key) + { + return isset($this->config[$key]); + } + + /** + * Retrieves a configuration value. + * + * @param string $key The configuration option's name. + * @return string The configuration value + */ + public function offsetGet($key) + { + return (isset($this->config[$key])) ? $this->config[$key] : ''; + } + + /** + * Temporarily overwrites the value of a configuration variable. + * + * The configuration change will not persist. It will be lost + * after the request. + * + * @param string $key The configuration option's name. + * @param string $value The temporary value. + */ + public function offsetSet($key, $value) + { + $this->config[$key] = $value; + } + + /** + * Called when deleting a configuration value directly, triggers an error. + * + * @param string $key The configuration option's name. + */ + public function offsetUnset($key) + { + trigger_error('Config values have to be deleted explicitly with the \phpbb\config\config::delete($key) method.', E_USER_ERROR); + } + + /** + * Retrieves the number of configuration options currently set. + * + * @return int Number of config options + */ + public function count() + { + return count($this->config); + } + + /** + * Removes a configuration option + * + * @param String $key The configuration option's name + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached + * @return null + */ + public function delete($key, $use_cache = true) + { + unset($this->config[$key]); + } + + /** + * Sets a configuration option's value + * + * @param string $key The configuration option's name + * @param string $value New configuration value + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + */ + public function set($key, $value, $use_cache = true) + { + $this->config[$key] = $value; + } + + /** + * Sets a configuration option's value only if the old_value matches the + * current configuration value or the configuration value does not exist yet. + * + * @param string $key The configuration option's name + * @param string $old_value Current configuration value + * @param string $new_value New configuration value + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + * @return bool True if the value was changed, false otherwise. + */ + public function set_atomic($key, $old_value, $new_value, $use_cache = true) + { + if (!isset($this->config[$key]) || $this->config[$key] == $old_value) + { + $this->config[$key] = $new_value; + return true; + } + return false; + } + + /** + * Increments an integer configuration value. + * + * @param string $key The configuration option's name + * @param int $increment Amount to increment by + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + */ + function increment($key, $increment, $use_cache = true) + { + if (!isset($this->config[$key])) + { + $this->config[$key] = 0; + } + + $this->config[$key] += $increment; + } +} diff --git a/phpbb/config/db.php b/phpbb/config/db.php new file mode 100644 index 0000000..26489bd --- /dev/null +++ b/phpbb/config/db.php @@ -0,0 +1,204 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\config; + +/** +* Configuration container class +*/ +class db extends \phpbb\config\config +{ + /** + * Cache instance + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * Database connection + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * Name of the database table used for configuration. + * @var string + */ + protected $table; + + /** + * Creates a configuration container with a default set of values + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param \phpbb\cache\driver\driver_interface $cache Cache instance + * @param string $table Configuration table name + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\driver\driver_interface $cache, $table) + { + $this->db = $db; + $this->cache = $cache; + $this->table = $table; + + if (($config = $cache->get('config')) !== false) + { + $sql = 'SELECT config_name, config_value + FROM ' . $this->table . ' + WHERE is_dynamic = 1'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $config[$row['config_name']] = $row['config_value']; + } + $this->db->sql_freeresult($result); + } + else + { + $config = $cached_config = array(); + + $sql = 'SELECT config_name, config_value, is_dynamic + FROM ' . $this->table; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!$row['is_dynamic']) + { + $cached_config[$row['config_name']] = $row['config_value']; + } + + $config[$row['config_name']] = $row['config_value']; + } + $this->db->sql_freeresult($result); + + $cache->put('config', $cached_config); + } + + parent::__construct($config); + } + + /** + * Removes a configuration option + * + * @param String $key The configuration option's name + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached + * @return null + */ + public function delete($key, $use_cache = true) + { + $sql = 'DELETE FROM ' . $this->table . " + WHERE config_name = '" . $this->db->sql_escape($key) . "'"; + $this->db->sql_query($sql); + + unset($this->config[$key]); + + if ($use_cache) + { + $this->cache->destroy('config'); + } + } + + /** + * Sets a configuration option's value + * + * @param string $key The configuration option's name + * @param string $value New configuration value + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + */ + public function set($key, $value, $use_cache = true) + { + $this->set_atomic($key, false, $value, $use_cache); + } + + /** + * Sets a configuration option's value only if the old_value matches the + * current configuration value or the configuration value does not exist yet. + * + * @param string $key The configuration option's name + * @param mixed $old_value Current configuration value or false to ignore + * the old value + * @param string $new_value New configuration value + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached + * @return bool True if the value was changed, false otherwise + */ + public function set_atomic($key, $old_value, $new_value, $use_cache = true) + { + $sql = 'UPDATE ' . $this->table . " + SET config_value = '" . $this->db->sql_escape($new_value) . "' + WHERE config_name = '" . $this->db->sql_escape($key) . "'"; + + if ($old_value !== false) + { + $sql .= " AND config_value = '" . $this->db->sql_escape($old_value) . "'"; + } + + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows() && isset($this->config[$key])) + { + return false; + } + + if (!isset($this->config[$key])) + { + $sql = 'INSERT INTO ' . $this->table . ' ' . $this->db->sql_build_array('INSERT', array( + 'config_name' => $key, + 'config_value' => $new_value, + 'is_dynamic' => ($use_cache) ? 0 : 1)); + $this->db->sql_query($sql); + } + + if ($use_cache) + { + $this->cache->destroy('config'); + } + + $this->config[$key] = $new_value; + return true; + } + + /** + * Increments an integer config value directly in the database. + * + * Using this method instead of setting the new value directly avoids race + * conditions and unlike set_atomic it cannot fail. + * + * @param string $key The configuration option's name + * @param int $increment Amount to increment by + * @param bool $use_cache Whether this variable should be cached or if it + * changes too frequently to be efficiently cached. + */ + function increment($key, $increment, $use_cache = true) + { + if (!isset($this->config[$key])) + { + $this->set($key, '0', $use_cache); + } + + $sql_update = $this->db->cast_expr_to_string($this->db->cast_expr_to_bigint('config_value') . ' + ' . (int) $increment); + + $this->db->sql_query('UPDATE ' . $this->table . ' + SET config_value = ' . $sql_update . " + WHERE config_name = '" . $this->db->sql_escape($key) . "'"); + + if ($use_cache) + { + $this->cache->destroy('config'); + } + + $this->config[$key] += $increment; + } +} diff --git a/phpbb/config/db_text.php b/phpbb/config/db_text.php new file mode 100644 index 0000000..818f6bd --- /dev/null +++ b/phpbb/config/db_text.php @@ -0,0 +1,159 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\config; + +/** +* Manages configuration options with an arbitrary length value stored in a TEXT +* column. In constrast to class \phpbb\config\db, values are never cached and +* prefetched, but every get operation sends a query to the database. +*/ +class db_text +{ + /** + * Database connection + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * Name of the database table used. + * @var string + */ + protected $table; + + /** + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param string $table Table name + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $table) + { + $this->db = $db; + $this->table = $this->db->sql_escape($table); + } + + /** + * Sets the configuration option with the name $key to $value. + * + * @param string $key The configuration option's name + * @param string $value New configuration value + * + * @return null + */ + public function set($key, $value) + { + $this->set_array(array($key => $value)); + } + + /** + * Gets the configuration value for the name $key. + * + * @param string $key The configuration option's name + * + * @return string|null String result on success + * null if there is no such option + */ + public function get($key) + { + $map = $this->get_array(array($key)); + + return isset($map[$key]) ? $map[$key] : null; + } + + /** + * Removes the configuration option with the name $key. + * + * @param string $key The configuration option's name + * + * @return null + */ + public function delete($key) + { + $this->delete_array(array($key)); + } + + /** + * Mass set configuration options: Receives an associative array, + * treats array keys as configuration option names and associated + * array values as their configuration option values. + * + * @param array $map Map from configuration names to values + * + * @return null + */ + public function set_array(array $map) + { + $this->db->sql_transaction('begin'); + + foreach ($map as $key => $value) + { + $sql = 'UPDATE ' . $this->table . " + SET config_value = '" . $this->db->sql_escape($value) . "' + WHERE config_name = '" . $this->db->sql_escape($key) . "'"; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + $sql = 'INSERT INTO ' . $this->table . ' ' . $this->db->sql_build_array('INSERT', array( + 'config_name' => (string) $key, + 'config_value' => (string) $value, + )); + $this->db->sql_query($sql); + } + } + + $this->db->sql_transaction('commit'); + } + + /** + * Mass get configuration options: Receives a set of configuration + * option names and returns the result as a key => value map where + * array keys are configuration option names and array values are + * associated config option values. + * + * @param array $keys Set of configuration option names + * + * @return array Map from configuration names to values + */ + public function get_array(array $keys) + { + $sql = 'SELECT * + FROM ' . $this->table . ' + WHERE ' . $this->db->sql_in_set('config_name', $keys, false, true); + $result = $this->db->sql_query($sql); + + $map = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $map[$row['config_name']] = $row['config_value']; + } + $this->db->sql_freeresult($result); + + return $map; + } + + /** + * Mass delete configuration options. + * + * @param array $keys Set of configuration option names + * + * @return null + */ + public function delete_array(array $keys) + { + $sql = 'DELETE + FROM ' . $this->table . ' + WHERE ' . $this->db->sql_in_set('config_name', $keys, false, true); + $this->db->sql_query($sql); + } +} diff --git a/phpbb/config_php_file.php b/phpbb/config_php_file.php new file mode 100644 index 0000000..7445e7d --- /dev/null +++ b/phpbb/config_php_file.php @@ -0,0 +1,160 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +class config_php_file +{ + /** @var string phpBB Root Path */ + protected $phpbb_root_path; + + /** @var string php file extension */ + protected $php_ext; + + /** + * Indicates whether the php config file has been loaded. + * + * @var bool + */ + protected $config_loaded = false; + + /** + * The content of the php config file + * + * @var array + */ + protected $config_data = array(); + + /** + * The path to the config file. (Default: $phpbb_root_path . 'config.' . $php_ext) + * + * @var string + */ + protected $config_file; + + private $defined_vars; + + /** + * Constructor + * + * @param string $phpbb_root_path phpBB Root Path + * @param string $php_ext php file extension + */ + function __construct($phpbb_root_path, $php_ext) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config_file = $this->phpbb_root_path . 'config.' . $this->php_ext; + } + + /** + * Set the path to the config file. + * + * @param string $config_file + */ + public function set_config_file($config_file) + { + $this->config_file = $config_file; + $this->config_loaded = false; + } + + /** + * Returns an associative array containing the variables defined by the config file. + * + * @return array Return the content of the config file or an empty array if the file does not exists. + */ + public function get_all() + { + $this->load_config_file(); + + return $this->config_data; + } + + /** + * Return the value of a variable defined into the config.php file or null if the variable does not exist. + * + * @param string $variable The name of the variable + * @return mixed Value of the variable or null if the variable is not defined. + */ + public function get($variable) + { + $this->load_config_file(); + + return isset($this->config_data[$variable]) ? $this->config_data[$variable] : null; + } + + /** + * Load the config file and store the information. + * + * @return null + */ + protected function load_config_file() + { + if (!$this->config_loaded && file_exists($this->config_file)) + { + $this->defined_vars = get_defined_vars(); + + require($this->config_file); + $this->config_data = array_diff_key(get_defined_vars(), $this->defined_vars); + + $this->config_loaded = true; + } + } + + /** + * Convert either 3.0 dbms or 3.1 db driver class name to 3.1 db driver class name. + * + * If $dbms is a valid 3.1 db driver class name, returns it unchanged. + * Otherwise prepends phpbb\db\driver\ to the dbms to convert a 3.0 dbms + * to 3.1 db driver class name. + * + * @param string $dbms dbms parameter + * @return string driver class + * @throws \RuntimeException + */ + public function convert_30_dbms_to_31($dbms) + { + // Note: this check is done first because mysqli extension + // supplies a mysqli class, and class_exists($dbms) would return + // true for mysqli class. + // However, per the docblock any valid 3.1 driver name should be + // recognized by this function, and have priority over 3.0 dbms. + if (strpos($dbms, 'phpbb\db\driver') === false && class_exists('phpbb\db\driver\\' . $dbms)) + { + return 'phpbb\db\driver\\' . $dbms; + } + + if (class_exists($dbms)) + { + // Additionally we could check that $dbms extends phpbb\db\driver\driver. + // http://php.net/manual/en/class.reflectionclass.php + // Beware of possible performance issues: + // http://stackoverflow.com/questions/294582/php-5-reflection-api-performance + // We could check for interface implementation in all paths or + // only when we do not prepend phpbb\db\driver\. + + /* + $reflection = new \ReflectionClass($dbms); + + if ($reflection->isSubclassOf('phpbb\db\driver\driver')) + { + return $dbms; + } + */ + + return $dbms; + } + + throw new \RuntimeException("You have specified an invalid dbms driver: $dbms"); + } +} diff --git a/phpbb/console/application.php b/phpbb/console/application.php new file mode 100644 index 0000000..dc9b801 --- /dev/null +++ b/phpbb/console/application.php @@ -0,0 +1,153 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console; + +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Shell; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class application extends \Symfony\Component\Console\Application +{ + /** + * @var bool Indicates whether or not we are in a shell + */ + protected $in_shell = false; + + /** + * @var \phpbb\language\language User object + */ + protected $language; + + /** + * @param string $name The name of the application + * @param string $version The version of the application + * @param \phpbb\language\language $language The user which runs the application (used for translation) + */ + public function __construct($name, $version, \phpbb\language\language $language) + { + $this->language = $language; + + parent::__construct($name, $version); + } + + /** + * {@inheritdoc} + */ + protected function getDefaultInputDefinition() + { + $input_definition = parent::getDefaultInputDefinition(); + + $this->register_global_options($input_definition); + + return $input_definition; + } + + /** + * Gets the help message. + * + * It's a hack of the default help message to display the --shell + * option only for the application and not for all the commands. + * + * @return string A help message. + */ + public function getHelp() + { + // If we are already in a shell + // we do not want to have the --shell option available + if ($this->in_shell) + { + return parent::getHelp(); + } + + try + { + $definition = $this->getDefinition(); + $definition->addOption(new InputOption( + '--shell', + '-s', + InputOption::VALUE_NONE, + $this->language->lang('CLI_DESCRIPTION_OPTION_SHELL') + )); + } + catch (\LogicException $e) + { + // Do nothing + } + + return parent::getHelp(); + } + + /** + * Register a set of commands from the container + * + * @param \phpbb\di\service_collection $command_collection The console service collection + */ + public function register_container_commands(\phpbb\di\service_collection $command_collection) + { + foreach ($command_collection as $service_command) + { + $this->add($service_command); + } + } + + /** + * {@inheritdoc} + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + // Run a shell if the --shell (or -s) option is set and if no command name is specified + // Also, we do not want to have the --shell option available if we are already in a shell + if (!$this->in_shell && $this->getCommandName($input) === null && $input->hasParameterOption(array('--shell', '-s'))) + { + $shell = new Shell($this); + $this->in_shell = true; + $shell->run(); + + return 0; + } + + return parent::doRun($input, $output); + } + + /** + * Register global options + * + * @param InputDefinition $definition An InputDefinition instance + */ + protected function register_global_options(InputDefinition $definition) + { + try + { + $definition->addOption(new InputOption( + 'safe-mode', + null, + InputOption::VALUE_NONE, + $this->language->lang('CLI_DESCRIPTION_OPTION_SAFE_MODE') + )); + + $definition->addOption(new InputOption( + 'env', + 'e', + InputOption::VALUE_REQUIRED, + $this->language->lang('CLI_DESCRIPTION_OPTION_ENV') + )); + } + catch (\LogicException $e) + { + // Do nothing + } + } +} diff --git a/phpbb/console/command/cache/purge.php b/phpbb/console/command/cache/purge.php new file mode 100644 index 0000000..b7a51b2 --- /dev/null +++ b/phpbb/console/command/cache/purge.php @@ -0,0 +1,91 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\cache; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class purge extends \phpbb\console\command\command +{ + /** @var \phpbb\cache\driver\driver_interface */ + protected $cache; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\auth\auth */ + protected $auth; + + /** @var \phpbb\log\log_interface */ + protected $log; + + /** @var \phpbb\config\config */ + protected $config; + + /** + * Constructor + * + * @param \phpbb\user $user User instance + * @param \phpbb\cache\driver\driver_interface $cache Cache instance + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param \phpbb\auth\auth $auth Auth instance + * @param \phpbb\log\log_interface $log Logger instance + * @param \phpbb\config\config $config Config instance + */ + public function __construct(\phpbb\user $user, \phpbb\cache\driver\driver_interface $cache, \phpbb\db\driver\driver_interface $db, \phpbb\auth\auth $auth, \phpbb\log\log_interface $log, \phpbb\config\config $config) + { + $this->cache = $cache; + $this->db = $db; + $this->auth = $auth; + $this->log = $log; + $this->config = $config; + parent::__construct($user); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('cache:purge') + ->setDescription($this->user->lang('PURGE_CACHE')) + ; + } + + /** + * Executes the command cache:purge. + * + * Purge the cache (including permissions) and increment the asset_version number + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->config->increment('assets_version', 1); + $this->cache->purge(); + + // Clear permissions + $this->auth->acl_clear_prefetch(); + phpbb_cache_moderators($this->db, $this->cache, $this->auth); + + $this->log->add('admin', ANONYMOUS, '', 'LOG_PURGE_CACHE', time(), array()); + + $io = new SymfonyStyle($input, $output); + $io->success($this->user->lang('PURGE_CACHE_SUCCESS')); + } +} diff --git a/phpbb/console/command/command.php b/phpbb/console/command/command.php new file mode 100644 index 0000000..0124c00 --- /dev/null +++ b/phpbb/console/command/command.php @@ -0,0 +1,76 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console\command; + +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +abstract class command extends \Symfony\Component\Console\Command\Command +{ + /** @var \phpbb\user */ + protected $user; + + /** + * Constructor + * + * @param \phpbb\user $user User instance (mostly for translation) + */ + public function __construct(\phpbb\user $user) + { + $this->user = $user; + parent::__construct(); + } + + /** + * Create a styled progress bar + * + * @param int $max Max value for the progress bar + * @param SymfonyStyle $io Symfony style output decorator + * @param OutputInterface $output The output stream, used to print messages + * @param bool $message Should we display message output under the progress bar? + * @return ProgressBar + */ + public function create_progress_bar($max, SymfonyStyle $io, OutputInterface $output, $message = false) + { + $progress = $io->createProgressBar($max); + if ($output->getVerbosity() === OutputInterface::VERBOSITY_VERBOSE) + { + $progress->setFormat('[%percent:3s%%] %message%'); + $progress->setOverwrite(false); + } + else if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERY_VERBOSE) + { + $progress->setFormat('[%current:s%/%max:s%][%elapsed%/%estimated%][%memory%] %message%'); + $progress->setOverwrite(false); + } + else + { + $io->newLine(2); + $progress->setFormat( + " %current:s%/%max:s% %bar% %percent:3s%%\n" . + " " . ($message ? '%message%' : ' ') . " %elapsed:6s%/%estimated:-6s% %memory:6s%\n"); + $progress->setBarWidth(60); + } + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) + { + $progress->setEmptyBarCharacter('░'); // light shade character \u2591 + $progress->setProgressCharacter(''); + $progress->setBarCharacter('▓'); // dark shade character \u2593 + } + + return $progress; + } +} diff --git a/phpbb/console/command/config/command.php b/phpbb/console/command/config/command.php new file mode 100644 index 0000000..19f67d3 --- /dev/null +++ b/phpbb/console/command/config/command.php @@ -0,0 +1,26 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\config; + +abstract class command extends \phpbb\console\command\command +{ + /** @var \phpbb\config\config */ + protected $config; + + public function __construct(\phpbb\user $user, \phpbb\config\config $config) + { + $this->config = $config; + + parent::__construct($user); + } +} diff --git a/phpbb/console/command/config/delete.php b/phpbb/console/command/config/delete.php new file mode 100644 index 0000000..2da0801 --- /dev/null +++ b/phpbb/console/command/config/delete.php @@ -0,0 +1,66 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class delete extends command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('config:delete') + ->setDescription($this->user->lang('CLI_DESCRIPTION_DELETE_CONFIG')) + ->addArgument( + 'key', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_OPTION_NAME') + ) + ; + } + + /** + * Executes the command config:delete. + * + * Removes a configuration option + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return void + * @see \phpbb\config\config::delete() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $key = $input->getArgument('key'); + + if (isset($this->config[$key])) + { + $this->config->delete($key); + + $io->success($this->user->lang('CLI_CONFIG_DELETE_SUCCESS', $key)); + } + else + { + $io->error($this->user->lang('CLI_CONFIG_NOT_EXISTS', $key)); + } + } +} diff --git a/phpbb/console/command/config/get.php b/phpbb/console/command/config/get.php new file mode 100644 index 0000000..f065787 --- /dev/null +++ b/phpbb/console/command/config/get.php @@ -0,0 +1,75 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class get extends command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('config:get') + ->setDescription($this->user->lang('CLI_DESCRIPTION_GET_CONFIG')) + ->addArgument( + 'key', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_OPTION_NAME') + ) + ->addOption( + 'no-newline', + null, + InputOption::VALUE_NONE, + $this->user->lang('CLI_CONFIG_PRINT_WITHOUT_NEWLINE') + ) + ; + } + + /** + * Executes the command config:get. + * + * Retrieves a configuration value. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return void + * @see \phpbb\config\config::offsetGet() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $key = $input->getArgument('key'); + + if (isset($this->config[$key]) && $input->getOption('no-newline')) + { + $output->write($this->config[$key]); + } + else if (isset($this->config[$key])) + { + $output->writeln($this->config[$key]); + } + else + { + $io->error($this->user->lang('CLI_CONFIG_NOT_EXISTS', $key)); + } + } +} diff --git a/phpbb/console/command/config/increment.php b/phpbb/console/command/config/increment.php new file mode 100644 index 0000000..647380a --- /dev/null +++ b/phpbb/console/command/config/increment.php @@ -0,0 +1,73 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class increment extends command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('config:increment') + ->setDescription($this->user->lang('CLI_DESCRIPTION_INCREMENT_CONFIG')) + ->addArgument( + 'key', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_OPTION_NAME') + ) + ->addArgument( + 'increment', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_INCREMENT_BY') + ) + ->addOption( + 'dynamic', + 'd', + InputOption::VALUE_NONE, + $this->user->lang('CLI_CONFIG_CANNOT_CACHED') + ) + ; + } + + /** + * Executes the command config:increment. + * + * Increments an integer configuration value. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return void + * @see \phpbb\config\config::increment() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $key = $input->getArgument('key'); + $increment = $input->getArgument('increment'); + $use_cache = !$input->getOption('dynamic'); + + $this->config->increment($key, $increment, $use_cache); + + $io->success($this->user->lang('CLI_CONFIG_INCREMENT_SUCCESS', $key)); + } +} diff --git a/phpbb/console/command/config/set.php b/phpbb/console/command/config/set.php new file mode 100644 index 0000000..e9f7f8f --- /dev/null +++ b/phpbb/console/command/config/set.php @@ -0,0 +1,73 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class set extends command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('config:set') + ->setDescription($this->user->lang('CLI_DESCRIPTION_SET_CONFIG')) + ->addArgument( + 'key', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_OPTION_NAME') + ) + ->addArgument( + 'value', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_NEW') + ) + ->addOption( + 'dynamic', + 'd', + InputOption::VALUE_NONE, + $this->user->lang('CLI_CONFIG_CANNOT_CACHED') + ) + ; + } + + /** + * Executes the command config:set. + * + * Sets a configuration option's value. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return void + * @see \phpbb\config\config::set() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $key = $input->getArgument('key'); + $value = $input->getArgument('value'); + $use_cache = !$input->getOption('dynamic'); + + $this->config->set($key, $value, $use_cache); + + $io->success($this->user->lang('CLI_CONFIG_SET_SUCCESS', $key)); + } +} diff --git a/phpbb/console/command/config/set_atomic.php b/phpbb/console/command/config/set_atomic.php new file mode 100644 index 0000000..475d8a9 --- /dev/null +++ b/phpbb/console/command/config/set_atomic.php @@ -0,0 +1,87 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\config; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class set_atomic extends command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('config:set-atomic') + ->setDescription($this->user->lang('CLI_DESCRIPTION_SET_ATOMIC_CONFIG')) + ->addArgument( + 'key', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_OPTION_NAME') + ) + ->addArgument( + 'old', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_CURRENT') + ) + ->addArgument( + 'new', + InputArgument::REQUIRED, + $this->user->lang('CLI_CONFIG_NEW') + ) + ->addOption( + 'dynamic', + 'd', + InputOption::VALUE_NONE, + $this->user->lang('CLI_CONFIG_CANNOT_CACHED') + ) + ; + } + + /** + * Executes the command config:set-atomic. + * + * Sets a configuration option's value only if the old_value matches the + * current configuration value or the configuration value does not exist yet. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return bool True if the value was changed, false otherwise. + * @see \phpbb\config\config::set_atomic() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $key = $input->getArgument('key'); + $old_value = $input->getArgument('old'); + $new_value = $input->getArgument('new'); + $use_cache = !$input->getOption('dynamic'); + + if ($this->config->set_atomic($key, $old_value, $new_value, $use_cache)) + { + $io->success($this->user->lang('CLI_CONFIG_SET_SUCCESS', $key)); + return 0; + } + else + { + $io->error($this->user->lang('CLI_CONFIG_SET_FAILURE', $key)); + return 1; + } + } +} diff --git a/phpbb/console/command/cron/cron_list.php b/phpbb/console/command/cron/cron_list.php new file mode 100644 index 0000000..ea61e45 --- /dev/null +++ b/phpbb/console/command/cron/cron_list.php @@ -0,0 +1,94 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\cron; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class cron_list extends \phpbb\console\command\command +{ + /** @var \phpbb\cron\manager */ + protected $cron_manager; + + /** + * Constructor + * + * @param \phpbb\user $user User instance + * @param \phpbb\cron\manager $cron_manager Cron manager + */ + public function __construct(\phpbb\user $user, \phpbb\cron\manager $cron_manager) + { + $this->cron_manager = $cron_manager; + parent::__construct($user); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('cron:list') + ->setDescription($this->user->lang('CLI_DESCRIPTION_CRON_LIST')) + ; + } + + /** + * Executes the command cron:list. + * + * Prints a list of ready and unready cron jobs. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $tasks = $this->cron_manager->get_tasks(); + + if (empty($tasks)) + { + $io->error($this->user->lang('CRON_NO_TASKS')); + return; + } + + $ready_tasks = $not_ready_tasks = array(); + foreach ($tasks as $task) + { + if ($task->is_ready()) + { + $ready_tasks[] = $task->get_name(); + } + else + { + $not_ready_tasks[] = $task->get_name(); + } + } + + if (!empty($ready_tasks)) + { + $io->title($this->user->lang('TASKS_READY')); + $io->listing($ready_tasks); + } + + if (!empty($not_ready_tasks)) + { + $io->title($this->user->lang('TASKS_NOT_READY')); + $io->listing($not_ready_tasks); + } + } +} diff --git a/phpbb/console/command/cron/run.php b/phpbb/console/command/cron/run.php new file mode 100644 index 0000000..dea6493 --- /dev/null +++ b/phpbb/console/command/cron/run.php @@ -0,0 +1,171 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console\command\cron; + +use phpbb\exception\runtime_exception; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Output\OutputInterface; + +class run extends \phpbb\console\command\command +{ + /** @var \phpbb\cron\manager */ + protected $cron_manager; + + /** @var \phpbb\lock\db */ + protected $lock_db; + + /** + * Construct method + * + * @param \phpbb\user $user The user object (used to get language information) + * @param \phpbb\cron\manager $cron_manager The cron manager containing + * the cron tasks to be executed. + * @param \phpbb\lock\db $lock_db The lock for accessing database. + */ + public function __construct(\phpbb\user $user, \phpbb\cron\manager $cron_manager, \phpbb\lock\db $lock_db) + { + $this->cron_manager = $cron_manager; + $this->lock_db = $lock_db; + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('cron:run') + ->setDescription($this->user->lang('CLI_DESCRIPTION_CRON_RUN')) + ->setHelp($this->user->lang('CLI_HELP_CRON_RUN')) + ->addArgument('name', InputArgument::OPTIONAL, $this->user->lang('CLI_DESCRIPTION_CRON_RUN_ARGUMENT_1')) + ; + } + + /** + * Executes the command cron:run. + * + * Tries to acquire the cron lock, then if no argument has been given runs all ready cron tasks. + * If the cron lock can not be obtained, an error message is printed + * and the exit status is set to 1. + * If the verbose option is specified, each start of a task is printed. + * Otherwise there is no output. + * If an argument is given to the command, only the task whose name matches the + * argument will be started. If verbose option is specified, + * an info message containing the name of the task is printed. + * If no task matches the argument given, an error message is printed + * and the exit status is set to 2. + * + * @param InputInterface $input The input stream used to get the argument and verboe option. + * @param OutputInterface $output The output stream, used for printing verbose-mode and error information. + * + * @return int 0 if all is ok, 1 if a lock error occured and 2 if no task matching the argument was found. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($this->lock_db->acquire()) + { + $task_name = $input->getArgument('name'); + if ($task_name) + { + $exit_status = $this->run_one($input, $output, $task_name); + } + else + { + $exit_status = $this->run_all($input, $output); + } + + $this->lock_db->release(); + return $exit_status; + } + else + { + throw new runtime_exception('CRON_LOCK_ERROR', array(), null, 1); + } + } + + /** + * Executes all ready cron tasks. + * + * If verbose mode is set, an info message will be printed if there is no task to + * be run, or else for each starting task. + * + * @see execute + * @param InputInterface $input The input stream used to get the argument and verbose option. + * @param OutputInterface $output The output stream, used for printing verbose-mode and error information. + * @return int 0 + */ + protected function run_all(InputInterface $input, OutputInterface $output) + { + $run_tasks = $this->cron_manager->find_all_ready_tasks(); + + if ($run_tasks) + { + foreach ($run_tasks as $task) + { + if ($input->getOption('verbose')) + { + $output->writeln('' . $this->user->lang('RUNNING_TASK', $task->get_name()) . ''); + } + + $task->run(); + } + } + else + { + if ($input->getOption('verbose')) + { + $output->writeln('' . $this->user->lang('CRON_NO_TASK') . ''); + } + } + + return 0; + } + + /** + * Executes a given cron task, if it is ready. + * + * If there is a task whose name matches $task_name, it is run and 0 is returned. + * and if verbose mode is set, print an info message with the name of the task. + * If there is no task matching $task_name, the function prints an error message + * and returns with status 2. + * + * @see execute + * @param string $task_name The name of the task that should be run. + * @param InputInterface $input The input stream used to get the argument and verbose option. + * @param OutputInterface $output The output stream, used for printing verbose-mode and error information. + * @return int 0 if all is well, 2 if no task matches $task_name. + */ + protected function run_one(InputInterface $input, OutputInterface $output, $task_name) + { + $task = $this->cron_manager->find_task($task_name); + if ($task) + { + if ($input->getOption('verbose')) + { + $output->writeln('' . $this->user->lang('RUNNING_TASK', $task_name) . ''); + } + + $task->run(); + return 0; + } + else + { + throw new runtime_exception('CRON_NO_SUCH_TASK', array( $task_name), null, 2); + } + } +} diff --git a/phpbb/console/command/db/console_migrator_output_handler.php b/phpbb/console/command/db/console_migrator_output_handler.php new file mode 100644 index 0000000..568b264 --- /dev/null +++ b/phpbb/console/command/db/console_migrator_output_handler.php @@ -0,0 +1,69 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console\command\db; + +use phpbb\db\output_handler\migrator_output_handler_interface; +use phpbb\user; +use Symfony\Component\Console\Output\OutputInterface; + +class console_migrator_output_handler implements migrator_output_handler_interface +{ + /** + * User object. + * + * @var user + */ + private $user; + + /** + * Console output object. + * + * @var OutputInterface + */ + private $output; + + /** + * Constructor + * + * @param user $user User object + * @param OutputInterface $output Console output object + */ + public function __construct(user $user, OutputInterface $output) + { + $this->user = $user; + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function write($message, $verbosity) + { + if ($verbosity <= $this->output->getVerbosity()) + { + $translated_message = call_user_func_array(array($this->user, 'lang'), $message); + + if ($verbosity === migrator_output_handler_interface::VERBOSITY_NORMAL) + { + $translated_message = '' . $translated_message . ''; + } + else if ($verbosity === migrator_output_handler_interface::VERBOSITY_VERBOSE) + { + $translated_message = '' . $translated_message . ''; + } + + $this->output->writeln($translated_message); + } + } +} diff --git a/phpbb/console/command/db/list_command.php b/phpbb/console/command/db/list_command.php new file mode 100644 index 0000000..77f26dd --- /dev/null +++ b/phpbb/console/command/db/list_command.php @@ -0,0 +1,81 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\db; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class list_command extends \phpbb\console\command\db\migration_command +{ + protected function configure() + { + $this + ->setName('db:list') + ->setDescription($this->user->lang('CLI_DESCRIPTION_DB_LIST')) + ->addOption( + 'available', + 'u', + InputOption::VALUE_NONE, + $this->user->lang('CLI_MIGRATIONS_ONLY_AVAILABLE') + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $show_installed = !$input->getOption('available'); + $installed = $available = array(); + + foreach ($this->load_migrations() as $name) + { + if ($this->migrator->migration_state($name) !== false) + { + $installed[] = $name; + } + else + { + $available[] = $name; + } + } + + if ($show_installed) + { + $io->section($this->user->lang('CLI_MIGRATIONS_INSTALLED')); + + if (!empty($installed)) + { + $io->listing($installed); + } + else + { + $io->text($this->user->lang('CLI_MIGRATIONS_EMPTY')); + $io->newLine(); + } + } + + $io->section($this->user->lang('CLI_MIGRATIONS_AVAILABLE')); + if (!empty($available)) + { + $io->listing($available); + } + else + { + $io->text($this->user->lang('CLI_MIGRATIONS_EMPTY')); + $io->newLine(); + } + } +} diff --git a/phpbb/console/command/db/migrate.php b/phpbb/console/command/db/migrate.php new file mode 100644 index 0000000..4270e2d --- /dev/null +++ b/phpbb/console/command/db/migrate.php @@ -0,0 +1,86 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\db; + +use phpbb\db\output_handler\log_wrapper_migrator_output_handler; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class migrate extends \phpbb\console\command\db\migration_command +{ + /** @var \phpbb\log\log */ + protected $log; + + /** @var string phpBB root path */ + protected $phpbb_root_path; + + /** @var \phpbb\filesystem\filesystem_interface */ + protected $filesystem; + + /** @var \phpbb\language\language */ + protected $language; + + public function __construct(\phpbb\user $user, \phpbb\language\language $language, \phpbb\db\migrator $migrator, \phpbb\extension\manager $extension_manager, \phpbb\config\config $config, \phpbb\cache\service $cache, \phpbb\log\log $log, \phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path) + { + $this->language = $language; + $this->log = $log; + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + parent::__construct($user, $migrator, $extension_manager, $config, $cache); + $this->language->add_lang(array('common', 'install', 'migrator')); + } + + protected function configure() + { + $this + ->setName('db:migrate') + ->setDescription($this->language->lang('CLI_DESCRIPTION_DB_MIGRATE')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $this->migrator->set_output_handler(new log_wrapper_migrator_output_handler($this->language, new console_migrator_output_handler($this->user, $output), $this->phpbb_root_path . 'store/migrations_' . time() . '.log', $this->filesystem)); + + $this->migrator->create_migrations_table(); + + $this->cache->purge(); + + $this->load_migrations(); + $orig_version = $this->config['version']; + while (!$this->migrator->finished()) + { + try + { + $this->migrator->update(); + } + catch (\phpbb\db\migration\exception $e) + { + $io->error($e->getLocalisedMessage($this->user)); + $this->finalise_update(); + return 1; + } + } + + if ($orig_version != $this->config['version']) + { + $this->log->add('admin', ANONYMOUS, '', 'LOG_UPDATE_DATABASE', time(), array($orig_version, $this->config['version'])); + } + + $this->finalise_update(); + $io->success($this->language->lang('INLINE_UPDATE_SUCCESSFUL')); + } +} diff --git a/phpbb/console/command/db/migration_command.php b/phpbb/console/command/db/migration_command.php new file mode 100644 index 0000000..851f404 --- /dev/null +++ b/phpbb/console/command/db/migration_command.php @@ -0,0 +1,56 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\db; + +abstract class migration_command extends \phpbb\console\command\command +{ + /** @var \phpbb\db\migrator */ + protected $migrator; + + /** @var \phpbb\extension\manager */ + protected $extension_manager; + + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\cache\service */ + protected $cache; + + public function __construct(\phpbb\user $user, \phpbb\db\migrator $migrator, \phpbb\extension\manager $extension_manager, \phpbb\config\config $config, \phpbb\cache\service $cache) + { + $this->migrator = $migrator; + $this->extension_manager = $extension_manager; + $this->config = $config; + $this->cache = $cache; + parent::__construct($user); + } + + protected function load_migrations() + { + $migrations = $this->extension_manager + ->get_finder() + ->core_path('phpbb/db/migration/data/') + ->extension_directory('/migrations') + ->get_classes(); + + $this->migrator->set_migrations($migrations); + + return $this->migrator->get_migrations(); + } + + protected function finalise_update() + { + $this->cache->purge(); + $this->config->increment('assets_version', 1); + } +} diff --git a/phpbb/console/command/db/revert.php b/phpbb/console/command/db/revert.php new file mode 100644 index 0000000..3c79d8c --- /dev/null +++ b/phpbb/console/command/db/revert.php @@ -0,0 +1,74 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\db; + +use phpbb\db\output_handler\log_wrapper_migrator_output_handler; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class revert extends \phpbb\console\command\db\migrate +{ + protected function configure() + { + $this + ->setName('db:revert') + ->setDescription($this->language->lang('CLI_DESCRIPTION_DB_REVERT')) + ->addArgument( + 'name', + InputArgument::REQUIRED, + $this->language->lang('CLI_MIGRATION_NAME') + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $name = str_replace('/', '\\', $input->getArgument('name')); + + $this->migrator->set_output_handler(new log_wrapper_migrator_output_handler($this->language, new console_migrator_output_handler($this->user, $output), $this->phpbb_root_path . 'store/migrations_' . time() . '.log', $this->filesystem)); + + $this->cache->purge(); + + if (!in_array($name, $this->load_migrations())) + { + $io->error($this->language->lang('MIGRATION_NOT_VALID', $name)); + return 1; + } + else if ($this->migrator->migration_state($name) === false) + { + $io->error($this->language->lang('MIGRATION_NOT_INSTALLED', $name)); + return 1; + } + + try + { + while ($this->migrator->migration_state($name) !== false) + { + $this->migrator->revert($name); + } + } + catch (\phpbb\db\migration\exception $e) + { + $io->error($e->getLocalisedMessage($this->user)); + $this->finalise_update(); + return 1; + } + + $this->finalise_update(); + $io->success($this->language->lang('INLINE_UPDATE_SUCCESSFUL')); + } +} diff --git a/phpbb/console/command/dev/migration_tips.php b/phpbb/console/command/dev/migration_tips.php new file mode 100644 index 0000000..2ca0ddd --- /dev/null +++ b/phpbb/console/command/dev/migration_tips.php @@ -0,0 +1,64 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\dev; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class migration_tips extends \phpbb\console\command\command +{ + /** @var \phpbb\extension\manager */ + protected $extension_manager; + + public function __construct(\phpbb\user $user, \phpbb\extension\manager $extension_manager) + { + $this->extension_manager = $extension_manager; + parent::__construct($user); + } + + protected function configure() + { + $this + ->setName('dev:migration-tips') + ->setDescription($this->user->lang('CLI_DESCRIPTION_FIND_MIGRATIONS')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $migrations = $this->extension_manager->get_finder() + ->set_extensions(array()) + ->core_path('phpbb/db/migration/data/') + ->get_classes(); + $tips = $migrations; + + foreach ($migrations as $migration_class) + { + foreach ($migration_class::depends_on() as $dependency) + { + $tips_key = array_search($dependency, $tips); + if ($tips_key !== false) + { + unset($tips[$tips_key]); + } + } + } + + $output->writeln("\t\tarray("); + foreach ($tips as $migration) + { + $output->writeln("\t\t\t'{$migration}',"); + } + $output->writeln("\t\t);"); + } +} diff --git a/phpbb/console/command/extension/command.php b/phpbb/console/command/extension/command.php new file mode 100644 index 0000000..364d954 --- /dev/null +++ b/phpbb/console/command/extension/command.php @@ -0,0 +1,30 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\extension; + +abstract class command extends \phpbb\console\command\command +{ + /** @var \phpbb\extension\manager */ + protected $manager; + + /** @var \phpbb\log\log */ + protected $log; + + public function __construct(\phpbb\user $user, \phpbb\extension\manager $manager, \phpbb\log\log $log) + { + $this->manager = $manager; + $this->log = $log; + + parent::__construct($user); + } +} diff --git a/phpbb/console/command/extension/disable.php b/phpbb/console/command/extension/disable.php new file mode 100644 index 0000000..b2e10fb --- /dev/null +++ b/phpbb/console/command/extension/disable.php @@ -0,0 +1,62 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\extension; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class disable extends command +{ + protected function configure() + { + $this + ->setName('extension:disable') + ->setDescription($this->user->lang('CLI_DESCRIPTION_DISABLE_EXTENSION')) + ->addArgument( + 'extension-name', + InputArgument::REQUIRED, + $this->user->lang('CLI_EXTENSION_NAME') + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $name = $input->getArgument('extension-name'); + + if (!$this->manager->is_enabled($name)) + { + $io->error($this->user->lang('CLI_EXTENSION_DISABLED', $name)); + return 2; + } + + $this->manager->disable($name); + $this->manager->load_extensions(); + + if ($this->manager->is_enabled($name)) + { + $io->error($this->user->lang('CLI_EXTENSION_DISABLE_FAILURE', $name)); + return 1; + } + else + { + $this->log->add('admin', ANONYMOUS, '', 'LOG_EXT_DISABLE', time(), array($name)); + $io->success($this->user->lang('CLI_EXTENSION_DISABLE_SUCCESS', $name)); + return 0; + } + } +} diff --git a/phpbb/console/command/extension/enable.php b/phpbb/console/command/extension/enable.php new file mode 100644 index 0000000..a6f5b10 --- /dev/null +++ b/phpbb/console/command/extension/enable.php @@ -0,0 +1,76 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\extension; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class enable extends command +{ + protected function configure() + { + $this + ->setName('extension:enable') + ->setDescription($this->user->lang('CLI_DESCRIPTION_ENABLE_EXTENSION')) + ->addArgument( + 'extension-name', + InputArgument::REQUIRED, + $this->user->lang('CLI_EXTENSION_NAME') + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $name = $input->getArgument('extension-name'); + + if (!$this->manager->is_available($name)) + { + $io->error($this->user->lang('CLI_EXTENSION_NOT_EXIST', $name)); + return 1; + } + + $extension = $this->manager->get_extension($name); + + if (!$extension->is_enableable()) + { + $io->error($this->user->lang('CLI_EXTENSION_NOT_ENABLEABLE', $name)); + return 1; + } + + if ($this->manager->is_enabled($name)) + { + $io->error($this->user->lang('CLI_EXTENSION_ENABLED', $name)); + return 1; + } + + $this->manager->enable($name); + $this->manager->load_extensions(); + + if ($this->manager->is_enabled($name)) + { + $this->log->add('admin', ANONYMOUS, '', 'LOG_EXT_ENABLE', time(), array($name)); + $io->success($this->user->lang('CLI_EXTENSION_ENABLE_SUCCESS', $name)); + return 0; + } + else + { + $io->error($this->user->lang('CLI_EXTENSION_ENABLE_FAILURE', $name)); + return 1; + } + } +} diff --git a/phpbb/console/command/extension/purge.php b/phpbb/console/command/extension/purge.php new file mode 100644 index 0000000..25bde50 --- /dev/null +++ b/phpbb/console/command/extension/purge.php @@ -0,0 +1,55 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\extension; + +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class purge extends command +{ + protected function configure() + { + $this + ->setName('extension:purge') + ->setDescription($this->user->lang('CLI_DESCRIPTION_PURGE_EXTENSION')) + ->addArgument( + 'extension-name', + InputArgument::REQUIRED, + $this->user->lang('CLI_EXTENSION_NAME') + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $name = $input->getArgument('extension-name'); + $this->manager->purge($name); + $this->manager->load_extensions(); + + if ($this->manager->is_enabled($name)) + { + $io->error($this->user->lang('CLI_EXTENSION_PURGE_FAILURE', $name)); + return 1; + } + else + { + $this->log->add('admin', ANONYMOUS, '', 'LOG_EXT_PURGE', time(), array($name)); + $io->success($this->user->lang('CLI_EXTENSION_PURGE_SUCCESS', $name)); + return 0; + } + } +} diff --git a/phpbb/console/command/extension/show.php b/phpbb/console/command/extension/show.php new file mode 100644 index 0000000..7bad0c0 --- /dev/null +++ b/phpbb/console/command/extension/show.php @@ -0,0 +1,54 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\extension; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class show extends command +{ + protected function configure() + { + $this + ->setName('extension:show') + ->setDescription($this->user->lang('CLI_DESCRIPTION_LIST_EXTENSIONS')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $this->manager->load_extensions(); + $all = array_keys($this->manager->all_available()); + + if (empty($all)) + { + $io->note($this->user->lang('CLI_EXTENSION_NOT_FOUND')); + return 3; + } + + $enabled = array_keys($this->manager->all_enabled()); + $io->section($this->user->lang('CLI_EXTENSIONS_ENABLED')); + $io->listing($enabled); + + $disabled = array_keys($this->manager->all_disabled()); + $io->section($this->user->lang('CLI_EXTENSIONS_DISABLED')); + $io->listing($disabled); + + $purged = array_diff($all, $enabled, $disabled); + $io->section($this->user->lang('CLI_EXTENSIONS_AVAILABLE')); + $io->listing($purged); + } +} diff --git a/phpbb/console/command/fixup/fix_left_right_ids.php b/phpbb/console/command/fixup/fix_left_right_ids.php new file mode 100644 index 0000000..271b099 --- /dev/null +++ b/phpbb/console/command/fixup/fix_left_right_ids.php @@ -0,0 +1,137 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console\command\fixup; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class fix_left_right_ids extends \phpbb\console\command\command +{ + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\cache\driver\driver_interface */ + protected $cache; + + /** + * Constructor + * + * @param \phpbb\user $user User instance + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param \phpbb\cache\driver\driver_interface $cache Cache instance + */ + public function __construct(\phpbb\user $user, \phpbb\db\driver\driver_interface $db, \phpbb\cache\driver\driver_interface $cache) + { + $this->user = $user; + $this->db = $db; + $this->cache = $cache; + + parent::__construct($user); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('fixup:fix-left-right-ids') + ->setDescription($this->user->lang('CLI_DESCRIPTION_FIX_LEFT_RIGHT_IDS')) + ; + } + + /** + * Executes the command fixup:fix-left-right-ids. + * + * Repairs the tree structure of the forums and modules. + * The code is mainly borrowed from Support toolkit for phpBB Olympus + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return void + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + // Fix Left/Right IDs for the modules table + $result = $this->db->sql_query('SELECT DISTINCT(module_class) FROM ' . MODULES_TABLE); + while ($row = $this->db->sql_fetchrow($result)) + { + $i = 1; + $where = array("module_class = '" . $this->db->sql_escape($row['module_class']) . "'"); + $this->fix_ids_tree($i, 'module_id', MODULES_TABLE, 0, $where); + } + $this->db->sql_freeresult($result); + + // Fix the Left/Right IDs for the forums table + $i = 1; + $this->fix_ids_tree($i, 'forum_id', FORUMS_TABLE); + + $this->cache->purge(); + + $io->success($this->user->lang('CLI_FIXUP_FIX_LEFT_RIGHT_IDS_SUCCESS')); + } + + /** + * Item's tree structure rebuild helper + * The item is either forum or ACP/MCP/UCP module + * + * @param int $i Item id offset index + * @param string $field The key field to fix, forum_id|module_id + * @param string $table The table name to perform, FORUMS_TABLE|MODULES_TABLE + * @param int $parent_id Parent item id + * @param array $where Additional WHERE clause condition + * + * @return bool True on rebuild success, false otherwise + */ + protected function fix_ids_tree(&$i, $field, $table, $parent_id = 0, $where = array()) + { + $changes_made = false; + $sql = 'SELECT * FROM ' . $table . ' + WHERE parent_id = ' . (int) $parent_id . + ((!empty($where)) ? ' AND ' . implode(' AND ', $where) : '') . ' + ORDER BY left_id ASC'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // Update the left_id for the item + if ($row['left_id'] != $i) + { + $this->db->sql_query('UPDATE ' . $table . ' SET ' . $this->db->sql_build_array('UPDATE', array('left_id' => $i)) . " WHERE $field = " . (int) $row[$field]); + $changes_made = true; + } + $i++; + + // Go through children and update their left/right IDs + $changes_made = (($this->fix_ids_tree($i, $field, $table, $row[$field], $where)) || $changes_made) ? true : false; + + // Update the right_id for the item + if ($row['right_id'] != $i) + { + $this->db->sql_query('UPDATE ' . $table . ' SET ' . $this->db->sql_build_array('UPDATE', array('right_id' => $i)) . " WHERE $field = " . (int) $row[$field]); + $changes_made = true; + } + $i++; + } + $this->db->sql_freeresult($result); + + return $changes_made; + } +} diff --git a/phpbb/console/command/fixup/recalculate_email_hash.php b/phpbb/console/command/fixup/recalculate_email_hash.php new file mode 100644 index 0000000..6f70962 --- /dev/null +++ b/phpbb/console/command/fixup/recalculate_email_hash.php @@ -0,0 +1,76 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\fixup; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class recalculate_email_hash extends \phpbb\console\command\command +{ + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + public function __construct(\phpbb\user $user, \phpbb\db\driver\driver_interface $db) + { + $this->db = $db; + + parent::__construct($user); + } + + protected function configure() + { + $this + ->setName('fixup:recalculate-email-hash') + ->setDescription($this->user->lang('CLI_DESCRIPTION_RECALCULATE_EMAIL_HASH')) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $sql = 'SELECT user_id, user_email, user_email_hash + FROM ' . USERS_TABLE . ' + WHERE user_type <> ' . USER_IGNORE . " + AND user_email <> ''"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $user_email_hash = phpbb_email_hash($row['user_email']); + if ($user_email_hash !== $row['user_email_hash']) + { + $sql_ary = array( + 'user_email_hash' => $user_email_hash, + ); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . (int) $row['user_id']; + $this->db->sql_query($sql); + + if ($output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) + { + $io->table( + array('user_id', 'user_email', 'user_email_hash'), + array(array($row['user_id'], $row['user_email'], $user_email_hash)) + ); + } + } + } + $this->db->sql_freeresult($result); + + $io->success($this->user->lang('CLI_FIXUP_RECALCULATE_EMAIL_HASH_SUCCESS')); + } +} diff --git a/phpbb/console/command/fixup/update_hashes.php b/phpbb/console/command/fixup/update_hashes.php new file mode 100644 index 0000000..9a0e9bc --- /dev/null +++ b/phpbb/console/command/fixup/update_hashes.php @@ -0,0 +1,117 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\fixup; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Helper\ProgressBar; + +class update_hashes extends \phpbb\console\command\command +{ + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\passwords\manager */ + protected $passwords_manager; + + /** @var string Default hashing type */ + protected $default_type; + + /** + * Update_hashes constructor + * + * @param \phpbb\config\config $config + * @param \phpbb\user $user + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\passwords\manager $passwords_manager + * @param array $hashing_algorithms Hashing driver + * service collection + * @param array $defaults Default password types + */ + public function __construct(\phpbb\config\config $config, \phpbb\user $user, + \phpbb\db\driver\driver_interface $db, \phpbb\passwords\manager $passwords_manager, + $hashing_algorithms, $defaults) + { + $this->config = $config; + $this->db = $db; + + $this->passwords_manager = $passwords_manager; + + foreach ($defaults as $type) + { + if ($hashing_algorithms[$type]->is_supported()) + { + $this->default_type = $type; + break; + } + } + + parent::__construct($user); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('fixup:update-hashes') + ->setDescription($this->user->lang('CLI_DESCRIPTION_UPDATE_HASH_BCRYPT')) + ; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + // Get count to be able to display progress + $sql = 'SELECT COUNT(user_id) AS count + FROM ' . USERS_TABLE . ' + WHERE user_password ' . $this->db->sql_like_expression('$H$' . $this->db->get_any_char()) . ' + OR user_password ' . $this->db->sql_like_expression('$CP$' . $this->db->get_any_char()); + $result = $this->db->sql_query($sql); + $total_update_passwords = $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + // Create progress bar + $progress_bar = new ProgressBar($output, $total_update_passwords); + $progress_bar->start(); + + $sql = 'SELECT user_id, user_password + FROM ' . USERS_TABLE . ' + WHERE user_password ' . $this->db->sql_like_expression('$H$' . $this->db->get_any_char()) . ' + OR user_password ' . $this->db->sql_like_expression('$CP$' . $this->db->get_any_char()); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $new_hash = $this->passwords_manager->hash($row['user_password'], array($this->default_type)); + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_password = '" . $this->db->sql_escape($new_hash) . "' + WHERE user_id = " . (int) $row['user_id']; + $this->db->sql_query($sql); + $progress_bar->advance(); + } + + $this->config->set('update_hashes_last_cron', time()); + + $progress_bar->finish(); + + $output->writeln('' . $this->user->lang('CLI_FIXUP_UPDATE_HASH_BCRYPT_SUCCESS') . ''); + } +} diff --git a/phpbb/console/command/reparser/list_all.php b/phpbb/console/command/reparser/list_all.php new file mode 100644 index 0000000..a79578a --- /dev/null +++ b/phpbb/console/command/reparser/list_all.php @@ -0,0 +1,72 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console\command\reparser; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class list_all extends \phpbb\console\command\command +{ + /** + * @var string[] Names of the reparser services + */ + protected $reparser_names; + + /** + * Constructor + * + * @param \phpbb\user $user + * @param \phpbb\di\service_collection $reparsers + */ + public function __construct(\phpbb\user $user, \phpbb\di\service_collection $reparsers) + { + parent::__construct($user); + $this->reparser_names = array(); + foreach ($reparsers as $reparser) + { + // Store the names without the "text_reparser." prefix + $this->reparser_names[] = $reparser->get_name(); + } + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('reparser:list') + ->setDescription($this->user->lang('CLI_DESCRIPTION_REPARSER_LIST')) + ; + } + + /** + * Executes the command reparser:list + * + * @param InputInterface $input + * @param OutputInterface $output + * @return integer + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $io->section($this->user->lang('CLI_DESCRIPTION_REPARSER_AVAILABLE')); + $io->listing($this->reparser_names); + + return 0; + } +} diff --git a/phpbb/console/command/reparser/reparse.php b/phpbb/console/command/reparser/reparse.php new file mode 100644 index 0000000..f285977 --- /dev/null +++ b/phpbb/console/command/reparser/reparse.php @@ -0,0 +1,242 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console\command\reparser; + +use phpbb\exception\runtime_exception; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class reparse extends \phpbb\console\command\command +{ + /** + * @var InputInterface + */ + protected $input; + + /** + * @var SymfonyStyle + */ + protected $io; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var \phpbb\lock\db + */ + protected $reparse_lock; + + /** + * @var \phpbb\textreparser\manager + */ + protected $reparser_manager; + + /** + * @var \phpbb\di\service_collection + */ + protected $reparsers; + + /** + * @var array The reparser's last $current ID as values + */ + protected $resume_data; + + /** + * Constructor + * + * @param \phpbb\user $user + * @param \phpbb\lock\db $reparse_lock + * @param \phpbb\textreparser\manager $reparser_manager + * @param \phpbb\di\service_collection $reparsers + */ + public function __construct(\phpbb\user $user, \phpbb\lock\db $reparse_lock, \phpbb\textreparser\manager $reparser_manager, \phpbb\di\service_collection $reparsers) + { + require_once __DIR__ . '/../../../../includes/functions_content.php'; + + $this->reparse_lock = $reparse_lock; + $this->reparser_manager = $reparser_manager; + $this->reparsers = $reparsers; + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('reparser:reparse') + ->setDescription($this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE')) + ->addArgument('reparser-name', InputArgument::OPTIONAL, $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_ARG_1')) + ->addOption( + 'dry-run', + null, + InputOption::VALUE_NONE, + $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_DRY_RUN') + ) + ->addOption( + 'resume', + null, + InputOption::VALUE_NONE, + $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RESUME') + ) + ->addOption( + 'range-min', + null, + InputOption::VALUE_REQUIRED, + $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MIN'), + 1 + ) + ->addOption( + 'range-max', + null, + InputOption::VALUE_REQUIRED, + $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_MAX') + ) + ->addOption( + 'range-size', + null, + InputOption::VALUE_REQUIRED, + $this->user->lang('CLI_DESCRIPTION_REPARSER_REPARSE_OPT_RANGE_SIZE'), + 100 + ); + ; + } + + /** + * Executes the command reparser:reparse + * + * @param InputInterface $input + * @param OutputInterface $output + * @return integer + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->output = $output; + $this->io = new SymfonyStyle($input, $output); + + if (!$this->reparse_lock->acquire()) + { + throw new runtime_exception('REPARSE_LOCK_ERROR', array(), null, 1); + } + + $name = $input->getArgument('reparser-name'); + if ($name) + { + $name = $this->reparser_manager->find_reparser($name); + $this->reparse($name); + } + else + { + foreach ($this->reparsers as $name => $service) + { + $this->reparse($name); + } + } + + $this->io->success($this->user->lang('CLI_REPARSER_REPARSE_SUCCESS')); + + $this->reparse_lock->release(); + + return 0; + } + + /** + * Get an option value, adjusted for given reparser + * + * Will use the last saved value if --resume is set and the option was not specified + * on the command line + * + * @param string $option_name Option name + * @return integer + */ + protected function get_option($option_name) + { + // Return the option from the resume_data if applicable + if ($this->input->getOption('resume') && isset($this->resume_data[$option_name]) && !$this->input->hasParameterOption('--' . $option_name)) + { + return $this->resume_data[$option_name]; + } + + return $this->input->getOption($option_name); + } + + /** + * Reparse all text handled by given reparser within given range + * + * @param string $name Reparser service name + */ + protected function reparse($name) + { + $reparser = $this->reparsers[$name]; + $this->resume_data = $this->reparser_manager->get_resume_data($name); + if ($this->input->getOption('dry-run')) + { + $reparser->disable_save(); + } + else + { + $reparser->enable_save(); + } + + // Start at range-max if specified or at the highest ID otherwise + $max = $this->get_option('range-max'); + $min = $this->get_option('range-min'); + $size = $this->get_option('range-size'); + + // range-max has no default value, it must be computed for each reparser + if ($max === null) + { + $max = $reparser->get_max_id(); + } + + if ($max < $min) + { + return; + } + + $this->io->section($this->user->lang('CLI_REPARSER_REPARSE_REPARSING', $reparser->get_name(), $min, $max)); + + $progress = $this->create_progress_bar($max, $this->io, $this->output, true); + $progress->setMessage($this->user->lang('CLI_REPARSER_REPARSE_REPARSING_START', $reparser->get_name())); + $progress->start(); + + // Start from $max and decrement $current by $size until we reach $min + $current = $max; + while ($current >= $min) + { + $start = max($min, $current + 1 - $size); + $end = max($min, $current); + + $progress->setMessage($this->user->lang('CLI_REPARSER_REPARSE_REPARSING', $reparser->get_name(), $start, $end)); + $reparser->reparse_range($start, $end); + + $current = $start - 1; + $progress->setProgress($max + 1 - $start); + + $this->reparser_manager->update_resume_data($name, $min, $current, $size, !$this->input->getOption('dry-run')); + } + $progress->finish(); + + $this->io->newLine(2); + } +} diff --git a/phpbb/console/command/thumbnail/delete.php b/phpbb/console/command/thumbnail/delete.php new file mode 100644 index 0000000..7b95c20 --- /dev/null +++ b/phpbb/console/command/thumbnail/delete.php @@ -0,0 +1,160 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\thumbnail; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class delete extends \phpbb\console\command\command +{ + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param \config\config $config The config + * @param \phpbb\user $user The user object (used to get language information) + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param string $phpbb_root_path Root path + */ + public function __construct(\phpbb\config\config $config, \phpbb\user $user, \phpbb\db\driver\driver_interface $db, $phpbb_root_path) + { + $this->config = $config; + $this->db = $db; + $this->phpbb_root_path = $phpbb_root_path; + + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('thumbnail:delete') + ->setDescription($this->user->lang('CLI_DESCRIPTION_THUMBNAIL_DELETE')) + ; + } + + /** + * Executes the command thumbnail:delete. + * + * Deletes all existing thumbnails and updates the database accordingly. + * + * @param InputInterface $input The input stream used to get the argument and verbose option. + * @param OutputInterface $output The output stream, used for printing verbose-mode and error information. + * + * @return int 0 if all is ok, 1 if a thumbnail couldn't be deleted. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $io->section($this->user->lang('CLI_THUMBNAIL_DELETING')); + + $sql = 'SELECT COUNT(*) AS nb_missing_thumbnails + FROM ' . ATTACHMENTS_TABLE . ' + WHERE thumbnail = 1'; + $result = $this->db->sql_query($sql); + $nb_missing_thumbnails = (int) $this->db->sql_fetchfield('nb_missing_thumbnails'); + $this->db->sql_freeresult($result); + + if ($nb_missing_thumbnails === 0) + { + $io->warning($this->user->lang('CLI_THUMBNAIL_NOTHING_TO_DELETE')); + return 0; + } + + $sql = 'SELECT attach_id, physical_filename, extension, real_filename, mimetype + FROM ' . ATTACHMENTS_TABLE . ' + WHERE thumbnail = 1'; + $result = $this->db->sql_query($sql); + + $progress = $this->create_progress_bar($nb_missing_thumbnails, $io, $output); + + $progress->setMessage($this->user->lang('CLI_THUMBNAIL_DELETING')); + + $progress->start(); + + $thumbnail_deleted = array(); + $return = 0; + while ($row = $this->db->sql_fetchrow($result)) + { + $thumbnail_path = $this->phpbb_root_path . $this->config['upload_path'] . '/thumb_' . $row['physical_filename']; + + if (@unlink($thumbnail_path)) + { + $thumbnail_deleted[] = $row['attach_id']; + + if (count($thumbnail_deleted) === 250) + { + $this->commit_changes($thumbnail_deleted); + $thumbnail_deleted = array(); + } + + $progress->setMessage($this->user->lang('CLI_THUMBNAIL_DELETED', $row['real_filename'], $row['physical_filename'])); + } + else + { + $return = 1; + $progress->setMessage('' . $this->user->lang('CLI_THUMBNAIL_SKIPPED', $row['real_filename'], $row['physical_filename']) . ''); + } + + $progress->advance(); + } + $this->db->sql_freeresult($result); + + if (!empty($thumbnail_deleted)) + { + $this->commit_changes($thumbnail_deleted); + } + + $progress->finish(); + + $io->newLine(2); + $io->success($this->user->lang('CLI_THUMBNAIL_DELETING_DONE')); + + return $return; + } + + /** + * Commits the changes to the database + * + * @param array $thumbnail_deleted + */ + protected function commit_changes(array $thumbnail_deleted) + { + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET thumbnail = 0 + WHERE ' . $this->db->sql_in_set('attach_id', $thumbnail_deleted); + $this->db->sql_query($sql); + } +} diff --git a/phpbb/console/command/thumbnail/generate.php b/phpbb/console/command/thumbnail/generate.php new file mode 100644 index 0000000..1f6582b --- /dev/null +++ b/phpbb/console/command/thumbnail/generate.php @@ -0,0 +1,186 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console\command\thumbnail; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class generate extends \phpbb\console\command\command +{ + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\cache\service + */ + protected $cache; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP extension. + * + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \config\config $config The config + * @param \phpbb\user $user The user object (used to get language information) + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param \phpbb\cache\service $cache The cache service + * @param string $phpbb_root_path Root path + * @param string $php_ext PHP extension + */ + public function __construct(\phpbb\config\config $config, \phpbb\user $user, \phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, $phpbb_root_path, $php_ext) + { + $this->config = $config; + $this->db = $db; + $this->cache = $cache; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('thumbnail:generate') + ->setDescription($this->user->lang('CLI_DESCRIPTION_THUMBNAIL_GENERATE')) + ; + } + + /** + * Executes the command thumbnail:generate. + * + * Generate a thumbnail for all attachments which need one and don't have it yet. + * + * @param InputInterface $input The input stream used to get the argument and verboe option. + * @param OutputInterface $output The output stream, used for printing verbose-mode and error information. + * + * @return int 0. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $io->section($this->user->lang('CLI_THUMBNAIL_GENERATING')); + + $sql = 'SELECT COUNT(*) AS nb_missing_thumbnails + FROM ' . ATTACHMENTS_TABLE . ' + WHERE thumbnail = 0'; + $result = $this->db->sql_query($sql); + $nb_missing_thumbnails = (int) $this->db->sql_fetchfield('nb_missing_thumbnails'); + $this->db->sql_freeresult($result); + + if ($nb_missing_thumbnails === 0) + { + $io->warning($this->user->lang('CLI_THUMBNAIL_NOTHING_TO_GENERATE')); + return 0; + } + + $extensions = $this->cache->obtain_attach_extensions(true); + + $sql = 'SELECT attach_id, physical_filename, extension, real_filename, mimetype + FROM ' . ATTACHMENTS_TABLE . ' + WHERE thumbnail = 0'; + $result = $this->db->sql_query($sql); + + if (!function_exists('create_thumbnail')) + { + require($this->phpbb_root_path . 'includes/functions_posting.' . $this->php_ext); + } + + $progress = $this->create_progress_bar($nb_missing_thumbnails, $io, $output); + + $progress->setMessage($this->user->lang('CLI_THUMBNAIL_GENERATING')); + + $progress->start(); + + $thumbnail_created = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + if (isset($extensions[$row['extension']]['display_cat']) && $extensions[$row['extension']]['display_cat'] == ATTACHMENT_CATEGORY_IMAGE) + { + $source = $this->phpbb_root_path . $this->config['upload_path'] . '/' . $row['physical_filename']; + $destination = $this->phpbb_root_path . $this->config['upload_path'] . '/thumb_' . $row['physical_filename']; + + if (create_thumbnail($source, $destination, $row['mimetype'])) + { + $thumbnail_created[] = (int) $row['attach_id']; + + if (count($thumbnail_created) === 250) + { + $this->commit_changes($thumbnail_created); + $thumbnail_created = array(); + } + + $progress->setMessage($this->user->lang('CLI_THUMBNAIL_GENERATED', $row['real_filename'], $row['physical_filename'])); + } + else + { + $progress->setMessage('' . $this->user->lang('CLI_THUMBNAIL_SKIPPED', $row['real_filename'], $row['physical_filename']) . ''); + } + } + + $progress->advance(); + } + $this->db->sql_freeresult($result); + + if (!empty($thumbnail_created)) + { + $this->commit_changes($thumbnail_created); + } + + $progress->finish(); + + $io->newLine(2); + $io->success($this->user->lang('CLI_THUMBNAIL_GENERATING_DONE')); + + return 0; + } + + /** + * Commits the changes to the database + * + * @param array $thumbnail_created + */ + protected function commit_changes(array $thumbnail_created) + { + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET thumbnail = 1 + WHERE ' . $this->db->sql_in_set('attach_id', $thumbnail_created); + $this->db->sql_query($sql); + } +} diff --git a/phpbb/console/command/thumbnail/recreate.php b/phpbb/console/command/thumbnail/recreate.php new file mode 100644 index 0000000..382da29 --- /dev/null +++ b/phpbb/console/command/thumbnail/recreate.php @@ -0,0 +1,72 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ +namespace phpbb\console\command\thumbnail; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; + +class recreate extends \phpbb\console\command\command +{ + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('thumbnail:recreate') + ->setDescription($this->user->lang('CLI_DESCRIPTION_THUMBNAIL_RECREATE')) + ; + } + + /** + * Executes the command thumbnail:recreate. + * + * This command is a "macro" to execute thumbnail:delete and then thumbnail:generate. + * + * @param InputInterface $input The input stream used to get the argument and verboe option. + * @param OutputInterface $output The output stream, used for printing verbose-mode and error information. + * + * @return int 0 if all is ok, 1 if a thumbnail couldn't be deleted. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $parameters = array( + 'command' => 'thumbnail:delete' + ); + + if ($input->getOption('verbose')) + { + $parameters['-' . str_repeat('v', $output->getVerbosity() - 1)] = true; + } + + $this->getApplication()->setAutoExit(false); + + $input_delete = new ArrayInput($parameters); + $return = $this->getApplication()->run($input_delete, $output); + + if ($return === 0) + { + $parameters['command'] = 'thumbnail:generate'; + + $input_create = new ArrayInput($parameters); + $return = $this->getApplication()->run($input_create, $output); + } + + $this->getApplication()->setAutoExit(true); + + return $return; + } +} diff --git a/phpbb/console/command/update/check.php b/phpbb/console/command/update/check.php new file mode 100644 index 0000000..9ced651 --- /dev/null +++ b/phpbb/console/command/update/check.php @@ -0,0 +1,331 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\console\command\update; + +use phpbb\config\config; +use phpbb\exception\exception_interface; +use phpbb\language\language; +use phpbb\user; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\DependencyInjection\ContainerInterface; + +class check extends \phpbb\console\command\command +{ + /** @var \phpbb\config\config */ + protected $config; + + /** @var \Symfony\Component\DependencyInjection\ContainerBuilder */ + protected $phpbb_container; + /** + * @var language + */ + private $language; + + /** + * Construct method + */ + public function __construct(user $user, config $config, ContainerInterface $phpbb_container, language $language) + { + $this->config = $config; + $this->phpbb_container = $phpbb_container; + $this->language = $language; + + $this->language->add_lang(array('acp/common', 'acp/extensions')); + + parent::__construct($user); + } + + /** + * Configures the service. + * + * Sets the name and description of the command. + * + * @return null + */ + protected function configure() + { + $this + ->setName('update:check') + ->setDescription($this->language->lang('CLI_DESCRIPTION_UPDATE_CHECK')) + ->addArgument('ext-name', InputArgument::OPTIONAL, $this->language->lang('CLI_DESCRIPTION_UPDATE_CHECK_ARGUMENT_1')) + ->addOption('stability', null, InputOption::VALUE_REQUIRED, $this->language->lang('CLI_DESCRIPTION_UPDATE_CHECK_OPTION_STABILITY')) + ->addOption('cache', 'c', InputOption::VALUE_NONE, $this->language->lang('CLI_DESCRIPTION_UPDATE_CHECK_OPTION_CACHE')) + ; + } + + /** + * Executes the command. + * + * Checks if an update is available. + * If at least one is available, a message is printed and if verbose mode is set the list of possible updates is printed. + * If their is none, nothing is printed unless verbose mode is set. + * + * @param InputInterface $input Input stream, used to get the options. + * @param OutputInterface $output Output stream, used to print messages. + * @return int 0 if the board is up to date, 1 if it is not and 2 if an error occured. + * @throws \RuntimeException + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $recheck = true; + if ($input->getOption('cache')) + { + $recheck = false; + } + + $stability = null; + if ($input->getOption('stability')) + { + $stability = $input->getOption('stability'); + if (!($stability == 'stable') && !($stability == 'unstable')) + { + $io->error($this->language->lang('CLI_ERROR_INVALID_STABILITY', $stability)); + return 3; + } + } + + $ext_name = $input->getArgument('ext-name'); + if ($ext_name != null) + { + if ($ext_name == 'all') + { + return $this->check_all_ext($io, $stability, $recheck); + } + else + { + return $this->check_ext($input, $io, $stability, $recheck, $ext_name); + } + } + else + { + return $this->check_core($input, $io, $stability, $recheck); + } + } + + /** + * Check if a given extension is up to date + * + * @param InputInterface $input Input stream, used to get the options. + * @param SymfonyStyle $io IO handler, for formatted and unified IO + * @param string $stability Force a given stability + * @param bool $recheck Disallow the use of the cache + * @param string $ext_name The extension name + * @return int + */ + protected function check_ext(InputInterface $input, SymfonyStyle $io, $stability, $recheck, $ext_name) + { + try + { + $ext_manager = $this->phpbb_container->get('ext.manager'); + $md_manager = $ext_manager->create_extension_metadata_manager($ext_name); + $updates_available = $ext_manager->version_check($md_manager, $recheck, false, $stability); + + $metadata = $md_manager->get_metadata('all'); + if ($input->getOption('verbose')) + { + $io->title($md_manager->get_metadata('display-name')); + + $io->note($this->language->lang('CURRENT_VERSION') . $this->language->lang('COLON') . ' ' . $metadata['version']); + } + + if (!empty($updates_available)) + { + if ($input->getOption('verbose')) + { + $io->caution($this->language->lang('NOT_UP_TO_DATE', $metadata['name'])); + + $this->display_versions($io, $updates_available); + } + + return 1; + } + else + { + if ($input->getOption('verbose')) + { + $io->success($this->language->lang('UPDATE_NOT_NEEDED')); + } + + return 0; + } + } + catch (\RuntimeException $e) + { + $io->error($this->language->lang('EXTENSION_NOT_INSTALLED', $ext_name)); + + return 1; + } + } + + /** + * Check if the core is up to date + * + * @param InputInterface $input Input stream, used to get the options. + * @param SymfonyStyle $io IO handler, for formatted and unified IO + * @param string $stability Force a given stability + * @param bool $recheck Disallow the use of the cache + * @return int + */ + protected function check_core(InputInterface $input, SymfonyStyle $io, $stability, $recheck) + { + $version_helper = $this->phpbb_container->get('version_helper'); + $version_helper->force_stability($stability); + + $updates_available = $version_helper->get_suggested_updates($recheck); + + if ($input->getOption('verbose')) + { + $io->title('phpBB core'); + + $io->note( $this->language->lang('CURRENT_VERSION') . $this->language->lang('COLON') . ' ' . $this->config['version']); + } + + if (!empty($updates_available)) + { + $io->caution($this->language->lang('UPDATE_NEEDED')); + + if ($input->getOption('verbose')) + { + $this->display_versions($io, $updates_available); + } + + return 1; + } + else + { + if ($input->getOption('verbose')) + { + $io->success($this->language->lang('UPDATE_NOT_NEEDED')); + } + + return 0; + } + } + + /** + * Check if all the available extensions are up to date + * + * @param SymfonyStyle $io IO handler, for formatted and unified IO + * @param bool $recheck Disallow the use of the cache + * @return int + */ + protected function check_all_ext(SymfonyStyle $io, $stability, $recheck) + { + /** @var \phpbb\extension\manager $ext_manager */ + $ext_manager = $this->phpbb_container->get('ext.manager'); + + $rows = []; + + foreach ($ext_manager->all_available() as $ext_name => $ext_path) + { + $row = []; + $row[] = sprintf("%s", $ext_name); + $md_manager = $ext_manager->create_extension_metadata_manager($ext_name); + try + { + $metadata = $md_manager->get_metadata('all'); + if (isset($metadata['extra']['version-check'])) + { + try { + $updates_available = $ext_manager->version_check($md_manager, $recheck, false, $stability); + if (!empty($updates_available)) + { + $versions = array_map(function($entry) + { + return $entry['current']; + }, $updates_available); + + $row[] = sprintf("%s", $metadata['version']); + $row[] = implode(', ', $versions); + } + else + { + $row[] = sprintf("%s", $metadata['version']); + $row[] = ''; + } + } catch (\RuntimeException $e) { + $row[] = $metadata['version']; + $row[] = ''; + } + } + else + { + $row[] = $metadata['version']; + $row[] = ''; + } + } + catch (exception_interface $e) + { + $exception_message = call_user_func_array(array($this->user, 'lang'), array_merge(array($e->getMessage()), $e->get_parameters())); + $row[] = '' . $exception_message . ''; + } + catch (\RuntimeException $e) + { + $row[] = '' . $e->getMessage() . ''; + } + + $rows[] = $row; + } + + $io->table([ + $this->language->lang('EXTENSION_NAME'), + $this->language->lang('CURRENT_VERSION'), + $this->language->lang('LATEST_VERSION'), + ], $rows); + + return 0; + } + + /** + * Display the details of the available updates + * + * @param SymfonyStyle $io IO handler, for formatted and unified IO + * @param array $updates_available The list of the available updates + */ + protected function display_versions(SymfonyStyle $io, $updates_available) + { + $io->section($this->language->lang('UPDATES_AVAILABLE')); + + $rows = []; + foreach ($updates_available as $version_data) + { + $row = ['', '', '']; + $row[0] = $version_data['current']; + + if (isset($version_data['announcement'])) + { + $row[1] = $version_data['announcement']; + } + + if (isset($version_data['download'])) + { + $row[2] = $version_data['download']; + } + + $rows[] = $row; + } + + $io->table([ + $this->language->lang('VERSION'), + $this->language->lang('ANNOUNCEMENT_TOPIC'), + $this->language->lang('DOWNLOAD_LATEST'), + ], $rows); + } +} diff --git a/phpbb/console/command/user/activate.php b/phpbb/console/command/user/activate.php new file mode 100644 index 0000000..9c85718 --- /dev/null +++ b/phpbb/console/command/user/activate.php @@ -0,0 +1,218 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\console\command\user; + +use phpbb\config\config; +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\notification\manager; +use phpbb\user; +use phpbb\user_loader; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class activate extends command +{ + /** @var driver_interface */ + protected $db; + + /** @var config */ + protected $config; + + /** @var language */ + protected $language; + + /** @var log_interface */ + protected $log; + + /** @var manager */ + protected $notifications; + + /** @var user_loader */ + protected $user_loader; + + /** + * phpBB root path + * + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP extension. + * + * @var string + */ + protected $php_ext; + + /** + * Construct method + * + * @param user $user + * @param driver_interface $db + * @param config $config + * @param language $language + * @param log_interface $log + * @param manager $notifications + * @param user_loader $user_loader + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(user $user, driver_interface $db, config $config, language $language, log_interface $log, manager $notifications, user_loader $user_loader, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->config = $config; + $this->language = $language; + $this->log = $log; + $this->notifications = $notifications; + $this->user_loader = $user_loader; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->language->add_lang('acp/users'); + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('user:activate') + ->setDescription($this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE')) + ->setHelp($this->language->lang('CLI_HELP_USER_ACTIVATE')) + ->addArgument( + 'username', + InputArgument::REQUIRED, + $this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE_USERNAME') + ) + ->addOption( + 'deactivate', + 'd', + InputOption::VALUE_NONE, + $this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE_DEACTIVATE') + ) + ->addOption( + 'send-email', + null, + InputOption::VALUE_NONE, + $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY') + ) + ; + } + + /** + * Executes the command user:activate + * + * Activate (or deactivate) a user account + * + * @param InputInterface $input The input stream used to get the options + * @param OutputInterface $output The output stream, used to print messages + * + * @return int 0 if all is well, 1 if any errors occurred + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $name = $input->getArgument('username'); + $mode = ($input->getOption('deactivate')) ? 'deactivate' : 'activate'; + + $user_id = $this->user_loader->load_user_by_username($name); + $user_row = $this->user_loader->get_user($user_id); + + if ($user_row['user_id'] == ANONYMOUS) + { + $io->error($this->language->lang('NO_USER')); + return 1; + } + + // Check if the user is already active (or inactive) + if ($mode == 'activate' && $user_row['user_type'] != USER_INACTIVE) + { + $io->error($this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE_ACTIVE')); + return 1; + } + else if ($mode == 'deactivate' && $user_row['user_type'] == USER_INACTIVE) + { + $io->error($this->language->lang('CLI_DESCRIPTION_USER_ACTIVATE_INACTIVE')); + return 1; + } + + // Activate the user account + if (!function_exists('user_active_flip')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + user_active_flip($mode, $user_row['user_id']); + + // Notify the user upon activation + if ($mode == 'activate' && $this->config['require_activation'] == USER_ACTIVATION_ADMIN) + { + $this->send_notification($user_row, $input); + } + + // Log and display the result + $msg = ($mode == 'activate') ? 'USER_ADMIN_ACTIVATED' : 'USER_ADMIN_DEACTIVED'; + $log = ($mode == 'activate') ? 'LOG_USER_ACTIVE' : 'LOG_USER_INACTIVE'; + + $this->log->add('admin', ANONYMOUS, '', $log, false, array($user_row['username'])); + $this->log->add('user', ANONYMOUS, '', $log . '_USER', false, array( + 'reportee_id' => $user_row['user_id'] + )); + + $io->success($this->language->lang($msg)); + + return 0; + } + + /** + * Send account activation notification to user + * + * @param array $user_row The user data array + * @param InputInterface $input The input stream used to get the options + * @return null + */ + protected function send_notification($user_row, InputInterface $input) + { + $this->notifications->delete_notifications('notification.type.admin_activate_user', $user_row['user_id']); + + if ($input->getOption('send-email')) + { + if (!class_exists('messenger')) + { + require($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + } + + $messenger = new \messenger(false); + $messenger->template('admin_welcome_activated', $user_row['user_lang']); + $messenger->set_addresses($user_row); + $messenger->anti_abuse_headers($this->config, $this->user); + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($user_row['username'])) + ); + + $messenger->send(NOTIFY_EMAIL); + } + } +} diff --git a/phpbb/console/command/user/add.php b/phpbb/console/command/user/add.php new file mode 100644 index 0000000..c60a059 --- /dev/null +++ b/phpbb/console/command/user/add.php @@ -0,0 +1,334 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\console\command\user; + +use phpbb\config\config; +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\exception\runtime_exception; +use phpbb\language\language; +use phpbb\passwords\manager; +use phpbb\user; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; + +class add extends command +{ + /** @var array Array of interactively acquired options */ + protected $data; + + /** @var driver_interface */ + protected $db; + + /** @var config */ + protected $config; + + /** @var language */ + protected $language; + + /** @var manager */ + protected $password_manager; + + /** + * phpBB root path + * + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP extension. + * + * @var string + */ + protected $php_ext; + + /** + * Construct method + * + * @param user $user + * @param driver_interface $db + * @param config $config + * @param language $language + * @param manager $password_manager + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(user $user, driver_interface $db, config $config, language $language, manager $password_manager, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->config = $config; + $this->language = $language; + $this->password_manager = $password_manager; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->language->add_lang('ucp'); + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('user:add') + ->setDescription($this->language->lang('CLI_DESCRIPTION_USER_ADD')) + ->setHelp($this->language->lang('CLI_HELP_USER_ADD')) + ->addOption( + 'username', + 'U', + InputOption::VALUE_REQUIRED, + $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_USERNAME') + ) + ->addOption( + 'password', + 'P', + InputOption::VALUE_REQUIRED, + $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_PASSWORD') + ) + ->addOption( + 'email', + 'E', + InputOption::VALUE_REQUIRED, + $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_EMAIL') + ) + ->addOption( + 'send-email', + null, + InputOption::VALUE_NONE, + $this->language->lang('CLI_DESCRIPTION_USER_ADD_OPTION_NOTIFY') + ) + ; + } + + /** + * Executes the command user:add + * + * Adds a new user to the database. If options are not provided, it will ask for the username, password and email. + * User is added to the registered user group. Language and timezone default to $config settings. + * + * @param InputInterface $input The input stream used to get the options + * @param OutputInterface $output The output stream, used to print messages + * + * @return int 0 if all is well, 1 if any errors occurred + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + try + { + $this->validate_user_data(); + $group_id = $this->get_group_id(); + } + catch (runtime_exception $e) + { + $io->error($e->getMessage()); + return 1; + } + + $user_row = array( + 'username' => $this->data['username'], + 'user_password' => $this->password_manager->hash($this->data['new_password']), + 'user_email' => $this->data['email'], + 'group_id' => $group_id, + 'user_timezone' => $this->config['board_timezone'], + 'user_lang' => $this->config['default_lang'], + 'user_type' => USER_NORMAL, + 'user_regdate' => time(), + ); + + $user_id = (int) user_add($user_row); + + if (!$user_id) + { + $io->error($this->language->lang('AUTH_NO_PROFILE_CREATED')); + return 1; + } + + if ($input->getOption('send-email') && $this->config['email_enable']) + { + $this->send_activation_email($user_id); + } + + $io->success($this->language->lang('CLI_USER_ADD_SUCCESS', $this->data['username'])); + + return 0; + } + + /** + * Interacts with the user. + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + $helper = $this->getHelper('question'); + + $this->data = array( + 'username' => $input->getOption('username'), + 'new_password' => $input->getOption('password'), + 'email' => $input->getOption('email'), + ); + + if (!$this->data['username']) + { + $question = new Question($this->ask_user('USERNAME')); + $this->data['username'] = $helper->ask($input, $output, $question); + } + + if (!$this->data['new_password']) + { + $question = new Question($this->ask_user('PASSWORD')); + $question->setValidator(function ($value) use ($helper, $input, $output) { + $question = new Question($this->ask_user('CONFIRM_PASSWORD')); + $question->setHidden(true); + if ($helper->ask($input, $output, $question) != $value) + { + throw new runtime_exception($this->language->lang('NEW_PASSWORD_ERROR')); + } + return $value; + }); + $question->setHidden(true); + $question->setMaxAttempts(5); + + $this->data['new_password'] = $helper->ask($input, $output, $question); + } + + if (!$this->data['email']) + { + $question = new Question($this->ask_user('EMAIL_ADDRESS')); + $this->data['email'] = $helper->ask($input, $output, $question); + } + } + + /** + * Validate the submitted user data + * + * @throws runtime_exception if any data fails validation + * @return null + */ + protected function validate_user_data() + { + if (!function_exists('validate_data')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $error = validate_data($this->data, array( + 'username' => array( + array('string', false, $this->config['min_name_chars'], $this->config['max_name_chars']), + array('username', '')), + 'new_password' => array( + array('string', false, $this->config['min_pass_chars'], $this->config['max_pass_chars']), + array('password')), + 'email' => array( + array('string', false, 6, 60), + array('user_email')), + )); + + if ($error) + { + throw new runtime_exception(implode("\n", array_map(array($this->language, 'lang'), $error))); + } + } + + /** + * Get the group id + * + * Go and find in the database the group_id corresponding to 'REGISTERED' + * + * @throws runtime_exception if the group id does not exist in database. + * @return null + */ + protected function get_group_id() + { + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape('REGISTERED') . "' + AND group_type = " . GROUP_SPECIAL; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row || !$row['group_id']) + { + throw new runtime_exception($this->language->lang('NO_GROUP')); + } + + return $row['group_id']; + } + + /** + * Send account activation email + * + * @param int $user_id The new user's id + * @return null + */ + protected function send_activation_email($user_id) + { + switch ($this->config['require_activation']) + { + case USER_ACTIVATION_SELF: + $email_template = 'user_welcome_inactive'; + $user_actkey = gen_rand_string(mt_rand(6, 10)); + break; + case USER_ACTIVATION_ADMIN: + $email_template = 'admin_welcome_inactive'; + $user_actkey = gen_rand_string(mt_rand(6, 10)); + break; + default: + $email_template = 'user_welcome'; + $user_actkey = ''; + break; + } + + if (!class_exists('messenger')) + { + require($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + } + + $messenger = new \messenger(false); + $messenger->template($email_template, $this->user->lang_name); + $messenger->to($this->data['email'], $this->data['username']); + $messenger->anti_abuse_headers($this->config, $this->user); + $messenger->assign_vars(array( + 'WELCOME_MSG' => htmlspecialchars_decode($this->language->lang('WELCOME_SUBJECT', $this->config['sitename'])), + 'USERNAME' => htmlspecialchars_decode($this->data['username']), + 'PASSWORD' => htmlspecialchars_decode($this->data['new_password']), + 'U_ACTIVATE' => generate_board_url() . "/ucp.{$this->php_ext}?mode=activate&u=$user_id&k=$user_actkey") + ); + + $messenger->send(NOTIFY_EMAIL); + } + + /** + * Helper to translate questions to the user + * + * @param string $key The language key + * @return string The language key translated with a colon and space appended + */ + protected function ask_user($key) + { + return $this->language->lang($key) . $this->language->lang('COLON') . ' '; + } +} diff --git a/phpbb/console/command/user/delete.php b/phpbb/console/command/user/delete.php new file mode 100644 index 0000000..8593541 --- /dev/null +++ b/phpbb/console/command/user/delete.php @@ -0,0 +1,170 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\console\command\user; + +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\user; +use phpbb\user_loader; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Style\SymfonyStyle; + +class delete extends command +{ + /** @var driver_interface */ + protected $db; + + /** @var language */ + protected $language; + + /** @var log_interface */ + protected $log; + + /** @var user_loader */ + protected $user_loader; + + /** + * phpBB root path + * + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP extension. + * + * @var string + */ + protected $php_ext; + + /** + * Construct method + * + * @param user $user + * @param driver_interface $db + * @param language $language + * @param log_interface $log + * @param user_loader $user_loader + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(user $user, driver_interface $db, language $language, log_interface $log, user_loader $user_loader, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->language = $language; + $this->log = $log; + $this->user_loader = $user_loader; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->language->add_lang('acp/users'); + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('user:delete') + ->setDescription($this->language->lang('CLI_DESCRIPTION_USER_DELETE')) + ->addArgument( + 'username', + InputArgument::REQUIRED, + $this->language->lang('CLI_DESCRIPTION_USER_DELETE_USERNAME') + ) + ->addOption( + 'delete-posts', + null, + InputOption::VALUE_NONE, + $this->language->lang('CLI_DESCRIPTION_USER_DELETE_OPTION_POSTS') + ) + ; + } + + /** + * Executes the command user:delete + * + * Deletes a user from the database. An option to delete the user's posts + * is available, by default posts will be retained. + * + * @param InputInterface $input The input stream used to get the options + * @param OutputInterface $output The output stream, used to print messages + * + * @return int 0 if all is well, 1 if any errors occurred + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $name = $input->getArgument('username'); + $mode = ($input->getOption('delete-posts')) ? 'remove' : 'retain'; + + if ($name) + { + $io = new SymfonyStyle($input, $output); + + $user_id = $this->user_loader->load_user_by_username($name); + $user_row = $this->user_loader->get_user($user_id); + + if ($user_row['user_id'] == ANONYMOUS) + { + $io->error($this->language->lang('NO_USER')); + return 1; + } + + if (!function_exists('user_delete')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + user_delete($mode, $user_row['user_id'], $user_row['username']); + + $this->log->add('admin', ANONYMOUS, '', 'LOG_USER_DELETED', false, array($user_row['username'])); + + $io->success($this->language->lang('USER_DELETED')); + } + + return 0; + } + + /** + * Interacts with the user. + * Confirm they really want to delete the account...last chance! + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + $helper = $this->getHelper('question'); + + $question = new ConfirmationQuestion( + $this->language->lang('CLI_USER_DELETE_CONFIRM', $input->getArgument('username')), + false + ); + + if (!$helper->ask($input, $output, $question)) + { + $input->setArgument('username', false); + } + } +} diff --git a/phpbb/console/command/user/reclean.php b/phpbb/console/command/user/reclean.php new file mode 100644 index 0000000..1a89f13 --- /dev/null +++ b/phpbb/console/command/user/reclean.php @@ -0,0 +1,158 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\console\command\user; + +use phpbb\console\command\command; +use phpbb\db\driver\driver_interface; +use phpbb\language\language; +use phpbb\user; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; + +class reclean extends command +{ + /** @var driver_interface */ + protected $db; + + /** @var language */ + protected $language; + + /** @var int A count of the number of re-cleaned user names */ + protected $processed; + + /** @var ProgressBar */ + protected $progress; + + /** + * Construct method + * + * @param user $user + * @param driver_interface $db + * @param language $language + */ + public function __construct(user $user, driver_interface $db, language $language) + { + $this->db = $db; + $this->language = $language; + + parent::__construct($user); + } + + /** + * Sets the command name and description + * + * @return null + */ + protected function configure() + { + $this + ->setName('user:reclean') + ->setDescription($this->language->lang('CLI_DESCRIPTION_USER_RECLEAN')) + ->setHelp($this->language->lang('CLI_HELP_USER_RECLEAN')) + ; + } + + /** + * Executes the command user:reclean + * + * Cleans user names that are unclean. + * + * @param InputInterface $input The input stream used to get the options + * @param OutputInterface $output The output stream, used to print messages + * + * @return int 0 if all is well, 1 if any errors occurred + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + $io->section($this->language->lang('CLI_USER_RECLEAN_START')); + + $this->processed = 0; + + $this->progress = $this->create_progress_bar($this->get_count(), $io, $output); + $this->progress->setMessage($this->language->lang('CLI_USER_RECLEAN_START')); + $this->progress->start(); + + $stage = 0; + while ($stage !== true) + { + $stage = $this->reclean_usernames($stage); + } + + $this->progress->finish(); + + $io->newLine(2); + $io->success($this->language->lang('CLI_USER_RECLEAN_DONE', $this->processed)); + + return 0; + } + + /** + * Re-clean user names + * Only user names that are unclean will be re-cleaned + * + * @param int $start An offset index + * @return bool|int Return the next offset index or true if all records have been processed. + */ + protected function reclean_usernames($start = 0) + { + $limit = 500; + $i = 0; + + $this->db->sql_transaction('begin'); + + $sql = 'SELECT user_id, username, username_clean FROM ' . USERS_TABLE; + $result = $this->db->sql_query_limit($sql, $limit, $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $i++; + $username_clean = $this->db->sql_escape(utf8_clean_string($row['username'])); + + if ($username_clean != $row['username_clean']) + { + $sql = 'UPDATE ' . USERS_TABLE . " + SET username_clean = '$username_clean' + WHERE user_id = {$row['user_id']}"; + $this->db->sql_query($sql); + + $this->processed++; + } + + $this->progress->advance(); + } + $this->db->sql_freeresult($result); + + $this->db->sql_transaction('commit'); + + return ($i < $limit) ? true : $start + $i; + } + + /** + * Get the count of users in the database + * + * @return int + */ + protected function get_count() + { + $sql = 'SELECT COUNT(user_id) AS count FROM ' . USERS_TABLE; + $result = $this->db->sql_query($sql); + $count = (int) $this->db->sql_fetchfield('count'); + $this->db->sql_freeresult($result); + + return $count; + } +} diff --git a/phpbb/console/exception_subscriber.php b/phpbb/console/exception_subscriber.php new file mode 100644 index 0000000..b240993 --- /dev/null +++ b/phpbb/console/exception_subscriber.php @@ -0,0 +1,65 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\console; + +use phpbb\exception\exception_interface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class exception_subscriber implements EventSubscriberInterface +{ + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * Construct method + * + * @param \phpbb\language\language $language Language object + */ + public function __construct(\phpbb\language\language $language) + { + $this->language = $language; + } + + /** + * This listener is run when the ConsoleEvents::EXCEPTION event is triggered. + * It translate the exception message. If din debug mode the original exception is embedded. + * + * @param ConsoleExceptionEvent $event + */ + public function on_exception(ConsoleExceptionEvent $event) + { + $original_exception = $event->getException(); + + if ($original_exception instanceof exception_interface) + { + $parameters = array_merge(array($original_exception->getMessage()), $original_exception->get_parameters()); + $message = call_user_func_array(array($this->language, 'lang'), $parameters); + + $exception = new \RuntimeException($message , $original_exception->getCode(), $original_exception); + + $event->setException($exception); + } + } + + static public function getSubscribedEvents() + { + return array( + ConsoleEvents::EXCEPTION => 'on_exception', + ); + } +} diff --git a/phpbb/content_visibility.php b/phpbb/content_visibility.php new file mode 100644 index 0000000..f023e07 --- /dev/null +++ b/phpbb/content_visibility.php @@ -0,0 +1,885 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** +* phpbb_visibility +* Handle fetching and setting the visibility for topics and posts +*/ +class content_visibility +{ + /** + * Database object + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Auth object + * @var \phpbb\auth\auth + */ + protected $auth; + + /** + * config object + * @var \phpbb\config\config + */ + protected $config; + + /** + * Event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $phpbb_dispatcher; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP Extension + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\auth\auth $auth Auth object + * @param \phpbb\config\config $config Config object + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object + * @param \phpbb\db\driver\driver_interface $db Database object + * @param \phpbb\user $user User object + * @param string $phpbb_root_path Root path + * @param string $php_ext PHP Extension + * @param string $forums_table Forums table name + * @param string $posts_table Posts table name + * @param string $topics_table Topics table name + * @param string $users_table Users table name + */ + public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\db\driver\driver_interface $db, \phpbb\user $user, $phpbb_root_path, $php_ext, $forums_table, $posts_table, $topics_table, $users_table) + { + $this->auth = $auth; + $this->config = $config; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->db = $db; + $this->user = $user; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->forums_table = $forums_table; + $this->posts_table = $posts_table; + $this->topics_table = $topics_table; + $this->users_table = $users_table; + } + + /** + * Can the current logged-in user soft-delete posts? + * + * @param $forum_id int Forum ID whose permissions to check + * @param $poster_id int Poster ID of the post in question + * @param $post_locked bool Is the post locked? + * @return bool + */ + public function can_soft_delete($forum_id, $poster_id, $post_locked) + { + if ($this->auth->acl_get('m_softdelete', $forum_id)) + { + return true; + } + else if ($this->auth->acl_get('f_softdelete', $forum_id) && $poster_id == $this->user->data['user_id'] && !$post_locked) + { + return true; + } + + return false; + } + + /** + * Get the topics post count or the forums post/topic count based on permissions + * + * @param $mode string One of topic_posts, forum_posts or forum_topics + * @param $data array Array with the topic/forum data to calculate from + * @param $forum_id int The forum id is used for permission checks + * @return int Number of posts/topics the user can see in the topic/forum + */ + public function get_count($mode, $data, $forum_id) + { + if (!$this->auth->acl_get('m_approve', $forum_id)) + { + return (int) $data[$mode . '_approved']; + } + + return (int) $data[$mode . '_approved'] + (int) $data[$mode . '_unapproved'] + (int) $data[$mode . '_softdeleted']; + } + + + /** + * Check topic/post visibility for a given forum ID + * + * Note: Read permissions are not checked. + * + * @param $mode string Either "topic" or "post" + * @param $forum_id int The forum id is used for permission checks + * @param $data array Array with item information to check visibility + * @return bool True if the item is visible, false if not + */ + public function is_visible($mode, $forum_id, $data) + { + $is_visible = $this->auth->acl_get('m_approve', $forum_id) || $data[$mode . '_visibility'] == ITEM_APPROVED; + + /** + * Allow changing the result of calling is_visible + * + * @event core.phpbb_content_visibility_is_visible + * @var bool is_visible Default visibility condition, to be modified by extensions if needed. + * @var string mode Either "topic" or "post" + * @var int forum_id Forum id of the current item + * @var array data Array of item information + * @since 3.2.2-RC1 + */ + $vars = array( + 'is_visible', + 'mode', + 'forum_id', + 'data', + ); + extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_is_visible', compact($vars))); + + return $is_visible; + } + + /** + * Create topic/post visibility SQL for a given forum ID + * + * Note: Read permissions are not checked. + * + * @param $mode string Either "topic" or "post" + * @param $forum_id int The forum id is used for permission checks + * @param $table_alias string Table alias to prefix in SQL queries + * @return string The appropriate combination SQL logic for topic/post_visibility + */ + public function get_visibility_sql($mode, $forum_id, $table_alias = '') + { + $where_sql = ''; + + $get_visibility_sql_overwrite = false; + + /** + * Allow changing the result of calling get_visibility_sql + * + * @event core.phpbb_content_visibility_get_visibility_sql_before + * @var string where_sql Extra visibility conditions. It must end with either an SQL "AND" or an "OR" + * @var string mode Either "topic" or "post" depending on the query this is being used in + * @var array forum_id The forum id in which the search is made. + * @var string table_alias Table alias to prefix in SQL queries + * @var mixed get_visibility_sql_overwrite If a string, forces the function to return get_forums_visibility_sql_overwrite after executing the event + * If false, get_visibility_sql continues normally + * It must be either boolean or string + * @since 3.1.4-RC1 + */ + $vars = array( + 'where_sql', + 'mode', + 'forum_id', + 'table_alias', + 'get_visibility_sql_overwrite', + ); + extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_get_visibility_sql_before', compact($vars))); + + if ($get_visibility_sql_overwrite !== false) + { + return $get_visibility_sql_overwrite; + } + + if ($this->auth->acl_get('m_approve', $forum_id)) + { + $where_sql .= '1 = 1'; + } + else + { + $where_sql .= $table_alias . $mode . '_visibility = ' . ITEM_APPROVED; + } + + return '(' . $where_sql . ')'; + } + + /** + * Create topic/post visibility SQL for a set of forums + * + * Note: Read permissions are not checked. Forums without read permissions + * should not be in $forum_ids + * + * @param $mode string Either "topic" or "post" + * @param $forum_ids array Array of forum ids which the posts/topics are limited to + * @param $table_alias string Table alias to prefix in SQL queries + * @return string The appropriate combination SQL logic for topic/post_visibility + */ + public function get_forums_visibility_sql($mode, $forum_ids = array(), $table_alias = '') + { + $where_sql = ''; + + $approve_forums = array_keys($this->auth->acl_getf('m_approve', true)); + if (!empty($forum_ids) && !empty($approve_forums)) + { + $approve_forums = array_intersect($forum_ids, $approve_forums); + $forum_ids = array_diff($forum_ids, $approve_forums); + } + + $get_forums_visibility_sql_overwrite = false; + /** + * Allow changing the result of calling get_forums_visibility_sql + * + * @event core.phpbb_content_visibility_get_forums_visibility_before + * @var string where_sql Extra visibility conditions. It must end with either an SQL "AND" or an "OR" + * @var string mode Either "topic" or "post" depending on the query this is being used in + * @var array forum_ids Array of forum ids which the posts/topics are limited to + * @var string table_alias Table alias to prefix in SQL queries + * @var array approve_forums Array of forums where the user has m_approve permissions + * @var mixed get_forums_visibility_sql_overwrite If a string, forces the function to return get_forums_visibility_sql_overwrite after executing the event + * If false, get_forums_visibility_sql continues normally + * It must be either boolean or string + * @since 3.1.3-RC1 + */ + $vars = array( + 'where_sql', + 'mode', + 'forum_ids', + 'table_alias', + 'approve_forums', + 'get_forums_visibility_sql_overwrite', + ); + extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_get_forums_visibility_before', compact($vars))); + + if ($get_forums_visibility_sql_overwrite !== false) + { + return $get_forums_visibility_sql_overwrite; + } + + // Moderator can view all posts/topics in the moderated forums + $where_sql .= '(' . $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums, false, true) . ' OR '; + // Normal user can view approved items only + $where_sql .= '(' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ' + AND ' . $this->db->sql_in_set($table_alias . 'forum_id', $forum_ids, false, true) . '))'; + + return '(' . $where_sql . ')'; + } + + /** + * Create topic/post visibility SQL for all forums on the board + * + * Note: Read permissions are not checked. Forums without read permissions + * should be in $exclude_forum_ids + * + * @param $mode string Either "topic" or "post" + * @param $exclude_forum_ids array Array of forum ids which are excluded + * @param $table_alias string Table alias to prefix in SQL queries + * @return string The appropriate combination SQL logic for topic/post_visibility + */ + public function get_global_visibility_sql($mode, $exclude_forum_ids = array(), $table_alias = '') + { + $where_sqls = array(); + + $approve_forums = array_diff(array_keys($this->auth->acl_getf('m_approve', true)), $exclude_forum_ids); + + $visibility_sql_overwrite = null; + + /** + * Allow changing the result of calling get_global_visibility_sql + * + * @event core.phpbb_content_visibility_get_global_visibility_before + * @var array where_sqls Array of extra visibility conditions. Will be joined by imploding with "OR". + * @var string mode Either "topic" or "post" depending on the query this is being used in + * @var array exclude_forum_ids Array of forum ids the current user doesn't have access to + * @var string table_alias Table alias to prefix in SQL queries + * @var array approve_forums Array of forums where the user has m_approve permissions + * @var string visibility_sql_overwrite If not empty, forces the function to return visibility_sql_overwrite after executing the event + * @since 3.1.3-RC1 + */ + $vars = array( + 'where_sqls', + 'mode', + 'exclude_forum_ids', + 'table_alias', + 'approve_forums', + 'visibility_sql_overwrite', + ); + extract($this->phpbb_dispatcher->trigger_event('core.phpbb_content_visibility_get_global_visibility_before', compact($vars))); + + if ($visibility_sql_overwrite) + { + return $visibility_sql_overwrite; + } + + // Include approved items in all forums but the excluded + $where_sqls[] = '(' . $this->db->sql_in_set($table_alias . 'forum_id', $exclude_forum_ids, true, true) . ' + AND ' . $table_alias . $mode . '_visibility = ' . ITEM_APPROVED . ')'; + + // If user has moderator permissions, add everything in the moderated forums + if (count($approve_forums)) + { + $where_sqls[] = $this->db->sql_in_set($table_alias . 'forum_id', $approve_forums); + } + + return '(' . implode(' OR ', $where_sqls) . ')'; + } + + /** + * Change visibility status of one post or all posts of a topic + * + * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE} + * @param $post_id mixed Post ID or array of post IDs to act on, + * if it is empty, all posts of topic_id will be modified + * @param $topic_id int Topic where $post_id is found + * @param $forum_id int Forum where $topic_id is found + * @param $user_id int User performing the action + * @param $time int Timestamp when the action is performed + * @param $reason string Reason why the visibility was changed. + * @param $is_starter bool Is this the first post of the topic changed? + * @param $is_latest bool Is this the last post of the topic changed? + * @param $limit_visibility mixed Limit updating per topic_id to a certain visibility + * @param $limit_delete_time mixed Limit updating per topic_id to a certain deletion time + * @return array Changed post data, empty array if an error occurred. + */ + public function set_post_visibility($visibility, $post_id, $topic_id, $forum_id, $user_id, $time, $reason, $is_starter, $is_latest, $limit_visibility = false, $limit_delete_time = false) + { + if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE))) + { + return array(); + } + + if ($post_id) + { + if (is_array($post_id)) + { + $where_sql = $this->db->sql_in_set('post_id', array_map('intval', $post_id)); + } + else + { + $where_sql = 'post_id = ' . (int) $post_id; + } + $where_sql .= ' AND topic_id = ' . (int) $topic_id; + } + else + { + $where_sql = 'topic_id = ' . (int) $topic_id; + + // Limit the posts to a certain visibility and deletion time + // This allows us to only restore posts, that were approved + // when the topic got soft deleted. So previous soft deleted + // and unapproved posts are still soft deleted/unapproved + if ($limit_visibility !== false) + { + $where_sql .= ' AND post_visibility = ' . (int) $limit_visibility; + } + + if ($limit_delete_time !== false) + { + $where_sql .= ' AND post_delete_time = ' . (int) $limit_delete_time; + } + } + + $sql = 'SELECT poster_id, post_id, post_postcount, post_visibility + FROM ' . $this->posts_table . ' + WHERE ' . $where_sql; + $result = $this->db->sql_query($sql); + + $post_ids = $poster_postcounts = $postcounts = $postcount_visibility = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['post_id']; + + if ($row['post_visibility'] != $visibility) + { + if ($row['post_postcount'] && !isset($poster_postcounts[(int) $row['poster_id']])) + { + $poster_postcounts[(int) $row['poster_id']] = 1; + } + else if ($row['post_postcount']) + { + $poster_postcounts[(int) $row['poster_id']]++; + } + + if (!isset($postcount_visibility[$row['post_visibility']])) + { + $postcount_visibility[$row['post_visibility']] = 1; + } + else + { + $postcount_visibility[$row['post_visibility']]++; + } + } + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return array(); + } + + if (!function_exists('truncate_string')) + { + include($this->phpbb_root_path . 'includes/functions_content.' . $this->php_ext); + } + + $data = array( + 'post_visibility' => (int) $visibility, + 'post_delete_user' => (int) $user_id, + 'post_delete_time' => ((int) $time) ?: time(), + 'post_delete_reason' => truncate_string($reason, 255, 255, false), + ); + /** + * Perform actions right before the query to change post visibility + * + * @event core.set_post_visibility_before_sql + * @var int visibility Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE} + * @var array post_id Array containing all post IDs to be modified. If blank, all posts within the topic are modified. + * @var int topic_id Topic of the post IDs to be modified. + * @var int forum_id Forum ID that the topic_id resides in. + * @var int user_id User ID doing this action. + * @var int time Timestamp of this action. + * @var string reason Reason specified by the user for this change. + * @var bool is_starter Are we changing the topic's starter? + * @var bool is_latest Are we changing the topic's latest post? + * @var array data The data array for this action. + * @since 3.1.10-RC1 + * @changed 3.2.2-RC1 Use time instead of non-existent timestamp + */ + $vars = array( + 'visibility', + 'post_id', + 'topic_id', + 'forum_id', + 'user_id', + 'time', + 'reason', + 'is_starter', + 'is_latest', + 'data', + ); + extract($this->phpbb_dispatcher->trigger_event('core.set_post_visibility_before_sql', compact($vars))); + $sql = 'UPDATE ' . $this->posts_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE ' . $this->db->sql_in_set('post_id', $post_ids); + $this->db->sql_query($sql); + + // Group the authors by post count, to reduce the number of queries + foreach ($poster_postcounts as $poster_id => $num_posts) + { + $postcounts[$num_posts][] = $poster_id; + } + + // Update users postcounts + foreach ($postcounts as $num_posts => $poster_ids) + { + if (in_array($visibility, array(ITEM_REAPPROVE, ITEM_DELETED))) + { + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = 0 + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . ' + AND user_posts < ' . $num_posts; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = user_posts - ' . $num_posts . ' + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids) . ' + AND user_posts >= ' . $num_posts; + $this->db->sql_query($sql); + } + else + { + $sql = 'UPDATE ' . $this->users_table . ' + SET user_posts = user_posts + ' . $num_posts . ' + WHERE ' . $this->db->sql_in_set('user_id', $poster_ids); + $this->db->sql_query($sql); + } + } + + $update_topic_postcount = true; + + // Sync the first/last topic information if needed + if (!$is_starter && $is_latest) + { + if (!function_exists('update_post_information')) + { + include($this->phpbb_root_path . 'includes/functions_posting.' . $this->php_ext); + } + + // update_post_information can only update the last post info ... + if ($topic_id) + { + update_post_information('topic', $topic_id, false); + } + if ($forum_id) + { + update_post_information('forum', $forum_id, false); + } + } + else if ($is_starter && $topic_id) + { + if (!function_exists('sync')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + + // ... so we need to use sync, if the first post is changed. + // The forum is resynced recursive by sync() itself. + sync('topic', 'topic_id', $topic_id, true); + + // sync recalculates the topic replies and forum posts by itself, so we don't do that. + $update_topic_postcount = false; + } + + $topic_update_array = array(); + // Update the topic's reply count and the forum's post count + if ($update_topic_postcount) + { + $field_alias = array( + ITEM_APPROVED => 'posts_approved', + ITEM_UNAPPROVED => 'posts_unapproved', + ITEM_DELETED => 'posts_softdeleted', + ITEM_REAPPROVE => 'posts_unapproved', + ); + $cur_posts = array_fill_keys($field_alias, 0); + + foreach ($postcount_visibility as $post_visibility => $visibility_posts) + { + $cur_posts[$field_alias[(int) $post_visibility]] += $visibility_posts; + } + + $sql_ary = array(); + $recipient_field = $field_alias[$visibility]; + + foreach ($cur_posts as $field => $count) + { + // Decrease the count for the old statuses. + if ($count && $field != $recipient_field) + { + $sql_ary[$field] = " - $count"; + } + } + // Add up the count from all statuses excluding the recipient status. + $count_increase = array_sum(array_diff($cur_posts, array($recipient_field))); + + if ($count_increase) + { + $sql_ary[$recipient_field] = " + $count_increase"; + } + + if (count($sql_ary)) + { + $forum_sql = array(); + + foreach ($sql_ary as $field => $value_change) + { + $topic_update_array[] = 'topic_' . $field . ' = topic_' . $field . $value_change; + $forum_sql[] = 'forum_' . $field . ' = forum_' . $field . $value_change; + } + + $sql = 'UPDATE ' . $this->forums_table . ' + SET ' . implode(', ', $forum_sql) . ' + WHERE forum_id = ' . (int) $forum_id; + $this->db->sql_query($sql); + } + } + + if ($post_id) + { + $sql = 'SELECT 1 AS has_attachments + FROM ' . POSTS_TABLE . ' + WHERE topic_id = ' . (int) $topic_id . ' + AND post_attachment = 1 + AND post_visibility = ' . ITEM_APPROVED . ' + AND ' . $this->db->sql_in_set('post_id', $post_id, true); + $result = $this->db->sql_query_limit($sql, 1); + + $has_attachment = (bool) $this->db->sql_fetchfield('has_attachments'); + $this->db->sql_freeresult($result); + + if ($has_attachment && $visibility == ITEM_APPROVED) + { + $topic_update_array[] = 'topic_attachment = 1'; + } + else if (!$has_attachment && $visibility != ITEM_APPROVED) + { + $topic_update_array[] = 'topic_attachment = 0'; + } + } + + if (!empty($topic_update_array)) + { + // Update the number for replies and posts, and update the attachments flag + $sql = 'UPDATE ' . $this->topics_table . ' + SET ' . implode(', ', $topic_update_array) . ' + WHERE topic_id = ' . (int) $topic_id; + $this->db->sql_query($sql); + } + /** + * Perform actions after all steps to changing post visibility + * + * @event core.set_post_visibility_after + * @var int visibility Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE} + * @var array post_id Array containing all post IDs to be modified. If blank, all posts within the topic are modified. + * @var int topic_id Topic of the post IDs to be modified. + * @var int forum_id Forum ID that the topic_id resides in. + * @var int user_id User ID doing this action. + * @var int time Timestamp of this action. + * @var string reason Reason specified by the user for this change. + * @var bool is_starter Are we changing the topic's starter? + * @var bool is_latest Are we changing the topic's latest post? + * @var array data The data array for this action. + * @since 3.1.10-RC1 + * @changed 3.2.2-RC1 Use time instead of non-existent timestamp + */ + $vars = array( + 'visibility', + 'post_id', + 'topic_id', + 'forum_id', + 'user_id', + 'time', + 'reason', + 'is_starter', + 'is_latest', + 'data', + ); + extract($this->phpbb_dispatcher->trigger_event('core.set_post_visibility_after', compact($vars))); + return $data; + } + + /** + * Set topic visibility + * + * Allows approving (which is akin to undeleting/restore) or soft deleting an entire topic. + * Calls set_post_visibility as needed. + * + * Note: By default, when a soft deleted topic is restored. Only posts that + * were approved at the time of soft deleting, are being restored. + * Same applies to soft deleting. Only approved posts will be marked + * as soft deleted. + * If you want to update all posts, use the force option. + * + * @param $visibility int Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE} + * @param $topic_id mixed Topic ID to act on + * @param $forum_id int Forum where $topic_id is found + * @param $user_id int User performing the action + * @param $time int Timestamp when the action is performed + * @param $reason string Reason why the visibilty was changed. + * @param $force_update_all bool Force to update all posts within the topic + * @return array Changed topic data, empty array if an error occured. + */ + public function set_topic_visibility($visibility, $topic_id, $forum_id, $user_id, $time, $reason, $force_update_all = false) + { + if (!in_array($visibility, array(ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE))) + { + return array(); + } + + if (!$force_update_all) + { + $sql = 'SELECT topic_visibility, topic_delete_time + FROM ' . $this->topics_table . ' + WHERE topic_id = ' . (int) $topic_id; + $result = $this->db->sql_query($sql); + $original_topic_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$original_topic_data) + { + // The topic does not exist... + return array(); + } + } + + if (!function_exists('truncate_string')) + { + include($this->phpbb_root_path . 'includes/functions_content.' . $this->php_ext); + } + + // Note, we do not set a reason for the posts, just for the topic + $data = array( + 'topic_visibility' => (int) $visibility, + 'topic_delete_user' => (int) $user_id, + 'topic_delete_time' => ((int) $time) ?: time(), + 'topic_delete_reason' => truncate_string($reason, 255, 255, false), + ); + /** + * Perform actions right before the query to change topic visibility + * + * @event core.set_topic_visibility_before_sql + * @var int visibility Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE} + * @var int topic_id Topic of the post IDs to be modified. + * @var int forum_id Forum ID that the topic_id resides in. + * @var int user_id User ID doing this action. + * @var int time Timestamp of this action. + * @var string reason Reason specified by the user for this change. + * @var bool force_update_all Force an update on all posts within the topic, regardless of their current approval state. + * @var array data The data array for this action. + * @since 3.1.10-RC1 + * @changed 3.2.2-RC1 Use time instead of non-existent timestamp + */ + $vars = array( + 'visibility', + 'topic_id', + 'forum_id', + 'user_id', + 'time', + 'reason', + 'force_update_all', + 'data', + ); + extract($this->phpbb_dispatcher->trigger_event('core.set_topic_visibility_before_sql', compact($vars))); + $sql = 'UPDATE ' . $this->topics_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $data) . ' + WHERE topic_id = ' . (int) $topic_id; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + return array(); + } + + if (!$force_update_all && $original_topic_data['topic_delete_time'] && $original_topic_data['topic_visibility'] == ITEM_DELETED && $visibility == ITEM_APPROVED) + { + // If we're restoring a topic we only restore posts, that were soft deleted through the topic soft deletion. + $this->set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility'], $original_topic_data['topic_delete_time']); + } + else if (!$force_update_all && $original_topic_data['topic_visibility'] == ITEM_APPROVED && $visibility == ITEM_DELETED) + { + // If we're soft deleting a topic we only mark approved posts as soft deleted. + $this->set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true, $original_topic_data['topic_visibility']); + } + else + { + $this->set_post_visibility($visibility, false, $topic_id, $forum_id, $user_id, $time, '', true, true); + } + /** + * Perform actions after all steps to changing topic visibility + * + * @event core.set_topic_visibility_after + * @var int visibility Element of {ITEM_APPROVED, ITEM_DELETED, ITEM_REAPPROVE} + * @var int topic_id Topic of the post IDs to be modified. + * @var int forum_id Forum ID that the topic_id resides in. + * @var int user_id User ID doing this action. + * @var int time Timestamp of this action. + * @var string reason Reason specified by the user for this change. + * @var bool force_update_all Force an update on all posts within the topic, regardless of their current approval state. + * @var array data The data array for this action. + * @since 3.1.10-RC1 + * @changed 3.2.2-RC1 Use time instead of non-existent timestamp + */ + $vars = array( + 'visibility', + 'topic_id', + 'forum_id', + 'user_id', + 'time', + 'reason', + 'force_update_all', + 'data', + ); + extract($this->phpbb_dispatcher->trigger_event('core.set_topic_visibility_after', compact($vars))); + return $data; + } + + /** + * Add post to topic and forum statistics + * + * @param $data array Contains information from the topics table about given topic + * @param &$sql_data array Populated with the SQL changes, may be empty at call time + * @return null + */ + public function add_post_to_statistic($data, &$sql_data) + { + $sql_data[$this->topics_table] = (($sql_data[$this->topics_table]) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved + 1'; + + $sql_data[$this->forums_table] = (($sql_data[$this->forums_table]) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved + 1'; + + if ($data['post_postcount']) + { + $sql_data[$this->users_table] = (($sql_data[$this->users_table]) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts + 1'; + } + + $this->config->increment('num_posts', 1, false); + } + + /** + * Remove post from topic and forum statistics + * + * @param $data array Contains information from the topics table about given topic + * @param &$sql_data array Populated with the SQL changes, may be empty at call time + * @return null + */ + public function remove_post_from_statistic($data, &$sql_data) + { + if ($data['post_visibility'] == ITEM_APPROVED) + { + $sql_data[$this->topics_table] = ((!empty($sql_data[$this->topics_table])) ? $sql_data[$this->topics_table] . ', ' : '') . 'topic_posts_approved = topic_posts_approved - 1'; + $sql_data[$this->forums_table] = ((!empty($sql_data[$this->forums_table])) ? $sql_data[$this->forums_table] . ', ' : '') . 'forum_posts_approved = forum_posts_approved - 1'; + + if ($data['post_postcount']) + { + $sql_data[$this->users_table] = ((!empty($sql_data[$this->users_table])) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts - 1'; + } + + $this->config->increment('num_posts', -1, false); + } + else if ($data['post_visibility'] == ITEM_UNAPPROVED || $data['post_visibility'] == ITEM_REAPPROVE) + { + $sql_data[FORUMS_TABLE] = (($sql_data[FORUMS_TABLE]) ? $sql_data[FORUMS_TABLE] . ', ' : '') . 'forum_posts_unapproved = forum_posts_unapproved - 1'; + $sql_data[TOPICS_TABLE] = (($sql_data[TOPICS_TABLE]) ? $sql_data[TOPICS_TABLE] . ', ' : '') . 'topic_posts_unapproved = topic_posts_unapproved - 1'; + } + else if ($data['post_visibility'] == ITEM_DELETED) + { + $sql_data[FORUMS_TABLE] = (($sql_data[FORUMS_TABLE]) ? $sql_data[FORUMS_TABLE] . ', ' : '') . 'forum_posts_softdeleted = forum_posts_softdeleted - 1'; + $sql_data[TOPICS_TABLE] = (($sql_data[TOPICS_TABLE]) ? $sql_data[TOPICS_TABLE] . ', ' : '') . 'topic_posts_softdeleted = topic_posts_softdeleted - 1'; + } + } + + /** + * Remove topic from forum statistics + * + * @param $data array Post and topic data + * @param &$sql_data array Populated with the SQL changes, may be empty at call time + * @return null + */ + public function remove_topic_from_statistic($data, &$sql_data) + { + if ($data['topic_visibility'] == ITEM_APPROVED) + { + $sql_data[FORUMS_TABLE] .= 'forum_posts_approved = forum_posts_approved - 1, forum_topics_approved = forum_topics_approved - 1'; + + if ($data['post_postcount']) + { + $sql_data[$this->users_table] = ((!empty($sql_data[$this->users_table])) ? $sql_data[$this->users_table] . ', ' : '') . 'user_posts = user_posts - 1'; + } + } + else if ($data['topic_visibility'] == ITEM_UNAPPROVED || $data['post_visibility'] == ITEM_REAPPROVE) + { + $sql_data[FORUMS_TABLE] .= 'forum_posts_unapproved = forum_posts_unapproved - 1, forum_topics_unapproved = forum_topics_unapproved - 1'; + } + else if ($data['topic_visibility'] == ITEM_DELETED) + { + $sql_data[FORUMS_TABLE] .= 'forum_posts_softdeleted = forum_posts_softdeleted - 1, forum_topics_softdeleted = forum_topics_softdeleted - 1'; + } + + } +} diff --git a/phpbb/controller/exception.php b/phpbb/controller/exception.php new file mode 100644 index 0000000..e227c7c --- /dev/null +++ b/phpbb/controller/exception.php @@ -0,0 +1,21 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\controller; + +/** +* Controller exception class +*/ +class exception extends \phpbb\exception\runtime_exception +{ +} diff --git a/phpbb/controller/helper.php b/phpbb/controller/helper.php new file mode 100644 index 0000000..664b4f4 --- /dev/null +++ b/phpbb/controller/helper.php @@ -0,0 +1,195 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\controller; + +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +/** +* Controller helper class, contains methods that do things for controllers +*/ +class helper +{ + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * config object + * @var \phpbb\config\config + */ + protected $config; + + /* @var \phpbb\symfony_request */ + protected $symfony_request; + + /* @var \phpbb\request\request_interface */ + protected $request; + + /** + * @var \phpbb\routing\helper + */ + protected $routing_helper; + + /** + * Constructor + * + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + * @param \phpbb\config\config $config Config object + * @param \phpbb\symfony_request $symfony_request Symfony Request object + * @param \phpbb\request\request_interface $request phpBB request object + * @param \phpbb\routing\helper $routing_helper Helper to generate the routes + */ + public function __construct(\phpbb\template\template $template, \phpbb\user $user, \phpbb\config\config $config, \phpbb\symfony_request $symfony_request, \phpbb\request\request_interface $request, \phpbb\routing\helper $routing_helper) + { + $this->template = $template; + $this->user = $user; + $this->config = $config; + $this->symfony_request = $symfony_request; + $this->request = $request; + $this->routing_helper = $routing_helper; + } + + /** + * Automate setting up the page and creating the response object. + * + * @param string $template_file The template handle to render + * @param string $page_title The title of the page to output + * @param int $status_code The status code to be sent to the page header + * @param bool $display_online_list Do we display online users list + * @param int $item_id Restrict online users to item id + * @param string $item Restrict online users to a certain session item, e.g. forum for session_forum_id + * @param bool $send_headers Whether headers should be sent by page_header(). Defaults to false for controllers. + * + * @return Response object containing rendered page + */ + public function render($template_file, $page_title = '', $status_code = 200, $display_online_list = false, $item_id = 0, $item = 'forum', $send_headers = false) + { + page_header($page_title, $display_online_list, $item_id, $item, $send_headers); + + $this->template->set_filenames(array( + 'body' => $template_file, + )); + + page_footer(true, false, false); + + $headers = !empty($this->user->data['is_bot']) ? array('X-PHPBB-IS-BOT' => 'yes') : array(); + + return new Response($this->template->assign_display('body'), $status_code, $headers); + } + + /** + * Generate a URL to a route + * + * @param string $route Name of the route to travel + * @param array $params String or array of additional url parameters + * @param bool $is_amp Is url using & (true) or & (false) + * @param string|bool $session_id Possibility to use a custom session id instead of the global one + * @param bool|string $reference_type The type of reference to be generated (one of the constants) + * @return string The URL already passed through append_sid() + */ + public function route($route, array $params = array(), $is_amp = true, $session_id = false, $reference_type = UrlGeneratorInterface::ABSOLUTE_PATH) + { + return $this->routing_helper->route($route, $params, $is_amp, $session_id, $reference_type); + } + + /** + * Output an error, effectively the same thing as trigger_error + * + * @param string $message The error message + * @param int $code The error code (e.g. 404, 500, 503, etc.) + * @return Response A Response instance + * + * @deprecated 3.1.3 (To be removed: 3.3.0) Use exceptions instead. + */ + public function error($message, $code = 500) + { + return $this->message($message, array(), 'INFORMATION', $code); + } + + /** + * Output a message + * + * In case of an error, please throw an exception instead + * + * @param string $message The message to display (must be a language variable) + * @param array $parameters The parameters to use with the language var + * @param string $title Title for the message (must be a language variable) + * @param int $code The HTTP status code (e.g. 404, 500, 503, etc.) + * @return Response A Response instance + */ + public function message($message, array $parameters = array(), $title = 'INFORMATION', $code = 200) + { + array_unshift($parameters, $message); + $message_text = call_user_func_array(array($this->user, 'lang'), $parameters); + $message_title = $this->user->lang($title); + + if ($this->request->is_ajax()) + { + global $refresh_data; + + return new JsonResponse( + array( + 'MESSAGE_TITLE' => $message_title, + 'MESSAGE_TEXT' => $message_text, + 'S_USER_WARNING' => false, + 'S_USER_NOTICE' => false, + 'REFRESH_DATA' => (!empty($refresh_data)) ? $refresh_data : null + ), + $code + ); + } + + $this->template->assign_vars(array( + 'MESSAGE_TEXT' => $message_text, + 'MESSAGE_TITLE' => $message_title, + )); + + return $this->render('message_body.html', $message_title, $code); + } + + /** + * Assigns automatic refresh time meta tag in template + * + * @param int $time time in seconds, when redirection should occur + * @param string $url the URL where the user should be redirected + * @return null + */ + public function assign_meta_refresh_var($time, $url) + { + $this->template->assign_vars(array( + 'META' => '', + )); + } + + /** + * Return the current url + * + * @return string + */ + public function get_current_url() + { + return generate_board_url(true) . $this->request->escape($this->symfony_request->getRequestUri(), true); + } +} diff --git a/phpbb/controller/resolver.php b/phpbb/controller/resolver.php new file mode 100644 index 0000000..f8dffc1 --- /dev/null +++ b/phpbb/controller/resolver.php @@ -0,0 +1,179 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\controller; + +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; + +/** +* Controller manager class +*/ +class resolver implements ControllerResolverInterface +{ + /** + * ContainerInterface object + * @var ContainerInterface + */ + protected $container; + + /** + * phpbb\template\template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * Request type cast helper object + * @var \phpbb\request\type_cast_helper + */ + protected $type_cast_helper; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * Construct method + * + * @param ContainerInterface $container ContainerInterface object + * @param string $phpbb_root_path Relative path to phpBB root + * @param \phpbb\template\template $template + */ + public function __construct(ContainerInterface $container, $phpbb_root_path, \phpbb\template\template $template = null) + { + $this->container = $container; + $this->template = $template; + $this->type_cast_helper = new \phpbb\request\type_cast_helper(); + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Load a controller callable + * + * @param \Symfony\Component\HttpFoundation\Request $request Symfony Request object + * @return bool|Callable Callable or false + * @throws \phpbb\controller\exception + */ + public function getController(Request $request) + { + $controller = $request->attributes->get('_controller'); + + if (!$controller) + { + throw new \phpbb\controller\exception('CONTROLLER_NOT_SPECIFIED'); + } + + // Require a method name along with the service name + if (stripos($controller, ':') === false) + { + throw new \phpbb\controller\exception('CONTROLLER_METHOD_NOT_SPECIFIED'); + } + + list($service, $method) = explode(':', $controller); + + if (!$this->container->has($service)) + { + throw new \phpbb\controller\exception('CONTROLLER_SERVICE_UNDEFINED', array($service)); + } + + $controller_object = $this->container->get($service); + + /* + * If this is an extension controller, we'll try to automatically set + * the style paths for the extension (the ext author can change them + * if necessary). + */ + $controller_dir = explode('\\', get_class($controller_object)); + + // 0 vendor, 1 extension name, ... + if (!is_null($this->template) && isset($controller_dir[1])) + { + $controller_style_dir = 'ext/' . $controller_dir[0] . '/' . $controller_dir[1] . '/styles'; + + if (is_dir($this->phpbb_root_path . $controller_style_dir)) + { + $this->template->set_style(array($controller_style_dir, 'styles')); + } + } + + return array($controller_object, $method); + } + + /** + * Dependencies should be specified in the service definition and can be + * then accessed in __construct(). Arguments are sent through the URL path + * and should match the parameters of the method you are using as your + * controller. + * + * @param \Symfony\Component\HttpFoundation\Request $request Symfony Request object + * @param mixed $controller A callable (controller class, method) + * @return array An array of arguments to pass to the controller + * @throws \phpbb\controller\exception + */ + public function getArguments(Request $request, $controller) + { + // At this point, $controller should be a callable + if (is_array($controller)) + { + list($object, $method) = $controller; + $mirror = new \ReflectionMethod($object, $method); + } + else if (is_object($controller) && !$controller instanceof \Closure) + { + $mirror = new \ReflectionObject($controller); + $mirror = $mirror->getMethod('__invoke'); + } + else + { + $mirror = new \ReflectionFunction($controller); + } + + $arguments = array(); + $parameters = $mirror->getParameters(); + $attributes = $request->attributes->all(); + foreach ($parameters as $param) + { + if (array_key_exists($param->name, $attributes)) + { + if (is_string($attributes[$param->name])) + { + $value = $attributes[$param->name]; + $this->type_cast_helper->set_var($value, $attributes[$param->name], 'string', true, false); + $arguments[] = $value; + } + else + { + $arguments[] = $attributes[$param->name]; + } + } + else if ($param->getClass() && $param->getClass()->isInstance($request)) + { + $arguments[] = $request; + } + else if ($param->isDefaultValueAvailable()) + { + $arguments[] = $param->getDefaultValue(); + } + else + { + throw new \phpbb\controller\exception('CONTROLLER_ARGUMENT_VALUE_MISSING', array($param->getPosition() + 1, get_class($object) . ':' . $method, $param->name)); + } + } + + return $arguments; + } +} diff --git a/phpbb/cron/manager.php b/phpbb/cron/manager.php new file mode 100644 index 0000000..9bd30a0 --- /dev/null +++ b/phpbb/cron/manager.php @@ -0,0 +1,147 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron; + +/** +* Cron manager class. +* +* Finds installed cron tasks, stores task objects, provides task selection. +*/ +class manager +{ + /** + * Set of \phpbb\cron\task\wrapper objects. + * Array holding all tasks that have been found. + * + * @var array + */ + protected $tasks = array(); + + protected $phpbb_root_path; + protected $php_ext; + + /** + * Constructor. Loads all available tasks. + * + * @param array|\Traversable $tasks Provides an iterable set of task names + * @param string $phpbb_root_path Relative path to phpBB root + * @param string $php_ext PHP file extension + */ + public function __construct($tasks, $phpbb_root_path, $php_ext) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->load_tasks($tasks); + } + + /** + * Loads tasks given by name, wraps them + * and puts them into $this->tasks. + * + * @param array|\Traversable $tasks Array of instances of \phpbb\cron\task\task + * + * @return null + */ + public function load_tasks($tasks) + { + foreach ($tasks as $task) + { + $this->tasks[] = $this->wrap_task($task); + } + } + + /** + * Finds a task that is ready to run. + * + * If several tasks are ready, any one of them could be returned. + * + * If no tasks are ready, null is returned. + * + * @return \phpbb\cron\task\wrapper|null + */ + public function find_one_ready_task() + { + shuffle($this->tasks); + foreach ($this->tasks as $task) + { + if ($task->is_ready()) + { + return $task; + } + } + return null; + } + + /** + * Finds all tasks that are ready to run. + * + * @return array List of tasks which are ready to run (wrapped in \phpbb\cron\task\wrapper). + */ + public function find_all_ready_tasks() + { + $tasks = array(); + foreach ($this->tasks as $task) + { + if ($task->is_ready()) + { + $tasks[] = $task; + } + } + return $tasks; + } + + /** + * Finds a task by name. + * + * If there is no task with the specified name, null is returned. + * + * Web runner uses this method to resolve names to tasks. + * + * @param string $name Name of the task to look up. + * @return \phpbb\cron\task\wrapper A wrapped task corresponding to the given name, or null. + */ + public function find_task($name) + { + foreach ($this->tasks as $task) + { + if ($task->get_name() == $name) + { + return $task; + } + } + return null; + } + + /** + * Find all tasks and return them. + * + * @return array List of all tasks. + */ + public function get_tasks() + { + return $this->tasks; + } + + /** + * Wraps a task inside an instance of \phpbb\cron\task\wrapper. + * + * @param \phpbb\cron\task\task $task The task. + * @return \phpbb\cron\task\wrapper The wrapped task. + */ + public function wrap_task(\phpbb\cron\task\task $task) + { + return new \phpbb\cron\task\wrapper($task, $this->phpbb_root_path, $this->php_ext); + } +} diff --git a/phpbb/cron/task/base.php b/phpbb/cron/task/base.php new file mode 100644 index 0000000..57c9912 --- /dev/null +++ b/phpbb/cron/task/base.php @@ -0,0 +1,72 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task; + +/** +* Cron task base class. Provides sensible defaults for cron tasks +* and partially implements cron task interface, making writing cron tasks easier. +* +* At a minimum, subclasses must override the run() method. +* +* Cron tasks need not inherit from this base class. If desired, +* they may implement cron task interface directly. +*/ +abstract class base implements \phpbb\cron\task\task +{ + private $name; + + /** + * Returns the name of the task. + * + * @return string Name of wrapped task. + */ + public function get_name() + { + return $this->name; + } + + /** + * Sets the name of the task. + * + * @param string $name The task name + */ + public function set_name($name) + { + $this->name = $name; + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * For example, a cron task that prunes forums can only run when + * forum pruning is enabled. + * + * @return bool + */ + public function is_runnable() + { + return true; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * @return bool + */ + public function should_run() + { + return true; + } +} diff --git a/phpbb/cron/task/core/prune_all_forums.php b/phpbb/cron/task/core/prune_all_forums.php new file mode 100644 index 0000000..5005f5b --- /dev/null +++ b/phpbb/cron/task/core/prune_all_forums.php @@ -0,0 +1,94 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Prune all forums cron task. +* +* It is intended to be invoked from system cron. +* This task will find all forums for which pruning is enabled, and will +* prune all forums as necessary. +*/ +class prune_all_forums extends \phpbb\cron\task\base +{ + protected $phpbb_root_path; + protected $php_ext; + protected $config; + protected $db; + + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP file extension + * @param \phpbb\config\config $config The config + * @param \phpbb\db\driver\driver_interface $db The db connection + */ + public function __construct($phpbb_root_path, $php_ext, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->db = $db; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('auto_prune')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + + $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, enable_shadow_prune, prune_shadow_days, prune_shadow_freq, prune_shadow_next, forum_flags, prune_freq + FROM ' . FORUMS_TABLE; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['enable_prune'] && $row['prune_next'] < time()) + { + if ($row['prune_days']) + { + auto_prune($row['forum_id'], 'posted', $row['forum_flags'], $row['prune_days'], $row['prune_freq']); + } + + if ($row['prune_viewed']) + { + auto_prune($row['forum_id'], 'viewed', $row['forum_flags'], $row['prune_viewed'], $row['prune_freq']); + } + } + if ($row['enable_shadow_prune'] && $row['prune_shadow_next'] < time() && $row['prune_shadow_days']) + { + auto_prune($row['forum_id'], 'shadow', $row['forum_flags'], $row['prune_shadow_days'], $row['prune_shadow_freq']); + } + } + $this->db->sql_freeresult($result); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * This cron task will only run when system cron is utilised. + * + * @return bool + */ + public function is_runnable() + { + return (bool) $this->config['use_system_cron']; + } +} diff --git a/phpbb/cron/task/core/prune_forum.php b/phpbb/cron/task/core/prune_forum.php new file mode 100644 index 0000000..abf91ae --- /dev/null +++ b/phpbb/cron/task/core/prune_forum.php @@ -0,0 +1,159 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Prune one forum cron task. +* +* It is intended to be used when cron is invoked via web. +* This task can decide whether it should be run using data obtained by viewforum +* code, without making additional database queries. +*/ +class prune_forum extends \phpbb\cron\task\base implements \phpbb\cron\task\parametrized +{ + protected $phpbb_root_path; + protected $php_ext; + protected $config; + protected $db; + + /** + * If $forum_data is given, it is assumed to contain necessary information + * about a single forum that is to be pruned. + * + * If $forum_data is not given, forum id will be retrieved via $request->variable() + * and a database query will be performed to load the necessary information + * about the forum. + */ + protected $forum_data; + + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext PHP file extension + * @param \phpbb\config\config $config The config + * @param \phpbb\db\driver\driver_interface $db The db connection + */ + public function __construct($phpbb_root_path, $php_ext, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->db = $db; + } + + /** + * Manually set forum data. + * + * @param array $forum_data Information about a forum to be pruned. + */ + public function set_forum_data($forum_data) + { + $this->forum_data = $forum_data; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('auto_prune')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + + if ($this->forum_data['prune_days']) + { + auto_prune($this->forum_data['forum_id'], 'posted', $this->forum_data['forum_flags'], $this->forum_data['prune_days'], $this->forum_data['prune_freq']); + } + + if ($this->forum_data['prune_viewed']) + { + auto_prune($this->forum_data['forum_id'], 'viewed', $this->forum_data['forum_flags'], $this->forum_data['prune_viewed'], $this->forum_data['prune_freq']); + } + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * This cron task will not run when system cron is utilised, as in + * such cases prune_all_forums task would run instead. + * + * Additionally, this task must be given the forum data, either via + * the constructor or parse_parameters method. + * + * @return bool + */ + public function is_runnable() + { + return !$this->config['use_system_cron'] && $this->forum_data; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * Forum pruning interval is specified in the forum data. + * + * @return bool + */ + public function should_run() + { + return $this->forum_data['enable_prune'] && $this->forum_data['prune_next'] < time(); + } + + /** + * Returns parameters of this cron task as an array. + * The array has one key, f, whose value is id of the forum to be pruned. + * + * @return array + */ + public function get_parameters() + { + return array('f' => $this->forum_data['forum_id']); + } + + /** + * Parses parameters found in $request, which is an instance of + * \phpbb\request\request_interface. + * + * It is expected to have a key f whose value is id of the forum to be pruned. + * + * @param \phpbb\request\request_interface $request Request object. + * + * @return null + */ + public function parse_parameters(\phpbb\request\request_interface $request) + { + $this->forum_data = null; + if ($request->is_set('f')) + { + $forum_id = $request->variable('f', 0); + + $sql = 'SELECT forum_id, prune_next, enable_prune, prune_days, prune_viewed, forum_flags, prune_freq + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + $this->forum_data = $row; + } + } + } +} diff --git a/phpbb/cron/task/core/prune_notifications.php b/phpbb/cron/task/core/prune_notifications.php new file mode 100644 index 0000000..ffa7e17 --- /dev/null +++ b/phpbb/cron/task/core/prune_notifications.php @@ -0,0 +1,61 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Prune notifications cron task. +*/ +class prune_notifications extends \phpbb\cron\task\base +{ + protected $config; + protected $notification_manager; + + /** + * Constructor. + * + * @param \phpbb\config\config $config The config + * @param \phpbb\notification\manager $notification_manager Notification manager + */ + public function __construct(\phpbb\config\config $config, \phpbb\notification\manager $notification_manager) + { + $this->config = $config; + $this->notification_manager = $notification_manager; + } + + /** + * {@inheritdoc} + */ + public function run() + { + // time minus expire days in seconds + $timestamp = time() - ($this->config['read_notification_expire_days'] * 60 * 60 * 24); + $this->notification_manager->prune_notifications($timestamp); + } + + /** + * {@inheritdoc} + */ + public function is_runnable() + { + return (bool) $this->config['read_notification_expire_days']; + } + + /** + * {@inheritdoc} + */ + public function should_run() + { + return $this->config['read_notification_last_gc'] < time() - $this->config['read_notification_gc']; + } +} diff --git a/phpbb/cron/task/core/prune_shadow_topics.php b/phpbb/cron/task/core/prune_shadow_topics.php new file mode 100644 index 0000000..0ab59f9 --- /dev/null +++ b/phpbb/cron/task/core/prune_shadow_topics.php @@ -0,0 +1,200 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Prune one forum of its shadow topics cron task. +* +* It is intended to be used when cron is invoked via web. +* This task can decide whether it should be run using data obtained by viewforum +* code, without making additional database queries. +*/ +class prune_shadow_topics extends \phpbb\cron\task\base implements \phpbb\cron\task\parametrized +{ + protected $phpbb_root_path; + protected $php_ext; + protected $config; + protected $db; + protected $log; + protected $user; + + /** + * If $forum_data is given, it is assumed to contain necessary information + * about a single forum that is to be pruned. + * + * If $forum_data is not given, forum id will be retrieved via $request->variable() + * and a database query will be performed to load the necessary information + * about the forum. + */ + protected $forum_data; + + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext PHP file extension + * @param \phpbb\config\config $config The config + * @param \phpbb\db\driver\driver_interface $db The db connection + * @param \phpbb\log\log $log The phpBB log system + * @param \phpbb\user $user The phpBB user object + */ + public function __construct($phpbb_root_path, $php_ext, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\log\log $log, \phpbb\user $user) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->db = $db; + $this->log = $log; + $this->user = $user; + } + + /** + * Manually set forum data. + * + * @param array $forum_data Information about a forum to be pruned. + */ + public function set_forum_data($forum_data) + { + $this->forum_data = $forum_data; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('auto_prune')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + + if ($this->forum_data['prune_shadow_days']) + { + $this->auto_prune_shadow_topics($this->forum_data['forum_id'], 'shadow', $this->forum_data['forum_flags'], $this->forum_data['prune_shadow_days'], $this->forum_data['prune_shadow_freq']); + } + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * This cron task will not run when system cron is utilised, as in + * such cases prune_all_forums task would run instead. + * + * Additionally, this task must be given the forum data, either via + * the constructor or parse_parameters method. + * + * @return bool + */ + public function is_runnable() + { + return !$this->config['use_system_cron'] && $this->forum_data; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * Forum pruning interval is specified in the forum data. + * + * @return bool + */ + public function should_run() + { + return $this->forum_data['enable_shadow_prune'] && $this->forum_data['prune_shadow_next'] < time(); + } + + /** + * Returns parameters of this cron task as an array. + * The array has one key, f, whose value is id of the forum to be pruned. + * + * @return array + */ + public function get_parameters() + { + return array('f' => $this->forum_data['forum_id']); + } + + /** + * Parses parameters found in $request, which is an instance of + * \phpbb\request\request_interface. + * + * It is expected to have a key f whose value is id of the forum to be pruned. + * + * @param \phpbb\request\request_interface $request Request object. + * + * @return null + */ + public function parse_parameters(\phpbb\request\request_interface $request) + { + $this->forum_data = null; + if ($request->is_set('f')) + { + $forum_id = $request->variable('f', 0); + + $sql = 'SELECT forum_id, prune_shadow_next, enable_shadow_prune, prune_shadow_days, forum_flags, prune_shadow_freq + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + $this->forum_data = $row; + } + } + } + + /** + * Automatically prune shadow topics + * Based on fuunction auto_prune() + * @param int $forum_id Forum ID of forum that should be pruned + * @param string $prune_mode Prune mode + * @param int $prune_flags Prune flags + * @param int $prune_days Prune date in days + * @param int $prune_freq Prune frequency + * @return null + */ + protected function auto_prune_shadow_topics($forum_id, $prune_mode, $prune_flags, $prune_days, $prune_freq) + { + $sql = 'SELECT forum_name + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $this->db->sql_query($sql, 3600); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + $prune_date = time() - ($prune_days * 86400); + $next_prune = time() + ($prune_freq * 86400); + + prune($forum_id, $prune_mode, $prune_date, $prune_flags, true); + + $sql = 'UPDATE ' . FORUMS_TABLE . " + SET prune_shadow_next = $next_prune + WHERE forum_id = $forum_id"; + $this->db->sql_query($sql); + + $user_id = (empty($this->user->data)) ? ANONYMOUS : $this->user->data['user_id']; + $user_ip = (empty($this->user->ip)) ? '' : $this->user->ip; + + $this->log->add('admin', $user_id, $user_ip, 'LOG_PRUNE_SHADOW', false, array($row['forum_name'])); + } + + return; + } +} diff --git a/phpbb/cron/task/core/queue.php b/phpbb/cron/task/core/queue.php new file mode 100644 index 0000000..eca69a5 --- /dev/null +++ b/phpbb/cron/task/core/queue.php @@ -0,0 +1,81 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Queue cron task. Sends email and jabber messages queued by other scripts. +*/ +class queue extends \phpbb\cron\task\base +{ + protected $phpbb_root_path; + protected $php_ext; + protected $cache_dir; + protected $config; + + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext PHP file extension + * @param \phpbb\config\config $config The config + * @param string $cache_dir phpBB cache directory + */ + public function __construct($phpbb_root_path, $php_ext, \phpbb\config\config $config, $cache_dir) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + $this->cache_dir = $cache_dir; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!class_exists('queue')) + { + include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + } + $queue = new \queue(); + $queue->process(); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * Queue task is only run if the email queue (file) exists. + * + * @return bool + */ + public function is_runnable() + { + return file_exists($this->cache_dir . 'queue.' . $this->php_ext); + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between queue runs is specified in board configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['last_queue_run'] < time() - $this->config['queue_interval']; + } +} diff --git a/phpbb/cron/task/core/tidy_cache.php b/phpbb/cron/task/core/tidy_cache.php new file mode 100644 index 0000000..506a245 --- /dev/null +++ b/phpbb/cron/task/core/tidy_cache.php @@ -0,0 +1,72 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Tidy cache cron task. +*/ +class tidy_cache extends \phpbb\cron\task\base +{ + protected $config; + protected $cache; + + /** + * Constructor. + * + * @param \phpbb\config\config $config The config + * @param \phpbb\cache\driver\driver_interface $cache The cache driver + */ + public function __construct(\phpbb\config\config $config, \phpbb\cache\driver\driver_interface $cache) + { + $this->config = $config; + $this->cache = $cache; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + $this->cache->tidy(); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * Tidy cache cron task runs if the cache implementation in use + * supports tidying. + * + * @return bool + */ + public function is_runnable() + { + return true; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between cache tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['cache_last_gc'] < time() - $this->config['cache_gc']; + } +} diff --git a/phpbb/cron/task/core/tidy_database.php b/phpbb/cron/task/core/tidy_database.php new file mode 100644 index 0000000..949bba8 --- /dev/null +++ b/phpbb/cron/task/core/tidy_database.php @@ -0,0 +1,66 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Tidy database cron task. +*/ +class tidy_database extends \phpbb\cron\task\base +{ + protected $phpbb_root_path; + protected $php_ext; + protected $config; + + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext The PHP file extension + * @param \phpbb\config\config $config The config + */ + public function __construct($phpbb_root_path, $php_ext, \phpbb\config\config $config) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('tidy_database')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + tidy_database(); + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between database tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['database_last_gc'] < time() - $this->config['database_gc']; + } +} diff --git a/phpbb/cron/task/core/tidy_plupload.php b/phpbb/cron/task/core/tidy_plupload.php new file mode 100644 index 0000000..37d0e9b --- /dev/null +++ b/phpbb/cron/task/core/tidy_plupload.php @@ -0,0 +1,126 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Cron task for cleaning plupload's temporary upload directory. +*/ +class tidy_plupload extends \phpbb\cron\task\base +{ + /** + * How old a file must be (in seconds) before it is deleted. + * @var int + */ + protected $max_file_age = 86400; + + /** + * How often we run the cron (in seconds). + * @var int + */ + protected $cron_frequency = 86400; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * Config object + * @var \phpbb\config\config + */ + protected $config; + + /** @var \phpbb\log\log_interface */ + protected $log; + + /** @var \phpbb\user */ + protected $user; + + /** + * Directory where plupload stores temporary files. + * @var string + */ + protected $plupload_upload_path; + + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param \phpbb\config\config $config The config + * @param \phpbb\log\log_interface $log Log + * @param \phpbb\user $user User object + */ + public function __construct($phpbb_root_path, \phpbb\config\config $config, \phpbb\log\log_interface $log, \phpbb\user $user) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->config = $config; + $this->log = $log; + $this->user = $user; + + $this->plupload_upload_path = $this->phpbb_root_path . $this->config['upload_path'] . '/plupload'; + } + + /** + * {@inheritDoc} + */ + public function run() + { + // Remove old temporary file (perhaps failed uploads?) + $last_valid_timestamp = time() - $this->max_file_age; + try + { + $iterator = new \DirectoryIterator($this->plupload_upload_path); + foreach ($iterator as $file) + { + if (strpos($file->getBasename(), $this->config['plupload_salt']) !== 0) + { + // Skip over any non-plupload files. + continue; + } + + if ($file->getMTime() < $last_valid_timestamp) + { + @unlink($file->getPathname()); + } + } + } + catch (\UnexpectedValueException $e) + { + $this->log->add('critical', $this->user->data['user_id'], $this->user->ip, 'LOG_PLUPLOAD_TIDY_FAILED', false, array( + $this->plupload_upload_path, + $e->getMessage(), + $e->getTraceAsString() + )); + } + + $this->config->set('plupload_last_gc', time(), true); + } + + /** + * {@inheritDoc} + */ + public function is_runnable() + { + return !empty($this->config['plupload_salt']) && is_dir($this->plupload_upload_path); + } + + /** + * {@inheritDoc} + */ + public function should_run() + { + return $this->config['plupload_last_gc'] < time() - $this->cron_frequency; + } +} diff --git a/phpbb/cron/task/core/tidy_search.php b/phpbb/cron/task/core/tidy_search.php new file mode 100644 index 0000000..eb39702 --- /dev/null +++ b/phpbb/cron/task/core/tidy_search.php @@ -0,0 +1,133 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Tidy search cron task. +* +* Will only run when the currently selected search backend supports tidying. +*/ +class tidy_search extends \phpbb\cron\task\base +{ + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP file extension + * @var string + */ + protected $php_ext; + + /** + * Auth object + * @var \phpbb\auth\auth + */ + protected $auth; + + /** + * Config object + * @var \phpbb\config\config + */ + protected $config; + + /** + * Database object + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $phpbb_dispatcher; + + /** + * Constructor. + * + * @param string $phpbb_root_path The phpBB root path + * @param string $php_ext The PHP file extension + * @param \phpbb\auth\auth $auth The auth object + * @param \phpbb\config\config $config The config object + * @param \phpbb\db\driver\driver_interface $db The database object + * @param \phpbb\user $user The user object + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher The event dispatcher object + */ + public function __construct($phpbb_root_path, $php_ext, \phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\user $user, \phpbb\event\dispatcher_interface $phpbb_dispatcher) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->auth = $auth; + $this->config = $config; + $this->db = $db; + $this->user = $user; + $this->phpbb_dispatcher = $phpbb_dispatcher; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + $search_type = $this->config['search_type']; + + // We do some additional checks in the module to ensure it can actually be utilised + $error = false; + $search = new $search_type($error, $this->phpbb_root_path, $this->php_ext, $this->auth, $this->config, $this->db, $this->user, $this->phpbb_dispatcher); + + if (!$error) + { + $search->tidy(); + } + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * Search cron task is runnable in all normal use. It may not be + * runnable if the search backend implementation selected in board + * configuration does not exist. + * + * @return bool + */ + public function is_runnable() + { + return class_exists($this->config['search_type']); + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between search tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['search_last_gc'] < time() - $this->config['search_gc']; + } +} diff --git a/phpbb/cron/task/core/tidy_sessions.php b/phpbb/cron/task/core/tidy_sessions.php new file mode 100644 index 0000000..5e6dabd --- /dev/null +++ b/phpbb/cron/task/core/tidy_sessions.php @@ -0,0 +1,59 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Tidy sessions cron task. +*/ +class tidy_sessions extends \phpbb\cron\task\base +{ + protected $config; + protected $user; + + /** + * Constructor. + * + * @param \phpbb\config\config $config The config + * @param \phpbb\user $user The user + */ + public function __construct(\phpbb\config\config $config, \phpbb\user $user) + { + $this->config = $config; + $this->user = $user; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + $this->user->session_gc(); + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between session tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['session_last_gc'] < time() - $this->config['session_gc']; + } +} diff --git a/phpbb/cron/task/core/tidy_warnings.php b/phpbb/cron/task/core/tidy_warnings.php new file mode 100644 index 0000000..7b67eae --- /dev/null +++ b/phpbb/cron/task/core/tidy_warnings.php @@ -0,0 +1,80 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task\core; + +/** +* Tidy warnings cron task. +* +* Will only run when warnings are configured to expire. +*/ +class tidy_warnings extends \phpbb\cron\task\base +{ + protected $phpbb_root_path; + protected $php_ext; + protected $config; + + /** + * Constructor. + * + * @param string $phpbb_root_path The root path + * @param string $php_ext PHP file extension + * @param \phpbb\config\config $config The config + */ + public function __construct($phpbb_root_path, $php_ext, \phpbb\config\config $config) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->config = $config; + } + + /** + * Runs this cron task. + * + * @return null + */ + public function run() + { + if (!function_exists('tidy_warnings')) + { + include($this->phpbb_root_path . 'includes/functions_admin.' . $this->php_ext); + } + tidy_warnings(); + } + + /** + * Returns whether this cron task can run, given current board configuration. + * + * If warnings are set to never expire, this cron task will not run. + * + * @return bool + */ + public function is_runnable() + { + return (bool) $this->config['warnings_expire_days']; + } + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * The interval between warnings tidying is specified in board + * configuration. + * + * @return bool + */ + public function should_run() + { + return $this->config['warnings_last_gc'] < time() - $this->config['warnings_gc']; + } +} diff --git a/phpbb/cron/task/core/update_hashes.php b/phpbb/cron/task/core/update_hashes.php new file mode 100644 index 0000000..ba095ab --- /dev/null +++ b/phpbb/cron/task/core/update_hashes.php @@ -0,0 +1,130 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\cron\task\core; + +/** + * Update old hashes to the current default hashing algorithm + * + * It is intended to gradually update all "old" style hashes to the + * current default hashing algorithm. + */ +class update_hashes extends \phpbb\cron\task\base +{ + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\lock\db */ + protected $update_lock; + + /** @var \phpbb\passwords\manager */ + protected $passwords_manager; + + /** @var string Default hashing type */ + protected $default_type; + + /** + * Constructor. + * + * @param \phpbb\config\config $config + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\lock\db $update_lock + * @param \phpbb\passwords\manager $passwords_manager + * @param array $hashing_algorithms Hashing driver + * service collection + * @param array $defaults Default password types + */ + public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\lock\db $update_lock, \phpbb\passwords\manager $passwords_manager, $hashing_algorithms, $defaults) + { + $this->config = $config; + $this->db = $db; + $this->passwords_manager = $passwords_manager; + $this->update_lock = $update_lock; + + foreach ($defaults as $type) + { + if ($hashing_algorithms[$type]->is_supported()) + { + $this->default_type = $type; + break; + } + } + } + + /** + * {@inheritdoc} + */ + public function is_runnable() + { + return !$this->config['use_system_cron']; + } + + /** + * {@inheritdoc} + */ + public function should_run() + { + if (!empty($this->config['update_hashes_lock'])) + { + $last_run = explode(' ', $this->config['update_hashes_lock']); + if ($last_run[0] + 60 >= time()) + { + return false; + } + } + + return $this->config['enable_update_hashes'] && $this->config['update_hashes_last_cron'] < (time() - 60); + } + + /** + * {@inheritdoc} + */ + public function run() + { + if ($this->update_lock->acquire()) + { + $sql = 'SELECT user_id, user_password + FROM ' . USERS_TABLE . ' + WHERE user_password ' . $this->db->sql_like_expression('$H$' . $this->db->get_any_char()) . ' + OR user_password ' . $this->db->sql_like_expression('$CP$' . $this->db->get_any_char()); + $result = $this->db->sql_query_limit($sql, 20); + + $affected_rows = 0; + + while ($row = $this->db->sql_fetchrow($result)) + { + $new_hash = $this->passwords_manager->hash($row['user_password'], array($this->default_type)); + + // Increase number so we know that users were selected from the database + $affected_rows++; + + $sql = 'UPDATE ' . USERS_TABLE . " + SET user_password = '" . $this->db->sql_escape($new_hash) . "' + WHERE user_id = " . (int) $row['user_id']; + $this->db->sql_query($sql); + } + + $this->config->set('update_hashes_last_cron', time()); + $this->update_lock->release(); + + // Stop cron for good once all hashes are converted + if ($affected_rows === 0) + { + $this->config->set('enable_update_hashes', '0'); + } + } + } +} diff --git a/phpbb/cron/task/parametrized.php b/phpbb/cron/task/parametrized.php new file mode 100644 index 0000000..7e190b9 --- /dev/null +++ b/phpbb/cron/task/parametrized.php @@ -0,0 +1,48 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task; + +/** +* Parametrized cron task interface. +* +* Parametrized cron tasks are somewhat of a cross between regular cron tasks and +* delayed jobs. Whereas regular cron tasks perform some action globally, +* parametrized cron tasks perform actions on a particular object (or objects). +* Parametrized cron tasks do not make sense and are not usable without +* specifying these objects. +*/ +interface parametrized extends \phpbb\cron\task\task +{ + /** + * Returns parameters of this cron task as an array. + * + * The array must map string keys to string values. + * + * @return array + */ + public function get_parameters(); + + /** + * Parses parameters found in $request, which is an instance of + * \phpbb\request\request_interface. + * + * $request contains user input and must not be trusted. + * Cron task must validate all data before using it. + * + * @param \phpbb\request\request_interface $request Request object. + * + * @return null + */ + public function parse_parameters(\phpbb\request\request_interface $request); +} diff --git a/phpbb/cron/task/task.php b/phpbb/cron/task/task.php new file mode 100644 index 0000000..6d5a383 --- /dev/null +++ b/phpbb/cron/task/task.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task; + +/** +* Cron task interface +*/ +interface task +{ + /** + * Returns the name of the task. + * + * @return string Name of wrapped task. + */ + public function get_name(); + + /** + * Runs this cron task. + * + * @return null + */ + public function run(); + + /** + * Returns whether this cron task can run, given current board configuration. + * + * For example, a cron task that prunes forums can only run when + * forum pruning is enabled. + * + * @return bool + */ + public function is_runnable(); + + /** + * Returns whether this cron task should run now, because enough time + * has passed since it was last run. + * + * @return bool + */ + public function should_run(); +} diff --git a/phpbb/cron/task/text_reparser/reparser.php b/phpbb/cron/task/text_reparser/reparser.php new file mode 100644 index 0000000..fa3bc67 --- /dev/null +++ b/phpbb/cron/task/text_reparser/reparser.php @@ -0,0 +1,168 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\cron\task\text_reparser; + +/** + * Reparse text cron task + */ +class reparser extends \phpbb\cron\task\base +{ + const MIN = 1; + const SIZE = 100; + + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var \phpbb\config\db_text + */ + protected $config_text; + + /** + * @var \phpbb\lock\db + */ + protected $reparse_lock; + + /** + * @var \phpbb\textreparser\manager + */ + protected $reparser_manager; + + /** + * @var string + */ + protected $reparser_name; + + /** + * @var \phpbb\di\service_collection + */ + protected $reparsers; + + /** + * @var array + */ + protected $resume_data; + + /** + * Constructor + * + * @param \phpbb\config\config $config + * @param \phpbb\config\db_text $config_text + * @param \phpbb\lock\db $reparse_lock + * @param \phpbb\textreparser\manager $reparser_manager + * @param \phpbb\di\service_collection $reparsers + */ + public function __construct(\phpbb\config\config $config, \phpbb\config\db_text $config_text, \phpbb\lock\db $reparse_lock, \phpbb\textreparser\manager $reparser_manager, \phpbb\di\service_collection $reparsers) + { + $this->config = $config; + $this->config_text = $config_text; + $this->reparse_lock = $reparse_lock; + $this->reparser_manager = $reparser_manager; + $this->reparsers = $reparsers; + } + + /** + * Sets the reparser for this cron task + * + * @param string $reparser + */ + public function set_reparser($reparser) + { + $this->reparser_name = !isset($this->reparsers[$reparser]) ? $this->reparser_manager->find_reparser($reparser) : $reparser; + + if ($this->resume_data === null) + { + $this->resume_data = $this->reparser_manager->get_resume_data($this->reparser_name); + } + } + + /** + * {@inheritdoc} + */ + public function is_runnable() + { + if ($this->resume_data === null) + { + $this->resume_data = $this->reparser_manager->get_resume_data($this->reparser_name); + } + + if (!isset($this->resume_data['range-max']) || $this->resume_data['range-max'] >= $this->resume_data['range-min']) + { + return true; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function should_run() + { + if (!empty($this->config['reparse_lock'])) + { + $last_run = explode(' ', $this->config['reparse_lock']); + + if ($last_run[0] + 3600 >= time()) + { + return false; + } + } + + if ($this->config[$this->reparser_name . '_cron_interval']) + { + return $this->config[$this->reparser_name . '_last_cron'] < time() - $this->config[$this->reparser_name . '_cron_interval']; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function run() + { + if ($this->reparse_lock->acquire()) + { + if ($this->resume_data === null) + { + $this->resume_data = $this->reparser_manager->get_resume_data($this->reparser_name); + } + + /** + * @var \phpbb\textreparser\reparser_interface $reparser + */ + $reparser = $this->reparsers[$this->reparser_name]; + + $min = isset($this->resume_data['range-min']) ? $this->resume_data['range-min'] : self::MIN; + $current = isset($this->resume_data['range-max']) ? $this->resume_data['range-max'] : $reparser->get_max_id(); + $size = isset($this->resume_data['range-size']) ? $this->resume_data['range-size'] : self::SIZE; + + if ($current >= $min) + { + $start = max($min, $current + 1 - $size); + $end = max($min, $current); + + $reparser->reparse_range($start, $end); + + $this->reparser_manager->update_resume_data($this->reparser_name, $min, $start - 1, $size); + } + + $this->config->set($this->reparser_name . '_last_cron', time()); + $this->reparse_lock->release(); + } + } +} diff --git a/phpbb/cron/task/wrapper.php b/phpbb/cron/task/wrapper.php new file mode 100644 index 0000000..8a4a8b1 --- /dev/null +++ b/phpbb/cron/task/wrapper.php @@ -0,0 +1,106 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\cron\task; + +/** +* Cron task wrapper class. +* Enhances cron tasks with convenience methods that work identically for all tasks. +*/ +class wrapper +{ + protected $task; + protected $phpbb_root_path; + protected $php_ext; + + /** + * Constructor. + * + * Wraps a task $task, which must implement cron_task interface. + * + * @param \phpbb\cron\task\task $task The cron task to wrap. + * @param string $phpbb_root_path Relative path to phpBB root + * @param string $php_ext PHP file extension + */ + public function __construct(\phpbb\cron\task\task $task, $phpbb_root_path, $php_ext) + { + $this->task = $task; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Returns whether the wrapped task is parametrised. + * + * Parametrized tasks accept parameters during initialization and must + * normally be scheduled with parameters. + * + * @return bool Whether or not this task is parametrized. + */ + public function is_parametrized() + { + return $this->task instanceof \phpbb\cron\task\parametrized; + } + + /** + * Returns whether the wrapped task is ready to run. + * + * A task is ready to run when it is runnable according to current configuration + * and enough time has passed since it was last run. + * + * @return bool Whether the wrapped task is ready to run. + */ + public function is_ready() + { + return $this->task->is_runnable() && $this->task->should_run(); + } + + /** + * Returns a url through which this task may be invoked via web. + * + * When system cron is not in use, running a cron task is accomplished + * by outputting an image with the url returned by this function as + * source. + * + * @return string URL through which this task may be invoked. + */ + public function get_url() + { + $name = $this->get_name(); + if ($this->is_parametrized()) + { + $params = $this->task->get_parameters(); + $extra = ''; + foreach ($params as $key => $value) + { + $extra .= '&' . $key . '=' . urlencode($value); + } + } + else + { + $extra = ''; + } + $url = append_sid($this->phpbb_root_path . 'cron.' . $this->php_ext, 'cron_type=' . $name . $extra); + return $url; + } + + /** + * Forwards all other method calls to the wrapped task implementation. + * + * @return mixed + */ + public function __call($name, $args) + { + return call_user_func_array(array($this->task, $name), $args); + } +} diff --git a/phpbb/datetime.php b/phpbb/datetime.php new file mode 100644 index 0000000..4b799b6 --- /dev/null +++ b/phpbb/datetime.php @@ -0,0 +1,174 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** +* phpBB custom extensions to the PHP DateTime class +* This handles the relative formats phpBB employs +*/ +class datetime extends \DateTime +{ + /** + * String used to wrap the date segment which should be replaced by today/tomorrow/yesterday + */ + const RELATIVE_WRAPPER = '|'; + + /** + * @var user User who is the context for this DateTime instance + */ + protected $user; + + /** + * @var array Date formats are preprocessed by phpBB, to save constant recalculation they are cached. + */ + static protected $format_cache = array(); + + /** + * Constructs a new instance of \phpbb\datetime, expanded to include an argument to inject + * the user context and modify the timezone to the users selected timezone if one is not set. + * + * @param user $user object for context. + * @param string $time String in a format accepted by strtotime(). + * @param \DateTimeZone $timezone Time zone of the time. + */ + public function __construct($user, $time = 'now', \DateTimeZone $timezone = null) + { + $this->user = $user; + $timezone = $timezone ?: $this->user->timezone; + + parent::__construct($time, $timezone); + } + + /** + * Formats the current date time into the specified format + * + * @param string $format Optional format to use for output, defaults to users chosen format + * @param boolean $force_absolute Force output of a non relative date + * @return string Formatted date time + */ + public function format($format = '', $force_absolute = false) + { + $format = $format ? $format : $this->user->date_format; + + if (substr($this->user->lang_name, 0,2) != 'en') + { + $format = preg_replace('/([^\\\])S/','$1', $format); + } + + $format = self::format_cache($format, $this->user); + $relative = ($format['is_short'] && !$force_absolute); + $now = new self($this->user, 'now', $this->user->timezone); + + $timestamp = $this->getTimestamp(); + $now_ts = $now->getTimeStamp(); + + $delta = $now_ts - $timestamp; + + if ($relative) + { + /* + * Check the delta is less than or equal to 1 hour + * and the delta not more than a minute in the past + * and the delta is either greater than -5 seconds or timestamp + * and current time are of the same minute (they must be in the same hour already) + * finally check that relative dates are supported by the language pack + */ + if ($delta <= 3600 && $delta > -60 && + ($delta >= -5 || (($now_ts / 60) % 60) == (($timestamp / 60) % 60)) + && isset($this->user->lang['datetime']['AGO'])) + { + return $this->user->lang(array('datetime', 'AGO'), max(0, (int) floor($delta / 60))); + } + else + { + $midnight = clone $now; + $midnight->setTime(0, 0, 0); + + $midnight = $midnight->getTimestamp(); + + if ($timestamp <= $midnight + 2 * 86400) + { + $day = false; + + if ($timestamp > $midnight + 86400) + { + $day = 'TOMORROW'; + } + else if ($timestamp > $midnight) + { + $day = 'TODAY'; + } + else if ($timestamp > $midnight - 86400) + { + $day = 'YESTERDAY'; + } + + if ($day !== false) + { + // Format using the short formatting and finally swap out the relative token placeholder with the correct value + return str_replace(self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER, $this->user->lang['datetime'][$day], strtr(parent::format($format['format_short']), $format['lang'])); + } + } + } + } + + return strtr(parent::format($format['format_long']), $format['lang']); + } + + /** + * Magic method to convert DateTime object to string + * + * @return string Formatted date time, according to the users default settings. + */ + public function __toString() + { + return $this->format(); + } + + /** + * Pre-processes the specified date format + * + * @param string $format Output format + * @param user $user User object to use for localisation + * @return array Processed date format + */ + static protected function format_cache($format, $user) + { + $lang = $user->lang_name; + + if (!isset(self::$format_cache[$lang])) + { + self::$format_cache[$lang] = array(); + } + + if (!isset(self::$format_cache[$lang][$format])) + { + // Is the user requesting a friendly date format (i.e. 'Today 12:42')? + self::$format_cache[$lang][$format] = array( + 'is_short' => strpos($format, self::RELATIVE_WRAPPER) !== false, + 'format_short' => substr($format, 0, strpos($format, self::RELATIVE_WRAPPER)) . self::RELATIVE_WRAPPER . self::RELATIVE_WRAPPER . substr(strrchr($format, self::RELATIVE_WRAPPER), 1), + 'format_long' => str_replace(self::RELATIVE_WRAPPER, '', $format), + 'lang' => array_filter($user->lang['datetime'], 'is_string'), + ); + + // Short representation of month in format? Some languages use different terms for the long and short format of May + if ((strpos($format, '\M') === false && strpos($format, 'M') !== false) || (strpos($format, '\r') === false && strpos($format, 'r') !== false)) + { + self::$format_cache[$lang][$format]['lang']['May'] = $user->lang['datetime']['May_short']; + } + } + + return self::$format_cache[$lang][$format]; + } +} diff --git a/phpbb/db/driver/driver.php b/phpbb/db/driver/driver.php new file mode 100644 index 0000000..a36ce8c --- /dev/null +++ b/phpbb/db/driver/driver.php @@ -0,0 +1,1220 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* Database Abstraction Layer +*/ +abstract class driver implements driver_interface +{ + var $db_connect_id; + var $query_result; + var $return_on_error = false; + var $transaction = false; + var $sql_time = 0; + var $num_queries = array(); + var $open_queries = array(); + + var $curtime = 0; + var $query_hold = ''; + var $html_hold = ''; + var $sql_report = ''; + + var $persistency = false; + var $user = ''; + var $server = ''; + var $dbname = ''; + + // Set to true if error triggered + var $sql_error_triggered = false; + + // Holding the last sql query on sql error + var $sql_error_sql = ''; + // Holding the error information - only populated if sql_error_triggered is set + var $sql_error_returned = array(); + + // Holding transaction count + var $transactions = 0; + + // Supports multi inserts? + var $multi_insert = false; + + /** + * Current sql layer + */ + var $sql_layer = ''; + + /** + * Wildcards for matching any (%) or exactly one (_) character within LIKE expressions + */ + var $any_char; + var $one_char; + + /** + * Exact version of the DBAL, directly queried + */ + var $sql_server_version = false; + + const LOGICAL_OP = 0; + const STATEMENTS = 1; + const LEFT_STMT = 0; + const COMPARE_OP = 1; + const RIGHT_STMT = 2; + const SUBQUERY_OP = 3; + const SUBQUERY_SELECT_TYPE = 4; + const SUBQUERY_BUILD = 5; + + /** + * Constructor + */ + function __construct() + { + $this->num_queries = array( + 'cached' => 0, + 'normal' => 0, + 'total' => 0, + ); + + // Fill default sql layer based on the class being called. + // This can be changed by the specified layer itself later if needed. + $this->sql_layer = substr(get_class($this), strlen('phpbb\db\driver\\')); + + // Do not change this please! This variable is used to easy the use of it - and is hardcoded. + $this->any_char = chr(0) . '%'; + $this->one_char = chr(0) . '_'; + } + + /** + * {@inheritdoc} + */ + public function get_sql_layer() + { + return $this->sql_layer; + } + + /** + * {@inheritdoc} + */ + public function get_db_name() + { + return $this->dbname; + } + + /** + * {@inheritdoc} + */ + public function get_any_char() + { + return $this->any_char; + } + + /** + * {@inheritdoc} + */ + public function get_one_char() + { + return $this->one_char; + } + + /** + * {@inheritdoc} + */ + public function get_db_connect_id() + { + return $this->db_connect_id; + } + + /** + * {@inheritdoc} + */ + public function get_sql_error_triggered() + { + return $this->sql_error_triggered; + } + + /** + * {@inheritdoc} + */ + public function get_sql_error_sql() + { + return $this->sql_error_sql; + } + + /** + * {@inheritdoc} + */ + public function get_transaction() + { + return $this->transaction; + } + + /** + * {@inheritdoc} + */ + public function get_sql_time() + { + return $this->sql_time; + } + + /** + * {@inheritdoc} + */ + public function get_sql_error_returned() + { + return $this->sql_error_returned; + } + + /** + * {@inheritdoc} + */ + public function get_multi_insert() + { + return $this->multi_insert; + } + + /** + * {@inheritdoc} + */ + public function set_multi_insert($multi_insert) + { + $this->multi_insert = $multi_insert; + } + + /** + * {@inheritDoc} + */ + function sql_return_on_error($fail = false) + { + $this->sql_error_triggered = false; + $this->sql_error_sql = ''; + + $this->return_on_error = $fail; + } + + /** + * {@inheritDoc} + */ + function sql_num_queries($cached = false) + { + return ($cached) ? $this->num_queries['cached'] : $this->num_queries['normal']; + } + + /** + * {@inheritDoc} + */ + function sql_add_num_queries($cached = false) + { + $this->num_queries['cached'] += ($cached !== false) ? 1 : 0; + $this->num_queries['normal'] += ($cached !== false) ? 0 : 1; + $this->num_queries['total'] += 1; + } + + /** + * {@inheritDoc} + */ + function sql_close() + { + if (!$this->db_connect_id) + { + return false; + } + + if ($this->transaction) + { + do + { + $this->sql_transaction('commit'); + } + while ($this->transaction); + } + + foreach ($this->open_queries as $query_id) + { + $this->sql_freeresult($query_id); + } + + // Connection closed correctly. Set db_connect_id to false to prevent errors + if ($result = $this->_sql_close()) + { + $this->db_connect_id = false; + } + + return $result; + } + + /** + * {@inheritDoc} + */ + function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + if (empty($query)) + { + return false; + } + + // Never use a negative total or offset + $total = ($total < 0) ? 0 : $total; + $offset = ($offset < 0) ? 0 : $offset; + + return $this->_sql_query_limit($query, $total, $offset, $cache_ttl); + } + + /** + * {@inheritDoc} + */ + function sql_fetchrowset($query_id = false) + { + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + $result = array(); + while ($row = $this->sql_fetchrow($query_id)) + { + $result[] = $row; + } + + return $result; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + if (!$query_id) + { + return false; + } + + $this->sql_freeresult($query_id); + $query_id = $this->sql_query($this->last_query_text); + + if (!$query_id) + { + return false; + } + + // We do not fetch the row for rownum == 0 because then the next resultset would be the second row + for ($i = 0; $i < $rownum; $i++) + { + if (!$this->sql_fetchrow($query_id)) + { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + function sql_fetchfield($field, $rownum = false, $query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($query_id) + { + if ($rownum !== false) + { + $this->sql_rowseek($rownum, $query_id); + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchfield($query_id, $field); + } + + $row = $this->sql_fetchrow($query_id); + return (isset($row[$field])) ? $row[$field] : false; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_like_expression($expression) + { + $expression = str_replace(array('_', '%'), array("\_", "\%"), $expression); + $expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); + + return $this->_sql_like_expression('LIKE \'' . $this->sql_escape($expression) . '\''); + } + + /** + * {@inheritDoc} + */ + function sql_not_like_expression($expression) + { + $expression = str_replace(array('_', '%'), array("\_", "\%"), $expression); + $expression = str_replace(array(chr(0) . "\_", chr(0) . "\%"), array('_', '%'), $expression); + + return $this->_sql_not_like_expression('NOT LIKE \'' . $this->sql_escape($expression) . '\''); + } + + /** + * {@inheritDoc} + */ + public function sql_case($condition, $action_true, $action_false = false) + { + $sql_case = 'CASE WHEN ' . $condition; + $sql_case .= ' THEN ' . $action_true; + $sql_case .= ($action_false !== false) ? ' ELSE ' . $action_false : ''; + $sql_case .= ' END'; + return $sql_case; + } + + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' || ' . $expr2; + } + + /** + * {@inheritDoc} + */ + function sql_buffer_nested_transactions() + { + return false; + } + + /** + * {@inheritDoc} + */ + function sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + // If we are within a transaction we will not open another one, but enclose the current one to not loose data (preventing auto commit) + if ($this->transaction) + { + $this->transactions++; + return true; + } + + $result = $this->_sql_transaction('begin'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = true; + break; + + case 'commit': + // If there was a previously opened transaction we do not commit yet... + // but count back the number of inner transactions + if ($this->transaction && $this->transactions) + { + $this->transactions--; + return true; + } + + // Check if there is a transaction (no transaction can happen if + // there was an error, with a combined rollback and error returning enabled) + // This implies we have transaction always set for autocommit db's + if (!$this->transaction) + { + return false; + } + + $result = $this->_sql_transaction('commit'); + + if (!$result) + { + $this->sql_error(); + } + + $this->transaction = false; + $this->transactions = 0; + break; + + case 'rollback': + $result = $this->_sql_transaction('rollback'); + $this->transaction = false; + $this->transactions = 0; + break; + + default: + $result = $this->_sql_transaction($status); + break; + } + + return $result; + } + + /** + * {@inheritDoc} + */ + function sql_build_array($query, $assoc_ary = false) + { + if (!is_array($assoc_ary)) + { + return false; + } + + $fields = $values = array(); + + if ($query == 'INSERT' || $query == 'INSERT_SELECT') + { + foreach ($assoc_ary as $key => $var) + { + $fields[] = $key; + + if (is_array($var) && is_string($var[0])) + { + // This is used for INSERT_SELECT(s) + $values[] = $var[0]; + } + else + { + $values[] = $this->_sql_validate_value($var); + } + } + + $query = ($query == 'INSERT') ? ' (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $values) . ')' : ' (' . implode(', ', $fields) . ') SELECT ' . implode(', ', $values) . ' '; + } + else if ($query == 'MULTI_INSERT') + { + trigger_error('The MULTI_INSERT query value is no longer supported. Please use sql_multi_insert() instead.', E_USER_ERROR); + } + else if ($query == 'UPDATE' || $query == 'SELECT' || $query == 'DELETE') + { + $values = array(); + foreach ($assoc_ary as $key => $var) + { + $values[] = "$key = " . $this->_sql_validate_value($var); + } + $query = implode(($query == 'UPDATE') ? ', ' : ' AND ', $values); + } + + return $query; + } + + /** + * {@inheritDoc} + */ + function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) + { + $array = (array) $array; + + if (!count($array)) + { + if (!$allow_empty_set) + { + // Print the backtrace to help identifying the location of the problematic code + $this->sql_error('No values specified for SQL IN comparison'); + } + else + { + // NOT IN () actually means everything so use a tautology + if ($negate) + { + return '1=1'; + } + // IN () actually means nothing so use a contradiction + else + { + return '1=0'; + } + } + } + + if (count($array) == 1) + { + @reset($array); + $var = current($array); + + return $field . ($negate ? ' <> ' : ' = ') . $this->_sql_validate_value($var); + } + else + { + return $field . ($negate ? ' NOT IN ' : ' IN ') . '(' . implode(', ', array_map(array($this, '_sql_validate_value'), $array)) . ')'; + } + } + + /** + * {@inheritDoc} + */ + function sql_bit_and($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_and')) + { + return $this->_sql_bit_and($column_name, $bit, $compare); + } + + return $column_name . ' & ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * {@inheritDoc} + */ + function sql_bit_or($column_name, $bit, $compare = '') + { + if (method_exists($this, '_sql_bit_or')) + { + return $this->_sql_bit_or($column_name, $bit, $compare); + } + + return $column_name . ' | ' . (1 << $bit) . (($compare) ? ' ' . $compare : ''); + } + + /** + * {@inheritDoc} + */ + function cast_expr_to_bigint($expression) + { + return $expression; + } + + /** + * {@inheritDoc} + */ + function cast_expr_to_string($expression) + { + return $expression; + } + + /** + * {@inheritDoc} + */ + function sql_lower_text($column_name) + { + return "LOWER($column_name)"; + } + + /** + * {@inheritDoc} + */ + function sql_multi_insert($table, $sql_ary) + { + if (!count($sql_ary)) + { + return false; + } + + if ($this->multi_insert) + { + $ary = array(); + foreach ($sql_ary as $id => $_sql_ary) + { + // If by accident the sql array is only one-dimensional we build a normal insert statement + if (!is_array($_sql_ary)) + { + return $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $sql_ary)); + } + + $values = array(); + foreach ($_sql_ary as $key => $var) + { + $values[] = $this->_sql_validate_value($var); + } + $ary[] = '(' . implode(', ', $values) . ')'; + } + + return $this->sql_query('INSERT INTO ' . $table . ' ' . ' (' . implode(', ', array_keys($sql_ary[0])) . ') VALUES ' . implode(', ', $ary)); + } + else + { + foreach ($sql_ary as $ary) + { + if (!is_array($ary)) + { + return false; + } + + $result = $this->sql_query('INSERT INTO ' . $table . ' ' . $this->sql_build_array('INSERT', $ary)); + + if (!$result) + { + return false; + } + } + } + + return true; + } + + /** + * Function for validating values + * @access private + */ + function _sql_validate_value($var) + { + if (is_null($var)) + { + return 'NULL'; + } + else if (is_string($var)) + { + return "'" . $this->sql_escape($var) . "'"; + } + else + { + return (is_bool($var)) ? intval($var) : $var; + } + } + + /** + * {@inheritDoc} + */ + function sql_build_query($query, $array) + { + $sql = ''; + switch ($query) + { + case 'SELECT': + case 'SELECT_DISTINCT'; + + $sql = str_replace('_', ' ', $query) . ' ' . $array['SELECT'] . ' FROM '; + + // Build table array. We also build an alias array for later checks. + $table_array = $aliases = array(); + $used_multi_alias = false; + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + $used_multi_alias = true; + + foreach ($alias as $multi_alias) + { + $table_array[] = $table_name . ' ' . $multi_alias; + $aliases[] = $multi_alias; + } + } + else + { + $table_array[] = $table_name . ' ' . $alias; + $aliases[] = $alias; + } + } + + // We run the following code to determine if we need to re-order the table array. ;) + // The reason for this is that for multi-aliased tables (two equal tables) in the FROM statement the last table need to match the first comparison. + // DBMS who rely on this: Oracle, PostgreSQL and MSSQL. For all other DBMS it makes absolutely no difference in which order the table is. + if (!empty($array['LEFT_JOIN']) && count($array['FROM']) > 1 && $used_multi_alias !== false) + { + // Take first LEFT JOIN + $join = current($array['LEFT_JOIN']); + + // Determine the table used there (even if there are more than one used, we only want to have one + preg_match('/(' . implode('|', $aliases) . ')\.[^\s]+/U', str_replace(array('(', ')', 'AND', 'OR', ' '), '', $join['ON']), $matches); + + // If there is a first join match, we need to make sure the table order is correct + if (!empty($matches[1])) + { + $first_join_match = trim($matches[1]); + $table_array = $last = array(); + + foreach ($array['FROM'] as $table_name => $alias) + { + if (is_array($alias)) + { + foreach ($alias as $multi_alias) + { + ($multi_alias === $first_join_match) ? $last[] = $table_name . ' ' . $multi_alias : $table_array[] = $table_name . ' ' . $multi_alias; + } + } + else + { + ($alias === $first_join_match) ? $last[] = $table_name . ' ' . $alias : $table_array[] = $table_name . ' ' . $alias; + } + } + + $table_array = array_merge($table_array, $last); + } + } + + $sql .= $this->_sql_custom_build('FROM', implode(' CROSS JOIN ', $table_array)); + + if (!empty($array['LEFT_JOIN'])) + { + foreach ($array['LEFT_JOIN'] as $join) + { + $sql .= ' LEFT JOIN ' . key($join['FROM']) . ' ' . current($join['FROM']) . ' ON (' . $join['ON'] . ')'; + } + } + + if (!empty($array['WHERE'])) + { + $sql .= ' WHERE '; + + if (is_array($array['WHERE'])) + { + $sql_where = $this->_process_boolean_tree_first($array['WHERE']); + } + else + { + $sql_where = $array['WHERE']; + } + + $sql .= $this->_sql_custom_build('WHERE', $sql_where); + } + + if (!empty($array['GROUP_BY'])) + { + $sql .= ' GROUP BY ' . $array['GROUP_BY']; + } + + if (!empty($array['ORDER_BY'])) + { + $sql .= ' ORDER BY ' . $array['ORDER_BY']; + } + + break; + } + + return $sql; + } + + + protected function _process_boolean_tree_first($operations_ary) + { + // In cases where an array exists but there is no head condition, + // it should be because there's only 1 WHERE clause. This seems the best way to deal with it. + if ($operations_ary[self::LOGICAL_OP] !== 'AND' && + $operations_ary[self::LOGICAL_OP] !== 'OR') + { + $operations_ary = array('AND', array($operations_ary)); + } + return $this->_process_boolean_tree($operations_ary) . "\n"; + } + + protected function _process_boolean_tree($operations_ary) + { + $operation = $operations_ary[self::LOGICAL_OP]; + + foreach ($operations_ary[self::STATEMENTS] as &$condition) + { + switch ($condition[self::LOGICAL_OP]) + { + case 'AND': + case 'OR': + + $condition = ' ( ' . $this->_process_boolean_tree($condition) . ') '; + + break; + case 'NOT': + + $condition = ' NOT (' . $this->_process_boolean_tree($condition) . ') '; + + break; + + default: + + switch (count($condition)) + { + case 3: + + // Typical 3 element clause with {left hand} {operator} {right hand} + switch ($condition[self::COMPARE_OP]) + { + case 'IN': + case 'NOT_IN': + + // As this is used with an IN, assume it is a set of elements for sql_in_set() + $condition = $this->sql_in_set($condition[self::LEFT_STMT], $condition[self::RIGHT_STMT], $condition[self::COMPARE_OP] === 'NOT_IN', true); + + break; + + case 'LIKE': + + $condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_like_expression($condition[self::RIGHT_STMT]) . ' '; + + break; + + case 'NOT_LIKE': + + $condition = $condition[self::LEFT_STMT] . ' ' . $this->sql_not_like_expression($condition[self::RIGHT_STMT]) . ' '; + + break; + + case 'IS_NOT': + + $condition[self::COMPARE_OP] = 'IS NOT'; + + // no break + case 'IS': + + // If the value is NULL, the string of it is the empty string ('') which is not the intended result. + // this should solve that + if ($condition[self::RIGHT_STMT] === null) + { + $condition[self::RIGHT_STMT] = 'NULL'; + } + + $condition = implode(' ', $condition); + + break; + + default: + + $condition = implode(' ', $condition); + + break; + } + + break; + + case 5: + + // Subquery with {left hand} {operator} {compare kind} {SELECT Kind } {Sub Query} + + $result = $condition[self::LEFT_STMT] . ' ' . $condition[self::COMPARE_OP] . ' ' . $condition[self::SUBQUERY_OP] . ' ( '; + $result .= $this->sql_build_query($condition[self::SUBQUERY_SELECT_TYPE], $condition[self::SUBQUERY_BUILD]); + $result .= ' )'; + $condition = $result; + + break; + + default: + // This is an unpredicted clause setup. Just join all elements. + $condition = implode(' ', $condition); + + break; + } + + break; + } + + } + + if ($operation === 'NOT') + { + $operations_ary = implode("", $operations_ary[self::STATEMENTS]); + } + else + { + $operations_ary = implode(" \n $operation ", $operations_ary[self::STATEMENTS]); + } + + return $operations_ary; + } + + + /** + * {@inheritDoc} + */ + function sql_error($sql = '') + { + global $auth, $user, $config; + + // Set var to retrieve errored status + $this->sql_error_triggered = true; + $this->sql_error_sql = $sql; + + $this->sql_error_returned = $this->_sql_error(); + + if (!$this->return_on_error) + { + $message = 'SQL ERROR [ ' . $this->sql_layer . ' ]

' . $this->sql_error_returned['message'] . ' [' . $this->sql_error_returned['code'] . ']'; + + // Show complete SQL error and path to administrators only + // Additionally show complete error on installation or if extended debug mode is enabled + // The DEBUG constant is for development only! + if ((isset($auth) && $auth->acl_get('a_')) || defined('IN_INSTALL') || defined('DEBUG')) + { + $message .= ($sql) ? '

SQL

' . htmlspecialchars($sql) : ''; + } + else + { + // If error occurs in initiating the session we need to use a pre-defined language string + // This could happen if the connection could not be established for example (then we are not able to grab the default language) + if (!isset($user->lang['SQL_ERROR_OCCURRED'])) + { + $message .= '

An sql error occurred while fetching this page. Please contact an administrator if this problem persists.'; + } + else + { + if (!empty($config['board_contact'])) + { + $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + else + { + $message .= '

' . sprintf($user->lang['SQL_ERROR_OCCURRED'], '', ''); + } + } + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + if (strlen($message) > 1024) + { + // We need to define $msg_long_text here to circumvent text stripping. + global $msg_long_text; + $msg_long_text = $message; + + trigger_error(false, E_USER_ERROR); + } + + trigger_error($message, E_USER_ERROR); + } + + if ($this->transaction) + { + $this->sql_transaction('rollback'); + } + + return $this->sql_error_returned; + } + + /** + * {@inheritDoc} + */ + function sql_report($mode, $query = '') + { + global $cache, $starttime, $phpbb_root_path, $phpbb_path_helper; + global $request; + + if (is_object($request) && !$request->variable('explain', false)) + { + return false; + } + + if (!$query && $this->query_hold != '') + { + $query = $this->query_hold; + } + + switch ($mode) + { + case 'display': + if (!empty($cache)) + { + $cache->unload(); + } + $this->sql_close(); + + $mtime = explode(' ', microtime()); + $totaltime = $mtime[0] + $mtime[1] - $starttime; + + echo ' + + + + + SQL Report + + + +
+ +
+
+
+ +
+

SQL Report

+
+

Page generated in ' . round($totaltime, 4) . " seconds with {$this->num_queries['normal']} queries" . (($this->num_queries['cached']) ? " + {$this->num_queries['cached']} " . (($this->num_queries['cached'] == 1) ? 'query' : 'queries') . ' returning data from cache' : '') . '

+ +

Time spent on ' . $this->sql_layer . ' queries: ' . round($this->sql_time, 5) . 's | Time spent on PHP: ' . round($totaltime - $this->sql_time, 5) . 's

+ +

+ ' . $this->sql_report . ' +
+ +
+
+
+ +
+ + '; + + exit_handler(); + + break; + + case 'stop': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $this->sql_report .= ' + + + + + + + + + + + + +
Query #' . $this->num_queries['total'] . '
+ + ' . $this->html_hold . ' + +

+ '; + + if ($this->query_result) + { + if (preg_match('/^(UPDATE|DELETE|REPLACE)/', $query)) + { + $this->sql_report .= 'Affected rows: ' . $this->sql_affectedrows() . ' | '; + } + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed: ' . sprintf('%.5f', $endtime - $this->curtime) . 's'; + } + else + { + $error = $this->sql_error(); + $this->sql_report .= 'FAILED - ' . $this->sql_layer . ' Error ' . $error['code'] . ': ' . htmlspecialchars($error['message']); + } + + $this->sql_report .= '



'; + + $this->sql_time += $endtime - $this->curtime; + break; + + case 'start': + $this->query_hold = $query; + $this->html_hold = ''; + + $this->_sql_report($mode, $query); + + $this->curtime = explode(' ', microtime()); + $this->curtime = $this->curtime[0] + $this->curtime[1]; + + break; + + case 'add_select_row': + + $html_table = func_get_arg(2); + $row = func_get_arg(3); + + if (!$html_table && count($row)) + { + $html_table = true; + $this->html_hold .= ''; + + foreach (array_keys($row) as $val) + { + $this->html_hold .= ''; + } + $this->html_hold .= ''; + } + $this->html_hold .= ''; + + $class = 'row1'; + foreach (array_values($row) as $val) + { + $class = ($class == 'row1') ? 'row2' : 'row1'; + $this->html_hold .= ''; + } + $this->html_hold .= ''; + + return $html_table; + + break; + + case 'fromcache': + + $this->_sql_report($mode, $query); + + break; + + case 'record_fromcache': + + $endtime = func_get_arg(2); + $splittime = func_get_arg(3); + + $time_cache = $endtime - $this->curtime; + $time_db = $splittime - $endtime; + $color = ($time_db > $time_cache) ? 'green' : 'red'; + + $this->sql_report .= '
' . (($val) ? ucwords(str_replace('_', ' ', $val)) : ' ') . '
' . (($val) ? $val : ' ') . '
'; + $this->sql_report .= '
Query results obtained from the cache
'; + $this->sql_report .= '

'; + $this->sql_report .= 'Before: ' . sprintf('%.5f', $this->curtime - $starttime) . 's | After: ' . sprintf('%.5f', $endtime - $starttime) . 's | Elapsed [cache]: ' . sprintf('%.5f', ($time_cache)) . 's | Elapsed [db]: ' . sprintf('%.5f', $time_db) . 's



'; + + // Pad the start time to not interfere with page timing + $starttime += $time_db; + + break; + + default: + + $this->_sql_report($mode, $query); + + break; + } + + return true; + } + + /** + * {@inheritDoc} + */ + function get_estimated_row_count($table_name) + { + return $this->get_row_count($table_name); + } + + /** + * {@inheritDoc} + */ + function get_row_count($table_name) + { + $sql = 'SELECT COUNT(*) AS rows_total + FROM ' . $this->sql_escape($table_name); + $result = $this->sql_query($sql); + $rows_total = $this->sql_fetchfield('rows_total'); + $this->sql_freeresult($result); + + return $rows_total; + } +} diff --git a/phpbb/db/driver/driver_interface.php b/phpbb/db/driver/driver_interface.php new file mode 100644 index 0000000..8b487c5 --- /dev/null +++ b/phpbb/db/driver/driver_interface.php @@ -0,0 +1,453 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +interface driver_interface +{ + /** + * Gets the name of the sql layer. + * + * @return string + */ + public function get_sql_layer(); + + /** + * Gets the name of the database. + * + * @return string + */ + public function get_db_name(); + + /** + * Wildcards for matching any (%) character within LIKE expressions + * + * @return string + */ + public function get_any_char(); + + /** + * Wildcards for matching exactly one (_) character within LIKE expressions + * + * @return string + */ + public function get_one_char(); + + /** + * Gets the time spent into the queries + * + * @return int + */ + public function get_sql_time(); + + /** + * Gets the connect ID. + * + * @return mixed + */ + public function get_db_connect_id(); + + /** + * Indicates if an error was triggered. + * + * @return bool + */ + public function get_sql_error_triggered(); + + /** + * Gets the last faulty query + * + * @return string + */ + public function get_sql_error_sql(); + + /** + * Indicates if we are in a transaction. + * + * @return bool + */ + public function get_transaction(); + + /** + * Gets the returned error. + * + * @return array + */ + public function get_sql_error_returned(); + + /** + * Indicates if multiple insertion can be used + * + * @return bool + */ + public function get_multi_insert(); + + /** + * Set if multiple insertion can be used + * + * @param bool $multi_insert + */ + public function set_multi_insert($multi_insert); + + /** + * Gets the exact number of rows in a specified table. + * + * @param string $table_name Table name + * @return string Exact number of rows in $table_name. + */ + public function get_row_count($table_name); + + /** + * Gets the estimated number of rows in a specified table. + * + * @param string $table_name Table name + * @return string Number of rows in $table_name. + * Prefixed with ~ if estimated (otherwise exact). + */ + public function get_estimated_row_count($table_name); + + /** + * Run LOWER() on DB column of type text (i.e. neither varchar nor char). + * + * @param string $column_name The column name to use + * @return string A SQL statement like "LOWER($column_name)" + */ + public function sql_lower_text($column_name); + + /** + * Display sql error page + * + * @param string $sql The SQL query causing the error + * @return mixed Returns the full error message, if $this->return_on_error + * is set, null otherwise + */ + public function sql_error($sql = ''); + + /** + * Returns whether results of a query need to be buffered to run a + * transaction while iterating over them. + * + * @return bool Whether buffering is required. + */ + public function sql_buffer_nested_transactions(); + + /** + * Run binary OR operator on DB column. + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the OR operator, + * will be converted to (1 << $bit). Is used by options, + * using the number schema... 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (e.g. "= 0") + * @return string A SQL statement like "$column | (1 << $bit) {$compare}" + */ + public function sql_bit_or($column_name, $bit, $compare = ''); + + /** + * Version information about used database + * + * @param bool $raw Only return the fetched sql_server_version + * @param bool $use_cache Is it safe to retrieve the value from the cache + * @return string sql server version + */ + public function sql_server_info($raw = false, $use_cache = true); + + /** + * Return on error or display error message + * + * @param bool $fail Should we return on errors, or stop + * @return null + */ + public function sql_return_on_error($fail = false); + + /** + * Build sql statement from an array + * + * @param string $query Should be on of the following strings: + * INSERT, INSERT_SELECT, UPDATE, SELECT, DELETE + * @param array $assoc_ary Array with "column => value" pairs + * @return string A SQL statement like "c1 = 'a' AND c2 = 'b'" + */ + public function sql_build_array($query, $assoc_ary = array()); + + /** + * Fetch all rows + * + * @param mixed $query_id Already executed query to get the rows from, + * if false, the last query will be used. + * @return mixed Nested array if the query had rows, false otherwise + */ + public function sql_fetchrowset($query_id = false); + + /** + * SQL Transaction + * + * @param string $status Should be one of the following strings: + * begin, commit, rollback + * @return mixed Buffered, seekable result handle, false on error + */ + public function sql_transaction($status = 'begin'); + + /** + * Build a concatenated expression + * + * @param string $expr1 Base SQL expression where we append the second one + * @param string $expr2 SQL expression that is appended to the first expression + * @return string Concatenated string + */ + public function sql_concatenate($expr1, $expr2); + + /** + * Build a case expression + * + * Note: The two statements action_true and action_false must have the same + * data type (int, vchar, ...) in the database! + * + * @param string $condition The condition which must be true, + * to use action_true rather then action_else + * @param string $action_true SQL expression that is used, if the condition is true + * @param mixed $action_false SQL expression that is used, if the condition is false + * @return string CASE expression including the condition and statements + */ + public function sql_case($condition, $action_true, $action_false = false); + + /** + * Build sql statement from array for select and select distinct statements + * + * Possible query values: SELECT, SELECT_DISTINCT + * + * @param string $query Should be one of: SELECT, SELECT_DISTINCT + * @param array $array Array with the query data: + * SELECT A comma imploded list of columns to select + * FROM Array with "table => alias" pairs, + * (alias can also be an array) + * Optional: LEFT_JOIN Array of join entries: + * FROM Table that should be joined + * ON Condition for the join + * Optional: WHERE Where SQL statement + * Optional: GROUP_BY Group by SQL statement + * Optional: ORDER_BY Order by SQL statement + * @return string A SQL statement ready for execution + */ + public function sql_build_query($query, $array); + + /** + * Fetch field + * if rownum is false, the current row is used, else it is pointing to the row (zero-based) + * + * @param string $field Name of the column + * @param mixed $rownum Row number, if false the current row will be used + * and the row curser will point to the next row + * Note: $rownum is 0 based + * @param mixed $query_id Already executed query to get the rows from, + * if false, the last query will be used. + * @return mixed String value of the field in the selected row, + * false, if the row does not exist + */ + public function sql_fetchfield($field, $rownum = false, $query_id = false); + + /** + * Fetch current row + * + * @param mixed $query_id Already executed query to get the rows from, + * if false, the last query will be used. + * @return mixed Array with the current row, + * false, if the row does not exist + */ + public function sql_fetchrow($query_id = false); + + /** + * Returns SQL string to cast a string expression to an int. + * + * @param string $expression An expression evaluating to string + * @return string Expression returning an int + */ + public function cast_expr_to_bigint($expression); + + /** + * Get last inserted id after insert statement + * + * @return string Autoincrement value of the last inserted row + */ + public function sql_nextid(); + + /** + * Add to query count + * + * @param bool $cached Is this query cached? + * @return null + */ + public function sql_add_num_queries($cached = false); + + /** + * Build LIMIT query + * + * @param string $query The SQL query to execute + * @param int $total The number of rows to select + * @param int $offset + * @param int $cache_ttl Either 0 to avoid caching or + * the time in seconds which the result shall be kept in cache + * @return mixed Buffered, seekable result handle, false on error + */ + public function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0); + + /** + * Base query method + * + * @param string $query The SQL query to execute + * @param int $cache_ttl Either 0 to avoid caching or + * the time in seconds which the result shall be kept in cache + * @return mixed Buffered, seekable result handle, false on error + */ + public function sql_query($query = '', $cache_ttl = 0); + + /** + * Returns SQL string to cast an integer expression to a string. + * + * @param string $expression An expression evaluating to int + * @return string Expression returning a string + */ + public function cast_expr_to_string($expression); + + /** + * Connect to server + * + * @param string $sqlserver Address of the database server + * @param string $sqluser User name of the SQL user + * @param string $sqlpassword Password of the SQL user + * @param string $database Name of the database + * @param mixed $port Port of the database server + * @param bool $persistency + * @param bool $new_link Should a new connection be established + * @return mixed Connection ID on success, string error message otherwise + */ + public function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false); + + /** + * Run binary AND operator on DB column. + * Results in sql statement: "{$column_name} & (1 << {$bit}) {$compare}" + * + * @param string $column_name The column name to use + * @param int $bit The value to use for the AND operator, + * will be converted to (1 << $bit). Is used by + * options, using the number schema: 0, 1, 2...29 + * @param string $compare Any custom SQL code after the check (for example "= 0") + * @return string A SQL statement like: "{$column} & (1 << {$bit}) {$compare}" + */ + public function sql_bit_and($column_name, $bit, $compare = ''); + + /** + * Free sql result + * + * @param mixed $query_id Already executed query result, + * if false, the last query will be used. + * @return null + */ + public function sql_freeresult($query_id = false); + + /** + * Return number of sql queries and cached sql queries used + * + * @param bool $cached Should we return the number of cached or normal queries? + * @return int Number of queries that have been executed + */ + public function sql_num_queries($cached = false); + + /** + * Run more than one insert statement. + * + * @param string $table Table name to run the statements on + * @param array $sql_ary Multi-dimensional array holding the statement data + * @return bool false if no statements were executed. + */ + public function sql_multi_insert($table, $sql_ary); + + /** + * Return number of affected rows + * + * @return mixed Number of the affected rows by the last query + * false if no query has been run before + */ + public function sql_affectedrows(); + + /** + * DBAL garbage collection, close SQL connection + * + * @return mixed False if no connection was opened before, + * Server response otherwise + */ + public function sql_close(); + + /** + * Seek to given row number + * + * @param mixed $rownum Row number the curser should point to + * Note: $rownum is 0 based + * @param mixed $query_id ID of the query to set the row cursor on + * if false, the last query will be used. + * $query_id will then be set correctly + * @return bool False if something went wrong + */ + public function sql_rowseek($rownum, &$query_id); + + /** + * Escape string used in sql query + * + * @param string $msg String to be escaped + * @return string Escaped version of $msg + */ + public function sql_escape($msg); + + /** + * Correctly adjust LIKE expression for special characters + * Some DBMS are handling them in a different way + * + * @param string $expression The expression to use. Every wildcard is + * escaped, except $this->any_char and $this->one_char + * @return string A SQL statement like: "LIKE 'bertie_%'" + */ + public function sql_like_expression($expression); + + /** + * Correctly adjust NOT LIKE expression for special characters + * Some DBMS are handling them in a different way + * + * @param string $expression The expression to use. Every wildcard is + * escaped, except $this->any_char and $this->one_char + * @return string A SQL statement like: "NOT LIKE 'bertie_%'" + */ + public function sql_not_like_expression($expression); + + /** + * Explain queries + * + * @param string $mode Available modes: display, start, stop, + * add_select_row, fromcache, record_fromcache + * @param string $query The Query that should be explained + * @return mixed Either a full HTML page, boolean or null + */ + public function sql_report($mode, $query = ''); + + /** + * Build IN or NOT IN sql comparison string, uses <> or = on single element + * arrays to improve comparison speed + * + * @param string $field Name of the sql column that shall be compared + * @param array $array Array of values that are (not) allowed + * @param bool $negate true for NOT IN (), false for IN () + * @param bool $allow_empty_set If true, allow $array to be empty, + * this function will return 1=1 or 1=0 then. + * @return string A SQL statement like: "IN (1, 2, 3, 4)" or "= 1" + */ + public function sql_in_set($field, $array, $negate = false, $allow_empty_set = false); +} diff --git a/phpbb/db/driver/factory.php b/phpbb/db/driver/factory.php new file mode 100644 index 0000000..fb3a826 --- /dev/null +++ b/phpbb/db/driver/factory.php @@ -0,0 +1,443 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +use \Symfony\Component\DependencyInjection\ContainerInterface; + +/** +* Database Abstraction Layer +*/ +class factory implements driver_interface +{ + /** + * @var driver_interface + */ + protected $driver = null; + + /** + * @var ContainerInterface + */ + protected $container; + + /** + * Constructor. + * + * @param ContainerInterface $container A ContainerInterface instance + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Return the current driver (and retrieved it from the container if necessary) + * + * @return driver_interface + */ + protected function get_driver() + { + if ($this->driver === null) + { + $this->driver = $this->container->get('dbal.conn.driver'); + } + + return $this->driver; + } + + /** + * Set the current driver + * + * @param driver_interface $driver + */ + public function set_driver(driver_interface $driver) + { + $this->driver = $driver; + } + + /** + * {@inheritdoc} + */ + public function get_sql_layer() + { + return $this->get_driver()->get_sql_layer(); + } + + /** + * {@inheritdoc} + */ + public function get_db_name() + { + return $this->get_driver()->get_db_name(); + } + + /** + * {@inheritdoc} + */ + public function get_any_char() + { + return $this->get_driver()->get_any_char(); + } + + /** + * {@inheritdoc} + */ + public function get_one_char() + { + return $this->get_driver()->get_one_char(); + } + + /** + * {@inheritdoc} + */ + public function get_db_connect_id() + { + return $this->get_driver()->get_db_connect_id(); + } + + /** + * {@inheritdoc} + */ + public function get_sql_error_triggered() + { + return $this->get_driver()->get_sql_error_triggered(); + } + + /** + * {@inheritdoc} + */ + public function get_sql_error_sql() + { + return $this->get_driver()->get_sql_error_sql(); + } + + /** + * {@inheritdoc} + */ + public function get_transaction() + { + return $this->get_driver()->get_transaction(); + } + + /** + * {@inheritdoc} + */ + public function get_sql_time() + { + return $this->get_driver()->get_sql_time(); + } + + /** + * {@inheritdoc} + */ + public function get_sql_error_returned() + { + return $this->get_driver()->get_sql_error_returned(); + } + + /** + * {@inheritdoc} + */ + public function get_multi_insert() + { + return $this->get_driver()->get_multi_insert(); + } + + /** + * {@inheritdoc} + */ + public function set_multi_insert($multi_insert) + { + $this->get_driver()->set_multi_insert($multi_insert); + } + + /** + * {@inheritdoc} + */ + public function get_row_count($table_name) + { + return $this->get_driver()->get_row_count($table_name); + } + + /** + * {@inheritdoc} + */ + public function get_estimated_row_count($table_name) + { + return $this->get_driver()->get_estimated_row_count($table_name); + } + + /** + * {@inheritdoc} + */ + public function sql_lower_text($column_name) + { + return $this->get_driver()->sql_lower_text($column_name); + } + + /** + * {@inheritdoc} + */ + public function sql_error($sql = '') + { + return $this->get_driver()->sql_error($sql); + } + + /** + * {@inheritdoc} + */ + public function sql_buffer_nested_transactions() + { + return $this->get_driver()->sql_buffer_nested_transactions(); + } + + /** + * {@inheritdoc} + */ + public function sql_bit_or($column_name, $bit, $compare = '') + { + return $this->get_driver()->sql_bit_or($column_name, $bit, $compare); + } + + /** + * {@inheritdoc} + */ + public function sql_server_info($raw = false, $use_cache = true) + { + return $this->get_driver()->sql_server_info($raw, $use_cache); + } + + /** + * {@inheritdoc} + */ + public function sql_return_on_error($fail = false) + { + return $this->get_driver()->sql_return_on_error($fail); + } + + /** + * {@inheritdoc} + */ + public function sql_build_array($query, $assoc_ary = array()) + { + return $this->get_driver()->sql_build_array($query, $assoc_ary); + } + + /** + * {@inheritdoc} + */ + public function sql_fetchrowset($query_id = false) + { + return $this->get_driver()->sql_fetchrowset($query_id); + } + + /** + * {@inheritdoc} + */ + public function sql_transaction($status = 'begin') + { + return $this->get_driver()->sql_transaction($status); + } + + /** + * {@inheritdoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $this->get_driver()->sql_concatenate($expr1, $expr2); + } + + /** + * {@inheritdoc} + */ + public function sql_case($condition, $action_true, $action_false = false) + { + return $this->get_driver()->sql_case($condition, $action_true, $action_false); + } + + /** + * {@inheritdoc} + */ + public function sql_build_query($query, $array) + { + return $this->get_driver()->sql_build_query($query, $array); + } + + /** + * {@inheritdoc} + */ + public function sql_fetchfield($field, $rownum = false, $query_id = false) + { + return $this->get_driver()->sql_fetchfield($field, $rownum, $query_id); + } + + /** + * {@inheritdoc} + */ + public function sql_fetchrow($query_id = false) + { + return $this->get_driver()->sql_fetchrow($query_id); + } + + /** + * {@inheritdoc} + */ + public function cast_expr_to_bigint($expression) + { + return $this->get_driver()->cast_expr_to_bigint($expression); + } + + /** + * {@inheritdoc} + */ + public function sql_nextid() + { + return $this->get_driver()->sql_nextid(); + } + + /** + * {@inheritdoc} + */ + public function sql_add_num_queries($cached = false) + { + return $this->get_driver()->sql_add_num_queries($cached); + } + + /** + * {@inheritdoc} + */ + public function sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + return $this->get_driver()->sql_query_limit($query, $total, $offset, $cache_ttl); + } + + /** + * {@inheritdoc} + */ + public function sql_query($query = '', $cache_ttl = 0) + { + return $this->get_driver()->sql_query($query, $cache_ttl); + } + + /** + * {@inheritdoc} + */ + public function cast_expr_to_string($expression) + { + return $this->get_driver()->cast_expr_to_string($expression); + } + + /** + * {@inheritdoc} + */ + public function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + throw new \Exception('Disabled method.'); + } + + /** + * {@inheritdoc} + */ + public function sql_bit_and($column_name, $bit, $compare = '') + { + return $this->get_driver()->sql_bit_and($column_name, $bit, $compare); + } + + /** + * {@inheritdoc} + */ + public function sql_freeresult($query_id = false) + { + return $this->get_driver()->sql_freeresult($query_id); + } + + /** + * {@inheritdoc} + */ + public function sql_num_queries($cached = false) + { + return $this->get_driver()->sql_num_queries($cached); + } + + /** + * {@inheritdoc} + */ + public function sql_multi_insert($table, $sql_ary) + { + return $this->get_driver()->sql_multi_insert($table, $sql_ary); + } + + /** + * {@inheritdoc} + */ + public function sql_affectedrows() + { + return $this->get_driver()->sql_affectedrows(); + } + + /** + * {@inheritdoc} + */ + public function sql_close() + { + return $this->get_driver()->sql_close(); + } + + /** + * {@inheritdoc} + */ + public function sql_rowseek($rownum, &$query_id) + { + return $this->get_driver()->sql_rowseek($rownum, $query_id); + } + + /** + * {@inheritdoc} + */ + public function sql_escape($msg) + { + return $this->get_driver()->sql_escape($msg); + } + + /** + * {@inheritdoc} + */ + public function sql_like_expression($expression) + { + return $this->get_driver()->sql_like_expression($expression); + } + + /** + * {@inheritdoc} + */ + public function sql_not_like_expression($expression) + { + return $this->get_driver()->sql_not_like_expression($expression); + } + + /** + * {@inheritdoc} + */ + public function sql_report($mode, $query = '') + { + return $this->get_driver()->sql_report($mode, $query); + } + + /** + * {@inheritdoc} + */ + public function sql_in_set($field, $array, $negate = false, $allow_empty_set = false) + { + return $this->get_driver()->sql_in_set($field, $array, $negate, $allow_empty_set); + } +} diff --git a/phpbb/db/driver/mssql_base.php b/phpbb/db/driver/mssql_base.php new file mode 100644 index 0000000..98d16ca --- /dev/null +++ b/phpbb/db/driver/mssql_base.php @@ -0,0 +1,79 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* MSSQL Database Base Abstraction Layer + */ +abstract class mssql_base extends \phpbb\db\driver\driver +{ + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return $expr1 . ' + ' . $expr2; + } + + /** + * {@inheritDoc} + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * {@inheritDoc} + */ + function sql_lower_text($column_name) + { + return "LOWER(SUBSTRING($column_name, 1, DATALENGTH($column_name)))"; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * Build NOT LIKE expression + * @access private + */ + function _sql_not_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * {@inheritDoc} + */ + function cast_expr_to_bigint($expression) + { + return 'CONVERT(BIGINT, ' . $expression . ')'; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } +} diff --git a/phpbb/db/driver/mssql_odbc.php b/phpbb/db/driver/mssql_odbc.php new file mode 100644 index 0000000..9d9ad60 --- /dev/null +++ b/phpbb/db/driver/mssql_odbc.php @@ -0,0 +1,385 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* Unified ODBC functions +* Unified ODBC functions support any database having ODBC driver, for example Adabas D, IBM DB2, iODBC, Solid, Sybase SQL Anywhere... +* Here we only support MSSQL Server 2000+ because of the provided schema +* +* @note number of bytes returned for returning data depends on odbc.defaultlrl php.ini setting. +* If it is limited to 4K for example only 4K of data is returned max, resulting in incomplete theme data for example. +* @note odbc.defaultbinmode may affect UTF8 characters +*/ +class mssql_odbc extends \phpbb\db\driver\mssql_base +{ + var $last_query_text = ''; + var $connect_error = ''; + + /** + * {@inheritDoc} + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + $max_size = @ini_get('odbc.defaultlrl'); + if (!empty($max_size)) + { + $unit = strtolower(substr($max_size, -1, 1)); + $max_size = (int) $max_size; + + if ($unit == 'k') + { + $max_size = floor($max_size / 1024); + } + else if ($unit == 'g') + { + $max_size *= 1024; + } + else if (is_numeric($unit)) + { + $max_size = floor((int) ($max_size . $unit) / 1048576); + } + $max_size = max(8, $max_size) . 'M'; + + @ini_set('odbc.defaultlrl', $max_size); + } + + if ($this->persistency) + { + if (!function_exists('odbc_pconnect')) + { + $this->connect_error = 'odbc_pconnect function does not exist, is odbc extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @odbc_pconnect($this->server, $this->user, $sqlpassword); + } + else + { + if (!function_exists('odbc_connect')) + { + $this->connect_error = 'odbc_connect function does not exist, is odbc extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @odbc_connect($this->server, $this->user, $sqlpassword); + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * {@inheritDoc} + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssqlodbc_version')) === false) + { + $result_id = @odbc_exec($this->db_connect_id, "SELECT SERVERPROPERTY('productversion'), SERVERPROPERTY('productlevel'), SERVERPROPERTY('edition')"); + + $row = false; + if ($result_id) + { + $row = odbc_fetch_array($result_id); + odbc_free_result($result_id); + } + + $this->sql_server_version = ($row) ? trim(implode(' ', $row)) : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('mssqlodbc_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL (ODBC)
' . $this->sql_server_version : 'MSSQL (ODBC)'; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @odbc_exec($this->db_connect_id, 'BEGIN TRANSACTION'); + break; + + case 'commit': + return @odbc_exec($this->db_connect_id, 'COMMIT TRANSACTION'); + break; + + case 'rollback': + return @odbc_exec($this->db_connect_id, 'ROLLBACK TRANSACTION'); + break; + } + + return true; + } + + /** + * {@inheritDoc} + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->curtime = microtime(true); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @odbc_exec($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->sql_time += microtime(true) - $this->curtime; + } + + if (!$this->query_result) + { + return false; + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // Since TOP is only returning a set number of rows we won't need it if total is set to 0 (return all rows) + if ($total) + { + // We need to grab the total number of rows + the offset number of rows to get the correct result + if (strpos($query, 'SELECT DISTINCT') === 0) + { + $query = 'SELECT DISTINCT TOP ' . ($total + $offset) . ' ' . substr($query, 15); + } + else + { + $query = 'SELECT TOP ' . ($total + $offset) . ' ' . substr($query, 6); + } + } + + $result = $this->sql_query($query, $cache_ttl); + + // Seek by $offset rows + if ($offset) + { + $this->sql_rowseek($offset, $result); + } + + return $result; + } + + /** + * {@inheritDoc} + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @odbc_num_rows($this->query_result) : false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id) ? odbc_fetch_array($query_id) : false; + } + + /** + * {@inheritDoc} + */ + function sql_nextid() + { + $result_id = @odbc_exec($this->db_connect_id, 'SELECT @@IDENTITY'); + + if ($result_id) + { + if (odbc_fetch_array($result_id)) + { + $id = odbc_result($result_id, 1); + odbc_free_result($result_id); + return $id; + } + odbc_free_result($result_id); + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return odbc_free_result($query_id); + } + + return false; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (function_exists('odbc_errormsg')) + { + $error = array( + 'message' => @odbc_errormsg(), + 'code' => @odbc_error(), + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @odbc_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @odbc_exec($this->db_connect_id, $query); + if ($result) + { + while ($void = odbc_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + odbc_free_result($result); + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpbb/db/driver/mssqlnative.php b/phpbb/db/driver/mssqlnative.php new file mode 100644 index 0000000..a4dcac5 --- /dev/null +++ b/phpbb/db/driver/mssqlnative.php @@ -0,0 +1,451 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* This is the MS SQL Server Native database abstraction layer. +* PHP mssql native driver required. +* @author Chris Pucci +* +*/ + +namespace phpbb\db\driver; + +class mssqlnative extends \phpbb\db\driver\mssql_base +{ + var $m_insert_id = null; + var $last_query_text = ''; + var $query_options = array(); + var $connect_error = ''; + + /** + * {@inheritDoc} + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + // Test for driver support, to avoid suppressed fatal error + if (!function_exists('sqlsrv_connect')) + { + $this->connect_error = 'Native MS SQL Server driver for PHP is missing or needs to be updated. Version 1.1 or later is required to install phpBB3. You can download the driver from: http://www.microsoft.com/sqlserver/2005/en/us/PHP-Driver.aspx'; + return $this->sql_error(''); + } + + //set up connection variables + $this->persistency = $persistency; + $this->user = $sqluser; + $this->dbname = $database; + $port_delimiter = (defined('PHP_OS') && substr(PHP_OS, 0, 3) === 'WIN') ? ',' : ':'; + $this->server = $sqlserver . (($port) ? $port_delimiter . $port : ''); + + //connect to database + $this->db_connect_id = sqlsrv_connect($this->server, array( + 'Database' => $this->dbname, + 'UID' => $this->user, + 'PWD' => $sqlpassword, + 'CharacterSet' => 'UTF-8' + )); + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * {@inheritDoc} + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mssql_version')) === false) + { + $arr_server_info = sqlsrv_server_info($this->db_connect_id); + $this->sql_server_version = $arr_server_info['SQLServerVersion']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mssql_version', $this->sql_server_version); + } + } + + if ($raw) + { + return $this->sql_server_version; + } + + return ($this->sql_server_version) ? 'MSSQL
' . $this->sql_server_version : 'MSSQL'; + } + + /** + * {@inheritDoc} + */ + function sql_buffer_nested_transactions() + { + return true; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return sqlsrv_begin_transaction($this->db_connect_id); + break; + + case 'commit': + return sqlsrv_commit($this->db_connect_id); + break; + + case 'rollback': + return sqlsrv_rollback($this->db_connect_id); + break; + } + return true; + } + + /** + * {@inheritDoc} + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->curtime = microtime(true); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @sqlsrv_query($this->db_connect_id, $query, array(), $this->query_options)) === false) + { + $this->sql_error($query); + } + // reset options for next query + $this->query_options = array(); + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->sql_time += microtime(true) - $this->curtime; + } + + if (!$this->query_result) + { + return false; + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // total == 0 means all results - not zero results + if ($offset == 0 && $total !== 0) + { + if (strpos($query, "SELECT") === false) + { + $query = "TOP {$total} " . $query; + } + else + { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP '.$total, $query); + } + } + else if ($offset > 0) + { + $query = preg_replace('/SELECT(\s*DISTINCT)?/Dsi', 'SELECT$1 TOP(10000000) ', $query); + $query = 'SELECT * + FROM (SELECT sub2.*, ROW_NUMBER() OVER(ORDER BY sub2.line2) AS line3 + FROM (SELECT 1 AS line2, sub1.* FROM (' . $query . ') AS sub1) as sub2) AS sub3'; + + if ($total > 0) + { + $query .= ' WHERE line3 BETWEEN ' . ($offset+1) . ' AND ' . ($offset + $total); + } + else + { + $query .= ' WHERE line3 > ' . $offset; + } + } + + $result = $this->sql_query($query, $cache_ttl); + + return $result; + } + + /** + * {@inheritDoc} + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @sqlsrv_rows_affected($this->query_result) : false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + if (!$query_id) + { + return false; + } + + $row = sqlsrv_fetch_array($query_id, SQLSRV_FETCH_ASSOC); + + if ($row) + { + foreach ($row as $key => $value) + { + $row[$key] = ($value === ' ' || $value === null) ? '' : $value; + } + + // remove helper values from LIMIT queries + if (isset($row['line2'])) + { + unset($row['line2'], $row['line3']); + } + } + return ($row !== null) ? $row : false; + } + + /** + * {@inheritDoc} + */ + function sql_nextid() + { + $result_id = @sqlsrv_query($this->db_connect_id, 'SELECT @@IDENTITY'); + + if ($result_id) + { + $row = sqlsrv_fetch_array($result_id); + $id = $row[0]; + sqlsrv_free_stmt($result_id); + return $id; + } + else + { + return false; + } + } + + /** + * {@inheritDoc} + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return sqlsrv_free_stmt($query_id); + } + + return false; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (function_exists('sqlsrv_errors')) + { + $errors = @sqlsrv_errors(SQLSRV_ERR_ERRORS); + $error_message = ''; + $code = 0; + + if ($errors != null) + { + foreach ($errors as $error) + { + $error_message .= "SQLSTATE: " . $error['SQLSTATE'] . "\n"; + $error_message .= "code: " . $error['code'] . "\n"; + $code = $error['code']; + $error_message .= "message: " . $error['message'] . "\n"; + } + $this->last_error_result = $error_message; + $error = $this->last_error_result; + } + else + { + $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); + } + + $error = array( + 'message' => $error, + 'code' => $code, + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @sqlsrv_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + $html_table = false; + @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT ON;'); + if ($result = @sqlsrv_query($this->db_connect_id, $query)) + { + sqlsrv_next_result($result); + while ($row = sqlsrv_fetch_array($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + sqlsrv_free_stmt($result); + } + @sqlsrv_query($this->db_connect_id, 'SET SHOWPLAN_TEXT OFF;'); + + if ($html_table) + { + $this->html_hold .= '
'; + } + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @sqlsrv_query($this->db_connect_id, $query); + if ($result) + { + while ($void = sqlsrv_fetch_array($result)) + { + // Take the time spent on parsing rows into account + } + sqlsrv_free_stmt($result); + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } + + /** + * Utility method used to retrieve number of rows + * Emulates mysql_num_rows + * Used in acp_database.php -> write_data_mssqlnative() + * Requires a static or keyset cursor to be definde via + * mssqlnative_set_query_options() + */ + function mssqlnative_num_rows($res) + { + if ($res !== false) + { + return sqlsrv_num_rows($res); + } + else + { + return false; + } + } + + /** + * Allows setting mssqlnative specific query options passed to sqlsrv_query as 4th parameter. + */ + function mssqlnative_set_query_options($options) + { + $this->query_options = $options; + } +} diff --git a/phpbb/db/driver/mysql.php b/phpbb/db/driver/mysql.php new file mode 100644 index 0000000..a94e88b --- /dev/null +++ b/phpbb/db/driver/mysql.php @@ -0,0 +1,503 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* MySQL4 Database Abstraction Layer +* Compatible with: +* MySQL 3.23+ +* MySQL 4.0+ +* MySQL 4.1+ +* MySQL 5.0+ +*/ +class mysql extends \phpbb\db\driver\mysql_base +{ + var $multi_insert = true; + var $connect_error = ''; + + /** + * {@inheritDoc} + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $this->sql_layer = 'mysql4'; + + if ($this->persistency) + { + if (!function_exists('mysql_pconnect')) + { + $this->connect_error = 'mysql_pconnect function does not exist, is mysql extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @mysql_pconnect($this->server, $this->user, $sqlpassword); + } + else + { + if (!function_exists('mysql_connect')) + { + $this->connect_error = 'mysql_connect function does not exist, is mysql extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @mysql_connect($this->server, $this->user, $sqlpassword, $new_link); + } + + if ($this->db_connect_id && $this->dbname != '') + { + if (@mysql_select_db($this->dbname, $this->db_connect_id)) + { + // Determine what version we are using and if it natively supports UNICODE + if (version_compare($this->sql_server_info(true), '4.1.0', '>=')) + { + @mysql_query("SET NAMES 'utf8'", $this->db_connect_id); + + // enforce strict mode on databases that support it + if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) + { + $result = @mysql_query('SELECT @@session.sql_mode AS sql_mode', $this->db_connect_id); + if ($result) + { + $row = mysql_fetch_assoc($result); + mysql_free_result($result); + $modes = array_map('trim', explode(',', $row['sql_mode'])); + } + else + { + $modes = array(); + } + + // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES + if (!in_array('TRADITIONAL', $modes)) + { + if (!in_array('STRICT_ALL_TABLES', $modes)) + { + $modes[] = 'STRICT_ALL_TABLES'; + } + + if (!in_array('STRICT_TRANS_TABLES', $modes)) + { + $modes[] = 'STRICT_TRANS_TABLES'; + } + } + + $mode = implode(',', $modes); + @mysql_query("SET SESSION sql_mode='{$mode}'", $this->db_connect_id); + } + } + else if (version_compare($this->sql_server_info(true), '4.0.0', '<')) + { + $this->sql_layer = 'mysql'; + } + + return $this->db_connect_id; + } + } + + return $this->sql_error(''); + } + + /** + * {@inheritDoc} + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysql_version')) === false) + { + $result = @mysql_query('SELECT VERSION() AS version', $this->db_connect_id); + if ($result) + { + $row = mysql_fetch_assoc($result); + mysql_free_result($result); + + $this->sql_server_version = $row['version']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mysql_version', $this->sql_server_version); + } + } + } + + return ($raw) ? $this->sql_server_version : 'MySQL ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @mysql_query('BEGIN', $this->db_connect_id); + break; + + case 'commit': + return @mysql_query('COMMIT', $this->db_connect_id); + break; + + case 'rollback': + return @mysql_query('ROLLBACK', $this->db_connect_id); + break; + } + + return true; + } + + /** + * {@inheritDoc} + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->curtime = microtime(true); + } + + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @mysql_query($query, $this->db_connect_id)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->sql_time += microtime(true) - $this->curtime; + } + + if (!$this->query_result) + { + return false; + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * {@inheritDoc} + */ + function sql_affectedrows() + { + if ($this->db_connect_id) + { + // We always want the number of matched rows + // instead of changed rows, when running an update. + // So when mysql_info() returns the number of matched rows + // we return that one instead of mysql_affected_rows() + $mysql_info = @mysql_info($this->db_connect_id); + if ($mysql_info !== false) + { + $match = array(); + preg_match('#^Rows matched: (\d)+ Changed: (\d)+ Warnings: (\d)+$#', $mysql_info, $match); + if (isset($match[1])) + { + return $match[1]; + } + } + + return @mysql_affected_rows($this->db_connect_id); + } + return false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id) ? mysql_fetch_assoc($query_id) : false; + } + + /** + * {@inheritDoc} + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id !== false) ? @mysql_data_seek($query_id, $rownum) : false; + } + + /** + * {@inheritDoc} + */ + function sql_nextid() + { + return ($this->db_connect_id) ? @mysql_insert_id($this->db_connect_id) : false; + } + + /** + * {@inheritDoc} + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return mysql_free_result($query_id); + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_escape($msg) + { + if (!$this->db_connect_id) + { + return @mysql_real_escape_string($msg); + } + + return @mysql_real_escape_string($msg, $this->db_connect_id); + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if ($this->db_connect_id) + { + $error = array( + 'message' => @mysql_error($this->db_connect_id), + 'code' => @mysql_errno($this->db_connect_id), + ); + } + else if (function_exists('mysql_error')) + { + $error = array( + 'message' => @mysql_error(), + 'code' => @mysql_errno(), + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @mysql_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + static $test_prof; + + // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING + if ($test_prof === null) + { + $test_prof = false; + if (version_compare($this->sql_server_info(true), '5.0.37', '>=') && version_compare($this->sql_server_info(true), '5.1', '<')) + { + $test_prof = true; + } + } + + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + // begin profiling + if ($test_prof) + { + @mysql_query('SET profiling = 1;', $this->db_connect_id); + } + + if ($result = @mysql_query("EXPLAIN $explain_query", $this->db_connect_id)) + { + while ($row = mysql_fetch_assoc($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + mysql_free_result($result); + } + + if ($html_table) + { + $this->html_hold .= ''; + } + + if ($test_prof) + { + $html_table = false; + + // get the last profile + if ($result = @mysql_query('SHOW PROFILE ALL;', $this->db_connect_id)) + { + $this->html_hold .= '
'; + while ($row = mysql_fetch_assoc($result)) + { + // make HTML safe + if (!empty($row['Source_function'])) + { + $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); + } + + // remove unsupported features + foreach ($row as $key => $val) + { + if ($val === null) + { + unset($row[$key]); + } + } + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + mysql_free_result($result); + } + + if ($html_table) + { + $this->html_hold .= ''; + } + + @mysql_query('SET profiling = 0;', $this->db_connect_id); + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mysql_query($query, $this->db_connect_id); + if ($result) + { + while ($void = mysql_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + mysql_free_result($result); + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpbb/db/driver/mysql_base.php b/phpbb/db/driver/mysql_base.php new file mode 100644 index 0000000..5e0b359 --- /dev/null +++ b/phpbb/db/driver/mysql_base.php @@ -0,0 +1,138 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* Abstract MySQL Database Base Abstraction Layer +*/ +abstract class mysql_base extends \phpbb\db\driver\driver +{ + /** + * {@inheritDoc} + */ + public function sql_concatenate($expr1, $expr2) + { + return 'CONCAT(' . $expr1 . ', ' . $expr2 . ')'; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + // MySQL 4.1+ no longer supports -1 in limit queries + $total = '18446744073709551615'; + } + + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * {@inheritDoc} + */ + function get_estimated_row_count($table_name) + { + $table_status = $this->get_table_status($table_name); + + if (isset($table_status['Engine'])) + { + if ($table_status['Engine'] === 'MyISAM') + { + return $table_status['Rows']; + } + else if ($table_status['Engine'] === 'InnoDB' && $table_status['Rows'] > 100000) + { + return '~' . $table_status['Rows']; + } + } + + return parent::get_row_count($table_name); + } + + /** + * {@inheritDoc} + */ + function get_row_count($table_name) + { + $table_status = $this->get_table_status($table_name); + + if (isset($table_status['Engine']) && $table_status['Engine'] === 'MyISAM') + { + return $table_status['Rows']; + } + + return parent::get_row_count($table_name); + } + + /** + * Gets some information about the specified table. + * + * @param string $table_name Table name + * + * @return array + * + * @access protected + */ + function get_table_status($table_name) + { + $sql = "SHOW TABLE STATUS + LIKE '" . $this->sql_escape($table_name) . "'"; + $result = $this->sql_query($sql); + $table_status = $this->sql_fetchrow($result); + $this->sql_freeresult($result); + + return $table_status; + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression; + } + + /** + * Build NOT LIKE expression + * @access private + */ + function _sql_not_like_expression($expression) + { + return $expression; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + switch ($stage) + { + case 'FROM': + $data = '(' . $data . ')'; + break; + } + + return $data; + } +} diff --git a/phpbb/db/driver/mysqli.php b/phpbb/db/driver/mysqli.php new file mode 100644 index 0000000..d43e201 --- /dev/null +++ b/phpbb/db/driver/mysqli.php @@ -0,0 +1,490 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* MySQLi Database Abstraction Layer +* mysqli-extension has to be compiled with: +* MySQL 4.1+ or MySQL 5.0+ +*/ +class mysqli extends \phpbb\db\driver\mysql_base +{ + var $multi_insert = true; + var $connect_error = ''; + + /** + * {@inheritDoc} + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + if (!function_exists('mysqli_connect')) + { + $this->connect_error = 'mysqli_connect function does not exist, is mysqli extension installed?'; + return $this->sql_error(''); + } + + $this->persistency = $persistency; + $this->user = $sqluser; + + // If persistent connection, set dbhost to localhost when empty and prepend it with 'p:' prefix + $this->server = ($this->persistency) ? 'p:' . (($sqlserver) ? $sqlserver : 'localhost') : $sqlserver; + + $this->dbname = $database; + $port = (!$port) ? null : $port; + + // If port is set and it is not numeric, most likely mysqli socket is set. + // Try to map it to the $socket parameter. + $socket = null; + if ($port) + { + if (is_numeric($port)) + { + $port = (int) $port; + } + else + { + $socket = $port; + $port = null; + } + } + + $this->db_connect_id = mysqli_init(); + + if (!@mysqli_real_connect($this->db_connect_id, $this->server, $this->user, $sqlpassword, $this->dbname, $port, $socket, MYSQLI_CLIENT_FOUND_ROWS)) + { + $this->db_connect_id = ''; + } + + if ($this->db_connect_id && $this->dbname != '') + { + @mysqli_query($this->db_connect_id, "SET NAMES 'utf8'"); + + // enforce strict mode on databases that support it + if (version_compare($this->sql_server_info(true), '5.0.2', '>=')) + { + $result = @mysqli_query($this->db_connect_id, 'SELECT @@session.sql_mode AS sql_mode'); + if ($result) + { + $row = mysqli_fetch_assoc($result); + mysqli_free_result($result); + + $modes = array_map('trim', explode(',', $row['sql_mode'])); + } + else + { + $modes = array(); + } + + // TRADITIONAL includes STRICT_ALL_TABLES and STRICT_TRANS_TABLES + if (!in_array('TRADITIONAL', $modes)) + { + if (!in_array('STRICT_ALL_TABLES', $modes)) + { + $modes[] = 'STRICT_ALL_TABLES'; + } + + if (!in_array('STRICT_TRANS_TABLES', $modes)) + { + $modes[] = 'STRICT_TRANS_TABLES'; + } + } + + $mode = implode(',', $modes); + @mysqli_query($this->db_connect_id, "SET SESSION sql_mode='{$mode}'"); + } + return $this->db_connect_id; + } + + return $this->sql_error(''); + } + + /** + * {@inheritDoc} + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('mysqli_version')) === false) + { + $result = @mysqli_query($this->db_connect_id, 'SELECT VERSION() AS version'); + if ($result) + { + $row = mysqli_fetch_assoc($result); + mysqli_free_result($result); + + $this->sql_server_version = $row['version']; + + if (!empty($cache) && $use_cache) + { + $cache->put('mysqli_version', $this->sql_server_version); + } + } + } + + return ($raw) ? $this->sql_server_version : 'MySQL(i) ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @mysqli_autocommit($this->db_connect_id, false); + break; + + case 'commit': + $result = @mysqli_commit($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + return $result; + break; + + case 'rollback': + $result = @mysqli_rollback($this->db_connect_id); + @mysqli_autocommit($this->db_connect_id, true); + return $result; + break; + } + + return true; + } + + /** + * {@inheritDoc} + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->curtime = microtime(true); + } + + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @mysqli_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->sql_time += microtime(true) - $this->curtime; + } + + if (!$this->query_result) + { + return false; + } + + if ($cache && $cache_ttl) + { + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * {@inheritDoc} + */ + function sql_affectedrows() + { + return ($this->db_connect_id) ? @mysqli_affected_rows($this->db_connect_id) : false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id) + { + $result = mysqli_fetch_assoc($query_id); + return $result !== null ? $result : false; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id) ? @mysqli_data_seek($query_id, $rownum) : false; + } + + /** + * {@inheritDoc} + */ + function sql_nextid() + { + return ($this->db_connect_id) ? @mysqli_insert_id($this->db_connect_id) : false; + } + + /** + * {@inheritDoc} + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (!$query_id) + { + return false; + } + + if ($query_id === true) + { + return true; + } + + return mysqli_free_result($query_id); + } + + /** + * {@inheritDoc} + */ + function sql_escape($msg) + { + return @mysqli_real_escape_string($this->db_connect_id, $msg); + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if ($this->db_connect_id) + { + $error = array( + 'message' => @mysqli_error($this->db_connect_id), + 'code' => @mysqli_errno($this->db_connect_id) + ); + } + else if (function_exists('mysqli_connect_error')) + { + $error = array( + 'message' => @mysqli_connect_error(), + 'code' => @mysqli_connect_errno(), + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @mysqli_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + static $test_prof; + + // current detection method, might just switch to see the existance of INFORMATION_SCHEMA.PROFILING + if ($test_prof === null) + { + $test_prof = false; + if (strpos(mysqli_get_server_info($this->db_connect_id), 'community') !== false) + { + $ver = mysqli_get_server_version($this->db_connect_id); + if ($ver >= 50037 && $ver < 50100) + { + $test_prof = true; + } + } + } + + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + // begin profiling + if ($test_prof) + { + @mysqli_query($this->db_connect_id, 'SET profiling = 1;'); + } + + if ($result = @mysqli_query($this->db_connect_id, "EXPLAIN $explain_query")) + { + while ($row = mysqli_fetch_assoc($result)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + mysqli_free_result($result); + } + + if ($html_table) + { + $this->html_hold .= ''; + } + + if ($test_prof) + { + $html_table = false; + + // get the last profile + if ($result = @mysqli_query($this->db_connect_id, 'SHOW PROFILE ALL;')) + { + $this->html_hold .= '
'; + while ($row = mysqli_fetch_assoc($result)) + { + // make HTML safe + if (!empty($row['Source_function'])) + { + $row['Source_function'] = str_replace(array('<', '>'), array('<', '>'), $row['Source_function']); + } + + // remove unsupported features + foreach ($row as $key => $val) + { + if ($val === null) + { + unset($row[$key]); + } + } + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + mysqli_free_result($result); + } + + if ($html_table) + { + $this->html_hold .= ''; + } + + @mysqli_query($this->db_connect_id, 'SET profiling = 0;'); + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @mysqli_query($this->db_connect_id, $query); + if ($result) + { + while ($void = mysqli_fetch_assoc($result)) + { + // Take the time spent on parsing rows into account + } + mysqli_free_result($result); + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpbb/db/driver/oracle.php b/phpbb/db/driver/oracle.php new file mode 100644 index 0000000..5fd1470 --- /dev/null +++ b/phpbb/db/driver/oracle.php @@ -0,0 +1,822 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* Oracle Database Abstraction Layer +*/ +class oracle extends \phpbb\db\driver\driver +{ + var $last_query_text = ''; + var $connect_error = ''; + + /** + * {@inheritDoc} + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + $this->persistency = $persistency; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + $connect = $database; + + // support for "easy connect naming" + if ($sqlserver !== '' && $sqlserver !== '/') + { + if (substr($sqlserver, -1, 1) == '/') + { + $sqlserver == substr($sqlserver, 0, -1); + } + $connect = $sqlserver . (($port) ? ':' . $port : '') . '/' . $database; + } + + if ($new_link) + { + if (!function_exists('ocinlogon')) + { + $this->connect_error = 'ocinlogon function does not exist, is oci extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ocinlogon($this->user, $sqlpassword, $connect, 'UTF8'); + } + else if ($this->persistency) + { + if (!function_exists('ociplogon')) + { + $this->connect_error = 'ociplogon function does not exist, is oci extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ociplogon($this->user, $sqlpassword, $connect, 'UTF8'); + } + else + { + if (!function_exists('ocilogon')) + { + $this->connect_error = 'ocilogon function does not exist, is oci extension installed?'; + return $this->sql_error(''); + } + $this->db_connect_id = @ocilogon($this->user, $sqlpassword, $connect, 'UTF8'); + } + + return ($this->db_connect_id) ? $this->db_connect_id : $this->sql_error(''); + } + + /** + * {@inheritDoc} + */ + function sql_server_info($raw = false, $use_cache = true) + { + /** + * force $use_cache false. I didn't research why the caching code below is commented out + * but I assume its because the Oracle extension provides a direct method to access it + * without a query. + */ +/* + global $cache; + + if (empty($cache) || ($this->sql_server_version = $cache->get('oracle_version')) === false) + { + $result = @ociparse($this->db_connect_id, 'SELECT * FROM v$version WHERE banner LIKE \'Oracle%\''); + @ociexecute($result, OCI_DEFAULT); + @ocicommit($this->db_connect_id); + + $row = array(); + @ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS); + @ocifreestatement($result); + $this->sql_server_version = trim($row['BANNER']); + + $cache->put('oracle_version', $this->sql_server_version); + } +*/ + $this->sql_server_version = @ociserverversion($this->db_connect_id); + + return $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return true; + break; + + case 'commit': + return @ocicommit($this->db_connect_id); + break; + + case 'rollback': + return @ocirollback($this->db_connect_id); + break; + } + + return true; + } + + /** + * Oracle specific code to handle the fact that it does not compare columns properly + * @access private + */ + function _rewrite_col_compare($args) + { + if (count($args) == 4) + { + if ($args[2] == '=') + { + return '(' . $args[0] . ' OR (' . $args[1] . ' is NULL AND ' . $args[3] . ' is NULL))'; + } + else if ($args[2] == '<>') + { + // really just a fancy way of saying foo <> bar or (foo is NULL XOR bar is NULL) but SQL has no XOR :P + return '(' . $args[0] . ' OR ((' . $args[1] . ' is NULL AND ' . $args[3] . ' is NOT NULL) OR (' . $args[1] . ' is NOT NULL AND ' . $args[3] . ' is NULL)))'; + } + } + else + { + return $this->_rewrite_where($args[0]); + } + } + + /** + * Oracle specific code to handle it's lack of sanity + * @access private + */ + function _rewrite_where($where_clause) + { + preg_match_all('/\s*(AND|OR)?\s*([\w_.()]++)\s*(?:(=|<[=>]?|>=?|LIKE)\s*((?>\'(?>[^\']++|\'\')*+\'|[\d-.()]+))|((NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))/', $where_clause, $result, PREG_SET_ORDER); + $out = ''; + foreach ($result as $val) + { + if (!isset($val[5])) + { + if ($val[4] !== "''") + { + $out .= $val[0]; + } + else + { + $out .= ' ' . $val[1] . ' ' . $val[2]; + if ($val[3] == '=') + { + $out .= ' is NULL'; + } + else if ($val[3] == '<>') + { + $out .= ' is NOT NULL'; + } + } + } + else + { + $in_clause = array(); + $sub_exp = substr($val[5], strpos($val[5], '(') + 1, -1); + $extra = false; + preg_match_all('/\'(?>[^\']++|\'\')*+\'|[\d-.]++/', $sub_exp, $sub_vals, PREG_PATTERN_ORDER); + $i = 0; + foreach ($sub_vals[0] as $sub_val) + { + // two things: + // 1) This determines if an empty string was in the IN clausing, making us turn it into a NULL comparison + // 2) This fixes the 1000 list limit that Oracle has (ORA-01795) + if ($sub_val !== "''") + { + $in_clause[(int) $i++/1000][] = $sub_val; + } + else + { + $extra = true; + } + } + if (!$extra && $i < 1000) + { + $out .= $val[0]; + } + else + { + $out .= ' ' . $val[1] . '('; + $in_array = array(); + + // constuct each IN() clause + foreach ($in_clause as $in_values) + { + $in_array[] = $val[2] . ' ' . (isset($val[6]) ? $val[6] : '') . 'IN(' . implode(', ', $in_values) . ')'; + } + + // Join the IN() clauses against a few ORs (IN is just a nicer OR anyway) + $out .= implode(' OR ', $in_array); + + // handle the empty string case + if ($extra) + { + $out .= ' OR ' . $val[2] . ' is ' . (isset($val[6]) ? $val[6] : '') . 'NULL'; + } + $out .= ')'; + + unset($in_array, $in_clause); + } + } + } + + return $out; + } + + /** + * {@inheritDoc} + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->curtime = microtime(true); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + $in_transaction = false; + if (!$this->transaction) + { + $this->sql_transaction('begin'); + } + else + { + $in_transaction = true; + } + + $array = array(); + + // We overcome Oracle's 4000 char limit by binding vars + if (strlen($query) > 4000) + { + if (preg_match('/^(INSERT INTO[^(]++)\\(([^()]+)\\) VALUES[^(]++\\((.*?)\\)$/sU', $query, $regs)) + { + if (strlen($regs[3]) > 4000) + { + $cols = explode(', ', $regs[2]); + + preg_match_all('/\'(?:[^\']++|\'\')*+\'|[\d-.]+/', $regs[3], $vals, PREG_PATTERN_ORDER); + +/* The code inside this comment block breaks clob handling, but does allow the + database restore script to work. If you want to allow no posts longer than 4KB + and/or need the db restore script, uncomment this. + + + if (count($cols) !== count($vals)) + { + // Try to replace some common data we know is from our restore script or from other sources + $regs[3] = str_replace("'||chr(47)||'", '/', $regs[3]); + $_vals = explode(', ', $regs[3]); + + $vals = array(); + $is_in_val = false; + $i = 0; + $string = ''; + + foreach ($_vals as $value) + { + if (strpos($value, "'") === false && !$is_in_val) + { + $vals[$i++] = $value; + continue; + } + + if (substr($value, -1) === "'") + { + $vals[$i] = $string . (($is_in_val) ? ', ' : '') . $value; + $string = ''; + $is_in_val = false; + + if ($vals[$i][0] !== "'") + { + $vals[$i] = "''" . $vals[$i]; + } + $i++; + continue; + } + else + { + $string .= (($is_in_val) ? ', ' : '') . $value; + $is_in_val = true; + } + } + + if ($string) + { + // New value if cols != value + $vals[(count($cols) !== count($vals)) ? $i : $i - 1] .= $string; + } + + $vals = array(0 => $vals); + } +*/ + + $inserts = $vals[0]; + unset($vals); + + foreach ($inserts as $key => $value) + { + if (!empty($value) && $value[0] === "'" && strlen($value) > 4002) // check to see if this thing is greater than the max + 'x2 + { + $inserts[$key] = ':' . strtoupper($cols[$key]); + $array[$inserts[$key]] = str_replace("''", "'", substr($value, 1, -1)); + } + } + + $query = $regs[1] . '(' . $regs[2] . ') VALUES (' . implode(', ', $inserts) . ')'; + } + } + else if (preg_match_all('/^(UPDATE [\\w_]++\\s+SET )([\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+)(?:,\\s*[\\w_]++\\s*=\\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]+))*+)\\s+(WHERE.*)$/s', $query, $data, PREG_SET_ORDER)) + { + if (strlen($data[0][2]) > 4000) + { + $update = $data[0][1]; + $where = $data[0][3]; + preg_match_all('/([\\w_]++)\\s*=\\s*(\'(?:[^\']++|\'\')*+\'|[\d-.]++)/', $data[0][2], $temp, PREG_SET_ORDER); + unset($data); + + $cols = array(); + foreach ($temp as $value) + { + if (!empty($value[2]) && $value[2][0] === "'" && strlen($value[2]) > 4002) // check to see if this thing is greater than the max + 'x2 + { + $cols[] = $value[1] . '=:' . strtoupper($value[1]); + $array[$value[1]] = str_replace("''", "'", substr($value[2], 1, -1)); + } + else + { + $cols[] = $value[1] . '=' . $value[2]; + } + } + + $query = $update . implode(', ', $cols) . ' ' . $where; + unset($cols); + } + } + } + + switch (substr($query, 0, 6)) + { + case 'DELETE': + if (preg_match('/^(DELETE FROM [\w_]++ WHERE)((?:\s*(?:AND|OR)?\s*[\w_]+\s*(?:(?:=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]+)|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]+,? ?)*+\)))*+)$/', $query, $regs)) + { + $query = $regs[1] . $this->_rewrite_where($regs[2]); + unset($regs); + } + break; + + case 'UPDATE': + if (preg_match('/^(UPDATE [\\w_]++\\s+SET [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++)(?:, [\\w_]+\s*=\s*(?:\'(?:[^\']++|\'\')*+\'|[\d-.]++|:\w++))*+\\s+WHERE)(.*)$/s', $query, $regs)) + { + $query = $regs[1] . $this->_rewrite_where($regs[2]); + unset($regs); + } + break; + + case 'SELECT': + $query = preg_replace_callback('/([\w_.]++)\s*(?:(=|<>)\s*(?>\'(?>[^\']++|\'\')*+\'|[\d-.]++|([\w_.]++))|(?:NOT )?IN\s*\((?>\'(?>[^\']++|\'\')*+\',? ?|[\d-.]++,? ?)*+\))/', array($this, '_rewrite_col_compare'), $query); + break; + } + + $this->query_result = @ociparse($this->db_connect_id, $query); + + foreach ($array as $key => $value) + { + @ocibindbyname($this->query_result, $key, $array[$key], -1); + } + + $success = @ociexecute($this->query_result, OCI_DEFAULT); + + if (!$success) + { + $this->sql_error($query); + $this->query_result = false; + } + else + { + if (!$in_transaction) + { + $this->sql_transaction('commit'); + } + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->sql_time += microtime(true) - $this->curtime; + } + + if (!$this->query_result) + { + return false; + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + $query = 'SELECT * FROM (SELECT /*+ FIRST_ROWS */ rownum AS xrownum, a.* FROM (' . $query . ') a WHERE rownum <= ' . ($offset + $total) . ') WHERE xrownum >= ' . $offset; + + return $this->sql_query($query, $cache_ttl); + } + + /** + * {@inheritDoc} + */ + function sql_affectedrows() + { + return ($this->query_result) ? @ocirowcount($this->query_result) : false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + if ($query_id) + { + $row = array(); + $result = ocifetchinto($query_id, $row, OCI_ASSOC + OCI_RETURN_NULLS); + + if (!$result || !$row) + { + return false; + } + + $result_row = array(); + foreach ($row as $key => $value) + { + // Oracle treats empty strings as null + if (is_null($value)) + { + $value = ''; + } + + // OCI->CLOB? + if (is_object($value)) + { + $value = $value->load(); + } + + $result_row[strtolower($key)] = $value; + } + + return $result_row; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + if (!$query_id) + { + return false; + } + + // Reset internal pointer + @ociexecute($query_id, OCI_DEFAULT); + + // We do not fetch the row for rownum == 0 because then the next resultset would be the second row + for ($i = 0; $i < $rownum; $i++) + { + if (!$this->sql_fetchrow($query_id)) + { + return false; + } + } + + return true; + } + + /** + * {@inheritDoc} + */ + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id !== false && $this->last_query_text != '') + { + if (preg_match('#^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)#is', $this->last_query_text, $tablename)) + { + $query = 'SELECT ' . $tablename[1] . '_seq.currval FROM DUAL'; + $stmt = @ociparse($this->db_connect_id, $query); + if ($stmt) + { + $success = @ociexecute($stmt, OCI_DEFAULT); + + if ($success) + { + $temp_result = ocifetchinto($stmt, $temp_array, OCI_ASSOC + OCI_RETURN_NULLS); + ocifreestatement($stmt); + + if ($temp_result) + { + return $temp_array['CURRVAL']; + } + else + { + return false; + } + } + } + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return ocifreestatement($query_id); + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_escape($msg) + { + return str_replace(array("'", "\0"), array("''", ''), $msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + /** + * Build NOT LIKE expression + * @access private + */ + function _sql_not_like_expression($expression) + { + return $expression . " ESCAPE '\\'"; + } + + function _sql_custom_build($stage, $data) + { + return $data; + } + + function _sql_bit_and($column_name, $bit, $compare = '') + { + return 'BITAND(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + function _sql_bit_or($column_name, $bit, $compare = '') + { + return 'BITOR(' . $column_name . ', ' . (1 << $bit) . ')' . (($compare) ? ' ' . $compare : ''); + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + if (function_exists('ocierror')) + { + $error = @ocierror(); + $error = (!$error) ? @ocierror($this->query_result) : $error; + $error = (!$error) ? @ocierror($this->db_connect_id) : $error; + + if ($error) + { + $this->last_error_result = $error; + } + else + { + $error = (isset($this->last_error_result) && $this->last_error_result) ? $this->last_error_result : array(); + } + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @ocilogoff($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + + $html_table = false; + + // Grab a plan table, any will do + $sql = "SELECT table_name + FROM USER_TABLES + WHERE table_name LIKE '%PLAN_TABLE%'"; + $stmt = ociparse($this->db_connect_id, $sql); + ociexecute($stmt); + $result = array(); + + if (ocifetchinto($stmt, $result, OCI_ASSOC + OCI_RETURN_NULLS)) + { + $table = $result['TABLE_NAME']; + + // This is the statement_id that will allow us to track the plan + $statement_id = substr(md5($query), 0, 30); + + // Remove any stale plans + $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); + ociexecute($stmt2); + ocifreestatement($stmt2); + + // Explain the plan + $sql = "EXPLAIN PLAN + SET STATEMENT_ID = '$statement_id' + FOR $query"; + $stmt2 = ociparse($this->db_connect_id, $sql); + ociexecute($stmt2); + ocifreestatement($stmt2); + + // Get the data from the plan + $sql = "SELECT operation, options, object_name, object_type, cardinality, cost + FROM plan_table + START WITH id = 0 AND statement_id = '$statement_id' + CONNECT BY PRIOR id = parent_id + AND statement_id = '$statement_id'"; + $stmt2 = ociparse($this->db_connect_id, $sql); + ociexecute($stmt2); + + $row = array(); + while (ocifetchinto($stmt2, $row, OCI_ASSOC + OCI_RETURN_NULLS)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + + ocifreestatement($stmt2); + + // Remove the plan we just made, we delete them on request anyway + $stmt2 = ociparse($this->db_connect_id, "DELETE FROM $table WHERE statement_id='$statement_id'"); + ociexecute($stmt2); + ocifreestatement($stmt2); + } + + ocifreestatement($stmt); + + if ($html_table) + { + $this->html_hold .= ''; + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @ociparse($this->db_connect_id, $query); + if ($result) + { + $success = @ociexecute($result, OCI_DEFAULT); + if ($success) + { + $row = array(); + + while (ocifetchinto($result, $row, OCI_ASSOC + OCI_RETURN_NULLS)) + { + // Take the time spent on parsing rows into account + } + @ocifreestatement($result); + } + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpbb/db/driver/postgres.php b/phpbb/db/driver/postgres.php new file mode 100644 index 0000000..4447661 --- /dev/null +++ b/phpbb/db/driver/postgres.php @@ -0,0 +1,501 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* PostgreSQL Database Abstraction Layer +* Minimum Requirement is Version 8.3+ +*/ +class postgres extends \phpbb\db\driver\driver +{ + var $multi_insert = true; + var $last_query_text = ''; + var $connect_error = ''; + + /** + * {@inheritDoc} + */ + function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + $connect_string = ''; + + if ($sqluser) + { + $connect_string .= "user=$sqluser "; + } + + if ($sqlpassword) + { + $connect_string .= "password=$sqlpassword "; + } + + if ($sqlserver) + { + // $sqlserver can carry a port separated by : for compatibility reasons + // If $sqlserver has more than one : it's probably an IPv6 address. + // In this case we only allow passing a port via the $port variable. + if (substr_count($sqlserver, ':') === 1) + { + list($sqlserver, $port) = explode(':', $sqlserver); + } + + if ($sqlserver !== 'localhost') + { + $connect_string .= "host=$sqlserver "; + } + + if ($port) + { + $connect_string .= "port=$port "; + } + } + + $schema = ''; + + if ($database) + { + $this->dbname = $database; + if (strpos($database, '.') !== false) + { + list($database, $schema) = explode('.', $database); + } + $connect_string .= "dbname=$database"; + } + + $this->persistency = $persistency; + + if ($this->persistency) + { + if (!function_exists('pg_pconnect')) + { + $this->connect_error = 'pg_pconnect function does not exist, is pgsql extension installed?'; + return $this->sql_error(''); + } + $collector = new \phpbb\error_collector; + $collector->install(); + $this->db_connect_id = (!$new_link) ? @pg_pconnect($connect_string) : @pg_pconnect($connect_string, PGSQL_CONNECT_FORCE_NEW); + } + else + { + if (!function_exists('pg_connect')) + { + $this->connect_error = 'pg_connect function does not exist, is pgsql extension installed?'; + return $this->sql_error(''); + } + $collector = new \phpbb\error_collector; + $collector->install(); + $this->db_connect_id = (!$new_link) ? @pg_connect($connect_string) : @pg_connect($connect_string, PGSQL_CONNECT_FORCE_NEW); + } + + $collector->uninstall(); + + if ($this->db_connect_id) + { + if ($schema !== '') + { + @pg_query($this->db_connect_id, 'SET search_path TO ' . $schema); + } + return $this->db_connect_id; + } + + $this->connect_error = $collector->format_errors(); + return $this->sql_error(''); + } + + /** + * {@inheritDoc} + */ + function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('pgsql_version')) === false) + { + $query_id = @pg_query($this->db_connect_id, 'SELECT VERSION() AS version'); + if ($query_id) + { + $row = pg_fetch_assoc($query_id, null); + pg_free_result($query_id); + + $this->sql_server_version = (!empty($row['version'])) ? trim(substr($row['version'], 10)) : 0; + + if (!empty($cache) && $use_cache) + { + $cache->put('pgsql_version', $this->sql_server_version); + } + } + } + + return ($raw) ? $this->sql_server_version : 'PostgreSQL ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * @access private + */ + function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return @pg_query($this->db_connect_id, 'BEGIN'); + break; + + case 'commit': + return @pg_query($this->db_connect_id, 'COMMIT'); + break; + + case 'rollback': + return @pg_query($this->db_connect_id, 'ROLLBACK'); + break; + } + + return true; + } + + /** + * {@inheritDoc} + */ + function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->curtime = microtime(true); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if (($this->query_result = @pg_query($this->db_connect_id, $query)) === false) + { + $this->sql_error($query); + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->sql_time += microtime(true) - $this->curtime; + } + + if (!$this->query_result) + { + return false; + } + + if ($cache && $cache_ttl) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + else if (strpos($query, 'SELECT') === 0) + { + $this->open_queries[(int) $this->query_result] = $this->query_result; + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build db-specific query data + * @access private + */ + function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Build LIMIT query + */ + function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = 'ALL'; + } + + $query .= "\n LIMIT $total OFFSET $offset"; + + return $this->sql_query($query, $cache_ttl); + } + + /** + * {@inheritDoc} + */ + function sql_affectedrows() + { + return ($this->query_result) ? @pg_affected_rows($this->query_result) : false; + } + + /** + * {@inheritDoc} + */ + function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return ($query_id) ? pg_fetch_assoc($query_id, null) : false; + } + + /** + * {@inheritDoc} + */ + function sql_rowseek($rownum, &$query_id) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && $cache->sql_exists($query_id)) + { + return $cache->sql_rowseek($rownum, $query_id); + } + + return ($query_id) ? @pg_result_seek($query_id, $rownum) : false; + } + + /** + * {@inheritDoc} + */ + function sql_nextid() + { + $query_id = $this->query_result; + + if ($query_id !== false && $this->last_query_text != '') + { + if (preg_match("/^INSERT[\t\n ]+INTO[\t\n ]+([a-z0-9\_\-]+)/is", $this->last_query_text, $tablename)) + { + $query = "SELECT currval('" . $tablename[1] . "_seq') AS last_value"; + $temp_q_id = @pg_query($this->db_connect_id, $query); + + if (!$temp_q_id) + { + return false; + } + + $temp_result = pg_fetch_assoc($temp_q_id, null); + pg_free_result($query_id); + + return ($temp_result) ? $temp_result['last_value'] : false; + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if (isset($this->open_queries[(int) $query_id])) + { + unset($this->open_queries[(int) $query_id]); + return pg_free_result($query_id); + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_escape($msg) + { + return @pg_escape_string($msg); + } + + /** + * Build LIKE expression + * @access private + */ + function _sql_like_expression($expression) + { + return $expression; + } + + /** + * Build NOT LIKE expression + * @access private + */ + function _sql_not_like_expression($expression) + { + return $expression; + } + + /** + * {@inheritDoc} + */ + function cast_expr_to_bigint($expression) + { + return 'CAST(' . $expression . ' as DECIMAL(255, 0))'; + } + + /** + * {@inheritDoc} + */ + function cast_expr_to_string($expression) + { + return 'CAST(' . $expression . ' as VARCHAR(255))'; + } + + /** + * return sql error array + * @access private + */ + function _sql_error() + { + // pg_last_error only works when there is an established connection. + // Connection errors have to be tracked by us manually. + if ($this->db_connect_id) + { + $message = @pg_last_error($this->db_connect_id); + } + else + { + $message = $this->connect_error; + } + + return array( + 'message' => $message, + 'code' => '' + ); + } + + /** + * Close sql connection + * @access private + */ + function _sql_close() + { + return @pg_close($this->db_connect_id); + } + + /** + * Build db-specific report + * @access private + */ + function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + if ($result = @pg_query($this->db_connect_id, "EXPLAIN $explain_query")) + { + while ($row = pg_fetch_assoc($result, null)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + pg_free_result($result); + } + + if ($html_table) + { + $this->html_hold .= ''; + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = @pg_query($this->db_connect_id, $query); + if ($result) + { + while ($void = pg_fetch_assoc($result, null)) + { + // Take the time spent on parsing rows into account + } + pg_free_result($result); + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpbb/db/driver/sqlite3.php b/phpbb/db/driver/sqlite3.php new file mode 100644 index 0000000..0508500 --- /dev/null +++ b/phpbb/db/driver/sqlite3.php @@ -0,0 +1,431 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\driver; + +/** +* SQLite3 Database Abstraction Layer +* Minimum Requirement: 3.6.15+ +*/ +class sqlite3 extends \phpbb\db\driver\driver +{ + /** + * @var string Stores errors during connection setup in case the driver is not available + */ + protected $connect_error = ''; + + /** + * @var \SQLite3 The SQLite3 database object to operate against + */ + protected $dbo = null; + + /** + * {@inheritDoc} + */ + public function sql_connect($sqlserver, $sqluser, $sqlpassword, $database, $port = false, $persistency = false, $new_link = false) + { + $this->persistency = false; + $this->user = $sqluser; + $this->server = $sqlserver . (($port) ? ':' . $port : ''); + $this->dbname = $database; + + if (!class_exists('SQLite3', false)) + { + $this->connect_error = 'SQLite3 not found, is the extension installed?'; + return $this->sql_error(''); + } + + try + { + $this->dbo = new \SQLite3($this->server, SQLITE3_OPEN_READWRITE | SQLITE3_OPEN_CREATE); + $this->dbo->busyTimeout(60000); + $this->db_connect_id = true; + } + catch (\Exception $e) + { + $this->connect_error = $e->getMessage(); + return array('message' => $this->connect_error); + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function sql_server_info($raw = false, $use_cache = true) + { + global $cache; + + if (!$use_cache || empty($cache) || ($this->sql_server_version = $cache->get('sqlite_version')) === false) + { + $version = \SQLite3::version(); + + $this->sql_server_version = $version['versionString']; + + if (!empty($cache) && $use_cache) + { + $cache->put('sqlite_version', $this->sql_server_version); + } + } + + return ($raw) ? $this->sql_server_version : 'SQLite ' . $this->sql_server_version; + } + + /** + * SQL Transaction + * + * @param string $status Should be one of the following strings: + * begin, commit, rollback + * @return bool Success/failure of the transaction query + */ + protected function _sql_transaction($status = 'begin') + { + switch ($status) + { + case 'begin': + return $this->dbo->exec('BEGIN IMMEDIATE'); + break; + + case 'commit': + return $this->dbo->exec('COMMIT'); + break; + + case 'rollback': + return @$this->dbo->exec('ROLLBACK'); + break; + } + + return true; + } + + /** + * {@inheritDoc} + */ + public function sql_query($query = '', $cache_ttl = 0) + { + if ($query != '') + { + global $cache; + + // EXPLAIN only in extra debug mode + if (defined('DEBUG')) + { + $this->sql_report('start', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->curtime = microtime(true); + } + + $this->last_query_text = $query; + $this->query_result = ($cache && $cache_ttl) ? $cache->sql_load($query) : false; + $this->sql_add_num_queries($this->query_result); + + if ($this->query_result === false) + { + if ($this->transaction === true && strpos($query, 'INSERT') === 0) + { + $query = preg_replace('/^INSERT INTO/', 'INSERT OR ROLLBACK INTO', $query); + } + + if (($this->query_result = @$this->dbo->query($query)) === false) + { + // Try to recover a lost database connection + if ($this->dbo && !@$this->dbo->lastErrorMsg()) + { + if ($this->sql_connect($this->server, $this->user, '', $this->dbname)) + { + $this->query_result = @$this->dbo->query($query); + } + } + + if ($this->query_result === false) + { + $this->sql_error($query); + } + } + + if (defined('DEBUG')) + { + $this->sql_report('stop', $query); + } + else if (defined('PHPBB_DISPLAY_LOAD_TIME')) + { + $this->sql_time += microtime(true) - $this->curtime; + } + + if (!$this->query_result) + { + return false; + } + + if ($cache && $cache_ttl) + { + $this->query_result = $cache->sql_save($this, $query, $this->query_result, $cache_ttl); + } + } + else if (defined('DEBUG')) + { + $this->sql_report('fromcache', $query); + } + } + else + { + return false; + } + + return $this->query_result; + } + + /** + * Build LIMIT query + * + * @param string $query The SQL query to execute + * @param int $total The number of rows to select + * @param int $offset + * @param int $cache_ttl Either 0 to avoid caching or + * the time in seconds which the result shall be kept in cache + * @return mixed Buffered, seekable result handle, false on error + */ + protected function _sql_query_limit($query, $total, $offset = 0, $cache_ttl = 0) + { + $this->query_result = false; + + // if $total is set to 0 we do not want to limit the number of rows + if ($total == 0) + { + $total = -1; + } + + $query .= "\n LIMIT " . ((!empty($offset)) ? $offset . ', ' . $total : $total); + + return $this->sql_query($query, $cache_ttl); + } + + /** + * {@inheritDoc} + */ + public function sql_affectedrows() + { + return ($this->db_connect_id) ? $this->dbo->changes() : false; + } + + /** + * {@inheritDoc} + */ + public function sql_fetchrow($query_id = false) + { + global $cache; + + if ($query_id === false) + { + /** @var \SQLite3Result $query_id */ + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_fetchrow($query_id); + } + + return is_object($query_id) ? @$query_id->fetchArray(SQLITE3_ASSOC) : false; + } + + /** + * {@inheritDoc} + */ + public function sql_nextid() + { + return ($this->db_connect_id) ? $this->dbo->lastInsertRowID() : false; + } + + /** + * {@inheritDoc} + */ + public function sql_freeresult($query_id = false) + { + global $cache; + + if ($query_id === false) + { + $query_id = $this->query_result; + } + + if ($cache && !is_object($query_id) && $cache->sql_exists($query_id)) + { + return $cache->sql_freeresult($query_id); + } + + if ($query_id) + { + return @$query_id->finalize(); + } + } + + /** + * {@inheritDoc} + */ + public function sql_escape($msg) + { + return \SQLite3::escapeString($msg); + } + + /** + * {@inheritDoc} + * + * For SQLite an underscore is an unknown character. + */ + public function sql_like_expression($expression) + { + // Unlike LIKE, GLOB is unfortunately case sensitive. + // We only catch * and ? here, not the character map possible on file globbing. + $expression = str_replace(array(chr(0) . '_', chr(0) . '%'), array(chr(0) . '?', chr(0) . '*'), $expression); + + $expression = str_replace(array('?', '*'), array("\?", "\*"), $expression); + $expression = str_replace(array(chr(0) . "\?", chr(0) . "\*"), array('?', '*'), $expression); + + return 'GLOB \'' . $this->sql_escape($expression) . '\''; + } + + /** + * {@inheritDoc} + * + * For SQLite an underscore is an unknown character. + */ + public function sql_not_like_expression($expression) + { + // Unlike NOT LIKE, NOT GLOB is unfortunately case sensitive + // We only catch * and ? here, not the character map possible on file globbing. + $expression = str_replace(array(chr(0) . '_', chr(0) . '%'), array(chr(0) . '?', chr(0) . '*'), $expression); + + $expression = str_replace(array('?', '*'), array("\?", "\*"), $expression); + $expression = str_replace(array(chr(0) . "\?", chr(0) . "\*"), array('?', '*'), $expression); + + return 'NOT GLOB \'' . $this->sql_escape($expression) . '\''; + } + + /** + * return sql error array + * + * @return array + */ + protected function _sql_error() + { + if (class_exists('SQLite3', false) && isset($this->dbo)) + { + $error = array( + 'message' => $this->dbo->lastErrorMsg(), + 'code' => $this->dbo->lastErrorCode(), + ); + } + else + { + $error = array( + 'message' => $this->connect_error, + 'code' => '', + ); + } + + return $error; + } + + /** + * Build db-specific query data + * + * @param string $stage Available stages: FROM, WHERE + * @param mixed $data A string containing the CROSS JOIN query or an array of WHERE clauses + * + * @return string The db-specific query fragment + */ + protected function _sql_custom_build($stage, $data) + { + return $data; + } + + /** + * Close sql connection + * + * @return bool False if failure + */ + protected function _sql_close() + { + return $this->dbo->close(); + } + + /** + * Build db-specific report + * + * @param string $mode Available modes: display, start, stop, + * add_select_row, fromcache, record_fromcache + * @param string $query The Query that should be explained + * @return mixed Either a full HTML page, boolean or null + */ + protected function _sql_report($mode, $query = '') + { + switch ($mode) + { + case 'start': + + $explain_query = $query; + if (preg_match('/UPDATE ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + else if (preg_match('/DELETE FROM ([a-z0-9_]+).*?WHERE(.*)/s', $query, $m)) + { + $explain_query = 'SELECT * FROM ' . $m[1] . ' WHERE ' . $m[2]; + } + + if (preg_match('/^SELECT/', $explain_query)) + { + $html_table = false; + + if ($result = $this->dbo->query("EXPLAIN QUERY PLAN $explain_query")) + { + while ($row = $result->fetchArray(SQLITE3_ASSOC)) + { + $html_table = $this->sql_report('add_select_row', $query, $html_table, $row); + } + } + + if ($html_table) + { + $this->html_hold .= ''; + } + } + + break; + + case 'fromcache': + $endtime = explode(' ', microtime()); + $endtime = $endtime[0] + $endtime[1]; + + $result = $this->dbo->query($query); + if ($result) + { + while ($void = $result->fetchArray(SQLITE3_ASSOC)) + { + // Take the time spent on parsing rows into account + } + } + + $splittime = explode(' ', microtime()); + $splittime = $splittime[0] + $splittime[1]; + + $this->sql_report('record_fromcache', $query, $endtime, $splittime); + + break; + } + } +} diff --git a/phpbb/db/extractor/base_extractor.php b/phpbb/db/extractor/base_extractor.php new file mode 100644 index 0000000..547c85f --- /dev/null +++ b/phpbb/db/extractor/base_extractor.php @@ -0,0 +1,252 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\invalid_format_exception; +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +/** + * Abstract base class for database extraction + */ +abstract class base_extractor implements extractor_interface +{ + /** + * @var string phpBB root path + */ + protected $phpbb_root_path; + + /** + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var bool + */ + protected $download; + + /** + * @var bool + */ + protected $store; + + /** + * @var int + */ + protected $time; + + /** + * @var string + */ + protected $format; + + /** + * @var resource + */ + protected $fp; + + /** + * @var string + */ + protected $write; + + /** + * @var string + */ + protected $close; + + /** + * @var bool + */ + protected $run_comp; + + /** + * @var bool + */ + protected $is_initialized; + + /** + * Constructor + * + * @param string $phpbb_root_path + * @param \phpbb\request\request_interface $request + * @param \phpbb\db\driver\driver_interface $db + */ + public function __construct($phpbb_root_path, \phpbb\request\request_interface $request, \phpbb\db\driver\driver_interface $db) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->request = $request; + $this->db = $db; + $this->fp = null; + + $this->is_initialized = false; + } + + /** + * {@inheritdoc} + */ + public function init_extractor($format, $filename, $time, $download = false, $store = false) + { + $this->download = $download; + $this->store = $store; + $this->time = $time; + $this->format = $format; + + switch ($format) + { + case 'text': + $ext = '.sql'; + $open = 'fopen'; + $this->write = 'fwrite'; + $this->close = 'fclose'; + $mimetype = 'text/x-sql'; + break; + case 'bzip2': + $ext = '.sql.bz2'; + $open = 'bzopen'; + $this->write = 'bzwrite'; + $this->close = 'bzclose'; + $mimetype = 'application/x-bzip2'; + break; + case 'gzip': + $ext = '.sql.gz'; + $open = 'gzopen'; + $this->write = 'gzwrite'; + $this->close = 'gzclose'; + $mimetype = 'application/x-gzip'; + break; + default: + throw new invalid_format_exception(); + break; + } + + if ($download === true) + { + $name = $filename . $ext; + header('Cache-Control: private, no-cache'); + header("Content-Type: $mimetype; name=\"$name\""); + header("Content-disposition: attachment; filename=$name"); + + switch ($format) + { + case 'bzip2': + ob_start(); + break; + + case 'gzip': + if (strpos($this->request->header('Accept-Encoding'), 'gzip') !== false && strpos(strtolower($this->request->header('User-Agent')), 'msie') === false) + { + ob_start('ob_gzhandler'); + } + else + { + $this->run_comp = true; + } + break; + } + } + + if ($store === true) + { + $file = $this->phpbb_root_path . 'store/' . $filename . $ext; + + $this->fp = $open($file, 'w'); + + if (!$this->fp) + { + trigger_error('FILE_WRITE_FAIL', E_USER_ERROR); + } + } + + $this->is_initialized = true; + } + + /** + * {@inheritdoc} + */ + public function write_end() + { + static $close; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->store) + { + if ($close === null) + { + $close = $this->close; + } + $close($this->fp); + } + + // bzip2 must be written all the way at the end + if ($this->download && $this->format === 'bzip2') + { + $c = ob_get_clean(); + echo bzcompress($c); + } + } + + /** + * {@inheritdoc} + */ + public function flush($data) + { + static $write; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->store === true) + { + if ($write === null) + { + $write = $this->write; + } + $write($this->fp, $data); + } + + if ($this->download === true) + { + if ($this->format === 'bzip2' || $this->format === 'text' || ($this->format === 'gzip' && !$this->run_comp)) + { + echo $data; + } + + // we can write the gzip data as soon as we get it + if ($this->format === 'gzip') + { + if ($this->run_comp) + { + echo gzencode($data); + } + else + { + ob_flush(); + flush(); + } + } + } + } +} diff --git a/phpbb/db/extractor/exception/extractor_not_initialized_exception.php b/phpbb/db/extractor/exception/extractor_not_initialized_exception.php new file mode 100644 index 0000000..62eb434 --- /dev/null +++ b/phpbb/db/extractor/exception/extractor_not_initialized_exception.php @@ -0,0 +1,24 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor\exception; + +use phpbb\exception\runtime_exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class extractor_not_initialized_exception extends runtime_exception +{ + +} diff --git a/phpbb/db/extractor/exception/invalid_format_exception.php b/phpbb/db/extractor/exception/invalid_format_exception.php new file mode 100644 index 0000000..6be24cb --- /dev/null +++ b/phpbb/db/extractor/exception/invalid_format_exception.php @@ -0,0 +1,22 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor\exception; + +/** +* This exception is thrown when invalid format is given to the extractor +*/ +class invalid_format_exception extends \InvalidArgumentException +{ + +} diff --git a/phpbb/db/extractor/extractor_interface.php b/phpbb/db/extractor/extractor_interface.php new file mode 100644 index 0000000..ff45df9 --- /dev/null +++ b/phpbb/db/extractor/extractor_interface.php @@ -0,0 +1,80 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +/** +* Database extractor interface +*/ +interface extractor_interface +{ + /** + * Start the extraction of the database + * + * This function initialize the database extraction. It is required to call this + * function before calling any other extractor functions. + * + * @param string $format + * @param string $filename + * @param int $time + * @param bool $download + * @param bool $store + * @return null + * @throws \phpbb\db\extractor\exception\invalid_format_exception when $format is invalid + */ + public function init_extractor($format, $filename, $time, $download = false, $store = false); + + /** + * Writes header comments to the database backup + * + * @param string $table_prefix prefix of phpBB database tables + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_start($table_prefix); + + /** + * Closes file and/or dumps download data + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end(); + + /** + * Extracts database table structure + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_table($table_name); + + /** + * Extracts data from database table + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_data($table_name); + + /** + * Writes data to file/download content + * + * @param string $data + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function flush($data); +} diff --git a/phpbb/db/extractor/factory.php b/phpbb/db/extractor/factory.php new file mode 100644 index 0000000..f27aae7 --- /dev/null +++ b/phpbb/db/extractor/factory.php @@ -0,0 +1,75 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +/** +* A factory which serves the suitable extractor instance for the given dbal +*/ +class factory +{ + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * Extractor factory constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \Symfony\Component\DependencyInjection\ContainerInterface $container) + { + $this->db = $db; + $this->container = $container; + } + + /** + * DB extractor factory getter + * + * @return \phpbb\db\extractor\extractor_interface an appropriate instance of the database extractor for the used database driver + * @throws \InvalidArgumentException when the database driver is unknown + */ + public function get() + { + // Return the appropriate DB extractor + if ($this->db instanceof \phpbb\db\driver\mssql_base) + { + return $this->container->get('dbal.extractor.extractors.mssql_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\mysql_base) + { + return $this->container->get('dbal.extractor.extractors.mysql_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\oracle) + { + return $this->container->get('dbal.extractor.extractors.oracle_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\postgres) + { + return $this->container->get('dbal.extractor.extractors.postgres_extractor'); + } + else if ($this->db instanceof \phpbb\db\driver\sqlite3) + { + return $this->container->get('dbal.extractor.extractors.sqlite3_extractor'); + } + + throw new \InvalidArgumentException('Invalid database driver given'); + } +} diff --git a/phpbb/db/extractor/mssql_extractor.php b/phpbb/db/extractor/mssql_extractor.php new file mode 100644 index 0000000..4eeab47 --- /dev/null +++ b/phpbb/db/extractor/mssql_extractor.php @@ -0,0 +1,415 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mssql_extractor extends base_extractor +{ + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT\nGO\n"); + parent::write_end(); + } + + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION\n"; + $sql_data .= "GO\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "IF OBJECT_ID(N'$table_name', N'U') IS NOT NULL\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + $sql_data .= "GO\n"; + $sql_data .= "\nCREATE TABLE [$table_name] (\n"; + $rows = array(); + + $text_flag = false; + + $sql = "SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMNPROPERTY(object_id(TABLE_NAME), COLUMN_NAME, 'IsIdentity') as IS_IDENTITY + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $line = "\t[{$row['COLUMN_NAME']}] [{$row['DATA_TYPE']}]"; + + if ($row['DATA_TYPE'] == 'text') + { + $text_flag = true; + } + + if ($row['IS_IDENTITY']) + { + $line .= ' IDENTITY (1 , 1)'; + } + + if ($row['CHARACTER_MAXIMUM_LENGTH'] && $row['DATA_TYPE'] !== 'text') + { + $line .= ' (' . $row['CHARACTER_MAXIMUM_LENGTH'] . ')'; + } + + if ($row['IS_NULLABLE'] == 'YES') + { + $line .= ' NULL'; + } + else + { + $line .= ' NOT NULL'; + } + + if ($row['COLUMN_DEFAULT']) + { + $line .= ' DEFAULT ' . $row['COLUMN_DEFAULT']; + } + + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n) ON [PRIMARY]"; + + if ($text_flag) + { + $sql_data .= " TEXTIMAGE_ON [PRIMARY]"; + } + + $sql_data .= "\nGO\n\n"; + $rows = array(); + + $sql = "SELECT CONSTRAINT_NAME, COLUMN_NAME + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE TABLE_NAME = '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!count($rows)) + { + $sql_data .= "ALTER TABLE [$table_name] WITH NOCHECK ADD\n"; + $sql_data .= "\tCONSTRAINT [{$row['CONSTRAINT_NAME']}] PRIMARY KEY CLUSTERED \n\t(\n"; + } + $rows[] = "\t\t[{$row['COLUMN_NAME']}]"; + } + if (count($rows)) + { + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n\t) ON [PRIMARY] \nGO\n"; + } + $this->db->sql_freeresult($result); + + $index = array(); + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + $index[$row['INDEX_NAME']][] = '[' . $row['COLUMN_NAME'] . ']'; + } + } + $this->db->sql_freeresult($result); + + foreach ($index as $index_name => $column_name) + { + $index[$index_name] = implode(', ', $column_name); + } + + foreach ($index as $index_name => $columns) + { + $sql_data .= "\nCREATE INDEX [$index_name] ON [$table_name]($columns) ON [PRIMARY]\nGO\n"; + } + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->db->get_sql_layer() === 'mssqlnative') + { + $this->write_data_mssqlnative($table_name); + } + else + { + $this->write_data_odbc($table_name); + } + } + + /** + * Extracts data from database table (for MSSQL Native driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mssqlnative($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + $ident_set = false; + $sql_data = ''; + + // Grab all of the data from current table. + $sql = "SELECT * FROM $table_name"; + $this->db->mssqlnative_set_query_options(array('Scrollable' => SQLSRV_CURSOR_STATIC)); + $result = $this->db->sql_query($sql); + + $retrieved_data = $this->db->mssqlnative_num_rows($result); + + if (!$retrieved_data) + { + $this->db->sql_freeresult($result); + return; + } + + $sql = "SELECT COLUMN_NAME, DATA_TYPE + FROM INFORMATION_SCHEMA.COLUMNS + WHERE INFORMATION_SCHEMA.COLUMNS.TABLE_NAME = '" . $this->db->sql_escape($table_name) . "'"; + $result_fields = $this->db->sql_query($sql); + + $i_num_fields = 0; + while ($row = $this->db->sql_fetchrow($result_fields)) + { + $ary_type[$i_num_fields] = $row['DATA_TYPE']; + $ary_name[$i_num_fields] = $row['COLUMN_NAME']; + $i_num_fields++; + } + $this->db->sql_freeresult($result_fields); + + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $this->db->sql_query($sql); + $row2 = $this->db->sql_fetchrow($result2); + + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $this->db->sql_freeresult($result2); + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + // defaults to type number - better quote just to be safe, so check for is_int too + if (is_int($ary_type[$i]) || preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + $sql_data = ''; + } + $this->db->sql_freeresult($result); + + if ($ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } + + /** + * Extracts data from database table (for ODBC driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_odbc($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + $ident_set = false; + $sql_data = ''; + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $retrieved_data = odbc_num_rows($result); + + if ($retrieved_data) + { + $sql = "SELECT 1 as has_identity + FROM INFORMATION_SCHEMA.COLUMNS + WHERE COLUMNPROPERTY(object_id('$table_name'), COLUMN_NAME, 'IsIdentity') = 1"; + $result2 = $this->db->sql_query($sql); + $row2 = $this->db->sql_fetchrow($result2); + if (!empty($row2['has_identity'])) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name ON\nGO\n"; + $ident_set = true; + } + $this->db->sql_freeresult($result2); + } + + $i_num_fields = odbc_num_fields($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[$i] = odbc_field_type($result, $i + 1); + $ary_name[$i] = odbc_field_name($result, $i + 1); + } + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|varbinary#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_mssql(str_replace("'", "''", $str_val)); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0' && !(is_int($str_val) || is_float($str_val))) + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = $ary_name[$i]; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data .= "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ");\nGO\n"; + + $this->flush($sql_data); + + $sql_data = ''; + + } + $this->db->sql_freeresult($result); + + if ($retrieved_data && $ident_set) + { + $sql_data .= "\nSET IDENTITY_INSERT $table_name OFF\nGO\n"; + } + $this->flush($sql_data); + } +} diff --git a/phpbb/db/extractor/mysql_extractor.php b/phpbb/db/extractor/mysql_extractor.php new file mode 100644 index 0000000..34e309c --- /dev/null +++ b/phpbb/db/extractor/mysql_extractor.php @@ -0,0 +1,403 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class mysql_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "#\n"; + $sql_data .= "# phpBB Backup Script\n"; + $sql_data .= "# Dump of tables for $table_prefix\n"; + $sql_data .= "# DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "#\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + static $new_extract; + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($new_extract === null) + { + if ($this->db->get_sql_layer() === 'mysqli' || version_compare($this->db->sql_server_info(true), '3.23.20', '>=')) + { + $new_extract = true; + } + else + { + $new_extract = false; + } + } + + if ($new_extract) + { + $this->new_write_table($table_name); + } + else + { + $this->old_write_table($table_name); + } + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + if ($this->db->get_sql_layer() === 'mysqli') + { + $this->write_data_mysqli($table_name); + } + else + { + $this->write_data_mysql($table_name); + } + } + + /** + * Extracts data from database table (for MySQLi driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mysqli($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT * + FROM $table_name"; + $result = mysqli_query($this->db->get_db_connect_id(), $sql, MYSQLI_USE_RESULT); + if ($result != false) + { + $fields_cnt = mysqli_num_fields($result); + + // Get field information + $field = mysqli_fetch_fields($result); + $field_set = array(); + + for ($j = 0; $j < $fields_cnt; $j++) + { + $field_set[] = $field[$j]->name; + } + + $search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); + $replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); + $fields = implode(', ', $field_set); + $sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; + $first_set = true; + $query_len = 0; + $max_len = get_usable_memory(); + + while ($row = mysqli_fetch_row($result)) + { + $values = array(); + if ($first_set) + { + $query = $sql_data . '('; + } + else + { + $query .= ',('; + } + + for ($j = 0; $j < $fields_cnt; $j++) + { + if (!isset($row[$j]) || is_null($row[$j])) + { + $values[$j] = 'NULL'; + } + else if (($field[$j]->flags & 32768) && !($field[$j]->flags & 1024)) + { + $values[$j] = $row[$j]; + } + else + { + $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; + } + } + $query .= implode(', ', $values) . ')'; + + $query_len += strlen($query); + if ($query_len > $max_len) + { + $this->flush($query . ";\n\n"); + $query = ''; + $query_len = 0; + $first_set = true; + } + else + { + $first_set = false; + } + } + mysqli_free_result($result); + + // check to make sure we have nothing left to flush + if (!$first_set && $query) + { + $this->flush($query . ";\n\n"); + } + } + } + + /** + * Extracts data from database table (for MySQL driver) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function write_data_mysql($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT * + FROM $table_name"; + $result = mysql_unbuffered_query($sql, $this->db->get_db_connect_id()); + + if ($result != false) + { + $fields_cnt = mysql_num_fields($result); + + // Get field information + $field = array(); + for ($i = 0; $i < $fields_cnt; $i++) + { + $field[] = mysql_fetch_field($result, $i); + } + $field_set = array(); + + for ($j = 0; $j < $fields_cnt; $j++) + { + $field_set[] = $field[$j]->name; + } + + $search = array("\\", "'", "\x00", "\x0a", "\x0d", "\x1a", '"'); + $replace = array("\\\\", "\\'", '\0', '\n', '\r', '\Z', '\\"'); + $fields = implode(', ', $field_set); + $sql_data = 'INSERT INTO ' . $table_name . ' (' . $fields . ') VALUES '; + $first_set = true; + $query_len = 0; + $max_len = get_usable_memory(); + + while ($row = mysql_fetch_row($result)) + { + $values = array(); + if ($first_set) + { + $query = $sql_data . '('; + } + else + { + $query .= ',('; + } + + for ($j = 0; $j < $fields_cnt; $j++) + { + if (!isset($row[$j]) || is_null($row[$j])) + { + $values[$j] = 'NULL'; + } + else if ($field[$j]->numeric && ($field[$j]->type !== 'timestamp')) + { + $values[$j] = $row[$j]; + } + else + { + $values[$j] = "'" . str_replace($search, $replace, $row[$j]) . "'"; + } + } + $query .= implode(', ', $values) . ')'; + + $query_len += strlen($query); + if ($query_len > $max_len) + { + $this->flush($query . ";\n\n"); + $query = ''; + $query_len = 0; + $first_set = true; + } + else + { + $first_set = false; + } + } + mysql_free_result($result); + + // check to make sure we have nothing left to flush + if (!$first_set && $query) + { + $this->flush($query . ";\n\n"); + } + } + } + + /** + * Extracts database table structure (for MySQLi or MySQL 3.23.20+) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function new_write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = 'SHOW CREATE TABLE ' . $table_name; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + + $sql_data = '# Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; + $this->flush($sql_data . $row['Create Table'] . ";\n\n"); + + $this->db->sql_freeresult($result); + } + + /** + * Extracts database table structure (for MySQL verisons older than 3.23.20) + * + * @param string $table_name name of the database table + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + protected function old_write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '# Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE IF EXISTS $table_name;\n"; + $sql_data .= "CREATE TABLE $table_name(\n"; + $rows = array(); + + $sql = "SHOW FIELDS + FROM $table_name"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $line = ' ' . $row['Field'] . ' ' . $row['Type']; + + if (!is_null($row['Default'])) + { + $line .= " DEFAULT '{$row['Default']}'"; + } + + if ($row['Null'] != 'YES') + { + $line .= ' NOT NULL'; + } + + if ($row['Extra'] != '') + { + $line .= ' ' . $row['Extra']; + } + + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql = "SHOW KEYS + FROM $table_name"; + + $result = $this->db->sql_query($sql); + + $index = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $kname = $row['Key_name']; + + if ($kname != 'PRIMARY') + { + if ($row['Non_unique'] == 0) + { + $kname = "UNIQUE|$kname"; + } + } + + if ($row['Sub_part']) + { + $row['Column_name'] .= '(' . $row['Sub_part'] . ')'; + } + $index[$kname][] = $row['Column_name']; + } + $this->db->sql_freeresult($result); + + foreach ($index as $key => $columns) + { + $line = ' '; + + if ($key == 'PRIMARY') + { + $line .= 'PRIMARY KEY (' . implode(', ', $columns) . ')'; + } + else if (strpos($key, 'UNIQUE') === 0) + { + $line .= 'UNIQUE ' . substr($key, 7) . ' (' . implode(', ', $columns) . ')'; + } + else if (strpos($key, 'FULLTEXT') === 0) + { + $line .= 'FULLTEXT ' . substr($key, 9) . ' (' . implode(', ', $columns) . ')'; + } + else + { + $line .= "KEY $key (" . implode(', ', $columns) . ')'; + } + + $rows[] = $line; + } + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n);\n\n"; + + $this->flush($sql_data); + } +} diff --git a/phpbb/db/extractor/oracle_extractor.php b/phpbb/db/extractor/oracle_extractor.php new file mode 100644 index 0000000..bc43a37 --- /dev/null +++ b/phpbb/db/extractor/oracle_extractor.php @@ -0,0 +1,263 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class oracle_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name\n/\n"; + $sql_data .= "\nCREATE TABLE $table_name (\n"; + + $sql = "SELECT COLUMN_NAME, DATA_TYPE, DATA_PRECISION, DATA_LENGTH, NULLABLE, DATA_DEFAULT + FROM ALL_TAB_COLS + WHERE table_name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $rows = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $line = ' "' . $row['column_name'] . '" ' . $row['data_type']; + + if ($row['data_type'] !== 'CLOB') + { + if ($row['data_type'] !== 'VARCHAR2' && $row['data_type'] !== 'CHAR') + { + $line .= '(' . $row['data_precision'] . ')'; + } + else + { + $line .= '(' . $row['data_length'] . ')'; + } + } + + if (!empty($row['data_default'])) + { + $line .= ' DEFAULT ' . $row['data_default']; + } + + if ($row['nullable'] == 'N') + { + $line .= ' NOT NULL'; + } + $rows[] = $line; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME + FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B + WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME + AND B.CONSTRAINT_TYPE = 'P' + AND A.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $primary_key = array(); + $constraint_name = ''; + while ($row = $this->db->sql_fetchrow($result)) + { + $constraint_name = '"' . $row['constraint_name'] . '"'; + $primary_key[] = '"' . $row['column_name'] . '"'; + } + $this->db->sql_freeresult($result); + + if (count($primary_key)) + { + $rows[] = " CONSTRAINT {$constraint_name} PRIMARY KEY (" . implode(', ', $primary_key) . ')'; + } + + $sql = "SELECT A.CONSTRAINT_NAME, A.COLUMN_NAME + FROM USER_CONS_COLUMNS A, USER_CONSTRAINTS B + WHERE A.CONSTRAINT_NAME = B.CONSTRAINT_NAME + AND B.CONSTRAINT_TYPE = 'U' + AND A.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $unique = array(); + $constraint_name = ''; + while ($row = $this->db->sql_fetchrow($result)) + { + $constraint_name = '"' . $row['constraint_name'] . '"'; + $unique[] = '"' . $row['column_name'] . '"'; + } + $this->db->sql_freeresult($result); + + if (count($unique)) + { + $rows[] = " CONSTRAINT {$constraint_name} UNIQUE (" . implode(', ', $unique) . ')'; + } + + $sql_data .= implode(",\n", $rows); + $sql_data .= "\n)\n/\n"; + + $sql = "SELECT A.REFERENCED_NAME, C.* + FROM USER_DEPENDENCIES A, USER_TRIGGERS B, USER_SEQUENCES C + WHERE A.REFERENCED_TYPE = 'SEQUENCE' + AND A.NAME = B.TRIGGER_NAME + AND B.TABLE_NAME = '{$table_name}' + AND C.SEQUENCE_NAME = A.REFERENCED_NAME"; + $result = $this->db->sql_query($sql); + + $type = $this->request->variable('type', ''); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_data .= "\nDROP SEQUENCE \"{$row['referenced_name']}\"\n/\n"; + $sql_data .= "\nCREATE SEQUENCE \"{$row['referenced_name']}\""; + + if ($type == 'full') + { + $sql_data .= ' START WITH ' . $row['last_number']; + } + + $sql_data .= "\n/\n"; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT DESCRIPTION, WHEN_CLAUSE, TRIGGER_BODY + FROM USER_TRIGGERS + WHERE TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_data .= "\nCREATE OR REPLACE TRIGGER {$row['description']}WHEN ({$row['when_clause']})\n{$row['trigger_body']}\n/\n"; + } + $this->db->sql_freeresult($result); + + $sql = "SELECT A.INDEX_NAME, B.COLUMN_NAME + FROM USER_INDEXES A, USER_IND_COLUMNS B + WHERE A.UNIQUENESS = 'NONUNIQUE' + AND A.INDEX_NAME = B.INDEX_NAME + AND B.TABLE_NAME = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + $index = array(); + + while ($row = $this->db->sql_fetchrow($result)) + { + $index[$row['index_name']][] = $row['column_name']; + } + + foreach ($index as $index_name => $column_names) + { + $sql_data .= "\nCREATE INDEX $index_name ON $table_name(" . implode(', ', $column_names) . ")\n/\n"; + } + $this->db->sql_freeresult($result); + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $ary_type = $ary_name = array(); + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $i_num_fields = ocinumcols($result); + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[$i] = ocicolumntype($result, $i + 1); + $ary_name[$i] = ocicolumnname($result, $i + 1); + } + + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = $schema_fields = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + // Oracle uses uppercase - we use lowercase + $str_val = $row[strtolower($ary_name[$i])]; + + if (preg_match('#char|text|bool|raw|clob#i', $ary_type[$i])) + { + $str_quote = ''; + $str_empty = "''"; + $str_val = sanitize_data_oracle($str_val); + } + else if (preg_match('#date|timestamp#i', $ary_type[$i])) + { + if (empty($str_val)) + { + $str_quote = ''; + } + else + { + $str_quote = "'"; + } + } + else + { + $str_quote = ''; + $str_empty = 'NULL'; + } + + if (empty($str_val) && $str_val !== '0') + { + $str_val = $str_empty; + } + + $schema_vals[$i] = $str_quote . $str_val . $str_quote; + $schema_fields[$i] = '"' . $ary_name[$i] . '"'; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $sql_data = "INSERT INTO $table_name (" . implode(', ', $schema_fields) . ') VALUES (' . implode(', ', $schema_vals) . ")\n/\n"; + + $this->flush($sql_data); + } + $this->db->sql_freeresult($result); + } + + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $this->flush($sql_data); + } +} diff --git a/phpbb/db/extractor/postgres_extractor.php b/phpbb/db/extractor/postgres_extractor.php new file mode 100644 index 0000000..0219d2a --- /dev/null +++ b/phpbb/db/extractor/postgres_extractor.php @@ -0,0 +1,339 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class postgres_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION;\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + static $domains_created = array(); + + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql = "SELECT a.domain_name, a.data_type, a.character_maximum_length, a.domain_default + FROM INFORMATION_SCHEMA.domains a, INFORMATION_SCHEMA.column_domain_usage b + WHERE a.domain_name = b.domain_name + AND b.table_name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (empty($domains_created[$row['domain_name']])) + { + $domains_created[$row['domain_name']] = true; + //$sql_data = "DROP DOMAIN {$row['domain_name']};\n"; + $sql_data = "CREATE DOMAIN {$row['domain_name']} as {$row['data_type']}"; + if (!empty($row['character_maximum_length'])) + { + $sql_data .= '(' . $row['character_maximum_length'] . ')'; + } + $sql_data .= ' NOT NULL'; + if (!empty($row['domain_default'])) + { + $sql_data .= ' DEFAULT ' . $row['domain_default']; + } + $this->flush($sql_data . ";\n"); + } + } + $this->db->sql_freeresult($result); + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + // PGSQL does not "tightly" bind sequences and tables, we must guess... + $sql = "SELECT relname + FROM pg_class + WHERE relkind = 'S' + AND relname = '{$table_name}_seq'"; + $result = $this->db->sql_query($sql); + // We don't even care about storing the results. We already know the answer if we get rows back. + if ($this->db->sql_fetchrow($result)) + { + $sql_data .= "DROP SEQUENCE IF EXISTS {$table_name}_seq;\n"; + $sql_data .= "CREATE SEQUENCE {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + + $field_query = "SELECT a.attnum, a.attname as field, t.typname as type, a.attlen as length, a.atttypmod as lengthvar, a.attnotnull as notnull + FROM pg_class c, pg_attribute a, pg_type t + WHERE c.relname = '" . $this->db->sql_escape($table_name) . "' + AND a.attnum > 0 + AND a.attrelid = c.oid + AND a.atttypid = t.oid + ORDER BY a.attnum"; + $result = $this->db->sql_query($field_query); + + $sql_data .= "CREATE TABLE $table_name(\n"; + $lines = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + // Get the data from the table + $sql_get_default = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault + FROM pg_attrdef d, pg_class c + WHERE (c.relname = '" . $this->db->sql_escape($table_name) . "') + AND (c.oid = d.adrelid) + AND d.adnum = " . $row['attnum']; + $def_res = $this->db->sql_query($sql_get_default); + $def_row = $this->db->sql_fetchrow($def_res); + $this->db->sql_freeresult($def_res); + + if (empty($def_row)) + { + unset($row['rowdefault']); + } + else + { + $row['rowdefault'] = $def_row['rowdefault']; + } + + if ($row['type'] == 'bpchar') + { + // Internally stored as bpchar, but isn't accepted in a CREATE TABLE statement. + $row['type'] = 'char'; + } + + $line = ' ' . $row['field'] . ' ' . $row['type']; + + if (strpos($row['type'], 'char') !== false) + { + if ($row['lengthvar'] > 0) + { + $line .= '(' . ($row['lengthvar'] - 4) . ')'; + } + } + + if (strpos($row['type'], 'numeric') !== false) + { + $line .= '('; + $line .= sprintf("%s,%s", (($row['lengthvar'] >> 16) & 0xffff), (($row['lengthvar'] - 4) & 0xffff)); + $line .= ')'; + } + + if (isset($row['rowdefault'])) + { + $line .= ' DEFAULT ' . $row['rowdefault']; + } + + if ($row['notnull'] == 't') + { + $line .= ' NOT NULL'; + } + + $lines[] = $line; + } + $this->db->sql_freeresult($result); + + // Get the listing of primary keys. + $sql_pri_keys = "SELECT ic.relname as index_name, bc.relname as tab_name, ta.attname as column_name, i.indisunique as unique_key, i.indisprimary as primary_key + FROM pg_class bc, pg_class ic, pg_index i, pg_attribute ta, pg_attribute ia + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (ia.attrelid = i.indexrelid) + AND (ta.attrelid = bc.oid) + AND (bc.relname = '" . $this->db->sql_escape($table_name) . "') + AND (ta.attrelid = i.indrelid) + AND (ta.attnum = i.indkey[ia.attnum-1]) + ORDER BY index_name, tab_name, column_name"; + + $result = $this->db->sql_query($sql_pri_keys); + + $index_create = $index_rows = $primary_key = array(); + + // We do this in two steps. It makes placing the comma easier + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['primary_key'] == 't') + { + $primary_key[] = $row['column_name']; + $primary_key_name = $row['index_name']; + } + else + { + // We have to store this all this info because it is possible to have a multi-column key... + // we can loop through it again and build the statement + $index_rows[$row['index_name']]['table'] = $table_name; + $index_rows[$row['index_name']]['unique'] = ($row['unique_key'] == 't') ? true : false; + $index_rows[$row['index_name']]['column_names'][] = $row['column_name']; + } + } + $this->db->sql_freeresult($result); + + if (!empty($index_rows)) + { + foreach ($index_rows as $idx_name => $props) + { + $index_create[] = 'CREATE ' . ($props['unique'] ? 'UNIQUE ' : '') . "INDEX $idx_name ON $table_name (" . implode(', ', $props['column_names']) . ");"; + } + } + + if (!empty($primary_key)) + { + $lines[] = " CONSTRAINT $primary_key_name PRIMARY KEY (" . implode(', ', $primary_key) . ")"; + } + + // Generate constraint clauses for CHECK constraints + $sql_checks = "SELECT conname as index_name, consrc + FROM pg_constraint, pg_class bc + WHERE conrelid = bc.oid + AND bc.relname = '" . $this->db->sql_escape($table_name) . "' + AND NOT EXISTS ( + SELECT * + FROM pg_constraint as c, pg_inherits as i + WHERE i.inhrelid = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + $result = $this->db->sql_query($sql_checks); + + // Add the constraints to the sql file. + while ($row = $this->db->sql_fetchrow($result)) + { + if (!is_null($row['consrc'])) + { + $lines[] = ' CONSTRAINT ' . $row['index_name'] . ' CHECK ' . $row['consrc']; + } + } + $this->db->sql_freeresult($result); + + $sql_data .= implode(", \n", $lines); + $sql_data .= "\n);\n"; + + if (!empty($index_create)) + { + $sql_data .= implode("\n", $index_create) . "\n\n"; + } + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + // Grab all of the data from current table. + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + $i_num_fields = pg_num_fields($result); + $seq = ''; + + for ($i = 0; $i < $i_num_fields; $i++) + { + $ary_type[] = pg_field_type($result, $i); + $ary_name[] = pg_field_name($result, $i); + + $sql = "SELECT pg_get_expr(d.adbin, d.adrelid) as rowdefault + FROM pg_attrdef d, pg_class c + WHERE (c.relname = '{$table_name}') + AND (c.oid = d.adrelid) + AND d.adnum = " . strval($i + 1); + $result2 = $this->db->sql_query($sql); + if ($row = $this->db->sql_fetchrow($result2)) + { + // Determine if we must reset the sequences + if (strpos($row['rowdefault'], "nextval('") === 0) + { + $seq .= "SELECT SETVAL('{$table_name}_seq',(select case when max({$ary_name[$i]})>0 then max({$ary_name[$i]})+1 else 1 end FROM {$table_name}));\n"; + } + } + } + + $this->flush("COPY $table_name (" . implode(', ', $ary_name) . ') FROM stdin;' . "\n"); + while ($row = $this->db->sql_fetchrow($result)) + { + $schema_vals = array(); + + // Build the SQL statement to recreate the data. + for ($i = 0; $i < $i_num_fields; $i++) + { + $str_val = $row[$ary_name[$i]]; + + if (preg_match('#char|text|bool|bytea#i', $ary_type[$i])) + { + $str_val = str_replace(array("\n", "\t", "\r", "\b", "\f", "\v"), array('\n', '\t', '\r', '\b', '\f', '\v'), addslashes($str_val)); + $str_empty = ''; + } + else + { + $str_empty = '\N'; + } + + if (empty($str_val) && $str_val !== '0') + { + $str_val = $str_empty; + } + + $schema_vals[] = $str_val; + } + + // Take the ordered fields and their associated data and build it + // into a valid sql statement to recreate that field in the data. + $this->flush(implode("\t", $schema_vals) . "\n"); + } + $this->db->sql_freeresult($result); + $this->flush("\\.\n"); + + // Write out the sequence statements + $this->flush($seq); + } + + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT;\n"); + parent::write_end(); + } +} diff --git a/phpbb/db/extractor/sqlite3_extractor.php b/phpbb/db/extractor/sqlite3_extractor.php new file mode 100644 index 0000000..ce8da6a --- /dev/null +++ b/phpbb/db/extractor/sqlite3_extractor.php @@ -0,0 +1,151 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\extractor; + +use phpbb\db\extractor\exception\extractor_not_initialized_exception; + +class sqlite3_extractor extends base_extractor +{ + /** + * {@inheritdoc} + */ + public function write_start($table_prefix) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = "--\n"; + $sql_data .= "-- phpBB Backup Script\n"; + $sql_data .= "-- Dump of tables for $table_prefix\n"; + $sql_data .= "-- DATE : " . gmdate("d-m-Y H:i:s", $this->time) . " GMT\n"; + $sql_data .= "--\n"; + $sql_data .= "BEGIN TRANSACTION;\n"; + $this->flush($sql_data); + } + + /** + * {@inheritdoc} + */ + public function write_table($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $sql_data = '-- Table: ' . $table_name . "\n"; + $sql_data .= "DROP TABLE $table_name;\n"; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '" . $this->db->sql_escape($table_name) . "' + ORDER BY name ASC;"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create Table + $sql_data .= $row['sql'] . ";\n"; + + $result = $this->db->sql_query("PRAGMA index_list('" . $this->db->sql_escape($table_name) . "');"); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (strpos($row['name'], 'autoindex') !== false) + { + continue; + } + + $result2 = $this->db->sql_query("PRAGMA index_info('" . $this->db->sql_escape($row['name']) . "');"); + + $fields = array(); + while ($row2 = $this->db->sql_fetchrow($result2)) + { + $fields[] = $row2['name']; + } + $this->db->sql_freeresult($result2); + + $sql_data .= 'CREATE ' . ($row['unique'] ? 'UNIQUE ' : '') . 'INDEX ' . $row['name'] . ' ON ' . $table_name . ' (' . implode(', ', $fields) . ");\n"; + } + $this->db->sql_freeresult($result); + + $this->flush($sql_data . "\n"); + } + + /** + * {@inheritdoc} + */ + public function write_data($table_name) + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $result = $this->db->sql_query("PRAGMA table_info('" . $this->db->sql_escape($table_name) . "');"); + + $col_types = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $col_types[$row['name']] = $row['type']; + } + $this->db->sql_freeresult($result); + + $sql_insert = 'INSERT INTO ' . $table_name . ' (' . implode(', ', array_keys($col_types)) . ') VALUES ('; + + $sql = "SELECT * + FROM $table_name"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + foreach ($row as $column_name => $column_data) + { + if (is_null($column_data)) + { + $row[$column_name] = 'NULL'; + } + else if ($column_data === '') + { + $row[$column_name] = "''"; + } + else if (stripos($col_types[$column_name], 'text') !== false || stripos($col_types[$column_name], 'char') !== false || stripos($col_types[$column_name], 'blob') !== false) + { + $row[$column_name] = sanitize_data_generic(str_replace("'", "''", $column_data)); + } + } + $this->flush($sql_insert . implode(', ', $row) . ");\n"); + } + } + + /** + * Writes closing line(s) to database backup + * + * @return null + * @throws \phpbb\db\extractor\exception\extractor_not_initialized_exception when calling this function before init_extractor() + */ + public function write_end() + { + if (!$this->is_initialized) + { + throw new extractor_not_initialized_exception(); + } + + $this->flush("COMMIT;\n"); + parent::write_end(); + } +} diff --git a/phpbb/db/migration/container_aware_migration.php b/phpbb/db/migration/container_aware_migration.php new file mode 100644 index 0000000..3b4b49b --- /dev/null +++ b/phpbb/db/migration/container_aware_migration.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration; + +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** +* Abstract base class for container aware database migrations. +*/ +abstract class container_aware_migration extends migration implements ContainerAwareInterface +{ + /** + * @var ContainerInterface + */ + protected $container; + + /** + * {@inheritdoc} + */ + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } +} diff --git a/phpbb/db/migration/data/v30x/.htaccess b/phpbb/db/migration/data/v30x/.htaccess new file mode 100644 index 0000000..44242b5 --- /dev/null +++ b/phpbb/db/migration/data/v30x/.htaccess @@ -0,0 +1,33 @@ +# With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from +# module mod_authz_host to a new module called mod_access_compat (which may be +# disabled) and a new "Require" syntax has been introduced to mod_authz_host. +# We could just conditionally provide both versions, but unfortunately Apache +# does not explicitly tell us its version if the module mod_version is not +# available. In this case, we check for the availability of module +# mod_authz_core (which should be on 2.4 or higher only) as a best guess. + + + + Order Allow,Deny + Deny from All + + + = 2.4> + + Require all denied + + + + + + + Order Allow,Deny + Deny from All + + + + + Require all denied + + + diff --git a/phpbb/db/migration/data/v30x/local_url_bbcode.php b/phpbb/db/migration/data/v30x/local_url_bbcode.php new file mode 100644 index 0000000..648ae9c --- /dev/null +++ b/phpbb/db/migration/data/v30x/local_url_bbcode.php @@ -0,0 +1,70 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class local_url_bbcode extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_12_rc1'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_local_url_bbcode'))), + ); + } + + /** + * Update BBCodes that currently use the LOCAL_URL tag + * + * To fix http://tracker.phpbb.com/browse/PHPBB3-8319 we changed + * the second_pass_replace value, so that needs updating for existing ones + */ + public function update_local_url_bbcode() + { + $sql = 'SELECT * + FROM ' . BBCODES_TABLE . ' + WHERE bbcode_match ' . $this->db->sql_like_expression($this->db->get_any_char() . 'LOCAL_URL' . $this->db->get_any_char()); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!class_exists('acp_bbcodes')) + { + if (function_exists('phpbb_require_updated')) + { + phpbb_require_updated('includes/acp/acp_bbcodes.' . $this->php_ext); + } + else + { + require($this->phpbb_root_path . 'includes/acp/acp_bbcodes.' . $this->php_ext); + } + } + + $bbcode_match = $row['bbcode_match']; + $bbcode_tpl = $row['bbcode_tpl']; + + $acp_bbcodes = new \acp_bbcodes(); + $sql_ary = $acp_bbcodes->build_regexp($bbcode_match, $bbcode_tpl); + + $sql = 'UPDATE ' . BBCODES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE bbcode_id = ' . (int) $row['bbcode_id']; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_0.php b/phpbb/db/migration/data/v30x/release_3_0_0.php new file mode 100644 index 0000000..26937d6 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_0.php @@ -0,0 +1,1181 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_0 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.0', '>='); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'attachments' => array( + 'COLUMNS' => array( + 'attach_id' => array('UINT', NULL, 'auto_increment'), + 'post_msg_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + 'in_message' => array('BOOL', 0), + 'poster_id' => array('UINT', 0), + 'is_orphan' => array('BOOL', 1), + 'physical_filename' => array('VCHAR', ''), + 'real_filename' => array('VCHAR', ''), + 'download_count' => array('UINT', 0), + 'attach_comment' => array('TEXT_UNI', ''), + 'extension' => array('VCHAR:100', ''), + 'mimetype' => array('VCHAR:100', ''), + 'filesize' => array('UINT:20', 0), + 'filetime' => array('TIMESTAMP', 0), + 'thumbnail' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'attach_id', + 'KEYS' => array( + 'filetime' => array('INDEX', 'filetime'), + 'post_msg_id' => array('INDEX', 'post_msg_id'), + 'topic_id' => array('INDEX', 'topic_id'), + 'poster_id' => array('INDEX', 'poster_id'), + 'is_orphan' => array('INDEX', 'is_orphan'), + ), + ), + + $this->table_prefix . 'acl_groups' => array( + 'COLUMNS' => array( + 'group_id' => array('UINT', 0), + 'forum_id' => array('UINT', 0), + 'auth_option_id' => array('UINT', 0), + 'auth_role_id' => array('UINT', 0), + 'auth_setting' => array('TINT:2', 0), + ), + 'KEYS' => array( + 'group_id' => array('INDEX', 'group_id'), + 'auth_opt_id' => array('INDEX', 'auth_option_id'), + 'auth_role_id' => array('INDEX', 'auth_role_id'), + ), + ), + + $this->table_prefix . 'acl_options' => array( + 'COLUMNS' => array( + 'auth_option_id' => array('UINT', NULL, 'auto_increment'), + 'auth_option' => array('VCHAR:50', ''), + 'is_global' => array('BOOL', 0), + 'is_local' => array('BOOL', 0), + 'founder_only' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'auth_option_id', + 'KEYS' => array( + 'auth_option' => array('INDEX', 'auth_option'), + ), + ), + + $this->table_prefix . 'acl_roles' => array( + 'COLUMNS' => array( + 'role_id' => array('UINT', NULL, 'auto_increment'), + 'role_name' => array('VCHAR_UNI', ''), + 'role_description' => array('TEXT_UNI', ''), + 'role_type' => array('VCHAR:10', ''), + 'role_order' => array('USINT', 0), + ), + 'PRIMARY_KEY' => 'role_id', + 'KEYS' => array( + 'role_type' => array('INDEX', 'role_type'), + 'role_order' => array('INDEX', 'role_order'), + ), + ), + + $this->table_prefix . 'acl_roles_data' => array( + 'COLUMNS' => array( + 'role_id' => array('UINT', 0), + 'auth_option_id' => array('UINT', 0), + 'auth_setting' => array('TINT:2', 0), + ), + 'PRIMARY_KEY' => array('role_id', 'auth_option_id'), + 'KEYS' => array( + 'ath_op_id' => array('INDEX', 'auth_option_id'), + ), + ), + + $this->table_prefix . 'acl_users' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'forum_id' => array('UINT', 0), + 'auth_option_id' => array('UINT', 0), + 'auth_role_id' => array('UINT', 0), + 'auth_setting' => array('TINT:2', 0), + ), + 'KEYS' => array( + 'user_id' => array('INDEX', 'user_id'), + 'auth_option_id' => array('INDEX', 'auth_option_id'), + 'auth_role_id' => array('INDEX', 'auth_role_id'), + ), + ), + + $this->table_prefix . 'banlist' => array( + 'COLUMNS' => array( + 'ban_id' => array('UINT', NULL, 'auto_increment'), + 'ban_userid' => array('UINT', 0), + 'ban_ip' => array('VCHAR:40', ''), + 'ban_email' => array('VCHAR_UNI:100', ''), + 'ban_start' => array('TIMESTAMP', 0), + 'ban_end' => array('TIMESTAMP', 0), + 'ban_exclude' => array('BOOL', 0), + 'ban_reason' => array('VCHAR_UNI', ''), + 'ban_give_reason' => array('VCHAR_UNI', ''), + ), + 'PRIMARY_KEY' => 'ban_id', + 'KEYS' => array( + 'ban_end' => array('INDEX', 'ban_end'), + 'ban_user' => array('INDEX', array('ban_userid', 'ban_exclude')), + 'ban_email' => array('INDEX', array('ban_email', 'ban_exclude')), + 'ban_ip' => array('INDEX', array('ban_ip', 'ban_exclude')), + ), + ), + + $this->table_prefix . 'bbcodes' => array( + 'COLUMNS' => array( + 'bbcode_id' => array('TINT:3', 0), + 'bbcode_tag' => array('VCHAR:16', ''), + 'bbcode_helpline' => array('VCHAR_UNI', ''), + 'display_on_posting' => array('BOOL', 0), + 'bbcode_match' => array('TEXT_UNI', ''), + 'bbcode_tpl' => array('MTEXT_UNI', ''), + 'first_pass_match' => array('MTEXT_UNI', ''), + 'first_pass_replace' => array('MTEXT_UNI', ''), + 'second_pass_match' => array('MTEXT_UNI', ''), + 'second_pass_replace' => array('MTEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'bbcode_id', + 'KEYS' => array( + 'display_on_post' => array('INDEX', 'display_on_posting'), + ), + ), + + $this->table_prefix . 'bookmarks' => array( + 'COLUMNS' => array( + 'topic_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + 'PRIMARY_KEY' => array('topic_id', 'user_id'), + ), + + $this->table_prefix . 'bots' => array( + 'COLUMNS' => array( + 'bot_id' => array('UINT', NULL, 'auto_increment'), + 'bot_active' => array('BOOL', 1), + 'bot_name' => array('STEXT_UNI', ''), + 'user_id' => array('UINT', 0), + 'bot_agent' => array('VCHAR', ''), + 'bot_ip' => array('VCHAR', ''), + ), + 'PRIMARY_KEY' => 'bot_id', + 'KEYS' => array( + 'bot_active' => array('INDEX', 'bot_active'), + ), + ), + + $this->table_prefix . 'config' => array( + 'COLUMNS' => array( + 'config_name' => array('VCHAR', ''), + 'config_value' => array('VCHAR_UNI', ''), + 'is_dynamic' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'config_name', + 'KEYS' => array( + 'is_dynamic' => array('INDEX', 'is_dynamic'), + ), + ), + + $this->table_prefix . 'confirm' => array( + 'COLUMNS' => array( + 'confirm_id' => array('CHAR:32', ''), + 'session_id' => array('CHAR:32', ''), + 'confirm_type' => array('TINT:3', 0), + 'code' => array('VCHAR:8', ''), + 'seed' => array('UINT:10', 0), + ), + 'PRIMARY_KEY' => array('session_id', 'confirm_id'), + 'KEYS' => array( + 'confirm_type' => array('INDEX', 'confirm_type'), + ), + ), + + $this->table_prefix . 'disallow' => array( + 'COLUMNS' => array( + 'disallow_id' => array('UINT', NULL, 'auto_increment'), + 'disallow_username' => array('VCHAR_UNI:255', ''), + ), + 'PRIMARY_KEY' => 'disallow_id', + ), + + $this->table_prefix . 'drafts' => array( + 'COLUMNS' => array( + 'draft_id' => array('UINT', NULL, 'auto_increment'), + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + 'forum_id' => array('UINT', 0), + 'save_time' => array('TIMESTAMP', 0), + 'draft_subject' => array('XSTEXT_UNI', ''), + 'draft_message' => array('MTEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'draft_id', + 'KEYS' => array( + 'save_time' => array('INDEX', 'save_time'), + ), + ), + + $this->table_prefix . 'extensions' => array( + 'COLUMNS' => array( + 'extension_id' => array('UINT', NULL, 'auto_increment'), + 'group_id' => array('UINT', 0), + 'extension' => array('VCHAR:100', ''), + ), + 'PRIMARY_KEY' => 'extension_id', + ), + + $this->table_prefix . 'extension_groups' => array( + 'COLUMNS' => array( + 'group_id' => array('UINT', NULL, 'auto_increment'), + 'group_name' => array('VCHAR_UNI', ''), + 'cat_id' => array('TINT:2', 0), + 'allow_group' => array('BOOL', 0), + 'download_mode' => array('BOOL', 1), + 'upload_icon' => array('VCHAR', ''), + 'max_filesize' => array('UINT:20', 0), + 'allowed_forums' => array('TEXT', ''), + 'allow_in_pm' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'group_id', + ), + + $this->table_prefix . 'forums' => array( + 'COLUMNS' => array( + 'forum_id' => array('UINT', NULL, 'auto_increment'), + 'parent_id' => array('UINT', 0), + 'left_id' => array('UINT', 0), + 'right_id' => array('UINT', 0), + 'forum_parents' => array('MTEXT', ''), + 'forum_name' => array('STEXT_UNI', ''), + 'forum_desc' => array('TEXT_UNI', ''), + 'forum_desc_bitfield' => array('VCHAR:255', ''), + 'forum_desc_options' => array('UINT:11', 7), + 'forum_desc_uid' => array('VCHAR:8', ''), + 'forum_link' => array('VCHAR_UNI', ''), + 'forum_password' => array('VCHAR_UNI:40', ''), + 'forum_style' => array('USINT', 0), + 'forum_image' => array('VCHAR', ''), + 'forum_rules' => array('TEXT_UNI', ''), + 'forum_rules_link' => array('VCHAR_UNI', ''), + 'forum_rules_bitfield' => array('VCHAR:255', ''), + 'forum_rules_options' => array('UINT:11', 7), + 'forum_rules_uid' => array('VCHAR:8', ''), + 'forum_topics_per_page' => array('TINT:4', 0), + 'forum_type' => array('TINT:4', 0), + 'forum_status' => array('TINT:4', 0), + 'forum_posts' => array('UINT', 0), + 'forum_topics' => array('UINT', 0), + 'forum_topics_real' => array('UINT', 0), + 'forum_last_post_id' => array('UINT', 0), + 'forum_last_poster_id' => array('UINT', 0), + 'forum_last_post_subject' => array('XSTEXT_UNI', ''), + 'forum_last_post_time' => array('TIMESTAMP', 0), + 'forum_last_poster_name'=> array('VCHAR_UNI', ''), + 'forum_last_poster_colour'=> array('VCHAR:6', ''), + 'forum_flags' => array('TINT:4', 32), + 'display_on_index' => array('BOOL', 1), + 'enable_indexing' => array('BOOL', 1), + 'enable_icons' => array('BOOL', 1), + 'enable_prune' => array('BOOL', 0), + 'prune_next' => array('TIMESTAMP', 0), + 'prune_days' => array('UINT', 0), + 'prune_viewed' => array('UINT', 0), + 'prune_freq' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'forum_id', + 'KEYS' => array( + 'left_right_id' => array('INDEX', array('left_id', 'right_id')), + 'forum_lastpost_id' => array('INDEX', 'forum_last_post_id'), + ), + ), + + $this->table_prefix . 'forums_access' => array( + 'COLUMNS' => array( + 'forum_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'session_id' => array('CHAR:32', ''), + ), + 'PRIMARY_KEY' => array('forum_id', 'user_id', 'session_id'), + ), + + $this->table_prefix . 'forums_track' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'forum_id' => array('UINT', 0), + 'mark_time' => array('TIMESTAMP', 0), + ), + 'PRIMARY_KEY' => array('user_id', 'forum_id'), + ), + + $this->table_prefix . 'forums_watch' => array( + 'COLUMNS' => array( + 'forum_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notify_status' => array('BOOL', 0), + ), + 'KEYS' => array( + 'forum_id' => array('INDEX', 'forum_id'), + 'user_id' => array('INDEX', 'user_id'), + 'notify_stat' => array('INDEX', 'notify_status'), + ), + ), + + $this->table_prefix . 'groups' => array( + 'COLUMNS' => array( + 'group_id' => array('UINT', NULL, 'auto_increment'), + 'group_type' => array('TINT:4', 1), + 'group_founder_manage' => array('BOOL', 0), + 'group_name' => array('VCHAR_CI', ''), + 'group_desc' => array('TEXT_UNI', ''), + 'group_desc_bitfield' => array('VCHAR:255', ''), + 'group_desc_options' => array('UINT:11', 7), + 'group_desc_uid' => array('VCHAR:8', ''), + 'group_display' => array('BOOL', 0), + 'group_avatar' => array('VCHAR', ''), + 'group_avatar_type' => array('TINT:2', 0), + 'group_avatar_width' => array('USINT', 0), + 'group_avatar_height' => array('USINT', 0), + 'group_rank' => array('UINT', 0), + 'group_colour' => array('VCHAR:6', ''), + 'group_sig_chars' => array('UINT', 0), + 'group_receive_pm' => array('BOOL', 0), + 'group_message_limit' => array('UINT', 0), + 'group_legend' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => 'group_id', + 'KEYS' => array( + 'group_legend' => array('INDEX', 'group_legend'), + ), + ), + + $this->table_prefix . 'icons' => array( + 'COLUMNS' => array( + 'icons_id' => array('UINT', NULL, 'auto_increment'), + 'icons_url' => array('VCHAR', ''), + 'icons_width' => array('TINT:4', 0), + 'icons_height' => array('TINT:4', 0), + 'icons_order' => array('UINT', 0), + 'display_on_posting' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => 'icons_id', + 'KEYS' => array( + 'display_on_posting' => array('INDEX', 'display_on_posting'), + ), + ), + + $this->table_prefix . 'lang' => array( + 'COLUMNS' => array( + 'lang_id' => array('TINT:4', NULL, 'auto_increment'), + 'lang_iso' => array('VCHAR:30', ''), + 'lang_dir' => array('VCHAR:30', ''), + 'lang_english_name' => array('VCHAR_UNI:100', ''), + 'lang_local_name' => array('VCHAR_UNI:255', ''), + 'lang_author' => array('VCHAR_UNI:255', ''), + ), + 'PRIMARY_KEY' => 'lang_id', + 'KEYS' => array( + 'lang_iso' => array('INDEX', 'lang_iso'), + ), + ), + + $this->table_prefix . 'log' => array( + 'COLUMNS' => array( + 'log_id' => array('UINT', NULL, 'auto_increment'), + 'log_type' => array('TINT:4', 0), + 'user_id' => array('UINT', 0), + 'forum_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + 'reportee_id' => array('UINT', 0), + 'log_ip' => array('VCHAR:40', ''), + 'log_time' => array('TIMESTAMP', 0), + 'log_operation' => array('TEXT_UNI', ''), + 'log_data' => array('MTEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'log_id', + 'KEYS' => array( + 'log_type' => array('INDEX', 'log_type'), + 'forum_id' => array('INDEX', 'forum_id'), + 'topic_id' => array('INDEX', 'topic_id'), + 'reportee_id' => array('INDEX', 'reportee_id'), + 'user_id' => array('INDEX', 'user_id'), + ), + ), + + $this->table_prefix . 'moderator_cache' => array( + 'COLUMNS' => array( + 'forum_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'username' => array('VCHAR_UNI:255', ''), + 'group_id' => array('UINT', 0), + 'group_name' => array('VCHAR_UNI', ''), + 'display_on_index' => array('BOOL', 1), + ), + 'KEYS' => array( + 'disp_idx' => array('INDEX', 'display_on_index'), + 'forum_id' => array('INDEX', 'forum_id'), + ), + ), + + $this->table_prefix . 'modules' => array( + 'COLUMNS' => array( + 'module_id' => array('UINT', NULL, 'auto_increment'), + 'module_enabled' => array('BOOL', 1), + 'module_display' => array('BOOL', 1), + 'module_basename' => array('VCHAR', ''), + 'module_class' => array('VCHAR:10', ''), + 'parent_id' => array('UINT', 0), + 'left_id' => array('UINT', 0), + 'right_id' => array('UINT', 0), + 'module_langname' => array('VCHAR', ''), + 'module_mode' => array('VCHAR', ''), + 'module_auth' => array('VCHAR', ''), + ), + 'PRIMARY_KEY' => 'module_id', + 'KEYS' => array( + 'left_right_id' => array('INDEX', array('left_id', 'right_id')), + 'module_enabled' => array('INDEX', 'module_enabled'), + 'class_left_id' => array('INDEX', array('module_class', 'left_id')), + ), + ), + + $this->table_prefix . 'poll_options' => array( + 'COLUMNS' => array( + 'poll_option_id' => array('TINT:4', 0), + 'topic_id' => array('UINT', 0), + 'poll_option_text' => array('TEXT_UNI', ''), + 'poll_option_total' => array('UINT', 0), + ), + 'KEYS' => array( + 'poll_opt_id' => array('INDEX', 'poll_option_id'), + 'topic_id' => array('INDEX', 'topic_id'), + ), + ), + + $this->table_prefix . 'poll_votes' => array( + 'COLUMNS' => array( + 'topic_id' => array('UINT', 0), + 'poll_option_id' => array('TINT:4', 0), + 'vote_user_id' => array('UINT', 0), + 'vote_user_ip' => array('VCHAR:40', ''), + ), + 'KEYS' => array( + 'topic_id' => array('INDEX', 'topic_id'), + 'vote_user_id' => array('INDEX', 'vote_user_id'), + 'vote_user_ip' => array('INDEX', 'vote_user_ip'), + ), + ), + + $this->table_prefix . 'posts' => array( + 'COLUMNS' => array( + 'post_id' => array('UINT', NULL, 'auto_increment'), + 'topic_id' => array('UINT', 0), + 'forum_id' => array('UINT', 0), + 'poster_id' => array('UINT', 0), + 'icon_id' => array('UINT', 0), + 'poster_ip' => array('VCHAR:40', ''), + 'post_time' => array('TIMESTAMP', 0), + 'post_approved' => array('BOOL', 1), + 'post_reported' => array('BOOL', 0), + 'enable_bbcode' => array('BOOL', 1), + 'enable_smilies' => array('BOOL', 1), + 'enable_magic_url' => array('BOOL', 1), + 'enable_sig' => array('BOOL', 1), + 'post_username' => array('VCHAR_UNI:255', ''), + 'post_subject' => array('XSTEXT_UNI', '', 'true_sort'), + 'post_text' => array('MTEXT_UNI', ''), + 'post_checksum' => array('VCHAR:32', ''), + 'post_attachment' => array('BOOL', 0), + 'bbcode_bitfield' => array('VCHAR:255', ''), + 'bbcode_uid' => array('VCHAR:8', ''), + 'post_postcount' => array('BOOL', 1), + 'post_edit_time' => array('TIMESTAMP', 0), + 'post_edit_reason' => array('STEXT_UNI', ''), + 'post_edit_user' => array('UINT', 0), + 'post_edit_count' => array('USINT', 0), + 'post_edit_locked' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'post_id', + 'KEYS' => array( + 'forum_id' => array('INDEX', 'forum_id'), + 'topic_id' => array('INDEX', 'topic_id'), + 'poster_ip' => array('INDEX', 'poster_ip'), + 'poster_id' => array('INDEX', 'poster_id'), + 'post_approved' => array('INDEX', 'post_approved'), + 'tid_post_time' => array('INDEX', array('topic_id', 'post_time')), + ), + ), + + $this->table_prefix . 'privmsgs' => array( + 'COLUMNS' => array( + 'msg_id' => array('UINT', NULL, 'auto_increment'), + 'root_level' => array('UINT', 0), + 'author_id' => array('UINT', 0), + 'icon_id' => array('UINT', 0), + 'author_ip' => array('VCHAR:40', ''), + 'message_time' => array('TIMESTAMP', 0), + 'enable_bbcode' => array('BOOL', 1), + 'enable_smilies' => array('BOOL', 1), + 'enable_magic_url' => array('BOOL', 1), + 'enable_sig' => array('BOOL', 1), + 'message_subject' => array('XSTEXT_UNI', ''), + 'message_text' => array('MTEXT_UNI', ''), + 'message_edit_reason' => array('STEXT_UNI', ''), + 'message_edit_user' => array('UINT', 0), + 'message_attachment' => array('BOOL', 0), + 'bbcode_bitfield' => array('VCHAR:255', ''), + 'bbcode_uid' => array('VCHAR:8', ''), + 'message_edit_time' => array('TIMESTAMP', 0), + 'message_edit_count' => array('USINT', 0), + 'to_address' => array('TEXT_UNI', ''), + 'bcc_address' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'msg_id', + 'KEYS' => array( + 'author_ip' => array('INDEX', 'author_ip'), + 'message_time' => array('INDEX', 'message_time'), + 'author_id' => array('INDEX', 'author_id'), + 'root_level' => array('INDEX', 'root_level'), + ), + ), + + $this->table_prefix . 'privmsgs_folder' => array( + 'COLUMNS' => array( + 'folder_id' => array('UINT', NULL, 'auto_increment'), + 'user_id' => array('UINT', 0), + 'folder_name' => array('VCHAR_UNI', ''), + 'pm_count' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'folder_id', + 'KEYS' => array( + 'user_id' => array('INDEX', 'user_id'), + ), + ), + + $this->table_prefix . 'privmsgs_rules' => array( + 'COLUMNS' => array( + 'rule_id' => array('UINT', NULL, 'auto_increment'), + 'user_id' => array('UINT', 0), + 'rule_check' => array('UINT', 0), + 'rule_connection' => array('UINT', 0), + 'rule_string' => array('VCHAR_UNI', ''), + 'rule_user_id' => array('UINT', 0), + 'rule_group_id' => array('UINT', 0), + 'rule_action' => array('UINT', 0), + 'rule_folder_id' => array('INT:11', 0), + ), + 'PRIMARY_KEY' => 'rule_id', + 'KEYS' => array( + 'user_id' => array('INDEX', 'user_id'), + ), + ), + + $this->table_prefix . 'privmsgs_to' => array( + 'COLUMNS' => array( + 'msg_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'author_id' => array('UINT', 0), + 'pm_deleted' => array('BOOL', 0), + 'pm_new' => array('BOOL', 1), + 'pm_unread' => array('BOOL', 1), + 'pm_replied' => array('BOOL', 0), + 'pm_marked' => array('BOOL', 0), + 'pm_forwarded' => array('BOOL', 0), + 'folder_id' => array('INT:11', 0), + ), + 'KEYS' => array( + 'msg_id' => array('INDEX', 'msg_id'), + 'author_id' => array('INDEX', 'author_id'), + 'usr_flder_id' => array('INDEX', array('user_id', 'folder_id')), + ), + ), + + $this->table_prefix . 'profile_fields' => array( + 'COLUMNS' => array( + 'field_id' => array('UINT', NULL, 'auto_increment'), + 'field_name' => array('VCHAR_UNI', ''), + 'field_type' => array('TINT:4', 0), + 'field_ident' => array('VCHAR:20', ''), + 'field_length' => array('VCHAR:20', ''), + 'field_minlen' => array('VCHAR', ''), + 'field_maxlen' => array('VCHAR', ''), + 'field_novalue' => array('VCHAR_UNI', ''), + 'field_default_value' => array('VCHAR_UNI', ''), + 'field_validation' => array('VCHAR_UNI:20', ''), + 'field_required' => array('BOOL', 0), + 'field_show_on_reg' => array('BOOL', 0), + 'field_hide' => array('BOOL', 0), + 'field_no_view' => array('BOOL', 0), + 'field_active' => array('BOOL', 0), + 'field_order' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'field_id', + 'KEYS' => array( + 'fld_type' => array('INDEX', 'field_type'), + 'fld_ordr' => array('INDEX', 'field_order'), + ), + ), + + $this->table_prefix . 'profile_fields_data' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'user_id', + ), + + $this->table_prefix . 'profile_fields_lang' => array( + 'COLUMNS' => array( + 'field_id' => array('UINT', 0), + 'lang_id' => array('UINT', 0), + 'option_id' => array('UINT', 0), + 'field_type' => array('TINT:4', 0), + 'lang_value' => array('VCHAR_UNI', ''), + ), + 'PRIMARY_KEY' => array('field_id', 'lang_id', 'option_id'), + ), + + $this->table_prefix . 'profile_lang' => array( + 'COLUMNS' => array( + 'field_id' => array('UINT', 0), + 'lang_id' => array('UINT', 0), + 'lang_name' => array('VCHAR_UNI', ''), + 'lang_explain' => array('TEXT_UNI', ''), + 'lang_default_value' => array('VCHAR_UNI', ''), + ), + 'PRIMARY_KEY' => array('field_id', 'lang_id'), + ), + + $this->table_prefix . 'ranks' => array( + 'COLUMNS' => array( + 'rank_id' => array('UINT', NULL, 'auto_increment'), + 'rank_title' => array('VCHAR_UNI', ''), + 'rank_min' => array('UINT', 0), + 'rank_special' => array('BOOL', 0), + 'rank_image' => array('VCHAR', ''), + ), + 'PRIMARY_KEY' => 'rank_id', + ), + + $this->table_prefix . 'reports' => array( + 'COLUMNS' => array( + 'report_id' => array('UINT', NULL, 'auto_increment'), + 'reason_id' => array('USINT', 0), + 'post_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'user_notify' => array('BOOL', 0), + 'report_closed' => array('BOOL', 0), + 'report_time' => array('TIMESTAMP', 0), + 'report_text' => array('MTEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'report_id', + ), + + $this->table_prefix . 'reports_reasons' => array( + 'COLUMNS' => array( + 'reason_id' => array('USINT', NULL, 'auto_increment'), + 'reason_title' => array('VCHAR_UNI', ''), + 'reason_description' => array('MTEXT_UNI', ''), + 'reason_order' => array('USINT', 0), + ), + 'PRIMARY_KEY' => 'reason_id', + ), + + $this->table_prefix . 'search_results' => array( + 'COLUMNS' => array( + 'search_key' => array('VCHAR:32', ''), + 'search_time' => array('TIMESTAMP', 0), + 'search_keywords' => array('MTEXT_UNI', ''), + 'search_authors' => array('MTEXT', ''), + ), + 'PRIMARY_KEY' => 'search_key', + ), + + $this->table_prefix . 'search_wordlist' => array( + 'COLUMNS' => array( + 'word_id' => array('UINT', NULL, 'auto_increment'), + 'word_text' => array('VCHAR_UNI', ''), + 'word_common' => array('BOOL', 0), + 'word_count' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'word_id', + 'KEYS' => array( + 'wrd_txt' => array('UNIQUE', 'word_text'), + 'wrd_cnt' => array('INDEX', 'word_count'), + ), + ), + + $this->table_prefix . 'search_wordmatch' => array( + 'COLUMNS' => array( + 'post_id' => array('UINT', 0), + 'word_id' => array('UINT', 0), + 'title_match' => array('BOOL', 0), + ), + 'KEYS' => array( + 'unq_mtch' => array('UNIQUE', array('word_id', 'post_id', 'title_match')), + 'word_id' => array('INDEX', 'word_id'), + 'post_id' => array('INDEX', 'post_id'), + ), + ), + + $this->table_prefix . 'sessions' => array( + 'COLUMNS' => array( + 'session_id' => array('CHAR:32', ''), + 'session_user_id' => array('UINT', 0), + 'session_last_visit' => array('TIMESTAMP', 0), + 'session_start' => array('TIMESTAMP', 0), + 'session_time' => array('TIMESTAMP', 0), + 'session_ip' => array('VCHAR:40', ''), + 'session_browser' => array('VCHAR:150', ''), + 'session_forwarded_for' => array('VCHAR:255', ''), + 'session_page' => array('VCHAR_UNI', ''), + 'session_viewonline' => array('BOOL', 1), + 'session_autologin' => array('BOOL', 0), + 'session_admin' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'session_id', + 'KEYS' => array( + 'session_time' => array('INDEX', 'session_time'), + 'session_user_id' => array('INDEX', 'session_user_id'), + ), + ), + + $this->table_prefix . 'sessions_keys' => array( + 'COLUMNS' => array( + 'key_id' => array('CHAR:32', ''), + 'user_id' => array('UINT', 0), + 'last_ip' => array('VCHAR:40', ''), + 'last_login' => array('TIMESTAMP', 0), + ), + 'PRIMARY_KEY' => array('key_id', 'user_id'), + 'KEYS' => array( + 'last_login' => array('INDEX', 'last_login'), + ), + ), + + $this->table_prefix . 'sitelist' => array( + 'COLUMNS' => array( + 'site_id' => array('UINT', NULL, 'auto_increment'), + 'site_ip' => array('VCHAR:40', ''), + 'site_hostname' => array('VCHAR', ''), + 'ip_exclude' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'site_id', + ), + + $this->table_prefix . 'smilies' => array( + 'COLUMNS' => array( + 'smiley_id' => array('UINT', NULL, 'auto_increment'), +// We may want to set 'code' to VCHAR:50 or check if unicode support is possible... at the moment only ASCII characters are allowed. + 'code' => array('VCHAR_UNI:50', ''), + 'emotion' => array('VCHAR_UNI:50', ''), + 'smiley_url' => array('VCHAR:50', ''), + 'smiley_width' => array('USINT', 0), + 'smiley_height' => array('USINT', 0), + 'smiley_order' => array('UINT', 0), + 'display_on_posting'=> array('BOOL', 1), + ), + 'PRIMARY_KEY' => 'smiley_id', + 'KEYS' => array( + 'display_on_post' => array('INDEX', 'display_on_posting'), + ), + ), + + $this->table_prefix . 'styles' => array( + 'COLUMNS' => array( + 'style_id' => array('USINT', NULL, 'auto_increment'), + 'style_name' => array('VCHAR_UNI:255', ''), + 'style_copyright' => array('VCHAR_UNI', ''), + 'style_active' => array('BOOL', 1), + 'template_id' => array('USINT', 0), + 'theme_id' => array('USINT', 0), + 'imageset_id' => array('USINT', 0), + ), + 'PRIMARY_KEY' => 'style_id', + 'KEYS' => array( + 'style_name' => array('UNIQUE', 'style_name'), + 'template_id' => array('INDEX', 'template_id'), + 'theme_id' => array('INDEX', 'theme_id'), + 'imageset_id' => array('INDEX', 'imageset_id'), + ), + ), + + $this->table_prefix . 'styles_template' => array( + 'COLUMNS' => array( + 'template_id' => array('USINT', NULL, 'auto_increment'), + 'template_name' => array('VCHAR_UNI:255', ''), + 'template_copyright' => array('VCHAR_UNI', ''), + 'template_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'template_storedb' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'template_id', + 'KEYS' => array( + 'tmplte_nm' => array('UNIQUE', 'template_name'), + ), + ), + + $this->table_prefix . 'styles_template_data' => array( + 'COLUMNS' => array( + 'template_id' => array('USINT', 0), + 'template_filename' => array('VCHAR:100', ''), + 'template_included' => array('TEXT', ''), + 'template_mtime' => array('TIMESTAMP', 0), + 'template_data' => array('MTEXT_UNI', ''), + ), + 'KEYS' => array( + 'tid' => array('INDEX', 'template_id'), + 'tfn' => array('INDEX', 'template_filename'), + ), + ), + + $this->table_prefix . 'styles_theme' => array( + 'COLUMNS' => array( + 'theme_id' => array('USINT', NULL, 'auto_increment'), + 'theme_name' => array('VCHAR_UNI:255', ''), + 'theme_copyright' => array('VCHAR_UNI', ''), + 'theme_path' => array('VCHAR:100', ''), + 'theme_storedb' => array('BOOL', 0), + 'theme_mtime' => array('TIMESTAMP', 0), + 'theme_data' => array('MTEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'theme_id', + 'KEYS' => array( + 'theme_name' => array('UNIQUE', 'theme_name'), + ), + ), + + $this->table_prefix . 'styles_imageset' => array( + 'COLUMNS' => array( + 'imageset_id' => array('USINT', NULL, 'auto_increment'), + 'imageset_name' => array('VCHAR_UNI:255', ''), + 'imageset_copyright' => array('VCHAR_UNI', ''), + 'imageset_path' => array('VCHAR:100', ''), + ), + 'PRIMARY_KEY' => 'imageset_id', + 'KEYS' => array( + 'imgset_nm' => array('UNIQUE', 'imageset_name'), + ), + ), + + $this->table_prefix . 'styles_imageset_data' => array( + 'COLUMNS' => array( + 'image_id' => array('USINT', NULL, 'auto_increment'), + 'image_name' => array('VCHAR:200', ''), + 'image_filename' => array('VCHAR:200', ''), + 'image_lang' => array('VCHAR:30', ''), + 'image_height' => array('USINT', 0), + 'image_width' => array('USINT', 0), + 'imageset_id' => array('USINT', 0), + ), + 'PRIMARY_KEY' => 'image_id', + 'KEYS' => array( + 'i_d' => array('INDEX', 'imageset_id'), + ), + ), + + $this->table_prefix . 'topics' => array( + 'COLUMNS' => array( + 'topic_id' => array('UINT', NULL, 'auto_increment'), + 'forum_id' => array('UINT', 0), + 'icon_id' => array('UINT', 0), + 'topic_attachment' => array('BOOL', 0), + 'topic_approved' => array('BOOL', 1), + 'topic_reported' => array('BOOL', 0), + 'topic_title' => array('XSTEXT_UNI', '', 'true_sort'), + 'topic_poster' => array('UINT', 0), + 'topic_time' => array('TIMESTAMP', 0), + 'topic_time_limit' => array('TIMESTAMP', 0), + 'topic_views' => array('UINT', 0), + 'topic_replies' => array('UINT', 0), + 'topic_replies_real' => array('UINT', 0), + 'topic_status' => array('TINT:3', 0), + 'topic_type' => array('TINT:3', 0), + 'topic_first_post_id' => array('UINT', 0), + 'topic_first_poster_name' => array('VCHAR_UNI', ''), + 'topic_first_poster_colour' => array('VCHAR:6', ''), + 'topic_last_post_id' => array('UINT', 0), + 'topic_last_poster_id' => array('UINT', 0), + 'topic_last_poster_name' => array('VCHAR_UNI', ''), + 'topic_last_poster_colour' => array('VCHAR:6', ''), + 'topic_last_post_subject' => array('XSTEXT_UNI', ''), + 'topic_last_post_time' => array('TIMESTAMP', 0), + 'topic_last_view_time' => array('TIMESTAMP', 0), + 'topic_moved_id' => array('UINT', 0), + 'topic_bumped' => array('BOOL', 0), + 'topic_bumper' => array('UINT', 0), + 'poll_title' => array('STEXT_UNI', ''), + 'poll_start' => array('TIMESTAMP', 0), + 'poll_length' => array('TIMESTAMP', 0), + 'poll_max_options' => array('TINT:4', 1), + 'poll_last_vote' => array('TIMESTAMP', 0), + 'poll_vote_change' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => 'topic_id', + 'KEYS' => array( + 'forum_id' => array('INDEX', 'forum_id'), + 'forum_id_type' => array('INDEX', array('forum_id', 'topic_type')), + 'last_post_time' => array('INDEX', 'topic_last_post_time'), + 'topic_approved' => array('INDEX', 'topic_approved'), + 'forum_appr_last' => array('INDEX', array('forum_id', 'topic_approved', 'topic_last_post_id')), + 'fid_time_moved' => array('INDEX', array('forum_id', 'topic_last_post_time', 'topic_moved_id')), + ), + ), + + $this->table_prefix . 'topics_track' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + 'forum_id' => array('UINT', 0), + 'mark_time' => array('TIMESTAMP', 0), + ), + 'PRIMARY_KEY' => array('user_id', 'topic_id'), + 'KEYS' => array( + 'forum_id' => array('INDEX', 'forum_id'), + ), + ), + + $this->table_prefix . 'topics_posted' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + 'topic_posted' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => array('user_id', 'topic_id'), + ), + + $this->table_prefix . 'topics_watch' => array( + 'COLUMNS' => array( + 'topic_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notify_status' => array('BOOL', 0), + ), + 'KEYS' => array( + 'topic_id' => array('INDEX', 'topic_id'), + 'user_id' => array('INDEX', 'user_id'), + 'notify_stat' => array('INDEX', 'notify_status'), + ), + ), + + $this->table_prefix . 'user_group' => array( + 'COLUMNS' => array( + 'group_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'group_leader' => array('BOOL', 0), + 'user_pending' => array('BOOL', 1), + ), + 'KEYS' => array( + 'group_id' => array('INDEX', 'group_id'), + 'user_id' => array('INDEX', 'user_id'), + 'group_leader' => array('INDEX', 'group_leader'), + ), + ), + + $this->table_prefix . 'users' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', NULL, 'auto_increment'), + 'user_type' => array('TINT:2', 0), + 'group_id' => array('UINT', 3), + 'user_permissions' => array('MTEXT', ''), + 'user_perm_from' => array('UINT', 0), + 'user_ip' => array('VCHAR:40', ''), + 'user_regdate' => array('TIMESTAMP', 0), + 'username' => array('VCHAR_CI', ''), + 'username_clean' => array('VCHAR_CI', ''), + 'user_password' => array('VCHAR_UNI:40', ''), + 'user_passchg' => array('TIMESTAMP', 0), + 'user_pass_convert' => array('BOOL', 0), + 'user_email' => array('VCHAR_UNI:100', ''), + 'user_email_hash' => array('BINT', 0), + 'user_birthday' => array('VCHAR:10', ''), + 'user_lastvisit' => array('TIMESTAMP', 0), + 'user_lastmark' => array('TIMESTAMP', 0), + 'user_lastpost_time' => array('TIMESTAMP', 0), + 'user_lastpage' => array('VCHAR_UNI:200', ''), + 'user_last_confirm_key' => array('VCHAR:10', ''), + 'user_last_search' => array('TIMESTAMP', 0), + 'user_warnings' => array('TINT:4', 0), + 'user_last_warning' => array('TIMESTAMP', 0), + 'user_login_attempts' => array('TINT:4', 0), + 'user_inactive_reason' => array('TINT:2', 0), + 'user_inactive_time' => array('TIMESTAMP', 0), + 'user_posts' => array('UINT', 0), + 'user_lang' => array('VCHAR:30', ''), + 'user_timezone' => array('DECIMAL', 0), + 'user_dst' => array('BOOL', 0), + 'user_dateformat' => array('VCHAR_UNI:30', 'd M Y H:i'), + 'user_style' => array('USINT', 0), + 'user_rank' => array('UINT', 0), + 'user_colour' => array('VCHAR:6', ''), + 'user_new_privmsg' => array('INT:4', 0), + 'user_unread_privmsg' => array('INT:4', 0), + 'user_last_privmsg' => array('TIMESTAMP', 0), + 'user_message_rules' => array('BOOL', 0), + 'user_full_folder' => array('INT:11', -3), + 'user_emailtime' => array('TIMESTAMP', 0), + 'user_topic_show_days' => array('USINT', 0), + 'user_topic_sortby_type' => array('VCHAR:1', 't'), + 'user_topic_sortby_dir' => array('VCHAR:1', 'd'), + 'user_post_show_days' => array('USINT', 0), + 'user_post_sortby_type' => array('VCHAR:1', 't'), + 'user_post_sortby_dir' => array('VCHAR:1', 'a'), + 'user_notify' => array('BOOL', 0), + 'user_notify_pm' => array('BOOL', 1), + 'user_notify_type' => array('TINT:4', 0), + 'user_allow_pm' => array('BOOL', 1), + 'user_allow_viewonline' => array('BOOL', 1), + 'user_allow_viewemail' => array('BOOL', 1), + 'user_allow_massemail' => array('BOOL', 1), + 'user_options' => array('UINT:11', 895), + 'user_avatar' => array('VCHAR', ''), + 'user_avatar_type' => array('TINT:2', 0), + 'user_avatar_width' => array('USINT', 0), + 'user_avatar_height' => array('USINT', 0), + 'user_sig' => array('MTEXT_UNI', ''), + 'user_sig_bbcode_uid' => array('VCHAR:8', ''), + 'user_sig_bbcode_bitfield' => array('VCHAR:255', ''), + 'user_from' => array('VCHAR_UNI:100', ''), + 'user_icq' => array('VCHAR:15', ''), + 'user_aim' => array('VCHAR_UNI', ''), + 'user_yim' => array('VCHAR_UNI', ''), + 'user_msnm' => array('VCHAR_UNI', ''), + 'user_jabber' => array('VCHAR_UNI', ''), + 'user_website' => array('VCHAR_UNI:200', ''), + 'user_occ' => array('TEXT_UNI', ''), + 'user_interests' => array('TEXT_UNI', ''), + 'user_actkey' => array('VCHAR:32', ''), + 'user_newpasswd' => array('VCHAR_UNI:40', ''), + 'user_form_salt' => array('VCHAR_UNI:32', ''), + + ), + 'PRIMARY_KEY' => 'user_id', + 'KEYS' => array( + 'user_birthday' => array('INDEX', 'user_birthday'), + 'user_email_hash' => array('INDEX', 'user_email_hash'), + 'user_type' => array('INDEX', 'user_type'), + 'username_clean' => array('UNIQUE', 'username_clean'), + ), + ), + + $this->table_prefix . 'warnings' => array( + 'COLUMNS' => array( + 'warning_id' => array('UINT', NULL, 'auto_increment'), + 'user_id' => array('UINT', 0), + 'post_id' => array('UINT', 0), + 'log_id' => array('UINT', 0), + 'warning_time' => array('TIMESTAMP', 0), + ), + 'PRIMARY_KEY' => 'warning_id', + ), + + $this->table_prefix . 'words' => array( + 'COLUMNS' => array( + 'word_id' => array('UINT', NULL, 'auto_increment'), + 'word' => array('VCHAR_UNI', ''), + 'replacement' => array('VCHAR_UNI', ''), + ), + 'PRIMARY_KEY' => 'word_id', + ), + + $this->table_prefix . 'zebra' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'zebra_id' => array('UINT', 0), + 'friend' => array('BOOL', 0), + 'foe' => array('BOOL', 0), + ), + 'PRIMARY_KEY' => array('user_id', 'zebra_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'attachments', + $this->table_prefix . 'acl_groups', + $this->table_prefix . 'acl_options', + $this->table_prefix . 'acl_roles', + $this->table_prefix . 'acl_roles_data', + $this->table_prefix . 'acl_users', + $this->table_prefix . 'banlist', + $this->table_prefix . 'bbcodes', + $this->table_prefix . 'bookmarks', + $this->table_prefix . 'bots', + $this->table_prefix . 'config', + $this->table_prefix . 'confirm', + $this->table_prefix . 'disallow', + $this->table_prefix . 'drafts', + $this->table_prefix . 'extensions', + $this->table_prefix . 'extension_groups', + $this->table_prefix . 'forums', + $this->table_prefix . 'forums_access', + $this->table_prefix . 'forums_track', + $this->table_prefix . 'forums_watch', + $this->table_prefix . 'groups', + $this->table_prefix . 'icons', + $this->table_prefix . 'lang', + $this->table_prefix . 'log', + $this->table_prefix . 'moderator_cache', + $this->table_prefix . 'modules', + $this->table_prefix . 'poll_options', + $this->table_prefix . 'poll_votes', + $this->table_prefix . 'posts', + $this->table_prefix . 'privmsgs', + $this->table_prefix . 'privmsgs_folder', + $this->table_prefix . 'privmsgs_rules', + $this->table_prefix . 'privmsgs_to', + $this->table_prefix . 'profile_fields', + $this->table_prefix . 'profile_fields_data', + $this->table_prefix . 'profile_fields_lang', + $this->table_prefix . 'profile_lang', + $this->table_prefix . 'ranks', + $this->table_prefix . 'reports', + $this->table_prefix . 'reports_reasons', + $this->table_prefix . 'search_results', + $this->table_prefix . 'search_wordlist', + $this->table_prefix . 'search_wordmatch', + $this->table_prefix . 'sessions', + $this->table_prefix . 'sessions_keys', + $this->table_prefix . 'sitelist', + $this->table_prefix . 'smilies', + $this->table_prefix . 'styles', + $this->table_prefix . 'styles_template', + $this->table_prefix . 'styles_template_data', + $this->table_prefix . 'styles_theme', + $this->table_prefix . 'styles_imageset', + $this->table_prefix . 'styles_imageset_data', + $this->table_prefix . 'topics', + $this->table_prefix . 'topics_track', + $this->table_prefix . 'topics_posted', + $this->table_prefix . 'topics_watch', + $this->table_prefix . 'user_group', + $this->table_prefix . 'users', + $this->table_prefix . 'warnings', + $this->table_prefix . 'words', + $this->table_prefix . 'zebra', + ), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_1.php b/phpbb/db/migration/data/v30x/release_3_0_1.php new file mode 100644 index 0000000..f5c7e56 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_1.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_1_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.1')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_10.php b/phpbb/db/migration/data/v30x/release_3_0_10.php new file mode 100644 index 0000000..0d3a1ca --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_10.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_10 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.10', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_10_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_10_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_10_rc1.php new file mode 100644 index 0000000..293c46c --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_10_rc1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_10_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.10-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_9'); + } + + public function update_data() + { + return array( + array('config.add', array('email_max_chunk_size', 50)), + + array('config.update', array('version', '3.0.10-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_10_rc2.php b/phpbb/db/migration/data/v30x/release_3_0_10_rc2.php new file mode 100644 index 0000000..f288912 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_10_rc2.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_10_rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.10-RC2', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_10_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_10_rc3.php b/phpbb/db/migration/data/v30x/release_3_0_10_rc3.php new file mode 100644 index 0000000..9d6697a --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_10_rc3.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_10_rc3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.10-RC3', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_10_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.10-RC3')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_11.php b/phpbb/db/migration/data/v30x/release_3_0_11.php new file mode 100644 index 0000000..e77b54a --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_11.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_11 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.11', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.11')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_11_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_11_rc1.php new file mode 100644 index 0000000..ed2dabf --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_11_rc1.php @@ -0,0 +1,101 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_11_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.11-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_10'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'cleanup_deactivated_styles'))), + array('custom', array(array(&$this, 'delete_orphan_private_messages'))), + + array('config.update', array('version', '3.0.11-RC1')), + ); + } + + public function cleanup_deactivated_styles() + { + // Updates users having current style a deactivated one + $sql = 'SELECT style_id + FROM ' . STYLES_TABLE . ' + WHERE style_active = 0'; + $result = $this->sql_query($sql); + + $deactivated_style_ids = array(); + while ($style_id = $this->db->sql_fetchfield('style_id', false, $result)) + { + $deactivated_style_ids[] = (int) $style_id; + } + $this->db->sql_freeresult($result); + + if (!empty($deactivated_style_ids)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_style = ' . (int) $this->config['default_style'] .' + WHERE ' . $this->db->sql_in_set('user_style', $deactivated_style_ids); + $this->sql_query($sql); + } + } + + public function delete_orphan_private_messages() + { + // Delete orphan private messages + $batch_size = 500; + + $sql_array = array( + 'SELECT' => 'p.msg_id', + 'FROM' => array( + PRIVMSGS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(PRIVMSGS_TO_TABLE => 't'), + 'ON' => 'p.msg_id = t.msg_id', + ), + ), + 'WHERE' => 't.user_id IS NULL', + ); + $sql = $this->db->sql_build_query('SELECT', $sql_array); + + $result = $this->db->sql_query_limit($sql, $batch_size); + + $delete_pms = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $delete_pms[] = (int) $row['msg_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($delete_pms)) + { + $sql = 'DELETE FROM ' . PRIVMSGS_TABLE . ' + WHERE ' . $this->db->sql_in_set('msg_id', $delete_pms); + $this->sql_query($sql); + + // Return false to have the Migrator call this function again + return false; + } + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_11_rc2.php b/phpbb/db/migration/data/v30x/release_3_0_11_rc2.php new file mode 100644 index 0000000..45d8870 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_11_rc2.php @@ -0,0 +1,56 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_11_rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.11-RC2', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11_rc1'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_novalue' => array('BOOL', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_novalue', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.11-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_12.php b/phpbb/db/migration/data/v30x/release_3_0_12.php new file mode 100644 index 0000000..c489c0c --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_12.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_12 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.12', '>=') && phpbb_version_compare($this->config['version'], '3.1.0-dev', '<'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_12_rc3'); + } + + public function update_data() + { + return array( + array('if', array( + phpbb_version_compare($this->config['version'], '3.0.12', '<'), + array('config.update', array('version', '3.0.12')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_12_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_12_rc1.php new file mode 100644 index 0000000..f9f6d9f --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_12_rc1.php @@ -0,0 +1,72 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +/** @todo DROP LOGIN_ATTEMPT_TABLE.attempt_id in 3.0.12-RC1 **/ + +class release_3_0_12_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.12-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_module_auth'))), + array('custom', array(array(&$this, 'disable_bots_from_receiving_pms'))), + + array('config.update', array('version', '3.0.12-RC1')), + ); + } + + public function disable_bots_from_receiving_pms() + { + // Disable receiving pms for bots + $sql = 'SELECT user_id + FROM ' . BOTS_TABLE; + $result = $this->db->sql_query($sql); + + $bot_user_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $bot_user_ids[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($bot_user_ids)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_allow_pm = 0 + WHERE ' . $this->db->sql_in_set('user_id', $bot_user_ids); + $this->sql_query($sql); + } + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_auth = \'acl_u_sig\' + WHERE module_class = \'ucp\' + AND module_basename = \'profile\' + AND module_mode = \'signature\''; + $this->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_12_rc2.php b/phpbb/db/migration/data/v30x/release_3_0_12_rc2.php new file mode 100644 index 0000000..8fac273 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_12_rc2.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_12_rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.12-RC2', '>=') && phpbb_version_compare($this->config['version'], '3.1.0-dev', '<'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_12_rc1'); + } + + public function update_data() + { + return array( + array('if', array( + phpbb_version_compare($this->config['version'], '3.0.12-RC2', '<'), + array('config.update', array('version', '3.0.12-RC2')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_12_rc3.php b/phpbb/db/migration/data/v30x/release_3_0_12_rc3.php new file mode 100644 index 0000000..fb1b801 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_12_rc3.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_12_rc3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.12-RC3', '>=') && phpbb_version_compare($this->config['version'], '3.1.0-dev', '<'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_12_rc2'); + } + + public function update_data() + { + return array( + array('if', array( + phpbb_version_compare($this->config['version'], '3.0.12-RC3', '<'), + array('config.update', array('version', '3.0.12-RC3')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_13.php b/phpbb/db/migration/data/v30x/release_3_0_13.php new file mode 100644 index 0000000..310fcc7 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_13.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_13 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.13', '>=') && phpbb_version_compare($this->config['version'], '3.1.0-dev', '<'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_13_rc1'); + } + + public function update_data() + { + return array( + array('if', array( + phpbb_version_compare($this->config['version'], '3.0.13', '<'), + array('config.update', array('version', '3.0.13')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_13_pl1.php b/phpbb/db/migration/data/v30x/release_3_0_13_pl1.php new file mode 100644 index 0000000..b12a96a --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_13_pl1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_13_pl1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.13-PL1', '>=') && phpbb_version_compare($this->config['version'], '3.1.0-dev', '<'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_13'); + } + + public function update_data() + { + return array( + array('if', array( + phpbb_version_compare($this->config['version'], '3.0.13-PL1', '<'), + array('config.update', array('version', '3.0.13-PL1')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_13_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_13_rc1.php new file mode 100644 index 0000000..9ea68fa --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_13_rc1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_13_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.13-RC1', '>=') && phpbb_version_compare($this->config['version'], '3.1.0-dev', '<'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_12'); + } + + public function update_data() + { + return array( + array('if', array( + phpbb_version_compare($this->config['version'], '3.0.13-RC1', '<'), + array('config.update', array('version', '3.0.13-RC1')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_14.php b/phpbb/db/migration/data/v30x/release_3_0_14.php new file mode 100644 index 0000000..51475f5 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_14.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_14 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.14', '>=') && phpbb_version_compare($this->config['version'], '3.1.0-dev', '<'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_14_rc1'); + } + + public function update_data() + { + return array( + array('if', array( + phpbb_version_compare($this->config['version'], '3.0.14', '<'), + array('config.update', array('version', '3.0.14')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_14_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_14_rc1.php new file mode 100644 index 0000000..421ef06 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_14_rc1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_14_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.14-RC1', '>=') && phpbb_version_compare($this->config['version'], '3.1.0-dev', '<'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_13'); + } + + public function update_data() + { + return array( + array('if', array( + phpbb_version_compare($this->config['version'], '3.0.14-RC1', '<'), + array('config.update', array('version', '3.0.14-RC1')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_1_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_1_rc1.php new file mode 100644 index 0000000..d1ae0b9 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_1_rc1.php @@ -0,0 +1,119 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_1_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.1-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_0'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'display_subforum_list' => array('BOOL', 1), + ), + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array('UINT', 0), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'groups' => array( + 'group_legend', + ), + ), + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array('session_forum_id'), + ), + $this->table_prefix . 'groups' => array( + 'group_legend_name' => array('group_legend', 'group_name'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array( + 'display_subforum_list', + ), + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + ), + 'add_index' => array( + $this->table_prefix . 'groups' => array( + 'group_legend' => array('group_legend'), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + $this->table_prefix . 'groups' => array( + 'group_legend_name', + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'fix_unset_last_view_time'))), + array('custom', array(array(&$this, 'reset_smiley_size'))), + + array('config.update', array('version', '3.0.1-RC1')), + ); + } + + public function fix_unset_last_view_time() + { + $sql = 'UPDATE ' . $this->table_prefix . "topics + SET topic_last_view_time = topic_last_post_time + WHERE topic_last_view_time = 0"; + $this->sql_query($sql); + } + + public function reset_smiley_size() + { + // Update smiley sizes + $smileys = array('icon_e_surprised.gif', 'icon_eek.gif', 'icon_cool.gif', 'icon_lol.gif', 'icon_mad.gif', 'icon_razz.gif', 'icon_redface.gif', 'icon_cry.gif', 'icon_evil.gif', 'icon_twisted.gif', 'icon_rolleyes.gif', 'icon_exclaim.gif', 'icon_question.gif', 'icon_idea.gif', 'icon_arrow.gif', 'icon_neutral.gif', 'icon_mrgreen.gif', 'icon_e_ugeek.gif'); + + foreach ($smileys as $smiley) + { + if (file_exists($this->phpbb_root_path . 'images/smilies/' . $smiley)) + { + list($width, $height) = getimagesize($this->phpbb_root_path . 'images/smilies/' . $smiley); + + $sql = 'UPDATE ' . SMILIES_TABLE . ' + SET smiley_width = ' . $width . ', smiley_height = ' . $height . " + WHERE smiley_url = '" . $this->db->sql_escape($smiley) . "'"; + + $this->sql_query($sql); + } + } + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_2.php b/phpbb/db/migration/data/v30x/release_3_0_2.php new file mode 100644 index 0000000..c08f01d --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_2.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.2', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_2_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.2')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_2_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_2_rc1.php new file mode 100644 index 0000000..2e7f141 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_2_rc1.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_2_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.2-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_1'); + } + + public function update_data() + { + return array( + array('config.add', array('referer_validation', '1')), + array('config.add', array('check_attachment_content', '1')), + array('config.add', array('mime_triggers', 'body|head|html|img|plaintext|a href|pre|script|table|title')), + + array('config.update', array('version', '3.0.2-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_2_rc2.php b/phpbb/db/migration/data/v30x/release_3_0_2_rc2.php new file mode 100644 index 0000000..bde5feb --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_2_rc2.php @@ -0,0 +1,86 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_2_rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.2-RC2', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_2_rc1'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'drafts' => array( + 'draft_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'forums' => array( + 'forum_last_post_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'posts' => array( + 'post_subject' => array('STEXT_UNI', '', 'true_sort'), + ), + $this->table_prefix . 'privmsgs' => array( + 'message_subject' => array('STEXT_UNI', ''), + ), + $this->table_prefix . 'topics' => array( + 'topic_title' => array('STEXT_UNI', '', 'true_sort'), + 'topic_last_post_subject' => array('STEXT_UNI', ''), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id', + ), + ), + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_fid' => array('session_forum_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'sessions' => array( + 'session_forum_id' => array( + 'session_forum_id', + ), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'sessions' => array( + 'session_fid', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.2-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_3.php b/phpbb/db/migration/data/v30x/release_3_0_3.php new file mode 100644 index 0000000..c277da2 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_3.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.3', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_3_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.3')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_3_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_3_rc1.php new file mode 100644 index 0000000..530eaf4 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_3_rc1.php @@ -0,0 +1,89 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_3_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.3-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_2'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'styles_template' => array( + 'template_inherits_id' => array('UINT:4', 0), + 'template_inherit_path' => array('VCHAR', ''), + ), + $this->table_prefix . 'groups' => array( + 'group_max_recipients' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'styles_template' => array( + 'template_inherits_id', + 'template_inherit_path', + ), + $this->table_prefix . 'groups' => array( + 'group_max_recipients', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('enable_queue_trigger', '0')), + array('config.add', array('queue_trigger_posts', '3')), + array('config.add', array('pm_max_recipients', '0')), + array('custom', array(array(&$this, 'set_group_default_max_recipients'))), + array('config.add', array('dbms_version', $this->db->sql_server_info(true))), + array('permission.add', array('u_masspm_group', true, 'u_masspm')), + array('custom', array(array(&$this, 'correct_acp_email_permissions'))), + + array('config.update', array('version', '3.0.3-RC1')), + ); + } + + public function correct_acp_email_permissions() + { + $sql = 'UPDATE ' . $this->table_prefix . 'modules + SET module_auth = \'acl_a_email && cfg_email_enable\' + WHERE module_class = \'acp\' + AND module_basename = \'email\''; + $this->sql_query($sql); + } + + public function set_group_default_max_recipients() + { + // Set maximum number of recipients for the registered users, bots, guests group + $sql = 'UPDATE ' . GROUPS_TABLE . ' SET group_max_recipients = 5 + WHERE ' . $this->db->sql_in_set('group_name', array('GUESTS', 'REGISTERED', 'REGISTERED_COPPA', 'BOTS')); + $this->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_4.php b/phpbb/db/migration/data/v30x/release_3_0_4.php new file mode 100644 index 0000000..9b08da0 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_4.php @@ -0,0 +1,55 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_4 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.4', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_4_rc1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'rename_log_delete_topic'))), + + array('config.update', array('version', '3.0.4')), + ); + } + + public function rename_log_delete_topic() + { + if ($this->db->get_sql_layer() == 'oracle') + { + // log_operation is CLOB - but we can change this later + $sql = 'UPDATE ' . $this->table_prefix . "log + SET log_operation = 'LOG_DELETE_TOPIC' + WHERE log_operation LIKE 'LOG_TOPIC_DELETED'"; + $this->sql_query($sql); + } + else + { + $sql = 'UPDATE ' . $this->table_prefix . "log + SET log_operation = 'LOG_DELETE_TOPIC' + WHERE log_operation = 'LOG_TOPIC_DELETED'"; + $this->sql_query($sql); + } + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_4_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_4_rc1.php new file mode 100644 index 0000000..1034343 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_4_rc1.php @@ -0,0 +1,129 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_4_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.4-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_3'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_profile' => array('BOOL', 0), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'styles' => array( + 'style_id' => array('UINT', NULL, 'auto_increment'), + 'template_id' => array('UINT', 0), + 'theme_id' => array('UINT', 0), + 'imageset_id' => array('UINT', 0), + ), + $this->table_prefix . 'styles_imageset' => array( + 'imageset_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_imageset_data' => array( + 'image_id' => array('UINT', NULL, 'auto_increment'), + 'imageset_id' => array('UINT', 0), + ), + $this->table_prefix . 'styles_theme' => array( + 'theme_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_template' => array( + 'template_id' => array('UINT', NULL, 'auto_increment'), + ), + $this->table_prefix . 'styles_template_data' => array( + 'template_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_style' => array('UINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_style' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_profile', + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_custom_profile_fields'))), + + array('config.update', array('version', '3.0.4-RC1')), + ); + } + + public function update_custom_profile_fields() + { + // Update the Custom Profile Fields based on previous settings to the new \format + $sql = 'SELECT field_id, field_required, field_show_on_reg, field_hide + FROM ' . PROFILE_FIELDS_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_ary = array( + 'field_required' => 0, + 'field_show_on_reg' => 0, + 'field_hide' => 0, + 'field_show_profile'=> 0, + ); + + if ($row['field_required']) + { + $sql_ary['field_required'] = $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; + } + else if ($row['field_show_on_reg']) + { + $sql_ary['field_show_on_reg'] = $sql_ary['field_show_profile'] = 1; + } + else if ($row['field_hide']) + { + // Only administrators and moderators can see this CPF, if the view is enabled, they can see it, otherwise just admins in the acp_users module + $sql_ary['field_hide'] = 1; + } + else + { + // equivelant to "none", which is the "Display in user control panel" option + $sql_ary['field_show_profile'] = 1; + } + + $this->sql_query('UPDATE ' . $this->table_prefix . 'profile_fields SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE field_id = ' . $row['field_id'], $errored, $error_ary); + } + + $this->db->sql_freeresult($result); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_5.php b/phpbb/db/migration/data/v30x/release_3_0_5.php new file mode 100644 index 0000000..09c2bfe --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_5.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_5 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.5', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_5_rc1part2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.5')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php new file mode 100644 index 0000000..084d00a --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_5_rc1.php @@ -0,0 +1,135 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +use phpbb\db\migration\container_aware_migration; + +class release_3_0_5_rc1 extends container_aware_migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.5-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_4'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_style' => array('UINT', 0), + ), + ), + ); + } + + public function update_data() + { + $search_indexing_state = $this->config['search_indexing_state']; + + return array( + array('config.add', array('captcha_gd_wave', 0)), + array('config.add', array('captcha_gd_3d_noise', 1)), + array('config.add', array('captcha_gd_fonts', 1)), + array('config.add', array('confirm_refresh', 1)), + array('config.add', array('max_num_search_keywords', 10)), + array('config.remove', array('search_indexing_state')), + array('config.add', array('search_indexing_state', $search_indexing_state, true)), + array('custom', array(array(&$this, 'hash_old_passwords'))), + array('custom', array(array(&$this, 'update_ichiro_bot'))), + ); + } + + public function hash_old_passwords() + { + /* @var $passwords_manager \phpbb\passwords\manager */ + $passwords_manager = $this->container->get('passwords.manager'); + + $sql = 'SELECT user_id, user_password + FROM ' . $this->table_prefix . 'users + WHERE user_pass_convert = 1'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (strlen($row['user_password']) == 32) + { + $sql_ary = array( + 'user_password' => '$CP$' . $passwords_manager->hash($row['user_password'], 'passwords.driver.salted_md5'), + ); + + $this->sql_query('UPDATE ' . $this->table_prefix . 'users SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' WHERE user_id = ' . $row['user_id']); + } + } + $this->db->sql_freeresult($result); + } + + public function update_ichiro_bot() + { + // Adjust bot entry + $sql = 'UPDATE ' . $this->table_prefix . "bots + SET bot_agent = 'ichiro/' + WHERE bot_agent = 'ichiro/2'"; + $this->sql_query($sql); + } + + public function remove_duplicate_auth_options() + { + // Before we are able to add a unique key to auth_option, we need to remove duplicate entries + $sql = 'SELECT auth_option + FROM ' . $this->table_prefix . 'acl_options + GROUP BY auth_option + HAVING COUNT(*) >= 2'; + $result = $this->db->sql_query($sql); + + $auth_options = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $auth_options[] = $row['auth_option']; + } + $this->db->sql_freeresult($result); + + // Remove specific auth options + if (!empty($auth_options)) + { + foreach ($auth_options as $option) + { + // Select auth_option_ids... the largest id will be preserved + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($option) . "' + ORDER BY auth_option_id DESC"; + // sql_query_limit not possible here, due to bug in postgresql layer + $result = $this->db->sql_query($sql); + + // Skip first row, this is our original auth option we want to preserve + $this->db->sql_fetchrow($result); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Ok, remove this auth option... + $this->sql_query('DELETE FROM ' . ACL_OPTIONS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_GROUPS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + $this->sql_query('DELETE FROM ' . ACL_USERS_TABLE . ' WHERE auth_option_id = ' . $row['auth_option_id']); + } + $this->db->sql_freeresult($result); + } + } + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_5_rc1part2.php b/phpbb/db/migration/data/v30x/release_3_0_5_rc1part2.php new file mode 100644 index 0000000..a9041ef --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_5_rc1part2.php @@ -0,0 +1,48 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_5_rc1part2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.5-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_5_rc1'); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'acl_options' => array('auth_option'), + ), + 'add_unique_index' => array( + $this->table_prefix . 'acl_options' => array( + 'auth_option' => array('auth_option'), + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.5-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_6.php b/phpbb/db/migration/data/v30x/release_3_0_6.php new file mode 100644 index 0000000..74c338a --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_6.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_6 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.6', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_6_rc4'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_6_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_6_rc1.php new file mode 100644 index 0000000..40bb58c --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_6_rc1.php @@ -0,0 +1,364 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_6_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.6-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_5'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'confirm' => array( + 'attempts' => array('UINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_new' => array('BOOL', 1), + 'user_reminded' => array('TINT:4', 0), + 'user_reminded_time' => array('TIMESTAMP', 0), + ), + $this->table_prefix . 'groups' => array( + 'group_skip_auth' => array('BOOL', 0, 'after' => 'group_founder_manage'), + ), + $this->table_prefix . 'privmsgs' => array( + 'message_reported' => array('BOOL', 0), + ), + $this->table_prefix . 'reports' => array( + 'pm_id' => array('UINT', 0), + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_vt' => array('BOOL', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_options' => array('UINT:20', 0), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_options' => array('UINT:11', 230271), + ), + ), + 'add_index' => array( + $this->table_prefix . 'reports' => array( + 'post_id' => array('post_id'), + 'pm_id' => array('pm_id'), + ), + $this->table_prefix . 'posts' => array( + 'post_username' => array('post_username:255'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'confirm' => array( + 'attempts', + ), + $this->table_prefix . 'users' => array( + 'user_new', + 'user_reminded', + 'user_reminded_time', + ), + $this->table_prefix . 'groups' => array( + 'group_skip_auth', + ), + $this->table_prefix . 'privmsgs' => array( + 'message_reported', + ), + $this->table_prefix . 'reports' => array( + 'pm_id', + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_vt', + ), + $this->table_prefix . 'forums' => array( + 'forum_options', + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'reports' => array( + 'post_id', + 'pm_id', + ), + $this->table_prefix . 'posts' => array( + 'post_username', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('captcha_plugin', 'phpbb_captcha_nogd')), + array('if', array( + ($this->config['captcha_gd']), + array('config.update', array('captcha_plugin', 'phpbb_captcha_gd')), + )), + + array('config.add', array('feed_enable', 0)), + array('config.add', array('feed_limit', 10)), + array('config.add', array('feed_overall_forums', 1)), + array('config.add', array('feed_overall_forums_limit', 15)), + array('config.add', array('feed_overall_topics', 0)), + array('config.add', array('feed_overall_topics_limit', 15)), + array('config.add', array('feed_forum', 1)), + array('config.add', array('feed_topic', 1)), + array('config.add', array('feed_item_statistics', 1)), + + array('config.add', array('smilies_per_page', 50)), + array('config.add', array('allow_pm_report', 1)), + array('config.add', array('min_post_chars', 1)), + array('config.add', array('allow_quick_reply', 1)), + array('config.add', array('new_member_post_limit', 0)), + array('config.add', array('new_member_group_default', 0)), + array('config.add', array('delete_time', $this->config['edit_time'])), + + array('config.add', array('allow_avatar', 0)), + array('if', array( + ($this->config['allow_avatar_upload'] || $this->config['allow_avatar_local'] || $this->config['allow_avatar_remote']), + array('config.update', array('allow_avatar', 1)), + )), + array('config.add', array('allow_avatar_remote_upload', 0)), + array('if', array( + ($this->config['allow_avatar_remote'] && $this->config['allow_avatar_upload']), + array('config.update', array('allow_avatar_remote_upload', 1)), + )), + + array('module.add', array( + 'acp', + 'ACP_BOARD_CONFIGURATION', + array( + 'module_basename' => 'acp_board', + 'module_langname' => 'ACP_FEED_SETTINGS', + 'module_mode' => 'feed', + 'module_auth' => 'acl_a_board', + 'after' => array('signature', 'ACP_SIGNATURE_SETTINGS'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_CAT_USERS', + array( + 'module_basename' => 'acp_users', + 'module_langname' => 'ACP_USER_WARNINGS', + 'module_mode' => 'warnings', + 'module_auth' => 'acl_a_user', + 'module_display' => false, + 'after' => array('feedback', 'ACP_USER_FEEDBACK'), + ), + )), + array('module.add', array( + 'acp', + 'ACP_SERVER_CONFIGURATION', + array( + 'module_basename' => 'acp_send_statistics', + 'module_langname' => 'ACP_SEND_STATISTICS', + 'module_mode' => 'send_statistics', + 'module_auth' => 'acl_a_server', + ), + )), + array('module.add', array( + 'acp', + 'ACP_FORUM_BASED_PERMISSIONS', + array( + 'module_basename' => 'acp_permissions', + 'module_langname' => 'ACP_FORUM_PERMISSIONS_COPY', + 'module_mode' => 'setting_forum_copy', + 'module_auth' => 'acl_a_fauth && acl_a_authusers && acl_a_authgroups && acl_a_mauth', + 'after' => array('setting_forum_local', 'ACP_FORUM_PERMISSIONS'), + ), + )), + array('module.add', array( + 'mcp', + 'MCP_REPORTS', + array( + 'module_basename' => 'mcp_pm_reports', + 'module_langname' => 'MCP_PM_REPORTS_OPEN', + 'module_mode' => 'pm_reports', + 'module_auth' => 'acl_m_pm_report', + ), + )), + array('module.add', array( + 'mcp', + 'MCP_REPORTS', + array( + 'module_basename' => 'mcp_pm_reports', + 'module_langname' => 'MCP_PM_REPORTS_CLOSED', + 'module_mode' => 'pm_reports_closed', + 'module_auth' => 'acl_m_pm_report', + ), + )), + array('module.add', array( + 'mcp', + 'MCP_REPORTS', + array( + 'module_basename' => 'mcp_pm_reports', + 'module_langname' => 'MCP_PM_REPORT_DETAILS', + 'module_mode' => 'pm_report_details', + 'module_auth' => 'acl_m_pm_report', + ), + )), + array('custom', array(array(&$this, 'add_newly_registered_group'))), + array('custom', array(array(&$this, 'set_user_options_default'))), + + array('config.update', array('version', '3.0.6-RC1')), + ); + } + + public function set_user_options_default() + { + // 229376 is the added value to enable all three signature options + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_options = user_options + 229376'; + $this->sql_query($sql); + } + + public function add_newly_registered_group() + { + // Add newly_registered group... but check if it already exists (we always supported running the updater on any schema) + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'NEWLY_REGISTERED'"; + $result = $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + $this->db->sql_freeresult($result); + + if (!$group_id) + { + $sql = 'INSERT INTO ' . GROUPS_TABLE . " (group_name, group_type, group_founder_manage, group_colour, group_legend, group_avatar, group_desc, group_desc_uid, group_max_recipients) VALUES ('NEWLY_REGISTERED', 3, 0, '', 0, '', '', '', 5)"; + $this->sql_query($sql); + + $group_id = $this->db->sql_nextid(); + } + + // Insert new user role... at the end of the chain + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = 'ROLE_USER_NEW_MEMBER' + AND role_type = 'u_'"; + $result = $this->db->sql_query($sql); + $u_role = (int) $this->db->sql_fetchfield('role_id'); + $this->db->sql_freeresult($result); + + if (!$u_role) + { + $sql = 'SELECT MAX(role_order) as max_order_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = 'u_'"; + $result = $this->db->sql_query($sql); + $next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); + $this->db->sql_freeresult($result); + + $next_order_id++; + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_USER_NEW_MEMBER', 'ROLE_DESCRIPTION_USER_NEW_MEMBER', 'u_', $next_order_id)"; + $this->sql_query($sql); + $u_role = $this->db->sql_nextid(); + + // Now add the correct data to the roles... + // The standard role says that new users are not able to send a PM, Mass PM, are not able to PM groups + $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $u_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'u_%' AND auth_option IN ('u_sendpm', 'u_masspm', 'u_masspm_group')"; + $this->sql_query($sql); + + // Add user role to group + $sql = 'INSERT INTO ' . ACL_GROUPS_TABLE . " (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES ($group_id, 0, 0, $u_role, 0)"; + $this->sql_query($sql); + } + + // Insert new forum role + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = 'ROLE_FORUM_NEW_MEMBER' + AND role_type = 'f_'"; + $result = $this->db->sql_query($sql); + $f_role = (int) $this->db->sql_fetchfield('role_id'); + $this->db->sql_freeresult($result); + + if (!$f_role) + { + $sql = 'SELECT MAX(role_order) as max_order_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = 'f_'"; + $result = $this->db->sql_query($sql); + $next_order_id = (int) $this->db->sql_fetchfield('max_order_id'); + $this->db->sql_freeresult($result); + + $next_order_id++; + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . " (role_name, role_description, role_type, role_order) VALUES ('ROLE_FORUM_NEW_MEMBER', 'ROLE_DESCRIPTION_FORUM_NEW_MEMBER', 'f_', $next_order_id)"; + $this->sql_query($sql); + $f_role = $this->db->sql_nextid(); + + $sql = 'INSERT INTO ' . ACL_ROLES_DATA_TABLE . " (role_id, auth_option_id, auth_setting) SELECT $f_role, auth_option_id, 0 FROM " . ACL_OPTIONS_TABLE . " WHERE auth_option LIKE 'f_%' AND auth_option IN ('f_noapprove')"; + $this->sql_query($sql); + } + + // Set every members user_new column to 0 (old users) only if there is no one yet (this makes sure we do not execute this more than once) + $sql = 'SELECT 1 + FROM ' . USERS_TABLE . ' + WHERE user_new = 0'; + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_new = 0'; + $this->sql_query($sql); + } + + // To mimick the old "feature" we will assign the forum role to every forum, regardless of the setting (this makes sure there are no "this does not work!!!! YUO!!!" posts... + // Check if the role is already assigned... + $sql = 'SELECT forum_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id = ' . $f_role; + $result = $this->db->sql_query($sql); + $is_options = (int) $this->db->sql_fetchfield('forum_id'); + $this->db->sql_freeresult($result); + + // Not assigned at all... :/ + if (!$is_options) + { + // Get postable forums + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type != ' . FORUM_LINK; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->sql_query('INSERT INTO ' . ACL_GROUPS_TABLE . ' (group_id, forum_id, auth_option_id, auth_role_id, auth_setting) VALUES (' . $group_id . ', ' . (int) $row['forum_id'] . ', 0, ' . $f_role . ', 0)'); + } + $this->db->sql_freeresult($result); + } + + // Clear permissions... + include_once($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); + $auth_admin = new \auth_admin(); + $auth_admin->acl_clear_prefetch(); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_6_rc2.php b/phpbb/db/migration/data/v30x/release_3_0_6_rc2.php new file mode 100644 index 0000000..c52b71d --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_6_rc2.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_6_rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.6-RC2', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_6_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_6_rc3.php b/phpbb/db/migration/data/v30x/release_3_0_6_rc3.php new file mode 100644 index 0000000..2db3341 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_6_rc3.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_6_rc3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.6-RC3', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_6_rc2'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_cp_fields'))), + + array('config.update', array('version', '3.0.6-RC3')), + ); + } + + public function update_cp_fields() + { + // Update the Custom Profile Fields based on previous settings to the new \format + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . ' + SET field_show_on_vt = 1 + WHERE field_hide = 0 + AND (field_required = 1 OR field_show_on_reg = 1 OR field_show_profile = 1)'; + $this->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_6_rc4.php b/phpbb/db/migration/data/v30x/release_3_0_6_rc4.php new file mode 100644 index 0000000..5734db2 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_6_rc4.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_6_rc4 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.6-RC4', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_6_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.6-RC4')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_7.php b/phpbb/db/migration/data/v30x/release_3_0_7.php new file mode 100644 index 0000000..d1d6028 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_7.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_7 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.7', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_7_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.7')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_7_pl1.php b/phpbb/db/migration/data/v30x/release_3_0_7_pl1.php new file mode 100644 index 0000000..784e810 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_7_pl1.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_7_pl1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.7-pl1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_7'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.7-pl1')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_7_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_7_rc1.php new file mode 100644 index 0000000..1843c3f --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_7_rc1.php @@ -0,0 +1,82 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_7_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.7-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_6'); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'log' => array( + 'log_time', + ), + ), + 'add_index' => array( + $this->table_prefix . 'topics_track' => array( + 'topic_id' => array('topic_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'log' => array( + 'log_time' => array('log_time'), + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'topics_track' => array( + 'topic_id', + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('feed_overall', 1)), + array('config.add', array('feed_http_auth', 0)), + array('config.add', array('feed_limit_post', $this->config['feed_limit'])), + array('config.add', array('feed_limit_topic', $this->config['feed_overall_topics_limit'])), + array('config.add', array('feed_topics_new', $this->config['feed_overall_topics'])), + array('config.add', array('feed_topics_active', $this->config['feed_overall_topics'])), + array('custom', array(array(&$this, 'delete_text_templates'))), + + array('config.update', array('version', '3.0.7-RC1')), + ); + } + + public function delete_text_templates() + { + // Delete all text-templates from the template_data + $sql = 'DELETE FROM ' . STYLES_TEMPLATE_DATA_TABLE . ' + WHERE template_filename ' . $this->db->sql_like_expression($this->db->get_any_char() . '.txt'); + $this->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_7_rc2.php b/phpbb/db/migration/data/v30x/release_3_0_7_rc2.php new file mode 100644 index 0000000..e497a38 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_7_rc2.php @@ -0,0 +1,79 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_7_rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.7-RC2', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_7_rc1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_email_hash'))), + + array('config.update', array('version', '3.0.7-RC2')), + ); + } + + public function update_email_hash($start = 0) + { + $limit = 1000; + + $sql = 'SELECT user_id, user_email, user_email_hash + FROM ' . USERS_TABLE . ' + WHERE user_type <> ' . USER_IGNORE . " + AND user_email <> ''"; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + $i = 0; + while ($row = $this->db->sql_fetchrow($result)) + { + $i++; + + // Snapshot of the phpbb_email_hash() function + // We cannot call it directly because the auto updater updates the DB first. :/ + $user_email_hash = sprintf('%u', crc32(strtolower($row['user_email']))) . strlen($row['user_email']); + + if ($user_email_hash != $row['user_email_hash']) + { + $sql_ary = array( + 'user_email_hash' => $user_email_hash, + ); + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . (int) $row['user_id']; + $this->sql_query($sql); + } + } + $this->db->sql_freeresult($result); + + if ($i < $limit) + { + // Completed + return; + } + + // Return the next start, will be sent to $start when this function is called again + return $start + $limit; + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_8.php b/phpbb/db/migration/data/v30x/release_3_0_8.php new file mode 100644 index 0000000..04b5bd4 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_8.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_8 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.8', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_8_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.8')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_8_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_8_rc1.php new file mode 100644 index 0000000..836cb45 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_8_rc1.php @@ -0,0 +1,170 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_8_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.8-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_7_pl1'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_file_extension_group_names'))), + array('custom', array(array(&$this, 'update_module_auth'))), + array('custom', array(array(&$this, 'delete_orphan_shadow_topics'))), + array('module.add', array( + 'acp', + 'ACP_MESSAGES', + array( + 'module_basename' => 'acp_board', + 'module_langname' => 'ACP_POST_SETTINGS', + 'module_mode' => 'post', + 'module_auth' => 'acl_a_board', + 'after' => array('message', 'ACP_MESSAGE_SETTINGS'), + ), + )), + array('config.add', array('load_unreads_search', 1)), + array('config.update_if_equals', array(600, 'queue_interval', 60)), + array('config.update_if_equals', array(50, 'email_package_size', 20)), + + array('config.update', array('version', '3.0.8-RC1')), + ); + } + + public function update_file_extension_group_names() + { + // Update file extension group names to use language strings. + $sql = 'SELECT lang_dir + FROM ' . LANG_TABLE; + $result = $this->db->sql_query($sql); + + $extension_groups_updated = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + if (empty($row['lang_dir'])) + { + continue; + } + + $lang_dir = basename($row['lang_dir']); + + // The language strings we need are either in language/.../acp/attachments.php + // in the update package if we're updating to 3.0.8-RC1 or later, + // or they are in language/.../install.php when we're updating from 3.0.7-PL1 or earlier. + // On an already updated board, they can also already be in language/.../acp/attachments.php + // in the board root. + $lang_files = array( + "{$this->phpbb_root_path}install/update/new/language/$lang_dir/acp/attachments.{$this->php_ext}", + "{$this->phpbb_root_path}language/$lang_dir/install.{$this->php_ext}", + "{$this->phpbb_root_path}language/$lang_dir/acp/attachments.{$this->php_ext}", + ); + + foreach ($lang_files as $lang_file) + { + if (!file_exists($lang_file)) + { + continue; + } + + $lang = array(); + include($lang_file); + + foreach($lang as $lang_key => $lang_val) + { + if (isset($extension_groups_updated[$lang_key]) || strpos($lang_key, 'EXT_GROUP_') !== 0) + { + continue; + } + + $sql_ary = array( + 'group_name' => substr($lang_key, 10), // Strip off 'EXT_GROUP_' + ); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " + WHERE group_name = '" . $this->db->sql_escape($lang_val) . "'"; + $this->sql_query($sql); + + $extension_groups_updated[$lang_key] = true; + } + } + } + $this->db->sql_freeresult($result); + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET module_auth = \'cfg_allow_avatar && (cfg_allow_avatar_local || cfg_allow_avatar_remote || cfg_allow_avatar_upload || cfg_allow_avatar_remote_upload)\' + WHERE module_class = \'ucp\' + AND module_basename = \'profile\' + AND module_mode = \'avatar\''; + $this->sql_query($sql); + } + + public function delete_orphan_shadow_topics() + { + // Delete shadow topics pointing to not existing topics + $batch_size = 500; + + // Set of affected forums we have to resync + $sync_forum_ids = array(); + + $sql_array = array( + 'SELECT' => 't1.topic_id, t1.forum_id', + 'FROM' => array( + TOPICS_TABLE => 't1', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(TOPICS_TABLE => 't2'), + 'ON' => 't1.topic_moved_id = t2.topic_id', + ), + ), + 'WHERE' => 't1.topic_moved_id <> 0 + AND t2.topic_id IS NULL', + ); + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query_limit($sql, $batch_size); + + $topic_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $topic_ids[] = (int) $row['topic_id']; + + $sync_forum_ids[(int) $row['forum_id']] = (int) $row['forum_id']; + } + $this->db->sql_freeresult($result); + + if (!empty($topic_ids)) + { + $sql = 'DELETE FROM ' . TOPICS_TABLE . ' + WHERE ' . $this->db->sql_in_set('topic_id', $topic_ids); + $this->db->sql_query($sql); + + // Sync the forums we have deleted shadow topics from. + sync('forum', 'forum_id', $sync_forum_ids, true, true); + + return false; + } + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_9.php b/phpbb/db/migration/data/v30x/release_3_0_9.php new file mode 100644 index 0000000..e69134c --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_9.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_9 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.9', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_9_rc4'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php b/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php new file mode 100644 index 0000000..5f928df --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_9_rc1.php @@ -0,0 +1,130 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_9_rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.9-RC1', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_8'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'login_attempts' => array( + 'COLUMNS' => array( + // this column was removed from the database updater + // after 3.0.9-RC3 was released. It might still exist + // in 3.0.9-RCX installations and has to be dropped as + // soon as the \phpbb\db\tools\tools class is capable of properly + // removing a primary key. + // 'attempt_id' => array('UINT', NULL, 'auto_increment'), + 'attempt_ip' => array('VCHAR:40', ''), + 'attempt_browser' => array('VCHAR:150', ''), + 'attempt_forwarded_for' => array('VCHAR:255', ''), + 'attempt_time' => array('TIMESTAMP', 0), + 'user_id' => array('UINT', 0), + 'username' => array('VCHAR_UNI:255', 0), + 'username_clean' => array('VCHAR_CI', 0), + ), + //'PRIMARY_KEY' => 'attempt_id', + 'KEYS' => array( + 'att_ip' => array('INDEX', array('attempt_ip', 'attempt_time')), + 'att_for' => array('INDEX', array('attempt_forwarded_for', 'attempt_time')), + 'att_time' => array('INDEX', array('attempt_time')), + 'user_id' => array('INDEX', 'user_id'), + ), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'bbcodes' => array( + 'bbcode_id' => array('USINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'login_attempts', + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('ip_login_limit_max', 50)), + array('config.add', array('ip_login_limit_time', 21600)), + array('config.add', array('ip_login_limit_use_forwarded', 0)), + array('custom', array(array(&$this, 'update_file_extension_group_names'))), + array('custom', array(array(&$this, 'fix_firebird_qa_captcha'))), + + array('config.update', array('version', '3.0.9-RC1')), + ); + } + + public function update_file_extension_group_names() + { + // Update file extension group names to use language strings, again. + $sql = 'SELECT group_id, group_name + FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE group_name ' . $this->db->sql_like_expression('EXT_GROUP_' . $this->db->get_any_char()); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_ary = array( + 'group_name' => substr($row['group_name'], 10), // Strip off 'EXT_GROUP_' + ); + + $sql = 'UPDATE ' . EXTENSION_GROUPS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE group_id = ' . $row['group_id']; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } + + public function fix_firebird_qa_captcha() + { + // Recover from potentially broken Q&A CAPTCHA table on firebird + // Q&A CAPTCHA was uninstallable, so it's safe to remove these + // without data loss + if ($this->db_tools->sql_layer == 'firebird') + { + $tables = array( + $this->table_prefix . 'captcha_questions', + $this->table_prefix . 'captcha_answers', + $this->table_prefix . 'qa_confirm', + ); + foreach ($tables as $table) + { + if ($this->db_tools->sql_table_exists($table)) + { + $this->db_tools->sql_table_drop($table); + } + } + } + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_9_rc2.php b/phpbb/db/migration/data/v30x/release_3_0_9_rc2.php new file mode 100644 index 0000000..46fd51e --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_9_rc2.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_9_rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.9-RC2', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_9_rc1'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_9_rc3.php b/phpbb/db/migration/data/v30x/release_3_0_9_rc3.php new file mode 100644 index 0000000..1696060 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_9_rc3.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_9_rc3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.9-RC3', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_9_rc2'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-RC3')), + ); + } +} diff --git a/phpbb/db/migration/data/v30x/release_3_0_9_rc4.php b/phpbb/db/migration/data/v30x/release_3_0_9_rc4.php new file mode 100644 index 0000000..fdc92b5 --- /dev/null +++ b/phpbb/db/migration/data/v30x/release_3_0_9_rc4.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v30x; + +class release_3_0_9_rc4 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.0.9-RC4', '>='); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_9_rc3'); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.0.9-RC4')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/.htaccess b/phpbb/db/migration/data/v310/.htaccess new file mode 100644 index 0000000..44242b5 --- /dev/null +++ b/phpbb/db/migration/data/v310/.htaccess @@ -0,0 +1,33 @@ +# With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from +# module mod_authz_host to a new module called mod_access_compat (which may be +# disabled) and a new "Require" syntax has been introduced to mod_authz_host. +# We could just conditionally provide both versions, but unfortunately Apache +# does not explicitly tell us its version if the module mod_version is not +# available. In this case, we check for the availability of module +# mod_authz_core (which should be on 2.4 or higher only) as a best guess. + + + + Order Allow,Deny + Deny from All + + + = 2.4> + + Require all denied + + + + + + + Order Allow,Deny + Deny from All + + + + + Require all denied + + + diff --git a/phpbb/db/migration/data/v310/acp_prune_users_module.php b/phpbb/db/migration/data/v310/acp_prune_users_module.php new file mode 100644 index 0000000..725c57c --- /dev/null +++ b/phpbb/db/migration/data/v310/acp_prune_users_module.php @@ -0,0 +1,76 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class acp_prune_users_module extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + $sql = 'SELECT module_id + FROM ' . MODULES_TABLE . " + WHERE module_class = 'acp' + AND module_langname = 'ACP_CAT_USERS'"; + $result = $this->db->sql_query($sql); + $acp_cat_users_id = (int) $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT parent_id + FROM ' . MODULES_TABLE . " + WHERE module_class = 'acp' + AND module_basename = 'acp_prune' + AND module_mode = 'users'"; + $result = $this->db->sql_query($sql); + $acp_prune_users_parent = (int) $this->db->sql_fetchfield('parent_id'); + $this->db->sql_freeresult($result); + + // Skip migration if "Users" category has been deleted + // or the module has already been moved to that category + return !$acp_cat_users_id || $acp_cat_users_id === $acp_prune_users_parent; + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\beta1'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'move_prune_users_module'))), + ); + } + + public function move_prune_users_module() + { + $sql = 'SELECT module_id + FROM ' . MODULES_TABLE . " + WHERE module_class = 'acp' + AND module_basename = 'acp_prune' + AND module_mode = 'users'"; + $result = $this->db->sql_query($sql); + $acp_prune_users_id = (int) $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT module_id + FROM ' . MODULES_TABLE . " + WHERE module_class = 'acp' + AND module_langname = 'ACP_CAT_USERS'"; + $result = $this->db->sql_query($sql); + $acp_cat_users_id = (int) $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + $module_manager = $this->container->get('module.manager'); + $module_manager->move_module($acp_prune_users_id, $acp_cat_users_id, 'acp'); + } +} diff --git a/phpbb/db/migration/data/v310/acp_style_components_module.php b/phpbb/db/migration/data/v310/acp_style_components_module.php new file mode 100644 index 0000000..4bd29f8 --- /dev/null +++ b/phpbb/db/migration/data/v310/acp_style_components_module.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class acp_style_components_module extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + $sql = 'SELECT module_id + FROM ' . MODULES_TABLE . " + WHERE module_class = 'acp' + AND module_langname = 'ACP_STYLE_COMPONENTS'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + return $module_id == false; + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_data() + { + return array( + array('module.remove', array( + 'acp', + false, + 'ACP_STYLE_COMPONENTS', + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/allow_cdn.php b/phpbb/db/migration/data/v310/allow_cdn.php new file mode 100644 index 0000000..286d20e --- /dev/null +++ b/phpbb/db/migration/data/v310/allow_cdn.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class allow_cdn extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return isset($this->config['allow_cdn']); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\jquery_update', + ); + } + + public function update_data() + { + return array( + array('config.add', array('allow_cdn', (int) $this->config['load_jquery_cdn'])), + array('config.remove', array('load_jquery_cdn')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/alpha1.php b/phpbb/db/migration/data/v310/alpha1.php new file mode 100644 index 0000000..4a48d28 --- /dev/null +++ b/phpbb/db/migration/data/v310/alpha1.php @@ -0,0 +1,53 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class alpha1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-a1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v30x\local_url_bbcode', + '\phpbb\db\migration\data\v30x\release_3_0_12', + '\phpbb\db\migration\data\v310\acp_style_components_module', + '\phpbb\db\migration\data\v310\allow_cdn', + '\phpbb\db\migration\data\v310\auth_provider_oauth', + '\phpbb\db\migration\data\v310\avatars', + '\phpbb\db\migration\data\v310\boardindex', + '\phpbb\db\migration\data\v310\config_db_text', + '\phpbb\db\migration\data\v310\forgot_password', + '\phpbb\db\migration\data\v310\mod_rewrite', + '\phpbb\db\migration\data\v310\mysql_fulltext_drop', + '\phpbb\db\migration\data\v310\namespaces', + '\phpbb\db\migration\data\v310\notifications_cron', + '\phpbb\db\migration\data\v310\notification_options_reconvert', + '\phpbb\db\migration\data\v310\plupload', + '\phpbb\db\migration\data\v310\signature_module_auth', + '\phpbb\db\migration\data\v310\softdelete_mcp_modules', + '\phpbb\db\migration\data\v310\teampage', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-a1')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/alpha2.php b/phpbb/db/migration/data/v310/alpha2.php new file mode 100644 index 0000000..bfbcc4f --- /dev/null +++ b/phpbb/db/migration/data/v310/alpha2.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class alpha2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-a2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\alpha1', + '\phpbb\db\migration\data\v310\notifications_cron_p2', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-a2')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/alpha3.php b/phpbb/db/migration/data/v310/alpha3.php new file mode 100644 index 0000000..bb0f904 --- /dev/null +++ b/phpbb/db/migration/data/v310/alpha3.php @@ -0,0 +1,39 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class alpha3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-a3', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\alpha2', + '\phpbb\db\migration\data\v310\avatar_types', + '\phpbb\db\migration\data\v310\passwords', + '\phpbb\db\migration\data\v310\profilefield_types', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-a3')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/auth_provider_oauth.php b/phpbb/db/migration/data/v310/auth_provider_oauth.php new file mode 100644 index 0000000..1e2024a --- /dev/null +++ b/phpbb/db/migration/data/v310/auth_provider_oauth.php @@ -0,0 +1,84 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class auth_provider_oauth extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'oauth_tokens'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_0'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'oauth_tokens' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), // phpbb_users.user_id + 'session_id' => array('CHAR:32', ''), // phpbb_sessions.session_id used only when user_id not set + 'provider' => array('VCHAR', ''), // Name of the OAuth provider + 'oauth_token' => array('MTEXT', ''), // Serialized token + ), + 'KEYS' => array( + 'user_id' => array('INDEX', 'user_id'), + 'provider' => array('INDEX', 'provider'), + ), + ), + $this->table_prefix . 'oauth_accounts' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'provider' => array('VCHAR', ''), + 'oauth_provider_id' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => array( + 'user_id', + 'provider', + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'oauth_tokens', + $this->table_prefix . 'oauth_accounts', + ), + ); + } + + public function update_data() + { + return array( + array('module.add', array( + 'ucp', + 'UCP_PROFILE', + array( + 'module_basename' => 'ucp_auth_link', + 'module_langname' => 'UCP_AUTH_LINK_MANAGE', + 'module_mode' => 'auth_link', + 'module_auth' => 'authmethod_oauth', + ), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/auth_provider_oauth2.php b/phpbb/db/migration/data/v310/auth_provider_oauth2.php new file mode 100644 index 0000000..e9e726a --- /dev/null +++ b/phpbb/db/migration/data/v310/auth_provider_oauth2.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class auth_provider_oauth2 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\auth_provider_oauth', + ); + } + + public function update_data() + { + return array( + array('custom', array( + array($this, 'update_auth_link_module_auth'), + )), + ); + } + + public function update_auth_link_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'authmethod_oauth' + WHERE module_class = 'ucp' + AND module_basename = 'ucp_auth_link' + AND module_mode = 'auth_link' + AND module_auth = ''"; + $this->db->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v310/avatar_types.php b/phpbb/db/migration/data/v310/avatar_types.php new file mode 100644 index 0000000..117e932 --- /dev/null +++ b/phpbb/db/migration/data/v310/avatar_types.php @@ -0,0 +1,64 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class avatar_types extends \phpbb\db\migration\migration +{ + /** + * @var avatar type map + */ + protected $avatar_type_map = array( + AVATAR_UPLOAD => 'avatar.driver.upload', + AVATAR_REMOTE => 'avatar.driver.remote', + AVATAR_GALLERY => 'avatar.driver.local', + ); + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + '\phpbb\db\migration\data\v310\avatars', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_user_avatar_type'))), + array('custom', array(array($this, 'update_group_avatar_type'))), + ); + } + + public function update_user_avatar_type() + { + foreach ($this->avatar_type_map as $old => $new) + { + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_avatar_type = '$new' + WHERE user_avatar_type = '$old'"; + $this->db->sql_query($sql); + } + } + + public function update_group_avatar_type() + { + foreach ($this->avatar_type_map as $old => $new) + { + $sql = 'UPDATE ' . $this->table_prefix . "groups + SET group_avatar_type = '$new' + WHERE group_avatar_type = '$old'"; + $this->db->sql_query($sql); + } + } +} diff --git a/phpbb/db/migration/data/v310/avatars.php b/phpbb/db/migration/data/v310/avatars.php new file mode 100644 index 0000000..9b03a8f --- /dev/null +++ b/phpbb/db/migration/data/v310/avatars.php @@ -0,0 +1,95 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class avatars extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + // Get current avatar type of guest user + $sql = 'SELECT user_avatar_type + FROM ' . $this->table_prefix . 'users + WHERE user_id = ' . ANONYMOUS; + $result = $this->db->sql_query($sql); + $backup_type = $this->db->sql_fetchfield('user_avatar_type'); + $this->db->sql_freeresult($result); + + // Try to set avatar type to string + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_avatar_type = 'avatar.driver.upload' + WHERE user_id = " . ANONYMOUS; + $this->db->sql_return_on_error(true); + $effectively_installed = $this->db->sql_query($sql); + $this->db->sql_return_on_error(); + + // Restore avatar type of guest user to previous state + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_avatar_type = '{$backup_type}' + WHERE user_id = " . ANONYMOUS; + $this->db->sql_query($sql); + + return $effectively_installed !== false; + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_avatar_type' => array('VCHAR:255', ''), + ), + $this->table_prefix . 'groups' => array( + 'group_avatar_type' => array('VCHAR:255', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_avatar_type' => array('TINT:2', ''), + ), + $this->table_prefix . 'groups' => array( + 'group_avatar_type' => array('TINT:2', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('config.add', array('allow_avatar_gravatar', 0)), + array('custom', array(array($this, 'update_module_auth'))), + ); + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . $this->table_prefix . "modules + SET module_auth = 'cfg_allow_avatar' + WHERE module_class = 'ucp' + AND module_basename = 'ucp_profile' + AND module_mode = 'avatar'"; + $this->db->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v310/beta1.php b/phpbb/db/migration/data/v310/beta1.php new file mode 100644 index 0000000..9feba52 --- /dev/null +++ b/phpbb/db/migration/data/v310/beta1.php @@ -0,0 +1,42 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class beta1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-b1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\alpha3', + '\phpbb\db\migration\data\v310\passwords_p2', + '\phpbb\db\migration\data\v310\postgres_fulltext_drop', + '\phpbb\db\migration\data\v310\profilefield_change_load_settings', + '\phpbb\db\migration\data\v310\profilefield_location', + '\phpbb\db\migration\data\v310\soft_delete_mod_convert2', + '\phpbb\db\migration\data\v310\ucp_popuppm_module', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-b1')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/beta2.php b/phpbb/db/migration/data/v310/beta2.php new file mode 100644 index 0000000..d5e31ce --- /dev/null +++ b/phpbb/db/migration/data/v310/beta2.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class beta2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-b2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\beta1', + '\phpbb\db\migration\data\v310\acp_prune_users_module', + '\phpbb\db\migration\data\v310\profilefield_location_cleanup', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-b2')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/beta3.php b/phpbb/db/migration/data/v310/beta3.php new file mode 100644 index 0000000..78c61e8 --- /dev/null +++ b/phpbb/db/migration/data/v310/beta3.php @@ -0,0 +1,41 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class beta3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-b3', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\beta2', + '\phpbb\db\migration\data\v310\auth_provider_oauth2', + '\phpbb\db\migration\data\v310\board_contact_name', + '\phpbb\db\migration\data\v310\jquery_update2', + '\phpbb\db\migration\data\v310\live_searches_config', + '\phpbb\db\migration\data\v310\prune_shadow_topics', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-b3')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/beta4.php b/phpbb/db/migration/data/v310/beta4.php new file mode 100644 index 0000000..e634785 --- /dev/null +++ b/phpbb/db/migration/data/v310/beta4.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class beta4 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-b4', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\beta3', + '\phpbb\db\migration\data\v310\extensions_version_check_force_unstable', + '\phpbb\db\migration\data\v310\reset_missing_captcha_plugin', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-b4')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/board_contact_name.php b/phpbb/db/migration/data/v310/board_contact_name.php new file mode 100644 index 0000000..6f51887 --- /dev/null +++ b/phpbb/db/migration/data/v310/board_contact_name.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class board_contact_name extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return isset($this->config['board_contact_name']); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\beta2'); + } + + public function update_data() + { + return array( + array('config.add', array('board_contact_name', '')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/boardindex.php b/phpbb/db/migration/data/v310/boardindex.php new file mode 100644 index 0000000..77a8558 --- /dev/null +++ b/phpbb/db/migration/data/v310/boardindex.php @@ -0,0 +1,29 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class boardindex extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return isset($this->config['board_index_text']); + } + + public function update_data() + { + return array( + array('config.add', array('board_index_text', '')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/bot_update.php b/phpbb/db/migration/data/v310/bot_update.php new file mode 100644 index 0000000..39b16c6 --- /dev/null +++ b/phpbb/db/migration/data/v310/bot_update.php @@ -0,0 +1,150 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class bot_update extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\rc6'); + } + + public function update_data() + { + return array( + array('custom', array(array(&$this, 'update_bing_bot'))), + array('custom', array(array(&$this, 'update_bots'))), + ); + } + + public function update_bing_bot() + { + $bot_name = 'Bing [Bot]'; + $bot_name_clean = utf8_clean_string($bot_name); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape($bot_name_clean) . "'"; + $result = $this->db->sql_query($sql); + $bing_already_added = (bool) $this->db->sql_fetchfield('user_id'); + $this->db->sql_freeresult($result); + + if (!$bing_already_added) + { + $bot_agent = 'bingbot/'; + $bot_ip = ''; + $sql = 'SELECT group_id, group_colour + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'BOTS'"; + $result = $this->db->sql_query($sql); + $group_row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$group_row) + { + // default fallback, should never get here + $group_row['group_id'] = 6; + $group_row['group_colour'] = '9E8DA7'; + } + + if (!function_exists('user_add')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $user_row = array( + 'user_type' => USER_IGNORE, + 'group_id' => $group_row['group_id'], + 'username' => $bot_name, + 'user_regdate' => time(), + 'user_password' => '', + 'user_colour' => $group_row['group_colour'], + 'user_email' => '', + 'user_lang' => $this->config['default_lang'], + 'user_style' => $this->config['default_style'], + 'user_timezone' => 0, + 'user_dateformat' => $this->config['default_dateformat'], + 'user_allow_massemail' => 0, + ); + + $user_id = user_add($user_row); + + $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', array( + 'bot_active' => 1, + 'bot_name' => (string) $bot_name, + 'user_id' => (int) $user_id, + 'bot_agent' => (string) $bot_agent, + 'bot_ip' => (string) $bot_ip, + )); + + $this->sql_query($sql); + } + } + + public function update_bots() + { + // Update bots + if (!function_exists('user_delete')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $bots_updates = array( + // Bot Deletions + 'NG-Search [Bot]' => false, + 'Nutch/CVS [Bot]' => false, + 'OmniExplorer [Bot]' => false, + 'Seekport [Bot]' => false, + 'Synoo [Bot]' => false, + 'WiseNut [Bot]' => false, + + // Bot Updates + // Bot name to bot user agent map + 'Baidu [Spider]' => 'Baiduspider', + 'Exabot [Bot]' => 'Exabot', + 'Voyager [Bot]' => 'voyager/', + 'W3C [Validator]' => 'W3C_Validator', + ); + + foreach ($bots_updates as $bot_name => $bot_agent) + { + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE user_type = ' . USER_IGNORE . " + AND username_clean = '" . $this->db->sql_escape(utf8_clean_string($bot_name)) . "'"; + $result = $this->db->sql_query($sql); + $bot_user_id = (int) $this->db->sql_fetchfield('user_id'); + $this->db->sql_freeresult($result); + + if ($bot_user_id) + { + if ($bot_agent === false) + { + $sql = 'DELETE FROM ' . BOTS_TABLE . " + WHERE user_id = $bot_user_id"; + $this->sql_query($sql); + + user_delete('retain', $bot_user_id); + } + else + { + $sql = 'UPDATE ' . BOTS_TABLE . " + SET bot_agent = '" . $this->db->sql_escape($bot_agent) . "' + WHERE user_id = $bot_user_id"; + $this->sql_query($sql); + } + } + } + } +} diff --git a/phpbb/db/migration/data/v310/captcha_plugins.php b/phpbb/db/migration/data/v310/captcha_plugins.php new file mode 100644 index 0000000..328c08f --- /dev/null +++ b/phpbb/db/migration/data/v310/captcha_plugins.php @@ -0,0 +1,48 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class captcha_plugins extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\rc2', + ); + } + + public function update_data() + { + $captcha_plugin = $this->config['captcha_plugin']; + if (strpos($captcha_plugin, 'phpbb_captcha_') === 0) + { + $captcha_plugin = substr($captcha_plugin, strlen('phpbb_captcha_')); + } + else if (strpos($captcha_plugin, 'phpbb_') === 0) + { + $captcha_plugin = substr($captcha_plugin, strlen('phpbb_')); + } + + return array( + array('if', array( + (is_file($this->phpbb_root_path . 'phpbb/captcha/plugins/' . $captcha_plugin . '.' . $this->php_ext)), + array('config.update', array('captcha_plugin', 'core.captcha.plugins.' . $captcha_plugin)), + )), + array('if', array( + (!is_file($this->phpbb_root_path . 'phpbb/captcha/plugins/' . $captcha_plugin . '.' . $this->php_ext)), + array('config.update', array('captcha_plugin', 'core.captcha.plugins.nogd')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/config_db_text.php b/phpbb/db/migration/data/v310/config_db_text.php new file mode 100644 index 0000000..438883c --- /dev/null +++ b/phpbb/db/migration/data/v310/config_db_text.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class config_db_text extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'config_text'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'config_text' => array( + 'COLUMNS' => array( + 'config_name' => array('VCHAR', ''), + 'config_value' => array('MTEXT', ''), + ), + 'PRIMARY_KEY' => 'config_name', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'config_text', + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/contact_admin_acp_module.php b/phpbb/db/migration/data/v310/contact_admin_acp_module.php new file mode 100644 index 0000000..e48a9a1 --- /dev/null +++ b/phpbb/db/migration/data/v310/contact_admin_acp_module.php @@ -0,0 +1,33 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class contact_admin_acp_module extends \phpbb\db\migration\migration +{ + public function update_data() + { + return array( + array('module.add', array( + 'acp', + 'ACP_BOARD_CONFIGURATION', + array( + 'module_basename' => 'acp_contact', + 'module_langname' => 'ACP_CONTACT_SETTINGS', + 'module_mode' => 'contact', + 'module_auth' => 'acl_a_board', + ), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/contact_admin_form.php b/phpbb/db/migration/data/v310/contact_admin_form.php new file mode 100644 index 0000000..5736369 --- /dev/null +++ b/phpbb/db/migration/data/v310/contact_admin_form.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class contact_admin_form extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return isset($this->config['contact_admin_form_enable']); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\config_db_text'); + } + + public function update_data() + { + return array( + array('config.add', array('contact_admin_form_enable', 1)), + array('custom', array(array($this, 'contact_admin_info'))), + ); + } + + public function contact_admin_info() + { + $text_config = new \phpbb\config\db_text($this->db, $this->table_prefix . 'config_text'); + $text_config->set_array(array( + 'contact_admin_info' => '', + 'contact_admin_info_uid' => '', + 'contact_admin_info_bitfield' => '', + 'contact_admin_info_flags' => OPTION_FLAG_BBCODE + OPTION_FLAG_SMILIES + OPTION_FLAG_LINKS, + )); + } +} diff --git a/phpbb/db/migration/data/v310/dev.php b/phpbb/db/migration/data/v310/dev.php new file mode 100644 index 0000000..9cc953a --- /dev/null +++ b/phpbb/db/migration/data/v310/dev.php @@ -0,0 +1,427 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class dev extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.1.0-dev', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\extensions', + '\phpbb\db\migration\data\v310\style_update_p2', + '\phpbb\db\migration\data\v310\timezone_p2', + '\phpbb\db\migration\data\v310\reported_posts_display', + '\phpbb\db\migration\data\v310\migrations_table', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'), + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_pm' => array('BOOL', 0), + ), + $this->table_prefix . 'styles' => array( + 'style_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'style_parent_id' => array('UINT:4', 0), + 'style_parent_tree' => array('TEXT', ''), + ), + $this->table_prefix . 'reports' => array( + 'reported_post_text' => array('MTEXT_UNI', ''), + 'reported_post_uid' => array('VCHAR:8', ''), + 'reported_post_bitfield' => array('VCHAR:255', ''), + ), + ), + 'change_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_legend' => array('UINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage', + ), + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_pm', + ), + $this->table_prefix . 'styles' => array( + 'style_path', + 'bbcode_bitfield', + 'style_parent_id', + 'style_parent_tree', + ), + $this->table_prefix . 'reports' => array( + 'reported_post_text', + 'reported_post_uid', + 'reported_post_bitfield', + ), + ), + ); + } + + public function update_data() + { + return array( + array('if', array( + (strpos('phpbb_search_', $this->config['search_type']) !== 0), + array('config.update', array('search_type', 'phpbb_search_' . $this->config['search_type'])), + )), + + array('config.add', array('fulltext_postgres_ts_name', 'simple')), + array('config.add', array('fulltext_postgres_min_word_len', 4)), + array('config.add', array('fulltext_postgres_max_word_len', 254)), + array('config.add', array('fulltext_sphinx_stopwords', 0)), + array('config.add', array('fulltext_sphinx_indexer_mem_limit', 512)), + + array('config.add', array('load_jquery_cdn', 0)), + array('config.add', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js')), + + array('config.add', array('use_system_cron', 0)), + + array('config.add', array('legend_sort_groupname', 0)), + array('config.add', array('teampage_forums', 1)), + array('config.add', array('teampage_memberships', 1)), + + array('config.add', array('load_cpf_pm', 0)), + + array('config.add', array('display_last_subject', 1)), + + array('config.add', array('assets_version', 1)), + + array('config.add', array('site_home_url', '')), + array('config.add', array('site_home_text', '')), + + array('permission.add', array('u_chgprofileinfo', true, 'u_sig')), + + array('module.add', array( + 'acp', + 'ACP_GROUPS', + array( + 'module_basename' => 'acp_groups', + 'module_langname' => 'ACP_GROUPS_POSITION', + 'module_mode' => 'position', + 'module_auth' => 'acl_a_group', + ), + )), + array('module.add', array( + 'acp', + 'ACP_ATTACHMENTS', + array( + 'module_basename' => 'acp_attachments', + 'module_langname' => 'ACP_MANAGE_ATTACHMENTS', + 'module_mode' => 'manage', + 'module_auth' => 'acl_a_attach', + ), + )), + array('module.add', array( + 'acp', + 'ACP_STYLE_MANAGEMENT', + array( + 'module_basename' => 'acp_styles', + 'module_langname' => 'ACP_STYLES_INSTALL', + 'module_mode' => 'install', + 'module_auth' => 'acl_a_styles', + ), + )), + array('module.add', array( + 'acp', + 'ACP_STYLE_MANAGEMENT', + array( + 'module_basename' => 'acp_styles', + 'module_langname' => 'ACP_STYLES_CACHE', + 'module_mode' => 'cache', + 'module_auth' => 'acl_a_styles', + ), + )), + array('module.add', array( + 'ucp', + 'UCP_PROFILE', + array( + 'module_basename' => 'ucp_profile', + 'module_langname' => 'UCP_PROFILE_AUTOLOGIN_KEYS', + 'module_mode' => 'autologin_keys', + ), + )), + // Module will be renamed later + array('module.add', array( + 'acp', + 'ACP_CAT_STYLES', + 'ACP_LANGUAGE' + )), + + array('module.remove', array( + 'acp', + false, + 'ACP_TEMPLATES', + )), + array('module.remove', array( + 'acp', + false, + 'ACP_THEMES', + )), + array('module.remove', array( + 'acp', + false, + 'ACP_IMAGESETS', + )), + + array('custom', array(array($this, 'rename_module_basenames'))), + array('custom', array(array($this, 'rename_styles_module'))), + array('custom', array(array($this, 'add_group_teampage'))), + array('custom', array(array($this, 'update_group_legend'))), + array('custom', array(array($this, 'localise_global_announcements'))), + array('custom', array(array($this, 'update_ucp_pm_basename'))), + array('custom', array(array($this, 'update_ucp_profile_auth'))), + array('custom', array(array($this, 'move_customise_modules'))), + + array('config.update', array('version', '3.1.0-dev')), + ); + } + + public function move_customise_modules() + { + // Move language management to new location in the Customise tab + // First get language module id + $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " + WHERE module_basename = 'acp_language'"; + $result = $this->db->sql_query($sql); + $language_module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + // Next get language management module id of the one just created + $sql = 'SELECT module_id FROM ' . MODULES_TABLE . " + WHERE module_langname = 'ACP_LANGUAGE'"; + $result = $this->db->sql_query($sql); + $language_management_module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + // acp_modules calls adm_back_link, which is undefined at this point + if (!function_exists('adm_back_link')) + { + include($this->phpbb_root_path . 'includes/functions_acp.' . $this->php_ext); + } + $module_manager = $this->container->get('module.manager'); + $module_manager->move_module($language_module_id, $language_management_module_id, 'acp'); + } + + public function update_ucp_pm_basename() + { + $sql = 'SELECT module_id, module_basename + FROM ' . MODULES_TABLE . " + WHERE module_basename <> 'ucp_pm' AND + module_langname='UCP_PM'"; + $result = $this->db->sql_query_limit($sql, 1); + + if ($row = $this->db->sql_fetchrow($result)) + { + // This update is still not applied. Applying it + + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_basename = 'ucp_pm' + WHERE module_id = " . (int) $row['module_id']; + + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } + + public function update_ucp_profile_auth() + { + // Update the auth setting for the module + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'acl_u_chgprofileinfo' + WHERE module_class = 'ucp' + AND module_basename = 'ucp_profile' + AND module_mode = 'profile_info'"; + $this->sql_query($sql); + } + + public function rename_styles_module() + { + // Rename styles module to Customise + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_langname = 'ACP_CAT_CUSTOMISE' + WHERE module_langname = 'ACP_CAT_STYLES'"; + $this->sql_query($sql); + } + + public function rename_module_basenames() + { + // rename all module basenames to full classname + $sql = 'SELECT module_id, module_basename, module_class + FROM ' . MODULES_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $module_id = (int) $row['module_id']; + unset($row['module_id']); + + if (!empty($row['module_basename']) && !empty($row['module_class'])) + { + // all the class names start with class name or with phpbb_ for auto loading + if (strpos($row['module_basename'], $row['module_class'] . '_') !== 0 && + strpos($row['module_basename'], 'phpbb_') !== 0) + { + $row['module_basename'] = $row['module_class'] . '_' . $row['module_basename']; + + $sql_update = $this->db->sql_build_array('UPDATE', $row); + + $sql = 'UPDATE ' . MODULES_TABLE . ' + SET ' . $sql_update . ' + WHERE module_id = ' . $module_id; + $this->sql_query($sql); + } + } + } + + $this->db->sql_freeresult($result); + } + + public function add_group_teampage() + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_teampage = 1 + WHERE group_type = ' . GROUP_SPECIAL . " + AND group_name = 'ADMINISTRATORS'"; + $this->sql_query($sql); + + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_teampage = 2 + WHERE group_type = ' . GROUP_SPECIAL . " + AND group_name = 'GLOBAL_MODERATORS'"; + $this->sql_query($sql); + } + + public function update_group_legend() + { + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . ' + WHERE group_legend = 1 + ORDER BY group_name ASC'; + $result = $this->db->sql_query($sql); + + $next_legend = 1; + while ($row = $this->db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = ' . $next_legend . ' + WHERE group_id = ' . (int) $row['group_id']; + $this->sql_query($sql); + + $next_legend++; + } + $this->db->sql_freeresult($result); + } + + public function localise_global_announcements() + { + // Localise Global Announcements + $sql = 'SELECT topic_id, topic_approved, (topic_replies + 1) AS topic_posts, topic_last_post_id, topic_last_post_subject, topic_last_post_time, topic_last_poster_id, topic_last_poster_name, topic_last_poster_colour + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = 0 + AND topic_type = ' . POST_GLOBAL; + $result = $this->db->sql_query($sql); + + $global_announcements = $update_lastpost_data = array(); + $update_lastpost_data['forum_last_post_time'] = 0; + $update_forum_data = array( + 'forum_posts' => 0, + 'forum_topics' => 0, + 'forum_topics_real' => 0, + ); + + while ($row = $this->db->sql_fetchrow($result)) + { + $global_announcements[] = (int) $row['topic_id']; + + $update_forum_data['forum_posts'] += (int) $row['topic_posts']; + $update_forum_data['forum_topics_real']++; + if ($row['topic_approved']) + { + $update_forum_data['forum_topics']++; + } + + if ($update_lastpost_data['forum_last_post_time'] < $row['topic_last_post_time']) + { + $update_lastpost_data = array( + 'forum_last_post_id' => (int) $row['topic_last_post_id'], + 'forum_last_post_subject' => $row['topic_last_post_subject'], + 'forum_last_post_time' => (int) $row['topic_last_post_time'], + 'forum_last_poster_id' => (int) $row['topic_last_poster_id'], + 'forum_last_poster_name' => $row['topic_last_poster_name'], + 'forum_last_poster_colour' => $row['topic_last_poster_colour'], + ); + } + } + $this->db->sql_freeresult($result); + + if (!empty($global_announcements)) + { + // Update the post/topic-count for the forum and the last-post if needed + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type = ' . FORUM_POST; + $result = $this->db->sql_query_limit($sql, 1); + $ga_forum_id = $this->db->sql_fetchfield('forum_id'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT forum_last_post_time + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $ga_forum_id; + $result = $this->db->sql_query($sql); + $lastpost = (int) $this->db->sql_fetchfield('forum_last_post_time'); + $this->db->sql_freeresult($result); + + $sql_update = 'forum_posts = forum_posts + ' . $update_forum_data['forum_posts'] . ', '; + $sql_update .= 'forum_topics_real = forum_topics_real + ' . $update_forum_data['forum_topics_real'] . ', '; + $sql_update .= 'forum_topics = forum_topics + ' . $update_forum_data['forum_topics']; + if ($lastpost < $update_lastpost_data['forum_last_post_time']) + { + $sql_update .= ', ' . $this->db->sql_build_array('UPDATE', $update_lastpost_data); + } + + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET ' . $sql_update . ' + WHERE forum_id = ' . $ga_forum_id; + $this->sql_query($sql); + + // Update some forum_ids + $table_ary = array(TOPICS_TABLE, POSTS_TABLE, LOG_TABLE, DRAFTS_TABLE, TOPICS_TRACK_TABLE); + foreach ($table_ary as $table) + { + $sql = "UPDATE $table + SET forum_id = $ga_forum_id + WHERE " . $this->db->sql_in_set('topic_id', $global_announcements); + $this->sql_query($sql); + } + unset($table_ary); + } + } +} diff --git a/phpbb/db/migration/data/v310/extensions.php b/phpbb/db/migration/data/v310/extensions.php new file mode 100644 index 0000000..2e7c5c5 --- /dev/null +++ b/phpbb/db/migration/data/v310/extensions.php @@ -0,0 +1,77 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class extensions extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'ext'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'ext' => array( + 'COLUMNS' => array( + 'ext_name' => array('VCHAR', ''), + 'ext_active' => array('BOOL', 0), + 'ext_state' => array('TEXT', ''), + ), + 'KEYS' => array( + 'ext_name' => array('UNIQUE', 'ext_name'), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'ext', + ), + ); + } + + public function update_data() + { + return array( + // Module will be renamed later + array('module.add', array( + 'acp', + 'ACP_CAT_STYLES', + 'ACP_EXTENSION_MANAGEMENT' + )), + array('module.add', array( + 'acp', + 'ACP_EXTENSION_MANAGEMENT', + array( + 'module_basename' => 'acp_extensions', + 'module_langname' => 'ACP_EXTENSIONS', + 'module_mode' => 'main', + 'module_auth' => 'acl_a_extensions', + ), + )), + array('permission.add', array('a_extensions', true, 'a_styles')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/extensions_version_check_force_unstable.php b/phpbb/db/migration/data/v310/extensions_version_check_force_unstable.php new file mode 100644 index 0000000..1d6276f --- /dev/null +++ b/phpbb/db/migration/data/v310/extensions_version_check_force_unstable.php @@ -0,0 +1,29 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class extensions_version_check_force_unstable extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_data() + { + return array( + array('config.add', array('extension_force_unstable', false)), + ); + } +} diff --git a/phpbb/db/migration/data/v310/forgot_password.php b/phpbb/db/migration/data/v310/forgot_password.php new file mode 100644 index 0000000..362457c --- /dev/null +++ b/phpbb/db/migration/data/v310/forgot_password.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class forgot_password extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return isset($this->config['allow_password_reset']); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_data() + { + return array( + array('config.add', array('allow_password_reset', 1)), + ); + } +} diff --git a/phpbb/db/migration/data/v310/gold.php b/phpbb/db/migration/data/v310/gold.php new file mode 100644 index 0000000..188851f --- /dev/null +++ b/phpbb/db/migration/data/v310/gold.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class gold extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\rc6', + '\phpbb\db\migration\data\v310\bot_update', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/jquery_update.php b/phpbb/db/migration/data/v310/jquery_update.php new file mode 100644 index 0000000..8011331 --- /dev/null +++ b/phpbb/db/migration/data/v310/jquery_update.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class jquery_update extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->config['load_jquery_url'] !== '//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js'; + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + ); + } + + public function update_data() + { + return array( + array('config.update', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js')), + ); + } + +} diff --git a/phpbb/db/migration/data/v310/jquery_update2.php b/phpbb/db/migration/data/v310/jquery_update2.php new file mode 100644 index 0000000..4061be5 --- /dev/null +++ b/phpbb/db/migration/data/v310/jquery_update2.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class jquery_update2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->config['load_jquery_url'] !== '//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js'; + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\jquery_update', + ); + } + + public function update_data() + { + return array( + array('config.update', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js')), + ); + } + +} diff --git a/phpbb/db/migration/data/v310/live_searches_config.php b/phpbb/db/migration/data/v310/live_searches_config.php new file mode 100644 index 0000000..3d87e04 --- /dev/null +++ b/phpbb/db/migration/data/v310/live_searches_config.php @@ -0,0 +1,29 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class live_searches_config extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return isset($this->config['allow_live_searches']); + } + + public function update_data() + { + return array( + array('config.add', array('allow_live_searches', '1')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/migrations_table.php b/phpbb/db/migration/data/v310/migrations_table.php new file mode 100644 index 0000000..48508b0 --- /dev/null +++ b/phpbb/db/migration/data/v310/migrations_table.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class migrations_table extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'migrations'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'migrations' => array( + 'COLUMNS' => array( + 'migration_name' => array('VCHAR', ''), + 'migration_depends_on' => array('TEXT', ''), + 'migration_schema_done' => array('BOOL', 0), + 'migration_data_done' => array('BOOL', 0), + 'migration_data_state' => array('TEXT', ''), + 'migration_start_time' => array('TIMESTAMP', 0), + 'migration_end_time' => array('TIMESTAMP', 0), + ), + 'PRIMARY_KEY' => 'migration_name', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'migrations', + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/mod_rewrite.php b/phpbb/db/migration/data/v310/mod_rewrite.php new file mode 100644 index 0000000..85e479d --- /dev/null +++ b/phpbb/db/migration/data/v310/mod_rewrite.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class mod_rewrite extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + ); + } + + public function update_data() + { + return array( + array('config.add', array('enable_mod_rewrite', '0')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/mysql_fulltext_drop.php b/phpbb/db/migration/data/v310/mysql_fulltext_drop.php new file mode 100644 index 0000000..e04a705 --- /dev/null +++ b/phpbb/db/migration/data/v310/mysql_fulltext_drop.php @@ -0,0 +1,80 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class mysql_fulltext_drop extends \phpbb\db\migration\migration +{ + protected $indexes; + + public function effectively_installed() + { + // This migration is irrelevant for all non-MySQL DBMSes. + if (strpos($this->db->get_sql_layer(), 'mysql') === false) + { + return true; + } + + $this->find_indexes_to_drop(); + return empty($this->indexes); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + ); + } + + public function update_schema() + { + if (empty($this->indexes)) + { + return array(); + } + + /* + * Drop FULLTEXT indexes related to MySQL fulltext search. + * Doing so is equivalent to dropping the search index from the ACP. + * Possibly time-consuming recreation of the search index (i.e. + * FULLTEXT indexes) is left as a task to the admin to not + * unnecessarily stall the upgrade process. The new search index will + * then require about 40% less table space (also see PHPBB3-11621). + */ + return array( + 'drop_keys' => array( + $this->table_prefix . 'posts' => $this->indexes, + ), + ); + } + + public function find_indexes_to_drop() + { + if ($this->indexes !== null) + { + return $this->indexes; + } + + $this->indexes = array(); + $potential_keys = array('post_subject', 'post_text', 'post_content'); + foreach ($potential_keys as $key) + { + if ($this->db_tools->sql_index_exists($this->table_prefix . 'posts', $key)) + { + $this->indexes[] = $key; + } + } + + return $this->indexes; + } +} diff --git a/phpbb/db/migration/data/v310/namespaces.php b/phpbb/db/migration/data/v310/namespaces.php new file mode 100644 index 0000000..2a49353 --- /dev/null +++ b/phpbb/db/migration/data/v310/namespaces.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class namespaces extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + ); + } + + public function update_data() + { + return array( + array('if', array( + (preg_match('#^phpbb_search_#', $this->config['search_type'])), + array('config.update', array('search_type', str_replace('phpbb_search_', '\\phpbb\\search\\', $this->config['search_type']))), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/notification_options_reconvert.php b/phpbb/db/migration/data/v310/notification_options_reconvert.php new file mode 100644 index 0000000..d43d432 --- /dev/null +++ b/phpbb/db/migration/data/v310/notification_options_reconvert.php @@ -0,0 +1,142 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class notification_options_reconvert extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\notifications_schema_fix'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'purge_notifications'))), + array('custom', array(array($this, 'convert_notifications'))), + ); + } + + public function purge_notifications() + { + $sql = 'DELETE FROM ' . $this->table_prefix . 'user_notifications'; + $this->sql_query($sql); + } + + public function convert_notifications($start) + { + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->table_prefix . 'user_notifications'); + + return $this->perform_conversion($insert_buffer, $start); + } + + /** + * Perform the conversion (separate for testability) + * + * @param \phpbb\db\sql_insert_buffer $insert_buffer + * @param int $start Start of staggering step + * @return mixed int start of the next step, null if the end was reached + */ + public function perform_conversion(\phpbb\db\sql_insert_buffer $insert_buffer, $start) + { + $limit = 250; + $converted_users = 0; + $start = $start ?: 0; + + $sql = 'SELECT user_id, user_notify_type, user_notify_pm + FROM ' . $this->table_prefix . 'users + ORDER BY user_id'; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $converted_users++; + $notification_methods = array(); + + // In-board notification + $notification_methods[] = ''; + + if ($row['user_notify_type'] == NOTIFY_EMAIL || $row['user_notify_type'] == NOTIFY_BOTH) + { + $notification_methods[] = 'email'; + } + + if ($row['user_notify_type'] == NOTIFY_IM || $row['user_notify_type'] == NOTIFY_BOTH) + { + $notification_methods[] = 'jabber'; + } + + // Notifications for posts + foreach (array('post', 'topic') as $item_type) + { + $this->add_method_rows( + $insert_buffer, + $item_type, + 0, + $row['user_id'], + $notification_methods + ); + } + + if ($row['user_notify_pm']) + { + // Notifications for private messages + // User either gets all methods or no method + $this->add_method_rows( + $insert_buffer, + 'pm', + 0, + $row['user_id'], + $notification_methods + ); + } + } + $this->db->sql_freeresult($result); + + $insert_buffer->flush(); + + if ($converted_users < $limit) + { + // No more users left, we are done... + return; + } + + return $start + $limit; + } + + /** + * Insert method rows to DB + * + * @param \phpbb\db\sql_insert_buffer $insert_buffer + * @param string $item_type + * @param int $item_id + * @param int $user_id + * @param string $methods + */ + protected function add_method_rows(\phpbb\db\sql_insert_buffer $insert_buffer, $item_type, $item_id, $user_id, array $methods) + { + $row_base = array( + 'item_type' => $item_type, + 'item_id' => (int) $item_id, + 'user_id' => (int) $user_id, + 'notify' => 1 + ); + + foreach ($methods as $method) + { + $row_base['method'] = $method; + $insert_buffer->insert($row_base); + } + } +} diff --git a/phpbb/db/migration/data/v310/notifications.php b/phpbb/db/migration/data/v310/notifications.php new file mode 100644 index 0000000..789aaa3 --- /dev/null +++ b/phpbb/db/migration/data/v310/notifications.php @@ -0,0 +1,105 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class notifications extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'notifications'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'notification_types' => array( + 'COLUMNS' => array( + 'notification_type' => array('VCHAR:255', ''), + 'notification_type_enabled' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'), + ), + $this->table_prefix . 'notifications' => array( + 'COLUMNS' => array( + 'notification_id' => array('UINT', null, 'auto_increment'), + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'item_parent_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notification_read' => array('BOOL', 0), + 'notification_time' => array('TIMESTAMP', 1), + 'notification_data' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'notification_id', + 'KEYS' => array( + 'item_ident' => array('INDEX', array('item_type', 'item_id')), + 'user' => array('INDEX', array('user_id', 'notification_read')), + ), + ), + $this->table_prefix . 'user_notifications' => array( + 'COLUMNS' => array( + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'method' => array('VCHAR:255', ''), + 'notify' => array('BOOL', 1), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'notification_types', + $this->table_prefix . 'notifications', + $this->table_prefix . 'user_notifications', + ), + ); + } + + public function update_data() + { + return array( + array('module.add', array( + 'ucp', + 'UCP_MAIN', + array( + 'module_basename' => 'ucp_notifications', + 'module_langname' => 'UCP_NOTIFICATION_LIST', + 'module_mode' => 'notification_list', + 'module_auth' => 'cfg_allow_board_notifications', + ), + )), + array('module.add', array( + 'ucp', + 'UCP_PREFS', + array( + 'module_basename' => 'ucp_notifications', + 'module_langname' => 'UCP_NOTIFICATION_OPTIONS', + 'module_mode' => 'notification_options', + ), + )), + array('config.add', array('load_notifications', 1)), + ); + } +} diff --git a/phpbb/db/migration/data/v310/notifications_cron.php b/phpbb/db/migration/data/v310/notifications_cron.php new file mode 100644 index 0000000..ba600f7 --- /dev/null +++ b/phpbb/db/migration/data/v310/notifications_cron.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class notifications_cron extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\notifications'); + } + + public function update_data() + { + return array( + array('config.add', array('read_notification_expire_days', 30)), + array('config.add', array('read_notification_last_gc', 0)), // last run + array('config.add', array('read_notification_gc', (60 * 60 * 24))), // seconds between run; 1 day + ); + } +} diff --git a/phpbb/db/migration/data/v310/notifications_cron_p2.php b/phpbb/db/migration/data/v310/notifications_cron_p2.php new file mode 100644 index 0000000..263584b --- /dev/null +++ b/phpbb/db/migration/data/v310/notifications_cron_p2.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class notifications_cron_p2 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\notifications_cron'); + } + + public function update_data() + { + return array( + // Make read_notification_last_gc dynamic. + array('config.remove', array('read_notification_last_gc')), + array('config.add', array('read_notification_last_gc', 0, 1)), + ); + } +} diff --git a/phpbb/db/migration/data/v310/notifications_schema_fix.php b/phpbb/db/migration/data/v310/notifications_schema_fix.php new file mode 100644 index 0000000..21a39a7 --- /dev/null +++ b/phpbb/db/migration/data/v310/notifications_schema_fix.php @@ -0,0 +1,98 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class notifications_schema_fix extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\notifications'); + } + + public function update_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'notification_types', + $this->table_prefix . 'notifications', + ), + 'add_tables' => array( + $this->table_prefix . 'notification_types' => array( + 'COLUMNS' => array( + 'notification_type_id' => array('USINT', null, 'auto_increment'), + 'notification_type_name' => array('VCHAR:255', ''), + 'notification_type_enabled' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => array('notification_type_id'), + 'KEYS' => array( + 'type' => array('UNIQUE', array('notification_type_name')), + ), + ), + $this->table_prefix . 'notifications' => array( + 'COLUMNS' => array( + 'notification_id' => array('UINT:10', null, 'auto_increment'), + 'notification_type_id' => array('USINT', 0), + 'item_id' => array('UINT', 0), + 'item_parent_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notification_read' => array('BOOL', 0), + 'notification_time' => array('TIMESTAMP', 1), + 'notification_data' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'notification_id', + 'KEYS' => array( + 'item_ident' => array('INDEX', array('notification_type_id', 'item_id')), + 'user' => array('INDEX', array('user_id', 'notification_read')), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'notification_types', + $this->table_prefix . 'notifications', + ), + 'add_tables' => array( + $this->table_prefix . 'notification_types' => array( + 'COLUMNS' => array( + 'notification_type' => array('VCHAR:255', ''), + 'notification_type_enabled' => array('BOOL', 1), + ), + 'PRIMARY_KEY' => array('notification_type', 'notification_type_enabled'), + ), + $this->table_prefix . 'notifications' => array( + 'COLUMNS' => array( + 'notification_id' => array('UINT', null, 'auto_increment'), + 'item_type' => array('VCHAR:255', ''), + 'item_id' => array('UINT', 0), + 'item_parent_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'notification_read' => array('BOOL', 0), + 'notification_time' => array('TIMESTAMP', 1), + 'notification_data' => array('TEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'notification_id', + 'KEYS' => array( + 'item_ident' => array('INDEX', array('item_type', 'item_id')), + 'user' => array('INDEX', array('user_id', 'notification_read')), + ), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/notifications_use_full_name.php b/phpbb/db/migration/data/v310/notifications_use_full_name.php new file mode 100644 index 0000000..112c1e8 --- /dev/null +++ b/phpbb/db/migration/data/v310/notifications_use_full_name.php @@ -0,0 +1,184 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class notifications_use_full_name extends \phpbb\db\migration\migration +{ + protected $notification_types = array( + 'admin_activate_user', + 'approve_post', + 'approve_topic', + 'bookmark', + 'disapprove_post', + 'disapprove_topic', + 'group_request', + 'group_request_approved', + 'pm', + 'post', + 'post_in_queue', + 'quote', + 'report_pm', + 'report_pm_closed', + 'report_post', + 'report_post_closed', + 'topic', + 'topic_in_queue'); + + protected $notification_methods = array( + 'email', + 'jabber', + ); + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\rc3'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_notifications_name'))), + array('custom', array(array($this, 'update_notifications_method_name'))), + ); + } + + public function revert_data() + { + return array( + array('custom', array(array($this, 'revert_notifications_name'))), + array('custom', array(array($this, 'revert_notifications_method_name'))), + ); + } + + public function update_notifications_method_name() + { + foreach ($this->notification_methods as $notification_method) + { + $sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " + SET method = 'notification.method.{$notification_method}' + WHERE method = '{$notification_method}'"; + $this->db->sql_query($sql); + } + } + + public function revert_notifications_method_name() + { + foreach ($this->notification_methods as $notification_method) + { + $sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " + SET method = '{$notification_method}' + WHERE method = 'notification.method.{$notification_method}'"; + $this->db->sql_query($sql); + } + } + + public function update_notifications_name() + { + $sql = 'UPDATE ' . NOTIFICATION_TYPES_TABLE . ' + SET notification_type_enabled = 0 + WHERE ' . $this->db->sql_in_set('notification_type_name', $this->notification_types, true); + $this->db->sql_query($sql); + + foreach ($this->notification_types as $notification_type) + { + $sql = 'SELECT notification_type_id + FROM ' . NOTIFICATION_TYPES_TABLE . " + WHERE notification_type_name = 'notification.type.{$notification_type}'"; + $result = $this->db->sql_query($sql); + $new_type_id = (int) $this->db->sql_fetchfield('notification_type_id'); + $this->db->sql_freeresult($result); + + if ($new_type_id) + { + // New type name already exists, + // so we delete the old type and update the type id of existing entries. + $sql = 'SELECT notification_type_id + FROM ' . NOTIFICATION_TYPES_TABLE . " + WHERE notification_type_name = '{$notification_type}'"; + $result = $this->db->sql_query($sql); + $old_type_id = (int) $this->db->sql_fetchfield('notification_type_id'); + $this->db->sql_freeresult($result); + + $sql = 'UPDATE ' . NOTIFICATIONS_TABLE . ' + SET notification_type_id = ' . (int) $new_type_id . ' + WHERE notification_type_id = ' . (int) $old_type_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . NOTIFICATION_TYPES_TABLE . " + WHERE notification_type_name = '{$notification_type}'"; + $this->db->sql_query($sql); + } + else + { + // Otherwise we just update the name + $sql = 'UPDATE ' . NOTIFICATION_TYPES_TABLE . " + SET notification_type_name = 'notification.type.{$notification_type}' + WHERE notification_type_name = '{$notification_type}'"; + $this->db->sql_query($sql); + } + + $sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " + SET item_type = 'notification.type.{$notification_type}' + WHERE item_type = '{$notification_type}'"; + $this->db->sql_query($sql); + } + } + + public function revert_notifications_name() + { + foreach ($this->notification_types as $notification_type) + { + $sql = 'SELECT notification_type_id + FROM ' . NOTIFICATION_TYPES_TABLE . " + WHERE notification_type_name = '{$notification_type}'"; + $result = $this->db->sql_query($sql); + $new_type_id = (int) $this->db->sql_fetchfield('notification_type_id'); + $this->db->sql_freeresult($result); + + if ($new_type_id) + { + // New type name already exists, + // so we delete the old type and update the type id of existing entries. + $sql = 'SELECT notification_type_id + FROM ' . NOTIFICATION_TYPES_TABLE . " + WHERE notification_type_name = 'notification.type.{$notification_type}'"; + $result = $this->db->sql_query($sql); + $old_type_id = (int) $this->db->sql_fetchfield('notification_type_id'); + $this->db->sql_freeresult($result); + + $sql = 'UPDATE ' . NOTIFICATIONS_TABLE . ' + SET notification_type_id = ' . (int) $new_type_id . ' + WHERE notification_type_id = ' . (int) $old_type_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . NOTIFICATION_TYPES_TABLE . " + WHERE notification_type_name = 'notification.type.{$notification_type}'"; + $this->db->sql_query($sql); + } + else + { + // Otherwise we just update the name + $sql = 'UPDATE ' . NOTIFICATION_TYPES_TABLE . " + SET notification_type_name = '{$notification_type}' + WHERE notification_type_name = 'notification.type.{$notification_type}'"; + $this->db->sql_query($sql); + } + + $sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " + SET item_type = '{$notification_type}' + WHERE item_type = 'notification.type.{$notification_type}'"; + $this->db->sql_query($sql); + } + } +} diff --git a/phpbb/db/migration/data/v310/passwords.php b/phpbb/db/migration/data/v310/passwords.php new file mode 100644 index 0000000..adee441 --- /dev/null +++ b/phpbb/db/migration/data/v310/passwords.php @@ -0,0 +1,50 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class passwords extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_password' => array('VCHAR:255', ''), + ), + $this->table_prefix . 'forums' => array( + 'forum_password' => array('VCHAR:255', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_password' => array('VCHAR:40', ''), + ), + $this->table_prefix . 'forums' => array( + 'forum_password' => array('VCHAR:40', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/passwords_convert_p1.php b/phpbb/db/migration/data/v310/passwords_convert_p1.php new file mode 100644 index 0000000..295f2d2 --- /dev/null +++ b/phpbb/db/migration/data/v310/passwords_convert_p1.php @@ -0,0 +1,81 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class passwords_convert_p1 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\passwords_p2'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_passwords'))), + ); + } + + public function update_passwords($start) + { + // Nothing to do if user_pass_convert column doesn't exist + if (!$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_pass_convert')) + { + return; + } + + $start = (int) $start; + $limit = 1000; + $converted_users = 0; + + $sql = 'SELECT user_password, user_id + FROM ' . $this->table_prefix . 'users + WHERE user_pass_convert = 1 + ORDER BY user_id'; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + $update_users = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $converted_users++; + + $user_id = (int) $row['user_id']; + // Only prefix passwords without proper prefix + if (!isset($update_users[$user_id]) && !preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $row['user_password'])) + { + // Use $CP$ prefix for passwords that need to + // be converted and set pass convert to false. + $update_users[$user_id] = '$CP$' . $row['user_password']; + } + } + $this->db->sql_freeresult($result); + + foreach ($update_users as $user_id => $user_password) + { + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_password = '" . $this->db->sql_escape($user_password) . "' + WHERE user_id = $user_id"; + $this->sql_query($sql); + } + + if ($converted_users < $limit) + { + // There are no more users to be converted + return; + } + + // There are still more users to query, return the next start value + return $start + $limit; + } +} diff --git a/phpbb/db/migration/data/v310/passwords_convert_p2.php b/phpbb/db/migration/data/v310/passwords_convert_p2.php new file mode 100644 index 0000000..26a9918 --- /dev/null +++ b/phpbb/db/migration/data/v310/passwords_convert_p2.php @@ -0,0 +1,49 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class passwords_convert_p2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_pass_convert'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\passwords_convert_p1'); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_pass_convert', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_pass_convert' => array('BOOL', 0, 'after' => 'user_passchg'), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/passwords_p2.php b/phpbb/db/migration/data/v310/passwords_p2.php new file mode 100644 index 0000000..afc7ba2 --- /dev/null +++ b/phpbb/db/migration/data/v310/passwords_p2.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class passwords_p2 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\passwords'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_newpasswd' => array('VCHAR:255', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_newpasswd' => array('VCHAR:40', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/plupload.php b/phpbb/db/migration/data/v310/plupload.php new file mode 100644 index 0000000..69367f8 --- /dev/null +++ b/phpbb/db/migration/data/v310/plupload.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class plupload extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return isset($this->config['plupload_last_gc']) && + isset($this->config['plupload_salt']); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_data() + { + return array( + array('config.add', array('plupload_last_gc', 0)), + array('config.add', array('plupload_salt', unique_id())), + ); + } +} diff --git a/phpbb/db/migration/data/v310/postgres_fulltext_drop.php b/phpbb/db/migration/data/v310/postgres_fulltext_drop.php new file mode 100644 index 0000000..3457c19 --- /dev/null +++ b/phpbb/db/migration/data/v310/postgres_fulltext_drop.php @@ -0,0 +1,80 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class postgres_fulltext_drop extends \phpbb\db\migration\migration +{ + protected $indexes; + + public function effectively_installed() + { + // This migration is irrelevant for all non-PostgreSQL DBMSes. + if (strpos($this->db->get_sql_layer(), 'postgres') === false) + { + return true; + } + + $this->find_indexes_to_drop(); + return empty($this->indexes); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + ); + } + + public function update_schema() + { + if (empty($this->indexes)) + { + return array(); + } + + /* + * Drop FULLTEXT indexes related to PostgreSQL fulltext search. + * Doing so is equivalent to dropping the search index from the ACP. + * Possibly time-consuming recreation of the search index (i.e. + * FULLTEXT indexes) is left as a task to the admin to not + * unnecessarily stall the upgrade process. The new search index will + * then require about 40% less table space (also see PHPBB3-11040). + */ + return array( + 'drop_keys' => array( + $this->table_prefix . 'posts' => $this->indexes, + ), + ); + } + + public function find_indexes_to_drop() + { + if ($this->indexes !== null) + { + return $this->indexes; + } + + $this->indexes = array(); + $potential_keys = array('post_subject', 'post_text', 'post_content'); + foreach ($potential_keys as $key) + { + if ($this->db_tools->sql_index_exists($this->table_prefix . 'posts', $key)) + { + $this->indexes[] = $key; + } + } + + return $this->indexes; + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_aol.php b/phpbb/db/migration/data/v310/profilefield_aol.php new file mode 100644 index 0000000..65d4fe1 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_aol.php @@ -0,0 +1,55 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_aol extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_yahoo_cleanup', + ); + } + + protected $profilefield_name = 'phpbb_aol'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_aol', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_aol', + 'field_length' => '40', + 'field_minlen' => '5', + 'field_maxlen' => '255', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '.*', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_on_ml' => 0, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => '', + 'field_contact_url' => '', + ); + + protected $user_column_name = 'user_aim'; +} diff --git a/phpbb/db/migration/data/v310/profilefield_aol_cleanup.php b/phpbb/db/migration/data/v310/profilefield_aol_cleanup.php new file mode 100644 index 0000000..f884d83 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_aol_cleanup.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_aol_cleanup extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_aim'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_aol', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_aim', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_aim' => array('VCHAR_UNI', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_change_load_settings.php b/phpbb/db/migration/data/v310/profilefield_change_load_settings.php new file mode 100644 index 0000000..7cc4fd8 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_change_load_settings.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_change_load_settings extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_aol_cleanup', + ); + } + + public function update_data() + { + return array( + array('config.update', array('load_cpf_memberlist', '1')), + array('config.update', array('load_cpf_pm', '1')), + array('config.update', array('load_cpf_viewprofile', '1')), + array('config.update', array('load_cpf_viewtopic', '1')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_cleanup.php b/phpbb/db/migration/data/v310/profilefield_cleanup.php new file mode 100644 index 0000000..c44167d --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_cleanup.php @@ -0,0 +1,55 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_cleanup extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_occ') && + !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_interests'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_interests', + '\phpbb\db\migration\data\v310\profilefield_occupation', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_occ', + 'user_interests', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_occ' => array('MTEXT', ''), + 'user_interests' => array('MTEXT', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_contact_field.php b/phpbb/db/migration/data/v310/profilefield_contact_field.php new file mode 100644 index 0000000..02cd420 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_contact_field.php @@ -0,0 +1,55 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_contact_field extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'profile_fields', 'field_is_contact'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_on_memberlist', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_is_contact' => array('BOOL', 0), + 'field_contact_desc' => array('VCHAR', ''), + 'field_contact_url' => array('VCHAR', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_is_contact', + 'field_contact_desc', + 'field_contact_url', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_facebook.php b/phpbb/db/migration/data/v310/profilefield_facebook.php new file mode 100644 index 0000000..7324b89 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_facebook.php @@ -0,0 +1,61 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_facebook extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_contact_field', + '\phpbb\db\migration\data\v310\profilefield_show_novalue', + '\phpbb\db\migration\data\v310\profilefield_types', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'create_custom_field'))), + ); + } + + protected $profilefield_name = 'phpbb_facebook'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_facebook', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_facebook', + 'field_length' => '20', + 'field_minlen' => '5', + 'field_maxlen' => '50', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '[\w.]+', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => 'VIEW_FACEBOOK_PROFILE', + 'field_contact_url' => 'http://facebook.com/%s/', + ); +} diff --git a/phpbb/db/migration/data/v310/profilefield_field_validation_length.php b/phpbb/db/migration/data/v310/profilefield_field_validation_length.php new file mode 100644 index 0000000..c7d8b2d --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_field_validation_length.php @@ -0,0 +1,90 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_field_validation_length extends \phpbb\db\migration\migration +{ + protected $validation_options_old = array( + 'ALPHA_SPACERS' => '[\w_\+\. \-\[\]]+', + ); + + protected $validation_options_new = array( + 'ALPHA_SPACERS' => '[\w\x20_+\-\[\]]+', + ); + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\rc3', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_validation' => array('VCHAR_UNI:64', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_validation' => array('VCHAR_UNI:20', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_profile_fields_validation'))), + ); + } + + public function revert_data() + { + return array( + array('custom', array(array($this, 'revert_profile_fields_validation'))), + ); + } + + public function update_profile_fields_validation() + { + foreach ($this->validation_options_new as $validation_type => $regex) + { + $sql = 'UPDATE ' . $this->table_prefix . "profile_fields + SET field_validation = '" . $this->db->sql_escape($this->validation_options_new[$validation_type]) . "' + WHERE field_validation = '" . $this->db->sql_escape($this->validation_options_old[$validation_type]) . "'"; + $this->sql_query($sql); + } + } + + public function revert_profile_fields_validation() + { + foreach ($this->validation_options_new as $validation_type => $regex) + { + $sql = 'UPDATE ' . $this->table_prefix . "profile_fields + SET field_validation = '" . $this->db->sql_escape($this->validation_options_old[$validation_type]) . "' + WHERE field_validation = '" . $this->db->sql_escape($this->validation_options_new[$validation_type]) . "'"; + $this->sql_query($sql); + } + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_googleplus.php b/phpbb/db/migration/data/v310/profilefield_googleplus.php new file mode 100644 index 0000000..3b0963f --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_googleplus.php @@ -0,0 +1,61 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_googleplus extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_contact_field', + '\phpbb\db\migration\data\v310\profilefield_show_novalue', + '\phpbb\db\migration\data\v310\profilefield_types', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'create_custom_field'))), + ); + } + + protected $profilefield_name = 'phpbb_googleplus'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_googleplus', + 'field_type' => 'profilefields.type.googleplus', + 'field_ident' => 'phpbb_googleplus', + 'field_length' => '20', + 'field_minlen' => '3', + 'field_maxlen' => '255', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '[\w]+', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => 'VIEW_GOOGLEPLUS_PROFILE', + 'field_contact_url' => 'http://plus.google.com/%s', + ); +} diff --git a/phpbb/db/migration/data/v310/profilefield_icq.php b/phpbb/db/migration/data/v310/profilefield_icq.php new file mode 100644 index 0000000..e61653f --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_icq.php @@ -0,0 +1,54 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_icq extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_contact_field', + ); + } + + protected $profilefield_name = 'phpbb_icq'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_icq', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_icq', + 'field_length' => '20', + 'field_minlen' => '3', + 'field_maxlen' => '15', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '[0-9]+', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => 'SEND_ICQ_MESSAGE', + 'field_contact_url' => 'https://www.icq.com/people/%s/', + ); + + protected $user_column_name = 'user_icq'; +} diff --git a/phpbb/db/migration/data/v310/profilefield_icq_cleanup.php b/phpbb/db/migration/data/v310/profilefield_icq_cleanup.php new file mode 100644 index 0000000..516c690 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_icq_cleanup.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_icq_cleanup extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_icq'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_icq', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_icq', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_icq' => array('VCHAR:20', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_interests.php b/phpbb/db/migration/data/v310/profilefield_interests.php new file mode 100644 index 0000000..33a5ba1 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_interests.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_interests extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_types', + '\phpbb\db\migration\data\v310\profilefield_show_novalue', + ); + } + + protected $profilefield_name = 'phpbb_interests'; + + protected $profilefield_database_type = array('MTEXT', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_interests', + 'field_type' => 'profilefields.type.text', + 'field_ident' => 'phpbb_interests', + 'field_length' => '3|30', + 'field_minlen' => '2', + 'field_maxlen' => '500', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '.*', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 0, + 'field_show_on_vt' => 0, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + ); + + protected $user_column_name = 'user_interests'; +} diff --git a/phpbb/db/migration/data/v310/profilefield_location.php b/phpbb/db/migration/data/v310/profilefield_location.php new file mode 100644 index 0000000..2d27c09 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_location.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_location extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_types', + '\phpbb\db\migration\data\v310\profilefield_on_memberlist', + ); + } + + protected $profilefield_name = 'phpbb_location'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_location', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_location', + 'field_length' => '20', + 'field_minlen' => '2', + 'field_maxlen' => '100', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '.*', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + ); + + protected $user_column_name = 'user_from'; +} diff --git a/phpbb/db/migration/data/v310/profilefield_location_cleanup.php b/phpbb/db/migration/data/v310/profilefield_location_cleanup.php new file mode 100644 index 0000000..b824e34 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_location_cleanup.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_location_cleanup extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_from'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_location', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_from', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_from' => array('VCHAR_UNI:100', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_occupation.php b/phpbb/db/migration/data/v310/profilefield_occupation.php new file mode 100644 index 0000000..75df2bc --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_occupation.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_occupation extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_interests', + ); + } + + protected $profilefield_name = 'phpbb_occupation'; + + protected $profilefield_database_type = array('MTEXT', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_occupation', + 'field_type' => 'profilefields.type.text', + 'field_ident' => 'phpbb_occupation', + 'field_length' => '3|30', + 'field_minlen' => '2', + 'field_maxlen' => '500', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '.*', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 0, + 'field_show_on_vt' => 0, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + ); + + protected $user_column_name = 'user_occ'; +} diff --git a/phpbb/db/migration/data/v310/profilefield_on_memberlist.php b/phpbb/db/migration/data/v310/profilefield_on_memberlist.php new file mode 100644 index 0000000..7ce5de0 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_on_memberlist.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_on_memberlist extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'profile_fields', 'field_show_on_ml'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_cleanup', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_ml' => array('BOOL', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_on_ml', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_show_novalue.php b/phpbb/db/migration/data/v310/profilefield_show_novalue.php new file mode 100644 index 0000000..5fc88b6 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_show_novalue.php @@ -0,0 +1,49 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_show_novalue extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'profile_fields', 'field_show_novalue'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\profilefield_types'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_novalue' => array('BOOL', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_show_novalue', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_skype.php b/phpbb/db/migration/data/v310/profilefield_skype.php new file mode 100644 index 0000000..0dbe904 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_skype.php @@ -0,0 +1,61 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_skype extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_contact_field', + '\phpbb\db\migration\data\v310\profilefield_show_novalue', + '\phpbb\db\migration\data\v310\profilefield_types', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'create_custom_field'))), + ); + } + + protected $profilefield_name = 'phpbb_skype'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_skype', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_skype', + 'field_length' => '20', + 'field_minlen' => '6', + 'field_maxlen' => '32', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '[a-zA-Z][\w\.,\-_]+', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => 'VIEW_SKYPE_PROFILE', + 'field_contact_url' => 'skype:%s?userinfo', + ); +} diff --git a/phpbb/db/migration/data/v310/profilefield_twitter.php b/phpbb/db/migration/data/v310/profilefield_twitter.php new file mode 100644 index 0000000..850e096 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_twitter.php @@ -0,0 +1,61 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_twitter extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_contact_field', + '\phpbb\db\migration\data\v310\profilefield_show_novalue', + '\phpbb\db\migration\data\v310\profilefield_types', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'create_custom_field'))), + ); + } + + protected $profilefield_name = 'phpbb_twitter'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_twitter', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_twitter', + 'field_length' => '20', + 'field_minlen' => '1', + 'field_maxlen' => '15', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '[\w_]+', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => 'VIEW_TWITTER_PROFILE', + 'field_contact_url' => 'http://twitter.com/%s', + ); +} diff --git a/phpbb/db/migration/data/v310/profilefield_types.php b/phpbb/db/migration/data/v310/profilefield_types.php new file mode 100644 index 0000000..5045eb8 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_types.php @@ -0,0 +1,110 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_types extends \phpbb\db\migration\migration +{ + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\alpha2', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'profile_fields' => array( + 'field_type' => array('VCHAR:100', ''), + ), + $this->table_prefix . 'profile_fields_lang' => array( + 'field_type' => array('VCHAR:100', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_profile_fields_type'))), + array('custom', array(array($this, 'update_profile_fields_lang_type'))), + ); + } + + public function update_profile_fields_type() + { + // Update profile field types + $sql = 'SELECT field_type + FROM ' . $this->table_prefix . 'profile_fields + GROUP BY field_type'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . $this->table_prefix . "profile_fields + SET field_type = '" . $this->db->sql_escape($this->convert_phpbb30_field_type($row['field_type'])) . "' + WHERE field_type = '" . $this->db->sql_escape($row['field_type']) . "'"; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } + + public function update_profile_fields_lang_type() + { + // Update profile field language types + $sql = 'SELECT field_type + FROM ' . $this->table_prefix . 'profile_fields_lang + GROUP BY field_type'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . $this->table_prefix . "profile_fields_lang + SET field_type = '" . $this->db->sql_escape($this->convert_phpbb30_field_type($row['field_type'])) . "' + WHERE field_type = '" . $this->db->sql_escape($row['field_type']) . "'"; + $this->sql_query($sql); + } + $this->db->sql_freeresult($result); + } + + /** + * Determine the new field type for a given phpBB 3.0 field type + * + * @param $field_type string Field type in 3.0 + * @return string Field new type which is used since 3.1 + */ + public function convert_phpbb30_field_type($field_type) + { + switch ($field_type) + { + case FIELD_INT: + return 'profilefields.type.int'; + case FIELD_STRING: + return 'profilefields.type.string'; + case FIELD_TEXT: + return 'profilefields.type.text'; + case FIELD_BOOL: + return 'profilefields.type.bool'; + case FIELD_DROPDOWN: + return 'profilefields.type.dropdown'; + case FIELD_DATE: + return 'profilefields.type.date'; + default: + return $field_type; + } + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_website.php b/phpbb/db/migration/data/v310/profilefield_website.php new file mode 100644 index 0000000..e1e10f0 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_website.php @@ -0,0 +1,56 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_website extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_on_memberlist', + '\phpbb\db\migration\data\v310\profilefield_icq_cleanup', + ); + } + + protected $profilefield_name = 'phpbb_website'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_website', + 'field_type' => 'profilefields.type.url', + 'field_ident' => 'phpbb_website', + 'field_length' => '40', + 'field_minlen' => '12', + 'field_maxlen' => '255', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_on_ml' => 1, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => 'VISIT_WEBSITE', + 'field_contact_url' => '%s', + ); + + protected $user_column_name = 'user_website'; +} diff --git a/phpbb/db/migration/data/v310/profilefield_website_cleanup.php b/phpbb/db/migration/data/v310/profilefield_website_cleanup.php new file mode 100644 index 0000000..94442f0 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_website_cleanup.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_website_cleanup extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_website'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_website', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_website', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_website' => array('VCHAR_UNI:200', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_wlm.php b/phpbb/db/migration/data/v310/profilefield_wlm.php new file mode 100644 index 0000000..2cd333f --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_wlm.php @@ -0,0 +1,55 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_wlm extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_website_cleanup', + ); + } + + protected $profilefield_name = 'phpbb_wlm'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_wlm', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_wlm', + 'field_length' => '40', + 'field_minlen' => '5', + 'field_maxlen' => '255', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '.*', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_on_ml' => 0, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => '', + 'field_contact_url' => '', + ); + + protected $user_column_name = 'user_msnm'; +} diff --git a/phpbb/db/migration/data/v310/profilefield_wlm_cleanup.php b/phpbb/db/migration/data/v310/profilefield_wlm_cleanup.php new file mode 100644 index 0000000..7ef9e44 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_wlm_cleanup.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_wlm_cleanup extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_msnm'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_wlm', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_msnm', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_msnm' => array('VCHAR_UNI', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_yahoo.php b/phpbb/db/migration/data/v310/profilefield_yahoo.php new file mode 100644 index 0000000..e269f88 --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_yahoo.php @@ -0,0 +1,55 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_yahoo extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_wlm_cleanup', + ); + } + + protected $profilefield_name = 'phpbb_yahoo'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_yahoo', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_yahoo', + 'field_length' => '40', + 'field_minlen' => '5', + 'field_maxlen' => '255', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '.*', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_on_ml' => 0, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => 'SEND_YIM_MESSAGE', + 'field_contact_url' => 'http://edit.yahoo.com/config/send_webmesg?.target=%s&.src=pg', + ); + + protected $user_column_name = 'user_yim'; +} diff --git a/phpbb/db/migration/data/v310/profilefield_yahoo_cleanup.php b/phpbb/db/migration/data/v310/profilefield_yahoo_cleanup.php new file mode 100644 index 0000000..bd724ff --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_yahoo_cleanup.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_yahoo_cleanup extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_yim'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_yahoo', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_yim', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_yim' => array('VCHAR_UNI', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/profilefield_youtube.php b/phpbb/db/migration/data/v310/profilefield_youtube.php new file mode 100644 index 0000000..40a569d --- /dev/null +++ b/phpbb/db/migration/data/v310/profilefield_youtube.php @@ -0,0 +1,61 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class profilefield_youtube extends \phpbb\db\migration\profilefield_base_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\profilefield_contact_field', + '\phpbb\db\migration\data\v310\profilefield_show_novalue', + '\phpbb\db\migration\data\v310\profilefield_types', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'create_custom_field'))), + ); + } + + protected $profilefield_name = 'phpbb_youtube'; + + protected $profilefield_database_type = array('VCHAR', ''); + + protected $profilefield_data = array( + 'field_name' => 'phpbb_youtube', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_youtube', + 'field_length' => '20', + 'field_minlen' => '3', + 'field_maxlen' => '60', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '[a-zA-Z][\w\.,\-_]+', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => 'VIEW_YOUTUBE_CHANNEL', + 'field_contact_url' => 'http://youtube.com/user/%s', + ); +} diff --git a/phpbb/db/migration/data/v310/prune_shadow_topics.php b/phpbb/db/migration/data/v310/prune_shadow_topics.php new file mode 100644 index 0000000..f6d27d3 --- /dev/null +++ b/phpbb/db/migration/data/v310/prune_shadow_topics.php @@ -0,0 +1,50 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class prune_shadow_topics extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'enable_shadow_prune' => array('BOOL', 0), + 'prune_shadow_days' => array('UINT', 7), + 'prune_shadow_freq' => array('UINT', 1), + 'prune_shadow_next' => array('INT:11', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array( + 'enable_shadow_prune', + 'prune_shadow_days', + 'prune_shadow_freq', + 'prune_shadow_next', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/rc1.php b/phpbb/db/migration/data/v310/rc1.php new file mode 100644 index 0000000..751208c --- /dev/null +++ b/phpbb/db/migration/data/v310/rc1.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\beta4', + '\phpbb\db\migration\data\v310\contact_admin_acp_module', + '\phpbb\db\migration\data\v310\contact_admin_form', + '\phpbb\db\migration\data\v310\passwords_convert_p2', + '\phpbb\db\migration\data\v310\profilefield_facebook', + '\phpbb\db\migration\data\v310\profilefield_googleplus', + '\phpbb\db\migration\data\v310\profilefield_skype', + '\phpbb\db\migration\data\v310\profilefield_twitter', + '\phpbb\db\migration\data\v310\profilefield_youtube', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/rc2.php b/phpbb/db/migration/data/v310/rc2.php new file mode 100644 index 0000000..5cd0393 --- /dev/null +++ b/phpbb/db/migration/data/v310/rc2.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-RC2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/rc3.php b/phpbb/db/migration/data/v310/rc3.php new file mode 100644 index 0000000..9fb483e --- /dev/null +++ b/phpbb/db/migration/data/v310/rc3.php @@ -0,0 +1,40 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class rc3 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-RC3', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\rc2', + '\phpbb\db\migration\data\v310\captcha_plugins', + '\phpbb\db\migration\data\v310\rename_too_long_indexes', + '\phpbb\db\migration\data\v310\search_type', + '\phpbb\db\migration\data\v310\topic_sort_username', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-RC3')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/rc4.php b/phpbb/db/migration/data/v310/rc4.php new file mode 100644 index 0000000..0d756c7 --- /dev/null +++ b/phpbb/db/migration/data/v310/rc4.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class rc4 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-RC4', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\rc3', + '\phpbb\db\migration\data\v310\notifications_use_full_name', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-RC4')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/rc5.php b/phpbb/db/migration/data/v310/rc5.php new file mode 100644 index 0000000..d92537d --- /dev/null +++ b/phpbb/db/migration/data/v310/rc5.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class rc5 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-RC5', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\rc4', + '\phpbb\db\migration\data\v310\profilefield_field_validation_length', + '\phpbb\db\migration\data\v310\remove_acp_styles_cache', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-RC5')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/rc6.php b/phpbb/db/migration/data/v310/rc6.php new file mode 100644 index 0000000..1df502a --- /dev/null +++ b/phpbb/db/migration/data/v310/rc6.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class rc6 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.0-RC6', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\rc5', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.0-RC6')), + ); + } +} diff --git a/phpbb/db/migration/data/v310/remove_acp_styles_cache.php b/phpbb/db/migration/data/v310/remove_acp_styles_cache.php new file mode 100644 index 0000000..7b84539 --- /dev/null +++ b/phpbb/db/migration/data/v310/remove_acp_styles_cache.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class remove_acp_styles_cache extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + $sql = 'SELECT module_id + FROM ' . MODULES_TABLE . " + WHERE module_class = 'acp' + AND module_langname = 'ACP_STYLES_CACHE'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + return !$module_id; + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\rc4'); + } + + public function update_data() + { + return array( + array('module.remove', array( + 'acp', + 'ACP_STYLE_MANAGEMENT', + array( + 'module_basename' => 'acp_styles', + 'module_langname' => 'ACP_STYLES_CACHE', + 'module_mode' => 'cache', + 'module_auth' => 'acl_a_styles', + ), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/rename_too_long_indexes.php b/phpbb/db/migration/data/v310/rename_too_long_indexes.php new file mode 100644 index 0000000..8d2a15d --- /dev/null +++ b/phpbb/db/migration/data/v310/rename_too_long_indexes.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class rename_too_long_indexes extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_0'); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'search_wordmatch' => array( + 'unq_mtch', + ), + ), + 'add_unique_index' => array( + $this->table_prefix . 'search_wordmatch' => array( + 'un_mtch' => array('word_id', 'post_id', 'title_match'), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/reported_posts_display.php b/phpbb/db/migration/data/v310/reported_posts_display.php new file mode 100644 index 0000000..575a65d --- /dev/null +++ b/phpbb/db/migration/data/v310/reported_posts_display.php @@ -0,0 +1,53 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class reported_posts_display extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'reports', 'reported_post_enable_bbcode'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'reports' => array( + 'reported_post_enable_bbcode' => array('BOOL', 1), + 'reported_post_enable_smilies' => array('BOOL', 1), + 'reported_post_enable_magic_url' => array('BOOL', 1), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'reports' => array( + 'reported_post_enable_bbcode', + 'reported_post_enable_smilies', + 'reported_post_enable_magic_url', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/reset_missing_captcha_plugin.php b/phpbb/db/migration/data/v310/reset_missing_captcha_plugin.php new file mode 100644 index 0000000..8211457 --- /dev/null +++ b/phpbb/db/migration/data/v310/reset_missing_captcha_plugin.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +/** +* Class captcha_plugin +* +* Reset the captcha setting to the default plugin if the defined 'captcha_plugin' is missing. +*/ +class reset_missing_captcha_plugin extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_data() + { + return array( + array('if', array( + (is_dir($this->phpbb_root_path . 'includes/captcha/plugins/') && + !is_file($this->phpbb_root_path . "includes/captcha/plugins/{$this->config['captcha_plugin']}_plugin." . $this->php_ext)), + array('config.update', array('captcha_plugin', 'phpbb_captcha_nogd')), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/search_type.php b/phpbb/db/migration/data/v310/search_type.php new file mode 100644 index 0000000..f89456a --- /dev/null +++ b/phpbb/db/migration/data/v310/search_type.php @@ -0,0 +1,34 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class search_type extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + ); + } + + public function update_data() + { + return array( + array('if', array( + (is_file($this->phpbb_root_path . 'phpbb/search/' . $this->config['search_type'] . $this->php_ext)), + array('config.update', array('search_type', '\\phpbb\\search\\' . $this->config['search_type'])), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/signature_module_auth.php b/phpbb/db/migration/data/v310/signature_module_auth.php new file mode 100644 index 0000000..e50f5e5 --- /dev/null +++ b/phpbb/db/migration/data/v310/signature_module_auth.php @@ -0,0 +1,57 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class signature_module_auth extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + $sql = 'SELECT module_auth + FROM ' . MODULES_TABLE . " + WHERE module_class = 'ucp' + AND module_basename = 'ucp_profile' + AND module_mode = 'signature'"; + $result = $this->db->sql_query($sql); + $module_auth = $this->db->sql_fetchfield('module_auth'); + $this->db->sql_freeresult($result); + + return $module_auth === 'acl_u_sig' || $module_auth === false; + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_data() + { + return array( + array('custom', array( + array($this, 'update_signature_module_auth'), + ), + ), + ); + } + + public function update_signature_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'acl_u_sig' + WHERE module_class = 'ucp' + AND module_basename = 'ucp_profile' + AND module_mode = 'signature' + AND module_auth = ''"; + $this->db->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v310/soft_delete_mod_convert.php b/phpbb/db/migration/data/v310/soft_delete_mod_convert.php new file mode 100644 index 0000000..85b90da --- /dev/null +++ b/phpbb/db/migration/data/v310/soft_delete_mod_convert.php @@ -0,0 +1,127 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +use phpbb\db\migration\container_aware_migration; + +/** + * Migration to convert the Soft Delete MOD for 3.0 + * + * https://www.phpbb.com/customise/db/mod/soft_delete/ + */ +class soft_delete_mod_convert extends container_aware_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\alpha3', + ); + } + + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_deleted'); + } + + public function update_data() + { + return array( + array('permission.remove', array('m_harddelete', true)), + array('permission.remove', array('m_harddelete', false)), + + array('custom', array(array($this, 'convert_posts'))), + array('custom', array(array($this, 'convert_topics'))), + ); + } + + public function convert_posts($start) + { + $content_visibility = $this->get_content_visibility(); + + $limit = 250; + $i = 0; + + $sql = 'SELECT p.*, t.topic_first_post_id, t.topic_last_post_id + FROM ' . $this->table_prefix . 'posts p, ' . $this->table_prefix . 'topics t + WHERE p.post_deleted > 0 + AND t.topic_id = p.topic_id'; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $content_visibility->set_post_visibility( + ITEM_DELETED, + $row['post_id'], + $row['topic_id'], + $row['forum_id'], + $row['post_deleted'], + $row['post_deleted_time'], + '', + ($row['post_id'] == $row['topic_first_post_id']) ? true : false, + ($row['post_id'] == $row['topic_last_post_id']) ? true : false + ); + + $i++; + } + + $this->db->sql_freeresult($result); + + if ($i == $limit) + { + return $start + $i; + } + } + + public function convert_topics($start) + { + $content_visibility = $this->get_content_visibility(); + + $limit = 100; + $i = 0; + + $sql = 'SELECT * + FROM ' . $this->table_prefix . 'topics + WHERE topic_deleted > 0'; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $content_visibility->set_topic_visibility( + ITEM_DELETED, + $row['topic_id'], + $row['forum_id'], + $row['topic_deleted'], + $row['topic_deleted_time'], + '' + ); + + $i++; + } + + $this->db->sql_freeresult($result); + + if ($i == $limit) + { + return $start + $i; + } + } + + /** + * @return \phpbb\content_visibility + */ + protected function get_content_visibility() + { + return $this->container->get('content.visibility'); + } +} diff --git a/phpbb/db/migration/data/v310/soft_delete_mod_convert2.php b/phpbb/db/migration/data/v310/soft_delete_mod_convert2.php new file mode 100644 index 0000000..246a267 --- /dev/null +++ b/phpbb/db/migration/data/v310/soft_delete_mod_convert2.php @@ -0,0 +1,66 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +/** + * Migration to convert the Soft Delete MOD for 3.0 + * + * https://www.phpbb.com/customise/db/mod/soft_delete/ + */ +class soft_delete_mod_convert2 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\soft_delete_mod_convert', + ); + } + + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_deleted'); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array('forum_deleted_topic_count', 'forum_deleted_reply_count'), + $this->table_prefix . 'posts' => array('post_deleted', 'post_deleted_time'), + $this->table_prefix . 'topics' => array('topic_deleted', 'topic_deleted_time', 'topic_deleted_reply_count'), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_deleted_topic_count' => array('UINT', 0), + 'forum_deleted_reply_count' => array('UINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_deleted' => array('UINT', 0), + 'post_deleted_time' => array('TIMESTAMP', 0), + ), + $this->table_prefix . 'topics' => array( + 'topic_deleted' => array('UINT', 0), + 'topic_deleted_time' => array('TIMESTAMP', 0), + 'topic_deleted_reply_count' => array('UINT', 0), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/softdelete_mcp_modules.php b/phpbb/db/migration/data/v310/softdelete_mcp_modules.php new file mode 100644 index 0000000..90dab99 --- /dev/null +++ b/phpbb/db/migration/data/v310/softdelete_mcp_modules.php @@ -0,0 +1,65 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class softdelete_mcp_modules extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + $sql = 'SELECT module_id + FROM ' . MODULES_TABLE . " + WHERE module_class = 'mcp' + AND module_basename = 'mcp_queue' + AND module_mode = 'deleted_topics'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + return $module_id !== false; + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + '\phpbb\db\migration\data\v310\softdelete_p2', + ); + } + + public function update_data() + { + return array( + array('module.add', array( + 'mcp', + 'MCP_QUEUE', + array( + 'module_basename' => 'mcp_queue', + 'module_langname' => 'MCP_QUEUE_DELETED_TOPICS', + 'module_mode' => 'deleted_topics', + 'module_auth' => 'aclf_m_approve', + ), + )), + array('module.add', array( + 'mcp', + 'MCP_QUEUE', + array( + 'module_basename' => 'mcp_queue', + 'module_langname' => 'MCP_QUEUE_DELETED_POSTS', + 'module_mode' => 'deleted_posts', + 'module_auth' => 'aclf_m_approve', + ), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v310/softdelete_p1.php b/phpbb/db/migration/data/v310/softdelete_p1.php new file mode 100644 index 0000000..b1e7486 --- /dev/null +++ b/phpbb/db/migration/data/v310/softdelete_p1.php @@ -0,0 +1,211 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class softdelete_p1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_visibility'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_posts_approved' => array('UINT', 0), + 'forum_posts_unapproved' => array('UINT', 0), + 'forum_posts_softdeleted' => array('UINT', 0), + 'forum_topics_approved' => array('UINT', 0), + 'forum_topics_unapproved' => array('UINT', 0), + 'forum_topics_softdeleted' => array('UINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_visibility' => array('TINT:3', 0), + 'post_delete_time' => array('TIMESTAMP', 0), + 'post_delete_reason' => array('STEXT_UNI', ''), + 'post_delete_user' => array('UINT', 0), + ), + $this->table_prefix . 'topics' => array( + 'topic_visibility' => array('TINT:3', 0), + 'topic_delete_time' => array('TIMESTAMP', 0), + 'topic_delete_reason' => array('STEXT_UNI', ''), + 'topic_delete_user' => array('UINT', 0), + 'topic_posts_approved' => array('UINT', 0), + 'topic_posts_unapproved' => array('UINT', 0), + 'topic_posts_softdeleted' => array('UINT', 0), + ), + ), + 'add_index' => array( + $this->table_prefix . 'posts' => array( + 'post_visibility' => array('post_visibility'), + ), + $this->table_prefix . 'topics' => array( + 'topic_visibility' => array('topic_visibility'), + 'forum_vis_last' => array('forum_id', 'topic_visibility', 'topic_last_post_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_posts_approved', + 'forum_posts_unapproved', + 'forum_posts_softdeleted', + 'forum_topics_approved', + 'forum_topics_unapproved', + 'forum_topics_softdeleted', + ), + $this->table_prefix . 'posts' => array( + 'post_visibility', + 'post_delete_time', + 'post_delete_reason', + 'post_delete_user', + ), + $this->table_prefix . 'topics' => array( + 'topic_visibility', + 'topic_delete_time', + 'topic_delete_reason', + 'topic_delete_user', + 'topic_posts_approved', + 'topic_posts_unapproved', + 'topic_posts_softdeleted', + ), + ), + 'drop_keys' => array( + $this->table_prefix . 'posts' => array('post_visibility'), + $this->table_prefix . 'topics' => array('topic_visibility', 'forum_vis_last'), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_post_visibility'))), + array('custom', array(array($this, 'update_topic_visibility'))), + array('custom', array(array($this, 'update_topics_post_counts'))), + array('custom', array(array($this, 'update_forums_topic_and_post_counts'))), + + array('permission.add', array('f_softdelete', false)), + array('permission.add', array('m_softdelete', false)), + ); + } + + public function update_post_visibility() + { + $sql = 'UPDATE ' . $this->table_prefix . 'posts + SET post_visibility = post_approved'; + $this->sql_query($sql); + } + + public function update_topic_visibility() + { + $sql = 'UPDATE ' . $this->table_prefix . 'topics + SET topic_visibility = topic_approved'; + $this->sql_query($sql); + } + + public function update_topics_post_counts() + { + /* + * Using sql_case here to avoid "BIGINT UNSIGNED value is out of range" errors. + * As we update all topics in 2 queries, one broken topic would stop the conversion + * for all topics and the surpressed error will cause the admin to not even notice it. + */ + $sql = 'UPDATE ' . $this->table_prefix . 'topics + SET topic_posts_approved = topic_replies + 1, + topic_posts_unapproved = ' . $this->db->sql_case('topic_replies_real > topic_replies', 'topic_replies_real - topic_replies', '0') . ' + WHERE topic_visibility = ' . ITEM_APPROVED; + $this->sql_query($sql); + + $sql = 'UPDATE ' . $this->table_prefix . 'topics + SET topic_posts_approved = 0, + topic_posts_unapproved = (' . $this->db->sql_case('topic_replies_real > topic_replies', 'topic_replies_real - topic_replies', '0') . ') + 1 + WHERE topic_visibility = ' . ITEM_UNAPPROVED; + $this->sql_query($sql); + } + + public function update_forums_topic_and_post_counts($start) + { + $start = (int) $start; + $limit = 10; + $converted_forums = 0; + + if (!$start) + { + // Preserve the forum_posts value for link forums as it represents redirects. + $sql = 'UPDATE ' . $this->table_prefix . 'forums + SET forum_posts_approved = forum_posts + WHERE forum_type = ' . FORUM_LINK; + $this->db->sql_query($sql); + } + + $sql = 'SELECT forum_id, topic_visibility, COUNT(topic_id) AS sum_topics, SUM(topic_posts_approved) AS sum_posts_approved, SUM(topic_posts_unapproved) AS sum_posts_unapproved + FROM ' . $this->table_prefix . 'topics + GROUP BY forum_id, topic_visibility + ORDER BY forum_id, topic_visibility'; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + $update_forums = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $converted_forums++; + + $forum_id = (int) $row['forum_id']; + if (!isset($update_forums[$forum_id])) + { + $update_forums[$forum_id] = array( + 'forum_posts_approved' => 0, + 'forum_posts_unapproved' => 0, + 'forum_topics_approved' => 0, + 'forum_topics_unapproved' => 0, + ); + } + + $update_forums[$forum_id]['forum_posts_approved'] += (int) $row['sum_posts_approved']; + $update_forums[$forum_id]['forum_posts_unapproved'] += (int) $row['sum_posts_unapproved']; + + $update_forums[$forum_id][(($row['topic_visibility'] == ITEM_APPROVED) ? 'forum_topics_approved' : 'forum_topics_unapproved')] += (int) $row['sum_topics']; + } + $this->db->sql_freeresult($result); + + foreach ($update_forums as $forum_id => $forum_data) + { + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $forum_data) . ' + WHERE forum_id = ' . $forum_id; + $this->sql_query($sql); + } + + if ($converted_forums < $limit) + { + // There are no more topics, we are done + return; + } + + // There are still more topics to query, return the next start value + return $start + $limit; + } +} diff --git a/phpbb/db/migration/data/v310/softdelete_p2.php b/phpbb/db/migration/data/v310/softdelete_p2.php new file mode 100644 index 0000000..849a996 --- /dev/null +++ b/phpbb/db/migration/data/v310/softdelete_p2.php @@ -0,0 +1,78 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class softdelete_p2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'posts', 'post_approved'); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\dev', + '\phpbb\db\migration\data\v310\softdelete_p1', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'forums' => array('forum_posts', 'forum_topics', 'forum_topics_real'), + $this->table_prefix . 'posts' => array('post_approved'), + $this->table_prefix . 'topics' => array('topic_approved', 'topic_replies', 'topic_replies_real'), + ), + 'drop_keys' => array( + $this->table_prefix . 'posts' => array('post_approved'), + $this->table_prefix . 'topics' => array( + 'forum_appr_last', + 'topic_approved', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_posts' => array('UINT', 0), + 'forum_topics' => array('UINT', 0), + 'forum_topics_real' => array('UINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_approved' => array('BOOL', 1), + ), + $this->table_prefix . 'topics' => array( + 'topic_approved' => array('BOOL', 1), + 'topic_replies' => array('UINT', 0), + 'topic_replies_real' => array('UINT', 0), + ), + ), + 'add_index' => array( + $this->table_prefix . 'posts' => array( + 'post_approved' => array('post_approved'), + ), + $this->table_prefix . 'topics' => array( + 'forum_appr_last' => array('forum_id', 'topic_approved', 'topic_last_post_id'), + 'topic_approved' => array('topic_approved'), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/style_update_p1.php b/phpbb/db/migration/data/v310/style_update_p1.php new file mode 100644 index 0000000..a7e30a9 --- /dev/null +++ b/phpbb/db/migration/data/v310/style_update_p1.php @@ -0,0 +1,191 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class style_update_p1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'styles' => array( + 'style_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'style_parent_id' => array('UINT', 0), + 'style_parent_tree' => array('TEXT', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'styles' => array( + 'style_path', + 'bbcode_bitfield', + 'style_parent_id', + 'style_parent_tree', + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'styles_update'))), + ); + } + + public function styles_update() + { + // Get list of valid 3.1 styles + $available_styles = array('prosilver'); + + $iterator = new \DirectoryIterator($this->phpbb_root_path . 'styles'); + $skip_dirs = array('.', '..', 'prosilver'); + foreach ($iterator as $fileinfo) + { + if ($fileinfo->isDir() && !in_array($fileinfo->getFilename(), $skip_dirs) && file_exists($fileinfo->getPathname() . '/style.cfg')) + { + $style_cfg = parse_cfg_file($fileinfo->getPathname() . '/style.cfg'); + if (isset($style_cfg['phpbb_version']) && version_compare($style_cfg['phpbb_version'], '3.1.0-dev', '>=')) + { + // 3.1 style + $available_styles[] = $fileinfo->getFilename(); + } + } + } + + // Get all installed styles + if ($this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset')) + { + $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id, i.imageset_path + FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . 'styles_theme c, ' . $this->table_prefix . "styles_imageset i + WHERE t.template_id = s.template_id + AND c.theme_id = s.theme_id + AND i.imageset_id = s.imageset_id"; + } + else + { + $sql = 'SELECT s.style_id, t.template_path, t.template_id, t.bbcode_bitfield, t.template_inherits_id, t.template_inherit_path, c.theme_path, c.theme_id + FROM ' . STYLES_TABLE . ' s, ' . $this->table_prefix . 'styles_template t, ' . $this->table_prefix . "styles_theme c + WHERE t.template_id = s.template_id + AND c.theme_id = s.theme_id"; + } + $result = $this->db->sql_query($sql); + + $styles = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $styles[] = $row; + } + $this->db->sql_freeresult($result); + + // Decide which styles to keep, all others will be deleted + $valid_styles = array(); + foreach ($styles as $style_row) + { + if ( + // Delete styles with parent style (not supported yet) + $style_row['template_inherits_id'] == 0 && + // Check if components match + $style_row['template_path'] == $style_row['theme_path'] && (!isset($style_row['imageset_path']) || $style_row['template_path'] == $style_row['imageset_path']) && + // Check if components are valid + in_array($style_row['template_path'], $available_styles) + ) + { + // Valid style. Keep it + $sql_ary = array( + 'style_path' => $style_row['template_path'], + 'bbcode_bitfield' => $style_row['bbcode_bitfield'], + 'style_parent_id' => 0, + 'style_parent_tree' => '', + ); + $this->sql_query('UPDATE ' . STYLES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE style_id = ' . $style_row['style_id']); + $valid_styles[] = (int) $style_row['style_id']; + } + } + + // Remove old entries from styles table + if (!count($valid_styles)) + { + // No valid styles: remove everything and add prosilver + $this->sql_query('DELETE FROM ' . STYLES_TABLE); + + $sql_ary = array( + 'style_name' => 'prosilver', + 'style_copyright' => '© phpBB Limited', + 'style_active' => 1, + 'style_path' => 'prosilver', + 'bbcode_bitfield' => 'lNg=', + 'style_parent_id' => 0, + 'style_parent_tree' => '', + + // Will be removed in the next step + 'imageset_id' => 0, + 'template_id' => 0, + 'theme_id' => 0, + ); + + $sql = 'INSERT INTO ' . STYLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->sql_query($sql); + + $sql = 'SELECT style_id + FROM ' . STYLES_TABLE . " + WHERE style_name = 'prosilver'"; + $result = $this->sql_query($sql); + $default_style = (int) $this->db->sql_fetchfield('style_id'); + $this->db->sql_freeresult($result); + + $this->config->set('default_style', $default_style); + + $sql = 'UPDATE ' . USERS_TABLE . ' SET user_style = ' . (int) $default_style; + $this->sql_query($sql); + } + else + { + // There are valid styles in styles table. Remove styles that are outdated + $this->sql_query('DELETE FROM ' . STYLES_TABLE . ' + WHERE ' . $this->db->sql_in_set('style_id', $valid_styles, true)); + + // Change default style + if (!in_array($this->config['default_style'], $valid_styles)) + { + $this->sql_query('UPDATE ' . CONFIG_TABLE . " + SET config_value = '" . $valid_styles[0] . "' + WHERE config_name = 'default_style'"); + } + + // Reset styles for users + $this->sql_query('UPDATE ' . USERS_TABLE . " + SET user_style = '" . (int) $valid_styles[0] . "' + WHERE " . $this->db->sql_in_set('user_style', $valid_styles, true)); + } + } +} diff --git a/phpbb/db/migration/data/v310/style_update_p2.php b/phpbb/db/migration/data/v310/style_update_p2.php new file mode 100644 index 0000000..52c8ffb --- /dev/null +++ b/phpbb/db/migration/data/v310/style_update_p2.php @@ -0,0 +1,151 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class style_update_p2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_table_exists($this->table_prefix . 'styles_imageset'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\style_update_p1'); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'styles' => array( + 'imageset_id', + 'template_id', + 'theme_id', + ), + ), + + 'drop_columns' => array( + $this->table_prefix . 'styles' => array( + 'imageset_id', + 'template_id', + 'theme_id', + ), + ), + + 'drop_tables' => array( + $this->table_prefix . 'styles_imageset', + $this->table_prefix . 'styles_imageset_data', + $this->table_prefix . 'styles_template', + $this->table_prefix . 'styles_template_data', + $this->table_prefix . 'styles_theme', + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'styles' => array( + 'imageset_id' => array('UINT', 0), + 'template_id' => array('UINT', 0), + 'theme_id' => array('UINT', 0), + ), + ), + + 'add_index' => array( + $this->table_prefix . 'styles' => array( + 'imageset_id' => array('imageset_id'), + 'template_id' => array('template_id'), + 'theme_id' => array('theme_id'), + ), + ), + + 'add_tables' => array( + $this->table_prefix . 'styles_imageset' => array( + 'COLUMNS' => array( + 'imageset_id' => array('UINT', null, 'auto_increment'), + 'imageset_name' => array('VCHAR_UNI:255', ''), + 'imageset_copyright' => array('VCHAR_UNI', ''), + 'imageset_path' => array('VCHAR:100', ''), + ), + 'PRIMARY_KEY' => 'imageset_id', + 'KEYS' => array( + 'imgset_nm' => array('UNIQUE', 'imageset_name'), + ), + ), + $this->table_prefix . 'styles_imageset_data' => array( + 'COLUMNS' => array( + 'image_id' => array('UINT', null, 'auto_increment'), + 'image_name' => array('VCHAR:200', ''), + 'image_filename' => array('VCHAR:200', ''), + 'image_lang' => array('VCHAR:30', ''), + 'image_height' => array('USINT', 0), + 'image_width' => array('USINT', 0), + 'imageset_id' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'image_id', + 'KEYS' => array( + 'i_d' => array('INDEX', 'imageset_id'), + ), + ), + $this->table_prefix . 'styles_template' => array( + 'COLUMNS' => array( + 'template_id' => array('UINT', null, 'auto_increment'), + 'template_name' => array('VCHAR_UNI:255', ''), + 'template_copyright' => array('VCHAR_UNI', ''), + 'template_path' => array('VCHAR:100', ''), + 'bbcode_bitfield' => array('VCHAR:255', 'kNg='), + 'template_storedb' => array('BOOL', 0), + 'template_inherits_id' => array('UINT:4', 0), + 'template_inherit_path' => array('VCHAR', ''), + ), + 'PRIMARY_KEY' => 'template_id', + 'KEYS' => array( + 'tmplte_nm' => array('UNIQUE', 'template_name'), + ), + ), + $this->table_prefix . 'styles_template_data' => array( + 'COLUMNS' => array( + 'template_id' => array('UINT', 0), + 'template_filename' => array('VCHAR:100', ''), + 'template_included' => array('TEXT', ''), + 'template_mtime' => array('TIMESTAMP', 0), + 'template_data' => array('MTEXT_UNI', ''), + ), + 'KEYS' => array( + 'tid' => array('INDEX', 'template_id'), + 'tfn' => array('INDEX', 'template_filename'), + ), + ), + $this->table_prefix . 'styles_theme' => array( + 'COLUMNS' => array( + 'theme_id' => array('UINT', null, 'auto_increment'), + 'theme_name' => array('VCHAR_UNI:255', ''), + 'theme_copyright' => array('VCHAR_UNI', ''), + 'theme_path' => array('VCHAR:100', ''), + 'theme_storedb' => array('BOOL', 0), + 'theme_mtime' => array('TIMESTAMP', 0), + 'theme_data' => array('MTEXT_UNI', ''), + ), + 'PRIMARY_KEY' => 'theme_id', + 'KEYS' => array( + 'theme_name' => array('UNIQUE', 'theme_name'), + ), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/teampage.php b/phpbb/db/migration/data/v310/teampage.php new file mode 100644 index 0000000..3a37b17 --- /dev/null +++ b/phpbb/db/migration/data/v310/teampage.php @@ -0,0 +1,110 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class teampage extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'teampage'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'teampage' => array( + 'COLUMNS' => array( + 'teampage_id' => array('UINT', null, 'auto_increment'), + 'group_id' => array('UINT', 0), + 'teampage_name' => array('VCHAR_UNI:255', ''), + 'teampage_position' => array('UINT', 0), + 'teampage_parent' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'teampage_id', + ), + ), + 'drop_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'teampage', + ), + 'add_columns' => array( + $this->table_prefix . 'groups' => array( + 'group_teampage' => array('UINT', 0, 'after' => 'group_legend'), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'add_groups_teampage'))), + ); + } + + public function add_groups_teampage() + { + $sql = 'SELECT teampage_id + FROM ' . TEAMPAGE_TABLE; + $result = $this->db->sql_query_limit($sql, 1); + $added_groups_teampage = (bool) $this->db->sql_fetchfield('teampage_id'); + $this->db->sql_freeresult($result); + + if (!$added_groups_teampage) + { + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' + WHERE group_type = ' . GROUP_SPECIAL . " + AND (group_name = 'ADMINISTRATORS' + OR group_name = 'GLOBAL_MODERATORS') + ORDER BY group_name ASC"; + $result = $this->db->sql_query($sql); + + $teampage_entries = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $teampage_entries[] = array( + 'group_id' => (int) $row['group_id'], + 'teampage_name' => '', + 'teampage_position' => count($teampage_entries) + 1, + 'teampage_parent' => 0, + ); + } + $this->db->sql_freeresult($result); + + if (count($teampage_entries)) + { + $this->db->sql_multi_insert(TEAMPAGE_TABLE, $teampage_entries); + } + unset($teampage_entries); + } + + } +} diff --git a/phpbb/db/migration/data/v310/timezone.php b/phpbb/db/migration/data/v310/timezone.php new file mode 100644 index 0000000..03a8d1a --- /dev/null +++ b/phpbb/db/migration/data/v310/timezone.php @@ -0,0 +1,194 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class timezone extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v30x\release_3_0_11'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_timezone' => array('VCHAR:100', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_timezones'))), + ); + } + + public function update_timezones($start) + { + $start = (int) $start; + $limit = 500; + $converted = 0; + + $update_blocks = array(); + + $sql = 'SELECT user_id, user_timezone, user_dst + FROM ' . $this->table_prefix . 'users + ORDER BY user_id ASC'; + $result = $this->db->sql_query_limit($sql, $limit, $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $converted++; + + // In case this is somehow run twice on a row. + // Otherwise it would just end up as UTC on the second run + if (is_numeric($row['user_timezone'])) + { + $update_blocks[$row['user_timezone'] . ':' . $row['user_dst']][] = (int) $row['user_id']; + } + } + $this->db->sql_freeresult($result); + + // Update blocks of users who share the same timezone/dst + foreach ($update_blocks as $timezone => $user_ids) + { + $timezone = explode(':', $timezone); + $converted_timezone = $this->convert_phpbb30_timezone($timezone[0], $timezone[1]); + + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_timezone = '" . $this->db->sql_escape($converted_timezone) . "' + WHERE " . $this->db->sql_in_set('user_id', $user_ids); + $this->sql_query($sql); + } + + if ($converted == $limit) + { + // There are still more to convert + return $start + $limit; + } + + // Update board default timezone + $sql = 'UPDATE ' . $this->table_prefix . "config + SET config_value = '" . $this->convert_phpbb30_timezone($this->config['board_timezone'], $this->config['board_dst']) . "' + WHERE config_name = 'board_timezone'"; + $this->sql_query($sql); + } + + /** + * Determine the new timezone for a given phpBB 3.0 timezone and + * "Daylight Saving Time" option + * + * @param $timezone float Users timezone in 3.0 + * @param $dst int Users daylight saving time + * @return string Users new php Timezone which is used since 3.1 + */ + public function convert_phpbb30_timezone($timezone, $dst) + { + $offset = (float) $timezone + (int) $dst; + + switch ($timezone) + { + case '-12': + return 'Etc/GMT+' . abs($offset); //'[UTC - 12] Baker Island Time' + case '-11': + return 'Etc/GMT+' . abs($offset); //'[UTC - 11] Niue Time, Samoa Standard Time' + case '-10': + return 'Etc/GMT+' . abs($offset); //'[UTC - 10] Hawaii-Aleutian Standard Time, Cook Island Time' + case '-9.5': + return 'Pacific/Marquesas'; //'[UTC - 9:30] Marquesas Islands Time' + case '-9': + return 'Etc/GMT+' . abs($offset); //'[UTC - 9] Alaska Standard Time, Gambier Island Time' + case '-8': + return 'Etc/GMT+' . abs($offset); //'[UTC - 8] Pacific Standard Time' + case '-7': + return 'Etc/GMT+' . abs($offset); //'[UTC - 7] Mountain Standard Time' + case '-6': + return 'Etc/GMT+' . abs($offset); //'[UTC - 6] Central Standard Time' + case '-5': + return 'Etc/GMT+' . abs($offset); //'[UTC - 5] Eastern Standard Time' + case '-4.5': + return 'America/Caracas'; //'[UTC - 4:30] Venezuelan Standard Time' + case '-4': + return 'Etc/GMT+' . abs($offset); //'[UTC - 4] Atlantic Standard Time' + case '-3.5': + return 'America/St_Johns'; //'[UTC - 3:30] Newfoundland Standard Time' + case '-3': + return 'Etc/GMT+' . abs($offset); //'[UTC - 3] Amazon Standard Time, Central Greenland Time' + case '-2': + return 'Etc/GMT+' . abs($offset); //'[UTC - 2] Fernando de Noronha Time, South Georgia & the South Sandwich Islands Time' + case '-1': + return 'Etc/GMT+' . abs($offset); //'[UTC - 1] Azores Standard Time, Cape Verde Time, Eastern Greenland Time' + case '0': + return (!$dst) ? 'UTC' : 'Etc/GMT-1'; //'[UTC] Western European Time, Greenwich Mean Time' + case '1': + return 'Etc/GMT-' . $offset; //'[UTC + 1] Central European Time, West African Time' + case '2': + return 'Etc/GMT-' . $offset; //'[UTC + 2] Eastern European Time, Central African Time' + case '3': + return 'Etc/GMT-' . $offset; //'[UTC + 3] Moscow Standard Time, Eastern African Time' + case '3.5': + return 'Asia/Tehran'; //'[UTC + 3:30] Iran Standard Time' + case '4': + return 'Etc/GMT-' . $offset; //'[UTC + 4] Gulf Standard Time, Samara Standard Time' + case '4.5': + return 'Asia/Kabul'; //'[UTC + 4:30] Afghanistan Time' + case '5': + return 'Etc/GMT-' . $offset; //'[UTC + 5] Pakistan Standard Time, Yekaterinburg Standard Time' + case '5.5': + return 'Asia/Kolkata'; //'[UTC + 5:30] Indian Standard Time, Sri Lanka Time' + case '5.75': + return 'Asia/Kathmandu'; //'[UTC + 5:45] Nepal Time' + case '6': + return 'Etc/GMT-' . $offset; //'[UTC + 6] Bangladesh Time, Bhutan Time, Novosibirsk Standard Time' + case '6.5': + return 'Indian/Cocos'; //'[UTC + 6:30] Cocos Islands Time, Myanmar Time' + case '7': + return 'Etc/GMT-' . $offset; //'[UTC + 7] Indochina Time, Krasnoyarsk Standard Time' + case '8': + return 'Etc/GMT-' . $offset; //'[UTC + 8] Chinese Standard Time, Australian Western Standard Time, Irkutsk Standard Time' + case '8.75': + return 'Australia/Eucla'; //'[UTC + 8:45] Southeastern Western Australia Standard Time' + case '9': + return 'Etc/GMT-' . $offset; //'[UTC + 9] Japan Standard Time, Korea Standard Time, Chita Standard Time' + case '9.5': + return 'Australia/ACT'; //'[UTC + 9:30] Australian Central Standard Time' + case '10': + return 'Etc/GMT-' . $offset; //'[UTC + 10] Australian Eastern Standard Time, Vladivostok Standard Time' + case '10.5': + return 'Australia/Lord_Howe'; //'[UTC + 10:30] Lord Howe Standard Time' + case '11': + return 'Etc/GMT-' . $offset; //'[UTC + 11] Solomon Island Time, Magadan Standard Time' + case '11.5': + return 'Pacific/Norfolk'; //'[UTC + 11:30] Norfolk Island Time' + case '12': + return 'Etc/GMT-12'; //'[UTC + 12] New Zealand Time, Fiji Time, Kamchatka Standard Time' + case '12.75': + return 'Pacific/Chatham'; //'[UTC + 12:45] Chatham Islands Time' + case '13': + return 'Pacific/Tongatapu'; //'[UTC + 13] Tonga Time, Phoenix Islands Time' + case '14': + return 'Pacific/Kiritimati'; //'[UTC + 14] Line Island Time' + default: + return 'UTC'; + } + } +} diff --git a/phpbb/db/migration/data/v310/timezone_p2.php b/phpbb/db/migration/data/v310/timezone_p2.php new file mode 100644 index 0000000..3ac7ab3 --- /dev/null +++ b/phpbb/db/migration/data/v310/timezone_p2.php @@ -0,0 +1,49 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class timezone_p2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return !$this->db_tools->sql_column_exists($this->table_prefix . 'users', 'user_dst'); + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\timezone'); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'users' => array( + 'user_dst', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'users' => array( + 'user_dst' => array('BOOL', 0), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/topic_sort_username.php b/phpbb/db/migration/data/v310/topic_sort_username.php new file mode 100644 index 0000000..527da20 --- /dev/null +++ b/phpbb/db/migration/data/v310/topic_sort_username.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class topic_sort_username extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'topics' => array( + 'topic_first_poster_name' => array('VCHAR_UNI:255', '', 'true_sort'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'topics' => array( + 'topic_first_poster_name' => array('VCHAR_UNI:255', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v310/ucp_popuppm_module.php b/phpbb/db/migration/data/v310/ucp_popuppm_module.php new file mode 100644 index 0000000..8600f6e --- /dev/null +++ b/phpbb/db/migration/data/v310/ucp_popuppm_module.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v310; + +class ucp_popuppm_module extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + $sql = 'SELECT module_id + FROM ' . MODULES_TABLE . " + WHERE module_class = 'ucp' + AND module_langname = 'UCP_PM_POPUP_TITLE'"; + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + return $module_id == false; + } + + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\dev'); + } + + public function update_data() + { + return array( + array('module.remove', array( + 'ucp', + 'UCP_PM', + 'UCP_PM_POPUP_TITLE', + )), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/.htaccess b/phpbb/db/migration/data/v31x/.htaccess new file mode 100644 index 0000000..44242b5 --- /dev/null +++ b/phpbb/db/migration/data/v31x/.htaccess @@ -0,0 +1,33 @@ +# With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from +# module mod_authz_host to a new module called mod_access_compat (which may be +# disabled) and a new "Require" syntax has been introduced to mod_authz_host. +# We could just conditionally provide both versions, but unfortunately Apache +# does not explicitly tell us its version if the module mod_version is not +# available. In this case, we check for the availability of module +# mod_authz_core (which should be on 2.4 or higher only) as a best guess. + + + + Order Allow,Deny + Deny from All + + + = 2.4> + + Require all denied + + + + + + + Order Allow,Deny + Deny from All + + + + + Require all denied + + + diff --git a/phpbb/db/migration/data/v31x/add_jabber_ssl_context_config_options.php b/phpbb/db/migration/data/v31x/add_jabber_ssl_context_config_options.php new file mode 100644 index 0000000..9f416fe --- /dev/null +++ b/phpbb/db/migration/data/v31x/add_jabber_ssl_context_config_options.php @@ -0,0 +1,32 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class add_jabber_ssl_context_config_options extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v3110'); + } + + public function update_data() + { + return array( + // See http://php.net/manual/en/context.ssl.php + array('config.add', array('jab_verify_peer', 1)), + array('config.add', array('jab_verify_peer_name', 1)), + array('config.add', array('jab_allow_self_signed', 0)), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/add_latest_topics_index.php b/phpbb/db/migration/data/v31x/add_latest_topics_index.php new file mode 100644 index 0000000..fa2899e --- /dev/null +++ b/phpbb/db/migration/data/v31x/add_latest_topics_index.php @@ -0,0 +1,51 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v31x; + +class add_latest_topics_index extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3110', + ); + } + + public function update_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'topics' => array( + 'latest_topics' => array( + 'forum_id', + 'topic_last_post_time', + 'topic_last_post_id', + 'topic_moved_id', + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'topics' => array( + 'latest_topics', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/add_log_time_index.php b/phpbb/db/migration/data/v31x/add_log_time_index.php new file mode 100644 index 0000000..f53eedc --- /dev/null +++ b/phpbb/db/migration/data/v31x/add_log_time_index.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class add_log_time_index extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v319', + ); + } + + public function update_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'log' => array( + 'log_time' => array('log_time'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'log' => array( + 'log_time', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/add_smtp_ssl_context_config_options.php b/phpbb/db/migration/data/v31x/add_smtp_ssl_context_config_options.php new file mode 100644 index 0000000..92051dc --- /dev/null +++ b/phpbb/db/migration/data/v31x/add_smtp_ssl_context_config_options.php @@ -0,0 +1,32 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class add_smtp_ssl_context_config_options extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v3110'); + } + + public function update_data() + { + return array( + // See http://php.net/manual/en/context.ssl.php + array('config.add', array('smtp_verify_peer', 1)), + array('config.add', array('smtp_verify_peer_name', 1)), + array('config.add', array('smtp_allow_self_signed', 0)), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/increase_size_of_dateformat.php b/phpbb/db/migration/data/v31x/increase_size_of_dateformat.php new file mode 100644 index 0000000..bdf83f3 --- /dev/null +++ b/phpbb/db/migration/data/v31x/increase_size_of_dateformat.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class increase_size_of_dateformat extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v317', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'users' => array( + 'user_dateformat' => array('VCHAR_UNI:64', 'd M Y H:i'), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/increase_size_of_emotion.php b/phpbb/db/migration/data/v31x/increase_size_of_emotion.php new file mode 100644 index 0000000..7e486ac --- /dev/null +++ b/phpbb/db/migration/data/v31x/increase_size_of_emotion.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class increase_size_of_emotion extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3110', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'smilies' => array( + 'emotion' => array('VCHAR_UNI', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'smilies' => array( + 'emotion' => array('VCHAR_UNI:50', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/m_pm_report.php b/phpbb/db/migration/data/v31x/m_pm_report.php new file mode 100644 index 0000000..9b5710c --- /dev/null +++ b/phpbb/db/migration/data/v31x/m_pm_report.php @@ -0,0 +1,64 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class m_pm_report extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v316rc1'); + } + + public function update_data() + { + return array( + array('permission.add', array('m_pm_report', true, 'm_report')), + array('custom', array( + array($this, 'update_module_auth'), + ), + ), + ); + } + + public function revert_data() + { + return array( + array('permission.remove', array('m_pm_report')), + array('custom', array( + array($this, 'revert_module_auth'), + ), + ), + ); + } + + public function update_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'acl_m_pm_report' + WHERE module_class = 'mcp' + AND module_basename = 'mcp_pm_reports' + AND module_auth = 'aclf_m_report'"; + $this->db->sql_query($sql); + } + + public function revert_module_auth() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'aclf_m_report' + WHERE module_class = 'mcp' + AND module_basename = 'mcp_pm_reports' + AND module_auth = 'acl_m_pm_report'"; + $this->db->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v31x/m_softdelete_global.php b/phpbb/db/migration/data/v31x/m_softdelete_global.php new file mode 100644 index 0000000..dd7e20e --- /dev/null +++ b/phpbb/db/migration/data/v31x/m_softdelete_global.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class m_softdelete_global extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v311'); + } + + public function update_data() + { + return array( + // Make m_softdelete global. The add method will take care of updating + // it if it already exists. + array('permission.add', array('m_softdelete', true)), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/plupload_last_gc_dynamic.php b/phpbb/db/migration/data/v31x/plupload_last_gc_dynamic.php new file mode 100644 index 0000000..0783d70 --- /dev/null +++ b/phpbb/db/migration/data/v31x/plupload_last_gc_dynamic.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class plupload_last_gc_dynamic extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v312'); + } + + public function update_data() + { + return array( + // Make plupload_last_gc dynamic. + array('config.remove', array('plupload_last_gc')), + array('config.add', array('plupload_last_gc', 0, 1)), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/profilefield_remove_underscore_from_alpha.php b/phpbb/db/migration/data/v31x/profilefield_remove_underscore_from_alpha.php new file mode 100644 index 0000000..60491f8 --- /dev/null +++ b/phpbb/db/migration/data/v31x/profilefield_remove_underscore_from_alpha.php @@ -0,0 +1,47 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v31x; + +class profilefield_remove_underscore_from_alpha extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v311'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'remove_underscore_from_alpha_validations'))), + ); + } + + public function remove_underscore_from_alpha_validations() + { + $this->update_validation_rule('[\w]+', '[a-zA-Z0-9]+'); + $this->update_validation_rule('[\w_]+', '[\w]+'); + $this->update_validation_rule('[\w.]+', '[a-zA-Z0-9.]+'); + $this->update_validation_rule('[\w\x20_+\-\[\]]+', '[\w\x20+\-\[\]]+'); + $this->update_validation_rule('[a-zA-Z][\w\.,\-_]+', '[a-zA-Z][\w\.,\-]+'); + } + + public function update_validation_rule($old_validation, $new_validation) + { + $sql = 'UPDATE ' . PROFILE_FIELDS_TABLE . " + SET field_validation = '" . $this->db->sql_escape($new_validation) . "' + WHERE field_validation = '" . $this->db->sql_escape($old_validation) . "'"; + $this->db->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v31x/profilefield_yahoo_update_url.php b/phpbb/db/migration/data/v31x/profilefield_yahoo_update_url.php new file mode 100644 index 0000000..4df9083 --- /dev/null +++ b/phpbb/db/migration/data/v31x/profilefield_yahoo_update_url.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class profilefield_yahoo_update_url extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v312'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_contact_url'))), + ); + } + + public function update_contact_url() + { + $sql = 'UPDATE ' . $this->table_prefix . "profile_fields + SET field_contact_url = 'ymsgr:sendim?%s' + WHERE field_name = 'phpbb_yahoo' + AND field_contact_url = 'http://edit.yahoo.com/config/send_webmesg?.target=%s&.src=pg'"; + $this->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v31x/remove_duplicate_migrations.php b/phpbb/db/migration/data/v31x/remove_duplicate_migrations.php new file mode 100644 index 0000000..417d569 --- /dev/null +++ b/phpbb/db/migration/data/v31x/remove_duplicate_migrations.php @@ -0,0 +1,77 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v31x; + +class remove_duplicate_migrations extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v31x\v3110'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'deduplicate_entries'))), + ); + } + + public function deduplicate_entries() + { + $migration_state = array(); + $duplicate_migrations = array(); + + $sql = "SELECT * + FROM " . $this->table_prefix . 'migrations'; + $result = $this->db->sql_query($sql); + + if (!$this->db->get_sql_error_triggered()) + { + while ($migration = $this->db->sql_fetchrow($result)) + { + $migration_state[$migration['migration_name']] = $migration; + + $migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']); + } + } + + $this->db->sql_freeresult($result); + + foreach ($migration_state as $name => $migration) + { + $prepended_name = ($name[0] == '\\' ? '' : '\\') . $name; + $prefixless_name = $name[0] == '\\' ? substr($name, 1) : $name; + + if ($prepended_name != $name && isset($migration_state[$prepended_name]) && $migration_state[$prepended_name]['migration_depends_on'] == $migration_state[$name]['migration_depends_on']) + { + $duplicate_migrations[] = $name; + unset($migration_state[$prepended_name]); + } + else if ($prefixless_name != $name && isset($migration_state[$prefixless_name]) && $migration_state[$prefixless_name]['migration_depends_on'] == $migration_state[$name]['migration_depends_on']) + { + $duplicate_migrations[] = $prefixless_name; + unset($migration_state[$prefixless_name]); + } + } + + if (count($duplicate_migrations)) + { + $sql = 'DELETE + FROM ' . $this->table_prefix . 'migrations + WHERE ' . $this->db->sql_in_set('migration_name', $duplicate_migrations); + $this->db->sql_query($sql); + } + } +} diff --git a/phpbb/db/migration/data/v31x/style_update.php b/phpbb/db/migration/data/v31x/style_update.php new file mode 100644 index 0000000..bb030bb --- /dev/null +++ b/phpbb/db/migration/data/v31x/style_update.php @@ -0,0 +1,136 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class style_update extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\gold'); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_installed_styles'))), + ); + } + + public function update_installed_styles() + { + // Get all currently available styles + $styles = $this->find_style_dirs(); + $style_paths = $style_ids = array(); + + $sql = 'SELECT style_path, style_id + FROM ' . $this->table_prefix . 'styles'; + $result = $this->db->sql_query($sql); + while ($styles_row = $this->db->sql_fetchrow()) + { + if (in_array($styles_row['style_path'], $styles)) + { + $style_paths[] = $styles_row['style_path']; + $style_ids[] = $styles_row['style_id']; + } + } + $this->db->sql_freeresult($result); + + // Install prosilver if no style is available and prosilver can be installed + if (empty($style_paths) && in_array('prosilver', $styles)) + { + // Try to parse config file + $cfg = parse_cfg_file($this->phpbb_root_path . 'styles/prosilver/style.cfg'); + + // Stop running this if prosilver cfg file can't be read + if (empty($cfg)) + { + throw new \RuntimeException('No styles available and could not fall back to prosilver.'); + } + + $style = array( + 'style_name' => 'prosilver', + 'style_copyright' => '© phpBB Limited', + 'style_active' => 1, + 'style_path' => 'prosilver', + 'bbcode_bitfield' => 'kNg=', + 'style_parent_id' => 0, + 'style_parent_tree' => '', + ); + + // Add to database + $this->db->sql_transaction('begin'); + + $sql = 'INSERT INTO ' . $this->table_prefix . 'styles + ' . $this->db->sql_build_array('INSERT', $style); + $this->db->sql_query($sql); + + $style_id = $this->db->sql_nextid(); + $style_ids[] = $style_id; + + $this->db->sql_transaction('commit'); + + // Set prosilver to default style + $this->config->set('default_style', $style_id); + } + else if (empty($styles) && empty($available_styles)) + { + throw new \RuntimeException('No valid styles available'); + } + + // Make sure default style is available + if (!in_array($this->config['default_style'], $style_ids)) + { + $this->config->set('default_style', array_pop($style_ids)); + } + + // Reset users to default style if their user_style is nonexistent + $sql = 'UPDATE ' . $this->table_prefix . "users + SET user_style = {$this->config['default_style']} + WHERE " . $this->db->sql_in_set('user_style', $style_ids, true, true); + $this->db->sql_query($sql); + } + + /** + * Find all directories that have styles + * Copied from acp_styles + * + * @return array Directory names + */ + protected function find_style_dirs() + { + $styles = array(); + $styles_path = $this->phpbb_root_path . 'styles/'; + + $dp = @opendir($styles_path); + if ($dp) + { + while (($file = readdir($dp)) !== false) + { + $dir = $styles_path . $file; + if ($file[0] == '.' || !is_dir($dir)) + { + continue; + } + + if (file_exists("{$dir}/style.cfg")) + { + $styles[] = $file; + } + } + closedir($dp); + } + + return $styles; + } +} diff --git a/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php b/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php new file mode 100644 index 0000000..14b7b7b --- /dev/null +++ b/phpbb/db/migration/data/v31x/update_custom_bbcodes_with_idn.php @@ -0,0 +1,69 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class update_custom_bbcodes_with_idn extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v312', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_bbcodes_table'))), + ); + } + + public function update_bbcodes_table() + { + if (!class_exists('acp_bbcodes')) + { + include($this->phpbb_root_path . 'includes/acp/acp_bbcodes.' . $this->php_ext); + } + + $bbcodes = new \acp_bbcodes(); + + $sql = 'SELECT bbcode_id, bbcode_match, bbcode_tpl + FROM ' . BBCODES_TABLE; + $result = $this->sql_query($sql); + + $sql_ary = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + if (preg_match('/(URL|LOCAL_URL|RELATIVE_URL)/', $row['bbcode_match'])) + { + $data = $bbcodes->build_regexp($row['bbcode_match'], $row['bbcode_tpl']); + $sql_ary[$row['bbcode_id']] = array( + 'first_pass_match' => $data['first_pass_match'], + 'first_pass_replace' => $data['first_pass_replace'], + 'second_pass_match' => $data['second_pass_match'], + 'second_pass_replace' => $data['second_pass_replace'] + ); + } + } + $this->db->sql_freeresult($result); + + foreach ($sql_ary as $bbcode_id => $bbcode_data) + { + $sql = 'UPDATE ' . BBCODES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $bbcode_data) . ' + WHERE bbcode_id = ' . (int) $bbcode_id; + $this->sql_query($sql); + } + } +} diff --git a/phpbb/db/migration/data/v31x/update_hashes.php b/phpbb/db/migration/data/v31x/update_hashes.php new file mode 100644 index 0000000..aa83c3f --- /dev/null +++ b/phpbb/db/migration/data/v31x/update_hashes.php @@ -0,0 +1,33 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v31x; + +class update_hashes extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3110', + ); + } + + public function update_data() + { + return array( + array('config.add', array('enable_update_hashes', '1')), + array('config.add', array('update_hashes_lock', '')), + array('config.add', array('update_hashes_last_cron', '0')) + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v311.php b/phpbb/db/migration/data/v31x/v311.php new file mode 100644 index 0000000..b9d6ed3 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v311.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v311 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\gold', + '\phpbb\db\migration\data\v31x\style_update', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v3110.php b/phpbb/db/migration/data/v31x/v3110.php new file mode 100644 index 0000000..b89b4cc --- /dev/null +++ b/phpbb/db/migration/data/v31x/v3110.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v3110 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.10', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3110rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.10')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v3110rc1.php b/phpbb/db/migration/data/v31x/v3110rc1.php new file mode 100644 index 0000000..da69f23 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v3110rc1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v3110rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.10-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v319', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.10-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v3111.php b/phpbb/db/migration/data/v31x/v3111.php new file mode 100644 index 0000000..f01bbc2 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v3111.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v3111 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.11', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3111rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.11')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v3111rc1.php b/phpbb/db/migration/data/v31x/v3111rc1.php new file mode 100644 index 0000000..2596562 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v3111rc1.php @@ -0,0 +1,43 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v3111rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.11-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3110', + '\phpbb\db\migration\data\v31x\add_log_time_index', + '\phpbb\db\migration\data\v31x\increase_size_of_emotion', + '\phpbb\db\migration\data\v31x\add_jabber_ssl_context_config_options', + '\phpbb\db\migration\data\v31x\add_smtp_ssl_context_config_options', + '\phpbb\db\migration\data\v31x\update_hashes', + '\phpbb\db\migration\data\v31x\remove_duplicate_migrations', + '\phpbb\db\migration\data\v31x\add_latest_topics_index', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.11-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v3112.php b/phpbb/db/migration/data/v31x/v3112.php new file mode 100644 index 0000000..0d75d35 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v3112.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v3112 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.12', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3111', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.12')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v312.php b/phpbb/db/migration/data/v31x/v312.php new file mode 100644 index 0000000..114c2b9 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v312.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v312 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v312rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.2')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v312rc1.php b/phpbb/db/migration/data/v31x/v312rc1.php new file mode 100644 index 0000000..e2408d4 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v312rc1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v312rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.2-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v311', + '\phpbb\db\migration\data\v31x\m_softdelete_global', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.2-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v313.php b/phpbb/db/migration/data/v31x/v313.php new file mode 100644 index 0000000..b86788d --- /dev/null +++ b/phpbb/db/migration/data/v31x/v313.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v313 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.3', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v313rc2', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.3')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v313rc1.php b/phpbb/db/migration/data/v31x/v313rc1.php new file mode 100644 index 0000000..b1dcc03 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v313rc1.php @@ -0,0 +1,40 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v313rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.3-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v30x\release_3_0_13_rc1', + '\phpbb\db\migration\data\v31x\plupload_last_gc_dynamic', + '\phpbb\db\migration\data\v31x\profilefield_remove_underscore_from_alpha', + '\phpbb\db\migration\data\v31x\profilefield_yahoo_update_url', + '\phpbb\db\migration\data\v31x\update_custom_bbcodes_with_idn', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.3-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v313rc2.php b/phpbb/db/migration/data/v31x/v313rc2.php new file mode 100644 index 0000000..b701dca --- /dev/null +++ b/phpbb/db/migration/data/v31x/v313rc2.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v313rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.3-RC2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v30x\release_3_0_13_pl1', + '\phpbb\db\migration\data\v31x\v313rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.3-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v314.php b/phpbb/db/migration/data/v31x/v314.php new file mode 100644 index 0000000..82dbbf2 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v314.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v314 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.4', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v30x\release_3_0_14', + '\phpbb\db\migration\data\v31x\v314rc2', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.4')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v314rc1.php b/phpbb/db/migration/data/v31x/v314rc1.php new file mode 100644 index 0000000..e7baf0c --- /dev/null +++ b/phpbb/db/migration/data/v31x/v314rc1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v314rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.4-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v313', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.4-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v314rc2.php b/phpbb/db/migration/data/v31x/v314rc2.php new file mode 100644 index 0000000..3fc5bf2 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v314rc2.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v314rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.4-RC2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v30x\release_3_0_14_rc1', + '\phpbb\db\migration\data\v31x\v314rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.4-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v315.php b/phpbb/db/migration/data/v31x/v315.php new file mode 100644 index 0000000..d5eacf8 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v315.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v315 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.5', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v315rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.5')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v315rc1.php b/phpbb/db/migration/data/v31x/v315rc1.php new file mode 100644 index 0000000..a58b6a0 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v315rc1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v315rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.5-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v314', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.5-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v316.php b/phpbb/db/migration/data/v31x/v316.php new file mode 100644 index 0000000..b3e0060 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v316.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v316 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.6', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v316rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.6')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v316rc1.php b/phpbb/db/migration/data/v31x/v316rc1.php new file mode 100644 index 0000000..6badfb6 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v316rc1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v316rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.6-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v315', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.6-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v317.php b/phpbb/db/migration/data/v31x/v317.php new file mode 100644 index 0000000..d95be06 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v317.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v317 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.7', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v317rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.7')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v317pl1.php b/phpbb/db/migration/data/v31x/v317pl1.php new file mode 100644 index 0000000..1cb39b0 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v317pl1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v317pl1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.7-pl1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v317', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.7-pl1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v317rc1.php b/phpbb/db/migration/data/v31x/v317rc1.php new file mode 100644 index 0000000..77759da --- /dev/null +++ b/phpbb/db/migration/data/v31x/v317rc1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v317rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.7-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\m_pm_report', + '\phpbb\db\migration\data\v31x\v316', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.7-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v318.php b/phpbb/db/migration/data/v31x/v318.php new file mode 100644 index 0000000..7663529 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v318.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v318 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.8', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v318rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.8')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v318rc1.php b/phpbb/db/migration/data/v31x/v318rc1.php new file mode 100644 index 0000000..2cab5c9 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v318rc1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v318rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.8-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\increase_size_of_dateformat', + '\phpbb\db\migration\data\v31x\v317pl1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.8-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v319.php b/phpbb/db/migration/data/v31x/v319.php new file mode 100644 index 0000000..f773814 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v319.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v319 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.9', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v319rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.9')), + ); + } +} diff --git a/phpbb/db/migration/data/v31x/v319rc1.php b/phpbb/db/migration/data/v31x/v319rc1.php new file mode 100644 index 0000000..9805b45 --- /dev/null +++ b/phpbb/db/migration/data/v31x/v319rc1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v31x; + +class v319rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.1.9-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v318', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.1.9-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/.htaccess b/phpbb/db/migration/data/v320/.htaccess new file mode 100644 index 0000000..44242b5 --- /dev/null +++ b/phpbb/db/migration/data/v320/.htaccess @@ -0,0 +1,33 @@ +# With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from +# module mod_authz_host to a new module called mod_access_compat (which may be +# disabled) and a new "Require" syntax has been introduced to mod_authz_host. +# We could just conditionally provide both versions, but unfortunately Apache +# does not explicitly tell us its version if the module mod_version is not +# available. In this case, we check for the availability of module +# mod_authz_core (which should be on 2.4 or higher only) as a best guess. + + + + Order Allow,Deny + Deny from All + + + = 2.4> + + Require all denied + + + + + + + Order Allow,Deny + Deny from All + + + + + Require all denied + + + diff --git a/phpbb/db/migration/data/v320/add_help_phpbb.php b/phpbb/db/migration/data/v320/add_help_phpbb.php new file mode 100644 index 0000000..804adc4 --- /dev/null +++ b/phpbb/db/migration/data/v320/add_help_phpbb.php @@ -0,0 +1,48 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class add_help_phpbb extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320rc1', + ); + } + + public function effectively_installed() + { + return isset($this->config['help_send_statistics']); + } + + public function update_data() + { + return array( + array('config.add', array('help_send_statistics', true)), + array('config.add', array('help_send_statistics_time', 0)), + array('module.remove', array('acp', false, 'ACP_SEND_STATISTICS')), + array('module.add', array( + 'acp', + 'ACP_SERVER_CONFIGURATION', + array( + 'module_basename' => 'acp_help_phpbb', + 'module_langname' => 'ACP_HELP_PHPBB', + 'module_mode' => 'help_phpbb', + 'module_auth' => 'acl_a_server', + ), + )), + ); + } +} diff --git a/phpbb/db/migration/data/v320/allowed_schemes_links.php b/phpbb/db/migration/data/v320/allowed_schemes_links.php new file mode 100644 index 0000000..726822b --- /dev/null +++ b/phpbb/db/migration/data/v320/allowed_schemes_links.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class allowed_schemes_links extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_data() + { + return array( + array('config.add', array('allowed_schemes_links', 'http,https,ftp')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/announce_global_permission.php b/phpbb/db/migration/data/v320/announce_global_permission.php new file mode 100644 index 0000000..7afecb8 --- /dev/null +++ b/phpbb/db/migration/data/v320/announce_global_permission.php @@ -0,0 +1,43 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class announce_global_permission extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = 'f_announce_global'"; + $result = $this->db->sql_query($sql); + $auth_option_id = $this->db->sql_fetchfield('auth_option_id'); + $this->db->sql_freeresult($result); + + return $auth_option_id !== false; + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_data() + { + return array( + array('permission.add', array('f_announce_global', false, 'f_announce')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/cookie_notice.php b/phpbb/db/migration/data/v320/cookie_notice.php new file mode 100644 index 0000000..75cb03b --- /dev/null +++ b/phpbb/db/migration/data/v320/cookie_notice.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class cookie_notice extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320rc2', + ); + } + + public function update_data() + { + return array( + array('config.add', array('cookie_notice', false)), + ); + } +} diff --git a/phpbb/db/migration/data/v320/default_data_type_ids.php b/phpbb/db/migration/data/v320/default_data_type_ids.php new file mode 100644 index 0000000..65e5b3f --- /dev/null +++ b/phpbb/db/migration/data/v320/default_data_type_ids.php @@ -0,0 +1,361 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class default_data_type_ids extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320a2', + '\phpbb\db\migration\data\v320\oauth_states', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'acl_users' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'attachments' => array( + 'attach_id' => array('ULINT', null, 'auto_increment'), + 'post_msg_id' => array('ULINT', 0), + 'poster_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'banlist' => array( + 'ban_id' => array('ULINT', null, 'auto_increment'), + 'ban_userid' => array('ULINT', 0), + ), + $this->table_prefix . 'bookmarks' => array( + 'topic_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'bots' => array( + 'bot_id' => array('ULINT', null, 'auto_increment'), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'drafts' => array( + 'draft_id' => array('ULINT', null, 'auto_increment'), + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_last_post_id' => array('ULINT', 0), + 'forum_last_poster_id' => array('ULINT', 0), + ), + $this->table_prefix . 'forums_access' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'forums_track' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'forums_watch' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'log' => array( + 'log_id' => array('ULINT', null, 'auto_increment'), + 'post_id' => array('ULINT', 0), + 'reportee_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'login_attempts' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'moderator_cache' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'notifications' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'oauth_accounts' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'oauth_states' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'oauth_tokens' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'poll_options' => array( + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'poll_votes' => array( + 'topic_id' => array('ULINT', 0), + 'vote_user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_id' => array('ULINT', null, 'auto_increment'), + 'poster_id' => array('ULINT', 0), + 'post_delete_user' => array('ULINT', 0), + 'post_edit_user' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'privmsgs' => array( + 'author_id' => array('ULINT', 0), + 'message_edit_user' => array('ULINT', 0), + 'msg_id' => array('ULINT', null, 'auto_increment'), + ), + $this->table_prefix . 'privmsgs_folder' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'privmsgs_rules' => array( + 'rule_user_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'privmsgs_to' => array( + 'author_id' => array('ULINT', 0), + 'msg_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'profile_fields_data' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'reports' => array( + 'report_id' => array('ULINT', 0), + 'pm_id' => array('ULINT', 0), + 'post_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'search_wordlist' => array( + 'word_id' => array('ULINT', null, 'auto_increment'), + ), + $this->table_prefix . 'search_wordmatch' => array( + 'post_id' => array('ULINT', 0), + 'word_id' => array('ULINT', 0), + ), + $this->table_prefix . 'sessions' => array( + 'session_user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'sessions_keys' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'topics' => array( + 'topic_id' => array('ULINT', null, 'auto_increment'), + 'topic_poster' => array('ULINT', 0), + 'topic_first_post_id' => array('ULINT', 0), + 'topic_last_post_id' => array('ULINT', 0), + 'topic_last_poster_id' => array('ULINT', 0), + 'topic_moved_id' => array('ULINT', 0), + 'topic_delete_user' => array('ULINT', 0), + ), + $this->table_prefix . 'topics_track' => array( + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'topics_posted' => array( + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'topics_watch' => array( + 'user_id' => array('ULINT', 0), + 'topic_id' => array('ULINT', 0), + ), + $this->table_prefix . 'user_notifications' => array( + 'item_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'user_group' => array( + 'user_id' => array('ULINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_id' => array('ULINT', null, 'auto_increment'), + ), + $this->table_prefix . 'warnings' => array( + 'log_id' => array('ULINT', 0), + 'user_id' => array('ULINT', 0), + 'post_id' => array('ULINT', 0), + ), + $this->table_prefix . 'words' => array( + 'word_id' => array('ULINT', null, 'auto_increment'), + ), + $this->table_prefix . 'zebra' => array( + 'user_id' => array('ULINT', 0), + 'zebra_id' => array('ULINT', 0), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'acl_users' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'attachments' => array( + 'attach_id' => array('UINT', null, 'auto_increment'), + 'post_msg_id' => array('UINT', 0), + 'poster_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'banlist' => array( + 'ban_id' => array('UINT', null, 'auto_increment'), + 'ban_userid' => array('UINT', 0), + ), + $this->table_prefix . 'bookmarks' => array( + 'topic_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'bots' => array( + 'bot_id' => array('UINT', null, 'auto_increment'), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'drafts' => array( + 'draft_id' => array('UINT', null, 'auto_increment'), + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums' => array( + 'forum_last_post_id' => array('UINT', 0), + 'forum_last_poster_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums_access' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums_track' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'forums_watch' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'log' => array( + 'log_id' => array('UINT', null, 'auto_increment'), + 'post_id' => array('UINT', 0), + 'reportee_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'login_attempts' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'moderator_cache' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'notifications' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'oauth_accounts' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'oauth_states' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'oauth_tokens' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'poll_options' => array( + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'poll_votes' => array( + 'topic_id' => array('UINT', 0), + 'vote_user_id' => array('UINT', 0), + ), + $this->table_prefix . 'posts' => array( + 'post_id' => array('UINT', null, 'auto_increment'), + 'poster_id' => array('UINT', 0), + 'post_delete_user' => array('UINT', 0), + 'post_edit_user' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'privmsgs' => array( + 'author_id' => array('UINT', 0), + 'message_edit_user' => array('UINT', 0), + 'msg_id' => array('UINT', null, 'auto_increment'), + ), + $this->table_prefix . 'privmsgs_folder' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'privmsgs_rules' => array( + 'rule_user_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'privmsgs_to' => array( + 'author_id' => array('UINT', 0), + 'msg_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'profile_fields_data' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'reports' => array( + 'report_id' => array('UINT', 0), + 'pm_id' => array('UINT', 0), + 'post_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'search_wordlist' => array( + 'word_id' => array('UINT', null, 'auto_increment'), + ), + $this->table_prefix . 'search_wordmatch' => array( + 'post_id' => array('UINT', 0), + 'word_id' => array('UINT', 0), + ), + $this->table_prefix . 'sessions' => array( + 'session_user_id' => array('UINT', 0), + ), + $this->table_prefix . 'sessions_keys' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'topics' => array( + 'topic_id' => array('UINT', null, 'auto_increment'), + 'topic_poster' => array('UINT', 0), + 'topic_first_post_id' => array('UINT', 0), + 'topic_last_post_id' => array('UINT', 0), + 'topic_last_poster_id' => array('UINT', 0), + 'topic_moved_id' => array('UINT', 0), + 'topic_delete_user' => array('UINT', 0), + ), + $this->table_prefix . 'topics_track' => array( + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'topics_posted' => array( + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'topics_watch' => array( + 'user_id' => array('UINT', 0), + 'topic_id' => array('UINT', 0), + ), + $this->table_prefix . 'user_notifications' => array( + 'item_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'user_group' => array( + 'user_id' => array('UINT', 0), + ), + $this->table_prefix . 'users' => array( + 'user_id' => array('UINT', null, 'auto_increment'), + ), + $this->table_prefix . 'warnings' => array( + 'log_id' => array('UINT', 0), + 'user_id' => array('UINT', 0), + 'post_id' => array('UINT', 0), + ), + $this->table_prefix . 'words' => array( + 'word_id' => array('UINT', null, 'auto_increment'), + ), + $this->table_prefix . 'zebra' => array( + 'user_id' => array('UINT', 0), + 'zebra_id' => array('UINT', 0), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v320/dev.php b/phpbb/db/migration/data/v320/dev.php new file mode 100644 index 0000000..ad2da3c --- /dev/null +++ b/phpbb/db/migration/data/v320/dev.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class dev extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-dev', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v316', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-dev')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/font_awesome_update.php b/phpbb/db/migration/data/v320/font_awesome_update.php new file mode 100644 index 0000000..817b638 --- /dev/null +++ b/phpbb/db/migration/data/v320/font_awesome_update.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class font_awesome_update extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function effectively_installed() + { + return isset($this->config['load_font_awesome_url']); + } + + public function update_data() + { + return array( + array('config.add', array('load_font_awesome_url', 'https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/icons_alt.php b/phpbb/db/migration/data/v320/icons_alt.php new file mode 100644 index 0000000..80132e5 --- /dev/null +++ b/phpbb/db/migration/data/v320/icons_alt.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class icons_alt extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'icons' => array( + 'icons_alt' => array('VCHAR', '', 'after' => 'icons_height'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'icons' => array( + 'icons_alt', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v320/log_post_id.php b/phpbb/db/migration/data/v320/log_post_id.php new file mode 100644 index 0000000..ead53c8 --- /dev/null +++ b/phpbb/db/migration/data/v320/log_post_id.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class log_post_id extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'log' => array( + 'post_id' => array('UINT', 0, 'after' => 'topic_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'log' => array( + 'post_id', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v320/notifications_board.php b/phpbb/db/migration/data/v320/notifications_board.php new file mode 100644 index 0000000..ac1b3a0 --- /dev/null +++ b/phpbb/db/migration/data/v320/notifications_board.php @@ -0,0 +1,75 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class notifications_board extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_data() + { + return array( + array('config.add', array('allow_board_notifications', 1)), + array('custom', array(array($this, 'update_user_subscriptions'))), + array('custom', array(array($this, 'update_module'))), + ); + } + + public function update_module() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET module_auth = 'cfg_allow_board_notifications' + WHERE module_basename = 'ucp_notifications' + AND module_mode = 'notification_list'"; + $this->sql_query($sql); + } + + public function update_user_subscriptions() + { + $sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " + SET method = 'notification.method.board' + WHERE method = ''"; + $this->sql_query($sql); + } + + public function revert_data() + { + return array( + array('custom', array(array($this, 'revert_user_subscriptions'))), + array('custom', array(array($this, 'revert_module'))), + ); + } + + public function revert_user_subscriptions() + { + $sql = 'UPDATE ' . USER_NOTIFICATIONS_TABLE . " + SET method = '' + WHERE method = 'notification.method.board'"; + $this->sql_query($sql); + } + + public function revert_module() + { + $sql = 'UPDATE ' . MODULES_TABLE . " + SET auth = '' + WHERE module_basename = 'ucp_notifications' + AND module_mode = 'notification_list'"; + $this->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v320/oauth_states.php b/phpbb/db/migration/data/v320/oauth_states.php new file mode 100644 index 0000000..22ab2da --- /dev/null +++ b/phpbb/db/migration/data/v320/oauth_states.php @@ -0,0 +1,56 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class oauth_states extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array('\phpbb\db\migration\data\v310\auth_provider_oauth'); + } + + public function effectively_installed() + { + return $this->db_tools->sql_table_exists($this->table_prefix . 'oauth_states'); + } + + public function update_schema() + { + return array( + 'add_tables' => array( + $this->table_prefix . 'oauth_states' => array( + 'COLUMNS' => array( + 'user_id' => array('UINT', 0), + 'session_id' => array('CHAR:32', ''), + 'provider' => array('VCHAR', ''), + 'oauth_state' => array('VCHAR', ''), + ), + 'KEYS' => array( + 'user_id' => array('INDEX', 'user_id'), + 'provider' => array('INDEX', 'provider'), + ), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_tables' => array( + $this->table_prefix . 'oauth_states', + ), + ); + } +} diff --git a/phpbb/db/migration/data/v320/remote_upload_validation.php b/phpbb/db/migration/data/v320/remote_upload_validation.php new file mode 100644 index 0000000..d61f6b9 --- /dev/null +++ b/phpbb/db/migration/data/v320/remote_upload_validation.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remote_upload_validation extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320a2', + ); + } + + public function update_data() + { + return array( + array('config.add', array('remote_upload_verify', '0')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/remove_outdated_media.php b/phpbb/db/migration/data/v320/remove_outdated_media.php new file mode 100644 index 0000000..88fe59c --- /dev/null +++ b/phpbb/db/migration/data/v320/remove_outdated_media.php @@ -0,0 +1,95 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remove_outdated_media extends \phpbb\db\migration\migration +{ + // Following constants were deprecated in 3.2 + // and moved from constants.php to compatibility_globals.php, + // thus define them as class constants + const ATTACHMENT_CATEGORY_WM = 2; + const ATTACHMENT_CATEGORY_RM = 3; + const ATTACHMENT_CATEGORY_QUICKTIME = 6; + + protected $cat_id = array( + self::ATTACHMENT_CATEGORY_WM, + self::ATTACHMENT_CATEGORY_RM, + self::ATTACHMENT_CATEGORY_QUICKTIME, + ); + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'change_extension_group'))), + ); + } + + public function change_extension_group() + { + // select group ids of outdated media + $sql = 'SELECT group_id + FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE ' . $this->db->sql_in_set('cat_id', $this->cat_id); + $result = $this->db->sql_query($sql); + + $group_ids = array(); + while ($group_id = (int) $this->db->sql_fetchfield('group_id')) + { + $group_ids[] = $group_id; + } + $this->db->sql_freeresult($result); + + // nothing to do, admin has removed all the outdated media extension groups + if (empty($group_ids)) + { + return true; + } + + // get the group id of downloadable files + $sql = 'SELECT group_id + FROM ' . EXTENSION_GROUPS_TABLE . " + WHERE group_name = 'DOWNLOADABLE_FILES'"; + $result = $this->db->sql_query($sql); + $download_id = (int) $this->db->sql_fetchfield('group_id'); + $this->db->sql_freeresult($result); + + if (empty($download_id)) + { + $sql = 'UPDATE ' . EXTENSIONS_TABLE . ' + SET group_id = 0 + WHERE ' . $this->db->sql_in_set('group_id', $group_ids); + } + else + { + // move outdated media extensions to downloadable files + $sql = 'UPDATE ' . EXTENSIONS_TABLE . " + SET group_id = $download_id" . ' + WHERE ' . $this->db->sql_in_set('group_id', $group_ids); + } + + $this->db->sql_query($sql); + + // delete the now empty, outdated media extension groups + $sql = 'DELETE FROM ' . EXTENSION_GROUPS_TABLE . ' + WHERE ' . $this->db->sql_in_set('group_id', $group_ids); + $this->db->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v320/remove_profilefield_wlm.php b/phpbb/db/migration/data/v320/remove_profilefield_wlm.php new file mode 100644 index 0000000..1cb9070 --- /dev/null +++ b/phpbb/db/migration/data/v320/remove_profilefield_wlm.php @@ -0,0 +1,152 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class remove_profilefield_wlm extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + ); + } + + public function update_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields_data' => array( + 'pf_phpbb_wlm', + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields_data' => array( + 'pf_phpbb_wlm' => array('VCHAR', ''), + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'delete_custom_profile_field_data'))), + ); + } + + public function revert_data() + { + return array( + array('custom', array(array($this, 'create_custom_field'))), + ); + } + + public function delete_custom_profile_field_data() + { + $field_id = $this->get_custom_profile_field_id(); + + $sql = 'DELETE FROM ' . PROFILE_FIELDS_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + } + + /** + * Get custom profile field id + * @return int custom profile filed id + */ + public function get_custom_profile_field_id() + { + $sql = 'SELECT field_id + FROM ' . PROFILE_FIELDS_TABLE . " + WHERE field_name = 'phpbb_wlm'"; + $result = $this->db->sql_query($sql); + $field_id = (int) $this->db->sql_fetchfield('field_id'); + $this->db->sql_freeresult($result); + + return $field_id; + } + + public function create_custom_field() + { + $sql = 'SELECT MAX(field_order) as max_field_order + FROM ' . PROFILE_FIELDS_TABLE; + $result = $this->db->sql_query($sql); + $max_field_order = (int) $this->db->sql_fetchfield('max_field_order'); + $this->db->sql_freeresult($result); + + $sql_ary = array( + 'field_name' => 'phpbb_wlm', + 'field_type' => 'profilefields.type.string', + 'field_ident' => 'phpbb_wlm', + 'field_length' => '40', + 'field_minlen' => '5', + 'field_maxlen' => '255', + 'field_novalue' => '', + 'field_default_value' => '', + 'field_validation' => '.*', + 'field_required' => 0, + 'field_show_novalue' => 0, + 'field_show_on_reg' => 0, + 'field_show_on_pm' => 1, + 'field_show_on_vt' => 1, + 'field_show_on_ml' => 0, + 'field_show_profile' => 1, + 'field_hide' => 0, + 'field_no_view' => 0, + 'field_active' => 1, + 'field_is_contact' => 1, + 'field_contact_desc' => '', + 'field_contact_url' => '', + 'field_order' => $max_field_order + 1, + ); + + $sql = 'INSERT INTO ' . PROFILE_FIELDS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + $field_id = (int) $this->db->sql_nextid(); + + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, PROFILE_LANG_TABLE); + + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE; + $result = $this->db->sql_query($sql); + $lang_name = 'WLM'; + while ($lang_id = (int) $this->db->sql_fetchfield('lang_id')) + { + $insert_buffer->insert(array( + 'field_id' => (int) $field_id, + 'lang_id' => (int) $lang_id, + 'lang_name' => $lang_name, + 'lang_explain' => '', + 'lang_default_value' => '', + )); + } + $this->db->sql_freeresult($result); + + $insert_buffer->flush(); + } +} diff --git a/phpbb/db/migration/data/v320/report_id_auto_increment.php b/phpbb/db/migration/data/v320/report_id_auto_increment.php new file mode 100644 index 0000000..6e81bae --- /dev/null +++ b/phpbb/db/migration/data/v320/report_id_auto_increment.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class report_id_auto_increment extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\default_data_type_ids', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'reports' => array( + 'report_id' => array('ULINT', null, 'auto_increment'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'reports' => array( + 'report_id' => array('ULINT', 0), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v320/text_reparser.php b/phpbb/db/migration/data/v320/text_reparser.php new file mode 100644 index 0000000..6b8cf93 --- /dev/null +++ b/phpbb/db/migration/data/v320/text_reparser.php @@ -0,0 +1,121 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v320; + +use phpbb\textreparser\manager; +use phpbb\textreparser\reparser_interface; + +class text_reparser extends \phpbb\db\migration\container_aware_migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v310\contact_admin_form', + '\phpbb\db\migration\data\v320\allowed_schemes_links', + ); + } + + public function effectively_installed() + { + return isset($this->config['reparse_lock']); + } + + public function update_data() + { + return array( + array('config.add', array('reparse_lock', 0, true)), + array('config.add', array('text_reparser.pm_text_cron_interval', 10)), + array('config.add', array('text_reparser.pm_text_last_cron', 0)), + array('config.add', array('text_reparser.poll_option_cron_interval', 10)), + array('config.add', array('text_reparser.poll_option_last_cron', 0)), + array('config.add', array('text_reparser.poll_title_cron_interval', 10)), + array('config.add', array('text_reparser.poll_title_last_cron', 0)), + array('config.add', array('text_reparser.post_text_cron_interval', 10)), + array('config.add', array('text_reparser.post_text_last_cron', 0)), + array('config.add', array('text_reparser.user_signature_cron_interval', 10)), + array('config.add', array('text_reparser.user_signature_last_cron', 0)), + array('custom', array(array($this, 'reparse'))), + ); + } + + public function reparse($resume_data) + { + /** @var manager $reparser_manager */ + $reparser_manager = $this->container->get('text_reparser.manager'); + + if (!is_array($resume_data)) + { + /** @var reparser_interface[] $reparsers */ + $reparsers = $this->container->get('text_reparser_collection'); + + // Initialize all reparsers + foreach ($reparsers as $name => $reparser) + { + $reparser_manager->update_resume_data($name, 1, $reparser->get_max_id(), 100); + } + } + + // Sometimes a cron job is too much + $limit = 100; + $fast_reparsers = array( + 'text_reparser.contact_admin_info', + 'text_reparser.forum_description', + 'text_reparser.forum_rules', + 'text_reparser.group_description', + ); + + if (!is_array($resume_data)) + { + $resume_data = array( + 'reparser' => 0, + 'current' => $this->container->get($fast_reparsers[0])->get_max_id(), + ); + } + + $fast_reparsers_size = count($fast_reparsers); + $processed_records = 0; + while ($processed_records < $limit && $resume_data['reparser'] < $fast_reparsers_size) + { + $reparser = $this->container->get($fast_reparsers[$resume_data['reparser']]); + + // New reparser + if ($resume_data['current'] === 0) + { + $resume_data['current'] = $reparser->get_max_id(); + } + + $start = max(1, $resume_data['current'] + 1 - ($limit - $processed_records)); + $end = max(1, $resume_data['current']); + $reparser->reparse_range($start, $end); + + $processed_records += $end - $start + 1; + $resume_data['current'] = $start - 1; + + if ($start === 1) + { + // Prevent CLI command from running these reparsers again + $reparser_manager->update_resume_data($fast_reparsers[$resume_data['reparser']], 1, 0, $limit); + + $resume_data['reparser']++; + } + } + + if ($resume_data['reparser'] === $fast_reparsers_size) + { + return true; + } + + return $resume_data; + } +} diff --git a/phpbb/db/migration/data/v320/v320.php b/phpbb/db/migration/data/v320/v320.php new file mode 100644 index 0000000..20e741c --- /dev/null +++ b/phpbb/db/migration/data/v320/v320.php @@ -0,0 +1,40 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +use phpbb\db\migration\migration; + +class v320 extends migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\increase_size_of_emotion', + '\phpbb\db\migration\data\v320\cookie_notice', + ); + + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/v320a1.php b/phpbb/db/migration/data/v320/v320a1.php new file mode 100644 index 0000000..d7ecb36 --- /dev/null +++ b/phpbb/db/migration/data/v320/v320a1.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320a1 extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-a1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\dev', + '\phpbb\db\migration\data\v320\allowed_schemes_links', + '\phpbb\db\migration\data\v320\announce_global_permission', + '\phpbb\db\migration\data\v320\remove_profilefield_wlm', + '\phpbb\db\migration\data\v320\font_awesome_update', + '\phpbb\db\migration\data\v320\icons_alt', + '\phpbb\db\migration\data\v320\log_post_id', + '\phpbb\db\migration\data\v320\remove_outdated_media', + '\phpbb\db\migration\data\v320\notifications_board', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-dev')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/v320a2.php b/phpbb/db/migration/data/v320/v320a2.php new file mode 100644 index 0000000..ae53a73 --- /dev/null +++ b/phpbb/db/migration/data/v320/v320a2.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320a2 extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-a2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v317rc1', + '\phpbb\db\migration\data\v320\text_reparser', + '\phpbb\db\migration\data\v320\v320a1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-a2')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/v320b1.php b/phpbb/db/migration/data/v320/v320b1.php new file mode 100644 index 0000000..5c3a379 --- /dev/null +++ b/phpbb/db/migration/data/v320/v320b1.php @@ -0,0 +1,39 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +class v320b1 extends \phpbb\db\migration\container_aware_migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-b1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v317pl1', + '\phpbb\db\migration\data\v320\v320a2', + '\phpbb\db\migration\data\v31x\increase_size_of_dateformat', + '\phpbb\db\migration\data\v320\default_data_type_ids', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-b1')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/v320b2.php b/phpbb/db/migration/data/v320/v320b2.php new file mode 100644 index 0000000..007f758 --- /dev/null +++ b/phpbb/db/migration/data/v320/v320b2.php @@ -0,0 +1,40 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +use phpbb\db\migration\migration; + +class v320b2 extends migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-b2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v318', + '\phpbb\db\migration\data\v320\v320b1', + '\phpbb\db\migration\data\v320\remote_upload_validation', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-b2')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/v320rc1.php b/phpbb/db/migration/data/v320/v320rc1.php new file mode 100644 index 0000000..a04a2ab --- /dev/null +++ b/phpbb/db/migration/data/v320/v320rc1.php @@ -0,0 +1,40 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +use phpbb\db\migration\migration; + +class v320rc1 extends migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v319', + '\phpbb\db\migration\data\v320\report_id_auto_increment', + '\phpbb\db\migration\data\v320\v320b2', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v320/v320rc2.php b/phpbb/db/migration/data/v320/v320rc2.php new file mode 100644 index 0000000..ec9bb62 --- /dev/null +++ b/phpbb/db/migration/data/v320/v320rc2.php @@ -0,0 +1,40 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v320; + +use phpbb\db\migration\migration; + +class v320rc2 extends migration +{ + public function effectively_installed() + { + return version_compare($this->config['version'], '3.2.0-RC2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\remove_duplicate_migrations', + '\phpbb\db\migration\data\v31x\add_log_time_index', + '\phpbb\db\migration\data\v320\add_help_phpbb', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.0-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/.htaccess b/phpbb/db/migration/data/v32x/.htaccess new file mode 100644 index 0000000..44242b5 --- /dev/null +++ b/phpbb/db/migration/data/v32x/.htaccess @@ -0,0 +1,33 @@ +# With Apache 2.4 the "Order, Deny" syntax has been deprecated and moved from +# module mod_authz_host to a new module called mod_access_compat (which may be +# disabled) and a new "Require" syntax has been introduced to mod_authz_host. +# We could just conditionally provide both versions, but unfortunately Apache +# does not explicitly tell us its version if the module mod_version is not +# available. In this case, we check for the availability of module +# mod_authz_core (which should be on 2.4 or higher only) as a best guess. + + + + Order Allow,Deny + Deny from All + + + = 2.4> + + Require all denied + + + + + + + Order Allow,Deny + Deny from All + + + + + Require all denied + + + diff --git a/phpbb/db/migration/data/v32x/cookie_notice_p2.php b/phpbb/db/migration/data/v32x/cookie_notice_p2.php new file mode 100644 index 0000000..1a83175 --- /dev/null +++ b/phpbb/db/migration/data/v32x/cookie_notice_p2.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class cookie_notice_p2 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320', + ); + } + + public function effectively_installed() + { + return isset($this->config['cookie_notice']); + } + + public function update_data() + { + return array( + array('config.add', array('cookie_notice', '0')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/disable_remote_avatar.php b/phpbb/db/migration/data/v32x/disable_remote_avatar.php new file mode 100644 index 0000000..b08833f --- /dev/null +++ b/phpbb/db/migration/data/v32x/disable_remote_avatar.php @@ -0,0 +1,34 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +use phpbb\db\migration\migration; + +class disable_remote_avatar extends migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v325', + ); + } + + public function update_data() + { + return array( + array('config.update', array('allow_avatar_remote', '0')), + array('config.update', array('allow_avatar_remote_upload', '0')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/email_force_sender.php b/phpbb/db/migration/data/v32x/email_force_sender.php new file mode 100644 index 0000000..5319b7f --- /dev/null +++ b/phpbb/db/migration/data/v32x/email_force_sender.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class email_force_sender extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v321', + ); + } + + public function effectively_installed() + { + return isset($this->config['email_force_sender']); + } + + public function update_data() + { + return array( + array('config.add', array('email_force_sender', '0')), + array('config.remove', array('email_function_name')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/enable_accurate_pm_button.php b/phpbb/db/migration/data/v32x/enable_accurate_pm_button.php new file mode 100644 index 0000000..a7b9960 --- /dev/null +++ b/phpbb/db/migration/data/v32x/enable_accurate_pm_button.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class enable_accurate_pm_button extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v322', + ); + } + + public function effectively_installed() + { + return isset($this->config['enable_accurate_pm_button']); + } + + public function update_data() + { + return array( + array('config.add', array('enable_accurate_pm_button', '1')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/f_list_topics_permission_add.php b/phpbb/db/migration/data/v32x/f_list_topics_permission_add.php new file mode 100644 index 0000000..49727e5 --- /dev/null +++ b/phpbb/db/migration/data/v32x/f_list_topics_permission_add.php @@ -0,0 +1,31 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class f_list_topics_permission_add extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v321', + ); + } + + public function update_data() + { + return array( + array('permission.add', array('f_list_topics', false, 'f_read')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/fix_user_styles.php b/phpbb/db/migration/data/v32x/fix_user_styles.php new file mode 100644 index 0000000..16fbdbc --- /dev/null +++ b/phpbb/db/migration/data/v32x/fix_user_styles.php @@ -0,0 +1,54 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class fix_user_styles extends \phpbb\db\migration\migration +{ + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'styles_fix'))), + ); + } + + public function styles_fix() + { + $default_style = (int) $this->config['default_style']; + $enabled_styles = array(); + + // Get enabled styles + $sql = 'SELECT style_id + FROM ' . STYLES_TABLE . ' + WHERE style_active = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $enabled_styles[] = (int) $row['style_id']; + } + $this->db->sql_freeresult($result); + + // Set the default style to users who have an invalid style + $this->sql_query('UPDATE ' . USERS_TABLE . ' + SET user_style = ' . (int) $default_style . ' + WHERE ' . $this->db->sql_in_set('user_style', $enabled_styles, true)); + } +} diff --git a/phpbb/db/migration/data/v32x/forum_topics_per_page_type.php b/phpbb/db/migration/data/v32x/forum_topics_per_page_type.php new file mode 100644 index 0000000..afcecf2 --- /dev/null +++ b/phpbb/db/migration/data/v32x/forum_topics_per_page_type.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class forum_topics_per_page_type extends \phpbb\db\migration\migration +{ + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v323', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'forums' => array( + 'forum_topics_per_page' => array('USINT', 0), + ), + ), + ); + } + +} diff --git a/phpbb/db/migration/data/v32x/jquery_update.php b/phpbb/db/migration/data/v32x/jquery_update.php new file mode 100644 index 0000000..6dc58ec --- /dev/null +++ b/phpbb/db/migration/data/v32x/jquery_update.php @@ -0,0 +1,37 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class jquery_update extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return $this->config['load_jquery_url'] === '//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js'; + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v325rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('load_jquery_url', '//ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js')), + ); + } + +} diff --git a/phpbb/db/migration/data/v32x/load_user_activity_limit.php b/phpbb/db/migration/data/v32x/load_user_activity_limit.php new file mode 100644 index 0000000..71bb6c0 --- /dev/null +++ b/phpbb/db/migration/data/v32x/load_user_activity_limit.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class load_user_activity_limit extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320', + ); + } + + public function effectively_installed() + { + return isset($this->config['load_user_activity_limit']); + } + + public function update_data() + { + return array( + array('config.add', array('load_user_activity_limit', '5000')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/merge_duplicate_bbcodes.php b/phpbb/db/migration/data/v32x/merge_duplicate_bbcodes.php new file mode 100644 index 0000000..71ee19e --- /dev/null +++ b/phpbb/db/migration/data/v32x/merge_duplicate_bbcodes.php @@ -0,0 +1,84 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class merge_duplicate_bbcodes extends \phpbb\db\migration\container_aware_migration +{ + public function update_data() + { + return [ + ['custom', [[$this, 'update_bbcodes_table']]], + ]; + } + + public function update_bbcodes_table() + { + $sql = 'SELECT bbcode_id, bbcode_tag, bbcode_helpline, bbcode_match, bbcode_tpl FROM ' . BBCODES_TABLE; + $result = $this->sql_query($sql); + $bbcodes = []; + while ($row = $this->db->sql_fetchrow($result)) + { + $variant = (substr($row['bbcode_tag'], -1) === '=') ? 'with': 'without'; + $bbcode_name = strtolower(rtrim($row['bbcode_tag'], '=')); + $bbcodes[$bbcode_name][$variant] = $row; + } + $this->db->sql_freeresult($result); + + foreach ($bbcodes as $bbcode_name => $variants) + { + if (count($variants) === 2) + { + $this->merge_bbcodes($variants['without'], $variants['with']); + } + } + } + + protected function merge_bbcodes(array $without, array $with) + { + try + { + $merged = $this->container->get('text_formatter.s9e.bbcode_merger')->merge_bbcodes( + [ + 'usage' => $without['bbcode_match'], + 'template' => $without['bbcode_tpl'] + ], + [ + 'usage' => $with['bbcode_match'], + 'template' => $with['bbcode_tpl'] + ] + ); + } + catch (\Exception $e) + { + // Ignore the pair and move on. The BBCodes would have to be fixed manually + return; + } + + $bbcode_data = [ + 'bbcode_tag' => $without['bbcode_tag'], + 'bbcode_helpline' => $without['bbcode_helpline'] . ' | ' . $with['bbcode_helpline'], + 'bbcode_match' => $merged['usage'], + 'bbcode_tpl' => $merged['template'] + ]; + + $sql = 'UPDATE ' . BBCODES_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $bbcode_data) . ' + WHERE bbcode_id = ' . (int) $without['bbcode_id']; + $this->sql_query($sql); + + $sql = 'DELETE FROM ' . BBCODES_TABLE . ' + WHERE bbcode_id = ' . (int) $with['bbcode_id']; + $this->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v32x/remove_imagick.php b/phpbb/db/migration/data/v32x/remove_imagick.php new file mode 100644 index 0000000..7ad396f --- /dev/null +++ b/phpbb/db/migration/data/v32x/remove_imagick.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class remove_imagick extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v324rc1', + ); + } + + public function update_data() + { + return array( + array('config.remove', array('img_imagick')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/smtp_dynamic_data.php b/phpbb/db/migration/data/v32x/smtp_dynamic_data.php new file mode 100644 index 0000000..aeaa3e8 --- /dev/null +++ b/phpbb/db/migration/data/v32x/smtp_dynamic_data.php @@ -0,0 +1,42 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class smtp_dynamic_data extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v326rc1', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'set_smtp_dynamic'))), + ); + } + + public function set_smtp_dynamic() + { + $smtp_auth_entries = [ + 'smtp_password', + 'smtp_username', + ]; + $this->sql_query('UPDATE ' . CONFIG_TABLE . ' + SET is_dynamic = 1 + WHERE ' . $this->db->sql_in_set('config_name', $smtp_auth_entries)); + } +} diff --git a/phpbb/db/migration/data/v32x/update_prosilver_bitfield.php b/phpbb/db/migration/data/v32x/update_prosilver_bitfield.php new file mode 100644 index 0000000..6e51a01 --- /dev/null +++ b/phpbb/db/migration/data/v32x/update_prosilver_bitfield.php @@ -0,0 +1,39 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class update_prosilver_bitfield extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v321', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'update_bbcode_bitfield'))), + ); + } + + public function update_bbcode_bitfield() + { + $sql = 'UPDATE ' . STYLES_TABLE . " + SET bbcode_bitfield = '//g=' + WHERE style_path = 'prosilver'"; + $this->sql_query($sql); + } +} diff --git a/phpbb/db/migration/data/v32x/user_notifications_table_index_p1.php b/phpbb/db/migration/data/v32x/user_notifications_table_index_p1.php new file mode 100644 index 0000000..93ff31e --- /dev/null +++ b/phpbb/db/migration/data/v32x/user_notifications_table_index_p1.php @@ -0,0 +1,46 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class user_notifications_table_index_p1 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\cookie_notice_p2', + ); + } + + public function update_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'user_notifications' => array( + 'user_id' => array('user_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'user_notifications' => array( + 'user_id', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/user_notifications_table_index_p2.php b/phpbb/db/migration/data/v32x/user_notifications_table_index_p2.php new file mode 100644 index 0000000..0a47176 --- /dev/null +++ b/phpbb/db/migration/data/v32x/user_notifications_table_index_p2.php @@ -0,0 +1,46 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class user_notifications_table_index_p2 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\user_notifications_table_index_p1', + ); + } + + public function update_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'user_notifications' => array( + 'uid_itm_id' => array('user_id', 'item_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'user_notifications' => array( + 'uid_itm_id', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/user_notifications_table_index_p3.php b/phpbb/db/migration/data/v32x/user_notifications_table_index_p3.php new file mode 100644 index 0000000..1636b30 --- /dev/null +++ b/phpbb/db/migration/data/v32x/user_notifications_table_index_p3.php @@ -0,0 +1,46 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class user_notifications_table_index_p3 extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\user_notifications_table_index_p2', + ); + } + + public function update_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'user_notifications' => array( + 'usr_itm_tpe' => array('user_id', 'item_type', 'item_id'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'user_notifications' => array( + 'usr_itm_tpe', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/user_notifications_table_reduce_column_sizes.php b/phpbb/db/migration/data/v32x/user_notifications_table_reduce_column_sizes.php new file mode 100644 index 0000000..e0a1077 --- /dev/null +++ b/phpbb/db/migration/data/v32x/user_notifications_table_reduce_column_sizes.php @@ -0,0 +1,48 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class user_notifications_table_reduce_column_sizes extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\user_notifications_table_index_p3', + ); + } + + public function update_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'user_notifications' => array( + 'item_type' => array('VCHAR:165', ''), + 'method' => array('VCHAR:165', ''), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'change_columns' => array( + $this->table_prefix . 'user_notifications' => array( + 'item_type' => array('VCHAR:255', ''), + 'method' => array('VCHAR:255', ''), + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/user_notifications_table_remove_duplicates.php b/phpbb/db/migration/data/v32x/user_notifications_table_remove_duplicates.php new file mode 100644 index 0000000..50d0642 --- /dev/null +++ b/phpbb/db/migration/data/v32x/user_notifications_table_remove_duplicates.php @@ -0,0 +1,55 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class user_notifications_table_remove_duplicates extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\user_notifications_table_temp_index', + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'remove_duplicates'))), + ); + } + + public function remove_duplicates() + { + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->table_prefix . 'user_notifications'); + + $sql = "SELECT item_type, item_id, user_id, method, MAX(notify) AS notify + FROM {$this->table_prefix}user_notifications + GROUP BY item_type, item_id, user_id, method + HAVING COUNT(item_type) > 1"; + + $result = $this->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + // Delete the duplicate entries + $this->sql_query("DELETE FROM {$this->table_prefix}user_notifications + WHERE user_id = {$row['user_id']} + AND item_type = '{$row['item_type']}' + AND method = '{$row['method']}'"); + + // And re-insert as a single one + $insert_buffer->insert($row); + } + $this->db->sql_freeresult($result); + } +} diff --git a/phpbb/db/migration/data/v32x/user_notifications_table_temp_index.php b/phpbb/db/migration/data/v32x/user_notifications_table_temp_index.php new file mode 100644 index 0000000..80256a0 --- /dev/null +++ b/phpbb/db/migration/data/v32x/user_notifications_table_temp_index.php @@ -0,0 +1,46 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class user_notifications_table_temp_index extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\user_notifications_table_reduce_column_sizes', + ); + } + + public function update_schema() + { + return array( + 'add_index' => array( + $this->table_prefix . 'user_notifications' => array( + 'itm_usr_mthd' => array('item_type', 'item_id', 'user_id', 'method'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'user_notifications' => array( + 'itm_usr_mthd', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/user_notifications_table_unique_index.php b/phpbb/db/migration/data/v32x/user_notifications_table_unique_index.php new file mode 100644 index 0000000..51cf90c --- /dev/null +++ b/phpbb/db/migration/data/v32x/user_notifications_table_unique_index.php @@ -0,0 +1,51 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration\data\v32x; + +class user_notifications_table_unique_index extends \phpbb\db\migration\migration +{ + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\user_notifications_table_remove_duplicates', + ); + } + + public function update_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'user_notifications' => array( + 'itm_usr_mthd', + ), + ), + 'add_unique_index' => array( + $this->table_prefix . 'user_notifications' => array( + 'itm_usr_mthd' => array('item_type', 'item_id', 'user_id', 'method'), + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_keys' => array( + $this->table_prefix . 'user_notifications' => array( + 'itm_usr_mthd', + ), + ), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v321.php b/phpbb/db/migration/data/v32x/v321.php new file mode 100644 index 0000000..fdbb5cf --- /dev/null +++ b/phpbb/db/migration/data/v32x/v321.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v321 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3111', + '\phpbb\db\migration\data\v32x\v321rc1', + ); + + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.1')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v321rc1.php b/phpbb/db/migration/data/v32x/v321rc1.php new file mode 100644 index 0000000..653a16f --- /dev/null +++ b/phpbb/db/migration/data/v32x/v321rc1.php @@ -0,0 +1,39 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v321rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.1-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v320\v320', + '\phpbb\db\migration\data\v31x\v3111rc1', + '\phpbb\db\migration\data\v32x\load_user_activity_limit', + '\phpbb\db\migration\data\v32x\user_notifications_table_unique_index', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.1-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v322.php b/phpbb/db/migration/data/v32x/v322.php new file mode 100644 index 0000000..7ecbbb3 --- /dev/null +++ b/phpbb/db/migration/data/v32x/v322.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v322 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v31x\v3112', + '\phpbb\db\migration\data\v32x\v322rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.2')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v322rc1.php b/phpbb/db/migration/data/v32x/v322rc1.php new file mode 100644 index 0000000..4fd6270 --- /dev/null +++ b/phpbb/db/migration/data/v32x/v322rc1.php @@ -0,0 +1,41 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v322rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.2-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v321', + '\phpbb\db\migration\data\v32x\fix_user_styles', + '\phpbb\db\migration\data\v32x\update_prosilver_bitfield', + '\phpbb\db\migration\data\v32x\email_force_sender', + '\phpbb\db\migration\data\v32x\f_list_topics_permission_add', + '\phpbb\db\migration\data\v32x\merge_duplicate_bbcodes', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.2-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v323.php b/phpbb/db/migration/data/v32x/v323.php new file mode 100644 index 0000000..1ec28ce --- /dev/null +++ b/phpbb/db/migration/data/v32x/v323.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v323 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.3', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v323rc2', + ); + + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.3')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v323rc1.php b/phpbb/db/migration/data/v32x/v323rc1.php new file mode 100644 index 0000000..c3fcd1a --- /dev/null +++ b/phpbb/db/migration/data/v32x/v323rc1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v323rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.3-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v322', + '\phpbb\db\migration\data\v32x\enable_accurate_pm_button', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.3-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v323rc2.php b/phpbb/db/migration/data/v32x/v323rc2.php new file mode 100644 index 0000000..32235ee --- /dev/null +++ b/phpbb/db/migration/data/v32x/v323rc2.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v323rc2 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.3-RC2', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v323rc1', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.3-RC2')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v324.php b/phpbb/db/migration/data/v32x/v324.php new file mode 100644 index 0000000..cd7783f --- /dev/null +++ b/phpbb/db/migration/data/v32x/v324.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v324 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.4', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v324rc1', + '\phpbb\db\migration\data\v32x\remove_imagick', + ); + + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.4')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v324rc1.php b/phpbb/db/migration/data/v32x/v324rc1.php new file mode 100644 index 0000000..0221e26 --- /dev/null +++ b/phpbb/db/migration/data/v32x/v324rc1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v324rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.4-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v323', + '\phpbb\db\migration\data\v32x\forum_topics_per_page_type', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.4-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v325.php b/phpbb/db/migration/data/v32x/v325.php new file mode 100644 index 0000000..59de491 --- /dev/null +++ b/phpbb/db/migration/data/v32x/v325.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v325 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.5', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v325rc1', + '\phpbb\db\migration\data\v32x\jquery_update', + ); + + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.5')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v325rc1.php b/phpbb/db/migration/data/v32x/v325rc1.php new file mode 100644 index 0000000..2d0de0a --- /dev/null +++ b/phpbb/db/migration/data/v32x/v325rc1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v325rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.5-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v324', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.5-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v326.php b/phpbb/db/migration/data/v32x/v326.php new file mode 100644 index 0000000..2d511b9 --- /dev/null +++ b/phpbb/db/migration/data/v32x/v326.php @@ -0,0 +1,39 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v326 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.6', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v326rc1', + '\phpbb\db\migration\data\v32x\disable_remote_avatar', + '\phpbb\db\migration\data\v32x\smtp_dynamic_data', + ); + + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.6')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v326rc1.php b/phpbb/db/migration/data/v32x/v326rc1.php new file mode 100644 index 0000000..092700d --- /dev/null +++ b/phpbb/db/migration/data/v32x/v326rc1.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v326rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.6-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v325', + ); + + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.6-RC1')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v327.php b/phpbb/db/migration/data/v32x/v327.php new file mode 100644 index 0000000..f9ea11f --- /dev/null +++ b/phpbb/db/migration/data/v32x/v327.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v327 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.7', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v327rc1', + ); + + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.7')), + ); + } +} diff --git a/phpbb/db/migration/data/v32x/v327rc1.php b/phpbb/db/migration/data/v32x/v327rc1.php new file mode 100644 index 0000000..c816910 --- /dev/null +++ b/phpbb/db/migration/data/v32x/v327rc1.php @@ -0,0 +1,36 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\data\v32x; + +class v327rc1 extends \phpbb\db\migration\migration +{ + public function effectively_installed() + { + return phpbb_version_compare($this->config['version'], '3.2.7-RC1', '>='); + } + + static public function depends_on() + { + return array( + '\phpbb\db\migration\data\v32x\v326', + ); + } + + public function update_data() + { + return array( + array('config.update', array('version', '3.2.7-RC1')), + ); + } +} diff --git a/phpbb/db/migration/exception.php b/phpbb/db/migration/exception.php new file mode 100644 index 0000000..7990e85 --- /dev/null +++ b/phpbb/db/migration/exception.php @@ -0,0 +1,75 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration; + +/** +* The migrator is responsible for applying new migrations in the correct order. +*/ +class exception extends \Exception +{ + /** + * Extra parameters sent to exception to aid in debugging + * @var array + */ + protected $parameters; + + /** + * Throw an exception. + * + * First argument is the error message. + * Additional arguments will be output with the error message. + */ + public function __construct() + { + $parameters = func_get_args(); + $message = array_shift($parameters); + parent::__construct($message); + + $this->parameters = $parameters; + } + + /** + * Output the error as a string + * + * @return string + */ + public function __toString() + { + return $this->message . ': ' . var_export($this->parameters, true); + } + + /** + * Get the parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get localised message (with $user->lang()) + * + * @param \phpbb\user $user + * @return string + */ + public function getLocalisedMessage(\phpbb\user $user) + { + $parameters = $this->getParameters(); + array_unshift($parameters, $this->getMessage()); + + return call_user_func_array(array($user, 'lang'), $parameters); + } +} diff --git a/phpbb/db/migration/helper.php b/phpbb/db/migration/helper.php new file mode 100644 index 0000000..bce2eff --- /dev/null +++ b/phpbb/db/migration/helper.php @@ -0,0 +1,116 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration; + +/** +* The migrator is responsible for applying new migrations in the correct order. +*/ +class helper +{ + /** + * Get the schema steps from an array of schema changes + * + * This splits up $schema_changes into individual changes so that the + * changes can be chunked + * + * @param array $schema_changes from migration + * @return array + */ + public function get_schema_steps($schema_changes) + { + $steps = array(); + + // Nested level of data (only supports 1/2 currently) + $nested_level = array( + 'drop_tables' => 1, + 'add_tables' => 1, + 'change_columns' => 2, + 'add_columns' => 2, + 'drop_keys' => 2, + 'drop_columns' => 2, + 'add_primary_keys' => 2, // perform_schema_changes only uses one level, but second is in the function + 'add_unique_index' => 2, + 'add_index' => 2, + ); + + foreach ($nested_level as $change_type => $data_depth) + { + if (!empty($schema_changes[$change_type])) + { + foreach ($schema_changes[$change_type] as $key => $value) + { + if ($data_depth === 1) + { + $steps[] = array( + 'dbtools.perform_schema_changes', array(array( + $change_type => array( + (!is_int($key)) ? $key : 0 => $value, + ), + )), + ); + } + else if ($data_depth === 2) + { + foreach ($value as $key2 => $value2) + { + $steps[] = array( + 'dbtools.perform_schema_changes', array(array( + $change_type => array( + $key => array( + $key2 => $value2, + ), + ), + )), + ); + } + } + } + } + } + + return $steps; + } + + /** + * Reverse the update steps from an array of data changes + * + * 'If' statements and custom methods will be skipped, for all + * other calls the reverse method of the tool class will be called + * + * @param array $steps Update changes from migration + * + * @return array + */ + public function reverse_update_data($steps) + { + $reversed_array = array(); + + foreach ($steps as $step) + { + $parts = explode('.', $step[0]); + $parameters = $step[1]; + + $class = $parts[0]; + $method = isset($parts[1]) ? $parts[1] : false; + + if ($class !== 'if' && $class !== 'custom') + { + array_unshift($parameters, $method); + $reversed_array[] = array($class . '.reverse', $parameters); + } + } + + return array_reverse($reversed_array); + } +} diff --git a/phpbb/db/migration/migration.php b/phpbb/db/migration/migration.php new file mode 100644 index 0000000..4e21834 --- /dev/null +++ b/phpbb/db/migration/migration.php @@ -0,0 +1,166 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration; + +/** +* Abstract base class for database migrations +* +* Each migration consists of a set of schema and data changes to be implemented +* in a subclass. This class provides various utility methods to simplify editing +* a phpBB. +*/ +abstract class migration implements migration_interface +{ + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\db\tools\tools_interface */ + protected $db_tools; + + /** @var string */ + protected $table_prefix; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var array Errors, if any occurred */ + protected $errors; + + /** @var array List of queries executed through $this->sql_query() */ + protected $queries = array(); + + /** + * Constructor + * + * @param \phpbb\config\config $config + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\db\tools\tools_interface $db_tools + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $table_prefix + */ + public function __construct(\phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $phpbb_root_path, $php_ext, $table_prefix) + { + $this->config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + $this->table_prefix = $table_prefix; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->errors = array(); + } + + /** + * {@inheritdoc} + */ + static public function depends_on() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function effectively_installed() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function update_schema() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function revert_schema() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function update_data() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function revert_data() + { + return array(); + } + + /** + * Wrapper for running queries to generate user feedback on updates + * + * @param string $sql SQL query to run on the database + * @return mixed Query result from db->sql_query() + */ + protected function sql_query($sql) + { + $this->queries[] = $sql; + + $this->db->sql_return_on_error(true); + + if ($sql === 'begin') + { + $result = $this->db->sql_transaction('begin'); + } + else if ($sql === 'commit') + { + $result = $this->db->sql_transaction('commit'); + } + else + { + $result = $this->db->sql_query($sql); + if ($this->db->get_sql_error_triggered()) + { + $this->errors[] = array( + 'sql' => $this->db->get_sql_error_sql(), + 'code' => $this->db->get_sql_error_returned(), + ); + } + } + + $this->db->sql_return_on_error(false); + + return $result; + } + + /** + * Get the list of queries run + * + * @return array + */ + public function get_queries() + { + return $this->queries; + } +} diff --git a/phpbb/db/migration/migration_interface.php b/phpbb/db/migration/migration_interface.php new file mode 100644 index 0000000..2aba5ec --- /dev/null +++ b/phpbb/db/migration/migration_interface.php @@ -0,0 +1,70 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\migration; + +/** + * Base class interface for database migrations + */ +interface migration_interface +{ + /** + * Defines other migrations to be applied first + * + * @return array An array of migration class names + */ + static public function depends_on(); + + /** + * Allows you to check if the migration is effectively installed (entirely optional) + * + * This is checked when a migration is installed. If true is returned, the migration will be set as + * installed without performing the database changes. + * This function is intended to help moving to migrations from a previous database updater, where some + * migrations may have been installed already even though they are not yet listed in the migrations table. + * + * @return bool True if this migration is installed, False if this migration is not installed (checked on install) + */ + public function effectively_installed(); + + /** + * Updates the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function update_schema(); + + /** + * Reverts the database schema by providing a set of change instructions + * + * @return array Array of schema changes (compatible with db_tools->perform_schema_changes()) + */ + public function revert_schema(); + + /** + * Updates data by returning a list of instructions to be executed + * + * @return array Array of data update instructions + */ + public function update_data(); + + /** + * Reverts data by returning a list of instructions to be executed + * + * @return array Array of data instructions that will be performed on revert + * NOTE: calls to tools (such as config.add) are automatically reverted when + * possible, so you should not attempt to revert those, this is mostly for + * otherwise unrevertable calls (custom functions for example) + */ + public function revert_data(); +} diff --git a/phpbb/db/migration/profilefield_base_migration.php b/phpbb/db/migration/profilefield_base_migration.php new file mode 100644 index 0000000..bc542a8 --- /dev/null +++ b/phpbb/db/migration/profilefield_base_migration.php @@ -0,0 +1,248 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration; + +abstract class profilefield_base_migration extends container_aware_migration +{ + protected $profilefield_name; + + protected $profilefield_database_type; + + protected $profilefield_data; + + /** + * Language data should be in array -> each language_data in separate key + * array( + * array( + * 'option_id' => value, + * 'field_type' => value, + * 'lang_value' => value, + * ), + * array( + * 'option_id' => value, + * 'field_type' => value, + * 'lang_value' => value, + * ), + * ) + */ + protected $profilefield_language_data; + + protected $user_column_name; + + public function effectively_installed() + { + return $this->db_tools->sql_column_exists($this->table_prefix . 'profile_fields_data', 'pf_' . $this->profilefield_name); + } + + public function update_schema() + { + return array( + 'add_columns' => array( + $this->table_prefix . 'profile_fields_data' => array( + 'pf_' . $this->profilefield_name => $this->profilefield_database_type, + ), + ), + ); + } + + public function revert_schema() + { + return array( + 'drop_columns' => array( + $this->table_prefix . 'profile_fields_data' => array( + 'pf_' . $this->profilefield_name, + ), + ), + ); + } + + public function update_data() + { + return array( + array('custom', array(array($this, 'create_custom_field'))), + array('custom', array(array($this, 'convert_user_field_to_custom_field'))), + ); + } + + public function revert_data() + { + return array( + array('custom', array(array($this, 'delete_custom_profile_field_data'))), + ); + } + + public function create_custom_field() + { + $sql = 'SELECT MAX(field_order) as max_field_order + FROM ' . PROFILE_FIELDS_TABLE; + $result = $this->db->sql_query($sql); + $max_field_order = (int) $this->db->sql_fetchfield('max_field_order'); + $this->db->sql_freeresult($result); + + $sql_ary = array_merge($this->profilefield_data, array( + 'field_order' => $max_field_order + 1, + )); + + $sql = 'INSERT INTO ' . PROFILE_FIELDS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + $field_id = (int) $this->db->sql_nextid(); + + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, PROFILE_LANG_TABLE); + + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE; + $result = $this->db->sql_query($sql); + $lang_name = (strpos($this->profilefield_name, 'phpbb_') === 0) ? strtoupper(substr($this->profilefield_name, 6)) : strtoupper($this->profilefield_name); + while ($lang_id = (int) $this->db->sql_fetchfield('lang_id')) + { + $insert_buffer->insert(array( + 'field_id' => (int) $field_id, + 'lang_id' => (int) $lang_id, + 'lang_name' => $lang_name, + 'lang_explain' => '', + 'lang_default_value' => '', + )); + } + $this->db->sql_freeresult($result); + + $insert_buffer->flush(); + } + + /** + * Create Custom profile fields languguage entries + */ + public function create_language_entries() + { + $field_id = $this->get_custom_profile_field_id(); + + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, PROFILE_FIELDS_LANG_TABLE); + + $sql = 'SELECT lang_id + FROM ' . LANG_TABLE; + $result = $this->db->sql_query($sql); + while ($lang_id = (int) $this->db->sql_fetchfield('lang_id')) + { + foreach ($this->profilefield_language_data as $language_data) + { + $insert_buffer->insert(array_merge(array( + 'field_id' => (int) $field_id, + 'lang_id' => (int) $lang_id, + ), $language_data)); + } + } + $this->db->sql_freeresult($result); + + $insert_buffer->flush(); + } + + /** + * Clean database when reverting the migration + */ + public function delete_custom_profile_field_data() + { + $field_id = $this->get_custom_profile_field_id(); + + $sql = 'DELETE FROM ' . PROFILE_FIELDS_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . PROFILE_LANG_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . PROFILE_FIELDS_LANG_TABLE . ' + WHERE field_id = ' . (int) $field_id; + $this->db->sql_query($sql); + } + + /** + * Get custom profile field id + * @return int custom profile filed id + */ + public function get_custom_profile_field_id() + { + $sql = 'SELECT field_id + FROM ' . PROFILE_FIELDS_TABLE . " + WHERE field_name = '" . $this->profilefield_name . "'"; + $result = $this->db->sql_query($sql); + $field_id = (int) $this->db->sql_fetchfield('field_id'); + $this->db->sql_freeresult($result); + + return $field_id; + } + + /** + * @param int $start Start of staggering step + * @return mixed int start of the next step, null if the end was reached + */ + public function convert_user_field_to_custom_field($start) + { + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->table_prefix . 'profile_fields_data'); + $limit = 250; + $converted_users = 0; + $start = $start ?: 0; + + $sql = 'SELECT user_id, ' . $this->user_column_name . ' + FROM ' . $this->table_prefix . 'users + WHERE ' . $this->user_column_name . " <> '' + ORDER BY user_id"; + $result = $this->db->sql_query_limit($sql, $limit, $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $converted_users++; + + $cp_data = array( + 'pf_' . $this->profilefield_name => $row[$this->user_column_name], + ); + + $sql = 'UPDATE ' . $this->table_prefix . 'profile_fields_data + SET ' . $this->db->sql_build_array('UPDATE', $cp_data) . ' + WHERE user_id = ' . (int) $row['user_id']; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + $cp_data['user_id'] = (int) $row['user_id']; + $cp_data = array_merge($this->get_insert_sql_array(), $cp_data); + $insert_buffer->insert($cp_data); + } + } + $this->db->sql_freeresult($result); + + $insert_buffer->flush(); + + if ($converted_users < $limit) + { + // No more users left, we are done... + return; + } + + return $start + $limit; + } + + protected function get_insert_sql_array() + { + static $profile_row; + + if ($profile_row === null) + { + /* @var $manager \phpbb\profilefields\manager */ + $manager = $this->container->get('profilefields.manager'); + $profile_row = $manager->build_insert_sql_array(array()); + } + + return $profile_row; + } +} diff --git a/phpbb/db/migration/schema_generator.php b/phpbb/db/migration/schema_generator.php new file mode 100644 index 0000000..c579e25 --- /dev/null +++ b/phpbb/db/migration/schema_generator.php @@ -0,0 +1,242 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration; + +/** +* The schema generator generates the schema based on the existing migrations +*/ +class schema_generator +{ + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\db\tools\tools_interface */ + protected $db_tools; + + /** @var array */ + protected $class_names; + + /** @var string */ + protected $table_prefix; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var array */ + protected $tables; + + /** @var array */ + protected $dependencies = array(); + + /** + * Constructor + */ + public function __construct(array $class_names, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $phpbb_root_path, $php_ext, $table_prefix) + { + $this->config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + $this->class_names = $class_names; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->table_prefix = $table_prefix; + } + + /** + * Loads all migrations and their application state from the database. + * + * @return array + */ + public function get_schema() + { + if (!empty($this->tables)) + { + return $this->tables; + } + + $migrations = $this->class_names; + + $tree = array(); + $check_dependencies = true; + while (!empty($migrations)) + { + foreach ($migrations as $key => $migration_class) + { + // Unset classes that are not a valid migration + if (\phpbb\db\migrator::is_migration($migration_class) === false) + { + unset($migrations[$key]); + continue; + } + + $open_dependencies = array_diff($migration_class::depends_on(), $tree); + + if (empty($open_dependencies)) + { + $migration = new $migration_class($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix); + $tree[] = $migration_class; + $migration_key = array_search($migration_class, $migrations); + + foreach ($migration->update_schema() as $change_type => $data) + { + if ($change_type === 'add_tables') + { + foreach ($data as $table => $table_data) + { + $this->tables[$table] = $table_data; + } + } + else if ($change_type === 'drop_tables') + { + foreach ($data as $table) + { + unset($this->tables[$table]); + } + } + else if ($change_type === 'add_columns') + { + foreach ($data as $table => $add_columns) + { + foreach ($add_columns as $column => $column_data) + { + if (isset($column_data['after'])) + { + $columns = $this->tables[$table]['COLUMNS']; + $offset = array_search($column_data['after'], array_keys($columns)); + unset($column_data['after']); + + if ($offset === false) + { + $this->tables[$table]['COLUMNS'][$column] = array_values($column_data); + } + else + { + $this->tables[$table]['COLUMNS'] = array_merge(array_slice($columns, 0, $offset + 1, true), array($column => array_values($column_data)), array_slice($columns, $offset)); + } + } + else + { + $this->tables[$table]['COLUMNS'][$column] = $column_data; + } + } + } + } + else if ($change_type === 'change_columns') + { + foreach ($data as $table => $change_columns) + { + foreach ($change_columns as $column => $column_data) + { + $this->tables[$table]['COLUMNS'][$column] = $column_data; + } + } + } + else if ($change_type === 'drop_columns') + { + foreach ($data as $table => $drop_columns) + { + if (is_array($drop_columns)) + { + foreach ($drop_columns as $column) + { + unset($this->tables[$table]['COLUMNS'][$column]); + } + } + else + { + unset($this->tables[$table]['COLUMNS'][$drop_columns]); + } + } + } + else if ($change_type === 'add_unique_index') + { + foreach ($data as $table => $add_index) + { + foreach ($add_index as $key => $index_data) + { + $this->tables[$table]['KEYS'][$key] = array('UNIQUE', $index_data); + } + } + } + else if ($change_type === 'add_index') + { + foreach ($data as $table => $add_index) + { + foreach ($add_index as $key => $index_data) + { + $this->tables[$table]['KEYS'][$key] = array('INDEX', $index_data); + } + } + } + else if ($change_type === 'drop_keys') + { + foreach ($data as $table => $drop_keys) + { + foreach ($drop_keys as $key) + { + unset($this->tables[$table]['KEYS'][$key]); + } + } + } + else + { + var_dump($change_type); + } + } + unset($migrations[$migration_key]); + } + else if ($check_dependencies) + { + $this->dependencies = array_merge($this->dependencies, $open_dependencies); + } + } + + // Only run this check after the first run + if ($check_dependencies) + { + $this->check_dependencies(); + $check_dependencies = false; + } + } + + ksort($this->tables); + return $this->tables; + } + + /** + * Check if one of the migrations files' dependencies can't be resolved + * by the supplied list of migrations + * + * @throws \UnexpectedValueException If a dependency can't be resolved + */ + protected function check_dependencies() + { + // Strip duplicate values from array + $this->dependencies = array_unique($this->dependencies); + + foreach ($this->dependencies as $dependency) + { + if (!in_array($dependency, $this->class_names)) + { + throw new \UnexpectedValueException("Unable to resolve the dependency '$dependency'"); + } + } + } +} diff --git a/phpbb/db/migration/tool/config.php b/phpbb/db/migration/tool/config.php new file mode 100644 index 0000000..a351c48 --- /dev/null +++ b/phpbb/db/migration/tool/config.php @@ -0,0 +1,165 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\tool; + +/** +* Migration config tool +*/ +class config implements \phpbb\db\migration\tool\tool_interface +{ + /** @var \phpbb\config\config */ + protected $config; + + /** + * Constructor + * + * @param \phpbb\config\config $config + */ + public function __construct(\phpbb\config\config $config) + { + $this->config = $config; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'config'; + } + + /** + * Add a config setting. + * + * @param string $config_name The name of the config setting + * you would like to add + * @param mixed $config_value The value of the config setting + * @param bool $is_dynamic True if it is dynamic (changes very often) + * and should not be stored in the cache, false if not. + * @return null + */ + public function add($config_name, $config_value, $is_dynamic = false) + { + if (isset($this->config[$config_name])) + { + return; + } + + $this->config->set($config_name, $config_value, !$is_dynamic); + } + + /** + * Update an existing config setting. + * + * @param string $config_name The name of the config setting you would + * like to update + * @param mixed $config_value The value of the config setting + * @return null + * @throws \phpbb\db\migration\exception + */ + public function update($config_name, $config_value) + { + if (!isset($this->config[$config_name])) + { + throw new \phpbb\db\migration\exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->set($config_name, $config_value); + } + + /** + * Update a config setting if the first argument equal to the + * current config value + * + * @param string $compare If equal to the current config value, will be + * updated to the new config value, otherwise not + * @param string $config_name The name of the config setting you would + * like to update + * @param mixed $config_value The value of the config setting + * @return null + * @throws \phpbb\db\migration\exception + */ + public function update_if_equals($compare, $config_name, $config_value) + { + if (!isset($this->config[$config_name])) + { + throw new \phpbb\db\migration\exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config->set_atomic($config_name, $compare, $config_value); + } + + /** + * Remove an existing config setting. + * + * @param string $config_name The name of the config setting you would + * like to remove + * @return null + */ + public function remove($config_name) + { + if (!isset($this->config[$config_name])) + { + return; + } + + $this->config->delete($config_name); + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + if (count($arguments) == 1) + { + $arguments[] = ''; + } + break; + + case 'update_if_equals': + $call = 'update_if_equals'; + + // Set to the original value if the current value is what we compared to originally + $arguments = array( + $arguments[2], + $arguments[1], + $arguments[0], + ); + break; + + case 'reverse': + // Reversing a reverse is just the call itself + $call = array_shift($arguments); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpbb/db/migration/tool/config_text.php b/phpbb/db/migration/tool/config_text.php new file mode 100644 index 0000000..5fe9a25 --- /dev/null +++ b/phpbb/db/migration/tool/config_text.php @@ -0,0 +1,130 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\tool; + +/** +* Migration config_text tool +*/ +class config_text implements \phpbb\db\migration\tool\tool_interface +{ + /** @var \phpbb\config\db_text */ + protected $config_text; + + /** + * Constructor + * + * @param \phpbb\config\db_text $config_text + */ + public function __construct(\phpbb\config\db_text $config_text) + { + $this->config_text = $config_text; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'config_text'; + } + + /** + * Add a config_text setting. + * + * @param string $config_name The name of the config_text setting + * you would like to add + * @param mixed $config_value The value of the config_text setting + * @return null + */ + public function add($config_name, $config_value) + { + if (!is_null($this->config_text->get($config_name))) + { + return; + } + + $this->config_text->set($config_name, $config_value); + } + + /** + * Update an existing config_text setting. + * + * @param string $config_name The name of the config_text setting you would + * like to update + * @param mixed $config_value The value of the config_text setting + * @return null + * @throws \phpbb\db\migration\exception + */ + public function update($config_name, $config_value) + { + if (is_null($this->config_text->get($config_name))) + { + throw new \phpbb\db\migration\exception('CONFIG_NOT_EXIST', $config_name); + } + + $this->config_text->set($config_name, $config_value); + } + + /** + * Remove an existing config_text setting. + * + * @param string $config_name The name of the config_text setting you would + * like to remove + * @return null + */ + public function remove($config_name) + { + if (is_null($this->config_text->get($config_name))) + { + return; + } + + $this->config_text->delete($config_name); + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + if (count($arguments) == 1) + { + $arguments[] = ''; + } + break; + + case 'reverse': + // Reversing a reverse is just the call itself + $call = array_shift($arguments); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpbb/db/migration/tool/module.php b/phpbb/db/migration/tool/module.php new file mode 100644 index 0000000..e5133c8 --- /dev/null +++ b/phpbb/db/migration/tool/module.php @@ -0,0 +1,556 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\tool; + +use phpbb\module\exception\module_exception; + +/** +* Migration module management tool +*/ +class module implements \phpbb\db\migration\tool\tool_interface +{ + /** @var \phpbb\cache\service */ + protected $cache; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\module\module_manager */ + protected $module_manager; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $modules_table; + + /** @var array */ + protected $module_categories = array(); + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\cache\service $cache + * @param \phpbb\user $user + * @param \phpbb\module\module_manager $module_manager + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $modules_table + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\user $user, \phpbb\module\module_manager $module_manager, $phpbb_root_path, $php_ext, $modules_table) + { + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->module_manager = $module_manager; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->modules_table = $modules_table; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'module'; + } + + /** + * Module Exists + * + * Check if a module exists + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string|bool $parent The parent module_id|module_langname (0 for no parent). + * Use false to ignore the parent check and check class wide. + * @param int|string $module The module_id|module_langname you would like to + * check for to see if it exists + * @param bool $lazy Checks lazily if the module exists. Returns true if it exists in at + * least one given parent. + * @return bool true if module exists in *all* given parents, false if not in any given parent; + * true if ignoring parent check and module exists class wide, false if not found at all. + */ + public function exists($class, $parent, $module, $lazy = false) + { + // the main root directory should return true + if (!$module) + { + return true; + } + + $parent_sqls = []; + if ($parent !== false) + { + $parents = $this->get_parent_module_id($parent, $module, false); + if ($parents === false) + { + return false; + } + + foreach ((array) $parents as $parent_id) + { + $parent_sqls[] = 'AND parent_id = ' . (int) $parent_id; + } + } + else + { + $parent_sqls[] = ''; + } + + foreach ($parent_sqls as $parent_sql) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql + AND " . ((is_numeric($module)) ? 'module_id = ' . (int) $module : "module_langname = '" . $this->db->sql_escape($module) . "'"); + $result = $this->db->sql_query($sql); + $module_id = $this->db->sql_fetchfield('module_id'); + $this->db->sql_freeresult($result); + + if (!$lazy && !$module_id) + { + return false; + } + if ($lazy && $module_id) + { + return true; + } + } + + // Returns true, if modules exist in all parents and false otherwise + return !$lazy; + } + + /** + * Module Add + * + * Add a new module + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string $parent The parent module_id|module_langname (0 for no parent) + * @param array $data an array of the data on the new \module. + * This can be setup in two different ways. + * 1. The "manual" way. For inserting a category or one at a time. + * It will be merged with the base array shown a bit below, + * but at the least requires 'module_langname' to be sent, and, + * if you want to create a module (instead of just a category) you must + * send module_basename and module_mode. + * array( + * 'module_enabled' => 1, + * 'module_display' => 1, + * 'module_basename' => '', + * 'module_class' => $class, + * 'parent_id' => (int) $parent, + * 'module_langname' => '', + * 'module_mode' => '', + * 'module_auth' => '', + * ) + * 2. The "automatic" way. For inserting multiple at a time based on the + * specs in the info file for the module(s). For this to work the + * modules must be correctly setup in the info file. + * An example follows (this would insert the settings, log, and flag + * modes from the includes/acp/info/acp_asacp.php file): + * array( + * 'module_basename' => 'asacp', + * 'modes' => array('settings', 'log', 'flag'), + * ) + * Optionally you may not send 'modes' and it will insert all of the + * modules in that info file. + * path, specify that here + * @return null + * @throws \phpbb\db\migration\exception + */ + public function add($class, $parent = 0, $data = array()) + { + global $user, $phpbb_log; + + // allow sending the name as a string in $data to create a category + if (!is_array($data)) + { + $data = array('module_langname' => $data); + } + + $parents = (array) $this->get_parent_module_id($parent, $data); + + if (!isset($data['module_langname'])) + { + // The "automatic" way + $basename = (isset($data['module_basename'])) ? $data['module_basename'] : ''; + $module = $this->get_module_info($class, $basename); + + foreach ($module['modes'] as $mode => $module_info) + { + if (!isset($data['modes']) || in_array($mode, $data['modes'])) + { + $new_module = array( + 'module_basename' => $basename, + 'module_langname' => $module_info['title'], + 'module_mode' => $mode, + 'module_auth' => $module_info['auth'], + 'module_display' => (isset($module_info['display'])) ? $module_info['display'] : true, + 'before' => (isset($module_info['before'])) ? $module_info['before'] : false, + 'after' => (isset($module_info['after'])) ? $module_info['after'] : false, + ); + + // Run the "manual" way with the data we've collected. + foreach ($parents as $parent) + { + $this->add($class, $parent, $new_module); + } + } + } + + return; + } + + foreach ($parents as $parent) + { + $data['parent_id'] = $parent; + + // The "manual" way + if (!$this->exists($class, false, $parent)) + { + throw new \phpbb\db\migration\exception('MODULE_NOT_EXIST', $parent); + } + + if ($this->exists($class, $parent, $data['module_langname'])) + { + throw new \phpbb\db\migration\exception('MODULE_EXISTS', $data['module_langname']); + } + + $module_data = array( + 'module_enabled' => (isset($data['module_enabled'])) ? $data['module_enabled'] : 1, + 'module_display' => (isset($data['module_display'])) ? $data['module_display'] : 1, + 'module_basename' => (isset($data['module_basename'])) ? $data['module_basename'] : '', + 'module_class' => $class, + 'parent_id' => (int) $parent, + 'module_langname' => (isset($data['module_langname'])) ? $data['module_langname'] : '', + 'module_mode' => (isset($data['module_mode'])) ? $data['module_mode'] : '', + 'module_auth' => (isset($data['module_auth'])) ? $data['module_auth'] : '', + ); + + try + { + $this->module_manager->update_module_data($module_data); + + // Success + $module_log_name = ((isset($this->user->lang[$data['module_langname']])) ? $this->user->lang[$data['module_langname']] : $data['module_langname']); + $phpbb_log->add('admin', (isset($user->data['user_id'])) ? $user->data['user_id'] : ANONYMOUS, $user->ip, 'LOG_MODULE_ADD', false, array($module_log_name)); + + // Move the module if requested above/below an existing one + if (isset($data['before']) && $data['before']) + { + $before_mode = $before_langname = ''; + if (is_array($data['before'])) + { + // Restore legacy-legacy behaviour from phpBB 3.0 + list($before_mode, $before_langname) = $data['before']; + } + else + { + // Legacy behaviour from phpBB 3.1+ + $before_langname = $data['before']; + } + + $sql = 'SELECT left_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND parent_id = " . (int) $parent . " + AND module_langname = '" . $this->db->sql_escape($before_langname) . "'" + . (($before_mode) ? " AND module_mode = '" . $this->db->sql_escape($before_mode) . "'" : ''); + $result = $this->db->sql_query($sql); + $to_left = (int) $this->db->sql_fetchfield('left_id'); + $this->db->sql_freeresult($result); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND left_id >= $to_left + AND left_id < {$module_data['left_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = $to_left, right_id = " . ($to_left + 1) . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND module_id = {$module_data['module_id']}"; + $this->db->sql_query($sql); + } + else if (isset($data['after']) && $data['after']) + { + $after_mode = $after_langname = ''; + if (is_array($data['after'])) + { + // Restore legacy-legacy behaviour from phpBB 3.0 + list($after_mode, $after_langname) = $data['after']; + } + else + { + // Legacy behaviour from phpBB 3.1+ + $after_langname = $data['after']; + } + + $sql = 'SELECT right_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND parent_id = " . (int) $parent . " + AND module_langname = '" . $this->db->sql_escape($after_langname) . "'" + . (($after_mode) ? " AND module_mode = '" . $this->db->sql_escape($after_mode) . "'" : ''); + $result = $this->db->sql_query($sql); + $to_right = (int) $this->db->sql_fetchfield('right_id'); + $this->db->sql_freeresult($result); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND left_id >= $to_right + AND left_id < {$module_data['left_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . ' + SET left_id = ' . ($to_right + 1) . ', right_id = ' . ($to_right + 2) . " + WHERE module_class = '" . $this->db->sql_escape($class) . "' + AND module_id = {$module_data['module_id']}"; + $this->db->sql_query($sql); + } + } + catch (module_exception $e) + { + // Error + throw new \phpbb\db\migration\exception('MODULE_ERROR', $e->getMessage()); + } + } + + // Clear the Modules Cache + $this->module_manager->remove_cache_file($class); + } + + /** + * Module Remove + * + * Remove a module + * + * @param string $class The module class(acp|mcp|ucp) + * @param int|string|bool $parent The parent module_id|module_langname(0 for no parent). + * Use false to ignore the parent check and check class wide. + * @param int|string $module The module id|module_langname + * specify that here + * @return null + * @throws \phpbb\db\migration\exception + */ + public function remove($class, $parent = 0, $module = '') + { + // Imitation of module_add's "automatic" and "manual" method so the uninstaller works from the same set of instructions for umil_auto + if (is_array($module)) + { + if (isset($module['module_langname'])) + { + // Manual Method + return $this->remove($class, $parent, $module['module_langname']); + } + + // Failed. + if (!isset($module['module_basename'])) + { + throw new \phpbb\db\migration\exception('MODULE_NOT_EXIST'); + } + + // Automatic method + $basename = $module['module_basename']; + $module_info = $this->get_module_info($class, $basename); + + foreach ($module_info['modes'] as $mode => $info) + { + if (!isset($module['modes']) || in_array($mode, $module['modes'])) + { + $this->remove($class, $parent, $info['title']); + } + } + } + else + { + if (!$this->exists($class, $parent, $module, true)) + { + return; + } + + $parent_sql = ''; + if ($parent !== false) + { + $parents = (array) $this->get_parent_module_id($parent, $module); + $parent_sql = 'AND ' . $this->db->sql_in_set('parent_id', $parents); + } + + $module_ids = array(); + if (!is_numeric($module)) + { + $sql = 'SELECT module_id + FROM ' . $this->modules_table . " + WHERE module_langname = '" . $this->db->sql_escape($module) . "' + AND module_class = '" . $this->db->sql_escape($class) . "' + $parent_sql"; + $result = $this->db->sql_query($sql); + while ($module_id = $this->db->sql_fetchfield('module_id')) + { + $module_ids[] = (int) $module_id; + } + $this->db->sql_freeresult($result); + } + else + { + $module_ids[] = (int) $module; + } + + foreach ($module_ids as $module_id) + { + $this->module_manager->delete_module($module_id, $class); + } + + $this->module_manager->remove_cache_file($class); + } + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + + case 'reverse': + // Reversing a reverse is just the call itself + $call = array_shift($arguments); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } + + /** + * Wrapper for \acp_modules::get_module_infos() + * + * @param string $class Module Class + * @param string $basename Module Basename + * @return array Module Information + * @throws \phpbb\db\migration\exception + */ + protected function get_module_info($class, $basename) + { + $module = $this->module_manager->get_module_infos($class, $basename, true); + + if (empty($module)) + { + throw new \phpbb\db\migration\exception('MODULE_INFO_FILE_NOT_EXIST', $class, $basename); + } + + return array_pop($module); + } + + /** + * Get the list of installed module categories + * key - module_id + * value - module_langname + * + * @return null + */ + protected function get_categories_list() + { + // Select the top level categories + // and 2nd level [sub]categories + $sql = 'SELECT m2.module_id, m2.module_langname + FROM ' . $this->modules_table . ' m1, ' . $this->modules_table . " m2 + WHERE m1.parent_id = 0 + AND (m1.module_id = m2.module_id OR m2.parent_id = m1.module_id) + ORDER BY m1.module_id, m2.module_id ASC"; + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $this->module_categories[(int) $row['module_id']] = $row['module_langname']; + } + $this->db->sql_freeresult($result); + } + + /** + * Get parent module id + * + * @param string|int $parent_id The parent module_id|module_langname + * @param int|string|array $data The module_id, module_langname for existance checking or module data array for adding + * @param bool $throw_exception The flag indicating if exception should be thrown on error + * @return mixed The int parent module_id, an array of int parent module_id values or false + * @throws \phpbb\db\migration\exception + */ + public function get_parent_module_id($parent_id, $data = '', $throw_exception = true) + { + // Allow '' to be sent as 0 + $parent_id = $parent_id ?: 0; + + if (!is_numeric($parent_id)) + { + // Refresh the $module_categories array + $this->get_categories_list(); + + // Search for the parent module_langname + $ids = array_keys($this->module_categories, $parent_id); + + switch (count($ids)) + { + // No parent with the given module_langname exist + case 0: + if ($throw_exception) + { + throw new \phpbb\db\migration\exception('MODULE_NOT_EXIST', $parent_id); + } + + return false; + break; + + // Return the module id + case 1: + return (int) $ids[0]; + break; + + default: + // This represents the old behaviour of phpBB 3.0 + return $ids; + break; + } + } + + return $parent_id; + } +} diff --git a/phpbb/db/migration/tool/permission.php b/phpbb/db/migration/tool/permission.php new file mode 100644 index 0000000..4b53aa3 --- /dev/null +++ b/phpbb/db/migration/tool/permission.php @@ -0,0 +1,652 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\tool; + +/** +* Migration permission management tool +*/ +class permission implements \phpbb\db\migration\tool\tool_interface +{ + /** @var \phpbb\auth\auth */ + protected $auth; + + /** @var \phpbb\cache\service */ + protected $cache; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\cache\service $cache + * @param \phpbb\auth\auth $auth + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext) + { + $this->db = $db; + $this->cache = $cache; + $this->auth = $auth; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function get_name() + { + return 'permission'; + } + + /** + * Permission Exists + * + * Check if a permission (auth) setting exists + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return bool true if it exists, false if not + */ + public function exists($auth_option, $global = true) + { + if ($global) + { + $type_sql = ' AND is_global = 1'; + } + else + { + $type_sql = ' AND is_local = 1'; + } + + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" + . $type_sql; + $result = $this->db->sql_query($sql); + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row) + { + return true; + } + + return false; + } + + /** + * Permission Add + * + * Add a permission (auth) option + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @param int|false $copy_from If set, contains the id of the permission from which to copy the new one. + * @return null + */ + public function add($auth_option, $global = true, $copy_from = false) + { + if ($this->exists($auth_option, $global)) + { + return; + } + + // We've added permissions, so set to true to notify the user. + $this->permissions_added = true; + + if (!class_exists('auth_admin')) + { + include($this->phpbb_root_path . 'includes/acp/auth.' . $this->php_ext); + } + $auth_admin = new \auth_admin(); + + // We have to add a check to see if the !$global (if global, local, and if local, global) permission already exists. If it does, acl_add_option currently has a bug which would break the ACL system, so we are having a work-around here. + if ($this->exists($auth_option, !$global)) + { + $sql_ary = array( + 'is_global' => 1, + 'is_local' => 1, + ); + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'"; + $this->db->sql_query($sql); + } + else + { + if ($global) + { + $auth_admin->acl_add_option(array('global' => array($auth_option))); + } + else + { + $auth_admin->acl_add_option(array('local' => array($auth_option))); + } + } + + // The permission has been added, now we can copy it if needed + if ($copy_from && isset($auth_admin->acl_options['id'][$copy_from])) + { + $old_id = $auth_admin->acl_options['id'][$copy_from]; + $new_id = $auth_admin->acl_options['id'][$auth_option]; + + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE); + + foreach ($tables as $table) + { + $sql = 'SELECT * + FROM ' . $table . ' + WHERE auth_option_id = ' . $old_id; + $result = $this->db->sql_query($sql); + + $sql_ary = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $row['auth_option_id'] = $new_id; + $sql_ary[] = $row; + } + $this->db->sql_freeresult($result); + + if (!empty($sql_ary)) + { + $this->db->sql_multi_insert($table, $sql_ary); + } + } + + $auth_admin->acl_clear_prefetch(); + } + } + + /** + * Permission Remove + * + * Remove a permission (auth) option + * + * @param string $auth_option The name of the permission (auth) option + * @param bool $global True for checking a global permission setting, + * False for a local permission setting + * @return null + */ + public function remove($auth_option, $global = true) + { + if (!$this->exists($auth_option, $global)) + { + return; + } + + if ($global) + { + $type_sql = ' AND is_global = 1'; + } + else + { + $type_sql = ' AND is_local = 1'; + } + $sql = 'SELECT auth_option_id, is_global, is_local + FROM ' . ACL_OPTIONS_TABLE . " + WHERE auth_option = '" . $this->db->sql_escape($auth_option) . "'" . + $type_sql; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $id = (int) $row['auth_option_id']; + + // If it is a local and global permission, do not remove the row! :P + if ($row['is_global'] && $row['is_local']) + { + $sql = 'UPDATE ' . ACL_OPTIONS_TABLE . ' + SET ' . (($global) ? 'is_global = 0' : 'is_local = 0') . ' + WHERE auth_option_id = ' . $id; + $this->db->sql_query($sql); + } + else + { + // Delete time + $tables = array(ACL_GROUPS_TABLE, ACL_ROLES_DATA_TABLE, ACL_USERS_TABLE, ACL_OPTIONS_TABLE); + foreach ($tables as $table) + { + $this->db->sql_query('DELETE FROM ' . $table . ' + WHERE auth_option_id = ' . $id); + } + } + + // Purge the auth cache + $this->cache->destroy('_acl_options'); + $this->auth->acl_clear_prefetch(); + } + + /** + * Add a new permission role + * + * @param string $role_name The new role name + * @param string $role_type The type (u_, m_, a_) + * @param string $role_description Description of the new role + * + * @return null + */ + public function role_add($role_name, $role_type, $role_description = '') + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if ($role_id) + { + return; + } + + $sql = 'SELECT MAX(role_order) AS max_role_order + FROM ' . ACL_ROLES_TABLE . " + WHERE role_type = '" . $this->db->sql_escape($role_type) . "'"; + $this->db->sql_query($sql); + $role_order = (int) $this->db->sql_fetchfield('max_role_order'); + $role_order = (!$role_order) ? 1 : $role_order + 1; + + $sql_ary = array( + 'role_name' => $role_name, + 'role_description' => $role_description, + 'role_type' => $role_type, + 'role_order' => $role_order, + ); + + $sql = 'INSERT INTO ' . ACL_ROLES_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + } + + /** + * Update the name on a permission role + * + * @param string $old_role_name The old role name + * @param string $new_role_name The new role name + * @return null + * @throws \phpbb\db\migration\exception + */ + public function role_update($old_role_name, $new_role_name) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new \phpbb\db\migration\exception('ROLE_NOT_EXIST', $old_role_name); + } + + $sql = 'UPDATE ' . ACL_ROLES_TABLE . " + SET role_name = '" . $this->db->sql_escape($new_role_name) . "' + WHERE role_name = '" . $this->db->sql_escape($old_role_name) . "'"; + $this->db->sql_query($sql); + } + + /** + * Remove a permission role + * + * @param string $role_name The role name to remove + * @return null + */ + public function role_remove($role_name) + { + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($role_name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + return; + } + + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + + $this->auth->acl_clear_prefetch(); + } + + /** + * Permission Set + * + * Allows you to set permissions for a certain group/role + * + * @param string $name The name of the role/group + * @param string|array $auth_option The auth_option or array of + * auth_options you would like to set + * @param string $type The type (role|group) + * @param bool $has_permission True if you want to give them permission, + * false if you want to deny them permission + * @return null + * @throws \phpbb\db\migration\exception + */ + public function permission_set($name, $auth_option, $type = 'role', $has_permission = true) + { + if (!is_array($auth_option)) + { + $auth_option = array($auth_option); + } + + $new_auth = array(); + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $new_auth[] = (int) $row['auth_option_id']; + } + $this->db->sql_freeresult($result); + + if (empty($new_auth)) + { + return; + } + + $current_auth = array(); + + $type = (string) $type; // Prevent PHP bug. + + switch ($type) + { + case 'role': + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new \phpbb\db\migration\exception('ROLE_NOT_EXIST', $name); + } + + $sql = 'SELECT auth_option_id, auth_setting + FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE role_id = ' . $role_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $current_auth[$row['auth_option_id']] = $row['auth_setting']; + } + $this->db->sql_freeresult($result); + break; + + case 'group': + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + + if (!$group_id) + { + throw new \phpbb\db\migration\exception('GROUP_NOT_EXIST', $name); + } + + // If the group has a role set for them we will add the requested permissions to that role. + $sql = 'SELECT auth_role_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id <> 0 + AND forum_id = 0'; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); + if ($role_id) + { + $sql = 'SELECT role_name, role_type + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + $role_data = $this->db->sql_fetchrow(); + $role_name = $role_data['role_name']; + $role_type = $role_data['role_type']; + + // Filter new auth options to match the role type: a_ | f_ | m_ | u_ + // Set new auth options to the role only if options matching the role type were found + $auth_option = array_filter($auth_option, + function ($option) use ($role_type) + { + return strpos($option, $role_type) === 0; + } + ); + + if (count($auth_option)) + { + return $this->permission_set($role_name, $auth_option, 'role', $has_permission); + } + } + + $sql = 'SELECT auth_option_id, auth_setting + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $current_auth[$row['auth_option_id']] = $row['auth_setting']; + } + $this->db->sql_freeresult($result); + break; + } + + $sql_ary = array(); + switch ($type) + { + case 'role': + foreach ($new_auth as $auth_option_id) + { + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = array( + 'role_id' => $role_id, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => $has_permission, + ); + } + } + + $this->db->sql_multi_insert(ACL_ROLES_DATA_TABLE, $sql_ary); + break; + + case 'group': + foreach ($new_auth as $auth_option_id) + { + if (!isset($current_auth[$auth_option_id])) + { + $sql_ary[] = array( + 'group_id' => $group_id, + 'auth_option_id' => $auth_option_id, + 'auth_setting' => $has_permission, + ); + } + } + + $this->db->sql_multi_insert(ACL_GROUPS_TABLE, $sql_ary); + break; + } + + $this->auth->acl_clear_prefetch(); + } + + /** + * Permission Unset + * + * Allows you to unset (remove) permissions for a certain group/role + * + * @param string $name The name of the role/group + * @param string|array $auth_option The auth_option or array of + * auth_options you would like to set + * @param string $type The type (role|group) + * @return null + * @throws \phpbb\db\migration\exception + */ + public function permission_unset($name, $auth_option, $type = 'role') + { + if (!is_array($auth_option)) + { + $auth_option = array($auth_option); + } + + $to_remove = array(); + $sql = 'SELECT auth_option_id + FROM ' . ACL_OPTIONS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option', $auth_option); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $to_remove[] = (int) $row['auth_option_id']; + } + $this->db->sql_freeresult($result); + + if (empty($to_remove)) + { + return; + } + + $type = (string) $type; // Prevent PHP bug. + + switch ($type) + { + case 'role': + $sql = 'SELECT role_id + FROM ' . ACL_ROLES_TABLE . " + WHERE role_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('role_id'); + + if (!$role_id) + { + throw new \phpbb\db\migration\exception('ROLE_NOT_EXIST', $name); + } + + $sql = 'DELETE FROM ' . ACL_ROLES_DATA_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove) . ' + AND role_id = ' . (int) $role_id; + $this->db->sql_query($sql); + break; + + case 'group': + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + + if (!$group_id) + { + throw new \phpbb\db\migration\exception('GROUP_NOT_EXIST', $name); + } + + // If the group has a role set for them we will remove the requested permissions from that role. + $sql = 'SELECT auth_role_id + FROM ' . ACL_GROUPS_TABLE . ' + WHERE group_id = ' . $group_id . ' + AND auth_role_id <> 0'; + $this->db->sql_query($sql); + $role_id = (int) $this->db->sql_fetchfield('auth_role_id'); + if ($role_id) + { + $sql = 'SELECT role_name + FROM ' . ACL_ROLES_TABLE . ' + WHERE role_id = ' . $role_id; + $this->db->sql_query($sql); + $role_name = $this->db->sql_fetchfield('role_name'); + + return $this->permission_unset($role_name, $auth_option, 'role'); + } + + $sql = 'DELETE FROM ' . ACL_GROUPS_TABLE . ' + WHERE ' . $this->db->sql_in_set('auth_option_id', $to_remove); + $this->db->sql_query($sql); + break; + } + + $this->auth->acl_clear_prefetch(); + } + + /** + * {@inheritdoc} + */ + public function reverse() + { + $arguments = func_get_args(); + $original_call = array_shift($arguments); + + $call = false; + switch ($original_call) + { + case 'add': + $call = 'remove'; + break; + + case 'remove': + $call = 'add'; + break; + + case 'permission_set': + $call = 'permission_unset'; + break; + + case 'permission_unset': + $call = 'permission_set'; + break; + + case 'role_add': + $call = 'role_remove'; + break; + + case 'role_remove': + $call = 'role_add'; + break; + + case 'role_update': + // Set to the original value if the current value is what we compared to originally + $arguments = array( + $arguments[1], + $arguments[0], + ); + break; + + case 'reverse': + // Reversing a reverse is just the call itself + $call = array_shift($arguments); + break; + } + + if ($call) + { + return call_user_func_array(array(&$this, $call), $arguments); + } + } +} diff --git a/phpbb/db/migration/tool/tool_interface.php b/phpbb/db/migration/tool/tool_interface.php new file mode 100644 index 0000000..07cd243 --- /dev/null +++ b/phpbb/db/migration/tool/tool_interface.php @@ -0,0 +1,37 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\migration\tool; + +/** +* Migration tool interface +*/ +interface tool_interface +{ + /** + * Retrieve a short name used for commands in migrations. + * + * @return string short name + */ + public function get_name(); + + /** + * Reverse an original install action + * + * First argument is the original call to the class (e.g. add, remove) + * After the first argument, send the original arguments to the function in the original call + * + * @return null + */ + public function reverse(); +} diff --git a/phpbb/db/migrator.php b/phpbb/db/migrator.php new file mode 100644 index 0000000..2b0c66f --- /dev/null +++ b/phpbb/db/migrator.php @@ -0,0 +1,1037 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db; + +use phpbb\db\output_handler\migrator_output_handler_interface; +use phpbb\db\output_handler\null_migrator_output_handler; +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** +* The migrator is responsible for applying new migrations in the correct order. +*/ +class migrator +{ + /** + * @var ContainerInterface + */ + protected $container; + + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\db\tools\tools_interface */ + protected $db_tools; + + /** @var \phpbb\db\migration\helper */ + protected $helper; + + /** @var string */ + protected $table_prefix; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $migrations_table; + + /** + * State of all migrations + * + * (SELECT * FROM migrations table) + * + * @var array + */ + protected $migration_state = array(); + + /** + * Array of all migrations available to be run + * + * @var array + */ + protected $migrations = array(); + + /** + * Array of migrations that have been determined to be fulfillable + * + * @var array + */ + protected $fulfillable_migrations = array(); + + /** + * 'name,' 'class,' and 'state' of the last migration run + * + * 'effectively_installed' set and set to true if the migration was effectively_installed + * + * @var array + */ + protected $last_run_migration = false; + + /** + * The output handler. A null handler is configured by default. + * + * @var migrator_output_handler_interface + */ + protected $output_handler; + + /** + * Constructor of the database migrator + */ + public function __construct(ContainerInterface $container, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\db\tools\tools_interface $db_tools, $migrations_table, $phpbb_root_path, $php_ext, $table_prefix, $tools, \phpbb\db\migration\helper $helper) + { + $this->container = $container; + $this->config = $config; + $this->db = $db; + $this->db_tools = $db_tools; + $this->helper = $helper; + + $this->migrations_table = $migrations_table; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->table_prefix = $table_prefix; + + $this->output_handler = new null_migrator_output_handler(); + + foreach ($tools as $tool) + { + $this->tools[$tool->get_name()] = $tool; + } + + $this->tools['dbtools'] = $this->db_tools; + + $this->load_migration_state(); + } + + /** + * Set the output handler. + * + * @param migrator_output_handler_interface $handler The output handler + */ + public function set_output_handler(migrator_output_handler_interface $handler) + { + $this->output_handler = $handler; + } + + /** + * Loads all migrations and their application state from the database. + * + * @return null + */ + public function load_migration_state() + { + $this->migration_state = array(); + + // prevent errors in case the table does not exist yet + $this->db->sql_return_on_error(true); + + $sql = "SELECT * + FROM " . $this->migrations_table; + $result = $this->db->sql_query($sql); + + if (!$this->db->get_sql_error_triggered()) + { + while ($migration = $this->db->sql_fetchrow($result)) + { + $this->migration_state[$migration['migration_name']] = $migration; + + $this->migration_state[$migration['migration_name']]['migration_depends_on'] = unserialize($migration['migration_depends_on']); + $this->migration_state[$migration['migration_name']]['migration_data_state'] = !empty($migration['migration_data_state']) ? unserialize($migration['migration_data_state']) : ''; + } + } + + $this->db->sql_freeresult($result); + + $this->db->sql_return_on_error(false); + } + + /** + * Get an array with information about the last migration run. + * + * The array contains 'name', 'class' and 'state'. 'effectively_installed' is set + * and set to true if the last migration was effectively_installed. + * + * @return array + */ + public function get_last_run_migration() + { + return $this->last_run_migration; + } + + /** + * Sets the list of available migration class names to the given array. + * + * @param array $class_names An array of migration class names + * @return null + */ + public function set_migrations($class_names) + { + foreach ($class_names as $key => $class) + { + if (!self::is_migration($class)) + { + unset($class_names[$key]); + } + } + + $this->migrations = $class_names; + } + + /** + * Get the list of available migration class names + * + * @return array Array of all migrations available to be run + */ + public function get_migrations() + { + return $this->migrations; + } + + /** + * Get the list of available and not installed migration class names + * + * @return array + */ + public function get_installable_migrations() + { + $unfinished_migrations = array(); + + foreach ($this->migrations as $name) + { + if (!isset($this->migration_state[$name]) || + !$this->migration_state[$name]['migration_schema_done'] || + !$this->migration_state[$name]['migration_data_done']) + { + $unfinished_migrations[] = $name; + } + } + + return $unfinished_migrations; + } + + /** + * Runs a single update step from the next migration to be applied. + * + * The update step can either be a schema or a (partial) data update. To + * check if update() needs to be called again use the finished() method. + * + * @return null + */ + public function update() + { + $this->container->get('dispatcher')->disable(); + $this->update_do(); + $this->container->get('dispatcher')->enable(); + } + + /** + * Get a valid migration name from the migration state array in case the + * supplied name is not in the migration state list. + * + * @param string $name Migration name + * @return string Migration name + */ + protected function get_valid_name($name) + { + // Try falling back to a valid migration name with or without leading backslash + if (!isset($this->migration_state[$name])) + { + $prepended_name = ($name[0] == '\\' ? '' : '\\') . $name; + $prefixless_name = $name[0] == '\\' ? substr($name, 1) : $name; + + if (isset($this->migration_state[$prepended_name])) + { + $name = $prepended_name; + } + else if (isset($this->migration_state[$prefixless_name])) + { + $name = $prefixless_name; + } + } + + return $name; + } + + /** + * Effectively runs a single update step from the next migration to be applied. + * + * @return null + */ + protected function update_do() + { + foreach ($this->migrations as $name) + { + $name = $this->get_valid_name($name); + + if (!isset($this->migration_state[$name]) || + !$this->migration_state[$name]['migration_schema_done'] || + !$this->migration_state[$name]['migration_data_done']) + { + if (!$this->try_apply($name)) + { + continue; + } + else + { + return; + } + } + else + { + $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_DEBUG); + } + } + } + + /** + * Attempts to apply a step of the given migration or one of its dependencies + * + * @param string $name The class name of the migration + * @return bool Whether any update step was successfully run + * @throws \phpbb\db\migration\exception + */ + protected function try_apply($name) + { + if (!class_exists($name)) + { + $this->output_handler->write(array('MIGRATION_NOT_VALID', $name), migrator_output_handler_interface::VERBOSITY_DEBUG); + return false; + } + + $migration = $this->get_migration($name); + + $state = (isset($this->migration_state[$name])) ? + $this->migration_state[$name] : + array( + 'migration_depends_on' => $migration->depends_on(), + 'migration_schema_done' => false, + 'migration_data_done' => false, + 'migration_data_state' => '', + 'migration_start_time' => 0, + 'migration_end_time' => 0, + ); + + if (!empty($state['migration_depends_on'])) + { + $this->output_handler->write(array('MIGRATION_APPLY_DEPENDENCIES', $name), migrator_output_handler_interface::VERBOSITY_DEBUG); + } + + foreach ($state['migration_depends_on'] as $depend) + { + $depend = $this->get_valid_name($depend); + + // Test all possible namings before throwing exception + if ($this->unfulfillable($depend) !== false) + { + throw new \phpbb\db\migration\exception('MIGRATION_NOT_FULFILLABLE', $name, $depend); + } + + if (!isset($this->migration_state[$depend]) || + !$this->migration_state[$depend]['migration_schema_done'] || + !$this->migration_state[$depend]['migration_data_done']) + { + return $this->try_apply($depend); + } + } + + $this->last_run_migration = array( + 'name' => $name, + 'class' => $migration, + 'state' => $state, + 'task' => '', + ); + + if (!isset($this->migration_state[$name])) + { + if ($state['migration_start_time'] == 0 && $migration->effectively_installed()) + { + $state = array( + 'migration_depends_on' => $migration->depends_on(), + 'migration_schema_done' => true, + 'migration_data_done' => true, + 'migration_data_state' => '', + 'migration_start_time' => 0, + 'migration_end_time' => 0, + ); + + $this->last_run_migration['effectively_installed'] = true; + + $this->output_handler->write(array('MIGRATION_EFFECTIVELY_INSTALLED', $name), migrator_output_handler_interface::VERBOSITY_VERBOSE); + } + else + { + $state['migration_start_time'] = time(); + } + } + + $this->set_migration_state($name, $state); + + if (!$state['migration_schema_done']) + { + $verbosity = empty($state['migration_data_state']) ? + migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; + $this->output_handler->write(array('MIGRATION_SCHEMA_RUNNING', $name), $verbosity); + + $this->last_run_migration['task'] = 'process_schema_step'; + + $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? + $state['migration_data_state']['_total_time'] : 0.0; + $elapsed_time = microtime(true); + + $steps = $this->helper->get_schema_steps($migration->update_schema()); + $result = $this->process_data_step($steps, $state['migration_data_state']); + + $elapsed_time = microtime(true) - $elapsed_time; + $total_time += $elapsed_time; + + if (is_array($result)) + { + $result['_total_time'] = $total_time; + } + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_schema_done'] = ($result === true); + + if ($state['migration_schema_done']) + { + $this->output_handler->write(array('MIGRATION_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); + } + else + { + $this->output_handler->write(array('MIGRATION_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); + } + } + else if (!$state['migration_data_done']) + { + try + { + $verbosity = empty($state['migration_data_state']) ? + migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; + $this->output_handler->write(array('MIGRATION_DATA_RUNNING', $name), $verbosity); + + $this->last_run_migration['task'] = 'process_data_step'; + + $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? + $state['migration_data_state']['_total_time'] : 0.0; + $elapsed_time = microtime(true); + + $result = $this->process_data_step($migration->update_data(), $state['migration_data_state']); + + $elapsed_time = microtime(true) - $elapsed_time; + $total_time += $elapsed_time; + + if (is_array($result)) + { + $result['_total_time'] = $total_time; + } + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_data_done'] = ($result === true); + $state['migration_end_time'] = ($result === true) ? time() : 0; + + if ($state['migration_data_done']) + { + $this->output_handler->write(array('MIGRATION_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); + } + else + { + $this->output_handler->write(array('MIGRATION_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); + } + } + catch (\phpbb\db\migration\exception $e) + { + // Reset data state and revert the schema changes + $state['migration_data_state'] = ''; + $this->set_migration_state($name, $state); + + $this->revert_do($name); + + throw $e; + } + } + + $this->set_migration_state($name, $state); + + return true; + } + + /** + * Runs a single revert step from the last migration installed + * + * YOU MUST ADD/SET ALL MIGRATIONS THAT COULD BE DEPENDENT ON THE MIGRATION TO REVERT TO BEFORE CALLING THIS METHOD! + * The revert step can either be a schema or a (partial) data revert. To + * check if revert() needs to be called again use the migration_state() method. + * + * @param string $migration String migration name to revert (including any that depend on this migration) + */ + public function revert($migration) + { + $this->container->get('dispatcher')->disable(); + $this->revert_do($migration); + $this->container->get('dispatcher')->enable(); + } + + /** + * Effectively runs a single revert step from the last migration installed + * + * @param string $migration String migration name to revert (including any that depend on this migration) + * @return null + */ + protected function revert_do($migration) + { + if (!isset($this->migration_state[$migration])) + { + // Not installed + return; + } + + foreach ($this->migrations as $name) + { + $state = $this->migration_state($name); + + if ($state && in_array($migration, $state['migration_depends_on']) && ($state['migration_schema_done'] || $state['migration_data_done'])) + { + $this->revert_do($name); + return; + } + } + + $this->try_revert($migration); + } + + /** + * Attempts to revert a step of the given migration or one of its dependencies + * + * @param string $name The class name of the migration + * @return bool Whether any update step was successfully run + */ + protected function try_revert($name) + { + if (!class_exists($name)) + { + return false; + } + + $migration = $this->get_migration($name); + + $state = $this->migration_state[$name]; + + $this->last_run_migration = array( + 'name' => $name, + 'class' => $migration, + 'task' => '', + ); + + if ($state['migration_data_done']) + { + $verbosity = empty($state['migration_data_state']) ? + migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; + $this->output_handler->write(array('MIGRATION_REVERT_DATA_RUNNING', $name), $verbosity); + + $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? + $state['migration_data_state']['_total_time'] : 0.0; + $elapsed_time = microtime(true); + + $steps = array_merge($this->helper->reverse_update_data($migration->update_data()), $migration->revert_data()); + $result = $this->process_data_step($steps, $state['migration_data_state']); + + $elapsed_time = microtime(true) - $elapsed_time; + $total_time += $elapsed_time; + + if (is_array($result)) + { + $result['_total_time'] = $total_time; + } + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_data_done'] = ($result === true) ? false : true; + + $this->set_migration_state($name, $state); + + if (!$state['migration_data_done']) + { + $this->output_handler->write(array('MIGRATION_REVERT_DATA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); + } + else + { + $this->output_handler->write(array('MIGRATION_REVERT_DATA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); + } + } + else if ($state['migration_schema_done']) + { + $verbosity = empty($state['migration_data_state']) ? + migrator_output_handler_interface::VERBOSITY_VERBOSE : migrator_output_handler_interface::VERBOSITY_DEBUG; + $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_RUNNING', $name), $verbosity); + + $total_time = (is_array($state['migration_data_state']) && isset($state['migration_data_state']['_total_time'])) ? + $state['migration_data_state']['_total_time'] : 0.0; + $elapsed_time = microtime(true); + + $steps = $this->helper->get_schema_steps($migration->revert_schema()); + $result = $this->process_data_step($steps, $state['migration_data_state']); + + $elapsed_time = microtime(true) - $elapsed_time; + $total_time += $elapsed_time; + + if (is_array($result)) + { + $result['_total_time'] = $total_time; + } + + $state['migration_data_state'] = ($result === true) ? '' : $result; + $state['migration_schema_done'] = ($result === true) ? false : true; + + if (!$state['migration_schema_done']) + { + $sql = 'DELETE FROM ' . $this->migrations_table . " + WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + $this->last_run_migration = false; + unset($this->migration_state[$name]); + + $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_DONE', $name, $total_time), migrator_output_handler_interface::VERBOSITY_NORMAL); + } + else + { + $this->set_migration_state($name, $state); + + $this->output_handler->write(array('MIGRATION_REVERT_SCHEMA_IN_PROGRESS', $name, $elapsed_time), migrator_output_handler_interface::VERBOSITY_VERY_VERBOSE); + } + } + + return true; + } + + /** + * Process the data step of the migration + * + * @param array $steps The steps to run + * @param bool|string $state Current state of the migration + * @param bool $revert true to revert a data step + * @return bool|string migration state. True if completed, serialized array if not finished + * @throws \phpbb\db\migration\exception + */ + protected function process_data_step($steps, $state, $revert = false) + { + if (count($steps) === 0) + { + return true; + } + + $state = is_array($state) ? $state : false; + + // reverse order of steps if reverting + if ($revert === true) + { + $steps = array_reverse($steps); + } + + $step = $last_result = 0; + if ($state) + { + $step = $state['step']; + + // We send the result from last time to the callable function + $last_result = $state['result']; + } + + try + { + // Result will be null or true if everything completed correctly + // Stop after each update step, to let the updater control the script runtime + $result = $this->run_step($steps[$step], $last_result, $revert); + if (($result !== null && $result !== true) || $step + 1 < count($steps)) + { + return array( + 'result' => $result, + // Move on if the last call finished + 'step' => ($result !== null && $result !== true) ? $step : $step + 1, + ); + } + } + catch (\phpbb\db\migration\exception $e) + { + // We should try rolling back here + foreach ($steps as $reverse_step_identifier => $reverse_step) + { + // If we've reached the current step we can break because we reversed everything that was run + if ($reverse_step_identifier == $step) + { + break; + } + + // Reverse the step that was run + $result = $this->run_step($reverse_step, false, !$revert); + } + + throw $e; + } + + return true; + } + + /** + * Run a single step + * + * An exception should be thrown if an error occurs + * + * @param mixed $step Data step from migration + * @param mixed $last_result Result to pass to the callable (only for 'custom' method) + * @param bool $reverse False to install, True to attempt uninstallation by reversing the call + * @return null + */ + protected function run_step($step, $last_result = 0, $reverse = false) + { + $callable_and_parameters = $this->get_callable_from_step($step, $last_result, $reverse); + + if ($callable_and_parameters === false) + { + return; + } + + $callable = $callable_and_parameters[0]; + $parameters = $callable_and_parameters[1]; + + return call_user_func_array($callable, $parameters); + } + + /** + * Get a callable statement from a data step + * + * @param array $step Data step from migration + * @param mixed $last_result Result to pass to the callable (only for 'custom' method) + * @param bool $reverse False to install, True to attempt uninstallation by reversing the call + * @return array Array with parameters for call_user_func_array(), 0 is the callable, 1 is parameters + * @throws \phpbb\db\migration\exception + */ + protected function get_callable_from_step(array $step, $last_result = 0, $reverse = false) + { + $type = $step[0]; + $parameters = $step[1]; + + $parts = explode('.', $type); + + $class = $parts[0]; + $method = false; + + if (isset($parts[1])) + { + $method = $parts[1]; + } + + switch ($class) + { + case 'if': + if (!isset($parameters[0])) + { + throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_CONDITION', $step); + } + + if (!isset($parameters[1])) + { + throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_MISSING_STEP', $step); + } + + if ($reverse) + { + // We might get unexpected results when trying + // to revert this, so just avoid it + return false; + } + + $condition = $parameters[0]; + + if (!$condition || (is_array($condition) && !$this->run_step($condition, $last_result, $reverse))) + { + return false; + } + + $step = $parameters[1]; + + return $this->get_callable_from_step($step); + break; + + case 'custom': + if (!is_callable($parameters[0])) + { + throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_CUSTOM_NOT_CALLABLE', $step); + } + + if ($reverse) + { + return false; + } + else + { + return array( + $parameters[0], + array($last_result), + ); + } + break; + + default: + if (!$method) + { + throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNKNOWN_TYPE', $step); + } + + if (!isset($this->tools[$class])) + { + throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_TOOL', $step); + } + + if (!method_exists(get_class($this->tools[$class]), $method)) + { + throw new \phpbb\db\migration\exception('MIGRATION_INVALID_DATA_UNDEFINED_METHOD', $step); + } + + // Attempt to reverse operations + if ($reverse) + { + array_unshift($parameters, $method); + + return array( + array($this->tools[$class], 'reverse'), + $parameters, + ); + } + + return array( + array($this->tools[$class], $method), + $parameters, + ); + break; + } + } + + /** + * Insert/Update migration row into the database + * + * @param string $name Name of the migration + * @param array $state + * @return null + */ + protected function set_migration_state($name, $state) + { + $migration_row = $state; + $migration_row['migration_depends_on'] = serialize($state['migration_depends_on']); + $migration_row['migration_data_state'] = !empty($state['migration_data_state']) ? serialize($state['migration_data_state']) : ''; + + if (isset($this->migration_state[$name])) + { + $sql = 'UPDATE ' . $this->migrations_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $migration_row) . " + WHERE migration_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + } + else + { + $migration_row['migration_name'] = $name; + $sql = 'INSERT INTO ' . $this->migrations_table . ' + ' . $this->db->sql_build_array('INSERT', $migration_row); + $this->db->sql_query($sql); + } + + $this->migration_state[$name] = $state; + + $this->last_run_migration['state'] = $state; + } + + /** + * Checks if a migration's dependencies can even theoretically be satisfied. + * + * @param string $name The class name of the migration + * @return bool|string False if fulfillable, string of missing migration name if unfulfillable + */ + public function unfulfillable($name) + { + $name = $this->get_valid_name($name); + + if (isset($this->migration_state[$name]) || isset($this->fulfillable_migrations[$name])) + { + return false; + } + + if (!class_exists($name)) + { + return $name; + } + + $migration = $this->get_migration($name); + $depends = $migration->depends_on(); + + foreach ($depends as $depend) + { + $depend = $this->get_valid_name($depend); + $unfulfillable = $this->unfulfillable($depend); + if ($unfulfillable !== false) + { + return $unfulfillable; + } + } + $this->fulfillable_migrations[$name] = true; + + return false; + } + + /** + * Checks whether all available, fulfillable migrations have been applied. + * + * @return bool Whether the migrations have been applied + */ + public function finished() + { + foreach ($this->migrations as $name) + { + if (!isset($this->migration_state[$name])) + { + // skip unfulfillable migrations, but fulfillables mean we + // are not finished yet + if ($this->unfulfillable($name) !== false) + { + continue; + } + + return false; + } + + $migration = $this->migration_state[$name]; + + if (!$migration['migration_schema_done'] || !$migration['migration_data_done']) + { + return false; + } + } + + return true; + } + + /** + * Gets a migration state (whether it is installed and to what extent) + * + * @param string $migration String migration name to check if it is installed + * @return bool|array False if the migration has not at all been installed, array + */ + public function migration_state($migration) + { + if (!isset($this->migration_state[$migration])) + { + return false; + } + + return $this->migration_state[$migration]; + } + + /** + * Helper to get a migration + * + * @param string $name Name of the migration + * @return \phpbb\db\migration\migration + */ + protected function get_migration($name) + { + $migration = new $name($this->config, $this->db, $this->db_tools, $this->phpbb_root_path, $this->php_ext, $this->table_prefix); + + if ($migration instanceof ContainerAwareInterface) + { + $migration->setContainer($this->container); + } + + return $migration; + } + + /** + * This function adds all migrations sent to it to the migrations table + * + * THIS SHOULD NOT GENERALLY BE USED! THIS IS FOR THE PHPBB INSTALLER. + * THIS WILL THROW ERRORS IF MIGRATIONS ALREADY EXIST IN THE TABLE, DO NOT CALL MORE THAN ONCE! + * + * @param array $migrations Array of migrations (names) to add to the migrations table + * @return null + */ + public function populate_migrations($migrations) + { + foreach ($migrations as $name) + { + if ($this->migration_state($name) === false) + { + $state = array( + 'migration_depends_on' => $name::depends_on(), + 'migration_schema_done' => true, + 'migration_data_done' => true, + 'migration_data_state' => '', + 'migration_start_time' => time(), + 'migration_end_time' => time(), + ); + $this->set_migration_state($name, $state); + } + } + } + + /** + * Creates the migrations table if it does not exist. + * @return null + */ + public function create_migrations_table() + { + // Make sure migrations have been installed. + if (!$this->db_tools->sql_table_exists($this->table_prefix . 'migrations')) + { + $this->db_tools->sql_create_table($this->table_prefix . 'migrations', array( + 'COLUMNS' => array( + 'migration_name' => array('VCHAR', ''), + 'migration_depends_on' => array('TEXT', ''), + 'migration_schema_done' => array('BOOL', 0), + 'migration_data_done' => array('BOOL', 0), + 'migration_data_state' => array('TEXT', ''), + 'migration_start_time' => array('TIMESTAMP', 0), + 'migration_end_time' => array('TIMESTAMP', 0), + ), + 'PRIMARY_KEY' => 'migration_name', + )); + } + } + + /** + * Check if a class is a migration. + * + * @param string $migration A migration class name + * @return bool Return true if class is a migration, false otherwise + */ + static public function is_migration($migration) + { + if (class_exists($migration)) + { + // Migration classes should extend the abstract class + // phpbb\db\migration\migration (which implements the + // migration_interface) and be instantiable. + $reflector = new \ReflectionClass($migration); + if ($reflector->implementsInterface('\phpbb\db\migration\migration_interface') && $reflector->isInstantiable()) + { + return true; + } + } + + return false; + } +} diff --git a/phpbb/db/output_handler/html_migrator_output_handler.php b/phpbb/db/output_handler/html_migrator_output_handler.php new file mode 100644 index 0000000..6730964 --- /dev/null +++ b/phpbb/db/output_handler/html_migrator_output_handler.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\output_handler; + +class html_migrator_output_handler implements migrator_output_handler_interface +{ + /** + * Language object. + * + * @var \phpbb\language\language + */ + private $language; + + /** + * Constructor + * + * @param \phpbb\language\language $language Language object + */ + public function __construct(\phpbb\language\language $language) + { + $this->language = $language; + } + + /** + * {@inheritdoc} + */ + public function write($message, $verbosity) + { + if ($verbosity <= migrator_output_handler_interface::VERBOSITY_VERBOSE) + { + $final_message = $this->language->lang_array(array_shift($message), $message); + echo $final_message . "
\n"; + } + } +} diff --git a/phpbb/db/output_handler/installer_migrator_output_handler.php b/phpbb/db/output_handler/installer_migrator_output_handler.php new file mode 100644 index 0000000..56d5cf4 --- /dev/null +++ b/phpbb/db/output_handler/installer_migrator_output_handler.php @@ -0,0 +1,46 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\output_handler; + +use phpbb\install\helper\iohandler\iohandler_interface; + +class installer_migrator_output_handler implements migrator_output_handler_interface +{ + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * Constructor + * + * @param iohandler_interface $iohandler Installer's IO-handler + */ + public function __construct(iohandler_interface $iohandler) + { + $this->iohandler = $iohandler; + } + + /** + * {@inheritdoc} + */ + public function write($message, $verbosity) + { + if ($verbosity <= migrator_output_handler_interface::VERBOSITY_VERBOSE) + { + $this->iohandler->add_log_message($message); + $this->iohandler->send_response(); + } + } +} diff --git a/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php b/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php new file mode 100644 index 0000000..e4bd3ac --- /dev/null +++ b/phpbb/db/output_handler/log_wrapper_migrator_output_handler.php @@ -0,0 +1,101 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\output_handler; + +class log_wrapper_migrator_output_handler implements migrator_output_handler_interface +{ + /** + * Language object. + * + * @var \phpbb\language\language + */ + protected $language; + + /** + * A migrator output handler + * + * @var migrator_output_handler_interface + */ + protected $migrator; + + /** + * Log file handle + * @var resource + */ + protected $file_handle = false; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * Constructor + * + * @param \phpbb\language\language $language Language object + * @param migrator_output_handler_interface $migrator Migrator output handler + * @param string $log_file File to log to + * @param \phpbb\filesystem\filesystem_interface $filesystem phpBB filesystem object + */ + public function __construct(\phpbb\language\language $language, migrator_output_handler_interface $migrator, $log_file, \phpbb\filesystem\filesystem_interface $filesystem) + { + $this->language = $language; + $this->migrator = $migrator; + $this->filesystem = $filesystem; + $this->file_open($log_file); + } + + /** + * Open file for logging + * + * @param string $file File to open + */ + protected function file_open($file) + { + if ($this->filesystem->is_writable(dirname($file))) + { + $this->file_handle = fopen($file, 'w'); + } + else + { + throw new \RuntimeException('Unable to write to migrator log file'); + } + } + + /** + * {@inheritdoc} + */ + public function write($message, $verbosity) + { + $this->migrator->write($message, $verbosity); + + if ($this->file_handle !== false) + { + + $translated_message = $this->language->lang_array(array_shift($message), $message); + + if ($verbosity <= migrator_output_handler_interface::VERBOSITY_NORMAL) + { + $translated_message = '[INFO] ' . $translated_message; + } + else + { + $translated_message = '[DEBUG] ' . $translated_message; + } + + fwrite($this->file_handle, $translated_message . "\n"); + fflush($this->file_handle); + } + } +} diff --git a/phpbb/db/output_handler/migrator_output_handler_interface.php b/phpbb/db/output_handler/migrator_output_handler_interface.php new file mode 100644 index 0000000..455d8aa --- /dev/null +++ b/phpbb/db/output_handler/migrator_output_handler_interface.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\output_handler; + +interface migrator_output_handler_interface +{ + const VERBOSITY_QUIET = 16; + const VERBOSITY_NORMAL = 32; + const VERBOSITY_VERBOSE = 64; + const VERBOSITY_VERY_VERBOSE = 128; + const VERBOSITY_DEBUG = 256; + + /** + * Write output using the configured closure. + * + * @param string|array $message The message to write or an array containing the language key and all of its parameters. + * @param int $verbosity The verbosity of the message. + */ + public function write($message, $verbosity); +} diff --git a/phpbb/db/output_handler/null_migrator_output_handler.php b/phpbb/db/output_handler/null_migrator_output_handler.php new file mode 100644 index 0000000..5fc2a52 --- /dev/null +++ b/phpbb/db/output_handler/null_migrator_output_handler.php @@ -0,0 +1,24 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\output_handler; + +class null_migrator_output_handler implements migrator_output_handler_interface +{ + /** + * {@inheritdoc} + */ + public function write($message, $verbosity) + { + } +} diff --git a/phpbb/db/sql_insert_buffer.php b/phpbb/db/sql_insert_buffer.php new file mode 100644 index 0000000..30e807b --- /dev/null +++ b/phpbb/db/sql_insert_buffer.php @@ -0,0 +1,146 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db; + +/** +* Collects rows for insert into a database until the buffer size is reached. +* Then flushes the buffer to the database and starts over again. +* +* Benefits over collecting a (possibly huge) insert array and then using +* $db->sql_multi_insert() include: +* +* - Going over max packet size of the database connection is usually prevented +* because the data is submitted in batches. +* +* - Reaching database connection timeout is usually prevented because +* submission of batches talks to the database every now and then. +* +* - Usage of less PHP memory because data no longer needed is discarded on +* buffer flush. +* +* Attention: +* Please note that users of this class have to call flush() to flush the +* remaining rows to the database after their batch insert operation is +* finished. +* +* Usage: +* +* $buffer = new \phpbb\db\sql_insert_buffer($db, 'test_table', 1234); +* +* while (do_stuff()) +* { +* $buffer->insert(array( +* 'column1' => 'value1', +* 'column2' => 'value2', +* )); +* } +* +* $buffer->flush(); +* +*/ +class sql_insert_buffer +{ + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var string */ + protected $table_name; + + /** @var int */ + protected $max_buffered_rows; + + /** @var array */ + protected $buffer = array(); + + /** + * @param \phpbb\db\driver\driver_interface $db + * @param string $table_name + * @param int $max_buffered_rows + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $table_name, $max_buffered_rows = 500) + { + $this->db = $db; + $this->table_name = $table_name; + $this->max_buffered_rows = $max_buffered_rows; + } + + /** + * Inserts a single row into the buffer if multi insert is supported by the + * database (otherwise an insert query is sent immediately). Then flushes + * the buffer if the number of rows in the buffer is now greater than or + * equal to $max_buffered_rows. + * + * @param array $row + * + * @return bool True when some data was flushed to the database. + * False otherwise. + */ + public function insert(array $row) + { + $this->buffer[] = $row; + + // Flush buffer if it is full or when DB does not support multi inserts. + // In the later case, the buffer will always only contain one row. + if (!$this->db->get_multi_insert() || count($this->buffer) >= $this->max_buffered_rows) + { + return $this->flush(); + } + + return false; + } + + /** + * Inserts a row set, i.e. an array of rows, by calling insert(). + * + * Please note that it is in most cases better to use insert() instead of + * first building a huge rowset. Or at least count($rows) should be kept + * small. + * + * @param array $rows + * + * @return bool True when some data was flushed to the database. + * False otherwise. + */ + public function insert_all(array $rows) + { + // Using bitwise |= because PHP does not have logical ||= + $result = 0; + + foreach ($rows as $row) + { + $result |= (int) $this->insert($row); + } + + return (bool) $result; + } + + /** + * Flushes the buffer content to the DB and clears the buffer. + * + * @return bool True when some data was flushed to the database. + * False otherwise. + */ + public function flush() + { + if (!empty($this->buffer)) + { + $this->db->sql_multi_insert($this->table_name, $this->buffer); + $this->buffer = array(); + + return true; + } + + return false; + } +} diff --git a/phpbb/db/tools.php b/phpbb/db/tools.php new file mode 100644 index 0000000..4d1b91f --- /dev/null +++ b/phpbb/db/tools.php @@ -0,0 +1,21 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db; + +/** + * @deprecated 3.2.0-dev (To be removed 3.3.0) use \phpbb\db\tools\tools instead + */ +class tools extends \phpbb\db\tools\tools +{ +} diff --git a/phpbb/db/tools/factory.php b/phpbb/db/tools/factory.php new file mode 100644 index 0000000..96471c3 --- /dev/null +++ b/phpbb/db/tools/factory.php @@ -0,0 +1,43 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * A factory which serves the suitable tools instance for the given dbal + */ +class factory +{ + /** + * @param mixed $db_driver + * @param bool $return_statements + * @return \phpbb\db\tools\tools_interface + */ + public function get($db_driver, $return_statements = false) + { + if ($db_driver instanceof \phpbb\db\driver\mssql_base) + { + return new \phpbb\db\tools\mssql($db_driver, $return_statements); + } + else if ($db_driver instanceof \phpbb\db\driver\postgres) + { + return new \phpbb\db\tools\postgres($db_driver, $return_statements); + } + else if ($db_driver instanceof \phpbb\db\driver\driver_interface) + { + return new \phpbb\db\tools\tools($db_driver, $return_statements); + } + + throw new \InvalidArgumentException('Invalid database driver given'); + } +} diff --git a/phpbb/db/tools/mssql.php b/phpbb/db/tools/mssql.php new file mode 100644 index 0000000..cbedf9a --- /dev/null +++ b/phpbb/db/tools/mssql.php @@ -0,0 +1,880 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class mssql extends tools +{ + /** + * Is the used MS SQL Server a SQL Server 2000? + * @var bool + */ + protected $is_sql_server_2000; + + /** + * Get the column types for mssql based databases + * + * @return array + */ + public static function get_dbms_type_map() + { + return array( + 'mssql' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'ULINT' => '[int]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[nvarchar] (100)', + 'STEXT_UNI' => '[nvarchar] (255)', + 'TEXT_UNI' => '[nvarchar] (4000)', + 'MTEXT_UNI' => '[ntext]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[nvarchar] (255)', + 'VCHAR_UNI:'=> '[nvarchar] (%d)', + 'VCHAR_CI' => '[nvarchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + + 'mssqlnative' => array( + 'INT:' => '[int]', + 'BINT' => '[float]', + 'ULINT' => '[int]', + 'UINT' => '[int]', + 'UINT:' => '[int]', + 'TINT:' => '[int]', + 'USINT' => '[int]', + 'BOOL' => '[int]', + 'VCHAR' => '[varchar] (255)', + 'VCHAR:' => '[varchar] (%d)', + 'CHAR:' => '[char] (%d)', + 'XSTEXT' => '[varchar] (1000)', + 'STEXT' => '[varchar] (3000)', + 'TEXT' => '[varchar] (8000)', + 'MTEXT' => '[text]', + 'XSTEXT_UNI'=> '[nvarchar] (100)', + 'STEXT_UNI' => '[nvarchar] (255)', + 'TEXT_UNI' => '[nvarchar] (4000)', + 'MTEXT_UNI' => '[ntext]', + 'TIMESTAMP' => '[int]', + 'DECIMAL' => '[float]', + 'DECIMAL:' => '[float]', + 'PDECIMAL' => '[float]', + 'PDECIMAL:' => '[float]', + 'VCHAR_UNI' => '[nvarchar] (255)', + 'VCHAR_UNI:'=> '[nvarchar] (%d)', + 'VCHAR_CI' => '[nvarchar] (255)', + 'VARBINARY' => '[varchar] (255)', + ), + ); + } + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param bool $return_statements True if only statements should be returned and no SQL being executed + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) + { + parent::__construct($db, $return_statements); + + // Determine mapping database type + switch ($this->db->get_sql_layer()) + { + case 'mssql_odbc': + $this->sql_layer = 'mssql'; + break; + + case 'mssqlnative': + $this->sql_layer = 'mssqlnative'; + break; + } + + $this->dbms_type_map = self::get_dbms_type_map(); + } + + /** + * {@inheritDoc} + */ + function sql_list_tables() + { + $sql = "SELECT name + FROM sysobjects + WHERE type='U'"; + $result = $this->db->sql_query($sql); + + $tables = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $name = current($row); + $tables[$name] = $name; + } + $this->db->sql_freeresult($result); + + return $tables; + } + + /** + * {@inheritDoc} + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + $table_sql = 'CREATE TABLE [' . $table_name . '] (' . "\n"; + + if (!isset($table_data['PRIMARY_KEY'])) + { + $table_data['COLUMNS']['mssqlindex'] = array('UINT', null, 'auto_increment'); + $table_data['PRIMARY_KEY'] = 'mssqlindex'; + } + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + $columns[] = "\t [{$column_name}] " . $prepared_column['column_type_sql_default']; + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // Close the table for two DBMS and add to the statements + $table_sql .= "\n);"; + $statements[] = $table_sql; + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $primary_key_stmts = $this->sql_create_primary_key($table_name, $table_data['PRIMARY_KEY']); + foreach ($primary_key_stmts as $pk_stmt) + { + $statements[] = $pk_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_columns($table_name) + { + $columns = array(); + + $sql = "SELECT c.name + FROM syscolumns c + LEFT JOIN sysobjects o ON c.id = o.id + WHERE o.name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * {@inheritDoc} + */ + function sql_index_exists($table_name, $index_name) + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_unique_index_exists($table_name, $index_name) + { + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Usually NON_UNIQUE is the column we want to check, but we allow for both + if ($row['TYPE'] == 3) + { + if (strtolower($row['INDEX_NAME']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + list($column_type, ) = $this->get_column_type($column_data[0]); + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = ''; + + $return_array = array(); + + $sql .= " {$column_type} "; + $sql_default = " {$column_type} "; + + // For adding columns we need the default definition + if (!is_null($column_data[1])) + { + // For hexadecimal values do not use single quotes + if (strpos($column_data[1], '0x') === 0) + { + $return_array['default'] = 'DEFAULT (' . $column_data[1] . ') '; + $sql_default .= $return_array['default']; + } + else + { + $return_array['default'] = 'DEFAULT (' . ((is_numeric($column_data[1])) ? $column_data[1] : "'{$column_data[1]}'") . ') '; + $sql_default .= $return_array['default']; + } + } + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + // $sql .= 'IDENTITY (1, 1) '; + $sql_default .= 'IDENTITY (1, 1) '; + } + + $return_array['textimage'] = $column_type === '[text]'; + + if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) + { + $sql .= 'NOT NULL'; + $sql_default .= 'NOT NULL'; + } + else + { + $sql .= 'NULL'; + $sql_default .= 'NULL'; + } + + $return_array['column_type_sql_default'] = $sql_default; + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD [' . $column_name . '] ' . $column_data['column_type_sql_default']; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $indexes = $this->get_existing_indexes($table_name, $column_name); + $indexes = array_merge($indexes, $this->get_existing_indexes($table_name, $column_name, true)); + + // Drop any indexes + $recreate_indexes = array(); + if (!empty($indexes)) + { + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_index_drop($table_name, $index_name); + $statements = array_merge($statements, $result); + if (count($index_data) > 1) + { + // Remove this column from the index and recreate it + $recreate_indexes[$index_name] = array_diff($index_data, array($column_name)); + } + } + } + + // Drop primary keys depending on this column + $result = $this->mssql_get_drop_default_primary_key_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Drop default value constraint + $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Remove the column + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP COLUMN [' . $column_name . ']'; + + if (!empty($recreate_indexes)) + { + // Recreate indexes after we removed the column + foreach ($recreate_indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + $this->return_statements = $old_return_statements; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + $statements[] = 'DROP INDEX [' . $table_name . '].[' . $index_name . ']'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + $sql = "ALTER TABLE [{$table_name}] WITH NOCHECK ADD "; + $sql .= "CONSTRAINT [PK_{$table_name}] PRIMARY KEY CLUSTERED ("; + $sql .= '[' . implode("],\n\t\t[", $column) . ']'; + $sql .= ')'; + + $statements[] = $sql; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + if ($this->mssql_is_sql_server_2000()) + { + $this->check_index_name_length($table_name, $index_name); + } + + $statements[] = 'CREATE UNIQUE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + // remove index length + $column = preg_replace('#:.*$#', '', $column); + + $statements[] = 'CREATE INDEX [' . $index_name . '] ON [' . $table_name . ']([' . implode('], [', $column) . '])'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritdoc} + */ + protected function get_max_index_name_length() + { + if ($this->mssql_is_sql_server_2000()) + { + return parent::get_max_index_name_length(); + } + else + { + return 128; + } + } + + /** + * {@inheritDoc} + */ + function sql_list_index($table_name) + { + $index_array = array(); + $sql = "EXEC sp_statistics '$table_name'"; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['TYPE'] == 3) + { + $index_array[] = strtolower($row['INDEX_NAME']); + } + } + $this->db->sql_freeresult($result); + + return $index_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $indexes = $this->get_existing_indexes($table_name, $column_name); + $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); + + // Drop any indexes + if (!empty($indexes) || !empty($unique_indexes)) + { + $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); + foreach ($drop_indexes as $index_name) + { + $result = $this->sql_index_drop($table_name, $index_name); + $statements = array_merge($statements, $result); + } + } + + // Drop default value constraint + $result = $this->mssql_get_drop_default_constraints_queries($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Change the column + $statements[] = 'ALTER TABLE [' . $table_name . '] ALTER COLUMN [' . $column_name . '] ' . $column_data['column_type_sql']; + + if (!empty($column_data['default']) && !$this->mssql_is_column_identity($table_name, $column_name)) + { + // Add new default value constraint + $statements[] = 'ALTER TABLE [' . $table_name . '] ADD CONSTRAINT [DF_' . $table_name . '_' . $column_name . '_1] ' . $column_data['default'] . ' FOR [' . $column_name . ']'; + } + + if (!empty($indexes)) + { + // Recreate indexes after we changed the column + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + if (!empty($unique_indexes)) + { + // Recreate unique indexes after we changed the column + foreach ($unique_indexes as $index_name => $index_data) + { + $result = $this->sql_create_unique_index($table_name, $index_name, $index_data); + $statements = array_merge($statements, $result); + } + } + + $this->return_statements = $old_return_statements; + + return $this->_sql_run_sql($statements); + } + + /** + * Get queries to drop the default constraints of a column + * + * We need to drop the default constraints of a column, + * before being able to change their type or deleting them. + * + * @param string $table_name + * @param string $column_name + * @return array Array with SQL statements + */ + protected function mssql_get_drop_default_constraints_queries($table_name, $column_name) + { + $statements = array(); + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT so.name AS def_name + FROM sysobjects so + JOIN sysconstraints sc ON so.id = sc.constid + WHERE object_name(so.parent_obj) = '{$table_name}' + AND so.xtype = 'D' + AND sc.colid = (SELECT colid FROM syscolumns + WHERE id = object_id('{$table_name}') + AND name = '{$column_name}')"; + } + else + { + $sql = "SELECT dobj.name AS def_name + FROM sys.columns col + LEFT OUTER JOIN sys.objects dobj ON (dobj.object_id = col.default_object_id AND dobj.type = 'D') + WHERE col.object_id = object_id('{$table_name}') + AND col.name = '{$column_name}' + AND dobj.name IS NOT NULL"; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $row['def_name'] . ']'; + } + $this->db->sql_freeresult($result); + + return $statements; + } + + /** + * Get queries to drop the primary keys depending on the specified column + * + * We need to drop primary keys depending on this column before being able + * to delete them. + * + * @param string $table_name + * @param string $column_name + * @return array Array with SQL statements + */ + protected function mssql_get_drop_default_primary_key_queries($table_name, $column_name) + { + $statements = array(); + + $sql = "SELECT ccu.CONSTRAINT_NAME, ccu.COLUMN_NAME + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc + JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE ccu ON tc.CONSTRAINT_NAME = ccu.Constraint_name + WHERE tc.TABLE_NAME = '{$table_name}' + AND tc.CONSTRAINT_TYPE = 'Primary Key' + AND ccu.COLUMN_NAME = '{$column_name}'"; + + $result = $this->db->sql_query($sql); + + while ($primary_key = $this->db->sql_fetchrow($result)) + { + $statements[] = 'ALTER TABLE [' . $table_name . '] DROP CONSTRAINT [' . $primary_key['CONSTRAINT_NAME'] . ']'; + } + $this->db->sql_freeresult($result); + + return $statements; + } + + /** + * Checks to see if column is an identity column + * + * Identity columns cannot have defaults set for them. + * + * @param string $table_name + * @param string $column_name + * @return bool true if identity, false if not + */ + protected function mssql_is_column_identity($table_name, $column_name) + { + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT COLUMNPROPERTY(object_id('{$table_name}'), '{$column_name}', 'IsIdentity') AS is_identity"; + } + else + { + $sql = "SELECT is_identity FROM sys.columns + WHERE object_id = object_id('{$table_name}') + AND name = '{$column_name}'"; + } + + $result = $this->db->sql_query($sql); + $is_identity = $this->db->sql_fetchfield('is_identity'); + $this->db->sql_freeresult($result); + + return (bool) $is_identity; + } + + /** + * Get a list with existing indexes for the column + * + * @param string $table_name + * @param string $column_name + * @param bool $unique Should we get unique indexes or normal ones + * @return array Array with Index name => columns + */ + public function get_existing_indexes($table_name, $column_name, $unique = false) + { + $existing_indexes = array(); + if ($this->mssql_is_sql_server_2000()) + { + // http://msdn.microsoft.com/en-us/library/aa175912%28v=sql.80%29.aspx + // Deprecated in SQL Server 2005 + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name + FROM sysindexes ix + INNER JOIN sysindexkeys ixc + ON ixc.id = ix.id + AND ixc.indid = ix.indid + INNER JOIN syscolumns cols + ON cols.colid = ixc.colid + AND cols.id = ix.id + WHERE ix.id = object_id('{$table_name}') + AND cols.name = '{$column_name}' + AND INDEXPROPERTY(ix.id, ix.name, 'IsUnique') = " . ($unique ? '1' : '0'); + } + else + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name + FROM sys.indexes ix + INNER JOIN sys.index_columns ixc + ON ixc.object_id = ix.object_id + AND ixc.index_id = ix.index_id + INNER JOIN sys.columns cols + ON cols.column_id = ixc.column_id + AND cols.object_id = ix.object_id + WHERE ix.object_id = object_id('{$table_name}') + AND cols.name = '{$column_name}' + AND ix.is_primary_key = 0 + AND ix.is_unique = " . ($unique ? '1' : '0'); + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE')) + { + $existing_indexes[$row['phpbb_index_name']] = array(); + } + } + $this->db->sql_freeresult($result); + + if (empty($existing_indexes)) + { + return array(); + } + + if ($this->mssql_is_sql_server_2000()) + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name + FROM sysindexes ix + INNER JOIN sysindexkeys ixc + ON ixc.id = ix.id + AND ixc.indid = ix.indid + INNER JOIN syscolumns cols + ON cols.colid = ixc.colid + AND cols.id = ix.id + WHERE ix.id = object_id('{$table_name}') + AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); + } + else + { + $sql = "SELECT DISTINCT ix.name AS phpbb_index_name, cols.name AS phpbb_column_name + FROM sys.indexes ix + INNER JOIN sys.index_columns ixc + ON ixc.object_id = ix.object_id + AND ixc.index_id = ix.index_id + INNER JOIN sys.columns cols + ON cols.column_id = ixc.column_id + AND cols.object_id = ix.object_id + WHERE ix.object_id = object_id('{$table_name}') + AND " . $this->db->sql_in_set('ix.name', array_keys($existing_indexes)); + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name']; + } + $this->db->sql_freeresult($result); + + return $existing_indexes; + } + + /** + * Is the used MS SQL Server a SQL Server 2000? + * + * @return bool + */ + protected function mssql_is_sql_server_2000() + { + if ($this->is_sql_server_2000 === null) + { + $sql = "SELECT CAST(SERVERPROPERTY('productversion') AS VARCHAR(25)) AS mssql_version"; + $result = $this->db->sql_query($sql); + $properties = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + $this->is_sql_server_2000 = $properties['mssql_version'][0] == '8'; + } + + return $this->is_sql_server_2000; + } + +} diff --git a/phpbb/db/tools/postgres.php b/phpbb/db/tools/postgres.php new file mode 100644 index 0000000..077d6e0 --- /dev/null +++ b/phpbb/db/tools/postgres.php @@ -0,0 +1,614 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\db\tools; + +/** + * Database Tools for handling cross-db actions such as altering columns, etc. + * Currently not supported is returning SQL for creating tables. + */ +class postgres extends tools +{ + /** + * Get the column types for postgres only + * + * @return array + */ + public static function get_dbms_type_map() + { + return array( + 'postgres' => array( + 'INT:' => 'INT4', + 'BINT' => 'INT8', + 'ULINT' => 'INT4', // unsigned + 'UINT' => 'INT4', // unsigned + 'UINT:' => 'INT4', // unsigned + 'USINT' => 'INT2', // unsigned + 'BOOL' => 'INT2', // unsigned + 'TINT:' => 'INT2', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar(1000)', + 'STEXT' => 'varchar(3000)', + 'TEXT' => 'varchar(8000)', + 'MTEXT' => 'TEXT', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT_UNI' => 'varchar(4000)', + 'MTEXT_UNI' => 'TEXT', + 'TIMESTAMP' => 'INT4', // unsigned + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar_ci', + 'VARBINARY' => 'bytea', + ), + ); + } + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param bool $return_statements True if only statements should be returned and no SQL being executed + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) + { + parent::__construct($db, $return_statements); + + // Determine mapping database type + $this->sql_layer = 'postgres'; + + $this->dbms_type_map = self::get_dbms_type_map(); + } + + /** + * {@inheritDoc} + */ + function sql_list_tables() + { + $sql = 'SELECT relname + FROM pg_stat_user_tables'; + $result = $this->db->sql_query($sql); + + $tables = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $name = current($row); + $tables[$name] = $name; + } + $this->db->sql_freeresult($result); + + return $tables; + } + + /** + * {@inheritDoc} + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + } + } + + // do we need to add a sequence for auto incrementing columns? + if ($create_sequence) + { + $statements[] = "CREATE SEQUENCE {$table_name}_seq;"; + } + + // close the table + $table_sql .= "\n);"; + $statements[] = $table_sql; + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_list_columns($table_name) + { + $columns = array(); + + $sql = "SELECT a.attname + FROM pg_class c, pg_attribute a + WHERE c.relname = '{$table_name}' + AND a.attnum > 0 + AND a.attrelid = c.oid"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * {@inheritDoc} + */ + function sql_index_exists($table_name, $index_name) + { + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // This DBMS prefixes index names with the table name + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + if (strtolower($row['index_name']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_unique_index_exists($table_name, $index_name) + { + $sql = "SELECT ic.relname as index_name, i.indisunique + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['indisunique'] != 't') + { + continue; + } + + // This DBMS prefixes index names with the table name + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + if (strtolower($row['index_name']) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Function to prepare some column information for better usage + * @access private + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + list($column_type, $orig_column_type) = $this->get_column_type($column_data[0]); + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = " {$column_type} "; + + $return_array = array( + 'column_type' => $column_type, + 'auto_increment' => false, + ); + + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $default_val = "nextval('{$table_name}_seq')"; + $return_array['auto_increment'] = true; + } + else if (!is_null($column_data[1])) + { + $default_val = "'" . $column_data[1] . "'"; + $return_array['null'] = 'NOT NULL'; + $sql .= 'NOT NULL '; + } + else + { + // Integers need to have 0 instead of empty string as default + if (strpos($column_type, 'INT') === 0) + { + $default_val = '0'; + } + else + { + $default_val = "'" . $column_data[1] . "'"; + } + $return_array['null'] = 'NULL'; + $sql .= 'NULL '; + } + + $return_array['default'] = $default_val; + + $sql .= "DEFAULT {$default_val}"; + + // Unsigned? Then add a CHECK contraint + if (in_array($orig_column_type, $this->unsigned_types)) + { + $return_array['constraint'] = "CHECK ({$column_name} >= 0)"; + $sql .= " CHECK ({$column_name} >= 0)"; + } + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * {@inheritDoc} + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + // Does not support AFTER, only through temporary table + if (version_compare($this->db->sql_server_info(true), '8.0', '>=')) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type_sql']; + } + else + { + // old versions cannot add columns with default and null information + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD COLUMN "' . $column_name . '" ' . $column_data['column_type'] . ' ' . $column_data['constraint']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $statements[] = 'ALTER TABLE ' . $table_name . ' ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN "' . $column_name . '"'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + $statements[] = 'DROP INDEX ' . $table_name . '_' . $index_name; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + // PGSQL does not "tightly" bind sequences and tables, we must guess... + $sql = "SELECT relname + FROM pg_class + WHERE relkind = 'S' + AND relname = '{$table_name}_seq'"; + $result = $this->db->sql_query($sql); + + // We don't even care about storing the results. We already know the answer if we get rows back. + if ($this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE IF EXISTS {$table_name}_seq;\n"; + } + $this->db->sql_freeresult($result); + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + $statements[] = 'CREATE UNIQUE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + $this->check_index_name_length($table_name, $index_name); + + // remove index length + $column = preg_replace('#:.*$#', '', $column); + + $statements[] = 'CREATE INDEX ' . $table_name . '_' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + + return $this->_sql_run_sql($statements); + } + + + /** + * {@inheritDoc} + */ + function sql_list_index($table_name) + { + $index_array = array(); + + $sql = "SELECT ic.relname as index_name + FROM pg_class bc, pg_class ic, pg_index i + WHERE (bc.oid = i.indrelid) + AND (ic.oid = i.indexrelid) + AND (bc.relname = '" . $table_name . "') + AND (i.indisunique != 't') + AND (i.indisprimary != 't')"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $row['index_name'] = $this->strip_table_name_from_index_name($table_name, $row['index_name']); + + $index_array[] = $row['index_name']; + } + $this->db->sql_freeresult($result); + + return array_map('strtolower', $index_array); + } + + /** + * {@inheritDoc} + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + $sql = 'ALTER TABLE ' . $table_name . ' '; + + $sql_array = array(); + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' TYPE ' . $column_data['column_type']; + + if (isset($column_data['null'])) + { + if ($column_data['null'] == 'NOT NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET NOT NULL'; + } + else if ($column_data['null'] == 'NULL') + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' DROP NOT NULL'; + } + } + + if (isset($column_data['default'])) + { + $sql_array[] = 'ALTER COLUMN ' . $column_name . ' SET DEFAULT ' . $column_data['default']; + } + + // we don't want to double up on constraints if we change different number data types + if (isset($column_data['constraint'])) + { + $constraint_sql = "SELECT consrc as constraint_data + FROM pg_constraint, pg_class bc + WHERE conrelid = bc.oid + AND bc.relname = '{$table_name}' + AND NOT EXISTS ( + SELECT * + FROM pg_constraint as c, pg_inherits as i + WHERE i.inhrelid = pg_constraint.conrelid + AND c.conname = pg_constraint.conname + AND c.consrc = pg_constraint.consrc + AND c.conrelid = i.inhparent + )"; + + $constraint_exists = false; + + $result = $this->db->sql_query($constraint_sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (trim($row['constraint_data']) == trim($column_data['constraint'])) + { + $constraint_exists = true; + break; + } + } + $this->db->sql_freeresult($result); + + if (!$constraint_exists) + { + $sql_array[] = 'ADD ' . $column_data['constraint']; + } + } + + $sql .= implode(', ', $sql_array); + + $statements[] = $sql; + + return $this->_sql_run_sql($statements); + } + + /** + * Get a list with existing indexes for the column + * + * @param string $table_name + * @param string $column_name + * @param bool $unique Should we get unique indexes or normal ones + * @return array Array with Index name => columns + */ + public function get_existing_indexes($table_name, $column_name, $unique = false) + { + // Not supported + throw new \Exception('DBMS is not supported'); + } +} diff --git a/phpbb/db/tools/tools.php b/phpbb/db/tools/tools.php new file mode 100644 index 0000000..d21d34b --- /dev/null +++ b/phpbb/db/tools/tools.php @@ -0,0 +1,1956 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** +* Database Tools for handling cross-db actions such as altering columns, etc. +* Currently not supported is returning SQL for creating tables. +*/ +class tools implements tools_interface +{ + /** + * Current sql layer + */ + var $sql_layer = ''; + + /** + * @var object DB object + */ + var $db = null; + + /** + * The Column types for every database we support + * @var array + */ + var $dbms_type_map = array(); + + /** + * Get the column types for every database we support + * + * @return array + */ + static public function get_dbms_type_map() + { + return array( + 'mysql_41' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'ULINT' => 'INT(10) UNSIGNED', + 'UINT' => 'mediumint(8) UNSIGNED', + 'UINT:' => 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'smallint(4) UNSIGNED', + 'BOOL' => 'tinyint(1) UNSIGNED', + 'VCHAR' => 'varchar(255)', + 'VCHAR:' => 'varchar(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'text', + 'XSTEXT_UNI'=> 'varchar(100)', + 'STEXT' => 'text', + 'STEXT_UNI' => 'varchar(255)', + 'TEXT' => 'text', + 'TEXT_UNI' => 'text', + 'MTEXT' => 'mediumtext', + 'MTEXT_UNI' => 'mediumtext', + 'TIMESTAMP' => 'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'varchar(255)', + 'VCHAR_UNI:'=> 'varchar(%d)', + 'VCHAR_CI' => 'varchar(255)', + 'VARBINARY' => 'varbinary(255)', + ), + + 'mysql_40' => array( + 'INT:' => 'int(%d)', + 'BINT' => 'bigint(20)', + 'ULINT' => 'INT(10) UNSIGNED', + 'UINT' => 'mediumint(8) UNSIGNED', + 'UINT:' => 'int(%d) UNSIGNED', + 'TINT:' => 'tinyint(%d)', + 'USINT' => 'smallint(4) UNSIGNED', + 'BOOL' => 'tinyint(1) UNSIGNED', + 'VCHAR' => 'varbinary(255)', + 'VCHAR:' => 'varbinary(%d)', + 'CHAR:' => 'binary(%d)', + 'XSTEXT' => 'blob', + 'XSTEXT_UNI'=> 'blob', + 'STEXT' => 'blob', + 'STEXT_UNI' => 'blob', + 'TEXT' => 'blob', + 'TEXT_UNI' => 'blob', + 'MTEXT' => 'mediumblob', + 'MTEXT_UNI' => 'mediumblob', + 'TIMESTAMP' => 'int(11) UNSIGNED', + 'DECIMAL' => 'decimal(5,2)', + 'DECIMAL:' => 'decimal(%d,2)', + 'PDECIMAL' => 'decimal(6,3)', + 'PDECIMAL:' => 'decimal(%d,3)', + 'VCHAR_UNI' => 'blob', + 'VCHAR_UNI:'=> array('varbinary(%d)', 'limit' => array('mult', 3, 255, 'blob')), + 'VCHAR_CI' => 'blob', + 'VARBINARY' => 'varbinary(255)', + ), + + 'oracle' => array( + 'INT:' => 'number(%d)', + 'BINT' => 'number(20)', + 'ULINT' => 'number(10)', + 'UINT' => 'number(8)', + 'UINT:' => 'number(%d)', + 'TINT:' => 'number(%d)', + 'USINT' => 'number(4)', + 'BOOL' => 'number(1)', + 'VCHAR' => 'varchar2(255)', + 'VCHAR:' => 'varchar2(%d)', + 'CHAR:' => 'char(%d)', + 'XSTEXT' => 'varchar2(1000)', + 'STEXT' => 'varchar2(3000)', + 'TEXT' => 'clob', + 'MTEXT' => 'clob', + 'XSTEXT_UNI'=> 'varchar2(300)', + 'STEXT_UNI' => 'varchar2(765)', + 'TEXT_UNI' => 'clob', + 'MTEXT_UNI' => 'clob', + 'TIMESTAMP' => 'number(11)', + 'DECIMAL' => 'number(5, 2)', + 'DECIMAL:' => 'number(%d, 2)', + 'PDECIMAL' => 'number(6, 3)', + 'PDECIMAL:' => 'number(%d, 3)', + 'VCHAR_UNI' => 'varchar2(765)', + 'VCHAR_UNI:'=> array('varchar2(%d)', 'limit' => array('mult', 3, 765, 'clob')), + 'VCHAR_CI' => 'varchar2(255)', + 'VARBINARY' => 'raw(255)', + ), + + 'sqlite3' => array( + 'INT:' => 'INT(%d)', + 'BINT' => 'BIGINT(20)', + 'ULINT' => 'INTEGER UNSIGNED', + 'UINT' => 'INTEGER UNSIGNED', + 'UINT:' => 'INTEGER UNSIGNED', + 'TINT:' => 'TINYINT(%d)', + 'USINT' => 'INTEGER UNSIGNED', + 'BOOL' => 'INTEGER UNSIGNED', + 'VCHAR' => 'VARCHAR(255)', + 'VCHAR:' => 'VARCHAR(%d)', + 'CHAR:' => 'CHAR(%d)', + 'XSTEXT' => 'TEXT(65535)', + 'STEXT' => 'TEXT(65535)', + 'TEXT' => 'TEXT(65535)', + 'MTEXT' => 'MEDIUMTEXT(16777215)', + 'XSTEXT_UNI'=> 'TEXT(65535)', + 'STEXT_UNI' => 'TEXT(65535)', + 'TEXT_UNI' => 'TEXT(65535)', + 'MTEXT_UNI' => 'MEDIUMTEXT(16777215)', + 'TIMESTAMP' => 'INTEGER UNSIGNED', //'int(11) UNSIGNED', + 'DECIMAL' => 'DECIMAL(5,2)', + 'DECIMAL:' => 'DECIMAL(%d,2)', + 'PDECIMAL' => 'DECIMAL(6,3)', + 'PDECIMAL:' => 'DECIMAL(%d,3)', + 'VCHAR_UNI' => 'VARCHAR(255)', + 'VCHAR_UNI:'=> 'VARCHAR(%d)', + 'VCHAR_CI' => 'VARCHAR(255)', + 'VARBINARY' => 'BLOB', + ), + ); + } + + /** + * A list of types being unsigned for better reference in some db's + * @var array + */ + var $unsigned_types = array('ULINT', 'UINT', 'UINT:', 'USINT', 'BOOL', 'TIMESTAMP'); + + /** + * This is set to true if user only wants to return the 'to-be-executed' SQL statement(s) (as an array). + * This mode has no effect on some methods (inserting of data for example). This is expressed within the methods command. + */ + var $return_statements = false; + + /** + * Constructor. Set DB Object and set {@link $return_statements return_statements}. + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param bool $return_statements True if only statements should be returned and no SQL being executed + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $return_statements = false) + { + $this->db = $db; + $this->return_statements = $return_statements; + + $this->dbms_type_map = self::get_dbms_type_map(); + + // Determine mapping database type + switch ($this->db->get_sql_layer()) + { + case 'mysql': + $this->sql_layer = 'mysql_40'; + break; + + case 'mysql4': + if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $this->sql_layer = 'mysql_41'; + } + else + { + $this->sql_layer = 'mysql_40'; + } + break; + + case 'mysqli': + $this->sql_layer = 'mysql_41'; + break; + + default: + $this->sql_layer = $this->db->get_sql_layer(); + break; + } + } + + /** + * Setter for {@link $return_statements return_statements}. + * + * @param bool $return_statements True if SQL should not be executed but returned as strings + * @return null + */ + public function set_return_statements($return_statements) + { + $this->return_statements = $return_statements; + } + + /** + * {@inheritDoc} + */ + function sql_list_tables() + { + switch ($this->db->get_sql_layer()) + { + case 'mysql': + case 'mysql4': + case 'mysqli': + $sql = 'SHOW TABLES'; + break; + + case 'sqlite3': + $sql = 'SELECT name + FROM sqlite_master + WHERE type = "table" + AND name <> "sqlite_sequence"'; + break; + + case 'oracle': + $sql = 'SELECT table_name + FROM USER_TABLES'; + break; + } + + $result = $this->db->sql_query($sql); + + $tables = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $name = current($row); + $tables[$name] = $name; + } + $this->db->sql_freeresult($result); + + return $tables; + } + + /** + * {@inheritDoc} + */ + function sql_table_exists($table_name) + { + $this->db->sql_return_on_error(true); + $result = $this->db->sql_query_limit('SELECT * FROM ' . $table_name, 1); + $this->db->sql_return_on_error(false); + + if ($result) + { + $this->db->sql_freeresult($result); + return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_create_table($table_name, $table_data) + { + // holds the DDL for a column + $columns = $statements = array(); + + if ($this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // Begin transaction + $statements[] = 'begin'; + + // Determine if we have created a PRIMARY KEY in the earliest + $primary_key_gen = false; + + // Determine if the table requires a sequence + $create_sequence = false; + + // Begin table sql statement + $table_sql = 'CREATE TABLE ' . $table_name . ' (' . "\n"; + + // Iterate through the columns to create a table + foreach ($table_data['COLUMNS'] as $column_name => $column_data) + { + // here lies an array, filled with information compiled on the column's data + $prepared_column = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + + if (isset($prepared_column['auto_increment']) && $prepared_column['auto_increment'] && strlen($column_name) > 26) // "${column_name}_gen" + { + trigger_error("Index name '${column_name}_gen' on table '$table_name' is too long. The maximum auto increment column length is 26 characters.", E_USER_ERROR); + } + + // here we add the definition of the new column to the list of columns + $columns[] = "\t {$column_name} " . $prepared_column['column_type_sql']; + + // see if we have found a primary key set due to a column definition if we have found it, we can stop looking + if (!$primary_key_gen) + { + $primary_key_gen = isset($prepared_column['primary_key_set']) && $prepared_column['primary_key_set']; + } + + // create sequence DDL based off of the existance of auto incrementing columns + if (!$create_sequence && isset($prepared_column['auto_increment']) && $prepared_column['auto_increment']) + { + $create_sequence = $column_name; + } + } + + // this makes up all the columns in the create table statement + $table_sql .= implode(",\n", $columns); + + // we have yet to create a primary key for this table, + // this means that we can add the one we really wanted instead + if (!$primary_key_gen) + { + // Write primary key + if (isset($table_data['PRIMARY_KEY'])) + { + if (!is_array($table_data['PRIMARY_KEY'])) + { + $table_data['PRIMARY_KEY'] = array($table_data['PRIMARY_KEY']); + } + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + case 'sqlite3': + $table_sql .= ",\n\t PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + + case 'oracle': + $table_sql .= ",\n\t CONSTRAINT pk_{$table_name} PRIMARY KEY (" . implode(', ', $table_data['PRIMARY_KEY']) . ')'; + break; + } + } + } + + // close the table + switch ($this->sql_layer) + { + case 'mysql_41': + // make sure the table is in UTF-8 mode + $table_sql .= "\n) CHARACTER SET `utf8` COLLATE `utf8_bin`;"; + $statements[] = $table_sql; + break; + + case 'mysql_40': + case 'sqlite3': + $table_sql .= "\n);"; + $statements[] = $table_sql; + break; + + case 'oracle': + $table_sql .= "\n)"; + $statements[] = $table_sql; + + // do we need to add a sequence and a tigger for auto incrementing columns? + if ($create_sequence) + { + // create the actual sequence + $statements[] = "CREATE SEQUENCE {$table_name}_seq"; + + // the trigger is the mechanism by which we increment the counter + $trigger = "CREATE OR REPLACE TRIGGER t_{$table_name}\n"; + $trigger .= "BEFORE INSERT ON {$table_name}\n"; + $trigger .= "FOR EACH ROW WHEN (\n"; + $trigger .= "\tnew.{$create_sequence} IS NULL OR new.{$create_sequence} = 0\n"; + $trigger .= ")\n"; + $trigger .= "BEGIN\n"; + $trigger .= "\tSELECT {$table_name}_seq.nextval\n"; + $trigger .= "\tINTO :new.{$create_sequence}\n"; + $trigger .= "\tFROM dual;\n"; + $trigger .= "END;"; + + $statements[] = $trigger; + } + break; + } + + // Write Keys + if (isset($table_data['KEYS'])) + { + foreach ($table_data['KEYS'] as $key_name => $key_data) + { + if (!is_array($key_data[1])) + { + $key_data[1] = array($key_data[1]); + } + + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + $key_stmts = ($key_data[0] == 'UNIQUE') ? $this->sql_create_unique_index($table_name, $key_name, $key_data[1]) : $this->sql_create_index($table_name, $key_name, $key_data[1]); + + foreach ($key_stmts as $key_stmt) + { + $statements[] = $key_stmt; + } + + $this->return_statements = $old_return_statements; + } + } + + // Commit Transaction + $statements[] = 'commit'; + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function perform_schema_changes($schema_changes) + { + if (empty($schema_changes)) + { + return; + } + + $statements = array(); + $sqlite = false; + + // For SQLite we need to perform the schema changes in a much more different way + if ($this->db->get_sql_layer() == 'sqlite3' && $this->return_statements) + { + $sqlite_data = array(); + $sqlite = true; + } + + // Drop tables? + if (!empty($schema_changes['drop_tables'])) + { + foreach ($schema_changes['drop_tables'] as $table) + { + // only drop table if it exists + if ($this->sql_table_exists($table)) + { + $result = $this->sql_table_drop($table); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add tables? + if (!empty($schema_changes['add_tables'])) + { + foreach ($schema_changes['add_tables'] as $table => $table_data) + { + $result = $this->sql_create_table($table, $table_data); + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + + // Change columns? + if (!empty($schema_changes['change_columns'])) + { + foreach ($schema_changes['change_columns'] as $table => $columns) + { + foreach ($columns as $column_name => $column_data) + { + // If the column exists we change it, else we add it ;) + if ($column_exists = $this->sql_column_exists($table, $column_name)) + { + $result = $this->sql_column_change($table, $column_name, $column_data, true); + } + else + { + $result = $this->sql_column_add($table, $column_name, $column_data, true); + } + + if ($sqlite) + { + if ($column_exists) + { + $sqlite_data[$table]['change_columns'][] = $result; + } + else + { + $sqlite_data[$table]['add_columns'][] = $result; + } + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add columns? + if (!empty($schema_changes['add_columns'])) + { + foreach ($schema_changes['add_columns'] as $table => $columns) + { + foreach ($columns as $column_name => $column_data) + { + // Only add the column if it does not exist yet + if ($column_exists = $this->sql_column_exists($table, $column_name)) + { + continue; + // This is commented out here because it can take tremendous time on updates +// $result = $this->sql_column_change($table, $column_name, $column_data, true); + } + else + { + $result = $this->sql_column_add($table, $column_name, $column_data, true); + } + + if ($sqlite) + { + if ($column_exists) + { + continue; +// $sqlite_data[$table]['change_columns'][] = $result; + } + else + { + $sqlite_data[$table]['add_columns'][] = $result; + } + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Remove keys? + if (!empty($schema_changes['drop_keys'])) + { + foreach ($schema_changes['drop_keys'] as $table => $indexes) + { + foreach ($indexes as $index_name) + { + if (!$this->sql_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_index_drop($table, $index_name); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Drop columns? + if (!empty($schema_changes['drop_columns'])) + { + foreach ($schema_changes['drop_columns'] as $table => $columns) + { + foreach ($columns as $column) + { + // Only remove the column if it exists... + if ($this->sql_column_exists($table, $column)) + { + $result = $this->sql_column_remove($table, $column, true); + + if ($sqlite) + { + $sqlite_data[$table]['drop_columns'][] = $result; + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + } + + // Add primary keys? + if (!empty($schema_changes['add_primary_keys'])) + { + foreach ($schema_changes['add_primary_keys'] as $table => $columns) + { + $result = $this->sql_create_primary_key($table, $columns, true); + + if ($sqlite) + { + $sqlite_data[$table]['primary_key'] = $result; + } + else if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + + // Add unique indexes? + if (!empty($schema_changes['add_unique_index'])) + { + foreach ($schema_changes['add_unique_index'] as $table => $index_array) + { + foreach ($index_array as $index_name => $column) + { + if ($this->sql_unique_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_create_unique_index($table, $index_name, $column); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + // Add indexes? + if (!empty($schema_changes['add_index'])) + { + foreach ($schema_changes['add_index'] as $table => $index_array) + { + foreach ($index_array as $index_name => $column) + { + if ($this->sql_index_exists($table, $index_name)) + { + continue; + } + + $result = $this->sql_create_index($table, $index_name, $column); + + if ($this->return_statements) + { + $statements = array_merge($statements, $result); + } + } + } + } + + if ($sqlite) + { + foreach ($sqlite_data as $table_name => $sql_schema_changes) + { + // Create temporary table with original data + $statements[] = 'begin'; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}' + ORDER BY type DESC, name;"; + $result = $this->db->sql_query($sql); + + if (!$result) + { + continue; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $row['sql']); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + // Get the columns... + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $plain_table_cols = trim($matches[1]); + $new_table_cols = preg_split('/,(?![\s\w]+\))/m', $plain_table_cols); + $column_list = array(); + + foreach ($new_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + // note down the primary key notation because sqlite only supports adding it to the end for the new table + $primary_key = false; + $_new_cols = array(); + + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + $primary_key = $declaration; + continue; + } + $_new_cols[] = $declaration; + } + + $new_table_cols = $_new_cols; + + // First of all... change columns + if (!empty($sql_schema_changes['change_columns'])) + { + foreach ($sql_schema_changes['change_columns'] as $column_sql) + { + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if (strpos($column_sql, $entities[0] . ' ') === 0) + { + $new_table_cols[$key] = $column_sql; + } + } + } + } + + if (!empty($sql_schema_changes['add_columns'])) + { + foreach ($sql_schema_changes['add_columns'] as $column_sql) + { + $new_table_cols[] = $column_sql; + } + } + + // Now drop them... + if (!empty($sql_schema_changes['drop_columns'])) + { + foreach ($sql_schema_changes['drop_columns'] as $column_name) + { + // Remove from column list... + $new_column_list = array(); + foreach ($column_list as $key => $value) + { + if ($value === $column_name) + { + continue; + } + + $new_column_list[] = $value; + } + + $column_list = $new_column_list; + + // Remove from table... + $_new_cols = array(); + foreach ($new_table_cols as $key => $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if (strpos($column_name . ' ', $entities[0] . ' ') === 0) + { + continue; + } + $_new_cols[] = $declaration; + } + $new_table_cols = $_new_cols; + } + } + + // Primary key... + if (!empty($sql_schema_changes['primary_key'])) + { + $new_table_cols[] = 'PRIMARY KEY (' . implode(', ', $sql_schema_changes['primary_key']) . ')'; + } + // Add a new one or the old primary key + else if ($primary_key !== false) + { + $new_table_cols[] = $primary_key; + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $new_table_cols) . ');'; + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + } + } + + if ($this->return_statements) + { + return $statements; + } + } + + /** + * {@inheritDoc} + */ + function sql_list_columns($table_name) + { + $columns = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $sql = "SHOW COLUMNS FROM $table_name"; + break; + + case 'oracle': + $sql = "SELECT column_name + FROM user_tab_columns + WHERE LOWER(table_name) = '" . strtolower($table_name) . "'"; + break; + + case 'sqlite3': + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}'"; + + $result = $this->db->sql_query($sql); + + if (!$result) + { + return false; + } + + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + preg_match('#\((.*)\)#s', $row['sql'], $matches); + + $cols = trim($matches[1]); + $col_array = preg_split('/,(?![\s\w]+\))/m', $cols); + + foreach ($col_array as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + + $column = strtolower($entities[0]); + $columns[$column] = $column; + } + + return $columns; + break; + } + + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $column = strtolower(current($row)); + $columns[$column] = $column; + } + $this->db->sql_freeresult($result); + + return $columns; + } + + /** + * {@inheritDoc} + */ + function sql_column_exists($table_name, $column_name) + { + $columns = $this->sql_list_columns($table_name); + + return isset($columns[$column_name]); + } + + /** + * {@inheritDoc} + */ + function sql_index_exists($table_name, $index_name) + { + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite3': + $sql = "PRAGMA index_list('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; + } + + switch ($this->sql_layer) + { + // These DBMS prefix index name with the table name + case 'oracle': + case 'sqlite3': + $new_index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name, false); + break; + default: + $new_index_name = $this->check_index_name_length($table_name, $index_name, false); + break; + } + + if (strtolower($row[$col]) == strtolower($new_index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * {@inheritDoc} + */ + function sql_unique_index_exists($table_name, $index_name) + { + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name, table_owner + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'UNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite3': + $sql = "PRAGMA index_list('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && ($row['Non_unique'] || $row[$col] == 'PRIMARY')) + { + continue; + } + + if ($this->sql_layer == 'sqlite3' && !$row['unique']) + { + continue; + } + + // These DBMS prefix index name with the table name + switch ($this->sql_layer) + { + case 'oracle': + // Two cases here... prefixed with U_[table_owner] and not prefixed with table_name + if (strpos($row[$col], 'U_') === 0) + { + $row[$col] = substr($row[$col], strlen('U_' . $row['table_owner']) + 1); + } + else if (strpos($row[$col], strtoupper($table_name)) === 0) + { + $row[$col] = substr($row[$col], strlen($table_name) + 1); + } + break; + + case 'sqlite3': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + if (strtolower($row[$col]) == strtolower($index_name)) + { + $this->db->sql_freeresult($result); + return true; + } + } + $this->db->sql_freeresult($result); + + return false; + } + + /** + * Private method for performing sql statements (either execute them or return them) + * @access private + */ + function _sql_run_sql($statements) + { + if ($this->return_statements) + { + return $statements; + } + + // We could add error handling here... + foreach ($statements as $sql) + { + if ($sql === 'begin') + { + $this->db->sql_transaction('begin'); + } + else if ($sql === 'commit') + { + $this->db->sql_transaction('commit'); + } + else + { + $this->db->sql_query($sql); + } + } + + return true; + } + + /** + * Function to prepare some column information for better usage + * @access private + */ + function sql_prepare_column_data($table_name, $column_name, $column_data) + { + if (strlen($column_name) > 30) + { + trigger_error("Column name '$column_name' on table '$table_name' is too long. The maximum is 30 characters.", E_USER_ERROR); + } + + // Get type + list($column_type) = $this->get_column_type($column_data[0]); + + // Adjust default value if db-dependent specified + if (is_array($column_data[1])) + { + $column_data[1] = (isset($column_data[1][$this->sql_layer])) ? $column_data[1][$this->sql_layer] : $column_data[1]['default']; + } + + $sql = ''; + + $return_array = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $sql .= " {$column_type} "; + + // For hexadecimal values do not use single quotes + if (!is_null($column_data[1]) && substr($column_type, -4) !== 'text' && substr($column_type, -4) !== 'blob') + { + $sql .= (strpos($column_data[1], '0x') === 0) ? "DEFAULT {$column_data[1]} " : "DEFAULT '{$column_data[1]}' "; + } + + if (!is_null($column_data[1]) || (isset($column_data[2]) && $column_data[2] == 'auto_increment')) + { + $sql .= 'NOT NULL'; + } + else + { + $sql .= 'NULL'; + } + + if (isset($column_data[2])) + { + if ($column_data[2] == 'auto_increment') + { + $sql .= ' auto_increment'; + } + else if ($this->sql_layer === 'mysql_41' && $column_data[2] == 'true_sort') + { + $sql .= ' COLLATE utf8_unicode_ci'; + } + } + + if (isset($column_data['after'])) + { + $return_array['after'] = $column_data['after']; + } + + break; + + case 'oracle': + $sql .= " {$column_type} "; + $sql .= (!is_null($column_data[1])) ? "DEFAULT '{$column_data[1]}' " : ''; + + // In Oracle empty strings ('') are treated as NULL. + // Therefore in oracle we allow NULL's for all DEFAULT '' entries + // Oracle does not like setting NOT NULL on a column that is already NOT NULL (this happens only on number fields) + if (!preg_match('/number/i', $column_type)) + { + $sql .= ($column_data[1] === '' || $column_data[1] === null) ? '' : 'NOT NULL'; + } + + $return_array['auto_increment'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $return_array['auto_increment'] = true; + } + + break; + + case 'sqlite3': + $return_array['primary_key_set'] = false; + if (isset($column_data[2]) && $column_data[2] == 'auto_increment') + { + $sql .= ' INTEGER PRIMARY KEY AUTOINCREMENT'; + $return_array['primary_key_set'] = true; + } + else + { + $sql .= ' ' . $column_type; + } + + if (!is_null($column_data[1])) + { + $sql .= ' NOT NULL '; + $sql .= "DEFAULT '{$column_data[1]}'"; + } + + break; + } + + $return_array['column_type_sql'] = $sql; + + return $return_array; + } + + /** + * Get the column's database type from the type map + * + * @param string $column_map_type + * @return array column type for this database + * and map type without length + */ + function get_column_type($column_map_type) + { + $column_type = ''; + if (strpos($column_map_type, ':') !== false) + { + list($orig_column_type, $column_length) = explode(':', $column_map_type); + if (!is_array($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'])) + { + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'], $column_length); + } + else + { + if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'])) + { + switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][0]) + { + case 'div': + $column_length /= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['rule'][1]; + $column_length = ceil($column_length); + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); + break; + } + } + + if (isset($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'])) + { + switch ($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][0]) + { + case 'mult': + $column_length *= $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][1]; + if ($column_length > $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][2]) + { + $column_type = $this->dbms_type_map[$this->sql_layer][$orig_column_type . ':']['limit'][3]; + } + else + { + $column_type = sprintf($this->dbms_type_map[$this->sql_layer][$orig_column_type . ':'][0], $column_length); + } + break; + } + } + } + $orig_column_type .= ':'; + } + else + { + $orig_column_type = $column_map_type; + $column_type = $this->dbms_type_map[$this->sql_layer][$column_map_type]; + } + + return array($column_type, $orig_column_type); + } + + /** + * {@inheritDoc} + */ + function sql_column_add($table_name, $column_name, $column_data, $inline = false) + { + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $after = (!empty($column_data['after'])) ? ' AFTER ' . $column_data['after'] : ''; + $statements[] = 'ALTER TABLE `' . $table_name . '` ADD COLUMN `' . $column_name . '` ' . $column_data['column_type_sql'] . $after; + break; + + case 'oracle': + // Does not support AFTER, only through temporary table + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; + break; + + case 'sqlite3': + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD ' . $column_name . ' ' . $column_data['column_type_sql']; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_column_remove($table_name, $column_name, $inline = false) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE `' . $table_name . '` DROP COLUMN `' . $column_name . '`'; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . ' DROP COLUMN ' . $column_name; + break; + + case 'sqlite3': + + if ($inline && $this->return_statements) + { + return $column_name; + } + + $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name, $column_name); + if (empty($recreate_queries)) + { + break; + } + + $statements[] = 'begin'; + + $sql_create_table = array_shift($recreate_queries); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $sql_create_table, $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY' || $entities[0] === $column_name) + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + $new_table_cols = trim(preg_replace('/' . $column_name . '\b[^,]+(?:,|$)/m', '', $new_table_cols)); + if (substr($new_table_cols, -1) === ',') + { + // Remove the comma from the last entry again + $new_table_cols = substr($new_table_cols, 0, -1); + } + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ');'; + $statements = array_merge($statements, $recreate_queries); + + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_index_drop($table_name, $index_name) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $index_name = $this->check_index_name_length($table_name, $index_name, false); + $statements[] = 'DROP INDEX ' . $index_name . ' ON ' . $table_name; + break; + + case 'oracle': + case 'sqlite3': + $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name, false); + $statements[] = 'DROP INDEX ' . $index_name; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_table_drop($table_name) + { + $statements = array(); + + if (!$this->sql_table_exists($table_name)) + { + return $this->_sql_run_sql($statements); + } + + // the most basic operation, get rid of the table + $statements[] = 'DROP TABLE ' . $table_name; + + switch ($this->sql_layer) + { + case 'oracle': + $sql = 'SELECT A.REFERENCED_NAME + FROM USER_DEPENDENCIES A, USER_TRIGGERS B + WHERE A.REFERENCED_TYPE = \'SEQUENCE\' + AND A.NAME = B.TRIGGER_NAME + AND B.TABLE_NAME = \'' . strtoupper($table_name) . "'"; + $result = $this->db->sql_query($sql); + + // any sequences ref'd to this table's triggers? + while ($row = $this->db->sql_fetchrow($result)) + { + $statements[] = "DROP SEQUENCE {$row['referenced_name']}"; + } + $this->db->sql_freeresult($result); + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_primary_key($table_name, $column, $inline = false) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD PRIMARY KEY (' . implode(', ', $column) . ')'; + break; + + case 'oracle': + $statements[] = 'ALTER TABLE ' . $table_name . ' add CONSTRAINT pk_' . $table_name . ' PRIMARY KEY (' . implode(', ', $column) . ')'; + break; + + case 'sqlite3': + + if ($inline && $this->return_statements) + { + return $column; + } + + $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name); + if (empty($recreate_queries)) + { + break; + } + + $statements[] = 'begin'; + + $sql_create_table = array_shift($recreate_queries); + + // Create a backup table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $sql_create_table, $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $declaration) + { + $entities = preg_split('#\s+#', trim($declaration)); + if ($entities[0] == 'PRIMARY') + { + continue; + } + $column_list[] = $entities[0]; + } + + $columns = implode(',', $column_list); + + // create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . $new_table_cols . ', PRIMARY KEY (' . implode(', ', $column) . '));'; + $statements = array_merge($statements, $recreate_queries); + + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_unique_index($table_name, $index_name, $column) + { + $statements = array(); + + switch ($this->sql_layer) + { + case 'oracle': + case 'sqlite3': + $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name); + $statements[] = 'CREATE UNIQUE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mysql_40': + case 'mysql_41': + $index_name = $this->check_index_name_length($table_name, $index_name); + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD UNIQUE INDEX ' . $index_name . '(' . implode(', ', $column) . ')'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * {@inheritDoc} + */ + function sql_create_index($table_name, $index_name, $column) + { + $statements = array(); + + // remove index length unless MySQL4 + if ('mysql_40' != $this->sql_layer) + { + $column = preg_replace('#:.*$#', '', $column); + } + + switch ($this->sql_layer) + { + case 'oracle': + case 'sqlite3': + $index_name = $this->check_index_name_length($table_name, $table_name . '_' . $index_name); + $statements[] = 'CREATE INDEX ' . $index_name . ' ON ' . $table_name . '(' . implode(', ', $column) . ')'; + break; + + case 'mysql_40': + // add index size to definition as required by MySQL4 + foreach ($column as $i => $col) + { + if (false !== strpos($col, ':')) + { + list($col, $index_size) = explode(':', $col); + $column[$i] = "$col($index_size)"; + } + } + // no break + case 'mysql_41': + $index_name = $this->check_index_name_length($table_name, $index_name); + $statements[] = 'ALTER TABLE ' . $table_name . ' ADD INDEX ' . $index_name . ' (' . implode(', ', $column) . ')'; + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Check whether the index name is too long + * + * @param string $table_name + * @param string $index_name + * @param bool $throw_error + * @return string The index name, shortened if too long + */ + protected function check_index_name_length($table_name, $index_name, $throw_error = true) + { + $max_index_name_length = $this->get_max_index_name_length(); + if (strlen($index_name) > $max_index_name_length) + { + // Try removing the table prefix if it's at the beginning + $table_prefix = substr(CONFIG_TABLE, 0, -6); // strlen(config) + if (strpos($index_name, $table_prefix) === 0) + { + $index_name = substr($index_name, strlen($table_prefix)); + return $this->check_index_name_length($table_name, $index_name, $throw_error); + } + + // Try removing the remaining suffix part of table name then + $table_suffix = substr($table_name, strlen($table_prefix)); + if (strpos($index_name, $table_suffix) === 0) + { + // Remove the suffix and underscore separator between table_name and index_name + $index_name = substr($index_name, strlen($table_suffix) + 1); + return $this->check_index_name_length($table_name, $index_name, $throw_error); + } + + if ($throw_error) + { + trigger_error("Index name '$index_name' on table '$table_name' is too long. The maximum is $max_index_name_length characters.", E_USER_ERROR); + } + } + + return $index_name; + } + + /** + * Get maximum index name length. Might vary depending on db type + * + * @return int Maximum index name length + */ + protected function get_max_index_name_length() + { + return 30; + } + + /** + * {@inheritDoc} + */ + function sql_list_index($table_name) + { + $index_array = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $sql = 'SHOW KEYS + FROM ' . $table_name; + $col = 'Key_name'; + break; + + case 'oracle': + $sql = "SELECT index_name + FROM user_indexes + WHERE table_name = '" . strtoupper($table_name) . "' + AND generated = 'N' + AND uniqueness = 'NONUNIQUE'"; + $col = 'index_name'; + break; + + case 'sqlite3': + $sql = "PRAGMA index_info('" . $table_name . "');"; + $col = 'name'; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (($this->sql_layer == 'mysql_40' || $this->sql_layer == 'mysql_41') && !$row['Non_unique']) + { + continue; + } + + switch ($this->sql_layer) + { + case 'oracle': + case 'sqlite3': + $row[$col] = substr($row[$col], strlen($table_name) + 1); + break; + } + + $index_array[] = $row[$col]; + } + $this->db->sql_freeresult($result); + + return array_map('strtolower', $index_array); + } + + /** + * Removes table_name from the index_name if it is at the beginning + * + * @param $table_name + * @param $index_name + * @return string + */ + protected function strip_table_name_from_index_name($table_name, $index_name) + { + return (strpos(strtoupper($index_name), strtoupper($table_name)) === 0) ? substr($index_name, strlen($table_name) + 1) : $index_name; + } + + /** + * {@inheritDoc} + */ + function sql_column_change($table_name, $column_name, $column_data, $inline = false) + { + $original_column_data = $column_data; + $column_data = $this->sql_prepare_column_data($table_name, $column_name, $column_data); + $statements = array(); + + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + $statements[] = 'ALTER TABLE `' . $table_name . '` CHANGE `' . $column_name . '` `' . $column_name . '` ' . $column_data['column_type_sql']; + break; + + case 'oracle': + // We need the data here + $old_return_statements = $this->return_statements; + $this->return_statements = true; + + // Get list of existing indexes + $indexes = $this->get_existing_indexes($table_name, $column_name); + $unique_indexes = $this->get_existing_indexes($table_name, $column_name, true); + + // Drop any indexes + if (!empty($indexes) || !empty($unique_indexes)) + { + $drop_indexes = array_merge(array_keys($indexes), array_keys($unique_indexes)); + foreach ($drop_indexes as $index_name) + { + $result = $this->sql_index_drop($table_name, $this->strip_table_name_from_index_name($table_name, $index_name)); + $statements = array_merge($statements, $result); + } + } + + $temp_column_name = 'temp_' . substr(md5($column_name), 0, 25); + // Add a temporary table with the new type + $result = $this->sql_column_add($table_name, $temp_column_name, $original_column_data); + $statements = array_merge($statements, $result); + + // Copy the data to the new column + $statements[] = 'UPDATE ' . $table_name . ' SET ' . $temp_column_name . ' = ' . $column_name; + + // Drop the original column + $result = $this->sql_column_remove($table_name, $column_name); + $statements = array_merge($statements, $result); + + // Recreate the original column with the new type + $result = $this->sql_column_add($table_name, $column_name, $original_column_data); + $statements = array_merge($statements, $result); + + if (!empty($indexes)) + { + // Recreate indexes after we changed the column + foreach ($indexes as $index_name => $index_data) + { + $result = $this->sql_create_index($table_name, $this->strip_table_name_from_index_name($table_name, $index_name), $index_data); + $statements = array_merge($statements, $result); + } + } + + if (!empty($unique_indexes)) + { + // Recreate unique indexes after we changed the column + foreach ($unique_indexes as $index_name => $index_data) + { + $result = $this->sql_create_unique_index($table_name, $this->strip_table_name_from_index_name($table_name, $index_name), $index_data); + $statements = array_merge($statements, $result); + } + } + + // Copy the data to the original column + $statements[] = 'UPDATE ' . $table_name . ' SET ' . $column_name . ' = ' . $temp_column_name; + + // Drop the temporary column again + $result = $this->sql_column_remove($table_name, $temp_column_name); + $statements = array_merge($statements, $result); + + $this->return_statements = $old_return_statements; + break; + + case 'sqlite3': + + if ($inline && $this->return_statements) + { + return $column_name . ' ' . $column_data['column_type_sql']; + } + + $recreate_queries = $this->sqlite_get_recreate_table_queries($table_name); + if (empty($recreate_queries)) + { + break; + } + + $statements[] = 'begin'; + + $sql_create_table = array_shift($recreate_queries); + + // Create a temp table and populate it, destroy the existing one + $statements[] = preg_replace('#CREATE\s+TABLE\s+"?' . $table_name . '"?#i', 'CREATE TEMPORARY TABLE ' . $table_name . '_temp', $sql_create_table); + $statements[] = 'INSERT INTO ' . $table_name . '_temp SELECT * FROM ' . $table_name; + $statements[] = 'DROP TABLE ' . $table_name; + + preg_match('#\((.*)\)#s', $sql_create_table, $matches); + + $new_table_cols = trim($matches[1]); + $old_table_cols = preg_split('/,(?![\s\w]+\))/m', $new_table_cols); + $column_list = array(); + + foreach ($old_table_cols as $key => $declaration) + { + $declaration = trim($declaration); + + // Check for the beginning of the constraint section and stop + if (preg_match('/[^\(]*\s*PRIMARY KEY\s+\(/', $declaration) || + preg_match('/[^\(]*\s*UNIQUE\s+\(/', $declaration) || + preg_match('/[^\(]*\s*FOREIGN KEY\s+\(/', $declaration) || + preg_match('/[^\(]*\s*CHECK\s+\(/', $declaration)) + { + break; + } + + $entities = preg_split('#\s+#', $declaration); + $column_list[] = $entities[0]; + if ($entities[0] == $column_name) + { + $old_table_cols[$key] = $column_name . ' ' . $column_data['column_type_sql']; + } + } + + $columns = implode(',', $column_list); + + // Create a new table and fill it up. destroy the temp one + $statements[] = 'CREATE TABLE ' . $table_name . ' (' . implode(',', $old_table_cols) . ');'; + $statements = array_merge($statements, $recreate_queries); + + $statements[] = 'INSERT INTO ' . $table_name . ' (' . $columns . ') SELECT ' . $columns . ' FROM ' . $table_name . '_temp;'; + $statements[] = 'DROP TABLE ' . $table_name . '_temp'; + + $statements[] = 'commit'; + + break; + } + + return $this->_sql_run_sql($statements); + } + + /** + * Get a list with existing indexes for the column + * + * @param string $table_name + * @param string $column_name + * @param bool $unique Should we get unique indexes or normal ones + * @return array Array with Index name => columns + */ + public function get_existing_indexes($table_name, $column_name, $unique = false) + { + switch ($this->sql_layer) + { + case 'mysql_40': + case 'mysql_41': + case 'sqlite3': + // Not supported + throw new \Exception('DBMS is not supported'); + break; + } + + $sql = ''; + $existing_indexes = array(); + + switch ($this->sql_layer) + { + case 'oracle': + $sql = "SELECT ix.index_name AS phpbb_index_name, ix.uniqueness AS is_unique + FROM all_ind_columns ixc, all_indexes ix + WHERE ix.index_name = ixc.index_name + AND ixc.table_name = '" . strtoupper($table_name) . "' + AND ixc.column_name = '" . strtoupper($column_name) . "'"; + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + if (!isset($row['is_unique']) || ($unique && $row['is_unique'] == 'UNIQUE') || (!$unique && $row['is_unique'] == 'NONUNIQUE')) + { + $existing_indexes[$row['phpbb_index_name']] = array(); + } + } + $this->db->sql_freeresult($result); + + if (empty($existing_indexes)) + { + return array(); + } + + switch ($this->sql_layer) + { + case 'oracle': + $sql = "SELECT index_name AS phpbb_index_name, column_name AS phpbb_column_name + FROM all_ind_columns + WHERE table_name = '" . strtoupper($table_name) . "' + AND " . $this->db->sql_in_set('index_name', array_keys($existing_indexes)); + break; + } + + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $existing_indexes[$row['phpbb_index_name']][] = $row['phpbb_column_name']; + } + $this->db->sql_freeresult($result); + + return $existing_indexes; + } + + /** + * Returns the Queries which are required to recreate a table including indexes + * + * @param string $table_name + * @param string $remove_column When we drop a column, we remove the column + * from all indexes. If the index has no other + * column, we drop it completly. + * @return array + */ + protected function sqlite_get_recreate_table_queries($table_name, $remove_column = '') + { + $queries = array(); + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'table' + AND name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + $sql_create_table = $this->db->sql_fetchfield('sql'); + $this->db->sql_freeresult($result); + + if (!$sql_create_table) + { + return array(); + } + $queries[] = $sql_create_table; + + $sql = "SELECT sql + FROM sqlite_master + WHERE type = 'index' + AND tbl_name = '{$table_name}'"; + $result = $this->db->sql_query($sql); + while ($sql_create_index = $this->db->sql_fetchfield('sql')) + { + if ($remove_column) + { + $match = array(); + preg_match('#(?:[\w ]+)\((.*)\)#', $sql_create_index, $match); + if (!isset($match[1])) + { + continue; + } + + // Find and remove $remove_column from the index + $columns = explode(', ', $match[1]); + $found_column = array_search($remove_column, $columns); + if ($found_column !== false) + { + unset($columns[$found_column]); + + // If the column list is not empty add the index to the list + if (!empty($columns)) + { + $queries[] = str_replace($match[1], implode(', ', $columns), $sql_create_index); + } + } + else + { + $queries[] = $sql_create_index; + } + } + else + { + $queries[] = $sql_create_index; + } + } + $this->db->sql_freeresult($result); + + return $queries; + } +} diff --git a/phpbb/db/tools/tools_interface.php b/phpbb/db/tools/tools_interface.php new file mode 100644 index 0000000..f153f73 --- /dev/null +++ b/phpbb/db/tools/tools_interface.php @@ -0,0 +1,202 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\db\tools; + +/** + * Interface for a Database Tools for handling cross-db actions such as altering columns, etc. + */ +interface tools_interface +{ + /** + * Handle passed database update array. + * Expected structure... + * Key being one of the following + * drop_tables: Drop tables + * add_tables: Add tables + * change_columns: Column changes (only type, not name) + * add_columns: Add columns to a table + * drop_keys: Dropping keys + * drop_columns: Removing/Dropping columns + * add_primary_keys: adding primary keys + * add_unique_index: adding an unique index + * add_index: adding an index (can be column:index_size if you need to provide size) + * + * The values are in this format: + * {TABLE NAME} => array( + * {COLUMN NAME} => array({COLUMN TYPE}, {DEFAULT VALUE}, {OPTIONAL VARIABLES}), + * {KEY/INDEX NAME} => array({COLUMN NAMES}), + * ) + * + * + * @param array $schema_changes + * @return null + */ + public function perform_schema_changes($schema_changes); + + /** + * Gets a list of tables in the database. + * + * @return array Array of table names (all lower case) + */ + public function sql_list_tables(); + + /** + * Check if table exists + * + * @param string $table_name The table name to check for + * @return bool true if table exists, else false + */ + public function sql_table_exists($table_name); + + /** + * Create SQL Table + * + * @param string $table_name The table name to create + * @param array $table_data Array containing table data. + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_table($table_name, $table_data); + + /** + * Drop Table + * + * @param string $table_name The table name to drop + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_table_drop($table_name); + + /** + * Gets a list of columns of a table. + * + * @param string $table_name Table name + * @return array Array of column names (all lower case) + */ + public function sql_list_columns($table_name); + + /** + * Check whether a specified column exist in a table + * + * @param string $table_name Table to check + * @param string $column_name Column to check + * @return bool True if column exists, false otherwise + */ + public function sql_column_exists($table_name, $column_name); + + /** + * Add new column + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to add + * @param array $column_data Column data + * @param bool $inline Whether the query should actually be run, + * or return a string for adding the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_add($table_name, $column_name, $column_data, $inline = false); + + /** + * Change column type (not name!) + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to modify + * @param array $column_data Column data + * @param bool $inline Whether the query should actually be run, + * or return a string for modifying the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_change($table_name, $column_name, $column_data, $inline = false); + + /** + * Drop column + * + * @param string $table_name Table to modify + * @param string $column_name Name of the column to drop + * @param bool $inline Whether the query should actually be run, + * or return a string for deleting the column + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_column_remove($table_name, $column_name, $inline = false); + + /** + * List all of the indices that belong to a table + * + * NOTE: does not list + * - UNIQUE indices + * - PRIMARY keys + * + * @param string $table_name Table to check + * @return array Array with index names + */ + public function sql_list_index($table_name); + + /** + * Check if a specified index exists in table. Does not return PRIMARY KEY and UNIQUE indexes. + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * @return bool True if index exists, else false + */ + public function sql_index_exists($table_name, $index_name); + + /** + * Add index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the index to create + * @param string|array $column Either a string with a column name, or an array with columns + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_index($table_name, $index_name, $column); + + /** + * Drop Index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the index to delete + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_index_drop($table_name, $index_name); + + /** + * Check if a specified index exists in table. + * + * NOTE: Does not return normal and PRIMARY KEY indexes + * + * @param string $table_name Table to check the index at + * @param string $index_name The index name to check + * @return bool True if index exists, else false + */ + public function sql_unique_index_exists($table_name, $index_name); + + /** + * Add unique index + * + * @param string $table_name Table to modify + * @param string $index_name Name of the unique index to create + * @param string|array $column Either a string with a column name, or an array with columns + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_unique_index($table_name, $index_name, $column); + + /** + * Add primary key + * + * @param string $table_name Table to modify + * @param string|array $column Either a string with a column name, or an array with columns + * @param bool $inline Whether the query should actually be run, + * or return a string for creating the key + * @return array|true Statements to run, or true if the statements have been executed + */ + public function sql_create_primary_key($table_name, $column, $inline = false); +} diff --git a/phpbb/debug/debug.php b/phpbb/debug/debug.php new file mode 100644 index 0000000..c5ffada --- /dev/null +++ b/phpbb/debug/debug.php @@ -0,0 +1,80 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\debug; + +use Symfony\Component\Debug\BufferingLogger; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\Debug\ExceptionHandler; + +/** + * Registers all the debug tools. + + * @see Symfony\Component\Debug\Debug + */ +class debug +{ + private static $enabled = false; + + /** + * Enables the debug tools. + * + * This method registers an error handler and an exception handler. + * + * If the Symfony ClassLoader component is available, a special + * class loader is also registered. + * + * @param int $errorReportingLevel The level of error reporting you want + * @param bool $displayErrors Whether to display errors (for development) or just log them (for production) + */ + public static function enable($errorReportingLevel = null, $displayErrors = true) + { + if (static::$enabled) + { + return; + } + + static::$enabled = true; + + if ($errorReportingLevel !== null) + { + error_reporting($errorReportingLevel); + } + else + { + error_reporting(-1); + } + + if ('cli' !== php_sapi_name()) + { + ini_set('display_errors', 0); + ExceptionHandler::register(); + } + else if ($displayErrors && (!ini_get('log_errors') || ini_get('error_log'))) + { + // CLI - display errors only if they're not already logged to STDERR + ini_set('display_errors', 1); + } + + if ($displayErrors) + { + error_handler::register(new error_handler(new BufferingLogger())); + } + else + { + error_handler::register()->throwAt(0, true); + } + + DebugClassLoader::enable(); + } +} diff --git a/phpbb/debug/error_handler.php b/phpbb/debug/error_handler.php new file mode 100644 index 0000000..ebd828b --- /dev/null +++ b/phpbb/debug/error_handler.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\debug; + +use Symfony\Component\Debug\ErrorHandler; + +class error_handler extends ErrorHandler +{ + public function handleError($type, $message, $file, $line) + { + if ($type === E_USER_WARNING || $type === E_USER_NOTICE) + { + $handler = defined('PHPBB_MSG_HANDLER') ? PHPBB_MSG_HANDLER : 'msg_handler'; + + $handler($type, $message, $file, $line); + } + + return parent::handleError($type, $message, $file, $line); + } +} diff --git a/phpbb/di/container_builder.php b/phpbb/di/container_builder.php new file mode 100644 index 0000000..8c1ce8b --- /dev/null +++ b/phpbb/di/container_builder.php @@ -0,0 +1,673 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\di; + +use phpbb\filesystem\filesystem; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Finder\Finder; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; + +class container_builder +{ + /** + * @var string The environment to use. + */ + protected $environment; + + /** + * @var string phpBB Root Path + */ + protected $phpbb_root_path; + + /** + * @var string php file extension + */ + protected $php_ext; + + /** + * The container under construction + * + * @var ContainerBuilder + */ + protected $container; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $dbal_connection = null; + + /** + * Indicates whether extensions should be used (default to true). + * + * @var bool + */ + protected $use_extensions = true; + + /** + * Defines a custom path to find the configuration of the container (default to $this->phpbb_root_path . 'config') + * + * @var string + */ + protected $config_path = null; + + /** + * Indicates whether the container should be dumped to the filesystem (default to true). + * + * If DEBUG_CONTAINER is set this option is ignored and a new container is build. + * + * @var bool + */ + protected $use_cache = true; + + /** + * Indicates if the container should be compiled automatically (default to true). + * + * @var bool + */ + protected $compile_container = true; + + /** + * Custom parameters to inject into the container. + * + * Default to: + * array( + * 'core.root_path', $this->phpbb_root_path, + * 'core.php_ext', $this->php_ext, + * ); + * + * @var array + */ + protected $custom_parameters = []; + + /** + * @var \phpbb\config_php_file + */ + protected $config_php_file; + + /** + * @var string + */ + protected $cache_dir; + + /** + * @var array + */ + private $container_extensions; + + /** @var \Exception */ + private $build_exception; + + /** + * Constructor + * + * @param string $phpbb_root_path Path to the phpbb includes directory. + * @param string $php_ext php file extension + */ + public function __construct($phpbb_root_path, $php_ext) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Build and return a new Container respecting the current configuration + * + * @return \phpbb_cache_container|ContainerBuilder + */ + public function get_container() + { + try + { + $container_filename = $this->get_container_filename(); + $config_cache = new ConfigCache($container_filename, defined('DEBUG')); + if ($this->use_cache && $config_cache->isFresh()) + { + if ($this->use_extensions) + { + $autoload_cache = new ConfigCache($this->get_autoload_filename(), defined('DEBUG')); + if (!$autoload_cache->isFresh()) + { + // autoload cache should be refreshed + $this->load_extensions(); + } + + require($this->get_autoload_filename()); + } + + require($config_cache->getPath()); + $this->container = new \phpbb_cache_container(); + } + else + { + $this->container_extensions = array(new extension\core($this->get_config_path())); + + if ($this->use_extensions) + { + $this->load_extensions(); + } + + // Inject the config + if ($this->config_php_file) + { + $this->container_extensions[] = new extension\config($this->config_php_file); + } + + $this->container = $this->create_container($this->container_extensions); + + // Easy collections through tags + $this->container->addCompilerPass(new pass\collection_pass()); + + // Event listeners "phpBB style" + $this->container->addCompilerPass(new RegisterListenersPass('dispatcher', 'event.listener_listener', 'event.listener')); + + // Event listeners "Symfony style" + $this->container->addCompilerPass(new RegisterListenersPass('dispatcher')); + + if ($this->use_extensions) + { + $this->register_ext_compiler_pass(); + } + + $filesystem = new filesystem(); + $loader = new YamlFileLoader($this->container, new FileLocator($filesystem->realpath($this->get_config_path()))); + $loader->load($this->container->getParameter('core.environment') . '/config.yml'); + + $this->inject_custom_parameters(); + + if ($this->compile_container) + { + $this->container->compile(); + + if ($this->use_cache) + { + $this->dump_container($config_cache); + } + } + } + + if ($this->compile_container && $this->config_php_file) + { + $this->container->set('config.php', $this->config_php_file); + } + + $this->inject_dbal_driver(); + + return $this->container; + } + catch (\Exception $e) + { + // Don't try to recover if we are in the development environment + if ($this->get_environment() === 'development') + { + throw $e; + } + + if ($this->build_exception === null) + { + $this->build_exception = $e; + + return $this + ->without_extensions() + ->without_cache() + ->with_custom_parameters(array_merge($this->custom_parameters, [ + 'container_exception' => $e, + ])) + ->get_container(); + } + else + { + // Rethrow the original exception if it's still failing + throw $this->build_exception; + } + } + } + + /** + * Enable the extensions. + * + * @param string $environment The environment to use + * @return $this + */ + public function with_environment($environment) + { + $this->environment = $environment; + + return $this; + } + + /** + * Enable the extensions. + * + * @return $this + */ + public function with_extensions() + { + $this->use_extensions = true; + + return $this; + } + + /** + * Disable the extensions. + * + * @return $this + */ + public function without_extensions() + { + $this->use_extensions = false; + + return $this; + } + + /** + * Enable the caching of the container. + * + * If DEBUG_CONTAINER is set this option is ignored and a new container is build. + * + * @return $this + */ + public function with_cache() + { + $this->use_cache = true; + + return $this; + } + + /** + * Disable the caching of the container. + * + * @return $this + */ + public function without_cache() + { + $this->use_cache = false; + + return $this; + } + + /** + * Set the cache directory. + * + * @param string $cache_dir The cache directory. + * @return $this + */ + public function with_cache_dir($cache_dir) + { + $this->cache_dir = $cache_dir; + + return $this; + } + + /** + * Enable the compilation of the container. + * + * @return $this + */ + public function with_compiled_container() + { + $this->compile_container = true; + + return $this; + } + + /** + * Disable the compilation of the container. + * + * @return $this + */ + public function without_compiled_container() + { + $this->compile_container = false; + + return $this; + } + + /** + * Set a custom path to find the configuration of the container. + * + * @param string $config_path + * @return $this + */ + public function with_config_path($config_path) + { + $this->config_path = $config_path; + + return $this; + } + + /** + * Set custom parameters to inject into the container. + * + * @param array $custom_parameters + * @return $this + */ + public function with_custom_parameters($custom_parameters) + { + $this->custom_parameters = $custom_parameters; + + return $this; + } + + /** + * Set custom parameters to inject into the container. + * + * @param \phpbb\config_php_file $config_php_file + * @return $this + */ + public function with_config(\phpbb\config_php_file $config_php_file) + { + $this->config_php_file = $config_php_file; + + return $this; + } + + /** + * Returns the path to the container configuration (default: root_path/config) + * + * @return string + */ + protected function get_config_path() + { + return $this->config_path ?: $this->phpbb_root_path . 'config'; + } + + /** + * Returns the path to the cache directory (default: root_path/cache/environment). + * + * @return string Path to the cache directory. + */ + protected function get_cache_dir() + { + return $this->cache_dir ?: $this->phpbb_root_path . 'cache/' . $this->get_environment() . '/'; + } + + /** + * Load the enabled extensions. + */ + protected function load_extensions() + { + if ($this->config_php_file !== null) + { + // Build an intermediate container to load the ext list from the database + $container_builder = new container_builder($this->phpbb_root_path, $this->php_ext); + $ext_container = $container_builder + ->without_cache() + ->without_extensions() + ->with_config($this->config_php_file) + ->with_config_path($this->get_config_path()) + ->with_environment('production') + ->without_compiled_container() + ->get_container() + ; + + $ext_container->register('cache.driver', '\\phpbb\\cache\\driver\\dummy'); + $ext_container->compile(); + + $extensions = $ext_container->get('ext.manager')->all_enabled(); + + // Load each extension found + $autoloaders = ' $path) + { + $extension_class = '\\' . str_replace('/', '\\', $ext_name) . '\\di\\extension'; + + if (!class_exists($extension_class)) + { + $extension_class = '\\phpbb\\extension\\di\\extension_base'; + } + + $this->container_extensions[] = new $extension_class($ext_name, $path); + + // Load extension autoloader + $filename = $path . 'vendor/autoload.php'; + if (file_exists($filename)) + { + $autoloaders .= "require('{$filename}');\n"; + } + } + + $configCache = new ConfigCache($this->get_autoload_filename(), false); + $configCache->write($autoloaders); + + require($this->get_autoload_filename()); + } + else + { + // To load the extensions we need the database credentials. + // Automatically disable the extensions if we don't have them. + $this->use_extensions = false; + } + } + + /** + * Dump the container to the disk. + * + * @param ConfigCache $cache The config cache + */ + protected function dump_container($cache) + { + try + { + $dumper = new PhpDumper($this->container); + $proxy_dumper = new ProxyDumper(); + $dumper->setProxyDumper($proxy_dumper); + + $cached_container_dump = $dumper->dump(array( + 'class' => 'phpbb_cache_container', + 'base_class' => 'Symfony\\Component\\DependencyInjection\\ContainerBuilder', + )); + + $cache->write($cached_container_dump, $this->container->getResources()); + } + catch (IOException $e) + { + // Don't fail if the cache isn't writeable + } + } + + /** + * Create the ContainerBuilder object + * + * @param array $extensions Array of Container extension objects + * @return ContainerBuilder object + */ + protected function create_container(array $extensions) + { + $container = new ContainerBuilder(new ParameterBag($this->get_core_parameters())); + $container->setProxyInstantiator(new proxy_instantiator($this->get_cache_dir())); + + $extensions_alias = array(); + + foreach ($extensions as $extension) + { + $container->registerExtension($extension); + $extensions_alias[] = $extension->getAlias(); + } + + $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions_alias)); + + return $container; + } + + /** + * Inject the customs parameters into the container + */ + protected function inject_custom_parameters() + { + foreach ($this->custom_parameters as $key => $value) + { + $this->container->setParameter($key, $value); + } + } + + /** + * Inject the dbal connection driver into container + */ + protected function inject_dbal_driver() + { + if (empty($this->config_php_file)) + { + return; + } + + $config_data = $this->config_php_file->get_all(); + if (!empty($config_data)) + { + if ($this->dbal_connection === null) + { + $dbal_driver_class = $this->config_php_file->convert_30_dbms_to_31($this->config_php_file->get('dbms')); + /** @var \phpbb\db\driver\driver_interface $dbal_connection */ + $this->dbal_connection = new $dbal_driver_class(); + $this->dbal_connection->sql_connect( + $this->config_php_file->get('dbhost'), + $this->config_php_file->get('dbuser'), + $this->config_php_file->get('dbpasswd'), + $this->config_php_file->get('dbname'), + $this->config_php_file->get('dbport'), + false, + defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK + ); + } + $this->container->set('dbal.conn.driver', $this->dbal_connection); + } + } + + /** + * Returns the core parameters. + * + * @return array An array of core parameters + */ + protected function get_core_parameters() + { + return array_merge( + array( + 'core.root_path' => $this->phpbb_root_path, + 'core.php_ext' => $this->php_ext, + 'core.environment' => $this->get_environment(), + 'core.debug' => defined('DEBUG') ? DEBUG : false, + 'core.cache_dir' => $this->get_cache_dir(), + ), + $this->get_env_parameters() + ); + } + + /** + * Gets the environment parameters. + * + * Only the parameters starting with "PHPBB__" are considered. + * + * @return array An array of parameters + */ + protected function get_env_parameters() + { + $parameters = array(); + foreach ($_SERVER as $key => $value) + { + if (0 === strpos($key, 'PHPBB__')) + { + $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value; + } + } + + return $parameters; + } + + /** + * Get the filename under which the dumped container will be stored. + * + * @return string Path for dumped container + */ + protected function get_container_filename() + { + $container_params = [ + 'phpbb_root_path' => $this->phpbb_root_path, + 'use_extensions' => $this->use_extensions, + 'config_path' => $this->config_path, + ]; + + return $this->get_cache_dir() . 'container_' . md5(implode(',', $container_params)) . '.' . $this->php_ext; + } + + /** + * Get the filename under which the dumped extensions autoloader will be stored. + * + * @return string Path for dumped extensions autoloader + */ + protected function get_autoload_filename() + { + $container_params = [ + 'phpbb_root_path' => $this->phpbb_root_path, + 'use_extensions' => $this->use_extensions, + 'config_path' => $this->config_path, + ]; + + return $this->get_cache_dir() . 'autoload_' . md5(implode(',', $container_params)) . '.' . $this->php_ext; + } + + /** + * Return the name of the current environment. + * + * @return string + */ + protected function get_environment() + { + return $this->environment ?: PHPBB_ENVIRONMENT; + } + + private function register_ext_compiler_pass() + { + $finder = new Finder(); + $finder + ->name('*_pass.php') + ->path('di/pass') + ->files() + ->ignoreDotFiles(true) + ->ignoreUnreadableDirs(true) + ->ignoreVCS(true) + ->followLinks() + ->in($this->phpbb_root_path . 'ext') + ; + + /** @var \SplFileInfo $pass */ + foreach ($finder as $pass) + { + $filename = $pass->getPathname(); + $filename = substr($filename, 0, -strlen('.' . $pass->getExtension())); + $filename = str_replace(DIRECTORY_SEPARATOR, '/', $filename); + $className = preg_replace('#^.*ext/#', '', $filename); + $className = '\\' . str_replace('/', '\\', $className); + + if (class_exists($className) && in_array('Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface', class_implements($className), true)) + { + $this->container->addCompilerPass(new $className()); + } + } + } +} diff --git a/phpbb/di/extension/config.php b/phpbb/di/extension/config.php new file mode 100644 index 0000000..8c9de48 --- /dev/null +++ b/phpbb/di/extension/config.php @@ -0,0 +1,83 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\di\extension; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** +* Container config extension +*/ +class config extends Extension +{ + /** @var array */ + protected $config_php; + + public function __construct(\phpbb\config_php_file $config_php) + { + $this->config_php = $config_php; + } + + /** + * Loads a specific configuration. + * + * @param array $config An array of configuration values + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @throws \InvalidArgumentException When provided tag is not defined in this extension + */ + public function load(array $config, ContainerBuilder $container) + { + $parameters = array( + 'core.adm_relative_path' => $this->config_php->get('phpbb_adm_relative_path') ? $this->config_php->get('phpbb_adm_relative_path') : 'adm/', + 'core.table_prefix' => $this->config_php->get('table_prefix'), + 'cache.driver.class' => $this->convert_30_acm_type($this->config_php->get('acm_type')), + 'dbal.new_link' => defined('PHPBB_DB_NEW_LINK') && PHPBB_DB_NEW_LINK, + ); + $parameter_bag = $container->getParameterBag(); + + foreach ($parameters as $parameter => $value) + { + $container->setParameter($parameter, $parameter_bag->escapeValue($value)); + } + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias() + { + return 'config'; + } + + /** + * Convert 3.0 ACM type to 3.1 cache driver class name + * + * @param string $acm_type ACM type + * @return string cache driver class + */ + protected function convert_30_acm_type($acm_type) + { + if (preg_match('#^[a-z]+$#', $acm_type)) + { + return 'phpbb\\cache\\driver\\' . $acm_type; + } + + return $acm_type; + } +} diff --git a/phpbb/di/extension/container_configuration.php b/phpbb/di/extension/container_configuration.php new file mode 100644 index 0000000..4585d65 --- /dev/null +++ b/phpbb/di/extension/container_configuration.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\di\extension; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class container_configuration implements ConfigurationInterface +{ + + /** + * Generates the configuration tree builder. + * + * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('core'); + $rootNode + ->children() + ->booleanNode('require_dev_dependencies')->defaultValue(false)->end() + ->arrayNode('debug') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('exceptions')->defaultValue(false)->end() + ->end() + ->end() + ->arrayNode('twig') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('debug')->defaultValue(null)->end() + ->booleanNode('auto_reload')->defaultValue(null)->end() + ->booleanNode('enable_debug_extension')->defaultValue(false)->end() + ->end() + ->end() + ->end() + ; + return $treeBuilder; + } +} diff --git a/phpbb/di/extension/core.php b/phpbb/di/extension/core.php new file mode 100644 index 0000000..67150f0 --- /dev/null +++ b/phpbb/di/extension/core.php @@ -0,0 +1,124 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\di\extension; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** +* Container core extension +*/ +class core extends Extension +{ + const TWIG_OPTIONS_POSITION = 7; + + /** + * Config path + * @var string + */ + protected $config_path; + + /** + * Constructor + * + * @param string $config_path Config path + */ + public function __construct($config_path) + { + $this->config_path = $config_path; + } + + /** + * Loads a specific configuration. + * + * @param array $configs An array of configuration values + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @throws \InvalidArgumentException When provided tag is not defined in this extension + */ + public function load(array $configs, ContainerBuilder $container) + { + $filesystem = new \phpbb\filesystem\filesystem(); + $loader = new YamlFileLoader($container, new FileLocator($filesystem->realpath($this->config_path))); + $loader->load($container->getParameter('core.environment') . '/container/environment.yml'); + + $config = $this->getConfiguration($configs, $container); + $config = $this->processConfiguration($config, $configs); + + if ($config['require_dev_dependencies']) + { + if (!class_exists('Goutte\Client', true)) + { + trigger_error( + 'Composer development dependencies have not been set up for the ' . $container->getParameter('core.environment') . ' environment yet, run ' . + "'php ../composer.phar install --dev' from the phpBB directory to do so.", + E_USER_ERROR + ); + } + } + + // Set the Twig options if defined in the environment + $definition = $container->getDefinition('template.twig.environment'); + $twig_environment_options = $definition->getArgument(static::TWIG_OPTIONS_POSITION); + if ($config['twig']['debug']) + { + $twig_environment_options['debug'] = true; + } + if ($config['twig']['auto_reload']) + { + $twig_environment_options['auto_reload'] = true; + } + + // Replace the 7th argument, the options passed to the environment + $definition->replaceArgument(static::TWIG_OPTIONS_POSITION, $twig_environment_options); + + if ($config['twig']['enable_debug_extension']) + { + $definition = $container->getDefinition('template.twig.extensions.debug'); + $definition->addTag('twig.extension'); + } + + // Set the debug options + foreach ($config['debug'] as $name => $value) + { + $container->setParameter('debug.' . $name, $value); + } + } + + /** + * {@inheritdoc} + */ + public function getConfiguration(array $config, ContainerBuilder $container) + { + $r = new \ReflectionClass('\phpbb\di\extension\container_configuration'); + $container->addResource(new FileResource($r->getFileName())); + + return new container_configuration(); + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias() + { + return 'core'; + } +} diff --git a/phpbb/di/ordered_service_collection.php b/phpbb/di/ordered_service_collection.php new file mode 100644 index 0000000..046012a --- /dev/null +++ b/phpbb/di/ordered_service_collection.php @@ -0,0 +1,117 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\di; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Collection of services in a specified order + */ +class ordered_service_collection extends service_collection +{ + /** + * @var bool + */ + protected $is_ordered; + + /** + * @var array + */ + protected $service_ids; + + /** + * Constructor + * + * @param ContainerInterface $container Container object + */ + public function __construct(ContainerInterface $container) + { + $this->is_ordered = false; + $this->service_ids = array(); + + parent::__construct($container); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + if (!$this->is_ordered) + { + $this->sort_services(); + } + + return new service_collection_iterator($this); + } + + /** + * {@inheritdoc} + */ + public function offsetExists($index) + { + if (!$this->is_ordered) + { + $this->sort_services(); + } + + return parent::offsetExists($index); + } + + /** + * {@inheritdoc} + */ + public function offsetGet($index) + { + if (!$this->is_ordered) + { + $this->sort_services(); + } + + return parent::offsetGet($index); + } + + /** + * Adds a service ID to the collection + * + * @param string $service_id + * @param int $order + */ + public function add($service_id, $order = 0) + { + $order = (int) $order; + $this->service_ids[$order][] = $service_id; + $this->is_ordered = false; + } + + protected function sort_services() + { + if ($this->is_ordered) + { + return; + } + + $this->exchangeArray(array()); + ksort($this->service_ids); + foreach ($this->service_ids as $service_order_group) + { + foreach ($service_order_group as $service_id) + { + $this->offsetSet($service_id, null); + } + } + + $this->is_ordered = true; + } +} diff --git a/phpbb/di/pass/collection_pass.php b/phpbb/di/pass/collection_pass.php new file mode 100644 index 0000000..341f885 --- /dev/null +++ b/phpbb/di/pass/collection_pass.php @@ -0,0 +1,64 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\di\pass; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; + +/** +* Appends an add method call to the definition of each collection service for +* the services tagged with the appropriate name defined in the collection's +* service_collection tag. +*/ +class collection_pass implements CompilerPassInterface +{ + /** + * Modify the container before it is passed to the rest of the code + * + * @param ContainerBuilder $container ContainerBuilder object + * @return null + */ + public function process(ContainerBuilder $container) + { + foreach ($container->findTaggedServiceIds('service_collection') as $id => $data) + { + $definition = $container->getDefinition($id); + $is_ordered_collection = (substr($definition->getClass(), -strlen('ordered_service_collection')) === 'ordered_service_collection'); + $is_class_name_aware = (isset($data[0]['class_name_aware']) && $data[0]['class_name_aware']); + + foreach ($container->findTaggedServiceIds($data[0]['tag']) as $service_id => $service_data) + { + if ($is_ordered_collection) + { + $arguments = array($service_id, $service_data[0]['order']); + } + else + { + $arguments = array($service_id); + } + + if ($is_class_name_aware) + { + $service_definition = $container->getDefinition($service_id); + $definition->addMethodCall('add_service_class', array( + $service_id, + $service_definition->getClass() + )); + } + + $definition->addMethodCall('add', $arguments); + } + } + } +} diff --git a/phpbb/di/proxy_instantiator.php b/phpbb/di/proxy_instantiator.php new file mode 100644 index 0000000..70295a3 --- /dev/null +++ b/phpbb/di/proxy_instantiator.php @@ -0,0 +1,72 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\di; + +use ProxyManager\Configuration; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; +use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; + +/** + * Runtime lazy loading proxy generator extended for allowing use while using + * open_basedir restrictions + * + * Original author: Marco Pivetta + */ +class proxy_instantiator implements InstantiatorInterface +{ + /** + * @var LazyLoadingValueHolderFactory + */ + private $factory; + + /** + * proxy_instantiator constructor + * @param string $cache_dir Cache dir for fall back when using open_basedir + */ + public function __construct($cache_dir) + { + $config = new Configuration(); + + // Prevent trying to write to system temp dir in case of open_basedir + // restrictions being in effect + $tmp_dir = (function_exists('sys_get_temp_dir')) ? sys_get_temp_dir() : ''; + if (empty($tmp_dir) || !@file_exists($tmp_dir) || !@is_writable($tmp_dir)) + { + $config->setProxiesTargetDir($cache_dir); + } + $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + + $this->factory = new LazyLoadingValueHolderFactory($config); + } + + /** + * {@inheritdoc} + */ + public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator) + { + return $this->factory->createProxy( + $definition->getClass(), + function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($realInstantiator) { + $wrappedInstance = call_user_func($realInstantiator); + + $proxy->setProxyInitializer(null); + + return true; + } + ); + } +} diff --git a/phpbb/di/service_collection.php b/phpbb/di/service_collection.php new file mode 100644 index 0000000..8e9175e --- /dev/null +++ b/phpbb/di/service_collection.php @@ -0,0 +1,106 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\di; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** +* Collection of services to be configured at container compile time. +*/ +class service_collection extends \ArrayObject +{ + /** + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * @var array + */ + protected $service_classes; + + /** + * Constructor + * + * @param ContainerInterface $container Container object + */ + public function __construct(ContainerInterface $container) + { + $this->container = $container; + $this->service_classes = array(); + } + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new service_collection_iterator($this); + } + + // Because of a PHP issue we have to redefine offsetExists + // (even with a call to the parent): + // https://bugs.php.net/bug.php?id=66834 + // https://bugs.php.net/bug.php?id=67067 + // But it triggers a sniffer issue that we have to skip + // @codingStandardsIgnoreStart + /** + * {@inheritdoc} + */ + public function offsetExists($index) + { + return parent::offsetExists($index); + } + // @codingStandardsIgnoreEnd + + /** + * {@inheritdoc} + */ + public function offsetGet($index) + { + return $this->container->get($index); + } + + /** + * Add a service to the collection + * + * @param string $name The service name + * @return null + */ + public function add($name) + { + $this->offsetSet($name, null); + } + + /** + * Add a service's class to the collection + * + * @param string $service_id + * @param string $class + */ + public function add_service_class($service_id, $class) + { + $this->service_classes[$service_id] = $class; + } + + /** + * Get services' classes + * + * @return array + */ + public function get_service_classes() + { + return $this->service_classes; + } +} diff --git a/phpbb/di/service_collection_iterator.php b/phpbb/di/service_collection_iterator.php new file mode 100644 index 0000000..31bc156 --- /dev/null +++ b/phpbb/di/service_collection_iterator.php @@ -0,0 +1,46 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\di; + +/** +* Iterator which loads the services when they are requested +*/ +class service_collection_iterator extends \ArrayIterator +{ + /** + * @var \phpbb\di\service_collection + */ + protected $collection; + + /** + * Construct an ArrayIterator for service_collection + * + * @param \phpbb\di\service_collection $collection The collection to iterate over + * @param int $flags Flags to control the behaviour of the ArrayObject object. + * @see ArrayObject::setFlags() + */ + public function __construct(service_collection $collection, $flags = 0) + { + parent::__construct($collection->getArrayCopy(), $flags); + $this->collection = $collection; + } + + /** + * {@inheritdoc} + */ + public function current() + { + return $this->collection->offsetGet($this->key()); + } +} diff --git a/phpbb/error_collector.php b/phpbb/error_collector.php new file mode 100644 index 0000000..bf8efd1 --- /dev/null +++ b/phpbb/error_collector.php @@ -0,0 +1,73 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +class error_collector +{ + var $errors; + var $error_types; + + /** + * Constructor. + * + * The variable $error_types may be set to a mask of PHP error types that + * the collector should keep, e.g. `E_ALL`. If unset, the current value of + * the error_reporting() function will be used to determine which errors + * the collector will keep. + * + * @see PHPBB3-13306 + * @param int|null $error_types + */ + function __construct($error_types = null) + { + $this->errors = array(); + $this->error_types = $error_types; + } + + function install() + { + set_error_handler(array(&$this, 'error_handler'), ($this->error_types !== null) ? $this->error_types : error_reporting()); + } + + function uninstall() + { + restore_error_handler(); + } + + function error_handler($errno, $msg_text, $errfile, $errline) + { + $this->errors[] = array($errno, $msg_text, $errfile, $errline); + } + + function format_errors() + { + $text = ''; + foreach ($this->errors as $error) + { + if (!empty($text)) + { + $text .= "
\n"; + } + + list($errno, $msg_text, $errfile, $errline) = $error; + + // Prevent leakage of local path to phpBB install + $errfile = phpbb_filter_root_path($errfile); + + $text .= "Errno $errno: $msg_text at $errfile line $errline"; + } + + return $text; + } +} diff --git a/phpbb/event/data.php b/phpbb/event/data.php new file mode 100644 index 0000000..276ab02 --- /dev/null +++ b/phpbb/event/data.php @@ -0,0 +1,78 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\event; + +use Symfony\Component\EventDispatcher\Event; + +class data extends Event implements \ArrayAccess +{ + private $data; + + public function __construct(array $data = array()) + { + $this->set_data($data); + } + + public function set_data(array $data = array()) + { + $this->data = $data; + } + + public function get_data() + { + return $this->data; + } + + /** + * Returns data filtered to only include specified keys. + * + * This effectively discards any keys added to data by hooks. + */ + public function get_data_filtered($keys) + { + return array_intersect_key($this->data, array_flip($keys)); + } + + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + public function offsetGet($offset) + { + return isset($this->data[$offset]) ? $this->data[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + + /** + * Returns data with updated key in specified offset. + * + * @param string $subarray Data array subarray + * @param string $key Subarray key + * @param mixed $value Value to update + */ + public function update_subarray($subarray, $key, $value) + { + $this->data[$subarray][$key] = $value; + } +} diff --git a/phpbb/event/dispatcher.php b/phpbb/event/dispatcher.php new file mode 100644 index 0000000..1ba2ab8 --- /dev/null +++ b/phpbb/event/dispatcher.php @@ -0,0 +1,83 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\event; + +use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; +use Symfony\Component\EventDispatcher\Event; + +/** +* Extension of the Symfony2 EventDispatcher +* +* It provides an additional `trigger_event` method, which +* gives some syntactic sugar for dispatching events. Instead +* of creating the event object, the method will do that for +* you. +* +* Example: +* +* $vars = array('page_title'); +* extract($phpbb_dispatcher->trigger_event('core.index', compact($vars))); +* +*/ +class dispatcher extends ContainerAwareEventDispatcher implements dispatcher_interface +{ + /** + * @var bool + */ + protected $disabled = false; + + /** + * {@inheritdoc} + */ + public function trigger_event($eventName, $data = array()) + { + $event = new \phpbb\event\data($data); + $this->dispatch($eventName, $event); + return $event->get_data_filtered(array_keys($data)); + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if ($this->disabled) + { + return $event; + } + + foreach ((array) $eventName as $name) + { + $event = parent::dispatch($name, $event); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function disable() + { + $this->disabled = true; + } + + /** + * {@inheritdoc} + */ + public function enable() + { + $this->disabled = false; + } +} diff --git a/phpbb/event/dispatcher_interface.php b/phpbb/event/dispatcher_interface.php new file mode 100644 index 0000000..c66aa98 --- /dev/null +++ b/phpbb/event/dispatcher_interface.php @@ -0,0 +1,50 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\event; + +/** +* Extension of the Symfony2 EventDispatcher +* +* It provides an additional `trigger_event` method, which +* gives some syntactic sugar for dispatching events. Instead +* of creating the event object, the method will do that for +* you. +* +* Example: +* +* $vars = array('page_title'); +* extract($phpbb_dispatcher->trigger_event('core.index', compact($vars))); +* +*/ +interface dispatcher_interface extends \Symfony\Component\EventDispatcher\EventDispatcherInterface +{ + /** + * Construct and dispatch an event + * + * @param string $eventName The event name + * @param array $data An array containing the variables sending with the event + * @return mixed + */ + public function trigger_event($eventName, $data = array()); + + /** + * Disable the event dispatcher. + */ + public function disable(); + + /** + * Enable the event dispatcher. + */ + public function enable(); +} diff --git a/phpbb/event/kernel_exception_subscriber.php b/phpbb/event/kernel_exception_subscriber.php new file mode 100644 index 0000000..373e59b --- /dev/null +++ b/phpbb/event/kernel_exception_subscriber.php @@ -0,0 +1,139 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\event; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpFoundation\Response; + +class kernel_exception_subscriber implements EventSubscriberInterface +{ + /** + * Set to true to show full exception messages + * + * @var bool + */ + protected $debug; + + /** + * Template object + * + * @var \phpbb\template\template + */ + protected $template; + + /** + * Language object + * + * @var \phpbb\language\language + */ + protected $language; + + /** @var \phpbb\request\type_cast_helper */ + protected $type_caster; + + /** + * Construct method + * + * @param \phpbb\template\template $template Template object + * @param \phpbb\language\language $language Language object + * @param bool $debug Set to true to show full exception messages + */ + public function __construct(\phpbb\template\template $template, \phpbb\language\language $language, $debug = false) + { + $this->debug = $debug || defined('DEBUG'); + $this->template = $template; + $this->language = $language; + $this->type_caster = new \phpbb\request\type_cast_helper(); + } + + /** + * This listener is run when the KernelEvents::EXCEPTION event is triggered + * + * @param GetResponseForExceptionEvent $event + * @return null + */ + public function on_kernel_exception(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + + $message = $exception->getMessage(); + $this->type_caster->set_var($message, $message, 'string', true, false); + + if ($exception instanceof \phpbb\exception\exception_interface) + { + $message = $this->language->lang_array($message, $exception->get_parameters()); + } + else if (!$this->debug && $exception instanceof NotFoundHttpException) + { + $message = $this->language->lang('PAGE_NOT_FOUND'); + } + + // Show text in bold + $message = preg_replace('#<(/?strong)>#i', '<$1>', $message); + + if (!$event->getRequest()->isXmlHttpRequest()) + { + page_header($this->language->lang('INFORMATION')); + + $this->template->assign_vars(array( + 'MESSAGE_TITLE' => $this->language->lang('INFORMATION'), + 'MESSAGE_TEXT' => $message, + )); + + $this->template->set_filenames(array( + 'body' => 'message_body.html', + )); + + page_footer(true, false, false); + + $response = new Response($this->template->assign_display('body'), 500); + } + else + { + $data = array(); + + if (!empty($message)) + { + $data['message'] = $message; + } + + if ($this->debug) + { + $data['trace'] = $exception->getTrace(); + } + + $response = new JsonResponse($data, 500); + } + + if ($exception instanceof HttpExceptionInterface) + { + $response->setStatusCode($exception->getStatusCode()); + $response->headers->add($exception->getHeaders()); + } + + $event->setResponse($response); + } + + static public function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => 'on_kernel_exception', + ); + } +} diff --git a/phpbb/event/kernel_terminate_subscriber.php b/phpbb/event/kernel_terminate_subscriber.php new file mode 100644 index 0000000..f0d0a2f --- /dev/null +++ b/phpbb/event/kernel_terminate_subscriber.php @@ -0,0 +1,41 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\event; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; + +class kernel_terminate_subscriber implements EventSubscriberInterface +{ + /** + * This listener is run when the KernelEvents::TERMINATE event is triggered + * This comes after a Response has been sent to the server; this is + * primarily cleanup stuff. + * + * @param PostResponseEvent $event + * @return null + */ + public function on_kernel_terminate(PostResponseEvent $event) + { + exit_handler(); + } + + static public function getSubscribedEvents() + { + return array( + KernelEvents::TERMINATE => array('on_kernel_terminate', ~PHP_INT_MAX), + ); + } +} diff --git a/phpbb/event/md_exporter.php b/phpbb/event/md_exporter.php new file mode 100644 index 0000000..c3942bd --- /dev/null +++ b/phpbb/event/md_exporter.php @@ -0,0 +1,561 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\event; + +/** +* Crawls through a markdown file and grabs all events +*/ +class md_exporter +{ + /** @var string Path where we look for files*/ + protected $path; + + /** @var string phpBB Root Path */ + protected $root_path; + + /** @var string The minimum version for the events to return */ + protected $min_version; + + /** @var string The maximum version for the events to return */ + protected $max_version; + + /** @var string */ + protected $filter; + + /** @var string */ + protected $current_event; + + /** @var array */ + protected $events; + + /** + * @param string $phpbb_root_path + * @param mixed $extension String 'vendor/ext' to filter, null for phpBB core + * @param string $min_version + * @param string $max_version + */ + public function __construct($phpbb_root_path, $extension = null, $min_version = null, $max_version = null) + { + $this->root_path = $phpbb_root_path; + $this->path = $this->root_path; + if ($extension) + { + $this->path .= 'ext/' . $extension . '/'; + } + + $this->events = array(); + $this->events_by_file = array(); + $this->filter = $this->current_event = ''; + $this->min_version = $min_version; + $this->max_version = $max_version; + } + + /** + * Get the list of all events + * + * @return array Array with events: name => details + */ + public function get_events() + { + return $this->events; + } + + /** + * @param string $md_file Relative from phpBB root + * @return int Number of events found + * @throws \LogicException + */ + public function crawl_phpbb_directory_adm($md_file) + { + $this->crawl_eventsmd($md_file, 'adm'); + + $file_list = $this->get_recursive_file_list($this->path . 'adm/style/'); + foreach ($file_list as $file) + { + $file_name = 'adm/style/' . $file; + $this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name)); + } + + return count($this->events); + } + + /** + * @param string $md_file Relative from phpBB root + * @return int Number of events found + * @throws \LogicException + */ + public function crawl_phpbb_directory_styles($md_file) + { + $this->crawl_eventsmd($md_file, 'styles'); + + $styles = array('prosilver'); + foreach ($styles as $style) + { + $file_list = $this->get_recursive_file_list( + $this->path . 'styles/' . $style . '/template/' + ); + + foreach ($file_list as $file) + { + $file_name = 'styles/' . $style . '/template/' . $file; + $this->validate_events_from_file($file_name, $this->crawl_file_for_events($file_name)); + } + } + + return count($this->events); + } + + /** + * @param string $md_file Relative from phpBB root + * @param string $filter Should be 'styles' or 'adm' + * @return int Number of events found + * @throws \LogicException + */ + public function crawl_eventsmd($md_file, $filter) + { + if (!file_exists($this->path . $md_file)) + { + throw new \LogicException("The event docs file '{$md_file}' could not be found"); + } + + $file_content = file_get_contents($this->path . $md_file); + $this->filter = $filter; + + $events = explode("\n\n", $file_content); + foreach ($events as $event) + { + // Last row of the file + if (strpos($event, "\n===\n") === false) + { + continue; + } + + list($event_name, $details) = explode("\n===\n", $event, 2); + $this->validate_event_name($event_name); + $sorted_events = [$this->current_event, $event_name]; + natsort($sorted_events); + $this->current_event = $event_name; + + if (isset($this->events[$this->current_event])) + { + throw new \LogicException("The event '{$this->current_event}' is defined multiple times"); + } + + // Use array_values() to get actual first element and check against natural order + if (array_values($sorted_events)[0] === $event_name) + { + throw new \LogicException("The event '{$sorted_events[1]}' should be defined before '{$sorted_events[0]}'"); + } + + if (($this->filter == 'adm' && strpos($this->current_event, 'acp_') !== 0) + || ($this->filter == 'styles' && strpos($this->current_event, 'acp_') === 0)) + { + continue; + } + + list($file_details, $details) = explode("\n* Since: ", $details, 2); + + $changed_versions = array(); + if (strpos($details, "\n* Changed: ") !== false) + { + list($since, $details) = explode("\n* Changed: ", $details, 2); + while (strpos($details, "\n* Changed: ") !== false) + { + list($changed, $details) = explode("\n* Changed: ", $details, 2); + $changed_versions[] = $changed; + } + list($changed, $description) = explode("\n* Purpose: ", $details, 2); + $changed_versions[] = $changed; + } + else + { + list($since, $description) = explode("\n* Purpose: ", $details, 2); + $changed_versions = array(); + } + + $files = $this->validate_file_list($file_details); + $since = $this->validate_since($since); + $changes = array(); + foreach ($changed_versions as $changed) + { + list($changed_version, $changed_description) = $this->validate_changed($changed); + + if (isset($changes[$changed_version])) + { + throw new \LogicException("Duplicate change information found for event '{$this->current_event}'"); + } + + $changes[$changed_version] = $changed_description; + } + $description = trim($description, "\n") . "\n"; + + if (!$this->version_is_filtered($since)) + { + $is_filtered = false; + foreach ($changes as $version => $null) + { + if ($this->version_is_filtered($version)) + { + $is_filtered = true; + break; + } + } + + if (!$is_filtered) + { + continue; + } + } + + $this->events[$event_name] = array( + 'event' => $this->current_event, + 'files' => $files, + 'since' => $since, + 'changed' => $changes, + 'description' => $description, + ); + } + + return count($this->events); + } + + /** + * The version to check + * + * @param string $version + * @return bool + */ + protected function version_is_filtered($version) + { + return (!$this->min_version || phpbb_version_compare($this->min_version, $version, '<=')) + && (!$this->max_version || phpbb_version_compare($this->max_version, $version, '>=')); + } + + /** + * Format the php events as a wiki table + * + * @param string $action + * @return string Number of events found + */ + public function export_events_for_wiki($action = '') + { + if ($this->filter === 'adm') + { + if ($action === 'diff') + { + $wiki_page = '=== ACP Template Events ===' . "\n"; + } + else + { + $wiki_page = '= ACP Template Events =' . "\n"; + } + $wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n"; + $wiki_page .= '! Identifier !! Placement !! Added in Release !! Explanation' . "\n"; + } + else + { + if ($action === 'diff') + { + $wiki_page = '=== Template Events ===' . "\n"; + } + else + { + $wiki_page = '= Template Events =' . "\n"; + } + $wiki_page .= '{| class="zebra sortable" cellspacing="0" cellpadding="5"' . "\n"; + $wiki_page .= '! Identifier !! Prosilver Placement (If applicable) !! Added in Release !! Explanation' . "\n"; + } + + foreach ($this->events as $event_name => $event) + { + $wiki_page .= "|- id=\"{$event_name}\"\n"; + $wiki_page .= "| [[#{$event_name}|{$event_name}]] || "; + + if ($this->filter === 'adm') + { + $wiki_page .= implode(', ', $event['files']['adm']); + } + else + { + $wiki_page .= implode(', ', $event['files']['prosilver']); + } + + $wiki_page .= " || {$event['since']} || " . str_replace("\n", ' ', $event['description']) . "\n"; + } + $wiki_page .= '|}' . "\n"; + + return $wiki_page; + } + + /** + * Validates a template event name + * + * @param $event_name + * @return null + * @throws \LogicException + */ + public function validate_event_name($event_name) + { + if (!preg_match('#^([a-z][a-z0-9]*(?:_[a-z][a-z0-9]*)+)$#', $event_name)) + { + throw new \LogicException("Invalid event name '{$event_name}'"); + } + } + + /** + * Validate "Since" Information + * + * @param string $since + * @return string + * @throws \LogicException + */ + public function validate_since($since) + { + if (!$this->validate_version($since)) + { + throw new \LogicException("Invalid since information found for event '{$this->current_event}'"); + } + + return $since; + } + + /** + * Validate "Changed" Information + * + * @param string $changed + * @return string + * @throws \LogicException + */ + public function validate_changed($changed) + { + if (strpos($changed, ' ') !== false) + { + list($version, $description) = explode(' ', $changed, 2); + } + else + { + $version = $changed; + $description = ''; + } + + if (!$this->validate_version($version)) + { + throw new \LogicException("Invalid changed information found for event '{$this->current_event}'"); + } + + return array($version, $description); + } + + /** + * Validate "version" Information + * + * @param string $version + * @return bool True if valid, false otherwise + */ + public function validate_version($version) + { + return preg_match('#^\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?$#', $version); + } + + /** + * Validate the files list + * + * @param string $file_details + * @return array + * @throws \LogicException + */ + public function validate_file_list($file_details) + { + $files_list = array( + 'prosilver' => array(), + 'adm' => array(), + ); + + // Multi file list + if (strpos($file_details, "* Locations:\n + ") === 0) + { + $file_details = substr($file_details, strlen("* Locations:\n + ")); + $files = explode("\n + ", $file_details); + foreach ($files as $file) + { + if (!file_exists($this->path . $file) || substr($file, -5) !== '.html') + { + throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1); + } + + if (($this->filter !== 'adm') && strpos($file, 'styles/prosilver/template/') === 0) + { + $files_list['prosilver'][] = substr($file, strlen('styles/prosilver/template/')); + } + else if (($this->filter === 'adm') && strpos($file, 'adm/style/') === 0) + { + $files_list['adm'][] = substr($file, strlen('adm/style/')); + } + else + { + throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 2); + } + + $this->events_by_file[$file][] = $this->current_event; + } + } + else if ($this->filter == 'adm') + { + $file = substr($file_details, strlen('* Location: ')); + if (!file_exists($this->path . $file) || substr($file, -5) !== '.html') + { + throw new \LogicException("Invalid file '{$file}' not found for event '{$this->current_event}'", 1); + } + + $files_list['adm'][] = substr($file, strlen('adm/style/')); + + $this->events_by_file[$file][] = $this->current_event; + } + else + { + throw new \LogicException("Invalid file list found for event '{$this->current_event}'", 2); + } + + return $files_list; + } + + /** + * Get all template events in a template file + * + * @param string $file + * @return array + * @throws \LogicException + */ + public function crawl_file_for_events($file) + { + if (!file_exists($this->path . $file)) + { + throw new \LogicException("File '{$file}' does not exist", 1); + } + + $event_list = array(); + $file_content = file_get_contents($this->path . $file); + + preg_match_all('/(?:{%|)/U', $file_content, $event_list); + + return $event_list[1]; + } + + /** + * Validates whether all events from $file are in the md file and vice-versa + * + * @param string $file + * @param array $events + * @return true + * @throws \LogicException + */ + public function validate_events_from_file($file, array $events) + { + if (empty($this->events_by_file[$file]) && empty($events)) + { + return true; + } + else if (empty($this->events_by_file[$file])) + { + $event_list = implode("', '", $events); + throw new \LogicException("File '{$file}' should not contain events, but contains: " + . "'{$event_list}'", 1); + } + else if (empty($events)) + { + $event_list = implode("', '", $this->events_by_file[$file]); + throw new \LogicException("File '{$file}' contains no events, but should contain: " + . "'{$event_list}'", 1); + } + + $missing_events_from_file = array(); + foreach ($this->events_by_file[$file] as $event) + { + if (!in_array($event, $events)) + { + $missing_events_from_file[] = $event; + } + } + + if (!empty($missing_events_from_file)) + { + $event_list = implode("', '", $missing_events_from_file); + throw new \LogicException("File '{$file}' does not contain events: '{$event_list}'", 2); + } + + $missing_events_from_md = array(); + foreach ($events as $event) + { + if (!in_array($event, $this->events_by_file[$file])) + { + $missing_events_from_md[] = $event; + } + } + + if (!empty($missing_events_from_md)) + { + $event_list = implode("', '", $missing_events_from_md); + throw new \LogicException("File '{$file}' contains additional events: '{$event_list}'", 3); + } + + return true; + } + + /** + * Returns a list of files in $dir + * + * Works recursive with any depth + * + * @param string $dir Directory to go through + * @return array List of files (including directories) + */ + public function get_recursive_file_list($dir) + { + try + { + $iterator = new \RecursiveIteratorIterator( + new \phpbb\recursive_dot_prefix_filter_iterator( + new \RecursiveDirectoryIterator( + $dir, + \FilesystemIterator::SKIP_DOTS + ) + ), + \RecursiveIteratorIterator::SELF_FIRST + ); + } + catch (\Exception $e) + { + return array(); + } + + $files = array(); + foreach ($iterator as $file_info) + { + /** @var \RecursiveDirectoryIterator $file_info */ + if ($file_info->isDir()) + { + continue; + } + + $relative_path = $iterator->getInnerIterator()->getSubPathname(); + + if (substr($relative_path, -5) == '.html') + { + $files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path); + } + } + + return $files; + } +} diff --git a/phpbb/event/php_exporter.php b/phpbb/event/php_exporter.php new file mode 100644 index 0000000..71c94a6 --- /dev/null +++ b/phpbb/event/php_exporter.php @@ -0,0 +1,741 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\event; + +/** +* Class php_exporter +* Crawls through a list of files and grabs all php-events +*/ +class php_exporter +{ + /** @var string Path where we look for files*/ + protected $path; + + /** @var string phpBB Root Path */ + protected $root_path; + + /** @var string The minimum version for the events to return */ + protected $min_version; + + /** @var string The maximum version for the events to return */ + protected $max_version; + + /** @var string */ + protected $current_file; + + /** @var string */ + protected $current_event; + + /** @var int */ + protected $current_event_line; + + /** @var array */ + protected $events; + + /** @var array */ + protected $file_lines; + + /** + * @param string $phpbb_root_path + * @param mixed $extension String 'vendor/ext' to filter, null for phpBB core + * @param string $min_version + * @param string $max_version + */ + public function __construct($phpbb_root_path, $extension = null, $min_version = null, $max_version = null) + { + $this->root_path = $phpbb_root_path; + $this->path = $phpbb_root_path; + $this->events = $this->file_lines = array(); + $this->current_file = $this->current_event = ''; + $this->current_event_line = 0; + $this->min_version = $min_version; + $this->max_version = $max_version; + + $this->path = $this->root_path; + if ($extension) + { + $this->path .= 'ext/' . $extension . '/'; + } + } + + /** + * Get the list of all events + * + * @return array Array with events: name => details + */ + public function get_events() + { + return $this->events; + } + + /** + * Set current event data + * + * @param string $name Name of the current event (used for error messages) + * @param int $line Line where the current event is placed in + * @return null + */ + public function set_current_event($name, $line) + { + $this->current_event = $name; + $this->current_event_line = $line; + } + + /** + * Set the content of this file + * + * @param array $content Array with the lines of the file + * @return null + */ + public function set_content($content) + { + $this->file_lines = $content; + } + + /** + * Crawl the phpBB/ directory for php events + * @return int The number of events found + */ + public function crawl_phpbb_directory_php() + { + $files = $this->get_recursive_file_list(); + $this->events = array(); + foreach ($files as $file) + { + $this->crawl_php_file($file); + } + ksort($this->events); + + return count($this->events); + } + + /** + * Returns a list of files in $dir + * + * @return array List of files (including the path) + */ + public function get_recursive_file_list() + { + try + { + $iterator = new \RecursiveIteratorIterator( + new \phpbb\event\recursive_event_filter_iterator( + new \RecursiveDirectoryIterator( + $this->path, + \FilesystemIterator::SKIP_DOTS + ), + $this->path + ), + \RecursiveIteratorIterator::LEAVES_ONLY + ); + } + catch (\Exception $e) + { + return array(); + } + + $files = array(); + foreach ($iterator as $file_info) + { + /** @var \RecursiveDirectoryIterator $file_info */ + $relative_path = $iterator->getInnerIterator()->getSubPathname(); + $files[] = str_replace(DIRECTORY_SEPARATOR, '/', $relative_path); + } + + return $files; + } + + /** + * Format the php events as a wiki table + * + * @param string $action + * @return string + */ + public function export_events_for_wiki($action = '') + { + if ($action === 'diff') + { + $wiki_page = '=== PHP Events (Hook Locations) ===' . "\n"; + } + else + { + $wiki_page = '= PHP Events (Hook Locations) =' . "\n"; + } + $wiki_page .= '{| class="sortable zebra" cellspacing="0" cellpadding="5"' . "\n"; + $wiki_page .= '! Identifier !! Placement !! Arguments !! Added in Release !! Explanation' . "\n"; + foreach ($this->events as $event) + { + $wiki_page .= '|- id="' . $event['event'] . '"' . "\n"; + $wiki_page .= '| [[#' . $event['event'] . '|' . $event['event'] . ']] || ' . $event['file'] . ' || ' . implode(', ', $event['arguments']) . ' || ' . $event['since'] . ' || ' . $event['description'] . "\n"; + } + $wiki_page .= '|}' . "\n"; + + return $wiki_page; + } + + /** + * @param string $file + * @return int Number of events found in this file + * @throws \LogicException + */ + public function crawl_php_file($file) + { + $this->current_file = $file; + $this->file_lines = array(); + $content = file_get_contents($this->path . $this->current_file); + $num_events_found = 0; + + if (strpos($content, 'dispatcher->trigger_event(') || strpos($content, 'dispatcher->dispatch(')) + { + $this->set_content(explode("\n", $content)); + for ($i = 0, $num_lines = count($this->file_lines); $i < $num_lines; $i++) + { + $event_line = false; + $found_trigger_event = strpos($this->file_lines[$i], 'dispatcher->trigger_event('); + $arguments = array(); + if ($found_trigger_event !== false) + { + $event_line = $i; + $this->set_current_event($this->get_event_name($event_line, false), $event_line); + + // Find variables of the event + $arguments = $this->get_vars_from_array(); + $doc_vars = $this->get_vars_from_docblock(); + $this->validate_vars_docblock_array($arguments, $doc_vars); + } + else + { + $found_dispatch = strpos($this->file_lines[$i], 'dispatcher->dispatch('); + if ($found_dispatch !== false) + { + $event_line = $i; + $this->set_current_event($this->get_event_name($event_line, true), $event_line); + } + } + + if ($event_line) + { + // Validate @event + $event_line_num = $this->find_event(); + $this->validate_event($this->current_event, $this->file_lines[$event_line_num]); + + // Validate @since + $since_line_num = $this->find_since(); + $since = $this->validate_since($this->file_lines[$since_line_num]); + + $changed_line_nums = $this->find_changed('changed'); + if (empty($changed_line_nums)) + { + $changed_line_nums = $this->find_changed('change'); + } + $changed_versions = array(); + if (!empty($changed_line_nums)) + { + foreach ($changed_line_nums as $changed_line_num) + { + $changed_versions[] = $this->validate_changed($this->file_lines[$changed_line_num]); + } + } + + if (!$this->version_is_filtered($since)) + { + $valid_version = false; + foreach ($changed_versions as $changed) + { + $valid_version = $valid_version || $this->version_is_filtered($changed); + } + + if (!$valid_version) + { + continue; + } + } + + // Find event description line + $description_line_num = $this->find_description(); + $description_lines = array(); + + while (true) + { + $description_line = substr(trim($this->file_lines[$description_line_num]), strlen('*')); + $description_line = trim(str_replace("\t", " ", $description_line)); + + // Reached end of description if line is a tag + if (strlen($description_line) && $description_line[0] == '@') + { + break; + } + + $description_lines[] = $description_line; + $description_line_num++; + } + + // If there is an empty line between description and first tag, remove it + if (!strlen(end($description_lines))) + { + array_pop($description_lines); + } + + $description = trim(implode('
', $description_lines)); + + if (isset($this->events[$this->current_event])) + { + throw new \LogicException("The event '{$this->current_event}' from file " + . "'{$this->current_file}:{$event_line_num}' already exists in file " + . "'{$this->events[$this->current_event]['file']}'", 10); + } + + sort($arguments); + $this->events[$this->current_event] = array( + 'event' => $this->current_event, + 'file' => $this->current_file, + 'arguments' => $arguments, + 'since' => $since, + 'description' => $description, + ); + $num_events_found++; + } + } + } + + return $num_events_found; + } + + /** + * The version to check + * + * @param string $version + * @return bool + */ + protected function version_is_filtered($version) + { + return (!$this->min_version || phpbb_version_compare($this->min_version, $version, '<=')) + && (!$this->max_version || phpbb_version_compare($this->max_version, $version, '>=')); + } + + /** + * Find the name of the event inside the dispatch() line + * + * @param int $event_line + * @param bool $is_dispatch Do we look for dispatch() or trigger_event() ? + * @return string Name of the event + * @throws \LogicException + */ + public function get_event_name($event_line, $is_dispatch) + { + $event_text_line = $this->file_lines[$event_line]; + $event_text_line = ltrim($event_text_line, "\t "); + + if ($is_dispatch) + { + $regex = '#\$[a-z](?:[a-z0-9_]|->)*'; + $regex .= '->dispatch\((\[)?'; + $regex .= '\'' . $this->preg_match_event_name() . '(?(1)\', \'(?2))+\''; + $regex .= '(?(1)\])\);#'; + } + else + { + $regex = '#extract\(\$[a-z](?:[a-z0-9_]|->)*'; + $regex .= '->trigger_event\((\[)?'; + $regex .= '\'' . $this->preg_match_event_name() . '(?(1)\', \'(?2))+\''; + $regex .= '(?(1)\]), compact\(\$vars\)\)\);#'; + } + + $match = array(); + preg_match($regex, $event_text_line, $match); + if (!isset($match[2])) + { + throw new \LogicException("Can not find event name in line '{$event_text_line}' " + . "in file '{$this->current_file}:{$event_line}'", 1); + } + + return $match[2]; + } + + /** + * Returns a regex match for the event name + * + * @return string + */ + protected function preg_match_event_name() + { + return '([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_]*)+)'; + } + + /** + * Find the $vars array + * + * @return array List of variables + * @throws \LogicException + */ + public function get_vars_from_array() + { + $line = ltrim($this->file_lines[$this->current_event_line - 1], "\t"); + if ($line === ');' || $line === '];') + { + $vars_array = $this->get_vars_from_multi_line_array(); + } + else + { + $vars_array = $this->get_vars_from_single_line_array($line); + } + + foreach ($vars_array as $var) + { + if (!preg_match('#^[a-z_][a-z0-9_]*$#i', $var)) + { + throw new \LogicException("Found invalid var '{$var}' in array for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); + } + } + + sort($vars_array); + return $vars_array; + } + + /** + * Find the variables in single line array + * + * @param string $line + * @param bool $throw_multiline Throw an exception when there are too + * many arguments in one line. + * @return array List of variables + * @throws \LogicException + */ + public function get_vars_from_single_line_array($line, $throw_multiline = true) + { + $match = array(); + preg_match('#^\$vars = (?:(\[)|array\()\'([a-z0-9_\' ,]+)\'(?(1)\]|\));$#i', $line, $match); + + if (isset($match[2])) + { + $vars_array = explode("', '", $match[2]); + if ($throw_multiline && count($vars_array) > 6) + { + throw new \LogicException('Should use multiple lines for $vars definition ' + . "for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); + } + return $vars_array; + } + else + { + throw new \LogicException("Can not find '\$vars = array();'-line for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); + } + } + + /** + * Find the variables in single line array + * + * @return array List of variables + * @throws \LogicException + */ + public function get_vars_from_multi_line_array() + { + $current_vars_line = 2; + $var_lines = array(); + while (!in_array(ltrim($this->file_lines[$this->current_event_line - $current_vars_line], "\t"), ['$vars = array(', '$vars = ['])) + { + $var_lines[] = substr(trim($this->file_lines[$this->current_event_line - $current_vars_line]), 0, -1); + + $current_vars_line++; + if ($current_vars_line > $this->current_event_line) + { + // Reached the start of the file + throw new \LogicException("Can not find end of \$vars array for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); + } + } + + return $this->get_vars_from_single_line_array('$vars = array(' . implode(", ", $var_lines) . ');', false); + } + + /** + * Find the $vars array + * + * @return array List of variables + * @throws \LogicException + */ + public function get_vars_from_docblock() + { + $doc_vars = array(); + $current_doc_line = 1; + $found_comment_end = false; + while (ltrim($this->file_lines[$this->current_event_line - $current_doc_line], "\t") !== '/**') + { + if (ltrim($this->file_lines[$this->current_event_line - $current_doc_line], "\t ") === '*/') + { + $found_comment_end = true; + } + + if ($found_comment_end) + { + $var_line = trim($this->file_lines[$this->current_event_line - $current_doc_line]); + $var_line = preg_replace('!\s+!', ' ', $var_line); + if (strpos($var_line, '* @var ') === 0) + { + $doc_line = explode(' ', $var_line, 5); + if (count($doc_line) !== 5) + { + throw new \LogicException("Found invalid line '{$this->file_lines[$this->current_event_line - $current_doc_line]}' " + . "for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); + } + $doc_vars[] = $doc_line[3]; + } + } + + $current_doc_line++; + if ($current_doc_line > $this->current_event_line) + { + // Reached the start of the file + throw new \LogicException("Can not find end of docblock for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); + } + } + + if (empty($doc_vars)) + { + // Reached the start of the file + throw new \LogicException("Can not find @var lines for event '{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); + } + + foreach ($doc_vars as $var) + { + if (!preg_match('#^[a-z_][a-z0-9_]*$#i', $var)) + { + throw new \LogicException("Found invalid @var '{$var}' in docblock for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 4); + } + } + + sort($doc_vars); + return $doc_vars; + } + + /** + * Find the "@since" Information line + * + * @return int Absolute line number + * @throws \LogicException + */ + public function find_since() + { + return $this->find_tag('since', array('event', 'var')); + } + + /** + * Find the "@changed" Information lines + * + * @param string $tag_name Should be 'change', not 'changed' + * @return array Absolute line numbers + * @throws \LogicException + */ + public function find_changed($tag_name) + { + $lines = array(); + $last_line = 0; + try + { + while ($line = $this->find_tag($tag_name, array('since'), $last_line)) + { + $lines[] = $line; + $last_line = $line; + } + } + catch (\LogicException $e) + { + // Not changed? No problem! + } + + return $lines; + } + + /** + * Find the "@event" Information line + * + * @return int Absolute line number + */ + public function find_event() + { + return $this->find_tag('event', array()); + } + + /** + * Find a "@*" Information line + * + * @param string $find_tag Name of the tag we are trying to find + * @param array $disallowed_tags List of tags that must not appear between + * the tag and the actual event + * @param int $skip_to_line Skip lines until this one + * @return int Absolute line number + * @throws \LogicException + */ + public function find_tag($find_tag, $disallowed_tags, $skip_to_line = 0) + { + $find_tag_line = $skip_to_line ? $this->current_event_line - $skip_to_line + 1 : 0; + $found_comment_end = ($skip_to_line) ? true : false; + while (strpos(ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t "), '* @' . $find_tag . ' ') !== 0) + { + if ($found_comment_end && ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t") === '/**') + { + // Reached the start of this doc block + throw new \LogicException("Can not find '@{$find_tag}' information for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); + } + + foreach ($disallowed_tags as $disallowed_tag) + { + if ($found_comment_end && strpos(ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t "), '* @' . $disallowed_tag) === 0) + { + // Found @var after the @since + throw new \LogicException("Found '@{$disallowed_tag}' information after '@{$find_tag}' for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 3); + } + } + + if (ltrim($this->file_lines[$this->current_event_line - $find_tag_line], "\t ") === '*/') + { + $found_comment_end = true; + } + + $find_tag_line++; + if ($find_tag_line >= $this->current_event_line) + { + // Reached the start of the file + throw new \LogicException("Can not find '@{$find_tag}' information for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); + } + } + + return $this->current_event_line - $find_tag_line; + } + + /** + * Find a "@*" Information line + * + * @return int Absolute line number + * @throws \LogicException + */ + public function find_description() + { + $find_desc_line = 0; + while (ltrim($this->file_lines[$this->current_event_line - $find_desc_line], "\t") !== '/**') + { + $find_desc_line++; + if ($find_desc_line > $this->current_event_line) + { + // Reached the start of the file + throw new \LogicException("Can not find a description for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); + } + } + + $find_desc_line = $this->current_event_line - $find_desc_line + 1; + + $desc = trim($this->file_lines[$find_desc_line]); + if (strpos($desc, '* @') === 0 || $desc[0] !== '*' || substr($desc, 1) == '') + { + // First line of the doc block is a @-line, empty or only contains "*" + throw new \LogicException("Can not find a description for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); + } + + return $find_desc_line; + } + + /** + * Validate "@since" Information + * + * @param string $line + * @return string + * @throws \LogicException + */ + public function validate_since($line) + { + $match = array(); + preg_match('#^\* @since (\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?)$#', ltrim($line, "\t "), $match); + if (!isset($match[1])) + { + throw new \LogicException("Invalid '@since' information for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'"); + } + + return $match[1]; + } + + /** + * Validate "@changed" Information + * + * @param string $line + * @return string + * @throws \LogicException + */ + public function validate_changed($line) + { + $match = array(); + $line = str_replace("\t", ' ', ltrim($line, "\t ")); + preg_match('#^\* @changed (\d+\.\d+\.\d+(?:-(?:a|b|RC|pl)\d+)?)( (?:.*))?$#', $line, $match); + if (!isset($match[2])) + { + throw new \LogicException("Invalid '@changed' information for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'"); + } + + return $match[2]; + } + + /** + * Validate "@event" Information + * + * @param string $event_name + * @param string $line + * @return string + * @throws \LogicException + */ + public function validate_event($event_name, $line) + { + $event = substr(ltrim($line, "\t "), strlen('* @event ')); + + if ($event !== trim($event)) + { + throw new \LogicException("Invalid '@event' information for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 1); + } + + if ($event !== $event_name) + { + throw new \LogicException("Event name does not match '@event' tag for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'", 2); + } + + return $event; + } + + /** + * Validates that two arrays contain the same strings + * + * @param array $vars_array Variables found in the array line + * @param array $vars_docblock Variables found in the doc block + * @return null + * @throws \LogicException + */ + public function validate_vars_docblock_array($vars_array, $vars_docblock) + { + $vars_array = array_unique($vars_array); + $vars_docblock = array_unique($vars_docblock); + $sizeof_vars_array = count($vars_array); + + if ($sizeof_vars_array !== count($vars_docblock) || $sizeof_vars_array !== count(array_intersect($vars_array, $vars_docblock))) + { + throw new \LogicException("\$vars array does not match the list of '@var' tags for event " + . "'{$this->current_event}' in file '{$this->current_file}:{$this->current_event_line}'"); + } + } +} diff --git a/phpbb/event/recursive_event_filter_iterator.php b/phpbb/event/recursive_event_filter_iterator.php new file mode 100644 index 0000000..64e2e56 --- /dev/null +++ b/phpbb/event/recursive_event_filter_iterator.php @@ -0,0 +1,71 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\event; + +/** +* This filter ignores directories and files starting with a dot. +* It also skips some directories that do not contain events anyway, +* such as e.g. files/, store/ and vendor/ +*/ +class recursive_event_filter_iterator extends \RecursiveFilterIterator +{ + protected $root_path; + + /** + * Construct + * + * @param \RecursiveIterator $iterator + * @param string $root_path + */ + public function __construct(\RecursiveIterator $iterator, $root_path) + { + $this->root_path = str_replace(DIRECTORY_SEPARATOR, '/', $root_path); + parent::__construct($iterator); + } + + /** + * Return the inner iterator's children contained in a recursive_event_filter_iterator + * + * @return recursive_event_filter_iterator + */ + public function getChildren() + { + return new self($this->getInnerIterator()->getChildren(), $this->root_path); + } + + /** + * {@inheritDoc} + */ + public function accept() + { + $relative_path = str_replace(DIRECTORY_SEPARATOR, '/', $this->current()); + $filename = $this->current()->getFilename(); + + return (substr($relative_path, -4) === '.php' || $this->current()->isDir()) + && $filename[0] !== '.' + && strpos($relative_path, $this->root_path . 'cache/') !== 0 + && strpos($relative_path, $this->root_path . 'develop/') !== 0 + && strpos($relative_path, $this->root_path . 'docs/') !== 0 + && strpos($relative_path, $this->root_path . 'ext/') !== 0 + && strpos($relative_path, $this->root_path . 'files/') !== 0 + && strpos($relative_path, $this->root_path . 'includes/utf/') !== 0 + && strpos($relative_path, $this->root_path . 'language/') !== 0 + && strpos($relative_path, $this->root_path . 'phpbb/db/migration/data/') !== 0 + && strpos($relative_path, $this->root_path . 'phpbb/event/') !== 0 + && strpos($relative_path, $this->root_path . 'store/') !== 0 + && strpos($relative_path, $this->root_path . 'tests/') !== 0 + && strpos($relative_path, $this->root_path . 'vendor/') !== 0 + ; + } +} diff --git a/phpbb/exception/exception_interface.php b/phpbb/exception/exception_interface.php new file mode 100644 index 0000000..e8526a3 --- /dev/null +++ b/phpbb/exception/exception_interface.php @@ -0,0 +1,29 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\exception; + +/** + * Interface exception_interface + * + * Define an exception which support a language var as message. + */ +interface exception_interface +{ + /** + * Return the arguments associated with the message if it's a language var. + * + * @return array + */ + public function get_parameters(); +} diff --git a/phpbb/exception/http_exception.php b/phpbb/exception/http_exception.php new file mode 100644 index 0000000..0e6ffe4 --- /dev/null +++ b/phpbb/exception/http_exception.php @@ -0,0 +1,70 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\exception; + +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + +/** + * Class http_exception + */ +class http_exception extends runtime_exception implements HttpExceptionInterface +{ + /** + * Http status code. + * + * @var integer + */ + private $status_code; + + /** + * Additional headers to set in the response. + * + * @var array + */ + private $headers; + + /** + * Constructor + * + * @param integer $status_code The http status code. + * @param string $message The Exception message to throw (must be a language variable). + * @param array $parameters The parameters to use with the language var. + * @param \Exception $previous The previous exception used for the exception chaining. + * @param array $headers Additional headers to set in the response. + * @param integer $code The Exception code. + */ + public function __construct($status_code, $message = "", array $parameters = array(), \Exception $previous = null, array $headers = array(), $code = 0) + { + $this->status_code = $status_code; + $this->headers = $headers; + + parent::__construct($message, $parameters, $previous, $code); + } + + /** + * {@inheritdoc} + */ + public function getStatusCode() + { + return $this->status_code; + } + + /** + * {@inheritdoc} + */ + public function getHeaders() + { + return $this->headers; + } +} diff --git a/phpbb/exception/runtime_exception.php b/phpbb/exception/runtime_exception.php new file mode 100644 index 0000000..6568bbf --- /dev/null +++ b/phpbb/exception/runtime_exception.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\exception; + +/** + * Class runtime_exception + * + * Define an exception which support a language var as message. + */ +class runtime_exception extends \RuntimeException implements exception_interface +{ + /** + * Parameters to use with the language var. + * + * @var array + */ + private $parameters; + + /** + * Constructor + * + * @param string $message The Exception message to throw (must be a language variable). + * @param array $parameters The parameters to use with the language var. + * @param \Exception $previous The previous runtime_exception used for the runtime_exception chaining. + * @param integer $code The Exception code. + */ + public function __construct($message = "", array $parameters = array(), \Exception $previous = null, $code = 0) + { + $this->parameters = $parameters; + + parent::__construct($message, $code, $previous); + } + + /** + * {@inheritdoc} + */ + public function get_parameters() + { + return $this->parameters; + } +} diff --git a/phpbb/exception/version_check_exception.php b/phpbb/exception/version_check_exception.php new file mode 100644 index 0000000..0810263 --- /dev/null +++ b/phpbb/exception/version_check_exception.php @@ -0,0 +1,21 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\exception; + +/** + * Define an exception related to the version checker. + */ +class version_check_exception extends runtime_exception +{ +} diff --git a/phpbb/extension/base.php b/phpbb/extension/base.php new file mode 100644 index 0000000..c7778cf --- /dev/null +++ b/phpbb/extension/base.php @@ -0,0 +1,142 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\extension; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** +* A base class for extensions without custom enable/disable/purge code. +*/ +class base implements \phpbb\extension\extension_interface +{ + /** @var ContainerInterface */ + protected $container; + + /** @var \phpbb\finder */ + protected $extension_finder; + + /** @var \phpbb\db\migrator */ + protected $migrator; + + /** @var string */ + protected $extension_name; + + /** @var string */ + protected $extension_path; + + /** @var string[] */ + private $migrations = false; + + /** + * Constructor + * + * @param ContainerInterface $container Container object + * @param \phpbb\finder $extension_finder + * @param \phpbb\db\migrator $migrator + * @param string $extension_name Name of this extension (from ext.manager) + * @param string $extension_path Relative path to this extension + */ + public function __construct(ContainerInterface $container, \phpbb\finder $extension_finder, \phpbb\db\migrator $migrator, $extension_name, $extension_path) + { + $this->container = $container; + $this->extension_finder = $extension_finder; + $this->migrator = $migrator; + + $this->extension_name = $extension_name; + $this->extension_path = $extension_path; + } + + /** + * {@inheritdoc} + */ + public function is_enableable() + { + return true; + } + + /** + * Single enable step that installs any included migrations + * + * @param mixed $old_state State returned by previous call of this method + * @return false Indicates no further steps are required + */ + public function enable_step($old_state) + { + $this->get_migration_file_list(); + + $this->migrator->update(); + + return !$this->migrator->finished(); + } + + /** + * Single disable step that does nothing + * + * @param mixed $old_state State returned by previous call of this method + * @return false Indicates no further steps are required + */ + public function disable_step($old_state) + { + return false; + } + + /** + * Single purge step that reverts any included and installed migrations + * + * @param mixed $old_state State returned by previous call of this method + * @return false Indicates no further steps are required + */ + public function purge_step($old_state) + { + $migrations = $this->get_migration_file_list(); + + foreach ($migrations as $migration) + { + while ($this->migrator->migration_state($migration) !== false) + { + $this->migrator->revert($migration); + + return true; + } + } + + return false; + } + + /** + * Get the list of migration files from this extension + * + * @return array + */ + protected function get_migration_file_list() + { + if ($this->migrations !== false) + { + return $this->migrations; + } + + // Only have the finder search in this extension path directory + $migrations = $this->extension_finder + ->extension_directory('/migrations') + ->find_from_extension($this->extension_name, $this->extension_path); + + $migrations = $this->extension_finder->get_classes_from_files($migrations); + + $this->migrator->set_migrations($migrations); + + $migrations = $this->migrator->get_migrations(); + + return $migrations; + } +} diff --git a/phpbb/extension/di/extension_base.php b/phpbb/extension/di/extension_base.php new file mode 100644 index 0000000..ba74615 --- /dev/null +++ b/phpbb/extension/di/extension_base.php @@ -0,0 +1,138 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\extension\di; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** + * Container core extension + */ +class extension_base extends Extension +{ + /** + * Name of the extension (vendor/name) + * + * @var string + */ + protected $extension_name; + + /** + * Path to the extension. + * + * @var string + */ + protected $ext_path; + + /** + * Constructor + * + * @param string $extension_name Name of the extension (vendor/name) + * @param string $ext_path Path to the extension + */ + public function __construct($extension_name, $ext_path) + { + $this->extension_name = $extension_name; + $this->ext_path = $ext_path; + } + + /** + * Loads a specific configuration. + * + * @param array $configs An array of configuration values + * @param ContainerBuilder $container A ContainerBuilder instance + * + * @throws \InvalidArgumentException When provided tag is not defined in this extension + */ + public function load(array $configs, ContainerBuilder $container) + { + $this->load_services($container); + } + + /** + * Loads the services.yml file. + * + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function load_services(ContainerBuilder $container) + { + $services_directory = false; + $services_file = false; + + if (file_exists($this->ext_path . 'config/' . $container->getParameter('core.environment') . '/container/environment.yml')) + { + $services_directory = $this->ext_path . 'config/' . $container->getParameter('core.environment') . '/container/'; + $services_file = 'environment.yml'; + } + else if (!is_dir($this->ext_path . 'config/' . $container->getParameter('core.environment'))) + { + if (file_exists($this->ext_path . 'config/default/container/environment.yml')) + { + $services_directory = $this->ext_path . 'config/default/container/'; + $services_file = 'environment.yml'; + } + else if (!is_dir($this->ext_path . 'config/default') && file_exists($this->ext_path . '/config/services.yml')) + { + $services_directory = $this->ext_path . 'config'; + $services_file = 'services.yml'; + } + } + + if ($services_directory && $services_file) + { + $filesystem = new \phpbb\filesystem\filesystem(); + $loader = new YamlFileLoader($container, new FileLocator($filesystem->realpath($services_directory))); + $loader->load($services_file); + } + } + + /** + * {@inheritdoc} + */ + public function getConfiguration(array $config, ContainerBuilder $container) + { + $reflected = new \ReflectionClass($this); + $namespace = $reflected->getNamespaceName(); + + $class = $namespace . '\\di\configuration'; + if (class_exists($class)) + { + $r = new \ReflectionClass($class); + $container->addResource(new FileResource($r->getFileName())); + + if (!method_exists($class, '__construct')) + { + $configuration = new $class(); + + return $configuration; + } + } + + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias() + { + return str_replace('/', '_', $this->extension_name); + } +} diff --git a/phpbb/extension/exception.php b/phpbb/extension/exception.php new file mode 100644 index 0000000..9050449 --- /dev/null +++ b/phpbb/extension/exception.php @@ -0,0 +1,21 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\extension; + +/** + * Exception class for metadata + */ +class exception extends \phpbb\exception\runtime_exception +{ +} diff --git a/phpbb/extension/extension_interface.php b/phpbb/extension/extension_interface.php new file mode 100644 index 0000000..6a6b6ad --- /dev/null +++ b/phpbb/extension/extension_interface.php @@ -0,0 +1,70 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\extension; + +/** +* The interface extension meta classes have to implement to run custom code +* on enable/disable/purge. +*/ +interface extension_interface +{ + /** + * Indicate whether or not the extension can be enabled. + * + * @return bool + */ + public function is_enableable(); + + /** + * enable_step is executed on enabling an extension until it returns false. + * + * Calls to this function can be made in subsequent requests, when the + * function is invoked through a webserver with a too low max_execution_time. + * + * @param mixed $old_state The return value of the previous call + * of this method, or false on the first call + * @return mixed Returns false after last step, otherwise + * temporary state which is passed as an + * argument to the next step + */ + public function enable_step($old_state); + + /** + * Disables the extension. + * + * Calls to this function can be made in subsequent requests, when the + * function is invoked through a webserver with a too low max_execution_time. + * + * @param mixed $old_state The return value of the previous call + * of this method, or false on the first call + * @return mixed Returns false after last step, otherwise + * temporary state which is passed as an + * argument to the next step + */ + public function disable_step($old_state); + + /** + * purge_step is executed on purging an extension until it returns false. + * + * Calls to this function can be made in subsequent requests, when the + * function is invoked through a webserver with a too low max_execution_time. + * + * @param mixed $old_state The return value of the previous call + * of this method, or false on the first call + * @return mixed Returns false after last step, otherwise + * temporary state which is passed as an + * argument to the next step + */ + public function purge_step($old_state); +} diff --git a/phpbb/extension/manager.php b/phpbb/extension/manager.php new file mode 100644 index 0000000..4b4109b --- /dev/null +++ b/phpbb/extension/manager.php @@ -0,0 +1,633 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\extension; + +use phpbb\exception\runtime_exception; +use phpbb\file_downloader; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** +* The extension manager provides means to activate/deactivate extensions. +*/ +class manager +{ + /** @var ContainerInterface */ + protected $container; + + protected $db; + protected $config; + protected $cache; + protected $php_ext; + protected $extensions; + protected $extension_table; + protected $phpbb_root_path; + protected $cache_name; + + /** + * Creates a manager and loads information from database + * + * @param ContainerInterface $container A container + * @param \phpbb\db\driver\driver_interface $db A database connection + * @param \phpbb\config\config $config Config object + * @param \phpbb\filesystem\filesystem_interface $filesystem + * @param string $extension_table The name of the table holding extensions + * @param string $phpbb_root_path Path to the phpbb includes directory. + * @param string $php_ext php file extension, defaults to php + * @param \phpbb\cache\service $cache A cache instance or null + * @param string $cache_name The name of the cache variable, defaults to _ext + */ + public function __construct(ContainerInterface $container, \phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\filesystem\filesystem_interface $filesystem, $extension_table, $phpbb_root_path, $php_ext = 'php', \phpbb\cache\service $cache = null, $cache_name = '_ext') + { + $this->cache = $cache; + $this->cache_name = $cache_name; + $this->config = $config; + $this->container = $container; + $this->db = $db; + $this->extension_table = $extension_table; + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->extensions = ($this->cache) ? $this->cache->get($this->cache_name) : false; + + if ($this->extensions === false) + { + $this->load_extensions(); + } + } + + /** + * Loads all extension information from the database + * + * @return null + */ + public function load_extensions() + { + $this->extensions = array(); + + // Do not try to load any extensions if the extension table + // does not exist or when installing or updating. + // Note: database updater invokes this code, and in 3.0 + // there is no extension table therefore the rest of this function + // fails + if (defined('IN_INSTALL') || version_compare($this->config['version'], '3.1.0-dev', '<')) + { + return; + } + + $sql = 'SELECT * + FROM ' . $this->extension_table; + + $result = $this->db->sql_query($sql); + $extensions = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + foreach ($extensions as $extension) + { + $extension['ext_path'] = $this->get_extension_path($extension['ext_name']); + $this->extensions[$extension['ext_name']] = $extension; + } + + ksort($this->extensions); + + if ($this->cache) + { + $this->cache->put($this->cache_name, $this->extensions); + } + } + + /** + * Generates the path to an extension + * + * @param string $name The name of the extension + * @param bool $phpbb_relative Whether the path should be relative to phpbb root + * @return string Path to an extension + */ + public function get_extension_path($name, $phpbb_relative = false) + { + $name = str_replace('.', '', $name); + + return (($phpbb_relative) ? $this->phpbb_root_path : '') . 'ext/' . $name . '/'; + } + + /** + * Instantiates the extension meta class for the extension with the given name + * + * @param string $name The extension name + * @return \phpbb\extension\extension_interface Instance of the extension meta class or + * \phpbb\extension\base if the class does not exist + */ + public function get_extension($name) + { + $extension_class_name = str_replace('/', '\\', $name) . '\\ext'; + + $migrator = $this->container->get('migrator'); + + if (class_exists($extension_class_name)) + { + return new $extension_class_name($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true)); + } + else + { + return new \phpbb\extension\base($this->container, $this->get_finder(), $migrator, $name, $this->get_extension_path($name, true)); + } + } + + /** + * Instantiates the metadata manager for the extension with the given name + * + * @param string $name The extension name + * @return \phpbb\extension\metadata_manager Instance of the metadata manager + */ + public function create_extension_metadata_manager($name) + { + if (!isset($this->extensions[$name]['metadata'])) + { + $metadata = new \phpbb\extension\metadata_manager($name, $this->get_extension_path($name, true)); + $this->extensions[$name]['metadata'] = $metadata; + } + return $this->extensions[$name]['metadata']; + } + + /** + * Runs a step of the extension enabling process. + * + * Allows the exentension to enable in a long running script that works + * in multiple steps across requests. State is kept for the extension + * in the extensions table. + * + * @param string $name The extension's name + * @return bool False if enabling is finished, true otherwise + */ + public function enable_step($name) + { + // ignore extensions that are already enabled + if ($this->is_enabled($name)) + { + return false; + } + + $old_state = (isset($this->extensions[$name]['ext_state'])) ? unserialize($this->extensions[$name]['ext_state']) : false; + + $extension = $this->get_extension($name); + + if (!$extension->is_enableable()) + { + return false; + } + + $state = $extension->enable_step($old_state); + + $active = ($state === false); + + $extension_data = array( + 'ext_name' => $name, + 'ext_active' => $active, + 'ext_state' => serialize($state), + ); + + $this->extensions[$name] = $extension_data; + $this->extensions[$name]['ext_path'] = $this->get_extension_path($extension_data['ext_name']); + ksort($this->extensions); + + $sql = 'SELECT COUNT(ext_name) as row_count + FROM ' . $this->extension_table . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $result = $this->db->sql_query($sql); + $count = $this->db->sql_fetchfield('row_count'); + $this->db->sql_freeresult($result); + + if ($count) + { + $sql = 'UPDATE ' . $this->extension_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + } + else + { + $sql = 'INSERT INTO ' . $this->extension_table . ' + ' . $this->db->sql_build_array('INSERT', $extension_data); + $this->db->sql_query($sql); + } + + if ($this->cache) + { + $this->cache->purge(); + } + + if ($active) + { + $this->config->increment('assets_version', 1); + } + + return !$active; + } + + /** + * Enables an extension + * + * This method completely enables an extension. But it could be long running + * so never call this in a script that has a max_execution time. + * + * @param string $name The extension's name + * @return null + */ + public function enable($name) + { + // @codingStandardsIgnoreStart + while ($this->enable_step($name)); + // @codingStandardsIgnoreEnd + } + + /** + * Disables an extension + * + * Calls the disable method on the extension's meta class to allow it to + * process the event. + * + * @param string $name The extension's name + * @return bool False if disabling is finished, true otherwise + */ + public function disable_step($name) + { + // ignore extensions that are not enabled + if (!$this->is_enabled($name)) + { + return false; + } + + $old_state = unserialize($this->extensions[$name]['ext_state']); + + $extension = $this->get_extension($name); + $state = $extension->disable_step($old_state); + + // continue until the state is false + if ($state !== false) + { + $extension_data = array( + 'ext_state' => serialize($state), + ); + $this->extensions[$name]['ext_state'] = serialize($state); + + $sql = 'UPDATE ' . $this->extension_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + if ($this->cache) + { + $this->cache->purge(); + } + + return true; + } + + $extension_data = array( + 'ext_active' => false, + 'ext_state' => serialize(false), + ); + $this->extensions[$name]['ext_active'] = false; + $this->extensions[$name]['ext_state'] = serialize(false); + + $sql = 'UPDATE ' . $this->extension_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + if ($this->cache) + { + $this->cache->purge(); + } + + return false; + } + + /** + * Disables an extension + * + * Disables an extension completely at once. This process could run for a + * while so never call this in a script that has a max_execution time. + * + * @param string $name The extension's name + * @return null + */ + public function disable($name) + { + // @codingStandardsIgnoreStart + while ($this->disable_step($name)); + // @codingStandardsIgnoreEnd + } + + /** + * Purge an extension + * + * Disables the extension first if active, and then calls purge on the + * extension's meta class to delete the extension's database content. + * + * @param string $name The extension's name + * @return bool False if purging is finished, true otherwise + */ + public function purge_step($name) + { + // ignore extensions that are not configured + if (!$this->is_configured($name)) + { + return false; + } + + // disable first if necessary + if ($this->extensions[$name]['ext_active']) + { + $this->disable($name); + } + + $old_state = unserialize($this->extensions[$name]['ext_state']); + + $extension = $this->get_extension($name); + $state = $extension->purge_step($old_state); + + // continue until the state is false + if ($state !== false) + { + $extension_data = array( + 'ext_state' => serialize($state), + ); + $this->extensions[$name]['ext_state'] = serialize($state); + + $sql = 'UPDATE ' . $this->extension_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $extension_data) . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + if ($this->cache) + { + $this->cache->purge(); + } + + return true; + } + + unset($this->extensions[$name]); + + $sql = 'DELETE FROM ' . $this->extension_table . " + WHERE ext_name = '" . $this->db->sql_escape($name) . "'"; + $this->db->sql_query($sql); + + if ($this->cache) + { + $this->cache->purge(); + } + + return false; + } + + /** + * Purge an extension + * + * Purges an extension completely at once. This process could run for a while + * so never call this in a script that has a max_execution time. + * + * @param string $name The extension's name + * @return null + */ + public function purge($name) + { + // @codingStandardsIgnoreStart + while ($this->purge_step($name)); + // @codingStandardsIgnoreEnd + } + + /** + * Retrieves a list of all available extensions on the filesystem + * + * @return array An array with extension names as keys and paths to the + * extension as values + */ + public function all_available() + { + $available = array(); + if (!is_dir($this->phpbb_root_path . 'ext/')) + { + return $available; + } + + $iterator = new \RecursiveIteratorIterator( + new \phpbb\recursive_dot_prefix_filter_iterator( + new \RecursiveDirectoryIterator($this->phpbb_root_path . 'ext/', \FilesystemIterator::NEW_CURRENT_AND_KEY | \FilesystemIterator::FOLLOW_SYMLINKS) + ), + \RecursiveIteratorIterator::SELF_FIRST + ); + $iterator->setMaxDepth(2); + + foreach ($iterator as $file_info) + { + if ($file_info->isFile() && $file_info->getFilename() == 'composer.json') + { + $ext_name = $iterator->getInnerIterator()->getSubPath(); + $ext_name = str_replace(DIRECTORY_SEPARATOR, '/', $ext_name); + if ($this->is_available($ext_name)) + { + $available[$ext_name] = $this->get_extension_path($ext_name, true); + } + } + } + ksort($available); + return $available; + } + + /** + * Retrieves all configured extensions. + * + * All enabled and disabled extensions are considered configured. A purged + * extension that is no longer in the database is not configured. + * + * @param bool $phpbb_relative Whether the path should be relative to phpbb root + * + * @return array An array with extension names as keys and and the + * database stored extension information as values + */ + public function all_configured($phpbb_relative = true) + { + $configured = array(); + foreach ($this->extensions as $name => $data) + { + if ($this->is_configured($name)) + { + unset($data['metadata']); + $data['ext_path'] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path']; + $configured[$name] = $data; + } + } + return $configured; + } + + /** + * Retrieves all enabled extensions. + * @param bool $phpbb_relative Whether the path should be relative to phpbb root + * + * @return array An array with extension names as keys and and the + * database stored extension information as values + */ + public function all_enabled($phpbb_relative = true) + { + $enabled = array(); + foreach ($this->extensions as $name => $data) + { + if ($this->is_enabled($name)) + { + $enabled[$name] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path']; + } + } + return $enabled; + } + + /** + * Retrieves all disabled extensions. + * + * @param bool $phpbb_relative Whether the path should be relative to phpbb root + * + * @return array An array with extension names as keys and and the + * database stored extension information as values + */ + public function all_disabled($phpbb_relative = true) + { + $disabled = array(); + foreach ($this->extensions as $name => $data) + { + if ($this->is_disabled($name)) + { + $disabled[$name] = ($phpbb_relative ? $this->phpbb_root_path : '') . $data['ext_path']; + } + } + return $disabled; + } + + /** + * Check to see if a given extension is available on the filesystem + * + * @param string $name Extension name to check NOTE: Can be user input + * @return bool Depending on whether or not the extension is available + */ + public function is_available($name) + { + $md_manager = $this->create_extension_metadata_manager($name); + try + { + return $md_manager->get_metadata('all') && $md_manager->validate_enable(); + } + catch (\phpbb\extension\exception $e) + { + return false; + } + } + + /** + * Check to see if a given extension is enabled + * + * @param string $name Extension name to check + * @return bool Depending on whether or not the extension is enabled + */ + public function is_enabled($name) + { + return isset($this->extensions[$name]['ext_active']) && $this->extensions[$name]['ext_active']; + } + + /** + * Check to see if a given extension is disabled + * + * @param string $name Extension name to check + * @return bool Depending on whether or not the extension is disabled + */ + public function is_disabled($name) + { + return isset($this->extensions[$name]['ext_active']) && !$this->extensions[$name]['ext_active']; + } + + /** + * Check to see if a given extension is configured + * + * All enabled and disabled extensions are considered configured. A purged + * extension that is no longer in the database is not configured. + * + * @param string $name Extension name to check + * @return bool Depending on whether or not the extension is configured + */ + public function is_configured($name) + { + return isset($this->extensions[$name]['ext_active']); + } + + /** + * Check the version and return the available updates (for an extension). + * + * @param \phpbb\extension\metadata_manager $md_manager The metadata manager for the version to check. + * @param bool $force_update Ignores cached data. Defaults to false. + * @param bool $force_cache Force the use of the cache. Override $force_update. + * @param string $stability Force the stability (null by default). + * @return array + * @throws runtime_exception + */ + public function version_check(\phpbb\extension\metadata_manager $md_manager, $force_update = false, $force_cache = false, $stability = null) + { + $meta = $md_manager->get_metadata('all'); + + if (!isset($meta['extra']['version-check'])) + { + throw new runtime_exception('NO_VERSIONCHECK'); + } + + $version_check = $meta['extra']['version-check']; + + $version_helper = new \phpbb\version_helper($this->cache, $this->config, new file_downloader()); + $version_helper->set_current_version($meta['version']); + $version_helper->set_file_location($version_check['host'], $version_check['directory'], $version_check['filename'], isset($version_check['ssl']) ? $version_check['ssl'] : false); + $version_helper->force_stability($stability); + + return $version_helper->get_ext_update_on_branch($force_update, $force_cache); + } + + /** + * Check to see if a given extension is purged + * + * An extension is purged if it is available, not enabled and not disabled. + * + * @param string $name Extension name to check + * @return bool Depending on whether or not the extension is purged + */ + public function is_purged($name) + { + return $this->is_available($name) && !$this->is_configured($name); + } + + /** + * Instantiates a \phpbb\finder. + * + * @param bool $use_all_available Should we load all extensions, or just enabled ones + * @return \phpbb\finder An extension finder instance + */ + public function get_finder($use_all_available = false) + { + $finder = new \phpbb\finder($this->filesystem, $this->phpbb_root_path, $this->cache, $this->php_ext, $this->cache_name . '_finder'); + if ($use_all_available) + { + $finder->set_extensions(array_keys($this->all_available())); + } + else + { + $finder->set_extensions(array_keys($this->all_enabled())); + } + return $finder; + } +} diff --git a/phpbb/extension/metadata_manager.php b/phpbb/extension/metadata_manager.php new file mode 100644 index 0000000..60b8db8 --- /dev/null +++ b/phpbb/extension/metadata_manager.php @@ -0,0 +1,260 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\extension; + +/** +* The extension metadata manager validates and gets meta-data for extensions +*/ +class metadata_manager +{ + /** + * Name (including vendor) of the extension + * @var string + */ + protected $ext_name; + + /** + * Metadata from the composer.json file + * @var array + */ + protected $metadata; + + /** + * Link (including root path) to the metadata file + * @var string + */ + protected $metadata_file; + + /** + * Creates the metadata manager + * + * @param string $ext_name Name (including vendor) of the extension + * @param string $ext_path Path to the extension directory including root path + */ + public function __construct($ext_name, $ext_path) + { + $this->ext_name = $ext_name; + $this->metadata = array(); + $this->metadata_file = $ext_path . 'composer.json'; + } + + /** + * Processes and gets the metadata requested + * + * @param string $element All for all metadata that it has and is valid, otherwise specify which section you want by its shorthand term. + * @return array Contains all of the requested metadata, throws an exception on failure + */ + public function get_metadata($element = 'all') + { + // Fetch and clean the metadata if not done yet + if ($this->metadata === array()) + { + $this->fetch_metadata_from_file(); + } + + switch ($element) + { + case 'all': + default: + $this->validate(); + return $this->metadata; + break; + + case 'version': + case 'name': + $this->validate($element); + return $this->metadata[$element]; + break; + + case 'display-name': + return (isset($this->metadata['extra']['display-name'])) ? $this->metadata['extra']['display-name'] : $this->get_metadata('name'); + break; + } + } + + /** + * Gets the metadata file contents and cleans loaded file + * + * @throws \phpbb\extension\exception + */ + private function fetch_metadata_from_file() + { + if (!file_exists($this->metadata_file)) + { + throw new \phpbb\extension\exception('FILE_NOT_FOUND', array($this->metadata_file)); + } + + if (!($file_contents = file_get_contents($this->metadata_file))) + { + throw new \phpbb\extension\exception('FILE_CONTENT_ERR', array($this->metadata_file)); + } + + if (($metadata = json_decode($file_contents, true)) === null) + { + throw new \phpbb\extension\exception('FILE_JSON_DECODE_ERR', array($this->metadata_file)); + } + + array_walk_recursive($metadata, array($this, 'sanitize_json')); + $this->metadata = $metadata; + } + + /** + * Sanitize input from JSON array using htmlspecialchars() + * + * @param mixed $value Value of array row + * @param string $key Key of array row + */ + public function sanitize_json(&$value, $key) + { + $value = htmlspecialchars($value); + } + + /** + * Validate fields + * + * @param string $name ("all" for display and enable validation + * "display" for name, type, and authors + * "name", "type") + * @return Bool True if valid, throws an exception if invalid + * @throws \phpbb\extension\exception + */ + public function validate($name = 'display') + { + // Basic fields + $fields = array( + 'name' => '#^[a-zA-Z0-9_\x7f-\xff]{2,}/[a-zA-Z0-9_\x7f-\xff]{2,}$#', + 'type' => '#^phpbb-extension$#', + 'license' => '#.+#', + 'version' => '#.+#', + ); + + switch ($name) + { + case 'all': + $this->validate_enable(); + // no break + + case 'display': + foreach ($fields as $field => $data) + { + $this->validate($field); + } + + $this->validate_authors(); + break; + + default: + if (isset($fields[$name])) + { + if (!isset($this->metadata[$name])) + { + throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array($name)); + } + + if (!preg_match($fields[$name], $this->metadata[$name])) + { + throw new \phpbb\extension\exception('META_FIELD_INVALID', array($name)); + } + } + break; + } + + return true; + } + + /** + * Validates the contents of the authors field + * + * @return boolean True when passes validation, throws exception if invalid + * @throws \phpbb\extension\exception + */ + public function validate_authors() + { + if (empty($this->metadata['authors'])) + { + throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('authors')); + } + + foreach ($this->metadata['authors'] as $author) + { + if (!isset($author['name'])) + { + throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('author name')); + } + } + + return true; + } + + /** + * This array handles the verification that this extension can be enabled on this board + * + * @return bool True if validation succeeded, throws an exception if invalid + * @throws \phpbb\extension\exception + */ + public function validate_enable() + { + // Check for valid directory & phpBB, PHP versions + return $this->validate_dir() && $this->validate_require_phpbb() && $this->validate_require_php(); + } + + /** + * Validates the most basic directory structure to ensure it follows / convention. + * + * @return boolean True when passes validation, throws an exception if invalid + * @throws \phpbb\extension\exception + */ + public function validate_dir() + { + if (substr_count($this->ext_name, '/') !== 1 || $this->ext_name != $this->get_metadata('name')) + { + throw new \phpbb\extension\exception('EXTENSION_DIR_INVALID'); + } + + return true; + } + + + /** + * Validates the contents of the phpbb requirement field + * + * @return boolean True when passes validation, throws an exception if invalid + * @throws \phpbb\extension\exception + */ + public function validate_require_phpbb() + { + if (!isset($this->metadata['extra']['soft-require']['phpbb/phpbb'])) + { + throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('soft-require')); + } + + return true; + } + + /** + * Validates the contents of the php requirement field + * + * @return boolean True when passes validation, throws an exception if invalid + * @throws \phpbb\extension\exception + */ + public function validate_require_php() + { + if (!isset($this->metadata['require']['php'])) + { + throw new \phpbb\extension\exception('META_FIELD_NOT_SET', array('require php')); + } + + return true; + } +} diff --git a/phpbb/extension/provider.php b/phpbb/extension/provider.php new file mode 100644 index 0000000..1c42cf7 --- /dev/null +++ b/phpbb/extension/provider.php @@ -0,0 +1,72 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\extension; + +/** +* Provides a set of items found in extensions. +* +* This abstract class is essentially a wrapper around item-specific +* finding logic. It handles storing the extension manager via constructor +* for the finding logic to use to find the items, and provides an +* iterator interface over the items found by the finding logic. +* +* Items could be anything, for example template paths or cron task names. +* Derived classes completely define what the items are. +*/ +abstract class provider implements \IteratorAggregate +{ + /** + * Array holding all found items + * @var array|null + */ + protected $items = null; + + /** + * An extension manager to search for items in extensions + * @var \phpbb\extension\manager + */ + protected $extension_manager; + + /** + * Constructor. Loads all available items. + * + * @param \phpbb\extension\manager $extension_manager phpBB extension manager + */ + public function __construct(\phpbb\extension\manager $extension_manager) + { + $this->extension_manager = $extension_manager; + } + + /** + * Finds items using the extension manager. + * + * @return array List of task names + */ + abstract protected function find(); + + /** + * Retrieve an iterator over all items + * + * @return \ArrayIterator An iterator for the array of template paths + */ + public function getIterator() + { + if ($this->items === null) + { + $this->items = $this->find(); + } + + return new \ArrayIterator($this->items); + } +} diff --git a/phpbb/feed/attachments_base.php b/phpbb/feed/attachments_base.php new file mode 100644 index 0000000..5d3272e --- /dev/null +++ b/phpbb/feed/attachments_base.php @@ -0,0 +1,97 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\feed; + +/** +* Abstract class for feeds displaying attachments +*/ +abstract class attachments_base extends base +{ + /** + * Attachments that may be displayed + */ + protected $attachments = array(); + + /** + * Retrieve the list of attachments that may be displayed + * + * @param array $post_ids Specify for which post IDs to fetch the attachments (optional) + * @param array $topic_ids Specify for which topic IDs to fetch the attachments (optional) + */ + protected function fetch_attachments($post_ids = array(), $topic_ids = array()) + { + $sql_array = array( + 'SELECT' => 'a.*', + 'FROM' => array( + ATTACHMENTS_TABLE => 'a' + ), + 'WHERE' => 'a.in_message = 0 ', + 'ORDER_BY' => 'a.filetime DESC, a.post_msg_id ASC', + ); + + if (!empty($post_ids)) + { + $sql_array['WHERE'] .= 'AND ' . $this->db->sql_in_set('a.post_msg_id', $post_ids); + } + else if (!empty($topic_ids)) + { + if (isset($this->topic_id)) + { + $topic_ids[] = $this->topic_id; + } + + $sql_array['WHERE'] .= 'AND ' . $this->db->sql_in_set('a.topic_id', $topic_ids); + } + else if (isset($this->topic_id)) + { + $sql_array['WHERE'] .= 'AND a.topic_id = ' . (int) $this->topic_id; + } + else if (isset($this->forum_id)) + { + $sql_array['LEFT_JOIN'] = array( + array( + 'FROM' => array(TOPICS_TABLE => 't'), + 'ON' => 'a.topic_id = t.topic_id', + ) + ); + $sql_array['WHERE'] .= 'AND t.forum_id = ' . (int) $this->forum_id; + } + else + { + // Do not allow querying the full attachments table + throw new \RuntimeException($this->user->lang('INVALID_FEED_ATTACHMENTS')); + } + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + + // Set attachments in feed items + while ($row = $this->db->sql_fetchrow($result)) + { + $this->attachments[$row['post_msg_id']][] = $row; + } + $this->db->sql_freeresult($result); + } + + /** + * Get attachments related to a given post + * + * @param $post_id int Post id + * @return mixed Attachments related to $post_id + */ + public function get_attachments($post_id) + { + return $this->attachments[$post_id]; + } +} diff --git a/phpbb/feed/base.php b/phpbb/feed/base.php new file mode 100644 index 0000000..d4be0dc --- /dev/null +++ b/phpbb/feed/base.php @@ -0,0 +1,337 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * Base class with some generic functions and settings. + */ +abstract class base implements feed_interface +{ + /** + * Feed helper object + * @var \phpbb\feed\helper + */ + protected $helper; + + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\cache\driver\driver_interface */ + protected $cache; + + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\auth\auth */ + protected $auth; + + /** @var \phpbb\content_visibility */ + protected $content_visibility; + + /** @var \phpbb\event\dispatcher_interface */ + protected $phpbb_dispatcher; + + /** @var string */ + protected $phpEx; + + /** + * SQL Query to be executed to get feed items + */ + protected $sql = array(); + + /** + * Keys specified for retrieval of title, content, etc. + */ + protected $keys = array(); + + /** + * Number of items to fetch. Usually overwritten by $config['feed_something'] + */ + protected $num_items = 15; + + /** + * Separator for title elements to separate items (for example forum / topic) + */ + protected $separator = "\xE2\x80\xA2"; // • + + /** + * Separator for the statistics row (Posted by, post date, replies, etc.) + */ + protected $separator_stats = "\xE2\x80\x94"; // — + + /** @var mixed Query result handle */ + protected $result; + + /** + * Constructor + * + * @param \phpbb\feed\helper $helper Feed helper + * @param \phpbb\config\config $config Config object + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param \phpbb\cache\driver\driver_interface $cache Cache object + * @param \phpbb\user $user User object + * @param \phpbb\auth\auth $auth Auth object + * @param \phpbb\content_visibility $content_visibility Auth object + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object + * @param string $phpEx php file extension + */ + public function __construct( + \phpbb\feed\helper $helper, + \phpbb\config\config $config, + \phpbb\db\driver\driver_interface $db, + \phpbb\cache\driver\driver_interface $cache, + \phpbb\user $user, + \phpbb\auth\auth $auth, + \phpbb\content_visibility $content_visibility, + \phpbb\event\dispatcher_interface $phpbb_dispatcher, + $phpEx + ) + { + $this->config = $config; + $this->helper = $helper; + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->auth = $auth; + $this->content_visibility = $content_visibility; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->phpEx = $phpEx; + + $this->set_keys(); + + // Allow num_items to be string + if (is_string($this->num_items)) + { + $this->num_items = (int) $this->config[$this->num_items]; + + // A precaution + if (!$this->num_items) + { + $this->num_items = 10; + } + } + } + + /** + * {@inheritdoc} + */ + public function set_keys() + { + } + + /** + * {@inheritdoc} + */ + public function open() + { + } + + /** + * {@inheritdoc} + */ + public function close() + { + if (!empty($this->result)) + { + $this->db->sql_freeresult($this->result); + } + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + $this->keys[$key] = $value; + } + + /** + * {@inheritdoc} + */ + public function get($key) + { + return (isset($this->keys[$key])) ? $this->keys[$key] : null; + } + + /** + * {@inheritdoc} + */ + public function get_item() + { + if (!isset($this->result)) + { + if (!$this->get_sql()) + { + return false; + } + + $sql_ary = $this->sql; + + /** + * Event to modify the feed item sql + * + * @event core.feed_base_modify_item_sql + * @var array sql_ary The SQL array to get the feed item data + * + * @since 3.1.10-RC1 + */ + $vars = array('sql_ary'); + extract($this->phpbb_dispatcher->trigger_event('core.feed_base_modify_item_sql', compact($vars))); + $this->sql = $sql_ary; + unset($sql_ary); + + // Query database + $sql = $this->db->sql_build_query('SELECT', $this->sql); + $this->result = $this->db->sql_query_limit($sql, $this->num_items); + } + + return $this->db->sql_fetchrow($this->result); + } + + /** + * Returns the ids of the forums readable by the current user. + * + * @return int[] + */ + protected function get_readable_forums() + { + static $forum_ids; + + if (!isset($forum_ids)) + { + $forum_ids = array_keys($this->auth->acl_getf('f_read', true)); + } + + return $forum_ids; + } + + /** + * Returns the ids of the forum for which the current user can approve the post in the moderation queue. + * + * @return int[] + */ + protected function get_moderator_approve_forums() + { + static $forum_ids; + + if (!isset($forum_ids)) + { + $forum_ids = array_keys($this->auth->acl_getf('m_approve', true)); + } + + return $forum_ids; + } + + /** + * Returns true if the current user can approve the post of the given forum + * + * @param int $forum_id Forum id to check + * @return bool + */ + protected function is_moderator_approve_forum($forum_id) + { + static $forum_ids; + + if (!isset($forum_ids)) + { + $forum_ids = array_flip($this->get_moderator_approve_forums()); + } + + return (isset($forum_ids[$forum_id])) ? true : false; + } + + /** + * Returns the ids of the forum excluded from the feeds + * + * @return int[] + */ + protected function get_excluded_forums() + { + static $forum_ids; + + // Matches acp/acp_board.php + $cache_name = 'feed_excluded_forum_ids'; + + if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_EXCLUDE, '<> 0'); + $result = $this->db->sql_query($sql); + + $forum_ids = array(); + while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) + { + $forum_ids[$forum_id] = $forum_id; + } + $this->db->sql_freeresult($result); + + $this->cache->put('_' . $cache_name, $forum_ids); + } + + return $forum_ids; + } + + /** + * Returns true if the given id is in the excluded forums list. + * + * @param int $forum_id Id to check + * @return bool + */ + protected function is_excluded_forum($forum_id) + { + $forum_ids = $this->get_excluded_forums(); + + return isset($forum_ids[$forum_id]) ? true : false; + } + + /** + * Returns all password protected forum ids the current user is currently NOT authenticated for. + * + * @return array Array of forum ids + */ + protected function get_passworded_forums() + { + return $this->user->get_passworded_forums(); + } + + /** + * Returns the link to the user profile. + * + * @return string + */ + protected function user_viewprofile($row) + { + $author_id = (int) $row[$this->get('author_id')]; + + if ($author_id == ANONYMOUS) + { + // Since we cannot link to a profile, we just return GUEST + // instead of $row['username'] + return $this->user->lang['GUEST']; + } + + return '' . $row[$this->get('creator')] . ''; + } + + /** + * Returns the SQL query used to retrieve the posts of the feed. + * + * @return string SQL SELECT query + */ + protected abstract function get_sql(); +} diff --git a/phpbb/feed/controller/feed.php b/phpbb/feed/controller/feed.php new file mode 100644 index 0000000..c0d7bc7 --- /dev/null +++ b/phpbb/feed/controller/feed.php @@ -0,0 +1,411 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\feed\controller; + +use phpbb\auth\auth; +use phpbb\config\config; +use phpbb\db\driver\driver_interface; +use \phpbb\event\dispatcher_interface; +use phpbb\exception\http_exception; +use phpbb\feed\feed_interface; +use phpbb\feed\exception\feed_unavailable_exception; +use phpbb\feed\exception\unauthorized_exception; +use phpbb\feed\helper as feed_helper; +use phpbb\controller\helper as controller_helper; +use phpbb\symfony_request; +use phpbb\user; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +class feed +{ + /** + * @var \Twig_Environment + */ + protected $template; + + /** + * @var symfony_request + */ + protected $request; + + /** + * @var controller_helper + */ + protected $controller_helper; + + /** + * @var config + */ + protected $config; + + /** + * @var driver_interface + */ + protected $db; + + /** + * @var ContainerInterface + */ + protected $container; + + /** + * @var feed_helper + */ + protected $feed_helper; + + /** + * @var user + */ + protected $user; + + /** + * @var auth + */ + protected $auth; + + /** + * @var dispatcher_interface + */ + protected $phpbb_dispatcher; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \Twig_Environment $twig + * @param symfony_request $request + * @param controller_helper $controller_helper + * @param config $config + * @param driver_interface $db + * @param ContainerInterface $container + * @param feed_helper $feed_helper + * @param user $user + * @param auth $auth + * @param dispatcher_interface $phpbb_dispatcher + * @param string $php_ext + */ + public function __construct(\Twig_Environment $twig, symfony_request $request, controller_helper $controller_helper, config $config, driver_interface $db, ContainerInterface $container, feed_helper $feed_helper, user $user, auth $auth, dispatcher_interface $phpbb_dispatcher, $php_ext) + { + $this->request = $request; + $this->controller_helper = $controller_helper; + $this->config = $config; + $this->db = $db; + $this->container = $container; + $this->feed_helper = $feed_helper; + $this->user = $user; + $this->auth = $auth; + $this->php_ext = $php_ext; + $this->template = $twig; + $this->phpbb_dispatcher = $phpbb_dispatcher; + } + + /** + * Controller for /feed/forums route + * + * @return Response + * + * @throws http_exception when the feed is disabled + */ + public function forums() + { + if (!$this->config['feed_overall_forums']) + { + $this->send_unavailable(); + } + + return $this->send_feed($this->container->get('feed.forums')); + } + + /** + * Controller for /feed/news route + * + * @return Response + * + * @throws http_exception when the feed is disabled + */ + public function news() + { + // Get at least one news forum + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); + $result = $this->db->sql_query_limit($sql, 1, 0, 600); + $s_feed_news = (int) $this->db->sql_fetchfield('forum_id'); + $this->db->sql_freeresult($result); + + if (!$s_feed_news) + { + $this->send_unavailable(); + } + + return $this->send_feed($this->container->get('feed.news')); + } + + /** + * Controller for /feed/topics route + * + * @return Response + * + * @throws http_exception when the feed is disabled + */ + public function topics() + { + if (!$this->config['feed_topics_new']) + { + $this->send_unavailable(); + } + + return $this->send_feed($this->container->get('feed.topics')); + } + + /** + * Controller for /feed/topics_new route + * + * @return Response + * + * @throws http_exception when the feed is disabled + */ + public function topics_new() + { + return $this->topics(); + } + + /** + * Controller for /feed/topics_active route + * + * @return Response + * + * @throws http_exception when the feed is disabled + */ + public function topics_active() + { + if (!$this->config['feed_topics_active']) + { + $this->send_unavailable(); + } + + return $this->send_feed($this->container->get('feed.topics_active')); + } + + /** + * Controller for /feed/forum/{forum_id} route + * + * @param int $forum_id + * + * @return Response + * + * @throws http_exception when the feed is disabled + */ + public function forum($forum_id) + { + if (!$this->config['feed_forum']) + { + $this->send_unavailable(); + } + + return $this->send_feed($this->container->get('feed.forum')->set_forum_id($forum_id)); + } + + /** + * Controller for /feed/topic/{topic_id} route + * + * @param int $topic_id + * + * @return Response + * + * @throws http_exception when the feed is disabled + */ + public function topic($topic_id) + { + if (!$this->config['feed_topic']) + { + $this->send_unavailable(); + } + + return $this->send_feed($this->container->get('feed.topic')->set_topic_id($topic_id)); + } + + /** + * Controller for /feed/{mode] route + * + * @return Response + * + * @throws http_exception when the feed is disabled + */ + public function overall() + { + if (!$this->config['feed_overall']) + { + $this->send_unavailable(); + } + + return $this->send_feed($this->container->get('feed.overall')); + } + + /** + * Display a given feed + * + * @param feed_interface $feed + * + * @return Response + */ + protected function send_feed(feed_interface $feed) + { + try + { + return $this->send_feed_do($feed); + } + catch (feed_unavailable_exception $e) + { + throw new http_exception(Response::HTTP_NOT_FOUND, $e->getMessage(), $e->get_parameters(), $e); + } + catch (unauthorized_exception $e) + { + throw new http_exception(Response::HTTP_FORBIDDEN, $e->getMessage(), $e->get_parameters(), $e); + } + } + + /** + * Really send the feed + * + * @param feed_interface $feed + * + * @return Response + * + * @throw exception\feed_exception + */ + protected function send_feed_do(feed_interface $feed) + { + $feed_updated_time = 0; + $item_vars = array(); + + $board_url = $this->feed_helper->get_board_url(); + + // Open Feed + $feed->open(); + + // Iterate through items + while ($row = $feed->get_item()) + { + /** + * Event to modify the feed row + * + * @event core.feed_modify_feed_row + * @var int forum_id Forum ID + * @var string mode Feeds mode (forums|topics|topics_new|topics_active|news) + * @var array row Array with feed data + * @var int topic_id Topic ID + * + * @since 3.1.10-RC1 + */ + $vars = array('forum_id', 'mode', 'row', 'topic_id'); + extract($this->phpbb_dispatcher->trigger_event('core.feed_modify_feed_row', compact($vars))); + + // BBCode options to correctly disable urls, smilies, bbcode... + if ($feed->get('options') === null) + { + // Allow all combinations + $options = 7; + + if ($feed->get('enable_bbcode') !== null && $feed->get('enable_smilies') !== null && $feed->get('enable_magic_url') !== null) + { + $options = (($row[$feed->get('enable_bbcode')]) ? OPTION_FLAG_BBCODE : 0) + (($row[$feed->get('enable_smilies')]) ? OPTION_FLAG_SMILIES : 0) + (($row[$feed->get('enable_magic_url')]) ? OPTION_FLAG_LINKS : 0); + } + } + else + { + $options = $row[$feed->get('options')]; + } + + $title = (isset($row[$feed->get('title')]) && $row[$feed->get('title')] !== '') ? $row[$feed->get('title')] : ((isset($row[$feed->get('title2')])) ? $row[$feed->get('title2')] : ''); + + $published = ($feed->get('published') !== null) ? (int) $row[$feed->get('published')] : 0; + $updated = ($feed->get('updated') !== null) ? (int) $row[$feed->get('updated')] : 0; + + $display_attachments = ($this->auth->acl_get('u_download') && $this->auth->acl_get('f_download', $row['forum_id']) && isset($row['post_attachment']) && $row['post_attachment']) ? true : false; + + $item_row = array( + 'author' => ($feed->get('creator') !== null) ? $row[$feed->get('creator')] : '', + 'published' => ($published > 0) ? $this->feed_helper->format_date($published) : '', + 'updated' => ($updated > 0) ? $this->feed_helper->format_date($updated) : '', + 'link' => '', + 'title' => censor_text($title), + 'category' => ($this->config['feed_item_statistics'] && !empty($row['forum_id'])) ? $board_url . '/viewforum.' . $this->php_ext . '?f=' . $row['forum_id'] : '', + 'category_name' => ($this->config['feed_item_statistics'] && isset($row['forum_name'])) ? $row['forum_name'] : '', + 'description' => censor_text($this->feed_helper->generate_content($row[$feed->get('text')], $row[$feed->get('bbcode_uid')], $row[$feed->get('bitfield')], $options, $row['forum_id'], ($display_attachments ? $feed->get_attachments($row['post_id']) : array()))), + 'statistics' => '', + ); + + // Adjust items, fill link, etc. + $feed->adjust_item($item_row, $row); + + $item_vars[] = $item_row; + + $feed_updated_time = max($feed_updated_time, $published, $updated); + } + + // If we do not have any items at all, sending the current time is better than sending no time. + if (!$feed_updated_time) + { + $feed_updated_time = time(); + } + + $feed->close(); + + $content = $this->template->render('feed.xml.twig', array( + // Some default assignments + // FEED_IMAGE is not used (atom) + 'FEED_IMAGE' => '', + 'SELF_LINK' => $this->controller_helper->route($this->request->attributes->get('_route'), $this->request->attributes->get('_route_params'), true, '', UrlGeneratorInterface::ABSOLUTE_URL), + 'FEED_LINK' => $board_url . '/index.' . $this->php_ext, + 'FEED_TITLE' => $this->config['sitename'], + 'FEED_SUBTITLE' => $this->config['site_desc'], + 'FEED_UPDATED' => $this->feed_helper->format_date($feed_updated_time), + 'FEED_LANG' => $this->user->lang['USER_LANG'], + 'FEED_AUTHOR' => $this->config['sitename'], + + // Feed entries + 'FEED_ROWS' => $item_vars, + )); + + $response = new Response($content); + $response->headers->set('Content-Type', 'application/atom+xml'); + $response->setCharset('UTF-8'); + $response->setLastModified(new \DateTime('@' . $feed_updated_time)); + + if (!empty($this->user->data['is_bot'])) + { + // Let reverse proxies know we detected a bot. + $response->headers->set('X-PHPBB-IS-BOT', 'yes'); + } + + return $response; + } + + /** + * Throw and exception saying that the feed isn't available + * + * @throw http_exception + */ + protected function send_unavailable() + { + throw new http_exception(404, 'FEATURE_NOT_AVAILABLE'); + } +} diff --git a/phpbb/feed/exception/feed_exception.php b/phpbb/feed/exception/feed_exception.php new file mode 100644 index 0000000..c9c8882 --- /dev/null +++ b/phpbb/feed/exception/feed_exception.php @@ -0,0 +1,21 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed\exception; + +use phpbb\exception\runtime_exception; + +abstract class feed_exception extends runtime_exception +{ + +} diff --git a/phpbb/feed/exception/feed_unavailable_exception.php b/phpbb/feed/exception/feed_unavailable_exception.php new file mode 100644 index 0000000..4b6605b --- /dev/null +++ b/phpbb/feed/exception/feed_unavailable_exception.php @@ -0,0 +1,19 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed\exception; + +abstract class feed_unavailable_exception extends feed_exception +{ + +} diff --git a/phpbb/feed/exception/no_feed_exception.php b/phpbb/feed/exception/no_feed_exception.php new file mode 100644 index 0000000..af6357b --- /dev/null +++ b/phpbb/feed/exception/no_feed_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed\exception; + +class no_feed_exception extends feed_unavailable_exception +{ + public function __construct(\Exception $previous = null, $code = 0) + { + parent::__construct('NO_FEED', array(), $previous, $code); + } +} diff --git a/phpbb/feed/exception/no_forum_exception.php b/phpbb/feed/exception/no_forum_exception.php new file mode 100644 index 0000000..a608329 --- /dev/null +++ b/phpbb/feed/exception/no_forum_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed\exception; + +class no_forum_exception extends feed_unavailable_exception +{ + public function __construct($forum_id, \Exception $previous = null, $code = 0) + { + parent::__construct('NO_FORUM', array($forum_id), $previous, $code); + } +} diff --git a/phpbb/feed/exception/no_topic_exception.php b/phpbb/feed/exception/no_topic_exception.php new file mode 100644 index 0000000..b961a65 --- /dev/null +++ b/phpbb/feed/exception/no_topic_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed\exception; + +class no_topic_exception extends feed_unavailable_exception +{ + public function __construct($topic_id, \Exception $previous = null, $code = 0) + { + parent::__construct('NO_TOPIC', array($topic_id), $previous, $code); + } +} diff --git a/phpbb/feed/exception/unauthorized_exception.php b/phpbb/feed/exception/unauthorized_exception.php new file mode 100644 index 0000000..7868975 --- /dev/null +++ b/phpbb/feed/exception/unauthorized_exception.php @@ -0,0 +1,19 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed\exception; + +abstract class unauthorized_exception extends feed_exception +{ + +} diff --git a/phpbb/feed/exception/unauthorized_forum_exception.php b/phpbb/feed/exception/unauthorized_forum_exception.php new file mode 100644 index 0000000..4384c7b --- /dev/null +++ b/phpbb/feed/exception/unauthorized_forum_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed\exception; + +class unauthorized_forum_exception extends unauthorized_exception +{ + public function __construct($forum_id, \Exception $previous = null, $code = 0) + { + parent::__construct('SORRY_AUTH_READ', array($forum_id), $previous, $code); + } +} diff --git a/phpbb/feed/exception/unauthorized_topic_exception.php b/phpbb/feed/exception/unauthorized_topic_exception.php new file mode 100644 index 0000000..f49f0a0 --- /dev/null +++ b/phpbb/feed/exception/unauthorized_topic_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed\exception; + +class unauthorized_topic_exception extends unauthorized_exception +{ + public function __construct($topic_id, \Exception $previous = null, $code = 0) + { + parent::__construct('SORRY_AUTH_READ_TOPIC', array($topic_id), $previous, $code); + } +} diff --git a/phpbb/feed/feed_interface.php b/phpbb/feed/feed_interface.php new file mode 100644 index 0000000..c185cd2 --- /dev/null +++ b/phpbb/feed/feed_interface.php @@ -0,0 +1,67 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * Interface implemented by all feeds types + */ +interface feed_interface +{ + /** + * Set keys. + */ + public function set_keys(); + + /** + * Open feed + */ + public function open(); + + /** + * Close feed + */ + public function close(); + + /** + * Set key + * + * @param string $key Key + * @param mixed $value Value + */ + public function set($key, $value); + + /** + * Get key + * + * @param string $key Key + * @return mixed + */ + public function get($key); + + /** + * Get the next post in the feed + * + * @return array + */ + public function get_item(); + + /** + * Adjust a feed entry + * + * @param $item_row + * @param $row + * @return array + */ + public function adjust_item(&$item_row, &$row); +} diff --git a/phpbb/feed/forum.php b/phpbb/feed/forum.php new file mode 100644 index 0000000..0c142e8 --- /dev/null +++ b/phpbb/feed/forum.php @@ -0,0 +1,178 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +use phpbb\feed\exception\no_feed_exception; +use phpbb\feed\exception\no_forum_exception; +use phpbb\feed\exception\unauthorized_forum_exception; + +/** + * Forum feed + * + * This will give you the last {$this->num_items} posts made + * within a specific forum. + */ +class forum extends post_base +{ + protected $forum_id = 0; + protected $forum_data = array(); + + /** + * Set the Forum ID + * + * @param int $forum_id Forum ID + * @return \phpbb\feed\forum + */ + public function set_forum_id($forum_id) + { + $this->forum_id = (int) $forum_id; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function open() + { + // Check if forum exists + $sql = 'SELECT forum_id, forum_name, forum_password, forum_type, forum_options + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $this->forum_id; + $result = $this->db->sql_query($sql); + $this->forum_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (empty($this->forum_data)) + { + throw new no_forum_exception($this->forum_id); + } + + // Forum needs to be postable + if ($this->forum_data['forum_type'] != FORUM_POST) + { + throw new no_feed_exception(); + } + + // Make sure forum is not excluded from feed + if (phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $this->forum_data['forum_options'])) + { + throw new no_feed_exception(); + } + + // Make sure we can read this forum + if (!$this->auth->acl_get('f_read', $this->forum_id)) + { + if ($this->user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + } + else + { + send_status_line(401, 'Unauthorized'); + } + throw new unauthorized_forum_exception($this->forum_id); + } + + // Make sure forum is not passworded or user is authed + if ($this->forum_data['forum_password']) + { + $forum_ids_passworded = $this->get_passworded_forums(); + + if (isset($forum_ids_passworded[$this->forum_id])) + { + if ($this->user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + } + else + { + send_status_line(401, 'Unauthorized'); + } + throw new unauthorized_forum_exception($this->forum_id); + } + + unset($forum_ids_passworded); + } + + parent::open(); + } + + /** + * {@inheritdoc} + */ + protected function get_sql() + { + // Determine topics with recent activity + $sql = 'SELECT topic_id, topic_last_post_time + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = ' . $this->forum_id . ' + AND topic_moved_id = 0 + AND ' . $this->content_visibility->get_visibility_sql('topic', $this->forum_id) . ' + ORDER BY topic_last_post_time DESC, topic_last_post_id DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $topic_ids = array(); + $min_post_time = 0; + while ($row = $this->db->sql_fetchrow()) + { + $topic_ids[] = (int) $row['topic_id']; + + $min_post_time = (int) $row['topic_last_post_time']; + } + $this->db->sql_freeresult($result); + + if (empty($topic_ids)) + { + return false; + } + + parent::fetch_attachments(array(), $topic_ids); + + $this->sql = array( + 'SELECT' => 'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, ' . + 'u.username, u.user_id', + 'FROM' => array( + POSTS_TABLE => 'p', + USERS_TABLE => 'u', + ), + 'WHERE' => $this->db->sql_in_set('p.topic_id', $topic_ids) . ' + AND ' . $this->content_visibility->get_visibility_sql('post', $this->forum_id, 'p.') . ' + AND p.post_time >= ' . $min_post_time . ' + AND p.poster_id = u.user_id', + 'ORDER_BY' => 'p.post_time DESC, p.post_id DESC', + ); + + return true; + } + + /** + * {@inheritdoc} + */ + public function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; + $item_row['forum_id'] = $this->forum_id; + } + + /** + * {@inheritdoc} + */ + public function get_item() + { + return ($row = parent::get_item()) ? array_merge($this->forum_data, $row) : $row; + } +} diff --git a/phpbb/feed/forums.php b/phpbb/feed/forums.php new file mode 100644 index 0000000..92f2b2d --- /dev/null +++ b/phpbb/feed/forums.php @@ -0,0 +1,77 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * 'All Forums' feed + * + * This will give you a list of all postable forums where feeds are enabled + * including forum description, topic stats and post stats + */ +class forums extends base +{ + protected $num_items = 0; + + /** + * {@inheritdoc} + */ + public function set_keys() + { + $this->set('title', 'forum_name'); + $this->set('text', 'forum_desc'); + $this->set('bitfield', 'forum_desc_bitfield'); + $this->set('bbcode_uid','forum_desc_uid'); + $this->set('updated', 'forum_last_post_time'); + $this->set('options', 'forum_desc_options'); + } + + /** + * {@inheritdoc} + */ + public function get_sql() + { + $in_fid_ary = array_diff($this->get_readable_forums(), $this->get_excluded_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + // Build SQL Query + $this->sql = array( + 'SELECT' => 'f.forum_id, f.left_id, f.forum_name, f.forum_last_post_time, + f.forum_desc, f.forum_desc_bitfield, f.forum_desc_uid, f.forum_desc_options, + f.forum_topics_approved, f.forum_posts_approved', + 'FROM' => array(FORUMS_TABLE => 'f'), + 'WHERE' => 'f.forum_type = ' . FORUM_POST . ' + AND ' . $this->db->sql_in_set('f.forum_id', $in_fid_ary), + 'ORDER_BY' => 'f.left_id ASC', + ); + + return true; + } + + /** + * {@inheritdoc} + */ + public function adjust_item(&$item_row, &$row) + { + $item_row['link'] = $this->helper->append_sid('viewforum.' . $this->phpEx, 'f=' . $row['forum_id']); + + if ($this->config['feed_item_statistics']) + { + $item_row['statistics'] = $this->user->lang('TOTAL_TOPICS', (int) $row['forum_topics_approved']) + . ' ' . $this->separator_stats . ' ' . $this->user->lang('TOTAL_POSTS_COUNT', (int) $row['forum_posts_approved']); + } + } +} diff --git a/phpbb/feed/helper.php b/phpbb/feed/helper.php new file mode 100644 index 0000000..7d50b7c --- /dev/null +++ b/phpbb/feed/helper.php @@ -0,0 +1,186 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +use phpbb\config\config; +use phpbb\path_helper; +use phpbb\textformatter\s9e\renderer; +use phpbb\user; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Class with some helpful functions used in feeds + */ +class helper +{ + /** @var config */ + protected $config; + + /** @var ContainerInterface */ + protected $container; + + /** @var path_helper */ + protected $path_helper; + + /** @var renderer */ + protected $renderer; + + /** @var user */ + protected $user; + + /** + * Constructor + * + * @param config $config Config object + * @param ContainerInterface $container Service container object + * @param path_helper $path_helper Path helper object + * @param renderer $renderer TextFormatter renderer object + * @param user $user User object + */ + public function __construct(config $config, ContainerInterface $container, path_helper $path_helper, renderer $renderer, user $user) + { + $this->config = $config; + $this->container = $container; + $this->path_helper = $path_helper; + $this->renderer = $renderer; + $this->user = $user; + } + + /** + * Returns the board url (and caches it in the function) + */ + public function get_board_url() + { + static $board_url; + + if (empty($board_url)) + { + $board_url = generate_board_url(); + } + + return $board_url; + } + + /** + * Run links through append_sid(), prepend generate_board_url() and remove session id + */ + public function append_sid($url, $params) + { + return append_sid($this->get_board_url() . '/' . $url, $params, true, ''); + } + + /** + * Generate ISO 8601 date string (RFC 3339) + */ + public function format_date($time) + { + static $zone_offset; + static $offset_string; + + if (empty($offset_string)) + { + $zone_offset = $this->user->create_datetime()->getOffset(); + $offset_string = phpbb_format_timezone_offset($zone_offset); + } + + return gmdate("Y-m-d\TH:i:s", $time + $zone_offset) . $offset_string; + } + + /** + * Generate text content + * + * @param string $content is feed text content + * @param string $uid is bbcode_uid + * @param string $bitfield is bbcode bitfield + * @param int $options bbcode flag options + * @param int $forum_id is the forum id + * @param array $post_attachments is an array containing the attachments and their respective info + * @return string the html content to be printed for the feed + */ + public function generate_content($content, $uid, $bitfield, $options, $forum_id, $post_attachments) + { + if (empty($content)) + { + return ''; + } + + // Setup our own quote_helper to remove all attributes from quotes + $this->renderer->configure_quote_helper($this->container->get('feed.quote_helper')); + + $this->renderer->set_smilies_path($this->get_board_url() . '/' . $this->config['smilies_path']); + + $content = generate_text_for_display($content, $uid, $bitfield, $options); + + // Remove "Select all" link and mouse events + $content = str_replace('' . $this->user->lang['SELECT_ALL_CODE'] . '', '', $content); + $content = preg_replace('#(onkeypress|onclick)="(.*?)"#si', '', $content); + + // Firefox does not support CSS for feeds, though + + // Remove font sizes + // $content = preg_replace('#([^>]+)#iU', '\1', $content); + + // Make text strong :P + // $content = preg_replace('#(.*?)#iU', '\1', $content); + + // Italic + // $content = preg_replace('#([^<]+)#iU', '\1', $content); + + // Underline + // $content = preg_replace('#([^<]+)#iU', '\1', $content); + + // Remove embed Windows Media Streams + $content = preg_replace( '#<\!--\[if \!IE\]>-->([^[]+)<\!--#si', '', $content); + + // Do not use < and >, because we want to retain code contained in [code][/code] + + // Remove embed and objects + $content = preg_replace( '#<(object|embed)(.*?) (value|src)=(.*?) ([^[]+)(object|embed)>#si',' $1 ',$content); + + // Remove some specials html tag, because somewhere there are a mod to allow html tags ;) + $content = preg_replace( '#<(script|iframe)([^[]+)\1>#siU', ' $1 ', $content); + + // Parse inline images to display with the feed + if (!empty($post_attachments)) + { + $update_count = array(); + parse_attachments($forum_id, $content, $post_attachments, $update_count); + $content .= implode('
', $post_attachments); + + // Convert attachments' relative path to absolute path + $content = str_replace($this->path_helper->get_web_root_path() . 'download/file.' . $this->path_helper->get_php_ext(), $this->get_board_url() . '/download/file.' . $this->path_helper->get_php_ext(), $content); + } + + // Remove Comments from inline attachments [ia] + $content = preg_replace('#
(.*?)
#','',$content); + + // Replace some entities with their unicode counterpart + $entities = array( + ' ' => "\xC2\xA0", + '•' => "\xE2\x80\xA2", + '·' => "\xC2\xB7", + '©' => "\xC2\xA9", + ); + + $content = str_replace(array_keys($entities), array_values($entities), $content); + + // Remove CDATA blocks. ;) + $content = preg_replace('#\<\!\[CDATA\[(.*?)\]\]\>#s', '', $content); + + // Other control characters + $content = preg_replace('#(?:[\x00-\x1F\x7F]+|(?:\xC2[\x80-\x9F])+)#', '', $content); + + return $content; + } +} diff --git a/phpbb/feed/news.php b/phpbb/feed/news.php new file mode 100644 index 0000000..13ca82c --- /dev/null +++ b/phpbb/feed/news.php @@ -0,0 +1,116 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * News feed + * + * This will give you {$this->num_items} first posts + * of all topics in the selected news forums. + */ +class news extends topic_base +{ + /** + * Returns the ids of the 'news forums' + * @return int[] + */ + private function get_news_forums() + { + static $forum_ids; + + // Matches acp/acp_board.php + $cache_name = 'feed_news_forum_ids'; + + if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0'); + $result = $this->db->sql_query($sql); + + $forum_ids = array(); + while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) + { + $forum_ids[$forum_id] = $forum_id; + } + $this->db->sql_freeresult($result); + + $this->cache->put('_' . $cache_name, $forum_ids); + } + + return $forum_ids; + } + + /** + * {@inheritdoc} + */ + protected function get_sql() + { + // Determine forum ids + $in_fid_ary = array_intersect($this->get_news_forums(), $this->get_readable_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + $in_fid_ary = array_diff($in_fid_ary, $this->get_passworded_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + // We really have to get the post ids first! + $sql = 'SELECT topic_first_post_id, topic_time + FROM ' . TOPICS_TABLE . ' + WHERE topic_moved_id = 0 + AND ' . $this->content_visibility->get_forums_visibility_sql('topic', $in_fid_ary) . ' + ORDER BY topic_time DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $post_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['topic_first_post_id']; + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return false; + } + + parent::fetch_attachments($post_ids); + + $this->sql = array( + 'SELECT' => 'f.forum_id, f.forum_name, + t.topic_id, t.topic_title, t.topic_poster, t.topic_first_poster_name, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, t.topic_time, t.topic_last_post_time, + p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, t.topic_visibility', + 'FROM' => array( + TOPICS_TABLE => 't', + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'p.forum_id = f.forum_id', + ), + ), + 'WHERE' => 'p.topic_id = t.topic_id + AND ' . $this->db->sql_in_set('p.post_id', $post_ids), + 'ORDER_BY' => 'p.post_time DESC, p.post_id DESC', + ); + + return true; + } +} diff --git a/phpbb/feed/overall.php b/phpbb/feed/overall.php new file mode 100644 index 0000000..b083df9 --- /dev/null +++ b/phpbb/feed/overall.php @@ -0,0 +1,94 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * Board wide feed (aka overall feed) + * + * This will give you the newest {$this->num_items} posts + * from the whole board. + */ +class overall extends post_base +{ + /** + * {@inheritdoc} + */ + protected function get_sql() + { + $forum_ids = array_diff($this->get_readable_forums(), $this->get_excluded_forums(), $this->get_passworded_forums()); + if (empty($forum_ids)) + { + return false; + } + + // Determine topics with recent activity + $sql = 'SELECT topic_id, topic_last_post_time + FROM ' . TOPICS_TABLE . ' + WHERE topic_moved_id = 0 + AND ' . $this->content_visibility->get_forums_visibility_sql('topic', $forum_ids) . ' + ORDER BY topic_last_post_time DESC, topic_last_post_id DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $topic_ids = array(); + $min_post_time = 0; + while ($row = $this->db->sql_fetchrow()) + { + $topic_ids[] = (int) $row['topic_id']; + + $min_post_time = (int) $row['topic_last_post_time']; + } + $this->db->sql_freeresult($result); + + if (empty($topic_ids)) + { + return false; + } + + parent::fetch_attachments(array(), $topic_ids); + + // Get the actual data + $this->sql = array( + 'SELECT' => 'f.forum_id, f.forum_name, ' . + 'p.post_id, p.topic_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, ' . + 'u.username, u.user_id', + 'FROM' => array( + USERS_TABLE => 'u', + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'f.forum_id = p.forum_id', + ), + ), + 'WHERE' => $this->db->sql_in_set('p.topic_id', $topic_ids) . ' + AND ' . $this->content_visibility->get_forums_visibility_sql('post', $forum_ids, 'p.') . ' + AND p.post_time >= ' . $min_post_time . ' + AND u.user_id = p.poster_id', + 'ORDER_BY' => 'p.post_time DESC, p.post_id DESC', + ); + + return true; + } + + /** + * {@inheritdoc} + */ + public function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; + } +} diff --git a/phpbb/feed/post_base.php b/phpbb/feed/post_base.php new file mode 100644 index 0000000..f6dc39c --- /dev/null +++ b/phpbb/feed/post_base.php @@ -0,0 +1,60 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * Abstract class for post based feeds + */ +abstract class post_base extends attachments_base +{ + protected $num_items = 'feed_limit_post'; + + /** + * {@inheritdoc} + */ + public function set_keys() + { + $this->set('title', 'post_subject'); + $this->set('title2', 'topic_title'); + + $this->set('author_id', 'user_id'); + $this->set('creator', 'username'); + $this->set('published', 'post_time'); + $this->set('updated', 'post_edit_time'); + $this->set('text', 'post_text'); + + $this->set('bitfield', 'bbcode_bitfield'); + $this->set('bbcode_uid','bbcode_uid'); + + $this->set('enable_bbcode', 'enable_bbcode'); + $this->set('enable_smilies', 'enable_smilies'); + $this->set('enable_magic_url', 'enable_magic_url'); + } + + /** + * {@inheritdoc} + */ + public function adjust_item(&$item_row, &$row) + { + $item_row['link'] = $this->helper->append_sid('viewtopic.' . $this->phpEx, "t={$row['topic_id']}&p={$row['post_id']}#p{$row['post_id']}"); + + if ($this->config['feed_item_statistics']) + { + $item_row['statistics'] = $this->user->lang['POSTED'] . ' ' . $this->user->lang['POST_BY_AUTHOR'] . ' ' . $this->user_viewprofile($row) + . ' ' . $this->separator_stats . ' ' . $this->user->format_date($row[$this->get('published')]) + . (($this->is_moderator_approve_forum($row['forum_id']) && (int) $row['post_visibility'] === ITEM_UNAPPROVED) ? ' ' . $this->separator_stats . ' ' . $this->user->lang['POST_UNAPPROVED'] : '') + . (($this->is_moderator_approve_forum($row['forum_id']) && (int) $row['post_visibility'] === ITEM_DELETED) ? ' ' . $this->separator_stats . ' ' . $this->user->lang['POST_DELETED'] : ''); + } + } +} diff --git a/phpbb/feed/quote_helper.php b/phpbb/feed/quote_helper.php new file mode 100644 index 0000000..843d075 --- /dev/null +++ b/phpbb/feed/quote_helper.php @@ -0,0 +1,36 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * Modified quote_helper for feeds (basically just removing all attributes) + */ +class quote_helper extends \phpbb\textformatter\s9e\quote_helper +{ + /** + * {@inheritdoc} + */ + public function inject_metadata($xml) + { + // In feeds we don't want any attributes, so delete all of them + return \s9e\TextFormatter\Utils::replaceAttributes( + $xml, + 'QUOTE', + function () + { + return []; + } + ); + } +} diff --git a/phpbb/feed/topic.php b/phpbb/feed/topic.php new file mode 100644 index 0000000..2504e41 --- /dev/null +++ b/phpbb/feed/topic.php @@ -0,0 +1,164 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +use phpbb\feed\exception\no_feed_exception; +use phpbb\feed\exception\no_topic_exception; +use phpbb\feed\exception\unauthorized_forum_exception; +use phpbb\feed\exception\unauthorized_topic_exception; + +/** + * Topic feed for a specific topic + * + * This will give you the last {$this->num_items} posts made within this topic. + */ +class topic extends post_base +{ + protected $topic_id = 0; + protected $forum_id = 0; + protected $topic_data = array(); + + /** + * Set the Topic ID + * + * @param int $topic_id Topic ID + * @return \phpbb\feed\topic + */ + public function set_topic_id($topic_id) + { + $this->topic_id = (int) $topic_id; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function open() + { + $sql = 'SELECT f.forum_options, f.forum_password, t.topic_id, t.forum_id, t.topic_visibility, t.topic_title, t.topic_time, t.topic_views, t.topic_posts_approved, t.topic_type + FROM ' . TOPICS_TABLE . ' t + LEFT JOIN ' . FORUMS_TABLE . ' f + ON (f.forum_id = t.forum_id) + WHERE t.topic_id = ' . $this->topic_id; + $result = $this->db->sql_query($sql); + $this->topic_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (empty($this->topic_data)) + { + throw new no_topic_exception($this->topic_id); + } + + $this->forum_id = (int) $this->topic_data['forum_id']; + + // Make sure topic is either approved or user authed + if ($this->topic_data['topic_visibility'] != ITEM_APPROVED && !$this->auth->acl_get('m_approve', $this->forum_id)) + { + if ($this->user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + } + else + { + send_status_line(401, 'Unauthorized'); + } + throw new unauthorized_topic_exception($this->topic_id); + } + + // Make sure forum is not excluded from feed + if (phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $this->topic_data['forum_options'])) + { + throw new no_feed_exception(); + } + + // Make sure we can read this forum + if (!$this->auth->acl_get('f_read', $this->forum_id)) + { + if ($this->user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + } + else + { + send_status_line(401, 'Unauthorized'); + } + throw new unauthorized_forum_exception($this->forum_id); + } + + // Make sure forum is not passworded or user is authed + if ($this->topic_data['forum_password']) + { + $forum_ids_passworded = $this->get_passworded_forums(); + + if (isset($forum_ids_passworded[$this->forum_id])) + { + if ($this->user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + } + else + { + send_status_line(401, 'Unauthorized'); + } + throw new unauthorized_forum_exception($this->forum_id); + } + + unset($forum_ids_passworded); + } + + parent::open(); + } + + /** + * {@inheritdoc} + */ + protected function get_sql() + { + parent::fetch_attachments(); + + $this->sql = array( + 'SELECT' => 'p.post_id, p.post_time, p.post_edit_time, p.post_visibility, p.post_subject, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, ' . + 'u.username, u.user_id', + 'FROM' => array( + POSTS_TABLE => 'p', + USERS_TABLE => 'u', + ), + 'WHERE' => 'p.topic_id = ' . $this->topic_id . ' + AND ' . $this->content_visibility->get_visibility_sql('post', $this->forum_id, 'p.') . ' + AND p.poster_id = u.user_id', + 'ORDER_BY' => 'p.post_time DESC, p.post_id DESC', + ); + + return true; + } + + /** + * {@inheritdoc} + */ + public function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['forum_id'] = $this->forum_id; + } + + /** + * {@inheritdoc} + */ + public function get_item() + { + return ($row = parent::get_item()) ? array_merge($this->topic_data, $row) : $row; + } +} diff --git a/phpbb/feed/topic_base.php b/phpbb/feed/topic_base.php new file mode 100644 index 0000000..0f1a9cc --- /dev/null +++ b/phpbb/feed/topic_base.php @@ -0,0 +1,76 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * Abstract class for topic based feeds + */ +abstract class topic_base extends attachments_base +{ + protected $num_items = 'feed_limit_topic'; + + /** + * {@inheritdoc} + */ + public function set_keys() + { + $this->set('title', 'topic_title'); + $this->set('title2', 'forum_name'); + + $this->set('author_id', 'topic_poster'); + $this->set('creator', 'topic_first_poster_name'); + $this->set('published', 'post_time'); + $this->set('updated', 'post_edit_time'); + $this->set('text', 'post_text'); + + $this->set('bitfield', 'bbcode_bitfield'); + $this->set('bbcode_uid','bbcode_uid'); + + $this->set('enable_bbcode', 'enable_bbcode'); + $this->set('enable_smilies', 'enable_smilies'); + $this->set('enable_magic_url', 'enable_magic_url'); + } + + /** + * {@inheritdoc} + */ + public function adjust_item(&$item_row, &$row) + { + $item_row['link'] = $this->helper->append_sid('viewtopic.' . $this->phpEx, 't=' . $row['topic_id'] . '&p=' . $row['post_id'] . '#p' . $row['post_id']); + + if ($this->config['feed_item_statistics']) + { + $item_row['statistics'] = $this->user->lang['POSTED'] . ' ' . $this->user->lang['POST_BY_AUTHOR'] . ' ' . $this->user_viewprofile($row) + . ' ' . $this->separator_stats . ' ' . $this->user->format_date($row[$this->get('published')]) + . ' ' . $this->separator_stats . ' ' . $this->user->lang['REPLIES'] . ' ' . ($this->content_visibility->get_count('topic_posts', $row, $row['forum_id']) - 1) + . ' ' . $this->separator_stats . ' ' . $this->user->lang['VIEWS'] . ' ' . $row['topic_views']; + + if ($this->is_moderator_approve_forum($row['forum_id'])) + { + if ((int) $row['topic_visibility'] === ITEM_DELETED) + { + $item_row['statistics'] .= ' ' . $this->separator_stats . ' ' . $this->user->lang['TOPIC_DELETED']; + } + else if ((int) $row['topic_visibility'] === ITEM_UNAPPROVED) + { + $item_row['statistics'] .= ' ' . $this->separator_stats . ' ' . $this->user->lang['TOPIC_UNAPPROVED']; + } + else if ($row['topic_posts_unapproved']) + { + $item_row['statistics'] .= ' ' . $this->separator_stats . ' ' . $this->user->lang['POSTS_UNAPPROVED']; + } + } + } + } +} diff --git a/phpbb/feed/topics.php b/phpbb/feed/topics.php new file mode 100644 index 0000000..183c29d --- /dev/null +++ b/phpbb/feed/topics.php @@ -0,0 +1,94 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * New Topics feed + * + * This will give you the last {$this->num_items} created topics + * including the first post. + */ +class topics extends topic_base +{ + /** + * {@inheritdoc} + */ + protected function get_sql() + { + $forum_ids_read = $this->get_readable_forums(); + if (empty($forum_ids_read)) + { + return false; + } + + $in_fid_ary = array_diff($forum_ids_read, $this->get_excluded_forums(), $this->get_passworded_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + // We really have to get the post ids first! + $sql = 'SELECT topic_first_post_id, topic_time + FROM ' . TOPICS_TABLE . ' + WHERE topic_moved_id = 0 + AND ' . $this->content_visibility->get_forums_visibility_sql('topic', $in_fid_ary) . ' + ORDER BY topic_time DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $post_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['topic_first_post_id']; + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return false; + } + + parent::fetch_attachments($post_ids); + + $this->sql = array( + 'SELECT' => 'f.forum_id, f.forum_name, + t.topic_id, t.topic_title, t.topic_poster, t.topic_first_poster_name, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, t.topic_time, t.topic_last_post_time, + p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, t.topic_visibility', + 'FROM' => array( + TOPICS_TABLE => 't', + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'p.forum_id = f.forum_id', + ), + ), + 'WHERE' => 'p.topic_id = t.topic_id + AND ' . $this->db->sql_in_set('p.post_id', $post_ids), + 'ORDER_BY' => 'p.post_time DESC, p.post_id DESC', + ); + + return true; + } + + /** + * {@inheritdoc} + */ + public function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; + } +} diff --git a/phpbb/feed/topics_active.php b/phpbb/feed/topics_active.php new file mode 100644 index 0000000..ea9ee97 --- /dev/null +++ b/phpbb/feed/topics_active.php @@ -0,0 +1,147 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\feed; + +/** + * Active Topics feed + * + * This will give you the last {$this->num_items} topics + * with replies made withing the last {$this->sort_days} days + * including the last post. + */ +class topics_active extends topic_base +{ + protected $sort_days = 7; + + /** + * {@inheritdoc} + */ + public function set_keys() + { + parent::set_keys(); + + $this->set('author_id', 'topic_last_poster_id'); + $this->set('creator', 'topic_last_poster_name'); + } + + /** + * {@inheritdoc} + */ + protected function get_sql() + { + $forum_ids_read = $this->get_readable_forums(); + if (empty($forum_ids_read)) + { + return false; + } + + $in_fid_ary = array_intersect($forum_ids_read, $this->get_forum_ids()); + $in_fid_ary = array_diff($in_fid_ary, $this->get_passworded_forums()); + if (empty($in_fid_ary)) + { + return false; + } + + // Search for topics in last X days + $last_post_time_sql = ($this->sort_days) ? ' AND topic_last_post_time > ' . (time() - ($this->sort_days * 24 * 3600)) : ''; + + // We really have to get the post ids first! + $sql = 'SELECT topic_last_post_id, topic_last_post_time + FROM ' . TOPICS_TABLE . ' + WHERE topic_moved_id = 0 + AND ' . $this->content_visibility->get_forums_visibility_sql('topic', $in_fid_ary) . ' + ' . $last_post_time_sql . ' + ORDER BY topic_last_post_time DESC, topic_last_post_id DESC'; + $result = $this->db->sql_query_limit($sql, $this->num_items); + + $post_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_ids[] = (int) $row['topic_last_post_id']; + } + $this->db->sql_freeresult($result); + + if (empty($post_ids)) + { + return false; + } + + parent::fetch_attachments($post_ids); + + $this->sql = array( + 'SELECT' => 'f.forum_id, f.forum_name, + t.topic_id, t.topic_title, t.topic_posts_approved, t.topic_posts_unapproved, t.topic_posts_softdeleted, t.topic_views, + t.topic_last_poster_id, t.topic_last_poster_name, t.topic_last_post_time, + p.post_id, p.post_time, p.post_edit_time, p.post_text, p.bbcode_bitfield, p.bbcode_uid, p.enable_bbcode, p.enable_smilies, p.enable_magic_url, p.post_attachment, t.topic_visibility', + 'FROM' => array( + TOPICS_TABLE => 't', + POSTS_TABLE => 'p', + ), + 'LEFT_JOIN' => array( + array( + 'FROM' => array(FORUMS_TABLE => 'f'), + 'ON' => 'p.forum_id = f.forum_id', + ), + ), + 'WHERE' => 'p.topic_id = t.topic_id + AND ' . $this->db->sql_in_set('p.post_id', $post_ids), + 'ORDER_BY' => 'p.post_time DESC, p.post_id DESC', + ); + + return true; + } + + /** + * Returns the ids of the forums not excluded from the active list + * + * @return int[] + */ + private function get_forum_ids() + { + static $forum_ids; + + $cache_name = 'feed_topic_active_forum_ids'; + + if (!isset($forum_ids) && ($forum_ids = $this->cache->get('_' . $cache_name)) === false) + { + $sql = 'SELECT forum_id + FROM ' . FORUMS_TABLE . ' + WHERE forum_type = ' . FORUM_POST . ' + AND ' . $this->db->sql_bit_and('forum_options', FORUM_OPTION_FEED_EXCLUDE, '= 0') . ' + AND ' . $this->db->sql_bit_and('forum_flags', round(log(FORUM_FLAG_ACTIVE_TOPICS, 2)), '<> 0'); + $result = $this->db->sql_query($sql); + + $forum_ids = array(); + while ($forum_id = (int) $this->db->sql_fetchfield('forum_id')) + { + $forum_ids[$forum_id] = $forum_id; + } + $this->db->sql_freeresult($result); + + $this->cache->put('_' . $cache_name, $forum_ids, 180); + } + + return $forum_ids; + } + + /** + * {@inheritdoc} + */ + public function adjust_item(&$item_row, &$row) + { + parent::adjust_item($item_row, $row); + + $item_row['title'] = (isset($row['forum_name']) && $row['forum_name'] !== '') ? $row['forum_name'] . ' ' . $this->separator . ' ' . $item_row['title'] : $item_row['title']; + } +} diff --git a/phpbb/file_downloader.php b/phpbb/file_downloader.php new file mode 100644 index 0000000..403ca5b --- /dev/null +++ b/phpbb/file_downloader.php @@ -0,0 +1,120 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +class file_downloader +{ + /** @var string Error string */ + protected $error_string = ''; + + /** @var int Error number */ + protected $error_number = 0; + + /** + * Retrieve contents from remotely stored file + * + * @param string $host File host + * @param string $directory Directory file is in + * @param string $filename Filename of file to retrieve + * @param int $port Port to connect to; default: 80 + * @param int $timeout Connection timeout in seconds; default: 6 + * + * @return mixed File data as string if file can be read and there is no + * timeout, false if there were errors or the connection timed out + * + * @throws \phpbb\exception\runtime_exception If data can't be retrieved and no error + * message is returned + */ + public function get($host, $directory, $filename, $port = 80, $timeout = 6) + { + // Set default values for error variables + $this->error_number = 0; + $this->error_string = ''; + + if ($socket = @fsockopen(($port == 443 ? 'ssl://' : '') . $host, $port, $this->error_number, $this->error_string, $timeout)) + { + @fputs($socket, "GET $directory/$filename HTTP/1.0\r\n"); + @fputs($socket, "HOST: $host\r\n"); + @fputs($socket, "Connection: close\r\n\r\n"); + + $timer_stop = time() + $timeout; + stream_set_timeout($socket, $timeout); + + $file_info = ''; + $get_info = false; + + while (!@feof($socket)) + { + if ($get_info) + { + $file_info .= @fread($socket, 1024); + } + else + { + $line = @fgets($socket, 1024); + if ($line == "\r\n") + { + $get_info = true; + } + else if (stripos($line, '404 not found') !== false) + { + throw new \phpbb\exception\runtime_exception('FILE_NOT_FOUND', array($filename)); + } + } + + $stream_meta_data = stream_get_meta_data($socket); + + if (!empty($stream_meta_data['timed_out']) || time() >= $timer_stop) + { + throw new \phpbb\exception\runtime_exception('FSOCK_TIMEOUT'); + } + } + @fclose($socket); + } + else + { + if ($this->error_string) + { + $this->error_string = utf8_convert_message($this->error_string); + return false; + } + else + { + throw new \phpbb\exception\runtime_exception('FSOCK_DISABLED'); + } + } + + return $file_info; + } + + /** + * Get error string + * + * @return string Error string + */ + public function get_error_string() + { + return $this->error_string; + } + + /** + * Get error number + * + * @return int Error number + */ + public function get_error_number() + { + return $this->error_number; + } +} diff --git a/phpbb/files/factory.php b/phpbb/files/factory.php new file mode 100644 index 0000000..84b7cc9 --- /dev/null +++ b/phpbb/files/factory.php @@ -0,0 +1,58 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files; + +class factory +{ + /** + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + private $container; + + /** + * Constructor + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + */ + public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Get files service + * + * @param string $name Service name + * + * @return object|bool Requested service or false if service could not be + * found by the container + */ + public function get($name) + { + $service = false; + + $name = (strpos($name, '.') === false) ? 'files.' . $name : $name; + + try + { + $service = $this->container->get($name); + } + catch (\Exception $e) + { + // do nothing + } + + return $service; + } +} diff --git a/phpbb/files/filespec.php b/phpbb/files/filespec.php new file mode 100644 index 0000000..6847bca --- /dev/null +++ b/phpbb/files/filespec.php @@ -0,0 +1,584 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files; + +use phpbb\language\language; + +/** + * Responsible for holding all file relevant information, as well as doing file-specific operations. + * The {@link fileupload fileupload class} can be used to upload several files, each of them being this object to operate further on. + */ +class filespec +{ + /** @var string File name */ + protected $filename = ''; + + /** @var string Real name of file */ + protected $realname = ''; + + /** @var string Upload name of file */ + protected $uploadname = ''; + + /** @var string Mimetype of file */ + protected $mimetype = ''; + + /** @var string File extension */ + protected $extension = ''; + + /** @var int File size */ + protected $filesize = 0; + + /** @var int Width of file */ + protected $width = 0; + + /** @var int Height of file */ + protected $height = 0; + + /** @var array Image info including type and size */ + protected $image_info = array(); + + /** @var string Destination file name */ + protected $destination_file = ''; + + /** @var string Destination file path */ + protected $destination_path = ''; + + /** @var bool Whether file was moved */ + protected $file_moved = false; + + /** @var bool Whether file is local */ + protected $local = false; + + /** @var bool Class initialization flag */ + protected $class_initialized = false; + + /** @var array Error array */ + public $error = array(); + + /** @var upload Instance of upload class */ + public $upload; + + /** @var \phpbb\filesystem\filesystem_interface */ + protected $filesystem; + + /** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper class */ + protected $php_ini; + + /** @var \FastImageSize\FastImageSize */ + protected $imagesize; + + /** @var language Language class */ + protected $language; + + /** @var string phpBB root path */ + protected $phpbb_root_path; + + /** @var \phpbb\plupload\plupload The plupload object */ + protected $plupload; + + /** @var \phpbb\mimetype\guesser phpBB Mimetype guesser */ + protected $mimetype_guesser; + + /** + * File upload class + * + * @param \phpbb\filesystem\filesystem_interface $phpbb_filesystem Filesystem + * @param language $language Language + * @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper + * @param \FastImageSize\FastImageSize $imagesize Imagesize class + * @param string $phpbb_root_path phpBB root path + * @param \phpbb\mimetype\guesser $mimetype_guesser Mime type guesser + * @param \phpbb\plupload\plupload $plupload Plupload + */ + public function __construct(\phpbb\filesystem\filesystem_interface $phpbb_filesystem, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, \FastImageSize\FastImageSize $imagesize, $phpbb_root_path, \phpbb\mimetype\guesser $mimetype_guesser = null, \phpbb\plupload\plupload $plupload = null) + { + $this->filesystem = $phpbb_filesystem; + $this->language = $language; + $this->php_ini = $php_ini; + $this->imagesize = $imagesize; + $this->phpbb_root_path = $phpbb_root_path; + $this->plupload = $plupload; + $this->mimetype_guesser = $mimetype_guesser; + } + + /** + * Set upload ary + * + * @param array $upload_ary Upload ary + * + * @return filespec This instance of the filespec class + */ + public function set_upload_ary($upload_ary) + { + if (!isset($upload_ary) || !count($upload_ary)) + { + return $this; + } + + $this->class_initialized = true; + $this->filename = $upload_ary['tmp_name']; + $this->filesize = $upload_ary['size']; + $name = $upload_ary['name']; + $name = trim(utf8_basename($name)); + $this->realname = $this->uploadname = $name; + $this->mimetype = $upload_ary['type']; + + // Opera adds the name to the mime type + $this->mimetype = (strpos($this->mimetype, '; name') !== false) ? str_replace(strstr($this->mimetype, '; name'), '', $this->mimetype) : $this->mimetype; + + if (!$this->mimetype) + { + $this->mimetype = 'application/octet-stream'; + } + + $this->extension = strtolower(self::get_extension($this->realname)); + + // Try to get real filesize from temporary folder (not always working) ;) + $this->filesize = ($this->get_filesize($this->filename)) ?: $this->filesize; + + $this->width = $this->height = 0; + $this->file_moved = false; + + $this->local = (isset($upload_ary['local_mode'])) ? true : false; + + return $this; + } + + /** + * Set the upload namespace + * + * @param upload $namespace Instance of upload class + * + * @return filespec This instance of the filespec class + */ + public function set_upload_namespace($namespace) + { + $this->upload = $namespace; + + return $this; + } + + /** + * Check if class members were not properly initialised yet + * + * @return bool True if there was an init error, false if not + */ + public function init_error() + { + return !$this->class_initialized; + } + + /** + * Set error in error array + * + * @param mixed $error Content for error array + * + * @return \phpbb\files\filespec This instance of the filespec class + */ + public function set_error($error) + { + $this->error[] = $error; + + return $this; + } + + /** + * Cleans destination filename + * + * @param string $mode Either real, unique, or unique_ext. Real creates a + * realname, filtering some characters, lowering every + * character. Unique creates a unique filename. + * @param string $prefix Prefix applied to filename + * @param string $user_id The user_id is only needed for when cleaning a user's avatar + */ + public function clean_filename($mode = 'unique', $prefix = '', $user_id = '') + { + if ($this->init_error()) + { + return; + } + + switch ($mode) + { + case 'real': + // Remove every extension from filename (to not let the mime bug being exposed) + if (strpos($this->realname, '.') !== false) + { + $this->realname = substr($this->realname, 0, strpos($this->realname, '.')); + } + + // Replace any chars which may cause us problems with _ + $bad_chars = array("'", "\\", ' ', '/', ':', '*', '?', '"', '<', '>', '|'); + + $this->realname = rawurlencode(str_replace($bad_chars, '_', strtolower($this->realname))); + $this->realname = preg_replace("/%(\w{2})/", '_', $this->realname); + + $this->realname = $prefix . $this->realname . '.' . $this->extension; + break; + + case 'unique': + $this->realname = $prefix . md5(unique_id()); + break; + + case 'avatar': + $this->extension = strtolower($this->extension); + $this->realname = $prefix . $user_id . '.' . $this->extension; + + break; + + case 'unique_ext': + default: + $this->realname = $prefix . md5(unique_id()) . '.' . $this->extension; + } + } + + /** + * Get property from file object + * + * @param string $property Name of property + * + * @return mixed Content of property + */ + public function get($property) + { + if ($this->init_error() || !isset($this->$property)) + { + return false; + } + + return $this->$property; + } + + /** + * Check if file is an image (mime type) + * + * @return bool true if it is an image, false if not + */ + public function is_image() + { + return (strpos($this->mimetype, 'image/') === 0); + } + + /** + * Check if the file got correctly uploaded + * + * @return bool true if it is a valid upload, false if not + */ + public function is_uploaded() + { + $is_plupload = $this->plupload && $this->plupload->is_active(); + + if (!$this->local && !$is_plupload && !is_uploaded_file($this->filename)) + { + return false; + } + + if (($this->local || $is_plupload) && !file_exists($this->filename)) + { + return false; + } + + return true; + } + + /** + * Remove file + */ + public function remove() + { + if ($this->file_moved) + { + @unlink($this->destination_file); + } + } + + /** + * Get file extension + * + * @param string $filename Filename that needs to be checked + * + * @return string Extension of the supplied filename + */ + static public function get_extension($filename) + { + $filename = utf8_basename($filename); + + if (strpos($filename, '.') === false) + { + return ''; + } + + $filename = explode('.', $filename); + return array_pop($filename); + } + + /** + * Get mime type + * + * @param string $filename Filename that needs to be checked + * @return string Mime type of supplied filename + */ + public function get_mimetype($filename) + { + if ($this->mimetype_guesser !== null) + { + $mimetype = $this->mimetype_guesser->guess($filename, $this->uploadname); + + if ($mimetype !== 'application/octet-stream') + { + $this->mimetype = $mimetype; + } + } + + return $this->mimetype; + } + + /** + * Get file size + * + * @param string $filename File name of file to check + * + * @return int File size + */ + public function get_filesize($filename) + { + return @filesize($filename); + } + + + /** + * Check the first 256 bytes for forbidden content + * + * @param array $disallowed_content Array containg disallowed content + * + * @return bool False if disallowed content found, true if not + */ + public function check_content($disallowed_content) + { + if (empty($disallowed_content)) + { + return true; + } + + $fp = @fopen($this->filename, 'rb'); + + if ($fp !== false) + { + $ie_mime_relevant = fread($fp, 256); + fclose($fp); + foreach ($disallowed_content as $forbidden) + { + if (stripos($ie_mime_relevant, '<' . $forbidden) !== false) + { + return false; + } + } + } + return true; + } + + /** + * Move file to destination folder + * The phpbb_root_path variable will be applied to the destination path + * + * @param string $destination Destination path, for example $config['avatar_path'] + * @param bool $overwrite If set to true, an already existing file will be overwritten + * @param bool $skip_image_check If set to true, the check for the file to be a valid image is skipped + * @param string|bool $chmod Permission mask for chmodding the file after a successful move. + * The mode entered here reflects the mode defined by {@link phpbb_chmod()} + * + * @return bool True if file was moved, false if not + * @access public + */ + public function move_file($destination, $overwrite = false, $skip_image_check = false, $chmod = false) + { + if (count($this->error)) + { + return false; + } + + $chmod = ($chmod === false) ? CHMOD_READ | CHMOD_WRITE : $chmod; + + // We need to trust the admin in specifying valid upload directories and an attacker not being able to overwrite it... + $this->destination_path = $this->phpbb_root_path . $destination; + + // Check if the destination path exist... + if (!file_exists($this->destination_path)) + { + @unlink($this->filename); + return false; + } + + $upload_mode = ($this->php_ini->getBool('open_basedir') || $this->php_ini->getBool('safe_mode')) ? 'move' : 'copy'; + $upload_mode = ($this->local) ? 'local' : $upload_mode; + $this->destination_file = $this->destination_path . '/' . utf8_basename($this->realname); + + // Check if the file already exist, else there is something wrong... + if (file_exists($this->destination_file) && !$overwrite) + { + @unlink($this->filename); + $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); + $this->file_moved = false; + return false; + } + else + { + if (file_exists($this->destination_file)) + { + @unlink($this->destination_file); + } + + switch ($upload_mode) + { + case 'copy': + + if (!@copy($this->filename, $this->destination_file)) + { + if (!@move_uploaded_file($this->filename, $this->destination_file)) + { + $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); + } + } + + break; + + case 'move': + + if (!@move_uploaded_file($this->filename, $this->destination_file)) + { + if (!@copy($this->filename, $this->destination_file)) + { + $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); + } + } + + break; + + case 'local': + + if (!@copy($this->filename, $this->destination_file)) + { + $this->error[] = $this->language->lang($this->upload->error_prefix . 'GENERAL_UPLOAD_ERROR', $this->destination_file); + } + + break; + } + + // Remove temporary filename + @unlink($this->filename); + + if (count($this->error)) + { + return false; + } + + try + { + $this->filesystem->phpbb_chmod($this->destination_file, $chmod); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + + // Try to get real filesize from destination folder + $this->filesize = ($this->get_filesize($this->destination_file)) ?: $this->filesize; + + // Get mimetype of supplied file + $this->mimetype = $this->get_mimetype($this->destination_file); + + if ($this->is_image() && !$skip_image_check) + { + $this->width = $this->height = 0; + + $this->image_info = $this->imagesize->getImageSize($this->destination_file, $this->mimetype); + + if ($this->image_info !== false) + { + $this->width = $this->image_info['width']; + $this->height = $this->image_info['height']; + + // Check image type + $types = upload::image_types(); + + if (!isset($types[$this->image_info['type']]) || !in_array($this->extension, $types[$this->image_info['type']])) + { + if (!isset($types[$this->image_info['type']])) + { + $this->error[] = $this->language->lang('IMAGE_FILETYPE_INVALID', $this->image_info['type'], $this->mimetype); + } + else + { + $this->error[] = $this->language->lang('IMAGE_FILETYPE_MISMATCH', $types[$this->image_info['type']][0], $this->extension); + } + } + + // Make sure the dimensions match a valid image + if (empty($this->width) || empty($this->height)) + { + $this->error[] = $this->language->lang('ATTACHED_IMAGE_NOT_IMAGE'); + } + } + else + { + $this->error[] = $this->language->lang('UNABLE_GET_IMAGE_SIZE'); + } + } + + $this->file_moved = true; + $this->additional_checks(); + unset($this->upload); + + return true; + } + + /** + * Performing additional checks + * + * @return bool False if issue was found, true if not + */ + public function additional_checks() + { + if (!$this->file_moved) + { + return false; + } + + // Filesize is too big or it's 0 if it was larger than the maxsize in the upload form + if ($this->upload->max_filesize && ($this->get('filesize') > $this->upload->max_filesize || $this->filesize == 0)) + { + $max_filesize = get_formatted_filesize($this->upload->max_filesize, false); + + $this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); + + return false; + } + + if (!$this->upload->valid_dimensions($this)) + { + $this->error[] = $this->language->lang($this->upload->error_prefix . 'WRONG_SIZE', + $this->language->lang('PIXELS', (int) $this->upload->min_width), + $this->language->lang('PIXELS', (int) $this->upload->min_height), + $this->language->lang('PIXELS', (int) $this->upload->max_width), + $this->language->lang('PIXELS', (int) $this->upload->max_height), + $this->language->lang('PIXELS', (int) $this->width), + $this->language->lang('PIXELS', (int) $this->height)); + + return false; + } + + return true; + } +} diff --git a/phpbb/files/types/base.php b/phpbb/files/types/base.php new file mode 100644 index 0000000..3313ad0 --- /dev/null +++ b/phpbb/files/types/base.php @@ -0,0 +1,65 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files\types; + +abstract class base implements type_interface +{ + /** @var \phpbb\language\language */ + protected $language; + + /** @var \bantu\IniGetWrapper\IniGetWrapper */ + protected $php_ini; + + /** @var \phpbb\files\upload */ + protected $upload; + + /** + * Check if upload exceeds maximum file size + * + * @param \phpbb\files\filespec $file Filespec object + * + * @return \phpbb\files\filespec Returns same filespec instance + */ + public function check_upload_size($file) + { + // PHP Upload filesize exceeded + if ($file->get('filename') == 'none') + { + $max_filesize = $this->php_ini->getString('upload_max_filesize'); + $unit = 'MB'; + + if (!empty($max_filesize)) + { + $unit = strtolower(substr($max_filesize, -1, 1)); + $max_filesize = (int) $max_filesize; + + $unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB'); + } + + $file->error[] = (empty($max_filesize)) ? $this->language->lang($this->upload->error_prefix . 'PHP_SIZE_NA') : $this->language->lang($this->upload->error_prefix . 'PHP_SIZE_OVERRUN', $max_filesize, $this->language->lang($unit)); + } + + return $file; + } + + /** + * {@inheritdoc} + */ + public function set_upload(\phpbb\files\upload $upload) + { + $this->upload = $upload; + + return $this; + } +} diff --git a/phpbb/files/types/form.php b/phpbb/files/types/form.php new file mode 100644 index 0000000..2c3beb6 --- /dev/null +++ b/phpbb/files/types/form.php @@ -0,0 +1,138 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\plupload\plupload; +use phpbb\request\request_interface; + +class form extends base +{ + /** @var factory Files factory */ + protected $factory; + + /** @var language */ + protected $language; + + /** @var IniGetWrapper */ + protected $php_ini; + + /** @var plupload */ + protected $plupload; + + /** @var request_interface */ + protected $request; + + /** @var \phpbb\files\upload */ + protected $upload; + + /** + * Construct a form upload type + * + * @param factory $factory Files factory + * @param language $language Language class + * @param IniGetWrapper $php_ini ini_get() wrapper + * @param plupload $plupload Plupload + * @param request_interface $request Request object + */ + public function __construct(factory $factory, language $language, IniGetWrapper $php_ini, plupload $plupload, request_interface $request) + { + $this->factory = $factory; + $this->language = $language; + $this->php_ini = $php_ini; + $this->plupload = $plupload; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function upload() + { + $args = func_get_args(); + return $this->form_upload($args[0]); + } + + /** + * Form upload method + * Upload file from users harddisk + * + * @param string $form_name Form name assigned to the file input field (if it is an array, the key has to be specified) + * + * @return filespec $file Object "filespec" is returned, all further operations can be done with this object + * @access public + */ + protected function form_upload($form_name) + { + $upload = $this->request->file($form_name); + unset($upload['local_mode']); + + $result = $this->plupload->handle_upload($form_name); + if (is_array($result)) + { + $upload = array_merge($upload, $result); + } + + /** @var filespec $file */ + $file = $this->factory->get('filespec') + ->set_upload_ary($upload) + ->set_upload_namespace($this->upload); + + if ($file->init_error()) + { + $file->error[] = ''; + return $file; + } + + // Error array filled? + if (isset($upload['error'])) + { + $error = $this->upload->assign_internal_error($upload['error']); + + if ($error !== false) + { + $file->error[] = $error; + return $file; + } + } + + // Check if empty file got uploaded (not catched by is_uploaded_file) + if (isset($upload['size']) && $upload['size'] == 0) + { + $file->error[] = $this->language->lang($this->upload->error_prefix . 'EMPTY_FILEUPLOAD'); + return $file; + } + + // PHP Upload file size check + $file = $this->check_upload_size($file); + if (count($file->error)) + { + return $file; + } + + // Not correctly uploaded + if (!$file->is_uploaded()) + { + $file->error[] = $this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED'); + return $file; + } + + $this->upload->common_checks($file); + + return $file; + } +} diff --git a/phpbb/files/types/local.php b/phpbb/files/types/local.php new file mode 100644 index 0000000..4dfe4f7 --- /dev/null +++ b/phpbb/files/types/local.php @@ -0,0 +1,136 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\request\request_interface; + +class local extends base +{ + /** @var factory Files factory */ + protected $factory; + + /** @var language */ + protected $language; + + /** @var IniGetWrapper */ + protected $php_ini; + + /** @var request_interface */ + protected $request; + + /** @var \phpbb\files\upload */ + protected $upload; + + /** + * Construct a form upload type + * + * @param factory $factory Files factory + * @param language $language Language class + * @param IniGetWrapper $php_ini ini_get() wrapper + * @param request_interface $request Request object + */ + public function __construct(factory $factory, language $language, IniGetWrapper $php_ini, request_interface $request) + { + $this->factory = $factory; + $this->language = $language; + $this->php_ini = $php_ini; + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function upload() + { + $args = func_get_args(); + return $this->local_upload($args[0], isset($args[1]) ? $args[1] : false); + } + + /** + * Move file from another location to phpBB + * + * @param string $source_file Filename of source file + * @param array|bool $filedata Array with filedata or false + * + * @return filespec Object "filespec" is returned, all further operations can be done with this object + */ + protected function local_upload($source_file, $filedata = false) + { + $upload = $this->get_upload_ary($source_file, $filedata); + + /** @var filespec $file */ + $file = $this->factory->get('filespec') + ->set_upload_ary($upload) + ->set_upload_namespace($this->upload); + + if ($file->init_error()) + { + $file->error[] = ''; + return $file; + } + + // PHP Upload file size check + $file = $this->check_upload_size($file); + if (count($file->error)) + { + return $file; + } + + // Not correctly uploaded + if (!$file->is_uploaded()) + { + $file->error[] = $this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED'); + return $file; + } + + $this->upload->common_checks($file); + $this->request->overwrite('local', $upload, request_interface::FILES); + + return $file; + } + + /** + * Retrieve upload array + * + * @param string $source_file Source file name + * @param array $filedata File data array + * + * @return array Upload array + */ + protected function get_upload_ary($source_file, $filedata) + { + $upload = array(); + + $upload['local_mode'] = true; + $upload['tmp_name'] = $source_file; + + if ($filedata === false) + { + $upload['name'] = utf8_basename($source_file); + $upload['size'] = 0; + } + else + { + $upload['name'] = $filedata['realname']; + $upload['size'] = $filedata['size']; + $upload['type'] = $filedata['type']; + } + + return $upload; + } +} diff --git a/phpbb/files/types/remote.php b/phpbb/files/types/remote.php new file mode 100644 index 0000000..1fdba0c --- /dev/null +++ b/phpbb/files/types/remote.php @@ -0,0 +1,207 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files\types; + +use bantu\IniGetWrapper\IniGetWrapper; +use phpbb\config\config; +use phpbb\files\factory; +use phpbb\files\filespec; +use phpbb\language\language; +use phpbb\request\request_interface; + +class remote extends base +{ + /** @var config phpBB config */ + protected $config; + + /** @var factory Files factory */ + protected $factory; + + /** @var language */ + protected $language; + + /** @var IniGetWrapper */ + protected $php_ini; + + /** @var request_interface */ + protected $request; + + /** @var \phpbb\files\upload */ + protected $upload; + + /** @var string phpBB root path */ + protected $phpbb_root_path; + + /** + * Construct a form upload type + * + * @param config $config phpBB config + * @param factory $factory Files factory + * @param language $language Language class + * @param IniGetWrapper $php_ini ini_get() wrapper + * @param request_interface $request Request object + * @param string $phpbb_root_path phpBB root path + */ + public function __construct(config $config, factory $factory, language $language, IniGetWrapper $php_ini, request_interface $request, $phpbb_root_path) + { + $this->config = $config; + $this->factory = $factory; + $this->language = $language; + $this->php_ini = $php_ini; + $this->request = $request; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * {@inheritdoc} + */ + public function upload() + { + $args = func_get_args(); + return $this->remote_upload($args[0]); + } + + /** + * Remote upload method + * Uploads file from given url + * + * @param string $upload_url URL pointing to file to upload, for example http://www.foobar.com/example.gif + * @return filespec $file Object "filespec" is returned, all further operations can be done with this object + * @access public + */ + protected function remote_upload($upload_url) + { + $upload_ary = array(); + $upload_ary['local_mode'] = true; + + if (!preg_match('#^(https?://).*?\.(' . implode('|', $this->upload->allowed_extensions) . ')$#i', $upload_url, $match)) + { + return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'URL_INVALID')); + } + + $url = parse_url($upload_url); + + $upload_ary['type'] = 'application/octet-stream'; + + $url['path'] = explode('.', $url['path']); + $ext = array_pop($url['path']); + + $url['path'] = implode('', $url['path']); + $upload_ary['name'] = utf8_basename($url['path']) . (($ext) ? '.' . $ext : ''); + + $remote_max_filesize = $this->get_max_file_size(); + + $guzzle_options = [ + 'timeout' => $this->upload->upload_timeout, + 'connect_timeout' => $this->upload->upload_timeout, + 'verify' => !empty($this->config['remote_upload_verify']) ? (bool) $this->config['remote_upload_verify'] : false, + ]; + $client = new \GuzzleHttp\Client($guzzle_options); + + try + { + $response = $client->get($upload_url, $guzzle_options); + } + catch (\GuzzleHttp\Exception\ClientException $clientException) + { + return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'URL_NOT_FOUND'); + } + catch (\GuzzleHttp\Exception\RequestException $requestException) + { + if (strpos($requestException->getMessage(), 'cURL error 28') !== false || preg_match('/408|504/', $requestException->getCode())) + { + return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'REMOTE_UPLOAD_TIMEOUT'); + } + else + { + return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED')); + } + } + catch (\Exception $e) + { + return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'NOT_UPLOADED')); + } + + $content_length = $response->getBody()->getSize(); + if ($remote_max_filesize && $content_length > $remote_max_filesize) + { + $max_filesize = get_formatted_filesize($remote_max_filesize, false); + + return $this->factory->get('filespec')->set_error($this->language->lang($this->upload->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit'])); + } + + if ($content_length == 0) + { + return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'EMPTY_REMOTE_DATA'); + } + + $data = $response->getBody(); + + $filename = tempnam(sys_get_temp_dir(), unique_id() . '-'); + + if (!($fp = @fopen($filename, 'wb'))) + { + return $this->factory->get('filespec')->set_error($this->upload->error_prefix . 'NOT_UPLOADED'); + } + + $upload_ary['size'] = fwrite($fp, $data); + fclose($fp); + unset($data); + + $upload_ary['tmp_name'] = $filename; + + /** @var filespec $file */ + $file = $this->factory->get('filespec') + ->set_upload_ary($upload_ary) + ->set_upload_namespace($this->upload); + $this->upload->common_checks($file); + + return $file; + } + + /** + * Get maximum file size for remote uploads + * + * @return int Maximum file size + */ + protected function get_max_file_size() + { + $max_file_size = $this->upload->max_filesize; + if (!$max_file_size) + { + $max_file_size = $this->php_ini->getString('upload_max_filesize'); + + if (!empty($max_file_size)) + { + $unit = strtolower(substr($max_file_size, -1, 1)); + $max_file_size = (int) $max_file_size; + + switch ($unit) + { + case 'g': + $max_file_size *= 1024; + // no break + case 'm': + $max_file_size *= 1024; + // no break + case 'k': + $max_file_size *= 1024; + // no break + } + } + } + + return $max_file_size; + } +} diff --git a/phpbb/files/types/type_interface.php b/phpbb/files/types/type_interface.php new file mode 100644 index 0000000..e070783 --- /dev/null +++ b/phpbb/files/types/type_interface.php @@ -0,0 +1,38 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files\types; + +use phpbb\files\upload; + +interface type_interface +{ + /** + * Handle upload for upload types. Arguments passed to this method will be + * handled by the upload type classes themselves. + * + * @return \phpbb\files\filespec|bool Filespec instance if upload is + * successful or false if not + */ + public function upload(); + + /** + * Set upload instance + * Needs to be executed before every upload. + * + * @param upload $upload Upload instance + * + * @return type_interface Returns itself + */ + public function set_upload(upload $upload); +} diff --git a/phpbb/files/upload.php b/phpbb/files/upload.php new file mode 100644 index 0000000..50e15c9 --- /dev/null +++ b/phpbb/files/upload.php @@ -0,0 +1,390 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\files; + +use phpbb\filesystem\filesystem_interface; +use phpbb\language\language; +use phpbb\request\request_interface; + +/** + * File upload class + * Init class (all parameters optional and able to be set/overwritten separately) - scope is global and valid for all uploads + */ +class upload +{ + /** @var array Allowed file extensions */ + public $allowed_extensions = array(); + + /** @var array Disallowed content */ + protected $disallowed_content = array('body', 'head', 'html', 'img', 'plaintext', 'a href', 'pre', 'script', 'table', 'title'); + + /** @var int Maximum filesize */ + public $max_filesize = 0; + + /** @var int Minimum width of images */ + public $min_width = 0; + + /** @var int Minimum height of images */ + public $min_height = 0; + + /** @var int Maximum width of images */ + public $max_width = 0; + + /** @var int Maximum height of images */ + public $max_height = 0; + + /** @var string Prefix for language variables of errors */ + public $error_prefix = ''; + + /** @var int Timeout for remote upload */ + public $upload_timeout = 6; + + /** @var filesystem_interface */ + protected $filesystem; + + /** @var \phpbb\files\factory Files factory */ + protected $factory; + + /** @var \bantu\IniGetWrapper\IniGetWrapper ini_get() wrapper */ + protected $php_ini; + + /** @var \phpbb\language\language Language class */ + protected $language; + + /** @var request_interface Request class */ + protected $request; + + /** + * Init file upload class. + * + * @param filesystem_interface $filesystem + * @param factory $factory Files factory + * @param language $language Language class + * @param \bantu\IniGetWrapper\IniGetWrapper $php_ini ini_get() wrapper + * @param request_interface $request Request class + */ + public function __construct(filesystem_interface $filesystem, factory $factory, language $language, \bantu\IniGetWrapper\IniGetWrapper $php_ini, request_interface $request) + { + $this->filesystem = $filesystem; + $this->factory = $factory; + $this->language = $language; + $this->php_ini = $php_ini; + $this->request = $request; + } + + /** + * Reset vars + */ + public function reset_vars() + { + $this->max_filesize = 0; + $this->min_width = $this->min_height = $this->max_width = $this->max_height = 0; + $this->error_prefix = ''; + $this->allowed_extensions = array(); + $this->disallowed_content = array(); + } + + /** + * Set allowed extensions + * + * @param array $allowed_extensions Allowed file extensions + * + * @return \phpbb\files\upload This instance of upload + */ + public function set_allowed_extensions($allowed_extensions) + { + if ($allowed_extensions !== false && is_array($allowed_extensions)) + { + $this->allowed_extensions = $allowed_extensions; + } + + return $this; + } + + /** + * Set allowed dimensions + * + * @param int $min_width Minimum image width + * @param int $min_height Minimum image height + * @param int $max_width Maximum image width + * @param int $max_height Maximum image height + * + * @return \phpbb\files\upload This instance of upload + */ + public function set_allowed_dimensions($min_width, $min_height, $max_width, $max_height) + { + $this->min_width = (int) $min_width; + $this->min_height = (int) $min_height; + $this->max_width = (int) $max_width; + $this->max_height = (int) $max_height; + + return $this; + } + + /** + * Set maximum allowed file size + * + * @param int $max_filesize Maximum file size + * + * @return \phpbb\files\upload This instance of upload + */ + public function set_max_filesize($max_filesize) + { + if ($max_filesize !== false && (int) $max_filesize) + { + $this->max_filesize = (int) $max_filesize; + } + + return $this; + } + + /** + * Set disallowed strings + * + * @param array $disallowed_content Disallowed content + * + * @return \phpbb\files\upload This instance of upload + */ + public function set_disallowed_content($disallowed_content) + { + if ($disallowed_content !== false && is_array($disallowed_content)) + { + $this->disallowed_content = array_diff($disallowed_content, array('')); + } + + return $this; + } + + /** + * Set error prefix + * + * @param string $error_prefix Prefix for language variables of errors + * + * @return \phpbb\files\upload This instance of upload + */ + public function set_error_prefix($error_prefix) + { + $this->error_prefix = $error_prefix; + + return $this; + } + + /** + * Handle upload based on type + * + * @param string $type Upload type + * + * @return \phpbb\files\filespec|bool A filespec instance if upload was + * successful, false if there were issues or the type is not supported + */ + public function handle_upload($type) + { + $args = func_get_args(); + array_shift($args); + $type_class = $this->factory->get($type) + ->set_upload($this); + + return (is_object($type_class)) ? call_user_func_array(array($type_class, 'upload'), $args) : false; + } + + /** + * Assign internal error + * + * @param string $errorcode Error code to assign + * + * @return string Error string + * @access public + */ + public function assign_internal_error($errorcode) + { + switch ($errorcode) + { + case UPLOAD_ERR_INI_SIZE: + $max_filesize = $this->php_ini->getString('upload_max_filesize'); + $unit = 'MB'; + + if (!empty($max_filesize)) + { + $unit = strtolower(substr($max_filesize, -1, 1)); + $max_filesize = (int) $max_filesize; + + $unit = ($unit == 'k') ? 'KB' : (($unit == 'g') ? 'GB' : 'MB'); + } + + $error = (empty($max_filesize)) ? $this->language->lang($this->error_prefix . 'PHP_SIZE_NA') : $this->language->lang($this->error_prefix . 'PHP_SIZE_OVERRUN', $max_filesize, $this->language->lang($unit)); + break; + + case UPLOAD_ERR_FORM_SIZE: + $max_filesize = get_formatted_filesize($this->max_filesize, false); + + $error = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); + break; + + case UPLOAD_ERR_PARTIAL: + $error = $this->language->lang($this->error_prefix . 'PARTIAL_UPLOAD'); + break; + + case UPLOAD_ERR_NO_FILE: + $error = $this->language->lang($this->error_prefix . 'NOT_UPLOADED'); + break; + + case UPLOAD_ERR_NO_TMP_DIR: + case UPLOAD_ERR_CANT_WRITE: + $error = $this->language->lang($this->error_prefix . 'NO_TEMP_DIR'); + break; + + case UPLOAD_ERR_EXTENSION: + $error = $this->language->lang($this->error_prefix . 'PHP_UPLOAD_STOPPED'); + break; + + default: + $error = false; + break; + } + + return $error; + } + + /** + * Perform common file checks + * + * @param filespec $file Instance of filespec class + */ + public function common_checks($file) + { + // Filesize is too big or it's 0 if it was larger than the maxsize in the upload form + if ($this->max_filesize && ($file->get('filesize') > $this->max_filesize || $file->get('filesize') == 0)) + { + $max_filesize = get_formatted_filesize($this->max_filesize, false); + + $file->error[] = $this->language->lang($this->error_prefix . 'WRONG_FILESIZE', $max_filesize['value'], $max_filesize['unit']); + } + + // check Filename + if (preg_match("#[\\/:*?\"<>|]#i", $file->get('realname'))) + { + $file->error[] = $this->language->lang($this->error_prefix . 'INVALID_FILENAME', $file->get('realname')); + } + + // Invalid Extension + if (!$this->valid_extension($file)) + { + $file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_EXTENSION', $file->get('extension')); + } + + // MIME Sniffing + if (!$this->valid_content($file)) + { + $file->error[] = $this->language->lang($this->error_prefix . 'DISALLOWED_CONTENT'); + } + } + + /** + * Check for allowed extension + * + * @param filespec $file Instance of filespec class + * + * @return bool True if extension is allowed, false if not + */ + public function valid_extension($file) + { + return (in_array($file->get('extension'), $this->allowed_extensions)) ? true : false; + } + + /** + * Check for allowed dimension + * + * @param filespec $file Instance of filespec class + * + * @return bool True if dimensions are valid or no constraints set, false + * if not + */ + public function valid_dimensions($file) + { + if (!$this->max_width && !$this->max_height && !$this->min_width && !$this->min_height) + { + return true; + } + + if (($file->get('width') > $this->max_width && $this->max_width) || + ($file->get('height') > $this->max_height && $this->max_height) || + ($file->get('width') < $this->min_width && $this->min_width) || + ($file->get('height') < $this->min_height && $this->min_height)) + { + return false; + } + + return true; + } + + /** + * Check if form upload is valid + * + * @param string $form_name Name of form + * + * @return bool True if form upload is valid, false if not + */ + public function is_valid($form_name) + { + $upload = $this->request->file($form_name); + + return (!empty($upload) && $upload['name'] !== 'none'); + } + + + /** + * Check for bad content (IE mime-sniffing) + * + * @param filespec $file Instance of filespec class + * + * @return bool True if content is valid, false if not + */ + public function valid_content($file) + { + return ($file->check_content($this->disallowed_content)); + } + + /** + * Get image type/extension mapping + * + * @return array Array containing the image types and their extensions + */ + static public function image_types() + { + $result = array( + IMAGETYPE_GIF => array('gif'), + IMAGETYPE_JPEG => array('jpg', 'jpeg'), + IMAGETYPE_PNG => array('png'), + IMAGETYPE_SWF => array('swf'), + IMAGETYPE_PSD => array('psd'), + IMAGETYPE_BMP => array('bmp'), + IMAGETYPE_TIFF_II => array('tif', 'tiff'), + IMAGETYPE_TIFF_MM => array('tif', 'tiff'), + IMAGETYPE_JPC => array('jpg', 'jpeg'), + IMAGETYPE_JP2 => array('jpg', 'jpeg'), + IMAGETYPE_JPX => array('jpg', 'jpeg'), + IMAGETYPE_JB2 => array('jpg', 'jpeg'), + IMAGETYPE_IFF => array('iff'), + IMAGETYPE_WBMP => array('wbmp'), + IMAGETYPE_XBM => array('xbm'), + ); + + if (defined('IMAGETYPE_SWC')) + { + $result[IMAGETYPE_SWC] = array('swc'); + } + + return $result; + } +} diff --git a/phpbb/filesystem.php b/phpbb/filesystem.php new file mode 100644 index 0000000..af56d78 --- /dev/null +++ b/phpbb/filesystem.php @@ -0,0 +1,21 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** + * @deprecated 3.2.0-dev (To be removed 3.3.0) use \phpbb\filesystem\filesystem instead + */ +class filesystem extends \phpbb\filesystem\filesystem +{ +} diff --git a/phpbb/filesystem/exception/filesystem_exception.php b/phpbb/filesystem/exception/filesystem_exception.php new file mode 100644 index 0000000..d68fa9a --- /dev/null +++ b/phpbb/filesystem/exception/filesystem_exception.php @@ -0,0 +1,42 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\filesystem\exception; + +class filesystem_exception extends \phpbb\exception\runtime_exception +{ + /** + * Constructor + * + * @param string $message The Exception message to throw (must be a language variable). + * @param string $filename The file that caused the error. + * @param array $parameters The parameters to use with the language var. + * @param \Exception $previous The previous runtime_exception used for the runtime_exception chaining. + * @param integer $code The Exception code. + */ + public function __construct($message = "", $filename = '', $parameters = array(), \Exception $previous = null, $code = 0) + { + parent::__construct($message, array_merge(array('filename' => $filename), $parameters), $previous, $code); + } + + /** + * Returns the filename that triggered the error + * + * @return string + */ + public function get_filename() + { + $parameters = parent::get_parameters(); + return $parameters['filename']; + } +} diff --git a/phpbb/filesystem/filesystem.php b/phpbb/filesystem/filesystem.php new file mode 100644 index 0000000..bfafdf5 --- /dev/null +++ b/phpbb/filesystem/filesystem.php @@ -0,0 +1,916 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\filesystem; + +use phpbb\filesystem\exception\filesystem_exception; + +/** + * A class with various functions that are related to paths, files and the filesystem + */ +class filesystem implements filesystem_interface +{ + /** + * Store some information about file ownership for phpBB's chmod function + * + * @var array + */ + protected $chmod_info; + + /** + * Stores current working directory + * + * @var string|bool current working directory or false if it cannot be recovered + */ + protected $working_directory; + + /** + * Symfony's Filesystem component + * + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected $symfony_filesystem; + + /** + * Constructor + */ + public function __construct() + { + $this->chmod_info = array(); + $this->symfony_filesystem = new \Symfony\Component\Filesystem\Filesystem(); + $this->working_directory = null; + } + + /** + * {@inheritdoc} + */ + public function chgrp($files, $group, $recursive = false) + { + try + { + $this->symfony_filesystem->chgrp($files, $group, $recursive); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + // Try to recover filename + // By the time this is written that is at the end of the message + $error = trim($e->getMessage()); + $file = substr($error, strrpos($error, ' ')); + + throw new filesystem_exception('CANNOT_CHANGE_FILE_GROUP', $file, array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function chmod($files, $perms = null, $recursive = false, $force_chmod_link = false) + { + if (is_null($perms)) + { + // Default to read permission for compatibility reasons + $perms = self::CHMOD_READ; + } + + // Check if we got a permission flag + if ($perms > self::CHMOD_ALL) + { + $file_perm = $perms; + + // Extract permissions + //$owner = ($file_perm >> 6) & 7; // This will be ignored + $group = ($file_perm >> 3) & 7; + $other = ($file_perm >> 0) & 7; + + // Does any permissions provided? if so we add execute bit for directories + $group = ($group !== 0) ? ($group | self::CHMOD_EXECUTE) : $group; + $other = ($other !== 0) ? ($other | self::CHMOD_EXECUTE) : $other; + + // Compute directory permissions + $dir_perm = (self::CHMOD_ALL << 6) + ($group << 3) + ($other << 3); + } + else + { + // Add execute bit to owner if execute bit is among perms + $owner_perm = (self::CHMOD_READ | self::CHMOD_WRITE) | ($perms & self::CHMOD_EXECUTE); + $file_perm = ($owner_perm << 6) + ($perms << 3) + ($perms << 0); + + // Compute directory permissions + $perm = ($perms !== 0) ? ($perms | self::CHMOD_EXECUTE) : $perms; + $dir_perm = (($owner_perm | self::CHMOD_EXECUTE) << 6) + ($perm << 3) + ($perm << 0); + } + + // Symfony's filesystem component does not support extra execution flags on directories + // so we need to implement it again + foreach ($this->to_iterator($files) as $file) + { + if ($recursive && is_dir($file) && !is_link($file)) + { + $this->chmod(new \FilesystemIterator($file), $perms, true); + } + + // Don't chmod links as mostly those require 0777 and that cannot be changed + if (is_dir($file) || (is_link($file) && $force_chmod_link)) + { + if (true !== @chmod($file, $dir_perm)) + { + throw new filesystem_exception('CANNOT_CHANGE_FILE_PERMISSIONS', $file, array()); + } + } + else if (is_file($file)) + { + if (true !== @chmod($file, $file_perm)) + { + throw new filesystem_exception('CANNOT_CHANGE_FILE_PERMISSIONS', $file, array()); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function chown($files, $user, $recursive = false) + { + try + { + $this->symfony_filesystem->chown($files, $user, $recursive); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + // Try to recover filename + // By the time this is written that is at the end of the message + $error = trim($e->getMessage()); + $file = substr($error, strrpos($error, ' ')); + + throw new filesystem_exception('CANNOT_CHANGE_FILE_GROUP', $file, array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function clean_path($path) + { + $exploded = explode('/', $path); + $filtered = array(); + foreach ($exploded as $part) + { + if ($part === '.' && !empty($filtered)) + { + continue; + } + + if ($part === '..' && !empty($filtered) && $filtered[count($filtered) - 1] !== '.' && $filtered[count($filtered) - 1] !== '..') + { + array_pop($filtered); + } + else + { + $filtered[] = $part; + } + } + $path = implode('/', $filtered); + return $path; + } + + /** + * {@inheritdoc} + */ + public function copy($origin_file, $target_file, $override = false) + { + try + { + $this->symfony_filesystem->copy($origin_file, $target_file, $override); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + throw new filesystem_exception('CANNOT_COPY_FILES', '', array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function dump_file($filename, $content) + { + try + { + $this->symfony_filesystem->dumpFile($filename, $content); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + throw new filesystem_exception('CANNOT_DUMP_FILE', $filename, array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function exists($files) + { + return $this->symfony_filesystem->exists($files); + } + + /** + * {@inheritdoc} + */ + public function is_absolute_path($path) + { + return (isset($path[0]) && $path[0] === '/' || preg_match('#^[a-z]:[/\\\]#i', $path)) ? true : false; + } + + /** + * {@inheritdoc} + */ + public function is_readable($files, $recursive = false) + { + foreach ($this->to_iterator($files) as $file) + { + if ($recursive && is_dir($file) && !is_link($file)) + { + if (!$this->is_readable(new \FilesystemIterator($file), true)) + { + return false; + } + } + + if (!is_readable($file)) + { + return false; + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function is_writable($files, $recursive = false) + { + if (defined('PHP_WINDOWS_VERSION_MAJOR') || !function_exists('is_writable')) + { + foreach ($this->to_iterator($files) as $file) + { + if ($recursive && is_dir($file) && !is_link($file)) + { + if (!$this->is_writable(new \FilesystemIterator($file), true)) + { + return false; + } + } + + if (!$this->phpbb_is_writable($file)) + { + return false; + } + } + } + else + { + // use built in is_writable + foreach ($this->to_iterator($files) as $file) + { + if ($recursive && is_dir($file) && !is_link($file)) + { + if (!$this->is_writable(new \FilesystemIterator($file), true)) + { + return false; + } + } + + if (!is_writable($file)) + { + return false; + } + } + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function make_path_relative($end_path, $start_path) + { + return $this->symfony_filesystem->makePathRelative($end_path, $start_path); + } + + /** + * {@inheritdoc} + */ + public function mirror($origin_dir, $target_dir, \Traversable $iterator = null, $options = array()) + { + try + { + $this->symfony_filesystem->mirror($origin_dir, $target_dir, $iterator, $options); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + $msg = $e->getMessage(); + $filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); + + throw new filesystem_exception('CANNOT_MIRROR_DIRECTORY', $filename, array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function mkdir($dirs, $mode = 0777) + { + try + { + $this->symfony_filesystem->mkdir($dirs, $mode); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + $msg = $e->getMessage(); + $filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); + + throw new filesystem_exception('CANNOT_CREATE_DIRECTORY', $filename, array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function phpbb_chmod($files, $perms = null, $recursive = false, $force_chmod_link = false) + { + if (is_null($perms)) + { + // Default to read permission for compatibility reasons + $perms = self::CHMOD_READ; + } + + if (empty($this->chmod_info)) + { + if (!function_exists('fileowner') || !function_exists('filegroup')) + { + $this->chmod_info['process'] = false; + } + else + { + $common_php_owner = @fileowner(__FILE__); + $common_php_group = @filegroup(__FILE__); + + // And the owner and the groups PHP is running under. + $php_uid = (function_exists('posix_getuid')) ? @posix_getuid() : false; + $php_gids = (function_exists('posix_getgroups')) ? @posix_getgroups() : false; + + // If we are unable to get owner/group, then do not try to set them by guessing + if (!$php_uid || empty($php_gids) || !$common_php_owner || !$common_php_group) + { + $this->chmod_info['process'] = false; + } + else + { + $this->chmod_info = array( + 'process' => true, + 'common_owner' => $common_php_owner, + 'common_group' => $common_php_group, + 'php_uid' => $php_uid, + 'php_gids' => $php_gids, + ); + } + } + } + + if ($this->chmod_info['process']) + { + try + { + foreach ($this->to_iterator($files) as $file) + { + $file_uid = @fileowner($file); + $file_gid = @filegroup($file); + + // Change owner + if ($file_uid !== $this->chmod_info['common_owner']) + { + $this->chown($file, $this->chmod_info['common_owner'], $recursive); + } + + // Change group + if ($file_gid !== $this->chmod_info['common_group']) + { + $this->chgrp($file, $this->chmod_info['common_group'], $recursive); + } + + clearstatcache(); + $file_uid = @fileowner($file); + $file_gid = @filegroup($file); + } + } + catch (filesystem_exception $e) + { + $this->chmod_info['process'] = false; + } + } + + // Still able to process? + if ($this->chmod_info['process']) + { + if ($file_uid === $this->chmod_info['php_uid']) + { + $php = 'owner'; + } + else if (in_array($file_gid, $this->chmod_info['php_gids'])) + { + $php = 'group'; + } + else + { + // Since we are setting the everyone bit anyway, no need to do expensive operations + $this->chmod_info['process'] = false; + } + } + + // We are not able to determine or change something + if (!$this->chmod_info['process']) + { + $php = 'other'; + } + + switch ($php) + { + case 'owner': + try + { + $this->chmod($files, $perms, $recursive, $force_chmod_link); + clearstatcache(); + if ($this->is_readable($files) && $this->is_writable($files)) + { + break; + } + } + catch (filesystem_exception $e) + { + // Do nothing + } + case 'group': + try + { + $this->chmod($files, $perms, $recursive, $force_chmod_link); + clearstatcache(); + if ((!($perms & self::CHMOD_READ) || $this->is_readable($files, $recursive)) && (!($perms & self::CHMOD_WRITE) || $this->is_writable($files, $recursive))) + { + break; + } + } + catch (filesystem_exception $e) + { + // Do nothing + } + case 'other': + default: + $this->chmod($files, $perms, $recursive, $force_chmod_link); + break; + } + } + + /** + * {@inheritdoc} + */ + public function realpath($path) + { + if (!function_exists('realpath')) + { + return $this->phpbb_own_realpath($path); + } + + $realpath = realpath($path); + + // Strangely there are provider not disabling realpath but returning strange values. :o + // We at least try to cope with them. + if ((!$this->is_absolute_path($path) && $realpath === $path) || $realpath === false) + { + return $this->phpbb_own_realpath($path); + } + + // Check for DIRECTORY_SEPARATOR at the end (and remove it!) + if (substr($realpath, -1) === DIRECTORY_SEPARATOR) + { + $realpath = substr($realpath, 0, -1); + } + + return $realpath; + } + + /** + * {@inheritdoc} + */ + public function remove($files) + { + try + { + $this->symfony_filesystem->remove($files); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + // Try to recover filename + // By the time this is written that is at the end of the message + $error = trim($e->getMessage()); + $file = substr($error, strrpos($error, ' ')); + + throw new filesystem_exception('CANNOT_DELETE_FILES', $file, array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function rename($origin, $target, $overwrite = false) + { + try + { + $this->symfony_filesystem->rename($origin, $target, $overwrite); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + $msg = $e->getMessage(); + $filename = substr($msg, strpos($msg, '"'), strrpos($msg, '"')); + + throw new filesystem_exception('CANNOT_RENAME_FILE', $filename, array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function symlink($origin_dir, $target_dir, $copy_on_windows = false) + { + try + { + $this->symfony_filesystem->symlink($origin_dir, $target_dir, $copy_on_windows); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + throw new filesystem_exception('CANNOT_CREATE_SYMLINK', $origin_dir, array(), $e); + } + } + + /** + * {@inheritdoc} + */ + public function touch($files, $time = null, $access_time = null) + { + try + { + $this->symfony_filesystem->touch($files, $time, $access_time); + } + catch (\Symfony\Component\Filesystem\Exception\IOException $e) + { + // Try to recover filename + // By the time this is written that is at the end of the message + $error = trim($e->getMessage()); + $file = substr($error, strrpos($error, ' ')); + + throw new filesystem_exception('CANNOT_TOUCH_FILES', $file, array(), $e); + } + } + + /** + * phpBB's implementation of is_writable + * + * @todo Investigate if is_writable is still buggy + * + * @param string $file file/directory to check if writable + * + * @return bool true if the given path is writable + */ + protected function phpbb_is_writable($file) + { + if (file_exists($file)) + { + // Canonicalise path to absolute path + $file = $this->realpath($file); + + if (is_dir($file)) + { + // Test directory by creating a file inside the directory + $result = @tempnam($file, 'i_w'); + + if (is_string($result) && file_exists($result)) + { + unlink($result); + + // Ensure the file is actually in the directory (returned realpathed) + return (strpos($result, $file) === 0) ? true : false; + } + } + else + { + $handle = @fopen($file, 'c'); + + if (is_resource($handle)) + { + fclose($handle); + return true; + } + } + } + else + { + // file does not exist test if we can write to the directory + $dir = dirname($file); + + if (file_exists($dir) && is_dir($dir) && $this->phpbb_is_writable($dir)) + { + return true; + } + } + + return false; + } + + /** + * Try to resolve real path when PHP's realpath failes to do so + * + * @param string $path + * @return bool|string + */ + protected function phpbb_own_realpath($path) + { + // Replace all directory separators with '/' + $path = str_replace(DIRECTORY_SEPARATOR, '/', $path); + + $is_absolute_path = false; + $path_prefix = ''; + + if ($this->is_absolute_path($path)) + { + $is_absolute_path = true; + } + else + { + // Resolve working directory and store it + if (is_null($this->working_directory)) + { + if (function_exists('getcwd')) + { + $this->working_directory = str_replace(DIRECTORY_SEPARATOR, '/', getcwd()); + } + + // + // From this point on we really just guessing + // If chdir were called we screwed + // + else if (function_exists('debug_backtrace')) + { + $call_stack = debug_backtrace(0); + $this->working_directory = str_replace(DIRECTORY_SEPARATOR, '/', dirname($call_stack[count($call_stack) - 1]['file'])); + } + else + { + // + // Assuming that the working directory is phpBB root + // we could use this as a fallback, when phpBB will use controllers + // everywhere this will be a safe assumption + // + //$dir_parts = explode(DIRECTORY_SEPARATOR, __DIR__); + //$namespace_parts = explode('\\', trim(__NAMESPACE__, '\\')); + + //$namespace_part_count = count($namespace_parts); + + // Check if we still loading from root + //if (array_slice($dir_parts, -$namespace_part_count) === $namespace_parts) + //{ + // $this->working_directory = implode('/', array_slice($dir_parts, 0, -$namespace_part_count)); + //} + //else + //{ + // $this->working_directory = false; + //} + + $this->working_directory = false; + } + } + + if ($this->working_directory !== false) + { + $is_absolute_path = true; + $path = $this->working_directory . '/' . $path; + } + } + + if ($is_absolute_path) + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) + { + $path_prefix = $path[0] . ':'; + $path = substr($path, 2); + } + else + { + $path_prefix = ''; + } + } + + $resolved_path = $this->resolve_path($path, $path_prefix, $is_absolute_path); + if ($resolved_path === false) + { + return false; + } + + if (!@file_exists($resolved_path) || (!@is_dir($resolved_path . '/') && !is_file($resolved_path))) + { + return false; + } + + // Return OS specific directory separators + $resolved = str_replace('/', DIRECTORY_SEPARATOR, $resolved_path); + + // Check for DIRECTORY_SEPARATOR at the end (and remove it!) + if (substr($resolved, -1) === DIRECTORY_SEPARATOR) + { + return substr($resolved, 0, -1); + } + + return $resolved; + } + + /** + * Convert file(s) to \Traversable object + * + * This is the same function as Symfony's toIterator, but that is private + * so we cannot use it. + * + * @param string|array|\Traversable $files filename/list of filenames + * @return \Traversable + */ + protected function to_iterator($files) + { + if (!$files instanceof \Traversable) + { + $files = new \ArrayObject(is_array($files) ? $files : array($files)); + } + + return $files; + } + + /** + * Try to resolve symlinks in path + * + * @param string $path The path to resolve + * @param string $prefix The path prefix (on windows the drive letter) + * @param bool $absolute Whether or not the path is absolute + * @param bool $return_array Whether or not to return path parts + * + * @return string|array|bool returns the resolved path or an array of parts of the path if $return_array is true + * or false if path cannot be resolved + */ + protected function resolve_path($path, $prefix = '', $absolute = false, $return_array = false) + { + if ($return_array) + { + $path = str_replace(DIRECTORY_SEPARATOR, '/', $path); + } + + trim ($path, '/'); + $path_parts = explode('/', $path); + $resolved = array(); + $resolved_path = $prefix; + $file_found = false; + + foreach ($path_parts as $path_part) + { + if ($file_found) + { + return false; + } + + if (empty($path_part) || ($path_part === '.' && ($absolute || !empty($resolved)))) + { + continue; + } + else if ($absolute && $path_part === '..') + { + if (empty($resolved)) + { + // No directories above root + return false; + } + + array_pop($resolved); + $resolved_path = false; + } + else if ($path_part === '..' && !empty($resolved) && !in_array($resolved[count($resolved) - 1], array('.', '..'))) + { + array_pop($resolved); + $resolved_path = false; + } + else + { + if ($resolved_path === false) + { + if (empty($resolved)) + { + $resolved_path = ($absolute) ? $prefix . '/' . $path_part : $path_part; + } + else + { + $tmp_array = $resolved; + if ($absolute) + { + array_unshift($tmp_array, $prefix); + } + + $resolved_path = implode('/', $tmp_array); + } + } + + $current_path = $resolved_path . '/' . $path_part; + + // Resolve symlinks + if (is_link($current_path)) + { + if (!function_exists('readlink')) + { + return false; + } + + $link = readlink($current_path); + + // Is link has an absolute path in it? + if ($this->is_absolute_path($link)) + { + if (defined('PHP_WINDOWS_VERSION_MAJOR')) + { + $prefix = $link[0] . ':'; + $link = substr($link, 2); + } + else + { + $prefix = ''; + } + + $resolved = $this->resolve_path($link, $prefix, true, true); + $absolute = true; + } + else + { + $resolved = $this->resolve_path($resolved_path . '/' . $link, $prefix, $absolute, true); + } + + if (!$resolved) + { + return false; + } + + $resolved_path = false; + } + else if (is_dir($current_path . '/')) + { + $resolved[] = $path_part; + $resolved_path = $current_path; + } + else if (is_file($current_path)) + { + $resolved[] = $path_part; + $resolved_path = $current_path; + $file_found = true; + } + else + { + return false; + } + } + } + + // If at the end of the path there were a .. or . + // we need to build the path again. + // Only doing this when a string is expected in return + if ($resolved_path === false && $return_array === false) + { + if (empty($resolved)) + { + $resolved_path = ($absolute) ? $prefix . '/' : './'; + } + else + { + $tmp_array = $resolved; + if ($absolute) + { + array_unshift($tmp_array, $prefix); + } + + $resolved_path = implode('/', $tmp_array); + } + } + + return ($return_array) ? $resolved : $resolved_path; + } +} diff --git a/phpbb/filesystem/filesystem_interface.php b/phpbb/filesystem/filesystem_interface.php new file mode 100644 index 0000000..1093be2 --- /dev/null +++ b/phpbb/filesystem/filesystem_interface.php @@ -0,0 +1,284 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\filesystem; + +/** + * Interface for phpBB's filesystem service + */ +interface filesystem_interface +{ + /** + * chmod all permissions flag + * + * @var int + */ + const CHMOD_ALL = 7; + + /** + * chmod read permissions flag + * + * @var int + */ + const CHMOD_READ = 4; + + /** + * chmod write permissions flag + * + * @var int + */ + const CHMOD_WRITE = 2; + + /** + * chmod execute permissions flag + * + * @var int + */ + const CHMOD_EXECUTE = 1; + + /** + * Change owner group of files/directories + * + * @param string|array|\Traversable $files The file(s)/directorie(s) to change group + * @param string $group The group that should own the files/directories + * @param bool $recursive If the group should be changed recursively + * @throws \phpbb\filesystem\exception\filesystem_exception the filename which triggered the error can be + * retrieved by filesystem_exception::get_filename() + */ + public function chgrp($files, $group, $recursive = false); + + /** + * Global function for chmodding directories and files for internal use + * + * The function accepts filesystem_interface::CHMOD_ flags in the permission argument + * or the user can specify octal values (or any integer if it makes sense). All directories will have + * an execution bit appended, if the user group (owner, group or other) has any bit specified. + * + * @param string|array|\Traversable $files The file/directory to be chmodded + * @param int $perms Permissions to set + * @param bool $recursive If the permissions should be changed recursively + * @param bool $force_chmod_link Try to apply permissions to symlinks as well + * + * @throws \phpbb\filesystem\exception\filesystem_exception the filename which triggered the error can be + * retrieved by filesystem_exception::get_filename() + */ + public function chmod($files, $perms = null, $recursive = false, $force_chmod_link = false); + + /** + * Change owner group of files/directories + * + * @param string|array|\Traversable $files The file(s)/directorie(s) to change group + * @param string $user The owner user name + * @param bool $recursive Whether change the owner recursively or not + * + * @throws \phpbb\filesystem\exception\filesystem_exception the filename which triggered the error can be + * retrieved by filesystem_exception::get_filename() + */ + public function chown($files, $user, $recursive = false); + + /** + * Eliminates useless . and .. components from specified path. + * + * @param string $path Path to clean + * + * @return string Cleaned path + */ + public function clean_path($path); + + /** + * Copies a file. + * + * This method only copies the file if the origin file is newer than the target file. + * + * By default, if the target already exists, it is not overridden. + * + * @param string $origin_file The original filename + * @param string $target_file The target filename + * @param bool $override Whether to override an existing file or not + * + * @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be copied + */ + public function copy($origin_file, $target_file, $override = false); + + /** + * Atomically dumps content into a file. + * + * @param string $filename The file to be written to. + * @param string $content The data to write into the file. + * + * @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be written + */ + public function dump_file($filename, $content); + + /** + * Checks the existence of files or directories. + * + * @param string|array|\Traversable $files files/directories to check + * + * @return bool Returns true if all files/directories exist, false otherwise + */ + public function exists($files); + + /** + * Checks if a path is absolute or not + * + * @param string $path Path to check + * + * @return bool true if the path is absolute, false otherwise + */ + public function is_absolute_path($path); + + /** + * Checks if files/directories are readable + * + * @param string|array|\Traversable $files files/directories to check + * @param bool $recursive Whether or not directories should be checked recursively + * + * @return bool True when the files/directories are readable, otherwise false. + */ + public function is_readable($files, $recursive = false); + + /** + * Test if a file/directory is writable + * + * @param string|array|\Traversable $files files/directories to perform write test on + * @param bool $recursive Whether or not directories should be checked recursively + * + * @return bool True when the files/directories are writable, otherwise false. + */ + public function is_writable($files, $recursive = false); + + /** + * Given an existing path, convert it to a path relative to a given starting path + * + * @param string $end_path Absolute path of target + * @param string $start_path Absolute path where traversal begins + * + * @return string Path of target relative to starting path + */ + public function make_path_relative($end_path, $start_path); + + /** + * Mirrors a directory to another. + * + * @param string $origin_dir The origin directory + * @param string $target_dir The target directory + * @param \Traversable $iterator A Traversable instance + * @param array $options An array of boolean options + * Valid options are: + * - $options['override'] Whether to override an existing file on copy or not (see copy()) + * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink()) + * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) + * + * @throws \phpbb\filesystem\exception\filesystem_exception When the file cannot be copied. + * The filename which triggered the error can be + * retrieved by filesystem_exception::get_filename() + */ + public function mirror($origin_dir, $target_dir, \Traversable $iterator = null, $options = array()); + + /** + * Creates a directory recursively. + * + * @param string|array|\Traversable $dirs The directory path + * @param int $mode The directory mode + * + * @throws \phpbb\filesystem\exception\filesystem_exception On any directory creation failure + * The filename which triggered the error can be + * retrieved by filesystem_exception::get_filename() + */ + public function mkdir($dirs, $mode = 0777); + + /** + * Global function for chmodding directories and files for internal use + * + * This function determines owner and group whom the file belongs to and user and group of PHP and then set safest possible file permissions. + * The function determines owner and group from common.php file and sets the same to the provided file. + * The function uses bit fields to build the permissions. + * The function sets the appropiate execute bit on directories. + * + * Supported constants representing bit fields are: + * + * filesystem_interface::CHMOD_ALL - all permissions (7) + * filesystem_interface::CHMOD_READ - read permission (4) + * filesystem_interface::CHMOD_WRITE - write permission (2) + * filesystem_interface::CHMOD_EXECUTE - execute permission (1) + * + * NOTE: The function uses POSIX extension and fileowner()/filegroup() functions. If any of them is disabled, this function tries to build proper permissions, by calling is_readable() and is_writable() functions. + * + * @param string|array|\Traversable $file The file/directory to be chmodded + * @param int $perms Permissions to set + * @param bool $recursive If the permissions should be changed recursively + * @param bool $force_chmod_link Try to apply permissions to symlinks as well + * + * @throws \phpbb\filesystem\exception\filesystem_exception the filename which triggered the error can be + * retrieved by filesystem_exception::get_filename() + */ + public function phpbb_chmod($file, $perms = null, $recursive = false, $force_chmod_link = false); + + /** + * A wrapper for PHP's realpath + * + * Try to resolve realpath when PHP's realpath is not available, or + * known to be buggy. + * + * @param string $path Path to resolve + * + * @return string Resolved path + */ + public function realpath($path); + + /** + * Removes files or directories. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove + * + * @throws \phpbb\filesystem\exception\filesystem_exception When removal fails. + * The filename which triggered the error can be + * retrieved by filesystem_exception::get_filename() + */ + public function remove($files); + + /** + * Renames a file or a directory. + * + * @param string $origin The origin filename or directory + * @param string $target The new filename or directory + * @param bool $overwrite Whether to overwrite the target if it already exists + * + * @throws \phpbb\filesystem\exception\filesystem_exception When target file or directory already exists, + * or origin cannot be renamed. + */ + public function rename($origin, $target, $overwrite = false); + + /** + * Creates a symbolic link or copy a directory. + * + * @param string $origin_dir The origin directory path + * @param string $target_dir The symbolic link name + * @param bool $copy_on_windows Whether to copy files if on Windows + * + * @throws \phpbb\filesystem\exception\filesystem_exception When symlink fails + */ + public function symlink($origin_dir, $target_dir, $copy_on_windows = false); + + /** + * Sets access and modification time of file. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create + * @param int $time The touch time as a Unix timestamp + * @param int $access_time The access time as a Unix timestamp + * + * @throws \phpbb\filesystem\exception\filesystem_exception When touch fails + */ + public function touch($files, $time = null, $access_time = null); +} diff --git a/phpbb/finder.php b/phpbb/finder.php new file mode 100644 index 0000000..1f1d931 --- /dev/null +++ b/phpbb/finder.php @@ -0,0 +1,547 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** +* The finder provides a simple way to locate files in the core and a set of extensions +*/ +class finder +{ + protected $extensions; + protected $filesystem; + protected $phpbb_root_path; + protected $cache; + protected $php_ext; + + /** + * The cache variable name used to store $this->cached_queries in $this->cache. + * + * Allows the use of multiple differently configured finders with the same cache. + * @var string + */ + protected $cache_name; + + /** + * An associative array, containing all search parameters set in methods. + * @var array + */ + protected $query; + + /** + * A map from md5 hashes of serialized queries to their previously retrieved + * results. + * @var array + */ + protected $cached_queries; + + /** + * Creates a new finder instance with its dependencies + * + * @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem instance + * @param string $phpbb_root_path Path to the phpbb root directory + * @param \phpbb\cache\service $cache A cache instance or null + * @param string $php_ext php file extension + * @param string $cache_name The name of the cache variable, defaults to + * _ext_finder + */ + public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path = '', \phpbb\cache\service $cache = null, $php_ext = 'php', $cache_name = '_ext_finder') + { + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + $this->cache = $cache; + $this->php_ext = $php_ext; + $this->cache_name = $cache_name; + + $this->query = array( + 'core_path' => false, + 'core_suffix' => false, + 'core_prefix' => false, + 'core_directory' => false, + 'extension_suffix' => false, + 'extension_prefix' => false, + 'extension_directory' => false, + ); + $this->extensions = array(); + + $this->cached_queries = ($this->cache) ? $this->cache->get($this->cache_name) : false; + } + + /** + * Set the array of extensions + * + * @param array $extensions A list of extensions that should be searched aswell + * @param bool $replace_list Should the list be emptied before adding the extensions + * @return \phpbb\finder This object for chaining calls + */ + public function set_extensions(array $extensions, $replace_list = true) + { + if ($replace_list) + { + $this->extensions = array(); + } + + foreach ($extensions as $ext_name) + { + $this->extensions[$ext_name] = $this->phpbb_root_path . 'ext/' . $ext_name . '/'; + } + return $this; + } + + /** + * Sets a core path to be searched in addition to extensions + * + * @param string $core_path The path relative to phpbb_root_path + * @return \phpbb\finder This object for chaining calls + */ + public function core_path($core_path) + { + $this->query['core_path'] = $core_path; + return $this; + } + + /** + * Sets the suffix all files found in extensions and core must match. + * + * There is no default file extension, so to find PHP files only, you will + * have to specify .php as a suffix. However when using get_classes, the .php + * file extension is automatically added to suffixes. + * + * @param string $suffix A filename suffix + * @return \phpbb\finder This object for chaining calls + */ + public function suffix($suffix) + { + $this->core_suffix($suffix); + $this->extension_suffix($suffix); + return $this; + } + + /** + * Sets a suffix all files found in extensions must match + * + * There is no default file extension, so to find PHP files only, you will + * have to specify .php as a suffix. However when using get_classes, the .php + * file extension is automatically added to suffixes. + * + * @param string $extension_suffix A filename suffix + * @return \phpbb\finder This object for chaining calls + */ + public function extension_suffix($extension_suffix) + { + $this->query['extension_suffix'] = $extension_suffix; + return $this; + } + + /** + * Sets a suffix all files found in the core path must match + * + * There is no default file extension, so to find PHP files only, you will + * have to specify .php as a suffix. However when using get_classes, the .php + * file extension is automatically added to suffixes. + * + * @param string $core_suffix A filename suffix + * @return \phpbb\finder This object for chaining calls + */ + public function core_suffix($core_suffix) + { + $this->query['core_suffix'] = $core_suffix; + return $this; + } + + /** + * Sets the prefix all files found in extensions and core must match + * + * @param string $prefix A filename prefix + * @return \phpbb\finder This object for chaining calls + */ + public function prefix($prefix) + { + $this->core_prefix($prefix); + $this->extension_prefix($prefix); + return $this; + } + + /** + * Sets a prefix all files found in extensions must match + * + * @param string $extension_prefix A filename prefix + * @return \phpbb\finder This object for chaining calls + */ + public function extension_prefix($extension_prefix) + { + $this->query['extension_prefix'] = $extension_prefix; + return $this; + } + + /** + * Sets a prefix all files found in the core path must match + * + * @param string $core_prefix A filename prefix + * @return \phpbb\finder This object for chaining calls + */ + public function core_prefix($core_prefix) + { + $this->query['core_prefix'] = $core_prefix; + return $this; + } + + /** + * Sets a directory all files found in extensions and core must be contained in + * + * Automatically sets the core_directory if its value does not differ from + * the current directory. + * + * @param string $directory + * @return \phpbb\finder This object for chaining calls + */ + public function directory($directory) + { + $this->core_directory($directory); + $this->extension_directory($directory); + return $this; + } + + /** + * Sets a directory all files found in extensions must be contained in + * + * @param string $extension_directory + * @return \phpbb\finder This object for chaining calls + */ + public function extension_directory($extension_directory) + { + $this->query['extension_directory'] = $this->sanitise_directory($extension_directory); + return $this; + } + + /** + * Sets a directory all files found in the core path must be contained in + * + * @param string $core_directory + * @return \phpbb\finder This object for chaining calls + */ + public function core_directory($core_directory) + { + $this->query['core_directory'] = $this->sanitise_directory($core_directory); + return $this; + } + + /** + * Removes occurances of /./ and makes sure path ends without trailing slash + * + * @param string $directory A directory pattern + * @return string A cleaned up directory pattern + */ + protected function sanitise_directory($directory) + { + $directory = $this->filesystem->clean_path($directory); + $dir_len = strlen($directory); + + if ($dir_len > 1 && $directory[$dir_len - 1] === '/') + { + $directory = substr($directory, 0, -1); + } + + return $directory; + } + + /** + * Finds classes matching the configured options if they follow phpBB naming rules. + * + * The php file extension is automatically added to suffixes. + * + * Note: If a file is matched but contains a class name not following the + * phpBB naming rules an incorrect class name will be returned. + * + * @param bool $cache Whether the result should be cached + * @return array An array of found class names + */ + public function get_classes($cache = true) + { + $this->query['extension_suffix'] .= '.' . $this->php_ext; + $this->query['core_suffix'] .= '.' . $this->php_ext; + + $files = $this->find($cache, false); + + return $this->get_classes_from_files($files); + } + + /** + * Get class names from a list of files + * + * @param array $files Array of files (from find()) + * @return array Array of class names + */ + public function get_classes_from_files($files) + { + $classes = array(); + foreach ($files as $file => $ext_name) + { + $class = substr($file, 0, -strlen('.' . $this->php_ext)); + if ($ext_name === '/' && preg_match('#^includes/#', $file)) + { + $class = preg_replace('#^includes/#', '', $class); + $classes[] = 'phpbb_' . str_replace('/', '_', $class); + } + else + { + $class = preg_replace('#^ext/#', '', $class); + $classes[] = '\\' . str_replace('/', '\\', $class); + } + } + return $classes; + } + + /** + * Finds all directories matching the configured options + * + * @param bool $cache Whether the result should be cached + * @param bool $extension_keys Whether the result should have extension name as array key + * @return array An array of paths to found directories + */ + public function get_directories($cache = true, $extension_keys = false) + { + return $this->find_with_root_path($cache, true, $extension_keys); + } + + /** + * Finds all files matching the configured options. + * + * @param bool $cache Whether the result should be cached + * @return array An array of paths to found files + */ + public function get_files($cache = true) + { + return $this->find_with_root_path($cache, false); + } + + /** + * A wrapper around the general find which prepends a root path to results + * + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @param bool $extension_keys If true, result will be associative array + * with extension name as key + * @return array An array of paths to found items + */ + protected function find_with_root_path($cache = true, $is_dir = false, $extension_keys = false) + { + $items = $this->find($cache, $is_dir); + + $result = array(); + foreach ($items as $item => $ext_name) + { + if ($extension_keys) + { + $result[$ext_name] = $this->phpbb_root_path . $item; + } + else + { + $result[] = $this->phpbb_root_path . $item; + } + } + + return $result; + } + + /** + * Finds all file system entries matching the configured options + * + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @return array An array of paths to found items + */ + public function find($cache = true, $is_dir = false) + { + $extensions = $this->extensions; + if ($this->query['core_path']) + { + $extensions['/'] = $this->phpbb_root_path . $this->query['core_path']; + } + + $files = array(); + $file_list = $this->find_from_paths($extensions, $cache, $is_dir); + + foreach ($file_list as $file) + { + $files[$file['named_path']] = $file['ext_name']; + } + + return $files; + } + + /** + * Finds all file system entries matching the configured options for one + * specific extension + * + * @param string $extension_name Name of the extension + * @param string $extension_path Relative path to the extension root directory + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @return array An array of paths to found items + */ + public function find_from_extension($extension_name, $extension_path, $cache = true, $is_dir = false) + { + $extensions = array( + $extension_name => $extension_path, + ); + + $files = array(); + $file_list = $this->find_from_paths($extensions, $cache, $is_dir); + + foreach ($file_list as $file) + { + $files[$file['named_path']] = $file['ext_name']; + } + + return $files; + } + + /** + * Finds all file system entries matching the configured options from + * an array of paths + * + * @param array $extensions Array of extensions (name => full relative path) + * @param bool $cache Whether the result should be cached + * @param bool $is_dir Directories will be returned when true, only files + * otherwise + * @return array An array of paths to found items + */ + public function find_from_paths($extensions, $cache = true, $is_dir = false) + { + $this->query['is_dir'] = $is_dir; + $query = md5(serialize($this->query) . serialize($extensions)); + + if (!defined('DEBUG') && $cache && isset($this->cached_queries[$query])) + { + return $this->cached_queries[$query]; + } + + $files = array(); + + foreach ($extensions as $name => $path) + { + $ext_name = $name; + + if (!file_exists($path)) + { + continue; + } + + if ($name === '/') + { + $location = $this->query['core_path']; + $name = ''; + $suffix = $this->query['core_suffix']; + $prefix = $this->query['core_prefix']; + $directory = $this->query['core_directory']; + } + else + { + $location = 'ext/'; + $name .= '/'; + $suffix = $this->query['extension_suffix']; + $prefix = $this->query['extension_prefix']; + $directory = $this->query['extension_directory']; + } + + // match only first directory if leading slash is given + if ($directory === '/') + { + $directory_pattern = '^' . preg_quote(DIRECTORY_SEPARATOR, '#'); + } + else if ($directory && $directory[0] === '/') + { + if (!$is_dir) + { + $path .= substr($directory, 1); + } + $directory_pattern = '^' . preg_quote(str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); + } + else + { + $directory_pattern = preg_quote(DIRECTORY_SEPARATOR . str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR, '#'); + } + if ($is_dir) + { + $directory_pattern .= '$'; + } + $directory_pattern = '#' . $directory_pattern . '#'; + + if (is_dir($path)) + { + $iterator = new \RecursiveIteratorIterator( + new \phpbb\recursive_dot_prefix_filter_iterator( + new \RecursiveDirectoryIterator( + $path, + \FilesystemIterator::SKIP_DOTS + ) + ), + \RecursiveIteratorIterator::SELF_FIRST + ); + + foreach ($iterator as $file_info) + { + $filename = $file_info->getFilename(); + + if ($file_info->isDir() == $is_dir) + { + if ($is_dir) + { + $relative_path = $iterator->getInnerIterator()->getSubPath() . DIRECTORY_SEPARATOR . basename($filename) . DIRECTORY_SEPARATOR; + if ($relative_path[0] !== DIRECTORY_SEPARATOR) + { + $relative_path = DIRECTORY_SEPARATOR . $relative_path; + } + } + else + { + $relative_path = $iterator->getInnerIterator()->getSubPathname(); + if ($directory && $directory[0] === '/') + { + $relative_path = str_replace('/', DIRECTORY_SEPARATOR, $directory) . DIRECTORY_SEPARATOR . $relative_path; + } + else + { + $relative_path = DIRECTORY_SEPARATOR . $relative_path; + } + } + + if ((!$suffix || substr($relative_path, -strlen($suffix)) === $suffix) && + (!$prefix || substr($filename, 0, strlen($prefix)) === $prefix) && + (!$directory || preg_match($directory_pattern, $relative_path))) + { + $files[] = array( + 'named_path' => str_replace(DIRECTORY_SEPARATOR, '/', $location . $name . substr($relative_path, 1)), + 'ext_name' => $ext_name, + 'path' => str_replace(array(DIRECTORY_SEPARATOR, $this->phpbb_root_path), array('/', ''), $file_info->getPath()) . '/', + 'filename' => $filename, + ); + } + } + } + } + } + + if ($cache && $this->cache) + { + $this->cached_queries[$query] = $files; + $this->cache->put($this->cache_name, $this->cached_queries); + } + + return $files; + } +} diff --git a/phpbb/group/helper.php b/phpbb/group/helper.php new file mode 100644 index 0000000..5befddf --- /dev/null +++ b/phpbb/group/helper.php @@ -0,0 +1,40 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\group; + +class helper +{ + /** @var \phpbb\language\language */ + protected $language; + + /** + * Constructor + * + * @param \phpbb\language\language $language Language object + */ + public function __construct(\phpbb\language\language $language) + { + $this->language = $language; + } + + /** + * @param $group_name string The stored group name + * + * @return string Group name or translated group name if it exists + */ + public function get_name($group_name) + { + return $this->language->is_set('G_' . utf8_strtoupper($group_name)) ? $this->language->lang('G_' . utf8_strtoupper($group_name)) : $group_name; + } +} diff --git a/phpbb/groupposition/exception.php b/phpbb/groupposition/exception.php new file mode 100644 index 0000000..956c723 --- /dev/null +++ b/phpbb/groupposition/exception.php @@ -0,0 +1,18 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\groupposition; + +class exception extends \Exception +{ +} diff --git a/phpbb/groupposition/groupposition_interface.php b/phpbb/groupposition/groupposition_interface.php new file mode 100644 index 0000000..3bd3fcc --- /dev/null +++ b/phpbb/groupposition/groupposition_interface.php @@ -0,0 +1,80 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\groupposition; + +/** +* Interface to manage group positions in various places of phpbb +* +* The interface provides simple methods to add, delete and move a group +*/ +interface groupposition_interface +{ + /** + * Returns the value for a given group, if the group exists. + * @param int $group_id group_id of the group to be selected + * @return int position of the group + */ + public function get_group_value($group_id); + + /** + * Get number of groups displayed + * + * @return int value of the last item displayed + */ + public function get_group_count(); + + /** + * Addes a group by group_id + * + * @param int $group_id group_id of the group to be added + * @return bool True if the group was added successfully + */ + public function add_group($group_id); + + /** + * Deletes a group by group_id + * + * @param int $group_id group_id of the group to be deleted + * @param bool $skip_group Skip setting the value for this group, to save the query, when you need to update it anyway. + * @return bool True if the group was deleted successfully + */ + public function delete_group($group_id, $skip_group = false); + + /** + * Moves a group up by group_id + * + * @param int $group_id group_id of the group to be moved + * @return bool True if the group was moved successfully + */ + public function move_up($group_id); + + /** + * Moves a group down by group_id + * + * @param int $group_id group_id of the group to be moved + * @return bool True if the group was moved successfully + */ + public function move_down($group_id); + + /** + * Moves a group up/down + * + * @param int $group_id group_id of the group to be moved + * @param int $delta number of steps: + * - positive = move up + * - negative = move down + * @return bool True if the group was moved successfully + */ + public function move($group_id, $delta); +} diff --git a/phpbb/groupposition/legend.php b/phpbb/groupposition/legend.php new file mode 100644 index 0000000..efea338 --- /dev/null +++ b/phpbb/groupposition/legend.php @@ -0,0 +1,243 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\groupposition; + +/** +* Legend group position class +* +* group_legend is an ascending list 1, 2, ..., n for groups which are displayed. 1 is the first group, n the last. +* If the value is 0 (self::GROUP_DISABLED) the group is not displayed. +*/ +class legend implements \phpbb\groupposition\groupposition_interface +{ + /** + * Group is not displayed + */ + const GROUP_DISABLED = 0; + + /** + * Database object + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db Database object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\user $user) + { + $this->db = $db; + $this->user = $user; + } + + /** + * Returns the group_legend for a given group, if the group exists. + * + * @param int $group_id group_id of the group to be selected + * @return int position of the group + * @throws \phpbb\groupposition\exception + */ + public function get_group_value($group_id) + { + $sql = 'SELECT group_legend + FROM ' . GROUPS_TABLE . ' + WHERE group_id = ' . (int) $group_id; + $result = $this->db->sql_query($sql); + $current_value = $this->db->sql_fetchfield('group_legend'); + $this->db->sql_freeresult($result); + + if ($current_value === false) + { + // Group not found. + throw new \phpbb\groupposition\exception('NO_GROUP'); + } + + return (int) $current_value; + } + + /** + * Get number of groups, displayed on the legend + * + * @return int value of the last item displayed + */ + public function get_group_count() + { + $sql = 'SELECT group_legend + FROM ' . GROUPS_TABLE . ' + ORDER BY group_legend DESC'; + $result = $this->db->sql_query_limit($sql, 1); + $group_count = (int) $this->db->sql_fetchfield('group_legend'); + $this->db->sql_freeresult($result); + + return $group_count; + } + + /** + * {@inheritDoc} + */ + public function add_group($group_id) + { + $current_value = $this->get_group_value($group_id); + + if ($current_value == self::GROUP_DISABLED) + { + // Group is currently not displayed, add it at the end. + $next_value = 1 + $this->get_group_count(); + + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = ' . $next_value . ' + WHERE group_legend = ' . self::GROUP_DISABLED . ' + AND group_id = ' . (int) $group_id; + $this->db->sql_query($sql); + return true; + } + + return false; + } + + /** + * Deletes a group by setting the field to self::GROUP_DISABLED and closing the gap in the list. + * + * @param int $group_id group_id of the group to be deleted + * @param bool $skip_group Skip setting the value for this group, to save the query, when you need to update it anyway. + * @return bool True if the group was deleted successfully + */ + public function delete_group($group_id, $skip_group = false) + { + $current_value = $this->get_group_value($group_id); + + if ($current_value != self::GROUP_DISABLED) + { + $this->db->sql_transaction('begin'); + + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = group_legend - 1 + WHERE group_legend > ' . $current_value; + $this->db->sql_query($sql); + + if (!$skip_group) + { + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = ' . self::GROUP_DISABLED . ' + WHERE group_id = ' . (int) $group_id; + $this->db->sql_query($sql); + } + + $this->db->sql_transaction('commit'); + + return true; + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function move_up($group_id) + { + return $this->move($group_id, 1); + } + + /** + * {@inheritDoc} + */ + public function move_down($group_id) + { + return $this->move($group_id, -1); + } + + /** + * {@inheritDoc} + */ + public function move($group_id, $delta) + { + $delta = (int) $delta; + if (!$delta) + { + return false; + } + + $move_up = ($delta > 0) ? true : false; + $current_value = $this->get_group_value($group_id); + + if ($current_value != self::GROUP_DISABLED) + { + $this->db->sql_transaction('begin'); + + // First we move all groups between our current value and the target value up/down 1, + // so we have a gap for our group to move. + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = group_legend' . (($move_up) ? ' + 1' : ' - 1') . ' + WHERE group_legend > ' . self::GROUP_DISABLED . ' + AND group_legend' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' + AND group_legend' . (($move_up) ? ' < ' : ' > ') . $current_value; + $this->db->sql_query($sql); + + // Because there might be fewer groups above/below the group than we wanted to move, + // we use the number of changed groups, to update the group. + $delta = (int) $this->db->sql_affectedrows(); + + if ($delta) + { + // And now finally, when we moved some other groups and built a gap, + // we can move the desired group to it. + $sql = 'UPDATE ' . GROUPS_TABLE . ' + SET group_legend = group_legend ' . (($move_up) ? ' - ' : ' + ') . $delta . ' + WHERE group_id = ' . (int) $group_id; + $this->db->sql_query($sql); + + $this->db->sql_transaction('commit'); + + return true; + } + + $this->db->sql_transaction('commit'); + } + + return false; + } + + /** + * Get group type language var + * + * @param int $group_type group_type from the groups-table + * @return string name of the language variable for the given group-type. + */ + static public function group_type_language($group_type) + { + switch ($group_type) + { + case GROUP_OPEN: + return 'GROUP_REQUEST'; + case GROUP_CLOSED: + return 'GROUP_CLOSED'; + case GROUP_HIDDEN: + return 'GROUP_HIDDEN'; + case GROUP_SPECIAL: + return 'GROUP_SPECIAL'; + case GROUP_FREE: + return 'GROUP_OPEN'; + } + } +} diff --git a/phpbb/groupposition/teampage.php b/phpbb/groupposition/teampage.php new file mode 100644 index 0000000..2985c51 --- /dev/null +++ b/phpbb/groupposition/teampage.php @@ -0,0 +1,597 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\groupposition; + +/** +* Teampage group position class +* +* Teampage position is an ascending list 1, 2, ..., n for items which are displayed. 1 is the first item, n the last. +*/ +class teampage implements \phpbb\groupposition\groupposition_interface +{ + /** + * Group is not displayed + */ + const GROUP_DISABLED = 0; + + /** + * No parent item + */ + const NO_PARENT = 0; + + /** + * Database object + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Cache object + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db Database object + * @param \phpbb\user $user User object + * @param \phpbb\cache\driver\driver_interface $cache Cache object + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\user $user, \phpbb\cache\driver\driver_interface $cache) + { + $this->db = $db; + $this->user = $user; + $this->cache = $cache; + } + + /** + * Returns the teampage position for a given group, if the group exists. + * + * @param int $group_id group_id of the group to be selected + * @return int position of the group + * @throws \phpbb\groupposition\exception + */ + public function get_group_value($group_id) + { + // The join is required to ensure that the group itself exists + $sql = 'SELECT g.group_id, t.teampage_position + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . TEAMPAGE_TABLE . ' t + ON (t.group_id = g.group_id) + WHERE g.group_id = ' . (int) $group_id; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row === false) + { + // Group not found. + throw new \phpbb\groupposition\exception('NO_GROUP'); + } + + return (int) $row['teampage_position']; + } + + /** + * Returns the row for a given group, if the group exists. + * + * @param int $group_id group_id of the group to be selected + * @return array Data row of the group + * @throws \phpbb\groupposition\exception + */ + public function get_group_values($group_id) + { + // The join is required to ensure that the group itself exists + $sql = 'SELECT * + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . TEAMPAGE_TABLE . ' t + ON (t.group_id = g.group_id) + WHERE g.group_id = ' . (int) $group_id; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row === false) + { + // Group not found. + throw new \phpbb\groupposition\exception('NO_GROUP'); + } + + return $row; + } + + /** + * Returns the teampage position for a given teampage item, if the item exists. + * + * @param int $teampage_id Teampage_id of the selected item + * @return int Teampage position of the item + * @throws \phpbb\groupposition\exception + */ + public function get_teampage_value($teampage_id) + { + $sql = 'SELECT teampage_position + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_id = ' . (int) $teampage_id; + $result = $this->db->sql_query($sql); + $current_value = $this->db->sql_fetchfield('teampage_position'); + $this->db->sql_freeresult($result); + + if ($current_value === false) + { + // Group not found. + throw new \phpbb\groupposition\exception('NO_GROUP'); + } + + return (int) $current_value; + } + + /** + * Returns the teampage row for a given teampage item, if the item exists. + * + * @param int $teampage_id Teampage_id of the selected item + * @return array Teampage row of the item + * @throws \phpbb\groupposition\exception + */ + public function get_teampage_values($teampage_id) + { + $sql = 'SELECT teampage_position, teampage_parent + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_id = ' . (int) $teampage_id; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if ($row === false) + { + // Group not found. + throw new \phpbb\groupposition\exception('NO_GROUP'); + } + + return $row; + } + + + /** + * {@inheritDoc} + */ + public function get_group_count() + { + $sql = 'SELECT teampage_position + FROM ' . TEAMPAGE_TABLE . ' + ORDER BY teampage_position DESC'; + $result = $this->db->sql_query_limit($sql, 1); + $group_count = (int) $this->db->sql_fetchfield('teampage_position'); + $this->db->sql_freeresult($result); + + return $group_count; + } + + /** + * {@inheritDoc} + */ + public function add_group($group_id) + { + return $this->add_group_teampage($group_id, self::NO_PARENT); + } + + /** + * Adds a group by group_id + * + * @param int $group_id group_id of the group to be added + * @param int $parent_id Teampage ID of the parent item + * @return bool True if the group was added successfully + */ + public function add_group_teampage($group_id, $parent_id) + { + $current_value = $this->get_group_value($group_id); + + if ($current_value == self::GROUP_DISABLED) + { + if ($parent_id != self::NO_PARENT) + { + // Check, whether the given parent is a category + $sql = 'SELECT teampage_id + FROM ' . TEAMPAGE_TABLE . ' + WHERE group_id = 0 + AND teampage_id = ' . (int) $parent_id; + $result = $this->db->sql_query_limit($sql, 1); + $parent_is_category = (bool) $this->db->sql_fetchfield('teampage_id'); + $this->db->sql_freeresult($result); + + if ($parent_is_category) + { + // Get value of last child from this parent and add group there + $sql = 'SELECT teampage_position + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_parent = ' . (int) $parent_id . ' + OR teampage_id = ' . (int) $parent_id . ' + ORDER BY teampage_position DESC'; + $result = $this->db->sql_query_limit($sql, 1); + $new_position = (int) $this->db->sql_fetchfield('teampage_position'); + $this->db->sql_freeresult($result); + + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position + 1 + WHERE teampage_position > ' . $new_position; + $this->db->sql_query($sql); + } + } + else + { + // Add group at the end + $new_position = $this->get_group_count(); + } + + $sql_ary = array( + 'group_id' => $group_id, + 'teampage_position' => $new_position + 1, + 'teampage_parent' => $parent_id, + ); + + $sql = 'INSERT INTO ' . TEAMPAGE_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return true; + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Adds a new category + * + * @param string $category_name Name of the category to be added + * @return bool True if the category was added successfully + */ + public function add_category_teampage($category_name) + { + if ($category_name === '') + { + return false; + } + + $num_entries = $this->get_group_count(); + + $sql_ary = array( + 'group_id' => 0, + 'teampage_position' => $num_entries + 1, + 'teampage_parent' => 0, + 'teampage_name' => truncate_string($category_name, 255, 255), + ); + + $sql = 'INSERT INTO ' . TEAMPAGE_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return true; + } + + /** + * Deletes a group from the list and closes the gap in the position list. + * + * @param int $group_id group_id of the group to be deleted + * @param bool $skip_group Skip setting the value for this group, to save the query, when you need to update it anyway. + * @return bool True if the group was deleted successfully + */ + public function delete_group($group_id, $skip_group = false) + { + $current_value = $this->get_group_value($group_id); + + if ($current_value != self::GROUP_DISABLED) + { + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position - 1 + WHERE teampage_position > ' . $current_value; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . TEAMPAGE_TABLE . ' + WHERE group_id = ' . $group_id; + $this->db->sql_query($sql); + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return true; + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Deletes an item from the list and closes the gap in the position list. + * + * @param int $teampage_id teampage_id of the item to be deleted + * @param bool $skip_group Skip setting the group to GROUP_DISABLED, to save the query, when you need to update it anyway. + * @return bool True if the item was deleted successfully + */ + public function delete_teampage($teampage_id, $skip_group = false) + { + $current_value = $this->get_teampage_value($teampage_id); + + if ($current_value != self::GROUP_DISABLED) + { + $sql = 'DELETE FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_id = ' . $teampage_id . ' + OR teampage_parent = ' . $teampage_id; + $this->db->sql_query($sql); + + $delta = (int) $this->db->sql_affectedrows(); + + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position - ' . $delta . ' + WHERE teampage_position > ' . $current_value; + $this->db->sql_query($sql); + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return true; + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * {@inheritDoc} + */ + public function move_up($group_id) + { + return $this->move($group_id, 1); + } + + /** + * Moves an item up by teampage_id + * + * @param int $teampage_id teampage_id of the item to be move + * @return bool True if the group was moved successfully + */ + public function move_up_teampage($teampage_id) + { + return $this->move_teampage($teampage_id, 1); + } + + /** + * {@inheritDoc} + */ + public function move_down($group_id) + { + return $this->move($group_id, -1); + } + + /** + * Moves an item down by teampage_id + * + * @param int $teampage_id teampage_id of the item to be moved + * @return bool True if the group was moved successfully + */ + public function move_down_teampage($teampage_id) + { + return $this->move_teampage($teampage_id, -1); + } + + /** + * {@inheritDoc} + */ + public function move($group_id, $delta) + { + $delta = (int) $delta; + if (!$delta) + { + return false; + } + + $move_up = ($delta > 0) ? true : false; + $data = $this->get_group_values($group_id); + + $current_value = (int) $data['teampage_position']; + if ($current_value != self::GROUP_DISABLED) + { + $this->db->sql_transaction('begin'); + + if (!$move_up && $data['teampage_parent'] == self::NO_PARENT) + { + // If we move items down, we need to grab the one sibling more, + // so we do not ignore the children of the previous sibling. + // We will remove the additional sibling later on. + $delta = abs($delta) + 1; + } + + $sql = 'SELECT teampage_position + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_parent = ' . (int) $data['teampage_parent'] . ' + AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' + ORDER BY teampage_position' . (($move_up) ? ' DESC' : ' ASC'); + $result = $this->db->sql_query_limit($sql, $delta); + + $sibling_count = 0; + $sibling_limit = $delta; + + // Reset the delta, as we recalculate the new real delta + $delta = 0; + while ($row = $this->db->sql_fetchrow($result)) + { + $sibling_count++; + $delta = $current_value - $row['teampage_position']; + + if (!$move_up && $data['teampage_parent'] == self::NO_PARENT && $sibling_count == $sibling_limit) + { + // Remove the additional sibling we added previously + $delta++; + } + } + $this->db->sql_freeresult($result); + + if ($delta) + { + // First we move all items between our current value and the target value up/down 1, + // so we have a gap for our item to move. + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position' . (($move_up) ? ' + 1' : ' - 1') . ' + WHERE teampage_position' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' + AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value; + $this->db->sql_query($sql); + + // And now finally, when we moved some other items and built a gap, + // we can move the desired item to it. + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position ' . (($move_up) ? ' - ' : ' + ') . abs($delta) . ' + WHERE group_id = ' . (int) $group_id; + $this->db->sql_query($sql); + + $this->db->sql_transaction('commit'); + $this->cache->destroy('sql', TEAMPAGE_TABLE); + + return true; + } + + $this->db->sql_transaction('commit'); + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Moves an item up/down + * + * @param int $teampage_id teampage_id of the item to be moved + * @param int $delta number of steps: + * - positive = move up + * - negative = move down + * @return bool True if the group was moved successfully + */ + public function move_teampage($teampage_id, $delta) + { + $delta = (int) $delta; + if (!$delta) + { + return false; + } + + $move_up = ($delta > 0) ? true : false; + $data = $this->get_teampage_values($teampage_id); + + $current_value = (int) $data['teampage_position']; + if ($current_value != self::GROUP_DISABLED) + { + $this->db->sql_transaction('begin'); + + if (!$move_up && $data['teampage_parent'] == self::NO_PARENT) + { + // If we move items down, we need to grab the one sibling more, + // so we do not ignore the children of the previous sibling. + // We will remove the additional sibling later on. + $delta = abs($delta) + 1; + } + + $sql = 'SELECT teampage_id, teampage_position + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_parent = ' . (int) $data['teampage_parent'] . ' + AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' + ORDER BY teampage_position' . (($move_up) ? ' DESC' : ' ASC'); + $result = $this->db->sql_query_limit($sql, $delta); + + $sibling_count = 0; + $sibling_limit = $delta; + + // Reset the delta, as we recalculate the new real delta + $delta = 0; + while ($row = $this->db->sql_fetchrow($result)) + { + $sibling_count++; + $delta = $current_value - $row['teampage_position']; + + // Remove the additional sibling we added previously + // But only, if we included it, this is not be the case + // when we reached the end of our list + if (!$move_up && $data['teampage_parent'] == self::NO_PARENT && $sibling_count == $sibling_limit) + { + $delta++; + } + } + $this->db->sql_freeresult($result); + + if ($delta) + { + $sql = 'SELECT COUNT(teampage_id) as num_items + FROM ' . TEAMPAGE_TABLE . ' + WHERE teampage_id = ' . (int) $teampage_id . ' + OR teampage_parent = ' . (int) $teampage_id; + $result = $this->db->sql_query($sql); + $num_items = (int) $this->db->sql_fetchfield('num_items'); + $this->db->sql_freeresult($result); + + // First we move all items between our current value and the target value up/down 1, + // so we have a gap for our item to move. + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position' . (($move_up) ? ' + ' : ' - ') . $num_items . ' + WHERE teampage_position' . (($move_up) ? ' >= ' : ' <= ') . ($current_value - $delta) . ' + AND teampage_position' . (($move_up) ? ' < ' : ' > ') . $current_value . ' + AND NOT (teampage_id = ' . (int) $teampage_id . ' + OR teampage_parent = ' . (int) $teampage_id . ')'; + $this->db->sql_query($sql); + + $delta = (!$move_up && $data['teampage_parent'] == self::NO_PARENT) ? (abs($delta) - ($num_items - 1)) : abs($delta); + + // And now finally, when we moved some other items and built a gap, + // we can move the desired item to it. + $sql = 'UPDATE ' . TEAMPAGE_TABLE . ' + SET teampage_position = teampage_position ' . (($move_up) ? ' - ' : ' + ') . $delta . ' + WHERE teampage_id = ' . (int) $teampage_id . ' + OR teampage_parent = ' . (int) $teampage_id; + $this->db->sql_query($sql); + + $this->db->sql_transaction('commit'); + $this->cache->destroy('sql', TEAMPAGE_TABLE); + + return true; + } + + $this->db->sql_transaction('commit'); + } + + $this->cache->destroy('sql', TEAMPAGE_TABLE); + return false; + } + + /** + * Get group type language var + * + * @param int $group_type group_type from the groups-table + * @return string name of the language variable for the given group-type. + */ + static public function group_type_language($group_type) + { + switch ($group_type) + { + case GROUP_OPEN: + return 'GROUP_REQUEST'; + case GROUP_CLOSED: + return 'GROUP_CLOSED'; + case GROUP_HIDDEN: + return 'GROUP_HIDDEN'; + case GROUP_SPECIAL: + return 'GROUP_SPECIAL'; + case GROUP_FREE: + return 'GROUP_OPEN'; + } + } +} diff --git a/phpbb/help/controller/bbcode.php b/phpbb/help/controller/bbcode.php new file mode 100644 index 0000000..e16f990 --- /dev/null +++ b/phpbb/help/controller/bbcode.php @@ -0,0 +1,85 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\help\controller; + +/** + * BBCode help page + */ +class bbcode extends controller +{ + /** + * @return string The title of the page + */ + public function display() + { + $this->language->add_lang('help/bbcode'); + + $this->manager->add_block( + 'HELP_BBCODE_BLOCK_INTRO', + false, + array( + 'HELP_BBCODE_INTRO_BBCODE_QUESTION' => 'HELP_BBCODE_INTRO_BBCODE_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_BBCODE_BLOCK_TEXT', + false, + array( + 'HELP_BBCODE_TEXT_BASIC_QUESTION' => 'HELP_BBCODE_TEXT_BASIC_ANSWER', + 'HELP_BBCODE_TEXT_COLOR_QUESTION' => 'HELP_BBCODE_TEXT_COLOR_ANSWER', + 'HELP_BBCODE_TEXT_COMBINE_QUESTION' => 'HELP_BBCODE_TEXT_COMBINE_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_BBCODE_BLOCK_QUOTES', + false, + array( + 'HELP_BBCODE_QUOTES_TEXT_QUESTION' => 'HELP_BBCODE_QUOTES_TEXT_ANSWER', + 'HELP_BBCODE_QUOTES_CODE_QUESTION' => 'HELP_BBCODE_QUOTES_CODE_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_BBCODE_BLOCK_LISTS', + false, + array( + 'HELP_BBCODE_LISTS_UNORDERER_QUESTION' => 'HELP_BBCODE_LISTS_UNORDERER_ANSWER', + 'HELP_BBCODE_LISTS_ORDERER_QUESTION' => 'HELP_BBCODE_LISTS_ORDERER_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_BBCODE_BLOCK_LINKS', + true, + array( + 'HELP_BBCODE_LINKS_BASIC_QUESTION' => 'HELP_BBCODE_LINKS_BASIC_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_BBCODE_BLOCK_IMAGES', + false, + array( + 'HELP_BBCODE_IMAGES_BASIC_QUESTION' => 'HELP_BBCODE_IMAGES_BASIC_ANSWER', + 'HELP_BBCODE_IMAGES_ATTACHMENT_QUESTION' => 'HELP_BBCODE_IMAGES_ATTACHMENT_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_BBCODE_BLOCK_OTHERS', + false, + array( + 'HELP_BBCODE_OTHERS_CUSTOM_QUESTION' => 'HELP_BBCODE_OTHERS_CUSTOM_ANSWER', + ) + ); + + return $this->language->lang('BBCODE_GUIDE'); + } +} diff --git a/phpbb/help/controller/controller.php b/phpbb/help/controller/controller.php new file mode 100644 index 0000000..2949420 --- /dev/null +++ b/phpbb/help/controller/controller.php @@ -0,0 +1,76 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\help\controller; + +/** + * BBCode help page + */ +abstract class controller +{ + /** @var \phpbb\controller\helper */ + protected $helper; + + /** @var \phpbb\help\manager */ + protected $manager; + + /** @var \phpbb\template\template */ + protected $template; + + /** @var \phpbb\language\language */ + protected $language; + + /** @var string */ + protected $root_path; + + /** @var string */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\controller\helper $helper + * @param \phpbb\help\manager $manager + * @param \phpbb\template\template $template + * @param \phpbb\language\language $language + * @param string $root_path + * @param string $php_ext + */ + public function __construct(\phpbb\controller\helper $helper, \phpbb\help\manager $manager, \phpbb\template\template $template, \phpbb\language\language $language, $root_path, $php_ext) + { + $this->helper = $helper; + $this->manager = $manager; + $this->template = $template; + $this->language = $language; + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * @return string + */ + abstract protected function display(); + + public function handle() + { + $title = $this->display(); + + $this->template->assign_vars(array( + 'L_FAQ_TITLE' => $title, + 'S_IN_FAQ' => true, + )); + + make_jumpbox(append_sid("{$this->root_path}viewforum.{$this->php_ext}")); + return $this->helper->render('faq_body.html', $title); + } +} diff --git a/phpbb/help/controller/faq.php b/phpbb/help/controller/faq.php new file mode 100644 index 0000000..5e45cfe --- /dev/null +++ b/phpbb/help/controller/faq.php @@ -0,0 +1,165 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\help\controller; + +/** + * FAQ help page + */ +class faq extends controller +{ + /** + * @return string The title of the page + */ + public function display() + { + $this->language->add_lang('help/faq'); + + $this->manager->add_block( + 'HELP_FAQ_BLOCK_LOGIN', + false, + array( + 'HELP_FAQ_LOGIN_REGISTER_QUESTION' => 'HELP_FAQ_LOGIN_REGISTER_ANSWER', + 'HELP_FAQ_LOGIN_COPPA_QUESTION' => 'HELP_FAQ_LOGIN_COPPA_ANSWER', + 'HELP_FAQ_LOGIN_CANNOT_REGISTER_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_REGISTER_ANSWER', + 'HELP_FAQ_LOGIN_REGISTER_CONFIRM_QUESTION' => 'HELP_FAQ_LOGIN_REGISTER_CONFIRM_ANSWER', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANSWER', + 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_QUESTION' => 'HELP_FAQ_LOGIN_CANNOT_LOGIN_ANYMORE_ANSWER', + 'HELP_FAQ_LOGIN_LOST_PASSWORD_QUESTION' => 'HELP_FAQ_LOGIN_LOST_PASSWORD_ANSWER', + 'HELP_FAQ_LOGIN_AUTO_LOGOUT_QUESTION' => 'HELP_FAQ_LOGIN_AUTO_LOGOUT_ANSWER', + 'HELP_FAQ_LOGIN_DELETE_COOKIES_QUESTION' => 'HELP_FAQ_LOGIN_DELETE_COOKIES_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_USERSETTINGS', + false, + array( + 'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_QUESTION' => 'HELP_FAQ_USERSETTINGS_CHANGE_SETTINGS_ANSWER', + 'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_QUESTION' => 'HELP_FAQ_USERSETTINGS_HIDE_ONLINE_ANSWER', + 'HELP_FAQ_USERSETTINGS_TIMEZONE_QUESTION' => 'HELP_FAQ_USERSETTINGS_TIMEZONE_ANSWER', + 'HELP_FAQ_USERSETTINGS_SERVERTIME_QUESTION' => 'HELP_FAQ_USERSETTINGS_SERVERTIME_ANSWER', + 'HELP_FAQ_USERSETTINGS_LANGUAGE_QUESTION' => 'HELP_FAQ_USERSETTINGS_LANGUAGE_ANSWER', + 'HELP_FAQ_USERSETTINGS_AVATAR_QUESTION' => 'HELP_FAQ_USERSETTINGS_AVATAR_ANSWER', + 'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_QUESTION' => 'HELP_FAQ_USERSETTINGS_AVATAR_DISPLAY_ANSWER', + 'HELP_FAQ_USERSETTINGS_RANK_QUESTION' => 'HELP_FAQ_USERSETTINGS_RANK_ANSWER', + 'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_QUESTION' => 'HELP_FAQ_USERSETTINGS_EMAIL_LOGIN_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_POSTING', + false, + array( + 'HELP_FAQ_POSTING_CREATE_QUESTION' => 'HELP_FAQ_POSTING_CREATE_ANSWER', + 'HELP_FAQ_POSTING_EDIT_DELETE_QUESTION' => 'HELP_FAQ_POSTING_EDIT_DELETE_ANSWER', + 'HELP_FAQ_POSTING_SIGNATURE_QUESTION' => 'HELP_FAQ_POSTING_SIGNATURE_ANSWER', + 'HELP_FAQ_POSTING_POLL_CREATE_QUESTION' => 'HELP_FAQ_POSTING_POLL_CREATE_ANSWER', + 'HELP_FAQ_POSTING_POLL_ADD_QUESTION' => 'HELP_FAQ_POSTING_POLL_ADD_ANSWER', + 'HELP_FAQ_POSTING_POLL_EDIT_QUESTION' => 'HELP_FAQ_POSTING_POLL_EDIT_ANSWER', + 'HELP_FAQ_POSTING_FORUM_RESTRICTED_QUESTION' => 'HELP_FAQ_POSTING_FORUM_RESTRICTED_ANSWER', + 'HELP_FAQ_POSTING_NO_ATTACHMENTS_QUESTION' => 'HELP_FAQ_POSTING_NO_ATTACHMENTS_ANSWER', + 'HELP_FAQ_POSTING_WARNING_QUESTION' => 'HELP_FAQ_POSTING_WARNING_ANSWER', + 'HELP_FAQ_POSTING_REPORT_QUESTION' => 'HELP_FAQ_POSTING_REPORT_ANSWER', + 'HELP_FAQ_POSTING_DRAFT_QUESTION' => 'HELP_FAQ_POSTING_DRAFT_ANSWER', + 'HELP_FAQ_POSTING_QUEUE_QUESTION' => 'HELP_FAQ_POSTING_QUEUE_ANSWER', + 'HELP_FAQ_POSTING_BUMP_QUESTION' => 'HELP_FAQ_POSTING_BUMP_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_FORMATTING', + false, + array( + 'HELP_FAQ_FORMATTING_BBOCDE_QUESTION' => 'HELP_FAQ_FORMATTING_BBOCDE_ANSWER', + 'HELP_FAQ_FORMATTING_HTML_QUESTION' => 'HELP_FAQ_FORMATTING_HTML_ANSWER', + 'HELP_FAQ_FORMATTING_SMILIES_QUESTION' => 'HELP_FAQ_FORMATTING_SMILIES_ANSWER', + 'HELP_FAQ_FORMATTING_IMAGES_QUESTION' => 'HELP_FAQ_FORMATTING_IMAGES_ANSWER', + 'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_QUESTION' => 'HELP_FAQ_FORMATTING_GLOBAL_ANNOUNCE_ANSWER', + 'HELP_FAQ_FORMATTING_ANNOUNCEMENT_QUESTION' => 'HELP_FAQ_FORMATTING_ANNOUNCEMENT_ANSWER', + 'HELP_FAQ_FORMATTING_STICKIES_QUESTION' => 'HELP_FAQ_FORMATTING_STICKIES_ANSWER', + 'HELP_FAQ_FORMATTING_LOCKED_QUESTION' => 'HELP_FAQ_FORMATTING_LOCKED_ANSWER', + 'HELP_FAQ_FORMATTING_ICONS_QUESTION' => 'HELP_FAQ_FORMATTING_ICONS_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_GROUPS', + true, + array( + 'HELP_FAQ_GROUPS_ADMINISTRATORS_QUESTION' => 'HELP_FAQ_GROUPS_ADMINISTRATORS_ANSWER', + 'HELP_FAQ_GROUPS_MODERATORS_QUESTION' => 'HELP_FAQ_GROUPS_MODERATORS_ANSWER', + 'HELP_FAQ_GROUPS_USERGROUPS_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_ANSWER', + 'HELP_FAQ_GROUPS_USERGROUPS_JOIN_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_JOIN_ANSWER', + 'HELP_FAQ_GROUPS_USERGROUPS_LEAD_QUESTION' => 'HELP_FAQ_GROUPS_USERGROUPS_LEAD_ANSWER', + 'HELP_FAQ_GROUPS_COLORS_QUESTION' => 'HELP_FAQ_GROUPS_COLORS_ANSWER', + 'HELP_FAQ_GROUPS_DEFAULT_QUESTION' => 'HELP_FAQ_GROUPS_DEFAULT_ANSWER', + 'HELP_FAQ_GROUPS_TEAM_QUESTION' => 'HELP_FAQ_GROUPS_TEAM_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_PMS', + false, + array( + 'HELP_FAQ_PMS_CANNOT_SEND_QUESTION' => 'HELP_FAQ_PMS_CANNOT_SEND_ANSWER', + 'HELP_FAQ_PMS_UNWANTED_QUESTION' => 'HELP_FAQ_PMS_UNWANTED_ANSWER', + 'HELP_FAQ_PMS_SPAM_QUESTION' => 'HELP_FAQ_PMS_SPAM_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_FRIENDS', + false, + array( + 'HELP_FAQ_FRIENDS_BASIC_QUESTION' => 'HELP_FAQ_FRIENDS_BASIC_ANSWER', + 'HELP_FAQ_FRIENDS_MANAGE_QUESTION' => 'HELP_FAQ_FRIENDS_MANAGE_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_SEARCH', + false, + array( + 'HELP_FAQ_SEARCH_FORUM_QUESTION' => 'HELP_FAQ_SEARCH_FORUM_ANSWER', + 'HELP_FAQ_SEARCH_NO_RESULT_QUESTION' => 'HELP_FAQ_SEARCH_NO_RESULT_ANSWER', + 'HELP_FAQ_SEARCH_BLANK_QUESTION' => 'HELP_FAQ_SEARCH_BLANK_ANSWER', + 'HELP_FAQ_SEARCH_MEMBERS_QUESTION' => 'HELP_FAQ_SEARCH_MEMBERS_ANSWER', + 'HELP_FAQ_SEARCH_OWN_QUESTION' => 'HELP_FAQ_SEARCH_OWN_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_BOOKMARKS', + false, + array( + 'HELP_FAQ_BOOKMARKS_DIFFERENCE_QUESTION' => 'HELP_FAQ_BOOKMARKS_DIFFERENCE_ANSWER', + 'HELP_FAQ_BOOKMARKS_TOPIC_QUESTION' => 'HELP_FAQ_BOOKMARKS_TOPIC_ANSWER', + 'HELP_FAQ_BOOKMARKS_FORUM_QUESTION' => 'HELP_FAQ_BOOKMARKS_FORUM_ANSWER', + 'HELP_FAQ_BOOKMARKS_REMOVE_QUESTION' => 'HELP_FAQ_BOOKMARKS_REMOVE_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_ATTACHMENTS', + false, + array( + 'HELP_FAQ_ATTACHMENTS_ALLOWED_QUESTION' => 'HELP_FAQ_ATTACHMENTS_ALLOWED_ANSWER', + 'HELP_FAQ_ATTACHMENTS_OWN_QUESTION' => 'HELP_FAQ_ATTACHMENTS_OWN_ANSWER', + ) + ); + $this->manager->add_block( + 'HELP_FAQ_BLOCK_ISSUES', + false, + array( + 'HELP_FAQ_ISSUES_WHOIS_PHPBB_QUESTION' => 'HELP_FAQ_ISSUES_WHOIS_PHPBB_ANSWER', + 'HELP_FAQ_ISSUES_FEATURE_QUESTION' => 'HELP_FAQ_ISSUES_FEATURE_ANSWER', + 'HELP_FAQ_ISSUES_LEGAL_QUESTION' => 'HELP_FAQ_ISSUES_LEGAL_ANSWER', + 'HELP_FAQ_ISSUES_ADMIN_QUESTION' => 'HELP_FAQ_ISSUES_ADMIN_ANSWER', + ) + ); + + return $this->language->lang('FAQ_EXPLAIN'); + } +} diff --git a/phpbb/help/controller/help.php b/phpbb/help/controller/help.php new file mode 100644 index 0000000..3bf6fe3 --- /dev/null +++ b/phpbb/help/controller/help.php @@ -0,0 +1,164 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\help\controller; + +use phpbb\exception\http_exception; + +class help +{ + /** @var \phpbb\controller\helper */ + protected $helper; + + /** @var \phpbb\event\dispatcher_interface */ + protected $dispatcher; + + /** @var \phpbb\template\template */ + protected $template; + + /** @var \phpbb\user */ + protected $user; + + /** @var string */ + protected $root_path; + + /** @var string */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\controller\helper $helper + * @param \phpbb\event\dispatcher_interface $dispatcher + * @param \phpbb\template\template $template + * @param \phpbb\user $user + * @param string $root_path + * @param string $php_ext + */ + public function __construct(\phpbb\controller\helper $helper, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\template\template $template, \phpbb\user $user, $root_path, $php_ext) + { + $this->helper = $helper; + $this->dispatcher = $dispatcher; + $this->template = $template; + $this->user = $user; + $this->root_path = $root_path; + $this->php_ext = $php_ext; + } + + /** + * Controller for /help/{mode} routes + * + * @param string $mode + * @return \Symfony\Component\HttpFoundation\Response A Symfony Response object + * @throws http_exception when the $mode is not known by any extension + */ + public function handle($mode) + { + $template_file = 'faq_body.html'; + switch ($mode) + { + case 'faq': + case 'bbcode': + $page_title = ($mode === 'faq') ? $this->user->lang['FAQ_EXPLAIN'] : $this->user->lang['BBCODE_GUIDE']; + $this->user->add_lang($mode, false, true); + break; + + default: + $page_title = $this->user->lang['FAQ_EXPLAIN']; + $ext_name = $lang_file = ''; + + /** + * You can use this event display a custom help page + * + * @event core.faq_mode_validation + * @var string page_title Title of the page + * @var string mode FAQ that is going to be displayed + * @var string lang_file Language file containing the help data + * @var string ext_name Vendor and extension name where the help + * language file can be loaded from + * @var string template_file Template file name + * @since 3.1.4-RC1 + * @changed 3.1.11-RC1 Added template_file var + */ + $vars = array( + 'page_title', + 'mode', + 'lang_file', + 'ext_name', + 'template_file', + ); + extract($this->dispatcher->trigger_event('core.faq_mode_validation', compact($vars))); + + if ($ext_name === '' || $lang_file === '') + { + throw new http_exception(404, 'Not Found'); + } + + $this->user->add_lang($lang_file, false, true, $ext_name); + break; + + } + + $this->template->assign_vars(array( + 'L_FAQ_TITLE' => $page_title, + 'S_IN_FAQ' => true, + )); + + $this->assign_to_template($this->user->help); + + make_jumpbox(append_sid("{$this->root_path}viewforum.{$this->php_ext}")); + return $this->helper->render($template_file, $page_title); + } + + /** + * Assigns the help data to the template blocks + * + * @param array $help_data + * @return null + */ + protected function assign_to_template(array $help_data) + { + // Pull the array data from the lang pack + $switch_column = $found_switch = false; + foreach ($help_data as $help_ary) + { + if ($help_ary[0] == '--') + { + if ($help_ary[1] == '--') + { + $switch_column = true; + $found_switch = true; + continue; + } + + $this->template->assign_block_vars('faq_block', array( + 'BLOCK_TITLE' => $help_ary[1], + 'SWITCH_COLUMN' => $switch_column, + )); + + if ($switch_column) + { + $switch_column = false; + } + continue; + } + + $this->template->assign_block_vars('faq_block.faq_row', array( + 'FAQ_QUESTION' => $help_ary[0], + 'FAQ_ANSWER' => $help_ary[1], + )); + } + + $this->template->assign_var('SWITCH_COLUMN_MANUALLY', !$found_switch); + } +} diff --git a/phpbb/help/manager.php b/phpbb/help/manager.php new file mode 100644 index 0000000..1637c58 --- /dev/null +++ b/phpbb/help/manager.php @@ -0,0 +1,137 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\help; + +/** + * Class help page manager + */ +class manager +{ + /** @var \phpbb\event\dispatcher_interface */ + protected $dispatcher; + + /** @var \phpbb\language\language */ + protected $language; + + /** @var \phpbb\template\template */ + protected $template; + + /** @var bool */ + protected $switched_column; + + /** + * Constructor + * + * @param \phpbb\event\dispatcher_interface $dispatcher + * @param \phpbb\language\language $language + * @param \phpbb\template\template $template + */ + public function __construct(\phpbb\event\dispatcher_interface $dispatcher, \phpbb\language\language $language, \phpbb\template\template $template) + { + $this->dispatcher = $dispatcher; + $this->language = $language; + $this->template = $template; + } + + /** + * Add a new faq block + * + * @param string $block_name Name or language key with the name of the block + * @param bool $switch_column Switch the column of the menu + * @param array $questions Array of frequently asked questions + */ + public function add_block($block_name, $switch_column = false, $questions = array()) + { + /** + * You can use this event to add a block before the current one. + * + * @event core.help_manager_add_block_before + * @var string block_name Language key of the block headline + * @var bool switch_column Should we switch the menu column before this headline + * @var array questions Array with questions + * @since 3.2.0-a1 + */ + $vars = array('block_name', 'switch_column', 'questions'); + extract($this->dispatcher->trigger_event('core.help_manager_add_block_before', compact($vars))); + + $this->template->assign_block_vars('faq_block', array( + 'BLOCK_TITLE' => $this->language->lang($block_name), + 'SWITCH_COLUMN' => !$this->switched_column && $switch_column, + )); + + foreach ($questions as $question => $answer) + { + $this->add_question($question, $answer); + } + + $this->switched_column = $this->switched_column || $switch_column; + + /** + * You can use this event to add a block after the current one. + * + * @event core.help_manager_add_block_after + * @var string block_name Language key of the block headline + * @var bool switch_column Should we switch the menu column before this headline + * @var array questions Array with questions + * @since 3.2.0-a1 + */ + $vars = array('block_name', 'switch_column', 'questions'); + extract($this->dispatcher->trigger_event('core.help_manager_add_block_after', compact($vars))); + } + + /** + * Add a new faq question + * + * @param string $question Question or language key with the question of the block + * @param string $answer Answer or language key with the answer of the block + */ + public function add_question($question, $answer) + { + /** + * You can use this event to add a question before the current one. + * + * @event core.help_manager_add_question_before + * @var string question Language key of the question + * @var string answer Language key of the answer + * @since 3.2.0-a1 + */ + $vars = array('question', 'answer'); + extract($this->dispatcher->trigger_event('core.help_manager_add_question_before', compact($vars))); + + $this->template->assign_block_vars('faq_block.faq_row', array( + 'FAQ_QUESTION' => $this->language->lang($question), + 'FAQ_ANSWER' => $this->language->lang($answer), + )); + + /** + * You can use this event to add a question after the current one. + * + * @event core.help_manager_add_question_after + * @var string question Language key of the question + * @var string answer Language key of the answer + * @since 3.2.0-a1 + */ + $vars = array('question', 'answer'); + extract($this->dispatcher->trigger_event('core.help_manager_add_question_after', compact($vars))); + } + + /** + * Returns whether the block titles switched side + * @return bool + */ + public function switched_column() + { + return $this->switched_column; + } +} diff --git a/phpbb/hook/finder.php b/phpbb/hook/finder.php new file mode 100644 index 0000000..f5a68a1 --- /dev/null +++ b/phpbb/hook/finder.php @@ -0,0 +1,91 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\hook; + +/** +* The hook finder locates installed hooks. +*/ +class finder +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Creates a new finder instance. + * + * @param string $phpbb_root_path Path to the phpbb root directory + * @param string $php_ext php file extension + * @param \phpbb\cache\driver\driver_interface $cache A cache instance or null + */ + public function __construct($phpbb_root_path, $php_ext, \phpbb\cache\driver\driver_interface $cache = null) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->cache = $cache; + $this->php_ext = $php_ext; + } + + /** + * Finds all hook files. + * + * @param bool $cache Whether the result should be cached + * @return array An array of paths to found hook files + */ + public function find($cache = true) + { + if (!defined('DEBUG') && $cache && $this->cache) + { + $hook_files = $this->cache->get('_hooks'); + if ($hook_files !== false) + { + return $hook_files; + } + } + + $hook_files = array(); + + // Now search for hooks... + $dh = @opendir($this->phpbb_root_path . 'includes/hooks/'); + + if ($dh) + { + while (($file = readdir($dh)) !== false) + { + if (strpos($file, 'hook_') === 0 && substr($file, -strlen('.' . $this->php_ext)) === '.' . $this->php_ext) + { + $hook_files[] = substr($file, 0, -(strlen($this->php_ext) + 1)); + } + } + closedir($dh); + } + + if ($cache && $this->cache) + { + $this->cache->put('_hooks', $hook_files); + } + + return $hook_files; + } +} diff --git a/phpbb/install/console/command/install/config/show.php b/phpbb/install/console/command/install/config/show.php new file mode 100644 index 0000000..b6c1195 --- /dev/null +++ b/phpbb/install/console/command/install/config/show.php @@ -0,0 +1,123 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install\console\command\install\config; + +use phpbb\install\helper\iohandler\factory; +use phpbb\install\installer_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class show extends \phpbb\console\command\command +{ + /** + * @var factory + */ + protected $iohandler_factory; + + /** + * @var language + */ + protected $language; + + /** + * Constructor + * + * @param language $language + * @param factory $factory + */ + public function __construct(language $language, factory $factory) + { + $this->iohandler_factory = $factory; + $this->language = $language; + + parent::__construct(new \phpbb\user($language, 'datetime')); + } + + /** + * + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('install:config:show') + ->addArgument( + 'config-file', + InputArgument::REQUIRED, + $this->language->lang('CLI_CONFIG_FILE')) + ->setDescription($this->language->lang('CLI_INSTALL_SHOW_CONFIG')) + ; + } + + /** + * Show the validated configuration + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return null + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->iohandler_factory->set_environment('cli'); + + /** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ + $iohandler = $this->iohandler_factory->get(); + $style = new SymfonyStyle($input, $output); + $iohandler->set_style($style, $output); + + $config_file = $input->getArgument('config-file'); + + if (!is_file($config_file)) + { + $iohandler->add_error_message(array('MISSING_FILE', $config_file)); + + return; + } + + try + { + $config = Yaml::parse(file_get_contents($config_file), true, false); + } + catch (ParseException $e) + { + $iohandler->add_error_message('INVALID_YAML_FILE'); + + return; + } + + $processor = new Processor(); + $configuration = new installer_configuration(); + + try + { + $config = $processor->processConfiguration($configuration, $config); + } + catch (Exception $e) + { + $iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + + return; + } + + $style->block(Yaml::dump(array('installer' => $config), 10, 4, true, false)); + } +} diff --git a/phpbb/install/console/command/install/config/validate.php b/phpbb/install/console/command/install/config/validate.php new file mode 100644 index 0000000..b48a1ac --- /dev/null +++ b/phpbb/install/console/command/install/config/validate.php @@ -0,0 +1,124 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install\console\command\install\config; + +use phpbb\install\helper\iohandler\factory; +use phpbb\install\installer_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class validate extends \phpbb\console\command\command +{ + /** + * @var factory + */ + protected $iohandler_factory; + + /** + * @var language + */ + protected $language; + + /** + * Constructor + * + * @param language $language + * @param factory $factory + */ + public function __construct(language $language, factory $factory) + { + $this->iohandler_factory = $factory; + $this->language = $language; + + parent::__construct(new \phpbb\user($language, 'datetime')); + } + + /** + * + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('install:config:validate') + ->addArgument( + 'config-file', + InputArgument::REQUIRED, + $this->language->lang('CLI_CONFIG_FILE')) + ->setDescription($this->language->lang('CLI_INSTALL_VALIDATE_CONFIG')) + ; + } + + /** + * Validate the configuration file + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return null + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->iohandler_factory->set_environment('cli'); + + /** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ + $iohandler = $this->iohandler_factory->get(); + $style = new SymfonyStyle($input, $output); + $iohandler->set_style($style, $output); + + $config_file = $input->getArgument('config-file'); + + if (!is_file($config_file)) + { + $iohandler->add_error_message(array('MISSING_FILE', array($config_file))); + + return 1; + } + + try + { + $config = Yaml::parse(file_get_contents($config_file), true, false); + } + catch (ParseException $e) + { + $iohandler->add_error_message('INVALID_YAML_FILE'); + + return 1; + } + + $processor = new Processor(); + $configuration = new installer_configuration(); + + try + { + $processor->processConfiguration($configuration, $config); + } + catch (Exception $e) + { + $iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + + return 1; + } + + $iohandler->add_success_message('CONFIGURATION_VALID'); + return 0; + } +} diff --git a/phpbb/install/console/command/install/install.php b/phpbb/install/console/command/install/install.php new file mode 100644 index 0000000..52a348f --- /dev/null +++ b/phpbb/install/console/command/install/install.php @@ -0,0 +1,210 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install\console\command\install; + +use phpbb\install\exception\installer_exception; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\iohandler\cli_iohandler; +use phpbb\install\helper\iohandler\factory; +use phpbb\install\installer; +use phpbb\install\installer_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class install extends \phpbb\console\command\command +{ + /** + * @var factory + */ + protected $iohandler_factory; + + /** + * @var installer + */ + protected $installer; + + /** + * @var install_helper + */ + protected $install_helper; + + /** + * @var language + */ + protected $language; + + /** + * Constructor + * + * @param language $language + * @param factory $factory + * @param installer $installer + * @param install_helper $install_helper + */ + public function __construct(language $language, factory $factory, installer $installer, install_helper $install_helper) + { + $this->iohandler_factory = $factory; + $this->installer = $installer; + $this->language = $language; + $this->install_helper = $install_helper; + + parent::__construct(new \phpbb\user($language, 'datetime')); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('install') + ->addArgument( + 'config-file', + InputArgument::REQUIRED, + $this->language->lang('CLI_CONFIG_FILE')) + ->setDescription($this->language->lang('CLI_INSTALL_BOARD')) + ; + } + + /** + * Executes the command install. + * + * Install the board + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return null + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->iohandler_factory->set_environment('cli'); + + /** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ + $iohandler = $this->iohandler_factory->get(); + $style = new SymfonyStyle($input, $output); + $iohandler->set_style($style, $output); + + $this->installer->set_iohandler($iohandler); + + $config_file = $input->getArgument('config-file'); + + if ($this->install_helper->is_phpbb_installed()) + { + $iohandler->add_error_message('INSTALL_PHPBB_INSTALLED'); + + return 1; + } + + if (!is_file($config_file)) + { + $iohandler->add_error_message(array('MISSING_FILE', $config_file)); + + return 1; + } + + try + { + $config = Yaml::parse(file_get_contents($config_file), true, false); + } + catch (ParseException $e) + { + $iohandler->add_error_message(array('INVALID_YAML_FILE', $config_file)); + + return 1; + } + + $processor = new Processor(); + $configuration = new installer_configuration(); + + try + { + $config = $processor->processConfiguration($configuration, $config); + } + catch (Exception $e) + { + $iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + + return 1; + } + + $this->register_configuration($iohandler, $config); + + try + { + $this->installer->run(); + return 0; + } + catch (installer_exception $e) + { + $iohandler->add_error_message($e->getMessage()); + return 1; + } + } + + /** + * Register the configuration to simulate the forms. + * + * @param cli_iohandler $iohandler + * @param array $config + */ + private function register_configuration(cli_iohandler $iohandler, $config) + { + $iohandler->set_input('admin_name', $config['admin']['name']); + $iohandler->set_input('admin_pass1', $config['admin']['password']); + $iohandler->set_input('admin_pass2', $config['admin']['password']); + $iohandler->set_input('board_email', $config['admin']['email']); + $iohandler->set_input('submit_admin', 'submit'); + + $iohandler->set_input('default_lang', $config['board']['lang']); + $iohandler->set_input('board_name', $config['board']['name']); + $iohandler->set_input('board_description', $config['board']['description']); + $iohandler->set_input('submit_board', 'submit'); + + $iohandler->set_input('dbms', $config['database']['dbms']); + $iohandler->set_input('dbhost', $config['database']['dbhost']); + $iohandler->set_input('dbport', $config['database']['dbport']); + $iohandler->set_input('dbuser', $config['database']['dbuser']); + $iohandler->set_input('dbpasswd', $config['database']['dbpasswd']); + $iohandler->set_input('dbname', $config['database']['dbname']); + $iohandler->set_input('table_prefix', $config['database']['table_prefix']); + $iohandler->set_input('submit_database', 'submit'); + + $iohandler->set_input('email_enable', $config['email']['enabled']); + $iohandler->set_input('smtp_delivery', $config['email']['smtp_delivery']); + $iohandler->set_input('smtp_host', $config['email']['smtp_host']); + $iohandler->set_input('smtp_port', $config['email']['smtp_port']); + $iohandler->set_input('smtp_auth', $config['email']['smtp_auth']); + $iohandler->set_input('smtp_user', $config['email']['smtp_user']); + $iohandler->set_input('smtp_pass', $config['email']['smtp_pass']); + $iohandler->set_input('submit_email', 'submit'); + + $iohandler->set_input('cookie_secure', $config['server']['cookie_secure']); + $iohandler->set_input('server_protocol', $config['server']['server_protocol']); + $iohandler->set_input('force_server_vars', $config['server']['force_server_vars']); + $iohandler->set_input('server_name', $config['server']['server_name']); + $iohandler->set_input('server_port', $config['server']['server_port']); + $iohandler->set_input('script_path', $config['server']['script_path']); + $iohandler->set_input('submit_server', 'submit'); + + $iohandler->set_input('install-extensions', $config['extensions']); + } +} diff --git a/phpbb/install/console/command/update/config/show.php b/phpbb/install/console/command/update/config/show.php new file mode 100644 index 0000000..e462763 --- /dev/null +++ b/phpbb/install/console/command/update/config/show.php @@ -0,0 +1,123 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install\console\command\update\config; + +use phpbb\install\helper\iohandler\factory; +use phpbb\install\updater_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class show extends \phpbb\console\command\command +{ + /** + * @var factory + */ + protected $iohandler_factory; + + /** + * @var language + */ + protected $language; + + /** + * Constructor + * + * @param language $language + * @param factory $factory + */ + public function __construct(language $language, factory $factory) + { + $this->iohandler_factory = $factory; + $this->language = $language; + + parent::__construct(new \phpbb\user($language, 'datetime')); + } + + /** + * + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('update:config:show') + ->addArgument( + 'config-file', + InputArgument::REQUIRED, + $this->language->lang('CLI_CONFIG_FILE')) + ->setDescription($this->language->lang('CLI_INSTALL_SHOW_CONFIG')) + ; + } + + /** + * Show the validated configuration + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return null + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->iohandler_factory->set_environment('cli'); + + /** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ + $iohandler = $this->iohandler_factory->get(); + $style = new SymfonyStyle($input, $output); + $iohandler->set_style($style, $output); + + $config_file = $input->getArgument('config-file'); + + if (!is_file($config_file)) + { + $iohandler->add_error_message(array('MISSING_FILE', $config_file)); + + return; + } + + try + { + $config = Yaml::parse(file_get_contents($config_file), true, false); + } + catch (ParseException $e) + { + $iohandler->add_error_message('INVALID_YAML_FILE'); + + return; + } + + $processor = new Processor(); + $configuration = new updater_configuration(); + + try + { + $config = $processor->processConfiguration($configuration, $config); + } + catch (Exception $e) + { + $iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + + return; + } + + $style->block(Yaml::dump(array('updater' => $config), 10, 4, true, false)); + } +} diff --git a/phpbb/install/console/command/update/config/validate.php b/phpbb/install/console/command/update/config/validate.php new file mode 100644 index 0000000..18de5ea --- /dev/null +++ b/phpbb/install/console/command/update/config/validate.php @@ -0,0 +1,124 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install\console\command\update\config; + +use phpbb\install\helper\iohandler\factory; +use phpbb\install\updater_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class validate extends \phpbb\console\command\command +{ + /** + * @var factory + */ + protected $iohandler_factory; + + /** + * @var language + */ + protected $language; + + /** + * Constructor + * + * @param language $language + * @param factory $factory + */ + public function __construct(language $language, factory $factory) + { + $this->iohandler_factory = $factory; + $this->language = $language; + + parent::__construct(new \phpbb\user($language, 'datetime')); + } + + /** + * + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('update:config:validate') + ->addArgument( + 'config-file', + InputArgument::REQUIRED, + $this->language->lang('CLI_CONFIG_FILE')) + ->setDescription($this->language->lang('CLI_INSTALL_VALIDATE_CONFIG')) + ; + } + + /** + * Validate the configuration file + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return null + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->iohandler_factory->set_environment('cli'); + + /** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ + $iohandler = $this->iohandler_factory->get(); + $style = new SymfonyStyle($input, $output); + $iohandler->set_style($style, $output); + + $config_file = $input->getArgument('config-file'); + + if (!is_file($config_file)) + { + $iohandler->add_error_message(array('MISSING_FILE', array($config_file))); + + return 1; + } + + try + { + $config = Yaml::parse(file_get_contents($config_file), true, false); + } + catch (ParseException $e) + { + $iohandler->add_error_message('INVALID_YAML_FILE'); + + return 1; + } + + $processor = new Processor(); + $configuration = new updater_configuration(); + + try + { + $processor->processConfiguration($configuration, $config); + } + catch (Exception $e) + { + $iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + + return 1; + } + + $iohandler->add_success_message('CONFIGURATION_VALID'); + return 0; + } +} diff --git a/phpbb/install/console/command/update/update.php b/phpbb/install/console/command/update/update.php new file mode 100644 index 0000000..e827761 --- /dev/null +++ b/phpbb/install/console/command/update/update.php @@ -0,0 +1,182 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install\console\command\update; + +use phpbb\install\exception\installer_exception; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\iohandler\cli_iohandler; +use phpbb\install\helper\iohandler\factory; +use phpbb\install\installer; +use phpbb\install\updater_configuration; +use phpbb\language\language; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Yaml; + +class update extends \phpbb\console\command\command +{ + /** + * @var factory + */ + protected $iohandler_factory; + + /** + * @var installer + */ + protected $installer; + + /** + * @var install_helper + */ + protected $install_helper; + + /** + * @var language + */ + protected $language; + + /** + * Constructor + * + * @param language $language + * @param factory $factory + * @param installer $installer + * @param install_helper $install_helper + */ + public function __construct(language $language, factory $factory, installer $installer, install_helper $install_helper) + { + $this->iohandler_factory = $factory; + $this->installer = $installer; + $this->language = $language; + $this->install_helper = $install_helper; + + parent::__construct(new \phpbb\user($language, 'datetime')); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('update') + ->addArgument( + 'config-file', + InputArgument::REQUIRED, + $this->language->lang('CLI_CONFIG_FILE')) + ->setDescription($this->language->lang('CLI_UPDATE_BOARD')) + ; + } + + /** + * Executes the command update. + * + * Update the board + * + * @param InputInterface $input An InputInterface instance + * @param OutputInterface $output An OutputInterface instance + * + * @return int + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->iohandler_factory->set_environment('cli'); + + /** @var \phpbb\install\helper\iohandler\cli_iohandler $iohandler */ + $iohandler = $this->iohandler_factory->get(); + $style = new SymfonyStyle($input, $output); + $iohandler->set_style($style, $output); + + $this->installer->set_iohandler($iohandler); + + $config_file = $input->getArgument('config-file'); + + if (!$this->install_helper->is_phpbb_installed()) + { + $iohandler->add_error_message('INSTALL_PHPBB_NOT_INSTALLED'); + + return 1; + } + + if (!is_file($config_file)) + { + $iohandler->add_error_message(array('MISSING_FILE', $config_file)); + + return 1; + } + + try + { + $config = Yaml::parse(file_get_contents($config_file), true, false); + } + catch (ParseException $e) + { + $iohandler->add_error_message(array('INVALID_YAML_FILE', $config_file)); + + return 1; + } + + $processor = new Processor(); + $configuration = new updater_configuration(); + + try + { + $config = $processor->processConfiguration($configuration, $config); + } + catch (Exception $e) + { + $iohandler->add_error_message('INVALID_CONFIGURATION', $e->getMessage()); + + return 1; + } + + $this->register_configuration($iohandler, $config); + + try + { + $this->installer->run(); + return 0; + } + catch (installer_exception $e) + { + $iohandler->add_error_message($e->getMessage()); + return 1; + } + } + + /** + * Register the configuration to simulate the forms. + * + * @param cli_iohandler $iohandler + * @param array $config + */ + private function register_configuration(cli_iohandler $iohandler, $config) + { + $iohandler->set_input('update_type', $config['type']); + $iohandler->set_input('submit_update', 'submit'); + + $iohandler->set_input('compression_method', '.tar'); + $iohandler->set_input('method', 'direct_file'); + $iohandler->set_input('submit_update_file', 'submit'); + + $iohandler->set_input('submit_continue_file_update', 'submit'); + + $iohandler->set_input('update-extensions', $config['extensions']); + } +} diff --git a/phpbb/install/controller/archive_download.php b/phpbb/install/controller/archive_download.php new file mode 100644 index 0000000..eabc0a9 --- /dev/null +++ b/phpbb/install/controller/archive_download.php @@ -0,0 +1,93 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\controller; + +use phpbb\exception\http_exception; +use phpbb\install\helper\config; +use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; + +class archive_download +{ + /** + * @var config + */ + protected $installer_config; + + /** + * Constructor + * + * @param config $config + */ + public function __construct(config $config) + { + $this->installer_config = $config; + $this->installer_config->load_config(); + } + + /** + * Sends response with the merge conflict archive + * + * Merge conflicts always have to be resolved manually, + * so we use a different archive for that. + * + * @return BinaryFileResponse + */ + public function conflict_archive() + { + $filename = $this->installer_config->get('update_file_conflict_archive', ''); + + if (empty($filename)) + { + throw new http_exception(404, 'URL_NOT_FOUND'); + } + + return $this->send_response($filename); + } + + /** + * Sends response with the updated files' archive + * + * @return BinaryFileResponse + */ + public function update_archive() + { + $filename = $this->installer_config->get('update_file_archive', ''); + + if (empty($filename)) + { + throw new http_exception(404, 'URL_NOT_FOUND'); + } + + return $this->send_response($filename); + } + + /** + * Generates a download response + * + * @param string $filename Path to the file to download + * + * @return BinaryFileResponse Response object + */ + private function send_response($filename) + { + $response = new BinaryFileResponse($filename); + $response->setContentDisposition( + ResponseHeaderBag::DISPOSITION_ATTACHMENT, + basename($filename) + ); + + return $response; + } +} diff --git a/phpbb/install/controller/helper.php b/phpbb/install/controller/helper.php new file mode 100644 index 0000000..ff7e691 --- /dev/null +++ b/phpbb/install/controller/helper.php @@ -0,0 +1,413 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\controller; + +use phpbb\install\helper\config; +use phpbb\install\helper\navigation\navigation_provider; +use phpbb\language\language; +use phpbb\language\language_file_helper; +use phpbb\path_helper; +use phpbb\request\request; +use phpbb\request\request_interface; +use phpbb\routing\router; +use phpbb\symfony_request; +use phpbb\template\template; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\Cookie; + +/** + * A duplicate of \phpbb\controller\helper + * + * This class is necessary because of controller\helper's legacy function calls + * to page_header() page_footer() functions which has unavailable dependencies. + */ +class helper +{ + /** + * @var config + */ + protected $installer_config; + + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * @var bool|string + */ + protected $language_cookie; + + /** + * @var \phpbb\language\language_file_helper + */ + protected $lang_helper; + + /** + * @var \phpbb\install\helper\navigation\navigation_provider + */ + protected $navigation_provider; + + /** + * @var \phpbb\template\template + */ + protected $template; + + /** + * @var \phpbb\path_helper + */ + protected $path_helper; + + /** + * @var \phpbb\request\request + */ + protected $phpbb_request; + + /** + * @var \phpbb\symfony_request + */ + protected $request; + + /** + * @var \phpbb\routing\router + */ + protected $router; + + /** + * @var string + */ + protected $phpbb_admin_path; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param config $config + * @param language $language + * @param language_file_helper $lang_helper + * @param navigation_provider $nav + * @param template $template + * @param path_helper $path_helper + * @param request $phpbb_request + * @param symfony_request $request + * @param router $router + * @param string $phpbb_root_path + */ + public function __construct(config $config, language $language, language_file_helper $lang_helper, navigation_provider $nav, template $template, path_helper $path_helper, request $phpbb_request, symfony_request $request, router $router, $phpbb_root_path) + { + $this->installer_config = $config; + $this->language = $language; + $this->language_cookie = false; + $this->lang_helper = $lang_helper; + $this->navigation_provider = $nav; + $this->template = $template; + $this->path_helper = $path_helper; + $this->phpbb_request = $phpbb_request; + $this->request = $request; + $this->router = $router; + $this->phpbb_root_path = $phpbb_root_path; + $this->phpbb_admin_path = $phpbb_root_path . 'adm/'; + } + + /** + * Automate setting up the page and creating the response object. + * + * @param string $template_file The template handle to render + * @param string $page_title The title of the page to output + * @param bool $selected_language True to enable language selector it, false otherwise + * @param int $status_code The status code to be sent to the page header + * + * @return Response object containing rendered page + */ + public function render($template_file, $page_title = '', $selected_language = false, $status_code = 200) + { + $this->page_header($page_title, $selected_language); + + $this->template->set_filenames(array( + 'body' => $template_file, + )); + + $response = new Response($this->template->assign_display('body'), $status_code); + + // Set language cookie + if ($this->language_cookie !== false) + { + $cookie = new Cookie('lang', $this->language_cookie, time() + 3600); + $response->headers->setCookie($cookie); + + $this->language_cookie = false; + } + + return $response; + } + + /** + * Returns path from route name + * + * @param string $route_name + * @param array $parameters + * + * @return string + */ + public function route($route_name, $parameters = array()) + { + $url = $this->router->generate($route_name, $parameters); + + return $url; + } + + /** + * Handles language selector form + */ + public function handle_language_select() + { + $lang = null; + + // Check if language form has been submited + $submit = $this->phpbb_request->variable('change_lang', ''); + if (!empty($submit)) + { + $lang = $this->phpbb_request->variable('language', ''); + } + + // Retrieve language from cookie + $lang_cookie = $this->phpbb_request->variable('lang', '', false, request_interface::COOKIE); + if (empty($lang) && !empty($lang_cookie)) + { + $lang = $lang_cookie; + } + + $lang = (!empty($lang) && strpos($lang, '/') === false) ? $lang : null; + $this->language_cookie = $lang; + + $this->render_language_select($lang); + + if ($lang !== null) + { + $this->language->set_user_language($lang, true); + $this->installer_config->set('user_language', $lang); + } + } + + /** + * Process navigation data to reflect active/completed stages + * + * @param \phpbb\install\helper\iohandler\iohandler_interface|null $iohandler + */ + public function handle_navigation($iohandler = null) + { + $nav_data = $this->installer_config->get_navigation_data(); + + // Set active navigation stage + if (isset($nav_data['active']) && is_array($nav_data['active'])) + { + if ($iohandler !== null) + { + $iohandler->set_active_stage_menu($nav_data['active']); + } + + $this->navigation_provider->set_nav_property($nav_data['active'], array( + 'selected' => true, + 'completed' => false, + )); + } + + // Set finished navigation stages + if (isset($nav_data['finished']) && is_array($nav_data['finished'])) + { + foreach ($nav_data['finished'] as $finished_stage) + { + if ($iohandler !== null) + { + $iohandler->set_finished_stage_menu($finished_stage); + } + + $this->navigation_provider->set_nav_property($finished_stage, array( + 'selected' => false, + 'completed' => true, + )); + } + } + } + + /** + * Set default template variables + * + * @param string $page_title Title of the page + * @param bool $selected_language True to enable language selector it, false otherwise + */ + protected function page_header($page_title, $selected_language = false) + { + // Path to templates + $paths = array($this->phpbb_root_path . 'install/update/new/adm/', $this->phpbb_admin_path); + $paths = array_filter($paths, 'is_dir'); + $path = array_shift($paths); + $path = substr($path, strlen($this->phpbb_root_path)); + + $this->template->assign_vars(array( + 'L_CHANGE' => $this->language->lang('CHANGE'), + 'L_COLON' => $this->language->lang('COLON'), + 'L_INSTALL_PANEL' => $this->language->lang('INSTALL_PANEL'), + 'L_SELECT_LANG' => $this->language->lang('SELECT_LANG'), + 'L_SKIP' => $this->language->lang('SKIP'), + 'PAGE_TITLE' => $this->language->lang($page_title), + 'T_IMAGE_PATH' => $this->path_helper->get_web_root_path() . $path . 'images', + 'T_JQUERY_LINK' => $this->path_helper->get_web_root_path() . $path . '../assets/javascript/jquery.min.js', + 'T_TEMPLATE_PATH' => $this->path_helper->get_web_root_path() . $path . 'style', + 'T_ASSETS_PATH' => $this->path_helper->get_web_root_path() . $path . '../assets', + + 'S_CONTENT_DIRECTION' => $this->language->lang('DIRECTION'), + 'S_CONTENT_FLOW_BEGIN' => ($this->language->lang('DIRECTION') === 'ltr') ? 'left' : 'right', + 'S_CONTENT_FLOW_END' => ($this->language->lang('DIRECTION') === 'ltr') ? 'right' : 'left', + 'S_CONTENT_ENCODING' => 'UTF-8', + 'S_LANG_SELECT' => $selected_language, + + 'S_USER_LANG' => $this->language->lang('USER_LANG'), + )); + + $this->render_navigation(); + } + + /** + * Render navigation + */ + protected function render_navigation() + { + // Get navigation items + $nav_array = $this->navigation_provider->get(); + $nav_array = $this->sort_navigation_level($nav_array); + + $active_main_menu = $this->get_active_main_menu($nav_array); + + // Pass navigation to template + foreach ($nav_array as $key => $entry) + { + $this->template->assign_block_vars('t_block1', array( + 'L_TITLE' => $this->language->lang($entry['label']), + 'S_SELECTED' => ($active_main_menu === $key), + 'U_TITLE' => $this->route($entry['route']), + )); + + if (is_array($entry[0]) && $active_main_menu === $key) + { + $entry[0] = $this->sort_navigation_level($entry[0]); + + foreach ($entry[0] as $name => $sub_entry) + { + if (isset($sub_entry['stage']) && $sub_entry['stage'] === true) + { + $this->template->assign_block_vars('l_block2', array( + 'L_TITLE' => $this->language->lang($sub_entry['label']), + 'S_SELECTED' => (isset($sub_entry['selected']) && $sub_entry['selected'] === true), + 'S_COMPLETE' => (isset($sub_entry['completed']) && $sub_entry['completed'] === true), + 'STAGE_NAME' => $name, + )); + } + else + { + $this->template->assign_block_vars('l_block1', array( + 'L_TITLE' => $this->language->lang($sub_entry['label']), + 'S_SELECTED' => (isset($sub_entry['route']) && $sub_entry['route'] === $this->request->get('_route')), + 'U_TITLE' => $this->route($sub_entry['route']), + )); + } + } + } + } + } + + /** + * Render language select form + * + * @param string $selected_language + */ + protected function render_language_select($selected_language = null) + { + $langs = $this->lang_helper->get_available_languages(); + foreach ($langs as $lang) + { + $this->template->assign_block_vars('language_select_item', array( + 'VALUE' => $lang['iso'], + 'NAME' => $lang['local_name'], + 'SELECTED' => ($lang['iso'] === $selected_language), + )); + } + } + + /** + * Returns the name of the active main menu item + * + * @param array $nav_array + * + * @return string|bool Returns the name of the active main menu element, if the element not found, returns false + */ + protected function get_active_main_menu($nav_array) + { + $active_route = $this->request->get('_route'); + + foreach ($nav_array as $nav_name => $nav_options) + { + $current_menu = $nav_name; + + if (isset($nav_options['route']) && $nav_options['route'] === $active_route) + { + return $nav_name; + } + + if (is_array($nav_options[0])) + { + foreach ($nav_options[0] as $sub_menus) + { + if (isset($sub_menus['route']) && $sub_menus['route'] === $active_route) + { + return $current_menu; + } + } + } + } + + return false; + } + + /** + * Sorts the top level of navigation array + * + * @param array $nav_array Navigation array + * + * @return array + */ + protected function sort_navigation_level($nav_array) + { + $sorted = array(); + foreach ($nav_array as $key => $nav) + { + $order = (isset($nav['order'])) ? $nav['order'] : 0; + $sorted[$order][$key] = $nav; + } + + // Linearization of navigation array + $nav_array = array(); + ksort($sorted); + foreach ($sorted as $nav) + { + $nav_array = array_merge($nav_array, $nav); + } + + return $nav_array; + } +} diff --git a/phpbb/install/controller/install.php b/phpbb/install/controller/install.php new file mode 100644 index 0000000..9250687 --- /dev/null +++ b/phpbb/install/controller/install.php @@ -0,0 +1,172 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\controller; + +use phpbb\exception\http_exception; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\navigation\navigation_provider; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpFoundation\Response; +use phpbb\install\helper\iohandler\factory; +use phpbb\template\template; +use phpbb\request\request_interface; +use phpbb\install\installer; +use phpbb\language\language; + +/** + * Controller for installing phpBB + */ +class install +{ + /** + * @var helper + */ + protected $controller_helper; + + /** + * @var factory + */ + protected $iohandler_factory; + + /** + * @var navigation_provider + */ + protected $menu_provider; + + /** + * @var language + */ + protected $language; + + /** + * @var template + */ + protected $template; + + /** + * @var request_interface + */ + protected $request; + + /** + * @var installer + */ + protected $installer; + + /** + * @var install_helper + */ + protected $install_helper; + + /** + * Constructor + * + * @param helper $helper + * @param factory $factory + * @param navigation_provider $nav_provider + * @param language $language + * @param template $template + * @param request_interface $request + * @param installer $installer + * @param install_helper $install_helper + */ + public function __construct(helper $helper, factory $factory, navigation_provider $nav_provider, language $language, template $template, request_interface $request, installer $installer, install_helper $install_helper) + { + $this->controller_helper = $helper; + $this->iohandler_factory = $factory; + $this->menu_provider = $nav_provider; + $this->language = $language; + $this->template = $template; + $this->request = $request; + $this->installer = $installer; + $this->install_helper = $install_helper; + } + + /** + * Controller logic + * + * @return Response|StreamedResponse + * + * @throws http_exception When phpBB is already installed + */ + public function handle() + { + if ($this->install_helper->is_phpbb_installed()) + { + throw new http_exception(403, 'INSTALL_PHPBB_INSTALLED'); + } + + $this->template->assign_vars(array( + 'U_ACTION' => $this->controller_helper->route('phpbb_installer_install'), + )); + + // Set up input-output handler + if ($this->request->is_ajax()) + { + $this->iohandler_factory->set_environment('ajax'); + } + else + { + $this->iohandler_factory->set_environment('nojs'); + } + + // Set the appropriate input-output handler + $this->installer->set_iohandler($this->iohandler_factory->get()); + $this->controller_helper->handle_language_select(); + + if ($this->request->is_ajax()) + { + $installer = $this->installer; + $response = new StreamedResponse(); + $response->setCallback(function() use ($installer) { + $installer->run(); + }); + + // Try to bypass any server output buffers + $response->headers->set('X-Accel-Buffering', 'no'); + + return $response; + } + else + { + // Determine whether the installation was started or not + if (true) + { + // Set active stage + $this->menu_provider->set_nav_property( + array('install', 0, 'introduction'), + array( + 'selected' => true, + 'completed' => false, + ) + ); + + // If not, let's render the welcome page + $this->template->assign_vars(array( + 'SHOW_INSTALL_START_FORM' => true, + 'TITLE' => $this->language->lang('INSTALL_INTRO'), + 'CONTENT' => $this->language->lang('INSTALL_INTRO_BODY'), + )); + + /** @var \phpbb\install\helper\iohandler\iohandler_interface $iohandler */ + $iohandler = $this->iohandler_factory->get(); + $this->controller_helper->handle_navigation($iohandler); + + return $this->controller_helper->render('installer_install.html', 'INSTALL', true); + } + + // @todo: implement no js controller logic + } + } +} diff --git a/phpbb/install/controller/installer_index.php b/phpbb/install/controller/installer_index.php new file mode 100644 index 0000000..c2d9572 --- /dev/null +++ b/phpbb/install/controller/installer_index.php @@ -0,0 +1,81 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\controller; + +class installer_index +{ + /** + * @var helper + */ + protected $helper; + + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * @var \phpbb\template\template + */ + protected $template; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param helper $helper + * @param \phpbb\language\language $language + * @param \phpbb\template\template $template + * @param string $phpbb_root_path + */ + public function __construct(helper $helper, \phpbb\language\language $language, \phpbb\template\template $template, $phpbb_root_path) + { + $this->helper = $helper; + $this->language = $language; + $this->template = $template; + $this->phpbb_root_path = $phpbb_root_path; + } + + public function handle($mode) + { + $this->helper->handle_language_select(); + + switch ($mode) + { + case "intro": + $title = $this->language->lang('INTRODUCTION_TITLE'); + $body = $this->language->lang('INTRODUCTION_BODY'); + break; + case "support": + $title = $this->language->lang('SUPPORT_TITLE'); + $body = $this->language->lang('SUPPORT_BODY'); + break; + case "license": + $title = $this->language->lang('LICENSE_TITLE'); + $body = implode("
\n", file($this->phpbb_root_path . 'docs/LICENSE.txt')); + break; + } + + $this->template->assign_vars(array( + 'TITLE' => $title, + 'BODY' => $body, + )); + + return $this->helper->render('installer_main.html', $title, true); + } +} diff --git a/phpbb/install/controller/timeout_check.php b/phpbb/install/controller/timeout_check.php new file mode 100644 index 0000000..1c90e3c --- /dev/null +++ b/phpbb/install/controller/timeout_check.php @@ -0,0 +1,80 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\controller; + +use Symfony\Component\HttpFoundation\JsonResponse; + +class timeout_check +{ + /** + * @var helper + */ + protected $helper; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param helper $helper + * @param string $phpbb_root_path + */ + public function __construct(helper $helper, $phpbb_root_path) + { + $this->helper = $helper; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Controller for querying installer status + */ + public function status() + { + $lock_file = $this->phpbb_root_path . 'store/io_lock.lock'; + $response = new JsonResponse(); + + if (!file_exists($lock_file)) + { + $response->setData(array( + 'status' => 'fail', + )); + } + else + { + $fp = @fopen($lock_file, 'r'); + + if ($fp && flock($fp, LOCK_EX | LOCK_NB)) + { + $status = (filesize($lock_file) >= 2 && fread($fp, 2) === 'ok') ? 'continue' : 'fail'; + + $response->setData(array( + 'status' => $status, + )); + flock($fp, LOCK_UN); + fclose($fp); + } + else + { + $response->setData(array( + 'status' => 'running', + )); + } + } + + return $response; + } +} diff --git a/phpbb/install/controller/update.php b/phpbb/install/controller/update.php new file mode 100644 index 0000000..6b88827 --- /dev/null +++ b/phpbb/install/controller/update.php @@ -0,0 +1,166 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\controller; + +use phpbb\exception\http_exception; +use phpbb\install\helper\install_helper; +use phpbb\install\helper\iohandler\factory; +use phpbb\install\helper\navigation\navigation_provider; +use phpbb\install\installer; +use phpbb\language\language; +use phpbb\request\request_interface; +use phpbb\template\template; +use Symfony\Component\HttpFoundation\StreamedResponse; + +/** + * Updater controller + */ +class update +{ + /** + * @var helper + */ + protected $controller_helper; + + /** + * @var installer + */ + protected $installer; + + /** + * @var install_helper + */ + protected $install_helper; + + /** + * @var factory + */ + protected $iohandler_factory; + + /** + * @var language + */ + protected $language; + + /** + * @var navigation_provider + */ + protected $menu_provider; + + /** + * @var request_interface + */ + protected $request; + + /** + * @var template + */ + protected $template; + + /** + * Constructor + * + * @param helper $controller_helper + * @param installer $installer + * @param install_helper $install_helper + * @param factory $iohandler + * @param language $language + * @param navigation_provider $menu_provider + * @param request_interface $request + * @param template $template + */ + public function __construct(helper $controller_helper, installer $installer, install_helper $install_helper, factory $iohandler, language $language, navigation_provider $menu_provider, request_interface $request, template $template) + { + $this->controller_helper = $controller_helper; + $this->installer = $installer; + $this->install_helper = $install_helper; + $this->iohandler_factory = $iohandler; + $this->language = $language; + $this->menu_provider = $menu_provider; + $this->request = $request; + $this->template = $template; + } + + /** + * Controller entry point + * + * @return Response|StreamedResponse + * + * @throws http_exception When phpBB is not installed + */ + public function handle() + { + if (!$this->install_helper->is_phpbb_installed()) + { + throw new http_exception(403, 'INSTALL_PHPBB_NOT_INSTALLED'); + } + + $this->template->assign_vars(array( + 'U_ACTION' => $this->controller_helper->route('phpbb_installer_update'), + )); + + // Set up input-output handler + if ($this->request->is_ajax()) + { + $this->iohandler_factory->set_environment('ajax'); + } + else + { + $this->iohandler_factory->set_environment('nojs'); + } + + // Set the appropriate input-output handler + $this->installer->set_iohandler($this->iohandler_factory->get()); + $this->controller_helper->handle_language_select(); + + // Render the intro page + if ($this->request->is_ajax()) + { + $installer = $this->installer; + $response = new StreamedResponse(); + $response->setCallback(function() use ($installer) { + $installer->run(); + }); + + // Try to bypass any server output buffers + $response->headers->set('X-Accel-Buffering', 'no'); + $response->headers->set('Content-type', 'application/json'); + + return $response; + } + else + { + // Set active stage + $this->menu_provider->set_nav_property( + array('update', 0, 'introduction'), + array( + 'selected' => true, + 'completed' => false, + ) + ); + + $this->template->assign_vars(array( + 'SHOW_INSTALL_START_FORM' => true, + 'TITLE' => $this->language->lang('UPDATE_INSTALLATION'), + 'CONTENT' => $this->language->lang('UPDATE_INSTALLATION_EXPLAIN'), + )); + + /** @var \phpbb\install\helper\iohandler\iohandler_interface $iohandler */ + $iohandler = $this->iohandler_factory->get(); + $this->controller_helper->handle_navigation($iohandler); + + return $this->controller_helper->render('installer_update.html', 'UPDATE_INSTALLATION', true); + } + } +} diff --git a/phpbb/install/event/kernel_exception_subscriber.php b/phpbb/install/event/kernel_exception_subscriber.php new file mode 100644 index 0000000..60b7d9a --- /dev/null +++ b/phpbb/install/event/kernel_exception_subscriber.php @@ -0,0 +1,126 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\event; + +use phpbb\exception\exception_interface; +use phpbb\install\controller\helper; +use phpbb\language\language; +use phpbb\template\template; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpFoundation\JsonResponse; + +/** + * Exception handler for the installer + */ +class kernel_exception_subscriber implements EventSubscriberInterface +{ + /** + * @var helper + */ + protected $controller_helper; + + /** + * @var language + */ + protected $language; + + /** + * @var template + */ + protected $template; + + /** + * Constructor + * + * @param helper $controller_helper + * @param language $language + * @param template $template + */ + public function __construct(helper $controller_helper, language $language, template $template) + { + $this->controller_helper = $controller_helper; + $this->language = $language; + $this->template = $template; + } + + /** + * This listener is run when the KernelEvents::EXCEPTION event is triggered + * + * @param GetResponseForExceptionEvent $event + */ + public function on_kernel_exception(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + $message = $exception->getMessage(); + + if ($exception instanceof exception_interface) + { + $message = $this->language->lang_array($message, $exception->get_parameters()); + } + + if (!$event->getRequest()->isXmlHttpRequest()) + { + $this->template->assign_vars(array( + 'TITLE' => $this->language->lang('INFORMATION'), + 'BODY' => $message, + )); + + $response = $this->controller_helper->render( + 'installer_main.html', + $this->language->lang('INFORMATION'), + false, + 500 + ); + } + else + { + $data = array(); + + if (!empty($message)) + { + $data['message'] = $message; + } + + if (defined('DEBUG')) + { + $data['trace'] = $exception->getTrace(); + } + + $response = new JsonResponse($data, 500); + } + + if ($exception instanceof HttpExceptionInterface) + { + $response->setStatusCode($exception->getStatusCode()); + $response->headers->add($exception->getHeaders()); + } + + $event->setResponse($response); + } + + /** + * Returns an array of events the object is subscribed to + * + * @return array Array of events the object is subscribed to + */ + static public function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => 'on_kernel_exception', + ); + } +} diff --git a/phpbb/install/exception/cannot_build_container_exception.php b/phpbb/install/exception/cannot_build_container_exception.php new file mode 100644 index 0000000..6cf12b0 --- /dev/null +++ b/phpbb/install/exception/cannot_build_container_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\exception; + +/** + * Thrown when the container cannot be built + */ +class cannot_build_container_exception extends installer_exception +{ + +} diff --git a/phpbb/install/exception/file_updater_failure_exception.php b/phpbb/install/exception/file_updater_failure_exception.php new file mode 100644 index 0000000..46ba2ed --- /dev/null +++ b/phpbb/install/exception/file_updater_failure_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\exception; + +/** + * Thrown when the file updater fails + */ +class file_updater_failure_exception extends installer_exception +{ + +} diff --git a/phpbb/install/exception/installer_config_not_writable_exception.php b/phpbb/install/exception/installer_config_not_writable_exception.php new file mode 100644 index 0000000..51864c5 --- /dev/null +++ b/phpbb/install/exception/installer_config_not_writable_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\exception; + +/** + * Thrown when installer config is not writable to disk + */ +class installer_config_not_writable_exception extends installer_exception +{ + +} diff --git a/phpbb/install/exception/installer_exception.php b/phpbb/install/exception/installer_exception.php new file mode 100644 index 0000000..f17dca8 --- /dev/null +++ b/phpbb/install/exception/installer_exception.php @@ -0,0 +1,24 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\exception; + +use phpbb\exception\runtime_exception; + +/** + * Installer's base exception + */ +class installer_exception extends runtime_exception +{ + +} diff --git a/phpbb/install/exception/invalid_dbms_exception.php b/phpbb/install/exception/invalid_dbms_exception.php new file mode 100644 index 0000000..38de5f6 --- /dev/null +++ b/phpbb/install/exception/invalid_dbms_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\exception; + +/** + * Thrown when an unavailable DBMS has been selected + */ +class invalid_dbms_exception extends installer_exception +{ + +} diff --git a/phpbb/install/exception/jump_to_restart_point_exception.php b/phpbb/install/exception/jump_to_restart_point_exception.php new file mode 100644 index 0000000..b628c4f --- /dev/null +++ b/phpbb/install/exception/jump_to_restart_point_exception.php @@ -0,0 +1,44 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\exception; + +class jump_to_restart_point_exception extends installer_exception +{ + /** + * @var string + */ + protected $restart_point_name; + + /** + * Constructor + * + * @param string $restart_point_name + */ + public function __construct($restart_point_name) + { + $this->restart_point_name = $restart_point_name; + + parent::__construct(); + } + + /** + * Returns the restart point's name + * + * @return string + */ + public function get_restart_point_name() + { + return $this->restart_point_name; + } +} diff --git a/phpbb/install/exception/resource_limit_reached_exception.php b/phpbb/install/exception/resource_limit_reached_exception.php new file mode 100644 index 0000000..025e09f --- /dev/null +++ b/phpbb/install/exception/resource_limit_reached_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\exception; + +/** + * Thrown when the installer is out of memory or time + */ +class resource_limit_reached_exception extends installer_exception +{ + +} diff --git a/phpbb/install/exception/user_interaction_required_exception.php b/phpbb/install/exception/user_interaction_required_exception.php new file mode 100644 index 0000000..d65a448 --- /dev/null +++ b/phpbb/install/exception/user_interaction_required_exception.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\exception; + +/** + * This exception should be thrown when user interaction is inevitable + * + * Note: Please note that the output should already be setup for the user + * when you use throw this exception + */ +class user_interaction_required_exception extends installer_exception +{ + +} diff --git a/phpbb/install/helper/config.php b/phpbb/install/helper/config.php new file mode 100644 index 0000000..7eb0ae3 --- /dev/null +++ b/phpbb/install/helper/config.php @@ -0,0 +1,452 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper; + +use phpbb\install\exception\installer_config_not_writable_exception; + +/** + * Stores common settings and installation status + */ +class config +{ + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * Array which contains config settings for the installer + * + * The array will also store all the user input, as well as any + * data that is passed to other tasks by a task. + * + * @var array + */ + protected $installer_config; + + /** + * @var string + */ + protected $install_config_file; + + /** + * @var \bantu\IniGetWrapper\IniGetWrapper + */ + protected $php_ini; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Array containing progress information + * + * @var array + */ + protected $progress_data; + + /** + * Array containing system information + * + * The array contains run time and memory limitations. + * + * @var array + */ + protected $system_data; + + /** + * Array containing navigation bar information + * + * @var array + */ + protected $navigation_data; + + /** + * Flag indicating that config file should be cleaned up + * + * @var bool + */ + protected $do_clean_up; + + /** + * Constructor + */ + public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, \bantu\IniGetWrapper\IniGetWrapper $php_ini, $phpbb_root_path) + { + $this->filesystem = $filesystem; + $this->php_ini = $php_ini; + $this->phpbb_root_path = $phpbb_root_path; + $this->do_clean_up = false; + + // Set up data arrays + $this->navigation_data = array(); + $this->installer_config = array(); + $this->system_data = array(); + $this->progress_data = array( + 'last_task_module_name' => '', // Stores the service name of the latest finished module + 'last_task_module_index' => 0, // Stores the index of the latest finished module + 'last_task_index' => 0, // Stores the index of the latest finished task + 'max_task_progress' => 0, + 'current_task_progress' => 0, + '_restart_points' => array(), + 'use_restart_point' => false, + ); + + $this->install_config_file = $this->phpbb_root_path . 'store/install_config.php'; + + $this->setup_system_data(); + } + + /** + * Returns data for a specified parameter + * + * @param string $param_name Name of the parameter to return + * @param mixed $default Default value to return when the specified data + * does not exist. + * + * @return mixed value of the specified parameter or the default value if the data + * cannot be recovered. + */ + public function get($param_name, $default = false) + { + return (isset($this->installer_config[$param_name])) ? $this->installer_config[$param_name] : $default; + } + + /** + * Sets a parameter in installer_config + * + * @param string $param_name Name of the parameter + * @param mixed $value Values to set the parameter + */ + public function set($param_name, $value) + { + $this->installer_config = array_merge($this->installer_config, array( + $param_name => $value, + )); + } + + /** + * Returns system parameter + * + * @param string $param_name Name of the parameter + * + * @return mixed Returns system parameter if it is defined, false otherwise + */ + public function system_get($param_name) + { + return (isset($this->system_data[$param_name])) ? $this->system_data[$param_name] : false; + } + + /** + * Returns remaining time until the run time limit + * + * @return int Remaining time until the run time limit in seconds + */ + public function get_time_remaining() + { + if ($this->system_data['max_execution_time'] <= 0) + { + return PHP_INT_MAX; + } + + return ($this->system_data['start_time'] + $this->system_data['max_execution_time']) - microtime(true); + } + + /** + * Returns remaining memory available for PHP + * + * @return int Remaining memory until reaching the limit + */ + public function get_memory_remaining() + { + if ($this->system_data['memory_limit'] <= 0) + { + return 1; + } + + if (function_exists('memory_get_usage')) + { + return ($this->system_data['memory_limit'] - memory_get_usage()); + } + + // If we cannot get the information then just return a positive number (and cross fingers) + return 1; + } + + /** + * Saves the latest executed task + * + * @param int $task_service_index Index of the installer task service in the module + */ + public function set_finished_task($task_service_index) + { + $this->progress_data['last_task_index'] = $task_service_index; + } + + /** + * Set active module + * + * @param string $module_service_name Name of the installer module service + * @param int $module_service_index Index of the installer module service + */ + public function set_active_module($module_service_name, $module_service_index) + { + $this->progress_data['last_task_module_name'] = $module_service_name; + $this->progress_data['last_task_module_index'] = $module_service_index; + } + + /** + * Getter for progress data + * + * @return array + */ + public function get_progress_data() + { + return $this->progress_data; + } + + /** + * Recovers install configuration from file + */ + public function load_config() + { + if (!$this->filesystem->exists($this->install_config_file)) + { + return; + } + + $file_content = @file_get_contents($this->install_config_file); + $serialized_data = trim(substr($file_content, 8)); + + $installer_config = array(); + $progress_data = array(); + $navigation_data = array(); + + if (!empty($serialized_data)) + { + $unserialized_data = json_decode($serialized_data, true); + + $installer_config = (is_array($unserialized_data['installer_config'])) ? $unserialized_data['installer_config'] : array(); + $progress_data = (is_array($unserialized_data['progress_data'])) ? $unserialized_data['progress_data'] : array(); + $navigation_data = (is_array($unserialized_data['navigation_data'])) ? $unserialized_data['navigation_data'] : array(); + } + + $this->installer_config = array_merge($this->installer_config, $installer_config); + $this->progress_data = array_merge($this->progress_data, $progress_data); + $this->navigation_data = array_merge($this->navigation_data, $navigation_data); + } + + /** + * Creates a progress restart point + * + * Restart points can be used to repeat certain tasks periodically. + * You need to call this method from the first task you want to repeat. + * + * @param string $name Name of the restart point + */ + public function create_progress_restart_point($name) + { + $tmp_progress_data = $this->progress_data; + unset($tmp_progress_data['_restart_points']); + + $this->progress_data['_restart_points'][$name] = $tmp_progress_data; + } + + /** + * Set restart point to continue from + * + * @param string $name Name of the restart point + * + * @return bool Returns false if the restart point name does not exist, otherwise true + */ + public function jump_to_restart_point($name) + { + if (!isset($this->progress_data['_restart_points'][$name]) || empty($this->progress_data['_restart_points'][$name])) + { + return false; + } + + foreach ($this->progress_data['_restart_points'][$name] as $key => $value) + { + $this->progress_data[$key] = $value; + } + + return true; + } + + /** + * Returns whether a restart point with a given name exists or not + * + * @param string $name Name of the restart point + * + * @return bool + */ + public function has_restart_point($name) + { + return isset($this->progress_data['_restart_points'][$name]); + } + + /** + * Dumps install configuration to disk + */ + public function save_config() + { + if ($this->do_clean_up) + { + @unlink($this->install_config_file); + return; + } + + // Create array to save + $save_array = array( + 'installer_config' => $this->installer_config, + 'progress_data' => $this->progress_data, + 'navigation_data' => $this->navigation_data, + ); + + // Create file content + $file_content = 'install_config_file, 'w'); + if (!$fp) + { + throw new installer_config_not_writable_exception(); + } + + fwrite($fp, $file_content); + fclose($fp); + // Enforce 0600 permission for install config + $this->filesystem->chmod([$this->install_config_file], 0600); + } + + /** + * Increments the task progress + * + * @param int $increment_by The amount to increment by + */ + public function increment_current_task_progress($increment_by = 1) + { + $this->progress_data['current_task_progress'] += $increment_by; + + if ($this->progress_data['current_task_progress'] > $this->progress_data['max_task_progress']) + { + $this->progress_data['current_task_progress'] = $this->progress_data['max_task_progress']; + } + } + + /** + * Sets the task progress to a specific number + * + * @param int $task_progress The task progress number to be set + */ + public function set_current_task_progress($task_progress) + { + $this->progress_data['current_task_progress'] = $task_progress; + } + + /** + * Sets the number of tasks belonging to the installer in the current mode. + * + * @param int $task_progress_count Number of tasks + */ + public function set_task_progress_count($task_progress_count) + { + $this->progress_data['max_task_progress'] = $task_progress_count; + } + + /** + * Returns the number of the current task being executed + * + * @return int + */ + public function get_current_task_progress() + { + return $this->progress_data['current_task_progress']; + } + + /** + * Returns the number of tasks belonging to the installer in the current mode. + * + * @return int + */ + public function get_task_progress_count() + { + return $this->progress_data['max_task_progress']; + } + + /** + * Marks stage as completed in the navigation bar + * + * @param array $nav_path Array to the navigation elem + */ + public function set_finished_navigation_stage($nav_path) + { + if (isset($this->navigation_data['finished']) && in_array($nav_path, $this->navigation_data['finished'])) + { + return; + } + + $this->navigation_data['finished'][] = $nav_path; + } + + /** + * Marks stage as active in the navigation bar + * + * @param array $nav_path Array to the navigation elem + */ + public function set_active_navigation_stage($nav_path) + { + $this->navigation_data['active'] = $nav_path; + } + + /** + * Returns navigation data + * + * @return array + */ + public function get_navigation_data() + { + return $this->navigation_data; + } + + /** + * Removes install config file + */ + public function clean_up_config_file() + { + $this->do_clean_up = true; + @unlink($this->install_config_file); + } + + /** + * Filling up system_data array + */ + protected function setup_system_data() + { + // Query maximum runtime from php.ini + $execution_time = $this->php_ini->getNumeric('max_execution_time'); + $execution_time = min(15, $execution_time / 2); + $this->system_data['max_execution_time'] = $execution_time; + + // Set start time + $this->system_data['start_time'] = microtime(true); + + // Get memory limit + $this->system_data['memory_limit'] = $this->php_ini->getBytes('memory_limit'); + } +} diff --git a/phpbb/install/helper/container_factory.php b/phpbb/install/helper/container_factory.php new file mode 100644 index 0000000..9e372fe --- /dev/null +++ b/phpbb/install/helper/container_factory.php @@ -0,0 +1,191 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper; + +use phpbb\install\exception\cannot_build_container_exception; +use phpbb\language\language; +use phpbb\request\request; + +class container_factory +{ + /** + * @var language + */ + protected $language; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * @var \phpbb\request\request + */ + protected $request; + + /** + * @var update_helper + */ + protected $update_helper; + + /** + * The full phpBB container + * + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * Constructor + * + * @param language $language Language service + * @param request $request Request interface + * @param update_helper $update_helper Update helper + * @param string $phpbb_root_path Path to phpBB's root + * @param string $php_ext Extension of PHP files + */ + public function __construct(language $language, request $request, update_helper $update_helper, $phpbb_root_path, $php_ext) + { + $this->language = $language; + $this->request = $request; + $this->update_helper = $update_helper; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->container = null; + } + + /** + * Container getter + * + * @param null|string $service_name Name of the service to return + * + * @return \Symfony\Component\DependencyInjection\ContainerInterface|Object phpBB's dependency injection container + * or the service specified in $service_name + * + * @throws \phpbb\install\exception\cannot_build_container_exception When container cannot be built + * @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException If the service is not defined + * @throws \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException When a circular reference is detected + * @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException When the service is not defined + */ + public function get($service_name = null) + { + // Check if container was built, if not try to build it + if ($this->container === null) + { + $this->build_container(); + } + + return ($service_name === null) ? $this->container : $this->container->get($service_name); + } + + /** + * Returns the specified parameter from the container + * + * @param string $param_name + * + * @return mixed + * + * @throws \phpbb\install\exception\cannot_build_container_exception When container cannot be built + */ + public function get_parameter($param_name) + { + // Check if container was built, if not try to build it + if ($this->container === null) + { + $this->build_container(); + } + + return $this->container->getParameter($param_name); + } + + /** + * Build dependency injection container + * + * @throws \phpbb\install\exception\cannot_build_container_exception When container cannot be built + */ + protected function build_container() + { + // If the container has been already built just return. + // Although this should never happen + if ($this->container instanceof \Symfony\Component\DependencyInjection\ContainerInterface) + { + return; + } + + // Check whether container can be built + // We need config.php for that so let's check if it has been set up yet + if (!filesize($this->phpbb_root_path . 'config.' . $this->php_ext)) + { + throw new cannot_build_container_exception(); + } + + $phpbb_config_php_file = new \phpbb\config_php_file($this->phpbb_root_path, $this->php_ext); + $phpbb_container_builder = new \phpbb\di\container_builder($this->phpbb_root_path, $this->php_ext); + + // For BC with functions that we need during install + global $phpbb_container, $table_prefix; + + $disable_super_globals = $this->request->super_globals_disabled(); + + // This is needed because container_builder::get_env_parameters() uses $_SERVER + if ($disable_super_globals) + { + $this->request->enable_super_globals(); + } + + $other_config_path = $this->phpbb_root_path . 'install/update/new/config'; + $config_path = (is_dir($other_config_path)) ? $other_config_path : $this->phpbb_root_path . 'config'; + + $this->container = $phpbb_container_builder + ->with_environment('production') + ->with_config($phpbb_config_php_file) + ->with_config_path($config_path) + ->without_compiled_container() + ->get_container(); + + // Setting request is required for the compatibility globals as those are generated from + // this container + if (!$this->container->isFrozen()) + { + $this->container->register('request')->setSynthetic(true); + $this->container->register('language')->setSynthetic(true); + } + + $this->container->set('request', $this->request); + $this->container->set('language', $this->language); + + $this->container->compile(); + + $phpbb_container = $this->container; + $table_prefix = $phpbb_config_php_file->get('table_prefix'); + + // Restore super globals to previous state + if ($disable_super_globals) + { + $this->request->disable_super_globals(); + } + + // Get compatibilty globals and constants + $this->update_helper->include_file('includes/compatibility_globals.' . $this->php_ext); + + register_compatibility_globals(); + + $this->update_helper->include_file('includes/constants.' . $this->php_ext); + } +} diff --git a/phpbb/install/helper/database.php b/phpbb/install/helper/database.php new file mode 100644 index 0000000..fa5a10c --- /dev/null +++ b/phpbb/install/helper/database.php @@ -0,0 +1,439 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper; + +use phpbb\install\exception\invalid_dbms_exception; + +/** + * Database related general functionality for installer + */ +class database +{ + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var array + */ + protected $supported_dbms = array( + // Note: php 5.5 alpha 2 deprecated mysql. + // Keep mysqli before mysql in this list. + 'mysqli' => array( + 'LABEL' => 'MySQL with MySQLi Extension', + 'SCHEMA' => 'mysql_41', + 'MODULE' => 'mysqli', + 'DELIM' => ';', + 'DRIVER' => 'phpbb\db\driver\mysqli', + 'AVAILABLE' => true, + '2.0.x' => true, + ), + 'mysql' => array( + 'LABEL' => 'MySQL', + 'SCHEMA' => 'mysql', + 'MODULE' => 'mysql', + 'DELIM' => ';', + 'DRIVER' => 'phpbb\db\driver\mysql', + 'AVAILABLE' => true, + '2.0.x' => true, + ), + 'mssql_odbc'=> array( + 'LABEL' => 'MS SQL Server [ ODBC ]', + 'SCHEMA' => 'mssql', + 'MODULE' => 'odbc', + 'DELIM' => ';', + 'DRIVER' => 'phpbb\db\driver\mssql_odbc', + 'AVAILABLE' => true, + '2.0.x' => true, + ), + 'mssqlnative' => array( + 'LABEL' => 'MS SQL Server 2005+ [ Native ]', + 'SCHEMA' => 'mssql', + 'MODULE' => 'sqlsrv', + 'DELIM' => ';', + 'DRIVER' => 'phpbb\db\driver\mssqlnative', + 'AVAILABLE' => true, + '2.0.x' => false, + ), + 'oracle' => array( + 'LABEL' => 'Oracle', + 'SCHEMA' => 'oracle', + 'MODULE' => 'oci8', + 'DELIM' => ';', + 'DRIVER' => 'phpbb\db\driver\oracle', + 'AVAILABLE' => true, + '2.0.x' => false, + ), + 'postgres' => array( + 'LABEL' => 'PostgreSQL 8.3+', + 'SCHEMA' => 'postgres', + 'MODULE' => 'pgsql', + 'DELIM' => ';', + 'DRIVER' => 'phpbb\db\driver\postgres', + 'AVAILABLE' => true, + '2.0.x' => true, + ), + 'sqlite3' => array( + 'LABEL' => 'SQLite3', + 'SCHEMA' => 'sqlite', + 'MODULE' => 'sqlite3', + 'DELIM' => ';', + 'DRIVER' => 'phpbb\db\driver\sqlite3', + 'AVAILABLE' => true, + '2.0.x' => false, + ), + ); + + /** + * Constructor + * + * @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem interface + * @param string $phpbb_root_path Path to phpBB's root + */ + public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $phpbb_root_path) + { + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Returns an array of available DBMS supported by phpBB + * + * If a DBMS is specified it will only return data for that DBMS + * and will load its extension if necessary. + * + * @param mixed $dbms name of the DBMS that's info is required or false for all DBMS info + * @param bool $return_unavailable set it to true if you expect unavailable but supported DBMS + * returned as well + * @param bool $only_20x_options set it to true if you only want to recover 2.0.x options + * + * @return array Array of available and supported DBMS + */ + public function get_available_dbms($dbms = false, $return_unavailable = false, $only_20x_options = false) + { + $available_dbms = $this->supported_dbms; + + if ($dbms) + { + if (isset($this->supported_dbms[$dbms])) + { + $available_dbms = array($dbms => $this->supported_dbms[$dbms]); + } + else + { + return array(); + } + } + + $any_dbms_available = false; + foreach ($available_dbms as $db_name => $db_array) + { + if ($only_20x_options && !$db_array['2.0.x']) + { + if ($return_unavailable) + { + $available_dbms[$db_name]['AVAILABLE'] = false; + } + else + { + unset($available_dbms[$db_name]); + } + + continue; + } + + $dll = $db_array['MODULE']; + if (!@extension_loaded($dll)) + { + if ($return_unavailable) + { + $available_dbms[$db_name]['AVAILABLE'] = false; + } + else + { + unset($available_dbms[$db_name]); + } + + continue; + } + + $any_dbms_available = true; + } + + if ($return_unavailable) + { + $available_dbms['ANY_DB_SUPPORT'] = $any_dbms_available; + } + + return $available_dbms; + } + + /** + * Removes "/* style" as well as "# style" comments from $input. + * + * @param string $sql_query Input string + * + * @return string Input string with comments removed + */ + public function remove_comments($sql_query) + { + // Remove /* */ comments (http://ostermiller.org/findcomment.html) + $sql_query = preg_replace('#/\*(.|[\r\n])*?\*/#', "\n", $sql_query); + + // Remove # style comments + $sql_query = preg_replace('/\n{2,}/', "\n", preg_replace('/^#.*$/m', "\n", $sql_query)); + + return $sql_query; + } + + /** + * split_sql_file() will split an uploaded sql file into single sql statements. + * + * Note: expects trim() to have already been run on $sql. + * + * @param string $sql SQL statements + * @param string $delimiter Delimiter between sql statements + * + * @return array Array of sql statements + */ + public function split_sql_file($sql, $delimiter) + { + $sql = str_replace("\r" , '', $sql); + $data = preg_split('/' . preg_quote($delimiter, '/') . '$/m', $sql); + + $data = array_map('trim', $data); + + // The empty case + $end_data = end($data); + + if (empty($end_data)) + { + unset($data[key($data)]); + } + + return $data; + } + + /** + * Validates table prefix + * + * @param string $dbms The selected dbms + * @param string $table_prefix The table prefix to validate + * + * @return bool|array true if table prefix is valid, array of errors otherwise + * + * @throws \phpbb\install\exception\invalid_dbms_exception When $dbms is not a valid + */ + public function validate_table_prefix($dbms, $table_prefix) + { + $errors = array(); + + if (!preg_match('#^[a-zA-Z][a-zA-Z0-9_]*$#', $table_prefix)) + { + $errors[] = array( + 'title' => 'INST_ERR_DB_INVALID_PREFIX', + ); + } + + // Do dbms specific checks + $dbms_info = $this->get_available_dbms($dbms); + switch ($dbms_info[$dbms]['SCHEMA']) + { + case 'mysql': + case 'mysql_41': + $prefix_length = 36; + break; + case 'mssql': + $prefix_length = 90; + break; + case 'oracle': + $prefix_length = 6; + break; + case 'postgres': + $prefix_length = 36; + break; + case 'sqlite': + $prefix_length = 200; + break; + default: + throw new invalid_dbms_exception(); + break; + } + + // Check the prefix length to ensure that index names are not too long + if (strlen($table_prefix) > $prefix_length) + { + $errors[] = array( + 'title' => array('INST_ERR_PREFIX_TOO_LONG', $prefix_length), + ); + } + + return (empty($errors)) ? true : $errors; + } + + /** + * Check if the user provided database parameters are correct + * + * This function checks the database connection data and also checks for + * any other problems that could cause an error during the installation + * such as if there is any database table names conflicting. + * + * Note: The function assumes that $table_prefix has been already validated + * with validate_table_prefix(). + * + * @param string $dbms Selected database type + * @param string $dbhost Database host address + * @param int $dbport Database port number + * @param string $dbuser Database username + * @param string $dbpass Database password + * @param string $dbname Database name + * @param string $table_prefix Database table prefix + * + * @return array|bool Returns true if test is successful, array of errors otherwise + */ + public function check_database_connection($dbms, $dbhost, $dbport, $dbuser, $dbpass, $dbname, $table_prefix) + { + $dbms_info = $this->get_available_dbms($dbms); + $dbms_info = $dbms_info[$dbms]; + $errors = array(); + + // Instantiate it and set return on error true + /** @var \phpbb\db\driver\driver_interface $db */ + $db = new $dbms_info['DRIVER']; + $db->sql_return_on_error(true); + + // Check that we actually have a database name before going any further + if (!in_array($dbms_info['SCHEMA'], array('sqlite', 'oracle'), true) && $dbname === '') + { + $errors[] = array( + 'title' => 'INST_ERR_DB_NO_NAME', + ); + } + + // Make sure we don't have a daft user who thinks having the SQLite database in the forum directory is a good idea + if ($dbms_info['SCHEMA'] === 'sqlite' + && stripos($this->filesystem->realpath($dbhost), $this->filesystem->realpath($this->phpbb_root_path) === 0)) + { + $errors[] = array( + 'title' =>'INST_ERR_DB_FORUM_PATH', + ); + } + + // Check if SQLite database is writable + if ($dbms_info['SCHEMA'] === 'sqlite' + && (($this->filesystem->exists($dbhost) && !$this->filesystem->is_writable($dbhost)) || !$this->filesystem->is_writable(pathinfo($dbhost, PATHINFO_DIRNAME)))) + { + $errors[] = array( + 'title' =>'INST_ERR_DB_NO_WRITABLE', + ); + } + + // Try to connect to db + if (is_array($db->sql_connect($dbhost, $dbuser, $dbpass, $dbname, $dbport, false, true))) + { + $db_error = $db->sql_error(); + $errors[] = array( + 'title' => 'INST_ERR_DB_CONNECT', + 'description' => ($db_error['message']) ? utf8_convert_message($db_error['message']) : 'INST_ERR_DB_NO_ERROR', + ); + } + else + { + // Check if there is any table name collisions + $temp_prefix = strtolower($table_prefix); + $table_ary = array( + $temp_prefix . 'attachments', + $temp_prefix . 'config', + $temp_prefix . 'sessions', + $temp_prefix . 'topics', + $temp_prefix . 'users', + ); + + $db_tools_factory = new \phpbb\db\tools\factory(); + $db_tools = $db_tools_factory->get($db); + $tables = $db_tools->sql_list_tables(); + $tables = array_map('strtolower', $tables); + $table_intersect = array_intersect($tables, $table_ary); + + if (count($table_intersect)) + { + $errors[] = array( + 'title' => 'INST_ERR_PREFIX', + ); + } + + // Check if database version is supported + switch ($dbms) + { + case 'mysqli': + if (version_compare($db->sql_server_info(true), '4.1.3', '<')) + { + $errors[] = array( + 'title' => 'INST_ERR_DB_NO_MYSQLI', + ); + } + break; + case 'sqlite3': + if (version_compare($db->sql_server_info(true), '3.6.15', '<')) + { + $errors[] = array( + 'title' => 'INST_ERR_DB_NO_SQLITE3', + ); + } + break; + case 'oracle': + $sql = "SELECT * + FROM NLS_DATABASE_PARAMETERS + WHERE PARAMETER = 'NLS_RDBMS_VERSION' + OR PARAMETER = 'NLS_CHARACTERSET'"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $stats[$row['parameter']] = $row['value']; + } + $db->sql_freeresult($result); + + if (version_compare($stats['NLS_RDBMS_VERSION'], '9.2', '<') && $stats['NLS_CHARACTERSET'] !== 'UTF8') + { + $errors[] = array( + 'title' => 'INST_ERR_DB_NO_ORACLE', + ); + } + break; + case 'postgres': + $sql = "SHOW server_encoding;"; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row['server_encoding'] !== 'UNICODE' && $row['server_encoding'] !== 'UTF8') + { + $errors[] = array( + 'title' => 'INST_ERR_DB_NO_POSTGRES', + ); + } + break; + } + } + + return (empty($errors)) ? true : $errors; + } +} diff --git a/phpbb/install/helper/file_updater/compression_file_updater.php b/phpbb/install/helper/file_updater/compression_file_updater.php new file mode 100644 index 0000000..ede992f --- /dev/null +++ b/phpbb/install/helper/file_updater/compression_file_updater.php @@ -0,0 +1,133 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\file_updater; + +use phpbb\install\helper\update_helper; + +/** + * File updater for generating archive with updated files + */ +class compression_file_updater implements file_updater_interface +{ + /** + * @var \compress + */ + protected $compress; + + /** + * @var update_helper + */ + protected $update_helper; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param update_helper $update_helper + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(update_helper $update_helper, $phpbb_root_path, $php_ext) + { + $this->compress = null; + $this->update_helper = $update_helper; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Set the compression method + * + * @param string $method Compression method's file extension + * + * @return string Archive's filename + */ + public function init($method) + { + $this->update_helper->include_file('includes/functions_compress.' . $this->php_ext); + + $archive_filename = 'update_archive_' . time() . '_' . uniqid(); + $path = $this->phpbb_root_path . 'store/' . $archive_filename . '' . $method; + + if ($method === '.zip') + { + $this->compress = new \compress_zip('w', $path); + } + else + { + $this->compress = new \compress_tar('w', $path, $method); + } + + return $path; + } + + /** + * Close archive writing process + */ + public function close() + { + $this->compress->close(); + } + + /** + * {@inheritdoc} + */ + public function delete_file($path_to_file) + { + // We do absolutely nothing here, as this function is called when a file should be + // removed from the filesystem, but since this is an archive generator, it clearly + // cannot do that. + } + + /** + * {@inheritdoc} + */ + public function create_new_file($path_to_file_to_create, $source, $create_from_content = false) + { + if ($create_from_content) + { + $this->compress->add_data($source, $path_to_file_to_create); + } + else + { + $this->compress->add_custom_file($source, $path_to_file_to_create); + } + } + + /** + * {@inheritdoc} + */ + public function update_file($path_to_file_to_update, $source, $create_from_content = false) + { + // Both functions are identical here + $this->create_new_file($path_to_file_to_update, $source, $create_from_content); + } + + /** + * {@inheritdoc} + */ + public function get_method_name() + { + return 'compression'; + } +} diff --git a/phpbb/install/helper/file_updater/factory.php b/phpbb/install/helper/file_updater/factory.php new file mode 100644 index 0000000..d3a2f22 --- /dev/null +++ b/phpbb/install/helper/file_updater/factory.php @@ -0,0 +1,69 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\file_updater; + +use phpbb\di\service_collection; +use phpbb\install\exception\file_updater_failure_exception; + +/** + * File updater factory + */ +class factory +{ + /** + * @var array + */ + protected $file_updaters; + + /** + * Constructor + * + * @param service_collection $collection File updater service collection + */ + public function __construct(service_collection $collection) + { + foreach ($collection as $service) + { + $this->register($service); + } + } + + /** + * Register updater object + * + * @param file_updater_interface $updater Updater object + */ + public function register(file_updater_interface $updater) + { + $name = $updater->get_method_name(); + $this->file_updaters[$name] = $updater; + } + + /** + * Returns file updater object + * + * @param string $name Name of the updater method + * + * @throws file_updater_failure_exception When the specified file updater does not exist + */ + public function get($name) + { + if (!isset($this->file_updaters[$name])) + { + throw new file_updater_failure_exception(); + } + + return $this->file_updaters[$name]; + } +} diff --git a/phpbb/install/helper/file_updater/file_updater.php b/phpbb/install/helper/file_updater/file_updater.php new file mode 100644 index 0000000..cc0f5c6 --- /dev/null +++ b/phpbb/install/helper/file_updater/file_updater.php @@ -0,0 +1,202 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\file_updater; + +use phpbb\filesystem\exception\filesystem_exception; +use phpbb\filesystem\filesystem; +use phpbb\install\exception\file_updater_failure_exception; + +/** + * File updater for direct filesystem access + */ +class file_updater implements file_updater_interface +{ + /** + * @var filesystem + */ + protected $filesystem; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param filesystem $filesystem + * @param string $phpbb_root_path + */ + public function __construct(filesystem $filesystem, $phpbb_root_path) + { + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * {@inheritdoc} + * + * @throws file_updater_failure_exception When the file is not writable + * @throws filesystem_exception When the filesystem class fails + */ + public function delete_file($path_to_file) + { + $this->filesystem->remove($this->phpbb_root_path . $path_to_file); + } + + /** + * {@inheritdoc} + * + * @throws file_updater_failure_exception When the file is not writable + * @throws filesystem_exception When the filesystem class fails + */ + public function create_new_file($path_to_file_to_create, $source, $create_from_content = false) + { + $path_to_file_to_create = $this->phpbb_root_path . $path_to_file_to_create; + + $dir = dirname($path_to_file_to_create); + if (!$this->filesystem->exists($dir)) + { + $this->make_dir($dir); + } + + $original_dir_perms = false; + + if (!$this->filesystem->is_writable($dir)) + { + // Extract last 9 bits we actually need + $original_dir_perms = @fileperms($dir) & 511; + $this->filesystem->phpbb_chmod($dir, filesystem::CHMOD_ALL); + } + + if (!$create_from_content) + { + try + { + $this->filesystem->copy($source, $path_to_file_to_create); + } + catch (filesystem_exception $e) + { + $this->write_file($path_to_file_to_create, $source, $create_from_content); + } + } + else + { + $this->write_file($path_to_file_to_create, $source, $create_from_content); + } + + if ($original_dir_perms !== false) + { + $this->filesystem->phpbb_chmod($dir, $original_dir_perms); + } + } + + /** + * {@inheritdoc} + * + * @throws file_updater_failure_exception When the file is not writable + * @throws filesystem_exception When the filesystem class fails + */ + public function update_file($path_to_file_to_update, $source, $create_from_content = false) + { + $path_to_file_to_update = $this->phpbb_root_path . $path_to_file_to_update; + $original_file_perms = false; + + // Maybe necessary for binary files + $dir = dirname($path_to_file_to_update); + if (!$this->filesystem->exists($dir)) + { + $this->make_dir($dir); + } + + if (!$this->filesystem->is_writable($path_to_file_to_update)) + { + // Extract last 9 bits we actually need + $original_file_perms = @fileperms($path_to_file_to_update) & 511; + $this->filesystem->phpbb_chmod($path_to_file_to_update, filesystem::CHMOD_WRITE); + } + + if (!$create_from_content) + { + try + { + $this->filesystem->copy($source, $path_to_file_to_update, true); + } + catch (filesystem_exception $e) + { + $this->write_file($path_to_file_to_update, $source, $create_from_content); + } + } + else + { + $this->write_file($path_to_file_to_update, $source, $create_from_content); + } + + if ($original_file_perms !== false) + { + $this->filesystem->phpbb_chmod($path_to_file_to_update, $original_file_perms); + } + } + + /** + * Creates directory structure + * + * @param string $path Path to the directory where the file should be placed (and non-existent) + */ + private function make_dir($path) + { + if (is_dir($path)) + { + return; + } + + $path = str_replace(DIRECTORY_SEPARATOR, '/', $path); + $this->filesystem->mkdir($path, 493); // 493 === 0755 + } + + /** + * Fallback function for file writing + * + * @param string $path_to_file Path to the file's location + * @param string $source Path to file to copy or string with the new file's content + * @param bool|false $create_from_content Whether or not to use $source as the content, false by default + * + * @throws file_updater_failure_exception When the file is not writable + */ + private function write_file($path_to_file, $source, $create_from_content = false) + { + if (!$create_from_content) + { + $source = @file_get_contents($source); + } + + $file_pointer = @fopen($path_to_file, 'w'); + + if (!is_resource($file_pointer)) + { + throw new file_updater_failure_exception(); + } + + @fwrite($file_pointer, $source); + @fclose($file_pointer); + } + + /** + * {@inheritdoc} + */ + public function get_method_name() + { + return 'direct_file'; + } +} diff --git a/phpbb/install/helper/file_updater/file_updater_interface.php b/phpbb/install/helper/file_updater/file_updater_interface.php new file mode 100644 index 0000000..b13d7c9 --- /dev/null +++ b/phpbb/install/helper/file_updater/file_updater_interface.php @@ -0,0 +1,49 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\file_updater; + +interface file_updater_interface +{ + /** + * Deletes a file + * + * @param string $path_to_file Path to the file to delete + */ + public function delete_file($path_to_file); + + /** + * Creates a new file + * + * @param string $path_to_file_to_create Path to the new file's location + * @param string $source Path to file to copy or string with the new file's content + * @param bool $create_from_content Whether or not to use $source as the content, false by default + */ + public function create_new_file($path_to_file_to_create, $source, $create_from_content = false); + + /** + * Update file + * + * @param string $path_to_file_to_update Path to the file's location + * @param string $source Path to file to copy or string with the new file's content + * @param bool $create_from_content Whether or not to use $source as the content, false by default + */ + public function update_file($path_to_file_to_update, $source, $create_from_content = false); + + /** + * Returns the name of the file updater method + * + * @return string + */ + public function get_method_name(); +} diff --git a/phpbb/install/helper/file_updater/ftp_file_updater.php b/phpbb/install/helper/file_updater/ftp_file_updater.php new file mode 100644 index 0000000..5cdc331 --- /dev/null +++ b/phpbb/install/helper/file_updater/ftp_file_updater.php @@ -0,0 +1,136 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\file_updater; + +use phpbb\install\helper\update_helper; + +/** + * File updater for FTP updates + */ +class ftp_file_updater implements file_updater_interface +{ + /** + * @var \transfer + */ + protected $transfer; + + /** + * @var update_helper + */ + protected $update_helper; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param update_helper $update_helper + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(update_helper $update_helper, $phpbb_root_path, $php_ext) + { + $this->transfer = null; + $this->update_helper = $update_helper; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Initialize FTP connection + * + * @param string $method + * @param string $host + * @param string $user + * @param string $pass + * @param string $path + * @param int $port + * @param int $timeout + */ + public function init($method, $host, $user, $pass, $path, $port, $timeout) + { + $this->update_helper->include_file('includes/functions_transfer.' . $this->php_ext); + $this->transfer = new $method($host, $user, $pass, $path, $port, $timeout); + $this->transfer->open_session(); + } + + /** + * Close FTP session + */ + public function close() + { + $this->transfer->close_session(); + } + + /** + * {@inheritdoc} + */ + public function delete_file($path_to_file) + { + $this->transfer->delete_file($path_to_file); + } + + /** + * {@inheritdoc} + */ + public function create_new_file($path_to_file_to_create, $source, $create_from_content = false) + { + $dirname = dirname($path_to_file_to_create); + + if ($dirname && !file_exists($this->phpbb_root_path . $dirname)) + { + $this->transfer->make_dir($dirname); + } + + if ($create_from_content) + { + $this->transfer->write_file($path_to_file_to_create, $source); + } + else + { + $this->transfer->copy_file($path_to_file_to_create, $source); + } + } + + /** + * {@inheritdoc} + */ + public function update_file($path_to_file_to_update, $source, $create_from_content = false) + { + if ($create_from_content) + { + $this->transfer->write_file($path_to_file_to_update, $source); + } + else + { + $this->transfer->copy_file($path_to_file_to_update, $source); + } + } + + /** + * {@inheritdoc} + */ + public function get_method_name() + { + return 'ftp'; + } +} diff --git a/phpbb/install/helper/install_helper.php b/phpbb/install/helper/install_helper.php new file mode 100644 index 0000000..ffe36cd --- /dev/null +++ b/phpbb/install/helper/install_helper.php @@ -0,0 +1,60 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper; + +/** + * General helper functionality for the installer + */ +class install_helper +{ + /** + * @var string + */ + protected $php_ext; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param string $phpbb_root_path path to phpBB's root + * @param string $php_ext Extension of PHP files + */ + public function __construct($phpbb_root_path, $php_ext) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Check whether phpBB is installed. + * + * @return bool + */ + public function is_phpbb_installed() + { + $config_path = $this->phpbb_root_path . 'config.' . $this->php_ext; + $install_lock_path = $this->phpbb_root_path . 'cache/install_lock'; + + if (file_exists($config_path) && !file_exists($install_lock_path) && filesize($config_path)) + { + return true; + } + + return false; + } +} diff --git a/phpbb/install/helper/iohandler/ajax_iohandler.php b/phpbb/install/helper/iohandler/ajax_iohandler.php new file mode 100644 index 0000000..2a608f5 --- /dev/null +++ b/phpbb/install/helper/iohandler/ajax_iohandler.php @@ -0,0 +1,509 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\iohandler; + +use phpbb\path_helper; +use phpbb\routing\router; + +/** + * Input-Output handler for the AJAX frontend + */ +class ajax_iohandler extends iohandler_base +{ + /** + * @var path_helper + */ + protected $path_helper; + + /** + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * @var \phpbb\template\template + */ + protected $template; + + /** + * @var router + */ + protected $router; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $file_status; + + /** + * @var string + */ + protected $form; + + /** + * @var bool + */ + protected $request_client_refresh; + + /** + * @var array + */ + protected $nav_data; + + /** + * @var array + */ + protected $cookies; + + /** + * @var array + */ + protected $download; + + /** + * @var array + */ + protected $redirect_url; + + /** + * @var resource + */ + protected $file_lock_pointer; + + /** + * Constructor + * + * @param path_helper $path_helper + * @param \phpbb\request\request_interface $request HTTP request interface + * @param \phpbb\template\template $template Template engine + * @param router $router Router + * @param string $root_path Path to phpBB's root + */ + public function __construct(path_helper $path_helper, \phpbb\request\request_interface $request, \phpbb\template\template $template, router $router, $root_path) + { + $this->path_helper = $path_helper; + $this->request = $request; + $this->router = $router; + $this->template = $template; + $this->form = ''; + $this->nav_data = array(); + $this->cookies = array(); + $this->download = array(); + $this->redirect_url = array(); + $this->file_status = ''; + $this->phpbb_root_path = $root_path; + + parent::__construct(); + } + + /** + * {@inheritdoc} + */ + public function get_input($name, $default, $multibyte = false) + { + return $this->request->variable($name, $default, $multibyte); + } + + /** + * {@inheritdoc} + */ + public function get_raw_input($name, $default) + { + return $this->request->raw_variable($name, $default); + } + + /** + * {@inheritdoc} + */ + public function get_server_variable($name, $default = '') + { + return $this->request->server($name, $default); + } + + /** + * {@inheritdoc} + */ + public function get_header_variable($name, $default = '') + { + return $this->request->header($name, $default); + } + + /** + * {@inheritdoc} + */ + public function is_secure() + { + return $this->request->is_secure(); + } + + /** + * {@inheritdoc} + */ + public function add_user_form_group($title, $form) + { + $this->form = $this->generate_form_render_data($title, $form); + } + + /** + * {@inheritdoc} + */ + public function generate_form_render_data($title, $form) + { + $this->template->assign_block_vars('options', array( + 'LEGEND' => $this->language->lang($title), + 'S_LEGEND' => true, + )); + + $not_button_form = false; + + foreach ($form as $input_name => $input_options) + { + if (!isset($input_options['type'])) + { + continue; + } + + $tpl_ary = array(); + $not_button_form = ($input_options['type'] !== 'submit' || $not_button_form); + + $tpl_ary['TYPE'] = $input_options['type']; + $tpl_ary['TITLE'] = $this->language->lang($input_options['label']); + $tpl_ary['KEY'] = $input_name; + $tpl_ary['S_EXPLAIN'] = false; + $tpl_ary['DISABLED'] = isset($input_options['disabled']) ? $input_options['disabled'] : false; + $tpl_ary['IS_SECONDARY'] = isset($input_options['is_secondary']) ? $input_options['is_secondary'] : false; + + if (isset($input_options['default'])) + { + $default = $input_options['default']; + $default = preg_replace_callback('#\{L_([A-Z0-9\-_]*)\}#s', array($this, 'lang_replace_callback'), $default); + $tpl_ary['DEFAULT'] = $default; + } + + if (isset($input_options['description'])) + { + $tpl_ary['TITLE_EXPLAIN'] = $this->language->lang($input_options['description']); + $tpl_ary['S_EXPLAIN'] = true; + } + + if (in_array($input_options['type'], array('select', 'radio'), true)) + { + for ($i = 0, $total = count($input_options['options']); $i < $total; $i++) + { + if (isset($input_options['options'][$i]['label'])) + { + $input_options['options'][$i]['label'] = $this->language->lang($input_options['options'][$i]['label']); + } + } + + $tpl_ary['OPTIONS'] = $input_options['options']; + } + + $block_name = ($input_options['type'] === 'submit') ? 'submit_buttons' : 'options'; + $this->template->assign_block_vars($block_name, $tpl_ary); + } + + if (isset($form['database_update_submit']) && !$form['database_update_submit']['disabled']) + { + $this->template->assign_var('FORM_TITLE', $this->language->lang('UPDATE_CONTINUE_UPDATE_PROCESS')); + } + + $this->template->assign_var('S_NOT_ONLY_BUTTON_FORM', $not_button_form); + + if (!$not_button_form) + { + $this->template->destroy_block_vars('options'); + } + + $this->template->set_filenames(array( + 'form_install' => 'installer_form.html', + )); + + return $this->template->assign_display('form_install'); + } + + /** + * {@inheritdoc} + */ + public function send_response($no_more_output = false) + { + $json_data_array = $this->prepare_json_array($no_more_output); + + if (empty($json_data_array)) + { + return; + } + + $json_data = json_encode($json_data_array); + + // Try to push content to the browser + print(str_pad(' ', 4096) . "\n"); + print($json_data . "\n\n"); + flush(); + } + + /** + * Prepares iohandler's data to be sent out to the client. + * + * @param bool $no_more_output Whether or not there will be more output in this response + * + * @return array + */ + protected function prepare_json_array($no_more_output = false) + { + $json_array = array(); + + if (!empty($this->errors)) + { + $json_array['errors'] = $this->errors; + $this->errors = array(); + } + + if (!empty($this->warnings)) + { + $json_array['warnings'] = $this->warnings; + $this->warnings = array(); + } + + if (!empty($this->logs)) + { + $json_array['logs'] = $this->logs; + $this->logs = array(); + } + + if (!empty($this->success)) + { + $json_array['success'] = $this->success; + $this->success = array(); + } + + if (!empty($this->download)) + { + $json_array['download'] = $this->download; + $this->download = array(); + } + + if (!empty($this->form)) + { + $json_array['form'] = $this->form; + $this->form = ''; + } + + if (!empty($this->file_status)) + { + $json_array['file_status'] = $this->file_status; + $this->file_status = ''; + } + + // If current task name is set, we push progress message to the client side + if (!empty($this->current_task_name)) + { + $json_array['progress'] = array( + 'task_name' => $this->current_task_name, + 'task_num' => $this->current_task_progress, + 'task_count' => $this->task_progress_count, + ); + + if ($this->restart_progress_bar) + { + $json_array['progress']['restart'] = 1; + $this->restart_progress_bar = false; + } + } + + if (!empty($this->nav_data)) + { + $json_array['nav'] = $this->nav_data; + $this->nav_data = array(); + } + + if ($this->request_client_refresh) + { + $json_array['refresh'] = true; + $this->request_client_refresh = false; + } + + if (!empty($this->cookies)) + { + $json_array['cookies'] = $this->cookies; + $this->cookies = array(); + } + + if (!empty($this->redirect_url)) + { + $json_array['redirect'] = $this->redirect_url; + $this->redirect_url = array(); + } + + if ($no_more_output) + { + $json_array['over'] = true; + } + + return $json_array; + } + + /** + * {@inheritdoc} + */ + public function set_progress($task_lang_key, $task_number) + { + parent::set_progress($task_lang_key, $task_number); + $this->send_response(); + } + + /** + * {@inheritdoc} + */ + public function request_refresh() + { + $this->request_client_refresh = true; + } + + /** + * {@inheritdoc} + */ + public function set_active_stage_menu($menu_path) + { + $this->nav_data['active'] = $menu_path[count($menu_path) - 1]; + $this->send_response(); + } + + /** + * {@inheritdoc} + */ + public function set_finished_stage_menu($menu_path) + { + $this->nav_data['finished'][] = $menu_path[count($menu_path) - 1]; + $this->send_response(); + } + + /** + * {@inheritdoc} + */ + public function set_cookie($cookie_name, $cookie_value) + { + $this->cookies[] = array( + 'name' => $cookie_name, + 'value' => $cookie_value + ); + } + + /** + * {@inheritdoc} + */ + public function add_download_link($route, $title, $msg = null) + { + $link_properties = array( + 'href' => $this->router->generate($route), + 'title' => $this->language->lang($title), + 'download' => $this->language->lang('DOWNLOAD'), + ); + + if ($msg !== null) + { + $link_properties['msg'] = htmlspecialchars_decode($this->language->lang($msg)); + } + + $this->download[] = $link_properties; + } + + /** + * {@inheritdoc} + */ + public function render_update_file_status($status_array) + { + $this->template->assign_vars(array( + 'T_IMAGE_PATH' => $this->path_helper->get_web_root_path() . 'adm/images/', + )); + + foreach ($status_array as $block => $list) + { + foreach ($list as $filename) + { + $dirname = dirname($filename); + + $this->template->assign_block_vars($block, array( + 'STATUS' => $block, + 'FILENAME' => $filename, + 'DIR_PART' => (!empty($dirname) && $dirname !== '.') ? dirname($filename) . '/' : false, + 'FILE_PART' => basename($filename), + )); + } + } + + $this->template->set_filenames(array( + 'file_status' => 'installer_update_file_status.html', + )); + + $this->file_status = $this->template->assign_display('file_status'); + } + + /** + * {@inheritdoc} + */ + public function redirect($url, $use_ajax = false) + { + $this->redirect_url = array('url' => $url, 'use_ajax' => $use_ajax); + $this->send_response(true); + } + + /** + * Acquires a file lock + */ + public function acquire_lock() + { + $lock_file = $this->phpbb_root_path . 'store/io_lock.lock'; + $this->file_lock_pointer = @fopen($lock_file, 'w+'); + + if ($this->file_lock_pointer) + { + flock($this->file_lock_pointer, LOCK_EX); + } + } + + /** + * Release file lock + */ + public function release_lock() + { + if ($this->file_lock_pointer) + { + fwrite($this->file_lock_pointer, 'ok'); + flock($this->file_lock_pointer, LOCK_UN); + fclose($this->file_lock_pointer); + } + } + + /** + * Callback function for language replacing + * + * @param array $matches + * @return string + */ + public function lang_replace_callback($matches) + { + if (!empty($matches[1])) + { + return $this->language->lang($matches[1]); + } + + return ''; + } +} diff --git a/phpbb/install/helper/iohandler/cli_iohandler.php b/phpbb/install/helper/iohandler/cli_iohandler.php new file mode 100644 index 0000000..4117a3d --- /dev/null +++ b/phpbb/install/helper/iohandler/cli_iohandler.php @@ -0,0 +1,323 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\iohandler; + +use phpbb\install\exception\installer_exception; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\OutputStyle; + +/** + * Input-Output handler for the CLI frontend + */ +class cli_iohandler extends iohandler_base +{ + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var OutputStyle + */ + protected $io; + + /** + * @var array + */ + protected $input_values = array(); + + /** + * @var \Symfony\Component\Console\Helper\ProgressBar + */ + protected $progress_bar; + + /** + * Set the style and output used to display feedback; + * + * @param OutputStyle $style + * @param OutputInterface $output + */ + public function set_style(OutputStyle $style, OutputInterface $output) + { + $this->io = $style; + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function get_input($name, $default, $multibyte = false) + { + $result = $default; + + if (isset($this->input_values[$name])) + { + $result = $this->input_values[$name]; + } + + if ($multibyte) + { + return utf8_normalize_nfc($result); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function get_raw_input($name, $default) + { + return $this->get_input($name, $default, true); + } + + /** + * Set input variable + * + * @param string $name Name of input variable + * @param mixed $value Value of input variable + */ + public function set_input($name, $value) + { + $this->input_values[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function get_server_variable($name, $default = '') + { + return $default; + } + + /** + * {@inheritdoc} + */ + public function get_header_variable($name, $default = '') + { + return $default; + } + + /** + * {@inheritdoc} + */ + public function is_secure() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function add_user_form_group($title, $form) + { + throw new installer_exception('MISSING_DATA'); + } + + /** + * {@inheritdoc} + */ + public function send_response($no_more_output = false) + { + } + + /** + * {@inheritdoc + */ + public function add_error_message($error_title, $error_description = false) + { + $this->io->newLine(); + $message = $this->translate_message($error_title, $error_description); + $message_string = $message['title'] . (!empty($message['description']) ? "\n" . $message['description'] : ''); + + if (strpos($message_string, '
') !== false) + { + $message_string = strip_tags(str_replace('
', "\n", $message_string)); + } + + $this->io->error($message_string); + + if ($this->progress_bar !== null) + { + $this->io->newLine(2); + $this->progress_bar->display(); + } + } + + /** + * {@inheritdoc + */ + public function add_warning_message($warning_title, $warning_description = false) + { + $this->io->newLine(); + + $message = $this->translate_message($warning_title, $warning_description); + $message_string = $message['title'] . (!empty($message['description']) ? "\n" . $message['description'] : ''); + $this->io->warning($message_string); + + if ($this->progress_bar !== null) + { + $this->io->newLine(2); + $this->progress_bar->display(); + } + } + + /** + * {@inheritdoc + */ + public function add_log_message($log_title, $log_description = false) + { + if ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) + { + $message = $this->translate_message($log_title, $log_description); + $this->output->writeln(sprintf('[%3d/%-3d] ---- %s', $this->current_task_progress, $this->task_progress_count, $message['title'])); + } + } + + /** + * {@inheritdoc + */ + public function add_success_message($error_title, $error_description = false) + { + $this->io->newLine(); + + $message = $this->translate_message($error_title, $error_description); + $message_string = $message['title'] . (!empty($message['description']) ? "\n" . $message['description'] : ''); + $this->io->success($message_string); + + if ($this->progress_bar !== null) + { + $this->io->newLine(2); + $this->progress_bar->display(); + } + } + + /** + * {@inheritdoc} + */ + public function set_task_count($task_count, $restart = false) + { + parent::set_task_count($task_count, $restart); + + if ($this->output->getVerbosity() === OutputInterface::VERBOSITY_NORMAL) + { + if ($this->progress_bar !== null) + { + // Symfony's ProgressBar is immutable regarding task_count, so delete the old and create a new one. + $this->progress_bar->clear(); + } + else + { + $this->io->newLine(2); + } + + $this->progress_bar = $this->io->createProgressBar($task_count); + $this->progress_bar->setFormat( + " %current:3s%/%max:-3s% %bar% %percent:3s%%\n" . + " %message%\n"); + $this->progress_bar->setBarWidth(60); + + if (!defined('PHP_WINDOWS_VERSION_BUILD')) + { + $this->progress_bar->setEmptyBarCharacter('░'); // light shade character \u2591 + $this->progress_bar->setProgressCharacter(''); + $this->progress_bar->setBarCharacter('▓'); // dark shade character \u2593 + } + + $this->progress_bar->setMessage(''); + $this->progress_bar->start(); + } + } + + /** + * {@inheritdoc} + */ + public function set_progress($task_lang_key, $task_number) + { + parent::set_progress($task_lang_key, $task_number); + + if ($this->progress_bar !== null) + { + $this->progress_bar->setProgress($this->current_task_progress); + $this->progress_bar->setMessage($this->current_task_name); + } + else + { + $this->output->writeln(sprintf('[%3d/%-3d] %s', $this->current_task_progress, $this->task_progress_count, $this->current_task_name)); + } + } + + /** + * {@inheritdoc} + */ + public function finish_progress($message_lang_key) + { + parent::finish_progress($message_lang_key); + + if ($this->progress_bar !== null) + { + $this->progress_bar->finish(); + $this->progress_bar = null; + } + } + + /** + * {@inheritdoc} + */ + public function request_refresh() + { + } + + /** + * {@inheritdoc} + */ + public function set_active_stage_menu($menu_path) + { + } + + /** + * {@inheritdoc} + */ + public function set_finished_stage_menu($menu_path) + { + } + + /** + * {@inheritdoc} + */ + public function set_cookie($cookie_name, $cookie_value) + { + } + + /** + * {@inheritdoc} + */ + public function add_download_link($route, $title, $msg = null) + { + } + + /** + * {@inheritdoc} + */ + public function render_update_file_status($status_array) + { + } + + /** + * {@inheritdoc} + */ + public function redirect($url, $use_ajax = false) + { + } +} diff --git a/phpbb/install/helper/iohandler/exception/iohandler_not_implemented_exception.php b/phpbb/install/helper/iohandler/exception/iohandler_not_implemented_exception.php new file mode 100644 index 0000000..f2ddeda --- /dev/null +++ b/phpbb/install/helper/iohandler/exception/iohandler_not_implemented_exception.php @@ -0,0 +1,19 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\iohandler\exception; + +class iohandler_not_implemented_exception extends \Exception +{ + +} diff --git a/phpbb/install/helper/iohandler/factory.php b/phpbb/install/helper/iohandler/factory.php new file mode 100644 index 0000000..1e83957 --- /dev/null +++ b/phpbb/install/helper/iohandler/factory.php @@ -0,0 +1,79 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\iohandler; + +use phpbb\install\helper\iohandler\exception\iohandler_not_implemented_exception; + +/** + * Input-output handler factory + */ +class factory +{ + /** + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * @var string + */ + protected $environment; + + /** + * Constructor + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container Dependency injection container + */ + public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) + { + $this->container = $container; + $this->environment = null; + } + + /** + * @param string $environment The name of the input-output handler to use + */ + public function set_environment($environment) + { + $this->environment = $environment; + } + + /** + * Factory getter for iohandler + * + * @return \phpbb\install\helper\iohandler\iohandler_interface + * + * @throws \phpbb\install\helper\iohandler\exception\iohandler_not_implemented_exception + * When the specified iohandler_interface does not exists + */ + public function get() + { + switch ($this->environment) + { + case 'ajax': + return $this->container->get('installer.helper.iohandler_ajax'); + break; + case 'nojs': + // @todo replace this + return $this->container->get('installer.helper.iohandler_ajax'); + break; + case 'cli': + return $this->container->get('installer.helper.iohandler_cli'); + break; + default: + throw new iohandler_not_implemented_exception(); + break; + } + } +} diff --git a/phpbb/install/helper/iohandler/iohandler_base.php b/phpbb/install/helper/iohandler/iohandler_base.php new file mode 100644 index 0000000..1797a6c --- /dev/null +++ b/phpbb/install/helper/iohandler/iohandler_base.php @@ -0,0 +1,209 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\iohandler; + +/** + * Base class for installer input-output handlers + */ +abstract class iohandler_base implements iohandler_interface +{ + /** + * Array of errors + * + * Errors should be added, when the installation cannot continue without + * user interaction. If the aim is to notify the user about something, please + * use a warning instead. + * + * @var array + */ + protected $errors; + + /** + * Array of warnings + * + * @var array + */ + protected $warnings; + + /** + * Array of logs + * + * @var array + */ + protected $logs; + + /** + * Array of success messages + * + * @var array + */ + protected $success; + + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * @var int + */ + protected $task_progress_count; + + /** + * @var int + */ + protected $current_task_progress; + + /** + * @var string + */ + protected $current_task_name; + + /** + * @var bool + */ + protected $restart_progress_bar; + + /** + * Constructor + */ + public function __construct() + { + $this->errors = array(); + $this->warnings = array(); + $this->logs = array(); + $this->success = array(); + + $this->restart_progress_bar = false; + $this->task_progress_count = 0; + $this->current_task_progress = 0; + $this->current_task_name = ''; + } + + /** + * Set language service + * + * @param \phpbb\language\language $language + */ + public function set_language(\phpbb\language\language $language) + { + $this->language = $language; + } + + /** + * {@inheritdoc} + */ + public function add_error_message($error_title, $error_description = false) + { + if (!is_array($error_title) && strpos($error_title, '
') !== false) + { + $error_title = strip_tags(htmlspecialchars_decode($error_title)); + } + $this->errors[] = $this->translate_message($error_title, $error_description); + } + + /** + * {@inheritdoc} + */ + public function add_warning_message($warning_title, $warning_description = false) + { + $this->warnings[] = $this->translate_message($warning_title, $warning_description); + } + + /** + * {@inheritdoc} + */ + public function add_log_message($log_title, $log_description = false) + { + $this->logs[] = $this->translate_message($log_title, $log_description); + } + + /** + * {@inheritdoc} + */ + public function add_success_message($success_title, $success_description = false) + { + $this->success[] = $this->translate_message($success_title, $success_description); + } + + /** + * {@inheritdoc} + */ + public function set_task_count($task_count, $restart = false) + { + $this->task_progress_count = $task_count; + $this->restart_progress_bar = $restart; + } + + /** + * {@inheritdoc} + */ + public function set_progress($task_lang_key, $task_number) + { + $this->current_task_name = ''; + + if (!empty($task_lang_key)) + { + $this->current_task_name = $this->language->lang($task_lang_key); + } + + $this->current_task_progress = $task_number; + } + + /** + * {@inheritdoc} + */ + public function finish_progress($message_lang_key) + { + if (!empty($message_lang_key)) + { + $this->current_task_name = $this->language->lang($message_lang_key); + } + + $this->current_task_progress = $this->task_progress_count; + } + + /** + * {@inheritdoc} + */ + public function generate_form_render_data($title, $form) + { + return ''; + } + + /** + * Localize message. + * + * Note: When an array is passed into the parameters below, it will be + * resolved as printf($param[0], $param[1], ...). + * + * @param array|string $title Title of the message + * @param array|string|bool $description Description of the message + * + * @return array Localized message in an array + */ + protected function translate_message($title, $description) + { + $message_array = array(); + + $message_array['title'] = call_user_func_array(array($this->language, 'lang'), (array) $title); + + if ($description !== false) + { + $message_array['description'] = call_user_func_array(array($this->language, 'lang'), (array) $description); + } + + return $message_array; + } +} diff --git a/phpbb/install/helper/iohandler/iohandler_interface.php b/phpbb/install/helper/iohandler/iohandler_interface.php new file mode 100644 index 0000000..4407489 --- /dev/null +++ b/phpbb/install/helper/iohandler/iohandler_interface.php @@ -0,0 +1,222 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\iohandler; + +/** + * Input-Output handler interface for the installer + */ +interface iohandler_interface +{ + /** + * Renders or returns response message + * + * @param bool $no_more_output Whether or not there will be more output in this output unit + */ + public function send_response($no_more_output = false); + + /** + * Returns input variable + * + * @param string $name Name of the input variable to obtain + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * + * @return mixed Value of the input variable + */ + public function get_input($name, $default, $multibyte = false); + + /** + * Returns raw input variable + * + * @param string $name Name of the input variable to obtain + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * + * @return mixed Value of the raw input variable + */ + public function get_raw_input($name, $default); + + /** + * Returns server variable + * + * This function should work the same as request_interface::server(). + * + * @param string $name Name of the server variable + * @param mixed $default Default value to return when the requested variable does not exist + * + * @return mixed Value of the server variable + */ + public function get_server_variable($name, $default = ''); + + /** + * Wrapper function for request_interface::header() + * + * @param string $name Name of the request header variable + * @param mixed $default Default value to return when the requested variable does not exist + * + * @return mixed + */ + public function get_header_variable($name, $default = ''); + + /** + * Returns true if the connection is encrypted + * + * @return bool + */ + public function is_secure(); + + /** + * Adds an error message to the rendering queue + * + * Note: When an array is passed into the parameters below, it will be + * resolved as printf($param[0], $param[1], ...). + * + * @param string|array $error_title Title of the error message. + * @param string|bool|array $error_description Description of the error (and possibly guidelines to resolve it), + * or false if the error description is not available. + */ + public function add_error_message($error_title, $error_description = false); + + /** + * Adds a warning message to the rendering queue + * + * Note: When an array is passed into the parameters below, it will be + * resolved as printf($param[0], $param[1], ...). + * + * @param string|array $warning_title Title of the warning message + * @param string|bool|array $warning_description Description of the warning (and possibly guidelines to resolve it), + * or false if the warning description is not available + */ + public function add_warning_message($warning_title, $warning_description = false); + + /** + * Adds a log message to the rendering queue + * + * Note: When an array is passed into the parameters below, it will be + * resolved as printf($param[0], $param[1], ...). + * + * @param string|array $log_title Title of the log message + * @param string|bool|array $log_description Description of the log, + * or false if the log description is not available + */ + public function add_log_message($log_title, $log_description = false); + + /** + * Adds a success message to the rendering queue + * + * Note: When an array is passed into the parameters below, it will be + * resolved as printf($param[0], $param[1], ...). + * + * @param string|array $success_title Title of the success message + * @param string|bool|array $success_description Description of the success, + * or false if the success description is not available + * + * @return null + */ + public function add_success_message($success_title, $success_description = false); + + /** + * Adds a requested data group to the rendering queue + * + * @param string $title Language variable with the title of the form + * @param array $form An array describing the required data (options etc) + */ + public function add_user_form_group($title, $form); + + /** + * Returns the rendering information for the form + * + * @param string $title Language variable with the title of the form + * @param array $form An array describing the required data (options etc) + * + * @return string Information to render the form + */ + public function generate_form_render_data($title, $form); + + /** + * Sets the number of tasks belonging to the installer in the current mode. + * + * @param int $task_count Number of tasks + * @param bool $restart Whether or not to restart the progress bar, false by default + */ + public function set_task_count($task_count, $restart = false); + + /** + * Sets the progress information + * + * @param string $task_lang_key Language key for the name of the task + * @param int $task_number Position of the current task in the task queue + */ + public function set_progress($task_lang_key, $task_number); + + /** + * Sends refresh request to the client + */ + public function request_refresh(); + + /** + * Marks stage as active in the navigation bar + * + * @param array $menu_path Array to the navigation elem + */ + public function set_active_stage_menu($menu_path); + + /** + * Marks stage as completed in the navigation bar + * + * @param array $menu_path Array to the navigation elem + */ + public function set_finished_stage_menu($menu_path); + + /** + * Finish the progress bar + * + * @param string $message_lang_key Language key for the message + */ + public function finish_progress($message_lang_key); + + /** + * Adds a download link + * + * @param string $route Route for the link + * @param string $title Language key for the title + * @param string|null|array $msg Language key for the message + */ + public function add_download_link($route, $title, $msg = null); + + /** + * Redirects the user to a new page + * + * @param string $url URL to redirect to + * @param bool $use_ajax Whether or not to use AJAX redirect + */ + public function redirect($url, $use_ajax = false); + + /** + * Renders the status of update files + * + * @param array $status_array Array containing files in groups to render + */ + public function render_update_file_status($status_array); + + /** + * Sends and sets cookies + * + * @param string $cookie_name Name of the cookie to set + * @param string $cookie_value Value of the cookie to set + */ + public function set_cookie($cookie_name, $cookie_value); +} diff --git a/phpbb/install/helper/navigation/convertor_navigation.php b/phpbb/install/helper/navigation/convertor_navigation.php new file mode 100644 index 0000000..54cab83 --- /dev/null +++ b/phpbb/install/helper/navigation/convertor_navigation.php @@ -0,0 +1,78 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\navigation; + +use phpbb\install\helper\install_helper; + +class convertor_navigation implements navigation_interface +{ + /** + * @var install_helper + */ + private $install_helper; + + /** + * Constructor + * + * @param install_helper $install_helper + */ + public function __construct(install_helper $install_helper) + { + $this->install_helper = $install_helper; + } + + /** + * {@inheritdoc} + */ + public function get() + { + if (!$this->install_helper->is_phpbb_installed()) + { + return array(); + } + + return array( + 'convert' => array( + 'label' => 'CONVERT', + 'route' => 'phpbb_convert_intro', + 'order' => 3, + array( + 'intro' => array( + 'label' => 'SUB_INTRO', + 'stage' => true, + 'order' => 0, + ), + 'settings' => array( + 'label' => 'STAGE_SETTINGS', + 'stage' => true, + 'route' => 'phpbb_convert_settings', + 'order' => 1, + ), + 'convert' => array( + 'label' => 'STAGE_IN_PROGRESS', + 'stage' => true, + 'route' => 'phpbb_convert_convert', + 'order' => 2, + ), + 'finish' => array( + 'label' => 'CONVERT_COMPLETE', + 'stage' => true, + 'route' => 'phpbb_convert_finish', + 'order' => 3, + ), + ), + ), + ); + } +} diff --git a/phpbb/install/helper/navigation/install_navigation.php b/phpbb/install/helper/navigation/install_navigation.php new file mode 100644 index 0000000..f690f8d --- /dev/null +++ b/phpbb/install/helper/navigation/install_navigation.php @@ -0,0 +1,75 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\navigation; + +use phpbb\install\helper\install_helper; + +class install_navigation implements navigation_interface +{ + /** + * @var install_helper + */ + private $install_helper; + + /** + * Constructor + * + * @param install_helper $install_helper + */ + public function __construct(install_helper $install_helper) + { + $this->install_helper = $install_helper; + } + + /** + * {@inheritdoc} + */ + public function get() + { + if ($this->install_helper->is_phpbb_installed()) + { + return array(); + } + + return array( + 'install' => array( + 'label' => 'INSTALL', + 'route' => 'phpbb_installer_install', + 'order' => 1, + array( + 'introduction' => array( + 'label' => 'INTRODUCTION_TITLE', + 'stage' => true, + 'order' => 0, + ), + 'requirements' => array( + 'label' => 'STAGE_REQUIREMENTS', + 'stage' => true, + 'order' => 1, + ), + 'obtain_data' => array( + 'label' => 'STAGE_OBTAIN_DATA', + 'stage' => true, + 'order' => 2, + ), + 'install' => array( + 'label' => 'STAGE_INSTALL', + 'stage' => true, + 'order' => 3, + ), + ), + ), + ); + } +} diff --git a/phpbb/install/helper/navigation/main_navigation.php b/phpbb/install/helper/navigation/main_navigation.php new file mode 100644 index 0000000..214bb04 --- /dev/null +++ b/phpbb/install/helper/navigation/main_navigation.php @@ -0,0 +1,48 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\navigation; + +class main_navigation implements navigation_interface +{ + /** + * {@inheritdoc} + */ + public function get() + { + return array( + 'overview' => array( + 'label' => 'MENU_OVERVIEW', + 'route' => 'phpbb_installer_index', + 'order' => 0, + array( + 'introduction' => array( + 'label' => 'MENU_INTRO', + 'route' => 'phpbb_installer_index', + 'order' => 0, + ), + 'support' => array( + 'label' => 'MENU_SUPPORT', + 'route' => 'phpbb_installer_support', + 'order' => 1, + ), + 'license' => array( + 'label' => 'MENU_LICENSE', + 'route' => 'phpbb_installer_license', + 'order' => 2, + ), + ), + ), + ); + } +} diff --git a/phpbb/install/helper/navigation/navigation_interface.php b/phpbb/install/helper/navigation/navigation_interface.php new file mode 100644 index 0000000..eebdbe9 --- /dev/null +++ b/phpbb/install/helper/navigation/navigation_interface.php @@ -0,0 +1,43 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\navigation; + +/** + * Interface for installer's navigation defining services + */ +interface navigation_interface +{ + /** + * Returns an array with the navigation items + * + * The returned array should have the following format: + * + * array( + * 'parent_nav_name' => array( + * 'nav_name' => array( + * 'label' => 'MY_MENU', + * 'route' => 'phpbb_route_name', + * ) + * ) + * ) + * + * + * Navigation item setting options: + * - label: The language variable name + * - route: Name of the route which it is belongs to + * + * @return array + */ + public function get(); +} diff --git a/phpbb/install/helper/navigation/navigation_provider.php b/phpbb/install/helper/navigation/navigation_provider.php new file mode 100644 index 0000000..d52aec8 --- /dev/null +++ b/phpbb/install/helper/navigation/navigation_provider.php @@ -0,0 +1,121 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\navigation; + +use phpbb\di\service_collection; + +/** + * Installers navigation provider + */ +class navigation_provider +{ + /** + * @var array + */ + private $menu_collection; + + /** + * Constructor + * + * @param service_collection $plugins + */ + public function __construct(service_collection $plugins) + { + $this->menu_collection = array(); + + foreach ($plugins as $plugin => $plugin_instance) + { + $this->register($plugin_instance); + } + } + + /** + * Returns navigation array + * + * @return array + */ + public function get() + { + return $this->menu_collection; + } + + /** + * Registers a navigation provider's navigation items + * + * @param navigation_interface $navigation + */ + public function register(navigation_interface $navigation) + { + $nav_arry = $navigation->get(); + $this->menu_collection = $this->merge($nav_arry, $this->menu_collection); + } + + /** + * Set a property in the navigation array + * + * @param array $nav_element Array to the navigation elem + * @param array $property_array Array with the properties to set + */ + public function set_nav_property($nav_element, $property_array) + { + $array_pointer = array(); + $array_root_pointer = &$array_pointer; + foreach ($nav_element as $array_path) + { + $array_pointer[$array_path] = array(); + $array_pointer = &$array_pointer[$array_path]; + } + + $array_pointer = $property_array; + + $this->menu_collection = $this->merge($array_root_pointer, $this->menu_collection); + } + + /** + * Recursive array merge + * + * This function is necessary to be able to replace the options of + * already set navigation items. + * + * @param array $array_to_merge + * @param array $array_to_merge_into + * + * @return array Merged array + */ + private function merge($array_to_merge, $array_to_merge_into) + { + $merged_array = $array_to_merge_into; + + foreach ($array_to_merge as $key => $value) + { + if (isset($array_to_merge_into[$key])) + { + if (is_array($array_to_merge_into[$key]) && is_array($value)) + { + $merged_array[$key] = $this->merge($value, $array_to_merge_into[$key]); + } + else + { + $merged_array[$key] = $value; + } + } + else + { + $merged_array[$key] = $value; + } + } + + return $merged_array; + } +} diff --git a/phpbb/install/helper/navigation/update_navigation.php b/phpbb/install/helper/navigation/update_navigation.php new file mode 100644 index 0000000..3d239c3 --- /dev/null +++ b/phpbb/install/helper/navigation/update_navigation.php @@ -0,0 +1,80 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper\navigation; + +use phpbb\install\helper\install_helper; + +class update_navigation implements navigation_interface +{ + /** + * @var install_helper + */ + private $install_helper; + + /** + * Constructor + * + * @param install_helper $install_helper + */ + public function __construct(install_helper $install_helper) + { + $this->install_helper = $install_helper; + } + + /** + * {@inheritdoc} + */ + public function get() + { + if (!$this->install_helper->is_phpbb_installed()) + { + return array(); + } + + return array( + 'update' => array( + 'label' => 'UPDATE', + 'route' => 'phpbb_installer_update', + 'order' => 1, + array( + 'introduction' => array( + 'label' => 'INTRODUCTION_TITLE', + 'stage' => true, + 'order' => 0, + ), + 'requirements' => array( + 'label' => 'STAGE_REQUIREMENTS', + 'stage' => true, + 'order' => 1, + ), + 'obtain_data' => array( + 'label' => 'STAGE_OBTAIN_DATA', + 'stage' => true, + 'order' => 2, + ), + 'update_files' => array( + 'label' => 'STAGE_UPDATE_FILES', + 'stage' => true, + 'order' => 3, + ), + 'update_database' => array( + 'label' => 'STAGE_UPDATE_DATABASE', + 'stage' => true, + 'order' => 4, + ), + ), + ), + ); + } +} diff --git a/phpbb/install/helper/update_helper.php b/phpbb/install/helper/update_helper.php new file mode 100644 index 0000000..a00731d --- /dev/null +++ b/phpbb/install/helper/update_helper.php @@ -0,0 +1,113 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\helper; + +/** + * General helper functionality for the updater + */ +class update_helper +{ + /** + * @var string + */ + protected $path_to_new_files; + + /** + * @var string + */ + protected $path_to_old_files; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param string $phpbb_root_path + */ + public function __construct($phpbb_root_path) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->path_to_new_files = $phpbb_root_path . 'install/update/new/'; + $this->path_to_old_files = $phpbb_root_path . 'install/update/old/'; + } + + /** + * Returns path to new update files + * + * @return string + */ + public function get_path_to_new_update_files() + { + return $this->path_to_new_files; + } + + /** + * Returns path to new update files + * + * @return string + */ + public function get_path_to_old_update_files() + { + return $this->path_to_old_files; + } + + /** + * Includes the updated file if available + * + * @param string $filename Path to the file relative to phpBB root path + */ + public function include_file($filename) + { + if (is_file($this->path_to_new_files . $filename)) + { + include_once($this->path_to_new_files . $filename); + } + else if (is_file($this->phpbb_root_path . $filename)) + { + include_once($this->phpbb_root_path . $filename); + } + } + + /** + * Customized version_compare() + * + * @param string $version_number1 + * @param string $version_number2 + * @param string|null $operator + * @return int|bool The returned value is identical to the PHP build-in function version_compare() + */ + public function phpbb_version_compare($version_number1, $version_number2, $operator = null) + { + if ($operator === null) + { + $result = version_compare( + str_replace('rc', 'RC', strtolower($version_number1)), + str_replace('rc', 'RC', strtolower($version_number2)) + ); + } + else + { + $result = version_compare( + str_replace('rc', 'RC', strtolower($version_number1)), + str_replace('rc', 'RC', strtolower($version_number2)), + $operator + ); + } + + return $result; + } +} diff --git a/phpbb/install/installer.php b/phpbb/install/installer.php new file mode 100644 index 0000000..e04e233 --- /dev/null +++ b/phpbb/install/installer.php @@ -0,0 +1,350 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install; + +use phpbb\cache\driver\driver_interface; +use phpbb\di\ordered_service_collection; +use phpbb\install\exception\cannot_build_container_exception; +use phpbb\install\exception\installer_config_not_writable_exception; +use phpbb\install\exception\jump_to_restart_point_exception; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\iohandler\ajax_iohandler; +use phpbb\install\helper\iohandler\cli_iohandler; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\path_helper; + +class installer +{ + /** + * @var driver_interface + */ + protected $cache; + + /** + * @var container_factory + */ + protected $container_factory; + + /** + * @var config + */ + protected $install_config; + + /** + * @var ordered_service_collection + */ + protected $installer_modules; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var string + */ + protected $web_root; + + /** + * Stores the number of steps that a given module has + * + * @var array + */ + protected $module_step_count; + + /** + * @var bool + */ + protected $purge_cache_before; + + /** + * Constructor + * + * @param driver_interface $cache Cache service + * @param config $config Installer config handler + * @param path_helper $path_helper Path helper + * @param container_factory $container Container + */ + public function __construct(driver_interface $cache, config $config, path_helper $path_helper, container_factory $container) + { + $this->cache = $cache; + $this->install_config = $config; + $this->container_factory = $container; + $this->installer_modules = null; + $this->web_root = $path_helper->get_web_root_path(); + $this->purge_cache_before = false; + } + + /** + * Sets modules to execute + * + * Note: The installer will run modules in the order they are set in + * the array. + * + * @param ordered_service_collection $modules Service collection of module service names + */ + public function set_modules(ordered_service_collection $modules) + { + $this->installer_modules = $modules; + } + + /** + * Sets input-output handler objects + * + * @param iohandler_interface $iohandler + */ + public function set_iohandler(iohandler_interface $iohandler) + { + $this->iohandler = $iohandler; + } + + /** + * Sets whether to purge cache before the installation process + * + * @param bool $purge_cache_before + */ + public function set_purge_cache_before($purge_cache_before) + { + $this->purge_cache_before = $purge_cache_before; + } + + /** + * Run phpBB installer + */ + public function run() + { + if ($this->iohandler instanceof ajax_iohandler) + { + $this->iohandler->acquire_lock(); + } + + // Load install progress + $this->install_config->load_config(); + + if (!$this->install_config->get('cache_purged_before', false) && $this->purge_cache_before) + { + /** @var \phpbb\cache\driver\driver_interface $cache */ + $cache = $this->container_factory->get('cache.driver'); + $cache->purge(); + $this->install_config->set('cache_purged_before', true); + } + + // Recover install progress + $module_index = $this->recover_progress(); + + // Variable used to check if the install process have been finished + $install_finished = false; + $fail_cleanup = false; + $send_refresh = false; + + // We are installing something, so the introduction stage can go now... + $this->install_config->set_finished_navigation_stage(array('install', 0, 'introduction')); + $this->iohandler->set_finished_stage_menu(array('install', 0, 'introduction')); + + if ($this->install_config->get_task_progress_count() === 0) + { + // Count all tasks in the current installer modules + $step_count = 0; + + /** @var \phpbb\install\module_interface $module */ + foreach ($this->installer_modules as $name => $module) + { + $module_step_count = $module->get_step_count(); + $step_count += $module_step_count; + $this->module_step_count[$name] = $module_step_count; + } + + // Set task count + $this->install_config->set_task_progress_count($step_count); + } + + // Set up progress information + $this->iohandler->set_task_count( + $this->install_config->get_task_progress_count() + ); + + try + { + $iterator = $this->installer_modules->getIterator(); + + if ($module_index < $iterator->count()) + { + $iterator->seek($module_index); + } + else + { + $iterator->seek($module_index - 1); + $iterator->next(); + } + + while ($iterator->valid()) + { + $module = $iterator->current(); + $name = $iterator->key(); + + // Check if module should be executed + if (!$module->is_essential() && !$module->check_requirements()) + { + $this->install_config->set_finished_navigation_stage($module->get_navigation_stage_path()); + $this->iohandler->set_finished_stage_menu($module->get_navigation_stage_path()); + + $this->iohandler->add_log_message(array( + 'SKIP_MODULE', + $name, + )); + $this->install_config->increment_current_task_progress($this->module_step_count[$name]); + } + else + { + // Set the correct stage in the navigation bar + $this->install_config->set_active_navigation_stage($module->get_navigation_stage_path()); + $this->iohandler->set_active_stage_menu($module->get_navigation_stage_path()); + + $this->iohandler->send_response(); + + $module->run(); + + $this->install_config->set_finished_navigation_stage($module->get_navigation_stage_path()); + $this->iohandler->set_finished_stage_menu($module->get_navigation_stage_path()); + } + + $module_index++; + $iterator->next(); + + // Save progress + $this->install_config->set_active_module($name, $module_index); + + if ($iterator->valid() && ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0)) + { + throw new resource_limit_reached_exception(); + } + } + + // Installation finished + $install_finished = true; + + if ($this->iohandler instanceof cli_iohandler) + { + $this->iohandler->add_success_message('INSTALLER_FINISHED'); + } + else + { + // Start session if not installing and get user object + // to allow redirecting to ACP + $user = $this->container_factory->get('user'); + if (!isset($module) || !($module instanceof \phpbb\install\module\install_finish\module)) + { + $auth = $this->container_factory->get('auth'); + + $user->session_begin(); + $auth->acl($user->data); + $user->setup(); + } + + $phpbb_root_path = $this->container_factory->get_parameter('core.root_path'); + + $acp_url = append_sid($phpbb_root_path . 'adm/index.php', 'i=acp_help_phpbb&mode=help_phpbb', true, $user->session_id); + $this->iohandler->add_success_message('INSTALLER_FINISHED', array( + 'ACP_LINK', + $acp_url, + )); + } + } + catch (user_interaction_required_exception $e) + { + $this->iohandler->send_response(true); + } + catch (resource_limit_reached_exception $e) + { + $send_refresh = true; + } + catch (jump_to_restart_point_exception $e) + { + $this->install_config->jump_to_restart_point($e->get_restart_point_name()); + $send_refresh = true; + } + catch (\Exception $e) + { + $this->iohandler->add_error_message($e->getMessage()); + $this->iohandler->send_response(true); + $fail_cleanup = true; + } + + if ($this->iohandler instanceof ajax_iohandler) + { + $this->iohandler->release_lock(); + } + + if ($install_finished) + { + // Send install finished message + $this->iohandler->set_progress('INSTALLER_FINISHED', $this->install_config->get_task_progress_count()); + $this->iohandler->send_response(true); + } + else if ($send_refresh) + { + $this->iohandler->request_refresh(); + $this->iohandler->send_response(true); + } + + // Save install progress + try + { + if ($install_finished || $fail_cleanup) + { + $this->install_config->clean_up_config_file(); + $this->cache->purge(); + + try + { + /** @var \phpbb\cache\driver\driver_interface $cache */ + $cache = $this->container_factory->get('cache.driver'); + $cache->purge(); + } + catch (cannot_build_container_exception $e) + { + // Do not do anything, this just means there is no config.php yet + } + } + else + { + $this->install_config->save_config(); + } + } + catch (installer_config_not_writable_exception $e) + { + // It is allowed to fail this test during requirements testing + $progress_data = $this->install_config->get_progress_data(); + + if ($progress_data['last_task_module_name'] !== 'installer.module.requirements_install') + { + $this->iohandler->add_error_message('INSTALLER_CONFIG_NOT_WRITABLE'); + } + } + } + + /** + * Recover install progress + * + * @return string Index of the next installer module to execute + */ + protected function recover_progress() + { + $progress_array = $this->install_config->get_progress_data(); + return $progress_array['last_task_module_index']; + } +} diff --git a/phpbb/install/installer_configuration.php b/phpbb/install/installer_configuration.php new file mode 100644 index 0000000..8051403 --- /dev/null +++ b/phpbb/install/installer_configuration.php @@ -0,0 +1,147 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class installer_configuration implements ConfigurationInterface +{ + + /** + * Generates the configuration tree builder. + * + * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('installer'); + $rootNode + ->children() + ->arrayNode('admin') + ->children() + ->scalarNode('name')->defaultValue('admin')->cannotBeEmpty()->end() + ->scalarNode('password')->defaultValue('adminadmin')->cannotBeEmpty()->end() + ->scalarNode('email')->defaultValue('admin@example.org')->cannotBeEmpty()->end() + ->end() + ->end() + ->arrayNode('board') + ->children() + ->scalarNode('lang') + ->defaultValue('en') + ->cannotBeEmpty() + ->end() + ->scalarNode('name') + ->defaultValue('My Board') + ->cannotBeEmpty() + ->end() + ->scalarNode('description') + ->defaultValue('My amazing new phpBB board') + ->cannotBeEmpty() + ->end() + ->end() + ->end() + ->arrayNode('database') + ->children() + ->scalarNode('dbms') + ->defaultValue('sqlite3') + ->cannotBeEmpty() + ->isRequired() + ->end() + ->scalarNode('dbhost') + ->defaultValue(null) + ->end() + ->scalarNode('dbport') + ->defaultValue(null) + ->end() + ->scalarNode('dbuser') + ->defaultValue(null) + ->end() + ->scalarNode('dbpasswd') + ->defaultValue(null) + ->end() + ->scalarNode('dbname') + ->defaultValue(null) + ->end() + ->scalarNode('table_prefix') + ->defaultValue('phpbb_') + ->cannotBeEmpty() + ->isRequired() + ->end() + ->end() + ->end() + ->arrayNode('email') + ->canBeEnabled() + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('smtp_delivery') + ->defaultValue(false) + ->treatNullLike(false) + ->end() + ->scalarNode('smtp_host') + ->defaultValue(null) + ->end() + ->scalarNode('smtp_port') + ->defaultValue(null) + ->end() + ->scalarNode('smtp_auth') + ->defaultValue(null) + ->end() + ->scalarNode('smtp_user') + ->defaultValue(null) + ->end() + ->scalarNode('smtp_pass') + ->defaultValue(null) + ->end() + ->end() + ->end() + ->arrayNode('server') + ->children() + ->booleanNode('cookie_secure') + ->defaultValue(false) + ->treatNullLike(false) + ->end() + ->scalarNode('server_protocol') + ->defaultValue('http://') + ->cannotBeEmpty() + ->end() + ->booleanNode('force_server_vars') + ->defaultValue(false) + ->treatNullLike(false) + ->end() + ->scalarNode('server_name') + ->defaultValue('localhost') + ->cannotBeEmpty() + ->end() + ->integerNode('server_port') + ->defaultValue(80) + ->min(1) + ->cannotBeEmpty() + ->end() + ->scalarNode('script_path') + ->defaultValue('/') + ->cannotBeEmpty() + ->end() + ->end() + ->end() + ->arrayNode('extensions') + ->prototype('scalar')->end() + ->defaultValue([]) + ->end() + ->end() + ; + return $treeBuilder; + } +} diff --git a/phpbb/install/module/install_data/module.php b/phpbb/install/module/install_data/module.php new file mode 100644 index 0000000..77f1f73 --- /dev/null +++ b/phpbb/install/module/install_data/module.php @@ -0,0 +1,28 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_data; + +/** + * Installer module for recovering and installing default data installation + */ +class module extends \phpbb\install\module_base +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('install', 0, 'install'); + } +} diff --git a/phpbb/install/module/install_data/task/add_bots.php b/phpbb/install/module/install_data/task/add_bots.php new file mode 100644 index 0000000..07f8e02 --- /dev/null +++ b/phpbb/install/module/install_data/task/add_bots.php @@ -0,0 +1,263 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_data\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +class add_bots extends \phpbb\install\task_base +{ + /** + * A list of the web-crawlers/bots we recognise by default + * + * Candidates but not included: + * 'Accoona [Bot]' 'Accoona-AI-Agent/' + * 'ASPseek [Crawler]' 'ASPseek/' + * 'Boitho [Crawler]' 'boitho.com-dc/' + * 'Bunnybot [Bot]' 'powered by www.buncat.de' + * 'Cosmix [Bot]' 'cfetch/' + * 'Crawler Search [Crawler]' '.Crawler-Search.de' + * 'Findexa [Crawler]' 'Findexa Crawler (' + * 'GBSpider [Spider]' 'GBSpider v' + * 'genie [Bot]' 'genieBot (' + * 'Hogsearch [Bot]' 'oegp v. 1.3.0' + * 'Insuranco [Bot]' 'InsurancoBot' + * 'IRLbot [Bot]' 'http://irl.cs.tamu.edu/crawler' + * 'ISC Systems [Bot]' 'ISC Systems iRc Search' + * 'Jyxobot [Bot]' 'Jyxobot/' + * 'Kraehe [Metasuche]' '-DIE-KRAEHE- META-SEARCH-ENGINE/' + * 'LinkWalker' 'LinkWalker' + * 'MMSBot [Bot]' 'http://www.mmsweb.at/bot.html' + * 'Naver [Bot]' 'nhnbot@naver.com)' + * 'NetResearchServer' 'NetResearchServer/' + * 'Nimble [Crawler]' 'NimbleCrawler' + * 'Ocelli [Bot]' 'Ocelli/' + * 'Onsearch [Bot]' 'onCHECK-Robot' + * 'Orange [Spider]' 'OrangeSpider' + * 'Sproose [Bot]' 'http://www.sproose.com/bot' + * 'Susie [Sync]' '!Susie (http://www.sync2it.com/susie)' + * 'Tbot [Bot]' 'Tbot/' + * 'Thumbshots [Capture]' 'thumbshots-de-Bot' + * 'Vagabondo [Crawler]' 'http://webagent.wise-guys.nl/' + * 'Walhello [Bot]' 'appie 1.1 (www.walhello.com)' + * 'WissenOnline [Bot]' 'WissenOnline-Bot' + * 'WWWeasel [Bot]' 'WWWeasel Robot v' + * 'Xaldon [Spider]' 'Xaldon WebSpider' + * + * @var array + */ + protected $bot_list = array( + 'AdsBot [Google]' => array('AdsBot-Google', ''), + 'Alexa [Bot]' => array('ia_archiver', ''), + 'Alta Vista [Bot]' => array('Scooter/', ''), + 'Ask Jeeves [Bot]' => array('Ask Jeeves', ''), + 'Baidu [Spider]' => array('Baiduspider', ''), + 'Bing [Bot]' => array('bingbot/', ''), + 'Exabot [Bot]' => array('Exabot', ''), + 'FAST Enterprise [Crawler]' => array('FAST Enterprise Crawler', ''), + 'FAST WebCrawler [Crawler]' => array('FAST-WebCrawler/', ''), + 'Francis [Bot]' => array('http://www.neomo.de/', ''), + 'Gigabot [Bot]' => array('Gigabot/', ''), + 'Google Adsense [Bot]' => array('Mediapartners-Google', ''), + 'Google Desktop' => array('Google Desktop', ''), + 'Google Feedfetcher' => array('Feedfetcher-Google', ''), + 'Google [Bot]' => array('Googlebot', ''), + 'Heise IT-Markt [Crawler]' => array('heise-IT-Markt-Crawler', ''), + 'Heritrix [Crawler]' => array('heritrix/1.', ''), + 'IBM Research [Bot]' => array('ibm.com/cs/crawler', ''), + 'ICCrawler - ICjobs' => array('ICCrawler - ICjobs', ''), + 'ichiro [Crawler]' => array('ichiro/', ''), + 'Majestic-12 [Bot]' => array('MJ12bot/', ''), + 'Metager [Bot]' => array('MetagerBot/', ''), + 'MSN NewsBlogs' => array('msnbot-NewsBlogs/', ''), + 'MSN [Bot]' => array('msnbot/', ''), + 'MSNbot Media' => array('msnbot-media/', ''), + 'Nutch [Bot]' => array('http://lucene.apache.org/nutch/', ''), + 'Online link [Validator]' => array('online link validator', ''), + 'psbot [Picsearch]' => array('psbot/0', ''), + 'Sensis [Crawler]' => array('Sensis Web Crawler', ''), + 'SEO Crawler' => array('SEO search Crawler/', ''), + 'Seoma [Crawler]' => array('Seoma [SEO Crawler]', ''), + 'SEOSearch [Crawler]' => array('SEOsearch/', ''), + 'Snappy [Bot]' => array('Snappy/1.1 ( http://www.urltrends.com/ )', ''), + 'Steeler [Crawler]' => array('http://www.tkl.iis.u-tokyo.ac.jp/~crawler/', ''), + 'Telekom [Bot]' => array('crawleradmin.t-info@telekom.de', ''), + 'TurnitinBot [Bot]' => array('TurnitinBot/', ''), + 'Voyager [Bot]' => array('voyager/', ''), + 'W3 [Sitesearch]' => array('W3 SiteSearch Crawler', ''), + 'W3C [Linkcheck]' => array('W3C-checklink/', ''), + 'W3C [Validator]' => array('W3C_Validator', ''), + 'YaCy [Bot]' => array('yacybot', ''), + 'Yahoo MMCrawler [Bot]' => array('Yahoo-MMCrawler/', ''), + 'Yahoo Slurp [Bot]' => array('Yahoo! DE Slurp', ''), + 'Yahoo [Bot]' => array('Yahoo! Slurp', ''), + 'YahooSeeker [Bot]' => array('YahooSeeker/', ''), + ); + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $io_handler; + + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $install_config Installer's config + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Input-output handler for the installer + * @param \phpbb\install\helper\container_factory $container Installer's DI container + * @param \phpbb\language\language $language Language provider + * @param string $phpbb_root_path Relative path to phpBB root + * @param string $php_ext PHP extension + */ + public function __construct(\phpbb\install\helper\config $install_config, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler, + \phpbb\install\helper\container_factory $container, + \phpbb\language\language $language, + $phpbb_root_path, + $php_ext) + { + parent::__construct(true); + + $this->db = $container->get('dbal.conn'); + $this->install_config = $install_config; + $this->io_handler = $iohandler; + $this->language = $language; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->db->sql_return_on_error(true); + + $sql = 'SELECT group_id + FROM ' . GROUPS_TABLE . " + WHERE group_name = 'BOTS'"; + $result = $this->db->sql_query($sql); + $group_id = (int) $this->db->sql_fetchfield('group_id'); + $this->db->sql_freeresult($result); + + if (!$group_id) + { + // If we reach this point then something has gone very wrong + $this->io_handler->add_error_message('NO_GROUP'); + } + + $i = $this->install_config->get('add_bot_index', 0); + $bot_list = array_slice($this->bot_list, $i); + + foreach ($bot_list as $bot_name => $bot_ary) + { + $user_row = array( + 'user_type' => USER_IGNORE, + 'group_id' => $group_id, + 'username' => $bot_name, + 'user_regdate' => time(), + 'user_password' => '', + 'user_colour' => '9E8DA7', + 'user_email' => '', + 'user_lang' => $this->install_config->get('default_lang'), + 'user_style' => 1, + 'user_timezone' => 'UTC', + 'user_dateformat' => $this->language->lang('default_dateformat'), + 'user_allow_massemail' => 0, + 'user_allow_pm' => 0, + ); + + if (!function_exists('user_add')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + + $user_id = user_add($user_row); + + if (!$user_id) + { + // If we can't insert this user then continue to the next one to avoid inconsistent data + $this->io_handler->add_error_message('CONV_ERROR_INSERT_BOT'); + + $i++; + continue; + } + + $sql = 'INSERT INTO ' . BOTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', array( + 'bot_active' => 1, + 'bot_name' => (string) $bot_name, + 'user_id' => (int) $user_id, + 'bot_agent' => (string) $bot_ary[0], + 'bot_ip' => (string) $bot_ary[1], + )); + + $this->db->sql_query($sql); + + $i++; + + // Stop execution if resource limit is reached + if ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0) + { + break; + } + } + + $this->install_config->set('add_bot_index', $i); + + if ($i < count($this->bot_list)) + { + throw new resource_limit_reached_exception(); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_ADD_BOTS'; + } +} diff --git a/phpbb/install/module/install_data/task/add_languages.php b/phpbb/install/module/install_data/task/add_languages.php new file mode 100644 index 0000000..7ffdf4f --- /dev/null +++ b/phpbb/install/module/install_data/task/add_languages.php @@ -0,0 +1,121 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_data\task; + +class add_languages extends \phpbb\install\task_base +{ + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var \phpbb\language\language_file_helper + */ + protected $language_helper; + + /** + * Constructor + * + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + * @param \phpbb\install\helper\container_factory $container Installer's DI container + * @param \phpbb\language\language_file_helper $language_helper Language file helper service + */ + public function __construct(\phpbb\install\helper\iohandler\iohandler_interface $iohandler, + \phpbb\install\helper\container_factory $container, + \phpbb\language\language_file_helper $language_helper) + { + $this->db = $container->get('dbal.conn'); + $this->iohandler = $iohandler; + $this->language_helper = $language_helper; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->db->sql_return_on_error(true); + + $languages = $this->language_helper->get_available_languages(); + $installed_languages = array(); + + foreach ($languages as $lang_info) + { + $lang_pack = array( + 'lang_iso' => $lang_info['iso'], + 'lang_dir' => $lang_info['iso'], + 'lang_english_name' => htmlspecialchars($lang_info['name']), + 'lang_local_name' => htmlspecialchars($lang_info['local_name'], ENT_COMPAT, 'UTF-8'), + 'lang_author' => htmlspecialchars($lang_info['author'], ENT_COMPAT, 'UTF-8'), + ); + + $this->db->sql_query('INSERT INTO ' . LANG_TABLE . ' ' . $this->db->sql_build_array('INSERT', $lang_pack)); + + $installed_languages[] = (int) $this->db->sql_nextid(); + if ($this->db->get_sql_error_triggered()) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message($error['message']); + } + } + + $sql = 'SELECT * FROM ' . PROFILE_FIELDS_TABLE; + $result = $this->db->sql_query($sql); + + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, PROFILE_LANG_TABLE); + while ($row = $this->db->sql_fetchrow($result)) + { + foreach ($installed_languages as $lang_id) + { + $insert_buffer->insert(array( + 'field_id' => $row['field_id'], + 'lang_id' => $lang_id, + + // Remove phpbb_ from field name + 'lang_name' => strtoupper(substr($row['field_name'], 6)), + 'lang_explain' => '', + 'lang_default_value' => '', + )); + } + } + + $this->db->sql_freeresult($result); + + $insert_buffer->flush(); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_ADD_LANGUAGES'; + } +} diff --git a/phpbb/install/module/install_data/task/add_modules.php b/phpbb/install/module/install_data/task/add_modules.php new file mode 100644 index 0000000..b64f4c3 --- /dev/null +++ b/phpbb/install/module/install_data/task/add_modules.php @@ -0,0 +1,568 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_data\task; + +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\iohandler\iohandler_interface; + +class add_modules extends \phpbb\install\task_base +{ + /** + * @var config + */ + protected $config; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\extension\manager + */ + protected $extension_manager; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var \phpbb\module\module_manager + */ + protected $module_manager; + + /** + * Define the module structure so that we can populate the database without + * needing to hard-code module_id values + * + * @var array + */ + protected $module_categories = array( + 'acp' => array( + 'ACP_CAT_GENERAL' => array( + 'ACP_QUICK_ACCESS', + 'ACP_BOARD_CONFIGURATION', + 'ACP_CLIENT_COMMUNICATION', + 'ACP_SERVER_CONFIGURATION', + ), + 'ACP_CAT_FORUMS' => array( + 'ACP_MANAGE_FORUMS', + 'ACP_FORUM_BASED_PERMISSIONS', + ), + 'ACP_CAT_POSTING' => array( + 'ACP_MESSAGES', + 'ACP_ATTACHMENTS', + ), + 'ACP_CAT_USERGROUP' => array( + 'ACP_CAT_USERS', + 'ACP_GROUPS', + 'ACP_USER_SECURITY', + ), + 'ACP_CAT_PERMISSIONS' => array( + 'ACP_GLOBAL_PERMISSIONS', + 'ACP_FORUM_BASED_PERMISSIONS', + 'ACP_PERMISSION_ROLES', + 'ACP_PERMISSION_MASKS', + ), + 'ACP_CAT_CUSTOMISE' => array( + 'ACP_STYLE_MANAGEMENT', + 'ACP_EXTENSION_MANAGEMENT', + 'ACP_LANGUAGE', + ), + 'ACP_CAT_MAINTENANCE' => array( + 'ACP_FORUM_LOGS', + 'ACP_CAT_DATABASE', + ), + 'ACP_CAT_SYSTEM' => array( + 'ACP_AUTOMATION', + 'ACP_GENERAL_TASKS', + 'ACP_MODULE_MANAGEMENT', + ), + 'ACP_CAT_DOT_MODS' => null, + ), + 'mcp' => array( + 'MCP_MAIN' => null, + 'MCP_QUEUE' => null, + 'MCP_REPORTS' => null, + 'MCP_NOTES' => null, + 'MCP_WARN' => null, + 'MCP_LOGS' => null, + 'MCP_BAN' => null, + ), + 'ucp' => array( + 'UCP_MAIN' => null, + 'UCP_PROFILE' => null, + 'UCP_PREFS' => null, + 'UCP_PM' => null, + 'UCP_USERGROUPS' => null, + 'UCP_ZEBRA' => null, + ), + ); + + /** + * @var array + */ + protected $module_categories_basenames = array( + 'UCP_PM' => 'ucp_pm', + ); + + /** + * @var array + */ + protected $module_extras = array( + 'acp' => array( + 'ACP_QUICK_ACCESS' => array( + 'ACP_MANAGE_USERS', + 'ACP_GROUPS_MANAGE', + 'ACP_MANAGE_FORUMS', + 'ACP_MOD_LOGS', + 'ACP_BOTS', + 'ACP_PHP_INFO', + ), + 'ACP_FORUM_BASED_PERMISSIONS' => array( + 'ACP_FORUM_PERMISSIONS', + 'ACP_FORUM_PERMISSIONS_COPY', + 'ACP_FORUM_MODERATORS', + 'ACP_USERS_FORUM_PERMISSIONS', + 'ACP_GROUPS_FORUM_PERMISSIONS', + ), + ), + ); + + /** + * Constructor + * + * @parma config $config Installer's config + * @param iohandler_interface $iohandler Installer's input-output handler + * @param container_factory $container Installer's DI container + */ + public function __construct(config $config, iohandler_interface $iohandler, container_factory $container) + { + $this->config = $config; + $this->db = $container->get('dbal.conn'); + $this->extension_manager = $container->get('ext.manager'); + $this->iohandler = $iohandler; + $this->module_manager = $container->get('module.manager'); + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->db->sql_return_on_error(true); + + $module_classes = array('acp', 'mcp', 'ucp'); + $total = count($module_classes); + $i = $this->config->get('module_class_index', 0); + $module_classes = array_slice($module_classes, $i); + + foreach ($module_classes as $module_class) + { + $categories = $this->config->get('module_categories_array', array()); + + $k = $this->config->get('module_categories_index', 0); + $module_categories = array_slice($this->module_categories[$module_class], $k); + $timed_out = false; + + foreach ($module_categories as $cat_name => $subs) + { + // Check if this sub-category has a basename. If it has, use it. + $basename = (isset($this->module_categories_basenames[$cat_name])) ? $this->module_categories_basenames[$cat_name] : ''; + + $module_data = array( + 'module_basename' => $basename, + 'module_enabled' => 1, + 'module_display' => 1, + 'parent_id' => 0, + 'module_class' => $module_class, + 'module_langname' => $cat_name, + 'module_mode' => '', + 'module_auth' => '', + ); + + $this->module_manager->update_module_data($module_data); + + // Check for last sql error happened + if ($this->db->get_sql_error_triggered()) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message('INST_ERR_DB', $error['message']); + } + + $categories[$cat_name]['id'] = (int) $module_data['module_id']; + $categories[$cat_name]['parent_id'] = 0; + + if (is_array($subs)) + { + foreach ($subs as $level2_name) + { + // Check if this sub-category has a basename. If it has, use it. + $basename = (isset($this->module_categories_basenames[$level2_name])) ? $this->module_categories_basenames[$level2_name] : ''; + + $module_data = array( + 'module_basename' => $basename, + 'module_enabled' => 1, + 'module_display' => 1, + 'parent_id' => (int) $categories[$cat_name]['id'], + 'module_class' => $module_class, + 'module_langname' => $level2_name, + 'module_mode' => '', + 'module_auth' => '', + ); + + $this->module_manager->update_module_data($module_data); + + // Check for last sql error happened + if ($this->db->get_sql_error_triggered()) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message('INST_ERR_DB', $error['message']); + } + + $categories[$level2_name]['id'] = (int) $module_data['module_id']; + $categories[$level2_name]['parent_id'] = (int) $categories[$cat_name]['id']; + } + } + + $k++; + + // Stop execution if resource limit is reached + if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) + { + $timed_out = true; + break; + } + } + + $this->config->set('module_categories_array', $categories); + $this->config->set('module_categories_index', $k); + + if ($timed_out) + { + throw new resource_limit_reached_exception(); + } + + // Get the modules we want to add... returned sorted by name + $module_info = $this->module_manager->get_module_infos($module_class); + + $k = $this->config->get('module_info_index', 0); + $module_info = array_slice($module_info, $k); + + foreach ($module_info as $module_basename => $fileinfo) + { + foreach ($fileinfo['modes'] as $module_mode => $row) + { + foreach ($row['cat'] as $cat_name) + { + if (!isset($categories[$cat_name])) + { + continue; + } + + $module_data = array( + 'module_basename' => $module_basename, + 'module_enabled' => 1, + 'module_display' => (isset($row['display'])) ? (int) $row['display'] : 1, + 'parent_id' => (int) $categories[$cat_name]['id'], + 'module_class' => $module_class, + 'module_langname' => $row['title'], + 'module_mode' => $module_mode, + 'module_auth' => $row['auth'], + ); + + $this->module_manager->update_module_data($module_data); + + // Check for last sql error happened + if ($this->db->get_sql_error_triggered()) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message('INST_ERR_DB', $error['message']); + } + } + } + + $k++; + + // Stop execution if resource limit is reached + if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) + { + $timed_out = true; + break; + } + } + + $this->config->set('module_info_index', $k); + + // Stop execution if resource limit is reached + if ($timed_out) + { + throw new resource_limit_reached_exception(); + } + + // Move some of the modules around since the code above will put them in the wrong place + if (!$this->config->get('modules_ordered', false)) + { + $this->order_modules($module_class); + $this->config->set('modules_ordered', true); + + // Stop execution if resource limit is reached + if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) + { + throw new resource_limit_reached_exception(); + } + } + + // And now for the special ones + // (these are modules which appear in multiple categories and thus get added manually + // to some for more control) + if (isset($this->module_extras[$module_class])) + { + $this->add_module_extras($module_class); + } + + $this->module_manager->remove_cache_file($module_class); + + $i++; + + $this->config->set('module_class_index', $i); + $this->config->set('module_categories_index', 0); + $this->config->set('module_info_index', 0); + $this->config->set('added_extra_modules', false); + $this->config->set('modules_ordered', false); + $this->config->set('module_categories_array', array()); + + // Stop execution if resource limit is reached + if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) + { + break; + } + } + + if ($i < $total) + { + throw new resource_limit_reached_exception(); + } + } + + /** + * Move modules to their correct place + * + * @param string $module_class + */ + protected function order_modules($module_class) + { + if ($module_class == 'acp') + { + // Move main module 4 up... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'acp_main' + AND module_class = 'acp' + AND module_mode = 'main'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'acp', 'move_up', 4); + + // Move permissions intro screen module 4 up... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'acp_permissions' + AND module_class = 'acp' + AND module_mode = 'intro'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'acp', 'move_up', 4); + + // Move manage users screen module 5 up... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'acp_users' + AND module_class = 'acp' + AND module_mode = 'overview'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'acp', 'move_up', 5); + + // Move extension management module 1 up... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_langname = 'ACP_EXTENSION_MANAGEMENT' + AND module_class = 'acp' + AND module_mode = '' + AND module_basename = ''"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'acp', 'move_up', 1); + } + + if ($module_class == 'mcp') + { + // Move pm report details module 3 down... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'mcp_pm_reports' + AND module_class = 'mcp' + AND module_mode = 'pm_report_details'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'mcp', 'move_down', 3); + + // Move closed pm reports module 3 down... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'mcp_pm_reports' + AND module_class = 'mcp' + AND module_mode = 'pm_reports_closed'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'mcp', 'move_down', 3); + + // Move open pm reports module 3 down... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'mcp_pm_reports' + AND module_class = 'mcp' + AND module_mode = 'pm_reports'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'mcp', 'move_down', 3); + } + + if ($module_class == 'ucp') + { + // Move attachment module 4 down... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'ucp_attachments' + AND module_class = 'ucp' + AND module_mode = 'attachments'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'ucp', 'move_down', 4); + + // Move notification options module 4 down... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'ucp_notifications' + AND module_class = 'ucp' + AND module_mode = 'notification_options'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'ucp', 'move_down', 4); + + // Move OAuth module 5 down... + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_basename = 'ucp_auth_link' + AND module_class = 'ucp' + AND module_mode = 'auth_link'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $this->module_manager->move_module_by($row, 'ucp', 'move_down', 5); + } + } + + /** + * Add extra modules + * + * @param string $module_class + */ + protected function add_module_extras($module_class) + { + foreach ($this->module_extras[$module_class] as $cat_name => $mods) + { + $sql = 'SELECT module_id, left_id, right_id + FROM ' . MODULES_TABLE . " + WHERE module_langname = '" . $this->db->sql_escape($cat_name) . "' + AND module_class = '" . $this->db->sql_escape($module_class) . "'"; + $result = $this->db->sql_query_limit($sql, 1); + $row2 = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + foreach ($mods as $mod_name) + { + $sql = 'SELECT * + FROM ' . MODULES_TABLE . " + WHERE module_langname = '" . $this->db->sql_escape($mod_name) . "' + AND module_class = '" . $this->db->sql_escape($module_class) . "' + AND module_basename <> ''"; + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $module_data = array( + 'module_basename' => $row['module_basename'], + 'module_enabled' => (int) $row['module_enabled'], + 'module_display' => (int) $row['module_display'], + 'parent_id' => (int) $row2['module_id'], + 'module_class' => $row['module_class'], + 'module_langname' => $row['module_langname'], + 'module_mode' => $row['module_mode'], + 'module_auth' => $row['module_auth'], + ); + + $this->module_manager->update_module_data($module_data); + + // Check for last sql error happened + if ($this->db->get_sql_error_triggered()) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message('INST_ERR_DB', $error['message']); + } + } + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_ADD_MODULES'; + } +} diff --git a/phpbb/install/module/install_data/task/create_search_index.php b/phpbb/install/module/install_data/task/create_search_index.php new file mode 100644 index 0000000..8a2f6aa --- /dev/null +++ b/phpbb/install/module/install_data/task/create_search_index.php @@ -0,0 +1,134 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_data\task; + +use phpbb\auth\auth; +use phpbb\db\driver\driver_interface; +use phpbb\event\dispatcher; +use phpbb\config\config; +use phpbb\install\helper\container_factory; +use phpbb\language\language; +use phpbb\search\fulltext_native; +use phpbb\user; + +class create_search_index extends \phpbb\install\task_base +{ + /** + * @var auth + */ + protected $auth; + + /** + * @var config + */ + protected $config; + + /** + * @var driver_interface + */ + protected $db; + + /** + * @var dispatcher + */ + protected $phpbb_dispatcher; + + /** + * @var language + */ + protected $language; + + /** + * @var user + */ + protected $user; + + /** + * @var string phpBB root path + */ + protected $phpbb_root_path; + + /** + * @var string PHP file extension + */ + protected $php_ext; + + /** + * Constructor + * + * @param config $config phpBB config + * @param container_factory $container Installer's DI container + * @param string $phpbb_root_path phpBB root path + * @param string $php_ext PHP file extension + */ + public function __construct(config $config, container_factory $container, + $phpbb_root_path, $php_ext) + { + $this->auth = $container->get('auth'); + $this->config = $config; + $this->db = $container->get('dbal.conn'); + $this->language = $container->get('language'); + $this->phpbb_dispatcher = $container->get('dispatcher'); + $this->user = $container->get('user'); + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Make sure fulltext native load update is set + $this->config->set('fulltext_native_load_upd', 1); + + $error = false; + $search = new fulltext_native( + $error, + $this->phpbb_root_path, + $this->php_ext, + $this->auth, + $this->config, + $this->db, + $this->user, + $this->phpbb_dispatcher + ); + + $sql = 'SELECT post_id, post_subject, post_text, poster_id, forum_id + FROM ' . POSTS_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $search->index('post', $row['post_id'], $row['post_text'], $row['post_subject'], $row['poster_id'], $row['forum_id']); + } + $this->db->sql_freeresult($result); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_CREATE_SEARCH_INDEX'; + } +} diff --git a/phpbb/install/module/install_database/module.php b/phpbb/install/module/install_database/module.php new file mode 100644 index 0000000..0d8b330 --- /dev/null +++ b/phpbb/install/module/install_database/module.php @@ -0,0 +1,28 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_database; + +/** + * Installer module for database installation + */ +class module extends \phpbb\install\module_base +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('install', 0, 'install'); + } +} diff --git a/phpbb/install/module/install_database/task/add_config_settings.php b/phpbb/install/module/install_database/task/add_config_settings.php new file mode 100644 index 0000000..ba43960 --- /dev/null +++ b/phpbb/install/module/install_database/task/add_config_settings.php @@ -0,0 +1,368 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create database schema + */ +class add_config_settings extends \phpbb\install\task_base +{ + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * @var \phpbb\passwords\manager + */ + protected $password_manager; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $config_table; + + /** + * @var string + */ + protected $user_table; + + /** + * @var string + */ + protected $topics_table; + + /** + * @var string + */ + protected $forums_table; + + /** + * @var string + */ + protected $posts_table; + + /** + * @var string + */ + protected $moderator_cache_table; + + /** + * Constructor + * + * @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem service + * @param \phpbb\install\helper\config $install_config Installer's config helper + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + * @param \phpbb\install\helper\container_factory $container Installer's DI container + * @param \phpbb\language\language $language Language service + * @param string $phpbb_root_path Path to phpBB's root + */ + public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, + \phpbb\install\helper\config $install_config, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler, + \phpbb\install\helper\container_factory $container, + \phpbb\language\language $language, + $phpbb_root_path) + { + $this->db = $container->get('dbal.conn'); + $this->filesystem = $filesystem; + $this->install_config = $install_config; + $this->iohandler = $iohandler; + $this->language = $language; + $this->password_manager = $container->get('passwords.manager'); + $this->phpbb_root_path = $phpbb_root_path; + + // Table names + $this->config_table = $container->get_parameter('tables.config'); + $this->forums_table = $container->get_parameter('tables.forums'); + $this->topics_table = $container->get_parameter('tables.topics'); + $this->user_table = $container->get_parameter('tables.users'); + $this->moderator_cache_table = $container->get_parameter('tables.moderator_cache'); + $this->posts_table = $container->get_parameter('tables.posts'); + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->db->sql_return_on_error(true); + + $server_name = $this->install_config->get('server_name'); + $current_time = time(); + $user_ip = phpbb_ip_normalise($this->iohandler->get_server_variable('REMOTE_ADDR')); + $user_ip = ($user_ip === false) ? '' : $user_ip; + $referer = $this->iohandler->get_server_variable('REFERER'); + + // Calculate cookie domain + $cookie_domain = $server_name; + + if (strpos($cookie_domain, 'www.') === 0) + { + $cookie_domain = substr($cookie_domain, 3); + } + + // Set default config and post data, this applies to all DB's + $sql_ary = array( + 'INSERT INTO ' . $this->config_table . " (config_name, config_value) + VALUES ('board_startdate', '$current_time')", + + 'INSERT INTO ' . $this->config_table . " (config_name, config_value) + VALUES ('default_lang', '" . $this->db->sql_escape($this->install_config->get('default_lang')) . "')", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_name')) . "' + WHERE config_name = 'server_name'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_port')) . "' + WHERE config_name = 'server_port'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_email')) . "' + WHERE config_name = 'board_email'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_email')) . "' + WHERE config_name = 'board_contact'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($cookie_domain) . "' + WHERE config_name = 'cookie_domain'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->language->lang('default_dateformat')) . "' + WHERE config_name = 'default_dateformat'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('email_enable')) . "' + WHERE config_name = 'email_enable'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_delivery')) . "' + WHERE config_name = 'smtp_delivery'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_host')) . "' + WHERE config_name = 'smtp_host'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_port')) . "' + WHERE config_name = 'smtp_port'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_auth')) . "' + WHERE config_name = 'smtp_auth_method'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_user')) . "' + WHERE config_name = 'smtp_username'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('smtp_pass')) . "' + WHERE config_name = 'smtp_password'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('cookie_secure')) . "' + WHERE config_name = 'cookie_secure'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('force_server_vars')) . "' + WHERE config_name = 'force_server_vars'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('script_path')) . "' + WHERE config_name = 'script_path'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('server_protocol')) . "' + WHERE config_name = 'server_protocol'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "' + WHERE config_name = 'newest_username'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . md5(mt_rand()) . "' + WHERE config_name = 'avatar_salt'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . md5(mt_rand()) . "' + WHERE config_name = 'plupload_salt'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_name')) . "' + WHERE config_name = 'sitename'", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->install_config->get('board_description')) . "' + WHERE config_name = 'site_desc'", + + 'UPDATE ' . $this->user_table . " + SET username = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "', + user_password='" . $this->password_manager->hash($this->install_config->get('admin_passwd')) . "', + user_ip = '" . $this->db->sql_escape($user_ip) . "', + user_lang = '" . $this->db->sql_escape($this->install_config->get('user_language', 'en')) . "', + user_email='" . $this->db->sql_escape($this->install_config->get('board_email')) . "', + user_dateformat='" . $this->db->sql_escape($this->language->lang('default_dateformat')) . "', + user_email_hash = " . $this->db->sql_escape(phpbb_email_hash($this->install_config->get('board_email'))) . ", + username_clean = '" . $this->db->sql_escape(utf8_clean_string($this->install_config->get('admin_name'))) . "' + WHERE username = 'Admin'", + + 'UPDATE ' . $this->moderator_cache_table . " + SET username = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "' + WHERE username = 'Admin'", + + 'UPDATE ' . $this->forums_table . " + SET forum_last_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "' + WHERE forum_last_poster_name = 'Admin'", + + 'UPDATE ' . $this->topics_table . " + SET topic_first_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "', + topic_last_poster_name = '" . $this->db->sql_escape($this->install_config->get('admin_name')) . "' + WHERE topic_first_poster_name = 'Admin' + OR topic_last_poster_name = 'Admin'", + + 'UPDATE ' . $this->user_table . " + SET user_regdate = $current_time", + + 'UPDATE ' . $this->posts_table . " + SET post_time = $current_time, poster_ip = '" . $this->db->sql_escape($user_ip) . "'", + + 'UPDATE ' . $this->topics_table . " + SET topic_time = $current_time, topic_last_post_time = $current_time", + + 'UPDATE ' . $this->forums_table . " + SET forum_last_post_time = $current_time", + + 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($this->db->sql_server_info(true)) . "' + WHERE config_name = 'dbms_version'", + ); + + if (@extension_loaded('gd')) + { + $sql_ary[] = 'UPDATE ' . $this->config_table . " + SET config_value = 'core.captcha.plugins.gd' + WHERE config_name = 'captcha_plugin'"; + + $sql_ary[] = 'UPDATE ' . $this->config_table . " + SET config_value = '1' + WHERE config_name = 'captcha_gd'"; + } + + $ref = substr($referer, strpos($referer, '://') + 3); + if (!(stripos($ref, $server_name) === 0)) + { + $sql_ary[] = 'UPDATE ' . $this->config_table . " + SET config_value = '0' + WHERE config_name = 'referer_validation'"; + } + + // We set a (semi-)unique cookie name to bypass login issues related to the cookie name. + $cookie_name = 'phpbb3_'; + $rand_str = md5(mt_rand()); + $rand_str = str_replace('0', 'z', base_convert($rand_str, 16, 35)); + $rand_str = substr($rand_str, 0, 5); + $cookie_name .= strtolower($rand_str); + + $sql_ary[] = 'UPDATE ' . $this->config_table . " + SET config_value = '" . $this->db->sql_escape($cookie_name) . "' + WHERE config_name = 'cookie_name'"; + + // Disable avatars if upload directory is not writable + if (!$this->filesystem->is_writable($this->phpbb_root_path . 'images/avatars/upload/')) + { + $sql_ary[] = 'UPDATE ' . $this->config_table . " + SET config_value = '0' + WHERE config_name = 'allow_avatar'"; + + $sql_ary[] = 'UPDATE ' . $this->config_table . " + SET config_value = '0' + WHERE config_name = 'allow_avatar_upload'"; + } + + $i = $this->install_config->get('add_config_settings_index', 0); + $total = count($sql_ary); + $sql_ary = array_slice($sql_ary, $i); + + foreach ($sql_ary as $sql) + { + if (!$this->db->sql_query($sql)) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message('INST_ERR_DB', $error['message']); + } + + $i++; + + // Stop execution if resource limit is reached + if ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0) + { + break; + } + } + + if ($i < $total) + { + $this->install_config->set('add_config_settings_index', $i); + throw new resource_limit_reached_exception(); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_ADD_CONFIG_SETTINGS'; + } +} diff --git a/phpbb/install/module/install_database/task/add_default_data.php b/phpbb/install/module/install_database/task/add_default_data.php new file mode 100644 index 0000000..c05e532 --- /dev/null +++ b/phpbb/install/module/install_database/task/add_default_data.php @@ -0,0 +1,184 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create database schema + */ +class add_default_data extends \phpbb\install\task_base +{ + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\install\helper\database + */ + protected $database_helper; + + /** + * @var \phpbb\install\helper\config + */ + protected $config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param \phpbb\install\helper\database $db_helper Installer's database helper + * @param \phpbb\install\helper\config $config Installer config + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + * @param \phpbb\install\helper\container_factory $container Installer's DI container + * @param \phpbb\language\language $language Language service + * @param string $root_path Root path of phpBB + */ + public function __construct(\phpbb\install\helper\database $db_helper, + \phpbb\install\helper\config $config, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler, + \phpbb\install\helper\container_factory $container, + \phpbb\language\language $language, + $root_path) + { + $this->db = $container->get('dbal.conn.driver'); + $this->database_helper = $db_helper; + $this->config = $config; + $this->iohandler = $iohandler; + $this->language = $language; + $this->phpbb_root_path = $root_path; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->db->sql_return_on_error(true); + + $table_prefix = $this->config->get('table_prefix'); + $dbms = $this->config->get('dbms'); + $dbms_info = $this->database_helper->get_available_dbms($dbms); + + // Get schema data from file + $sql_query = @file_get_contents($this->phpbb_root_path . 'install/schemas/schema_data.sql'); + + // Clean up SQL + $sql_query = $this->replace_dbms_specific_sql($sql_query); + $sql_query = preg_replace('# phpbb_([^\s]*) #i', ' ' . $table_prefix . '\1 ', $sql_query); + $sql_query = preg_replace_callback('#\{L_([A-Z0-9\-_]*)\}#s', array($this, 'lang_replace_callback'), $sql_query); + $sql_query = $this->database_helper->remove_comments($sql_query); + $sql_query = $this->database_helper->split_sql_file($sql_query, $dbms_info[$dbms]['DELIM']); + + $i = $this->config->get('add_default_data_index', 0); + $total = count($sql_query); + $sql_query = array_slice($sql_query, $i); + + foreach ($sql_query as $sql) + { + if (!$this->db->sql_query($sql)) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message('INST_ERR_DB', $error['message']); + } + + $i++; + + // Stop execution if resource limit is reached + if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) + { + break; + } + } + + $this->config->set('add_default_data_index', $i); + + if ($i < $total) + { + throw new resource_limit_reached_exception(); + } + } + + /** + * Process DB specific SQL + * + * @return string + */ + protected function replace_dbms_specific_sql($query) + { + if ($this->db instanceof \phpbb\db\driver\mssql_base) + { + $query = preg_replace('#\# MSSQL IDENTITY (phpbb_[a-z_]+) (ON|OFF) \##s', 'SET IDENTITY_INSERT \1 \2;', $query); + } + else if ($this->db instanceof \phpbb\db\driver\postgres) + { + $query = preg_replace('#\# POSTGRES (BEGIN|COMMIT) \##s', '\1; ', $query); + } + else if ($this->db instanceof \phpbb\db\driver\mysql_base) + { + $query = str_replace('\\', '\\\\', $query); + } + + return $query; + } + + /** + * Callback function for language replacing + * + * @param array $matches + * @return string + */ + public function lang_replace_callback($matches) + { + if (!empty($matches[1])) + { + return $this->db->sql_escape($this->language->lang($matches[1])); + } + + return ''; + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_ADD_DEFAULT_DATA'; + } +} diff --git a/phpbb/install/module/install_database/task/add_tables.php b/phpbb/install/module/install_database/task/add_tables.php new file mode 100644 index 0000000..dc814f3 --- /dev/null +++ b/phpbb/install/module/install_database/task/add_tables.php @@ -0,0 +1,151 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create tables + */ +class add_tables extends \phpbb\install\task_base +{ + /** + * @var \phpbb\install\helper\config + */ + protected $config; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\db\tools\tools_interface + */ + protected $db_tools; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var string + */ + protected $schema_file_path; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $config + * @param \phpbb\install\helper\database $db_helper + * @param \phpbb\filesystem\filesystem_interface $filesystem + * @param string $phpbb_root_path + */ + public function __construct(\phpbb\install\helper\config $config, + \phpbb\install\helper\database $db_helper, + \phpbb\filesystem\filesystem_interface $filesystem, + $phpbb_root_path) + { + $dbms = $db_helper->get_available_dbms($config->get('dbms')); + $dbms = $dbms[$config->get('dbms')]['DRIVER']; + $factory = new \phpbb\db\tools\factory(); + + $this->db = new $dbms(); + $this->db->sql_connect( + $config->get('dbhost'), + $config->get('dbuser'), + $config->get('dbpasswd'), + $config->get('dbname'), + $config->get('dbport'), + false, + false + ); + + $this->config = $config; + $this->db_tools = $factory->get($this->db); + $this->filesystem = $filesystem; + $this->schema_file_path = $phpbb_root_path . 'store/schema.json'; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->db->sql_return_on_error(true); + + $table_prefix = $this->config->get('table_prefix'); + $change_prefix = $this->config->get('change_table_prefix', true); + + if (!defined('CONFIG_TABLE')) + { + // CONFIG_TABLE is required by sql_create_index() to check the + // length of index names. However table_prefix is not defined + // here yet, so we need to create the constant ourselves. + define('CONFIG_TABLE', $table_prefix . 'config'); + } + + $db_table_schema = @file_get_contents($this->schema_file_path); + $db_table_schema = json_decode($db_table_schema, true); + $total = count($db_table_schema); + $i = $this->config->get('add_table_index', 0); + $db_table_schema = array_slice($db_table_schema, $i); + + foreach ($db_table_schema as $table_name => $table_data) + { + $i++; + + $this->db_tools->sql_create_table( + ( ($change_prefix) ? ($table_prefix . substr($table_name, 6)) : $table_name ), + $table_data + ); + + // Stop execution if resource limit is reached + if ($this->config->get_time_remaining() <= 0 || $this->config->get_memory_remaining() <= 0) + { + break; + } + } + + $this->config->set('add_table_index', $i); + + if ($i < $total) + { + throw new resource_limit_reached_exception(); + } + else + { + @unlink($this->schema_file_path); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_CREATE_TABLES'; + } +} diff --git a/phpbb/install/module/install_database/task/create_schema.php b/phpbb/install/module/install_database/task/create_schema.php new file mode 100644 index 0000000..a5635d5 --- /dev/null +++ b/phpbb/install/module/install_database/task/create_schema.php @@ -0,0 +1,234 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create database schema + */ +class create_schema extends \phpbb\install\task_base +{ + /** + * @var \phpbb\install\helper\config + */ + protected $config; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\db\tools\tools_interface + */ + protected $db_tools; + + /** + * @var \phpbb\install\helper\database + */ + protected $database_helper; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $config Installer's config provider + * @param \phpbb\install\helper\database $db_helper Installer's database helper + * @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem service + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + * @param string $phpbb_root_path Path phpBB's root + * @param string $php_ext Extension of PHP files + */ + public function __construct(\phpbb\install\helper\config $config, + \phpbb\install\helper\database $db_helper, + \phpbb\filesystem\filesystem_interface $filesystem, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler, + $phpbb_root_path, + $php_ext) + { + $dbms = $db_helper->get_available_dbms($config->get('dbms')); + $dbms = $dbms[$config->get('dbms')]['DRIVER']; + $factory = new \phpbb\db\tools\factory(); + + $this->db = new $dbms(); + $this->db->sql_connect( + $config->get('dbhost'), + $config->get('dbuser'), + $config->get('dbpasswd'), + $config->get('dbname'), + $config->get('dbport'), + false, + false + ); + + $this->config = $config; + $this->db_tools = $factory->get($this->db); + $this->database_helper = $db_helper; + $this->filesystem = $filesystem; + $this->iohandler = $iohandler; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // As this task may take a large amount of time to complete refreshing the page might be necessary for some + // server configurations with limited resources + if (!$this->config->get('pre_schema_forced_refresh')) + { + if ($this->config->get_time_remaining() < 5) + { + $this->config->set('pre_schema_forced_refresh', true); + throw new resource_limit_reached_exception(); + } + } + + $this->db->sql_return_on_error(true); + + $dbms = $this->config->get('dbms'); + $dbms_info = $this->database_helper->get_available_dbms($dbms); + $schema_name = $dbms_info[$dbms]['SCHEMA']; + $delimiter = $dbms_info[$dbms]['DELIM']; + $table_prefix = $this->config->get('table_prefix'); + + if ($dbms === 'mysql') + { + if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $schema_name .= '_41'; + } + else + { + $schema_name .= '_40'; + } + } + + $db_schema_path = $this->phpbb_root_path . 'install/schemas/' . $schema_name . '_schema.sql'; + + // Load database vendor specific code if there is any + if ($this->filesystem->exists($db_schema_path)) + { + $sql_query = @file_get_contents($db_schema_path); + $sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query); + $sql_query = $this->database_helper->remove_comments($sql_query); + $sql_query = $this->database_helper->split_sql_file($sql_query, $delimiter); + + foreach ($sql_query as $sql) + { + if (!$this->db->sql_query($sql)) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message('INST_ERR_DB', $error['message']); + } + } + + unset($sql_query); + } + + $change_prefix = false; + + // Generate database schema + if ($this->filesystem->exists($this->phpbb_root_path . 'install/schemas/schema.json')) + { + $db_table_schema = @file_get_contents($this->phpbb_root_path . 'install/schemas/schema.json'); + $db_table_schema = json_decode($db_table_schema, true); + $change_prefix = true; + } + else + { + global $table_prefix; + + $table_prefix = $this->config->get('table_prefix'); + + if (!defined('CONFIG_TABLE')) + { + // We need to include the constants file for the table constants + // when we generate the schema from the migration files. + include ($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); + } + + $finder = new \phpbb\finder($this->filesystem, $this->phpbb_root_path, null, $this->php_ext); + $migrator_classes = $finder->core_path('phpbb/db/migration/data/')->get_classes(); + $factory = new \phpbb\db\tools\factory(); + $db_tools = $factory->get($this->db, true); + $schema_generator = new \phpbb\db\migration\schema_generator( + $migrator_classes, + new \phpbb\config\config(array()), + $this->db, + $db_tools, + $this->phpbb_root_path, + $this->php_ext, + $table_prefix + ); + $db_table_schema = $schema_generator->get_schema(); + } + + if (!defined('CONFIG_TABLE')) + { + // CONFIG_TABLE is required by sql_create_index() to check the + // length of index names. However table_prefix is not defined + // here yet, so we need to create the constant ourselves. + define('CONFIG_TABLE', $table_prefix . 'config'); + } + + foreach ($db_table_schema as $table_name => $table_data) + { + $this->db_tools->sql_create_table( + ( ($change_prefix) ? ($table_prefix . substr($table_name, 6)) : $table_name ), + $table_data + ); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_CREATE_DATABASE_SCHEMA'; + } +} diff --git a/phpbb/install/module/install_database/task/create_schema_file.php b/phpbb/install/module/install_database/task/create_schema_file.php new file mode 100644 index 0000000..b6d6ece --- /dev/null +++ b/phpbb/install/module/install_database/task/create_schema_file.php @@ -0,0 +1,164 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Create database schema + */ +class create_schema_file extends \phpbb\install\task_base +{ + /** + * @var \phpbb\install\helper\config + */ + protected $config; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $config Installer's config provider + * @param \phpbb\install\helper\database $db_helper Installer's database helper + * @param \phpbb\filesystem\filesystem_interface $filesystem Filesystem service + * @param string $phpbb_root_path Path phpBB's root + * @param string $php_ext Extension of PHP files + */ + public function __construct(\phpbb\install\helper\config $config, + \phpbb\install\helper\database $db_helper, + \phpbb\filesystem\filesystem_interface $filesystem, + $phpbb_root_path, + $php_ext) + { + $dbms = $db_helper->get_available_dbms($config->get('dbms')); + $dbms = $dbms[$config->get('dbms')]['DRIVER']; + + $this->db = new $dbms(); + $this->db->sql_connect( + $config->get('dbhost'), + $config->get('dbuser'), + $config->get('dbpasswd'), + $config->get('dbname'), + $config->get('dbport'), + false, + false + ); + + $this->config = $config; + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Generate database schema + if ($this->filesystem->exists($this->phpbb_root_path . 'install/schemas/schema.json')) + { + $db_table_schema = @file_get_contents($this->phpbb_root_path . 'install/schemas/schema.json'); + $this->config->set('change_table_prefix', true); + } + else + { + global $table_prefix; + + // As this task may take a large amount of time to complete refreshing the page might be necessary for some + // server configurations with limited resources + if (!$this->config->get('pre_schema_forced_refresh', false)) + { + if ($this->config->get_time_remaining() < 5) + { + $this->config->set('pre_schema_forced_refresh', true); + throw new resource_limit_reached_exception(); + } + } + + $table_prefix = $this->config->get('table_prefix'); + + if (!defined('CONFIG_TABLE')) + { + // We need to include the constants file for the table constants + // when we generate the schema from the migration files. + include ($this->phpbb_root_path . 'includes/constants.' . $this->php_ext); + } + + $finder = new \phpbb\finder($this->filesystem, $this->phpbb_root_path, null, $this->php_ext); + $migrator_classes = $finder->core_path('phpbb/db/migration/data/')->get_classes(); + $factory = new \phpbb\db\tools\factory(); + $db_tools = $factory->get($this->db, true); + $schema_generator = new \phpbb\db\migration\schema_generator( + $migrator_classes, + new \phpbb\config\config(array()), + $this->db, + $db_tools, + $this->phpbb_root_path, + $this->php_ext, + $table_prefix + ); + $db_table_schema = $schema_generator->get_schema(); + $db_table_schema = json_encode($db_table_schema, JSON_PRETTY_PRINT); + + $this->config->set('change_table_prefix', false); + } + + $fp = @fopen($this->phpbb_root_path . 'store/schema.json', 'wb'); + if (!$fp) + { + throw new \Exception('INST_SCHEMA_FILE_NOT_WRITABLE'); + } + + fwrite($fp, $db_table_schema); + fclose($fp); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_CREATE_DATABASE_SCHEMA_FILE'; + } +} diff --git a/phpbb/install/module/install_database/task/set_up_database.php b/phpbb/install/module/install_database/task/set_up_database.php new file mode 100644 index 0000000..49c8ea2 --- /dev/null +++ b/phpbb/install/module/install_database/task/set_up_database.php @@ -0,0 +1,164 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_database\task; + +/** + * Set up database for table generation + */ +class set_up_database extends \phpbb\install\task_base +{ + /** + * @var \phpbb\install\helper\config + */ + protected $config; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\install\helper\database + */ + protected $database_helper; + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var string + */ + protected $schema_file_path; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $config + * @param \phpbb\install\helper\database $db_helper + * @param \phpbb\filesystem\filesystem_interface $filesystem + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler + * @param string $phpbb_root_path + */ + public function __construct(\phpbb\install\helper\config $config, + \phpbb\install\helper\database $db_helper, + \phpbb\filesystem\filesystem_interface $filesystem, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler, + $phpbb_root_path) + { + $dbms = $db_helper->get_available_dbms($config->get('dbms')); + $dbms = $dbms[$config->get('dbms')]['DRIVER']; + + $this->db = new $dbms(); + $this->db->sql_connect( + $config->get('dbhost'), + $config->get('dbuser'), + $config->get('dbpasswd'), + $config->get('dbname'), + $config->get('dbport'), + false, + false + ); + + $this->config = $config; + $this->database_helper = $db_helper; + $this->filesystem = $filesystem; + $this->iohandler = $iohandler; + $this->phpbb_root_path = $phpbb_root_path; + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + $dbms = $this->config->get('dbms'); + $dbms_info = $this->database_helper->get_available_dbms($dbms); + $schema_name = $dbms_info[$dbms]['SCHEMA']; + + if ($dbms === 'mysql') + { + if (version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $schema_name .= '_41'; + } + else + { + $schema_name .= '_40'; + } + } + + $this->schema_file_path = $this->phpbb_root_path . 'install/schemas/' . $schema_name . '_schema.sql'; + + return $this->filesystem->exists($this->schema_file_path); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->db->sql_return_on_error(true); + + $dbms = $this->config->get('dbms'); + $dbms_info = $this->database_helper->get_available_dbms($dbms); + $delimiter = $dbms_info[$dbms]['DELIM']; + $table_prefix = $this->config->get('table_prefix'); + + $sql_query = @file_get_contents($this->schema_file_path); + $sql_query = preg_replace('#phpbb_#i', $table_prefix, $sql_query); + $sql_query = $this->database_helper->remove_comments($sql_query); + $sql_query = $this->database_helper->split_sql_file($sql_query, $delimiter); + + foreach ($sql_query as $sql) + { + if (!$this->db->sql_query($sql)) + { + $error = $this->db->sql_error($this->db->get_sql_error_sql()); + $this->iohandler->add_error_message('INST_ERR_DB', $error['message']); + } + } + + unset($sql_query); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_SETUP_DATABASE'; + } +} diff --git a/phpbb/install/module/install_filesystem/module.php b/phpbb/install/module/install_filesystem/module.php new file mode 100644 index 0000000..7215449 --- /dev/null +++ b/phpbb/install/module/install_filesystem/module.php @@ -0,0 +1,28 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_filesystem; + +/** + * Installer module for filesystem installation + */ +class module extends \phpbb\install\module_base +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('install', 0, 'install'); + } +} diff --git a/phpbb/install/module/install_filesystem/task/create_config_file.php b/phpbb/install/module/install_filesystem/task/create_config_file.php new file mode 100644 index 0000000..5bc425b --- /dev/null +++ b/phpbb/install/module/install_filesystem/task/create_config_file.php @@ -0,0 +1,244 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_filesystem\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * Dumps config file + */ +class create_config_file extends \phpbb\install\task_base +{ + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var \phpbb\install\helper\database + */ + protected $db_helper; + + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * @var array + */ + protected $options; + + /** + * Constructor + * + * @param \phpbb\filesystem\filesystem_interface $filesystem + * @param \phpbb\install\helper\config $install_config + * @param \phpbb\install\helper\database $db_helper + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler + * @param string $phpbb_root_path + * @param string $php_ext + * @param array $options + */ + public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, + \phpbb\install\helper\config $install_config, + \phpbb\install\helper\database $db_helper, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler, + $phpbb_root_path, + $php_ext, + $options = array()) + { + $this->install_config = $install_config; + $this->db_helper = $db_helper; + $this->filesystem = $filesystem; + $this->iohandler = $iohandler; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->options = array_merge(array( + 'debug' => false, + 'debug_container' => false, + 'environment' => null, + ), $options); + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $config_written = true; + + // Create config.php + $path_to_config = $this->phpbb_root_path . 'config.' . $this->php_ext; + + $fp = @fopen($path_to_config, 'w'); + if (!$fp) + { + $config_written = false; + } + + $config_content = $this->get_config_data($this->options['debug'], $this->options['debug_container'], $this->options['environment']); + + if (!@fwrite($fp, $config_content)) + { + $config_written = false; + } + + @fclose($fp); + + // chmod config.php to be only readable + if ($config_written) + { + try + { + $this->filesystem->phpbb_chmod($path_to_config, \phpbb\filesystem\filesystem_interface::CHMOD_READ); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing, the user will get a notice later + } + } + else + { + $this->iohandler->add_error_message('UNABLE_TO_WRITE_CONFIG_FILE'); + throw new user_interaction_required_exception(); + } + + // Create a lock file to indicate that there is an install in progress + $fp = @fopen($this->phpbb_root_path . 'cache/install_lock', 'wb'); + if ($fp === false) + { + // We were unable to create the lock file - abort + $this->iohandler->add_error_message('UNABLE_TO_WRITE_LOCK'); + throw new user_interaction_required_exception(); + } + @fclose($fp); + + try + { + $this->filesystem->phpbb_chmod($this->phpbb_root_path . 'cache/install_lock', 0777); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing, the user will get a notice later + } + } + + /** + * Returns the content which should be dumped to config.php + * + * @param bool $debug If the debug constants should be enabled by default or not + * @param bool $debug_container If the container should be compiled on + * every page load or not + * @param string $environment The environment to use + * + * @return string content to be written to the config file + */ + protected function get_config_data($debug = false, $debug_container = false, $environment = null) + { + $config_content = "install_config->get('dbms'); + $db_driver = $this->db_helper->get_available_dbms($dbms); + $db_driver = $db_driver[$dbms]['DRIVER']; + + $config_data_array = array( + 'dbms' => $db_driver, + 'dbhost' => $this->install_config->get('dbhost'), + 'dbport' => $this->install_config->get('dbport'), + 'dbname' => $this->install_config->get('dbname'), + 'dbuser' => $this->install_config->get('dbuser'), + 'dbpasswd' => $this->install_config->get('dbpasswd'), + 'table_prefix' => $this->install_config->get('table_prefix'), + + 'phpbb_adm_relative_path' => 'adm/', + + 'acm_type' => 'phpbb\cache\driver\file', + ); + + foreach ($config_data_array as $key => $value) + { + $config_content .= "\${$key} = '" . str_replace("'", "\\'", str_replace('\\', '\\\\', $value)) . "';\n"; + } + + $config_content .= "\n@define('PHPBB_INSTALLED', true);\n"; + $config_content .= "// @define('PHPBB_DISPLAY_LOAD_TIME', true);\n"; + + if ($environment) + { + $config_content .= "@define('PHPBB_ENVIRONMENT', 'test');\n"; + } + else if ($debug) + { + $config_content .= "@define('PHPBB_ENVIRONMENT', 'development');\n"; + } + else + { + $config_content .= "@define('PHPBB_ENVIRONMENT', 'production');\n"; + } + + if ($debug_container) + { + $config_content .= "@define('DEBUG_CONTAINER', true);\n"; + } + else + { + $config_content .= "// @define('DEBUG_CONTAINER', true);\n"; + } + + if ($environment === 'test') + { + $config_content .= "@define('DEBUG_TEST', true);\n"; + + // Mandatory for the functional tests, will be removed by PHPBB3-12623 + $config_content .= "@define('DEBUG', true);\n"; + } + + return $config_content; + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_CREATE_CONFIG_FILE'; + } +} diff --git a/phpbb/install/module/install_finish/module.php b/phpbb/install/module/install_finish/module.php new file mode 100644 index 0000000..3a7544b --- /dev/null +++ b/phpbb/install/module/install_finish/module.php @@ -0,0 +1,28 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_finish; + +/** + * Installer module for filesystem installation + */ +class module extends \phpbb\install\module_base +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('install', 0, 'install'); + } +} diff --git a/phpbb/install/module/install_finish/task/install_extensions.php b/phpbb/install/module/install_finish/task/install_extensions.php new file mode 100644 index 0000000..47ea156 --- /dev/null +++ b/phpbb/install/module/install_finish/task/install_extensions.php @@ -0,0 +1,207 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_finish\task; + +use phpbb\install\exception\resource_limit_reached_exception; + +/** + * Installs extensions that exist in ext folder upon install + */ +class install_extensions extends \phpbb\install\task_base +{ + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var \phpbb\config\db + */ + protected $config; + + /** + * @var \phpbb\log\log_interface + */ + protected $log; + + /** + * @var \phpbb\user + */ + protected $user; + + /** @var \phpbb\extension\manager */ + protected $extension_manager; + + /** @var \Symfony\Component\Finder\Finder */ + protected $finder; + + /** @var string Extension table */ + protected $extension_table; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** + * Constructor + * + * @param \phpbb\install\helper\container_factory $container + * @param \phpbb\install\helper\config $install_config + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler + * @param string $phpbb_root_path phpBB root path + */ + public function __construct(\phpbb\install\helper\container_factory $container, \phpbb\install\helper\config $install_config, \phpbb\install\helper\iohandler\iohandler_interface $iohandler, $phpbb_root_path) + { + $this->install_config = $install_config; + $this->iohandler = $iohandler; + $this->extension_table = $container->get_parameter('tables.ext'); + + $this->log = $container->get('log'); + $this->user = $container->get('user'); + $this->extension_manager = $container->get('ext.manager'); + $this->config = $container->get('config'); + $this->db = $container->get('dbal.conn'); + $this->finder = new \Symfony\Component\Finder\Finder(); + $this->finder->in($phpbb_root_path . 'ext/') + ->ignoreUnreadableDirs() + ->depth('< 3') + ->files() + ->name('composer.json'); + + // Make sure asset version exists in config. Otherwise we might try to + // insert the assets_version setting into the database and cause a + // duplicate entry error. + if (!isset($this->config['assets_version'])) + { + $this->config['assets_version'] = 0; + } + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->user->session_begin(); + $this->user->setup(array('common', 'acp/common', 'cli')); + + $install_extensions = $this->iohandler->get_input('install-extensions', array()); + + $all_available_extensions = $this->extension_manager->all_available(); + $i = $this->install_config->get('install_extensions_index', 0); + $available_extensions = array_slice($all_available_extensions, $i); + + // Install extensions + foreach ($available_extensions as $ext_name => $ext_path) + { + if (!empty($install_extensions) && $install_extensions !== ['all'] && !in_array($ext_name, $install_extensions)) + { + continue; + } + + try + { + $extension = $this->extension_manager->get_extension($ext_name); + + if (!$extension->is_enableable()) + { + $this->iohandler->add_log_message(array('CLI_EXTENSION_NOT_ENABLEABLE', $ext_name)); + continue; + } + + $this->extension_manager->enable($ext_name); + $extensions = $this->get_extensions(); + + if (isset($extensions[$ext_name]) && $extensions[$ext_name]['ext_active']) + { + // Create log + $this->log->add('admin', ANONYMOUS, '', 'LOG_EXT_ENABLE', time(), array($ext_name)); + $this->iohandler->add_success_message(array('CLI_EXTENSION_ENABLE_SUCCESS', $ext_name)); + } + else + { + $this->iohandler->add_log_message(array('CLI_EXTENSION_ENABLE_FAILURE', $ext_name)); + } + } + catch (\Exception $e) + { + // Add fail log and continue + $this->iohandler->add_log_message(array('CLI_EXTENSION_ENABLE_FAILURE', $ext_name)); + } + + $i++; + + // Stop execution if resource limit is reached + if ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0) + { + break; + } + } + + $this->install_config->set('install_extensions_index', $i); + + if ($i < count($all_available_extensions)) + { + throw new resource_limit_reached_exception(); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_INSTALL_EXTENSIONS'; + } + + /** + * Get extensions from database + * + * @return array List of extensions + */ + private function get_extensions() + { + $sql = 'SELECT * + FROM ' . $this->extension_table; + + $result = $this->db->sql_query($sql); + $extensions_row = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + $extensions = array(); + + foreach ($extensions_row as $extension) + { + $extensions[$extension['ext_name']] = $extension; + } + + ksort($extensions); + + return $extensions; + } +} diff --git a/phpbb/install/module/install_finish/task/notify_user.php b/phpbb/install/module/install_finish/task/notify_user.php new file mode 100644 index 0000000..292be57 --- /dev/null +++ b/phpbb/install/module/install_finish/task/notify_user.php @@ -0,0 +1,174 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_finish\task; + +use phpbb\config\db; + +/** + * Logs installation and sends an email to the admin + */ +class notify_user extends \phpbb\install\task_base +{ + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var \phpbb\auth\auth + */ + protected $auth; + + /** + * @var \phpbb\config\db + */ + protected $config; + + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * @var \phpbb\log\log_interface + */ + protected $log; + + /** + * @var \phpbb\user + */ + protected $user; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\install\helper\container_factory $container + * @param \phpbb\install\helper\config $install_config + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(\phpbb\install\helper\container_factory $container, \phpbb\install\helper\config $install_config, \phpbb\install\helper\iohandler\iohandler_interface $iohandler, $phpbb_root_path, $php_ext) + { + $this->install_config = $install_config; + $this->iohandler = $iohandler; + + $this->auth = $container->get('auth'); + $this->language = $container->get('language'); + $this->log = $container->get('log'); + $this->user = $container->get('user'); + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + // We need to reload config for cases when it doesn't have all values + /** @var \phpbb\cache\driver\driver_interface $cache */ + $cache = $container->get('cache.driver'); + $cache->destroy('config'); + + $this->config = new db( + $container->get('dbal.conn'), + $cache, + $container->get_parameter('tables.config') + ); + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->user->session_begin(); + $this->user->setup('common'); + + if ($this->config['email_enable']) + { + include ($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + + // functions_messenger.php uses config to determine language paths + // Remove when able + global $config; + $config = $this->config; + + $messenger = new \messenger(false); + $messenger->template('installed', $this->install_config->get('user_language', 'en')); + $messenger->to($this->config['board_email'], $this->install_config->get('admin_name')); + $messenger->anti_abuse_headers($this->config, $this->user); + $messenger->assign_vars(array( + 'USERNAME' => htmlspecialchars_decode($this->install_config->get('admin_name')), + 'PASSWORD' => htmlspecialchars_decode($this->install_config->get('admin_passwd'))) + ); + $messenger->send(NOTIFY_EMAIL); + } + + // Login admin + // Ugly but works + $this->auth->login( + $this->install_config->get('admin_name'), + $this->install_config->get('admin_passwd'), + false, + true, + true + ); + + $this->iohandler->set_cookie($this->config['cookie_name'] . '_sid', $this->user->session_id); + $this->iohandler->set_cookie($this->config['cookie_name'] . '_u', $this->user->cookie_data['u']); + $this->iohandler->set_cookie($this->config['cookie_name'] . '_k', $this->user->cookie_data['k']); + + // Create log + $this->log->add( + 'admin', + $this->user->data['user_id'], + $this->user->ip, + 'LOG_INSTALL_INSTALLED', + false, + array($this->config['version']) + ); + + // Remove install_lock + @unlink($this->phpbb_root_path . 'cache/install_lock'); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_NOTIFY_USER'; + } +} diff --git a/phpbb/install/module/install_finish/task/populate_migrations.php b/phpbb/install/module/install_finish/task/populate_migrations.php new file mode 100644 index 0000000..cebf0f4 --- /dev/null +++ b/phpbb/install/module/install_finish/task/populate_migrations.php @@ -0,0 +1,93 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\install_finish\task; + +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; + +/** + * Populates migrations + */ +class populate_migrations extends \phpbb\install\task_base +{ + /** + * @var config + */ + protected $config; + + /** + * @var \phpbb\extension\manager + */ + protected $extension_manager; + + /** + * @var \phpbb\db\migrator + */ + protected $migrator; + + /** + * Constructor + * + * @param config $config Installer's config + * @param container_factory $container phpBB's DI contianer + */ + public function __construct(config $config, container_factory $container) + { + $this->config = $config; + $this->extension_manager = $container->get('ext.manager'); + $this->migrator = $container->get('migrator'); + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + if (!$this->config->get('populate_migration_refresh_before', false)) + { + if ($this->config->get_time_remaining() < 1) + { + $this->config->set('populate_migration_refresh_before', true); + throw new resource_limit_reached_exception(); + } + } + + $finder = $this->extension_manager->get_finder(); + + $migrations = $finder + ->core_path('phpbb/db/migration/data/') + ->set_extensions(array()) + ->get_classes(); + $this->migrator->populate_migrations($migrations); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_POPULATE_MIGRATIONS'; + } +} diff --git a/phpbb/install/module/obtain_data/install_module.php b/phpbb/install/module/obtain_data/install_module.php new file mode 100644 index 0000000..deb4be9 --- /dev/null +++ b/phpbb/install/module/obtain_data/install_module.php @@ -0,0 +1,33 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data; + +class install_module extends \phpbb\install\module_base +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('install', 0, 'obtain_data'); + } + + /** + * {@inheritdoc} + */ + public function get_step_count() + { + return 0; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_admin_data.php b/phpbb/install/module/obtain_data/task/obtain_admin_data.php new file mode 100644 index 0000000..d1f1af6 --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_admin_data.php @@ -0,0 +1,218 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * This class requests and validates admin account data from the user + */ +class obtain_admin_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $io_handler; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $install_config Installer's config helper + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + */ + public function __construct(\phpbb\install\helper\config $install_config, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler) + { + $this->install_config = $install_config; + $this->io_handler = $iohandler; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Check if data is sent + if ($this->io_handler->get_input('submit_admin', false)) + { + $this->process_form(); + } + else + { + $this->request_form_data(); + } + } + + /** + * Process form data + */ + protected function process_form() + { + // Admin data + $admin_name = $this->io_handler->get_input('admin_name', '', true); + $admin_pass1 = $this->io_handler->get_input('admin_pass1', '', true); + $admin_pass2 = $this->io_handler->get_input('admin_pass2', '', true); + $board_email = $this->io_handler->get_input('board_email', '', true); + + $admin_data_valid = $this->check_admin_data($admin_name, $admin_pass1, $admin_pass2, $board_email); + + if ($admin_data_valid) + { + $this->install_config->set('admin_name', $admin_name); + $this->install_config->set('admin_passwd', $admin_pass1); + $this->install_config->set('board_email', $board_email); + } + else + { + $this->request_form_data(true); + } + } + + /** + * Request data from the user + * + * @param bool $use_request_data Whether to use submited data + * + * @throws \phpbb\install\exception\user_interaction_required_exception When the user is required to provide data + */ + protected function request_form_data($use_request_data = false) + { + if ($use_request_data) + { + $admin_username = $this->io_handler->get_input('admin_name', '', true); + $admin_email = $this->io_handler->get_input('board_email', '', true); + } + else + { + $admin_username = ''; + $admin_email = ''; + } + + $admin_form = array( + 'admin_name' => array( + 'label' => 'ADMIN_USERNAME', + 'description' => 'ADMIN_USERNAME_EXPLAIN', + 'type' => 'text', + 'default' => $admin_username, + ), + 'board_email' => array( + 'label' => 'CONTACT_EMAIL', + 'type' => 'email', + 'default' => $admin_email, + ), + 'admin_pass1' => array( + 'label' => 'ADMIN_PASSWORD', + 'description' => 'ADMIN_PASSWORD_EXPLAIN', + 'type' => 'password', + ), + 'admin_pass2' => array( + 'label' => 'ADMIN_PASSWORD_CONFIRM', + 'type' => 'password', + ), + 'submit_admin' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + ); + + $this->io_handler->add_user_form_group('ADMIN_CONFIG', $admin_form); + + // Require user interaction + throw new user_interaction_required_exception(); + } + + /** + * Check admin data + * + * @param string $username Admin username + * @param string $pass1 Admin password + * @param string $pass2 Admin password confirmation + * @param string $email Admin e-mail address + * + * @return bool True if data is valid, false otherwise + */ + protected function check_admin_data($username, $pass1, $pass2, $email) + { + $data_valid = true; + + // Check if none of admin data is empty + if (in_array('', array($username, $pass1, $pass2, $email), true)) + { + $this->io_handler->add_error_message('INST_ERR_MISSING_DATA'); + $data_valid = false; + } + + if (utf8_strlen($username) < 3) + { + $this->io_handler->add_error_message('INST_ERR_USER_TOO_SHORT'); + $data_valid = false; + } + + if (utf8_strlen($username) > 20) + { + $this->io_handler->add_error_message('INST_ERR_USER_TOO_LONG'); + $data_valid = false; + } + + if ($pass1 !== $pass2 && $pass1 !== '') + { + $this->io_handler->add_error_message('INST_ERR_PASSWORD_MISMATCH'); + $data_valid = false; + } + + // Test against the default password rules + if (utf8_strlen($pass1) < 6) + { + $this->io_handler->add_error_message('INST_ERR_PASSWORD_TOO_SHORT'); + $data_valid = false; + } + + if (utf8_strlen($pass1) > 30) + { + $this->io_handler->add_error_message('INST_ERR_PASSWORD_TOO_LONG'); + $data_valid = false; + } + + if (!preg_match('/^' . get_preg_expression('email') . '$/i', $email)) + { + $this->io_handler->add_error_message('INST_ERR_EMAIL_INVALID'); + $data_valid = false; + } + + return $data_valid; + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_board_data.php b/phpbb/install/module/obtain_data/task/obtain_board_data.php new file mode 100644 index 0000000..ff2a0a2 --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_board_data.php @@ -0,0 +1,185 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * This class obtains default data from the user related to board (Board name, Board descritpion, etc...) + */ +class obtain_board_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $io_handler; + + /** + * @var \phpbb\language\language_file_helper + */ + protected $language_helper; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $config Installer's config + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + * @param \phpbb\language\language_file_helper $lang_helper Language file helper + */ + public function __construct(\phpbb\install\helper\config $config, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler, + \phpbb\language\language_file_helper $lang_helper) + { + $this->install_config = $config; + $this->io_handler = $iohandler; + $this->language_helper = $lang_helper; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Check if data is sent + if ($this->io_handler->get_input('submit_board', false)) + { + $this->process_form(); + } + else + { + $this->request_form_data(); + } + } + + /** + * Process form data + */ + protected function process_form() + { + // Board data + $default_lang = $this->io_handler->get_input('default_lang', ''); + $board_name = $this->io_handler->get_input('board_name', '', true); + $board_desc = $this->io_handler->get_input('board_description', '', true); + + // Check default lang + $langs = $this->language_helper->get_available_languages(); + $lang_valid = false; + + foreach ($langs as $lang) + { + if ($lang['iso'] === $default_lang) + { + $lang_valid = true; + break; + } + } + + $this->install_config->set('board_name', $board_name); + $this->install_config->set('board_description', $board_desc); + + if ($lang_valid) + { + $this->install_config->set('default_lang', $default_lang); + } + else + { + $this->request_form_data(true); + } + } + + /** + * Request data from the user + * + * @param bool $use_request_data Whether to use submited data + * + * @throws \phpbb\install\exception\user_interaction_required_exception When the user is required to provide data + */ + protected function request_form_data($use_request_data = false) + { + if ($use_request_data) + { + $board_name = $this->io_handler->get_input('board_name', '', true); + $board_desc = $this->io_handler->get_input('board_description', '', true); + } + else + { + $board_name = '{L_CONFIG_SITENAME}'; + $board_desc = '{L_CONFIG_SITE_DESC}'; + } + + // Use language because we only check this to be valid + $default_lang = $this->install_config->get('user_language', 'en'); + + $langs = $this->language_helper->get_available_languages(); + $lang_options = array(); + + foreach ($langs as $lang) + { + $lang_options[] = array( + 'value' => $lang['iso'], + 'label' => $lang['local_name'], + 'selected' => ($default_lang === $lang['iso']), + ); + } + + $board_form = array( + 'default_lang' => array( + 'label' => 'DEFAULT_LANGUAGE', + 'type' => 'select', + 'options' => $lang_options, + ), + 'board_name' => array( + 'label' => 'BOARD_NAME', + 'type' => 'text', + 'default' => $board_name, + ), + 'board_description' => array( + 'label' => 'BOARD_DESCRIPTION', + 'type' => 'text', + 'default' => $board_desc, + ), + 'submit_board' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + ); + + $this->io_handler->add_user_form_group('BOARD_CONFIG', $board_form); + + throw new user_interaction_required_exception(); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_database_data.php b/phpbb/install/module/obtain_data/task/obtain_database_data.php new file mode 100644 index 0000000..6ec1e61 --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_database_data.php @@ -0,0 +1,270 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * This class requests and validates database information from the user + */ +class obtain_database_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ + /** + * @var \phpbb\install\helper\database + */ + protected $database_helper; + + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $io_handler; + + /** + * Constructor + * + * @param \phpbb\install\helper\database $database_helper Installer's database helper + * @param \phpbb\install\helper\config $install_config Installer's config helper + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + */ + public function __construct(\phpbb\install\helper\database $database_helper, + \phpbb\install\helper\config $install_config, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler) + { + $this->database_helper = $database_helper; + $this->install_config = $install_config; + $this->io_handler = $iohandler; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Check if data is sent + if ($this->io_handler->get_input('submit_database', false)) + { + $this->process_form(); + } + else + { + $this->request_form_data(); + } + } + + /** + * Process form data + */ + protected function process_form() + { + // Collect database data + $dbms = $this->io_handler->get_input('dbms', ''); + $dbhost = $this->io_handler->get_input('dbhost', '', true); + $dbport = $this->io_handler->get_input('dbport', ''); + $dbuser = $this->io_handler->get_input('dbuser', '', true); + $dbpasswd = $this->io_handler->get_raw_input('dbpasswd', '', true); + $dbname = $this->io_handler->get_input('dbname', '', true); + $table_prefix = $this->io_handler->get_input('table_prefix', '', true); + + // Check database data + $user_data_vaild = $this->check_database_data($dbms, $dbhost, $dbport, $dbuser, $dbpasswd, $dbname, $table_prefix); + + // Save database data if it is correct + if ($user_data_vaild) + { + $this->install_config->set('dbms', $dbms); + $this->install_config->set('dbhost', $dbhost); + $this->install_config->set('dbport', $dbport); + $this->install_config->set('dbuser', $dbuser); + $this->install_config->set('dbpasswd', $dbpasswd); + $this->install_config->set('dbname', $dbname); + $this->install_config->set('table_prefix', $table_prefix); + } + else + { + $this->request_form_data(true); + } + } + + /** + * Request data from the user + * + * @param bool $use_request_data Whether to use submited data + * + * @throws \phpbb\install\exception\user_interaction_required_exception When the user is required to provide data + */ + protected function request_form_data($use_request_data = false) + { + if ($use_request_data) + { + $dbms = $this->io_handler->get_input('dbms', ''); + $dbhost = $this->io_handler->get_input('dbhost', '', true); + $dbport = $this->io_handler->get_input('dbport', ''); + $dbuser = $this->io_handler->get_input('dbuser', ''); + $dbname = $this->io_handler->get_input('dbname', ''); + $table_prefix = $this->io_handler->get_input('table_prefix', 'phpbb_'); + } + else + { + $dbms = ''; + $dbhost = ''; + $dbport = ''; + $dbuser = ''; + $dbname = ''; + $table_prefix = 'phpbb_'; + } + + $dbms_select = array(); + foreach ($this->database_helper->get_available_dbms() as $dbms_key => $dbms_array) + { + $dbms_select[] = array( + 'value' => $dbms_key, + 'label' => 'DB_OPTION_' . strtoupper($dbms_key), + 'selected' => ($dbms_key === $dbms), + ); + } + + $database_form = array( + 'dbms' => array( + 'label' => 'DBMS', + 'type' => 'select', + 'options' => $dbms_select, + ), + 'dbhost' => array( + 'label' => 'DB_HOST', + 'description' => 'DB_HOST_EXPLAIN', + 'type' => 'text', + 'default' => $dbhost, + ), + 'dbport' => array( + 'label' => 'DB_PORT', + 'description' => 'DB_PORT_EXPLAIN', + 'type' => 'text', + 'default' => $dbport, + ), + 'dbuser' => array( + 'label' => 'DB_USERNAME', + 'type' => 'text', + 'default' => $dbuser, + ), + 'dbpasswd' => array( + 'label' => 'DB_PASSWORD', + 'type' => 'password', + ), + 'dbname' => array( + 'label' => 'DB_NAME', + 'type' => 'text', + 'default' => $dbname, + ), + 'table_prefix' => array( + 'label' => 'TABLE_PREFIX', + 'description' => 'TABLE_PREFIX_EXPLAIN', + 'type' => 'text', + 'default' => $table_prefix, + ), + 'submit_database' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + ); + + $this->io_handler->add_user_form_group('DB_CONFIG', $database_form); + + // Require user interaction + throw new user_interaction_required_exception(); + } + + /** + * Check database data + * + * @param string $dbms Selected database type + * @param string $dbhost Database host address + * @param int $dbport Database port number + * @param string $dbuser Database username + * @param string $dbpass Database password + * @param string $dbname Database name + * @param string $table_prefix Database table prefix + * + * @return bool True if database data is correct, false otherwise + */ + protected function check_database_data($dbms, $dbhost, $dbport, $dbuser, $dbpass, $dbname, $table_prefix) + { + $available_dbms = $this->database_helper->get_available_dbms(); + $data_valid = true; + + // Check if PHP has the database extensions for the specified DBMS + if (!isset($available_dbms[$dbms])) + { + $this->io_handler->add_error_message('INST_ERR_NO_DB'); + $data_valid = false; + } + + // Validate table prefix + $prefix_valid = $this->database_helper->validate_table_prefix($dbms, $table_prefix); + if (is_array($prefix_valid)) + { + foreach ($prefix_valid as $error) + { + $this->io_handler->add_error_message( + $error['title'], + (isset($error['description'])) ? $error['description'] : false + ); + } + + $data_valid = false; + } + + // Try to connect to database if all provided data is valid + if ($data_valid) + { + $connect_test = $this->database_helper->check_database_connection($dbms, $dbhost, $dbport, $dbuser, $dbpass, $dbname, $table_prefix); + if (is_array($connect_test)) + { + foreach ($connect_test as $error) + { + $this->io_handler->add_error_message( + $error['title'], + (isset($error['description'])) ? $error['description'] : false + ); + } + + $data_valid = false; + } + } + + return $data_valid; + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_email_data.php b/phpbb/install/module/obtain_data/task/obtain_email_data.php new file mode 100644 index 0000000..7cd0d7b --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_email_data.php @@ -0,0 +1,173 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +class obtain_email_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $io_handler; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $config Installer's config + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + */ + public function __construct(\phpbb\install\helper\config $config, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler) + { + $this->install_config = $config; + $this->io_handler = $iohandler; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // E-mail data + $email_enable = $this->io_handler->get_input('email_enable', true); + $smtp_delivery = $this->io_handler->get_input('smtp_delivery', ''); + $smtp_host = $this->io_handler->get_input('smtp_host', '', true); + $smtp_port = $this->io_handler->get_input('smtp_port', ''); + $smtp_auth = $this->io_handler->get_input('smtp_auth', ''); + $smtp_user = $this->io_handler->get_input('smtp_user', '', true); + $smtp_passwd = $this->io_handler->get_input('smtp_pass', '', true); + + $auth_methods = array('PLAIN', 'LOGIN', 'CRAM-MD5', 'DIGEST-MD5', 'POP-BEFORE-SMTP'); + + // Check if data is sent + if ($this->io_handler->get_input('submit_email', false)) + { + $this->install_config->set('email_enable', $email_enable); + $this->install_config->set('smtp_delivery', $smtp_delivery); + $this->install_config->set('smtp_host', $smtp_host); + $this->install_config->set('smtp_port', $smtp_port); + $this->install_config->set('smtp_auth', $smtp_auth); + $this->install_config->set('smtp_user', $smtp_user); + $this->install_config->set('smtp_pass', $smtp_passwd); + } + else + { + $auth_options = array(); + foreach ($auth_methods as $method) + { + $auth_options[] = array( + 'value' => $method, + 'label' => 'SMTP_' . str_replace('-', '_', $method), + 'selected' => false, + ); + } + + $email_form = array( + 'email_enable' => array( + 'label' => 'ENABLE_EMAIL', + 'description' => 'ENABLE_EMAIL_EXPLAIN', + 'type' => 'radio', + 'options' => array( + array( + 'value' => 1, + 'label' => 'ENABLE', + 'selected' => true, + ), + array( + 'value' => 0, + 'label' => 'DISABLE', + 'selected' => false, + ), + ), + ), + 'smtp_delivery' => array( + 'label' => 'USE_SMTP', + 'description' => 'USE_SMTP_EXPLAIN', + 'type' => 'radio', + 'options' => array( + array( + 'value' => 0, + 'label' => 'NO', + 'selected' => true, + ), + array( + 'value' => 1, + 'label' => 'YES', + 'selected' => false, + ), + ), + ), + 'smtp_host' => array( + 'label' => 'SMTP_SERVER', + 'type' => 'text', + 'default' => $smtp_host, + ), + 'smtp_port' => array( + 'label' => 'SMTP_PORT', + 'type' => 'text', + 'default' => $smtp_port, + ), + 'smtp_auth' => array( + 'label' => 'SMTP_AUTH_METHOD', + 'description' => 'SMTP_AUTH_METHOD_EXPLAIN', + 'type' => 'select', + 'options' => $auth_options, + ), + 'smtp_user' => array( + 'label' => 'SMTP_USERNAME', + 'description' => 'SMTP_USERNAME_EXPLAIN', + 'type' => 'text', + 'default' => $smtp_user, + ), + 'smtp_pass' => array( + 'label' => 'SMTP_PASSWORD', + 'description' => 'SMTP_PASSWORD_EXPLAIN', + 'type' => 'password', + ), + 'submit_email' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + ); + + $this->io_handler->add_user_form_group('EMAIL_CONFIG', $email_form); + + throw new user_interaction_required_exception(); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_file_updater_method.php b/phpbb/install/module/obtain_data/task/obtain_file_updater_method.php new file mode 100644 index 0000000..d5a8855 --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_file_updater_method.php @@ -0,0 +1,167 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class obtain_file_updater_method extends task_base +{ + /** + * @var array Supported compression methods + * + * Note: .tar is assumed to be supported, but not in the list + */ + protected $available_methods; + + /** + * @var \phpbb\install\helper\config + */ + protected $installer_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * Constructor + * + * @param config $installer_config + * @param iohandler_interface $iohandler + */ + public function __construct(config $installer_config, iohandler_interface $iohandler) + { + $this->installer_config = $installer_config; + $this->iohandler = $iohandler; + + $this->available_methods = array('.tar.gz' => 'zlib', '.tar.bz2' => 'bz2', '.zip' => 'zlib'); + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + return $this->installer_config->get('do_update_files', false); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Check if data is sent + if ($this->iohandler->get_input('submit_update_file', false)) + { + $supported_methods = array('compression', 'ftp', 'direct_file'); + $method = $this->iohandler->get_input('method', 'compression'); + $update_method = (in_array($method, $supported_methods, true)) ? $method : 'compression'; + $this->installer_config->set('file_update_method', $update_method); + + $compression = $this->iohandler->get_input('compression_method', '.zip'); + $supported_methods = array_keys($this->available_methods); + $supported_methods[] = '.tar'; + $compression = (in_array($compression, $supported_methods, true)) ? $compression : '.zip'; + $this->installer_config->set('file_update_compression', $compression); + } + else + { + $this->iohandler->add_user_form_group('UPDATE_FILE_METHOD_TITLE', array( + 'method' => array( + 'label' => 'UPDATE_FILE_METHOD', + 'type' => 'select', + 'options' => array( + array( + 'value' => 'compression', + 'label' => 'UPDATE_FILE_METHOD_DOWNLOAD', + 'selected' => true, + ), + array( + 'value' => 'ftp', + 'label' => 'UPDATE_FILE_METHOD_FTP', + 'selected' => false, + ), + array( + 'value' => 'direct_file', + 'label' => 'UPDATE_FILE_METHOD_FILESYSTEM', + 'selected' => false, + ), + ), + ), + 'compression_method' => array( + 'label' => 'SELECT_DOWNLOAD_FORMAT', + 'type' => 'select', + 'options' => $this->get_available_compression_methods(), + ), + 'submit_update_file' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + )); + + throw new user_interaction_required_exception(); + } + } + + /** + * Returns form elements in an array of available compression methods + * + * @return array + */ + protected function get_available_compression_methods() + { + $methods[] = array( + 'value' => '.tar', + 'label' => '.tar', + 'selected' => true, + ); + + foreach ($this->available_methods as $type => $module) + { + if (!@extension_loaded($module)) + { + continue; + } + + $methods[] = array( + 'value' => $type, + 'label' => $type, + 'selected' => false, + ); + } + + return $methods; + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_server_data.php b/phpbb/install/module/obtain_data/task/obtain_server_data.php new file mode 100644 index 0000000..5096ce2 --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_server_data.php @@ -0,0 +1,202 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; + +/** + * This class requests and saves some information about the server + */ +class obtain_server_data extends \phpbb\install\task_base implements \phpbb\install\task_interface +{ + /** + * @var \phpbb\install\helper\config + */ + protected $install_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $io_handler; + + /** + * Constructor + * + * @param \phpbb\install\helper\config $config Installer's config + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler Installer's input-output handler + */ + public function __construct(\phpbb\install\helper\config $config, + \phpbb\install\helper\iohandler\iohandler_interface $iohandler) + { + $this->install_config = $config; + $this->io_handler = $iohandler; + + parent::__construct(true); + } + /** + * {@inheritdoc} + */ + public function run() + { + $cookie_secure = $this->io_handler->is_secure(); + $server_protocol = ($this->io_handler->is_secure()) ? 'https://' : 'http://'; + $server_port = $this->io_handler->get_server_variable('SERVER_PORT', 0); + + // HTTP_HOST is having the correct browser url in most cases... + $server_name = strtolower(htmlspecialchars_decode($this->io_handler->get_header_variable( + 'Host', + $this->io_handler->get_server_variable('SERVER_NAME') + ))); + + // HTTP HOST can carry a port number... + if (strpos($server_name, ':') !== false) + { + $server_name = substr($server_name, 0, strpos($server_name, ':')); + } + + $script_path = htmlspecialchars_decode($this->io_handler->get_server_variable('PHP_SELF')); + + if (!$script_path) + { + $script_path = htmlspecialchars_decode($this->io_handler->get_server_variable('REQUEST_URI')); + } + + $script_path = str_replace(array('\\', '//'), '/', $script_path); + $script_path = trim(dirname(dirname(dirname($script_path)))); // Because we are in install/app.php/route_name + + // Server data + $cookie_secure = $this->io_handler->get_input('cookie_secure', $cookie_secure); + $server_protocol = $this->io_handler->get_input('server_protocol', $server_protocol); + $force_server_vars = $this->io_handler->get_input('force_server_vars', 0); + $server_name = $this->io_handler->get_input('server_name', $server_name, true); + $server_port = $this->io_handler->get_input('server_port', $server_port); + $script_path = $this->io_handler->get_input('script_path', $script_path, true); + + // Clean up script path + if ($script_path !== '/') + { + // Adjust destination path (no trailing slash) + if (substr($script_path, -1) === '/') + { + $script_path = substr($script_path, 0, -1); + } + + $script_path = str_replace(array('../', './'), '', $script_path); + + if ($script_path[0] !== '/') + { + $script_path = '/' . $script_path; + } + } + + // Check if data is sent + if ($this->io_handler->get_input('submit_server', false)) + { + $this->install_config->set('cookie_secure', $cookie_secure); + $this->install_config->set('server_protocol', $server_protocol); + $this->install_config->set('force_server_vars', $force_server_vars); + $this->install_config->set('server_name', $server_name); + $this->install_config->set('server_port', $server_port); + $this->install_config->set('script_path', $script_path); + } + else + { + // Render form + $server_form = array( + 'cookie_secure' => array( + 'label' => 'COOKIE_SECURE', + 'description' => 'COOKIE_SECURE_EXPLAIN', + 'type' => 'radio', + 'options' => array( + array( + 'value' => 0, + 'label' => 'NO', + 'selected' => (!$cookie_secure), + ), + array( + 'value' => 1, + 'label' => 'YES', + 'selected' => ($cookie_secure), + ), + ), + ), + 'force_server_vars' => array( + 'label' => 'FORCE_SERVER_VARS', + 'description' => 'FORCE_SERVER_VARS_EXPLAIN', + 'type' => 'radio', + 'options' => array( + array( + 'value' => 0, + 'label' => 'NO', + 'selected' => true, + ), + array( + 'value' => 1, + 'label' => 'YES', + 'selected' => false, + ), + ), + ), + 'server_protocol' => array( + 'label' => 'SERVER_PROTOCOL', + 'description' => 'SERVER_PROTOCOL_EXPLAIN', + 'type' => 'text', + 'default' => $server_protocol, + ), + 'server_name' => array( + 'label' => 'SERVER_NAME', + 'description' => 'SERVER_NAME_EXPLAIN', + 'type' => 'text', + 'default' => $server_name, + ), + 'server_port' => array( + 'label' => 'SERVER_PORT', + 'description' => 'SERVER_PORT_EXPLAIN', + 'type' => 'text', + 'default' => $server_port, + ), + 'script_path' => array( + 'label' => 'SCRIPT_PATH', + 'description' => 'SCRIPT_PATH_EXPLAIN', + 'type' => 'text', + 'default' => $script_path, + ), + 'submit_server' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ) + ); + + $this->io_handler->add_user_form_group('SERVER_CONFIG', $server_form); + + throw new user_interaction_required_exception(); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_update_files.php b/phpbb/install/module/obtain_data/task/obtain_update_files.php new file mode 100644 index 0000000..0cb8091 --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_update_files.php @@ -0,0 +1,113 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class obtain_update_files extends task_base +{ + /** + * @var config + */ + protected $installer_config; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param config $config + * @param iohandler_interface $iohandler + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(config $config, iohandler_interface $iohandler, $phpbb_root_path, $php_ext) + { + $this->installer_config = $config; + $this->iohandler = $iohandler; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + return $this->installer_config->get('do_update_files', false); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Load update info file + // The file should be checked in the requirements, so we assume that it exists + $update_info_file = $this->phpbb_root_path . 'install/update/index.' . $this->php_ext; + include($update_info_file); + $info = (empty($update_info) || !is_array($update_info)) ? false : $update_info; + + // If the file is invalid, abort mission + if (!$info) + { + $this->iohandler->add_error_message('WRONG_INFO_FILE_FORMAT'); + throw new user_interaction_required_exception(); + } + + // Replace .php with $this->php_ext if needed + if ($this->php_ext !== 'php') + { + $custom_extension = '.' . $this->php_ext; + $info['files'] = preg_replace('#\.php$#i', $custom_extension, $info['files']); + } + + // Save update info + $this->installer_config->set('update_info_unprocessed', $info); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_update_ftp_data.php b/phpbb/install/module/obtain_data/task/obtain_update_ftp_data.php new file mode 100644 index 0000000..3c17576 --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_update_ftp_data.php @@ -0,0 +1,163 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +class obtain_update_ftp_data extends task_base +{ + /** + * @var \phpbb\install\helper\config + */ + protected $installer_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var update_helper + */ + protected $update_helper; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param config $installer_config + * @param iohandler_interface $iohandler + * @param update_helper $update_helper + * @param string $php_ext + */ + public function __construct(config $installer_config, iohandler_interface $iohandler, update_helper $update_helper, $php_ext) + { + $this->installer_config = $installer_config; + $this->iohandler = $iohandler; + $this->update_helper = $update_helper; + $this->php_ext = $php_ext; + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + return ($this->installer_config->get('do_update_files', false) && + ($this->installer_config->get('file_update_method', '') === 'ftp') + ); + } + + /** + * {@inheritdoc} + */ + public function run() + { + if ($this->iohandler->get_input('submit_ftp', false)) + { + $this->update_helper->include_file('includes/functions_transfer.' . $this->php_ext); + + $method = 'ftp'; + $methods = \transfer::methods(); + if (!in_array($method, $methods, true)) + { + $method = $methods[0]; + } + + $ftp_host = $this->iohandler->get_input('ftp_host', '', true); + $ftp_user = $this->iohandler->get_input('ftp_user', '', true); + $ftp_pass = htmlspecialchars_decode($this->iohandler->get_input('ftp_pass', '', true)); + $ftp_path = $this->iohandler->get_input('ftp_path', '', true); + $ftp_port = $this->iohandler->get_input('ftp_port', 21); + $ftp_time = $this->iohandler->get_input('ftp_timeout', 10); + + $this->installer_config->set('ftp_host', $ftp_host); + $this->installer_config->set('ftp_user', $ftp_user); + $this->installer_config->set('ftp_pass', $ftp_pass); + $this->installer_config->set('ftp_path', $ftp_path); + $this->installer_config->set('ftp_port', (int) $ftp_port); + $this->installer_config->set('ftp_timeout', (int) $ftp_time); + $this->installer_config->set('ftp_method', $method); + } + else + { + $this->iohandler->add_user_form_group('FTP_SETTINGS', array( + 'ftp_host' => array( + 'label' => 'FTP_HOST', + 'description' => 'FTP_HOST_EXPLAIN', + 'type' => 'text', + ), + 'ftp_user' => array( + 'label' => 'FTP_USERNAME', + 'description' => 'FTP_USERNAME_EXPLAIN', + 'type' => 'text', + ), + 'ftp_pass' => array( + 'label' => 'FTP_PASSWORD', + 'description' => 'FTP_PASSWORD_EXPLAIN', + 'type' => 'password', + ), + 'ftp_path' => array( + 'label' => 'FTP_ROOT_PATH', + 'description' => 'FTP_ROOT_PATH_EXPLAIN', + 'type' => 'text', + ), + 'ftp_port' => array( + 'label' => 'FTP_PORT', + 'description' => 'FTP_PORT_EXPLAIN', + 'type' => 'text', + 'default' => 21, + ), + 'ftp_timeout' => array( + 'label' => 'FTP_TIMEOUT', + 'description' => 'FTP_TIMEOUT_EXPLAIN', + 'type' => 'text', + 'default' => 10, + ), + 'submit_ftp' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + )); + + throw new user_interaction_required_exception(); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/task/obtain_update_settings.php b/phpbb/install/module/obtain_data/task/obtain_update_settings.php new file mode 100644 index 0000000..3b24e8b --- /dev/null +++ b/phpbb/install/module/obtain_data/task/obtain_update_settings.php @@ -0,0 +1,123 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data\task; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class obtain_update_settings extends task_base +{ + /** + * @var \phpbb\install\helper\config + */ + protected $installer_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * Constructor + * + * @param config $installer_config + * @param iohandler_interface $iohandler + */ + public function __construct(config $installer_config, iohandler_interface $iohandler) + { + $this->installer_config = $installer_config; + $this->iohandler = $iohandler; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Check if data is sent + if ($this->iohandler->get_input('submit_update', false)) + { + $update_files = $this->iohandler->get_input('update_type', 'all') === 'all'; + + if ($this->installer_config->get('disable_filesystem_update', false) && $update_files) + { + $this->iohandler->add_error_message('UPDATE_FILES_NOT_FOUND'); + + throw new user_interaction_required_exception(); + } + + $this->installer_config->set('do_update_files', $update_files); + } + else + { + if ($this->installer_config->get('disable_filesystem_update', false)) + { + $options[] = array( + 'value' => 'db_only', + 'label' => 'UPDATE_TYPE_DB_ONLY', + 'selected' => true, + ); + } + else + { + $options = array( + array( + 'value' => 'all', + 'label' => 'UPDATE_TYPE_ALL', + 'selected' => true, + ), + array( + 'value' => 'db_only', + 'label' => 'UPDATE_TYPE_DB_ONLY', + 'selected' => false, + ), + ); + } + + $this->iohandler->add_user_form_group('UPDATE_TYPE', array( + 'update_type' => array( + 'label' => 'UPDATE_TYPE', + 'type' => 'radio', + 'options' => $options, + ), + 'submit_update' => array( + 'label' => 'SUBMIT', + 'type' => 'submit', + ), + )); + + throw new user_interaction_required_exception(); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/obtain_data/update_module.php b/phpbb/install/module/obtain_data/update_module.php new file mode 100644 index 0000000..c2f9019 --- /dev/null +++ b/phpbb/install/module/obtain_data/update_module.php @@ -0,0 +1,33 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\obtain_data; + +class update_module extends \phpbb\install\module_base +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('update', 0, 'obtain_data'); + } + + /** + * {@inheritdoc} + */ + public function get_step_count() + { + return 0; + } +} diff --git a/phpbb/install/module/requirements/abstract_requirements_module.php b/phpbb/install/module/requirements/abstract_requirements_module.php new file mode 100644 index 0000000..121b4ff --- /dev/null +++ b/phpbb/install/module/requirements/abstract_requirements_module.php @@ -0,0 +1,71 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\requirements; + +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\module_base; + +/** + * Base class for requirements installer module + */ +abstract class abstract_requirements_module extends module_base +{ + public function run() + { + $tests_passed = true; + foreach ($this->task_collection as $name => $task) + { + // Check if we can run the task + if (!$task->is_essential() && !$task->check_requirements()) + { + continue; + } + + if ($this->allow_progress_bar) + { + $this->install_config->increment_current_task_progress(); + } + + $test_result = $task->run(); + $tests_passed = ($tests_passed) ? $test_result : false; + } + + // Module finished, so clear task progress + $this->install_config->set_finished_task(0); + + // Check if tests have failed + if (!$tests_passed) + { + // If requirements are not met, exit form installer + // Set up UI for retesting + $this->iohandler->add_user_form_group('', array( + 'install' => array( + 'label' => 'RETEST_REQUIREMENTS', + 'type' => 'submit', + ), + )); + + // Send the response and quit + throw new user_interaction_required_exception(); + } + } + + /** + * {@inheritdoc} + */ + public function get_step_count() + { + return 0; + } +} diff --git a/phpbb/install/module/requirements/install_module.php b/phpbb/install/module/requirements/install_module.php new file mode 100644 index 0000000..ed0c5fb --- /dev/null +++ b/phpbb/install/module/requirements/install_module.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\requirements; + +class install_module extends abstract_requirements_module +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('install', 0, 'requirements'); + } +} diff --git a/phpbb/install/module/requirements/task/check_filesystem.php b/phpbb/install/module/requirements/task/check_filesystem.php new file mode 100644 index 0000000..868af39 --- /dev/null +++ b/phpbb/install/module/requirements/task/check_filesystem.php @@ -0,0 +1,279 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\requirements\task; + +/** + * Checks filesystem requirements + */ +class check_filesystem extends \phpbb\install\task_base +{ + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * @var array + */ + protected $files_to_check; + + /** + * @var bool + */ + protected $tests_passed; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $response; + + /** + * Constructor + * + * @param \phpbb\filesystem\filesystem_interface $filesystem filesystem handler + * @param \phpbb\install\helper\iohandler\iohandler_interface $response response helper + * @param string $phpbb_root_path relative path to phpBB's root + * @param string $php_ext extension of php files + * @param bool $check_config_php Whether or not to check if config.php is writable + */ + public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, \phpbb\install\helper\iohandler\iohandler_interface $response, $phpbb_root_path, $php_ext, $check_config_php = true) + { + parent::__construct(true); + + $this->filesystem = $filesystem; + $this->response = $response; + $this->phpbb_root_path = $phpbb_root_path; + + $this->tests_passed = false; + + // Files/Directories to check + // All file/directory names must be relative to phpBB's root path + $this->files_to_check = array( + array( + 'path' => 'cache/', + 'failable' => false, + 'is_file' => false, + ), + array( + 'path' => 'store/', + 'failable' => false, + 'is_file' => false, + ), + array( + 'path' => 'files/', + 'failable' => false, + 'is_file' => false, + ), + array( + 'path' => 'images/avatars/upload/', + 'failable' => true, + 'is_file' => false, + ), + ); + + if ($check_config_php) + { + $this->files_to_check[] = array( + 'path' => "config.$php_ext", + 'failable' => false, + 'is_file' => true, + ); + } + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->tests_passed = true; + + // Check files/directories to be writable + foreach ($this->files_to_check as $file) + { + if ($file['is_file']) + { + $this->check_file($file['path'], $file['failable']); + } + else + { + $this->check_dir($file['path'], $file['failable']); + } + } + + return $this->tests_passed; + } + + /** + * Sets $this->tests_passed + * + * @param bool $is_passed + */ + protected function set_test_passed($is_passed) + { + // If one test failed, tests_passed should be false + $this->tests_passed = (!$this->tests_passed) ? false : $is_passed; + } + + /** + * Check if a file is readable and writable + * + * @param string $file Filename + * @param bool $failable Whether failing test should interrupt installation process + */ + protected function check_file($file, $failable = false) + { + $path = $this->phpbb_root_path . $file; + $exists = $writable = true; + + // Try to create file if it does not exists + if (!file_exists($path)) + { + $fp = @fopen($path, 'w'); + @fclose($fp); + try + { + $this->filesystem->phpbb_chmod($path, + \phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE + ); + $exists = true; + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + + if (file_exists($path)) + { + if (!$this->filesystem->is_writable($path)) + { + $writable = false; + } + } + else + { + $exists = $writable = false; + } + + $this->set_test_passed(($exists && $writable) || $failable); + + if (!($exists && $writable)) + { + $title = ($exists) ? 'FILE_NOT_WRITABLE' : 'FILE_NOT_EXISTS'; + $lang_suffix = '_EXPLAIN'; + $lang_suffix .= ($failable) ? '_OPTIONAL' : ''; + $description = array($title . $lang_suffix, $file); + + if ($failable) + { + $this->response->add_warning_message($title, $description); + } + else + { + $this->response->add_error_message($title, $description); + } + } + } + + /** + * Check if a directory is readable and writable + * + * @param string $dir Filename + * @param bool $failable Whether failing test should abort the installation process + */ + protected function check_dir($dir, $failable = false) + { + $path = $this->phpbb_root_path . $dir; + $exists = $writable = false; + + // Try to create the directory if it does not exist + if (!file_exists($path)) + { + try + { + $this->filesystem->mkdir($path, 0777); + $this->filesystem->phpbb_chmod($path, + \phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE + ); + $exists = true; + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + + // Now really check + if (file_exists($path) && is_dir($path)) + { + try + { + $exists = true; + $this->filesystem->phpbb_chmod($path, + \phpbb\filesystem\filesystem_interface::CHMOD_READ | \phpbb\filesystem\filesystem_interface::CHMOD_WRITE + ); + } + catch (\phpbb\filesystem\exception\filesystem_exception $e) + { + // Do nothing + } + } + + if ($this->filesystem->is_writable($path)) + { + $writable = true; + } + + $this->set_test_passed(($exists && $writable) || $failable); + + if (!($exists && $writable)) + { + $title = ($exists) ? 'DIRECTORY_NOT_WRITABLE' : 'DIRECTORY_NOT_EXISTS'; + $lang_suffix = '_EXPLAIN'; + $lang_suffix .= ($failable) ? '_OPTIONAL' : ''; + $description = array($title . $lang_suffix, $dir); + + if ($failable) + { + $this->response->add_warning_message($title, $description); + } + else + { + $this->response->add_error_message($title, $description); + } + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/requirements/task/check_server_environment.php b/phpbb/install/module/requirements/task/check_server_environment.php new file mode 100644 index 0000000..29f9777 --- /dev/null +++ b/phpbb/install/module/requirements/task/check_server_environment.php @@ -0,0 +1,209 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\requirements\task; + +/** + * Installer task that checks if the server meats phpBB requirements + */ +class check_server_environment extends \phpbb\install\task_base +{ + /** + * @var \phpbb\install\helper\database + */ + protected $database_helper; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $response_helper; + + /** + * @var bool + */ + protected $tests_passed; + + /** + * Constructor + * + * @param \phpbb\install\helper\database $database_helper + * @param \phpbb\install\helper\iohandler\iohandler_interface $response + */ + public function __construct(\phpbb\install\helper\database $database_helper, + \phpbb\install\helper\iohandler\iohandler_interface $response) + { + $this->database_helper = $database_helper; + $this->response_helper = $response; + $this->tests_passed = true; + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // + // Check requirements + // The error messages should be set in the check_ functions + // + + // Check PHP version + $this->check_php_version(); + + // Check for getimagesize() + $this->check_image_size(); + + // Check for PCRE support + $this->check_pcre(); + + // Check for JSON support + $this->check_json(); + + // XML extension support check + $this->check_xml(); + + // Check for dbms support + $this->check_available_dbms(); + + return $this->tests_passed; + } + + /** + * Sets $this->tests_passed + * + * @param bool $is_passed + */ + protected function set_test_passed($is_passed) + { + // If one test failed, tests_passed should be false + $this->tests_passed = (!$this->tests_passed) ? false : $is_passed; + } + + /** + * Check if the requirements for PHP version is met + */ + protected function check_php_version() + { + $php_version = PHP_VERSION; + + if (version_compare($php_version, '5.4') < 0) + { + $this->response_helper->add_error_message('PHP_VERSION_REQD', 'PHP_VERSION_REQD_EXPLAIN'); + + $this->set_test_passed(false); + return; + } + + $this->set_test_passed(true); + } + + /** + * Checks if the installed PHP has getimagesize() available + */ + protected function check_image_size() + { + if (!@function_exists('getimagesize')) + { + $this->response_helper->add_error_message('PHP_GETIMAGESIZE_SUPPORT', 'PHP_GETIMAGESIZE_SUPPORT_EXPLAIN'); + + $this->set_test_passed(false); + return; + } + + $this->set_test_passed(true); + } + + /** + * Checks if the installed PHP supports PCRE + */ + protected function check_pcre() + { + if (@preg_match('//u', '')) + { + $this->set_test_passed(true); + return; + } + + $this->response_helper->add_error_message('PCRE_UTF_SUPPORT', 'PCRE_UTF_SUPPORT_EXPLAIN'); + + $this->set_test_passed(false); + } + + /** + * Checks whether PHP's JSON extension is available or not + */ + protected function check_json() + { + if (@extension_loaded('json')) + { + $this->set_test_passed(true); + return; + } + + $this->response_helper->add_error_message('PHP_JSON_SUPPORT', 'PHP_JSON_SUPPORT_EXPLAIN'); + + $this->set_test_passed(false); + } + + /** + * Checks whether or not the XML PHP extension is available (Required by the text formatter) + */ + protected function check_xml() + { + if (class_exists('DOMDocument')) + { + $this->set_test_passed(true); + return; + } + + $this->response_helper->add_error_message('PHP_XML_SUPPORT', 'PHP_XML_SUPPORT_EXPLAIN'); + + $this->set_test_passed(false); + } + + /** + * Check if any supported DBMS is available + */ + protected function check_available_dbms() + { + $available_dbms = $this->database_helper->get_available_dbms(false, true); + + if ($available_dbms['ANY_DB_SUPPORT']) + { + $this->set_test_passed(true); + return; + } + + $this->response_helper->add_error_message('PHP_SUPPORTED_DB', 'PHP_SUPPORTED_DB_EXPLAIN'); + + $this->set_test_passed(false); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/requirements/task/check_update.php b/phpbb/install/module/requirements/task/check_update.php new file mode 100644 index 0000000..4eb2c6d --- /dev/null +++ b/phpbb/install/module/requirements/task/check_update.php @@ -0,0 +1,198 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\requirements\task; + +use phpbb\filesystem\filesystem; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +/** + * Check the availability of updater files and update version + */ +class check_update extends task_base +{ + /** + * @var \phpbb\config\db + */ + protected $config; + + /** + * @var filesystem + */ + protected $filesystem; + + /** + * @var config + */ + protected $installer_config; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var update_helper + */ + protected $update_helper; + + /** + * @var \phpbb\version_helper + */ + protected $version_helper; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * @var bool + */ + protected $tests_passed; + + /** + * Constructor + * + * @param container_factory $container + * @param filesystem $filesystem + * @param config $config + * @param iohandler_interface $iohandler + * @param update_helper $update_helper + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(container_factory $container, filesystem $filesystem, config $config, iohandler_interface $iohandler, update_helper $update_helper, $phpbb_root_path, $php_ext) + { + $this->filesystem = $filesystem; + $this->installer_config = $config; + $this->iohandler = $iohandler; + $this->update_helper = $update_helper; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->tests_passed = true; + + $this->config = $container->get('config'); + $this->version_helper = $container->get('version_helper'); + + parent::__construct(true); + } + + /** + * Sets $this->tests_passed + * + * @param bool $is_passed + */ + protected function set_test_passed($is_passed) + { + // If one test failed, tests_passed should be false + $this->tests_passed = $this->tests_passed && $is_passed; + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Array of update files + $update_files = array( + $this->phpbb_root_path . 'install/update', + $this->phpbb_root_path . 'install/update/index.' . $this->php_ext, + ); + + // Check for a valid update directory + if (!$this->filesystem->exists($update_files) || !$this->filesystem->is_readable($update_files)) + { + if ($this->iohandler->get_input('update_type', 'all') === 'all') + { + $this->iohandler->add_warning_message('UPDATE_FILES_NOT_FOUND'); + $this->set_test_passed(false); + } + + // If there are no update files, we can't check the version etc + // However, we can let the users run migrations if they really want to... + $this->installer_config->set('disable_filesystem_update', true); + return true; + } + + // Recover version numbers + $update_info = array(); + @include($this->phpbb_root_path . 'install/update/index.' . $this->php_ext); + $info = (empty($update_info) || !is_array($update_info)) ? false : $update_info; + $update_version = false; + + if ($info !== false) + { + $update_version = (!empty($info['version']['to'])) ? trim($info['version']['to']) : false; + } + + // Get current and latest version + try + { + $latest_version = $this->version_helper->get_latest_on_current_branch(true); + } + catch (\RuntimeException $e) + { + $latest_version = $update_version; + } + + $current_version = (!empty($this->config['version_update_from'])) ? $this->config['version_update_from'] : $this->config['version']; + + // Check if the update package + if (!$this->update_helper->phpbb_version_compare($current_version, $update_version, '<')) + { + $this->iohandler->add_error_message('NO_UPDATE_FILES_UP_TO_DATE'); + $this->tests_passed = false; + } + + // Check if the update package works with the installed version + if (empty($info['version']['from']) || $info['version']['from'] !== $current_version) + { + $this->iohandler->add_error_message(array('INCOMPATIBLE_UPDATE_FILES', $current_version, $info['version']['from'], $update_version)); + $this->tests_passed = false; + } + + // check if this is the latest update package + if ($this->update_helper->phpbb_version_compare($update_version, $latest_version, '<')) + { + $this->iohandler->add_warning_message(array('OLD_UPDATE_FILES', $info['version']['from'], $update_version, $latest_version)); + } + + return $this->tests_passed; + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/requirements/update_module.php b/phpbb/install/module/requirements/update_module.php new file mode 100644 index 0000000..223d12f --- /dev/null +++ b/phpbb/install/module/requirements/update_module.php @@ -0,0 +1,25 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\requirements; + +class update_module extends abstract_requirements_module +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('update', 0, 'requirements'); + } +} diff --git a/phpbb/install/module/update_database/module.php b/phpbb/install/module/update_database/module.php new file mode 100644 index 0000000..ee38afe --- /dev/null +++ b/phpbb/install/module/update_database/module.php @@ -0,0 +1,33 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_database; + +class module extends \phpbb\install\module_base +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('update', 0, 'update_database'); + } + + /** + * {@inheritdoc} + */ + public function get_step_count() + { + return 0; + } +} diff --git a/phpbb/install/module/update_database/task/update.php b/phpbb/install/module/update_database/task/update.php new file mode 100644 index 0000000..fb9eb44 --- /dev/null +++ b/phpbb/install/module/update_database/task/update.php @@ -0,0 +1,234 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_database\task; + +use phpbb\db\migration\exception; +use phpbb\db\output_handler\installer_migrator_output_handler; +use phpbb\db\output_handler\log_wrapper_migrator_output_handler; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\task_base; + +/** + * Database updater task + */ +class update extends task_base +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var \phpbb\extension\manager + */ + protected $extension_manager; + + /** + * @var \phpbb\filesystem\filesystem + */ + protected $filesystem; + + /** + * @var \phpbb\install\helper\config + */ + protected $installer_config; + + /** + * @var \phpbb\install\helper\iohandler\iohandler_interface + */ + protected $iohandler; + + /** + * @var \phpbb\language\language + */ + protected $language; + + /** + * @var \phpbb\log\log + */ + protected $log; + + /** + * @var \phpbb\db\migrator + */ + protected $migrator; + + /** + * @var \phpbb\user + */ + protected $user; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param \phpbb\install\helper\container_factory $container + * @param \phpbb\filesystem\filesystem $filesystem + * @param \phpbb\install\helper\config $installer_config + * @param \phpbb\install\helper\iohandler\iohandler_interface $iohandler + * @param \phpbb\language\language $language + * @param string $phpbb_root_path + */ + public function __construct(\phpbb\install\helper\container_factory $container, \phpbb\filesystem\filesystem $filesystem, \phpbb\install\helper\config $installer_config, \phpbb\install\helper\iohandler\iohandler_interface $iohandler, \phpbb\language\language $language, $phpbb_root_path) + { + $this->filesystem = $filesystem; + $this->installer_config = $installer_config; + $this->iohandler = $iohandler; + $this->language = $language; + $this->phpbb_root_path = $phpbb_root_path; + + $this->cache = $container->get('cache.driver'); + $this->config = $container->get('config'); + $this->extension_manager = $container->get('ext.manager'); + $this->log = $container->get('log'); + $this->migrator = $container->get('migrator'); + $this->user = $container->get('user'); + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->language->add_lang('migrator'); + + if (!isset($this->config['version_update_from'])) + { + $this->config->set('version_update_from', $this->config['version']); + } + + $original_version = $this->config['version_update_from']; + + $this->migrator->set_output_handler( + new log_wrapper_migrator_output_handler( + $this->language, + new installer_migrator_output_handler($this->iohandler), + $this->phpbb_root_path . 'store/migrations_' . time() . '.log', + $this->filesystem + ) + ); + + $this->migrator->create_migrations_table(); + + $migrations = $this->extension_manager + ->get_finder() + ->core_path('phpbb/db/migration/data/') + ->extension_directory('/migrations') + ->get_classes(); + + $this->migrator->set_migrations($migrations); + + $migration_step_count = $this->installer_config->get('database_update_migration_steps', -1); + if ($migration_step_count < 0) + { + $migration_step_count = count($this->migrator->get_installable_migrations()) * 2; + $this->installer_config->set('database_update_migration_steps', $migration_step_count); + } + + $progress_count = $this->installer_config->get('database_update_count', 0); + $restart_progress_bar = ($progress_count === 0); // Only "restart" when the update runs for the first time + $this->iohandler->set_task_count($migration_step_count, $restart_progress_bar); + $this->installer_config->set_task_progress_count($migration_step_count); + + while (!$this->migrator->finished()) + { + try + { + $this->migrator->update(); + $progress_count++; + + $last_run_migration = $this->migrator->get_last_run_migration(); + if (isset($last_run_migration['effectively_installed']) && $last_run_migration['effectively_installed']) + { + // We skipped two step, so increment $progress_count by another one + $progress_count++; + } + else if (($last_run_migration['task'] === 'process_schema_step' && !$last_run_migration['state']['migration_schema_done']) || + ($last_run_migration['task'] === 'process_data_step' && !$last_run_migration['state']['migration_data_done'])) + { + // We just run a step that wasn't counted yet so make it count + $migration_step_count++; + } + + $this->iohandler->set_task_count($migration_step_count); + $this->installer_config->set_task_progress_count($migration_step_count); + $this->iohandler->set_progress('STAGE_UPDATE_DATABASE', $progress_count); + } + catch (exception $e) + { + $msg = $e->getParameters(); + array_unshift($msg, $e->getMessage()); + + $this->iohandler->add_error_message($msg); + throw new user_interaction_required_exception(); + } + + if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0) + { + $this->installer_config->set('database_update_count', $progress_count); + $this->installer_config->set('database_update_migration_steps', $migration_step_count); + throw new resource_limit_reached_exception(); + } + } + + if ($original_version !== $this->config['version']) + { + $this->log->add( + 'admin', + (isset($this->user->data['user_id'])) ? $this->user->data['user_id'] : ANONYMOUS, + $this->user->ip, + 'LOG_UPDATE_DATABASE', + false, + array( + $original_version, + $this->config['version'] + ) + ); + } + + $this->iohandler->add_success_message('INLINE_UPDATE_SUCCESSFUL'); + + $this->cache->purge(); + + $this->config->increment('assets_version', 1); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/update_database/task/update_extensions.php b/phpbb/install/module/update_database/task/update_extensions.php new file mode 100644 index 0000000..0195b9c --- /dev/null +++ b/phpbb/install/module/update_database/task/update_extensions.php @@ -0,0 +1,263 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_database\task; + +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; +use Symfony\Component\Finder\Finder; + +/** + * Installs extensions that exist in ext folder upon install + */ +class update_extensions extends task_base +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var config + */ + protected $install_config; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** @var update_helper */ + protected $update_helper; + + /** + * @var \phpbb\config\db + */ + protected $config; + + /** + * @var \phpbb\log\log_interface + */ + protected $log; + + /** + * @var \phpbb\user + */ + protected $user; + + /** @var \phpbb\extension\manager */ + protected $extension_manager; + + /** @var Finder */ + protected $finder; + + /** @var string Extension table */ + protected $extension_table; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** + * @var array List of default extensions to update, grouped by version + * they were added + */ + static public $default_extensions_update = [ + '3.2.0-RC2' => ['phpbb/viglink'] + ]; + + /** + * Constructor + * + * @param container_factory $container + * @param config $install_config + * @param iohandler_interface $iohandler + * @param $update_helper $update_helper + * @param string $phpbb_root_path phpBB root path + */ + public function __construct(container_factory $container, config $install_config, iohandler_interface $iohandler, update_helper $update_helper, $phpbb_root_path) + { + $this->install_config = $install_config; + $this->iohandler = $iohandler; + $this->extension_table = $container->get_parameter('tables.ext'); + + $this->log = $container->get('log'); + $this->user = $container->get('user'); + $this->extension_manager = $container->get('ext.manager'); + $this->cache = $container->get('cache.driver'); + $this->config = $container->get('config'); + $this->db = $container->get('dbal.conn'); + $this->update_helper = $update_helper; + $this->finder = new Finder(); + $this->finder->in($phpbb_root_path . 'ext/') + ->ignoreUnreadableDirs() + ->depth('< 3') + ->files() + ->name('composer.json'); + + // Make sure asset version exists in config. Otherwise we might try to + // insert the assets_version setting into the database and cause a + // duplicate entry error. + if (!isset($this->config['assets_version'])) + { + $this->config['assets_version'] = 0; + } + + parent::__construct(true); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->user->session_begin(); + $this->user->setup(array('common', 'acp/common', 'cli')); + + $update_info = $this->install_config->get('update_info_unprocessed', []); + $version_from = !empty($update_info) ? $update_info['version']['from'] : $this->config['version_update_from']; + + if (!empty($version_from)) + { + $update_extensions = $this->iohandler->get_input('update-extensions', []); + + // Create list of default extensions that need to be enabled in update + $default_update_extensions = []; + foreach (self::$default_extensions_update as $version => $extensions) + { + if ($this->update_helper->phpbb_version_compare($version_from, $version, '<')) + { + $default_update_extensions = array_merge($default_update_extensions, $extensions); + } + } + + $all_available_extensions = $this->extension_manager->all_available(); + $i = $this->install_config->get('update_extensions_index', 0); + $available_extensions = array_slice($all_available_extensions, $i); + + // Update available extensions + foreach ($available_extensions as $ext_name => $ext_path) + { + // Update extensions if: + // 1) Extension is currently enabled + // 2) Extension was implicitly defined as needing an update + // 3) Extension was newly added as default phpBB extension in + // this update and should be enabled by default. + if ($this->extension_manager->is_enabled($ext_name) || + in_array($ext_name, $update_extensions) || + in_array($ext_name, $default_update_extensions) + ) + { + try + { + $extension_enabled = $this->extension_manager->is_enabled($ext_name); + if ($extension_enabled) + { + $this->extension_manager->disable($ext_name); + } + $this->extension_manager->enable($ext_name); + $extensions = $this->get_extensions(); + + if (isset($extensions[$ext_name]) && $extensions[$ext_name]['ext_active']) + { + // Create log + $this->log->add('admin', ANONYMOUS, '', 'LOG_EXT_UPDATE', time(), array($ext_name)); + $this->iohandler->add_success_message(array('CLI_EXTENSION_UPDATE_SUCCESS', $ext_name)); + } + else + { + $this->iohandler->add_log_message('CLI_EXTENSION_UPDATE_FAILURE', array($ext_name)); + } + + // Disable extensions if it was disabled by the admin before + if (!$extension_enabled && !in_array($ext_name, $default_update_extensions)) + { + $this->extension_manager->disable($ext_name); + } + } + catch (\Exception $e) + { + // Add fail log and continue + $this->iohandler->add_log_message('CLI_EXTENSION_UPDATE_FAILURE', array($ext_name)); + } + } + + $i++; + + // Stop execution if resource limit is reached + if ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0) + { + break; + } + } + + $this->install_config->set('update_extensions_index', $i); + + if ($i < count($all_available_extensions)) + { + throw new resource_limit_reached_exception(); + } + } + + $this->config->delete('version_update_from'); + + $this->cache->purge(); + + $this->config->increment('assets_version', 1); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 1; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return 'TASK_UPDATE_EXTENSIONS'; + } + + /** + * Get extensions from database + * + * @return array List of extensions + */ + private function get_extensions() + { + $sql = 'SELECT * + FROM ' . $this->extension_table; + + $result = $this->db->sql_query($sql); + $extensions_row = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + $extensions = array(); + + foreach ($extensions_row as $extension) + { + $extensions[$extension['ext_name']] = $extension; + } + + ksort($extensions); + + return $extensions; + } +} diff --git a/phpbb/install/module/update_filesystem/module.php b/phpbb/install/module/update_filesystem/module.php new file mode 100644 index 0000000..157c78a --- /dev/null +++ b/phpbb/install/module/update_filesystem/module.php @@ -0,0 +1,33 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_filesystem; + +class module extends \phpbb\install\module_base +{ + /** + * {@inheritdoc} + */ + public function get_navigation_stage_path() + { + return array('update', 0, 'update_files'); + } + + /** + * {@inheritdoc} + */ + public function get_step_count() + { + return 0; + } +} diff --git a/phpbb/install/module/update_filesystem/task/diff_files.php b/phpbb/install/module/update_filesystem/task/diff_files.php new file mode 100644 index 0000000..2f6048b --- /dev/null +++ b/phpbb/install/module/update_filesystem/task/diff_files.php @@ -0,0 +1,253 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_filesystem\task; + +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +/** + * Merges user made changes into the files + */ +class diff_files extends task_base +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var config + */ + protected $installer_config; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * @var update_helper + */ + protected $update_helper; + + /** + * Constructor + * + * @param container_factory $container + * @param config $config + * @param iohandler_interface $iohandler + * @param update_helper $update_helper + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(container_factory $container, config $config, iohandler_interface $iohandler, update_helper $update_helper, $phpbb_root_path, $php_ext) + { + $this->installer_config = $config; + $this->iohandler = $iohandler; + $this->update_helper = $update_helper; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->cache = $container->get('cache.driver'); + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + $files_to_diff = $this->installer_config->get('update_files', array()); + $files_to_diff = (isset($files_to_diff['update_with_diff'])) ? $files_to_diff['update_with_diff'] : array(); + + return $this->installer_config->get('do_update_files', false) && count($files_to_diff) > 0; + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Include diff engine + $this->update_helper->include_file('includes/diff/diff.' . $this->php_ext); + $this->update_helper->include_file('includes/diff/engine.' . $this->php_ext); + + // Set up basic vars + $old_path = $this->update_helper->get_path_to_old_update_files(); + $new_path = $this->update_helper->get_path_to_new_update_files(); + + $update_files = $this->installer_config->get('update_files', array()); + $files_to_diff = $update_files['update_with_diff']; + + // Set progress bar + $this->iohandler->set_task_count(count($files_to_diff), true); + $this->iohandler->set_progress('UPDATE_FILE_DIFF', 0); + $progress_count = $this->installer_config->get('file_diff_update_count', 0); + + // Recover progress + $progress_key = $this->installer_config->get('differ_progress_key', -1); + $progress_recovered = ($progress_key === -1); + $merge_conflicts = $this->installer_config->get('merge_conflict_list', array()); + + foreach ($files_to_diff as $key => $filename) + { + if ($progress_recovered === false) + { + if ($progress_key === $key) + { + $progress_recovered = true; + } + + continue; + } + + // Read in files' content + $file_contents = array(); + + // Handle the special case when user created a file with the filename that is now new in the core + if (file_exists($old_path . $filename)) + { + $file_contents[0] = file_get_contents($old_path . $filename); + + $filenames = array( + $this->phpbb_root_path . $filename, + $new_path . $filename + ); + + foreach ($filenames as $file_to_diff) + { + $file_contents[] = file_get_contents($file_to_diff); + + if ($file_contents[count($file_contents) - 1] === false) + { + $this->iohandler->add_error_message(array('FILE_DIFFER_ERROR_FILE_CANNOT_BE_READ', $files_to_diff)); + unset($file_contents); + throw new user_interaction_required_exception(); + } + } + + $diff = new \diff3($file_contents[0], $file_contents[1], $file_contents[2]); + + // Handle conflicts + if ($diff->get_num_conflicts() !== 0) + { + $merge_conflicts[] = $filename; + } + + if ($diff->merged_output() !== $file_contents[1]) + { + // Save merged output + $this->cache->put( + '_file_' . md5($filename), + base64_encode(implode("\n", $diff->merged_output())) + ); + } + else + { + unset($update_files['update_with_diff'][$key]); + } + + unset($file_contents); + unset($diff); + } + else + { + $new_file_content = file_get_contents($new_path . $filename); + + if ($new_file_content === false) + { + $this->iohandler->add_error_message(array('FILE_DIFFER_ERROR_FILE_CANNOT_BE_READ', $files_to_diff)); + unset($new_file_content ); + throw new user_interaction_required_exception(); + } + + // Save new file content to cache + $this->cache->put( + '_file_' . md5($filename), + base64_encode($new_file_content) + ); + unset($new_file_content); + } + + $progress_count++; + $this->iohandler->set_progress('UPDATE_FILE_DIFF', $progress_count); + + if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0) + { + // Save differ progress + $this->installer_config->set('differ_progress_key', $key); + $this->installer_config->set('merge_conflict_list', $merge_conflicts); + $this->installer_config->set('file_diff_update_count', $progress_count); + + foreach ($update_files as $type => $files) + { + if (empty($files)) + { + unset($update_files[$type]); + } + } + + $this->installer_config->set('update_files', $update_files); + + // Request refresh + throw new resource_limit_reached_exception(); + } + } + + $this->iohandler->finish_progress('ALL_FILES_DIFFED'); + $this->installer_config->set('merge_conflict_list', $merge_conflicts); + + foreach ($update_files as $type => $files) + { + if (empty($files)) + { + unset($update_files[$type]); + } + } + + $this->installer_config->set('update_files', $update_files); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/update_filesystem/task/download_updated_files.php b/phpbb/install/module/update_filesystem/task/download_updated_files.php new file mode 100644 index 0000000..4d7f0e0 --- /dev/null +++ b/phpbb/install/module/update_filesystem/task/download_updated_files.php @@ -0,0 +1,133 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_filesystem\task; + +use phpbb\filesystem\filesystem; +use phpbb\install\exception\jump_to_restart_point_exception; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class download_updated_files extends task_base +{ + /** + * @var config + */ + protected $installer_config; + + /** + * @var filesystem + */ + protected $filesystem; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * Constructor + * + * @param config $config + * @param iohandler_interface $iohandler + * @param filesystem $filesystem + */ + public function __construct(config $config, iohandler_interface $iohandler, filesystem $filesystem) + { + $this->installer_config = $config; + $this->iohandler = $iohandler; + $this->filesystem = $filesystem; + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + return $this->installer_config->get('do_update_files', false) + && $this->installer_config->get('file_update_method', '') === 'compression'; + } + + /** + * {@inheritdoc} + */ + public function run() + { + if ($this->iohandler->get_input('database_update_submit', false)) + { + // Remove archive + $this->filesystem->remove( + $this->installer_config->get('update_file_archive', null) + ); + + $this->installer_config->set('update_file_archive', null); + } + else if ($this->iohandler->get_input('update_recheck_files_submit', false)) + { + $this->installer_config->set('file_updater_elem_progress', ''); + $this->installer_config->set('update_files', array()); + throw new jump_to_restart_point_exception('check_update_files'); + } + else + { + $file_update_info = $this->installer_config->get('update_files', array()); + + // Display download box only if the archive won't be empty + if (!empty($file_update_info) && !(isset($file_update_info['delete']) && count($file_update_info) == 1)) + { + // Render download box + $this->iohandler->add_download_link( + 'phpbb_installer_update_file_download', + 'DOWNLOAD_UPDATE_METHOD', + 'DOWNLOAD_UPDATE_METHOD_EXPLAIN' + ); + } + + // Add form to continue update + $this->iohandler->add_user_form_group('UPDATE_CONTINUE_UPDATE_PROCESS', array( + 'update_recheck_files_submit' => array( + 'label' => 'UPDATE_RECHECK_UPDATE_FILES', + 'type' => 'submit', + 'is_secondary' => empty($file_update_info), + ), + 'database_update_submit' => array( + 'label' => 'UPDATE_CONTINUE_UPDATE_PROCESS', + 'type' => 'submit', + 'disabled' => !empty($file_update_info), + ), + )); + + throw new user_interaction_required_exception(); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/update_filesystem/task/file_check.php b/phpbb/install/module/update_filesystem/task/file_check.php new file mode 100644 index 0000000..9daa853 --- /dev/null +++ b/phpbb/install/module/update_filesystem/task/file_check.php @@ -0,0 +1,248 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_filesystem\task; + +use phpbb\filesystem\filesystem; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +/** + * Updater task performing file checking + */ +class file_check extends task_base +{ + /** + * @var filesystem + */ + protected $filesystem; + + /** + * @var config + */ + protected $installer_config; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var update_helper + */ + protected $update_helper; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Construct + * + * @param filesystem $filesystem + * @param config $config + * @param iohandler_interface $iohandler + * @param update_helper $update_helper + * @param string $phpbb_root_path + */ + public function __construct(filesystem $filesystem, config $config, iohandler_interface $iohandler, update_helper $update_helper, $phpbb_root_path) + { + $this->filesystem = $filesystem; + $this->installer_config = $config; + $this->iohandler = $iohandler; + $this->update_helper = $update_helper; + $this->phpbb_root_path = $phpbb_root_path; + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + return $this->installer_config->get('do_update_files', false); + } + + /** + * {@inheritdoc} + */ + public function run() + { + if (!$this->installer_config->has_restart_point('check_update_files')) + { + $this->installer_config->create_progress_restart_point('check_update_files'); + } + + $old_path = $this->update_helper->get_path_to_old_update_files(); + $new_path = $this->update_helper->get_path_to_new_update_files(); + + $update_info = $this->installer_config->get('update_info', array()); + $file_update_info = $this->installer_config->get('update_files', array()); + + if (empty($update_info)) + { + $root_path = $this->phpbb_root_path; + + $update_info = $this->installer_config->get('update_info_unprocessed', array()); + + $file_update_info = array(); + $file_update_info['update_without_diff'] = array_diff($update_info['binary'], $update_info['deleted']); + + foreach ($file_update_info['update_without_diff'] as $key => $binary_file) + { + $new_file = $new_path . $binary_file; + $file = $this->phpbb_root_path . $binary_file; + + if (!$this->filesystem->exists($file)) + { + continue; + } + + if (md5_file($file) === md5_file($new_file)) + { + // File already up to date + unset($file_update_info['update_without_diff'][$key]); + } + } + + // Remove update without diff info if empty + if (count($file_update_info['update_without_diff']) < 1) + { + unset($file_update_info['update_without_diff']); + } + + // Filter out files that are already deleted + $file_update_info['delete'] = array_filter( + $update_info['deleted'], + function ($filename) use ($root_path) + { + return file_exists($root_path . $filename); + } + ); + + // Remove files to delete list if empty + if (count($file_update_info['delete']) < 1) + { + unset($file_update_info['delete']); + } + } + + $progress_count = $this->installer_config->get('file_check_progress_count', 0); + $task_count = count($update_info['files']); + $this->iohandler->set_task_count($task_count); + $this->iohandler->set_progress('UPDATE_CHECK_FILES', 0); + + // Create list of default extensions that should have been added prior + // to this update + $default_update_extensions = []; + foreach (\phpbb\install\module\update_database\task\update_extensions::$default_extensions_update as $version => $extensions) + { + if ($this->update_helper->phpbb_version_compare($update_info['version']['from'], $version, '>=')) + { + $default_update_extensions = array_merge($default_update_extensions, $extensions); + } + } + + foreach ($update_info['files'] as $key => $filename) + { + $old_file = $old_path . $filename; + $new_file = $new_path . $filename; + $file = $this->phpbb_root_path . $filename; + + if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0) + { + // Save progress + $this->installer_config->set('update_info', $update_info); + $this->installer_config->set('file_check_progress_count', $progress_count); + $this->installer_config->set('update_files', $file_update_info); + + // Request refresh + throw new resource_limit_reached_exception(); + } + + $progress_count++; + $this->iohandler->set_progress('UPDATE_CHECK_FILES', $progress_count); + + // Do not copy default extension again if the previous version was + // packaged with it but it does not exist (e.g. deleted by admin) + if (strpos($file, $this->phpbb_root_path . 'ext/') !== false) + { + $skip_file = false; + foreach ($default_update_extensions as $ext_name) + { + if (strpos($file, $this->phpbb_root_path . 'ext/' . $ext_name) !== false && + !$this->filesystem->exists($this->phpbb_root_path . 'ext/' . $ext_name . '/composer.json')) + { + $skip_file = true; + break; + } + } + + if ($skip_file) + { + continue; + } + } + + if (!$this->filesystem->exists($file)) + { + $file_update_info['new'][] = $filename; + } + else + { + $file_checksum = md5_file($file); + + if ($file_checksum === md5_file($new_file)) + { + // File already up to date + continue; + } + else if ($this->filesystem->exists($old_file) && $file_checksum === md5_file($old_file)) + { + // No need to diff the file + $file_update_info['update_without_diff'][] = $filename; + } + else + { + $file_update_info['update_with_diff'][] = $filename; + } + } + + unset($update_info['files'][$key]); + } + + $this->installer_config->set('update_files', $file_update_info); + $this->installer_config->set('update_info', array()); + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/update_filesystem/task/show_file_status.php b/phpbb/install/module/update_filesystem/task/show_file_status.php new file mode 100644 index 0000000..0e82f91 --- /dev/null +++ b/phpbb/install/module/update_filesystem/task/show_file_status.php @@ -0,0 +1,170 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_filesystem\task; + +use phpbb\filesystem\filesystem; +use phpbb\install\exception\user_interaction_required_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\file_updater\factory; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\task_base; + +class show_file_status extends task_base +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var filesystem + */ + protected $filesystem; + + /** + * @var config + */ + protected $installer_config; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var \phpbb\install\helper\file_updater\compression_file_updater + */ + protected $file_updater; + + /** + * Constructor + * + * @param container_factory $container + * @param config $config + * @param iohandler_interface $iohandler + * @param filesystem $filesystem + * @param factory $file_updater_factory + */ + public function __construct(container_factory $container, config $config, iohandler_interface $iohandler, filesystem $filesystem, factory $file_updater_factory) + { + $this->installer_config = $config; + $this->iohandler = $iohandler; + $this->filesystem = $filesystem; + + $this->cache = $container->get('cache.driver'); + + // Initialize compression file updater + $this->file_updater = $file_updater_factory->get('compression'); + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + return $this->installer_config->get('do_update_files', false); + } + + /** + * {@inheritdoc} + */ + public function run() + { + if (!$this->iohandler->get_input('submit_continue_file_update', false)) + { + // Handle merge conflicts + $merge_conflicts = $this->installer_config->get('merge_conflict_list', array()); + + // Create archive for merge conflicts + if (!empty($merge_conflicts)) + { + $compression_method = $this->installer_config->get('file_update_compression', ''); + $conflict_archive = $this->file_updater->init($compression_method); + $this->installer_config->set('update_file_conflict_archive', $conflict_archive); + + foreach ($merge_conflicts as $filename) + { + $this->file_updater->create_new_file( + $filename, + base64_decode($this->cache->get('_file_' . md5($filename))), + true + ); + } + + // Render download box + $this->iohandler->add_download_link( + 'phpbb_installer_update_conflict_download', + 'DOWNLOAD_CONFLICTS', + 'DOWNLOAD_CONFLICTS_EXPLAIN' + ); + + $this->file_updater->close(); + } + + // Render update file statuses + $file_update_info = $this->installer_config->get('update_files', array()); + $file_status = array( + 'deleted' => (!isset($file_update_info['delete'])) ? array() : $file_update_info['delete'], + 'new' => (!isset($file_update_info['new'])) ? array() : $file_update_info['new'], + 'conflict' => $this->installer_config->get('merge_conflict_list', array()), + 'modified' => (!isset($file_update_info['update_with_diff'])) ? array() : $file_update_info['update_with_diff'], + 'not_modified' => (!isset($file_update_info['update_without_diff'])) ? array() : $file_update_info['update_without_diff'], + ); + + $this->iohandler->render_update_file_status($file_status); + + // Add form to continue update + $this->iohandler->add_user_form_group('UPDATE_CONTINUE_FILE_UPDATE', array( + 'submit_continue_file_update' => array( + 'label' => 'UPDATE_CONTINUE_FILE_UPDATE', + 'type' => 'submit', + ), + )); + + // Show results to the user + throw new user_interaction_required_exception(); + } + else + { + $conflict_archive_path = $this->installer_config->get('update_file_conflict_archive', null); + + // Remove archive + if ($conflict_archive_path !== null && $this->filesystem->exists($conflict_archive_path)) + { + $this->filesystem->remove($conflict_archive_path); + } + + $this->installer_config->set('update_file_conflict_archive', null); + } + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module/update_filesystem/task/update_files.php b/phpbb/install/module/update_filesystem/task/update_files.php new file mode 100644 index 0000000..fbb465c --- /dev/null +++ b/phpbb/install/module/update_filesystem/task/update_files.php @@ -0,0 +1,294 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install\module\update_filesystem\task; + +use phpbb\exception\runtime_exception; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\container_factory; +use phpbb\install\helper\file_updater\factory; +use phpbb\install\helper\file_updater\file_updater_interface; +use phpbb\install\helper\iohandler\iohandler_interface; +use phpbb\install\helper\update_helper; +use phpbb\install\task_base; + +/** + * File updater task + */ +class update_files extends task_base +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var config + */ + protected $installer_config; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var factory + */ + protected $factory; + + /** + * @var file_updater_interface + */ + protected $file_updater; + + /** + * @var update_helper + */ + protected $update_helper; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param container_factory $container + * @param config $config + * @param iohandler_interface $iohandler + * @param factory $file_updater_factory + * @param update_helper $update_helper + * @param string $phpbb_root_path + */ + public function __construct(container_factory $container, config $config, iohandler_interface $iohandler, factory $file_updater_factory, update_helper $update_helper, $phpbb_root_path) + { + $this->factory = $file_updater_factory; + $this->installer_config = $config; + $this->iohandler = $iohandler; + $this->update_helper = $update_helper; + $this->phpbb_root_path = $phpbb_root_path; + + $this->cache = $container->get('cache.driver'); + $this->file_updater = null; + + parent::__construct(false); + } + + /** + * {@inheritdoc} + */ + public function check_requirements() + { + return $this->installer_config->get('do_update_files', false); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $new_path = $this->update_helper->get_path_to_new_update_files(); + + $file_update_info = $this->installer_config->get('update_files', array()); + + $update_type_progress = $this->installer_config->get('file_updater_type_progress', ''); + $update_elem_progress = $this->installer_config->get('file_updater_elem_progress', ''); + $type_progress_found = false; + $elem_progress_found = false; + + // Progress bar + $task_count = 0; + foreach ($file_update_info as $sub_array) + { + $task_count += count($sub_array); + } + + // Everything is up to date, so just continue + if ($task_count === 0) + { + return; + } + + $progress_count = $this->installer_config->get('file_update_progress_count', 0); + $this->iohandler->set_task_count($task_count, true); + $this->iohandler->set_progress('UPDATE_UPDATING_FILES', 0); + + $this->file_updater = $this->get_file_updater(); + + // File updater fallback logic + try + { + // Update files + foreach ($file_update_info as $type => $file_update_vector) + { + if (!$type_progress_found) + { + if ($type === $update_type_progress || empty($update_elem_progress)) + { + $type_progress_found = true; + } + else + { + continue; + } + } + + foreach ($file_update_vector as $path) + { + if (!$elem_progress_found) + { + if ($path === $update_elem_progress || empty($update_elem_progress)) + { + $elem_progress_found = true; + } + else + { + continue; + } + } + + switch ($type) + { + case 'delete': + $this->file_updater->delete_file($path); + break; + case 'new': + $this->file_updater->create_new_file($path, $new_path . $path); + break; + case 'update_without_diff': + $this->file_updater->update_file($path, $new_path . $path); + break; + case 'update_with_diff': + $this->file_updater->update_file( + $path, + base64_decode($this->cache->get('_file_' . md5($path))), + true + ); + break; + } + + // Save progress + $this->installer_config->set('file_updater_type_progress', $type); + $this->installer_config->set('file_updater_elem_progress', $path); + $progress_count++; + $this->iohandler->set_progress('UPDATE_UPDATING_FILES', $progress_count); + + if ($this->installer_config->get_time_remaining() <= 0 || $this->installer_config->get_memory_remaining() <= 0) + { + // Request refresh + throw new resource_limit_reached_exception(); + } + } + } + + $this->iohandler->finish_progress('UPDATE_UPDATING_FILES'); + } + catch (runtime_exception $e) + { + if ($e instanceof resource_limit_reached_exception) + { + throw new resource_limit_reached_exception(); + } + + $current_method = $this->installer_config->get('file_update_method', ''); + + // File updater failed, try to fallback to download file update mode + if ($current_method !== 'compression') + { + $this->iohandler->add_warning_message(array( + 'UPDATE_FILE_UPDATER_HAS_FAILED', + $current_method, + 'compression' + )); + $this->installer_config->set('file_update_method', 'compression'); + + // We only want a simple refresh here + throw new resource_limit_reached_exception(); + } + else + { + // Nowhere to fallback to :( + // Due to the way the installer handles fatal errors, we need to throw a low level exception + throw new runtime_exception('UPDATE_FILE_UPDATERS_HAVE_FAILED'); + } + } + + $file_updater_method = $this->installer_config->get('file_update_method', ''); + if ($file_updater_method === 'compression' || $file_updater_method === 'ftp') + { + $this->file_updater->close(); + } + } + + /** + * Get file updater + * + * @param null|string $file_updater_method Name of the file updater to use + * + * @return file_updater_interface File updater + */ + protected function get_file_updater($file_updater_method = null) + { + $file_updater_method = ($file_updater_method === null) ? $this->installer_config->get('file_update_method', '') : $file_updater_method; + + if ($file_updater_method === 'compression') + { + $compression_method = $this->installer_config->get('file_update_compression', ''); + + /** @var \phpbb\install\helper\file_updater\compression_file_updater $file_updater */ + $file_updater = $this->factory->get('compression'); + $archive_path = $file_updater->init($compression_method); + $this->installer_config->set('update_file_archive', $archive_path); + } + else if ($file_updater_method === 'ftp') + { + /** @var \phpbb\install\helper\file_updater\ftp_file_updater $file_updater */ + $file_updater = $this->factory->get('ftp'); + $file_updater->init( + $this->installer_config->get('ftp_method', ''), + $this->installer_config->get('ftp_host', ''), + $this->installer_config->get('ftp_user', ''), + $this->installer_config->get('ftp_pass', ''), + $this->installer_config->get('ftp_path', ''), + $this->installer_config->get('ftp_port', 0), + $this->installer_config->get('ftp_timeout', 10) + ); + } + else + { + /** @var file_updater_interface $file_updater */ + $file_updater = $this->factory->get('direct_file'); + } + + return $file_updater; + } + + /** + * {@inheritdoc} + */ + static public function get_step_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_task_lang_name() + { + return ''; + } +} diff --git a/phpbb/install/module_base.php b/phpbb/install/module_base.php new file mode 100644 index 0000000..93c10bd --- /dev/null +++ b/phpbb/install/module_base.php @@ -0,0 +1,213 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install; + +use phpbb\di\ordered_service_collection; +use phpbb\install\exception\resource_limit_reached_exception; +use phpbb\install\helper\config; +use phpbb\install\helper\iohandler\iohandler_interface; + +/** + * Base class for installer module + */ +abstract class module_base implements module_interface +{ + /** + * @var config + */ + protected $install_config; + + /** + * @var iohandler_interface + */ + protected $iohandler; + + /** + * @var bool + */ + protected $is_essential; + + /** + * Array of tasks for installer module + * + * @var ordered_service_collection + */ + protected $task_collection; + + /** + * @var array + */ + protected $task_step_count; + + /** + * @var bool + */ + protected $allow_progress_bar; + + /** + * Installer module constructor + * + * @param ordered_service_collection $tasks array of installer tasks for installer module + * @param bool $essential flag indicating whether the module is essential or not + * @param bool $allow_progress_bar flag indicating whether or not to send progress information from within the module + */ + public function __construct(ordered_service_collection $tasks, $essential = true, $allow_progress_bar = true) + { + $this->task_collection = $tasks; + $this->is_essential = $essential; + $this->allow_progress_bar = $allow_progress_bar; + } + + /** + * Dependency getter + * + * @param config $config + * @param iohandler_interface $iohandler + */ + public function setup(config $config, iohandler_interface $iohandler) + { + $this->install_config = $config; + $this->iohandler = $iohandler; + } + + /** + * {@inheritdoc} + */ + public function is_essential() + { + return $this->is_essential; + } + + /** + * {@inheritdoc} + * + * Overwrite this method if your task is non-essential! + */ + public function check_requirements() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function run() + { + // Recover install progress + $task_index = $this->recover_progress(); + $iterator = $this->task_collection->getIterator(); + + if ($task_index < $iterator->count()) + { + $iterator->seek($task_index); + } + else + { + $this->install_config->set_finished_task(0); + return; + } + + while ($iterator->valid()) + { + $task = $iterator->current(); + $name = $iterator->key(); + + // Check if we can run the task + if (!$task->is_essential() && !$task->check_requirements()) + { + $this->iohandler->add_log_message(array( + 'SKIP_TASK', + $name, + )); + + $this->install_config->increment_current_task_progress($this->task_step_count[$name]); + } + else + { + // Send progress information + if ($this->allow_progress_bar) + { + $this->iohandler->set_progress( + $task->get_task_lang_name(), + $this->install_config->get_current_task_progress() + ); + + $this->iohandler->send_response(); + } + + $task->run(); + + if ($this->allow_progress_bar) + { + // Only increment progress by one, as if a task has more than one steps + // then that should be incremented in the task itself + $this->install_config->increment_current_task_progress(); + } + } + + $task_index++; + $this->install_config->set_finished_task($task_index); + $iterator->next(); + + // Send progress information + if ($this->allow_progress_bar) + { + $this->iohandler->set_progress( + $task->get_task_lang_name(), + $this->install_config->get_current_task_progress() + ); + } + + $this->iohandler->send_response(); + + // Stop execution if resource limit is reached + if ($iterator->valid() && ($this->install_config->get_time_remaining() <= 0 || $this->install_config->get_memory_remaining() <= 0)) + { + throw new resource_limit_reached_exception(); + } + } + + // Module finished, so clear task progress + $this->install_config->set_finished_task(0); + } + + /** + * Returns the next task's name + * + * @return string Index of the array element of the next task + */ + protected function recover_progress() + { + $progress_array = $this->install_config->get_progress_data(); + return $progress_array['last_task_index']; + } + + /** + * {@inheritdoc} + */ + public function get_step_count() + { + $task_step_count = 0; + $task_class_names = $this->task_collection->get_service_classes(); + + foreach ($task_class_names as $name => $task_class) + { + $step_count = $task_class::get_step_count(); + $task_step_count += $step_count; + $this->task_step_count[$name] = $step_count; + } + + return $task_step_count; + } +} diff --git a/phpbb/install/module_interface.php b/phpbb/install/module_interface.php new file mode 100644 index 0000000..a2d61e3 --- /dev/null +++ b/phpbb/install/module_interface.php @@ -0,0 +1,63 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install; + +/** + * Interface for installer modules + * + * An installer module is a task collection which executes installer tasks. + */ +interface module_interface +{ + /** + * Checks if the execution of the module is essential to install phpBB or it can be skipped + * + * Note: Please note that all the non-essential modules have to implement check_requirements() + * method. + * + * @return bool true if the module is essential, false otherwise + */ + public function is_essential(); + + /** + * Checks requirements for the tasks + * + * Note: Only need to be implemented for non-essential tasks, as essential tasks + * requirements should be checked in the requirements install module. + * + * @return bool true if the task's requirements are met + */ + public function check_requirements(); + + /** + * Executes the task + * + * @return null + */ + public function run(); + + /** + * Returns the number of tasks in the module + * + * @return int + */ + public function get_step_count(); + + /** + * Returns an array to the correct navigation stage + * + * @return array + */ + public function get_navigation_stage_path(); +} diff --git a/phpbb/install/task_base.php b/phpbb/install/task_base.php new file mode 100644 index 0000000..b5199be --- /dev/null +++ b/phpbb/install/task_base.php @@ -0,0 +1,53 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install; + +/** + * Base class for installer task + */ +abstract class task_base implements task_interface +{ + /** + * @var bool + */ + protected $is_essential; + + /** + * Constructor + * + * @param bool $essential + */ + public function __construct($essential = true) + { + $this->is_essential = $essential; + } + + /** + * {@inheritdoc} + */ + public function is_essential() + { + return $this->is_essential; + } + + /** + * {@inheritdoc} + * + * Note: Overwrite this method if your task is non-essential! + */ + public function check_requirements() + { + return true; + } +} diff --git a/phpbb/install/task_interface.php b/phpbb/install/task_interface.php new file mode 100644 index 0000000..794cb16 --- /dev/null +++ b/phpbb/install/task_interface.php @@ -0,0 +1,61 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\install; + +/** + * Interface for installer tasks + */ +interface task_interface +{ + /** + * Returns the number of steps the task contains + * + * This is a helper method to provide a better progress bar for the front-end. + * + * @return int The number of steps that the task contains + */ + static public function get_step_count(); + + /** + * Checks if the task is essential to install phpBB or it can be skipped + * + * Note: Please note that all the non-essential modules have to implement check_requirements() + * method. + * + * @return bool true if the task is essential, false otherwise + */ + public function is_essential(); + + /** + * Checks requirements for the tasks + * + * Note: Only need to be implemented for non-essential tasks, as essential tasks + * requirements should be checked in the requirements install module. + * + * @return bool true if the task's requirements are met + */ + public function check_requirements(); + + /** + * Executes the task + */ + public function run(); + + /** + * Returns the language key of the name of the task + * + * @return string + */ + public function get_task_lang_name(); +} diff --git a/phpbb/install/updater_configuration.php b/phpbb/install/updater_configuration.php new file mode 100644 index 0000000..5c1c29f --- /dev/null +++ b/phpbb/install/updater_configuration.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\install; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +class updater_configuration implements ConfigurationInterface +{ + + /** + * Generates the configuration tree builder. + * + * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('updater'); + $rootNode + ->addDefaultsIfNotSet() + ->children() + ->enumNode('type')->values(['all','db_only'])->defaultValue('all')->end() + ->arrayNode('extensions') + ->prototype('scalar')->end() + ->defaultValue([]) + ->end() + ->end() + ; + + return $treeBuilder; + } +} diff --git a/phpbb/json_response.php b/phpbb/json_response.php new file mode 100644 index 0000000..5219cd0 --- /dev/null +++ b/phpbb/json_response.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** +* JSON class +*/ +class json_response +{ + /** + * Send the data to the client and exit the script. + * + * @param array $data Any additional data to send. + * @param bool $exit Will exit the script if true. + */ + public function send($data, $exit = true) + { + header('Content-Type: application/json'); + echo json_encode($data); + + if ($exit) + { + garbage_collection(); + exit_handler(); + } + } +} diff --git a/phpbb/language/exception/invalid_plural_rule_exception.php b/phpbb/language/exception/invalid_plural_rule_exception.php new file mode 100644 index 0000000..94e3466 --- /dev/null +++ b/phpbb/language/exception/invalid_plural_rule_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\language\exception; + +/** + * Thrown when nonexistent plural rule is specified + */ +class invalid_plural_rule_exception extends language_exception +{ + +} diff --git a/phpbb/language/exception/language_exception.php b/phpbb/language/exception/language_exception.php new file mode 100644 index 0000000..b125841 --- /dev/null +++ b/phpbb/language/exception/language_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\language\exception; + +/** + * Base exception class for language exceptions + */ +class language_exception extends \phpbb\exception\runtime_exception +{ + +} diff --git a/phpbb/language/exception/language_file_not_found.php b/phpbb/language/exception/language_file_not_found.php new file mode 100644 index 0000000..8936426 --- /dev/null +++ b/phpbb/language/exception/language_file_not_found.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\language\exception; + +/** + * This exception is thrown when the language file is not found + */ +class language_file_not_found extends language_exception +{ + +} diff --git a/phpbb/language/language.php b/phpbb/language/language.php new file mode 100644 index 0000000..51e6d0b --- /dev/null +++ b/phpbb/language/language.php @@ -0,0 +1,673 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\language; + +use phpbb\language\exception\invalid_plural_rule_exception; + +/** + * Wrapper class for loading translations + */ +class language +{ + /** + * Global fallback language + * + * ISO code of the language to fallback to when the specified language entries + * cannot be found. + * + * @var string + */ + const FALLBACK_LANGUAGE = 'en'; + + /** + * @var array List of common language files + */ + protected $common_language_files; + + /** + * @var bool + */ + protected $common_language_files_loaded; + + /** + * @var string ISO code of the default board language + */ + protected $default_language; + + /** + * @var string ISO code of the User's language + */ + protected $user_language; + + /** + * @var array Language fallback array (the order is important) + */ + protected $language_fallback; + + /** + * @var array Array of language variables + */ + protected $lang; + + /** + * @var array Loaded language sets + */ + protected $loaded_language_sets; + + /** + * @var \phpbb\language\language_file_loader Language file loader + */ + protected $loader; + + /** + * Constructor + * + * @param \phpbb\language\language_file_loader $loader Language file loader + * @param array|null $common_modules Array of common language modules to load (optional) + */ + public function __construct(language_file_loader $loader, $common_modules = null) + { + $this->loader = $loader; + + // Set up default information + $this->user_language = false; + $this->default_language = false; + $this->lang = array(); + $this->loaded_language_sets = array( + 'core' => array(), + 'ext' => array(), + ); + + // Common language files + if (is_array($common_modules)) + { + $this->common_language_files = $common_modules; + } + else + { + $this->common_language_files = array( + 'common', + ); + } + + $this->common_language_files_loaded = false; + + $this->language_fallback = array(self::FALLBACK_LANGUAGE); + } + + /** + * Function to set user's language to display. + * + * @param string $user_lang_iso ISO code of the User's language + * @param bool $reload Whether or not to reload language files + */ + public function set_user_language($user_lang_iso, $reload = false) + { + $this->user_language = $user_lang_iso; + + $this->set_fallback_array($reload); + } + + /** + * Function to set the board's default language to display. + * + * @param string $default_lang_iso ISO code of the board's default language + * @param bool $reload Whether or not to reload language files + */ + public function set_default_language($default_lang_iso, $reload = false) + { + $this->default_language = $default_lang_iso; + + $this->set_fallback_array($reload); + } + + /** + * Returns language array + * + * Note: This function is needed for the BC purposes, until \phpbb\user::lang[] is + * not removed. + * + * @return array Array of loaded language strings + */ + public function get_lang_array() + { + // Load common language files if they not loaded yet + if (!$this->common_language_files_loaded) + { + $this->load_common_language_files(); + } + + return $this->lang; + } + + /** + * Add Language Items + * + * Examples: + * + * $component = array('posting'); + * $component = array('posting', 'viewtopic') + * $component = 'posting' + * + * + * @param string|array $component The name of the language component to load + * @param string|null $extension_name Name of the extension to load component from, or null for core file + */ + public function add_lang($component, $extension_name = null) + { + // Load common language files if they not loaded yet + // This needs to be here to correctly merge language arrays + if (!$this->common_language_files_loaded) + { + $this->load_common_language_files(); + } + + if (!is_array($component)) + { + if (!is_null($extension_name)) + { + $this->load_extension($extension_name, $component); + } + else + { + $this->load_core_file($component); + } + } + else + { + foreach ($component as $lang_file) + { + $this->add_lang($lang_file, $extension_name); + } + } + } + + /** + * @param $key array|string The language key we want to know more about. Can be string or array. + * + * @return bool Returns whether the language key is set. + */ + public function is_set($key) + { + // Load common language files if they not loaded yet + if (!$this->common_language_files_loaded) + { + $this->load_common_language_files(); + } + + if (is_array($key)) + { + $lang = &$this->lang[array_shift($key)]; + + foreach ($key as $_key) + { + $lang = &$lang[$_key]; + } + } + else + { + $lang = &$this->lang[$key]; + } + + return isset($lang); + } + + /** + * Advanced language substitution + * + * Function to mimic sprintf() with the possibility of using phpBB's language system to substitute nullar/singular/plural forms. + * Params are the language key and the parameters to be substituted. + * This function/functionality is inspired by SHS` and Ashe. + * + * Example call: $user->lang('NUM_POSTS_IN_QUEUE', 1); + * + * If the first parameter is an array, the elements are used as keys and subkeys to get the language entry: + * Example: $user->lang(array('datetime', 'AGO'), 1) uses $user->lang['datetime']['AGO'] as language entry. + * + * @return string Return localized string or the language key if the translation is not available + */ + public function lang() + { + $args = func_get_args(); + $key = array_shift($args); + + return $this->lang_array($key, $args); + } + + /** + * Returns the raw value associated to a language key or the language key no translation is available. + * No parameter substitution is performed, can be a string or an array. + * + * @param string|array $key Language key + * + * @return array|string + */ + public function lang_raw($key) + { + // Load common language files if they not loaded yet + if (!$this->common_language_files_loaded) + { + $this->load_common_language_files(); + } + + if (is_array($key)) + { + $lang = &$this->lang[array_shift($key)]; + + foreach ($key as $_key) + { + $lang = &$lang[$_key]; + } + } + else + { + $lang = &$this->lang[$key]; + } + + // Return if language string does not exist + if (!isset($lang) || (!is_string($lang) && !is_array($lang))) + { + return $key; + } + + return $lang; + } + + /** + * Act like lang() but takes a key and an array of parameters instead of using variadic + * + * @param string|array $key Language key + * @param array $args Parameters + * + * @return string + */ + public function lang_array($key, $args = array()) + { + $lang = $this->lang_raw($key); + + if ($lang === $key) + { + return $key; + } + + // If the language entry is a string, we simply mimic sprintf() behaviour + if (is_string($lang)) + { + if (count($args) === 0) + { + return $lang; + } + + // Replace key with language entry and simply pass along... + return vsprintf($lang, $args); + } + else if (count($lang) == 0) + { + // If the language entry is an empty array, we just return the language key + return $key; + } + + // It is an array... now handle different nullar/singular/plural forms + $key_found = false; + + // We now get the first number passed and will select the key based upon this number + for ($i = 0, $num_args = count($args); $i < $num_args; $i++) + { + if (is_int($args[$i]) || is_float($args[$i])) + { + if ($args[$i] == 0 && isset($lang[0])) + { + // We allow each translation using plural forms to specify a version for the case of 0 things, + // so that "0 users" may be displayed as "No users". + $key_found = 0; + break; + } + else + { + $use_plural_form = $this->get_plural_form($args[$i]); + if (isset($lang[$use_plural_form])) + { + // The key we should use exists, so we use it. + $key_found = $use_plural_form; + } + else + { + // If the key we need to use does not exist, we fall back to the previous one. + $numbers = array_keys($lang); + + foreach ($numbers as $num) + { + if ($num > $use_plural_form) + { + break; + } + + $key_found = $num; + } + } + break; + } + } + } + + // Ok, let's check if the key was found, else use the last entry (because it is mostly the plural form) + if ($key_found === false) + { + $numbers = array_keys($lang); + $key_found = end($numbers); + } + + // Use the language string we determined and pass it to sprintf() + return vsprintf($lang[$key_found], $args); + } + + /** + * Loads common language files + */ + protected function load_common_language_files() + { + if (!$this->common_language_files_loaded) + { + foreach ($this->common_language_files as $lang_file) + { + $this->load_core_file($lang_file); + } + + $this->common_language_files_loaded = true; + } + } + + /** + * Determine which plural form we should use. + * + * For some languages this is not as simple as for English. + * + * @param int|float $number The number we want to get the plural case for. Float numbers are floored. + * @param int|bool $force_rule False to use the plural rule of the language package + * or an integer to force a certain plural rule + * + * @return int The plural-case we need to use for the number plural-rule combination + * + * @throws \phpbb\language\exception\invalid_plural_rule_exception When $force_rule has an invalid value + */ + public function get_plural_form($number, $force_rule = false) + { + $number = (int) $number; + $plural_rule = ($force_rule !== false) ? $force_rule : ((isset($this->lang['PLURAL_RULE'])) ? $this->lang['PLURAL_RULE'] : 1); + + if ($plural_rule > 15 || $plural_rule < 0) + { + throw new invalid_plural_rule_exception('INVALID_PLURAL_RULE', array( + 'plural_rule' => $plural_rule, + )); + } + + /** + * The following plural rules are based on a list published by the Mozilla Developer Network + * https://developer.mozilla.org/en/Localization_and_Plurals + */ + switch ($plural_rule) + { + case 0: + /** + * Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao + * 1 - everything: 0, 1, 2, ... + */ + return 1; + + case 1: + /** + * Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish), Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque), Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan) + * 1 - 1 + * 2 - everything else: 0, 2, 3, ... + */ + return ($number === 1) ? 1 : 2; + + case 2: + /** + * Families: Romanic (French, Brazilian Portuguese) + * 1 - 0, 1 + * 2 - everything else: 2, 3, ... + */ + return (($number === 0) || ($number === 1)) ? 1 : 2; + + case 3: + /** + * Families: Baltic (Latvian) + * 1 - 0 + * 2 - ends in 1, not 11: 1, 21, ... 101, 121, ... + * 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ... + */ + return ($number === 0) ? 1 : ((($number % 10 === 1) && ($number % 100 != 11)) ? 2 : 3); + + case 4: + /** + * Families: Celtic (Scottish Gaelic) + * 1 - is 1 or 11: 1, 11 + * 2 - is 2 or 12: 2, 12 + * 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19 + * 4 - everything else: 0, 20, 21, ... + */ + return ($number === 1 || $number === 11) ? 1 : (($number === 2 || $number === 12) ? 2 : (($number >= 3 && $number <= 19) ? 3 : 4)); + + case 5: + /** + * Families: Romanic (Romanian) + * 1 - 1 + * 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ... + * 3 - everything else: 20, 21, ... + */ + return ($number === 1) ? 1 : ((($number === 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 2 : 3); + + case 6: + /** + * Families: Baltic (Lithuanian) + * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... + * 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ... + * 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ... + */ + return (($number % 10 === 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 < 2) || (($number % 100 >= 10) && ($number % 100 < 20))) ? 2 : 3); + + case 7: + /** + * Families: Slavic (Croatian, Serbian, Russian, Ukrainian) + * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ... + * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... + * 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ... + */ + return (($number % 10 === 1) && ($number % 100 != 11)) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 2 : 3); + + case 8: + /** + * Families: Slavic (Slovak, Czech) + * 1 - 1 + * 2 - 2, 3, 4 + * 3 - everything else: 0, 5, 6, 7, ... + */ + return ($number === 1) ? 1 : ((($number >= 2) && ($number <= 4)) ? 2 : 3); + + case 9: + /** + * Families: Slavic (Polish) + * 1 - 1 + * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ... + * 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ... + */ + return ($number === 1) ? 1 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 2 : 3); + + case 10: + /** + * Families: Slavic (Slovenian, Sorbian) + * 1 - ends in 01: 1, 101, 201, ... + * 2 - ends in 02: 2, 102, 202, ... + * 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ... + * 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ... + */ + return ($number % 100 === 1) ? 1 : (($number % 100 === 2) ? 2 : ((($number % 100 === 3) || ($number % 100 === 4)) ? 3 : 4)); + + case 11: + /** + * Families: Celtic (Irish Gaeilge) + * 1 - 1 + * 2 - 2 + * 3 - is 3-6: 3, 4, 5, 6 + * 4 - is 7-10: 7, 8, 9, 10 + * 5 - everything else: 0, 11, 12, ... + */ + return ($number === 1) ? 1 : (($number === 2) ? 2 : (($number >= 3 && $number <= 6) ? 3 : (($number >= 7 && $number <= 10) ? 4 : 5))); + + case 12: + /** + * Families: Semitic (Arabic) + * 1 - 1 + * 2 - 2 + * 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ... + * 4 - ends in 11-99: 11, ... 99, 111, 112, ... + * 5 - everything else: 100, 101, 102, 200, 201, 202, ... + * 6 - 0 + */ + return ($number === 1) ? 1 : (($number === 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : (($number != 0) ? 5 : 6)))); + + case 13: + /** + * Families: Semitic (Maltese) + * 1 - 1 + * 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ... + * 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ... + * 4 - everything else: 20, 21, ... + */ + return ($number === 1) ? 1 : ((($number === 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 2 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 3 : 4)); + + case 14: + /** + * Families: Slavic (Macedonian) + * 1 - ends in 1: 1, 11, 21, ... + * 2 - ends in 2: 2, 12, 22, ... + * 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ... + */ + return ($number % 10 === 1) ? 1 : (($number % 10 === 2) ? 2 : 3); + + case 15: + /** + * Families: Icelandic + * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ... + * 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ... + */ + return (($number % 10 === 1) && ($number % 100 != 11)) ? 1 : 2; + } + } + + /** + * Returns the ISO code of the used language + * + * @return string The ISO code of the currently used language + */ + public function get_used_language() + { + return $this->language_fallback[0]; + } + + /** + * Returns language fallback data + * + * @param bool $reload Whether or not to reload language files + * + * @return array + */ + protected function set_fallback_array($reload = false) + { + $fallback_array = array(); + + if ($this->user_language) + { + $fallback_array[] = $this->user_language; + } + + if ($this->default_language) + { + $fallback_array[] = $this->default_language; + } + + $fallback_array[] = self::FALLBACK_LANGUAGE; + + $this->language_fallback = $fallback_array; + + if ($reload) + { + $this->reload_language_files(); + } + } + + /** + * Load core language file + * + * @param string $component Name of the component to load + */ + protected function load_core_file($component) + { + // Check if the component is already loaded + if (isset($this->loaded_language_sets['core'][$component])) + { + return; + } + + $this->loader->load($component, $this->language_fallback, $this->lang); + $this->loaded_language_sets['core'][$component] = true; + } + + /** + * Load extension language file + * + * @param string $extension_name Name of the extension to load language from + * @param string $component Name of the component to load + */ + protected function load_extension($extension_name, $component) + { + // Check if the component is already loaded + if (isset($this->loaded_language_sets['ext'][$extension_name][$component])) + { + return; + } + + $this->loader->load_extension($extension_name, $component, $this->language_fallback, $this->lang); + $this->loaded_language_sets['ext'][$extension_name][$component] = true; + } + + /** + * Reload language files + */ + protected function reload_language_files() + { + $loaded_files = $this->loaded_language_sets; + $this->loaded_language_sets = array( + 'core' => array(), + 'ext' => array(), + ); + + // Reload core files + foreach ($loaded_files['core'] as $component => $value) + { + $this->load_core_file($component); + } + + // Reload extension files + foreach ($loaded_files['ext'] as $ext_name => $ext_info) + { + foreach ($ext_info as $ext_component => $value) + { + $this->load_extension($ext_name, $ext_component); + } + } + } +} diff --git a/phpbb/language/language_file_helper.php b/phpbb/language/language_file_helper.php new file mode 100644 index 0000000..85de034 --- /dev/null +++ b/phpbb/language/language_file_helper.php @@ -0,0 +1,72 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\language; + +use Symfony\Component\Finder\Finder; + +/** + * Helper class for language file related functions + */ +class language_file_helper +{ + /** + * @var string Path to phpBB's root + */ + protected $phpbb_root_path; + + /** + * Constructor + * + * @param string $phpbb_root_path Path to phpBB's root + */ + public function __construct($phpbb_root_path) + { + $this->phpbb_root_path = $phpbb_root_path; + } + + /** + * Returns available languages + * + * @return array + */ + public function get_available_languages() + { + // Find available language packages + $finder = new Finder(); + $finder->files() + ->name('iso.txt') + ->depth('== 1') + ->followLinks() + ->in($this->phpbb_root_path . 'language'); + + $available_languages = array(); + foreach ($finder as $file) + { + $path = $file->getRelativePath(); + $info = explode("\n", $file->getContents()); + + $available_languages[] = array( + // Get the name of the directory containing iso.txt + 'iso' => $path, + + // Recover data from file + 'name' => trim($info[0]), + 'local_name' => trim($info[1]), + 'author' => trim($info[2]) + ); + } + + return $available_languages; + } +} diff --git a/phpbb/language/language_file_loader.php b/phpbb/language/language_file_loader.php new file mode 100644 index 0000000..b6816af --- /dev/null +++ b/phpbb/language/language_file_loader.php @@ -0,0 +1,206 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\language; + +use \phpbb\language\exception\language_file_not_found; + +/** + * Language file loader + */ +class language_file_loader +{ + /** + * @var string Path to phpBB's root + */ + protected $phpbb_root_path; + + /** + * @var string Extension of PHP files + */ + protected $php_ext; + + /** + * @var \phpbb\extension\manager Extension manager + */ + protected $extension_manager; + + /** + * Constructor + * + * @param string $phpbb_root_path Path to phpBB's root + * @param string $php_ext Extension of PHP files + */ + public function __construct($phpbb_root_path, $php_ext) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->extension_manager = null; + } + + /** + * Extension manager setter + * + * @param \phpbb\extension\manager $extension_manager Extension manager + */ + public function set_extension_manager(\phpbb\extension\manager $extension_manager) + { + $this->extension_manager = $extension_manager; + } + + /** + * Loads language array for the given component + * + * @param string $component Name of the language component + * @param string|array $locale ISO code of the language to load, or array of ISO codes if you want to + * specify additional language fallback steps + * @param array $lang Array reference containing language strings + */ + public function load($component, $locale, &$lang) + { + $locale = (array) $locale; + + // Determine path to language directory + $path = $this->phpbb_root_path . 'language/'; + + $this->load_file($path, $component, $locale, $lang); + } + + /** + * Loads language array for the given extension component + * + * @param string $extension Name of the extension + * @param string $component Name of the language component + * @param string|array $locale ISO code of the language to load, or array of ISO codes if you want to + * specify additional language fallback steps + * @param array $lang Array reference containing language strings + */ + public function load_extension($extension, $component, $locale, &$lang) + { + // Check if extension manager was loaded + if ($this->extension_manager === null) + { + // If not, let's return + return; + } + + $locale = (array) $locale; + + // Determine path to language directory + $path = $this->extension_manager->get_extension_path($extension, true) . 'language/'; + + $this->load_file($path, $component, $locale, $lang); + } + + /** + * Prepares language file loading + * + * @param string $path Path to search for file in + * @param string $component Name of the language component + * @param array $locale Array containing language fallback options + * @param array $lang Array reference of language strings + */ + protected function load_file($path, $component, $locale, &$lang) + { + // This is BC stuff and not the best idea as it makes language fallback + // implementation quite hard like below. + if (strpos($this->phpbb_root_path . $component, $path) === 0) + { + // Filter out the path + $path_diff = str_replace($path, '', dirname($this->phpbb_root_path . $component)); + $language_file = basename($component, '.' . $this->php_ext); + $component = ''; + + // This step is needed to resolve language/en/subdir style $component + // $path already points to the language base directory so we need to eliminate + // the first directory from the path (that should be the language directory) + $path_diff_parts = explode('/', $path_diff); + + if (count($path_diff_parts) > 1) + { + array_shift($path_diff_parts); + $component = implode('/', $path_diff_parts) . '/'; + } + + $component .= $language_file; + } + + // Determine filename + $filename = $component . '.' . $this->php_ext; + + // Determine path to file + $file_path = $this->get_language_file_path($path, $filename, $locale); + + // Load language array + $this->load_language_file($file_path, $lang); + } + + /** + * This function implements language fallback logic + * + * @param string $path Path to language directory + * @param string $filename Filename to load language strings from + * + * @return string Relative path to language file + * + * @throws language_file_not_found When the path to the file cannot be resolved + */ + protected function get_language_file_path($path, $filename, $locales) + { + $language_file_path = $filename; + + // Language fallback logic + foreach ($locales as $locale) + { + $language_file_path = $path . $locale . '/' . $filename; + + // If we are in install, try to use the updated version, when available + if (defined('IN_INSTALL')) + { + $install_language_path = str_replace('language/', 'install/update/new/language/', $language_file_path); + if (file_exists($install_language_path)) + { + return $install_language_path; + } + } + + if (file_exists($language_file_path)) + { + return $language_file_path; + } + } + + // The language file is not exist + throw new language_file_not_found('Language file ' . $language_file_path . ' couldn\'t be opened.'); + } + + /** + * Loads language file + * + * @param string $path Path to language file to load + * @param array $lang Reference of the array of language strings + */ + protected function load_language_file($path, &$lang) + { + // Do not suppress error if in DEBUG mode + if (defined('DEBUG')) + { + include $path; + } + else + { + @include $path; + } + } +} diff --git a/phpbb/lock/db.php b/phpbb/lock/db.php new file mode 100644 index 0000000..85ba9a7 --- /dev/null +++ b/phpbb/lock/db.php @@ -0,0 +1,146 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\lock; + +/** +* Database locking class +*/ +class db +{ + /** + * Name of the config variable this lock uses + * @var string + */ + private $config_name; + + /** + * Unique identifier for this lock. + * + * @var string + */ + private $unique_id; + + /** + * Stores the state of this lock + * @var bool + */ + private $locked; + + /** + * The phpBB configuration + * @var \phpbb\config\config + */ + private $config; + + /** + * A database connection + * @var \phpbb\db\driver\driver_interface + */ + private $db; + + /** + * Creates a named released instance of the lock. + * + * You have to call acquire() to actually create the lock. + * + * @param string $config_name A config variable to be used for locking + * @param \phpbb\config\config $config The phpBB configuration + * @param \phpbb\db\driver\driver_interface $db A database connection + */ + public function __construct($config_name, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db) + { + $this->config_name = $config_name; + $this->config = $config; + $this->db = $db; + } + + /** + * Tries to acquire the lock by updating + * the configuration variable in the database. + * + * As a lock may only be held by one process at a time, lock + * acquisition may fail if another process is holding the lock + * or if another process obtained the lock but never released it. + * Locks are forcibly released after a timeout of 1 hour. + * + * @return bool true if lock was acquired + * false otherwise + */ + public function acquire() + { + if ($this->locked) + { + return false; + } + + if (!isset($this->config[$this->config_name])) + { + $this->config->set($this->config_name, '0', false); + } + $lock_value = $this->config[$this->config_name]; + + // make sure lock cannot be acquired by multiple processes + if ($lock_value) + { + // if the other process is running more than an hour already we have to assume it + // aborted without cleaning the lock + $time = explode(' ', $lock_value); + $time = $time[0]; + + if ($time + 3600 >= time()) + { + return false; + } + } + + $this->unique_id = time() . ' ' . unique_id(); + + // try to update the config value, if it was already modified by another + // process we failed to acquire the lock. + $this->locked = $this->config->set_atomic($this->config_name, $lock_value, $this->unique_id, false); + + return $this->locked; + } + + /** + * Does this process own the lock? + * + * @return bool true if lock is owned + * false otherwise + */ + public function owns_lock() + { + return (bool) $this->locked; + } + + /** + * Releases the lock. + * + * The lock must have been previously obtained, that is, acquire() call + * was issued and returned true. + * + * Note: Attempting to release a lock that is already released, + * that is, calling release() multiple times, is harmless. + * + * @return null + */ + public function release() + { + if ($this->locked) + { + $this->config->set_atomic($this->config_name, $this->unique_id, '0', false); + $this->locked = false; + } + } +} diff --git a/phpbb/lock/flock.php b/phpbb/lock/flock.php new file mode 100644 index 0000000..df88e14 --- /dev/null +++ b/phpbb/lock/flock.php @@ -0,0 +1,141 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\lock; + +/** +* File locking class +*/ +class flock +{ + /** + * Path to the file to which access is controlled + * + * @var string + */ + private $path; + + /** + * File pointer for the lock file + * @var string + */ + private $lock_fp; + + /** + * Constructor. + * + * You have to call acquire() to actually acquire the lock. + * + * @param string $path Path to the file to which access is controlled + */ + public function __construct($path) + { + $this->path = $path; + $this->lock_fp = null; + } + + /** + * Tries to acquire the lock. + * + * If the lock is already held by another process, this call will block + * until the other process releases the lock. If a lock is acquired and + * is not released before script finishes but the process continues to + * live (apache/fastcgi) then subsequent processes trying to acquire + * the same lock will be blocked forever. + * + * If the lock is already held by the same process via another instance + * of this class, this call will block forever. + * + * If flock function is disabled in php or fails to work, lock + * acquisition will fail and false will be returned. + * + * @return bool true if lock was acquired + * false otherwise + */ + public function acquire() + { + if ($this->lock_fp) + { + return false; + } + + // For systems that can't have two processes opening + // one file for writing simultaneously + if (file_exists($this->path . '.lock')) + { + $mode = 'rb'; + } + else + { + $mode = 'wb'; + } + + $this->lock_fp = @fopen($this->path . '.lock', $mode); + + if ($mode == 'wb') + { + if (!$this->lock_fp) + { + // Two processes may attempt to create lock file at the same time. + // Have the losing process try opening the lock file again for reading + // on the assumption that the winning process created it + $mode = 'rb'; + $this->lock_fp = @fopen($this->path . '.lock', $mode); + } + else + { + // Only need to set mode when the lock file is written + @chmod($this->path . '.lock', 0666); + } + } + + if ($this->lock_fp) + { + @flock($this->lock_fp, LOCK_EX); + } + + return (bool) $this->lock_fp; + } + + /** + * Does this process own the lock? + * + * @return bool true if lock is owned + * false otherwise + */ + public function owns_lock() + { + return (bool) $this->lock_fp; + } + + /** + * Releases the lock. + * + * The lock must have been previously obtained, that is, acquire() call + * was issued and returned true. + * + * Note: Attempting to release a lock that is already released, + * that is, calling release() multiple times, is harmless. + * + * @return null + */ + public function release() + { + if ($this->lock_fp) + { + @flock($this->lock_fp, LOCK_UN); + fclose($this->lock_fp); + $this->lock_fp = null; + } + } +} diff --git a/phpbb/log/dummy.php b/phpbb/log/dummy.php new file mode 100644 index 0000000..5c2d145 --- /dev/null +++ b/phpbb/log/dummy.php @@ -0,0 +1,81 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\log; + +/** +* Dummy logger +*/ +class dummy implements log_interface +{ + /** + * {@inheritdoc} + */ + public function is_enabled($type = '') + { + return false; + } + + /** + * {@inheritdoc} + */ + public function disable($type = '') + { + } + + /** + * {@inheritdoc} + */ + public function enable($type = '') + { + } + + /** + * {@inheritdoc} + */ + public function add($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = array()) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function delete($mode, $conditions = array()) + { + } + + /** + * {@inheritdoc} + */ + public function get_logs($mode, $count_logs = true, $limit = 0, $offset = 0, $forum_id = 0, $topic_id = 0, $user_id = 0, $log_time = 0, $sort_by = 'l.log_time DESC', $keywords = '') + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function get_log_count() + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function get_valid_offset() + { + return 0; + } +} diff --git a/phpbb/log/log.php b/phpbb/log/log.php new file mode 100644 index 0000000..5333fe2 --- /dev/null +++ b/phpbb/log/log.php @@ -0,0 +1,1001 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\log; + +/** +* This class is used to add entries into the log table. +*/ +class log implements \phpbb\log\log_interface +{ + /** + * If set, administrative user profile links will be returned and messages + * will not be censored. + * @var bool + */ + protected $is_in_admin; + + /** + * An array with the disabled log types. Logs of such types will not be + * added when add() is called. + * @var array + */ + protected $disabled_types; + + /** + * Keeps the total log count of the last call to get_logs() + * @var int + */ + protected $entry_count; + + /** + * Keeps the offset of the last valid page of the last call to get_logs() + * @var int + */ + protected $last_page_offset; + + /** + * The table we use to store our logs. + * @var string + */ + protected $log_table; + + /** + * Database object + * @var \phpbb\db\driver\driver + */ + protected $db; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Auth object + * @var \phpbb\auth\auth + */ + protected $auth; + + /** + * Event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * Admin root path + * @var string + */ + protected $phpbb_admin_path; + + /** + * PHP Extension + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db Database object + * @param \phpbb\user $user User object + * @param \phpbb\auth\auth $auth Auth object + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher + * @param string $phpbb_root_path Root path + * @param string $relative_admin_path Relative admin root path + * @param string $php_ext PHP Extension + * @param string $log_table Name of the table we use to store our logs + */ + public function __construct($db, $user, $auth, $phpbb_dispatcher, $phpbb_root_path, $relative_admin_path, $php_ext, $log_table) + { + $this->db = $db; + $this->user = $user; + $this->auth = $auth; + $this->dispatcher = $phpbb_dispatcher; + $this->phpbb_root_path = $phpbb_root_path; + $this->phpbb_admin_path = $this->phpbb_root_path . $relative_admin_path; + $this->php_ext = $php_ext; + $this->log_table = $log_table; + + /* + * IN_ADMIN is set after the session is created, + * so we need to take ADMIN_START into account as well, otherwise + * it will not work for the \phpbb\log\log object we create in common.php + */ + $this->set_is_admin((defined('ADMIN_START') && ADMIN_START) || (defined('IN_ADMIN') && IN_ADMIN)); + $this->enable(); + } + + /** + * Set is_in_admin in order to return administrative user profile links + * in get_logs() + * + * @param bool $is_in_admin Are we called from within the acp? + * @return null + */ + public function set_is_admin($is_in_admin) + { + $this->is_in_admin = (bool) $is_in_admin; + } + + /** + * Returns the is_in_admin option + * + * @return bool + */ + public function get_is_admin() + { + return $this->is_in_admin; + } + + /** + * Set table name + * + * @param string $log_table Can overwrite the table to use for the logs + * @return null + */ + public function set_log_table($log_table) + { + $this->log_table = $log_table; + } + + /** + * {@inheritDoc} + */ + public function is_enabled($type = '') + { + if ($type == '' || $type == 'all') + { + return !isset($this->disabled_types['all']); + } + return !isset($this->disabled_types[$type]) && !isset($this->disabled_types['all']); + } + + /** + * {@inheritDoc} + */ + public function disable($type = '') + { + if (is_array($type)) + { + foreach ($type as $disable_type) + { + $this->disable($disable_type); + } + return; + } + + // Empty string is an equivalent for all types. + if ($type == '') + { + $type = 'all'; + } + $this->disabled_types[$type] = true; + } + + /** + * {@inheritDoc} + */ + public function enable($type = '') + { + if (is_array($type)) + { + foreach ($type as $enable_type) + { + $this->enable($enable_type); + } + return; + } + + if ($type == '' || $type == 'all') + { + $this->disabled_types = array(); + return; + } + unset($this->disabled_types[$type]); + } + + /** + * {@inheritDoc} + */ + public function add($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = array()) + { + if (!$this->is_enabled($mode)) + { + return false; + } + + if ($log_time === false) + { + $log_time = time(); + } + + $sql_ary = array( + 'user_id' => !empty($user_id) ? $user_id : ANONYMOUS, + 'log_ip' => !empty($log_ip) ? $log_ip : '', + 'log_time' => $log_time, + 'log_operation' => $log_operation, + ); + + switch ($mode) + { + case 'admin': + $sql_ary += array( + 'log_type' => LOG_ADMIN, + 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', + ); + break; + + case 'mod': + $forum_id = isset($additional_data['forum_id']) ? (int) $additional_data['forum_id'] : 0; + unset($additional_data['forum_id']); + $topic_id = isset($additional_data['topic_id']) ? (int) $additional_data['topic_id'] : 0; + unset($additional_data['topic_id']); + $post_id = isset($additional_data['post_id']) ? (int) $additional_data['post_id'] : 0; + unset($additional_data['post_id']); + $sql_ary += array( + 'log_type' => LOG_MOD, + 'forum_id' => $forum_id, + 'topic_id' => $topic_id, + 'post_id' => $post_id, + 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', + ); + break; + + case 'user': + $reportee_id = (int) $additional_data['reportee_id']; + unset($additional_data['reportee_id']); + + $sql_ary += array( + 'log_type' => LOG_USERS, + 'reportee_id' => $reportee_id, + 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', + ); + break; + + case 'critical': + $sql_ary += array( + 'log_type' => LOG_CRITICAL, + 'log_data' => (!empty($additional_data)) ? serialize($additional_data) : '', + ); + break; + } + + /** + * Allows to modify log data before we add it to the database + * + * NOTE: if sql_ary does not contain a log_type value, the entry will + * not be stored in the database. So ensure to set it, if needed. + * + * @event core.add_log + * @var string mode Mode of the entry we log + * @var int user_id ID of the user who triggered the log + * @var string log_ip IP of the user who triggered the log + * @var string log_operation Language key of the log operation + * @var int log_time Timestamp, when the log was added + * @var array additional_data Array with additional log data + * @var array sql_ary Array with log data we insert into the + * database. If sql_ary[log_type] is not set, + * we won't add the entry to the database. + * @since 3.1.0-a1 + */ + $vars = array( + 'mode', + 'user_id', + 'log_ip', + 'log_operation', + 'log_time', + 'additional_data', + 'sql_ary', + ); + extract($this->dispatcher->trigger_event('core.add_log', compact($vars))); + + // We didn't find a log_type, so we don't save it in the database. + if (!isset($sql_ary['log_type'])) + { + return false; + } + + $this->db->sql_query('INSERT INTO ' . $this->log_table . ' ' . $this->db->sql_build_array('INSERT', $sql_ary)); + + return $this->db->sql_nextid(); + } + + /** + * {@inheritDoc} + */ + public function delete($mode, $conditions = array()) + { + switch ($mode) + { + case 'admin': + $log_type = LOG_ADMIN; + break; + + case 'mod': + $log_type = LOG_MOD; + break; + + case 'user': + $log_type = LOG_USERS; + break; + + case 'users': + $log_type = LOG_USERS; + break; + + case 'critical': + $log_type = LOG_CRITICAL; + break; + + default: + $log_type = false; + } + + /** + * Allows to modify log data before we delete it from the database + * + * NOTE: if sql_ary does not contain a log_type value, the entry will + * not be deleted in the database. So ensure to set it, if needed. + * + * @event core.delete_log + * @var string mode Mode of the entry we log + * @var string log_type Type ID of the log (should be different than false) + * @var array conditions An array of conditions, 3 different forms are accepted + * 1) => transformed into 'AND = ' (value should be an integer) + * 2) => array(, ) transformed into 'AND ' (values can't be an array) + * 3) => array('IN' => array()) transformed into 'AND IN ' + * A special field, keywords, can also be defined. In this case only the log entries that have the keywords in log_operation or log_data will be deleted. + * @since 3.1.0-b4 + */ + $vars = array( + 'mode', + 'log_type', + 'conditions', + ); + extract($this->dispatcher->trigger_event('core.delete_log', compact($vars))); + + if ($log_type === false) + { + return; + } + + $sql_where = 'WHERE log_type = ' . $log_type; + + if (isset($conditions['keywords'])) + { + $sql_where .= $this->generate_sql_keyword($conditions['keywords'], ''); + + unset($conditions['keywords']); + } + + foreach ($conditions as $field => $field_value) + { + $sql_where .= ' AND '; + + if (is_array($field_value) && count($field_value) == 2 && !is_array($field_value[1])) + { + $sql_where .= $field . ' ' . $field_value[0] . ' ' . $field_value[1]; + } + else if (is_array($field_value) && isset($field_value['IN']) && is_array($field_value['IN'])) + { + $sql_where .= $this->db->sql_in_set($field, $field_value['IN']); + } + else + { + $sql_where .= $field . ' = ' . $field_value; + } + } + + $sql = 'DELETE FROM ' . $this->log_table . " + $sql_where"; + $this->db->sql_query($sql); + + $this->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_CLEAR_' . strtoupper($mode)); + } + + /** + * {@inheritDoc} + */ + public function get_logs($mode, $count_logs = true, $limit = 0, $offset = 0, $forum_id = 0, $topic_id = 0, $user_id = 0, $log_time = 0, $sort_by = 'l.log_time DESC', $keywords = '') + { + $this->entry_count = 0; + $this->last_page_offset = $offset; + + $topic_id_list = $reportee_id_list = array(); + + $profile_url = ($this->get_is_admin() && $this->phpbb_admin_path) ? append_sid("{$this->phpbb_admin_path}index.{$this->php_ext}", 'i=users&mode=overview') : append_sid("{$this->phpbb_root_path}memberlist.{$this->php_ext}", 'mode=viewprofile'); + + switch ($mode) + { + case 'admin': + $log_type = LOG_ADMIN; + $sql_additional = ''; + break; + + case 'mod': + $log_type = LOG_MOD; + $sql_additional = ''; + + if ($topic_id) + { + $sql_additional = 'AND l.topic_id = ' . (int) $topic_id; + } + else if (is_array($forum_id)) + { + $sql_additional = 'AND ' . $this->db->sql_in_set('l.forum_id', array_map('intval', $forum_id)); + } + else if ($forum_id) + { + $sql_additional = 'AND l.forum_id = ' . (int) $forum_id; + } + break; + + case 'user': + $log_type = LOG_USERS; + $sql_additional = 'AND l.reportee_id = ' . (int) $user_id; + break; + + case 'users': + $log_type = LOG_USERS; + $sql_additional = ''; + break; + + case 'critical': + $log_type = LOG_CRITICAL; + $sql_additional = ''; + break; + + default: + $log_type = false; + $sql_additional = ''; + } + + /** + * Overwrite log type and limitations before we count and get the logs + * + * NOTE: if log_type is false, no entries will be returned. + * + * @event core.get_logs_modify_type + * @var string mode Mode of the entries we display + * @var bool count_logs Do we count all matching entries? + * @var int limit Limit the number of entries + * @var int offset Offset when fetching the entries + * @var mixed forum_id Limit entries to the forum_id, + * can also be an array of forum_ids + * @var int topic_id Limit entries to the topic_id + * @var int user_id Limit entries to the user_id + * @var int log_time Limit maximum age of log entries + * @var string sort_by SQL order option + * @var string keywords Will only return entries that have the + * keywords in log_operation or log_data + * @var string profile_url URL to the users profile + * @var int log_type Limit logs to a certain type. If log_type + * is false, no entries will be returned. + * @var string sql_additional Additional conditions for the entries, + * e.g.: 'AND l.forum_id = 1' + * @since 3.1.0-a1 + */ + $vars = array( + 'mode', + 'count_logs', + 'limit', + 'offset', + 'forum_id', + 'topic_id', + 'user_id', + 'log_time', + 'sort_by', + 'keywords', + 'profile_url', + 'log_type', + 'sql_additional', + ); + extract($this->dispatcher->trigger_event('core.get_logs_modify_type', compact($vars))); + + if ($log_type === false) + { + $this->last_page_offset = 0; + return array(); + } + + $sql_keywords = ''; + if (!empty($keywords)) + { + // Get the SQL condition for our keywords + $sql_keywords = $this->generate_sql_keyword($keywords); + } + + $get_logs_sql_ary = array( + 'SELECT' => 'l.*, u.username, u.username_clean, u.user_colour', + 'FROM' => array( + $this->log_table => 'l', + USERS_TABLE => 'u', + ), + 'WHERE' => 'l.log_type = ' . (int) $log_type . " + AND l.user_id = u.user_id + $sql_keywords + $sql_additional", + + 'ORDER_BY' => $sort_by, + ); + + if ($log_time) + { + $get_logs_sql_ary['WHERE'] = 'l.log_time >= ' . (int) $log_time . ' + AND ' . $get_logs_sql_ary['WHERE']; + } + + /** + * Modify the query to obtain the logs data + * + * @event core.get_logs_main_query_before + * @var array get_logs_sql_ary The array in the format of the query builder with the query + * to get the log count and the log list + * @var string mode Mode of the entries we display + * @var bool count_logs Do we count all matching entries? + * @var int limit Limit the number of entries + * @var int offset Offset when fetching the entries + * @var mixed forum_id Limit entries to the forum_id, + * can also be an array of forum_ids + * @var int topic_id Limit entries to the topic_id + * @var int user_id Limit entries to the user_id + * @var int log_time Limit maximum age of log entries + * @var string sort_by SQL order option + * @var string keywords Will only return entries that have the + * keywords in log_operation or log_data + * @var string profile_url URL to the users profile + * @var int log_type Limit logs to a certain type. If log_type + * is false, no entries will be returned. + * @var string sql_additional Additional conditions for the entries, + * e.g.: 'AND l.forum_id = 1' + * @since 3.1.5-RC1 + */ + $vars = array( + 'get_logs_sql_ary', + 'mode', + 'count_logs', + 'limit', + 'offset', + 'forum_id', + 'topic_id', + 'user_id', + 'log_time', + 'sort_by', + 'keywords', + 'profile_url', + 'log_type', + 'sql_additional', + ); + extract($this->dispatcher->trigger_event('core.get_logs_main_query_before', compact($vars))); + + if ($count_logs) + { + $count_logs_sql_ary = $get_logs_sql_ary; + + $count_logs_sql_ary['SELECT'] = 'COUNT(l.log_id) AS total_entries'; + unset($count_logs_sql_ary['ORDER_BY']); + + $sql = $this->db->sql_build_query('SELECT', $count_logs_sql_ary); + $result = $this->db->sql_query($sql); + $this->entry_count = (int) $this->db->sql_fetchfield('total_entries'); + $this->db->sql_freeresult($result); + + if ($this->entry_count == 0) + { + // Save the queries, because there are no logs to display + $this->last_page_offset = 0; + return array(); + } + + // Return the user to the last page that is valid + while ($this->last_page_offset >= $this->entry_count) + { + $this->last_page_offset = max(0, $this->last_page_offset - $limit); + } + } + + $sql = $this->db->sql_build_query('SELECT', $get_logs_sql_ary); + $result = $this->db->sql_query_limit($sql, $limit, $this->last_page_offset); + + $i = 0; + $log = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $row['forum_id'] = (int) $row['forum_id']; + if ($row['topic_id']) + { + $topic_id_list[] = (int) $row['topic_id']; + } + + if ($row['reportee_id']) + { + $reportee_id_list[] = (int) $row['reportee_id']; + } + + $log_entry_data = array( + 'id' => (int) $row['log_id'], + + 'reportee_id' => (int) $row['reportee_id'], + 'reportee_username' => '', + 'reportee_username_full'=> '', + + 'user_id' => (int) $row['user_id'], + 'username' => $row['username'], + 'username_full' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'], false, $profile_url), + + 'ip' => $row['log_ip'], + 'time' => (int) $row['log_time'], + 'forum_id' => (int) $row['forum_id'], + 'topic_id' => (int) $row['topic_id'], + 'post_id' => (int) $row['post_id'], + + 'viewforum' => ($row['forum_id'] && $this->auth->acl_get('f_read', $row['forum_id'])) ? append_sid("{$this->phpbb_root_path}viewforum.{$this->php_ext}", 'f=' . $row['forum_id']) : false, + 'action' => (isset($this->user->lang[$row['log_operation']])) ? $row['log_operation'] : '{' . ucfirst(str_replace('_', ' ', $row['log_operation'])) . '}', + ); + + /** + * Modify the entry's data before it is returned + * + * @event core.get_logs_modify_entry_data + * @var array row Entry data from the database + * @var array log_entry_data Entry's data which is returned + * @since 3.1.0-a1 + */ + $vars = array('row', 'log_entry_data'); + extract($this->dispatcher->trigger_event('core.get_logs_modify_entry_data', compact($vars))); + + $log[$i] = $log_entry_data; + + if (!empty($row['log_data'])) + { + $log_data_ary = unserialize($row['log_data']); + $log_data_ary = ($log_data_ary !== false) ? $log_data_ary : array(); + + if (isset($this->user->lang[$row['log_operation']])) + { + // Check if there are more occurrences of % than + // arguments, if there are we fill out the arguments + // array. It doesn't matter if we add more arguments than + // placeholders. + $num_args = 0; + if (!is_array($this->user->lang[$row['log_operation']])) + { + $num_args = substr_count($this->user->lang[$row['log_operation']], '%'); + } + else + { + foreach ($this->user->lang[$row['log_operation']] as $case => $plural_string) + { + $num_args = max($num_args, substr_count($plural_string, '%')); + } + } + + if (($num_args - count($log_data_ary)) > 0) + { + $log_data_ary = array_merge($log_data_ary, array_fill(0, $num_args - count($log_data_ary), '')); + } + + $lang_arguments = array_merge(array($log[$i]['action']), $log_data_ary); + $log[$i]['action'] = call_user_func_array(array($this->user, 'lang'), $lang_arguments); + + // If within the admin panel we do not censor text out + if ($this->get_is_admin()) + { + $log[$i]['action'] = bbcode_nl2br($log[$i]['action']); + } + else + { + $log[$i]['action'] = bbcode_nl2br(censor_text($log[$i]['action'])); + } + } + else if (!empty($log_data_ary)) + { + $log[$i]['action'] .= '
' . implode('', $log_data_ary); + } + + /* Apply make_clickable... has to be seen if it is for good. :/ + // Seems to be not for the moment, reconsider later... + $log[$i]['action'] = make_clickable($log[$i]['action']); + */ + } + else + { + $log[$i]['action'] = $this->user->lang($log[$i]['action']); + } + + $i++; + } + $this->db->sql_freeresult($result); + + /** + * Get some additional data after we got all log entries + * + * @event core.get_logs_get_additional_data + * @var array log Array with all our log entries + * @var array topic_id_list Array of topic ids, for which we + * get the permission data + * @var array reportee_id_list Array of additional user IDs we + * get the username strings for + * @since 3.1.0-a1 + */ + $vars = array('log', 'topic_id_list', 'reportee_id_list'); + extract($this->dispatcher->trigger_event('core.get_logs_get_additional_data', compact($vars))); + + if (count($topic_id_list)) + { + $topic_auth = $this->get_topic_auth($topic_id_list); + + foreach ($log as $key => $row) + { + $log[$key]['viewtopic'] = (isset($topic_auth['f_read'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}viewtopic.{$this->php_ext}", 'f=' . $topic_auth['f_read'][$row['topic_id']] . '&t=' . $row['topic_id']) : false; + $log[$key]['viewpost'] = (isset($topic_auth['f_read'][$row['topic_id']]) && $row['post_id']) ? append_sid("{$this->phpbb_root_path}viewtopic.{$this->php_ext}", 'f=' . $topic_auth['f_read'][$row['topic_id']] . '&t=' . $row['topic_id'] . '&p=' . $row['post_id'] . '#p' . $row['post_id']) : false; + $log[$key]['viewlogs'] = (isset($topic_auth['m_'][$row['topic_id']])) ? append_sid("{$this->phpbb_root_path}mcp.{$this->php_ext}", 'i=logs&mode=topic_logs&t=' . $row['topic_id'], true, $this->user->session_id) : false; + } + } + + if (count($reportee_id_list)) + { + $reportee_data_list = $this->get_reportee_data($reportee_id_list); + + foreach ($log as $key => $row) + { + if (!isset($reportee_data_list[$row['reportee_id']])) + { + continue; + } + + $log[$key]['reportee_username'] = $reportee_data_list[$row['reportee_id']]['username']; + $log[$key]['reportee_username_full'] = get_username_string('full', $row['reportee_id'], $reportee_data_list[$row['reportee_id']]['username'], $reportee_data_list[$row['reportee_id']]['user_colour'], false, $profile_url); + } + } + + /** + * Allow modifying or execute extra final filter on log entries + * + * @event core.get_logs_after + * @var array log Array with all our log entries + * @var array topic_id_list Array of topic ids, for which we + * get the permission data + * @var array reportee_id_list Array of additional user IDs we + * get the username strings for + * @var string mode Mode of the entries we display + * @var bool count_logs Do we count all matching entries? + * @var int limit Limit the number of entries + * @var int offset Offset when fetching the entries + * @var mixed forum_id Limit entries to the forum_id, + * can also be an array of forum_ids + * @var int topic_id Limit entries to the topic_id + * @var int user_id Limit entries to the user_id + * @var int log_time Limit maximum age of log entries + * @var string sort_by SQL order option + * @var string keywords Will only return entries that have the + * keywords in log_operation or log_data + * @var string profile_url URL to the users profile + * @var int log_type The type of logs it was filtered + * @since 3.1.3-RC1 + */ + $vars = array( + 'log', + 'topic_id_list', + 'reportee_id_list', + 'mode', + 'count_logs', + 'limit', + 'offset', + 'forum_id', + 'topic_id', + 'user_id', + 'log_time', + 'sort_by', + 'keywords', + 'profile_url', + 'log_type', + ); + extract($this->dispatcher->trigger_event('core.get_logs_after', compact($vars))); + + return $log; + } + + /** + * Generates a sql condition for the specified keywords + * + * @param string $keywords The keywords the user specified to search for + * @param string $table_alias The alias of the logs' table ('l.' by default) + * @param string $statement_operator The operator used to prefix the statement ('AND' by default) + * + * @return string Returns the SQL condition searching for the keywords + */ + protected function generate_sql_keyword($keywords, $table_alias = 'l.', $statement_operator = 'AND') + { + // Use no preg_quote for $keywords because this would lead to sole + // backslashes being added. We also use an OR connection here for + // spaces and the | string. Currently, regex is not supported for + // searching (but may come later). + $keywords = preg_split('#[\s|]+#u', utf8_strtolower($keywords), 0, PREG_SPLIT_NO_EMPTY); + $sql_keywords = ''; + + if (!empty($keywords)) + { + $keywords_pattern = array(); + + // Build pattern and keywords... + for ($i = 0, $num_keywords = count($keywords); $i < $num_keywords; $i++) + { + $keywords_pattern[] = preg_quote($keywords[$i], '#'); + $keywords[$i] = $this->db->sql_like_expression($this->db->get_any_char() . $keywords[$i] . $this->db->get_any_char()); + } + + $keywords_pattern = '#' . implode('|', $keywords_pattern) . '#ui'; + + $operations = array(); + foreach ($this->user->lang as $key => $value) + { + if (substr($key, 0, 4) == 'LOG_') + { + if (is_array($value)) + { + foreach ($value as $plural_value) + { + if (preg_match($keywords_pattern, $plural_value)) + { + $operations[] = $key; + break; + } + } + } + else if (preg_match($keywords_pattern, $value)) + { + $operations[] = $key; + } + } + } + + $sql_keywords = ' ' . $statement_operator . ' ('; + if (!empty($operations)) + { + $sql_keywords .= $this->db->sql_in_set($table_alias . 'log_operation', $operations) . ' OR '; + } + $sql_lower = $this->db->sql_lower_text($table_alias . 'log_data'); + $sql_keywords .= " $sql_lower " . implode(" OR $sql_lower ", $keywords) . ')'; + } + + return $sql_keywords; + } + + /** + * Determine whether the user is allowed to read and/or moderate the forum of the topic + * + * @param array $topic_ids Array with the topic ids + * + * @return array Returns an array with two keys 'm_' and 'read_f' which are also an array of topic_id => forum_id sets when the permissions are given. Sample: + * array( + * 'permission' => array( + * topic_id => forum_id + * ), + * ), + */ + protected function get_topic_auth(array $topic_ids) + { + $forum_auth = array('f_read' => array(), 'm_' => array()); + $topic_ids = array_unique($topic_ids); + + $sql_ary = array( + 'SELECT' => 'topic_id, forum_id', + 'FROM' => array( + TOPICS_TABLE => 't', + ), + 'WHERE' => $this->db->sql_in_set('topic_id', array_map('intval', $topic_ids)), + ); + + /** + * Allow modifying SQL query before topic data is retrieved. + * + * @event core.phpbb_log_get_topic_auth_sql_before + * @var array topic_ids Array with unique topic IDs + * @var array sql_ary SQL array + * @since 3.1.11-RC1 + */ + $vars = array( + 'topic_ids', + 'sql_ary', + ); + extract($this->dispatcher->trigger_event('core.phpbb_log_get_topic_auth_sql_before', compact($vars))); + + $sql = $this->db->sql_build_query('SELECT', $sql_ary); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $row['topic_id'] = (int) $row['topic_id']; + $row['forum_id'] = (int) $row['forum_id']; + + if ($this->auth->acl_get('f_read', $row['forum_id'])) + { + $forum_auth['f_read'][$row['topic_id']] = $row['forum_id']; + } + + /** + * Allow modifying SQL query after topic data is retrieved (inside loop). + * + * @event core.phpbb_log_get_topic_auth_sql_after + * @var array forum_auth Forum permissions + * @var array row One row of data from SQL query + * @since 3.2.2-RC1 + */ + $vars = array( + 'forum_auth', + 'row', + ); + extract($this->dispatcher->trigger_event('core.phpbb_log_get_topic_auth_sql_after', compact($vars))); + + if ($this->auth->acl_gets('a_', 'm_', $row['forum_id'])) + { + $forum_auth['m_'][$row['topic_id']] = $row['forum_id']; + } + } + $this->db->sql_freeresult($result); + + return $forum_auth; + } + + /** + * Get the data for all reportee from the database + * + * @param array $reportee_ids Array with the user ids of the reportees + * + * @return array Returns an array with the reportee data + */ + protected function get_reportee_data(array $reportee_ids) + { + $reportee_ids = array_unique($reportee_ids); + $reportee_data_list = array(); + + $sql = 'SELECT user_id, username, user_colour + FROM ' . USERS_TABLE . ' + WHERE ' . $this->db->sql_in_set('user_id', $reportee_ids); + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $reportee_data_list[$row['user_id']] = $row; + } + $this->db->sql_freeresult($result); + + return $reportee_data_list; + } + + /** + * {@inheritDoc} + */ + public function get_log_count() + { + return ($this->entry_count) ? $this->entry_count : 0; + } + + /** + * {@inheritDoc} + */ + public function get_valid_offset() + { + return ($this->last_page_offset) ? $this->last_page_offset : 0; + } +} diff --git a/phpbb/log/log_interface.php b/phpbb/log/log_interface.php new file mode 100644 index 0000000..86286e6 --- /dev/null +++ b/phpbb/log/log_interface.php @@ -0,0 +1,114 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\log; + +/** +* The interface for the log-system. +*/ +interface log_interface +{ + /** + * This function returns the state of the log system. + * + * @param string $type The log type we want to check. Empty to get + * global log status. + * + * @return bool True if log for the type is enabled + */ + public function is_enabled($type = ''); + + /** + * Disable log + * + * This function allows disabling the log system or parts of it, for this + * page call. When add() is called and the type is disabled, the log will + * not be added to the database. + * + * @param mixed $type The log type we want to disable. Empty to + * disable all logs. Can also be an array of types. + * + * @return null + */ + public function disable($type = ''); + + /** + * Enable log + * + * This function allows re-enabling the log system. + * + * @param mixed $type The log type we want to enable. Empty to + * enable all logs. Can also be an array of types. + * + * @return null + */ + public function enable($type = ''); + + /** + * Adds a log entry to the database + * + * @param string $mode The mode defines which log_type is used and from which log the entry is retrieved + * @param int $user_id User ID of the user + * @param string $log_ip IP address of the user + * @param string $log_operation Name of the operation + * @param int|bool $log_time Timestamp when the log entry was added. If false, time() will be used + * @param array $additional_data More arguments can be added, depending on the log_type + * + * @return int|bool Returns the log_id, if the entry was added to the database, false otherwise. + */ + public function add($mode, $user_id, $log_ip, $log_operation, $log_time = false, $additional_data = array()); + + /** + * Delete entries in the logs + * + * @param string $mode The mode defines which log_type is used and from which log the entries are deleted + * @param array $conditions An array of conditions, 3 different forms are accepted + * 1) => transformed into 'AND = ' (value should be an integer) + * 2) => array(, ) transformed into 'AND ' (values can't be an array) + * 3) => array('IN' => array()) transformed into 'AND IN ' + * A special field, keywords, can also be defined. In this case only the log entries that have the keywords in log_operation or log_data will be deleted. + */ + public function delete($mode, $conditions = array()); + + /** + * Grab the logs from the database + * + * @param string $mode The mode defines which log_type is used and ifrom which log the entry is retrieved + * @param bool $count_logs Shall we count all matching log entries? + * @param int $limit Limit the number of entries that are returned + * @param int $offset Offset when fetching the log entries, f.e. when paginating + * @param mixed $forum_id Restrict the log entries to the given forum_id (can also be an array of forum_ids) + * @param int $topic_id Restrict the log entries to the given topic_id + * @param int $user_id Restrict the log entries to the given user_id + * @param int $log_time Only get log entries newer than the given timestamp + * @param string $sort_by SQL order option, e.g. 'l.log_time DESC' + * @param string $keywords Will only return log entries that have the keywords in log_operation or log_data + * + * @return array The result array with the logs + */ + public function get_logs($mode, $count_logs = true, $limit = 0, $offset = 0, $forum_id = 0, $topic_id = 0, $user_id = 0, $log_time = 0, $sort_by = 'l.log_time DESC', $keywords = ''); + + /** + * Get total log count + * + * @return int Returns the number of matching logs from the last call to get_logs() + */ + public function get_log_count(); + + /** + * Get offset of the last valid page + * + * @return int Returns the offset of the last valid page from the last call to get_logs() + */ + public function get_valid_offset(); +} diff --git a/phpbb/message/admin_form.php b/phpbb/message/admin_form.php new file mode 100644 index 0000000..ae1c1d8 --- /dev/null +++ b/phpbb/message/admin_form.php @@ -0,0 +1,220 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\message; + +/** +* Class admin_form +* Displays a message to the user and allows him to send an email +*/ +class admin_form extends form +{ + /** @var \phpbb\config\db_text */ + protected $config_text; + + /** @var \phpbb\event\dispatcher_interface */ + protected $dispatcher; + + /** @var string */ + protected $subject; + /** @var string */ + protected $sender_name; + /** @var string */ + protected $sender_address; + + /** + * Construct + * + * @param \phpbb\auth\auth $auth + * @param \phpbb\config\config $config + * @param \phpbb\config\db_text $config_text + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\user $user + * @param \phpbb\event\dispatcher_interface $dispatcher + * @param string $phpbb_root_path + * @param string $phpEx + */ + public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\config\db_text $config_text, \phpbb\db\driver\driver_interface $db, \phpbb\user $user, \phpbb\event\dispatcher_interface $dispatcher, $phpbb_root_path, $phpEx) + { + parent::__construct($auth, $config, $db, $user, $phpbb_root_path, $phpEx); + $this->config_text = $config_text; + $this->dispatcher = $dispatcher; + } + + /** + * {inheritDoc} + */ + public function check_allow() + { + $error = parent::check_allow(); + if ($error) + { + return $error; + } + + if (!$this->config['contact_admin_form_enable']) + { + return 'NO_CONTACT_PAGE'; + } + + return false; + } + + /** + * {inheritDoc} + */ + public function bind(\phpbb\request\request_interface $request) + { + parent::bind($request); + + $this->subject = $request->variable('subject', '', true); + $this->sender_address = $request->variable('email', ''); + $this->sender_name = $request->variable('name', '', true); + } + + /** + * {inheritDoc} + */ + public function submit(\messenger $messenger) + { + if (!$this->subject) + { + $this->errors[] = $this->user->lang['EMPTY_SUBJECT_EMAIL']; + } + if (!$this->body) + { + $this->errors[] = $this->user->lang['EMPTY_MESSAGE_EMAIL']; + } + + $subject = $this->subject; + $body = $this->body; + $errors = $this->errors; + + /** + * You can use this event to modify subject and/or body and add new errors. + * + * @event core.message_admin_form_submit_before + * @var string subject Message subject + * @var string body Message body + * @var array errors Form errors + * @since 3.2.6-RC1 + */ + $vars = [ + 'subject', + 'body', + 'errors', + ]; + extract($this->dispatcher->trigger_event('core.message_admin_form_submit_before', compact($vars))); + $this->subject = $subject; + $this->body = $body; + $this->errors = $errors; + + if ($this->user->data['is_registered']) + { + $this->message->set_sender_from_user($this->user); + $this->sender_name = $this->user->data['username']; + $this->sender_address = $this->user->data['user_email']; + } + else + { + if (!$this->sender_name) + { + $this->errors[] = $this->user->lang['EMPTY_SENDER_NAME']; + } + + if (!function_exists('validate_data')) + { + require($this->phpbb_root_path . 'includes/functions_user.' . $this->phpEx); + } + + $validate_array = validate_data( + array( + 'email' => $this->sender_address, + ), + array( + 'email' => array( + array('string', false, 6, 60), + array('email'), + ), + ) + ); + + foreach ($validate_array as $error) + { + $this->errors[] = $this->user->lang[$error]; + } + + $this->message->set_sender($this->user->ip, $this->sender_name, $this->sender_address, $this->user->lang_name); + $this->message->set_sender_notify_type(NOTIFY_EMAIL); + } + + $this->message->set_template('contact_admin'); + $this->message->set_subject($this->subject); + $this->message->set_body($this->body); + $this->message->add_recipient( + $this->user->lang['ADMINISTRATOR'], + $this->config['board_contact'], + $this->config['default_lang'], + NOTIFY_EMAIL + ); + + $this->message->set_template_vars(array( + 'FROM_EMAIL_ADDRESS' => $this->sender_address, + 'FROM_IP_ADDRESS' => $this->user->ip, + 'S_IS_REGISTERED' => $this->user->data['is_registered'], + + 'U_FROM_PROFILE' => generate_board_url() . '/memberlist.' . $this->phpEx . '?mode=viewprofile&u=' . $this->user->data['user_id'], + )); + + parent::submit($messenger); + } + + /** + * {inheritDoc} + */ + public function render(\phpbb\template\template $template) + { + $l_admin_info = $this->config_text->get('contact_admin_info'); + if ($l_admin_info) + { + $contact_admin_data = $this->config_text->get_array(array( + 'contact_admin_info', + 'contact_admin_info_uid', + 'contact_admin_info_bitfield', + 'contact_admin_info_flags', + )); + + $l_admin_info = generate_text_for_display( + $contact_admin_data['contact_admin_info'], + $contact_admin_data['contact_admin_info_uid'], + $contact_admin_data['contact_admin_info_bitfield'], + $contact_admin_data['contact_admin_info_flags'] + ); + } + + $template->assign_vars(array( + 'S_CONTACT_ADMIN' => true, + 'S_CONTACT_FORM' => $this->config['contact_admin_form_enable'], + 'S_IS_REGISTERED' => $this->user->data['is_registered'], + 'S_POST_ACTION' => append_sid($this->phpbb_root_path . 'memberlist.' . $this->phpEx, 'mode=contactadmin'), + + 'CONTACT_INFO' => $l_admin_info, + 'MESSAGE' => $this->body, + 'SUBJECT' => $this->subject, + 'NAME' => $this->sender_name, + 'EMAIL' => $this->sender_address, + )); + + parent::render($template); + } +} diff --git a/phpbb/message/form.php b/phpbb/message/form.php new file mode 100644 index 0000000..63bada9 --- /dev/null +++ b/phpbb/message/form.php @@ -0,0 +1,175 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\message; + +/** +* Abstract class form +*/ +abstract class form +{ + /** @var \phpbb\auth\auth */ + protected $auth; + /** @var \phpbb\config\config */ + protected $config; + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + /** @var \phpbb\message\message */ + protected $message; + /** @var \phpbb\user */ + protected $user; + + /** @var string */ + protected $phpbb_root_path; + /** @var string */ + protected $phpEx; + + /** @var array */ + protected $errors = array(); + /** @var bool */ + protected $cc_sender; + /** @var string */ + protected $body; + + /** + * Construct + * + * @param \phpbb\auth\auth $auth + * @param \phpbb\config\config $config + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\user $user + * @param string $phpbb_root_path + * @param string $phpEx + */ + public function __construct(\phpbb\auth\auth $auth, \phpbb\config\config $config, \phpbb\db\driver\driver_interface $db, \phpbb\user $user, $phpbb_root_path, $phpEx) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->phpEx = $phpEx; + $this->user = $user; + $this->auth = $auth; + $this->config = $config; + $this->db = $db; + + $this->message = new message($config['server_name']); + $this->message->set_sender_from_user($this->user); + } + + /** + * Returns the title for the email form page + * + * @return string + */ + public function get_page_title() + { + return $this->user->lang['SEND_EMAIL']; + } + + /** + * Returns the file name of the form template + * + * @return string + */ + public function get_template_file() + { + return 'memberlist_email.html'; + } + + /** + * Checks whether the user is allowed to use the form + * + * @return false|string Error string if not allowed, false otherwise + */ + public function check_allow() + { + if (!$this->config['email_enable']) + { + return 'EMAIL_DISABLED'; + } + + if (time() - $this->user->data['user_emailtime'] < $this->config['flood_interval']) + { + return 'FLOOD_EMAIL_LIMIT'; + } + + return false; + } + + /** + * Get the return link after the message has been sent + * + * @return string + */ + public function get_return_message() + { + return sprintf($this->user->lang['RETURN_INDEX'], '', ''); + } + + /** + * Bind the values of the request to the form + * + * @param \phpbb\request\request_interface $request + * @return null + */ + public function bind(\phpbb\request\request_interface $request) + { + $this->cc_sender = $request->is_set_post('cc_sender'); + $this->body = $request->variable('message', '', true); + } + + /** + * Submit form, generate the email and send it + * + * @param \messenger $messenger + * @return null + */ + public function submit(\messenger $messenger) + { + if (!check_form_key('memberlist_email')) + { + $this->errors[] = 'FORM_INVALID'; + } + + if (!count($this->errors)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_emailtime = ' . time() . ' + WHERE user_id = ' . $this->user->data['user_id']; + $this->db->sql_query($sql); + + if ($this->cc_sender && $this->user->data['is_registered']) + { + $this->message->cc_sender(); + } + + $this->message->send($messenger, phpbb_get_board_contact($this->config, $this->phpEx)); + + meta_refresh(3, append_sid($this->phpbb_root_path . 'index.' . $this->phpEx)); + trigger_error($this->user->lang['EMAIL_SENT'] . '

' . $this->get_return_message()); + } + } + + /** + * Render the template of the form + * + * @param \phpbb\template\template $template + * @return null + */ + public function render(\phpbb\template\template $template) + { + add_form_key('memberlist_email'); + + $template->assign_vars(array( + 'ERROR_MESSAGE' => (count($this->errors)) ? implode('
', $this->errors) : '', + )); + } +} diff --git a/phpbb/message/message.php b/phpbb/message/message.php new file mode 100644 index 0000000..fa701d1 --- /dev/null +++ b/phpbb/message/message.php @@ -0,0 +1,282 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\message; + +/** +* Class message +* Holds all information for an email and sends it in the end +*/ +class message +{ + /** @var string */ + protected $server_name; + + /** @var string */ + protected $subject = ''; + /** @var string */ + protected $body = ''; + /** @var string */ + protected $template = ''; + /** @var array */ + protected $template_vars = array(); + + /** @var string */ + protected $sender_ip = ''; + /** @var string */ + protected $sender_name = ''; + /** @var string */ + protected $sender_address = ''; + /** @var string */ + protected $sender_lang = ''; + /** @var string */ + protected $sender_id = ''; + /** @var string */ + protected $sender_username = ''; + /** @var string */ + protected $sender_jabber = ''; + /** @var int */ + protected $sender_notify_type = NOTIFY_EMAIL; + + /** @var array */ + protected $recipients; + + /** + * Construct + * + * @param string $server_name Used for AntiAbuse header + */ + public function __construct($server_name) + { + $this->server_name = $server_name; + } + + /** + * Set the subject of the email + * + * @param string $subject + * @return null + */ + public function set_subject($subject) + { + $this->subject = $subject; + } + + /** + * Set the body of the email text + * + * @param string $body + * @return null + */ + public function set_body($body) + { + $this->body = $body; + } + + /** + * Set the name of the email template to use + * + * @param string $template + * @return null + */ + public function set_template($template) + { + $this->template = $template; + } + + /** + * Set the array with the "template" data for the email + * + * @param array $template_vars + * @return null + */ + public function set_template_vars($template_vars) + { + $this->template_vars = $template_vars; + } + + /** + * Add a recipient from \phpbb\user + * + * @param array $user + * @return null + */ + public function add_recipient_from_user_row(array $user) + { + $this->add_recipient( + $user['username'], + $user['user_email'], + $user['user_lang'], + $user['user_notify_type'], + $user['username'], + $user['user_jabber'] + ); + } + + /** + * Add a recipient + * + * @param string $recipient_name Displayed sender name + * @param string $recipient_address Email address + * @param string $recipient_lang + * @param int $recipient_notify_type Used notification methods (Jabber, Email, ...) + * @param string $recipient_username User Name (used for AntiAbuse header) + * @param string $recipient_jabber + * @return null + */ + public function add_recipient($recipient_name, $recipient_address, $recipient_lang, $recipient_notify_type = NOTIFY_EMAIL, $recipient_username = '', $recipient_jabber = '') + { + $this->recipients[] = array( + 'name' => $recipient_name, + 'address' => $recipient_address, + 'lang' => $recipient_lang, + 'username' => $recipient_username, + 'jabber' => $recipient_jabber, + 'notify_type' => $recipient_notify_type, + 'to_name' => $recipient_name, + ); + } + + /** + * Set the senders data from \phpbb\user object + * + * @param \phpbb\user $user + * @return null + */ + public function set_sender_from_user($user) + { + $this->set_sender( + $user->ip, + $user->data['username'], + $user->data['user_email'], + $user->lang_name, + $user->data['user_id'], + $user->data['username'], + $user->data['user_jabber'] + ); + + $this->set_sender_notify_type($user->data['user_notify_type']); + } + + /** + * Set the senders data + * + * @param string $sender_ip + * @param string $sender_name Displayed sender name + * @param string $sender_address Email address + * @param string $sender_lang + * @param int $sender_id User ID + * @param string $sender_username User Name (used for AntiAbuse header) + * @param string $sender_jabber + * @return null + */ + public function set_sender($sender_ip, $sender_name, $sender_address, $sender_lang = '', $sender_id = 0, $sender_username = '', $sender_jabber = '') + { + $this->sender_ip = $sender_ip; + $this->sender_name = $sender_name; + $this->sender_address = $sender_address; + $this->sender_lang = $sender_lang; + $this->sender_id = $sender_id; + $this->sender_username = $sender_username; + $this->sender_jabber = $sender_jabber; + } + + /** + * Which notification type should be used? Jabber, Email, ...? + * + * @param int $sender_notify_type + * @return null + */ + public function set_sender_notify_type($sender_notify_type) + { + $this->sender_notify_type = $sender_notify_type; + } + + /** + * Ok, now the same email if CC specified, but without exposing the user's email address + * + * @return null + */ + public function cc_sender() + { + if (!count($this->recipients)) + { + trigger_error('No email recipients specified'); + } + if (!$this->sender_address) + { + trigger_error('No email sender specified'); + } + + $this->recipients[] = array( + 'lang' => $this->sender_lang, + 'address' => $this->sender_address, + 'name' => $this->sender_name, + 'username' => $this->sender_username, + 'jabber' => $this->sender_jabber, + 'notify_type' => $this->sender_notify_type, + 'to_name' => $this->recipients[0]['to_name'], + ); + } + + /** + * Send the email + * + * @param \messenger $messenger + * @param string $contact + * @return null + */ + public function send(\messenger $messenger, $contact) + { + if (!count($this->recipients)) + { + return; + } + + foreach ($this->recipients as $recipient) + { + $messenger->template($this->template, $recipient['lang']); + $messenger->replyto($this->sender_address); + $messenger->to($recipient['address'], $recipient['name']); + $messenger->im($recipient['jabber'], $recipient['username']); + + $messenger->headers('X-AntiAbuse: Board servername - ' . $this->server_name); + $messenger->headers('X-AntiAbuse: User IP - ' . $this->sender_ip); + + if ($this->sender_id) + { + $messenger->headers('X-AntiAbuse: User_id - ' . $this->sender_id); + } + if ($this->sender_username) + { + $messenger->headers('X-AntiAbuse: Username - ' . $this->sender_username); + } + + $messenger->subject(htmlspecialchars_decode($this->subject)); + + $messenger->assign_vars(array( + 'BOARD_CONTACT' => $contact, + 'TO_USERNAME' => htmlspecialchars_decode($recipient['to_name']), + 'FROM_USERNAME' => htmlspecialchars_decode($this->sender_name), + 'MESSAGE' => htmlspecialchars_decode($this->body)) + ); + + if (count($this->template_vars)) + { + $messenger->assign_vars($this->template_vars); + } + + $messenger->send($recipient['notify_type']); + } + } +} diff --git a/phpbb/message/topic_form.php b/phpbb/message/topic_form.php new file mode 100644 index 0000000..dbb883c --- /dev/null +++ b/phpbb/message/topic_form.php @@ -0,0 +1,166 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\message; + +/** +* Class topic_form +* Form used to send topics as notification emails +*/ +class topic_form extends form +{ + /** @var int */ + protected $topic_id; + /** @var array */ + protected $topic_row; + /** @var string */ + protected $recipient_address; + /** @var string */ + protected $recipient_name; + /** @var string */ + protected $recipient_lang; + + /** + * Get the data of the topic + * + * @param int $topic_id + * @return false|array false if the topic does not exist, array otherwise + */ + protected function get_topic_row($topic_id) + { + $sql = 'SELECT forum_id, topic_title + FROM ' . TOPICS_TABLE . ' + WHERE topic_id = ' . (int) $topic_id; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $row; + } + + /** + * {inheritDoc} + */ + public function check_allow() + { + $error = parent::check_allow(); + if ($error) + { + return $error; + } + + if (!$this->auth->acl_get('u_sendemail')) + { + return 'NO_EMAIL'; + } + + if (!$this->topic_row) + { + return 'NO_TOPIC'; + } + + if (!$this->auth->acl_get('f_read', $this->topic_row['forum_id'])) + { + if ($this->user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + } + else + { + send_status_line(401, 'Unauthorized'); + } + return 'SORRY_AUTH_READ'; + } + + if (!$this->auth->acl_get('f_email', $this->topic_row['forum_id'])) + { + return 'NO_EMAIL'; + } + + return false; + } + + /** + * {inheritDoc} + */ + public function bind(\phpbb\request\request_interface $request) + { + parent::bind($request); + + $this->topic_id = $request->variable('t', 0); + $this->recipient_address = $request->variable('email', ''); + $this->recipient_name = $request->variable('name', '', true); + $this->recipient_lang = $request->variable('lang', $this->config['default_lang']); + + $this->topic_row = $this->get_topic_row($this->topic_id); + } + + /** + * {inheritDoc} + */ + public function submit(\messenger $messenger) + { + if (!$this->recipient_address || !preg_match('/^' . get_preg_expression('email') . '$/i', $this->recipient_address)) + { + $this->errors[] = $this->user->lang['EMPTY_ADDRESS_EMAIL']; + } + + if (!$this->recipient_name) + { + $this->errors[] = $this->user->lang['EMPTY_NAME_EMAIL']; + } + + $this->message->set_template('email_notify'); + $this->message->set_template_vars(array( + 'TOPIC_NAME' => htmlspecialchars_decode($this->topic_row['topic_title']), + 'U_TOPIC' => generate_board_url() . '/viewtopic.' . $this->phpEx . '?f=' . $this->topic_row['forum_id'] . '&t=' . $this->topic_id, + )); + $this->message->set_body($this->body); + $this->message->add_recipient( + $this->recipient_name, + $this->recipient_address, + $this->recipient_lang, + NOTIFY_EMAIL + ); + $this->message->set_sender_notify_type(NOTIFY_EMAIL); + + parent::submit($messenger); + } + + /** + * {inheritDoc} + */ + public function get_return_message() + { + return sprintf($this->user->lang['RETURN_TOPIC'], '', ''); + } + + /** + * {inheritDoc} + */ + public function render(\phpbb\template\template $template) + { + parent::render($template); + + $this->user->add_lang('viewtopic'); + $template->assign_vars(array( + 'EMAIL' => $this->recipient_address, + 'NAME' => $this->recipient_name, + 'S_LANG_OPTIONS' => language_select($this->recipient_lang), + 'MESSAGE' => $this->body, + + 'L_EMAIL_BODY_EXPLAIN' => $this->user->lang['EMAIL_TOPIC_EXPLAIN'], + 'S_POST_ACTION' => append_sid($this->phpbb_root_path . 'memberlist.' . $this->phpEx, 'mode=email&t=' . $this->topic_id)) + ); + } +} diff --git a/phpbb/message/user_form.php b/phpbb/message/user_form.php new file mode 100644 index 0000000..007e575 --- /dev/null +++ b/phpbb/message/user_form.php @@ -0,0 +1,136 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\message; + +/** +* Class user_form +* Allows users to send emails to other users +*/ +class user_form extends form +{ + /** @var int */ + protected $recipient_id; + /** @var array */ + protected $recipient_row; + /** @var string */ + protected $subject; + + /** + * Get the data of the recipient + * + * @param int $user_id + * @return false|array false if the user does not exist, array otherwise + */ + protected function get_user_row($user_id) + { + $sql = 'SELECT user_id, username, user_colour, user_email, user_allow_viewemail, user_lang, user_jabber, user_notify_type + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $user_id . ' + AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return $row; + } + + /** + * {inheritDoc} + */ + public function check_allow() + { + $error = parent::check_allow(); + if ($error) + { + return $error; + } + + if (!$this->auth->acl_get('u_sendemail')) + { + return 'NO_EMAIL'; + } + + if ($this->recipient_id == ANONYMOUS || !$this->config['board_email_form']) + { + return 'NO_EMAIL'; + } + + if (!$this->recipient_row) + { + return 'NO_USER'; + } + + // Can we send email to this user? + if (!$this->recipient_row['user_allow_viewemail'] && !$this->auth->acl_get('a_user')) + { + return 'NO_EMAIL'; + } + + return false; + } + + /** + * {inheritDoc} + */ + public function bind(\phpbb\request\request_interface $request) + { + parent::bind($request); + + $this->recipient_id = $request->variable('u', 0); + $this->subject = $request->variable('subject', '', true); + + $this->recipient_row = $this->get_user_row($this->recipient_id); + } + + /** + * {inheritDoc} + */ + public function submit(\messenger $messenger) + { + if (!$this->subject) + { + $this->errors[] = $this->user->lang['EMPTY_SUBJECT_EMAIL']; + } + + if (!$this->body) + { + $this->errors[] = $this->user->lang['EMPTY_MESSAGE_EMAIL']; + } + + $this->message->set_template('profile_send_email'); + $this->message->set_subject($this->subject); + $this->message->set_body($this->body); + $this->message->add_recipient_from_user_row($this->recipient_row); + + parent::submit($messenger); + } + + /** + * {inheritDoc} + */ + public function render(\phpbb\template\template $template) + { + parent::render($template); + + $template->assign_vars(array( + 'S_SEND_USER' => true, + 'S_POST_ACTION' => append_sid($this->phpbb_root_path . 'memberlist.' . $this->phpEx, 'mode=email&u=' . $this->recipient_id), + + 'L_SEND_EMAIL_USER' => $this->user->lang('SEND_EMAIL_USER', $this->recipient_row['username']), + 'USERNAME_FULL' => get_username_string('full', $this->recipient_row['user_id'], $this->recipient_row['username'], $this->recipient_row['user_colour']), + 'SUBJECT' => $this->subject, + 'MESSAGE' => $this->body, + )); + } +} diff --git a/phpbb/mimetype/content_guesser.php b/phpbb/mimetype/content_guesser.php new file mode 100644 index 0000000..f3ad7f5 --- /dev/null +++ b/phpbb/mimetype/content_guesser.php @@ -0,0 +1,33 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\mimetype; + +class content_guesser extends guesser_base +{ + /** + * {@inheritdoc} + */ + public function is_supported() + { + return function_exists('mime_content_type') && is_callable('mime_content_type'); + } + + /** + * {@inheritdoc} + */ + public function guess($file, $file_name = '') + { + return mime_content_type($file); + } +} diff --git a/phpbb/mimetype/extension_guesser.php b/phpbb/mimetype/extension_guesser.php new file mode 100644 index 0000000..9e36c07 --- /dev/null +++ b/phpbb/mimetype/extension_guesser.php @@ -0,0 +1,509 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\mimetype; + +class extension_guesser extends guesser_base +{ + /** + * @var file extension map + */ + protected $extension_map = array( + '3dm' => 'x-world/x-3dmf', + '3dmf' => 'x-world/x-3dmf', + 'a' => 'application/octet-stream', + 'aab' => 'application/x-authorware-bin', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abc' => 'text/vnd.abc', + 'acgi' => 'text/html', + 'afl' => 'video/animaflex', + 'ai' => 'application/postscript', + 'aif' => 'audio/aiff', + 'aifc' => 'audio/aiff', + 'aiff' => 'audio/aiff', + 'aim' => 'application/x-aim', + 'aip' => 'text/x-audiosoft-intra', + 'ani' => 'application/x-navi-animation', + 'aos' => 'application/x-nokia-9000-communicator-add-on-software', + 'aps' => 'application/mime', + 'arc' => 'application/octet-stream', + 'arj' => 'application/arj', + 'art' => 'image/x-jg', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'asp' => 'text/asp', + 'asx' => 'video/x-ms-asf', + 'au' => 'audio/x-au', + 'avi' => 'video/avi', + 'avs' => 'video/avs-video', + 'bcpio' => 'application/x-bcpio', + 'bin' => 'application/x-binary', + 'bm' => 'image/bmp', + 'bmp' => 'image/bmp', + 'boo' => 'application/book', + 'book' => 'application/book', + 'boz' => 'application/x-bzip2', + 'bsh' => 'application/x-bsh', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c++' => 'text/x-c', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cc' => 'text/plain', + 'ccad' => 'application/clariscad', + 'cco' => 'application/x-cocoa', + 'cdf' => 'application/cdf', + 'cer' => 'application/x-x509-ca-cert', + 'cha' => 'application/x-chat', + 'chat' => 'application/x-chat', + 'class' => 'application/java', + 'com' => 'application/octet-stream', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/x-cpt', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'csh' => 'application/x-csh', + 'css' => 'text/css', + 'cxx' => 'text/plain', + 'dcr' => 'application/x-director', + 'deepv' => 'application/x-deepv', + 'def' => 'text/plain', + 'der' => 'application/x-x509-ca-cert', + 'dif' => 'video/x-dv', + 'dir' => 'application/x-director', + 'dl' => 'video/dl', + 'doc' => 'application/msword', + 'dot' => 'application/msword', + 'dp' => 'application/commonground', + 'drw' => 'application/drafting', + 'dump' => 'application/octet-stream', + 'dv' => 'video/x-dv', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/x-dwg', + 'dxf' => 'image/x-dwg', + 'dxr' => 'application/x-director', + 'el' => 'text/x-script.elisp', + 'elc' => 'application/x-elc', + 'env' => 'application/x-envoy', + 'eps' => 'application/postscript', + 'es' => 'application/x-esrehber', + 'etx' => 'text/x-setext', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'f' => 'text/x-fortran', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fdf' => 'application/vnd.fdf', + 'fif' => 'image/fif', + 'fli' => 'video/x-fli', + 'flo' => 'image/florian', + 'flx' => 'text/vnd.fmi.flexstor', + 'fmf' => 'video/x-atomic3d-feature', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frl' => 'application/freeloader', + 'funk' => 'audio/make', + 'g' => 'text/plain', + 'g3' => 'image/g3fax', + 'gif' => 'image/gif', + 'gl' => 'video/x-gl', + 'gsd' => 'audio/x-gsm', + 'gsm' => 'audio/x-gsm', + 'gsp' => 'application/x-gsp', + 'gss' => 'application/x-gss', + 'gtar' => 'application/x-gtar', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'h' => 'text/x-h', + 'hdf' => 'application/x-hdf', + 'help' => 'application/x-helpfile', + 'hgl' => 'application/vnd.hp-hpgl', + 'hh' => 'text/x-h', + 'hlb' => 'text/x-script', + 'hlp' => 'application/hlp', + 'hpg' => 'application/vnd.hp-hpgl', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hqx' => 'application/x-binhex40', + 'hta' => 'application/hta', + 'htc' => 'text/x-component', + 'htm' => 'text/html', + 'html' => 'text/html', + 'htmls' => 'text/html', + 'htt' => 'text/webviewhtml', + 'htx' => 'text/html', + 'ice' => 'x-conference/x-cooltalk', + 'ico' => 'image/x-icon', + 'idc' => 'text/plain', + 'ief' => 'image/ief', + 'iefs' => 'image/ief', + 'iges' => 'application/iges', + 'igs' => 'application/iges', + 'ima' => 'application/x-ima', + 'imap' => 'application/x-httpd-imap', + 'inf' => 'application/inf', + 'ins' => 'application/x-internett-signup', + 'ip' => 'application/x-ip2', + 'isu' => 'video/x-isvideo', + 'it' => 'audio/it', + 'iv' => 'application/x-inventor', + 'ivr' => 'i-world/i-vrml', + 'ivy' => 'application/x-livescreen', + 'jam' => 'audio/x-jam', + 'jav' => 'text/plain', + 'jav' => 'text/x-java-source', + 'java' => 'text/x-java-source', + 'jcm' => 'application/x-java-commerce', + 'jfif' => 'image/jpeg', + 'jfif-tbnl' => 'image/jpeg', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jps' => 'image/x-jps', + 'js' => 'application/x-javascript', + 'jut' => 'image/jutvision', + 'kar' => 'audio/midi', + 'ksh' => 'text/x-script.ksh', + 'la' => 'audio/x-nspaudio', + 'lam' => 'audio/x-liveaudio', + 'latex' => 'application/x-latex', + 'lha' => 'application/x-lha', + 'lhx' => 'application/octet-stream', + 'list' => 'text/plain', + 'lma' => 'audio/x-nspaudio', + 'log' => 'text/plain', + 'lsp' => 'text/x-script.lisp', + 'lst' => 'text/plain', + 'lsx' => 'text/x-la-asf', + 'ltx' => 'application/x-latex', + 'lzh' => 'application/x-lzh', + 'lzx' => 'application/x-lzx', + 'm' => 'text/x-m', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3u' => 'audio/x-mpequrl', + 'man' => 'application/x-troff-man', + 'map' => 'application/x-navimap', + 'mar' => 'text/plain', + 'mbd' => 'application/mbedlet', + 'mc$' => 'application/x-magic-cap-package-1.0', + 'mcd' => 'application/x-mathcad', + 'mcf' => 'text/mcf', + 'mcp' => 'application/netmc', + 'me' => 'application/x-troff-me', + 'mht' => 'message/rfc822', + 'mhtml' => 'message/rfc822', + 'mid' => 'audio/x-midi', + 'midi' => 'audio/x-midi', + 'mif' => 'application/x-mif', + 'mime' => 'www/mime', + 'mjf' => 'audio/x-vnd.audioexplosion.mjuicemediafile', + 'mjpg' => 'video/x-motion-jpeg', + 'mm' => 'application/x-meme', + 'mme' => 'application/base64', + 'mod' => 'audio/x-mod', + 'moov' => 'video/quicktime', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/x-mpeg', + 'mp3' => 'audio/x-mpeg-3', + 'mpa' => 'audio/mpeg', + 'mpc' => 'application/x-project', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpga' => 'audio/mpeg', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/x-project', + 'mpv' => 'application/x-project', + 'mpx' => 'application/x-project', + 'mrc' => 'application/marc', + 'ms' => 'application/x-troff-ms', + 'mv' => 'video/x-sgi-movie', + 'my' => 'audio/make', + 'mzz' => 'application/x-vnd.audioexplosion.mzz', + 'nap' => 'image/naplps', + 'naplps' => 'image/naplps', + 'nc' => 'application/x-netcdf', + 'ncm' => 'application/vnd.nokia.configuration-message', + 'nif' => 'image/x-niff', + 'niff' => 'image/x-niff', + 'nix' => 'application/x-mix-transfer', + 'nsc' => 'application/x-conference', + 'nvd' => 'application/x-navidoc', + 'o' => 'application/octet-stream', + 'oda' => 'application/oda', + 'omc' => 'application/x-omc', + 'omcd' => 'application/x-omcdatamaker', + 'omcr' => 'application/x-omcregerator', + 'p' => 'text/x-pascal', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7a' => 'application/x-pkcs7-signature', + 'p7c' => 'application/x-pkcs7-mime', + 'p7m' => 'application/x-pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'part' => 'application/pro_eng', + 'pas' => 'text/pascal', + 'pbm' => 'image/x-portable-bitmap', + 'pcl' => 'application/x-pcl', + 'pct' => 'image/x-pict', + 'pcx' => 'image/x-pcx', + 'pdb' => 'chemical/x-pdb', + 'pdf' => 'application/pdf', + 'pfunk' => 'audio/make.my.funk', + 'pgm' => 'image/x-portable-greymap', + 'pic' => 'image/pict', + 'pict' => 'image/pict', + 'pkg' => 'application/x-newton-compatible-pkg', + 'pko' => 'application/vnd.ms-pki.pko', + 'pl' => 'text/x-script.perl', + 'plx' => 'application/x-pixclscript', + 'pm' => 'text/x-script.perl-module', + 'pm4' => 'application/x-pagemaker', + 'pm5' => 'application/x-pagemaker', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'pot' => 'application/mspowerpoint', + 'pov' => 'model/x-pov', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/mspowerpoint', + 'ppt' => 'application/mspowerpoint', + 'ppz' => 'application/mspowerpoint', + 'pre' => 'application/x-freelance', + 'prt' => 'application/pro_eng', + 'ps' => 'application/postscript', + 'psd' => 'application/octet-stream', + 'pvu' => 'paleovu/x-pv', + 'pwz' => 'application/vnd.ms-powerpoint', + 'py' => 'text/x-script.phyton', + 'pyc' => 'applicaiton/x-bytecode.python', + 'qcp' => 'audio/vnd.qcelp', + 'qd3' => 'x-world/x-3dmf', + 'qd3d' => 'x-world/x-3dmf', + 'qif' => 'image/x-quicktime', + 'qt' => 'video/quicktime', + 'qtc' => 'video/x-qtc', + 'qti' => 'image/x-quicktime', + 'qtif' => 'image/x-quicktime', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'ras' => 'image/x-cmu-raster', + 'rast' => 'image/cmu-raster', + 'rexx' => 'text/x-script.rexx', + 'rf' => 'image/vnd.rn-realflash', + 'rgb' => 'image/x-rgb', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/mid', + 'rmm' => 'audio/x-pn-realaudio', + 'rmp' => 'audio/x-pn-realaudio', + 'rng' => 'application/vnd.nokia.ringing-tone', + 'rnx' => 'application/vnd.rn-realplayer', + 'roff' => 'application/x-troff', + 'rp' => 'image/vnd.rn-realpix', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rt' => 'text/richtext', + 'rtf' => 'text/richtext', + 'rtx' => 'text/richtext', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saveme' => 'application/octet-stream', + 'sbk' => 'application/x-tbook', + 'scm' => 'video/x-scm', + 'sdml' => 'text/plain', + 'sdp' => 'application/x-sdp', + 'sdr' => 'application/sounder', + 'sea' => 'application/x-sea', + 'set' => 'application/set', + 'sgm' => 'text/x-sgml', + 'sgml' => 'text/x-sgml', + 'sh' => 'text/x-script.sh', + 'shar' => 'application/x-shar', + 'shtml' => 'text/x-server-parsed-html', + 'sid' => 'audio/x-psid', + 'sit' => 'application/x-stuffit', + 'skd' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'skp' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'sl' => 'application/x-seelogo', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'snd' => 'audio/x-adpcm', + 'sol' => 'application/solids', + 'spc' => 'text/x-speech', + 'spl' => 'application/futuresplash', + 'spr' => 'application/x-sprite', + 'sprite' => 'application/x-sprite', + 'src' => 'application/x-wais-source', + 'ssi' => 'text/x-server-parsed-html', + 'ssm' => 'application/streamingmedia', + 'sst' => 'application/vnd.ms-pki.certstore', + 'step' => 'application/step', + 'stl' => 'application/vnd.ms-pki.stl', + 'stp' => 'application/step', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svf' => 'image/x-dwg', + 'svr' => 'application/x-world', + 'swf' => 'application/x-shockwave-flash', + 't' => 'application/x-troff', + 'talk' => 'text/x-speech', + 'tar' => 'application/x-tar', + 'tbk' => 'application/x-tbook', + 'tcl' => 'text/x-script.tcl', + 'tcsh' => 'text/x-script.tcsh', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tgz' => 'application/x-compressed', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tr' => 'application/x-troff', + 'tsi' => 'audio/tsp-audio', + 'tsp' => 'audio/tsplayer', + 'tsv' => 'text/tab-separated-values', + 'turbot' => 'image/florian', + 'txt' => 'text/plain', + 'uil' => 'text/x-uil', + 'uni' => 'text/uri-list', + 'unis' => 'text/uri-list', + 'unv' => 'application/i-deas', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'ustar' => 'multipart/x-ustar', + 'uu' => 'text/x-uuencode', + 'uue' => 'text/x-uuencode', + 'vcd' => 'application/x-cdlink', + 'vcs' => 'text/x-vcalendar', + 'vda' => 'application/vda', + 'vdo' => 'video/vdo', + 'vew' => 'application/groupwise', + 'viv' => 'video/vivo', + 'vivo' => 'video/vivo', + 'vmd' => 'application/vocaltec-media-desc', + 'vmf' => 'application/vocaltec-media-file', + 'voc' => 'audio/voc', + 'vos' => 'video/vosaic', + 'vox' => 'audio/voxware', + 'vqe' => 'audio/x-twinvq-plugin', + 'vqf' => 'audio/x-twinvq', + 'vql' => 'audio/x-twinvq-plugin', + 'vrml' => 'application/x-vrml', + 'vrt' => 'x-world/x-vrt', + 'vsd' => 'application/x-visio', + 'vst' => 'application/x-visio', + 'vsw' => 'application/x-visio', + 'w60' => 'application/wordperfect6.0', + 'w61' => 'application/wordperfect6.1', + 'w6w' => 'application/msword', + 'wav' => 'audio/wav', + 'wb1' => 'application/x-qpro', + 'wbmp' => 'image/vnd.wap.wbmp', + 'web' => 'application/vnd.xara', + 'wiz' => 'application/msword', + 'wk1' => 'application/x-123', + 'wmf' => 'windows/metafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'word' => 'application/msword', + 'wp' => 'application/wordperfect', + 'wp5' => 'application/wordperfect', + 'wp6' => 'application/wordperfect', + 'wpd' => 'application/wordperfect', + 'wq1' => 'application/x-lotus', + 'wri' => 'application/mswrite', + 'wrl' => 'model/vrml', + 'wrz' => 'model/vrml', + 'wsc' => 'text/scriplet', + 'wsrc' => 'application/x-wais-source', + 'wtk' => 'application/x-wintalk', + 'xbm' => 'image/xbm', + 'xdr' => 'video/x-amt-demorun', + 'xgz' => 'xgl/drawing', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/excel', + 'xlb' => 'application/excel', + 'xlc' => 'application/excel', + 'xld' => 'application/excel', + 'xlk' => 'application/excel', + 'xll' => 'application/excel', + 'xlm' => 'application/excel', + 'xls' => 'application/excel', + 'xlt' => 'application/excel', + 'xlv' => 'application/excel', + 'xlw' => 'application/excel', + 'xm' => 'audio/xm', + 'xml' => 'text/xml', + 'xmz' => 'xgl/movie', + 'xpix' => 'application/x-vnd.ls-xpix', + 'xpm' => 'image/xpm', + 'x-png' => 'image/png', + 'xsr' => 'video/x-amt-showrun', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-pdb', + 'z' => 'application/x-compressed', + 'zip' => 'application/x-zip-compressed', + 'zoo' => 'application/octet-stream', + 'zsh' => 'text/x-script.zsh', + ); + + /** + * {@inheritdoc} + */ + public function is_supported() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function guess($file, $file_name = '') + { + $file_name = (empty($file_name)) ? $file : $file_name; + return $this->map_extension_to_type($file_name); + } + + /** + * Map extension of supplied file_name to mime type + * + * @param string $file_name Path to file or filename + * + * @return string|null Mimetype if known or null if not + */ + protected function map_extension_to_type($file_name) + { + $extension = pathinfo($file_name, PATHINFO_EXTENSION); + + if (isset($this->extension_map[$extension])) + { + return $this->extension_map[$extension]; + } + else + { + return null; + } + } +} diff --git a/phpbb/mimetype/guesser.php b/phpbb/mimetype/guesser.php new file mode 100644 index 0000000..8baa770 --- /dev/null +++ b/phpbb/mimetype/guesser.php @@ -0,0 +1,156 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\mimetype; + +class guesser +{ + /** + * @const Default priority for mimetype guessers + */ + const PRIORITY_DEFAULT = 0; + + /** + * @var array guessers + */ + protected $guessers; + + /** + * Construct a mimetype guesser object + * + * @param array $mimetype_guessers Mimetype guesser service collection + */ + public function __construct($mimetype_guessers) + { + $this->register_guessers($mimetype_guessers); + } + + /** + * Register MimeTypeGuessers and sort them by priority + * + * @param array $mimetype_guessers Mimetype guesser service collection + * + * @throws \LogicException If incorrect or not mimetype guessers have + * been supplied to class + */ + protected function register_guessers($mimetype_guessers) + { + foreach ($mimetype_guessers as $guesser) + { + $is_supported = (method_exists($guesser, 'is_supported')) ? 'is_supported' : ''; + $is_supported = (method_exists($guesser, 'isSupported')) ? 'isSupported' : $is_supported; + + if (empty($is_supported)) + { + throw new \LogicException('Incorrect mimetype guesser supplied.'); + } + + if ($guesser->$is_supported()) + { + $this->guessers[] = $guesser; + } + } + + if (empty($this->guessers)) + { + throw new \LogicException('No mimetype guesser supplied.'); + } + + // Sort guessers by priority + usort($this->guessers, array($this, 'sort_priority')); + } + + /** + * Sort the priority of supplied guessers + * This is a compare function for usort. A guesser with higher priority + * should be used first and vice versa. usort() orders the array values + * from low to high depending on what the comparison function returns + * to it. Return value should be smaller than 0 if value a is smaller + * than value b. This has been reversed in the comparision function in + * order to sort the guessers from high to low. + * Method has been set to public in order to allow proper testing. + * + * @param object $guesser_a Mimetype guesser a + * @param object $guesser_b Mimetype guesser b + * + * @return int If both guessers have the same priority 0, bigger + * than 0 if first guesser has lower priority, and lower + * than 0 if first guesser has higher priority + */ + public function sort_priority($guesser_a, $guesser_b) + { + $priority_a = (int) (method_exists($guesser_a, 'get_priority')) ? $guesser_a->get_priority() : self::PRIORITY_DEFAULT; + $priority_b = (int) (method_exists($guesser_b, 'get_priority')) ? $guesser_b->get_priority() : self::PRIORITY_DEFAULT; + + return $priority_b - $priority_a; + } + + /** + * Guess mimetype of supplied file + * + * @param string $file Path to file + * @param string $file_name The real file name + * + * @return string Guess for mimetype of file + */ + public function guess($file, $file_name = '') + { + if (!is_file($file)) + { + return false; + } + + if (!is_readable($file)) + { + return false; + } + + $mimetype = 'application/octet-stream'; + + foreach ($this->guessers as $guesser) + { + $mimetype_guess = $guesser->guess($file, $file_name); + + $mimetype = $this->choose_mime_type($mimetype, $mimetype_guess); + } + // Return any mimetype if we got a result or the fallback value + return $mimetype; + } + + /** + * Choose the best mime type based on the current mime type and the guess + * If a guesser returns nulls or application/octet-stream, we will keep + * the current guess. Guesses with a slash inside them will be favored over + * already existing ones. However, any guess that will pass the first check + * will always overwrite the default application/octet-stream. + * + * @param string $mime_type The current mime type + * @param string $guess The current mime type guess + * + * @return string The best mime type based on current mime type and guess + */ + public function choose_mime_type($mime_type, $guess) + { + if ($guess === null || $guess == 'application/octet-stream') + { + return $mime_type; + } + + if ($mime_type == 'application/octet-stream' || strpos($guess, '/') !== false) + { + $mime_type = $guess; + } + + return $mime_type; + } +} diff --git a/phpbb/mimetype/guesser_base.php b/phpbb/mimetype/guesser_base.php new file mode 100644 index 0000000..225dfd5 --- /dev/null +++ b/phpbb/mimetype/guesser_base.php @@ -0,0 +1,38 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\mimetype; + +abstract class guesser_base implements guesser_interface +{ + /** + * @var int Guesser Priority + */ + protected $priority; + + /** + * {@inheritdoc} + */ + public function get_priority() + { + return $this->priority; + } + + /** + * {@inheritdoc} + */ + public function set_priority($priority) + { + $this->priority = $priority; + } +} diff --git a/phpbb/mimetype/guesser_interface.php b/phpbb/mimetype/guesser_interface.php new file mode 100644 index 0000000..a400528 --- /dev/null +++ b/phpbb/mimetype/guesser_interface.php @@ -0,0 +1,50 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\mimetype; + +interface guesser_interface +{ + /** + * Returns whether this guesser is supported on the current OS + * + * @return bool True if guesser is supported, false if not + */ + public function is_supported(); + + /** + * Guess mimetype of supplied file + * + * @param string $file Path to file + * @param string $file_name The real file name + * + * @return string Guess for mimetype of file + */ + public function guess($file, $file_name = ''); + + /** + * Get the guesser priority + * + * @return int Guesser priority + */ + public function get_priority(); + + /** + * Set the guesser priority + * + * @param int Guesser priority + * + * @return void + */ + public function set_priority($priority); +} diff --git a/phpbb/module/exception/module_exception.php b/phpbb/module/exception/module_exception.php new file mode 100644 index 0000000..8ad7511 --- /dev/null +++ b/phpbb/module/exception/module_exception.php @@ -0,0 +1,19 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\module\exception; + +class module_exception extends \phpbb\exception\runtime_exception +{ + +} diff --git a/phpbb/module/exception/module_not_found_exception.php b/phpbb/module/exception/module_not_found_exception.php new file mode 100644 index 0000000..2d485e7 --- /dev/null +++ b/phpbb/module/exception/module_not_found_exception.php @@ -0,0 +1,19 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\module\exception; + +class module_not_found_exception extends module_exception +{ + +} diff --git a/phpbb/module/module_manager.php b/phpbb/module/module_manager.php new file mode 100644 index 0000000..00df33f --- /dev/null +++ b/phpbb/module/module_manager.php @@ -0,0 +1,564 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\module; + +use phpbb\module\exception\module_exception; +use phpbb\module\exception\module_not_found_exception; + +class module_manager +{ + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\extension\manager + */ + protected $extension_manager; + + /** + * @var string + */ + protected $modules_table; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\cache\driver\driver_interface $cache Cache driver + * @param \phpbb\db\driver\driver_interface $db Database driver + * @param \phpbb\extension\manager $ext_manager Extension manager + * @param string $modules_table Module database table's name + * @param string $phpbb_root_path Path to phpBB's root + * @param string $php_ext Extension of PHP files + */ + public function __construct(\phpbb\cache\driver\driver_interface $cache, \phpbb\db\driver\driver_interface $db, \phpbb\extension\manager $ext_manager, $modules_table, $phpbb_root_path, $php_ext) + { + $this->cache = $cache; + $this->db = $db; + $this->extension_manager = $ext_manager; + $this->modules_table = $modules_table; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Get row for specified module + * + * @param int $module_id ID of the module + * @param string $module_class Class of the module (acp, ucp, mcp etc...) + * + * @return array Array of data fetched from the database + * + * @throws \phpbb\module\exception\module_not_found_exception When there is no module with $module_id + */ + public function get_module_row($module_id, $module_class) + { + $module_id = (int) $module_id; + + $sql = 'SELECT * + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND module_id = $module_id"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + throw new module_not_found_exception('NO_MODULE'); + } + + return $row; + } + + /** + * Get available module information from module files + * + * @param string $module_class Class of the module (acp, ucp, mcp etc...) + * @param string $module ID of module + * @param bool $use_all_available Use all available instead of just all + * enabled extensions + * + * @return array Array with module information gathered from module info files. + */ + public function get_module_infos($module_class, $module = '', $use_all_available = false) + { + $directory = $this->phpbb_root_path . 'includes/' . $module_class . '/info/'; + $fileinfo = array(); + + $finder = $this->extension_manager->get_finder($use_all_available); + + $modules = $finder + ->extension_suffix('_module') + ->extension_directory("/$module_class") + ->core_path("includes/$module_class/info/") + ->core_prefix($module_class . '_') + ->get_classes(true); + + foreach ($modules as $cur_module) + { + // Skip entries we do not need if we know the module we are + // looking for + if ($module && strpos(str_replace('\\', '_', $cur_module), $module) === false && $module !== $cur_module) + { + continue; + } + + $info_class = preg_replace('/_module$/', '_info', $cur_module); + + // If the class does not exist it might be following the old + // format. phpbb_acp_info_acp_foo needs to be turned into + // acp_foo_info and the respective file has to be included + // manually because it does not support auto loading + $old_info_class_file = str_replace("phpbb_{$module_class}_info_", '', $cur_module); + $old_info_class = $old_info_class_file . '_info'; + + if (class_exists($old_info_class)) + { + $info_class = $old_info_class; + } + else if (!class_exists($info_class)) + { + $info_class = $old_info_class; + + // need to check class exists again because previous checks triggered autoloading + if (!class_exists($info_class) && file_exists($directory . $old_info_class_file . '.' . $this->php_ext)) + { + include($directory . $old_info_class_file . '.' . $this->php_ext); + } + } + + if (class_exists($info_class)) + { + $info = new $info_class(); + $module_info = $info->module(); + + $main_class = (isset($module_info['filename'])) ? $module_info['filename'] : $cur_module; + + $fileinfo[$main_class] = $module_info; + } + } + + ksort($fileinfo); + + return $fileinfo; + } + + /** + * Get module branch + * + * @param int $module_id ID of the module + * @param string $module_class Class of the module (acp, ucp, mcp etc...) + * @param string $type Type of branch (Expected values: all, parents or children) + * @param bool $include_module Whether or not to include the specified module with $module_id + * + * @return array Returns an array containing the modules in the specified branch type. + */ + public function get_module_branch($module_id, $module_class, $type = 'all', $include_module = true) + { + $module_id = (int) $module_id; + + switch ($type) + { + case 'parents': + $condition = 'm1.left_id BETWEEN m2.left_id AND m2.right_id'; + break; + + case 'children': + $condition = 'm2.left_id BETWEEN m1.left_id AND m1.right_id'; + break; + + default: + $condition = 'm2.left_id BETWEEN m1.left_id AND m1.right_id OR m1.left_id BETWEEN m2.left_id AND m2.right_id'; + break; + } + + $rows = array(); + + $sql = 'SELECT m2.* + FROM ' . $this->modules_table . ' m1 + LEFT JOIN ' . $this->modules_table . " m2 ON ($condition) + WHERE m1.module_class = '" . $this->db->sql_escape($module_class) . "' + AND m2.module_class = '" . $this->db->sql_escape($module_class) . "' + AND m1.module_id = $module_id + ORDER BY m2.left_id"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (!$include_module && $row['module_id'] == $module_id) + { + continue; + } + + $rows[] = $row; + } + $this->db->sql_freeresult($result); + + return $rows; + } + + /** + * Remove modules cache file + * + * @param string $module_class Class of the module (acp, ucp, mcp etc...) + */ + public function remove_cache_file($module_class) + { + // Sanitise for future path use, it's escaped as appropriate for queries + $cache_class = str_replace(array('.', '/', '\\'), '', basename($module_class)); + $this->cache->destroy('_modules_' . $cache_class); + $this->cache->destroy('sql', $this->modules_table); + } + + /** + * Update/Add module + * + * @param array &$module_data The module data + * + * @throws \phpbb\module\exception\module_not_found_exception When parent module or the category is not exist + */ + public function update_module_data(&$module_data) + { + if (!isset($module_data['module_id'])) + { + // no module_id means we're creating a new category/module + if ($module_data['parent_id']) + { + $sql = 'SELECT left_id, right_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "' + AND module_id = " . (int) $module_data['parent_id']; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row) + { + throw new module_not_found_exception('PARENT_NOT_EXIST'); + } + + // Workaround + $row['left_id'] = (int) $row['left_id']; + $row['right_id'] = (int) $row['right_id']; + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + 2, right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "' + AND left_id > {$row['right_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . " + SET right_id = right_id + 2 + WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "' + AND {$row['left_id']} BETWEEN left_id AND right_id"; + $this->db->sql_query($sql); + + $module_data['left_id'] = (int) $row['right_id']; + $module_data['right_id'] = (int) $row['right_id'] + 1; + } + else + { + $sql = 'SELECT MAX(right_id) AS right_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "'"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $module_data['left_id'] = (int) $row['right_id'] + 1; + $module_data['right_id'] = (int) $row['right_id'] + 2; + } + + $sql = 'INSERT INTO ' . $this->modules_table . ' ' . $this->db->sql_build_array('INSERT', $module_data); + $this->db->sql_query($sql); + + $module_data['module_id'] = $this->db->sql_nextid(); + } + else + { + $row = $this->get_module_row($module_data['module_id'], $module_data['module_class']); + + if ($module_data['module_basename'] && !$row['module_basename']) + { + // we're turning a category into a module + $branch = $this->get_module_branch($module_data['module_id'], $module_data['module_class'], 'children', false); + + if (count($branch)) + { + throw new module_not_found_exception('NO_CATEGORY_TO_MODULE'); + } + } + + if ($row['parent_id'] != $module_data['parent_id']) + { + $this->move_module($module_data['module_id'], $module_data['parent_id'], $module_data['module_class']); + } + + $update_ary = $module_data; + unset($update_ary['module_id']); + + $sql = 'UPDATE ' . $this->modules_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $update_ary) . " + WHERE module_class = '" . $this->db->sql_escape($module_data['module_class']) . "' + AND module_id = " . (int) $module_data['module_id']; + $this->db->sql_query($sql); + } + } + + /** + * Move module around the tree + * + * @param int $from_module_id ID of the current parent module + * @param int $to_parent_id ID of the target parent module + * @param string $module_class Class of the module (acp, ucp, mcp etc...) + * + * @throws \phpbb\module\exception\module_not_found_exception If the module specified to move modules from does not + * have any children. + */ + public function move_module($from_module_id, $to_parent_id, $module_class) + { + $moved_modules = $this->get_module_branch($from_module_id, $module_class, 'children'); + + if (empty($moved_modules)) + { + throw new module_not_found_exception(); + } + + $from_data = $moved_modules[0]; + $diff = count($moved_modules) * 2; + + $moved_ids = array(); + for ($i = 0, $size = count($moved_modules); $i < $size; ++$i) + { + $moved_ids[] = $moved_modules[$i]['module_id']; + } + + // Resync parents + $sql = 'UPDATE ' . $this->modules_table . " + SET right_id = right_id - $diff + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND left_id < " . (int) $from_data['right_id'] . ' + AND right_id > ' . (int) $from_data['right_id']; + $this->db->sql_query($sql); + + // Resync righthand side of tree + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id - $diff, right_id = right_id - $diff + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND left_id > " . (int) $from_data['right_id']; + $this->db->sql_query($sql); + + if ($to_parent_id > 0) + { + $to_data = $this->get_module_row($to_parent_id, $module_class); + + // Resync new parents + $sql = 'UPDATE ' . $this->modules_table . " + SET right_id = right_id + $diff + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND " . (int) $to_data['right_id'] . ' BETWEEN left_id AND right_id + AND ' . $this->db->sql_in_set('module_id', $moved_ids, true); + $this->db->sql_query($sql); + + // Resync the righthand side of the tree + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + $diff, right_id = right_id + $diff + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND left_id > " . (int) $to_data['right_id'] . ' + AND ' . $this->db->sql_in_set('module_id', $moved_ids, true); + $this->db->sql_query($sql); + + // Resync moved branch + $to_data['right_id'] += $diff; + if ($to_data['right_id'] > $from_data['right_id']) + { + $diff = '+ ' . ($to_data['right_id'] - $from_data['right_id'] - 1); + } + else + { + $diff = '- ' . abs($to_data['right_id'] - $from_data['right_id'] - 1); + } + } + else + { + $sql = 'SELECT MAX(right_id) AS right_id + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND " . $this->db->sql_in_set('module_id', $moved_ids, true); + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $diff = '+ ' . (int) ($row['right_id'] - $from_data['left_id'] + 1); + } + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id $diff, right_id = right_id $diff + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND " . $this->db->sql_in_set('module_id', $moved_ids); + $this->db->sql_query($sql); + } + + /** + * Remove module from tree + * + * @param int $module_id ID of the module to delete + * @param string $module_class Class of the module (acp, ucp, mcp etc...) + * + * @throws \phpbb\module\exception\module_exception When the specified module cannot be removed + */ + public function delete_module($module_id, $module_class) + { + $module_id = (int) $module_id; + + $row = $this->get_module_row($module_id, $module_class); + + $branch = $this->get_module_branch($module_id, $module_class, 'children', false); + + if (count($branch)) + { + throw new module_exception('CANNOT_REMOVE_MODULE'); + } + + // If not move + $diff = 2; + $sql = 'DELETE FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND module_id = $module_id"; + $this->db->sql_query($sql); + + $row['right_id'] = (int) $row['right_id']; + $row['left_id'] = (int) $row['left_id']; + + // Resync tree + $sql = 'UPDATE ' . $this->modules_table . " + SET right_id = right_id - $diff + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND left_id < {$row['right_id']} AND right_id > {$row['right_id']}"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id - $diff, right_id = right_id - $diff + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND left_id > {$row['right_id']}"; + $this->db->sql_query($sql); + } + + /** + * Move module position by $steps up/down + * + * @param array $module_row Array of module data + * @param string $module_class Class of the module (acp, ucp, mcp etc...) + * @param string $action Direction of moving (valid values: move_up or move_down) + * @param int $steps Number of steps to move module + * + * @return string Returns the language name of the module + * + * @throws \phpbb\module\exception\module_not_found_exception When the specified module does not exists + */ + public function move_module_by($module_row, $module_class, $action = 'move_up', $steps = 1) + { + /** + * Fetch all the siblings between the module's current spot + * and where we want to move it to. If there are less than $steps + * siblings between the current spot and the target then the + * module will move as far as possible + */ + $sql = 'SELECT module_id, left_id, right_id, module_langname + FROM ' . $this->modules_table . " + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND parent_id = " . (int) $module_row['parent_id'] . ' + AND ' . (($action == 'move_up') ? 'right_id < ' . (int) $module_row['right_id'] . ' ORDER BY right_id DESC' : 'left_id > ' . (int) $module_row['left_id'] . ' ORDER BY left_id ASC'); + $result = $this->db->sql_query_limit($sql, $steps); + + $target = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $target = $row; + } + $this->db->sql_freeresult($result); + + if (!count($target)) + { + // The module is already on top or bottom + throw new module_not_found_exception(); + } + + /** + * $left_id and $right_id define the scope of the nodes that are affected by the move. + * $diff_up and $diff_down are the values to substract or add to each node's left_id + * and right_id in order to move them up or down. + * $move_up_left and $move_up_right define the scope of the nodes that are moving + * up. Other nodes in the scope of ($left_id, $right_id) are considered to move down. + */ + if ($action == 'move_up') + { + $left_id = (int) $target['left_id']; + $right_id = (int) $module_row['right_id']; + + $diff_up = (int) ($module_row['left_id'] - $target['left_id']); + $diff_down = (int) ($module_row['right_id'] + 1 - $module_row['left_id']); + + $move_up_left = (int) $module_row['left_id']; + $move_up_right = (int) $module_row['right_id']; + } + else + { + $left_id = (int) $module_row['left_id']; + $right_id = (int) $target['right_id']; + + $diff_up = (int) ($module_row['right_id'] + 1 - $module_row['left_id']); + $diff_down = (int) ($target['right_id'] - $module_row['right_id']); + + $move_up_left = (int) ($module_row['right_id'] + 1); + $move_up_right = (int) $target['right_id']; + } + + // Now do the dirty job + $sql = 'UPDATE ' . $this->modules_table . " + SET left_id = left_id + CASE + WHEN left_id BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} + ELSE {$diff_down} + END, + right_id = right_id + CASE + WHEN right_id BETWEEN {$move_up_left} AND {$move_up_right} THEN -{$diff_up} + ELSE {$diff_down} + END + WHERE module_class = '" . $this->db->sql_escape($module_class) . "' + AND left_id BETWEEN {$left_id} AND {$right_id} + AND right_id BETWEEN {$left_id} AND {$right_id}"; + $this->db->sql_query($sql); + + $this->remove_cache_file($module_class); + + return $target['module_langname']; + } +} diff --git a/phpbb/notification/exception.php b/phpbb/notification/exception.php new file mode 100644 index 0000000..e416438 --- /dev/null +++ b/phpbb/notification/exception.php @@ -0,0 +1,22 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification; + +/** +* Notifications exception +*/ + +class exception extends \phpbb\exception\runtime_exception +{ +} diff --git a/phpbb/notification/manager.php b/phpbb/notification/manager.php new file mode 100644 index 0000000..52c650d --- /dev/null +++ b/phpbb/notification/manager.php @@ -0,0 +1,978 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** +* Notifications service class +*/ +class manager +{ + /** @var array */ + protected $notification_types; + + /** @var array */ + protected $subscription_types; + + /** @var method\method_interface[] */ + protected $notification_methods; + + /** @var ContainerInterface */ + protected $phpbb_container; + + /** @var \phpbb\user_loader */ + protected $user_loader; + + /** @var \phpbb\event\dispatcher_interface */ + protected $phpbb_dispatcher; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\cache\service */ + protected $cache; + + /** @var \phpbb\language\language */ + protected $language; + + /** @var \phpbb\user */ + protected $user; + + /** @var string */ + protected $notification_types_table; + + /** @var string */ + protected $user_notifications_table; + + /** + * Notification Constructor + * + * @param array $notification_types + * @param array $notification_methods + * @param ContainerInterface $phpbb_container + * @param \phpbb\user_loader $user_loader + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\cache\service $cache + * @param \phpbb\language\language $language + * @param \phpbb\user $user + * @param string $notification_types_table + * @param string $user_notifications_table + * + * @return \phpbb\notification\manager + */ + public function __construct($notification_types, $notification_methods, ContainerInterface $phpbb_container, \phpbb\user_loader $user_loader, \phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\db\driver\driver_interface $db, \phpbb\cache\service $cache, \phpbb\language\language $language, \phpbb\user $user, $notification_types_table, $user_notifications_table) + { + $this->notification_types = $notification_types; + $this->notification_methods = $notification_methods; + $this->phpbb_container = $phpbb_container; + + $this->user_loader = $user_loader; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->db = $db; + $this->cache = $cache; + $this->language = $language; + $this->user = $user; + + $this->notification_types_table = $notification_types_table; + $this->user_notifications_table = $user_notifications_table; + } + + /** + * Load the user's notifications for a given method + * + * @param string $method_name + * @param array $options Optional options to control what notifications are loaded + * notification_id Notification id to load (or array of notification ids) + * user_id User id to load notifications for (Default: $user->data['user_id']) + * order_by Order by (Default: notification_time) + * order_dir Order direction (Default: DESC) + * limit Number of notifications to load (Default: 5) + * start Notifications offset (Default: 0) + * all_unread Load all unread notifications? If set to true, count_unread is set to true (Default: false) + * count_unread Count all unread notifications? (Default: false) + * count_total Count all notifications? (Default: false) + * @return array Array of information based on the request with keys: + * 'notifications' array of notification type objects + * 'unread_count' number of unread notifications the user has if count_unread is true in the options + * 'total_count' number of notifications the user has if count_total is true in the options + * @throws \phpbb\notification\exception when the method doesn't refer to a class extending \phpbb\notification\method\method_interface + */ + public function load_notifications($method_name, array $options = array()) + { + $method = $this->get_method_class($method_name); + + if (! $method instanceof \phpbb\notification\method\method_interface) + { + throw new \phpbb\notification\exception($this->language->lang('NOTIFICATION_METHOD_INVALID', $method_name)); + } + else if ($method->is_available()) + { + return $method->load_notifications($options); + } + else + { + return array( + 'notifications' => array(), + 'unread_count' => 0, + 'total_count' => 0, + ); + } + } + + /** + * Mark notifications read or unread for all available methods + * + * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types + * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + * + * @deprecated since 3.2 + */ + public function mark_notifications_read($notification_type_name, $item_id, $user_id, $time = false) + { + $this->mark_notifications($notification_type_name, $item_id, $user_id, $time); + } + + /** + * Mark notifications read or unread for all available methods + * + * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to mark read for all item types + * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) + */ + public function mark_notifications($notification_type_name, $item_id, $user_id, $time = false, $mark_read = true) + { + if (is_array($notification_type_name)) + { + $notification_type_id = $this->get_notification_type_ids($notification_type_name); + } + else if ($notification_type_name !== false) + { + $notification_type_id = $this->get_notification_type_id($notification_type_name); + } + else + { + $notification_type_id = false; + } + + /** @var method\method_interface $method */ + foreach ($this->get_available_subscription_methods() as $method) + { + $method->mark_notifications($notification_type_id, $item_id, $user_id, $time, $mark_read); + } + } + + /** + * Mark notifications read or unread from a parent identifier for all available methods + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + * + * @deprecated since 3.2 + */ + public function mark_notifications_read_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false) + { + $this->mark_notifications_by_parent($notification_type_name, $item_parent_id, $user_id, $time); + } + + /** + * Mark notifications read or unread from a parent identifier for all available methods + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) + */ + public function mark_notifications_by_parent($notification_type_name, $item_parent_id, $user_id, $time = false, $mark_read = true) + { + if (is_array($notification_type_name)) + { + $notification_type_id = $this->get_notification_type_ids($notification_type_name); + } + else + { + $notification_type_id = $this->get_notification_type_id($notification_type_name); + } + + /** @var method\method_interface $method */ + foreach ($this->get_available_subscription_methods() as $method) + { + $method->mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time, $mark_read); + } + } + + /** + * Mark notifications read or unread for a given method + * + * @param string $method_name + * @param int|array $notification_id Notification id or array of notification ids. + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) + */ + public function mark_notifications_by_id($method_name, $notification_id, $time = false, $mark_read = true) + { + $method = $this->get_method_class($method_name); + + if ($method instanceof \phpbb\notification\method\method_interface && $method->is_available()) + { + $method->mark_notifications_by_id($notification_id, $time, $mark_read); + } + } + + /** + * Add a notification + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * Note: If you send an array of types, any user who could receive multiple notifications from this single item will only receive + * a single notification. If they MUST receive multiple notifications, call this function multiple times instead of sending an array + * @param array $data Data specific for this type that will be inserted + * @param array $options Optional options to control what notifications are loaded + * ignore_users array of data to specify which users should not receive certain types of notifications + * @return array Information about what users were notified and how they were notified + */ + public function add_notifications($notification_type_name, $data, array $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + if (is_array($notification_type_name)) + { + $notified_users = array(); + $temp_options = $options; + + foreach ($notification_type_name as $type) + { + $temp_options['ignore_users'] = $options['ignore_users'] + $notified_users; + $notified_users += $this->add_notifications($type, $data, $temp_options); + } + + return $notified_users; + } + + // find out which users want to receive this type of notification + $notify_users = $this->get_item_type_class($notification_type_name)->find_users_for_notification($data, $options); + + /** + * Allow filtering the notify_users array for a notification that is about to be sent. + * Here, $notify_users is already filtered by f_read and the ignored list included in the options variable + * + * @event core.notification_manager_add_notifications + * @var string notification_type_name The forum id from where the topic belongs + * @var array data Data specific for the notification_type_name used will be inserted + * @var array notify_users The array of userid that are going to be notified for this notification. Set to array() to cancel. + * @var array options The options that were used when this method was called (read only) + * + * @since 3.1.3-RC1 + */ + $vars = array( + 'notification_type_name', + 'data', + 'notify_users', + 'options', + ); + extract($this->phpbb_dispatcher->trigger_event('core.notification_manager_add_notifications', compact($vars))); + + $this->add_notifications_for_users($notification_type_name, $data, $notify_users); + + return $notify_users; + } + + /** + * Add a notification for specific users + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param array $data Data specific for this type that will be inserted + * @param array $notify_users User list to notify + */ + public function add_notifications_for_users($notification_type_name, $data, $notify_users) + { + if (is_array($notification_type_name)) + { + foreach ($notification_type_name as $type) + { + $this->add_notifications_for_users($type, $data, $notify_users); + } + + return; + } + + $notification_type_id = $this->get_notification_type_id($notification_type_name); + + $item_id = $this->get_item_type_class($notification_type_name)->get_item_id($data); + + $user_ids = array(); + $notification_methods = array(); + + // Never send notifications to the anonymous user! + unset($notify_users[ANONYMOUS]); + + // Make sure not to send new notifications to users who've already been notified about this item + // This may happen when an item was added, but now new users are able to see the item + // We remove each user which was already notified by at least one method. + /** @var method\method_interface $method */ + foreach ($this->get_subscription_methods_instances() as $method) + { + $notified_users = $method->get_notified_users($notification_type_id, array('item_id' => $item_id)); + foreach ($notified_users as $user => $notifications) + { + unset($notify_users[$user]); + } + } + + if (!count($notify_users)) + { + return; + } + + // Allow notifications to perform actions before creating the insert array (such as run a query to cache some data needed for all notifications) + $notification = $this->get_item_type_class($notification_type_name); + $pre_create_data = $notification->pre_create_insert_array($data, $notify_users); + unset($notification); + + // Go through each user so we can insert a row in the DB and then notify them by their desired means + foreach ($notify_users as $user => $methods) + { + $notification = $this->get_item_type_class($notification_type_name); + + $notification->user_id = (int) $user; + + // Generate the insert_array + $notification->create_insert_array($data, $pre_create_data); + + // Users are needed to send notifications + $user_ids = array_merge($user_ids, $notification->users_to_query()); + + foreach ($methods as $method) + { + // setup the notification methods and add the notification to the queue + if (!isset($notification_methods[$method])) + { + $notification_methods[$method] = $this->get_method_class($method); + } + + $notification_methods[$method]->add_to_queue($notification); + } + } + + // We need to load all of the users to send notifications + $this->user_loader->load_users($user_ids); + + // run the queue for each method to send notifications + foreach ($notification_methods as $method) + { + $method->notify(); + } + } + + /** + * Update notification + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types) + * @param array $data Data specific for this type that will be updated + * @param array $options + */ + public function update_notifications($notification_type_name, array $data, array $options = array()) + { + if (is_array($notification_type_name)) + { + foreach ($notification_type_name as $type) + { + $this->update_notifications($type, $data); + } + + return; + } + + $this->update_notification($this->get_item_type_class($notification_type_name), $data, $options); + } + + /** + * Update a notification + * + * @param \phpbb\notification\type\type_interface $notification The notification + * @param array $data Data specific for this type that will be updated + * @param array $options + */ + public function update_notification(\phpbb\notification\type\type_interface $notification, array $data, array $options = array()) + { + if (empty($options)) + { + $options['item_id'] = $notification->get_item_id($data); + } + + /** @var method\method_interface $method */ + foreach ($this->get_available_subscription_methods() as $method) + { + $method->update_notification($notification, $data, $options); + } + } + + /** + * Delete a notification + * + * @param string|array $notification_type_name Type identifier or array of item types (only acceptable if the $item_id is identical for the specified types) + * @param int|array $item_id Identifier within the type (or array of ids) + * @param mixed $parent_id Parent identifier within the type (or array of ids), used in combination with item_id if specified (Default: false; not checked) + * @param mixed $user_id User id (Default: false; not checked) + */ + public function delete_notifications($notification_type_name, $item_id, $parent_id = false, $user_id = false) + { + if (is_array($notification_type_name)) + { + foreach ($notification_type_name as $type) + { + $this->delete_notifications($type, $item_id, $parent_id, $user_id); + } + + return; + } + + $notification_type_id = $this->get_notification_type_id($notification_type_name); + + /** @var method\method_interface $method */ + foreach ($this->get_available_subscription_methods() as $method) + { + $method->delete_notifications($notification_type_id, $item_id, $parent_id, $user_id); + } + } + + /** + * Get all of the subscription types + * + * @return array Array of item types + */ + public function get_subscription_types() + { + if ($this->subscription_types === null) + { + $this->subscription_types = array(); + + foreach ($this->notification_types as $type_name => $data) + { + /** @var type\base $type */ + $type = $this->get_item_type_class($type_name); + + if ($type instanceof \phpbb\notification\type\type_interface && $type->is_available()) + { + $options = array_merge(array( + 'type' => $type, + 'id' => $type->get_type(), + 'lang' => 'NOTIFICATION_TYPE_' . strtoupper($type->get_type()), + 'group' => 'NOTIFICATION_GROUP_MISCELLANEOUS', + ), (($type::$notification_option !== false) ? $type::$notification_option : array())); + + $this->subscription_types[$options['group']][$options['id']] = $options; + } + } + + // Move Miscellaneous to the very last section + if (isset($this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'])) + { + $miscellaneous = $this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']; + unset($this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS']); + $this->subscription_types['NOTIFICATION_GROUP_MISCELLANEOUS'] = $miscellaneous; + } + } + + return $this->subscription_types; + } + + /** + * Get all of the subscription methods + * + * @return array Array of methods + */ + public function get_subscription_methods() + { + $subscription_methods = array(); + + /** @var method\method_interface $method */ + foreach ($this->get_available_subscription_methods() as $method_name => $method) + { + $subscription_methods[$method_name] = array( + 'method' => $method, + 'id' => $method->get_type(), + 'lang' => str_replace('.', '_', strtoupper($method->get_type())), + ); + } + + return $subscription_methods; + } + + /** + * Get all of the subscription methods + * + * @return array Array of method's instances + */ + private function get_subscription_methods_instances() + { + $subscription_methods = array(); + + foreach ($this->notification_methods as $method_name => $data) + { + $method = $this->get_method_class($method_name); + + if ($method instanceof \phpbb\notification\method\method_interface) + { + $subscription_methods[$method_name] = $method; + } + } + + return $subscription_methods; + } + + /** + * Get all of the available subscription methods + * + * @return array Array of method's instances + */ + private function get_available_subscription_methods() + { + $subscription_methods = array(); + + /** @var method\method_interface $method */ + foreach ($this->get_subscription_methods_instances() as $method_name => $method) + { + if ($method->is_available()) + { + $subscription_methods[$method_name] = $method; + } + } + + return $subscription_methods; + } + + + /** + * Get user's notification data + * + * @param int $user_id The user_id of the user to get the notifications for + * + * @return array User's notification + */ + protected function get_user_notifications($user_id) + { + $sql = 'SELECT method, notify, item_type + FROM ' . $this->user_notifications_table . ' + WHERE user_id = ' . (int) $user_id . ' + AND item_id = 0'; + + $result = $this->db->sql_query($sql); + $user_notifications = array(); + + while ($row = $this->db->sql_fetchrow($result)) + { + $user_notifications[$row['item_type']][] = $row; + } + + $this->db->sql_freeresult($result); + + return $user_notifications; + } + + /** + * Get global subscriptions (item_id = 0) + * + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + * + * @return array Subscriptions + */ + public function get_global_subscriptions($user_id = false) + { + $user_id = $user_id ?: $this->user->data['user_id']; + + $subscriptions = array(); + $default_methods = $this->get_default_methods(); + + $user_notifications = $this->get_user_notifications($user_id); + + foreach ($this->get_subscription_types() as $types) + { + foreach ($types as $id => $type) + { + $type_subscriptions = $default_methods; + if (!empty($user_notifications[$id])) + { + foreach ($user_notifications[$id] as $user_notification) + { + $key = array_search($user_notification['method'], $type_subscriptions, true); + if (!$user_notification['notify']) + { + if ($key !== false) + { + unset($type_subscriptions[$key]); + } + + continue; + } + else if ($key === false) + { + $type_subscriptions[] = $user_notification['method']; + } + } + } + + if (!empty($type_subscriptions)) + { + $subscriptions[$id] = $type_subscriptions; + } + } + } + + return $subscriptions; + } + + /** + * Add a subscription + * + * @param string $item_type Type identifier of the subscription + * @param int $item_id The id of the item + * @param string $method The method of the notification e.g. 'board', 'email', or 'jabber' + * (if null a subscription will be added for all the defaults methods) + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + */ + public function add_subscription($item_type, $item_id = 0, $method = null, $user_id = false) + { + if ($method === null) + { + foreach ($this->get_default_methods() as $method_name) + { + $this->add_subscription($item_type, $item_id, $method_name, $user_id); + } + + return; + } + + $user_id = ($user_id === false) ? $this->user->data['user_id'] : $user_id; + + $sql = 'SELECT notify + FROM ' . $this->user_notifications_table . " + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + $current = $this->db->sql_fetchfield('notify'); + $this->db->sql_freeresult(); + + if ($current === false) + { + $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . + $this->db->sql_build_array('INSERT', array( + 'item_type' => $item_type, + 'item_id' => (int) $item_id, + 'user_id' => (int) $user_id, + 'method' => $method, + 'notify' => 1, + )); + $this->db->sql_query($sql); + } + else if (!$current) + { + $sql = 'UPDATE ' . $this->user_notifications_table . " + SET notify = 1 + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + } + } + + /** + * Delete a subscription + * + * @param string $item_type Type identifier of the subscription + * @param int $item_id The id of the item + * @param string $method The method of the notification e.g. 'board', 'email', or 'jabber' + * @param bool|int $user_id The user_id to add the subscription for (bool false for current user) + */ + public function delete_subscription($item_type, $item_id = 0, $method = null, $user_id = false) + { + if ($method === null) + { + foreach ($this->get_default_methods() as $method_name) + { + $this->delete_subscription($item_type, $item_id, $method_name, $user_id); + } + + return; + } + + $user_id = $user_id ?: $this->user->data['user_id']; + + $sql = 'UPDATE ' . $this->user_notifications_table . " + SET notify = 0 + WHERE item_type = '" . $this->db->sql_escape($item_type) . "' + AND item_id = " . (int) $item_id . ' + AND user_id = ' .(int) $user_id . " + AND method = '" . $this->db->sql_escape($method) . "'"; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + $sql = 'INSERT INTO ' . $this->user_notifications_table . ' ' . + $this->db->sql_build_array('INSERT', array( + 'item_type' => $item_type, + 'item_id' => (int) $item_id, + 'user_id' => (int) $user_id, + 'method' => $method, + 'notify' => 0, + )); + $this->db->sql_query($sql); + } + } + + /** + * Disable all notifications of a certain type + * + * This should be called when an extension which has notification types + * is disabled so that all those notifications are hidden and do not + * cause errors + * + * @param string $notification_type_name Type identifier of the subscription + */ + public function disable_notifications($notification_type_name) + { + $sql = 'UPDATE ' . $this->notification_types_table . " + SET notification_type_enabled = 0 + WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'"; + $this->db->sql_query($sql); + } + + /** + * Purge all notifications of a certain type + * + * This should be called when an extension which has notification types + * is purged so that all those notifications are removed + * + * @param string $notification_type_name Type identifier of the subscription + */ + public function purge_notifications($notification_type_name) + { + // If a notification is never used, its type will not be added to the database + // nor its id cached. If this method is called by an extension during the + // purge step, and that extension never used its notifications, + // get_notification_type_id() will throw an exception. However, + // because no notification type was added to the database, + // there is nothing to delete, so we can silently drop the exception. + try + { + $notification_type_id = $this->get_notification_type_id($notification_type_name); + + /** @var method\method_interface $method */ + foreach ($this->get_available_subscription_methods() as $method) + { + $method->purge_notifications($notification_type_id); + } + + } + catch (\phpbb\notification\exception $e) + { + // Continue + } + } + + /** + * Enable all notifications of a certain type + * + * This should be called when an extension which has notification types + * that was disabled is re-enabled so that all those notifications that + * were hidden are shown again + * + * @param string $notification_type_name Type identifier of the subscription + */ + public function enable_notifications($notification_type_name) + { + $sql = 'UPDATE ' . $this->notification_types_table . " + SET notification_type_enabled = 1 + WHERE notification_type_name = '" . $this->db->sql_escape($notification_type_name) . "'"; + $this->db->sql_query($sql); + } + + /** + * Delete all notifications older than a certain time + * + * @param int $timestamp Unix timestamp to delete all notifications that were created before + * @param bool $only_read True (default) to only prune read notifications + */ + public function prune_notifications($timestamp, $only_read = true) + { + /** @var method\method_interface $method */ + foreach ($this->get_available_subscription_methods() as $method) + { + $method->prune_notifications($timestamp, $only_read); + } + } + + /** + * Helper to get the list of methods enabled by default + * + * @return method\method_interface[] + */ + public function get_default_methods() + { + $default_methods = array(); + + foreach ($this->notification_methods as $method) + { + if ($method->is_enabled_by_default() && $method->is_available()) + { + $default_methods[] = $method->get_type(); + } + } + + return $default_methods; + } + + /** + * Helper to get the notifications item type class and set it up + * + * @param string $notification_type_name + * @param array $data + * @return type\type_interface + */ + public function get_item_type_class($notification_type_name, $data = array()) + { + $item = $this->load_object($notification_type_name); + + $item->set_initial_data($data); + + return $item; + } + + /** + * Helper to get the notifications method class and set it up + * + * @param string $method_name + * @return method\method_interface + */ + public function get_method_class($method_name) + { + return $this->load_object($method_name); + } + + /** + * Helper to load objects (notification types/methods) + * + * @param string $object_name + * @return method\method_interface|type\type_interface + */ + protected function load_object($object_name) + { + $object = $this->phpbb_container->get($object_name); + + if (method_exists($object, 'set_notification_manager')) + { + $object->set_notification_manager($this); + } + + return $object; + } + + /** + * Get the notification type id from the name + * + * @param string $notification_type_name The name + * @return int the notification_type_id + * @throws \phpbb\notification\exception + */ + public function get_notification_type_id($notification_type_name) + { + $sql = 'SELECT notification_type_id, notification_type_name + FROM ' . $this->notification_types_table; + $result = $this->db->sql_query($sql, 604800); // cache for one week + while ($row = $this->db->sql_fetchrow($result)) + { + $notification_type_ids[$row['notification_type_name']] = (int) $row['notification_type_id']; + } + $this->db->sql_freeresult($result); + + if (!isset($notification_type_ids[$notification_type_name])) + { + if (!isset($this->notification_types[$notification_type_name]) && !isset($this->notification_types['notification.type.' . $notification_type_name])) + { + throw new \phpbb\notification\exception('NOTIFICATION_TYPE_NOT_EXIST', array($notification_type_name)); + } + + $sql = 'INSERT INTO ' . $this->notification_types_table . ' ' . $this->db->sql_build_array('INSERT', array( + 'notification_type_name' => $notification_type_name, + 'notification_type_enabled' => 1, + )); + $this->db->sql_query($sql); + + // expose new notification type ID for this request + $notification_type_ids[$notification_type_name] = (int) $this->db->sql_nextid(); + + // destroy cache, we have a new addition which we have to to load next time + $this->cache->destroy('sql', $this->notification_types_table); + } + + return $notification_type_ids[$notification_type_name]; + } + + /** + * Get notification type ids (as an array) + * + * @param string|array $notification_type_names Notification type names + * @return array Array of integers + */ + public function get_notification_type_ids($notification_type_names) + { + if (!is_array($notification_type_names)) + { + $notification_type_names = array($notification_type_names); + } + + $notification_type_ids = array(); + + foreach ($notification_type_names as $name) + { + $notification_type_ids[$name] = $this->get_notification_type_id($name); + } + + return $notification_type_ids; + } + + /** + * Find the users which are already notified + * + * @param bool|string|array $notification_type_name Type identifier or array of item types (only acceptable if the $data is identical for the specified types). False to retrieve all item types + * @param array $options + * @return array The list of the notified users + */ + public function get_notified_users($notification_type_name, array $options) + { + $notification_type_id = $this->get_notification_type_id($notification_type_name); + + $notified_users = array(); + + /** @var method\method_interface $method */ + foreach ($this->get_available_subscription_methods() as $method) + { + $notified_users = $notified_users + $method->get_notified_users($notification_type_id, $options); + } + + return $notified_users; + } +} diff --git a/phpbb/notification/method/base.php b/phpbb/notification/method/base.php new file mode 100644 index 0000000..4a183ca --- /dev/null +++ b/phpbb/notification/method/base.php @@ -0,0 +1,137 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\method; + +/** +* Base notifications method class +*/ +abstract class base implements \phpbb\notification\method\method_interface +{ + /** @var \phpbb\notification\manager */ + protected $notification_manager; + + /** + * Queue of messages to be sent + * + * @var array + */ + protected $queue = array(); + + /** + * Set notification manager (required) + * + * @param \phpbb\notification\manager $notification_manager + */ + public function set_notification_manager(\phpbb\notification\manager $notification_manager) + { + $this->notification_manager = $notification_manager; + } + + /** + * Is the method enable by default? + * + * @return bool + */ + public function is_enabled_by_default() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function get_notified_users($notification_type_id, array $options) + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function load_notifications(array $options = array()) + { + return array( + 'notifications' => array(), + 'unread_count' => 0, + 'total_count' => 0, + ); + } + + /** + * Add a notification to the queue + * + * @param \phpbb\notification\type\type_interface $notification + */ + public function add_to_queue(\phpbb\notification\type\type_interface $notification) + { + $this->queue[] = $notification; + } + + /** + * {@inheritdoc} + */ + public function update_notification($notification, array $data, array $options) + { + } + + /** + * {@inheritdoc + */ + public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) + { + } + + /** + * {@inheritdoc} + */ + public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true) + { + } + + /** + * {@inheritdoc} + */ + public function mark_notifications_by_id($notification_id, $time = false, $mark_read = true) + { + } + + /** + * {@inheritdoc} + */ + public function delete_notifications($notification_type_id, $item_id, $parent_id = false, $user_id = false) + { + } + + /** + * {@inheritdoc} + */ + public function prune_notifications($timestamp, $only_read = true) + { + } + + /** + * {@inheritdoc} + */ + public function purge_notifications($notification_type_id) + { + } + + /** + * Empty the queue + */ + protected function empty_queue() + { + $this->queue = array(); + } +} diff --git a/phpbb/notification/method/board.php b/phpbb/notification/method/board.php new file mode 100644 index 0000000..faa5357 --- /dev/null +++ b/phpbb/notification/method/board.php @@ -0,0 +1,399 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\method; + +/** +* In Board notification method class +* This class handles in board notifications. This method is enabled by default. +* +* @package notifications +*/ +class board extends \phpbb\notification\method\base +{ + /** @var \phpbb\user_loader */ + protected $user_loader; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\cache\driver\driver_interface */ + protected $cache; + + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\config\config */ + protected $config; + + /** @var string */ + protected $notification_types_table; + + /** @var string */ + protected $notifications_table; + + /** + * Notification Method Board Constructor + * + * @param \phpbb\user_loader $user_loader + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\cache\driver\driver_interface $cache + * @param \phpbb\user $user + * @param \phpbb\config\config $config + * @param string $notification_types_table + * @param string $notifications_table + */ + public function __construct(\phpbb\user_loader $user_loader, \phpbb\db\driver\driver_interface $db, \phpbb\cache\driver\driver_interface $cache, \phpbb\user $user, \phpbb\config\config $config, $notification_types_table, $notifications_table) + { + $this->user_loader = $user_loader; + $this->db = $db; + $this->cache = $cache; + $this->user = $user; + $this->config = $config; + $this->notification_types_table = $notification_types_table; + $this->notifications_table = $notifications_table; + + } + + /** + * {@inheritdoc} + */ + public function add_to_queue(\phpbb\notification\type\type_interface $notification) + { + $this->queue[] = $notification; + } + + /** + * {@inheritdoc} + */ + public function get_type() + { + return 'notification.method.board'; + } + + /** + * {@inheritdoc} + */ + public function is_available() + { + return $this->config['allow_board_notifications']; + } + + /** + * {@inheritdoc} + */ + public function is_enabled_by_default() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function get_notified_users($notification_type_id, array $options) + { + $notified_users = array(); + $sql = 'SELECT n.* + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.notification_type_id = ' . (int) $notification_type_id . + (isset($options['item_id']) ? ' AND n.item_id = ' . (int) $options['item_id'] : '') . + (isset($options['item_parent_id']) ? ' AND n.item_parent_id = ' . (int) $options['item_parent_id'] : '') . + (isset($options['user_id']) ? ' AND n.user_id = ' . (int) $options['user_id'] : '') . + (isset($options['read']) ? ' AND n.notification_read = ' . (int) $options['read'] : '') .' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $notified_users[$row['user_id']] = $row; + } + $this->db->sql_freeresult($result); + + return $notified_users; + } + + /** + * {@inheritdoc} + */ + public function load_notifications(array $options = array()) + { + // Merge default options + $options = array_merge(array( + 'notification_id' => false, + 'user_id' => $this->user->data['user_id'], + 'order_by' => 'notification_time', + 'order_dir' => 'DESC', + 'limit' => 0, + 'start' => 0, + 'all_unread' => false, + 'count_unread' => false, + 'count_total' => false, + ), $options); + + // If all_unread, count_unread must be true + $options['count_unread'] = ($options['all_unread']) ? true : $options['count_unread']; + + // Anonymous users and bots never receive notifications + if ($options['user_id'] == $this->user->data['user_id'] && ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['user_type'] == USER_IGNORE)) + { + return array( + 'notifications' => array(), + 'unread_count' => 0, + 'total_count' => 0, + ); + } + + $notifications = $user_ids = array(); + $load_special = array(); + $total_count = $unread_count = 0; + + if ($options['count_unread']) + { + // Get the total number of unread notifications + $sql = 'SELECT COUNT(n.notification_id) AS unread_count + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND n.notification_read = 0 + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + $unread_count = (int) $this->db->sql_fetchfield('unread_count'); + $this->db->sql_freeresult($result); + } + + if ($options['count_total']) + { + // Get the total number of notifications + $sql = 'SELECT COUNT(n.notification_id) AS total_count + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1'; + $result = $this->db->sql_query($sql); + $total_count = (int) $this->db->sql_fetchfield('total_count'); + $this->db->sql_freeresult($result); + } + + if (!$options['count_total'] || $total_count) + { + $rowset = array(); + + // Get the main notifications + $sql = 'SELECT n.*, nt.notification_type_name + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . + (($options['notification_id']) ? ((is_array($options['notification_id'])) ? ' AND ' . $this->db->sql_in_set('n.notification_id', $options['notification_id']) : ' AND n.notification_id = ' . (int) $options['notification_id']) : '') . ' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1 + ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); + $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); + + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[$row['notification_id']] = $row; + } + $this->db->sql_freeresult($result); + + // Get all unread notifications + if ($unread_count && $options['all_unread'] && !empty($rowset)) + { + $sql = 'SELECT n.*, nt.notification_type_name + FROM ' . $this->notifications_table . ' n, ' . $this->notification_types_table . ' nt + WHERE n.user_id = ' . (int) $options['user_id'] . ' + AND n.notification_read = 0 + AND ' . $this->db->sql_in_set('n.notification_id', array_keys($rowset), true) . ' + AND nt.notification_type_id = n.notification_type_id + AND nt.notification_type_enabled = 1 + ORDER BY n.' . $this->db->sql_escape($options['order_by']) . ' ' . $this->db->sql_escape($options['order_dir']); + $result = $this->db->sql_query_limit($sql, $options['limit'], $options['start']); + + while ($row = $this->db->sql_fetchrow($result)) + { + $rowset[$row['notification_id']] = $row; + } + $this->db->sql_freeresult($result); + } + + foreach ($rowset as $row) + { + $notification = $this->notification_manager->get_item_type_class($row['notification_type_name'], $row); + + // Array of user_ids to query all at once + $user_ids = array_merge($user_ids, $notification->users_to_query()); + + // Some notification types also require querying additional tables themselves + if (!isset($load_special[$row['notification_type_name']])) + { + $load_special[$row['notification_type_name']] = array(); + } + $load_special[$row['notification_type_name']] = array_merge($load_special[$row['notification_type_name']], $notification->get_load_special()); + + $notifications[$row['notification_id']] = $notification; + } + + $this->user_loader->load_users($user_ids); + + // Allow each type to load its own special items + foreach ($load_special as $item_type => $data) + { + $item_class = $this->notification_manager->get_item_type_class($item_type); + + $item_class->load_special($data, $notifications); + } + } + + return array( + 'notifications' => $notifications, + 'unread_count' => $unread_count, + 'total_count' => $total_count, + ); + } + + /** + * {@inheritdoc} + */ + public function notify() + { + $insert_buffer = new \phpbb\db\sql_insert_buffer($this->db, $this->notifications_table); + + /** @var \phpbb\notification\type\type_interface $notification */ + foreach ($this->queue as $notification) + { + $data = $notification->get_insert_array(); + $insert_buffer->insert($data); + } + + $insert_buffer->flush(); + + // We're done, empty the queue + $this->empty_queue(); + } + + /** + * {@inheritdoc} + */ + public function update_notification($notification, array $data, array $options) + { + // Allow the notifications class to over-ride the update_notifications functionality + if (method_exists($notification, 'update_notifications')) + { + // Return False to over-ride the rest of the update + if ($notification->update_notifications($data) === false) + { + return; + } + } + + $notification_type_id = $this->notification_manager->get_notification_type_id($notification->get_type()); + $update_array = $notification->create_update_array($data); + + $sql = 'UPDATE ' . $this->notifications_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $update_array) . ' + WHERE notification_type_id = ' . (int) $notification_type_id . + (isset($options['item_id']) ? ' AND item_id = ' . (int) $options['item_id'] : '') . + (isset($options['item_parent_id']) ? ' AND item_parent_id = ' . (int) $options['item_parent_id'] : '') . + (isset($options['user_id']) ? ' AND user_id = ' . (int) $options['user_id'] : '') . + (isset($options['read']) ? ' AND notification_read = ' . (int) $options['read'] : ''); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true) + { + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . ' + SET notification_read = ' . ($mark_read ? 1 : 0) . ' + WHERE notification_time <= ' . (int) $time . + (($notification_type_id !== false) ? ' AND ' . + (is_array($notification_type_id) ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : 'notification_type_id = ' . $notification_type_id) : '') . + (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : '') . + (($item_id !== false) ? ' AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) : ''); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true) + { + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . ' + SET notification_read = ' . ($mark_read ? 1 : 0) . ' + WHERE notification_time <= ' . (int) $time . + (($notification_type_id !== false) ? ' AND ' . + (is_array($notification_type_id) ? $this->db->sql_in_set('notification_type_id', $notification_type_id) : 'notification_type_id = ' . $notification_type_id) : '') . + (($item_parent_id !== false) ? ' AND ' . (is_array($item_parent_id) ? $this->db->sql_in_set('item_parent_id', $item_parent_id, false, true) : 'item_parent_id = ' . (int) $item_parent_id) : '') . + (($user_id !== false) ? ' AND ' . (is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id) : ''); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function mark_notifications_by_id($notification_id, $time = false, $mark_read = true) + { + $time = ($time !== false) ? $time : time(); + + $sql = 'UPDATE ' . $this->notifications_table . ' + SET notification_read = ' . ($mark_read ? 1 : 0) . ' + WHERE notification_time <= ' . (int) $time . ' + AND ' . ((is_array($notification_id)) ? $this->db->sql_in_set('notification_id', $notification_id) : 'notification_id = ' . (int) $notification_id); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function delete_notifications($notification_type_id, $item_id, $parent_id = false, $user_id = false) + { + $sql = 'DELETE FROM ' . $this->notifications_table . ' + WHERE notification_type_id = ' . (int) $notification_type_id . ' + AND ' . (is_array($item_id) ? $this->db->sql_in_set('item_id', $item_id) : 'item_id = ' . (int) $item_id) . + (($parent_id !== false) ? ' AND ' . ((is_array($parent_id) ? $this->db->sql_in_set('item_parent_id', $parent_id) : 'item_parent_id = ' . (int) $parent_id)) : '') . + (($user_id !== false) ? ' AND ' . ((is_array($user_id) ? $this->db->sql_in_set('user_id', $user_id) : 'user_id = ' . (int) $user_id)) : ''); + $this->db->sql_query($sql); + } + + /** + * {@inheritdoc} + */ + public function prune_notifications($timestamp, $only_read = true) + { + $sql = 'DELETE FROM ' . $this->notifications_table . ' + WHERE notification_time < ' . (int) $timestamp . + (($only_read) ? ' AND notification_read = 1' : ''); + $this->db->sql_query($sql); + + $this->config->set('read_notification_last_gc', time(), false); + } + + /** + * {@inheritdoc} + */ + public function purge_notifications($notification_type_id) + { + $sql = 'DELETE FROM ' . $this->notifications_table . ' + WHERE notification_type_id = ' . (int) $notification_type_id; + $this->db->sql_query($sql); + + $sql = 'DELETE FROM ' . $this->notification_types_table . ' + WHERE notification_type_id = ' . (int) $notification_type_id; + $this->db->sql_query($sql); + + $this->cache->destroy('sql', $this->notification_types_table); + } +} diff --git a/phpbb/notification/method/email.php b/phpbb/notification/method/email.php new file mode 100644 index 0000000..6376d13 --- /dev/null +++ b/phpbb/notification/method/email.php @@ -0,0 +1,78 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\method; + +use phpbb\notification\type\type_interface; + +/** +* Email notification method class +* This class handles sending emails for notifications +*/ + +class email extends \phpbb\notification\method\messenger_base +{ + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\config\config */ + protected $config; + + /** + * Notification Method email Constructor + * + * @param \phpbb\user_loader $user_loader + * @param \phpbb\user $user + * @param \phpbb\config\config $config + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(\phpbb\user_loader $user_loader, \phpbb\user $user, \phpbb\config\config $config, $phpbb_root_path, $php_ext) + { + parent::__construct($user_loader, $phpbb_root_path, $php_ext); + + $this->user = $user; + $this->config = $config; + } + + /** + * Get notification method name + * + * @return string + */ + public function get_type() + { + return 'notification.method.email'; + } + + /** + * Is this method available for the user? + * This is checked on the notifications options + * + * @param type_interface $notification_type An optional instance of a notification type. If provided, this + * method additionally checks if the type provides an email template. + * @return bool + */ + public function is_available(type_interface $notification_type = null) + { + return parent::is_available($notification_type) && $this->config['email_enable'] && !empty($this->user->data['user_email']); + } + + /** + * Parse the queue and notify the users + */ + public function notify() + { + return $this->notify_using_messenger(NOTIFY_EMAIL); + } +} diff --git a/phpbb/notification/method/jabber.php b/phpbb/notification/method/jabber.php new file mode 100644 index 0000000..81fdb37 --- /dev/null +++ b/phpbb/notification/method/jabber.php @@ -0,0 +1,95 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\method; + +use phpbb\notification\type\type_interface; + +/** +* Jabber notification method class +* This class handles sending Jabber messages for notifications +*/ + +class jabber extends \phpbb\notification\method\messenger_base +{ + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\config\config */ + protected $config; + + /** + * Notification Method jabber Constructor + * + * @param \phpbb\user_loader $user_loader + * @param \phpbb\user $user + * @param \phpbb\config\config $config + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(\phpbb\user_loader $user_loader, \phpbb\user $user, \phpbb\config\config $config, $phpbb_root_path, $php_ext) + { + parent::__construct($user_loader, $phpbb_root_path, $php_ext); + + $this->user = $user; + $this->config = $config; + } + + /** + * Get notification method name + * + * @return string + */ + public function get_type() + { + return 'notification.method.jabber'; + } + + /** + * Is this method available for the user? + * This is checked on the notifications options + * + * @param type_interface $notification_type An optional instance of a notification type. If provided, this + * method additionally checks if the type provides an email template. + * @return bool + */ + public function is_available(type_interface $notification_type = null) + { + return parent::is_available($notification_type) && $this->global_available() && $this->user->data['user_jabber']; + } + + /** + * Is this method available at all? + * This is checked before notifications are sent + */ + public function global_available() + { + return !( + empty($this->config['jab_enable']) || + empty($this->config['jab_host']) || + empty($this->config['jab_username']) || + empty($this->config['jab_password']) || + !@extension_loaded('xml') + ); + } + + public function notify() + { + if (!$this->global_available()) + { + return; + } + + $this->notify_using_messenger(NOTIFY_IM, 'short/'); + } +} diff --git a/phpbb/notification/method/messenger_base.php b/phpbb/notification/method/messenger_base.php new file mode 100644 index 0000000..f82017b --- /dev/null +++ b/phpbb/notification/method/messenger_base.php @@ -0,0 +1,134 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\method; + +use phpbb\notification\type\type_interface; + +/** +* Abstract notification method handling email and jabber notifications +* using the phpBB messenger. +*/ +abstract class messenger_base extends \phpbb\notification\method\base +{ + /** @var \phpbb\user_loader */ + protected $user_loader; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** + * Notification Method Board Constructor + * + * @param \phpbb\user_loader $user_loader + * @param string $phpbb_root_path + * @param string $php_ext + */ + public function __construct(\phpbb\user_loader $user_loader, $phpbb_root_path, $php_ext) + { + $this->user_loader = $user_loader; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Is this method available for the user? + * This is checked on the notifications options + * + * @param type_interface $notification_type An optional instance of a notification type. This method returns false + * only if the type is provided and if it doesn't provide an email template. + * @return bool + */ + public function is_available(type_interface $notification_type = null) + { + return $notification_type === null || $notification_type->get_email_template() !== false; + } + + /** + * Notify using phpBB messenger + * + * @param int $notify_method Notify method for messenger (e.g. NOTIFY_IM) + * @param string $template_dir_prefix Base directory to prepend to the email template name + * + * @return null + */ + protected function notify_using_messenger($notify_method, $template_dir_prefix = '') + { + if (empty($this->queue)) + { + return; + } + + // Load all users we want to notify (we need their email address) + $user_ids = $users = array(); + foreach ($this->queue as $notification) + { + $user_ids[] = $notification->user_id; + } + + // We do not send emails to banned users + if (!function_exists('phpbb_get_banned_user_ids')) + { + include($this->phpbb_root_path . 'includes/functions_user.' . $this->php_ext); + } + $banned_users = phpbb_get_banned_user_ids($user_ids); + + // Load all the users we need + $this->user_loader->load_users(array_diff($user_ids, $banned_users), array(USER_IGNORE)); + + // Load the messenger + if (!class_exists('messenger')) + { + include($this->phpbb_root_path . 'includes/functions_messenger.' . $this->php_ext); + } + $messenger = new \messenger(); + + // Time to go through the queue and send emails + /** @var \phpbb\notification\type\type_interface $notification */ + foreach ($this->queue as $notification) + { + if ($notification->get_email_template() === false) + { + continue; + } + + $user = $this->user_loader->get_user($notification->user_id); + + if ($user['user_type'] == USER_INACTIVE && $user['user_inactive_reason'] == INACTIVE_MANUAL) + { + continue; + } + + $messenger->template($notification->get_email_template(), $user['user_lang'], '', $template_dir_prefix); + + $messenger->set_addresses($user); + + $messenger->assign_vars(array_merge(array( + 'USERNAME' => $user['username'], + + 'U_NOTIFICATION_SETTINGS' => generate_board_url() . '/ucp.' . $this->php_ext . '?i=ucp_notifications&mode=notification_options', + ), $notification->get_email_template_variables())); + + $messenger->send($notify_method); + } + + // Save the queue in the messenger class (has to be called or these emails could be lost?) + $messenger->save_queue(); + + // We're done, empty the queue + $this->empty_queue(); + } +} diff --git a/phpbb/notification/method/method_interface.php b/phpbb/notification/method/method_interface.php new file mode 100644 index 0000000..c2e4940 --- /dev/null +++ b/phpbb/notification/method/method_interface.php @@ -0,0 +1,149 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\method; + +/** +* Base notifications method interface +*/ +interface method_interface +{ + /** + * Get notification method name + * + * @return string + */ + public function get_type(); + + /** + * Is the method enable by default? + * + * @return bool + */ + public function is_enabled_by_default(); + + /** + * Is this method available for the user? + * This is checked on the notifications options + */ + public function is_available(); + + /** + * Return the list of the users already notified + * + * @param int $notification_type_id Type of the notification + * @param array $options + * @return array User + */ + public function get_notified_users($notification_type_id, array $options); + + /** + * Load the user's notifications + * + * @param array $options Optional options to control what notifications are loaded + * notification_id Notification id to load (or array of notification ids) + * user_id User id to load notifications for (Default: $user->data['user_id']) + * order_by Order by (Default: notification_time) + * order_dir Order direction (Default: DESC) + * limit Number of notifications to load (Default: 5) + * start Notifications offset (Default: 0) + * all_unread Load all unread notifications? If set to true, count_unread is set to true (Default: false) + * count_unread Count all unread notifications? (Default: false) + * count_total Count all notifications? (Default: false) + * @return array Array of information based on the request with keys: + * 'notifications' array of notification type objects + * 'unread_count' number of unread notifications the user has if count_unread is true in the options + * 'total_count' number of notifications the user has if count_total is true in the options + */ + public function load_notifications(array $options = array()); + + /** + * Add a notification to the queue + * + * @param \phpbb\notification\type\type_interface $notification + */ + public function add_to_queue(\phpbb\notification\type\type_interface $notification); + + /** + * Parse the queue and notify the users + */ + public function notify(); + + /** + * Update a notification + * + * @param \phpbb\notification\type\type_interface $notification Notification to update + * @param array $data Data specific for this type that will be updated + * @param array $options + */ + public function update_notification($notification, array $data, array $options); + + /** + * Mark notifications read or unread + * + * @param bool|string $notification_type_id Type identifier of item types. False to mark read for all item types + * @param bool|int|array $item_id Item id or array of item ids. False to mark read for all item ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) + */ + public function mark_notifications($notification_type_id, $item_id, $user_id, $time = false, $mark_read = true); + + /** + * Mark notifications read or unread from a parent identifier + * + * @param string $notification_type_id Type identifier of item types + * @param bool|int|array $item_parent_id Item parent id or array of item parent ids. False to mark read for all item parent ids + * @param bool|int|array $user_id User id or array of user ids. False to mark read for all user ids + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) + */ + public function mark_notifications_by_parent($notification_type_id, $item_parent_id, $user_id, $time = false, $mark_read = true); + + /** + * Mark notifications read or unread + * + * @param int $notification_id Notification id of notification ids. + * @param bool|int $time Time at which to mark all notifications prior to as read. False to mark all as read. (Default: False) + * @param bool $mark_read Define if the notification as to be set to True or False. (Default: True) + */ + public function mark_notifications_by_id($notification_id, $time = false, $mark_read = true); + + /** + * Delete a notification + * + * @param string $notification_type_id Type identifier of item types + * @param int|array $item_id Identifier within the type (or array of ids) + * @param mixed $parent_id Parent identifier within the type (or array of ids), used in combination with item_id if specified (Default: false; not checked) + * @param mixed $user_id User id (Default: false; not checked) + */ + public function delete_notifications($notification_type_id, $item_id, $parent_id = false, $user_id = false); + + /** + * Delete all notifications older than a certain time + * + * @param int $timestamp Unix timestamp to delete all notifications that were created before + * @param bool $only_read True (default) to only prune read notifications + */ + public function prune_notifications($timestamp, $only_read = true); + + /** + * Purge all notifications of a certain type + * + * This should be called when an extension which has notification types + * is purged so that all those notifications are removed + * + * @param string $notification_type_id Type identifier of the subscription + */ + public function purge_notifications($notification_type_id); +} diff --git a/phpbb/notification/type/admin_activate_user.php b/phpbb/notification/type/admin_activate_user.php new file mode 100644 index 0000000..78c10ac --- /dev/null +++ b/phpbb/notification/type/admin_activate_user.php @@ -0,0 +1,185 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Admin activation notifications class +* This class handles notifications for users requiring admin activation +*/ + +class admin_activate_user extends \phpbb\notification\type\base +{ + /** + * {@inheritdoc} + */ + public function get_type() + { + return 'notification.type.admin_activate_user'; + } + + /** + * {@inheritdoc} + */ + protected $language_key = 'NOTIFICATION_ADMIN_ACTIVATE_USER'; + + /** + * {@inheritdoc} + */ + static public $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_ADMIN_ACTIVATE_USER', + 'group' => 'NOTIFICATION_GROUP_ADMINISTRATION', + ); + + /** @var \phpbb\user_loader */ + protected $user_loader; + + /** @var \phpbb\config\config */ + protected $config; + + public function set_config(\phpbb\config\config $config) + { + $this->config = $config; + } + + public function set_user_loader(\phpbb\user_loader $user_loader) + { + $this->user_loader = $user_loader; + } + + /** + * {@inheritdoc} + */ + public function is_available() + { + return ($this->auth->acl_get('a_user') && $this->config['require_activation'] == USER_ACTIVATION_ADMIN); + } + + /** + * {@inheritdoc} + */ + static public function get_item_id($user) + { + return (int) $user['user_id']; + } + + /** + * {@inheritdoc} + */ + static public function get_item_parent_id($post) + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function find_users_for_notification($user, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // Grab admins that have permission to administer users. + $admin_ary = $this->auth->acl_get_list(false, 'a_user', false); + $users = (!empty($admin_ary[0]['a_user'])) ? $admin_ary[0]['a_user'] : array(); + + // Grab founders + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE user_type = ' . USER_FOUNDER; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + if (empty($users)) + { + return array(); + } + $users = array_unique($users); + + return $this->check_user_notification_options($users, $options); + } + + /** + * {@inheritdoc} + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->item_id, false, true); + } + + /** + * {@inheritdoc} + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->item_id, 'no_profile'); + + return $this->language->lang($this->language_key, $username); + } + + /** + * {@inheritdoc} + */ + public function get_email_template() + { + return 'admin_activate'; + } + + /** + * {@inheritdoc} + */ + public function get_email_template_variables() + { + $board_url = generate_board_url(); + $username = $this->user_loader->get_username($this->item_id, 'username'); + + return array( + 'USERNAME' => htmlspecialchars_decode($username), + 'U_USER_DETAILS' => "{$board_url}/memberlist.{$this->php_ext}?mode=viewprofile&u={$this->item_id}", + 'U_ACTIVATE' => "{$board_url}/ucp.{$this->php_ext}?mode=activate&u={$this->item_id}&k={$this->get_data('user_actkey')}", + ); + } + + /** + * {@inheritdoc} + */ + public function get_url() + { + return $this->user_loader->get_username($this->item_id, 'profile'); + } + + /** + * {@inheritdoc} + */ + public function users_to_query() + { + return array($this->item_id); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($user, $pre_create_data = array()) + { + $this->set_data('user_actkey', $user['user_actkey']); + $this->notification_time = $user['user_regdate']; + + parent::create_insert_array($user, $pre_create_data); + } +} diff --git a/phpbb/notification/type/approve_post.php b/phpbb/notification/type/approve_post.php new file mode 100644 index 0000000..e4b111e --- /dev/null +++ b/phpbb/notification/type/approve_post.php @@ -0,0 +1,149 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Post approved notifications class +* This class handles notifications for posts when they are approved (to their authors) +*/ + +class approve_post extends \phpbb\notification\type\post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.approve_post'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_POST_APPROVED'; + + /** + * Inherit notification read status from post. + * + * @var bool + */ + protected $inherit_read_status = false; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'id' => 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return !$this->auth->acl_get('m_approve'); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from submit_post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + $users[$post['poster_id']] = $this->notification_manager->get_default_methods(); + + return $this->get_authorised_recipients(array_keys($users), $post['forum_id'], array_merge($options, array( + 'item_type' => static::$notification_option['id'], + ))); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + // In the parent class, this is used to check if the post is already + // read by a user and marks the notification read if it was marked read. + // Returning an empty array in effect, forces it to be marked as unread + // (and also saves a query) + return array(); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('post_subject', $post['post_subject']); + + parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = time(); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = parent::get_insert_array(); + $data['notification_time'] = $this->notification_time; + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_approved'; + } + + /** + * {inheritDoc} + */ + public function get_redirect_url() + { + return $this->get_url(); + } +} diff --git a/phpbb/notification/type/approve_topic.php b/phpbb/notification/type/approve_topic.php new file mode 100644 index 0000000..f8a3fde --- /dev/null +++ b/phpbb/notification/type/approve_topic.php @@ -0,0 +1,140 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Topic approved notifications class +* This class handles notifications for topics when they are approved (for authors) +*/ + +class approve_topic extends \phpbb\notification\type\topic +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.approve_topic'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_TOPIC_APPROVED'; + + /** + * Inherit notification read status from topic. + * + * @var bool + */ + protected $inherit_read_status = false; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'id' => 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return !$this->auth->acl_get('m_approve'); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from submit_post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + $users[$post['poster_id']] = $this->notification_manager->get_default_methods(); + + return $this->get_authorised_recipients(array_keys($users), $post['forum_id'], array_merge($options, array( + 'item_type' => static::$notification_option['id'], + ))); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + // In the parent class, this is used to check if the post is already + // read by a user and marks the notification read if it was marked read. + // Returning an empty array in effect, forces it to be marked as unread + // (and also saves a query) + return array(); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + + parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = time(); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = parent::get_insert_array(); + $data['notification_time'] = $this->notification_time; + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_approved'; + } +} diff --git a/phpbb/notification/type/base.php b/phpbb/notification/type/base.php new file mode 100644 index 0000000..77ed7f2 --- /dev/null +++ b/phpbb/notification/type/base.php @@ -0,0 +1,577 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Base notifications class +*/ +abstract class base implements \phpbb\notification\type\type_interface +{ + /** @var \phpbb\notification\manager */ + protected $notification_manager; + + /** @var \phpbb\db\driver\driver_interface */ + protected $db; + + /** @var \phpbb\language\language */ + protected $language; + + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\auth\auth */ + protected $auth; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $user_notifications_table; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use its default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = false; + + /** + * The notification_type_id, set upon creation of the class + * This is the notification_type_id from the notification_types table + * + * @var int + */ + protected $notification_type_id; + + /** + * Identification data + * notification_type_id - ID of the item type (auto generated, from notification types table) + * item_id - ID of the item (e.g. post_id, msg_id) + * item_parent_id - Parent item id (ex: for topic => forum_id, for post => topic_id, etc) + * user_id + * notification_read + * notification_time + * notification_data (special serialized field that each notification type can use to store stuff) + * + * @var array $data Notification row from the database + * This must be private, all interaction should use __get(), __set(), get_data(), set_data() + */ + private $data = array(); + + /** + * Notification Type Base Constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\language\language $language + * @param \phpbb\user $user + * @param \phpbb\auth\auth $auth + * @param string $phpbb_root_path + * @param string $php_ext + * @param string $user_notifications_table + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\language\language $language, \phpbb\user $user, \phpbb\auth\auth $auth, $phpbb_root_path, $php_ext, $user_notifications_table) + { + $this->db = $db; + $this->language = $language; + $this->user = $user; + $this->auth = $auth; + + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + + $this->user_notifications_table = $user_notifications_table; + } + + /** + * Set notification manager (required) + * + * @param \phpbb\notification\manager $notification_manager + */ + public function set_notification_manager(\phpbb\notification\manager $notification_manager) + { + $this->notification_manager = $notification_manager; + + $this->notification_type_id = $this->notification_manager->get_notification_type_id($this->get_type()); + } + + /** + * Set initial data from the database + * + * @param array $data Row directly from the database + */ + public function set_initial_data($data = array()) + { + // The row from the database (unless this is a new notification we're going to add) + $this->data = $data; + $this->data['notification_data'] = (isset($this->data['notification_data'])) ? unserialize($this->data['notification_data']) : array(); + } + + /** + * Magic method to get data from this notification + * + * @param mixed $name + * @return mixed + */ + public function __get($name) + { + return (!isset($this->data[$name])) ? null : $this->data[$name]; + } + + + /** + * Magic method to set data on this notification + * + * @param mixed $name + * @param mixed $value + * + * @return null + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + + /** + * Magic method to get a string of this notification + * + * Primarily for testing + * + * @return mixed + */ + public function __toString() + { + return (!empty($this->data)) ? var_export($this->data, true) : $this->get_type(); + } + + /** + * Get special data (only important for the classes that extend this) + * + * @param string $name Name of the variable to get + * @return mixed + */ + protected function get_data($name) + { + return ($name === false) ? $this->data['notification_data'] : ((isset($this->data['notification_data'][$name])) ? $this->data['notification_data'][$name] : null); + } + + /** + * Set special data (only important for the classes that extend this) + * + * @param string $name Name of the variable to set + * @param mixed $value Value to set to the variable + * @return mixed + */ + protected function set_data($name, $value) + { + $this->data['notification_data'][$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($type_data, $pre_create_data = array()) + { + // Defaults + $this->data = array_merge(array( + 'item_id' => static::get_item_id($type_data), + 'notification_type_id' => $this->notification_type_id, + 'item_parent_id' => static::get_item_parent_id($type_data), + + 'notification_time' => time(), + 'notification_read' => false, + + 'notification_data' => array(), + ), $this->data); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = $this->data; + + $data['notification_data'] = serialize($data['notification_data']); + + return $data; + } + + /** + * Function for preparing the data for update in an SQL query + * (The service handles insertion) + * + * @param array $type_data Data unique to this notification type + * @return array Array of data ready to be updated in the database + */ + public function create_update_array($type_data) + { + $this->create_insert_array($type_data); + $data = $this->get_insert_array(); + + // Unset data unique to each row + unset( + $data['notification_time'], // Also unsetting time, since it always tries to change the time to current (if you actually need to change the time, over-ride this function) + $data['notification_id'], + $data['notification_read'], + $data['user_id'] + ); + + return $data; + } + + /** + * Mark this item read + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + public function mark_read($return = false) + { + return $this->mark(false, $return); + } + + /** + * Mark this item unread + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + public function mark_unread($return = false) + { + return $this->mark(true, $return); + } + + /** + * {inheritDoc} + */ + public function get_redirect_url() + { + return $this->get_url(); + } + + /** + * Prepare to output the notification to the template + * + * @return array Template variables + */ + public function prepare_for_display() + { + $mark_hash = generate_link_hash('mark_notification_read'); + + if ($this->get_url()) + { + $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&hash=' . $mark_hash); + } + else + { + $redirect = (($this->user->page['page_dir']) ? $this->user->page['page_dir'] . '/' : '') . $this->user->page['page_name'] . (($this->user->page['query_string']) ? '?' . $this->user->page['query_string'] : ''); + + $u_mark_read = append_sid($this->phpbb_root_path . 'index.' . $this->php_ext, 'mark_notification=' . $this->notification_id . '&hash=' . $mark_hash . '&redirect=' . urlencode($redirect)); + } + + return array( + 'NOTIFICATION_ID' => $this->notification_id, + 'STYLING' => $this->get_style_class(), + 'AVATAR' => $this->get_avatar(), + 'FORMATTED_TITLE' => $this->get_title(), + 'REFERENCE' => $this->get_reference(), + 'FORUM' => $this->get_forum(), + 'REASON' => $this->get_reason(), + 'URL' => $this->get_url(), + 'TIME' => $this->user->format_date($this->notification_time), + 'UNREAD' => !$this->notification_read, + 'U_MARK_READ' => (!$this->notification_read) ? $u_mark_read : '', + ); + } + + /** + * -------------- Fall back functions ------------------- + */ + + /** + * URL to unsubscribe to this notification (fall back) + * + * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item + * @return false + */ + public function get_unsubscribe_url($method = false) + { + return false; + } + + /** + * Get the CSS style class of the notification (fall back) + * + * @return string + */ + public function get_style_class() + { + return ''; + } + + /** + * Get the user's avatar (fall back) + * + * @return string + */ + public function get_avatar() + { + return ''; + } + + /** + * Get the reference of the notifcation (fall back) + * + * @return string + */ + public function get_reference() + { + return ''; + } + + /** + * Get the forum of the notification reference (fall back) + * + * @return string + */ + public function get_forum() + { + return ''; + } + + /** + * Get the reason for the notifcation (fall back) + * + * @return string + */ + public function get_reason() + { + return ''; + } + + /** + * Get the special items to load (fall back) + * + * @return array + */ + public function get_load_special() + { + return array(); + } + + /** + * Load the special items (fall back) + * + * @param array $data + * @param array $notifications + */ + public function load_special($data, $notifications) + { + return; + } + + /** + * Is available (fall back) + * + * @return bool + */ + public function is_available() + { + return true; + } + + /** + * Pre create insert array function (fall back) + * + * @param array $type_data + * @param array $notify_users + * @return array + */ + public function pre_create_insert_array($type_data, $notify_users) + { + return array(); + } + + /** + * -------------- Helper functions ------------------- + */ + + /** + * Find the users who want to receive notifications (helper) + * + * @param array|bool $user_ids User IDs to check if they want to receive notifications + * (Bool False to check all users besides anonymous and bots (USER_IGNORE)) + * @param array $options + * @return array + */ + protected function check_user_notification_options($user_ids = false, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + 'item_type' => $this->get_type(), + 'item_id' => 0, // Global by default + ), $options); + + if ($user_ids === false) + { + $user_ids = array(); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE user_id <> ' . ANONYMOUS . ' + AND user_type <> ' . USER_IGNORE; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $user_ids[] = $row['user_id']; + } + $this->db->sql_freeresult($result); + } + + if (empty($user_ids)) + { + return array(); + } + + $rowset = $output = array(); + + $sql = 'SELECT user_id, method, notify + FROM ' . $this->user_notifications_table . ' + WHERE ' . $this->db->sql_in_set('user_id', $user_ids) . " + AND item_type = '" . $this->db->sql_escape($options['item_type']) . "' + AND item_id = " . (int) $options['item_id']; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + if (isset($options['ignore_users'][$row['user_id']]) && in_array($row['method'], $options['ignore_users'][$row['user_id']])) + { + continue; + } + + if (!isset($rowset[$row['user_id']])) + { + $rowset[$row['user_id']] = array(); + } + $rowset[$row['user_id']][$row['method']] = $row['notify']; + + if (!isset($output[$row['user_id']])) + { + $output[$row['user_id']] = array(); + } + if ($row['notify']) + { + $output[$row['user_id']][] = $row['method']; + } + } + + $this->db->sql_freeresult($result); + + $default_methods = $this->notification_manager->get_default_methods(); + + foreach ($user_ids as $user_id) + { + if (isset($options['ignore_users'][$user_id])) + { + continue; + } + if (!array_key_exists($user_id, $rowset)) + { + // No rows at all for this user, use the default methods + $output[$user_id] = $default_methods; + } + else + { + foreach ($default_methods as $default_method) + { + if (!array_key_exists($default_method, $rowset[$user_id])) + { + // No user preference for this type recorded, but it should be enabled by default. + $output[$user_id][] = $default_method; + } + } + } + } + + return $output; + } + + /** + * Mark this item read/unread helper + * + * @param bool $unread Unread (True/False) (Default: False) + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string|null If $return is False, nothing will be returned, else the sql code to update this item + */ + protected function mark($unread = true, $return = false) + { + $this->notification_read = (bool) !$unread; + + if ($return) + { + $where = array( + 'notification_type_id = ' . (int) $this->notification_type_id, + 'item_id = ' . (int) $this->item_id, + 'user_id = ' . (int) $this->user_id, + ); + + $where = implode(' AND ', $where); + return $where; + } + else + { + $this->notification_manager->mark_notifications($this->get_type(), (int) $this->item_id, (int) $this->user_id, false, $this->notification_read); + } + + return null; + } + + /** + * Get a list of users that are authorised to receive notifications + * + * @param array $users Array of users that have subscribed to a notification + * @param int $forum_id Forum ID of the forum + * @param array $options Array of notification options + * @param bool $sort Whether the users array should be sorted. Default: false + * @return array Array of users that are authorised recipients + */ + protected function get_authorised_recipients($users, $forum_id, $options, $sort = false) + { + if (empty($users)) + { + return array(); + } + + $users = array_unique($users); + + if ($sort) + { + sort($users); + } + + $auth_read = $this->auth->acl_get_list($users, 'f_read', $forum_id); + + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$forum_id]['f_read'], $options); + } +} diff --git a/phpbb/notification/type/bookmark.php b/phpbb/notification/type/bookmark.php new file mode 100644 index 0000000..ebbb961 --- /dev/null +++ b/phpbb/notification/type/bookmark.php @@ -0,0 +1,128 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Bookmark updating notifications class +* This class handles notifications for replies to a bookmarked topic +*/ + +class bookmark extends \phpbb\notification\type\post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.bookmark'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_BOOKMARK'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_BOOKMARK', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_bookmarks']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from submit_post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . BOOKMARKS_TABLE . ' + WHERE ' . $this->db->sql_in_set('topic_id', $post['topic_id']) . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + $notify_users = $this->get_authorised_recipients($users, $post['forum_id'], $options, true); + + if (empty($notify_users)) + { + return array(); + } + + // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications + $notified_users = $this->notification_manager->get_notified_users($this->get_type(), array( + 'item_parent_id' => static::get_item_parent_id($post), + 'read' => 0, + )); + + foreach ($notified_users as $user => $notification_data) + { + unset($notify_users[$user]); + + /** @var bookmark $notification */ + $notification = $this->notification_manager->get_item_type_class($this->get_type(), $notification_data); + $update_responders = $notification->add_responders($post); + if (!empty($update_responders)) + { + $this->notification_manager->update_notification($notification, $update_responders, array( + 'item_parent_id' => self::get_item_parent_id($post), + 'read' => 0, + 'user_id' => $user, + )); + } + } + + return $notify_users; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'bookmark'; + } +} diff --git a/phpbb/notification/type/disapprove_post.php b/phpbb/notification/type/disapprove_post.php new file mode 100644 index 0000000..2d908eb --- /dev/null +++ b/phpbb/notification/type/disapprove_post.php @@ -0,0 +1,159 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Post disapproved notifications class +* This class handles notifications for posts when they are disapproved (for authors) +*/ + +class disapprove_post extends \phpbb\notification\type\approve_post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.disapprove_post'; + } + + /** + * Get the CSS style class of the notification + * + * @return string + */ + public function get_style_class() + { + return 'notification-disapproved'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_POST_DISAPPROVED'; + + /** + * Inherit notification read status from post. + * + * @var bool + */ + protected $inherit_read_status = false; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'id' => 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + return $this->language->lang($this->language_key); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + censor_text($this->get_data('topic_title')) + ); + } + + /** + * Get the reason for the disapproval notification + * + * @return string + */ + public function get_reason() + { + return $this->language->lang( + 'NOTIFICATION_REASON', + $this->get_data('disapprove_reason') + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array_merge(parent::get_email_template_variables(), array( + 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')), + )); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('disapprove_reason', $post['disapprove_reason']); + + parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = time(); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = parent::get_insert_array(); + $data['notification_time'] = $this->notification_time; + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_disapproved'; + } +} diff --git a/phpbb/notification/type/disapprove_topic.php b/phpbb/notification/type/disapprove_topic.php new file mode 100644 index 0000000..c2522fb --- /dev/null +++ b/phpbb/notification/type/disapprove_topic.php @@ -0,0 +1,159 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Topic disapproved notifications class +* This class handles notifications for topics when they are disapproved (for authors) +*/ + +class disapprove_topic extends \phpbb\notification\type\approve_topic +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.disapprove_topic'; + } + + /** + * Get the CSS style class of the notification + * + * @return string + */ + public function get_style_class() + { + return 'notification-disapproved'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_TOPIC_DISAPPROVED'; + + /** + * Inherit notification read status from topic. + * + * @var bool + */ + protected $inherit_read_status = false; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'id' => 'moderation_queue', + 'lang' => 'NOTIFICATION_TYPE_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + return $this->language->lang($this->language_key); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + censor_text($this->get_data('topic_title')) + ); + } + + /** + * Get the reason for the disapproval notification + * + * @return string + */ + public function get_reason() + { + return $this->language->lang( + 'NOTIFICATION_REASON', + $this->get_data('disapprove_reason') + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array_merge(parent::get_email_template_variables(), array( + 'REASON' => htmlspecialchars_decode($this->get_data('disapprove_reason')), + )); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('disapprove_reason', $post['disapprove_reason']); + + parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = time(); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = parent::get_insert_array(); + $data['notification_time'] = $this->notification_time; + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_disapproved'; + } +} diff --git a/phpbb/notification/type/group_request.php b/phpbb/notification/type/group_request.php new file mode 100644 index 0000000..28a9e73 --- /dev/null +++ b/phpbb/notification/type/group_request.php @@ -0,0 +1,169 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +class group_request extends \phpbb\notification\type\base +{ + /** + * {@inheritdoc} + */ + public function get_type() + { + return 'notification.type.group_request'; + } + + /** + * {@inheritdoc} + */ + static public $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_GROUP_REQUEST', + ); + + /** @var \phpbb\user_loader */ + protected $user_loader; + + public function set_user_loader(\phpbb\user_loader $user_loader) + { + $this->user_loader = $user_loader; + } + + /** + * {@inheritdoc} + */ + public function is_available() + { + // Leader of any groups? + $sql = 'SELECT group_id + FROM ' . USER_GROUP_TABLE . ' + WHERE user_id = ' . (int) $this->user->data['user_id'] . ' + AND group_leader = 1'; + $result = $this->db->sql_query_limit($sql, 1); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + return (!empty($row)) ? true : false; + } + + /** + * {@inheritdoc} + */ + static public function get_item_id($group) + { + return (int) $group['user_id']; + } + + /** + * {@inheritdoc} + */ + static public function get_item_parent_id($group) + { + // Group id is the parent + return (int) $group['group_id']; + } + + /** + * {@inheritdoc} + */ + public function find_users_for_notification($group, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $sql = 'SELECT user_id + FROM ' . USER_GROUP_TABLE . ' + WHERE group_leader = 1 + AND group_id = ' . (int) $group['group_id']; + $result = $this->db->sql_query($sql); + + $user_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $user_ids[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + $this->user_loader->load_users($user_ids); + + return $this->check_user_notification_options($user_ids, $options); + } + + /** + * {@inheritdoc} + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->item_id, false, true); + } + + /** + * {@inheritdoc} + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->item_id, 'no_profile'); + + return $this->language->lang('NOTIFICATION_GROUP_REQUEST', $username, $this->get_data('group_name')); + } + + /** + * {@inheritdoc} + */ + public function get_email_template() + { + return 'group_request'; + } + + /** + * {@inheritdoc} + */ + public function get_email_template_variables() + { + $user_data = $this->user_loader->get_user($this->item_id); + + return array( + 'GROUP_NAME' => htmlspecialchars_decode($this->get_data('group_name')), + 'REQUEST_USERNAME' => htmlspecialchars_decode($user_data['username']), + + 'U_PENDING' => generate_board_url() . "/ucp.{$this->php_ext}?i=groups&mode=manage&action=list&g={$this->item_parent_id}", + 'U_GROUP' => generate_board_url() . "/memberlist.{$this->php_ext}?mode=group&g={$this->item_parent_id}", + ); + } + + /** + * {@inheritdoc} + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'ucp.' . $this->php_ext, "i=groups&mode=manage&action=list&g={$this->item_parent_id}"); + } + + /** + * {@inheritdoc} + */ + public function users_to_query() + { + return array($this->item_id); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($group, $pre_create_data = array()) + { + $this->set_data('group_name', $group['group_name']); + + parent::create_insert_array($group, $pre_create_data); + } +} diff --git a/phpbb/notification/type/group_request_approved.php b/phpbb/notification/type/group_request_approved.php new file mode 100644 index 0000000..f55d28b --- /dev/null +++ b/phpbb/notification/type/group_request_approved.php @@ -0,0 +1,116 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +class group_request_approved extends \phpbb\notification\type\base +{ + /** + * {@inheritdoc} + */ + public function get_type() + { + return 'notification.type.group_request_approved'; + } + + /** + * {@inheritdoc} + */ + public function is_available() + { + return false; + } + + /** + * {@inheritdoc} + */ + static public function get_item_id($group) + { + return (int) $group['group_id']; + } + + /** + * {@inheritdoc} + */ + static public function get_item_parent_id($group) + { + return 0; + } + + /** + * {@inheritdoc} + */ + public function find_users_for_notification($group, $options = array()) + { + $users = array(); + + $group['user_ids'] = (!is_array($group['user_ids'])) ? array($group['user_ids']) : $group['user_ids']; + + foreach ($group['user_ids'] as $user_id) + { + $users[$user_id] = $this->notification_manager->get_default_methods(); + } + + return $users; + } + + /** + * {@inheritdoc} + */ + public function get_title() + { + return $this->language->lang('NOTIFICATION_GROUP_REQUEST_APPROVED', $this->get_data('group_name')); + } + + /** + * {@inheritdoc} + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'memberlist.' . $this->php_ext, "mode=group&g={$this->item_id}"); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($group, $pre_create_data = array()) + { + $this->set_data('group_name', $group['group_name']); + + parent::create_insert_array($group, $pre_create_data); + } + + /** + * {@inheritdoc} + */ + public function users_to_query() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function get_email_template() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function get_email_template_variables() + { + return array(); + } +} diff --git a/phpbb/notification/type/pm.php b/phpbb/notification/type/pm.php new file mode 100644 index 0000000..c51586a --- /dev/null +++ b/phpbb/notification/type/pm.php @@ -0,0 +1,205 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Private message notifications class +* This class handles notifications for private messages +*/ + +class pm extends \phpbb\notification\type\base +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.pm'; + } + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_PM', + ); + + /** @var \phpbb\user_loader */ + protected $user_loader; + + /** @var \phpbb\config\config */ + protected $config; + + public function set_config(\phpbb\config\config $config) + { + $this->config = $config; + } + + public function set_user_loader(\phpbb\user_loader $user_loader) + { + $this->user_loader = $user_loader; + } + + /** + * Is available + */ + public function is_available() + { + return ($this->config['allow_privmsg'] && $this->auth->acl_get('u_readpm')); + } + + /** + * Get the id of the + * + * @param array $pm The data from the private message + */ + static public function get_item_id($pm) + { + return (int) $pm['msg_id']; + } + + /** + * Get the id of the parent + * + * @param array $pm The data from the pm + */ + static public function get_item_parent_id($pm) + { + // No parent + return 0; + } + + /** + * Find the users who want to receive notifications + * + * @param array $pm Data from submit_pm + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($pm, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + if (!count($pm['recipients'])) + { + return array(); + } + + unset($pm['recipients'][$pm['from_user_id']]); + + $this->user_loader->load_users(array_keys($pm['recipients'])); + + return $this->check_user_notification_options(array_keys($pm['recipients']), $options); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('from_user_id'), false, true); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('from_user_id'), 'no_profile'); + + return $this->language->lang('NOTIFICATION_PM', $username); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + $this->get_data('message_subject') + ); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'privmsg_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $user_data = $this->user_loader->get_user($this->get_data('from_user_id')); + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), + + 'U_VIEW_MESSAGE' => generate_board_url() . '/ucp.' . $this->php_ext . "?i=pm&mode=view&p={$this->item_id}", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'ucp.' . $this->php_ext, "i=pm&mode=view&p={$this->item_id}"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('from_user_id')); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($pm, $pre_create_data = array()) + { + $this->set_data('from_user_id', $pm['from_user_id']); + + $this->set_data('message_subject', $pm['message_subject']); + + parent::create_insert_array($pm, $pre_create_data); + } +} diff --git a/phpbb/notification/type/post.php b/phpbb/notification/type/post.php new file mode 100644 index 0000000..254f4c0 --- /dev/null +++ b/phpbb/notification/type/post.php @@ -0,0 +1,467 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Post notifications class +* This class handles notifications for replies to a topic +*/ + +class post extends \phpbb\notification\type\base +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.post'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_POST'; + + /** + * Inherit notification read status from post. + * + * @var bool + */ + protected $inherit_read_status = true; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_POST', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** @var \phpbb\user_loader */ + protected $user_loader; + + /** @var \phpbb\config\config */ + protected $config; + + public function set_config(\phpbb\config\config $config) + { + $this->config = $config; + } + + public function set_user_loader(\phpbb\user_loader $user_loader) + { + $this->user_loader = $user_loader; + } + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_topic_notify']; + } + + /** + * Get the id of the item + * + * @param array $post The data from the post + * @return int The post id + */ + static public function get_item_id($post) + { + return (int) $post['post_id']; + } + + /** + * Get the id of the parent + * + * @param array $post The data from the post + * @return int The topic id + */ + static public function get_item_parent_id($post) + { + return (int) $post['topic_id']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from submit_post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . TOPICS_WATCH_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND notify_status = ' . NOTIFY_YES . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + $sql = 'SELECT user_id + FROM ' . FORUMS_WATCH_TABLE . ' + WHERE forum_id = ' . (int) $post['forum_id'] . ' + AND notify_status = ' . NOTIFY_YES . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + $notify_users = $this->get_authorised_recipients($users, $post['forum_id'], $options, true); + + if (empty($notify_users)) + { + return array(); + } + + // Try to find the users who already have been notified about replies and have not read the topic since and just update their notifications + $notified_users = $this->notification_manager->get_notified_users($this->get_type(), array( + 'item_parent_id' => static::get_item_parent_id($post), + 'read' => 0, + )); + + foreach ($notified_users as $user => $notification_data) + { + unset($notify_users[$user]); + + /** @var post $notification */ + $notification = $this->notification_manager->get_item_type_class($this->get_type(), $notification_data); + $update_responders = $notification->add_responders($post); + if (!empty($update_responders)) + { + $this->notification_manager->update_notification($notification, $update_responders, array( + 'item_parent_id' => self::get_item_parent_id($post), + 'read' => 0, + 'user_id' => $user, + )); + } + } + + return $notify_users; + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('poster_id'), false, true); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $responders = $this->get_data('responders'); + $usernames = array(); + + if (!is_array($responders)) + { + $responders = array(); + } + + $responders = array_merge(array(array( + 'poster_id' => $this->get_data('poster_id'), + 'username' => $this->get_data('post_username'), + )), $responders); + + $responders_cnt = count($responders); + $responders = $this->trim_user_ary($responders); + $trimmed_responders_cnt = $responders_cnt - count($responders); + + foreach ($responders as $responder) + { + if ($responder['username']) + { + $usernames[] = $responder['username']; + } + else + { + $usernames[] = $this->user_loader->get_username($responder['poster_id'], 'no_profile'); + } + } + + if ($trimmed_responders_cnt > 20) + { + $usernames[] = $this->language->lang('NOTIFICATION_MANY_OTHERS'); + } + else if ($trimmed_responders_cnt) + { + $usernames[] = $this->language->lang('NOTIFICATION_X_OTHERS', $trimmed_responders_cnt); + } + + return $this->language->lang( + $this->language_key, + phpbb_generate_string_list($usernames, $this->user), + $responders_cnt + ); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + censor_text($this->get_data('topic_title')) + ); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'username'); + } + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($username), + 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_VIEW_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}", + 'U_NEWEST_POST' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&e=1&view=unread#unread", + 'U_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_VIEW_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_FORUM' => generate_board_url() . "/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}", + 'U_STOP_WATCHING_TOPIC' => generate_board_url() . "/viewtopic.{$this->php_ext}?uid={$this->user_id}&f={$this->get_data('forum_id')}&t={$this->item_parent_id}&unwatch=topic", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "p={$this->item_id}#p{$this->item_id}"); + } + + /** + * {inheritDoc} + */ + public function get_redirect_url() + { + return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "t={$this->item_parent_id}&view=unread#unread"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + $responders = $this->get_data('responders'); + $users = array( + $this->get_data('poster_id'), + ); + + if (is_array($responders)) + { + foreach ($responders as $responder) + { + $users[] = $responder['poster_id']; + } + } + + return $this->trim_user_ary($users); + } + + /** + * Trim the user array passed down to 3 users if the array contains + * more than 4 users. + * + * @param array $users Array of users + * @return array Trimmed array of user_ids + */ + public function trim_user_ary($users) + { + if (count($users) > 4) + { + array_splice($users, 3); + } + return $users; + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + if (!count($notify_users) || !$this->inherit_read_status) + { + return array(); + } + + $tracking_data = array(); + $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users)); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $tracking_data[$row['user_id']] = $row['mark_time']; + } + $this->db->sql_freeresult($result); + + return $tracking_data; + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('poster_id', $post['poster_id']); + + $this->set_data('topic_title', $post['topic_title']); + + $this->set_data('post_subject', $post['post_subject']); + + $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : '')); + + $this->set_data('forum_id', $post['forum_id']); + + $this->set_data('forum_name', $post['forum_name']); + + $this->notification_time = $post['post_time']; + + // Topics can be "read" before they are public (while awaiting approval). + // Make sure that if the user has read the topic, it's marked as read in the notification + if ($this->inherit_read_status && isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) + { + $this->notification_read = true; + } + + parent::create_insert_array($post, $pre_create_data); + } + + /** + * Add responders to the notification + * + * @param mixed $post + * @return array Array of responder data + */ + public function add_responders($post) + { + // Do not add them as a responder if they were the original poster that created the notification + if ($this->get_data('poster_id') == $post['poster_id']) + { + return array(); + } + + $responders = $this->get_data('responders'); + + $responders = ($responders === null) ? array() : $responders; + + // Do not add more than 25 responders, + // we trim the username list to "a, b, c and x others" anyway + // so there is no use to add all of them anyway. + if (count($responders) > 25) + { + return array(); + } + + foreach ($responders as $responder) + { + // Do not add them as a responder multiple times + if ($responder['poster_id'] == $post['poster_id']) + { + return array(); + } + } + + $responders[] = array( + 'poster_id' => $post['poster_id'], + 'username' => (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : ''), + ); + + $this->set_data('responders', $responders); + + $serialized_data = serialize($this->get_data(false)); + + // If the data is longer then 4000 characters, it would cause a SQL error. + // We don't add the username to the list if this is the case. + if (utf8_strlen($serialized_data) >= 4000) + { + return array(); + } + + $data_array = array_merge(array( + 'post_time' => $post['post_time'], + 'post_id' => $post['post_id'], + 'topic_id' => $post['topic_id'] + ), $this->get_data(false)); + + return $data_array; + } +} diff --git a/phpbb/notification/type/post_in_queue.php b/phpbb/notification/type/post_in_queue.php new file mode 100644 index 0000000..2d556fc --- /dev/null +++ b/phpbb/notification/type/post_in_queue.php @@ -0,0 +1,163 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Post in queue notifications class +* This class handles notifications for posts that are put in the moderation queue (for moderators) +*/ + +class post_in_queue extends \phpbb\notification\type\post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.post_in_queue'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_POST_IN_QUEUE'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'id' => 'notification.type.needs_approval', + 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_approve'; + + /** + * Is available + */ + public function is_available() + { + $has_permission = $this->auth->acl_getf($this->permission, true); + + return (!empty($has_permission)); + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from the post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // 0 is for global moderator permissions + $auth_approve = $this->auth->acl_get_list(false, $this->permission, array($post['forum_id'], 0)); + + if (empty($auth_approve)) + { + return array(); + } + + $has_permission = array(); + + if (isset($auth_approve[$post['forum_id']][$this->permission])) + { + $has_permission = $auth_approve[$post['forum_id']][$this->permission]; + } + + if (isset($auth_approve[0][$this->permission])) + { + $has_permission = array_unique(array_merge($has_permission, $auth_approve[0][$this->permission])); + } + sort($has_permission); + + $auth_read = $this->auth->acl_get_list($has_permission, 'f_read', $post['forum_id']); + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$post['forum_id']]['f_read'], array_merge($options, array( + 'item_type' => static::$notification_option['id'], + ))); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&mode=approve_details&f={$this->get_data('forum_id')}&p={$this->item_id}"); + } + + /** + * {inheritDoc} + */ + public function get_redirect_url() + { + return parent::get_url(); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = time(); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = parent::get_insert_array(); + $data['notification_time'] = $this->notification_time; + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'post_in_queue'; + } +} diff --git a/phpbb/notification/type/quote.php b/phpbb/notification/type/quote.php new file mode 100644 index 0000000..323c18b --- /dev/null +++ b/phpbb/notification/type/quote.php @@ -0,0 +1,184 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Post quoting notifications class +* This class handles notifying users when they have been quoted in a post +*/ + +class quote extends \phpbb\notification\type\post +{ + /** + * @var \phpbb\textformatter\utils_interface + */ + protected $utils; + + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.quote'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_QUOTE'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_QUOTE', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** + * Is available + */ + public function is_available() + { + return true; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from submit_post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $usernames = $this->utils->get_outermost_quote_authors($post['post_text']); + + if (empty($usernames)) + { + return array(); + } + + $usernames = array_unique($usernames); + + $usernames = array_map('utf8_clean_string', $usernames); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . USERS_TABLE . ' + WHERE ' . $this->db->sql_in_set('username_clean', $usernames) . ' + AND user_id <> ' . (int) $post['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + return $this->get_authorised_recipients($users, $post['forum_id'], $options, true); + } + + /** + * Update a notification + * + * @param array $post Data specific for this type that will be updated + * @return true + */ + public function update_notifications($post) + { + $old_notifications = $this->notification_manager->get_notified_users($this->get_type(), array( + 'item_id' => static::get_item_id($post), + )); + + // Find the new users to notify + $notifications = $this->find_users_for_notification($post); + + // Find the notifications we must delete + $remove_notifications = array_diff(array_keys($old_notifications), array_keys($notifications)); + + // Find the notifications we must add + $add_notifications = array(); + foreach (array_diff(array_keys($notifications), array_keys($old_notifications)) as $user_id) + { + $add_notifications[$user_id] = $notifications[$user_id]; + } + + // Add the necessary notifications + $this->notification_manager->add_notifications_for_users($this->get_type(), $post, $add_notifications); + + // Remove the necessary notifications + if (!empty($remove_notifications)) + { + $this->notification_manager->delete_notifications($this->get_type(), static::get_item_id($post), false, $remove_notifications); + } + + // return true to continue with the update code in the notifications service (this will update the rest of the notifications) + return true; + } + + /** + * {inheritDoc} + */ + public function get_redirect_url() + { + return $this->get_url(); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'quote'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $user_data = $this->user_loader->get_user($this->get_data('poster_id')); + + return array_merge(parent::get_email_template_variables(), array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + )); + } + + /** + * Set the utils service used to retrieve quote authors + * + * @param \phpbb\textformatter\utils_interface $utils + */ + public function set_utils(\phpbb\textformatter\utils_interface $utils) + { + $this->utils = $utils; + } +} diff --git a/phpbb/notification/type/report_pm.php b/phpbb/notification/type/report_pm.php new file mode 100644 index 0000000..444f982 --- /dev/null +++ b/phpbb/notification/type/report_pm.php @@ -0,0 +1,259 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Private message reported notifications class +* This class handles notifications for private messages when they are reported +*/ + +class report_pm extends \phpbb\notification\type\pm +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.report_pm'; + } + + /** + * Get the CSS style class of the notification + * + * @return string + */ + public function get_style_class() + { + return 'notification-reported'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_REPORT_PM'; + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_pm_report'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'id' => 'notification.type.report', + 'lang' => 'NOTIFICATION_TYPE_REPORT', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Get the id of the parent + * + * @param array $pm The data from the pm + * @return int The report id + */ + static public function get_item_parent_id($pm) + { + return (int) $pm['report_id']; + } + + /** + * Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options) + * + * @return bool True/False whether or not this is available to the user + */ + public function is_available() + { + $m_approve = $this->auth->acl_getf($this->permission, true); + + return (!empty($m_approve)); + } + + + /** + * Find the users who want to receive notifications + * (copied from post_in_queue) + * + * @param array $post Data from the post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // Global + $post['forum_id'] = 0; + + $auth_approve = $this->auth->acl_get_list(false, $this->permission, $post['forum_id']); + + if (empty($auth_approve)) + { + return array(); + } + + if (($key = array_search($this->user->data['user_id'], $auth_approve[$post['forum_id']][$this->permission]))) + { + unset($auth_approve[$post['forum_id']][$this->permission][$key]); + } + + return $this->check_user_notification_options($auth_approve[$post['forum_id']][$this->permission], array_merge($options, array( + 'item_type' => static::$notification_option['id'], + ))); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'report_pm'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $user_data = $this->user_loader->get_user($this->get_data('from_user_id')); + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($user_data['username']), + 'SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), + + /** @deprecated 3.2.6-RC1 (to be removed in 4.0.0) use {SUBJECT} instead in report_pm.txt */ + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('message_subject'))), + + 'U_VIEW_REPORT' => generate_board_url() . "/mcp.{$this->php_ext}?r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "r={$this->item_parent_id}&i=pm_reports&mode=pm_report_details"); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $this->language->add_lang('mcp'); + + $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); + + return $this->language->lang( + $this->language_key, + $username + ); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + censor_text($this->get_data('message_subject')) + ); + } + + /** + * Get the reason for the notification + * + * @return string + */ + public function get_reason() + { + if ($this->get_data('report_text')) + { + return $this->language->lang( + 'NOTIFICATION_REASON', + $this->get_data('report_text') + ); + } + + if ($this->language->is_set($this->get_data('reason_title'))) + { + return $this->language->lang( + 'NOTIFICATION_REASON', + $this->language->lang($this->get_data('reason_title')) + ); + } + + return $this->language->lang( + 'NOTIFICATION_REASON', + $this->get_data('reason_description') + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('reporter_id'), false, true); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array( + $this->get_data('from_user_id'), + $this->get_data('reporter_id'), + ); } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('reporter_id', $this->user->data['user_id']); + $this->set_data('reason_title', strtoupper($post['reason_title'])); + $this->set_data('reason_description', $post['reason_description']); + $this->set_data('report_text', $post['report_text']); + + parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpbb/notification/type/report_pm_closed.php b/phpbb/notification/type/report_pm_closed.php new file mode 100644 index 0000000..5e98eb5 --- /dev/null +++ b/phpbb/notification/type/report_pm_closed.php @@ -0,0 +1,168 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* PM report closed notifications class +* This class handles notifications for when reports are closed on PMs (for the one who reported the PM) +*/ + +class report_pm_closed extends \phpbb\notification\type\pm +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.report_pm_closed'; + } + + /** + * Email template to use to send notifications + * + * @var string + */ + public $email_template = ''; + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_REPORT_CLOSED'; + + public function is_available() + { + return false; + } + + /** + * Find the users who want to receive notifications + * + * @param array $pm Data from submit_pm + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($pm, $options = array()) + { + if ($pm['reporter'] == $this->user->data['user_id']) + { + return array(); + } + + return array($pm['reporter'] => $this->notification_manager->get_default_methods()); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return false; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array(); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); + + return $this->language->lang( + $this->language_key, + $username + ); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + censor_text($this->get_data('message_subject')) + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('closer_id'), false, true); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('closer_id')); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($pm, $pre_create_data = array()) + { + $this->set_data('closer_id', $pm['closer_id']); + + parent::create_insert_array($pm, $pre_create_data); + + $this->notification_time = time(); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = parent::get_insert_array(); + $data['notification_time'] = $this->notification_time; + + return $data; + } +} diff --git a/phpbb/notification/type/report_post.php b/phpbb/notification/type/report_post.php new file mode 100644 index 0000000..84a5241 --- /dev/null +++ b/phpbb/notification/type/report_post.php @@ -0,0 +1,224 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Reported post notifications class +* This class handles notifications for reported posts +*/ +class report_post extends \phpbb\notification\type\post_in_queue +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.report_post'; + } + + /** + * Get the CSS style class of the notification + * + * @return string + */ + public function get_style_class() + { + return 'notification-reported'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_REPORT_POST'; + + /** + * Inherit notification read status from post. + * + * @var bool + */ + protected $inherit_read_status = false; + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_report'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id' and 'lang') + */ + static public $notification_option = array( + 'id' => 'notification.type.report', + 'lang' => 'NOTIFICATION_TYPE_REPORT', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from the post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + $notify_users = parent::find_users_for_notification($post, $options); + + // never notify reporter + unset($notify_users[$this->user->data['user_id']]); + + return $notify_users; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'report_post'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $board_url = generate_board_url(); + + return array( + 'POST_SUBJECT' => htmlspecialchars_decode(censor_text($this->get_data('post_subject'))), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_VIEW_REPORT' => "{$board_url}/mcp.{$this->php_ext}?f={$this->get_data('forum_id')}&p={$this->item_id}&i=reports&mode=report_details#reports", + 'U_VIEW_POST' => "{$board_url}/viewtopic.{$this->php_ext}?p={$this->item_id}#p{$this->item_id}", + 'U_NEWEST_POST' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}&view=unread#unread", + 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->get_data('forum_id')}&t={$this->item_parent_id}", + 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->get_data('forum_id')}", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "f={$this->get_data('forum_id')}&p={$this->item_id}&i=reports&mode=report_details#reports"); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $this->language->add_lang('mcp'); + + $username = $this->user_loader->get_username($this->get_data('reporter_id'), 'no_profile'); + + return $this->language->lang( + $this->language_key, + $username + ); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + censor_text($this->get_data('post_subject')) + ); + } + + /** + * Get the reason for the notification + * + * @return string + */ + public function get_reason() + { + if ($this->get_data('report_text')) + { + return $this->language->lang( + 'NOTIFICATION_REASON', + $this->get_data('report_text') + ); + } + + if ($this->language->is_set($this->get_data('reason_title'))) + { + return $this->language->lang( + 'NOTIFICATION_REASON', + $this->language->lang($this->get_data('reason_title')) + ); + } + + return $this->language->lang( + 'NOTIFICATION_REASON', + $this->get_data('reason_description') + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('reporter_id'), false, true); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('reporter_id')); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('reporter_id', $this->user->data['user_id']); + $this->set_data('reason_title', strtoupper($post['reason_title'])); + $this->set_data('reason_description', $post['reason_description']); + $this->set_data('report_text', $post['report_text']); + + parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpbb/notification/type/report_post_closed.php b/phpbb/notification/type/report_post_closed.php new file mode 100644 index 0000000..165034d --- /dev/null +++ b/phpbb/notification/type/report_post_closed.php @@ -0,0 +1,175 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Post report closed notifications class +* This class handles notifications for when reports are closed on posts (for the one who reported the post) +*/ + +class report_post_closed extends \phpbb\notification\type\post +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.report_post_closed'; + } + + /** + * Email template to use to send notifications + * + * @var string + */ + public $email_template = ''; + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_REPORT_CLOSED'; + + /** + * Inherit notification read status from post. + * + * @var bool + */ + protected $inherit_read_status = false; + + public function is_available() + { + return false; + } + + /** + * Find the users who want to receive notifications + * + * @param array $post Data from submit_post + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($post, $options = array()) + { + if ($post['reporter'] == $this->user->data['user_id']) + { + return array(); + } + + return array($post['reporter'] => $this->notification_manager->get_default_methods()); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return false; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + return array(); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return ''; + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + $username = $this->user_loader->get_username($this->get_data('closer_id'), 'no_profile'); + + return $this->language->lang( + $this->language_key, + $username + ); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + censor_text($this->get_data('post_subject')) + ); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('closer_id'), false, true); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('closer_id')); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('closer_id', $post['closer_id']); + + parent::create_insert_array($post, $pre_create_data); + + $this->notification_time = time(); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = parent::get_insert_array(); + $data['notification_time'] = $this->notification_time; + + return $data; + } +} diff --git a/phpbb/notification/type/topic.php b/phpbb/notification/type/topic.php new file mode 100644 index 0000000..5c42afa --- /dev/null +++ b/phpbb/notification/type/topic.php @@ -0,0 +1,307 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Topic notifications class +* This class handles notifications for new topics +*/ + +class topic extends \phpbb\notification\type\base +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.topic'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_TOPIC'; + + /** + * Inherit notification read status from topic. + * + * @var bool + */ + protected $inherit_read_status = true; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'lang' => 'NOTIFICATION_TYPE_TOPIC', + 'group' => 'NOTIFICATION_GROUP_POSTING', + ); + + /** @var \phpbb\user_loader */ + protected $user_loader; + + /** @var \phpbb\config\config */ + protected $config; + + public function set_config(\phpbb\config\config $config) + { + $this->config = $config; + } + + public function set_user_loader(\phpbb\user_loader $user_loader) + { + $this->user_loader = $user_loader; + } + + /** + * Is available + */ + public function is_available() + { + return $this->config['allow_forum_notify']; + } + + /** + * Get the id of the item + * + * @param array $post The data from the post + * @return int The topic id + */ + static public function get_item_id($post) + { + return (int) $post['topic_id']; + } + + /** + * Get the id of the parent + * + * @param array $post The data from the post + * @return int The forum id + */ + static public function get_item_parent_id($post) + { + return (int) $post['forum_id']; + } + + /** + * Find the users who want to receive notifications + * + * @param array $topic Data from the topic + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($topic, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + $users = array(); + + $sql = 'SELECT user_id + FROM ' . FORUMS_WATCH_TABLE . ' + WHERE forum_id = ' . (int) $topic['forum_id'] . ' + AND notify_status = ' . NOTIFY_YES . ' + AND user_id <> ' . (int) $topic['poster_id']; + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $users[] = (int) $row['user_id']; + } + $this->db->sql_freeresult($result); + + return $this->get_authorised_recipients($users, $topic['forum_id'], $options); + } + + /** + * Get the user's avatar + */ + public function get_avatar() + { + return $this->user_loader->get_avatar($this->get_data('poster_id'), false, true); + } + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title() + { + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'no_profile'); + } + + return $this->language->lang( + $this->language_key, + $username + ); + } + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference() + { + return $this->language->lang( + 'NOTIFICATION_REFERENCE', + censor_text($this->get_data('topic_title')) + ); + } + + /** + * Get the forum of the notification reference + * + * @return string + */ + public function get_forum() + { + return $this->language->lang( + 'NOTIFICATION_FORUM', + $this->get_data('forum_name') + ); + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'newtopic_notify'; + } + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables() + { + $board_url = generate_board_url(); + + if ($this->get_data('post_username')) + { + $username = $this->get_data('post_username'); + } + else + { + $username = $this->user_loader->get_username($this->get_data('poster_id'), 'username'); + } + + return array( + 'AUTHOR_NAME' => htmlspecialchars_decode($username), + 'FORUM_NAME' => htmlspecialchars_decode($this->get_data('forum_name')), + 'TOPIC_TITLE' => htmlspecialchars_decode(censor_text($this->get_data('topic_title'))), + + 'U_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}", + 'U_VIEW_TOPIC' => "{$board_url}/viewtopic.{$this->php_ext}?f={$this->item_parent_id}&t={$this->item_id}", + 'U_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?f={$this->item_parent_id}", + 'U_STOP_WATCHING_FORUM' => "{$board_url}/viewforum.{$this->php_ext}?uid={$this->user_id}&f={$this->item_parent_id}&unwatch=forum", + ); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'viewtopic.' . $this->php_ext, "f={$this->item_parent_id}&t={$this->item_id}"); + } + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query() + { + return array($this->get_data('poster_id')); + } + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $post Post data from submit_post + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($post, $notify_users) + { + if (!count($notify_users) || !$this->inherit_read_status) + { + return array(); + } + + $tracking_data = array(); + $sql = 'SELECT user_id, mark_time FROM ' . TOPICS_TRACK_TABLE . ' + WHERE topic_id = ' . (int) $post['topic_id'] . ' + AND ' . $this->db->sql_in_set('user_id', array_keys($notify_users)); + $result = $this->db->sql_query($sql); + while ($row = $this->db->sql_fetchrow($result)) + { + $tracking_data[$row['user_id']] = $row['mark_time']; + } + $this->db->sql_freeresult($result); + + return $tracking_data; + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($post, $pre_create_data = array()) + { + $this->set_data('poster_id', $post['poster_id']); + + $this->set_data('topic_title', $post['topic_title']); + + $this->set_data('post_username', (($post['poster_id'] == ANONYMOUS) ? $post['post_username'] : '')); + + $this->set_data('forum_name', $post['forum_name']); + + $this->notification_time = $post['post_time']; + + // Topics can be "read" before they are public (while awaiting approval). + // Make sure that if the user has read the topic, it's marked as read in the notification + if ($this->inherit_read_status && isset($pre_create_data[$this->user_id]) && $pre_create_data[$this->user_id] >= $this->notification_time) + { + $this->notification_read = true; + } + + parent::create_insert_array($post, $pre_create_data); + } +} diff --git a/phpbb/notification/type/topic_in_queue.php b/phpbb/notification/type/topic_in_queue.php new file mode 100644 index 0000000..2d732b9 --- /dev/null +++ b/phpbb/notification/type/topic_in_queue.php @@ -0,0 +1,155 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Topic in queue notifications class +* This class handles notifications for topics when they are put in the moderation queue (for moderators) +*/ + +class topic_in_queue extends \phpbb\notification\type\topic +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type() + { + return 'notification.type.topic_in_queue'; + } + + /** + * Language key used to output the text + * + * @var string + */ + protected $language_key = 'NOTIFICATION_TOPIC_IN_QUEUE'; + + /** + * Notification option data (for outputting to the user) + * + * @var bool|array False if the service should use it's default data + * Array of data (including keys 'id', 'lang', and 'group') + */ + static public $notification_option = array( + 'id' => 'notification.type.needs_approval', + 'lang' => 'NOTIFICATION_TYPE_IN_MODERATION_QUEUE', + 'group' => 'NOTIFICATION_GROUP_MODERATION', + ); + + /** + * Permission to check for (in find_users_for_notification) + * + * @var string Permission name + */ + protected $permission = 'm_approve'; + + /** + * Is available + */ + public function is_available() + { + $has_permission = $this->auth->acl_getf($this->permission, true); + + return (!empty($has_permission)); + } + + /** + * Find the users who want to receive notifications + * + * @param array $topic Data from the topic + * @param array $options Options for finding users for notification + * + * @return array + */ + public function find_users_for_notification($topic, $options = array()) + { + $options = array_merge(array( + 'ignore_users' => array(), + ), $options); + + // 0 is for global moderator permissions + $auth_approve = $this->auth->acl_get_list(false, 'm_approve', array($topic['forum_id'], 0)); + + if (empty($auth_approve)) + { + return array(); + } + + $has_permission = array(); + + if (isset($auth_approve[$topic['forum_id']][$this->permission])) + { + $has_permission = $auth_approve[$topic['forum_id']][$this->permission]; + } + + if (isset($auth_approve[0][$this->permission])) + { + $has_permission = array_unique(array_merge($has_permission, $auth_approve[0][$this->permission])); + } + sort($has_permission); + + $auth_read = $this->auth->acl_get_list($has_permission, 'f_read', $topic['forum_id']); + if (empty($auth_read)) + { + return array(); + } + + return $this->check_user_notification_options($auth_read[$topic['forum_id']]['f_read'], array_merge($options, array( + 'item_type' => static::$notification_option['id'], + ))); + } + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url() + { + return append_sid($this->phpbb_root_path . 'mcp.' . $this->php_ext, "i=queue&mode=approve_details&f={$this->item_parent_id}&t={$this->item_id}"); + } + + /** + * {@inheritdoc} + */ + public function create_insert_array($topic, $pre_create_data = array()) + { + parent::create_insert_array($topic, $pre_create_data); + + $this->notification_time = time(); + } + + /** + * {@inheritdoc} + */ + public function get_insert_array() + { + $data = parent::get_insert_array(); + $data['notification_time'] = $this->notification_time; + + return $data; + } + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template() + { + return 'topic_in_queue'; + } +} diff --git a/phpbb/notification/type/type_interface.php b/phpbb/notification/type/type_interface.php new file mode 100644 index 0000000..f9f832b --- /dev/null +++ b/phpbb/notification/type/type_interface.php @@ -0,0 +1,218 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\notification\type; + +/** +* Base notifications interface +*/ +interface type_interface +{ + /** + * Get notification type name + * + * @return string + */ + public function get_type(); + + /** + * Set initial data from the database + * + * @param array $data Row directly from the database + */ + public function set_initial_data($data); + + /** + * Get the id of the item + * + * @param array $type_data The type specific data + */ + static public function get_item_id($type_data); + + /** + * Get the id of the parent + * + * @param array $type_data The type specific data + */ + static public function get_item_parent_id($type_data); + + /** + * Is this type available to the current user (defines whether or not it will be shown in the UCP Edit notification options) + * + * @return bool True/False whether or not this is available to the user + */ + public function is_available(); + + /** + * Find the users who want to receive notifications + * + * @param array $type_data The type specific data + * @param array $options Options for finding users for notification + * ignore_users => array of users and user types that should not receive notifications from this type because they've already been notified + * e.g.: array(2 => array(''), 3 => array('', 'email'), ...) + * + * @return array + */ + public function find_users_for_notification($type_data, $options); + + /** + * Users needed to query before this notification can be displayed + * + * @return array Array of user_ids + */ + public function users_to_query(); + + /** + * Get the special items to load + * + * @return array Data will be combined sent to load_special() so you can run a single query and get data required for this notification type + */ + public function get_load_special(); + + /** + * Load the special items + * + * @param array $data Data from get_load_special() + * @param array $notifications Array of notifications (key is notification_id, value is the notification objects) + */ + public function load_special($data, $notifications); + + /** + * Get the CSS style class of the notification + * + * @return string + */ + public function get_style_class(); + + /** + * Get the HTML formatted title of this notification + * + * @return string + */ + public function get_title(); + + /** + * Get the HTML formatted reference of the notification + * + * @return string + */ + public function get_reference(); + + /** + * Get the forum of the notification reference + * + * @return string + */ + public function get_forum(); + + /** + * Get the url to this item + * + * @return string URL + */ + public function get_url(); + + /** + * Get the url to redirect after the item has been marked as read + * + * @return string URL + */ + public function get_redirect_url(); + + /** + * URL to unsubscribe to this notification + * + * @param string|bool $method Method name to unsubscribe from (email|jabber|etc), False to unsubscribe from all notifications for this item + */ + public function get_unsubscribe_url($method); + + /** + * Get the user's avatar (the user who caused the notification typically) + * + * @return string + */ + public function get_avatar(); + + /** + * Prepare to output the notification to the template + */ + public function prepare_for_display(); + + /** + * Get email template + * + * @return string|bool + */ + public function get_email_template(); + + /** + * Get email template variables + * + * @return array + */ + public function get_email_template_variables(); + + /** + * Pre create insert array function + * This allows you to perform certain actions, like run a query + * and load data, before create_insert_array() is run. The data + * returned from this function will be sent to create_insert_array(). + * + * @param array $type_data The type specific data + * @param array $notify_users Notify users list + * Formated from find_users_for_notification() + * @return array Whatever you want to send to create_insert_array(). + */ + public function pre_create_insert_array($type_data, $notify_users); + + /** + * Function for preparing the data for insertion in an SQL query + * + * @param array $type_data The type specific data + * @param array $pre_create_data Data from pre_create_insert_array() + */ + public function create_insert_array($type_data, $pre_create_data); + + /** + * Function for getting the data for insertion in an SQL query + * + * @return array Array of data ready to be inserted into the database + */ + public function get_insert_array(); + + /** + * Function for preparing the data for update in an SQL query + * (The service handles insertion) + * + * @param array $type_data Data unique to this notification type + * + * @return array Array of data ready to be updated in the database + */ + public function create_update_array($type_data); + + /** + * Mark this item read + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string + */ + public function mark_read($return = false); + + /** + * Mark this item unread + * + * @param bool $return True to return a string containing the SQL code to update this item, False to execute it (Default: False) + * @return string + */ + public function mark_unread($return = false); +} diff --git a/phpbb/pagination.php b/phpbb/pagination.php new file mode 100644 index 0000000..a7086f6 --- /dev/null +++ b/phpbb/pagination.php @@ -0,0 +1,362 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +class pagination +{ + /** @var \phpbb\template\template */ + protected $template; + + /** @var \phpbb\user */ + protected $user; + + /** @var \phpbb\controller\helper */ + protected $helper; + + /** @var \phpbb\event\dispatcher_interface */ + protected $phpbb_dispatcher; + + /** + * Constructor + * + * @param \phpbb\template\template $template + * @param \phpbb\user $user + * @param \phpbb\controller\helper $helper + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher + */ + public function __construct(\phpbb\template\template $template, \phpbb\user $user, \phpbb\controller\helper $helper, \phpbb\event\dispatcher_interface $phpbb_dispatcher) + { + $this->template = $template; + $this->user = $user; + $this->helper = $helper; + $this->phpbb_dispatcher = $phpbb_dispatcher; + } + + /** + * Generate a pagination link based on the url and the page information + * + * @param string|array $base_url is url prepended to all links generated within the function + * If you use page numbers inside your controller route, base_url should contains a placeholder (%d) + * for the page. Also be sure to specify the pagination path information into the start_name argument + * @param string $on_page is the page for which we want to generate the link + * @param string $start_name is the name of the parameter containing the first item of the given page (example: start=20) + * If you use page numbers inside your controller route, start name should be the string + * that should be removed for the first page (example: /page/%d) + * @param int $per_page the number of items, posts, etc. to display per page, used to determine the number of pages to produce + * @return string URL for the requested page + */ + protected function generate_page_link($base_url, $on_page, $start_name, $per_page) + { + // A listener can set this variable to the new pagination URL + // to override the generate_page_link() function generated value + $generate_page_link_override = false; + + /** + * Execute code and/or override generate_page_link() + * + * To override the generate_page_link() function generated value + * set $generate_page_link_override to the new URL value + * + * @event core.pagination_generate_page_link + * @var string|array base_url is url prepended to all links generated within the function + * If you use page numbers inside your controller route, base_url should contains a placeholder (%d) + * for the page. Also be sure to specify the pagination path information into the start_name argument + * @var string on_page is the page for which we want to generate the link + * @var string start_name is the name of the parameter containing the first item of the given page (example: start=20) + * If you use page numbers inside your controller route, start name should be the string + * that should be removed for the first page (example: /page/%d) + * @var int per_page the number of items, posts, etc. to display per page, used to determine the number of pages to produce + * @var bool|string generate_page_link_override Shall we return custom pagination link (string URL) or not (false) + * @since 3.1.0-RC5 + */ + $vars = array('base_url', 'on_page', 'start_name', 'per_page', 'generate_page_link_override'); + extract($this->phpbb_dispatcher->trigger_event('core.pagination_generate_page_link', compact($vars))); + + if ($generate_page_link_override) + { + return $generate_page_link_override; + } + + if (!is_string($base_url)) + { + if (is_array($base_url['routes'])) + { + $route = ($on_page > 1) ? $base_url['routes'][1] : $base_url['routes'][0]; + } + else + { + $route = $base_url['routes']; + } + $params = (isset($base_url['params'])) ? $base_url['params'] : array(); + $is_amp = (isset($base_url['is_amp'])) ? $base_url['is_amp'] : true; + $session_id = (isset($base_url['session_id'])) ? $base_url['session_id'] : false; + + if ($on_page > 1 || !is_array($base_url['routes'])) + { + $params[$start_name] = (int) $on_page; + } + + return $this->helper->route($route, $params, $is_amp, $session_id); + } + else + { + $url_delim = (strpos($base_url, '?') === false) ? '?' : ((strpos($base_url, '?') === strlen($base_url) - 1) ? '' : '&'); + return ($on_page > 1) ? $base_url . $url_delim . $start_name . '=' . (($on_page - 1) * $per_page) : $base_url; + } + } + + /** + * Generate template rendered pagination + * Allows full control of rendering of pagination with the template + * + * @param string|array $base_url is url prepended to all links generated within the function + * If you use page numbers inside your controller route, base_url should contains a placeholder (%d) + * for the page. Also be sure to specify the pagination path information into the start_name argument + * @param string $block_var_name is the name assigned to the pagination data block within the template (example: ) + * @param string $start_name is the name of the parameter containing the first item of the given page (example: start=20) + * If you use page numbers inside your controller route, start name should be the string + * that should be removed for the first page (example: /page/%d) + * @param int $num_items the total number of items, posts, etc., used to determine the number of pages to produce + * @param int $per_page the number of items, posts, etc. to display per page, used to determine the number of pages to produce + * @param int $start the item which should be considered currently active, used to determine the page we're on + * @param bool $reverse_count determines whether we weight display of the list towards the start (false) or end (true) of the list + * @param bool $ignore_on_page decides whether we enable an active (unlinked) item, used primarily for embedded lists + * @return void + */ + public function generate_template_pagination($base_url, $block_var_name, $start_name, $num_items, $per_page, $start = 1, $reverse_count = false, $ignore_on_page = false) + { + if (empty($base_url)) + { + return; + } + + $total_pages = ceil($num_items / $per_page); + $on_page = $this->get_on_page($per_page, $start); + $u_previous_page = $u_next_page = ''; + + if ($total_pages > 1) + { + if ($reverse_count) + { + $start_page = ($total_pages > 5) ? $total_pages - 4 : 1; + $end_page = $total_pages; + } + else + { + // What we're doing here is calculating what the "start" and "end" pages should be. We + // do this by assuming pagination is "centered" around the currently active page with + // the three previous and three next page links displayed. Anything more than that and + // we display the ellipsis, likewise anything less. + // + // $start_page is the page at which we start creating the list. When we have five or less + // pages we start at page 1 since there will be no ellipsis displayed. Anymore than that + // and we calculate the start based on the active page. This is the min/max calculation. + // First (max) would we end up starting on a page less than 1? Next (min) would we end + // up starting so close to the end that we'd not display our minimum number of pages. + // + // $end_page is the last page in the list to display. Like $start_page we use a min/max to + // determine this number. Again at most five pages? Then just display them all. More than + // five and we first (min) determine whether we'd end up listing more pages than exist. + // We then (max) ensure we're displaying the minimum number of pages. + $start_page = ($total_pages > 5) ? min(max(1, $on_page - 2), $total_pages - 4) : 1; + $end_page = ($total_pages > 5) ? max(min($total_pages, $on_page + 2), 5) : $total_pages; + } + + if ($on_page != 1) + { + $u_previous_page = $this->generate_page_link($base_url, $on_page - 1, $start_name, $per_page); + + $this->template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => '', + 'PAGE_URL' => $u_previous_page, + 'S_IS_CURRENT' => false, + 'S_IS_PREV' => true, + 'S_IS_NEXT' => false, + 'S_IS_ELLIPSIS' => false, + )); + } + + // This do...while exists purely to negate the need for start and end assign_block_vars, i.e. + // to display the first and last page in the list plus any ellipsis. We use this loop to jump + // around a little within the list depending on where we're starting (and ending). + $at_page = 1; + do + { + // We decide whether to display the ellipsis during the loop. The ellipsis is always + // displayed as either the second or penultimate item in the list. So are we at either + // of those points and of course do we even need to display it, i.e. is the list starting + // on at least page 3 and ending three pages before the final item. + $this->template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => $at_page, + 'PAGE_URL' => $this->generate_page_link($base_url, $at_page, $start_name, $per_page), + 'S_IS_CURRENT' => (!$ignore_on_page && $at_page == $on_page), + 'S_IS_NEXT' => false, + 'S_IS_PREV' => false, + 'S_IS_ELLIPSIS' => ($at_page == 2 && $start_page > 2) || ($at_page == $total_pages - 1 && $end_page < $total_pages - 1), + )); + + // We may need to jump around in the list depending on whether we have or need to display + // the ellipsis. Are we on page 2 and are we more than one page away from the start + // of the list? Yes? Then we jump to the start of the list. Likewise are we at the end of + // the list and are there more than two pages left in total? Yes? Then jump to the penultimate + // page (so we can display the ellipsis next pass). Else, increment the counter and keep + // going + if ($at_page == 2 && $at_page < $start_page - 1) + { + $at_page = $start_page; + } + else if ($at_page == $end_page && $end_page < $total_pages - 1) + { + $at_page = $total_pages - 1; + } + else + { + $at_page++; + } + } + while ($at_page <= $total_pages); + + if ($on_page != $total_pages) + { + $u_next_page = $this->generate_page_link($base_url, $on_page + 1, $start_name, $per_page); + + $this->template->assign_block_vars($block_var_name, array( + 'PAGE_NUMBER' => '', + 'PAGE_URL' => $u_next_page, + 'S_IS_CURRENT' => false, + 'S_IS_PREV' => false, + 'S_IS_NEXT' => true, + 'S_IS_ELLIPSIS' => false, + )); + } + } + + // If the block_var_name is a nested block, we will use the last (most + // inner) block as a prefix for the template variables. If the last block + // name is pagination, the prefix is empty. If the rest of the + // block_var_name is not empty, we will modify the last row of that block + // and add our pagination items. + $tpl_block_name = $tpl_prefix = ''; + if (strrpos($block_var_name, '.') !== false) + { + $tpl_block_name = substr($block_var_name, 0, strrpos($block_var_name, '.')); + $tpl_prefix = strtoupper(substr($block_var_name, strrpos($block_var_name, '.') + 1)); + } + else + { + $tpl_prefix = strtoupper($block_var_name); + } + $tpl_prefix = ($tpl_prefix == 'PAGINATION') ? '' : $tpl_prefix . '_'; + + $template_array = array( + $tpl_prefix . 'BASE_URL' => is_string($base_url) ? $base_url : '',//@todo: Fix this for routes + $tpl_prefix . 'START_NAME' => $start_name, + $tpl_prefix . 'PER_PAGE' => $per_page, + 'U_' . $tpl_prefix . 'PREVIOUS_PAGE' => ($on_page != 1) ? $u_previous_page : '', + 'U_' . $tpl_prefix . 'NEXT_PAGE' => ($on_page != $total_pages) ? $u_next_page : '', + $tpl_prefix . 'TOTAL_PAGES' => $total_pages, + $tpl_prefix . 'CURRENT_PAGE' => $on_page, + $tpl_prefix . 'PAGE_NUMBER' => $this->on_page($num_items, $per_page, $start), + ); + + if ($tpl_block_name) + { + $this->template->alter_block_array($tpl_block_name, $template_array, true, 'change'); + } + else + { + $this->template->assign_vars($template_array); + } + } + + /** + * Get current page number + * + * @param int $per_page the number of items, posts, etc. per page + * @param int $start the item which should be considered currently active, used to determine the page we're on + * @return int Current page number + */ + public function get_on_page($per_page, $start) + { + return floor((int) $start / (int) $per_page) + 1; + } + + /** + * Return current page + * + * @param int $num_items the total number of items, posts, topics, etc. + * @param int $per_page the number of items, posts, etc. per page + * @param int $start the item which should be considered currently active, used to determine the page we're on + * @return string Descriptive pagination string (e.g. "page 1 of 10") + */ + public function on_page($num_items, $per_page, $start) + { + $on_page = $this->get_on_page($per_page, $start); + return $this->user->lang('PAGE_OF', $on_page, max(ceil($num_items / $per_page), 1)); + } + + /** + * Get current page number + * + * @param int $start the item which should be considered currently active, used to determine the page we're on + * @param int $per_page the number of items, posts, etc. per page + * @param int $num_items the total number of items, posts, topics, etc. + * @return int Current page number + */ + public function validate_start($start, $per_page, $num_items) + { + if ($start < 0 || $start >= $num_items) + { + return ($start < 0 || $num_items <= 0) ? 0 : floor(($num_items - 1) / $per_page) * $per_page; + } + + return $start; + } + + /** + * Get new start when searching from the end + * + * If the user is trying to reach late pages, start searching from the end. + * + * @param int $start the item which should be considered currently active, used to determine the page we're on + * @param int $limit the number of items, posts, etc. to display + * @param int $num_items the total number of items, posts, topics, etc. + * @return int Current page number + */ + public function reverse_start($start, $limit, $num_items) + { + return max(0, $num_items - $limit - $start); + } + + /** + * Get new item limit when searching from the end + * + * If the user is trying to reach late pages, start searching from the end. + * In this case the items to display might be lower then the actual per_page setting. + * + * @param int $start the item which should be considered currently active, used to determine the page we're on + * @param int $per_page the number of items, posts, etc. per page + * @param int $num_items the total number of items, posts, topics, etc. + * @return int Current page number + */ + public function reverse_limit($start, $per_page, $num_items) + { + if ($start + $per_page > $num_items) + { + return min($per_page, max(1, $num_items - $start)); + } + + return $per_page; + } +} diff --git a/phpbb/passwords/driver/base.php b/phpbb/passwords/driver/base.php new file mode 100644 index 0000000..0997b5b --- /dev/null +++ b/phpbb/passwords/driver/base.php @@ -0,0 +1,70 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +abstract class base implements rehashable_driver_interface +{ + /** @var \phpbb\config\config */ + protected $config; + + /** @var \phpbb\passwords\driver\helper */ + protected $helper; + + /** @var string Driver name */ + protected $name; + + /** + * Constructor of passwords driver object + * + * @param \phpbb\config\config $config phpBB config + * @param \phpbb\passwords\driver\helper $helper Password driver helper + */ + public function __construct(\phpbb\config\config $config, helper $helper) + { + $this->config = $config; + $this->helper = $helper; + } + + /** + * {@inheritdoc} + */ + public function is_supported() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function needs_rehash($hash) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function get_settings_only($hash, $full = false) + { + return false; + } +} diff --git a/phpbb/passwords/driver/bcrypt.php b/phpbb/passwords/driver/bcrypt.php new file mode 100644 index 0000000..eb1aeee --- /dev/null +++ b/phpbb/passwords/driver/bcrypt.php @@ -0,0 +1,135 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class bcrypt extends base +{ + const PREFIX = '$2a$'; + + /** @var int Hashing cost factor */ + protected $cost_factor; + + /** + * Constructor of passwords driver object + * + * @param \phpbb\config\config $config phpBB config + * @param \phpbb\passwords\driver\helper $helper Password driver helper + * @param int $cost_factor Hashing cost factor (optional) + */ + public function __construct(\phpbb\config\config $config, helper $helper, $cost_factor = 10) + { + parent::__construct($config, $helper); + + // Don't allow cost factor to be below default setting + $this->cost_factor = max(10, $cost_factor); + } + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function needs_rehash($hash) + { + preg_match('/^' . preg_quote($this->get_prefix()) . '([0-9]+)\$/', $hash, $matches); + + list(, $cost_factor) = $matches; + + return empty($cost_factor) || $this->cost_factor !== intval($cost_factor); + } + + /** + * {@inheritdoc} + */ + public function hash($password, $salt = '') + { + // The 2x and 2y prefixes of bcrypt might not be supported + // Revert to 2a if this is the case + $prefix = (!$this->is_supported()) ? '$2a$' : $this->get_prefix(); + + // Do not support 8-bit characters with $2a$ bcrypt + // Also see http://www.php.net/security/crypt_blowfish.php + if ($prefix === self::PREFIX) + { + if (ord($password[strlen($password)-1]) & 128) + { + return false; + } + } + + if ($salt == '') + { + $salt = $prefix . $this->cost_factor . '$' . $this->get_random_salt(); + } + + $hash = crypt($password, $salt); + if (strlen($hash) < 60) + { + return false; + } + return $hash; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + $salt = substr($hash, 0, 29); + if (strlen($salt) != 29) + { + return false; + } + + if ($this->helper->string_compare($hash, $this->hash($password, $salt))) + { + return true; + } + return false; + } + + /** + * Get a random salt value with a length of 22 characters + * + * @return string Salt for password hashing + */ + protected function get_random_salt() + { + return $this->helper->hash_encode64($this->helper->get_random_salt(22), 22); + } + + /** + * {@inheritdoc} + */ + public function get_settings_only($hash, $full = false) + { + if ($full) + { + $pos = stripos($hash, '$', 1) + 1; + $length = 22 + (strripos($hash, '$') + 1 - $pos); + } + else + { + $pos = strripos($hash, '$') + 1; + $length = 22; + } + return substr($hash, $pos, $length); + } +} diff --git a/phpbb/passwords/driver/bcrypt_2y.php b/phpbb/passwords/driver/bcrypt_2y.php new file mode 100644 index 0000000..c710e0d --- /dev/null +++ b/phpbb/passwords/driver/bcrypt_2y.php @@ -0,0 +1,35 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class bcrypt_2y extends bcrypt +{ + const PREFIX = '$2y$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_supported() + { + return (version_compare(PHP_VERSION, '5.3.7', '<')) ? false : true; + } +} diff --git a/phpbb/passwords/driver/bcrypt_wcf2.php b/phpbb/passwords/driver/bcrypt_wcf2.php new file mode 100644 index 0000000..0eee98d --- /dev/null +++ b/phpbb/passwords/driver/bcrypt_wcf2.php @@ -0,0 +1,84 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class bcrypt_wcf2 extends base +{ + const PREFIX = '$wcf2$'; + + /** @var \phpbb\passwords\driver\bcrypt */ + protected $bcrypt; + + /** @var \phpbb\passwords\driver\helper */ + protected $helper; + + /** + * Constructor of passwords driver object + * + * @param \phpbb\passwords\driver\bcrypt $bcrypt Salted md5 driver + * @param \phpbb\passwords\driver\helper $helper Password driver helper + */ + public function __construct(\phpbb\passwords\driver\bcrypt $bcrypt, helper $helper) + { + $this->bcrypt = $bcrypt; + $this->helper = $helper; + } + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + // Do not support hashing + return false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + if (empty($hash) || strlen($hash) != 60) + { + return false; + } + else + { + $salt = substr($hash, 0, 29); + + if (strlen($salt) != 29) + { + return false; + } + // Works for standard WCF 2.x, i.e. WBB4 and similar + return $this->helper->string_compare($hash, $this->bcrypt->hash($this->bcrypt->hash($password, $salt), $salt)); + } + } +} diff --git a/phpbb/passwords/driver/convert_password.php b/phpbb/passwords/driver/convert_password.php new file mode 100644 index 0000000..eb70434 --- /dev/null +++ b/phpbb/passwords/driver/convert_password.php @@ -0,0 +1,43 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class convert_password extends base +{ + const PREFIX = '$CP$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + return false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + return false; + } +} diff --git a/phpbb/passwords/driver/driver_interface.php b/phpbb/passwords/driver/driver_interface.php new file mode 100644 index 0000000..3974484 --- /dev/null +++ b/phpbb/passwords/driver/driver_interface.php @@ -0,0 +1,69 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +interface driver_interface +{ + /** + * Check if hash type is supported + * + * @return bool True if supported, false if not + */ + public function is_supported(); + + /** + * Check if hash type is a legacy hash type + * + * @return bool True if it's a legacy hash type, false if not + */ + public function is_legacy(); + + /** + * Returns the hash prefix + * + * @return string Hash prefix + */ + public function get_prefix(); + + /** + * Hash the password + * + * @param string $password The password that should be hashed + * + * @return bool|string Password hash or false if something went wrong + * during hashing + */ + public function hash($password); + + /** + * Check the password against the supplied hash + * + * @param string $password The password to check + * @param string $hash The password hash to check against + * @param array $user_row User's row in users table + * + * @return bool True if password is correct, else false + */ + public function check($password, $hash, $user_row = array()); + + /** + * Get only the settings of the specified hash + * + * @param string $hash Password hash + * @param bool $full Return full settings or only settings + * related to the salt + * @return string String containing the hash settings + */ + public function get_settings_only($hash, $full = false); +} diff --git a/phpbb/passwords/driver/helper.php b/phpbb/passwords/driver/helper.php new file mode 100644 index 0000000..f80c3e3 --- /dev/null +++ b/phpbb/passwords/driver/helper.php @@ -0,0 +1,177 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class helper +{ + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * base64 alphabet + * @var string + */ + public $itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + /** + * Construct a driver helper object + * + * @param \phpbb\config\config $config phpBB configuration + */ + public function __construct(\phpbb\config\config $config) + { + $this->config = $config; + } + + /** + * Base64 encode hash + * + * @param string $input Input string + * @param int $count Input string length + * + * @return string base64 encoded string + */ + public function hash_encode64($input, $count) + { + $output = ''; + $i = 0; + + do + { + $value = ord($input[$i++]); + $output .= $this->itoa64[$value & 0x3f]; + + if ($i < $count) + { + $value |= ord($input[$i]) << 8; + } + + $output .= $this->itoa64[($value >> 6) & 0x3f]; + + if ($i++ >= $count) + { + break; + } + + if ($i < $count) + { + $value |= ord($input[$i]) << 16; + } + + $output .= $this->itoa64[($value >> 12) & 0x3f]; + + if ($i++ >= $count) + { + break; + } + + $output .= $this->itoa64[($value >> 18) & 0x3f]; + } + while ($i < $count); + + return $output; + } + + /** + * Return unique id + * + * @param string $extra Additional entropy + * + * @return string Unique id + */ + public function unique_id($extra = 'c') + { + static $dss_seeded = false; + + $val = $this->config['rand_seed'] . microtime(); + $val = md5($val); + $this->config['rand_seed'] = md5($this->config['rand_seed'] . $val . $extra); + + if ($dss_seeded !== true && ($this->config['rand_seed_last_update'] < time() - rand(1,10))) + { + $this->config->set('rand_seed_last_update', time(), true); + $this->config->set('rand_seed', $this->config['rand_seed'], true); + $dss_seeded = true; + } + + return substr($val, 4, 16); + } + + /** + * Get random salt with specified length + * + * @param int $length Salt length + * @param string $rand_seed Seed for random data (optional). For tests. + * + * @return string Random salt with specified length + */ + public function get_random_salt($length, $rand_seed = '/dev/urandom') + { + $random = ''; + + if (($fh = @fopen($rand_seed, 'rb'))) + { + $random = fread($fh, $length); + fclose($fh); + } + + if (strlen($random) < $length) + { + $random = ''; + $random_state = $this->unique_id(); + + for ($i = 0; $i < $length; $i += 16) + { + $random_state = md5($this->unique_id() . $random_state); + $random .= pack('H*', md5($random_state)); + } + $random = substr($random, 0, $length); + } + return $random; + } + + /** + * Compare two strings byte by byte + * + * @param string $string_a The first string + * @param string $string_b The second string + * + * @return bool True if strings are the same, false if not + */ + public function string_compare($string_a, $string_b) + { + // Return if input variables are not strings or if length does not match + if (!is_string($string_a) || !is_string($string_b) || strlen($string_a) != strlen($string_b)) + { + return false; + } + + // Use hash_equals() if it's available + if (function_exists('hash_equals')) + { + return hash_equals($string_a, $string_b); + } + + $difference = 0; + + for ($i = 0; $i < strlen($string_a) && $i < strlen($string_b); $i++) + { + $difference |= ord($string_a[$i]) ^ ord($string_b[$i]); + } + + return $difference === 0; + } +} diff --git a/phpbb/passwords/driver/md5_mybb.php b/phpbb/passwords/driver/md5_mybb.php new file mode 100644 index 0000000..f631cea --- /dev/null +++ b/phpbb/passwords/driver/md5_mybb.php @@ -0,0 +1,60 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class md5_mybb extends base +{ + const PREFIX = '$md5_mybb$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + // Do not support hashing + return false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + if (empty($hash) || strlen($hash) != 32 || !isset($user_row['user_passwd_salt'])) + { + return false; + } + else + { + // Works for myBB 1.1.x, 1.2.x, 1.4.x, 1.6.x + return $this->helper->string_compare($hash, md5(md5($user_row['user_passwd_salt']) . md5($password))); + } + } +} diff --git a/phpbb/passwords/driver/md5_phpbb2.php b/phpbb/passwords/driver/md5_phpbb2.php new file mode 100644 index 0000000..b38b041 --- /dev/null +++ b/phpbb/passwords/driver/md5_phpbb2.php @@ -0,0 +1,123 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class md5_phpbb2 extends base +{ + const PREFIX = '$md5_phpbb2$'; + + /** @var \phpbb\request\request phpBB request object */ + protected $request; + + /** @var \phpbb\passwords\driver\salted_md5 */ + protected $salted_md5; + + /** @var \phpbb\passwords\driver\helper */ + protected $helper; + + /** @var string phpBB root path */ + protected $phpbb_root_path; + + /** @var string php file extension */ + protected $php_ext; + + /** + * Constructor of passwords driver object + * + * @param \phpbb\request\request $request phpBB request object + * @param \phpbb\passwords\driver\salted_md5 $salted_md5 Salted md5 driver + * @param \phpbb\passwords\driver\helper $helper Driver helper + * @param string $phpbb_root_path phpBB root path + * @param string $php_ext PHP file extension + */ + public function __construct($request, salted_md5 $salted_md5, helper $helper, $phpbb_root_path, $php_ext) + { + $this->request = $request; + $this->salted_md5 = $salted_md5; + $this->helper = $helper; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + // Do not support hashing + return false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + if (strlen($hash) != 32 && strlen($hash) != 34) + { + return false; + } + + // enable super globals to get literal value + // this is needed to prevent unicode normalization + $super_globals_disabled = $this->request->super_globals_disabled(); + if ($super_globals_disabled) + { + $this->request->enable_super_globals(); + } + + // in phpBB2 passwords were used exactly as they were sent, with addslashes applied + $password_old_format = isset($_REQUEST['password']) ? (string) $_REQUEST['password'] : ''; + $password_old_format = addslashes($password_old_format); + $password_new_format = $this->request->variable('password', '', true); + + if ($super_globals_disabled) + { + $this->request->disable_super_globals(); + } + + if ($password == $password_new_format) + { + if (!function_exists('utf8_to_cp1252')) + { + include($this->phpbb_root_path . 'includes/utf/data/recode_basic.' . $this->php_ext); + } + + if ($this->helper->string_compare(md5($password_old_format), $hash) || $this->helper->string_compare(md5(\utf8_to_cp1252($password_old_format)), $hash) + || $this->salted_md5->check(md5($password_old_format), $hash) === true + || $this->salted_md5->check(md5(\utf8_to_cp1252($password_old_format)), $hash) === true) + { + return true; + } + } + + return false; + } +} diff --git a/phpbb/passwords/driver/md5_vb.php b/phpbb/passwords/driver/md5_vb.php new file mode 100644 index 0000000..280b711 --- /dev/null +++ b/phpbb/passwords/driver/md5_vb.php @@ -0,0 +1,60 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class md5_vb extends base +{ + const PREFIX = '$md5_vb$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + // Do not support hashing + return false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + if (empty($hash) || strlen($hash) != 32 || !isset($user_row['user_passwd_salt'])) + { + return false; + } + else + { + // Works for vB 3.8.x, 4.x.x, 5.0.x + return $this->helper->string_compare($hash, md5(md5($password) . $user_row['user_passwd_salt'])); + } + } +} diff --git a/phpbb/passwords/driver/phpass.php b/phpbb/passwords/driver/phpass.php new file mode 100644 index 0000000..bef8355 --- /dev/null +++ b/phpbb/passwords/driver/phpass.php @@ -0,0 +1,27 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class phpass extends salted_md5 +{ + const PREFIX = '$P$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } +} diff --git a/phpbb/passwords/driver/rehashable_driver_interface.php b/phpbb/passwords/driver/rehashable_driver_interface.php new file mode 100644 index 0000000..ca30748 --- /dev/null +++ b/phpbb/passwords/driver/rehashable_driver_interface.php @@ -0,0 +1,25 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +interface rehashable_driver_interface extends driver_interface +{ + /** + * Check if password needs to be rehashed + * + * @param string $hash Hash to check for rehash + * @return bool True if password needs to be rehashed, false if not + */ + public function needs_rehash($hash); +} diff --git a/phpbb/passwords/driver/salted_md5.php b/phpbb/passwords/driver/salted_md5.php new file mode 100644 index 0000000..38d6d9c --- /dev/null +++ b/phpbb/passwords/driver/salted_md5.php @@ -0,0 +1,169 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +/** +* +* @version Version 0.1 / slightly modified for phpBB 3.1.x (using $H$ as hash type identifier) +* +* Portable PHP password hashing framework. +* +* Written by Solar Designer in 2004-2006 and placed in +* the public domain. +* +* There's absolutely no warranty. +* +* The homepage URL for this framework is: +* +* http://www.openwall.com/phpass/ +* +* Please be sure to update the Version line if you edit this file in any way. +* It is suggested that you leave the main version number intact, but indicate +* your project name (after the slash) and add your own revision information. +* +* Please do not change the "private" password hashing method implemented in +* here, thereby making your hashes incompatible. However, if you must, please +* change the hash type identifier (the "$P$") to something different. +* +* Obviously, since this code is in the public domain, the above are not +* requirements (there can be none), but merely suggestions. +* +*/ + +class salted_md5 extends base +{ + const PREFIX = '$H$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $setting = '') + { + if ($setting) + { + if (($settings = $this->get_hash_settings($setting)) === false) + { + // Return md5 of password if settings do not + // comply with our standards. This will only + // happen if pre-determined settings are + // directly passed to the driver. The manager + // will not do this. Same as the old hashing + // implementation in phpBB 3.0 + return md5($password); + } + } + else + { + $settings = $this->get_hash_settings($this->generate_salt()); + } + + $hash = md5($settings['salt'] . $password, true); + do + { + $hash = md5($hash . $password, true); + } + while (--$settings['count']); + + $output = $settings['full']; + $output .= $this->helper->hash_encode64($hash, 16); + + return $output; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + if (strlen($hash) !== 34) + { + return md5($password) === $hash; + } + + return $this->helper->string_compare($hash, $this->hash($password, $hash)); + } + + /** + * Generate salt for hashing method + * + * @return string Salt for hashing method + */ + protected function generate_salt() + { + $count = 6; + + $random = $this->helper->get_random_salt($count); + + $salt = $this->get_prefix(); + $salt .= $this->helper->itoa64[min($count + 5, 30)]; + $salt .= $this->helper->hash_encode64($random, $count); + + return $salt; + } + + /** + * Get hash settings + * + * @param string $hash The hash that contains the settings + * + * @return bool|array Array containing the count_log2, salt, and full + * hash settings string or false if supplied hash is empty + * or contains incorrect settings + */ + public function get_hash_settings($hash) + { + if (empty($hash)) + { + return false; + } + + $count_log2 = strpos($this->helper->itoa64, $hash[3]); + $salt = substr($hash, 4, 8); + + if ($count_log2 < 7 || $count_log2 > 30 || strlen($salt) != 8) + { + return false; + } + + return array( + 'count' => 1 << $count_log2, + 'salt' => $salt, + 'full' => substr($hash, 0, 12), + ); + } + + /** + * {@inheritdoc} + */ + public function get_settings_only($hash, $full = false) + { + return substr($hash, 3, 9); + } +} diff --git a/phpbb/passwords/driver/sha1.php b/phpbb/passwords/driver/sha1.php new file mode 100644 index 0000000..1abead4 --- /dev/null +++ b/phpbb/passwords/driver/sha1.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class sha1 extends base +{ + const PREFIX = '$sha1$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + // Do not support hashing + return false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + return (strlen($hash) == 40) ? $this->helper->string_compare($hash, sha1($password)) : false; + } +} diff --git a/phpbb/passwords/driver/sha1_smf.php b/phpbb/passwords/driver/sha1_smf.php new file mode 100644 index 0000000..b30d872 --- /dev/null +++ b/phpbb/passwords/driver/sha1_smf.php @@ -0,0 +1,51 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class sha1_smf extends base +{ + const PREFIX = '$smf$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + return (isset($user_row['login_name'])) ? sha1(strtolower($user_row['login_name']) . $password) : false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + return (strlen($hash) == 40) ? $this->helper->string_compare($hash, $this->hash($password, $user_row)) : false; + } +} diff --git a/phpbb/passwords/driver/sha1_wcf1.php b/phpbb/passwords/driver/sha1_wcf1.php new file mode 100644 index 0000000..6800648 --- /dev/null +++ b/phpbb/passwords/driver/sha1_wcf1.php @@ -0,0 +1,60 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class sha1_wcf1 extends base +{ + const PREFIX = '$wcf1$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + // Do not support hashing + return false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + if (empty($hash) || strlen($hash) != 40 || !isset($user_row['user_passwd_salt'])) + { + return false; + } + else + { + // Works for standard WCF 1.x, i.e. WBB3 and similar + return $this->helper->string_compare($hash, sha1($user_row['user_passwd_salt'] . sha1($user_row['user_passwd_salt'] . sha1($password)))); + } + } +} diff --git a/phpbb/passwords/driver/sha_xf1.php b/phpbb/passwords/driver/sha_xf1.php new file mode 100644 index 0000000..9d8f017 --- /dev/null +++ b/phpbb/passwords/driver/sha_xf1.php @@ -0,0 +1,68 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords\driver; + +class sha_xf1 extends base +{ + const PREFIX = '$xf1$'; + + /** + * {@inheritdoc} + */ + public function get_prefix() + { + return self::PREFIX; + } + + /** + * {@inheritdoc} + */ + public function is_legacy() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function hash($password, $user_row = '') + { + // Do not support hashing + return false; + } + + /** + * {@inheritdoc} + */ + public function check($password, $hash, $user_row = array()) + { + if (empty($hash) || (strlen($hash) != 40 && strlen($hash) != 64) || !isset($user_row['user_passwd_salt'])) + { + return false; + } + else + { + // Works for xenforo 1.0, 1.1 + if ($this->helper->string_compare($hash, sha1(sha1($password) . $user_row['user_passwd_salt'])) + || $this->helper->string_compare($hash, hash('sha256', hash('sha256', $password) . $user_row['user_passwd_salt']))) + { + return true; + } + else + { + return false; + } + } + } +} diff --git a/phpbb/passwords/helper.php b/phpbb/passwords/helper.php new file mode 100644 index 0000000..c2a4920 --- /dev/null +++ b/phpbb/passwords/helper.php @@ -0,0 +1,104 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords; + +class helper +{ + /** + * Get hash settings from combined hash + * + * @param string $hash Password hash of combined hash + * + * @return array An array containing the hash settings for the hash + * types in successive order as described by the combined + * password hash or an empty array if hash does not + * properly fit the combined hash format + */ + public function get_combined_hash_settings($hash) + { + $output = array(); + + preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $hash, $match); + $hash_settings = substr($hash, strpos($hash, $match[1]) + strlen($match[1]) + 1); + $matches = explode('\\', $match[1]); + foreach ($matches as $cur_type) + { + $dollar_position = strpos($hash_settings, '$'); + $output[] = substr($hash_settings, 0, ($dollar_position != false) ? $dollar_position : strlen($hash_settings)); + $hash_settings = substr($hash_settings, $dollar_position + 1); + } + + return $output; + } + + /** + * Combine hash prefixes, settings, and actual hash + * + * @param array $data Array containing the keys 'prefix' and 'settings'. + * It will hold the prefixes and settings + * @param string $type Data type of the supplied value + * @param string $value Value that should be put into the data array + * + * @return string|null Return complete combined hash if type is neither + * 'prefix' nor 'settings', nothing if it is + */ + public function combine_hash_output(&$data, $type, $value) + { + if ($type == 'prefix') + { + $data[$type] .= ($data[$type] !== '$') ? '\\' : ''; + $data[$type] .= str_replace('$', '', $value); + } + else if ($type == 'settings') + { + $data[$type] .= ($data[$type] !== '$') ? '$' : ''; + $data[$type] .= $value; + } + else + { + // Return full hash + return $data['prefix'] . $data['settings'] . '$' . $value; + } + } + + /** + * Rebuild hash for hashing functions + * + * @param string $prefix Hash prefix + * @param string $settings Hash settings + * + * @return string Rebuilt hash for hashing functions + */ + public function rebuild_hash($prefix, $settings) + { + $rebuilt_hash = $prefix; + if (strpos($settings, '\\') !== false) + { + $settings = str_replace('\\', '$', $settings); + } + $rebuilt_hash .= $settings; + return $rebuilt_hash; + } + + /** + * Obtain only the actual hash after the prefixes + * + * @param string $hash The full password hash + * @return string Actual hash (incl. settings) + */ + public function obtain_hash_only($hash) + { + return substr($hash, strripos($hash, '$') + 1); + } +} diff --git a/phpbb/passwords/manager.php b/phpbb/passwords/manager.php new file mode 100644 index 0000000..fad76a9 --- /dev/null +++ b/phpbb/passwords/manager.php @@ -0,0 +1,407 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\passwords; + +class manager +{ + /** + * Default hashing method + */ + protected $type = false; + + /** + * Hashing algorithm type map + * Will be used to map hash prefix to type + */ + protected $type_map = false; + + /** + * Service collection of hashing algorithms + * Needs to be public for passwords helper + */ + public $algorithms = false; + + /** + * Password convert flag. Signals that password should be converted + */ + public $convert_flag = false; + + /** + * Passwords helper + * @var \phpbb\passwords\helper + */ + protected $helper; + + /** + * phpBB configuration + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var bool Whether or not initialized() has been called + */ + private $initialized = false; + + /** + * @var array Hashing driver service collection + */ + private $hashing_algorithms; + + /** + * @var array List of default driver types + */ + private $defaults; + + /** + * Construct a passwords object + * + * @param \phpbb\config\config $config phpBB configuration + * @param array $hashing_algorithms Hashing driver service collection + * @param \phpbb\passwords\helper $helper Passwords helper object + * @param array $defaults List of default driver types + */ + public function __construct(\phpbb\config\config $config, $hashing_algorithms, helper $helper, $defaults) + { + $this->config = $config; + $this->helper = $helper; + $this->hashing_algorithms = $hashing_algorithms; + $this->defaults = $defaults; + } + + /** + * Initialize the internal state + */ + protected function initialize() + { + if (!$this->initialized) + { + $this->initialized = true; + $this->fill_type_map($this->hashing_algorithms); + $this->register_default_type($this->defaults); + } + } + + /** + * Register default type + * Will register the first supported type from the list of default types + * + * @param array $defaults List of default types in order from first to + * use to last to use + */ + protected function register_default_type($defaults) + { + foreach ($defaults as $type) + { + if ($this->algorithms[$type]->is_supported()) + { + $this->type = $this->algorithms[$type]->get_prefix(); + break; + } + } + } + + /** + * Fill algorithm type map + * + * @param \phpbb\di\service_collection $hashing_algorithms + */ + protected function fill_type_map($hashing_algorithms) + { + foreach ($hashing_algorithms as $algorithm) + { + if (!isset($this->type_map[$algorithm->get_prefix()])) + { + $this->type_map[$algorithm->get_prefix()] = $algorithm; + } + } + $this->algorithms = $hashing_algorithms; + } + + /** + * Get the algorithm specified by a specific prefix + * + * @param string $prefix Password hash prefix + * + * @return object|bool The hash type object or false if prefix is not + * supported + */ + protected function get_algorithm($prefix) + { + if (isset($this->type_map[$prefix])) + { + return $this->type_map[$prefix]; + } + else + { + return false; + } + } + + /** + * Detect the hash type of the supplied hash + * + * @param string $hash Password hash that should be checked + * + * @return object|bool The hash type object or false if the specified + * type is not supported + */ + public function detect_algorithm($hash) + { + /* + * preg_match() will also show hashing algos like $2a\H$, which + * is a combination of bcrypt and phpass. Legacy algorithms + * like md5 will not be matched by this and need to be treated + * differently. + */ + if (!preg_match('#^\$([a-zA-Z0-9\\\]*?)\$#', $hash, $match)) + { + return false; + } + + $this->initialize(); + + // Be on the lookout for multiple hashing algorithms + // 2 is correct: H\2a > 2, H\P > 2 + if (strlen($match[1]) > 2 && strpos($match[1], '\\') !== false) + { + $hash_types = explode('\\', $match[1]); + $return_ary = array(); + foreach ($hash_types as $type) + { + // we do not support the same hashing + // algorithm more than once + if (isset($return_ary[$type])) + { + return false; + } + + $return_ary[$type] = $this->get_algorithm('$' . $type . '$'); + + if (empty($return_ary[$type])) + { + return false; + } + } + return $return_ary; + } + + // get_algorithm() will automatically return false if prefix + // is not supported + return $this->get_algorithm($match[0]); + } + + /** + * Hash supplied password + * + * @param string $password Password that should be hashed + * @param string $type Hash type. Will default to standard hash type if + * none is supplied + * @return string|bool Password hash of supplied password or false if + * if something went wrong during hashing + */ + public function hash($password, $type = '') + { + if (strlen($password) > 4096) + { + // If the password is too huge, we will simply reject it + // and not let the server try to hash it. + return false; + } + + $this->initialize(); + + // Try to retrieve algorithm by service name if type doesn't + // start with dollar sign + if (!is_array($type) && strpos($type, '$') !== 0 && isset($this->algorithms[$type])) + { + $type = $this->algorithms[$type]->get_prefix(); + } + + $type = ($type === '') ? $this->type : $type; + + if (is_array($type)) + { + return $this->combined_hash_password($password, $type); + } + + if (isset($this->type_map[$type])) + { + $hashing_algorithm = $this->type_map[$type]; + } + else + { + return false; + } + + return $hashing_algorithm->hash($password); + } + + /** + * Check supplied password against hash and set convert_flag if password + * needs to be converted to different format (preferrably newer one) + * + * @param string $password Password that should be checked + * @param string $hash Stored hash + * @param array $user_row User's row in users table + * @return string|bool True if password is correct, false if not + */ + public function check($password, $hash, $user_row = array()) + { + if (strlen($password) > 4096) + { + // If the password is too huge, we will simply reject it + // and not let the server try to hash it. + return false; + } + + // Empty hashes can't be checked + if (empty($hash)) + { + return false; + } + + $this->initialize(); + + // First find out what kind of hash we're dealing with + $stored_hash_type = $this->detect_algorithm($hash); + if ($stored_hash_type == false) + { + // Still check MD5 hashes as that is what the installer + // will default to for the admin user + return $this->get_algorithm('$H$')->check($password, $hash); + } + + // Multiple hash passes needed + if (is_array($stored_hash_type)) + { + $correct = $this->check_combined_hash($password, $stored_hash_type, $hash); + $this->convert_flag = ($correct === true) ? true : false; + return $correct; + } + + if ($stored_hash_type->get_prefix() !== $this->type) + { + $this->convert_flag = true; + } + else + { + if ($stored_hash_type instanceof driver\rehashable_driver_interface) + { + $this->convert_flag = $stored_hash_type->needs_rehash($hash); + } + else + { + $this->convert_flag = false; + } + } + + // Check all legacy hash types if prefix is $CP$ + if ($stored_hash_type->get_prefix() === '$CP$') + { + // Remove $CP$ prefix for proper checking + $hash = substr($hash, 4); + + foreach ($this->type_map as $algorithm) + { + if ($algorithm->is_legacy() && $algorithm->check($password, $hash, $user_row) === true) + { + return true; + } + } + } + + return $stored_hash_type->check($password, $hash); + } + + /** + * Create combined hash from already hashed password + * + * @param string $password_hash Complete current password hash + * @param string $type Type of the hashing algorithm the password hash + * should be combined with + * @return string|bool Combined password hash if combined hashing was + * successful, else false + */ + public function combined_hash_password($password_hash, $type) + { + $this->initialize(); + + $data = array( + 'prefix' => '$', + 'settings' => '$', + ); + $hash_settings = $this->helper->get_combined_hash_settings($password_hash); + $hash = $hash_settings[0]; + + // Put settings of current hash into data array + $stored_hash_type = $this->detect_algorithm($password_hash); + $this->helper->combine_hash_output($data, 'prefix', $stored_hash_type->get_prefix()); + $this->helper->combine_hash_output($data, 'settings', $stored_hash_type->get_settings_only($password_hash)); + + // Hash current hash with the defined types + foreach ($type as $cur_type) + { + if (isset($this->algorithms[$cur_type])) + { + $new_hash_type = $this->algorithms[$cur_type]; + } + else + { + $new_hash_type = $this->get_algorithm($cur_type); + } + + if (!$new_hash_type) + { + return false; + } + + $new_hash = $new_hash_type->hash(str_replace($stored_hash_type->get_settings_only($password_hash), '', $hash)); + $this->helper->combine_hash_output($data, 'prefix', $new_hash_type->get_prefix()); + $this->helper->combine_hash_output($data, 'settings', substr(str_replace('$', '\\', $new_hash_type->get_settings_only($new_hash, true)), 0)); + $hash = str_replace($new_hash_type->get_settings_only($new_hash), '', $this->helper->obtain_hash_only($new_hash)); + } + return $this->helper->combine_hash_output($data, 'hash', $hash); + } + + /** + * Check combined password hash against the supplied password + * + * @param string $password Password entered by user + * @param array $stored_hash_type An array containing the hash types + * as described by stored password hash + * @param string $hash Stored password hash + * + * @return bool True if password is correct, false if not + */ + public function check_combined_hash($password, $stored_hash_type, $hash) + { + $i = 0; + $data = array( + 'prefix' => '$', + 'settings' => '$', + ); + $hash_settings = $this->helper->get_combined_hash_settings($hash); + foreach ($stored_hash_type as $key => $hash_type) + { + $rebuilt_hash = $this->helper->rebuild_hash($hash_type->get_prefix(), $hash_settings[$i]); + $this->helper->combine_hash_output($data, 'prefix', $key); + $this->helper->combine_hash_output($data, 'settings', $hash_settings[$i]); + $cur_hash = $hash_type->hash($password, $rebuilt_hash); + $password = str_replace($rebuilt_hash, '', $cur_hash); + $i++; + } + return ($hash === $this->helper->combine_hash_output($data, 'hash', $password)); + } +} diff --git a/phpbb/path_helper.php b/phpbb/path_helper.php new file mode 100644 index 0000000..5b6db35 --- /dev/null +++ b/phpbb/path_helper.php @@ -0,0 +1,512 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** +* A class with various functions that are related to paths, files and the filesystem +*/ +class path_helper +{ + /** @var \phpbb\symfony_request */ + protected $symfony_request; + + /** @var \phpbb\filesystem\filesystem_interface */ + protected $filesystem; + + /** @var \phpbb\request\request_interface */ + protected $request; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $adm_relative_path; + + /** @var string */ + protected $php_ext; + + /** @var string */ + protected $web_root_path; + + /** + * Constructor + * + * @param \phpbb\symfony_request $symfony_request + * @param \phpbb\filesystem\filesystem_interface $filesystem + * @param \phpbb\request\request_interface $request + * @param string $phpbb_root_path Relative path to phpBB root + * @param string $php_ext PHP file extension + * @param mixed $adm_relative_path Relative path admin path to adm/ root + */ + public function __construct(\phpbb\symfony_request $symfony_request, \phpbb\filesystem\filesystem_interface $filesystem, \phpbb\request\request_interface $request, $phpbb_root_path, $php_ext, $adm_relative_path = null) + { + $this->symfony_request = $symfony_request; + $this->filesystem = $filesystem; + $this->request = $request; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->adm_relative_path = $adm_relative_path; + } + + /** + * Get the phpBB root path + * + * @return string + */ + public function get_phpbb_root_path() + { + return $this->phpbb_root_path; + } + + /** + * Get the adm root path + * + * @return string + */ + public function get_adm_relative_path() + { + return $this->adm_relative_path; + } + + /** + * Get the php extension + * + * @return string + */ + public function get_php_ext() + { + return $this->php_ext; + } + + /** + * Update a web path to the correct relative root path + * + * This replaces $phpbb_root_path . some_url with + * get_web_root_path() . some_url + * + * @param string $path The path to be updated + * @return string + */ + public function update_web_root_path($path) + { + $web_root_path = $this->get_web_root_path(); + + // Removes the web root path if it is already present + if (strpos($path, $web_root_path) === 0) + { + $path = $this->phpbb_root_path . substr($path, strlen($web_root_path)); + } + + if (strpos($path, $this->phpbb_root_path) === 0) + { + $path = substr($path, strlen($this->phpbb_root_path)); + + if (substr($web_root_path, -8) === 'app.php/' && substr($path, 0, 7) === 'app.php') + { + $path = substr($path, 8); + } + + return $this->filesystem->clean_path($web_root_path . $path); + } + + return $path; + } + + /** + * Strips away the web root path and prepends the normal root path + * + * This replaces get_web_root_path() . some_url with + * $phpbb_root_path . some_url + * + * @param string $path The path to be updated + * @return string + */ + public function remove_web_root_path($path) + { + if (strpos($path, $this->get_web_root_path()) === 0) + { + $path = substr($path, strlen($this->get_web_root_path())); + + return $this->phpbb_root_path . $path; + } + + return $path; + } + + /** + * Get a relative root path from the current URL + * + * @return string + */ + public function get_web_root_path() + { + if ($this->symfony_request === null) + { + return $this->phpbb_root_path; + } + + if (null !== $this->web_root_path) + { + return $this->web_root_path; + } + + // We do not need to escape $path_info, $request_uri and $script_name because we can not find their content in the result. + // Path info (e.g. /foo/bar) + $path_info = $this->filesystem->clean_path($this->symfony_request->getPathInfo()); + + // Full request URI (e.g. phpBB/app.php/foo/bar) + $request_uri = $this->symfony_request->getRequestUri(); + + // Script name URI (e.g. phpBB/app.php) + $script_name = $this->symfony_request->getScriptName(); + + /* + * If the path info is empty but we're using app.php, then we + * might be using an empty route like app.php/ which is + * supported by symfony's routing + */ + if ($path_info === '/' && preg_match('/app\.' . $this->php_ext . '\/$/', $request_uri)) + { + return $this->web_root_path = $this->filesystem->clean_path('./../' . $this->phpbb_root_path); + } + + /* + * If the path info is empty (single /), then we're not using + * a route like app.php/foo/bar + */ + if ($path_info === '/') + { + return $this->web_root_path = $this->phpbb_root_path; + } + + /* + * Check AJAX request: + * If the current request is a AJAX we need to fix the paths. + * We need to get the root path based on the Referer, so we can use + * the generated URLs in the template of the Referer. If we do not + * generate the relative path based on the Referer, but based on the + * currently requested URL, the generated URLs will not point to the + * intended locations: + * Referer desired URL desired relative root path + * memberlist.php faq.php ./ + * memberlist.php app.php/foo/bar ./ + * app.php/foo memberlist.php ../ + * app.php/foo app.php/fox ../ + * app.php/foo/bar memberlist.php ../../ + * ../page.php memberlist.php ./phpBB/ + * ../sub/page.php memberlist.php ./../phpBB/ + * + * The referer must be specified as a parameter in the query. + */ + if ($this->request->is_ajax() && $this->symfony_request->get('_referer')) + { + // We need to escape $absolute_board_url because it can be partially concatenated to the result. + $absolute_board_url = $this->request->escape($this->symfony_request->getSchemeAndHttpHost() . $this->symfony_request->getBasePath(), true); + + $referer_web_root_path = $this->get_web_root_path_from_ajax_referer( + $this->symfony_request->get('_referer'), + $absolute_board_url + ); + return $this->web_root_path = $this->phpbb_root_path . $referer_web_root_path; + } + + // How many corrections might we need? + $corrections = substr_count($path_info, '/'); + + /* + * If the script name (e.g. phpBB/app.php) does not exists in the + * requestUri (e.g. phpBB/app.php/foo/template), then we are rewriting + * the URL. So we must reduce the slash count by 1. + */ + if (strpos($request_uri, $script_name) !== 0) + { + $corrections--; + } + + // Prepend ../ to the phpbb_root_path as many times as / exists in path_info + $this->web_root_path = $this->filesystem->clean_path( + './' . str_repeat('../', $corrections) . $this->phpbb_root_path + ); + return $this->web_root_path; + } + + /** + * Get the web root path of the referer form an ajax request + * + * @param string $absolute_referer_url + * @param string $absolute_board_url + * @return string + */ + public function get_web_root_path_from_ajax_referer($absolute_referer_url, $absolute_board_url) + { + // If the board URL is in the beginning of the referer, this means + // we the referer is in the board URL or a subdirectory of it. + // So we just need to count the / (slashes) in the left over part of + // the referer and prepend ../ the the current root_path, to get the + // web root path of the referer. + if (strpos($absolute_referer_url, $absolute_board_url) === 0) + { + $relative_referer_path = substr($absolute_referer_url, strlen($absolute_board_url)); + $has_params = strpos($relative_referer_path, '?'); + if ($has_params !== false) + { + $relative_referer_path = substr($relative_referer_path, 0, $has_params); + } + $corrections = substr_count($relative_referer_path, '/'); + return $this->phpbb_root_path . str_repeat('../', $corrections - 1); + } + + // If not, it's a bit more complicated. We go to the parent directory + // of the referer until we find the remaining referer in the board URL. + // Foreach directory we need to add a ../ to the fixed root_path. + // When we finally found it, we need to remove the remaining referer + // from the board URL, to get the boards root path. + // If the then append these two strings, we get our fixed web root path. + $fixed_root_path = ''; + $referer_dir = $absolute_referer_url; + $has_params = strpos($referer_dir, '?'); + if ($has_params !== false) + { + $referer_dir = substr($referer_dir, 0, $has_params); + } + + // If we do not find a slash at the end of the referer, we come + // from a file. So the first dirname() does not need a traversal + // path correction. + if (substr($referer_dir, -1) !== '/') + { + $referer_dir = dirname($referer_dir); + } + + while (($dir_position = strpos($absolute_board_url, $referer_dir)) !== 0) + { + $fixed_root_path .= '../'; + $referer_dir = dirname($referer_dir); + + // Just return phpbb_root_path if we reach the top directory + if ($referer_dir === '.') + { + return $this->phpbb_root_path; + } + } + + $fixed_root_path .= substr($absolute_board_url, strlen($referer_dir) + 1); + // Add trailing slash + return $this->phpbb_root_path . $fixed_root_path . '/'; + } + + /** + * Eliminates useless . and .. components from specified URL + * + * @param string $url URL to clean + * + * @return string Cleaned URL + */ + public function clean_url($url) + { + $delimiter_position = strpos($url, '://'); + // URL should contain :// but it shouldn't start with it. + // Do not clean URLs that do not fit these constraints. + if (empty($delimiter_position)) + { + return $url; + } + $scheme = substr($url, 0, $delimiter_position) . '://'; + // Add length of URL delimiter to position + $path = substr($url, $delimiter_position + 3); + + return $scheme . $this->filesystem->clean_path($path); + } + + /** + * Glue URL parameters together + * + * @param array $params URL parameters in the form of array(name => value) + * @return string Returns the glued string, e.g. name1=value1&name2&name3=value3 + */ + public function glue_url_params($params) + { + $_params = array(); + + foreach ($params as $key => $value) + { + // some parameters do not have value + if ($value !== null) + { + $_params[] = $key . '=' . $value; + } + else + { + $_params[] = $key; + } + } + return implode('&', $_params); + } + + /** + * Get the base and parameters of a URL + * + * @param string $url URL to break apart + * @param bool $is_amp Is the parameter separator &. Defaults to true. + * @return array Returns the base and parameters in the form of array('base' => string, 'params' => array(name => value)) + */ + public function get_url_parts($url, $is_amp = true) + { + $separator = ($is_amp) ? '&' : '&'; + $params = array(); + + if (strpos($url, '?') !== false) + { + $base = substr($url, 0, strpos($url, '?')); + $args = substr($url, strlen($base) + 1); + $args = ($args) ? explode($separator, $args) : array(); + + foreach ($args as $argument) + { + if (empty($argument)) + { + continue; + } + + // some parameters don't have value + if (strpos($argument, '=') !== false) + { + list($key, $value) = explode('=', $argument, 2); + } + else + { + $key = $argument; + $value = null; + } + + if ($key === '') + { + continue; + } + + $params[$key] = $value; + } + } + else + { + $base = $url; + } + + return array( + 'base' => $base, + 'params' => $params, + ); + } + + /** + * Strip parameters from an already built URL. + * + * @param string $url URL to strip parameters from + * @param array|string $strip Parameters to strip. + * @param bool $is_amp Is the parameter separator &. Defaults to true. + * @return string Returns the new URL. + */ + public function strip_url_params($url, $strip, $is_amp = true) + { + $url_parts = $this->get_url_parts($url, $is_amp); + $params = $url_parts['params']; + + if (!is_array($strip)) + { + $strip = array($strip); + } + + if (!empty($params)) + { + // Strip the parameters off + foreach ($strip as $param) + { + unset($params[$param]); + } + } + + return $url_parts['base'] . (($params) ? '?' . $this->glue_url_params($params) : ''); + } + + /** + * Append parameters to an already built URL. + * + * @param string $url URL to append parameters to + * @param array $new_params Parameters to add in the form of array(name => value) + * @param bool $is_amp Is the parameter separator &. Defaults to true. + * @return string Returns the new URL. + */ + public function append_url_params($url, $new_params, $is_amp = true) + { + $url_parts = $this->get_url_parts($url, $is_amp); + $params = array_merge($url_parts['params'], $new_params); + + // Move the sid to the end if it's set + if (isset($params['sid'])) + { + $sid = $params['sid']; + unset($params['sid']); + $params['sid'] = $sid; + } + + return $url_parts['base'] . (($params) ? '?' . $this->glue_url_params($params) : ''); + } + + /** + * Get a valid page + * + * @param string $page The page to verify + * @param bool $mod_rewrite Whether mod_rewrite is enabled, default: false + * + * @return string A valid page based on given page and mod_rewrite + */ + public function get_valid_page($page, $mod_rewrite = false) + { + // We need to be cautious here. + // On some situations, the redirect path is an absolute URL, sometimes a relative path + // For a relative path, let's prefix it with $phpbb_root_path to point to the correct location, + // else we use the URL directly. + $url_parts = parse_url($page); + + // URL + if ($url_parts === false || empty($url_parts['scheme']) || empty($url_parts['host'])) + { + // Remove 'app.php/' from the page, when rewrite is enabled. + // Treat app.php as a reserved file name and remove on mod rewrite + // even if it might not be in the phpBB root. + if ($mod_rewrite && ($app_position = strpos($page, 'app.' . $this->php_ext . '/')) !== false) + { + $page = substr($page, 0, $app_position) . substr($page, $app_position + strlen('app.' . $this->php_ext . '/')); + } + + // Remove preceding slashes from page name and prepend root path + $page = $this->get_phpbb_root_path() . ltrim($page, '/\\'); + } + + return $page; + } + + /** + * Tells if the router is currently in use (if the current page is a route or not) + * + * @return bool + */ + public function is_router_used() + { + // Script name URI (e.g. phpBB/app.php) + $script_name = $this->symfony_request->getScriptName(); + + return basename($script_name) === 'app.' . $this->php_ext; + } +} diff --git a/phpbb/permissions.php b/phpbb/permissions.php new file mode 100644 index 0000000..7697884 --- /dev/null +++ b/phpbb/permissions.php @@ -0,0 +1,364 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +class permissions +{ + /** + * Event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Constructor + * + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher + * @param \phpbb\user $user User Object + */ + public function __construct(\phpbb\event\dispatcher_interface $phpbb_dispatcher, \phpbb\user $user) + { + $this->dispatcher = $phpbb_dispatcher; + $this->user = $user; + + $categories = $this->categories; + $types = $this->types; + $permissions = $this->permissions; + + /** + * Allows to specify additional permission categories, types and permissions + * + * @event core.permissions + * @var array types Array with permission types (a_, u_, m_, etc.) + * @var array categories Array with permission categories (pm, post, settings, misc, etc.) + * @var array permissions Array with permissions. Each Permission has the following layout: + * '' => array( + * 'lang' => 'Language Key with a Short description', // Optional, if not set, + * // the permissions identifier '' is used with + * // all uppercase. + * 'cat' => 'Identifier of the category, the permission should be displayed in', + * ), + * Example: + * 'u_viewprofile' => array( + * 'lang' => 'ACL_U_VIEWPROFILE', + * 'cat' => 'profile', + * ), + * @since 3.1.0-a1 + */ + $vars = array('types', 'categories', 'permissions'); + extract($phpbb_dispatcher->trigger_event('core.permissions', compact($vars))); + + $this->categories = $categories; + $this->types = $types; + $this->permissions = $permissions; + } + + /** + * Returns an array with all the permission categories (pm, post, settings, misc, etc.) + * + * @return array Layout: cat-identifier => Language key + */ + public function get_categories() + { + return $this->categories; + } + + /** + * Returns the language string of a permission category + * + * @param string $category Identifier of the category + * @return string Language string + */ + public function get_category_lang($category) + { + return $this->user->lang($this->categories[$category]); + } + + /** + * Returns an array with all the permission types (a_, u_, m_, etc.) + * + * @return array Layout: type-identifier => Language key + */ + public function get_types() + { + return $this->types; + } + + /** + * Returns the language string of a permission type + * + * @param string $type Identifier of the type + * @param mixed $scope Scope of the type (should be 'global', 'local' or false) + * @return string Language string + */ + public function get_type_lang($type, $scope = false) + { + if ($scope && isset($this->types[$scope][$type])) + { + $lang_key = $this->types[$scope][$type]; + } + else if (isset($this->types[$type])) + { + $lang_key = $this->types[$type]; + } + else + { + $lang_key = 'ACL_TYPE_' . strtoupper(($scope) ? $scope . '_' . $type : $type); + } + + return $this->user->lang($lang_key); + } + + /** + * Returns an array with all the permissions. + * Each Permission has the following layout: + * '' => array( + * 'lang' => 'Language Key with a Short description', // Optional, if not set, + * // the permissions identifier '' is used with + * // all uppercase. + * 'cat' => 'Identifier of the category, the permission should be displayed in', + * ), + * Example: + * 'u_viewprofile' => array( + * 'lang' => 'ACL_U_VIEWPROFILE', + * 'cat' => 'profile', + * ), + * + * @return array + */ + public function get_permissions() + { + return $this->permissions; + } + + /** + * Returns the category of a permission + * + * @param string $permission Identifier of the permission + * @return string Returns the category identifier of the permission + */ + public function get_permission_category($permission) + { + return (isset($this->permissions[$permission]['cat'])) ? $this->permissions[$permission]['cat'] : 'misc'; + } + + /** + * Checks if a category has been defined + * + * @param string $category Identifier of the category + * @return bool True if the category is defined, false otherwise + */ + public function category_defined($category) + { + return isset($this->categories[$category]); + } + + /** + * Checks if a permission has been defined + * + * @param string $permission Identifier of the permission + * @return bool True if the permission is defined, false otherwise + */ + public function permission_defined($permission) + { + return isset($this->permissions[$permission]); + } + + /** + * Returns the language string of a permission + * + * @param string $permission Identifier of the permission + * @return string Language string + */ + public function get_permission_lang($permission) + { + return (isset($this->permissions[$permission]['lang'])) ? $this->user->lang($this->permissions[$permission]['lang']) : $this->user->lang('ACL_' . strtoupper($permission)); + } + + protected $types = array( + 'u_' => 'ACL_TYPE_U_', + 'a_' => 'ACL_TYPE_A_', + 'm_' => 'ACL_TYPE_M_', + 'f_' => 'ACL_TYPE_F_', + 'global' => array( + 'm_' => 'ACL_TYPE_GLOBAL_M_', + ), + ); + + protected $categories = array( + 'actions' => 'ACL_CAT_ACTIONS', + 'content' => 'ACL_CAT_CONTENT', + 'forums' => 'ACL_CAT_FORUMS', + 'misc' => 'ACL_CAT_MISC', + 'permissions' => 'ACL_CAT_PERMISSIONS', + 'pm' => 'ACL_CAT_PM', + 'polls' => 'ACL_CAT_POLLS', + 'post' => 'ACL_CAT_POST', + 'post_actions' => 'ACL_CAT_POST_ACTIONS', + 'posting' => 'ACL_CAT_POSTING', + 'profile' => 'ACL_CAT_PROFILE', + 'settings' => 'ACL_CAT_SETTINGS', + 'topic_actions' => 'ACL_CAT_TOPIC_ACTIONS', + 'user_group' => 'ACL_CAT_USER_GROUP', + ); + + protected $permissions = array( + // User Permissions + 'u_viewprofile' => array('lang' => 'ACL_U_VIEWPROFILE', 'cat' => 'profile'), + 'u_chgname' => array('lang' => 'ACL_U_CHGNAME', 'cat' => 'profile'), + 'u_chgpasswd' => array('lang' => 'ACL_U_CHGPASSWD', 'cat' => 'profile'), + 'u_chgemail' => array('lang' => 'ACL_U_CHGEMAIL', 'cat' => 'profile'), + 'u_chgavatar' => array('lang' => 'ACL_U_CHGAVATAR', 'cat' => 'profile'), + 'u_chggrp' => array('lang' => 'ACL_U_CHGGRP', 'cat' => 'profile'), + 'u_chgprofileinfo' => array('lang' => 'ACL_U_CHGPROFILEINFO', 'cat' => 'profile'), + + 'u_attach' => array('lang' => 'ACL_U_ATTACH', 'cat' => 'post'), + 'u_download' => array('lang' => 'ACL_U_DOWNLOAD', 'cat' => 'post'), + 'u_savedrafts' => array('lang' => 'ACL_U_SAVEDRAFTS', 'cat' => 'post'), + 'u_chgcensors' => array('lang' => 'ACL_U_CHGCENSORS', 'cat' => 'post'), + 'u_sig' => array('lang' => 'ACL_U_SIG', 'cat' => 'post'), + + 'u_sendpm' => array('lang' => 'ACL_U_SENDPM', 'cat' => 'pm'), + 'u_masspm' => array('lang' => 'ACL_U_MASSPM', 'cat' => 'pm'), + 'u_masspm_group'=> array('lang' => 'ACL_U_MASSPM_GROUP', 'cat' => 'pm'), + 'u_readpm' => array('lang' => 'ACL_U_READPM', 'cat' => 'pm'), + 'u_pm_edit' => array('lang' => 'ACL_U_PM_EDIT', 'cat' => 'pm'), + 'u_pm_delete' => array('lang' => 'ACL_U_PM_DELETE', 'cat' => 'pm'), + 'u_pm_forward' => array('lang' => 'ACL_U_PM_FORWARD', 'cat' => 'pm'), + 'u_pm_emailpm' => array('lang' => 'ACL_U_PM_EMAILPM', 'cat' => 'pm'), + 'u_pm_printpm' => array('lang' => 'ACL_U_PM_PRINTPM', 'cat' => 'pm'), + 'u_pm_attach' => array('lang' => 'ACL_U_PM_ATTACH', 'cat' => 'pm'), + 'u_pm_download' => array('lang' => 'ACL_U_PM_DOWNLOAD', 'cat' => 'pm'), + 'u_pm_bbcode' => array('lang' => 'ACL_U_PM_BBCODE', 'cat' => 'pm'), + 'u_pm_smilies' => array('lang' => 'ACL_U_PM_SMILIES', 'cat' => 'pm'), + 'u_pm_img' => array('lang' => 'ACL_U_PM_IMG', 'cat' => 'pm'), + 'u_pm_flash' => array('lang' => 'ACL_U_PM_FLASH', 'cat' => 'pm'), + + 'u_sendemail' => array('lang' => 'ACL_U_SENDEMAIL', 'cat' => 'misc'), + 'u_sendim' => array('lang' => 'ACL_U_SENDIM', 'cat' => 'misc'), + 'u_ignoreflood' => array('lang' => 'ACL_U_IGNOREFLOOD', 'cat' => 'misc'), + 'u_hideonline' => array('lang' => 'ACL_U_HIDEONLINE', 'cat' => 'misc'), + 'u_viewonline' => array('lang' => 'ACL_U_VIEWONLINE', 'cat' => 'misc'), + 'u_search' => array('lang' => 'ACL_U_SEARCH', 'cat' => 'misc'), + + // Forum Permissions + 'f_list' => array('lang' => 'ACL_F_LIST', 'cat' => 'actions'), + 'f_list_topics' => array('lang' => 'ACL_F_LIST_TOPICS', 'cat' => 'actions'), + 'f_read' => array('lang' => 'ACL_F_READ', 'cat' => 'actions'), + 'f_search' => array('lang' => 'ACL_F_SEARCH', 'cat' => 'actions'), + 'f_subscribe' => array('lang' => 'ACL_F_SUBSCRIBE', 'cat' => 'actions'), + 'f_print' => array('lang' => 'ACL_F_PRINT', 'cat' => 'actions'), + 'f_email' => array('lang' => 'ACL_F_EMAIL', 'cat' => 'actions'), + 'f_bump' => array('lang' => 'ACL_F_BUMP', 'cat' => 'actions'), + 'f_user_lock' => array('lang' => 'ACL_F_USER_LOCK', 'cat' => 'actions'), + 'f_download' => array('lang' => 'ACL_F_DOWNLOAD', 'cat' => 'actions'), + 'f_report' => array('lang' => 'ACL_F_REPORT', 'cat' => 'actions'), + + 'f_post' => array('lang' => 'ACL_F_POST', 'cat' => 'post'), + 'f_sticky' => array('lang' => 'ACL_F_STICKY', 'cat' => 'post'), + 'f_announce' => array('lang' => 'ACL_F_ANNOUNCE', 'cat' => 'post'), + 'f_announce_global' => array('lang' => 'ACL_F_ANNOUNCE_GLOBAL', 'cat' => 'post'), + 'f_reply' => array('lang' => 'ACL_F_REPLY', 'cat' => 'post'), + 'f_edit' => array('lang' => 'ACL_F_EDIT', 'cat' => 'post'), + 'f_delete' => array('lang' => 'ACL_F_DELETE', 'cat' => 'post'), + 'f_softdelete' => array('lang' => 'ACL_F_SOFTDELETE', 'cat' => 'post'), + 'f_ignoreflood' => array('lang' => 'ACL_F_IGNOREFLOOD', 'cat' => 'post'), + 'f_postcount' => array('lang' => 'ACL_F_POSTCOUNT', 'cat' => 'post'), + 'f_noapprove' => array('lang' => 'ACL_F_NOAPPROVE', 'cat' => 'post'), + + 'f_attach' => array('lang' => 'ACL_F_ATTACH', 'cat' => 'content'), + 'f_icons' => array('lang' => 'ACL_F_ICONS', 'cat' => 'content'), + 'f_bbcode' => array('lang' => 'ACL_F_BBCODE', 'cat' => 'content'), + 'f_flash' => array('lang' => 'ACL_F_FLASH', 'cat' => 'content'), + 'f_img' => array('lang' => 'ACL_F_IMG', 'cat' => 'content'), + 'f_sigs' => array('lang' => 'ACL_F_SIGS', 'cat' => 'content'), + 'f_smilies' => array('lang' => 'ACL_F_SMILIES', 'cat' => 'content'), + + 'f_poll' => array('lang' => 'ACL_F_POLL', 'cat' => 'polls'), + 'f_vote' => array('lang' => 'ACL_F_VOTE', 'cat' => 'polls'), + 'f_votechg' => array('lang' => 'ACL_F_VOTECHG', 'cat' => 'polls'), + + // Moderator Permissions + 'm_edit' => array('lang' => 'ACL_M_EDIT', 'cat' => 'post_actions'), + 'm_delete' => array('lang' => 'ACL_M_DELETE', 'cat' => 'post_actions'), + 'm_approve' => array('lang' => 'ACL_M_APPROVE', 'cat' => 'post_actions'), + 'm_report' => array('lang' => 'ACL_M_REPORT', 'cat' => 'post_actions'), + 'm_chgposter' => array('lang' => 'ACL_M_CHGPOSTER', 'cat' => 'post_actions'), + 'm_info' => array('lang' => 'ACL_M_INFO', 'cat' => 'post_actions'), + 'm_softdelete' => array('lang' => 'ACL_M_SOFTDELETE', 'cat' => 'post_actions'), + + 'm_move' => array('lang' => 'ACL_M_MOVE', 'cat' => 'topic_actions'), + 'm_lock' => array('lang' => 'ACL_M_LOCK', 'cat' => 'topic_actions'), + 'm_split' => array('lang' => 'ACL_M_SPLIT', 'cat' => 'topic_actions'), + 'm_merge' => array('lang' => 'ACL_M_MERGE', 'cat' => 'topic_actions'), + + 'm_warn' => array('lang' => 'ACL_M_WARN', 'cat' => 'misc'), + 'm_pm_report' => array('lang' => 'ACL_M_PM_REPORT', 'cat' => 'misc'), + 'm_ban' => array('lang' => 'ACL_M_BAN', 'cat' => 'misc'), + + // Admin Permissions + 'a_board' => array('lang' => 'ACL_A_BOARD', 'cat' => 'settings'), + 'a_server' => array('lang' => 'ACL_A_SERVER', 'cat' => 'settings'), + 'a_jabber' => array('lang' => 'ACL_A_JABBER', 'cat' => 'settings'), + 'a_phpinfo' => array('lang' => 'ACL_A_PHPINFO', 'cat' => 'settings'), + + 'a_forum' => array('lang' => 'ACL_A_FORUM', 'cat' => 'forums'), + 'a_forumadd' => array('lang' => 'ACL_A_FORUMADD', 'cat' => 'forums'), + 'a_forumdel' => array('lang' => 'ACL_A_FORUMDEL', 'cat' => 'forums'), + 'a_prune' => array('lang' => 'ACL_A_PRUNE', 'cat' => 'forums'), + + 'a_icons' => array('lang' => 'ACL_A_ICONS', 'cat' => 'posting'), + 'a_words' => array('lang' => 'ACL_A_WORDS', 'cat' => 'posting'), + 'a_bbcode' => array('lang' => 'ACL_A_BBCODE', 'cat' => 'posting'), + 'a_attach' => array('lang' => 'ACL_A_ATTACH', 'cat' => 'posting'), + + 'a_user' => array('lang' => 'ACL_A_USER', 'cat' => 'user_group'), + 'a_userdel' => array('lang' => 'ACL_A_USERDEL', 'cat' => 'user_group'), + 'a_group' => array('lang' => 'ACL_A_GROUP', 'cat' => 'user_group'), + 'a_groupadd' => array('lang' => 'ACL_A_GROUPADD', 'cat' => 'user_group'), + 'a_groupdel' => array('lang' => 'ACL_A_GROUPDEL', 'cat' => 'user_group'), + 'a_ranks' => array('lang' => 'ACL_A_RANKS', 'cat' => 'user_group'), + 'a_profile' => array('lang' => 'ACL_A_PROFILE', 'cat' => 'user_group'), + 'a_names' => array('lang' => 'ACL_A_NAMES', 'cat' => 'user_group'), + 'a_ban' => array('lang' => 'ACL_A_BAN', 'cat' => 'user_group'), + + 'a_viewauth' => array('lang' => 'ACL_A_VIEWAUTH', 'cat' => 'permissions'), + 'a_authgroups' => array('lang' => 'ACL_A_AUTHGROUPS', 'cat' => 'permissions'), + 'a_authusers' => array('lang' => 'ACL_A_AUTHUSERS', 'cat' => 'permissions'), + 'a_fauth' => array('lang' => 'ACL_A_FAUTH', 'cat' => 'permissions'), + 'a_mauth' => array('lang' => 'ACL_A_MAUTH', 'cat' => 'permissions'), + 'a_aauth' => array('lang' => 'ACL_A_AAUTH', 'cat' => 'permissions'), + 'a_uauth' => array('lang' => 'ACL_A_UAUTH', 'cat' => 'permissions'), + 'a_roles' => array('lang' => 'ACL_A_ROLES', 'cat' => 'permissions'), + 'a_switchperm' => array('lang' => 'ACL_A_SWITCHPERM', 'cat' => 'permissions'), + + 'a_styles' => array('lang' => 'ACL_A_STYLES', 'cat' => 'misc'), + 'a_extensions' => array('lang' => 'ACL_A_EXTENSIONS', 'cat' => 'misc'), + 'a_viewlogs' => array('lang' => 'ACL_A_VIEWLOGS', 'cat' => 'misc'), + 'a_clearlogs' => array('lang' => 'ACL_A_CLEARLOGS', 'cat' => 'misc'), + 'a_modules' => array('lang' => 'ACL_A_MODULES', 'cat' => 'misc'), + 'a_language' => array('lang' => 'ACL_A_LANGUAGE', 'cat' => 'misc'), + 'a_email' => array('lang' => 'ACL_A_EMAIL', 'cat' => 'misc'), + 'a_bots' => array('lang' => 'ACL_A_BOTS', 'cat' => 'misc'), + 'a_reasons' => array('lang' => 'ACL_A_REASONS', 'cat' => 'misc'), + 'a_backup' => array('lang' => 'ACL_A_BACKUP', 'cat' => 'misc'), + 'a_search' => array('lang' => 'ACL_A_SEARCH', 'cat' => 'misc'), + ); +} diff --git a/phpbb/php/ini.php b/phpbb/php/ini.php new file mode 100644 index 0000000..73a3065 --- /dev/null +++ b/phpbb/php/ini.php @@ -0,0 +1,171 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\php; + +/** +* Wrapper class for ini_get function. +* +* Provides easier handling of the different interpretations of ini values. +*/ +class ini +{ + /** + * Simple wrapper for ini_get() + * See http://php.net/manual/en/function.ini-get.php + * + * @param string $varname The configuration option name. + * @return bool|string False if configuration option does not exist, + * the configuration option value (string) otherwise. + */ + public function get($varname) + { + return ini_get($varname); + } + + /** + * Gets the configuration option value as a trimmed string. + * + * @param string $varname The configuration option name. + * @return bool|string False if configuration option does not exist, + * the configuration option value (string) otherwise. + */ + public function get_string($varname) + { + $value = $this->get($varname); + + if ($value === false) + { + return false; + } + + return trim($value); + } + + /** + * Gets configuration option value as a boolean. + * Interprets the string value 'off' as false. + * + * @param string $varname The configuration option name. + * @return bool False if configuration option does not exist. + * False if configuration option is disabled. + * True otherwise. + */ + public function get_bool($varname) + { + $value = $this->get_string($varname); + + if (empty($value) || strtolower($value) == 'off') + { + return false; + } + + return true; + } + + /** + * Gets configuration option value as an integer. + * + * @param string $varname The configuration option name. + * @return bool|int False if configuration option does not exist, + * false if configuration option value is not numeric, + * the configuration option value (integer) otherwise. + */ + public function get_int($varname) + { + $value = $this->get_string($varname); + + if (!is_numeric($value)) + { + return false; + } + + return (int) $value; + } + + /** + * Gets configuration option value as a float. + * + * @param string $varname The configuration option name. + * @return bool|float False if configuration option does not exist, + * false if configuration option value is not numeric, + * the configuration option value (float) otherwise. + */ + public function get_float($varname) + { + $value = $this->get_string($varname); + + if (!is_numeric($value)) + { + return false; + } + + return (float) $value; + } + + /** + * Gets configuration option value in bytes. + * Converts strings like '128M' to bytes (integer or float). + * + * @param string $varname The configuration option name. + * @return bool|int|float False if configuration option does not exist, + * false if configuration option value is not well-formed, + * the configuration option value otherwise. + */ + public function get_bytes($varname) + { + $value = $this->get_string($varname); + + if ($value === false) + { + return false; + } + + if (is_numeric($value)) + { + // Already in bytes. + return phpbb_to_numeric($value); + } + else if (strlen($value) < 2) + { + // Single character. + return false; + } + else if (strlen($value) < 3 && $value[0] === '-') + { + // Two characters but the first one is a minus. + return false; + } + + $value_lower = strtolower($value); + $value_numeric = phpbb_to_numeric($value); + + switch ($value_lower[strlen($value_lower) - 1]) + { + case 'g': + $value_numeric *= 1024; + case 'm': + $value_numeric *= 1024; + case 'k': + $value_numeric *= 1024; + break; + + default: + // It's not already in bytes (and thus numeric) + // and does not carry a unit. + return false; + } + + return $value_numeric; + } +} diff --git a/phpbb/plupload/plupload.php b/phpbb/plupload/plupload.php new file mode 100644 index 0000000..eb698fb --- /dev/null +++ b/phpbb/plupload/plupload.php @@ -0,0 +1,402 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\plupload; + +/** +* This class handles all server-side plupload functions +*/ +class plupload +{ + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * @var \phpbb\user + */ + protected $user; + + /** + * @var \bantu\IniGetWrapper\IniGetWrapper + */ + protected $php_ini; + + /** + * @var \phpbb\mimetype\guesser + */ + protected $mimetype_guesser; + + /** + * Final destination for uploaded files, i.e. the "files" directory. + * @var string + */ + protected $upload_directory; + + /** + * Temporary upload directory for plupload uploads. + * @var string + */ + protected $temporary_directory; + + /** + * Constructor. + * + * @param string $phpbb_root_path + * @param \phpbb\config\config $config + * @param \phpbb\request\request_interface $request + * @param \phpbb\user $user + * @param \bantu\IniGetWrapper\IniGetWrapper $php_ini + * @param \phpbb\mimetype\guesser $mimetype_guesser + */ + public function __construct($phpbb_root_path, \phpbb\config\config $config, \phpbb\request\request_interface $request, \phpbb\user $user, \bantu\IniGetWrapper\IniGetWrapper $php_ini, \phpbb\mimetype\guesser $mimetype_guesser) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->config = $config; + $this->request = $request; + $this->user = $user; + $this->php_ini = $php_ini; + $this->mimetype_guesser = $mimetype_guesser; + + $this->set_default_directories(); + } + + /** + * Plupload allows for chunking so we must check for that and assemble + * the whole file first before performing any checks on it. + * + * @param string $form_name The name of the file element in the upload form + * + * @return array|null null if there are no chunks to piece together + * otherwise array containing the path to the + * pieced-together file and its size + */ + public function handle_upload($form_name) + { + $chunks_expected = $this->request->variable('chunks', 0); + + // If chunking is disabled or we are not using plupload, just return + // and handle the file as usual + if ($chunks_expected < 2) + { + return; + } + + $file_name = $this->request->variable('name', ''); + $chunk = $this->request->variable('chunk', 0); + + $this->user->add_lang('plupload'); + $this->prepare_temporary_directory(); + + $file_path = $this->temporary_filepath($file_name); + $this->integrate_uploaded_file($form_name, $chunk, $file_path); + + // If we are done with all the chunks, strip the .part suffix and then + // handle the resulting file as normal, otherwise die and await the + // next chunk. + if ($chunk == $chunks_expected - 1) + { + rename("{$file_path}.part", $file_path); + + // Reset upload directories to defaults once completed + $this->set_default_directories(); + + // Need to modify some of the $_FILES values to reflect the new file + return array( + 'tmp_name' => $file_path, + 'name' => $this->request->variable('real_filename', '', true), + 'size' => filesize($file_path), + 'type' => $this->mimetype_guesser->guess($file_path, $file_name), + ); + } + else + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'jsonrpc' => '2.0', + 'id' => 'id', + 'result' => null, + )); + } + } + + /** + * Fill in the plupload configuration options in the template + * + * @param \phpbb\cache\service $cache + * @param \phpbb\template\template $template + * @param string $s_action The URL to submit the POST data to + * @param int $forum_id The ID of the forum + * @param int $max_files Maximum number of files allowed. 0 for unlimited. + * + * @return null + */ + public function configure(\phpbb\cache\service $cache, \phpbb\template\template $template, $s_action, $forum_id, $max_files) + { + $filters = $this->generate_filter_string($cache, $forum_id); + $chunk_size = $this->get_chunk_size(); + $resize = $this->generate_resize_string(); + + $template->assign_vars(array( + 'S_RESIZE' => $resize, + 'S_PLUPLOAD' => true, + 'FILTERS' => $filters, + 'CHUNK_SIZE' => $chunk_size, + 'S_PLUPLOAD_URL' => htmlspecialchars_decode($s_action), + 'MAX_ATTACHMENTS' => $max_files, + 'ATTACH_ORDER' => ($this->config['display_order']) ? 'asc' : 'desc', + 'L_TOO_MANY_ATTACHMENTS' => $this->user->lang('TOO_MANY_ATTACHMENTS', $max_files), + )); + + $this->user->add_lang('plupload'); + } + + /** + * Checks whether the page request was sent by plupload or not + * + * @return bool + */ + public function is_active() + { + return $this->request->header('X-PHPBB-USING-PLUPLOAD', false); + } + + /** + * Returns whether the current HTTP request is a multipart request. + * + * @return bool + */ + public function is_multipart() + { + $content_type = $this->request->server('CONTENT_TYPE'); + + return strpos($content_type, 'multipart') === 0; + } + + /** + * Sends an error message back to the client via JSON response + * + * @param int $code The error code + * @param string $msg The translation string of the message to be sent + * + * @return null + */ + public function emit_error($code, $msg) + { + $json_response = new \phpbb\json_response(); + $json_response->send(array( + 'jsonrpc' => '2.0', + 'id' => 'id', + 'error' => array( + 'code' => $code, + 'message' => $this->user->lang($msg), + ), + )); + } + + /** + * Looks at the list of allowed extensions and generates a string + * appropriate for use in configuring plupload with + * + * @param \phpbb\cache\service $cache + * @param string $forum_id The ID of the forum + * + * @return string + */ + public function generate_filter_string(\phpbb\cache\service $cache, $forum_id) + { + $attach_extensions = $cache->obtain_attach_extensions($forum_id); + unset($attach_extensions['_allowed_']); + $groups = array(); + + // Re-arrange the extension array to $groups[$group_name][] + foreach ($attach_extensions as $extension => $extension_info) + { + if (!isset($groups[$extension_info['group_name']])) + { + $groups[$extension_info['group_name']] = array(); + } + + $groups[$extension_info['group_name']][] = $extension; + } + + $filters = array(); + foreach ($groups as $group => $extensions) + { + $filters[] = sprintf( + "{title: '%s', extensions: '%s'}", + addslashes(ucfirst(strtolower($group))), + addslashes(implode(',', $extensions)) + ); + } + + return implode(',', $filters); + } + + /** + * Generates a string that is used to tell plupload to automatically resize + * files before uploading them. + * + * @return string + */ + public function generate_resize_string() + { + $resize = ''; + if ($this->config['img_max_height'] > 0 && $this->config['img_max_width'] > 0) + { + $resize = sprintf( + 'resize: {width: %d, height: %d, quality: 85},', + (int) $this->config['img_max_width'], + (int) $this->config['img_max_height'] + ); + } + + return $resize; + } + + /** + * Checks various php.ini values and the maximum file size to determine + * the maximum size chunks a file can be split up into for upload + * + * @return int + */ + public function get_chunk_size() + { + $max = min( + $this->php_ini->getBytes('upload_max_filesize'), + $this->php_ini->getBytes('post_max_size'), + max(1, $this->php_ini->getBytes('memory_limit')), + $this->config['max_filesize'] + ); + + // Use half of the maximum possible to leave plenty of room for other + // POST data. + return floor($max / 2); + } + + protected function temporary_filepath($file_name) + { + // Must preserve the extension for plupload to work. + return sprintf( + '%s/%s_%s%s', + $this->temporary_directory, + $this->config['plupload_salt'], + md5($file_name), + \phpbb\files\filespec::get_extension($file_name) + ); + } + + /** + * Checks whether the chunk we are about to deal with was actually uploaded + * by PHP and actually exists, if not, it generates an error + * + * @param string $form_name The name of the file in the form data + * + * @return null + */ + protected function integrate_uploaded_file($form_name, $chunk, $file_path) + { + $is_multipart = $this->is_multipart(); + $upload = $this->request->file($form_name); + if ($is_multipart && (!isset($upload['tmp_name']) || !is_uploaded_file($upload['tmp_name']))) + { + $this->emit_error(103, 'PLUPLOAD_ERR_MOVE_UPLOADED'); + } + + $tmp_file = $this->temporary_filepath($upload['tmp_name']); + + if (!phpbb_is_writable($this->temporary_directory) || !move_uploaded_file($upload['tmp_name'], $tmp_file)) + { + $this->emit_error(103, 'PLUPLOAD_ERR_MOVE_UPLOADED'); + } + + $out = fopen("{$file_path}.part", $chunk == 0 ? 'wb' : 'ab'); + if (!$out) + { + $this->emit_error(102, 'PLUPLOAD_ERR_OUTPUT'); + } + + $in = fopen(($is_multipart) ? $tmp_file : 'php://input', 'rb'); + if (!$in) + { + $this->emit_error(101, 'PLUPLOAD_ERR_INPUT'); + } + + while ($buf = fread($in, 4096)) + { + fwrite($out, $buf); + } + + fclose($in); + fclose($out); + + if ($is_multipart) + { + unlink($tmp_file); + } + } + + /** + * Creates the temporary directory if it does not already exist. + * + * @return null + */ + protected function prepare_temporary_directory() + { + if (!file_exists($this->temporary_directory)) + { + mkdir($this->temporary_directory); + + copy( + $this->upload_directory . '/index.htm', + $this->temporary_directory . '/index.htm' + ); + } + } + + /** + * Sets the default directories for uploads + * + * @return null + */ + protected function set_default_directories() + { + $this->upload_directory = $this->phpbb_root_path . $this->config['upload_path']; + $this->temporary_directory = $this->upload_directory . '/plupload'; + } + + /** + * Sets the upload directories to the specified paths + * + * @param string $upload_directory Upload directory + * @param string $temporary_directory Temporary directory + * + * @return null + */ + public function set_upload_directories($upload_directory, $temporary_directory) + { + $this->upload_directory = $upload_directory; + $this->temporary_directory = $temporary_directory; + } +} diff --git a/phpbb/profilefields/lang_helper.php b/phpbb/profilefields/lang_helper.php new file mode 100644 index 0000000..2e35372 --- /dev/null +++ b/phpbb/profilefields/lang_helper.php @@ -0,0 +1,140 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields; + +/** +* Custom Profile Fields +*/ +class lang_helper +{ + /** + * Array with the language option, grouped by field and language + * @var array + */ + protected $options_lang = array(); + + /** + * Database object + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * Table where the language strings are stored + * @var string + */ + protected $language_table; + + /** + * Construct + * + * @param \phpbb\db\driver\driver_interface $db Database object + * @param string $language_table Table where the language strings are stored + */ + public function __construct($db, $language_table) + { + $this->db = $db; + $this->language_table = $language_table; + } + + /** + * Loads preview options into language entries for options + * + * @param int $field_id + * @param int $lang_id + * @param mixed $preview_options + */ + public function load_preview_options($field_id, $lang_id, $preview_options) + { + $lang_options = (!is_array($preview_options)) ? explode("\n", $preview_options) : $preview_options; + + foreach ($lang_options as $num => $var) + { + if (!isset($this->options_lang[$field_id])) + { + $this->options_lang[$field_id] = array(); + } + if (!isset($this->options_lang[$field_id][$lang_id])) + { + $this->options_lang[$field_id][$lang_id] = array(); + } + $this->options_lang[$field_id][$lang_id][($num + 1)] = $var; + } + } + + /** + * Fetches language entries for options from DB + * + * @param int $lang_id + */ + public function load_option_lang($lang_id) + { + $sql = 'SELECT field_id, option_id, lang_value + FROM ' . $this->language_table . ' + WHERE lang_id = ' . (int) $lang_id . " + ORDER BY option_id"; + + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->options_lang[$row['field_id']][$lang_id][($row['option_id'] + 1)] = $row['lang_value']; + } + + $this->db->sql_freeresult($result); + } + + /** + * Are language options set for this field? + * + * @param int $field_id Database ID of the field + * @param int $lang_id ID of the language + * @param int $field_value Selected value of the field + * @return boolean + */ + public function is_set($field_id, $lang_id = null, $field_value = null) + { + $is_set = isset($this->options_lang[$field_id]); + + if ($is_set && (!is_null($lang_id) || !is_null($field_value))) + { + $is_set = isset($this->options_lang[$field_id][$lang_id]); + } + + if ($is_set && !is_null($field_value)) + { + $is_set = isset($this->options_lang[$field_id][$lang_id][$field_value]); + } + + return $is_set; + } + + /** + * Get the selected language string + * + * @param int $field_id Database ID of the field + * @param int $lang_id ID of the language + * @param int $field_value Selected value of the field + * @return string + */ + public function get($field_id, $lang_id, $field_value = null) + { + if (is_null($field_value)) + { + return $this->options_lang[$field_id][$lang_id]; + } + + return $this->options_lang[$field_id][$lang_id][$field_value]; + } +} diff --git a/phpbb/profilefields/manager.php b/phpbb/profilefields/manager.php new file mode 100644 index 0000000..35b18dd --- /dev/null +++ b/phpbb/profilefields/manager.php @@ -0,0 +1,502 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields; + +/** +* Custom Profile Fields +*/ +class manager +{ + /** + * Auth object + * @var \phpbb\auth\auth + */ + protected $auth; + + /** + * Database object + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * Event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * Request object + * @var \phpbb\request\request + */ + protected $request; + + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * Service Collection object + * @var \phpbb\di\service_collection + */ + protected $type_collection; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + protected $fields_table; + + protected $fields_language_table; + + protected $fields_data_table; + + protected $profile_cache = array(); + + /** + * Construct + * + * @param \phpbb\auth\auth $auth Auth object + * @param \phpbb\db\driver\driver_interface $db Database object + * @param \phpbb\event\dispatcher_interface $dispatcher Event dispatcher object + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\di\service_collection $type_collection + * @param \phpbb\user $user User object + * @param string $fields_table + * @param string $fields_language_table + * @param string $fields_data_table + */ + public function __construct(\phpbb\auth\auth $auth, \phpbb\db\driver\driver_interface $db, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\di\service_collection $type_collection, \phpbb\user $user, $fields_table, $fields_language_table, $fields_data_table) + { + $this->auth = $auth; + $this->db = $db; + $this->dispatcher = $dispatcher; + $this->request = $request; + $this->template = $template; + $this->type_collection = $type_collection; + $this->user = $user; + + $this->fields_table = $fields_table; + $this->fields_language_table = $fields_language_table; + $this->fields_data_table = $fields_data_table; + } + + /** + * Assign editable fields to template, mode can be profile (for profile change) or register (for registration) + * Called by ucp_profile and ucp_register + */ + public function generate_profile_fields($mode, $lang_id) + { + $sql_where = ''; + switch ($mode) + { + case 'register': + // If the field is required we show it on the registration page + $sql_where .= ' AND f.field_show_on_reg = 1'; + break; + + case 'profile': + // Show hidden fields to moderators/admins + if (!$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_')) + { + $sql_where .= ' AND f.field_show_profile = 1'; + } + break; + + default: + trigger_error('Wrong profile mode specified', E_USER_ERROR); + break; + } + + $sql = 'SELECT l.*, f.* + FROM ' . $this->fields_language_table . ' l, ' . $this->fields_table . " f + WHERE f.field_active = 1 + $sql_where + AND l.lang_id = " . (int) $lang_id . ' + AND l.field_id = f.field_id + ORDER BY f.field_order'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // Return templated field + $profile_field = $this->type_collection[$row['field_type']]; + $tpl_snippet = $profile_field->process_field_row('change', $row); + + $this->template->assign_block_vars('profile_fields', array( + 'LANG_NAME' => $this->user->lang($row['lang_name']), + 'LANG_EXPLAIN' => $this->user->lang($row['lang_explain']), + 'FIELD' => $tpl_snippet, + 'FIELD_ID' => $profile_field->get_field_ident($row), + 'S_REQUIRED' => ($row['field_required']) ? true : false, + )); + } + $this->db->sql_freeresult($result); + } + + /** + * Build profile cache, used for display + */ + protected function build_cache() + { + $this->profile_cache = array(); + + // Display hidden/no_view fields for admin/moderator + $sql = 'SELECT l.*, f.* + FROM ' . $this->fields_language_table . ' l, ' . $this->fields_table . ' f + WHERE l.lang_id = ' . $this->user->get_iso_lang_id() . ' + AND f.field_active = 1 ' . + ((!$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_')) ? ' AND f.field_hide = 0 ' : '') . ' + AND f.field_no_view = 0 + AND l.field_id = f.field_id + ORDER BY f.field_order'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $this->profile_cache[$row['field_ident']] = $row; + } + $this->db->sql_freeresult($result); + } + + /** + * Submit profile field for validation + */ + public function submit_cp_field($mode, $lang_id, &$cp_data, &$cp_error) + { + $sql_where = ''; + switch ($mode) + { + case 'register': + // If the field is required we show it on the registration page + $sql_where .= ' AND f.field_show_on_reg = 1'; + break; + + case 'profile': + // Show hidden fields to moderators/admins + if (!$this->auth->acl_gets('a_', 'm_') && !$this->auth->acl_getf_global('m_')) + { + $sql_where .= ' AND f.field_show_profile = 1'; + } + break; + + default: + trigger_error('Wrong profile mode specified', E_USER_ERROR); + break; + } + + $sql = 'SELECT l.*, f.* + FROM ' . $this->fields_language_table . ' l, ' . $this->fields_table . ' f + WHERE l.lang_id = ' . (int) $lang_id . " + AND f.field_active = 1 + $sql_where + AND l.field_id = f.field_id + ORDER BY f.field_order"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $profile_field = $this->type_collection[$row['field_type']]; + $cp_data['pf_' . $row['field_ident']] = $profile_field->get_profile_field($row); + $check_value = $cp_data['pf_' . $row['field_ident']]; + + if (($cp_result = $profile_field->validate_profile_field($check_value, $row)) !== false) + { + // If the result is not false, it's an error message + $cp_error[] = $cp_result; + } + } + $this->db->sql_freeresult($result); + } + + /** + * Update profile field data directly + */ + public function update_profile_field_data($user_id, $cp_data) + { + if (!count($cp_data)) + { + return; + } + + $sql = 'UPDATE ' . $this->fields_data_table . ' + SET ' . $this->db->sql_build_array('UPDATE', $cp_data) . ' + WHERE user_id = ' . (int) $user_id; + $this->db->sql_query($sql); + + if (!$this->db->sql_affectedrows()) + { + $cp_data = $this->build_insert_sql_array($cp_data); + $cp_data['user_id'] = (int) $user_id; + + $sql = 'INSERT INTO ' . $this->fields_data_table . ' ' . $this->db->sql_build_array('INSERT', $cp_data); + $this->db->sql_query($sql); + } + } + + /** + * Generate the template arrays in order to display the column names + * + * @param string $restrict_option Restrict the published fields to a certain profile field option + * @return array Returns an array with the template variables type, name and explain for the fields to display + */ + public function generate_profile_fields_template_headlines($restrict_option = '') + { + if (!count($this->profile_cache)) + { + $this->build_cache(); + } + + $tpl_fields = array(); + + // Go through the fields in correct order + foreach ($this->profile_cache as $field_ident => $field_data) + { + if ($restrict_option && !$field_data[$restrict_option]) + { + continue; + } + + $profile_field = $this->type_collection[$field_data['field_type']]; + + $tpl_fields[] = array( + 'PROFILE_FIELD_IDENT' => $field_ident, + 'PROFILE_FIELD_TYPE' => $field_data['field_type'], + 'PROFILE_FIELD_NAME' => $profile_field->get_field_name($field_data['lang_name']), + 'PROFILE_FIELD_EXPLAIN' => $this->user->lang($field_data['lang_explain']), + ); + } + + $profile_cache = $this->profile_cache; + + /** + * Event to modify template headlines of the generated profile fields + * + * @event core.generate_profile_fields_template_headlines + * @var string restrict_option Restrict the published fields to a certain profile field option + * @var array tpl_fields Array with template data fields + * @var array profile_cache A copy of the profile cache to make additional checks + * @since 3.1.6-RC1 + */ + $vars = array( + 'restrict_option', + 'tpl_fields', + 'profile_cache', + ); + extract($this->dispatcher->trigger_event('core.generate_profile_fields_template_headlines', compact($vars))); + unset($profile_cache); + + return $tpl_fields; + } + + /** + * Grab the user specific profile fields data + * + * @param int|array $user_ids Single user id or an array of ids + * @return array Users profile fields data + */ + public function grab_profile_fields_data($user_ids = 0) + { + if (!is_array($user_ids)) + { + $user_ids = array($user_ids); + } + + if (!count($this->profile_cache)) + { + $this->build_cache(); + } + + if (!count($user_ids)) + { + return array(); + } + + $sql = 'SELECT * + FROM ' . $this->fields_data_table . ' + WHERE ' . $this->db->sql_in_set('user_id', array_map('intval', $user_ids)); + $result = $this->db->sql_query($sql); + + $field_data = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $field_data[$row['user_id']] = $row; + } + $this->db->sql_freeresult($result); + + /** + * Event to modify profile fields data retrieved from the database + * + * @event core.grab_profile_fields_data + * @var array user_ids Single user id or an array of ids + * @var array field_data Array with profile fields data + * @since 3.1.0-b3 + */ + $vars = array('user_ids', 'field_data'); + extract($this->dispatcher->trigger_event('core.grab_profile_fields_data', compact($vars))); + + $user_fields = array(); + + // Go through the fields in correct order + foreach (array_keys($this->profile_cache) as $used_ident) + { + foreach ($field_data as $user_id => $row) + { + $user_fields[$user_id][$used_ident]['value'] = $row['pf_' . $used_ident]; + $user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident]; + } + + foreach ($user_ids as $user_id) + { + if (!isset($user_fields[$user_id][$used_ident]) && $this->profile_cache[$used_ident]['field_show_novalue']) + { + $user_fields[$user_id][$used_ident]['value'] = ''; + $user_fields[$user_id][$used_ident]['data'] = $this->profile_cache[$used_ident]; + } + } + } + + return $user_fields; + } + + /** + * Assign the user's profile fields data to the template + * + * @param array $profile_row Array with users profile field data + * @param bool $use_contact_fields Should we display contact fields as such? + * This requires special treatments (links should not be parsed in the values, and more) + * @return array + */ + public function generate_profile_fields_template_data($profile_row, $use_contact_fields = true) + { + // $profile_row == $user_fields[$row['user_id']]; + $tpl_fields = array(); + $tpl_fields['row'] = $tpl_fields['blockrow'] = array(); + + /** + * Event to modify data of the generated profile fields, before the template assignment loop + * + * @event core.generate_profile_fields_template_data_before + * @var array profile_row Array with users profile field data + * @var array tpl_fields Array with template data fields + * @var bool use_contact_fields Should we display contact fields as such? + * @since 3.1.0-b3 + */ + $vars = array('profile_row', 'tpl_fields', 'use_contact_fields'); + extract($this->dispatcher->trigger_event('core.generate_profile_fields_template_data_before', compact($vars))); + + foreach ($profile_row as $ident => $ident_ary) + { + $profile_field = $this->type_collection[$ident_ary['data']['field_type']]; + $value = $profile_field->get_profile_value($ident_ary['value'], $ident_ary['data']); + $value_raw = $profile_field->get_profile_value_raw($ident_ary['value'], $ident_ary['data']); + + if ($value === null) + { + continue; + } + + $field_desc = $contact_url = ''; + if ($use_contact_fields && $ident_ary['data']['field_is_contact']) + { + $value = $profile_field->get_profile_contact_value($ident_ary['value'], $ident_ary['data']); + $field_desc = $this->user->lang($ident_ary['data']['field_contact_desc']); + if (strpos($field_desc, '%s') !== false) + { + $field_desc = sprintf($field_desc, $value); + } + $contact_url = ''; + if (strpos($ident_ary['data']['field_contact_url'], '%s') !== false) + { + $contact_url = sprintf($ident_ary['data']['field_contact_url'], $value); + } + } + + $tpl_fields['row'] += array( + 'PROFILE_' . strtoupper($ident) . '_IDENT' => $ident, + 'PROFILE_' . strtoupper($ident) . '_VALUE' => $value, + 'PROFILE_' . strtoupper($ident) . '_VALUE_RAW' => $value_raw, + 'PROFILE_' . strtoupper($ident) . '_CONTACT' => $contact_url, + 'PROFILE_' . strtoupper($ident) . '_DESC' => $field_desc, + 'PROFILE_' . strtoupper($ident) . '_TYPE' => $ident_ary['data']['field_type'], + 'PROFILE_' . strtoupper($ident) . '_NAME' => $this->user->lang($ident_ary['data']['lang_name']), + 'PROFILE_' . strtoupper($ident) . '_EXPLAIN' => $this->user->lang($ident_ary['data']['lang_explain']), + + 'S_PROFILE_' . strtoupper($ident) . '_CONTACT' => $ident_ary['data']['field_is_contact'], + 'S_PROFILE_' . strtoupper($ident) => true, + ); + + $tpl_fields['blockrow'][] = array( + 'PROFILE_FIELD_IDENT' => $ident, + 'PROFILE_FIELD_VALUE' => $value, + 'PROFILE_FIELD_VALUE_RAW' => $value_raw, + 'PROFILE_FIELD_CONTACT' => $contact_url, + 'PROFILE_FIELD_DESC' => $field_desc, + 'PROFILE_FIELD_TYPE' => $ident_ary['data']['field_type'], + 'PROFILE_FIELD_NAME' => $this->user->lang($ident_ary['data']['lang_name']), + 'PROFILE_FIELD_EXPLAIN' => $this->user->lang($ident_ary['data']['lang_explain']), + + 'S_PROFILE_CONTACT' => $ident_ary['data']['field_is_contact'], + 'S_PROFILE_' . strtoupper($ident) => true, + ); + } + + /** + * Event to modify template data of the generated profile fields + * + * @event core.generate_profile_fields_template_data + * @var array profile_row Array with users profile field data + * @var array tpl_fields Array with template data fields + * @var bool use_contact_fields Should we display contact fields as such? + * @since 3.1.0-b3 + */ + $vars = array('profile_row', 'tpl_fields', 'use_contact_fields'); + extract($this->dispatcher->trigger_event('core.generate_profile_fields_template_data', compact($vars))); + + return $tpl_fields; + } + + /** + * Build Array for user insertion into custom profile fields table + */ + public function build_insert_sql_array($cp_data) + { + $sql_not_in = array(); + foreach ($cp_data as $key => $null) + { + $sql_not_in[] = (strncmp($key, 'pf_', 3) === 0) ? substr($key, 3) : $key; + } + + $sql = 'SELECT f.field_type, f.field_ident, f.field_default_value, l.lang_default_value + FROM ' . $this->fields_language_table . ' l, ' . $this->fields_table . ' f + WHERE l.lang_id = ' . $this->user->get_iso_lang_id() . ' + ' . ((count($sql_not_in)) ? ' AND ' . $this->db->sql_in_set('f.field_ident', $sql_not_in, true) : '') . ' + AND l.field_id = f.field_id'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $profile_field = $this->type_collection[$row['field_type']]; + $cp_data['pf_' . $row['field_ident']] = $profile_field->get_default_field_value($row); + } + $this->db->sql_freeresult($result); + + return $cp_data; + } +} diff --git a/phpbb/profilefields/type/type_base.php b/phpbb/profilefields/type/type_base.php new file mode 100644 index 0000000..9b4bada --- /dev/null +++ b/phpbb/profilefields/type/type_base.php @@ -0,0 +1,206 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +abstract class type_base implements type_interface +{ + /** + * Request object + * @var \phpbb\request\request + */ + protected $request; + + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Construct + * + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) + { + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function get_name() + { + return $this->user->lang('FIELD_' . strtoupper($this->get_name_short())); + } + + /** + * {@inheritDoc} + */ + public function get_service_name() + { + return 'profilefields.type.' . $this->get_name_short(); + } + + /** + * {@inheritDoc} + */ + public function get_template_filename() + { + return 'profilefields/' . $this->get_name_short() . '.html'; + } + + /** + * {@inheritDoc} + */ + public function get_field_ident($field_data) + { + return 'pf_' . $field_data['field_ident']; + } + + /** + * {@inheritDoc} + */ + public function get_field_name($field_name) + { + return isset($this->user->lang[$field_name]) ? $this->user->lang[$field_name] : $field_name; + } + + /** + * {@inheritDoc} + */ + public function get_profile_contact_value($field_value, $field_data) + { + return $this->get_profile_value($field_value, $field_data); + } + + /** + * {@inheritDoc} + */ + public function get_language_options_input($field_data) + { + $field_data['l_lang_name'] = $this->request->variable('l_lang_name', array(0 => ''), true); + $field_data['l_lang_explain'] = $this->request->variable('l_lang_explain', array(0 => ''), true); + $field_data['l_lang_default_value'] = $this->request->variable('l_lang_default_value', array(0 => ''), true); + $field_data['l_lang_options'] = $this->request->variable('l_lang_options', array(0 => ''), true); + + return $field_data; + } + + /** + * {@inheritDoc} + */ + public function prepare_options_form(&$exclude_options, &$visibility_options) + { + return $this->request->variable('lang_options', '', true); + } + + /** + * {@inheritDoc} + */ + public function validate_options_on_submit($error, $field_data) + { + return $error; + } + + /** + * {@inheritDoc} + */ + public function get_excluded_options($key, $action, $current_value, &$field_data, $step) + { + if ($step == 3 && ($field_data[$key] || $action != 'edit') && $key == 'l_lang_options' && is_array($field_data[$key])) + { + foreach ($field_data[$key] as $lang_id => $options) + { + $field_data[$key][$lang_id] = is_array($options) ? $options : explode("\n", $options); + } + + return $current_value; + } + + return $current_value; + } + + /** + * {@inheritDoc} + */ + public function prepare_hidden_fields($step, $key, $action, &$field_data) + { + if (!$this->request->is_set($key)) + { + // Do not set this variable, we will use the default value + return null; + } + else if ($key == 'field_ident' && isset($field_data[$key])) + { + return $field_data[$key]; + } + else + { + $default_value = ''; + $lang_fields = array( + 'l_lang_name', + 'l_lang_explain', + 'l_lang_default_value', + 'l_lang_options', + ); + + if (in_array($key, $lang_fields)) + { + $default_value = array(0 => ''); + } + return $this->request->variable($key, $default_value, true); + } + } + + /** + * {@inheritDoc} + */ + public function display_options(&$template_vars, &$field_data) + { + return; + } + + /** + * Return templated value/field. Possible values for $mode are: + * change == user is able to set/enter profile values; preview == just show the value + */ + public function process_field_row($mode, $profile_row) + { + $preview_options = ($mode == 'preview') ? $profile_row['lang_options'] : false; + + // set template filename + $this->template->set_filenames(array( + 'cp_body' => $this->get_template_filename(), + )); + + // empty previously filled blockvars + $this->template->destroy_block_vars($this->get_name_short()); + + // Assign template variables + $this->generate_field($profile_row, $preview_options); + + return $this->template->assign_display('cp_body'); + } +} diff --git a/phpbb/profilefields/type/type_bool.php b/phpbb/profilefields/type/type_bool.php new file mode 100644 index 0000000..9c09e27 --- /dev/null +++ b/phpbb/profilefields/type/type_bool.php @@ -0,0 +1,415 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +class type_bool extends type_base +{ + /** + * Profile fields language helper + * @var \phpbb\profilefields\lang_helper + */ + protected $lang_helper; + + /** + * Request object + * @var \phpbb\request\request + */ + protected $request; + + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Construct + * + * @param \phpbb\profilefields\lang_helper $lang_helper Profile fields language helper + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\profilefields\lang_helper $lang_helper, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) + { + $this->lang_helper = $lang_helper; + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function get_name_short() + { + return 'bool'; + } + + /** + * {@inheritDoc} + */ + public function get_options($default_lang_id, $field_data) + { + $profile_row = array( + 'var_name' => 'field_default_value', + 'field_id' => 1, + 'lang_name' => $field_data['lang_name'], + 'lang_explain' => $field_data['lang_explain'], + 'lang_id' => $default_lang_id, + 'field_default_value' => $field_data['field_default_value'], + 'field_ident' => 'field_default_value', + 'field_type' => $this->get_service_name(), + 'field_length' => $field_data['field_length'], + 'lang_options' => $field_data['lang_options'], + ); + + $options = array( + 0 => array('TITLE' => $this->user->lang['FIELD_TYPE'], 'EXPLAIN' => $this->user->lang['BOOL_TYPE_EXPLAIN'], 'FIELD' => ''), + 1 => array('TITLE' => $this->user->lang['DEFAULT_VALUE'], 'FIELD' => $this->process_field_row('preview', $profile_row)), + ); + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_default_option_values() + { + return array( + 'field_length' => 1, + 'field_minlen' => 0, + 'field_maxlen' => 0, + 'field_validation' => '', + 'field_novalue' => 0, + 'field_default_value' => 0, + ); + } + + /** + * {@inheritDoc} + */ + public function get_default_field_value($field_data) + { + return $field_data['field_default_value']; + } + + /** + * {@inheritDoc} + */ + public function get_profile_field($profile_row) + { + $var_name = 'pf_' . $profile_row['field_ident']; + + // Checkbox + if ($profile_row['field_length'] == 2) + { + return ($this->request->is_set($var_name)) ? 1 : 0; + } + else + { + return $this->request->variable($var_name, (int) $profile_row['field_default_value']); + } + } + + /** + * {@inheritDoc} + */ + public function validate_profile_field(&$field_value, $field_data) + { + $field_value = (bool) $field_value; + + if (!$field_value && $field_data['field_required']) + { + return $this->user->lang('FIELD_REQUIRED', $this->get_field_name($field_data['lang_name'])); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value($field_value, $field_data) + { + $field_id = $field_data['field_id']; + $lang_id = $field_data['lang_id']; + + if (!$this->lang_helper->is_set($field_id, $lang_id)) + { + $this->lang_helper->load_option_lang($lang_id); + } + + if (!$field_value && $field_data['field_show_novalue']) + { + $field_value = $field_data['field_default_value']; + } + + if ($field_data['field_length'] == 1) + { + return ($this->lang_helper->is_set($field_id, $lang_id, (int) $field_value)) ? $this->lang_helper->get($field_id, $lang_id, (int) $field_value) : null; + } + else if (!$field_value) + { + return null; + } + else + { + return $this->lang_helper->is_set($field_id, $lang_id, $field_value + 1) ? $this->lang_helper->get($field_id, $lang_id, $field_value + 1) : null; + } + } + + /** + * {@inheritDoc} + */ + public function get_profile_value_raw($field_value, $field_data) + { + if ($field_value == $field_data['field_novalue'] && !$field_data['field_show_novalue']) + { + return null; + } + + if (!$field_value && $field_data['field_show_novalue']) + { + $field_value = $field_data['field_novalue']; + } + + return $field_value; + } + + /** + * {@inheritDoc} + */ + public function generate_field($profile_row, $preview_options = false) + { + $profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; + $field_ident = $profile_row['field_ident']; + $default_value = $profile_row['field_default_value']; + + // checkbox - set the value to "true" if it has been set to 1 + if ($profile_row['field_length'] == 2) + { + $value = ($this->request->is_set($field_ident) && $this->request->variable($field_ident, $default_value) == 1) ? true : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); + } + else + { + $value = ($this->request->is_set($field_ident)) ? $this->request->variable($field_ident, $default_value) : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); + } + + $profile_row['field_value'] = (int) $value; + $this->template->assign_block_vars('bool', array_change_key_case($profile_row, CASE_UPPER)); + + if ($profile_row['field_length'] == 1) + { + if (!$this->lang_helper->is_set($profile_row['field_id'], $profile_row['lang_id'], 1)) + { + if ($preview_options) + { + $this->lang_helper->load_preview_options($profile_row['field_id'], $profile_row['lang_id'], $preview_options); + } + else + { + $this->lang_helper->load_option_lang($profile_row['lang_id']); + } + } + + $options = $this->lang_helper->get($profile_row['field_id'], $profile_row['lang_id']); + foreach ($options as $option_id => $option_value) + { + $this->template->assign_block_vars('bool.options', array( + 'OPTION_ID' => $option_id, + 'CHECKED' => ($value == $option_id) ? ' checked="checked"' : '', + 'VALUE' => $option_value, + )); + } + } + } + + /** + * {@inheritDoc} + */ + public function get_field_ident($field_data) + { + return ($field_data['field_length'] == '1') ? '' : 'pf_' . $field_data['field_ident']; + } + + /** + * {@inheritDoc} + */ + public function get_database_column_type() + { + return 'TINT:2'; + } + + /** + * {@inheritDoc} + */ + public function get_language_options($field_data) + { + $options = array( + 'lang_name' => 'string', + 'lang_options' => 'two_options', + ); + + if ($field_data['lang_explain']) + { + $options['lang_explain'] = 'text'; + } + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_language_options_input($field_data) + { + $field_data['l_lang_name'] = $this->request->variable('l_lang_name', array(0 => ''), true); + $field_data['l_lang_explain'] = $this->request->variable('l_lang_explain', array(0 => ''), true); + $field_data['l_lang_default_value'] = $this->request->variable('l_lang_default_value', array(0 => ''), true); + + /** + * @todo check if this line is correct... + $field_data['l_lang_default_value'] = $this->request->variable('l_lang_default_value', array(0 => array('')), true); + */ + $field_data['l_lang_options'] = $this->request->variable('l_lang_options', array(0 => array('')), true); + + return $field_data; + } + + /** + * {@inheritDoc} + */ + public function prepare_options_form(&$exclude_options, &$visibility_options) + { + $exclude_options[1][] = 'lang_options'; + + return $this->request->variable('lang_options', array(''), true); + } + + /** + * {@inheritDoc} + */ + public function validate_options_on_submit($error, $field_data) + { + if (empty($field_data['lang_options'][0]) || empty($field_data['lang_options'][1])) + { + $error[] = $this->user->lang['NO_FIELD_ENTRIES']; + } + + return $error; + } + + /** + * {@inheritDoc} + */ + public function get_excluded_options($key, $action, $current_value, &$field_data, $step) + { + if ($step == 2 && $key == 'field_default_value') + { + // 'field_length' == 1 defines radio buttons. Possible values are 1 or 2 only. + // 'field_length' == 2 defines checkbox. Possible values are 0 or 1 only. + // If we switch the type on step 2, we have to adjust field value. + // 1 is a common value for the checkbox and radio buttons. + + // Adjust unchecked checkbox value. + // If we return or save settings from 2nd/3rd page + // and the checkbox is unchecked, set the value to 0. + if ($this->request->is_set('step') && !$this->request->is_set($key)) + { + return 0; + } + + // If we switch to the checkbox type but former radio buttons value was 2, + // which is not the case for the checkbox, set it to 0 (unchecked). + if ($field_data['field_length'] == 2 && $current_value == 2) + { + return 0; + } + // If we switch to the radio buttons but the former checkbox value was 0, + // which is not the case for the radio buttons, set it to 0. + else if ($field_data['field_length'] == 1 && $current_value == 0) + { + return 2; + } + } + + if ($key == 'l_lang_options' && $this->request->is_set($key)) + { + $field_data[$key] = $this->request->variable($key, array(0 => array('')), true); + + return $current_value; + } + + return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); + } + + /** + * {@inheritDoc} + */ + public function prepare_hidden_fields($step, $key, $action, &$field_data) + { + if ($key == 'field_default_value') + { + $field_length = $this->request->variable('field_length', 0); + + // Do a simple is set check if using checkbox. + if ($field_length == 2) + { + return $this->request->is_set($key); + } + return $this->request->variable($key, $field_data[$key], true); + } + + $default_lang_options = array( + 'l_lang_options' => array(0 => array('')), + 'lang_options' => array(0 => ''), + ); + + if (isset($default_lang_options[$key]) && $this->request->is_set($key)) + { + return $this->request->variable($key, $default_lang_options[$key], true); + } + + return parent::prepare_hidden_fields($step, $key, $action, $field_data); + } + + /** + * {@inheritDoc} + */ + public function display_options(&$template_vars, &$field_data) + { + // Initialize these array elements if we are creating a new field + if (!count($field_data['lang_options'])) + { + // No options have been defined for a boolean field. + $field_data['lang_options'][0] = ''; + $field_data['lang_options'][1] = ''; + } + + $template_vars = array_merge($template_vars, array( + 'S_BOOL' => true, + 'L_LANG_OPTIONS_EXPLAIN' => $this->user->lang['BOOL_ENTRIES_EXPLAIN'], + 'FIRST_LANG_OPTION' => $field_data['lang_options'][0], + 'SECOND_LANG_OPTION' => $field_data['lang_options'][1], + )); + } +} diff --git a/phpbb/profilefields/type/type_date.php b/phpbb/profilefields/type/type_date.php new file mode 100644 index 0000000..5a1a6db --- /dev/null +++ b/phpbb/profilefields/type/type_date.php @@ -0,0 +1,374 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +class type_date extends type_base +{ + /** + * Request object + * @var \phpbb\request\request + */ + protected $request; + + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Construct + * + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) + { + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function get_name_short() + { + return 'date'; + } + + /** + * {@inheritDoc} + */ + public function get_options($default_lang_id, $field_data) + { + $profile_row = array( + 'var_name' => 'field_default_value', + 'lang_name' => $field_data['lang_name'], + 'lang_explain' => $field_data['lang_explain'], + 'lang_id' => $default_lang_id, + 'field_default_value' => $field_data['field_default_value'], + 'field_ident' => 'field_default_value', + 'field_type' => $this->get_service_name(), + 'field_length' => $field_data['field_length'], + 'lang_options' => $field_data['lang_options'], + ); + + $always_now = $this->request->variable('always_now', -1); + if ($always_now == -1) + { + $s_checked = ($field_data['field_default_value'] == 'now') ? true : false; + } + else + { + $s_checked = ($always_now) ? true : false; + } + + $options = array( + 0 => array('TITLE' => $this->user->lang['DEFAULT_VALUE'], 'FIELD' => $this->process_field_row('preview', $profile_row)), + 1 => array('TITLE' => $this->user->lang['ALWAYS_TODAY'], 'FIELD' => ''), + ); + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_default_option_values() + { + return array( + 'field_length' => 10, + 'field_minlen' => 10, + 'field_maxlen' => 10, + 'field_validation' => '', + 'field_novalue' => ' 0- 0- 0', + 'field_default_value' => ' 0- 0- 0', + ); + } + + /** + * {@inheritDoc} + */ + public function get_default_field_value($field_data) + { + if ($field_data['field_default_value'] == 'now') + { + $now = getdate(); + $field_data['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); + } + + return $field_data['field_default_value']; + } + + /** + * {@inheritDoc} + */ + public function get_profile_field($profile_row) + { + $var_name = 'pf_' . $profile_row['field_ident']; + + if (!$this->request->is_set($var_name . '_day')) + { + if ($profile_row['field_default_value'] == 'now') + { + $now = getdate(); + $profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); + } + list($day, $month, $year) = explode('-', $profile_row['field_default_value']); + } + else + { + $day = $this->request->variable($var_name . '_day', 0); + $month = $this->request->variable($var_name . '_month', 0); + $year = $this->request->variable($var_name . '_year', 0); + } + + return sprintf('%2d-%2d-%4d', $day, $month, $year); + } + + /** + * {@inheritDoc} + */ + public function validate_profile_field(&$field_value, $field_data) + { + $field_validate = explode('-', $field_value); + + $day = (isset($field_validate[0])) ? (int) $field_validate[0] : 0; + $month = (isset($field_validate[1])) ? (int) $field_validate[1] : 0; + $year = (isset($field_validate[2])) ? (int) $field_validate[2] : 0; + + if ((!$day || !$month || !$year) && !$field_data['field_required']) + { + return false; + } + + if ((!$day || !$month || !$year) && $field_data['field_required']) + { + return $this->user->lang('FIELD_REQUIRED', $this->get_field_name($field_data['lang_name'])); + } + + if ($day < 0 || $day > 31 || $month < 0 || $month > 12 || ($year < 1901 && $year > 0) || $year > gmdate('Y', time()) + 50) + { + return $this->user->lang('FIELD_INVALID_DATE', $this->get_field_name($field_data['lang_name'])); + } + + if (checkdate($month, $day, $year) === false) + { + return $this->user->lang('FIELD_INVALID_DATE', $this->get_field_name($field_data['lang_name'])); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value($field_value, $field_data) + { + $date = explode('-', $field_value); + $day = (isset($date[0])) ? (int) $date[0] : 0; + $month = (isset($date[1])) ? (int) $date[1] : 0; + $year = (isset($date[2])) ? (int) $date[2] : 0; + + if (!$day && !$month && !$year && !$field_data['field_show_novalue']) + { + return null; + } + else if ($day && $month && $year) + { + // Date should display as the same date for every user regardless of timezone + return $this->user->create_datetime() + ->setDate($year, $month, $day) + ->setTime(0, 0, 0) + ->format($this->user->lang['DATE_FORMAT'], true); + } + + return $field_value; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value_raw($field_value, $field_data) + { + if (($field_value === '' || $field_value === null) && !$field_data['field_show_novalue']) + { + return null; + } + + return $field_value; + } + + /** + * {@inheritDoc} + */ + public function generate_field($profile_row, $preview_options = false) + { + $profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; + $field_ident = $profile_row['field_ident']; + + $now = getdate(); + + if (!$this->request->is_set($profile_row['field_ident'] . '_day')) + { + if ($profile_row['field_default_value'] == 'now') + { + $profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); + } + list($day, $month, $year) = explode('-', ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $profile_row['field_default_value'] : $this->user->profile_fields[$field_ident])); + } + else + { + if ($preview_options !== false && $profile_row['field_default_value'] == 'now') + { + $profile_row['field_default_value'] = sprintf('%2d-%2d-%4d', $now['mday'], $now['mon'], $now['year']); + list($day, $month, $year) = explode('-', ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $profile_row['field_default_value'] : $this->user->profile_fields[$field_ident])); + } + else + { + $day = $this->request->variable($profile_row['field_ident'] . '_day', 0); + $month = $this->request->variable($profile_row['field_ident'] . '_month', 0); + $year = $this->request->variable($profile_row['field_ident'] . '_year', 0); + } + } + + $profile_row['s_day_options'] = ''; + for ($i = 1; $i < 32; $i++) + { + $profile_row['s_day_options'] .= '"; + } + + $profile_row['s_month_options'] = ''; + for ($i = 1; $i < 13; $i++) + { + $profile_row['s_month_options'] .= '"; + } + + $profile_row['s_year_options'] = ''; + for ($i = 1901; $i <= $now['year'] + 50; $i++) + { + $profile_row['s_year_options'] .= '"; + } + + $profile_row['field_value'] = 0; + $this->template->assign_block_vars('date', array_change_key_case($profile_row, CASE_UPPER)); + } + + /** + * {@inheritDoc} + */ + public function get_field_ident($field_data) + { + return ''; + } + + /** + * {@inheritDoc} + */ + public function get_database_column_type() + { + return 'VCHAR:10'; + } + + /** + * {@inheritDoc} + */ + public function get_language_options($field_data) + { + $options = array( + 'lang_name' => 'string', + ); + + if ($field_data['lang_explain']) + { + $options['lang_explain'] = 'text'; + } + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_excluded_options($key, $action, $current_value, &$field_data, $step) + { + if ($step == 2 && $key == 'field_default_value') + { + $always_now = $this->request->variable('always_now', -1); + + if ($always_now == 1 || ($always_now === -1 && $current_value == 'now')) + { + $now = getdate(); + + $field_data['field_default_value_day'] = $now['mday']; + $field_data['field_default_value_month'] = $now['mon']; + $field_data['field_default_value_year'] = $now['year']; + $current_value = 'now'; + $this->request->overwrite('field_default_value', $current_value, \phpbb\request\request_interface::POST); + } + else + { + if ($this->request->is_set('field_default_value_day')) + { + $field_data['field_default_value_day'] = $this->request->variable('field_default_value_day', 0); + $field_data['field_default_value_month'] = $this->request->variable('field_default_value_month', 0); + $field_data['field_default_value_year'] = $this->request->variable('field_default_value_year', 0); + $current_value = sprintf('%2d-%2d-%4d', $field_data['field_default_value_day'], $field_data['field_default_value_month'], $field_data['field_default_value_year']); + $this->request->overwrite('field_default_value', $current_value, \phpbb\request\request_interface::POST); + } + else + { + list($field_data['field_default_value_day'], $field_data['field_default_value_month'], $field_data['field_default_value_year']) = explode('-', $current_value); + } + } + + return $current_value; + } + + return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); + } + + /** + * {@inheritDoc} + */ + public function prepare_hidden_fields($step, $key, $action, &$field_data) + { + if ($key == 'field_default_value') + { + $always_now = $this->request->variable('always_now', 0); + + if ($always_now) + { + return 'now'; + } + else if ($this->request->is_set('field_default_value_day')) + { + $field_data['field_default_value_day'] = $this->request->variable('field_default_value_day', 0); + $field_data['field_default_value_month'] = $this->request->variable('field_default_value_month', 0); + $field_data['field_default_value_year'] = $this->request->variable('field_default_value_year', 0); + return sprintf('%2d-%2d-%4d', $field_data['field_default_value_day'], $field_data['field_default_value_month'], $field_data['field_default_value_year']); + } + } + + return parent::prepare_hidden_fields($step, $key, $action, $field_data); + } +} diff --git a/phpbb/profilefields/type/type_dropdown.php b/phpbb/profilefields/type/type_dropdown.php new file mode 100644 index 0000000..d54404b --- /dev/null +++ b/phpbb/profilefields/type/type_dropdown.php @@ -0,0 +1,325 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +class type_dropdown extends type_base +{ + /** + * Profile fields language helper + * @var \phpbb\profilefields\lang_helper + */ + protected $lang_helper; + + /** + * Request object + * @var \phpbb\request\request + */ + protected $request; + + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Construct + * + * @param \phpbb\profilefields\lang_helper $lang_helper Profile fields language helper + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\profilefields\lang_helper $lang_helper, \phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) + { + $this->lang_helper = $lang_helper; + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function get_name_short() + { + return 'dropdown'; + } + + /** + * {@inheritDoc} + */ + public function get_options($default_lang_id, $field_data) + { + $profile_row[0] = array( + 'var_name' => 'field_default_value', + 'field_id' => 1, + 'lang_name' => $field_data['lang_name'], + 'lang_explain' => $field_data['lang_explain'], + 'lang_id' => $default_lang_id, + 'field_default_value' => $field_data['field_default_value'], + 'field_ident' => 'field_default_value', + 'field_type' => $this->get_service_name(), + 'lang_options' => $field_data['lang_options'], + ); + + $profile_row[1] = $profile_row[0]; + $profile_row[1]['var_name'] = 'field_novalue'; + $profile_row[1]['field_ident'] = 'field_novalue'; + $profile_row[1]['field_default_value'] = $field_data['field_novalue']; + + $options = array( + 0 => array('TITLE' => $this->user->lang['DEFAULT_VALUE'], 'FIELD' => $this->process_field_row('preview', $profile_row[0])), + 1 => array('TITLE' => $this->user->lang['NO_VALUE_OPTION'], 'EXPLAIN' => $this->user->lang['NO_VALUE_OPTION_EXPLAIN'], 'FIELD' => $this->process_field_row('preview', $profile_row[1])), + ); + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_default_option_values() + { + return array( + 'field_length' => 0, + 'field_minlen' => 0, + 'field_maxlen' => 5, + 'field_validation' => '', + 'field_novalue' => 0, + 'field_default_value' => 0, + ); + } + + /** + * {@inheritDoc} + */ + public function get_default_field_value($field_data) + { + return $field_data['field_default_value']; + } + + /** + * {@inheritDoc} + */ + public function get_profile_field($profile_row) + { + $var_name = 'pf_' . $profile_row['field_ident']; + return $this->request->variable($var_name, (int) $profile_row['field_default_value']); + } + + /** + * {@inheritDoc} + */ + public function validate_profile_field(&$field_value, $field_data) + { + $field_value = (int) $field_value; + + // retrieve option lang data if necessary + if (!$this->lang_helper->is_set($field_data['field_id'], $field_data['lang_id'], 1)) + { + $this->lang_helper->load_option_lang($field_data['lang_id']); + } + + if (!$this->lang_helper->is_set($field_data['field_id'], $field_data['lang_id'], $field_value)) + { + return $this->user->lang('FIELD_INVALID_VALUE', $this->get_field_name($field_data['lang_name'])); + } + + if ($field_value == $field_data['field_novalue'] && $field_data['field_required']) + { + return $this->user->lang('FIELD_REQUIRED', $this->get_field_name($field_data['lang_name'])); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value($field_value, $field_data) + { + $field_id = $field_data['field_id']; + $lang_id = $field_data['lang_id']; + if (!$this->lang_helper->is_set($field_id, $lang_id)) + { + $this->lang_helper->load_option_lang($lang_id); + } + + if ($field_value == $field_data['field_novalue'] && !$field_data['field_show_novalue']) + { + return null; + } + + $field_value = (int) $field_value; + + // User not having a value assigned + if (!$this->lang_helper->is_set($field_id, $lang_id, $field_value)) + { + if ($field_data['field_show_novalue']) + { + $field_value = $field_data['field_novalue']; + } + else + { + return null; + } + } + + return $this->lang_helper->get($field_id, $lang_id, $field_value); + } + + /** + * {@inheritDoc} + */ + public function get_profile_value_raw($field_value, $field_data) + { + if ($field_value == $field_data['field_novalue'] && !$field_data['field_show_novalue']) + { + return null; + } + + if (!$field_value && $field_data['field_show_novalue']) + { + $field_value = $field_data['field_novalue']; + } + + return $field_value; + } + + /** + * {@inheritDoc} + */ + public function generate_field($profile_row, $preview_options = false) + { + $profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; + $field_ident = $profile_row['field_ident']; + $default_value = $profile_row['field_default_value']; + + $value = ($this->request->is_set($field_ident)) ? $this->request->variable($field_ident, $default_value) : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); + + if (!$this->lang_helper->is_set($profile_row['field_id'], $profile_row['lang_id'], 1)) + { + if ($preview_options) + { + $this->lang_helper->load_preview_options($profile_row['field_id'], $profile_row['lang_id'], $preview_options); + } + else + { + $this->lang_helper->load_option_lang($profile_row['lang_id']); + } + } + + $profile_row['field_value'] = (int) $value; + $this->template->assign_block_vars('dropdown', array_change_key_case($profile_row, CASE_UPPER)); + + $options = $this->lang_helper->get($profile_row['field_id'], $profile_row['lang_id']); + foreach ($options as $option_id => $option_value) + { + $this->template->assign_block_vars('dropdown.options', array( + 'OPTION_ID' => $option_id, + 'SELECTED' => ($value == $option_id) ? ' selected="selected"' : '', + 'VALUE' => $option_value, + )); + } + } + + /** + * {@inheritDoc} + */ + public function get_database_column_type() + { + return 'UINT'; + } + + /** + * {@inheritDoc} + */ + public function get_language_options($field_data) + { + $options = array( + 'lang_name' => 'string', + 'lang_options' => 'optionfield', + ); + + if ($field_data['lang_explain']) + { + $options['lang_explain'] = 'text'; + } + + return $options; + } + + /** + * {@inheritDoc} + */ + public function prepare_options_form(&$exclude_options, &$visibility_options) + { + $exclude_options[1][] = 'lang_options'; + + return $this->request->variable('lang_options', '', true); + } + + /** + * {@inheritDoc} + */ + public function validate_options_on_submit($error, $field_data) + { + if (!count($field_data['lang_options'])) + { + $error[] = $this->user->lang['NO_FIELD_ENTRIES']; + } + + return $error; + } + + /** + * {@inheritDoc} + */ + public function get_excluded_options($key, $action, $current_value, &$field_data, $step) + { + if ($step == 2 && $key == 'field_maxlen') + { + // Get the number of options if this key is 'field_maxlen' + return count(explode("\n", $this->request->variable('lang_options', '', true))); + } + + return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); + } + + /** + * {@inheritDoc} + */ + public function display_options(&$template_vars, &$field_data) + { + // Initialize these array elements if we are creating a new field + if (!count($field_data['lang_options'])) + { + // No options have been defined for the dropdown menu + $field_data['lang_options'] = array(); + } + + $template_vars = array_merge($template_vars, array( + 'S_DROPDOWN' => true, + 'L_LANG_OPTIONS_EXPLAIN' => $this->user->lang['DROPDOWN_ENTRIES_EXPLAIN'], + 'LANG_OPTIONS' => implode("\n", $field_data['lang_options']), + )); + } +} diff --git a/phpbb/profilefields/type/type_googleplus.php b/phpbb/profilefields/type/type_googleplus.php new file mode 100644 index 0000000..e6729b1 --- /dev/null +++ b/phpbb/profilefields/type/type_googleplus.php @@ -0,0 +1,66 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +class type_googleplus extends type_string +{ + /** + * {@inheritDoc} + */ + public function get_name() + { + return $this->user->lang('FIELD_GOOGLEPLUS'); + } + + /** + * {@inheritDoc} + */ + public function get_service_name() + { + return 'profilefields.type.googleplus'; + } + + /** + * {@inheritDoc} + */ + public function get_default_option_values() + { + return array( + 'field_length' => 20, + 'field_minlen' => 3, + 'field_maxlen' => 255, + 'field_validation' => '(?:(?!\.{2,})([^<>=+]))+', + 'field_novalue' => '', + 'field_default_value' => '', + ); + } + + /** + * {@inheritDoc} + */ + public function get_profile_contact_value($field_value, $field_data) + { + if (!$field_value && !$field_data['field_show_novalue']) + { + return null; + } + + if (!is_numeric($field_value)) + { + $field_value = '+' . $field_value; + } + + return $field_value; + } +} diff --git a/phpbb/profilefields/type/type_int.php b/phpbb/profilefields/type/type_int.php new file mode 100644 index 0000000..9dc0181 --- /dev/null +++ b/phpbb/profilefields/type/type_int.php @@ -0,0 +1,249 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +class type_int extends type_base +{ + /** + * Request object + * @var \phpbb\request\request + */ + protected $request; + + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Construct + * + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) + { + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function get_name_short() + { + return 'int'; + } + + /** + * {@inheritDoc} + */ + public function get_options($default_lang_id, $field_data) + { + $options = array( + 0 => array('TITLE' => $this->user->lang['FIELD_LENGTH'], 'FIELD' => ''), + 1 => array('TITLE' => $this->user->lang['MIN_FIELD_NUMBER'], 'FIELD' => ''), + 2 => array('TITLE' => $this->user->lang['MAX_FIELD_NUMBER'], 'FIELD' => ''), + 3 => array('TITLE' => $this->user->lang['DEFAULT_VALUE'], 'FIELD' => ''), + ); + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_default_option_values() + { + return array( + 'field_length' => 5, + 'field_minlen' => 0, + 'field_maxlen' => 100, + 'field_validation' => '', + 'field_novalue' => 0, + 'field_default_value' => 0, + ); + } + + /** + * {@inheritDoc} + */ + public function get_default_field_value($field_data) + { + if ($field_data['field_default_value'] === '') + { + // We cannot insert an empty string into an integer column. + return null; + } + + return $field_data['field_default_value']; + } + + /** + * {@inheritDoc} + */ + public function get_profile_field($profile_row) + { + $var_name = 'pf_' . $profile_row['field_ident']; + if ($this->request->is_set($var_name) && $this->request->variable($var_name, '') === '') + { + return null; + } + else + { + return $this->request->variable($var_name, (int) $profile_row['field_default_value']); + } + } + + /** + * {@inheritDoc} + */ + public function validate_profile_field(&$field_value, $field_data) + { + if (trim($field_value) === '' && !$field_data['field_required']) + { + return false; + } + + $field_value = (int) $field_value; + + if ($field_value < $field_data['field_minlen']) + { + return $this->user->lang('FIELD_TOO_SMALL', (int) $field_data['field_minlen'], $this->get_field_name($field_data['lang_name'])); + } + else if ($field_value > $field_data['field_maxlen']) + { + return $this->user->lang('FIELD_TOO_LARGE', (int) $field_data['field_maxlen'], $this->get_field_name($field_data['lang_name'])); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value($field_value, $field_data) + { + if (($field_value === '' || $field_value === null) && !$field_data['field_show_novalue']) + { + return null; + } + return (int) $field_value; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value_raw($field_value, $field_data) + { + if (($field_value === '' || $field_value === null) && !$field_data['field_show_novalue']) + { + return null; + } + return (int) $field_value; + } + + /** + * {@inheritDoc} + */ + public function generate_field($profile_row, $preview_options = false) + { + $profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; + $field_ident = $profile_row['field_ident']; + $default_value = $profile_row['field_default_value']; + + if ($this->request->is_set($field_ident)) + { + $value = ($this->request->variable($field_ident, '') === '') ? null : $this->request->variable($field_ident, $default_value); + } + else + { + if ($preview_options === false && array_key_exists($field_ident, $this->user->profile_fields) && is_null($this->user->profile_fields[$field_ident])) + { + $value = null; + } + else if (!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) + { + $value = $default_value; + } + else + { + $value = $this->user->profile_fields[$field_ident]; + } + } + + $profile_row['field_value'] = (is_null($value) || $value === '') ? '' : (int) $value; + + $this->template->assign_block_vars('int', array_change_key_case($profile_row, CASE_UPPER)); + } + + /** + * {@inheritDoc} + */ + public function get_field_ident($field_data) + { + return 'pf_' . $field_data['field_ident']; + } + + /** + * {@inheritDoc} + */ + public function get_database_column_type() + { + return 'BINT'; + } + + /** + * {@inheritDoc} + */ + public function get_language_options($field_data) + { + $options = array( + 'lang_name' => 'string', + ); + + if ($field_data['lang_explain']) + { + $options['lang_explain'] = 'text'; + } + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_excluded_options($key, $action, $current_value, &$field_data, $step) + { + if ($step == 2 && $key == 'field_default_value') + { + // Permit an empty string + if ($action == 'create' && $this->request->variable('field_default_value', '') === '') + { + return ''; + } + } + + return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); + } +} diff --git a/phpbb/profilefields/type/type_interface.php b/phpbb/profilefields/type/type_interface.php new file mode 100644 index 0000000..93b9e4b --- /dev/null +++ b/phpbb/profilefields/type/type_interface.php @@ -0,0 +1,226 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +interface type_interface +{ + /** + * Get the translated name of the type + * + * @return string Translated name of the field type + */ + public function get_name(); + + /** + * Get the short name of the type, used for error messages and template loops + * + * @return string lowercase version of the fields type + */ + public function get_name_short(); + + /** + * Get the name of service representing the type + * + * @return string lowercase version of the fields type + */ + public function get_service_name(); + + /** + * Get the name of template file for this type + * + * @return string Returns the name of the template file + */ + public function get_template_filename(); + + /** + * Get dropdown options for second step in ACP + * + * @param string $default_lang_id ID of the default language + * @param array $field_data Array with data for this field + * @return array with the acp options + */ + public function get_options($default_lang_id, $field_data); + + /** + * Get default values for the options of this type + * + * @return array with values like default field size and more + */ + public function get_default_option_values(); + + /** + * Get default value for this type + * + * @param array $field_data Array with data for this field + * @return mixed default value for new users when no value is given + */ + public function get_default_field_value($field_data); + + /** + * Get profile field value on submit + * + * @param array $profile_row Array with data for this field + * @return mixed Submitted value of the profile field + */ + public function get_profile_field($profile_row); + + /** + * Validate entered profile field data + * + * @param mixed $field_value Field value to validate + * @param array $field_data Array with requirements of the field + * @return mixed String with the error message + */ + public function validate_profile_field(&$field_value, $field_data); + + /** + * Get Profile Value for display + * + * @param mixed $field_value Field value as stored in the database + * @param array $field_data Array with requirements of the field + * @return mixed Field value to display + */ + public function get_profile_value($field_value, $field_data); + + /** + * Get Profile Value ID for display (the raw, unprocessed user data) + * + * @param mixed $field_value Field value as stored in the database + * @param array $field_data Array with requirements of the field + * @return mixed Field value ID to display + */ + public function get_profile_value_raw($field_value, $field_data); + + /** + * Get Profile Value for display + * + * When displaying a contact field, we don't want to have links already parsed and more + * + * @param mixed $field_value Field value as stored in the database + * @param array $field_data Array with requirements of the field + * @return mixed Field value to display + */ + public function get_profile_contact_value($field_value, $field_data); + + /** + * Generate the input field for display + * + * @param array $profile_row Array with data for this field + * @param mixed $preview_options When previewing we use different data + * @return null + */ + public function generate_field($profile_row, $preview_options = false); + + /** + * Get the ident of the field + * + * Some types are multivalue, we can't give them a field_id + * as we would not know which to pick. + * + * @param array $field_data Array with data for this field + * @return string ident of the field + */ + public function get_field_ident($field_data); + + /** + * Get the localized name of the field + * + * @param string $field_name Unlocalized name of this field + * @return string Localized name of the field + */ + public function get_field_name($field_name); + + /** + * Get the column type for the database + * + * @return string Returns the database column type + */ + public function get_database_column_type(); + + /** + * Get the options we need to display for the language input fields in the ACP + * + * @param array $field_data Array with data for this field + * @return array Returns the language options we need to generate + */ + public function get_language_options($field_data); + + /** + * Get the input for the supplied language options + * + * @param array $field_data Array with data for this field + * @return array Returns the language options we need to generate + */ + public function get_language_options_input($field_data); + + /** + * Allows exclusion of options in single steps of the creation process + * + * @param array $exclude_options Array with options that should be excluded in the steps + * @param array $visibility_options Array with options responsible for the fields visibility + * @return mixed Returns the provided language options + */ + public function prepare_options_form(&$exclude_options, &$visibility_options); + + /** + * Allows exclusion of options in single steps of the creation process + * + * @param array $error Array with error messages + * @param array $field_data Array with data for this field + * @return array Array with error messages + */ + public function validate_options_on_submit($error, $field_data); + + /** + * Allows manipulating the intended variables if needed + * + * @param string $key Name of the option + * @param string $action Currently performed action (create|edit) + * @param mixed $current_value Currently value of the option + * @param array $field_data Array with data for this field + * @param int $step Step on which the option is excluded + * @return mixed Final value of the option + */ + public function get_excluded_options($key, $action, $current_value, &$field_data, $step); + + /** + * Allows manipulating the intended variables if needed + * + * @param int $step Step on which the option is hidden + * @param string $key Name of the option + * @param string $action Currently performed action (create|edit) + * @param array $field_data Array with data for this field + * @return mixed Final value of the option + */ + public function prepare_hidden_fields($step, $key, $action, &$field_data); + + /** + * Allows assigning of additional template variables + * + * @param array $template_vars Template variables we are going to assign + * @param array $field_data Array with data for this field + * @return null + */ + public function display_options(&$template_vars, &$field_data); + + /** + * Return templated value/field. Possible values for $mode are: + * change == user is able to set/enter profile values; preview == just show the value + * + * @param string $mode Mode for displaying the field (preview|change) + * @param array $profile_row Array with data for this field + * @return null + */ + public function process_field_row($mode, $profile_row); +} diff --git a/phpbb/profilefields/type/type_string.php b/phpbb/profilefields/type/type_string.php new file mode 100644 index 0000000..8710c8c --- /dev/null +++ b/phpbb/profilefields/type/type_string.php @@ -0,0 +1,159 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +class type_string extends type_string_common +{ + /** + * Request object + * @var \phpbb\request\request + */ + protected $request; + + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Construct + * + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) + { + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function get_name_short() + { + return 'string'; + } + + /** + * {@inheritDoc} + */ + public function get_options($default_lang_id, $field_data) + { + $options = array( + 0 => array('TITLE' => $this->user->lang['FIELD_LENGTH'], 'FIELD' => ''), + 1 => array('TITLE' => $this->user->lang['MIN_FIELD_CHARS'], 'FIELD' => ''), + 2 => array('TITLE' => $this->user->lang['MAX_FIELD_CHARS'], 'FIELD' => ''), + 3 => array('TITLE' => $this->user->lang['FIELD_VALIDATION'], 'FIELD' => ''), + ); + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_default_option_values() + { + return array( + 'field_length' => 10, + 'field_minlen' => 0, + 'field_maxlen' => 20, + 'field_validation' => '.*', + 'field_novalue' => '', + 'field_default_value' => '', + ); + } + + /** + * {@inheritDoc} + */ + public function get_profile_field($profile_row) + { + $var_name = 'pf_' . $profile_row['field_ident']; + return $this->request->variable($var_name, (string) $profile_row['field_default_value'], true); + } + + /** + * {@inheritDoc} + */ + public function validate_profile_field(&$field_value, $field_data) + { + return $this->validate_string_profile_field('string', $field_value, $field_data); + } + + /** + * {@inheritDoc} + */ + public function generate_field($profile_row, $preview_options = false) + { + $profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; + $field_ident = $profile_row['field_ident']; + $default_value = $profile_row['lang_default_value']; + $profile_row['field_value'] = ($this->request->is_set($field_ident)) ? $this->request->variable($field_ident, $default_value, true) : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); + + $this->template->assign_block_vars($this->get_name_short(), array_change_key_case($profile_row, CASE_UPPER)); + } + + /** + * {@inheritDoc} + */ + public function get_database_column_type() + { + return 'VCHAR'; + } + + /** + * {@inheritDoc} + */ + public function get_language_options($field_data) + { + $options = array( + 'lang_name' => 'string', + ); + + if ($field_data['lang_explain']) + { + $options['lang_explain'] = 'text'; + } + + if (strlen($field_data['lang_default_value'])) + { + $options['lang_default_value'] = 'string'; + } + + return $options; + } + + /** + * {@inheritDoc} + */ + public function display_options(&$template_vars, &$field_data) + { + $template_vars = array_merge($template_vars, array( + 'S_STRING' => true, + 'L_DEFAULT_VALUE_EXPLAIN' => $this->user->lang['STRING_DEFAULT_VALUE_EXPLAIN'], + 'LANG_DEFAULT_VALUE' => $field_data['lang_default_value'], + )); + } +} diff --git a/phpbb/profilefields/type/type_string_common.php b/phpbb/profilefields/type/type_string_common.php new file mode 100644 index 0000000..f5e1992 --- /dev/null +++ b/phpbb/profilefields/type/type_string_common.php @@ -0,0 +1,147 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +abstract class type_string_common extends type_base +{ + protected $validation_options = array( + 'CHARS_ANY' => '.*', + 'NUMBERS_ONLY' => '[0-9]+', + 'ALPHA_ONLY' => '[a-zA-Z0-9]+', + 'ALPHA_UNDERSCORE' => '[\w]+', + 'ALPHA_DOTS' => '[a-zA-Z0-9.]+', + 'ALPHA_SPACERS' => '[\w\x20+\-\[\]]+', + 'ALPHA_PUNCTUATION' => '[a-zA-Z][\w\.,\-]+', + 'LETTER_NUM_ONLY' => '[\p{Lu}\p{Ll}0-9]+', + 'LETTER_NUM_UNDERSCORE' => '[\p{Lu}\p{Ll}0-9_]+', + 'LETTER_NUM_DOTS' => '[\p{Lu}\p{Ll}0-9.]+', + 'LETTER_NUM_SPACERS' => '[\p{Lu}\p{Ll}0-9\x20_+\-\[\]]+', + 'LETTER_NUM_PUNCTUATION' => '[\p{Lu}\p{Ll}][\p{Lu}\p{Ll}0-9.,\-_]+', + ); + + /** + * Return possible validation options + */ + public function validate_options($field_data) + { + $validate_options = ''; + foreach ($this->validation_options as $lang => $value) + { + $selected = ($field_data['field_validation'] == $value) ? ' selected="selected"' : ''; + $validate_options .= ''; + } + + return $validate_options; + } + + /** + * {@inheritDoc} + */ + public function get_default_field_value($field_data) + { + return $field_data['lang_default_value']; + } + + /** + * Validate entered profile field data + * + * @param string $field_type Field type (string or text) + * @param mixed $field_value Field value to validate + * @param array $field_data Array with requirements of the field + * @return mixed String with key of the error language string, false otherwise + */ + public function validate_string_profile_field($field_type, &$field_value, $field_data) + { + if (trim($field_value) === '' && !$field_data['field_required']) + { + return false; + } + else if (trim($field_value) === '' && $field_data['field_required']) + { + return $this->user->lang('FIELD_REQUIRED', $this->get_field_name($field_data['lang_name'])); + } + + if ($field_data['field_minlen'] && utf8_strlen($field_value) < $field_data['field_minlen']) + { + return $this->user->lang('FIELD_TOO_SHORT', (int) $field_data['field_minlen'], $this->get_field_name($field_data['lang_name'])); + } + else if ($field_data['field_maxlen'] && utf8_strlen(html_entity_decode($field_value)) > $field_data['field_maxlen']) + { + return $this->user->lang('FIELD_TOO_LONG', (int) $field_data['field_maxlen'], $this->get_field_name($field_data['lang_name'])); + } + + if (!empty($field_data['field_validation']) && $field_data['field_validation'] != '.*') + { + $field_validate = ($field_type != 'text') ? $field_value : bbcode_nl2br($field_value); + if (!preg_match('#^' . str_replace('\\\\', '\\', $field_data['field_validation']) . '$#iu', $field_validate)) + { + $validation = array_search($field_data['field_validation'], $this->validation_options); + if ($validation) + { + return $this->user->lang('FIELD_INVALID_CHARS_' . $validation, $this->get_field_name($field_data['lang_name'])); + } + return $this->user->lang('FIELD_INVALID_CHARS_INVALID', $this->get_field_name($field_data['lang_name'])); + } + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value($field_value, $field_data) + { + if (($field_value === null || $field_value === '') && !$field_data['field_show_novalue']) + { + return null; + } + + $field_value = make_clickable($field_value); + $field_value = censor_text($field_value); + $field_value = bbcode_nl2br($field_value); + return $field_value; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value_raw($field_value, $field_data) + { + if (($field_value === null || $field_value === '') && !$field_data['field_show_novalue']) + { + return null; + } + + return $field_value; + } + + /** + * {@inheritDoc} + */ + public function get_profile_contact_value($field_value, $field_data) + { + return $this->get_profile_value_raw($field_value, $field_data); + } + + /** + * {@inheritDoc} + */ + public function prepare_options_form(&$exclude_options, &$visibility_options) + { + $exclude_options[1][] = 'lang_default_value'; + + return $this->request->variable('lang_options', '', true); + } +} diff --git a/phpbb/profilefields/type/type_text.php b/phpbb/profilefields/type/type_text.php new file mode 100644 index 0000000..79ee823 --- /dev/null +++ b/phpbb/profilefields/type/type_text.php @@ -0,0 +1,204 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +class type_text extends type_string_common +{ + /** + * Request object + * @var \phpbb\request\request + */ + protected $request; + + /** + * Template object + * @var \phpbb\template\template + */ + protected $template; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Construct + * + * @param \phpbb\request\request $request Request object + * @param \phpbb\template\template $template Template object + * @param \phpbb\user $user User object + */ + public function __construct(\phpbb\request\request $request, \phpbb\template\template $template, \phpbb\user $user) + { + $this->request = $request; + $this->template = $template; + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function get_name_short() + { + return 'text'; + } + + /** + * {@inheritDoc} + */ + public function get_options($default_lang_id, $field_data) + { + $options = array( + 0 => array('TITLE' => $this->user->lang['FIELD_LENGTH'], 'FIELD' => ' ' . $this->user->lang['ROWS'] . '
' . $this->user->lang['COLUMNS'] . ' '), + 1 => array('TITLE' => $this->user->lang['MIN_FIELD_CHARS'], 'FIELD' => ''), + 2 => array('TITLE' => $this->user->lang['MAX_FIELD_CHARS'], 'FIELD' => ''), + 3 => array('TITLE' => $this->user->lang['FIELD_VALIDATION'], 'FIELD' => ''), + ); + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_default_option_values() + { + return array( + 'field_length' => '5|80', + 'field_minlen' => 0, + 'field_maxlen' => 1000, + 'field_validation' => '.*', + 'field_novalue' => '', + 'field_default_value' => '', + ); + } + + /** + * {@inheritDoc} + */ + public function get_profile_field($profile_row) + { + $var_name = 'pf_' . $profile_row['field_ident']; + return $this->request->variable($var_name, (string) $profile_row['field_default_value'], true); + } + + /** + * {@inheritDoc} + */ + public function validate_profile_field(&$field_value, $field_data) + { + return $this->validate_string_profile_field('text', $field_value, $field_data); + } + + /** + * {@inheritDoc} + */ + public function generate_field($profile_row, $preview_options = false) + { + $field_length = explode('|', $profile_row['field_length']); + $profile_row['field_rows'] = $field_length[0]; + $profile_row['field_cols'] = $field_length[1]; + $profile_row['field_ident'] = (isset($profile_row['var_name'])) ? $profile_row['var_name'] : 'pf_' . $profile_row['field_ident']; + $field_ident = $profile_row['field_ident']; + $default_value = $profile_row['lang_default_value']; + + $profile_row['field_value'] = ($this->request->is_set($field_ident)) ? $this->request->variable($field_ident, $default_value, true) : ((!isset($this->user->profile_fields[$field_ident]) || $preview_options !== false) ? $default_value : $this->user->profile_fields[$field_ident]); + + $this->template->assign_block_vars('text', array_change_key_case($profile_row, CASE_UPPER)); + } + + /** + * {@inheritDoc} + */ + public function get_database_column_type() + { + return 'MTEXT'; + } + + /** + * {@inheritDoc} + */ + public function get_language_options($field_data) + { + $options = array( + 'lang_name' => 'string', + ); + + if ($field_data['lang_explain']) + { + $options['lang_explain'] = 'text'; + } + + if (strlen($field_data['lang_default_value'])) + { + $options['lang_default_value'] = 'text'; + } + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_excluded_options($key, $action, $current_value, &$field_data, $step) + { + if ($step == 2 && $key == 'field_length') + { + if ($this->request->is_set('rows')) + { + $field_data['rows'] = $this->request->variable('rows', 0); + $field_data['columns'] = $this->request->variable('columns', 0); + $current_value = $field_data['rows'] . '|' . $field_data['columns']; + } + else + { + $row_col = explode('|', $current_value); + $field_data['rows'] = $row_col[0]; + $field_data['columns'] = $row_col[1]; + } + + return $current_value; + } + + return parent::get_excluded_options($key, $action, $current_value, $field_data, $step); + } + + /** + * {@inheritDoc} + */ + public function prepare_hidden_fields($step, $key, $action, &$field_data) + { + if ($key == 'field_length' && $this->request->is_set('rows')) + { + $field_data['rows'] = $this->request->variable('rows', 0); + $field_data['columns'] = $this->request->variable('columns', 0); + return $field_data['rows'] . '|' . $field_data['columns']; + } + + return parent::prepare_hidden_fields($step, $key, $action, $field_data); + } + + /** + * {@inheritDoc} + */ + public function display_options(&$template_vars, &$field_data) + { + $template_vars = array_merge($template_vars, array( + 'S_TEXT' => true, + 'L_DEFAULT_VALUE_EXPLAIN' => $this->user->lang['TEXT_DEFAULT_VALUE_EXPLAIN'], + 'LANG_DEFAULT_VALUE' => $field_data['lang_default_value'], + )); + } +} diff --git a/phpbb/profilefields/type/type_url.php b/phpbb/profilefields/type/type_url.php new file mode 100644 index 0000000..37815b6 --- /dev/null +++ b/phpbb/profilefields/type/type_url.php @@ -0,0 +1,87 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\profilefields\type; + +class type_url extends type_string +{ + /** + * {@inheritDoc} + */ + public function get_name_short() + { + return 'url'; + } + + /** + * {@inheritDoc} + */ + public function get_options($default_lang_id, $field_data) + { + $options = array( + 0 => array('TITLE' => $this->user->lang['FIELD_LENGTH'], 'FIELD' => ''), + 1 => array('TITLE' => $this->user->lang['MIN_FIELD_CHARS'], 'FIELD' => ''), + 2 => array('TITLE' => $this->user->lang['MAX_FIELD_CHARS'], 'FIELD' => ''), + ); + + return $options; + } + + /** + * {@inheritDoc} + */ + public function get_default_option_values() + { + return array( + 'field_length' => 40, + 'field_minlen' => 0, + 'field_maxlen' => 200, + 'field_validation' => '', + 'field_novalue' => '', + 'field_default_value' => '', + ); + } + + /** + * {@inheritDoc} + */ + public function validate_profile_field(&$field_value, $field_data) + { + $field_value = trim($field_value); + + if ($field_value === '' && !$field_data['field_required']) + { + return false; + } + + if (!preg_match('#^' . get_preg_expression('url_http') . '$#iu', $field_value)) + { + return $this->user->lang('FIELD_INVALID_URL', $this->get_field_name($field_data['lang_name'])); + } + + return false; + } + + /** + * {@inheritDoc} + */ + public function get_profile_value($field_value, $field_data) + { + if (!preg_match('#^' . get_preg_expression('url_http') . '$#iu', $field_value)) + { + return null; + } + + return parent::get_profile_value($field_value, $field_data); + } +} diff --git a/phpbb/recursive_dot_prefix_filter_iterator.php b/phpbb/recursive_dot_prefix_filter_iterator.php new file mode 100644 index 0000000..1446551 --- /dev/null +++ b/phpbb/recursive_dot_prefix_filter_iterator.php @@ -0,0 +1,30 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** +* Class recursive_dot_prefix_filter_iterator +* +* This filter ignores directories starting with a dot. +* When searching for php classes and template files of extensions +* we don't need to look inside these directories. +*/ +class recursive_dot_prefix_filter_iterator extends \RecursiveFilterIterator +{ + public function accept() + { + $filename = $this->current()->getFilename(); + return $filename[0] !== '.' || !$this->current()->isDir(); + } +} diff --git a/phpbb/report/controller/report.php b/phpbb/report/controller/report.php new file mode 100644 index 0000000..0aa6833 --- /dev/null +++ b/phpbb/report/controller/report.php @@ -0,0 +1,319 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report\controller; + +use phpbb\exception\http_exception; +use Symfony\Component\HttpFoundation\RedirectResponse; + +class report +{ + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var \phpbb\user + */ + protected $user; + + /** + * @var \phpbb\template\template + */ + protected $template; + + /** + * @var \phpbb\controller\helper + */ + protected $helper; + + /** + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * @var \phpbb\captcha\factory + */ + protected $captcha_factory; + + /** + * @var string + */ + protected $phpbb_root_path; + + /** + * @var string + */ + protected $php_ext; + + /** + * @var \phpbb\report\report_handler_interface + */ + protected $report_handler; + + /** + * @var \phpbb\report\report_reason_list_provider + */ + protected $report_reason_provider; + + public function __construct(\phpbb\config\config $config, \phpbb\user $user, \phpbb\template\template $template, \phpbb\controller\helper $helper, \phpbb\request\request_interface $request, \phpbb\captcha\factory $captcha_factory, \phpbb\report\handler_factory $report_factory, \phpbb\report\report_reason_list_provider $ui_provider, $phpbb_root_path, $php_ext) + { + $this->config = $config; + $this->user = $user; + $this->template = $template; + $this->helper = $helper; + $this->request = $request; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + $this->captcha_factory = $captcha_factory; + $this->report_handler = $report_factory; + + // User interface factory + $this->report_reason_provider = $ui_provider; + } + + /** + * Controller for /path_to_entities/{id}/report routes + * + * Because of how phpBB organizes routes $mode must be set in the route config. + * + * @param int $id ID of the entity to report + * @param string $mode + * @return \Symfony\Component\HttpFoundation\Response a Symfony response object + * @throws \phpbb\exception\http_exception when $mode or $id is invalid for some reason + */ + public function handle($id, $mode) + { + // Get report handler + $this->report_handler = $this->report_handler->get_instance($mode); + + $this->user->add_lang('mcp'); + + $user_notify = ($this->user->data['is_registered']) ? $this->request->variable('notify', 0) : false; + $reason_id = $this->request->variable('reason_id', 0); + $report_text = $this->request->variable('report_text', '', true); + + $submit = $this->request->variable('submit', ''); + $cancel = $this->request->variable('cancel', ''); + + $error = array(); + $s_hidden_fields = ''; + + $redirect_url = append_sid( + $this->phpbb_root_path . ( ($mode === 'pm') ? 'ucp' : 'viewtopic' ) . ".{$this->php_ext}", + ($mode == 'pm') ? "i=pm&mode=view&p=$id" : "p=$id" + ); + $redirect_url .= ($mode === 'post') ? "#p$id" : ''; + + // Set up CAPTCHA if necessary + if ($this->config['enable_post_confirm'] && !$this->user->data['is_registered']) + { + $captcha = $this->captcha_factory->get_instance($this->config['captcha_plugin']); + $captcha->init(CONFIRM_REPORT); + } + + //Has the report been cancelled? + if (!empty($cancel)) + { + return new RedirectResponse($redirect_url, 302); + } + + // Check CAPTCHA, if the form was submited + if (!empty($submit) && isset($captcha)) + { + $captcha_template_array = $this->check_captcha($captcha); + $error = $captcha_template_array['error']; + $s_hidden_fields = $captcha_template_array['hidden_fields']; + } + + // Handle request + try + { + if (!empty($submit) && count($error) === 0) + { + $this->report_handler->add_report( + (int) $id, + (int) $reason_id, + (string) $report_text, + (int) $user_notify + ); + + // Send success message + switch ($mode) + { + case 'pm': + $lang_return = $this->user->lang['RETURN_PM']; + $lang_success = $this->user->lang['PM_REPORTED_SUCCESS']; + break; + case 'post': + $lang_return = $this->user->lang['RETURN_TOPIC']; + $lang_success = $this->user->lang['POST_REPORTED_SUCCESS']; + break; + } + + $this->helper->assign_meta_refresh_var(3, $redirect_url); + $message = $lang_success . '

' . sprintf($lang_return, '', ''); + return $this->helper->message($message); + } + else + { + $this->report_handler->validate_report_request($id); + } + } + catch (\phpbb\report\exception\pm_reporting_disabled_exception $exception) + { + throw new http_exception(404, 'PAGE_NOT_FOUND'); + } + catch (\phpbb\report\exception\already_reported_exception $exception) + { + switch ($mode) + { + case 'pm': + $message = $this->user->lang['ALREADY_REPORTED_PM']; + $message .= '

' . sprintf($this->user->lang['RETURN_PM'], '', ''); + break; + case 'post': + $message = $this->user->lang['ALREADY_REPORTED']; + $message .= '

' . sprintf($this->user->lang['RETURN_TOPIC'], '', ''); + break; + } + + return $this->helper->message($message); + } + catch (\phpbb\report\exception\report_permission_denied_exception $exception) + { + $message = $exception->getMessage(); + if (isset($this->user->lang[$message])) + { + $message = $this->user->lang[$message]; + } + + throw new http_exception(403, $message); + } + catch (\phpbb\report\exception\entity_not_found_exception $exception) + { + $message = $exception->getMessage(); + if (isset($this->user->lang[$message])) + { + $message = $this->user->lang[$message]; + } + + throw new http_exception(404, $message); + } + catch (\phpbb\report\exception\empty_report_exception $exception) + { + $error[] = $this->user->lang['EMPTY_REPORT']; + } + catch (\phpbb\report\exception\invalid_report_exception $exception) + { + return $this->helper->message($exception->getMessage()); + } + + // Setting up an rendering template + $page_title = ($mode === 'pm') ? $this->user->lang['REPORT_MESSAGE'] : $this->user->lang['REPORT_POST']; + $this->assign_template_data( + $mode, + $id, + $reason_id, + $report_text, + $user_notify, + $error, + $s_hidden_fields, + ( isset($captcha) ? $captcha : false ) + ); + + return $this->helper->render('report_body.html', $page_title); + } + + /** + * Assigns template variables + * + * @param int $mode + * @param int $id + * @param int $reason_id + * @param string $report_text + * @param mixed $user_notify + * @param array $error + * @param string $s_hidden_fields + * @param mixed $captcha + * @return null + */ + protected function assign_template_data($mode, $id, $reason_id, $report_text, $user_notify, $error = array(), $s_hidden_fields = '', $captcha = false) + { + if ($captcha !== false && $captcha->is_solved() === false) + { + $this->template->assign_vars(array( + 'S_CONFIRM_CODE' => true, + 'CAPTCHA_TEMPLATE' => $captcha->get_template(), + )); + } + + $this->report_reason_provider->display_reasons($reason_id); + + switch ($mode) + { + case 'pm': + $report_route = $this->helper->route('phpbb_report_pm_controller', array('id' => $id)); + break; + case 'post': + $report_route = $this->helper->route('phpbb_report_post_controller', array('id' => $id)); + break; + } + + $this->template->assign_vars(array( + 'ERROR' => (count($error) > 0) ? implode('
', $error) : '', + 'S_REPORT_POST' => ($mode === 'pm') ? false : true, + 'REPORT_TEXT' => $report_text, + 'S_HIDDEN_FIELDS' => (!empty($s_hidden_fields)) ? $s_hidden_fields : null, + 'S_REPORT_ACTION' => $report_route, + + 'S_NOTIFY' => $user_notify, + 'S_CAN_NOTIFY' => ($this->user->data['is_registered']) ? true : false, + 'S_IN_REPORT' => true, + )); + } + + /** + * Check CAPTCHA + * + * @param object $captcha A phpBB CAPTCHA object + * @return array template variables which ensures that CAPTCHA's work correctly + */ + protected function check_captcha($captcha) + { + $error = array(); + $captcha_hidden_fields = ''; + + $visual_confirmation_response = $captcha->validate(); + if ($visual_confirmation_response) + { + $error[] = $visual_confirmation_response; + } + + if (count($error) === 0) + { + $captcha->reset(); + } + else if ($captcha->is_solved() !== false) + { + $captcha_hidden_fields = build_hidden_fields($captcha->get_hidden_fields()); + } + + return array( + 'error' => $error, + 'hidden_fields' => $captcha_hidden_fields, + ); + } +} diff --git a/phpbb/report/exception/already_reported_exception.php b/phpbb/report/exception/already_reported_exception.php new file mode 100644 index 0000000..5417404 --- /dev/null +++ b/phpbb/report/exception/already_reported_exception.php @@ -0,0 +1,19 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report\exception; + +class already_reported_exception extends invalid_report_exception +{ + +} diff --git a/phpbb/report/exception/empty_report_exception.php b/phpbb/report/exception/empty_report_exception.php new file mode 100644 index 0000000..8c968dc --- /dev/null +++ b/phpbb/report/exception/empty_report_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report\exception; + +class empty_report_exception extends invalid_report_exception +{ + public function __construct() + { + parent::__construct('EMPTY_REPORT'); + } +} diff --git a/phpbb/report/exception/entity_not_found_exception.php b/phpbb/report/exception/entity_not_found_exception.php new file mode 100644 index 0000000..732aa58 --- /dev/null +++ b/phpbb/report/exception/entity_not_found_exception.php @@ -0,0 +1,19 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report\exception; + +class entity_not_found_exception extends invalid_report_exception +{ + +} diff --git a/phpbb/report/exception/factory_invalid_argument_exception.php b/phpbb/report/exception/factory_invalid_argument_exception.php new file mode 100644 index 0000000..19de91e --- /dev/null +++ b/phpbb/report/exception/factory_invalid_argument_exception.php @@ -0,0 +1,21 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report\exception; + +use \phpbb\exception\runtime_exception; + +class factory_invalid_argument_exception extends runtime_exception +{ + +} diff --git a/phpbb/report/exception/invalid_report_exception.php b/phpbb/report/exception/invalid_report_exception.php new file mode 100644 index 0000000..03ff0a8 --- /dev/null +++ b/phpbb/report/exception/invalid_report_exception.php @@ -0,0 +1,21 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report\exception; + +use \phpbb\exception\runtime_exception; + +class invalid_report_exception extends runtime_exception +{ + +} diff --git a/phpbb/report/exception/pm_reporting_disabled_exception.php b/phpbb/report/exception/pm_reporting_disabled_exception.php new file mode 100644 index 0000000..2c8ab8c --- /dev/null +++ b/phpbb/report/exception/pm_reporting_disabled_exception.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report\exception; + +class pm_reporting_disabled_exception extends invalid_report_exception +{ + public function __construct() + { + + } +} diff --git a/phpbb/report/exception/report_permission_denied_exception.php b/phpbb/report/exception/report_permission_denied_exception.php new file mode 100644 index 0000000..c706928 --- /dev/null +++ b/phpbb/report/exception/report_permission_denied_exception.php @@ -0,0 +1,19 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report\exception; + +class report_permission_denied_exception extends invalid_report_exception +{ + +} diff --git a/phpbb/report/handler_factory.php b/phpbb/report/handler_factory.php new file mode 100644 index 0000000..ec229aa --- /dev/null +++ b/phpbb/report/handler_factory.php @@ -0,0 +1,56 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report; + +use phpbb\report\exception\factory_invalid_argument_exception; + +class handler_factory +{ + /** + * @var \Symfony\Component\DependencyInjection\ContainerInterface + */ + protected $container; + + /** + * Constructor + * + * @param \Symfony\Component\DependencyInjection\ContainerInterface $container + */ + public function __construct(\Symfony\Component\DependencyInjection\ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Return a new instance of an appropriate report handler + * + * @param string $type + * @return \phpbb\report\report_handler_interface + * @throws \phpbb\report\exception\factory_invalid_argument_exception if $type is not valid + */ + public function get_instance($type) + { + switch ($type) + { + case 'pm': + return $this->container->get('phpbb.report.handlers.report_handler_pm'); + break; + case 'post': + return $this->container->get('phpbb.report.handlers.report_handler_post'); + break; + } + + throw new factory_invalid_argument_exception(); + } +} diff --git a/phpbb/report/report_handler.php b/phpbb/report/report_handler.php new file mode 100644 index 0000000..854318c --- /dev/null +++ b/phpbb/report/report_handler.php @@ -0,0 +1,104 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report; + +abstract class report_handler implements report_handler_interface +{ + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var \phpbb\auth\auth + */ + protected $auth; + + /** + * @var \phpbb\user + */ + protected $user; + + /** + * @var \phpbb\notification\manager + */ + protected $notifications; + + /** + * @var array + */ + protected $report_data; + + /** + * Construtor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\event\dispatcher_interface $dispatcher + * @param \phpbb\config\db $config + * @param \phpbb\auth\auth $auth + * @param \phpbb\user $user + * @param \phpbb\notification\manager $notification + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\config\config $config, \phpbb\auth\auth $auth, \phpbb\user $user, \phpbb\notification\manager $notification) + { + $this->db = $db; + $this->dispatcher = $dispatcher; + $this->config = $config; + $this->auth = $auth; + $this->user = $user; + $this->notifications = $notification; + $this->report_data = array(); + } + + /** + * Creates a report entity in the database + * + * @param array $report_data + * @return int the ID of the created entity + */ + protected function create_report(array $report_data) + { + $sql_ary = array( + 'reason_id' => (int) $report_data['reason_id'], + 'post_id' => $report_data['post_id'], + 'pm_id' => $report_data['pm_id'], + 'user_id' => (int) $this->user->data['user_id'], + 'user_notify' => (int) $report_data['user_notify'], + 'report_closed' => 0, + 'report_time' => (int) time(), + 'report_text' => (string) $report_data['report_text'], + 'reported_post_text' => $report_data['reported_post_text'], + 'reported_post_uid' => $report_data['reported_post_uid'], + 'reported_post_bitfield' => $report_data['reported_post_bitfield'], + 'reported_post_enable_bbcode' => $report_data['reported_post_enable_bbcode'], + 'reported_post_enable_smilies' => $report_data['reported_post_enable_smilies'], + 'reported_post_enable_magic_url' => $report_data['reported_post_enable_magic_url'], + ); + + $sql = 'INSERT INTO ' . REPORTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + + return $this->db->sql_nextid(); + } +} diff --git a/phpbb/report/report_handler_interface.php b/phpbb/report/report_handler_interface.php new file mode 100644 index 0000000..8dafc39 --- /dev/null +++ b/phpbb/report/report_handler_interface.php @@ -0,0 +1,43 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report; + +interface report_handler_interface +{ + /** + * Reports a message + * + * @param int $id + * @param int $reason_id + * @param string $report_text + * @param int $user_notify + * @return null + * @throws \phpbb\report\exception\empty_report_exception when the given report is empty + * @throws \phpbb\report\exception\already_reported_exception when the entity is already reported + * @throws \phpbb\report\exception\entity_not_found_exception when the entity does not exist or the user does not have viewing permissions for it + * @throws \phpbb\report\exception\invalid_report_exception when the entity cannot be reported for some other reason + */ + public function add_report($id, $reason_id, $report_text, $user_notify); + + /** + * Checks if the message is reportable + * + * @param int $id + * @return null + * @throws \phpbb\report\exception\already_reported_exception when the entity is already reported + * @throws \phpbb\report\exception\entity_not_found_exception when the entity does not exist or the user does not have viewing permissions for it + * @throws \phpbb\report\exception\invalid_report_exception when the entity cannot be reported for some other reason + */ + public function validate_report_request($id); +} diff --git a/phpbb/report/report_handler_pm.php b/phpbb/report/report_handler_pm.php new file mode 100644 index 0000000..774ca32 --- /dev/null +++ b/phpbb/report/report_handler_pm.php @@ -0,0 +1,137 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report; + +use phpbb\report\exception\empty_report_exception; +use phpbb\report\exception\already_reported_exception; +use phpbb\report\exception\pm_reporting_disabled_exception; +use phpbb\report\exception\entity_not_found_exception; + +class report_handler_pm extends report_handler +{ + /** + * {@inheritdoc} + * @throws \phpbb\report\exception\pm_reporting_disabled_exception when PM reporting is disabled on the board + */ + public function add_report($id, $reason_id, $report_text, $user_notify) + { + // Cast the input variables + $id = (int) $id; + $reason_id = (int) $reason_id; + $report_text = (string) $report_text; + $user_notify = (int) $user_notify; + + $this->validate_report_request($id); + + $sql = 'SELECT * + FROM ' . REPORTS_REASONS_TABLE . " + WHERE reason_id = $reason_id"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row || (empty($report_text) && strtolower($row['reason_title']) === 'other')) + { + throw new empty_report_exception(); + } + + $report_data = array( + 'reason_id' => $reason_id, + 'post_id' => 0, + 'pm_id' => $id, + 'user_notify' => $user_notify, + 'report_text' => $report_text, + 'reported_post_text' => $this->report_data['message_text'], + 'reported_post_uid' => $this->report_data['bbcode_uid'], + 'reported_post_bitfield' => $this->report_data['bbcode_bitfield'], + 'reported_post_enable_bbcode' => $this->report_data['enable_bbcode'], + 'reported_post_enable_smilies' => $this->report_data['enable_smilies'], + 'reported_post_enable_magic_url' => $this->report_data['enable_magic_url'], + ); + + $report_id = $this->create_report($report_data); + + $sql = 'UPDATE ' . PRIVMSGS_TABLE . ' + SET message_reported = 1 + WHERE msg_id = ' . $id; + $this->db->sql_query($sql); + + $sql_ary = array( + 'msg_id' => $id, + 'user_id' => ANONYMOUS, + 'author_id' => (int) $this->report_data['author_id'], + 'pm_deleted' => 0, + 'pm_new' => 0, + 'pm_unread' => 0, + 'pm_replied' => 0, + 'pm_marked' => 0, + 'pm_forwarded' => 0, + 'folder_id' => PRIVMSGS_INBOX, + ); + + $sql = 'INSERT INTO ' . PRIVMSGS_TO_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary); + $this->db->sql_query($sql); + + $this->notifications->add_notifications('notification.type.report_pm', array_merge($this->report_data, $row, array( + 'report_text' => $report_text, + 'from_user_id' => $this->report_data['author_id'], + 'report_id' => $report_id, + ))); + } + + /** + * {@inheritdoc} + * @throws \phpbb\report\exception\pm_reporting_disabled_exception when PM reporting is disabled on the board + */ + public function validate_report_request($id) + { + $id = (int) $id; + + // Check if reporting PMs is enabled + if (!$this->config['allow_pm_report']) + { + throw new pm_reporting_disabled_exception(); + } + else if ($id <= 0) + { + throw new entity_not_found_exception('NO_POST_SELECTED'); + } + + // Grab all relevant data + $sql = 'SELECT p.*, pt.* + FROM ' . PRIVMSGS_TABLE . ' p, ' . PRIVMSGS_TO_TABLE . " pt + WHERE p.msg_id = $id + AND p.msg_id = pt.msg_id + AND (p.author_id = " . $this->user->data['user_id'] . " + OR pt.user_id = " . $this->user->data['user_id'] . ")"; + $result = $this->db->sql_query($sql); + $report_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + // Check if message exists + if (!$report_data) + { + $this->user->add_lang('ucp'); + throw new entity_not_found_exception('NO_MESSAGE'); + } + + // Check if message is already reported + if ($report_data['message_reported']) + { + throw new already_reported_exception(); + } + + $this->report_data = $report_data; + } +} diff --git a/phpbb/report/report_handler_post.php b/phpbb/report/report_handler_post.php new file mode 100644 index 0000000..52f0968 --- /dev/null +++ b/phpbb/report/report_handler_post.php @@ -0,0 +1,175 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report; + +use phpbb\report\exception\invalid_report_exception; +use phpbb\report\exception\empty_report_exception; +use phpbb\report\exception\already_reported_exception; +use phpbb\report\exception\entity_not_found_exception; +use phpbb\report\exception\report_permission_denied_exception; + +class report_handler_post extends report_handler +{ + /** + * @var array + */ + protected $forum_data; + + /** + * {@inheritdoc} + * @throws \phpbb\report\exception\report_permission_denied_exception when the user does not have permission to report the post + */ + public function add_report($id, $reason_id, $report_text, $user_notify) + { + // Cast the input variables + $id = (int) $id; + $reason_id = (int) $reason_id; + $report_text = (string) $report_text; + $user_notify = (int) $user_notify; + + $this->validate_report_request($id); + + $sql = 'SELECT * + FROM ' . REPORTS_REASONS_TABLE . " + WHERE reason_id = $reason_id"; + $result = $this->db->sql_query($sql); + $row = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$row || (empty($report_text) && strtolower($row['reason_title']) === 'other')) + { + throw new empty_report_exception(); + } + + $report_data = array( + 'reason_id' => $reason_id, + 'post_id' => $id, + 'pm_id' => 0, + 'user_notify' => $user_notify, + 'report_text' => $report_text, + 'reported_post_text' => $this->report_data['post_text'], + 'reported_post_uid' => $this->report_data['bbcode_uid'], + 'reported_post_bitfield' => $this->report_data['bbcode_bitfield'], + 'reported_post_enable_bbcode' => $this->report_data['enable_bbcode'], + 'reported_post_enable_smilies' => $this->report_data['enable_smilies'], + 'reported_post_enable_magic_url' => $this->report_data['enable_magic_url'], + ); + + $this->create_report($report_data); + + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_reported = 1 + WHERE post_id = ' . $id; + $this->db->sql_query($sql); + + if (!$this->report_data['topic_reported']) + { + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_reported = 1 + WHERE topic_id = ' . $this->report_data['topic_id'] . ' + OR topic_moved_id = ' . $this->report_data['topic_id']; + $this->db->sql_query($sql); + } + + $this->notifications->add_notifications('notification.type.report_post', array_merge($this->report_data, $row, $this->forum_data, array( + 'report_text' => $report_text, + ))); + } + + /** + * {@inheritdoc} + * @throws \phpbb\report\exception\report_permission_denied_exception when the user does not have permission to report the post + */ + public function validate_report_request($id) + { + $id = (int) $id; + + // Check if id is valid + if ($id <= 0) + { + throw new entity_not_found_exception('NO_POST_SELECTED'); + } + + // Grab all relevant data + $sql = 'SELECT t.*, p.* + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . " t + WHERE p.post_id = $id + AND p.topic_id = t.topic_id"; + $result = $this->db->sql_query($sql); + $report_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$report_data) + { + throw new entity_not_found_exception('POST_NOT_EXIST'); + } + + $forum_id = (int) $report_data['forum_id']; + + $sql = 'SELECT * + FROM ' . FORUMS_TABLE . ' + WHERE forum_id = ' . $forum_id; + $result = $this->db->sql_query($sql); + $forum_data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + if (!$forum_data) + { + throw new invalid_report_exception('FORUM_NOT_EXIST'); + } + + $acl_check_ary = array( + 'f_list' => 'POST_NOT_EXIST', + 'f_read' => 'USER_CANNOT_READ', + 'f_report' => 'USER_CANNOT_REPORT' + ); + + /** + * This event allows you to do extra auth checks and verify if the user + * has the required permissions + * + * @event core.report_post_auth + * @var array forum_data All data available from the forums table on this post's forum + * @var array report_data All data available from the topics and the posts tables on this post (and its topic) + * @var array acl_check_ary An array with the ACL to be tested. The evaluation is made in the same order as the array is sorted + * The key is the ACL name and the value is the language key for the error message. + * @since 3.1.3-RC1 + */ + $vars = array( + 'forum_data', + 'report_data', + 'acl_check_ary', + ); + extract($this->dispatcher->trigger_event('core.report_post_auth', compact($vars))); + + $this->auth->acl($this->user->data); + + foreach ($acl_check_ary as $acl => $error) + { + if (!$this->auth->acl_get($acl, $forum_id)) + { + throw new report_permission_denied_exception($error); + } + } + unset($acl_check_ary); + + if ($report_data['post_reported']) + { + throw new already_reported_exception(); + } + + $this->report_data = $report_data; + $this->forum_data = $forum_data; + } +} diff --git a/phpbb/report/report_reason_list_provider.php b/phpbb/report/report_reason_list_provider.php new file mode 100644 index 0000000..388a61d --- /dev/null +++ b/phpbb/report/report_reason_list_provider.php @@ -0,0 +1,78 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\report; + +class report_reason_list_provider +{ + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var \phpbb\template\template + */ + protected $template; + + /** + * @var \phpbb\user + */ + protected $user; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db + * @param \phpbb\template\template $template + * @param \phpbb\user $user + */ + public function __construct(\phpbb\db\driver\driver_interface $db, \phpbb\template\template $template, \phpbb\user $user) + { + $this->db = $db; + $this->template = $template; + $this->user = $user; + } + + /** + * Sets template variables to render report reasons select HTML input + * + * @param int $reason_id + * @return null + */ + public function display_reasons($reason_id = 0) + { + $sql = 'SELECT * + FROM ' . REPORTS_REASONS_TABLE . ' + ORDER BY reason_order ASC'; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // If the reason is defined within the language file, we will use the localized version, else just use the database entry... + if (isset($this->user->lang['report_reasons']['TITLE'][strtoupper($row['reason_title'])]) && isset($this->user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])])) + { + $row['reason_description'] = $this->user->lang['report_reasons']['DESCRIPTION'][strtoupper($row['reason_title'])]; + $row['reason_title'] = $this->user->lang['report_reasons']['TITLE'][strtoupper($row['reason_title'])]; + } + + $this->template->assign_block_vars('reason', array( + 'ID' => $row['reason_id'], + 'TITLE' => $row['reason_title'], + 'DESCRIPTION' => $row['reason_description'], + 'S_SELECTED' => ($row['reason_id'] == $reason_id) ? true : false, + )); + } + $this->db->sql_freeresult($result); + } +} diff --git a/phpbb/request/deactivated_super_global.php b/phpbb/request/deactivated_super_global.php new file mode 100644 index 0000000..ab56240 --- /dev/null +++ b/phpbb/request/deactivated_super_global.php @@ -0,0 +1,116 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\request; + +/** +* Replacement for a superglobal (like $_GET or $_POST) which calls +* trigger_error on all operations but isset, overloads the [] operator with SPL. +*/ +class deactivated_super_global implements \ArrayAccess, \Countable, \IteratorAggregate +{ + /** + * @var string Holds the name of the superglobal this is replacing. + */ + private $name; + + /** + * @var \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE Super global constant. + */ + private $super_global; + + /** + * @var \phpbb\request\request_interface The request class instance holding the actual request data. + */ + private $request; + + /** + * Constructor generates an error message fitting the super global to be used within the other functions. + * + * @param \phpbb\request\request_interface $request A request class instance holding the real super global data. + * @param string $name Name of the super global this is a replacement for - e.g. '_GET'. + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global The variable's super global constant. + */ + public function __construct(\phpbb\request\request_interface $request, $name, $super_global) + { + $this->request = $request; + $this->name = $name; + $this->super_global = $super_global; + } + + /** + * Calls trigger_error with the file and line number the super global was used in. + */ + private function error() + { + $file = ''; + $line = 0; + + $message = 'Illegal use of $' . $this->name . '. You must use the request class to access input data. Found in %s on line %d. This error message was generated by deactivated_super_global.'; + + $backtrace = debug_backtrace(); + if (isset($backtrace[1])) + { + $file = $backtrace[1]['file']; + $line = $backtrace[1]['line']; + } + trigger_error(sprintf($message, $file, $line), E_USER_ERROR); + } + + /** + * Redirects isset to the correct request class call. + * + * @param string $offset The key of the super global being accessed. + * + * @return bool Whether the key on the super global exists. + */ + public function offsetExists($offset) + { + return $this->request->is_set($offset, $this->super_global); + } + + /**#@+ + * Part of the \ArrayAccess implementation, will always result in a FATAL error. + */ + public function offsetGet($offset) + { + $this->error(); + } + + public function offsetSet($offset, $value) + { + $this->error(); + } + + public function offsetUnset($offset) + { + $this->error(); + } + /**#@-*/ + + /** + * Part of the \Countable implementation, will always result in a FATAL error + */ + public function count() + { + $this->error(); + } + + /** + * Part of the Traversable/IteratorAggregate implementation, will always result in a FATAL error + */ + public function getIterator() + { + $this->error(); + } +} diff --git a/phpbb/request/request.php b/phpbb/request/request.php new file mode 100644 index 0000000..a0267d1 --- /dev/null +++ b/phpbb/request/request.php @@ -0,0 +1,454 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\request; + +/** +* All application input is accessed through this class. +* +* It provides a method to disable access to input data through super globals. +* This should force MOD authors to read about data validation. +*/ +class request implements \phpbb\request\request_interface +{ + /** + * @var array The names of super global variables that this class should protect if super globals are disabled. + */ + protected $super_globals = array( + \phpbb\request\request_interface::POST => '_POST', + \phpbb\request\request_interface::GET => '_GET', + \phpbb\request\request_interface::REQUEST => '_REQUEST', + \phpbb\request\request_interface::COOKIE => '_COOKIE', + \phpbb\request\request_interface::SERVER => '_SERVER', + \phpbb\request\request_interface::FILES => '_FILES', + ); + + /** + * @var array Stores original contents of $_REQUEST array. + */ + protected $original_request = null; + + /** + * @var + */ + protected $super_globals_disabled = false; + + /** + * @var array An associative array that has the value of super global constants as keys and holds their data as values. + */ + protected $input; + + /** + * @var \phpbb\request\type_cast_helper_interface An instance of a type cast helper providing convenience methods for type conversions. + */ + protected $type_cast_helper; + + /** + * Initialises the request class, that means it stores all input data in {@link $input input} + * and then calls {@link \phpbb\request\deactivated_super_global \phpbb\request\deactivated_super_global} + */ + public function __construct(\phpbb\request\type_cast_helper_interface $type_cast_helper = null, $disable_super_globals = true) + { + if ($type_cast_helper) + { + $this->type_cast_helper = $type_cast_helper; + } + else + { + $this->type_cast_helper = new \phpbb\request\type_cast_helper(); + } + + foreach ($this->super_globals as $const => $super_global) + { + $this->input[$const] = isset($GLOBALS[$super_global]) ? $GLOBALS[$super_global] : array(); + } + + // simulate request_order = GP + $this->original_request = $this->input[\phpbb\request\request_interface::REQUEST]; + $this->input[\phpbb\request\request_interface::REQUEST] = $this->input[\phpbb\request\request_interface::POST] + $this->input[\phpbb\request\request_interface::GET]; + + if ($disable_super_globals) + { + $this->disable_super_globals(); + } + } + + /** + * Getter for $super_globals_disabled + * + * @return bool Whether super globals are disabled or not. + */ + public function super_globals_disabled() + { + return $this->super_globals_disabled; + } + + /** + * Disables access of super globals specified in $super_globals. + * This is achieved by overwriting the super globals with instances of {@link \phpbb\request\deactivated_super_global \phpbb\request\deactivated_super_global} + */ + public function disable_super_globals() + { + if (!$this->super_globals_disabled) + { + foreach ($this->super_globals as $const => $super_global) + { + unset($GLOBALS[$super_global]); + $GLOBALS[$super_global] = new \phpbb\request\deactivated_super_global($this, $super_global, $const); + } + + $this->super_globals_disabled = true; + } + } + + /** + * Enables access of super globals specified in $super_globals if they were disabled by {@link disable_super_globals disable_super_globals}. + * This is achieved by making the super globals point to the data stored within this class in {@link $input input}. + */ + public function enable_super_globals() + { + if ($this->super_globals_disabled) + { + foreach ($this->super_globals as $const => $super_global) + { + $GLOBALS[$super_global] = $this->input[$const]; + } + + $GLOBALS['_REQUEST'] = $this->original_request; + + $this->super_globals_disabled = false; + } + } + + /** + * This function allows overwriting or setting a value in one of the super global arrays. + * + * Changes which are performed on the super globals directly will not have any effect on the results of + * other methods this class provides. Using this function should be avoided if possible! It will + * consume twice the the amount of memory of the value + * + * @param string $var_name The name of the variable that shall be overwritten + * @param mixed $value The value which the variable shall contain. + * If this is null the variable will be unset. + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global shall be changed + */ + public function overwrite($var_name, $value, $super_global = \phpbb\request\request_interface::REQUEST) + { + if (!isset($this->super_globals[$super_global])) + { + return; + } + + // setting to null means unsetting + if ($value === null) + { + unset($this->input[$super_global][$var_name]); + if (!$this->super_globals_disabled()) + { + unset($GLOBALS[$this->super_globals[$super_global]][$var_name]); + } + } + else + { + $this->input[$super_global][$var_name] = $value; + if (!$this->super_globals_disabled()) + { + $GLOBALS[$this->super_globals[$super_global]][$var_name] = $value; + } + } + } + + /** + * Central type safe input handling function. + * All variables in GET or POST requests should be retrieved through this function to maximise security. + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + public function variable($var_name, $default, $multibyte = false, $super_global = \phpbb\request\request_interface::REQUEST) + { + return $this->_variable($var_name, $default, $multibyte, $super_global, true); + } + + /** + * Get a variable, but without trimming strings. + * Same functionality as variable(), except does not run trim() on strings. + * This method should be used when handling passwords. + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + public function untrimmed_variable($var_name, $default, $multibyte = false, $super_global = \phpbb\request\request_interface::REQUEST) + { + return $this->_variable($var_name, $default, $multibyte, $super_global, false); + } + + /** + * {@inheritdoc} + */ + public function raw_variable($var_name, $default, $super_global = \phpbb\request\request_interface::REQUEST) + { + $path = false; + + // deep direct access to multi dimensional arrays + if (is_array($var_name)) + { + $path = $var_name; + // make sure at least the variable name is specified + if (empty($path)) + { + return (is_array($default)) ? array() : $default; + } + // the variable name is the first element on the path + $var_name = array_shift($path); + } + + if (!isset($this->input[$super_global][$var_name])) + { + return (is_array($default)) ? array() : $default; + } + $var = $this->input[$super_global][$var_name]; + + if ($path) + { + // walk through the array structure and find the element we are looking for + foreach ($path as $key) + { + if (is_array($var) && isset($var[$key])) + { + $var = $var[$key]; + } + else + { + return (is_array($default)) ? array() : $default; + } + } + } + + return $var; + } + + /** + * Shortcut method to retrieve SERVER variables. + * + * Also fall back to getenv(), some CGI setups may need it (probably not, but + * whatever). + * + * @param string|array $var_name See \phpbb\request\request_interface::variable + * @param mixed $Default See \phpbb\request\request_interface::variable + * + * @return mixed The server variable value. + */ + public function server($var_name, $default = '') + { + $multibyte = true; + + if ($this->is_set($var_name, \phpbb\request\request_interface::SERVER)) + { + return $this->variable($var_name, $default, $multibyte, \phpbb\request\request_interface::SERVER); + } + else + { + $var = getenv($var_name); + $this->type_cast_helper->recursive_set_var($var, $default, $multibyte); + return $var; + } + } + + /** + * Shortcut method to retrieve the value of client HTTP headers. + * + * @param string|array $header_name The name of the header to retrieve. + * @param mixed $default See \phpbb\request\request_interface::variable + * + * @return mixed The header value. + */ + public function header($header_name, $default = '') + { + $var_name = 'HTTP_' . str_replace('-', '_', strtoupper($header_name)); + return $this->server($var_name, $default); + } + + /** + * Shortcut method to retrieve $_FILES variables + * + * @param string $form_name The name of the file input form element + * + * @return array The uploaded file's information or an empty array if the + * variable does not exist in _FILES. + */ + public function file($form_name) + { + return $this->variable($form_name, array('name' => 'none'), true, \phpbb\request\request_interface::FILES); + } + + /** + * Checks whether a certain variable was sent via POST. + * To make sure that a request was sent using POST you should call this function + * on at least one variable. + * + * @param string $name The name of the form variable which should have a + * _p suffix to indicate the check in the code that creates the form too. + * + * @return bool True if the variable was set in a POST request, false otherwise. + */ + public function is_set_post($name) + { + return $this->is_set($name, \phpbb\request\request_interface::POST); + } + + /** + * Checks whether a certain variable is set in one of the super global + * arrays. + * + * @param string $var Name of the variable + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies the super global which shall be checked + * + * @return bool True if the variable was sent as input + */ + public function is_set($var, $super_global = \phpbb\request\request_interface::REQUEST) + { + return isset($this->input[$super_global][$var]); + } + + /** + * Checks whether the current request is an AJAX request (XMLHttpRequest) + * + * @return bool True if the current request is an ajax request + */ + public function is_ajax() + { + return $this->header('X-Requested-With') == 'XMLHttpRequest'; + } + + /** + * Checks if the current request is happening over HTTPS. + * + * @return bool True if the request is secure. + */ + public function is_secure() + { + $https = $this->server('HTTPS'); + $https = $this->server('HTTP_X_FORWARDED_PROTO') === 'https' ? 'on' : $https; + return !empty($https) && $https !== 'off'; + } + + /** + * Returns all variable names for a given super global + * + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * The super global from which names shall be taken + * + * @return array All variable names that are set for the super global. + * Pay attention when using these, they are unsanitised! + */ + public function variable_names($super_global = \phpbb\request\request_interface::REQUEST) + { + if (!isset($this->input[$super_global])) + { + return array(); + } + + return array_keys($this->input[$super_global]); + } + + /** + * Helper function used by variable() and untrimmed_variable(). + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * @param bool $trim Indicates whether trim() should be applied to string values. + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + protected function _variable($var_name, $default, $multibyte = false, $super_global = \phpbb\request\request_interface::REQUEST, $trim = true) + { + $var = $this->raw_variable($var_name, $default, $super_global); + + // Return prematurely if raw variable is empty array or the same as + // the default. Using strict comparison to ensure that one can't + // prevent proper type checking on any input variable + if ($var === array() || $var === $default) + { + return $var; + } + + $this->type_cast_helper->recursive_set_var($var, $default, $multibyte, $trim); + + return $var; + } + + /** + * {@inheritdoc} + */ + public function get_super_global($super_global = \phpbb\request\request_interface::REQUEST) + { + return $this->input[$super_global]; + } + + /** + * {@inheritdoc} + */ + public function escape($var, $multibyte) + { + if (is_array($var)) + { + $result = array(); + foreach ($var as $key => $value) + { + $this->type_cast_helper->set_var($key, $key, gettype($key), $multibyte); + $result[$key] = $this->escape($value, $multibyte); + } + $var = $result; + } + else + { + $this->type_cast_helper->set_var($var, $var, 'string', $multibyte); + } + + return $var; + } +} diff --git a/phpbb/request/request_interface.php b/phpbb/request/request_interface.php new file mode 100644 index 0000000..3bfa8bb --- /dev/null +++ b/phpbb/request/request_interface.php @@ -0,0 +1,177 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\request; + +/** +* An interface through which all application input can be accessed. +*/ +interface request_interface +{ + /**#@+ + * Constant identifying the super global with the same name. + */ + const POST = 0; + const GET = 1; + const REQUEST = 2; + const COOKIE = 3; + const SERVER = 4; + const FILES = 5; + /**#@-*/ + + /** + * This function allows overwriting or setting a value in one of the super global arrays. + * + * Changes which are performed on the super globals directly will not have any effect on the results of + * other methods this class provides. Using this function should be avoided if possible! It will + * consume twice the the amount of memory of the value + * + * @param string $var_name The name of the variable that shall be overwritten + * @param mixed $value The value which the variable shall contain. + * If this is null the variable will be unset. + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global shall be changed + */ + public function overwrite($var_name, $value, $super_global = \phpbb\request\request_interface::REQUEST); + + /** + * Central type safe input handling function. + * All variables in GET or POST requests should be retrieved through this function to maximise security. + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param bool $multibyte If $default is a string this paramater has to be true if the variable may contain any UTF-8 characters + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + public function variable($var_name, $default, $multibyte = false, $super_global = \phpbb\request\request_interface::REQUEST); + + /** + * Get a variable without trimming strings and without escaping. + * This method MUST NOT be used with queries. + * Same functionality as variable(), except does not run trim() on strings + * and does not escape input. + * This method should only be used when the raw input is needed without + * any escaping, i.e. for database password during the installation. + * + * @param string|array $var_name The form variable's name from which data shall be retrieved. + * If the value is an array this may be an array of indizes which will give + * direct access to a value at any depth. E.g. if the value of "var" is array(1 => "a") + * then specifying array("var", 1) as the name will return "a". + * @param mixed $default A default value that is returned if the variable was not set. + * This function will always return a value of the same type as the default. + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies which super global should be used + * + * @return mixed The value of $_REQUEST[$var_name] run through {@link set_var set_var} to ensure that the type is the + * the same as that of $default. If the variable is not set $default is returned. + */ + public function raw_variable($var_name, $default, $super_global = \phpbb\request\request_interface::REQUEST); + + /** + * Shortcut method to retrieve SERVER variables. + * + * @param string|array $var_name See \phpbb\request\request_interface::variable + * @param mixed $default See \phpbb\request\request_interface::variable + * + * @return mixed The server variable value. + */ + public function server($var_name, $default = ''); + + /** + * Shortcut method to retrieve the value of client HTTP headers. + * + * @param string|array $header_name The name of the header to retrieve. + * @param mixed $default See \phpbb\request\request_interface::variable + * + * @return mixed The header value. + */ + public function header($var_name, $default = ''); + + /** + * Checks whether a certain variable was sent via POST. + * To make sure that a request was sent using POST you should call this function + * on at least one variable. + * + * @param string $name The name of the form variable which should have a + * _p suffix to indicate the check in the code that creates the form too. + * + * @return bool True if the variable was set in a POST request, false otherwise. + */ + public function is_set_post($name); + + /** + * Checks whether a certain variable is set in one of the super global + * arrays. + * + * @param string $var Name of the variable + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * Specifies the super global which shall be checked + * + * @return bool True if the variable was sent as input + */ + public function is_set($var, $super_global = \phpbb\request\request_interface::REQUEST); + + /** + * Checks whether the current request is an AJAX request (XMLHttpRequest) + * + * @return bool True if the current request is an ajax request + */ + public function is_ajax(); + + /** + * Checks if the current request is happening over HTTPS. + * + * @return bool True if the request is secure. + */ + public function is_secure(); + + /** + * Returns all variable names for a given super global + * + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * The super global from which names shall be taken + * + * @return array All variable names that are set for the super global. + * Pay attention when using these, they are unsanitised! + */ + public function variable_names($super_global = \phpbb\request\request_interface::REQUEST); + + /** + * Returns the original array of the requested super global + * + * @param \phpbb\request\request_interface::POST|GET|REQUEST|COOKIE $super_global + * The super global which will be returned + * + * @return array The original array of the requested super global. + */ + public function get_super_global($super_global = \phpbb\request\request_interface::REQUEST); + + /** + * Escape a string variable. + * + * @param mixed $value The contents to fill with + * @param bool $multibyte Indicates whether string values may contain UTF-8 characters. + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. + * @return string|array + */ + public function escape($value, $multibyte); +} diff --git a/phpbb/request/type_cast_helper.php b/phpbb/request/type_cast_helper.php new file mode 100644 index 0000000..9124949 --- /dev/null +++ b/phpbb/request/type_cast_helper.php @@ -0,0 +1,124 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\request; + +/** +* A helper class that provides convenience methods for type casting. +*/ +class type_cast_helper implements \phpbb\request\type_cast_helper_interface +{ + /** + * Set variable $result to a particular type. + * + * @param mixed &$result The variable to fill + * @param mixed $var The contents to fill with + * @param mixed $type The variable type. Will be used with {@link settype()} + * @param bool $multibyte Indicates whether string values may contain UTF-8 characters. + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. + * @param bool $trim Indicates whether trim() should be applied to string values. + * Default is true. + */ + public function set_var(&$result, $var, $type, $multibyte = false, $trim = true) + { + settype($var, $type); + $result = $var; + + if ($type == 'string') + { + $result = str_replace(array("\r\n", "\r", "\0"), array("\n", "\n", ''), $result); + + if ($trim) + { + $result = trim($result); + } + + $result = htmlspecialchars($result, ENT_COMPAT, 'UTF-8'); + + if ($multibyte) + { + $result = utf8_normalize_nfc($result); + } + + if (!empty($result)) + { + // Make sure multibyte characters are wellformed + if ($multibyte) + { + if (!preg_match('/^./u', $result)) + { + $result = ''; + } + } + else + { + // no multibyte, allow only ASCII (0-127) + $result = preg_replace('/[\x80-\xFF]/', '?', $result); + } + } + } + } + + /** + * Recursively sets a variable to a given type using {@link set_var set_var} + * + * @param string $var The value which shall be sanitised (passed by reference). + * @param mixed $default Specifies the type $var shall have. + * If it is an array and $var is not one, then an empty array is returned. + * Otherwise var is cast to the same type, and if $default is an array all + * keys and values are cast recursively using this function too. + * @param bool $multibyte Indicates whether string keys and values may contain UTF-8 characters. + * Default is false, causing all bytes outside the ASCII range (0-127) to + * be replaced with question marks. + * @param bool $trim Indicates whether trim() should be applied to string values. + * Default is true. + */ + public function recursive_set_var(&$var, $default, $multibyte, $trim = true) + { + if (is_array($var) !== is_array($default)) + { + $var = (is_array($default)) ? array() : $default; + return; + } + + if (!is_array($default)) + { + $type = gettype($default); + $this->set_var($var, $var, $type, $multibyte, $trim); + } + else + { + // make sure there is at least one key/value pair to use get the + // types from + if (empty($default)) + { + $var = array(); + return; + } + + list($default_key, $default_value) = each($default); + $key_type = gettype($default_key); + + $_var = $var; + $var = array(); + + foreach ($_var as $k => $v) + { + $this->set_var($k, $k, $key_type, $multibyte); + + $this->recursive_set_var($v, $default_value, $multibyte, $trim); + $var[$k] = $v; + } + } + } +} diff --git a/phpbb/request/type_cast_helper_interface.php b/phpbb/request/type_cast_helper_interface.php new file mode 100644 index 0000000..9671573 --- /dev/null +++ b/phpbb/request/type_cast_helper_interface.php @@ -0,0 +1,45 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\request; + +/** +* An interface for type cast operations. +*/ +interface type_cast_helper_interface +{ + /** + * Set variable $result to a particular type. + * + * @param mixed &$result The variable to fill + * @param mixed $var The contents to fill with + * @param mixed $type The variable type. Will be used with {@link settype()} + * @param bool $multibyte Indicates whether string values may contain UTF-8 characters. + * Default is false, causing all bytes outside the ASCII range (0-127) to be replaced with question marks. + */ + public function set_var(&$result, $var, $type, $multibyte = false); + + /** + * Recursively sets a variable to a given type using {@link set_var set_var}. + * + * @param string $var The value which shall be sanitised (passed by reference). + * @param mixed $default Specifies the type $var shall have. + * If it is an array and $var is not one, then an empty array is returned. + * Otherwise var is cast to the same type, and if $default is an array all + * keys and values are cast recursively using this function too. + * @param bool $multibyte Indicates whether string keys and values may contain UTF-8 characters. + * Default is false, causing all bytes outside the ASCII range (0-127) to + * be replaced with question marks. + */ + public function recursive_set_var(&$var, $default, $multibyte); +} diff --git a/phpbb/routing/file_locator.php b/phpbb/routing/file_locator.php new file mode 100644 index 0000000..64efcc6 --- /dev/null +++ b/phpbb/routing/file_locator.php @@ -0,0 +1,33 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\routing; + +use phpbb\filesystem\filesystem_interface; +use Symfony\Component\Config\FileLocator; + +class file_locator extends FileLocator +{ + public function __construct(filesystem_interface $filesystem, $paths = []) + { + $paths = (array) $paths; + $absolute_paths = []; + + foreach ($paths as $path) + { + $absolute_paths[] = $filesystem->realpath($path); + } + + parent::__construct($absolute_paths); + } +} diff --git a/phpbb/routing/helper.php b/phpbb/routing/helper.php new file mode 100644 index 0000000..c15608d --- /dev/null +++ b/phpbb/routing/helper.php @@ -0,0 +1,162 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\routing; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\RequestContext; + +/** +* Controller helper class, contains methods that do things for controllers +*/ +class helper +{ + /** + * config object + * @var \phpbb\config\config + */ + protected $config; + + /** + * phpBB router + * @var \phpbb\routing\router + */ + protected $router; + + /** + * @var \phpbb\symfony_request + */ + protected $symfony_request; + + /** + * @var \phpbb\request\request_interface + */ + protected $request; + + /** + * @var \phpbb\filesystem The filesystem object + */ + protected $filesystem; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP file extension + * @var string + */ + protected $php_ext; + + /** + * Constructor + * + * @param \phpbb\config\config $config Config object + * @param \phpbb\routing\router $router phpBB router + * @param \phpbb\symfony_request $symfony_request Symfony Request object + * @param \phpbb\request\request_interface $request phpBB request object + * @param \phpbb\filesystem\filesystem $filesystem The filesystem object + * @param string $phpbb_root_path phpBB root path + * @param string $php_ext PHP file extension + */ + public function __construct(\phpbb\config\config $config, \phpbb\routing\router $router, \phpbb\symfony_request $symfony_request, \phpbb\request\request_interface $request, \phpbb\filesystem\filesystem $filesystem, $phpbb_root_path, $php_ext) + { + $this->config = $config; + $this->router = $router; + $this->symfony_request = $symfony_request; + $this->request = $request; + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $php_ext; + } + + /** + * Generate a URL to a route + * + * @param string $route Name of the route to travel + * @param array $params String or array of additional url parameters + * @param bool $is_amp Is url using & (true) or & (false) + * @param string|bool $session_id Possibility to use a custom session id instead of the global one + * @param bool|string $reference_type The type of reference to be generated (one of the constants) + * @return string The URL already passed through append_sid() + */ + public function route($route, array $params = array(), $is_amp = true, $session_id = false, $reference_type = UrlGeneratorInterface::ABSOLUTE_PATH) + { + $anchor = ''; + if (isset($params['#'])) + { + $anchor = '#' . $params['#']; + unset($params['#']); + } + + $context = new RequestContext(); + $context->fromRequest($this->symfony_request); + + if ($this->config['force_server_vars']) + { + $context->setHost($this->config['server_name']); + $context->setScheme(substr($this->config['server_protocol'], 0, -3)); + $context->setHttpPort($this->config['server_port']); + $context->setHttpsPort($this->config['server_port']); + $context->setBaseUrl(rtrim($this->config['script_path'], '/')); + } + + $script_name = $this->symfony_request->getScriptName(); + $page_name = substr($script_name, -1, 1) == '/' ? '' : utf8_basename($script_name); + + $base_url = $context->getBaseUrl(); + + // Append page name if base URL does not contain it + if (!empty($page_name) && strpos($base_url, '/' . $page_name) === false) + { + $base_url .= '/' . $page_name; + } + + // If enable_mod_rewrite is false we need to replace the current front-end by app.php, otherwise we need to remove it. + $base_url = str_replace('/' . $page_name, empty($this->config['enable_mod_rewrite']) ? '/app.' . $this->php_ext : '', $base_url); + + // We need to update the base url to move to the directory of the app.php file if the current script is not app.php + if ($page_name !== 'app.php' && !$this->config['force_server_vars']) + { + if (empty($this->config['enable_mod_rewrite'])) + { + $base_url = str_replace('/app.' . $this->php_ext, '/' . $this->phpbb_root_path . 'app.' . $this->php_ext, $base_url); + } + else + { + $base_url .= preg_replace(get_preg_expression('path_remove_dot_trailing_slash'), '$2', $this->phpbb_root_path); + } + } + + $base_url = $this->request->escape($this->filesystem->clean_path($base_url), true); + + $context->setBaseUrl($base_url); + + $this->router->setContext($context); + $route_url = $this->router->generate($route, $params, $reference_type); + + if ($is_amp) + { + $route_url = str_replace(array('&', '&'), array('&', '&'), $route_url); + } + + if ($reference_type === UrlGeneratorInterface::RELATIVE_PATH && empty($this->config['enable_mod_rewrite'])) + { + $route_url = 'app.' . $this->php_ext . '/' . $route_url; + } + + return append_sid($route_url . $anchor, false, $is_amp, $session_id, true); + } +} diff --git a/phpbb/routing/loader_resolver.php b/phpbb/routing/loader_resolver.php new file mode 100644 index 0000000..13fbc64 --- /dev/null +++ b/phpbb/routing/loader_resolver.php @@ -0,0 +1,50 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\routing; + +use Symfony\Component\Config\Loader\LoaderResolverInterface; + +/** + * @see Symfony\Component\Config\Loader\LoaderResolver + */ +class loader_resolver implements LoaderResolverInterface +{ + /** + * @var \Symfony\Component\Config\Loader\LoaderInterface[] An array of LoaderInterface objects + */ + protected $loaders = []; + + public function __construct($loaders = []) + { + $this->loaders = $loaders; + } + + /** + * {@inheritdoc} + */ + public function resolve($resource, $type = null) + { + /** @var \Symfony\Component\Config\Loader\LoaderInterface $loader */ + foreach ($this->loaders as $loader) + { + if ($loader->supports($resource, $type)) + { + $loader->setResolver($this); + return $loader; + } + } + + return false; + } +} diff --git a/phpbb/routing/resources_locator/chained_resources_locator.php b/phpbb/routing/resources_locator/chained_resources_locator.php new file mode 100644 index 0000000..db9abf2 --- /dev/null +++ b/phpbb/routing/resources_locator/chained_resources_locator.php @@ -0,0 +1,47 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\routing\resources_locator; + +class chained_resources_locator implements resources_locator_interface +{ + /** + * @var resources_locator_interface[] + */ + protected $locators; + + /** + * Construct method + * + * @param resources_locator_interface[] $locators Locators + */ + public function __construct($locators) + { + $this->locators = $locators; + } + + /** + * {@inheritdoc} + */ + public function locate_resources() + { + $resources = []; + + foreach ($this->locators as $locator) + { + $resources = array_merge($resources, $locator->locate_resources()); + } + + return $resources; + } +} diff --git a/phpbb/routing/resources_locator/default_resources_locator.php b/phpbb/routing/resources_locator/default_resources_locator.php new file mode 100644 index 0000000..90c3877 --- /dev/null +++ b/phpbb/routing/resources_locator/default_resources_locator.php @@ -0,0 +1,105 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\routing\resources_locator; + +use phpbb\extension\manager; + +/** + * Locates the yaml routing resources located in the default locations + */ +class default_resources_locator implements resources_locator_interface +{ + /** + * phpBB root path + * + * @var string + */ + protected $phpbb_root_path; + + /** + * Name of the current environment + * + * @var string + */ + protected $environment; + + /** + * Extension manager + * + * @var manager + */ + protected $extension_manager; + + /** + * Construct method + * + * @param string $phpbb_root_path phpBB root path + * @param string $environment Name of the current environment + * @param manager $extension_manager Extension manager + */ + public function __construct($phpbb_root_path, $environment, manager $extension_manager = null) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->environment = $environment; + $this->extension_manager = $extension_manager; + } + + /** + * {@inheritdoc} + */ + public function locate_resources() + { + $resources = [['config/' . $this->environment . '/routing/environment.yml', 'yaml']]; + + $resources = $this->append_ext_resources($resources); + + return $resources; + } + + /** + * Append extension resources to an array of resouces + * + * @see resources_locator_interface::locate_resources() + * + * @param mixed[] $resources List of resources + * + * @return mixed[] List of resources + */ + protected function append_ext_resources(array $resources) + { + if ($this->extension_manager !== null) + { + foreach ($this->extension_manager->all_enabled(false) as $path) + { + if (file_exists($this->phpbb_root_path . $path . 'config/' . $this->environment . '/routing/environment.yml')) + { + $resources[] = [$path . 'config/' . $this->environment . '/routing/environment.yml', 'yaml']; + } + else if (!is_dir($this->phpbb_root_path . $path . 'config/' . $this->environment)) + { + if (file_exists($this->phpbb_root_path . $path . 'config/default/routing/environment.yml')) + { + $resources[] = [$path . 'config/default/routing/environment.yml', 'yaml']; + } + else if (!is_dir($this->phpbb_root_path . $path . 'config/default/routing') && file_exists($this->phpbb_root_path . $path . 'config/routing.yml')) + { + $resources[] = [$path . 'config/routing.yml', 'yaml']; + } + } + } + } + + return $resources; + } +} diff --git a/phpbb/routing/resources_locator/installer_resources_locator.php b/phpbb/routing/resources_locator/installer_resources_locator.php new file mode 100644 index 0000000..42cd0f1 --- /dev/null +++ b/phpbb/routing/resources_locator/installer_resources_locator.php @@ -0,0 +1,78 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\routing\resources_locator; + +use phpbb\filesystem\filesystem_interface; + +/** + * Locates the yaml routing resources taking update directories into consideration + */ +class installer_resources_locator implements resources_locator_interface +{ + /** + * phpBB's filesystem handler + * + * @var filesystem_interface + */ + protected $filesystem; + + /** + * phpBB root path + * + * @var string + */ + protected $phpbb_root_path; + + /** + * Name of the current environment + * + * @var string + */ + protected $environment; + + /** + * Construct method + * + * @param filesystem_interface $filesystem phpBB's filesystem handler + * @param string $phpbb_root_path phpBB root path + * @param string $environment Name of the current environment + */ + public function __construct(filesystem_interface $filesystem, $phpbb_root_path, $environment) + { + $this->filesystem = $filesystem; + $this->phpbb_root_path = $phpbb_root_path; + $this->environment = $environment; + } + + /** + * {@inheritdoc} + */ + public function locate_resources() + { + if ($this->filesystem->exists($this->phpbb_root_path . 'install/update/new/config')) + { + $resources = array( + array('install/update/new/config/' . $this->environment . '/routing/environment.yml', 'yaml') + ); + } + else + { + $resources = array( + array('config/' . $this->environment . '/routing/environment.yml', 'yaml') + ); + } + + return $resources; + } +} diff --git a/phpbb/routing/resources_locator/resources_locator_interface.php b/phpbb/routing/resources_locator/resources_locator_interface.php new file mode 100644 index 0000000..46335cb --- /dev/null +++ b/phpbb/routing/resources_locator/resources_locator_interface.php @@ -0,0 +1,27 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\routing\resources_locator; + +interface resources_locator_interface +{ + /** + * Locates a list of resources used to load the routes + * + * Each entry of the list can be either the resource or an array composed of 2 elements: + * the resource and its type. + * + * @return mixed[] List of resources + */ + public function locate_resources(); +} diff --git a/phpbb/routing/router.php b/phpbb/routing/router.php new file mode 100644 index 0000000..f19886f --- /dev/null +++ b/phpbb/routing/router.php @@ -0,0 +1,395 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\routing; + +use phpbb\routing\resources_locator\resources_locator_interface; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Routing\Generator\Dumper\PhpGeneratorDumper; +use Symfony\Component\Routing\Generator\UrlGenerator; +use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Routing\RouterInterface; + +/** + * Integration of all pieces of the routing system for easier use. + */ +class router implements RouterInterface +{ + /** + * @var ContainerInterface + */ + protected $container; + + /** + * @var resources_locator_interface + */ + protected $resources_locator; + + /** + * @var LoaderInterface + */ + protected $loader; + + /** + * PHP file extensions + * + * @var string + */ + protected $php_ext; + + /** + * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface|null + */ + protected $matcher; + + /** + * @var \Symfony\Component\Routing\Generator\UrlGeneratorInterface|null + */ + protected $generator; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var RouteCollection + */ + protected $route_collection; + + /** + * @var string + */ + protected $cache_dir; + + /** + * Construct method + * + * @param ContainerInterface $container DI container + * @param resources_locator_interface $resources_locator Resources locator + * @param LoaderInterface $loader Resources loader + * @param string $php_ext PHP file extension + * @param string $cache_dir phpBB cache directory + */ + public function __construct(ContainerInterface $container, resources_locator_interface $resources_locator, LoaderInterface $loader, $php_ext, $cache_dir) + { + $this->container = $container; + $this->resources_locator = $resources_locator; + $this->loader = $loader; + $this->php_ext = $php_ext; + $this->context = new RequestContext(); + $this->cache_dir = $cache_dir; + } + + /** + * Get the list of routes + * + * @return RouteCollection Get the route collection + */ + public function get_routes() + { + if ($this->route_collection === null /*|| $this->route_collection->count() === 0*/) + { + $this->route_collection = new RouteCollection; + foreach ($this->resources_locator->locate_resources() as $resource) + { + if (is_array($resource)) + { + $this->route_collection->addCollection($this->loader->load($resource[0], $resource[1])); + } + else + { + $this->route_collection->addCollection($this->loader->load($resource)); + } + } + + $this->resolveParameters($this->route_collection); + } + + return $this->route_collection; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollection() + { + return $this->get_routes(); + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + + if ($this->matcher !== null) + { + $this->get_matcher()->setContext($context); + } + if ($this->generator !== null) + { + $this->get_generator()->setContext($context); + } + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + return $this->get_generator()->generate($name, $parameters, $referenceType); + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + return $this->get_matcher()->match($pathinfo); + } + + /** + * Gets the UrlMatcher instance associated with this Router. + * + * @return \Symfony\Component\Routing\Matcher\UrlMatcherInterface A UrlMatcherInterface instance + */ + public function get_matcher() + { + if ($this->matcher !== null) + { + return $this->matcher; + } + + $this->create_dumped_url_matcher(); + + return $this->matcher; + } + + /** + * Creates a new dumped URL Matcher (dump it if necessary) + */ + protected function create_dumped_url_matcher() + { + try + { + $cache = new ConfigCache("{$this->cache_dir}url_matcher.{$this->php_ext}", defined('DEBUG')); + if (!$cache->isFresh()) + { + $dumper = new PhpMatcherDumper($this->get_routes()); + + $options = array( + 'class' => 'phpbb_url_matcher', + 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + ); + + $cache->write($dumper->dump($options), $this->get_routes()->getResources()); + } + + require_once($cache->getPath()); + + $this->matcher = new \phpbb_url_matcher($this->context); + } + catch (IOException $e) + { + $this->create_new_url_matcher(); + } + } + + /** + * Creates a new URL Matcher + */ + protected function create_new_url_matcher() + { + $this->matcher = new UrlMatcher($this->get_routes(), $this->context); + } + + /** + * Gets the UrlGenerator instance associated with this Router. + * + * @return \Symfony\Component\Routing\Generator\UrlGeneratorInterface A UrlGeneratorInterface instance + */ + public function get_generator() + { + if ($this->generator !== null) + { + return $this->generator; + } + + $this->create_dumped_url_generator(); + + return $this->generator; + } + + /** + * Creates a new dumped URL Generator (dump it if necessary) + */ + protected function create_dumped_url_generator() + { + try + { + $cache = new ConfigCache("{$this->cache_dir}url_generator.{$this->php_ext}", defined('DEBUG')); + if (!$cache->isFresh()) + { + $dumper = new PhpGeneratorDumper($this->get_routes()); + + $options = array( + 'class' => 'phpbb_url_generator', + 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + ); + + $cache->write($dumper->dump($options), $this->get_routes()->getResources()); + } + + require_once($cache->getPath()); + + $this->generator = new \phpbb_url_generator($this->context); + } + catch (IOException $e) + { + $this->create_new_url_generator(); + } + } + + /** + * Creates a new URL Generator + */ + protected function create_new_url_generator() + { + $this->generator = new UrlGenerator($this->get_routes(), $this->context); + } + + /** + * Replaces placeholders with service container parameter values in: + * - the route defaults, + * - the route requirements, + * - the route path, + * - the route host, + * - the route schemes, + * - the route methods. + * + * @param RouteCollection $collection + */ + protected function resolveParameters(RouteCollection $collection) + { + /** @var \Symfony\Component\Routing\Route $route */ + foreach ($collection as $route) + { + foreach ($route->getDefaults() as $name => $value) + { + $route->setDefault($name, $this->resolve($value)); + } + + $requirements = $route->getRequirements(); + unset($requirements['_scheme']); + unset($requirements['_method']); + + foreach ($requirements as $name => $value) + { + $route->setRequirement($name, $this->resolve($value)); + } + + $route->setPath($this->resolve($route->getPath())); + $route->setHost($this->resolve($route->getHost())); + + $schemes = array(); + foreach ($route->getSchemes() as $scheme) + { + $schemes = array_merge($schemes, explode('|', $this->resolve($scheme))); + } + + $route->setSchemes($schemes); + $methods = array(); + foreach ($route->getMethods() as $method) + { + $methods = array_merge($methods, explode('|', $this->resolve($method))); + } + + $route->setMethods($methods); + $route->setCondition($this->resolve($route->getCondition())); + } + } + + /** + * Recursively replaces placeholders with the service container parameters. + * + * @param mixed $value The source which might contain "%placeholders%" + * + * @return mixed The source with the placeholders replaced by the container + * parameters. Arrays are resolved recursively. + * + * @throws ParameterNotFoundException When a placeholder does not exist as a container parameter + * @throws RuntimeException When a container value is not a string or a numeric value + */ + private function resolve($value) + { + if (is_array($value)) + { + foreach ($value as $key => $val) + { + $value[$key] = $this->resolve($val); + } + + return $value; + } + + if (!is_string($value)) + { + return $value; + } + + $container = $this->container; + $escapedValue = preg_replace_callback('/%%|%([^%\s]++)%/', function ($match) use ($container, $value) + { + // skip %% + if (!isset($match[1])) + { + return '%%'; + } + + $resolved = $container->getParameter($match[1]); + if (is_string($resolved) || is_numeric($resolved)) + { + return (string) $resolved; + } + + throw new RuntimeException(sprintf( + 'The container parameter "%s", used in the route configuration value "%s", '. + 'must be a string or numeric, but it is of type %s.', + $match[1], + $value, + gettype($resolved) + ) + ); + }, $value); + + return str_replace('%%', '%', $escapedValue); + } +} diff --git a/phpbb/search/base.php b/phpbb/search/base.php new file mode 100644 index 0000000..e7d0774 --- /dev/null +++ b/phpbb/search/base.php @@ -0,0 +1,292 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search; + +/** +* @ignore +*/ +define('SEARCH_RESULT_NOT_IN_CACHE', 0); +define('SEARCH_RESULT_IN_CACHE', 1); +define('SEARCH_RESULT_INCOMPLETE', 2); + +/** +* optional base class for search plugins providing simple caching based on ACM +* and functions to retrieve ignore_words and synonyms +*/ +class base +{ + var $ignore_words = array(); + var $match_synonym = array(); + var $replace_synonym = array(); + + function search_backend(&$error) + { + // This class cannot be used as a search plugin + $error = true; + } + + /** + * Retrieves cached search results + * + * @param string $search_key an md5 string generated from all the passed search options to identify the results + * @param int &$result_count will contain the number of all results for the search (not only for the current page) + * @param array &$id_ary is filled with the ids belonging to the requested page that are stored in the cache + * @param int &$start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @param string $sort_dir is either a or d representing ASC and DESC + * + * @return int SEARCH_RESULT_NOT_IN_CACHE or SEARCH_RESULT_IN_CACHE or SEARCH_RESULT_INCOMPLETE + */ + function obtain_ids($search_key, &$result_count, &$id_ary, &$start, $per_page, $sort_dir) + { + global $cache; + + if (!($stored_ids = $cache->get('_search_results_' . $search_key))) + { + // no search results cached for this search_key + return SEARCH_RESULT_NOT_IN_CACHE; + } + else + { + $result_count = $stored_ids[-1]; + $reverse_ids = ($stored_ids[-2] != $sort_dir) ? true : false; + $complete = true; + + // Change start parameter in case out of bounds + if ($result_count) + { + if ($start < 0) + { + $start = 0; + } + else if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + } + } + + // change the start to the actual end of the current request if the sort direction differs + // from the dirction in the cache and reverse the ids later + if ($reverse_ids) + { + $start = $result_count - $start - $per_page; + + // the user requested a page past the last index + if ($start < 0) + { + return SEARCH_RESULT_NOT_IN_CACHE; + } + } + + for ($i = $start, $n = $start + $per_page; ($i < $n) && ($i < $result_count); $i++) + { + if (!isset($stored_ids[$i])) + { + $complete = false; + } + else + { + $id_ary[] = $stored_ids[$i]; + } + } + unset($stored_ids); + + if ($reverse_ids) + { + $id_ary = array_reverse($id_ary); + } + + if (!$complete) + { + return SEARCH_RESULT_INCOMPLETE; + } + return SEARCH_RESULT_IN_CACHE; + } + } + + /** + * Caches post/topic ids + * + * @param string $search_key an md5 string generated from all the passed search options to identify the results + * @param string $keywords contains the keywords as entered by the user + * @param array $author_ary an array of author ids, if the author should be ignored during the search the array is empty + * @param int $result_count contains the number of all results for the search (not only for the current page) + * @param array &$id_ary contains a list of post or topic ids that shall be cached, the first element + * must have the absolute index $start in the result set. + * @param int $start indicates the first index of the page + * @param string $sort_dir is either a or d representing ASC and DESC + * + * @return null + */ + function save_ids($search_key, $keywords, $author_ary, $result_count, &$id_ary, $start, $sort_dir) + { + global $cache, $config, $db, $user; + + $length = min(count($id_ary), $config['search_block_size']); + + // nothing to cache so exit + if (!$length) + { + return; + } + + $store_ids = array_slice($id_ary, 0, $length); + + // create a new resultset if there is none for this search_key yet + // or add the ids to the existing resultset + if (!($store = $cache->get('_search_results_' . $search_key))) + { + // add the current keywords to the recent searches in the cache which are listed on the search page + if (!empty($keywords) || count($author_ary)) + { + $sql = 'SELECT search_time + FROM ' . SEARCH_RESULTS_TABLE . ' + WHERE search_key = \'' . $db->sql_escape($search_key) . '\''; + $result = $db->sql_query($sql); + + if (!$db->sql_fetchrow($result)) + { + $sql_ary = array( + 'search_key' => $search_key, + 'search_time' => time(), + 'search_keywords' => $keywords, + 'search_authors' => ' ' . implode(' ', $author_ary) . ' ' + ); + + $sql = 'INSERT INTO ' . SEARCH_RESULTS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + } + $db->sql_freeresult($result); + } + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_last_search = ' . time() . ' + WHERE user_id = ' . $user->data['user_id']; + $db->sql_query($sql); + + $store = array(-1 => $result_count, -2 => $sort_dir); + $id_range = range($start, $start + $length - 1); + } + else + { + // we use one set of results for both sort directions so we have to calculate the indizes + // for the reversed array and we also have to reverse the ids themselves + if ($store[-2] != $sort_dir) + { + $store_ids = array_reverse($store_ids); + $id_range = range($store[-1] - $start - $length, $store[-1] - $start - 1); + } + else + { + $id_range = range($start, $start + $length - 1); + } + } + + $store_ids = array_combine($id_range, $store_ids); + + // append the ids + if (is_array($store_ids)) + { + $store += $store_ids; + + // if the cache is too big + if (count($store) - 2 > 20 * $config['search_block_size']) + { + // remove everything in front of two blocks in front of the current start index + for ($i = 0, $n = $id_range[0] - 2 * $config['search_block_size']; $i < $n; $i++) + { + if (isset($store[$i])) + { + unset($store[$i]); + } + } + + // remove everything after two blocks after the current stop index + end($id_range); + for ($i = $store[-1] - 1, $n = current($id_range) + 2 * $config['search_block_size']; $i > $n; $i--) + { + if (isset($store[$i])) + { + unset($store[$i]); + } + } + } + $cache->put('_search_results_' . $search_key, $store, $config['search_store_results']); + + $sql = 'UPDATE ' . SEARCH_RESULTS_TABLE . ' + SET search_time = ' . time() . ' + WHERE search_key = \'' . $db->sql_escape($search_key) . '\''; + $db->sql_query($sql); + } + + unset($store); + unset($store_ids); + unset($id_range); + } + + /** + * Removes old entries from the search results table and removes searches with keywords that contain a word in $words. + */ + function destroy_cache($words, $authors = false) + { + global $db, $cache, $config; + + // clear all searches that searched for the specified words + if (count($words)) + { + $sql_where = ''; + foreach ($words as $word) + { + $sql_where .= " OR search_keywords " . $db->sql_like_expression($db->get_any_char() . $word . $db->get_any_char()); + } + + $sql = 'SELECT search_key + FROM ' . SEARCH_RESULTS_TABLE . " + WHERE search_keywords LIKE '%*%' $sql_where"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $cache->destroy('_search_results_' . $row['search_key']); + } + $db->sql_freeresult($result); + } + + // clear all searches that searched for the specified authors + if (is_array($authors) && count($authors)) + { + $sql_where = ''; + foreach ($authors as $author) + { + $sql_where .= (($sql_where) ? ' OR ' : '') . 'search_authors ' . $db->sql_like_expression($db->get_any_char() . ' ' . (int) $author . ' ' . $db->get_any_char()); + } + + $sql = 'SELECT search_key + FROM ' . SEARCH_RESULTS_TABLE . " + WHERE $sql_where"; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $cache->destroy('_search_results_' . $row['search_key']); + } + $db->sql_freeresult($result); + } + + $sql = 'DELETE + FROM ' . SEARCH_RESULTS_TABLE . ' + WHERE search_time < ' . (time() - (int) $config['search_store_results']); + $db->sql_query($sql); + } +} diff --git a/phpbb/search/fulltext_mysql.php b/phpbb/search/fulltext_mysql.php new file mode 100644 index 0000000..137ed74 --- /dev/null +++ b/phpbb/search/fulltext_mysql.php @@ -0,0 +1,1227 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search; + +/** +* Fulltext search for MySQL +*/ +class fulltext_mysql extends \phpbb\search\base +{ + /** + * Associative array holding index stats + * @var array + */ + protected $stats = array(); + + /** + * Holds the words entered by user, obtained by splitting the entered query on whitespace + * @var array + */ + protected $split_words = array(); + + /** + * Config object + * @var \phpbb\config\config + */ + protected $config; + + /** + * Database connection + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * phpBB event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $phpbb_dispatcher; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Associative array stores the min and max word length to be searched + * @var array + */ + protected $word_length = array(); + + /** + * Contains tidied search query. + * Operators are prefixed in search query and common words excluded + * @var string + */ + protected $search_query; + + /** + * Contains common words. + * Common words are words with length less/more than min/max length + * @var array + */ + protected $common_words = array(); + + /** + * Constructor + * Creates a new \phpbb\search\fulltext_mysql, which is used as a search backend + * + * @param string|bool $error Any error that occurs is passed on through this reference variable otherwise false + * @param string $phpbb_root_path Relative path to phpBB root + * @param string $phpEx PHP file extension + * @param \phpbb\auth\auth $auth Auth object + * @param \phpbb\config\config $config Config object + * @param \phpbb\db\driver\driver_interface Database object + * @param \phpbb\user $user User object + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object + */ + public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher) + { + $this->config = $config; + $this->db = $db; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->user = $user; + + $this->word_length = array('min' => $this->config['fulltext_mysql_min_word_len'], 'max' => $this->config['fulltext_mysql_max_word_len']); + + /** + * Load the UTF tools + */ + if (!function_exists('utf8_strlen')) + { + include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); + } + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'MySQL Fulltext'; + } + + /** + * Returns the search_query + * + * @return string search query + */ + public function get_search_query() + { + return $this->search_query; + } + + /** + * Returns the common_words array + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return $this->common_words; + } + + /** + * Returns the word_length array + * + * @return array min and max word length for searching + */ + public function get_word_length() + { + return $this->word_length; + } + + /** + * Checks for correct MySQL version and stores min/max word length in the config + * + * @return string|bool Language key of the error/incompatiblity occurred + */ + public function init() + { + if ($this->db->get_sql_layer() != 'mysql4' && $this->db->get_sql_layer() != 'mysqli') + { + return $this->user->lang['FULLTEXT_MYSQL_INCOMPATIBLE_DATABASE']; + } + + $result = $this->db->sql_query('SHOW TABLE STATUS LIKE \'' . POSTS_TABLE . '\''); + $info = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + + $engine = ''; + if (isset($info['Engine'])) + { + $engine = $info['Engine']; + } + else if (isset($info['Type'])) + { + $engine = $info['Type']; + } + + $fulltext_supported = + $engine === 'MyISAM' || + // FULLTEXT is supported on InnoDB since MySQL 5.6.4 according to + // http://dev.mysql.com/doc/refman/5.6/en/innodb-storage-engine.html + // We also require https://bugs.mysql.com/bug.php?id=67004 to be + // fixed for proper overall operation. Hence we require 5.6.8. + $engine === 'InnoDB' && + phpbb_version_compare($this->db->sql_server_info(true), '5.6.8', '>='); + + if (!$fulltext_supported) + { + return $this->user->lang['FULLTEXT_MYSQL_NOT_SUPPORTED']; + } + + $sql = 'SHOW VARIABLES + LIKE \'ft\_%\''; + $result = $this->db->sql_query($sql); + + $mysql_info = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $mysql_info[$row['Variable_name']] = $row['Value']; + } + $this->db->sql_freeresult($result); + + $this->config->set('fulltext_mysql_max_word_len', $mysql_info['ft_max_word_len']); + $this->config->set('fulltext_mysql_min_word_len', $mysql_info['ft_min_word_len']); + + return false; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string &$keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return bool false if no valid keywords were found and otherwise true + */ + public function split_keywords(&$keywords, $terms) + { + if ($terms == 'all') + { + $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#(^|\s)\+#', '#(^|\s)-#', '#(^|\s)\|#'); + $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); + + $keywords = preg_replace($match, $replace, $keywords); + } + + // Filter out as above + $split_keywords = preg_replace("#[\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); + + // Split words + $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); + $this->split_words = $matches[1]; + + // We limit the number of allowed keywords to minimize load on the database + if ($this->config['max_num_search_keywords'] && count($this->split_words) > $this->config['max_num_search_keywords']) + { + trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', (int) $this->config['max_num_search_keywords'], count($this->split_words))); + } + + // to allow phrase search, we need to concatenate quoted words + $tmp_split_words = array(); + $phrase = ''; + foreach ($this->split_words as $word) + { + if ($phrase) + { + $phrase .= ' ' . $word; + if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1) + { + $tmp_split_words[] = $phrase; + $phrase = ''; + } + } + else if (strpos($word, '"') !== false && substr_count($word, '"') % 2 == 1) + { + $phrase = $word; + } + else + { + $tmp_split_words[] = $word; + } + } + if ($phrase) + { + $tmp_split_words[] = $phrase; + } + + $this->split_words = $tmp_split_words; + + unset($tmp_split_words); + unset($phrase); + + foreach ($this->split_words as $i => $word) + { + // Check for not allowed search queries for InnoDB. + // We assume similar restrictions for MyISAM, which is usually even + // slower but not as restrictive as InnoDB. + // InnoDB full-text search does not support the use of a leading + // plus sign with wildcard ('+*'), a plus and minus sign + // combination ('+-'), or leading a plus and minus sign combination. + // InnoDB full-text search only supports leading plus or minus signs. + // For example, InnoDB supports '+apple' but does not support 'apple+'. + // Specifying a trailing plus or minus sign causes InnoDB to report + // a syntax error. InnoDB full-text search does not support the use + // of multiple operators on a single search word, as in this example: + // '++apple'. Use of multiple operators on a single search word + // returns a syntax error to standard out. + // Also, ensure that the wildcard character is only used at the + // end of the line as it's intended by MySQL. + if (preg_match('#^(\+[+-]|\+\*|.+[+-]$|.+\*(?!$))#', $word)) + { + unset($this->split_words[$i]); + continue; + } + + $clean_word = preg_replace('#^[+\-|"]#', '', $word); + + // check word length + $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); + if (($clean_len < $this->config['fulltext_mysql_min_word_len']) || ($clean_len > $this->config['fulltext_mysql_max_word_len'])) + { + $this->common_words[] = $word; + unset($this->split_words[$i]); + } + } + + if ($terms == 'any') + { + $this->search_query = ''; + foreach ($this->split_words as $word) + { + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) + { + $word = substr($word, 1); + } + $this->search_query .= $word . ' '; + } + } + else + { + $this->search_query = ''; + foreach ($this->split_words as $word) + { + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0)) + { + $this->search_query .= $word . ' '; + } + else if (strpos($word, '|') === 0) + { + $this->search_query .= substr($word, 1) . ' '; + } + else + { + $this->search_query .= '+' . $word . ' '; + } + } + } + + $this->search_query = utf8_htmlspecialchars($this->search_query); + + if ($this->search_query) + { + $this->split_words = array_values($this->split_words); + sort($this->split_words); + return true; + } + return false; + } + + /** + * Turns text into an array of words + * @param string $text contains post text/subject + */ + public function split_message($text) + { + // Split words + $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); + $text = $matches[1]; + + // remove too short or too long words + $text = array_values($text); + for ($i = 0, $n = count($text); $i < $n; $i++) + { + $text[$i] = trim($text[$i]); + if (utf8_strlen($text[$i]) < $this->config['fulltext_mysql_min_word_len'] || utf8_strlen($text[$i]) > $this->config['fulltext_mysql_max_word_len']) + { + unset($text[$i]); + } + } + + return array_values($text); + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No keywords? No posts + if (!$this->search_query) + { + return false; + } + + // generate a search_key from all the options to identify the results + $search_key_array = array( + implode(', ', $this->split_words), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary) + ); + + /** + * Allow changing the search_key for cached results + * + * @event core.search_mysql_by_keyword_modify_search_key + * @var array search_key_array Array with search parameters to generate the search_key + * @var string type Searching type ('posts', 'topics') + * @var string fields Searching fields ('titleonly', 'msgonly', 'firstpost', 'all') + * @var string terms Searching terms ('all', 'any') + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sort_key The sort type used from the possible sort types + * @var int topic_id Limit the search to this topic_id only + * @var array ex_fid_ary Which forums not to search on + * @var string post_visibility Post visibility data + * @var array author_ary Array of user_id containing the users to filter the results to + * @since 3.1.7-RC1 + */ + $vars = array( + 'search_key_array', + 'type', + 'fields', + 'terms', + 'sort_days', + 'sort_key', + 'topic_id', + 'ex_fid_ary', + 'post_visibility', + 'author_ary', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_mysql_by_keyword_modify_search_key', compact($vars))); + + $search_key = md5(implode('#', $search_key_array)); + + if ($start < 0) + { + $start = 0; + } + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + $join_topic = ($type == 'posts') ? false : true; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $join_topic = true; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $sql_match = 'p.post_subject'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + case 'msgonly': + $sql_match = 'p.post_text'; + $sql_match_where = ''; + break; + + case 'firstpost': + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + default: + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ''; + break; + } + + $search_query = $this->search_query; + + /** + * Allow changing the query used to search for posts using fulltext_mysql + * + * @event core.search_mysql_keywords_main_query_before + * @var string search_query The parsed keywords used for this search + * @var int result_count The previous result count for the format of the query. + * Set to 0 to force a re-count + * @var bool join_topic Weather or not TOPICS_TABLE should be CROSS JOIN'ED + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name An extra username to search on (!empty(author_ary) must be true, to be relevant) + * @var array ex_fid_ary Which forums not to search on + * @var int topic_id Limit the search to this topic_id only + * @var string sql_sort_table Extra tables to include in the SQL query. + * Used in conjunction with sql_sort_join + * @var string sql_sort_join SQL conditions to join all the tables used together. + * Used in conjunction with sql_sort_table + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sql_match Which columns to do the search on. + * @var string sql_match_where Extra conditions to use to properly filter the matching process + * @var string sort_by_sql The possible predefined sort types + * @var string sort_key The sort type used from the possible sort types + * @var string sort_dir "a" for ASC or "d" dor DESC for the sort order used + * @var string sql_sort The result SQL when processing sort_by_sql + sort_key + sort_dir + * @var int start How many posts to skip in the search results (used for pagination) + * @since 3.1.5-RC1 + */ + $vars = array( + 'search_query', + 'result_count', + 'join_topic', + 'author_ary', + 'author_name', + 'ex_fid_ary', + 'topic_id', + 'sql_sort_table', + 'sql_sort_join', + 'sort_days', + 'sql_match', + 'sql_match_where', + 'sort_by_sql', + 'sort_key', + 'sort_dir', + 'sql_sort', + 'start', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_mysql_keywords_main_query_before', compact($vars))); + + $sql_select = (!$result_count) ? 'SQL_CALC_FOUND_ROWS ' : ''; + $sql_select = ($type == 'posts') ? $sql_select . 'p.post_id' : 'DISTINCT ' . $sql_select . 't.topic_id'; + $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; + $field = ($type == 'posts') ? 'post_id' : 'topic_id'; + if (count($author_ary) && $author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = ' AND (' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else if (count($author_ary)) + { + $sql_author = ' AND ' . $this->db->sql_in_set('p.poster_id', $author_ary); + } + else + { + $sql_author = ''; + } + + $sql_where_options = $sql_sort_join; + $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; + $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; + $sql_where_options .= (count($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_where_options .= ' AND ' . $post_visibility; + $sql_where_options .= $sql_author; + $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_where_options .= $sql_match_where; + + $sql = "SELECT $sql_select + FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p + WHERE MATCH ($sql_match) AGAINST ('" . $this->db->sql_escape(htmlspecialchars_decode($this->search_query)) . "' IN BOOLEAN MODE) + $sql_where_options + ORDER BY $sql_sort"; + $this->db->sql_return_on_error(true); + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + + // if the total result count is not cached yet, retrieve it from the db + if (!$result_count && count($id_ary)) + { + $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; + $result = $this->db->sql_query($sql_found_rows); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + $this->db->sql_freeresult($result); + + if (!$result_count) + { + return false; + } + } + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No author? No posts + if (!count($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key_array = array( + '', + $type, + ($firstpost_only) ? 'firstpost' : '', + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary), + $author_name, + ); + + /** + * Allow changing the search_key for cached results + * + * @event core.search_mysql_by_author_modify_search_key + * @var array search_key_array Array with search parameters to generate the search_key + * @var string type Searching type ('posts', 'topics') + * @var boolean firstpost_only Flag indicating if only topic starting posts are considered + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sort_key The sort type used from the possible sort types + * @var int topic_id Limit the search to this topic_id only + * @var array ex_fid_ary Which forums not to search on + * @var string post_visibility Post visibility data + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name The username to search on + * @since 3.1.7-RC1 + */ + $vars = array( + 'search_key_array', + 'type', + 'firstpost_only', + 'sort_days', + 'sort_key', + 'topic_id', + 'ex_fid_ary', + 'post_visibility', + 'author_ary', + 'author_name', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_mysql_by_author_modify_search_key', compact($vars))); + + $search_key = md5(implode('#', $search_key_array)); + + if ($start < 0) + { + $start = 0; + } + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + // Create some display specific sql strings + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); + } + $sql_fora = (count($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + $m_approve_fid_sql = ' AND ' . $post_visibility; + + /** + * Allow changing the query used to search for posts by author in fulltext_mysql + * + * @event core.search_mysql_author_query_before + * @var int result_count The previous result count for the format of the query. + * Set to 0 to force a re-count + * @var string sql_sort_table CROSS JOIN'ed table to allow doing the sort chosen + * @var string sql_sort_join Condition to define how to join the CROSS JOIN'ed table specifyed in sql_sort_table + * @var string type Either "posts" or "topics" specifying the type of search being made + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name An extra username to search on + * @var string sql_author SQL WHERE condition for the post author ids + * @var int topic_id Limit the search to this topic_id only + * @var string sql_topic_id SQL of topic_id + * @var string sort_by_sql The possible predefined sort types + * @var string sort_key The sort type used from the possible sort types + * @var string sort_dir "a" for ASC or "d" dor DESC for the sort order used + * @var string sql_sort The result SQL when processing sort_by_sql + sort_key + sort_dir + * @var string sort_days Time, in days, that the oldest post showing can have + * @var string sql_time The SQL to search on the time specifyed by sort_days + * @var bool firstpost_only Wether or not to search only on the first post of the topics + * @var string sql_firstpost The SQL with the conditions to join the tables when using firstpost_only + * @var array ex_fid_ary Forum ids that must not be searched on + * @var array sql_fora SQL query for ex_fid_ary + * @var string m_approve_fid_sql WHERE clause condition on post_visibility restrictions + * @var int start How many posts to skip in the search results (used for pagination) + * @since 3.1.5-RC1 + */ + $vars = array( + 'result_count', + 'sql_sort_table', + 'sql_sort_join', + 'type', + 'author_ary', + 'author_name', + 'sql_author', + 'topic_id', + 'sql_topic_id', + 'sort_by_sql', + 'sort_key', + 'sort_dir', + 'sql_sort', + 'sort_days', + 'sql_time', + 'firstpost_only', + 'sql_firstpost', + 'ex_fid_ary', + 'sql_fora', + 'm_approve_fid_sql', + 'start', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_mysql_author_query_before', compact($vars))); + + // If the cache was completely empty count the results + $calc_results = ($result_count) ? '' : 'SQL_CALC_FOUND_ROWS '; + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT {$calc_results}p.post_id + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT {$calc_results}t.topic_id + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id + ORDER BY $sql_sort"; + $field = 'topic_id'; + } + + // Only read one block of posts from the db and then cache it + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + // retrieve the total result count if needed + if (!$result_count) + { + $sql_found_rows = 'SELECT FOUND_ROWS() as result_count'; + $result = $this->db->sql_query($sql_found_rows); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + $this->db->sql_freeresult($result); + + if (!$result_count) + { + return false; + } + } + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + + if (count($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $result_count; + } + return false; + } + + /** + * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated + * + * @param string $mode contains the post mode: edit, post, reply, quote ... + * @param int $post_id contains the post id of the post to index + * @param string $message contains the post text of the post + * @param string $subject contains the subject of the post to index + * @param int $poster_id contains the user id of the poster + * @param int $forum_id contains the forum id of parent forum of the post + */ + public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + // Split old and new post/subject to obtain array of words + $split_text = $this->split_message($message); + $split_title = ($subject) ? $this->split_message($subject) : array(); + + $words = array_unique(array_merge($split_text, $split_title)); + + /** + * Event to modify method arguments and words before the MySQL search index is updated + * + * @event core.search_mysql_index_before + * @var string mode Contains the post mode: edit, post, reply, quote + * @var int post_id The id of the post which is modified/created + * @var string message New or updated post content + * @var string subject New or updated post subject + * @var int poster_id Post author's user id + * @var int forum_id The id of the forum in which the post is located + * @var array words List of words added to the index + * @var array split_text Array of words from the message + * @var array split_title Array of words from the title + * @since 3.2.3-RC1 + */ + $vars = array( + 'mode', + 'post_id', + 'message', + 'subject', + 'poster_id', + 'forum_id', + 'words', + 'split_text', + 'split_title', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_mysql_index_before', compact($vars))); + + unset($split_text); + unset($split_title); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache($words, array($poster_id)); + + unset($words); + } + + /** + * Destroy cached results, that might be outdated after deleting a post + */ + public function index_remove($post_ids, $author_ids, $forum_ids) + { + $this->destroy_cache(array(), array_unique($author_ids)); + } + + /** + * Destroy old cache entries + */ + public function tidy() + { + // destroy too old cached search results + $this->destroy_cache(array()); + + $this->config->set('search_last_gc', time(), false); + } + + /** + * Create fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function create_index($acp_module, $u_action) + { + // Make sure we can actually use MySQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + $alter_list = array(); + + if (!isset($this->stats['post_subject'])) + { + $alter_entry = array(); + if ($this->db->get_sql_layer() == 'mysqli' || version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $alter_entry[] = 'MODIFY post_subject varchar(255) COLLATE utf8_unicode_ci DEFAULT \'\' NOT NULL'; + } + else + { + $alter_entry[] = 'MODIFY post_subject text NOT NULL'; + } + $alter_entry[] = 'ADD FULLTEXT (post_subject)'; + $alter_list[] = $alter_entry; + } + + if (!isset($this->stats['post_content'])) + { + $alter_entry = array(); + if ($this->db->get_sql_layer() == 'mysqli' || version_compare($this->db->sql_server_info(true), '4.1.3', '>=')) + { + $alter_entry[] = 'MODIFY post_text mediumtext COLLATE utf8_unicode_ci NOT NULL'; + } + else + { + $alter_entry[] = 'MODIFY post_text mediumtext NOT NULL'; + } + + $alter_entry[] = 'ADD FULLTEXT post_content (post_text, post_subject)'; + $alter_list[] = $alter_entry; + } + + $sql_queries = []; + + foreach ($alter_list as $alter) + { + $sql_queries[] = 'ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter); + } + + if (!isset($this->stats['post_text'])) + { + $sql_queries[] = 'ALTER TABLE ' . POSTS_TABLE . ' ADD FULLTEXT post_text (post_text)'; + } + + $stats = $this->stats; + + /** + * Event to modify SQL queries before the MySQL search index is created + * + * @event core.search_mysql_create_index_before + * @var array sql_queries Array with queries for creating the search index + * @var array stats Array with statistics of the current index (read only) + * @since 3.2.3-RC1 + */ + $vars = array( + 'sql_queries', + 'stats', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_mysql_create_index_before', compact($vars))); + + foreach ($sql_queries as $sql_query) + { + $this->db->sql_query($sql_query); + } + + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Drop fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function delete_index($acp_module, $u_action) + { + // Make sure we can actually use MySQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + $alter = array(); + + if (isset($this->stats['post_subject'])) + { + $alter[] = 'DROP INDEX post_subject'; + } + + if (isset($this->stats['post_content'])) + { + $alter[] = 'DROP INDEX post_content'; + } + + if (isset($this->stats['post_text'])) + { + $alter[] = 'DROP INDEX post_text'; + } + + $sql_queries = []; + + if (count($alter)) + { + $sql_queries[] = 'ALTER TABLE ' . POSTS_TABLE . ' ' . implode(', ', $alter); + } + + $stats = $this->stats; + + /** + * Event to modify SQL queries before the MySQL search index is deleted + * + * @event core.search_mysql_delete_index_before + * @var array sql_queries Array with queries for deleting the search index + * @var array stats Array with statistics of the current index (read only) + * @since 3.2.3-RC1 + */ + $vars = array( + 'sql_queries', + 'stats', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_mysql_delete_index_before', compact($vars))); + + foreach ($sql_queries as $sql_query) + { + $this->db->sql_query($sql_query); + } + + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Returns true if both FULLTEXT indexes exist + */ + public function index_created() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return isset($this->stats['post_subject']) && isset($this->stats['post_content']) && isset($this->stats['post_text']); + } + + /** + * Returns an associative array containing information about the indexes + */ + public function index_stats() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return array( + $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + ); + } + + /** + * Computes the stats and store them in the $this->stats associative array + */ + protected function get_stats() + { + if (strpos($this->db->get_sql_layer(), 'mysql') === false) + { + $this->stats = array(); + return; + } + + $sql = 'SHOW INDEX + FROM ' . POSTS_TABLE; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // deal with older MySQL versions which didn't use Index_type + $index_type = (isset($row['Index_type'])) ? $row['Index_type'] : $row['Comment']; + + if ($index_type == 'FULLTEXT') + { + if ($row['Key_name'] == 'post_subject') + { + $this->stats['post_subject'] = $row; + } + else if ($row['Key_name'] == 'post_text') + { + $this->stats['post_text'] = $row; + } + else if ($row['Key_name'] == 'post_content') + { + $this->stats['post_content'] = $row; + } + } + } + $this->db->sql_freeresult($result); + + $this->stats['total_posts'] = empty($this->stats) ? 0 : $this->db->get_estimated_row_count(POSTS_TABLE); + } + + /** + * Display a note, that UTF-8 support is not available with certain versions of PHP + * + * @return associative array containing template and config variables + */ + public function acp() + { + $tpl = ' +
+

' . $this->user->lang['FULLTEXT_MYSQL_MIN_SEARCH_CHARS_EXPLAIN'] . '
+
' . $this->config['fulltext_mysql_min_word_len'] . '
+
+
+

' . $this->user->lang['FULLTEXT_MYSQL_MAX_SEARCH_CHARS_EXPLAIN'] . '
+
' . $this->config['fulltext_mysql_max_word_len'] . '
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => array() + ); + } +} diff --git a/phpbb/search/fulltext_native.php b/phpbb/search/fulltext_native.php new file mode 100644 index 0000000..c83de75 --- /dev/null +++ b/phpbb/search/fulltext_native.php @@ -0,0 +1,2062 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search; + +/** +* phpBB's own db driven fulltext search, version 2 +*/ +class fulltext_native extends \phpbb\search\base +{ + const UTF8_HANGUL_FIRST = "\xEA\xB0\x80"; + const UTF8_HANGUL_LAST = "\xED\x9E\xA3"; + const UTF8_CJK_FIRST = "\xE4\xB8\x80"; + const UTF8_CJK_LAST = "\xE9\xBE\xBB"; + const UTF8_CJK_B_FIRST = "\xF0\xA0\x80\x80"; + const UTF8_CJK_B_LAST = "\xF0\xAA\x9B\x96"; + + /** + * Associative array holding index stats + * @var array + */ + protected $stats = array(); + + /** + * Associative array stores the min and max word length to be searched + * @var array + */ + protected $word_length = array(); + + /** + * Contains tidied search query. + * Operators are prefixed in search query and common words excluded + * @var string + */ + protected $search_query; + + /** + * Contains common words. + * Common words are words with length less/more than min/max length + * @var array + */ + protected $common_words = array(); + + /** + * Post ids of posts containing words that are to be included + * @var array + */ + protected $must_contain_ids = array(); + + /** + * Post ids of posts containing words that should not be included + * @var array + */ + protected $must_not_contain_ids = array(); + + /** + * Post ids of posts containing at least one word that needs to be excluded + * @var array + */ + protected $must_exclude_one_ids = array(); + + /** + * Relative path to board root + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP Extension + * @var string + */ + protected $php_ext; + + /** + * Config object + * @var \phpbb\config\config + */ + protected $config; + + /** + * Database connection + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * phpBB event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $phpbb_dispatcher; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Initialises the fulltext_native search backend with min/max word length + * + * @param boolean|string &$error is passed by reference and should either be set to false on success or an error message on failure + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object + */ + public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $phpEx; + $this->config = $config; + $this->db = $db; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->user = $user; + + $this->word_length = array('min' => (int) $this->config['fulltext_native_min_chars'], 'max' => (int) $this->config['fulltext_native_max_chars']); + + /** + * Load the UTF tools + */ + if (!function_exists('utf8_decode_ncr')) + { + include($this->phpbb_root_path . 'includes/utf/utf_tools.' . $this->php_ext); + } + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'phpBB Native Fulltext'; + } + + /** + * Returns the search_query + * + * @return string search query + */ + public function get_search_query() + { + return $this->search_query; + } + + /** + * Returns the common_words array + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return $this->common_words; + } + + /** + * Returns the word_length array + * + * @return array min and max word length for searching + */ + public function get_word_length() + { + return $this->word_length; + } + + /** + * This function fills $this->search_query with the cleaned user search query + * + * If $terms is 'any' then the words will be extracted from the search query + * and combined with | inside brackets. They will afterwards be treated like + * an standard search query. + * + * Then it analyses the query and fills the internal arrays $must_not_contain_ids, + * $must_contain_ids and $must_exclude_one_ids which are later used by keyword_search() + * + * @param string $keywords contains the search query string as entered by the user + * @param string $terms is either 'all' (use search query as entered, default words to 'must be contained in post') + * or 'any' (find all posts containing at least one of the given words) + * @return boolean false if no valid keywords were found and otherwise true + */ + public function split_keywords($keywords, $terms) + { + $tokens = '+-|()* '; + + $keywords = trim($this->cleanup($keywords, $tokens)); + + // allow word|word|word without brackets + if ((strpos($keywords, ' ') === false) && (strpos($keywords, '|') !== false) && (strpos($keywords, '(') === false)) + { + $keywords = '(' . $keywords . ')'; + } + + $open_bracket = $space = false; + for ($i = 0, $n = strlen($keywords); $i < $n; $i++) + { + if ($open_bracket !== false) + { + switch ($keywords[$i]) + { + case ')': + if ($open_bracket + 1 == $i) + { + $keywords[$i - 1] = '|'; + $keywords[$i] = '|'; + } + $open_bracket = false; + break; + case '(': + $keywords[$i] = '|'; + break; + case '+': + case '-': + case ' ': + $keywords[$i] = '|'; + break; + case '*': + // $i can never be 0 here since $open_bracket is initialised to false + if (strpos($tokens, $keywords[$i - 1]) !== false && ($i + 1 === $n || strpos($tokens, $keywords[$i + 1]) !== false)) + { + $keywords[$i] = '|'; + } + break; + } + } + else + { + switch ($keywords[$i]) + { + case ')': + $keywords[$i] = ' '; + break; + case '(': + $open_bracket = $i; + $space = false; + break; + case '|': + $keywords[$i] = ' '; + break; + case '-': + case '+': + $space = $keywords[$i]; + break; + case ' ': + if ($space !== false) + { + $keywords[$i] = $space; + } + break; + default: + $space = false; + } + } + } + + if ($open_bracket !== false) + { + $keywords .= ')'; + } + + $match = array( + '# +#', + '#\|\|+#', + '#(\+|\-)(?:\+|\-)+#', + '#\(\|#', + '#\|\)#', + ); + $replace = array( + ' ', + '|', + '$1', + '(', + ')', + ); + + $keywords = preg_replace($match, $replace, $keywords); + $num_keywords = count(explode(' ', $keywords)); + + // We limit the number of allowed keywords to minimize load on the database + if ($this->config['max_num_search_keywords'] && $num_keywords > $this->config['max_num_search_keywords']) + { + trigger_error($this->user->lang('MAX_NUM_SEARCH_KEYWORDS_REFINE', (int) $this->config['max_num_search_keywords'], $num_keywords)); + } + + // $keywords input format: each word separated by a space, words in a bracket are not separated + + // the user wants to search for any word, convert the search query + if ($terms == 'any') + { + $words = array(); + + preg_match_all('#([^\\s+\\-|()]+)(?:$|[\\s+\\-|()])#u', $keywords, $words); + if (count($words[1])) + { + $keywords = '(' . implode('|', $words[1]) . ')'; + } + } + + // Remove non trailing wildcards from each word to prevent a full table scan (it's now using the database index) + $match = '#\*(?!$|\s)#'; + $replace = '$1'; + $keywords = preg_replace($match, $replace, $keywords); + + // Only allow one wildcard in the search query to limit the database load + $match = '#\*#'; + $replace = '$1'; + $count_wildcards = substr_count($keywords, '*'); + + // Reverse the string to remove all wildcards except the first one + $keywords = strrev(preg_replace($match, $replace, strrev($keywords), $count_wildcards - 1)); + unset($count_wildcards); + + // set the search_query which is shown to the user + $this->search_query = $keywords; + + $exact_words = array(); + preg_match_all('#([^\\s+\\-|()]+)(?:$|[\\s+\\-|()])#u', $keywords, $exact_words); + $exact_words = $exact_words[1]; + + $common_ids = $words = array(); + + if (count($exact_words)) + { + $sql = 'SELECT word_id, word_text, word_common + FROM ' . SEARCH_WORDLIST_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_text', $exact_words) . ' + ORDER BY word_count ASC'; + $result = $this->db->sql_query($sql); + + // store an array of words and ids, remove common words + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['word_common']) + { + $this->common_words[] = $row['word_text']; + $common_ids[$row['word_text']] = (int) $row['word_id']; + continue; + } + + $words[$row['word_text']] = (int) $row['word_id']; + } + $this->db->sql_freeresult($result); + } + + // Handle +, - without preceeding whitespace character + $match = array('#(\S)\+#', '#(\S)-#'); + $replace = array('$1 +', '$1 +'); + + $keywords = preg_replace($match, $replace, $keywords); + + // now analyse the search query, first split it using the spaces + $query = explode(' ', $keywords); + + $this->must_contain_ids = array(); + $this->must_not_contain_ids = array(); + $this->must_exclude_one_ids = array(); + + foreach ($query as $word) + { + if (empty($word)) + { + continue; + } + + // words which should not be included + if ($word[0] == '-') + { + $word = substr($word, 1); + + // a group of which at least one may not be in the resulting posts + if ($word[0] == '(') + { + $word = array_unique(explode('|', substr($word, 1, -1))); + $mode = 'must_exclude_one'; + } + // one word which should not be in the resulting posts + else + { + $mode = 'must_not_contain'; + } + $ignore_no_id = true; + } + // words which have to be included + else + { + // no prefix is the same as a +prefix + if ($word[0] == '+') + { + $word = substr($word, 1); + } + + // a group of words of which at least one word should be in every resulting post + if ($word[0] == '(') + { + $word = array_unique(explode('|', substr($word, 1, -1))); + } + $ignore_no_id = false; + $mode = 'must_contain'; + } + + if (empty($word)) + { + continue; + } + + // if this is an array of words then retrieve an id for each + if (is_array($word)) + { + $non_common_words = array(); + $id_words = array(); + foreach ($word as $i => $word_part) + { + if (strpos($word_part, '*') !== false) + { + $len = utf8_strlen(str_replace('*', '', $word_part)); + if ($len >= $this->word_length['min'] && $len <= $this->word_length['max']) + { + $id_words[] = '\'' . $this->db->sql_escape(str_replace('*', '%', $word_part)) . '\''; + $non_common_words[] = $word_part; + } + else + { + $this->common_words[] = $word_part; + } + } + else if (isset($words[$word_part])) + { + $id_words[] = $words[$word_part]; + $non_common_words[] = $word_part; + } + else + { + $len = utf8_strlen($word_part); + if ($len < $this->word_length['min'] || $len > $this->word_length['max']) + { + $this->common_words[] = $word_part; + } + } + } + if (count($id_words)) + { + sort($id_words); + if (count($id_words) > 1) + { + $this->{$mode . '_ids'}[] = $id_words; + } + else + { + $mode = ($mode == 'must_exclude_one') ? 'must_not_contain' : $mode; + $this->{$mode . '_ids'}[] = $id_words[0]; + } + } + // throw an error if we shall not ignore unexistant words + else if (!$ignore_no_id && count($non_common_words)) + { + trigger_error(sprintf($this->user->lang['WORDS_IN_NO_POST'], implode($this->user->lang['COMMA_SEPARATOR'], $non_common_words))); + } + unset($non_common_words); + } + // else we only need one id + else if (($wildcard = strpos($word, '*') !== false) || isset($words[$word])) + { + if ($wildcard) + { + $len = utf8_strlen(str_replace('*', '', $word)); + if ($len >= $this->word_length['min'] && $len <= $this->word_length['max']) + { + $this->{$mode . '_ids'}[] = '\'' . $this->db->sql_escape(str_replace('*', '%', $word)) . '\''; + } + else + { + $this->common_words[] = $word; + } + } + else + { + $this->{$mode . '_ids'}[] = $words[$word]; + } + } + else + { + if (!isset($common_ids[$word])) + { + $len = utf8_strlen($word); + if ($len < $this->word_length['min'] || $len > $this->word_length['max']) + { + $this->common_words[] = $word; + } + } + } + } + + // Return true if all words are not common words + if (count($exact_words) - count($this->common_words) > 0) + { + return true; + } + return false; + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No keywords? No posts. + if (empty($this->search_query)) + { + return false; + } + + // we can't search for negatives only + if (empty($this->must_contain_ids)) + { + return false; + } + + $must_contain_ids = $this->must_contain_ids; + $must_not_contain_ids = $this->must_not_contain_ids; + $must_exclude_one_ids = $this->must_exclude_one_ids; + + sort($must_contain_ids); + sort($must_not_contain_ids); + sort($must_exclude_one_ids); + + // generate a search_key from all the options to identify the results + $search_key_array = array( + serialize($must_contain_ids), + serialize($must_not_contain_ids), + serialize($must_exclude_one_ids), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary), + $author_name, + ); + + /** + * Allow changing the search_key for cached results + * + * @event core.search_native_by_keyword_modify_search_key + * @var array search_key_array Array with search parameters to generate the search_key + * @var array must_contain_ids Array with post ids of posts containing words that are to be included + * @var array must_not_contain_ids Array with post ids of posts containing words that should not be included + * @var array must_exclude_one_ids Array with post ids of posts containing at least one word that needs to be excluded + * @var string type Searching type ('posts', 'topics') + * @var string fields Searching fields ('titleonly', 'msgonly', 'firstpost', 'all') + * @var string terms Searching terms ('all', 'any') + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sort_key The sort type used from the possible sort types + * @var int topic_id Limit the search to this topic_id only + * @var array ex_fid_ary Which forums not to search on + * @var string post_visibility Post visibility data + * @var array author_ary Array of user_id containing the users to filter the results to + * @since 3.1.7-RC1 + */ + $vars = array( + 'search_key_array', + 'must_contain_ids', + 'must_not_contain_ids', + 'must_exclude_one_ids', + 'type', + 'fields', + 'terms', + 'sort_days', + 'sort_key', + 'topic_id', + 'ex_fid_ary', + 'post_visibility', + 'author_ary', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_native_by_keyword_modify_search_key', compact($vars))); + + $search_key = md5(implode('#', $search_key_array)); + + // try reading the results from cache + $total_results = 0; + if ($this->obtain_ids($search_key, $total_results, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $total_results; + } + + $id_ary = array(); + + $sql_where = array(); + $m_num = 0; + $w_num = 0; + + $sql_array = array( + 'SELECT' => ($type == 'posts') ? 'p.post_id' : 'p.topic_id', + 'FROM' => array( + SEARCH_WORDMATCH_TABLE => array(), + SEARCH_WORDLIST_TABLE => array(), + ), + 'LEFT_JOIN' => array(array( + 'FROM' => array(POSTS_TABLE => 'p'), + 'ON' => 'm0.post_id = p.post_id', + )), + ); + + $title_match = ''; + $left_join_topics = false; + $group_by = true; + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $title_match = 'title_match = 1'; + $group_by = false; + // no break + case 'firstpost': + $left_join_topics = true; + $sql_where[] = 'p.post_id = t.topic_first_post_id'; + break; + + case 'msgonly': + $title_match = 'title_match = 0'; + $group_by = false; + break; + } + + if ($type == 'topics') + { + $left_join_topics = true; + $group_by = true; + } + + /** + * @todo Add a query optimizer (handle stuff like "+(4|3) +4") + */ + + foreach ($this->must_contain_ids as $subquery) + { + if (is_array($subquery)) + { + $group_by = true; + + $word_id_sql = array(); + $word_ids = array(); + foreach ($subquery as $id) + { + if (is_string($id)) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), + 'ON' => "w$w_num.word_text LIKE $id" + ); + $word_ids[] = "w$w_num.word_id"; + + $w_num++; + } + else + { + $word_ids[] = $id; + } + } + + $sql_where[] = $this->db->sql_in_set("m$m_num.word_id", $word_ids); + + unset($word_id_sql); + unset($word_ids); + } + else if (is_string($subquery)) + { + $sql_array['FROM'][SEARCH_WORDLIST_TABLE][] = 'w' . $w_num; + + $sql_where[] = "w$w_num.word_text LIKE $subquery"; + $sql_where[] = "m$m_num.word_id = w$w_num.word_id"; + + $group_by = true; + $w_num++; + } + else + { + $sql_where[] = "m$m_num.word_id = $subquery"; + } + + $sql_array['FROM'][SEARCH_WORDMATCH_TABLE][] = 'm' . $m_num; + + if ($title_match) + { + $sql_where[] = "m$m_num.$title_match"; + } + + if ($m_num != 0) + { + $sql_where[] = "m$m_num.post_id = m0.post_id"; + } + $m_num++; + } + + foreach ($this->must_not_contain_ids as $key => $subquery) + { + if (is_string($subquery)) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), + 'ON' => "w$w_num.word_text LIKE $subquery" + ); + + $this->must_not_contain_ids[$key] = "w$w_num.word_id"; + + $group_by = true; + $w_num++; + } + } + + if (count($this->must_not_contain_ids)) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), + 'ON' => $this->db->sql_in_set("m$m_num.word_id", $this->must_not_contain_ids) . (($title_match) ? " AND m$m_num.$title_match" : '') . " AND m$m_num.post_id = m0.post_id" + ); + + $sql_where[] = "m$m_num.word_id IS NULL"; + $m_num++; + } + + foreach ($this->must_exclude_one_ids as $ids) + { + $is_null_joins = array(); + foreach ($ids as $id) + { + if (is_string($id)) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDLIST_TABLE => 'w' . $w_num), + 'ON' => "w$w_num.word_text LIKE $id" + ); + $id = "w$w_num.word_id"; + + $group_by = true; + $w_num++; + } + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(SEARCH_WORDMATCH_TABLE => 'm' . $m_num), + 'ON' => "m$m_num.word_id = $id AND m$m_num.post_id = m0.post_id" . (($title_match) ? " AND m$m_num.$title_match" : '') + ); + $is_null_joins[] = "m$m_num.word_id IS NULL"; + + $m_num++; + } + $sql_where[] = '(' . implode(' OR ', $is_null_joins) . ')'; + } + + $sql_where[] = $post_visibility; + + $search_query = $this->search_query; + $must_exclude_one_ids = $this->must_exclude_one_ids; + $must_not_contain_ids = $this->must_not_contain_ids; + $must_contain_ids = $this->must_contain_ids; + + /** + * Allow changing the query used for counting for posts using fulltext_native + * + * @event core.search_native_keywords_count_query_before + * @var string search_query The parsed keywords used for this search + * @var array must_not_contain_ids Ids that cannot be taken into account for the results + * @var array must_exclude_one_ids Ids that cannot be on the results + * @var array must_contain_ids Ids that must be on the results + * @var int total_results The previous result count for the format of the query + * Set to 0 to force a re-count + * @var array sql_array The data on how to search in the DB at this point + * @var bool left_join_topics Whether or not TOPICS_TABLE should be CROSS JOIN'ED + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name An extra username to search on (!empty(author_ary) must be true, to be relevant) + * @var array ex_fid_ary Which forums not to search on + * @var int topic_id Limit the search to this topic_id only + * @var string sql_sort_table Extra tables to include in the SQL query. + * Used in conjunction with sql_sort_join + * @var string sql_sort_join SQL conditions to join all the tables used together. + * Used in conjunction with sql_sort_table + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sql_where An array of the current WHERE clause conditions + * @var string sql_match Which columns to do the search on + * @var string sql_match_where Extra conditions to use to properly filter the matching process + * @var bool group_by Whether or not the SQL query requires a GROUP BY for the elements in the SELECT clause + * @var string sort_by_sql The possible predefined sort types + * @var string sort_key The sort type used from the possible sort types + * @var string sort_dir "a" for ASC or "d" dor DESC for the sort order used + * @var string sql_sort The result SQL when processing sort_by_sql + sort_key + sort_dir + * @var int start How many posts to skip in the search results (used for pagination) + * @since 3.1.5-RC1 + */ + $vars = array( + 'search_query', + 'must_not_contain_ids', + 'must_exclude_one_ids', + 'must_contain_ids', + 'total_results', + 'sql_array', + 'left_join_topics', + 'author_ary', + 'author_name', + 'ex_fid_ary', + 'topic_id', + 'sql_sort_table', + 'sql_sort_join', + 'sort_days', + 'sql_where', + 'sql_match', + 'sql_match_where', + 'group_by', + 'sort_by_sql', + 'sort_key', + 'sort_dir', + 'sql_sort', + 'start', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_native_keywords_count_query_before', compact($vars))); + + if ($topic_id) + { + $sql_where[] = 'p.topic_id = ' . $topic_id; + } + + if (count($author_ary)) + { + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); + } + $sql_where[] = $sql_author; + } + + if (count($ex_fid_ary)) + { + $sql_where[] = $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true); + } + + if ($sort_days) + { + $sql_where[] = 'p.post_time >= ' . (time() - ($sort_days * 86400)); + } + + $sql_array['WHERE'] = implode(' AND ', $sql_where); + + $is_mysql = false; + // if the total result count is not cached yet, retrieve it from the db + if (!$total_results) + { + $sql = ''; + $sql_array_count = $sql_array; + + if ($left_join_topics) + { + $sql_array_count['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_TABLE => 't'), + 'ON' => 'p.topic_id = t.topic_id' + ); + } + + switch ($this->db->get_sql_layer()) + { + case 'mysql4': + case 'mysqli': + + // 3.x does not support SQL_CALC_FOUND_ROWS + // $sql_array['SELECT'] = 'SQL_CALC_FOUND_ROWS ' . $sql_array['SELECT']; + $is_mysql = true; + + break; + + case 'sqlite3': + $sql_array_count['SELECT'] = ($type == 'posts') ? 'DISTINCT p.post_id' : 'DISTINCT p.topic_id'; + $sql = 'SELECT COUNT(' . (($type == 'posts') ? 'post_id' : 'topic_id') . ') as total_results + FROM (' . $this->db->sql_build_query('SELECT', $sql_array_count) . ')'; + + // no break + + default: + $sql_array_count['SELECT'] = ($type == 'posts') ? 'COUNT(DISTINCT p.post_id) AS total_results' : 'COUNT(DISTINCT p.topic_id) AS total_results'; + $sql = (!$sql) ? $this->db->sql_build_query('SELECT', $sql_array_count) : $sql; + + $result = $this->db->sql_query($sql); + $total_results = (int) $this->db->sql_fetchfield('total_results'); + $this->db->sql_freeresult($result); + + if (!$total_results) + { + return false; + } + break; + } + + unset($sql_array_count, $sql); + } + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + + switch ($sql_sort[0]) + { + case 'u': + $sql_array['FROM'][USERS_TABLE] = 'u'; + $sql_where[] = 'u.user_id = p.poster_id '; + break; + + case 't': + $left_join_topics = true; + break; + + case 'f': + $sql_array['FROM'][FORUMS_TABLE] = 'f'; + $sql_where[] = 'f.forum_id = p.forum_id'; + break; + } + + if ($left_join_topics) + { + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_TABLE => 't'), + 'ON' => 'p.topic_id = t.topic_id' + ); + } + + // if using mysql and the total result count is not calculated yet, get it from the db + if (!$total_results && $is_mysql) + { + // Also count rows for the query as if there was not LIMIT. Add SQL_CALC_FOUND_ROWS to SQL + $sql_array['SELECT'] = 'SQL_CALC_FOUND_ROWS ' . $sql_array['SELECT']; + } + + $sql_array['WHERE'] = implode(' AND ', $sql_where); + $sql_array['GROUP_BY'] = ($group_by) ? (($type == 'posts') ? 'p.post_id' : 'p.topic_id') . ', ' . $sort_by_sql[$sort_key] : ''; + $sql_array['ORDER_BY'] = $sql_sort; + + unset($sql_where, $sql_sort, $group_by); + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; + } + $this->db->sql_freeresult($result); + + if (!$total_results && $is_mysql) + { + // Get the number of results as calculated by MySQL + $sql_count = 'SELECT FOUND_ROWS() as total_results'; + $result = $this->db->sql_query($sql_count); + $total_results = (int) $this->db->sql_fetchfield('total_results'); + $this->db->sql_freeresult($result); + + if (!$total_results) + { + return false; + } + } + + if ($start >= $total_results) + { + $start = floor(($total_results - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[(($type == 'posts') ? 'post_id' : 'topic_id')]; + } + $this->db->sql_freeresult($result); + + } + + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, $this->search_query, $author_ary, $total_results, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $total_results; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No author? No posts + if (!count($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key_array = array( + '', + $type, + ($firstpost_only) ? 'firstpost' : '', + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary), + $author_name, + ); + + /** + * Allow changing the search_key for cached results + * + * @event core.search_native_by_author_modify_search_key + * @var array search_key_array Array with search parameters to generate the search_key + * @var string type Searching type ('posts', 'topics') + * @var boolean firstpost_only Flag indicating if only topic starting posts are considered + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sort_key The sort type used from the possible sort types + * @var int topic_id Limit the search to this topic_id only + * @var array ex_fid_ary Which forums not to search on + * @var string post_visibility Post visibility data + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name The username to search on + * @since 3.1.7-RC1 + */ + $vars = array( + 'search_key_array', + 'type', + 'firstpost_only', + 'sort_days', + 'sort_key', + 'topic_id', + 'ex_fid_ary', + 'post_visibility', + 'author_ary', + 'author_name', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_native_by_author_modify_search_key', compact($vars))); + + $search_key = md5(implode('#', $search_key_array)); + + // try reading the results from cache + $total_results = 0; + if ($this->obtain_ids($search_key, $total_results, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $total_results; + } + + $id_ary = array(); + + // Create some display specific sql strings + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); + } + $sql_fora = (count($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; + $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; + $post_visibility = ($post_visibility) ? ' AND ' . $post_visibility : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ' AND u.user_id = p.poster_id '; + break; + + case 't': + $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + $select = ($type == 'posts') ? 'p.post_id' : 't.topic_id'; + $is_mysql = false; + + /** + * Allow changing the query used to search for posts by author in fulltext_native + * + * @event core.search_native_author_count_query_before + * @var int total_results The previous result count for the format of the query. + * Set to 0 to force a re-count + * @var string type The type of search being made + * @var string select SQL SELECT clause for what to get + * @var string sql_sort_table CROSS JOIN'ed table to allow doing the sort chosen + * @var string sql_sort_join Condition to define how to join the CROSS JOIN'ed table specifyed in sql_sort_table + * @var array sql_author SQL WHERE condition for the post author ids + * @var int topic_id Limit the search to this topic_id only + * @var string sort_by_sql The possible predefined sort types + * @var string sort_key The sort type used from the possible sort types + * @var string sort_dir "a" for ASC or "d" dor DESC for the sort order used + * @var string sql_sort The result SQL when processing sort_by_sql + sort_key + sort_dir + * @var string sort_days Time, in days, that the oldest post showing can have + * @var string sql_time The SQL to search on the time specifyed by sort_days + * @var bool firstpost_only Wether or not to search only on the first post of the topics + * @var string sql_firstpost The SQL used in the WHERE claused to filter by firstpost. + * @var array ex_fid_ary Forum ids that must not be searched on + * @var array sql_fora SQL query for ex_fid_ary + * @var int start How many posts to skip in the search results (used for pagination) + * @since 3.1.5-RC1 + */ + $vars = array( + 'total_results', + 'type', + 'select', + 'sql_sort_table', + 'sql_sort_join', + 'sql_author', + 'topic_id', + 'sort_by_sql', + 'sort_key', + 'sort_dir', + 'sql_sort', + 'sort_days', + 'sql_time', + 'firstpost_only', + 'sql_firstpost', + 'ex_fid_ary', + 'sql_fora', + 'start', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_native_author_count_query_before', compact($vars))); + + // If the cache was completely empty count the results + if (!$total_results) + { + switch ($this->db->get_sql_layer()) + { + case 'mysql4': + case 'mysqli': +// $select = 'SQL_CALC_FOUND_ROWS ' . $select; + $is_mysql = true; + break; + + default: + if ($type == 'posts') + { + $sql = 'SELECT COUNT(p.post_id) as total_results + FROM ' . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $post_visibility + $sql_fora + $sql_time"; + } + else + { + if ($this->db->get_sql_layer() == 'sqlite3') + { + $sql = 'SELECT COUNT(topic_id) as total_results + FROM (SELECT DISTINCT t.topic_id'; + } + else + { + $sql = 'SELECT COUNT(DISTINCT t.topic_id) as total_results'; + } + + $sql .= ' FROM ' . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $post_visibility + $sql_fora + AND t.topic_id = p.topic_id + $sql_time" . ($this->db->get_sql_layer() == 'sqlite3' ? ')' : ''); + } + $result = $this->db->sql_query($sql); + + $total_results = (int) $this->db->sql_fetchfield('total_results'); + $this->db->sql_freeresult($result); + + if (!$total_results) + { + return false; + } + break; + } + } + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT $select + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t' : '') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $post_visibility + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT $select + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $post_visibility + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, " . $sort_by_sql[$sort_key] . ' + ORDER BY ' . $sql_sort; + $field = 'topic_id'; + } + + // Only read one block of posts from the db and then cache it + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + if (!$total_results && $is_mysql) + { + // Count rows for the executed queries. Replace $select within $sql with SQL_CALC_FOUND_ROWS, and run it. + $sql_calc = str_replace('SELECT ' . $select, 'SELECT SQL_CALC_FOUND_ROWS ' . $select, $sql); + + $result = $this->db->sql_query($sql_calc); + $this->db->sql_freeresult($result); + + $sql_count = 'SELECT FOUND_ROWS() as total_results'; + $result = $this->db->sql_query($sql_count); + $total_results = (int) $this->db->sql_fetchfield('total_results'); + $this->db->sql_freeresult($result); + + if (!$total_results) + { + return false; + } + } + + if ($start >= $total_results) + { + $start = floor(($total_results - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + } + + if (count($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $total_results, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $total_results; + } + return false; + } + + /** + * Split a text into words of a given length + * + * The text is converted to UTF-8, cleaned up, and split. Then, words that + * conform to the defined length range are returned in an array. + * + * NOTE: duplicates are NOT removed from the return array + * + * @param string $text Text to split, encoded in UTF-8 + * @return array Array of UTF-8 words + */ + public function split_message($text) + { + $match = $words = array(); + + /** + * Taken from the original code + */ + // Do not index code + $match[] = '#\[code(?:=.*?)?(\:?[0-9a-z]{5,})\].*?\[\/code(\:?[0-9a-z]{5,})\]#is'; + // BBcode + $match[] = '#\[\/?[a-z0-9\*\+\-]+(?:=.*?)?(?::[a-z])?(\:?[0-9a-z]{5,})\]#'; + + $min = $this->word_length['min']; + + $isset_min = $min - 1; + + /** + * Clean up the string, remove HTML tags, remove BBCodes + */ + $word = strtok($this->cleanup(preg_replace($match, ' ', strip_tags($text)), -1), ' '); + + while (strlen($word)) + { + if (strlen($word) > 255 || strlen($word) <= $isset_min) + { + /** + * Words longer than 255 bytes are ignored. This will have to be + * changed whenever we change the length of search_wordlist.word_text + * + * Words shorter than $isset_min bytes are ignored, too + */ + $word = strtok(' '); + continue; + } + + $len = utf8_strlen($word); + + /** + * Test whether the word is too short to be indexed. + * + * Note that this limit does NOT apply to CJK and Hangul + */ + if ($len < $min) + { + /** + * Note: this could be optimized. If the codepoint is lower than Hangul's range + * we know that it will also be lower than CJK ranges + */ + if ((strncmp($word, self::UTF8_HANGUL_FIRST, 3) < 0 || strncmp($word, self::UTF8_HANGUL_LAST, 3) > 0) + && (strncmp($word, self::UTF8_CJK_FIRST, 3) < 0 || strncmp($word, self::UTF8_CJK_LAST, 3) > 0) + && (strncmp($word, self::UTF8_CJK_B_FIRST, 4) < 0 || strncmp($word, self::UTF8_CJK_B_LAST, 4) > 0)) + { + $word = strtok(' '); + continue; + } + } + + $words[] = $word; + $word = strtok(' '); + } + + return $words; + } + + /** + * Updates wordlist and wordmatch tables when a message is posted or changed + * + * @param string $mode Contains the post mode: edit, post, reply, quote + * @param int $post_id The id of the post which is modified/created + * @param string &$message New or updated post content + * @param string &$subject New or updated post subject + * @param int $poster_id Post author's user id + * @param int $forum_id The id of the forum in which the post is located + */ + public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + if (!$this->config['fulltext_native_load_upd']) + { + /** + * The search indexer is disabled, return + */ + return; + } + + // Split old and new post/subject to obtain array of 'words' + $split_text = $this->split_message($message); + $split_title = $this->split_message($subject); + + $cur_words = array('post' => array(), 'title' => array()); + + $words = array(); + if ($mode == 'edit') + { + $words['add']['post'] = array(); + $words['add']['title'] = array(); + $words['del']['post'] = array(); + $words['del']['title'] = array(); + + $sql = 'SELECT w.word_id, w.word_text, m.title_match + FROM ' . SEARCH_WORDLIST_TABLE . ' w, ' . SEARCH_WORDMATCH_TABLE . " m + WHERE m.post_id = $post_id + AND w.word_id = m.word_id"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + $which = ($row['title_match']) ? 'title' : 'post'; + $cur_words[$which][$row['word_text']] = $row['word_id']; + } + $this->db->sql_freeresult($result); + + $words['add']['post'] = array_diff($split_text, array_keys($cur_words['post'])); + $words['add']['title'] = array_diff($split_title, array_keys($cur_words['title'])); + $words['del']['post'] = array_diff(array_keys($cur_words['post']), $split_text); + $words['del']['title'] = array_diff(array_keys($cur_words['title']), $split_title); + } + else + { + $words['add']['post'] = $split_text; + $words['add']['title'] = $split_title; + $words['del']['post'] = array(); + $words['del']['title'] = array(); + } + + /** + * Event to modify method arguments and words before the native search index is updated + * + * @event core.search_native_index_before + * @var string mode Contains the post mode: edit, post, reply, quote + * @var int post_id The id of the post which is modified/created + * @var string message New or updated post content + * @var string subject New or updated post subject + * @var int poster_id Post author's user id + * @var int forum_id The id of the forum in which the post is located + * @var array words Grouped lists of words added to or remove from the index + * @var array split_text Array of words from the message + * @var array split_title Array of words from the title + * @var array cur_words Array of words currently in the index for comparing to new words + * when mode is edit. Empty for other modes. + * @since 3.2.3-RC1 + */ + $vars = array( + 'mode', + 'post_id', + 'message', + 'subject', + 'poster_id', + 'forum_id', + 'words', + 'split_text', + 'split_title', + 'cur_words', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_native_index_before', compact($vars))); + + unset($split_text); + unset($split_title); + + // Get unique words from the above arrays + $unique_add_words = array_unique(array_merge($words['add']['post'], $words['add']['title'])); + + // We now have unique arrays of all words to be added and removed and + // individual arrays of added and removed words for text and title. What + // we need to do now is add the new words (if they don't already exist) + // and then add (or remove) matches between the words and this post + if (count($unique_add_words)) + { + $sql = 'SELECT word_id, word_text + FROM ' . SEARCH_WORDLIST_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_text', $unique_add_words); + $result = $this->db->sql_query($sql); + + $word_ids = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $word_ids[$row['word_text']] = $row['word_id']; + } + $this->db->sql_freeresult($result); + $new_words = array_diff($unique_add_words, array_keys($word_ids)); + + $this->db->sql_transaction('begin'); + if (count($new_words)) + { + $sql_ary = array(); + + foreach ($new_words as $word) + { + $sql_ary[] = array('word_text' => (string) $word, 'word_count' => 0); + } + $this->db->sql_return_on_error(true); + $this->db->sql_multi_insert(SEARCH_WORDLIST_TABLE, $sql_ary); + $this->db->sql_return_on_error(false); + } + unset($new_words, $sql_ary); + } + else + { + $this->db->sql_transaction('begin'); + } + + // now update the search match table, remove links to removed words and add links to new words + foreach ($words['del'] as $word_in => $word_ary) + { + $title_match = ($word_in == 'title') ? 1 : 0; + + if (count($word_ary)) + { + $sql_in = array(); + foreach ($word_ary as $word) + { + $sql_in[] = $cur_words[$word_in][$word]; + } + + $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . ' + AND post_id = ' . intval($post_id) . " + AND title_match = $title_match"; + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_count = word_count - 1 + WHERE ' . $this->db->sql_in_set('word_id', $sql_in) . ' + AND word_count > 0'; + $this->db->sql_query($sql); + + unset($sql_in); + } + } + + $this->db->sql_return_on_error(true); + foreach ($words['add'] as $word_in => $word_ary) + { + $title_match = ($word_in == 'title') ? 1 : 0; + + if (count($word_ary)) + { + $sql = 'INSERT INTO ' . SEARCH_WORDMATCH_TABLE . ' (post_id, word_id, title_match) + SELECT ' . (int) $post_id . ', word_id, ' . (int) $title_match . ' + FROM ' . SEARCH_WORDLIST_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_text', $word_ary); + $this->db->sql_query($sql); + + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_count = word_count + 1 + WHERE ' . $this->db->sql_in_set('word_text', $word_ary); + $this->db->sql_query($sql); + } + } + $this->db->sql_return_on_error(false); + + $this->db->sql_transaction('commit'); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache(array_unique(array_merge($words['add']['post'], $words['add']['title'], $words['del']['post'], $words['del']['title'])), array($poster_id)); + + unset($unique_add_words); + unset($words); + unset($cur_words); + } + + /** + * Removes entries from the wordmatch table for the specified post_ids + */ + public function index_remove($post_ids, $author_ids, $forum_ids) + { + if (count($post_ids)) + { + $sql = 'SELECT w.word_id, w.word_text, m.title_match + FROM ' . SEARCH_WORDMATCH_TABLE . ' m, ' . SEARCH_WORDLIST_TABLE . ' w + WHERE ' . $this->db->sql_in_set('m.post_id', $post_ids) . ' + AND w.word_id = m.word_id'; + $result = $this->db->sql_query($sql); + + $message_word_ids = $title_word_ids = $word_texts = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + if ($row['title_match']) + { + $title_word_ids[] = $row['word_id']; + } + else + { + $message_word_ids[] = $row['word_id']; + } + $word_texts[] = $row['word_text']; + } + $this->db->sql_freeresult($result); + + if (count($title_word_ids)) + { + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_count = word_count - 1 + WHERE ' . $this->db->sql_in_set('word_id', $title_word_ids) . ' + AND word_count > 0'; + $this->db->sql_query($sql); + } + + if (count($message_word_ids)) + { + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_count = word_count - 1 + WHERE ' . $this->db->sql_in_set('word_id', $message_word_ids) . ' + AND word_count > 0'; + $this->db->sql_query($sql); + } + + unset($title_word_ids); + unset($message_word_ids); + + $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' + WHERE ' . $this->db->sql_in_set('post_id', $post_ids); + $this->db->sql_query($sql); + } + + $this->destroy_cache(array_unique($word_texts), array_unique($author_ids)); + } + + /** + * Tidy up indexes: Tag 'common words' and remove + * words no longer referenced in the match table + */ + public function tidy() + { + // Is the fulltext indexer disabled? If yes then we need not + // carry on ... it's okay ... I know when I'm not wanted boo hoo + if (!$this->config['fulltext_native_load_upd']) + { + $this->config->set('search_last_gc', time(), false); + return; + } + + $destroy_cache_words = array(); + + // Remove common words + if ($this->config['num_posts'] >= 100 && $this->config['fulltext_native_common_thres']) + { + $common_threshold = ((double) $this->config['fulltext_native_common_thres']) / 100.0; + // First, get the IDs of common words + $sql = 'SELECT word_id, word_text + FROM ' . SEARCH_WORDLIST_TABLE . ' + WHERE word_count > ' . floor($this->config['num_posts'] * $common_threshold) . ' + OR word_common = 1'; + $result = $this->db->sql_query($sql); + + $sql_in = array(); + while ($row = $this->db->sql_fetchrow($result)) + { + $sql_in[] = $row['word_id']; + $destroy_cache_words[] = $row['word_text']; + } + $this->db->sql_freeresult($result); + + if (count($sql_in)) + { + // Flag the words + $sql = 'UPDATE ' . SEARCH_WORDLIST_TABLE . ' + SET word_common = 1 + WHERE ' . $this->db->sql_in_set('word_id', $sql_in); + $this->db->sql_query($sql); + + // by setting search_last_gc to the new time here we make sure that if a user reloads because the + // following query takes too long, he won't run into it again + $this->config->set('search_last_gc', time(), false); + + // Delete the matches + $sql = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE . ' + WHERE ' . $this->db->sql_in_set('word_id', $sql_in); + $this->db->sql_query($sql); + } + unset($sql_in); + } + + if (count($destroy_cache_words)) + { + // destroy cached search results containing any of the words that are now common or were removed + $this->destroy_cache(array_unique($destroy_cache_words)); + } + + $this->config->set('search_last_gc', time(), false); + } + + /** + * Deletes all words from the index + */ + public function delete_index($acp_module, $u_action) + { + $sql_queries = []; + + switch ($this->db->get_sql_layer()) + { + case 'sqlite3': + $sql_queries[] = 'DELETE FROM ' . SEARCH_WORDLIST_TABLE; + $sql_queries[] = 'DELETE FROM ' . SEARCH_WORDMATCH_TABLE; + $sql_queries[] = 'DELETE FROM ' . SEARCH_RESULTS_TABLE; + break; + + default: + $sql_queries[] = 'TRUNCATE TABLE ' . SEARCH_WORDLIST_TABLE; + $sql_queries[] = 'TRUNCATE TABLE ' . SEARCH_WORDMATCH_TABLE; + $sql_queries[] = 'TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE; + break; + } + + $stats = $this->stats; + + /** + * Event to modify SQL queries before the native search index is deleted + * + * @event core.search_native_delete_index_before + * @var array sql_queries Array with queries for deleting the search index + * @var array stats Array with statistics of the current index (read only) + * @since 3.2.3-RC1 + */ + $vars = array( + 'sql_queries', + 'stats', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_native_delete_index_before', compact($vars))); + + foreach ($sql_queries as $sql_query) + { + $this->db->sql_query($sql_query); + } + } + + /** + * Returns true if both FULLTEXT indexes exist + */ + public function index_created() + { + if (!count($this->stats)) + { + $this->get_stats(); + } + + return ($this->stats['total_words'] && $this->stats['total_matches']) ? true : false; + } + + /** + * Returns an associative array containing information about the indexes + */ + public function index_stats() + { + if (!count($this->stats)) + { + $this->get_stats(); + } + + return array( + $this->user->lang['TOTAL_WORDS'] => $this->stats['total_words'], + $this->user->lang['TOTAL_MATCHES'] => $this->stats['total_matches']); + } + + protected function get_stats() + { + $this->stats['total_words'] = $this->db->get_estimated_row_count(SEARCH_WORDLIST_TABLE); + $this->stats['total_matches'] = $this->db->get_estimated_row_count(SEARCH_WORDMATCH_TABLE); + } + + /** + * Clean up a text to remove non-alphanumeric characters + * + * This method receives a UTF-8 string, normalizes and validates it, replaces all + * non-alphanumeric characters with strings then returns the result. + * + * Any number of "allowed chars" can be passed as a UTF-8 string in NFC. + * + * @param string $text Text to split, in UTF-8 (not normalized or sanitized) + * @param string $allowed_chars String of special chars to allow + * @param string $encoding Text encoding + * @return string Cleaned up text, only alphanumeric chars are left + */ + protected function cleanup($text, $allowed_chars = null, $encoding = 'utf-8') + { + static $conv = array(), $conv_loaded = array(); + $allow = array(); + + // Convert the text to UTF-8 + $encoding = strtolower($encoding); + if ($encoding != 'utf-8') + { + $text = utf8_recode($text, $encoding); + } + + $utf_len_mask = array( + "\xC0" => 2, + "\xD0" => 2, + "\xE0" => 3, + "\xF0" => 4 + ); + + /** + * Replace HTML entities and NCRs + */ + $text = htmlspecialchars_decode(utf8_decode_ncr($text), ENT_QUOTES); + + /** + * Normalize to NFC + */ + $text = \Normalizer::normalize($text); + + /** + * The first thing we do is: + * + * - convert ASCII-7 letters to lowercase + * - remove the ASCII-7 non-alpha characters + * - remove the bytes that should not appear in a valid UTF-8 string: 0xC0, + * 0xC1 and 0xF5-0xFF + * + * @todo in theory, the third one is already taken care of during normalization and those chars should have been replaced by Unicode replacement chars + */ + $sb_match = "ISTCPAMELRDOJBNHFGVWUQKYXZ\r\n\t!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\xC0\xC1\xF5\xF6\xF7\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xFF"; + $sb_replace = 'istcpamelrdojbnhfgvwuqkyxz '; + + /** + * This is the list of legal ASCII chars, it is automatically extended + * with ASCII chars from $allowed_chars + */ + $legal_ascii = ' eaisntroludcpmghbfvq10xy2j9kw354867z'; + + /** + * Prepare an array containing the extra chars to allow + */ + if (isset($allowed_chars[0])) + { + $pos = 0; + $len = strlen($allowed_chars); + do + { + $c = $allowed_chars[$pos]; + + if ($c < "\x80") + { + /** + * ASCII char + */ + $sb_pos = strpos($sb_match, $c); + if (is_int($sb_pos)) + { + /** + * Remove the char from $sb_match and its corresponding + * replacement in $sb_replace + */ + $sb_match = substr($sb_match, 0, $sb_pos) . substr($sb_match, $sb_pos + 1); + $sb_replace = substr($sb_replace, 0, $sb_pos) . substr($sb_replace, $sb_pos + 1); + $legal_ascii .= $c; + } + + ++$pos; + } + else + { + /** + * UTF-8 char + */ + $utf_len = $utf_len_mask[$c & "\xF0"]; + $allow[substr($allowed_chars, $pos, $utf_len)] = 1; + $pos += $utf_len; + } + } + while ($pos < $len); + } + + $text = strtr($text, $sb_match, $sb_replace); + $ret = ''; + + $pos = 0; + $len = strlen($text); + + do + { + /** + * Do all consecutive ASCII chars at once + */ + if ($spn = strspn($text, $legal_ascii, $pos)) + { + $ret .= substr($text, $pos, $spn); + $pos += $spn; + } + + if ($pos >= $len) + { + return $ret; + } + + /** + * Capture the UTF char + */ + $utf_len = $utf_len_mask[$text[$pos] & "\xF0"]; + $utf_char = substr($text, $pos, $utf_len); + $pos += $utf_len; + + if (($utf_char >= self::UTF8_HANGUL_FIRST && $utf_char <= self::UTF8_HANGUL_LAST) + || ($utf_char >= self::UTF8_CJK_FIRST && $utf_char <= self::UTF8_CJK_LAST) + || ($utf_char >= self::UTF8_CJK_B_FIRST && $utf_char <= self::UTF8_CJK_B_LAST)) + { + /** + * All characters within these ranges are valid + * + * We separate them with a space in order to index each character + * individually + */ + $ret .= ' ' . $utf_char . ' '; + continue; + } + + if (isset($allow[$utf_char])) + { + /** + * The char is explicitly allowed + */ + $ret .= $utf_char; + continue; + } + + if (isset($conv[$utf_char])) + { + /** + * The char is mapped to something, maybe to itself actually + */ + $ret .= $conv[$utf_char]; + continue; + } + + /** + * The char isn't mapped, but did we load its conversion table? + * + * The search indexer table is split into blocks. The block number of + * each char is equal to its codepoint right-shifted for 11 bits. It + * means that out of the 11, 16 or 21 meaningful bits of a 2-, 3- or + * 4- byte sequence we only keep the leftmost 0, 5 or 10 bits. Thus, + * all UTF chars encoded in 2 bytes are in the same first block. + */ + if (isset($utf_char[2])) + { + if (isset($utf_char[3])) + { + /** + * 1111 0nnn 10nn nnnn 10nx xxxx 10xx xxxx + * 0000 0111 0011 1111 0010 0000 + */ + $idx = ((ord($utf_char[0]) & 0x07) << 7) | ((ord($utf_char[1]) & 0x3F) << 1) | ((ord($utf_char[2]) & 0x20) >> 5); + } + else + { + /** + * 1110 nnnn 10nx xxxx 10xx xxxx + * 0000 0111 0010 0000 + */ + $idx = ((ord($utf_char[0]) & 0x07) << 1) | ((ord($utf_char[1]) & 0x20) >> 5); + } + } + else + { + /** + * 110x xxxx 10xx xxxx + * 0000 0000 0000 0000 + */ + $idx = 0; + } + + /** + * Check if the required conv table has been loaded already + */ + if (!isset($conv_loaded[$idx])) + { + $conv_loaded[$idx] = 1; + $file = $this->phpbb_root_path . 'includes/utf/data/search_indexer_' . $idx . '.' . $this->php_ext; + + if (file_exists($file)) + { + $conv += include($file); + } + } + + if (isset($conv[$utf_char])) + { + $ret .= $conv[$utf_char]; + } + else + { + /** + * We add an entry to the conversion table so that we + * don't have to convert to codepoint and perform the checks + * that are above this block + */ + $conv[$utf_char] = ' '; + $ret .= ' '; + } + } + while (1); + + return $ret; + } + + /** + * Returns a list of options for the ACP to display + */ + public function acp() + { + /** + * if we need any options, copied from fulltext_native for now, will have to be adjusted or removed + */ + + $tpl = ' +
+

' . $this->user->lang['YES_SEARCH_UPDATE_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['MIN_SEARCH_CHARS_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['MAX_SEARCH_CHARS_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['COMMON_WORD_THRESHOLD_EXPLAIN'] . '
+
%
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => array('fulltext_native_load_upd' => 'bool', 'fulltext_native_min_chars' => 'integer:0:255', 'fulltext_native_max_chars' => 'integer:0:255', 'fulltext_native_common_thres' => 'double:0:100') + ); + } +} diff --git a/phpbb/search/fulltext_postgres.php b/phpbb/search/fulltext_postgres.php new file mode 100644 index 0000000..2f387e7 --- /dev/null +++ b/phpbb/search/fulltext_postgres.php @@ -0,0 +1,1178 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search; + +/** +* Fulltext search for PostgreSQL +*/ +class fulltext_postgres extends \phpbb\search\base +{ + /** + * Associative array holding index stats + * @var array + */ + protected $stats = array(); + + /** + * Holds the words entered by user, obtained by splitting the entered query on whitespace + * @var array + */ + protected $split_words = array(); + + /** + * Stores the tsearch query + * @var string + */ + protected $tsearch_query; + + /** + * True if phrase search is supported. + * PostgreSQL fulltext currently doesn't support it + * @var boolean + */ + protected $phrase_search = false; + + /** + * Config object + * @var \phpbb\config\config + */ + protected $config; + + /** + * Database connection + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * phpBB event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $phpbb_dispatcher; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Contains tidied search query. + * Operators are prefixed in search query and common words excluded + * @var string + */ + protected $search_query; + + /** + * Contains common words. + * Common words are words with length less/more than min/max length + * @var array + */ + protected $common_words = array(); + + /** + * Associative array stores the min and max word length to be searched + * @var array + */ + protected $word_length = array(); + + /** + * Constructor + * Creates a new \phpbb\search\fulltext_postgres, which is used as a search backend + * + * @param string|bool $error Any error that occurs is passed on through this reference variable otherwise false + * @param string $phpbb_root_path Relative path to phpBB root + * @param string $phpEx PHP file extension + * @param \phpbb\auth\auth $auth Auth object + * @param \phpbb\config\config $config Config object + * @param \phpbb\db\driver\driver_interface Database object + * @param \phpbb\user $user User object + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object + */ + public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher) + { + $this->config = $config; + $this->db = $db; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->user = $user; + + $this->word_length = array('min' => $this->config['fulltext_postgres_min_word_len'], 'max' => $this->config['fulltext_postgres_max_word_len']); + + /** + * Load the UTF tools + */ + if (!function_exists('utf8_strlen')) + { + include($phpbb_root_path . 'includes/utf/utf_tools.' . $phpEx); + } + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'PostgreSQL Fulltext'; + } + + /** + * Returns the search_query + * + * @return string search query + */ + public function get_search_query() + { + return $this->search_query; + } + + /** + * Returns the common_words array + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return $this->common_words; + } + + /** + * Returns the word_length array + * + * @return array min and max word length for searching + */ + public function get_word_length() + { + return $this->word_length; + } + + /** + * Returns if phrase search is supported or not + * + * @return bool + */ + public function supports_phrase_search() + { + return $this->phrase_search; + } + + /** + * Checks for correct PostgreSQL version and stores min/max word length in the config + * + * @return string|bool Language key of the error/incompatiblity occurred + */ + public function init() + { + if ($this->db->get_sql_layer() != 'postgres') + { + return $this->user->lang['FULLTEXT_POSTGRES_INCOMPATIBLE_DATABASE']; + } + + return false; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string &$keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return bool false if no valid keywords were found and otherwise true + */ + public function split_keywords(&$keywords, $terms) + { + if ($terms == 'all') + { + $match = array('#\sand\s#iu', '#\sor\s#iu', '#\snot\s#iu', '#(^|\s)\+#', '#(^|\s)-#', '#(^|\s)\|#'); + $replace = array(' +', ' |', ' -', ' +', ' -', ' |'); + + $keywords = preg_replace($match, $replace, $keywords); + } + + // Filter out as above + $split_keywords = preg_replace("#[\"\n\r\t]+#", ' ', trim(htmlspecialchars_decode($keywords))); + + // Split words + $split_keywords = preg_replace('#([^\p{L}\p{N}\'*"()])#u', '$1$1', str_replace('\'\'', '\' \'', trim($split_keywords))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*"()]|^)([+\-|]?(?:[\p{L}\p{N}*"()]+\'?)*[\p{L}\p{N}*"()])(?:[^\p{L}\p{N}*"()]|$)#u', $split_keywords, $matches); + $this->split_words = $matches[1]; + + foreach ($this->split_words as $i => $word) + { + $clean_word = preg_replace('#^[+\-|"]#', '', $word); + + // check word length + $clean_len = utf8_strlen(str_replace('*', '', $clean_word)); + if (($clean_len < $this->config['fulltext_postgres_min_word_len']) || ($clean_len > $this->config['fulltext_postgres_max_word_len'])) + { + $this->common_words[] = $word; + unset($this->split_words[$i]); + } + } + + if ($terms == 'any') + { + $this->search_query = ''; + $this->tsearch_query = ''; + foreach ($this->split_words as $word) + { + if ((strpos($word, '+') === 0) || (strpos($word, '-') === 0) || (strpos($word, '|') === 0)) + { + $word = substr($word, 1); + } + $this->search_query .= $word . ' '; + $this->tsearch_query .= '|' . $word . ' '; + } + } + else + { + $this->search_query = ''; + $this->tsearch_query = ''; + foreach ($this->split_words as $word) + { + if (strpos($word, '+') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '&' . substr($word, 1) . ' '; + } + else if (strpos($word, '-') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '&!' . substr($word, 1) . ' '; + } + else if (strpos($word, '|') === 0) + { + $this->search_query .= $word . ' '; + $this->tsearch_query .= '|' . substr($word, 1) . ' '; + } + else + { + $this->search_query .= '+' . $word . ' '; + $this->tsearch_query .= '&' . $word . ' '; + } + } + } + + $this->tsearch_query = substr($this->tsearch_query, 1); + $this->search_query = utf8_htmlspecialchars($this->search_query); + + if ($this->search_query) + { + $this->split_words = array_values($this->split_words); + sort($this->split_words); + return true; + } + return false; + } + + /** + * Turns text into an array of words + * @param string $text contains post text/subject + */ + public function split_message($text) + { + // Split words + $text = preg_replace('#([^\p{L}\p{N}\'*])#u', '$1$1', str_replace('\'\'', '\' \'', trim($text))); + $matches = array(); + preg_match_all('#(?:[^\p{L}\p{N}*]|^)([+\-|]?(?:[\p{L}\p{N}*]+\'?)*[\p{L}\p{N}*])(?:[^\p{L}\p{N}*]|$)#u', $text, $matches); + $text = $matches[1]; + + // remove too short or too long words + $text = array_values($text); + for ($i = 0, $n = count($text); $i < $n; $i++) + { + $text[$i] = trim($text[$i]); + if (utf8_strlen($text[$i]) < $this->config['fulltext_postgres_min_word_len'] || utf8_strlen($text[$i]) > $this->config['fulltext_postgres_max_word_len']) + { + unset($text[$i]); + } + } + + return array_values($text); + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No keywords? No posts + if (!$this->search_query) + { + return false; + } + + // When search query contains queries like -foo + if (strpos($this->search_query, '+') === false) + { + return false; + } + + // generate a search_key from all the options to identify the results + $search_key_array = array( + implode(', ', $this->split_words), + $type, + $fields, + $terms, + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary) + ); + + /** + * Allow changing the search_key for cached results + * + * @event core.search_postgres_by_keyword_modify_search_key + * @var array search_key_array Array with search parameters to generate the search_key + * @var string type Searching type ('posts', 'topics') + * @var string fields Searching fields ('titleonly', 'msgonly', 'firstpost', 'all') + * @var string terms Searching terms ('all', 'any') + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sort_key The sort type used from the possible sort types + * @var int topic_id Limit the search to this topic_id only + * @var array ex_fid_ary Which forums not to search on + * @var string post_visibility Post visibility data + * @var array author_ary Array of user_id containing the users to filter the results to + * @since 3.1.7-RC1 + */ + $vars = array( + 'search_key_array', + 'type', + 'fields', + 'terms', + 'sort_days', + 'sort_key', + 'topic_id', + 'ex_fid_ary', + 'post_visibility', + 'author_ary', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_postgres_by_keyword_modify_search_key', compact($vars))); + + $search_key = md5(implode('#', $search_key_array)); + + if ($start < 0) + { + $start = 0; + } + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + $join_topic = ($type == 'posts') ? false : true; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $join_topic = true; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + // Build some display specific sql strings + switch ($fields) + { + case 'titleonly': + $sql_match = 'p.post_subject'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + case 'msgonly': + $sql_match = 'p.post_text'; + $sql_match_where = ''; + break; + + case 'firstpost': + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ' AND p.post_id = t.topic_first_post_id'; + $join_topic = true; + break; + + default: + $sql_match = 'p.post_subject, p.post_text'; + $sql_match_where = ''; + break; + } + + $tsearch_query = $this->tsearch_query; + + /** + * Allow changing the query used to search for posts using fulltext_postgres + * + * @event core.search_postgres_keywords_main_query_before + * @var string tsearch_query The parsed keywords used for this search + * @var int result_count The previous result count for the format of the query. + * Set to 0 to force a re-count + * @var bool join_topic Weather or not TOPICS_TABLE should be CROSS JOIN'ED + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name An extra username to search on (!empty(author_ary) must be true, to be relevant) + * @var array ex_fid_ary Which forums not to search on + * @var int topic_id Limit the search to this topic_id only + * @var string sql_sort_table Extra tables to include in the SQL query. + * Used in conjunction with sql_sort_join + * @var string sql_sort_join SQL conditions to join all the tables used together. + * Used in conjunction with sql_sort_table + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sql_match Which columns to do the search on. + * @var string sql_match_where Extra conditions to use to properly filter the matching process + * @var string sort_by_sql The possible predefined sort types + * @var string sort_key The sort type used from the possible sort types + * @var string sort_dir "a" for ASC or "d" dor DESC for the sort order used + * @var string sql_sort The result SQL when processing sort_by_sql + sort_key + sort_dir + * @var int start How many posts to skip in the search results (used for pagination) + * @since 3.1.5-RC1 + */ + $vars = array( + 'tsearch_query', + 'result_count', + 'join_topic', + 'author_ary', + 'author_name', + 'ex_fid_ary', + 'topic_id', + 'sql_sort_table', + 'sql_sort_join', + 'sort_days', + 'sql_match', + 'sql_match_where', + 'sort_by_sql', + 'sort_key', + 'sort_dir', + 'sql_sort', + 'start', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_postgres_keywords_main_query_before', compact($vars))); + + $sql_select = ($type == 'posts') ? 'p.post_id' : 'DISTINCT t.topic_id, ' . $sort_by_sql[$sort_key]; + $sql_from = ($join_topic) ? TOPICS_TABLE . ' t, ' : ''; + $field = ($type == 'posts') ? 'post_id' : 'topic_id'; + + if (count($author_ary) && $author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else if (count($author_ary)) + { + $sql_author = ' AND ' . $this->db->sql_in_set('p.poster_id', $author_ary); + } + else + { + $sql_author = ''; + } + + $sql_where_options = $sql_sort_join; + $sql_where_options .= ($topic_id) ? ' AND p.topic_id = ' . $topic_id : ''; + $sql_where_options .= ($join_topic) ? ' AND t.topic_id = p.topic_id' : ''; + $sql_where_options .= (count($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_where_options .= ' AND ' . $post_visibility; + $sql_where_options .= $sql_author; + $sql_where_options .= ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_where_options .= $sql_match_where; + + $sql_match = str_replace(',', " || ' ' ||", $sql_match); + $tmp_sql_match = "to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', " . $sql_match . ") @@ to_tsquery ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', '" . $this->db->sql_escape($this->tsearch_query) . "')"; + + $this->db->sql_transaction('begin'); + + $sql_from = "FROM $sql_from$sql_sort_table" . POSTS_TABLE . " p"; + $sql_where = "WHERE (" . $tmp_sql_match . ") + $sql_where_options"; + $sql = "SELECT $sql_select + $sql_from + $sql_where + ORDER BY $sql_sort"; + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + + // if the total result count is not cached yet, retrieve it from the db + if (!$result_count) + { + $sql_count = "SELECT COUNT(*) as result_count + $sql_from + $sql_where"; + $result = $this->db->sql_query($sql_count); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + $this->db->sql_freeresult($result); + + if (!$result_count) + { + return false; + } + } + + $this->db->sql_transaction('commit'); + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + + // store the ids, from start on then delete anything that isn't on the current page because we only need ids for one page + $this->save_ids($search_key, implode(' ', $this->split_words), $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + // No author? No posts + if (!count($author_ary)) + { + return 0; + } + + // generate a search_key from all the options to identify the results + $search_key_array = array( + '', + $type, + ($firstpost_only) ? 'firstpost' : '', + '', + '', + $sort_days, + $sort_key, + $topic_id, + implode(',', $ex_fid_ary), + $post_visibility, + implode(',', $author_ary), + $author_name, + ); + + /** + * Allow changing the search_key for cached results + * + * @event core.search_postgres_by_author_modify_search_key + * @var array search_key_array Array with search parameters to generate the search_key + * @var string type Searching type ('posts', 'topics') + * @var boolean firstpost_only Flag indicating if only topic starting posts are considered + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sort_key The sort type used from the possible sort types + * @var int topic_id Limit the search to this topic_id only + * @var array ex_fid_ary Which forums not to search on + * @var string post_visibility Post visibility data + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name The username to search on + * @since 3.1.7-RC1 + */ + $vars = array( + 'search_key_array', + 'type', + 'firstpost_only', + 'sort_days', + 'sort_key', + 'topic_id', + 'ex_fid_ary', + 'post_visibility', + 'author_ary', + 'author_name', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_postgres_by_author_modify_search_key', compact($vars))); + + $search_key = md5(implode('#', $search_key_array)); + + if ($start < 0) + { + $start = 0; + } + + // try reading the results from cache + $result_count = 0; + if ($this->obtain_ids($search_key, $result_count, $id_ary, $start, $per_page, $sort_dir) == SEARCH_RESULT_IN_CACHE) + { + return $result_count; + } + + $id_ary = array(); + + // Create some display specific sql strings + if ($author_name) + { + // first one matches post of registered users, second one guests and deleted users + $sql_author = '(' . $this->db->sql_in_set('p.poster_id', array_diff($author_ary, array(ANONYMOUS)), false, true) . ' OR p.post_username ' . $author_name . ')'; + } + else + { + $sql_author = $this->db->sql_in_set('p.poster_id', $author_ary); + } + $sql_fora = (count($ex_fid_ary)) ? ' AND ' . $this->db->sql_in_set('p.forum_id', $ex_fid_ary, true) : ''; + $sql_topic_id = ($topic_id) ? ' AND p.topic_id = ' . (int) $topic_id : ''; + $sql_time = ($sort_days) ? ' AND p.post_time >= ' . (time() - ($sort_days * 86400)) : ''; + $sql_firstpost = ($firstpost_only) ? ' AND p.post_id = t.topic_first_post_id' : ''; + + // Build sql strings for sorting + $sql_sort = $sort_by_sql[$sort_key] . (($sort_dir == 'a') ? ' ASC' : ' DESC'); + $sql_sort_table = $sql_sort_join = ''; + switch ($sql_sort[0]) + { + case 'u': + $sql_sort_table = USERS_TABLE . ' u, '; + $sql_sort_join = ($type == 'posts') ? ' AND u.user_id = p.poster_id ' : ' AND u.user_id = t.topic_poster '; + break; + + case 't': + $sql_sort_table = ($type == 'posts' && !$firstpost_only) ? TOPICS_TABLE . ' t, ' : ''; + $sql_sort_join = ($type == 'posts' && !$firstpost_only) ? ' AND t.topic_id = p.topic_id ' : ''; + break; + + case 'f': + $sql_sort_table = FORUMS_TABLE . ' f, '; + $sql_sort_join = ' AND f.forum_id = p.forum_id '; + break; + } + + $m_approve_fid_sql = ' AND ' . $post_visibility; + + /** + * Allow changing the query used to search for posts by author in fulltext_postgres + * + * @event core.search_postgres_author_count_query_before + * @var int result_count The previous result count for the format of the query. + * Set to 0 to force a re-count + * @var string sql_sort_table CROSS JOIN'ed table to allow doing the sort chosen + * @var string sql_sort_join Condition to define how to join the CROSS JOIN'ed table specifyed in sql_sort_table + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name An extra username to search on + * @var string sql_author SQL WHERE condition for the post author ids + * @var int topic_id Limit the search to this topic_id only + * @var string sql_topic_id SQL of topic_id + * @var string sort_by_sql The possible predefined sort types + * @var string sort_key The sort type used from the possible sort types + * @var string sort_dir "a" for ASC or "d" dor DESC for the sort order used + * @var string sql_sort The result SQL when processing sort_by_sql + sort_key + sort_dir + * @var string sort_days Time, in days, that the oldest post showing can have + * @var string sql_time The SQL to search on the time specifyed by sort_days + * @var bool firstpost_only Wether or not to search only on the first post of the topics + * @var array ex_fid_ary Forum ids that must not be searched on + * @var array sql_fora SQL query for ex_fid_ary + * @var string m_approve_fid_sql WHERE clause condition on post_visibility restrictions + * @var int start How many posts to skip in the search results (used for pagination) + * @since 3.1.5-RC1 + */ + $vars = array( + 'result_count', + 'sql_sort_table', + 'sql_sort_join', + 'author_ary', + 'author_name', + 'sql_author', + 'topic_id', + 'sql_topic_id', + 'sort_by_sql', + 'sort_key', + 'sort_dir', + 'sql_sort', + 'sort_days', + 'sql_time', + 'firstpost_only', + 'ex_fid_ary', + 'sql_fora', + 'm_approve_fid_sql', + 'start', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_postgres_author_count_query_before', compact($vars))); + + // Build the query for really selecting the post_ids + if ($type == 'posts') + { + $sql = "SELECT p.post_id + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time + ORDER BY $sql_sort"; + $field = 'post_id'; + } + else + { + $sql = "SELECT t.topic_id + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, $sort_by_sql[$sort_key] + ORDER BY $sql_sort"; + $field = 'topic_id'; + } + + $this->db->sql_transaction('begin'); + + // Only read one block of posts from the db and then cache it + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = $row[$field]; + } + $this->db->sql_freeresult($result); + + // retrieve the total result count if needed + if (!$result_count) + { + if ($type == 'posts') + { + $sql_count = "SELECT COUNT(*) as result_count + FROM " . $sql_sort_table . POSTS_TABLE . ' p' . (($firstpost_only) ? ', ' . TOPICS_TABLE . ' t ' : ' ') . " + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + $sql_sort_join + $sql_time"; + } + else + { + $sql_count = "SELECT COUNT(*) as result_count + FROM " . $sql_sort_table . TOPICS_TABLE . ' t, ' . POSTS_TABLE . " p + WHERE $sql_author + $sql_topic_id + $sql_firstpost + $m_approve_fid_sql + $sql_fora + AND t.topic_id = p.topic_id + $sql_sort_join + $sql_time + GROUP BY t.topic_id, $sort_by_sql[$sort_key]"; + } + + $this->db->sql_query($sql_count); + $result_count = (int) $this->db->sql_fetchfield('result_count'); + + if (!$result_count) + { + return false; + } + } + + $this->db->sql_transaction('commit'); + + if ($start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $result = $this->db->sql_query_limit($sql, $this->config['search_block_size'], $start); + while ($row = $this->db->sql_fetchrow($result)) + { + $id_ary[] = (int) $row[$field]; + } + $this->db->sql_freeresult($result); + + $id_ary = array_unique($id_ary); + } + + if (count($id_ary)) + { + $this->save_ids($search_key, '', $author_ary, $result_count, $id_ary, $start, $sort_dir); + $id_ary = array_slice($id_ary, 0, $per_page); + + return $result_count; + } + return false; + } + + /** + * Destroys cached search results, that contained one of the new words in a post so the results won't be outdated + * + * @param string $mode contains the post mode: edit, post, reply, quote ... + * @param int $post_id contains the post id of the post to index + * @param string $message contains the post text of the post + * @param string $subject contains the subject of the post to index + * @param int $poster_id contains the user id of the poster + * @param int $forum_id contains the forum id of parent forum of the post + */ + public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + // Split old and new post/subject to obtain array of words + $split_text = $this->split_message($message); + $split_title = ($subject) ? $this->split_message($subject) : array(); + + $words = array_unique(array_merge($split_text, $split_title)); + + /** + * Event to modify method arguments and words before the PostgreSQL search index is updated + * + * @event core.search_postgres_index_before + * @var string mode Contains the post mode: edit, post, reply, quote + * @var int post_id The id of the post which is modified/created + * @var string message New or updated post content + * @var string subject New or updated post subject + * @var int poster_id Post author's user id + * @var int forum_id The id of the forum in which the post is located + * @var array words Array of words added to the index + * @var array split_text Array of words from the message + * @var array split_title Array of words from the title + * @since 3.2.3-RC1 + */ + $vars = array( + 'mode', + 'post_id', + 'message', + 'subject', + 'poster_id', + 'forum_id', + 'words', + 'split_text', + 'split_title', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_postgres_index_before', compact($vars))); + + unset($split_text); + unset($split_title); + + // destroy cached search results containing any of the words removed or added + $this->destroy_cache($words, array($poster_id)); + + unset($words); + } + + /** + * Destroy cached results, that might be outdated after deleting a post + */ + public function index_remove($post_ids, $author_ids, $forum_ids) + { + $this->destroy_cache(array(), $author_ids); + } + + /** + * Destroy old cache entries + */ + public function tidy() + { + // destroy too old cached search results + $this->destroy_cache(array()); + + $this->config->set('search_last_gc', time(), false); + } + + /** + * Create fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function create_index($acp_module, $u_action) + { + // Make sure we can actually use PostgreSQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + $sql_queries = []; + + if (!isset($this->stats['post_subject'])) + { + $sql_queries[] = "CREATE INDEX " . POSTS_TABLE . "_" . $this->config['fulltext_postgres_ts_name'] . "_post_subject ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', post_subject))"; + } + + if (!isset($this->stats['post_content'])) + { + $sql_queries[] = "CREATE INDEX " . POSTS_TABLE . "_" . $this->config['fulltext_postgres_ts_name'] . "_post_content ON " . POSTS_TABLE . " USING gin (to_tsvector ('" . $this->db->sql_escape($this->config['fulltext_postgres_ts_name']) . "', post_text || ' ' || post_subject))"; + } + + $stats = $this->stats; + + /** + * Event to modify SQL queries before the Postgres search index is created + * + * @event core.search_postgres_create_index_before + * @var array sql_queries Array with queries for creating the search index + * @var array stats Array with statistics of the current index (read only) + * @since 3.2.3-RC1 + */ + $vars = array( + 'sql_queries', + 'stats', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_postgres_create_index_before', compact($vars))); + + foreach ($sql_queries as $sql_query) + { + $this->db->sql_query($sql_query); + } + + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Drop fulltext index + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function delete_index($acp_module, $u_action) + { + // Make sure we can actually use PostgreSQL with fulltext indexes + if ($error = $this->init()) + { + return $error; + } + + if (empty($this->stats)) + { + $this->get_stats(); + } + + $sql_queries = []; + + if (isset($this->stats['post_subject'])) + { + $sql_queries[] = 'DROP INDEX ' . $this->stats['post_subject']['relname']; + } + + if (isset($this->stats['post_content'])) + { + $sql_queries[] = 'DROP INDEX ' . $this->stats['post_content']['relname']; + } + + $stats = $this->stats; + + /** + * Event to modify SQL queries before the Postgres search index is created + * + * @event core.search_postgres_delete_index_before + * @var array sql_queries Array with queries for deleting the search index + * @var array stats Array with statistics of the current index (read only) + * @since 3.2.3-RC1 + */ + $vars = array( + 'sql_queries', + 'stats', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_postgres_delete_index_before', compact($vars))); + + foreach ($sql_queries as $sql_query) + { + $this->db->sql_query($sql_query); + } + + $this->db->sql_query('TRUNCATE TABLE ' . SEARCH_RESULTS_TABLE); + + return false; + } + + /** + * Returns true if both FULLTEXT indexes exist + */ + public function index_created() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return (isset($this->stats['post_subject']) && isset($this->stats['post_content'])) ? true : false; + } + + /** + * Returns an associative array containing information about the indexes + */ + public function index_stats() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return array( + $this->user->lang['FULLTEXT_POSTGRES_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + ); + } + + /** + * Computes the stats and store them in the $this->stats associative array + */ + protected function get_stats() + { + if ($this->db->get_sql_layer() != 'postgres') + { + $this->stats = array(); + return; + } + + $sql = "SELECT c2.relname, pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS indexdef + FROM pg_catalog.pg_class c1, pg_catalog.pg_index i, pg_catalog.pg_class c2 + WHERE c1.relname = '" . POSTS_TABLE . "' + AND pg_catalog.pg_table_is_visible(c1.oid) + AND c1.oid = i.indrelid + AND i.indexrelid = c2.oid"; + $result = $this->db->sql_query($sql); + + while ($row = $this->db->sql_fetchrow($result)) + { + // deal with older PostgreSQL versions which didn't use Index_type + if (strpos($row['indexdef'], 'to_tsvector') !== false) + { + if ($row['relname'] == POSTS_TABLE . '_' . $this->config['fulltext_postgres_ts_name'] . '_post_subject' || $row['relname'] == POSTS_TABLE . '_post_subject') + { + $this->stats['post_subject'] = $row; + } + else if ($row['relname'] == POSTS_TABLE . '_' . $this->config['fulltext_postgres_ts_name'] . '_post_content' || $row['relname'] == POSTS_TABLE . '_post_content') + { + $this->stats['post_content'] = $row; + } + } + } + $this->db->sql_freeresult($result); + + $this->stats['total_posts'] = $this->config['num_posts']; + } + + /** + * Display various options that can be configured for the backend from the acp + * + * @return associative array containing template and config variables + */ + public function acp() + { + $tpl = ' +
+

' . $this->user->lang['FULLTEXT_POSTGRES_VERSION_CHECK_EXPLAIN'] . '
+
' . (($this->db->get_sql_layer() == 'postgres') ? $this->user->lang['YES'] : $this->user->lang['NO']) . '
+
+
+

' . $this->user->lang['FULLTEXT_POSTGRES_TS_NAME_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['FULLTEXT_POSTGRES_MIN_WORD_LEN_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['FULLTEXT_POSTGRES_MAX_WORD_LEN_EXPLAIN'] . '
+
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => array('fulltext_postgres_ts_name' => 'string', 'fulltext_postgres_min_word_len' => 'integer:0:255', 'fulltext_postgres_max_word_len' => 'integer:0:255') + ); + } +} diff --git a/phpbb/search/fulltext_sphinx.php b/phpbb/search/fulltext_sphinx.php new file mode 100644 index 0000000..2c2eb84 --- /dev/null +++ b/phpbb/search/fulltext_sphinx.php @@ -0,0 +1,992 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search; + +define('SPHINX_MAX_MATCHES', 20000); +define('SPHINX_CONNECT_RETRIES', 3); +define('SPHINX_CONNECT_WAIT_TIME', 300); + +/** +* Fulltext search based on the sphinx search deamon +*/ +class fulltext_sphinx +{ + /** + * Associative array holding index stats + * @var array + */ + protected $stats = array(); + + /** + * Holds the words entered by user, obtained by splitting the entered query on whitespace + * @var array + */ + protected $split_words = array(); + + /** + * Holds unique sphinx id + * @var string + */ + protected $id; + + /** + * Stores the names of both main and delta sphinx indexes + * separated by a semicolon + * @var string + */ + protected $indexes; + + /** + * Sphinx searchd client object + * @var SphinxClient + */ + protected $sphinx; + + /** + * Relative path to board root + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP Extension + * @var string + */ + protected $php_ext; + + /** + * Auth object + * @var \phpbb\auth\auth + */ + protected $auth; + + /** + * Config object + * @var \phpbb\config\config + */ + protected $config; + + /** + * Database connection + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * Database Tools object + * @var \phpbb\db\tools\tools_interface + */ + protected $db_tools; + + /** + * Stores the database type if supported by sphinx + * @var string + */ + protected $dbtype; + + /** + * phpBB event dispatcher object + * @var \phpbb\event\dispatcher_interface + */ + protected $phpbb_dispatcher; + + /** + * User object + * @var \phpbb\user + */ + protected $user; + + /** + * Stores the generated content of the sphinx config file + * @var string + */ + protected $config_file_data = ''; + + /** + * Contains tidied search query. + * Operators are prefixed in search query and common words excluded + * @var string + */ + protected $search_query; + + /** + * Constructor + * Creates a new \phpbb\search\fulltext_postgres, which is used as a search backend + * + * @param string|bool $error Any error that occurs is passed on through this reference variable otherwise false + * @param string $phpbb_root_path Relative path to phpBB root + * @param string $phpEx PHP file extension + * @param \phpbb\auth\auth $auth Auth object + * @param \phpbb\config\config $config Config object + * @param \phpbb\db\driver\driver_interface Database object + * @param \phpbb\user $user User object + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object + */ + public function __construct(&$error, $phpbb_root_path, $phpEx, $auth, $config, $db, $user, $phpbb_dispatcher) + { + $this->phpbb_root_path = $phpbb_root_path; + $this->php_ext = $phpEx; + $this->config = $config; + $this->phpbb_dispatcher = $phpbb_dispatcher; + $this->user = $user; + $this->db = $db; + $this->auth = $auth; + + // Initialize \phpbb\db\tools\tools object + global $phpbb_container; // TODO inject into object + $this->db_tools = $phpbb_container->get('dbal.tools'); + + if (!$this->config['fulltext_sphinx_id']) + { + $this->config->set('fulltext_sphinx_id', unique_id()); + } + $this->id = $this->config['fulltext_sphinx_id']; + $this->indexes = 'index_phpbb_' . $this->id . '_delta;index_phpbb_' . $this->id . '_main'; + + if (!class_exists('SphinxClient')) + { + require($this->phpbb_root_path . 'includes/sphinxapi.' . $this->php_ext); + } + + // Initialize sphinx client + $this->sphinx = new \SphinxClient(); + + $this->sphinx->SetServer(($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost'), ($this->config['fulltext_sphinx_port'] ? (int) $this->config['fulltext_sphinx_port'] : 9312)); + + $error = false; + } + + /** + * Returns the name of this search backend to be displayed to administrators + * + * @return string Name + */ + public function get_name() + { + return 'Sphinx Fulltext'; + } + + /** + * Returns the search_query + * + * @return string search query + */ + public function get_search_query() + { + return $this->search_query; + } + + /** + * Returns false as there is no word_len array + * + * @return false + */ + public function get_word_length() + { + return false; + } + + /** + * Returns an empty array as there are no common_words + * + * @return array common words that are ignored by search backend + */ + public function get_common_words() + { + return array(); + } + + /** + * Checks permissions and paths, if everything is correct it generates the config file + * + * @return string|bool Language key of the error/incompatiblity encountered, or false if successful + */ + public function init() + { + if ($this->db->get_sql_layer() != 'mysql' && $this->db->get_sql_layer() != 'mysql4' && $this->db->get_sql_layer() != 'mysqli' && $this->db->get_sql_layer() != 'postgres') + { + return $this->user->lang['FULLTEXT_SPHINX_WRONG_DATABASE']; + } + + // Move delta to main index each hour + $this->config->set('search_gc', 3600); + + return false; + } + + /** + * Generates content of sphinx.conf + * + * @return bool True if sphinx.conf content is correctly generated, false otherwise + */ + protected function config_generate() + { + // Check if Database is supported by Sphinx + if ($this->db->get_sql_layer() =='mysql' || $this->db->get_sql_layer() == 'mysql4' || $this->db->get_sql_layer() == 'mysqli') + { + $this->dbtype = 'mysql'; + } + else if ($this->db->get_sql_layer() == 'postgres') + { + $this->dbtype = 'pgsql'; + } + else + { + $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_WRONG_DATABASE'); + return false; + } + + // Check if directory paths have been filled + if (!$this->config['fulltext_sphinx_data_path']) + { + $this->config_file_data = $this->user->lang('FULLTEXT_SPHINX_NO_CONFIG_DATA'); + return false; + } + + include($this->phpbb_root_path . 'config.' . $this->php_ext); + + /* Now that we're sure everything was entered correctly, + generate a config for the index. We use a config value + fulltext_sphinx_id for this, as it should be unique. */ + $config_object = new \phpbb\search\sphinx\config($this->config_file_data); + $config_data = array( + 'source source_phpbb_' . $this->id . '_main' => array( + array('type', $this->dbtype . ' # mysql or pgsql'), + // This config value sql_host needs to be changed incase sphinx and sql are on different servers + array('sql_host', $dbhost . ' # SQL server host sphinx connects to'), + array('sql_user', '[dbuser]'), + array('sql_pass', '[dbpassword]'), + array('sql_db', $dbname), + array('sql_port', $dbport . ' # optional, default is 3306 for mysql and 5432 for pgsql'), + array('sql_query_pre', 'SET NAMES \'utf8\''), + array('sql_query_pre', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = (SELECT MAX(post_id) FROM ' . POSTS_TABLE . ') WHERE counter_id = 1'), + array('sql_query_range', 'SELECT MIN(post_id), MAX(post_id) FROM ' . POSTS_TABLE . ''), + array('sql_range_step', '5000'), + array('sql_query', 'SELECT + p.post_id AS id, + p.forum_id, + p.topic_id, + p.poster_id, + p.post_visibility, + CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, + p.post_time, + p.post_subject, + p.post_subject as title, + p.post_text as data, + t.topic_last_post_time, + 0 as deleted + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t + WHERE + p.topic_id = t.topic_id + AND p.post_id >= $start AND p.post_id <= $end'), + array('sql_query_post', ''), + array('sql_query_post_index', 'UPDATE ' . SPHINX_TABLE . ' SET max_doc_id = $maxid WHERE counter_id = 1'), + array('sql_attr_uint', 'forum_id'), + array('sql_attr_uint', 'topic_id'), + array('sql_attr_uint', 'poster_id'), + array('sql_attr_uint', 'post_visibility'), + array('sql_attr_bool', 'topic_first_post'), + array('sql_attr_bool', 'deleted'), + array('sql_attr_timestamp', 'post_time'), + array('sql_attr_timestamp', 'topic_last_post_time'), + array('sql_attr_string', 'post_subject'), + ), + 'source source_phpbb_' . $this->id . '_delta : source_phpbb_' . $this->id . '_main' => array( + array('sql_query_pre', 'SET NAMES \'utf8\''), + array('sql_query_range', ''), + array('sql_range_step', ''), + array('sql_query', 'SELECT + p.post_id AS id, + p.forum_id, + p.topic_id, + p.poster_id, + p.post_visibility, + CASE WHEN p.post_id = t.topic_first_post_id THEN 1 ELSE 0 END as topic_first_post, + p.post_time, + p.post_subject, + p.post_subject as title, + p.post_text as data, + t.topic_last_post_time, + 0 as deleted + FROM ' . POSTS_TABLE . ' p, ' . TOPICS_TABLE . ' t + WHERE + p.topic_id = t.topic_id + AND p.post_id >= ( SELECT max_doc_id FROM ' . SPHINX_TABLE . ' WHERE counter_id=1 )'), + array('sql_query_post_index', ''), + ), + 'index index_phpbb_' . $this->id . '_main' => array( + array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_main'), + array('source', 'source_phpbb_' . $this->id . '_main'), + array('docinfo', 'extern'), + array('morphology', 'none'), + array('stopwords', ''), + array('min_word_len', '2'), + array('charset_table', 'U+FF10..U+FF19->0..9, 0..9, U+FF41..U+FF5A->a..z, U+FF21..U+FF3A->a..z, A..Z->a..z, a..z, U+0149, U+017F, U+0138, U+00DF, U+00FF, U+00C0..U+00D6->U+00E0..U+00F6, U+00E0..U+00F6, U+00D8..U+00DE->U+00F8..U+00FE, U+00F8..U+00FE, U+0100->U+0101, U+0101, U+0102->U+0103, U+0103, U+0104->U+0105, U+0105, U+0106->U+0107, U+0107, U+0108->U+0109, U+0109, U+010A->U+010B, U+010B, U+010C->U+010D, U+010D, U+010E->U+010F, U+010F, U+0110->U+0111, U+0111, U+0112->U+0113, U+0113, U+0114->U+0115, U+0115, U+0116->U+0117, U+0117, U+0118->U+0119, U+0119, U+011A->U+011B, U+011B, U+011C->U+011D, U+011D, U+011E->U+011F, U+011F, U+0130->U+0131, U+0131, U+0132->U+0133, U+0133, U+0134->U+0135, U+0135, U+0136->U+0137, U+0137, U+0139->U+013A, U+013A, U+013B->U+013C, U+013C, U+013D->U+013E, U+013E, U+013F->U+0140, U+0140, U+0141->U+0142, U+0142, U+0143->U+0144, U+0144, U+0145->U+0146, U+0146, U+0147->U+0148, U+0148, U+014A->U+014B, U+014B, U+014C->U+014D, U+014D, U+014E->U+014F, U+014F, U+0150->U+0151, U+0151, U+0152->U+0153, U+0153, U+0154->U+0155, U+0155, U+0156->U+0157, U+0157, U+0158->U+0159, U+0159, U+015A->U+015B, U+015B, U+015C->U+015D, U+015D, U+015E->U+015F, U+015F, U+0160->U+0161, U+0161, U+0162->U+0163, U+0163, U+0164->U+0165, U+0165, U+0166->U+0167, U+0167, U+0168->U+0169, U+0169, U+016A->U+016B, U+016B, U+016C->U+016D, U+016D, U+016E->U+016F, U+016F, U+0170->U+0171, U+0171, U+0172->U+0173, U+0173, U+0174->U+0175, U+0175, U+0176->U+0177, U+0177, U+0178->U+00FF, U+00FF, U+0179->U+017A, U+017A, U+017B->U+017C, U+017C, U+017D->U+017E, U+017E, U+0410..U+042F->U+0430..U+044F, U+0430..U+044F, U+4E00..U+9FFF'), + array('min_prefix_len', '0'), + array('min_infix_len', '0'), + ), + 'index index_phpbb_' . $this->id . '_delta : index_phpbb_' . $this->id . '_main' => array( + array('path', $this->config['fulltext_sphinx_data_path'] . 'index_phpbb_' . $this->id . '_delta'), + array('source', 'source_phpbb_' . $this->id . '_delta'), + ), + 'indexer' => array( + array('mem_limit', $this->config['fulltext_sphinx_indexer_mem_limit'] . 'M'), + ), + 'searchd' => array( + array('listen' , ($this->config['fulltext_sphinx_host'] ? $this->config['fulltext_sphinx_host'] : 'localhost') . ':' . ($this->config['fulltext_sphinx_port'] ? $this->config['fulltext_sphinx_port'] : '9312')), + array('log', $this->config['fulltext_sphinx_data_path'] . 'log/searchd.log'), + array('query_log', $this->config['fulltext_sphinx_data_path'] . 'log/sphinx-query.log'), + array('read_timeout', '5'), + array('max_children', '30'), + array('pid_file', $this->config['fulltext_sphinx_data_path'] . 'searchd.pid'), + array('binlog_path', $this->config['fulltext_sphinx_data_path']), + ), + ); + + $non_unique = array('sql_query_pre' => true, 'sql_attr_uint' => true, 'sql_attr_timestamp' => true, 'sql_attr_str2ordinal' => true, 'sql_attr_bool' => true); + $delete = array('sql_group_column' => true, 'sql_date_column' => true, 'sql_str2ordinal_column' => true); + + /** + * Allow adding/changing the Sphinx configuration data + * + * @event core.search_sphinx_modify_config_data + * @var array config_data Array with the Sphinx configuration data + * @var array non_unique Array with the Sphinx non-unique variables to delete + * @var array delete Array with the Sphinx variables to delete + * @since 3.1.7-RC1 + */ + $vars = array( + 'config_data', + 'non_unique', + 'delete', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_modify_config_data', compact($vars))); + + foreach ($config_data as $section_name => $section_data) + { + $section = $config_object->get_section_by_name($section_name); + if (!$section) + { + $section = $config_object->add_section($section_name); + } + + foreach ($delete as $key => $void) + { + $section->delete_variables_by_name($key); + } + + foreach ($non_unique as $key => $void) + { + $section->delete_variables_by_name($key); + } + + foreach ($section_data as $entry) + { + $key = $entry[0]; + $value = $entry[1]; + + if (!isset($non_unique[$key])) + { + $variable = $section->get_variable_by_name($key); + if (!$variable) + { + $section->create_variable($key, $value); + } + else + { + $variable->set_value($value); + } + } + else + { + $section->create_variable($key, $value); + } + } + } + $this->config_file_data = $config_object->get_data(); + + return true; + } + + /** + * Splits keywords entered by a user into an array of words stored in $this->split_words + * Stores the tidied search query in $this->search_query + * + * @param string $keywords Contains the keyword as entered by the user + * @param string $terms is either 'all' or 'any' + * @return false if no valid keywords were found and otherwise true + */ + public function split_keywords(&$keywords, $terms) + { + if ($terms == 'all') + { + $match = array('#\sand\s#i', '#\sor\s#i', '#\snot\s#i', '#\+#', '#-#', '#\|#', '#@#'); + $replace = array(' & ', ' | ', ' - ', ' +', ' -', ' |', ''); + + $keywords = preg_replace($match, $replace, $keywords); + $this->sphinx->SetMatchMode(SPH_MATCH_EXTENDED); + } + else + { + $this->sphinx->SetMatchMode(SPH_MATCH_ANY); + } + + // Keep quotes and new lines + $keywords = str_replace(array('"', "\n"), array('"', ' '), trim($keywords)); + + if (strlen($keywords) > 0) + { + $this->search_query = str_replace('"', '"', $keywords); + return true; + } + + return false; + } + + /** + * Performs a search on keywords depending on display specific params. You have to run split_keywords() first + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param string $fields contains either titleonly (topic titles should be searched), msgonly (only message bodies should be searched), firstpost (only subject and body of the first post should be searched) or all (all post bodies and subjects should be searched) + * @param string $terms is either 'all' (use query as entered, words without prefix should default to "have to be in field") or 'any' (ignore search query parts and just return all posts that contain any of the specified words) + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids if the author should be ignored during the search the array is empty + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, &$start, $per_page) + { + global $user, $phpbb_log; + + // No keywords? No posts. + if (!strlen($this->search_query) && !count($author_ary)) + { + return false; + } + + $id_ary = array(); + + // Sorting + + if ($type == 'topics') + { + switch ($sort_key) + { + case 'a': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'poster_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + + case 'f': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'forum_id ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + + case 'i': + + case 's': + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'post_subject ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + + case 't': + + default: + $this->sphinx->SetGroupBy('topic_id', SPH_GROUPBY_ATTR, 'topic_last_post_time ' . (($sort_dir == 'a') ? 'ASC' : 'DESC')); + break; + } + } + else + { + switch ($sort_key) + { + case 'a': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'poster_id'); + break; + + case 'f': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'forum_id'); + break; + + case 'i': + + case 's': + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_subject'); + break; + + case 't': + + default: + $this->sphinx->SetSortMode(($sort_dir == 'a') ? SPH_SORT_ATTR_ASC : SPH_SORT_ATTR_DESC, 'post_time'); + break; + } + } + + // Most narrow filters first + if ($topic_id) + { + $this->sphinx->SetFilter('topic_id', array($topic_id)); + } + + /** + * Allow modifying the Sphinx search options + * + * @event core.search_sphinx_keywords_modify_options + * @var string type Searching type ('posts', 'topics') + * @var string fields Searching fields ('titleonly', 'msgonly', 'firstpost', 'all') + * @var string terms Searching terms ('all', 'any') + * @var int sort_days Time, in days, of the oldest possible post to list + * @var string sort_key The sort type used from the possible sort types + * @var int topic_id Limit the search to this topic_id only + * @var array ex_fid_ary Which forums not to search on + * @var string post_visibility Post visibility data + * @var array author_ary Array of user_id containing the users to filter the results to + * @var string author_name The username to search on + * @var object sphinx The Sphinx searchd client object + * @since 3.1.7-RC1 + */ + $sphinx = $this->sphinx; + $vars = array( + 'type', + 'fields', + 'terms', + 'sort_days', + 'sort_key', + 'topic_id', + 'ex_fid_ary', + 'post_visibility', + 'author_ary', + 'author_name', + 'sphinx', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_keywords_modify_options', compact($vars))); + $this->sphinx = $sphinx; + unset($sphinx); + + $search_query_prefix = ''; + + switch ($fields) + { + case 'titleonly': + // Only search the title + if ($terms == 'all') + { + $search_query_prefix = '@title '; + } + // Weight for the title + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); + // 1 is first_post, 0 is not first post + $this->sphinx->SetFilter('topic_first_post', array(1)); + break; + + case 'msgonly': + // Only search the body + if ($terms == 'all') + { + $search_query_prefix = '@data '; + } + // Weight for the body + $this->sphinx->SetFieldWeights(array("title" => 1, "data" => 5)); + break; + + case 'firstpost': + // More relative weight for the title, also search the body + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); + // 1 is first_post, 0 is not first post + $this->sphinx->SetFilter('topic_first_post', array(1)); + break; + + default: + // More relative weight for the title, also search the body + $this->sphinx->SetFieldWeights(array("title" => 5, "data" => 1)); + break; + } + + if (count($author_ary)) + { + $this->sphinx->SetFilter('poster_id', $author_ary); + } + + // As this is not simply possible at the moment, we limit the result to approved posts. + // This will make it impossible for moderators to search unapproved and softdeleted posts, + // but at least it will also cause the same for normal users. + $this->sphinx->SetFilter('post_visibility', array(ITEM_APPROVED)); + + if (count($ex_fid_ary)) + { + // All forums that a user is allowed to access + $fid_ary = array_unique(array_intersect(array_keys($this->auth->acl_getf('f_read', true)), array_keys($this->auth->acl_getf('f_search', true)))); + // All forums that the user wants to and can search in + $search_forums = array_diff($fid_ary, $ex_fid_ary); + + if (count($search_forums)) + { + $this->sphinx->SetFilter('forum_id', $search_forums); + } + } + + $this->sphinx->SetFilter('deleted', array(0)); + + $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES); + $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('"', '"', $this->search_query)), $this->indexes); + + // Could be connection to localhost:9312 failed (errno=111, + // msg=Connection refused) during rotate, retry if so + $retries = SPHINX_CONNECT_RETRIES; + while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) + { + usleep(SPHINX_CONNECT_WAIT_TIME); + $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('"', '"', $this->search_query)), $this->indexes); + } + + if ($this->sphinx->GetLastError()) + { + $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_SPHINX_ERROR', false, array($this->sphinx->GetLastError())); + if ($this->auth->acl_get('a_')) + { + trigger_error($this->user->lang('SPHINX_SEARCH_FAILED', $this->sphinx->GetLastError())); + } + else + { + trigger_error($this->user->lang('SPHINX_SEARCH_FAILED_LOG')); + } + } + + $result_count = $result['total_found']; + + if ($result_count && $start >= $result_count) + { + $start = floor(($result_count - 1) / $per_page) * $per_page; + + $this->sphinx->SetLimits((int) $start, (int) $per_page, SPHINX_MAX_MATCHES); + $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('"', '"', $this->search_query)), $this->indexes); + + // Could be connection to localhost:9312 failed (errno=111, + // msg=Connection refused) during rotate, retry if so + $retries = SPHINX_CONNECT_RETRIES; + while (!$result && (strpos($this->sphinx->GetLastError(), "errno=111,") !== false) && $retries--) + { + usleep(SPHINX_CONNECT_WAIT_TIME); + $result = $this->sphinx->Query($search_query_prefix . $this->sphinx->EscapeString(str_replace('"', '"', $this->search_query)), $this->indexes); + } + } + + $id_ary = array(); + if (isset($result['matches'])) + { + if ($type == 'posts') + { + $id_ary = array_keys($result['matches']); + } + else + { + foreach ($result['matches'] as $key => $value) + { + $id_ary[] = $value['attrs']['topic_id']; + } + } + } + else + { + return false; + } + + $id_ary = array_slice($id_ary, 0, (int) $per_page); + + return $result_count; + } + + /** + * Performs a search on an author's posts without caring about message contents. Depends on display specific params + * + * @param string $type contains either posts or topics depending on what should be searched for + * @param boolean $firstpost_only if true, only topic starting posts will be considered + * @param array $sort_by_sql contains SQL code for the ORDER BY part of a query + * @param string $sort_key is the key of $sort_by_sql for the selected sorting + * @param string $sort_dir is either a or d representing ASC and DESC + * @param string $sort_days specifies the maximum amount of days a post may be old + * @param array $ex_fid_ary specifies an array of forum ids which should not be searched + * @param string $post_visibility specifies which types of posts the user can view in which forums + * @param int $topic_id is set to 0 or a topic id, if it is not 0 then only posts in this topic should be searched + * @param array $author_ary an array of author ids + * @param string $author_name specifies the author match, when ANONYMOUS is also a search-match + * @param array &$id_ary passed by reference, to be filled with ids for the page specified by $start and $per_page, should be ordered + * @param int $start indicates the first index of the page + * @param int $per_page number of ids each page is supposed to contain + * @return boolean|int total number of results + */ + public function author_search($type, $firstpost_only, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, &$id_ary, $start, $per_page) + { + $this->search_query = ''; + + $this->sphinx->SetMatchMode(SPH_MATCH_FULLSCAN); + $fields = ($firstpost_only) ? 'firstpost' : 'all'; + $terms = 'all'; + return $this->keyword_search($type, $fields, $terms, $sort_by_sql, $sort_key, $sort_dir, $sort_days, $ex_fid_ary, $post_visibility, $topic_id, $author_ary, $author_name, $id_ary, $start, $per_page); + } + + /** + * Updates wordlist and wordmatch tables when a message is posted or changed + * + * @param string $mode Contains the post mode: edit, post, reply, quote + * @param int $post_id The id of the post which is modified/created + * @param string &$message New or updated post content + * @param string &$subject New or updated post subject + * @param int $poster_id Post author's user id + * @param int $forum_id The id of the forum in which the post is located + */ + public function index($mode, $post_id, &$message, &$subject, $poster_id, $forum_id) + { + /** + * Event to modify method arguments before the Sphinx search index is updated + * + * @event core.search_sphinx_index_before + * @var string mode Contains the post mode: edit, post, reply, quote + * @var int post_id The id of the post which is modified/created + * @var string message New or updated post content + * @var string subject New or updated post subject + * @var int poster_id Post author's user id + * @var int forum_id The id of the forum in which the post is located + * @since 3.2.3-RC1 + */ + $vars = array( + 'mode', + 'post_id', + 'message', + 'subject', + 'poster_id', + 'forum_id', + ); + extract($this->phpbb_dispatcher->trigger_event('core.search_sphinx_index_before', compact($vars))); + + if ($mode == 'edit') + { + $this->sphinx->UpdateAttributes($this->indexes, array('forum_id', 'poster_id'), array((int) $post_id => array((int) $forum_id, (int) $poster_id))); + } + else if ($mode != 'post' && $post_id) + { + // Update topic_last_post_time for full topic + $sql_array = array( + 'SELECT' => 'p1.post_id', + 'FROM' => array( + POSTS_TABLE => 'p1', + ), + 'LEFT_JOIN' => array(array( + 'FROM' => array( + POSTS_TABLE => 'p2' + ), + 'ON' => 'p1.topic_id = p2.topic_id', + )), + 'WHERE' => 'p2.post_id = ' . ((int) $post_id), + ); + + $sql = $this->db->sql_build_query('SELECT', $sql_array); + $result = $this->db->sql_query($sql); + + $post_updates = array(); + $post_time = time(); + while ($row = $this->db->sql_fetchrow($result)) + { + $post_updates[(int) $row['post_id']] = array($post_time); + } + $this->db->sql_freeresult($result); + + if (count($post_updates)) + { + $this->sphinx->UpdateAttributes($this->indexes, array('topic_last_post_time'), $post_updates); + } + } + } + + /** + * Delete a post from the index after it was deleted + */ + public function index_remove($post_ids, $author_ids, $forum_ids) + { + $values = array(); + foreach ($post_ids as $post_id) + { + $values[$post_id] = array(1); + } + + $this->sphinx->UpdateAttributes($this->indexes, array('deleted'), $values); + } + + /** + * Nothing needs to be destroyed + */ + public function tidy($create = false) + { + $this->config->set('search_last_gc', time(), false); + } + + /** + * Create sphinx table + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function create_index($acp_module, $u_action) + { + if (!$this->index_created()) + { + $table_data = array( + 'COLUMNS' => array( + 'counter_id' => array('UINT', 0), + 'max_doc_id' => array('UINT', 0), + ), + 'PRIMARY_KEY' => 'counter_id', + ); + $this->db_tools->sql_create_table(SPHINX_TABLE, $table_data); + + $sql = 'TRUNCATE TABLE ' . SPHINX_TABLE; + $this->db->sql_query($sql); + + $data = array( + 'counter_id' => '1', + 'max_doc_id' => '0', + ); + $sql = 'INSERT INTO ' . SPHINX_TABLE . ' ' . $this->db->sql_build_array('INSERT', $data); + $this->db->sql_query($sql); + } + + return false; + } + + /** + * Drop sphinx table + * + * @return string|bool error string is returned incase of errors otherwise false + */ + public function delete_index($acp_module, $u_action) + { + if (!$this->index_created()) + { + return false; + } + + $this->db_tools->sql_table_drop(SPHINX_TABLE); + + return false; + } + + /** + * Returns true if the sphinx table was created + * + * @return bool true if sphinx table was created + */ + public function index_created($allow_new_files = true) + { + $created = false; + + if ($this->db_tools->sql_table_exists(SPHINX_TABLE)) + { + $created = true; + } + + return $created; + } + + /** + * Returns an associative array containing information about the indexes + * + * @return string|bool Language string of error false otherwise + */ + public function index_stats() + { + if (empty($this->stats)) + { + $this->get_stats(); + } + + return array( + $this->user->lang['FULLTEXT_SPHINX_MAIN_POSTS'] => ($this->index_created()) ? $this->stats['main_posts'] : 0, + $this->user->lang['FULLTEXT_SPHINX_DELTA_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] - $this->stats['main_posts'] : 0, + $this->user->lang['FULLTEXT_MYSQL_TOTAL_POSTS'] => ($this->index_created()) ? $this->stats['total_posts'] : 0, + ); + } + + /** + * Collects stats that can be displayed on the index maintenance page + */ + protected function get_stats() + { + if ($this->index_created()) + { + $sql = 'SELECT COUNT(post_id) as total_posts + FROM ' . POSTS_TABLE; + $result = $this->db->sql_query($sql); + $this->stats['total_posts'] = (int) $this->db->sql_fetchfield('total_posts'); + $this->db->sql_freeresult($result); + + $sql = 'SELECT COUNT(p.post_id) as main_posts + FROM ' . POSTS_TABLE . ' p, ' . SPHINX_TABLE . ' m + WHERE p.post_id <= m.max_doc_id + AND m.counter_id = 1'; + $result = $this->db->sql_query($sql); + $this->stats['main_posts'] = (int) $this->db->sql_fetchfield('main_posts'); + $this->db->sql_freeresult($result); + } + } + + /** + * Returns a list of options for the ACP to display + * + * @return associative array containing template and config variables + */ + public function acp() + { + $config_vars = array( + 'fulltext_sphinx_data_path' => 'string', + 'fulltext_sphinx_host' => 'string', + 'fulltext_sphinx_port' => 'string', + 'fulltext_sphinx_indexer_mem_limit' => 'int', + ); + + $tpl = ' + ' . $this->user->lang['FULLTEXT_SPHINX_CONFIGURE']. ' +
+

' . $this->user->lang['FULLTEXT_SPHINX_DATA_PATH_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['FULLTEXT_SPHINX_HOST_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['FULLTEXT_SPHINX_PORT_EXPLAIN'] . '
+
+
+
+

' . $this->user->lang['FULLTEXT_SPHINX_INDEXER_MEM_LIMIT_EXPLAIN'] . '
+
' . $this->user->lang['MIB'] . '
+
+
+

' . $this->user->lang['FULLTEXT_SPHINX_CONFIG_FILE_EXPLAIN'] . '
+
' . (($this->config_generate()) ? '' : $this->config_file_data) . '
+
+ '; + + // These are fields required in the config table + return array( + 'tpl' => $tpl, + 'config' => $config_vars + ); + } +} diff --git a/phpbb/search/index.htm b/phpbb/search/index.htm new file mode 100644 index 0000000..ee1f723 --- /dev/null +++ b/phpbb/search/index.htm @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/phpbb/search/sphinx/config.php b/phpbb/search/sphinx/config.php new file mode 100644 index 0000000..3205574 --- /dev/null +++ b/phpbb/search/sphinx/config.php @@ -0,0 +1,284 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search\sphinx; + +/** +* An object representing the sphinx configuration +* Can read it from file and write it back out after modification +*/ +class config +{ + private $sections = array(); + + /** + * Constructor which optionally loads data from a variable + * + * @param string $config_data Variable containing the sphinx configuration data + * + * @access public + */ + function __construct($config_data) + { + if ($config_data != '') + { + $this->read($config_data); + } + } + + /** + * Get a section object by its name + * + * @param string $name The name of the section that shall be returned + * @return \phpbb\search\sphinx\config_section The section object or null if none was found + * + * @access public + */ + function get_section_by_name($name) + { + for ($i = 0, $size = count($this->sections); $i < $size; $i++) + { + // Make sure this is really a section object and not a comment + if (($this->sections[$i] instanceof \phpbb\search\sphinx\config_section) && $this->sections[$i]->get_name() == $name) + { + return $this->sections[$i]; + } + } + } + + /** + * Appends a new empty section to the end of the config + * + * @param string $name The name for the new section + * @return \phpbb\search\sphinx\config_section The newly created section object + * + * @access public + */ + function add_section($name) + { + $this->sections[] = new \phpbb\search\sphinx\config_section($name, ''); + return $this->sections[count($this->sections) - 1]; + } + + /** + * Reads the config file data + * + * @param string $config_data The config file data + * + * @access private + */ + function read($config_data) + { + $this->sections = array(); + + $section = null; + $found_opening_bracket = false; + $in_value = false; + + foreach ($config_data as $i => $line) + { + // If the value of a variable continues to the next line because the line + // break was escaped then we don't trim leading space but treat it as a part of the value + if ($in_value) + { + $line = rtrim($line); + } + else + { + $line = trim($line); + } + + // If we're not inside a section look for one + if (!$section) + { + // Add empty lines and comments as comment objects to the section list + // that way they're not deleted when reassembling the file from the sections + if (!$line || $line[0] == '#') + { + $this->sections[] = new \phpbb\search\sphinx\config_comment($config_file[$i]); + continue; + } + else + { + // Otherwise we scan the line reading the section name until we find + // an opening curly bracket or a comment + $section_name = ''; + $section_name_comment = ''; + $found_opening_bracket = false; + for ($j = 0, $length = strlen($line); $j < $length; $j++) + { + if ($line[$j] == '#') + { + $section_name_comment = substr($line, $j); + break; + } + + if ($found_opening_bracket) + { + continue; + } + + if ($line[$j] == '{') + { + $found_opening_bracket = true; + continue; + } + + $section_name .= $line[$j]; + } + + // And then we create the new section object + $section_name = trim($section_name); + $section = new \phpbb\search\sphinx\config_section($section_name, $section_name_comment); + } + } + else + { + // If we're looking for variables inside a section + $skip_first = false; + + // If we're not in a value continuing over the line feed + if (!$in_value) + { + // Then add empty lines and comments as comment objects to the variable list + // of this section so they're not deleted on reassembly + if (!$line || $line[0] == '#') + { + $section->add_variable(new \phpbb\search\sphinx\config_comment($config_file[$i])); + continue; + } + + // As long as we haven't yet actually found an opening bracket for this section + // we treat everything as comments so it's not deleted either + if (!$found_opening_bracket) + { + if ($line[0] == '{') + { + $skip_first = true; + $line = substr($line, 1); + $found_opening_bracket = true; + } + else + { + $section->add_variable(new \phpbb\search\sphinx\config_comment($config_file[$i])); + continue; + } + } + } + + // If we did not find a comment in this line or still add to the previous + // line's value ... + if ($line || $in_value) + { + if (!$in_value) + { + $name = ''; + $value = ''; + $comment = ''; + $found_assignment = false; + } + $in_value = false; + $end_section = false; + + /* ... then we should prase this line char by char: + - first there's the variable name + - then an equal sign + - the variable value + - possibly a backslash before the linefeed in this case we need to continue + parsing the value in the next line + - a # indicating that the rest of the line is a comment + - a closing curly bracket indicating the end of this section*/ + for ($j = 0, $length = strlen($line); $j < $length; $j++) + { + if ($line[$j] == '#') + { + $comment = substr($line, $j); + break; + } + else if ($line[$j] == '}') + { + $comment = substr($line, $j + 1); + $end_section = true; + break; + } + else if (!$found_assignment) + { + if ($line[$j] == '=') + { + $found_assignment = true; + } + else + { + $name .= $line[$j]; + } + } + else + { + if ($line[$j] == '\\' && $j == $length - 1) + { + $value .= "\n"; + $in_value = true; + // Go to the next line and keep processing the value in there + continue 2; + } + $value .= $line[$j]; + } + } + + // If a name and an equal sign were found then we have append a + // new variable object to the section + if ($name && $found_assignment) + { + $section->add_variable(new \phpbb\search\sphinx\config_variable(trim($name), trim($value), ($end_section) ? '' : $comment)); + continue; + } + + /* If we found a closing curly bracket this section has been completed + and we can append it to the section list and continue with looking for + the next section */ + if ($end_section) + { + $section->set_end_comment($comment); + $this->sections[] = $section; + $section = null; + continue; + } + } + + // If we did not find anything meaningful up to here, then just treat it + // as a comment + $comment = ($skip_first) ? "\t" . substr(ltrim($config_file[$i]), 1) : $config_file[$i]; + $section->add_variable(new \phpbb\search\sphinx\config_comment($comment)); + } + } + + } + + /** + * Returns the config data + * + * @return string $data The config data that is generated + * + * @access public + */ + function get_data() + { + $data = ""; + foreach ($this->sections as $section) + { + $data .= $section->to_string(); + } + + return $data; + } +} diff --git a/phpbb/search/sphinx/config_comment.php b/phpbb/search/sphinx/config_comment.php new file mode 100644 index 0000000..b5cd0a3 --- /dev/null +++ b/phpbb/search/sphinx/config_comment.php @@ -0,0 +1,47 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search\sphinx; + +/** +* \phpbb\search\sphinx\config_comment +* Represents a comment inside the sphinx configuration +*/ +class config_comment +{ + private $exact_string; + + /** + * Create a new comment + * + * @param string $exact_string The content of the comment including newlines, leading whitespace, etc. + * + * @access public + */ + function __construct($exact_string) + { + $this->exact_string = $exact_string; + } + + /** + * Simply returns the comment as it was created + * + * @return string The exact string that was specified in the constructor + * + * @access public + */ + function to_string() + { + return $this->exact_string; + } +} diff --git a/phpbb/search/sphinx/config_section.php b/phpbb/search/sphinx/config_section.php new file mode 100644 index 0000000..2fc8b2d --- /dev/null +++ b/phpbb/search/sphinx/config_section.php @@ -0,0 +1,160 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search\sphinx; + +/** +* \phpbb\search\sphinx\config_section +* Represents a single section inside the sphinx configuration +*/ +class config_section +{ + private $name; + private $comment; + private $end_comment; + private $variables = array(); + + /** + * Construct a new section + * + * @param string $name Name of the section + * @param string $comment Comment that should be appended after the name in the + * textual format. + * + * @access public + */ + function __construct($name, $comment) + { + $this->name = $name; + $this->comment = $comment; + $this->end_comment = ''; + } + + /** + * Add a variable object to the list of variables in this section + * + * @param \phpbb\search\sphinx\config_variable $variable The variable object + * + * @access public + */ + function add_variable($variable) + { + $this->variables[] = $variable; + } + + /** + * Adds a comment after the closing bracket in the textual representation + * + * @param string $end_comment + * + * @access public + */ + function set_end_comment($end_comment) + { + $this->end_comment = $end_comment; + } + + /** + * Getter for the name of this section + * + * @return string Section's name + * + * @access public + */ + function get_name() + { + return $this->name; + } + + /** + * Get a variable object by its name + * + * @param string $name The name of the variable that shall be returned + * @return \phpbb\search\sphinx\config_section The first variable object from this section with the + * given name or null if none was found + * + * @access public + */ + function get_variable_by_name($name) + { + for ($i = 0, $size = count($this->variables); $i < $size; $i++) + { + // Make sure this is a variable object and not a comment + if (($this->variables[$i] instanceof \phpbb\search\sphinx\config_variable) && $this->variables[$i]->get_name() == $name) + { + return $this->variables[$i]; + } + } + } + + /** + * Deletes all variables with the given name + * + * @param string $name The name of the variable objects that are supposed to be removed + * + * @access public + */ + function delete_variables_by_name($name) + { + for ($i = 0, $size = count($this->variables); $i < $size; $i++) + { + // Make sure this is a variable object and not a comment + if (($this->variables[$i] instanceof \phpbb\search\sphinx\config_variable) && $this->variables[$i]->get_name() == $name) + { + array_splice($this->variables, $i, 1); + $i--; + } + } + } + + /** + * Create a new variable object and append it to the variable list of this section + * + * @param string $name The name for the new variable + * @param string $value The value for the new variable + * @return \phpbb\search\sphinx\config_variable Variable object that was created + * + * @access public + */ + function create_variable($name, $value) + { + $this->variables[] = new \phpbb\search\sphinx\config_variable($name, $value, ''); + return $this->variables[count($this->variables) - 1]; + } + + /** + * Turns this object into a string which can be written to a config file + * + * @return string Config data in textual form, parsable for sphinx + * + * @access public + */ + function to_string() + { + $content = $this->name . ' ' . $this->comment . "\n{\n"; + + // Make sure we don't get too many newlines after the opening bracket + while (trim($this->variables[0]->to_string()) == '') + { + array_shift($this->variables); + } + + foreach ($this->variables as $variable) + { + $content .= $variable->to_string(); + } + $content .= '}' . $this->end_comment . "\n"; + + return $content; + } +} diff --git a/phpbb/search/sphinx/config_variable.php b/phpbb/search/sphinx/config_variable.php new file mode 100644 index 0000000..85cee20 --- /dev/null +++ b/phpbb/search/sphinx/config_variable.php @@ -0,0 +1,78 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\search\sphinx; + +/** +* \phpbb\search\sphinx\config_variable +* Represents a single variable inside the sphinx configuration +*/ +class config_variable +{ + private $name; + private $value; + private $comment; + + /** + * Constructs a new variable object + * + * @param string $name Name of the variable + * @param string $value Value of the variable + * @param string $comment Optional comment after the variable in the + * config file + * + * @access public + */ + function __construct($name, $value, $comment) + { + $this->name = $name; + $this->value = $value; + $this->comment = $comment; + } + + /** + * Getter for the variable's name + * + * @return string The variable object's name + * + * @access public + */ + function get_name() + { + return $this->name; + } + + /** + * Allows changing the variable's value + * + * @param string $value New value for this variable + * + * @access public + */ + function set_value($value) + { + $this->value = $value; + } + + /** + * Turns this object into a string readable by sphinx + * + * @return string Config data in textual form + * + * @access public + */ + function to_string() + { + return "\t" . $this->name . ' = ' . str_replace("\n", " \\\n", $this->value) . ' ' . $this->comment . "\n"; + } +} diff --git a/phpbb/session.php b/phpbb/session.php new file mode 100644 index 0000000..31f32af --- /dev/null +++ b/phpbb/session.php @@ -0,0 +1,1650 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +/** +* Session class +*/ +class session +{ + var $cookie_data = array(); + var $page = array(); + var $data = array(); + var $browser = ''; + var $forwarded_for = ''; + var $host = ''; + var $session_id = ''; + var $ip = ''; + var $load = 0; + var $time_now = 0; + var $update_session_page = true; + + /** + * Extract current session page + * + * @param string $root_path current root path (phpbb_root_path) + * @return array + */ + static function extract_current_page($root_path) + { + global $request, $symfony_request, $phpbb_filesystem; + + $page_array = array(); + + // First of all, get the request uri... + $script_name = $request->escape($symfony_request->getScriptName(), true); + $args = $request->escape(explode('&', $symfony_request->getQueryString()), true); + + // If we are unable to get the script name we use REQUEST_URI as a failover and note it within the page array for easier support... + if (!$script_name) + { + $script_name = htmlspecialchars_decode($request->server('REQUEST_URI')); + $script_name = (($pos = strpos($script_name, '?')) !== false) ? substr($script_name, 0, $pos) : $script_name; + $page_array['failover'] = 1; + } + + // Replace backslashes and doubled slashes (could happen on some proxy setups) + $script_name = str_replace(array('\\', '//'), '/', $script_name); + + // Now, remove the sid and let us get a clean query string... + $use_args = array(); + + // Since some browser do not encode correctly we need to do this with some "special" characters... + // " -> %22, ' => %27, < -> %3C, > -> %3E + $find = array('"', "'", '<', '>', '"', '<', '>'); + $replace = array('%22', '%27', '%3C', '%3E', '%22', '%3C', '%3E'); + + foreach ($args as $key => $argument) + { + if (strpos($argument, 'sid=') === 0) + { + continue; + } + + $use_args[] = str_replace($find, $replace, $argument); + } + unset($args); + + // The following examples given are for an request uri of {path to the phpbb directory}/adm/index.php?i=10&b=2 + + // The current query string + $query_string = trim(implode('&', $use_args)); + + // basenamed page name (for example: index.php) + $page_name = (substr($script_name, -1, 1) == '/') ? '' : basename($script_name); + $page_name = urlencode(htmlspecialchars($page_name)); + + $symfony_request_path = $phpbb_filesystem->clean_path($symfony_request->getPathInfo()); + if ($symfony_request_path !== '/') + { + $page_name .= str_replace('%2F', '/', urlencode($symfony_request_path)); + } + + if (substr($root_path, 0, 2) === './' && strpos($root_path, '..') === false) + { + $root_dirs = explode('/', str_replace('\\', '/', rtrim($root_path, '/'))); + $page_dirs = explode('/', str_replace('\\', '/', '.')); + } + else + { + // current directory within the phpBB root (for example: adm) + $root_dirs = explode('/', str_replace('\\', '/', $phpbb_filesystem->realpath($root_path))); + $page_dirs = explode('/', str_replace('\\', '/', $phpbb_filesystem->realpath('./'))); + } + + $intersection = array_intersect_assoc($root_dirs, $page_dirs); + + $root_dirs = array_diff_assoc($root_dirs, $intersection); + $page_dirs = array_diff_assoc($page_dirs, $intersection); + + $page_dir = str_repeat('../', count($root_dirs)) . implode('/', $page_dirs); + + if ($page_dir && substr($page_dir, -1, 1) == '/') + { + $page_dir = substr($page_dir, 0, -1); + } + + // Current page from phpBB root (for example: adm/index.php?i=10&b=2) + $page = (($page_dir) ? $page_dir . '/' : '') . $page_name; + if ($query_string) + { + $page .= '?' . $query_string; + } + + // The script path from the webroot to the current directory (for example: /phpBB3/adm/) : always prefixed with / and ends in / + $script_path = $symfony_request->getBasePath(); + + // The script path from the webroot to the phpBB root (for example: /phpBB3/) + $script_dirs = explode('/', $script_path); + array_splice($script_dirs, -count($page_dirs)); + $root_script_path = implode('/', $script_dirs) . (count($root_dirs) ? '/' . implode('/', $root_dirs) : ''); + + // We are on the base level (phpBB root == webroot), lets adjust the variables a bit... + if (!$root_script_path) + { + $root_script_path = ($page_dir) ? str_replace($page_dir, '', $script_path) : $script_path; + } + + $script_path .= (substr($script_path, -1, 1) == '/') ? '' : '/'; + $root_script_path .= (substr($root_script_path, -1, 1) == '/') ? '' : '/'; + + $forum_id = $request->variable('f', 0); + // maximum forum id value is maximum value of mediumint unsigned column + $forum_id = ($forum_id > 0 && $forum_id < 16777215) ? $forum_id : 0; + + $page_array += array( + 'page_name' => $page_name, + 'page_dir' => $page_dir, + + 'query_string' => $query_string, + 'script_path' => str_replace(' ', '%20', htmlspecialchars($script_path)), + 'root_script_path' => str_replace(' ', '%20', htmlspecialchars($root_script_path)), + + 'page' => $page, + 'forum' => $forum_id, + ); + + return $page_array; + } + + /** + * Get valid hostname/port. HTTP_HOST is used, SERVER_NAME if HTTP_HOST not present. + */ + function extract_current_hostname() + { + global $config, $request; + + // Get hostname + $host = htmlspecialchars_decode($request->header('Host', $request->server('SERVER_NAME'))); + + // Should be a string and lowered + $host = (string) strtolower($host); + + // If host is equal the cookie domain or the server name (if config is set), then we assume it is valid + if ((isset($config['cookie_domain']) && $host === $config['cookie_domain']) || (isset($config['server_name']) && $host === $config['server_name'])) + { + return $host; + } + + // Is the host actually a IP? If so, we use the IP... (IPv4) + if (long2ip(ip2long($host)) === $host) + { + return $host; + } + + // Now return the hostname (this also removes any port definition). The http:// is prepended to construct a valid URL, hosts never have a scheme assigned + $host = @parse_url('http://' . $host); + $host = (!empty($host['host'])) ? $host['host'] : ''; + + // Remove any portions not removed by parse_url (#) + $host = str_replace('#', '', $host); + + // If, by any means, the host is now empty, we will use a "best approach" way to guess one + if (empty($host)) + { + if (!empty($config['server_name'])) + { + $host = $config['server_name']; + } + else if (!empty($config['cookie_domain'])) + { + $host = (strpos($config['cookie_domain'], '.') === 0) ? substr($config['cookie_domain'], 1) : $config['cookie_domain']; + } + else + { + // Set to OS hostname or localhost + $host = (function_exists('php_uname')) ? php_uname('n') : 'localhost'; + } + } + + // It may be still no valid host, but for sure only a hostname (we may further expand on the cookie domain... if set) + return $host; + } + + /** + * Start session management + * + * This is where all session activity begins. We gather various pieces of + * information from the client and server. We test to see if a session already + * exists. If it does, fine and dandy. If it doesn't we'll go on to create a + * new one ... pretty logical heh? We also examine the system load (if we're + * running on a system which makes such information readily available) and + * halt if it's above an admin definable limit. + * + * @param bool $update_session_page if true the session page gets updated. + * This can be set to circumvent certain scripts to update the users last visited page. + */ + function session_begin($update_session_page = true) + { + global $phpEx, $SID, $_SID, $_EXTRA_URL, $db, $config, $phpbb_root_path; + global $request, $phpbb_container, $user, $phpbb_log, $phpbb_dispatcher; + + // Give us some basic information + $this->time_now = time(); + $this->cookie_data = array('u' => 0, 'k' => ''); + $this->update_session_page = $update_session_page; + $this->browser = $request->header('User-Agent'); + $this->referer = $request->header('Referer'); + $this->forwarded_for = $request->header('X-Forwarded-For'); + + $this->host = $this->extract_current_hostname(); + $this->page = $this->extract_current_page($phpbb_root_path); + + // if the forwarded for header shall be checked we have to validate its contents + if ($config['forwarded_for_check']) + { + $this->forwarded_for = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $this->forwarded_for)); + + // split the list of IPs + $ips = explode(' ', $this->forwarded_for); + foreach ($ips as $ip) + { + // check IPv4 first, the IPv6 is hopefully only going to be used very seldomly + if (!empty($ip) && !preg_match(get_preg_expression('ipv4'), $ip) && !preg_match(get_preg_expression('ipv6'), $ip)) + { + // contains invalid data, don't use the forwarded for header + $this->forwarded_for = ''; + break; + } + } + } + else + { + $this->forwarded_for = ''; + } + + if ($request->is_set($config['cookie_name'] . '_sid', \phpbb\request\request_interface::COOKIE) || $request->is_set($config['cookie_name'] . '_u', \phpbb\request\request_interface::COOKIE)) + { + $this->cookie_data['u'] = $request->variable($config['cookie_name'] . '_u', 0, false, \phpbb\request\request_interface::COOKIE); + $this->cookie_data['k'] = $request->variable($config['cookie_name'] . '_k', '', false, \phpbb\request\request_interface::COOKIE); + $this->session_id = $request->variable($config['cookie_name'] . '_sid', '', false, \phpbb\request\request_interface::COOKIE); + + $SID = (defined('NEED_SID')) ? '?sid=' . $this->session_id : '?sid='; + $_SID = (defined('NEED_SID')) ? $this->session_id : ''; + + if (empty($this->session_id)) + { + $this->session_id = $_SID = $request->variable('sid', ''); + $SID = '?sid=' . $this->session_id; + $this->cookie_data = array('u' => 0, 'k' => ''); + } + } + else + { + $this->session_id = $_SID = $request->variable('sid', ''); + $SID = '?sid=' . $this->session_id; + } + + $_EXTRA_URL = array(); + + // Why no forwarded_for et al? Well, too easily spoofed. With the results of my recent requests + // it's pretty clear that in the majority of cases you'll at least be left with a proxy/cache ip. + $ip = htmlspecialchars_decode($request->server('REMOTE_ADDR')); + $ip = preg_replace('# {2,}#', ' ', str_replace(',', ' ', $ip)); + + /** + * Event to alter user IP address + * + * @event core.session_ip_after + * @var string ip REMOTE_ADDR + * @since 3.1.10-RC1 + */ + $vars = array('ip'); + extract($phpbb_dispatcher->trigger_event('core.session_ip_after', compact($vars))); + + // split the list of IPs + $ips = explode(' ', trim($ip)); + + // Default IP if REMOTE_ADDR is invalid + $this->ip = '127.0.0.1'; + + foreach ($ips as $ip) + { + if (function_exists('phpbb_ip_normalise')) + { + // Normalise IP address + $ip = phpbb_ip_normalise($ip); + + if (empty($ip)) + { + // IP address is invalid. + break; + } + + // IP address is valid. + $this->ip = $ip; + + // Skip legacy code. + continue; + } + + if (preg_match(get_preg_expression('ipv4'), $ip)) + { + $this->ip = $ip; + } + else if (preg_match(get_preg_expression('ipv6'), $ip)) + { + // Quick check for IPv4-mapped address in IPv6 + if (stripos($ip, '::ffff:') === 0) + { + $ipv4 = substr($ip, 7); + + if (preg_match(get_preg_expression('ipv4'), $ipv4)) + { + $ip = $ipv4; + } + } + + $this->ip = $ip; + } + else + { + // We want to use the last valid address in the chain + // Leave foreach loop when address is invalid + break; + } + } + + $this->load = false; + + // Load limit check (if applicable) + if ($config['limit_load'] || $config['limit_search_load']) + { + if ((function_exists('sys_getloadavg') && $load = sys_getloadavg()) || ($load = explode(' ', @file_get_contents('/proc/loadavg')))) + { + $this->load = array_slice($load, 0, 1); + $this->load = floatval($this->load[0]); + } + else + { + $config->set('limit_load', '0'); + $config->set('limit_search_load', '0'); + } + } + + // if no session id is set, redirect to index.php + $session_id = $request->variable('sid', ''); + if (defined('NEED_SID') && (empty($session_id) || $this->session_id !== $session_id)) + { + send_status_line(401, 'Unauthorized'); + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + // if session id is set + if (!empty($this->session_id)) + { + $sql = 'SELECT u.*, s.* + FROM ' . SESSIONS_TABLE . ' s, ' . USERS_TABLE . " u + WHERE s.session_id = '" . $db->sql_escape($this->session_id) . "' + AND u.user_id = s.session_user_id"; + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + // Did the session exist in the DB? + if (isset($this->data['user_id'])) + { + // Validate IP length according to admin ... enforces an IP + // check on bots if admin requires this +// $quadcheck = ($config['ip_check_bot'] && $this->data['user_type'] & USER_BOT) ? 4 : $config['ip_check']; + + if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false) + { + $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']); + $u_ip = short_ipv6($this->ip, $config['ip_check']); + } + else + { + $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); + $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); + } + + $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : ''; + $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : ''; + + $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; + $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; + + // referer checks + // The @ before $config['referer_validation'] suppresses notices present while running the updater + $check_referer_path = (@$config['referer_validation'] == REFERER_VALIDATE_PATH); + $referer_valid = true; + + // we assume HEAD and TRACE to be foul play and thus only whitelist GET + if (@$config['referer_validation'] && strtolower($request->server('REQUEST_METHOD')) !== 'get') + { + $referer_valid = $this->validate_referer($check_referer_path); + } + + if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for && $referer_valid) + { + $session_expired = false; + + // Check whether the session is still valid if we have one + /* @var $provider_collection \phpbb\auth\provider_collection */ + $provider_collection = $phpbb_container->get('auth.provider_collection'); + $provider = $provider_collection->get_provider(); + + if (!($provider instanceof \phpbb\auth\provider\provider_interface)) + { + throw new \RuntimeException($provider . ' must implement \phpbb\auth\provider\provider_interface'); + } + + $ret = $provider->validate_session($this->data); + if ($ret !== null && !$ret) + { + $session_expired = true; + } + + if (!$session_expired) + { + // Check the session length timeframe if autologin is not enabled. + // Else check the autologin length... and also removing those having autologin enabled but no longer allowed board-wide. + if (!$this->data['session_autologin']) + { + if ($this->data['session_time'] < $this->time_now - ($config['session_length'] + 60)) + { + $session_expired = true; + } + } + else if (!$config['allow_autologin'] || ($config['max_autologin_time'] && $this->data['session_time'] < $this->time_now - (86400 * (int) $config['max_autologin_time']) + 60)) + { + $session_expired = true; + } + } + + if (!$session_expired) + { + $this->data['is_registered'] = ($this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; + $this->data['is_bot'] = (!$this->data['is_registered'] && $this->data['user_id'] != ANONYMOUS) ? true : false; + $this->data['user_lang'] = basename($this->data['user_lang']); + + // Is user banned? Are they excluded? Won't return on ban, exists within method + $this->check_ban_for_current_session($config); + + return true; + } + } + else + { + // Added logging temporarly to help debug bugs... + if (defined('DEBUG') && $this->data['user_id'] != ANONYMOUS) + { + if ($referer_valid) + { + $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IP_BROWSER_FORWARDED_CHECK', false, array( + $u_ip, + $s_ip, + $u_browser, + $s_browser, + htmlspecialchars($u_forwarded_for), + htmlspecialchars($s_forwarded_for) + )); + } + else + { + $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_REFERER_INVALID', false, array($this->referer)); + } + } + } + } + } + + // If we reach here then no (valid) session exists. So we'll create a new one + return $this->session_create(); + } + + /** + * Create a new session + * + * If upon trying to start a session we discover there is nothing existing we + * jump here. Additionally this method is called directly during login to regenerate + * the session for the specific user. In this method we carry out a number of tasks; + * garbage collection, (search)bot checking, banned user comparison. Basically + * though this method will result in a new session for a specific user. + */ + function session_create($user_id = false, $set_admin = false, $persist_login = false, $viewonline = true) + { + global $SID, $_SID, $db, $config, $cache, $phpbb_container, $phpbb_dispatcher; + + $this->data = array(); + + /* Garbage collection ... remove old sessions updating user information + // if necessary. It means (potentially) 11 queries but only infrequently + if ($this->time_now > $config['session_last_gc'] + $config['session_gc']) + { + $this->session_gc(); + }*/ + + // Do we allow autologin on this board? No? Then override anything + // that may be requested here + if (!$config['allow_autologin']) + { + $this->cookie_data['k'] = $persist_login = false; + } + + /** + * Here we do a bot check, oh er saucy! No, not that kind of bot + * check. We loop through the list of bots defined by the admin and + * see if we have any useragent and/or IP matches. If we do, this is a + * bot, act accordingly + */ + $bot = false; + $active_bots = $cache->obtain_bots(); + + foreach ($active_bots as $row) + { + if ($row['bot_agent'] && preg_match('#' . str_replace('\*', '.*?', preg_quote($row['bot_agent'], '#')) . '#i', $this->browser)) + { + $bot = $row['user_id']; + } + + // If ip is supplied, we will make sure the ip is matching too... + if ($row['bot_ip'] && ($bot || !$row['bot_agent'])) + { + // Set bot to false, then we only have to set it to true if it is matching + $bot = false; + + foreach (explode(',', $row['bot_ip']) as $bot_ip) + { + $bot_ip = trim($bot_ip); + + if (!$bot_ip) + { + continue; + } + + if (strpos($this->ip, $bot_ip) === 0) + { + $bot = (int) $row['user_id']; + break; + } + } + } + + if ($bot) + { + break; + } + } + + /* @var $provider_collection \phpbb\auth\provider_collection */ + $provider_collection = $phpbb_container->get('auth.provider_collection'); + $provider = $provider_collection->get_provider(); + $this->data = $provider->autologin(); + + if ($user_id !== false && isset($this->data['user_id']) && $this->data['user_id'] != $user_id) + { + $this->data = array(); + } + + if (isset($this->data['user_id'])) + { + $this->cookie_data['k'] = ''; + $this->cookie_data['u'] = $this->data['user_id']; + } + + // If we're presented with an autologin key we'll join against it. + // Else if we've been passed a user_id we'll grab data based on that + if (isset($this->cookie_data['k']) && $this->cookie_data['k'] && $this->cookie_data['u'] && empty($this->data)) + { + $sql = 'SELECT u.* + FROM ' . USERS_TABLE . ' u, ' . SESSIONS_KEYS_TABLE . ' k + WHERE u.user_id = ' . (int) $this->cookie_data['u'] . ' + AND u.user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ") + AND k.user_id = u.user_id + AND k.key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'"; + $result = $db->sql_query($sql); + $user_data = $db->sql_fetchrow($result); + + if ($user_id === false || (isset($user_data['user_id']) && $user_id == $user_data['user_id'])) + { + $this->data = $user_data; + $bot = false; + } + + $db->sql_freeresult($result); + } + + if ($user_id !== false && empty($this->data)) + { + $this->cookie_data['k'] = ''; + $this->cookie_data['u'] = $user_id; + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $this->cookie_data['u'] . ' + AND user_type IN (' . USER_NORMAL . ', ' . USER_FOUNDER . ')'; + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + $bot = false; + } + + // Bot user, if they have a SID in the Request URI we need to get rid of it + // otherwise they'll index this page with the SID, duplicate content oh my! + if ($bot && isset($_GET['sid'])) + { + send_status_line(301, 'Moved Permanently'); + redirect(build_url(array('sid'))); + } + + // If no data was returned one or more of the following occurred: + // Key didn't match one in the DB + // User does not exist + // User is inactive + // User is bot + if (!is_array($this->data) || !count($this->data)) + { + $this->cookie_data['k'] = ''; + $this->cookie_data['u'] = ($bot) ? $bot : ANONYMOUS; + + if (!$bot) + { + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $this->cookie_data['u']; + } + else + { + // We give bots always the same session if it is not yet expired. + $sql = 'SELECT u.*, s.* + FROM ' . USERS_TABLE . ' u + LEFT JOIN ' . SESSIONS_TABLE . ' s ON (s.session_user_id = u.user_id) + WHERE u.user_id = ' . (int) $bot; + } + + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + if ($this->data['user_id'] != ANONYMOUS && !$bot) + { + $this->data['session_last_visit'] = (isset($this->data['session_time']) && $this->data['session_time']) ? $this->data['session_time'] : (($this->data['user_lastvisit']) ? $this->data['user_lastvisit'] : time()); + } + else + { + $this->data['session_last_visit'] = $this->time_now; + } + + // Force user id to be integer... + $this->data['user_id'] = (int) $this->data['user_id']; + + // At this stage we should have a filled data array, defined cookie u and k data. + // data array should contain recent session info if we're a real user and a recent + // session exists in which case session_id will also be set + + // Is user banned? Are they excluded? Won't return on ban, exists within method + $this->check_ban_for_current_session($config); + + $this->data['is_registered'] = (!$bot && $this->data['user_id'] != ANONYMOUS && ($this->data['user_type'] == USER_NORMAL || $this->data['user_type'] == USER_FOUNDER)) ? true : false; + $this->data['is_bot'] = ($bot) ? true : false; + + // If our friend is a bot, we re-assign a previously assigned session + if ($this->data['is_bot'] && $bot == $this->data['user_id'] && $this->data['session_id']) + { + // Only assign the current session if the ip, browser and forwarded_for match... + if (strpos($this->ip, ':') !== false && strpos($this->data['session_ip'], ':') !== false) + { + $s_ip = short_ipv6($this->data['session_ip'], $config['ip_check']); + $u_ip = short_ipv6($this->ip, $config['ip_check']); + } + else + { + $s_ip = implode('.', array_slice(explode('.', $this->data['session_ip']), 0, $config['ip_check'])); + $u_ip = implode('.', array_slice(explode('.', $this->ip), 0, $config['ip_check'])); + } + + $s_browser = ($config['browser_check']) ? trim(strtolower(substr($this->data['session_browser'], 0, 149))) : ''; + $u_browser = ($config['browser_check']) ? trim(strtolower(substr($this->browser, 0, 149))) : ''; + + $s_forwarded_for = ($config['forwarded_for_check']) ? substr($this->data['session_forwarded_for'], 0, 254) : ''; + $u_forwarded_for = ($config['forwarded_for_check']) ? substr($this->forwarded_for, 0, 254) : ''; + + if ($u_ip === $s_ip && $s_browser === $u_browser && $s_forwarded_for === $u_forwarded_for) + { + $this->session_id = $this->data['session_id']; + + // Only update session DB a minute or so after last update or if page changes + if ($this->time_now - $this->data['session_time'] > 60 || ($this->update_session_page && $this->data['session_page'] != $this->page['page'])) + { + // Update the last visit time + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + } + + $SID = '?sid='; + $_SID = ''; + return true; + } + else + { + // If the ip and browser does not match make sure we only have one bot assigned to one session + $db->sql_query('DELETE FROM ' . SESSIONS_TABLE . ' WHERE session_user_id = ' . $this->data['user_id']); + } + } + + $session_autologin = (($this->cookie_data['k'] || $persist_login) && $this->data['is_registered']) ? true : false; + $set_admin = ($set_admin && $this->data['is_registered']) ? true : false; + + // Create or update the session + $sql_ary = array( + 'session_user_id' => (int) $this->data['user_id'], + 'session_start' => (int) $this->time_now, + 'session_last_visit' => (int) $this->data['session_last_visit'], + 'session_time' => (int) $this->time_now, + 'session_browser' => (string) trim(substr($this->browser, 0, 149)), + 'session_forwarded_for' => (string) $this->forwarded_for, + 'session_ip' => (string) $this->ip, + 'session_autologin' => ($session_autologin) ? 1 : 0, + 'session_admin' => ($set_admin) ? 1 : 0, + 'session_viewonline' => ($viewonline) ? 1 : 0, + ); + + if ($this->update_session_page) + { + $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199); + $sql_ary['session_forum_id'] = $this->page['forum']; + } + + $db->sql_return_on_error(true); + + $sql = 'DELETE + FROM ' . SESSIONS_TABLE . ' + WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\' + AND session_user_id = ' . ANONYMOUS; + + if (!defined('IN_ERROR_HANDLER') && (!$this->session_id || !$db->sql_query($sql) || !$db->sql_affectedrows())) + { + // Limit new sessions in 1 minute period (if required) + if (empty($this->data['session_time']) && $config['active_sessions']) + { +// $db->sql_return_on_error(false); + + $sql = 'SELECT COUNT(session_id) AS sessions + FROM ' . SESSIONS_TABLE . ' + WHERE session_time >= ' . ($this->time_now - 60); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ((int) $row['sessions'] > (int) $config['active_sessions']) + { + send_status_line(503, 'Service Unavailable'); + trigger_error('BOARD_UNAVAILABLE'); + } + } + } + + // Since we re-create the session id here, the inserted row must be unique. Therefore, we display potential errors. + // Commented out because it will not allow forums to update correctly +// $db->sql_return_on_error(false); + + // Something quite important: session_page always holds the *last* page visited, except for the *first* visit. + // We are not able to simply have an empty session_page btw, therefore we need to tell phpBB how to detect this special case. + // If the session id is empty, we have a completely new one and will set an "identifier" here. This identifier is able to be checked later. + if (empty($this->data['session_id'])) + { + // This is a temporary variable, only set for the very first visit + $this->data['session_created'] = true; + } + + $this->session_id = $this->data['session_id'] = md5(unique_id()); + + $sql_ary['session_id'] = (string) $this->session_id; + $sql_ary['session_page'] = (string) substr($this->page['page'], 0, 199); + $sql_ary['session_forum_id'] = $this->page['forum']; + + $sql = 'INSERT INTO ' . SESSIONS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + + $db->sql_return_on_error(false); + + // Regenerate autologin/persistent login key + if ($session_autologin) + { + $this->set_login_key(); + } + + // refresh data + $SID = '?sid=' . $this->session_id; + $_SID = $this->session_id; + $this->data = array_merge($this->data, $sql_ary); + + if (!$bot) + { + $cookie_expire = $this->time_now + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000); + + $this->set_cookie('u', $this->cookie_data['u'], $cookie_expire); + $this->set_cookie('k', $this->cookie_data['k'], $cookie_expire); + $this->set_cookie('sid', $this->session_id, $cookie_expire); + + unset($cookie_expire); + + $sql = 'SELECT COUNT(session_id) AS sessions + FROM ' . SESSIONS_TABLE . ' + WHERE session_user_id = ' . (int) $this->data['user_id'] . ' + AND session_time >= ' . (int) ($this->time_now - (max((int) $config['session_length'], (int) $config['form_token_lifetime']))); + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ((int) $row['sessions'] <= 1 || empty($this->data['user_form_salt'])) + { + $this->data['user_form_salt'] = unique_id(); + // Update the form key + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_form_salt = \'' . $db->sql_escape($this->data['user_form_salt']) . '\' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + } + } + else + { + $this->data['session_time'] = $this->data['session_last_visit'] = $this->time_now; + + // Update the last visit time + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + + $SID = '?sid='; + $_SID = ''; + } + + $session_data = $sql_ary; + /** + * Event to send new session data to extension + * Read-only event + * + * @event core.session_create_after + * @var array session_data Associative array of session keys to be updated + * @since 3.1.6-RC1 + */ + $vars = array('session_data'); + extract($phpbb_dispatcher->trigger_event('core.session_create_after', compact($vars))); + unset($session_data); + + return true; + } + + /** + * Kills a session + * + * This method does what it says on the tin. It will delete a pre-existing session. + * It resets cookie information (destroying any autologin key within that cookie data) + * and update the users information from the relevant session data. It will then + * grab guest user information. + */ + function session_kill($new_session = true) + { + global $SID, $_SID, $db, $phpbb_container, $phpbb_dispatcher; + + $sql = 'DELETE FROM ' . SESSIONS_TABLE . " + WHERE session_id = '" . $db->sql_escape($this->session_id) . "' + AND session_user_id = " . (int) $this->data['user_id']; + $db->sql_query($sql); + + $user_id = (int) $this->data['user_id']; + $session_id = $this->session_id; + /** + * Event to send session kill information to extension + * Read-only event + * + * @event core.session_kill_after + * @var int user_id user_id of the session user. + * @var string session_id current user's session_id + * @var bool new_session should we create new session for user + * @since 3.1.6-RC1 + */ + $vars = array('user_id', 'session_id', 'new_session'); + extract($phpbb_dispatcher->trigger_event('core.session_kill_after', compact($vars))); + unset($user_id); + unset($session_id); + + // Allow connecting logout with external auth method logout + /* @var $provider_collection \phpbb\auth\provider_collection */ + $provider_collection = $phpbb_container->get('auth.provider_collection'); + $provider = $provider_collection->get_provider(); + $provider->logout($this->data, $new_session); + + if ($this->data['user_id'] != ANONYMOUS) + { + // Delete existing session, update last visit info first! + if (!isset($this->data['session_time'])) + { + $this->data['session_time'] = time(); + } + + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $this->data['session_time'] . ' + WHERE user_id = ' . (int) $this->data['user_id']; + $db->sql_query($sql); + + if ($this->cookie_data['k']) + { + $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' + WHERE user_id = ' . (int) $this->data['user_id'] . " + AND key_id = '" . $db->sql_escape(md5($this->cookie_data['k'])) . "'"; + $db->sql_query($sql); + } + + // Reset the data array + $this->data = array(); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . ANONYMOUS; + $result = $db->sql_query($sql); + $this->data = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + $cookie_expire = $this->time_now - 31536000; + $this->set_cookie('u', '', $cookie_expire); + $this->set_cookie('k', '', $cookie_expire); + $this->set_cookie('sid', '', $cookie_expire); + unset($cookie_expire); + + $SID = '?sid='; + $this->session_id = $_SID = ''; + + // To make sure a valid session is created we create one for the anonymous user + if ($new_session) + { + $this->session_create(ANONYMOUS); + } + + return true; + } + + /** + * Session garbage collection + * + * This looks a lot more complex than it really is. Effectively we are + * deleting any sessions older than an admin definable limit. Due to the + * way in which we maintain session data we have to ensure we update user + * data before those sessions are destroyed. In addition this method + * removes autologin key information that is older than an admin defined + * limit. + */ + function session_gc() + { + global $db, $config, $phpbb_container, $phpbb_dispatcher; + + $batch_size = 10; + + if (!$this->time_now) + { + $this->time_now = time(); + } + + // Firstly, delete guest sessions + $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' + WHERE session_user_id = ' . ANONYMOUS . ' + AND session_time < ' . (int) ($this->time_now - $config['session_length']); + $db->sql_query($sql); + + // Get expired sessions, only most recent for each user + $sql = 'SELECT session_user_id, session_page, MAX(session_time) AS recent_time + FROM ' . SESSIONS_TABLE . ' + WHERE session_time < ' . ($this->time_now - $config['session_length']) . ' + GROUP BY session_user_id, session_page'; + $result = $db->sql_query_limit($sql, $batch_size); + + $del_user_id = array(); + $del_sessions = 0; + + while ($row = $db->sql_fetchrow($result)) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $row['recent_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' + WHERE user_id = " . (int) $row['session_user_id']; + $db->sql_query($sql); + + $del_user_id[] = (int) $row['session_user_id']; + $del_sessions++; + } + $db->sql_freeresult($result); + + if (count($del_user_id)) + { + // Delete expired sessions + $sql = 'DELETE FROM ' . SESSIONS_TABLE . ' + WHERE ' . $db->sql_in_set('session_user_id', $del_user_id) . ' + AND session_time < ' . ($this->time_now - $config['session_length']); + $db->sql_query($sql); + } + + if ($del_sessions < $batch_size) + { + // Less than 10 users, update gc timer ... else we want gc + // called again to delete other sessions + $config->set('session_last_gc', $this->time_now, false); + + if ($config['max_autologin_time']) + { + $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' + WHERE last_login < ' . (time() - (86400 * (int) $config['max_autologin_time'])); + $db->sql_query($sql); + } + + // only called from CRON; should be a safe workaround until the infrastructure gets going + /* @var $captcha_factory \phpbb\captcha\factory */ + $captcha_factory = $phpbb_container->get('captcha.factory'); + $captcha_factory->garbage_collect($config['captcha_plugin']); + + $sql = 'DELETE FROM ' . LOGIN_ATTEMPT_TABLE . ' + WHERE attempt_time < ' . (time() - (int) $config['ip_login_limit_time']); + $db->sql_query($sql); + } + + /** + * Event to trigger extension on session_gc + * + * @event core.session_gc_after + * @since 3.1.6-RC1 + */ + $phpbb_dispatcher->dispatch('core.session_gc_after'); + + return; + } + + /** + * Sets a cookie + * + * Sets a cookie of the given name with the specified data for the given length of time. If no time is specified, a session cookie will be set. + * + * @param string $name Name of the cookie, will be automatically prefixed with the phpBB cookie name. track becomes [cookie_name]_track then. + * @param string $cookiedata The data to hold within the cookie + * @param int $cookietime The expiration time as UNIX timestamp. If 0 is provided, a session cookie is set. + * @param bool $httponly Use HttpOnly. Defaults to true. Use false to make cookie accessible by client-side scripts. + */ + function set_cookie($name, $cookiedata, $cookietime, $httponly = true) + { + global $config; + + // If headers are already set, we just return + if (headers_sent()) + { + return; + } + + $name_data = rawurlencode($config['cookie_name'] . '_' . $name) . '=' . rawurlencode($cookiedata); + $expire = gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime); + $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == '127.0.0.1' || strpos($config['cookie_domain'], '.') === false) ? '' : '; domain=' . $config['cookie_domain']; + + header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . ';' . (($httponly) ? ' HttpOnly' : ''), false); + } + + /** + * Check for banned user + * + * Checks whether the supplied user is banned by id, ip or email. If no parameters + * are passed to the method pre-existing session data is used. + * + * @param int|false $user_id The user id + * @param mixed $user_ips Can contain a string with one IP or an array of multiple IPs + * @param string|false $user_email The user email + * @param bool $return If $return is false this routine does not return on finding a banned user, + * it outputs a relevant message and stops execution. + */ + function check_ban($user_id = false, $user_ips = false, $user_email = false, $return = false) + { + global $config, $db, $phpbb_dispatcher; + + if (defined('IN_CHECK_BAN') || defined('SKIP_CHECK_BAN')) + { + return; + } + + $banned = false; + $cache_ttl = 3600; + $where_sql = array(); + + $sql = 'SELECT ban_ip, ban_userid, ban_email, ban_exclude, ban_give_reason, ban_end + FROM ' . BANLIST_TABLE . ' + WHERE '; + + // Determine which entries to check, only return those + if ($user_email === false) + { + $where_sql[] = "ban_email = ''"; + } + + if ($user_ips === false) + { + $where_sql[] = "(ban_ip = '' OR ban_exclude = 1)"; + } + + if ($user_id === false) + { + $where_sql[] = '(ban_userid = 0 OR ban_exclude = 1)'; + } + else + { + $cache_ttl = ($user_id == ANONYMOUS) ? 3600 : 0; + $_sql = '(ban_userid = ' . $user_id; + + if ($user_email !== false) + { + $_sql .= " OR ban_email <> ''"; + } + + if ($user_ips !== false) + { + $_sql .= " OR ban_ip <> ''"; + } + + $_sql .= ')'; + + $where_sql[] = $_sql; + } + + $sql .= (count($where_sql)) ? implode(' AND ', $where_sql) : ''; + $result = $db->sql_query($sql, $cache_ttl); + + $ban_triggered_by = 'user'; + while ($row = $db->sql_fetchrow($result)) + { + if ($row['ban_end'] && $row['ban_end'] < time()) + { + continue; + } + + $ip_banned = false; + if (!empty($row['ban_ip'])) + { + if (!is_array($user_ips)) + { + $ip_banned = preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ips); + } + else + { + foreach ($user_ips as $user_ip) + { + if (preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_ip'], '#')) . '$#i', $user_ip)) + { + $ip_banned = true; + break; + } + } + } + } + + if ((!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) || + $ip_banned || + (!empty($row['ban_email']) && preg_match('#^' . str_replace('\*', '.*?', preg_quote($row['ban_email'], '#')) . '$#i', $user_email))) + { + if (!empty($row['ban_exclude'])) + { + $banned = false; + break; + } + else + { + $banned = true; + $ban_row = $row; + + if (!empty($row['ban_userid']) && intval($row['ban_userid']) == $user_id) + { + $ban_triggered_by = 'user'; + } + else if ($ip_banned) + { + $ban_triggered_by = 'ip'; + } + else + { + $ban_triggered_by = 'email'; + } + + // Don't break. Check if there is an exclude rule for this user + } + } + } + $db->sql_freeresult($result); + + /** + * Event to set custom ban type + * + * @event core.session_set_custom_ban + * @var bool return If $return is false this routine does not return on finding a banned user, it outputs a relevant message and stops execution + * @var bool banned Check if user already banned + * @var array|false ban_row Ban data + * @var string ban_triggered_by Method that caused ban, can be your custom method + * @since 3.1.3-RC1 + */ + $ban_row = isset($ban_row) ? $ban_row : false; + $vars = array('return', 'banned', 'ban_row', 'ban_triggered_by'); + extract($phpbb_dispatcher->trigger_event('core.session_set_custom_ban', compact($vars))); + + if ($banned && !$return) + { + global $phpbb_root_path, $phpEx; + + // If the session is empty we need to create a valid one... + if (empty($this->session_id)) + { + // This seems to be no longer needed? - #14971 +// $this->session_create(ANONYMOUS); + } + + // Initiate environment ... since it won't be set at this stage + $this->setup(); + + // Logout the user, banned users are unable to use the normal 'logout' link + if ($this->data['user_id'] != ANONYMOUS) + { + $this->session_kill(); + } + + // We show a login box here to allow founders accessing the board if banned by IP + if (defined('IN_LOGIN') && $this->data['user_id'] == ANONYMOUS) + { + $this->setup('ucp'); + $this->data['is_registered'] = $this->data['is_bot'] = false; + + // Set as a precaution to allow login_box() handling this case correctly as well as this function not being executed again. + define('IN_CHECK_BAN', 1); + + login_box("index.$phpEx"); + + // The false here is needed, else the user is able to circumvent the ban. + $this->session_kill(false); + } + + // Ok, we catch the case of an empty session id for the anonymous user... + // This can happen if the user is logging in, banned by username and the login_box() being called "again". + if (empty($this->session_id) && defined('IN_CHECK_BAN')) + { + $this->session_create(ANONYMOUS); + } + + // Determine which message to output + $till_date = ($ban_row['ban_end']) ? $this->format_date($ban_row['ban_end']) : ''; + $message = ($ban_row['ban_end']) ? 'BOARD_BAN_TIME' : 'BOARD_BAN_PERM'; + + $contact_link = phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx); + $message = sprintf($this->lang[$message], $till_date, '', ''); + $message .= ($ban_row['ban_give_reason']) ? '

' . sprintf($this->lang['BOARD_BAN_REASON'], $ban_row['ban_give_reason']) : ''; + $message .= '

' . $this->lang['BAN_TRIGGERED_BY_' . strtoupper($ban_triggered_by)] . ''; + + // A very special case... we are within the cron script which is not supposed to print out the ban message... show blank page + if (defined('IN_CRON')) + { + garbage_collection(); + exit_handler(); + exit; + } + + // To circumvent session_begin returning a valid value and the check_ban() not called on second page view, we kill the session again + $this->session_kill(false); + + trigger_error($message); + } + + if (!empty($ban_row)) + { + $ban_row['ban_triggered_by'] = $ban_triggered_by; + } + + return ($banned && $ban_row) ? $ban_row : $banned; + } + + /** + * Check the current session for bans + * + * @return true if session user is banned. + */ + protected function check_ban_for_current_session($config) + { + if (!defined('SKIP_CHECK_BAN') && $this->data['user_type'] != USER_FOUNDER) + { + if (!$config['forwarded_for_check']) + { + $this->check_ban($this->data['user_id'], $this->ip); + } + else + { + $ips = explode(' ', $this->forwarded_for); + $ips[] = $this->ip; + $this->check_ban($this->data['user_id'], $ips); + } + } + } + + /** + * Check if ip is blacklisted + * This should be called only where absolutely necessary + * + * Only IPv4 (rbldns does not support AAAA records/IPv6 lookups) + * + * @author satmd (from the php manual) + * @param string $mode register/post - spamcop for example is ommitted for posting + * @param string|false $ip the IPv4 address to check + * + * @return false if ip is not blacklisted, else an array([checked server], [lookup]) + */ + function check_dnsbl($mode, $ip = false) + { + if ($ip === false) + { + $ip = $this->ip; + } + + // Neither Spamhaus nor Spamcop supports IPv6 addresses. + if (strpos($ip, ':') !== false) + { + return false; + } + + $dnsbl_check = array( + 'sbl.spamhaus.org' => 'http://www.spamhaus.org/query/bl?ip=', + ); + + if ($mode == 'register') + { + $dnsbl_check['bl.spamcop.net'] = 'http://spamcop.net/bl.shtml?'; + } + + if ($ip) + { + $quads = explode('.', $ip); + $reverse_ip = $quads[3] . '.' . $quads[2] . '.' . $quads[1] . '.' . $quads[0]; + + // Need to be listed on all servers... + $listed = true; + $info = array(); + + foreach ($dnsbl_check as $dnsbl => $lookup) + { + if (phpbb_checkdnsrr($reverse_ip . '.' . $dnsbl . '.', 'A') === true) + { + $info = array($dnsbl, $lookup . $ip); + } + else + { + $listed = false; + } + } + + if ($listed) + { + return $info; + } + } + + return false; + } + + /** + * Check if URI is blacklisted + * This should be called only where absolutly necessary, for example on the submitted website field + * This function is not in use at the moment and is only included for testing purposes, it may not work at all! + * This means it is untested at the moment and therefore commented out + * + * @param string $uri URI to check + * @return true if uri is on blacklist, else false. Only blacklist is checked (~zero FP), no grey lists + function check_uribl($uri) + { + // Normally parse_url() is not intended to parse uris + // We need to get the top-level domain name anyway... change. + $uri = parse_url($uri); + + if ($uri === false || empty($uri['host'])) + { + return false; + } + + $uri = trim($uri['host']); + + if ($uri) + { + // One problem here... the return parameter for the "windows" method is different from what + // we expect... this may render this check useless... + if (phpbb_checkdnsrr($uri . '.multi.uribl.com.', 'A') === true) + { + return true; + } + } + + return false; + } + */ + + /** + * Set/Update a persistent login key + * + * This method creates or updates a persistent session key. When a user makes + * use of persistent (formerly auto-) logins a key is generated and stored in the + * DB. When they revisit with the same key it's automatically updated in both the + * DB and cookie. Multiple keys may exist for each user representing different + * browsers or locations. As with _any_ non-secure-socket no passphrase login this + * remains vulnerable to exploit. + */ + function set_login_key($user_id = false, $key = false, $user_ip = false) + { + global $db; + + $user_id = ($user_id === false) ? $this->data['user_id'] : $user_id; + $user_ip = ($user_ip === false) ? $this->ip : $user_ip; + $key = ($key === false) ? (($this->cookie_data['k']) ? $this->cookie_data['k'] : false) : $key; + + $key_id = unique_id(hexdec(substr($this->session_id, 0, 8))); + + $sql_ary = array( + 'key_id' => (string) md5($key_id), + 'last_ip' => (string) $user_ip, + 'last_login' => (int) time() + ); + + if (!$key) + { + $sql_ary += array( + 'user_id' => (int) $user_id + ); + } + + if ($key) + { + $sql = 'UPDATE ' . SESSIONS_KEYS_TABLE . ' + SET ' . $db->sql_build_array('UPDATE', $sql_ary) . ' + WHERE user_id = ' . (int) $user_id . " + AND key_id = '" . $db->sql_escape(md5($key)) . "'"; + } + else + { + $sql = 'INSERT INTO ' . SESSIONS_KEYS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + } + $db->sql_query($sql); + + $this->cookie_data['k'] = $key_id; + + return false; + } + + /** + * Reset all login keys for the specified user + * + * This method removes all current login keys for a specified (or the current) + * user. It will be called on password change to render old keys unusable + */ + function reset_login_keys($user_id = false) + { + global $db; + + $user_id = ($user_id === false) ? (int) $this->data['user_id'] : (int) $user_id; + + $sql = 'DELETE FROM ' . SESSIONS_KEYS_TABLE . ' + WHERE user_id = ' . (int) $user_id; + $db->sql_query($sql); + + // If the user is logged in, update last visit info first before deleting sessions + $sql = 'SELECT session_time, session_page + FROM ' . SESSIONS_TABLE . ' + WHERE session_user_id = ' . (int) $user_id . ' + ORDER BY session_time DESC'; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if ($row) + { + $sql = 'UPDATE ' . USERS_TABLE . ' + SET user_lastvisit = ' . (int) $row['session_time'] . ", user_lastpage = '" . $db->sql_escape($row['session_page']) . "' + WHERE user_id = " . (int) $user_id; + $db->sql_query($sql); + } + + // Let's also clear any current sessions for the specified user_id + // If it's the current user then we'll leave this session intact + $sql_where = 'session_user_id = ' . (int) $user_id; + $sql_where .= ($user_id === (int) $this->data['user_id']) ? " AND session_id <> '" . $db->sql_escape($this->session_id) . "'" : ''; + + $sql = 'DELETE FROM ' . SESSIONS_TABLE . " + WHERE $sql_where"; + $db->sql_query($sql); + + // We're changing the password of the current user and they have a key + // Lets regenerate it to be safe + if ($user_id === (int) $this->data['user_id'] && $this->cookie_data['k']) + { + $this->set_login_key($user_id); + } + } + + + /** + * Check if the request originated from the same page. + * @param bool $check_script_path If true, the path will be checked as well + */ + function validate_referer($check_script_path = false) + { + global $config, $request; + + // no referer - nothing to validate, user's fault for turning it off (we only check on POST; so meta can't be the reason) + if (empty($this->referer) || empty($this->host)) + { + return true; + } + + $host = htmlspecialchars($this->host); + $ref = substr($this->referer, strpos($this->referer, '://') + 3); + + if (!(stripos($ref, $host) === 0) && (!$config['force_server_vars'] || !(stripos($ref, $config['server_name']) === 0))) + { + return false; + } + else if ($check_script_path && rtrim($this->page['root_script_path'], '/') !== '') + { + $ref = substr($ref, strlen($host)); + $server_port = $request->server('SERVER_PORT', 0); + + if ($server_port !== 80 && $server_port !== 443 && stripos($ref, ":$server_port") === 0) + { + $ref = substr($ref, strlen(":$server_port")); + } + + if (!(stripos(rtrim($ref, '/'), rtrim($this->page['root_script_path'], '/')) === 0)) + { + return false; + } + } + + return true; + } + + + function unset_admin() + { + global $db; + $sql = 'UPDATE ' . SESSIONS_TABLE . ' + SET session_admin = 0 + WHERE session_id = \'' . $db->sql_escape($this->session_id) . '\''; + $db->sql_query($sql); + } + + /** + * Update the session data + * + * @param array $session_data associative array of session keys to be updated + * @param string $session_id optional session_id, defaults to current user's session_id + */ + public function update_session($session_data, $session_id = null) + { + global $db, $phpbb_dispatcher; + + $session_id = ($session_id) ? $session_id : $this->session_id; + + $sql = 'UPDATE ' . SESSIONS_TABLE . ' SET ' . $db->sql_build_array('UPDATE', $session_data) . " + WHERE session_id = '" . $db->sql_escape($session_id) . "'"; + $db->sql_query($sql); + + /** + * Event to send update session information to extension + * Read-only event + * + * @event core.update_session_after + * @var array session_data Associative array of session keys to be updated + * @var string session_id current user's session_id + * @since 3.1.6-RC1 + */ + $vars = array('session_data', 'session_id'); + extract($phpbb_dispatcher->trigger_event('core.update_session_after', compact($vars))); + } + + public function update_session_infos() + { + global $config, $db, $request; + + // No need to update if it's a new session. Informations are already inserted by session_create() + if (isset($this->data['session_created']) && $this->data['session_created']) + { + return; + } + + // Do not update the session page for ajax requests, so the view online still works as intended + $page_changed = $this->update_session_page && $this->data['session_page'] != $this->page['page'] && !$request->is_ajax(); + + // Only update session DB a minute or so after last update or if page changes + if ($this->time_now - (isset($this->data['session_time']) ? $this->data['session_time'] : 0) > 60 || $page_changed) + { + $sql_ary = array('session_time' => $this->time_now); + + if ($page_changed) + { + $sql_ary['session_page'] = substr($this->page['page'], 0, 199); + $sql_ary['session_forum_id'] = $this->page['forum']; + } + + $db->sql_return_on_error(true); + + $this->update_session($sql_ary); + + $db->sql_return_on_error(false); + + $this->data = array_merge($this->data, $sql_ary); + + if ($this->data['user_id'] != ANONYMOUS && isset($config['new_member_post_limit']) && $this->data['user_new'] && $config['new_member_post_limit'] <= $this->data['user_posts']) + { + $this->leave_newly_registered(); + } + } + } +} diff --git a/phpbb/symfony_request.php b/phpbb/symfony_request.php new file mode 100644 index 0000000..2931cae --- /dev/null +++ b/phpbb/symfony_request.php @@ -0,0 +1,39 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb; + +use Symfony\Component\HttpFoundation\Request; + +/** + * WARNING: The Symfony request does not escape the input and should be used very carefully + * prefer the phpbb request as possible + */ +class symfony_request extends Request +{ + /** + * Constructor + * + * @param \phpbb\request\request_interface $phpbb_request + */ + public function __construct(\phpbb\request\request_interface $phpbb_request) + { + $get_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::GET); + $post_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::POST); + $server_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::SERVER); + $files_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::FILES); + $cookie_parameters = $phpbb_request->get_super_global(\phpbb\request\request_interface::COOKIE); + + parent::__construct($get_parameters, $post_parameters, array(), $cookie_parameters, $files_parameters, $server_parameters); + } +} diff --git a/phpbb/template/asset.php b/phpbb/template/asset.php new file mode 100644 index 0000000..cb00f16 --- /dev/null +++ b/phpbb/template/asset.php @@ -0,0 +1,210 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template; + +class asset +{ + protected $components = array(); + + /** @var \phpbb\path_helper **/ + protected $path_helper; + + /** @var \phpbb\filesystem\filesystem */ + protected $filesystem; + + /** + * Constructor + * + * @param string $url URL + * @param \phpbb\path_helper $path_helper Path helper object + * @param \phpbb\filesystem\filesystem $filesystem + */ + public function __construct($url, \phpbb\path_helper $path_helper, \phpbb\filesystem\filesystem $filesystem) + { + $this->path_helper = $path_helper; + $this->filesystem = $filesystem; + + $this->set_url($url); + } + + /** + * Set URL + * + * @param string $url URL + */ + public function set_url($url) + { + if (version_compare(PHP_VERSION, '5.4.7') < 0 && substr($url, 0, 2) === '//') + { + // Workaround for PHP 5.4.6 and older bug #62844 - add fake scheme and then remove it + $this->components = parse_url('http:' . $url); + $this->components['scheme'] = ''; + return; + } + $this->components = parse_url($url); + } + + /** + * Convert URL components into string + * + * @param array $components URL components + * @return string URL + */ + protected function join_url($components) + { + $path = ''; + if (isset($components['scheme'])) + { + $path = $components['scheme'] === '' ? '//' : $components['scheme'] . '://'; + } + + if (isset($components['user']) || isset($components['pass'])) + { + if ($path === '' && !isset($components['port'])) + { + $path = '//'; + } + $path .= $components['user']; + if (isset($components['pass'])) + { + $path .= ':' . $components['pass']; + } + $path .= '@'; + } + + if (isset($components['host'])) + { + if ($path === '' && !isset($components['port'])) + { + $path = '//'; + } + $path .= $components['host']; + if (isset($components['port'])) + { + $path .= ':' . $components['port']; + } + } + + if (isset($components['path'])) + { + $path .= $components['path']; + } + + if (isset($components['query'])) + { + $path .= '?' . $components['query']; + } + + if (isset($components['fragment'])) + { + $path .= '#' . $components['fragment']; + } + + return $path; + } + + /** + * Get URL + * + * @return string URL + */ + public function get_url() + { + return $this->path_helper->update_web_root_path($this->join_url($this->components)); + } + + /** + * Checks if URL is local and relative + * + * @return boolean True if URL is local and relative + */ + public function is_relative() + { + if (empty($this->components) || !isset($this->components['path'])) + { + // Invalid URL + return false; + } + return !isset($this->components['scheme']) && !isset($this->components['host']) && substr($this->components['path'], 0, 1) !== '/'; + } + + /** + * Get path component of current URL + * + * @return string Path + */ + public function get_path() + { + return isset($this->components['path']) ? $this->components['path'] : ''; + } + + /** + * Set path component + * + * @param string $path Path component + * @param boolean $urlencode If true, parts of path should be encoded with rawurlencode() + */ + public function set_path($path, $urlencode = false) + { + // Since 1.7.0 Twig returns the real path of the file. We need it to be relative. + $real_root_path = $this->filesystem->realpath($this->path_helper->get_phpbb_root_path()) . DIRECTORY_SEPARATOR; + + // If the asset is under the phpBB root path we need to remove its path and then prepend $phpbb_root_path + if ($real_root_path && substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path) + { + $path = $this->path_helper->get_phpbb_root_path() . str_replace('\\', '/', substr($path, strlen($real_root_path))); + } + else + { + // Else we make the path relative to the current working directory + $real_root_path = $this->filesystem->realpath('.') . DIRECTORY_SEPARATOR; + if ($real_root_path && substr($path . DIRECTORY_SEPARATOR, 0, strlen($real_root_path)) === $real_root_path) + { + $path = str_replace('\\', '/', substr($path, strlen($real_root_path))); + } + } + + if ($urlencode) + { + $paths = explode('/', $path); + foreach ($paths as &$dir) + { + $dir = rawurlencode($dir); + } + $path = implode('/', $paths); + } + + $this->components['path'] = $path; + } + + /** + * Add assets_version parameter to URL. + * Parameter will not be added if assets_version already exists in URL + * + * @param string $version Version + */ + public function add_assets_version($version) + { + if (!isset($this->components['query'])) + { + $this->components['query'] = 'assets_version=' . $version; + return; + } + $query = $this->components['query']; + if (!preg_match('/(^|[&;])assets_version=/', $query)) + { + $this->components['query'] = $query . '&assets_version=' . $version; + } + } +} diff --git a/phpbb/template/assets_bag.php b/phpbb/template/assets_bag.php new file mode 100644 index 0000000..067b0eb --- /dev/null +++ b/phpbb/template/assets_bag.php @@ -0,0 +1,95 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template; + +class assets_bag +{ + /** @var asset[] */ + protected $stylesheets = []; + + /** @var asset[] */ + protected $scripts = []; + + /** + * Add a css asset to the bag + * + * @param asset $asset + */ + public function add_stylesheet(asset $asset) + { + $this->stylesheets[] = $asset; + } + + /** + * Add a js script asset to the bag + * + * @param asset $asset + */ + public function add_script(asset $asset) + { + $this->scripts[] = $asset; + } + + /** + * Returns all css assets + * + * @return asset[] + */ + public function get_stylesheets() + { + return $this->stylesheets; + } + + /** + * Returns all js assets + * + * @return asset[] + */ + public function get_scripts() + { + return $this->scripts; + } + + /** + * Returns the HTML code to includes all css assets + * + * @return string + */ + public function get_stylesheets_content() + { + $output = ''; + foreach ($this->stylesheets as $stylesheet) + { + $output .= "get_url()}\" rel=\"stylesheet\" media=\"screen\" />\n"; + } + + return $output; + } + + /** + * Returns the HTML code to includes all js assets + * + * @return string + */ + public function get_scripts_content() + { + $output = ''; + foreach ($this->scripts as $script) + { + $output .= "\n"; + } + + return $output; + } +} diff --git a/phpbb/template/base.php b/phpbb/template/base.php new file mode 100644 index 0000000..d502ace --- /dev/null +++ b/phpbb/template/base.php @@ -0,0 +1,193 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template; + +abstract class base implements template +{ + /** + * Template context. + * Stores template data used during template rendering. + * + * @var \phpbb\template\context + */ + protected $context; + + /** + * Array of filenames assigned to set_filenames + * + * @var array + */ + protected $filenames = array(); + + /** + * {@inheritdoc} + */ + public function set_filenames(array $filename_array) + { + $this->filenames = array_merge($this->filenames, $filename_array); + + return $this; + } + + /** + * Get a filename from the handle + * + * @param string $handle + * @return string + */ + protected function get_filename_from_handle($handle) + { + return (isset($this->filenames[$handle])) ? $this->filenames[$handle] : $handle; + } + + /** + * {@inheritdoc} + */ + public function destroy() + { + $this->context->clear(); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function destroy_block_vars($blockname) + { + $this->context->destroy_block_vars($blockname); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function assign_vars(array $vararray) + { + foreach ($vararray as $key => $val) + { + $this->assign_var($key, $val); + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function assign_var($varname, $varval) + { + $this->context->assign_var($varname, $varval); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function append_var($varname, $varval) + { + $this->context->append_var($varname, $varval); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function retrieve_vars(array $vararray) + { + $result = array(); + foreach ($vararray as $varname) + { + $result[$varname] = $this->retrieve_var($varname); + } + return $result; + } + + /** + * {@inheritdoc} + */ + public function retrieve_var($varname) + { + return $this->context->retrieve_var($varname); + } + + /** + * {@inheritdoc} + */ + public function assign_block_vars($blockname, array $vararray) + { + $this->context->assign_block_vars($blockname, $vararray); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function assign_block_vars_array($blockname, array $block_vars_array) + { + $this->context->assign_block_vars_array($blockname, $block_vars_array); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function retrieve_block_vars($blockname, array $vararray) + { + return $this->context->retrieve_block_vars($blockname, $vararray); + } + + /** + * {@inheritdoc} + */ + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') + { + return $this->context->alter_block_array($blockname, $vararray, $key, $mode); + } + + /** + * {@inheritdoc} + */ + public function find_key_index($blockname, $key) + { + return $this->context->find_key_index($blockname, $key); + } + + /** + * Calls hook if any is defined. + * + * @param string $handle Template handle being displayed. + * @param string $method Method name of the caller. + */ + protected function call_hook($handle, $method) + { + global $phpbb_hook; + + if (!empty($phpbb_hook) && $phpbb_hook->call_hook(array('template', $method), $handle, $this)) + { + if ($phpbb_hook->hook_return(array('template', $method))) + { + $result = $phpbb_hook->hook_return_result(array('template', $method)); + return array($result); + } + } + + return false; + } +} diff --git a/phpbb/template/context.php b/phpbb/template/context.php new file mode 100644 index 0000000..202e29c --- /dev/null +++ b/phpbb/template/context.php @@ -0,0 +1,631 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template; + +/** +* Stores variables assigned to template. +*/ +class context +{ + /** + * variable that holds all the data we'll be substituting into + * the compiled templates. Takes form: + * --> $this->tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value + * if it's a root-level variable, it'll be like this: + * --> $this->tpldata[.][0][varname] == value + * + * @var array + */ + private $tpldata = array('.' => array(0 => array())); + + /** + * @var array Reference to template->tpldata['.'][0] + */ + private $rootref; + + /** + * @var bool + */ + private $num_rows_is_set; + + public function __construct() + { + $this->clear(); + } + + /** + * Clears template data set. + */ + public function clear() + { + $this->tpldata = array('.' => array(0 => array())); + $this->rootref = &$this->tpldata['.'][0]; + $this->num_rows_is_set = false; + } + + /** + * Assign a single scalar value to a single key. + * + * Value can be a string, an integer or a boolean. + * + * @param string $varname Variable name + * @param string $varval Value to assign to variable + * @return true + */ + public function assign_var($varname, $varval) + { + $this->rootref[$varname] = $varval; + + return true; + } + + /** + * Append text to the string value stored in a key. + * + * Text is appended using the string concatenation operator (.). + * + * @param string $varname Variable name + * @param string $varval Value to append to variable + * @return true + */ + public function append_var($varname, $varval) + { + $this->rootref[$varname] = (isset($this->rootref[$varname]) ? $this->rootref[$varname] : '') . $varval; + + return true; + } + + /** + * Retreive a single scalar value from a single key. + * + * @param string $varname Variable name + * @return mixed Variable value, or null if not set + */ + public function retrieve_var($varname) + { + return isset($this->rootref[$varname]) ? $this->rootref[$varname] : null; + } + + /** + * Returns a reference to template data array. + * + * This function is public so that template renderer may invoke it. + * Users should alter template variables via functions in \phpbb\template\template. + * + * Note: modifying returned array will affect data stored in the context. + * + * @return array template data + */ + public function &get_data_ref() + { + // returning a reference directly is not + // something php is capable of doing + $ref = &$this->tpldata; + + if (!$this->num_rows_is_set) + { + /* + * We do not set S_NUM_ROWS while adding a row, to reduce the complexity + * If we would set it on adding, each subsequent adding would cause + * n modifications, resulting in a O(n!) complexity, rather then O(n) + */ + foreach ($ref as $loop_name => &$loop_data) + { + if ($loop_name === '.') + { + continue; + } + + $this->set_num_rows($loop_data); + } + $this->num_rows_is_set = true; + } + + return $ref; + } + + /** + * Set S_NUM_ROWS for each row in this template block + * + * @param array $loop_data + */ + protected function set_num_rows(&$loop_data) + { + $s_num_rows = count($loop_data); + foreach ($loop_data as &$mod_block) + { + foreach ($mod_block as $sub_block_name => &$sub_block) + { + // If the key name is lowercase and the data is an array, + // it could be a template loop. So we set the S_NUM_ROWS there + // aswell. + if ($sub_block_name === strtolower($sub_block_name) && is_array($sub_block)) + { + $this->set_num_rows($sub_block); + } + } + + // Check whether we are inside a block before setting the variable + if (isset($mod_block['S_BLOCK_NAME'])) + { + $mod_block['S_NUM_ROWS'] = $s_num_rows; + } + } + } + + /** + * Returns a reference to template root scope. + * + * This function is public so that template renderer may invoke it. + * Users should not need to invoke this function. + * + * Note: modifying returned array will affect data stored in the context. + * + * @return array template data + */ + public function &get_root_ref() + { + // rootref is already a reference + return $this->rootref; + } + + /** + * Assign key variable pairs from an array to a specified block + * + * @param string $blockname Name of block to assign $vararray to + * @param array $vararray A hash of variable name => value pairs + * @return true + */ + public function assign_block_vars($blockname, array $vararray) + { + $this->num_rows_is_set = false; + + // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 + $blocks = explode('.', $blockname); + $blockcount = count($blocks) - 1; + + $block = &$this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + $pos = strpos($blocks[$i], '['); + $name = ($pos !== false) ? substr($blocks[$i], 0, $pos) : $blocks[$i]; + $block = &$block[$name]; + $block_count = empty($block) ? 0 : count($block) - 1; + $index = (!$pos || strpos($blocks[$i], '[]') === $pos) ? $block_count : (min((int) substr($blocks[$i], $pos + 1, -1), $block_count)); + $block = &$block[$index]; + } + + // $block = &$block[$blocks[$i]]; // Do not traverse the last block as it might be empty + $name = $blocks[$i]; + + // Assign S_ROW_COUNT and S_ROW_NUM + $s_row_count = isset($block[$name]) ? count($block[$name]) : 0; + $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count; + + // Assign S_FIRST_ROW + if (!$s_row_count) + { + $vararray['S_FIRST_ROW'] = true; + } + + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $name; + + // Now the tricky part, we always assign S_LAST_ROW and remove the entry before + // This is much more clever than going through the complete template data on display (phew) + $vararray['S_LAST_ROW'] = true; + if ($s_row_count > 0) + { + unset($block[$name][($s_row_count - 1)]['S_LAST_ROW']); + } + + // Now we add the block that we're actually assigning to. + // We're adding a new iteration to this block with the given + // variable assignments. + $block[$name][] = $vararray; + + return true; + } + + /** + * Assign key variable pairs from an array to a whole specified block loop + * + * @param string $blockname Name of block to assign $block_vars_array to + * @param array $block_vars_array An array of hashes of variable name => value pairs + * @return true + */ + public function assign_block_vars_array($blockname, array $block_vars_array) + { + foreach ($block_vars_array as $vararray) + { + $this->assign_block_vars($blockname, $vararray); + } + + return true; + } + + /** + * Retrieve key variable pairs from the specified block + * + * @param string $blockname Name of block to retrieve $vararray from + * @param array $vararray An array of variable names, empty array retrieves all vars + * @return array of hashes with variable name as key and retrieved value or null as value + */ + public function retrieve_block_vars($blockname, array $vararray) + { + // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 + $blocks = explode('.', $blockname); + $blockcount = count($blocks) - 1; + + $block = $this->tpldata; + for ($i = 0; $i <= $blockcount; $i++) + { + if (($pos = strpos($blocks[$i], '[')) !== false) + { + $name = substr($blocks[$i], 0, $pos); + + if (empty($block[$name])) + { + return array(); + } + + if (strpos($blocks[$i], '[]') === $pos) + { + $index = count($block[$name]) - 1; + } + else + { + $index = min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1); + } + } + else + { + $name = $blocks[$i]; + if (empty($block[$name])) + { + return array(); + } + + $index = count($block[$name]) - 1; + } + $block = $block[$name]; + $block = $block[$index]; + } + + $result = array(); + if ($vararray === array()) + { + // The calculated vars that depend on the block position are excluded from the complete block returned results + $excluded_vars = array('S_FIRST_ROW', 'S_LAST_ROW', 'S_BLOCK_NAME', 'S_NUM_ROWS', 'S_ROW_COUNT', 'S_ROW_NUM'); + + foreach ($block as $varname => $varvalue) + { + if ($varname === strtoupper($varname) && !is_array($varvalue) && !in_array($varname, $excluded_vars)) + { + $result[$varname] = $varvalue; + } + } + } + else + { + foreach ($vararray as $varname) + { + $result[$varname] = isset($block[$varname]) ? $block[$varname] : null; + } + } + return $result; + } + + /** + * Find the index for a specified key in the innermost specified block + * + * @param string $blockname the blockname, for example 'loop' + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to search for] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @return mixed false if not found, index position otherwise; be sure to test with === + */ + public function find_key_index($blockname, $key) + { + // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 + $blocks = explode('.', $blockname); + $blockcount = count($blocks) - 1; + + $block = $this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + $pos = strpos($blocks[$i], '['); + $name = ($pos !== false) ? substr($blocks[$i], 0, $pos) : $blocks[$i]; + + if (!isset($block[$name])) + { + return false; + } + + $index = (!$pos || strpos($blocks[$i], '[]') === $pos) ? (count($block[$name]) - 1) : (min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1)); + + if (!isset($block[$name][$index])) + { + return false; + } + $block = $block[$name][$index]; + } + + if (!isset($block[$blocks[$i]])) + { + return false; + } + $block = $block[$blocks[$i]]; // Traverse the last block + + // Change key to zero (change first position) if false and to last position if true + if (is_bool($key)) + { + return (!$key) ? 0 : count($block) - 1; + } + + // Get correct position if array given + if (is_array($key)) + { + // Search array to get correct position + list($search_key, $search_value) = @each($key); + foreach ($block as $i => $val_ary) + { + if ($val_ary[$search_key] === $search_value) + { + return $i; + } + } + } + + return (is_int($key) && ((0 <= $key) && ($key < count($block)))) ? $key : false; + } + + /** + * Change already assigned key variable pair (one-dimensional - single loop entry) + * + * An example of how to use this function: + * {@example alter_block_array.php} + * + * @param string $blockname the blockname, for example 'loop' + * @param array $vararray the var array to insert/add or merge + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to change or insert at directly given] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @param string $mode Mode to execute (valid modes are 'insert', 'change' and 'delete') + * + * If insert, the vararray is inserted at the given position (position counting from zero). + * If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new \value). + * If delete, the vararray is ignored, and the block at the given position (counting from zero) is removed. + * + * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) + * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) + * + * @return bool false on error, true on success + */ + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert') + { + $this->num_rows_is_set = false; + + // For nested block, $blockcount > 0, for top-level block, $blockcount == 0 + $blocks = explode('.', $blockname); + $blockcount = count($blocks) - 1; + + $block = &$this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + if (($pos = strpos($blocks[$i], '[')) !== false) + { + $name = substr($blocks[$i], 0, $pos); + + if (strpos($blocks[$i], '[]') === $pos) + { + $index = count($block[$name]) - 1; + } + else + { + $index = min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1); + } + } + else + { + $name = $blocks[$i]; + $index = count($block[$name]) - 1; + } + $block = &$block[$name]; + $block = &$block[$index]; + } + $name = $blocks[$i]; + + // If last block does not exist and we are inserting, and not searching for key, we create it empty; otherwise, nothing to do + if (!isset($block[$name])) + { + if ($mode != 'insert' || is_array($key)) + { + return false; + } + $block[$name] = array(); + } + + $block = &$block[$name]; // Now we can traverse the last block + + // Change key to zero (change first position) if false and to last position if true + if ($key === false || $key === true) + { + $key = ($key === false) ? 0 : count($block); + } + + // Get correct position if array given + if (is_array($key)) + { + // Search array to get correct position + list($search_key, $search_value) = @each($key); + + $key = null; + foreach ($block as $i => $val_ary) + { + if ($val_ary[$search_key] === $search_value) + { + $key = $i; + break; + } + } + + // key/value pair not found + if ($key === null) + { + return false; + } + } + + // Insert Block + if ($mode == 'insert') + { + // Make sure we are not exceeding the last iteration + if ($key >= count($block)) + { + $key = count($block); + unset($block[($key - 1)]['S_LAST_ROW']); + $vararray['S_LAST_ROW'] = true; + } + if ($key <= 0) + { + $key = 0; + unset($block[0]['S_FIRST_ROW']); + $vararray['S_FIRST_ROW'] = true; + } + + // Assign S_BLOCK_NAME + $vararray['S_BLOCK_NAME'] = $name; + + // Re-position template blocks + for ($i = count($block); $i > $key; $i--) + { + $block[$i] = $block[$i-1]; + + $block[$i]['S_ROW_COUNT'] = $block[$i]['S_ROW_NUM'] = $i; + } + + // Insert vararray at given position + $block[$key] = $vararray; + $block[$key]['S_ROW_COUNT'] = $block[$key]['S_ROW_NUM'] = $key; + + return true; + } + + // Which block to change? + if ($mode == 'change') + { + // If key is out of bounds, do not change anything + if ($key > count($block) || $key < 0) + { + return false; + } + + if ($key == count($block)) + { + $key--; + } + + $block[$key] = array_merge($block[$key], $vararray); + + return true; + } + + // Delete Block + if ($mode == 'delete') + { + // If we are exceeding last iteration, do not delete anything + if ($key > count($block) || $key < 0) + { + return false; + } + + // If we are positioned at the end, we remove the last element + if ($key == count($block)) + { + $key--; + } + + // We are deleting the last element in the block, so remove the block + if (count($block) === 1) + { + $block = null; // unset($block); does not work on references + return true; + } + + // Re-position template blocks + for ($i = $key; $i < count($block)-1; $i++) + { + $block[$i] = $block[$i+1]; + $block[$i]['S_ROW_COUNT'] = $block[$i]['S_ROW_NUM'] = $i; + } + + // Remove the last element + unset($block[$i]); + + // Set first and last elements again, in case they were removed + $block[0]['S_FIRST_ROW'] = true; + $block[count($block)-1]['S_LAST_ROW'] = true; + + return true; + } + + return false; + } + + /** + * Reset/empty complete block + * + * @param string $blockname Name of block to destroy + * @return true + */ + public function destroy_block_vars($blockname) + { + $this->num_rows_is_set = false; + if (strpos($blockname, '.') !== false) + { + // Nested block. + $blocks = explode('.', $blockname); + $blockcount = count($blocks) - 1; + + $str = &$this->tpldata; + for ($i = 0; $i < $blockcount; $i++) + { + $str = &$str[$blocks[$i]]; + $str = &$str[count($str) - 1]; + } + + unset($str[$blocks[$blockcount]]); + } + else + { + // Top-level block. + unset($this->tpldata[$blockname]); + } + + return true; + } +} diff --git a/phpbb/template/exception/user_object_not_available.php b/phpbb/template/exception/user_object_not_available.php new file mode 100644 index 0000000..62fd274 --- /dev/null +++ b/phpbb/template/exception/user_object_not_available.php @@ -0,0 +1,22 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\template\exception; + +/** + * This exception is thrown when the user object was not set but it is required by the called method + */ +class user_object_not_available extends \phpbb\exception\runtime_exception +{ + +} diff --git a/phpbb/template/template.php b/phpbb/template/template.php new file mode 100644 index 0000000..df83d5b --- /dev/null +++ b/phpbb/template/template.php @@ -0,0 +1,224 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template; + +interface template +{ + + /** + * Clear the cache + * + * @return \phpbb\template\template + */ + public function clear_cache(); + + /** + * Sets the template filenames for handles. + * + * @param array $filename_array Should be a hash of handle => filename pairs. + * @return \phpbb\template\template $this + */ + public function set_filenames(array $filename_array); + + /** + * Get the style tree of the style preferred by the current user + * + * @return array Style tree, most specific first + */ + public function get_user_style(); + + /** + * Set style location based on (current) user's chosen style. + * + * @param array $style_directories The directories to add style paths for + * E.g. array('ext/foo/bar/styles', 'styles') + * Default: array('styles') (phpBB's style directory) + * @return \phpbb\template\template $this + */ + public function set_style($style_directories = array('styles')); + + /** + * Set custom style location (able to use directory outside of phpBB). + * + * Note: Templates are still compiled to phpBB's cache directory. + * + * @param string|array $names Array of names or string of name of template(s) in inheritance tree order, used by extensions. + * @param string|array or string $paths Array of style paths, relative to current root directory + * @return \phpbb\template\template $this + */ + public function set_custom_style($names, $paths); + + /** + * Clears all variables and blocks assigned to this template. + * + * @return \phpbb\template\template $this + */ + public function destroy(); + + /** + * Reset/empty complete block + * + * @param string $blockname Name of block to destroy + * @return \phpbb\template\template $this + */ + public function destroy_block_vars($blockname); + + /** + * Display a template for provided handle. + * + * The template will be loaded and compiled, if necessary, first. + * + * This function calls hooks. + * + * @param string $handle Handle to display + * @return \phpbb\template\template $this + */ + public function display($handle); + + /** + * Display the handle and assign the output to a template variable + * or return the compiled result. + * + * @param string $handle Handle to operate on + * @param string $template_var Template variable to assign compiled handle to + * @param bool $return_content If true return compiled handle, otherwise assign to $template_var + * @return \phpbb\template\template|string if $return_content is true return string of the compiled handle, otherwise return $this + */ + public function assign_display($handle, $template_var = '', $return_content = true); + + /** + * Assign key variable pairs from an array + * + * @param array $vararray A hash of variable name => value pairs + * @return \phpbb\template\template $this + */ + public function assign_vars(array $vararray); + + /** + * Assign a single scalar value to a single key. + * + * Value can be a string, an integer or a boolean. + * + * @param string $varname Variable name + * @param string $varval Value to assign to variable + * @return \phpbb\template\template $this + */ + public function assign_var($varname, $varval); + + /** + * Append text to the string value stored in a key. + * + * Text is appended using the string concatenation operator (.). + * + * @param string $varname Variable name + * @param string $varval Value to append to variable + * @return \phpbb\template\template $this + */ + public function append_var($varname, $varval); + + /** + * Retrieve multiple template values + * + * @param array $vararray An array with variable names + * @return array A hash of variable name => value pairs (value is null if not set) + */ + public function retrieve_vars(array $vararray); + + /** + * Retreive a single scalar value from a single key. + * + * @param string $varname Variable name + * @return mixed Variable value, or null if not set + */ + public function retrieve_var($varname); + + /** + * Assign key variable pairs from an array to a specified block + * @param string $blockname Name of block to assign $vararray to + * @param array $vararray A hash of variable name => value pairs + * @return \phpbb\template\template $this + */ + public function assign_block_vars($blockname, array $vararray); + + /** + * Assign key variable pairs from an array to a whole specified block loop + * @param string $blockname Name of block to assign $block_vars_array to + * @param array $block_vars_array An array of hashes of variable name => value pairs + * @return \phpbb\template\template $this + */ + public function assign_block_vars_array($blockname, array $block_vars_array); + + /** + * Retrieve variable values from an specified block + * @param string $blockname Name of block to retrieve $vararray from + * @param array $vararray An array with variable names, empty array gets all vars + * @return array A hash of variable name => value pairs (value is null if not set) + */ + public function retrieve_block_vars($blockname, array $vararray); + + /** + * Change already assigned key variable pair (one-dimensional - single loop entry) + * + * An example of how to use this function: + * {@example alter_block_array.php} + * + * @param string $blockname the blockname, for example 'loop' + * @param array $vararray the var array to insert/add or merge + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to change or insert at directly given] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @param string $mode Mode to execute (valid modes are 'insert', 'change' and 'delete') + * + * If insert, the vararray is inserted at the given position (position counting from zero). + * If change, the current block gets merged with the vararray (resulting in new \key/value pairs be added and existing keys be replaced by the new \value). + * If delete, the vararray is ignored, and the block at the given position (counting from zero) is removed. + * + * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array) + * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars) + * + * @return bool false on error, true on success + */ + public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert'); + + /** + * Find the index for a specified key in the innermost specified block + * + * @param string $blockname the blockname, for example 'loop' + * @param mixed $key Key to search for + * + * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position] + * + * int: Position [the position to search for] + * + * If key is false the position is set to 0 + * If key is true the position is set to the last entry + * + * @return mixed false if not found, index position otherwise; be sure to test with === + */ + public function find_key_index($blockname, $key); + + /** + * Get path to template for handle (required for BBCode parser) + * + * @param string $handle Handle to retrieve the source file + * @return string + */ + public function get_source_file_for_handle($handle); +} diff --git a/phpbb/template/twig/definition.php b/phpbb/template/twig/definition.php new file mode 100644 index 0000000..cb3c953 --- /dev/null +++ b/phpbb/template/twig/definition.php @@ -0,0 +1,69 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig; + +/** +* This class holds all DEFINE variables from the current page load +*/ +class definition +{ + /** @var array **/ + protected $definitions = array(); + + /** + * Get a DEFINE'd variable + * + * @param string $name + * @param array $arguments + * + * @return mixed Null if not found + */ + public function __call($name, $arguments) + { + return (isset($this->definitions[$name])) ? $this->definitions[$name] : null; + } + + /** + * DEFINE a variable + * + * @param string $name + * @param mixed $value + * @return \phpbb\template\twig\definition + */ + public function set($name, $value) + { + $this->definitions[$name] = $value; + + return $this; + } + + /** + * Append to a variable + * + * @param string $name + * @param string $value + * @return \phpbb\template\twig\definition + */ + public function append($name, $value) + { + if (!isset($this->definitions[$name])) + { + $this->definitions[$name] = ''; + } + + $this->definitions[$name] .= $value; + + return $this; + } +} diff --git a/phpbb/template/twig/environment.php b/phpbb/template/twig/environment.php new file mode 100644 index 0000000..ac4b16e --- /dev/null +++ b/phpbb/template/twig/environment.php @@ -0,0 +1,331 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig; + +use phpbb\template\assets_bag; + +class environment extends \Twig_Environment +{ + /** @var \phpbb\config\config */ + protected $phpbb_config; + + /** @var \phpbb\filesystem\filesystem */ + protected $filesystem; + + /** @var \phpbb\path_helper */ + protected $phpbb_path_helper; + + /** @var \Symfony\Component\DependencyInjection\ContainerInterface */ + protected $container; + + /** @var \phpbb\extension\manager */ + protected $extension_manager; + + /** @var \phpbb\event\dispatcher_interface */ + protected $phpbb_dispatcher; + + /** @var string */ + protected $phpbb_root_path; + + /** @var string */ + protected $web_root_path; + + /** @var array **/ + protected $namespace_look_up_order = array('__main__'); + + /** @var assets_bag */ + protected $assets_bag; + + /** + * Constructor + * + * @param \phpbb\config\config $phpbb_config The phpBB configuration + * @param \phpbb\filesystem\filesystem $filesystem + * @param \phpbb\path_helper $path_helper phpBB path helper + * @param string $cache_path The path to the cache directory + * @param \phpbb\extension\manager $extension_manager phpBB extension manager + * @param \Twig_LoaderInterface $loader Twig loader interface + * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher object + * @param array $options Array of options to pass to Twig + */ + public function __construct(\phpbb\config\config $phpbb_config, \phpbb\filesystem\filesystem $filesystem, \phpbb\path_helper $path_helper, $cache_path, \phpbb\extension\manager $extension_manager = null, \Twig_LoaderInterface $loader = null, \phpbb\event\dispatcher_interface $phpbb_dispatcher = null, $options = array()) + { + $this->phpbb_config = $phpbb_config; + + $this->filesystem = $filesystem; + $this->phpbb_path_helper = $path_helper; + $this->extension_manager = $extension_manager; + $this->phpbb_dispatcher = $phpbb_dispatcher; + + $this->phpbb_root_path = $this->phpbb_path_helper->get_phpbb_root_path(); + $this->web_root_path = $this->phpbb_path_helper->get_web_root_path(); + + $this->assets_bag = new assets_bag(); + + $options = array_merge(array( + 'cache' => (defined('IN_INSTALL')) ? false : $cache_path, + 'debug' => false, + 'auto_reload' => (bool) $this->phpbb_config['load_tplcompile'], + 'autoescape' => false, + ), $options); + + parent::__construct($loader, $options); + } + + /** + * Get the list of enabled phpBB extensions + * + * Used in EVENT node + * + * @return array + */ + public function get_phpbb_extensions() + { + return ($this->extension_manager) ? $this->extension_manager->all_enabled() : array(); + } + + /** + * Get phpBB config + * + * @return \phpbb\config\config + */ + public function get_phpbb_config() + { + return $this->phpbb_config; + } + + /** + * Get the phpBB root path + * + * @return string + */ + public function get_phpbb_root_path() + { + return $this->phpbb_root_path; + } + + /** + * Get the filesystem object + * + * @return \phpbb\filesystem\filesystem + */ + public function get_filesystem() + { + return $this->filesystem; + } + + /** + * Get the web root path + * + * @return string + */ + public function get_web_root_path() + { + return $this->web_root_path; + } + + /** + * Get the phpbb path helper object + * + * @return \phpbb\path_helper + */ + public function get_path_helper() + { + return $this->phpbb_path_helper; + } + + /** + * Gets the assets bag + * + * @return assets_bag + */ + public function get_assets_bag() + { + return $this->assets_bag; + } + + /** + * Get the namespace look up order + * + * @return array + */ + public function getNamespaceLookUpOrder() + { + return $this->namespace_look_up_order; + } + + /** + * Set the namespace look up order to load templates from + * + * @param array $namespace + * @return \Twig_Environment + */ + public function setNamespaceLookUpOrder($namespace) + { + $this->namespace_look_up_order = $namespace; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function render($name, array $context = []) + { + return $this->display_with_assets($name, $context); + } + + /** + * {@inheritdoc} + */ + public function display($name, array $context = []) + { + echo $this->display_with_assets($name, $context); + } + + /** + * {@inheritdoc} + */ + private function display_with_assets($name, array $context = []) + { + $placeholder_salt = unique_id(); + + if (array_key_exists('definition', $context)) + { + $context['definition']->set('SCRIPTS', '__SCRIPTS_' . $placeholder_salt . '__'); + $context['definition']->set('STYLESHEETS', '__STYLESHEETS_' . $placeholder_salt . '__'); + } + + /** + * Allow changing the template output stream before rendering + * + * @event core.twig_environment_render_template_before + * @var array context Array with template variables + * @var string name The template name + * @since 3.2.1-RC1 + */ + if ($this->phpbb_dispatcher) + { + $vars = array('context', 'name'); + extract($this->phpbb_dispatcher->trigger_event('core.twig_environment_render_template_before', compact($vars))); + } + + $output = parent::render($name, $context); + + /** + * Allow changing the template output stream after rendering + * + * @event core.twig_environment_render_template_after + * @var array context Array with template variables + * @var string name The template name + * @var string output Rendered template output stream + * @since 3.2.1-RC1 + */ + if ($this->phpbb_dispatcher) + { + $vars = array('context', 'name', 'output'); + extract($this->phpbb_dispatcher->trigger_event('core.twig_environment_render_template_after', compact($vars))); + } + + return $this->inject_assets($output, $placeholder_salt); + } + + /** + * Injects the assets (from INCLUDECSS/JS) in the output. + * + * @param string $output + * + * @return string + */ + private function inject_assets($output, $placeholder_salt) + { + $output = str_replace('__STYLESHEETS_' . $placeholder_salt . '__', $this->assets_bag->get_stylesheets_content(), $output); + $output = str_replace('__SCRIPTS_' . $placeholder_salt . '__', $this->assets_bag->get_scripts_content(), $output); + + return $output; + } + + /** + * Loads a template by name. + * + * @param string $name The template name + * @param integer $index The index if it is an embedded template + * @return \Twig_TemplateInterface A template instance representing the given template name + * @throws \Twig_Error_Loader + */ + public function loadTemplate($name, $index = null) + { + if (strpos($name, '@') === false) + { + foreach ($this->getNamespaceLookUpOrder() as $namespace) + { + try + { + if ($namespace === '__main__') + { + return parent::loadTemplate($name, $index); + } + + return parent::loadTemplate('@' . $namespace . '/' . $name, $index); + } + catch (\Twig_Error_Loader $e) + { + } + } + + // We were unable to load any templates + throw $e; + } + else + { + return parent::loadTemplate($name, $index); + } + } + + /** + * Finds a template by name. + * + * @param string $name The template name + * @return string + * @throws \Twig_Error_Loader + */ + public function findTemplate($name) + { + if (strpos($name, '@') === false) + { + foreach ($this->getNamespaceLookUpOrder() as $namespace) + { + try + { + if ($namespace === '__main__') + { + return parent::getLoader()->getCacheKey($name); + } + + return parent::getLoader()->getCacheKey('@' . $namespace . '/' . $name); + } + catch (\Twig_Error_Loader $e) + { + } + } + + // We were unable to load any templates + throw $e; + } + else + { + return parent::getLoader()->getCacheKey($name); + } + } +} diff --git a/phpbb/template/twig/extension.php b/phpbb/template/twig/extension.php new file mode 100644 index 0000000..f6f8e03 --- /dev/null +++ b/phpbb/template/twig/extension.php @@ -0,0 +1,185 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig; + +class extension extends \Twig_Extension +{ + /** @var \phpbb\template\context */ + protected $context; + + /** @var \phpbb\language\language */ + protected $language; + + /** + * Constructor + * + * @param \phpbb\template\context $context + * @param \phpbb\language\language $language + * @return \phpbb\template\twig\extension + */ + public function __construct(\phpbb\template\context $context, $language) + { + $this->context = $context; + $this->language = $language; + } + + /** + * Get the name of this extension + * + * @return string + */ + public function getName() + { + return 'phpbb'; + } + + /** + * Returns the token parser instance to add to the existing list. + * + * @return array An array of Twig_TokenParser instances + */ + public function getTokenParsers() + { + return array( + new \phpbb\template\twig\tokenparser\defineparser, + new \phpbb\template\twig\tokenparser\includeparser, + new \phpbb\template\twig\tokenparser\includejs, + new \phpbb\template\twig\tokenparser\includecss, + new \phpbb\template\twig\tokenparser\event, + new \phpbb\template\twig\tokenparser\includephp, + new \phpbb\template\twig\tokenparser\php, + ); + } + + /** + * Returns a list of filters to add to the existing list. + * + * @return array An array of filters + */ + public function getFilters() + { + return array( + new \Twig_SimpleFilter('subset', array($this, 'loop_subset'), array('needs_environment' => true)), + // @deprecated 3.2.0 Uses twig's JS escape method instead of addslashes + new \Twig_SimpleFilter('addslashes', 'addslashes'), + ); + } + + /** + * Returns a list of global functions to add to the existing list. + * + * @return array An array of global functions + */ + public function getFunctions() + { + return array( + new \Twig_SimpleFunction('lang', array($this, 'lang')), + ); + } + + /** + * Returns a list of operators to add to the existing list. + * + * @return array An array of operators + */ + public function getOperators() + { + return array( + array( + '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), + ), + array( + // precedence settings are copied from similar operators in Twig core extension + '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + + 'eq' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + + 'ne' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + 'neq' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + '<>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + + '===' => array('precedence' => 20, 'class' => '\phpbb\template\twig\node\expression\binary\equalequal', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + '!==' => array('precedence' => 20, 'class' => '\phpbb\template\twig\node\expression\binary\notequalequal', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + + 'gt' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + 'gte' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + 'ge' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + 'lt' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + 'lte' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + 'le' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + + 'mod' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => \Twig_ExpressionParser::OPERATOR_LEFT), + ), + ); + } + + /** + * Grabs a subset of a loop + * + * @param \Twig_Environment $env A Twig_Environment instance + * @param mixed $item A variable + * @param integer $start Start of the subset + * @param integer $end End of the subset + * @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + */ + function loop_subset(\Twig_Environment $env, $item, $start, $end = null, $preserveKeys = false) + { + // We do almost the same thing as Twig's slice (array_slice), except when $end is positive + if ($end >= 1) + { + // When end is > 1, subset will end on the last item in an array with the specified $end + // This is different from slice in that it is the number we end on rather than the number + // of items to grab (length) + + // Start must always be the actual starting number for this calculation (not negative) + $start = ($start < 0) ? count($item) + $start : $start; + $end = $end - $start; + } + + // We always include the last element (this was the past design) + $end = ($end == -1 || $end === null) ? null : $end + 1; + + return twig_slice($env, $item, $start, $end, $preserveKeys); + } + + /** + * Get output for a language variable (L_FOO, LA_FOO) + * + * This function checks to see if the language var was outputted to $context + * (e.g. in the ACP, L_TITLE) + * If not, we return the result of $user->lang() + * + * @return string + */ + function lang() + { + $args = func_get_args(); + $key = $args[0]; + + $context_vars = $this->context->get_root_ref(); + + if (is_string($key) && isset($context_vars['L_' . $key])) + { + return $context_vars['L_' . $key]; + } + + // LA_ is transformed into lang(\'$1\')|escape('js'), so we should not + // need to check for it + + return call_user_func_array(array($this->language, 'lang'), $args); + } +} diff --git a/phpbb/template/twig/extension/routing.php b/phpbb/template/twig/extension/routing.php new file mode 100644 index 0000000..829ce73 --- /dev/null +++ b/phpbb/template/twig/extension/routing.php @@ -0,0 +1,43 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\extension; + +use Symfony\Bridge\Twig\Extension\RoutingExtension; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; + +class routing extends RoutingExtension +{ + /** @var \phpbb\controller\helper */ + protected $helper; + + /** + * Constructor + * + * @param \phpbb\routing\helper $helper + */ + public function __construct(\phpbb\routing\helper $helper) + { + $this->helper = $helper; + } + + public function getPath($name, $parameters = array(), $relative = false) + { + return $this->helper->route($name, $parameters, true, false, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); + } + + public function getUrl($name, $parameters = array(), $schemeRelative = false) + { + return $this->helper->route($name, $parameters, true, false, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); + } +} diff --git a/phpbb/template/twig/lexer.php b/phpbb/template/twig/lexer.php new file mode 100644 index 0000000..d0bcfa6 --- /dev/null +++ b/phpbb/template/twig/lexer.php @@ -0,0 +1,368 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig; + +class lexer extends \Twig_Lexer +{ + public function set_environment(\Twig_Environment $env) + { + $this->env = $env; + } + + public function tokenize($code, $filename = null) + { + // Handle \Twig_Source format input + if ($code instanceof \Twig_Source) + { + $source = $code; + $code = $source->getCode(); + $filename = $source->getName(); + } + + // Our phpBB tags + // Commented out tokens are handled separately from the main replace + $phpbb_tags = array( + /*'BEGIN', + 'BEGINELSE', + 'END', + 'IF', + 'ELSE', + 'ELSEIF', + 'ENDIF', + 'DEFINE', + 'UNDEFINE',*/ + 'ENDDEFINE', + 'INCLUDE', + 'INCLUDEPHP', + 'INCLUDEJS', + 'INCLUDECSS', + 'PHP', + 'ENDPHP', + 'EVENT', + ); + + // Twig tag masks + $twig_tags = array( + 'autoescape', + 'endautoescape', + 'if', + 'elseif', + 'else', + 'endif', + 'block', + 'endblock', + 'use', + 'extends', + 'embed', + 'filter', + 'endfilter', + 'flush', + 'for', + 'endfor', + 'macro', + 'endmacro', + 'import', + 'from', + 'sandbox', + 'endsandbox', + 'set', + 'endset', + 'spaceless', + 'endspaceless', + 'verbatim', + 'endverbatim', + ); + + // Fix tokens that may have inline variables (e.g. with Twig style, {% TOKEN %} + // This also strips outer parenthesis, becomes + $code = preg_replace('##', '{% $1 $2 %}', $code); + + // Replace all of our twig masks with Twig code (e.g. with {% block $1 %}) + $code = $this->replace_twig_tag_masks($code, $twig_tags); + + // Replace all of our language variables, {L_VARNAME}, with Twig style, {{ lang('NAME') }} + // Appends any filters after lang() + $code = preg_replace('#{L_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2 }}', $code); + + // Replace all of our escaped language variables, {LA_VARNAME}, with Twig style, {{ lang('NAME')|escape('js') }} + // Appends any filters after lang(), but before escape('js') + $code = preg_replace('#{LA_([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ lang(\'$1\')$2|escape(\'js\') }}', $code); + + // Replace all of our variables, {VARNAME}, with Twig style, {{ VARNAME }} + // Appends any filters + $code = preg_replace('#{([a-zA-Z0-9_\.]+)(\|[^}]+?)?}#', '{{ $1$2 }}', $code); + + // Tokenize \Twig_Source instance + return parent::tokenize(new \Twig_Source($code, $filename)); + } + + /** + * Strip surrounding quotes + * + * First step to fix tokens that may have inline variables + * E.g. #', '', $code); + } + + /** + * Fix tokens that may have inline variables + * + * Second step to fix tokens that may have inline variables + * E.g. "; + }; + + return preg_replace_callback('##', $callback, $code); + } + + /** + * Add surrounding quotes + * + * Last step to fix tokens that may have inline variables + * E.g. #', '', $code); + } + + /** + * Fix begin tokens (convert our BEGIN to Twig for) + * + * Not meant to be used outside of this context, public because the anonymous function calls this + * + * @param string $code + * @param array $parent_nodes (used in recursion) + * @return string + */ + public function fix_begin_tokens($code, $parent_nodes = array()) + { + // PHP 5.3 cannot use $this in an anonymous function, so use this as a work-around + $parent_class = $this; + $callback = function ($matches) use ($parent_class, $parent_nodes) + { + $hard_parents = explode('.', $matches[1]); + array_pop($hard_parents); // ends with . + if ($hard_parents) + { + $parent_nodes = array_merge($hard_parents, $parent_nodes); + } + + $name = $matches[2]; + $subset = trim(substr($matches[3], 1, -1)); // Remove parenthesis + $body = $matches[4]; + + // Replace + $body = str_replace('', '{% else %}', $body); + + // Is the designer wanting to call another loop in a loop? + // + // + // + // + // 'loop2' is actually on the same nesting level as 'loop' you assign + // variables to it with template->assign_block_vars('loop2', array(...)) + if (strpos($name, '!') === 0) + { + // Count the number if ! occurrences + $count = substr_count($name, '!'); + for ($i = 0; $i < $count; $i++) + { + array_pop($parent_nodes); + $name = substr($name, 1); + } + } + + // Remove all parent nodes, e.g. foo, bar from foo.bar.foobar.VAR + foreach ($parent_nodes as $node) + { + $body = preg_replace('#([^a-zA-Z0-9_])' . $node . '\.([a-zA-Z0-9_]+)\.#', '$1$2.', $body); + } + + // Add current node to list of parent nodes for child nodes + $parent_nodes[] = $name; + + // Recursive...fix any child nodes + $body = $parent_class->fix_begin_tokens($body, $parent_nodes); + + // Need the parent variable name + array_pop($parent_nodes); + $parent = (!empty($parent_nodes)) ? end($parent_nodes) . '.' : ''; + + if ($subset !== '') + { + $subset = '|subset(' . $subset . ')'; + } + + $parent = ($parent) ?: 'loops.'; + // Turn into a Twig for loop + return "{% for {$name} in {$parent}{$name}{$subset} %}{$body}{% endfor %}"; + }; + + return preg_replace_callback('#(.+?)#s', $callback, $code); + } + + /** + * Fix IF statements + * + * @param string $code + * @return string + */ + protected function fix_if_tokens($code) + { + // Replace ELSE IF with ELSEIF + $code = preg_replace('##', '', $code); + + // Replace our "div by" with Twig's divisibleby (Twig does not like test names with spaces) + $code = preg_replace('# div by ([0-9]+)#', ' divisibleby($1)', $code); + + $callback = function($matches) + { + $inner = $matches[2]; + // Replace $TEST with definition.TEST + $inner = preg_replace('#(\s\(*!?)\$([a-zA-Z_0-9]+)#', '$1definition.$2', $inner); + + // Replace .foo with loops.foo|length + $inner = preg_replace('#(\s\(*!?)\.([a-zA-Z_0-9]+)([^a-zA-Z_0-9\.])#', '$1loops.$2|length$3', $inner); + + // Replace .foo.bar with foo.bar|length + $inner = preg_replace('#(\s\(*!?)\.([a-zA-Z_0-9\.]+)([^a-zA-Z_0-9\.])#', '$1$2|length$3', $inner); + + return ""; + }; + + return preg_replace_callback('##', $callback, $code); + } + + /** + * Fix DEFINE statements and {$VARNAME} variables + * + * @param string $code + * @return string + */ + protected function fix_define_tokens($code) + { + /** + * Changing $VARNAME to definition.varname because set is only local + * context (e.g. DEFINE $TEST will only make $TEST available in current + * template and any child templates, but not any parent templates). + * + * DEFINE handles setting it properly to definition in its node, but the + * variables reading FROM it need to be altered to definition.VARNAME + * + * Setting up definition as a class in the array passed to Twig + * ($context) makes set definition.TEST available in the global context + */ + + // Replace #', '{% DEFINE $1 %}', $code); + + // Changing UNDEFINE NAME to DEFINE NAME = null to save from creating an extra token parser/node + $code = preg_replace('##', '{% DEFINE $1= null %}', $code); + + // Replace all of our variables, {$VARNAME}, with Twig style, {{ definition.VARNAME }} + $code = preg_replace('#{\$([a-zA-Z0-9_\.]+)}#', '{{ definition.$1 }}', $code); + + // Replace all of our variables, ~ $VARNAME ~, with Twig style, ~ definition.VARNAME ~ + $code = preg_replace('#~ \$([a-zA-Z0-9_\.]+) ~#', '~ definition.$1 ~', $code); + + return $code; + } + + /** + * Replace Twig tag masks with Twig tag calls + * + * E.g. with {% block foo %} + * + * @param string $code + * @param array $twig_tags All tags we want to create a mask for + * @return string + */ + protected function replace_twig_tag_masks($code, $twig_tags) + { + $callback = function ($matches) + { + $matches[1] = strtolower($matches[1]); + + return "{% {$matches[1]}{$matches[2]}%}"; + }; + + foreach ($twig_tags as &$tag) + { + $tag = strtoupper($tag); + } + + // twig_tags is an array of the twig tags, which are all lowercase, but we use all uppercase tags + $code = preg_replace_callback('##',$callback, $code); + + return $code; + } +} diff --git a/phpbb/template/twig/loader.php b/phpbb/template/twig/loader.php new file mode 100644 index 0000000..c13e3ee --- /dev/null +++ b/phpbb/template/twig/loader.php @@ -0,0 +1,176 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig; + +/** +* Twig Template loader +*/ +class loader extends \Twig_Loader_Filesystem +{ + protected $safe_directories = array(); + + /** + * @var \phpbb\filesystem\filesystem_interface + */ + protected $filesystem; + + /** + * Constructor + * + * @param \phpbb\filesystem\filesystem_interface $filesystem + * @param string|array $paths + */ + public function __construct(\phpbb\filesystem\filesystem_interface $filesystem, $paths = array()) + { + $this->filesystem = $filesystem; + + parent::__construct($paths, $this->filesystem->realpath(dirname(__FILE__))); + } + + /** + * Set safe directories + * + * @param array $directories Array of directories that are safe (empty to clear) + * @return \Twig_Loader_Filesystem + */ + public function setSafeDirectories($directories = array()) + { + $this->safe_directories = array(); + + if (!empty($directories)) + { + foreach ($directories as $directory) + { + $this->addSafeDirectory($directory); + } + } + + return $this; + } + + /** + * Add safe directory + * + * @param string $directory Directory that should be added + * @return \Twig_Loader_Filesystem + */ + public function addSafeDirectory($directory) + { + $directory = $this->filesystem->realpath($directory); + + if ($directory !== false) + { + $this->safe_directories[] = $directory; + } + + return $this; + } + + /** + * Get current safe directories + * + * @return array + */ + public function getSafeDirectories() + { + return $this->safe_directories; + } + + /** + * Override for parent::validateName() + * + * This is done because we added support for safe directories, and when Twig + * findTemplate() is called, validateName() is called first, which would + * always throw an exception if the file is outside of the configured + * template directories. + */ + protected function validateName($name) + { + return; + } + + /** + * Adds a realpath call to fix a BC break in Twig 1.26 (https://github.com/twigphp/Twig/issues/2145) + * + * {@inheritdoc} + */ + public function addPath($path, $namespace = self::MAIN_NAMESPACE) + { + return parent::addPath($this->filesystem->realpath($path), $namespace); + } + + /** + * Find the template + * + * Override for Twig_Loader_Filesystem::findTemplate to add support + * for loading from safe directories. + */ + protected function findTemplate($name) + { + $name = (string) $name; + + // normalize name + $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/')); + + // If this is in the cache we can skip the entire process below + // as it should have already been validated + if (isset($this->cache[$name])) + { + return $this->cache[$name]; + } + + // First, find the template name. The override above of validateName + // causes the validateName process to be skipped for this call + $file = parent::findTemplate($name); + + try + { + // Try validating the name (which may throw an exception) + parent::validateName($name); + } + catch (\Twig_Error_Loader $e) + { + if (strpos($e->getRawMessage(), 'Looks like you try to load a template outside configured directories') === 0) + { + // Ok, so outside of the configured template directories, we + // can now check if we're within a "safe" directory + + // Find the real path of the directory the file is in + $directory = $this->filesystem->realpath(dirname($file)); + + if ($directory === false) + { + // Some sort of error finding the actual path, must throw the exception + throw $e; + } + + foreach ($this->safe_directories as $safe_directory) + { + if (strpos($directory, $safe_directory) === 0) + { + // The directory being loaded is below a directory + // that is "safe". We're good to load it! + return $file; + } + } + } + + // Not within any safe directories + throw $e; + } + + // No exception from validateName, safe to load. + return $file; + } +} diff --git a/phpbb/template/twig/node/definenode.php b/phpbb/template/twig/node/definenode.php new file mode 100644 index 0000000..ddbd151 --- /dev/null +++ b/phpbb/template/twig/node/definenode.php @@ -0,0 +1,57 @@ + +* @copyright Portions (c) 2009 Fabien Potencier, Armin Ronacher +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node; + +class definenode extends \Twig_Node +{ + public function __construct($capture, \Twig_NodeInterface $name, \Twig_NodeInterface $value, $lineno, $tag = null) + { + parent::__construct(array('name' => $name, 'value' => $value), array('capture' => $capture, 'safe' => false), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param \Twig_Compiler A Twig_Compiler instance + */ + public function compile(\Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->getAttribute('capture')) + { + $compiler + ->write("ob_start();\n") + ->subcompile($this->getNode('value')) + ; + + $compiler->write("\$value = ('' === \$value = ob_get_clean()) ? '' : new \Twig_Markup(\$value, \$this->env->getCharset());\n"); + } + else + { + $compiler + ->write("\$value = ") + ->subcompile($this->getNode('value')) + ->raw(";\n") + ; + } + + $compiler + ->write("\$context['definition']->set('") + ->raw($this->getNode('name')->getAttribute('name')) + ->raw("', \$value);\n") + ; + } +} diff --git a/phpbb/template/twig/node/event.php b/phpbb/template/twig/node/event.php new file mode 100644 index 0000000..11fdb75 --- /dev/null +++ b/phpbb/template/twig/node/event.php @@ -0,0 +1,82 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node; + +class event extends \Twig_Node +{ + /** + * The subdirectory in which all template listener files must be placed + * @var string + */ + protected $listener_directory = 'event/'; + + /** @var \Twig_Environment */ + protected $environment; + + public function __construct(\Twig_Node_Expression $expr, \phpbb\template\twig\environment $environment, $lineno, $tag = null) + { + $this->environment = $environment; + + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param \Twig_Compiler A Twig_Compiler instance + */ + public function compile(\Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $location = $this->listener_directory . $this->getNode('expr')->getAttribute('name'); + + foreach ($this->environment->get_phpbb_extensions() as $ext_namespace => $ext_path) + { + $ext_namespace = str_replace('/', '_', $ext_namespace); + + if ($this->environment->isDebug()) + { + // If debug mode is enabled, lets check for new/removed EVENT + // templates on page load rather than at compile. This is + // slower, but makes developing extensions easier (no need to + // purge the cache when a new event template file is added) + $compiler + ->write("if (\$this->env->getLoader()->exists('@{$ext_namespace}/{$location}.html')) {\n") + ->indent() + ; + } + + if ($this->environment->isDebug() || $this->environment->getLoader()->exists('@' . $ext_namespace . '/' . $location . '.html')) + { + $compiler + ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") + + // We set the namespace lookup order to be this extension first, then the main path + ->write("\$this->env->setNamespaceLookUpOrder(array('{$ext_namespace}', '__main__'));\n") + ->write("\$this->env->loadTemplate('@{$ext_namespace}/{$location}.html')->display(\$context);\n") + ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") + ; + } + + if ($this->environment->isDebug()) + { + $compiler + ->outdent() + ->write("}\n\n") + ; + } + } + } +} diff --git a/phpbb/template/twig/node/expression/binary/equalequal.php b/phpbb/template/twig/node/expression/binary/equalequal.php new file mode 100644 index 0000000..2cd15d5 --- /dev/null +++ b/phpbb/template/twig/node/expression/binary/equalequal.php @@ -0,0 +1,22 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node\expression\binary; + +class equalequal extends \Twig_Node_Expression_Binary +{ + public function operator(\Twig_Compiler $compiler) + { + return $compiler->raw('==='); + } +} diff --git a/phpbb/template/twig/node/expression/binary/notequalequal.php b/phpbb/template/twig/node/expression/binary/notequalequal.php new file mode 100644 index 0000000..5f2908f --- /dev/null +++ b/phpbb/template/twig/node/expression/binary/notequalequal.php @@ -0,0 +1,22 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node\expression\binary; + +class notequalequal extends \Twig_Node_Expression_Binary +{ + public function operator(\Twig_Compiler $compiler) + { + return $compiler->raw('!=='); + } +} diff --git a/phpbb/template/twig/node/includeasset.php b/phpbb/template/twig/node/includeasset.php new file mode 100644 index 0000000..12034b7 --- /dev/null +++ b/phpbb/template/twig/node/includeasset.php @@ -0,0 +1,71 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node; + +abstract class includeasset extends \Twig_Node +{ + /** @var \Twig_Environment */ + protected $environment; + + public function __construct(\Twig_Node_Expression $expr, \phpbb\template\twig\environment $environment, $lineno, $tag = null) + { + $this->environment = $environment; + + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + /** + * Compiles the node to PHP. + * + * @param \Twig_Compiler A Twig_Compiler instance + */ + public function compile(\Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + $compiler + ->write("\$asset_file = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("\$asset = new \phpbb\\template\\asset(\$asset_file, \$this->getEnvironment()->get_path_helper(), \$this->getEnvironment()->get_filesystem());\n") + ->write("if (substr(\$asset_file, 0, 2) !== './' && \$asset->is_relative()) {\n") + ->indent() + ->write("\$asset_path = \$asset->get_path();") + ->write("\$local_file = \$this->getEnvironment()->get_phpbb_root_path() . \$asset_path;\n") + ->write("if (!file_exists(\$local_file)) {\n") + ->indent() + ->write("\$local_file = \$this->getEnvironment()->findTemplate(\$asset_path);\n") + ->write("\$asset->set_path(\$local_file, true);\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ->write("\n") + ->write("if (\$asset->is_relative()) {\n") + ->indent() + ->write("\$asset->add_assets_version('{$config['assets_version']}');\n") + ->outdent() + ->write("}\n") + ->write("\$this->getEnvironment()->get_assets_bag()->add_{$this->get_setters_name()}(\$asset);") + ; + } + + /** + * Get the name of the assets bag setter + * + * @return string (e.g. 'script') + */ + abstract public function get_setters_name(); +} diff --git a/phpbb/template/twig/node/includecss.php b/phpbb/template/twig/node/includecss.php new file mode 100644 index 0000000..2e97d49 --- /dev/null +++ b/phpbb/template/twig/node/includecss.php @@ -0,0 +1,25 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node; + +class includecss extends \phpbb\template\twig\node\includeasset +{ + /** + * {@inheritdoc} + */ + public function get_setters_name() + { + return 'stylesheet'; + } +} diff --git a/phpbb/template/twig/node/includejs.php b/phpbb/template/twig/node/includejs.php new file mode 100644 index 0000000..505b497 --- /dev/null +++ b/phpbb/template/twig/node/includejs.php @@ -0,0 +1,25 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node; + +class includejs extends \phpbb\template\twig\node\includeasset +{ + /** + * {@inheritdoc} + */ + public function get_setters_name() + { + return 'script'; + } +} diff --git a/phpbb/template/twig/node/includenode.php b/phpbb/template/twig/node/includenode.php new file mode 100644 index 0000000..c36ac3c --- /dev/null +++ b/phpbb/template/twig/node/includenode.php @@ -0,0 +1,53 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node; + +class includenode extends \Twig_Node_Include +{ + /** + * Compiles the node to PHP. + * + * @param \Twig_Compiler A Twig_Compiler instance + */ + public function compile(\Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $compiler + ->write("\$location = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("\$namespace = false;\n") + ->write("if (strpos(\$location, '@') === 0) {\n") + ->indent() + ->write("\$namespace = substr(\$location, 1, strpos(\$location, '/') - 1);\n") + ->write("\$previous_look_up_order = \$this->env->getNamespaceLookUpOrder();\n") + + // We set the namespace lookup order to be this namespace first, then the main path + ->write("\$this->env->setNamespaceLookUpOrder(array(\$namespace, '__main__'));\n") + ->outdent() + ->write("}\n") + ; + + parent::compile($compiler); + + $compiler + ->write("if (\$namespace) {\n") + ->indent() + ->write("\$this->env->setNamespaceLookUpOrder(\$previous_look_up_order);\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/phpbb/template/twig/node/includephp.php b/phpbb/template/twig/node/includephp.php new file mode 100644 index 0000000..76182c2 --- /dev/null +++ b/phpbb/template/twig/node/includephp.php @@ -0,0 +1,91 @@ + +* Sections (c) 2009 Fabien Potencier, Armin Ronacher +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node; + +class includephp extends \Twig_Node +{ + /** @var \Twig_Environment */ + protected $environment; + + public function __construct(\Twig_Node_Expression $expr, \phpbb\template\twig\environment $environment, $lineno, $ignoreMissing = false, $tag = null) + { + $this->environment = $environment; + + parent::__construct(array('expr' => $expr), array('ignore_missing' => (Boolean) $ignoreMissing), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param \Twig_Compiler A Twig_Compiler instance + */ + public function compile(\Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + if (!$config['tpl_allow_php']) + { + $compiler + ->write("// INCLUDEPHP Disabled\n") + ; + + return; + } + + if ($this->getAttribute('ignore_missing')) + { + $compiler + ->write("try {\n") + ->indent() + ; + } + + $compiler + ->write("\$location = ") + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ->write("if (phpbb_is_absolute(\$location)) {\n") + ->indent() + // Absolute path specified + ->write("require(\$location);\n") + ->outdent() + ->write("} else if (file_exists(\$this->getEnvironment()->get_phpbb_root_path() . \$location)) {\n") + ->indent() + // PHP file relative to phpbb_root_path + ->write("require(\$this->getEnvironment()->get_phpbb_root_path() . \$location);\n") + ->outdent() + ->write("} else {\n") + ->indent() + // Local path (behaves like INCLUDE) + ->write("require(\$this->getEnvironment()->getLoader()->getCacheKey(\$location));\n") + ->outdent() + ->write("}\n") + ; + + if ($this->getAttribute('ignore_missing')) + { + $compiler + ->outdent() + ->write("} catch (\Twig_Error_Loader \$e) {\n") + ->indent() + ->write("// ignore missing template\n") + ->outdent() + ->write("}\n\n") + ; + } + } +} diff --git a/phpbb/template/twig/node/php.php b/phpbb/template/twig/node/php.php new file mode 100644 index 0000000..4ee415e --- /dev/null +++ b/phpbb/template/twig/node/php.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\node; + +class php extends \Twig_Node +{ + /** @var \Twig_Environment */ + protected $environment; + + public function __construct(\Twig_Node_Text $text, \phpbb\template\twig\environment $environment, $lineno, $tag = null) + { + $this->environment = $environment; + + parent::__construct(array('text' => $text), array(), $lineno, $tag); + } + + /** + * Compiles the node to PHP. + * + * @param \Twig_Compiler A Twig_Compiler instance + */ + public function compile(\Twig_Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $config = $this->environment->get_phpbb_config(); + + if (!$config['tpl_allow_php']) + { + $compiler + ->write("// PHP Disabled\n") + ; + + return; + } + + $compiler + ->raw($this->getNode('text')->getAttribute('data')) + ; + } +} diff --git a/phpbb/template/twig/tokenparser/defineparser.php b/phpbb/template/twig/tokenparser/defineparser.php new file mode 100644 index 0000000..b755836 --- /dev/null +++ b/phpbb/template/twig/tokenparser/defineparser.php @@ -0,0 +1,76 @@ + +* @copyright Portions (c) 2009 Fabien Potencier, Armin Ronacher +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\tokenparser; + +class defineparser extends \Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param \Twig_Token $token A Twig_Token instance + * + * @return \Twig_NodeInterface A Twig_NodeInterface instance + * @throws \Twig_Error_Syntax + * @throws \phpbb\template\twig\node\definenode + */ + public function parse(\Twig_Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $this->parser->getExpressionParser()->parseExpression(); + + $capture = false; + if ($stream->test(\Twig_Token::OPERATOR_TYPE, '=')) + { + $stream->next(); + $value = $this->parser->getExpressionParser()->parseExpression(); + + if ($value instanceof \Twig_Node_Expression_Name) + { + // This would happen if someone improperly formed their DEFINE syntax + // e.g. + throw new \Twig_Error_Syntax('Invalid DEFINE', $token->getLine(), $this->parser->getFilename()); + } + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + } + else + { + $capture = true; + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + $value = $this->parser->subparse(array($this, 'decideBlockEnd'), true); + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + } + + return new \phpbb\template\twig\node\definenode($capture, $name, $value, $lineno, $this->getTag()); + } + + public function decideBlockEnd(\Twig_Token $token) + { + return $token->test('ENDDEFINE'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'DEFINE'; + } +} diff --git a/phpbb/template/twig/tokenparser/event.php b/phpbb/template/twig/tokenparser/event.php new file mode 100644 index 0000000..f73ef4a --- /dev/null +++ b/phpbb/template/twig/tokenparser/event.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\tokenparser; + +class event extends \Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param \Twig_Token $token A Twig_Token instance + * + * @return \Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(\Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + return new \phpbb\template\twig\node\event($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'EVENT'; + } +} diff --git a/phpbb/template/twig/tokenparser/includecss.php b/phpbb/template/twig/tokenparser/includecss.php new file mode 100644 index 0000000..1f30811 --- /dev/null +++ b/phpbb/template/twig/tokenparser/includecss.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\tokenparser; + +class includecss extends \Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param \Twig_Token $token A Twig_Token instance + * + * @return \Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(\Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + return new \phpbb\template\twig\node\includecss($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDECSS'; + } +} diff --git a/phpbb/template/twig/tokenparser/includejs.php b/phpbb/template/twig/tokenparser/includejs.php new file mode 100644 index 0000000..4b67d2c --- /dev/null +++ b/phpbb/template/twig/tokenparser/includejs.php @@ -0,0 +1,44 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\tokenparser; + +class includejs extends \Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param \Twig_Token $token A Twig_Token instance + * + * @return \Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(\Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + return new \phpbb\template\twig\node\includejs($expr, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDEJS'; + } +} diff --git a/phpbb/template/twig/tokenparser/includeparser.php b/phpbb/template/twig/tokenparser/includeparser.php new file mode 100644 index 0000000..aa7236a --- /dev/null +++ b/phpbb/template/twig/tokenparser/includeparser.php @@ -0,0 +1,44 @@ + +* @copyright Portions (c) 2009 Fabien Potencier, Armin Ronacher +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\tokenparser; + +class includeparser extends \Twig_TokenParser_Include +{ + /** + * Parses a token and returns a node. + * + * @param \Twig_Token $token A Twig_Token instance + * + * @return \Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(\Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + return new \phpbb\template\twig\node\includenode($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDE'; + } +} diff --git a/phpbb/template/twig/tokenparser/includephp.php b/phpbb/template/twig/tokenparser/includephp.php new file mode 100644 index 0000000..3992636 --- /dev/null +++ b/phpbb/template/twig/tokenparser/includephp.php @@ -0,0 +1,55 @@ + +* @copyright Portions (c) 2009 Fabien Potencier, Armin Ronacher +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\tokenparser; + +class includephp extends \Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param \Twig_Token $token A Twig_Token instance + * + * @return \Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(\Twig_Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $stream = $this->parser->getStream(); + + $ignoreMissing = false; + if ($stream->test(\Twig_Token::NAME_TYPE, 'ignore')) + { + $stream->next(); + $stream->expect(\Twig_Token::NAME_TYPE, 'missing'); + + $ignoreMissing = true; + } + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + return new \phpbb\template\twig\node\includephp($expr, $this->parser->getEnvironment(), $token->getLine(), $ignoreMissing, $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'INCLUDEPHP'; + } +} diff --git a/phpbb/template/twig/tokenparser/php.php b/phpbb/template/twig/tokenparser/php.php new file mode 100644 index 0000000..f11ce35 --- /dev/null +++ b/phpbb/template/twig/tokenparser/php.php @@ -0,0 +1,52 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig\tokenparser; + +class php extends \Twig_TokenParser +{ + /** + * Parses a token and returns a node. + * + * @param \Twig_Token $token A Twig_Token instance + * + * @return \Twig_NodeInterface A Twig_NodeInterface instance + */ + public function parse(\Twig_Token $token) + { + $stream = $this->parser->getStream(); + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'decideEnd'), true); + + $stream->expect(\Twig_Token::BLOCK_END_TYPE); + + return new \phpbb\template\twig\node\php($body, $this->parser->getEnvironment(), $token->getLine(), $this->getTag()); + } + + public function decideEnd(\Twig_Token $token) + { + return $token->test('ENDPHP'); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'PHP'; + } +} diff --git a/phpbb/template/twig/twig.php b/phpbb/template/twig/twig.php new file mode 100644 index 0000000..f322778 --- /dev/null +++ b/phpbb/template/twig/twig.php @@ -0,0 +1,384 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\template\twig; + +use phpbb\template\exception\user_object_not_available; + +/** +* Twig Template class. +*/ +class twig extends \phpbb\template\base +{ + /** + * Path of the cache directory for the template + * + * Cannot be changed during runtime. + * + * @var string + */ + private $cachepath = ''; + + /** + * phpBB path helper + * @var \phpbb\path_helper + */ + protected $path_helper; + + /** + * phpBB root path + * @var string + */ + protected $phpbb_root_path; + + /** + * PHP file extension + * @var string + */ + protected $php_ext; + + /** + * phpBB config instance + * @var \phpbb\config\config + */ + protected $config; + + /** + * Current user + * @var \phpbb\user + */ + protected $user; + + /** + * Extension manager. + * + * @var \phpbb\extension\manager + */ + protected $extension_manager; + + /** + * Twig Environment + * + * @var \Twig_Environment + */ + protected $twig; + + /** + * Constructor. + * + * @param \phpbb\path_helper $path_helper + * @param \phpbb\config\config $config + * @param \phpbb\template\context $context template context + * @param \phpbb\template\twig\environment $twig_environment + * @param string $cache_path + * @param \phpbb\user|null $user + * @param array|\ArrayAccess $extensions + * @param \phpbb\extension\manager $extension_manager extension manager, if null then template events will not be invoked + */ + public function __construct(\phpbb\path_helper $path_helper, $config, \phpbb\template\context $context, \phpbb\template\twig\environment $twig_environment, $cache_path, \phpbb\user $user = null, $extensions = array(), \phpbb\extension\manager $extension_manager = null) + { + $this->path_helper = $path_helper; + $this->phpbb_root_path = $path_helper->get_phpbb_root_path(); + $this->php_ext = $path_helper->get_php_ext(); + $this->config = $config; + $this->user = $user; + $this->context = $context; + $this->extension_manager = $extension_manager; + $this->cachepath = $cache_path; + $this->twig = $twig_environment; + + foreach ($extensions as $extension) + { + $this->twig->addExtension($extension); + } + + // Add admin namespace + if ($this->path_helper->get_adm_relative_path() !== null && is_dir($this->phpbb_root_path . $this->path_helper->get_adm_relative_path() . 'style/')) + { + $this->twig->getLoader()->setPaths($this->phpbb_root_path . $this->path_helper->get_adm_relative_path() . 'style/', 'admin'); + } + } + + /** + * Clear the cache + * + * @return \phpbb\template\template + */ + public function clear_cache() + { + if (is_dir($this->cachepath)) + { + $this->twig->clearCacheFiles(); + } + + return $this; + } + + /** + * Get the style tree of the style preferred by the current user + * + * @return array Style tree, most specific first + * + * @throws \phpbb\template\exception\user_object_not_available When user service was not set + */ + public function get_user_style() + { + if ($this->user === null) + { + throw new user_object_not_available(); + } + + $style_list = array( + $this->user->style['style_path'], + ); + + if ($this->user->style['style_parent_id']) + { + $style_list = array_merge($style_list, array_reverse(explode('/', $this->user->style['style_parent_tree']))); + } + + return $style_list; + } + + /** + * Set style location based on (current) user's chosen style. + * + * @param array $style_directories The directories to add style paths for + * E.g. array('ext/foo/bar/styles', 'styles') + * Default: array('styles') (phpBB's style directory) + * @return \phpbb\template\template $this + */ + public function set_style($style_directories = array('styles')) + { + if ($style_directories !== array('styles') && $this->twig->getLoader()->getPaths('core') === array()) + { + // We should set up the core styles path since not already setup + $this->set_style(); + } + + $names = $this->get_user_style(); + // Add 'all' folder to $names array + // It allows extensions to load a template file from 'all' folder, + // if a style doesn't include it. + $names[] = 'all'; + + $paths = array(); + foreach ($style_directories as $directory) + { + foreach ($names as $name) + { + $path = $this->phpbb_root_path . trim($directory, '/') . "/{$name}/"; + $template_path = $path . 'template/'; + $theme_path = $path . 'theme/'; + + $is_valid_dir = false; + if (is_dir($template_path)) + { + $is_valid_dir = true; + $paths[] = $template_path; + } + if (is_dir($theme_path)) + { + $is_valid_dir = true; + $paths[] = $theme_path; + } + + if ($is_valid_dir) + { + // Add the base style directory as a safe directory + $this->twig->getLoader()->addSafeDirectory($path); + } + } + } + + // If we're setting up the main phpBB styles directory and the core + // namespace isn't setup yet, we will set it up now + if ($style_directories === array('styles') && $this->twig->getLoader()->getPaths('core') === array()) + { + // Set up the core style paths namespace + $this->twig->getLoader()->setPaths($paths, 'core'); + } + + $this->set_custom_style($names, $paths); + + return $this; + } + + /** + * Set custom style location (able to use directory outside of phpBB). + * + * Note: Templates are still compiled to phpBB's cache directory. + * + * @param string|array $names Array of names (or detailed names) or string of name of template(s) in inheritance tree order, used by extensions. + * E.g. array( + * 'name' => 'adm', + * 'ext_path' => 'adm/style/', + * ) + * @param string|array of string $paths Array of style paths, relative to current root directory + * @return \phpbb\template\template $this + */ + public function set_custom_style($names, $paths) + { + $paths = (is_string($paths)) ? array($paths) : $paths; + $names = (is_string($names)) ? array($names) : $names; + + // Set as __main__ namespace + $this->twig->getLoader()->setPaths($paths); + + // Add all namespaces for all extensions + if ($this->extension_manager instanceof \phpbb\extension\manager) + { + $names[] = 'all'; + + foreach ($this->extension_manager->all_enabled() as $ext_namespace => $ext_path) + { + // namespaces cannot contain / + $namespace = str_replace('/', '_', $ext_namespace); + $paths = array(); + + foreach ($names as $template_dir) + { + if (is_array($template_dir)) + { + if (isset($template_dir['ext_path'])) + { + $ext_style_template_path = $ext_path . $template_dir['ext_path']; + $ext_style_path = dirname($ext_style_template_path); + $ext_style_theme_path = $ext_style_path . 'theme/'; + } + else + { + $ext_style_path = $ext_path . 'styles/' . $template_dir['name'] . '/'; + $ext_style_template_path = $ext_style_path . 'template/'; + $ext_style_theme_path = $ext_style_path . 'theme/'; + } + } + else + { + $ext_style_path = $ext_path . 'styles/' . $template_dir . '/'; + $ext_style_template_path = $ext_style_path . 'template/'; + $ext_style_theme_path = $ext_style_path . 'theme/'; + } + + $is_valid_dir = false; + if (is_dir($ext_style_template_path)) + { + $is_valid_dir = true; + $paths[] = $ext_style_template_path; + } + if (is_dir($ext_style_theme_path)) + { + $is_valid_dir = true; + $paths[] = $ext_style_theme_path; + } + + if ($is_valid_dir) + { + // Add the base style directory as a safe directory + $this->twig->getLoader()->addSafeDirectory($ext_style_path); + } + } + + $this->twig->getLoader()->setPaths($paths, $namespace); + } + } + + return $this; + } + + /** + * Display a template for provided handle. + * + * The template will be loaded and compiled, if necessary, first. + * + * This function calls hooks. + * + * @param string $handle Handle to display + * @return \phpbb\template\template $this + */ + public function display($handle) + { + $result = $this->call_hook($handle, __FUNCTION__); + if ($result !== false) + { + return $result[0]; + } + + $this->twig->display($this->get_filename_from_handle($handle), $this->get_template_vars()); + + return $this; + } + + /** + * Display the handle and assign the output to a template variable + * or return the compiled result. + * + * @param string $handle Handle to operate on + * @param string $template_var Template variable to assign compiled handle to + * @param bool $return_content If true return compiled handle, otherwise assign to $template_var + * @return \phpbb\template\template|string if $return_content is true return string of the compiled handle, otherwise return $this + */ + public function assign_display($handle, $template_var = '', $return_content = true) + { + if ($return_content) + { + return $this->twig->render($this->get_filename_from_handle($handle), $this->get_template_vars()); + } + + $this->assign_var($template_var, $this->twig->render($this->get_filename_from_handle($handle), $this->get_template_vars())); + + return $this; + } + + /** + * Get template vars in a format Twig will use (from the context) + * + * @return array + */ + protected function get_template_vars() + { + $context_vars = $this->context->get_data_ref(); + + $vars = array_merge( + $context_vars['.'][0], // To get normal vars + array( + 'definition' => new \phpbb\template\twig\definition(), + 'loops' => $context_vars, // To get loops + ) + ); + + if ($this->user instanceof \phpbb\user) + { + $vars['user'] = $this->user; + } + + // cleanup + unset($vars['loops']['.']); + + // Inject in the main context the value added by assign_block_vars() to be able to use directly the Twig loops. + foreach ($vars['loops'] as $key => &$value) + { + $vars[$key] = $value; + } + + return $vars; + } + + /** + * {@inheritdoc} + */ + public function get_source_file_for_handle($handle) + { + return $this->twig->getLoader()->getCacheKey($this->get_filename_from_handle($handle)); + } +} diff --git a/phpbb/textformatter/cache_interface.php b/phpbb/textformatter/cache_interface.php new file mode 100644 index 0000000..f6b5f19 --- /dev/null +++ b/phpbb/textformatter/cache_interface.php @@ -0,0 +1,31 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter; + +/** +* Currently only used to signal that something that could effect the rendering has changed. +* BBCodes, smilies, censored words, templates, etc... +*/ +interface cache_interface +{ + /** + * Invalidate and/or regenerate this text formatter's cache(s) + */ + public function invalidate(); + + /** + * Tidy/prune this text formatter's cache(s) + */ + public function tidy(); +} diff --git a/phpbb/textformatter/data_access.php b/phpbb/textformatter/data_access.php new file mode 100644 index 0000000..0d37e62 --- /dev/null +++ b/phpbb/textformatter/data_access.php @@ -0,0 +1,252 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter; + +/** +* Data access layer that fetchs BBCodes, smilies and censored words from the database. +* To be extended to include insert/update/delete operations. +* +* Also used to get templates. +*/ +class data_access +{ + /** + * @var string Name of the BBCodes table + */ + protected $bbcodes_table; + + /** + * @var \phpbb\db\driver\driver_interface + */ + protected $db; + + /** + * @var string Name of the smilies table + */ + protected $smilies_table; + + /** + * @var string Name of the styles table + */ + protected $styles_table; + + /** + * @var string Path to the styles dir + */ + protected $styles_path; + + /** + * @var string Name of the words table + */ + protected $words_table; + + /** + * Constructor + * + * @param \phpbb\db\driver\driver_interface $db Database connection + * @param string $bbcodes_table Name of the BBCodes table + * @param string $smilies_table Name of the smilies table + * @param string $styles_table Name of the styles table + * @param string $words_table Name of the words table + * @param string $styles_path Path to the styles dir + */ + public function __construct(\phpbb\db\driver\driver_interface $db, $bbcodes_table, $smilies_table, $styles_table, $words_table, $styles_path) + { + $this->db = $db; + + $this->bbcodes_table = $bbcodes_table; + $this->smilies_table = $smilies_table; + $this->styles_table = $styles_table; + $this->words_table = $words_table; + + $this->styles_path = $styles_path; + } + + /** + * Return the list of custom BBCodes + * + * @return array + */ + public function get_bbcodes() + { + $sql = 'SELECT bbcode_match, bbcode_tpl FROM ' . $this->bbcodes_table; + + return $this->fetch_decoded_rowset($sql, ['bbcode_match']); + } + + /** + * Return the list of smilies + * + * @return array + */ + public function get_smilies() + { + // NOTE: smilies that are displayed on the posting page are processed first because they're + // typically the most used smilies and it ends up producing a slightly more efficient + // renderer + $sql = 'SELECT code, emotion, smiley_url, smiley_width, smiley_height + FROM ' . $this->smilies_table . ' + ORDER BY display_on_posting DESC'; + + return $this->fetch_decoded_rowset($sql, ['code', 'emotion', 'smiley_url']); + } + + /** + * Return the list of installed styles + * + * @return array + */ + protected function get_styles() + { + $sql = 'SELECT style_id, style_path, style_parent_id, bbcode_bitfield FROM ' . $this->styles_table; + + return $this->fetch_decoded_rowset($sql); + } + + /** + * Return the bbcode.html template for every installed style + * + * @return array 2D array. style_id as keys, each element is an array with a "template" element that contains the style's bbcode.html and a "bbcodes" element that contains the name of each BBCode that is to be stylised + */ + public function get_styles_templates() + { + $templates = array(); + + $bbcode_ids = array( + 'quote' => 0, + 'b' => 1, + 'i' => 2, + 'url' => 3, + 'img' => 4, + 'size' => 5, + 'color' => 6, + 'u' => 7, + 'code' => 8, + 'list' => 9, + '*' => 9, + 'email' => 10, + 'flash' => 11, + 'attachment' => 12, + ); + + $styles = array(); + foreach ($this->get_styles() as $row) + { + $styles[$row['style_id']] = $row; + } + + foreach ($styles as $style_id => $style) + { + $bbcodes = array(); + + // Collect the name of the BBCodes whose bit is set in the style's bbcode_bitfield + $template_bitfield = new \bitfield($style['bbcode_bitfield']); + foreach ($bbcode_ids as $bbcode_name => $bit) + { + if ($template_bitfield->get($bit)) + { + $bbcodes[] = $bbcode_name; + } + } + + $filename = $this->resolve_style_filename($styles, $style); + if ($filename === false) + { + // Ignore this style, it will use the default templates + continue; + } + + $templates[$style_id] = array( + 'bbcodes' => $bbcodes, + 'template' => file_get_contents($filename), + ); + } + + return $templates; + } + + /** + * Resolve inheritance for given style and return the path to their bbcode.html file + * + * @param array $styles Associative array of [style_id => style] containing all styles + * @param array $style Style for which we resolve + * @return string|bool Path to this style's bbcode.html, or FALSE + */ + protected function resolve_style_filename(array $styles, array $style) + { + // Look for a bbcode.html in this style's dir + $filename = $this->styles_path . $style['style_path'] . '/template/bbcode.html'; + if (file_exists($filename)) + { + return $filename; + } + + // Resolve using this style's parent + $parent_id = $style['style_parent_id']; + if ($parent_id && !empty($styles[$parent_id])) + { + return $this->resolve_style_filename($styles, $styles[$parent_id]); + } + + return false; + } + + /** + * Return the list of censored words + * + * @return array + */ + public function get_censored_words() + { + $sql = 'SELECT word, replacement FROM ' . $this->words_table; + + return $this->fetch_decoded_rowset($sql, ['word', 'replacement']); + } + + /** + * Decode HTML special chars in given rowset + * + * @param array $rows Original rowset + * @param array $columns List of columns to decode + * @return array Decoded rowset + */ + protected function decode_rowset(array $rows, array $columns) + { + foreach ($rows as &$row) + { + foreach ($columns as $column) + { + $row[$column] = htmlspecialchars_decode($row[$column]); + } + } + + return $rows; + } + + /** + * Fetch all rows for given query and decode plain text columns + * + * @param string $sql SELECT query + * @param array $columns List of columns to decode + * @return array + */ + protected function fetch_decoded_rowset($sql, array $columns = []) + { + $result = $this->db->sql_query($sql); + $rows = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + + return $this->decode_rowset($rows, $columns); + } +} diff --git a/phpbb/textformatter/parser_interface.php b/phpbb/textformatter/parser_interface.php new file mode 100644 index 0000000..ad611fb --- /dev/null +++ b/phpbb/textformatter/parser_interface.php @@ -0,0 +1,112 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter; + +interface parser_interface +{ + /** + * Parse given text + * + * @param string $text + * @return string + */ + public function parse($text); + + /** + * Disable a specific BBCode + * + * @param string $name BBCode name + * @return null + */ + public function disable_bbcode($name); + + /** + * Disable BBCodes in general + */ + public function disable_bbcodes(); + + /** + * Disable the censor + */ + public function disable_censor(); + + /** + * Disable magic URLs + */ + public function disable_magic_url(); + + /** + * Disable smilies + */ + public function disable_smilies(); + + /** + * Enable a specific BBCode + * + * @param string $name BBCode name + * @return null + */ + public function enable_bbcode($name); + + /** + * Enable BBCodes in general + */ + public function enable_bbcodes(); + + /** + * Enable the censor + */ + public function enable_censor(); + + /** + * Enable magic URLs + */ + public function enable_magic_url(); + + /** + * Enable smilies + */ + public function enable_smilies(); + + /** + * Get the list of errors that were generated during last parsing + * + * @return array[] Array of arrays. Each array contains a lang string at index 0 plus any number + * of optional parameters + */ + public function get_errors(); + + /** + * Set a variable to be used by the parser + * + * - max_font_size + * - max_img_height + * - max_img_width + * - max_smilies + * - max_urls + * + * @param string $name + * @param mixed $value + * @return null + */ + public function set_var($name, $value); + + /** + * Set multiple variables to be used by the parser + * + * @param array $vars Associative array of [name => value] + * @return null + */ + public function set_vars(array $vars); +} diff --git a/phpbb/textformatter/renderer_interface.php b/phpbb/textformatter/renderer_interface.php new file mode 100644 index 0000000..609b0bb --- /dev/null +++ b/phpbb/textformatter/renderer_interface.php @@ -0,0 +1,92 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter; + +interface renderer_interface +{ + /** + * Render given text + * + * @param string $text Text, as parsed by something that implements \phpbb\textformatter\parser + * @return string + */ + public function render($text); + + /** + * Set the smilies' path + * + * @return null + */ + public function set_smilies_path($path); + + /** + * Return the value of the "viewcensors" option + * + * @return bool Option's value + */ + public function get_viewcensors(); + + /** + * Return the value of the "viewflash" option + * + * @return bool Option's value + */ + public function get_viewflash(); + + /** + * Return the value of the "viewimg" option + * + * @return bool Option's value + */ + public function get_viewimg(); + + /** + * Return the value of the "viewsmilies" option + * + * @return bool Option's value + */ + public function get_viewsmilies(); + + /** + * Set the "viewcensors" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewcensors($value); + + /** + * Set the "viewflash" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewflash($value); + + /** + * Set the "viewimg" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewimg($value); + + /** + * Set the "viewsmilies" option + * + * @param bool $value Option's value + * @return null + */ + public function set_viewsmilies($value); +} diff --git a/phpbb/textformatter/s9e/bbcode_merger.php b/phpbb/textformatter/s9e/bbcode_merger.php new file mode 100644 index 0000000..a05ca3c --- /dev/null +++ b/phpbb/textformatter/s9e/bbcode_merger.php @@ -0,0 +1,183 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter\s9e; + +use phpbb\textformatter\s9e\factory; +use s9e\TextFormatter\Configurator\Helpers\TemplateHelper; +use s9e\TextFormatter\Configurator\Items\UnsafeTemplate; + +class bbcode_merger +{ + /** + * @var \s9e\TextFormatter\Configurator $configurator Configurator instance used to inspect BBCodes + */ + protected $configurator; + + /** + * @param \phpbb\textformatter\s9e\factory $factory + */ + public function __construct(factory $factory) + { + $this->configurator = $factory->get_configurator(); + } + + /** + * Merge two BBCode definitions + * + * All of the arrays contain a "usage" element and a "template" element + * + * @throws InvalidArgumentException if a definition cannot be interpreted + * @throws RuntimeException if something unexpected occurs + * + * @param array $without BBCode definition without an attribute + * @param array $with BBCode definition with an attribute + * @return array Merged definition + */ + public function merge_bbcodes(array $without, array $with) + { + $without = $this->create_bbcode($without); + $with = $this->create_bbcode($with); + + // Select the appropriate strategy for merging this BBCode + if ($this->is_content_bbcode($without, $with)) + { + $merged = $this->merge_content_bbcode($without, $with); + } + else + { + $merged = $this->merge_optional_bbcode($without, $with); + } + + $merged['template'] = $this->normalize_template($merged['template']); + + return $merged; + } + + /** + * Create a custom BBCode for inspection + * + * @param array $definition Original BBCode definition + * @return array Updated definition containing a BBCode object and a Tag + */ + protected function create_bbcode(array $definition) + { + $bbcode = $this->configurator->BBCodes->addCustom( + $definition['usage'], + new UnsafeTemplate($definition['template']) + ); + + $definition['bbcode'] = $bbcode; + $definition['tag'] = $this->configurator->tags[$bbcode->tagName]; + + return $definition; + } + + /** + * Indent given template for readability + * + * @param string $template + * @return string + */ + protected function indent_template($template) + { + $dom = TemplateHelper::loadTemplate($template); + $dom->formatOutput = true; + $template = TemplateHelper::saveTemplate($dom); + + // Remove the first level of indentation if the template starts with whitespace + if (preg_match('(^\\n +)', $template, $m)) + { + $template = str_replace($m[0], "\n", $template); + } + + return trim($template); + } + + /** + * Test whether the two definitions form a "content"-style BBCode + * + * Such BBCodes include the [URL] BBCode, which uses its text content as + * attribute if none is provided + * + * @param array $without BBCode definition without an attribute + * @param array $with BBCode definition with an attribute + * @return array Merged definition + */ + protected function is_content_bbcode(array $without, array $with) + { + // Test whether we find the same non-TEXT token between "]" and "[" in the usage + // as between ">" and "<" in the template + return (preg_match('(\\]\\s*(\\{(?!TEXT)[^}]+\\})\\s*\\[)', $without['usage'], $m) + && preg_match('(>[^<]*?' . preg_quote($m[1]) . '[^>]*?<)s', $without['template'])); + } + + /** + * Merge the two BBCode definitions of a "content"-style BBCode + * + * @param array $without BBCode definition without an attribute + * @param array $with BBCode definition with an attribute + * @return array Merged definition + */ + protected function merge_content_bbcode(array $without, array $with) + { + // Convert [X={X}] into [X={X;useContent}] + $usage = preg_replace('(\\})', ';useContent}', $with['usage'], 1); + + // Use the template from the definition that uses an attribute + $template = $with['tag']->template; + + return ['usage' => $usage, 'template' => $template]; + } + + /** + * Merge the two BBCode definitions of a BBCode with an optional argument + * + * Such BBCodes include the [QUOTE] BBCode, which takes an optional argument + * but otherwise does not behave differently + * + * @param array $without BBCode definition without an attribute + * @param array $with BBCode definition with an attribute + * @return array Merged definition + */ + protected function merge_optional_bbcode(array $without, array $with) + { + // Convert [X={X}] into [X={X?}] + $usage = preg_replace('(\\})', '?}', $with['usage'], 1); + + // Build a template for both versions + $template = '' . $with['tag']->template . '' . $without['tag']->template . ''; + + return ['usage' => $usage, 'template' => $template]; + } + + /** + * Normalize a template + * + * @param string $template + * @return string + */ + protected function normalize_template($template) + { + // Normalize the template to simplify it + $template = $this->configurator->templateNormalizer->normalizeTemplate($template); + + // Convert xsl:value-of elements back to {L_} tokens where applicable + $template = preg_replace('()', '{$1}', $template); + + // Beautify the template + $template = $this->indent_template($template); + + return $template; + } +} diff --git a/phpbb/textformatter/s9e/factory.php b/phpbb/textformatter/s9e/factory.php new file mode 100644 index 0000000..6191b9a --- /dev/null +++ b/phpbb/textformatter/s9e/factory.php @@ -0,0 +1,676 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter\s9e; + +use s9e\TextFormatter\Configurator; +use s9e\TextFormatter\Configurator\Items\AttributeFilters\RegexpFilter; +use s9e\TextFormatter\Configurator\Items\UnsafeTemplate; + +/** +* Creates s9e\TextFormatter objects +*/ +class factory implements \phpbb\textformatter\cache_interface +{ + /** + * @var \phpbb\textformatter\s9e\link_helper + */ + protected $link_helper; + + /** + * @var \phpbb\cache\driver\driver_interface + */ + protected $cache; + + /** + * @var string Path to the cache dir + */ + protected $cache_dir; + + /** + * @var string Cache key used for the parser + */ + protected $cache_key_parser; + + /** + * @var string Cache key used for the renderer + */ + protected $cache_key_renderer; + + /** + * @var \phpbb\config\config + */ + protected $config; + + /** + * @var array Custom tokens used in bbcode.html and their corresponding token from the definition + */ + protected $custom_tokens = array( + 'email' => array('{DESCRIPTION}' => '{TEXT}'), + 'flash' => array('{WIDTH}' => '{NUMBER1}', '{HEIGHT}' => '{NUMBER2}'), + 'img' => array('{URL}' => '{IMAGEURL}'), + 'list' => array('{LIST_TYPE}' => '{HASHMAP}'), + 'quote' => array('{USERNAME}' => '{TEXT1}'), + 'size' => array('{SIZE}' => '{FONTSIZE}'), + 'url' => array('{DESCRIPTION}' => '{TEXT}'), + ); + + /** + * @var \phpbb\textformatter\data_access + */ + protected $data_access; + + /** + * @var array Default BBCode definitions + */ + protected $default_definitions = array( + 'attachment' => '[ATTACHMENT index={NUMBER} filename={TEXT;useContent}]', + 'b' => '[B]{TEXT}[/B]', + 'code' => '[CODE lang={IDENTIFIER;optional}]{TEXT}[/CODE]', + 'color' => '[COLOR={COLOR}]{TEXT}[/COLOR]', + 'email' => '[EMAIL={EMAIL;useContent} subject={TEXT1;optional;postFilter=rawurlencode} body={TEXT2;optional;postFilter=rawurlencode}]{TEXT}[/EMAIL]', + 'flash' => '[FLASH={NUMBER1},{NUMBER2} width={NUMBER1;postFilter=#flashwidth} height={NUMBER2;postFilter=#flashheight} url={URL;useContent} /]', + 'i' => '[I]{TEXT}[/I]', + 'img' => '[IMG src={IMAGEURL;useContent}]', + 'list' => '[LIST type={HASHMAP=1:decimal,a:lower-alpha,A:upper-alpha,i:lower-roman,I:upper-roman;optional;postFilter=#simpletext} #createChild=LI]{TEXT}[/LIST]', + 'li' => '[* $tagName=LI]{TEXT}[/*]', + 'quote' => + "[QUOTE + author={TEXT1;optional} + post_id={UINT;optional} + post_url={URL;optional;postFilter=#false} + profile_url={URL;optional;postFilter=#false} + time={UINT;optional} + url={URL;optional} + user_id={UINT;optional} + author={PARSE=/^\\[url=(?'url'.*?)](?'author'.*)\\[\\/url]$/i} + author={PARSE=/^\\[url](?'author'(?'url'.*?))\\[\\/url]$/i} + author={PARSE=/(?'url'https?:\\/\\/[^[\\]]+)/i} + ]{TEXT2}[/QUOTE]", + 'size' => '[SIZE={FONTSIZE}]{TEXT}[/SIZE]', + 'u' => '[U]{TEXT}[/U]', + 'url' => '[URL={URL;useContent} $forceLookahead=true]{TEXT}[/URL]', + ); + + /** + * @var array Default templates, taken from bbcode::bbcode_tpl() + */ + protected $default_templates = array( + 'b' => '', + 'i' => '', + 'u' => '', + 'img' => '{L_IMAGE}', + 'size' => '', + 'color' => '', + 'email' => ' + + mailto: + + + ? + subject= + &body= + + + + ', + ); + + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * @var \phpbb\log\log_interface + */ + protected $log; + + /** + * Constructor + * + * @param \phpbb\textformatter\data_access $data_access + * @param \phpbb\cache\driver\driver_interface $cache + * @param \phpbb\event\dispatcher_interface $dispatcher + * @param \phpbb\config\config $config + * @param \phpbb\textformatter\s9e\link_helper $link_helper + * @param \phpbb\log\log_interface $log + * @param string $cache_dir Path to the cache dir + * @param string $cache_key_parser Cache key used for the parser + * @param string $cache_key_renderer Cache key used for the renderer + */ + public function __construct(\phpbb\textformatter\data_access $data_access, \phpbb\cache\driver\driver_interface $cache, \phpbb\event\dispatcher_interface $dispatcher, \phpbb\config\config $config, \phpbb\textformatter\s9e\link_helper $link_helper, \phpbb\log\log_interface $log, $cache_dir, $cache_key_parser, $cache_key_renderer) + { + $this->link_helper = $link_helper; + $this->cache = $cache; + $this->cache_dir = $cache_dir; + $this->cache_key_parser = $cache_key_parser; + $this->cache_key_renderer = $cache_key_renderer; + $this->config = $config; + $this->data_access = $data_access; + $this->dispatcher = $dispatcher; + $this->log = $log; + } + + /** + * {@inheritdoc} + */ + public function invalidate() + { + $this->regenerate(); + } + + /** + * {@inheritdoc} + * + * Will remove old renderers from the cache dir but won't touch the current renderer + */ + public function tidy() + { + // Get the name of current renderer + $renderer_data = $this->cache->get($this->cache_key_renderer); + $renderer_file = ($renderer_data) ? $renderer_data['class'] . '.php' : null; + + foreach (glob($this->cache_dir . 's9e_*') as $filename) + { + // Only remove the file if it's not the current renderer + if (!$renderer_file || substr($filename, -strlen($renderer_file)) !== $renderer_file) + { + unlink($filename); + } + } + } + + /** + * Generate and return a new configured instance of s9e\TextFormatter\Configurator + * + * @return Configurator + */ + public function get_configurator() + { + // Create a new Configurator + $configurator = new Configurator; + + /** + * Modify the s9e\TextFormatter configurator before the default settings are set + * + * @event core.text_formatter_s9e_configure_before + * @var \s9e\TextFormatter\Configurator configurator Configurator instance + * @since 3.2.0-a1 + */ + $vars = array('configurator'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_before', compact($vars))); + + // Reset the list of allowed schemes + foreach ($configurator->urlConfig->getAllowedSchemes() as $scheme) + { + $configurator->urlConfig->disallowScheme($scheme); + } + foreach (explode(',', $this->config['allowed_schemes_links']) as $scheme) + { + $configurator->urlConfig->allowScheme(trim($scheme)); + } + + // Convert newlines to br elements by default + $configurator->rootRules->enableAutoLineBreaks(); + + // Don't automatically ignore text in places where text is not allowed + $configurator->rulesGenerator->remove('IgnoreTextIfDisallowed'); + + // Don't remove comments and instead convert them to xsl:comment elements + $configurator->templateNormalizer->remove('RemoveComments'); + $configurator->templateNormalizer->add('TransposeComments'); + + // Set the rendering engine and configure it to save to the cache dir + $configurator->rendering->engine = 'PHP'; + $configurator->rendering->engine->cacheDir = $this->cache_dir; + $configurator->rendering->engine->defaultClassPrefix = 's9e_renderer_'; + $configurator->rendering->engine->enableQuickRenderer = true; + + // Create custom filters for BBCode tokens that are supported in phpBB but not in + // s9e\TextFormatter + $filter = new RegexpFilter('#^' . get_preg_expression('relative_url') . '$#Du'); + $configurator->attributeFilters->add('#local_url', $filter); + $configurator->attributeFilters->add('#relative_url', $filter); + + // INTTEXT regexp from acp_bbcodes + $filter = new RegexpFilter('!^([\p{L}\p{N}\-+,_. ]+)$!Du'); + $configurator->attributeFilters->add('#inttext', $filter); + + // Create custom filters for Flash restrictions, which use the same values as the image + // restrictions but have their own error message + $configurator->attributeFilters + ->add('#flashheight', __NAMESPACE__ . '\\parser::filter_flash_height') + ->addParameterByName('max_img_height') + ->addParameterByName('logger'); + + $configurator->attributeFilters + ->add('#flashwidth', __NAMESPACE__ . '\\parser::filter_flash_width') + ->addParameterByName('max_img_width') + ->addParameterByName('logger'); + + // Create a custom filter for phpBB's per-mode font size limits + $configurator->attributeFilters + ->add('#fontsize', __NAMESPACE__ . '\\parser::filter_font_size') + ->addParameterByName('max_font_size') + ->addParameterByName('logger') + ->markAsSafeInCSS(); + + // Create a custom filter for image URLs + $configurator->attributeFilters + ->add('#imageurl', __NAMESPACE__ . '\\parser::filter_img_url') + ->addParameterByName('urlConfig') + ->addParameterByName('logger') + ->addParameterByName('max_img_height') + ->addParameterByName('max_img_width') + ->markAsSafeAsURL() + ->setJS('UrlFilter.filter'); + + // Add default BBCodes + foreach ($this->get_default_bbcodes($configurator) as $bbcode) + { + $this->add_bbcode($configurator, $bbcode['usage'], $bbcode['template']); + } + if (isset($configurator->tags['QUOTE'])) + { + // Remove the nesting limit and let other services remove quotes at parsing time + $configurator->tags['QUOTE']->nestingLimit = PHP_INT_MAX; + } + + // Modify the template to disable images/flash depending on user's settings + foreach (array('FLASH', 'IMG') as $name) + { + $tag = $configurator->tags[$name]; + $tag->template = '' . $tag->template . ''; + } + + // Load custom BBCodes + foreach ($this->data_access->get_bbcodes() as $row) + { + // Insert the board's URL before {LOCAL_URL} tokens + $tpl = preg_replace_callback( + '#\\{LOCAL_URL\\d*\\}#', + function ($m) + { + return generate_board_url() . '/' . $m[0]; + }, + $row['bbcode_tpl'] + ); + $this->add_bbcode($configurator, $row['bbcode_match'], $tpl); + } + + // Load smilies + foreach ($this->data_access->get_smilies() as $row) + { + $configurator->Emoticons->set( + $row['code'], + '{.}' + ); + } + + if (isset($configurator->Emoticons)) + { + // Force emoticons to be rendered as text if $S_VIEWSMILIES is not set + $configurator->Emoticons->notIfCondition = 'not($S_VIEWSMILIES)'; + + // Only parse emoticons at the beginning of the text or if they're preceded by any + // one of: a new line, a space, a dot, or a right square bracket + $configurator->Emoticons->notAfter = '[^\\n .\\]]'; + + // Ignore emoticons that are immediately followed by a "word" character + $configurator->Emoticons->notBefore = '\\w'; + } + + // Load the censored words + $censor = $this->data_access->get_censored_words(); + if (!empty($censor)) + { + // Use a namespaced tag to avoid collisions + $configurator->plugins->load('Censor', array('tagName' => 'censor:tag')); + foreach ($censor as $row) + { + $configurator->Censor->add($row['word'], $row['replacement']); + } + } + + // Load the magic links plugins. We do that after BBCodes so that they use the same tags + $this->configure_autolink($configurator); + + // Register some vars with a default value. Those should be set at runtime by whatever calls + // the parser + $configurator->registeredVars['max_font_size'] = 0; + $configurator->registeredVars['max_img_height'] = 0; + $configurator->registeredVars['max_img_width'] = 0; + + // Load the Emoji plugin and modify its tag's template to obey viewsmilies + $tag = $configurator->Emoji->getTag(); + $tag->template = ' + + {.} + + + {.} + + '; + $tag->template = '' . str_replace('class="emoji"', 'class="emoji smilies"', $tag->template) . ''; + + /** + * Modify the s9e\TextFormatter configurator after the default settings are set + * + * @event core.text_formatter_s9e_configure_after + * @var \s9e\TextFormatter\Configurator configurator Configurator instance + * @since 3.2.0-a1 + */ + $vars = array('configurator'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_after', compact($vars))); + + return $configurator; + } + + /** + * Regenerate and cache a new parser and renderer + * + * @return array Associative array with at least two elements: "parser" and "renderer" + */ + public function regenerate() + { + $configurator = $this->get_configurator(); + + // Get the censor helper and remove the Censor plugin if applicable + if (isset($configurator->Censor)) + { + $censor = $configurator->Censor->getHelper(); + unset($configurator->Censor); + unset($configurator->tags['censor:tag']); + } + + $objects = $configurator->finalize(); + + /** + * Access the objects returned by finalize() before they are saved to cache + * + * @event core.text_formatter_s9e_configure_finalize + * @var array objects Array containing a "parser" object, a "renderer" object and optionally a "js" string + * @since 3.2.2-RC1 + */ + $vars = array('objects'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_configure_finalize', compact($vars))); + + $parser = $objects['parser']; + $renderer = $objects['renderer']; + + // Cache the parser as-is + $this->cache->put($this->cache_key_parser, $parser); + + // We need to cache the name of the renderer's generated class + $renderer_data = array('class' => get_class($renderer)); + if (isset($censor)) + { + $renderer_data['censor'] = $censor; + } + $this->cache->put($this->cache_key_renderer, $renderer_data); + + return array('parser' => $parser, 'renderer' => $renderer); + } + + /** + * Add a BBCode to given configurator + * + * @param Configurator $configurator + * @param string $usage + * @param string $template + * @return void + */ + protected function add_bbcode(Configurator $configurator, $usage, $template) + { + try + { + $configurator->BBCodes->addCustom($usage, new UnsafeTemplate($template)); + } + catch (\Exception $e) + { + $this->log->add('critical', null, null, 'LOG_BBCODE_CONFIGURATION_ERROR', false, [$usage, $e->getMessage()]); + } + } + + /** + * Configure the Autolink / Autoemail plugins used to linkify text + * + * @param \s9e\TextFormatter\Configurator $configurator + * @return void + */ + protected function configure_autolink(Configurator $configurator) + { + $configurator->plugins->load('Autoemail'); + $configurator->plugins->load('Autolink', array('matchWww' => true)); + + // Add a tag filter that creates a tag that stores and replace the + // content of a link created by the Autolink plugin + $configurator->Autolink->getTag()->filterChain + ->add(array($this->link_helper, 'generate_link_text_tag')) + ->resetParameters() + ->addParameterByName('tag') + ->addParameterByName('parser'); + + // Create a tag that will be used to display the truncated text by + // replacing the original content with the content of the @text attribute + $tag = $configurator->tags->add('LINK_TEXT'); + $tag->attributes->add('text'); + $tag->template = ''; + + $tag->filterChain + ->add(array($this->link_helper, 'truncate_local_url')) + ->resetParameters() + ->addParameterByName('tag') + ->addParameterByValue(generate_board_url() . '/'); + $tag->filterChain + ->add(array($this->link_helper, 'truncate_text')) + ->resetParameters() + ->addParameterByName('tag'); + $tag->filterChain + ->add(array($this->link_helper, 'cleanup_tag')) + ->resetParameters() + ->addParameterByName('tag') + ->addParameterByName('parser'); + } + + /** + * Escape a literal to be used in an HTML attribute in an XSL template + * + * Escapes "HTML special chars" for obvious reasons and curly braces to avoid them + * being interpreted as an attribute value template + * + * @param string $value Original string + * @return string Escaped string + */ + protected function escape_html_attribute($value) + { + return htmlspecialchars(strtr($value, ['{' => '{{', '}' => '}}']), ENT_COMPAT | ENT_XML1, 'UTF-8'); + } + + /** + * Return the default BBCodes configuration + * + * @return array 2D array. Each element has a 'usage' key, a 'template' key, and an optional 'options' key + */ + protected function get_default_bbcodes($configurator) + { + // For each BBCode, build an associative array matching style_ids to their template + $templates = array(); + foreach ($this->data_access->get_styles_templates() as $style_id => $data) + { + foreach ($this->extract_templates($data['template']) as $bbcode_name => $template) + { + $templates[$bbcode_name][$style_id] = $template; + } + + // Add default templates wherever missing, or for BBCodes that were not specified in + // this template's bitfield. For instance, prosilver has a custom template for b but its + // bitfield does not enable it so the default template is used instead + foreach ($this->default_templates as $bbcode_name => $template) + { + if (!isset($templates[$bbcode_name][$style_id]) || !in_array($bbcode_name, $data['bbcodes'], true)) + { + $templates[$bbcode_name][$style_id] = $template; + } + } + } + + // Replace custom tokens and normalize templates + foreach ($templates as $bbcode_name => $style_templates) + { + foreach ($style_templates as $i => $template) + { + if (isset($this->custom_tokens[$bbcode_name])) + { + $template = strtr($template, $this->custom_tokens[$bbcode_name]); + } + + $templates[$bbcode_name][$i] = $configurator->templateNormalizer->normalizeTemplate($template); + } + } + + $bbcodes = array(); + foreach ($this->default_definitions as $bbcode_name => $usage) + { + $bbcodes[$bbcode_name] = array( + 'usage' => $usage, + 'template' => $this->merge_templates($templates[$bbcode_name]), + ); + } + + return $bbcodes; + } + + /** + * Extract and recompose individual BBCode templates from a style's template file + * + * @param string $template Style template (bbcode.html) + * @return array Associative array matching BBCode names to their template + */ + protected function extract_templates($template) + { + // Capture the template fragments + // Allow either phpBB template or the Twig syntax + preg_match_all('#(.*?)#s', $template, $matches, PREG_SET_ORDER) ?: + preg_match_all('#{% for (.*?) in .*? %}(.*?){% endfor %}#s', $template, $matches, PREG_SET_ORDER); + + $fragments = array(); + foreach ($matches as $match) + { + // Normalize the whitespace + $fragment = preg_replace('#>\\n\\t*<#', '><', trim($match[2])); + + $fragments[$match[1]] = $fragment; + } + + // Automatically recompose templates split between *_open and *_close + foreach ($fragments as $fragment_name => $fragment) + { + if (preg_match('#^(\\w+)_close$#', $fragment_name, $match)) + { + $bbcode_name = $match[1]; + + if (isset($fragments[$bbcode_name . '_open'])) + { + $templates[$bbcode_name] = $fragments[$bbcode_name . '_open'] . '' . $fragment; + } + } + } + + // Manually recompose and overwrite irregular templates + $templates['list'] = + ' + + ' . $fragments['ulist_open_default'] . '' . $fragments['ulist_close'] . ' + + + ' . $fragments['olist_open'] . '' . $fragments['olist_close'] . ' + + + ' . $fragments['ulist_open'] . '' . $fragments['ulist_close'] . ' + + '; + + $templates['li'] = $fragments['listitem'] . '' . $fragments['listitem_close']; + + // Replace the regular quote template with the extended quote template if available + if (isset($fragments['quote_extended'])) + { + $templates['quote'] = $fragments['quote_extended']; + } + + // The [attachment] BBCode uses the inline_attachment template to output a comment that + // is post-processed by parse_attachments() + $templates['attachment'] = $fragments['inline_attachment_open'] . ' ia ia ' . $fragments['inline_attachment_close']; + + // Add fragments as templates + foreach ($fragments as $fragment_name => $fragment) + { + if (preg_match('#^\\w+$#', $fragment_name)) + { + $templates[$fragment_name] = $fragment; + } + } + + // Keep only templates that are named after an existing BBCode + $templates = array_intersect_key($templates, $this->default_definitions); + + return $templates; + } + + /** + * Merge the templates from any number of styles into one BBCode template + * + * When multiple templates are available for the same BBCode (because of multiple styles) we + * merge them into a single template that uses an xsl:choose construct that determines which + * style to use at rendering time. + * + * @param array $style_templates Associative array matching style_ids to their template + * @return string + */ + protected function merge_templates(array $style_templates) + { + // Return the template as-is if there's only one style or all styles share the same template + if (count(array_unique($style_templates)) === 1) + { + return end($style_templates); + } + + // Group identical templates together + $grouped_templates = array(); + foreach ($style_templates as $style_id => $style_template) + { + $grouped_templates[$style_template][] = '$STYLE_ID=' . $style_id; + } + + // Sort templates by frequency descending + $templates_cnt = array_map('sizeof', $grouped_templates); + array_multisort($grouped_templates, $templates_cnt); + + // Remove the most frequent template from the list; It becomes the default + reset($grouped_templates); + $default_template = key($grouped_templates); + unset($grouped_templates[$default_template]); + + // Build an xsl:choose switch + $template = ''; + foreach ($grouped_templates as $style_template => $exprs) + { + $template .= '' . $style_template . ''; + } + $template .= '' . $default_template . ''; + + return $template; + } +} diff --git a/phpbb/textformatter/s9e/link_helper.php b/phpbb/textformatter/s9e/link_helper.php new file mode 100644 index 0000000..483794a --- /dev/null +++ b/phpbb/textformatter/s9e/link_helper.php @@ -0,0 +1,115 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter\s9e; + +class link_helper +{ + /** + * Clean up and invalidate a LINK_TEXT tag if applicable + * + * Will invalidate the tag if its replacement text is the same as the original + * text and would have no visible effect + * + * @param \s9e\TextFormatter\Parser\Tag $tag LINK_TEXT tag + * @param \s9e\TextFormatter\Parser $parser Parser + * @return void + */ + public function cleanup_tag(\s9e\TextFormatter\Parser\Tag $tag, \s9e\TextFormatter\Parser $parser) + { + // Invalidate if the content of the tag matches the text attribute + $text = substr($parser->getText(), $tag->getPos(), $tag->getLen()); + if ($text === $tag->getAttribute('text')) + { + $tag->invalidate(); + } + } + + /** + * Create a LINK_TEXT tag inside of a link + * + * Meant to only apply to linkified URLs and [url] BBCodes without a parameter + * + * @param \s9e\TextFormatter\Parser\Tag $tag URL tag (start tag) + * @param \s9e\TextFormatter\Parser $parser Parser + * @return void + */ + public function generate_link_text_tag(\s9e\TextFormatter\Parser\Tag $tag, \s9e\TextFormatter\Parser $parser) + { + // Only create a LINK_TEXT tag if the start tag is paired with an end + // tag, which is the case with tags from the Autolink plugins and with + // the [url] BBCode when its content is used for the URL + if (!$tag->getEndTag() || !$this->should_shorten($tag, $parser->getText())) + { + return; + } + + // Capture the text between the start tag and its end tag + $start = $tag->getPos() + $tag->getLen(); + $end = $tag->getEndTag()->getPos(); + $length = $end - $start; + $text = substr($parser->getText(), $start, $length); + + // Create a tag that consumes the link's text and make it depends on this tag + $link_text_tag = $parser->addSelfClosingTag('LINK_TEXT', $start, $length); + $link_text_tag->setAttribute('text', $text); + $tag->cascadeInvalidationTo($link_text_tag); + } + + /** + * Test whether we should shorten this tag's text + * + * Will test whether the tag either does not use any markup or uses a single + * [url] BBCode + * + * @param \s9e\TextFormatter\Parser\Tag $tag URL tag + * @param string $text Original text + * @return bool + */ + protected function should_shorten(\s9e\TextFormatter\Parser\Tag $tag, $text) + { + return ($tag->getLen() === 0 || strtolower(substr($text, $tag->getPos(), $tag->getLen())) === '[url]'); + } + + /** + * Remove the board's root URL from a the start of a string + * + * @param \s9e\TextFormatter\Parser\Tag $tag LINK_TEXT tag + * @param string $board_url Forum's root URL (with trailing slash) + * @return void + */ + public function truncate_local_url(\s9e\TextFormatter\Parser\Tag $tag, $board_url) + { + $text = $tag->getAttribute('text'); + if (stripos($text, $board_url) === 0 && strlen($text) > strlen($board_url)) + { + $tag->setAttribute('text', substr($text, strlen($board_url))); + } + } + + /** + * Truncate the replacement text set in a LINK_TEXT tag + * + * @param \s9e\TextFormatter\Parser\Tag $tag LINK_TEXT tag + * @return void + */ + public function truncate_text(\s9e\TextFormatter\Parser\Tag $tag) + { + $text = $tag->getAttribute('text'); + if (utf8_strlen($text) > 55) + { + $text = utf8_substr($text, 0, 39) . ' ... ' . utf8_substr($text, -10); + $tag->setAttribute('text', $text); + } + } +} diff --git a/phpbb/textformatter/s9e/parser.php b/phpbb/textformatter/s9e/parser.php new file mode 100644 index 0000000..3698dca --- /dev/null +++ b/phpbb/textformatter/s9e/parser.php @@ -0,0 +1,399 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter\s9e; + +use s9e\TextFormatter\Parser\AttributeFilters\UrlFilter; +use s9e\TextFormatter\Parser\Logger; + +/** +* s9e\TextFormatter\Parser adapter +*/ +class parser implements \phpbb\textformatter\parser_interface +{ + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * @var \s9e\TextFormatter\Parser + */ + protected $parser; + + /** + * Constructor + * + * @param \phpbb\cache\driver_interface $cache + * @param string $key Cache key + * @param factory $factory + * @param \phpbb\event\dispatcher_interface $dispatcher + */ + public function __construct(\phpbb\cache\driver\driver_interface $cache, $key, factory $factory, \phpbb\event\dispatcher_interface $dispatcher) + { + $parser = $cache->get($key); + if (!$parser) + { + $objects = $factory->regenerate(); + $parser = $objects['parser']; + } + + $this->dispatcher = $dispatcher; + $this->parser = $parser; + + $parser = $this; + + /** + * Configure the parser service + * + * Can be used to: + * - toggle features or BBCodes + * - register variables or custom parsers in the s9e\TextFormatter parser + * - configure the s9e\TextFormatter parser's runtime settings + * + * @event core.text_formatter_s9e_parser_setup + * @var \phpbb\textformatter\s9e\parser parser This parser service + * @since 3.2.0-a1 + */ + $vars = array('parser'); + extract($dispatcher->trigger_event('core.text_formatter_s9e_parser_setup', compact($vars))); + } + + /** + * {@inheritdoc} + */ + public function parse($text) + { + $parser = $this; + + /** + * Modify a text before it is parsed + * + * @event core.text_formatter_s9e_parse_before + * @var \phpbb\textformatter\s9e\parser parser This parser service + * @var string text The original text + * @since 3.2.0-a1 + */ + $vars = array('parser', 'text'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_before', compact($vars))); + + $xml = $this->parser->parse($text); + + /** + * Modify a parsed text in its XML form + * + * @event core.text_formatter_s9e_parse_after + * @var \phpbb\textformatter\s9e\parser parser This parser service + * @var string xml The parsed text, in XML + * @since 3.2.0-a1 + */ + $vars = array('parser', 'xml'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_parse_after', compact($vars))); + + return $xml; + } + + /** + * {@inheritdoc} + */ + public function disable_bbcode($name) + { + $this->parser->disableTag(strtoupper($name)); + } + + /** + * {@inheritdoc} + */ + public function disable_bbcodes() + { + $this->parser->disablePlugin('BBCodes'); + } + + /** + * {@inheritdoc} + */ + public function disable_censor() + { + $this->parser->disablePlugin('Censor'); + } + + /** + * {@inheritdoc} + */ + public function disable_magic_url() + { + $this->parser->disablePlugin('Autoemail'); + $this->parser->disablePlugin('Autolink'); + } + + /** + * {@inheritdoc} + */ + public function disable_smilies() + { + $this->parser->disablePlugin('Emoticons'); + $this->parser->disablePlugin('Emoji'); + } + + /** + * {@inheritdoc} + */ + public function enable_bbcode($name) + { + $this->parser->enableTag(strtoupper($name)); + } + + /** + * {@inheritdoc} + */ + public function enable_bbcodes() + { + $this->parser->enablePlugin('BBCodes'); + } + + /** + * {@inheritdoc} + */ + public function enable_censor() + { + $this->parser->enablePlugin('Censor'); + } + + /** + * {@inheritdoc} + */ + public function enable_magic_url() + { + $this->parser->enablePlugin('Autoemail'); + $this->parser->enablePlugin('Autolink'); + } + + /** + * {@inheritdoc} + */ + public function enable_smilies() + { + $this->parser->enablePlugin('Emoticons'); + $this->parser->enablePlugin('Emoji'); + } + + /** + * {@inheritdoc} + * + * This will convert the log entries found in s9e\TextFormatter's logger into phpBB error + * messages + */ + public function get_errors() + { + $errors = array(); + foreach ($this->parser->getLogger()->getLogs() as $entry) + { + list(, $msg, $context) = $entry; + + if ($msg === 'Tag limit exceeded') + { + if ($context['tagName'] === 'E') + { + $errors[] = array('TOO_MANY_SMILIES', $context['tagLimit']); + } + else if ($context['tagName'] === 'URL') + { + $errors[] = array('TOO_MANY_URLS', $context['tagLimit']); + } + } + else if ($msg === 'MAX_FONT_SIZE_EXCEEDED') + { + $errors[] = array($msg, $context['max_size']); + } + else if (preg_match('/^MAX_(?:FLASH|IMG)_(HEIGHT|WIDTH)_EXCEEDED$/D', $msg, $m)) + { + $errors[] = array($msg, $context['max_' . strtolower($m[1])]); + } + else if ($msg === 'Tag is disabled') + { + $name = strtolower($context['tag']->getName()); + $errors[] = array('UNAUTHORISED_BBCODE', '[' . $name . ']'); + } + else if ($msg === 'UNABLE_GET_IMAGE_SIZE') + { + $errors[] = array($msg); + } + } + + // Deduplicate error messages. array_unique() only works on strings so we have to serialize + if (!empty($errors)) + { + $errors = array_map('unserialize', array_unique(array_map('serialize', $errors))); + } + + return $errors; + } + + /** + * Return the instance of s9e\TextFormatter\Parser used by this object + * + * @return \s9e\TextFormatter\Parser + */ + public function get_parser() + { + return $this->parser; + } + + /** + * {@inheritdoc} + */ + public function set_var($name, $value) + { + if ($name === 'max_smilies') + { + $this->parser->setTagLimit('E', $value ?: PHP_INT_MAX); + } + else if ($name === 'max_urls') + { + $this->parser->setTagLimit('URL', $value ?: PHP_INT_MAX); + } + else + { + $this->parser->registeredVars[$name] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function set_vars(array $vars) + { + foreach ($vars as $name => $value) + { + $this->set_var($name, $value); + } + } + + /** + * Filter a flash object's height + * + * @see bbcode_firstpass::bbcode_flash() + * + * @param string $height + * @param integer $max_height + * @param Logger $logger + * @return mixed Original value if valid, FALSE otherwise + */ + static public function filter_flash_height($height, $max_height, Logger $logger) + { + if ($max_height && $height > $max_height) + { + $logger->err('MAX_FLASH_HEIGHT_EXCEEDED', array('max_height' => $max_height)); + + return false; + } + + return $height; + } + + /** + * Filter a flash object's width + * + * @see bbcode_firstpass::bbcode_flash() + * + * @param string $width + * @param integer $max_width + * @param Logger $logger + * @return mixed Original value if valid, FALSE otherwise + */ + static public function filter_flash_width($width, $max_width, Logger $logger) + { + if ($max_width && $width > $max_width) + { + $logger->err('MAX_FLASH_WIDTH_EXCEEDED', array('max_width' => $max_width)); + + return false; + } + + return $width; + } + + /** + * Filter the value used in a [size] BBCode + * + * @see bbcode_firstpass::bbcode_size() + * + * @param string $size Original size + * @param integer $max_size Maximum allowed size + * @param Logger $logger + * @return mixed Original value if valid, FALSE otherwise + */ + static public function filter_font_size($size, $max_size, Logger $logger) + { + if ($max_size && $size > $max_size) + { + $logger->err('MAX_FONT_SIZE_EXCEEDED', array('max_size' => $max_size)); + + return false; + } + + if ($size < 1) + { + return false; + } + + return $size; + } + + /** + * Filter an image's URL to enforce restrictions on its dimensions + * + * @see bbcode_firstpass::bbcode_img() + * + * @param string $url Original URL + * @param array $url_config Config used by the URL filter + * @param Logger $logger + * @param integer $max_height Maximum height allowed + * @param integer $max_width Maximum width allowed + * @return string|bool Original value if valid, FALSE otherwise + */ + static public function filter_img_url($url, array $url_config, Logger $logger, $max_height, $max_width) + { + // Validate the URL + $url = UrlFilter::filter($url, $url_config, $logger); + if ($url === false) + { + return false; + } + + if ($max_height || $max_width) + { + $imagesize = new \FastImageSize\FastImageSize(); + $size_info = $imagesize->getImageSize($url); + if ($size_info === false) + { + $logger->err('UNABLE_GET_IMAGE_SIZE'); + return false; + } + + if ($max_height && $max_height < $size_info['height']) + { + $logger->err('MAX_IMG_HEIGHT_EXCEEDED', array('max_height' => $max_height)); + return false; + } + + if ($max_width && $max_width < $size_info['width']) + { + $logger->err('MAX_IMG_WIDTH_EXCEEDED', array('max_width' => $max_width)); + return false; + } + } + + return $url; + } +} diff --git a/phpbb/textformatter/s9e/quote_helper.php b/phpbb/textformatter/s9e/quote_helper.php new file mode 100644 index 0000000..86c33c7 --- /dev/null +++ b/phpbb/textformatter/s9e/quote_helper.php @@ -0,0 +1,81 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter\s9e; + +class quote_helper +{ + /** + * @var string Base URL for a post link, uses {POST_ID} as placeholder + */ + protected $post_url; + + /** + * @var string Base URL for a profile link, uses {USER_ID} as placeholder + */ + protected $profile_url; + + /** + * @var \phpbb\user + */ + protected $user; + + /** + * Constructor + * + * @param \phpbb\user $user + * @param string $root_path + * @param string $php_ext + */ + public function __construct(\phpbb\user $user, $root_path, $php_ext) + { + $this->post_url = append_sid($root_path . 'viewtopic.' . $php_ext, 'p={POST_ID}#p{POST_ID}', false); + $this->profile_url = append_sid($root_path . 'memberlist.' . $php_ext, 'mode=viewprofile&u={USER_ID}', false); + $this->user = $user; + } + + /** + * Inject dynamic metadata into QUOTE tags in given XML + * + * @param string $xml Original XML + * @return string Modified XML + */ + public function inject_metadata($xml) + { + $post_url = $this->post_url; + $profile_url = $this->profile_url; + $user = $this->user; + + return \s9e\TextFormatter\Utils::replaceAttributes( + $xml, + 'QUOTE', + function ($attributes) use ($post_url, $profile_url, $user) + { + if (isset($attributes['post_id'])) + { + $attributes['post_url'] = str_replace('{POST_ID}', $attributes['post_id'], $post_url); + } + if (isset($attributes['time'])) + { + $attributes['date'] = $user->format_date($attributes['time']); + } + if (isset($attributes['user_id'])) + { + $attributes['profile_url'] = str_replace('{USER_ID}', $attributes['user_id'], $profile_url); + } + + return $attributes; + } + ); + } +} diff --git a/phpbb/textformatter/s9e/renderer.php b/phpbb/textformatter/s9e/renderer.php new file mode 100644 index 0000000..6fcd2b0 --- /dev/null +++ b/phpbb/textformatter/s9e/renderer.php @@ -0,0 +1,313 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter\s9e; + +/** +* s9e\TextFormatter\Renderer adapter +*/ +class renderer implements \phpbb\textformatter\renderer_interface +{ + /** + * @var \s9e\TextFormatter\Plugins\Censor\Helper + */ + protected $censor; + + /** + * @var \phpbb\event\dispatcher_interface + */ + protected $dispatcher; + + /** + * @var quote_helper + */ + protected $quote_helper; + + /** + * @var \s9e\TextFormatter\Renderer + */ + protected $renderer; + + /** + * @var bool Status of the viewcensors option + */ + protected $viewcensors = false; + + /** + * @var bool Status of the viewflash option + */ + protected $viewflash = false; + + /** + * @var bool Status of the viewimg option + */ + protected $viewimg = false; + + /** + * @var bool Status of the viewsmilies option + */ + protected $viewsmilies = false; + + /** + * Constructor + * + * @param \phpbb\cache\driver\driver_interface $cache + * @param string $cache_dir Path to the cache dir + * @param string $key Cache key + * @param factory $factory + * @param \phpbb\event\dispatcher_interface $dispatcher + */ + public function __construct(\phpbb\cache\driver\driver_interface $cache, $cache_dir, $key, factory $factory, \phpbb\event\dispatcher_interface $dispatcher) + { + $renderer_data = $cache->get($key); + if ($renderer_data) + { + $class = $renderer_data['class']; + if (!class_exists($class, false)) + { + // Try to load the renderer class from its cache file + $cache_file = $cache_dir . $class . '.php'; + + if (file_exists($cache_file)) + { + include($cache_file); + } + } + if (class_exists($class, false)) + { + $renderer = new $class; + } + if (isset($renderer_data['censor'])) + { + $censor = $renderer_data['censor']; + } + } + if (!isset($renderer)) + { + $objects = $factory->regenerate(); + $renderer = $objects['renderer']; + } + + if (isset($censor)) + { + $this->censor = $censor; + } + $this->dispatcher = $dispatcher; + $this->renderer = $renderer; + $renderer = $this; + + /** + * Configure the renderer service + * + * @event core.text_formatter_s9e_renderer_setup + * @var \phpbb\textformatter\s9e\renderer renderer This renderer service + * @since 3.2.0-a1 + */ + $vars = array('renderer'); + extract($dispatcher->trigger_event('core.text_formatter_s9e_renderer_setup', compact($vars))); + } + + /** + * Configure the quote_helper object used to display extended information in quotes + * + * @param quote_helper $quote_helper + */ + public function configure_quote_helper(quote_helper $quote_helper) + { + $this->quote_helper = $quote_helper; + } + + /** + * Automatically set the smilies path based on config + * + * @param \phpbb\config\config $config + * @param \phpbb\path_helper $path_helper + * @return null + */ + public function configure_smilies_path(\phpbb\config\config $config, \phpbb\path_helper $path_helper) + { + /** + * @see smiley_text() + */ + $root_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? generate_board_url() . '/' : $path_helper->get_web_root_path(); + + $this->set_smilies_path($root_path . $config['smilies_path']); + } + + /** + * Configure this renderer as per the user's settings + * + * Should set the locale as well as the viewcensor/viewflash/viewimg/viewsmilies options. + * + * @param \phpbb\user $user + * @param \phpbb\config\config $config + * @param \phpbb\auth\auth $auth + * @return null + */ + public function configure_user(\phpbb\user $user, \phpbb\config\config $config, \phpbb\auth\auth $auth) + { + $censor = $user->optionget('viewcensors') || !$config['allow_nocensors'] || !$auth->acl_get('u_chgcensors'); + + $this->set_viewcensors($censor); + $this->set_viewflash($user->optionget('viewflash')); + $this->set_viewimg($user->optionget('viewimg')); + $this->set_viewsmilies($user->optionget('viewsmilies')); + + // Set the stylesheet parameters + foreach (array_keys($this->renderer->getParameters()) as $param_name) + { + if (strpos($param_name, 'L_') === 0) + { + // L_FOO is set to $user->lang('FOO') + $this->renderer->setParameter($param_name, $user->lang(substr($param_name, 2))); + } + } + + // Set this user's style id and other parameters + $this->renderer->setParameters(array( + 'S_IS_BOT' => $user->data['is_bot'], + 'S_REGISTERED_USER' => $user->data['is_registered'], + 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS), + 'STYLE_ID' => $user->style['style_id'], + )); + } + + /** + * Return the instance of s9e\TextFormatter\Renderer used by this object + * + * @return \s9e\TextFormatter\Renderer + */ + public function get_renderer() + { + return $this->renderer; + } + + /** + * {@inheritdoc} + */ + public function get_viewcensors() + { + return $this->viewcensors; + } + + /** + * {@inheritdoc} + */ + public function get_viewflash() + { + return $this->viewflash; + } + + /** + * {@inheritdoc} + */ + public function get_viewimg() + { + return $this->viewimg; + } + + /** + * {@inheritdoc} + */ + public function get_viewsmilies() + { + return $this->viewsmilies; + } + + /** + * {@inheritdoc} + */ + public function render($xml) + { + if (isset($this->quote_helper)) + { + $xml = $this->quote_helper->inject_metadata($xml); + } + + $renderer = $this; + + /** + * Modify a parsed text before it is rendered + * + * @event core.text_formatter_s9e_render_before + * @var \phpbb\textformatter\s9e\renderer renderer This renderer service + * @var string xml The parsed text, in its XML form + * @since 3.2.0-a1 + */ + $vars = array('renderer', 'xml'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_render_before', compact($vars))); + + $html = $this->renderer->render($xml); + if (isset($this->censor) && $this->viewcensors) + { + $html = $this->censor->censorHtml($html, true); + } + + /** + * Modify a rendered text + * + * @event core.text_formatter_s9e_render_after + * @var string html The rendered text's HTML + * @var \phpbb\textformatter\s9e\renderer renderer This renderer service + * @since 3.2.0-a1 + */ + $vars = array('html', 'renderer'); + extract($this->dispatcher->trigger_event('core.text_formatter_s9e_render_after', compact($vars))); + + return $html; + } + + /** + * {@inheritdoc} + */ + public function set_smilies_path($path) + { + $this->renderer->setParameter('T_SMILIES_PATH', $path); + } + + /** + * {@inheritdoc} + */ + public function set_viewcensors($value) + { + $this->viewcensors = $value; + $this->renderer->setParameter('S_VIEWCENSORS', $value); + } + + /** + * {@inheritdoc} + */ + public function set_viewflash($value) + { + $this->viewflash = $value; + $this->renderer->setParameter('S_VIEWFLASH', $value); + } + + /** + * {@inheritdoc} + */ + public function set_viewimg($value) + { + $this->viewimg = $value; + $this->renderer->setParameter('S_VIEWIMG', $value); + } + + /** + * {@inheritdoc} + */ + public function set_viewsmilies($value) + { + $this->viewsmilies = $value; + $this->renderer->setParameter('S_VIEWSMILIES', $value); + } +} diff --git a/phpbb/textformatter/s9e/utils.php b/phpbb/textformatter/s9e/utils.php new file mode 100644 index 0000000..a9a6d4b --- /dev/null +++ b/phpbb/textformatter/s9e/utils.php @@ -0,0 +1,152 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter\s9e; + +/** +* Text manipulation utilities +*/ +class utils implements \phpbb\textformatter\utils_interface +{ + /** + * Replace BBCodes and other formatting elements with whitespace + * + * NOTE: preserves smilies as text + * + * @param string $xml Parsed text + * @return string Plain text + */ + public function clean_formatting($xml) + { + // Insert a space before and then remove formatting + $xml = preg_replace('#<[es]>#', ' $0', $xml); + + return \s9e\TextFormatter\Utils::removeFormatting($xml); + } + + /** + * Format given string to be used as an attribute value + * + * Will return the string as-is if it can be used in a BBCode without quotes. Otherwise, + * it will use either single- or double- quotes depending on whichever requires less escaping. + * Quotes and backslashes are escaped with backslashes where necessary + * + * @param string $str Original string + * @return string Same string if possible, escaped string within quotes otherwise + */ + protected function format_attribute_value($str) + { + if (!preg_match('/[ "\'\\\\\\]]/', $str)) + { + // Return as-is if it contains none of: space, ' " \ or ] + return $str; + } + $singleQuoted = "'" . addcslashes($str, "\\'") . "'"; + $doubleQuoted = '"' . addcslashes($str, '\\"') . '"'; + + return (strlen($singleQuoted) < strlen($doubleQuoted)) ? $singleQuoted : $doubleQuoted; + } + + /** + * {@inheritdoc} + */ + public function generate_quote($text, array $attributes = array()) + { + $text = trim($text); + $quote = '[quote'; + if (isset($attributes['author'])) + { + // Add the author as the BBCode's default attribute + $quote .= '=' . $this->format_attribute_value($attributes['author']); + unset($attributes['author']); + } + + if (isset($attributes['user_id']) && $attributes['user_id'] == ANONYMOUS) + { + unset($attributes['user_id']); + } + + ksort($attributes); + foreach ($attributes as $name => $value) + { + $quote .= ' ' . $name . '=' . $this->format_attribute_value($value); + } + $quote .= ']'; + $newline = (strlen($quote . $text . '[/quote]') > 80 || strpos($text, "\n") !== false) ? "\n" : ''; + $quote .= $newline . $text . $newline . '[/quote]'; + + return $quote; + } + + /** + * Get a list of quote authors, limited to the outermost quotes + * + * @param string $xml Parsed text + * @return string[] List of authors + */ + public function get_outermost_quote_authors($xml) + { + $authors = array(); + if (strpos($xml, 'loadXML($xml); + $xpath = new \DOMXPath($dom); + foreach ($xpath->query('//QUOTE[not(ancestor::QUOTE)]/@author') as $author) + { + $authors[] = $author->textContent; + } + + return $authors; + } + + /** + * Remove given BBCode and its content, at given nesting depth + * + * @param string $xml Parsed text + * @param string $bbcode_name BBCode's name + * @param integer $depth Minimum nesting depth (number of parents of the same name) + * @return string Parsed text + */ + public function remove_bbcode($xml, $bbcode_name, $depth = 0) + { + return \s9e\TextFormatter\Utils::removeTag($xml, strtoupper($bbcode_name), $depth); + } + + /** + * Return a parsed text to its original form + * + * @param string $xml Parsed text + * @return string Original plain text + */ + public function unparse($xml) + { + return \s9e\TextFormatter\Unparser::unparse($xml); + } + + /** + * {@inheritdoc} + */ + public function is_empty($text) + { + if ($text === null || $text === '') + { + return true; + } + + return trim($this->unparse($text)) === ''; + } +} diff --git a/phpbb/textformatter/utils_interface.php b/phpbb/textformatter/utils_interface.php new file mode 100644 index 0000000..4b73929 --- /dev/null +++ b/phpbb/textformatter/utils_interface.php @@ -0,0 +1,79 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textformatter; + +/** +* Used to manipulate a parsed text +*/ +interface utils_interface +{ + /** + * Replace BBCodes and other formatting elements with whitespace + * + * NOTE: preserves smilies as text + * + * @param string $text Parsed text + * @return string Plain text + */ + public function clean_formatting($text); + + /** + * Create a quote block for given text + * + * Possible attributes: + * - author: author's name (usually a username) + * - post_id: post_id of the post being quoted + * - user_id: user_id of the user being quoted + * - time: timestamp of the original message + * + * @param string $text Quote's text + * @param array $attributes Quote's attributes + * @return string Quote block to be used in a new post/text + */ + public function generate_quote($text, array $attributes = array()); + + /** + * Get a list of quote authors, limited to the outermost quotes + * + * @param string $text Parsed text + * @return string[] List of authors + */ + public function get_outermost_quote_authors($text); + + /** + * Remove given BBCode and its content, at given nesting depth + * + * @param string $text Parsed text + * @param string $bbcode_name BBCode's name + * @param integer $depth Minimum nesting depth (number of parents of the same name) + * @return string Parsed text + */ + public function remove_bbcode($text, $bbcode_name, $depth = 0); + + /** + * Return a parsed text to its original form + * + * @param string $text Parsed text + * @return string Original plain text + */ + public function unparse($text); + + /** + * Return whether or not a parsed text represent an empty text. + * + * @param string $text Parsed text + * @return bool Tue if the original text is empty + */ + public function is_empty($text); +} diff --git a/phpbb/textreparser/base.php b/phpbb/textreparser/base.php new file mode 100644 index 0000000..2ee6ea2 --- /dev/null +++ b/phpbb/textreparser/base.php @@ -0,0 +1,269 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\textreparser; + +abstract class base implements reparser_interface +{ + /** + * @var string The reparser name + */ + protected $name; + + /** + * @var bool Whether to save changes to the database + */ + protected $save_changes = true; + + /** + * {@inheritdoc} + */ + abstract public function get_max_id(); + + /** + * Return all records in given range + * + * @param integer $min_id Lower bound + * @param integer $max_id Upper bound + * @return array Array of records + */ + abstract protected function get_records_by_range($min_id, $max_id); + + /** + * {@inheritdoc} + */ + abstract protected function save_record(array $record); + + /** + * Add fields to given record, if applicable + * + * The enable_* fields are not always saved to the database. Sometimes we need to guess their + * original value based on the text content or possibly other fields + * + * @param array $record Original record + * @return array Complete record + */ + protected function add_missing_fields(array $record) + { + if (!isset($record['enable_bbcode'], $record['enable_smilies'], $record['enable_magic_url'])) + { + if (isset($record['options'])) + { + $record += array( + 'enable_bbcode' => (bool) ($record['options'] & OPTION_FLAG_BBCODE), + 'enable_smilies' => (bool) ($record['options'] & OPTION_FLAG_SMILIES), + 'enable_magic_url' => (bool) ($record['options'] & OPTION_FLAG_LINKS), + ); + } + else + { + $record += array( + 'enable_bbcode' => $this->guess_bbcodes($record), + 'enable_smilies' => $this->guess_smilies($record), + 'enable_magic_url' => $this->guess_magic_url($record), + ); + } + } + + // Those BBCodes are disabled based on context and user permissions and that value is never + // stored in the database. Here we test whether they were used in the original text. + $bbcodes = array('flash', 'img', 'quote', 'url'); + foreach ($bbcodes as $bbcode) + { + $field_name = 'enable_' . $bbcode . '_bbcode'; + $record[$field_name] = $this->guess_bbcode($record, $bbcode); + } + + // Magic URLs are tied to the URL BBCode, that's why if magic URLs are enabled we make sure + // that the URL BBCode is also enabled + if ($record['enable_magic_url']) + { + $record['enable_url_bbcode'] = true; + } + + return $record; + } + + /** + * Returns the name of the reparser + * + * @return string Name of reparser + */ + public function get_name() + { + return $this->name; + } + + /** + * Sets the name of the reparser + * + * @param string $name The reparser name + */ + public function set_name($name) + { + $this->name = $name; + } + + /** + * Disable saving changes to the database + */ + public function disable_save() + { + $this->save_changes = false; + } + + /** + * Enable saving changes to the database + */ + public function enable_save() + { + $this->save_changes = true; + } + + /** + * Guess whether given BBCode is in use in given record + * + * @param array $record + * @param string $bbcode + * @return bool + */ + protected function guess_bbcode(array $record, $bbcode) + { + if (!empty($record['bbcode_uid'])) + { + // Look for the closing tag, e.g. [/url] + $match = '[/' . $bbcode . ':' . $record['bbcode_uid']; + if (strpos($record['text'], $match) !== false) + { + return true; + } + } + + if (substr($record['text'], 0, 2) === '[/url] + $match = '[/' . $bbcode . ']'; + if (stripos($record['text'], $match) !== false) + { + return true; + } + } + + return false; + } + + /** + * Guess whether any BBCode is in use in given record + * + * @param array $record + * @return bool + */ + protected function guess_bbcodes(array $record) + { + if (!empty($record['bbcode_uid'])) + { + // Test whether the bbcode_uid is in use + $match = ':' . $record['bbcode_uid']; + if (strpos($record['text'], $match) !== false) + { + return true; + } + } + + if (substr($record['text'], 0, 2) === '\\[/\\w+\\])', $match); + } + + return false; + } + + /** + * Guess whether magic URLs are in use in given record + * + * @param array $record + * @return bool + */ + protected function guess_magic_url(array $record) + { + // Look for or for a URL tag that's not immediately followed by + return (strpos($record['text'], '') !== false || preg_match('(]++>(?!))', $record['text'])); + } + + /** + * Guess whether smilies are in use in given record + * + * @param array $record + * @return bool + */ + protected function guess_smilies(array $record) + { + return (strpos($record['text'], ' + +

{L_FAQ_TITLE}

+ +
+ + + +
+
+ +
+

{faq_block.BLOCK_TITLE}

+ +
+
{faq_block.faq_row.FAQ_QUESTION}
+
{faq_block.faq_row.FAQ_ANSWER}
+
+ + {L_BACK_TO_TOP} + +
+ +
+ +
+
+ +
+ + diff --git a/styles/Milk_v2/template/forum_fn.js b/styles/Milk_v2/template/forum_fn.js new file mode 100644 index 0000000..597ddc2 --- /dev/null +++ b/styles/Milk_v2/template/forum_fn.js @@ -0,0 +1,1105 @@ +/* global phpbb */ + +/** +* phpBB3 forum functions +*/ + +/** +* Find a member +*/ +function find_username(url) { + 'use strict'; + + popup(url, 760, 570, '_usersearch'); + return false; +} + +/** +* Window popup +*/ +function popup(url, width, height, name) { + 'use strict'; + + if (!name) { + name = '_popup'; + } + + window.open(url.replace(/&/g, '&'), name, 'height=' + height + ',resizable=yes,scrollbars=yes, width=' + width); + return false; +} + +/** +* Jump to page +*/ +function pageJump(item) { + 'use strict'; + + var page = parseInt(item.val(), 10), + perPage = item.attr('data-per-page'), + baseUrl = item.attr('data-base-url'), + startName = item.attr('data-start-name'); + + if (page !== null && !isNaN(page) && page === Math.floor(page) && page > 0) { + if (baseUrl.indexOf('?') === -1) { + document.location.href = baseUrl + '?' + startName + '=' + ((page - 1) * perPage); + } else { + document.location.href = baseUrl.replace(/&/g, '&') + '&' + startName + '=' + ((page - 1) * perPage); + } + } +} + +/** +* Mark/unmark checklist +* id = ID of parent container, name = name prefix, state = state [true/false] +*/ +function marklist(id, name, state) { + 'use strict'; + + jQuery('#' + id + ' input[type=checkbox][name]').each(function() { + var $this = jQuery(this); + if ($this.attr('name').substr(0, name.length) === name && !$this.prop('disabled')) { + $this.prop('checked', state); + } + }); +} + +/** +* Resize viewable area for attached image or topic review panel (possibly others to come) +* e = element +*/ +function viewableArea(e, itself) { + 'use strict'; + + if (!e) { + return; + } + + if (!itself) { + e = e.parentNode; + } + + if (!e.vaHeight) { + // Store viewable area height before changing style to auto + e.vaHeight = e.offsetHeight; + e.vaMaxHeight = e.style.maxHeight; + e.style.height = 'auto'; + e.style.maxHeight = 'none'; + e.style.overflow = 'visible'; + } else { + // Restore viewable area height to the default + e.style.height = e.vaHeight + 'px'; + e.style.overflow = 'auto'; + e.style.maxHeight = e.vaMaxHeight; + e.vaHeight = false; + } +} + +/** +* Alternate display of subPanels +*/ +jQuery(function($) { + 'use strict'; + + $('.sub-panels').each(function() { + + var $childNodes = $('a[data-subpanel]', this), + panels = $childNodes.map(function () { + return this.getAttribute('data-subpanel'); + }), + showPanel = this.getAttribute('data-show-panel'); + + if (panels.length) { + activateSubPanel(showPanel, panels); + $childNodes.click(function () { + activateSubPanel(this.getAttribute('data-subpanel'), panels); + return false; + }); + } + }); +}); + +/** +* Activate specific subPanel +*/ +function activateSubPanel(p, panels) { + 'use strict'; + + var i, showPanel; + + if (typeof p === 'string') { + showPanel = p; + } + $('input[name="show_panel"]').val(showPanel); + + if (typeof panels === 'undefined') { + panels = jQuery('.sub-panels a[data-subpanel]').map(function() { + return this.getAttribute('data-subpanel'); + }); + } + + for (i = 0; i < panels.length; i++) { + jQuery('#' + panels[i]).css('display', panels[i] === showPanel ? 'block' : 'none'); + jQuery('#' + panels[i] + '-tab').toggleClass('activetab', panels[i] === showPanel); + } +} + +function selectCode(a) { + 'use strict'; + + // Get ID of code block + var e = a.parentNode.parentNode.getElementsByTagName('CODE')[0]; + var s, r; + + // Not IE and IE9+ + if (window.getSelection) { + s = window.getSelection(); + // Safari and Chrome + if (s.setBaseAndExtent) { + var l = (e.innerText.length > 1) ? e.innerText.length - 1 : 1; + try { + s.setBaseAndExtent(e, 0, e, l); + } catch (error) { + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + } + // Firefox and Opera + else { + // workaround for bug # 42885 + if (window.opera && e.innerHTML.substring(e.innerHTML.length - 4) === '
') { + e.innerHTML = e.innerHTML + ' '; + } + + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + } + // Some older browsers + else if (document.getSelection) { + s = document.getSelection(); + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + // IE + else if (document.selection) { + r = document.body.createTextRange(); + r.moveToElementText(e); + r.select(); + } +} + +/** +* Play quicktime file by determining it's width/height +* from the displayed rectangle area +*/ +function play_qt_file(obj) { + 'use strict'; + + var rectangle = obj.GetRectangle(); + var width, height; + + if (rectangle) { + rectangle = rectangle.split(','); + var x1 = parseInt(rectangle[0], 10); + var x2 = parseInt(rectangle[2], 10); + var y1 = parseInt(rectangle[1], 10); + var y2 = parseInt(rectangle[3], 10); + + width = (x1 < 0) ? (x1 * -1) + x2 : x2 - x1; + height = (y1 < 0) ? (y1 * -1) + y2 : y2 - y1; + } else { + width = 200; + height = 0; + } + + obj.width = width; + obj.height = height + 16; + + obj.SetControllerVisible(true); + obj.Play(); +} + +/** +* Play quicktime file by determining it's width/height +* from the displayed rectangle area +*/ +function play_qt_file(obj) { + 'use strict'; + + var rectangle = obj.GetRectangle(); + var width, height; + + if (rectangle) { + rectangle = rectangle.split(','); + var x1 = parseInt(rectangle[0], 10); + var x2 = parseInt(rectangle[2], 10); + var y1 = parseInt(rectangle[1], 10); + var y2 = parseInt(rectangle[3], 10); + + width = (x1 < 0) ? (x1 * -1) + x2 : x2 - x1; + height = (y1 < 0) ? (y1 * -1) + y2 : y2 - y1; + } else { + width = 200; + height = 0; + } + + obj.width = width; + obj.height = height + 16; + + obj.SetControllerVisible(true); + obj.Play(); +} + +var inAutocomplete = false; +var lastKeyEntered = ''; + +/** +* Check event key +*/ +function phpbbCheckKey(event) { + 'use strict'; + + // Keycode is array down or up? + if (event.keyCode && (event.keyCode === 40 || event.keyCode === 38)) { + inAutocomplete = true; + } + + // Make sure we are not within an "autocompletion" field + if (inAutocomplete) { + // If return pressed and key changed we reset the autocompletion + if (!lastKeyEntered || lastKeyEntered === event.which) { + inAutocomplete = false; + return true; + } + } + + // Keycode is not return, then return. ;) + if (event.which !== 13) { + lastKeyEntered = event.which; + return true; + } + + return false; +} + +/** +* Apply onkeypress event for forcing default submit button on ENTER key press +*/ +jQuery(function($) { + 'use strict'; + + $('form input[type=text], form input[type=password]').on('keypress', function (e) { + var defaultButton = $(this).parents('form').find('input[type=submit].default-submit-action'); + + if (!defaultButton || defaultButton.length <= 0) { + return true; + } + + if (phpbbCheckKey(e)) { + return true; + } + + if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) { + defaultButton.click(); + return false; + } + + return true; + }); +}); + +/** +* Functions for user search popup +*/ +function insertUser(formId, value) { + 'use strict'; + + var $form = jQuery(formId), + formName = $form.attr('data-form-name'), + fieldName = $form.attr('data-field-name'), + item = opener.document.forms[formName][fieldName]; + + if (item.value.length && item.type === 'textarea') { + value = item.value + '\n' + value; + } + + item.value = value; +} + +function insert_marked_users(formId, users) { + 'use strict'; + + $(users).filter(':checked').each(function() { + insertUser(formId, this.value); + }); + + window.close(); +} + +function insert_single_user(formId, user) { + 'use strict'; + + insertUser(formId, user); + window.close(); +} + +/** +* Parse document block +*/ +function parseDocument($container) { + 'use strict'; + + var test = document.createElement('div'), + oldBrowser = (typeof test.style.borderRadius === 'undefined'), + $body = $('body'); + + /** + * Reset avatar dimensions when changing URL or EMAIL + */ + $container.find('input[data-reset-on-edit]').on('keyup', function() { + $(this.getAttribute('data-reset-on-edit')).val(''); + }); + + /** + * Pagination + */ + $container.find('.pagination .page-jump-form :button').click(function() { + var $input = $(this).siblings('input.inputbox'); + pageJump($input); + }); + + $container.find('.pagination .page-jump-form input.inputbox').on('keypress', function(event) { + if (event.which === 13 || event.keyCode === 13) { + event.preventDefault(); + pageJump($(this)); + } + }); + + $container.find('.pagination .dropdown-trigger').click(function() { + var $dropdownContainer = $(this).parent(); + // Wait a little bit to make sure the dropdown has activated + setTimeout(function() { + if ($dropdownContainer.hasClass('dropdown-visible')) { + $dropdownContainer.find('input.inputbox').focus(); + } + }, 100); + }); + + /** + * Adjust HTML code for IE8 and older versions + */ + // if (oldBrowser) { + // // Fix .linklist.bulletin lists + // $container + // .find('ul.linklist.bulletin > li') + // .filter(':first-child, .rightside:last-child') + // .addClass('no-bulletin'); + // } + + /** + * Resize navigation (breadcrumbs) block to keep all links on same line + */ + $container.find('.navlinks').each(function() { + var $this = $(this), + $left = $this.children().not('.rightside'), + $right = $this.children('.rightside'); + + if ($left.length !== 1 || !$right.length) { + return; + } + + function resize() { + var width = 0, + diff = $left.outerWidth(true) - $left.width(), + minWidth = Math.max($this.width() / 3, 240), + maxWidth; + + $right.each(function() { + var $this = $(this); + if ($this.is(':visible')) { + width += $this.outerWidth(true); + } + }); + + maxWidth = $this.width() - width - diff; + $left.css('max-width', Math.floor(Math.max(maxWidth, minWidth)) + 'px'); + } + + resize(); + $(window).resize(resize); + }); + + /** + * Makes breadcrumbs responsive + */ + $container.find('.breadcrumbs:not([data-skip-responsive])').each(function() { + var $this = $(this), + $links = $this.find('.crumb'), + length = $links.length, + classes = ['wrapped-max', 'wrapped-wide', 'wrapped-medium', 'wrapped-small', 'wrapped-tiny'], + classesLength = classes.length, + maxHeight = 0, + lastWidth = false, + wrapped = false; + + // Set tooltips + $this.find('a').each(function() { + var $link = $(this); + $link.attr('title', $link.text()); + }); + + // Function that checks breadcrumbs + function check() { + var height = $this.height(), + width; + + // Test max-width set in code for .navlinks above + width = parseInt($this.css('max-width'), 10); + if (!width) { + width = $body.width(); + } + + maxHeight = parseInt($this.css('line-height'), 10); + $links.each(function() { + if ($(this).height() > 0) { + maxHeight = Math.max(maxHeight, $(this).outerHeight(true)); + } + }); + + if (height <= maxHeight) { + if (!wrapped || lastWidth === false || lastWidth >= width) { + return; + } + } + lastWidth = width; + + if (wrapped) { + $this.removeClass('wrapped').find('.crumb.wrapped').removeClass('wrapped ' + classes.join(' ')); + if ($this.height() <= maxHeight) { + return; + } + } + + wrapped = true; + $this.addClass('wrapped'); + if ($this.height() <= maxHeight) { + return; + } + + for (var i = 0; i < classesLength; i++) { + for (var j = length - 1; j >= 0; j--) { + $links.eq(j).addClass('wrapped ' + classes[i]); + if ($this.height() <= maxHeight) { + return; + } + } + } + } + + // Run function and set event + check(); + $(window).resize(check); + }); + + /** + * Responsive link lists + */ + var selector = '.linklist:not(.navlinks, [data-skip-responsive]),' + + '.postbody .post-buttons:not([data-skip-responsive])'; + $container.find(selector).each(function() { + var $this = $(this), + filterSkip = '.breadcrumbs, [data-skip-responsive]', + filterLast = '.edit-icon, .quote-icon, [data-last-responsive]', + $linksAll = $this.children(), + $linksNotSkip = $linksAll.not(filterSkip), // All items that can potentially be hidden + $linksFirst = $linksNotSkip.not(filterLast), // The items that will be hidden first + $linksLast = $linksNotSkip.filter(filterLast), // The items that will be hidden last + persistent = $this.attr('id') === 'nav-main', // Does this list already have a menu (such as quick-links)? + html = '', + slack = 3; // Vertical slack space (in pixels). Determines how sensitive the script is in determining whether a line-break has occured. + + // Add a hidden drop-down menu to each links list (except those that already have one) + if (!persistent) { + if ($linksNotSkip.is('.rightside')) { + $linksNotSkip.filter('.rightside:first').before(html); + $this.children('.responsive-menu').addClass('rightside'); + } else { + $this.append(html); + } + } + + // Set some object references and initial states + var $menu = $this.children('.responsive-menu'), + $menuContents = $menu.find('.dropdown-contents'), + persistentContent = $menuContents.find('li:not(.separator)').length, + lastWidth = false, + compact = false, + responsive1 = false, + responsive2 = false, + copied1 = false, + copied2 = false, + maxHeight = 0; + + // Find the tallest element in the list (we assume that all elements are roughly the same height) + $linksAll.each(function() { + if (!$(this).height()) { + return; + } + maxHeight = Math.max(maxHeight, $(this).outerHeight(true)); + }); + if (maxHeight < 1) { + return; // Shouldn't be possible, but just in case, abort + } else { + maxHeight = maxHeight + slack; + } + + function check() { + var width = $body.width(); + // We can't make it any smaller than this, so just skip + if (responsive2 && compact && (width <= lastWidth)) { + return; + } + lastWidth = width; + + // Reset responsive and compact layout + if (responsive1 || responsive2) { + $linksNotSkip.removeClass('hidden'); + $menuContents.children('.clone').addClass('hidden'); + responsive1 = responsive2 = false; + } + if (compact) { + $this.removeClass('compact'); + compact = false; + } + + // Unhide the quick-links menu if it has "persistent" content + if (persistent && persistentContent) { + $menu.removeClass('hidden'); + } else { + $menu.addClass('hidden'); + } + + // Nothing to resize if block's height is not bigger than tallest element's height + if ($this.height() <= maxHeight) { + return; + } + + // STEP 1: Compact + if (!compact) { + $this.addClass('compact'); + compact = true; + } + if ($this.height() <= maxHeight) { + return; + } + + // STEP 2: First responsive set - compact + if (compact) { + $this.removeClass('compact'); + compact = false; + } + // Copy the list items to the dropdown + if (!copied1) { + var $clones1 = $linksFirst.clone(); + $menuContents.prepend($clones1.addClass('clone clone-first').removeClass('leftside rightside')); + + if ($this.hasClass('post-buttons')) { + $('.button', $menuContents).removeClass('button'); + $('.sr-only', $menuContents).removeClass('sr-only'); + $('.js-responsive-menu-link').addClass('button').addClass('button-icon-only'); + $('.js-responsive-menu-link .icon').removeClass('fa-bars').addClass('fa-ellipsis-h'); + } + copied1 = true; + } + if (!responsive1) { + $linksFirst.addClass('hidden'); + responsive1 = true; + $menuContents.children('.clone-first').removeClass('hidden'); + $menu.removeClass('hidden'); + } + if ($this.height() <= maxHeight) { + return; + } + + // STEP 3: First responsive set + compact + if (!compact) { + $this.addClass('compact'); + compact = true; + } + if ($this.height() <= maxHeight) { + return; + } + + // STEP 4: Last responsive set - compact + if (!$linksLast.length) { + return; // No other links to hide, can't do more + } + if (compact) { + $this.removeClass('compact'); + compact = false; + } + // Copy the list items to the dropdown + if (!copied2) { + var $clones2 = $linksLast.clone(); + $menuContents.prepend($clones2.addClass('clone clone-last').removeClass('leftside rightside')); + copied2 = true; + } + if (!responsive2) { + $linksLast.addClass('hidden'); + responsive2 = true; + $menuContents.children('.clone-last').removeClass('hidden'); + } + if ($this.height() <= maxHeight) { + return; + } + + // STEP 5: Last responsive set + compact + if (!compact) { + $this.addClass('compact'); + compact = true; + } + } + + if (!persistent) { + phpbb.registerDropdown($menu.find('a.js-responsive-menu-link'), $menu.find('.dropdown'), false); + } + + // If there are any images in the links list, run the check again after they have loaded + $linksAll.find('img').each(function() { + $(this).on('load', function() { + check(); + }); + }); + + check(); + $(window).resize(check); + }); + + /** + * Do not run functions below for old browsers + */ + if (oldBrowser) { + return; + } + + /** + * Adjust topiclist lists with check boxes + */ + $container.find('ul.topiclist dd.mark').siblings('dt').children('.list-inner').addClass('with-mark'); + + /** + * Appends contents of all extra columns to first column in + * .topiclist lists for mobile devices. Copies contents as is. + * + * To add that functionality to .topiclist list simply add + * responsive-show-all to list of classes + */ + $container.find('.topiclist.responsive-show-all > li > dl').each(function() { + var $this = $(this), + $block = $this.find('dt .responsive-show:last-child'), + first = true; + + // Create block that is visible only on mobile devices + if (!$block.length) { + $this.find('dt > .list-inner').append(' + + + + +
+
+ + +
+ + +
+ +
    + + + + + +
  • + + + + +
    +
    + +
    + + + + + {forumrow.FORUM_IMAGE} + + {forumrow.FORUM_NAME} +
    {forumrow.FORUM_DESC} + +
    + + {L_REDIRECTS}{L_COLON} {forumrow.CLICKS} + + {L_TOPICS}{L_COLON} {forumrow.TOPICS}   {L_POSTS}{L_COLON} {forumrow.POSTS} +
    + + + + + +
    {forumrow.L_MODERATOR_STR}{L_COLON} {forumrow.MODERATORS} + + + +
    +
    {forumrow.L_SUBFORUM_STR}{L_COLON} + + + + + +
    +
    + + +
    {L_REDIRECTS}{L_COLON} {forumrow.CLICKS}
    + + +
    {forumrow.POSTS}{L_POSTS}
    + +
    {forumrow.TOPICS} {L_TOPICS}
    +
    {forumrow.POSTS} {L_POSTS}
    + +
    + + + + {L_TOPICS_UNAPPROVED} + + + + {L_POSTS_UNAPPROVED_FORUM} + + + + {L_LAST_POST} + + + {forumrow.LAST_POST_SUBJECT_TRUNCATED}
    + + {L_POST_BY_AUTHOR} {forumrow.LAST_POSTER_FULL} + + + {L_VIEW_LATEST_POST} + + +
    {forumrow.LAST_POST_TIME} + + {% if forumrow.U_UNAPPROVED_TOPICS %} + {{ lang('TOPIC_UNAPPROVED_FORUM', forumrow.TOPICS) }} + {% else %} + {{ lang('NO_POSTS') }} + {% endif %} + +
    +
    + +
     
    + + +
    + +
  • + + + + +
+ +
+
+ + + + +
+
+ {L_NO_FORUMS} +
+
+ + +
\ No newline at end of file diff --git a/styles/Milk_v2/template/forumlist_grid.html b/styles/Milk_v2/template/forumlist_grid.html new file mode 100644 index 0000000..4724d0a --- /dev/null +++ b/styles/Milk_v2/template/forumlist_grid.html @@ -0,0 +1,86 @@ +
+ + + + + + +
+
+ + + + +
+
+ + +
+ + +
+ + + +
+
+ + + + +
+
+ {L_NO_FORUMS} +
+
+ + +
diff --git a/styles/Milk_v2/template/index.htm b/styles/Milk_v2/template/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/template/index_body.html b/styles/Milk_v2/template/index_body.html new file mode 100644 index 0000000..0ddd729 --- /dev/null +++ b/styles/Milk_v2/template/index_body.html @@ -0,0 +1,119 @@ + + + + + + +
+ + + + + + + +
+ + {CURRENT_TIME} + + +
{L_MARK_FORUMS_READ} + +
+ + + +
+
+
    +
  • +
    +
     {L_INFORMATION}
    +
    +
  • +
+ +
+ + +
+ +
    + + + + + + + + + + +
  • +
    +

    {L_WHO_IS_ONLINE}

    {L_WHO_IS_ONLINE}

    +

    + + {TOTAL_USERS_ONLINE} ({L_ONLINE_EXPLAIN})
    {RECORD_USERS}
    + +
    {LOGGED_IN_USER_LIST} + +
    {L_LEGEND}{L_COLON} {LEGEND} + +

    +
    +
  • + + +
  • +
    +

    {L_STATISTICS}

    +

    + + {TOTAL_POSTS} • {TOTAL_TOPICS} • {TOTAL_USERS} • {NEWEST_USER} + +

    +
    +
  • + + + +
  • +
    +

    {L_BIRTHDAYS}

    +

    + + {L_CONGRATULATIONS}{L_COLON} {birthdays.USERNAME} ({birthdays.AGE}), {L_NO_BIRTHDAYS} + +

    +
    +
  • + + + + +
+
+
+ +
+ + + diff --git a/styles/Milk_v2/template/jquery.collapse.js b/styles/Milk_v2/template/jquery.collapse.js new file mode 100644 index 0000000..69ce09a --- /dev/null +++ b/styles/Milk_v2/template/jquery.collapse.js @@ -0,0 +1,176 @@ +/* + * Collapse plugin for jQuery + * -- + * source: http://github.com/danielstocks/jQuery-Collapse/ + * site: http://webcloud.se/jQuery-Collapse + * + * @author Daniel Stocks (http://webcloud.se) + * Copyright 2013, Daniel Stocks + * Released under the MIT, BSD, and GPL Licenses. + */ + +(function($) { + + // Constructor + function Collapse (el, options) { + options = options || {}; + var _this = this, + query = options.query || "> :even"; + + $.extend(_this, { + $el: el, + options : options, + sections: [], + isAccordion : options.accordion || false, + db : options.persist ? jQueryCollapseStorage(el.get(0).id) : false + }); + + // Figure out what sections are open if storage is used + _this.states = _this.db ? _this.db.read() : []; + + // For every pair of elements in given + // element, create a section + _this.$el.find(query).each(function() { + new jQueryCollapseSection($(this), _this); + }); + + // Capute ALL the clicks! + (function(scope) { + _this.$el.on("click", "[data-collapse-summary] " + (scope.options.clickQuery || ""), + $.proxy(_this.handleClick, scope)); + + _this.$el.bind("toggle close open", + $.proxy(_this.handleEvent, scope)); + + }(_this)); + } + + Collapse.prototype = { + handleClick: function(e, state) { + e.preventDefault(); + var state = state || "toggle" + var sections = this.sections, + l = sections.length; + while(l--) { + if($.contains(sections[l].$summary[0], e.target)) { + sections[l][state](); + break; + } + } + }, + handleEvent: function(e) { + if(e.target == this.$el.get(0)) return this[e.type](); + this.handleClick(e, e.type); + }, + open: function(eq) { + if(isFinite(eq)) return this.sections[eq].open(); + $.each(this.sections, function(i, section) { + section.open(); + }) + }, + close: function(eq) { + if(isFinite(eq)) return this.sections[eq].close(); + $.each(this.sections, function(i, section) { + section.close(); + }) + }, + toggle: function(eq) { + if(isFinite(eq)) return this.sections[eq].toggle(); + $.each(this.sections, function(i, section) { + section.toggle(); + }) + } + }; + + // Section constructor + function Section($el, parent) { + + if(!parent.options.clickQuery) $el.wrapInner(''); + + $.extend(this, { + isOpen : false, + $summary : $el.attr("data-collapse-summary",""), + $details : $el.next(), + options: parent.options, + parent: parent + }); + parent.sections.push(this); + + // Check current state of section + var state = parent.states[this._index()]; + + if(state === 0) { + this.close(true) + } + else if(this.$summary.is(".open") || state === 1) { + this.open(true); + } else { + this.close(true) + } + } + + Section.prototype = { + toggle : function() { + this.isOpen ? this.close() : this.open(); + }, + close: function(bypass) { + this._changeState("close", bypass); + }, + open: function(bypass) { + var _this = this; + if(_this.options.accordion && !bypass) { + $.each(_this.parent.sections, function(i, section) { + section.close() + }); + } + _this._changeState("open", bypass); + }, + _index: function() { + return $.inArray(this, this.parent.sections); + }, + _changeState: function(state, bypass) { + + var _this = this; + _this.isOpen = state == "open"; + if($.isFunction(_this.options[state]) && !bypass) { + _this.options[state].apply(_this.$details); + } else { + _this.$details[_this.isOpen ? "show" : "hide"](); + } + + _this.$summary.toggleClass("open", state != "close") + _this.$details.attr("aria-hidden", state == "close"); + _this.$summary.attr("aria-expanded", state == "open"); + _this.$summary.trigger(state == "open" ? "opened" : "closed", _this); + if(_this.parent.db) { + _this.parent.db.write(_this._index(), _this.isOpen); + } + } + }; + + // Expose in jQuery API + $.fn.extend({ + collapse: function(options, scan) { + var nodes = (scan) ? $("body").find("[data-collapse]") : $(this); + return nodes.each(function() { + var settings = (scan) ? {} : options, + values = $(this).attr("data-collapse") || ""; + $.each(values.split(" "), function(i,v) { + if(v) settings[v] = true; + }); + new Collapse($(this), settings); + }); + } + }); + + //jQuery DOM Ready + $(function() { + $.fn.collapse(false, true); + }); + + // Expose constructor to + // global namespace + jQueryCollapse = Collapse; + jQueryCollapseSection = Section; + +})(window.jQuery); \ No newline at end of file diff --git a/styles/Milk_v2/template/jquery.collapse_storage.js b/styles/Milk_v2/template/jquery.collapse_storage.js new file mode 100644 index 0000000..93cb450 --- /dev/null +++ b/styles/Milk_v2/template/jquery.collapse_storage.js @@ -0,0 +1,56 @@ +/* + * Storage for jQuery Collapse + * -- + * source: http://github.com/danielstocks/jQuery-Collapse/ + * site: http://webcloud.se/jQuery-Collapse + * + * @author Daniel Stocks (http://webcloud.se) + * Copyright 2013, Daniel Stocks + * Released under the MIT, BSD, and GPL Licenses. + */ + +(function($) { + + var STORAGE_KEY = "jQuery-Collapse"; + + function Storage(id) { + var DB; + try { + DB = window.localStorage || $.fn.collapse.cookieStorage; + } catch(e) { + DB = false; + } + return DB ? new _Storage(id, DB) : false; + } + function _Storage(id, DB) { + this.id = id; + this.db = DB; + this.data = []; + } + _Storage.prototype = { + write: function(position, state) { + var _this = this; + _this.data[position] = state ? 1 : 0; + // Pad out data array with zero values + $.each(_this.data, function(i) { + if(typeof _this.data[i] == 'undefined') { + _this.data[i] = 0; + } + }); + var obj = this._getDataObject(); + obj[this.id] = this.data; + this.db.setItem(STORAGE_KEY, JSON.stringify(obj)); + }, + read: function() { + var obj = this._getDataObject(); + return obj[this.id] || []; + }, + _getDataObject: function() { + var string = this.db.getItem(STORAGE_KEY); + return string ? JSON.parse(string) : {}; + } + }; + + jQueryCollapseStorage = Storage; + +})(jQuery); diff --git a/styles/Milk_v2/template/js.cookie.js b/styles/Milk_v2/template/js.cookie.js new file mode 100644 index 0000000..18802c1 --- /dev/null +++ b/styles/Milk_v2/template/js.cookie.js @@ -0,0 +1,162 @@ +/*! + * JavaScript Cookie v2.2.0 + * https://github.com/js-cookie/js-cookie + * + * Copyright 2006, 2015 Klaus Hartl & Fagner Brack + * Released under the MIT license + */ +;(function (factory) { + var registeredInModuleLoader; + if (typeof define === 'function' && define.amd) { + define(factory); + registeredInModuleLoader = true; + } + if (typeof exports === 'object') { + module.exports = factory(); + registeredInModuleLoader = true; + } + if (!registeredInModuleLoader) { + var OldCookies = window.Cookies; + var api = window.Cookies = factory(); + api.noConflict = function () { + window.Cookies = OldCookies; + return api; + }; + } +}(function () { + function extend () { + var i = 0; + var result = {}; + for (; i < arguments.length; i++) { + var attributes = arguments[ i ]; + for (var key in attributes) { + result[key] = attributes[key]; + } + } + return result; + } + + function init (converter) { + function api (key, value, attributes) { + if (typeof document === 'undefined') { + return; + } + + // Write + + if (arguments.length > 1) { + attributes = extend({ + path: '/' + }, api.defaults, attributes); + + if (typeof attributes.expires === 'number') { + attributes.expires = new Date(new Date() * 1 + attributes.expires * 864e+5); + } + + // We're using "expires" because "max-age" is not supported by IE + attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; + + try { + var result = JSON.stringify(value); + if (/^[\{\[]/.test(result)) { + value = result; + } + } catch (e) {} + + value = converter.write ? + converter.write(value, key) : + encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + + key = encodeURIComponent(String(key)) + .replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent) + .replace(/[\(\)]/g, escape); + + var stringifiedAttributes = ''; + for (var attributeName in attributes) { + if (!attributes[attributeName]) { + continue; + } + stringifiedAttributes += '; ' + attributeName; + if (attributes[attributeName] === true) { + continue; + } + + // Considers RFC 6265 section 5.2: + // ... + // 3. If the remaining unparsed-attributes contains a %x3B (";") + // character: + // Consume the characters of the unparsed-attributes up to, + // not including, the first %x3B (";") character. + // ... + stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]; + } + + return (document.cookie = key + '=' + value + stringifiedAttributes); + } + + // Read + + var jar = {}; + var decode = function (s) { + return s.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent); + }; + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. + var cookies = document.cookie ? document.cookie.split('; ') : []; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var cookie = parts.slice(1).join('='); + + if (!this.json && cookie.charAt(0) === '"') { + cookie = cookie.slice(1, -1); + } + + try { + var name = decode(parts[0]); + cookie = (converter.read || converter)(cookie, name) || + decode(cookie); + + if (this.json) { + try { + cookie = JSON.parse(cookie); + } catch (e) {} + } + + jar[name] = cookie; + + if (key === name) { + break; + } + } catch (e) {} + } + + return key ? jar[key] : jar; + } + + api.set = api; + api.get = function (key) { + return api.call(api, key); + }; + api.getJSON = function () { + return api.apply({ + json: true + }, arguments); + }; + api.remove = function (key, attributes) { + api(key, '', extend(attributes, { + expires: -1 + })); + }; + + api.defaults = {}; + + api.withConverter = init; + + return api; + } + + return init(function () {}); +})); diff --git a/styles/Milk_v2/template/login_body.html b/styles/Milk_v2/template/login_body.html new file mode 100644 index 0000000..7d89d1d --- /dev/null +++ b/styles/Milk_v2/template/login_body.html @@ -0,0 +1,74 @@ + + + + + + + diff --git a/styles/Milk_v2/template/login_forum.html b/styles/Milk_v2/template/login_forum.html new file mode 100644 index 0000000..8628653 --- /dev/null +++ b/styles/Milk_v2/template/login_forum.html @@ -0,0 +1,34 @@ + + + + + + diff --git a/styles/Milk_v2/template/mcp_forum.html b/styles/Milk_v2/template/mcp_forum.html new file mode 100644 index 0000000..afba0a6 --- /dev/null +++ b/styles/Milk_v2/template/mcp_forum.html @@ -0,0 +1,152 @@ + + + + + +

{L_FORUM}{L_COLON} {FORUM_NAME}

+ +
+ +
+
+ +
+ +
+ + +
    +
  • +
    +
    {L_TOPICS}
    +
    {L_REPLIES}
    +
    {L_LAST_POST}
    +
    {L_MARK}
    +
    +
  • +
+
    + + +
  • +
    +
    style="background-image: url({T_ICONS_PATH}{topicrow.TOPIC_ICON_IMG}); background-repeat: no-repeat;"> + +
    + + + [ {L_SELECT_MERGE} ]   + + + + + + + {topicrow.TOPIC_TITLE} + + + + + + + +  [ {L_DELETE_SHADOW_TOPIC} ] +
    + + + + + + + + +
    + + {% EVENT topiclist_row_topic_by_author_before %} + {L_POST_BY_AUTHOR} {topicrow.TOPIC_AUTHOR_FULL} » {topicrow.FIRST_POST_TIME} + {% EVENT topiclist_row_topic_by_author_after %} +
    + +
    +
    +
    {topicrow.REPLIES} {L_REPLIES}
    +
    {L_LAST_POST} {L_POST_BY_AUTHOR} {topicrow.LAST_POST_AUTHOR_FULL}
    {topicrow.LAST_POST_TIME}
    + +
    + checked="checked" />  +
    + +
    +
  • + +
+ +
    +
  • {L_NO_TOPICS}

  • +
+ + +
+ + + +
+ +
+
+ + +
+ + + + + + {S_FORM_TOKEN} +
+ +
+ + diff --git a/styles/Milk_v2/template/mcp_front.html b/styles/Milk_v2/template/mcp_front.html new file mode 100644 index 0000000..4d01d69 --- /dev/null +++ b/styles/Milk_v2/template/mcp_front.html @@ -0,0 +1,193 @@ + + +

{PAGE_TITLE}

+ + + + + +
+ +
+
+ +

{L_LATEST_UNAPPROVED}

+

{L_UNAPPROVED_TOTAL}

+ + +
    +
  • +
    +
    {L_VIEW_DETAILS}
    +
    {L_TOPIC} & {L_FORUM}
    +
    +
  • +
+ + + +
+ {S_FORM_TOKEN} +
+ + +
+ {S_HIDDEN_FIELDS} +   + + +
+ +
+ + + + + +
+
+ +

{L_LATEST_REPORTED}

+

{L_REPORTS_TOTAL}

+ + +
    +
  • +
    +
    {L_VIEW_DETAILS}
    +
    {L_REPORTER} & {L_FORUM}
    +
    +
  • +
+
    + + +
  • +
    +
    +
    + {report.SUBJECT}
    + {L_POSTED} {L_POST_BY_AUTHOR} {report.AUTHOR_FULL} » {report.POST_TIME} +
    +
    +
    + {L_REPORTED} {L_POST_BY_AUTHOR} {report.REPORTER_FULL} {L_REPORTED_ON_DATE}
    + {L_FORUM}{L_COLON} {report.FORUM_NAME}
    +
    +
    +
  • + +
+ + +
+
+ + + + + +
+
+ +

{L_LATEST_REPORTED_PMS}

+

{L_PM_REPORTS_TOTAL}

+ + +
    +
  • +
    +
    {L_VIEW_DETAILS}
    +
    {L_REPORTER}
    +
    +
  • +
+
    + + +
  • +
    +
    +
    + {pm_report.PM_SUBJECT}
    + {L_MESSAGE_BY_AUTHOR} {pm_report.PM_AUTHOR_FULL} » {pm_report.PM_TIME}
    + {L_MESSAGE_TO} {pm_report.RECIPIENTS} +
    +
    +
    + {L_REPORTED} {L_POST_BY_AUTHOR} {pm_report.REPORTER_FULL} {L_REPORTED_ON_DATE} {pm_report.REPORT_TIME} +
    +
    +
  • + +
+ + +
+
+ + + + + +
+
+ +

{L_LATEST_LOGS}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_ACTION}{L_USERNAME}{L_IP}{L_VIEW_TOPIC}{L_VIEW_TOPIC_LOGS}{L_TIME}
{log.ACTION}{log.USERNAME}{log.IP}{L_VIEW_TOPIC} {L_VIEW_TOPIC_LOGS} {log.TIME}
{L_NO_ENTRIES}
+ +
+
+ + + + + diff --git a/styles/Milk_v2/template/mcp_post.html b/styles/Milk_v2/template/mcp_post.html new file mode 100644 index 0000000..317e952 --- /dev/null +++ b/styles/Milk_v2/template/mcp_post.html @@ -0,0 +1,362 @@ + + + + +

{L_PM_REPORT_DETAILS}

+ +

{L_REPORT_DETAILS}

+ + +
+
+ +
+

{L_REPORT_REASON}{L_COLON} {REPORT_REASON_TITLE}

+

{L_REPORTED} {L_POST_BY_AUTHOR} {REPORTER_FULL} « {REPORT_DATE}

+ +

{L_REPORT_CLOSED}

+ +
+ + {REPORT_TEXT} + + {REPORT_REASON_DESCRIPTION} + +
+
+ +
+
+ +
+ +
+ {% EVENT mcp_post_report_buttons_top_before %} + +   + + + {% EVENT mcp_post_report_buttons_top_after %} + + {S_FORM_TOKEN} +
+
+ + +

{L_POST_DETAILS}

+ + +
+
+ +
+

{POST_SUBJECT}

+ + + + +

+ {L_SENT_AT}{L_COLON} {POST_DATE} +
{L_PM_FROM}{L_COLON} {POST_AUTHOR_FULL}
{L_TO}{L_COLON} {to_recipient.NAME_FULL} style="color:{to_recipient.COLOUR};">{to_recipient.NAME}  +
{L_BCC}{L_COLON} {bcc_recipient.NAME_FULL} style="color:{bcc_recipient.COLOUR};">{bcc_recipient.NAME}  +

+ +

{MINI_POST_IMG} {L_POSTED} {L_POST_BY_AUTHOR} {POST_AUTHOR_FULL} » {POST_DATE}

+ + + +
+ +

+   + + + + {S_FORM_TOKEN} +

+
+ +
+ +

+   + + + + {S_FORM_TOKEN} +

+
+ + + +

+ {L_TOPIC_REPORTED} {L_MESSAGE_REPORTED} +

+ + + {% EVENT mcp_post_text_before %} + +
+ {POST_PREVIEW} +
+ + {% EVENT mcp_post_text_after %} + + +
+
{L_ATTACHMENTS}
+ +
{attachment.DISPLAY_ATTACHMENT}
+ +
+ + + +
+ {DELETED_MESSAGE} +
{L_REASON}{L_COLON} {DELETE_REASON} +
+ + + +
{SIGNATURE}
+ + + +
+
{L_THIS_PM_IP}{L_THIS_POST_IP}{L_COLON} + {POST_IPADDR}{POST_IP} ({POST_IP}{L_LOOKUP_IP}) + + {POST_IPADDR} ({POST_IP}){POST_IP} ({L_LOOKUP_IP}) +
+ + +
+ +
+
+ + +
+
+ +

{L_MOD_OPTIONS}

+ +
+ +
+
+
+
+
+ + +
+ [ {L_FIND_USERNAME} ] +
+
+ {S_FORM_TOKEN} +
+
+ + + + + +
+ +
+
+
+
+
+
+ {S_FORM_TOKEN} +
+
+ + +
+
+ + + + +
+
+ +

{RETURN_QUEUE} | {RETURN_TOPIC_SIMPLE} | {RETURN_POST}{RETURN_REPORTS} | {L_VIEW_POST} | {L_VIEW_TOPIC} | {L_VIEW_FORUM}{RETURN_TOPIC}

+ +
+
+ + + + + + +
+
+ +
+ + +

{L_FEEDBACK}

+ + + {L_REPORTED_BY}{L_COLON} {usernotes.REPORT_BY} « {usernotes.REPORT_AT} +
+
{usernotes.ACTION}
+ +
+ + + +
+   + +
+ + + +

{L_ADD_FEEDBACK}

+

{L_ADD_FEEDBACK_EXPLAIN}

+ +
+ +
+ +
+   + + {S_FORM_TOKEN} +
+
+ +
+
+ + + +
+
+ +

{L_MCP_POST_REPORTS}

+ + + {L_REPORTED_BY}{L_COLON} {reports.REPORTER}{reports.REPORTER} « {reports.REPORT_TIME} +

{reports.REASON_TITLE}{L_COLON} {reports.REASON_DESC}
{reports.REPORT_TEXT}

+ + +
+
+ + + +
+
+ +

{L_THIS_POST_IP}{L_COLON} + {POST_IPADDR}{POST_IP} ({POST_IP}{L_LOOKUP_IP}) + + {POST_IPADDR} ({POST_IP}){POST_IP} ({L_LOOKUP_IP}) +

+ + + + + + + + + + + + + + + + + + + + +
{L_OTHER_USERS}{L_POSTS}
{userrow.USERNAME}{userrow.USERNAME}{userrow.NUM_POSTS}
{L_NO_MATCHES_FOUND}
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
{L_IPS_POSTED_FROM}{L_POSTS}
{iprow.HOSTNAME} ({iprow.IP}){iprow.IP} ({L_LOOKUP_IP}){iprow.NUM_POSTS}
{L_NO_MATCHES_FOUND}
+ + + + + +
+
+ + + + + + + diff --git a/styles/Milk_v2/template/mcp_queue.html b/styles/Milk_v2/template/mcp_queue.html new file mode 100644 index 0000000..e9ea47d --- /dev/null +++ b/styles/Milk_v2/template/mcp_queue.html @@ -0,0 +1,123 @@ + + +
+ +
+ + + {S_FORM_TOKEN} +
+ +

{L_TITLE}

+ +
+
+ +

{L_EXPLAIN}

+ + +
+ +
+ +
    +
  • +
    +
    {L_TOPIC}{L_POST}
    +
    {L_TOPIC} & {L_FORUM}
    +
    {L_MARK}
    +
    +
  • +
+ + +
+ + + + +
+ + +

+ + {L_NO_TOPICS_DELETED}{L_NO_POSTS_DELETED} + + {L_NO_TOPICS_QUEUE}{L_NO_POSTS_QUEUE} + +

+ + +
+
+ + +
+ +   + + +   + + + +
+ +
+ + diff --git a/styles/Milk_v2/template/memberlist_view.html b/styles/Milk_v2/template/memberlist_view.html new file mode 100644 index 0000000..4e68a09 --- /dev/null +++ b/styles/Milk_v2/template/memberlist_view.html @@ -0,0 +1,145 @@ + + +

{PAGE_TITLE}

+ +
+ + + +
+
+
+ + +
+
{AVATAR_IMG}
+ +
{RANK_TITLE}
+
{RANK_IMG}
+ +
+ + +
+
{L_USERNAME}{L_COLON}
+
{USERNAME} + [ {L_EDIT_PROFILE} ] + [ {L_USER_ADMIN} ] + [ {L_USER_BAN} ] + [ {L_USE_PERMISSIONS} ] +
+ + +
{L_RANK}{L_COLON}
{RANK_TITLE}
+
 {L_RANK}{L_COLON}
{RANK_IMG}
+ + +
{L_USER_IS_INACTIVE}{L_COLON}
{USER_INACTIVE_REASON}
+
{L_AGE}{L_COLON}
{AGE}
+
{L_USERGROUPS}{L_COLON}
+ + + +
{custom_fields.PROFILE_FIELD_NAME}{L_COLON}
{custom_fields.PROFILE_FIELD_VALUE}
+ + + + + + +
 
{L_REMOVE_FRIEND}
+ +
 
{L_REMOVE_FOE}
+ + +
 
{L_ADD_FRIEND}
+ + +
 
{L_ADD_FOE}
+ + + + +
+ +
+
+ + +
+
+ +
+

{L_CONTACT_USER}

+ +
+
{L_EMAIL_ADDRESS}{L_COLON}
{L_SEND_EMAIL_USER}
+
{L_PM}{L_COLON}
{L_SEND_PRIVATE_MESSAGE}
+
{L_JABBER}{L_COLON}
{L_SEND_JABBER_MESSAGE}
{L_JABBER}{L_COLON}
{USER_JABBER}
+ + + +
{custom_fields.PROFILE_FIELD_NAME}{L_COLON}
+ +
{custom_fields.PROFILE_FIELD_DESC}
+ +
{custom_fields.PROFILE_FIELD_VALUE}
+ + + + + + +
{PROFILE_FIELD1_NAME}{L_COLON}
{PROFILE_FIELD1_VALUE}
+ +
+
+ +
+

{L_USER_FORUM}

+
+ +
{L_JOINED}{L_COLON}
{JOINED}
+
{L_LAST_ACTIVE}{L_COLON}
{LAST_ACTIVE}
+ +
{L_WARNINGS}{L_COLON}
+
{WARNINGS} [ {L_VIEW_NOTES} | {L_WARN_USER} ]
+ +
{L_TOTAL_POSTS}{L_COLON}
+
{POSTS} | {L_SEARCH_USER_POSTS} +
({POSTS_PCT} / {POSTS_DAY}) +
({L_POSTS_IN_QUEUE})
({L_POSTS_IN_QUEUE}) +
+ +
{L_ACTIVE_IN_FORUM}{L_COLON}
{ACTIVE_FORUM}
({ACTIVE_FORUM_POSTS} / {ACTIVE_FORUM_PCT}) -
+
{L_ACTIVE_IN_TOPIC}{L_COLON}
{ACTIVE_TOPIC}
({ACTIVE_TOPIC_POSTS} / {ACTIVE_TOPIC_PCT}) -
+ + +
+
+ +
+
+ + + +
+
+ +

{L_SIGNATURE}

+ +
{SIGNATURE}
+ +
+
+ + +
+ + + +
+ + + + diff --git a/styles/Milk_v2/template/navbar_footer.html b/styles/Milk_v2/template/navbar_footer.html new file mode 100644 index 0000000..ed43a83 --- /dev/null +++ b/styles/Milk_v2/template/navbar_footer.html @@ -0,0 +1,81 @@ + diff --git a/styles/Milk_v2/template/navbar_header.html b/styles/Milk_v2/template/navbar_header.html new file mode 100644 index 0000000..4ef62af --- /dev/null +++ b/styles/Milk_v2/template/navbar_header.html @@ -0,0 +1,188 @@ + diff --git a/styles/Milk_v2/template/notification_dropdown.html b/styles/Milk_v2/template/notification_dropdown.html new file mode 100644 index 0000000..b7eba51 --- /dev/null +++ b/styles/Milk_v2/template/notification_dropdown.html @@ -0,0 +1,47 @@ + diff --git a/styles/Milk_v2/template/overall_footer.html b/styles/Milk_v2/template/overall_footer.html new file mode 100644 index 0000000..413c575 --- /dev/null +++ b/styles/Milk_v2/template/overall_footer.html @@ -0,0 +1,199 @@ + +
+
+
+ + +
+
+
+ + + +
+
+
+ +
+ + + + +
+ + + +
+ + + + + + + + + + + + +
+ +
+ + {RUN_CRON_TASK} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {$SCRIPTS} + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/template/overall_footer_simplified.html b/styles/Milk_v2/template/overall_footer_simplified.html new file mode 100644 index 0000000..b81c33c --- /dev/null +++ b/styles/Milk_v2/template/overall_footer_simplified.html @@ -0,0 +1,104 @@ + +
+ + {RUN_CRON_TASK} +
+ + + + + + + + + + + + + + + + + + + + {$SCRIPTS} + + + + + + + + + + + diff --git a/styles/Milk_v2/template/overall_header.html b/styles/Milk_v2/template/overall_header.html new file mode 100644 index 0000000..e400dee --- /dev/null +++ b/styles/Milk_v2/template/overall_header.html @@ -0,0 +1,257 @@ + + + + + + +{META} +{STYLE_SETTINGS_HTML_4} +<!-- IF UNREAD_NOTIFICATIONS_COUNT -->({UNREAD_NOTIFICATIONS_COUNT}) <!-- ENDIF --><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{$STYLESHEETS} + + + + + + + + + + +
+ + + + +
+ + + + + + + + + + +
+ +
+
+ {L_INFORMATION}{L_COLON} {L_BOARD_DISABLED} +
+
+ + + + + +
+
+
+
+ diff --git a/styles/Milk_v2/template/overall_header_simplified.html b/styles/Milk_v2/template/overall_header_simplified.html new file mode 100644 index 0000000..25c7926 --- /dev/null +++ b/styles/Milk_v2/template/overall_header_simplified.html @@ -0,0 +1,136 @@ + + + + + + +{META} +{STYLE_SETTINGS_HTML_4} +<!-- IF UNREAD_NOTIFICATIONS_COUNT -->({UNREAD_NOTIFICATIONS_COUNT}) <!-- ENDIF --><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{$STYLESHEETS} + + + + + diff --git a/styles/Milk_v2/template/parallax.js b/styles/Milk_v2/template/parallax.js new file mode 100644 index 0000000..90b5b78 --- /dev/null +++ b/styles/Milk_v2/template/parallax.js @@ -0,0 +1,404 @@ +/*! + * parallax.js v1.4.2 (http://pixelcog.github.io/parallax.js/) + * @copyright 2016 PixelCog, Inc. + * @license MIT (https://github.com/pixelcog/parallax.js/blob/master/LICENSE) + */ + +;(function ( $, window, document, undefined ) { + + // Polyfill for requestAnimationFrame + // via: https://gist.github.com/paulirish/1579671 + + (function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] + || window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function(callback) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; + }()); + + + // Parallax Constructor + + function Parallax(element, options) { + var self = this; + + if (typeof options == 'object') { + delete options.refresh; + delete options.render; + $.extend(this, options); + } + + this.$element = $(element); + + if (!this.imageSrc && this.$element.is('img')) { + this.imageSrc = this.$element.attr('src'); + } + + var positions = (this.position + '').toLowerCase().match(/\S+/g) || []; + + if (positions.length < 1) { + positions.push('center'); + } + if (positions.length == 1) { + positions.push(positions[0]); + } + + if (positions[0] == 'top' || positions[0] == 'bottom' || positions[1] == 'left' || positions[1] == 'right') { + positions = [positions[1], positions[0]]; + } + + if (this.positionX != undefined) positions[0] = this.positionX.toLowerCase(); + if (this.positionY != undefined) positions[1] = this.positionY.toLowerCase(); + + self.positionX = positions[0]; + self.positionY = positions[1]; + + if (this.positionX != 'left' && this.positionX != 'right') { + if (isNaN(parseInt(this.positionX))) { + this.positionX = 'center'; + } else { + this.positionX = parseInt(this.positionX); + } + } + + if (this.positionY != 'top' && this.positionY != 'bottom') { + if (isNaN(parseInt(this.positionY))) { + this.positionY = 'center'; + } else { + this.positionY = parseInt(this.positionY); + } + } + + this.position = + this.positionX + (isNaN(this.positionX)? '' : 'px') + ' ' + + this.positionY + (isNaN(this.positionY)? '' : 'px'); + + if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) { + if (this.imageSrc && this.iosFix && !this.$element.is('img')) { + this.$element.css({ + backgroundImage: 'url(' + this.imageSrc + ')', + backgroundSize: 'cover', + backgroundPosition: this.position + }); + } + return this; + } + + if (navigator.userAgent.match(/(Android)/)) { + if (this.imageSrc && this.androidFix && !this.$element.is('img')) { + this.$element.css({ + backgroundImage: 'url(' + this.imageSrc + ')', + backgroundSize: 'cover', + backgroundPosition: this.position + }); + } + return this; + } + + this.$mirror = $('
').prependTo('body'); + + var slider = this.$element.find('>.parallax-slider'); + var sliderExisted = false; + + if (slider.length == 0) + this.$slider = $('').prependTo(this.$mirror); + else { + this.$slider = slider.prependTo(this.$mirror) + sliderExisted = true; + } + + this.$mirror.addClass('parallax-mirror').css({ + visibility: 'hidden', + zIndex: this.zIndex, + position: 'fixed', + top: 0, + left: 0, + overflow: 'hidden' + }); + + this.$slider.addClass('parallax-slider').one('load', function() { + if (!self.naturalHeight || !self.naturalWidth) { + self.naturalHeight = this.naturalHeight || this.height || 1; + self.naturalWidth = this.naturalWidth || this.width || 1; + } + self.aspectRatio = self.naturalWidth / self.naturalHeight; + + Parallax.isSetup || Parallax.setup(); + Parallax.sliders.push(self); + Parallax.isFresh = false; + Parallax.requestRender(); + }); + + if (!sliderExisted) + this.$slider[0].src = this.imageSrc; + + if (this.naturalHeight && this.naturalWidth || this.$slider[0].complete || slider.length > 0) { + this.$slider.trigger('load'); + } + + }; + + + // Parallax Instance Methods + + $.extend(Parallax.prototype, { + speed: 0.2, + bleed: 0, + zIndex: -100, + iosFix: true, + androidFix: true, + position: 'center', + overScrollFix: false, + + refresh: function() { + this.boxWidth = this.$element.outerWidth(); + this.boxHeight = this.$element.outerHeight() + this.bleed * 2; + this.boxOffsetTop = this.$element.offset().top - this.bleed; + this.boxOffsetLeft = this.$element.offset().left; + this.boxOffsetBottom = this.boxOffsetTop + this.boxHeight; + + var winHeight = Parallax.winHeight; + var docHeight = Parallax.docHeight; + var maxOffset = Math.min(this.boxOffsetTop, docHeight - winHeight); + var minOffset = Math.max(this.boxOffsetTop + this.boxHeight - winHeight, 0); + var imageHeightMin = this.boxHeight + (maxOffset - minOffset) * (1 - this.speed) | 0; + var imageOffsetMin = (this.boxOffsetTop - maxOffset) * (1 - this.speed) | 0; + + if (imageHeightMin * this.aspectRatio >= this.boxWidth) { + this.imageWidth = imageHeightMin * this.aspectRatio | 0; + this.imageHeight = imageHeightMin; + this.offsetBaseTop = imageOffsetMin; + + var margin = this.imageWidth - this.boxWidth; + + if (this.positionX == 'left') { + this.offsetLeft = 0; + } else if (this.positionX == 'right') { + this.offsetLeft = - margin; + } else if (!isNaN(this.positionX)) { + this.offsetLeft = Math.max(this.positionX, - margin); + } else { + this.offsetLeft = - margin / 2 | 0; + } + } else { + this.imageWidth = this.boxWidth; + this.imageHeight = this.boxWidth / this.aspectRatio | 0; + this.offsetLeft = 0; + + var margin = this.imageHeight - imageHeightMin; + + if (this.positionY == 'top') { + this.offsetBaseTop = imageOffsetMin; + } else if (this.positionY == 'bottom') { + this.offsetBaseTop = imageOffsetMin - margin; + } else if (!isNaN(this.positionY)) { + this.offsetBaseTop = imageOffsetMin + Math.max(this.positionY, - margin); + } else { + this.offsetBaseTop = imageOffsetMin - margin / 2 | 0; + } + } + }, + + render: function() { + var scrollTop = Parallax.scrollTop; + var scrollLeft = Parallax.scrollLeft; + var overScroll = this.overScrollFix ? Parallax.overScroll : 0; + var scrollBottom = scrollTop + Parallax.winHeight; + + if (this.boxOffsetBottom > scrollTop && this.boxOffsetTop <= scrollBottom) { + this.visibility = 'visible'; + this.mirrorTop = this.boxOffsetTop - scrollTop; + this.mirrorLeft = this.boxOffsetLeft - scrollLeft; + this.offsetTop = this.offsetBaseTop - this.mirrorTop * (1 - this.speed); + } else { + this.visibility = 'hidden'; + } + + this.$mirror.css({ + transform: 'translate3d(0px, 0px, 0px)', + visibility: this.visibility, + top: this.mirrorTop - overScroll, + left: this.mirrorLeft, + height: this.boxHeight, + width: this.boxWidth + }); + + this.$slider.css({ + transform: 'translate3d(0px, 0px, 0px)', + position: 'absolute', + top: this.offsetTop, + left: this.offsetLeft, + height: this.imageHeight, + width: this.imageWidth, + maxWidth: 'none' + }); + } + }); + + + // Parallax Static Methods + + $.extend(Parallax, { + scrollTop: 0, + scrollLeft: 0, + winHeight: 0, + winWidth: 0, + docHeight: 1 << 30, + docWidth: 1 << 30, + sliders: [], + isReady: false, + isFresh: false, + isBusy: false, + + setup: function() { + if (this.isReady) return; + + var $doc = $(document), $win = $(window); + + var loadDimensions = function() { + Parallax.winHeight = $win.height(); + Parallax.winWidth = $win.width(); + Parallax.docHeight = $doc.height(); + Parallax.docWidth = $doc.width(); + }; + + var loadScrollPosition = function() { + var winScrollTop = $win.scrollTop(); + var scrollTopMax = Parallax.docHeight - Parallax.winHeight; + var scrollLeftMax = Parallax.docWidth - Parallax.winWidth; + Parallax.scrollTop = Math.max(0, Math.min(scrollTopMax, winScrollTop)); + Parallax.scrollLeft = Math.max(0, Math.min(scrollLeftMax, $win.scrollLeft())); + Parallax.overScroll = Math.max(winScrollTop - scrollTopMax, Math.min(winScrollTop, 0)); + }; + + $win.on('resize.px.parallax load.px.parallax', function() { + loadDimensions(); + Parallax.isFresh = false; + Parallax.requestRender(); + }) + .on('scroll.px.parallax load.px.parallax', function() { + loadScrollPosition(); + Parallax.requestRender(); + }); + + loadDimensions(); + loadScrollPosition(); + + this.isReady = true; + }, + + configure: function(options) { + if (typeof options == 'object') { + delete options.refresh; + delete options.render; + $.extend(this.prototype, options); + } + }, + + refresh: function() { + $.each(this.sliders, function(){ this.refresh() }); + this.isFresh = true; + }, + + render: function() { + this.isFresh || this.refresh(); + $.each(this.sliders, function(){ this.render() }); + }, + + requestRender: function() { + var self = this; + + if (!this.isBusy) { + this.isBusy = true; + window.requestAnimationFrame(function() { + self.render(); + self.isBusy = false; + }); + } + }, + destroy: function(el){ + var i, + parallaxElement = $(el).data('px.parallax'); + parallaxElement.$mirror.remove(); + for(i=0; i < this.sliders.length; i+=1){ + if(this.sliders[i] == parallaxElement){ + this.sliders.splice(i, 1); + } + } + $(el).data('px.parallax', false); + if(this.sliders.length === 0){ + $(window).off('scroll.px.parallax resize.px.parallax load.px.parallax'); + this.isReady = false; + Parallax.isSetup = false; + } + } + }); + + + // Parallax Plugin Definition + + function Plugin(option) { + return this.each(function () { + var $this = $(this); + var options = typeof option == 'object' && option; + + if (this == window || this == document || $this.is('body')) { + Parallax.configure(options); + } + else if (!$this.data('px.parallax')) { + options = $.extend({}, $this.data(), options); + $this.data('px.parallax', new Parallax(this, options)); + } + else if (typeof option == 'object') + { + $.extend($this.data('px.parallax'), options); + } + if (typeof option == 'string') { + if(option == 'destroy'){ + Parallax['destroy'](this); + }else{ + Parallax[option](); + } + } + }) + }; + + var old = $.fn.parallax; + + $.fn.parallax = Plugin; + $.fn.parallax.Constructor = Parallax; + + + // Parallax No Conflict + + $.fn.parallax.noConflict = function () { + $.fn.parallax = old; + return this; + }; + + + // Parallax Data-API + + $(document).on('ready.px.parallax.data-api', function () { + $('[data-parallax="scroll"]').parallax(); + }); + +}(jQuery, window, document)); diff --git a/styles/Milk_v2/template/particles.app.js b/styles/Milk_v2/template/particles.app.js new file mode 100644 index 0000000..0d055eb --- /dev/null +++ b/styles/Milk_v2/template/particles.app.js @@ -0,0 +1,127 @@ +/* ----------------------------------------------- +/* How to use? : Check the GitHub README +/* ----------------------------------------------- */ + +/* To load a config file (particles.json) you need to host this demo (MAMP/WAMP/local)... */ +/* +particlesJS.load('particles-js', 'particles.json', function() { + console.log('particles.js loaded - callback'); +}); +*/ + +/* Otherwise just put the config content (json): */ + +particlesJS('particles-js', + +{ + "particles": { + "number": { + "value": 120, + "density": { + "enable": true, + "value_area": 800 + } + }, + "color": { + "value": "#ffffff" + }, + "shape": { + "type": "circle", + "stroke": { + "width": 0, + "color": "#000000" + }, + "polygon": { + "nb_sides": 5 + }, + "image": { + "src": "img/github.svg", + "width": 100, + "height": 100 + } + }, + "opacity": { + "value": 0.5, + "random": false, + "anim": { + "enable": false, + "speed": 1, + "opacity_min": 0.1, + "sync": false + } + }, + "size": { + "value": 3, + "random": true, + "anim": { + "enable": false, + "speed": 40, + "size_min": 0.1, + "sync": false + } + }, + "line_linked": { + "enable": true, + "distance": 150, + "color": "#ffffff", + "opacity": 0.4, + "width": 1 + }, + "move": { + "enable": true, + "speed": 6, + "direction": "none", + "random": false, + "straight": false, + "out_mode": "out", + "bounce": false, + "attract": { + "enable": false, + "rotateX": 600, + "rotateY": 1200 + } + } + }, + "interactivity": { + "detect_on": "canvas", + "events": { + "onhover": { + "enable": true, + "mode": "grab" + }, + "onclick": { + "enable": true, + "mode": "push" + }, + "resize": true + }, + "modes": { + "grab": { + "distance": 175, + "line_linked": { + "opacity": 1 + } + }, + "bubble": { + "distance": 400, + "size": 40, + "duration": 2, + "opacity": 8, + "speed": 3 + }, + "repulse": { + "distance": 200, + "duration": 0.4 + }, + "push": { + "particles_nb": 4 + }, + "remove": { + "particles_nb": 2 + } + } + }, + "retina_detect": true +} + +); diff --git a/styles/Milk_v2/template/particles.js b/styles/Milk_v2/template/particles.js new file mode 100644 index 0000000..325d834 --- /dev/null +++ b/styles/Milk_v2/template/particles.js @@ -0,0 +1,1541 @@ +/* ----------------------------------------------- +/* Author : Vincent Garreau - vincentgarreau.com +/* MIT license: http://opensource.org/licenses/MIT +/* Demo / Generator : vincentgarreau.com/particles.js +/* GitHub : github.com/VincentGarreau/particles.js +/* How to use? : Check the GitHub README +/* v2.0.0 +/* ----------------------------------------------- */ + +var pJS = function(tag_id, params){ + + var canvas_el = document.querySelector('#'+tag_id+' > .particles-js-canvas-el'); + + /* particles.js variables with default values */ + this.pJS = { + canvas: { + el: canvas_el, + w: canvas_el.offsetWidth, + h: canvas_el.offsetHeight + }, + particles: { + number: { + value: 400, + density: { + enable: true, + value_area: 800 + } + }, + color: { + value: '#fff' + }, + shape: { + type: 'circle', + stroke: { + width: 0, + color: '#ff0000' + }, + polygon: { + nb_sides: 5 + }, + image: { + src: '', + width: 100, + height: 100 + } + }, + opacity: { + value: 1, + random: false, + anim: { + enable: false, + speed: 2, + opacity_min: 0, + sync: false + } + }, + size: { + value: 20, + random: false, + anim: { + enable: false, + speed: 20, + size_min: 0, + sync: false + } + }, + line_linked: { + enable: true, + distance: 100, + color: '#fff', + opacity: 1, + width: 1 + }, + move: { + enable: true, + speed: 2, + direction: 'none', + random: false, + straight: false, + out_mode: 'out', + bounce: false, + attract: { + enable: false, + rotateX: 3000, + rotateY: 3000 + } + }, + array: [] + }, + interactivity: { + detect_on: 'canvas', + events: { + onhover: { + enable: true, + mode: 'grab' + }, + onclick: { + enable: true, + mode: 'push' + }, + resize: true + }, + modes: { + grab:{ + distance: 100, + line_linked:{ + opacity: 1 + } + }, + bubble:{ + distance: 200, + size: 80, + duration: 0.4 + }, + repulse:{ + distance: 200, + duration: 0.4 + }, + push:{ + particles_nb: 4 + }, + remove:{ + particles_nb: 2 + } + }, + mouse:{} + }, + retina_detect: false, + fn: { + interact: {}, + modes: {}, + vendors:{} + }, + tmp: {} + }; + + var pJS = this.pJS; + + /* params settings */ + if(params){ + Object.deepExtend(pJS, params); + } + + pJS.tmp.obj = { + size_value: pJS.particles.size.value, + size_anim_speed: pJS.particles.size.anim.speed, + move_speed: pJS.particles.move.speed, + line_linked_distance: pJS.particles.line_linked.distance, + line_linked_width: pJS.particles.line_linked.width, + mode_grab_distance: pJS.interactivity.modes.grab.distance, + mode_bubble_distance: pJS.interactivity.modes.bubble.distance, + mode_bubble_size: pJS.interactivity.modes.bubble.size, + mode_repulse_distance: pJS.interactivity.modes.repulse.distance + }; + + + pJS.fn.retinaInit = function(){ + + if(pJS.retina_detect && window.devicePixelRatio > 1){ + pJS.canvas.pxratio = window.devicePixelRatio; + pJS.tmp.retina = true; + } + else{ + pJS.canvas.pxratio = 1; + pJS.tmp.retina = false; + } + + pJS.canvas.w = pJS.canvas.el.offsetWidth * pJS.canvas.pxratio; + pJS.canvas.h = pJS.canvas.el.offsetHeight * pJS.canvas.pxratio; + + pJS.particles.size.value = pJS.tmp.obj.size_value * pJS.canvas.pxratio; + pJS.particles.size.anim.speed = pJS.tmp.obj.size_anim_speed * pJS.canvas.pxratio; + pJS.particles.move.speed = pJS.tmp.obj.move_speed * pJS.canvas.pxratio; + pJS.particles.line_linked.distance = pJS.tmp.obj.line_linked_distance * pJS.canvas.pxratio; + pJS.interactivity.modes.grab.distance = pJS.tmp.obj.mode_grab_distance * pJS.canvas.pxratio; + pJS.interactivity.modes.bubble.distance = pJS.tmp.obj.mode_bubble_distance * pJS.canvas.pxratio; + pJS.particles.line_linked.width = pJS.tmp.obj.line_linked_width * pJS.canvas.pxratio; + pJS.interactivity.modes.bubble.size = pJS.tmp.obj.mode_bubble_size * pJS.canvas.pxratio; + pJS.interactivity.modes.repulse.distance = pJS.tmp.obj.mode_repulse_distance * pJS.canvas.pxratio; + + }; + + + + /* ---------- pJS functions - canvas ------------ */ + + pJS.fn.canvasInit = function(){ + pJS.canvas.ctx = pJS.canvas.el.getContext('2d'); + }; + + pJS.fn.canvasSize = function(){ + + pJS.canvas.el.width = pJS.canvas.w; + pJS.canvas.el.height = pJS.canvas.h; + + if(pJS && pJS.interactivity.events.resize){ + + window.addEventListener('resize', function(){ + + pJS.canvas.w = pJS.canvas.el.offsetWidth; + pJS.canvas.h = pJS.canvas.el.offsetHeight; + + /* resize canvas */ + if(pJS.tmp.retina){ + pJS.canvas.w *= pJS.canvas.pxratio; + pJS.canvas.h *= pJS.canvas.pxratio; + } + + pJS.canvas.el.width = pJS.canvas.w; + pJS.canvas.el.height = pJS.canvas.h; + + /* repaint canvas on anim disabled */ + if(!pJS.particles.move.enable){ + pJS.fn.particlesEmpty(); + pJS.fn.particlesCreate(); + pJS.fn.particlesDraw(); + pJS.fn.vendors.densityAutoParticles(); + } + + /* density particles enabled */ + pJS.fn.vendors.densityAutoParticles(); + + }); + + } + + }; + + + pJS.fn.canvasPaint = function(){ + pJS.canvas.ctx.fillRect(0, 0, pJS.canvas.w, pJS.canvas.h); + }; + + pJS.fn.canvasClear = function(){ + pJS.canvas.ctx.clearRect(0, 0, pJS.canvas.w, pJS.canvas.h); + }; + + + /* --------- pJS functions - particles ----------- */ + + pJS.fn.particle = function(color, opacity, position){ + + /* size */ + this.radius = (pJS.particles.size.random ? Math.random() : 1) * pJS.particles.size.value; + if(pJS.particles.size.anim.enable){ + this.size_status = false; + this.vs = pJS.particles.size.anim.speed / 100; + if(!pJS.particles.size.anim.sync){ + this.vs = this.vs * Math.random(); + } + } + + /* position */ + this.x = position ? position.x : Math.random() * pJS.canvas.w; + this.y = position ? position.y : Math.random() * pJS.canvas.h; + + /* check position - into the canvas */ + if(this.x > pJS.canvas.w - this.radius*2) this.x = this.x - this.radius; + else if(this.x < this.radius*2) this.x = this.x + this.radius; + if(this.y > pJS.canvas.h - this.radius*2) this.y = this.y - this.radius; + else if(this.y < this.radius*2) this.y = this.y + this.radius; + + /* check position - avoid overlap */ + if(pJS.particles.move.bounce){ + pJS.fn.vendors.checkOverlap(this, position); + } + + /* color */ + this.color = {}; + if(typeof(color.value) == 'object'){ + + if(color.value instanceof Array){ + var color_selected = color.value[Math.floor(Math.random() * pJS.particles.color.value.length)]; + this.color.rgb = hexToRgb(color_selected); + }else{ + if(color.value.r != undefined && color.value.g != undefined && color.value.b != undefined){ + this.color.rgb = { + r: color.value.r, + g: color.value.g, + b: color.value.b + } + } + if(color.value.h != undefined && color.value.s != undefined && color.value.l != undefined){ + this.color.hsl = { + h: color.value.h, + s: color.value.s, + l: color.value.l + } + } + } + + } + else if(color.value == 'random'){ + this.color.rgb = { + r: (Math.floor(Math.random() * (255 - 0 + 1)) + 0), + g: (Math.floor(Math.random() * (255 - 0 + 1)) + 0), + b: (Math.floor(Math.random() * (255 - 0 + 1)) + 0) + } + } + else if(typeof(color.value) == 'string'){ + this.color = color; + this.color.rgb = hexToRgb(this.color.value); + } + + /* opacity */ + this.opacity = (pJS.particles.opacity.random ? Math.random() : 1) * pJS.particles.opacity.value; + if(pJS.particles.opacity.anim.enable){ + this.opacity_status = false; + this.vo = pJS.particles.opacity.anim.speed / 100; + if(!pJS.particles.opacity.anim.sync){ + this.vo = this.vo * Math.random(); + } + } + + /* animation - velocity for speed */ + var velbase = {} + switch(pJS.particles.move.direction){ + case 'top': + velbase = { x:0, y:-1 }; + break; + case 'top-right': + velbase = { x:0.5, y:-0.5 }; + break; + case 'right': + velbase = { x:1, y:-0 }; + break; + case 'bottom-right': + velbase = { x:0.5, y:0.5 }; + break; + case 'bottom': + velbase = { x:0, y:1 }; + break; + case 'bottom-left': + velbase = { x:-0.5, y:1 }; + break; + case 'left': + velbase = { x:-1, y:0 }; + break; + case 'top-left': + velbase = { x:-0.5, y:-0.5 }; + break; + default: + velbase = { x:0, y:0 }; + break; + } + + if(pJS.particles.move.straight){ + this.vx = velbase.x; + this.vy = velbase.y; + if(pJS.particles.move.random){ + this.vx = this.vx * (Math.random()); + this.vy = this.vy * (Math.random()); + } + }else{ + this.vx = velbase.x + Math.random()-0.5; + this.vy = velbase.y + Math.random()-0.5; + } + + // var theta = 2.0 * Math.PI * Math.random(); + // this.vx = Math.cos(theta); + // this.vy = Math.sin(theta); + + this.vx_i = this.vx; + this.vy_i = this.vy; + + + + /* if shape is image */ + + var shape_type = pJS.particles.shape.type; + if(typeof(shape_type) == 'object'){ + if(shape_type instanceof Array){ + var shape_selected = shape_type[Math.floor(Math.random() * shape_type.length)]; + this.shape = shape_selected; + } + }else{ + this.shape = shape_type; + } + + if(this.shape == 'image'){ + var sh = pJS.particles.shape; + this.img = { + src: sh.image.src, + ratio: sh.image.width / sh.image.height + } + if(!this.img.ratio) this.img.ratio = 1; + if(pJS.tmp.img_type == 'svg' && pJS.tmp.source_svg != undefined){ + pJS.fn.vendors.createSvgImg(this); + if(pJS.tmp.pushing){ + this.img.loaded = false; + } + } + } + + + + }; + + + pJS.fn.particle.prototype.draw = function() { + + var p = this; + + if(p.radius_bubble != undefined){ + var radius = p.radius_bubble; + }else{ + var radius = p.radius; + } + + if(p.opacity_bubble != undefined){ + var opacity = p.opacity_bubble; + }else{ + var opacity = p.opacity; + } + + if(p.color.rgb){ + var color_value = 'rgba('+p.color.rgb.r+','+p.color.rgb.g+','+p.color.rgb.b+','+opacity+')'; + }else{ + var color_value = 'hsla('+p.color.hsl.h+','+p.color.hsl.s+'%,'+p.color.hsl.l+'%,'+opacity+')'; + } + + pJS.canvas.ctx.fillStyle = color_value; + pJS.canvas.ctx.beginPath(); + + switch(p.shape){ + + case 'circle': + pJS.canvas.ctx.arc(p.x, p.y, radius, 0, Math.PI * 2, false); + break; + + case 'edge': + pJS.canvas.ctx.rect(p.x-radius, p.y-radius, radius*2, radius*2); + break; + + case 'triangle': + pJS.fn.vendors.drawShape(pJS.canvas.ctx, p.x-radius, p.y+radius / 1.66, radius*2, 3, 2); + break; + + case 'polygon': + pJS.fn.vendors.drawShape( + pJS.canvas.ctx, + p.x - radius / (pJS.particles.shape.polygon.nb_sides/3.5), // startX + p.y - radius / (2.66/3.5), // startY + radius*2.66 / (pJS.particles.shape.polygon.nb_sides/3), // sideLength + pJS.particles.shape.polygon.nb_sides, // sideCountNumerator + 1 // sideCountDenominator + ); + break; + + case 'star': + pJS.fn.vendors.drawShape( + pJS.canvas.ctx, + p.x - radius*2 / (pJS.particles.shape.polygon.nb_sides/4), // startX + p.y - radius / (2*2.66/3.5), // startY + radius*2*2.66 / (pJS.particles.shape.polygon.nb_sides/3), // sideLength + pJS.particles.shape.polygon.nb_sides, // sideCountNumerator + 2 // sideCountDenominator + ); + break; + + case 'image': + + function draw(){ + pJS.canvas.ctx.drawImage( + img_obj, + p.x-radius, + p.y-radius, + radius*2, + radius*2 / p.img.ratio + ); + } + + if(pJS.tmp.img_type == 'svg'){ + var img_obj = p.img.obj; + }else{ + var img_obj = pJS.tmp.img_obj; + } + + if(img_obj){ + draw(); + } + + break; + + } + + pJS.canvas.ctx.closePath(); + + if(pJS.particles.shape.stroke.width > 0){ + pJS.canvas.ctx.strokeStyle = pJS.particles.shape.stroke.color; + pJS.canvas.ctx.lineWidth = pJS.particles.shape.stroke.width; + pJS.canvas.ctx.stroke(); + } + + pJS.canvas.ctx.fill(); + + }; + + + pJS.fn.particlesCreate = function(){ + for(var i = 0; i < pJS.particles.number.value; i++) { + pJS.particles.array.push(new pJS.fn.particle(pJS.particles.color, pJS.particles.opacity.value)); + } + }; + + pJS.fn.particlesUpdate = function(){ + + for(var i = 0; i < pJS.particles.array.length; i++){ + + /* the particle */ + var p = pJS.particles.array[i]; + + // var d = ( dx = pJS.interactivity.mouse.click_pos_x - p.x ) * dx + ( dy = pJS.interactivity.mouse.click_pos_y - p.y ) * dy; + // var f = -BANG_SIZE / d; + // if ( d < BANG_SIZE ) { + // var t = Math.atan2( dy, dx ); + // p.vx = f * Math.cos(t); + // p.vy = f * Math.sin(t); + // } + + /* move the particle */ + if(pJS.particles.move.enable){ + var ms = pJS.particles.move.speed/2; + p.x += p.vx * ms; + p.y += p.vy * ms; + } + + /* change opacity status */ + if(pJS.particles.opacity.anim.enable) { + if(p.opacity_status == true) { + if(p.opacity >= pJS.particles.opacity.value) p.opacity_status = false; + p.opacity += p.vo; + }else { + if(p.opacity <= pJS.particles.opacity.anim.opacity_min) p.opacity_status = true; + p.opacity -= p.vo; + } + if(p.opacity < 0) p.opacity = 0; + } + + /* change size */ + if(pJS.particles.size.anim.enable){ + if(p.size_status == true){ + if(p.radius >= pJS.particles.size.value) p.size_status = false; + p.radius += p.vs; + }else{ + if(p.radius <= pJS.particles.size.anim.size_min) p.size_status = true; + p.radius -= p.vs; + } + if(p.radius < 0) p.radius = 0; + } + + /* change particle position if it is out of canvas */ + if(pJS.particles.move.out_mode == 'bounce'){ + var new_pos = { + x_left: p.radius, + x_right: pJS.canvas.w, + y_top: p.radius, + y_bottom: pJS.canvas.h + } + }else{ + var new_pos = { + x_left: -p.radius, + x_right: pJS.canvas.w + p.radius, + y_top: -p.radius, + y_bottom: pJS.canvas.h + p.radius + } + } + + if(p.x - p.radius > pJS.canvas.w){ + p.x = new_pos.x_left; + p.y = Math.random() * pJS.canvas.h; + } + else if(p.x + p.radius < 0){ + p.x = new_pos.x_right; + p.y = Math.random() * pJS.canvas.h; + } + if(p.y - p.radius > pJS.canvas.h){ + p.y = new_pos.y_top; + p.x = Math.random() * pJS.canvas.w; + } + else if(p.y + p.radius < 0){ + p.y = new_pos.y_bottom; + p.x = Math.random() * pJS.canvas.w; + } + + /* out of canvas modes */ + switch(pJS.particles.move.out_mode){ + case 'bounce': + if (p.x + p.radius > pJS.canvas.w) p.vx = -p.vx; + else if (p.x - p.radius < 0) p.vx = -p.vx; + if (p.y + p.radius > pJS.canvas.h) p.vy = -p.vy; + else if (p.y - p.radius < 0) p.vy = -p.vy; + break; + } + + /* events */ + if(isInArray('grab', pJS.interactivity.events.onhover.mode)){ + pJS.fn.modes.grabParticle(p); + } + + if(isInArray('bubble', pJS.interactivity.events.onhover.mode) || isInArray('bubble', pJS.interactivity.events.onclick.mode)){ + pJS.fn.modes.bubbleParticle(p); + } + + if(isInArray('repulse', pJS.interactivity.events.onhover.mode) || isInArray('repulse', pJS.interactivity.events.onclick.mode)){ + pJS.fn.modes.repulseParticle(p); + } + + /* interaction auto between particles */ + if(pJS.particles.line_linked.enable || pJS.particles.move.attract.enable){ + for(var j = i + 1; j < pJS.particles.array.length; j++){ + var p2 = pJS.particles.array[j]; + + /* link particles */ + if(pJS.particles.line_linked.enable){ + pJS.fn.interact.linkParticles(p,p2); + } + + /* attract particles */ + if(pJS.particles.move.attract.enable){ + pJS.fn.interact.attractParticles(p,p2); + } + + /* bounce particles */ + if(pJS.particles.move.bounce){ + pJS.fn.interact.bounceParticles(p,p2); + } + + } + } + + + } + + }; + + pJS.fn.particlesDraw = function(){ + + /* clear canvas */ + pJS.canvas.ctx.clearRect(0, 0, pJS.canvas.w, pJS.canvas.h); + + /* update each particles param */ + pJS.fn.particlesUpdate(); + + /* draw each particle */ + for(var i = 0; i < pJS.particles.array.length; i++){ + var p = pJS.particles.array[i]; + p.draw(); + } + + }; + + pJS.fn.particlesEmpty = function(){ + pJS.particles.array = []; + }; + + pJS.fn.particlesRefresh = function(){ + + /* init all */ + cancelRequestAnimFrame(pJS.fn.checkAnimFrame); + cancelRequestAnimFrame(pJS.fn.drawAnimFrame); + pJS.tmp.source_svg = undefined; + pJS.tmp.img_obj = undefined; + pJS.tmp.count_svg = 0; + pJS.fn.particlesEmpty(); + pJS.fn.canvasClear(); + + /* restart */ + pJS.fn.vendors.start(); + + }; + + + /* ---------- pJS functions - particles interaction ------------ */ + + pJS.fn.interact.linkParticles = function(p1, p2){ + + var dx = p1.x - p2.x, + dy = p1.y - p2.y, + dist = Math.sqrt(dx*dx + dy*dy); + + /* draw a line between p1 and p2 if the distance between them is under the config distance */ + if(dist <= pJS.particles.line_linked.distance){ + + var opacity_line = pJS.particles.line_linked.opacity - (dist / (1/pJS.particles.line_linked.opacity)) / pJS.particles.line_linked.distance; + + if(opacity_line > 0){ + + /* style */ + var color_line = pJS.particles.line_linked.color_rgb_line; + pJS.canvas.ctx.strokeStyle = 'rgba('+color_line.r+','+color_line.g+','+color_line.b+','+opacity_line+')'; + pJS.canvas.ctx.lineWidth = pJS.particles.line_linked.width; + //pJS.canvas.ctx.lineCap = 'round'; /* performance issue */ + + /* path */ + pJS.canvas.ctx.beginPath(); + pJS.canvas.ctx.moveTo(p1.x, p1.y); + pJS.canvas.ctx.lineTo(p2.x, p2.y); + pJS.canvas.ctx.stroke(); + pJS.canvas.ctx.closePath(); + + } + + } + + }; + + + pJS.fn.interact.attractParticles = function(p1, p2){ + + /* condensed particles */ + var dx = p1.x - p2.x, + dy = p1.y - p2.y, + dist = Math.sqrt(dx*dx + dy*dy); + + if(dist <= pJS.particles.line_linked.distance){ + + var ax = dx/(pJS.particles.move.attract.rotateX*1000), + ay = dy/(pJS.particles.move.attract.rotateY*1000); + + p1.vx -= ax; + p1.vy -= ay; + + p2.vx += ax; + p2.vy += ay; + + } + + + } + + + pJS.fn.interact.bounceParticles = function(p1, p2){ + + var dx = p1.x - p2.x, + dy = p1.y - p2.y, + dist = Math.sqrt(dx*dx + dy*dy), + dist_p = p1.radius+p2.radius; + + if(dist <= dist_p){ + p1.vx = -p1.vx; + p1.vy = -p1.vy; + + p2.vx = -p2.vx; + p2.vy = -p2.vy; + } + + } + + + /* ---------- pJS functions - modes events ------------ */ + + pJS.fn.modes.pushParticles = function(nb, pos){ + + pJS.tmp.pushing = true; + + for(var i = 0; i < nb; i++){ + pJS.particles.array.push( + new pJS.fn.particle( + pJS.particles.color, + pJS.particles.opacity.value, + { + 'x': pos ? pos.pos_x : Math.random() * pJS.canvas.w, + 'y': pos ? pos.pos_y : Math.random() * pJS.canvas.h + } + ) + ) + if(i == nb-1){ + if(!pJS.particles.move.enable){ + pJS.fn.particlesDraw(); + } + pJS.tmp.pushing = false; + } + } + + }; + + + pJS.fn.modes.removeParticles = function(nb){ + + pJS.particles.array.splice(0, nb); + if(!pJS.particles.move.enable){ + pJS.fn.particlesDraw(); + } + + }; + + + pJS.fn.modes.bubbleParticle = function(p){ + + /* on hover event */ + if(pJS.interactivity.events.onhover.enable && isInArray('bubble', pJS.interactivity.events.onhover.mode)){ + + var dx_mouse = p.x - pJS.interactivity.mouse.pos_x, + dy_mouse = p.y - pJS.interactivity.mouse.pos_y, + dist_mouse = Math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse), + ratio = 1 - dist_mouse / pJS.interactivity.modes.bubble.distance; + + function init(){ + p.opacity_bubble = p.opacity; + p.radius_bubble = p.radius; + } + + /* mousemove - check ratio */ + if(dist_mouse <= pJS.interactivity.modes.bubble.distance){ + + if(ratio >= 0 && pJS.interactivity.status == 'mousemove'){ + + /* size */ + if(pJS.interactivity.modes.bubble.size != pJS.particles.size.value){ + + if(pJS.interactivity.modes.bubble.size > pJS.particles.size.value){ + var size = p.radius + (pJS.interactivity.modes.bubble.size*ratio); + if(size >= 0){ + p.radius_bubble = size; + } + }else{ + var dif = p.radius - pJS.interactivity.modes.bubble.size, + size = p.radius - (dif*ratio); + if(size > 0){ + p.radius_bubble = size; + }else{ + p.radius_bubble = 0; + } + } + + } + + /* opacity */ + if(pJS.interactivity.modes.bubble.opacity != pJS.particles.opacity.value){ + + if(pJS.interactivity.modes.bubble.opacity > pJS.particles.opacity.value){ + var opacity = pJS.interactivity.modes.bubble.opacity*ratio; + if(opacity > p.opacity && opacity <= pJS.interactivity.modes.bubble.opacity){ + p.opacity_bubble = opacity; + } + }else{ + var opacity = p.opacity - (pJS.particles.opacity.value-pJS.interactivity.modes.bubble.opacity)*ratio; + if(opacity < p.opacity && opacity >= pJS.interactivity.modes.bubble.opacity){ + p.opacity_bubble = opacity; + } + } + + } + + } + + }else{ + init(); + } + + + /* mouseleave */ + if(pJS.interactivity.status == 'mouseleave'){ + init(); + } + + } + + /* on click event */ + else if(pJS.interactivity.events.onclick.enable && isInArray('bubble', pJS.interactivity.events.onclick.mode)){ + + + if(pJS.tmp.bubble_clicking){ + var dx_mouse = p.x - pJS.interactivity.mouse.click_pos_x, + dy_mouse = p.y - pJS.interactivity.mouse.click_pos_y, + dist_mouse = Math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse), + time_spent = (new Date().getTime() - pJS.interactivity.mouse.click_time)/1000; + + if(time_spent > pJS.interactivity.modes.bubble.duration){ + pJS.tmp.bubble_duration_end = true; + } + + if(time_spent > pJS.interactivity.modes.bubble.duration*2){ + pJS.tmp.bubble_clicking = false; + pJS.tmp.bubble_duration_end = false; + } + } + + + function process(bubble_param, particles_param, p_obj_bubble, p_obj, id){ + + if(bubble_param != particles_param){ + + if(!pJS.tmp.bubble_duration_end){ + if(dist_mouse <= pJS.interactivity.modes.bubble.distance){ + if(p_obj_bubble != undefined) var obj = p_obj_bubble; + else var obj = p_obj; + if(obj != bubble_param){ + var value = p_obj - (time_spent * (p_obj - bubble_param) / pJS.interactivity.modes.bubble.duration); + if(id == 'size') p.radius_bubble = value; + if(id == 'opacity') p.opacity_bubble = value; + } + }else{ + if(id == 'size') p.radius_bubble = undefined; + if(id == 'opacity') p.opacity_bubble = undefined; + } + }else{ + if(p_obj_bubble != undefined){ + var value_tmp = p_obj - (time_spent * (p_obj - bubble_param) / pJS.interactivity.modes.bubble.duration), + dif = bubble_param - value_tmp; + value = bubble_param + dif; + if(id == 'size') p.radius_bubble = value; + if(id == 'opacity') p.opacity_bubble = value; + } + } + + } + + } + + if(pJS.tmp.bubble_clicking){ + /* size */ + process(pJS.interactivity.modes.bubble.size, pJS.particles.size.value, p.radius_bubble, p.radius, 'size'); + /* opacity */ + process(pJS.interactivity.modes.bubble.opacity, pJS.particles.opacity.value, p.opacity_bubble, p.opacity, 'opacity'); + } + + } + + }; + + + pJS.fn.modes.repulseParticle = function(p){ + + if(pJS.interactivity.events.onhover.enable && isInArray('repulse', pJS.interactivity.events.onhover.mode) && pJS.interactivity.status == 'mousemove') { + + var dx_mouse = p.x - pJS.interactivity.mouse.pos_x, + dy_mouse = p.y - pJS.interactivity.mouse.pos_y, + dist_mouse = Math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse); + + var normVec = {x: dx_mouse/dist_mouse, y: dy_mouse/dist_mouse}, + repulseRadius = pJS.interactivity.modes.repulse.distance, + velocity = 100, + repulseFactor = clamp((1/repulseRadius)*(-1*Math.pow(dist_mouse/repulseRadius,2)+1)*repulseRadius*velocity, 0, 50); + + var pos = { + x: p.x + normVec.x * repulseFactor, + y: p.y + normVec.y * repulseFactor + } + + if(pJS.particles.move.out_mode == 'bounce'){ + if(pos.x - p.radius > 0 && pos.x + p.radius < pJS.canvas.w) p.x = pos.x; + if(pos.y - p.radius > 0 && pos.y + p.radius < pJS.canvas.h) p.y = pos.y; + }else{ + p.x = pos.x; + p.y = pos.y; + } + + } + + + else if(pJS.interactivity.events.onclick.enable && isInArray('repulse', pJS.interactivity.events.onclick.mode)) { + + if(!pJS.tmp.repulse_finish){ + pJS.tmp.repulse_count++; + if(pJS.tmp.repulse_count == pJS.particles.array.length){ + pJS.tmp.repulse_finish = true; + } + } + + if(pJS.tmp.repulse_clicking){ + + var repulseRadius = Math.pow(pJS.interactivity.modes.repulse.distance/6, 3); + + var dx = pJS.interactivity.mouse.click_pos_x - p.x, + dy = pJS.interactivity.mouse.click_pos_y - p.y, + d = dx*dx + dy*dy; + + var force = -repulseRadius / d * 1; + + function process(){ + + var f = Math.atan2(dy,dx); + p.vx = force * Math.cos(f); + p.vy = force * Math.sin(f); + + if(pJS.particles.move.out_mode == 'bounce'){ + var pos = { + x: p.x + p.vx, + y: p.y + p.vy + } + if (pos.x + p.radius > pJS.canvas.w) p.vx = -p.vx; + else if (pos.x - p.radius < 0) p.vx = -p.vx; + if (pos.y + p.radius > pJS.canvas.h) p.vy = -p.vy; + else if (pos.y - p.radius < 0) p.vy = -p.vy; + } + + } + + // default + if(d <= repulseRadius){ + process(); + } + + // bang - slow motion mode + // if(!pJS.tmp.repulse_finish){ + // if(d <= repulseRadius){ + // process(); + // } + // }else{ + // process(); + // } + + + }else{ + + if(pJS.tmp.repulse_clicking == false){ + + p.vx = p.vx_i; + p.vy = p.vy_i; + + } + + } + + } + + } + + + pJS.fn.modes.grabParticle = function(p){ + + if(pJS.interactivity.events.onhover.enable && pJS.interactivity.status == 'mousemove'){ + + var dx_mouse = p.x - pJS.interactivity.mouse.pos_x, + dy_mouse = p.y - pJS.interactivity.mouse.pos_y, + dist_mouse = Math.sqrt(dx_mouse*dx_mouse + dy_mouse*dy_mouse); + + /* draw a line between the cursor and the particle if the distance between them is under the config distance */ + if(dist_mouse <= pJS.interactivity.modes.grab.distance){ + + var opacity_line = pJS.interactivity.modes.grab.line_linked.opacity - (dist_mouse / (1/pJS.interactivity.modes.grab.line_linked.opacity)) / pJS.interactivity.modes.grab.distance; + + if(opacity_line > 0){ + + /* style */ + var color_line = pJS.particles.line_linked.color_rgb_line; + pJS.canvas.ctx.strokeStyle = 'rgba('+color_line.r+','+color_line.g+','+color_line.b+','+opacity_line+')'; + pJS.canvas.ctx.lineWidth = pJS.particles.line_linked.width; + //pJS.canvas.ctx.lineCap = 'round'; /* performance issue */ + + /* path */ + pJS.canvas.ctx.beginPath(); + pJS.canvas.ctx.moveTo(p.x, p.y); + pJS.canvas.ctx.lineTo(pJS.interactivity.mouse.pos_x, pJS.interactivity.mouse.pos_y); + pJS.canvas.ctx.stroke(); + pJS.canvas.ctx.closePath(); + + } + + } + + } + + }; + + + + /* ---------- pJS functions - vendors ------------ */ + + pJS.fn.vendors.eventsListeners = function(){ + + /* events target element */ + if(pJS.interactivity.detect_on == 'window'){ + pJS.interactivity.el = window; + }else{ + pJS.interactivity.el = pJS.canvas.el; + } + + + /* detect mouse pos - on hover / click event */ + if(pJS.interactivity.events.onhover.enable || pJS.interactivity.events.onclick.enable){ + + /* el on mousemove */ + pJS.interactivity.el.addEventListener('mousemove', function(e){ + + if(pJS.interactivity.el == window){ + var pos_x = e.clientX, + pos_y = e.clientY; + } + else{ + var pos_x = e.offsetX || e.clientX, + pos_y = e.offsetY || e.clientY; + } + + pJS.interactivity.mouse.pos_x = pos_x; + pJS.interactivity.mouse.pos_y = pos_y; + + if(pJS.tmp.retina){ + pJS.interactivity.mouse.pos_x *= pJS.canvas.pxratio; + pJS.interactivity.mouse.pos_y *= pJS.canvas.pxratio; + } + + pJS.interactivity.status = 'mousemove'; + + }); + + /* el on onmouseleave */ + pJS.interactivity.el.addEventListener('mouseleave', function(e){ + + pJS.interactivity.mouse.pos_x = null; + pJS.interactivity.mouse.pos_y = null; + pJS.interactivity.status = 'mouseleave'; + + }); + + } + + /* on click event */ + if(pJS.interactivity.events.onclick.enable){ + + pJS.interactivity.el.addEventListener('click', function(){ + + pJS.interactivity.mouse.click_pos_x = pJS.interactivity.mouse.pos_x; + pJS.interactivity.mouse.click_pos_y = pJS.interactivity.mouse.pos_y; + pJS.interactivity.mouse.click_time = new Date().getTime(); + + if(pJS.interactivity.events.onclick.enable){ + + switch(pJS.interactivity.events.onclick.mode){ + + case 'push': + if(pJS.particles.move.enable){ + pJS.fn.modes.pushParticles(pJS.interactivity.modes.push.particles_nb, pJS.interactivity.mouse); + }else{ + if(pJS.interactivity.modes.push.particles_nb == 1){ + pJS.fn.modes.pushParticles(pJS.interactivity.modes.push.particles_nb, pJS.interactivity.mouse); + } + else if(pJS.interactivity.modes.push.particles_nb > 1){ + pJS.fn.modes.pushParticles(pJS.interactivity.modes.push.particles_nb); + } + } + break; + + case 'remove': + pJS.fn.modes.removeParticles(pJS.interactivity.modes.remove.particles_nb); + break; + + case 'bubble': + pJS.tmp.bubble_clicking = true; + break; + + case 'repulse': + pJS.tmp.repulse_clicking = true; + pJS.tmp.repulse_count = 0; + pJS.tmp.repulse_finish = false; + setTimeout(function(){ + pJS.tmp.repulse_clicking = false; + }, pJS.interactivity.modes.repulse.duration*1000) + break; + + } + + } + + }); + + } + + + }; + + pJS.fn.vendors.densityAutoParticles = function(){ + + if(pJS.particles.number.density.enable){ + + /* calc area */ + var area = pJS.canvas.el.width * pJS.canvas.el.height / 1000; + if(pJS.tmp.retina){ + area = area/(pJS.canvas.pxratio*2); + } + + /* calc number of particles based on density area */ + var nb_particles = area * pJS.particles.number.value / pJS.particles.number.density.value_area; + + /* add or remove X particles */ + var missing_particles = pJS.particles.array.length - nb_particles; + if(missing_particles < 0) pJS.fn.modes.pushParticles(Math.abs(missing_particles)); + else pJS.fn.modes.removeParticles(missing_particles); + + } + + }; + + + pJS.fn.vendors.checkOverlap = function(p1, position){ + for(var i = 0; i < pJS.particles.array.length; i++){ + var p2 = pJS.particles.array[i]; + + var dx = p1.x - p2.x, + dy = p1.y - p2.y, + dist = Math.sqrt(dx*dx + dy*dy); + + if(dist <= p1.radius + p2.radius){ + p1.x = position ? position.x : Math.random() * pJS.canvas.w; + p1.y = position ? position.y : Math.random() * pJS.canvas.h; + pJS.fn.vendors.checkOverlap(p1); + } + } + }; + + + pJS.fn.vendors.createSvgImg = function(p){ + + /* set color to svg element */ + var svgXml = pJS.tmp.source_svg, + rgbHex = /#([0-9A-F]{3,6})/gi, + coloredSvgXml = svgXml.replace(rgbHex, function (m, r, g, b) { + if(p.color.rgb){ + var color_value = 'rgba('+p.color.rgb.r+','+p.color.rgb.g+','+p.color.rgb.b+','+p.opacity+')'; + }else{ + var color_value = 'hsla('+p.color.hsl.h+','+p.color.hsl.s+'%,'+p.color.hsl.l+'%,'+p.opacity+')'; + } + return color_value; + }); + + /* prepare to create img with colored svg */ + var svg = new Blob([coloredSvgXml], {type: 'image/svg+xml;charset=utf-8'}), + DOMURL = window.URL || window.webkitURL || window, + url = DOMURL.createObjectURL(svg); + + /* create particle img obj */ + var img = new Image(); + img.addEventListener('load', function(){ + p.img.obj = img; + p.img.loaded = true; + DOMURL.revokeObjectURL(url); + pJS.tmp.count_svg++; + }); + img.src = url; + + }; + + + pJS.fn.vendors.destroypJS = function(){ + cancelAnimationFrame(pJS.fn.drawAnimFrame); + canvas_el.remove(); + pJSDom = null; + }; + + + pJS.fn.vendors.drawShape = function(c, startX, startY, sideLength, sideCountNumerator, sideCountDenominator){ + + // By Programming Thomas - https://programmingthomas.wordpress.com/2013/04/03/n-sided-shapes/ + var sideCount = sideCountNumerator * sideCountDenominator; + var decimalSides = sideCountNumerator / sideCountDenominator; + var interiorAngleDegrees = (180 * (decimalSides - 2)) / decimalSides; + var interiorAngle = Math.PI - Math.PI * interiorAngleDegrees / 180; // convert to radians + c.save(); + c.beginPath(); + c.translate(startX, startY); + c.moveTo(0,0); + for (var i = 0; i < sideCount; i++) { + c.lineTo(sideLength,0); + c.translate(sideLength,0); + c.rotate(interiorAngle); + } + //c.stroke(); + c.fill(); + c.restore(); + + }; + + pJS.fn.vendors.exportImg = function(){ + window.open(pJS.canvas.el.toDataURL('image/png'), '_blank'); + }; + + + pJS.fn.vendors.loadImg = function(type){ + + pJS.tmp.img_error = undefined; + + if(pJS.particles.shape.image.src != ''){ + + if(type == 'svg'){ + + var xhr = new XMLHttpRequest(); + xhr.open('GET', pJS.particles.shape.image.src); + xhr.onreadystatechange = function (data) { + if(xhr.readyState == 4){ + if(xhr.status == 200){ + pJS.tmp.source_svg = data.currentTarget.response; + pJS.fn.vendors.checkBeforeDraw(); + }else{ + console.log('Error pJS - Image not found'); + pJS.tmp.img_error = true; + } + } + } + xhr.send(); + + }else{ + + var img = new Image(); + img.addEventListener('load', function(){ + pJS.tmp.img_obj = img; + pJS.fn.vendors.checkBeforeDraw(); + }); + img.src = pJS.particles.shape.image.src; + + } + + }else{ + console.log('Error pJS - No image.src'); + pJS.tmp.img_error = true; + } + + }; + + + pJS.fn.vendors.draw = function(){ + + if(pJS.particles.shape.type == 'image'){ + + if(pJS.tmp.img_type == 'svg'){ + + if(pJS.tmp.count_svg >= pJS.particles.number.value){ + pJS.fn.particlesDraw(); + if(!pJS.particles.move.enable) cancelRequestAnimFrame(pJS.fn.drawAnimFrame); + else pJS.fn.drawAnimFrame = requestAnimFrame(pJS.fn.vendors.draw); + }else{ + //console.log('still loading...'); + if(!pJS.tmp.img_error) pJS.fn.drawAnimFrame = requestAnimFrame(pJS.fn.vendors.draw); + } + + }else{ + + if(pJS.tmp.img_obj != undefined){ + pJS.fn.particlesDraw(); + if(!pJS.particles.move.enable) cancelRequestAnimFrame(pJS.fn.drawAnimFrame); + else pJS.fn.drawAnimFrame = requestAnimFrame(pJS.fn.vendors.draw); + }else{ + if(!pJS.tmp.img_error) pJS.fn.drawAnimFrame = requestAnimFrame(pJS.fn.vendors.draw); + } + + } + + }else{ + pJS.fn.particlesDraw(); + if(!pJS.particles.move.enable) cancelRequestAnimFrame(pJS.fn.drawAnimFrame); + else pJS.fn.drawAnimFrame = requestAnimFrame(pJS.fn.vendors.draw); + } + + }; + + + pJS.fn.vendors.checkBeforeDraw = function(){ + + // if shape is image + if(pJS.particles.shape.type == 'image'){ + + if(pJS.tmp.img_type == 'svg' && pJS.tmp.source_svg == undefined){ + pJS.tmp.checkAnimFrame = requestAnimFrame(check); + }else{ + //console.log('images loaded! cancel check'); + cancelRequestAnimFrame(pJS.tmp.checkAnimFrame); + if(!pJS.tmp.img_error){ + pJS.fn.vendors.init(); + pJS.fn.vendors.draw(); + } + + } + + }else{ + pJS.fn.vendors.init(); + pJS.fn.vendors.draw(); + } + + }; + + + pJS.fn.vendors.init = function(){ + + /* init canvas + particles */ + pJS.fn.retinaInit(); + pJS.fn.canvasInit(); + pJS.fn.canvasSize(); + pJS.fn.canvasPaint(); + pJS.fn.particlesCreate(); + pJS.fn.vendors.densityAutoParticles(); + + /* particles.line_linked - convert hex colors to rgb */ + pJS.particles.line_linked.color_rgb_line = hexToRgb(pJS.particles.line_linked.color); + + }; + + + pJS.fn.vendors.start = function(){ + + if(isInArray('image', pJS.particles.shape.type)){ + pJS.tmp.img_type = pJS.particles.shape.image.src.substr(pJS.particles.shape.image.src.length - 3); + pJS.fn.vendors.loadImg(pJS.tmp.img_type); + }else{ + pJS.fn.vendors.checkBeforeDraw(); + } + + }; + + + + + /* ---------- pJS - start ------------ */ + + + pJS.fn.vendors.eventsListeners(); + + pJS.fn.vendors.start(); + + + +}; + +/* ---------- global functions - vendors ------------ */ + +Object.deepExtend = function(destination, source) { + for (var property in source) { + if (source[property] && source[property].constructor && + source[property].constructor === Object) { + destination[property] = destination[property] || {}; + arguments.callee(destination[property], source[property]); + } else { + destination[property] = source[property]; + } + } + return destination; +}; + +window.requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback){ + window.setTimeout(callback, 1000 / 60); + }; +})(); + +window.cancelRequestAnimFrame = ( function() { + return window.cancelAnimationFrame || + window.webkitCancelRequestAnimationFrame || + window.mozCancelRequestAnimationFrame || + window.oCancelRequestAnimationFrame || + window.msCancelRequestAnimationFrame || + clearTimeout +} )(); + +function hexToRgb(hex){ + // By Tim Down - http://stackoverflow.com/a/5624139/3493650 + // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") + var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + hex = hex.replace(shorthandRegex, function(m, r, g, b) { + return r + r + g + g + b + b; + }); + var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result ? { + r: parseInt(result[1], 16), + g: parseInt(result[2], 16), + b: parseInt(result[3], 16) + } : null; +}; + +function clamp(number, min, max) { + return Math.min(Math.max(number, min), max); +}; + +function isInArray(value, array) { + return array.indexOf(value) > -1; +} + + +/* ---------- particles.js functions - start ------------ */ + +window.pJSDom = []; + +window.particlesJS = function(tag_id, params){ + + //console.log(params); + + /* no string id? so it's object params, and set the id with default id */ + if(typeof(tag_id) != 'string'){ + params = tag_id; + tag_id = 'particles-js'; + } + + /* no id? set the id to default id */ + if(!tag_id){ + tag_id = 'particles-js'; + } + + /* pJS elements */ + var pJS_tag = document.getElementById(tag_id), + pJS_canvas_class = 'particles-js-canvas-el', + exist_canvas = pJS_tag.getElementsByClassName(pJS_canvas_class); + + /* remove canvas if exists into the pJS target tag */ + if(exist_canvas.length){ + while(exist_canvas.length > 0){ + pJS_tag.removeChild(exist_canvas[0]); + } + } + + /* create canvas element */ + var canvas_el = document.createElement('canvas'); + canvas_el.className = pJS_canvas_class; + + /* set size canvas */ + canvas_el.style.width = "100%"; + canvas_el.style.height = "100%"; + + /* append canvas */ + var canvas = document.getElementById(tag_id).appendChild(canvas_el); + + /* launch particle.js */ + if(canvas != null){ + pJSDom.push(new pJS(tag_id, params)); + } + +}; + +window.particlesJS.load = function(tag_id, path_config_json, callback){ + + /* load json config */ + var xhr = new XMLHttpRequest(); + xhr.open('GET', path_config_json); + xhr.onreadystatechange = function (data) { + if(xhr.readyState == 4){ + if(xhr.status == 200){ + var params = JSON.parse(data.currentTarget.response); + window.particlesJS(tag_id, params); + if(callback) callback(); + }else{ + console.log('Error pJS - XMLHttpRequest status: '+xhr.status); + console.log('Error pJS - File config not found'); + } + } + }; + xhr.send(); + +}; \ No newline at end of file diff --git a/styles/Milk_v2/template/profilefields/bool.html b/styles/Milk_v2/template/profilefields/bool.html new file mode 100644 index 0000000..f1d7ba7 --- /dev/null +++ b/styles/Milk_v2/template/profilefields/bool.html @@ -0,0 +1,7 @@ + + + + + checked="checked" /> + + diff --git a/styles/Milk_v2/template/profilefields/date.html b/styles/Milk_v2/template/profilefields/date.html new file mode 100644 index 0000000..5d5bc04 --- /dev/null +++ b/styles/Milk_v2/template/profilefields/date.html @@ -0,0 +1,5 @@ + + + + + diff --git a/styles/Milk_v2/template/profilefields/dropdown.html b/styles/Milk_v2/template/profilefields/dropdown.html new file mode 100644 index 0000000..243b703 --- /dev/null +++ b/styles/Milk_v2/template/profilefields/dropdown.html @@ -0,0 +1,5 @@ + + + diff --git a/styles/Milk_v2/template/profilefields/int.html b/styles/Milk_v2/template/profilefields/int.html new file mode 100644 index 0000000..a6f9a0a --- /dev/null +++ b/styles/Milk_v2/template/profilefields/int.html @@ -0,0 +1,3 @@ + + + diff --git a/styles/Milk_v2/template/profilefields/string.html b/styles/Milk_v2/template/profilefields/string.html new file mode 100644 index 0000000..cf457d3 --- /dev/null +++ b/styles/Milk_v2/template/profilefields/string.html @@ -0,0 +1,3 @@ + + + diff --git a/styles/Milk_v2/template/profilefields/text.html b/styles/Milk_v2/template/profilefields/text.html new file mode 100644 index 0000000..f54c639 --- /dev/null +++ b/styles/Milk_v2/template/profilefields/text.html @@ -0,0 +1,3 @@ + + + diff --git a/styles/Milk_v2/template/profilefields/url.html b/styles/Milk_v2/template/profilefields/url.html new file mode 100644 index 0000000..8dd3a90 --- /dev/null +++ b/styles/Milk_v2/template/profilefields/url.html @@ -0,0 +1,3 @@ + + + diff --git a/styles/Milk_v2/template/search_results.html b/styles/Milk_v2/template/search_results.html new file mode 100644 index 0000000..a6b15d5 --- /dev/null +++ b/styles/Milk_v2/template/search_results.html @@ -0,0 +1,258 @@ + + + + +

{SEARCH_TITLE}{SEARCH_MATCHES}{L_COLON} {SEARCH_WORDS}

+

{L_SEARCHED_QUERY}{L_COLON} {SEARCHED_QUERY}

+

{L_IGNORED_TERMS}{L_COLON} {IGNORED_WORDS}

+

{L_PHRASE_SEARCH_DISABLED}

+ + + + + + + + + + +
+ + + + + + + + +
+ + + + + +
+ +
+
    +
  • +
    +
    {L_TOPICS}
    +
    {L_REPLIES}
    +
    {L_VIEWS}
    +
    {L_LAST_POST}
    +
    +
  • +
+
    + + + +
  • +
    + style="background-image: url({T_ICONS_PATH}{searchresults.TOPIC_ICON_IMG}); background-repeat: no-repeat;" title="{searchresults.TOPIC_FOLDER_IMG_ALT}"> + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {searchresults.TOPIC_TITLE} +
    + + + + + + +
    + + + {% EVENT topiclist_row_topic_by_author_before %} + {L_POST_BY_AUTHOR} {searchresults.TOPIC_AUTHOR_FULL} » {searchresults.FIRST_POST_TIME} » {L_IN} {searchresults.FORUM_TITLE} + {% EVENT topiclist_row_topic_by_author_after %} +
    + + + + + + + +
    + +
    {searchresults.TOPIC_REPLIES} {L_REPLIES}
    +
    {searchresults.TOPIC_VIEWS} {L_VIEWS}
    +
    + {L_LAST_POST} {L_POST_BY_AUTHOR} {searchresults.LAST_POST_AUTHOR_FULL} + + + {VIEW_LATEST_POST} + + +
    {searchresults.LAST_POST_TIME} +
    +
    +
    +
  • + + +
+ +
+
+ +
+
+ {L_NO_SEARCH_RESULTS} +
+
+ + + +
+ + + +
+
+ + +
+ {searchresults.L_IGNORE_POST} +
+ +
+ +
{L_POST_BY_AUTHOR} {searchresults.POST_AUTHOR_FULL}
+
{searchresults.POST_DATE}
+
{L_FORUM}{L_COLON} {searchresults.FORUM_TITLE}
+
{L_TOPIC}{L_COLON} {searchresults.TOPIC_TITLE}
+ +
{L_REPLIES}{L_COLON} {searchresults.TOPIC_REPLIES}
+
{L_VIEWS}{L_COLON} {searchresults.TOPIC_VIEWS}
+ +
+ +
+

{searchresults.POST_SUBJECT}

+
{searchresults.MESSAGE}
+ +
+ + + + + + +
+
+ + +
+
+ {L_NO_SEARCH_RESULTS} +
+
+ +

+ + +
+ +
+ +
+ + + +
+ + + + diff --git a/styles/Milk_v2/template/sidebar_left.html b/styles/Milk_v2/template/sidebar_left.html new file mode 100644 index 0000000..69e7e79 --- /dev/null +++ b/styles/Milk_v2/template/sidebar_left.html @@ -0,0 +1 @@ +{STYLE_SETTINGS_HTML_1} \ No newline at end of file diff --git a/styles/Milk_v2/template/sidebar_right.html b/styles/Milk_v2/template/sidebar_right.html new file mode 100644 index 0000000..16967df --- /dev/null +++ b/styles/Milk_v2/template/sidebar_right.html @@ -0,0 +1,123 @@ + diff --git a/styles/Milk_v2/template/simple_header.html b/styles/Milk_v2/template/simple_header.html new file mode 100644 index 0000000..7a4cf6a --- /dev/null +++ b/styles/Milk_v2/template/simple_header.html @@ -0,0 +1,85 @@ + + + + + + +{META} +{SITENAME} • <!-- IF S_IN_MCP -->{L_MCP} • <!-- ELSEIF S_IN_UCP -->{L_UCP} • <!-- ENDIF -->{PAGE_TITLE} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{$STYLESHEETS} + + + + + + + + + +
+ +
diff --git a/styles/Milk_v2/template/tooltipster.bundle.min.js b/styles/Milk_v2/template/tooltipster.bundle.min.js new file mode 100644 index 0000000..87cbf09 --- /dev/null +++ b/styles/Milk_v2/template/tooltipster.bundle.min.js @@ -0,0 +1,2 @@ +/*! tooltipster v4.1.6 */!function(a,b){"function"==typeof define&&define.amd?define(["jquery"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("jquery")):b(jQuery)}(this,function(a){function b(a){this.$container,this.constraints=null,this.__$tooltip,this.__init(a)}function c(b,c){var d=!0;return a.each(b,function(a,e){return void 0===c[a]||b[a]!==c[a]?(d=!1,!1):void 0}),d}function d(b){var c=b.attr("id"),d=c?h.window.document.getElementById(c):null;return d?d===b[0]:a.contains(h.window.document.body,b[0])}function e(){if(!g)return!1;var a=g.document.body||g.document.documentElement,b=a.style,c="transition",d=["Moz","Webkit","Khtml","O","ms"];if("string"==typeof b[c])return!0;c=c.charAt(0).toUpperCase()+c.substr(1);for(var e=0;e0?e=c.__plugins[d]:a.each(c.__plugins,function(a,b){return b.name.substring(b.name.length-d.length-1)=="."+d?(e=b,!1):void 0}),e}if(b.name.indexOf(".")<0)throw new Error("Plugins must be namespaced");return c.__plugins[b.name]=b,b.core&&c.__bridge(b.core,c,b.name),this},_trigger:function(){var a=Array.prototype.slice.apply(arguments);return"string"==typeof a[0]&&(a[0]={type:a[0]}),this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate,a),this.__$emitterPublic.trigger.apply(this.__$emitterPublic,a),this},instances:function(b){var c=[],d=b||".tooltipstered";return a(d).each(function(){var b=a(this),d=b.data("tooltipster-ns");d&&a.each(d,function(a,d){c.push(b.data(d))})}),c},instancesLatest:function(){return this.__instancesLatestArr},off:function(){return this.__$emitterPublic.off.apply(this.__$emitterPublic,Array.prototype.slice.apply(arguments)),this},on:function(){return this.__$emitterPublic.on.apply(this.__$emitterPublic,Array.prototype.slice.apply(arguments)),this},one:function(){return this.__$emitterPublic.one.apply(this.__$emitterPublic,Array.prototype.slice.apply(arguments)),this},origins:function(b){var c=b?b+" ":"";return a(c+".tooltipstered").toArray()},setDefaults:function(b){return a.extend(f,b),this},triggerHandler:function(){return this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic,Array.prototype.slice.apply(arguments)),this}},a.tooltipster=new i,a.Tooltipster=function(b,c){this.__callbacks={close:[],open:[]},this.__closingTime,this.__Content,this.__contentBcr,this.__destroyed=!1,this.__destroying=!1,this.__$emitterPrivate=a({}),this.__$emitterPublic=a({}),this.__enabled=!0,this.__garbageCollector,this.__Geometry,this.__lastPosition,this.__namespace="tooltipster-"+Math.round(1e6*Math.random()),this.__options,this.__$originParents,this.__pointerIsOverOrigin=!1,this.__previousThemes=[],this.__state="closed",this.__timeouts={close:[],open:null},this.__touchEvents=[],this.__tracker=null,this._$origin,this._$tooltip,this.__init(b,c)},a.Tooltipster.prototype={__init:function(b,c){var d=this;if(d._$origin=a(b),d.__options=a.extend(!0,{},f,c),d.__optionsFormat(),!h.IE||h.IE>=d.__options.IEmin){var e=null;if(void 0===d._$origin.data("tooltipster-initialTitle")&&(e=d._$origin.attr("title"),void 0===e&&(e=null),d._$origin.data("tooltipster-initialTitle",e)),null!==d.__options.content)d.__contentSet(d.__options.content);else{var g,i=d._$origin.attr("data-tooltip-content");i&&(g=a(i)),g&&g[0]?d.__contentSet(g.first()):d.__contentSet(e)}d._$origin.removeAttr("title").addClass("tooltipstered"),d.__prepareOrigin(),d.__prepareGC(),a.each(d.__options.plugins,function(a,b){d._plug(b)}),h.hasTouchCapability&&a("body").on("touchmove."+d.__namespace+"-triggerOpen",function(a){d._touchRecordEvent(a)}),d._on("created",function(){d.__prepareTooltip()})._on("repositioned",function(a){d.__lastPosition=a.position})}else d.__options.disabled=!0},__contentInsert:function(){var a=this,b=a._$tooltip.find(".tooltipster-content"),c=a.__Content,d=function(a){c=a};return a._trigger({type:"format",content:a.__Content,format:d}),a.__options.functionFormat&&(c=a.__options.functionFormat.call(a,a,{origin:a._$origin[0]},a.__Content)),"string"!=typeof c||a.__options.contentAsHTML?b.empty().append(c):b.text(c),a},__contentSet:function(b){return b instanceof a&&this.__options.contentCloning&&(b=b.clone(!0)),this.__Content=b,this._trigger({type:"updated",content:b}),this},__destroyError:function(){throw new Error("This tooltip has been destroyed and cannot execute your method call.")},__geometry:function(){var b=this,c=b._$origin,d=b._$origin.is("area");if(d){var e=b._$origin.parent().attr("name");c=a('img[usemap="#'+e+'"]')}var f=c[0].getBoundingClientRect(),g=a(h.window.document),i=a(h.window),j=c,k={available:{document:null,window:null},document:{size:{height:g.height(),width:g.width()}},window:{scroll:{left:h.window.scrollX||h.window.document.documentElement.scrollLeft,top:h.window.scrollY||h.window.document.documentElement.scrollTop},size:{height:i.height(),width:i.width()}},origin:{fixedLineage:!1,offset:{},size:{height:f.bottom-f.top,width:f.right-f.left},usemapImage:d?c[0]:null,windowOffset:{bottom:f.bottom,left:f.left,right:f.right,top:f.top}}};if(d){var l=b._$origin.attr("shape"),m=b._$origin.attr("coords");if(m&&(m=m.split(","),a.map(m,function(a,b){m[b]=parseInt(a)})),"default"!=l)switch(l){case"circle":var n=m[0],o=m[1],p=m[2],q=o-p,r=n-p;k.origin.size.height=2*p,k.origin.size.width=k.origin.size.height,k.origin.windowOffset.left+=r,k.origin.windowOffset.top+=q;break;case"rect":var s=m[0],t=m[1],u=m[2],v=m[3];k.origin.size.height=v-t,k.origin.size.width=u-s,k.origin.windowOffset.left+=s,k.origin.windowOffset.top+=t;break;case"poly":for(var w=0,x=0,y=0,z=0,A="even",B=0;By&&(y=C,0===B&&(w=y)),w>C&&(w=C),A="odd"):(C>z&&(z=C,1==B&&(x=z)),x>C&&(x=C),A="even")}k.origin.size.height=z-x,k.origin.size.width=y-w,k.origin.windowOffset.left+=w,k.origin.windowOffset.top+=x}}var D=function(a){k.origin.size.height=a.height,k.origin.windowOffset.left=a.left,k.origin.windowOffset.top=a.top,k.origin.size.width=a.width};for(b._trigger({type:"geometry",edit:D,geometry:{height:k.origin.size.height,left:k.origin.windowOffset.left,top:k.origin.windowOffset.top,width:k.origin.size.width}}),k.origin.windowOffset.right=k.origin.windowOffset.left+k.origin.size.width,k.origin.windowOffset.bottom=k.origin.windowOffset.top+k.origin.size.height,k.origin.offset.left=k.origin.windowOffset.left+k.window.scroll.left,k.origin.offset.top=k.origin.windowOffset.top+k.window.scroll.top,k.origin.offset.bottom=k.origin.offset.top+k.origin.size.height,k.origin.offset.right=k.origin.offset.left+k.origin.size.width,k.available.document={bottom:{height:k.document.size.height-k.origin.offset.bottom,width:k.document.size.width},left:{height:k.document.size.height,width:k.origin.offset.left},right:{height:k.document.size.height,width:k.document.size.width-k.origin.offset.right},top:{height:k.origin.offset.top,width:k.document.size.width}},k.available.window={bottom:{height:Math.max(k.window.size.height-Math.max(k.origin.windowOffset.bottom,0),0),width:k.window.size.width},left:{height:k.window.size.height,width:Math.max(k.origin.windowOffset.left,0)},right:{height:k.window.size.height,width:Math.max(k.window.size.width-Math.max(k.origin.windowOffset.right,0),0)},top:{height:Math.max(k.origin.windowOffset.top,0),width:k.window.size.width}};"html"!=j[0].tagName.toLowerCase();){if("fixed"==j.css("position")){k.origin.fixedLineage=!0;break}j=j.parent()}return k},__optionsFormat:function(){return"number"==typeof this.__options.animationDuration&&(this.__options.animationDuration=[this.__options.animationDuration,this.__options.animationDuration]),"number"==typeof this.__options.delay&&(this.__options.delay=[this.__options.delay,this.__options.delay]),"number"==typeof this.__options.delayTouch&&(this.__options.delayTouch=[this.__options.delayTouch,this.__options.delayTouch]),"string"==typeof this.__options.theme&&(this.__options.theme=[this.__options.theme]),"string"==typeof this.__options.parent&&(this.__options.parent=a(this.__options.parent)),"hover"==this.__options.trigger?(this.__options.triggerOpen={mouseenter:!0,touchstart:!0},this.__options.triggerClose={mouseleave:!0,originClick:!0,touchleave:!0}):"click"==this.__options.trigger&&(this.__options.triggerOpen={click:!0,tap:!0},this.__options.triggerClose={click:!0,tap:!0}),this._trigger("options"),this},__prepareGC:function(){var b=this;return b.__options.selfDestruction?b.__garbageCollector=setInterval(function(){var c=(new Date).getTime();b.__touchEvents=a.grep(b.__touchEvents,function(a,b){return c-a.time>6e4}),d(b._$origin)||b.destroy()},2e4):clearInterval(b.__garbageCollector),b},__prepareOrigin:function(){var a=this;if(a._$origin.off("."+a.__namespace+"-triggerOpen"),h.hasTouchCapability&&a._$origin.on("touchstart."+a.__namespace+"-triggerOpen touchend."+a.__namespace+"-triggerOpen touchcancel."+a.__namespace+"-triggerOpen",function(b){a._touchRecordEvent(b)}),a.__options.triggerOpen.click||a.__options.triggerOpen.tap&&h.hasTouchCapability){var b="";a.__options.triggerOpen.click&&(b+="click."+a.__namespace+"-triggerOpen "),a.__options.triggerOpen.tap&&h.hasTouchCapability&&(b+="touchend."+a.__namespace+"-triggerOpen"),a._$origin.on(b,function(b){a._touchIsMeaningfulEvent(b)&&a._open(b)})}if(a.__options.triggerOpen.mouseenter||a.__options.triggerOpen.touchstart&&h.hasTouchCapability){var b="";a.__options.triggerOpen.mouseenter&&(b+="mouseenter."+a.__namespace+"-triggerOpen "),a.__options.triggerOpen.touchstart&&h.hasTouchCapability&&(b+="touchstart."+a.__namespace+"-triggerOpen"),a._$origin.on(b,function(b){!a._touchIsTouchEvent(b)&&a._touchIsEmulatedEvent(b)||(a.__pointerIsOverOrigin=!0,a._openShortly(b))})}if(a.__options.triggerClose.mouseleave||a.__options.triggerClose.touchleave&&h.hasTouchCapability){var b="";a.__options.triggerClose.mouseleave&&(b+="mouseleave."+a.__namespace+"-triggerOpen "),a.__options.triggerClose.touchleave&&h.hasTouchCapability&&(b+="touchend."+a.__namespace+"-triggerOpen touchcancel."+a.__namespace+"-triggerOpen"),a._$origin.on(b,function(b){a._touchIsMeaningfulEvent(b)&&(a.__pointerIsOverOrigin=!1)})}return a},__prepareTooltip:function(){var b=this,c=b.__options.interactive?"auto":"";return b._$tooltip.attr("id",b.__namespace).css({"pointer-events":c,zIndex:b.__options.zIndex}),a.each(b.__previousThemes,function(a,c){b._$tooltip.removeClass(c)}),a.each(b.__options.theme,function(a,c){b._$tooltip.addClass(c)}),b.__previousThemes=a.merge([],b.__options.theme),b},__scrollHandler:function(b){var c=this;if(c.__options.triggerClose.scroll)c._close(b);else{if(b.target===h.window.document)c.__Geometry.origin.fixedLineage||c.__options.repositionOnScroll&&c.reposition(b);else{var d=c.__geometry(),e=!1;if("fixed"!=c._$origin.css("position")&&c.__$originParents.each(function(b,c){var f=a(c),g=f.css("overflow-x"),h=f.css("overflow-y");if("visible"!=g||"visible"!=h){var i=c.getBoundingClientRect();if("visible"!=g&&(d.origin.windowOffset.lefti.right))return e=!0,!1;if("visible"!=h&&(d.origin.windowOffset.topi.bottom))return e=!0,!1}return"fixed"==f.css("position")?!1:void 0}),e)c._$tooltip.css("visibility","hidden");else if(c._$tooltip.css("visibility","visible"),c.__options.repositionOnScroll)c.reposition(b);else{var f=d.origin.offset.left-c.__Geometry.origin.offset.left,g=d.origin.offset.top-c.__Geometry.origin.offset.top;c._$tooltip.css({left:c.__lastPosition.coord.left+f,top:c.__lastPosition.coord.top+g})}}c._trigger({type:"scroll",event:b})}return c},__stateSet:function(a){return this.__state=a,this._trigger({type:"state",state:a}),this},__timeoutsClear:function(){return clearTimeout(this.__timeouts.open),this.__timeouts.open=null,a.each(this.__timeouts.close,function(a,b){clearTimeout(b)}),this.__timeouts.close=[],this},__trackerStart:function(){var a=this,b=a._$tooltip.find(".tooltipster-content");return a.__options.trackTooltip&&(a.__contentBcr=b[0].getBoundingClientRect()),a.__tracker=setInterval(function(){if(d(a._$origin)&&d(a._$tooltip)){if(a.__options.trackOrigin){var e=a.__geometry(),f=!1;c(e.origin.size,a.__Geometry.origin.size)&&(a.__Geometry.origin.fixedLineage?c(e.origin.windowOffset,a.__Geometry.origin.windowOffset)&&(f=!0):c(e.origin.offset,a.__Geometry.origin.offset)&&(f=!0)),f||(a.__options.triggerClose.mouseleave?a._close():a.reposition())}if(a.__options.trackTooltip){var g=b[0].getBoundingClientRect();g.height===a.__contentBcr.height&&g.width===a.__contentBcr.width||(a.reposition(),a.__contentBcr=g)}}else a._close()},a.__options.trackerInterval),a},_close:function(b,c){var d=this,e=!0;if(d._trigger({type:"close",event:b,stop:function(){e=!1}}),e||d.__destroying){c&&d.__callbacks.close.push(c),d.__callbacks.open=[],d.__timeoutsClear();var f=function(){a.each(d.__callbacks.close,function(a,c){c.call(d,d,{event:b,origin:d._$origin[0]})}),d.__callbacks.close=[]};if("closed"!=d.__state){var g=!0,i=new Date,j=i.getTime(),k=j+d.__options.animationDuration[1];if("disappearing"==d.__state&&k>d.__closingTime&&(g=!1),g){d.__closingTime=k,"disappearing"!=d.__state&&d.__stateSet("disappearing");var l=function(){clearInterval(d.__tracker),d._trigger({type:"closing",event:b}),d._$tooltip.off("."+d.__namespace+"-triggerClose").removeClass("tooltipster-dying"),a(h.window).off("."+d.__namespace+"-triggerClose"),d.__$originParents.each(function(b,c){a(c).off("scroll."+d.__namespace+"-triggerClose")}),d.__$originParents=null,a("body").off("."+d.__namespace+"-triggerClose"),d._$origin.off("."+d.__namespace+"-triggerClose"),d._off("dismissable"),d.__stateSet("closed"),d._trigger({type:"after",event:b}),d.__options.functionAfter&&d.__options.functionAfter.call(d,d,{event:b,origin:d._$origin[0]}),f()};h.hasTransitions?(d._$tooltip.css({"-moz-animation-duration":d.__options.animationDuration[1]+"ms","-ms-animation-duration":d.__options.animationDuration[1]+"ms","-o-animation-duration":d.__options.animationDuration[1]+"ms","-webkit-animation-duration":d.__options.animationDuration[1]+"ms","animation-duration":d.__options.animationDuration[1]+"ms","transition-duration":d.__options.animationDuration[1]+"ms"}),d._$tooltip.clearQueue().removeClass("tooltipster-show").addClass("tooltipster-dying"),d.__options.animationDuration[1]>0&&d._$tooltip.delay(d.__options.animationDuration[1]),d._$tooltip.queue(l)):d._$tooltip.stop().fadeOut(d.__options.animationDuration[1],l)}}else f()}return d},_off:function(){return this.__$emitterPrivate.off.apply(this.__$emitterPrivate,Array.prototype.slice.apply(arguments)),this},_on:function(){return this.__$emitterPrivate.on.apply(this.__$emitterPrivate,Array.prototype.slice.apply(arguments)),this},_one:function(){return this.__$emitterPrivate.one.apply(this.__$emitterPrivate,Array.prototype.slice.apply(arguments)),this},_open:function(b,c){var e=this;if(!e.__destroying&&d(e._$origin)&&e.__enabled){var f=!0;if("closed"==e.__state&&(e._trigger({type:"before",event:b,stop:function(){f=!1}}),f&&e.__options.functionBefore&&(f=e.__options.functionBefore.call(e,e,{event:b,origin:e._$origin[0]}))),f!==!1&&null!==e.__Content){c&&e.__callbacks.open.push(c),e.__callbacks.close=[],e.__timeoutsClear();var g,i=function(){"stable"!=e.__state&&e.__stateSet("stable"),a.each(e.__callbacks.open,function(a,b){b.call(e,e,{origin:e._$origin[0],tooltip:e._$tooltip[0]})}),e.__callbacks.open=[]};if("closed"!==e.__state)g=0,"disappearing"===e.__state?(e.__stateSet("appearing"),h.hasTransitions?(e._$tooltip.clearQueue().removeClass("tooltipster-dying").addClass("tooltipster-show"),e.__options.animationDuration[0]>0&&e._$tooltip.delay(e.__options.animationDuration[0]),e._$tooltip.queue(i)):e._$tooltip.stop().fadeIn(i)):"stable"==e.__state&&i();else{if(e.__stateSet("appearing"),g=e.__options.animationDuration[0],e.__contentInsert(),e.reposition(b,!0),h.hasTransitions?(e._$tooltip.addClass("tooltipster-"+e.__options.animation).addClass("tooltipster-initial").css({"-moz-animation-duration":e.__options.animationDuration[0]+"ms","-ms-animation-duration":e.__options.animationDuration[0]+"ms","-o-animation-duration":e.__options.animationDuration[0]+"ms","-webkit-animation-duration":e.__options.animationDuration[0]+"ms","animation-duration":e.__options.animationDuration[0]+"ms","transition-duration":e.__options.animationDuration[0]+"ms"}),setTimeout(function(){"closed"!=e.__state&&(e._$tooltip.addClass("tooltipster-show").removeClass("tooltipster-initial"),e.__options.animationDuration[0]>0&&e._$tooltip.delay(e.__options.animationDuration[0]),e._$tooltip.queue(i))},0)):e._$tooltip.css("display","none").fadeIn(e.__options.animationDuration[0],i),e.__trackerStart(),a(h.window).on("resize."+e.__namespace+"-triggerClose",function(b){var c=a(document.activeElement);(c.is("input")||c.is("textarea"))&&a.contains(e._$tooltip[0],c[0])||e.reposition(b)}).on("scroll."+e.__namespace+"-triggerClose",function(a){e.__scrollHandler(a)}),e.__$originParents=e._$origin.parents(),e.__$originParents.each(function(b,c){a(c).on("scroll."+e.__namespace+"-triggerClose",function(a){e.__scrollHandler(a)})}),e.__options.triggerClose.mouseleave||e.__options.triggerClose.touchleave&&h.hasTouchCapability){e._on("dismissable",function(a){a.dismissable?a.delay?(m=setTimeout(function(){e._close(a.event)},a.delay),e.__timeouts.close.push(m)):e._close(a):clearTimeout(m)});var j=e._$origin,k="",l="",m=null;e.__options.interactive&&(j=j.add(e._$tooltip)),e.__options.triggerClose.mouseleave&&(k+="mouseenter."+e.__namespace+"-triggerClose ",l+="mouseleave."+e.__namespace+"-triggerClose "),e.__options.triggerClose.touchleave&&h.hasTouchCapability&&(k+="touchstart."+e.__namespace+"-triggerClose",l+="touchend."+e.__namespace+"-triggerClose touchcancel."+e.__namespace+"-triggerClose"),j.on(l,function(a){if(e._touchIsTouchEvent(a)||!e._touchIsEmulatedEvent(a)){var b="mouseleave"==a.type?e.__options.delay:e.__options.delayTouch;e._trigger({delay:b[1],dismissable:!0,event:a,type:"dismissable"})}}).on(k,function(a){!e._touchIsTouchEvent(a)&&e._touchIsEmulatedEvent(a)||e._trigger({dismissable:!1,event:a,type:"dismissable"})})}e.__options.triggerClose.originClick&&e._$origin.on("click."+e.__namespace+"-triggerClose",function(a){e._touchIsTouchEvent(a)||e._touchIsEmulatedEvent(a)||e._close(a)}),(e.__options.triggerClose.click||e.__options.triggerClose.tap&&h.hasTouchCapability)&&setTimeout(function(){if("closed"!=e.__state){var b="";e.__options.triggerClose.click&&(b+="click."+e.__namespace+"-triggerClose "),e.__options.triggerClose.tap&&h.hasTouchCapability&&(b+="touchend."+e.__namespace+"-triggerClose"),a("body").on(b,function(b){e._touchIsMeaningfulEvent(b)&&(e._touchRecordEvent(b),e.__options.interactive&&a.contains(e._$tooltip[0],b.target)||e._close(b))}),e.__options.triggerClose.tap&&h.hasTouchCapability&&a("body").on("touchstart."+e.__namespace+"-triggerClose",function(a){e._touchRecordEvent(a)})}},0),e._trigger("ready"),e.__options.functionReady&&e.__options.functionReady.call(e,e,{origin:e._$origin[0],tooltip:e._$tooltip[0]})}if(e.__options.timer>0){var m=setTimeout(function(){e._close()},e.__options.timer+g);e.__timeouts.close.push(m)}}}return e},_openShortly:function(a){var b=this,c=!0;if("stable"!=b.__state&&"appearing"!=b.__state&&!b.__timeouts.open&&(b._trigger({type:"start",event:a,stop:function(){c=!1}}),c)){var d=0==a.type.indexOf("touch")?b.__options.delayTouch:b.__options.delay;d[0]?b.__timeouts.open=setTimeout(function(){b.__timeouts.open=null,b.__pointerIsOverOrigin&&b._touchIsMeaningfulEvent(a)?(b._trigger("startend"),b._open(a)):b._trigger("startcancel")},d[0]):(b._trigger("startend"),b._open(a))}return b},_optionsExtract:function(b,c){var d=this,e=a.extend(!0,{},c),f=d.__options[b];return f||(f={},a.each(c,function(a,b){var c=d.__options[a];void 0!==c&&(f[a]=c)})),a.each(e,function(b,c){void 0!==f[b]&&("object"!=typeof c||c instanceof Array||null==c||"object"!=typeof f[b]||f[b]instanceof Array||null==f[b]?e[b]=f[b]:a.extend(e[b],f[b]))}),e},_plug:function(b){var c=a.tooltipster._plugin(b);if(!c)throw new Error('The "'+b+'" plugin is not defined');return c.instance&&a.tooltipster.__bridge(c.instance,this,c.name),this},_touchIsEmulatedEvent:function(a){for(var b=!1,c=(new Date).getTime(),d=this.__touchEvents.length-1;d>=0;d--){var e=this.__touchEvents[d];if(!(c-e.time<500))break;e.target===a.target&&(b=!0)}return b},_touchIsMeaningfulEvent:function(a){return this._touchIsTouchEvent(a)&&!this._touchSwiped(a.target)||!this._touchIsTouchEvent(a)&&!this._touchIsEmulatedEvent(a)},_touchIsTouchEvent:function(a){return 0==a.type.indexOf("touch")},_touchRecordEvent:function(a){return this._touchIsTouchEvent(a)&&(a.time=(new Date).getTime(),this.__touchEvents.push(a)),this},_touchSwiped:function(a){for(var b=!1,c=this.__touchEvents.length-1;c>=0;c--){var d=this.__touchEvents[c];if("touchmove"==d.type){b=!0;break}if("touchstart"==d.type&&a===d.target)break}return b},_trigger:function(){var b=Array.prototype.slice.apply(arguments);return"string"==typeof b[0]&&(b[0]={type:b[0]}),b[0].instance=this,b[0].origin=this._$origin?this._$origin[0]:null,b[0].tooltip=this._$tooltip?this._$tooltip[0]:null,this.__$emitterPrivate.trigger.apply(this.__$emitterPrivate,b),a.tooltipster._trigger.apply(a.tooltipster,b),this.__$emitterPublic.trigger.apply(this.__$emitterPublic,b),this},_unplug:function(b){var c=this;if(c[b]){var d=a.tooltipster._plugin(b);d.instance&&a.each(d.instance,function(a,d){c[a]&&c[a].bridged===c[b]&&delete c[a]}),c[b].__destroy&&c[b].__destroy(),delete c[b]}return c},close:function(a){return this.__destroyed?this.__destroyError():this._close(null,a),this},content:function(a){var b=this;if(void 0===a)return b.__Content;if(b.__destroyed)b.__destroyError();else if(b.__contentSet(a),null!==b.__Content){if("closed"!==b.__state&&(b.__contentInsert(),b.reposition(),b.__options.updateAnimation))if(h.hasTransitions){var c=b.__options.updateAnimation;b._$tooltip.addClass("tooltipster-update-"+c),setTimeout(function(){"closed"!=b.__state&&b._$tooltip.removeClass("tooltipster-update-"+c)},1e3)}else b._$tooltip.fadeTo(200,.5,function(){"closed"!=b.__state&&b._$tooltip.fadeTo(200,1)})}else b._close();return b},destroy:function(){var b=this;return b.__destroyed?b.__destroyError():b.__destroying||(b.__destroying=!0,b._close(null,function(){b._trigger("destroy"),b.__destroying=!1,b.__destroyed=!0,b._$origin.removeData(b.__namespace).off("."+b.__namespace+"-triggerOpen"),a("body").off("."+b.__namespace+"-triggerOpen");var c=b._$origin.data("tooltipster-ns");if(c)if(1===c.length){var d=null;"previous"==b.__options.restoration?d=b._$origin.data("tooltipster-initialTitle"):"current"==b.__options.restoration&&(d="string"==typeof b.__Content?b.__Content:a("
").append(b.__Content).html()),d&&b._$origin.attr("title",d),b._$origin.removeClass("tooltipstered"),b._$origin.removeData("tooltipster-ns").removeData("tooltipster-initialTitle")}else c=a.grep(c,function(a,c){return a!==b.__namespace}),b._$origin.data("tooltipster-ns",c);b._trigger("destroyed"),b._off(),b.off(),b.__Content=null,b.__$emitterPrivate=null,b.__$emitterPublic=null,b.__options.parent=null,b._$origin=null,b._$tooltip=null,a.tooltipster.__instancesLatestArr=a.grep(a.tooltipster.__instancesLatestArr,function(a,c){return b!==a}),clearInterval(b.__garbageCollector)})),b},disable:function(){return this.__destroyed?(this.__destroyError(),this):(this._close(),this.__enabled=!1,this)},elementOrigin:function(){return this.__destroyed?void this.__destroyError():this._$origin[0]},elementTooltip:function(){return this._$tooltip?this._$tooltip[0]:null},enable:function(){return this.__enabled=!0,this},hide:function(a){return this.close(a)},instance:function(){return this},off:function(){return this.__destroyed||this.__$emitterPublic.off.apply(this.__$emitterPublic,Array.prototype.slice.apply(arguments)),this},on:function(){return this.__destroyed?this.__destroyError():this.__$emitterPublic.on.apply(this.__$emitterPublic,Array.prototype.slice.apply(arguments)),this},one:function(){return this.__destroyed?this.__destroyError():this.__$emitterPublic.one.apply(this.__$emitterPublic,Array.prototype.slice.apply(arguments)),this},open:function(a){return this.__destroyed||this.__destroying?this.__destroyError():this._open(null,a),this},option:function(b,c){return void 0===c?this.__options[b]:(this.__destroyed?this.__destroyError():(this.__options[b]=c,this.__optionsFormat(),a.inArray(b,["trigger","triggerClose","triggerOpen"])>=0&&this.__prepareOrigin(),"selfDestruction"===b&&this.__prepareGC()),this)},reposition:function(a,b){var c=this;return c.__destroyed?c.__destroyError():"closed"!=c.__state&&d(c._$origin)&&(b||d(c._$tooltip))&&(b||c._$tooltip.detach(),c.__Geometry=c.__geometry(),c._trigger({type:"reposition",event:a,helper:{geo:c.__Geometry}})),c},show:function(a){return this.open(a)},status:function(){return{destroyed:this.__destroyed,destroying:this.__destroying,enabled:this.__enabled,open:"closed"!==this.__state,state:this.__state}},triggerHandler:function(){return this.__destroyed?this.__destroyError():this.__$emitterPublic.triggerHandler.apply(this.__$emitterPublic,Array.prototype.slice.apply(arguments)),this}},a.fn.tooltipster=function(){var b=Array.prototype.slice.apply(arguments),c="You are using a single HTML element as content for several tooltips. You probably want to set the contentCloning option to TRUE.";if(0===this.length)return this;if("string"==typeof b[0]){var d="#*$~&";return this.each(function(){var e=a(this).data("tooltipster-ns"),f=e?a(this).data(e[0]):null;if(!f)throw new Error("You called Tooltipster's \""+b[0]+'" method on an uninitialized element');if("function"!=typeof f[b[0]])throw new Error('Unknown method "'+b[0]+'"');this.length>1&&"content"==b[0]&&(b[1]instanceof a||"object"==typeof b[1]&&null!=b[1]&&b[1].tagName)&&!f.__options.contentCloning&&f.__options.debug&&console.log(c);var g=f[b[0]](b[1],b[2]);return g!==f||"instance"===b[0]?(d=g,!1):void 0}),"#*$~&"!==d?d:this}a.tooltipster.__instancesLatestArr=[];var e=b[0]&&void 0!==b[0].multiple,g=e&&b[0].multiple||!e&&f.multiple,h=b[0]&&void 0!==b[0].content,i=h&&b[0].content||!h&&f.content,j=b[0]&&void 0!==b[0].contentCloning,k=j&&b[0].contentCloning||!j&&f.contentCloning,l=b[0]&&void 0!==b[0].debug,m=l&&b[0].debug||!l&&f.debug;return this.length>1&&(i instanceof a||"object"==typeof i&&null!=i&&i.tagName)&&!k&&m&&console.log(c),this.each(function(){var c=!1,d=a(this),e=d.data("tooltipster-ns"),f=null;e?g?c=!0:m&&(console.log("Tooltipster: one or more tooltips are already attached to the element below. Ignoring."),console.log(this)):c=!0,c&&(f=new a.Tooltipster(this,b[0]),e||(e=[]),e.push(f.__namespace),d.data("tooltipster-ns",e),d.data(f.__namespace,f),f.__options.functionInit&&f.__options.functionInit.call(f,f,{origin:this}),f._trigger("init")),a.tooltipster.__instancesLatestArr.push(f)}),this},b.prototype={__init:function(b){this.__$tooltip=b,this.__$tooltip.css({left:0,overflow:"hidden",position:"absolute",top:0}).find(".tooltipster-content").css("overflow","auto"),this.$container=a('
').append(this.__$tooltip).appendTo("body")},__forceRedraw:function(){var a=this.__$tooltip.parent();this.__$tooltip.detach(),this.__$tooltip.appendTo(a)},constrain:function(a,b){return this.constraints={width:a,height:b},this.__$tooltip.css({display:"block",height:"",overflow:"auto",width:a}),this},destroy:function(){this.__$tooltip.detach().find(".tooltipster-content").css({display:"",overflow:""}),this.$container.remove()},free:function(){return this.constraints=null,this.__$tooltip.css({display:"",height:"",overflow:"visible",width:""}),this},measure:function(){this.__forceRedraw();var a=this.__$tooltip[0].getBoundingClientRect(),b={size:{height:a.height||a.bottom,width:a.width||a.right}};if(this.constraints){var c=this.__$tooltip.find(".tooltipster-content"),d=this.__$tooltip.outerHeight(),e=c[0].getBoundingClientRect(),f={height:d<=this.constraints.height,width:a.width<=this.constraints.width&&e.width>=c[0].scrollWidth-1};b.fits=f.height&&f.width}return h.IE&&h.IE<=11&&b.size.width!==h.window.document.documentElement.clientWidth&&(b.size.width=Math.ceil(b.size.width)+1),b}};var j=navigator.userAgent.toLowerCase();-1!=j.indexOf("msie")?h.IE=parseInt(j.split("msie")[1]):-1!==j.toLowerCase().indexOf("trident")&&-1!==j.indexOf(" rv:11")?h.IE=11:-1!=j.toLowerCase().indexOf("edge/")&&(h.IE=parseInt(j.toLowerCase().split("edge/")[1]));var k="tooltipster.sideTip";return a.tooltipster._plugin({name:k,instance:{__defaults:function(){return{arrow:!0,distance:6,functionPosition:null,maxWidth:null,minIntersection:16,minWidth:0,position:null,side:"top",viewportAware:!0}},__init:function(a){var b=this;b.__instance=a,b.__namespace="tooltipster-sideTip-"+Math.round(1e6*Math.random()),b.__previousState="closed",b.__options,b.__optionsFormat(),b.__instance._on("state."+b.__namespace,function(a){"closed"==a.state?b.__close():"appearing"==a.state&&"closed"==b.__previousState&&b.__create(),b.__previousState=a.state}),b.__instance._on("options."+b.__namespace,function(){b.__optionsFormat()}),b.__instance._on("reposition."+b.__namespace,function(a){b.__reposition(a.event,a.helper)})},__close:function(){this.__instance.content()instanceof a&&this.__instance.content().detach(),this.__instance._$tooltip.remove(),this.__instance._$tooltip=null},__create:function(){var b=a('
');this.__options.arrow||b.find(".tooltipster-box").css("margin",0).end().find(".tooltipster-arrow").hide(),this.__options.minWidth&&b.css("min-width",this.__options.minWidth+"px"),this.__options.maxWidth&&b.css("max-width",this.__options.maxWidth+"px"),this.__instance._$tooltip=b,this.__instance._trigger("created")},__destroy:function(){this.__instance._off("."+self.__namespace)},__optionsFormat:function(){var b=this;if(b.__options=b.__instance._optionsExtract(k,b.__defaults()), +b.__options.position&&(b.__options.side=b.__options.position),"object"!=typeof b.__options.distance&&(b.__options.distance=[b.__options.distance]),b.__options.distance.length<4&&(void 0===b.__options.distance[1]&&(b.__options.distance[1]=b.__options.distance[0]),void 0===b.__options.distance[2]&&(b.__options.distance[2]=b.__options.distance[0]),void 0===b.__options.distance[3]&&(b.__options.distance[3]=b.__options.distance[1]),b.__options.distance={top:b.__options.distance[0],right:b.__options.distance[1],bottom:b.__options.distance[2],left:b.__options.distance[3]}),"string"==typeof b.__options.side){var c={top:"bottom",right:"left",bottom:"top",left:"right"};b.__options.side=[b.__options.side,c[b.__options.side]],"left"==b.__options.side[0]||"right"==b.__options.side[0]?b.__options.side.push("top","bottom"):b.__options.side.push("right","left")}6===a.tooltipster._env.IE&&b.__options.arrow!==!0&&(b.__options.arrow=!1)},__reposition:function(b,c){var d,e=this,f=e.__targetFind(c),g=[];e.__instance._$tooltip.detach();var h=e.__instance._$tooltip.clone(),i=a.tooltipster._getRuler(h),j=!1,k=e.__instance.option("animation");switch(k&&h.removeClass("tooltipster-"+k),a.each(["window","document"],function(d,k){var l=null;if(e.__instance._trigger({container:k,helper:c,satisfied:j,takeTest:function(a){l=a},results:g,type:"positionTest"}),1==l||0!=l&&0==j&&("window"!=k||e.__options.viewportAware))for(var d=0;d=h.outerSize.width&&c.geo.available[k][n].height>=h.outerSize.height?h.fits=!0:h.fits=!1:h.fits=p.fits,"window"==k&&(h.fits?"top"==n||"bottom"==n?h.whole=c.geo.origin.windowOffset.right>=e.__options.minIntersection&&c.geo.window.size.width-c.geo.origin.windowOffset.left>=e.__options.minIntersection:h.whole=c.geo.origin.windowOffset.bottom>=e.__options.minIntersection&&c.geo.window.size.height-c.geo.origin.windowOffset.top>=e.__options.minIntersection:h.whole=!1),g.push(h),h.whole)j=!0;else if("natural"==h.mode&&(h.fits||h.size.width<=c.geo.available[k][n].width))return!1}})}}),e.__instance._trigger({edit:function(a){g=a},event:b,helper:c,results:g,type:"positionTested"}),g.sort(function(a,b){if(a.whole&&!b.whole)return-1;if(!a.whole&&b.whole)return 1;if(a.whole&&b.whole){var c=e.__options.side.indexOf(a.side),d=e.__options.side.indexOf(b.side);return d>c?-1:c>d?1:"natural"==a.mode?-1:1}if(a.fits&&!b.fits)return-1;if(!a.fits&&b.fits)return 1;if(a.fits&&b.fits){var c=e.__options.side.indexOf(a.side),d=e.__options.side.indexOf(b.side);return d>c?-1:c>d?1:"natural"==a.mode?-1:1}return"document"==a.container&&"bottom"==a.side&&"natural"==a.mode?-1:1}),d=g[0],d.coord={},d.side){case"left":case"right":d.coord.top=Math.floor(d.target-d.size.height/2);break;case"bottom":case"top":d.coord.left=Math.floor(d.target-d.size.width/2)}switch(d.side){case"left":d.coord.left=c.geo.origin.windowOffset.left-d.outerSize.width;break;case"right":d.coord.left=c.geo.origin.windowOffset.right+d.distance.horizontal;break;case"top":d.coord.top=c.geo.origin.windowOffset.top-d.outerSize.height;break;case"bottom":d.coord.top=c.geo.origin.windowOffset.bottom+d.distance.vertical}"window"==d.container?"top"==d.side||"bottom"==d.side?d.coord.left<0?c.geo.origin.windowOffset.right-this.__options.minIntersection>=0?d.coord.left=0:d.coord.left=c.geo.origin.windowOffset.right-this.__options.minIntersection-1:d.coord.left>c.geo.window.size.width-d.size.width&&(c.geo.origin.windowOffset.left+this.__options.minIntersection<=c.geo.window.size.width?d.coord.left=c.geo.window.size.width-d.size.width:d.coord.left=c.geo.origin.windowOffset.left+this.__options.minIntersection+1-d.size.width):d.coord.top<0?c.geo.origin.windowOffset.bottom-this.__options.minIntersection>=0?d.coord.top=0:d.coord.top=c.geo.origin.windowOffset.bottom-this.__options.minIntersection-1:d.coord.top>c.geo.window.size.height-d.size.height&&(c.geo.origin.windowOffset.top+this.__options.minIntersection<=c.geo.window.size.height?d.coord.top=c.geo.window.size.height-d.size.height:d.coord.top=c.geo.origin.windowOffset.top+this.__options.minIntersection+1-d.size.height):(d.coord.left>c.geo.window.size.width-d.size.width&&(d.coord.left=c.geo.window.size.width-d.size.width),d.coord.left<0&&(d.coord.left=0)),e.__sideChange(h,d.side),c.tooltipClone=h[0],c.tooltipParent=e.__instance.option("parent").parent[0],c.mode=d.mode,c.whole=d.whole,c.origin=e.__instance._$origin[0],c.tooltip=e.__instance._$tooltip[0],delete d.container,delete d.fits,delete d.mode,delete d.outerSize,delete d.whole,d.distance=d.distance.horizontal||d.distance.vertical;var l=a.extend(!0,{},d);if(e.__instance._trigger({edit:function(a){d=a},event:b,helper:c,position:l,type:"position"}),e.__options.functionPosition){var m=e.__options.functionPosition.call(e,e.__instance,c,l);m&&(d=m)}i.destroy();var n,o;"top"==d.side||"bottom"==d.side?(n={prop:"left",val:d.target-d.coord.left},o=d.size.width-this.__options.minIntersection):(n={prop:"top",val:d.target-d.coord.top},o=d.size.height-this.__options.minIntersection),n.valo&&(n.val=o);var p;p=c.geo.origin.fixedLineage?c.geo.origin.windowOffset:{left:c.geo.origin.windowOffset.left+c.geo.window.scroll.left,top:c.geo.origin.windowOffset.top+c.geo.window.scroll.top},d.coord={left:p.left+(d.coord.left-c.geo.origin.windowOffset.left),top:p.top+(d.coord.top-c.geo.origin.windowOffset.top)},e.__sideChange(e.__instance._$tooltip,d.side),c.geo.origin.fixedLineage?e.__instance._$tooltip.css("position","fixed"):e.__instance._$tooltip.css("position",""),e.__instance._$tooltip.css({left:d.coord.left,top:d.coord.top,height:d.size.height,width:d.size.width}).find(".tooltipster-arrow").css({left:"",top:""}).css(n.prop,n.val),e.__instance._$tooltip.appendTo(e.__instance.option("parent")),e.__instance._trigger({type:"repositioned",event:b,position:d})},__sideChange:function(a,b){a.removeClass("tooltipster-bottom").removeClass("tooltipster-left").removeClass("tooltipster-right").removeClass("tooltipster-top").addClass("tooltipster-"+b)},__targetFind:function(a){var b={},c=this.__instance._$origin[0].getClientRects();if(c.length>1){var d=this.__instance._$origin.css("opacity");1==d&&(this.__instance._$origin.css("opacity",.99),c=this.__instance._$origin[0].getClientRects(),this.__instance._$origin.css("opacity",1))}if(c.length<2)b.top=Math.floor(a.geo.origin.windowOffset.left+a.geo.origin.size.width/2),b.bottom=b.top,b.left=Math.floor(a.geo.origin.windowOffset.top+a.geo.origin.size.height/2),b.right=b.left;else{var e=c[0];b.top=Math.floor(e.left+(e.right-e.left)/2),e=c.length>2?c[Math.ceil(c.length/2)-1]:c[0],b.right=Math.floor(e.top+(e.bottom-e.top)/2),e=c[c.length-1],b.bottom=Math.floor(e.left+(e.right-e.left)/2),e=c.length>2?c[Math.ceil((c.length+1)/2)-1]:c[c.length-1],b.left=Math.floor(e.top+(e.bottom-e.top)/2)}return b}}}),a}); \ No newline at end of file diff --git a/styles/Milk_v2/template/ucp_agreement.html b/styles/Milk_v2/template/ucp_agreement.html new file mode 100644 index 0000000..d3edb69 --- /dev/null +++ b/styles/Milk_v2/template/ucp_agreement.html @@ -0,0 +1,74 @@ + + + + + + + +
+

+ + {S_HIDDEN_FIELDS} +

+
+ +
+ + + +
+ +
+
+
+

{SITENAME} - {L_REGISTRATION}

+ +

{L_COPPA_BIRTHDAY}{L_TERMS_OF_USE}

+ +
+
+
+ +
+
+
+ + {L_COPPA_NO}  {L_COPPA_YES} + +   + + + {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} +
+
+
+
+ + + +
+
+
+

{SITENAME} - {AGREEMENT_TITLE}

+

{AGREEMENT_TEXT}

+
+
+
+ + + + diff --git a/styles/Milk_v2/template/ucp_auth_link_oauth.html b/styles/Milk_v2/template/ucp_auth_link_oauth.html new file mode 100644 index 0000000..60061a3 --- /dev/null +++ b/styles/Milk_v2/template/ucp_auth_link_oauth.html @@ -0,0 +1,29 @@ + +
+

{oauth.SERVICE_NAME}

+ +
+ +
+
{L_UCP_AUTH_LINK_ID}{L_COLON}
+
{oauth.UNIQUE_ID}
+
+
+
 
+
+
+ +
+
{L_UCP_AUTH_LINK_ASK}
+
+
+
 
+
+
+ +
+ {oauth.HIDDEN_FIELDS} + {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} +
+ diff --git a/styles/Milk_v2/template/ucp_main_front.html b/styles/Milk_v2/template/ucp_main_front.html new file mode 100644 index 0000000..55eb1fd --- /dev/null +++ b/styles/Milk_v2/template/ucp_main_front.html @@ -0,0 +1,82 @@ + + +

{L_TITLE}

+ +
+
+ +

{L_UCP_WELCOME}

+ + +

{L_IMPORTANT_NEWS}

+ + + + +

{L_YOUR_DETAILS}

+ + +
+ +
{L_JOINED}{L_COLON}
{JOINED}
+
{L_LAST_ACTIVE}{L_COLON}
{LAST_VISIT_YOU}
+
{L_TOTAL_POSTS}{L_COLON}
{POSTS} | {L_SEARCH_YOUR_POSTS}
({POSTS_DAY} / {POSTS_PCT}){POSTS}
+
{L_ACTIVE_IN_FORUM}{L_COLON}
{ACTIVE_FORUM}
({ACTIVE_FORUM_POSTS} / {ACTIVE_FORUM_PCT})
+
{L_ACTIVE_IN_TOPIC}{L_COLON}
{ACTIVE_TOPIC}
({ACTIVE_TOPIC_POSTS} / {ACTIVE_TOPIC_PCT})
+
{L_YOUR_WARNINGS}{L_COLON}
[{WARNINGS}]
+ +
+ + +
+
+ + diff --git a/styles/Milk_v2/template/ucp_notifications.html b/styles/Milk_v2/template/ucp_notifications.html new file mode 100644 index 0000000..e67d122 --- /dev/null +++ b/styles/Milk_v2/template/ucp_notifications.html @@ -0,0 +1,122 @@ + + +
+ +

{TITLE}

+
+
+ +

{TITLE_EXPLAIN}

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{L_NOTIFICATION_TYPE}{notification_methods.NAME}
{notification_types.GROUP_NAME}
+ {notification_types.NAME} +
   {notification_types.EXPLAIN} +
checked="checked" disabled="disabled" />
+
+ + +
+ +
+ +
+
    +
  • +
    +
    {L_NOTIFICATIONS}
    +
    {L_MARK_READ}
    +
    +
  • +
+ +
+ +
+ +
+ + +

{L_NO_NOTIFICATIONS}

+ + + +
+
+ + +
+ + {S_HIDDEN_FIELDS} + + + {S_FORM_TOKEN} +
+ + +
+ + diff --git a/styles/Milk_v2/template/ucp_pm_message_header.html b/styles/Milk_v2/template/ucp_pm_message_header.html new file mode 100644 index 0000000..3e3c575 --- /dev/null +++ b/styles/Milk_v2/template/ucp_pm_message_header.html @@ -0,0 +1,70 @@ +

{L_TITLE}{L_COLON} {CUR_FOLDER_NAME}

+ +
+ +
+
+

{FOLDER_STATUS}

+ +
+ + + + {L_BUTTON_PM_REPLY} + + + + {L_BUTTON_PM_NEW} + + + + + {L_BUTTON_PM_FORWARD} + + + + + {L_BUTTON_PM_REPLY_ALL} + + + + + + + + + + + +
diff --git a/styles/Milk_v2/template/ucp_pm_viewmessage.html b/styles/Milk_v2/template/ucp_pm_viewmessage.html new file mode 100644 index 0000000..cd17d5c --- /dev/null +++ b/styles/Milk_v2/template/ucp_pm_viewmessage.html @@ -0,0 +1,200 @@ + + +
+ + + +
+
+ + + +
+ + + {L_VIEW_PREVIOUS_HISTORY} + + + + + {L_VIEW_NEXT_HISTORY} + + +
+ + + +
+
+ +
+
+
+ + {AUTHOR_AVATAR} + +
+ {MESSAGE_AUTHOR_FULL} +
+ + +
{RANK_TITLE}
{RANK_IMG}
+ + +
{L_POSTS}{L_COLON} {AUTHOR_POSTS}{AUTHOR_POSTS}
+
{L_JOINED}{L_COLON} {AUTHOR_JOINED}
+ + + + +
{custom_fields.PROFILE_FIELD_NAME}{L_COLON} {custom_fields.PROFILE_FIELD_VALUE}
+ + + + + + +
+ {L_CONTACT}{L_COLON} + +
+ + +
+ +
+

{SUBJECT}

+ + + + + + + + +

+ {L_SENT_AT}{L_COLON} {SENT_DATE} +
{L_PM_FROM}{L_COLON} {MESSAGE_AUTHOR_FULL} +
{L_TO}{L_COLON} {to_recipient.NAME_FULL} style="color:{to_recipient.COLOUR};">{to_recipient.NAME}  +
{L_BCC}{L_COLON} {bcc_recipient.NAME_FULL} style="color:{bcc_recipient.COLOUR};">{bcc_recipient.NAME}  +

+ + +
{MESSAGE}
+ + +
+
+ {L_ATTACHMENTS} +
+ +
{attachment.DISPLAY_ATTACHMENT}
+ +
+ + + +
{L_DOWNLOAD_NOTICE}
+ + + +
{EDITED_MESSAGE} +
{L_REASON}{L_COLON} {EDIT_REASON} +
+ + + +
{SIGNATURE}
+ +
+ + + +
+
+ + + +
+ +   + + + {L_VIEW_PREVIOUS_PM} + + + + + {L_VIEW_NEXT_PM} + + + + + + +
+ + + + + + +
+ + diff --git a/styles/Milk_v2/template/viewforum_body.html b/styles/Milk_v2/template/viewforum_body.html new file mode 100644 index 0000000..632238c --- /dev/null +++ b/styles/Milk_v2/template/viewforum_body.html @@ -0,0 +1,377 @@ + + +

{FORUM_NAME}

+ + +
+ +
{FORUM_DESC}
+

{L_MODERATOR}{L_MODERATORS}{L_COLON} {MODERATORS}

+
+ + + +
+
+ + + {L_FORUM_RULES} + + {L_FORUM_RULES}
+ {FORUM_RULES} + + +
+
+ + + + + + + + + + + +
+ + + + + + + {L_BUTTON_FORUM_LOCKED} + + {L_BUTTON_NEW_TOPIC} + + + + + + + + + + + +
+ + + + +
+
+ {L_NO_READ_ACCESS} +
+
+ + + +
+ +
+
+ +
+

{L_LOGIN_LOGOUT}  •  {L_REGISTER}

+ +
+
+
+
+
+
+
+
+
+
+
+
+
 
+
+
+ {S_LOGIN_REDIRECT} + {S_FORM_TOKEN_LOGIN} +
+
+ +
+
+ +
+ + + + + + + + + + + +
+
+ + + +
+
+
    +
  • +
    + id="active_topics">
    {L_ACTIVE_TOPICS}{L_ANNOUNCEMENTS}{L_TOPICS}
    +
    +
    +
    +
    +
  • +
+ +
+
+ + + + +
+
+ {L_NO_TOPICS} +
+
+ +
+
+ {L_NO_FORUMS_IN_CATEGORY} +
+
+ + + + +
+ + + + + + {L_BUTTON_FORUM_LOCKED} + + {L_BUTTON_NEW_TOPIC} + + + + + + + + + + + +
+ +
+ + + +
+ + + + + +
+
+
    +
  • +
    +
    {L_INFORMATION}
    +
    +
  • +
+ +
+ + +
+ +
    + + +
  • +
    +

    {L_WHO_IS_ONLINE}

    +

    {LOGGED_IN_USER_LIST}

    +
    +
  • + + + +
  • +
    +

    {L_FORUM_PERMISSIONS}

    +

    {rules.RULE}

    +
    +
  • + + +
+
+
+ + + diff --git a/styles/Milk_v2/template/viewtopic_body.html b/styles/Milk_v2/template/viewtopic_body.html new file mode 100644 index 0000000..9ed933b --- /dev/null +++ b/styles/Milk_v2/template/viewtopic_body.html @@ -0,0 +1,483 @@ + + + + +
+ +

{TOPIC_TITLE}

+ + +
{FORUM_DESC}
+ + +

+ {L_MODERATOR}{L_MODERATORS}{L_COLON} {MODERATORS} +

+ + + +
+
+ + + {L_FORUM_RULES} + + {L_FORUM_RULES}
+ {FORUM_RULES} + + +
+
+ + +
+ + + + + + {L_BUTTON_TOPIC_LOCKED} + + {L_BUTTON_POST_REPLY} + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+ +
+

{POLL_QUESTION}

+

{L_POLL_LENGTH}
{L_MAX_VOTES}

+ +
+ + +
title="{L_POLL_VOTED_OPTION}" data-alt-text="{L_POLL_VOTED_OPTION}" data-poll-option-id="{poll_option.POLL_OPTION_ID}"> +
{poll_option.POLL_OPTION_CAPTION}
+
checked="checked" /> checked="checked" />
+
{poll_option.POLL_OPTION_RESULT}
+
{L_NO_VOTES}{poll_option.POLL_OPTION_PERCENT}
+
+ + + +
+
 
+
{L_TOTAL_VOTES}{L_COLON} {TOTAL_VOTES}
+
+ + +
+
 
+
+
+ + + +
+
 
+
{L_VIEW_RESULTS}
+
+ +
+ +
+ +
+ {S_FORM_TOKEN} + {S_HIDDEN_FIELDS} +
+ +
+ + + + +
+ + + + + data-url="{postrow.U_MINI_POST}"> + +
+
+ +
style="display: none;"> +
+
+ + + {postrow.POSTER_AVATAR}{postrow.POSTER_AVATAR} + + +
+ + {postrow.POST_AUTHOR_FULL}{postrow.POST_AUTHOR_FULL} + + +
+ + + +
{postrow.RANK_TITLE}
{postrow.RANK_IMG}
+ + +
{L_POSTS}{L_COLON} {postrow.POSTER_POSTS}
+
{L_JOINED}{L_COLON} {postrow.POSTER_JOINED}
+
{L_WARNINGS}{L_COLON} {postrow.POSTER_WARNINGS}
+ + + +
{postrow.PROFILE_FIELD1_NAME}{L_COLON} {postrow.PROFILE_FIELD1_VALUE}
+ + + + + +
{postrow.custom_fields.PROFILE_FIELD_NAME}{L_COLON} {postrow.custom_fields.PROFILE_FIELD_VALUE}
+ + + + + + +
+ {L_CONTACT}{L_COLON} + +
+ + + +
+ +
+ + +
+ {postrow.L_POST_DELETED_MESSAGE}
+ {postrow.L_POST_DISPLAY} +
+ +
+ {postrow.L_IGNORE_POST}
+ {postrow.L_POST_DISPLAY} +
+ + +
style="display: none;"> + + +

class="first">{postrow.POST_ICON_IMG_ALT}

+ + + + + + + + + + + +

+ + {postrow.MINI_POST} + + + {postrow.MINI_POST} + + + {postrow.POST_DATE} +

+ + + +
+

+ + {L_POST_UNAPPROVED_ACTION} + + + + {S_FORM_TOKEN} +

+
+ +
+

+ {L_POST_DELETED_ACTION} + + + + + + {S_FORM_TOKEN} +

+
+ + + +

+ {L_POST_REPORTED} +

+ + +
{postrow.MESSAGE}
+ + + +
+
+ {L_ATTACHMENTS} +
+ +
{postrow.attachment.DISPLAY_ATTACHMENT}
+ +
+ + + +
{L_DOWNLOAD_NOTICE}
+ +
+ {postrow.DELETED_MESSAGE} +
{L_REASON}{L_COLON} {postrow.DELETE_REASON} +
+ +
+ {postrow.EDITED_MESSAGE} +
{L_REASON}{L_COLON} {postrow.EDIT_REASON} +
+ + +


{postrow.BUMPED_MESSAGE}
+ +
{postrow.SIGNATURE}
+ + +
+ +
+ + + + + +
+
+ + + + + +
+ + +
+ + + +
+ + + + + + {L_BUTTON_TOPIC_LOCKED} + + {L_BUTTON_POST_REPLY} + + + + + + + + +
+ +
+ + + + + + + + + + + +
+ + + + + + +
+
+
    +
  • +
    +
    {L_INFORMATION}
    +
    +
  • +
+ +
+ + +
+ + +
+
+ + +
+ + \ No newline at end of file diff --git a/styles/Milk_v2/theme/animate.css b/styles/Milk_v2/theme/animate.css new file mode 100644 index 0000000..7148b57 --- /dev/null +++ b/styles/Milk_v2/theme/animate.css @@ -0,0 +1,3340 @@ +@charset "UTF-8"; + +/*! + * animate.css -http://daneden.me/animate + * Version - 3.5.1 + * Licensed under the MIT license - http://opensource.org/licenses/MIT + * + * Copyright (c) 2016 Daniel Eden + */ + +.animated { + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +.animated.infinite { + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; +} + +.animated.hinge { + -webkit-animation-duration: 2s; + animation-duration: 2s; +} + +.animated.flipOutX, +.animated.flipOutY, +.animated.bounceIn, +.animated.bounceOut { + -webkit-animation-duration: .75s; + animation-duration: .75s; +} + +@-webkit-keyframes bounce { + from, 20%, 53%, 80%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + -webkit-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); + } + + 40%, 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0,-4px,0); + transform: translate3d(0,-4px,0); + } +} + +@keyframes bounce { + from, 20%, 53%, 80%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + -webkit-transform: translate3d(0,0,0); + transform: translate3d(0,0,0); + } + + 40%, 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -30px, 0); + transform: translate3d(0, -30px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060); + -webkit-transform: translate3d(0, -15px, 0); + transform: translate3d(0, -15px, 0); + } + + 90% { + -webkit-transform: translate3d(0,-4px,0); + transform: translate3d(0,-4px,0); + } +} + +.bounce { + -webkit-animation-name: bounce; + animation-name: bounce; + -webkit-transform-origin: center bottom; + transform-origin: center bottom; +} + +@-webkit-keyframes flash { + from, 50%, to { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +@keyframes flash { + from, 50%, to { + opacity: 1; + } + + 25%, 75% { + opacity: 0; + } +} + +.flash { + -webkit-animation-name: flash; + animation-name: flash; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.pulse { + -webkit-animation-name: pulse; + animation-name: pulse; +} + +@-webkit-keyframes rubberBand { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(.95, 1.05, 1); + transform: scale3d(.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, .95, 1); + transform: scale3d(1.05, .95, 1); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes rubberBand { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(.95, 1.05, 1); + transform: scale3d(.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, .95, 1); + transform: scale3d(1.05, .95, 1); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.rubberBand { + -webkit-animation-name: rubberBand; + animation-name: rubberBand; +} + +@-webkit-keyframes shake { + from, to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +@keyframes shake { + from, to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 10%, 30%, 50%, 70%, 90% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 20%, 40%, 60%, 80% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } +} + +.shake { + -webkit-animation-name: shake; + animation-name: shake; +} + +@-webkit-keyframes headShake { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 6.5% { + -webkit-transform: translateX(-6px) rotateY(-9deg); + transform: translateX(-6px) rotateY(-9deg); + } + + 18.5% { + -webkit-transform: translateX(5px) rotateY(7deg); + transform: translateX(5px) rotateY(7deg); + } + + 31.5% { + -webkit-transform: translateX(-3px) rotateY(-5deg); + transform: translateX(-3px) rotateY(-5deg); + } + + 43.5% { + -webkit-transform: translateX(2px) rotateY(3deg); + transform: translateX(2px) rotateY(3deg); + } + + 50% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes headShake { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 6.5% { + -webkit-transform: translateX(-6px) rotateY(-9deg); + transform: translateX(-6px) rotateY(-9deg); + } + + 18.5% { + -webkit-transform: translateX(5px) rotateY(7deg); + transform: translateX(5px) rotateY(7deg); + } + + 31.5% { + -webkit-transform: translateX(-3px) rotateY(-5deg); + transform: translateX(-3px) rotateY(-5deg); + } + + 43.5% { + -webkit-transform: translateX(2px) rotateY(3deg); + transform: translateX(2px) rotateY(3deg); + } + + 50% { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +.headShake { + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + -webkit-animation-name: headShake; + animation-name: headShake; +} + +@-webkit-keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + to { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +@keyframes swing { + 20% { + -webkit-transform: rotate3d(0, 0, 1, 15deg); + transform: rotate3d(0, 0, 1, 15deg); + } + + 40% { + -webkit-transform: rotate3d(0, 0, 1, -10deg); + transform: rotate3d(0, 0, 1, -10deg); + } + + 60% { + -webkit-transform: rotate3d(0, 0, 1, 5deg); + transform: rotate3d(0, 0, 1, 5deg); + } + + 80% { + -webkit-transform: rotate3d(0, 0, 1, -5deg); + transform: rotate3d(0, 0, 1, -5deg); + } + + to { + -webkit-transform: rotate3d(0, 0, 1, 0deg); + transform: rotate3d(0, 0, 1, 0deg); + } +} + +.swing { + -webkit-transform-origin: top center; + transform-origin: top center; + -webkit-animation-name: swing; + animation-name: swing; +} + +@-webkit-keyframes tada { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, 20% { + -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes tada { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 10%, 20% { + -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg); + } + + 30%, 50%, 70%, 90% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + } + + 40%, 60%, 80% { + -webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.tada { + -webkit-animation-name: tada; + animation-name: tada; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes wobble { + from { + -webkit-transform: none; + transform: none; + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +@keyframes wobble { + from { + -webkit-transform: none; + transform: none; + } + + 15% { + -webkit-transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); + } + + 30% { + -webkit-transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); + } + + 45% { + -webkit-transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); + } + + 60% { + -webkit-transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); + } + + 75% { + -webkit-transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +.wobble { + -webkit-animation-name: wobble; + animation-name: wobble; +} + +@-webkit-keyframes jello { + from, 11.1%, to { + -webkit-transform: none; + transform: none; + } + + 22.2% { + -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); + transform: skewX(-12.5deg) skewY(-12.5deg); + } + + 33.3% { + -webkit-transform: skewX(6.25deg) skewY(6.25deg); + transform: skewX(6.25deg) skewY(6.25deg); + } + + 44.4% { + -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); + transform: skewX(-3.125deg) skewY(-3.125deg); + } + + 55.5% { + -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); + transform: skewX(1.5625deg) skewY(1.5625deg); + } + + 66.6% { + -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); + transform: skewX(-0.78125deg) skewY(-0.78125deg); + } + + 77.7% { + -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); + transform: skewX(0.390625deg) skewY(0.390625deg); + } + + 88.8% { + -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + } +} + +@keyframes jello { + from, 11.1%, to { + -webkit-transform: none; + transform: none; + } + + 22.2% { + -webkit-transform: skewX(-12.5deg) skewY(-12.5deg); + transform: skewX(-12.5deg) skewY(-12.5deg); + } + + 33.3% { + -webkit-transform: skewX(6.25deg) skewY(6.25deg); + transform: skewX(6.25deg) skewY(6.25deg); + } + + 44.4% { + -webkit-transform: skewX(-3.125deg) skewY(-3.125deg); + transform: skewX(-3.125deg) skewY(-3.125deg); + } + + 55.5% { + -webkit-transform: skewX(1.5625deg) skewY(1.5625deg); + transform: skewX(1.5625deg) skewY(1.5625deg); + } + + 66.6% { + -webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg); + transform: skewX(-0.78125deg) skewY(-0.78125deg); + } + + 77.7% { + -webkit-transform: skewX(0.390625deg) skewY(0.390625deg); + transform: skewX(0.390625deg) skewY(0.390625deg); + } + + 88.8% { + -webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + transform: skewX(-0.1953125deg) skewY(-0.1953125deg); + } +} + +.jello { + -webkit-animation-name: jello; + animation-name: jello; + -webkit-transform-origin: center; + transform-origin: center; +} + +@-webkit-keyframes bounceIn { + from, 20%, 40%, 60%, 80%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(.97, .97, .97); + transform: scale3d(.97, .97, .97); + } + + to { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes bounceIn { + from, 20%, 40%, 60%, 80%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + 40% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + + 80% { + -webkit-transform: scale3d(.97, .97, .97); + transform: scale3d(.97, .97, .97); + } + + to { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.bounceIn { + -webkit-animation-name: bounceIn; + animation-name: bounceIn; +} + +@-webkit-keyframes bounceInDown { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInDown { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -3000px, 0); + transform: translate3d(0, -3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, 25px, 0); + transform: translate3d(0, 25px, 0); + } + + 75% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, 5px, 0); + transform: translate3d(0, 5px, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +.bounceInDown { + -webkit-animation-name: bounceInDown; + animation-name: bounceInDown; +} + +@-webkit-keyframes bounceInLeft { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInLeft { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + 0% { + opacity: 0; + -webkit-transform: translate3d(-3000px, 0, 0); + transform: translate3d(-3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(25px, 0, 0); + transform: translate3d(25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(-10px, 0, 0); + transform: translate3d(-10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(5px, 0, 0); + transform: translate3d(5px, 0, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +.bounceInLeft { + -webkit-animation-name: bounceInLeft; + animation-name: bounceInLeft; +} + +@-webkit-keyframes bounceInRight { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + from { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +@keyframes bounceInRight { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + from { + opacity: 0; + -webkit-transform: translate3d(3000px, 0, 0); + transform: translate3d(3000px, 0, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(-25px, 0, 0); + transform: translate3d(-25px, 0, 0); + } + + 75% { + -webkit-transform: translate3d(10px, 0, 0); + transform: translate3d(10px, 0, 0); + } + + 90% { + -webkit-transform: translate3d(-5px, 0, 0); + transform: translate3d(-5px, 0, 0); + } + + to { + -webkit-transform: none; + transform: none; + } +} + +.bounceInRight { + -webkit-animation-name: bounceInRight; + animation-name: bounceInRight; +} + +@-webkit-keyframes bounceInUp { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + from { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes bounceInUp { + from, 60%, 75%, 90%, to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000); + } + + from { + opacity: 0; + -webkit-transform: translate3d(0, 3000px, 0); + transform: translate3d(0, 3000px, 0); + } + + 60% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + 75% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -5px, 0); + transform: translate3d(0, -5px, 0); + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.bounceInUp { + -webkit-animation-name: bounceInUp; + animation-name: bounceInUp; +} + +@-webkit-keyframes bounceOut { + 20% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 50%, 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } +} + +@keyframes bounceOut { + 20% { + -webkit-transform: scale3d(.9, .9, .9); + transform: scale3d(.9, .9, .9); + } + + 50%, 55% { + opacity: 1; + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } +} + +.bounceOut { + -webkit-animation-name: bounceOut; + animation-name: bounceOut; +} + +@-webkit-keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes bounceOutDown { + 20% { + -webkit-transform: translate3d(0, 10px, 0); + transform: translate3d(0, 10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.bounceOutDown { + -webkit-animation-name: bounceOutDown; + animation-name: bounceOutDown; +} + +@-webkit-keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes bounceOutLeft { + 20% { + opacity: 1; + -webkit-transform: translate3d(20px, 0, 0); + transform: translate3d(20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.bounceOutLeft { + -webkit-animation-name: bounceOutLeft; + animation-name: bounceOutLeft; +} + +@-webkit-keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes bounceOutRight { + 20% { + opacity: 1; + -webkit-transform: translate3d(-20px, 0, 0); + transform: translate3d(-20px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.bounceOutRight { + -webkit-animation-name: bounceOutRight; + animation-name: bounceOutRight; +} + +@-webkit-keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes bounceOutUp { + 20% { + -webkit-transform: translate3d(0, -10px, 0); + transform: translate3d(0, -10px, 0); + } + + 40%, 45% { + opacity: 1; + -webkit-transform: translate3d(0, 20px, 0); + transform: translate3d(0, 20px, 0); + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.bounceOutUp { + -webkit-animation-name: bounceOutUp; + animation-name: bounceOutUp; +} + +@-webkit-keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.fadeIn { + -webkit-animation-name: fadeIn; + animation-name: fadeIn; +} + +@-webkit-keyframes fadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; +} + +@-webkit-keyframes fadeInDownBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInDownBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInDownBig { + -webkit-animation-name: fadeInDownBig; + animation-name: fadeInDownBig; +} + +@-webkit-keyframes fadeInLeft { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInLeft { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInLeft { + -webkit-animation-name: fadeInLeft; + animation-name: fadeInLeft; +} + +@-webkit-keyframes fadeInLeftBig { + from { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInLeftBig { + from { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInLeftBig { + -webkit-animation-name: fadeInLeftBig; + animation-name: fadeInLeftBig; +} + +@-webkit-keyframes fadeInRight { + from { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInRight { + from { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInRight { + -webkit-animation-name: fadeInRight; + animation-name: fadeInRight; +} + +@-webkit-keyframes fadeInRightBig { + from { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInRightBig { + from { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInRightBig { + -webkit-animation-name: fadeInRightBig; + animation-name: fadeInRightBig; +} + +@-webkit-keyframes fadeInUp { + from { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInUp { + -webkit-animation-name: fadeInUp; + animation-name: fadeInUp; +} + +@-webkit-keyframes fadeInUpBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInUpBig { + from { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.fadeInUpBig { + -webkit-animation-name: fadeInUpBig; + animation-name: fadeInUpBig; +} + +@-webkit-keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } +} + +.fadeOut { + -webkit-animation-name: fadeOut; + animation-name: fadeOut; +} + +@-webkit-keyframes fadeOutDown { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes fadeOutDown { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.fadeOutDown { + -webkit-animation-name: fadeOutDown; + animation-name: fadeOutDown; +} + +@-webkit-keyframes fadeOutDownBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +@keyframes fadeOutDownBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, 2000px, 0); + transform: translate3d(0, 2000px, 0); + } +} + +.fadeOutDownBig { + -webkit-animation-name: fadeOutDownBig; + animation-name: fadeOutDownBig; +} + +@-webkit-keyframes fadeOutLeft { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes fadeOutLeft { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.fadeOutLeft { + -webkit-animation-name: fadeOutLeft; + animation-name: fadeOutLeft; +} + +@-webkit-keyframes fadeOutLeftBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +@keyframes fadeOutLeftBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(-2000px, 0, 0); + transform: translate3d(-2000px, 0, 0); + } +} + +.fadeOutLeftBig { + -webkit-animation-name: fadeOutLeftBig; + animation-name: fadeOutLeftBig; +} + +@-webkit-keyframes fadeOutRight { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes fadeOutRight { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.fadeOutRight { + -webkit-animation-name: fadeOutRight; + animation-name: fadeOutRight; +} + +@-webkit-keyframes fadeOutRightBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +@keyframes fadeOutRightBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(2000px, 0, 0); + transform: translate3d(2000px, 0, 0); + } +} + +.fadeOutRightBig { + -webkit-animation-name: fadeOutRightBig; + animation-name: fadeOutRightBig; +} + +@-webkit-keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes fadeOutUp { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.fadeOutUp { + -webkit-animation-name: fadeOutUp; + animation-name: fadeOutUp; +} + +@-webkit-keyframes fadeOutUpBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +@keyframes fadeOutUpBig { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(0, -2000px, 0); + transform: translate3d(0, -2000px, 0); + } +} + +.fadeOutUpBig { + -webkit-animation-name: fadeOutUpBig; + animation-name: fadeOutUpBig; +} + +@-webkit-keyframes flip { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(.95, .95, .95); + transform: perspective(400px) scale3d(.95, .95, .95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +@keyframes flip { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + transform: perspective(400px) rotate3d(0, 1, 0, -360deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 40% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg); + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; + } + + 50% { + -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 80% { + -webkit-transform: perspective(400px) scale3d(.95, .95, .95); + transform: perspective(400px) scale3d(.95, .95, .95); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } +} + +.animated.flip { + -webkit-backface-visibility: visible; + backface-visibility: visible; + -webkit-animation-name: flip; + animation-name: flip; +} + +@-webkit-keyframes flipInX { + from { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInX { + from { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + transform: perspective(400px) rotate3d(1, 0, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + transform: perspective(400px) rotate3d(1, 0, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInX { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInX; + animation-name: flipInX; +} + +@-webkit-keyframes flipInY { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +@keyframes flipInY { + from { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + opacity: 0; + } + + 40% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + transform: perspective(400px) rotate3d(0, 1, 0, -20deg); + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; + } + + 60% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + transform: perspective(400px) rotate3d(0, 1, 0, 10deg); + opacity: 1; + } + + 80% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + transform: perspective(400px) rotate3d(0, 1, 0, -5deg); + } + + to { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } +} + +.flipInY { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipInY; + animation-name: flipInY; +} + +@-webkit-keyframes flipOutX { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutX { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + transform: perspective(400px) rotate3d(1, 0, 0, -20deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + transform: perspective(400px) rotate3d(1, 0, 0, 90deg); + opacity: 0; + } +} + +.flipOutX { + -webkit-animation-name: flipOutX; + animation-name: flipOutX; + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; +} + +@-webkit-keyframes flipOutY { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +@keyframes flipOutY { + from { + -webkit-transform: perspective(400px); + transform: perspective(400px); + } + + 30% { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + transform: perspective(400px) rotate3d(0, 1, 0, -15deg); + opacity: 1; + } + + to { + -webkit-transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + transform: perspective(400px) rotate3d(0, 1, 0, 90deg); + opacity: 0; + } +} + +.flipOutY { + -webkit-backface-visibility: visible !important; + backface-visibility: visible !important; + -webkit-animation-name: flipOutY; + animation-name: flipOutY; +} + +@-webkit-keyframes lightSpeedIn { + from { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + opacity: 1; + } + + to { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes lightSpeedIn { + from { + -webkit-transform: translate3d(100%, 0, 0) skewX(-30deg); + transform: translate3d(100%, 0, 0) skewX(-30deg); + opacity: 0; + } + + 60% { + -webkit-transform: skewX(20deg); + transform: skewX(20deg); + opacity: 1; + } + + 80% { + -webkit-transform: skewX(-5deg); + transform: skewX(-5deg); + opacity: 1; + } + + to { + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.lightSpeedIn { + -webkit-animation-name: lightSpeedIn; + animation-name: lightSpeedIn; + -webkit-animation-timing-function: ease-out; + animation-timing-function: ease-out; +} + +@-webkit-keyframes lightSpeedOut { + from { + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +@keyframes lightSpeedOut { + from { + opacity: 1; + } + + to { + -webkit-transform: translate3d(100%, 0, 0) skewX(30deg); + transform: translate3d(100%, 0, 0) skewX(30deg); + opacity: 0; + } +} + +.lightSpeedOut { + -webkit-animation-name: lightSpeedOut; + animation-name: lightSpeedOut; + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in; +} + +@-webkit-keyframes rotateIn { + from { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateIn { + from { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, -200deg); + transform: rotate3d(0, 0, 1, -200deg); + opacity: 0; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateIn { + -webkit-animation-name: rotateIn; + animation-name: rotateIn; +} + +@-webkit-keyframes rotateInDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInDownLeft { + -webkit-animation-name: rotateInDownLeft; + animation-name: rotateInDownLeft; +} + +@-webkit-keyframes rotateInDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInDownRight { + -webkit-animation-name: rotateInDownRight; + animation-name: rotateInDownRight; +} + +@-webkit-keyframes rotateInUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInUpLeft { + -webkit-animation-name: rotateInUpLeft; + animation-name: rotateInUpLeft; +} + +@-webkit-keyframes rotateInUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +@keyframes rotateInUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -90deg); + transform: rotate3d(0, 0, 1, -90deg); + opacity: 0; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: none; + transform: none; + opacity: 1; + } +} + +.rotateInUpRight { + -webkit-animation-name: rotateInUpRight; + animation-name: rotateInUpRight; +} + +@-webkit-keyframes rotateOut { + from { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +@keyframes rotateOut { + from { + -webkit-transform-origin: center; + transform-origin: center; + opacity: 1; + } + + to { + -webkit-transform-origin: center; + transform-origin: center; + -webkit-transform: rotate3d(0, 0, 1, 200deg); + transform: rotate3d(0, 0, 1, 200deg); + opacity: 0; + } +} + +.rotateOut { + -webkit-animation-name: rotateOut; + animation-name: rotateOut; +} + +@-webkit-keyframes rotateOutDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, 45deg); + transform: rotate3d(0, 0, 1, 45deg); + opacity: 0; + } +} + +.rotateOutDownLeft { + -webkit-animation-name: rotateOutDownLeft; + animation-name: rotateOutDownLeft; +} + +@-webkit-keyframes rotateOutDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutDownRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutDownRight { + -webkit-animation-name: rotateOutDownRight; + animation-name: rotateOutDownRight; +} + +@-webkit-keyframes rotateOutUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +@keyframes rotateOutUpLeft { + from { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: left bottom; + transform-origin: left bottom; + -webkit-transform: rotate3d(0, 0, 1, -45deg); + transform: rotate3d(0, 0, 1, -45deg); + opacity: 0; + } +} + +.rotateOutUpLeft { + -webkit-animation-name: rotateOutUpLeft; + animation-name: rotateOutUpLeft; +} + +@-webkit-keyframes rotateOutUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +@keyframes rotateOutUpRight { + from { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + opacity: 1; + } + + to { + -webkit-transform-origin: right bottom; + transform-origin: right bottom; + -webkit-transform: rotate3d(0, 0, 1, 90deg); + transform: rotate3d(0, 0, 1, 90deg); + opacity: 0; + } +} + +.rotateOutUpRight { + -webkit-animation-name: rotateOutUpRight; + animation-name: rotateOutUpRight; +} + +@-webkit-keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + to { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +@keyframes hinge { + 0% { + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 20%, 60% { + -webkit-transform: rotate3d(0, 0, 1, 80deg); + transform: rotate3d(0, 0, 1, 80deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + 40%, 80% { + -webkit-transform: rotate3d(0, 0, 1, 60deg); + transform: rotate3d(0, 0, 1, 60deg); + -webkit-transform-origin: top left; + transform-origin: top left; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + opacity: 1; + } + + to { + -webkit-transform: translate3d(0, 700px, 0); + transform: translate3d(0, 700px, 0); + opacity: 0; + } +} + +.hinge { + -webkit-animation-name: hinge; + animation-name: hinge; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollIn { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes rollIn { + from { + opacity: 0; + -webkit-transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + transform: translate3d(-100%, 0, 0) rotate3d(0, 0, 1, -120deg); + } + + to { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +.rollIn { + -webkit-animation-name: rollIn; + animation-name: rollIn; +} + +/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */ + +@-webkit-keyframes rollOut { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +@keyframes rollOut { + from { + opacity: 1; + } + + to { + opacity: 0; + -webkit-transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + transform: translate3d(100%, 0, 0) rotate3d(0, 0, 1, 120deg); + } +} + +.rollOut { + -webkit-animation-name: rollOut; + animation-name: rollOut; +} + +@-webkit-keyframes zoomIn { + from { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 50% { + opacity: 1; + } +} + +@keyframes zoomIn { + from { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + 50% { + opacity: 1; + } +} + +.zoomIn { + -webkit-animation-name: zoomIn; + animation-name: zoomIn; +} + +@-webkit-keyframes zoomInDown { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInDown { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInDown { + -webkit-animation-name: zoomInDown; + animation-name: zoomInDown; +} + +@-webkit-keyframes zoomInLeft { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInLeft { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInLeft { + -webkit-animation-name: zoomInLeft; + animation-name: zoomInLeft; +} + +@-webkit-keyframes zoomInRight { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInRight { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInRight { + -webkit-animation-name: zoomInRight; + animation-name: zoomInRight; +} + +@-webkit-keyframes zoomInUp { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomInUp { + from { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + 60% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomInUp { + -webkit-animation-name: zoomInUp; + animation-name: zoomInUp; +} + +@-webkit-keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + to { + opacity: 0; + } +} + +@keyframes zoomOut { + from { + opacity: 1; + } + + 50% { + opacity: 0; + -webkit-transform: scale3d(.3, .3, .3); + transform: scale3d(.3, .3, .3); + } + + to { + opacity: 0; + } +} + +.zoomOut { + -webkit-animation-name: zoomOut; + animation-name: zoomOut; +} + +@-webkit-keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomOutDown { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomOutDown { + -webkit-animation-name: zoomOutDown; + animation-name: zoomOutDown; +} + +@-webkit-keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(.1) translate3d(-2000px, 0, 0); + transform: scale(.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +@keyframes zoomOutLeft { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(.1) translate3d(-2000px, 0, 0); + transform: scale(.1) translate3d(-2000px, 0, 0); + -webkit-transform-origin: left center; + transform-origin: left center; + } +} + +.zoomOutLeft { + -webkit-animation-name: zoomOutLeft; + animation-name: zoomOutLeft; +} + +@-webkit-keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(.1) translate3d(2000px, 0, 0); + transform: scale(.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +@keyframes zoomOutRight { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0); + } + + to { + opacity: 0; + -webkit-transform: scale(.1) translate3d(2000px, 0, 0); + transform: scale(.1) translate3d(2000px, 0, 0); + -webkit-transform-origin: right center; + transform-origin: right center; + } +} + +.zoomOutRight { + -webkit-animation-name: zoomOutRight; + animation-name: zoomOutRight; +} + +@-webkit-keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +@keyframes zoomOutUp { + 40% { + opacity: 1; + -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0); + -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190); + } + + to { + opacity: 0; + -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0); + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1); + } +} + +.zoomOutUp { + -webkit-animation-name: zoomOutUp; + animation-name: zoomOutUp; +} + +@-webkit-keyframes slideInDown { + from { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInDown { + from { + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInDown { + -webkit-animation-name: slideInDown; + animation-name: slideInDown; +} + +@-webkit-keyframes slideInLeft { + from { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInLeft { + from { + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInLeft { + -webkit-animation-name: slideInLeft; + animation-name: slideInLeft; +} + +@-webkit-keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInRight { + from { + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInRight { + -webkit-animation-name: slideInRight; + animation-name: slideInRight; +} + +@-webkit-keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +@keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slideInUp { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; +} + +@-webkit-keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes slideOutDown { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.slideOutDown { + -webkit-animation-name: slideOutDown; + animation-name: slideOutDown; +} + +@-webkit-keyframes slideOutLeft { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +@keyframes slideOutLeft { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } +} + +.slideOutLeft { + -webkit-animation-name: slideOutLeft; + animation-name: slideOutLeft; +} + +@-webkit-keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +@keyframes slideOutRight { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } +} + +.slideOutRight { + -webkit-animation-name: slideOutRight; + animation-name: slideOutRight; +} + +@-webkit-keyframes slideOutUp { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +@keyframes slideOutUp { + from { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + to { + visibility: hidden; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } +} + +.slideOutUp { + -webkit-animation-name: slideOutUp; + animation-name: slideOutUp; +} diff --git a/styles/Milk_v2/theme/base.css b/styles/Milk_v2/theme/base.css new file mode 100644 index 0000000..437492b --- /dev/null +++ b/styles/Milk_v2/theme/base.css @@ -0,0 +1,113 @@ +/* -------------------------------------------------------------- + $Base +-------------------------------------------------------------- */ + +/** { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +}*/ + +/* Define your base font-size here; most elements will inherit this. _NO__DOTCOMMA__AFTER__*/ +html { + font-size: 1em; /* Assuming 16px... */ + line-height: 1.5; /* 24px (This is now our magic number; all subsequent margin-bottoms and line-heights want to be a multiple of this number in order to maintain vertical rhythm.) _NO__DOTCOMMA__AFTER__*/ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + color: #333333; + background-color: #ffffff; +} + +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +figure { margin: 0 } +img { vertical-align: middle } + +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #e5e5e5; +} + +a { + color: #428bca; + text-decoration: none; +} + +a:hover, +a:active { + color: #2a6496; + text-decoration: underline; +} + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +figure, +p, +pre { margin: 0 } +button { + background: transparent; + border: 0; + padding: 0; +} + +/** + * Work around a Firefox/IE bug where the transparent `button` background + * results in a loss of the default `button` focus styles. + */ +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +fieldset { + border: 0; + margin: 0; + padding: 0; +} + +iframe { border: 0 } +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} + +/** + * Suppress the focus outline on links that cannot be accessed via keyboard. + * This prevents an unwanted focus outline from appearing around elements that + * might still respond to pointer events. + */ +[tabindex="-1"]:focus { outline: none !important } + +/** + * Remove double underline from recent version of firefox + */ +abbr[title] { + text-decoration: none; +} + diff --git a/styles/Milk_v2/theme/bidi.css b/styles/Milk_v2/theme/bidi.css new file mode 100644 index 0000000..4ef16c1 --- /dev/null +++ b/styles/Milk_v2/theme/bidi.css @@ -0,0 +1,1093 @@ +/* RTL definitions +---------------------------------------- */ + +/** +* common.css +*/ +.rtl h1 { + margin-right: 0; + margin-left: 200px; +} + +.rtl p.right { + text-align: left; +} + +.rtl p.jumpbox-return { + float: right; +} + +.rtl div.rules ul { + margin-left: 0; + margin-right: 20px; +} + +/* Main blocks +---------------------------------------- */ +.rtl .icon { + padding-right: 0; + padding-left: 2px; +} + +.rtl .logo { + float: right; + padding: 10px 10px 0 13px; +} + +/* Site Description +--------------------------------------------- */ +.rtl .site-description { + float: right; +} + +.rtl .site-description h1 { + margin-left: 0; +} + +/* Round cornered boxes and backgrounds +---------------------------------------- */ +.rtl .post { + background-position: 0 0; +} + +/* Horizontal lists +----------------------------------------*/ +.rtl ul.linklist > li { + float: right; + margin-right: 0; + margin-left: 7px; +} + +.rtl ul.linklist > li.rightside, .rtl p.rightside, .rtl a.rightside { + float: left; + margin-right: 7px; + margin-left: 0; + text-align: left; +} + +.rtl ul.leftside > li, .rtl ul.rightside > li { + float: left; +} + +.rtl ul.leftside { + float: right; + margin-left: 5px; + margin-right: 0; + text-align: right; +} + +.rtl ul.rightside { + float: left; + margin-left: -5px; + margin-right: 5px; + text-align: left; +} + +/* Bulletin icons for list items +----------------------------------------*/ +.rtl ul.linklist.bulletin > li:before { + padding-left: 4px; + padding-right: 0; +} + +/* Dropdown menu +---------------------------------------- */ +.rtl .dropdown-container.topic-tools, .rtl .dropdown-container-left { + float: right; +} + +.rtl .dropdown li { + text-align: right; +} + +.rtl .dropdown-contents > li { + padding-left: 15px; + padding-right: 0; +} + +.rtl .dropdown-nonscroll > li { + padding-left: 0; +} + +.rtl .dropdown li li { + padding-left: 0; + padding-right: 18px; +} + +.rtl .dropdown-extended .header { + text-align: right; +} + +.rtl .dropdown-extended .header .header_settings, .rtl .dropdown-container-right { + float: left; +} + +.rtl .jumpbox .dropdown-contents a { + margin-right: 0; + margin-left: 20px; +} + +/* Notifications +-----------------------------------------*/ +.rtl .notification_list ul li img { + float: right; + margin-left: 5px; + margin-right: 0; +} + +.rtl .notification_list div.notifications { + margin-left: 0; + margin-right: 50px; +} + +.rtl .notification_text { + margin-left: 0; + margin-right: 58px; +} + +.rtl .notification_list p.notification-time { + text-align: left; +} + +/* Responsive breadcrumbs +----------------------------------------*/ +.rtl .breadcrumbs .crumb { + float: right; +} + +/* Table styles +----------------------------------------*/ +.rtl table.table1 thead th { + padding: 0 3px 4px 0; +} + +.rtl table.table1 thead th span { + padding-left: 0; + padding-right: 7px; +} + +.rtl table.table1 tbody th { + text-align: right; +} + +/* Specific column styles */ +.rtl table.table1 .name { text-align: right; } +.rtl table.table1 .joined { text-align: right; } +.rtl table.table1 .active { text-align: right; } +.rtl table.table1 .info { text-align: right; } +.rtl table.table1 thead .autocol { padding-left: 0; padding-right: 1em; } + +.rtl table.table1 span.rank-img { + float: left; +} + +.rtl table.info tbody th { + text-align: left; +} + +.rtl .forumbg table.table1 { + margin: 0 -1px -1px -2px; +} + +/* Misc layout styles +---------------------------------------- */ +/* column[1-2] styles are containers for two column layouts */ +.rtl .column1 { + float: right; + clear: right; +} + +.rtl .column2 { + float: left; + clear: left; +} + +/* General classes for placing floating blocks */ +.rtl .left-box { + float: right; + text-align: right; +} + +.rtl .right-box { + float: left; + text-align: left; +} + +.rtl dl.details dt { + float: right; + clear: right; + text-align: left; +} + +.rtl dl.details dd { + margin-right: 0; + margin-left: 0; + padding-right: 5px; + padding-left: 0; + float: right; +} + +*:first-child+html dl.details dd { + margin-right: 30%; + float: none; +} + +* html dl.details dd { + margin-right: 30%; + float: none; +} + +/* Pagination +---------------------------------------- */ +.rtl .page-number { + float: left; +} + +.rtl .pagination { + text-align: left; + float: left; +} + +.rtl .pagination > ul { + margin-left: 0; + margin-right: 5px; +} + +/* Pagination in viewforum for multipage topics */ +.rtl .row .pagination { + background-position: 100% 50%; + float: left; + padding-left: 0; + padding-right: 15px; +} + +.rtl .row .pagination > ul { + margin: 0; +} + +.rtl .pagination span { + direction: ltr; +} + +.pagination li.page-jump { + margin-left: 5px; + margin-right: 0; +} + + +/* Action Bar styles +---------------------------------------- */ +.rtl .action-bar .button { + margin-right: 0; + float: right; +} + +.rtl .action-bar > .button { + margin-left: 5px; + float: right; +} + +.rtl .action-bar .dropdown-button-control .button { + margin-left: 5px; +} + + +/* Miscellaneous styles +---------------------------------------- */ +.rtl .quick-links { + margin-left: 7px; + margin-right: 0; +} + +.rtl .header-avatar span:after { + float: left; + padding-left: 0; + padding-right: 2px; +} + +.rtl .member-search { + float: right; +} + +/** +* links.css +*/ + +/* Links adjustment to correctly display an order of rtl/ltr mixed content */ +.rtl a { + direction: rtl; + unicode-bidi: embed; +} + +li.breadcrumbs span:first-child > a { + padding-left: 0; +} + +/* Notification mark read link */ +.rtl .dropdown-extended a.mark_read { + border-radius: 0 3px 3px 0; + left: 0; + right: auto; +} + +.rtl .back2top .top { + float: left; + margin-left: -10px; +} + +.rtl .skiplink { + /* invisible skip link, used for accessibility */ + left: 0; + right: -999px; +} + +.rtl a.feed-icon-forum { + float: left; +} + +/** +* content.css +*/ +.rtl ul.topiclist dt, .rtl li.header dt { + float: right; + margin-right: 0; + margin-left: -440px; +} + +.rtl ul.topiclist.missing-column dt { + margin-right: 0; + margin-left: -345px; +} + +.rtl ul.topiclist.two-long-columns dt { + margin-right: 0; + margin-left: -250px; +} + +.rtl ul.topiclist.two-columns dt { + margin-right: 0; + margin-left: -80px; +} + +.rtl ul.topiclist dt .list-inner { + margin-right: 0; + margin-left: 440px; +} + +.rtl ul.topiclist.missing-column dt .list-inner { + margin-right: 0; + margin-left: 330px; +} + +.rtl ul.topiclist.two-long-columns dt .list-inner { + margin-right: 0; + margin-left: 250px; +} + +.rtl ul.topiclist.two-columns dt .list-inner { + margin-right: 0; + margin-left: 80px; +} + +.rtl ul.topiclist dd { + float: right; + border-right-width: 1px; + border-right-style: solid; + border-left: none; +} + +.rtl ul.topiclist dfn { + left: auto; + right: -999px; +} + +.rtl ul.topiclist li.row dt a.subforum { + padding-right: 12px; + background-position: right; + position: static; +} + +.rtl .forum-image { + float: right; + margin-right: 0; + margin-left: 5px; +} + +.rtl li.header dt, .rtl li.header dd { + border-right-width: 0; +} + +.rtl li.header dd { + padding-left: 0; + padding-right: 1px; +} + +.rtl dl.row-item{ + background-position: 99.5% 50%; +} + +.rtl li.header dl.row-item dt .list-inner { + /* Tweak for headers alignment when folder icon used */ + padding-right: 0; + padding-left: 50px; +} + +.rtl dl.row-item dt { + background-position: 99.5% 95%; /* Position of topic icon */ +} + +.rtl dl.row-item dt .list-inner { + padding-left: 5px; + padding-right: 45px; /* Space for folder icon */ +} + +.rtl dl a.row-item-link { /* topic row icon links */ + display: inline-block; + left: auto; + right: 0; + margin-left: 0; + margin-right: 2px; +} + +.rtl dd.lastpost > span, .rtl ul.topiclist dd.info > span, .rtl ul.topiclist dd.time > span, .rtl dd.redirect > span, .rtl dd.moderation > span { + padding-left: 0; + padding-right: 5px; +} + +/* Post body styles +----------------------------------------*/ +.rtl .date { + float: left; +} + +.rtl .postbody, .rtl .postbody h3 { + float: right; +} + +.rtl .has-profile .postbody h3 { + margin-right: 0; + margin-left: 180px; +} + +.rtl p.post-notice { + padding-left: 5px; +} + +.rtl p.post-notice:before { + left: auto; + right: 0; +} + +/* Topic review panel +----------------------------------------*/ +.rtl .topicreview { + padding-right: 0; + padding-left: 5px; +} + +/* Content container styles +----------------------------------------*/ +.rtl .content ul, .rtl .content ol { + margin-right: 3em; + margin-left: 0; +} + +.rtl .signature { + clear: right; +} + +.rtl .notice { + clear: right; +} + +/* Jump to post link for now */ +.rtl ul.searchresults { + text-align: left; +} + +/* BB Code styles +----------------------------------------*/ +/* Quote block */ +.rtl blockquote { + margin: 0.5em 25px 0 1px; +} + +.rtl blockquote blockquote { + /* Nested quotes */ + margin: 0.5em 15px 0 1px; +} + +.rtl blockquote cite { + /* Username/source of quoter */ + margin-left: 0; +} + +.rtl blockquote cite:before, .rtl .uncited:before { + padding-left: 5px; +} + +.rtl blockquote .codebox { + margin-right: 0; +} + +.rtl code { + direction: ltr; +} + +/* Attachments +----------------------------------------*/ +.rtl .attachbox { + float: right; + margin: 5px 0 5px 5px; + clear: right; +} + +.rtl .attachbox dd { + clear: right; +} + +.rtl .attachbox p { + clear: right; +} + +.rtl .attachbox p.stats { + clear: right; +} + +/* Post poll styles +----------------------------------------*/ +.rtl fieldset.polls dt { + text-align: right; + float: right; + border-left: none; +} + +.rtl fieldset.polls dd { + float: right; + border-right: none; + margin-right: 0; +} + +.rtl fieldset.polls dd div { + text-align: left; +} + +.rtl .pollbar1, .rtl .pollbar2, .rtl .pollbar3, .rtl .pollbar4, .rtl .pollbar5 { + border-left-width: 1px; + border-left-style: solid; + border-right: none; +} + +/* Poster profile block +----------------------------------------*/ +.rtl .postprofile { + border-width: 0 1px 0 0; + float: left; +/* text-align: right; */ +} + +.rtl .pm .postprofile { + border-right-width: 1px; + border-right-style: solid; + border-left: none; +} + +.rtl .postprofile dd, .rtl .postprofile dt { + margin-left: 0; + margin-right: 8px; +} + +.rtl .postprofile .avatar { + float: right; +} + +.rtl .online { + background-position: 0 0; +} + +.rtl dl.pmlist dd { + margin-right: 61% !important; + margin-left: 0 !important; +} + +/** +* buttons.css +*/ + +.rtl .caret { + border-right: 1px solid; + border-right-color: inherit; + border-left: none; + right: 6px; +} + + + +/* Post control buttons +--------------------------------------------- */ +.rtl .post-buttons { + float: left; +} + +.rtl .has-profile .post-buttons { + left: 0; + right: auto; +} + +.rtl .post-buttons li { + float: right; +} + +/* Poster contact icons + ----------------------------------------*/ +.rtl .contact-icons a { + border-left-width: 1px; + border-left-style: dotted; + border-right: none; + float: right; +} + +.rtl .contact-icons .last-cell { + border-left: none; +} + +/** +* cp.css +*/ +/* Control Panel Styles +---------------------------------------- */ + + +/* Main CP box +----------------------------------------*/ +.rtl .cp-menu { + float: right; +} + +.rtl .cp-main { + float: right; +} + +.rtl .cp-main .panel ol { + margin-right: 2em; + margin-left: 0; +} + +.rtl .cp-main .buttons { + margin-right: 0; + margin-left: 0; +} + +.tabs-container h2 { + float: right; +} + +/* CP tabbed menu +----------------------------------------*/ +.rtl .tabs { + margin-left: 0; + margin-right: 7px; +} + +.rtl .tabs .tab { + float: right; +} + +.rtl .tabs .tab > a { + margin-left: 1px; + margin-right: 0; +} + +/* Mini tabbed menu used in MCP +----------------------------------------*/ +.rtl .minitabs { + float: left; + margin-right: 0; + margin-left: 7px; +} + +.rtl .minitabs .tab { + float: left; +} + +.rtl .minitabs .tab > a { + margin-right: 2px; + margin-left: 0; +} + +/* Responsive tabs +----------------------------------------*/ +.rtl .tabs .dropdown { + margin-left: -2px; +} + +.rtl .tabs .dropdown li { + text-align: left; +} + +.rtl .minitabs .dropdown { + margin-left: -4px; +} + +.rtl .minitabs .dropdown li { + text-align: right; +} + +/* Responsive *CP navigation +----------------------------------------*/ +@media only screen and (max-width: 900px), only screen and (max-device-width: 900px) +{ + .rtl .cp-menu, .rtl .navigation, .rtl .cp-main { + float: none; + } +} + +/* UCP navigation menu +----------------------------------------*/ + +/* Preferences pane layout +----------------------------------------*/ +.rtl .cp-main h2 { + margin-left: 0; + margin-right: 10px; +} + +/* Friends list */ +.rtl .cp-mini { + margin: 10px 5px 10px 15px; +} + +/* PM Styles +----------------------------------------*/ + +/* PM panel adjustments */ +.rtl .reply-all a.right { + background-position: 5% 60%; +} + +.rtl .reply-all a.right:hover { + background-position: 3% 60%; +} + +.rtl .reply-all { + padding-left: 5px; +} + +/* Defined rules list for PM options */ +.rtl ol.def-rules { + padding-right: 0; +} + +/* PM marking colours */ +.rtl .pm-legend { + border-right-width: 10px; + border-right-style: solid; + border-left-width: 0; + padding-left: 0; + padding-right: 3px; +} + +/* Avatar gallery */ +.rtl .gallery label { + float: right; +} + +/* Responsive *CP navigation +----------------------------------------*/ +@media only screen and (max-width: 900px), only screen and (max-device-width: 900px) +{ + .rtl .cp-menu, .rtl .navigation, .rtl .cp-main { + float: none; + } +} + +/** +* forms.css +*/ + +/* General form styles +----------------------------------------*/ + +.rtl option { + padding-right: 0; + padding-left: 1em; +} + +.rtl label { + padding-right: 0; + padding-left: 5px; +} + +/* Definition list layout for forms +---------------------------------------- */ +.rtl fieldset dt { + float: right; + text-align: right; +} + +.rtl fieldset dd { + margin-left: 0; + margin-right: 41%; +} + +/* Specific layout 1 */ +.rtl fieldset.fields1 dt { + border-left-width: 0; + border-right-width: 1px; +} + +.rtl fieldset.fields1 dd { + margin-right: 15em; + margin-left: 0; + border-right-width: 0; + border-left-width: 1px; +} + +/* Specific layout 2 */ +.rtl fieldset.fields2 dt { + border-right-width: 1px; + border-left-width: 0; +} + +.rtl fieldset.fields2 dd { + margin-right: 16em; + margin-left: 0; + border-left-width: 1px; + border-right-width: 0; +} + +/* Form elements */ +.rtl dt label { + text-align: right; +} + +.rtl dd input, .rtl dd textarea { + margin-left: 3px; + margin-right: 0; +} + +/* Quick-login on index page */ +.rtl fieldset.quick-login input.inputbox { + margin-left: 5px; + margin-right: 0; +} + +.rtl fieldset.quick-login label { + padding-left: 2px; + padding-right: 0; +} + +/* Display options on viewtopic/viewforum pages */ +.rtl fieldset.display-options label { + padding-left: 2px; + padding-right: 0; +} + +.rtl .dropdown fieldset.display-options label { + text-align: left; +} + +/* Display actions for ucp and mcp pages */ +.rtl fieldset.display-actions { + text-align: left; + padding-left: 1em; + padding-right: 0; +} + +.rtl fieldset.display-actions label { + padding-left: 2px; + padding-right: 0; +} + +/* MCP forum selection*/ +.rtl fieldset.forum-selection { + float: left; +} + +.rtl fieldset.forum-selection2 { + float: left; +} + +/* Posting page styles +----------------------------------------*/ + +/* Emoticons panel */ +.rtl .smiley-box { + float: left; +} + +/* Search box +---------------------------------------- */ + +/* Topic and forum Search */ +.rtl .search-box { + float: right; +} + +.rtl .search-box .inputbox { + border-left-width: 0; + border-right-width: 1px; + border-radius: 0 4px 4px 0; + float: right; + padding: 3px; +} + +.rtl .button-search, +.button-search-end { + float: right; +} + +.rtl .button-search-end { + border-radius: 4px 0 0 4px; + border-left-width: 1px; + border-right-width: 0; +} + +.rtl .search-header .button-search-end { + border: 0; + border-radius: 4px 0 0 4px; +} + +.rtl .search-header { + float: left; + margin-right: 0; + margin-left: 5px; +} + +/* Form button styles +---------------------------------------- */ + +/** Reference: Bug #27155 */ +.rtl .wrap, .rtl .headerbar, .rtl .site-description, .rtl .navbar { + position: relative; +} + +/** +* plupload.css +*/ + +.rtl .attach-controls { + float: left; +} + +/** +* responsive.css +*/ +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + /* .topiclist lists + ----------------------------------------*/ + .rtl ul.topiclist li.header dt, .rtl ul.topiclist li.header dt .list-inner { + margin-left: 0 !important; + padding-left: 0; + } + + .rtl ul.topiclist dt, .rtl ul.topiclist dt .list-inner, + .rtl ul.topiclist.missing-column dt, .rtl ul.topiclist.missing-column dt .list-inner, + .rtl ul.topiclist.two-long-columns dt, .rtl ul.topiclist.two-long-columns dt .list-inner, + .rtl ul.topiclist.two-columns dt, .rtl ul.topiclist.two-columns dt .list-inner { + margin-left: 0; + } + + .rtl ul.topiclist dt .list-inner.with-mark { + padding-left: 34px; + } + + /* Forums and topics lists + ----------------------------------------*/ + .rtl ul.topiclist.forums dt { + margin-left: -250px; + } + .rtl ul.topiclist.forums dt .list-inner { + margin-left: 250px; + } + + .rtl ul.topiclist dd.mark { + left: 5px; + right: auto; + text-align: right; + } + + .rtl table.responsive.show-header thead, .rtl table.responsive.show-header th:first-child { + text-align: right !important; + } + + .rtl table.responsive td { + text-align: right !important; + } + + /* User profile + ----------------------------------------*/ + .rtl .column1, .rtl .column2, .rtl .left-box.profile-details { + float: none; + } + + /* Post + ----------------------------------------*/ + .rtl .postprofile, .rtl .postbody, .rtl .search .postbody { + float: none; + } + + .rtl .post .postprofile { + border-width: 0 0 1px 0; + } + + .rtl .postprofile dt, .rtl .postprofile dd.profile-rank, .rtl .search .postprofile dd { + margin: 0; + } + + .rtl .postprofile .avatar { + margin-left: 5px; + margin-right: 0; + } + + .rtl .has-profile .post-buttons { + left: 20px; + } + + /* Forms + ----------------------------------------*/ + .rtl fieldset dt, .rtl fieldset.fields1 dt, .rtl fieldset.fields2 dt { + float: none; + } + + .rtl fieldset dd, .rtl fieldset.fields1 dd, .rtl fieldset.fields2 dd { + margin-right: 20px; + } +} + +@media only screen and (max-width: 550px), only screen and (max-device-width: 550px) +{ + /* .topiclist lists + ----------------------------------------*/ + .rtl ul.topiclist.forums dt { + margin-left: 0; + } + + .rtl ul.topiclist.forums dt .list-inner { + margin-left: 0; + } +} + +@media only screen and (max-width: 500px), only screen and (max-device-width: 500px) +{ + .rtl dl.details dt, .rtl dl.details dd { + float: none; + text-align: right; + } + + .rtl dl.details dd { + margin-left: 0; + margin-right: 20px; + } + + .captcha-panel dd.captcha { + margin-right: 0; + } + + .rtl p.responsive-center { + float: none; + text-align: center; + margin-bottom: 5px; + } +} diff --git a/styles/Milk_v2/theme/blank.css.css b/styles/Milk_v2/theme/blank.css.css new file mode 100644 index 0000000..34f1449 --- /dev/null +++ b/styles/Milk_v2/theme/blank.css.css @@ -0,0 +1,3 @@ +/* This file is intentionally left blank. +It's used by the light/dark toggle +If deleted, you'll get a console log error (but things will still work) */ diff --git a/styles/Milk_v2/theme/buttons.css b/styles/Milk_v2/theme/buttons.css new file mode 100644 index 0000000..c13e627 --- /dev/null +++ b/styles/Milk_v2/theme/buttons.css @@ -0,0 +1,187 @@ +/* Button Styles +---------------------------------------- */ + +.button { + display: inline-block; + padding: 8px 15px; + line-height: 1.4; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 1px solid transparent; +} + +.button:focus, +.button:hover { + text-decoration: none; + outline: none; +} + +.caret { + position: relative; + right: -3px; +} + +.caret i { + vertical-align: top; +} + +/* Posting page styles +----------------------------------------*/ +.button-search, +.button-search-end { + float: left; + border-radius: 0; + margin: 0; + padding: 0 9px; + height: 37px; +} + +.button-search-end { + height: 35px; + line-height: 35px; +} + +.button-search-end { + border-left-width: 0; +} + +.search-header .button-search, +.search-header .button-search-end { + border-top-width: 0; + border-bottom-width: 0; + padding: 6px 7px; +} + +.search-header .button-search-end { + border-right-width: 0; +} + +.button-icon-only { + padding-left: 3px; + padding-right: 3px; +} + +/* Poster contact icons +----------------------------------------*/ +.contact-icons.dropdown-contents { + min-width: 0; + padding: 0; +} + +.contact-icon { + background-repeat: no-repeat; + display: block; + height: 16px; + width: 16px; +} +.contact-icons a { + border-bottom: 1px dotted; + border-right: 1px dotted; + display: block; + float: left; + padding: 8px; +} + +.contact-icons .last-cell { + border-right: none; +} + +.contact-icons div:last-child a { + border-bottom: none; +} + +.contact-icons div { + clear: left; +} + +/* Post control buttons +--------------------------------------------- */ +.post-buttons { + float: right; + list-style: none; + margin-top: 2px; +} + +.has-profile .post-buttons { + float: none; + position: absolute; + margin: 0; + right: 0; + top: 5px; +} + +.post-buttons > li { + float: left; + margin-right: 3px; +} + +.post-buttons .button, .format-buttons .button { + padding-left: 7px; + padding-right: 7px; +} + +.hastouch .post-buttons { + margin-right: 10px; +} + +/* Responsive buttons in post body */ +.post-buttons .dropdown { + top: 18px; +} + +.post-buttons .dropdown a { + display: block; + text-align: right; +} + +.hasjs .postbody .post-buttons { + max-width: 40%; +} + +/* Browser-specific tweaks */ +button::-moz-focus-inner { + padding: 0; + border: 0 +} + +/* Deprecated as of version 3.2 +-------------------------------------------------*/ +.small-icon { + background-position: 0 50%; + background-repeat: no-repeat; + background-image: none; +} + +.dropdown .small-icon { + background-position: 5px 50%; + padding: 5px; +} + +.small-icon > a { + padding: 0 0 0 18px; +} + +ul.linklist.bulletin > li.small-icon:before { + display: none; +} + +.dropdown .small-icon > a { + display: block; +} + +.rtl .small-icon { + background-position: 100% 50%; +} + +.rtl .small-icon > a { + padding-left: 0; + padding-right: 19px; +} diff --git a/styles/Milk_v2/theme/colour-presets/Aqua.css b/styles/Milk_v2/theme/colour-presets/Aqua.css new file mode 100644 index 0000000..3e30d78 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Aqua.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #51e2c3; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #51e2c3; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #51e2c3; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#51e2c3+0,83bcfd+100 */ + background: rgb(81,226,195); /* Old browsers */ + background: -moz-linear-gradient(left, rgba(81,226,195,1) 0%, rgba(131,188,253,1) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(81,226,195,1) 0%,rgba(131,188,253,1) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(81,226,195,1) 0%,rgba(131,188,253,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#51e2c3', endColorstr='#83bcfd',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#51e2c3+0,83bcfd+100&0.58+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(81,226,195,0.58) 0%, rgba(131,188,253,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(81,226,195,0.58) 0%,rgba(131,188,253,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(81,226,195,0.58) 0%,rgba(131,188,253,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#9451e2c3', endColorstr='#cc83bcfd',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #56edcd; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #56edcd; +} + +/* Border light */ +.specialbutton:hover { + border-color: #56edcd; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Blue_Lagoon.css b/styles/Milk_v2/theme/colour-presets/Blue_Lagoon.css new file mode 100644 index 0000000..c24ab78 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Blue_Lagoon.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #191654; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #191654; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #191654; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#191654+0,43c6ac+100 */ + background: #191654; /* Old browsers */ + background: -moz-linear-gradient(left, #191654 0%, #43c6ac 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #191654 0%,#43c6ac 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #191654 0%,#43c6ac 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#191654', endColorstr='#43c6ac',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#191654+0,43c6ac+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(25,22,84,0.8) 0%, rgba(67,198,172,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(25,22,84,0.8) 0%,rgba(67,198,172,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(25,22,84,0.8) 0%,rgba(67,198,172,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc191654', endColorstr='#cc43c6ac',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #27237e; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #27237e; +} + +/* Border light */ +.specialbutton:hover { + border-color: #27237e; +} + diff --git a/styles/Milk_v2/theme/colour-presets/Blush.css b/styles/Milk_v2/theme/colour-presets/Blush.css new file mode 100644 index 0000000..0e762a5 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Blush.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #f15f79; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #f15f79; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #f15f79; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#f15f79+0,b24592+100 */ + background: #f15f79; /* Old browsers */ + background: -moz-linear-gradient(left, #f15f79 0%, #b24592 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #f15f79 0%,#b24592 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #f15f79 0%,#b24592 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f15f79', endColorstr='#b24592',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#f15f79+0,b24592+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(241,95,121,0.8) 0%, rgba(178,69,146,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(241,95,121,0.8) 0%,rgba(178,69,146,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(241,95,121,0.8) 0%,rgba(178,69,146,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ccf15f79', endColorstr='#ccb24592',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #ff768e; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #ff768e; +} + +/* Border light */ +.specialbutton:hover { + border-color: #ff768e; +} + diff --git a/styles/Milk_v2/theme/colour-presets/Calm_Darya.css b/styles/Milk_v2/theme/colour-presets/Calm_Darya.css new file mode 100644 index 0000000..e7e83e0 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Calm_Darya.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #49a09d; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #49a09d; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #49a09d; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#49a09d+0,5f2c82+100 */ + background: #49a09d; /* Old browsers */ + background: -moz-linear-gradient(left, #49a09d 0%, #5f2c82 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #49a09d 0%,#5f2c82 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #49a09d 0%,#5f2c82 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#49a09d', endColorstr='#5f2c82',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#49a09d+0,5f2c82+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(73,160,157,0.8) 0%, rgba(95,44,130,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(73,160,157,0.8) 0%,rgba(95,44,130,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(73,160,157,0.8) 0%,rgba(95,44,130,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc49a09d', endColorstr='#cc5f2c82',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #52b5b2; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #52b5b2; +} + +/* Border light */ +.specialbutton:hover { + border-color: #52b5b2; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Electric_Violet.css b/styles/Milk_v2/theme/colour-presets/Electric_Violet.css new file mode 100644 index 0000000..164dbe3 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Electric_Violet.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #8e54e9; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #8e54e9; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #8e54e9; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#8e54e9+0,4776e6+100 */ + background: #8e54e9; /* Old browsers */ + background: -moz-linear-gradient(left, #8e54e9 0%, #4776e6 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #8e54e9 0%,#4776e6 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #8e54e9 0%,#4776e6 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#8e54e9', endColorstr='#4776e6',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#8e54e9+0,4776e6+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(142,84,233,0.8) 0%, rgba(71,118,230,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(142,84,233,0.8) 0%,rgba(71,118,230,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(142,84,233,0.8) 0%,rgba(71,118,230,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc8e54e9', endColorstr='#cc4776e6',GradientType=1 ); /* IE6-9 */ + +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #a76fff; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #a76fff; +} + +/* Border light */ +.specialbutton:hover { + border-color: #a76fff; +} diff --git a/styles/Milk_v2/theme/colour-presets/HoneyDew.css b/styles/Milk_v2/theme/colour-presets/HoneyDew.css new file mode 100644 index 0000000..1a9697a --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/HoneyDew.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #ade7ad; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #ade7ad; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #ade7ad; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#f8ffae+0,43c6ac+100 */ + background: #f8ffae; /* Old browsers */ + background: -moz-linear-gradient(left, #f8ffae 0%, #43c6ac 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #f8ffae 0%,#43c6ac 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #f8ffae 0%,#43c6ac 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f8ffae', endColorstr='#43c6ac',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#f8ffae+0,43c6ac+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(248,255,174,0.8) 0%, rgba(67,198,172,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(248,255,174,0.8) 0%,rgba(67,198,172,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(248,255,174,0.8) 0%,rgba(67,198,172,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ccf8ffae', endColorstr='#cc43c6ac',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #b8f4b8; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #b8f4b8; +} + +/* Border light */ +.specialbutton:hover { + border-color: #b8f4b8; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Hot_Red.css b/styles/Milk_v2/theme/colour-presets/Hot_Red.css new file mode 100644 index 0000000..75ef3b5 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Hot_Red.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #ea384d; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #ea384d; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #ea384d; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ea384d+0,d31027+100 */ + background: rgb(234,56,77); /* Old browsers */ + background: -moz-linear-gradient(left, rgba(234,56,77,1) 0%, rgba(211,16,39,1) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(234,56,77,1) 0%,rgba(211,16,39,1) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(234,56,77,1) 0%,rgba(211,16,39,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ea384d', endColorstr='#d31027',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ea384d+0,d31027+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(234,56,77,0.8) 0%, rgba(211,16,39,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(234,56,77,0.8) 0%,rgba(211,16,39,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(234,56,77,0.8) 0%,rgba(211,16,39,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ccea384d', endColorstr='#ccd31027',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #fb485d; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #fb485d; +} + +/* Border light */ +.specialbutton:hover { + border-color: #fb485d; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Ibiza_Sunset.css b/styles/Milk_v2/theme/colour-presets/Ibiza_Sunset.css new file mode 100644 index 0000000..e89100c --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Ibiza_Sunset.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #ff6a00; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #ff6a00; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #ff6a00; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ff6a00+0,ee0979+100 */ + background: #ff6a00; /* Old browsers */ + background: -moz-linear-gradient(left, #ff6a00 0%, #ee0979 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #ff6a00 0%,#ee0979 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #ff6a00 0%,#ee0979 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff6a00', endColorstr='#ee0979',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ff6a00+0,ee0979+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(255,106,0,0.8) 0%, rgba(238,9,121,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(255,106,0,0.8) 0%,rgba(238,9,121,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(255,106,0,0.8) 0%,rgba(238,9,121,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ccff6a00', endColorstr='#ccee0979',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #ff7e22; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #ff7e22; +} + +/* Border light */ +.specialbutton:hover { + border-color: #ff7e22; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Little_Leaf.css b/styles/Milk_v2/theme/colour-presets/Little_Leaf.css new file mode 100644 index 0000000..054a74f --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Little_Leaf.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #8dc26f; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #8dc26f; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #8dc26f; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#8dc26f+0,76b852+100 */ + background: #8dc26f; /* Old browsers */ + background: -moz-linear-gradient(left, #8dc26f 0%, #76b852 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #8dc26f 0%,#76b852 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #8dc26f 0%,#76b852 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#8dc26f', endColorstr='#76b852',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#8dc26f+0,76b852+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(141,194,111,0.8) 0%, rgba(118,184,82,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(141,194,111,0.8) 0%,rgba(118,184,82,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(141,194,111,0.8) 0%,rgba(118,184,82,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc8dc26f', endColorstr='#cc76b852',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #9dd57d; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #9dd57d; +} + +/* Border light */ +.specialbutton:hover { + border-color: #9dd57d; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Neon_Blue.css b/styles/Milk_v2/theme/colour-presets/Neon_Blue.css new file mode 100644 index 0000000..557862a --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Neon_Blue.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #52D4FF; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #52D4FF; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #52D4FF; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { +/* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#52d4ff+0,0caade+100 */ +background: rgb(82,212,255); /* Old browsers */ +background: -moz-linear-gradient(left, rgba(82,212,255,1) 0%, rgba(12,170,222,1) 100%); /* FF3.6-15 */ +background: -webkit-linear-gradient(left, rgba(82,212,255,1) 0%,rgba(12,170,222,1) 100%); /* Chrome10-25,Safari5.1-6 */ +background: linear-gradient(to right, rgba(82,212,255,1) 0%,rgba(12,170,222,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#52d4ff', endColorstr='#0caade',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { +/* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#52d4ff+0,0caade+100&0.8+0,0.8+100 */ +background: -moz-linear-gradient(left, rgba(82,212,255,0.8) 0%, rgba(12,170,222,0.8) 100%); /* FF3.6-15 */ +background: -webkit-linear-gradient(left, rgba(82,212,255,0.8) 0%,rgba(12,170,222,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ +background: linear-gradient(to right, rgba(82,212,255,0.8) 0%,rgba(12,170,222,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc52d4ff', endColorstr='#cc0caade',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #76ddff; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #76ddff; +} + +/* Border light */ +.specialbutton:hover { + border-color: #76ddff; +} + diff --git a/styles/Milk_v2/theme/colour-presets/Nighthawk.css b/styles/Milk_v2/theme/colour-presets/Nighthawk.css new file mode 100644 index 0000000..4197945 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Nighthawk.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #2c3e50; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #2c3e50; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #2c3e50; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#2c3e50+0,2980b9+100 */ + background: #2c3e50; /* Old browsers */ + background: -moz-linear-gradient(left, #2c3e50 0%, #2980b9 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #2c3e50 0%,#2980b9 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #2c3e50 0%,#2980b9 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#2c3e50', endColorstr='#2980b9',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#2c3e50+0,2980b9+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(44,62,80,0.8) 0%, rgba(41,128,185,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(44,62,80,0.8) 0%,rgba(41,128,185,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(44,62,80,0.8) 0%,rgba(41,128,185,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc2c3e50', endColorstr='#cc2980b9',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #415b74; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #415b74; +} + +/* Border light */ +.specialbutton:hover { + border-color: #415b74; +} + diff --git a/styles/Milk_v2/theme/colour-presets/Pacific_Dream.css b/styles/Milk_v2/theme/colour-presets/Pacific_Dream.css new file mode 100644 index 0000000..2a64c54 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Pacific_Dream.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #0f3443; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #0f3443; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #0f3443; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#0f3443+0,34e89e+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(15,52,67,0.8) 0%, rgba(52,232,158,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(15,52,67,0.8) 0%,rgba(52,232,158,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(15,52,67,0.8) 0%,rgba(52,232,158,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc0f3443', endColorstr='#cc34e89e',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#0f3443+0,34e89e+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(15,52,67,0.8) 0%, rgba(52,232,158,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(15,52,67,0.8) 0%,rgba(52,232,158,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(15,52,67,0.8) 0%,rgba(52,232,158,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc0f3443', endColorstr='#cc34e89e',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #154558; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #154558; +} + +/* Border light */ +.specialbutton:hover { + border-color: #154558; +} + diff --git a/styles/Milk_v2/theme/colour-presets/Peach.css b/styles/Milk_v2/theme/colour-presets/Peach.css new file mode 100644 index 0000000..3597bad --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Peach.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #f8a697; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #f8a697; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #f8a697; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ffedbc+0,ed4264+100 */ + background: rgb(255,237,188); /* Old browsers */ + background: -moz-linear-gradient(left, rgba(255,237,188,1) 0%, rgba(237,66,100,1) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(255,237,188,1) 0%,rgba(237,66,100,1) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(255,237,188,1) 0%,rgba(237,66,100,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffedbc', endColorstr='#ed4264',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#ffedbc+0,ed4264+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(255,237,188,0.8) 0%, rgba(237,66,100,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(255,237,188,0.8) 0%,rgba(237,66,100,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(255,237,188,0.8) 0%,rgba(237,66,100,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ccffedbc', endColorstr='#cced4264',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #f4b2a6; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #f4b2a6; +} + +/* Border light */ +.specialbutton:hover { + border-color: #f4b2a6; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Purple_Love.css b/styles/Milk_v2/theme/colour-presets/Purple_Love.css new file mode 100644 index 0000000..b2bde48 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Purple_Love.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #B06AB3; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #B06AB3; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #B06AB3; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#b06ab3+0,4568dc+100 */ + background: rgb(176,106,179); /* Old browsers */ + background: -moz-linear-gradient(left, rgba(176,106,179,1) 0%, rgba(69,104,220,1) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(176,106,179,1) 0%,rgba(69,104,220,1) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(176,106,179,1) 0%,rgba(69,104,220,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#b06ab3', endColorstr='#4568dc',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#b06ab3+0,4568dc+100&0.8+1,0.8+100 */ + background: -moz-linear-gradient(left, rgba(176,106,179,0.8) 0%, rgba(175,106,179,0.8) 1%, rgba(69,104,220,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(176,106,179,0.8) 0%,rgba(175,106,179,0.8) 1%,rgba(69,104,220,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(176,106,179,0.8) 0%,rgba(175,106,179,0.8) 1%,rgba(69,104,220,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ccb06ab3', endColorstr='#cc4568dc',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #cd81d0; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #cd81d0; +} + +/* Border light */ +.specialbutton:hover { + border-color: #cd81d0; +} + diff --git a/styles/Milk_v2/theme/colour-presets/Rose_Water.css b/styles/Milk_v2/theme/colour-presets/Rose_Water.css new file mode 100644 index 0000000..0822c21 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Rose_Water.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #5fc3e4; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #5fc3e4; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #5fc3e4; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#5fc3e4+0,e55d87+100 */ + background: #5fc3e4; /* Old browsers */ + background: -moz-linear-gradient(left, #5fc3e4 0%, #e55d87 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #5fc3e4 0%,#e55d87 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #5fc3e4 0%,#e55d87 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#5fc3e4', endColorstr='#e55d87',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#5fc3e4+0,e55d87+100&0.8+1,0.8+100 */ + background: -moz-linear-gradient(left, rgba(95,195,228,0.8) 0%, rgba(96,194,227,0.8) 1%, rgba(229,93,135,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(95,195,228,0.8) 0%,rgba(96,194,227,0.8) 1%,rgba(229,93,135,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(95,195,228,0.8) 0%,rgba(96,194,227,0.8) 1%,rgba(229,93,135,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc5fc3e4', endColorstr='#cce55d87',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #78deff; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #78deff; +} + +/* Border light */ +.specialbutton:hover { + border-color: #78deff; +} + diff --git a/styles/Milk_v2/theme/colour-presets/Sahara.css b/styles/Milk_v2/theme/colour-presets/Sahara.css new file mode 100644 index 0000000..8f5f440 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Sahara.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #f9d423; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #f9d423; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #f9d423; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#f9d423+1,ff4e50+100 */ + background: #f9d423; /* Old browsers */ + background: -moz-linear-gradient(left, #f9d423 1%, #ff4e50 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #f9d423 1%,#ff4e50 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #f9d423 1%,#ff4e50 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f9d423', endColorstr='#ff4e50',GradientType=1 ); /* IE6-9 */ + +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#f9d423+0,ff4e50+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(249,212,35,0.8) 0%, rgba(255,78,80,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(249,212,35,0.8) 0%,rgba(255,78,80,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(249,212,35,0.8) 0%,rgba(255,78,80,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ccf9d423', endColorstr='#ccff4e50',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #f4d74b; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #f4d74b; +} + +/* Border light */ +.specialbutton:hover { + border-color: #f4d74b; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Titanium.css b/styles/Milk_v2/theme/colour-presets/Titanium.css new file mode 100644 index 0000000..e82b33d --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Titanium.css @@ -0,0 +1,50 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #859398; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #859398; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #859398; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#859398+0,283048+100 */ + background: #859398; /* Old browsers */ + background: -moz-linear-gradient(left, #859398 0%, #283048 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #859398 0%,#283048 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #859398 0%,#283048 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#859398', endColorstr='#283048',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#859398+0,283048+100&0.8+0,0.8+100 */ + background: -moz-linear-gradient(left, rgba(133,147,152,0.8) 0%, rgba(40,48,72,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(133,147,152,0.8) 0%,rgba(40,48,72,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(133,147,152,0.8) 0%,rgba(40,48,72,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc859398', endColorstr='#cc283048',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #9cacb2; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #9cacb2; +} + +/* Border light */ +.specialbutton:hover { + border-color: #9cacb2; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/colour-presets/Warm_Sunset.css b/styles/Milk_v2/theme/colour-presets/Warm_Sunset.css new file mode 100644 index 0000000..5d68398 --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/Warm_Sunset.css @@ -0,0 +1,51 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #4a569d; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #4a569d; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #4a569d; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#4a569d+1,dc2424+100 */ + background: #4a569d; /* Old browsers */ + background: -moz-linear-gradient(left, #4a569d 1%, #dc2424 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, #4a569d 1%,#dc2424 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, #4a569d 1%,#dc2424 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4a569d', endColorstr='#dc2424',GradientType=1 ); /* IE6-9 */ +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#4a569d+0,dc2424+100&0.8+1,0.8+100 */ + background: -moz-linear-gradient(left, rgba(74,86,157,0.8) 0%, rgba(75,86,156,0.8) 1%, rgba(220,36,36,0.8) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(left, rgba(74,86,157,0.8) 0%,rgba(75,86,156,0.8) 1%,rgba(220,36,36,0.8) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to right, rgba(74,86,157,0.8) 0%,rgba(75,86,156,0.8) 1%,rgba(220,36,36,0.8) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cc4a569d', endColorstr='#ccdc2424',GradientType=1 ); /* IE6-9 */ +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #5e6dc2; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #5e6dc2; +} + +/* Border light */ +.specialbutton:hover { + border-color: #5e6dc2; +} + diff --git a/styles/Milk_v2/theme/colour-presets/default.css b/styles/Milk_v2/theme/colour-presets/default.css new file mode 100644 index 0000000..e6ffc7e --- /dev/null +++ b/styles/Milk_v2/theme/colour-presets/default.css @@ -0,0 +1,43 @@ +/* Color */ +a:hover, .navbar_footer a, .copyright_bar a, .social_links_footer a:hover span, .icon.fa-file.icon-red, a:hover .icon.fa-file.icon-red, .navigation .active-subsection a, .navigation .active-subsection a:hover, .navigation a:hover, .tabs .tab > a:hover, .tabs .activetab > a, .tabs .activetab > a:hover, a.postlink, a.postlink:visited, .navbar_in_header .badge, .button:focus .icon, .button:hover .icon, .dark_base .social_links_footer a span, .dark_base h2, .dark_base h2 a, .dark_base a:link, .dark_base a:visited, .button-secondary:focus, .button-secondary:hover, .notification_unread { + color: #00ACFF; +} + +/* Background Solid */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, .jumpbox-cat-link, .dropdown-contents > li > a:hover, a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover, a.no_avatar:hover, .jumpbox-cat-link:hover, input.button1:focus, input.button2:focus, input.button3:focus, .specialbutton, input.specialbutton, .panel .specialbutton, a.specialbutton, .scrollToTop, a.specialbutton, .dark_base .social_links_footer a:hover span, .topic_type, .badge, .grid_unread { + background: #00ACFF; +} + +/* Borders Dark */ +.pagination li a:hover, .pagination li.active span, .pagination li a:focus, blockquote, .codebox code, .jumpbox-cat-link, a.postlink, input.button1:focus, input.button2:focus, input.button3:focus, input.specialbutton, .inputbox:hover, .inputbox:focus, .specialbutton, .button:hover, .button:focus, a.specialbutton, .dark_base .social_links_footer a span, .dark_base .social_links_footer a:hover span { + border-color: #00ACFF; +} + +/* Background Gradient */ +.headerbar, body.content_block_header_block li.header, body.content_block_header_stripe li.header:before, .no_avatar, .social_links_footer, .forumbg-table, .sidebar_block_stripe:before, .fancy_panel:before { + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#4a569d+1,dc2424+100 */ + background: #00ACFF; +} + + +/* The same as above, just with some opacity */ +.headerbar_overlay_active, .grid_colour_overlay:hover { + background-color: rgba(0, 172, 255, 0.8); +} + + +/* Colour Hover Light */ +.navbar_footer a:hover, .copyright_bar a:hover, a.postlink:hover, .dark_base h2 a:hover { + color: #45c2ff; +} + +/* Background Hover Light */ +a.scrollToTop:hover, input.specialbutton:hover, a.specialbutton:hover { + background-color: #45c2ff; +} + +/* Border light */ +.specialbutton:hover { + border-color: #45c2ff; +} + diff --git a/styles/Milk_v2/theme/colour-presets/index.htm b/styles/Milk_v2/theme/colour-presets/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/colours.css b/styles/Milk_v2/theme/colours.css new file mode 100644 index 0000000..16d7f49 --- /dev/null +++ b/styles/Milk_v2/theme/colours.css @@ -0,0 +1,1199 @@ +/* +-------------------------------------------------------------- +Colours and backgrounds for common.css +-------------------------------------------------------------- */ + +html, body { + color: #4C5D77; + background-color: #ECF0F1; +} + +body { + background-color: transparent; /* Required for parallax */ +} + +h1 { + color: #FFFFFF; +} + +h2 { + color: #28313F; +} + +h3 { + border-bottom-color: #CCCCCC; +} + +hr { + border-color: #FFFFFF; + border-top-color: #CCCCCC; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for links.css +-------------------------------------------------------------- */ + +a { color: #676767; } +a:hover { transition:.2s; } + +body.high_contrast_links .navbar_footer a, +body.high_contrast_links .copyright_bar a { + color: #CCCCCC !important; +} + +/* Links on gradient backgrounds */ +body.content_block_header_block .forumbg .header a, body.content_block_header_block .forabg .header a, th a {color: #FFFFFF;} +body.content_block_header_block .forumbg .header a:hover, body.content_block_header_block .forabg .header a:hover, th a:hover {color: #FFFFFF;} + +/* Links on light backgrounds */ + +/* Notification mark read link */ +.dropdown-extended a.mark_read { + background-color: #FFFFFF; +} + +/* Post body links */ +.postlink { + +} + +.postlink:visited { + +} + +.postlink:hover { + +} + +.signature a, .signature a:hover { + background-color: transparent; +} + +/* Back to top of page */ +.top i { + color: #999999; +} + +/* Arrow links */ +.arrow-left:hover, .arrow-right:hover { + color: #368AD2; +} + +/* Round cornered boxes and backgrounds +---------------------------------------- */ +.wrap { +} + +.headerbar { + color: #FFFFFF; +} + +.headerbar { +} + +.forabg, .forumbg, .viewtopic_wrapper { + background-color: #F8F8F8; +} + +.navbar { + background-color: #2D3039; +} + +.navbar_in_header { + background: none; +} + +.navbar a { + color: #bec4c9; +} + +.navbar_in_header ul.linklist > li > a { + color: #FFFFFF; + text-shadow: 0 1px 1px rgba(0,0,0,0.5); +} + +.panel { + background-color: #F9F9F9; + color: #28313F; +} + +.post:target .content { + color: #000000; +} + +.post:target h3 a { + color: #000000; +} + +.bg1 { + background-color: #FFFFFF; +} + +table.zebra-list tr:nth-child(odd) td, ul.zebra-list li:nth-child(odd) { + background-color: #FFFFFF; +} + +.bg2 { + background-color: #FFFFFF; +} + +table.zebra-list tr:nth-child(even) td, ul.zebra-list li:nth-child(even) { + background-color: #FFFFFF; +} + +.bg3 { + background-color: #F9F9F9; +} + +.ucprowbg { + background-color: #DCDEE2; +} + +.fieldsbg { + background-color: #E7E8EA; +} + +.site_logo { + background-image: url("./images/logo.png"); +} + +/* Horizontal lists +----------------------------------------*/ + +ul.navlinks { + border-top-color: #FFFFFF; +} + +/* Table styles +----------------------------------------*/ +table.table1 thead th { + color: #FFFFFF; +} + +table.table1 tbody tr { + border-color: #BFC1CF; +} + +table.table1 td { + color: #536482; +} + +table.table1 tbody td { + border-top-color: #FAFAFA; +} + +table.table1 tbody th { + border-bottom-color: #000000; + color: #333333; + background-color: #FFFFFF; +} + +table.info tbody th { + color: #000000; +} + +/* Misc layout styles +---------------------------------------- */ +dl.details dt { + color: #000000; +} + +dl.details dd { + color: #536482; +} + +.sep { + color: #1198D9; +} + +/* Icon styles +---------------------------------------- */ +.icon.icon-blue, a:hover .icon.icon-blue { + color: #196db5; +} + +.icon.icon-green, a:hover .icon.icon-green{ + color: #1b9A1B; +} + +.icon.icon-red, a:hover .icon.icon-red { + color: #BC2A4D; +} + +.icon.icon-orange, a:hover .icon.icon-orange{ + color: #FF6600; +} + +.icon.icon-bluegray, a:hover .icon.icon-bluegray{ + color: #536482; +} + +.icon.icon-gray, a:hover .icon.icon-gray{ + color: #777777; +} + +.icon.icon-lightgray, a:hover .icon.icon-lightgray{ + color: #999999; +} + +.icon.icon-black, a:hover .icon.icon-black{ + color: #333333; +} + +.alert_close .icon:before { + background-color: #FFFFFF; +} + +/* Jumpbox */ +.jumpbox .dropdown li { + border-top-color: #CCCCCC; +} + +.jumpbox-cat-link { + color: #FFFFFF; +} + +.jumpbox-cat-link:hover { + border-top-color: #12A3EB; + color: #FFFFFF; +} + +.jumpbox-forum-link { + background-color: #E1EBF2; +} + +.jumpbox-forum-link:hover { + background-color: #F6F4D0; +} + +.jumpbox .dropdown .pointer-inner { + border-color: #E1EBF2 transparent; +} + +.jumpbox-sub-link { + +} + +.jumpbox-sub-link:hover { +} + +/* Miscellaneous styles +---------------------------------------- */ + +.copyright { + color: #555555; +} + +.error { + color: #BC2A4D; +} + +.reported { + background-color: #F7ECEF !important; +} + +li.reported:hover { + background-color: #ECD5D8 !important; +} +.sticky, .announce { + /* you can add a background for stickies and announcements*/ +} + +div.rules { + background-color: #ECD5D8; + color: #BC2A4D; +} + +p.post-notice { + background-color: #ECD5D8; + background-image: none; + border-color: #BC2A4D; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for content.css +-------------------------------------------------------------- */ + +ul.forums { +} + +ul.topiclist li { +} + +ul.topiclist dd { + border-left-color: #FFFFFF; +} + +.rtl ul.topiclist dd { + border-right-color: #FFFFFF; + border-left-color: transparent; +} + +li.row {border-bottom-color: #ECF0F1; background-color: #FFFFFF;} +.forumlist_grid dl.row-item {border-bottom: 1px solid #ECF0F1;} + + +li.row strong { + color: #000000; +} + +li.row:hover { + +} + +li.row:hover dd { + border-left-color: #CCCCCC; +} + +.rtl li.row:hover dd { + border-right-color: #CCCCCC; + border-left-color: transparent; +} + +body.content_block_header_block li.header dt, body.content_block_header_block li.header dd { + color: #FFFFFF; +} + +/* Post body styles +----------------------------------------*/ +.postbody { + color: #333333; +} + +/* Content container styles +----------------------------------------*/ +.content { + color: #333333; +} + +.content h2, .panel h2 { + border-bottom-color: #CCCCCC; +} + +dl.faq dt { + color: #333333; +} + +.posthilit { + background-color: #F3BFCC; + color: #BC2A4D; +} + +.announce, .unreadpost { + /* Highlight the announcements & unread posts box */ +} + +/* Post signature */ +.signature { + border-top-color: #ECF0F1; +} + +/* Post noticies */ +.notice { + border-top-color: #CCCCCC; +} + +/* BB Code styles +----------------------------------------*/ +/* Quote block */ +blockquote { + background-color: #F8F8F8; +} + + +blockquote blockquote { + /* Nested quotes */ + background-color:#FFFFFF; +} + +blockquote blockquote blockquote { + /* Nested quotes */ + background-color: #F8F8F8; +} + +/* Code block */ +.codebox { + background-color: transparent; + border-color: #C9D2D8; +} + +.codebox p { + border-bottom-color: #CCCCCC; +} + +.codebox code { + color: #2E8B57; + background-color: #F8F8F8; + border-left: 5px solid; +} + +/* Attachments +----------------------------------------*/ +.attachbox { + background-color: #FFFFFF; + border-color: #C9D2D8; +} + +.pm-message .attachbox { + background-color: #F2F3F3; +} + +.attachbox dd { + border-top-color: #C9D2D8; +} + +.attachbox p { + color: #666666; +} + +.attachbox p.stats { + color: #666666; +} + +.attach-image img { + border-color: #999999; +} + +/* Inline image thumbnails */ + +dl.file dd { + color: #666666; +} + +dl.thumbnail img { + border-color: #666666; + background-color: #FFFFFF; +} + +dl.thumbnail dd { + color: #666666; +} + +dl.thumbnail dt a:hover { + background-color: #EEEEEE; +} + +dl.thumbnail dt a:hover img { + border-color: #368AD2; +} + +/* Post poll styles +----------------------------------------*/ + +fieldset.polls dl { + border-top-color: #DCDEE2; +} + +fieldset.polls dl.voted { + color: #000000; +} + +fieldset.polls dd div { + color: #FFFFFF; +} + +.rtl .pollbar1, .rtl .pollbar2, .rtl .pollbar3, .rtl .pollbar4, .rtl .pollbar5 { + border-right-color: transparent; +} + +.pollbar1 { + background-color: #AA2346; + border-bottom-color: #74162C; + border-right-color: #74162C; +} + +.rtl .pollbar1 { + border-left-color: #74162C; +} + +.pollbar2 { + background-color: #BE1E4A; + border-bottom-color: #8C1C38; + border-right-color: #8C1C38; +} + +.rtl .pollbar2 { + border-left-color: #8C1C38; +} + +.pollbar3 { + background-color: #D11A4E; + border-bottom-color: #AA2346; + border-right-color: #AA2346; +} + +.rtl .pollbar3 { + border-left-color: #AA2346; +} + +.pollbar4 { + background-color: #E41653; + border-bottom-color: #BE1E4A; + border-right-color: #BE1E4A; +} + +.rtl .pollbar4 { + border-left-color: #BE1E4A; +} + +.pollbar5 { + background-color: #F81157; + border-bottom-color: #D11A4E; + border-right-color: #D11A4E; +} + +.rtl .pollbar5 { + border-left-color: #D11A4E; +} + +/* Poster profile block +----------------------------------------*/ +.post { + border-color: #ECF0F1; +} + +.postprofile { + color: #666666; + border-color: #FFFFFF; +} + +.pm .postprofile { + border-color: #DDDDDD; +} + +.postprofile strong { + color: #000000; +} + +.online { + background-image: url("./en/icon_user_online.gif"); +} + +dd.profile-warnings { + color: #BC2A4D; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for buttons.css +-------------------------------------------------------------- */ +.button { + border-color: #edecec; + background-color: #F7F7F7; /* Old browsers */ /* FF3.6+ */ + color: #333333; +} + +.panel .button { + background-color: #FFFFFF; +} + + +.button:hover, +.button:focus { + +} + + +.button .icon, +.button-secondary { + color: #8f8f8f; +} + +.button-search:hover, +.button-search-end:hover { + border-color: #C7C3BF; +} + +.caret { border-color: #DADADA; } +.caret { border-color: #C7C3BF; } + +.contact-icons a { border-color: #DCDCDC; } +.contact-icons a:hover { background-color: #F2F6F9; } + +/* Pagination +---------------------------------------- */ + +.pagination li a { + background: #ECEDEE; + filter: none; + border-color: #B4BAC0; + box-shadow: none; + -webkit-box-shadow: none; + color: #5C758C; +} + +.pagination li a:hover, .pagination li a:hover .icon, .pagination li a:focus { + color: #FFFFFF; + text-shadow: none; +} + +.pagination li.ellipsis span { + background: transparent; + color: #000000; +} + +.pagination li.active span { + color: #FFFFFF; +} + +.pagination .dropdown-visible a.dropdown-trigger, .nojs .pagination .dropdown-container:hover a.dropdown-trigger { + background: #368AD2; + border-color: #368AD2; + filter: none; + color: #FFFFFF; + text-shadow: none; +} + +/* Search box +--------------------------------------------- */ + +.search-box .inputbox, +.search-box .inputbox:hover, +.search-box .inputbox:focus { + +} + +.search-header { + +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for cp.css +-------------------------------------------------------------- */ + +/* Main CP box +----------------------------------------*/ + + +.panel-container .panel li.row { + border-bottom-color: #B5C1CB; +} + +ul.cplist { + border-top-color: #B5C1CB; +} + +.content_block_header_block .panel-container .panel li.header dd, .content_block_header_block .panel-container .panel li.header dd a, .content_block_header_block .panel-container .panel li.header dt, .content_block_header_block .panel-container .panel li.header dt a { + color: #FFFFFF; +} + +.panel-container table.table1 thead th { + color: #FFFFFF; + border-bottom-color: #333333; +} + +.cp-main .pm-message { + border-color: #DBDEE2; + background-color: #FFFFFF; +} + +/* CP tabbed menu +----------------------------------------*/ +.tabs .tab > a { + background: #ecf0f1; + color: #536482; +} + +.tabs .tab > a:hover { + background: #F9F9F9; +} + +.tabs .activetab > a, +.tabs .activetab > a:hover { + background-color: #F9F9F9; + border-color: #F9F9F9; +} + +.tabs .activetab > a:hover { + color: #000000; +} + +/* Mini tabbed menu used in MCP +----------------------------------------*/ +.minitabs .tab > a { + background-color: #E1EBF2; +} + +.minitabs .activetab > a, +.minitabs .activetab > a:hover { + background-color: #FFFFFF; + color: #333333; +} + +/* Responsive tabs +----------------------------------------*/ +.responsive-tab .responsive-tab-link:before { + border-color: #536482; +} + +.responsive-tab .responsive-tab-link:hover:before { + border-color: #D31141; +} + +/* UCP navigation menu +----------------------------------------*/ + +/* Link styles for the sub-section links */ +.navigation a { + color: #333; + background: #ecf0f1; +} + +.rtl .navigation a { + background: #ecf0f1; +} + +.navigation a:hover { + background: #FFFFFF; +} + +.navigation .active-subsection a { + background: #FFFFFF; +} + +.navigation .active-subsection a, .navigation .active-subsection a:hover { +} + + + +/* Preferences pane layout +----------------------------------------*/ +.panel-container h2 { + color: #333333; +} + +.panel-container .panel { + background-color: #FFFFFF; +} + +.cp-main .pm { + background-color: #FFFFFF; +} + +/* Friends list */ +.cp-mini { + background-color: #F9F9F9; +} + +dl.mini dt { + color: #425067; +} + +/* PM Styles +----------------------------------------*/ +/* PM Message history */ +.current { + color: #000000 !important; +} + +/* PM marking colours */ +.pmlist li.pm_message_reported_colour, .pm_message_reported_colour { + border-left-color: #BC2A4D; + border-right-color: #BC2A4D; +} + +.pmlist li.pm_marked_colour, .pm_marked_colour { + border-color: #FF6600; +} + +.pmlist li.pm_replied_colour, .pm_replied_colour { + border-color: #A9B8C2; +} + +.pmlist li.pm_friend_colour, .pm_friend_colour { + border-color: #5D8FBD; +} + +.pmlist li.pm_foe_colour, .pm_foe_colour { + border-color: #000000; +} + +/* Avatar gallery */ +.gallery label { + background: #FFFFFF; + border-color: #CCC; +} + +.gallery label:hover { + background-color: #EEE; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for forms.css +-------------------------------------------------------------- */ + +/* General form styles +----------------------------------------*/ +select { + border-color: #edecec; + background-color: #F7F7F7; +} + +option.disabled-option { + color: graytext; +} + +/* Definition list layout for forms +---------------------------------------- */ +dd label { + color: #333; +} + +fieldset.fields1 { + background-color: transparent; +} + +/* Hover effects */ +fieldset dl:hover dt label { + color: #000000; +} + +fieldset.fields2 dl:hover dt label { + color: inherit; +} + +/* Quick-login on index page */ +fieldset.quick-login input.inputbox { + background-color: #F2F3F3; +} + +/* Posting page styles +----------------------------------------*/ + +.message-box textarea { + color: #333333; +} + +.message-box textarea.drag-n-drop { + outline-color: rgba(102, 102, 102, 0.5); +} + +.message-box textarea.drag-n-drop-highlight { + outline-color: rgba(17, 163, 234, 0.5); +} + +/* Input field styles +---------------------------------------- */ +.inputbox { + background-color: #F7F7F7; + border-color: #edecec; + color: #333333; +} + +.panel .inputbox { + background-color: #FFFFFF; +} + +.inputbox[type=text], .inputbox[type=password], input[type=submit] { + -webkit-appearance: none; + -webkit-border-radius: 0; +} + +.inputbox:-moz-placeholder { + color: #333333; +} + +.inputbox::-webkit-input-placeholder { + color: #333333; +} + +.inputbox:hover, .inputbox:focus { + +} + +.inputbox:focus:-moz-placeholder { + color: transparent; +} + +.inputbox:focus::-webkit-input-placeholder { + color: transparent; +} + + +/* Form button styles +---------------------------------------- */ + +a.button1, input.button1, input.button3, a.button2, input.button2 { + color: #717171; + background-color: #F7F7F7; + border-color: #D9D9D9; +} + +input.button3 { + background-image: none; +} + +/* button in the style of the form buttons */ +a.button1, a.button2 { + color: #FFFFFF; +} + +/* Hover states */ +a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover { + color: #FFFFFF; +} + +/* Focus states */ +input.button1:focus, input.button2:focus, input.button3:focus { + color: #FFFFFF; +} + +input.disabled { + color: #666666; +} + +.specialbutton, .specialbutton .icon, .specialbutton:hover, .specialbutton:hover .icon { + color: #FFFFFF !important; +} + +/* Approve and unapprove */ +.button_approve, .button_disapprove { + color: #FFFFFF !important; +} + +.button_approve {background: #27ae60 !important;} +.button_disapprove {background: #e74c3c !important;} + + + + +/* jQuery popups +---------------------------------------- */ +.phpbb_alert { + background-color: #FFFFFF; + border-color: #999999; +} +.darken { + background-color: #000000; +} + +.loading_indicator { + background-color: #000000; + background-image: url("./images/loading.gif"); +} + +.dropdown-extended ul li { + border-top-color: #B9B9B9; +} + +.dropdown-extended ul li:hover { + color: #000000; +} + +.dropdown-extended .header, .dropdown-extended .footer { + border-color: #B9B9B9; + color: #000000; +} + +.dropdown-extended .footer { + border-top-style: solid; + border-top-width: 1px; +} + +.dropdown-extended .header { + background-color: #FFFFFF; +} + +.dropdown .pointer { + border-color: #B9B9B9 transparent; +} + +.dropdown .pointer-inner { + border-color: #FFF transparent; +} + +.dropdown-extended .pointer-inner { + border-color: #FFFFFF transparent; +} + +.dropdown .dropdown-contents { + background: #fff; + border-color: #B9B9B9; + box-shadow: 1px 3px 5px rgba(0, 0, 0, 0.2); +} + +.dropdown-up .dropdown-contents { + box-shadow: 1px 0 5px rgba(0, 0, 0, 0.2); +} + +.dropdown li, .dropdown li li { + border-color: #DCDCDC; +} + +.dropdown li.separator { + border-color: #DCDCDC; +} + +/* Notifications +---------------------------------------- */ + +.notification_list p.notification-time { + color: #4C5D77; +} + +li.notification-reported strong, li.notification-disapproved strong { + color: #D31141; +} + +.badge { + color: #ffffff; +} + +.navbar_in_header .badge { + background: #FFFFFF; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for merlin.css +-------------------------------------------------------------- */ + +/* Headerbar +---------------------------------------- */ +.headerbar_overlay {} + +.headerbar_overlay_darken { + background-color: rgba(0,0,0,0.6); +} + +/* Global Containers +---------------------------------------- */ +.fancy_panel { + background-color: #FFF; +} + +body.content_block_header_stripe li.header { + background-color: #FFFFFF; +} + +.alt_block li.header dt, .alt_block .collapse-trigger a { + color: #FFFFFF; +} + + +/* Sidebar and Profile widgets +---------------------------------------- */ + + +/* Grid Forumlist +---------------------------------------- */ +.grid_desc { + background: #FFFFFF; +} + +.forumlist_grid .grid_colour_overlay { + color: #FFFFFF; + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#000000+0,000000+100&0+0,0.55+100 */ + background: -moz-linear-gradient(top, rgba(0,0,0,0) 0%, rgba(0,0,0,0.55) 100%); /* FF3.6-15 */ + background: -webkit-linear-gradient(top, rgba(0,0,0,0) 0%,rgba(0,0,0,0.55) 100%); /* Chrome10-25,Safari5.1-6 */ + background: linear-gradient(to bottom, rgba(0,0,0,0) 0%,rgba(0,0,0,0.55) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00000000', endColorstr='#8c000000',GradientType=0 ); /* IE6-9 */ + text-shadow: 0 1px 3px rgba(0,0,0,0.4); +} + +.forumlist_grid ul.forums li.row { + background: none; +} + +/* Custom Login Page +---------------------------------------- */ +.login_container_left_section_content { + -webkit-box-shadow: 0px 15px 30px 0px rgba(0,0,0,0.27); + -moz-box-shadow: 0px 15px 30px 0px rgba(0,0,0,0.27); + box-shadow: 0px 15px 30px 0px rgba(0,0,0,0.27); +} + +/* Postprofile Things +---------------------------------------- */ +.online_indicator { + color: #84BD00; +} + +/* Social Links +---------------------------------------- */ +.social_links_footer a span { + color: #FFFFFF; +} + +.social_links_footer a:hover span { + background-color: #FFFFFF; + border-color: #FFFFFF; +} + +/* Scroll to Top +---------------------------------------- */ +a.scrollToTop, a.scrollTopTop:hover { + color: #FFFFFF !important; +} + +/* Collapsible Panels +---------------------------------------- */ +body.content_block_header_block .forabg .collapse-trigger a, +body.content_block_header_block .forumbg .collapse-trigger a, +body.content_block_header_block .sidebar_box .collapse-trigger a, +body.content_block_header_block .forabg .collapse-trigger a:hover, +body.content_block_header_block .forumbg .collapse-trigger a:hover, +body.content_block_header_block .sidebar_box .collapse-trigger a:hover, +body.content_block_header_block .forabg .collapse-trigger a:focus, +body.content_block_header_block .forumbg .collapse-trigger a:focus, +body.content_block_header_block .sidebar_box .collapse-trigger a:focus { + color: #FFFFFF; +} + + +/* Missing Avatar Placeholder +---------------------------------------- */ +a.no_avatar, a.no_avatar:hover { + color: #FFFFFF; + text-shadow: 0 1px 3px rgba(0,0,0,0.5); +} + +/* Footer Bars +---------------------------------------- */ +.navbar_footer { + background-color: #2D3039; + color: #A4A4A7; +} + +.copyright_bar { + background-color: #1D1F25; + color: #A4A4A7; +} + +/* Index and Permissions Blocks (and sidebar, probably) +---------------------------------------- */ +.alt_block li.header { + background: #343840 !important; +} + + +/* Misc +---------------------------------------- */ +.topic_type, .grid_unread { + color: #FFFFFF; +} + +.topic_type a { + color: #FFFFFF !important; +} + +.sidebar_content, .profile_widget_list, .login_form_forgot_link, .forumlist_grid_block_stats { + border-top-color: #ECF0F1; +} + +p.author, p.author a { + color: #bec4c9; +} + +.dropdown-contents > li > a:hover { + color: #FFFFFF; + text-decoration: none; +} + +.viewtopic_wrapper .phpbb-ads-center { + background: #FFFFFF; +} diff --git a/styles/Milk_v2/theme/colours_dark.css b/styles/Milk_v2/theme/colours_dark.css new file mode 100644 index 0000000..03d07a4 --- /dev/null +++ b/styles/Milk_v2/theme/colours_dark.css @@ -0,0 +1,397 @@ +/* Dark Backgrounds */ +html { + background-color: #1c262f; +} + +.panel, .panel.bg2 { + background-color: #1c262f; +} + +.forabg, .forumbg, .viewtopic_wrapper { + background-color: #1c262f; +} + +.bg3 { + background-color: #1c262f; +} + +body.content_block_header_stripe li.header, .alt_block li.header { + background-color: #1c262f !important; +} + +div.rules { + background-color: #1c262f; +} + +a.button1, input.button1, input.button3, a.button2, input.button2, .inputbox, .button { + background-color: #1c262f; + border-color: #323f4e; +} + +.panel-container .panel .inputbox, .panel-container .panel select, .panel-container .panel .pm .button, .panel-container .panel .button-secondary, .panel .pm .button { + background-color: #1c262f; +} + +fieldset.quick-login input.inputbox { + background-color: #1c262f; +} + +.dropdown .dropdown-contents { + background-color: #1c262f; +} + +.pagination li a { + background-color: #1c262f; + border-color: #323f4e; +} + +/* So as to not blind users with super high contrast block of colour */ +.social_links_footer { + background: #1c262f !important; +} + +.social_links_footer a:hover span { + color: #1c262f !important; +} + +.topic_type { + background-color: #1c262f; +} + +.tabs .activetab > a, .tabs .activetab > a:hover { + background-color: #1c262f !important; + border-color: #1c262f; +} + +.navigation a, .tabs .tab > a:hover { + background-color: #1c262f; +} + + +.dropdown-extended .header { + background-color: #1c262f; + text-shadow: none; +} + +.dropdown-extended .header, .dropdown-extended .footer { + border-color: #323f4e; +} + +dl.attachbox, .codebox code, blockquote, blockquote blockquote blockquote { + background-color: #1c262f; +} + + +/* Light Backgrounds */ +/* Border color: 27313b */ +#inner-wrap { + background-color: #252f39; +} +.bg1, .bg2, .viewtopic_wrapper .phpbb-ads-center { + background-color: #252F39; +} + +li.row, .forumlist_grid .grid_desc { + background-color: #252f39; + border-color: #252F39; +} + +.panel-container .panel { + background-color: #252f39; +} + +.fancy_panel { + background-color: #252f39; +} + +.navigation a:hover { + background-color: #252f39; +} + +.panel .inputbox, .panel .button, select, .dropdown-contents .button2 { + background-color: #252F39; + border-color: #2c3742; +} + +.tabs .tab > a { + background-color: #252F39; +} + +.navigation .active-subsection a { + background-color: #252f39; +} + +.cp-mini { + background-color: #252f39; +} + +.cp-main .pm { + background-color: #252f39; +} + +blockquote blockquote { + background-color: #252f39; +} + + + +/* Light Neural Text */ + +/* Light Neutral Borders */ + +/* Stuff */ + +html, body, .copyright_bar, .navbar_footer, .content, .signature, .postprofile, .panel, dd label, .pm .postprofile, .postbody, .post:target .content { + color: #90a3c0; +} + +.alt_block li.header dt { + color: #90a3c0; +} + +.sidebar_content, .profile_widget_list, .login_form_forgot_link, .forumlist_grid_block_stats, .dropdown .dropdown-contents, hr, .signature, .postprofile, .pm .postprofile, .post, .jumpbox .dropdown li, .dropdown li, .dropdown li.separator, table.table1 tbody tr, table.table1 tbody td, fieldset.polls dl, dl.attachbox, dl.attachbox dd { + border-color: #323f4e; +} + +.panel-container .panel li.row { + border-bottom-color: #323f4e; +} + +.dropdown .pointer, .dropdown .pointer-inner { + border-color: #323f4e transparent !important; +} + +li.row strong, .postprofile strong, .button .icon, .button-secondary, .inputbox, .message-box textarea, a.button1, input.button1, input.button3, a.button2, input.button2, .inputbox, .button, fieldset dl:hover dt label, .topic_type, .icon.icon-gray, dl.faq dt, dl.details dt, .dropdown-extended .header, .dropdown-extended .footer, fieldset.polls dl.voted, .dark_base .forumlist_grid .grid_colour_overlay { + color: #e1edff; +} + + +.inputbox::-webkit-input-placeholder { + color: #e1edff; +} +.inputbox::-moz-placeholder { + color: #e1edff; +} +.inputbox:-ms-input-placeholder { + color: #e1edff; +} +.inputbox:-moz-placeholder { + color: #e1edff; +} + + +a.jumpbox-cat-link, th a { + color: #FFFFFF !important; +} + + +ul.cplist, .forumlist_grid dl.row-item { + border: none; +} + + + + + + + +/* Icons */ + +/* Forum icons & Topic icons */ +.global_read { + background-image: url("./images/icons/png/dark/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read.svg"); /* Modern browsers */ +} +.global_read_mine { + background-image: url("./images/icons/png/dark/icon_read_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_mine.svg"); /* Modern browsers */ +} +.global_read_locked { + background-image: url("./images/icons/png/dark/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked.svg"); /* Modern browsers */ +} +.global_read_locked_mine { + background-image: url("./images/icons/png/dark/icon_read_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked_mine.svg"); /* Modern browsers */ +} + +.global_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.global_unread_mine { + background-image: url("./images/icons/png/dark/icon_unread_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_mine.svg"); /* Modern browsers */ +} +.global_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.global_unread_locked_mine { + background-image: url("./images/icons/png/dark/icon_unread_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_locked_mine.svg"); /* Modern browsers */ +} + +.announce_read { + background-image: url("./images/icons/png/dark/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read.svg"); /* Modern browsers */ +} +.announce_read_mine { + background-image: url("./images/icons/png/dark/icon_read_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_mine.svg"); /* Modern browsers */ +} +.announce_read_locked { + background-image: url("./images/icons/png/dark/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked.svg"); /* Modern browsers */ +} +.announce_read_locked_mine { + background-image: url("./images/icons/png/dark/icon_read_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked_mine.svg"); /* Modern browsers */ +} + +.announce_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.announce_unread_mine { + background-image: url("./images/icons/png/dark/icon_unread_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_mine.svg"); /* Modern browsers */ +} +.announce_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.announce_unread_locked_mine { + background-image: url("./images/icons/png/dark/icon_unread_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_locked_mine.svg"); /* Modern browsers */ +} + +.forum_link { + background-image: url("./images/icons/png/forum_link.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/forum_link.svg"); /* Modern browsers */ +} + +.forum_read { + background-image: url("./images/icons/png/dark/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read.svg"); /* Modern browsers */ +} +.forum_read_locked { + background-image: url("./images/icons/png/dark/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked.svg"); /* Modern browsers */ +} +.forum_read_subforum { + background-image: url("./images/icons/png/dark/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read.svg"); /* Modern browsers */ +} + +.forum_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.forum_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.forum_unread_subforum { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} + + +.sticky_read { + background-image: url("./images/icons/png/dark/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read.svg"); /* Modern browsers */ +} +.sticky_read_mine { + background-image: url("./images/icons/png/dark/icon_read_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_mine.svg"); /* Modern browsers */ +} +.sticky_read_locked { + background-image: url("./images/icons/png/dark/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked.svg"); /* Modern browsers */ +} +.sticky_read_locked_mine { + background-image: url("./images/icons/png/dark/icon_read_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked_mine.svg"); /* Modern browsers */ +} + +.sticky_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.sticky_unread_mine { + background-image: url("./images/icons/png/dark/icon_unread_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_mine.svg"); /* Modern browsers */ +} +.sticky_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.sticky_unread_locked_mine { + background-image: url("./images/icons/png/dark/icon_unread_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_locked_mine.svg"); /* Modern browsers */ +} + +.topic_moved { + background-image: url("./images/icons/png/forum_link.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/forum_link.svg"); /* Modern browsers */ +} + +.topic_read { + background-image: url("./images/icons/png/dark/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read.svg"); /* Modern browsers */ +} +.topic_read_mine { + background-image: url("./images/icons/png/dark/icon_read_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_mine.svg"); /* Modern browsers */ +} +.topic_read_hot { + background-image: url("./images/icons/png/dark/icon_read_hot.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_hot.svg"); /* Modern browsers */ +} +.topic_read_hot_mine { + background-image: url("./images/icons/png/dark/icon_read_hot_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_hot_mine.svg"); /* Modern browsers */ +} +.topic_read_locked { + background-image: url("./images/icons/png/dark/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked.svg"); /* Modern browsers */ +} +.topic_read_locked_mine { + background-image: url("./images/icons/png/dark/icon_read_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read_locked_mine.svg"); /* Modern browsers */ +} + +.topic_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.topic_unread_mine { + background-image: url("./images/icons/png/dark/icon_unread_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_mine.svg"); /* Modern browsers */ +} +.topic_unread_hot { + background-image: url("./images/icons/png/dark/icon_unread_hot.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_hot.svg"); /* Modern browsers */ +} +.topic_unread_hot_mine { + background-image: url("./images/icons/png/dark/icon_unread_hot_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_hot_mine.svg"); /* Modern browsers */ +} +.topic_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.topic_unread_locked_mine { + background-image: url("./images/icons/png/dark/icon_unread_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_unread_locked_mine.svg"); /* Modern browsers */ +} + +.pm_read { + background-image: url("./images/icons/png/dark/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/dark/icon_read.svg"); /* Modern browsers */ +} +.pm_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/common.css b/styles/Milk_v2/theme/common.css new file mode 100644 index 0000000..634cebf --- /dev/null +++ b/styles/Milk_v2/theme/common.css @@ -0,0 +1,1230 @@ +/* General Markup Styles +---------------------------------------- */ +html { + /* Always show a scrollbar for short pages - stops the jump when the scrollbar appears. non-IE browsers */ + height: 101%; +} + +body { + line-height: normal; + margin: 0; + padding: 0; + word-wrap: break-word; + -webkit-print-color-adjust: exact; +} + +html, body { + height: 100%; +} + +h1 { + /* Forum name */ + margin-right: 200px; +} + +h2 { + /* Forum header titles */ + margin-bottom: 30px; +} + +h2.solo { + margin-bottom: 1em; +} + +h3 { + /* Sub-headers (also used as post headers, but defined later) */ + margin-bottom: 15px; +} + +h4 { + /* Forum and topic list titles */ +} + +p { + line-height: 1.3em; + margin-bottom: 1.5em; +} + +img { + border-width: 0; +} + +hr { + border: 0 solid transparent; + border-top-width: 1px; + height: 1px; + margin: 15px 0; + display: block; + clear: both; +} + +hr.dashed { + border-top-style: dashed; + margin: 10px 0; +} + +hr.divider { + display: none; +} + +p.right { + text-align: right; +} + +p.jumpbox-return { + margin-top: 10px; + margin-bottom: 0; + float: left; +} + +p.login_container_info { + font-size: 0.8em; +} + +u { + text-decoration: underline; +} + +ul { + list-style-type: disc; +} + +ol { + list-style-type: decimal; +} + +li { + display: list-item; +} + +ul ul, ol ul { + list-style-type: circle; +} + +ol ol ul, ol ul ul, ul ol ul, ul ul ul { + list-style-type: square; +} + +a:hover { text-decoration: underline; } + +/* Main blocks +---------------------------------------- */ +.wrap { + border-radius: 8px; + margin: 0 auto; + padding: 0; + width: 100%; +} + +.page-body { + margin: 0; + clear: both; +} + +.page-footer { + clear: both; +} + +.page-footer h3 { + margin-top: 20px; +} + +.logo { + display: block; + width: auto; + padding: 0; +} + +.logo:hover { + text-decoration: none; +} + +.site_logo { + display: inline-block; + width: 275px; + height: 51px; + background-size: cover; +} + +/* Site description and logo */ +.site-description { + text-align: center; + /* margin: 50px auto 100px; */ + position: relative; + z-index: 2; + display: inline-block; +} + +.site-description.logo_left { + text-align: left; + /* margin: 100px 50px; */ + float: left; +} + +.site-description.logo_right { + text-align: right; + /* margin: 100px 50px; */ + float: right; +} + +body.navbar_i_header .site-description {margin: 50px auto 100px;} +body.navbar_i_header .site-description.logo_left {margin: 100px 50px 100px;} +body.navbar_i_header .site-description.logo_right {margin: 100px 50px 100px;} + +body.navbar_o_header .site-description {margin: 50px auto 100px;} +body.navbar_o_header .site-description.logo_left {margin: 50px 50px 100px;} +body.navbar_o_header .site-description.logo_right {margin: 50px 50px 100px;} + +.site-description h1 { + margin-right: 0; + margin-top: 30px; +} + +.site-description p { + margin-bottom: 0; +} + +/* Round cornered boxes and backgrounds +---------------------------------------- */ +.headerbar { + border-radius: 0px; /* Must be defined in .parallax-mirror if parallax header is enabled */ + position: relative; +} + +#page-header { + position: relative; +} + +.navbar { + padding: 15px; + z-index: 10; + top: 0; + left: 0; + right: 0; + box-sizing: border-box; + position: relative; +} + +.navbar.navbar_in_header { + position: absolute; +} + +.forabg, .forumbg { + margin-bottom: 30px; + clear: both; +} + +.panel { + margin-bottom: 15px; + padding: 15px; +} + +.post { + padding: 15px; + margin-bottom: 10px; + background-repeat: no-repeat; + background-position: 100% 0; + position: relative; + border-bottom: 1px solid; +} + +.rowbg { + margin: 5px 5px 2px 5px; +} + +/* Horizontal lists +----------------------------------------*/ +.navbar ul.linklist { + padding: 2px 0; + list-style-type: none; +} + +ul.linklist { + display: block; + margin: 0; +} + +.cp-main .panel { + padding: 5px 10px; +} + +ul.linklist > li { + float: left; + line-height: 28px !important; + list-style-type: none; + margin-right: 15px; + width: auto; +} + +ul.linklist > li.rightside, p.rightside, a.rightside { + float: right; + margin-right: 0; + margin-left: 15px; + text-align: right; +} + +ul.navlinks { + +} + +ul#nav-breadcrumbs { + margin-bottom: 30px; +} + +ul.leftside { + float: left; + margin-left: 0; + margin-right: 5px; + text-align: left; +} + +ul.rightside { + float: right; + margin-left: 5px; + margin-right: -5px; + text-align: right; +} + +ul.linklist li.responsive-menu { + position: relative; + margin: 0 15px 0 0; +} + +ul.linklist li.rightside.responsive-menu { + margin-left: 15px; + margin-right: 0; +} + +.hasjs ul.linklist.leftside, .hasjs ul.linklist.rightside { + max-width: 48%; +} + +.hasjs ul.linklist.fullwidth { + max-width: none; +} + +li.responsive-menu.dropdown-right .dropdown { + left: -9px; +} + +li.responsive-menu.dropdown-left .dropdown { + right: -6px; +} + +ul.linklist .dropdown { + top: 45px; +} + +ul.linklist .dropdown-up .dropdown { + bottom: 18px; + top: auto; +} + +/* Bulletin icons for list items +----------------------------------------*/ +ul.linklist.bulletin > li:before { + display: inline-block; + content: "\2022"; + font-size: inherit; + line-height: inherit; + padding-right: 4px; +} + +ul.linklist.bulletin > li:first-child:before, +ul.linklist.bulletin > li.rightside:last-child:before { + content: none; +} + +ul.linklist.bulletin > li.no-bulletin:before { + content: none; +} + +.responsive-menu:before { + display: none !important; +} + +/* Profile in overall_header.html */ +.header-profile { + display: inline-block; + vertical-align: top; +} + +a.header-avatar, +a.header-avatar:hover { + text-decoration: none; +} + +a.header-avatar img { + margin-bottom: 2px; + max-height: 20px; + vertical-align: middle; + width: auto; +} + +a.header-avatar span:after { + content: '\f0dd'; + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + padding-left: 6px; + padding-top: 5px; + vertical-align: top; +} + +/* Dropdown menu +----------------------------------------*/ +.dropdown-container { + position: relative; +} + +.dropdown-container-right { + float: right; +} + +.dropdown-container-left { + float: left; +} + +.nojs .dropdown-container:hover .dropdown { + display: block !important; +} + +.dropdown { + display: none; + position: absolute; + left: 0; + top: 1.2em; + z-index: 5; + border: 1px solid transparent; + border-radius: 5px; + padding: 9px 0 0; + margin-right: -500px; +} + +.dropdown.live-search { + top: auto; +} + +.dropdown-container.topic-tools { + float: left; +} + +.dropdown-up .dropdown { + top: auto; + bottom: 1.2em; + padding: 0 0 9px; +} + +.dropdown-left .dropdown, .nojs .rightside .dropdown { + left: auto; + right: 0; + margin-left: -500px; + margin-right: 0; +} + +.dropdown-button-control .dropdown { + top: 40px; +} + +.dropdown-button-control.dropdown-up .dropdown { + top: auto; + bottom: 40px; +} + +.dropdown .pointer, .dropdown .pointer-inner { + position: absolute; + width: 0; + height: 0; + border-top-width: 0; + border-bottom: 10px solid transparent; + border-left: 10px dashed transparent; + border-right: 10px dashed transparent; + -webkit-transform: rotate(360deg); /* better anti-aliasing in webkit */ + display: block; +} + +.dropdown-up .pointer, .dropdown-up .pointer-inner { + border-bottom-width: 0; + border-top: 10px solid transparent; +} + +.dropdown .pointer { + right: auto; + left: 10px; + top: -1px; + z-index: 3; +} + +.dropdown-up .pointer { + bottom: -1px; + top: auto; +} + +.dropdown-left .dropdown .pointer, .nojs .rightside .dropdown .pointer { + left: auto; + right: 10px; +} + +.dropdown .pointer-inner { + top: auto; + bottom: -11px; + left: -10px; +} + +.dropdown-up .pointer-inner { + bottom: auto; + top: -11px; +} + +.dropdown .dropdown-contents { + z-index: 2; + overflow: hidden; + overflow-y: auto; + border: 1px solid transparent; + border-radius: 5px; + padding: 7px; + position: relative; + max-height: 300px; +} + +.dropdown-contents a { + display: block; + padding: 5px; +} + +.jumpbox { + margin: 5px 0; +} + +.jumpbox .dropdown li { + border-top: 1px solid transparent; +} + +.jumpbox .dropdown-select { + margin: 0; +} + +.jumpbox .dropdown-contents { + padding: 0; + text-decoration: none; +} + +.jumpbox .dropdown-contents li { + padding: 0; +} + +.jumpbox .dropdown-contents a { + margin-right: 20px; + padding: 5px 10px; + text-decoration: none; + width: 100%; +} + +.jumpbox .spacer { + display: inline-block; + width: 0px; +} + +.jumpbox .spacer + .spacer { + width: 20px; +} + +.dropdown-contents a { + display: block; + padding: 5px; +} + +.dropdown-contents a:hover { + border-radius: 4px; +} + +.jumpbox .dropdown-select { + margin: 0; +} + +.jumpbox .dropdown-contents a { + text-decoration: none; +} + +.dropdown li { + display: list-item; + border-top: 1px dotted transparent; + float: none !important; + line-height: normal !important; + list-style: none; + margin: 0; + white-space: nowrap; + text-align: left; +} + +.dropdown-contents > li { + padding-right: 15px; +} + +.dropdown-nonscroll > li { + padding-right: 0; +} + +.dropdown li:first-child, .dropdown li.separator + li, .dropdown li li { + border-top: 0; +} + +.dropdown li li:first-child { + margin-top: 4px; +} + +.dropdown li li:last-child { + padding-bottom: 0; +} + +.dropdown li li { + border-top: 1px dotted transparent; + padding-left: 18px; +} + +.wrap .dropdown li, .dropdown.wrap li, .dropdown-extended li { + white-space: normal; +} + +.dropdown li.separator { + border-top: 1px solid transparent; + padding: 0; +} + +.dropdown li.separator:first-child, .dropdown li.separator:last-child { + display: none !important; +} + +/* Responsive breadcrumbs +----------------------------------------*/ +.breadcrumbs .crumb { + float: left; + word-wrap: normal; +} + +.breadcrumbs .crumb:before { + content: '\2039'; + padding: 0 0.5em; +} + +.breadcrumbs .crumb:first-child:before { + content: none; +} + +.breadcrumbs .crumb a { + white-space: nowrap; + text-overflow: ellipsis; + vertical-align: bottom; + overflow: hidden; +} + +.breadcrumbs.wrapped .crumb a { letter-spacing: -.3px; } +.breadcrumbs.wrapped .crumb.wrapped-medium a { letter-spacing: -.4px; } +.breadcrumbs.wrapped .crumb.wrapped-tiny a { letter-spacing: -.5px; } + +.breadcrumbs .crumb.wrapped-max a { max-width: 120px; } +.breadcrumbs .crumb.wrapped-wide a { max-width: 100px; } +.breadcrumbs .crumb.wrapped-medium a { max-width: 80px; } +.breadcrumbs .crumb.wrapped-small a { max-width: 60px; } +.breadcrumbs .crumb.wrapped-tiny a { max-width: 40px; } + +/* Table styles +----------------------------------------*/ +table.table1 { + width: 100%; +} + +.ucp-main table.table1 { + padding: 2px; +} + +table.table1 thead th { + text-transform: uppercase; + line-height: 1.3em; + padding: 10px; +} + +table.table1 thead th span { + padding-left: 7px; +} + +table.table1 tbody tr { + border: 1px solid transparent; +} + +table.table1 tbody td { + padding: 10px; + border-top: 1px solid transparent; +} + +table.table1 tbody th { + padding: 5px; + border-bottom: 1px solid transparent; + text-align: left; +} + +/* Specific column styles */ +table.table1 .name { text-align: left; } +table.table1 .center { text-align: center; } +table.table1 .reportby { width: 15%; } +table.table1 .posts { text-align: center; width: 7%; } +table.table1 .joined { text-align: left; width: 15%; } +table.table1 .active { text-align: left; width: 15%; } +table.table1 .mark { text-align: center; width: 7%; } +table.table1 .info { text-align: left; width: 30%; } +table.table1 .info div { width: 100%; white-space: normal; overflow: hidden; } +table.table1 .autocol { line-height: 2em; white-space: nowrap; } +table.table1 thead .autocol { padding-left: 1em; } + +table.table1 span.rank-img { + float: right; + width: auto; +} + +table.info td { + padding: 3px; +} + +table.info tbody th { + padding: 3px; + text-align: right; + vertical-align: top; +} + +.forumbg table.table1 { + margin: 0; +} + +.forumbg-table > .inner { + margin: 0 -1px; +} + +.color_palette_placeholder table { + border-collapse: separate; + border-spacing: 1px; +} + +/* Misc layout styles +---------------------------------------- */ +/* column[1-2] styles are containers for two column layouts */ +.column1 { + float: left; + clear: left; + width: 49%; +} + +.column2 { + float: right; + clear: right; + width: 49%; +} + +/* General classes for placing floating blocks */ +.left-box { + float: left; + width: auto; + text-align: left; + max-width: 100%; +} + +.left-box.profile-details { + width: 80%; +} + +.right-box { + float: right; + width: auto; + text-align: right; + max-width: 100%; +} + +dl.details dt { + float: left; + clear: left; + width: 30%; + text-align: right; + display: block; +} + +dl.details dd { + margin-left: 0; + padding-left: 5px; + margin-bottom: 5px; + float: left; + width: 65%; + overflow: hidden; + text-overflow: ellipsis; +} + +.clearfix, fieldset dl, ul.topiclist dl, dl.polls { + overflow: hidden; +} + +fieldset.fields1 ul.recipients { + list-style-type: none; + line-height: 1.8; + max-height: 150px; + overflow-y: auto; +} + +fieldset.fields1 dd.recipients { + clear: left; + margin-left: 1em; +} + +fieldset.fields1 ul.recipients input.button2{ + margin-right: 0; + padding: 0; +} + +fieldset.fields1 dl.pmlist > dt { + width: auto !important; +} + +fieldset.fields1 dl.pmlist dd.recipients { + margin-left: 0 !important; +} + +/* Action-bars (container for post/reply buttons, pagination, etc.) +---------------------------------------- */ +.action-bar { + margin: 0 0 30px; +} + +.action-bar.bar-bottom { + margin-top: 30px; +} + +.forabg + .action-bar { + margin-top: 2em; +} + +.action-bar .button { + margin-right: 15px; + float: left; +} + +.action-bar .button-search { + margin-right: 0; +} + +/* Pagination +---------------------------------------- */ +.pagination { + float: right; + text-align: right; + width: auto; +} + +.action-bar .pagination { + margin: 3px 0; +} + +.action-bar.bar-bottom .pagination { + margin-top: 0; +} + +.action-bar .pagination .button { + margin-right: 0; + float: none; +} + +.pagination > ul { + display: inline-block; + list-style: none !important; + margin-left: 5px; +} + +.pagination > ul > li { + display: inline-block !important; + padding: 0; + line-height: normal; + vertical-align: middle; +} + +.pagination li a, .pagination li span { + padding: 2px 5px; +} + +.pagination li.active span { + display: inline-block; + line-height: 1.4; + text-align: center; + white-space: nowrap; + vertical-align: middle; + border: 1px solid transparent; +} + +.pagination li.ellipsis span { + border: none; + padding: 0; +} + +.pagination li.page-jump { + margin-right: 5px; +} + +.pagination li.page-jump a { + padding: 0 8px; +} + +.pagination .arrow a { + padding: 2px 0; +} + +/* Pagination in viewforum for multipage topics */ +.row .pagination { + display: block; + margin-top: 3px; + margin-bottom: 3px; +} + +.row .pagination > ul { + margin: 0; +} + +.row .pagination li a, .row .pagination li span { + padding: 1px 5px; +} + +/* jQuery popups +---------------------------------------- */ +.phpbb_alert { + border: 1px solid transparent; + display: none; + left: 0; + padding: 0 25px 20px 25px; + position: fixed; + right: 0; + top: 150px; + z-index: 50; + width: 620px; + margin: 0 auto; +} + +@media only screen and (max-height: 500px), only screen and (max-device-width: 500px) +{ + .phpbb_alert { + top: 25px; + } +} + +.phpbb_alert .alert_close { + float: right; + margin-right: -36px; + margin-top: -8px; +} + +.phpbb_alert p { + margin: 8px 0; + padding-bottom: 8px; +} + +.phpbb_alert label { + display: block; + margin: 8px 0; + padding-bottom: 8px; +} + +.darkenwrapper { + display: none; + position: relative; + z-index: 44; +} + +.darken { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0.5; + z-index: 45; +} + +.loading_indicator { + background: center center no-repeat; + border-radius: 5px; + display: none; + opacity: 0.8; + margin-top: -50px; + margin-left: -50px; + height: 50px; + width: 50px; + position: fixed; + left: 50%; + top: 50%; + z-index: 51; +} + +/* Miscellaneous styles +---------------------------------------- */ +.copyright { + padding: 5px; + text-align: center; + +} + +.titlespace { + margin-bottom: 15px; +} + +.headerspace { + +} + +.error { + margin-bottom: 15px !important; +} + +div.rules { + margin-bottom: 30px; + padding: 15px; + border-left: 5px solid; +} + +div.rules ul, div.rules ol { + margin-left: 20px; +} + +p.post-notice { + position: relative; + padding: 15px; + min-height: 14px; + margin: 30px 0; + border-left: 5px solid; +} + +form > p.post-notice strong { + line-height: 20px; +} + +.stat-block { + clear: both; +} + +.top-anchor { + display: block; + position: absolute; + top: -20px; +} + +.clear { + display: block; + clear: both; + font-size: 1px; + line-height: 1px; + background: transparent; +} + +/* Inner box-model clearing */ +.inner:after, +ul.linklist:after, +.action-bar:after, +.notification_text:after, +.tabs-container:after, +.tabs > ul:after, +.minitabs > ul:after, +.postprofile .avatar-container:after { + clear: both; + content: ''; + display: block; +} + +.emoji { + min-height: 18px; + min-width: 18px; + height: 1em; + width: 1em; +} + +.smilies { + vertical-align: text-bottom; +} + +.icon-notification { + position: relative; +} + +.member-search { + float: left; + margin: 0; + padding: 6px 10px; +} + +.dropdown-extended { + display: none; + z-index: 1; +} + +.dropdown-extended ul { + max-height: 350px; + overflow-y: auto; + overflow-x: hidden; + clear: both; +} + +.dropdown-extended ul li { + padding: 0; + margin: 0 !important; + float: none; + border-top: 1px solid; + list-style-type: none; + clear: both; + position: relative; +} + +.dropdown-extended ul li:first-child { + border-top: none; +} + +.dropdown-extended ul li.no_notifications { + padding: 10px; +} + +.dropdown-extended .dropdown-contents { + max-height: none; + padding: 0; + position: absolute; + width: 340px; +} + +.nojs .dropdown-extended .dropdown-contents { + position: relative; +} + +.dropdown-extended .header { + padding: 0 10px; + text-align: left; + text-shadow: 1px 1px 1px white; + text-transform: uppercase; + line-height: 39px; + border-bottom: 1px solid; + border-radius: 5px 5px 0 0; +} + +.dropdown-extended .header .header_settings { + float: right; + text-transform: none; +} + +.dropdown-extended .header .header_settings a { + display: inline-block; + padding: 0 5px; +} + +.dropdown-extended .header:after { + content: ''; + display: table; + clear: both; +} + +.dropdown-extended .footer { + text-align: center; +} + +.dropdown-extended ul li a, .dropdown-extended ul li.no-url { + padding: 8px; +} + +.dropdown-extended .footer > a { + padding: 5px 0; +} + +.dropdown-extended ul li a, .notification_list dt > a, .dropdown-extended .footer > a { + display: block; + text-decoration: none; +} + +.notification_list ul li img { + float: left; + max-height: 50px; + max-width: 50px; + width: auto !important; + height: auto !important; + margin-right: 5px; +} + +.notification_list ul li p { + margin-bottom: 4px; +} + +.notification_list p.notification-reference, +.notification_list p.notification-location, +.notification_list li a p.notification-reason { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.notification_list p.notification-time { + margin: 0; + text-align: right; +} + +.notification_list div.notifications { + margin-left: 50px; + padding: 5px; +} + +.notification_list div.notifications a { + display: block; +} + +.notification_text { + margin-left: 58px; +} + +.badge { + border-radius: 10px; + text-align: center; + white-space: nowrap; + line-height: 1; + float: right; + display: inline-block; + margin-left: 3px; + vertical-align: baseline; + position: relative; + top: 7px; + padding: 4px 6px; +} + + + +.badge.hidden { + display: none; +} + +/* Navbar specific list items +----------------------------------------*/ + +.linklist .quick-links { + margin: 0 30px 0 0; +} + +.linklist.compact .rightside > a > span { + display: none; +} + +.dropdown-page-jump .dropdown { + top: 20px; +} + +.dropdown-page-jump.dropdown-up .dropdown { + bottom: 20px; +} + +.dropdown-page-jump input.tiny { + width: 50px; +} + +.dropdown .clone.hidden { + display: none; +} + +.dropdown .clone.hidden + li.separator { + display: none; +} + +.dropdown .clone.hidden + li { + border-top: none; +} diff --git a/styles/Milk_v2/theme/content.css b/styles/Milk_v2/theme/content.css new file mode 100644 index 0000000..5512065 --- /dev/null +++ b/styles/Milk_v2/theme/content.css @@ -0,0 +1,791 @@ +/* Content Styles +---------------------------------------- */ + +ul.forums, ul.topics { + padding: 10px 10px 5px; +} + +ul.topiclist { + display: block; + list-style-type: none; + margin: 0; +} + +ul.topiclist li { + display: block; + list-style-type: none; + margin: 0; +} + +.content_block_header_block ul.topiclist li.header { + +} + +ul.topiclist dl { + position: relative; +} + +ul.topiclist li.row dl { + +} + +ul.topiclist dt, ul.topiclist dd { + display: block; + float: left; +} + ++ul.topiclist li.row dd { + padding: 4px 0 999px 0; + margin-bottom: -995px; +} + +ul.topiclist dt { + width: 100%; + margin-right: -490px; +} + +ul.topiclist.missing-column dt { + margin-right: -395px; +} + +ul.topiclist.missing-column li.header dt { + margin-right: -425px; +} + +ul.topiclist.two-long-columns dt { + margin-right: -300px; +} + +ul.topiclist.two-long-columns li.header dt { + margin-right: -330px; +} + +ul.topiclist.two-columns dt { + margin-right: -95px; +} + +ul.topiclist dt .list-inner { + margin-right: 490px; + padding-left: 5px; + padding-right: 5px; +} + +ul.topiclist.missing-column dt .list-inner { + margin-right: 395px; +} + +ul.topiclist.two-long-columns dt .list-inner { + margin-right: 250px; +} + +ul.topiclist.two-columns dt .list-inner { + margin-right: 80px; +} + +ul.topiclist.two-columns li.header dt { + margin-right: -130px; +} + +ul.topiclist dd { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +ul.topiclist dfn { + /* Labels for post/view counts */ + position: absolute; + left: -999px; + width: 990px; +} + +.list-inner img { + max-width: 100%; +} + +.forum-image { + float: left; + padding-top: 5px; + margin-right: 5px; +} + +li.row { + border-bottom: 1px solid transparent; + padding: 15px; + margin-bottom: 5px !important; +} + +li.header dt, li.header dd { + line-height: 1em; + border-left-width: 0; + margin: 14px 0 14px 0; + padding-top: 2px; + padding-bottom: 2px; +} + +li.header dt { + width: 100%; + margin-right: -520px; + padding-left: 15px; +} + +.forabg li.header dt , .forumbg li.header dt { + margin-right: -530px; /* Accounts for 10px forumlist padding */ +} + +li.header dt .list-inner { + margin-right: 440px; +} + +li.header dd { + padding-left: 1px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +li.header dl.row-item dt, li.header dl.row-item dd { + min-height: 0; +} + +li.header dl.row-item dt .list-inner { + /* Tweak for headers alignment when folder icon used */ + padding-left: 0; + padding-right: 50px; +} + +/* Forum list column styles */ + + +dl.row-item { + background-position: 12px 50%; /* Position of folder icon */ + background-repeat: no-repeat; +} + +dl.row-item dt { + background-repeat: no-repeat; + background-position: 5px 95%; /* Position of topic icon */ +} + +dl.row-item dt .list-inner { + padding-left: 68px; /* Space for folder icon */ +} + +dl.row-item dt, dl.row-item dd { + min-height: 35px; +} + +dl.row-item dt a { + display: inline; +} + +dl a.row-item-link { /* topic row icon links */ + display: block; + width: 30px; + height: 30px; + padding: 0; + position: absolute; + top: 50%; + left: 0; + margin-top: -15px; + margin-left: 9px; +} + +dd.posts, dd.topics, dd.views, dd.extra, dd.mark { + width: 80px; + text-align: center; + line-height: 2.2em; +} + +dd.posts, dd.topics, dd.views { + width: 95px; +} + +/* List in forum description */ +dl.row-item dt ol, +dl.row-item dt ul { + list-style-position: inside; + margin-left: 1em; +} + +dl.row-item dt li { + display: list-item; + list-style-type: inherit; +} + +dd.lastpost, dd.redirect, dd.moderation, dd.time, dd.info { + width: 300px; +} + +dd.redirect { + line-height: 2.5em; +} + +dd.time { + line-height: 200%; +} + +dd.lastpost > span, ul.topiclist dd.info > span, ul.topiclist dd.time > span, dd.redirect > span, dd.moderation > span { + display: block; + padding-left: 5px; +} + +dd.extra, dd.mark { + line-height: 200%; +} + +/* Post body styles +----------------------------------------*/ +.postbody { + padding: 0; + line-height: 1.48em; + width: 76%; + float: left; + position: relative; +} + +/* Merlin Framework Adjustment */ +.postprofile_Left .postbody { + float: right; +} + +.postbody h3 { + /* Postbody requires a different h3 format - so change it here */ + float: left; + padding: 2px 0 0 0; + margin-top: 0 !important; + margin-bottom: 0.3em !important; + text-transform: none; + border: none; + line-height: 125%; +} + +.postbody h3 img { + vertical-align: bottom; +} + +.has-profile .postbody h3 { + /* If there is a post-profile, we position the post-buttons differently */ + float: none !important; + margin-right: 180px; +} + +.postbody .content { + overflow-x: auto; +} + +.postbody img.postimage { + max-width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.search .postbody { + width: 68% +} + +/* Topic review panel +----------------------------------------*/ +.panel .review { + margin-top: 2em; +} + +.topicreview { + padding-right: 5px; + overflow: auto; + height: 300px; +} + +.topicreview .postbody { + width: auto; + float: none; + margin: 0; + height: auto; +} + +.topicreview .post { + height: auto; +} + +.topicreview h2 { + border-bottom-width: 0; +} + +.post-ignore .postbody { + display: none; +} + +/* MCP Post details +----------------------------------------*/ +.post_details { + /* This will only work in IE7+, plus the others */ + overflow: auto; + max-height: 300px; +} + +/* Content container styles +----------------------------------------*/ +.content { + clear: both; + min-height: 3em; + overflow: hidden; + line-height: 1.4em; + padding-bottom: 1px; +} + +.content h2, .panel h2 { + margin-bottom: 30px; +} + +.panel h3 { + margin: 15px 0 15px 0; +} + +.panel p { + margin-bottom: 1em; + line-height: 1.4em; +} + +.content p { + margin-bottom: 1em; + line-height: 1.4em; +} + +dl.faq { + margin-top: 1em; + margin-bottom: 2em; + line-height: 1.4em; +} + + +.content dl.faq { + margin-bottom: 0.5em; +} + +.content li { + list-style-type: inherit; +} + +.content ul, .content ol { + margin: 0.8em 0 0.9em 3em; +} + +.posthilit { + padding: 0 2px 1px 2px; +} + +/* Post author */ +p.author { + margin-bottom: 0.6em; + padding: 15px 0 30px 0; + line-height: 1.2em; + clear: both; +} + +/* Post signature */ +.signature { + margin-top: 15px; + padding-top: 15px; + border-top: 1px solid transparent; + clear: left; + line-height: 140%; + overflow: hidden; + width: 100%; +} + +.signature.standalone { + border-top-width: 0; + margin-top: 0; +} + +dd .signature { + margin: 0; + padding: 0; + clear: none; + border: none; +} + +.signature li { + list-style-type: inherit; +} + +.signature ul, .signature ol { + margin: 0.8em 0 0.9em 3em; +} + +/* Post noticies */ +.notice { + width: auto; + margin-top: 1.5em; + padding-top: 0.2em; + border-top: 1px dashed transparent; + clear: left; + line-height: 130%; +} + +/* Jump to post link for now */ +ul.searchresults { + list-style: none; + text-align: right; + clear: both; +} + +/* BB Code styles +----------------------------------------*/ +/* Quote block */ +blockquote { + background: transparent none 6px 8px no-repeat; + margin: 30px 1px 30px 25px; + overflow: hidden; + padding: 15px; + border-left: 5px solid; +} + +blockquote blockquote { + /* Nested quotes */ + margin: 15px 1px 20px 15px; +} + +blockquote cite { + /* Username/source of quoter */ + margin-bottom: 5px; + display: block; +} + +blockquote cite:before { + content: '\f10d'; + font-family: FontAwesome; + font-weight: 300; + margin-right: 5px; +} + +blockquote.uncited { + padding-top: 25px; +} + +blockquote cite > div { + float: right; +} + +.postbody .content li blockquote { + overflow: inherit; + margin-left: 0; +} + +/* Code block */ +.codebox { + padding: 0px; + margin: 30px 0; +} + +.codebox p { + text-transform: uppercase; + margin-bottom: 3px; + display: block; +} + +blockquote .codebox { + margin-left: 0; +} + +.codebox code { + overflow: auto; + display: block; + height: auto; + max-height: 200px; + padding-top: 5px; + line-height: 1.3em; + margin: 2px 0; + padding: 20px; +} + +/* Attachments +----------------------------------------*/ +.attachbox { + float: left; + width: auto; + max-width: 100%; + margin: 5px 5px 5px 0; + padding: 6px; + border: 1px dashed transparent; + clear: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* Merlin Framework Adjustment */ +.postprofile_Left .attachbox { + float: right; + clear: right; +} + +.attachbox dt { + text-transform: uppercase; +} + +.attachbox dd { + margin-top: 4px; + padding-top: 4px; + clear: left; + border-top: 1px solid transparent; + overflow-x: auto; +} + +.attachbox dd dd { + border: none; +} + +.attachbox p { + line-height: 110%; + clear: left; +} + +.attachbox p.stats +{ + line-height: 110%; + clear: left; +} + +.attach-image { + margin: 3px 0; + max-width: 100%; +} + +.attach-image img { +/* cursor: move; */ + cursor: default; +} + +/* Inline image thumbnails */ +div.inline-attachment dl.thumbnail, div.inline-attachment dl.file { + display: block; + margin-bottom: 4px; +} + +dl.file { + display: block; +} + +dl.file dt { + text-transform: none; + margin: 0; + padding: 0; +} + +dl.file dd { + margin: 0; + padding: 0; +} + +dl.thumbnail img { + padding: 3px; + border: 1px solid transparent; + box-sizing: border-box; +} + +dl.thumbnail dt a:hover img { + border: 1px solid transparent; +} + +/* Post poll styles +----------------------------------------*/ +fieldset.polls dl { + border-top: 1px solid transparent; + padding: 10px 0; + line-height: 120%; +} + +fieldset.polls dt { + text-align: left; + float: left; + display: block; + width: 30%; + border-right: none; + padding: 0; + margin: 0; +} + +fieldset.polls dd { + float: left; + width: 10%; + border-left: none; + padding: 0 5px; + margin-left: 0; +} + +fieldset.polls dd.resultbar { + width: 50%; +} + +fieldset.polls dd input { + margin: 2px 0; +} + +fieldset.polls dd div { + text-align: right; + padding: 2px 2px 0 2px; + overflow: visible; + min-width: 8px; +} + +.pollbar1, .pollbar2, .pollbar3, .pollbar4, .pollbar5 { + border-bottom: 1px solid transparent; + border-right: 1px solid transparent; +} + +.vote-submitted { + text-align: center; +} + +/* Poster profile block +----------------------------------------*/ +.postprofile { + margin: 5px 0 10px 0; + min-height: 80px; + border: 1px solid transparent; + border-width: 0 0 0 1px; + width: 22%; + float: right; + display: inline; + text-align: center; +} + +/* Merlin Framework Adjustment */ +.postprofile_Left .postprofile { + border-width: 0 1px 0 0; + float: left; +} + +.postprofile dd, .postprofile dt { + line-height: 1.2em; +} + +/* Merlin Framework Adjustment */ +.postprofile_Horizontal .postprofile dt{ + margin-left: 0; +} + +.postprofile dd { + overflow: hidden; + text-overflow: ellipsis; +} + +.postprofile dt.no-profile-rank, .postprofile dd.profile-rank, .postprofile .search-result-date { + margin-bottom: 10px; +} + +/* Post-profile avatars */ +.postprofile .has-avatar .avatar-container { + margin-bottom: 15px; + overflow: hidden; +} + +.postprofile span .avatar { + display: block; + max-width: 100%; + text-align: center; /* gravatar fix */ + margin: 0 auto; +} + +.postprofile .avatar img { + display: block; + height: auto !important; + max-width: 100%; +} + +dd.profile-contact { + overflow: visible; +} + +.profile-contact .dropdown-container { + display: inline-block; +} + +.profile-contact .icon_contact { + vertical-align: middle; +} + +.profile-contact .dropdown { + margin-right: -14px; +} + +.online { + background-image: none; + background-position: 100% 0; + background-repeat: no-repeat; +} + +/* Poster profile used by search*/ +.search .postprofile { + width: 30%; +} + +/* Profile used on view-profile */ +.profile-avatar img { + max-width: 100%; +} + +/* pm list in compose message if mass pm is enabled */ +dl.pmlist dt { + width: 60% !important; +} + +dl.pmlist dt textarea { + width: 95%; +} + +dl.pmlist dd { + margin-left: 61% !important; + margin-bottom: 2px; +} + +.action-bar div.dl_links { + padding: 10px 0 0 10px; +} + +div.dl_links { + display: inline-block; + text-transform: none; +} + +.dl_links ul { + list-style-type: none; + margin: 0; + display: inline-block; +} + +.dl_links li { + display: inline-block; +} + +.attachment-filename { + width: 100%; +} + +.ellipsis-text { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +table.fixed-width-table { + table-layout: fixed; +} + +/* Show scrollbars for items with overflow on iOS devices +----------------------------------------*/ +.postbody .content::-webkit-scrollbar, .topicreview::-webkit-scrollbar, .post_details::-webkit-scrollbar, .codebox code::-webkit-scrollbar, .attachbox dd::-webkit-scrollbar, .attach-image::-webkit-scrollbar, .dropdown-extended ul::-webkit-scrollbar { + width: 8px; + height: 8px; + -webkit-appearance: none; + background: rgba(0, 0, 0, .1); + border-radius: 3px; +} + +.postbody .content::-webkit-scrollbar-thumb, .topicreview::-webkit-scrollbar-thumb, .post_details::-webkit-scrollbar-thumb, .codebox code::-webkit-scrollbar-thumb, .attachbox dd::-webkit-scrollbar-thumb, .attach-image::-webkit-scrollbar-thumb, .dropdown-extended ul::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, .3); + border-radius: 3px; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/cp.css b/styles/Milk_v2/theme/cp.css new file mode 100644 index 0000000..a506978 --- /dev/null +++ b/styles/Milk_v2/theme/cp.css @@ -0,0 +1,338 @@ +/* Control Panel Styles +---------------------------------------- */ + + +/* Main CP box +----------------------------------------*/ +.cp-menu { + float:left; + width: 19%; + margin-top: 1em; + margin-bottom: 5px; +} + +.cp-main { + float: left; + width: 81%; +} + +.cp-main .content { + padding: 0; +} + +.panel-container .panel ol { + margin-left: 2em; +} + +.panel-container .panel li.row { + border-bottom: 1px solid transparent; + border-top: 1px solid transparent; +} + +ul.cplist { + margin-bottom: 30px; + border-top: 1px solid transparent; +} + +.panel-container .panel li.header dd, .panel-container .panel li.header dt { + margin-bottom: 15px; +} + +.panel-container table.table1 { + margin-bottom: 1em; +} + +.panel-container table.table1 thead th { + border-bottom: 1px solid transparent; + padding: 5px; +} + +.panel-container table.table1 tbody th { + background-color: transparent !important; + border-bottom: none; +} + +.cp-main .pm-message { + border: 1px solid transparent; + margin: 10px 0; + width: auto; + float: none; +} + +.pm-message h2 { + padding-bottom: 5px; +} + +.cp-main .postbody h3, .cp-main .box2 h3 { + margin-top: 0; +} + +.cp-main .buttons { + margin-left: 0; +} + +.cp-main ul.linklist { + margin: 0; +} + +/* MCP Specific tweaks */ +.mcp-main .postbody { + width: 100%; +} + +.tabs-container h2 { + float: left; + margin-bottom: 0px; +} + +/* CP tabs shared +----------------------------------------*/ +.tabs, .minitabs { + line-height: normal; +} + +.tabs > ul, .minitabs > ul { + list-style: none; + margin: 0; + padding: 0; + position: relative; +} + +.tabs .tab, .minitabs .tab { + display: block; + float: left; + line-height: 1.4em; +} + +.tabs .tab > a, .minitabs .tab > a { + display: block; + padding: 8px 14px; + position: relative; + text-decoration: none; + white-space: nowrap; + cursor: pointer; +} + +/* CP tabbed menu +----------------------------------------*/ +.tabs { + margin: 20px 0 0 7px; +} + +.tabs .tab > a { + border: 1px solid transparent; + margin: 1px 1px 0 0; +} + +.tabs .activetab > a { + margin-top: 0; + padding-bottom: 9px; +} + +/* Mini tabbed menu used in MCP +----------------------------------------*/ +.minitabs { + float: right; + margin: 15px 7px 0 0; + max-width: 50%; +} + +.minitabs .tab { + float: right; +} + +.minitabs .tab > a { + border-radius: 5px 5px 0 0; + margin-left: 2px; +} + +.minitabs .tab > a:hover { + text-decoration: none; +} + +/* Responsive tabs +----------------------------------------*/ +.responsive-tab { + position: relative; +} + +.responsive-tab > a.responsive-tab-link { + display: block; + position: relative; + width: 16px; + line-height: 0.9em; + text-decoration: none; +} + +.responsive-tab .responsive-tab-link:before { + content: ''; + position: absolute; + left: 10px; + top: 7px; + height: .125em; + width: 14px; + border-bottom: 0.125em solid transparent; + border-top: 0.375em double transparent; +} + +.tabs .dropdown, .minitabs .dropdown { + top: 20px; + margin-right: -2px; +} + +.minitabs .dropdown { + margin-right: -4px; +} + +.tabs .dropdown-up .dropdown, .minitabs .dropdown-up .dropdown { + bottom: 20px; + top: auto; +} + +.tabs .dropdown li { + text-align: right; +} + +.minitabs .dropdown li { + text-align: left; +} + +/* UCP navigation menu +----------------------------------------*/ +/* Container for sub-navigation list */ +.navigation { + width: 100%; + padding-top: 50px; +} + +.navigation ul { + list-style: none; +} + +/* Default list state */ +.navigation li { + display: inline; + margin: 1px 0; + padding: 0; +} + +/* Link styles for the sub-section links */ +.navigation a { + display: block; + padding: 10px; + margin: 1px 0; + text-decoration: none; +} + +.navigation a:hover { + text-decoration: none; +} + +/* Preferences pane layout +----------------------------------------*/ +.cp-main h2 { + border-bottom: none; + padding: 0; + margin-left: 10px; +} + +/* Friends list */ +.cp-mini { + margin: 10px 15px 10px 5px; + max-height: 200px; + overflow-y: auto; + padding: 5px 10px; + border-radius: 7px; +} + +dl.mini dd { + padding-top: 4px; +} + +/* PM Styles +----------------------------------------*/ +/* Defined rules list for PM options */ +ol.def-rules { + padding-left: 0; +} + +ol.def-rules li { + line-height: 180%; + padding: 1px; +} + +/* PM marking colours */ + + +/* DEPRECATED 3.2.6 +.pmlist li.pm_message_reported_colour, .pm_message_reported_colour { + border-left-color: transparent; + border-right-color: transparent; +} +*/ + +.pmlist li.pm_message_reported_colour, .pm_message_reported_colour, +.pmlist li.pm_marked_colour, .pm_marked_colour, +.pmlist li.pm_replied_colour, .pm_replied_colour, +.pmlist li.pm_friend_colour, .pm_friend_colour, +.pmlist li.pm_foe_colour, .pm_foe_colour { + border: solid 3px transparent; + border-width: 0 3px; +} + +.pm-legend { + border-left-width: 10px; + border-left-style: solid; + border-right-width: 0; + margin-bottom: 3px; + padding-left: 3px; +} + +/* Avatar gallery */ +.gallery label { + position: relative; + float: left; + margin: 10px; + padding: 5px; + width: auto; + border: 1px solid transparent; + text-align: center; +} + +/* Responsive *CP navigation +----------------------------------------*/ +@media only screen and (max-width: 900px), only screen and (max-device-width: 900px) +{ + .nojs .tabs a span, .nojs .minitabs a span { + max-width: 40px; + overflow: hidden; + text-overflow: ellipsis; + letter-spacing: -.5px; + } + + .cp-menu, .navigation, .cp-main { + float: none; + width: auto; + margin: 0; + } + + .navigation { + padding: 0; + margin: 0 auto; + max-width: 320px; + } + + .navigation a { + background-image: none; + } + + .navigation li:first-child a { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + } + + .navigation li:last-child a { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + } +} diff --git a/styles/Milk_v2/theme/en/icon_user_online.gif b/styles/Milk_v2/theme/en/icon_user_online.gif new file mode 100644 index 0000000..6b571ff Binary files /dev/null and b/styles/Milk_v2/theme/en/icon_user_online.gif differ diff --git a/styles/Milk_v2/theme/en/stylesheet.css b/styles/Milk_v2/theme/en/stylesheet.css new file mode 100644 index 0000000..604b299 --- /dev/null +++ b/styles/Milk_v2/theme/en/stylesheet.css @@ -0,0 +1,2 @@ +/* Online image */ +.online { background-image: url("./icon_user_online.gif"); } diff --git a/styles/Milk_v2/theme/extensions.css b/styles/Milk_v2/theme/extensions.css new file mode 100644 index 0000000..0290486 --- /dev/null +++ b/styles/Milk_v2/theme/extensions.css @@ -0,0 +1,4 @@ +/* Enhanced Support for phpBB Advertisement Management: https://www.phpbb.com/customise/db/extension/ads/ */ +.phpbb-ads-center img { + max-width: 100%; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/fonts.css b/styles/Milk_v2/theme/fonts.css new file mode 100644 index 0000000..c910821 --- /dev/null +++ b/styles/Milk_v2/theme/fonts.css @@ -0,0 +1,99 @@ +/* Consolidation of fonts (*shield your eyes, there are many of them!). Families, then sizes and weights */ + +/* The basics */ +html { + font-size: 100%; +} + +i, em { + font-style: italic; +} + +b, strong { + font-weight: 500; +} + +body { + +} + +.codebox code { + font: 0.9em Monaco, "Andale Mono","Courier New", Courier, monospace; +} + + +/* Sizes */ +h1 {/* Forum name */ font-size: 30px;} +.site-description p {font-size: 14px;} +h2 {/* Forum header titles */ font-size: 1.8em;} +h3 {/* Sub-headers (also used as post headers, but defined later) */ font-size: 1em;} +h4 {/* Forum and topic list titles */ font-size: 1.3em;} +ul#nav-main > li > a, ul#nav-main > li > div > a {font-size: 1.2em;} +dd.posts, dd.topics, dd.views, dd.extra, dd.mark, ul#nav-breadcrumbs li {font-size: 1.2em;} +.small {font-size: 0.9em !important;} +a.forumtitle, a.topictitle {font-size: 1.2em;} +.badge {font-size: 10px;} +.pagination li a {font-size: 0.9em;} +.contact-icons {font-size: 0;} +.topic_type {font-size: 0.9em;} +.forumlist_grid_title {font-size: 1.2em;} +.grid_unread {font-size: 0.8em;} +li.row dd.simpleposts span {font-size: 1.5em;} + + + + +/* Weights */ +h1 {/* Forum name */ font-weight: 300;} +h2 {/* Forum header titles */ font-weight: 300;} +li.row dd.simpleposts span {font-weight: 300;} +h3 {/* Sub-headers (also used as post headers, but defined later) */ font-weight: 500;} +ul#nav-breadcrumbs > li.breadcrumbs {font-weight: 300;} +.breadcrumbs .crumb:before {font-weight: 500;} +table.table1 thead th {font-weight: normal;} +table.info tbody th {font-weight: normal;} +.pagination li.active span {font-weight: normal;} +.error {font-weight: 500;} +.dropdown-extended .header {font-weight: 500;} +.dropdown-extended .header .header_settings {font-weight: normal;} +.notification_list p.notifications_title strong {font-weight: 500;} +.button {font-weight: normal;} +ul.topiclist dt {font-weight: normal;} +li.header dt, li.header dd {font-weight: 500;} +li.header dt {font-weight: 500;} +dd.posts, dd.topics, dd.views, dd.extra, dd.mark {font-weight: 300;} +li.row dd.simpleposts span {font-weight: 300;} +dl.faq dt {font-weight: 500;} +blockquote cite {/* Username/source of quoter */ font-style: normal; font-weight: 500;} +blockquote cite > div {font-weight: normal;} +.codebox p {font-weight: 500;} +.attachbox p {font-weight: normal;} +.attachbox p.stats {font-weight: normal;} +dl.file dt {font-weight: 500;} +dl.thumbnail dd {font-style: italic;} +fieldset.polls dl.voted {font-weight: 500;} +dd.profile-warnings {font-weight: 500;} +.dl_links strong {font-weight: 500;} +#memberlist tr.inactive, #team tr.inactive {font-style: italic;} +.panel-container table.table1 tbody th {font-style: italic;} +.tabs .tab, .minitabs .tab {font-weight: 500;} +.tabs .dropdown, .minitabs .dropdown {font-weight: normal;} +.navigation li {font-weight: 500;} +dl.mini dt {font-weight: 500;} +.friend-online {font-weight: 500;} +.friend-offline {font-style: italic;} +.tabs .dropdown, .minitabs .dropdown {font-weight: normal;} +input {font-weight: normal;} +select {font-weight: normal;} +dt label {font-weight: 500;} +a.button1, input.button1 {font-weight: 500;} +input.button3 {font-variant: small-caps;} +input.disabled {font-weight: normal;} +.jumpbox-cat-link, .jumpbox-forum-link { font-weight: 500; } +a.forumtitle {font-weight: 500;} +a.topictitle {font-weight: 500;} +a.lastsubject {font-weight: 500;text-decoration: none;} +.postprofile a, .postprofile dt.author a {font-weight: 500; text-decoration: none;} +.search .postprofile a {font-weight: normal;} +.username-coloured {font-weight: 500;} +.grid_unread {font-weight: 300;} diff --git a/styles/Milk_v2/theme/forms.css b/styles/Milk_v2/theme/forms.css new file mode 100644 index 0000000..4167144 --- /dev/null +++ b/styles/Milk_v2/theme/forms.css @@ -0,0 +1,422 @@ +/* Form Styles +---------------------------------------- */ + +/* General form styles +----------------------------------------*/ +fieldset { + border-width: 0; +} + +input { + vertical-align: middle; + padding: 0 3px; +} + +input:hover { + transition:.2s; +} + +select { + cursor: pointer; + vertical-align: middle; + border: 1px solid transparent; + padding: 7px 7px 6px; +} + +select:focus { + outline-style: none; +} + +option { + padding-right: 1em; +} + +select optgroup option { + padding-right: 1em; +} + +textarea { + width: 60%; + padding: 2px; + line-height: 1.4em; +} + +label { + cursor: default; + padding-right: 5px; +} + +label input { + vertical-align: middle; +} + +label img { + vertical-align: middle; +} + +/* Definition list layout for forms +---------------------------------------- */ +fieldset dl { + padding: 4px 0; +} + +fieldset dt { + float: left; + width: 40%; + text-align: left; + display: block; +} + +.login_form fieldset dt { + width: 100%; +} + +fieldset dd { + margin-left: 41%; + vertical-align: top; + margin-bottom: 3px; +} + +/* Specific layout 1 */ +fieldset.fields1 dt { + width: 15em; + border-right-width: 0; + padding: 5px 0; +} + +fieldset.fields1 dd { + margin-left: 15em; + border-left-width: 0; +} + +fieldset.fields1 div { + margin-bottom: 5px; +} + +/* Set it back to 0px for the reCaptcha divs: PHPBB3-9587 */ +fieldset.fields1 .live-search div { + margin-bottom: 0; +} + +/* Specific layout 2 */ +fieldset.fields2 dt { + width: 15em; + border-right-width: 0; + padding: 5px 0; +} + +fieldset.fields2 dd { + margin-left: 16em; + border-left-width: 0; +} + +/* Form elements */ +dt label { + text-align: left; +} + +dd label { + white-space: nowrap; +} + +dd input, dd textarea { + margin-right: 3px; +} + +dd select { + width: auto; +} + +dd select[multiple] { + width: 100%; +} + +dd textarea { + width: 85%; +} + +/* Hover effects */ +.timezone { + width: 95%; +} + +/* Browser-specific tweaks */ +button::-moz-focus-inner { + padding: 0; + border: 0 +} + +/* Quick-login on index page */ +fieldset.quick-login { + margin-top: 5px; +} + +fieldset.quick-login input { + width: auto; +} + +fieldset.quick-login input.inputbox { + width: 15%; + vertical-align: middle; + margin-right: 5px; +} + +fieldset.quick-login label { + white-space: nowrap; + padding-right: 2px; +} + +/* Display options on viewtopic/viewforum pages */ +fieldset.display-options { + text-align: center; + margin: 3px 0 5px 0; +} + +fieldset.display-options label { + white-space: nowrap; + padding-right: 2px; +} + +fieldset.display-options a { + margin-top: 3px; +} + +.dropdown fieldset.display-options { + margin: 0; + padding: 0; +} + +.dropdown fieldset.display-options label { + display: block; + margin: 4px; + padding: 0; + text-align: right; + white-space: nowrap; +} + +.dropdown fieldset.display-options select { + min-width: 120px; +} + +/* Display actions for ucp and mcp pages */ +fieldset.display-actions { + text-align: right; + line-height: 2em; + white-space: nowrap; + padding-right: 1em; +} + +fieldset.display-actions label { + white-space: nowrap; + padding-right: 2px; +} + +fieldset.sort-options { + line-height: 2em; +} + +/* MCP forum selection*/ +fieldset.forum-selection { + margin: 5px 0 3px 0; + float: right; +} + +fieldset.forum-selection2 { + margin: 13px 0 3px 0; + float: right; +} + +/* Submit button fieldset */ +fieldset.submit-buttons { + text-align: center; + vertical-align: middle; + margin: 5px 0; +} + +fieldset.submit-buttons input { + vertical-align: middle; +} + +/* Posting page styles +----------------------------------------*/ + +/* Buttons used in the editor */ +.format-buttons { + margin: 15px 0 2px 0; +} + +.format-buttons input, .format-buttons select { + vertical-align: middle; +} + +/* Main message box */ +.message-box { + width: 80%; +} + +.message-box textarea { + width: 450px; + height: 270px; + min-width: 100%; + max-width: 100%; + resize: vertical; + outline: 3px dashed transparent; + outline-offset: -4px; + -webkit-transition: all .5s ease; + -moz-transition: all .5s ease; + -ms-transition: all .5s ease; + -o-transition: all .5s ease; + transition: all .5s ease; +} + +/* Emoticons panel */ +.smiley-box { + width: 18%; + float: right; +} + +.smiley-box img { + margin: 3px; +} + +/* Input field styles +---------------------------------------- */ +.inputbox { + border: 1px solid transparent; + padding: 7px; +} + +.inputbox:hover, .inputbox:focus { + border: 1px solid transparent; + outline-style: none; +} + +input.inputbox { + width: 100%; + box-sizing: border-box; +} + +input.medium { width: 50%; } +input.narrow { width: 25%; } +input.tiny { width: 150px; } +input.sidebar_search { width: 225px; } + +textarea.inputbox { + width: 85%; +} + +.autowidth { + width: auto !important; +} + +input[type="number"] { + -moz-padding-end: 0; +} + +input[type="search"] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; +} + +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-results-button, input[type="search"]::-webkit-search-results-decoration { + display: none; +} + +input[type="search"]::-webkit-search-cancel-button { + cursor: pointer; +} + +/* Form button styles +---------------------------------------- */ +a.button1, input.button1, input.button3, a.button2, input.button2 { + width: auto !important; + padding: 7px; + background: transparent none repeat-x top left; + line-height: 1.5; +} + +a.button1, input.button1 { + border: 1px solid transparent; +} + +input.button3 { + padding: 0; + margin: 0; + line-height: 5px; + height: 12px; + background-image: none; +} + +input[type="button"], input[type="submit"], input[type="reset"], input[type="checkbox"], input[type="radio"] { + cursor: pointer; +} + +/* Alternative button */ +a.button2, input.button2, input.button3 { + border: 1px solid transparent; +} + +/* button in the style of the form buttons */ +a.button1, a.button2 { + text-decoration: none; + padding: 7px; + vertical-align: text-bottom; +} + +/* Hover states */ +a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover { + border: 1px solid transparent; +} + +/* Focus states */ +input.button1:focus, input.button2:focus, input.button3:focus { + outline-style: none; +} + +/* Topic and forum Search */ +.search-box { + float: left; +} + +.search-box .inputbox { + background-image: none; + border-right-width: 0; + float: left; + padding: 0 8px; + height: 37px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-border-radius:0; + border-radius:0; +} + +/* Search box (header) +--------------------------------------------- */ +.search-header { + border-radius: 4px; + display: block; + float: right; + margin-right: 5px; + margin-top: 50px; +} + +.search-header .inputbox { + border: 0; + -webkit-border-radius:0; + border-radius:0; +} + +li.responsive-search { display: none; } + +input.search { + background-image: none; + background-repeat: no-repeat; + background-position: left 1px; + padding-left: 17px; +} + +.full { width: 95%; } +.medium { width: 50%;} +.narrow { width: 25%;} +.tiny { width: 10%;} diff --git a/styles/Milk_v2/theme/icons.css b/styles/Milk_v2/theme/icons.css new file mode 100644 index 0000000..411feca --- /dev/null +++ b/styles/Milk_v2/theme/icons.css @@ -0,0 +1,87 @@ +/* -------------------------------------------------------------- + $Icons +-------------------------------------------------------------- */ + +/* Global module setup +--------------------------------*/ + +/* Renamed version of .fa class for agnostic useage of icon fonts. + * Just change the name of the font after the 14/1 to the name of + * the font you wish to use. + */ +.icon, .button .icon { + display: inline-block; + font-weight: normal; + font-style: normal; + font-variant: normal; + font-family: FontAwesome; + font-size: 14px; + line-height: 1; + text-rendering: auto; /* optimizelegibility throws things off #1094 */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon:before { padding-right: 2px; } + +.button .icon:before { + padding-right: 0; +} + +/* Icon size classes - Default size is 14px, use these for small variations */ + +.icon.icon-xl { + font-size: 20px; +} + +.icon.icon-lg { + font-size: 16px; +} + +.icon.icon-md { + font-size: 10px; +} + +.icon.icon-sm { + font-size: 8px; +} + +/* icon modifiers */ +.icon-tiny { + width: 12px; + transform: scale(0.65, 0.75); + vertical-align: text-bottom; + font-size: 16px; +} + +.arrow-left .icon { + float: left; +} + +.arrow-left:hover .icon { + margin-left: -5px; + margin-right: 5px; +} + +.arrow-right .icon { + float: right; +} + +.arrow-right:hover .icon { + margin-left: 5px; + margin-right: -5px; +} + +.post-buttons .dropdown-contents .icon { + float: right; + margin-left: 5px; +} + +.alert_close .icon:before { + padding: 0; + border-radius: 50%; + width: 11px; + display: block; + line-height: .9; + height: 12px; +} diff --git a/styles/Milk_v2/theme/icons_forums_topics.css b/styles/Milk_v2/theme/icons_forums_topics.css new file mode 100644 index 0000000..62fa722 --- /dev/null +++ b/styles/Milk_v2/theme/icons_forums_topics.css @@ -0,0 +1,219 @@ + +/* Icon images +---------------------------------------- */ + +.contact-icon { background-image: url("./images/icons_contact.png"); } + +/* Profile & navigation icons */ +.pm-icon { background-position: 0 0; } +.email-icon { background-position: -21px 0; } +.jabber-icon { background-position: -80px 0; } +.phpbb_icq-icon { background-position: -61px 0 ; } +.phpbb_wlm-icon { background-position: -182px 0; } +.phpbb_aol-icon { background-position: -244px 0; } +.phpbb_website-icon { background-position: -40px 0; } +.phpbb_youtube-icon { background-position: -98px 0; } +.phpbb_facebook-icon { background-position: -119px 0; } +.phpbb_googleplus-icon { background-position: -140px 0; } +.phpbb_skype-icon { background-position: -161px 0; } +.phpbb_twitter-icon { background-position: -203px 0; } +.phpbb_yahoo-icon { background-position: -224px 0; } + +/* Forum icons & Topic icons */ +.global_read { + background-image: url("./images/icons/png/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read.svg"); /* Modern browsers */ +} +.global_read_mine { + background-image: url("./images/icons/png/icon_read_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_mine.svg"); /* Modern browsers */ +} +.global_read_locked { + background-image: url("./images/icons/png/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked.svg"); /* Modern browsers */ +} +.global_read_locked_mine { + background-image: url("./images/icons/png/icon_read_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked_mine.svg"); /* Modern browsers */ +} + +.global_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.global_unread_mine { + background-image: url("./images/icons/png/icon_unread_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_mine.svg"); /* Modern browsers */ +} +.global_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.global_unread_locked_mine { + background-image: url("./images/icons/png/icon_unread_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked_mine.svg"); /* Modern browsers */ +} + +.announce_read { + background-image: url("./images/icons/png/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read.svg"); /* Modern browsers */ +} +.announce_read_mine { + background-image: url("./images/icons/png/icon_read_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_mine.svg"); /* Modern browsers */ +} +.announce_read_locked { + background-image: url("./images/icons/png/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked.svg"); /* Modern browsers */ +} +.announce_read_locked_mine { + background-image: url("./images/icons/png/icon_read_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked_mine.svg"); /* Modern browsers */ +} + +.announce_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.announce_unread_mine { + background-image: url("./images/icons/png/icon_unread_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_mine.svg"); /* Modern browsers */ +} +.announce_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.announce_unread_locked_mine { + background-image: url("./images/icons/png/icon_unread_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked_mine.svg"); /* Modern browsers */ +} + +.forum_link { + background-image: url("./images/icons/png/forum_link.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/forum_link.svg"); /* Modern browsers */ +} + +.forum_read { + background-image: url("./images/icons/png/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read.svg"); /* Modern browsers */ +} +.forum_read_locked { + background-image: url("./images/icons/png/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked.svg"); /* Modern browsers */ +} +.forum_read_subforum { + background-image: url("./images/icons/png/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read.svg"); /* Modern browsers */ +} + +.forum_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.forum_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.forum_unread_subforum { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} + + +.sticky_read { + background-image: url("./images/icons/png/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read.svg"); /* Modern browsers */ +} +.sticky_read_mine { + background-image: url("./images/icons/png/icon_read_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_mine.svg"); /* Modern browsers */ +} +.sticky_read_locked { + background-image: url("./images/icons/png/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked.svg"); /* Modern browsers */ +} +.sticky_read_locked_mine { + background-image: url("./images/icons/png/icon_read_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked_mine.svg"); /* Modern browsers */ +} + +.sticky_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.sticky_unread_mine { + background-image: url("./images/icons/png/icon_unread_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_mine.svg"); /* Modern browsers */ +} +.sticky_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.sticky_unread_locked_mine { + background-image: url("./images/icons/png/icon_unread_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked_mine.svg"); /* Modern browsers */ +} + +.topic_moved { + background-image: url("./images/icons/png/forum_link.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/forum_link.svg"); /* Modern browsers */ +} + +.topic_read { + background-image: url("./images/icons/png/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read.svg"); /* Modern browsers */ +} +.topic_read_mine { + background-image: url("./images/icons/png/icon_read_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_mine.svg"); /* Modern browsers */ +} +.topic_read_hot { + background-image: url("./images/icons/png/icon_read_hot.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_hot.svg"); /* Modern browsers */ +} +.topic_read_hot_mine { + background-image: url("./images/icons/png/icon_read_hot_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_hot_mine.svg"); /* Modern browsers */ +} +.topic_read_locked { + background-image: url("./images/icons/png/icon_read_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked.svg"); /* Modern browsers */ +} +.topic_read_locked_mine { + background-image: url("./images/icons/png/icon_read_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read_locked_mine.svg"); /* Modern browsers */ +} + +.topic_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} +.topic_unread_mine { + background-image: url("./images/icons/png/icon_unread_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_mine.svg"); /* Modern browsers */ +} +.topic_unread_hot { + background-image: url("./images/icons/png/icon_unread_hot.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_hot.svg"); /* Modern browsers */ +} +.topic_unread_hot_mine { + background-image: url("./images/icons/png/icon_unread_hot_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_hot_mine.svg"); /* Modern browsers */ +} +.topic_unread_locked { + background-image: url("./images/icons/png/icon_unread_locked.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked.svg"); /* Modern browsers */ +} +.topic_unread_locked_mine { + background-image: url("./images/icons/png/icon_unread_locked_mine.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread_locked_mine.svg"); /* Modern browsers */ +} + +.pm_read { + background-image: url("./images/icons/png/icon_read.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_read.svg"); /* Modern browsers */ +} +.pm_unread { + background-image: url("./images/icons/png/icon_unread.png"); /* IE8 + below */ + background-image: linear-gradient(transparent, transparent), url("./images/icons/svg/icon_unread.svg"); /* Modern browsers */ +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/images/dna.svg b/styles/Milk_v2/theme/images/dna.svg new file mode 100644 index 0000000..7c29600 --- /dev/null +++ b/styles/Milk_v2/theme/images/dna.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/styles/Milk_v2/theme/images/icon_download.gif b/styles/Milk_v2/theme/images/icon_download.gif new file mode 100644 index 0000000..70cd61c Binary files /dev/null and b/styles/Milk_v2/theme/images/icon_download.gif differ diff --git a/styles/Milk_v2/theme/images/icon_offline.gif b/styles/Milk_v2/theme/images/icon_offline.gif new file mode 100644 index 0000000..5dc4212 Binary files /dev/null and b/styles/Milk_v2/theme/images/icon_offline.gif differ diff --git a/styles/Milk_v2/theme/images/icon_online.gif b/styles/Milk_v2/theme/images/icon_online.gif new file mode 100644 index 0000000..d0d202d Binary files /dev/null and b/styles/Milk_v2/theme/images/icon_online.gif differ diff --git a/styles/Milk_v2/theme/images/icon_rate_bad.gif b/styles/Milk_v2/theme/images/icon_rate_bad.gif new file mode 100644 index 0000000..7901889 Binary files /dev/null and b/styles/Milk_v2/theme/images/icon_rate_bad.gif differ diff --git a/styles/Milk_v2/theme/images/icon_rate_good.gif b/styles/Milk_v2/theme/images/icon_rate_good.gif new file mode 100644 index 0000000..6d23034 Binary files /dev/null and b/styles/Milk_v2/theme/images/icon_rate_good.gif differ diff --git a/styles/Milk_v2/theme/images/icons/index.htm b/styles/Milk_v2/theme/images/icons/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_read.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_read.png new file mode 100644 index 0000000..794f4ef Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_read.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_read_hot.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_hot.png new file mode 100644 index 0000000..971b290 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_hot.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_read_hot_mine.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_hot_mine.png new file mode 100644 index 0000000..fbf95cc Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_hot_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_read_locked.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_locked.png new file mode 100644 index 0000000..a0a0b76 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_locked.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_read_locked_mine.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_locked_mine.png new file mode 100644 index 0000000..146f628 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_locked_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_read_mine.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_mine.png new file mode 100644 index 0000000..1ca3f2f Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_read_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_hot.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_hot.png new file mode 100644 index 0000000..e258838 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_hot.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_hot_mine.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_hot_mine.png new file mode 100644 index 0000000..a02e16d Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_hot_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_locked_mine.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_locked_mine.png new file mode 100644 index 0000000..4486129 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_locked_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_mine.png b/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_mine.png new file mode 100644 index 0000000..a680384 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/dark/icon_unread_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/dark/index.htm b/styles/Milk_v2/theme/images/icons/png/dark/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/images/icons/png/forum_link.png b/styles/Milk_v2/theme/images/icons/png/forum_link.png new file mode 100644 index 0000000..c0208f2 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/forum_link.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_read.png b/styles/Milk_v2/theme/images/icons/png/icon_read.png new file mode 100644 index 0000000..81cde7a Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_read.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_read_hot.png b/styles/Milk_v2/theme/images/icons/png/icon_read_hot.png new file mode 100644 index 0000000..4d00b6f Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_read_hot.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_read_hot_mine.png b/styles/Milk_v2/theme/images/icons/png/icon_read_hot_mine.png new file mode 100644 index 0000000..fd3535b Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_read_hot_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_read_locked.png b/styles/Milk_v2/theme/images/icons/png/icon_read_locked.png new file mode 100644 index 0000000..29c6909 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_read_locked.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_read_locked_mine.png b/styles/Milk_v2/theme/images/icons/png/icon_read_locked_mine.png new file mode 100644 index 0000000..e7830e5 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_read_locked_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_read_mine.png b/styles/Milk_v2/theme/images/icons/png/icon_read_mine.png new file mode 100644 index 0000000..8bfee24 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_read_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_unread.png b/styles/Milk_v2/theme/images/icons/png/icon_unread.png new file mode 100644 index 0000000..fc988d9 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_unread.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_unread_hot.png b/styles/Milk_v2/theme/images/icons/png/icon_unread_hot.png new file mode 100644 index 0000000..97b1da9 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_unread_hot.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_unread_hot_mine.png b/styles/Milk_v2/theme/images/icons/png/icon_unread_hot_mine.png new file mode 100644 index 0000000..f84b65d Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_unread_hot_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_unread_locked.png b/styles/Milk_v2/theme/images/icons/png/icon_unread_locked.png new file mode 100644 index 0000000..d4e3d2c Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_unread_locked.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_unread_locked_mine.png b/styles/Milk_v2/theme/images/icons/png/icon_unread_locked_mine.png new file mode 100644 index 0000000..067137a Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_unread_locked_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/icon_unread_mine.png b/styles/Milk_v2/theme/images/icons/png/icon_unread_mine.png new file mode 100644 index 0000000..782060d Binary files /dev/null and b/styles/Milk_v2/theme/images/icons/png/icon_unread_mine.png differ diff --git a/styles/Milk_v2/theme/images/icons/png/index.htm b/styles/Milk_v2/theme/images/icons/png/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_read.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read.svg new file mode 100644 index 0000000..61cc0e5 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_hot.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_hot.svg new file mode 100644 index 0000000..cd5fb60 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_hot.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_hot_mine.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_hot_mine.svg new file mode 100644 index 0000000..dd4999a --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_hot_mine.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_locked.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_locked.svg new file mode 100644 index 0000000..db3296d --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_locked.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_locked_mine.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_locked_mine.svg new file mode 100644 index 0000000..e7fe361 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_locked_mine.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_mine.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_mine.svg new file mode 100644 index 0000000..b9f6dab --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_read_mine.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_hot.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_hot.svg new file mode 100644 index 0000000..c3c594d --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_hot.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_hot_mine.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_hot_mine.svg new file mode 100644 index 0000000..56a714a --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_hot_mine.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_locked_mine.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_locked_mine.svg new file mode 100644 index 0000000..1a257ea --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_locked_mine.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_mine.svg b/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_mine.svg new file mode 100644 index 0000000..84f9c59 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/dark/icon_unread_mine.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/dark/index.htm b/styles/Milk_v2/theme/images/icons/svg/dark/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/images/icons/svg/forum_link.svg b/styles/Milk_v2/theme/images/icons/svg/forum_link.svg new file mode 100644 index 0000000..00046b9 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/forum_link.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_read.svg b/styles/Milk_v2/theme/images/icons/svg/icon_read.svg new file mode 100644 index 0000000..c8a9762 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_read.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_read_hot.svg b/styles/Milk_v2/theme/images/icons/svg/icon_read_hot.svg new file mode 100644 index 0000000..04ca42a --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_read_hot.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_read_hot_mine.svg b/styles/Milk_v2/theme/images/icons/svg/icon_read_hot_mine.svg new file mode 100644 index 0000000..e26130c --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_read_hot_mine.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_read_locked.svg b/styles/Milk_v2/theme/images/icons/svg/icon_read_locked.svg new file mode 100644 index 0000000..263f917 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_read_locked.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_read_locked_mine.svg b/styles/Milk_v2/theme/images/icons/svg/icon_read_locked_mine.svg new file mode 100644 index 0000000..e175282 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_read_locked_mine.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_read_mine.svg b/styles/Milk_v2/theme/images/icons/svg/icon_read_mine.svg new file mode 100644 index 0000000..81fb29a --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_read_mine.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_unread.svg b/styles/Milk_v2/theme/images/icons/svg/icon_unread.svg new file mode 100644 index 0000000..55fffae --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_unread.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_unread_hot.svg b/styles/Milk_v2/theme/images/icons/svg/icon_unread_hot.svg new file mode 100644 index 0000000..7ecaac3 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_unread_hot.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_unread_hot_mine.svg b/styles/Milk_v2/theme/images/icons/svg/icon_unread_hot_mine.svg new file mode 100644 index 0000000..a73d469 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_unread_hot_mine.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_unread_locked.svg b/styles/Milk_v2/theme/images/icons/svg/icon_unread_locked.svg new file mode 100644 index 0000000..b69cd47 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_unread_locked.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_unread_locked_mine.svg b/styles/Milk_v2/theme/images/icons/svg/icon_unread_locked_mine.svg new file mode 100644 index 0000000..cc9e365 --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_unread_locked_mine.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/icon_unread_mine.svg b/styles/Milk_v2/theme/images/icons/svg/icon_unread_mine.svg new file mode 100644 index 0000000..959acef --- /dev/null +++ b/styles/Milk_v2/theme/images/icons/svg/icon_unread_mine.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/Milk_v2/theme/images/icons/svg/index.htm b/styles/Milk_v2/theme/images/icons/svg/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/images/icons_contact.png b/styles/Milk_v2/theme/images/icons_contact.png new file mode 100644 index 0000000..f84abd3 Binary files /dev/null and b/styles/Milk_v2/theme/images/icons_contact.png differ diff --git a/styles/Milk_v2/theme/images/index.htm b/styles/Milk_v2/theme/images/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/images/loading.gif b/styles/Milk_v2/theme/images/loading.gif new file mode 100644 index 0000000..e1ed088 Binary files /dev/null and b/styles/Milk_v2/theme/images/loading.gif differ diff --git a/styles/Milk_v2/theme/images/logo.png b/styles/Milk_v2/theme/images/logo.png new file mode 100644 index 0000000..027dc77 Binary files /dev/null and b/styles/Milk_v2/theme/images/logo.png differ diff --git a/styles/Milk_v2/theme/images/no_avatar.gif b/styles/Milk_v2/theme/images/no_avatar.gif new file mode 100644 index 0000000..ad73330 Binary files /dev/null and b/styles/Milk_v2/theme/images/no_avatar.gif differ diff --git a/styles/Milk_v2/theme/images/plupload/done.gif b/styles/Milk_v2/theme/images/plupload/done.gif new file mode 100644 index 0000000..29f3ed7 Binary files /dev/null and b/styles/Milk_v2/theme/images/plupload/done.gif differ diff --git a/styles/Milk_v2/theme/images/plupload/error.gif b/styles/Milk_v2/theme/images/plupload/error.gif new file mode 100644 index 0000000..4682b63 Binary files /dev/null and b/styles/Milk_v2/theme/images/plupload/error.gif differ diff --git a/styles/Milk_v2/theme/images/plupload/throbber.gif b/styles/Milk_v2/theme/images/plupload/throbber.gif new file mode 100644 index 0000000..4ae8b16 Binary files /dev/null and b/styles/Milk_v2/theme/images/plupload/throbber.gif differ diff --git a/styles/Milk_v2/theme/images/quote.gif b/styles/Milk_v2/theme/images/quote.gif new file mode 100644 index 0000000..d199227 Binary files /dev/null and b/styles/Milk_v2/theme/images/quote.gif differ diff --git a/styles/Milk_v2/theme/images/quote_rtl.gif b/styles/Milk_v2/theme/images/quote_rtl.gif new file mode 100644 index 0000000..ac719cf Binary files /dev/null and b/styles/Milk_v2/theme/images/quote_rtl.gif differ diff --git a/styles/Milk_v2/theme/images/sticky_read_locked.gif b/styles/Milk_v2/theme/images/sticky_read_locked.gif new file mode 100644 index 0000000..79f581b Binary files /dev/null and b/styles/Milk_v2/theme/images/sticky_read_locked.gif differ diff --git a/styles/Milk_v2/theme/images/sticky_read_locked_mine.gif b/styles/Milk_v2/theme/images/sticky_read_locked_mine.gif new file mode 100644 index 0000000..ad05608 Binary files /dev/null and b/styles/Milk_v2/theme/images/sticky_read_locked_mine.gif differ diff --git a/styles/Milk_v2/theme/images/sticky_read_mine.gif b/styles/Milk_v2/theme/images/sticky_read_mine.gif new file mode 100644 index 0000000..8f5f28f Binary files /dev/null and b/styles/Milk_v2/theme/images/sticky_read_mine.gif differ diff --git a/styles/Milk_v2/theme/images/sticky_unread.gif b/styles/Milk_v2/theme/images/sticky_unread.gif new file mode 100644 index 0000000..d62b3c0 Binary files /dev/null and b/styles/Milk_v2/theme/images/sticky_unread.gif differ diff --git a/styles/Milk_v2/theme/index.htm b/styles/Milk_v2/theme/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/links.css b/styles/Milk_v2/theme/links.css new file mode 100644 index 0000000..b9ebbc4 --- /dev/null +++ b/styles/Milk_v2/theme/links.css @@ -0,0 +1,174 @@ +/* Link Styles +---------------------------------------- */ + +/* Links adjustment to correctly display an order of rtl/ltr mixed content */ +a { + direction: ltr; + unicode-bidi: embed; + text-decoration: none; + /* we use links inline more often then not so to address several bugs with + IE and some other browsers we render all links as inlineblock by default */ + display: inline-block; + +} + +/* Coloured usernames */ +.username-coloured { + display: inline !important; + padding: 0 !important; +} + +/* Links on gradient backgrounds */ +.forumbg .header a, .forabg .header a, th a { + text-decoration: none; +} + +.forumbg .header a:hover, .forabg .header a:hover, th a:hover { + text-decoration: underline; +} + +/* Notification mark read link */ +.dropdown-extended a.mark_read { + background-position: center center; + background-repeat: no-repeat; + border-radius: 3px 0 0 3px; + display: none; + margin-top: -20px; + position: absolute; + z-index: 2; + right: 0; + top: 50%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.dropdown-extended li:hover a.mark_read { + display: block; +} + +.dropdown-extended a.mark_read:hover { + width: 50px; +} + + +/* Links for forum/topic lists */ +a.forumtitle { + text-decoration: none; +} + +a.forumtitle:hover { + text-decoration: underline; +} + +a.topictitle { + text-decoration: none; + display: inline; +} + +a.topictitle:hover { + text-decoration: underline; +} + +a.lastsubject { + text-decoration: none; +} + +a.lastsubject:hover { + text-decoration: underline; +} + +/* Post body links */ +.postlink { + text-decoration: none; + border-bottom: 1px solid transparent; + padding-bottom: 0; +} + +.postlink:hover { + text-decoration: none; +} + +.signature a, .signature a:hover { + border: none; + text-decoration: underline; +} + +/* Profile links */ +.postprofile a, .postprofile dt.author a { + text-decoration: none; +} + +.postprofile a:hover, .postprofile dt.author a:hover { + text-decoration: underline; +} + +/* Profile searchresults */ +.search .postprofile a { + text-decoration: none; +} + +.search .postprofile a:hover { + text-decoration: underline; +} + +.top { + text-decoration: none; + margin-top: 10px; +} + +/* Back to top of page */ +.back2top { + clear: both; +} + +.back2top .top { + float: right; + margin-right: -10px; + margin-top: 0; +} + +/* Arrow links */ + +.arrow-up { + padding-left: 10px; + text-decoration: none; + border-bottom-width: 0; +} + +.arrow-up:hover { + +} + +.arrow-down { + padding-right: 10px; +} + +.arrow-down:hover { + +} + +.arrow-left:hover { + text-decoration: none; +} + +.arrow-right:hover { + text-decoration: none; +} + +/* invisible skip link, used for accessibility */ +.skiplink { + position: absolute; + left: -999px; + width: 990px; +} + +/* Feed icon in forumlist_body.html */ +a.feed-icon-forum { + float: right; + margin: 3px; +} + +a.anchor { + display: block; +} diff --git a/styles/Milk_v2/theme/merlin.css b/styles/Milk_v2/theme/merlin.css new file mode 100644 index 0000000..861e7e1 --- /dev/null +++ b/styles/Milk_v2/theme/merlin.css @@ -0,0 +1,582 @@ +/* New code for new things */ + + +/* Ignore this. Used for testing only */ +body {border-top: 0px solid;} + +/* Boxed and Fluid Layout Switches +---------------------------------------- */ +.body-layout-Fluid .wrap {} +.body-layout-Boxed .wrap {} /* Defined in responsive/large-desktops.css */ + + +/* Headerbar +---------------------------------------- */ +.headerbar_overlay_container { +} + +.headerbar_overlay_active { + +} + + +/* Particles +---------------------------------------- */ +#particles-js { +} + +.particles_container { + width: 100%; + position: relative; + overflow: hidden; + z-index: 1; + font-size: 0; /* Fix white space */ + text-align: center; +} + + + +/* Global Containers +---------------------------------------- */ +.fancy_panel { +} + +.fancy_panel_padding { + padding: 15px; +} + +#sidebar_right .fancy_panel_padding { + padding: 15px 0; +} + +body.content_block_header_stripe li.header:before, .fancy_panel:before, .sidebar { + content: ''; + width: 100%; + display: block; + height: 3px; +} + +.alt_block li.header:before { + height: 0px !important; +} + +#inner-wrap { + padding: 30px; + box-sizing: border-box; + box-shadow: 0 0 5px rgba(0,0,0,0.05); +} + +.body-layout-Fluid #inner-wrap { + width: 95%; + margin: 0 auto; +} + +/* Sidebar Structure +---------------------------------------- */ +#contentwrapper { + float: left; + width: 100%; +} + +/* Main content */ +#contentcolumn { + margin: 0 300px 0 120px; /*Margins for content column. Should be "0 RightColumnWidth 0 LeftColumnWidth*/ + padding: 0 30px; +} + +#leftcolumn { + float: left; + width: 120px; /*Width of left column*/ + margin-left: -100%; +} + +#rightcolumn { + float: left; + width: 300px; /*Width of right column*/ + margin-left: -300px; /*Set left margin to -(RightColumnWidth)*/ +} + +.innertube { + margin: 0; /*Margins for inner DIV inside each column (to provide padding)*/ + margin-top: 0; +} + +/* Conditional padding adjustments (to cater for flightdeck setting) */ +.sidebar-left-only #contentcolumn {margin-right: 0px; padding-right: 0;} +.sidebar-right-only #contentcolumn {margin-left: 0px; padding-left: 0;} + + +/* Sidebar Widgets +---------------------------------------- */ + +/* Sidebar Widget */ +.sidebar_widget { + margin-bottom: 30px; + clear: both; +} + +.sidebar_widget h3 { + display: block; + text-align: left; + margin: 0; + margin-bottom: 15px; +} + +.sidebar_widget p:last-child { + margin-bottom: 0; +} + +.sidebar_content { + border-top: 1px solid; + padding-top: 15px; +} + + +/* Profile Widget */ +.profile_widget { +} + +.profile_widget ul, .profile_widget li { + list-style: none; +} + +.profile_widget_avatar { + float: left; + width: 100px; + margin-bottom: 30px; +} + +.profile_widget_avatar img { + max-width: 100px; + max-height: 100px; + +} + +.profile_widget_info { + float: left; + margin-left: 30px; +} + +.profile_widget_list { + clear: both; + padding-top: 20px; + border-top: 1px solid; +} + +.profile_widget_list ul li { + line-height: 2.2em; +} + +.profile_widget_list ul li .icon { + margin-right: 5px; +} + +/* Ad Widget */ +.ad_grid { + padding: 0; + text-align: center; +} + +.ad_grid a { + display: inline-block; + margin: 10px; +} + +/* Blank Widget */ +.blank_widget { +} + + +/* Grid Forumlist +---------------------------------------- */ + +/* For easy margins on grid items, we apply them to all sides. Here we need to pull the container +margins out so that the grid items 'appear' to be flush with the container and center column */ +.forumlist_grid ul.forums { + margin: 10px 0 0; +} + +.forumlist_grid li.row { + margin-bottom: 0px !important; +} + +/* Sets the 3 column structure */ +.forumlist_grid li.row { + float: left; + display: block; + width: 33.3333333%; + border: none; + padding: 0; +} + +.forumlist_grid .grid_image_container { + margin: 0 15px 30px 15px; +} + +/* Do the same for forum images */ +.forumlist_grid .forumlist_grid_forum_image { + background-size: cover; +} + +.forumlist_grid .grid_colour_overlay { + display: block; + height: 200px; + position: relative; +} + +.forumlist_grid .forumlist_grid_title { + position: absolute; + bottom: 42px; + padding: 0 15px; +} + +.forumlist_grid .forumlist_grid_numbers { + position: absolute; + bottom: 0; + padding: 15px; +} + +/* Since this is the only column, we don't need to push the others over to the right */ +.forumlist_grid ul.topiclist dt .list-inner { + padding: 0; + margin: 0; +} + +.forumlist_grid li.row:hover { + background: none; +} + +.forumlist_grid .forum_description { + display: block; + height: 35px; + overflow: hidden; +} + +.forumlist_grid_numbers { + bottom: 0; +} + +.grid_desc { + padding: 20px; + box-shadow: 0 2px 3px rgba(0,0,0,0.1); + overflow: hidden; +} + +/* Stop the responsive breakpoints affecting the grid layout */ +.forumlist_grid ul.topiclist dt .list-inner { + margin-right: 0 !important; +} + + +/* Simple Forumlist +---------------------------------------- */ +dd.simpleposts { + text-align: center; + line-height: 2em; + width: 190px; +} + +dd.simpleposts span { + display: block; +} + +/* Custom Login Page +---------------------------------------- */ + +/* Sets the width and 100% height required for vertical alignment */ +.login_container { + width: 100%; /* Scale up in responsive stylesheets */ + margin: 0 auto; + min-height: 95%; + height: 95%; + display: table; +} + + +/* Vertically aligns the left content */ +.login_container_left { + width: 100%; + display: block; +} + +/* Vertically aligns the left content */ +.login_container_right { + width: 100%; + display: block; +} + +/* Content and fancypanel are one */ +.login_container_left_section_content { + position: relative; + z-index: 2; /* Ensures the box shadow overlaps the 'registration' box */ +} + +.login_container_right_section_content { + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} + +.login_container_padding { + padding: 30px; +} + +/* Login Widget */ +.login_form input.inputbox { + margin-bottom: 15px; +} + +.login_form input#autologin2, .login_form input#autologin { + margin-right: 5px; +} + +.login_form input.button2, .login { + margin: 15px 0; /* Usually we wouldn't do margin-top, but adding margin-bottom to the checkbox above gets weird */ + width: 100% !important; +} + +.login_form_forgot_link { + border-top: 1px solid; + margin-top: 15px; + padding-top: 15px; +} + +/* Captcha Override */ +.login_form fieldset dl { + margin: 15px 0 0; + padding: 0; +} + +.login_form fieldset.fields2 dd { + margin-left: 0; +} + +/* Right Side tweaks */ +.login_container_right a.button2 { + width: 100% !important; + text-align: center; +} + + +/* Postprofile Things (next release) +---------------------------------------- */ + + +/* Social Links +---------------------------------------- */ +.social_links_footer { + text-align: center; + clear: both; + padding: 80px 0 30px; + margin-top: -50px; + position: relative; + z-index: 2; +} + +.social_links_footer a { + display: inline-block; + margin: 0 7.5px; +} + +.social_links_footer a span { + font-size: 24px; + width: 46px; + height: 46px; + line-height: 44px; + text-align: center; + border: 2px solid; + border-radius: 25px; +} + +.social_links_footer a span:hover { + transition:.2s; +} + +/* Scroll to Top +---------------------------------------- */ +.scrollToTop { + width: 40px; + height: 40px; + line-height: 40px; + text-align:center; + text-decoration: none; + position: fixed; + bottom: 20px; + right: 20px; + display: none; + z-index: 4; +} + +.scrollToTop:hover { + text-decoration:none; + color: #FFFFFF; +} + + +/* Collapsible Panels +---------------------------------------- */ +.collapse-trigger { + display: block; + float: right; + position: relative; + margin: -31px 18px 0 0; +} + +.fancy_panel_padding .collapse-trigger { + margin-right: 0; +} + +.collapse-trigger a:hover, .collapse-trigger a:focus { + text-decoration: none; +} + +span.fa-plus {display: block;} +span.fa-minus {display: none;} + +.open span.fa-plus {display: none;} +.open span.fa-minus {display: block;} + + +/* As sidebar blocks entered via flightdeck can't use IF statements, we'll set the trigger visibility for the right sidebar here */ + +/* Hide them by default, then show when they're enabled */ +#sidebar_right .collapse-trigger {display: none;} +#sidebar_right.sidebar_right_collapse .collapse-trigger {display: inline-block;} + + +/* Missing Avatar Placeholder +---------------------------------------- */ +a.no_avatar { + display: block; + text-align: center; + border-radius: 50px; + height: 100px; + width: 100px; + line-height: 110px; +} + +a.no_avatar i.icon { + font-size: 25px; + padding-left: 7px; +} + + +/* Topic Types +---------------------------------------- */ +.topic_type { + display: inline-block; + padding: 2px 2px 2px 4px; + font-weight: 500; + float: left; + margin-right: 3px; +} + +.topic_type span { + display: inline-block; + text-transform: lowercase; +} + +.topic_type span::first-letter { + text-transform: uppercase; +} + +.grid_unread { + display: inline-block; + padding: 2px 4px; + float: left; + margin-right: 10px; +} + + +/* Footer Bars +---------------------------------------- */ +.copyright_bar { + clear: both; + padding: 15px; +} + +.navbar_footer { + padding: 15px; +} + + +/* Last Post Avatar +---------------------------------------- */ +span.lastpostavatar img { + position: absolute; + margin-left: -45px; + margin-top: 4px; /* Counteracts the last topic title line height */ +} + +span.lastpostavatar img{ + border: none; +} + +li.has_last_post_avatar dd.lastpost { + padding-left: 45px; +} + +/* Apparently viewforum doesn't like the above code. Weird..it's the same structure as index.php *shrug* */ +ul.topics li.row span.lastpostavatar img { + position: static; + margin-left: 0; + margin-right: 15px; +} + + +/* Columns +---------------------------------------- */ + + +/* Index and Permissions Blocks (and sidebar, probably) +---------------------------------------- */ +.alt_block { + margin-bottom: 30px; +} + + +/* Misc +---------------------------------------- */ +.squishy-show {display: inline-block;} +.quishy-hide {display: none;} + +.planetstyles_credit_hidden { + display: none; +} + +.clutter { + display: none; +} + +.post_forumlist_links { + text-align: center; + margin-bottom: 30px; +} + +.responsive_forumlist_row_stats { + margin-top: 4px; + opacity: 0.5; +} + +.stat-block p { + margin-bottom: 0; +} + +/* Fade out soft deleted topics */ +li.row.deleted { + opacity: 0.5; +} + +.button_disapprove, .button_approve { + margin-left: 5px; +} + +dd.captcha { + margin-top: 15px; + clear: both; /* Captcha fix */ +} + + +/* Remove the 5px top forumbg margin when stripe headers are enabled */ +body.content_block_header_stripe.dark_base ul.forums, body.content_block_header_stripe.dark_base ul.topics { + padding-top: 0; +} diff --git a/styles/Milk_v2/theme/milk.css b/styles/Milk_v2/theme/milk.css new file mode 100644 index 0000000..512f0ac --- /dev/null +++ b/styles/Milk_v2/theme/milk.css @@ -0,0 +1,36 @@ +/* New code for new things */ +#inner-wrap { + background: #FFFFFF; + position: relative; + margin-top: -50px !important; + z-index: 3; +} + +#inner-wrap:after { + content: ""; + display: table; + clear: both; + height: 0; +} + +.viewtopic_wrapper { + padding: 10px 10px 5px; +} + +.sub-forumlist ul { + float: left; + list-style-type: none; + margin-left: 20px !important; +} + +.sub-forumlist li { + line-height: 1.5em; +} + +body.force_rounded_avatars span.lastpostavatar img.avatar { + border-radius: 25px; +} + +.viewtopic_wrapper .phpbb-ads-center { + padding: 15px; +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/normalize.css b/styles/Milk_v2/theme/normalize.css new file mode 100644 index 0000000..23d8449 --- /dev/null +++ b/styles/Milk_v2/theme/normalize.css @@ -0,0 +1,424 @@ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS and IE text size adjust after device orientation change, + * without disabling user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability of focused elements when they are also in an + * active/hover state. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + box-sizing: content-box; /* 2 */ +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/styles/Milk_v2/theme/plupload.css b/styles/Milk_v2/theme/plupload.css new file mode 100644 index 0000000..b1f3ae2 --- /dev/null +++ b/styles/Milk_v2/theme/plupload.css @@ -0,0 +1,90 @@ +.attach-panel-multi { + display: none; + margin-bottom: 1em; +} + +.attach-row-tpl { + display: none; +} + +.file-list td { + vertical-align: middle; +} + +.attach-name { + width: 50%; +} + +.attach-comment { + width: 30%; +} + +.attach-comment .inputbox { + resize: vertical; + width: 100%; +} + +.attach-filesize { + width: 15%; +} + +.attach-status { + width: 5%; +} + +.attach-filesize, .attach-status { + text-align: center; +} + +.attach-controls { + display: inline-block; + float: right; +} + +.nojs .file-inline-bbcode { + display: none; +} + +.file-total-progress { + height: 2px; + display: block; + position: relative; + margin: 4px -10px -6px -10px; +} + +.file-progress { + background-color: #CCCCCC; + display:inline-block; + height: 8px; + width: 50px; +} + +.file-progress-bar, .file-total-progress-bar { + background-color: green; + display: block; + height: 100%; + width: 0; +} + +.file-status.file-working { + background: url('./images/plupload/throbber.gif'); +} + +.file-status.file-uploaded { + background: url('./images/plupload/done.gif'); +} + +.file-status.file-error { + background: url('./images/plupload/error.gif'); +} + +.file-status { + display: inline-block; + height: 16px; + width: 16px; +} + +.file-name { + max-width: 65%; + vertical-align: bottom; +} diff --git a/styles/Milk_v2/theme/print.css b/styles/Milk_v2/theme/print.css new file mode 100644 index 0000000..9445279 --- /dev/null +++ b/styles/Milk_v2/theme/print.css @@ -0,0 +1,150 @@ +/* Print Style Sheet +---------------------------------------- */ + + +/* Lots still TODO here! */ + +/* General markup styles */ +* { + padding: 0; + margin: 0; +} + +body { + font: 11pt Verdana, Arial, Helvetica, sans-serif; + color:#000000; +} + +a:link { color: #000000; text-decoration: none; } +a:visited { color: #000000; text-decoration: none; } +a:active { color: #000000; text-decoration: none; } + +img, .noprint, .navbar, .box1, .divider, .signature { display: none; } +/* Display smilies (Bug #47265) */ +.content img { + display: inline; +} + +/* Container for the main body */ +.wrap { + margin: 0 2em; +} + +p { font-size: 85%; } +.copyright { font-size: 75%; } +.page-number { float:right; width: auto; text-align: right; font-size: 75%; } + +h1, h2, h3, h1 a, h2 a, h3 a { + font-family: "Trebuchet MS",georgia,Verdana,Sans-serif; + color: #000000; + background: none; + text-decoration: none; + font-weight: bold; +} + +h1 { font-size: 20pt; } +h2 { font-size: 16pt; margin-top: 1em; } +h3 { font-size: 14pt; margin-top: 1em; } + +.content { + font-size: 11pt; + line-height: 14pt; + margin-bottom: 1em; + font-family: "Lucida Grande", "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; + overflow: hidden; +} + +/* CSS2 Print tip from: http://www.alistapart.com/articles/goingtoprint/ */ +.postbody a:link, .postbody a:visited, .postbody a:hover, .postbody a:active { + text-decoration: underline; + padding: 0.1em 0.2em; + margin: -0.1em -0.2em; + color: #666; + background: none; + font-size: 100%; +} + +html>body .postbody a:link:after, html>body .postbody a:visited:after { + content: " (" attr(href) ") "; + font-size: 90%; + text-decoration: none; +} + +hr { + height: 1px; + background-color: #999999; + border-width: 0; +} + +.author { + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 75%; + margin-bottom: 0.6em; +} + +.date { + font-family: Verdana, Arial, Helvetica, sans-serif; + float: right; + position: relative; + text-align: right; + font-size: 75%; +} + +/* Dont want to print url for names or titles in content area */ +.postbody .author a:link, .postbody .author a:visited, +html>body .postbody .author a:link:after, +html>body .postbody .author a:visited:after, +.postquote .quote-by a:link, .postquote .quote-by a:visited, +html>body .postquote .quote-by a:link:after, +html>body .postquote .quote-by a:visited:after, +html>body .postbody h1 a:link:after, html>body .postbody h2 a:link:after { + text-decoration: none; + content: ""; +} + +/* Poster profile */ +.postprofile { display: none; } +.grip-show { display:none; } + +/* Quote */ +.postquote, blockquote { + font-size: 85%; + margin: 1em 18% 1em 4%; + padding: 0.5em; + position: relative; + line-height: 1.5em; + border: 1px #999999 solid; +} + +.postquote img { display: none; } +.postquote span { display: block; } +.postquote span .postquote { font-size: 100%; } +.quote-by, blockquote cite { + color: black; + display : block; + font-weight: bold; +} + +/* List */ +ol, ul { + margin-left: 15pt +} + +/* Misc page elements */ +div.spacer { clear: both; } + +code { display: block; } + +/* Accessibility tweaks: Mozilla.org */ +.skip_link { display: none; } + +.codebox p { display: none; } + +/* stylelint-disable declaration-property-unit-whitelist */ +.emoji { + min-height: 18px; + min-width: 18px; + height: 1em; + width: 1em; +} +/* stylelint-enable declaration-property-unit-whitelist */ diff --git a/styles/Milk_v2/theme/responsive/index.htm b/styles/Milk_v2/theme/responsive/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/Milk_v2/theme/responsive/large-desktops.css b/styles/Milk_v2/theme/responsive/large-desktops.css new file mode 100644 index 0000000..1e6b5bd --- /dev/null +++ b/styles/Milk_v2/theme/responsive/large-desktops.css @@ -0,0 +1,149 @@ +@media (min-width: 1200px) { + + body{ border-top-color: #F3F;} + + /* Fix the body layout for large desktop devices */ + .body-layout-Boxed .wrap {width: 1200px;} + + + /* Forumlist Colums (also see squishy.css for fluid/both fix) + ---------------------------------------- */ + + /* Remove the 'topics' column */ + body.sidebar-both.body-layout-Boxed .forabg dd.posts, body.sidebar-both.body-layout-Boxed .forumbg dd.views { + display: none; + } + + /* Or, reduce the width of 'simpleposts' by 50% (from 190 to 95) */ + body.sidebar-both.body-layout-Boxed dd.simpleposts, body.sidebar-both.body-layout-Boxed dd.simpleposts { + width: 95px; + } + + /* Adjust the other margins to account for the (now missing) 95px */ + body.sidebar-both.body-layout-Boxed ul.topiclist dt { + margin-right: -395px; + } + + body.sidebar-both.body-layout-Boxed ul.topiclist dt .list-inner { + margin-right: 395px; + } + + body.sidebar-both.body-layout-Boxed li.header dt { + margin-right: -425px; + } + + /* UCP columns + ---------------------------------------- */ + + body.sidebar-both.body-layout-Boxed ul.topiclist.pmlist dt .list-inner { + margin-right: 95px; /* Creates the space */ + } + body.sidebar-both.body-layout-Boxed ul.topiclist.two-columns dt { + margin-right: -95px; + } + body.sidebar-both.body-layout-Boxed ul.topiclist.two-columns li.header dt { + margin-right: -130px; + } + + /* UCP Two long columns */ + body.sidebar-both.body-layout-Boxed ul.two-long-columns dt .list-inner { + margin-right: 0; + } + body.sidebar-both ul.two-long-columns dd.lastpost { + display: none; + } + + /* MCP Columns + ---------------------------------------- */ + + /* Manually enabling the responsive layout. First, we remove the second column header */ + body.sidebar-both.body-layout-Boxed .missing-column dd, + body.sidebar-right-only.body-layout-Boxed .missing-column dd { + display: none; + } + + /* But we still want the mark column, so re-enable that */ + body.sidebar-both.body-layout-Boxed .missing-column dd.mark, + body.sidebar-right-only.body-layout-Boxed .missing-column dd.mark { + display: block; + } + + /* Reduce the left column margin to 95px, just enough for the mark box */ + body.sidebar-both.body-layout-Boxed ul.topiclist.missing-column dt, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.missing-column dt { + margin-right: -95px; + } + body.sidebar-both.body-layout-Boxed ul.topiclist.missing-column dt .list-inner, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.missing-column dt .list-inner { + margin-right: 95px; + } + /* Then enable the extra info below */ + body.sidebar-both.body-layout-Boxed ul.topiclist.missing-column .responsive-show, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.missing-column .responsive-show { + display: block !important; + } + + /* Hacky bookmark fix */ + body.sidebar-both.body-layout-Boxed ul.topiclist.missing-column li.header dt, + body.sidebar-both.body-layout-Fluid ul.topiclist.missing-column li.header dt, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.missing-column li.header dt { + margin-right: -125px; /* 95px + 30px padding */ + } + + + + /* Manually enabling the responsive layout. First, we remove the second column header */ + body.sidebar-both.body-layout-Boxed ul.topiclist.two-long-columns dd, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.two-long-columns dd { + display: none; + } + + /* Increase the left column width to 100% */ + body.sidebar-both.body-layout-Boxed ul.topiclist.two-long-columns dt, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.two-long-columns dt { + margin-right: 0; + } + + /* Then enable the extra info below */ + body.sidebar-both.body-layout-Boxed ul.topiclist.two-long-columns .responsive-show, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.two-long-columns .responsive-show { + display: block !important; + } + + + + /* Custom Login Page + ---------------------------------------- */ + + /* Clear the veritcal alignment. Make it wider for larger screens */ + .login_container { + width: 900px; + } + + /* For ACP Auth (and login_body.html...probably), reduce container width to 450px */ + .auth-page-admin .login_container { + width: 450px; + } + + /* Split the columns down */ + .login_container_left, .login_container_right { + width: 50%; + display: table-cell; + vertical-align: middle; + } + + /* Min height for left column content, so that it's taller than the registration box */ + .login_container_left_section_content { + min-height: 400px; + } + + /* For ACP Auth (and login_body.html...probably), remove min-height */ + .auth-page-admin .login_container_left_section_content { + min-height: 0; + } + /* responsive logo + ---------------------------------------- */ + + + +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/responsive/medium-ipad.css b/styles/Milk_v2/theme/responsive/medium-ipad.css new file mode 100644 index 0000000..f87b9df --- /dev/null +++ b/styles/Milk_v2/theme/responsive/medium-ipad.css @@ -0,0 +1,220 @@ +@media (min-width: 992px)and (max-width: 1199px) { + + body{ border-top-color: #0F9;} + + /* Global Containers + ---------------------------------------- */ + + /* Add a small margin */ + .body-layout-Boxed #wrap { + padding: 0 15px; + width: auto; + } + + /* Sidebar Adjustments + ----------------------------*/ + + /* Hide left sidebar on tiny screens */ + #leftcolumn { + display: none; + } + + /* Clear center column padding */ + #contentcolumn { + padding-left: 0; + } + + /* Expand center column to fill empty gap */ + #contentcolumn { + margin-left: 0; + } + + /* Grid Forumlist + ---------------------------------------- */ + + /* Turn 3 columns into 2 */ + .forumlist_grid li.row { + width: 50%; + } + + /* Forumlist Colums + ---------------------------------------- */ + + /* Remove the 'last post' column */ + body.sidebar-both .forabg dd.lastpost, + body.sidebar-both .forumbg dd.lastpost, + body.sidebar-right-only .forabg dd.lastpost, + body.sidebar-right-only .forumbg dd.lastpost { + display: none; + } + + /* Increase the other margins to account for the (now missing) 300px */ + body.sidebar-both ul.topiclist.forums dt, + body.sidebar-right-only ul.topiclist.forums dt, + body.sidebar-both ul.topiclist.topics dt, + body.sidebar-right-only ul.topiclist.topics dt{ + margin-right: -195px; + } + + body.sidebar-both ul.topiclist dt .list-inner, + body.sidebar-right-only ul.topiclist dt .list-inner { + margin-right: 195px; + } + + body.sidebar-both li.header dt, + body.sidebar-right-only li.header dt { + margin-right: -235px; + } + + + + /* UCP Columns + ---------------------------------------- */ + + /* For the UCP columns, we need to override this */ + body.sidebar-both ul.topiclist.pmlist dt, body.sidebar-right-only ul.topiclist.pmlist dt { + margin-right: -95px; + } + + /* Same again... */ + body.sidebar-both ul.topiclist.pmlist dt .list-inner, body.sidebar-right-only ul.topiclist.pmlist dt .list-inner { + margin-right: 95px; + } + + /* UCP Two long columns */ + body.sidebar-both ul.two-long-columns dt .list-inner { + margin-right: 0; + } + body.sidebar-both ul.two-long-columns dd.lastpost { + display: none; + } + + + /* MCP Columns + ---------------------------------------- */ + + /* Manually enabling the responsive layout. First, we remove the second column */ + body.sidebar-both.body-layout-Boxed .missing-column dd, + body.sidebar-both.body-layout-Fluid .missing-column dd, + body.sidebar-right-only.body-layout-Boxed .missing-column dd, + body.sidebar-right-only.body-layout-Fluid .missing-column dd { + display: none; + } + + /* But we still want the mark column, so re-enable that */ + body.sidebar-both.body-layout-Boxed .missing-column dd.mark, + body.sidebar-both.body-layout-Fluid .missing-column dd.mark, + body.sidebar-right-only.body-layout-Boxed .missing-column dd.mark, + body.sidebar-right-only.body-layout-Fluid .missing-column dd.mark { + display: block; + } + + /* Reduce the left column margin to 95px, just enough for the mark box */ + body.sidebar-both.body-layout-Boxed ul.topiclist.missing-column dt, + body.sidebar-both.body-layout-Fluid ul.topiclist.missing-column dt, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.missing-column dt, + body.sidebar-right-only.body-layout-Fluid ul.topiclist.missing-column dt { + margin-right: -95px; + } + + body.sidebar-both.body-layout-Boxed ul.topiclist.missing-column dt .list-inner, + body.sidebar-both.body-layout-Fluid ul.topiclist.missing-column dt .list-inner, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.missing-column dt .list-inner, + body.sidebar-right-only.body-layout-Fluid ul.topiclist.missing-column dt .list-inner { + margin-right: 95px; + } + + /* Then enable the extra info below */ + body.sidebar-both.body-layout-Boxed ul.topiclist.missing-column .responsive-show, + body.sidebar-both.body-layout-Fluid ul.topiclist.missing-column .responsive-show, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.missing-column .responsive-show, + body.sidebar-right-only.body-layout-Fluid ul.topiclist.missing-column .responsive-show { + display: block !important; + } + + /* Hacky bookmark fix */ + body.sidebar-both.body-layout-Boxed ul.topiclist.missing-column li.header dt, + body.sidebar-both.body-layout-Fluid ul.topiclist.missing-column li.header dt, + body.sidebar-right-only.body-layout-Boxed ul.topiclist.missing-column li.header dt, + body.sidebar-right-only.body-layout-Fluid ul.topiclist.missing-column li.header dt { + margin-right: -125px; /* 95px + 30px padding */ + } + + + /* Manually enabling the responsive layout. First, we remove the second column header */ + body.sidebar-both.body-layout-Boxed ul.topiclist.two-long-columns dd, + body.sidebar-both.body-layout-Fluid ul.topiclist.two-long-columns dd, + body.sidebar-right-only.body-layout-Fluid ul.topiclist.two-long-columns dd { + display: none; + } + + /* Increase the left column width to 100% */ + body.sidebar-both.body-layout-Boxed ul.topiclist.two-long-columns dt, + body.sidebar-both.body-layout-Fluid ul.topiclist.two-long-columns dt, + body.sidebar-right-only.body-layout-Fluid ul.topiclist.two-long-columns dt .list-inner { + margin-right: 0; + } + + /* Then enable the extra info below */ + body.sidebar-both.body-layout-Boxed ul.topiclist.two-long-columns .responsive-show, + body.sidebar-both.body-layout-Fluid ul.topiclist.two-long-columns .responsive-show, + body.sidebar-right-only.body-layout-Fluid ul.topiclist.two-long-columns .responsive-show { + display: block !important; + } + + + + + + + /* Custom Login Page + ---------------------------------------- */ + + /* Clear the veritcal alignment. Make it wider for larger screens */ + .login_container { + width: 900px; + } + + /* For ACP Auth (and login_body.html...probably), reduce container width to 450px */ + .auth-page-admin .login_container { + width: 450px; + } + + /* Split the columns down */ + .login_container_left, .login_container_right { + width: 50%; + display: table-cell; + vertical-align: middle; + } + + /* Min height for left column content, so that it's taller than the registration box */ + .login_container_left_section_content { + min-height: 400px; + } + + /* For ACP Auth (and login_body.html...probably), remove min-height */ + .auth-page-admin .login_container_left_section_content { + min-height: 0; + } + + /* Subforums in column + ---------------------------------------- */ + ul.sub-list { + width: 100%; + } + + /* responsive logo + ---------------------------------------- */ + + /* override the dynamic inline width so that we can relatively resize logo below */ + #site-description { + width: 100% !important; + } + + #site-description img { + max-width: 85%; + } + + + +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/responsive/responsive.css b/styles/Milk_v2/theme/responsive/responsive.css new file mode 100644 index 0000000..16c139e --- /dev/null +++ b/styles/Milk_v2/theme/responsive/responsive.css @@ -0,0 +1,609 @@ +/* Responsive Design +---------------------------------------- */ + +@media (max-width: 320px) { + select, .inputbox { + max-width: 240px; + } +} + +/* Notifications list +----------------------------------------*/ +@media (max-width: 350px) { + .dropdown-extended .dropdown-contents { + width: auto; + } +} + +@media (max-width: 430px) { + .action-bar .search-box .inputbox { + width: 120px; + } + + .section-viewtopic .search-box .inputbox { + width: 57px; + } + + .action-bar .search-box .inputbox ::-moz-placeholder { + content: "Search..."; + } + + .action-bar .search-box .inputbox :-ms-input-placeholder { + content: "Search..."; + } + + .action-bar .search-box .inputbox ::-webkit-input-placeholder { + content: "Search..."; + } +} + +@media (max-width: 500px) { + dd label { + white-space: normal; + } + + select, .inputbox { + max-width: 260px; + } + + .captcha-panel dd.captcha { + margin-left: 0; + } + + .captcha-panel dd.captcha-image img { + width: 100%; + } + + dl.details dt, dl.details dd { + width: auto; + float: none; + text-align: left; + } + + dl.details dd { + margin-left: 20px; + } + + p.responsive-center { + float: none; + text-align: center; + margin-bottom: 5px; + } + + .action-bar > div { + margin-bottom: 5px; + } + + .action-bar > .pagination { + float: none; + clear: both; + padding-bottom: 1px; + text-align: center; + } + + .action-bar > .pagination li.page-jump { + margin: 0 2px; + } + + p.jumpbox-return { + display: none; + } + + .display-options > label:nth-child(1) { + display: block; + margin-bottom: 5px; + } + + .attach-controls { + margin-top: 5px; + width: 100%; + } + + .quick-links .dropdown-trigger span { + display: none; + } +} + +@media (max-width: 550px) { + ul.topiclist.forums dt { + margin-right: 0; + } + + ul.topiclist.forums dt .list-inner { + margin-right: 0; + } + + ul.topiclist.forums dd.lastpost { + display: none; + } +} + +@media (max-width: 700px) { + .responsive-hide { display: none !important; } + .responsive-show { display: block !important; } + .responsive-show-inline { display: inline !important; } + .responsive-show-inline-block { display: inline-block !important; } + + /* Content wrappers + ----------------------------------------*/ + html { + height: auto; + } + + body { + padding: 0; + } + + .wrap { + border: none; + border-radius: 0; + margin: 0; + padding: 0; + width: 100%; + min-width: 290px; + overflow: hidden; + + } + + .body-layout-Fluid #inner-wrap { + width: 100%; + padding: 10px + } + + /* Common block wrappers + ----------------------------------------*/ + .headerbar { + background-size: cover !important; + } + + .headerbar, .navbar, .forabg, .forumbg, .post, .panel { + border-radius: 0; + margin-left: -5px; + margin-right: -5px; + } + + .cp-main .forabg, .cp-main .forumdb, .cp-main .post, .cp-main .panel { + border-radius: 7px; + } + + /* Logo block + ----------------------------------------*/ + .site-description { + float: none; + width: auto; + text-align: center; + } + + .logo { + display: block; + float: none; + padding: 0; + } + + .site_logo { + margin: 50px 0 0 0; + } + + .site-description h1, .site-description p { + text-align: inherit; + float: none; + line-height: 1.2em; + overflow: hidden; + text-overflow: ellipsis; + } + + .site-description p, .search-header { + display: none; + } + + /* Navigation + ----------------------------------------*/ + .headerbar + .navbar { + margin-top: -5px; + } + + /* Search + ----------------------------------------*/ + .responsive-search { display: block !important; } + + /* .topiclist lists + ----------------------------------------*/ + li.header dt { + text-align: center; + text-transform: none; + line-height: 1em; + font-size: 1.2em; + padding-bottom: 4px; + } + + ul.topiclist li.header dt, ul.topiclist li.header dt .list-inner { + margin-right: 0 !important; + padding-right: 0; + } + + ul.topiclist li.header dd { + display: none !important; + } + + ul.topiclist dt, ul.topiclist dt .list-inner, + ul.topiclist.missing-column dt, ul.topiclist.missing-column dt .list-inner, + ul.topiclist.two-long-columns dt, ul.topiclist.two-long-columns dt .list-inner, + ul.topiclist.two-columns dt, ul.topiclist.two-columns dt .list-inner { + margin-right: 0; + } + + ul.topiclist dt .list-inner.with-mark { + padding-right: 34px; + } + + ul.topiclist dt .list-inner { + min-height: 28px; + } + + ul.topiclist li.header dt .list-inner { + min-height: 0; + } + + ul.topiclist dd { + display: none; + } + ul.topiclist dd.mark { + display: block; + } + + /* Forums and topics lists + ----------------------------------------*/ + ul.topiclist.forums dt { + margin-right: -250px; + } + + ul.topiclist dd.mark { + display: block; + position: absolute; + right: 5px; + top: 0; + margin: 0; + width: auto; + min-width: 0; + text-align: left; + } + + ul.topiclist.forums dd.topics dfn, ul.topiclist.topics dd.posts dfn { + position: relative; + left: 0; + width: auto; + display: inline; + font-weight: normal; + } + + li.header dt { + padding-left: 0; + } + + li.row .responsive-show strong { + font-weight: bold; + color: inherit; + } + + ul.topiclist li.row dt a.subforum { + vertical-align: bottom; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100px; + } + + /* Pagination + ----------------------------------------*/ + .pagination > ul { + margin: 5px 0 0; + } + + .row .pagination .ellipsis + li { + display: none !important; + } + + /* Responsive tables + ----------------------------------------*/ + table.responsive, table.responsive tbody, table.responsive tr, table.responsive td { + display: block; + } + + table.responsive thead, table.responsive th { + display: none; + } + + table.responsive.show-header thead, table.responsive.show-header th:first-child { + display: block; + width: auto !important; + text-align: left !important; + } + + table.responsive.show-header th:first-child span.rank-img { + display: none; + } + + table.responsive tr { + margin: 2px 0; + } + + table.responsive td { + width: auto !important; + text-align: left !important; + padding: 4px; + } + + table.responsive td.empty { + display: none !important; + } + + table.responsive td > dfn { + display: inline-block !important; + } + + table.responsive td > dfn:after { + content: ':'; + padding-right: 5px; + } + + table.responsive span.rank-img { + float: none; + padding-right: 5px; + } + + table.responsive.memberlist td:first-child input[type="checkbox"] { + float: right; + } + + /* Forms + ----------------------------------------*/ + fieldset dt, fieldset.fields1 dt, fieldset.fields2 dt { + width: auto; + float: none; + } + + fieldset dd, fieldset.fields1 dd, fieldset.fields2 dd { + margin-left: 0px; + } + + textarea, dd textarea, .message-box textarea { + width: 100%; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + dl.pmlist dt { + width: auto !important; + margin-bottom: 5px; + } + + dl.pmlist dd { + display: inline-block; + margin-left: 0 !important; + } + + dl.pmlist dd:first-of-type { + padding-left: 20px; + } + + .smiley-box, .message-box { + float: none; + width: auto; + } + + .smiley-box { + margin-top: 5px; + } + + .bbcode-status { + display: none; + } + + .colour-palette, .colour-palette tbody, .colour-palette tr { + display: block; + } + + .colour-palette td { + display: inline-block; + margin-right: 2px; + } + + .horizontal-palette td:nth-child(2n), .vertical-palette tr:nth-child(2n) { + display: none; + } + + fieldset.quick-login label { + display: block; + margin-bottom: 5px; + white-space: normal; + } + + fieldset.quick-login label > span { + display: inline-block; + min-width: 100px; + } + + fieldset.quick-login input.inputbox { + width: 85%; + max-width: 300px; + margin-left: 20px; + } + + fieldset.quick-login label[for="autologin"] { + display: inline-block; + text-align: right; + min-width: 50%; + } + + /* User profile + ----------------------------------------*/ + .column1, .column2, .left-box.profile-details { + float: none; + width: auto; + clear: both; + } + + /* Polls + ----------------------------------------*/ + fieldset.polls dt { + width: 90%; + } + + fieldset.polls dd.resultbar { + padding-left: 20px; + } + + fieldset.polls dd.poll_option_percent { + width: 20%; + } + + fieldset.polls dd.resultbar, fieldset.polls dd.poll_option_percent { + margin-top: 5px; + } + + /* Post + ----------------------------------------*/ + .postbody { + position: inherit; + float: none !important; + } + + .postprofile, .postbody, .search .postbody { + display: block; + width: auto; + float: none; + padding: 0; + min-height: 0; + text-align: left; + } + + .post .postprofile { + width: auto; + border-width: 0 0 1px 0; + padding-bottom: 5px; + margin: 0; + margin-bottom: 5px; + min-height: 40px; + overflow: hidden; + float: none; + } + + .postprofile dd { + display: none; + } + + .postprofile dt, .postprofile dd.profile-rank, .search .postprofile dd { + display: block; + margin: 0; + } + + .postprofile .has-avatar .avatar-container { + margin: 0; + overflow: inherit; + } + + .postprofile .avatar-container:after { + clear: none; + } + + .postprofile .avatar { + margin: 0 5px 0 0px; + float: left; + } + + .postprofile .avatar img { + width: auto !important; + height: auto !important; + max-height: 32px; + } + + .has-profile .postbody h3 { + margin-left: 0 !important; + margin-right: 0 !important; + } + + .has-profile .post-buttons { + right: 30px; + top: 15px; + } + + .online { + background-size: 40px; + } + + /* Misc stuff + ----------------------------------------*/ + h2 { + margin-top: .5em; + } + + p { + margin-bottom: .5em; + overflow: hidden; + } + + p.rightside { + margin-bottom: 0; + } + + fieldset.display-options label { + display: block; + clear: both; + margin-bottom: 5px; + } + + dl.mini dd.pm-legend { + float: left; + min-width: 200px; + } + + .topicreview { + margin: 0 -5px; + padding: 0 5px; + } + + fieldset.display-actions { + white-space: normal; + } + + .phpbb_alert { + width: auto; + margin: 0 5px; + } + + .attach-comment dfn { + width: 100%; + } + +} + +@media (min-width: 700px) { + .postbody { width: 70%; } +} + +@media (min-width: 850px) { + .postbody { width: 76%; } +} + +@media (max-width: 850px) { + .postprofile { width: 28%; } +} + +@media (min-width: 701px) and (max-width: 950px) { + .row .pagination { + margin-top: 2px; + margin-bottom: 2px; + } + + ul.topiclist dt { + margin-right: -410px; + } + + ul.topiclist dt .list-inner { + margin-right: 410px; + } + + dd.posts, dd.topics, dd.views { + width: 80px; + } +} + diff --git a/styles/Milk_v2/theme/responsive/small-smaller-tablets.css b/styles/Milk_v2/theme/responsive/small-smaller-tablets.css new file mode 100644 index 0000000..2a3122e --- /dev/null +++ b/styles/Milk_v2/theme/responsive/small-smaller-tablets.css @@ -0,0 +1,152 @@ +@media (min-width: 768px) and (max-width: 991px) { + + body{ border-top-color: #FF3;} + + /* Global Containers + ---------------------------------------- */ + + /* Add a small margin */ + .body-layout-Boxed #wrap { + padding: 0 15px; + width: auto; + } + + + /* Sidebar Structure + ---------------------------------------- */ + + /* Hide left sidebar on tiny screens and move it out of the way */ + #leftcolumn { + display: none; + margin-left: -100%; + } + + /* Clear center column padding */ + #contentcolumn { + margin: 0; + padding: 0; + } + + /* Break right column (sidebar) onto new line cleanly */ + #rightcolumn { + float: none; + width: 100%; + margin-left: 0; + clear: both; + } + + /* Sidebar Widgets + ---------------------------------------- */ + .sidebar_widget { + table-layout: fixed; + } + + .profile_widget_avatar, .profile_widget_info, .profile_widget_list { + display: table-cell; + vertical-align: top; + float: none; + width: auto; + } + + .profile_widget_avatar { + margin-bottom: 0; + } + + .profile_widget_info { + padding: 0 30px; + } + + .profile_widget_list { + padding-top: 0; + border-top: none; + } + + /* Grid Forumlist + ---------------------------------------- */ + + /* Turn 3 columns into 2 */ + .forumlist_grid li.row { + width: 50%; + } + + /* Forumlist Colums + ---------------------------------------- */ + + /* Remove the 'topics' column */ + .forabg dd.posts, .forumbg dd.views { + display: none; + } + + /* Or, reduce the width of 'simpleposts' by 50% (from 190 to 95) */ + dd.simpleposts { + width: 95px; + } + + /* Adjust the other margins to account for the (now missing) 95px */ + ul.topiclist dt { + margin-right: -395px; + } + + ul.topiclist dt .list-inner { + margin-right: 395px; + } + + .forabg li.header dt, .forumbg li.header dt { + margin-right: -435px; + } + + + /* Custom Login Page + ---------------------------------------- */ + + /* Clear the veritcal alignment. Top alignment is fine for smaller devices. Also make it narrower */ + .login_container { + display: block; + width: 450px; + } + + /* Increase left and right container width to 100% */ + .login_container_left, .login_container_right { + width: 100%; + display: block; + } + + /* Bit of top margin though, just so it doesn't look totally weird */ + .login_container_left { + margin-top: 50px; + } + + /* no min height on the left content for phones, way too small */ + .login_container_left_section_content { + min-height: 0; + } + + /* Misc + ---------------------------------------- */ + ul.topiclist li.stat_login_hide { + display: none; + } + + /* Hide the topic type labels when we're in a boxed layout with both sidebars */ + span.topic_type span { + display: none; + } + + /* Subforums in column + ---------------------------------------- */ + ul.sub-list { + width: 100%; + } + + /* responsive logo + ---------------------------------------- */ + + /* override the dynamic inline width so that we can relatively resize logo below */ + #site-description { + width: 100% !important; + } + + #site-description img { + max-width: 85%; + } +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/responsive/squishy.css b/styles/Milk_v2/theme/responsive/squishy.css new file mode 100644 index 0000000..b0b99e1 --- /dev/null +++ b/styles/Milk_v2/theme/responsive/squishy.css @@ -0,0 +1,163 @@ +/* Interim Breakpoints to stop the content squishing up when sidebars are enabled +---------------------------------------- */ + + /* Hide the topic type labels when we're in a boxed layout with both sidebars */ + body.sidebar-both.body-layout-Boxed span.topic_type span { + display: none; + } + + + /* Forumlist Colums + ---------------------------------------- */ + + /* When the layout is fluid and both sidebars are enabled, hide the 'lastpost' column */ + @media (min-width: 1200px)and (max-width: 1299px) { + /* Remove the 'topics' column */ + body.sidebar-both.body-layout-Fluid .forabg dd.posts, + body.sidebar-both.body-layout-Fluid .forumbg dd.views { + display: none; + } + + /* Or, reduce the width of 'simpleposts' by 50% (from 190 to 95) */ + body.sidebar-both.body-layout-Fluid dd.simpleposts, + body.sidebar-right-only.body-layout-Fluid dd.simpleposts { + width: 95px; + } + + /* Adjust the other margins to account for the (now missing) 95px */ + body.sidebar-both.body-layout-Fluid ul.topiclist dt { + margin-right: -395px; + } + + + body.sidebar-both.body-layout-Fluid ul.topiclist dt .list-inner { + margin-right: 395px; + } + + body.sidebar-both.body-layout-Fluid li.header dt { + margin-right: -435px; + } + + /* UCP Columns + ---------------------------------------- */ + + body.sidebar-both ul.topiclist.pmlist dt .list-inner, body.sidebar-right-only ul.topiclist.pmlist dt .list-inner { + margin-right: 95px; + } + body.sidebar-both ul.topiclist.two-columns dt { + margin-right: -95px; + } + body.sidebar-both ul.topiclist.two-columns li.header dt { + margin-right: -130px; + } + + /* UCP Two long columns */ + body.sidebar-both ul.two-long-columns dt .list-inner { + margin-right: 0 !important; + } + + /* MCP Columns + ---------------------------------------- */ + + /* Manually enabling the responsive layout. First, we remove the second column header */ + body.sidebar-right-only.body-layout-Fluid .missing-column dd { + display: none; + } + + /* But we still want the mark column, so re-enable that */ + body.sidebar-right-only.body-layout-Fluid .missing-column dd.mark { + display: block; + } + + /* Reduce the left column margin to 95px, just enough for the mark box */ + body.sidebar-right-only.body-layout-Fluid ul.topiclist.missing-column dt { + margin-right: -95px; + } + + body.sidebar-right-only.body-layout-Fluid ul.topiclist.missing-column dt .list-inner { + margin-right: 95px; + } + + /* Then enable the extra info below */ + body.sidebar-right-only.body-layout-Fluid ul.topiclist.missing-column .responsive-show { + display: block !important; + } + + /* Manually enabling the responsive layout. First, we remove the second column header */ + body.sidebar-right-only.body-layout-Fluid ul.topiclist.two-long-columns dd { + display: none; + } + + /* Increase the left column width to 100% */ + body.sidebar-right-only.body-layout-Fluid ul.topiclist.two-long-columns dt .list-inner { + margin-right: 0; + } + + /* Then enable the extra info below */ + body.sidebar-right-only.body-layout-Fluid ul.topiclist.two-long-columns .responsive-show { + display: block !important; + } + + + } + + /* Between 1200 and 1400px, and when we're fluid with both sidebars enabled, apply the responsive layout for mcp missing-column */ + @media (min-width: 1200px)and (max-width: 1399px) { + /* Manually enabling the responsive layout. First, we remove the second column header */ + body.sidebar-both.body-layout-Fluid .missing-column dd { + display: none; + } + + /* But we still want the mark column, so re-enable that */ + body.sidebar-both.body-layout-Fluid .missing-column dd.mark { + display: block; + } + + /* Reduce the left column margin to 95px, just enough for the mark box */ + body.sidebar-both.body-layout-Fluid ul.topiclist.missing-column dt { + margin-right: -95px; + } + + body.sidebar-both.body-layout-Fluid ul.topiclist.missing-column dt .list-inner { + margin-right: 95px; + } + + /* Then enable the extra info below */ + body.sidebar-both.body-layout-Fluid ul.topiclist.missing-column .responsive-show { + display: block !important; + } + + /* Manually enabling the responsive layout. First, we remove the second column header */ + body.sidebar-both.body-layout-Fluid ul.topiclist.two-long-columns dd { + display: none; + } + + /* Increase the left column width to 100% */ + body.sidebar-both.body-layout-Fluid ul.topiclist.two-long-columns dt .list-inner { + margin-right: 0; + } + + /* Then enable the extra info below */ + body.sidebar-both.body-layout-Fluid ul.topiclist.two-long-columns .responsive-show { + display: block !important; + } + + + + } + + /* Grid Forumlist + ---------------------------------------- */ + + /* When boxed and both sidebars are enabled, reduce grid forumlist down to 2 columns */ + body.sidebar-both.body-layout-Boxed .forumlist_grid li.row { + width: 50%; + } + + /* When fluid and the right sidebar is present, reduce to 2 columns between 1200px and 1500px */ + @media (min-width: 1200px)and (max-width: 1499px) { + body.sidebar-both.body-layout-Fluid .forumlist_grid li.row, body.sidebar-right-only.body-layout-Fluid .forumlist_grid li.row { + width: 50%; + } + } + \ No newline at end of file diff --git a/styles/Milk_v2/theme/responsive/xs-phones.css b/styles/Milk_v2/theme/responsive/xs-phones.css new file mode 100644 index 0000000..9658fbe --- /dev/null +++ b/styles/Milk_v2/theme/responsive/xs-phones.css @@ -0,0 +1,105 @@ +@media (max-width: 767px) { + + body{ border-top-color: #FF0000;} + + /* Sidebar Adjustments + ----------------------------*/ + + /* Hide left sidebar on tiny screens. Adjust the CSS to stop it borking the other columns */ + #leftcolumn { + display: none; + float: none; + width: 100%; + clear: both; + margin-left: 0; + } + + /* Center column padding/margin adjustments */ + #contentcolumn { + margin: 0 !important; + padding: 0; + } + + /* Break right column onto new line cleanly */ + #rightcolumn { + float: none; + width: 100%; + margin-left: 0; + clear: both; + } + + /* Sidebar Widgets + ---------------------------------------- */ + .sidebar_widget { + table-layout: fixed; + } + + .profile_widget_avatar, .profile_widget_info, .profile_widget_list { + display: table-cell; + vertical-align: top; + float: none; + width: auto; + } + + .profile_widget_avatar { + margin-bottom: 0; + } + + .profile_widget_info { + padding: 0 30px; + } + + .profile_widget_list { + padding-top: 0; + border-top: none; + } + + /* Grid Forumlist + ---------------------------------------- */ + + /* Turn 3 columns into 1 */ + .forumlist_grid li.row { + width: 100%; + } + + + /* Custom Login Page + ---------------------------------------- */ + + /* Last Post Avatar + ---------------------------------------- */ + span.lastpostavatar { + display: none; + } + + /* Subforums in column + ---------------------------------------- */ + ul.sub-list { + width: 100%; + } + + /* responsive logo + ---------------------------------------- */ + + /* override the dynamic inline width so that we can relatively resize logo below */ + #site-description { + width: 100% !important; + } + + #site-description img { + max-width: 85%; + } + + /* pagination fix + ---------------------------------------- */ + .row .pagination { + display: block; + margin-top: 0; + } + + /* Fix overlapping forum images */ + .list-inner img { + max-width: 100%; + } + +} \ No newline at end of file diff --git a/styles/Milk_v2/theme/stylesheet_jour.css b/styles/Milk_v2/theme/stylesheet_jour.css new file mode 100644 index 0000000..96f21db --- /dev/null +++ b/styles/Milk_v2/theme/stylesheet_jour.css @@ -0,0 +1,40 @@ +/* phpBB3 Style Sheet + -------------------------------------------------------------- + phpBB style name: Milk v2 + Based on style: Merlin Framework (http://www.planetstyles.net) + -------------------------------------------------------------- +*/ + +/* Core */ +@import url("merlin.css"); +@import url("normalize.css"); +@import url("base.css"); +@import url("utilities.css"); +@import url("common.css"); +@import url("links.css"); +@import url("content.css"); +@import url("buttons.css"); +@import url("cp.css"); +@import url("forms.css"); + +/* Icons */ +@import url("icons.css"); +@import url("icons_forums_topics.css"); + +/* Custom Additions */ +@import url("animate.css"); +@import url("tooltipster.bundle.min.css"); +@import url("tooltipster-sideTip-borderless.min.css"); + +/* Responsive */ +@import url("responsive/responsive.css"); +@import url("responsive/xs-phones.css"); +@import url("responsive/small-smaller-tablets.css"); +@import url("responsive/medium-ipad.css"); +@import url("responsive/large-desktops.css"); +@import url("responsive/squishy.css"); + +/* Facelift */ +@import url("colours.css"); +@import url("fonts.css"); +@import url("milk.css"); \ No newline at end of file diff --git a/styles/Milk_v2/theme/stylesheet_nuit.css b/styles/Milk_v2/theme/stylesheet_nuit.css new file mode 100644 index 0000000..96f21db --- /dev/null +++ b/styles/Milk_v2/theme/stylesheet_nuit.css @@ -0,0 +1,40 @@ +/* phpBB3 Style Sheet + -------------------------------------------------------------- + phpBB style name: Milk v2 + Based on style: Merlin Framework (http://www.planetstyles.net) + -------------------------------------------------------------- +*/ + +/* Core */ +@import url("merlin.css"); +@import url("normalize.css"); +@import url("base.css"); +@import url("utilities.css"); +@import url("common.css"); +@import url("links.css"); +@import url("content.css"); +@import url("buttons.css"); +@import url("cp.css"); +@import url("forms.css"); + +/* Icons */ +@import url("icons.css"); +@import url("icons_forums_topics.css"); + +/* Custom Additions */ +@import url("animate.css"); +@import url("tooltipster.bundle.min.css"); +@import url("tooltipster-sideTip-borderless.min.css"); + +/* Responsive */ +@import url("responsive/responsive.css"); +@import url("responsive/xs-phones.css"); +@import url("responsive/small-smaller-tablets.css"); +@import url("responsive/medium-ipad.css"); +@import url("responsive/large-desktops.css"); +@import url("responsive/squishy.css"); + +/* Facelift */ +@import url("colours.css"); +@import url("fonts.css"); +@import url("milk.css"); \ No newline at end of file diff --git a/styles/Milk_v2/theme/tooltipster-sideTip-borderless.min.css b/styles/Milk_v2/theme/tooltipster-sideTip-borderless.min.css new file mode 100644 index 0000000..19408cb --- /dev/null +++ b/styles/Milk_v2/theme/tooltipster-sideTip-borderless.min.css @@ -0,0 +1 @@ +.tooltipster-sidetip.tooltipster-borderless .tooltipster-box{border:none;background:#1b1b1b;background:rgba(10,10,10,.9)}.tooltipster-sidetip.tooltipster-borderless.tooltipster-bottom .tooltipster-box{margin-top:8px}.tooltipster-sidetip.tooltipster-borderless.tooltipster-left .tooltipster-box{margin-right:8px}.tooltipster-sidetip.tooltipster-borderless.tooltipster-right .tooltipster-box{margin-left:8px}.tooltipster-sidetip.tooltipster-borderless.tooltipster-top .tooltipster-box{margin-bottom:8px}.tooltipster-sidetip.tooltipster-borderless .tooltipster-arrow{height:8px;margin-left:-8px;width:16px}.tooltipster-sidetip.tooltipster-borderless.tooltipster-left .tooltipster-arrow,.tooltipster-sidetip.tooltipster-borderless.tooltipster-right .tooltipster-arrow{height:16px;margin-left:0;margin-top:-8px;width:8px}.tooltipster-sidetip.tooltipster-borderless .tooltipster-arrow-background{display:none}.tooltipster-sidetip.tooltipster-borderless .tooltipster-arrow-border{border:8px solid transparent}.tooltipster-sidetip.tooltipster-borderless.tooltipster-bottom .tooltipster-arrow-border{border-bottom-color:#1b1b1b;border-bottom-color:rgba(10,10,10,.9)}.tooltipster-sidetip.tooltipster-borderless.tooltipster-left .tooltipster-arrow-border{border-left-color:#1b1b1b;border-left-color:rgba(10,10,10,.9)}.tooltipster-sidetip.tooltipster-borderless.tooltipster-right .tooltipster-arrow-border{border-right-color:#1b1b1b;border-right-color:rgba(10,10,10,.9)}.tooltipster-sidetip.tooltipster-borderless.tooltipster-top .tooltipster-arrow-border{border-top-color:#1b1b1b;border-top-color:rgba(10,10,10,.9)}.tooltipster-sidetip.tooltipster-borderless.tooltipster-bottom .tooltipster-arrow-uncropped{top:-8px}.tooltipster-sidetip.tooltipster-borderless.tooltipster-right .tooltipster-arrow-uncropped{left:-8px} \ No newline at end of file diff --git a/styles/Milk_v2/theme/tooltipster.bundle.min.css b/styles/Milk_v2/theme/tooltipster.bundle.min.css new file mode 100644 index 0000000..d8f30fe --- /dev/null +++ b/styles/Milk_v2/theme/tooltipster.bundle.min.css @@ -0,0 +1 @@ +.tooltipster-fall,.tooltipster-grow.tooltipster-show{-webkit-transition-timing-function:cubic-bezier(.175,.885,.32,1);-moz-transition-timing-function:cubic-bezier(.175,.885,.32,1.15);-ms-transition-timing-function:cubic-bezier(.175,.885,.32,1.15);-o-transition-timing-function:cubic-bezier(.175,.885,.32,1.15)}.tooltipster-base{display:flex;pointer-events:none;position:absolute}.tooltipster-box{flex:1 1 auto}.tooltipster-content{box-sizing:border-box;max-height:100%;max-width:100%;overflow:auto}.tooltipster-ruler{bottom:0;left:0;overflow:hidden;position:fixed;right:0;top:0;visibility:hidden}.tooltipster-fade{opacity:0;-webkit-transition-property:opacity;-moz-transition-property:opacity;-o-transition-property:opacity;-ms-transition-property:opacity;transition-property:opacity}.tooltipster-fade.tooltipster-show{opacity:1}.tooltipster-grow{-webkit-transform:scale(0,0);-moz-transform:scale(0,0);-o-transform:scale(0,0);-ms-transform:scale(0,0);transform:scale(0,0);-webkit-transition-property:-webkit-transform;-moz-transition-property:-moz-transform;-o-transition-property:-o-transform;-ms-transition-property:-ms-transform;transition-property:transform;-webkit-backface-visibility:hidden}.tooltipster-grow.tooltipster-show{-webkit-transform:scale(1,1);-moz-transform:scale(1,1);-o-transform:scale(1,1);-ms-transform:scale(1,1);transform:scale(1,1);-webkit-transition-timing-function:cubic-bezier(.175,.885,.32,1.15);transition-timing-function:cubic-bezier(.175,.885,.32,1.15)}.tooltipster-swing{opacity:0;-webkit-transform:rotateZ(4deg);-moz-transform:rotateZ(4deg);-o-transform:rotateZ(4deg);-ms-transform:rotateZ(4deg);transform:rotateZ(4deg);-webkit-transition-property:-webkit-transform,opacity;-moz-transition-property:-moz-transform;-o-transition-property:-o-transform;-ms-transition-property:-ms-transform;transition-property:transform}.tooltipster-swing.tooltipster-show{opacity:1;-webkit-transform:rotateZ(0);-moz-transform:rotateZ(0);-o-transform:rotateZ(0);-ms-transform:rotateZ(0);transform:rotateZ(0);-webkit-transition-timing-function:cubic-bezier(.23,.635,.495,1);-webkit-transition-timing-function:cubic-bezier(.23,.635,.495,2.4);-moz-transition-timing-function:cubic-bezier(.23,.635,.495,2.4);-ms-transition-timing-function:cubic-bezier(.23,.635,.495,2.4);-o-transition-timing-function:cubic-bezier(.23,.635,.495,2.4);transition-timing-function:cubic-bezier(.23,.635,.495,2.4)}.tooltipster-fall{-webkit-transition-property:top;-moz-transition-property:top;-o-transition-property:top;-ms-transition-property:top;transition-property:top;-webkit-transition-timing-function:cubic-bezier(.175,.885,.32,1.15);transition-timing-function:cubic-bezier(.175,.885,.32,1.15)}.tooltipster-fall.tooltipster-initial{top:0!important}.tooltipster-fall.tooltipster-dying{-webkit-transition-property:all;-moz-transition-property:all;-o-transition-property:all;-ms-transition-property:all;transition-property:all;top:0!important;opacity:0}.tooltipster-slide{-webkit-transition-property:left;-moz-transition-property:left;-o-transition-property:left;-ms-transition-property:left;transition-property:left;-webkit-transition-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transition-timing-function:cubic-bezier(.175,.885,.32,1.15);-moz-transition-timing-function:cubic-bezier(.175,.885,.32,1.15);-ms-transition-timing-function:cubic-bezier(.175,.885,.32,1.15);-o-transition-timing-function:cubic-bezier(.175,.885,.32,1.15);transition-timing-function:cubic-bezier(.175,.885,.32,1.15)}.tooltipster-slide.tooltipster-initial{left:-40px!important}.tooltipster-slide.tooltipster-dying{-webkit-transition-property:all;-moz-transition-property:all;-o-transition-property:all;-ms-transition-property:all;transition-property:all;left:0!important;opacity:0}@keyframes tooltipster-fading{0%{opacity:0}100%{opacity:1}}.tooltipster-update-fade{animation:tooltipster-fading .4s}@keyframes tooltipster-rotating{25%{transform:rotate(-2deg)}75%{transform:rotate(2deg)}100%{transform:rotate(0)}}.tooltipster-update-rotate{animation:tooltipster-rotating .6s}@keyframes tooltipster-scaling{50%{transform:scale(1.1)}100%{transform:scale(1)}}.tooltipster-update-scale{animation:tooltipster-scaling .6s}.tooltipster-sidetip .tooltipster-box{background:#565656;border:2px solid #000;border-radius:4px}.tooltipster-sidetip.tooltipster-bottom .tooltipster-box{margin-top:8px}.tooltipster-sidetip.tooltipster-left .tooltipster-box{margin-right:8px}.tooltipster-sidetip.tooltipster-right .tooltipster-box{margin-left:8px}.tooltipster-sidetip.tooltipster-top .tooltipster-box{margin-bottom:8px}.tooltipster-sidetip .tooltipster-content{color:#fff;line-height:18px;padding:6px 14px}.tooltipster-sidetip .tooltipster-arrow{overflow:hidden;position:absolute}.tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow{height:10px;margin-left:-10px;top:0;width:20px}.tooltipster-sidetip.tooltipster-left .tooltipster-arrow{height:20px;margin-top:-10px;right:0;top:0;width:10px}.tooltipster-sidetip.tooltipster-right .tooltipster-arrow{height:20px;margin-top:-10px;left:0;top:0;width:10px}.tooltipster-sidetip.tooltipster-top .tooltipster-arrow{bottom:0;height:10px;margin-left:-10px;width:20px}.tooltipster-sidetip .tooltipster-arrow-background,.tooltipster-sidetip .tooltipster-arrow-border{height:0;position:absolute;width:0}.tooltipster-sidetip .tooltipster-arrow-background{border:10px solid transparent}.tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-background{border-bottom-color:#565656;left:0;top:3px}.tooltipster-sidetip.tooltipster-left .tooltipster-arrow-background{border-left-color:#565656;left:-3px;top:0}.tooltipster-sidetip.tooltipster-right .tooltipster-arrow-background{border-right-color:#565656;left:3px;top:0}.tooltipster-sidetip.tooltipster-top .tooltipster-arrow-background{border-top-color:#565656;left:0;top:-3px}.tooltipster-sidetip .tooltipster-arrow-border{border:10px solid transparent;left:0;top:0}.tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-border{border-bottom-color:#000}.tooltipster-sidetip.tooltipster-left .tooltipster-arrow-border{border-left-color:#000}.tooltipster-sidetip.tooltipster-right .tooltipster-arrow-border{border-right-color:#000}.tooltipster-sidetip.tooltipster-top .tooltipster-arrow-border{border-top-color:#000}.tooltipster-sidetip .tooltipster-arrow-uncropped{position:relative}.tooltipster-sidetip.tooltipster-bottom .tooltipster-arrow-uncropped{top:-10px}.tooltipster-sidetip.tooltipster-right .tooltipster-arrow-uncropped{left:-10px} \ No newline at end of file diff --git a/styles/Milk_v2/theme/tweaks.css b/styles/Milk_v2/theme/tweaks.css new file mode 100644 index 0000000..ba82551 --- /dev/null +++ b/styles/Milk_v2/theme/tweaks.css @@ -0,0 +1,41 @@ +/* Style Sheet Tweaks + +These style definitions are IE 8 & 9 only. +They are required due to the poor CSS support in IE browsers. +------------------------------------------------------------------------------*/ + +/* IE 8 Tweaks (value)\9 equates to IE <= 8 +------------------------------------------------------------------------------*/ + +/* Clear float fix */ +.inner, ul.linklist { zoom: 1\9; } + +/* Align checkboxes/radio buttons nicely */ +dd label input { vertical-align: text-bottom\9; } + +/* Fixes header-avatar aspect-ratio */ +.header-avatar img { height: 20px\9; } + +/* IE8 often can't handle max-width in %, so we use px instead */ +.postprofile .avatar img { max-width: 150px\9; } + + +/* IE 9 Tweaks +------------------------------------------------------------------------------*/ + +/* Border-radius bleed fix in IE9 */ +.search-header, .search-header .inputbox, .search-header a.button { + border-radius: 0; +} + +.headerbar, .forumbg { + background-image: url("./images/bg_header.gif"); +} + +.forabg { + background-image: url("./images/bg_list.gif"); +} + +.tabs .tab > a { + border-radius: 0; +} diff --git a/styles/Milk_v2/theme/utilities.css b/styles/Milk_v2/theme/utilities.css new file mode 100644 index 0000000..cbb8127 --- /dev/null +++ b/styles/Milk_v2/theme/utilities.css @@ -0,0 +1,66 @@ +/* -------------------------------------------------------------- + $Utilities +-------------------------------------------------------------- */ + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} + +.clearfix:before, +.clearfix:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after { + content: " "; + display: table; +} +.clearfix:after, +.container:after, +.container-fluid:after, +.row:after { clear: both } + +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} + +.pull-right { float: right !important } +.pull-left { float: left !important } +.hide { display: none !important } +.show { display: block !important } +.invisible { visibility: hidden } + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.hidden { + display: none ; +} + +.affix { position: fixed } diff --git a/styles/all/template/feed.xml.twig b/styles/all/template/feed.xml.twig new file mode 100644 index 0000000..91467c6 --- /dev/null +++ b/styles/all/template/feed.xml.twig @@ -0,0 +1,37 @@ + + + + + {% if not FEED_TITLE is empty %}{{ FEED_TITLE }}{% endif %} + + {% if not FEED_SUBTITLE is empty %}{{ FEED_SUBTITLE }}{% endif %} + + {% if not FEED_LINK is empty %}{% endif %} + + {{ FEED_UPDATED }} + + + {{ SELF_LINK }} + + {% for row in FEED_ROWS %} + + {% if not row.author is empty %}{% endif %} + + {% if not row.updated is empty %}{{ row.updated }} {% else %}{{ row.published }}{% endif %} + + {% if not row.published is empty %}{{ row.published }}{% endif %} + + {{ row.link }} + + <![CDATA[{{ row.title }}]]> + + {% if not row.category is empty and row.category_name is defined and row.category_name != '' %} + + {% endif %} + + {{ lang('STATISTICS') }}: {{ row.statistics }}

{% endif %}
+]]>
+
+ {% endfor %} +
diff --git a/styles/prosilver/style.cfg b/styles/prosilver/style.cfg new file mode 100644 index 0000000..4485b17 --- /dev/null +++ b/styles/prosilver/style.cfg @@ -0,0 +1,32 @@ +# +# phpBB Style Configuration File +# +# This file is part of the phpBB Forum Software package. +# +# @copyright (c) phpBB Limited +# @license GNU General Public License, version 2 (GPL-2.0) +# +# For full copyright and license information, please see +# the docs/CREDITS.txt file. +# +# At the left is the name, please do not change this +# At the right the value is entered +# +# Values get trimmed, if you want to add a space in front or at the end of +# the value, then enclose the value with single or double quotes. +# Single and double quotes do not need to be escaped. +# +# + +# General Information about this style +name = prosilver +copyright = © phpBB Limited, 2007 +style_version = 3.2.7 +phpbb_version = 3.2.7 + +# Defining a different template bitfield +# template_bitfield = //g= + +# Parent style +# Set value to empty or to this style's name if this style does not have a parent style +parent = prosilver diff --git a/styles/prosilver/template/ajax.js b/styles/prosilver/template/ajax.js new file mode 100644 index 0000000..5e66e5c --- /dev/null +++ b/styles/prosilver/template/ajax.js @@ -0,0 +1,394 @@ +/* global phpbb */ + +(function($) { // Avoid conflicts with other libraries + +'use strict'; + +// This callback will mark all forum icons read +phpbb.addAjaxCallback('mark_forums_read', function(res) { + var readTitle = res.NO_UNREAD_POSTS; + var unreadTitle = res.UNREAD_POSTS; + var iconsArray = { + forum_unread: 'forum_read', + forum_unread_subforum: 'forum_read_subforum', + forum_unread_locked: 'forum_read_locked' + }; + + $('li.row').find('dl[class*="forum_unread"]').each(function() { + var $this = $(this); + + $.each(iconsArray, function(unreadClass, readClass) { + if ($this.hasClass(unreadClass)) { + $this.removeClass(unreadClass).addClass(readClass); + } + }); + $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle); + }); + + // Mark subforums read + $('a.subforum[class*="unread"]').removeClass('unread').addClass('read').children('.icon.icon-red').removeClass('icon-red').addClass('icon-blue'); + + // Mark topics read if we are watching a category and showing active topics + if ($('#active_topics').length) { + phpbb.ajaxCallbacks.mark_topics_read.call(this, res, false); + } + + // Update mark forums read links + $('[data-ajax="mark_forums_read"]').attr('href', res.U_MARK_FORUMS); + + phpbb.closeDarkenWrapper(3000); +}); + +/** +* This callback will mark all topic icons read +* +* @param {bool} [update_topic_links=true] Whether "Mark topics read" links +* should be updated. Defaults to true. +*/ +phpbb.addAjaxCallback('mark_topics_read', function(res, updateTopicLinks) { + var readTitle = res.NO_UNREAD_POSTS; + var unreadTitle = res.UNREAD_POSTS; + var iconsArray = { + global_unread: 'global_read', + announce_unread: 'announce_read', + sticky_unread: 'sticky_read', + topic_unread: 'topic_read' + }; + var iconsState = ['', '_hot', '_hot_mine', '_locked', '_locked_mine', '_mine']; + var unreadClassSelectors; + var classMap = {}; + var classNames = []; + + if (typeof updateTopicLinks === 'undefined') { + updateTopicLinks = true; + } + + $.each(iconsArray, function(unreadClass, readClass) { + $.each(iconsState, function(key, value) { + // Only topics can be hot + if ((value === '_hot' || value === '_hot_mine') && unreadClass !== 'topic_unread') { + return true; + } + classMap[unreadClass + value] = readClass + value; + classNames.push(unreadClass + value); + }); + }); + + unreadClassSelectors = '.' + classNames.join(',.'); + + $('li.row').find(unreadClassSelectors).each(function() { + var $this = $(this); + $.each(classMap, function(unreadClass, readClass) { + if ($this.hasClass(unreadClass)) { + $this.removeClass(unreadClass).addClass(readClass); + } + }); + $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle); + }); + + // Remove link to first unread post + $('a.unread').has('.icon-red').remove(); + + // Update mark topics read links + if (updateTopicLinks) { + $('[data-ajax="mark_topics_read"]').attr('href', res.U_MARK_TOPICS); + } + + phpbb.closeDarkenWrapper(3000); +}); + +// This callback will mark all notifications read +phpbb.addAjaxCallback('notification.mark_all_read', function(res) { + if (typeof res.success !== 'undefined') { + phpbb.markNotifications($('#notification_list li.bg2'), 0); + phpbb.closeDarkenWrapper(3000); + } +}); + +// This callback will mark a notification read +phpbb.addAjaxCallback('notification.mark_read', function(res) { + if (typeof res.success !== 'undefined') { + var unreadCount = Number($('#notification_list_button strong').html()) - 1; + phpbb.markNotifications($(this).parent('li.bg2'), unreadCount); + } +}); + +/** + * Mark notification popup rows as read. + * + * @param {jQuery} $popup jQuery object(s) to mark read. + * @param {int} unreadCount The new unread notifications count. + */ +phpbb.markNotifications = function($popup, unreadCount) { + // Remove the unread status. + $popup.removeClass('bg2'); + $popup.find('a.mark_read').remove(); + + // Update the notification link to the real URL. + $popup.each(function() { + var link = $(this).find('a'); + link.attr('href', link.attr('data-real-url')); + }); + + // Update the unread count. + $('strong', '#notification_list_button').html(unreadCount); + // Remove the Mark all read link and hide notification count if there are no unread notifications. + if (!unreadCount) { + $('#mark_all_notifications').remove(); + $('#notification_list_button > strong').addClass('hidden'); + } + + // Update page title + var $title = $('title'); + var originalTitle = $title.text().replace(/(\((\d+)\))/, ''); + $title.text((unreadCount ? '(' + unreadCount + ')' : '') + originalTitle); +}; + +// This callback finds the post from the delete link, and removes it. +phpbb.addAjaxCallback('post_delete', function() { + var $this = $(this), + postId; + + if ($this.attr('data-refresh') === undefined) { + postId = $this[0].href.split('&p=')[1]; + var post = $this.parents('#p' + postId).css('pointer-events', 'none'); + if (post.hasClass('bg1') || post.hasClass('bg2')) { + var posts1 = post.nextAll('.bg1'); + post.nextAll('.bg2').removeClass('bg2').addClass('bg1'); + posts1.removeClass('bg1').addClass('bg2'); + } + post.fadeOut(function() { + $(this).remove(); + }); + } +}); + +// This callback removes the approve / disapprove div or link. +phpbb.addAjaxCallback('post_visibility', function(res) { + var remove = (res.visible) ? $(this) : $(this).parents('.post'); + $(remove).css('pointer-events', 'none').fadeOut(function() { + $(this).remove(); + }); + + if (res.visible) { + // Remove the "Deleted by" message from the post on restoring. + remove.parents('.post').find('.post_deleted_msg').css('pointer-events', 'none').fadeOut(function() { + $(this).remove(); + }); + } +}); + +// This removes the parent row of the link or form that fired the callback. +phpbb.addAjaxCallback('row_delete', function() { + $(this).parents('tr').remove(); +}); + +// This handles friend / foe additions removals. +phpbb.addAjaxCallback('zebra', function(res) { + var zebra; + + if (res.success) { + zebra = $('.zebra'); + zebra.first().html(res.MESSAGE_TEXT); + zebra.not(':first').html(' ').prev().html(' '); + } +}); + +/** + * This callback updates the poll results after voting. + */ +phpbb.addAjaxCallback('vote_poll', function(res) { + if (typeof res.success !== 'undefined') { + var poll = $(this).closest('.topic_poll'); + var panel = poll.find('.panel'); + var resultsVisible = poll.find('dl:first-child .resultbar').is(':visible'); + var mostVotes = 0; + + // Set min-height to prevent the page from jumping when the content changes + var updatePanelHeight = function (height) { + height = (typeof height === 'undefined') ? panel.find('.inner').outerHeight() : height; + panel.css('min-height', height); + }; + updatePanelHeight(); + + // Remove the View results link + if (!resultsVisible) { + poll.find('.poll_view_results').hide(500); + } + + if (!res.can_vote) { + poll.find('.polls, .poll_max_votes, .poll_vote, .poll_option_select').fadeOut(500, function () { + poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(); + }); + } else { + // If the user can still vote, simply slide down the results + poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(500); + } + + // Get the votes count of the highest poll option + poll.find('[data-poll-option-id]').each(function() { + var option = $(this); + var optionId = option.attr('data-poll-option-id'); + mostVotes = (res.vote_counts[optionId] >= mostVotes) ? res.vote_counts[optionId] : mostVotes; + }); + + // Update the total votes count + poll.find('.poll_total_vote_cnt').html(res.total_votes); + + // Update each option + poll.find('[data-poll-option-id]').each(function() { + var $this = $(this); + var optionId = $this.attr('data-poll-option-id'); + var voted = (typeof res.user_votes[optionId] !== 'undefined'); + var mostVoted = (res.vote_counts[optionId] === mostVotes); + var percent = (!res.total_votes) ? 0 : Math.round((res.vote_counts[optionId] / res.total_votes) * 100); + var percentRel = (mostVotes === 0) ? 0 : Math.round((res.vote_counts[optionId] / mostVotes) * 100); + var altText; + + altText = $this.attr('data-alt-text'); + if (voted) { + $this.attr('title', $.trim(altText)); + } else { + $this.attr('title', ''); + }; + $this.toggleClass('voted', voted); + $this.toggleClass('most-votes', mostVoted); + + // Update the bars + var bar = $this.find('.resultbar div'); + var barTimeLapse = (res.can_vote) ? 500 : 1500; + var newBarClass = (percent === 100) ? 'pollbar5' : 'pollbar' + (Math.floor(percent / 20) + 1); + + setTimeout(function () { + bar.animate({ width: percentRel + '%' }, 500) + .removeClass('pollbar1 pollbar2 pollbar3 pollbar4 pollbar5') + .addClass(newBarClass) + .html(res.vote_counts[optionId]); + + var percentText = percent ? percent + '%' : res.NO_VOTES; + $this.find('.poll_option_percent').html(percentText); + }, barTimeLapse); + }); + + if (!res.can_vote) { + poll.find('.polls').delay(400).fadeIn(500); + } + + // Display "Your vote has been cast." message. Disappears after 5 seconds. + var confirmationDelay = (res.can_vote) ? 300 : 900; + poll.find('.vote-submitted').delay(confirmationDelay).slideDown(200, function() { + if (resultsVisible) { + updatePanelHeight(); + } + + $(this).delay(5000).fadeOut(500, function() { + resizePanel(300); + }); + }); + + // Remove the gap resulting from removing options + setTimeout(function() { + resizePanel(500); + }, 1500); + + var resizePanel = function (time) { + var panelHeight = panel.height(); + var innerHeight = panel.find('.inner').outerHeight(); + + if (panelHeight !== innerHeight) { + panel.css({ minHeight: '', height: panelHeight }) + .animate({ height: innerHeight }, time, function () { + panel.css({ minHeight: innerHeight, height: '' }); + }); + } + }; + } +}); + +/** + * Show poll results when clicking View results link. + */ +$('.poll_view_results a').click(function(e) { + // Do not follow the link + e.preventDefault(); + + var $poll = $(this).parents('.topic_poll'); + + $poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(500); + $poll.find('.poll_view_results').hide(500); +}); + +$('[data-ajax]').each(function() { + var $this = $(this); + var ajax = $this.attr('data-ajax'); + var filter = $this.attr('data-filter'); + + if (ajax !== 'false') { + var fn = (ajax !== 'true') ? ajax : null; + filter = (filter !== undefined) ? phpbb.getFunctionByName(filter) : null; + + phpbb.ajaxify({ + selector: this, + refresh: $this.attr('data-refresh') !== undefined, + filter: filter, + callback: fn + }); + } +}); + + +/** + * This simply appends #preview to the action of the + * QR action when you click the Full Editor & Preview button + */ +$('#qr_full_editor').click(function() { + $('#qr_postform').attr('action', function(i, val) { + return val + '#preview'; + }); +}); + + +/** + * Make the display post links to use JS + */ +$('.display_post').click(function(e) { + // Do not follow the link + e.preventDefault(); + + var postId = $(this).attr('data-post-id'); + $('#post_content' + postId).show(); + $('#profile' + postId).show(); + $('#post_hidden' + postId).hide(); +}); + +/** +* Toggle the member search panel in memberlist.php. +* +* If user returns to search page after viewing results the search panel is automatically displayed. +* In any case the link will toggle the display status of the search panel and link text will be +* appropriately changed based on the status of the search panel. +*/ +$('#member_search').click(function () { + var $memberlistSearch = $('#memberlist_search'); + + $memberlistSearch.slideToggle('fast'); + phpbb.ajaxCallbacks.alt_text.call(this); + + // Focus on the username textbox if it's available and displayed + if ($memberlistSearch.is(':visible')) { + $('#username').focus(); + } + return false; +}); + +/** +* Automatically resize textarea +*/ +$(function() { + var $textarea = $('textarea:not(#message-box textarea, .no-auto-resize)'); + phpbb.resizeTextArea($textarea, { minHeight: 75, maxHeight: 250 }); + phpbb.resizeTextArea($('textarea', '#message-box')); +}); + + +})(jQuery); // Avoid conflicts with other libraries diff --git a/styles/prosilver/template/attachment.html b/styles/prosilver/template/attachment.html new file mode 100644 index 0000000..0978d91 --- /dev/null +++ b/styles/prosilver/template/attachment.html @@ -0,0 +1,48 @@ + + + + +

[{_file.DENIED_MESSAGE}]

+ + + + +
+
{_file.DOWNLOAD_NAME}
+
{_file.COMMENT}
+
+ + + +
+
{_file.DOWNLOAD_NAME}
+
{_file.COMMENT}
+
{_file.DOWNLOAD_NAME} ({_file.FILESIZE} {_file.SIZE_LANG}) {_file.L_DOWNLOAD_COUNT}
+
+ + + +
+
{_file.UPLOAD_ICON} {_file.DOWNLOAD_NAME}
+
{_file.COMMENT}
+
({_file.FILESIZE} {_file.SIZE_LANG}) {_file.L_DOWNLOAD_COUNT}
+
+ + + + + + + + + + + + +

{_file.DOWNLOAD_NAME} [ {_file.FILESIZE} {_file.SIZE_LANG} | {_file.L_DOWNLOAD_COUNT} ]

+ + + + + + diff --git a/styles/prosilver/template/bbcode.html b/styles/prosilver/template/bbcode.html new file mode 100644 index 0000000..940c0ac --- /dev/null +++ b/styles/prosilver/template/bbcode.html @@ -0,0 +1,75 @@ +
    +
      +
    + +
      +
    + +
  • +
  • + +
    {USERNAME} {L_WROTE}{L_COLON} +
    +
    + +
    + + uncited + +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    +
    + + +

    {L_CODE}{L_COLON} {L_SELECT_ALL_CODE}

    
    +
    + +
    +
    + + + + + + + + + + +{TEXT} + +{TEXT} + +{L_IMAGE} + +{DESCRIPTION} + +{DESCRIPTION} + + diff --git a/styles/prosilver/template/captcha_default.html b/styles/prosilver/template/captcha_default.html new file mode 100644 index 0000000..02899bc --- /dev/null +++ b/styles/prosilver/template/captcha_default.html @@ -0,0 +1,24 @@ + +
    +
    + +

    {L_CONFIRMATION}

    +

    {L_CONFIRM_EXPLAIN}

    + +
    + + +
    +
    +
    {L_CONFIRM_CODE}
    +
    + +
    +
    {L_CONFIRM_CODE_EXPLAIN}
    +
    + + +
    +
    +
    + diff --git a/styles/prosilver/template/captcha_qa.html b/styles/prosilver/template/captcha_qa.html new file mode 100644 index 0000000..b8c6678 --- /dev/null +++ b/styles/prosilver/template/captcha_qa.html @@ -0,0 +1,21 @@ + +
    +
    + +

    {L_CONFIRMATION}

    +
    + + +
    +

    {L_CONFIRM_QUESTION_EXPLAIN}
    +
    + + +
    +
    + + +
    +
    +
    + diff --git a/styles/prosilver/template/captcha_recaptcha.html b/styles/prosilver/template/captcha_recaptcha.html new file mode 100644 index 0000000..a123f54 --- /dev/null +++ b/styles/prosilver/template/captcha_recaptcha.html @@ -0,0 +1,30 @@ + +
    +
    + +

    {L_CONFIRMATION}

    +

    {L_CONFIRM_EXPLAIN}

    + +
    + + + +
    +

    {L_RECAPTCHA_EXPLAIN}
    +
    + + +
    +
    +
    + +{L_RECAPTCHA_NOT_AVAILABLE} + + + +
    +
    +
    + diff --git a/styles/prosilver/template/confirm_body.html b/styles/prosilver/template/confirm_body.html new file mode 100644 index 0000000..aaea5cf --- /dev/null +++ b/styles/prosilver/template/confirm_body.html @@ -0,0 +1,35 @@ + +
    +

    {MESSAGE_TITLE}

    +

    {MESSAGE_TEXT}

    + +
    +   + +
    +
    + + + + + +
    +
    +
    + +

    {MESSAGE_TITLE}

    +

    {MESSAGE_TEXT}

    + +
    + {S_HIDDEN_FIELDS} +   + +
    + +
    +
    +
    + + + + diff --git a/styles/prosilver/template/confirm_delete_body.html b/styles/prosilver/template/confirm_delete_body.html new file mode 100644 index 0000000..637830a --- /dev/null +++ b/styles/prosilver/template/confirm_delete_body.html @@ -0,0 +1,72 @@ + +
    +

    {MESSAGE_TEXT}

    + + + + + + + + + +
    +   + +
    +
    + + + + + +
    +
    +
    + +

    {MESSAGE_TITLE}

    + +

    {MESSAGE_TEXT}

    + + +
    + +
    +
    +
    + +
    +
    + + + {% EVENT confirm_delete_body_delete_reason_before %} + +
    +

    {L_DELETE_REASON_EXPLAIN}
    +
    +
    +
    + + +
    + {S_HIDDEN_FIELDS} +   + +
    + +
    +
    +
    + + + diff --git a/styles/prosilver/template/display_options.html b/styles/prosilver/template/display_options.html new file mode 100644 index 0000000..a426d08 --- /dev/null +++ b/styles/prosilver/template/display_options.html @@ -0,0 +1,27 @@ + diff --git a/styles/prosilver/template/drafts.html b/styles/prosilver/template/drafts.html new file mode 100644 index 0000000..ea2849a --- /dev/null +++ b/styles/prosilver/template/drafts.html @@ -0,0 +1,49 @@ + + + +
    +
    + +

    {L_LOAD_DRAFT}

    +

    {L_LOAD_DRAFT_EXPLAIN}

    + +
    +
    + +
    +
    + +
      +
    • +
      +
      {L_LOAD_DRAFT}
      +
      {L_SAVE_DATE}
      +
      +
    • +
    + + +
    +
    + diff --git a/styles/prosilver/template/faq_body.html b/styles/prosilver/template/faq_body.html new file mode 100644 index 0000000..e55c12a --- /dev/null +++ b/styles/prosilver/template/faq_body.html @@ -0,0 +1,50 @@ + + +

    {L_FAQ_TITLE}

    + + + + + +
    +
    + +
    +

    {faq_block.BLOCK_TITLE}

    + +
    +
    {faq_block.faq_row.FAQ_QUESTION}
    +
    {faq_block.faq_row.FAQ_ANSWER}
    +
    + + {L_BACK_TO_TOP} + +
    + +
    + +
    +
    + + + + diff --git a/styles/prosilver/template/forum_fn.js b/styles/prosilver/template/forum_fn.js new file mode 100644 index 0000000..3f28f8a --- /dev/null +++ b/styles/prosilver/template/forum_fn.js @@ -0,0 +1,937 @@ +/* global phpbb */ + +/** +* phpBB3 forum functions +*/ + +/** +* Find a member +*/ +function find_username(url) { + 'use strict'; + + popup(url, 760, 570, '_usersearch'); + return false; +} + +/** +* Window popup +*/ +function popup(url, width, height, name) { + 'use strict'; + + if (!name) { + name = '_popup'; + } + + window.open(url.replace(/&/g, '&'), name, 'height=' + height + ',resizable=yes,scrollbars=yes, width=' + width); + return false; +} + +/** +* Jump to page +*/ +function pageJump(item) { + 'use strict'; + + var page = parseInt(item.val(), 10), + perPage = item.attr('data-per-page'), + baseUrl = item.attr('data-base-url'), + startName = item.attr('data-start-name'); + + if (page !== null && !isNaN(page) && page === Math.floor(page) && page > 0) { + if (baseUrl.indexOf('?') === -1) { + document.location.href = baseUrl + '?' + startName + '=' + ((page - 1) * perPage); + } else { + document.location.href = baseUrl.replace(/&/g, '&') + '&' + startName + '=' + ((page - 1) * perPage); + } + } +} + +/** +* Mark/unmark checklist +* id = ID of parent container, name = name prefix, state = state [true/false] +*/ +function marklist(id, name, state) { + 'use strict'; + + jQuery('#' + id + ' input[type=checkbox][name]').each(function() { + var $this = jQuery(this); + if ($this.attr('name').substr(0, name.length) === name && !$this.prop('disabled')) { + $this.prop('checked', state); + } + }); +} + +/** +* Resize viewable area for attached image or topic review panel (possibly others to come) +* e = element +*/ +function viewableArea(e, itself) { + 'use strict'; + + if (!e) { + return; + } + + if (!itself) { + e = e.parentNode; + } + + if (!e.vaHeight) { + // Store viewable area height before changing style to auto + e.vaHeight = e.offsetHeight; + e.vaMaxHeight = e.style.maxHeight; + e.style.height = 'auto'; + e.style.maxHeight = 'none'; + e.style.overflow = 'visible'; + } else { + // Restore viewable area height to the default + e.style.height = e.vaHeight + 'px'; + e.style.overflow = 'auto'; + e.style.maxHeight = e.vaMaxHeight; + e.vaHeight = false; + } +} + +/** +* Alternate display of subPanels +*/ +jQuery(function($) { + 'use strict'; + + $('.sub-panels').each(function() { + + var $childNodes = $('a[data-subpanel]', this), + panels = $childNodes.map(function () { + return this.getAttribute('data-subpanel'); + }), + showPanel = this.getAttribute('data-show-panel'); + + if (panels.length) { + activateSubPanel(showPanel, panels); + $childNodes.click(function () { + activateSubPanel(this.getAttribute('data-subpanel'), panels); + return false; + }); + } + }); +}); + +/** +* Activate specific subPanel +*/ +function activateSubPanel(p, panels) { + 'use strict'; + + var i, showPanel; + + if (typeof p === 'string') { + showPanel = p; + } + $('input[name="show_panel"]').val(showPanel); + + if (typeof panels === 'undefined') { + panels = jQuery('.sub-panels a[data-subpanel]').map(function() { + return this.getAttribute('data-subpanel'); + }); + } + + for (i = 0; i < panels.length; i++) { + jQuery('#' + panels[i]).css('display', panels[i] === showPanel ? 'block' : 'none'); + jQuery('#' + panels[i] + '-tab').toggleClass('activetab', panels[i] === showPanel); + } +} + +function selectCode(a) { + 'use strict'; + + // Get ID of code block + var e = a.parentNode.parentNode.getElementsByTagName('CODE')[0]; + var s, r; + + // Not IE and IE9+ + if (window.getSelection) { + s = window.getSelection(); + // Safari and Chrome + if (s.setBaseAndExtent) { + var l = (e.innerText.length > 1) ? e.innerText.length - 1 : 1; + try { + s.setBaseAndExtent(e, 0, e, l); + } catch (error) { + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + } + // Firefox and Opera + else { + // workaround for bug # 42885 + if (window.opera && e.innerHTML.substring(e.innerHTML.length - 4) === '
    ') { + e.innerHTML = e.innerHTML + ' '; + } + + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + } + // Some older browsers + else if (document.getSelection) { + s = document.getSelection(); + r = document.createRange(); + r.selectNodeContents(e); + s.removeAllRanges(); + s.addRange(r); + } + // IE + else if (document.selection) { + r = document.body.createTextRange(); + r.moveToElementText(e); + r.select(); + } +} + +var inAutocomplete = false; +var lastKeyEntered = ''; + +/** +* Check event key +*/ +function phpbbCheckKey(event) { + 'use strict'; + + // Keycode is array down or up? + if (event.keyCode && (event.keyCode === 40 || event.keyCode === 38)) { + inAutocomplete = true; + } + + // Make sure we are not within an "autocompletion" field + if (inAutocomplete) { + // If return pressed and key changed we reset the autocompletion + if (!lastKeyEntered || lastKeyEntered === event.which) { + inAutocomplete = false; + return true; + } + } + + // Keycode is not return, then return. ;) + if (event.which !== 13) { + lastKeyEntered = event.which; + return true; + } + + return false; +} + +/** +* Apply onkeypress event for forcing default submit button on ENTER key press +*/ +jQuery(function($) { + 'use strict'; + + $('form input[type=text], form input[type=password]').on('keypress', function (e) { + var defaultButton = $(this).parents('form').find('input[type=submit].default-submit-action'); + + if (!defaultButton || defaultButton.length <= 0) { + return true; + } + + if (phpbbCheckKey(e)) { + return true; + } + + if ((e.which && e.which === 13) || (e.keyCode && e.keyCode === 13)) { + defaultButton.click(); + return false; + } + + return true; + }); +}); + +/** +* Functions for user search popup +*/ +function insertUser(formId, value) { + 'use strict'; + + var $form = jQuery(formId), + formName = $form.attr('data-form-name'), + fieldName = $form.attr('data-field-name'), + item = opener.document.forms[formName][fieldName]; + + if (item.value.length && item.type === 'textarea') { + value = item.value + '\n' + value; + } + + item.value = value; +} + +function insert_marked_users(formId, users) { + 'use strict'; + + $(users).filter(':checked').each(function() { + insertUser(formId, this.value); + }); + + window.close(); +} + +function insert_single_user(formId, user) { + 'use strict'; + + insertUser(formId, user); + window.close(); +} + +/** +* Parse document block +*/ +function parseDocument($container) { + 'use strict'; + + var test = document.createElement('div'), + oldBrowser = (typeof test.style.borderRadius === 'undefined'), + $body = $('body'); + + /** + * Reset avatar dimensions when changing URL or EMAIL + */ + $container.find('input[data-reset-on-edit]').on('keyup', function() { + $(this.getAttribute('data-reset-on-edit')).val(''); + }); + + /** + * Pagination + */ + $container.find('.pagination .page-jump-form :button').click(function() { + var $input = $(this).siblings('input.inputbox'); + pageJump($input); + }); + + $container.find('.pagination .page-jump-form input.inputbox').on('keypress', function(event) { + if (event.which === 13 || event.keyCode === 13) { + event.preventDefault(); + pageJump($(this)); + } + }); + + $container.find('.pagination .dropdown-trigger').click(function() { + var $dropdownContainer = $(this).parent(); + // Wait a little bit to make sure the dropdown has activated + setTimeout(function() { + if ($dropdownContainer.hasClass('dropdown-visible')) { + $dropdownContainer.find('input.inputbox').focus(); + } + }, 100); + }); + + /** + * Adjust HTML code for IE8 and older versions + */ + // if (oldBrowser) { + // // Fix .linklist.bulletin lists + // $container + // .find('ul.linklist.bulletin > li') + // .filter(':first-child, .rightside:last-child') + // .addClass('no-bulletin'); + // } + + /** + * Resize navigation (breadcrumbs) block to keep all links on same line + */ + $container.find('.navlinks').each(function() { + var $this = $(this), + $left = $this.children().not('.rightside'), + $right = $this.children('.rightside'); + + if ($left.length !== 1 || !$right.length) { + return; + } + + function resize() { + var width = 0, + diff = $left.outerWidth(true) - $left.width(), + minWidth = Math.max($this.width() / 3, 240), + maxWidth; + + $right.each(function() { + var $this = $(this); + if ($this.is(':visible')) { + width += $this.outerWidth(true); + } + }); + + maxWidth = $this.width() - width - diff; + $left.css('max-width', Math.floor(Math.max(maxWidth, minWidth)) + 'px'); + } + + resize(); + $(window).resize(resize); + }); + + /** + * Makes breadcrumbs responsive + */ + $container.find('.breadcrumbs:not([data-skip-responsive])').each(function() { + var $this = $(this), + $links = $this.find('.crumb'), + length = $links.length, + classes = ['wrapped-max', 'wrapped-wide', 'wrapped-medium', 'wrapped-small', 'wrapped-tiny'], + classesLength = classes.length, + maxHeight = 0, + lastWidth = false, + wrapped = false; + + // Set tooltips + $this.find('a').each(function() { + var $link = $(this); + $link.attr('title', $link.text()); + }); + + // Function that checks breadcrumbs + function check() { + var height = $this.height(), + width; + + // Test max-width set in code for .navlinks above + width = parseInt($this.css('max-width'), 10); + if (!width) { + width = $body.width(); + } + + maxHeight = parseInt($this.css('line-height'), 10); + $links.each(function() { + if ($(this).height() > 0) { + maxHeight = Math.max(maxHeight, $(this).outerHeight(true)); + } + }); + + if (height <= maxHeight) { + if (!wrapped || lastWidth === false || lastWidth >= width) { + return; + } + } + lastWidth = width; + + if (wrapped) { + $this.removeClass('wrapped').find('.crumb.wrapped').removeClass('wrapped ' + classes.join(' ')); + if ($this.height() <= maxHeight) { + return; + } + } + + wrapped = true; + $this.addClass('wrapped'); + if ($this.height() <= maxHeight) { + return; + } + + for (var i = 0; i < classesLength; i++) { + for (var j = length - 1; j >= 0; j--) { + $links.eq(j).addClass('wrapped ' + classes[i]); + if ($this.height() <= maxHeight) { + return; + } + } + } + } + + // Run function and set event + check(); + $(window).resize(check); + }); + + /** + * Responsive link lists + */ + var selector = '.linklist:not(.navlinks, [data-skip-responsive]),' + + '.postbody .post-buttons:not([data-skip-responsive])'; + $container.find(selector).each(function() { + var $this = $(this), + filterSkip = '.breadcrumbs, [data-skip-responsive]', + filterLast = '.edit-icon, .quote-icon, [data-last-responsive]', + $linksAll = $this.children(), + $linksNotSkip = $linksAll.not(filterSkip), // All items that can potentially be hidden + $linksFirst = $linksNotSkip.not(filterLast), // The items that will be hidden first + $linksLast = $linksNotSkip.filter(filterLast), // The items that will be hidden last + persistent = $this.attr('id') === 'nav-main', // Does this list already have a menu (such as quick-links)? + html = '', + slack = 3; // Vertical slack space (in pixels). Determines how sensitive the script is in determining whether a line-break has occured. + + // Add a hidden drop-down menu to each links list (except those that already have one) + if (!persistent) { + if ($linksNotSkip.is('.rightside')) { + $linksNotSkip.filter('.rightside:first').before(html); + $this.children('.responsive-menu').addClass('rightside'); + } else { + $this.append(html); + } + } + + // Set some object references and initial states + var $menu = $this.children('.responsive-menu'), + $menuContents = $menu.find('.dropdown-contents'), + persistentContent = $menuContents.find('li:not(.separator)').length, + lastWidth = false, + compact = false, + responsive1 = false, + responsive2 = false, + copied1 = false, + copied2 = false, + maxHeight = 0; + + // Find the tallest element in the list (we assume that all elements are roughly the same height) + $linksAll.each(function() { + if (!$(this).height()) { + return; + } + maxHeight = Math.max(maxHeight, $(this).outerHeight(true)); + }); + if (maxHeight < 1) { + return; // Shouldn't be possible, but just in case, abort + } else { + maxHeight = maxHeight + slack; + } + + function check() { + var width = $body.width(); + // We can't make it any smaller than this, so just skip + if (responsive2 && compact && (width <= lastWidth)) { + return; + } + lastWidth = width; + + // Reset responsive and compact layout + if (responsive1 || responsive2) { + $linksNotSkip.removeClass('hidden'); + $menuContents.children('.clone').addClass('hidden'); + responsive1 = responsive2 = false; + } + if (compact) { + $this.removeClass('compact'); + compact = false; + } + + // Unhide the quick-links menu if it has "persistent" content + if (persistent && persistentContent) { + $menu.removeClass('hidden'); + } else { + $menu.addClass('hidden'); + } + + // Nothing to resize if block's height is not bigger than tallest element's height + if ($this.height() <= maxHeight) { + return; + } + + // STEP 1: Compact + if (!compact) { + $this.addClass('compact'); + compact = true; + } + if ($this.height() <= maxHeight) { + return; + } + + // STEP 2: First responsive set - compact + if (compact) { + $this.removeClass('compact'); + compact = false; + } + // Copy the list items to the dropdown + if (!copied1) { + var $clones1 = $linksFirst.clone(); + $menuContents.prepend($clones1.addClass('clone clone-first').removeClass('leftside rightside')); + + if ($this.hasClass('post-buttons')) { + $('.button', $menuContents).removeClass('button'); + $('.sr-only', $menuContents).removeClass('sr-only'); + $('.js-responsive-menu-link').addClass('button').addClass('button-icon-only'); + $('.js-responsive-menu-link .icon').removeClass('fa-bars').addClass('fa-ellipsis-h'); + } + copied1 = true; + } + if (!responsive1) { + $linksFirst.addClass('hidden'); + responsive1 = true; + $menuContents.children('.clone-first').removeClass('hidden'); + $menu.removeClass('hidden'); + } + if ($this.height() <= maxHeight) { + return; + } + + // STEP 3: First responsive set + compact + if (!compact) { + $this.addClass('compact'); + compact = true; + } + if ($this.height() <= maxHeight) { + return; + } + + // STEP 4: Last responsive set - compact + if (!$linksLast.length) { + return; // No other links to hide, can't do more + } + if (compact) { + $this.removeClass('compact'); + compact = false; + } + // Copy the list items to the dropdown + if (!copied2) { + var $clones2 = $linksLast.clone(); + $menuContents.prepend($clones2.addClass('clone clone-last').removeClass('leftside rightside')); + copied2 = true; + } + if (!responsive2) { + $linksLast.addClass('hidden'); + responsive2 = true; + $menuContents.children('.clone-last').removeClass('hidden'); + } + if ($this.height() <= maxHeight) { + return; + } + + // STEP 5: Last responsive set + compact + if (!compact) { + $this.addClass('compact'); + compact = true; + } + } + + if (!persistent) { + phpbb.registerDropdown($menu.find('a.js-responsive-menu-link'), $menu.find('.dropdown'), false); + } + + // If there are any images in the links list, run the check again after they have loaded + $linksAll.find('img').each(function() { + $(this).on('load', function() { + check(); + }); + }); + + check(); + $(window).resize(check); + }); + + /** + * Do not run functions below for old browsers + */ + if (oldBrowser) { + return; + } + + /** + * Adjust topiclist lists with check boxes + */ + $container.find('ul.topiclist dd.mark').siblings('dt').children('.list-inner').addClass('with-mark'); + + /** + * Appends contents of all extra columns to first column in + * .topiclist lists for mobile devices. Copies contents as is. + * + * To add that functionality to .topiclist list simply add + * responsive-show-all to list of classes + */ + $container.find('.topiclist.responsive-show-all > li > dl').each(function() { + var $this = $(this), + $block = $this.find('dt .responsive-show:last-child'), + first = true; + + // Create block that is visible only on mobile devices + if (!$block.length) { + $this.find('dt > .list-inner').append('
+ +
+
+ + + + +
+
+ +
    + + + + + +
  • + +
    +
    + +
    + + + + + + {forumrow.FORUM_IMAGE} + + + {forumrow.FORUM_NAME} +
    {forumrow.FORUM_DESC} + +
    {forumrow.L_MODERATOR_STR}{L_COLON} {forumrow.MODERATORS} + + + +
    {forumrow.L_SUBFORUM_STR}{L_COLON} + + + {forumrow.subforum.SUBFORUM_NAME}{L_COMMA_SEPARATOR} + + + + + + + +
    +
    + +
    {L_REDIRECTS}{L_COLON} {forumrow.CLICKS}
    + +
    {forumrow.TOPICS} {L_TOPICS}
    +
    {forumrow.POSTS} {L_POSTS}
    +
    + + + + {L_TOPICS_UNAPPROVED} + + + + {L_POSTS_UNAPPROVED_FORUM} + + + + {L_LAST_POST} + + + {forumrow.LAST_POST_SUBJECT_TRUNCATED}
    + + {L_POST_BY_AUTHOR} {forumrow.LAST_POSTER_FULL} + + + {L_VIEW_LATEST_POST} + + +
    {forumrow.LAST_POST_TIME} + + {% if forumrow.U_UNAPPROVED_TOPICS %} + {{ lang('TOPIC_UNAPPROVED_FORUM', forumrow.TOPICS) }} + {% else %} + {{ lang('NO_POSTS') }} + {% endif %} + +
    +
    + +
     
    + +
    + +
  • + + + + +
+ +
+
+ + + + +
+
+ {L_NO_FORUMS} +
+
+ diff --git a/styles/prosilver/template/index.htm b/styles/prosilver/template/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/prosilver/template/index_body.html b/styles/prosilver/template/index_body.html new file mode 100644 index 0000000..239a91c --- /dev/null +++ b/styles/prosilver/template/index_body.html @@ -0,0 +1,80 @@ + + +

{LAST_VISIT_DATE}{CURRENT_TIME}

+

{CURRENT_TIME}

+ + + + + + + + + + + + +
+

{L_LOGIN_LOGOUT}  •  {L_REGISTER}

+ +
+ + + + + +
+

{L_WHO_IS_ONLINE}

{L_WHO_IS_ONLINE}

+

+ + {TOTAL_USERS_ONLINE} ({L_ONLINE_EXPLAIN})
{RECORD_USERS}
+ +
{LOGGED_IN_USER_LIST} +
{L_LEGEND}{L_COLON} {LEGEND} + + +

+
+ + + + + +
+

{L_BIRTHDAYS}

+

+ + {L_CONGRATULATIONS}{L_COLON} {birthdays.USERNAME} ({birthdays.AGE}), {L_NO_BIRTHDAYS} + +

+
+ + + +
+

{L_STATISTICS}

+

+ + {TOTAL_POSTS} • {TOTAL_TOPICS} • {TOTAL_USERS} • {NEWEST_USER} + +

+
+ + + + + diff --git a/styles/prosilver/template/jumpbox.html b/styles/prosilver/template/jumpbox.html new file mode 100644 index 0000000..070efc0 --- /dev/null +++ b/styles/prosilver/template/jumpbox.html @@ -0,0 +1,50 @@ + +
+ +

+ + {L_RETURN_TO_FORUM} + +

+ +

+ + {L_RETURN_TO_INDEX} + +

+ +

+ + {L_RETURN_TO_TOPIC} + +

+ +

+ + {L_GO_TO_SEARCH_ADV} + +

+ + + + + + +

+ +
diff --git a/styles/prosilver/template/login_body.html b/styles/prosilver/template/login_body.html new file mode 100644 index 0000000..dc597af --- /dev/null +++ b/styles/prosilver/template/login_body.html @@ -0,0 +1,69 @@ + + +
+
+
+ +
+ + +
class="fields1"class="fields2"> +
{LOGIN_ERROR}
+
+
+
+
+
+
+
+ +
{L_FORGOT_PASS}
+
{L_RESEND_ACTIVATION}
+ +
+ + + + + +
+
+
+
+ + + {S_LOGIN_REDIRECT} + {S_FORM_TOKEN_LOGIN} +
+
 
+
{S_HIDDEN_FIELDS}
+
+
+
+ + + + +
+
+ + + +
+
+ +
+

{L_REGISTER}

+

{L_LOGIN_INFO}

+

{L_TERMS_USE} | {L_PRIVACY}

+
+

{L_REGISTER}

+
+ +
+
+ + +
+ + diff --git a/styles/prosilver/template/login_body_oauth.html b/styles/prosilver/template/login_body_oauth.html new file mode 100644 index 0000000..1364d01 --- /dev/null +++ b/styles/prosilver/template/login_body_oauth.html @@ -0,0 +1,6 @@ +
+
+ {% for oauth in oauth %} + {{ oauth.SERVICE_NAME }} + {% endfor %} +
diff --git a/styles/prosilver/template/login_forum.html b/styles/prosilver/template/login_forum.html new file mode 100644 index 0000000..c5c36d4 --- /dev/null +++ b/styles/prosilver/template/login_forum.html @@ -0,0 +1,42 @@ + + +

{FORUM_NAME}

+ +
+{S_FORM_TOKEN} +
+
+ +
+ + +

{L_LOGIN_FORUM}

+ +
+ +
+
 
+
{LOGIN_ERROR}
+
+ + +
+
+
+
+ {S_LOGIN_REDIRECT} + {S_FORM_TOKEN_LOGIN} +
+
 
+
{S_HIDDEN_FIELDS}
+
+
+
+ +
+
+ +
+ + + diff --git a/styles/prosilver/template/mcp_approve.html b/styles/prosilver/template/mcp_approve.html new file mode 100644 index 0000000..f7874ab --- /dev/null +++ b/styles/prosilver/template/mcp_approve.html @@ -0,0 +1,81 @@ + + +

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+ + + + + + + + + + + +
+   + +
+ + + + + +
+
+ {S_FORM_TOKEN} +
+ +
+ +

{MESSAGE_TITLE}

+

{ADDITIONAL_MSG}

+ +
+ +
+
 
+
+
+ + + +
+
+
+
+
+
+

{L_CAN_LEAVE_BLANK}
+
+
+ + +
+
 
+
{MESSAGE_TEXT}
+
+
+ +
+ {S_HIDDEN_FIELDS}  + +
+ +
+ +
+
+ +
+ + + diff --git a/styles/prosilver/template/mcp_ban.html b/styles/prosilver/template/mcp_ban.html new file mode 100644 index 0000000..86a3224 --- /dev/null +++ b/styles/prosilver/template/mcp_ban.html @@ -0,0 +1,135 @@ + + + + +
+ +

{L_TITLE}

+ +
+
+ +

{L_TITLE}

+

{L_EXPLAIN}

+ +
+ +
+
+
+
{L_FIND_USERNAME}
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+ +
+

{L_BAN_EXCLUDE_EXPLAIN}
+
+ + +
+
+ +
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+ +
+
+ +

{L_UNBAN_TITLE}

+

{L_UNBAN_EXPLAIN}

+ + +
+ +
+
+
+
+
+
{L_BAN_LENGTH}{L_COLON}
+
+
+
+
{L_BAN_REASON}{L_COLON}
+
+
+
+
{L_BAN_GIVE_REASON}{L_COLON}
+
+
+ +
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + +
+ + + +

{L_NO_BAN_CELL}

+ +
+
+ + + + + diff --git a/styles/prosilver/template/mcp_footer.html b/styles/prosilver/template/mcp_footer.html new file mode 100644 index 0000000..89ce7c3 --- /dev/null +++ b/styles/prosilver/template/mcp_footer.html @@ -0,0 +1,8 @@ + +
+ +
+
+
+ + diff --git a/styles/prosilver/template/mcp_forum.html b/styles/prosilver/template/mcp_forum.html new file mode 100644 index 0000000..f6c518e --- /dev/null +++ b/styles/prosilver/template/mcp_forum.html @@ -0,0 +1,153 @@ + + + + + +

{L_FORUM}{L_COLON} {FORUM_NAME}

+ +
+ +
+
+ +
+ +
+ + +
    +
  • +
    +
    {L_TOPICS}
    +
    {L_REPLIES}
    +
    {L_LAST_POST}
    +
    {L_MARK}
    +
    +
  • +
+ + +
    +
  • {L_NO_TOPICS}

  • +
+ + +
+ + + +
+ +
+
+ + +
+ + + + + + {S_FORM_TOKEN} +
+ +
+ + diff --git a/styles/prosilver/template/mcp_front.html b/styles/prosilver/template/mcp_front.html new file mode 100644 index 0000000..90793d0 --- /dev/null +++ b/styles/prosilver/template/mcp_front.html @@ -0,0 +1,193 @@ + + +

{PAGE_TITLE}

+ + + + + +
+ +
+
+ +

{L_LATEST_UNAPPROVED}

+

{L_UNAPPROVED_TOTAL}

+ + +
    +
  • +
    +
    {L_VIEW_DETAILS}
    +
    {L_TOPIC} & {L_FORUM}
    +
    +
  • +
+ + + +
+ {S_FORM_TOKEN} +
+ + +
+ {S_HIDDEN_FIELDS} +   + + +
+ +
+ + + + + +
+
+ +

{L_LATEST_REPORTED}

+

{L_REPORTS_TOTAL}

+ + +
    +
  • +
    +
    {L_VIEW_DETAILS}
    +
    {L_REPORTER} & {L_FORUM}
    +
    +
  • +
+
    + + +
  • +
    +
    +
    + {report.SUBJECT}
    + {L_POSTED} {L_POST_BY_AUTHOR} {report.AUTHOR_FULL} » {report.POST_TIME} +
    +
    +
    + {L_REPORTED} {L_POST_BY_AUTHOR} {report.REPORTER_FULL} {L_REPORTED_ON_DATE}
    + {L_FORUM}{L_COLON} {report.FORUM_NAME}
    +
    +
    +
  • + +
+ + +
+
+ + + + + +
+
+ +

{L_LATEST_REPORTED_PMS}

+

{L_PM_REPORTS_TOTAL}

+ + +
    +
  • +
    +
    {L_VIEW_DETAILS}
    +
    {L_REPORTER}
    +
    +
  • +
+
    + + +
  • +
    +
    +
    + {pm_report.PM_SUBJECT}
    + {L_MESSAGE_BY_AUTHOR} {pm_report.PM_AUTHOR_FULL} » {pm_report.PM_TIME}
    + {L_MESSAGE_TO} {pm_report.RECIPIENTS} +
    +
    +
    + {L_REPORTED} {L_POST_BY_AUTHOR} {pm_report.REPORTER_FULL} {L_REPORTED_ON_DATE} {pm_report.REPORT_TIME} +
    +
    +
  • + +
+ + +
+
+ + + + + +
+
+ +

{L_LATEST_LOGS}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_ACTION}{L_USERNAME}{L_IP}{L_VIEW_TOPIC}{L_VIEW_TOPIC_LOGS}{L_TIME}
{log.ACTION}{log.USERNAME}{log.IP}{L_VIEW_TOPIC} {L_VIEW_TOPIC_LOGS} {log.TIME}
{L_NO_ENTRIES}
+ +
+
+ + + + + diff --git a/styles/prosilver/template/mcp_header.html b/styles/prosilver/template/mcp_header.html new file mode 100644 index 0000000..5841c1b --- /dev/null +++ b/styles/prosilver/template/mcp_header.html @@ -0,0 +1,49 @@ + + +

{L_MCP}

+ + +

+ [ {L_ACP} | {L_MCP} | {L_MODERATE_FORUM} | {L_MODERATE_TOPIC} | {L_MODERATE_POST} ] +

+ + +
+ +
+ +
+
+ +
+ + + +
+ +
+

{L_MESSAGE}

+

{MESSAGE}

+

{return_links.MESSAGE_LINK}

+
+ diff --git a/styles/prosilver/template/mcp_logs.html b/styles/prosilver/template/mcp_logs.html new file mode 100644 index 0000000..03216b4 --- /dev/null +++ b/styles/prosilver/template/mcp_logs.html @@ -0,0 +1,88 @@ + + +

{L_TITLE}

+ +
+ +
+
+ +
+ {L_SEARCH_KEYWORDS}{L_COLON}   + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_IP}{L_TIME}{L_ACTION}{L_MARK}
{log.USERNAME}{log.IP}{log.DATE}{log.ACTION}
+ {log.DATA} +
{L_NO_ENTRIES}
+ + +
+ + + +
+ + {S_FORM_TOKEN} +
+
+ + +
+ +   + + +
+ + + {S_FORM_TOKEN} +
+
+ + + +
+ + diff --git a/styles/prosilver/template/mcp_message.html b/styles/prosilver/template/mcp_message.html new file mode 100644 index 0000000..062103b --- /dev/null +++ b/styles/prosilver/template/mcp_message.html @@ -0,0 +1,8 @@ + + +
+

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+
+ + diff --git a/styles/prosilver/template/mcp_move.html b/styles/prosilver/template/mcp_move.html new file mode 100644 index 0000000..45a9ae8 --- /dev/null +++ b/styles/prosilver/template/mcp_move.html @@ -0,0 +1,71 @@ + + +

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+ +

{ADDITIONAL_MSG}

+ + + + + + + + + + + +
+   + +
+ + + + + + + +
+ +
+
+ +
+

{MESSAGE_TITLE}

+

{ADDITIONAL_MSG}

+ +
+
+
+
+
+
+
+
+
 
+
{MESSAGE_TEXT}
+
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+ +
+ +
+
+
+ + + diff --git a/styles/prosilver/template/mcp_notes_front.html b/styles/prosilver/template/mcp_notes_front.html new file mode 100644 index 0000000..11f3623 --- /dev/null +++ b/styles/prosilver/template/mcp_notes_front.html @@ -0,0 +1,28 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +
+
+
+
+
{L_FIND_USERNAME}
+
+
+ +
+
+ +
+   + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/mcp_notes_user.html b/styles/prosilver/template/mcp_notes_user.html new file mode 100644 index 0000000..62d0562 --- /dev/null +++ b/styles/prosilver/template/mcp_notes_user.html @@ -0,0 +1,121 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +

{USERNAME_FULL}

+ +
+
+
{AVATAR_IMG}
+
+ +
+
+
{L_RANK}{L_COLON}
{RANK_TITLE}
+
 {L_RANK}{L_COLON}
{RANK_IMG}
+
{L_JOINED}{L_COLON}
{JOINED}
+
{L_TOTAL_POSTS}{L_COLON}
{POSTS}
+
{L_WARNINGS}{L_COLON}
{WARNINGS}
+
+
+
+ +
+
+ +
+
+ +

{L_ADD_FEEDBACK}

+

{L_ADD_FEEDBACK_EXPLAIN}

+ +
+ +
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+ +
+
+ +
+ {L_SEARCH_KEYWORDS}{L_COLON}   + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_REPORT_BY}{L_IP}{L_TIME}{L_ACTION_NOTE}{L_MARK}
{usernotes.REPORT_BY}{usernotes.IP}{usernotes.REPORT_AT}{usernotes.ACTION}
{L_NO_ENTRIES}
+ +
+ + + +
+ +
+
+ + +
+ +   +
+ +
+ +
+ +
+ + diff --git a/styles/prosilver/template/mcp_post.html b/styles/prosilver/template/mcp_post.html new file mode 100644 index 0000000..c2297a8 --- /dev/null +++ b/styles/prosilver/template/mcp_post.html @@ -0,0 +1,362 @@ + + + + +

{L_PM_REPORT_DETAILS}

+ +

{L_REPORT_DETAILS}

+ + +
+
+ +
+

{L_REPORT_REASON}{L_COLON} {REPORT_REASON_TITLE}

+

{L_REPORTED} {L_POST_BY_AUTHOR} {REPORTER_FULL} « {REPORT_DATE}

+ +

{L_REPORT_CLOSED}

+ +
+ + {REPORT_TEXT} + + {REPORT_REASON_DESCRIPTION} + +
+
+ +
+
+ +
+ +
+ {% EVENT mcp_post_report_buttons_top_before %} + +   + + + {% EVENT mcp_post_report_buttons_top_after %} + + {S_FORM_TOKEN} +
+
+ + +

{L_POST_DETAILS}

+ + +
+
+ +
+

{POST_SUBJECT}

+ + + + +

+ {L_SENT_AT}{L_COLON} {POST_DATE} +
{L_PM_FROM}{L_COLON} {POST_AUTHOR_FULL} +
{L_TO}{L_COLON} {to_recipient.NAME_FULL} style="color:{to_recipient.COLOUR};">{to_recipient.NAME}  +
{L_BCC}{L_COLON} {bcc_recipient.NAME_FULL} style="color:{bcc_recipient.COLOUR};">{bcc_recipient.NAME}  +

+ +

{MINI_POST_IMG} {L_POSTED} {L_POST_BY_AUTHOR} {POST_AUTHOR_FULL} » {POST_DATE}

+ + + +
+ +

+   + + + + {S_FORM_TOKEN} +

+
+ +
+ +

+   + + + + {S_FORM_TOKEN} +

+
+ + + +

+ {L_TOPIC_REPORTED} {L_MESSAGE_REPORTED} +

+ + + {% EVENT mcp_post_text_before %} + +
+ {POST_PREVIEW} +
+ + {% EVENT mcp_post_text_after %} + + +
+
{L_ATTACHMENTS}
+ +
{attachment.DISPLAY_ATTACHMENT}
+ +
+ + + +
+ {DELETED_MESSAGE} +
{L_REASON}{L_COLON} {DELETE_REASON} +
+ + + +
{SIGNATURE}
+ + + +
+
{L_THIS_PM_IP}{L_THIS_POST_IP}{L_COLON} + {POST_IPADDR}{POST_IP} ({POST_IP}{L_LOOKUP_IP}) + + {POST_IPADDR} ({POST_IP}){POST_IP} ({L_LOOKUP_IP}) +
+ + +
+ +
+
+ + +
+
+ +

{L_MOD_OPTIONS}

+ +
+ +
+
+
+
+
+ + +
+ [ {L_FIND_USERNAME} ] +
+
+ {S_FORM_TOKEN} +
+
+ + + + + +
+ +
+
+
+
+
+
+ {S_FORM_TOKEN} +
+
+ + +
+
+ + + + +
+
+ +

{RETURN_QUEUE} | {RETURN_TOPIC_SIMPLE} | {RETURN_POST}{RETURN_REPORTS} | {L_VIEW_POST} | {L_VIEW_TOPIC} | {L_VIEW_FORUM}{RETURN_TOPIC}

+ +
+
+ + + + + + +
+
+ +
+ + +

{L_FEEDBACK}

+ + + {L_REPORTED_BY}{L_COLON} {usernotes.REPORT_BY} « {usernotes.REPORT_AT} +
+
{usernotes.ACTION}
+ +
+ + + +
+   + +
+ + + +

{L_ADD_FEEDBACK}

+

{L_ADD_FEEDBACK_EXPLAIN}

+ +
+ +
+ +
+   + + {S_FORM_TOKEN} +
+
+ +
+
+ + + +
+
+ +

{L_MCP_POST_REPORTS}

+ + + {L_REPORTED_BY}{L_COLON} {reports.REPORTER}{reports.REPORTER} « {reports.REPORT_TIME} +

{reports.REASON_TITLE}{L_COLON} {reports.REASON_DESC}
{reports.REPORT_TEXT}

+ + +
+
+ + + +
+
+ +

{L_THIS_POST_IP}{L_COLON} + {POST_IPADDR}{POST_IP} ({POST_IP}{L_LOOKUP_IP}) + + {POST_IPADDR} ({POST_IP}){POST_IP} ({L_LOOKUP_IP}) +

+ + + + + + + + + + + + + + + + + + + + +
{L_OTHER_USERS}{L_POSTS}
{userrow.USERNAME}{userrow.USERNAME}{userrow.NUM_POSTS}
{L_NO_MATCHES_FOUND}
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + +
{L_IPS_POSTED_FROM}{L_POSTS}
{iprow.HOSTNAME} ({iprow.IP}){iprow.IP} ({L_LOOKUP_IP}){iprow.NUM_POSTS}
{L_NO_MATCHES_FOUND}
+ + + + + +
+
+ + + + + + + diff --git a/styles/prosilver/template/mcp_queue.html b/styles/prosilver/template/mcp_queue.html new file mode 100644 index 0000000..ee69bf4 --- /dev/null +++ b/styles/prosilver/template/mcp_queue.html @@ -0,0 +1,123 @@ + + +
+ +
+ + + {S_FORM_TOKEN} +
+ +

{L_TITLE}

+ +
+
+ +

{L_EXPLAIN}

+ + +
+ +
+ +
    +
  • +
    +
    {L_TOPIC}{L_POST}
    +
    {L_TOPIC} & {L_FORUM}
    +
    {L_MARK}
    +
    +
  • +
+ + +
+ + + + +
+ + +

+ + {L_NO_TOPICS_DELETED}{L_NO_POSTS_DELETED} + + {L_NO_TOPICS_QUEUE}{L_NO_POSTS_QUEUE} + +

+ + +
+
+ + +
+ +   + + +   + + + +
+ +
+ + diff --git a/styles/prosilver/template/mcp_reports.html b/styles/prosilver/template/mcp_reports.html new file mode 100644 index 0000000..e7e0a5c --- /dev/null +++ b/styles/prosilver/template/mcp_reports.html @@ -0,0 +1,112 @@ + + +
+ + +
+ + + {S_FORM_TOKEN} +
+ + +

{L_TITLE}

+ +
+
+ +

{L_EXPLAIN}

+ + +
+ +
+ +
    +
  • +
    +
    {L_VIEW_DETAILS}
    +
    {L_REPORTER} & {L_FORUM}
    +
    {L_MARK}
    +
    +
  • +
+
    + + +
  • +
    + +
    +
    + {postrow.PM_SUBJECT}
    + {L_MESSAGE_BY_AUTHOR} {postrow.PM_AUTHOR_FULL} » {postrow.PM_TIME}
    + {L_MESSAGE_TO} {postrow.RECIPIENTS} + +
    +
    +
    + {postrow.REPORTER_FULL} « {postrow.REPORT_TIME} +
    + +
    +
    + {postrow.POST_SUBJECT}
    + {L_POSTED} {L_POST_BY_AUTHOR} {postrow.POST_AUTHOR_FULL} » {postrow.POST_TIME} + +
    +
    +
    + {postrow.REPORTER_FULL} « {postrow.REPORT_TIME}
    + {L_FORUM}{L_COLON} {postrow.FORUM_NAME}{postrow.FORUM_NAME}
    +
    + +
    +
    +
  • + +
+ +
+ + + + +
+ + +

{L_NO_REPORTS}

+ + +
+
+ + +
+ +   + +
+ +
+ + diff --git a/styles/prosilver/template/mcp_topic.html b/styles/prosilver/template/mcp_topic.html new file mode 100644 index 0000000..b56ed18 --- /dev/null +++ b/styles/prosilver/template/mcp_topic.html @@ -0,0 +1,202 @@ + + + + +
+ +
+
+ +
+
+

{L_POSTS_PER_PAGE_EXPLAIN}
+
+
+
+
+
{S_SELECT_SORT_DAYS}  
+
+
+ + +
+

{L_SPLIT_TOPIC_EXPLAIN}

+ + +
+
+
+
+
+ + + +
+
+
+
+ +
+
+
+
+
+ + + +
+

{L_MERGE_TOPIC_EXPLAIN}

+
+
+
+ + {L_SELECT_TOPIC} +
+
{TO_TOPIC_INFO}
+
+
+ + +
+
+ +
+
+ +

+ {L_EXPAND_VIEW} + {L_TOPIC_REVIEW}{L_COLON} {TOPIC_TITLE} +

+ +
+ + +
+
+ +
+ + + +

{postrow.POST_SUBJECT}

+ + + +

+ + {postrow.MINI_POST} + {L_POSTED} {postrow.POST_DATE} {L_POST_BY_AUTHOR} {postrow.POST_AUTHOR_FULL} [ {L_POST_DETAILS} ] +

+ + + +

+ {L_POST_UNAPPROVED} +

+ + + +

+ {L_POST_DELETED} +

+ + + +

+ {L_POST_REPORTED} +

+ + +
{postrow.MESSAGE}
+ + + + +
+
{L_ATTACHMENTS}
+ +
{postrow.attachment.DISPLAY_ATTACHMENT}
+ +
+ + + + +
+ +
+
+ +
+ +
+ +
+ +
+ +
+
+ +
+   + + +{S_HIDDEN_FIELDS} +{S_FORM_TOKEN} +
+ +
+ + diff --git a/styles/prosilver/template/mcp_warn_front.html b/styles/prosilver/template/mcp_warn_front.html new file mode 100644 index 0000000..9b188b5 --- /dev/null +++ b/styles/prosilver/template/mcp_warn_front.html @@ -0,0 +1,97 @@ + + +
+ +

{L_WARN_USER}

+ +
+
+ +

{L_SELECT_USER}

+ +
+
+
+
+
{L_FIND_USERNAME}
+
+
+ +
+
+ +
+   + + {S_FORM_TOKEN} +
+
+ +
+
+ +

{L_MOST_WARNINGS}

+ + + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_WARNINGS}{L_LATEST_WARNING_TIME}
{highest.USERNAME_FULL}{highest.WARNINGS}{highest.WARNING_TIME}{L_VIEW_NOTES}
+ +

{L_NO_WARNINGS}

+ + +
+
+ +
+
+ +

{L_LATEST_WARNINGS}

+ + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_TIME}{L_TOTAL_WARNINGS}
{latest.USERNAME_FULL}{latest.WARNING_TIME}{latest.WARNINGS}{L_VIEW_NOTES}
+ +

{L_NO_WARNINGS}

+ + +
+
+ + diff --git a/styles/prosilver/template/mcp_warn_list.html b/styles/prosilver/template/mcp_warn_list.html new file mode 100644 index 0000000..29a2d29 --- /dev/null +++ b/styles/prosilver/template/mcp_warn_list.html @@ -0,0 +1,70 @@ + + +
+ +

{L_WARNED_USERS}

+ +
+
+ +

{L_WARNED_USERS_EXPLAIN}

+ + +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_WARNINGS}{L_LATEST_WARNING_TIME}
{user.USERNAME_FULL}{user.WARNINGS}{user.WARNING_TIME}{L_VIEW_NOTES}
+ +
+ + + +
+ + +

{L_NO_WARNINGS}

+ + +
+ +{S_FORM_TOKEN} +
+ +
+ + diff --git a/styles/prosilver/template/mcp_warn_post.html b/styles/prosilver/template/mcp_warn_post.html new file mode 100644 index 0000000..5e39480 --- /dev/null +++ b/styles/prosilver/template/mcp_warn_post.html @@ -0,0 +1,78 @@ + + +
+ +

{L_MCP_WARN_POST}

+ +
+
+ +

{USERNAME}{USERNAME}

+ +
+
+
{AVATAR_IMG}
+
+ +
+
+
{L_RANK}{L_COLON}
{RANK_TITLE}
+
 {L_RANK}{L_COLON}
{RANK_IMG}
+
{L_JOINED}{L_COLON}
{JOINED}
+
{L_TOTAL_POSTS}{L_COLON}
{POSTS}
+
{L_WARNINGS}{L_COLON}
{WARNINGS}
+
+
+
+ +
+
+ +
+
+ +

{L_POST_DETAILS}

+ +
+ +
+ {POST} +
+ +
+ +
+
+ + + +
+
+ +

{L_ADD_WARNING}

+

{L_ADD_WARNING_EXPLAIN}

+ +
+ + +

+
+
 
+
+
+ +
+ +
+
+ + + +
+   + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/mcp_warn_user.html b/styles/prosilver/template/mcp_warn_user.html new file mode 100644 index 0000000..f4dbf28 --- /dev/null +++ b/styles/prosilver/template/mcp_warn_user.html @@ -0,0 +1,62 @@ + + +
+ +

{L_WARN_USER}

+ +
+
+ +

{USERNAME_FULL}

+ +
+
+
{AVATAR_IMG}
+
+ +
+
+
{L_RANK}{L_COLON}
{RANK_TITLE}
+
 {L_RANK}{L_COLON}
{RANK_IMG}
+
{L_JOINED}{L_COLON}
{JOINED}
+
{L_TOTAL_POSTS}{L_COLON}
{POSTS}
+
{L_WARNINGS}{L_COLON}
{WARNINGS}
+
+
+
+ +
+
+ + + +
+
+ +

{L_ADD_WARNING}

+

{L_ADD_WARNING_EXPLAIN}

+ +
+ + +

+
+
 
+
+
+ +
+ +
+
+ + + +
+   + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/mcp_whois.html b/styles/prosilver/template/mcp_whois.html new file mode 100644 index 0000000..1d08a46 --- /dev/null +++ b/styles/prosilver/template/mcp_whois.html @@ -0,0 +1,22 @@ + +

{L_WHOIS}

+ +
+ +
+ + diff --git a/styles/prosilver/template/memberlist_body.html b/styles/prosilver/template/memberlist_body.html new file mode 100644 index 0000000..5f03ad9 --- /dev/null +++ b/styles/prosilver/template/memberlist_body.html @@ -0,0 +1,179 @@ + + + +
+ + + + + + + + +{% EVENT memberlist_body_page_header_after %} + + + {% EVENT memberlist_body_group_name_before %} +

style="color:#{GROUP_COLOR};">{GROUP_NAME}

+ {% EVENT memberlist_body_group_name_after %} + +

{L_MANAGE_GROUP}

+ +

{GROUP_DESC} {GROUP_TYPE}

+ + {% EVENT memberlist_body_group_desc_after %} + +

+ {AVATAR_IMG} + {% EVENT memberlist_body_group_rank_before %} + {RANK_IMG} + {GROUP_RANK} + {% EVENT memberlist_body_group_rank_after %} +

+ + {% EVENT memberlist_body_page_title_before %} +

{PAGE_TITLE}{L_COLON} {SEARCH_WORDS}

+ +
+ + + +
+ + + +
+
+ + + + + + + + + + {% EVENT memberlist_body_memberlist_after %} + + + + + + + + + + + + + + +
{L_RANK}{L_GROUP_LEADER}{L_USERNAME}{L_POSTS}{L_COMMA_SEPARATOR} {custom_fields.PROFILE_FIELD_NAME}{L_JOINED}{L_LAST_ACTIVE}
 
+ +
+
+ +
+
+ + + + + + + + + + + {% EVENT memberlist_body_leaders_set_after %} + + + + + + + {% EVENT memberlist_body_show_group_after %} + + + + + + + + + + + + + + + {% EVENT memberlist_body_memberrow_after %} + + + + + + + +
{L_RANK}{L_GROUP_MEMBERS}{L_USERNAME}{L_POSTS}{% for field in custom_fields %}{% if not loop.first %}{L_COMMA_SEPARATOR} {% endif %}{{ field.PROFILE_FIELD_NAME }}{% endfor %}{L_JOINED}{L_LAST_ACTIVE}{L_GROUP_MEMBERS}{L_POSTS}{L_COMMA_SEPARATOR} {custom_fields.PROFILE_FIELD_NAME}{L_JOINED}{L_LAST_ACTIVE}
{memberrow.RANK_IMG}{memberrow.RANK_TITLE} {memberrow.USERNAME_FULL} ({L_INACTIVE})
{L_SELECT} ]
{memberrow.POSTS}{memberrow.POSTS}
{memberrow.custom_fields.PROFILE_FIELD_VALUE}
 
{memberrow.JOINED}{memberrow.LAST_ACTIVE} 
{L_NO_MEMBERS}
+ +
+
+ + +
+ + +
+ + + +
+
+ + + +
+ + + +
+ + +
+ +
+ +
+ +{% EVENT memberlist_body_page_footer_before %} + + + + + + + diff --git a/styles/prosilver/template/memberlist_email.html b/styles/prosilver/template/memberlist_email.html new file mode 100644 index 0000000..eea699d --- /dev/null +++ b/styles/prosilver/template/memberlist_email.html @@ -0,0 +1,107 @@ + + + + + +

{L_CONTACT_ADMIN}

+ +

{L_SEND_EMAIL_USER}

+ +

{L_EMAIL_TOPIC}

+ + +
+ + +
+
+
+
+ {CONTACT_INFO} +
+
+
+
+
+ + +
+
+
+ +

{ERROR_MESSAGE}

+
+ +
+
+
{USERNAME_FULL}
+
+
+
+
+
+ +
+
+
{L_ADMINISTRATOR}
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+

+ {L_DEST_LANG_EXPLAIN}
+
+
+ +
+

+ {L_EMAIL_BODY_EXPLAIN}
+
+
+ +
+
 
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+
+{S_FORM_TOKEN} +
+ +
+ + diff --git a/styles/prosilver/template/memberlist_im.html b/styles/prosilver/template/memberlist_im.html new file mode 100644 index 0000000..874607d --- /dev/null +++ b/styles/prosilver/template/memberlist_im.html @@ -0,0 +1,46 @@ + + +

{L_SEND_IM}

+ +
+ +
+
+ +

{L_SEND_IM_EXPLAIN}

+ + +

{L_IM_SENT_JABBER}

+ + +
+
+
+
{USERNAME} [ {IM_CONTACT} ] {PRESENCE_IMG}
+
+ + +
+
+
+
+
+
 
+
+
+ +
+
 
+
{L_IM_NO_JABBER}
+
+ + {S_FORM_TOKEN} +
+ +
+
+
+ +{L_CLOSE_WINDOW} + + diff --git a/styles/prosilver/template/memberlist_search.html b/styles/prosilver/template/memberlist_search.html new file mode 100644 index 0000000..b1c7a81 --- /dev/null +++ b/styles/prosilver/template/memberlist_search.html @@ -0,0 +1,87 @@ +

{L_FIND_USERNAME}

+ +
+
+
+ +

{L_FIND_USERNAME_EXPLAIN}

+ + +
+
+
+
+ + +
+
+ +
+
+
+
+ + +
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + +
+ +
+ +
+ +
+   + + {S_FORM_TOKEN} +
+ +
+
+ +
diff --git a/styles/prosilver/template/memberlist_team.html b/styles/prosilver/template/memberlist_team.html new file mode 100644 index 0000000..59041fb --- /dev/null +++ b/styles/prosilver/template/memberlist_team.html @@ -0,0 +1,47 @@ + + +

{PAGE_TITLE}

+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
{L_RANK} {group.GROUP_NAME}{group.GROUP_NAME}{L_PRIMARY_GROUP}{L_MODERATOR}
{group.user.RANK_IMG}{group.user.RANK_TITLE}{group.user.USERNAME_FULL} ({L_INACTIVE}) + style="font-weight: bold; color: #{group.user.GROUP_COLOR}" href="{group.user.U_GROUP}">{group.user.GROUP_NAME} + + {group.user.GROUP_NAME} + {group.user.FORUMS}-
{L_NO_MEMBERS}
+ +
+
+ + +
+ + + diff --git a/styles/prosilver/template/memberlist_view.html b/styles/prosilver/template/memberlist_view.html new file mode 100644 index 0000000..debf64c --- /dev/null +++ b/styles/prosilver/template/memberlist_view.html @@ -0,0 +1,142 @@ + + +

{PAGE_TITLE}

+ + + +
+
+
+ + +
+
{AVATAR_IMG}
+ +
{RANK_TITLE}
+
{RANK_IMG}
+ +
+ + +
+
{L_USERNAME}{L_COLON}
+
+ {USERNAME} + [ {L_EDIT_PROFILE} ] + [ {L_USER_ADMIN} ] + [ {L_USER_BAN} ] + [ {L_USE_PERMISSIONS} ] +
+ + +
{L_RANK}{L_COLON}
{RANK_TITLE}
+
 {L_RANK}{L_COLON}
{RANK_IMG}
+ + +
{L_USER_IS_INACTIVE}{L_COLON}
{USER_INACTIVE_REASON}
+
{L_AGE}{L_COLON}
{AGE}
+
{L_USERGROUPS}{L_COLON}
+ + + +
{custom_fields.PROFILE_FIELD_NAME}{L_COLON}
{custom_fields.PROFILE_FIELD_VALUE}
+ + + + + + +
 
{L_REMOVE_FRIEND}
+ +
 
{L_REMOVE_FOE}
+ + +
 
{L_ADD_FRIEND}
+ + +
 
{L_ADD_FOE}
+ + + + +
+ +
+
+ + +
+
+ +
+

{L_CONTACT_USER}

+ +
+
{L_EMAIL_ADDRESS}{L_COLON}
{L_SEND_EMAIL_USER}
+
{L_PM}{L_COLON}
{L_SEND_PRIVATE_MESSAGE}
+
{L_JABBER}{L_COLON}
{L_SEND_JABBER_MESSAGE}
{L_JABBER}{L_COLON}
{USER_JABBER}
+ + + +
{custom_fields.PROFILE_FIELD_NAME}{L_COLON}
+ +
{custom_fields.PROFILE_FIELD_DESC}
+ +
{custom_fields.PROFILE_FIELD_VALUE}
+ + + + + + +
{PROFILE_FIELD1_NAME}{L_COLON}
{PROFILE_FIELD1_VALUE}
+ +
+
+ +
+

{L_USER_FORUM}

+
+ +
{L_JOINED}{L_COLON}
{JOINED}
+
{L_LAST_ACTIVE}{L_COLON}
{LAST_ACTIVE}
+ +
{L_WARNINGS}{L_COLON}
+
{WARNINGS} [ {L_VIEW_NOTES} | {L_WARN_USER} ]
+ +
{L_TOTAL_POSTS}{L_COLON}
+
{POSTS} | {L_SEARCH_USER_POSTS} +
({POSTS_PCT} / {POSTS_DAY}) +
({L_POSTS_IN_QUEUE})
({L_POSTS_IN_QUEUE}) +
+ +
{L_ACTIVE_IN_FORUM}{L_COLON}
{ACTIVE_FORUM}
({ACTIVE_FORUM_POSTS} / {ACTIVE_FORUM_PCT}) -
+
{L_ACTIVE_IN_TOPIC}{L_COLON}
{ACTIVE_TOPIC}
({ACTIVE_TOPIC_POSTS} / {ACTIVE_TOPIC_PCT}) -
+ + +
+
+ +
+
+ + + +
+
+ +

{L_SIGNATURE}

+ +
{SIGNATURE}
+ +
+
+ + +
+ + + + + + diff --git a/styles/prosilver/template/message_body.html b/styles/prosilver/template/message_body.html new file mode 100644 index 0000000..330203e --- /dev/null +++ b/styles/prosilver/template/message_body.html @@ -0,0 +1,25 @@ + + + + + + +
+
+

{MESSAGE_TITLE}

+

{MESSAGE_TEXT}

+ +

+ + {L_GO_TO_SEARCH_ADV} + +

+ +
+
+ + + + + + diff --git a/styles/prosilver/template/navbar_footer.html b/styles/prosilver/template/navbar_footer.html new file mode 100644 index 0000000..4e3d1e2 --- /dev/null +++ b/styles/prosilver/template/navbar_footer.html @@ -0,0 +1,69 @@ + diff --git a/styles/prosilver/template/navbar_header.html b/styles/prosilver/template/navbar_header.html new file mode 100644 index 0000000..dc29285 --- /dev/null +++ b/styles/prosilver/template/navbar_header.html @@ -0,0 +1,210 @@ + diff --git a/styles/prosilver/template/notification_dropdown.html b/styles/prosilver/template/notification_dropdown.html new file mode 100644 index 0000000..e444d8f --- /dev/null +++ b/styles/prosilver/template/notification_dropdown.html @@ -0,0 +1,47 @@ + diff --git a/styles/prosilver/template/overall_footer.html b/styles/prosilver/template/overall_footer.html new file mode 100644 index 0000000..bdff1a0 --- /dev/null +++ b/styles/prosilver/template/overall_footer.html @@ -0,0 +1,120 @@ + +
+ + + + + +
+ +
+ + {RUN_CRON_TASK} +
+ + + + + + + + + + + + + + + + + + +{$SCRIPTS} + + + + + diff --git a/styles/prosilver/template/overall_header.html b/styles/prosilver/template/overall_header.html new file mode 100644 index 0000000..09824d0 --- /dev/null +++ b/styles/prosilver/template/overall_header.html @@ -0,0 +1,131 @@ + + + + + + +{META} +<!-- IF UNREAD_NOTIFICATIONS_COUNT -->({UNREAD_NOTIFICATIONS_COUNT}) <!-- ENDIF --><!-- IF not S_VIEWTOPIC and not S_VIEWFORUM -->{SITENAME} - <!-- ENDIF --><!-- IF S_IN_MCP -->{L_MCP} - <!-- ELSEIF S_IN_UCP -->{L_UCP} - <!-- ENDIF -->{PAGE_TITLE}<!-- IF S_VIEWTOPIC or S_VIEWFORUM --> - {SITENAME}<!-- ENDIF --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{$STYLESHEETS} + + + + + + + + +
+ + + + + + +
+ +
+
+ {L_INFORMATION}{L_COLON} {L_BOARD_DISABLED} +
+
+ + + diff --git a/styles/prosilver/template/pagination.html b/styles/prosilver/template/pagination.html new file mode 100644 index 0000000..5d48451 --- /dev/null +++ b/styles/prosilver/template/pagination.html @@ -0,0 +1,30 @@ + diff --git a/styles/prosilver/template/plupload.html b/styles/prosilver/template/plupload.html new file mode 100644 index 0000000..1eb8437 --- /dev/null +++ b/styles/prosilver/template/plupload.html @@ -0,0 +1,67 @@ + + + diff --git a/styles/prosilver/template/posting_attach_body.html b/styles/prosilver/template/posting_attach_body.html new file mode 100644 index 0000000..b46e9c9 --- /dev/null +++ b/styles/prosilver/template/posting_attach_body.html @@ -0,0 +1,93 @@ +
+
+ +

{L_ADD_ATTACHMENT_EXPLAIN}

+ +
+
+
+
+ + +
+
+
+
+
+
+
+ +
+ +
+ + {% EVENT posting_attach_body_file_list_before %} +
+
+ + + + + + + + + + + + + + + + + {% EVENT posting_attach_body_attach_row_before %} + + {% EVENT posting_attach_body_attach_row_prepend %} + + + + + + + {% EVENT posting_attach_body_attach_row_append %} + + {% EVENT posting_attach_body_attach_row_after %} + +
{L_PLUPLOAD_FILENAME}{L_FILE_COMMENT}{L_PLUPLOAD_SIZE}{L_PLUPLOAD_STATUS}
+ + +   + + + + + + + + + + + + +
+ {attach_row.FILENAME} + {% EVENT posting_attach_body_attach_row_controls_prepend %} + +   + + + {% EVENT posting_attach_body_attach_row_controls_append %} + + + + {attach_row.S_HIDDEN} + + {attach_row.FILESIZE} + + +
+
+
+ {% EVENT posting_attach_body_file_list_after %} +
+
diff --git a/styles/prosilver/template/posting_body.html b/styles/prosilver/template/posting_body.html new file mode 100644 index 0000000..73b8270 --- /dev/null +++ b/styles/prosilver/template/posting_body.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/styles/prosilver/template/posting_buttons.html b/styles/prosilver/template/posting_buttons.html new file mode 100644 index 0000000..122afdf --- /dev/null +++ b/styles/prosilver/template/posting_buttons.html @@ -0,0 +1,126 @@ + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/styles/prosilver/template/posting_editor.html b/styles/prosilver/template/posting_editor.html new file mode 100644 index 0000000..d963c98 --- /dev/null +++ b/styles/prosilver/template/posting_editor.html @@ -0,0 +1,196 @@ +
+

{ERROR}

+ + +
+
+
+ + +
+
+ + + +
+
+
+
+ + + + + +
+
+
+ + + +
+
+ + + + + + + + + + +
+ + + {L_SMILIES}
+ + {smiley.SMILEY_CODE} + + + +
{L_MORE_SMILIES} + + + +
+
+ {BBCODE_STATUS}
+ + {IMG_STATUS}
+ {FLASH_STATUS}
+ {URL_STATUS}
+ + {SMILIES_STATUS} +
+ + + +
+ {L_BACK_TO_DRAFTS} + {L_TOPIC_REVIEW} + +
+ + + +
+ +
+ + +
+ + + + +
+
+ + + +
+
+
+ {S_HIDDEN_ADDRESS_FIELD} + {S_HIDDEN_FIELDS} + +   +   + onclick="document.getElementById('postform').action += '#preview';" />  +   + +
+ +
+
+ + + +
+ +
+ + + +
+
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + +
+ + + +
+
+
+
+ + + +
+
+
+
{L_STICK_TOPIC_FOR_EXPLAIN}
+
+ + + +
+
+
+
+ +
+ +
+ + + +
+
+
+
+ +
+ + + diff --git a/styles/prosilver/template/posting_layout.html b/styles/prosilver/template/posting_layout.html new file mode 100644 index 0000000..bca9195 --- /dev/null +++ b/styles/prosilver/template/posting_layout.html @@ -0,0 +1,86 @@ + + + +

{TOPIC_TITLE}

+ +

{FORUM_NAME}

+ + + +
+
+ + + {L_FORUM_RULES} + + {L_FORUM_RULES}
+ {FORUM_RULES} + + +
+
+ + +
+ + +
+
+ +

{L_INFORMATION}

+

{L_DRAFT_LOADED}

+ +
+
+ + + + + + + +
+
+
+

{L_SELECT_DESTINATION_FORUM}

+

{L_UNGLOBALISE_EXPLAIN}

+
+
+
+
+ +
+
 
+
+
+
+ +
+
+ + + + +
+
+ +

{L_POST_A}

+ + + + + {S_FORM_TOKEN} +
+
+ + + + + + + + + +
+ + diff --git a/styles/prosilver/template/posting_pm_header.html b/styles/prosilver/template/posting_pm_header.html new file mode 100644 index 0000000..7fee914 --- /dev/null +++ b/styles/prosilver/template/posting_pm_header.html @@ -0,0 +1,83 @@ +
+ + + +
+
+
+
+ + +
+ +
+
+
+ + + + {L_FIND_USERNAME} + +
+
+ +
+
+
+ +
+
+
+ +
+
+ +
+ +
+
+
+
+ +
+
+
+ + +
+
+

{L_FIND_USERNAME}
+ +
+ + +
+
+ +
+
+ + + +
diff --git a/styles/prosilver/template/posting_pm_layout.html b/styles/prosilver/template/posting_pm_layout.html new file mode 100644 index 0000000..316fa79 --- /dev/null +++ b/styles/prosilver/template/posting_pm_layout.html @@ -0,0 +1,43 @@ + + + +
+
+ +

{L_INFORMATION}

+

{L_DRAFT_LOADED_PM}

+ +
+
+ + + + + + +

{L_TITLE}

+ +
+
+ + + +
+
+ +
+
+ + + + +
+
+ + + + + + + + diff --git a/styles/prosilver/template/posting_poll_body.html b/styles/prosilver/template/posting_poll_body.html new file mode 100644 index 0000000..ee7100a --- /dev/null +++ b/styles/prosilver/template/posting_poll_body.html @@ -0,0 +1,53 @@ +
+
+ + +

{L_ADD_POLL_EXPLAIN}

+ + +
+ +
+
+
+
+ + + +
+
+
+
+
+

{L_POLL_OPTIONS_EXPLAIN}
+
+
+ +
+ +
+
+
+
{L_POLL_MAX_OPTIONS_EXPLAIN}
+
+
+
+
+
{L_POLL_FOR_EXPLAIN}
+
+ + +
+ +
+
+
+
+ + + + +
+ +
+
diff --git a/styles/prosilver/template/posting_preview.html b/styles/prosilver/template/posting_preview.html new file mode 100644 index 0000000..781d0de --- /dev/null +++ b/styles/prosilver/template/posting_preview.html @@ -0,0 +1,51 @@ +
+
+ + +
+

{L_PREVIEW}{L_COLON} {POLL_QUESTION}

+

{L_POLL_LENGTH}
{L_MAX_VOTES}

+ +
+ +
+
+
checked="checked" /> checked="checked" />
+
+ +
+
+ +
+
+ +
+
+ + + + + +
+

{L_PREVIEW}{L_COLON} {PREVIEW_SUBJECT}

+ +
{PREVIEW_MESSAGE}
+ + + + +
+
{L_ATTACHMENTS}
+ +
{attachment.DISPLAY_ATTACHMENT}
+ +
+ + +
{PREVIEW_SIGNATURE}
+
+ +
+
+ +
diff --git a/styles/prosilver/template/posting_review.html b/styles/prosilver/template/posting_review.html new file mode 100644 index 0000000..1304046 --- /dev/null +++ b/styles/prosilver/template/posting_review.html @@ -0,0 +1,44 @@ +

{L_POST_REVIEW}

+ +

{L_POST_REVIEW_EXPLAIN}

+ + + +
+
+ {post_review_row.L_IGNORE_POST} + +
+
+ + +
+

{post_review_row.POST_SUBJECT}

+

+ + {post_review_row.MINI_POST} + + + {post_review_row.MINI_POST} + + + {L_POST_BY_AUTHOR} {post_review_row.POST_AUTHOR_FULL} » {post_review_row.POST_DATE} +

+
{post_review_row.MESSAGE}
+ + +
+
{L_ATTACHMENTS}
+ +
{post_review_row.attachment.DISPLAY_ATTACHMENT}
+ +
+ + +
+ +
+
+ + +
diff --git a/styles/prosilver/template/posting_smilies.html b/styles/prosilver/template/posting_smilies.html new file mode 100644 index 0000000..b5794d5 --- /dev/null +++ b/styles/prosilver/template/posting_smilies.html @@ -0,0 +1,25 @@ + + + + + +

{L_SMILIES}

+
+
+ + {smiley.SMILEY_CODE} + + +
+
+ + + +{L_CLOSE_WINDOW} + + diff --git a/styles/prosilver/template/posting_topic_review.html b/styles/prosilver/template/posting_topic_review.html new file mode 100644 index 0000000..93c4484 --- /dev/null +++ b/styles/prosilver/template/posting_topic_review.html @@ -0,0 +1,89 @@ + +

+ {L_EXPAND_VIEW} + {L_TOPIC_REVIEW}{L_COLON} {TOPIC_TITLE} +

+ +
+ + + + +
+
+ {topic_review_row.L_IGNORE_POST} + +
+
+ {topic_review_row.L_DELETE_POST} + +
+
+ + +
+

{topic_review_row.POST_SUBJECT}

+ + + + + + +

+ + {topic_review_row.MINI_POST} + + + {topic_review_row.MINI_POST} + + + {L_POST_BY_AUTHOR} {topic_review_row.POST_AUTHOR_FULL} » {topic_review_row.POST_DATE} +

+ + +
{topic_review_row.MESSAGE}
+ + + + +
+
{L_ATTACHMENTS}
+ +
{topic_review_row.attachment.DISPLAY_ATTACHMENT}
+ +
+ + + + + +
+
+
+ +
+ +
+ +

+ + {L_BACK_TO_TOP} + +

diff --git a/styles/prosilver/template/profilefields/bool.html b/styles/prosilver/template/profilefields/bool.html new file mode 100644 index 0000000..f1d7ba7 --- /dev/null +++ b/styles/prosilver/template/profilefields/bool.html @@ -0,0 +1,7 @@ + + + + + checked="checked" /> + + diff --git a/styles/prosilver/template/profilefields/date.html b/styles/prosilver/template/profilefields/date.html new file mode 100644 index 0000000..5d5bc04 --- /dev/null +++ b/styles/prosilver/template/profilefields/date.html @@ -0,0 +1,5 @@ + + + + + diff --git a/styles/prosilver/template/profilefields/dropdown.html b/styles/prosilver/template/profilefields/dropdown.html new file mode 100644 index 0000000..243b703 --- /dev/null +++ b/styles/prosilver/template/profilefields/dropdown.html @@ -0,0 +1,5 @@ + + + diff --git a/styles/prosilver/template/profilefields/int.html b/styles/prosilver/template/profilefields/int.html new file mode 100644 index 0000000..a6f9a0a --- /dev/null +++ b/styles/prosilver/template/profilefields/int.html @@ -0,0 +1,3 @@ + + + diff --git a/styles/prosilver/template/profilefields/string.html b/styles/prosilver/template/profilefields/string.html new file mode 100644 index 0000000..cf457d3 --- /dev/null +++ b/styles/prosilver/template/profilefields/string.html @@ -0,0 +1,3 @@ + + + diff --git a/styles/prosilver/template/profilefields/text.html b/styles/prosilver/template/profilefields/text.html new file mode 100644 index 0000000..f54c639 --- /dev/null +++ b/styles/prosilver/template/profilefields/text.html @@ -0,0 +1,3 @@ + + + diff --git a/styles/prosilver/template/profilefields/url.html b/styles/prosilver/template/profilefields/url.html new file mode 100644 index 0000000..8dd3a90 --- /dev/null +++ b/styles/prosilver/template/profilefields/url.html @@ -0,0 +1,3 @@ + + + diff --git a/styles/prosilver/template/quickreply_editor.html b/styles/prosilver/template/quickreply_editor.html new file mode 100644 index 0000000..9839494 --- /dev/null +++ b/styles/prosilver/template/quickreply_editor.html @@ -0,0 +1,27 @@ +
+ +
+
+

{L_QUICKREPLY}

+
+ +
+
+
+
+ +
+ +
+ +
+
+ {S_FORM_TOKEN} + {QR_HIDDEN_FIELDS} +   +   +
+
+
+ +
diff --git a/styles/prosilver/template/report_body.html b/styles/prosilver/template/report_body.html new file mode 100644 index 0000000..285e8ec --- /dev/null +++ b/styles/prosilver/template/report_body.html @@ -0,0 +1,55 @@ + + +

{L_REPORT_POST}{L_REPORT_MESSAGE}

+ +
+
+
+ +
+

{L_REPORT_POST_EXPLAIN}{L_REPORT_MESSAGE_EXPLAIN}

+ +
+
{ERROR}
+
+
+
+
+ +
+

{L_REPORT_NOTIFY_EXPLAIN}
+
+ + +
+
+ +
+

{L_CAN_LEAVE_BLANK}
+
+
+ + + +
+
+ +
+
+ +
+
+ +
+
+   + + {S_FORM_TOKEN} +
+
+ +
+
+
+ + diff --git a/styles/prosilver/template/search_body.html b/styles/prosilver/template/search_body.html new file mode 100644 index 0000000..618e268 --- /dev/null +++ b/styles/prosilver/template/search_body.html @@ -0,0 +1,136 @@ + + +

{L_SEARCH}

+ + +
+ +
+
+

{L_SEARCH_QUERY}

+ + +
+ +
+

{L_SEARCH_KEYWORDS_EXPLAIN}
+
+
+
+
+
+

{L_SEARCH_AUTHOR_EXPLAIN}
+
+
+ +
+ + +
+
+ +
+
+ +

{L_SEARCH_OPTIONS}

+ + +
+ +
+

{L_SEARCH_FORUMS_EXPLAIN}
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ + +
+ + +
+
+
+ + +
+
+
+
+
{S_SELECT_SORT_KEY}  + + +
+
+
+
+
{S_SELECT_SORT_DAYS}
+
+
+
+
{L_POST_CHARACTERS}
+
+ +
+ + +
+
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + +
+ +
+
+ +
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + +
{L_RECENT_SEARCHES}
{recentsearch.KEYWORDS}{recentsearch.TIME}
{L_NO_RECENT_SEARCHES}
+ +
+
+ + + + diff --git a/styles/prosilver/template/search_results.html b/styles/prosilver/template/search_results.html new file mode 100644 index 0000000..d4dc6aa --- /dev/null +++ b/styles/prosilver/template/search_results.html @@ -0,0 +1,242 @@ + + + + +

{SEARCH_TITLE}{SEARCH_MATCHES}{L_COLON} {SEARCH_WORDS}

+

{L_SEARCHED_QUERY}{L_COLON} {SEARCHED_QUERY}

+

{L_IGNORED_TERMS}{L_COLON} {IGNORED_WORDS}

+

{L_PHRASE_SEARCH_DISABLED}

+ + + + + + + + + + +
+ + + + + + + + +
+ + + + + +
+ +
+
    +
  • +
    +
    {L_TOPICS}
    +
    {L_REPLIES}
    +
    {L_VIEWS}
    +
    {L_LAST_POST}
    +
    +
  • +
+ + +
+
+ +
+
+ {L_NO_SEARCH_RESULTS} +
+
+ + + + + + +
+
+ + +
+ {searchresults.L_IGNORE_POST} +
+ +
+ +
{L_POST_BY_AUTHOR} {searchresults.POST_AUTHOR_FULL}
+
{searchresults.POST_DATE}
+
{L_FORUM}{L_COLON} {searchresults.FORUM_TITLE}
+
{L_TOPIC}{L_COLON} {searchresults.TOPIC_TITLE}
+ +
{L_REPLIES}{L_COLON} {searchresults.TOPIC_REPLIES}
+
{L_VIEWS}{L_COLON} {searchresults.TOPIC_VIEWS}
+ +
+ +
+

{searchresults.POST_SUBJECT}

+
{searchresults.MESSAGE}
+ +
+ + + + + + +
+
+ + +
+
+ {L_NO_SEARCH_RESULTS} +
+
+ + + +
+ +
+ +
+ + + +
+ + + + diff --git a/styles/prosilver/template/simple_footer.html b/styles/prosilver/template/simple_footer.html new file mode 100644 index 0000000..1ef44d1 --- /dev/null +++ b/styles/prosilver/template/simple_footer.html @@ -0,0 +1,40 @@ +
+ + + +
+
 
+
+
+ +
+ + + +

+
+
+ + + +
+
+
+ + + + + + + + + +{$SCRIPTS} + +{% EVENT simple_footer_body_after %} + + + diff --git a/styles/prosilver/template/simple_header.html b/styles/prosilver/template/simple_header.html new file mode 100644 index 0000000..905d250 --- /dev/null +++ b/styles/prosilver/template/simple_header.html @@ -0,0 +1,54 @@ + + + + + + +{META} +{SITENAME} • <!-- IF S_IN_MCP -->{L_MCP} • <!-- ELSEIF S_IN_UCP -->{L_UCP} • <!-- ENDIF -->{PAGE_TITLE} + + + + + + + + + + + + + + + + + + +{$STYLESHEETS} + + + + + + + + + +
+ +
diff --git a/styles/prosilver/template/timezone.js b/styles/prosilver/template/timezone.js new file mode 100644 index 0000000..44ec1b0 --- /dev/null +++ b/styles/prosilver/template/timezone.js @@ -0,0 +1,20 @@ +/* global phpbb */ + +(function($) { // Avoid conflicts with other libraries + +'use strict'; + +$('#tz_date').change(function() { + phpbb.timezoneSwitchDate(false); +}); + +$('#tz_select_date_suggest').click(function(){ + phpbb.timezonePreselectSelect(true); +}); + +$(function () { + phpbb.timezoneEnableDateSelection(); + phpbb.timezonePreselectSelect($('#tz_select_date_suggest').attr('timezone-preselect') === 'true'); +}); + +})(jQuery); // Avoid conflicts with other libraries diff --git a/styles/prosilver/template/timezone_option.html b/styles/prosilver/template/timezone_option.html new file mode 100644 index 0000000..728dc94 --- /dev/null +++ b/styles/prosilver/template/timezone_option.html @@ -0,0 +1,28 @@ +
+
+ + + +
+ + + +
+
diff --git a/styles/prosilver/template/ucp_agreement.html b/styles/prosilver/template/ucp_agreement.html new file mode 100644 index 0000000..ace6525 --- /dev/null +++ b/styles/prosilver/template/ucp_agreement.html @@ -0,0 +1,73 @@ + + + + + + + +
+

+ + {S_HIDDEN_FIELDS} +

+
+ +
+ + + +
+ +
+
+
+

{SITENAME} - {L_REGISTRATION}

+ +

{L_COPPA_BIRTHDAY}{L_TERMS_OF_USE}

+ +
+
+
+ +
+
+
+ + {L_COPPA_NO}  {L_COPPA_YES} + +   + + + {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} +
+
+
+
+ + + +
+
+
+

{SITENAME} - {AGREEMENT_TITLE}

+

{AGREEMENT_TEXT}

+
+

{L_BACK}

+
+
+
+ + + + diff --git a/styles/prosilver/template/ucp_attachments.html b/styles/prosilver/template/ucp_attachments.html new file mode 100644 index 0000000..696f621 --- /dev/null +++ b/styles/prosilver/template/ucp_attachments.html @@ -0,0 +1,83 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +

{L_ATTACHMENTS_EXPLAIN}

+ + +
+ +
+ + + + +
+ + {S_FORM_TOKEN} + + +
+ + +

{L_UCP_NO_ATTACHMENTS}

+ + +
+
+ + +
+ + + {S_FORM_TOKEN} +
+ +
+ + diff --git a/styles/prosilver/template/ucp_auth_link.html b/styles/prosilver/template/ucp_auth_link.html new file mode 100644 index 0000000..078da58 --- /dev/null +++ b/styles/prosilver/template/ucp_auth_link.html @@ -0,0 +1,15 @@ + + +

{L_UCP_AUTH_LINK_TITLE}

+ +
+
+
{ERROR}
+ + + + +
+
+ + diff --git a/styles/prosilver/template/ucp_auth_link_oauth.html b/styles/prosilver/template/ucp_auth_link_oauth.html new file mode 100644 index 0000000..60061a3 --- /dev/null +++ b/styles/prosilver/template/ucp_auth_link_oauth.html @@ -0,0 +1,29 @@ + +
+

{oauth.SERVICE_NAME}

+ +
+ +
+
{L_UCP_AUTH_LINK_ID}{L_COLON}
+
{oauth.UNIQUE_ID}
+
+
+
 
+
+
+ +
+
{L_UCP_AUTH_LINK_ASK}
+
+
+
 
+
+
+ +
+ {oauth.HIDDEN_FIELDS} + {S_HIDDEN_FIELDS} + {S_FORM_TOKEN} +
+ diff --git a/styles/prosilver/template/ucp_avatar_options.html b/styles/prosilver/template/ucp_avatar_options.html new file mode 100644 index 0000000..2cf9488 --- /dev/null +++ b/styles/prosilver/template/ucp_avatar_options.html @@ -0,0 +1,47 @@ +
+
+ +

{L_AVATAR_FEATURES_DISABLED}

+ + +
+

{ERROR}

+
+

{L_AVATAR_EXPLAIN}
+
{AVATAR}
+
+
+
+

{L_AVATAR_SELECT}

+
+
+
+
+
+
+
+ +
+ +

{avatar_drivers.L_EXPLAIN}

+ +
+ {avatar_drivers.OUTPUT} +
+
+ +
+ +
+   + +
+ +
+
diff --git a/styles/prosilver/template/ucp_avatar_options_gravatar.html b/styles/prosilver/template/ucp_avatar_options_gravatar.html new file mode 100644 index 0000000..130a7c2 --- /dev/null +++ b/styles/prosilver/template/ucp_avatar_options_gravatar.html @@ -0,0 +1,11 @@ +
+

{L_GRAVATAR_AVATAR_EMAIL_EXPLAIN}
+
+
+
+

{L_GRAVATAR_AVATAR_SIZE_EXPLAIN}
+
+ ×  + +
+
diff --git a/styles/prosilver/template/ucp_avatar_options_local.html b/styles/prosilver/template/ucp_avatar_options_local.html new file mode 100644 index 0000000..e431b74 --- /dev/null +++ b/styles/prosilver/template/ucp_avatar_options_local.html @@ -0,0 +1,20 @@ + + + + + + +

{L_NO_AVATARS}

+ diff --git a/styles/prosilver/template/ucp_avatar_options_remote.html b/styles/prosilver/template/ucp_avatar_options_remote.html new file mode 100644 index 0000000..8e17562 --- /dev/null +++ b/styles/prosilver/template/ucp_avatar_options_remote.html @@ -0,0 +1,11 @@ +
+

{L_LINK_REMOTE_AVATAR_EXPLAIN}
+
+
+
+

{L_LINK_REMOTE_SIZE_EXPLAIN}
+
+ ×  + +
+
diff --git a/styles/prosilver/template/ucp_avatar_options_upload.html b/styles/prosilver/template/ucp_avatar_options_upload.html new file mode 100644 index 0000000..63a734e --- /dev/null +++ b/styles/prosilver/template/ucp_avatar_options_upload.html @@ -0,0 +1,11 @@ +
+
+
+
+ + +
+

{L_UPLOAD_AVATAR_URL_EXPLAIN}
+
+
+ diff --git a/styles/prosilver/template/ucp_footer.html b/styles/prosilver/template/ucp_footer.html new file mode 100644 index 0000000..eb07f52 --- /dev/null +++ b/styles/prosilver/template/ucp_footer.html @@ -0,0 +1,12 @@ + +
+ +
+
+
+ +
{S_FORM_TOKEN}
+ + + + diff --git a/styles/prosilver/template/ucp_groups_manage.html b/styles/prosilver/template/ucp_groups_manage.html new file mode 100644 index 0000000..f2b4f00 --- /dev/null +++ b/styles/prosilver/template/ucp_groups_manage.html @@ -0,0 +1,247 @@ + + + style="color:#{GROUP_COLOR};">{L_USERGROUPS} :: {GROUP_NAME} + +
+ +
+
+ + +
+

{ERROR_MSG}

+
+ + +

{L_GROUPS_EXPLAIN}

+ + +

{L_GROUP_DETAILS}

+ +
+
+
+
style="color: #{GROUP_COLOUR};">{GROUP_NAME} +
+
+
+
+
+
  
+
+ +
+

{L_GROUP_TYPE_EXPLAIN}
+
+ + + + +
+
+ + + +
+ +
+
+ +
+
+

{L_GROUP_SETTINGS_SAVE}

+ +
+
+

{L_GROUP_COLOR_EXPLAIN}
+
+ +     + [ {L_COLOUR_SWATCH} ] + +
+
+
+
+
+
+
+ +
+
+ + + +
+ {S_HIDDEN_FIELDS} +   + + {S_FORM_TOKEN} +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
{L_GROUP_LEAD}{L_GROUP_DEFAULT}{L_POSTS}{L_JOINED}{L_MARK}
{leader.USERNAME_FULL}{L_YES}{L_NO}{leader.USER_POSTS}{leader.JOINED} 
+ + + + + + + + + + + + + + + + + + +
{L_GROUP_PENDING}{L_GROUP_DEFAULT}{L_POSTS}{L_JOINED}{L_MARK}
+ + + + + + + + + + + + + + + + + + + + + + +
{L_GROUP_APPROVED}{L_GROUP_DEFAULT}{L_POSTS}{L_JOINED}{L_MARK}
{member.USERNAME_FULL}{L_YES}{L_NO}{member.USER_POSTS}{member.JOINED}
+ + + + + + + + + + + +
{L_MEMBERS}
{L_GROUPS_NO_MEMBERS}
+ + +
+ +
+ + +
+
+ +
+ + + +
+ +
+
+ +

{L_ADD_USERS}

+ +

{L_ADD_USERS_UCP_EXPLAIN}

+ +
+
+

{L_USER_GROUP_DEFAULT_EXPLAIN}
+
+ + +
+
+
+

{L_USERNAMES_EXPLAIN}
+
+
{L_FIND_USERNAME}
+
+
+ +
+
+ +
+ + {S_FORM_TOKEN} +
+ + + + +
    +
  • +
    +
    {L_GROUP_LEADER}
    +
    {L_OPTIONS}
    +
    +
  • +
+ + +

{L_NO_LEADERS}

+ + +
+
+ + + + + diff --git a/styles/prosilver/template/ucp_groups_membership.html b/styles/prosilver/template/ucp_groups_membership.html new file mode 100644 index 0000000..e824a7b --- /dev/null +++ b/styles/prosilver/template/ucp_groups_membership.html @@ -0,0 +1,174 @@ + + +

{L_USERGROUPS}

+ +
+ +
+
+ +

{L_GROUPS_EXPLAIN}

+ + +
    +
  • +
    +
    {L_GROUP_LEADER}
    +
    {L_SELECT}
    +
    +
  • +
+ + + + +
    +
  • +
    +
    {L_GROUP_MEMBER}
    +
    {L_SELECT}
    +
    +
  • +
+ + +
+
+ + +
+
+
    +
  • +
    +
    {L_GROUP_PENDING}
    +
    {L_SELECT}
    +
    +
  • +
+ +
+
+ + +
+
+
    +
  • +
    +
    {L_GROUP_NONMEMBER}
    +
    {L_SELECT}
    +
    +
  • +
+ +
+
+ + + + +
+ +
+ + {S_FORM_TOKEN} +
+ + + +
+ +   + + {S_FORM_TOKEN} +
+ +
+ + +
+ + diff --git a/styles/prosilver/template/ucp_header.html b/styles/prosilver/template/ucp_header.html new file mode 100644 index 0000000..98d2eee --- /dev/null +++ b/styles/prosilver/template/ucp_header.html @@ -0,0 +1,101 @@ + + +

{L_UCP}

+ +
+ +
+ + +
+ + +
+
+ +
+ +
+ + + +
+
+ +
+
{L_FRIENDS}
+ + +
{friends_online.USERNAME_FULL}
+ + + +
{friends_offline.USERNAME_FULL}
+ +
+ +
+
+ + + +
+
+ +
+
{L_MESSAGE_COLOURS}
+ +
{pm_colour_info.IMG} {pm_colour_info.LANG}
+ +
+ +
+
+ + +
+ +
diff --git a/styles/prosilver/template/ucp_login_link.html b/styles/prosilver/template/ucp_login_link.html new file mode 100644 index 0000000..be17331 --- /dev/null +++ b/styles/prosilver/template/ucp_login_link.html @@ -0,0 +1,58 @@ + + +
+
+ +

{SITENAME} - {L_LOGIN_LINK}

+ +

{L_LOGIN_LINK_EXPLAIN}

+ +
+
{LOGIN_LINK_ERROR}
+
+ +
+

{L_REGISTER}

+ + +
+
+
 
+
{S_HIDDEN_FIELDS}
+
+
+ +
+ +
+

{L_LOGIN}

+ +
+
+
{LOGIN_ERROR}
+
+
+
+
+
+
+
+
+ + + + + + {S_LOGIN_REDIRECT} +
+
 
+
{S_HIDDEN_FIELDS}
+
+
+
+
+ +
+
+ + diff --git a/styles/prosilver/template/ucp_main_bookmarks.html b/styles/prosilver/template/ucp_main_bookmarks.html new file mode 100644 index 0000000..25647af --- /dev/null +++ b/styles/prosilver/template/ucp_main_bookmarks.html @@ -0,0 +1,124 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +

{L_BOOKMARKS_EXPLAIN}

+ + +

{L_BOOKMARKS_DISABLED}

+ + + +
    +
  • +
    +
    {L_BOOKMARKS}
    +
    {L_LAST_POST}
    +
    {L_MARK}
    +
    +
  • +
+ + +
+ +
+ + +

{L_NO_BOOKMARKS}

+ + + + +
+
+ + +
+ + + {S_FORM_TOKEN} +
+ +
+ + diff --git a/styles/prosilver/template/ucp_main_drafts.html b/styles/prosilver/template/ucp_main_drafts.html new file mode 100644 index 0000000..52ad5b5 --- /dev/null +++ b/styles/prosilver/template/ucp_main_drafts.html @@ -0,0 +1,79 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +

{L_DRAFTS_EXPLAIN}

+ + + + +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+ + + + +
    +
  • +
    +
    {L_DRAFT_TITLE}
    +
    {L_SAVE_DATE}
    +
    {L_MARK}
    +
    +
  • +
+ + +

{L_NO_SAVED_DRAFTS}

+ + +
+
+ + +
+ + + {S_FORM_TOKEN} +
+ + + + + + + diff --git a/styles/prosilver/template/ucp_main_front.html b/styles/prosilver/template/ucp_main_front.html new file mode 100644 index 0000000..04b568f --- /dev/null +++ b/styles/prosilver/template/ucp_main_front.html @@ -0,0 +1,82 @@ + + +

{L_TITLE}

+ +
+
+ +

{L_UCP_WELCOME}

+ + +

{L_IMPORTANT_NEWS}

+ + + + +

{L_YOUR_DETAILS}

+ + +
+ +
{L_JOINED}{L_COLON}
{JOINED}
+
{L_LAST_ACTIVE}{L_COLON}
{LAST_VISIT_YOU}
+
{L_TOTAL_POSTS}{L_COLON}
{POSTS} | {L_SEARCH_YOUR_POSTS}
({POSTS_DAY} / {POSTS_PCT}){POSTS}
+
{L_ACTIVE_IN_FORUM}{L_COLON}
{ACTIVE_FORUM}
({ACTIVE_FORUM_POSTS} / {ACTIVE_FORUM_PCT})
+
{L_ACTIVE_IN_TOPIC}{L_COLON}
{ACTIVE_TOPIC}
({ACTIVE_TOPIC_POSTS} / {ACTIVE_TOPIC_PCT})
+
{L_YOUR_WARNINGS}{L_COLON}
[{WARNINGS}]
+ +
+ + +
+
+ + diff --git a/styles/prosilver/template/ucp_main_subscribed.html b/styles/prosilver/template/ucp_main_subscribed.html new file mode 100644 index 0000000..d8de7fd --- /dev/null +++ b/styles/prosilver/template/ucp_main_subscribed.html @@ -0,0 +1,170 @@ + + +
+ +

{L_TITLE}

+
+
+ +

{L_WATCHED_EXPLAIN}

+ + +
    +
  • +
    +
    {L_WATCHED_FORUMS}
    +
    {L_LAST_POST}
    +
    {L_MARK}
    +
    +
  • +
+ + +
    +
  • +
    +
    {L_WATCHED_FORUMS}
    +
    +
  • +
+

{L_NO_WATCHED_FORUMS}

+ +
+ + +
    +
  • +
    +
    {L_WATCHED_TOPICS}
    +
    {L_LAST_POST}
    +
    {L_MARK}
    +
    +
  • +
+ + +
+ +
+ + +
    +
  • +
    +
    {L_WATCHED_TOPICS}
    +
    +
  • +
+

{L_NO_WATCHED_TOPICS}

+ + +
+
+ + +
+ + + {S_FORM_TOKEN} +
+ +
+ + diff --git a/styles/prosilver/template/ucp_notifications.html b/styles/prosilver/template/ucp_notifications.html new file mode 100644 index 0000000..55e3047 --- /dev/null +++ b/styles/prosilver/template/ucp_notifications.html @@ -0,0 +1,120 @@ + + +
+ +

{TITLE}

+
+
+ +

{TITLE_EXPLAIN}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_NOTIFICATION_TYPE}{notification_methods.NAME}
{notification_types.GROUP_NAME}
+ {notification_types.NAME} +
   {notification_types.EXPLAIN} +
checked="checked" disabled="disabled" />
+ + +
+ +
+ +
+
    +
  • +
    +
    {L_NOTIFICATIONS}
    +
    {L_MARK_READ}
    +
    +
  • +
+ +
+ +
+ +
+ + +

{L_NO_NOTIFICATIONS}

+ + + +
+
+ + +
+ + {S_HIDDEN_FIELDS} + + + {S_FORM_TOKEN} +
+ + +
+ + diff --git a/styles/prosilver/template/ucp_pm_history.html b/styles/prosilver/template/ucp_pm_history.html new file mode 100644 index 0000000..6362a0b --- /dev/null +++ b/styles/prosilver/template/ucp_pm_history.html @@ -0,0 +1,57 @@ + +

+ {L_EXPAND_VIEW} + {L_MESSAGE_HISTORY}{L_COLON} +

+ + +
+ + +
+
+ +
+

class="current">{history_row.SUBJECT}

+ + + + + + + + +

+ {history_row.MINI_POST} {L_SENT_AT}{L_COLON} {history_row.SENT_DATE} +
+ {L_MESSAGE_BY_AUTHOR} {history_row.MESSAGE_AUTHOR_FULL} +

+
{history_row.MESSAGE}{L_MESSAGE_REMOVED_FROM_OUTBOX}
+ +
+ +
+
+ +
+ + +
+

+ + {L_BACK_TO_TOP} + +

+ diff --git a/styles/prosilver/template/ucp_pm_message_footer.html b/styles/prosilver/template/ucp_pm_message_footer.html new file mode 100644 index 0000000..acf6f24 --- /dev/null +++ b/styles/prosilver/template/ucp_pm_message_footer.html @@ -0,0 +1,2 @@ +
{S_FORM_TOKEN}
+ diff --git a/styles/prosilver/template/ucp_pm_message_header.html b/styles/prosilver/template/ucp_pm_message_header.html new file mode 100644 index 0000000..6ad9e9c --- /dev/null +++ b/styles/prosilver/template/ucp_pm_message_header.html @@ -0,0 +1,70 @@ +

{L_TITLE}{L_COLON} {CUR_FOLDER_NAME}

+ +
+ +
+
+

{FOLDER_STATUS}

+ +
+ + + + {L_BUTTON_PM_REPLY} + + + + {L_BUTTON_PM_NEW} + + + + + {L_BUTTON_PM_FORWARD} + + + + + {L_BUTTON_PM_REPLY_ALL} + + + + + + + + + + + +
diff --git a/styles/prosilver/template/ucp_pm_options.html b/styles/prosilver/template/ucp_pm_options.html new file mode 100644 index 0000000..247be8b --- /dev/null +++ b/styles/prosilver/template/ucp_pm_options.html @@ -0,0 +1,131 @@ + + +

{L_TITLE}

+ + + +
+
+ +

{ERROR_MESSAGE}

+

{NOTIFICATION_MESSAGE}

+ +

{L_DEFINED_RULES}

+ +
    + +
  1. {L_IF} {rule.CHECK} {rule.RULE} {rule.STRING} | {rule.ACTION}{L_COLON} {rule.FOLDER}
  2. + +
  3. {L_NO_RULES_DEFINED}
  4. + +
+ +

{L_ADD_NEW_RULE}

+ +
+ + +
+
for="check_option">{L_IF}{L_COLON}
+
+ {CHECK_CURRENT} +
+
+ + + +
+
+
{RULE_CURRENT}
+
+ + + + +
+
+
+ + + + +  [ {L_FIND_USERNAME} ] + + {L_NO_GROUPS} + + + + {COND_CURRENT} + +
+
+ + + + + + + + +
+
+
{ACTION_CURRENT}
+
+ + +
+ +

{L_FOLDER_OPTIONS}

+ +
+ + +
+
+
{L_MAX_FOLDER_REACHED}
+
+
+ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+ +
+
+
+
+
+
+ + +
+

{L_DEFAULT_ACTION_EXPLAIN}
+
{DEFAULT_ACTION}
+
+
+
+ +
+ {S_FORM_TOKEN} +
+ + + diff --git a/styles/prosilver/template/ucp_pm_viewfolder.html b/styles/prosilver/template/ucp_pm_viewfolder.html new file mode 100644 index 0000000..a290313 --- /dev/null +++ b/styles/prosilver/template/ucp_pm_viewfolder.html @@ -0,0 +1,130 @@ + + + + + + + +

{L_EXPORT_AS_CSV}

+
+
+
+

{L_OPTIONS}

+
+
+
+
+
+
+
+
+
+
+
+
+
+ +   +   + {S_FORM_TOKEN} +
+
+ + + + +
+

{RULE_REMOVED_MESSAGES}

+
+ + + +
+

{NOT_MOVED_MESSAGES}
{RELEASE_MESSAGE_INFO}

+
+ + + +
    +
  • +
    +
    {L_MESSAGE}
    +
    {L_MARK}
    +
    +
  • +
+
    + + +
  • +
    + style="background-image: url({messagerow.PM_ICON_URL}); background-repeat: no-repeat;"> + +
    + + + {L_DELETE_MESSAGE}
    + {L_MESSAGE_REMOVED_FROM_OUTBOX} + + {messagerow.SUBJECT} + + +
    {L_PM_FROM_REMOVED_AUTHOR} + + + + {PM_REPORTED} + +
    + {L_MESSAGE_TO} {messagerow.RECIPIENTS}{L_MESSAGE_BY_AUTHOR} {messagerow.MESSAGE_AUTHOR_FULL} » {messagerow.SENT_TIME} + +
    + +
    {L_SENT_AT}{L_COLON} {messagerow.SENT_TIME}
    +
    {messagerow.FOLDER}{L_UNKNOWN_FOLDER}
    +
    +
    +
  • + + +
+ +

+ + {L_USER_NEW_PERMISSION_DISALLOWED}{L_NO_AUTH_SEND_MESSAGE} + + {L_NO_MESSAGES} + +

+ + + +
+

+ + +
+ +
+ +
+ + + + +
+ + +
+
+ + + + diff --git a/styles/prosilver/template/ucp_pm_viewmessage.html b/styles/prosilver/template/ucp_pm_viewmessage.html new file mode 100644 index 0000000..7cb44a0 --- /dev/null +++ b/styles/prosilver/template/ucp_pm_viewmessage.html @@ -0,0 +1,196 @@ + + + + +
+
+ + + +
+ + + {L_VIEW_PREVIOUS_HISTORY} + + + + + {L_VIEW_NEXT_HISTORY} + + +
+ + + +
+
+ +
+
+
+ + {AUTHOR_AVATAR} + +
+ {MESSAGE_AUTHOR_FULL} +
+ + +
{RANK_TITLE}
{RANK_IMG}
+ + +
{L_POSTS}{L_COLON} {AUTHOR_POSTS}{AUTHOR_POSTS}
+
{L_JOINED}{L_COLON} {AUTHOR_JOINED}
+ + + + +
{custom_fields.PROFILE_FIELD_NAME}{L_COLON} {custom_fields.PROFILE_FIELD_VALUE}
+ + + + + + +
+ {L_CONTACT}{L_COLON} + +
+ + +
+ +
+

{SUBJECT}

+ + + + + + + + +

+ {L_SENT_AT}{L_COLON} {SENT_DATE} +
{L_PM_FROM}{L_COLON} {MESSAGE_AUTHOR_FULL} +
{L_TO}{L_COLON} {to_recipient.NAME_FULL} style="color:{to_recipient.COLOUR};">{to_recipient.NAME}  +
{L_BCC}{L_COLON} {bcc_recipient.NAME_FULL} style="color:{bcc_recipient.COLOUR};">{bcc_recipient.NAME}  +

+ + +
{MESSAGE}
+ + +
+
+ {L_ATTACHMENTS} +
+ +
{attachment.DISPLAY_ATTACHMENT}
+ +
+ + + +
{L_DOWNLOAD_NOTICE}
+ + + +
{EDITED_MESSAGE} +
{L_REASON}{L_COLON} {EDIT_REASON} +
+ + + +
{SIGNATURE}
+ +
+ + + +
+
+ + + +
+ +   + + + {L_VIEW_PREVIOUS_PM} + + + + + {L_VIEW_NEXT_PM} + + + + + + +
+ + + + + + + diff --git a/styles/prosilver/template/ucp_pm_viewmessage_print.html b/styles/prosilver/template/ucp_pm_viewmessage_print.html new file mode 100644 index 0000000..7a88492 --- /dev/null +++ b/styles/prosilver/template/ucp_pm_viewmessage_print.html @@ -0,0 +1,50 @@ + + + + + + +{META} +{SITENAME} • {PAGE_TITLE} + + + + + + +
+ + + + +
+
{PAGE_NUMBER}
+
+

{SUBJECT}

+
{L_SENT_AT} {SENT_DATE}
+
{L_PM_FROM} {MESSAGE_AUTHOR}
+ +
{L_TO} {to_recipient.NAME} 
+ + +
{L_BCC} {bcc_recipient.NAME} 
+ +
+
{MESSAGE}
+
+
+
+ + +
+ + + diff --git a/styles/prosilver/template/ucp_prefs_personal.html b/styles/prosilver/template/ucp_prefs_personal.html new file mode 100644 index 0000000..1650705 --- /dev/null +++ b/styles/prosilver/template/ucp_prefs_personal.html @@ -0,0 +1,122 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +
+

{ERROR}

+ +
+
+
+ + +
+
+
+
+
+ + +
+
+
+

{L_ALLOW_PM_EXPLAIN}
+
+ + +
+
+ +
+

{L_HIDE_ONLINE_EXPLAIN}
+
+ + +
+
+ + +
+
+
+ + + +
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+

{L_BOARD_DATE_FORMAT_EXPLAIN}
+
+ +
+ +
+ +
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+
+ + + + diff --git a/styles/prosilver/template/ucp_prefs_post.html b/styles/prosilver/template/ucp_prefs_post.html new file mode 100644 index 0000000..169d41b --- /dev/null +++ b/styles/prosilver/template/ucp_prefs_post.html @@ -0,0 +1,53 @@ + + +
+ +

{L_TITLE}

+
+
+ +
+

{ERROR}

+ +
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+ +
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/ucp_prefs_view.html b/styles/prosilver/template/ucp_prefs_view.html new file mode 100644 index 0000000..4b7142f --- /dev/null +++ b/styles/prosilver/template/ucp_prefs_view.html @@ -0,0 +1,98 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +
+

{ERROR}

+ +
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+
+ + +
+
+ +
+
+
+ + +
+
+ + +
+ +
+
+
{S_TOPIC_SORT_DAYS}
+
+
+
+
{S_TOPIC_SORT_KEY}
+
+
+
+
{S_TOPIC_SORT_DIR}
+
+
+
+
+
{S_POST_SORT_DAYS}
+
+
+
+
{S_POST_SORT_KEY}
+
+
+
+
{S_POST_SORT_DIR}
+
+ +
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/ucp_profile_autologin_keys.html b/styles/prosilver/template/ucp_profile_autologin_keys.html new file mode 100644 index 0000000..65909b7 --- /dev/null +++ b/styles/prosilver/template/ucp_profile_autologin_keys.html @@ -0,0 +1,45 @@ + + +
+ +

{L_TITLE}

+
+
+

{L_PROFILE_AUTOLOGIN_KEYS}

+

{ERROR}

+ + + + + + + + + + + + + + + + + + + + + +
{L_LOGIN_KEY}{L_IP}{L_LOGIN_TIME}{L_MARK}
{sessions.IP}{sessions.LOGIN_TIME}
{L_PROFILE_NO_AUTOLOGIN_KEYS}
+
+
+ + +
+ {S_HIDDEN_FIELDS} + + {S_FORM_TOKEN} +
+ + +
+ + diff --git a/styles/prosilver/template/ucp_profile_avatar.html b/styles/prosilver/template/ucp_profile_avatar.html new file mode 100644 index 0000000..8157d8c --- /dev/null +++ b/styles/prosilver/template/ucp_profile_avatar.html @@ -0,0 +1,13 @@ + + +
+ +

{L_TITLE}

+ + + +{S_HIDDEN_FIELDS} +{S_FORM_TOKEN} +
+ + diff --git a/styles/prosilver/template/ucp_profile_profile_info.html b/styles/prosilver/template/ucp_profile_profile_info.html new file mode 100644 index 0000000..69eda8c --- /dev/null +++ b/styles/prosilver/template/ucp_profile_profile_info.html @@ -0,0 +1,51 @@ + + +
+ +

{L_TITLE} [ {L_VIEW_PROFILE} ]

+ +
+
+

{L_PROFILE_INFO_NOTICE}

+ +
+

{ERROR}

+ + +
+

{L_BIRTHDAY_EXPLAIN}
+
+ + + +
+
+ + +
+
+
+
+ + +
+
for="{profile_fields.FIELD_ID}">{profile_fields.LANG_NAME}{L_COLON} * +
{profile_fields.LANG_EXPLAIN}
+
{profile_fields.ERROR}
+
{profile_fields.FIELD}
+
+ + +
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/ucp_profile_reg_details.html b/styles/prosilver/template/ucp_profile_reg_details.html new file mode 100644 index 0000000..f62d3cf --- /dev/null +++ b/styles/prosilver/template/ucp_profile_reg_details.html @@ -0,0 +1,59 @@ + + +
+ +

{L_TITLE}

+
+
+ + +

{L_FORCE_PASSWORD_EXPLAIN}

+ + +
+

{ERROR}

+ +
+

{L_USERNAME_EXPLAIN}
+
{USERNAME}
+
+
+
+
{EMAIL}
+
+ +
+

{L_CHANGE_PASSWORD_EXPLAIN}
+
+
+
+

{L_CONFIRM_PASSWORD_EXPLAIN}
+
+
+ + +
+
+
+ +
+
+ +
+
+

{L_CURRENT_CHANGE_PASSWORD_EXPLAIN}{L_CURRENT_PASSWORD_EXPLAIN}
+
+
+
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/ucp_profile_signature.html b/styles/prosilver/template/ucp_profile_signature.html new file mode 100644 index 0000000..ed28b7a --- /dev/null +++ b/styles/prosilver/template/ucp_profile_signature.html @@ -0,0 +1,52 @@ + + +
+ +

{L_TITLE}

+ + +
+
+

{L_SIGNATURE_PREVIEW}

+
+
{SIGNATURE_PREVIEW}
+
+
+
+ + +
+
+ +

{L_SIGNATURE_EXPLAIN}

+ + + +

{L_OPTIONS}

+
+ {% EVENT ucp_profile_signature_posting_editor_options_prepend %} + +
+ + +
+ + +
+ + +
+ +
+
+ +
+ {S_HIDDEN_FIELDS} +   +   + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/ucp_register.html b/styles/prosilver/template/ucp_register.html new file mode 100644 index 0000000..bf39990 --- /dev/null +++ b/styles/prosilver/template/ucp_register.html @@ -0,0 +1,106 @@ + + + + +
+ +
+
+ +

{SITENAME} - {L_REGISTRATION}

+ +
+
{ERROR}
+ +
{L_REG_COND}
+ + +
+

{L_USERNAME_EXPLAIN}
+
+
+
+
+
+
+
+

{L_PASSWORD_EXPLAIN}
+
+
+
+
+
+
+ + +
+ + +
+
+
+
+ + + + + +
{L_ITEMS_REQUIRED}
+ + +
+
for="{profile_fields.FIELD_ID}">{profile_fields.LANG_NAME}{L_COLON} * +
{profile_fields.LANG_EXPLAIN} +
{profile_fields.ERROR}
+
{profile_fields.FIELD}
+
+ + + + +
+
+
+ + + + + + +
+
+ +

{L_COPPA_COMPLIANCE}

+ +

{L_COPPA_EXPLAIN}

+
+
+ + + + +
+
+ +
+ {S_HIDDEN_FIELDS} +   + + {S_FORM_TOKEN} +
+ +
+
+
+ + diff --git a/styles/prosilver/template/ucp_remind.html b/styles/prosilver/template/ucp_remind.html new file mode 100644 index 0000000..8b700de --- /dev/null +++ b/styles/prosilver/template/ucp_remind.html @@ -0,0 +1,37 @@ + + +
+ +
+
+ +
+

{L_SEND_PASSWORD}

+ +
+ {% if USERNAME_REQUIRED %} +

{{ lang('EMAIL_NOT_UNIQUE') }}

+ {% endif %} +
+

{L_EMAIL_REMIND}
+
+
+ {% if USERNAME_REQUIRED %} +
+
+
+
+ {% endif %} +
+
 
+
{S_HIDDEN_FIELDS} 
+
+ {S_FORM_TOKEN} +
+
+ +
+
+
+ + diff --git a/styles/prosilver/template/ucp_resend.html b/styles/prosilver/template/ucp_resend.html new file mode 100644 index 0000000..7713efe --- /dev/null +++ b/styles/prosilver/template/ucp_resend.html @@ -0,0 +1,32 @@ + + + +
+ +
+
+ +
+

{L_UCP_RESEND}

+ +
+
+
+
+
+
+

{L_EMAIL_REMIND}
+
+
+
+
 
+
{S_HIDDEN_FIELDS}{S_FORM_TOKEN} 
+
+
+
+ +
+
+
+ + diff --git a/styles/prosilver/template/ucp_zebra_foes.html b/styles/prosilver/template/ucp_zebra_foes.html new file mode 100644 index 0000000..2a0f6e0 --- /dev/null +++ b/styles/prosilver/template/ucp_zebra_foes.html @@ -0,0 +1,41 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +

{L_FOES_EXPLAIN}

+ +
+

{ERROR}

+
+

{L_YOUR_FOES_EXPLAIN}
+
+ + + + {L_NO_FOES} + +
+
+
+

{L_ADD_FOES_EXPLAIN}
+
+
{L_FIND_USERNAME}
+
+
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/ucp_zebra_friends.html b/styles/prosilver/template/ucp_zebra_friends.html new file mode 100644 index 0000000..e584d87 --- /dev/null +++ b/styles/prosilver/template/ucp_zebra_friends.html @@ -0,0 +1,43 @@ + + +
+ +

{L_TITLE}

+ +
+
+ +

{L_FRIENDS_EXPLAIN}

+ +
+

{ERROR}

+ +
+

{L_YOUR_FRIENDS_EXPLAIN}
+
+ + + + {L_NO_FRIENDS} + +
+
+ +
+

{L_ADD_FRIENDS_EXPLAIN}
+
+
{L_FIND_USERNAME}
+
+
+ +
+
+ +
+ {S_HIDDEN_FIELDS}  + + {S_FORM_TOKEN} +
+
+ + diff --git a/styles/prosilver/template/viewforum_body.html b/styles/prosilver/template/viewforum_body.html new file mode 100644 index 0000000..d7099f3 --- /dev/null +++ b/styles/prosilver/template/viewforum_body.html @@ -0,0 +1,313 @@ + + +

{FORUM_NAME}

+ + +
+ +
{FORUM_DESC}
+

{L_MODERATOR}{L_MODERATORS}{L_COLON} {MODERATORS}

+
+ + + +
+
+ + + {L_FORUM_RULES} + + {L_FORUM_RULES}
+ {FORUM_RULES} + + +
+
+ + + + + + + + + + +
+ + + + + + + {L_BUTTON_FORUM_LOCKED} + + {L_BUTTON_NEW_TOPIC} + + + + + + + + + + + +
+ + + + +
+
+ {L_NO_READ_ACCESS} +
+
+ + + +
+ +
+
+ +
+

{L_LOGIN_LOGOUT}  •  {L_REGISTER}

+ +
+
+
+
+
+
+
+
+
+
+
+
+
 
+
+
+ {S_LOGIN_REDIRECT} + {S_FORM_TOKEN_LOGIN} +
+
+ +
+
+ +
+ + + + + + + + + + + +
+
+ + + +
+
+
    +
  • +
    + id="active_topics">
    {L_ACTIVE_TOPICS}{L_ANNOUNCEMENTS}{L_TOPICS}
    +
    {L_REPLIES}
    +
    {L_VIEWS}
    +
    {L_LAST_POST}
    +
    +
  • +
+ +
+
+ + + + +
+
+ {L_NO_TOPICS} +
+
+ +
+
+ {L_NO_FORUMS_IN_CATEGORY} +
+
+ + + + +
+ + + + + + {L_BUTTON_FORUM_LOCKED} + + {L_BUTTON_NEW_TOPIC} + + + + + + + +
+ +
+ + + +
+ + + + + +
+

{L_WHO_IS_ONLINE}

+

{LOGGED_IN_USER_LIST}

+
+ + + +
+

{L_FORUM_PERMISSIONS}

+

{rules.RULE}

+
+ + + diff --git a/styles/prosilver/template/viewonline_body.html b/styles/prosilver/template/viewonline_body.html new file mode 100644 index 0000000..c019977 --- /dev/null +++ b/styles/prosilver/template/viewonline_body.html @@ -0,0 +1,63 @@ + + +

{TOTAL_REGISTERED_USERS_ONLINE}

+

{TOTAL_GUEST_USERS_ONLINE}{L_SWITCH_GUEST_DISPLAY}

+ +
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{L_USERNAME}{L_FORUM_LOCATION}{L_LAST_UPDATED}
{user_row.USERNAME_FULL} {L_IP}{L_COLON} {user_row.USER_IP} » {L_WHOIS} +
{user_row.USER_BROWSER}
{user_row.FORUM_LOCATION}{user_row.LASTUPDATE}
{L_NO_ONLINE_USERS}{L_SWITCH_GUEST_DISPLAY}
+ +
+
+ +

{L_LEGEND}{L_COLON} {LEGEND}

+ +
+ +
+ + + diff --git a/styles/prosilver/template/viewonline_whois.html b/styles/prosilver/template/viewonline_whois.html new file mode 100644 index 0000000..5d78049 --- /dev/null +++ b/styles/prosilver/template/viewonline_whois.html @@ -0,0 +1,12 @@ + + +

{L_WHOIS}

+ +
+
+
{WHOIS}
+
+
+{L_CLOSE_WINDOW} + + diff --git a/styles/prosilver/template/viewtopic_body.html b/styles/prosilver/template/viewtopic_body.html new file mode 100644 index 0000000..8d7e26f --- /dev/null +++ b/styles/prosilver/template/viewtopic_body.html @@ -0,0 +1,452 @@ + + + +

{TOPIC_TITLE}

+ + +
{FORUM_DESC}
+ + +

+ {L_MODERATOR}{L_MODERATORS}{L_COLON} {MODERATORS} +

+ + + +
+
+ + + {L_FORUM_RULES} + + {L_FORUM_RULES}
+ {FORUM_RULES} + + +
+
+ + +
+ + + + + + {L_BUTTON_TOPIC_LOCKED} + + {L_BUTTON_POST_REPLY} + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+ +
+

{POLL_QUESTION}

+

{L_POLL_LENGTH}
{L_MAX_VOTES}

+ +
+ + +
title="{L_POLL_VOTED_OPTION}" data-alt-text="{L_POLL_VOTED_OPTION}" data-poll-option-id="{poll_option.POLL_OPTION_ID}"> +
{poll_option.POLL_OPTION_CAPTION}
+
checked="checked" /> checked="checked" />
+
{poll_option.POLL_OPTION_RESULT}
+
{L_NO_VOTES}{poll_option.POLL_OPTION_PERCENT}
+
+ + + +
+
 
+
{L_TOTAL_VOTES}{L_COLON} {TOTAL_VOTES}
+
+ + +
+
 
+
+
+ + + +
+
 
+
{L_VIEW_RESULTS}
+
+ +
+ +
+ +
+ {S_FORM_TOKEN} + {S_HIDDEN_FIELDS} +
+ +
+
+ + + + + + + + data-url="{postrow.U_MINI_POST}"> + +
+
+ +
style="display: none;"> +
+
+ + + {postrow.POSTER_AVATAR}{postrow.POSTER_AVATAR} + + +
+ + {postrow.POST_AUTHOR_FULL}{postrow.POST_AUTHOR_FULL} + +
+ + +
{postrow.RANK_TITLE}
{postrow.RANK_IMG}
+ + +
{L_POSTS}{L_COLON} {postrow.POSTER_POSTS}
+
{L_JOINED}{L_COLON} {postrow.POSTER_JOINED}
+
{L_WARNINGS}{L_COLON} {postrow.POSTER_WARNINGS}
+ + + +
{postrow.PROFILE_FIELD1_NAME}{L_COLON} {postrow.PROFILE_FIELD1_VALUE}
+ + + + + +
{postrow.custom_fields.PROFILE_FIELD_NAME}{L_COLON} {postrow.custom_fields.PROFILE_FIELD_VALUE}
+ + + + + + +
+ {L_CONTACT}{L_COLON} + +
+ + + +
+ +
+ + +
+ {postrow.L_POST_DELETED_MESSAGE}
+ {postrow.L_POST_DISPLAY} +
+ +
+ {postrow.L_IGNORE_POST}
+ {postrow.L_POST_DISPLAY} +
+ + +
style="display: none;"> + + +

class="first">{postrow.POST_ICON_IMG_ALT} {postrow.POST_SUBJECT}

+ + + + + + + + + + + +

+ + {postrow.MINI_POST} + + + {postrow.MINI_POST} + + + {L_POST_BY_AUTHOR} {postrow.POST_AUTHOR_FULL} » {postrow.POST_DATE} +

+ + + +
+

+ + {L_POST_UNAPPROVED_ACTION} + + + + {S_FORM_TOKEN} +

+
+ +
+

+ {L_POST_DELETED_ACTION} + + + + + + {S_FORM_TOKEN} +

+
+ + + +

+ {L_POST_REPORTED} +

+ + +
{postrow.MESSAGE}
+ + + + +
+
+ {L_ATTACHMENTS} +
+ +
{postrow.attachment.DISPLAY_ATTACHMENT}
+ +
+ + + +
{L_DOWNLOAD_NOTICE}
+ +
+ {postrow.DELETED_MESSAGE} +
{L_REASON}{L_COLON} {postrow.DELETE_REASON} +
+ +
+ {postrow.EDITED_MESSAGE} +
{L_REASON}{L_COLON} {postrow.EDIT_REASON} +
+ + +


{postrow.BUMPED_MESSAGE}
+ +
{postrow.SIGNATURE}
+ + +
+ +
+ + + + + +
+
+ +
+ + + + + + + + +
+ + + + + + {L_BUTTON_TOPIC_LOCKED} + + {L_BUTTON_POST_REPLY} + + + + + + + + +
+ +
+ + + + + + + + + + + +
+ + + + + +
+

{L_WHO_IS_ONLINE}

+

{LOGGED_IN_USER_LIST}

+
+ + + diff --git a/styles/prosilver/template/viewtopic_print.html b/styles/prosilver/template/viewtopic_print.html new file mode 100644 index 0000000..b504949 --- /dev/null +++ b/styles/prosilver/template/viewtopic_print.html @@ -0,0 +1,46 @@ + + + + + + +{META} +{SITENAME} • {PAGE_TITLE} + + + + + + +
+ + + + +
+
{PAGE_NUMBER}
+ +
+

{postrow.POST_SUBJECT}

+
{L_POSTED}{L_COLON} {postrow.POST_DATE}
+
{L_POST_BY_AUTHOR} {postrow.POST_AUTHOR}
+
{postrow.MESSAGE}
+
+
+ +
+ + +
+ + + diff --git a/styles/prosilver/template/viewtopic_topic_tools.html b/styles/prosilver/template/viewtopic_topic_tools.html new file mode 100644 index 0000000..272a434 --- /dev/null +++ b/styles/prosilver/template/viewtopic_topic_tools.html @@ -0,0 +1,50 @@ + + + diff --git a/styles/prosilver/theme/base.css b/styles/prosilver/theme/base.css new file mode 100644 index 0000000..98c57d9 --- /dev/null +++ b/styles/prosilver/theme/base.css @@ -0,0 +1,115 @@ +/* -------------------------------------------------------------- + $Base +-------------------------------------------------------------- */ + +/** { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +}*/ + +/* Define your base font-size here; most elements will inherit this. _NO__DOTCOMMA__AFTER__*/ +html { + font-size: 1em; /* Assuming 16px... */ + line-height: 1.5; /* 24px (This is now our magic number; all subsequent margin-bottoms and line-heights want to be a multiple of this number in order to maintain vertical rhythm.) _NO__DOTCOMMA__AFTER__*/ + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #333333; + background-color: #ffffff; +} + +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +figure { margin: 0 } +img { vertical-align: middle } + +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #e5e5e5; +} + +a { + color: #428bca; + text-decoration: none; +} + +a:hover, +a:focus, +a:active { + color: #2a6496; + text-decoration: underline; +} + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +figure, +p, +pre { margin: 0 } +button { + background: transparent; + border: 0; + padding: 0; +} + +/** + * Work around a Firefox/IE bug where the transparent `button` background + * results in a loss of the default `button` focus styles. + */ +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +fieldset { + border: 0; + margin: 0; + padding: 0; +} + +iframe { border: 0 } +ol, +ul { + list-style: none; + margin: 0; + padding: 0; +} + +/** + * Suppress the focus outline on links that cannot be accessed via keyboard. + * This prevents an unwanted focus outline from appearing around elements that + * might still respond to pointer events. + */ +[tabindex="-1"]:focus { outline: none !important } + +/** + * Remove double underline from recent version of firefox + */ +abbr[title] { + text-decoration: none; +} + diff --git a/styles/prosilver/theme/bidi.css b/styles/prosilver/theme/bidi.css new file mode 100644 index 0000000..79b769b --- /dev/null +++ b/styles/prosilver/theme/bidi.css @@ -0,0 +1,1090 @@ +/* RTL definitions +---------------------------------------- */ + +/** +* common.css +*/ +.rtl h1 { + margin-right: 0; + margin-left: 200px; +} + +.rtl p.right { + text-align: left; +} + +.rtl p.jumpbox-return { + float: right; +} + +.rtl div.rules ul { + margin-left: 0; + margin-right: 20px; +} + +/* Main blocks +---------------------------------------- */ +.rtl .icon { + padding-right: 0; + padding-left: 2px; +} + +.rtl .logo { + float: right; + padding: 10px 10px 0 13px; +} + +/* Site Description +--------------------------------------------- */ +.rtl .site-description { + float: right; +} + +.rtl .site-description h1 { + margin-left: 0; +} + +/* Round cornered boxes and backgrounds +---------------------------------------- */ +.rtl .post { + background-position: 0 0; +} + +/* Horizontal lists +----------------------------------------*/ +.rtl ul.linklist > li { + float: right; + margin-right: 0; + margin-left: 7px; +} + +.rtl ul.linklist > li.rightside, .rtl p.rightside, .rtl a.rightside { + float: left; + margin-right: 7px; + margin-left: 0; + text-align: left; +} + +.rtl ul.leftside > li, .rtl ul.rightside > li { + float: left; +} + +.rtl ul.leftside { + float: right; + margin-left: 5px; + margin-right: 0; + text-align: right; +} + +.rtl ul.rightside { + float: left; + margin-left: -5px; + margin-right: 5px; + text-align: left; +} + +/* Bulletin icons for list items +----------------------------------------*/ +.rtl ul.linklist.bulletin > li:before { + padding-left: 4px; + padding-right: 0; +} + +/* Dropdown menu +---------------------------------------- */ +.rtl .dropdown-container.topic-tools, .rtl .dropdown-container-left { + float: right; +} + +.rtl .dropdown li { + text-align: right; +} + +.rtl .dropdown-contents > li { + padding-left: 15px; + padding-right: 0; +} + +.rtl .dropdown-nonscroll > li { + padding-left: 0; +} + +.rtl .dropdown li li { + padding-left: 0; + padding-right: 18px; +} + +.rtl .dropdown-extended .header { + text-align: right; +} + +.rtl .dropdown-extended .header .header_settings, .rtl .dropdown-container-right { + float: left; +} + +.rtl .jumpbox .dropdown-contents a { + margin-right: 0; + margin-left: 20px; +} + +/* Notifications +-----------------------------------------*/ +.rtl .notification_list ul li img { + float: right; + margin-left: 5px; + margin-right: 0; +} + +.rtl .notification_list div.notifications { + margin-left: 0; + margin-right: 50px; +} + +.rtl .notification_text { + margin-left: 0; + margin-right: 58px; +} + +.rtl .notification_list p.notification-time { + text-align: left; +} + +/* Responsive breadcrumbs +----------------------------------------*/ +.rtl .breadcrumbs .crumb { + float: right; +} + +/* Table styles +----------------------------------------*/ +.rtl table.table1 thead th { + padding: 0 3px 4px 0; +} + +.rtl table.table1 thead th span { + padding-left: 0; + padding-right: 7px; +} + +.rtl table.table1 tbody th { + text-align: right; +} + +/* Specific column styles */ +.rtl table.table1 .name { text-align: right; } +.rtl table.table1 .joined { text-align: right; } +.rtl table.table1 .active { text-align: right; } +.rtl table.table1 .info { text-align: right; } +.rtl table.table1 thead .autocol { padding-left: 0; padding-right: 1em; } + +.rtl table.table1 span.rank-img { + float: left; +} + +.rtl table.info tbody th { + text-align: left; +} + +.rtl .forumbg table.table1 { + margin: 0 -1px -1px -2px; +} + +/* Misc layout styles +---------------------------------------- */ +/* column[1-2] styles are containers for two column layouts */ +.rtl .column1 { + float: right; + clear: right; +} + +.rtl .column2 { + float: left; + clear: left; +} + +/* General classes for placing floating blocks */ +.rtl .left-box { + float: right; + text-align: right; +} + +.rtl .right-box { + float: left; + text-align: left; +} + +.rtl dl.details dt { + float: right; + clear: right; + text-align: left; +} + +.rtl dl.details dd { + margin-right: 0; + margin-left: 0; + padding-right: 5px; + padding-left: 0; + float: right; +} + +*:first-child+html dl.details dd { + margin-right: 30%; + float: none; +} + +* html dl.details dd { + margin-right: 30%; + float: none; +} + +/* Pagination +---------------------------------------- */ +.rtl .page-number { + float: left; +} + +.rtl .pagination { + text-align: left; + float: left; +} + +.rtl .pagination > ul { + margin-left: 0; + margin-right: 5px; +} + +/* Pagination in viewforum for multipage topics */ +.rtl .row .pagination { + background-position: 100% 50%; + float: left; + padding-left: 0; + padding-right: 15px; +} + +.rtl .row .pagination > ul { + margin: 0; +} + +.rtl .pagination span { + direction: ltr; +} + +.pagination li.page-jump { + margin-left: 5px; + margin-right: 0; +} + +/* Action Bar styles +---------------------------------------- */ +.rtl .action-bar .button { + margin-right: 0; + float: right; +} + +.rtl .action-bar > .button { + margin-left: 5px; + float: right; +} + +.rtl .action-bar .dropdown-button-control .button { + margin-left: 5px; +} + + +/* Miscellaneous styles +---------------------------------------- */ +.rtl .quick-links { + margin-left: 7px; + margin-right: 0; +} + +.rtl .header-avatar span:after { + float: left; + padding-left: 0; + padding-right: 2px; +} + +.rtl .member-search { + float: right; +} + +/** +* links.css +*/ + +/* Links adjustment to correctly display an order of rtl/ltr mixed content */ +.rtl a { + direction: rtl; + unicode-bidi: embed; +} + +li.breadcrumbs span:first-child > a { + padding-left: 0; +} + +/* Notification mark read link */ +.rtl .dropdown-extended a.mark_read { + border-radius: 0 3px 3px 0; + left: 0; + right: auto; +} + +.rtl .back2top .top { + float: left; + margin-left: -10px; +} + +.rtl .skiplink { + /* invisible skip link, used for accessibility */ + left: 0; + right: -999px; +} + +.rtl a.feed-icon-forum { + float: left; +} + +/** +* content.css +*/ +.rtl ul.topiclist dt, .rtl li.header dt { + float: right; + margin-right: 0; + margin-left: -440px; +} + +.rtl ul.topiclist.missing-column dt { + margin-right: 0; + margin-left: -345px; +} + +.rtl ul.topiclist.two-long-columns dt { + margin-right: 0; + margin-left: -250px; +} + +.rtl ul.topiclist.two-columns dt { + margin-right: 0; + margin-left: -80px; +} + +.rtl ul.topiclist dt .list-inner { + margin-right: 0; + margin-left: 440px; +} + +.rtl ul.topiclist.missing-column dt .list-inner { + margin-right: 0; + margin-left: 330px; +} + +.rtl ul.topiclist.two-long-columns dt .list-inner { + margin-right: 0; + margin-left: 250px; +} + +.rtl ul.topiclist.two-columns dt .list-inner { + margin-right: 0; + margin-left: 80px; +} + +.rtl ul.topiclist dd { + float: right; + border-right-width: 1px; + border-right-style: solid; + border-left: none; +} + +.rtl ul.topiclist dfn { + left: auto; + right: -999px; +} + +.rtl ul.topiclist li.row dt a.subforum { + padding-right: 12px; + background-position: right; + position: static; +} + +.rtl .forum-image { + float: right; + margin-right: 0; + margin-left: 5px; +} + +.rtl li.header dt, .rtl li.header dd { + border-right-width: 0; +} + +.rtl li.header dd { + padding-left: 0; + padding-right: 1px; +} + +.rtl dl.row-item{ + background-position: 99.5% 50%; +} + +.rtl li.header dl.row-item dt .list-inner { + /* Tweak for headers alignment when folder icon used */ + padding-right: 0; + padding-left: 50px; +} + +.rtl dl.row-item dt { + background-position: 99.5% 95%; /* Position of topic icon */ +} + +.rtl dl.row-item dt .list-inner { + padding-left: 5px; + padding-right: 45px; /* Space for folder icon */ +} + +.rtl dl a.row-item-link { /* topic row icon links */ + display: inline-block; + left: auto; + right: 0; + margin-left: 0; + margin-right: 2px; +} + +.rtl dd.lastpost > span, .rtl ul.topiclist dd.info > span, .rtl ul.topiclist dd.time > span, .rtl dd.redirect > span, .rtl dd.moderation > span { + padding-left: 0; + padding-right: 5px; +} + +/* Post body styles +----------------------------------------*/ +.rtl .date { + float: left; +} + +.rtl .postbody, .rtl .postbody h3 { + float: right; +} + +.rtl .has-profile .postbody h3 { + margin-right: 0; + margin-left: 180px; +} + +.rtl p.post-notice { + padding-left: 5px; +} + +.rtl p.post-notice:before { + left: auto; + right: 0; +} + +/* Topic review panel +----------------------------------------*/ +.rtl .topicreview { + padding-right: 0; + padding-left: 5px; +} + +/* Content container styles +----------------------------------------*/ +.rtl .content ul, .rtl .content ol { + margin-right: 3em; + margin-left: 0; +} + +.rtl .signature { + clear: right; +} + +.rtl .notice { + clear: right; +} + +/* Jump to post link for now */ +.rtl ul.searchresults { + text-align: left; +} + +/* BB Code styles +----------------------------------------*/ +/* Quote block */ +.rtl blockquote { + margin: 0.5em 25px 0 1px; +} + +.rtl blockquote blockquote { + /* Nested quotes */ + margin: 0.5em 15px 0 1px; +} + +.rtl blockquote cite { + /* Username/source of quoter */ + margin-left: 0; +} + +.rtl blockquote cite:before, .rtl .uncited:before { + padding-left: 5px; +} + +.rtl blockquote .codebox { + margin-right: 0; +} + +.rtl code { + direction: ltr; +} + +/* Attachments +----------------------------------------*/ +.rtl .attachbox { + float: right; + margin: 5px 0 5px 5px; + clear: right; +} + +.rtl .attachbox dd { + clear: right; +} + +.rtl .attachbox p { + clear: right; +} + +.rtl .attachbox p.stats { + clear: right; +} + +/* Post poll styles +----------------------------------------*/ +.rtl fieldset.polls dt { + text-align: right; + float: right; + border-left: none; +} + +.rtl fieldset.polls dd { + float: right; + border-right: none; + margin-right: 0; +} + +.rtl fieldset.polls dd div { + text-align: left; +} + +.rtl .pollbar1, .rtl .pollbar2, .rtl .pollbar3, .rtl .pollbar4, .rtl .pollbar5 { + border-left-width: 1px; + border-left-style: solid; + border-right: none; +} + +/* Poster profile block +----------------------------------------*/ +.rtl .postprofile { + border-width: 0 1px 0 0; + float: left; +/* text-align: right; */ +} + +.rtl .pm .postprofile { + border-right-width: 1px; + border-right-style: solid; + border-left: none; +} + +.rtl .postprofile dd, .rtl .postprofile dt { + margin-left: 0; + margin-right: 8px; +} + +.rtl .postprofile .avatar { + float: right; +} + +.rtl .online { + background-position: 0 0; +} + +.rtl dl.pmlist dd { + margin-right: 61% !important; + margin-left: 0 !important; +} + +/** +* buttons.css +*/ + +.rtl .caret { + border-right: 1px solid; + border-right-color: inherit; + border-left: none; + right: 6px; +} + +/* Post control buttons +--------------------------------------------- */ +.rtl .post-buttons { + float: left; +} + +.rtl .has-profile .post-buttons { + left: 0; + right: auto; +} + +.rtl .post-buttons li { + float: right; +} + +/* Poster contact icons + ----------------------------------------*/ +.rtl .contact-icons a { + border-left-width: 1px; + border-left-style: dotted; + border-right: none; + float: right; +} + +.rtl .contact-icons .last-cell { + border-left: none; +} + +/** +* cp.css +*/ +/* Control Panel Styles +---------------------------------------- */ + + +/* Main CP box +----------------------------------------*/ +.rtl .cp-menu { + float: right; +} + +.rtl .cp-main { + float: right; +} + +.rtl .cp-main .panel ol { + margin-right: 2em; + margin-left: 0; +} + +.rtl .cp-main .buttons { + margin-right: 0; + margin-left: 0; +} + +.tabs-container h2 { + float: right; +} + +/* CP tabbed menu +----------------------------------------*/ +.rtl .tabs { + margin-left: 0; + margin-right: 7px; +} + +.rtl .tabs .tab { + float: right; +} + +.rtl .tabs .tab > a { + margin-left: 1px; + margin-right: 0; +} + +/* Mini tabbed menu used in MCP +----------------------------------------*/ +.rtl .minitabs { + float: left; + margin-right: 0; + margin-left: 7px; +} + +.rtl .minitabs .tab { + float: left; +} + +.rtl .minitabs .tab > a { + margin-right: 2px; + margin-left: 0; +} + +/* Responsive tabs +----------------------------------------*/ +.rtl .tabs .dropdown { + margin-left: -2px; +} + +.rtl .tabs .dropdown li { + text-align: left; +} + +.rtl .minitabs .dropdown { + margin-left: -4px; +} + +.rtl .minitabs .dropdown li { + text-align: right; +} + +/* Responsive *CP navigation +----------------------------------------*/ +@media only screen and (max-width: 900px), only screen and (max-device-width: 900px) +{ + .rtl .cp-menu, .rtl .navigation, .rtl .cp-main { + float: none; + } +} + +/* UCP navigation menu +----------------------------------------*/ + +/* Preferences pane layout +----------------------------------------*/ +.rtl .cp-main h2 { + margin-left: 0; + margin-right: 10px; +} + +/* Friends list */ +.rtl .cp-mini { + margin: 10px 5px 10px 15px; +} + +/* PM Styles +----------------------------------------*/ + +/* PM panel adjustments */ +.rtl .reply-all a.right { + background-position: 5% 60%; +} + +.rtl .reply-all a.right:hover { + background-position: 3% 60%; +} + +.rtl .reply-all { + padding-left: 5px; +} + +/* Defined rules list for PM options */ +.rtl ol.def-rules { + padding-right: 0; +} + +/* PM marking colours */ +.rtl .pm-legend { + border-right-width: 10px; + border-right-style: solid; + border-left-width: 0; + padding-left: 0; + padding-right: 3px; +} + +/* Avatar gallery */ +.rtl .gallery label { + float: right; +} + +/* Responsive *CP navigation +----------------------------------------*/ +@media only screen and (max-width: 900px), only screen and (max-device-width: 900px) +{ + .rtl .cp-menu, .rtl .navigation, .rtl .cp-main { + float: none; + } +} + +/** +* forms.css +*/ + +/* General form styles +----------------------------------------*/ + +.rtl option { + padding-right: 0; + padding-left: 1em; +} + +.rtl label { + padding-right: 0; + padding-left: 5px; +} + +/* Definition list layout for forms +---------------------------------------- */ +.rtl fieldset dt { + float: right; + text-align: right; +} + +.rtl fieldset dd { + margin-left: 0; + margin-right: 41%; +} + +/* Specific layout 1 */ +.rtl fieldset.fields1 dt { + border-left-width: 0; + border-right-width: 1px; +} + +.rtl fieldset.fields1 dd { + margin-right: 15em; + margin-left: 0; + border-right-width: 0; + border-left-width: 1px; +} + +/* Specific layout 2 */ +.rtl fieldset.fields2 dt { + border-right-width: 1px; + border-left-width: 0; +} + +.rtl fieldset.fields2 dd { + margin-right: 16em; + margin-left: 0; + border-left-width: 1px; + border-right-width: 0; +} + +/* Form elements */ +.rtl dt label { + text-align: right; +} + +.rtl dd input, .rtl dd textarea { + margin-left: 3px; + margin-right: 0; +} + +/* Quick-login on index page */ +.rtl fieldset.quick-login input.inputbox { + margin-left: 5px; + margin-right: 0; +} + +.rtl fieldset.quick-login label { + padding-left: 2px; + padding-right: 0; +} + +/* Display options on viewtopic/viewforum pages */ +.rtl fieldset.display-options label { + padding-left: 2px; + padding-right: 0; +} + +.rtl .dropdown fieldset.display-options label { + text-align: left; +} + +/* Display actions for ucp and mcp pages */ +.rtl fieldset.display-actions { + text-align: left; + padding-left: 1em; + padding-right: 0; +} + +.rtl fieldset.display-actions label { + padding-left: 2px; + padding-right: 0; +} + +/* MCP forum selection*/ +.rtl fieldset.forum-selection { + float: left; +} + +.rtl fieldset.forum-selection2 { + float: left; +} + +/* Posting page styles +----------------------------------------*/ + +/* Emoticons panel */ +.rtl .smiley-box { + float: left; +} + +/* Search box +---------------------------------------- */ + +/* Topic and forum Search */ +.rtl .search-box { + float: right; +} + +.rtl .search-box .inputbox { + border-left-width: 0; + border-right-width: 1px; + border-radius: 0 4px 4px 0; + float: right; + padding: 3px; +} + +.rtl .button-search, +.button-search-end { + float: right; +} + +.rtl .button-search-end { + border-radius: 4px 0 0 4px; + border-left-width: 1px; + border-right-width: 0; +} + +.rtl .search-header .button-search-end { + border: 0; + border-radius: 4px 0 0 4px; +} + +.rtl .search-header { + float: left; + margin-right: 0; + margin-left: 5px; +} + +/* Form button styles +---------------------------------------- */ + +/** Reference: Bug #27155 */ +.rtl .wrap, .rtl .headerbar, .rtl .site-description, .rtl .navbar { + position: relative; +} + +/** +* plupload.css +*/ + +.rtl .attach-controls { + float: left; +} + +/** +* responsive.css +*/ +@media only screen and (max-width: 700px), only screen and (max-device-width: 700px) +{ + /* .topiclist lists + ----------------------------------------*/ + .rtl ul.topiclist li.header dt, .rtl ul.topiclist li.header dt .list-inner { + margin-left: 0 !important; + padding-left: 0; + } + + .rtl ul.topiclist dt, .rtl ul.topiclist dt .list-inner, + .rtl ul.topiclist.missing-column dt, .rtl ul.topiclist.missing-column dt .list-inner, + .rtl ul.topiclist.two-long-columns dt, .rtl ul.topiclist.two-long-columns dt .list-inner, + .rtl ul.topiclist.two-columns dt, .rtl ul.topiclist.two-columns dt .list-inner { + margin-left: 0; + } + + .rtl ul.topiclist dt .list-inner.with-mark { + padding-left: 34px; + } + + /* Forums and topics lists + ----------------------------------------*/ + .rtl ul.topiclist.forums dt { + margin-left: -250px; + } + .rtl ul.topiclist.forums dt .list-inner { + margin-left: 250px; + } + + .rtl ul.topiclist dd.mark { + left: 5px; + right: auto; + text-align: right; + } + + .rtl table.responsive.show-header thead, .rtl table.responsive.show-header th:first-child { + text-align: right !important; + } + + .rtl table.responsive td { + text-align: right !important; + } + + /* User profile + ----------------------------------------*/ + .rtl .column1, .rtl .column2, .rtl .left-box.profile-details { + float: none; + } + + /* Post + ----------------------------------------*/ + .rtl .postprofile, .rtl .postbody, .rtl .search .postbody { + float: none; + } + + .rtl .post .postprofile { + border-width: 0 0 1px 0; + } + + .rtl .postprofile dt, .rtl .postprofile dd.profile-rank, .rtl .search .postprofile dd { + margin: 0; + } + + .rtl .postprofile .avatar { + margin-left: 5px; + margin-right: 0; + } + + .rtl .has-profile .post-buttons { + left: 20px; + } + + /* Forms + ----------------------------------------*/ + .rtl fieldset dt, .rtl fieldset.fields1 dt, .rtl fieldset.fields2 dt { + float: none; + } + + .rtl fieldset dd, .rtl fieldset.fields1 dd, .rtl fieldset.fields2 dd { + margin-right: 20px; + } +} + +@media only screen and (max-width: 550px), only screen and (max-device-width: 550px) +{ + /* .topiclist lists + ----------------------------------------*/ + .rtl ul.topiclist.forums dt { + margin-left: 0; + } + + .rtl ul.topiclist.forums dt .list-inner { + margin-left: 0; + } +} + +@media only screen and (max-width: 500px), only screen and (max-device-width: 500px) +{ + .rtl dl.details dt, .rtl dl.details dd { + float: none; + text-align: right; + } + + .rtl dl.details dd { + margin-left: 0; + margin-right: 20px; + } + + .captcha-panel dd.captcha { + margin-right: 0; + } + + .rtl p.responsive-center { + float: none; + text-align: center; + margin-bottom: 5px; + } +} diff --git a/styles/prosilver/theme/buttons.css b/styles/prosilver/theme/buttons.css new file mode 100644 index 0000000..575c41a --- /dev/null +++ b/styles/prosilver/theme/buttons.css @@ -0,0 +1,193 @@ +/* Button Styles +---------------------------------------- */ + +.button { + display: inline-block; + padding: 2px 8px; + font-size: 13px; + font-weight: 600; + font-family: "Open Sans", "Droid Sans", Verdana, Arial, Helvetica; + line-height: 1.4; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 1px solid transparent; + border-radius: 4px; +} + +.button:focus, +.button:hover { + text-decoration: none; + outline: none; +} + +.caret { + border-left: 1px solid; + position: relative; + right: -6px; +} + +.caret i { + vertical-align: top; +} + +/* Posting page styles +----------------------------------------*/ +.button-search, +.button-search-end { + float: left; + border-radius: 0; + margin: 0; + padding: 2px 5px; +} + +.button-search-end { + border-left-width: 0; + border-radius: 0 4px 4px 0; +} + +.search-header .button-search, +.search-header .button-search-end { + border-top-width: 0; + border-bottom-width: 0; + padding: 3px 5px; +} + +.search-header .button-search-end { + border-right-width: 0; +} + +.button-icon-only { + padding-left: 3px; + padding-right: 3px; +} + +/* Poster contact icons +----------------------------------------*/ +.contact-icons.dropdown-contents { + min-width: 0; + padding: 0; + font-size: 0; +} + +.contact-icon { + background-repeat: no-repeat; + display: block; + height: 16px; + width: 16px; +} +.contact-icons a { + border-bottom: 1px dotted; + border-right: 1px dotted; + display: block; + float: left; + padding: 8px; +} + +.contact-icons .last-cell { + border-right: none; +} + +.contact-icons div:last-child a { + border-bottom: none; +} + +.contact-icons div { + clear: left; +} + +/* Post control buttons +--------------------------------------------- */ +.post-buttons { + float: right; + list-style: none; + margin-top: 2px; +} + +.has-profile .post-buttons { + float: none; + position: absolute; + margin: 0; + right: 0; + top: 5px; +} + +.post-buttons > li { + float: left; + margin-right: 3px; +} + +.post-buttons .button, .format-buttons .button { + padding-left: 3px; + padding-right: 3px; +} + +.hastouch .post-buttons { + margin-right: 10px; +} + +.post-buttons .button span { + font-size: 0; +} + +/* Responsive buttons in post body */ +.post-buttons .dropdown { + top: 18px; +} + +.post-buttons .dropdown a { + display: block; + font-size: 1.2em; + text-align: right; +} + +.hasjs .postbody .post-buttons { + max-width: 40%; +} + +/* Browser-specific tweaks */ +button::-moz-focus-inner { + padding: 0; + border: 0 +} + +/* Deprecated as of version 3.2 +-------------------------------------------------*/ +.small-icon { + background-position: 0 50%; + background-repeat: no-repeat; + background-image: none; +} + +.dropdown .small-icon { + background-position: 5px 50%; + padding: 5px; +} + +.small-icon > a { + padding: 0 0 0 18px; +} + +ul.linklist.bulletin > li.small-icon:before { + display: none; +} + +.dropdown .small-icon > a { + display: block; +} + +.rtl .small-icon { + background-position: 100% 50%; +} + +.rtl .small-icon > a { + padding-left: 0; + padding-right: 19px; +} diff --git a/styles/prosilver/theme/colours.css b/styles/prosilver/theme/colours.css new file mode 100644 index 0000000..ffaa710 --- /dev/null +++ b/styles/prosilver/theme/colours.css @@ -0,0 +1,1163 @@ +/* +-------------------------------------------------------------- +Colours and backgrounds for common.css +-------------------------------------------------------------- */ + +html, body { + color: #536482; + background-color: #F5F7FA; +} + +h1 { + color: #FFFFFF; +} + +h2 { + color: #28313F; +} + +h3 { + border-bottom-color: #CCCCCC; + color: #115098; +} + +hr { + border-color: #FFFFFF; + border-top-color: #CCCCCC; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for links.css +-------------------------------------------------------------- */ + +a { color: #105289; } +a:hover { color: #D31141; } + +/* Links on gradient backgrounds */ +.forumbg .header a, .forabg .header a, th a { + color: #FFFFFF; +} + +.forumbg .header a:hover, .forabg .header a:hover, th a:hover { + color: #A8D8FF; +} + +/* Notification mark read link */ +.dropdown-extended a.mark_read { + background-color: #FFFFFF; +} + +/* Post body links */ +.postlink { + border-bottom-color: #368AD2; + color: #368AD2; +} + +.postlink:visited { + border-bottom-color: #5D8FBD; + color: #5D8FBD; +} + +.postlink:hover { + background-color: #D0E4F6; + color: #0D4473; +} + +.signature a, .signature a:hover { + background-color: transparent; +} + +/* Back to top of page */ +.top i { + color: #999999; +} + +/* Arrow links */ +.arrow-left:hover, .arrow-right:hover { + color: #368AD2; +} + +/* Round cornered boxes and backgrounds +---------------------------------------- */ +.wrap { + background-color: #FFF; + border-color: #E6E9ED; +} + +.headerbar { + color: #FFFFFF; +} + +.headerbar, .forumbg { + background-color: #12A3EB; + background-image: -webkit-linear-gradient(top, #6ACEFF 0%, #0076B1 2px, #12A3EB 92px, #12A3EB 100%); + background-image: linear-gradient(to bottom, #6ACEFF 0%,#0076B1 2px,#12A3EB 92px,#12A3EB 100%); + background-repeat: repeat-x; +} + +.forabg { + background-color: #0076B1; + background-image: -webkit-linear-gradient(top, #6ACEFF 0%, #12A3EB 2px, #0076B1 92px, #0076B1 100%); + background-image: linear-gradient(to bottom, #6ACEFF 0%,#12A3EB 2px,#0076B1 92px,#0076B1 100%); + background-repeat: repeat-x; +} + +.navbar { + background-color: #CADCEB; +} + +.panel { + background-color: #ECF1F3; + color: #28313F; +} + +.post:target .content { + color: #000000; +} + +.post:target h3 a { + color: #000000; +} + +.bg1 { + background-color: #ECF3F7; +} + +table.zebra-list tr:nth-child(odd) td, ul.zebra-list li:nth-child(odd) { + background-color: #ECF3F7; +} + +.bg2 { + background-color: #E1EBF2; +} + +table.zebra-list tr:nth-child(even) td, ul.zebra-list li:nth-child(even) { + background-color: #E1EBF2; +} + +.bg3 { + background-color: #CADCEB; +} + +.ucprowbg { + background-color: #DCDEE2; +} + +.fieldsbg { + background-color: #E7E8EA; +} + +.site_logo { + background-image: url("./images/site_logo.gif"); +} + +/* Horizontal lists +----------------------------------------*/ + +ul.navlinks { + border-top-color: #FFFFFF; +} + +/* Table styles +----------------------------------------*/ +table.table1 thead th { + color: #FFFFFF; +} + +table.table1 tbody tr { + border-color: #BFC1CF; +} + +table.table1 tbody tr:hover, table.table1 tbody tr.hover { + background-color: #CFE1F6; + color: #000; +} + +table.table1 td { + color: #536482; +} + +table.table1 tbody td { + border-top-color: #FAFAFA; +} + +table.table1 tbody th { + border-bottom-color: #000000; + color: #333333; + background-color: #FFFFFF; +} + +table.info tbody th { + color: #000000; +} + +/* Misc layout styles +---------------------------------------- */ +dl.details dt { + color: #000000; +} + +dl.details dd { + color: #536482; +} + +.sep { + color: #1198D9; +} + +/* Icon styles +---------------------------------------- */ +.icon.icon-blue, a:hover .icon.icon-blue { + color: #196db5; +} + +.icon.icon-green, a:hover .icon.icon-green{ + color: #1b9A1B; +} + +.icon.icon-red, a:hover .icon.icon-red{ + color: #BC2A4D; +} + +.icon.icon-orange, a:hover .icon.icon-orange{ + color: #FF6600; +} + +.icon.icon-bluegray, a:hover .icon.icon-bluegray{ + color: #536482; +} + +.icon.icon-gray, a:hover .icon.icon-gray{ + color: #777777; +} + +.icon.icon-lightgray, a:hover .icon.icon-lightgray{ + color: #999999; +} + +.icon.icon-black, a:hover .icon.icon-black{ + color: #333333; +} + +.alert_close .icon:before { + background-color: #FFFFFF; +} + +/* Jumpbox */ +.jumpbox .dropdown li { + border-top-color: #CCCCCC; +} + +.jumpbox-cat-link { + background-color: #0076b1; + border-top-color: #0076B1; + color: #FFFFFF; +} + +.jumpbox-cat-link:hover { + background-color: #12A3EB; + border-top-color: #12A3EB; + color: #FFFFFF; +} + +.jumpbox-forum-link { + background-color: #E1EBF2; +} + +.jumpbox-forum-link:hover { + background-color: #F6F4D0; +} + +.jumpbox .dropdown .pointer-inner { + border-color: #E1EBF2 transparent; +} + +.jumpbox-sub-link { + background-color: #E1EBF2; +} + +.jumpbox-sub-link:hover { + background-color: #F1F8FF; +} + +/* Miscellaneous styles +---------------------------------------- */ + +.copyright { + color: #555555; +} + +.error { + color: #BC2A4D; +} + +.reported { + background-color: #F7ECEF; +} + +li.reported:hover { + background-color: #ECD5D8 !important; +} +.sticky, .announce { + /* you can add a background for stickies and announcements*/ +} + +div.rules { + background-color: #ECD5D8; + color: #BC2A4D; +} + +p.post-notice { + background-color: #ECD5D8; + background-image: none; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for content.css +-------------------------------------------------------------- */ + +ul.forums { + background-color: #EEF5F9; /* Old browsers */ /* FF3.6+ */ + background-image: -webkit-linear-gradient(top, #D2E0EB 0%, #EEF5F9 100%); + background-image: linear-gradient(to bottom, #D2E0EB 0%,#EEF5F9 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#D2E0EB', endColorstr='#EEF5F9',GradientType=0 ); /* IE6-9 */ +} + +ul.topiclist li { + color: #4C5D77; +} + +ul.topiclist dd { + border-left-color: #FFFFFF; +} + +.rtl ul.topiclist dd { + border-right-color: #FFFFFF; + border-left-color: transparent; +} + +li.row { + border-top-color: #FFFFFF; + border-bottom-color: #00608F; +} + +li.row strong { + color: #000000; +} + +li.row:hover { + background-color: #F6F4D0; +} + +li.row:hover dd { + border-left-color: #CCCCCC; +} + +.rtl li.row:hover dd { + border-right-color: #CCCCCC; + border-left-color: transparent; +} + +li.header dt, li.header dd { + color: #FFFFFF; +} + +/* Post body styles +----------------------------------------*/ +.postbody { + color: #333333; +} + +/* Content container styles +----------------------------------------*/ +.content { + color: #333333; +} + +.content h2, .panel h2 { + color: #115098; + border-bottom-color: #CCCCCC; +} + +dl.faq dt { + color: #333333; +} + +.posthilit { + background-color: #F3BFCC; + color: #BC2A4D; +} + +.announce, .unreadpost { + /* Highlight the announcements & unread posts box */ +} + +/* Post signature */ +.signature { + border-top-color: #CCCCCC; +} + +/* Post noticies */ +.notice { + border-top-color: #CCCCCC; +} + +/* BB Code styles +----------------------------------------*/ +/* Quote block */ +blockquote { + background-color: #EBEADD; + border-color:#DBDBCE; +} + +blockquote blockquote { + /* Nested quotes */ + background-color:#EFEED9; +} + +blockquote blockquote blockquote { + /* Nested quotes */ + background-color: #EBEADD; +} + +/* Code block */ +.codebox { + background-color: #FFFFFF; + border-color: #C9D2D8; +} + +.codebox p { + border-bottom-color: #CCCCCC; +} + +.codebox code { + color: #2E8B57; +} + +/* Attachments +----------------------------------------*/ +.attachbox { + background-color: #FFFFFF; + border-color: #C9D2D8; +} + +.pm-message .attachbox { + background-color: #F2F3F3; +} + +.attachbox dd { + border-top-color: #C9D2D8; +} + +.attachbox p { + color: #666666; +} + +.attachbox p.stats { + color: #666666; +} + +.attach-image img { + border-color: #999999; +} + +/* Inline image thumbnails */ + +dl.file dd { + color: #666666; +} + +dl.thumbnail img { + border-color: #666666; + background-color: #FFFFFF; +} + +dl.thumbnail dd { + color: #666666; +} + +dl.thumbnail dt a:hover { + background-color: #EEEEEE; +} + +dl.thumbnail dt a:hover img { + border-color: #368AD2; +} + +/* Post poll styles +----------------------------------------*/ + +fieldset.polls dl { + border-top-color: #DCDEE2; + color: #666666; +} + +fieldset.polls dl.voted { + color: #000000; +} + +fieldset.polls dd div { + color: #FFFFFF; +} + +.rtl .pollbar1, .rtl .pollbar2, .rtl .pollbar3, .rtl .pollbar4, .rtl .pollbar5 { + border-right-color: transparent; +} + +.pollbar1 { + background-color: #AA2346; + border-bottom-color: #74162C; + border-right-color: #74162C; +} + +.rtl .pollbar1 { + border-left-color: #74162C; +} + +.pollbar2 { + background-color: #BE1E4A; + border-bottom-color: #8C1C38; + border-right-color: #8C1C38; +} + +.rtl .pollbar2 { + border-left-color: #8C1C38; +} + +.pollbar3 { + background-color: #D11A4E; + border-bottom-color: #AA2346; + border-right-color: #AA2346; +} + +.rtl .pollbar3 { + border-left-color: #AA2346; +} + +.pollbar4 { + background-color: #E41653; + border-bottom-color: #BE1E4A; + border-right-color: #BE1E4A; +} + +.rtl .pollbar4 { + border-left-color: #BE1E4A; +} + +.pollbar5 { + background-color: #F81157; + border-bottom-color: #D11A4E; + border-right-color: #D11A4E; +} + +.rtl .pollbar5 { + border-left-color: #D11A4E; +} + +/* Poster profile block +----------------------------------------*/ +.postprofile { + color: #666666; + border-color: #FFFFFF; +} + +.pm .postprofile { + border-color: #DDDDDD; +} + +.postprofile strong { + color: #000000; +} + +.online { + background-image: url("./en/icon_user_online.gif"); +} + +dd.profile-warnings { + color: #BC2A4D; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for buttons.css +-------------------------------------------------------------- */ +.button { + border-color: #C7C3BF; + background-color: #E9E9E9; /* Old browsers */ /* FF3.6+ */ + background-image: -webkit-linear-gradient(top, #FFFFFF 0%, #E9E9E9 100%); + background-image: linear-gradient(to bottom, #FFFFFF 0%,#E9E9E9 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#E9E9E9',GradientType=0 ); /* IE6-9 */ + box-shadow: 0 0 0 1px #FFFFFF inset; + -webkit-box-shadow: 0 0 0 1px #FFFFFF inset; + color: #D31141; +} + +.button:hover, +.button:focus { + border-color: #0A8ED0; + background-color: #FFFFFF; /* Old browsers */ /* FF3.6+ */ + background-image: -webkit-linear-gradient(top, #E9E9E9 0%, #FFFFFF 100%); + background-image: linear-gradient(to bottom, #E9E9E9 0%,#FFFFFF 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#E9E9E9', endColorstr='#FFFFFF',GradientType=0 ); /* IE6-9 */ + text-shadow: 1px 1px 0 #FFFFFF, -1px -1px 0 #FFFFFF, -1px -1px 0 rgba(188, 42, 77, 0.2); +} + + +.button .icon, +.button-secondary { + color: #8f8f8f; +} + +.button-secondary:focus, +.button-secondary:hover, +.button:focus .icon, +.button:hover .icon { + color: #0A8ED0; +} + +.button-search:hover, +.button-search-end:hover { + border-color: #C7C3BF; +} + +.caret { border-color: #DADADA; } +.caret { border-color: #C7C3BF; } + +.contact-icons a { border-color: #DCDCDC; } +.contact-icons a:hover { background-color: #F2F6F9; } + +/* Pagination +---------------------------------------- */ + +.pagination li a { + background: #ECEDEE; + filter: none; + border-color: #B4BAC0; + box-shadow: none; + -webkit-box-shadow: none; + color: #5C758C; +} + +.pagination li.ellipsis span { + background: transparent; + color: #000000; +} + +.pagination li.active span { + background: #4692BF; + border-color: #4692BF; + color: #FFFFFF; +} + +.pagination li a:hover, .pagination li a:hover .icon, .pagination .dropdown-visible a.dropdown-trigger, .nojs .pagination .dropdown-container:hover a.dropdown-trigger { + background: #368AD2; + border-color: #368AD2; + filter: none; + color: #FFFFFF; + text-shadow: none; +} + +/* Search box +--------------------------------------------- */ + +.search-box .inputbox, +.search-box .inputbox:hover, +.search-box .inputbox:focus { + border-color: #C7C3BF; +} + +.search-header { + box-shadow: 0 0 10px #0075B0; +} + +/* Icon images +---------------------------------------- */ + +.contact-icon { background-image: url("./images/icons_contact.png"); } + +/* Profile & navigation icons */ +.pm-icon { background-position: 0 0; } +.email-icon { background-position: -21px 0; } +.jabber-icon { background-position: -80px 0; } +.phpbb_icq-icon { background-position: -61px 0 ; } +.phpbb_wlm-icon { background-position: -182px 0; } +.phpbb_aol-icon { background-position: -244px 0; } +.phpbb_website-icon { background-position: -40px 0; } +.phpbb_youtube-icon { background-position: -98px 0; } +.phpbb_facebook-icon { background-position: -119px 0; } +.phpbb_googleplus-icon { background-position: -140px 0; } +.phpbb_skype-icon { background-position: -161px 0; } +.phpbb_twitter-icon { background-position: -203px 0; } +.phpbb_yahoo-icon { background-position: -224px 0; } + +/* Forum icons & Topic icons */ +.global_read { background-image: url("./images/announce_read.gif"); } +.global_read_mine { background-image: url("./images/announce_read_mine.gif"); } +.global_read_locked { background-image: url("./images/announce_read_locked.gif"); } +.global_read_locked_mine { background-image: url("./images/announce_read_locked_mine.gif"); } +.global_unread { background-image: url("./images/announce_unread.gif"); } +.global_unread_mine { background-image: url("./images/announce_unread_mine.gif"); } +.global_unread_locked { background-image: url("./images/announce_unread_locked.gif"); } +.global_unread_locked_mine { background-image: url("./images/announce_unread_locked_mine.gif"); } + +.announce_read { background-image: url("./images/announce_read.gif"); } +.announce_read_mine { background-image: url("./images/announce_read_mine.gif"); } +.announce_read_locked { background-image: url("./images/announce_read_locked.gif"); } +.announce_read_locked_mine { background-image: url("./images/announce_read_locked_mine.gif"); } +.announce_unread { background-image: url("./images/announce_unread.gif"); } +.announce_unread_mine { background-image: url("./images/announce_unread_mine.gif"); } +.announce_unread_locked { background-image: url("./images/announce_unread_locked.gif"); } +.announce_unread_locked_mine { background-image: url("./images/announce_unread_locked_mine.gif"); } + +.forum_link { background-image: url("./images/forum_link.gif"); } +.forum_read { background-image: url("./images/forum_read.gif"); } +.forum_read_locked { background-image: url("./images/forum_read_locked.gif"); } +.forum_read_subforum { background-image: url("./images/forum_read_subforum.gif"); } +.forum_unread { background-image: url("./images/forum_unread.gif"); } +.forum_unread_locked { background-image: url("./images/forum_unread_locked.gif"); } +.forum_unread_subforum { background-image: url("./images/forum_unread_subforum.gif"); } + +.sticky_read { background-image: url("./images/sticky_read.gif"); } +.sticky_read_mine { background-image: url("./images/sticky_read_mine.gif"); } +.sticky_read_locked { background-image: url("./images/sticky_read_locked.gif"); } +.sticky_read_locked_mine { background-image: url("./images/sticky_read_locked_mine.gif"); } +.sticky_unread { background-image: url("./images/sticky_unread.gif"); } +.sticky_unread_mine { background-image: url("./images/sticky_unread_mine.gif"); } +.sticky_unread_locked { background-image: url("./images/sticky_unread_locked.gif"); } +.sticky_unread_locked_mine { background-image: url("./images/sticky_unread_locked_mine.gif"); } + +.topic_moved { background-image: url("./images/topic_moved.gif"); } +.pm_read, +.topic_read { background-image: url("./images/topic_read.gif"); } +.topic_read_mine { background-image: url("./images/topic_read_mine.gif"); } +.topic_read_hot { background-image: url("./images/topic_read_hot.gif"); } +.topic_read_hot_mine { background-image: url("./images/topic_read_hot_mine.gif"); } +.topic_read_locked { background-image: url("./images/topic_read_locked.gif"); } +.topic_read_locked_mine { background-image: url("./images/topic_read_locked_mine.gif"); } +.pm_unread, +.topic_unread { background-image: url("./images/topic_unread.gif"); } +.topic_unread_mine { background-image: url("./images/topic_unread_mine.gif"); } +.topic_unread_hot { background-image: url("./images/topic_unread_hot.gif"); } +.topic_unread_hot_mine { background-image: url("./images/topic_unread_hot_mine.gif"); } +.topic_unread_locked { background-image: url("./images/topic_unread_locked.gif"); } +.topic_unread_locked_mine { background-image: url("./images/topic_unread_locked_mine.gif"); } + + +/* +-------------------------------------------------------------- +Colours and backgrounds for cp.css +-------------------------------------------------------------- */ + +/* Main CP box +----------------------------------------*/ + +.panel-container h3, .panel-container hr, .cp-menu hr { + border-color: #A4B3BF; +} + +.panel-container .panel li.row { + border-bottom-color: #B5C1CB; + border-top-color: #F9F9F9; +} + +ul.cplist { + border-top-color: #B5C1CB; +} + +.panel-container .panel li.header dd, .panel-container .panel li.header dt { + color: #000000; +} + +.panel-container table.table1 thead th { + color: #333333; + border-bottom-color: #333333; +} + +.cp-main .pm-message { + border-color: #DBDEE2; + background-color: #FFFFFF; +} + +/* CP tabbed menu +----------------------------------------*/ +.tabs .tab > a { + background: #BACCD9; + color: #536482; +} + +.tabs .tab > a:hover { + background: #DDEDFB; + color: #D31141; +} + +.tabs .activetab > a, +.tabs .activetab > a:hover { + background-color: #CADCEB; /* Old browsers */ /* FF3.6+ */ + background-image: -webkit-linear-gradient(top, #E2F2FF 0%, #CADCEB 100%); + background-image: linear-gradient(to bottom, #E2F2FF 0%,#CADCEB 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#E2F2FF', endColorstr='#CADCEB',GradientType=0 ); /* IE6-9 */ + border-color: #CADCEB; + box-shadow: 0 1px 1px #F2F9FF inset; + color: #333333; +} + +.tabs .activetab > a:hover { + color: #000000; +} + +/* Mini tabbed menu used in MCP +----------------------------------------*/ +.minitabs .tab > a { + background-color: #E1EBF2; +} + +.minitabs .activetab > a, +.minitabs .activetab > a:hover { + background-color: #F9F9F9; + color: #333333; +} + +/* Responsive tabs +----------------------------------------*/ +.responsive-tab .responsive-tab-link:before { + border-color: #536482; +} + +.responsive-tab .responsive-tab-link:hover:before { + border-color: #D31141; +} + +/* UCP navigation menu +----------------------------------------*/ + +/* Link styles for the sub-section links */ +.navigation a { + color: #333; + background: #CADCEB; /* Old browsers */ /* FF3.6+ */ + background: -webkit-linear-gradient(left, #B4C4D1 50%, #CADCEB 100%); + background: linear-gradient(to right, #B4C4D1 50%,#CADCEB 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#B4C4D1', endColorstr='#CADCEB',GradientType=1 ); /* IE6-9 */ +} + +.rtl .navigation a { + background: #B4C4D1; /* Old browsers */ /* FF3.6+ */ + background: -webkit-linear-gradient(left, #CADCEB 50%, #B4C4D1 100%); + background: linear-gradient(to right, #CADCEB 50%,#B4C4D1 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#CADCEB', endColorstr='#B4C4D1',GradientType=1 ); /* IE6-9 */ +} + +.navigation a:hover { + background: #AABAC6; + color: #BC2A4D; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} + +.navigation .active-subsection a { + background: #F9F9F9; + color: #D31141; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} + +.navigation .active-subsection a:hover { + color: #D31141; +} + +@media only screen and (max-width: 900px), only screen and (max-device-width: 900px) +{ + #navigation a, .rtl #navigation a { + background: #B2C2CF; + } +} + +/* Preferences pane layout +----------------------------------------*/ +.panel-container h2 { + color: #333333; +} + +.panel-container .panel { + background-color: #F9F9F9; +} + +.cp-main .pm { + background-color: #FFFFFF; +} + +/* Friends list */ +.cp-mini { + background-color: #EEF5F9; +} + +dl.mini dt { + color: #425067; +} + +/* PM Styles +----------------------------------------*/ +/* PM Message history */ +.current { + color: #000000 !important; +} + +/* PM marking colours */ +.pmlist li.pm_message_reported_colour, .pm_message_reported_colour { + border-left-color: #BC2A4D; + border-right-color: #BC2A4D; +} + +.pmlist li.pm_marked_colour, .pm_marked_colour { + border-color: #FF6600; +} + +.pmlist li.pm_replied_colour, .pm_replied_colour { + border-color: #A9B8C2; +} + +.pmlist li.pm_friend_colour, .pm_friend_colour { + border-color: #5D8FBD; +} + +.pmlist li.pm_foe_colour, .pm_foe_colour { + border-color: #000000; +} + +/* Avatar gallery */ +.gallery label { + background: #FFFFFF; + border-color: #CCC; +} + +.gallery label:hover { + background-color: #EEE; +} + +/* +-------------------------------------------------------------- +Colours and backgrounds for forms.css +-------------------------------------------------------------- */ + +/* General form styles +----------------------------------------*/ +select { + border-color: #666666; + background-color: #FAFAFA; + color: #000; +} + +label { + color: #425067; +} + +option.disabled-option { + color: graytext; +} + +/* Definition list layout for forms +---------------------------------------- */ +dd label { + color: #333; +} + +fieldset.fields1 { + background-color: transparent; +} + +/* Hover effects */ +fieldset dl:hover dt label { + color: #000000; +} + +fieldset.fields2 dl:hover dt label { + color: inherit; +} + +/* Quick-login on index page */ +fieldset.quick-login input.inputbox { + background-color: #F2F3F3; +} + +/* Posting page styles +----------------------------------------*/ + +.message-box textarea { + color: #333333; +} + +.message-box textarea.drag-n-drop { + outline-color: rgba(102, 102, 102, 0.5); +} + +.message-box textarea.drag-n-drop-highlight { + outline-color: rgba(17, 163, 234, 0.5); +} + +/* Input field styles +---------------------------------------- */ +.inputbox { + background-color: #FFFFFF; + border-color: #B4BAC0; + color: #333333; +} + +.inputbox:-moz-placeholder { + color: #333333; +} + +.inputbox::-webkit-input-placeholder { + color: #333333; +} + +.inputbox:hover { + border-color: #11A3EA; +} + +.inputbox:focus { + border-color: #11A3EA; +} + +.inputbox:focus:-moz-placeholder { + color: transparent; +} + +.inputbox:focus::-webkit-input-placeholder { + color: transparent; +} + + +/* Form button styles +---------------------------------------- */ + +a.button1, input.button1, input.button3, a.button2, input.button2 { + color: #000; + background-color: #EFEFEF; /* Old browsers */ /* FF3.6+ */ + background-image: -webkit-linear-gradient(top, #D2D2D2 0%, #EFEFEF 100%); + background-image: linear-gradient(to bottom, #D2D2D2 0%,#EFEFEF 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#D2D2D2', endColorstr='#EFEFEF',GradientType=0 ); /* IE6-9 */ +} + +a.button1, input.button1 { + border-color: #666666; +} + +input.button3 { + background-image: none; +} + +/* Alternative button */ +a.button2, input.button2, input.button3 { + border-color: #666666; +} + +/* button in the style of the form buttons */ +a.button1, a.button2 { + color: #000000; +} + +/* Hover states */ +a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover { + border-color: #D31141; + color: #D31141; + background-color: #D2D2D2; /* Old browsers */ /* FF3.6+ */ + background-image: -webkit-linear-gradient(top, #EFEFEF 0%, #D2D2D2 100%); + background-image: linear-gradient(to bottom, #EFEFEF 0%,#D2D2D2 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#EFEFEF', endColorstr='#D2D2D2',GradientType=0 ); /* IE6-9 */ +} + +/* Focus states */ +input.button1:focus, input.button2:focus, input.button3:focus { + border-color: #11A3EA; + color: #0F4987; +} + +input.disabled { + color: #666666; +} + +/* jQuery popups +---------------------------------------- */ +.phpbb_alert { + background-color: #FFFFFF; + border-color: #999999; +} +.darken { + background-color: #000000; +} + +.loading_indicator { + background-color: #000000; + background-image: url("./images/loading.gif"); +} + +.dropdown-extended ul li { + border-top-color: #B9B9B9; +} + +.dropdown-extended ul li:hover { + background-color: #CFE1F6; + color: #000000; +} + +.dropdown-extended .header, .dropdown-extended .footer { + border-color: #B9B9B9; + color: #000000; +} + +.dropdown-extended .footer { + border-top-style: solid; + border-top-width: 1px; +} + +.dropdown-extended .header { + background-color: #F1F8FF; /* Old browsers */ /* FF3.6+ */ + background-image: -webkit-linear-gradient(top, #F1F8FF 0%, #CADCEB 100%); + background-image: linear-gradient(to bottom, #F1F8FF 0%,#CADCEB 100%); /* W3C */ + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#F1F8FF', endColorstr='#CADCEB',GradientType=0 ); /* IE6-9 */ +} + +.dropdown .pointer { + border-color: #B9B9B9 transparent; +} + +.dropdown .pointer-inner { + border-color: #FFF transparent; +} + +.dropdown-extended .pointer-inner { + border-color: #F1F8FF transparent; +} + +.dropdown .dropdown-contents { + background: #fff; + border-color: #B9B9B9; + box-shadow: 1px 3px 5px rgba(0, 0, 0, 0.2); +} + +.dropdown-up .dropdown-contents { + box-shadow: 1px 0 5px rgba(0, 0, 0, 0.2); +} + +.dropdown li, .dropdown li li { + border-color: #DCDCDC; +} + +.dropdown li.separator { + border-color: #DCDCDC; +} + +/* Notifications +---------------------------------------- */ + +.notification_list p.notification-time { + color: #4C5D77; +} + +li.notification-reported strong, li.notification-disapproved strong { + color: #D31141; +} + +.badge { + background-color: #D31141; + color: #ffffff; +} diff --git a/styles/prosilver/theme/common.css b/styles/prosilver/theme/common.css new file mode 100644 index 0000000..a0dc5e0 --- /dev/null +++ b/styles/prosilver/theme/common.css @@ -0,0 +1,1287 @@ +/* General Markup Styles +---------------------------------------- */ +html { + font-size: 100%; + /* Always show a scrollbar for short pages - stops the jump when the scrollbar appears. non-IE browsers */ + height: 101%; +} + +body { + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 10px; + line-height: normal; + margin: 0; + padding: 12px 0; + word-wrap: break-word; + -webkit-print-color-adjust: exact; +} + +h1 { + /* Forum name */ + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + margin-right: 200px; + margin-top: 15px; + font-weight: bold; + font-size: 2em; +} + +h2 { + /* Forum header titles */ + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-weight: normal; + font-size: 2em; + margin: 0.8em 0 0.2em 0; +} + +h2.solo { + margin-bottom: 1em; +} + +h3 { + /* Sub-headers (also used as post headers, but defined later) */ + font-family: Arial, Helvetica, sans-serif; + font-weight: bold; + text-transform: uppercase; + border-bottom: 1px solid transparent; + margin-bottom: 3px; + padding-bottom: 2px; + font-size: 1.05em; + margin-top: 20px; +} + +h4 { + /* Forum and topic list titles */ + font-family: "Trebuchet MS", Verdana, Helvetica, Arial, Sans-serif; + font-size: 1.3em; +} + +p { + line-height: 1.3em; + font-size: 1.1em; + margin-bottom: 1.5em; +} + +img { + border-width: 0; +} + +hr { + border: 0 solid transparent; + border-top-width: 1px; + height: 1px; + margin: 5px 0; + display: block; + clear: both; +} + +hr.dashed { + border-top-style: dashed; + margin: 10px 0; +} + +hr.divider { + display: none; +} + +p.right { + text-align: right; +} + +p.jumpbox-return { + margin-top: 10px; + margin-bottom: 0; + float: left; +} + +b, strong { + font-weight: bold; +} + +.text-strong { + font-weight: bold; +} + +i, em { + font-style: italic; +} + +.text-italics { + font-style: italic; +} + +u { + text-decoration: underline; +} + +ul { + list-style-type: disc; +} + +ol { + list-style-type: decimal; +} + +li { + display: list-item; +} + +ul ul, ol ul { + list-style-type: circle; +} + +ol ol ul, ol ul ul, ul ol ul, ul ul ul { + list-style-type: square; +} + +a:hover { text-decoration: underline; } + +/* Main blocks +---------------------------------------- */ +.wrap { + border: 1px solid transparent; + border-radius: 8px; + margin: 0 auto; + max-width: 1152px; + min-width: 625px; + padding: 15px; +} + +@media only screen and (max-width: 1220px), only screen and (max-device-width: 1220px) { + .wrap { + margin: 0 12px; + } +} + +.page-body { + margin: 4px 0; + clear: both; +} + +.page-footer { + clear: both; +} + +.page-footer h3 { + margin-top: 20px; +} + +.logo { + float: left; + width: auto; + padding: 10px 13px 0 10px; +} + +.logo:hover { + text-decoration: none; +} + +.site_logo { + display: inline-block; + width: 149px; + height: 52px; +} + +/* Site description and logo */ +.site-description { + float: left; + width: 65%; +} + +.site-description h1 { + margin-right: 0; +} + +/* Round cornered boxes and backgrounds +---------------------------------------- */ +.headerbar { + margin-bottom: 4px; + padding: 5px; + border-radius: 7px; +} + +.navbar { + padding: 3px 10px; + border-radius: 7px; +} + +.forabg { + margin-bottom: 4px; + padding: 5px; + clear: both; + border-radius: 7px; +} + +.forumbg { + margin-bottom: 4px; + padding: 5px; + clear: both; + border-radius: 7px; +} + +.panel { + margin-bottom: 4px; + padding: 5px 10px; + border-radius: 7px; +} + +.post { + padding: 5px 10px; + margin-bottom: 4px; + background-repeat: no-repeat; + background-position: 100% 0; + border-radius: 7px; + position: relative; +} + +.rowbg { + margin: 5px 5px 2px 5px; +} + +/* Horizontal lists +----------------------------------------*/ +.navbar ul.linklist { + padding: 2px 0; + list-style-type: none; +} + +ul.linklist { + display: block; + margin: 0; +} + +.cp-main .panel { + padding: 5px 10px; +} + +ul.linklist > li { + float: left; + font-size: 1.1em; + line-height: 2.2em; + list-style-type: none; + margin-right: 7px; + padding-top: 1px; + width: auto; +} + +ul.linklist > li.rightside, p.rightside, a.rightside { + float: right; + margin-right: 0; + margin-left: 7px; + text-align: right; +} + +ul.navlinks { + border-top: 1px solid transparent; +} + +ul.leftside { + float: left; + margin-left: 0; + margin-right: 5px; + text-align: left; +} + +ul.rightside { + float: right; + margin-left: 5px; + margin-right: -5px; + text-align: right; +} + +ul.linklist li.responsive-menu { + position: relative; + margin: 0 5px 0 0; +} + +.hasjs ul.linklist.leftside, .hasjs ul.linklist.rightside { + max-width: 48%; +} + +.hasjs ul.linklist.fullwidth { + max-width: none; +} + +li.responsive-menu.dropdown-right .dropdown { + left: -9px; +} + +li.responsive-menu.dropdown-left .dropdown { + right: -6px; +} + +ul.linklist .dropdown { + top: 22px; +} + +ul.linklist .dropdown-up .dropdown { + bottom: 18px; + top: auto; +} + +/* Bulletin icons for list items +----------------------------------------*/ +ul.linklist.bulletin > li:before { + display: inline-block; + content: "\2022"; + font-size: inherit; + line-height: inherit; + padding-right: 4px; +} + +ul.linklist.bulletin > li:first-child:before, +ul.linklist.bulletin > li.rightside:last-child:before { + content: none; +} + +ul.linklist.bulletin > li.no-bulletin:before { + content: none; +} + +.responsive-menu:before { + display: none !important; +} + +/* Profile in overall_header.html */ +.header-profile { + display: inline-block; + vertical-align: top; +} + +a.header-avatar, +a.header-avatar:hover { + text-decoration: none; +} + +a.header-avatar img { + margin-bottom: 2px; + max-height: 20px; + vertical-align: middle; + width: auto; +} + +a.header-avatar span:after { + content: '\f0dd'; + display: inline-block; + font: normal normal normal 14px/1 FontAwesome; + padding-left: 6px; + padding-top: 2px; + vertical-align: top; +} + +/* Dropdown menu +----------------------------------------*/ +.dropdown-container { + position: relative; +} + +.dropdown-container-right { + float: right; +} + +.dropdown-container-left { + float: left; +} + +.nojs .dropdown-container:hover .dropdown { + display: block !important; +} + +.dropdown { + display: none; + position: absolute; + left: 0; + top: 1.2em; + z-index: 2; + border: 1px solid transparent; + border-radius: 5px; + padding: 9px 0 0; + margin-right: -500px; +} + +.dropdown.live-search { + top: auto; +} + +.dropdown-container.topic-tools { + float: left; +} + +.dropdown-up .dropdown { + top: auto; + bottom: 1.2em; + padding: 0 0 9px; +} + +.dropdown-left .dropdown, .nojs .rightside .dropdown { + left: auto; + right: 0; + margin-left: -500px; + margin-right: 0; +} + +.dropdown-button-control .dropdown { + top: 24px; +} + +.dropdown-button-control.dropdown-up .dropdown { + top: auto; + bottom: 24px; +} + +.dropdown .pointer, .dropdown .pointer-inner { + position: absolute; + width: 0; + height: 0; + border-top-width: 0; + border-bottom: 10px solid transparent; + border-left: 10px dashed transparent; + border-right: 10px dashed transparent; + -webkit-transform: rotate(360deg); /* better anti-aliasing in webkit */ + display: block; +} + +.dropdown-up .pointer, .dropdown-up .pointer-inner { + border-bottom-width: 0; + border-top: 10px solid transparent; +} + +.dropdown .pointer { + right: auto; + left: 10px; + top: -1px; + z-index: 3; +} + +.dropdown-up .pointer { + bottom: -1px; + top: auto; +} + +.dropdown-left .dropdown .pointer, .nojs .rightside .dropdown .pointer { + left: auto; + right: 10px; +} + +.dropdown .pointer-inner { + top: auto; + bottom: -11px; + left: -10px; +} + +.dropdown-up .pointer-inner { + bottom: auto; + top: -11px; +} + +.dropdown .dropdown-contents { + z-index: 2; + overflow: hidden; + overflow-y: auto; + border: 1px solid transparent; + border-radius: 5px; + padding: 5px; + position: relative; + max-height: 300px; +} + +.dropdown-contents a { + display: block; + padding: 5px; +} + +.jumpbox { + margin: 5px 0; +} + +.jumpbox .dropdown li { + border-top: 1px solid transparent; +} + +.jumpbox .dropdown-select { + margin: 0; +} + +.jumpbox .dropdown-contents { + padding: 0; + text-decoration: none; +} + +.jumpbox .dropdown-contents li { + padding: 0; +} + +.jumpbox .dropdown-contents a { + margin-right: 20px; + padding: 5px 10px; + text-decoration: none; + width: 100%; +} + +.jumpbox .spacer { + display: inline-block; + width: 0px; +} + +.jumpbox .spacer + .spacer { + width: 20px; +} + +.dropdown-contents a { + display: block; + padding: 5px; +} + +.jumpbox .dropdown-select { + margin: 0; +} + +.jumpbox .dropdown-contents a { + text-decoration: none; +} + +.dropdown li { + display: list-item; + border-top: 1px dotted transparent; + float: none !important; + line-height: normal !important; + font-size: 1em !important; + list-style: none; + margin: 0; + white-space: nowrap; + text-align: left; +} + +.dropdown-contents > li { + padding-right: 15px; +} + +.dropdown-nonscroll > li { + padding-right: 0; +} + +.dropdown li:first-child, .dropdown li.separator + li, .dropdown li li { + border-top: 0; +} + +.dropdown li li:first-child { + margin-top: 4px; +} + +.dropdown li li:last-child { + padding-bottom: 0; +} + +.dropdown li li { + border-top: 1px dotted transparent; + padding-left: 18px; +} + +.wrap .dropdown li, .dropdown.wrap li, .dropdown-extended li { + white-space: normal; +} + +.dropdown li.separator { + border-top: 1px solid transparent; + padding: 0; +} + +.dropdown li.separator:first-child, .dropdown li.separator:last-child { + display: none !important; +} + +/* Responsive breadcrumbs +----------------------------------------*/ +.breadcrumbs .crumb { + float: left; + font-weight: bold; + word-wrap: normal; +} + +.breadcrumbs .crumb:before { + content: '\2039'; + font-weight: bold; + padding: 0 0.5em; +} + +.breadcrumbs .crumb:first-child:before { + content: none; +} + +.breadcrumbs .crumb a { + white-space: nowrap; + text-overflow: ellipsis; + vertical-align: bottom; + overflow: hidden; +} + +.breadcrumbs.wrapped .crumb a { letter-spacing: -.3px; } +.breadcrumbs.wrapped .crumb.wrapped-medium a { letter-spacing: -.4px; } +.breadcrumbs.wrapped .crumb.wrapped-tiny a { letter-spacing: -.5px; } + +.breadcrumbs .crumb.wrapped-max a { max-width: 120px; } +.breadcrumbs .crumb.wrapped-wide a { max-width: 100px; } +.breadcrumbs .crumb.wrapped-medium a { max-width: 80px; } +.breadcrumbs .crumb.wrapped-small a { max-width: 60px; } +.breadcrumbs .crumb.wrapped-tiny a { max-width: 40px; } + +/* Table styles +----------------------------------------*/ +table.table1 { + width: 100%; +} + +.ucp-main table.table1 { + padding: 2px; +} + +table.table1 thead th { + font-weight: normal; + text-transform: uppercase; + line-height: 1.3em; + font-size: 1em; + padding: 0 0 4px 3px; +} + +table.table1 thead th span { + padding-left: 7px; +} + +table.table1 tbody tr { + border: 1px solid transparent; +} + +table.table1 td { + font-size: 1.1em; +} + +table.table1 tbody td { + padding: 5px; + border-top: 1px solid transparent; +} + +table.table1 tbody th { + padding: 5px; + border-bottom: 1px solid transparent; + text-align: left; +} + +/* Specific column styles */ +table.table1 .name { text-align: left; } +table.table1 .center { text-align: center; } +table.table1 .reportby { width: 15%; } +table.table1 .posts { text-align: center; width: 7%; } +table.table1 .joined { text-align: left; width: 15%; } +table.table1 .active { text-align: left; width: 15%; } +table.table1 .mark { text-align: center; width: 7%; } +table.table1 .info { text-align: left; width: 30%; } +table.table1 .info div { width: 100%; white-space: normal; overflow: hidden; } +table.table1 .autocol { line-height: 2em; white-space: nowrap; } +table.table1 thead .autocol { padding-left: 1em; } + +table.table1 span.rank-img { + float: right; + width: auto; +} + +table.info td { + padding: 3px; +} + +table.info tbody th { + padding: 3px; + text-align: right; + vertical-align: top; + font-weight: normal; +} + +.forumbg table.table1 { + margin: 0; +} + +.forumbg-table > .inner { + margin: 0 -1px; +} + +.color_palette_placeholder table { + border-collapse: separate; + border-spacing: 1px; +} + +/* Misc layout styles +---------------------------------------- */ +/* column[1-2] styles are containers for two column layouts */ +.column1 { + float: left; + clear: left; + width: 49%; +} + +.column2 { + float: right; + clear: right; + width: 49%; +} + +/* General classes for placing floating blocks */ +.left-box { + float: left; + width: auto; + text-align: left; + max-width: 100%; +} + +.left-box.profile-details { + width: 80%; +} + +.right-box { + float: right; + width: auto; + text-align: right; + max-width: 100%; +} + +dl.details { + /*font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif;*/ + font-size: 1.1em; +} + +dl.details dt { + float: left; + clear: left; + width: 30%; + text-align: right; + display: block; +} + +dl.details dd { + margin-left: 0; + padding-left: 5px; + margin-bottom: 5px; + float: left; + width: 65%; + overflow: hidden; + text-overflow: ellipsis; +} + +.clearfix, fieldset dl, ul.topiclist dl, dl.polls { + overflow: hidden; +} + +fieldset.fields1 ul.recipients { + list-style-type: none; + line-height: 1.8; + max-height: 150px; + overflow-y: auto; +} + +fieldset.fields1 dd.recipients { + clear: left; + margin-left: 1em; +} + +fieldset.fields1 ul.recipients input.button2{ + font-size: 0.8em; + margin-right: 0; + padding: 0; +} + +fieldset.fields1 dl.pmlist > dt { + width: auto !important; +} + +fieldset.fields1 dl.pmlist dd.recipients { + margin-left: 0 !important; +} + +/* Action-bars (container for post/reply buttons, pagination, etc.) +---------------------------------------- */ +.action-bar { + font-size: 11px; + margin: 4px 0; +} + +.forabg + .action-bar { + margin-top: 2em; +} + +.action-bar .button { + margin-right: 5px; + float: left; +} + +.action-bar .button-search { + margin-right: 0; +} + +/* Pagination +---------------------------------------- */ +.pagination { + float: right; + text-align: right; + width: auto; +} + +.action-bar.bar-bottom .pagination { + margin-top: 0; +} + +.action-bar .pagination .button { + margin-right: 0; + float: none; +} + +.pagination > ul { + display: inline-block; + list-style: none !important; + margin-left: 5px; +} + +.pagination > ul > li { + display: inline-block !important; + padding: 0; + font-size: 100%; + line-height: normal; + vertical-align: middle; +} + +.pagination li a, .pagination li span { + border-radius: 2px; + padding: 2px 5px; +} + +.pagination li.active span { + display: inline-block; + font-size: 13px; + font-weight: normal; + font-family: "Open Sans", "Droid Sans", Verdana, Arial, Helvetica; + line-height: 1.4; + text-align: center; + white-space: nowrap; + vertical-align: middle; + border: 1px solid transparent; +} + +.pagination li.ellipsis span { + border: none; + padding: 0; +} + +.pagination li.page-jump { + margin-right: 5px; +} + +.pagination li.page-jump a { + padding: 0 8px; +} + +.pagination li.page-jump a i { + font-size: 21px; +} + +.pagination .arrow a { + padding: 2px 0; +} + +/* Pagination in viewforum for multipage topics */ +.row .pagination { + display: block; + margin-top: 3px; + margin-bottom: 3px; +} + +.row .pagination > ul { + margin: 0; +} + +.row .pagination li a, .row .pagination li span { + border-radius: 2px; + padding: 1px 3px; + font-size: 9px; +} + +/* jQuery popups +---------------------------------------- */ +.phpbb_alert { + border: 1px solid transparent; + display: none; + left: 0; + padding: 0 25px 20px 25px; + position: fixed; + right: 0; + top: 150px; + z-index: 50; + width: 620px; + margin: 0 auto; +} + +@media only screen and (max-height: 500px), only screen and (max-device-width: 500px) +{ + .phpbb_alert { + top: 25px; + } +} + +.phpbb_alert .alert_close { + float: right; + margin-right: -36px; + margin-top: -8px; +} + +.phpbb_alert p { + margin: 8px 0; + padding-bottom: 8px; +} + +.phpbb_alert label { + display: block; + margin: 8px 0; + padding-bottom: 8px; +} + +.phpbb_alert div.alert_text > p, +.phpbb_alert div.alert_text > label, +.phpbb_alert div.alert_text > select, +.phpbb_alert div.alert_text > textarea, +.phpbb_alert div.alert_text > input { + font-size: 1.1em; +} + +.darkenwrapper { + display: none; + position: relative; + z-index: 44; +} + +.darken { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0.5; + z-index: 45; +} + +.loading_indicator { + background: center center no-repeat; + border-radius: 5px; + display: none; + opacity: 0.8; + margin-top: -50px; + margin-left: -50px; + height: 50px; + width: 50px; + position: fixed; + left: 50%; + top: 50%; + z-index: 51; +} + +/* Miscellaneous styles +---------------------------------------- */ +.copyright { + font-size: 10px; + text-align: center; + padding: 10px; +} + +.footer-row { + font-size: 10px; + line-height: 1.8; + margin: 0; +} + +.small { + font-size: 0.9em !important; +} + +.titlespace { + margin-bottom: 15px; +} + +.headerspace { + margin-top: 20px; +} + +.error { + font-weight: bold; + font-size: 1em; +} + +div.rules { + margin: 10px 0; + font-size: 1.1em; + padding: 5px 10px; + border-radius: 7px; +} + +div.rules ul, div.rules ol { + margin-left: 20px; +} + +p.post-notice { + position: relative; + padding: 5px; + min-height: 14px; + margin-bottom: 1em; +} + +form > p.post-notice strong { + line-height: 20px; +} + +.stat-block { + clear: both; +} + +.top-anchor { + display: block; + position: absolute; + top: -20px; +} + +.clear { + display: block; + clear: both; + font-size: 1px; + line-height: 1px; + background: transparent; +} + +/* Inner box-model clearing */ +.inner:after, +ul.linklist:after, +.action-bar:after, +.notification_text:after, +.tabs-container:after, +.tabs > ul:after, +.minitabs > ul:after, +.postprofile .avatar-container:after { + clear: both; + content: ''; + display: block; +} + +.emoji { + min-height: 18px; + min-width: 18px; + height: 1em; + width: 1em; +} + +.smilies { + vertical-align: text-bottom; +} + +.icon-notification { + position: relative; +} + +.member-search { + float: left; + margin: 0; + padding: 6px 10px; +} + +.member-search strong { + font-size: 0.95em; +} + +.dropdown-extended { + display: none; + z-index: 1; +} + +.dropdown-extended ul { + max-height: 350px; + overflow-y: auto; + overflow-x: hidden; + clear: both; +} + +.dropdown-extended ul li { + padding: 0; + margin: 0 !important; + float: none; + border-top: 1px solid; + list-style-type: none; + font-size: 0.95em; + clear: both; + position: relative; +} + +.dropdown-extended ul li:first-child { + border-top: none; +} + +.dropdown-extended ul li.no_notifications { + padding: 10px; +} + +.dropdown-extended .dropdown-contents { + max-height: none; + padding: 0; + position: absolute; + width: 340px; +} + +.nojs .dropdown-extended .dropdown-contents { + position: relative; +} + +.dropdown-extended .header { + padding: 0 10px; + font-family: Arial, "Helvetica Neue", Helvetica, Arial, sans-serif; + font-weight: bold; + text-align: left; + text-shadow: 1px 1px 1px white; + text-transform: uppercase; + line-height: 3em; + border-bottom: 1px solid; + border-radius: 5px 5px 0 0; +} + +.dropdown-extended .header .header_settings { + float: right; + font-weight: normal; + text-transform: none; +} + +.dropdown-extended .header .header_settings a { + display: inline-block; + padding: 0 5px; +} + +.dropdown-extended .header:after { + content: ''; + display: table; + clear: both; +} + +.dropdown-extended .footer { + text-align: center; + font-size: 1.1em; +} + +.dropdown-extended ul li a, .dropdown-extended ul li.no-url { + padding: 8px; +} + +.dropdown-extended .footer > a { + padding: 5px 0; +} + +.dropdown-extended ul li a, .notification_list dt > a, .dropdown-extended .footer > a { + display: block; + text-decoration: none; +} + +.notification_list ul li img { + float: left; + max-height: 50px; + max-width: 50px; + width: auto !important; + height: auto !important; + margin-right: 5px; +} + +.notification_list ul li p { + margin-bottom: 4px; + font-size: 1em; +} + +.notification_list p.notification-reference, +.notification_list p.notification-location, +.notification_list li a p.notification-reason { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.notification_list p.notification-time { + font-size: 0.9em; + margin: 0; + text-align: right; +} + +.notification_list div.notifications { + margin-left: 50px; + padding: 5px; +} + +.notification_list div.notifications a { + display: block; +} + +.notification_list p.notifications_title { + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + font-size: 1.2em !important; +} + +.notification_list p.notifications_title strong { + font-weight: bold; +} + +.notification_list p.notifications_time { + font-size: 0.9em !important; +} + +.notification_text { + margin-left: 58px; +} + +.badge { + border-radius: 10px; + opacity: 0.8; + text-align: center; + white-space: nowrap; + font-size: 10px; + line-height: 1; + float: right; + display: inline-block; + margin-left: 3px; + vertical-align: baseline; + position: relative; + top: 3px; + padding: 4px 6px; +} + +.badge.hidden { + display: none; +} + +/* Navbar specific list items +----------------------------------------*/ + +.linklist .quick-links { + margin: 0 7px 0 0; +} + +.linklist.compact .rightside > a > span { + display: none; +} + +.dropdown-page-jump .dropdown { + top: 20px; +} + +.dropdown-page-jump.dropdown-up .dropdown { + bottom: 20px; +} + +.dropdown-page-jump input.tiny { + width: 50px; +} + +.dropdown .clone.hidden { + display: none; +} + +.dropdown .clone.hidden + li.separator { + display: none; +} + +.dropdown .clone.hidden + li { + border-top: none; +} diff --git a/styles/prosilver/theme/content.css b/styles/prosilver/theme/content.css new file mode 100644 index 0000000..8076338 --- /dev/null +++ b/styles/prosilver/theme/content.css @@ -0,0 +1,855 @@ +/* Content Styles +---------------------------------------- */ + +ul.topiclist { + display: block; + list-style-type: none; + margin: 0; +} + +ul.topiclist li { + display: block; + list-style-type: none; + margin: 0; +} + +ul.topiclist dl { + position: relative; +} + +ul.topiclist li.row dl { + margin: 2px 0; +} + +ul.topiclist dt, ul.topiclist dd { + display: block; + float: left; +} + +ul.topiclist dt { + width: 100%; + margin-right: -440px; + font-size: 1.1em; +} + +ul.topiclist.missing-column dt { + margin-right: -345px; +} + +ul.topiclist.two-long-columns dt { + margin-right: -250px; +} + +ul.topiclist.two-columns dt { + margin-right: -80px; +} + +ul.topiclist dt .list-inner { + margin-right: 440px; + padding-left: 5px; + padding-right: 5px; +} + +ul.topiclist.missing-column dt .list-inner { + margin-right: 345px; +} + +ul.topiclist.two-long-columns dt .list-inner { + margin-right: 250px; +} + +ul.topiclist.two-columns dt .list-inner { + margin-right: 80px; +} + +ul.topiclist dd { + border-left: 1px solid transparent; + padding: 4px 0; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +ul.topiclist li.row dd { + padding: 4px 0 999px 0; + margin-bottom: -995px; +} + +ul.topiclist dfn { + /* Labels for post/view counts */ + position: absolute; + left: -999px; + width: 990px; +} + +.forum-image { + float: left; + padding-top: 5px; + margin-right: 5px; +} + +li.row { + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; +} + +li.row strong { + font-weight: normal; +} + +li.header dt, li.header dd { + line-height: 1em; + border-left-width: 0; + margin: 2px 0 4px 0; + padding-top: 2px; + padding-bottom: 2px; + font-size: 1em; + font-family: Arial, Helvetica, sans-serif; + text-transform: uppercase; +} + +li.header dt { + font-weight: bold; + width: 100%; + margin-right: -440px; +} + +li.header dt .list-inner { + margin-right: 440px; +} + +li.header dd { + padding-left: 1px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +li.header dl.row-item dt, li.header dl.row-item dd { + min-height: 0; +} + +li.header dl.row-item dt .list-inner { + /* Tweak for headers alignment when folder icon used */ + padding-left: 0; + padding-right: 50px; +} + +/* Forum list column styles */ +.row .list-inner { padding: 4px 0; } + +dl.row-item { + background-position: 10px 50%; /* Position of folder icon */ + background-repeat: no-repeat; + background-size: 32px; +} + +dl.row-item dt { + background-repeat: no-repeat; + background-position: 5px 95%; /* Position of topic icon */ + background-size: 17px; +} + +dl.row-item dt .list-inner { + padding-left: 52px; /* Space for folder icon */ +} + +dl.row-item dt, dl.row-item dd { + min-height: 35px; +} + +dl.row-item dt a { + display: inline; +} + +dl a.row-item-link { /* topic row icon links */ + display: block; + width: 30px; + height: 30px; + padding: 0; + position: absolute; + top: 50%; + left: 0; + margin-top: -15px; + margin-left: 9px; +} + +dd.posts, dd.topics, dd.views, dd.extra, dd.mark { + width: 80px; + text-align: center; + line-height: 2.2em; + font-size: 1.2em; +} + +dd.posts, dd.topics, dd.views { + width: 95px; +} + +/* List in forum description */ +dl.row-item dt ol, +dl.row-item dt ul { + list-style-position: inside; + margin-left: 1em; +} + +dl.row-item dt li { + display: list-item; + list-style-type: inherit; +} + +dd.lastpost, dd.redirect, dd.moderation, dd.time, dd.info { + width: 250px; + font-size: 1.1em; +} + +dd.redirect { + line-height: 2.5em; +} + +dd.time { + line-height: 200%; +} + +dd.lastpost > span, ul.topiclist dd.info > span, ul.topiclist dd.time > span, dd.redirect > span, dd.moderation > span { + display: block; + padding-left: 5px; +} + +dd.extra, dd.mark { + line-height: 200%; +} + +dd.option { + width: 125px; + line-height: 200%; + text-align: center; + font-size: 1.1em; +} + +/* Post body styles +----------------------------------------*/ +.postbody { + padding: 0; + line-height: 1.48em; + width: 76%; + float: left; + position: relative; +} + +.postbody .ignore { + font-size: 1.1em; +} + +.postbody h3.first { + /* The first post on the page uses this */ + font-size: 1.7em; +} + +.postbody h3 { + /* Postbody requires a different h3 format - so change it here */ + float: left; + font-size: 1.5em; + padding: 2px 0 0 0; + margin-top: 0 !important; + margin-bottom: 0.3em !important; + text-transform: none; + border: none; + font-family: "Trebuchet MS", Verdana, Helvetica, Arial, sans-serif; + line-height: 125%; +} + +.postbody h3 img { + vertical-align: bottom; +} + +.has-profile .postbody h3 { + /* If there is a post-profile, we position the post-buttons differently */ + float: none !important; + margin-right: 180px; +} + +.postbody .content { + font-size: 1.3em; + overflow-x: auto; +} + +.postbody img.postimage { + max-width: 100%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.search .postbody { + width: 68% +} + +/* Topic review panel +----------------------------------------*/ +.panel .review { + margin-top: 2em; +} + +.topicreview { + padding-right: 5px; + overflow: auto; + height: 300px; +} + +.topicreview .postbody { + width: auto; + float: none; + margin: 0; + height: auto; +} + +.topicreview .post { + height: auto; +} + +.topicreview h2 { + border-bottom-width: 0; +} + +.post-ignore .postbody { + display: none; +} + +/* MCP Post details +----------------------------------------*/ +.post_details { + /* This will only work in IE7+, plus the others */ + overflow: auto; + max-height: 300px; +} + +/* Content container styles +----------------------------------------*/ +.content { + clear: both; + min-height: 3em; + overflow: hidden; + line-height: 1.4em; + font-family: "Lucida Grande", "Trebuchet MS", Verdana, Helvetica, Arial, sans-serif; + font-size: 1em; + padding-bottom: 1px; +} + +.content h2, .panel h2 { + font-weight: normal; + border-bottom: 1px solid transparent; + font-size: 1.6em; + margin-top: 0.5em; + margin-bottom: 0.5em; + padding-bottom: 0.5em; +} + +.panel h3 { + margin: 0.5em 0; +} + +.panel p { + font-size: 1.2em; + margin-bottom: 1em; + line-height: 1.4em; +} + +.content p { + font-family: "Lucida Grande", "Trebuchet MS", Verdana, Helvetica, Arial, sans-serif; + font-size: 1.2em; + margin-bottom: 1em; + line-height: 1.4em; +} + +dl.faq { + font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; + font-size: 1.1em; + margin-top: 1em; + margin-bottom: 2em; + line-height: 1.4em; +} + +dl.faq dt { + font-weight: bold; +} + +.content dl.faq { + font-size: 1.2em; + margin-bottom: 0.5em; +} + +.content li { + list-style-type: inherit; +} + +.content ul, .content ol { + margin: 0.8em 0 0.9em 3em; +} + +.posthilit { + padding: 0 2px 1px 2px; +} + +/* Post author */ +p.author { + margin-bottom: 0.6em; + padding: 0 0 5px 0; + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 1em; + line-height: 1.2em; + clear: both; +} + +/* Post signature */ +.signature { + margin-top: 1.5em; + padding-top: 0.2em; + font-size: 1.1em; + border-top: 1px solid transparent; + clear: left; + line-height: 140%; + overflow: hidden; + width: 100%; +} + +.signature.standalone { + border-top-width: 0; + margin-top: 0; +} + +dd .signature { + margin: 0; + padding: 0; + clear: none; + border: none; +} + +.signature li { + list-style-type: inherit; +} + +.signature ul, .signature ol { + margin: 0.8em 0 0.9em 3em; +} + +/* Post noticies */ +.notice { + font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; + width: auto; + margin-top: 1.5em; + padding-top: 0.2em; + font-size: 1em; + border-top: 1px dashed transparent; + clear: left; + line-height: 130%; +} + +/* Jump to post link for now */ +ul.searchresults { + list-style: none; + text-align: right; + clear: both; +} + +/* BB Code styles +----------------------------------------*/ +/* Quote block */ +blockquote { + border: 1px solid transparent; + font-size: 0.95em; + margin: 1em 1px 1em 25px; + overflow: hidden; + padding: 5px; +} + +blockquote blockquote { + /* Nested quotes */ + font-size: 1em; + margin: 1em 1px 1em 15px; +} + +blockquote cite { + /* Username/source of quoter */ + font-style: normal; + font-weight: bold; + display: block; + font-size: 0.9em; +} + +blockquote cite cite { + font-size: 1em; +} + +blockquote cite:before, .uncited:before { + padding-right: 5px; +} + +blockquote cite > div { + float: right; + font-weight: normal; +} + +.postbody .content li blockquote { + overflow: inherit; + margin-left: 0; +} + +/* Code block */ +.codebox { + border: 1px solid transparent; + font-size: 1em; + margin: 1em 0 1.2em 0; + word-wrap: normal; +} + +.codebox p { + text-transform: uppercase; + border-bottom: 1px solid transparent; + margin-bottom: 0; + padding: 3px; + font-size: 0.8em !important; + font-weight: bold; + display: block; +} + +blockquote .codebox { + margin-left: 0; +} + +.codebox code { + overflow: auto; + display: block; + height: auto; + max-height: 200px; + padding: 5px 3px; + font: 0.9em Monaco, "Andale Mono","Courier New", Courier, monospace; + line-height: 1.3em; +} + +/* Attachments +----------------------------------------*/ +.attachbox { + font-size: 13px; + float: left; + width: auto; + max-width: 100%; + margin: 5px 5px 5px 0; + padding: 6px; + border: 1px dashed transparent; + clear: left; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.attachbox dt { + font-family: Arial, Helvetica, sans-serif; + text-transform: uppercase; +} + +.attachbox dd { + margin-top: 4px; + padding-top: 4px; + clear: left; + border-top: 1px solid transparent; + overflow-x: auto; +} + +.attachbox dd dd { + border: none; +} + +.attachbox p { + line-height: 110%; + font-weight: normal; + clear: left; +} + +.attachbox p.stats +{ + line-height: 110%; + font-weight: normal; + clear: left; +} + +.attach-image { + margin: 3px 0; + max-width: 100%; +} + +.attach-image img { + border: 1px solid transparent; +/* cursor: move; */ + cursor: default; +} + +/* Inline image thumbnails */ +div.inline-attachment dl.thumbnail, div.inline-attachment dl.file { + display: block; + margin-bottom: 4px; +} + +div.inline-attachment p { + font-size: 100%; +} + +dl.file { + font-family: Verdana, Arial, Helvetica, sans-serif; + display: block; +} + +dl.file dt { + text-transform: none; + margin: 0; + padding: 0; + font-weight: bold; + font-family: Verdana, Arial, Helvetica, sans-serif; +} + +dl.file dd { + margin: 0; + padding: 0; +} + +dl.thumbnail img { + padding: 3px; + border: 1px solid transparent; + box-sizing: border-box; +} + +dl.thumbnail dd { + font-style: italic; + font-family: Verdana, Arial, Helvetica, sans-serif; +} + +.attachbox dl.thumbnail dd { + font-size: 100%; +} + +dl.thumbnail dt a:hover img { + border: 1px solid transparent; +} + +/* Post poll styles +----------------------------------------*/ +fieldset.polls { + font-family: "Trebuchet MS", Verdana, Helvetica, Arial, sans-serif; +} + +fieldset.polls dl { + margin-top: 5px; + border-top: 1px solid transparent; + padding: 5px 0 0 0; + line-height: 120%; +} + +fieldset.polls dl.voted { + font-weight: bold; +} + +fieldset.polls dt { + text-align: left; + float: left; + display: block; + width: 30%; + border-right: none; + padding: 0; + margin: 0; + font-size: 1.1em; +} + +fieldset.polls dd { + float: left; + width: 10%; + border-left: none; + padding: 0 5px; + margin-left: 0; + font-size: 1.1em; +} + +fieldset.polls dd.resultbar { + width: 50%; +} + +fieldset.polls dd input { + margin: 2px 0; +} + +fieldset.polls dd div { + text-align: right; + font-family: Arial, Helvetica, sans-serif; + font-weight: bold; + padding: 2px 2px 0 2px; + overflow: visible; + min-width: 8px; +} + +.pollbar1, .pollbar2, .pollbar3, .pollbar4, .pollbar5 { + border-bottom: 1px solid transparent; + border-right: 1px solid transparent; +} + +.vote-submitted { + font-size: 1.2em; + font-weight: bold; + text-align: center; +} + +/* Poster profile block +----------------------------------------*/ +.postprofile { + margin: 5px 0 10px 0; + min-height: 80px; + border: 1px solid transparent; + border-width: 0 0 0 1px; + width: 22%; + float: right; + display: inline; +} + +.postprofile dd, .postprofile dt { + line-height: 1.2em; + margin-left: 8px; +} + +.postprofile dd { + overflow: hidden; + text-overflow: ellipsis; +} + +.postprofile strong { + font-weight: normal; +} + +.postprofile dt.no-profile-rank, .postprofile dd.profile-rank, .postprofile .search-result-date { + margin-bottom: 10px; +} + +/* Post-profile avatars */ +.postprofile .has-avatar .avatar-container { + margin-bottom: 3px; + overflow: hidden; +} + +.postprofile .avatar { + display: block; + float: left; + max-width: 100%; +} + +.postprofile .avatar img { + display: block; + height: auto !important; + max-width: 100%; +} + +.postprofile .profile-posts a { + font-weight: normal; +} + +dd.profile-warnings { + font-weight: bold; +} + +dd.profile-contact { + overflow: visible; +} + +.profile-contact .dropdown-container { + display: inline-block; +} + +.profile-contact .icon_contact { + vertical-align: middle; +} + +.profile-contact .dropdown { + margin-right: -14px; +} + +.online { + background-image: none; + background-position: 100% 0; + background-repeat: no-repeat; +} + +/* Poster profile used by search*/ +.search .postprofile { + width: 30%; +} + +/* Profile used on view-profile */ +.profile-avatar img { + max-width: 100%; +} + +/* pm list in compose message if mass pm is enabled */ +dl.pmlist dt { + width: 60% !important; +} + +dl.pmlist dt textarea { + width: 95%; +} + +dl.pmlist dd { + margin-left: 61% !important; + margin-bottom: 2px; +} + +.action-bar div.dl_links { + padding: 10px 0 0 10px; +} + +div.dl_links { + display: inline-block; + text-transform: none; +} + +.dl_links strong { + font-weight: bold; +} + +.dl_links ul { + list-style-type: none; + margin: 0; + display: inline-block; +} + +.dl_links li { + display: inline-block; +} + +.attachment-filename { + width: 100%; +} + +.ellipsis-text { + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +table.fixed-width-table { + table-layout: fixed; +} + +/* Show scrollbars for items with overflow on iOS devices +----------------------------------------*/ +.postbody .content::-webkit-scrollbar, .topicreview::-webkit-scrollbar, .post_details::-webkit-scrollbar, .codebox code::-webkit-scrollbar, .attachbox dd::-webkit-scrollbar, .attach-image::-webkit-scrollbar, .dropdown-extended ul::-webkit-scrollbar { + width: 8px; + height: 8px; + -webkit-appearance: none; + background: rgba(0, 0, 0, .1); + border-radius: 3px; +} + +.postbody .content::-webkit-scrollbar-thumb, .topicreview::-webkit-scrollbar-thumb, .post_details::-webkit-scrollbar-thumb, .codebox code::-webkit-scrollbar-thumb, .attachbox dd::-webkit-scrollbar-thumb, .attach-image::-webkit-scrollbar-thumb, .dropdown-extended ul::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, .3); + border-radius: 3px; +} + +#memberlist tr.inactive, #team tr.inactive { + font-style: italic; +} diff --git a/styles/prosilver/theme/cp.css b/styles/prosilver/theme/cp.css new file mode 100644 index 0000000..0041417 --- /dev/null +++ b/styles/prosilver/theme/cp.css @@ -0,0 +1,375 @@ +/* Control Panel Styles +---------------------------------------- */ + + +/* Main CP box +----------------------------------------*/ +.cp-menu { + float:left; + width: 19%; + margin-top: 1em; + margin-bottom: 5px; +} + +.cp-main { + float: left; + width: 81%; +} + +.cp-main .content { + padding: 0; +} + +.panel-container .panel p { + font-size: 1.1em; +} + +.panel-container .panel ol { + margin-left: 2em; + font-size: 1.1em; +} + +.panel-container .panel li.row { + border-bottom: 1px solid transparent; + border-top: 1px solid transparent; +} + +ul.cplist { + margin-bottom: 5px; + border-top: 1px solid transparent; +} + +.panel-container .panel li.header dd, .panel-container .panel li.header dt { + margin-bottom: 2px; +} + +.panel-container table.table1 { + margin-bottom: 1em; +} + +.panel-container table.table1 thead th { + font-weight: bold; + border-bottom: 1px solid transparent; + padding: 5px; +} + +.panel-container table.table1 tbody th { + font-style: italic; + background-color: transparent !important; + border-bottom: none; +} + +.cp-main .pm-message { + border: 1px solid transparent; + margin: 10px 0; + width: auto; + float: none; +} + +.pm-message h2 { + padding-bottom: 5px; +} + +.cp-main .postbody h3, .cp-main .box2 h3 { + margin-top: 0; +} + +.panel-container .postbody p.author { + font-size: 1.1em; +} + +.cp-main .buttons { + margin-left: 0; +} + +.cp-main ul.linklist { + margin: 0; +} + +/* MCP Specific tweaks */ +.mcp-main .postbody { + width: 100%; +} + +.tabs-container h2 { + float: left; + margin-bottom: 0px; +} + +/* CP tabs shared +----------------------------------------*/ +.tabs, .minitabs { + line-height: normal; +} + +.tabs > ul, .minitabs > ul { + list-style: none; + margin: 0; + padding: 0; + position: relative; +} + +.tabs .tab, .minitabs .tab { + display: block; + float: left; + font-size: 1em; + font-weight: bold; + line-height: 1.4em; +} + +.tabs .tab > a, .minitabs .tab > a { + display: block; + padding: 5px 9px; + position: relative; + text-decoration: none; + white-space: nowrap; + cursor: pointer; +} + +/* CP tabbed menu +----------------------------------------*/ +.tabs { + margin: 20px 0 0 7px; +} + +.tabs .tab > a { + border: 1px solid transparent; + border-radius: 4px 4px 0 0; + margin: 1px 1px 0 0; +} + +.tabs .activetab > a { + margin-top: 0; + padding-bottom: 7px; +} + +/* Mini tabbed menu used in MCP +----------------------------------------*/ +.minitabs { + float: right; + margin: 15px 7px 0 0; + max-width: 50%; +} + +.minitabs .tab { + float: right; +} + +.minitabs .tab > a { + border-radius: 5px 5px 0 0; + margin-left: 2px; +} + +.minitabs .tab > a:hover { + text-decoration: none; +} + +/* Responsive tabs +----------------------------------------*/ +.responsive-tab { + position: relative; +} + +.responsive-tab > a.responsive-tab-link { + display: block; + font-size: 1.6em; + position: relative; + width: 16px; + line-height: 0.9em; + text-decoration: none; +} + +.responsive-tab .responsive-tab-link:before { + content: ''; + position: absolute; + left: 10px; + top: 7px; + height: .125em; + width: 14px; + border-bottom: 0.125em solid transparent; + border-top: 0.375em double transparent; +} + +.tabs .dropdown, .minitabs .dropdown { + top: 20px; + margin-right: -2px; + font-size: 1.1em; + font-weight: normal; +} + +.minitabs .dropdown { + margin-right: -4px; +} + +.tabs .dropdown-up .dropdown, .minitabs .dropdown-up .dropdown { + bottom: 20px; + top: auto; +} + +.tabs .dropdown li { + text-align: right; +} + +.minitabs .dropdown li { + text-align: left; +} + +/* UCP navigation menu +----------------------------------------*/ +/* Container for sub-navigation list */ +.navigation { + width: 100%; + padding-top: 36px; +} + +.navigation ul { + list-style: none; +} + +/* Default list state */ +.navigation li { + display: inline; + font-weight: bold; + margin: 1px 0; + padding: 0; +} + +/* Link styles for the sub-section links */ +.navigation a { + display: block; + padding: 5px; + margin: 1px 0; + text-decoration: none; +} + +.navigation a:hover { + text-decoration: none; +} + +/* Preferences pane layout +----------------------------------------*/ +.cp-main h2 { + border-bottom: none; + padding: 0; + margin-left: 10px; +} + +/* Friends list */ +.cp-mini { + margin: 10px 15px 10px 5px; + max-height: 200px; + overflow-y: auto; + padding: 5px 10px; + border-radius: 7px; +} + +dl.mini dt { + font-weight: bold; +} + +dl.mini dd { + padding-top: 4px; +} + +.friend-online { + font-weight: bold; +} + +.friend-offline { + font-style: italic; +} + +/* PM Styles +----------------------------------------*/ +/* Defined rules list for PM options */ +ol.def-rules { + padding-left: 0; +} + +ol.def-rules li { + line-height: 180%; + padding: 1px; +} + +/* PM marking colours */ +.pmlist li.bg1 { + padding: 0 3px; +} + +.pmlist li.bg2 { + padding: 0 3px; +} + +/* DEPRECATED 3.2.6 +.pmlist li.pm_message_reported_colour, .pm_message_reported_colour { + border-left-color: transparent; + border-right-color: transparent; +} +*/ + +.pmlist li.pm_message_reported_colour, .pm_message_reported_colour, +.pmlist li.pm_marked_colour, .pm_marked_colour, +.pmlist li.pm_replied_colour, .pm_replied_colour, +.pmlist li.pm_friend_colour, .pm_friend_colour, +.pmlist li.pm_foe_colour, .pm_foe_colour { + padding: 0; + border: solid 3px transparent; + border-width: 0 3px; +} + +.pm-legend { + border-left-width: 10px; + border-left-style: solid; + border-right-width: 0; + margin-bottom: 3px; + padding-left: 3px; +} + +/* Avatar gallery */ +.gallery label { + position: relative; + float: left; + margin: 10px; + padding: 5px; + width: auto; + border: 1px solid transparent; + text-align: center; +} + +/* Responsive *CP navigation +----------------------------------------*/ +@media only screen and (max-width: 900px), only screen and (max-device-width: 900px) +{ + .nojs .tabs a span, .nojs .minitabs a span { + max-width: 40px; + overflow: hidden; + text-overflow: ellipsis; + letter-spacing: -.5px; + } + + .cp-menu, .navigation, .cp-main { + float: none; + width: auto; + margin: 0; + } + + .navigation { + padding: 0; + margin: 0 auto; + max-width: 320px; + } + + .navigation a { + background-image: none; + } + + .navigation li:first-child a { + border-top-left-radius: 5px; + border-top-right-radius: 5px; + } + + .navigation li:last-child a { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; + } +} diff --git a/styles/prosilver/theme/en/icon_user_online.gif b/styles/prosilver/theme/en/icon_user_online.gif new file mode 100644 index 0000000..6b571ff Binary files /dev/null and b/styles/prosilver/theme/en/icon_user_online.gif differ diff --git a/styles/prosilver/theme/en/stylesheet.css b/styles/prosilver/theme/en/stylesheet.css new file mode 100644 index 0000000..604b299 --- /dev/null +++ b/styles/prosilver/theme/en/stylesheet.css @@ -0,0 +1,2 @@ +/* Online image */ +.online { background-image: url("./icon_user_online.gif"); } diff --git a/styles/prosilver/theme/forms.css b/styles/prosilver/theme/forms.css new file mode 100644 index 0000000..5646a7d --- /dev/null +++ b/styles/prosilver/theme/forms.css @@ -0,0 +1,429 @@ +/* Form Styles +---------------------------------------- */ + +/* General form styles +----------------------------------------*/ +fieldset { + border-width: 0; + font-family: Verdana, Helvetica, Arial, sans-serif; + font-size: 1.1em; +} + +input { + font-weight: normal; + vertical-align: middle; + padding: 0 3px; + font-size: 1em; + font-family: Verdana, Helvetica, Arial, sans-serif; +} + +select { + font-family: Verdana, Helvetica, Arial, sans-serif; + font-weight: normal; + cursor: pointer; + vertical-align: middle; + border: 1px solid transparent; + padding: 1px; + font-size: 1em; +} + +select:focus { + outline-style: none; +} + +option { + padding-right: 1em; +} + +select optgroup option { + padding-right: 1em; + font-family: Verdana, Helvetica, Arial, sans-serif; +} + +textarea { + font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; + width: 60%; + padding: 2px; + font-size: 1em; + line-height: 1.4em; +} + +label { + cursor: default; + padding-right: 5px; +} + +label input { + vertical-align: middle; +} + +label img { + vertical-align: middle; +} + +/* Definition list layout for forms +---------------------------------------- */ +fieldset dl { + padding: 4px 0; +} + +fieldset dt { + float: left; + width: 40%; + text-align: left; + display: block; +} + +fieldset dd { + margin-left: 41%; + vertical-align: top; + margin-bottom: 3px; +} + +/* Specific layout 1 */ +fieldset.fields1 dt { + width: 15em; + border-right-width: 0; +} + +fieldset.fields1 dd { + margin-left: 15em; + border-left-width: 0; +} + +fieldset.fields1 div { + margin-bottom: 3px; +} + +/* Set it back to 0px for the reCaptcha divs: PHPBB3-9587 */ +fieldset.fields1 .live-search div { + margin-bottom: 0; +} + +/* Specific layout 2 */ +fieldset.fields2 dt { + width: 15em; + border-right-width: 0; +} + +fieldset.fields2 dd { + margin-left: 16em; + border-left-width: 0; +} + +/* Form elements */ +dt label { + font-weight: bold; + text-align: left; +} + +dd label { + white-space: nowrap; +} + +dd input, dd textarea { + margin-right: 3px; +} + +dd select { + width: auto; +} + +dd select[multiple] { + width: 100%; +} + +dd textarea { + width: 85%; +} + +/* Hover effects */ +.timezone { + width: 95%; +} + +/* Browser-specific tweaks */ +button::-moz-focus-inner { + padding: 0; + border: 0 +} + +/* Quick-login on index page */ +fieldset.quick-login { + margin-top: 5px; +} + +fieldset.quick-login input { + width: auto; +} + +fieldset.quick-login input.inputbox { + width: 15%; + vertical-align: middle; + margin-right: 5px; +} + +fieldset.quick-login label { + white-space: nowrap; + padding-right: 2px; +} + +/* Display options on viewtopic/viewforum pages */ +fieldset.display-options { + text-align: center; + margin: 3px 0 5px 0; +} + +fieldset.display-options label { + white-space: nowrap; + padding-right: 2px; +} + +fieldset.display-options a { + margin-top: 3px; +} + +.dropdown fieldset.display-options { + font-size: 1em; + margin: 0; + padding: 0; +} + +.dropdown fieldset.display-options label { + display: block; + margin: 4px; + padding: 0; + text-align: right; + white-space: nowrap; +} + +.dropdown fieldset.display-options select { + min-width: 120px; +} + +/* Display actions for ucp and mcp pages */ +fieldset.display-actions { + text-align: right; + line-height: 2em; + white-space: nowrap; + padding-right: 1em; +} + +fieldset.display-actions label { + white-space: nowrap; + padding-right: 2px; +} + +fieldset.sort-options { + line-height: 2em; +} + +/* MCP forum selection*/ +fieldset.forum-selection { + margin: 5px 0 3px 0; + float: right; +} + +fieldset.forum-selection2 { + margin: 13px 0 3px 0; + float: right; +} + +/* Submit button fieldset */ +fieldset.submit-buttons { + text-align: center; + vertical-align: middle; + margin: 5px 0; +} + +fieldset.submit-buttons input { + vertical-align: middle; +} + +/* Posting page styles +----------------------------------------*/ + +/* Buttons used in the editor */ +.format-buttons { + margin: 15px 0 2px 0; +} + +.format-buttons input, .format-buttons select { + vertical-align: middle; +} + +/* Main message box */ +.message-box { + width: 80%; +} + +.message-box textarea { + font-family: "Trebuchet MS", Verdana, Helvetica, Arial, sans-serif; + width: 450px; + height: 270px; + min-width: 100%; + max-width: 100%; + font-size: 1.2em; + resize: vertical; + outline: 3px dashed transparent; + outline-offset: -4px; + -webkit-transition: all .5s ease, height 1ms linear; + -moz-transition: all .5s ease, height 1ms linear; + -ms-transition: all .5s ease, height 1ms linear; + -o-transition: all .5s ease, height 1ms linear; + transition: all .5s ease, height 1ms linear; +} + +/* Emoticons panel */ +.smiley-box { + width: 18%; + float: right; +} + +.smiley-box img { + margin: 3px; +} + +/* Input field styles +---------------------------------------- */ +.inputbox { + border: 1px solid transparent; + padding: 2px; +} + +.inputbox:hover, .inputbox:focus { + border: 1px solid transparent; + outline-style: none; +} + +input.inputbox { width: 85%; } +input.medium { width: 50%; } +input.narrow { width: 25%; } +input.tiny { width: 150px; } + +textarea.inputbox { + width: 85%; +} + +.autowidth { + width: auto !important; +} + +input[type="number"] { + -moz-padding-end: 0; +} + +input[type="search"] { + -webkit-appearance: textfield; + -webkit-box-sizing: content-box; +} + +input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-results-button, input[type="search"]::-webkit-search-results-decoration { + display: none; +} + +input[type="search"]::-webkit-search-cancel-button { + cursor: pointer; +} + +/* Form button styles +---------------------------------------- */ +input.button1, input.button2 { + font-size: 1em; +} + +a.button1, input.button1, input.button3, a.button2, input.button2 { + width: auto !important; + padding-top: 1px; + padding-bottom: 1px; + font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; + background: transparent none repeat-x top left; + line-height: 1.5; +} + +a.button1, input.button1 { + font-weight: bold; + border: 1px solid transparent; +} + +input.button3 { + padding: 0; + margin: 0; + line-height: 5px; + height: 12px; + background-image: none; + font-variant: small-caps; +} + +input[type="button"], input[type="submit"], input[type="reset"], input[type="checkbox"], input[type="radio"] { + cursor: pointer; +} + +/* Alternative button */ +a.button2, input.button2, input.button3 { + border: 1px solid transparent; +} + +/* button in the style of the form buttons */ +a.button1, a.button2 { + text-decoration: none; + padding: 0 3px; + vertical-align: text-bottom; +} + +/* Hover states */ +a.button1:hover, input.button1:hover, a.button2:hover, input.button2:hover, input.button3:hover { + border: 1px solid transparent; +} + +input.disabled { + font-weight: normal; +} + +/* Focus states */ +input.button1:focus, input.button2:focus, input.button3:focus { + outline-style: none; +} + +/* Topic and forum Search */ +.search-box { + float: left; +} + +.search-box .inputbox { + background-image: none; + border-right-width: 0; + border-radius: 4px 0 0 4px; + float: left; + height: 24px; + padding: 3px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* Search box (header) +--------------------------------------------- */ +.search-header { + border-radius: 4px; + display: block; + float: right; + margin-right: 5px; + margin-top: 30px; +} + +.search-header .inputbox { border: 0; } + +.navbar .linklist > li.responsive-search { display: none; } + +input.search { + background-image: none; + background-repeat: no-repeat; + background-position: left 1px; + padding-left: 17px; +} + +.full { width: 95%; } +.medium { width: 50%;} +.narrow { width: 25%;} +.tiny { width: 10%;} diff --git a/styles/prosilver/theme/fr/icon_user_online.gif b/styles/prosilver/theme/fr/icon_user_online.gif new file mode 100644 index 0000000..99b6707 Binary files /dev/null and b/styles/prosilver/theme/fr/icon_user_online.gif differ diff --git a/styles/prosilver/theme/fr/stylesheet.css b/styles/prosilver/theme/fr/stylesheet.css new file mode 100644 index 0000000..604b299 --- /dev/null +++ b/styles/prosilver/theme/fr/stylesheet.css @@ -0,0 +1,2 @@ +/* Online image */ +.online { background-image: url("./icon_user_online.gif"); } diff --git a/styles/prosilver/theme/icons.css b/styles/prosilver/theme/icons.css new file mode 100644 index 0000000..6643f12 --- /dev/null +++ b/styles/prosilver/theme/icons.css @@ -0,0 +1,96 @@ +/* -------------------------------------------------------------- + $Icons +-------------------------------------------------------------- */ + +/* Global module setup +--------------------------------*/ + +/* Renamed version of .fa class for agnostic useage of icon fonts. + * Just change the name of the font after the 14/1 to the name of + * the font you wish to use. + */ +.icon, .button .icon, blockquote cite:before, .uncited:before { + display: inline-block; + font-weight: normal; + font-style: normal; + font-variant: normal; + font-family: FontAwesome; + font-size: 14px; + line-height: 1; + text-rendering: auto; /* optimizelegibility throws things off #1094 */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon:before { padding-right: 2px; } + +.button .icon:before { + padding-right: 0; +} + +/* Icon size classes - Default size is 14px, use these for small variations */ + +.icon.icon-xl { + font-size: 20px; +} + +.icon.icon-lg { + font-size: 16px; +} + +.icon.icon-md { + font-size: 10px; +} + +.icon.icon-sm { + font-size: 8px; +} + +/* icon modifiers */ +.icon-tiny { + width: 12px; + transform: scale(0.65, 0.75); + vertical-align: text-bottom; + font-size: 16px; +} + +.arrow-left .icon { + float: left; +} + +.arrow-left:hover .icon { + margin-left: -5px; + margin-right: 5px; +} + +.arrow-right .icon { + float: right; +} + +.arrow-right:hover .icon { + margin-left: 5px; + margin-right: -5px; +} + +.post-buttons .dropdown-contents .icon { + float: right; + margin-left: 5px; +} + +.alert_close .icon:before { + padding: 0; + border-radius: 50%; + width: 11px; + display: block; + line-height: .9; + height: 12px; +} + +blockquote cite:before, .uncited:before { + content: '\f10d'; /* Font Awesome quote-left */ +} + +.rtl blockquote cite:before, .rtl .uncited:before { + content: '\f10e'; /* Font Awesome quote-right */ +} + diff --git a/styles/prosilver/theme/images/announce_read.gif b/styles/prosilver/theme/images/announce_read.gif new file mode 100644 index 0000000..a3b3d7b Binary files /dev/null and b/styles/prosilver/theme/images/announce_read.gif differ diff --git a/styles/prosilver/theme/images/announce_read_locked.gif b/styles/prosilver/theme/images/announce_read_locked.gif new file mode 100644 index 0000000..0a6cf64 Binary files /dev/null and b/styles/prosilver/theme/images/announce_read_locked.gif differ diff --git a/styles/prosilver/theme/images/announce_read_locked_mine.gif b/styles/prosilver/theme/images/announce_read_locked_mine.gif new file mode 100644 index 0000000..56af0ab Binary files /dev/null and b/styles/prosilver/theme/images/announce_read_locked_mine.gif differ diff --git a/styles/prosilver/theme/images/announce_read_mine.gif b/styles/prosilver/theme/images/announce_read_mine.gif new file mode 100644 index 0000000..c333e3b Binary files /dev/null and b/styles/prosilver/theme/images/announce_read_mine.gif differ diff --git a/styles/prosilver/theme/images/announce_unread.gif b/styles/prosilver/theme/images/announce_unread.gif new file mode 100644 index 0000000..9f75cc3 Binary files /dev/null and b/styles/prosilver/theme/images/announce_unread.gif differ diff --git a/styles/prosilver/theme/images/announce_unread_locked.gif b/styles/prosilver/theme/images/announce_unread_locked.gif new file mode 100644 index 0000000..4ad85bb Binary files /dev/null and b/styles/prosilver/theme/images/announce_unread_locked.gif differ diff --git a/styles/prosilver/theme/images/announce_unread_locked_mine.gif b/styles/prosilver/theme/images/announce_unread_locked_mine.gif new file mode 100644 index 0000000..30db894 Binary files /dev/null and b/styles/prosilver/theme/images/announce_unread_locked_mine.gif differ diff --git a/styles/prosilver/theme/images/announce_unread_mine.gif b/styles/prosilver/theme/images/announce_unread_mine.gif new file mode 100644 index 0000000..3a2cbca Binary files /dev/null and b/styles/prosilver/theme/images/announce_unread_mine.gif differ diff --git a/styles/prosilver/theme/images/bg_header.gif b/styles/prosilver/theme/images/bg_header.gif new file mode 100644 index 0000000..351de9f Binary files /dev/null and b/styles/prosilver/theme/images/bg_header.gif differ diff --git a/styles/prosilver/theme/images/bg_list.gif b/styles/prosilver/theme/images/bg_list.gif new file mode 100644 index 0000000..89f8963 Binary files /dev/null and b/styles/prosilver/theme/images/bg_list.gif differ diff --git a/styles/prosilver/theme/images/forum_link.gif b/styles/prosilver/theme/images/forum_link.gif new file mode 100644 index 0000000..09f8dfa Binary files /dev/null and b/styles/prosilver/theme/images/forum_link.gif differ diff --git a/styles/prosilver/theme/images/forum_read.gif b/styles/prosilver/theme/images/forum_read.gif new file mode 100644 index 0000000..891fa20 Binary files /dev/null and b/styles/prosilver/theme/images/forum_read.gif differ diff --git a/styles/prosilver/theme/images/forum_read_locked.gif b/styles/prosilver/theme/images/forum_read_locked.gif new file mode 100644 index 0000000..2348240 Binary files /dev/null and b/styles/prosilver/theme/images/forum_read_locked.gif differ diff --git a/styles/prosilver/theme/images/forum_read_subforum.gif b/styles/prosilver/theme/images/forum_read_subforum.gif new file mode 100644 index 0000000..5b4d30f Binary files /dev/null and b/styles/prosilver/theme/images/forum_read_subforum.gif differ diff --git a/styles/prosilver/theme/images/forum_unread.gif b/styles/prosilver/theme/images/forum_unread.gif new file mode 100644 index 0000000..e925da8 Binary files /dev/null and b/styles/prosilver/theme/images/forum_unread.gif differ diff --git a/styles/prosilver/theme/images/forum_unread_locked.gif b/styles/prosilver/theme/images/forum_unread_locked.gif new file mode 100644 index 0000000..5ff59b7 Binary files /dev/null and b/styles/prosilver/theme/images/forum_unread_locked.gif differ diff --git a/styles/prosilver/theme/images/forum_unread_subforum.gif b/styles/prosilver/theme/images/forum_unread_subforum.gif new file mode 100644 index 0000000..7d6ddb9 Binary files /dev/null and b/styles/prosilver/theme/images/forum_unread_subforum.gif differ diff --git a/styles/prosilver/theme/images/icon_download.gif b/styles/prosilver/theme/images/icon_download.gif new file mode 100644 index 0000000..70cd61c Binary files /dev/null and b/styles/prosilver/theme/images/icon_download.gif differ diff --git a/styles/prosilver/theme/images/icon_offline.gif b/styles/prosilver/theme/images/icon_offline.gif new file mode 100644 index 0000000..5dc4212 Binary files /dev/null and b/styles/prosilver/theme/images/icon_offline.gif differ diff --git a/styles/prosilver/theme/images/icon_online.gif b/styles/prosilver/theme/images/icon_online.gif new file mode 100644 index 0000000..d0d202d Binary files /dev/null and b/styles/prosilver/theme/images/icon_online.gif differ diff --git a/styles/prosilver/theme/images/icon_rate_bad.gif b/styles/prosilver/theme/images/icon_rate_bad.gif new file mode 100644 index 0000000..7901889 Binary files /dev/null and b/styles/prosilver/theme/images/icon_rate_bad.gif differ diff --git a/styles/prosilver/theme/images/icon_rate_good.gif b/styles/prosilver/theme/images/icon_rate_good.gif new file mode 100644 index 0000000..6d23034 Binary files /dev/null and b/styles/prosilver/theme/images/icon_rate_good.gif differ diff --git a/styles/prosilver/theme/images/icons_contact.png b/styles/prosilver/theme/images/icons_contact.png new file mode 100644 index 0000000..f84abd3 Binary files /dev/null and b/styles/prosilver/theme/images/icons_contact.png differ diff --git a/styles/prosilver/theme/images/index.htm b/styles/prosilver/theme/images/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/prosilver/theme/images/loading.gif b/styles/prosilver/theme/images/loading.gif new file mode 100644 index 0000000..e1ed088 Binary files /dev/null and b/styles/prosilver/theme/images/loading.gif differ diff --git a/styles/prosilver/theme/images/no_avatar.gif b/styles/prosilver/theme/images/no_avatar.gif new file mode 100644 index 0000000..ad73330 Binary files /dev/null and b/styles/prosilver/theme/images/no_avatar.gif differ diff --git a/styles/prosilver/theme/images/plupload/done.gif b/styles/prosilver/theme/images/plupload/done.gif new file mode 100644 index 0000000..29f3ed7 Binary files /dev/null and b/styles/prosilver/theme/images/plupload/done.gif differ diff --git a/styles/prosilver/theme/images/plupload/error.gif b/styles/prosilver/theme/images/plupload/error.gif new file mode 100644 index 0000000..4682b63 Binary files /dev/null and b/styles/prosilver/theme/images/plupload/error.gif differ diff --git a/styles/prosilver/theme/images/plupload/throbber.gif b/styles/prosilver/theme/images/plupload/throbber.gif new file mode 100644 index 0000000..4ae8b16 Binary files /dev/null and b/styles/prosilver/theme/images/plupload/throbber.gif differ diff --git a/styles/prosilver/theme/images/quote.gif b/styles/prosilver/theme/images/quote.gif new file mode 100644 index 0000000..d199227 Binary files /dev/null and b/styles/prosilver/theme/images/quote.gif differ diff --git a/styles/prosilver/theme/images/quote_rtl.gif b/styles/prosilver/theme/images/quote_rtl.gif new file mode 100644 index 0000000..ac719cf Binary files /dev/null and b/styles/prosilver/theme/images/quote_rtl.gif differ diff --git a/styles/prosilver/theme/images/site_logo.gif b/styles/prosilver/theme/images/site_logo.gif new file mode 100644 index 0000000..2517fbe Binary files /dev/null and b/styles/prosilver/theme/images/site_logo.gif differ diff --git a/styles/prosilver/theme/images/sticky_read.gif b/styles/prosilver/theme/images/sticky_read.gif new file mode 100644 index 0000000..e8142dd Binary files /dev/null and b/styles/prosilver/theme/images/sticky_read.gif differ diff --git a/styles/prosilver/theme/images/sticky_read_locked.gif b/styles/prosilver/theme/images/sticky_read_locked.gif new file mode 100644 index 0000000..fcd8b85 Binary files /dev/null and b/styles/prosilver/theme/images/sticky_read_locked.gif differ diff --git a/styles/prosilver/theme/images/sticky_read_locked_mine.gif b/styles/prosilver/theme/images/sticky_read_locked_mine.gif new file mode 100644 index 0000000..0a8dc2a Binary files /dev/null and b/styles/prosilver/theme/images/sticky_read_locked_mine.gif differ diff --git a/styles/prosilver/theme/images/sticky_read_mine.gif b/styles/prosilver/theme/images/sticky_read_mine.gif new file mode 100644 index 0000000..37c4ed0 Binary files /dev/null and b/styles/prosilver/theme/images/sticky_read_mine.gif differ diff --git a/styles/prosilver/theme/images/sticky_unread.gif b/styles/prosilver/theme/images/sticky_unread.gif new file mode 100644 index 0000000..88a212d Binary files /dev/null and b/styles/prosilver/theme/images/sticky_unread.gif differ diff --git a/styles/prosilver/theme/images/sticky_unread_locked.gif b/styles/prosilver/theme/images/sticky_unread_locked.gif new file mode 100644 index 0000000..0241da2 Binary files /dev/null and b/styles/prosilver/theme/images/sticky_unread_locked.gif differ diff --git a/styles/prosilver/theme/images/sticky_unread_locked_mine.gif b/styles/prosilver/theme/images/sticky_unread_locked_mine.gif new file mode 100644 index 0000000..8d69b44 Binary files /dev/null and b/styles/prosilver/theme/images/sticky_unread_locked_mine.gif differ diff --git a/styles/prosilver/theme/images/sticky_unread_mine.gif b/styles/prosilver/theme/images/sticky_unread_mine.gif new file mode 100644 index 0000000..6529102 Binary files /dev/null and b/styles/prosilver/theme/images/sticky_unread_mine.gif differ diff --git a/styles/prosilver/theme/images/topic_moved.gif b/styles/prosilver/theme/images/topic_moved.gif new file mode 100644 index 0000000..8e9c1f4 Binary files /dev/null and b/styles/prosilver/theme/images/topic_moved.gif differ diff --git a/styles/prosilver/theme/images/topic_read.gif b/styles/prosilver/theme/images/topic_read.gif new file mode 100644 index 0000000..5ed739e Binary files /dev/null and b/styles/prosilver/theme/images/topic_read.gif differ diff --git a/styles/prosilver/theme/images/topic_read_hot.gif b/styles/prosilver/theme/images/topic_read_hot.gif new file mode 100644 index 0000000..81a42d0 Binary files /dev/null and b/styles/prosilver/theme/images/topic_read_hot.gif differ diff --git a/styles/prosilver/theme/images/topic_read_hot_mine.gif b/styles/prosilver/theme/images/topic_read_hot_mine.gif new file mode 100644 index 0000000..b98808c Binary files /dev/null and b/styles/prosilver/theme/images/topic_read_hot_mine.gif differ diff --git a/styles/prosilver/theme/images/topic_read_locked.gif b/styles/prosilver/theme/images/topic_read_locked.gif new file mode 100644 index 0000000..61bb1ef Binary files /dev/null and b/styles/prosilver/theme/images/topic_read_locked.gif differ diff --git a/styles/prosilver/theme/images/topic_read_locked_mine.gif b/styles/prosilver/theme/images/topic_read_locked_mine.gif new file mode 100644 index 0000000..dbe9019 Binary files /dev/null and b/styles/prosilver/theme/images/topic_read_locked_mine.gif differ diff --git a/styles/prosilver/theme/images/topic_read_mine.gif b/styles/prosilver/theme/images/topic_read_mine.gif new file mode 100644 index 0000000..8fb165c Binary files /dev/null and b/styles/prosilver/theme/images/topic_read_mine.gif differ diff --git a/styles/prosilver/theme/images/topic_unread.gif b/styles/prosilver/theme/images/topic_unread.gif new file mode 100644 index 0000000..43ea76b Binary files /dev/null and b/styles/prosilver/theme/images/topic_unread.gif differ diff --git a/styles/prosilver/theme/images/topic_unread_hot.gif b/styles/prosilver/theme/images/topic_unread_hot.gif new file mode 100644 index 0000000..a45bc4b Binary files /dev/null and b/styles/prosilver/theme/images/topic_unread_hot.gif differ diff --git a/styles/prosilver/theme/images/topic_unread_hot_mine.gif b/styles/prosilver/theme/images/topic_unread_hot_mine.gif new file mode 100644 index 0000000..dc67326 Binary files /dev/null and b/styles/prosilver/theme/images/topic_unread_hot_mine.gif differ diff --git a/styles/prosilver/theme/images/topic_unread_locked.gif b/styles/prosilver/theme/images/topic_unread_locked.gif new file mode 100644 index 0000000..68dd342 Binary files /dev/null and b/styles/prosilver/theme/images/topic_unread_locked.gif differ diff --git a/styles/prosilver/theme/images/topic_unread_locked_mine.gif b/styles/prosilver/theme/images/topic_unread_locked_mine.gif new file mode 100644 index 0000000..4f5a36e Binary files /dev/null and b/styles/prosilver/theme/images/topic_unread_locked_mine.gif differ diff --git a/styles/prosilver/theme/images/topic_unread_mine.gif b/styles/prosilver/theme/images/topic_unread_mine.gif new file mode 100644 index 0000000..24e9817 Binary files /dev/null and b/styles/prosilver/theme/images/topic_unread_mine.gif differ diff --git a/styles/prosilver/theme/index.htm b/styles/prosilver/theme/index.htm new file mode 100644 index 0000000..e69de29 diff --git a/styles/prosilver/theme/links.css b/styles/prosilver/theme/links.css new file mode 100644 index 0000000..6a61e9a --- /dev/null +++ b/styles/prosilver/theme/links.css @@ -0,0 +1,199 @@ +/* Link Styles +---------------------------------------- */ + +/* Links adjustment to correctly display an order of rtl/ltr mixed content */ +a { + direction: ltr; + unicode-bidi: embed; + text-decoration: none; + /* we use links inline more often then not so to address several bugs with + IE and some other browsers we render all links as inlineblock by default */ + display: inline-block; + +} + +/* Coloured usernames */ +.username-coloured { + font-weight: bold; + display: inline !important; + padding: 0 !important; +} + +/* Links on gradient backgrounds */ +.forumbg .header a, .forabg .header a, th a { + text-decoration: none; +} + +.forumbg .header a:hover, .forabg .header a:hover, th a:hover { + text-decoration: underline; +} + +/* Notification mark read link */ +.dropdown-extended a.mark_read { + background-position: center center; + background-repeat: no-repeat; + border-radius: 3px 0 0 3px; + display: none; + margin-top: -20px; + position: absolute; + z-index: 2; + right: 0; + top: 50%; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.dropdown-extended li:hover a.mark_read { + display: block; +} + +.dropdown-extended a.mark_read:hover { + width: 50px; +} + +.jumpbox-cat-link, +.jumpbox-forum-link { font-weight: bold; } + + +/* Links for forum/topic lists */ +a.forumtitle { + font-family: "Trebuchet MS", Helvetica, Arial, Sans-serif; + font-size: 1.2em; + font-weight: bold; + text-decoration: none; +} + +a.forumtitle:hover { + text-decoration: underline; +} + +a.topictitle { + font-family: "Trebuchet MS", Helvetica, Arial, Sans-serif; + font-size: 1.2em; + font-weight: bold; + text-decoration: none; + display: inline; +} + +a.topictitle:hover { + text-decoration: underline; +} + +a.lastsubject { + font-weight: bold; + text-decoration: none; +} + +a.lastsubject:hover { + text-decoration: underline; +} + +.row-item a:hover { + text-decoration: none +} + +.row-item .topictitle:hover, +.row-item .subforum:hover, +.row-item .username:hover, +.row-item .username-coloured:hover { + text-decoration: underline; +} + +/* Post body links */ +.postlink { + text-decoration: none; + border-bottom: 1px solid transparent; + padding-bottom: 0; +} + +.postlink:hover { + text-decoration: none; +} + +.signature a, .signature a:hover { + border: none; + text-decoration: underline; +} + +/* Profile links */ +.postprofile a, .postprofile dt.author a { + font-weight: bold; + text-decoration: none; +} + +.postprofile a:hover, .postprofile dt.author a:hover { + text-decoration: underline; +} + +/* Profile searchresults */ +.search .postprofile a { + text-decoration: none; + font-weight: normal; +} + +.search .postprofile a:hover { + text-decoration: underline; +} + +.top { + font-size: 12px; + text-decoration: none; + margin-top: 10px; +} + +/* Back to top of page */ +.back2top { + clear: both; +} + +.back2top .top { + float: right; + margin-right: -10px; + margin-top: 0; +} + +/* Arrow links */ + +.arrow-up { + padding-left: 10px; + text-decoration: none; + border-bottom-width: 0; +} + +.arrow-up:hover { + +} + +.arrow-down { + padding-right: 10px; +} + +.arrow-down:hover { + +} + +.arrow-left:hover { + text-decoration: none; +} + +.arrow-right:hover { + text-decoration: none; +} + +/* invisible skip link, used for accessibility */ +.skiplink { + position: absolute; + left: -999px; + width: 990px; +} + +/* Feed icon in forumlist_body.html */ +a.feed-icon-forum { + float: right; + margin: 3px; +} + +a.anchor { + display: block; +} diff --git a/styles/prosilver/theme/normalize.css b/styles/prosilver/theme/normalize.css new file mode 100644 index 0000000..23d8449 --- /dev/null +++ b/styles/prosilver/theme/normalize.css @@ -0,0 +1,424 @@ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS and IE text size adjust after device orientation change, + * without disabling user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability of focused elements when they are also in an + * active/hover state. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + box-sizing: content-box; /* 2 */ +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/styles/prosilver/theme/plupload.css b/styles/prosilver/theme/plupload.css new file mode 100644 index 0000000..f466803 --- /dev/null +++ b/styles/prosilver/theme/plupload.css @@ -0,0 +1,86 @@ +.attach-panel-multi { + display: none; + margin-bottom: 1em; +} + +.file-list td { + vertical-align: middle; +} + +.attach-name { + width: 50%; +} + +.attach-comment { + width: 30%; +} + +.attach-comment .inputbox { + resize: vertical; + width: 100%; +} + +.attach-filesize { + width: 15%; +} + +.attach-status { + width: 5%; +} + +.attach-filesize, .attach-status { + text-align: center; +} + +.attach-controls { + display: inline-block; + float: right; +} + +.nojs .file-inline-bbcode { + display: none; +} + +.file-total-progress { + height: 2px; + display: block; + position: relative; + margin: 4px -10px -6px -10px; +} + +.file-progress { + background-color: #CCCCCC; + display:inline-block; + height: 8px; + width: 50px; +} + +.file-progress-bar, .file-total-progress-bar { + background-color: green; + display: block; + height: 100%; + width: 0; +} + +.file-status.file-working { + background: url('./images/plupload/throbber.gif'); +} + +.file-status.file-uploaded { + background: url('./images/plupload/done.gif'); +} + +.file-status.file-error { + background: url('./images/plupload/error.gif'); +} + +.file-status { + display: inline-block; + height: 16px; + width: 16px; +} + +.file-name { + max-width: 65%; + vertical-align: bottom; +} diff --git a/styles/prosilver/theme/print.css b/styles/prosilver/theme/print.css new file mode 100644 index 0000000..9445279 --- /dev/null +++ b/styles/prosilver/theme/print.css @@ -0,0 +1,150 @@ +/* Print Style Sheet +---------------------------------------- */ + + +/* Lots still TODO here! */ + +/* General markup styles */ +* { + padding: 0; + margin: 0; +} + +body { + font: 11pt Verdana, Arial, Helvetica, sans-serif; + color:#000000; +} + +a:link { color: #000000; text-decoration: none; } +a:visited { color: #000000; text-decoration: none; } +a:active { color: #000000; text-decoration: none; } + +img, .noprint, .navbar, .box1, .divider, .signature { display: none; } +/* Display smilies (Bug #47265) */ +.content img { + display: inline; +} + +/* Container for the main body */ +.wrap { + margin: 0 2em; +} + +p { font-size: 85%; } +.copyright { font-size: 75%; } +.page-number { float:right; width: auto; text-align: right; font-size: 75%; } + +h1, h2, h3, h1 a, h2 a, h3 a { + font-family: "Trebuchet MS",georgia,Verdana,Sans-serif; + color: #000000; + background: none; + text-decoration: none; + font-weight: bold; +} + +h1 { font-size: 20pt; } +h2 { font-size: 16pt; margin-top: 1em; } +h3 { font-size: 14pt; margin-top: 1em; } + +.content { + font-size: 11pt; + line-height: 14pt; + margin-bottom: 1em; + font-family: "Lucida Grande", "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; + overflow: hidden; +} + +/* CSS2 Print tip from: http://www.alistapart.com/articles/goingtoprint/ */ +.postbody a:link, .postbody a:visited, .postbody a:hover, .postbody a:active { + text-decoration: underline; + padding: 0.1em 0.2em; + margin: -0.1em -0.2em; + color: #666; + background: none; + font-size: 100%; +} + +html>body .postbody a:link:after, html>body .postbody a:visited:after { + content: " (" attr(href) ") "; + font-size: 90%; + text-decoration: none; +} + +hr { + height: 1px; + background-color: #999999; + border-width: 0; +} + +.author { + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 75%; + margin-bottom: 0.6em; +} + +.date { + font-family: Verdana, Arial, Helvetica, sans-serif; + float: right; + position: relative; + text-align: right; + font-size: 75%; +} + +/* Dont want to print url for names or titles in content area */ +.postbody .author a:link, .postbody .author a:visited, +html>body .postbody .author a:link:after, +html>body .postbody .author a:visited:after, +.postquote .quote-by a:link, .postquote .quote-by a:visited, +html>body .postquote .quote-by a:link:after, +html>body .postquote .quote-by a:visited:after, +html>body .postbody h1 a:link:after, html>body .postbody h2 a:link:after { + text-decoration: none; + content: ""; +} + +/* Poster profile */ +.postprofile { display: none; } +.grip-show { display:none; } + +/* Quote */ +.postquote, blockquote { + font-size: 85%; + margin: 1em 18% 1em 4%; + padding: 0.5em; + position: relative; + line-height: 1.5em; + border: 1px #999999 solid; +} + +.postquote img { display: none; } +.postquote span { display: block; } +.postquote span .postquote { font-size: 100%; } +.quote-by, blockquote cite { + color: black; + display : block; + font-weight: bold; +} + +/* List */ +ol, ul { + margin-left: 15pt +} + +/* Misc page elements */ +div.spacer { clear: both; } + +code { display: block; } + +/* Accessibility tweaks: Mozilla.org */ +.skip_link { display: none; } + +.codebox p { display: none; } + +/* stylelint-disable declaration-property-unit-whitelist */ +.emoji { + min-height: 18px; + min-width: 18px; + height: 1em; + width: 1em; +} +/* stylelint-enable declaration-property-unit-whitelist */ diff --git a/styles/prosilver/theme/responsive.css b/styles/prosilver/theme/responsive.css new file mode 100644 index 0000000..ca4054c --- /dev/null +++ b/styles/prosilver/theme/responsive.css @@ -0,0 +1,583 @@ +/* Responsive Design +---------------------------------------- */ + +@media (max-width: 320px) { + select, .inputbox { + max-width: 240px; + } +} + +/* Notifications list +----------------------------------------*/ +@media (max-width: 350px) { + .dropdown-extended .dropdown-contents { + width: auto; + } +} + +@media (max-width: 430px) { + .action-bar .search-box .inputbox { + width: 120px; + } + + .section-viewtopic .search-box .inputbox { + width: 57px; + } + + .action-bar .search-box .inputbox ::-moz-placeholder { + content: "Search..."; + } + + .action-bar .search-box .inputbox :-ms-input-placeholder { + content: "Search..."; + } + + .action-bar .search-box .inputbox ::-webkit-input-placeholder { + content: "Search..."; + } +} + +@media (max-width: 500px) { + dd label { + white-space: normal; + } + + select, .inputbox { + max-width: 260px; + } + + .captcha-panel dd.captcha { + margin-left: 0; + } + + .captcha-panel dd.captcha-image img { + width: 100%; + } + + dl.details dt, dl.details dd { + width: auto; + float: none; + text-align: left; + } + + dl.details dd { + margin-left: 20px; + } + + p.responsive-center { + float: none; + text-align: center; + margin-bottom: 5px; + } + + .action-bar > div { + margin-bottom: 5px; + } + + .action-bar > .pagination { + float: none; + clear: both; + padding-bottom: 1px; + text-align: center; + } + + .action-bar > .pagination li.page-jump { + margin: 0 2px; + } + + p.jumpbox-return { + display: none; + } + + .display-options > label:nth-child(1) { + display: block; + margin-bottom: 5px; + } + + .attach-controls { + margin-top: 5px; + width: 100%; + } + + .quick-links .dropdown-trigger span { + display: none; + } +} + +@media (max-width: 550px) { + ul.topiclist.forums dt { + margin-right: 0; + } + + ul.topiclist.forums dt .list-inner { + margin-right: 0; + } + + ul.topiclist.forums dd.lastpost { + display: none; + } +} + +@media (max-width: 700px) { + .responsive-hide { display: none !important; } + .responsive-show { display: block !important; } + .responsive-show-inline { display: inline !important; } + .responsive-show-inline-block { display: inline-block !important; } + + /* Content wrappers + ----------------------------------------*/ + html { + height: auto; + } + + body { + padding: 0; + } + + .wrap { + border: none; + border-radius: 0; + margin: 0; + min-width: 290px; + padding: 0 5px; + } + + /* Common block wrappers + ----------------------------------------*/ + .headerbar, .navbar, .forabg, .forumbg, .post, .panel { + border-radius: 0; + margin-left: -5px; + margin-right: -5px; + } + + .cp-main .forabg, .cp-main .forumdb, .cp-main .post, .cp-main .panel { + border-radius: 7px; + } + + /* Logo block + ----------------------------------------*/ + .site-description { + float: none; + width: auto; + text-align: center; + } + + .logo { + /* change display value to inline-block to show logo */ + display: none; + float: none; + padding: 10px; + } + + .site-description h1, .site-description p { + text-align: inherit; + float: none; + margin: 5px; + line-height: 1.2em; + overflow: hidden; + text-overflow: ellipsis; + } + + .site-description p, .search-header { + display: none; + } + + /* Navigation + ----------------------------------------*/ + .headerbar + .navbar { + margin-top: -5px; + } + + /* Search + ----------------------------------------*/ + .responsive-search { display: block !important; } + + /* .topiclist lists + ----------------------------------------*/ + li.header dt { + text-align: center; + text-transform: none; + line-height: 1em; + font-size: 1.2em; + padding-bottom: 4px; + } + + ul.topiclist li.header dt, ul.topiclist li.header dt .list-inner { + margin-right: 0 !important; + padding-right: 0; + } + + ul.topiclist li.header dd { + display: none !important; + } + + ul.topiclist dt, ul.topiclist dt .list-inner, + ul.topiclist.missing-column dt, ul.topiclist.missing-column dt .list-inner, + ul.topiclist.two-long-columns dt, ul.topiclist.two-long-columns dt .list-inner, + ul.topiclist.two-columns dt, ul.topiclist.two-columns dt .list-inner { + margin-right: 0; + } + + ul.topiclist dt .list-inner.with-mark { + padding-right: 34px; + } + + ul.topiclist dt .list-inner { + min-height: 28px; + } + + ul.topiclist li.header dt .list-inner { + min-height: 0; + } + + ul.topiclist dd { + display: none; + } + ul.topiclist dd.mark { + display: block; + } + + /* Forums and topics lists + ----------------------------------------*/ + ul.topiclist.forums dt { + margin-right: -250px; + } + + ul.topiclist dd.mark { + display: block; + position: absolute; + right: 5px; + top: 0; + margin: 0; + width: auto; + min-width: 0; + text-align: left; + } + + ul.topiclist.forums dd.topics dfn, ul.topiclist.topics dd.posts dfn { + position: relative; + left: 0; + width: auto; + display: inline; + font-weight: normal; + } + + li.row .responsive-show strong { + font-weight: bold; + color: inherit; + } + + ul.topiclist li.row dt a.subforum { + vertical-align: bottom; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100px; + } + + /* Pagination + ----------------------------------------*/ + .pagination > ul { + margin: 5px 0 0; + } + + .row .pagination .ellipsis + li { + display: none !important; + } + + /* Responsive tables + ----------------------------------------*/ + table.responsive, table.responsive tbody, table.responsive tr, table.responsive td { + display: block; + } + + table.responsive thead, table.responsive th { + display: none; + } + + table.responsive.show-header thead, table.responsive.show-header th:first-child { + display: block; + width: auto !important; + text-align: left !important; + } + + table.responsive.show-header th:first-child span.rank-img { + display: none; + } + + table.responsive tr { + margin: 2px 0; + } + + table.responsive td { + width: auto !important; + text-align: left !important; + padding: 4px; + } + + table.responsive td.empty { + display: none !important; + } + + table.responsive td > dfn { + display: inline-block !important; + } + + table.responsive td > dfn:after { + content: ':'; + padding-right: 5px; + } + + table.responsive span.rank-img { + float: none; + padding-right: 5px; + } + + table.responsive.memberlist td:first-child input[type="checkbox"] { + float: right; + } + + /* Forms + ----------------------------------------*/ + fieldset dt, fieldset.fields1 dt, fieldset.fields2 dt { + width: auto; + float: none; + } + + fieldset dd, fieldset.fields1 dd, fieldset.fields2 dd { + margin-left: 0px; + } + + textarea, dd textarea, .message-box textarea { + width: 100%; + -moz-box-sizing: border-box; + box-sizing: border-box; + } + + dl.pmlist dt { + width: auto !important; + margin-bottom: 5px; + } + + dl.pmlist dd { + display: inline-block; + margin-left: 0 !important; + } + + dl.pmlist dd:first-of-type { + padding-left: 20px; + } + + .smiley-box, .message-box { + float: none; + width: auto; + } + + .smiley-box { + margin-top: 5px; + } + + .bbcode-status { + display: none; + } + + .colour-palette, .colour-palette tbody, .colour-palette tr { + display: block; + } + + .colour-palette td { + display: inline-block; + margin-right: 2px; + } + + .horizontal-palette td:nth-child(2n), .vertical-palette tr:nth-child(2n) { + display: none; + } + + fieldset.quick-login label { + display: block; + margin-bottom: 5px; + white-space: normal; + } + + fieldset.quick-login label > span { + display: inline-block; + min-width: 100px; + } + + fieldset.quick-login input.inputbox { + width: 85%; + max-width: 300px; + margin-left: 20px; + } + + fieldset.quick-login label[for="autologin"] { + display: inline-block; + text-align: right; + min-width: 50%; + } + + /* User profile + ----------------------------------------*/ + .column1, .column2, .left-box.profile-details { + float: none; + width: auto; + clear: both; + } + + /* Polls + ----------------------------------------*/ + fieldset.polls dt { + width: 90%; + } + + fieldset.polls dd.resultbar { + padding-left: 20px; + } + + fieldset.polls dd.poll_option_percent { + width: 20%; + } + + fieldset.polls dd.resultbar, fieldset.polls dd.poll_option_percent { + margin-top: 5px; + } + + /* Post + ----------------------------------------*/ + .postbody { + position: inherit; + } + + .postprofile, .postbody, .search .postbody { + display: block; + width: auto; + float: none; + padding: 0; + min-height: 0; + } + + .post .postprofile { + width: auto; + border-width: 0 0 1px 0; + padding-bottom: 5px; + margin: 0; + margin-bottom: 5px; + min-height: 40px; + overflow: hidden; + } + + .postprofile dd { + display: none; + } + + .postprofile dt, .postprofile dd.profile-rank, .search .postprofile dd { + display: block; + margin: 0; + } + + .postprofile .has-avatar .avatar-container { + margin: 0; + overflow: inherit; + } + + .postprofile .avatar-container:after { + clear: none; + } + + .postprofile .avatar { + margin-right: 5px; + } + + .postprofile .avatar img { + width: auto !important; + height: auto !important; + max-height: 32px; + } + + .has-profile .postbody h3 { + margin-left: 0 !important; + margin-right: 0 !important; + } + + .has-profile .post-buttons { + right: 30px; + top: 15px; + } + + .online { + background-size: 40px; + } + + /* Misc stuff + ----------------------------------------*/ + h2 { + margin-top: .5em; + } + + p { + margin-bottom: .5em; + overflow: hidden; + } + + p.rightside { + margin-bottom: 0; + } + + fieldset.display-options label { + display: block; + clear: both; + margin-bottom: 5px; + } + + dl.mini dd.pm-legend { + float: left; + min-width: 200px; + } + + .topicreview { + margin: 0 -5px; + padding: 0 5px; + } + + fieldset.display-actions { + white-space: normal; + } + + .phpbb_alert { + width: auto; + margin: 0 5px; + } + + .attach-comment dfn { + width: 100%; + } +} + +@media (min-width: 700px) { + .postbody { width: 70%; } +} + +@media (min-width: 850px) { + .postbody { width: 76%; } +} + +@media (max-width: 850px) { + .postprofile { width: 28%; } + + +} + +@media (min-width: 701px) and (max-width: 950px) { + + ul.topiclist dt { + margin-right: -410px; + } + + ul.topiclist dt .list-inner { + margin-right: 410px; + } + + dd.posts, dd.topics, dd.views { + width: 80px; + } +} diff --git a/styles/prosilver/theme/stylesheet.css b/styles/prosilver/theme/stylesheet.css new file mode 100644 index 0000000..45eb5b6 --- /dev/null +++ b/styles/prosilver/theme/stylesheet.css @@ -0,0 +1,21 @@ +/* phpBB3 Style Sheet + -------------------------------------------------------------- + Style name: prosilver (the default phpBB 3.2.x style) + Based on style: + Original author: Tom Beddard ( http://www.subblue.com/ ) + Modified by: phpBB Limited ( https://www.phpbb.com/ ) + -------------------------------------------------------------- +*/ + +@import url("normalize.css?v=3.2"); +@import url("base.css?v=3.2"); +@import url("utilities.css?v=3.2"); +@import url("common.css?v=3.2"); +@import url("links.css?v=3.2"); +@import url("content.css?v=3.2"); +@import url("buttons.css?v=3.2"); +@import url("cp.css?v=3.2"); +@import url("forms.css?v=3.2"); +@import url("icons.css?v=3.2"); +@import url("colours.css?v=3.2"); +@import url("responsive.css?v=3.2"); diff --git a/styles/prosilver/theme/tweaks.css b/styles/prosilver/theme/tweaks.css new file mode 100644 index 0000000..ba82551 --- /dev/null +++ b/styles/prosilver/theme/tweaks.css @@ -0,0 +1,41 @@ +/* Style Sheet Tweaks + +These style definitions are IE 8 & 9 only. +They are required due to the poor CSS support in IE browsers. +------------------------------------------------------------------------------*/ + +/* IE 8 Tweaks (value)\9 equates to IE <= 8 +------------------------------------------------------------------------------*/ + +/* Clear float fix */ +.inner, ul.linklist { zoom: 1\9; } + +/* Align checkboxes/radio buttons nicely */ +dd label input { vertical-align: text-bottom\9; } + +/* Fixes header-avatar aspect-ratio */ +.header-avatar img { height: 20px\9; } + +/* IE8 often can't handle max-width in %, so we use px instead */ +.postprofile .avatar img { max-width: 150px\9; } + + +/* IE 9 Tweaks +------------------------------------------------------------------------------*/ + +/* Border-radius bleed fix in IE9 */ +.search-header, .search-header .inputbox, .search-header a.button { + border-radius: 0; +} + +.headerbar, .forumbg { + background-image: url("./images/bg_header.gif"); +} + +.forabg { + background-image: url("./images/bg_list.gif"); +} + +.tabs .tab > a { + border-radius: 0; +} diff --git a/styles/prosilver/theme/utilities.css b/styles/prosilver/theme/utilities.css new file mode 100644 index 0000000..cbb8127 --- /dev/null +++ b/styles/prosilver/theme/utilities.css @@ -0,0 +1,66 @@ +/* -------------------------------------------------------------- + $Utilities +-------------------------------------------------------------- */ + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} + +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} + +.clearfix:before, +.clearfix:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after { + content: " "; + display: table; +} +.clearfix:after, +.container:after, +.container-fluid:after, +.row:after { clear: both } + +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} + +.pull-right { float: right !important } +.pull-left { float: left !important } +.hide { display: none !important } +.show { display: block !important } +.invisible { visibility: hidden } + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.hidden { + display: none ; +} + +.affix { position: fixed } diff --git a/ucp.php b/ucp.php new file mode 100644 index 0000000..96a3efe --- /dev/null +++ b/ucp.php @@ -0,0 +1,406 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +require($phpbb_root_path . 'common.' . $phpEx); +require($phpbb_root_path . 'includes/functions_user.' . $phpEx); +require($phpbb_root_path . 'includes/functions_module.' . $phpEx); + +// Basic parameter data +$id = $request->variable('i', ''); +$mode = $request->variable('mode', ''); + +if (in_array($mode, array('login', 'login_link', 'logout', 'confirm', 'sendpassword', 'activate'))) +{ + define('IN_LOGIN', true); +} + +if ($mode === 'delete_cookies') +{ + define('SKIP_CHECK_BAN', true); + define('SKIP_CHECK_DISABLED', true); +} + +// Start session management +$user->session_begin(); +$auth->acl($user->data); +$user->setup('ucp'); + +// Setting a variable to let the style designer know where he is... +$template->assign_var('S_IN_UCP', true); + +$module = new p_master(); +$default = false; + +// Basic "global" modes +switch ($mode) +{ + case 'activate': + $module->load('ucp', 'activate'); + $module->display($user->lang['UCP_ACTIVATE']); + + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + break; + + case 'resend_act': + $module->load('ucp', 'resend'); + $module->display($user->lang['UCP_RESEND']); + break; + + case 'sendpassword': + $module->load('ucp', 'remind'); + $module->display($user->lang['UCP_REMIND']); + break; + + case 'register': + if ($user->data['is_registered'] || isset($_REQUEST['not_agreed'])) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + $module->load('ucp', 'register'); + $module->display($user->lang['REGISTER']); + break; + + case 'confirm': + $module->load('ucp', 'confirm'); + break; + + case 'login': + if ($user->data['is_registered']) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + login_box($request->variable('redirect', "index.$phpEx")); + break; + + case 'login_link': + if ($user->data['is_registered']) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + $module->load('ucp', 'login_link'); + $module->display($user->lang['UCP_LOGIN_LINK']); + break; + + case 'logout': + if ($user->data['user_id'] != ANONYMOUS && $request->is_set('sid') && $request->variable('sid', '') === $user->session_id) + { + $user->session_kill(); + } + else if ($user->data['user_id'] != ANONYMOUS) + { + meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx")); + + $message = $user->lang['LOGOUT_FAILED'] . '

' . sprintf($user->lang['RETURN_INDEX'], '
', ' '); + trigger_error($message); + } + + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + break; + + case 'terms': + case 'privacy': + + $message = ($mode == 'terms') ? 'TERMS_OF_USE_CONTENT' : 'PRIVACY_POLICY'; + $title = ($mode == 'terms') ? 'TERMS_USE' : 'PRIVACY'; + + if (empty($user->lang[$message])) + { + if ($user->data['is_registered']) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + login_box(); + } + + $template->set_filenames(array( + 'body' => 'ucp_agreement.html') + ); + + // Disable online list + page_header($user->lang[$title]); + + $template->assign_vars(array( + 'S_AGREEMENT' => true, + 'AGREEMENT_TITLE' => $user->lang[$title], + 'AGREEMENT_TEXT' => sprintf($user->lang[$message], $config['sitename'], generate_board_url()), + 'U_BACK' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login'), + 'L_BACK' => $user->lang['BACK_TO_PREV'], + )); + + page_footer(); + + break; + + case 'delete_cookies': + + // Delete Cookies with dynamic names (do NOT delete poll cookies) + if (confirm_box(true)) + { + $set_time = time() - 31536000; + + foreach ($request->variable_names(\phpbb\request\request_interface::COOKIE) as $cookie_name) + { + $cookie_data = $request->variable($cookie_name, '', true, \phpbb\request\request_interface::COOKIE); + + // Only delete board cookies, no other ones... + if (strpos($cookie_name, $config['cookie_name'] . '_') !== 0) + { + continue; + } + + $cookie_name = str_replace($config['cookie_name'] . '_', '', $cookie_name); + + /** + * Event to save custom cookies from deletion + * + * @event core.ucp_delete_cookies + * @var string cookie_name Cookie name to checking + * @var bool retain_cookie Do we retain our cookie or not, true if retain + * @since 3.1.3-RC1 + */ + $retain_cookie = false; + $vars = array('cookie_name', 'retain_cookie'); + extract($phpbb_dispatcher->trigger_event('core.ucp_delete_cookies', compact($vars))); + if ($retain_cookie) + { + continue; + } + + // Polls are stored as {cookie_name}_poll_{topic_id}, cookie_name_ got removed, therefore checking for poll_ + if (strpos($cookie_name, 'poll_') !== 0) + { + $user->set_cookie($cookie_name, '', $set_time); + } + } + + $user->set_cookie('track', '', $set_time); + $user->set_cookie('u', '', $set_time); + $user->set_cookie('k', '', $set_time); + $user->set_cookie('sid', '', $set_time); + + // We destroy the session here, the user will be logged out nevertheless + $user->session_kill(); + $user->session_begin(); + + meta_refresh(3, append_sid("{$phpbb_root_path}index.$phpEx")); + + $message = $user->lang['COOKIES_DELETED'] . '

' . sprintf($user->lang['RETURN_INDEX'], '', ''); + trigger_error($message); + } + else + { + confirm_box(false, 'DELETE_COOKIES', ''); + } + + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + + break; + + case 'switch_perm': + + $user_id = $request->variable('u', 0); + + $sql = 'SELECT * + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . (int) $user_id; + $result = $db->sql_query($sql); + $user_row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$auth->acl_get('a_switchperm') || !$user_row || $user_id == $user->data['user_id'] || !check_link_hash($request->variable('hash', ''), 'switchperm')) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + include($phpbb_root_path . 'includes/acp/auth.' . $phpEx); + + $auth_admin = new auth_admin(); + if (!$auth_admin->ghost_permissions($user_id, $user->data['user_id'])) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ACL_TRANSFER_PERMISSIONS', false, array($user_row['username'])); + + $message = sprintf($user->lang['PERMISSIONS_TRANSFERRED'], $user_row['username']) . '

' . sprintf($user->lang['RETURN_INDEX'], '', ''); + + /** + * Event to run code after permissions are switched + * + * @event core.ucp_switch_permissions + * @var int user_id User ID to switch permission to + * @var array user_row User data + * @var string message Success message + * @since 3.1.11-RC1 + */ + $vars = array('user_id', 'user_row', 'message'); + extract($phpbb_dispatcher->trigger_event('core.ucp_switch_permissions', compact($vars))); + + trigger_error($message); + + break; + + case 'restore_perm': + + if (!$user->data['user_perm_from'] || !$auth->acl_get('a_switchperm')) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + $auth->acl_cache($user->data); + + $sql = 'SELECT username + FROM ' . USERS_TABLE . ' + WHERE user_id = ' . $user->data['user_perm_from']; + $result = $db->sql_query($sql); + $username = $db->sql_fetchfield('username'); + $db->sql_freeresult($result); + + $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ACL_RESTORE_PERMISSIONS', false, array($username)); + + $message = $user->lang['PERMISSIONS_RESTORED'] . '

' . sprintf($user->lang['RETURN_INDEX'], '', ''); + + /** + * Event to run code after permissions are restored + * + * @event core.ucp_restore_permissions + * @var string username User name + * @var string message Success message + * @since 3.1.11-RC1 + */ + $vars = array('username', 'message'); + extract($phpbb_dispatcher->trigger_event('core.ucp_restore_permissions', compact($vars))); + + trigger_error($message); + + break; + + default: + $default = true; + break; +} + +// We use this approach because it does not impose large code changes +if (!$default) +{ + return true; +} + +// Only registered users can go beyond this point +if (!$user->data['is_registered']) +{ + if ($user->data['is_bot']) + { + redirect(append_sid("{$phpbb_root_path}index.$phpEx")); + } + + if ($id == 'pm' && $mode == 'view' && isset($_GET['p'])) + { + $redirect_url = append_sid("{$phpbb_root_path}ucp.$phpEx?i=pm&p=" . $request->variable('p', 0)); + login_box($redirect_url, $user->lang['LOGIN_EXPLAIN_UCP']); + } + + login_box('', $user->lang['LOGIN_EXPLAIN_UCP']); +} + +// Instantiate module system and generate list of available modules +$module->list_modules('ucp'); + +// Check if the zebra module is set +if ($module->is_active('zebra', 'friends')) +{ + // Output listing of friends online + $update_time = $config['load_online_time'] * 60; + + $sql_ary = array( + 'SELECT' => 'u.user_id, u.username, u.username_clean, u.user_colour, MAX(s.session_time) as online_time, MIN(s.session_viewonline) AS viewonline', + + 'FROM' => array( + USERS_TABLE => 'u', + ZEBRA_TABLE => 'z', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(SESSIONS_TABLE => 's'), + 'ON' => 's.session_user_id = z.zebra_id', + ), + ), + + 'WHERE' => 'z.user_id = ' . $user->data['user_id'] . ' + AND z.friend = 1 + AND u.user_id = z.zebra_id', + + 'GROUP_BY' => 'z.zebra_id, u.user_id, u.username_clean, u.user_colour, u.username', + + 'ORDER_BY' => 'u.username_clean ASC', + ); + + $sql = $db->sql_build_query('SELECT_DISTINCT', $sql_ary); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $which = (time() - $update_time < $row['online_time'] && ($row['viewonline'] || $auth->acl_get('u_viewonline'))) ? 'online' : 'offline'; + + $template->assign_block_vars("friends_{$which}", array( + 'USER_ID' => $row['user_id'], + + 'U_PROFILE' => get_username_string('profile', $row['user_id'], $row['username'], $row['user_colour']), + 'USER_COLOUR' => get_username_string('colour', $row['user_id'], $row['username'], $row['user_colour']), + 'USERNAME' => get_username_string('username', $row['user_id'], $row['username'], $row['user_colour']), + 'USERNAME_FULL' => get_username_string('full', $row['user_id'], $row['username'], $row['user_colour'])) + ); + } + $db->sql_freeresult($result); +} + +// Do not display subscribed topics/forums if not allowed +if (!$config['allow_topic_notify'] && !$config['allow_forum_notify']) +{ + $module->set_display('main', 'subscribed', false); +} + +/** +* Use this event to enable and disable additional UCP modules +* +* @event core.ucp_display_module_before +* @var p_master module Object holding all modules and their status +* @var mixed id Active module category (can be the int or string) +* @var string mode Active module +* @since 3.1.0-a1 +*/ +$vars = array('module', 'id', 'mode'); +extract($phpbb_dispatcher->trigger_event('core.ucp_display_module_before', compact($vars))); + +// Select the active module +$module->set_active($id, $mode); + +// Load and execute the relevant module +$module->load_active(); + +// Assign data to the template engine for the list of modules +$module->assign_tpl_vars(append_sid("{$phpbb_root_path}ucp.$phpEx")); + +// Generate the page, do not display/query online list +$module->display($module->get_page_title()); diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..ab14645 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +namespace bantu\IniGetWrapper; + +/** +* Wrapper class around built-in ini_get() function. +* +* Provides easier handling of the different interpretations of ini values. +*/ +class IniGetWrapper +{ + /** + * Simple wrapper around ini_get() + * See http://php.net/manual/en/function.ini-get.php + * + * @param string $varname The configuration option name. + * @return null|string Null if configuration option does not exist. + * The configuration option value (string) otherwise. + */ + public function get($varname) + { + $value = $this->getPhp($varname); + return $value === false ? null : $value; + } + + /** + * Gets the configuration option value as a trimmed string. + * + * @param string $varname The configuration option name. + * @return null|string Null if configuration option does not exist. + * The configuration option value (string) otherwise. + */ + public function getString($varname) + { + $value = $this->get($varname); + return $value === null ? null : trim($value); + } + + /** + * Gets configuration option value as a boolean. + * Interprets the string value 'off' as false. + * + * @param string $varname The configuration option name. + * @return null|bool Null if configuration option does not exist. + * False if configuration option is disabled. + * True otherwise. + */ + public function getBool($varname) + { + $value = $this->getString($varname); + return $value === null ? null : $value && strtolower($value) !== 'off'; + } + + /** + * Gets configuration option value as an integer. + * + * @param string $varname The configuration option name. + * @return null|int|float Null if configuration option does not exist or is not numeric. + * The configuration option value (integer or float) otherwise. + */ + public function getNumeric($varname) + { + $value = $this->getString($varname); + return is_numeric($value) ? $value + 0 : null; + } + + /** + * Gets configuration option value in bytes. + * Converts strings like '128M' to bytes (integer or float). + * + * @param string $varname The configuration option name. + * @return null|int|float Null if configuration option does not exist or is not well-formed. + * The configuration option value as bytes (integer or float) otherwise. + */ + public function getBytes($varname) + { + $value = $this->getString($varname); + + if ($value === null) { + return null; + } + + if (is_numeric($value)) { + // Already in bytes. + return $value + 0; + } + + if (strlen($value) < 2 || strlen($value) < 3 && $value[0] === '-') { + // Either a single character + // or two characters where the first one is a minus. + return null; + } + + // Split string into numeric value and unit. + $value_numeric = substr($value, 0, -1); + if (!is_numeric($value_numeric)) { + return null; + } + + switch (strtolower($value[strlen($value) - 1])) { + case 'g': + $value_numeric *= 1024; + // no break + case 'm': + $value_numeric *= 1024; + // no break + case 'k': + $value_numeric *= 1024; + break; + + default: + // It's not already in bytes (and thus numeric) + // and does not carry a unit. + return null; + } + + return $value_numeric; + } + + /** + * Gets configuration option value as a list (array). + * Converts comma-separated string into list (array). + * + * @param string $varname The configuration option name. + * @return null|array Null if configuration option does not exist. + * The configuration option value as a list (array) otherwise. + */ + public function getList($varname) + { + $value = $this->getString($varname); + return $value === null ? null : explode(',', $value); + } + + /** + * Checks whether a list contains a given element (string). + * + * @param string $varname The configuration option name. + * @param string $needle The element to check whether it is contained in the list. + * @return null|bool Null if configuration option does not exist. + * Whether $needle is contained in the list otherwise. + */ + public function listContains($varname, $needle) + { + $list = $this->getList($varname); + return $list === null ? null : in_array($needle, $list, true); + } + + /** + * @param string $varname The configuration option name. + */ + protected function getPhp($varname) + { + return ini_get($varname); + } +} diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..fce8549 --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..3c6870d --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,1876 @@ + $vendorDir . '/symfony/polyfill-php54/Resources/stubs/CallbackFilterIterator.php', + 'FastImageSize\\FastImageSize' => $vendorDir . '/marc1706/fast-image-size/lib/FastImageSize.php', + 'FastImageSize\\Type\\TypeBase' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeBase.php', + 'FastImageSize\\Type\\TypeBmp' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeBmp.php', + 'FastImageSize\\Type\\TypeGif' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeGif.php', + 'FastImageSize\\Type\\TypeIco' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeIco.php', + 'FastImageSize\\Type\\TypeIff' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeIff.php', + 'FastImageSize\\Type\\TypeInterface' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeInterface.php', + 'FastImageSize\\Type\\TypeJp2' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeJp2.php', + 'FastImageSize\\Type\\TypeJpeg' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeJpeg.php', + 'FastImageSize\\Type\\TypePng' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypePng.php', + 'FastImageSize\\Type\\TypePsd' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypePsd.php', + 'FastImageSize\\Type\\TypeTif' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeTif.php', + 'FastImageSize\\Type\\TypeWbmp' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeWbmp.php', + 'FastImageSize\\Type\\TypeWebp' => $vendorDir . '/marc1706/fast-image-size/lib/Type/TypeWebp.php', + 'GuzzleHttp\\BatchResults' => $vendorDir . '/guzzlehttp/guzzle/src/BatchResults.php', + 'GuzzleHttp\\Client' => $vendorDir . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => $vendorDir . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\Collection' => $vendorDir . '/guzzlehttp/guzzle/src/Collection.php', + 'GuzzleHttp\\Cookie\\CookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => $vendorDir . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Event\\AbstractEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/AbstractEvent.php', + 'GuzzleHttp\\Event\\AbstractRequestEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php', + 'GuzzleHttp\\Event\\AbstractRetryableEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php', + 'GuzzleHttp\\Event\\AbstractTransferEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php', + 'GuzzleHttp\\Event\\BeforeEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/BeforeEvent.php', + 'GuzzleHttp\\Event\\CompleteEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/CompleteEvent.php', + 'GuzzleHttp\\Event\\Emitter' => $vendorDir . '/guzzlehttp/guzzle/src/Event/Emitter.php', + 'GuzzleHttp\\Event\\EmitterInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Event/EmitterInterface.php', + 'GuzzleHttp\\Event\\EndEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/EndEvent.php', + 'GuzzleHttp\\Event\\ErrorEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/ErrorEvent.php', + 'GuzzleHttp\\Event\\EventInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Event/EventInterface.php', + 'GuzzleHttp\\Event\\HasEmitterInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Event/HasEmitterInterface.php', + 'GuzzleHttp\\Event\\HasEmitterTrait' => $vendorDir . '/guzzlehttp/guzzle/src/Event/HasEmitterTrait.php', + 'GuzzleHttp\\Event\\ListenerAttacherTrait' => $vendorDir . '/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php', + 'GuzzleHttp\\Event\\ProgressEvent' => $vendorDir . '/guzzlehttp/guzzle/src/Event/ProgressEvent.php', + 'GuzzleHttp\\Event\\RequestEvents' => $vendorDir . '/guzzlehttp/guzzle/src/Event/RequestEvents.php', + 'GuzzleHttp\\Event\\SubscriberInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Event/SubscriberInterface.php', + 'GuzzleHttp\\Exception\\BadResponseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\CouldNotRewindStreamException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/CouldNotRewindStreamException.php', + 'GuzzleHttp\\Exception\\ParseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ParseException.php', + 'GuzzleHttp\\Exception\\RequestException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\ServerException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\StateException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/StateException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\Exception\\XmlParseException' => $vendorDir . '/guzzlehttp/guzzle/src/Exception/XmlParseException.php', + 'GuzzleHttp\\HasDataTrait' => $vendorDir . '/guzzlehttp/guzzle/src/HasDataTrait.php', + 'GuzzleHttp\\Message\\AbstractMessage' => $vendorDir . '/guzzlehttp/guzzle/src/Message/AbstractMessage.php', + 'GuzzleHttp\\Message\\AppliesHeadersInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php', + 'GuzzleHttp\\Message\\FutureResponse' => $vendorDir . '/guzzlehttp/guzzle/src/Message/FutureResponse.php', + 'GuzzleHttp\\Message\\MessageFactory' => $vendorDir . '/guzzlehttp/guzzle/src/Message/MessageFactory.php', + 'GuzzleHttp\\Message\\MessageFactoryInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php', + 'GuzzleHttp\\Message\\MessageInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Message/MessageInterface.php', + 'GuzzleHttp\\Message\\MessageParser' => $vendorDir . '/guzzlehttp/guzzle/src/Message/MessageParser.php', + 'GuzzleHttp\\Message\\Request' => $vendorDir . '/guzzlehttp/guzzle/src/Message/Request.php', + 'GuzzleHttp\\Message\\RequestInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Message/RequestInterface.php', + 'GuzzleHttp\\Message\\Response' => $vendorDir . '/guzzlehttp/guzzle/src/Message/Response.php', + 'GuzzleHttp\\Message\\ResponseInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Message/ResponseInterface.php', + 'GuzzleHttp\\Mimetypes' => $vendorDir . '/guzzlehttp/guzzle/src/Mimetypes.php', + 'GuzzleHttp\\Pool' => $vendorDir . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\Post\\MultipartBody' => $vendorDir . '/guzzlehttp/guzzle/src/Post/MultipartBody.php', + 'GuzzleHttp\\Post\\PostBody' => $vendorDir . '/guzzlehttp/guzzle/src/Post/PostBody.php', + 'GuzzleHttp\\Post\\PostBodyInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Post/PostBodyInterface.php', + 'GuzzleHttp\\Post\\PostFile' => $vendorDir . '/guzzlehttp/guzzle/src/Post/PostFile.php', + 'GuzzleHttp\\Post\\PostFileInterface' => $vendorDir . '/guzzlehttp/guzzle/src/Post/PostFileInterface.php', + 'GuzzleHttp\\Query' => $vendorDir . '/guzzlehttp/guzzle/src/Query.php', + 'GuzzleHttp\\QueryParser' => $vendorDir . '/guzzlehttp/guzzle/src/QueryParser.php', + 'GuzzleHttp\\RequestFsm' => $vendorDir . '/guzzlehttp/guzzle/src/RequestFsm.php', + 'GuzzleHttp\\RingBridge' => $vendorDir . '/guzzlehttp/guzzle/src/RingBridge.php', + 'GuzzleHttp\\Ring\\Client\\ClientUtils' => $vendorDir . '/guzzlehttp/ringphp/src/Client/ClientUtils.php', + 'GuzzleHttp\\Ring\\Client\\CurlFactory' => $vendorDir . '/guzzlehttp/ringphp/src/Client/CurlFactory.php', + 'GuzzleHttp\\Ring\\Client\\CurlHandler' => $vendorDir . '/guzzlehttp/ringphp/src/Client/CurlHandler.php', + 'GuzzleHttp\\Ring\\Client\\CurlMultiHandler' => $vendorDir . '/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php', + 'GuzzleHttp\\Ring\\Client\\Middleware' => $vendorDir . '/guzzlehttp/ringphp/src/Client/Middleware.php', + 'GuzzleHttp\\Ring\\Client\\MockHandler' => $vendorDir . '/guzzlehttp/ringphp/src/Client/MockHandler.php', + 'GuzzleHttp\\Ring\\Client\\StreamHandler' => $vendorDir . '/guzzlehttp/ringphp/src/Client/StreamHandler.php', + 'GuzzleHttp\\Ring\\Core' => $vendorDir . '/guzzlehttp/ringphp/src/Core.php', + 'GuzzleHttp\\Ring\\Exception\\CancelledException' => $vendorDir . '/guzzlehttp/ringphp/src/Exception/CancelledException.php', + 'GuzzleHttp\\Ring\\Exception\\CancelledFutureAccessException' => $vendorDir . '/guzzlehttp/ringphp/src/Exception/CancelledFutureAccessException.php', + 'GuzzleHttp\\Ring\\Exception\\ConnectException' => $vendorDir . '/guzzlehttp/ringphp/src/Exception/ConnectException.php', + 'GuzzleHttp\\Ring\\Exception\\RingException' => $vendorDir . '/guzzlehttp/ringphp/src/Exception/RingException.php', + 'GuzzleHttp\\Ring\\Future\\BaseFutureTrait' => $vendorDir . '/guzzlehttp/ringphp/src/Future/BaseFutureTrait.php', + 'GuzzleHttp\\Ring\\Future\\CompletedFutureArray' => $vendorDir . '/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php', + 'GuzzleHttp\\Ring\\Future\\CompletedFutureValue' => $vendorDir . '/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php', + 'GuzzleHttp\\Ring\\Future\\FutureArray' => $vendorDir . '/guzzlehttp/ringphp/src/Future/FutureArray.php', + 'GuzzleHttp\\Ring\\Future\\FutureArrayInterface' => $vendorDir . '/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php', + 'GuzzleHttp\\Ring\\Future\\FutureInterface' => $vendorDir . '/guzzlehttp/ringphp/src/Future/FutureInterface.php', + 'GuzzleHttp\\Ring\\Future\\FutureValue' => $vendorDir . '/guzzlehttp/ringphp/src/Future/FutureValue.php', + 'GuzzleHttp\\Ring\\Future\\MagicFutureTrait' => $vendorDir . '/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php', + 'GuzzleHttp\\Stream\\AppendStream' => $vendorDir . '/guzzlehttp/streams/src/AppendStream.php', + 'GuzzleHttp\\Stream\\AsyncReadStream' => $vendorDir . '/guzzlehttp/streams/src/AsyncReadStream.php', + 'GuzzleHttp\\Stream\\BufferStream' => $vendorDir . '/guzzlehttp/streams/src/BufferStream.php', + 'GuzzleHttp\\Stream\\CachingStream' => $vendorDir . '/guzzlehttp/streams/src/CachingStream.php', + 'GuzzleHttp\\Stream\\DroppingStream' => $vendorDir . '/guzzlehttp/streams/src/DroppingStream.php', + 'GuzzleHttp\\Stream\\Exception\\CannotAttachException' => $vendorDir . '/guzzlehttp/streams/src/Exception/CannotAttachException.php', + 'GuzzleHttp\\Stream\\Exception\\SeekException' => $vendorDir . '/guzzlehttp/streams/src/Exception/SeekException.php', + 'GuzzleHttp\\Stream\\FnStream' => $vendorDir . '/guzzlehttp/streams/src/FnStream.php', + 'GuzzleHttp\\Stream\\GuzzleStreamWrapper' => $vendorDir . '/guzzlehttp/streams/src/GuzzleStreamWrapper.php', + 'GuzzleHttp\\Stream\\InflateStream' => $vendorDir . '/guzzlehttp/streams/src/InflateStream.php', + 'GuzzleHttp\\Stream\\LazyOpenStream' => $vendorDir . '/guzzlehttp/streams/src/LazyOpenStream.php', + 'GuzzleHttp\\Stream\\LimitStream' => $vendorDir . '/guzzlehttp/streams/src/LimitStream.php', + 'GuzzleHttp\\Stream\\MetadataStreamInterface' => $vendorDir . '/guzzlehttp/streams/src/MetadataStreamInterface.php', + 'GuzzleHttp\\Stream\\NoSeekStream' => $vendorDir . '/guzzlehttp/streams/src/NoSeekStream.php', + 'GuzzleHttp\\Stream\\NullStream' => $vendorDir . '/guzzlehttp/streams/src/NullStream.php', + 'GuzzleHttp\\Stream\\PumpStream' => $vendorDir . '/guzzlehttp/streams/src/PumpStream.php', + 'GuzzleHttp\\Stream\\Stream' => $vendorDir . '/guzzlehttp/streams/src/Stream.php', + 'GuzzleHttp\\Stream\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/streams/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Stream\\StreamInterface' => $vendorDir . '/guzzlehttp/streams/src/StreamInterface.php', + 'GuzzleHttp\\Stream\\Utils' => $vendorDir . '/guzzlehttp/streams/src/Utils.php', + 'GuzzleHttp\\Subscriber\\Cookie' => $vendorDir . '/guzzlehttp/guzzle/src/Subscriber/Cookie.php', + 'GuzzleHttp\\Subscriber\\History' => $vendorDir . '/guzzlehttp/guzzle/src/Subscriber/History.php', + 'GuzzleHttp\\Subscriber\\HttpError' => $vendorDir . '/guzzlehttp/guzzle/src/Subscriber/HttpError.php', + 'GuzzleHttp\\Subscriber\\Mock' => $vendorDir . '/guzzlehttp/guzzle/src/Subscriber/Mock.php', + 'GuzzleHttp\\Subscriber\\Prepare' => $vendorDir . '/guzzlehttp/guzzle/src/Subscriber/Prepare.php', + 'GuzzleHttp\\Subscriber\\Redirect' => $vendorDir . '/guzzlehttp/guzzle/src/Subscriber/Redirect.php', + 'GuzzleHttp\\ToArrayInterface' => $vendorDir . '/guzzlehttp/guzzle/src/ToArrayInterface.php', + 'GuzzleHttp\\Transaction' => $vendorDir . '/guzzlehttp/guzzle/src/Transaction.php', + 'GuzzleHttp\\UriTemplate' => $vendorDir . '/guzzlehttp/guzzle/src/UriTemplate.php', + 'GuzzleHttp\\Url' => $vendorDir . '/guzzlehttp/guzzle/src/Url.php', + 'GuzzleHttp\\Utils' => $vendorDir . '/guzzlehttp/guzzle/src/Utils.php', + 'Normalizer' => $vendorDir . '/patchwork/utf8/src/Normalizer.php', + 'OAuth\\Common\\AutoLoader' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/AutoLoader.php', + 'OAuth\\Common\\Consumer\\Credentials' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Consumer/Credentials.php', + 'OAuth\\Common\\Consumer\\CredentialsInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Consumer/CredentialsInterface.php', + 'OAuth\\Common\\Exception\\Exception' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Exception/Exception.php', + 'OAuth\\Common\\Http\\Client\\AbstractClient' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Client/AbstractClient.php', + 'OAuth\\Common\\Http\\Client\\ClientInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Client/ClientInterface.php', + 'OAuth\\Common\\Http\\Client\\CurlClient' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Client/CurlClient.php', + 'OAuth\\Common\\Http\\Client\\StreamClient' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Client/StreamClient.php', + 'OAuth\\Common\\Http\\Exception\\TokenResponseException' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Exception/TokenResponseException.php', + 'OAuth\\Common\\Http\\Uri\\Uri' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Uri/Uri.php', + 'OAuth\\Common\\Http\\Uri\\UriFactory' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactory.php', + 'OAuth\\Common\\Http\\Uri\\UriFactoryInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactoryInterface.php', + 'OAuth\\Common\\Http\\Uri\\UriInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriInterface.php', + 'OAuth\\Common\\Service\\AbstractService' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Service/AbstractService.php', + 'OAuth\\Common\\Service\\ServiceInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Service/ServiceInterface.php', + 'OAuth\\Common\\Storage\\Exception\\AuthorizationStateNotFoundException' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Storage/Exception/AuthorizationStateNotFoundException.php', + 'OAuth\\Common\\Storage\\Exception\\StorageException' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Storage/Exception/StorageException.php', + 'OAuth\\Common\\Storage\\Exception\\TokenNotFoundException' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Storage/Exception/TokenNotFoundException.php', + 'OAuth\\Common\\Storage\\Memory' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Storage/Memory.php', + 'OAuth\\Common\\Storage\\Redis' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Storage/Redis.php', + 'OAuth\\Common\\Storage\\Session' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Storage/Session.php', + 'OAuth\\Common\\Storage\\SymfonySession' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Storage/SymfonySession.php', + 'OAuth\\Common\\Storage\\TokenStorageInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Storage/TokenStorageInterface.php', + 'OAuth\\Common\\Token\\AbstractToken' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Token/AbstractToken.php', + 'OAuth\\Common\\Token\\Exception\\ExpiredTokenException' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Token/Exception/ExpiredTokenException.php', + 'OAuth\\Common\\Token\\TokenInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/Common/Token/TokenInterface.php', + 'OAuth\\OAuth1\\Service\\AbstractService' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/AbstractService.php', + 'OAuth\\OAuth1\\Service\\BitBucket' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/BitBucket.php', + 'OAuth\\OAuth1\\Service\\Etsy' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Etsy.php', + 'OAuth\\OAuth1\\Service\\FitBit' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/FitBit.php', + 'OAuth\\OAuth1\\Service\\FiveHundredPx' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/FiveHundredPx.php', + 'OAuth\\OAuth1\\Service\\Flickr' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Flickr.php', + 'OAuth\\OAuth1\\Service\\QuickBooks' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/QuickBooks.php', + 'OAuth\\OAuth1\\Service\\Redmine' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Redmine.php', + 'OAuth\\OAuth1\\Service\\ScoopIt' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/ScoopIt.php', + 'OAuth\\OAuth1\\Service\\ServiceInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/ServiceInterface.php', + 'OAuth\\OAuth1\\Service\\Tumblr' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Tumblr.php', + 'OAuth\\OAuth1\\Service\\Twitter' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Twitter.php', + 'OAuth\\OAuth1\\Service\\Xing' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Xing.php', + 'OAuth\\OAuth1\\Service\\Yahoo' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Yahoo.php', + 'OAuth\\OAuth1\\Signature\\Exception\\UnsupportedHashAlgorithmException' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php', + 'OAuth\\OAuth1\\Signature\\Signature' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Signature/Signature.php', + 'OAuth\\OAuth1\\Signature\\SignatureInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Signature/SignatureInterface.php', + 'OAuth\\OAuth1\\Token\\StdOAuth1Token' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Token/StdOAuth1Token.php', + 'OAuth\\OAuth1\\Token\\TokenInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth1/Token/TokenInterface.php', + 'OAuth\\OAuth2\\Service\\AbstractService' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/AbstractService.php', + 'OAuth\\OAuth2\\Service\\Amazon' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Amazon.php', + 'OAuth\\OAuth2\\Service\\BattleNet' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/BattleNet.php', + 'OAuth\\OAuth2\\Service\\Bitly' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitly.php', + 'OAuth\\OAuth2\\Service\\Bitrix24' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitrix24.php', + 'OAuth\\OAuth2\\Service\\Box' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Box.php', + 'OAuth\\OAuth2\\Service\\Buffer' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Buffer.php', + 'OAuth\\OAuth2\\Service\\Dailymotion' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Dailymotion.php', + 'OAuth\\OAuth2\\Service\\Deezer' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Deezer.php', + 'OAuth\\OAuth2\\Service\\Delicious' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Delicious.php', + 'OAuth\\OAuth2\\Service\\DeviantArt' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/DeviantArt.php', + 'OAuth\\OAuth2\\Service\\Dropbox' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Dropbox.php', + 'OAuth\\OAuth2\\Service\\EveOnline' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/EveOnline.php', + 'OAuth\\OAuth2\\Service\\Exception\\InvalidAccessTypeException' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php', + 'OAuth\\OAuth2\\Service\\Exception\\InvalidAuthorizationStateException' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidAuthorizationStateException.php', + 'OAuth\\OAuth2\\Service\\Exception\\InvalidScopeException' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidScopeException.php', + 'OAuth\\OAuth2\\Service\\Exception\\MissingRefreshTokenException' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php', + 'OAuth\\OAuth2\\Service\\Facebook' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Facebook.php', + 'OAuth\\OAuth2\\Service\\Foursquare' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Foursquare.php', + 'OAuth\\OAuth2\\Service\\GitHub' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/GitHub.php', + 'OAuth\\OAuth2\\Service\\Google' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Google.php', + 'OAuth\\OAuth2\\Service\\Harvest' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Harvest.php', + 'OAuth\\OAuth2\\Service\\Heroku' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Heroku.php', + 'OAuth\\OAuth2\\Service\\Hubic' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Hubic.php', + 'OAuth\\OAuth2\\Service\\Instagram' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Instagram.php', + 'OAuth\\OAuth2\\Service\\JawboneUP' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/JawboneUP.php', + 'OAuth\\OAuth2\\Service\\Linkedin' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Linkedin.php', + 'OAuth\\OAuth2\\Service\\Mailchimp' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Mailchimp.php', + 'OAuth\\OAuth2\\Service\\Microsoft' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Microsoft.php', + 'OAuth\\OAuth2\\Service\\Mondo' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Mondo.php', + 'OAuth\\OAuth2\\Service\\Nest' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Nest.php', + 'OAuth\\OAuth2\\Service\\Netatmo' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Netatmo.php', + 'OAuth\\OAuth2\\Service\\ParrotFlowerPower' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/ParrotFlowerPower.php', + 'OAuth\\OAuth2\\Service\\Paypal' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Paypal.php', + 'OAuth\\OAuth2\\Service\\Pinterest' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Pinterest.php', + 'OAuth\\OAuth2\\Service\\Pocket' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Pocket.php', + 'OAuth\\OAuth2\\Service\\Reddit' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Reddit.php', + 'OAuth\\OAuth2\\Service\\RunKeeper' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/RunKeeper.php', + 'OAuth\\OAuth2\\Service\\Salesforce' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Salesforce.php', + 'OAuth\\OAuth2\\Service\\ServiceInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/ServiceInterface.php', + 'OAuth\\OAuth2\\Service\\SoundCloud' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/SoundCloud.php', + 'OAuth\\OAuth2\\Service\\Spotify' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Spotify.php', + 'OAuth\\OAuth2\\Service\\Strava' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Strava.php', + 'OAuth\\OAuth2\\Service\\Ustream' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Ustream.php', + 'OAuth\\OAuth2\\Service\\Vimeo' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Vimeo.php', + 'OAuth\\OAuth2\\Service\\Vkontakte' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Vkontakte.php', + 'OAuth\\OAuth2\\Service\\Yahoo' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Yahoo.php', + 'OAuth\\OAuth2\\Service\\Yammer' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Yammer.php', + 'OAuth\\OAuth2\\Token\\StdOAuth2Token' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Token/StdOAuth2Token.php', + 'OAuth\\OAuth2\\Token\\TokenInterface' => $vendorDir . '/lusitanian/oauth/src/OAuth/OAuth2/Token/TokenInterface.php', + 'OAuth\\ServiceFactory' => $vendorDir . '/lusitanian/oauth/src/OAuth/ServiceFactory.php', + 'Patchwork\\PHP\\Shim\\Iconv' => $vendorDir . '/patchwork/utf8/src/Patchwork/PHP/Shim/Iconv.php', + 'Patchwork\\PHP\\Shim\\Intl' => $vendorDir . '/patchwork/utf8/src/Patchwork/PHP/Shim/Intl.php', + 'Patchwork\\PHP\\Shim\\Mbstring' => $vendorDir . '/patchwork/utf8/src/Patchwork/PHP/Shim/Mbstring.php', + 'Patchwork\\PHP\\Shim\\Normalizer' => $vendorDir . '/patchwork/utf8/src/Patchwork/PHP/Shim/Normalizer.php', + 'Patchwork\\PHP\\Shim\\Xml' => $vendorDir . '/patchwork/utf8/src/Patchwork/PHP/Shim/Xml.php', + 'Patchwork\\TurkishUtf8' => $vendorDir . '/patchwork/utf8/src/Patchwork/TurkishUtf8.php', + 'Patchwork\\Utf8' => $vendorDir . '/patchwork/utf8/src/Patchwork/Utf8.php', + 'Patchwork\\Utf8\\BestFit' => $vendorDir . '/patchwork/utf8/src/Patchwork/Utf8/BestFit.php', + 'Patchwork\\Utf8\\Bootup' => $vendorDir . '/patchwork/utf8/src/Patchwork/Utf8/Bootup.php', + 'Patchwork\\Utf8\\WindowsStreamWrapper' => $vendorDir . '/patchwork/utf8/src/Patchwork/Utf8/WindowsStreamWrapper.php', + 'ProxyManager\\Autoloader\\Autoloader' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Autoloader/Autoloader.php', + 'ProxyManager\\Autoloader\\AutoloaderInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Autoloader/AutoloaderInterface.php', + 'ProxyManager\\Configuration' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Configuration.php', + 'ProxyManager\\Exception\\DisabledMethodException' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Exception/DisabledMethodException.php', + 'ProxyManager\\Exception\\ExceptionInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Exception/ExceptionInterface.php', + 'ProxyManager\\Exception\\FileNotWritableException' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Exception/FileNotWritableException.php', + 'ProxyManager\\Exception\\InvalidProxiedClassException' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxiedClassException.php', + 'ProxyManager\\Exception\\InvalidProxyDirectoryException' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxyDirectoryException.php', + 'ProxyManager\\Exception\\UnsupportedProxiedClassException' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Exception/UnsupportedProxiedClassException.php', + 'ProxyManager\\Factory\\AbstractBaseFactory' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractBaseFactory.php', + 'ProxyManager\\Factory\\AbstractLazyFactory' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractLazyFactory.php', + 'ProxyManager\\Factory\\AccessInterceptorScopeLocalizerFactory' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorScopeLocalizerFactory.php', + 'ProxyManager\\Factory\\AccessInterceptorValueHolderFactory' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorValueHolderFactory.php', + 'ProxyManager\\Factory\\LazyLoadingGhostFactory' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingGhostFactory.php', + 'ProxyManager\\Factory\\LazyLoadingValueHolderFactory' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingValueHolderFactory.php', + 'ProxyManager\\Factory\\NullObjectFactory' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/NullObjectFactory.php', + 'ProxyManager\\Factory\\RemoteObjectFactory' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObjectFactory.php', + 'ProxyManager\\Factory\\RemoteObject\\AdapterInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/AdapterInterface.php', + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\BaseAdapter' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/BaseAdapter.php', + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\JsonRpc' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/JsonRpc.php', + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\Soap' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/Soap.php', + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\XmlRpc' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/XmlRpc.php', + 'ProxyManager\\FileLocator\\FileLocator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocator.php', + 'ProxyManager\\FileLocator\\FileLocatorInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocatorInterface.php', + 'ProxyManager\\GeneratorStrategy\\BaseGeneratorStrategy' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/BaseGeneratorStrategy.php', + 'ProxyManager\\GeneratorStrategy\\EvaluatingGeneratorStrategy' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/EvaluatingGeneratorStrategy.php', + 'ProxyManager\\GeneratorStrategy\\FileWriterGeneratorStrategy' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/FileWriterGeneratorStrategy.php', + 'ProxyManager\\GeneratorStrategy\\GeneratorStrategyInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/GeneratorStrategyInterface.php', + 'ProxyManager\\Generator\\ClassGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Generator/ClassGenerator.php', + 'ProxyManager\\Generator\\MagicMethodGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Generator/MagicMethodGenerator.php', + 'ProxyManager\\Generator\\MethodGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Generator/MethodGenerator.php', + 'ProxyManager\\Generator\\ParameterGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Generator/ParameterGenerator.php', + 'ProxyManager\\Generator\\Util\\ClassGeneratorUtils' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Generator/Util/ClassGeneratorUtils.php', + 'ProxyManager\\Generator\\Util\\UniqueIdentifierGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Generator/Util/UniqueIdentifierGenerator.php', + 'ProxyManager\\Inflector\\ClassNameInflector' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflector.php', + 'ProxyManager\\Inflector\\ClassNameInflectorInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflectorInterface.php', + 'ProxyManager\\Inflector\\Util\\ParameterEncoder' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterEncoder.php', + 'ProxyManager\\Inflector\\Util\\ParameterHasher' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterHasher.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizerGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizerGenerator.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\Constructor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\InterceptedMethod' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethod.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicClone' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicClone.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicGet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicIsset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicSet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicSleep' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleep.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicUnset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\Util\\InterceptorGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGenerator.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolderGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolderGenerator.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\Constructor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\InterceptedMethod' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethod.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicClone' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicClone.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicGet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicIsset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicSet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicUnset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\Util\\InterceptorGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGenerator.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\MethodGenerator\\MagicWakeup' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/MagicWakeup.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\MethodGenerator\\SetMethodPrefixInterceptor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptor.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\MethodGenerator\\SetMethodSuffixInterceptor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptor.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\PropertyGenerator\\MethodPrefixInterceptors' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptors.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\PropertyGenerator\\MethodSuffixInterceptors' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptors.php', + 'ProxyManager\\ProxyGenerator\\Assertion\\CanProxyAssertion' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Assertion/CanProxyAssertion.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhostGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhostGenerator.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\CallInitializer' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\GetProxyInitializer' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\InitializeProxy' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxy.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\IsProxyInitialized' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitialized.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\LazyLoadingMethodInterceptor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/LazyLoadingMethodInterceptor.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicClone' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicClone.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicGet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicIsset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicSet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicSleep' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleep.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicUnset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\SetProxyInitializer' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\PropertyGenerator\\InitializationTracker' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTracker.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\PropertyGenerator\\InitializerProperty' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerProperty.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolderGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolderGenerator.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\GetProxyInitializer' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\InitializeProxy' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxy.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\IsProxyInitialized' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitialized.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\LazyLoadingMethodInterceptor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptor.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicClone' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicClone.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicGet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicIsset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicSet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicSleep' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleep.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicUnset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\SetProxyInitializer' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\PropertyGenerator\\InitializerProperty' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerProperty.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\PropertyGenerator\\ValueHolderProperty' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderProperty.php', + 'ProxyManager\\ProxyGenerator\\LazyLoading\\MethodGenerator\\Constructor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoading/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\NullObjectGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObjectGenerator.php', + 'ProxyManager\\ProxyGenerator\\NullObject\\MethodGenerator\\Constructor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\NullObject\\MethodGenerator\\NullObjectMethodInterceptor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptor.php', + 'ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesDefaults' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesDefaults.php', + 'ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesMap.php', + 'ProxyManager\\ProxyGenerator\\ProxyGeneratorInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ProxyGeneratorInterface.php', + 'ProxyManager\\ProxyGenerator\\RemoteObjectGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObjectGenerator.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\Constructor' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\MagicGet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\MagicIsset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\MagicSet' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\MagicUnset' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\RemoteObjectMethod' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\PropertyGenerator\\AdapterProperty' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterProperty.php', + 'ProxyManager\\ProxyGenerator\\Util\\ProxiedMethodsFilter' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/ProxiedMethodsFilter.php', + 'ProxyManager\\ProxyGenerator\\Util\\PublicScopeSimulator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/PublicScopeSimulator.php', + 'ProxyManager\\ProxyGenerator\\ValueHolder\\MethodGenerator\\GetWrappedValueHolderValue' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValue.php', + 'ProxyManager\\ProxyGenerator\\ValueHolder\\MethodGenerator\\MagicSleep' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleep.php', + 'ProxyManager\\Proxy\\AccessInterceptorInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/AccessInterceptorInterface.php', + 'ProxyManager\\Proxy\\Exception\\RemoteObjectException' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/Exception/RemoteObjectException.php', + 'ProxyManager\\Proxy\\FallbackValueHolderInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/FallbackValueHolderInterface.php', + 'ProxyManager\\Proxy\\GhostObjectInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/GhostObjectInterface.php', + 'ProxyManager\\Proxy\\LazyLoadingInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/LazyLoadingInterface.php', + 'ProxyManager\\Proxy\\NullObjectInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/NullObjectInterface.php', + 'ProxyManager\\Proxy\\ProxyInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/ProxyInterface.php', + 'ProxyManager\\Proxy\\RemoteObjectInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/RemoteObjectInterface.php', + 'ProxyManager\\Proxy\\SmartReferenceInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/SmartReferenceInterface.php', + 'ProxyManager\\Proxy\\ValueHolderInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/ValueHolderInterface.php', + 'ProxyManager\\Proxy\\VirtualProxyInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Proxy/VirtualProxyInterface.php', + 'ProxyManager\\Signature\\ClassSignatureGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGenerator.php', + 'ProxyManager\\Signature\\ClassSignatureGeneratorInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGeneratorInterface.php', + 'ProxyManager\\Signature\\Exception\\ExceptionInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/ExceptionInterface.php', + 'ProxyManager\\Signature\\Exception\\InvalidSignatureException' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/InvalidSignatureException.php', + 'ProxyManager\\Signature\\Exception\\MissingSignatureException' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/MissingSignatureException.php', + 'ProxyManager\\Signature\\SignatureChecker' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureChecker.php', + 'ProxyManager\\Signature\\SignatureCheckerInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureCheckerInterface.php', + 'ProxyManager\\Signature\\SignatureGenerator' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGenerator.php', + 'ProxyManager\\Signature\\SignatureGeneratorInterface' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGeneratorInterface.php', + 'ProxyManager\\Version' => $vendorDir . '/ocramius/proxy-manager/src/ProxyManager/Version.php', + 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.php', + 'ReCaptcha\\ReCaptcha' => $vendorDir . '/google/recaptcha/src/ReCaptcha/ReCaptcha.php', + 'ReCaptcha\\RequestMethod' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod.php', + 'ReCaptcha\\RequestMethod\\Curl' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Curl.php', + 'ReCaptcha\\RequestMethod\\CurlPost' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php', + 'ReCaptcha\\RequestMethod\\Post' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php', + 'ReCaptcha\\RequestMethod\\Socket' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php', + 'ReCaptcha\\RequestMethod\\SocketPost' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php', + 'ReCaptcha\\RequestParameters' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestParameters.php', + 'ReCaptcha\\Response' => $vendorDir . '/google/recaptcha/src/ReCaptcha/Response.php', + 'React\\Promise\\CancellablePromiseInterface' => $vendorDir . '/react/promise/src/CancellablePromiseInterface.php', + 'React\\Promise\\CancellationQueue' => $vendorDir . '/react/promise/src/CancellationQueue.php', + 'React\\Promise\\Deferred' => $vendorDir . '/react/promise/src/Deferred.php', + 'React\\Promise\\Exception\\LengthException' => $vendorDir . '/react/promise/src/Exception/LengthException.php', + 'React\\Promise\\ExtendedPromiseInterface' => $vendorDir . '/react/promise/src/ExtendedPromiseInterface.php', + 'React\\Promise\\FulfilledPromise' => $vendorDir . '/react/promise/src/FulfilledPromise.php', + 'React\\Promise\\LazyPromise' => $vendorDir . '/react/promise/src/LazyPromise.php', + 'React\\Promise\\Promise' => $vendorDir . '/react/promise/src/Promise.php', + 'React\\Promise\\PromiseInterface' => $vendorDir . '/react/promise/src/PromiseInterface.php', + 'React\\Promise\\PromisorInterface' => $vendorDir . '/react/promise/src/PromisorInterface.php', + 'React\\Promise\\RejectedPromise' => $vendorDir . '/react/promise/src/RejectedPromise.php', + 'React\\Promise\\UnhandledRejectionException' => $vendorDir . '/react/promise/src/UnhandledRejectionException.php', + 'RecursiveCallbackFilterIterator' => $vendorDir . '/symfony/polyfill-php54/Resources/stubs/RecursiveCallbackFilterIterator.php', + 'SessionHandlerInterface' => $vendorDir . '/symfony/polyfill-php54/Resources/stubs/SessionHandlerInterface.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\Instantiator\\LazyLoadingValueHolderFactoryV1' => $vendorDir . '/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\Instantiator\\LazyLoadingValueHolderFactoryV2' => $vendorDir . '/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\Instantiator\\RuntimeInstantiator' => $vendorDir . '/symfony/proxy-manager-bridge/LazyProxy/Instantiator/RuntimeInstantiator.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\PhpDumper\\LazyLoadingValueHolderGenerator' => $vendorDir . '/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\PhpDumper\\ProxyDumper' => $vendorDir . '/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/ProxyDumper.php', + 'Symfony\\Bridge\\Twig\\AppVariable' => $vendorDir . '/symfony/twig-bridge/AppVariable.php', + 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => $vendorDir . '/symfony/twig-bridge/Command/DebugCommand.php', + 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bridge/Command/LintCommand.php', + 'Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector' => $vendorDir . '/symfony/twig-bridge/DataCollector/TwigDataCollector.php', + 'Symfony\\Bridge\\Twig\\Extension\\AssetExtension' => $vendorDir . '/symfony/twig-bridge/Extension/AssetExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\CodeExtension' => $vendorDir . '/symfony/twig-bridge/Extension/CodeExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\DumpExtension' => $vendorDir . '/symfony/twig-bridge/Extension/DumpExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\ExpressionExtension' => $vendorDir . '/symfony/twig-bridge/Extension/ExpressionExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\FormExtension' => $vendorDir . '/symfony/twig-bridge/Extension/FormExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\HttpFoundationExtension' => $vendorDir . '/symfony/twig-bridge/Extension/HttpFoundationExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelExtension' => $vendorDir . '/symfony/twig-bridge/Extension/HttpKernelExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\LogoutUrlExtension' => $vendorDir . '/symfony/twig-bridge/Extension/LogoutUrlExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension' => $vendorDir . '/symfony/twig-bridge/Extension/ProfilerExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\RoutingExtension' => $vendorDir . '/symfony/twig-bridge/Extension/RoutingExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\SecurityExtension' => $vendorDir . '/symfony/twig-bridge/Extension/SecurityExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\StopwatchExtension' => $vendorDir . '/symfony/twig-bridge/Extension/StopwatchExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\TranslationExtension' => $vendorDir . '/symfony/twig-bridge/Extension/TranslationExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\YamlExtension' => $vendorDir . '/symfony/twig-bridge/Extension/YamlExtension.php', + 'Symfony\\Bridge\\Twig\\Form\\TwigRenderer' => $vendorDir . '/symfony/twig-bridge/Form/TwigRenderer.php', + 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngine' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererEngine.php', + 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngineInterface' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererEngineInterface.php', + 'Symfony\\Bridge\\Twig\\Form\\TwigRendererInterface' => $vendorDir . '/symfony/twig-bridge/Form/TwigRendererInterface.php', + 'Symfony\\Bridge\\Twig\\NodeVisitor\\Scope' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/Scope.php', + 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationDefaultDomainNodeVisitor' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php', + 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationNodeVisitor' => $vendorDir . '/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php', + 'Symfony\\Bridge\\Twig\\Node\\DumpNode' => $vendorDir . '/symfony/twig-bridge/Node/DumpNode.php', + 'Symfony\\Bridge\\Twig\\Node\\FormEnctypeNode' => $vendorDir . '/symfony/twig-bridge/Node/FormEnctypeNode.php', + 'Symfony\\Bridge\\Twig\\Node\\FormThemeNode' => $vendorDir . '/symfony/twig-bridge/Node/FormThemeNode.php', + 'Symfony\\Bridge\\Twig\\Node\\RenderBlockNode' => $vendorDir . '/symfony/twig-bridge/Node/RenderBlockNode.php', + 'Symfony\\Bridge\\Twig\\Node\\SearchAndRenderBlockNode' => $vendorDir . '/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php', + 'Symfony\\Bridge\\Twig\\Node\\StopwatchNode' => $vendorDir . '/symfony/twig-bridge/Node/StopwatchNode.php', + 'Symfony\\Bridge\\Twig\\Node\\TransDefaultDomainNode' => $vendorDir . '/symfony/twig-bridge/Node/TransDefaultDomainNode.php', + 'Symfony\\Bridge\\Twig\\Node\\TransNode' => $vendorDir . '/symfony/twig-bridge/Node/TransNode.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\DumpTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/DumpTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\FormThemeTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\StopwatchTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\TransChoiceTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\TransDefaultDomainTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\TransTokenParser' => $vendorDir . '/symfony/twig-bridge/TokenParser/TransTokenParser.php', + 'Symfony\\Bridge\\Twig\\Translation\\TwigExtractor' => $vendorDir . '/symfony/twig-bridge/Translation/TwigExtractor.php', + 'Symfony\\Bridge\\Twig\\TwigEngine' => $vendorDir . '/symfony/twig-bridge/TwigEngine.php', + 'Symfony\\Component\\Config\\ConfigCache' => $vendorDir . '/symfony/config/ConfigCache.php', + 'Symfony\\Component\\Config\\ConfigCacheFactory' => $vendorDir . '/symfony/config/ConfigCacheFactory.php', + 'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => $vendorDir . '/symfony/config/ConfigCacheFactoryInterface.php', + 'Symfony\\Component\\Config\\ConfigCacheInterface' => $vendorDir . '/symfony/config/ConfigCacheInterface.php', + 'Symfony\\Component\\Config\\Definition\\ArrayNode' => $vendorDir . '/symfony/config/Definition/ArrayNode.php', + 'Symfony\\Component\\Config\\Definition\\BaseNode' => $vendorDir . '/symfony/config/Definition/BaseNode.php', + 'Symfony\\Component\\Config\\Definition\\BooleanNode' => $vendorDir . '/symfony/config/Definition/BooleanNode.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ExprBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/MergeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NodeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => $vendorDir . '/symfony/config/Definition/Builder/NodeParentInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/NormalizationBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/NumericNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => $vendorDir . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => $vendorDir . '/symfony/config/Definition/Builder/TreeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => $vendorDir . '/symfony/config/Definition/Builder/ValidationBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => $vendorDir . '/symfony/config/Definition/Builder/VariableNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => $vendorDir . '/symfony/config/Definition/ConfigurationInterface.php', + 'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => $vendorDir . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\EnumNode' => $vendorDir . '/symfony/config/Definition/EnumNode.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => $vendorDir . '/symfony/config/Definition/Exception/DuplicateKeyException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\Exception' => $vendorDir . '/symfony/config/Definition/Exception/Exception.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => $vendorDir . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidConfigurationException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidDefinitionException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => $vendorDir . '/symfony/config/Definition/Exception/InvalidTypeException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => $vendorDir . '/symfony/config/Definition/Exception/UnsetKeyException.php', + 'Symfony\\Component\\Config\\Definition\\FloatNode' => $vendorDir . '/symfony/config/Definition/FloatNode.php', + 'Symfony\\Component\\Config\\Definition\\IntegerNode' => $vendorDir . '/symfony/config/Definition/IntegerNode.php', + 'Symfony\\Component\\Config\\Definition\\NodeInterface' => $vendorDir . '/symfony/config/Definition/NodeInterface.php', + 'Symfony\\Component\\Config\\Definition\\NumericNode' => $vendorDir . '/symfony/config/Definition/NumericNode.php', + 'Symfony\\Component\\Config\\Definition\\Processor' => $vendorDir . '/symfony/config/Definition/Processor.php', + 'Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => $vendorDir . '/symfony/config/Definition/PrototypeNodeInterface.php', + 'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => $vendorDir . '/symfony/config/Definition/PrototypedArrayNode.php', + 'Symfony\\Component\\Config\\Definition\\ReferenceDumper' => $vendorDir . '/symfony/config/Definition/ReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\ScalarNode' => $vendorDir . '/symfony/config/Definition/ScalarNode.php', + 'Symfony\\Component\\Config\\Definition\\VariableNode' => $vendorDir . '/symfony/config/Definition/VariableNode.php', + 'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => $vendorDir . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', + 'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => $vendorDir . '/symfony/config/Exception/FileLoaderLoadException.php', + 'Symfony\\Component\\Config\\FileLocator' => $vendorDir . '/symfony/config/FileLocator.php', + 'Symfony\\Component\\Config\\FileLocatorInterface' => $vendorDir . '/symfony/config/FileLocatorInterface.php', + 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => $vendorDir . '/symfony/config/Loader/DelegatingLoader.php', + 'Symfony\\Component\\Config\\Loader\\FileLoader' => $vendorDir . '/symfony/config/Loader/FileLoader.php', + 'Symfony\\Component\\Config\\Loader\\Loader' => $vendorDir . '/symfony/config/Loader/Loader.php', + 'Symfony\\Component\\Config\\Loader\\LoaderInterface' => $vendorDir . '/symfony/config/Loader/LoaderInterface.php', + 'Symfony\\Component\\Config\\Loader\\LoaderResolver' => $vendorDir . '/symfony/config/Loader/LoaderResolver.php', + 'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => $vendorDir . '/symfony/config/Loader/LoaderResolverInterface.php', + 'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => $vendorDir . '/symfony/config/ResourceCheckerConfigCache.php', + 'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => $vendorDir . '/symfony/config/ResourceCheckerConfigCacheFactory.php', + 'Symfony\\Component\\Config\\ResourceCheckerInterface' => $vendorDir . '/symfony/config/ResourceCheckerInterface.php', + 'Symfony\\Component\\Config\\Resource\\BCResourceInterfaceChecker' => $vendorDir . '/symfony/config/Resource/BCResourceInterfaceChecker.php', + 'Symfony\\Component\\Config\\Resource\\DirectoryResource' => $vendorDir . '/symfony/config/Resource/DirectoryResource.php', + 'Symfony\\Component\\Config\\Resource\\FileExistenceResource' => $vendorDir . '/symfony/config/Resource/FileExistenceResource.php', + 'Symfony\\Component\\Config\\Resource\\FileResource' => $vendorDir . '/symfony/config/Resource/FileResource.php', + 'Symfony\\Component\\Config\\Resource\\ResourceInterface' => $vendorDir . '/symfony/config/Resource/ResourceInterface.php', + 'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceChecker.php', + 'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => $vendorDir . '/symfony/config/Resource/SelfCheckingResourceInterface.php', + 'Symfony\\Component\\Config\\Util\\XmlUtils' => $vendorDir . '/symfony/config/Util/XmlUtils.php', + 'Symfony\\Component\\Console\\Application' => $vendorDir . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\Command\\Command' => $vendorDir . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => $vendorDir . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => $vendorDir . '/symfony/console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => $vendorDir . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => $vendorDir . '/symfony/console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => $vendorDir . '/symfony/console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => $vendorDir . '/symfony/console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => $vendorDir . '/symfony/console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => $vendorDir . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => $vendorDir . '/symfony/console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => $vendorDir . '/symfony/console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => $vendorDir . '/symfony/console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => $vendorDir . '/symfony/console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => $vendorDir . '/symfony/console/Event/ConsoleExceptionEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => $vendorDir . '/symfony/console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => $vendorDir . '/symfony/console/Exception/CommandNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/console/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/console/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => $vendorDir . '/symfony/console/Exception/InvalidOptionException.php', + 'Symfony\\Component\\Console\\Exception\\LogicException' => $vendorDir . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\RuntimeException' => $vendorDir . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => $vendorDir . '/symfony/console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => $vendorDir . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => $vendorDir . '/symfony/console/Helper/DebugFormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => $vendorDir . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\DialogHelper' => $vendorDir . '/symfony/console/Helper/DialogHelper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => $vendorDir . '/symfony/console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => $vendorDir . '/symfony/console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => $vendorDir . '/symfony/console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => $vendorDir . '/symfony/console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => $vendorDir . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => $vendorDir . '/symfony/console/Helper/ProcessHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressBar' => $vendorDir . '/symfony/console/Helper/ProgressBar.php', + 'Symfony\\Component\\Console\\Helper\\ProgressHelper' => $vendorDir . '/symfony/console/Helper/ProgressHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => $vendorDir . '/symfony/console/Helper/ProgressIndicator.php', + 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => $vendorDir . '/symfony/console/Helper/QuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => $vendorDir . '/symfony/console/Helper/SymfonyQuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\Table' => $vendorDir . '/symfony/console/Helper/Table.php', + 'Symfony\\Component\\Console\\Helper\\TableCell' => $vendorDir . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableHelper' => $vendorDir . '/symfony/console/Helper/TableHelper.php', + 'Symfony\\Component\\Console\\Helper\\TableSeparator' => $vendorDir . '/symfony/console/Helper/TableSeparator.php', + 'Symfony\\Component\\Console\\Helper\\TableStyle' => $vendorDir . '/symfony/console/Helper/TableStyle.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => $vendorDir . '/symfony/console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => $vendorDir . '/symfony/console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => $vendorDir . '/symfony/console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => $vendorDir . '/symfony/console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => $vendorDir . '/symfony/console/Input/InputAwareInterface.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => $vendorDir . '/symfony/console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => $vendorDir . '/symfony/console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => $vendorDir . '/symfony/console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => $vendorDir . '/symfony/console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => $vendorDir . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Output\\BufferedOutput' => $vendorDir . '/symfony/console/Output/BufferedOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => $vendorDir . '/symfony/console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => $vendorDir . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => $vendorDir . '/symfony/console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => $vendorDir . '/symfony/console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => $vendorDir . '/symfony/console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => $vendorDir . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => $vendorDir . '/symfony/console/Question/ChoiceQuestion.php', + 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => $vendorDir . '/symfony/console/Question/ConfirmationQuestion.php', + 'Symfony\\Component\\Console\\Question\\Question' => $vendorDir . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\Shell' => $vendorDir . '/symfony/console/Shell.php', + 'Symfony\\Component\\Console\\Style\\OutputStyle' => $vendorDir . '/symfony/console/Style/OutputStyle.php', + 'Symfony\\Component\\Console\\Style\\StyleInterface' => $vendorDir . '/symfony/console/Style/StyleInterface.php', + 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => $vendorDir . '/symfony/console/Style/SymfonyStyle.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => $vendorDir . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => $vendorDir . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Debug\\BufferingLogger' => $vendorDir . '/symfony/debug/BufferingLogger.php', + 'Symfony\\Component\\Debug\\Debug' => $vendorDir . '/symfony/debug/Debug.php', + 'Symfony\\Component\\Debug\\DebugClassLoader' => $vendorDir . '/symfony/debug/DebugClassLoader.php', + 'Symfony\\Component\\Debug\\ErrorHandler' => $vendorDir . '/symfony/debug/ErrorHandler.php', + 'Symfony\\Component\\Debug\\ErrorHandlerCanary' => $vendorDir . '/symfony/debug/ErrorHandler.php', + 'Symfony\\Component\\Debug\\ExceptionHandler' => $vendorDir . '/symfony/debug/ExceptionHandler.php', + 'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => $vendorDir . '/symfony/debug/Exception/ClassNotFoundException.php', + 'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => $vendorDir . '/symfony/debug/Exception/ContextErrorException.php', + 'Symfony\\Component\\Debug\\Exception\\DummyException' => $vendorDir . '/symfony/debug/Exception/DummyException.php', + 'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => $vendorDir . '/symfony/debug/Exception/FatalErrorException.php', + 'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => $vendorDir . '/symfony/debug/Exception/FatalThrowableError.php', + 'Symfony\\Component\\Debug\\Exception\\FlattenException' => $vendorDir . '/symfony/debug/Exception/FlattenException.php', + 'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => $vendorDir . '/symfony/debug/Exception/OutOfMemoryException.php', + 'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => $vendorDir . '/symfony/debug/Exception/UndefinedFunctionException.php', + 'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => $vendorDir . '/symfony/debug/Exception/UndefinedMethodException.php', + 'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php', + 'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => $vendorDir . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php', + 'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php', + 'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => $vendorDir . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php', + 'Symfony\\Component\\DependencyInjection\\Alias' => $vendorDir . '/symfony/dependency-injection/Alias.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AutoAliasServicePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutoAliasServicePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowirePass' => $vendorDir . '/symfony/dependency-injection/Compiler/AutowirePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckCircularReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckExceptionOnInvalidReferenceBehaviorPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckReferenceValidityPass' => $vendorDir . '/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\Compiler' => $vendorDir . '/symfony/dependency-injection/Compiler/Compiler.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface' => $vendorDir . '/symfony/dependency-injection/Compiler/CompilerPassInterface.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\DecoratorServicePass' => $vendorDir . '/symfony/dependency-injection/Compiler/DecoratorServicePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ExtensionCompilerPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\InlineServiceDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\LoggingFormatter' => $vendorDir . '/symfony/dependency-injection/Compiler/LoggingFormatter.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\MergeExtensionConfigurationPass' => $vendorDir . '/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\PassConfig' => $vendorDir . '/symfony/dependency-injection/Compiler/PassConfig.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveUnusedDefinitionsPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RepeatablePassInterface' => $vendorDir . '/symfony/dependency-injection/Compiler/RepeatablePassInterface.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RepeatedPass' => $vendorDir . '/symfony/dependency-injection/Compiler/RepeatedPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ReplaceAliasByActualDefinitionPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveDefinitionTemplatesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInvalidReferencesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveParameterPlaceHoldersPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveReferencesToAliasesPass' => $vendorDir . '/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraph' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphEdge' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphNode' => $vendorDir . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php', + 'Symfony\\Component\\DependencyInjection\\Container' => $vendorDir . '/symfony/dependency-injection/Container.php', + 'Symfony\\Component\\DependencyInjection\\ContainerAware' => $vendorDir . '/symfony/dependency-injection/ContainerAware.php', + 'Symfony\\Component\\DependencyInjection\\ContainerAwareInterface' => $vendorDir . '/symfony/dependency-injection/ContainerAwareInterface.php', + 'Symfony\\Component\\DependencyInjection\\ContainerAwareTrait' => $vendorDir . '/symfony/dependency-injection/ContainerAwareTrait.php', + 'Symfony\\Component\\DependencyInjection\\ContainerBuilder' => $vendorDir . '/symfony/dependency-injection/ContainerBuilder.php', + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => $vendorDir . '/symfony/dependency-injection/ContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\Definition' => $vendorDir . '/symfony/dependency-injection/Definition.php', + 'Symfony\\Component\\DependencyInjection\\DefinitionDecorator' => $vendorDir . '/symfony/dependency-injection/DefinitionDecorator.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\Dumper' => $vendorDir . '/symfony/dependency-injection/Dumper/Dumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\DumperInterface' => $vendorDir . '/symfony/dependency-injection/Dumper/DumperInterface.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\GraphvizDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/GraphvizDumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/PhpDumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\XmlDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/XmlDumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\YamlDumper' => $vendorDir . '/symfony/dependency-injection/Dumper/YamlDumper.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\BadMethodCallException' => $vendorDir . '/symfony/dependency-injection/Exception/BadMethodCallException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/dependency-injection/Exception/ExceptionInterface.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\InactiveScopeException' => $vendorDir . '/symfony/dependency-injection/Exception/InactiveScopeException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException' => $vendorDir . '/symfony/dependency-injection/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\LogicException' => $vendorDir . '/symfony/dependency-injection/Exception/LogicException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\OutOfBoundsException' => $vendorDir . '/symfony/dependency-injection/Exception/OutOfBoundsException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ParameterCircularReferenceException' => $vendorDir . '/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ParameterNotFoundException' => $vendorDir . '/symfony/dependency-injection/Exception/ParameterNotFoundException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\RuntimeException' => $vendorDir . '/symfony/dependency-injection/Exception/RuntimeException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ScopeCrossingInjectionException' => $vendorDir . '/symfony/dependency-injection/Exception/ScopeCrossingInjectionException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException' => $vendorDir . '/symfony/dependency-injection/Exception/ScopeWideningInjectionException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ServiceCircularReferenceException' => $vendorDir . '/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException' => $vendorDir . '/symfony/dependency-injection/Exception/ServiceNotFoundException.php', + 'Symfony\\Component\\DependencyInjection\\ExpressionLanguage' => $vendorDir . '/symfony/dependency-injection/ExpressionLanguage.php', + 'Symfony\\Component\\DependencyInjection\\ExpressionLanguageProvider' => $vendorDir . '/symfony/dependency-injection/ExpressionLanguageProvider.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\ConfigurationExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\Extension' => $vendorDir . '/symfony/dependency-injection/Extension/Extension.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/ExtensionInterface.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface' => $vendorDir . '/symfony/dependency-injection/Extension/PrependExtensionInterface.php', + 'Symfony\\Component\\DependencyInjection\\IntrospectableContainerInterface' => $vendorDir . '/symfony/dependency-injection/IntrospectableContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\InstantiatorInterface' => $vendorDir . '/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\RealServiceInstantiator' => $vendorDir . '/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\DumperInterface' => $vendorDir . '/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\NullDumper' => $vendorDir . '/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\ClosureLoader' => $vendorDir . '/symfony/dependency-injection/Loader/ClosureLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\DirectoryLoader' => $vendorDir . '/symfony/dependency-injection/Loader/DirectoryLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\FileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/FileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\IniFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/IniFileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/PhpFileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/XmlFileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/dependency-injection/Loader/YamlFileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Parameter' => $vendorDir . '/symfony/dependency-injection/Parameter.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\FrozenParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ParameterBag.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => $vendorDir . '/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php', + 'Symfony\\Component\\DependencyInjection\\Reference' => $vendorDir . '/symfony/dependency-injection/Reference.php', + 'Symfony\\Component\\DependencyInjection\\ResettableContainerInterface' => $vendorDir . '/symfony/dependency-injection/ResettableContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\Scope' => $vendorDir . '/symfony/dependency-injection/Scope.php', + 'Symfony\\Component\\DependencyInjection\\ScopeInterface' => $vendorDir . '/symfony/dependency-injection/ScopeInterface.php', + 'Symfony\\Component\\DependencyInjection\\SimpleXMLElement' => $vendorDir . '/symfony/dependency-injection/SimpleXMLElement.php', + 'Symfony\\Component\\DependencyInjection\\TaggedContainerInterface' => $vendorDir . '/symfony/dependency-injection/TaggedContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\Variable' => $vendorDir . '/symfony/dependency-injection/Variable.php', + 'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ContainerAwareEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => $vendorDir . '/symfony/event-dispatcher/Debug/WrappedListener.php', + 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => $vendorDir . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', + 'Symfony\\Component\\EventDispatcher\\Event' => $vendorDir . '/symfony/event-dispatcher/Event.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => $vendorDir . '/symfony/event-dispatcher/EventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => $vendorDir . '/symfony/event-dispatcher/EventDispatcherInterface.php', + 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => $vendorDir . '/symfony/event-dispatcher/EventSubscriberInterface.php', + 'Symfony\\Component\\EventDispatcher\\GenericEvent' => $vendorDir . '/symfony/event-dispatcher/GenericEvent.php', + 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => $vendorDir . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/filesystem/Exception/FileNotFoundException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => $vendorDir . '/symfony/filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => $vendorDir . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => $vendorDir . '/symfony/filesystem/Filesystem.php', + 'Symfony\\Component\\Filesystem\\LockHandler' => $vendorDir . '/symfony/filesystem/LockHandler.php', + 'Symfony\\Component\\Finder\\Adapter\\AbstractAdapter' => $vendorDir . '/symfony/finder/Adapter/AbstractAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\AbstractFindAdapter' => $vendorDir . '/symfony/finder/Adapter/AbstractFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\AdapterInterface' => $vendorDir . '/symfony/finder/Adapter/AdapterInterface.php', + 'Symfony\\Component\\Finder\\Adapter\\BsdFindAdapter' => $vendorDir . '/symfony/finder/Adapter/BsdFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\GnuFindAdapter' => $vendorDir . '/symfony/finder/Adapter/GnuFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\PhpAdapter' => $vendorDir . '/symfony/finder/Adapter/PhpAdapter.php', + 'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php', + 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php', + 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php', + 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php', + 'Symfony\\Component\\Finder\\Exception\\AdapterFailureException' => $vendorDir . '/symfony/finder/Exception/AdapterFailureException.php', + 'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/finder/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Finder\\Exception\\OperationNotPermitedException' => $vendorDir . '/symfony/finder/Exception/OperationNotPermitedException.php', + 'Symfony\\Component\\Finder\\Exception\\ShellCommandFailureException' => $vendorDir . '/symfony/finder/Exception/ShellCommandFailureException.php', + 'Symfony\\Component\\Finder\\Expression\\Expression' => $vendorDir . '/symfony/finder/Expression/Expression.php', + 'Symfony\\Component\\Finder\\Expression\\Glob' => $vendorDir . '/symfony/finder/Expression/Glob.php', + 'Symfony\\Component\\Finder\\Expression\\Regex' => $vendorDir . '/symfony/finder/Expression/Regex.php', + 'Symfony\\Component\\Finder\\Expression\\ValueInterface' => $vendorDir . '/symfony/finder/Expression/ValueInterface.php', + 'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php', + 'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php', + 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilePathsIterator' => $vendorDir . '/symfony/finder/Iterator/FilePathsIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FileTypeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\Shell\\Command' => $vendorDir . '/symfony/finder/Shell/Command.php', + 'Symfony\\Component\\Finder\\Shell\\Shell' => $vendorDir . '/symfony/finder/Shell/Shell.php', + 'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php', + 'Symfony\\Component\\HttpFoundation\\AcceptHeader' => $vendorDir . '/symfony/http-foundation/AcceptHeader.php', + 'Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => $vendorDir . '/symfony/http-foundation/AcceptHeaderItem.php', + 'Symfony\\Component\\HttpFoundation\\ApacheRequest' => $vendorDir . '/symfony/http-foundation/ApacheRequest.php', + 'Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => $vendorDir . '/symfony/http-foundation/BinaryFileResponse.php', + 'Symfony\\Component\\HttpFoundation\\Cookie' => $vendorDir . '/symfony/http-foundation/Cookie.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\ConflictingHeadersException' => $vendorDir . '/symfony/http-foundation/Exception/ConflictingHeadersException.php', + 'Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => $vendorDir . '/symfony/http-foundation/ExpressionRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\FileBag' => $vendorDir . '/symfony/http-foundation/FileBag.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => $vendorDir . '/symfony/http-foundation/File/Exception/FileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => $vendorDir . '/symfony/http-foundation/File/Exception/FileNotFoundException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => $vendorDir . '/symfony/http-foundation/File/Exception/UnexpectedTypeException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => $vendorDir . '/symfony/http-foundation/File/Exception/UploadException.php', + 'Symfony\\Component\\HttpFoundation\\File\\File' => $vendorDir . '/symfony/http-foundation/File/File.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/ExtensionGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesserInterface' => $vendorDir . '/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileBinaryMimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileinfoMimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeExtensionGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesser' => $vendorDir . '/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesserInterface' => $vendorDir . '/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php', + 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => $vendorDir . '/symfony/http-foundation/File/UploadedFile.php', + 'Symfony\\Component\\HttpFoundation\\HeaderBag' => $vendorDir . '/symfony/http-foundation/HeaderBag.php', + 'Symfony\\Component\\HttpFoundation\\IpUtils' => $vendorDir . '/symfony/http-foundation/IpUtils.php', + 'Symfony\\Component\\HttpFoundation\\JsonResponse' => $vendorDir . '/symfony/http-foundation/JsonResponse.php', + 'Symfony\\Component\\HttpFoundation\\ParameterBag' => $vendorDir . '/symfony/http-foundation/ParameterBag.php', + 'Symfony\\Component\\HttpFoundation\\RedirectResponse' => $vendorDir . '/symfony/http-foundation/RedirectResponse.php', + 'Symfony\\Component\\HttpFoundation\\Request' => $vendorDir . '/symfony/http-foundation/Request.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher' => $vendorDir . '/symfony/http-foundation/RequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface' => $vendorDir . '/symfony/http-foundation/RequestMatcherInterface.php', + 'Symfony\\Component\\HttpFoundation\\RequestStack' => $vendorDir . '/symfony/http-foundation/RequestStack.php', + 'Symfony\\Component\\HttpFoundation\\Response' => $vendorDir . '/symfony/http-foundation/Response.php', + 'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag' => $vendorDir . '/symfony/http-foundation/ResponseHeaderBag.php', + 'Symfony\\Component\\HttpFoundation\\ServerBag' => $vendorDir . '/symfony/http-foundation/ServerBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag' => $vendorDir . '/symfony/http-foundation/Session/Attribute/AttributeBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface' => $vendorDir . '/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag' => $vendorDir . '/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag' => $vendorDir . '/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag' => $vendorDir . '/symfony/http-foundation/Session/Flash/FlashBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface' => $vendorDir . '/symfony/http-foundation/Session/Flash/FlashBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Session' => $vendorDir . '/symfony/http-foundation/Session/Session.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface' => $vendorDir . '/symfony/http-foundation/Session/SessionBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionInterface' => $vendorDir . '/symfony/http-foundation/Session/SessionInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\LegacyPdoSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/LegacyPdoSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\WriteCheckSessionHandler' => $vendorDir . '/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => $vendorDir . '/symfony/http-foundation/Session/Storage/MetadataBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/NativeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage' => $vendorDir . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\NativeProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => $vendorDir . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => $vendorDir . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', + 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => $vendorDir . '/symfony/http-foundation/StreamedResponse.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => $vendorDir . '/symfony/http-kernel/Bundle/Bundle.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => $vendorDir . '/symfony/http-kernel/Bundle/BundleInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => $vendorDir . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheClearer\\ChainCacheClearer' => $vendorDir . '/symfony/http-kernel/CacheClearer/ChainCacheClearer.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmer' => $vendorDir . '/symfony/http-kernel/CacheWarmer/CacheWarmer.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerAggregate' => $vendorDir . '/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface' => $vendorDir . '/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\WarmableInterface' => $vendorDir . '/symfony/http-kernel/CacheWarmer/WarmableInterface.php', + 'Symfony\\Component\\HttpKernel\\Client' => $vendorDir . '/symfony/http-kernel/Client.php', + 'Symfony\\Component\\HttpKernel\\Config\\EnvParametersResource' => $vendorDir . '/symfony/http-kernel/Config/EnvParametersResource.php', + 'Symfony\\Component\\HttpKernel\\Config\\FileLocator' => $vendorDir . '/symfony/http-kernel/Config/FileLocator.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => $vendorDir . '/symfony/http-kernel/Controller/ControllerReference.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/ControllerResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => $vendorDir . '/symfony/http-kernel/Controller/ControllerResolverInterface.php', + 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => $vendorDir . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\ConfigDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/ConfigDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/DataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface' => $vendorDir . '/symfony/http-kernel/DataCollector/DataCollectorInterface.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\DumpDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/DumpDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\EventDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/EventDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\ExceptionDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/ExceptionDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\LateDataCollectorInterface' => $vendorDir . '/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\LoggerDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/LoggerDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\MemoryDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/MemoryDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/RequestDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/RouterDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => $vendorDir . '/symfony/http-kernel/DataCollector/TimeDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\Util\\ValueExporter' => $vendorDir . '/symfony/http-kernel/DataCollector/Util/ValueExporter.php', + 'Symfony\\Component\\HttpKernel\\Debug\\ErrorHandler' => $vendorDir . '/symfony/http-kernel/Debug/ErrorHandler.php', + 'Symfony\\Component\\HttpKernel\\Debug\\ExceptionHandler' => $vendorDir . '/symfony/http-kernel/Debug/ExceptionHandler.php', + 'Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => $vendorDir . '/symfony/http-kernel/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddClassesToCachePass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel' => $vendorDir . '/symfony/http-kernel/DependencyInjection/ContainerAwareHttpKernel.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\Extension' => $vendorDir . '/symfony/http-kernel/DependencyInjection/Extension.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\FragmentRendererPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\LazyLoadingFragmentHandler' => $vendorDir . '/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterListenersPass' => $vendorDir . '/symfony/http-kernel/DependencyInjection/RegisterListenersPass.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => $vendorDir . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => $vendorDir . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => $vendorDir . '/symfony/http-kernel/EventListener/DumpListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorsLoggerListener' => $vendorDir . '/symfony/http-kernel/EventListener/ErrorsLoggerListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\EsiListener' => $vendorDir . '/symfony/http-kernel/EventListener/EsiListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener' => $vendorDir . '/symfony/http-kernel/EventListener/ExceptionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => $vendorDir . '/symfony/http-kernel/EventListener/FragmentListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => $vendorDir . '/symfony/http-kernel/EventListener/LocaleListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => $vendorDir . '/symfony/http-kernel/EventListener/ProfilerListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => $vendorDir . '/symfony/http-kernel/EventListener/ResponseListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener' => $vendorDir . '/symfony/http-kernel/EventListener/RouterListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\SaveSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/SaveSessionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/SessionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => $vendorDir . '/symfony/http-kernel/EventListener/StreamedResponseListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\SurrogateListener' => $vendorDir . '/symfony/http-kernel/EventListener/SurrogateListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => $vendorDir . '/symfony/http-kernel/EventListener/TestSessionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\TranslatorListener' => $vendorDir . '/symfony/http-kernel/EventListener/TranslatorListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => $vendorDir . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', + 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent' => $vendorDir . '/symfony/http-kernel/Event/FilterControllerEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/FilterResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent' => $vendorDir . '/symfony/http-kernel/Event/FinishRequestEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/GetResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent' => $vendorDir . '/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent' => $vendorDir . '/symfony/http-kernel/Event/GetResponseForExceptionEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => $vendorDir . '/symfony/http-kernel/Event/KernelEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent' => $vendorDir . '/symfony/http-kernel/Event/PostResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/AccessDeniedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => $vendorDir . '/symfony/http-kernel/Exception/BadRequestHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => $vendorDir . '/symfony/http-kernel/Exception/ConflictHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => $vendorDir . '/symfony/http-kernel/Exception/GoneHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\HttpException' => $vendorDir . '/symfony/http-kernel/Exception/HttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => $vendorDir . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', + 'Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Exception/LengthRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => $vendorDir . '/symfony/http-kernel/Exception/NotAcceptableHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException' => $vendorDir . '/symfony/http-kernel/Exception/NotFoundHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionFailedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/PreconditionFailedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionRequiredHttpException' => $vendorDir . '/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => $vendorDir . '/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => $vendorDir . '/symfony/http-kernel/Exception/TooManyRequestsHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnauthorizedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnprocessableEntityHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnsupportedMediaTypeHttpException' => $vendorDir . '/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\AbstractSurrogateFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\EsiFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/EsiFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentHandler' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentHandler.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentRendererInterface' => $vendorDir . '/symfony/http-kernel/Fragment/FragmentRendererInterface.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\HIncludeFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\InlineFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/InlineFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\RoutableFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\SsiFragmentRenderer' => $vendorDir . '/symfony/http-kernel/Fragment/SsiFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\Esi' => $vendorDir . '/symfony/http-kernel/HttpCache/Esi.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\EsiResponseCacheStrategy' => $vendorDir . '/symfony/http-kernel/HttpCache/EsiResponseCacheStrategy.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\EsiResponseCacheStrategyInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/EsiResponseCacheStrategyInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache' => $vendorDir . '/symfony/http-kernel/HttpCache/HttpCache.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\ResponseCacheStrategy' => $vendorDir . '/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\ResponseCacheStrategyInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\Ssi' => $vendorDir . '/symfony/http-kernel/HttpCache/Ssi.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\Store' => $vendorDir . '/symfony/http-kernel/HttpCache/Store.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/StoreInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler' => $vendorDir . '/symfony/http-kernel/HttpCache/SubRequestHandler.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\SurrogateInterface' => $vendorDir . '/symfony/http-kernel/HttpCache/SurrogateInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpKernel' => $vendorDir . '/symfony/http-kernel/HttpKernel.php', + 'Symfony\\Component\\HttpKernel\\HttpKernelInterface' => $vendorDir . '/symfony/http-kernel/HttpKernelInterface.php', + 'Symfony\\Component\\HttpKernel\\Kernel' => $vendorDir . '/symfony/http-kernel/Kernel.php', + 'Symfony\\Component\\HttpKernel\\KernelEvents' => $vendorDir . '/symfony/http-kernel/KernelEvents.php', + 'Symfony\\Component\\HttpKernel\\KernelInterface' => $vendorDir . '/symfony/http-kernel/KernelInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface' => $vendorDir . '/symfony/http-kernel/Log/DebugLoggerInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\LoggerInterface' => $vendorDir . '/symfony/http-kernel/Log/LoggerInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\NullLogger' => $vendorDir . '/symfony/http-kernel/Log/NullLogger.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\BaseMemcacheProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/BaseMemcacheProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\FileProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/FileProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MemcacheProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/MemcacheProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MemcachedProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/MemcachedProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MongoDbProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/MongoDbProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MysqlProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/MysqlProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\PdoProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/PdoProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\Profile' => $vendorDir . '/symfony/http-kernel/Profiler/Profile.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\Profiler' => $vendorDir . '/symfony/http-kernel/Profiler/Profiler.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface' => $vendorDir . '/symfony/http-kernel/Profiler/ProfilerStorageInterface.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\RedisProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/RedisProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\SqliteProfilerStorage' => $vendorDir . '/symfony/http-kernel/Profiler/SqliteProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\TerminableInterface' => $vendorDir . '/symfony/http-kernel/TerminableInterface.php', + 'Symfony\\Component\\HttpKernel\\UriSigner' => $vendorDir . '/symfony/http-kernel/UriSigner.php', + 'Symfony\\Component\\Routing\\Annotation\\Route' => $vendorDir . '/symfony/routing/Annotation/Route.php', + 'Symfony\\Component\\Routing\\CompiledRoute' => $vendorDir . '/symfony/routing/CompiledRoute.php', + 'Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/routing/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Routing\\Exception\\InvalidParameterException' => $vendorDir . '/symfony/routing/Exception/InvalidParameterException.php', + 'Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException' => $vendorDir . '/symfony/routing/Exception/MethodNotAllowedException.php', + 'Symfony\\Component\\Routing\\Exception\\MissingMandatoryParametersException' => $vendorDir . '/symfony/routing/Exception/MissingMandatoryParametersException.php', + 'Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => $vendorDir . '/symfony/routing/Exception/ResourceNotFoundException.php', + 'Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => $vendorDir . '/symfony/routing/Exception/RouteNotFoundException.php', + 'Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => $vendorDir . '/symfony/routing/Generator/ConfigurableRequirementsInterface.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/GeneratorDumper.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => $vendorDir . '/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper' => $vendorDir . '/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php', + 'Symfony\\Component\\Routing\\Generator\\UrlGenerator' => $vendorDir . '/symfony/routing/Generator/UrlGenerator.php', + 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface' => $vendorDir . '/symfony/routing/Generator/UrlGeneratorInterface.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationClassLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => $vendorDir . '/symfony/routing/Loader/AnnotationFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ClosureLoader' => $vendorDir . '/symfony/routing/Loader/ClosureLoader.php', + 'Symfony\\Component\\Routing\\Loader\\DependencyInjection\\ServiceRouterLoader' => $vendorDir . '/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php', + 'Symfony\\Component\\Routing\\Loader\\DirectoryLoader' => $vendorDir . '/symfony/routing/Loader/DirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ObjectRouteLoader' => $vendorDir . '/symfony/routing/Loader/ObjectRouteLoader.php', + 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => $vendorDir . '/symfony/routing/Loader/PhpFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\RecursiveCallbackFilterIterator' => $vendorDir . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => $vendorDir . '/symfony/routing/Loader/XmlFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => $vendorDir . '/symfony/routing/Loader/YamlFileLoader.php', + 'Symfony\\Component\\Routing\\Matcher\\ApacheUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/ApacheUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\ApacheMatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/ApacheMatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperCollection' => $vendorDir . '/symfony/routing/Matcher/Dumper/DumperCollection.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperPrefixCollection' => $vendorDir . '/symfony/routing/Matcher/Dumper/DumperPrefixCollection.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperRoute' => $vendorDir . '/symfony/routing/Matcher/Dumper/DumperRoute.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/MatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumperInterface' => $vendorDir . '/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper' => $vendorDir . '/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/RedirectableUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\RequestMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/RequestMatcherInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\TraceableUrlMatcher' => $vendorDir . '/symfony/routing/Matcher/TraceableUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher' => $vendorDir . '/symfony/routing/Matcher/UrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface' => $vendorDir . '/symfony/routing/Matcher/UrlMatcherInterface.php', + 'Symfony\\Component\\Routing\\RequestContext' => $vendorDir . '/symfony/routing/RequestContext.php', + 'Symfony\\Component\\Routing\\RequestContextAwareInterface' => $vendorDir . '/symfony/routing/RequestContextAwareInterface.php', + 'Symfony\\Component\\Routing\\Route' => $vendorDir . '/symfony/routing/Route.php', + 'Symfony\\Component\\Routing\\RouteCollection' => $vendorDir . '/symfony/routing/RouteCollection.php', + 'Symfony\\Component\\Routing\\RouteCollectionBuilder' => $vendorDir . '/symfony/routing/RouteCollectionBuilder.php', + 'Symfony\\Component\\Routing\\RouteCompiler' => $vendorDir . '/symfony/routing/RouteCompiler.php', + 'Symfony\\Component\\Routing\\RouteCompilerInterface' => $vendorDir . '/symfony/routing/RouteCompilerInterface.php', + 'Symfony\\Component\\Routing\\Router' => $vendorDir . '/symfony/routing/Router.php', + 'Symfony\\Component\\Routing\\RouterInterface' => $vendorDir . '/symfony/routing/RouterInterface.php', + 'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php', + 'Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php', + 'Symfony\\Component\\Yaml\\Exception\\DumpException' => $vendorDir . '/symfony/yaml/Exception/DumpException.php', + 'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/yaml/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Yaml\\Exception\\ParseException' => $vendorDir . '/symfony/yaml/Exception/ParseException.php', + 'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => $vendorDir . '/symfony/yaml/Exception/RuntimeException.php', + 'Symfony\\Component\\Yaml\\Inline' => $vendorDir . '/symfony/yaml/Inline.php', + 'Symfony\\Component\\Yaml\\Parser' => $vendorDir . '/symfony/yaml/Parser.php', + 'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php', + 'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php54\\Php54' => $vendorDir . '/symfony/polyfill-php54/Php54.php', + 'Symfony\\Polyfill\\Php55\\Php55' => $vendorDir . '/symfony/polyfill-php55/Php55.php', + 'Symfony\\Polyfill\\Php55\\Php55ArrayColumn' => $vendorDir . '/symfony/polyfill-php55/Php55ArrayColumn.php', + 'Twig\\Cache\\CacheInterface' => $vendorDir . '/twig/twig/src/Cache/CacheInterface.php', + 'Twig\\Cache\\FilesystemCache' => $vendorDir . '/twig/twig/src/Cache/FilesystemCache.php', + 'Twig\\Cache\\NullCache' => $vendorDir . '/twig/twig/src/Cache/NullCache.php', + 'Twig\\Compiler' => $vendorDir . '/twig/twig/src/Compiler.php', + 'Twig\\Environment' => $vendorDir . '/twig/twig/src/Environment.php', + 'Twig\\Error\\Error' => $vendorDir . '/twig/twig/src/Error/Error.php', + 'Twig\\Error\\LoaderError' => $vendorDir . '/twig/twig/src/Error/LoaderError.php', + 'Twig\\Error\\RuntimeError' => $vendorDir . '/twig/twig/src/Error/RuntimeError.php', + 'Twig\\Error\\SyntaxError' => $vendorDir . '/twig/twig/src/Error/SyntaxError.php', + 'Twig\\ExpressionParser' => $vendorDir . '/twig/twig/src/ExpressionParser.php', + 'Twig\\Extension\\AbstractExtension' => $vendorDir . '/twig/twig/src/Extension/AbstractExtension.php', + 'Twig\\Extension\\CoreExtension' => $vendorDir . '/twig/twig/src/Extension/CoreExtension.php', + 'Twig\\Extension\\DebugExtension' => $vendorDir . '/twig/twig/src/Extension/DebugExtension.php', + 'Twig\\Extension\\EscaperExtension' => $vendorDir . '/twig/twig/src/Extension/EscaperExtension.php', + 'Twig\\Extension\\ExtensionInterface' => $vendorDir . '/twig/twig/src/Extension/ExtensionInterface.php', + 'Twig\\Extension\\GlobalsInterface' => $vendorDir . '/twig/twig/src/Extension/GlobalsInterface.php', + 'Twig\\Extension\\InitRuntimeInterface' => $vendorDir . '/twig/twig/src/Extension/InitRuntimeInterface.php', + 'Twig\\Extension\\OptimizerExtension' => $vendorDir . '/twig/twig/src/Extension/OptimizerExtension.php', + 'Twig\\Extension\\ProfilerExtension' => $vendorDir . '/twig/twig/src/Extension/ProfilerExtension.php', + 'Twig\\Extension\\RuntimeExtensionInterface' => $vendorDir . '/twig/twig/src/Extension/RuntimeExtensionInterface.php', + 'Twig\\Extension\\SandboxExtension' => $vendorDir . '/twig/twig/src/Extension/SandboxExtension.php', + 'Twig\\Extension\\StagingExtension' => $vendorDir . '/twig/twig/src/Extension/StagingExtension.php', + 'Twig\\Extension\\StringLoaderExtension' => $vendorDir . '/twig/twig/src/Extension/StringLoaderExtension.php', + 'Twig\\FileExtensionEscapingStrategy' => $vendorDir . '/twig/twig/src/FileExtensionEscapingStrategy.php', + 'Twig\\Lexer' => $vendorDir . '/twig/twig/src/Lexer.php', + 'Twig\\Loader\\ArrayLoader' => $vendorDir . '/twig/twig/src/Loader/ArrayLoader.php', + 'Twig\\Loader\\ChainLoader' => $vendorDir . '/twig/twig/src/Loader/ChainLoader.php', + 'Twig\\Loader\\ExistsLoaderInterface' => $vendorDir . '/twig/twig/src/Loader/ExistsLoaderInterface.php', + 'Twig\\Loader\\FilesystemLoader' => $vendorDir . '/twig/twig/src/Loader/FilesystemLoader.php', + 'Twig\\Loader\\LoaderInterface' => $vendorDir . '/twig/twig/src/Loader/LoaderInterface.php', + 'Twig\\Loader\\SourceContextLoaderInterface' => $vendorDir . '/twig/twig/src/Loader/SourceContextLoaderInterface.php', + 'Twig\\Markup' => $vendorDir . '/twig/twig/src/Markup.php', + 'Twig\\NodeTraverser' => $vendorDir . '/twig/twig/src/NodeTraverser.php', + 'Twig\\NodeVisitor\\AbstractNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', + 'Twig\\NodeVisitor\\EscaperNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\NodeVisitorInterface' => $vendorDir . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', + 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', + 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', + 'Twig\\NodeVisitor\\SandboxNodeVisitor' => $vendorDir . '/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php', + 'Twig\\Node\\AutoEscapeNode' => $vendorDir . '/twig/twig/src/Node/AutoEscapeNode.php', + 'Twig\\Node\\BlockNode' => $vendorDir . '/twig/twig/src/Node/BlockNode.php', + 'Twig\\Node\\BlockReferenceNode' => $vendorDir . '/twig/twig/src/Node/BlockReferenceNode.php', + 'Twig\\Node\\BodyNode' => $vendorDir . '/twig/twig/src/Node/BodyNode.php', + 'Twig\\Node\\CheckSecurityNode' => $vendorDir . '/twig/twig/src/Node/CheckSecurityNode.php', + 'Twig\\Node\\CheckToStringNode' => $vendorDir . '/twig/twig/src/Node/CheckToStringNode.php', + 'Twig\\Node\\DeprecatedNode' => $vendorDir . '/twig/twig/src/Node/DeprecatedNode.php', + 'Twig\\Node\\DoNode' => $vendorDir . '/twig/twig/src/Node/DoNode.php', + 'Twig\\Node\\EmbedNode' => $vendorDir . '/twig/twig/src/Node/EmbedNode.php', + 'Twig\\Node\\Expression\\AbstractExpression' => $vendorDir . '/twig/twig/src/Node/Expression/AbstractExpression.php', + 'Twig\\Node\\Expression\\ArrayExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ArrayExpression.php', + 'Twig\\Node\\Expression\\AssignNameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/AssignNameExpression.php', + 'Twig\\Node\\Expression\\Binary\\AbstractBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AbstractBinary.php', + 'Twig\\Node\\Expression\\Binary\\AddBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AddBinary.php', + 'Twig\\Node\\Expression\\Binary\\AndBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/AndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseAndBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseOrBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseXorBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php', + 'Twig\\Node\\Expression\\Binary\\ConcatBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/ConcatBinary.php', + 'Twig\\Node\\Expression\\Binary\\DivBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/DivBinary.php', + 'Twig\\Node\\Expression\\Binary\\EndsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\EqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/EqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\FloorDivBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/GreaterBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\InBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/InBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/LessBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\MatchesBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/MatchesBinary.php', + 'Twig\\Node\\Expression\\Binary\\ModBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/ModBinary.php', + 'Twig\\Node\\Expression\\Binary\\MulBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/MulBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotEqualBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotInBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/NotInBinary.php', + 'Twig\\Node\\Expression\\Binary\\OrBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/OrBinary.php', + 'Twig\\Node\\Expression\\Binary\\PowerBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php', + 'Twig\\Node\\Expression\\Binary\\RangeBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php', + 'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\SubBinary' => $vendorDir . '/twig/twig/src/Node/Expression/Binary/SubBinary.php', + 'Twig\\Node\\Expression\\BlockReferenceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php', + 'Twig\\Node\\Expression\\CallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/CallExpression.php', + 'Twig\\Node\\Expression\\ConditionalExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConditionalExpression.php', + 'Twig\\Node\\Expression\\ConstantExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ConstantExpression.php', + 'Twig\\Node\\Expression\\FilterExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FilterExpression.php', + 'Twig\\Node\\Expression\\Filter\\DefaultFilter' => $vendorDir . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php', + 'Twig\\Node\\Expression\\FunctionExpression' => $vendorDir . '/twig/twig/src/Node/Expression/FunctionExpression.php', + 'Twig\\Node\\Expression\\GetAttrExpression' => $vendorDir . '/twig/twig/src/Node/Expression/GetAttrExpression.php', + 'Twig\\Node\\Expression\\InlinePrint' => $vendorDir . '/twig/twig/src/Node/Expression/InlinePrint.php', + 'Twig\\Node\\Expression\\MethodCallExpression' => $vendorDir . '/twig/twig/src/Node/Expression/MethodCallExpression.php', + 'Twig\\Node\\Expression\\NameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/NameExpression.php', + 'Twig\\Node\\Expression\\NullCoalesceExpression' => $vendorDir . '/twig/twig/src/Node/Expression/NullCoalesceExpression.php', + 'Twig\\Node\\Expression\\ParentExpression' => $vendorDir . '/twig/twig/src/Node/Expression/ParentExpression.php', + 'Twig\\Node\\Expression\\TempNameExpression' => $vendorDir . '/twig/twig/src/Node/Expression/TempNameExpression.php', + 'Twig\\Node\\Expression\\TestExpression' => $vendorDir . '/twig/twig/src/Node/Expression/TestExpression.php', + 'Twig\\Node\\Expression\\Test\\ConstantTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/ConstantTest.php', + 'Twig\\Node\\Expression\\Test\\DefinedTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/DefinedTest.php', + 'Twig\\Node\\Expression\\Test\\DivisiblebyTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php', + 'Twig\\Node\\Expression\\Test\\EvenTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/EvenTest.php', + 'Twig\\Node\\Expression\\Test\\NullTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/NullTest.php', + 'Twig\\Node\\Expression\\Test\\OddTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/OddTest.php', + 'Twig\\Node\\Expression\\Test\\SameasTest' => $vendorDir . '/twig/twig/src/Node/Expression/Test/SameasTest.php', + 'Twig\\Node\\Expression\\Unary\\AbstractUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/AbstractUnary.php', + 'Twig\\Node\\Expression\\Unary\\NegUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NegUnary.php', + 'Twig\\Node\\Expression\\Unary\\NotUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/NotUnary.php', + 'Twig\\Node\\Expression\\Unary\\PosUnary' => $vendorDir . '/twig/twig/src/Node/Expression/Unary/PosUnary.php', + 'Twig\\Node\\FlushNode' => $vendorDir . '/twig/twig/src/Node/FlushNode.php', + 'Twig\\Node\\ForLoopNode' => $vendorDir . '/twig/twig/src/Node/ForLoopNode.php', + 'Twig\\Node\\ForNode' => $vendorDir . '/twig/twig/src/Node/ForNode.php', + 'Twig\\Node\\IfNode' => $vendorDir . '/twig/twig/src/Node/IfNode.php', + 'Twig\\Node\\ImportNode' => $vendorDir . '/twig/twig/src/Node/ImportNode.php', + 'Twig\\Node\\IncludeNode' => $vendorDir . '/twig/twig/src/Node/IncludeNode.php', + 'Twig\\Node\\MacroNode' => $vendorDir . '/twig/twig/src/Node/MacroNode.php', + 'Twig\\Node\\ModuleNode' => $vendorDir . '/twig/twig/src/Node/ModuleNode.php', + 'Twig\\Node\\Node' => $vendorDir . '/twig/twig/src/Node/Node.php', + 'Twig\\Node\\NodeCaptureInterface' => $vendorDir . '/twig/twig/src/Node/NodeCaptureInterface.php', + 'Twig\\Node\\NodeOutputInterface' => $vendorDir . '/twig/twig/src/Node/NodeOutputInterface.php', + 'Twig\\Node\\PrintNode' => $vendorDir . '/twig/twig/src/Node/PrintNode.php', + 'Twig\\Node\\SandboxNode' => $vendorDir . '/twig/twig/src/Node/SandboxNode.php', + 'Twig\\Node\\SandboxedPrintNode' => $vendorDir . '/twig/twig/src/Node/SandboxedPrintNode.php', + 'Twig\\Node\\SetNode' => $vendorDir . '/twig/twig/src/Node/SetNode.php', + 'Twig\\Node\\SetTempNode' => $vendorDir . '/twig/twig/src/Node/SetTempNode.php', + 'Twig\\Node\\SpacelessNode' => $vendorDir . '/twig/twig/src/Node/SpacelessNode.php', + 'Twig\\Node\\TextNode' => $vendorDir . '/twig/twig/src/Node/TextNode.php', + 'Twig\\Node\\WithNode' => $vendorDir . '/twig/twig/src/Node/WithNode.php', + 'Twig\\Parser' => $vendorDir . '/twig/twig/src/Parser.php', + 'Twig\\Profiler\\Dumper\\BaseDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/BaseDumper.php', + 'Twig\\Profiler\\Dumper\\BlackfireDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/BlackfireDumper.php', + 'Twig\\Profiler\\Dumper\\HtmlDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/HtmlDumper.php', + 'Twig\\Profiler\\Dumper\\TextDumper' => $vendorDir . '/twig/twig/src/Profiler/Dumper/TextDumper.php', + 'Twig\\Profiler\\NodeVisitor\\ProfilerNodeVisitor' => $vendorDir . '/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php', + 'Twig\\Profiler\\Node\\EnterProfileNode' => $vendorDir . '/twig/twig/src/Profiler/Node/EnterProfileNode.php', + 'Twig\\Profiler\\Node\\LeaveProfileNode' => $vendorDir . '/twig/twig/src/Profiler/Node/LeaveProfileNode.php', + 'Twig\\Profiler\\Profile' => $vendorDir . '/twig/twig/src/Profiler/Profile.php', + 'Twig\\RuntimeLoader\\ContainerRuntimeLoader' => $vendorDir . '/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php', + 'Twig\\RuntimeLoader\\FactoryRuntimeLoader' => $vendorDir . '/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php', + 'Twig\\RuntimeLoader\\RuntimeLoaderInterface' => $vendorDir . '/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php', + 'Twig\\Sandbox\\SecurityError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFilterError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFunctionError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig\\Sandbox\\SecurityNotAllowedMethodError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig\\Sandbox\\SecurityNotAllowedPropertyError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig\\Sandbox\\SecurityNotAllowedTagError' => $vendorDir . '/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php', + 'Twig\\Sandbox\\SecurityPolicy' => $vendorDir . '/twig/twig/src/Sandbox/SecurityPolicy.php', + 'Twig\\Sandbox\\SecurityPolicyInterface' => $vendorDir . '/twig/twig/src/Sandbox/SecurityPolicyInterface.php', + 'Twig\\Source' => $vendorDir . '/twig/twig/src/Source.php', + 'Twig\\Template' => $vendorDir . '/twig/twig/src/Template.php', + 'Twig\\TemplateWrapper' => $vendorDir . '/twig/twig/src/TemplateWrapper.php', + 'Twig\\Test\\IntegrationTestCase' => $vendorDir . '/twig/twig/src/Test/IntegrationTestCase.php', + 'Twig\\Test\\NodeTestCase' => $vendorDir . '/twig/twig/src/Test/NodeTestCase.php', + 'Twig\\Token' => $vendorDir . '/twig/twig/src/Token.php', + 'Twig\\TokenParser\\AbstractTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AbstractTokenParser.php', + 'Twig\\TokenParser\\AutoEscapeTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/AutoEscapeTokenParser.php', + 'Twig\\TokenParser\\BlockTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/BlockTokenParser.php', + 'Twig\\TokenParser\\DeprecatedTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/DeprecatedTokenParser.php', + 'Twig\\TokenParser\\DoTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/DoTokenParser.php', + 'Twig\\TokenParser\\EmbedTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/EmbedTokenParser.php', + 'Twig\\TokenParser\\ExtendsTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ExtendsTokenParser.php', + 'Twig\\TokenParser\\FilterTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FilterTokenParser.php', + 'Twig\\TokenParser\\FlushTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FlushTokenParser.php', + 'Twig\\TokenParser\\ForTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ForTokenParser.php', + 'Twig\\TokenParser\\FromTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/FromTokenParser.php', + 'Twig\\TokenParser\\IfTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/IfTokenParser.php', + 'Twig\\TokenParser\\ImportTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/ImportTokenParser.php', + 'Twig\\TokenParser\\IncludeTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/IncludeTokenParser.php', + 'Twig\\TokenParser\\MacroTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/MacroTokenParser.php', + 'Twig\\TokenParser\\SandboxTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SandboxTokenParser.php', + 'Twig\\TokenParser\\SetTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SetTokenParser.php', + 'Twig\\TokenParser\\SpacelessTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/SpacelessTokenParser.php', + 'Twig\\TokenParser\\TokenParserInterface' => $vendorDir . '/twig/twig/src/TokenParser/TokenParserInterface.php', + 'Twig\\TokenParser\\UseTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/UseTokenParser.php', + 'Twig\\TokenParser\\WithTokenParser' => $vendorDir . '/twig/twig/src/TokenParser/WithTokenParser.php', + 'Twig\\TokenStream' => $vendorDir . '/twig/twig/src/TokenStream.php', + 'Twig\\TwigFilter' => $vendorDir . '/twig/twig/src/TwigFilter.php', + 'Twig\\TwigFunction' => $vendorDir . '/twig/twig/src/TwigFunction.php', + 'Twig\\TwigTest' => $vendorDir . '/twig/twig/src/TwigTest.php', + 'Twig\\Util\\DeprecationCollector' => $vendorDir . '/twig/twig/src/Util/DeprecationCollector.php', + 'Twig\\Util\\TemplateDirIterator' => $vendorDir . '/twig/twig/src/Util/TemplateDirIterator.php', + 'Twig_Autoloader' => $vendorDir . '/twig/twig/lib/Twig/Autoloader.php', + 'Twig_BaseNodeVisitor' => $vendorDir . '/twig/twig/lib/Twig/BaseNodeVisitor.php', + 'Twig_CacheInterface' => $vendorDir . '/twig/twig/lib/Twig/CacheInterface.php', + 'Twig_Cache_Filesystem' => $vendorDir . '/twig/twig/lib/Twig/Cache/Filesystem.php', + 'Twig_Cache_Null' => $vendorDir . '/twig/twig/lib/Twig/Cache/Null.php', + 'Twig_Compiler' => $vendorDir . '/twig/twig/lib/Twig/Compiler.php', + 'Twig_CompilerInterface' => $vendorDir . '/twig/twig/lib/Twig/CompilerInterface.php', + 'Twig_ContainerRuntimeLoader' => $vendorDir . '/twig/twig/lib/Twig/ContainerRuntimeLoader.php', + 'Twig_Environment' => $vendorDir . '/twig/twig/lib/Twig/Environment.php', + 'Twig_Error' => $vendorDir . '/twig/twig/lib/Twig/Error.php', + 'Twig_Error_Loader' => $vendorDir . '/twig/twig/lib/Twig/Error/Loader.php', + 'Twig_Error_Runtime' => $vendorDir . '/twig/twig/lib/Twig/Error/Runtime.php', + 'Twig_Error_Syntax' => $vendorDir . '/twig/twig/lib/Twig/Error/Syntax.php', + 'Twig_ExistsLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/ExistsLoaderInterface.php', + 'Twig_ExpressionParser' => $vendorDir . '/twig/twig/lib/Twig/ExpressionParser.php', + 'Twig_Extension' => $vendorDir . '/twig/twig/lib/Twig/Extension.php', + 'Twig_ExtensionInterface' => $vendorDir . '/twig/twig/lib/Twig/ExtensionInterface.php', + 'Twig_Extension_Core' => $vendorDir . '/twig/twig/lib/Twig/Extension/Core.php', + 'Twig_Extension_Debug' => $vendorDir . '/twig/twig/lib/Twig/Extension/Debug.php', + 'Twig_Extension_Escaper' => $vendorDir . '/twig/twig/lib/Twig/Extension/Escaper.php', + 'Twig_Extension_GlobalsInterface' => $vendorDir . '/twig/twig/lib/Twig/Extension/GlobalsInterface.php', + 'Twig_Extension_InitRuntimeInterface' => $vendorDir . '/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php', + 'Twig_Extension_Optimizer' => $vendorDir . '/twig/twig/lib/Twig/Extension/Optimizer.php', + 'Twig_Extension_Profiler' => $vendorDir . '/twig/twig/lib/Twig/Extension/Profiler.php', + 'Twig_Extension_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/Extension/Sandbox.php', + 'Twig_Extension_Staging' => $vendorDir . '/twig/twig/lib/Twig/Extension/Staging.php', + 'Twig_Extension_StringLoader' => $vendorDir . '/twig/twig/lib/Twig/Extension/StringLoader.php', + 'Twig_FactoryRuntimeLoader' => $vendorDir . '/twig/twig/lib/Twig/FactoryRuntimeLoader.php', + 'Twig_FileExtensionEscapingStrategy' => $vendorDir . '/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php', + 'Twig_Filter' => $vendorDir . '/twig/twig/lib/Twig/Filter.php', + 'Twig_FilterCallableInterface' => $vendorDir . '/twig/twig/lib/Twig/FilterCallableInterface.php', + 'Twig_FilterInterface' => $vendorDir . '/twig/twig/lib/Twig/FilterInterface.php', + 'Twig_Filter_Function' => $vendorDir . '/twig/twig/lib/Twig/Filter/Function.php', + 'Twig_Filter_Method' => $vendorDir . '/twig/twig/lib/Twig/Filter/Method.php', + 'Twig_Filter_Node' => $vendorDir . '/twig/twig/lib/Twig/Filter/Node.php', + 'Twig_Function' => $vendorDir . '/twig/twig/lib/Twig/Function.php', + 'Twig_FunctionCallableInterface' => $vendorDir . '/twig/twig/lib/Twig/FunctionCallableInterface.php', + 'Twig_FunctionInterface' => $vendorDir . '/twig/twig/lib/Twig/FunctionInterface.php', + 'Twig_Function_Function' => $vendorDir . '/twig/twig/lib/Twig/Function/Function.php', + 'Twig_Function_Method' => $vendorDir . '/twig/twig/lib/Twig/Function/Method.php', + 'Twig_Function_Node' => $vendorDir . '/twig/twig/lib/Twig/Function/Node.php', + 'Twig_Lexer' => $vendorDir . '/twig/twig/lib/Twig/Lexer.php', + 'Twig_LexerInterface' => $vendorDir . '/twig/twig/lib/Twig/LexerInterface.php', + 'Twig_LoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/LoaderInterface.php', + 'Twig_Loader_Array' => $vendorDir . '/twig/twig/lib/Twig/Loader/Array.php', + 'Twig_Loader_Chain' => $vendorDir . '/twig/twig/lib/Twig/Loader/Chain.php', + 'Twig_Loader_Filesystem' => $vendorDir . '/twig/twig/lib/Twig/Loader/Filesystem.php', + 'Twig_Loader_String' => $vendorDir . '/twig/twig/lib/Twig/Loader/String.php', + 'Twig_Markup' => $vendorDir . '/twig/twig/lib/Twig/Markup.php', + 'Twig_Node' => $vendorDir . '/twig/twig/lib/Twig/Node.php', + 'Twig_NodeCaptureInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeCaptureInterface.php', + 'Twig_NodeInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeInterface.php', + 'Twig_NodeOutputInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeOutputInterface.php', + 'Twig_NodeTraverser' => $vendorDir . '/twig/twig/lib/Twig/NodeTraverser.php', + 'Twig_NodeVisitorInterface' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitorInterface.php', + 'Twig_NodeVisitor_Escaper' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Escaper.php', + 'Twig_NodeVisitor_Optimizer' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Optimizer.php', + 'Twig_NodeVisitor_SafeAnalysis' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php', + 'Twig_NodeVisitor_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/NodeVisitor/Sandbox.php', + 'Twig_Node_AutoEscape' => $vendorDir . '/twig/twig/lib/Twig/Node/AutoEscape.php', + 'Twig_Node_Block' => $vendorDir . '/twig/twig/lib/Twig/Node/Block.php', + 'Twig_Node_BlockReference' => $vendorDir . '/twig/twig/lib/Twig/Node/BlockReference.php', + 'Twig_Node_Body' => $vendorDir . '/twig/twig/lib/Twig/Node/Body.php', + 'Twig_Node_CheckSecurity' => $vendorDir . '/twig/twig/lib/Twig/Node/CheckSecurity.php', + 'Twig_Node_Deprecated' => $vendorDir . '/twig/twig/lib/Twig/Node/Deprecated.php', + 'Twig_Node_Do' => $vendorDir . '/twig/twig/lib/Twig/Node/Do.php', + 'Twig_Node_Embed' => $vendorDir . '/twig/twig/lib/Twig/Node/Embed.php', + 'Twig_Node_Expression' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression.php', + 'Twig_Node_Expression_Array' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Array.php', + 'Twig_Node_Expression_AssignName' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/AssignName.php', + 'Twig_Node_Expression_Binary' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary.php', + 'Twig_Node_Expression_Binary_Add' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Add.php', + 'Twig_Node_Expression_Binary_And' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/And.php', + 'Twig_Node_Expression_Binary_BitwiseAnd' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php', + 'Twig_Node_Expression_Binary_BitwiseOr' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php', + 'Twig_Node_Expression_Binary_BitwiseXor' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php', + 'Twig_Node_Expression_Binary_Concat' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php', + 'Twig_Node_Expression_Binary_Div' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Div.php', + 'Twig_Node_Expression_Binary_EndsWith' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php', + 'Twig_Node_Expression_Binary_Equal' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php', + 'Twig_Node_Expression_Binary_FloorDiv' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php', + 'Twig_Node_Expression_Binary_Greater' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php', + 'Twig_Node_Expression_Binary_GreaterEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php', + 'Twig_Node_Expression_Binary_In' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/In.php', + 'Twig_Node_Expression_Binary_Less' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Less.php', + 'Twig_Node_Expression_Binary_LessEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php', + 'Twig_Node_Expression_Binary_Matches' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php', + 'Twig_Node_Expression_Binary_Mod' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php', + 'Twig_Node_Expression_Binary_Mul' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php', + 'Twig_Node_Expression_Binary_NotEqual' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php', + 'Twig_Node_Expression_Binary_NotIn' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php', + 'Twig_Node_Expression_Binary_Or' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Or.php', + 'Twig_Node_Expression_Binary_Power' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Power.php', + 'Twig_Node_Expression_Binary_Range' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Range.php', + 'Twig_Node_Expression_Binary_StartsWith' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php', + 'Twig_Node_Expression_Binary_Sub' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php', + 'Twig_Node_Expression_BlockReference' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/BlockReference.php', + 'Twig_Node_Expression_Call' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Call.php', + 'Twig_Node_Expression_Conditional' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Conditional.php', + 'Twig_Node_Expression_Constant' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Constant.php', + 'Twig_Node_Expression_ExtensionReference' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php', + 'Twig_Node_Expression_Filter' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Filter.php', + 'Twig_Node_Expression_Filter_Default' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Filter/Default.php', + 'Twig_Node_Expression_Function' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Function.php', + 'Twig_Node_Expression_GetAttr' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/GetAttr.php', + 'Twig_Node_Expression_MethodCall' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/MethodCall.php', + 'Twig_Node_Expression_Name' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Name.php', + 'Twig_Node_Expression_NullCoalesce' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php', + 'Twig_Node_Expression_Parent' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Parent.php', + 'Twig_Node_Expression_TempName' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/TempName.php', + 'Twig_Node_Expression_Test' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test.php', + 'Twig_Node_Expression_Test_Constant' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Constant.php', + 'Twig_Node_Expression_Test_Defined' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Defined.php', + 'Twig_Node_Expression_Test_Divisibleby' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php', + 'Twig_Node_Expression_Test_Even' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Even.php', + 'Twig_Node_Expression_Test_Null' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Null.php', + 'Twig_Node_Expression_Test_Odd' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Odd.php', + 'Twig_Node_Expression_Test_Sameas' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php', + 'Twig_Node_Expression_Unary' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary.php', + 'Twig_Node_Expression_Unary_Neg' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php', + 'Twig_Node_Expression_Unary_Not' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Not.php', + 'Twig_Node_Expression_Unary_Pos' => $vendorDir . '/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php', + 'Twig_Node_Flush' => $vendorDir . '/twig/twig/lib/Twig/Node/Flush.php', + 'Twig_Node_For' => $vendorDir . '/twig/twig/lib/Twig/Node/For.php', + 'Twig_Node_ForLoop' => $vendorDir . '/twig/twig/lib/Twig/Node/ForLoop.php', + 'Twig_Node_If' => $vendorDir . '/twig/twig/lib/Twig/Node/If.php', + 'Twig_Node_Import' => $vendorDir . '/twig/twig/lib/Twig/Node/Import.php', + 'Twig_Node_Include' => $vendorDir . '/twig/twig/lib/Twig/Node/Include.php', + 'Twig_Node_Macro' => $vendorDir . '/twig/twig/lib/Twig/Node/Macro.php', + 'Twig_Node_Module' => $vendorDir . '/twig/twig/lib/Twig/Node/Module.php', + 'Twig_Node_Print' => $vendorDir . '/twig/twig/lib/Twig/Node/Print.php', + 'Twig_Node_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/Node/Sandbox.php', + 'Twig_Node_SandboxedPrint' => $vendorDir . '/twig/twig/lib/Twig/Node/SandboxedPrint.php', + 'Twig_Node_Set' => $vendorDir . '/twig/twig/lib/Twig/Node/Set.php', + 'Twig_Node_SetTemp' => $vendorDir . '/twig/twig/lib/Twig/Node/SetTemp.php', + 'Twig_Node_Spaceless' => $vendorDir . '/twig/twig/lib/Twig/Node/Spaceless.php', + 'Twig_Node_Text' => $vendorDir . '/twig/twig/lib/Twig/Node/Text.php', + 'Twig_Node_With' => $vendorDir . '/twig/twig/lib/Twig/Node/With.php', + 'Twig_Parser' => $vendorDir . '/twig/twig/lib/Twig/Parser.php', + 'Twig_ParserInterface' => $vendorDir . '/twig/twig/lib/Twig/ParserInterface.php', + 'Twig_Profiler_Dumper_Base' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Base.php', + 'Twig_Profiler_Dumper_Blackfire' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php', + 'Twig_Profiler_Dumper_Html' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Html.php', + 'Twig_Profiler_Dumper_Text' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Dumper/Text.php', + 'Twig_Profiler_NodeVisitor_Profiler' => $vendorDir . '/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php', + 'Twig_Profiler_Node_EnterProfile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php', + 'Twig_Profiler_Node_LeaveProfile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php', + 'Twig_Profiler_Profile' => $vendorDir . '/twig/twig/lib/Twig/Profiler/Profile.php', + 'Twig_RuntimeLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/RuntimeLoaderInterface.php', + 'Twig_Sandbox_SecurityError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityError.php', + 'Twig_Sandbox_SecurityNotAllowedFilterError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig_Sandbox_SecurityNotAllowedFunctionError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig_Sandbox_SecurityNotAllowedMethodError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig_Sandbox_SecurityNotAllowedPropertyError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig_Sandbox_SecurityNotAllowedTagError' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php', + 'Twig_Sandbox_SecurityPolicy' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php', + 'Twig_Sandbox_SecurityPolicyInterface' => $vendorDir . '/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php', + 'Twig_SimpleFilter' => $vendorDir . '/twig/twig/lib/Twig/SimpleFilter.php', + 'Twig_SimpleFunction' => $vendorDir . '/twig/twig/lib/Twig/SimpleFunction.php', + 'Twig_SimpleTest' => $vendorDir . '/twig/twig/lib/Twig/SimpleTest.php', + 'Twig_Source' => $vendorDir . '/twig/twig/lib/Twig/Source.php', + 'Twig_SourceContextLoaderInterface' => $vendorDir . '/twig/twig/lib/Twig/SourceContextLoaderInterface.php', + 'Twig_Template' => $vendorDir . '/twig/twig/lib/Twig/Template.php', + 'Twig_TemplateInterface' => $vendorDir . '/twig/twig/lib/Twig/TemplateInterface.php', + 'Twig_TemplateWrapper' => $vendorDir . '/twig/twig/lib/Twig/TemplateWrapper.php', + 'Twig_Test' => $vendorDir . '/twig/twig/lib/Twig/Test.php', + 'Twig_TestCallableInterface' => $vendorDir . '/twig/twig/lib/Twig/TestCallableInterface.php', + 'Twig_TestInterface' => $vendorDir . '/twig/twig/lib/Twig/TestInterface.php', + 'Twig_Test_Function' => $vendorDir . '/twig/twig/lib/Twig/Test/Function.php', + 'Twig_Test_IntegrationTestCase' => $vendorDir . '/twig/twig/lib/Twig/Test/IntegrationTestCase.php', + 'Twig_Test_Method' => $vendorDir . '/twig/twig/lib/Twig/Test/Method.php', + 'Twig_Test_Node' => $vendorDir . '/twig/twig/lib/Twig/Test/Node.php', + 'Twig_Test_NodeTestCase' => $vendorDir . '/twig/twig/lib/Twig/Test/NodeTestCase.php', + 'Twig_Token' => $vendorDir . '/twig/twig/lib/Twig/Token.php', + 'Twig_TokenParser' => $vendorDir . '/twig/twig/lib/Twig/TokenParser.php', + 'Twig_TokenParserBroker' => $vendorDir . '/twig/twig/lib/Twig/TokenParserBroker.php', + 'Twig_TokenParserBrokerInterface' => $vendorDir . '/twig/twig/lib/Twig/TokenParserBrokerInterface.php', + 'Twig_TokenParserInterface' => $vendorDir . '/twig/twig/lib/Twig/TokenParserInterface.php', + 'Twig_TokenParser_AutoEscape' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/AutoEscape.php', + 'Twig_TokenParser_Block' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Block.php', + 'Twig_TokenParser_Deprecated' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Deprecated.php', + 'Twig_TokenParser_Do' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Do.php', + 'Twig_TokenParser_Embed' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Embed.php', + 'Twig_TokenParser_Extends' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Extends.php', + 'Twig_TokenParser_Filter' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Filter.php', + 'Twig_TokenParser_Flush' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Flush.php', + 'Twig_TokenParser_For' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/For.php', + 'Twig_TokenParser_From' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/From.php', + 'Twig_TokenParser_If' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/If.php', + 'Twig_TokenParser_Import' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Import.php', + 'Twig_TokenParser_Include' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Include.php', + 'Twig_TokenParser_Macro' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Macro.php', + 'Twig_TokenParser_Sandbox' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Sandbox.php', + 'Twig_TokenParser_Set' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Set.php', + 'Twig_TokenParser_Spaceless' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Spaceless.php', + 'Twig_TokenParser_Use' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/Use.php', + 'Twig_TokenParser_With' => $vendorDir . '/twig/twig/lib/Twig/TokenParser/With.php', + 'Twig_TokenStream' => $vendorDir . '/twig/twig/lib/Twig/TokenStream.php', + 'Twig_Util_DeprecationCollector' => $vendorDir . '/twig/twig/lib/Twig/Util/DeprecationCollector.php', + 'Twig_Util_TemplateDirIterator' => $vendorDir . '/twig/twig/lib/Twig/Util/TemplateDirIterator.php', + 'Zend\\Code\\Annotation\\AnnotationCollection' => $vendorDir . '/zendframework/zend-code/src/Annotation/AnnotationCollection.php', + 'Zend\\Code\\Annotation\\AnnotationInterface' => $vendorDir . '/zendframework/zend-code/src/Annotation/AnnotationInterface.php', + 'Zend\\Code\\Annotation\\AnnotationManager' => $vendorDir . '/zendframework/zend-code/src/Annotation/AnnotationManager.php', + 'Zend\\Code\\Annotation\\Parser\\DoctrineAnnotationParser' => $vendorDir . '/zendframework/zend-code/src/Annotation/Parser/DoctrineAnnotationParser.php', + 'Zend\\Code\\Annotation\\Parser\\GenericAnnotationParser' => $vendorDir . '/zendframework/zend-code/src/Annotation/Parser/GenericAnnotationParser.php', + 'Zend\\Code\\Annotation\\Parser\\ParserInterface' => $vendorDir . '/zendframework/zend-code/src/Annotation/Parser/ParserInterface.php', + 'Zend\\Code\\Exception\\BadMethodCallException' => $vendorDir . '/zendframework/zend-code/src/Exception/BadMethodCallException.php', + 'Zend\\Code\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-code/src/Exception/ExceptionInterface.php', + 'Zend\\Code\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-code/src/Exception/InvalidArgumentException.php', + 'Zend\\Code\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-code/src/Exception/RuntimeException.php', + 'Zend\\Code\\Generator\\AbstractGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/AbstractGenerator.php', + 'Zend\\Code\\Generator\\AbstractMemberGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/AbstractMemberGenerator.php', + 'Zend\\Code\\Generator\\BodyGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/BodyGenerator.php', + 'Zend\\Code\\Generator\\ClassGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/ClassGenerator.php', + 'Zend\\Code\\Generator\\DocBlockGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlockGenerator.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag.php', + 'Zend\\Code\\Generator\\DocBlock\\TagManager' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/TagManager.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\AbstractTypeableTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/AbstractTypeableTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\AuthorTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/AuthorTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\GenericTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/GenericTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\LicenseTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/LicenseTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\MethodTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/MethodTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\ParamTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/ParamTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\PropertyTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/PropertyTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\ReturnTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/ReturnTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\TagInterface' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/TagInterface.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\ThrowsTag' => $vendorDir . '/zendframework/zend-code/src/Generator/DocBlock/Tag/ThrowsTag.php', + 'Zend\\Code\\Generator\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-code/src/Generator/Exception/ExceptionInterface.php', + 'Zend\\Code\\Generator\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-code/src/Generator/Exception/InvalidArgumentException.php', + 'Zend\\Code\\Generator\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-code/src/Generator/Exception/RuntimeException.php', + 'Zend\\Code\\Generator\\FileGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/FileGenerator.php', + 'Zend\\Code\\Generator\\FileGeneratorRegistry' => $vendorDir . '/zendframework/zend-code/src/Generator/FileGeneratorRegistry.php', + 'Zend\\Code\\Generator\\GeneratorInterface' => $vendorDir . '/zendframework/zend-code/src/Generator/GeneratorInterface.php', + 'Zend\\Code\\Generator\\MethodGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/MethodGenerator.php', + 'Zend\\Code\\Generator\\ParameterGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/ParameterGenerator.php', + 'Zend\\Code\\Generator\\PropertyGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/PropertyGenerator.php', + 'Zend\\Code\\Generator\\PropertyValueGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/PropertyValueGenerator.php', + 'Zend\\Code\\Generator\\TraitGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/TraitGenerator.php', + 'Zend\\Code\\Generator\\TraitUsageGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/TraitUsageGenerator.php', + 'Zend\\Code\\Generator\\TraitUsageInterface' => $vendorDir . '/zendframework/zend-code/src/Generator/TraitUsageInterface.php', + 'Zend\\Code\\Generator\\ValueGenerator' => $vendorDir . '/zendframework/zend-code/src/Generator/ValueGenerator.php', + 'Zend\\Code\\Generic\\Prototype\\PrototypeClassFactory' => $vendorDir . '/zendframework/zend-code/src/Generic/Prototype/PrototypeClassFactory.php', + 'Zend\\Code\\Generic\\Prototype\\PrototypeGenericInterface' => $vendorDir . '/zendframework/zend-code/src/Generic/Prototype/PrototypeGenericInterface.php', + 'Zend\\Code\\Generic\\Prototype\\PrototypeInterface' => $vendorDir . '/zendframework/zend-code/src/Generic/Prototype/PrototypeInterface.php', + 'Zend\\Code\\NameInformation' => $vendorDir . '/zendframework/zend-code/src/NameInformation.php', + 'Zend\\Code\\Reflection\\ClassReflection' => $vendorDir . '/zendframework/zend-code/src/Reflection/ClassReflection.php', + 'Zend\\Code\\Reflection\\DocBlockReflection' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlockReflection.php', + 'Zend\\Code\\Reflection\\DocBlock\\TagManager' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/TagManager.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\AuthorTag' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/AuthorTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\GenericTag' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/GenericTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\LicenseTag' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/LicenseTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\MethodTag' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/MethodTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\ParamTag' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/ParamTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\PhpDocTypedTagInterface' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\PropertyTag' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/PropertyTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\ReturnTag' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/ReturnTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\TagInterface' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/TagInterface.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\ThrowsTag' => $vendorDir . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/ThrowsTag.php', + 'Zend\\Code\\Reflection\\Exception\\BadMethodCallException' => $vendorDir . '/zendframework/zend-code/src/Reflection/Exception/BadMethodCallException.php', + 'Zend\\Code\\Reflection\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-code/src/Reflection/Exception/ExceptionInterface.php', + 'Zend\\Code\\Reflection\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-code/src/Reflection/Exception/InvalidArgumentException.php', + 'Zend\\Code\\Reflection\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-code/src/Reflection/Exception/RuntimeException.php', + 'Zend\\Code\\Reflection\\FileReflection' => $vendorDir . '/zendframework/zend-code/src/Reflection/FileReflection.php', + 'Zend\\Code\\Reflection\\FunctionReflection' => $vendorDir . '/zendframework/zend-code/src/Reflection/FunctionReflection.php', + 'Zend\\Code\\Reflection\\MethodReflection' => $vendorDir . '/zendframework/zend-code/src/Reflection/MethodReflection.php', + 'Zend\\Code\\Reflection\\ParameterReflection' => $vendorDir . '/zendframework/zend-code/src/Reflection/ParameterReflection.php', + 'Zend\\Code\\Reflection\\PropertyReflection' => $vendorDir . '/zendframework/zend-code/src/Reflection/PropertyReflection.php', + 'Zend\\Code\\Reflection\\ReflectionInterface' => $vendorDir . '/zendframework/zend-code/src/Reflection/ReflectionInterface.php', + 'Zend\\Code\\Scanner\\AggregateDirectoryScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/AggregateDirectoryScanner.php', + 'Zend\\Code\\Scanner\\AnnotationScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/AnnotationScanner.php', + 'Zend\\Code\\Scanner\\CachingFileScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/CachingFileScanner.php', + 'Zend\\Code\\Scanner\\ClassScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/ClassScanner.php', + 'Zend\\Code\\Scanner\\ConstantScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/ConstantScanner.php', + 'Zend\\Code\\Scanner\\DerivedClassScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/DerivedClassScanner.php', + 'Zend\\Code\\Scanner\\DirectoryScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/DirectoryScanner.php', + 'Zend\\Code\\Scanner\\DocBlockScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/DocBlockScanner.php', + 'Zend\\Code\\Scanner\\FileScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/FileScanner.php', + 'Zend\\Code\\Scanner\\FunctionScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/FunctionScanner.php', + 'Zend\\Code\\Scanner\\MethodScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/MethodScanner.php', + 'Zend\\Code\\Scanner\\ParameterScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/ParameterScanner.php', + 'Zend\\Code\\Scanner\\PropertyScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/PropertyScanner.php', + 'Zend\\Code\\Scanner\\ScannerInterface' => $vendorDir . '/zendframework/zend-code/src/Scanner/ScannerInterface.php', + 'Zend\\Code\\Scanner\\TokenArrayScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/TokenArrayScanner.php', + 'Zend\\Code\\Scanner\\Util' => $vendorDir . '/zendframework/zend-code/src/Scanner/Util.php', + 'Zend\\Code\\Scanner\\ValueScanner' => $vendorDir . '/zendframework/zend-code/src/Scanner/ValueScanner.php', + 'Zend\\EventManager\\AbstractListenerAggregate' => $vendorDir . '/zendframework/zend-eventmanager/src/AbstractListenerAggregate.php', + 'Zend\\EventManager\\Event' => $vendorDir . '/zendframework/zend-eventmanager/src/Event.php', + 'Zend\\EventManager\\EventInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/EventInterface.php', + 'Zend\\EventManager\\EventManager' => $vendorDir . '/zendframework/zend-eventmanager/src/EventManager.php', + 'Zend\\EventManager\\EventManagerAwareInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/EventManagerAwareInterface.php', + 'Zend\\EventManager\\EventManagerAwareTrait' => $vendorDir . '/zendframework/zend-eventmanager/src/EventManagerAwareTrait.php', + 'Zend\\EventManager\\EventManagerInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/EventManagerInterface.php', + 'Zend\\EventManager\\EventsCapableInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/EventsCapableInterface.php', + 'Zend\\EventManager\\Exception\\DomainException' => $vendorDir . '/zendframework/zend-eventmanager/src/Exception/DomainException.php', + 'Zend\\EventManager\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/Exception/ExceptionInterface.php', + 'Zend\\EventManager\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-eventmanager/src/Exception/InvalidArgumentException.php', + 'Zend\\EventManager\\Exception\\InvalidCallbackException' => $vendorDir . '/zendframework/zend-eventmanager/src/Exception/InvalidCallbackException.php', + 'Zend\\EventManager\\FilterChain' => $vendorDir . '/zendframework/zend-eventmanager/src/FilterChain.php', + 'Zend\\EventManager\\Filter\\FilterInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/Filter/FilterInterface.php', + 'Zend\\EventManager\\Filter\\FilterIterator' => $vendorDir . '/zendframework/zend-eventmanager/src/Filter/FilterIterator.php', + 'Zend\\EventManager\\GlobalEventManager' => $vendorDir . '/zendframework/zend-eventmanager/src/GlobalEventManager.php', + 'Zend\\EventManager\\ListenerAggregateInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/ListenerAggregateInterface.php', + 'Zend\\EventManager\\ListenerAggregateTrait' => $vendorDir . '/zendframework/zend-eventmanager/src/ListenerAggregateTrait.php', + 'Zend\\EventManager\\ProvidesEvents' => $vendorDir . '/zendframework/zend-eventmanager/src/ProvidesEvents.php', + 'Zend\\EventManager\\ResponseCollection' => $vendorDir . '/zendframework/zend-eventmanager/src/ResponseCollection.php', + 'Zend\\EventManager\\SharedEventAggregateAwareInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/SharedEventAggregateAwareInterface.php', + 'Zend\\EventManager\\SharedEventManager' => $vendorDir . '/zendframework/zend-eventmanager/src/SharedEventManager.php', + 'Zend\\EventManager\\SharedEventManagerAwareInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/SharedEventManagerAwareInterface.php', + 'Zend\\EventManager\\SharedEventManagerInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/SharedEventManagerInterface.php', + 'Zend\\EventManager\\SharedListenerAggregateInterface' => $vendorDir . '/zendframework/zend-eventmanager/src/SharedListenerAggregateInterface.php', + 'Zend\\EventManager\\StaticEventManager' => $vendorDir . '/zendframework/zend-eventmanager/src/StaticEventManager.php', + 'Zend\\Stdlib\\AbstractOptions' => $vendorDir . '/zendframework/zend-stdlib/src/AbstractOptions.php', + 'Zend\\Stdlib\\ArrayObject' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayObject.php', + 'Zend\\Stdlib\\ArraySerializableInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ArraySerializableInterface.php', + 'Zend\\Stdlib\\ArrayStack' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayStack.php', + 'Zend\\Stdlib\\ArrayUtils' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayUtils.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeRemoveKey' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayUtils/MergeRemoveKey.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeReplaceKey' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKey.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeReplaceKeyInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php', + 'Zend\\Stdlib\\CallbackHandler' => $vendorDir . '/zendframework/zend-stdlib/src/CallbackHandler.php', + 'Zend\\Stdlib\\DateTime' => $vendorDir . '/zendframework/zend-stdlib/src/DateTime.php', + 'Zend\\Stdlib\\DispatchableInterface' => $vendorDir . '/zendframework/zend-stdlib/src/DispatchableInterface.php', + 'Zend\\Stdlib\\ErrorHandler' => $vendorDir . '/zendframework/zend-stdlib/src/ErrorHandler.php', + 'Zend\\Stdlib\\Exception\\BadMethodCallException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/BadMethodCallException.php', + 'Zend\\Stdlib\\Exception\\DomainException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/DomainException.php', + 'Zend\\Stdlib\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/ExceptionInterface.php', + 'Zend\\Stdlib\\Exception\\ExtensionNotLoadedException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/ExtensionNotLoadedException.php', + 'Zend\\Stdlib\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/InvalidArgumentException.php', + 'Zend\\Stdlib\\Exception\\InvalidCallbackException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/InvalidCallbackException.php', + 'Zend\\Stdlib\\Exception\\LogicException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/LogicException.php', + 'Zend\\Stdlib\\Exception\\RuntimeException' => $vendorDir . '/zendframework/zend-stdlib/src/Exception/RuntimeException.php', + 'Zend\\Stdlib\\Extractor\\ExtractionInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Extractor/ExtractionInterface.php', + 'Zend\\Stdlib\\Glob' => $vendorDir . '/zendframework/zend-stdlib/src/Glob.php', + 'Zend\\Stdlib\\Guard\\AllGuardsTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/AllGuardsTrait.php', + 'Zend\\Stdlib\\Guard\\ArrayOrTraversableGuardTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php', + 'Zend\\Stdlib\\Guard\\EmptyGuardTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/EmptyGuardTrait.php', + 'Zend\\Stdlib\\Guard\\GuardUtils' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/GuardUtils.php', + 'Zend\\Stdlib\\Guard\\NullGuardTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Guard/NullGuardTrait.php', + 'Zend\\Stdlib\\Hydrator\\AbstractHydrator' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/AbstractHydrator.php', + 'Zend\\Stdlib\\Hydrator\\Aggregate\\AggregateHydrator' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Aggregate/AggregateHydrator.php', + 'Zend\\Stdlib\\Hydrator\\Aggregate\\ExtractEvent' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Aggregate/ExtractEvent.php', + 'Zend\\Stdlib\\Hydrator\\Aggregate\\HydrateEvent' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydrateEvent.php', + 'Zend\\Stdlib\\Hydrator\\Aggregate\\HydratorListener' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydratorListener.php', + 'Zend\\Stdlib\\Hydrator\\ArraySerializable' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/ArraySerializable.php', + 'Zend\\Stdlib\\Hydrator\\ClassMethods' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/ClassMethods.php', + 'Zend\\Stdlib\\Hydrator\\DelegatingHydrator' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/DelegatingHydrator.php', + 'Zend\\Stdlib\\Hydrator\\DelegatingHydratorFactory' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/DelegatingHydratorFactory.php', + 'Zend\\Stdlib\\Hydrator\\FilterEnabledInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/FilterEnabledInterface.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\FilterComposite' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/FilterComposite.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\FilterInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/FilterInterface.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\FilterProviderInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/FilterProviderInterface.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\GetFilter' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/GetFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\HasFilter' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/HasFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\IsFilter' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/IsFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\MethodMatchFilter' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/MethodMatchFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\NumberOfParameterFilter' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/NumberOfParameterFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\OptionalParametersFilter' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Filter/OptionalParametersFilter.php', + 'Zend\\Stdlib\\Hydrator\\HydrationInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/HydrationInterface.php', + 'Zend\\Stdlib\\Hydrator\\HydratorAwareInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/HydratorAwareInterface.php', + 'Zend\\Stdlib\\Hydrator\\HydratorAwareTrait' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/HydratorAwareTrait.php', + 'Zend\\Stdlib\\Hydrator\\HydratorInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/HydratorInterface.php', + 'Zend\\Stdlib\\Hydrator\\HydratorOptionsInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/HydratorOptionsInterface.php', + 'Zend\\Stdlib\\Hydrator\\HydratorPluginManager' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/HydratorPluginManager.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategyEnabledInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategyEnabledInterface.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\ArrayMapNamingStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/ArrayMapNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\CompositeNamingStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/CompositeNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\IdentityNamingStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/IdentityNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\MapNamingStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/MapNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\NamingStrategyInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/NamingStrategyInterface.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\UnderscoreNamingStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/UnderscoreNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\ObjectProperty' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/ObjectProperty.php', + 'Zend\\Stdlib\\Hydrator\\Reflection' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Reflection.php', + 'Zend\\Stdlib\\Hydrator\\StrategyEnabledInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/StrategyEnabledInterface.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\BooleanStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/BooleanStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\ClosureStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/ClosureStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\DateTimeFormatterStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/DateTimeFormatterStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\DefaultStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/DefaultStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\Exception\\ExceptionInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/Exception/ExceptionInterface.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\Exception\\InvalidArgumentException' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/Exception/InvalidArgumentException.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\ExplodeStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/ExplodeStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\SerializableStrategy' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/SerializableStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\StrategyChain' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyChain.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\StrategyInterface' => $vendorDir . '/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyInterface.php', + 'Zend\\Stdlib\\InitializableInterface' => $vendorDir . '/zendframework/zend-stdlib/src/InitializableInterface.php', + 'Zend\\Stdlib\\JsonSerializable' => $vendorDir . '/zendframework/zend-stdlib/src/JsonSerializable.php', + 'Zend\\Stdlib\\JsonSerializable\\PhpLegacyCompatibility' => $vendorDir . '/zendframework/zend-stdlib/src/JsonSerializable/PhpLegacyCompatibility.php', + 'Zend\\Stdlib\\Message' => $vendorDir . '/zendframework/zend-stdlib/src/Message.php', + 'Zend\\Stdlib\\MessageInterface' => $vendorDir . '/zendframework/zend-stdlib/src/MessageInterface.php', + 'Zend\\Stdlib\\ParameterObjectInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ParameterObjectInterface.php', + 'Zend\\Stdlib\\Parameters' => $vendorDir . '/zendframework/zend-stdlib/src/Parameters.php', + 'Zend\\Stdlib\\ParametersInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ParametersInterface.php', + 'Zend\\Stdlib\\PriorityList' => $vendorDir . '/zendframework/zend-stdlib/src/PriorityList.php', + 'Zend\\Stdlib\\PriorityQueue' => $vendorDir . '/zendframework/zend-stdlib/src/PriorityQueue.php', + 'Zend\\Stdlib\\Request' => $vendorDir . '/zendframework/zend-stdlib/src/Request.php', + 'Zend\\Stdlib\\RequestInterface' => $vendorDir . '/zendframework/zend-stdlib/src/RequestInterface.php', + 'Zend\\Stdlib\\Response' => $vendorDir . '/zendframework/zend-stdlib/src/Response.php', + 'Zend\\Stdlib\\ResponseInterface' => $vendorDir . '/zendframework/zend-stdlib/src/ResponseInterface.php', + 'Zend\\Stdlib\\SplPriorityQueue' => $vendorDir . '/zendframework/zend-stdlib/src/SplPriorityQueue.php', + 'Zend\\Stdlib\\SplQueue' => $vendorDir . '/zendframework/zend-stdlib/src/SplQueue.php', + 'Zend\\Stdlib\\SplStack' => $vendorDir . '/zendframework/zend-stdlib/src/SplStack.php', + 'Zend\\Stdlib\\StringUtils' => $vendorDir . '/zendframework/zend-stdlib/src/StringUtils.php', + 'Zend\\Stdlib\\StringWrapper\\AbstractStringWrapper' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/AbstractStringWrapper.php', + 'Zend\\Stdlib\\StringWrapper\\Iconv' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/Iconv.php', + 'Zend\\Stdlib\\StringWrapper\\Intl' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/Intl.php', + 'Zend\\Stdlib\\StringWrapper\\MbString' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/MbString.php', + 'Zend\\Stdlib\\StringWrapper\\Native' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/Native.php', + 'Zend\\Stdlib\\StringWrapper\\StringWrapperInterface' => $vendorDir . '/zendframework/zend-stdlib/src/StringWrapper/StringWrapperInterface.php', + 'bantu\\IniGetWrapper\\IniGetWrapper' => $vendorDir . '/bantu/ini-get-wrapper/src/IniGetWrapper.php', + 's9e\\TextFormatter\\Bundle' => $vendorDir . '/s9e/text-formatter/src/Bundle.php', + 's9e\\TextFormatter\\Bundles\\Fatdown' => $vendorDir . '/s9e/text-formatter/src/Bundles/Fatdown.php', + 's9e\\TextFormatter\\Bundles\\Fatdown\\Renderer' => $vendorDir . '/s9e/text-formatter/src/Bundles/Fatdown/Renderer.php', + 's9e\\TextFormatter\\Bundles\\Forum' => $vendorDir . '/s9e/text-formatter/src/Bundles/Forum.php', + 's9e\\TextFormatter\\Bundles\\Forum\\Renderer' => $vendorDir . '/s9e/text-formatter/src/Bundles/Forum/Renderer.php', + 's9e\\TextFormatter\\Bundles\\MediaPack' => $vendorDir . '/s9e/text-formatter/src/Bundles/MediaPack.php', + 's9e\\TextFormatter\\Bundles\\MediaPack\\Renderer' => $vendorDir . '/s9e/text-formatter/src/Bundles/MediaPack/Renderer.php', + 's9e\\TextFormatter\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Bundle' => $vendorDir . '/s9e/text-formatter/src/Configurator/Bundle.php', + 's9e\\TextFormatter\\Configurator\\BundleGenerator' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Bundles\\Fatdown' => $vendorDir . '/s9e/text-formatter/src/Configurator/Bundles/Fatdown.php', + 's9e\\TextFormatter\\Configurator\\Bundles\\Forum' => $vendorDir . '/s9e/text-formatter/src/Configurator/Bundles/Forum.php', + 's9e\\TextFormatter\\Configurator\\Bundles\\MediaPack' => $vendorDir . '/s9e/text-formatter/src/Configurator/Bundles/MediaPack.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributeCollection' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributeFilterChain' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributeFilterCollection' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributeList' => $vendorDir . '/s9e/text-formatter/src/Configurator/Collections/AttributeList.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributePreprocessorCollection' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\Collection' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\FilterChain' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\HostnameList' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\MinifierList' => $vendorDir . '/s9e/text-formatter/src/Configurator/Collections/MinifierList.php', + 's9e\\TextFormatter\\Configurator\\Collections\\NormalizedCollection' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\NormalizedList' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\PluginCollection' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\RulesGeneratorList' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\Ruleset' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\SchemeList' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TagCollection' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TagFilterChain' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TagList' => $vendorDir . '/s9e/text-formatter/src/Configurator/Collections/TagList.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TemplateCheckList' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TemplateNormalizationList' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TemplateParameterCollection' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\ConfigProvider' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Exceptions\\UnsafeTemplateException' => $vendorDir . '/s9e/text-formatter/src/Configurator/Exceptions/UnsafeTemplateException.php', + 's9e\\TextFormatter\\Configurator\\FilterableConfigValue' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\AVTHelper' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\CharacterClassBuilder' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\ConfigHelper' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\ContextSafeness' => $vendorDir . '/s9e/text-formatter/src/Configurator/Helpers/ContextSafeness.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\ElementInspector' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\NodeLocator' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\RegexpBuilder' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\RegexpParser' => $vendorDir . '/s9e/text-formatter/src/Configurator/Helpers/RegexpParser.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\RulesHelper' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateHelper' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateInspector' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateLoader' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateModifier' => $vendorDir . '/s9e/text-formatter/src/Configurator/Helpers/TemplateModifier.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser\\IRProcessor' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser\\Normalizer' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser\\Optimizer' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser\\Parser' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\XPathHelper' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\Attribute' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\AlnumFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/AlnumFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\ChoiceFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/ChoiceFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\ColorFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/ColorFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\EmailFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/EmailFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\FalseFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/FalseFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\FloatFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/FloatFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\FontfamilyFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/FontfamilyFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\HashmapFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/HashmapFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\IdentifierFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/IdentifierFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\IntFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/IntFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\IpFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/IpFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\IpportFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/IpportFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\Ipv4Filter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/Ipv4Filter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\Ipv6Filter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/Ipv6Filter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\MapFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/MapFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\NumberFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/NumberFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\RangeFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/RangeFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\RegexpFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/RegexpFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\SimpletextFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/SimpletextFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\TimestampFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/TimestampFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\UintFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/UintFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\UrlFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributePreprocessor' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/AttributePreprocessor.php', + 's9e\\TextFormatter\\Configurator\\Items\\Filter' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\ProgrammableCallback' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\Regexp' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\Tag' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\TagFilter' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\Template' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\TemplateDocument' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/TemplateDocument.php', + 's9e\\TextFormatter\\Configurator\\Items\\UnsafeTemplate' => $vendorDir . '/s9e/text-formatter/src/Configurator/Items/UnsafeTemplate.php', + 's9e\\TextFormatter\\Configurator\\JavaScript' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\CallbackGenerator' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/CallbackGenerator.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Code' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\ConfigOptimizer' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/ConfigOptimizer.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\ConfigValue' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/ConfigValue.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Dictionary' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/Dictionary.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Encoder' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/Encoder.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\FunctionProvider' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\HintGenerator' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/HintGenerator.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifier' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/Minifier.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\ClosureCompilerApplication' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/ClosureCompilerApplication.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\ClosureCompilerService' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/ClosureCompilerService.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\FirstAvailable' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/FirstAvailable.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\MatthiasMullieMinify' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/MatthiasMullieMinify.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\Noop' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/Noop.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\OnlineMinifier' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/OnlineMinifier.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\RegexpConvertor' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/RegexpConvertor.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\StylesheetCompressor' => $vendorDir . '/s9e/text-formatter/src/Configurator/JavaScript/StylesheetCompressor.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerator' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\AbstractOptimizer' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\BranchOutputOptimizer' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\ControlStructuresOptimizer' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\Optimizer' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\Quick' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\Serializer' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\SwitchStatement' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\XPathConvertor' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\Unformatted' => $vendorDir . '/s9e/text-formatter/src/Configurator/RendererGenerators/Unformatted.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\XSLT' => $vendorDir . '/s9e/text-formatter/src/Configurator/RendererGenerators/XSLT.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\XSLT\\Optimizer' => $vendorDir . '/s9e/text-formatter/src/Configurator/RendererGenerators/XSLT/Optimizer.php', + 's9e\\TextFormatter\\Configurator\\Rendering' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerator' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\AllowAll' => $vendorDir . '/s9e/text-formatter/src/Configurator/RulesGenerators/AllowAll.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\AutoCloseIfVoid' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\AutoReopenFormattingElements' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\BlockElementsCloseFormattingElements' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\BlockElementsFosterFormattingElements' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\DisableAutoLineBreaksIfNewLinesArePreserved' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\EnforceContentModels' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\EnforceOptionalEndTags' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\IgnoreTagsInCode' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\IgnoreTextIfDisallowed' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\IgnoreWhitespaceAroundBlockElements' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\Interfaces\\BooleanRulesGenerator' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\Interfaces\\TargetedRulesGenerator' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\ManageParagraphs' => $vendorDir . '/s9e/text-formatter/src/Configurator/RulesGenerators/ManageParagraphs.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\TrimFirstLineInCodeBlocks' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateCheck' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecker' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\AbstractDynamicContentCheck' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\AbstractFlashRestriction' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowAttributeSets' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowCopy' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowDisableOutputEscaping' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowDynamicAttributeNames' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowDynamicElementNames' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowElement' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateChecks/DisallowElement.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowElementNS' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowFlashFullScreen' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateChecks/DisallowFlashFullScreen.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowNodeByXPath' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateChecks/DisallowNodeByXPath.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowObjectParamsWithGeneratedName' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowPHPTags' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowUnsafeCopyOf' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowUnsafeDynamicCSS' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowUnsafeDynamicJS' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowUnsafeDynamicURL' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowXPathFunction' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\RestrictFlashNetworking' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateChecks/RestrictFlashNetworking.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\RestrictFlashScriptAccess' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\AbstractChooseOptimization' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\AbstractConstantFolding' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\AbstractNormalization' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\ConvertCurlyExpressionsInText' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/ConvertCurlyExpressionsInText.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\Custom' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/Custom.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\EnforceHTMLOmittedEndTags' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\FixUnescapedCurlyBracesInHtmlAttributes' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\FoldArithmeticConstants' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\FoldConstantXPathExpressions' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineAttributes' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineCDATA' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineElements' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineInferredValues' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineTextElements' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineXPathLiterals' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\MergeConsecutiveCopyOf' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/MergeConsecutiveCopyOf.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\MergeIdenticalConditionalBranches' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/MergeIdenticalConditionalBranches.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\MinifyInlineCSS' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\MinifyXPathExpressions' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\NormalizeAttributeNames' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\NormalizeElementNames' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\NormalizeUrls' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeChoose' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeChooseText' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeConditionalAttributes' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeConditionalValueOf' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeNestedConditionals' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/OptimizeNestedConditionals.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\PreserveSingleSpaces' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\RemoveComments' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\RemoveInterElementWhitespace' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\RemoveLivePreviewAttributes' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\SetRelNoreferrerOnTargetedLinks' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\SortAttributesByName' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/SortAttributesByName.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\TransposeComments' => $vendorDir . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/TransposeComments.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\UninlineAttributes' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizer' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Traits\\CollectionProxy' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Traits\\Configurable' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Traits\\TemplateSafeness' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\UrlConfig' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Validators\\AttributeName' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Validators\\TagName' => $vendorDir . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Validators\\TemplateParameterName' => $vendorDir . '/s9e/text-formatter/src/Configurator/Validators/TemplateParameterName.php', + 's9e\\TextFormatter\\Parser' => $vendorDir . '/s9e/text-formatter/src/Parser.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\EmailFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/EmailFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\FalseFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/FalseFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\HashmapFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/HashmapFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\MapFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/MapFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/NetworkFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/NumericFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\RegexpFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/RegexpFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\TimestampFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/TimestampFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\UrlFilter' => $vendorDir . '/s9e/text-formatter/src/Parser/AttributeFilters/UrlFilter.php', + 's9e\\TextFormatter\\Parser\\FilterProcessing' => $vendorDir . '/s9e/text-formatter/src/Parser/FilterProcessing.php', + 's9e\\TextFormatter\\Parser\\Logger' => $vendorDir . '/s9e/text-formatter/src/Parser/Logger.php', + 's9e\\TextFormatter\\Parser\\Tag' => $vendorDir . '/s9e/text-formatter/src/Parser/Tag.php', + 's9e\\TextFormatter\\Plugins\\Autoemail\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Autoemail/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Autoemail\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Autoemail/Parser.php', + 's9e\\TextFormatter\\Plugins\\Autoimage\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Autoimage/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Autoimage\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Autoimage/Parser.php', + 's9e\\TextFormatter\\Plugins\\Autolink\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Autolink/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Autolink\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Autolink/Parser.php', + 's9e\\TextFormatter\\Plugins\\Autovideo\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Autovideo/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Autovideo\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Autovideo/Parser.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\BBCode' => $vendorDir . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/BBCode.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\BBCodeCollection' => $vendorDir . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/BBCodeCollection.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\BBCodeMonkey' => $vendorDir . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/BBCodeMonkey.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\Repository' => $vendorDir . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/Repository.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\RepositoryCollection' => $vendorDir . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/RepositoryCollection.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/BBCodes/Parser.php', + 's9e\\TextFormatter\\Plugins\\Censor\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Censor/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Censor\\Helper' => $vendorDir . '/s9e/text-formatter/src/Plugins/Censor/Helper.php', + 's9e\\TextFormatter\\Plugins\\Censor\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Censor/Parser.php', + 's9e\\TextFormatter\\Plugins\\ConfiguratorBase' => $vendorDir . '/s9e/text-formatter/src/Plugins/ConfiguratorBase.php', + 's9e\\TextFormatter\\Plugins\\Emoji\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Emoji/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Emoji\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Emoji/Parser.php', + 's9e\\TextFormatter\\Plugins\\Emoticons\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Emoticons/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Emoticons\\Configurator\\EmoticonCollection' => $vendorDir . '/s9e/text-formatter/src/Plugins/Emoticons/Configurator/EmoticonCollection.php', + 's9e\\TextFormatter\\Plugins\\Emoticons\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Emoticons/Parser.php', + 's9e\\TextFormatter\\Plugins\\Escaper\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Escaper/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Escaper\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Escaper/Parser.php', + 's9e\\TextFormatter\\Plugins\\FancyPants\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/FancyPants/Configurator.php', + 's9e\\TextFormatter\\Plugins\\FancyPants\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/FancyPants/Parser.php', + 's9e\\TextFormatter\\Plugins\\HTMLComments\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/HTMLComments/Configurator.php', + 's9e\\TextFormatter\\Plugins\\HTMLComments\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/HTMLComments/Parser.php', + 's9e\\TextFormatter\\Plugins\\HTMLElements\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/HTMLElements/Configurator.php', + 's9e\\TextFormatter\\Plugins\\HTMLElements\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/HTMLElements/Parser.php', + 's9e\\TextFormatter\\Plugins\\HTMLEntities\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/HTMLEntities/Configurator.php', + 's9e\\TextFormatter\\Plugins\\HTMLEntities\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/HTMLEntities/Parser.php', + 's9e\\TextFormatter\\Plugins\\Keywords\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Keywords/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Keywords\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Keywords/Parser.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\LinkAttributesSetter' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/LinkAttributesSetter.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\ParsedText' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/ParsedText.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\AbstractPass' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/AbstractPass.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\AbstractScript' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/AbstractScript.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Blocks' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Blocks.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Emphasis' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Emphasis.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\ForcedLineBreaks' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/ForcedLineBreaks.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Images' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Images.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\InlineCode' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/InlineCode.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\LinkReferences' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/LinkReferences.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Links' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Links.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Strikethrough' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Strikethrough.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Subscript' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Subscript.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Superscript' => $vendorDir . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Superscript.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\Collections\\CachedDefinitionCollection' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/Collections/CachedDefinitionCollection.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\Collections\\SiteDefinitionCollection' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/Collections/SiteDefinitionCollection.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\Collections\\XmlFileDefinitionCollection' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/Collections/XmlFileDefinitionCollection.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateBuilder' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateBuilder.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateGenerator' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateGenerator.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateGenerators\\Choose' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateGenerators/Choose.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateGenerators\\Flash' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateGenerators/Flash.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateGenerators\\Iframe' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateGenerators/Iframe.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/MediaEmbed/Parser.php', + 's9e\\TextFormatter\\Plugins\\ParserBase' => $vendorDir . '/s9e/text-formatter/src/Plugins/ParserBase.php', + 's9e\\TextFormatter\\Plugins\\PipeTables\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/PipeTables/Configurator.php', + 's9e\\TextFormatter\\Plugins\\PipeTables\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/PipeTables/Parser.php', + 's9e\\TextFormatter\\Plugins\\Preg\\Configurator' => $vendorDir . '/s9e/text-formatter/src/Plugins/Preg/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Preg\\Parser' => $vendorDir . '/s9e/text-formatter/src/Plugins/Preg/Parser.php', + 's9e\\TextFormatter\\Renderer' => $vendorDir . '/s9e/text-formatter/src/Renderer.php', + 's9e\\TextFormatter\\Renderers\\PHP' => $vendorDir . '/s9e/text-formatter/src/Renderers/PHP.php', + 's9e\\TextFormatter\\Renderers\\Unformatted' => $vendorDir . '/s9e/text-formatter/src/Renderers/Unformatted.php', + 's9e\\TextFormatter\\Renderers\\XSLT' => $vendorDir . '/s9e/text-formatter/src/Renderers/XSLT.php', + 's9e\\TextFormatter\\Unparser' => $vendorDir . '/s9e/text-formatter/src/Unparser.php', + 's9e\\TextFormatter\\Utils' => $vendorDir . '/s9e/text-formatter/src/Utils.php', + 's9e\\TextFormatter\\Utils\\Http' => $vendorDir . '/s9e/text-formatter/src/Utils/Http.php', + 's9e\\TextFormatter\\Utils\\Http\\Client' => $vendorDir . '/s9e/text-formatter/src/Utils/Http/Client.php', + 's9e\\TextFormatter\\Utils\\Http\\Clients\\Cached' => $vendorDir . '/s9e/text-formatter/src/Utils/Http/Clients/Cached.php', + 's9e\\TextFormatter\\Utils\\Http\\Clients\\Curl' => $vendorDir . '/s9e/text-formatter/src/Utils/Http/Clients/Curl.php', + 's9e\\TextFormatter\\Utils\\Http\\Clients\\Native' => $vendorDir . '/s9e/text-formatter/src/Utils/Http/Clients/Native.php', + 's9e\\TextFormatter\\Utils\\XPath' => $vendorDir . '/s9e/text-formatter/src/Utils/XPath.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..febd533 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,16 @@ + $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + 'e40631d46120a9c38ea139981f8dab26' => $vendorDir . '/ircmaxell/password-compat/lib/password.php', + 'edc6464955a37aa4d5fbf39d40fb6ee7' => $vendorDir . '/symfony/polyfill-php55/bootstrap.php', + '3e2471375464aac821502deb0ac64275' => $vendorDir . '/symfony/polyfill-php54/bootstrap.php', + 'ad155f8f1cf0d418fe49e248db8c661b' => $vendorDir . '/react/promise/src/functions_include.php', + '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..b103a80 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,13 @@ + array($vendorDir . '/twig/twig/lib'), + 'ProxyManager\\' => array($vendorDir . '/ocramius/proxy-manager/src'), + 'OAuth\\Unit' => array($vendorDir . '/lusitanian/oauth/tests'), + 'OAuth' => array($vendorDir . '/lusitanian/oauth/src'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..1801ab0 --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,41 @@ + array($vendorDir . '/s9e/text-formatter/src'), + 'bantu\\IniGetWrapper\\' => array($vendorDir . '/bantu/ini-get-wrapper/src'), + 'Zend\\Stdlib\\' => array($vendorDir . '/zendframework/zend-stdlib/src'), + 'Zend\\EventManager\\' => array($vendorDir . '/zendframework/zend-eventmanager/src'), + 'Zend\\Code\\' => array($vendorDir . '/zendframework/zend-code/src'), + 'Twig\\' => array($vendorDir . '/twig/twig/src'), + 'Symfony\\Polyfill\\Php55\\' => array($vendorDir . '/symfony/polyfill-php55'), + 'Symfony\\Polyfill\\Php54\\' => array($vendorDir . '/symfony/polyfill-php54'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), + 'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'), + 'Symfony\\Component\\HttpKernel\\' => array($vendorDir . '/symfony/http-kernel'), + 'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'), + 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), + 'Symfony\\Component\\DependencyInjection\\' => array($vendorDir . '/symfony/dependency-injection'), + 'Symfony\\Component\\Debug\\' => array($vendorDir . '/symfony/debug'), + 'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'), + 'Symfony\\Component\\Config\\' => array($vendorDir . '/symfony/config'), + 'Symfony\\Bridge\\Twig\\' => array($vendorDir . '/symfony/twig-bridge'), + 'Symfony\\Bridge\\ProxyManager\\' => array($vendorDir . '/symfony/proxy-manager-bridge'), + 'React\\Promise\\' => array($vendorDir . '/react/promise/src'), + 'ReCaptcha\\' => array($vendorDir . '/google/recaptcha/src/ReCaptcha'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Patchwork\\' => array($vendorDir . '/patchwork/utf8/src/Patchwork'), + 'GuzzleHttp\\Stream\\' => array($vendorDir . '/guzzlehttp/streams/src'), + 'GuzzleHttp\\Ring\\' => array($vendorDir . '/guzzlehttp/ringphp/src'), + 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), + 'FastImageSize\\tests\\' => array($vendorDir . '/marc1706/fast-image-size/tests'), + 'FastImageSize\\' => array($vendorDir . '/marc1706/fast-image-size/lib'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..34a73aa --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,70 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit55438ae291a1dfe4cf962a21b03284ed::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit55438ae291a1dfe4cf962a21b03284ed::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire55438ae291a1dfe4cf962a21b03284ed($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire55438ae291a1dfe4cf962a21b03284ed($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..79e8b52 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,2120 @@ + __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + 'e40631d46120a9c38ea139981f8dab26' => __DIR__ . '/..' . '/ircmaxell/password-compat/lib/password.php', + 'edc6464955a37aa4d5fbf39d40fb6ee7' => __DIR__ . '/..' . '/symfony/polyfill-php55/bootstrap.php', + '3e2471375464aac821502deb0ac64275' => __DIR__ . '/..' . '/symfony/polyfill-php54/bootstrap.php', + 'ad155f8f1cf0d418fe49e248db8c661b' => __DIR__ . '/..' . '/react/promise/src/functions_include.php', + '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', + ); + + public static $prefixLengthsPsr4 = array ( + 's' => + array ( + 's9e\\TextFormatter\\' => 18, + ), + 'b' => + array ( + 'bantu\\IniGetWrapper\\' => 20, + ), + 'Z' => + array ( + 'Zend\\Stdlib\\' => 12, + 'Zend\\EventManager\\' => 18, + 'Zend\\Code\\' => 10, + ), + 'T' => + array ( + 'Twig\\' => 5, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php55\\' => 23, + 'Symfony\\Polyfill\\Php54\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + 'Symfony\\Component\\Yaml\\' => 23, + 'Symfony\\Component\\Routing\\' => 26, + 'Symfony\\Component\\HttpKernel\\' => 29, + 'Symfony\\Component\\HttpFoundation\\' => 33, + 'Symfony\\Component\\Finder\\' => 25, + 'Symfony\\Component\\Filesystem\\' => 29, + 'Symfony\\Component\\EventDispatcher\\' => 34, + 'Symfony\\Component\\DependencyInjection\\' => 38, + 'Symfony\\Component\\Debug\\' => 24, + 'Symfony\\Component\\Console\\' => 26, + 'Symfony\\Component\\Config\\' => 25, + 'Symfony\\Bridge\\Twig\\' => 20, + 'Symfony\\Bridge\\ProxyManager\\' => 28, + ), + 'R' => + array ( + 'React\\Promise\\' => 14, + 'ReCaptcha\\' => 10, + ), + 'P' => + array ( + 'Psr\\Log\\' => 8, + 'Patchwork\\' => 10, + ), + 'G' => + array ( + 'GuzzleHttp\\Stream\\' => 18, + 'GuzzleHttp\\Ring\\' => 16, + 'GuzzleHttp\\' => 11, + ), + 'F' => + array ( + 'FastImageSize\\tests\\' => 20, + 'FastImageSize\\' => 14, + ), + ); + + public static $prefixDirsPsr4 = array ( + 's9e\\TextFormatter\\' => + array ( + 0 => __DIR__ . '/..' . '/s9e/text-formatter/src', + ), + 'bantu\\IniGetWrapper\\' => + array ( + 0 => __DIR__ . '/..' . '/bantu/ini-get-wrapper/src', + ), + 'Zend\\Stdlib\\' => + array ( + 0 => __DIR__ . '/..' . '/zendframework/zend-stdlib/src', + ), + 'Zend\\EventManager\\' => + array ( + 0 => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src', + ), + 'Zend\\Code\\' => + array ( + 0 => __DIR__ . '/..' . '/zendframework/zend-code/src', + ), + 'Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/src', + ), + 'Symfony\\Polyfill\\Php55\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php55', + ), + 'Symfony\\Polyfill\\Php54\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php54', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Symfony\\Component\\Yaml\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/yaml', + ), + 'Symfony\\Component\\Routing\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/routing', + ), + 'Symfony\\Component\\HttpKernel\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-kernel', + ), + 'Symfony\\Component\\HttpFoundation\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/http-foundation', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Symfony\\Component\\Filesystem\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/filesystem', + ), + 'Symfony\\Component\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/event-dispatcher', + ), + 'Symfony\\Component\\DependencyInjection\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/dependency-injection', + ), + 'Symfony\\Component\\Debug\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/debug', + ), + 'Symfony\\Component\\Console\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/console', + ), + 'Symfony\\Component\\Config\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/config', + ), + 'Symfony\\Bridge\\Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/twig-bridge', + ), + 'Symfony\\Bridge\\ProxyManager\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/proxy-manager-bridge', + ), + 'React\\Promise\\' => + array ( + 0 => __DIR__ . '/..' . '/react/promise/src', + ), + 'ReCaptcha\\' => + array ( + 0 => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Patchwork\\' => + array ( + 0 => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork', + ), + 'GuzzleHttp\\Stream\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/streams/src', + ), + 'GuzzleHttp\\Ring\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/ringphp/src', + ), + 'GuzzleHttp\\' => + array ( + 0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src', + ), + 'FastImageSize\\tests\\' => + array ( + 0 => __DIR__ . '/..' . '/marc1706/fast-image-size/tests', + ), + 'FastImageSize\\' => + array ( + 0 => __DIR__ . '/..' . '/marc1706/fast-image-size/lib', + ), + ); + + public static $prefixesPsr0 = array ( + 'T' => + array ( + 'Twig_' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/lib', + ), + ), + 'P' => + array ( + 'ProxyManager\\' => + array ( + 0 => __DIR__ . '/..' . '/ocramius/proxy-manager/src', + ), + ), + 'O' => + array ( + 'OAuth\\Unit' => + array ( + 0 => __DIR__ . '/..' . '/lusitanian/oauth/tests', + ), + 'OAuth' => + array ( + 0 => __DIR__ . '/..' . '/lusitanian/oauth/src', + ), + ), + ); + + public static $classMap = array ( + 'CallbackFilterIterator' => __DIR__ . '/..' . '/symfony/polyfill-php54/Resources/stubs/CallbackFilterIterator.php', + 'FastImageSize\\FastImageSize' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/FastImageSize.php', + 'FastImageSize\\Type\\TypeBase' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeBase.php', + 'FastImageSize\\Type\\TypeBmp' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeBmp.php', + 'FastImageSize\\Type\\TypeGif' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeGif.php', + 'FastImageSize\\Type\\TypeIco' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeIco.php', + 'FastImageSize\\Type\\TypeIff' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeIff.php', + 'FastImageSize\\Type\\TypeInterface' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeInterface.php', + 'FastImageSize\\Type\\TypeJp2' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeJp2.php', + 'FastImageSize\\Type\\TypeJpeg' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeJpeg.php', + 'FastImageSize\\Type\\TypePng' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypePng.php', + 'FastImageSize\\Type\\TypePsd' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypePsd.php', + 'FastImageSize\\Type\\TypeTif' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeTif.php', + 'FastImageSize\\Type\\TypeWbmp' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeWbmp.php', + 'FastImageSize\\Type\\TypeWebp' => __DIR__ . '/..' . '/marc1706/fast-image-size/lib/Type/TypeWebp.php', + 'GuzzleHttp\\BatchResults' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/BatchResults.php', + 'GuzzleHttp\\Client' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Client.php', + 'GuzzleHttp\\ClientInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ClientInterface.php', + 'GuzzleHttp\\Collection' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Collection.php', + 'GuzzleHttp\\Cookie\\CookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJar.php', + 'GuzzleHttp\\Cookie\\CookieJarInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php', + 'GuzzleHttp\\Cookie\\FileCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/FileCookieJar.php', + 'GuzzleHttp\\Cookie\\SessionCookieJar' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php', + 'GuzzleHttp\\Cookie\\SetCookie' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Cookie/SetCookie.php', + 'GuzzleHttp\\Event\\AbstractEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/AbstractEvent.php', + 'GuzzleHttp\\Event\\AbstractRequestEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php', + 'GuzzleHttp\\Event\\AbstractRetryableEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php', + 'GuzzleHttp\\Event\\AbstractTransferEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php', + 'GuzzleHttp\\Event\\BeforeEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/BeforeEvent.php', + 'GuzzleHttp\\Event\\CompleteEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/CompleteEvent.php', + 'GuzzleHttp\\Event\\Emitter' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/Emitter.php', + 'GuzzleHttp\\Event\\EmitterInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/EmitterInterface.php', + 'GuzzleHttp\\Event\\EndEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/EndEvent.php', + 'GuzzleHttp\\Event\\ErrorEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/ErrorEvent.php', + 'GuzzleHttp\\Event\\EventInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/EventInterface.php', + 'GuzzleHttp\\Event\\HasEmitterInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/HasEmitterInterface.php', + 'GuzzleHttp\\Event\\HasEmitterTrait' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/HasEmitterTrait.php', + 'GuzzleHttp\\Event\\ListenerAttacherTrait' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php', + 'GuzzleHttp\\Event\\ProgressEvent' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/ProgressEvent.php', + 'GuzzleHttp\\Event\\RequestEvents' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/RequestEvents.php', + 'GuzzleHttp\\Event\\SubscriberInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Event/SubscriberInterface.php', + 'GuzzleHttp\\Exception\\BadResponseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/BadResponseException.php', + 'GuzzleHttp\\Exception\\ClientException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ClientException.php', + 'GuzzleHttp\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ConnectException.php', + 'GuzzleHttp\\Exception\\CouldNotRewindStreamException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/CouldNotRewindStreamException.php', + 'GuzzleHttp\\Exception\\ParseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ParseException.php', + 'GuzzleHttp\\Exception\\RequestException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/RequestException.php', + 'GuzzleHttp\\Exception\\ServerException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/ServerException.php', + 'GuzzleHttp\\Exception\\StateException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/StateException.php', + 'GuzzleHttp\\Exception\\TooManyRedirectsException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TooManyRedirectsException.php', + 'GuzzleHttp\\Exception\\TransferException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/TransferException.php', + 'GuzzleHttp\\Exception\\XmlParseException' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Exception/XmlParseException.php', + 'GuzzleHttp\\HasDataTrait' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/HasDataTrait.php', + 'GuzzleHttp\\Message\\AbstractMessage' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/AbstractMessage.php', + 'GuzzleHttp\\Message\\AppliesHeadersInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php', + 'GuzzleHttp\\Message\\FutureResponse' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/FutureResponse.php', + 'GuzzleHttp\\Message\\MessageFactory' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/MessageFactory.php', + 'GuzzleHttp\\Message\\MessageFactoryInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php', + 'GuzzleHttp\\Message\\MessageInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/MessageInterface.php', + 'GuzzleHttp\\Message\\MessageParser' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/MessageParser.php', + 'GuzzleHttp\\Message\\Request' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/Request.php', + 'GuzzleHttp\\Message\\RequestInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/RequestInterface.php', + 'GuzzleHttp\\Message\\Response' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/Response.php', + 'GuzzleHttp\\Message\\ResponseInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Message/ResponseInterface.php', + 'GuzzleHttp\\Mimetypes' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Mimetypes.php', + 'GuzzleHttp\\Pool' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Pool.php', + 'GuzzleHttp\\Post\\MultipartBody' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Post/MultipartBody.php', + 'GuzzleHttp\\Post\\PostBody' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Post/PostBody.php', + 'GuzzleHttp\\Post\\PostBodyInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Post/PostBodyInterface.php', + 'GuzzleHttp\\Post\\PostFile' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Post/PostFile.php', + 'GuzzleHttp\\Post\\PostFileInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Post/PostFileInterface.php', + 'GuzzleHttp\\Query' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Query.php', + 'GuzzleHttp\\QueryParser' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/QueryParser.php', + 'GuzzleHttp\\RequestFsm' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RequestFsm.php', + 'GuzzleHttp\\RingBridge' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/RingBridge.php', + 'GuzzleHttp\\Ring\\Client\\ClientUtils' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/ClientUtils.php', + 'GuzzleHttp\\Ring\\Client\\CurlFactory' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/CurlFactory.php', + 'GuzzleHttp\\Ring\\Client\\CurlHandler' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/CurlHandler.php', + 'GuzzleHttp\\Ring\\Client\\CurlMultiHandler' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php', + 'GuzzleHttp\\Ring\\Client\\Middleware' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/Middleware.php', + 'GuzzleHttp\\Ring\\Client\\MockHandler' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/MockHandler.php', + 'GuzzleHttp\\Ring\\Client\\StreamHandler' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Client/StreamHandler.php', + 'GuzzleHttp\\Ring\\Core' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Core.php', + 'GuzzleHttp\\Ring\\Exception\\CancelledException' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Exception/CancelledException.php', + 'GuzzleHttp\\Ring\\Exception\\CancelledFutureAccessException' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Exception/CancelledFutureAccessException.php', + 'GuzzleHttp\\Ring\\Exception\\ConnectException' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Exception/ConnectException.php', + 'GuzzleHttp\\Ring\\Exception\\RingException' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Exception/RingException.php', + 'GuzzleHttp\\Ring\\Future\\BaseFutureTrait' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/BaseFutureTrait.php', + 'GuzzleHttp\\Ring\\Future\\CompletedFutureArray' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php', + 'GuzzleHttp\\Ring\\Future\\CompletedFutureValue' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php', + 'GuzzleHttp\\Ring\\Future\\FutureArray' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/FutureArray.php', + 'GuzzleHttp\\Ring\\Future\\FutureArrayInterface' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php', + 'GuzzleHttp\\Ring\\Future\\FutureInterface' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/FutureInterface.php', + 'GuzzleHttp\\Ring\\Future\\FutureValue' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/FutureValue.php', + 'GuzzleHttp\\Ring\\Future\\MagicFutureTrait' => __DIR__ . '/..' . '/guzzlehttp/ringphp/src/Future/MagicFutureTrait.php', + 'GuzzleHttp\\Stream\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/AppendStream.php', + 'GuzzleHttp\\Stream\\AsyncReadStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/AsyncReadStream.php', + 'GuzzleHttp\\Stream\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/BufferStream.php', + 'GuzzleHttp\\Stream\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/CachingStream.php', + 'GuzzleHttp\\Stream\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/DroppingStream.php', + 'GuzzleHttp\\Stream\\Exception\\CannotAttachException' => __DIR__ . '/..' . '/guzzlehttp/streams/src/Exception/CannotAttachException.php', + 'GuzzleHttp\\Stream\\Exception\\SeekException' => __DIR__ . '/..' . '/guzzlehttp/streams/src/Exception/SeekException.php', + 'GuzzleHttp\\Stream\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/FnStream.php', + 'GuzzleHttp\\Stream\\GuzzleStreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/streams/src/GuzzleStreamWrapper.php', + 'GuzzleHttp\\Stream\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/InflateStream.php', + 'GuzzleHttp\\Stream\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/LazyOpenStream.php', + 'GuzzleHttp\\Stream\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/LimitStream.php', + 'GuzzleHttp\\Stream\\MetadataStreamInterface' => __DIR__ . '/..' . '/guzzlehttp/streams/src/MetadataStreamInterface.php', + 'GuzzleHttp\\Stream\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/NoSeekStream.php', + 'GuzzleHttp\\Stream\\NullStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/NullStream.php', + 'GuzzleHttp\\Stream\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/PumpStream.php', + 'GuzzleHttp\\Stream\\Stream' => __DIR__ . '/..' . '/guzzlehttp/streams/src/Stream.php', + 'GuzzleHttp\\Stream\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/streams/src/StreamDecoratorTrait.php', + 'GuzzleHttp\\Stream\\StreamInterface' => __DIR__ . '/..' . '/guzzlehttp/streams/src/StreamInterface.php', + 'GuzzleHttp\\Stream\\Utils' => __DIR__ . '/..' . '/guzzlehttp/streams/src/Utils.php', + 'GuzzleHttp\\Subscriber\\Cookie' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Subscriber/Cookie.php', + 'GuzzleHttp\\Subscriber\\History' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Subscriber/History.php', + 'GuzzleHttp\\Subscriber\\HttpError' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Subscriber/HttpError.php', + 'GuzzleHttp\\Subscriber\\Mock' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Subscriber/Mock.php', + 'GuzzleHttp\\Subscriber\\Prepare' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Subscriber/Prepare.php', + 'GuzzleHttp\\Subscriber\\Redirect' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Subscriber/Redirect.php', + 'GuzzleHttp\\ToArrayInterface' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/ToArrayInterface.php', + 'GuzzleHttp\\Transaction' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Transaction.php', + 'GuzzleHttp\\UriTemplate' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/UriTemplate.php', + 'GuzzleHttp\\Url' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Url.php', + 'GuzzleHttp\\Utils' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/Utils.php', + 'Normalizer' => __DIR__ . '/..' . '/patchwork/utf8/src/Normalizer.php', + 'OAuth\\Common\\AutoLoader' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/AutoLoader.php', + 'OAuth\\Common\\Consumer\\Credentials' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Consumer/Credentials.php', + 'OAuth\\Common\\Consumer\\CredentialsInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Consumer/CredentialsInterface.php', + 'OAuth\\Common\\Exception\\Exception' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Exception/Exception.php', + 'OAuth\\Common\\Http\\Client\\AbstractClient' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Client/AbstractClient.php', + 'OAuth\\Common\\Http\\Client\\ClientInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Client/ClientInterface.php', + 'OAuth\\Common\\Http\\Client\\CurlClient' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Client/CurlClient.php', + 'OAuth\\Common\\Http\\Client\\StreamClient' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Client/StreamClient.php', + 'OAuth\\Common\\Http\\Exception\\TokenResponseException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Exception/TokenResponseException.php', + 'OAuth\\Common\\Http\\Uri\\Uri' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Uri/Uri.php', + 'OAuth\\Common\\Http\\Uri\\UriFactory' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactory.php', + 'OAuth\\Common\\Http\\Uri\\UriFactoryInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactoryInterface.php', + 'OAuth\\Common\\Http\\Uri\\UriInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriInterface.php', + 'OAuth\\Common\\Service\\AbstractService' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Service/AbstractService.php', + 'OAuth\\Common\\Service\\ServiceInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Service/ServiceInterface.php', + 'OAuth\\Common\\Storage\\Exception\\AuthorizationStateNotFoundException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Storage/Exception/AuthorizationStateNotFoundException.php', + 'OAuth\\Common\\Storage\\Exception\\StorageException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Storage/Exception/StorageException.php', + 'OAuth\\Common\\Storage\\Exception\\TokenNotFoundException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Storage/Exception/TokenNotFoundException.php', + 'OAuth\\Common\\Storage\\Memory' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Storage/Memory.php', + 'OAuth\\Common\\Storage\\Redis' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Storage/Redis.php', + 'OAuth\\Common\\Storage\\Session' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Storage/Session.php', + 'OAuth\\Common\\Storage\\SymfonySession' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Storage/SymfonySession.php', + 'OAuth\\Common\\Storage\\TokenStorageInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Storage/TokenStorageInterface.php', + 'OAuth\\Common\\Token\\AbstractToken' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Token/AbstractToken.php', + 'OAuth\\Common\\Token\\Exception\\ExpiredTokenException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Token/Exception/ExpiredTokenException.php', + 'OAuth\\Common\\Token\\TokenInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/Common/Token/TokenInterface.php', + 'OAuth\\OAuth1\\Service\\AbstractService' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/AbstractService.php', + 'OAuth\\OAuth1\\Service\\BitBucket' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/BitBucket.php', + 'OAuth\\OAuth1\\Service\\Etsy' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Etsy.php', + 'OAuth\\OAuth1\\Service\\FitBit' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/FitBit.php', + 'OAuth\\OAuth1\\Service\\FiveHundredPx' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/FiveHundredPx.php', + 'OAuth\\OAuth1\\Service\\Flickr' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Flickr.php', + 'OAuth\\OAuth1\\Service\\QuickBooks' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/QuickBooks.php', + 'OAuth\\OAuth1\\Service\\Redmine' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Redmine.php', + 'OAuth\\OAuth1\\Service\\ScoopIt' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/ScoopIt.php', + 'OAuth\\OAuth1\\Service\\ServiceInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/ServiceInterface.php', + 'OAuth\\OAuth1\\Service\\Tumblr' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Tumblr.php', + 'OAuth\\OAuth1\\Service\\Twitter' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Twitter.php', + 'OAuth\\OAuth1\\Service\\Xing' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Xing.php', + 'OAuth\\OAuth1\\Service\\Yahoo' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Service/Yahoo.php', + 'OAuth\\OAuth1\\Signature\\Exception\\UnsupportedHashAlgorithmException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php', + 'OAuth\\OAuth1\\Signature\\Signature' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Signature/Signature.php', + 'OAuth\\OAuth1\\Signature\\SignatureInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Signature/SignatureInterface.php', + 'OAuth\\OAuth1\\Token\\StdOAuth1Token' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Token/StdOAuth1Token.php', + 'OAuth\\OAuth1\\Token\\TokenInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth1/Token/TokenInterface.php', + 'OAuth\\OAuth2\\Service\\AbstractService' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/AbstractService.php', + 'OAuth\\OAuth2\\Service\\Amazon' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Amazon.php', + 'OAuth\\OAuth2\\Service\\BattleNet' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/BattleNet.php', + 'OAuth\\OAuth2\\Service\\Bitly' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitly.php', + 'OAuth\\OAuth2\\Service\\Bitrix24' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitrix24.php', + 'OAuth\\OAuth2\\Service\\Box' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Box.php', + 'OAuth\\OAuth2\\Service\\Buffer' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Buffer.php', + 'OAuth\\OAuth2\\Service\\Dailymotion' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Dailymotion.php', + 'OAuth\\OAuth2\\Service\\Deezer' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Deezer.php', + 'OAuth\\OAuth2\\Service\\Delicious' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Delicious.php', + 'OAuth\\OAuth2\\Service\\DeviantArt' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/DeviantArt.php', + 'OAuth\\OAuth2\\Service\\Dropbox' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Dropbox.php', + 'OAuth\\OAuth2\\Service\\EveOnline' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/EveOnline.php', + 'OAuth\\OAuth2\\Service\\Exception\\InvalidAccessTypeException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php', + 'OAuth\\OAuth2\\Service\\Exception\\InvalidAuthorizationStateException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidAuthorizationStateException.php', + 'OAuth\\OAuth2\\Service\\Exception\\InvalidScopeException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidScopeException.php', + 'OAuth\\OAuth2\\Service\\Exception\\MissingRefreshTokenException' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php', + 'OAuth\\OAuth2\\Service\\Facebook' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Facebook.php', + 'OAuth\\OAuth2\\Service\\Foursquare' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Foursquare.php', + 'OAuth\\OAuth2\\Service\\GitHub' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/GitHub.php', + 'OAuth\\OAuth2\\Service\\Google' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Google.php', + 'OAuth\\OAuth2\\Service\\Harvest' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Harvest.php', + 'OAuth\\OAuth2\\Service\\Heroku' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Heroku.php', + 'OAuth\\OAuth2\\Service\\Hubic' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Hubic.php', + 'OAuth\\OAuth2\\Service\\Instagram' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Instagram.php', + 'OAuth\\OAuth2\\Service\\JawboneUP' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/JawboneUP.php', + 'OAuth\\OAuth2\\Service\\Linkedin' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Linkedin.php', + 'OAuth\\OAuth2\\Service\\Mailchimp' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Mailchimp.php', + 'OAuth\\OAuth2\\Service\\Microsoft' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Microsoft.php', + 'OAuth\\OAuth2\\Service\\Mondo' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Mondo.php', + 'OAuth\\OAuth2\\Service\\Nest' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Nest.php', + 'OAuth\\OAuth2\\Service\\Netatmo' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Netatmo.php', + 'OAuth\\OAuth2\\Service\\ParrotFlowerPower' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/ParrotFlowerPower.php', + 'OAuth\\OAuth2\\Service\\Paypal' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Paypal.php', + 'OAuth\\OAuth2\\Service\\Pinterest' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Pinterest.php', + 'OAuth\\OAuth2\\Service\\Pocket' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Pocket.php', + 'OAuth\\OAuth2\\Service\\Reddit' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Reddit.php', + 'OAuth\\OAuth2\\Service\\RunKeeper' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/RunKeeper.php', + 'OAuth\\OAuth2\\Service\\Salesforce' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Salesforce.php', + 'OAuth\\OAuth2\\Service\\ServiceInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/ServiceInterface.php', + 'OAuth\\OAuth2\\Service\\SoundCloud' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/SoundCloud.php', + 'OAuth\\OAuth2\\Service\\Spotify' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Spotify.php', + 'OAuth\\OAuth2\\Service\\Strava' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Strava.php', + 'OAuth\\OAuth2\\Service\\Ustream' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Ustream.php', + 'OAuth\\OAuth2\\Service\\Vimeo' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Vimeo.php', + 'OAuth\\OAuth2\\Service\\Vkontakte' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Vkontakte.php', + 'OAuth\\OAuth2\\Service\\Yahoo' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Yahoo.php', + 'OAuth\\OAuth2\\Service\\Yammer' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Service/Yammer.php', + 'OAuth\\OAuth2\\Token\\StdOAuth2Token' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Token/StdOAuth2Token.php', + 'OAuth\\OAuth2\\Token\\TokenInterface' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/OAuth2/Token/TokenInterface.php', + 'OAuth\\ServiceFactory' => __DIR__ . '/..' . '/lusitanian/oauth/src/OAuth/ServiceFactory.php', + 'Patchwork\\PHP\\Shim\\Iconv' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/PHP/Shim/Iconv.php', + 'Patchwork\\PHP\\Shim\\Intl' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/PHP/Shim/Intl.php', + 'Patchwork\\PHP\\Shim\\Mbstring' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/PHP/Shim/Mbstring.php', + 'Patchwork\\PHP\\Shim\\Normalizer' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/PHP/Shim/Normalizer.php', + 'Patchwork\\PHP\\Shim\\Xml' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/PHP/Shim/Xml.php', + 'Patchwork\\TurkishUtf8' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/TurkishUtf8.php', + 'Patchwork\\Utf8' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/Utf8.php', + 'Patchwork\\Utf8\\BestFit' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/Utf8/BestFit.php', + 'Patchwork\\Utf8\\Bootup' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/Utf8/Bootup.php', + 'Patchwork\\Utf8\\WindowsStreamWrapper' => __DIR__ . '/..' . '/patchwork/utf8/src/Patchwork/Utf8/WindowsStreamWrapper.php', + 'ProxyManager\\Autoloader\\Autoloader' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Autoloader/Autoloader.php', + 'ProxyManager\\Autoloader\\AutoloaderInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Autoloader/AutoloaderInterface.php', + 'ProxyManager\\Configuration' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Configuration.php', + 'ProxyManager\\Exception\\DisabledMethodException' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Exception/DisabledMethodException.php', + 'ProxyManager\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Exception/ExceptionInterface.php', + 'ProxyManager\\Exception\\FileNotWritableException' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Exception/FileNotWritableException.php', + 'ProxyManager\\Exception\\InvalidProxiedClassException' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxiedClassException.php', + 'ProxyManager\\Exception\\InvalidProxyDirectoryException' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxyDirectoryException.php', + 'ProxyManager\\Exception\\UnsupportedProxiedClassException' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Exception/UnsupportedProxiedClassException.php', + 'ProxyManager\\Factory\\AbstractBaseFactory' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractBaseFactory.php', + 'ProxyManager\\Factory\\AbstractLazyFactory' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractLazyFactory.php', + 'ProxyManager\\Factory\\AccessInterceptorScopeLocalizerFactory' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorScopeLocalizerFactory.php', + 'ProxyManager\\Factory\\AccessInterceptorValueHolderFactory' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorValueHolderFactory.php', + 'ProxyManager\\Factory\\LazyLoadingGhostFactory' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingGhostFactory.php', + 'ProxyManager\\Factory\\LazyLoadingValueHolderFactory' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingValueHolderFactory.php', + 'ProxyManager\\Factory\\NullObjectFactory' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/NullObjectFactory.php', + 'ProxyManager\\Factory\\RemoteObjectFactory' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObjectFactory.php', + 'ProxyManager\\Factory\\RemoteObject\\AdapterInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/AdapterInterface.php', + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\BaseAdapter' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/BaseAdapter.php', + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\JsonRpc' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/JsonRpc.php', + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\Soap' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/Soap.php', + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\XmlRpc' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/XmlRpc.php', + 'ProxyManager\\FileLocator\\FileLocator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocator.php', + 'ProxyManager\\FileLocator\\FileLocatorInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocatorInterface.php', + 'ProxyManager\\GeneratorStrategy\\BaseGeneratorStrategy' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/BaseGeneratorStrategy.php', + 'ProxyManager\\GeneratorStrategy\\EvaluatingGeneratorStrategy' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/EvaluatingGeneratorStrategy.php', + 'ProxyManager\\GeneratorStrategy\\FileWriterGeneratorStrategy' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/FileWriterGeneratorStrategy.php', + 'ProxyManager\\GeneratorStrategy\\GeneratorStrategyInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/GeneratorStrategyInterface.php', + 'ProxyManager\\Generator\\ClassGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Generator/ClassGenerator.php', + 'ProxyManager\\Generator\\MagicMethodGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Generator/MagicMethodGenerator.php', + 'ProxyManager\\Generator\\MethodGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Generator/MethodGenerator.php', + 'ProxyManager\\Generator\\ParameterGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Generator/ParameterGenerator.php', + 'ProxyManager\\Generator\\Util\\ClassGeneratorUtils' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Generator/Util/ClassGeneratorUtils.php', + 'ProxyManager\\Generator\\Util\\UniqueIdentifierGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Generator/Util/UniqueIdentifierGenerator.php', + 'ProxyManager\\Inflector\\ClassNameInflector' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflector.php', + 'ProxyManager\\Inflector\\ClassNameInflectorInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflectorInterface.php', + 'ProxyManager\\Inflector\\Util\\ParameterEncoder' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterEncoder.php', + 'ProxyManager\\Inflector\\Util\\ParameterHasher' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterHasher.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizerGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizerGenerator.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\Constructor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\InterceptedMethod' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethod.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicClone' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicClone.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicGet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicIsset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicSet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicSleep' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleep.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\MagicUnset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizer\\MethodGenerator\\Util\\InterceptorGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGenerator.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolderGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolderGenerator.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\Constructor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\InterceptedMethod' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethod.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicClone' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicClone.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicGet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicIsset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicSet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\MagicUnset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolder\\MethodGenerator\\Util\\InterceptorGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGenerator.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\MethodGenerator\\MagicWakeup' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/MagicWakeup.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\MethodGenerator\\SetMethodPrefixInterceptor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptor.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\MethodGenerator\\SetMethodSuffixInterceptor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptor.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\PropertyGenerator\\MethodPrefixInterceptors' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptors.php', + 'ProxyManager\\ProxyGenerator\\AccessInterceptor\\PropertyGenerator\\MethodSuffixInterceptors' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptors.php', + 'ProxyManager\\ProxyGenerator\\Assertion\\CanProxyAssertion' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Assertion/CanProxyAssertion.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhostGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhostGenerator.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\CallInitializer' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\GetProxyInitializer' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\InitializeProxy' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxy.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\IsProxyInitialized' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitialized.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\LazyLoadingMethodInterceptor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/LazyLoadingMethodInterceptor.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicClone' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicClone.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicGet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicIsset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicSet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicSleep' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleep.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\MagicUnset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\MethodGenerator\\SetProxyInitializer' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\PropertyGenerator\\InitializationTracker' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTracker.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhost\\PropertyGenerator\\InitializerProperty' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerProperty.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolderGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolderGenerator.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\GetProxyInitializer' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\InitializeProxy' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxy.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\IsProxyInitialized' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitialized.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\LazyLoadingMethodInterceptor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptor.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicClone' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicClone.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicGet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicIsset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicSet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicSleep' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleep.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\MagicUnset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\MethodGenerator\\SetProxyInitializer' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializer.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\PropertyGenerator\\InitializerProperty' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerProperty.php', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolder\\PropertyGenerator\\ValueHolderProperty' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderProperty.php', + 'ProxyManager\\ProxyGenerator\\LazyLoading\\MethodGenerator\\Constructor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoading/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\NullObjectGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObjectGenerator.php', + 'ProxyManager\\ProxyGenerator\\NullObject\\MethodGenerator\\Constructor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\NullObject\\MethodGenerator\\NullObjectMethodInterceptor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptor.php', + 'ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesDefaults' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesDefaults.php', + 'ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesMap.php', + 'ProxyManager\\ProxyGenerator\\ProxyGeneratorInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ProxyGeneratorInterface.php', + 'ProxyManager\\ProxyGenerator\\RemoteObjectGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObjectGenerator.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\Constructor' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/Constructor.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\MagicGet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicGet.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\MagicIsset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicIsset.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\MagicSet' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicSet.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\MagicUnset' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnset.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\MethodGenerator\\RemoteObjectMethod' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php', + 'ProxyManager\\ProxyGenerator\\RemoteObject\\PropertyGenerator\\AdapterProperty' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterProperty.php', + 'ProxyManager\\ProxyGenerator\\Util\\ProxiedMethodsFilter' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/ProxiedMethodsFilter.php', + 'ProxyManager\\ProxyGenerator\\Util\\PublicScopeSimulator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/PublicScopeSimulator.php', + 'ProxyManager\\ProxyGenerator\\ValueHolder\\MethodGenerator\\GetWrappedValueHolderValue' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValue.php', + 'ProxyManager\\ProxyGenerator\\ValueHolder\\MethodGenerator\\MagicSleep' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleep.php', + 'ProxyManager\\Proxy\\AccessInterceptorInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/AccessInterceptorInterface.php', + 'ProxyManager\\Proxy\\Exception\\RemoteObjectException' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/Exception/RemoteObjectException.php', + 'ProxyManager\\Proxy\\FallbackValueHolderInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/FallbackValueHolderInterface.php', + 'ProxyManager\\Proxy\\GhostObjectInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/GhostObjectInterface.php', + 'ProxyManager\\Proxy\\LazyLoadingInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/LazyLoadingInterface.php', + 'ProxyManager\\Proxy\\NullObjectInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/NullObjectInterface.php', + 'ProxyManager\\Proxy\\ProxyInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/ProxyInterface.php', + 'ProxyManager\\Proxy\\RemoteObjectInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/RemoteObjectInterface.php', + 'ProxyManager\\Proxy\\SmartReferenceInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/SmartReferenceInterface.php', + 'ProxyManager\\Proxy\\ValueHolderInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/ValueHolderInterface.php', + 'ProxyManager\\Proxy\\VirtualProxyInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Proxy/VirtualProxyInterface.php', + 'ProxyManager\\Signature\\ClassSignatureGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGenerator.php', + 'ProxyManager\\Signature\\ClassSignatureGeneratorInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGeneratorInterface.php', + 'ProxyManager\\Signature\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/ExceptionInterface.php', + 'ProxyManager\\Signature\\Exception\\InvalidSignatureException' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/InvalidSignatureException.php', + 'ProxyManager\\Signature\\Exception\\MissingSignatureException' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/MissingSignatureException.php', + 'ProxyManager\\Signature\\SignatureChecker' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureChecker.php', + 'ProxyManager\\Signature\\SignatureCheckerInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureCheckerInterface.php', + 'ProxyManager\\Signature\\SignatureGenerator' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGenerator.php', + 'ProxyManager\\Signature\\SignatureGeneratorInterface' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGeneratorInterface.php', + 'ProxyManager\\Version' => __DIR__ . '/..' . '/ocramius/proxy-manager/src/ProxyManager/Version.php', + 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.php', + 'ReCaptcha\\ReCaptcha' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/ReCaptcha.php', + 'ReCaptcha\\RequestMethod' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod.php', + 'ReCaptcha\\RequestMethod\\Curl' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Curl.php', + 'ReCaptcha\\RequestMethod\\CurlPost' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php', + 'ReCaptcha\\RequestMethod\\Post' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php', + 'ReCaptcha\\RequestMethod\\Socket' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php', + 'ReCaptcha\\RequestMethod\\SocketPost' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php', + 'ReCaptcha\\RequestParameters' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestParameters.php', + 'ReCaptcha\\Response' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/Response.php', + 'React\\Promise\\CancellablePromiseInterface' => __DIR__ . '/..' . '/react/promise/src/CancellablePromiseInterface.php', + 'React\\Promise\\CancellationQueue' => __DIR__ . '/..' . '/react/promise/src/CancellationQueue.php', + 'React\\Promise\\Deferred' => __DIR__ . '/..' . '/react/promise/src/Deferred.php', + 'React\\Promise\\Exception\\LengthException' => __DIR__ . '/..' . '/react/promise/src/Exception/LengthException.php', + 'React\\Promise\\ExtendedPromiseInterface' => __DIR__ . '/..' . '/react/promise/src/ExtendedPromiseInterface.php', + 'React\\Promise\\FulfilledPromise' => __DIR__ . '/..' . '/react/promise/src/FulfilledPromise.php', + 'React\\Promise\\LazyPromise' => __DIR__ . '/..' . '/react/promise/src/LazyPromise.php', + 'React\\Promise\\Promise' => __DIR__ . '/..' . '/react/promise/src/Promise.php', + 'React\\Promise\\PromiseInterface' => __DIR__ . '/..' . '/react/promise/src/PromiseInterface.php', + 'React\\Promise\\PromisorInterface' => __DIR__ . '/..' . '/react/promise/src/PromisorInterface.php', + 'React\\Promise\\RejectedPromise' => __DIR__ . '/..' . '/react/promise/src/RejectedPromise.php', + 'React\\Promise\\UnhandledRejectionException' => __DIR__ . '/..' . '/react/promise/src/UnhandledRejectionException.php', + 'RecursiveCallbackFilterIterator' => __DIR__ . '/..' . '/symfony/polyfill-php54/Resources/stubs/RecursiveCallbackFilterIterator.php', + 'SessionHandlerInterface' => __DIR__ . '/..' . '/symfony/polyfill-php54/Resources/stubs/SessionHandlerInterface.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\Instantiator\\LazyLoadingValueHolderFactoryV1' => __DIR__ . '/..' . '/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\Instantiator\\LazyLoadingValueHolderFactoryV2' => __DIR__ . '/..' . '/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\Instantiator\\RuntimeInstantiator' => __DIR__ . '/..' . '/symfony/proxy-manager-bridge/LazyProxy/Instantiator/RuntimeInstantiator.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\PhpDumper\\LazyLoadingValueHolderGenerator' => __DIR__ . '/..' . '/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php', + 'Symfony\\Bridge\\ProxyManager\\LazyProxy\\PhpDumper\\ProxyDumper' => __DIR__ . '/..' . '/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/ProxyDumper.php', + 'Symfony\\Bridge\\Twig\\AppVariable' => __DIR__ . '/..' . '/symfony/twig-bridge/AppVariable.php', + 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/DebugCommand.php', + 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/LintCommand.php', + 'Symfony\\Bridge\\Twig\\DataCollector\\TwigDataCollector' => __DIR__ . '/..' . '/symfony/twig-bridge/DataCollector/TwigDataCollector.php', + 'Symfony\\Bridge\\Twig\\Extension\\AssetExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/AssetExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\CodeExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/CodeExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\DumpExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/DumpExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\ExpressionExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ExpressionExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\FormExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/FormExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\HttpFoundationExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HttpFoundationExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\HttpKernelExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/HttpKernelExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\LogoutUrlExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/LogoutUrlExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/ProfilerExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\RoutingExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/RoutingExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\SecurityExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/SecurityExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\StopwatchExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/StopwatchExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\TranslationExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/TranslationExtension.php', + 'Symfony\\Bridge\\Twig\\Extension\\YamlExtension' => __DIR__ . '/..' . '/symfony/twig-bridge/Extension/YamlExtension.php', + 'Symfony\\Bridge\\Twig\\Form\\TwigRenderer' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRenderer.php', + 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngine' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererEngine.php', + 'Symfony\\Bridge\\Twig\\Form\\TwigRendererEngineInterface' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererEngineInterface.php', + 'Symfony\\Bridge\\Twig\\Form\\TwigRendererInterface' => __DIR__ . '/..' . '/symfony/twig-bridge/Form/TwigRendererInterface.php', + 'Symfony\\Bridge\\Twig\\NodeVisitor\\Scope' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/Scope.php', + 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationDefaultDomainNodeVisitor' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php', + 'Symfony\\Bridge\\Twig\\NodeVisitor\\TranslationNodeVisitor' => __DIR__ . '/..' . '/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php', + 'Symfony\\Bridge\\Twig\\Node\\DumpNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/DumpNode.php', + 'Symfony\\Bridge\\Twig\\Node\\FormEnctypeNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/FormEnctypeNode.php', + 'Symfony\\Bridge\\Twig\\Node\\FormThemeNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/FormThemeNode.php', + 'Symfony\\Bridge\\Twig\\Node\\RenderBlockNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/RenderBlockNode.php', + 'Symfony\\Bridge\\Twig\\Node\\SearchAndRenderBlockNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php', + 'Symfony\\Bridge\\Twig\\Node\\StopwatchNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/StopwatchNode.php', + 'Symfony\\Bridge\\Twig\\Node\\TransDefaultDomainNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/TransDefaultDomainNode.php', + 'Symfony\\Bridge\\Twig\\Node\\TransNode' => __DIR__ . '/..' . '/symfony/twig-bridge/Node/TransNode.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\DumpTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/DumpTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\FormThemeTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\StopwatchTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\TransChoiceTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\TransDefaultDomainTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php', + 'Symfony\\Bridge\\Twig\\TokenParser\\TransTokenParser' => __DIR__ . '/..' . '/symfony/twig-bridge/TokenParser/TransTokenParser.php', + 'Symfony\\Bridge\\Twig\\Translation\\TwigExtractor' => __DIR__ . '/..' . '/symfony/twig-bridge/Translation/TwigExtractor.php', + 'Symfony\\Bridge\\Twig\\TwigEngine' => __DIR__ . '/..' . '/symfony/twig-bridge/TwigEngine.php', + 'Symfony\\Component\\Config\\ConfigCache' => __DIR__ . '/..' . '/symfony/config/ConfigCache.php', + 'Symfony\\Component\\Config\\ConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactory.php', + 'Symfony\\Component\\Config\\ConfigCacheFactoryInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheFactoryInterface.php', + 'Symfony\\Component\\Config\\ConfigCacheInterface' => __DIR__ . '/..' . '/symfony/config/ConfigCacheInterface.php', + 'Symfony\\Component\\Config\\Definition\\ArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/ArrayNode.php', + 'Symfony\\Component\\Config\\Definition\\BaseNode' => __DIR__ . '/..' . '/symfony/config/Definition/BaseNode.php', + 'Symfony\\Component\\Config\\Definition\\BooleanNode' => __DIR__ . '/..' . '/symfony/config/Definition/BooleanNode.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ArrayNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\BooleanNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/BooleanNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\EnumNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/EnumNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ExprBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\FloatNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/FloatNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\IntegerNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/IntegerNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\MergeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/MergeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NodeParentInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NodeParentInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NormalizationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NormalizationBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\NumericNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/NumericNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ParentNodeDefinitionInterface' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ScalarNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ScalarNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/TreeBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\ValidationBuilder' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/ValidationBuilder.php', + 'Symfony\\Component\\Config\\Definition\\Builder\\VariableNodeDefinition' => __DIR__ . '/..' . '/symfony/config/Definition/Builder/VariableNodeDefinition.php', + 'Symfony\\Component\\Config\\Definition\\ConfigurationInterface' => __DIR__ . '/..' . '/symfony/config/Definition/ConfigurationInterface.php', + 'Symfony\\Component\\Config\\Definition\\Dumper\\XmlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/XmlReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\Dumper\\YamlReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/Dumper/YamlReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\EnumNode' => __DIR__ . '/..' . '/symfony/config/Definition/EnumNode.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\DuplicateKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/DuplicateKeyException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\Exception' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/Exception.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\ForbiddenOverwriteException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/ForbiddenOverwriteException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidConfigurationException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidConfigurationException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidDefinitionException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidDefinitionException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\InvalidTypeException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/InvalidTypeException.php', + 'Symfony\\Component\\Config\\Definition\\Exception\\UnsetKeyException' => __DIR__ . '/..' . '/symfony/config/Definition/Exception/UnsetKeyException.php', + 'Symfony\\Component\\Config\\Definition\\FloatNode' => __DIR__ . '/..' . '/symfony/config/Definition/FloatNode.php', + 'Symfony\\Component\\Config\\Definition\\IntegerNode' => __DIR__ . '/..' . '/symfony/config/Definition/IntegerNode.php', + 'Symfony\\Component\\Config\\Definition\\NodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/NodeInterface.php', + 'Symfony\\Component\\Config\\Definition\\NumericNode' => __DIR__ . '/..' . '/symfony/config/Definition/NumericNode.php', + 'Symfony\\Component\\Config\\Definition\\Processor' => __DIR__ . '/..' . '/symfony/config/Definition/Processor.php', + 'Symfony\\Component\\Config\\Definition\\PrototypeNodeInterface' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypeNodeInterface.php', + 'Symfony\\Component\\Config\\Definition\\PrototypedArrayNode' => __DIR__ . '/..' . '/symfony/config/Definition/PrototypedArrayNode.php', + 'Symfony\\Component\\Config\\Definition\\ReferenceDumper' => __DIR__ . '/..' . '/symfony/config/Definition/ReferenceDumper.php', + 'Symfony\\Component\\Config\\Definition\\ScalarNode' => __DIR__ . '/..' . '/symfony/config/Definition/ScalarNode.php', + 'Symfony\\Component\\Config\\Definition\\VariableNode' => __DIR__ . '/..' . '/symfony/config/Definition/VariableNode.php', + 'Symfony\\Component\\Config\\Exception\\FileLoaderImportCircularReferenceException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderImportCircularReferenceException.php', + 'Symfony\\Component\\Config\\Exception\\FileLoaderLoadException' => __DIR__ . '/..' . '/symfony/config/Exception/FileLoaderLoadException.php', + 'Symfony\\Component\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/config/FileLocator.php', + 'Symfony\\Component\\Config\\FileLocatorInterface' => __DIR__ . '/..' . '/symfony/config/FileLocatorInterface.php', + 'Symfony\\Component\\Config\\Loader\\DelegatingLoader' => __DIR__ . '/..' . '/symfony/config/Loader/DelegatingLoader.php', + 'Symfony\\Component\\Config\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/config/Loader/FileLoader.php', + 'Symfony\\Component\\Config\\Loader\\Loader' => __DIR__ . '/..' . '/symfony/config/Loader/Loader.php', + 'Symfony\\Component\\Config\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderInterface.php', + 'Symfony\\Component\\Config\\Loader\\LoaderResolver' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolver.php', + 'Symfony\\Component\\Config\\Loader\\LoaderResolverInterface' => __DIR__ . '/..' . '/symfony/config/Loader/LoaderResolverInterface.php', + 'Symfony\\Component\\Config\\ResourceCheckerConfigCache' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCache.php', + 'Symfony\\Component\\Config\\ResourceCheckerConfigCacheFactory' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerConfigCacheFactory.php', + 'Symfony\\Component\\Config\\ResourceCheckerInterface' => __DIR__ . '/..' . '/symfony/config/ResourceCheckerInterface.php', + 'Symfony\\Component\\Config\\Resource\\BCResourceInterfaceChecker' => __DIR__ . '/..' . '/symfony/config/Resource/BCResourceInterfaceChecker.php', + 'Symfony\\Component\\Config\\Resource\\DirectoryResource' => __DIR__ . '/..' . '/symfony/config/Resource/DirectoryResource.php', + 'Symfony\\Component\\Config\\Resource\\FileExistenceResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileExistenceResource.php', + 'Symfony\\Component\\Config\\Resource\\FileResource' => __DIR__ . '/..' . '/symfony/config/Resource/FileResource.php', + 'Symfony\\Component\\Config\\Resource\\ResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/ResourceInterface.php', + 'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceChecker' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceChecker.php', + 'Symfony\\Component\\Config\\Resource\\SelfCheckingResourceInterface' => __DIR__ . '/..' . '/symfony/config/Resource/SelfCheckingResourceInterface.php', + 'Symfony\\Component\\Config\\Util\\XmlUtils' => __DIR__ . '/..' . '/symfony/config/Util/XmlUtils.php', + 'Symfony\\Component\\Console\\Application' => __DIR__ . '/..' . '/symfony/console/Application.php', + 'Symfony\\Component\\Console\\Command\\Command' => __DIR__ . '/..' . '/symfony/console/Command/Command.php', + 'Symfony\\Component\\Console\\Command\\HelpCommand' => __DIR__ . '/..' . '/symfony/console/Command/HelpCommand.php', + 'Symfony\\Component\\Console\\Command\\ListCommand' => __DIR__ . '/..' . '/symfony/console/Command/ListCommand.php', + 'Symfony\\Component\\Console\\ConsoleEvents' => __DIR__ . '/..' . '/symfony/console/ConsoleEvents.php', + 'Symfony\\Component\\Console\\Descriptor\\ApplicationDescription' => __DIR__ . '/..' . '/symfony/console/Descriptor/ApplicationDescription.php', + 'Symfony\\Component\\Console\\Descriptor\\Descriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/Descriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\DescriptorInterface' => __DIR__ . '/..' . '/symfony/console/Descriptor/DescriptorInterface.php', + 'Symfony\\Component\\Console\\Descriptor\\JsonDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/JsonDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\MarkdownDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/MarkdownDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\TextDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/TextDescriptor.php', + 'Symfony\\Component\\Console\\Descriptor\\XmlDescriptor' => __DIR__ . '/..' . '/symfony/console/Descriptor/XmlDescriptor.php', + 'Symfony\\Component\\Console\\Event\\ConsoleCommandEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleCommandEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleExceptionEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleExceptionEvent.php', + 'Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent' => __DIR__ . '/..' . '/symfony/console/Event/ConsoleTerminateEvent.php', + 'Symfony\\Component\\Console\\Exception\\CommandNotFoundException' => __DIR__ . '/..' . '/symfony/console/Exception/CommandNotFoundException.php', + 'Symfony\\Component\\Console\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/console/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Console\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\Console\\Exception\\InvalidOptionException' => __DIR__ . '/..' . '/symfony/console/Exception/InvalidOptionException.php', + 'Symfony\\Component\\Console\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/console/Exception/LogicException.php', + 'Symfony\\Component\\Console\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/console/Exception/RuntimeException.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatter' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatter.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyle.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleInterface' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleInterface.php', + 'Symfony\\Component\\Console\\Formatter\\OutputFormatterStyleStack' => __DIR__ . '/..' . '/symfony/console/Formatter/OutputFormatterStyleStack.php', + 'Symfony\\Component\\Console\\Helper\\DebugFormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DebugFormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\DescriptorHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DescriptorHelper.php', + 'Symfony\\Component\\Console\\Helper\\DialogHelper' => __DIR__ . '/..' . '/symfony/console/Helper/DialogHelper.php', + 'Symfony\\Component\\Console\\Helper\\FormatterHelper' => __DIR__ . '/..' . '/symfony/console/Helper/FormatterHelper.php', + 'Symfony\\Component\\Console\\Helper\\Helper' => __DIR__ . '/..' . '/symfony/console/Helper/Helper.php', + 'Symfony\\Component\\Console\\Helper\\HelperInterface' => __DIR__ . '/..' . '/symfony/console/Helper/HelperInterface.php', + 'Symfony\\Component\\Console\\Helper\\HelperSet' => __DIR__ . '/..' . '/symfony/console/Helper/HelperSet.php', + 'Symfony\\Component\\Console\\Helper\\InputAwareHelper' => __DIR__ . '/..' . '/symfony/console/Helper/InputAwareHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProcessHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProcessHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressBar' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressBar.php', + 'Symfony\\Component\\Console\\Helper\\ProgressHelper' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressHelper.php', + 'Symfony\\Component\\Console\\Helper\\ProgressIndicator' => __DIR__ . '/..' . '/symfony/console/Helper/ProgressIndicator.php', + 'Symfony\\Component\\Console\\Helper\\QuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/QuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\SymfonyQuestionHelper' => __DIR__ . '/..' . '/symfony/console/Helper/SymfonyQuestionHelper.php', + 'Symfony\\Component\\Console\\Helper\\Table' => __DIR__ . '/..' . '/symfony/console/Helper/Table.php', + 'Symfony\\Component\\Console\\Helper\\TableCell' => __DIR__ . '/..' . '/symfony/console/Helper/TableCell.php', + 'Symfony\\Component\\Console\\Helper\\TableHelper' => __DIR__ . '/..' . '/symfony/console/Helper/TableHelper.php', + 'Symfony\\Component\\Console\\Helper\\TableSeparator' => __DIR__ . '/..' . '/symfony/console/Helper/TableSeparator.php', + 'Symfony\\Component\\Console\\Helper\\TableStyle' => __DIR__ . '/..' . '/symfony/console/Helper/TableStyle.php', + 'Symfony\\Component\\Console\\Input\\ArgvInput' => __DIR__ . '/..' . '/symfony/console/Input/ArgvInput.php', + 'Symfony\\Component\\Console\\Input\\ArrayInput' => __DIR__ . '/..' . '/symfony/console/Input/ArrayInput.php', + 'Symfony\\Component\\Console\\Input\\Input' => __DIR__ . '/..' . '/symfony/console/Input/Input.php', + 'Symfony\\Component\\Console\\Input\\InputArgument' => __DIR__ . '/..' . '/symfony/console/Input/InputArgument.php', + 'Symfony\\Component\\Console\\Input\\InputAwareInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputAwareInterface.php', + 'Symfony\\Component\\Console\\Input\\InputDefinition' => __DIR__ . '/..' . '/symfony/console/Input/InputDefinition.php', + 'Symfony\\Component\\Console\\Input\\InputInterface' => __DIR__ . '/..' . '/symfony/console/Input/InputInterface.php', + 'Symfony\\Component\\Console\\Input\\InputOption' => __DIR__ . '/..' . '/symfony/console/Input/InputOption.php', + 'Symfony\\Component\\Console\\Input\\StringInput' => __DIR__ . '/..' . '/symfony/console/Input/StringInput.php', + 'Symfony\\Component\\Console\\Logger\\ConsoleLogger' => __DIR__ . '/..' . '/symfony/console/Logger/ConsoleLogger.php', + 'Symfony\\Component\\Console\\Output\\BufferedOutput' => __DIR__ . '/..' . '/symfony/console/Output/BufferedOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutput' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutput.php', + 'Symfony\\Component\\Console\\Output\\ConsoleOutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/ConsoleOutputInterface.php', + 'Symfony\\Component\\Console\\Output\\NullOutput' => __DIR__ . '/..' . '/symfony/console/Output/NullOutput.php', + 'Symfony\\Component\\Console\\Output\\Output' => __DIR__ . '/..' . '/symfony/console/Output/Output.php', + 'Symfony\\Component\\Console\\Output\\OutputInterface' => __DIR__ . '/..' . '/symfony/console/Output/OutputInterface.php', + 'Symfony\\Component\\Console\\Output\\StreamOutput' => __DIR__ . '/..' . '/symfony/console/Output/StreamOutput.php', + 'Symfony\\Component\\Console\\Question\\ChoiceQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ChoiceQuestion.php', + 'Symfony\\Component\\Console\\Question\\ConfirmationQuestion' => __DIR__ . '/..' . '/symfony/console/Question/ConfirmationQuestion.php', + 'Symfony\\Component\\Console\\Question\\Question' => __DIR__ . '/..' . '/symfony/console/Question/Question.php', + 'Symfony\\Component\\Console\\Shell' => __DIR__ . '/..' . '/symfony/console/Shell.php', + 'Symfony\\Component\\Console\\Style\\OutputStyle' => __DIR__ . '/..' . '/symfony/console/Style/OutputStyle.php', + 'Symfony\\Component\\Console\\Style\\StyleInterface' => __DIR__ . '/..' . '/symfony/console/Style/StyleInterface.php', + 'Symfony\\Component\\Console\\Style\\SymfonyStyle' => __DIR__ . '/..' . '/symfony/console/Style/SymfonyStyle.php', + 'Symfony\\Component\\Console\\Tester\\ApplicationTester' => __DIR__ . '/..' . '/symfony/console/Tester/ApplicationTester.php', + 'Symfony\\Component\\Console\\Tester\\CommandTester' => __DIR__ . '/..' . '/symfony/console/Tester/CommandTester.php', + 'Symfony\\Component\\Debug\\BufferingLogger' => __DIR__ . '/..' . '/symfony/debug/BufferingLogger.php', + 'Symfony\\Component\\Debug\\Debug' => __DIR__ . '/..' . '/symfony/debug/Debug.php', + 'Symfony\\Component\\Debug\\DebugClassLoader' => __DIR__ . '/..' . '/symfony/debug/DebugClassLoader.php', + 'Symfony\\Component\\Debug\\ErrorHandler' => __DIR__ . '/..' . '/symfony/debug/ErrorHandler.php', + 'Symfony\\Component\\Debug\\ErrorHandlerCanary' => __DIR__ . '/..' . '/symfony/debug/ErrorHandler.php', + 'Symfony\\Component\\Debug\\ExceptionHandler' => __DIR__ . '/..' . '/symfony/debug/ExceptionHandler.php', + 'Symfony\\Component\\Debug\\Exception\\ClassNotFoundException' => __DIR__ . '/..' . '/symfony/debug/Exception/ClassNotFoundException.php', + 'Symfony\\Component\\Debug\\Exception\\ContextErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/ContextErrorException.php', + 'Symfony\\Component\\Debug\\Exception\\DummyException' => __DIR__ . '/..' . '/symfony/debug/Exception/DummyException.php', + 'Symfony\\Component\\Debug\\Exception\\FatalErrorException' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalErrorException.php', + 'Symfony\\Component\\Debug\\Exception\\FatalThrowableError' => __DIR__ . '/..' . '/symfony/debug/Exception/FatalThrowableError.php', + 'Symfony\\Component\\Debug\\Exception\\FlattenException' => __DIR__ . '/..' . '/symfony/debug/Exception/FlattenException.php', + 'Symfony\\Component\\Debug\\Exception\\OutOfMemoryException' => __DIR__ . '/..' . '/symfony/debug/Exception/OutOfMemoryException.php', + 'Symfony\\Component\\Debug\\Exception\\UndefinedFunctionException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedFunctionException.php', + 'Symfony\\Component\\Debug\\Exception\\UndefinedMethodException' => __DIR__ . '/..' . '/symfony/debug/Exception/UndefinedMethodException.php', + 'Symfony\\Component\\Debug\\FatalErrorHandler\\ClassNotFoundFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php', + 'Symfony\\Component\\Debug\\FatalErrorHandler\\FatalErrorHandlerInterface' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php', + 'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedFunctionFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php', + 'Symfony\\Component\\Debug\\FatalErrorHandler\\UndefinedMethodFatalErrorHandler' => __DIR__ . '/..' . '/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php', + 'Symfony\\Component\\DependencyInjection\\Alias' => __DIR__ . '/..' . '/symfony/dependency-injection/Alias.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AnalyzeServiceReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AutoAliasServicePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutoAliasServicePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\AutowirePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/AutowirePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckCircularReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckExceptionOnInvalidReferenceBehaviorPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CheckReferenceValidityPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\Compiler' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/Compiler.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/CompilerPassInterface.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\DecoratorServicePass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/DecoratorServicePass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ExtensionCompilerPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\InlineServiceDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\LoggingFormatter' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/LoggingFormatter.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\MergeExtensionConfigurationPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\PassConfig' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/PassConfig.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveAbstractDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RemovePrivateAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RemoveUnusedDefinitionsPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RepeatablePassInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RepeatablePassInterface.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\RepeatedPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/RepeatedPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ReplaceAliasByActualDefinitionPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveDefinitionTemplatesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveInvalidReferencesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveParameterPlaceHoldersPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ResolveReferencesToAliasesPass' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraph' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphEdge' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php', + 'Symfony\\Component\\DependencyInjection\\Compiler\\ServiceReferenceGraphNode' => __DIR__ . '/..' . '/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php', + 'Symfony\\Component\\DependencyInjection\\Container' => __DIR__ . '/..' . '/symfony/dependency-injection/Container.php', + 'Symfony\\Component\\DependencyInjection\\ContainerAware' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerAware.php', + 'Symfony\\Component\\DependencyInjection\\ContainerAwareInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerAwareInterface.php', + 'Symfony\\Component\\DependencyInjection\\ContainerAwareTrait' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerAwareTrait.php', + 'Symfony\\Component\\DependencyInjection\\ContainerBuilder' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerBuilder.php', + 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\Definition' => __DIR__ . '/..' . '/symfony/dependency-injection/Definition.php', + 'Symfony\\Component\\DependencyInjection\\DefinitionDecorator' => __DIR__ . '/..' . '/symfony/dependency-injection/DefinitionDecorator.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\Dumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/Dumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/DumperInterface.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\GraphvizDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/GraphvizDumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\PhpDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/PhpDumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\XmlDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/XmlDumper.php', + 'Symfony\\Component\\DependencyInjection\\Dumper\\YamlDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/Dumper/YamlDumper.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/BadMethodCallException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ExceptionInterface.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\InactiveScopeException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/InactiveScopeException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/InvalidArgumentException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\LogicException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/LogicException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\OutOfBoundsException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/OutOfBoundsException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ParameterCircularReferenceException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ParameterNotFoundException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ParameterNotFoundException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/RuntimeException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ScopeCrossingInjectionException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ScopeCrossingInjectionException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ScopeWideningInjectionException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ServiceCircularReferenceException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php', + 'Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException' => __DIR__ . '/..' . '/symfony/dependency-injection/Exception/ServiceNotFoundException.php', + 'Symfony\\Component\\DependencyInjection\\ExpressionLanguage' => __DIR__ . '/..' . '/symfony/dependency-injection/ExpressionLanguage.php', + 'Symfony\\Component\\DependencyInjection\\ExpressionLanguageProvider' => __DIR__ . '/..' . '/symfony/dependency-injection/ExpressionLanguageProvider.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\ConfigurationExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\Extension' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/Extension.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/ExtensionInterface.php', + 'Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/Extension/PrependExtensionInterface.php', + 'Symfony\\Component\\DependencyInjection\\IntrospectableContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/IntrospectableContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\InstantiatorInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\Instantiator\\RealServiceInstantiator' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\DumperInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php', + 'Symfony\\Component\\DependencyInjection\\LazyProxy\\PhpDumper\\NullDumper' => __DIR__ . '/..' . '/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\ClosureLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/ClosureLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\DirectoryLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/DirectoryLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\FileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/FileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\IniFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/IniFileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\PhpFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/PhpFileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/XmlFileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/dependency-injection/Loader/YamlFileLoader.php', + 'Symfony\\Component\\DependencyInjection\\Parameter' => __DIR__ . '/..' . '/symfony/dependency-injection/Parameter.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\FrozenParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ParameterBag.php', + 'Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBagInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php', + 'Symfony\\Component\\DependencyInjection\\Reference' => __DIR__ . '/..' . '/symfony/dependency-injection/Reference.php', + 'Symfony\\Component\\DependencyInjection\\ResettableContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ResettableContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\Scope' => __DIR__ . '/..' . '/symfony/dependency-injection/Scope.php', + 'Symfony\\Component\\DependencyInjection\\ScopeInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/ScopeInterface.php', + 'Symfony\\Component\\DependencyInjection\\SimpleXMLElement' => __DIR__ . '/..' . '/symfony/dependency-injection/SimpleXMLElement.php', + 'Symfony\\Component\\DependencyInjection\\TaggedContainerInterface' => __DIR__ . '/..' . '/symfony/dependency-injection/TaggedContainerInterface.php', + 'Symfony\\Component\\DependencyInjection\\Variable' => __DIR__ . '/..' . '/symfony/dependency-injection/Variable.php', + 'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ContainerAwareEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php', + 'Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener' => __DIR__ . '/..' . '/symfony/event-dispatcher/Debug/WrappedListener.php', + 'Symfony\\Component\\EventDispatcher\\DependencyInjection\\RegisterListenersPass' => __DIR__ . '/..' . '/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php', + 'Symfony\\Component\\EventDispatcher\\Event' => __DIR__ . '/..' . '/symfony/event-dispatcher/Event.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcher.php', + 'Symfony\\Component\\EventDispatcher\\EventDispatcherInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventDispatcherInterface.php', + 'Symfony\\Component\\EventDispatcher\\EventSubscriberInterface' => __DIR__ . '/..' . '/symfony/event-dispatcher/EventSubscriberInterface.php', + 'Symfony\\Component\\EventDispatcher\\GenericEvent' => __DIR__ . '/..' . '/symfony/event-dispatcher/GenericEvent.php', + 'Symfony\\Component\\EventDispatcher\\ImmutableEventDispatcher' => __DIR__ . '/..' . '/symfony/event-dispatcher/ImmutableEventDispatcher.php', + 'Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/FileNotFoundException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOException' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOException.php', + 'Symfony\\Component\\Filesystem\\Exception\\IOExceptionInterface' => __DIR__ . '/..' . '/symfony/filesystem/Exception/IOExceptionInterface.php', + 'Symfony\\Component\\Filesystem\\Filesystem' => __DIR__ . '/..' . '/symfony/filesystem/Filesystem.php', + 'Symfony\\Component\\Filesystem\\LockHandler' => __DIR__ . '/..' . '/symfony/filesystem/LockHandler.php', + 'Symfony\\Component\\Finder\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/AbstractAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\AbstractFindAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/AbstractFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\AdapterInterface' => __DIR__ . '/..' . '/symfony/finder/Adapter/AdapterInterface.php', + 'Symfony\\Component\\Finder\\Adapter\\BsdFindAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/BsdFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\GnuFindAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/GnuFindAdapter.php', + 'Symfony\\Component\\Finder\\Adapter\\PhpAdapter' => __DIR__ . '/..' . '/symfony/finder/Adapter/PhpAdapter.php', + 'Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php', + 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php', + 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php', + 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/finder/Exception/AccessDeniedException.php', + 'Symfony\\Component\\Finder\\Exception\\AdapterFailureException' => __DIR__ . '/..' . '/symfony/finder/Exception/AdapterFailureException.php', + 'Symfony\\Component\\Finder\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/finder/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Finder\\Exception\\OperationNotPermitedException' => __DIR__ . '/..' . '/symfony/finder/Exception/OperationNotPermitedException.php', + 'Symfony\\Component\\Finder\\Exception\\ShellCommandFailureException' => __DIR__ . '/..' . '/symfony/finder/Exception/ShellCommandFailureException.php', + 'Symfony\\Component\\Finder\\Expression\\Expression' => __DIR__ . '/..' . '/symfony/finder/Expression/Expression.php', + 'Symfony\\Component\\Finder\\Expression\\Glob' => __DIR__ . '/..' . '/symfony/finder/Expression/Glob.php', + 'Symfony\\Component\\Finder\\Expression\\Regex' => __DIR__ . '/..' . '/symfony/finder/Expression/Regex.php', + 'Symfony\\Component\\Finder\\Expression\\ValueInterface' => __DIR__ . '/..' . '/symfony/finder/Expression/ValueInterface.php', + 'Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.php', + 'Symfony\\Component\\Finder\\Glob' => __DIR__ . '/..' . '/symfony/finder/Glob.php', + 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/CustomFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DateRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilePathsIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilePathsIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FileTypeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilecontentFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilenameFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/PathFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\Shell\\Command' => __DIR__ . '/..' . '/symfony/finder/Shell/Command.php', + 'Symfony\\Component\\Finder\\Shell\\Shell' => __DIR__ . '/..' . '/symfony/finder/Shell/Shell.php', + 'Symfony\\Component\\Finder\\SplFileInfo' => __DIR__ . '/..' . '/symfony/finder/SplFileInfo.php', + 'Symfony\\Component\\HttpFoundation\\AcceptHeader' => __DIR__ . '/..' . '/symfony/http-foundation/AcceptHeader.php', + 'Symfony\\Component\\HttpFoundation\\AcceptHeaderItem' => __DIR__ . '/..' . '/symfony/http-foundation/AcceptHeaderItem.php', + 'Symfony\\Component\\HttpFoundation\\ApacheRequest' => __DIR__ . '/..' . '/symfony/http-foundation/ApacheRequest.php', + 'Symfony\\Component\\HttpFoundation\\BinaryFileResponse' => __DIR__ . '/..' . '/symfony/http-foundation/BinaryFileResponse.php', + 'Symfony\\Component\\HttpFoundation\\Cookie' => __DIR__ . '/..' . '/symfony/http-foundation/Cookie.php', + 'Symfony\\Component\\HttpFoundation\\Exception\\ConflictingHeadersException' => __DIR__ . '/..' . '/symfony/http-foundation/Exception/ConflictingHeadersException.php', + 'Symfony\\Component\\HttpFoundation\\ExpressionRequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/ExpressionRequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\FileBag' => __DIR__ . '/..' . '/symfony/http-foundation/FileBag.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/AccessDeniedException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FileException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/FileNotFoundException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UnexpectedTypeException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/UnexpectedTypeException.php', + 'Symfony\\Component\\HttpFoundation\\File\\Exception\\UploadException' => __DIR__ . '/..' . '/symfony/http-foundation/File/Exception/UploadException.php', + 'Symfony\\Component\\HttpFoundation\\File\\File' => __DIR__ . '/..' . '/symfony/http-foundation/File/File.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/ExtensionGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\ExtensionGuesserInterface' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileBinaryMimeTypeGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\FileinfoMimeTypeGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeExtensionGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesser' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php', + 'Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesserInterface' => __DIR__ . '/..' . '/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php', + 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' => __DIR__ . '/..' . '/symfony/http-foundation/File/UploadedFile.php', + 'Symfony\\Component\\HttpFoundation\\HeaderBag' => __DIR__ . '/..' . '/symfony/http-foundation/HeaderBag.php', + 'Symfony\\Component\\HttpFoundation\\IpUtils' => __DIR__ . '/..' . '/symfony/http-foundation/IpUtils.php', + 'Symfony\\Component\\HttpFoundation\\JsonResponse' => __DIR__ . '/..' . '/symfony/http-foundation/JsonResponse.php', + 'Symfony\\Component\\HttpFoundation\\ParameterBag' => __DIR__ . '/..' . '/symfony/http-foundation/ParameterBag.php', + 'Symfony\\Component\\HttpFoundation\\RedirectResponse' => __DIR__ . '/..' . '/symfony/http-foundation/RedirectResponse.php', + 'Symfony\\Component\\HttpFoundation\\Request' => __DIR__ . '/..' . '/symfony/http-foundation/Request.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcher' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcher.php', + 'Symfony\\Component\\HttpFoundation\\RequestMatcherInterface' => __DIR__ . '/..' . '/symfony/http-foundation/RequestMatcherInterface.php', + 'Symfony\\Component\\HttpFoundation\\RequestStack' => __DIR__ . '/..' . '/symfony/http-foundation/RequestStack.php', + 'Symfony\\Component\\HttpFoundation\\Response' => __DIR__ . '/..' . '/symfony/http-foundation/Response.php', + 'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag' => __DIR__ . '/..' . '/symfony/http-foundation/ResponseHeaderBag.php', + 'Symfony\\Component\\HttpFoundation\\ServerBag' => __DIR__ . '/..' . '/symfony/http-foundation/ServerBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/AttributeBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/FlashBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Flash/FlashBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Session' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Session.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionBagInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\SessionInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/SessionInterface.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\LegacyPdoSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/LegacyPdoSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\WriteCheckSessionHandler' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MetadataBag.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/NativeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\PhpBridgeSessionStorage' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\AbstractProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\NativeProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\Proxy\\SessionHandlerProxy' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php', + 'Symfony\\Component\\HttpFoundation\\Session\\Storage\\SessionStorageInterface' => __DIR__ . '/..' . '/symfony/http-foundation/Session/Storage/SessionStorageInterface.php', + 'Symfony\\Component\\HttpFoundation\\StreamedResponse' => __DIR__ . '/..' . '/symfony/http-foundation/StreamedResponse.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\Bundle' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/Bundle.php', + 'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Bundle/BundleInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheClearer\\CacheClearerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/CacheClearerInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheClearer\\ChainCacheClearer' => __DIR__ . '/..' . '/symfony/http-kernel/CacheClearer/ChainCacheClearer.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmer' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/CacheWarmer.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerAggregate' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\CacheWarmerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php', + 'Symfony\\Component\\HttpKernel\\CacheWarmer\\WarmableInterface' => __DIR__ . '/..' . '/symfony/http-kernel/CacheWarmer/WarmableInterface.php', + 'Symfony\\Component\\HttpKernel\\Client' => __DIR__ . '/..' . '/symfony/http-kernel/Client.php', + 'Symfony\\Component\\HttpKernel\\Config\\EnvParametersResource' => __DIR__ . '/..' . '/symfony/http-kernel/Config/EnvParametersResource.php', + 'Symfony\\Component\\HttpKernel\\Config\\FileLocator' => __DIR__ . '/..' . '/symfony/http-kernel/Config/FileLocator.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerReference' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerReference.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerResolver.php', + 'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/ControllerResolverInterface.php', + 'Symfony\\Component\\HttpKernel\\Controller\\TraceableControllerResolver' => __DIR__ . '/..' . '/symfony/http-kernel/Controller/TraceableControllerResolver.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\AjaxDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/AjaxDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\ConfigDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/ConfigDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\DataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/DataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\DataCollectorInterface' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/DataCollectorInterface.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\DumpDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/DumpDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\EventDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/EventDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\ExceptionDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/ExceptionDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\LateDataCollectorInterface' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\LoggerDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/LoggerDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\MemoryDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/MemoryDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\RequestDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/RequestDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\RouterDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/RouterDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\TimeDataCollector' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/TimeDataCollector.php', + 'Symfony\\Component\\HttpKernel\\DataCollector\\Util\\ValueExporter' => __DIR__ . '/..' . '/symfony/http-kernel/DataCollector/Util/ValueExporter.php', + 'Symfony\\Component\\HttpKernel\\Debug\\ErrorHandler' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/ErrorHandler.php', + 'Symfony\\Component\\HttpKernel\\Debug\\ExceptionHandler' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/ExceptionHandler.php', + 'Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher' => __DIR__ . '/..' . '/symfony/http-kernel/Debug/TraceableEventDispatcher.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\AddClassesToCachePass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ConfigurableExtension' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/ContainerAwareHttpKernel.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\Extension' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/Extension.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\FragmentRendererPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\LazyLoadingFragmentHandler' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\MergeExtensionConfigurationPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php', + 'Symfony\\Component\\HttpKernel\\DependencyInjection\\RegisterListenersPass' => __DIR__ . '/..' . '/symfony/http-kernel/DependencyInjection/RegisterListenersPass.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\AddRequestFormatsListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/AddRequestFormatsListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\DebugHandlersListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DebugHandlersListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\DumpListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/DumpListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ErrorsLoggerListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ErrorsLoggerListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\EsiListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/EsiListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ExceptionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\FragmentListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/FragmentListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\LocaleListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/LocaleListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ProfilerListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ResponseListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/RouterListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\SaveSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SaveSessionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\SessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SessionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\StreamedResponseListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/StreamedResponseListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\SurrogateListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/SurrogateListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\TestSessionListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/TestSessionListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\TranslatorListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/TranslatorListener.php', + 'Symfony\\Component\\HttpKernel\\EventListener\\ValidateRequestListener' => __DIR__ . '/..' . '/symfony/http-kernel/EventListener/ValidateRequestListener.php', + 'Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FilterControllerEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FilterResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\FinishRequestEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/FinishRequestEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/GetResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/GetResponseForExceptionEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\KernelEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/KernelEvent.php', + 'Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent' => __DIR__ . '/..' . '/symfony/http-kernel/Event/PostResponseEvent.php', + 'Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/AccessDeniedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\BadRequestHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/BadRequestHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ConflictHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ConflictHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\GoneHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/GoneHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\HttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/HttpExceptionInterface.php', + 'Symfony\\Component\\HttpKernel\\Exception\\LengthRequiredHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/LengthRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\MethodNotAllowedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\NotAcceptableHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/NotAcceptableHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/NotFoundHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionFailedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/PreconditionFailedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\PreconditionRequiredHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\ServiceUnavailableHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\TooManyRequestsHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/TooManyRequestsHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnauthorizedHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnauthorizedHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnprocessableEntityHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php', + 'Symfony\\Component\\HttpKernel\\Exception\\UnsupportedMediaTypeHttpException' => __DIR__ . '/..' . '/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\AbstractSurrogateFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\EsiFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/EsiFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentHandler' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentHandler.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\FragmentRendererInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/FragmentRendererInterface.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\HIncludeFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\InlineFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/InlineFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\RoutableFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\Fragment\\SsiFragmentRenderer' => __DIR__ . '/..' . '/symfony/http-kernel/Fragment/SsiFragmentRenderer.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\Esi' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/Esi.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\EsiResponseCacheStrategy' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/EsiResponseCacheStrategy.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\EsiResponseCacheStrategyInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/EsiResponseCacheStrategyInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/HttpCache.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\ResponseCacheStrategy' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\ResponseCacheStrategyInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\Ssi' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/Ssi.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\Store' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/Store.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/StoreInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\SubRequestHandler' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/SubRequestHandler.php', + 'Symfony\\Component\\HttpKernel\\HttpCache\\SurrogateInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpCache/SurrogateInterface.php', + 'Symfony\\Component\\HttpKernel\\HttpKernel' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernel.php', + 'Symfony\\Component\\HttpKernel\\HttpKernelInterface' => __DIR__ . '/..' . '/symfony/http-kernel/HttpKernelInterface.php', + 'Symfony\\Component\\HttpKernel\\Kernel' => __DIR__ . '/..' . '/symfony/http-kernel/Kernel.php', + 'Symfony\\Component\\HttpKernel\\KernelEvents' => __DIR__ . '/..' . '/symfony/http-kernel/KernelEvents.php', + 'Symfony\\Component\\HttpKernel\\KernelInterface' => __DIR__ . '/..' . '/symfony/http-kernel/KernelInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\DebugLoggerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Log/DebugLoggerInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\LoggerInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Log/LoggerInterface.php', + 'Symfony\\Component\\HttpKernel\\Log\\NullLogger' => __DIR__ . '/..' . '/symfony/http-kernel/Log/NullLogger.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\BaseMemcacheProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/BaseMemcacheProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\FileProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/FileProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MemcacheProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/MemcacheProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MemcachedProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/MemcachedProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MongoDbProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/MongoDbProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\MysqlProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/MysqlProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\PdoProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/PdoProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\Profile' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/Profile.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\Profiler' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/Profiler.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\ProfilerStorageInterface' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/ProfilerStorageInterface.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\RedisProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/RedisProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\Profiler\\SqliteProfilerStorage' => __DIR__ . '/..' . '/symfony/http-kernel/Profiler/SqliteProfilerStorage.php', + 'Symfony\\Component\\HttpKernel\\TerminableInterface' => __DIR__ . '/..' . '/symfony/http-kernel/TerminableInterface.php', + 'Symfony\\Component\\HttpKernel\\UriSigner' => __DIR__ . '/..' . '/symfony/http-kernel/UriSigner.php', + 'Symfony\\Component\\Routing\\Annotation\\Route' => __DIR__ . '/..' . '/symfony/routing/Annotation/Route.php', + 'Symfony\\Component\\Routing\\CompiledRoute' => __DIR__ . '/..' . '/symfony/routing/CompiledRoute.php', + 'Symfony\\Component\\Routing\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/routing/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Routing\\Exception\\InvalidParameterException' => __DIR__ . '/..' . '/symfony/routing/Exception/InvalidParameterException.php', + 'Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException' => __DIR__ . '/..' . '/symfony/routing/Exception/MethodNotAllowedException.php', + 'Symfony\\Component\\Routing\\Exception\\MissingMandatoryParametersException' => __DIR__ . '/..' . '/symfony/routing/Exception/MissingMandatoryParametersException.php', + 'Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException' => __DIR__ . '/..' . '/symfony/routing/Exception/ResourceNotFoundException.php', + 'Symfony\\Component\\Routing\\Exception\\RouteNotFoundException' => __DIR__ . '/..' . '/symfony/routing/Exception/RouteNotFoundException.php', + 'Symfony\\Component\\Routing\\Generator\\ConfigurableRequirementsInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/ConfigurableRequirementsInterface.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/GeneratorDumper.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\GeneratorDumperInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php', + 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper' => __DIR__ . '/..' . '/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php', + 'Symfony\\Component\\Routing\\Generator\\UrlGenerator' => __DIR__ . '/..' . '/symfony/routing/Generator/UrlGenerator.php', + 'Symfony\\Component\\Routing\\Generator\\UrlGeneratorInterface' => __DIR__ . '/..' . '/symfony/routing/Generator/UrlGeneratorInterface.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationClassLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationClassLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ClosureLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ClosureLoader.php', + 'Symfony\\Component\\Routing\\Loader\\DependencyInjection\\ServiceRouterLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php', + 'Symfony\\Component\\Routing\\Loader\\DirectoryLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/DirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\ObjectRouteLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/ObjectRouteLoader.php', + 'Symfony\\Component\\Routing\\Loader\\PhpFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/PhpFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\RecursiveCallbackFilterIterator' => __DIR__ . '/..' . '/symfony/routing/Loader/AnnotationDirectoryLoader.php', + 'Symfony\\Component\\Routing\\Loader\\XmlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/XmlFileLoader.php', + 'Symfony\\Component\\Routing\\Loader\\YamlFileLoader' => __DIR__ . '/..' . '/symfony/routing/Loader/YamlFileLoader.php', + 'Symfony\\Component\\Routing\\Matcher\\ApacheUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/ApacheUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\ApacheMatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/ApacheMatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperCollection' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/DumperCollection.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperPrefixCollection' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/DumperPrefixCollection.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\DumperRoute' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/DumperRoute.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/MatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\MatcherDumperInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper' => __DIR__ . '/..' . '/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php', + 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/RedirectableUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\RequestMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/RequestMatcherInterface.php', + 'Symfony\\Component\\Routing\\Matcher\\TraceableUrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/TraceableUrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher' => __DIR__ . '/..' . '/symfony/routing/Matcher/UrlMatcher.php', + 'Symfony\\Component\\Routing\\Matcher\\UrlMatcherInterface' => __DIR__ . '/..' . '/symfony/routing/Matcher/UrlMatcherInterface.php', + 'Symfony\\Component\\Routing\\RequestContext' => __DIR__ . '/..' . '/symfony/routing/RequestContext.php', + 'Symfony\\Component\\Routing\\RequestContextAwareInterface' => __DIR__ . '/..' . '/symfony/routing/RequestContextAwareInterface.php', + 'Symfony\\Component\\Routing\\Route' => __DIR__ . '/..' . '/symfony/routing/Route.php', + 'Symfony\\Component\\Routing\\RouteCollection' => __DIR__ . '/..' . '/symfony/routing/RouteCollection.php', + 'Symfony\\Component\\Routing\\RouteCollectionBuilder' => __DIR__ . '/..' . '/symfony/routing/RouteCollectionBuilder.php', + 'Symfony\\Component\\Routing\\RouteCompiler' => __DIR__ . '/..' . '/symfony/routing/RouteCompiler.php', + 'Symfony\\Component\\Routing\\RouteCompilerInterface' => __DIR__ . '/..' . '/symfony/routing/RouteCompilerInterface.php', + 'Symfony\\Component\\Routing\\Router' => __DIR__ . '/..' . '/symfony/routing/Router.php', + 'Symfony\\Component\\Routing\\RouterInterface' => __DIR__ . '/..' . '/symfony/routing/RouterInterface.php', + 'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php', + 'Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php', + 'Symfony\\Component\\Yaml\\Exception\\DumpException' => __DIR__ . '/..' . '/symfony/yaml/Exception/DumpException.php', + 'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/yaml/Exception/ExceptionInterface.php', + 'Symfony\\Component\\Yaml\\Exception\\ParseException' => __DIR__ . '/..' . '/symfony/yaml/Exception/ParseException.php', + 'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/yaml/Exception/RuntimeException.php', + 'Symfony\\Component\\Yaml\\Inline' => __DIR__ . '/..' . '/symfony/yaml/Inline.php', + 'Symfony\\Component\\Yaml\\Parser' => __DIR__ . '/..' . '/symfony/yaml/Parser.php', + 'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php', + 'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php', + 'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php54\\Php54' => __DIR__ . '/..' . '/symfony/polyfill-php54/Php54.php', + 'Symfony\\Polyfill\\Php55\\Php55' => __DIR__ . '/..' . '/symfony/polyfill-php55/Php55.php', + 'Symfony\\Polyfill\\Php55\\Php55ArrayColumn' => __DIR__ . '/..' . '/symfony/polyfill-php55/Php55ArrayColumn.php', + 'Twig\\Cache\\CacheInterface' => __DIR__ . '/..' . '/twig/twig/src/Cache/CacheInterface.php', + 'Twig\\Cache\\FilesystemCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/FilesystemCache.php', + 'Twig\\Cache\\NullCache' => __DIR__ . '/..' . '/twig/twig/src/Cache/NullCache.php', + 'Twig\\Compiler' => __DIR__ . '/..' . '/twig/twig/src/Compiler.php', + 'Twig\\Environment' => __DIR__ . '/..' . '/twig/twig/src/Environment.php', + 'Twig\\Error\\Error' => __DIR__ . '/..' . '/twig/twig/src/Error/Error.php', + 'Twig\\Error\\LoaderError' => __DIR__ . '/..' . '/twig/twig/src/Error/LoaderError.php', + 'Twig\\Error\\RuntimeError' => __DIR__ . '/..' . '/twig/twig/src/Error/RuntimeError.php', + 'Twig\\Error\\SyntaxError' => __DIR__ . '/..' . '/twig/twig/src/Error/SyntaxError.php', + 'Twig\\ExpressionParser' => __DIR__ . '/..' . '/twig/twig/src/ExpressionParser.php', + 'Twig\\Extension\\AbstractExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/AbstractExtension.php', + 'Twig\\Extension\\CoreExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/CoreExtension.php', + 'Twig\\Extension\\DebugExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/DebugExtension.php', + 'Twig\\Extension\\EscaperExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/EscaperExtension.php', + 'Twig\\Extension\\ExtensionInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/ExtensionInterface.php', + 'Twig\\Extension\\GlobalsInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/GlobalsInterface.php', + 'Twig\\Extension\\InitRuntimeInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/InitRuntimeInterface.php', + 'Twig\\Extension\\OptimizerExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/OptimizerExtension.php', + 'Twig\\Extension\\ProfilerExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/ProfilerExtension.php', + 'Twig\\Extension\\RuntimeExtensionInterface' => __DIR__ . '/..' . '/twig/twig/src/Extension/RuntimeExtensionInterface.php', + 'Twig\\Extension\\SandboxExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/SandboxExtension.php', + 'Twig\\Extension\\StagingExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/StagingExtension.php', + 'Twig\\Extension\\StringLoaderExtension' => __DIR__ . '/..' . '/twig/twig/src/Extension/StringLoaderExtension.php', + 'Twig\\FileExtensionEscapingStrategy' => __DIR__ . '/..' . '/twig/twig/src/FileExtensionEscapingStrategy.php', + 'Twig\\Lexer' => __DIR__ . '/..' . '/twig/twig/src/Lexer.php', + 'Twig\\Loader\\ArrayLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ArrayLoader.php', + 'Twig\\Loader\\ChainLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/ChainLoader.php', + 'Twig\\Loader\\ExistsLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/ExistsLoaderInterface.php', + 'Twig\\Loader\\FilesystemLoader' => __DIR__ . '/..' . '/twig/twig/src/Loader/FilesystemLoader.php', + 'Twig\\Loader\\LoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/LoaderInterface.php', + 'Twig\\Loader\\SourceContextLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/Loader/SourceContextLoaderInterface.php', + 'Twig\\Markup' => __DIR__ . '/..' . '/twig/twig/src/Markup.php', + 'Twig\\NodeTraverser' => __DIR__ . '/..' . '/twig/twig/src/NodeTraverser.php', + 'Twig\\NodeVisitor\\AbstractNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php', + 'Twig\\NodeVisitor\\EscaperNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php', + 'Twig\\NodeVisitor\\NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/NodeVisitorInterface.php', + 'Twig\\NodeVisitor\\OptimizerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php', + 'Twig\\NodeVisitor\\SafeAnalysisNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php', + 'Twig\\NodeVisitor\\SandboxNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php', + 'Twig\\Node\\AutoEscapeNode' => __DIR__ . '/..' . '/twig/twig/src/Node/AutoEscapeNode.php', + 'Twig\\Node\\BlockNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockNode.php', + 'Twig\\Node\\BlockReferenceNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BlockReferenceNode.php', + 'Twig\\Node\\BodyNode' => __DIR__ . '/..' . '/twig/twig/src/Node/BodyNode.php', + 'Twig\\Node\\CheckSecurityNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckSecurityNode.php', + 'Twig\\Node\\CheckToStringNode' => __DIR__ . '/..' . '/twig/twig/src/Node/CheckToStringNode.php', + 'Twig\\Node\\DeprecatedNode' => __DIR__ . '/..' . '/twig/twig/src/Node/DeprecatedNode.php', + 'Twig\\Node\\DoNode' => __DIR__ . '/..' . '/twig/twig/src/Node/DoNode.php', + 'Twig\\Node\\EmbedNode' => __DIR__ . '/..' . '/twig/twig/src/Node/EmbedNode.php', + 'Twig\\Node\\Expression\\AbstractExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/AbstractExpression.php', + 'Twig\\Node\\Expression\\ArrayExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ArrayExpression.php', + 'Twig\\Node\\Expression\\AssignNameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/AssignNameExpression.php', + 'Twig\\Node\\Expression\\Binary\\AbstractBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AbstractBinary.php', + 'Twig\\Node\\Expression\\Binary\\AddBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AddBinary.php', + 'Twig\\Node\\Expression\\Binary\\AndBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/AndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseAndBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseOrBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php', + 'Twig\\Node\\Expression\\Binary\\BitwiseXorBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php', + 'Twig\\Node\\Expression\\Binary\\ConcatBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/ConcatBinary.php', + 'Twig\\Node\\Expression\\Binary\\DivBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/DivBinary.php', + 'Twig\\Node\\Expression\\Binary\\EndsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\EqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/EqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\FloorDivBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/GreaterBinary.php', + 'Twig\\Node\\Expression\\Binary\\GreaterEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\InBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/InBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/LessBinary.php', + 'Twig\\Node\\Expression\\Binary\\LessEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\MatchesBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/MatchesBinary.php', + 'Twig\\Node\\Expression\\Binary\\ModBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/ModBinary.php', + 'Twig\\Node\\Expression\\Binary\\MulBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/MulBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotEqualBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php', + 'Twig\\Node\\Expression\\Binary\\NotInBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/NotInBinary.php', + 'Twig\\Node\\Expression\\Binary\\OrBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/OrBinary.php', + 'Twig\\Node\\Expression\\Binary\\PowerBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/PowerBinary.php', + 'Twig\\Node\\Expression\\Binary\\RangeBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/RangeBinary.php', + 'Twig\\Node\\Expression\\Binary\\StartsWithBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php', + 'Twig\\Node\\Expression\\Binary\\SubBinary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Binary/SubBinary.php', + 'Twig\\Node\\Expression\\BlockReferenceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/BlockReferenceExpression.php', + 'Twig\\Node\\Expression\\CallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/CallExpression.php', + 'Twig\\Node\\Expression\\ConditionalExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConditionalExpression.php', + 'Twig\\Node\\Expression\\ConstantExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ConstantExpression.php', + 'Twig\\Node\\Expression\\FilterExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FilterExpression.php', + 'Twig\\Node\\Expression\\Filter\\DefaultFilter' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Filter/DefaultFilter.php', + 'Twig\\Node\\Expression\\FunctionExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/FunctionExpression.php', + 'Twig\\Node\\Expression\\GetAttrExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/GetAttrExpression.php', + 'Twig\\Node\\Expression\\InlinePrint' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/InlinePrint.php', + 'Twig\\Node\\Expression\\MethodCallExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/MethodCallExpression.php', + 'Twig\\Node\\Expression\\NameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/NameExpression.php', + 'Twig\\Node\\Expression\\NullCoalesceExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/NullCoalesceExpression.php', + 'Twig\\Node\\Expression\\ParentExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/ParentExpression.php', + 'Twig\\Node\\Expression\\TempNameExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/TempNameExpression.php', + 'Twig\\Node\\Expression\\TestExpression' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/TestExpression.php', + 'Twig\\Node\\Expression\\Test\\ConstantTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/ConstantTest.php', + 'Twig\\Node\\Expression\\Test\\DefinedTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/DefinedTest.php', + 'Twig\\Node\\Expression\\Test\\DivisiblebyTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php', + 'Twig\\Node\\Expression\\Test\\EvenTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/EvenTest.php', + 'Twig\\Node\\Expression\\Test\\NullTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/NullTest.php', + 'Twig\\Node\\Expression\\Test\\OddTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/OddTest.php', + 'Twig\\Node\\Expression\\Test\\SameasTest' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Test/SameasTest.php', + 'Twig\\Node\\Expression\\Unary\\AbstractUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/AbstractUnary.php', + 'Twig\\Node\\Expression\\Unary\\NegUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NegUnary.php', + 'Twig\\Node\\Expression\\Unary\\NotUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/NotUnary.php', + 'Twig\\Node\\Expression\\Unary\\PosUnary' => __DIR__ . '/..' . '/twig/twig/src/Node/Expression/Unary/PosUnary.php', + 'Twig\\Node\\FlushNode' => __DIR__ . '/..' . '/twig/twig/src/Node/FlushNode.php', + 'Twig\\Node\\ForLoopNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForLoopNode.php', + 'Twig\\Node\\ForNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ForNode.php', + 'Twig\\Node\\IfNode' => __DIR__ . '/..' . '/twig/twig/src/Node/IfNode.php', + 'Twig\\Node\\ImportNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ImportNode.php', + 'Twig\\Node\\IncludeNode' => __DIR__ . '/..' . '/twig/twig/src/Node/IncludeNode.php', + 'Twig\\Node\\MacroNode' => __DIR__ . '/..' . '/twig/twig/src/Node/MacroNode.php', + 'Twig\\Node\\ModuleNode' => __DIR__ . '/..' . '/twig/twig/src/Node/ModuleNode.php', + 'Twig\\Node\\Node' => __DIR__ . '/..' . '/twig/twig/src/Node/Node.php', + 'Twig\\Node\\NodeCaptureInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeCaptureInterface.php', + 'Twig\\Node\\NodeOutputInterface' => __DIR__ . '/..' . '/twig/twig/src/Node/NodeOutputInterface.php', + 'Twig\\Node\\PrintNode' => __DIR__ . '/..' . '/twig/twig/src/Node/PrintNode.php', + 'Twig\\Node\\SandboxNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SandboxNode.php', + 'Twig\\Node\\SandboxedPrintNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SandboxedPrintNode.php', + 'Twig\\Node\\SetNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SetNode.php', + 'Twig\\Node\\SetTempNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SetTempNode.php', + 'Twig\\Node\\SpacelessNode' => __DIR__ . '/..' . '/twig/twig/src/Node/SpacelessNode.php', + 'Twig\\Node\\TextNode' => __DIR__ . '/..' . '/twig/twig/src/Node/TextNode.php', + 'Twig\\Node\\WithNode' => __DIR__ . '/..' . '/twig/twig/src/Node/WithNode.php', + 'Twig\\Parser' => __DIR__ . '/..' . '/twig/twig/src/Parser.php', + 'Twig\\Profiler\\Dumper\\BaseDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/BaseDumper.php', + 'Twig\\Profiler\\Dumper\\BlackfireDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/BlackfireDumper.php', + 'Twig\\Profiler\\Dumper\\HtmlDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/HtmlDumper.php', + 'Twig\\Profiler\\Dumper\\TextDumper' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Dumper/TextDumper.php', + 'Twig\\Profiler\\NodeVisitor\\ProfilerNodeVisitor' => __DIR__ . '/..' . '/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php', + 'Twig\\Profiler\\Node\\EnterProfileNode' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Node/EnterProfileNode.php', + 'Twig\\Profiler\\Node\\LeaveProfileNode' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Node/LeaveProfileNode.php', + 'Twig\\Profiler\\Profile' => __DIR__ . '/..' . '/twig/twig/src/Profiler/Profile.php', + 'Twig\\RuntimeLoader\\ContainerRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php', + 'Twig\\RuntimeLoader\\FactoryRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php', + 'Twig\\RuntimeLoader\\RuntimeLoaderInterface' => __DIR__ . '/..' . '/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php', + 'Twig\\Sandbox\\SecurityError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFilterError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig\\Sandbox\\SecurityNotAllowedFunctionError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig\\Sandbox\\SecurityNotAllowedMethodError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig\\Sandbox\\SecurityNotAllowedPropertyError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig\\Sandbox\\SecurityNotAllowedTagError' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php', + 'Twig\\Sandbox\\SecurityPolicy' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityPolicy.php', + 'Twig\\Sandbox\\SecurityPolicyInterface' => __DIR__ . '/..' . '/twig/twig/src/Sandbox/SecurityPolicyInterface.php', + 'Twig\\Source' => __DIR__ . '/..' . '/twig/twig/src/Source.php', + 'Twig\\Template' => __DIR__ . '/..' . '/twig/twig/src/Template.php', + 'Twig\\TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/src/TemplateWrapper.php', + 'Twig\\Test\\IntegrationTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/IntegrationTestCase.php', + 'Twig\\Test\\NodeTestCase' => __DIR__ . '/..' . '/twig/twig/src/Test/NodeTestCase.php', + 'Twig\\Token' => __DIR__ . '/..' . '/twig/twig/src/Token.php', + 'Twig\\TokenParser\\AbstractTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AbstractTokenParser.php', + 'Twig\\TokenParser\\AutoEscapeTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/AutoEscapeTokenParser.php', + 'Twig\\TokenParser\\BlockTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/BlockTokenParser.php', + 'Twig\\TokenParser\\DeprecatedTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/DeprecatedTokenParser.php', + 'Twig\\TokenParser\\DoTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/DoTokenParser.php', + 'Twig\\TokenParser\\EmbedTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/EmbedTokenParser.php', + 'Twig\\TokenParser\\ExtendsTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ExtendsTokenParser.php', + 'Twig\\TokenParser\\FilterTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FilterTokenParser.php', + 'Twig\\TokenParser\\FlushTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FlushTokenParser.php', + 'Twig\\TokenParser\\ForTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ForTokenParser.php', + 'Twig\\TokenParser\\FromTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/FromTokenParser.php', + 'Twig\\TokenParser\\IfTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/IfTokenParser.php', + 'Twig\\TokenParser\\ImportTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/ImportTokenParser.php', + 'Twig\\TokenParser\\IncludeTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/IncludeTokenParser.php', + 'Twig\\TokenParser\\MacroTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/MacroTokenParser.php', + 'Twig\\TokenParser\\SandboxTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SandboxTokenParser.php', + 'Twig\\TokenParser\\SetTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SetTokenParser.php', + 'Twig\\TokenParser\\SpacelessTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/SpacelessTokenParser.php', + 'Twig\\TokenParser\\TokenParserInterface' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/TokenParserInterface.php', + 'Twig\\TokenParser\\UseTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/UseTokenParser.php', + 'Twig\\TokenParser\\WithTokenParser' => __DIR__ . '/..' . '/twig/twig/src/TokenParser/WithTokenParser.php', + 'Twig\\TokenStream' => __DIR__ . '/..' . '/twig/twig/src/TokenStream.php', + 'Twig\\TwigFilter' => __DIR__ . '/..' . '/twig/twig/src/TwigFilter.php', + 'Twig\\TwigFunction' => __DIR__ . '/..' . '/twig/twig/src/TwigFunction.php', + 'Twig\\TwigTest' => __DIR__ . '/..' . '/twig/twig/src/TwigTest.php', + 'Twig\\Util\\DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/src/Util/DeprecationCollector.php', + 'Twig\\Util\\TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/src/Util/TemplateDirIterator.php', + 'Twig_Autoloader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Autoloader.php', + 'Twig_BaseNodeVisitor' => __DIR__ . '/..' . '/twig/twig/lib/Twig/BaseNodeVisitor.php', + 'Twig_CacheInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/CacheInterface.php', + 'Twig_Cache_Filesystem' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Cache/Filesystem.php', + 'Twig_Cache_Null' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Cache/Null.php', + 'Twig_Compiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Compiler.php', + 'Twig_CompilerInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/CompilerInterface.php', + 'Twig_ContainerRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ContainerRuntimeLoader.php', + 'Twig_Environment' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Environment.php', + 'Twig_Error' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error.php', + 'Twig_Error_Loader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Loader.php', + 'Twig_Error_Runtime' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Runtime.php', + 'Twig_Error_Syntax' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Error/Syntax.php', + 'Twig_ExistsLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExistsLoaderInterface.php', + 'Twig_ExpressionParser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExpressionParser.php', + 'Twig_Extension' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension.php', + 'Twig_ExtensionInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ExtensionInterface.php', + 'Twig_Extension_Core' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Core.php', + 'Twig_Extension_Debug' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Debug.php', + 'Twig_Extension_Escaper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Escaper.php', + 'Twig_Extension_GlobalsInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/GlobalsInterface.php', + 'Twig_Extension_InitRuntimeInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/InitRuntimeInterface.php', + 'Twig_Extension_Optimizer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Optimizer.php', + 'Twig_Extension_Profiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Profiler.php', + 'Twig_Extension_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Sandbox.php', + 'Twig_Extension_Staging' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/Staging.php', + 'Twig_Extension_StringLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Extension/StringLoader.php', + 'Twig_FactoryRuntimeLoader' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FactoryRuntimeLoader.php', + 'Twig_FileExtensionEscapingStrategy' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FileExtensionEscapingStrategy.php', + 'Twig_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter.php', + 'Twig_FilterCallableInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FilterCallableInterface.php', + 'Twig_FilterInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FilterInterface.php', + 'Twig_Filter_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter/Function.php', + 'Twig_Filter_Method' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter/Method.php', + 'Twig_Filter_Node' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Filter/Node.php', + 'Twig_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function.php', + 'Twig_FunctionCallableInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FunctionCallableInterface.php', + 'Twig_FunctionInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/FunctionInterface.php', + 'Twig_Function_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function/Function.php', + 'Twig_Function_Method' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function/Method.php', + 'Twig_Function_Node' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Function/Node.php', + 'Twig_Lexer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Lexer.php', + 'Twig_LexerInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/LexerInterface.php', + 'Twig_LoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/LoaderInterface.php', + 'Twig_Loader_Array' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Array.php', + 'Twig_Loader_Chain' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Chain.php', + 'Twig_Loader_Filesystem' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/Filesystem.php', + 'Twig_Loader_String' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Loader/String.php', + 'Twig_Markup' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Markup.php', + 'Twig_Node' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node.php', + 'Twig_NodeCaptureInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeCaptureInterface.php', + 'Twig_NodeInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeInterface.php', + 'Twig_NodeOutputInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeOutputInterface.php', + 'Twig_NodeTraverser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeTraverser.php', + 'Twig_NodeVisitorInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitorInterface.php', + 'Twig_NodeVisitor_Escaper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Escaper.php', + 'Twig_NodeVisitor_Optimizer' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Optimizer.php', + 'Twig_NodeVisitor_SafeAnalysis' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/SafeAnalysis.php', + 'Twig_NodeVisitor_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/NodeVisitor/Sandbox.php', + 'Twig_Node_AutoEscape' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/AutoEscape.php', + 'Twig_Node_Block' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Block.php', + 'Twig_Node_BlockReference' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/BlockReference.php', + 'Twig_Node_Body' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Body.php', + 'Twig_Node_CheckSecurity' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/CheckSecurity.php', + 'Twig_Node_Deprecated' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Deprecated.php', + 'Twig_Node_Do' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Do.php', + 'Twig_Node_Embed' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Embed.php', + 'Twig_Node_Expression' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression.php', + 'Twig_Node_Expression_Array' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Array.php', + 'Twig_Node_Expression_AssignName' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/AssignName.php', + 'Twig_Node_Expression_Binary' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary.php', + 'Twig_Node_Expression_Binary_Add' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Add.php', + 'Twig_Node_Expression_Binary_And' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/And.php', + 'Twig_Node_Expression_Binary_BitwiseAnd' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php', + 'Twig_Node_Expression_Binary_BitwiseOr' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php', + 'Twig_Node_Expression_Binary_BitwiseXor' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php', + 'Twig_Node_Expression_Binary_Concat' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Concat.php', + 'Twig_Node_Expression_Binary_Div' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Div.php', + 'Twig_Node_Expression_Binary_EndsWith' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/EndsWith.php', + 'Twig_Node_Expression_Binary_Equal' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Equal.php', + 'Twig_Node_Expression_Binary_FloorDiv' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/FloorDiv.php', + 'Twig_Node_Expression_Binary_Greater' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Greater.php', + 'Twig_Node_Expression_Binary_GreaterEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php', + 'Twig_Node_Expression_Binary_In' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/In.php', + 'Twig_Node_Expression_Binary_Less' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Less.php', + 'Twig_Node_Expression_Binary_LessEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/LessEqual.php', + 'Twig_Node_Expression_Binary_Matches' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Matches.php', + 'Twig_Node_Expression_Binary_Mod' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Mod.php', + 'Twig_Node_Expression_Binary_Mul' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Mul.php', + 'Twig_Node_Expression_Binary_NotEqual' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/NotEqual.php', + 'Twig_Node_Expression_Binary_NotIn' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/NotIn.php', + 'Twig_Node_Expression_Binary_Or' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Or.php', + 'Twig_Node_Expression_Binary_Power' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Power.php', + 'Twig_Node_Expression_Binary_Range' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Range.php', + 'Twig_Node_Expression_Binary_StartsWith' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/StartsWith.php', + 'Twig_Node_Expression_Binary_Sub' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Binary/Sub.php', + 'Twig_Node_Expression_BlockReference' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/BlockReference.php', + 'Twig_Node_Expression_Call' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Call.php', + 'Twig_Node_Expression_Conditional' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Conditional.php', + 'Twig_Node_Expression_Constant' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Constant.php', + 'Twig_Node_Expression_ExtensionReference' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/ExtensionReference.php', + 'Twig_Node_Expression_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Filter.php', + 'Twig_Node_Expression_Filter_Default' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Filter/Default.php', + 'Twig_Node_Expression_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Function.php', + 'Twig_Node_Expression_GetAttr' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/GetAttr.php', + 'Twig_Node_Expression_MethodCall' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/MethodCall.php', + 'Twig_Node_Expression_Name' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Name.php', + 'Twig_Node_Expression_NullCoalesce' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/NullCoalesce.php', + 'Twig_Node_Expression_Parent' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Parent.php', + 'Twig_Node_Expression_TempName' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/TempName.php', + 'Twig_Node_Expression_Test' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test.php', + 'Twig_Node_Expression_Test_Constant' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Constant.php', + 'Twig_Node_Expression_Test_Defined' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Defined.php', + 'Twig_Node_Expression_Test_Divisibleby' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Divisibleby.php', + 'Twig_Node_Expression_Test_Even' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Even.php', + 'Twig_Node_Expression_Test_Null' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Null.php', + 'Twig_Node_Expression_Test_Odd' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Odd.php', + 'Twig_Node_Expression_Test_Sameas' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Test/Sameas.php', + 'Twig_Node_Expression_Unary' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary.php', + 'Twig_Node_Expression_Unary_Neg' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Neg.php', + 'Twig_Node_Expression_Unary_Not' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Not.php', + 'Twig_Node_Expression_Unary_Pos' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Expression/Unary/Pos.php', + 'Twig_Node_Flush' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Flush.php', + 'Twig_Node_For' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/For.php', + 'Twig_Node_ForLoop' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/ForLoop.php', + 'Twig_Node_If' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/If.php', + 'Twig_Node_Import' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Import.php', + 'Twig_Node_Include' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Include.php', + 'Twig_Node_Macro' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Macro.php', + 'Twig_Node_Module' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Module.php', + 'Twig_Node_Print' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Print.php', + 'Twig_Node_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Sandbox.php', + 'Twig_Node_SandboxedPrint' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/SandboxedPrint.php', + 'Twig_Node_Set' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Set.php', + 'Twig_Node_SetTemp' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/SetTemp.php', + 'Twig_Node_Spaceless' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Spaceless.php', + 'Twig_Node_Text' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/Text.php', + 'Twig_Node_With' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Node/With.php', + 'Twig_Parser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Parser.php', + 'Twig_ParserInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/ParserInterface.php', + 'Twig_Profiler_Dumper_Base' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Base.php', + 'Twig_Profiler_Dumper_Blackfire' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Blackfire.php', + 'Twig_Profiler_Dumper_Html' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Html.php', + 'Twig_Profiler_Dumper_Text' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Dumper/Text.php', + 'Twig_Profiler_NodeVisitor_Profiler' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/NodeVisitor/Profiler.php', + 'Twig_Profiler_Node_EnterProfile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Node/EnterProfile.php', + 'Twig_Profiler_Node_LeaveProfile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Node/LeaveProfile.php', + 'Twig_Profiler_Profile' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Profiler/Profile.php', + 'Twig_RuntimeLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/RuntimeLoaderInterface.php', + 'Twig_Sandbox_SecurityError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityError.php', + 'Twig_Sandbox_SecurityNotAllowedFilterError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFilterError.php', + 'Twig_Sandbox_SecurityNotAllowedFunctionError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedFunctionError.php', + 'Twig_Sandbox_SecurityNotAllowedMethodError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedMethodError.php', + 'Twig_Sandbox_SecurityNotAllowedPropertyError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedPropertyError.php', + 'Twig_Sandbox_SecurityNotAllowedTagError' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityNotAllowedTagError.php', + 'Twig_Sandbox_SecurityPolicy' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityPolicy.php', + 'Twig_Sandbox_SecurityPolicyInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Sandbox/SecurityPolicyInterface.php', + 'Twig_SimpleFilter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleFilter.php', + 'Twig_SimpleFunction' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleFunction.php', + 'Twig_SimpleTest' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SimpleTest.php', + 'Twig_Source' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Source.php', + 'Twig_SourceContextLoaderInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/SourceContextLoaderInterface.php', + 'Twig_Template' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Template.php', + 'Twig_TemplateInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TemplateInterface.php', + 'Twig_TemplateWrapper' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TemplateWrapper.php', + 'Twig_Test' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test.php', + 'Twig_TestCallableInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TestCallableInterface.php', + 'Twig_TestInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TestInterface.php', + 'Twig_Test_Function' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test/Function.php', + 'Twig_Test_IntegrationTestCase' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test/IntegrationTestCase.php', + 'Twig_Test_Method' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test/Method.php', + 'Twig_Test_Node' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test/Node.php', + 'Twig_Test_NodeTestCase' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Test/NodeTestCase.php', + 'Twig_Token' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Token.php', + 'Twig_TokenParser' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser.php', + 'Twig_TokenParserBroker' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParserBroker.php', + 'Twig_TokenParserBrokerInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParserBrokerInterface.php', + 'Twig_TokenParserInterface' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParserInterface.php', + 'Twig_TokenParser_AutoEscape' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/AutoEscape.php', + 'Twig_TokenParser_Block' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Block.php', + 'Twig_TokenParser_Deprecated' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Deprecated.php', + 'Twig_TokenParser_Do' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Do.php', + 'Twig_TokenParser_Embed' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Embed.php', + 'Twig_TokenParser_Extends' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Extends.php', + 'Twig_TokenParser_Filter' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Filter.php', + 'Twig_TokenParser_Flush' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Flush.php', + 'Twig_TokenParser_For' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/For.php', + 'Twig_TokenParser_From' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/From.php', + 'Twig_TokenParser_If' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/If.php', + 'Twig_TokenParser_Import' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Import.php', + 'Twig_TokenParser_Include' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Include.php', + 'Twig_TokenParser_Macro' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Macro.php', + 'Twig_TokenParser_Sandbox' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Sandbox.php', + 'Twig_TokenParser_Set' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Set.php', + 'Twig_TokenParser_Spaceless' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Spaceless.php', + 'Twig_TokenParser_Use' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/Use.php', + 'Twig_TokenParser_With' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenParser/With.php', + 'Twig_TokenStream' => __DIR__ . '/..' . '/twig/twig/lib/Twig/TokenStream.php', + 'Twig_Util_DeprecationCollector' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Util/DeprecationCollector.php', + 'Twig_Util_TemplateDirIterator' => __DIR__ . '/..' . '/twig/twig/lib/Twig/Util/TemplateDirIterator.php', + 'Zend\\Code\\Annotation\\AnnotationCollection' => __DIR__ . '/..' . '/zendframework/zend-code/src/Annotation/AnnotationCollection.php', + 'Zend\\Code\\Annotation\\AnnotationInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Annotation/AnnotationInterface.php', + 'Zend\\Code\\Annotation\\AnnotationManager' => __DIR__ . '/..' . '/zendframework/zend-code/src/Annotation/AnnotationManager.php', + 'Zend\\Code\\Annotation\\Parser\\DoctrineAnnotationParser' => __DIR__ . '/..' . '/zendframework/zend-code/src/Annotation/Parser/DoctrineAnnotationParser.php', + 'Zend\\Code\\Annotation\\Parser\\GenericAnnotationParser' => __DIR__ . '/..' . '/zendframework/zend-code/src/Annotation/Parser/GenericAnnotationParser.php', + 'Zend\\Code\\Annotation\\Parser\\ParserInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Annotation/Parser/ParserInterface.php', + 'Zend\\Code\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/zendframework/zend-code/src/Exception/BadMethodCallException.php', + 'Zend\\Code\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Exception/ExceptionInterface.php', + 'Zend\\Code\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-code/src/Exception/InvalidArgumentException.php', + 'Zend\\Code\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-code/src/Exception/RuntimeException.php', + 'Zend\\Code\\Generator\\AbstractGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/AbstractGenerator.php', + 'Zend\\Code\\Generator\\AbstractMemberGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/AbstractMemberGenerator.php', + 'Zend\\Code\\Generator\\BodyGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/BodyGenerator.php', + 'Zend\\Code\\Generator\\ClassGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/ClassGenerator.php', + 'Zend\\Code\\Generator\\DocBlockGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlockGenerator.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag.php', + 'Zend\\Code\\Generator\\DocBlock\\TagManager' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/TagManager.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\AbstractTypeableTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/AbstractTypeableTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\AuthorTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/AuthorTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\GenericTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/GenericTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\LicenseTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/LicenseTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\MethodTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/MethodTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\ParamTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/ParamTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\PropertyTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/PropertyTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\ReturnTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/ReturnTag.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\TagInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/TagInterface.php', + 'Zend\\Code\\Generator\\DocBlock\\Tag\\ThrowsTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/DocBlock/Tag/ThrowsTag.php', + 'Zend\\Code\\Generator\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/Exception/ExceptionInterface.php', + 'Zend\\Code\\Generator\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/Exception/InvalidArgumentException.php', + 'Zend\\Code\\Generator\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/Exception/RuntimeException.php', + 'Zend\\Code\\Generator\\FileGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/FileGenerator.php', + 'Zend\\Code\\Generator\\FileGeneratorRegistry' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/FileGeneratorRegistry.php', + 'Zend\\Code\\Generator\\GeneratorInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/GeneratorInterface.php', + 'Zend\\Code\\Generator\\MethodGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/MethodGenerator.php', + 'Zend\\Code\\Generator\\ParameterGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/ParameterGenerator.php', + 'Zend\\Code\\Generator\\PropertyGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/PropertyGenerator.php', + 'Zend\\Code\\Generator\\PropertyValueGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/PropertyValueGenerator.php', + 'Zend\\Code\\Generator\\TraitGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/TraitGenerator.php', + 'Zend\\Code\\Generator\\TraitUsageGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/TraitUsageGenerator.php', + 'Zend\\Code\\Generator\\TraitUsageInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/TraitUsageInterface.php', + 'Zend\\Code\\Generator\\ValueGenerator' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generator/ValueGenerator.php', + 'Zend\\Code\\Generic\\Prototype\\PrototypeClassFactory' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generic/Prototype/PrototypeClassFactory.php', + 'Zend\\Code\\Generic\\Prototype\\PrototypeGenericInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generic/Prototype/PrototypeGenericInterface.php', + 'Zend\\Code\\Generic\\Prototype\\PrototypeInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Generic/Prototype/PrototypeInterface.php', + 'Zend\\Code\\NameInformation' => __DIR__ . '/..' . '/zendframework/zend-code/src/NameInformation.php', + 'Zend\\Code\\Reflection\\ClassReflection' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/ClassReflection.php', + 'Zend\\Code\\Reflection\\DocBlockReflection' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlockReflection.php', + 'Zend\\Code\\Reflection\\DocBlock\\TagManager' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/TagManager.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\AuthorTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/AuthorTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\GenericTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/GenericTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\LicenseTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/LicenseTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\MethodTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/MethodTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\ParamTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/ParamTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\PhpDocTypedTagInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\PropertyTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/PropertyTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\ReturnTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/ReturnTag.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\TagInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/TagInterface.php', + 'Zend\\Code\\Reflection\\DocBlock\\Tag\\ThrowsTag' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/DocBlock/Tag/ThrowsTag.php', + 'Zend\\Code\\Reflection\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/Exception/BadMethodCallException.php', + 'Zend\\Code\\Reflection\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/Exception/ExceptionInterface.php', + 'Zend\\Code\\Reflection\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/Exception/InvalidArgumentException.php', + 'Zend\\Code\\Reflection\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/Exception/RuntimeException.php', + 'Zend\\Code\\Reflection\\FileReflection' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/FileReflection.php', + 'Zend\\Code\\Reflection\\FunctionReflection' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/FunctionReflection.php', + 'Zend\\Code\\Reflection\\MethodReflection' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/MethodReflection.php', + 'Zend\\Code\\Reflection\\ParameterReflection' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/ParameterReflection.php', + 'Zend\\Code\\Reflection\\PropertyReflection' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/PropertyReflection.php', + 'Zend\\Code\\Reflection\\ReflectionInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Reflection/ReflectionInterface.php', + 'Zend\\Code\\Scanner\\AggregateDirectoryScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/AggregateDirectoryScanner.php', + 'Zend\\Code\\Scanner\\AnnotationScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/AnnotationScanner.php', + 'Zend\\Code\\Scanner\\CachingFileScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/CachingFileScanner.php', + 'Zend\\Code\\Scanner\\ClassScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/ClassScanner.php', + 'Zend\\Code\\Scanner\\ConstantScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/ConstantScanner.php', + 'Zend\\Code\\Scanner\\DerivedClassScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/DerivedClassScanner.php', + 'Zend\\Code\\Scanner\\DirectoryScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/DirectoryScanner.php', + 'Zend\\Code\\Scanner\\DocBlockScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/DocBlockScanner.php', + 'Zend\\Code\\Scanner\\FileScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/FileScanner.php', + 'Zend\\Code\\Scanner\\FunctionScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/FunctionScanner.php', + 'Zend\\Code\\Scanner\\MethodScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/MethodScanner.php', + 'Zend\\Code\\Scanner\\ParameterScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/ParameterScanner.php', + 'Zend\\Code\\Scanner\\PropertyScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/PropertyScanner.php', + 'Zend\\Code\\Scanner\\ScannerInterface' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/ScannerInterface.php', + 'Zend\\Code\\Scanner\\TokenArrayScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/TokenArrayScanner.php', + 'Zend\\Code\\Scanner\\Util' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/Util.php', + 'Zend\\Code\\Scanner\\ValueScanner' => __DIR__ . '/..' . '/zendframework/zend-code/src/Scanner/ValueScanner.php', + 'Zend\\EventManager\\AbstractListenerAggregate' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/AbstractListenerAggregate.php', + 'Zend\\EventManager\\Event' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/Event.php', + 'Zend\\EventManager\\EventInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/EventInterface.php', + 'Zend\\EventManager\\EventManager' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/EventManager.php', + 'Zend\\EventManager\\EventManagerAwareInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/EventManagerAwareInterface.php', + 'Zend\\EventManager\\EventManagerAwareTrait' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/EventManagerAwareTrait.php', + 'Zend\\EventManager\\EventManagerInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/EventManagerInterface.php', + 'Zend\\EventManager\\EventsCapableInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/EventsCapableInterface.php', + 'Zend\\EventManager\\Exception\\DomainException' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/Exception/DomainException.php', + 'Zend\\EventManager\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/Exception/ExceptionInterface.php', + 'Zend\\EventManager\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/Exception/InvalidArgumentException.php', + 'Zend\\EventManager\\Exception\\InvalidCallbackException' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/Exception/InvalidCallbackException.php', + 'Zend\\EventManager\\FilterChain' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/FilterChain.php', + 'Zend\\EventManager\\Filter\\FilterInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/Filter/FilterInterface.php', + 'Zend\\EventManager\\Filter\\FilterIterator' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/Filter/FilterIterator.php', + 'Zend\\EventManager\\GlobalEventManager' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/GlobalEventManager.php', + 'Zend\\EventManager\\ListenerAggregateInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/ListenerAggregateInterface.php', + 'Zend\\EventManager\\ListenerAggregateTrait' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/ListenerAggregateTrait.php', + 'Zend\\EventManager\\ProvidesEvents' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/ProvidesEvents.php', + 'Zend\\EventManager\\ResponseCollection' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/ResponseCollection.php', + 'Zend\\EventManager\\SharedEventAggregateAwareInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/SharedEventAggregateAwareInterface.php', + 'Zend\\EventManager\\SharedEventManager' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/SharedEventManager.php', + 'Zend\\EventManager\\SharedEventManagerAwareInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/SharedEventManagerAwareInterface.php', + 'Zend\\EventManager\\SharedEventManagerInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/SharedEventManagerInterface.php', + 'Zend\\EventManager\\SharedListenerAggregateInterface' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/SharedListenerAggregateInterface.php', + 'Zend\\EventManager\\StaticEventManager' => __DIR__ . '/..' . '/zendframework/zend-eventmanager/src/StaticEventManager.php', + 'Zend\\Stdlib\\AbstractOptions' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/AbstractOptions.php', + 'Zend\\Stdlib\\ArrayObject' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayObject.php', + 'Zend\\Stdlib\\ArraySerializableInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArraySerializableInterface.php', + 'Zend\\Stdlib\\ArrayStack' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayStack.php', + 'Zend\\Stdlib\\ArrayUtils' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayUtils.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeRemoveKey' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayUtils/MergeRemoveKey.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeReplaceKey' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKey.php', + 'Zend\\Stdlib\\ArrayUtils\\MergeReplaceKeyInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php', + 'Zend\\Stdlib\\CallbackHandler' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/CallbackHandler.php', + 'Zend\\Stdlib\\DateTime' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/DateTime.php', + 'Zend\\Stdlib\\DispatchableInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/DispatchableInterface.php', + 'Zend\\Stdlib\\ErrorHandler' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ErrorHandler.php', + 'Zend\\Stdlib\\Exception\\BadMethodCallException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/BadMethodCallException.php', + 'Zend\\Stdlib\\Exception\\DomainException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/DomainException.php', + 'Zend\\Stdlib\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/ExceptionInterface.php', + 'Zend\\Stdlib\\Exception\\ExtensionNotLoadedException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/ExtensionNotLoadedException.php', + 'Zend\\Stdlib\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/InvalidArgumentException.php', + 'Zend\\Stdlib\\Exception\\InvalidCallbackException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/InvalidCallbackException.php', + 'Zend\\Stdlib\\Exception\\LogicException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/LogicException.php', + 'Zend\\Stdlib\\Exception\\RuntimeException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Exception/RuntimeException.php', + 'Zend\\Stdlib\\Extractor\\ExtractionInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Extractor/ExtractionInterface.php', + 'Zend\\Stdlib\\Glob' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Glob.php', + 'Zend\\Stdlib\\Guard\\AllGuardsTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/AllGuardsTrait.php', + 'Zend\\Stdlib\\Guard\\ArrayOrTraversableGuardTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/ArrayOrTraversableGuardTrait.php', + 'Zend\\Stdlib\\Guard\\EmptyGuardTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/EmptyGuardTrait.php', + 'Zend\\Stdlib\\Guard\\GuardUtils' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/GuardUtils.php', + 'Zend\\Stdlib\\Guard\\NullGuardTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Guard/NullGuardTrait.php', + 'Zend\\Stdlib\\Hydrator\\AbstractHydrator' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/AbstractHydrator.php', + 'Zend\\Stdlib\\Hydrator\\Aggregate\\AggregateHydrator' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Aggregate/AggregateHydrator.php', + 'Zend\\Stdlib\\Hydrator\\Aggregate\\ExtractEvent' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Aggregate/ExtractEvent.php', + 'Zend\\Stdlib\\Hydrator\\Aggregate\\HydrateEvent' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydrateEvent.php', + 'Zend\\Stdlib\\Hydrator\\Aggregate\\HydratorListener' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydratorListener.php', + 'Zend\\Stdlib\\Hydrator\\ArraySerializable' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/ArraySerializable.php', + 'Zend\\Stdlib\\Hydrator\\ClassMethods' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/ClassMethods.php', + 'Zend\\Stdlib\\Hydrator\\DelegatingHydrator' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/DelegatingHydrator.php', + 'Zend\\Stdlib\\Hydrator\\DelegatingHydratorFactory' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/DelegatingHydratorFactory.php', + 'Zend\\Stdlib\\Hydrator\\FilterEnabledInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/FilterEnabledInterface.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\FilterComposite' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/FilterComposite.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\FilterInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/FilterInterface.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\FilterProviderInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/FilterProviderInterface.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\GetFilter' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/GetFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\HasFilter' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/HasFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\IsFilter' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/IsFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\MethodMatchFilter' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/MethodMatchFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\NumberOfParameterFilter' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/NumberOfParameterFilter.php', + 'Zend\\Stdlib\\Hydrator\\Filter\\OptionalParametersFilter' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Filter/OptionalParametersFilter.php', + 'Zend\\Stdlib\\Hydrator\\HydrationInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/HydrationInterface.php', + 'Zend\\Stdlib\\Hydrator\\HydratorAwareInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/HydratorAwareInterface.php', + 'Zend\\Stdlib\\Hydrator\\HydratorAwareTrait' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/HydratorAwareTrait.php', + 'Zend\\Stdlib\\Hydrator\\HydratorInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/HydratorInterface.php', + 'Zend\\Stdlib\\Hydrator\\HydratorOptionsInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/HydratorOptionsInterface.php', + 'Zend\\Stdlib\\Hydrator\\HydratorPluginManager' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/HydratorPluginManager.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategyEnabledInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategyEnabledInterface.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\ArrayMapNamingStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/ArrayMapNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\CompositeNamingStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/CompositeNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\IdentityNamingStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/IdentityNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\MapNamingStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/MapNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\NamingStrategyInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/NamingStrategyInterface.php', + 'Zend\\Stdlib\\Hydrator\\NamingStrategy\\UnderscoreNamingStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/UnderscoreNamingStrategy.php', + 'Zend\\Stdlib\\Hydrator\\ObjectProperty' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/ObjectProperty.php', + 'Zend\\Stdlib\\Hydrator\\Reflection' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Reflection.php', + 'Zend\\Stdlib\\Hydrator\\StrategyEnabledInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/StrategyEnabledInterface.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\BooleanStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/BooleanStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\ClosureStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/ClosureStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\DateTimeFormatterStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/DateTimeFormatterStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\DefaultStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/DefaultStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/Exception/ExceptionInterface.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/Exception/InvalidArgumentException.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\ExplodeStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/ExplodeStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\SerializableStrategy' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/SerializableStrategy.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\StrategyChain' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyChain.php', + 'Zend\\Stdlib\\Hydrator\\Strategy\\StrategyInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyInterface.php', + 'Zend\\Stdlib\\InitializableInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/InitializableInterface.php', + 'Zend\\Stdlib\\JsonSerializable' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/JsonSerializable.php', + 'Zend\\Stdlib\\JsonSerializable\\PhpLegacyCompatibility' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/JsonSerializable/PhpLegacyCompatibility.php', + 'Zend\\Stdlib\\Message' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Message.php', + 'Zend\\Stdlib\\MessageInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/MessageInterface.php', + 'Zend\\Stdlib\\ParameterObjectInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ParameterObjectInterface.php', + 'Zend\\Stdlib\\Parameters' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Parameters.php', + 'Zend\\Stdlib\\ParametersInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ParametersInterface.php', + 'Zend\\Stdlib\\PriorityList' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/PriorityList.php', + 'Zend\\Stdlib\\PriorityQueue' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/PriorityQueue.php', + 'Zend\\Stdlib\\Request' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Request.php', + 'Zend\\Stdlib\\RequestInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/RequestInterface.php', + 'Zend\\Stdlib\\Response' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/Response.php', + 'Zend\\Stdlib\\ResponseInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/ResponseInterface.php', + 'Zend\\Stdlib\\SplPriorityQueue' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/SplPriorityQueue.php', + 'Zend\\Stdlib\\SplQueue' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/SplQueue.php', + 'Zend\\Stdlib\\SplStack' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/SplStack.php', + 'Zend\\Stdlib\\StringUtils' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringUtils.php', + 'Zend\\Stdlib\\StringWrapper\\AbstractStringWrapper' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/AbstractStringWrapper.php', + 'Zend\\Stdlib\\StringWrapper\\Iconv' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/Iconv.php', + 'Zend\\Stdlib\\StringWrapper\\Intl' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/Intl.php', + 'Zend\\Stdlib\\StringWrapper\\MbString' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/MbString.php', + 'Zend\\Stdlib\\StringWrapper\\Native' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/Native.php', + 'Zend\\Stdlib\\StringWrapper\\StringWrapperInterface' => __DIR__ . '/..' . '/zendframework/zend-stdlib/src/StringWrapper/StringWrapperInterface.php', + 'bantu\\IniGetWrapper\\IniGetWrapper' => __DIR__ . '/..' . '/bantu/ini-get-wrapper/src/IniGetWrapper.php', + 's9e\\TextFormatter\\Bundle' => __DIR__ . '/..' . '/s9e/text-formatter/src/Bundle.php', + 's9e\\TextFormatter\\Bundles\\Fatdown' => __DIR__ . '/..' . '/s9e/text-formatter/src/Bundles/Fatdown.php', + 's9e\\TextFormatter\\Bundles\\Fatdown\\Renderer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Bundles/Fatdown/Renderer.php', + 's9e\\TextFormatter\\Bundles\\Forum' => __DIR__ . '/..' . '/s9e/text-formatter/src/Bundles/Forum.php', + 's9e\\TextFormatter\\Bundles\\Forum\\Renderer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Bundles/Forum/Renderer.php', + 's9e\\TextFormatter\\Bundles\\MediaPack' => __DIR__ . '/..' . '/s9e/text-formatter/src/Bundles/MediaPack.php', + 's9e\\TextFormatter\\Bundles\\MediaPack\\Renderer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Bundles/MediaPack/Renderer.php', + 's9e\\TextFormatter\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Bundle' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Bundle.php', + 's9e\\TextFormatter\\Configurator\\BundleGenerator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Bundles\\Fatdown' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Bundles/Fatdown.php', + 's9e\\TextFormatter\\Configurator\\Bundles\\Forum' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Bundles/Forum.php', + 's9e\\TextFormatter\\Configurator\\Bundles\\MediaPack' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Bundles/MediaPack.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributeCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributeFilterChain' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributeFilterCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributeList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Collections/AttributeList.php', + 's9e\\TextFormatter\\Configurator\\Collections\\AttributePreprocessorCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\Collection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\FilterChain' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\HostnameList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\MinifierList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Collections/MinifierList.php', + 's9e\\TextFormatter\\Configurator\\Collections\\NormalizedCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\NormalizedList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\PluginCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\RulesGeneratorList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\Ruleset' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\SchemeList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TagCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TagFilterChain' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TagList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Collections/TagList.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TemplateCheckList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TemplateNormalizationList' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Collections\\TemplateParameterCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\ConfigProvider' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Exceptions\\UnsafeTemplateException' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Exceptions/UnsafeTemplateException.php', + 's9e\\TextFormatter\\Configurator\\FilterableConfigValue' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\AVTHelper' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\CharacterClassBuilder' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\ConfigHelper' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\ContextSafeness' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Helpers/ContextSafeness.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\ElementInspector' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\NodeLocator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\RegexpBuilder' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\RegexpParser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Helpers/RegexpParser.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\RulesHelper' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateHelper' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateInspector' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateLoader' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateModifier' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Helpers/TemplateModifier.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser\\IRProcessor' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser\\Normalizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser\\Optimizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\TemplateParser\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Helpers\\XPathHelper' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\Attribute' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\AlnumFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/AlnumFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\ChoiceFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/ChoiceFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\ColorFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/ColorFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\EmailFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/EmailFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\FalseFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/FalseFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\FloatFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/FloatFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\FontfamilyFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/FontfamilyFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\HashmapFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/HashmapFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\IdentifierFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/IdentifierFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\IntFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/IntFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\IpFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/IpFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\IpportFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/IpportFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\Ipv4Filter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/Ipv4Filter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\Ipv6Filter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/Ipv6Filter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\MapFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/MapFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\NumberFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/NumberFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\RangeFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/RangeFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\RegexpFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/RegexpFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\SimpletextFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/SimpletextFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\TimestampFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/TimestampFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\UintFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributeFilters/UintFilter.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\UrlFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\AttributePreprocessor' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/AttributePreprocessor.php', + 's9e\\TextFormatter\\Configurator\\Items\\Filter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\ProgrammableCallback' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\Regexp' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\Tag' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\TagFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\Template' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Items\\TemplateDocument' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/TemplateDocument.php', + 's9e\\TextFormatter\\Configurator\\Items\\UnsafeTemplate' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Items/UnsafeTemplate.php', + 's9e\\TextFormatter\\Configurator\\JavaScript' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\CallbackGenerator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/CallbackGenerator.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Code' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\ConfigOptimizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/ConfigOptimizer.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\ConfigValue' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/ConfigValue.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Dictionary' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/Dictionary.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Encoder' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/Encoder.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\FunctionProvider' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\HintGenerator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/HintGenerator.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifier' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/Minifier.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\ClosureCompilerApplication' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/ClosureCompilerApplication.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\ClosureCompilerService' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/ClosureCompilerService.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\FirstAvailable' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/FirstAvailable.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\MatthiasMullieMinify' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/MatthiasMullieMinify.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\Minifiers\\Noop' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/Minifiers/Noop.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\OnlineMinifier' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/OnlineMinifier.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\RegexpConvertor' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/RegexpConvertor.php', + 's9e\\TextFormatter\\Configurator\\JavaScript\\StylesheetCompressor' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/JavaScript/StylesheetCompressor.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\AbstractOptimizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\BranchOutputOptimizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\ControlStructuresOptimizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\Optimizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\Quick' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\Serializer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\SwitchStatement' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\PHP\\XPathConvertor' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\Unformatted' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/RendererGenerators/Unformatted.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\XSLT' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/RendererGenerators/XSLT.php', + 's9e\\TextFormatter\\Configurator\\RendererGenerators\\XSLT\\Optimizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/RendererGenerators/XSLT/Optimizer.php', + 's9e\\TextFormatter\\Configurator\\Rendering' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\AllowAll' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/RulesGenerators/AllowAll.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\AutoCloseIfVoid' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\AutoReopenFormattingElements' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\BlockElementsCloseFormattingElements' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\BlockElementsFosterFormattingElements' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\DisableAutoLineBreaksIfNewLinesArePreserved' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\EnforceContentModels' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\EnforceOptionalEndTags' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\IgnoreTagsInCode' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\IgnoreTextIfDisallowed' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\IgnoreWhitespaceAroundBlockElements' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\Interfaces\\BooleanRulesGenerator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\Interfaces\\TargetedRulesGenerator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\ManageParagraphs' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/RulesGenerators/ManageParagraphs.php', + 's9e\\TextFormatter\\Configurator\\RulesGenerators\\TrimFirstLineInCodeBlocks' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateCheck' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecker' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\AbstractDynamicContentCheck' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\AbstractFlashRestriction' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowAttributeSets' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowCopy' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowDisableOutputEscaping' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowDynamicAttributeNames' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowDynamicElementNames' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowElement' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateChecks/DisallowElement.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowElementNS' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowFlashFullScreen' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateChecks/DisallowFlashFullScreen.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowNodeByXPath' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateChecks/DisallowNodeByXPath.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowObjectParamsWithGeneratedName' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowPHPTags' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowUnsafeCopyOf' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowUnsafeDynamicCSS' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowUnsafeDynamicJS' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowUnsafeDynamicURL' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\DisallowXPathFunction' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\RestrictFlashNetworking' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateChecks/RestrictFlashNetworking.php', + 's9e\\TextFormatter\\Configurator\\TemplateChecks\\RestrictFlashScriptAccess' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\AbstractChooseOptimization' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\AbstractConstantFolding' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\AbstractNormalization' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\ConvertCurlyExpressionsInText' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/ConvertCurlyExpressionsInText.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\Custom' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/Custom.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\EnforceHTMLOmittedEndTags' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\FixUnescapedCurlyBracesInHtmlAttributes' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\FoldArithmeticConstants' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\FoldConstantXPathExpressions' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineAttributes' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineCDATA' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineElements' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineInferredValues' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineTextElements' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\InlineXPathLiterals' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\MergeConsecutiveCopyOf' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/MergeConsecutiveCopyOf.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\MergeIdenticalConditionalBranches' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/MergeIdenticalConditionalBranches.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\MinifyInlineCSS' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\MinifyXPathExpressions' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\NormalizeAttributeNames' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\NormalizeElementNames' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\NormalizeUrls' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeChoose' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeChooseText' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeConditionalAttributes' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeConditionalValueOf' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\OptimizeNestedConditionals' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/OptimizeNestedConditionals.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\PreserveSingleSpaces' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\RemoveComments' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\RemoveInterElementWhitespace' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\RemoveLivePreviewAttributes' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\SetRelNoreferrerOnTargetedLinks' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\SortAttributesByName' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/SortAttributesByName.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\TransposeComments' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/TemplateNormalizations/TransposeComments.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\UninlineAttributes' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\TemplateNormalizer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Traits\\CollectionProxy' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Traits\\Configurable' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Traits\\TemplateSafeness' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\UrlConfig' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Validators\\AttributeName' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Validators\\TagName' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator.php', + 's9e\\TextFormatter\\Configurator\\Validators\\TemplateParameterName' => __DIR__ . '/..' . '/s9e/text-formatter/src/Configurator/Validators/TemplateParameterName.php', + 's9e\\TextFormatter\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\EmailFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/EmailFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\FalseFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/FalseFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\HashmapFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/HashmapFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\MapFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/MapFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\NetworkFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/NetworkFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/NumericFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\RegexpFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/RegexpFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\TimestampFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/TimestampFilter.php', + 's9e\\TextFormatter\\Parser\\AttributeFilters\\UrlFilter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/AttributeFilters/UrlFilter.php', + 's9e\\TextFormatter\\Parser\\FilterProcessing' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/FilterProcessing.php', + 's9e\\TextFormatter\\Parser\\Logger' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/Logger.php', + 's9e\\TextFormatter\\Parser\\Tag' => __DIR__ . '/..' . '/s9e/text-formatter/src/Parser/Tag.php', + 's9e\\TextFormatter\\Plugins\\Autoemail\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Autoemail/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Autoemail\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Autoemail/Parser.php', + 's9e\\TextFormatter\\Plugins\\Autoimage\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Autoimage/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Autoimage\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Autoimage/Parser.php', + 's9e\\TextFormatter\\Plugins\\Autolink\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Autolink/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Autolink\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Autolink/Parser.php', + 's9e\\TextFormatter\\Plugins\\Autovideo\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Autovideo/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Autovideo\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Autovideo/Parser.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\BBCode' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/BBCode.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\BBCodeCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/BBCodeCollection.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\BBCodeMonkey' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/BBCodeMonkey.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\Repository' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/Repository.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Configurator\\RepositoryCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/BBCodes/Configurator/RepositoryCollection.php', + 's9e\\TextFormatter\\Plugins\\BBCodes\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/BBCodes/Parser.php', + 's9e\\TextFormatter\\Plugins\\Censor\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Censor/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Censor\\Helper' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Censor/Helper.php', + 's9e\\TextFormatter\\Plugins\\Censor\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Censor/Parser.php', + 's9e\\TextFormatter\\Plugins\\ConfiguratorBase' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/ConfiguratorBase.php', + 's9e\\TextFormatter\\Plugins\\Emoji\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Emoji/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Emoji\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Emoji/Parser.php', + 's9e\\TextFormatter\\Plugins\\Emoticons\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Emoticons/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Emoticons\\Configurator\\EmoticonCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Emoticons/Configurator/EmoticonCollection.php', + 's9e\\TextFormatter\\Plugins\\Emoticons\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Emoticons/Parser.php', + 's9e\\TextFormatter\\Plugins\\Escaper\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Escaper/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Escaper\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Escaper/Parser.php', + 's9e\\TextFormatter\\Plugins\\FancyPants\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/FancyPants/Configurator.php', + 's9e\\TextFormatter\\Plugins\\FancyPants\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/FancyPants/Parser.php', + 's9e\\TextFormatter\\Plugins\\HTMLComments\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/HTMLComments/Configurator.php', + 's9e\\TextFormatter\\Plugins\\HTMLComments\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/HTMLComments/Parser.php', + 's9e\\TextFormatter\\Plugins\\HTMLElements\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/HTMLElements/Configurator.php', + 's9e\\TextFormatter\\Plugins\\HTMLElements\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/HTMLElements/Parser.php', + 's9e\\TextFormatter\\Plugins\\HTMLEntities\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/HTMLEntities/Configurator.php', + 's9e\\TextFormatter\\Plugins\\HTMLEntities\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/HTMLEntities/Parser.php', + 's9e\\TextFormatter\\Plugins\\Keywords\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Keywords/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Keywords\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Keywords/Parser.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\LinkAttributesSetter' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/LinkAttributesSetter.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\ParsedText' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/ParsedText.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\AbstractPass' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/AbstractPass.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\AbstractScript' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/AbstractScript.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Blocks' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Blocks.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Emphasis' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Emphasis.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\ForcedLineBreaks' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/ForcedLineBreaks.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Images' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Images.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\InlineCode' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/InlineCode.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\LinkReferences' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/LinkReferences.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Links' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Links.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Strikethrough' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Strikethrough.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Subscript' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Subscript.php', + 's9e\\TextFormatter\\Plugins\\Litedown\\Parser\\Passes\\Superscript' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Litedown/Parser/Passes/Superscript.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\Collections\\CachedDefinitionCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/Collections/CachedDefinitionCollection.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\Collections\\SiteDefinitionCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/Collections/SiteDefinitionCollection.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\Collections\\XmlFileDefinitionCollection' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/Collections/XmlFileDefinitionCollection.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateBuilder' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateBuilder.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateGenerator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateGenerator.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateGenerators\\Choose' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateGenerators/Choose.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateGenerators\\Flash' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateGenerators/Flash.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Configurator\\TemplateGenerators\\Iframe' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Configurator/TemplateGenerators/Iframe.php', + 's9e\\TextFormatter\\Plugins\\MediaEmbed\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/MediaEmbed/Parser.php', + 's9e\\TextFormatter\\Plugins\\ParserBase' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/ParserBase.php', + 's9e\\TextFormatter\\Plugins\\PipeTables\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/PipeTables/Configurator.php', + 's9e\\TextFormatter\\Plugins\\PipeTables\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/PipeTables/Parser.php', + 's9e\\TextFormatter\\Plugins\\Preg\\Configurator' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Preg/Configurator.php', + 's9e\\TextFormatter\\Plugins\\Preg\\Parser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Plugins/Preg/Parser.php', + 's9e\\TextFormatter\\Renderer' => __DIR__ . '/..' . '/s9e/text-formatter/src/Renderer.php', + 's9e\\TextFormatter\\Renderers\\PHP' => __DIR__ . '/..' . '/s9e/text-formatter/src/Renderers/PHP.php', + 's9e\\TextFormatter\\Renderers\\Unformatted' => __DIR__ . '/..' . '/s9e/text-formatter/src/Renderers/Unformatted.php', + 's9e\\TextFormatter\\Renderers\\XSLT' => __DIR__ . '/..' . '/s9e/text-formatter/src/Renderers/XSLT.php', + 's9e\\TextFormatter\\Unparser' => __DIR__ . '/..' . '/s9e/text-formatter/src/Unparser.php', + 's9e\\TextFormatter\\Utils' => __DIR__ . '/..' . '/s9e/text-formatter/src/Utils.php', + 's9e\\TextFormatter\\Utils\\Http' => __DIR__ . '/..' . '/s9e/text-formatter/src/Utils/Http.php', + 's9e\\TextFormatter\\Utils\\Http\\Client' => __DIR__ . '/..' . '/s9e/text-formatter/src/Utils/Http/Client.php', + 's9e\\TextFormatter\\Utils\\Http\\Clients\\Cached' => __DIR__ . '/..' . '/s9e/text-formatter/src/Utils/Http/Clients/Cached.php', + 's9e\\TextFormatter\\Utils\\Http\\Clients\\Curl' => __DIR__ . '/..' . '/s9e/text-formatter/src/Utils/Http/Clients/Curl.php', + 's9e\\TextFormatter\\Utils\\Http\\Clients\\Native' => __DIR__ . '/..' . '/s9e/text-formatter/src/Utils/Http/Clients/Native.php', + 's9e\\TextFormatter\\Utils\\XPath' => __DIR__ . '/..' . '/s9e/text-formatter/src/Utils/XPath.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit55438ae291a1dfe4cf962a21b03284ed::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit55438ae291a1dfe4cf962a21b03284ed::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit55438ae291a1dfe4cf962a21b03284ed::$prefixesPsr0; + $loader->classMap = ComposerStaticInit55438ae291a1dfe4cf962a21b03284ed::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..5916772 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,2040 @@ +[ + { + "name": "bantu/ini-get-wrapper", + "version": "v1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/bantuXorg/php-ini-get-wrapper.git", + "reference": "4770c7feab370c62e23db4f31c112b7c6d90aee2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bantuXorg/php-ini-get-wrapper/zipball/4770c7feab370c62e23db4f31c112b7c6d90aee2", + "reference": "4770c7feab370c62e23db4f31c112b7c6d90aee2", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*" + }, + "time": "2014-09-15T13:12:35+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "bantu\\IniGetWrapper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Convenience wrapper around ini_get()" + }, + { + "name": "google/recaptcha", + "version": "1.1.2", + "version_normalized": "1.1.2.0", + "source": { + "type": "git", + "url": "https://github.com/google/recaptcha.git", + "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/google/recaptcha/zipball/2b7e00566afca82a38a1d3adb8e42c118006296e", + "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "time": "2015-09-02T17:23:59+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "ReCaptcha\\": "src/ReCaptcha" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Client library for reCAPTCHA, a free service that protect websites from spam and abuse.", + "homepage": "http://www.google.com/recaptcha/", + "keywords": [ + "Abuse", + "captcha", + "recaptcha", + "spam" + ] + }, + { + "name": "guzzlehttp/guzzle", + "version": "5.3.3", + "version_normalized": "5.3.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "93bbdb30d59be6cd9839495306c65f2907370eb9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/93bbdb30d59be6cd9839495306c65f2907370eb9", + "reference": "93bbdb30d59be6cd9839495306c65f2907370eb9", + "shasum": "" + }, + "require": { + "guzzlehttp/ringphp": "^1.1", + "php": ">=5.4.0", + "react/promise": "^2.2" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0" + }, + "time": "2018-07-31T13:33:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ] + }, + { + "name": "guzzlehttp/ringphp", + "version": "1.1.1", + "version_normalized": "1.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/RingPHP.git", + "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/5e2a174052995663dd68e6b5ad838afd47dd615b", + "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b", + "shasum": "" + }, + "require": { + "guzzlehttp/streams": "~3.0", + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "time": "2018-07-31T13:22:33+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function." + }, + { + "name": "guzzlehttp/streams", + "version": "3.0.0", + "version_normalized": "3.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/streams.git", + "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "time": "2014-10-12T19:18:40+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "GuzzleHttp\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Provides a simple abstraction over streams of data", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "stream" + ] + }, + { + "name": "ircmaxell/password-compat", + "version": "v1.0.4", + "version_normalized": "1.0.4.0", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "time": "2014-11-20T16:49:30+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ] + }, + { + "name": "lusitanian/oauth", + "version": "v0.8.11", + "version_normalized": "0.8.11.0", + "source": { + "type": "git", + "url": "https://github.com/Lusitanian/PHPoAuthLib.git", + "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/fc11a53db4b66da555a6a11fce294f574a8374f9", + "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "predis/predis": "0.8.*@dev", + "squizlabs/php_codesniffer": "2.*", + "symfony/http-foundation": "~2.1" + }, + "suggest": { + "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.", + "predis/predis": "Allows using the Redis storage backend.", + "symfony/http-foundation": "Allows using the Symfony Session storage backend." + }, + "time": "2016-07-12T22:15:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "OAuth": "src", + "OAuth\\Unit": "tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Desberg", + "email": "david@daviddesberg.com" + }, + { + "name": "Elliot Chance", + "email": "elliotchance@gmail.com" + }, + { + "name": "Pieter Hordijk", + "email": "info@pieterhordijk.com" + } + ], + "description": "PHP 5.3+ oAuth 1/2 Library", + "keywords": [ + "Authentication", + "authorization", + "oauth", + "security" + ] + }, + { + "name": "marc1706/fast-image-size", + "version": "v1.1.4", + "version_normalized": "1.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/marc1706/fast-image-size.git", + "reference": "c4ded0223a4e49ae45a2183a69f6afac5baf7250" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/marc1706/fast-image-size/zipball/c4ded0223a4e49ae45a2183a69f6afac5baf7250", + "reference": "c4ded0223a4e49ae45a2183a69f6afac5baf7250", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "time": "2017-10-23T18:52:01+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "FastImageSize\\": "lib", + "FastImageSize\\tests\\": "tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marc Alexander", + "email": "admin@m-a-styles.de", + "homepage": "https://www.m-a-styles.de", + "role": "Developer" + } + ], + "description": "fast-image-size is a PHP library that does almost everything PHP's getimagesize() does but without the large overhead of downloading the complete file.", + "homepage": "https://www.m-a-styles.de", + "keywords": [ + "fast", + "getimagesize", + "image", + "imagesize", + "php", + "size" + ] + }, + { + "name": "ocramius/proxy-manager", + "version": "1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/ProxyManager.git", + "reference": "57e9272ec0e8deccf09421596e0e2252df440e11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/ProxyManager/zipball/57e9272ec0e8deccf09421596e0e2252df440e11", + "reference": "57e9272ec0e8deccf09421596e0e2252df440e11", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "zendframework/zend-code": ">2.2.5,<3.0" + }, + "require-dev": { + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "1.5.*" + }, + "suggest": { + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", + "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", + "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)", + "zendframework/zend-stdlib": "To use the hydrator proxy", + "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)" + }, + "time": "2015-08-09T04:28:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "ProxyManager\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "homepage": "https://github.com/Ocramius/ProxyManager", + "keywords": [ + "aop", + "lazy loading", + "proxy", + "proxy pattern", + "service proxies" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v1.4.3", + "version_normalized": "1.4.3.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/9b3899e3c3ddde89016f576edb8c489708ad64cd", + "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2017-03-13T16:22:52+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ] + }, + { + "name": "patchwork/utf8", + "version": "v1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/tchwork/utf8.git", + "reference": "30ec6451aec7d2536f0af8fe535f70c764f2c47a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tchwork/utf8/zipball/30ec6451aec7d2536f0af8fe535f70c764f2c47a", + "reference": "30ec6451aec7d2536f0af8fe535f70c764f2c47a", + "shasum": "" + }, + "require": { + "lib-pcre": ">=7.3", + "php": ">=5.3.0" + }, + "suggest": { + "ext-iconv": "Use iconv for best performance", + "ext-intl": "Use Intl for best performance", + "ext-mbstring": "Use Mbstring for best performance", + "ext-wfio": "Use WFIO for UTF-8 filesystem access on Windows" + }, + "time": "2016-05-18T13:57:10+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Patchwork\\": "src/Patchwork/" + }, + "classmap": [ + "src/Normalizer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "(Apache-2.0 or GPL-2.0)" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + } + ], + "description": "Portable and performant UTF-8, Unicode and Grapheme Clusters for PHP", + "homepage": "https://github.com/tchwork/utf8", + "keywords": [ + "grapheme", + "i18n", + "unicode", + "utf-8", + "utf8" + ] + }, + { + "name": "psr/log", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2018-11-20T15:27:04+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "react/promise", + "version": "v2.7.1", + "version_normalized": "2.7.1.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "time": "2019-01-07T21:25:54+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ] + }, + { + "name": "s9e/text-formatter", + "version": "1.4.2", + "version_normalized": "1.4.2.0", + "source": { + "type": "git", + "url": "https://github.com/s9e/TextFormatter.git", + "reference": "dc7efff70b67b9cee00881ad3bef0a1da076b31e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/s9e/TextFormatter/zipball/dc7efff70b67b9cee00881ad3bef0a1da076b31e", + "reference": "dc7efff70b67b9cee00881ad3bef0a1da076b31e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "lib-pcre": ">=7.2", + "php": ">=5.4.7" + }, + "require-dev": { + "matthiasmullie/minify": "*", + "php-coveralls/php-coveralls": "*", + "s9e/regexp-builder": "1.*" + }, + "suggest": { + "ext-curl": "Improves the performance of the MediaEmbed plugin and some JavaScript minifiers", + "ext-intl": "Allows international URLs to be accepted by the URL filter", + "ext-json": "Enables the generation of a JavaScript parser", + "ext-mbstring": "Improves the performance of the PHP renderer", + "ext-tokenizer": "Improves the performance of the PHP renderer", + "ext-xsl": "Enables the XSLT renderer", + "ext-zlib": "Enables gzip compression when scraping content via the MediaEmbed plugin" + }, + "time": "2019-03-27T14:19:41+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "s9e\\TextFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Multi-purpose text formatting and markup library. Plugins offer support for BBCodes, Markdown, emoticons, HTML, embedding media (YouTube, etc...), enhanced typography and more.", + "homepage": "https://github.com/s9e/TextFormatter/", + "keywords": [ + "bbcode", + "bbcodes", + "blog", + "censor", + "embed", + "emoji", + "emoticons", + "engine", + "forum", + "html", + "markdown", + "markup", + "media", + "parser", + "shortcodes" + ] + }, + { + "name": "symfony/config", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/config.git", + "reference": "7dd5f5040dc04c118d057fb5886563963eb70011" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/config/zipball/7dd5f5040dc04c118d057fb5886563963eb70011", + "reference": "7dd5f5040dc04c118d057fb5886563963eb70011", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3|~3.0.0", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/yaml": "~2.7|~3.0.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "time": "2018-11-26T09:38:12+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Config Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/console", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/debug": "^2.7.2|~3.0.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/process": "" + }, + "time": "2018-11-20T15:55:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/debug", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "reference": "74251c8d50dd3be7c4ce0c7b862497cdc641a5d0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" + }, + "time": "2018-11-11T11:18:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/dependency-injection", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "a2f40df187f0053bc361bcea3b27ff2b85744d9f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a2f40df187f0053bc361bcea3b27ff2b85744d9f", + "reference": "a2f40df187f0053bc361bcea3b27ff2b85744d9f", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/expression-language": "<2.6" + }, + "require-dev": { + "symfony/config": "~2.2|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/yaml": "~2.3.42|~2.7.14|~2.8.7|~3.0.7" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" + }, + "time": "2018-11-11T11:18:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/event-dispatcher", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "time": "2018-11-21T14:20:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/filesystem", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7ae46872dad09dffb7fe1e93a0937097339d0080", + "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "time": "2018-11-11T11:18:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/finder", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "time": "2018-11-11T11:18:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/http-foundation", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "d0ab719bedc9fc6748a95b2dcb04137292a27b92" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/d0ab719bedc9fc6748a95b2dcb04137292a27b92", + "reference": "d0ab719bedc9fc6748a95b2dcb04137292a27b92", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php54": "~1.0", + "symfony/polyfill-php55": "~1.0" + }, + "require-dev": { + "symfony/expression-language": "~2.4|~3.0.0" + }, + "time": "2018-11-25T11:27:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/http-kernel", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "3df0207d4c973eb9c91b38a608aef4654dc256fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3df0207d4c973eb9c91b38a608aef4654dc256fa", + "reference": "3df0207d4c973eb9c91b38a608aef4654dc256fa", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0", + "symfony/debug": "^2.6.2", + "symfony/event-dispatcher": "^2.6.7|~3.0.0", + "symfony/http-foundation": "~2.7.36|~2.8.29|~3.1.6", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/config": "<2.7", + "twig/twig": "<1.34|<2.4,>=2" + }, + "require-dev": { + "symfony/browser-kit": "~2.3|~3.0.0", + "symfony/class-loader": "~2.1|~3.0.0", + "symfony/config": "~2.8", + "symfony/console": "~2.3|~3.0.0", + "symfony/css-selector": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.8|~3.0.0", + "symfony/dom-crawler": "^2.0.5|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "symfony/finder": "^2.0.5|~3.0.0", + "symfony/process": "^2.0.5|~3.0.0", + "symfony/routing": "~2.8|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0", + "symfony/templating": "~2.2|~3.0.0", + "symfony/translation": "^2.0.5|~3.0.0", + "symfony/var-dumper": "~2.6|~3.0.0" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "time": "2018-12-06T14:45:07+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "82ebae02209c21113908c229e9883c419720738a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2019-02-06T07:57:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "backendtea@gmail.com" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ] + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2019-02-06T07:57:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-php54", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php54.git", + "reference": "2964b17ddc32dba7bcba009d5501c84d3fba1452" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/2964b17ddc32dba7bcba009d5501c84d3fba1452", + "reference": "2964b17ddc32dba7bcba009d5501c84d3fba1452", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2019-02-06T07:57:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php54\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/polyfill-php55", + "version": "v1.11.0", + "version_normalized": "1.11.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php55.git", + "reference": "96fa25cef405ea452919559a0025d5dc16e30e4c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/96fa25cef405ea452919559a0025d5dc16e30e4c", + "reference": "96fa25cef405ea452919559a0025d5dc16e30e4c", + "shasum": "" + }, + "require": { + "ircmaxell/password-compat": "~1.0", + "php": ">=5.3.3" + }, + "time": "2019-02-06T07:57:58+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php55\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ] + }, + { + "name": "symfony/proxy-manager-bridge", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/proxy-manager-bridge.git", + "reference": "9c5f8d58e9c8017affdbeaec86c89d558aee4ec8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/proxy-manager-bridge/zipball/9c5f8d58e9c8017affdbeaec86c89d558aee4ec8", + "reference": "9c5f8d58e9c8017affdbeaec86c89d558aee4ec8", + "shasum": "" + }, + "require": { + "ocramius/proxy-manager": "~0.4|~1.0|~2.0", + "php": ">=5.3.9", + "symfony/dependency-injection": "~2.8|~3.0.0" + }, + "require-dev": { + "symfony/config": "~2.3|~3.0.0" + }, + "time": "2018-11-11T11:18:13+00:00", + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\ProxyManager\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony ProxyManager Bridge", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/routing", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "8b0df6869d1997baafff6a1541826eac5a03d067" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/8b0df6869d1997baafff6a1541826eac5a03d067", + "reference": "8b0df6869d1997baafff6a1541826eac5a03d067", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "psr/log": "~1.0", + "symfony/config": "~2.7|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "symfony/http-foundation": "~2.3|~3.0.0", + "symfony/yaml": "^2.0.5|~3.0.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/dependency-injection": "For loading routes from a service", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "time": "2018-11-20T15:55:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ] + }, + { + "name": "symfony/twig-bridge", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "ecc1e30d05fa99f25b504e2d6a8684555ae39f7c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/ecc1e30d05fa99f25b504e2d6a8684555ae39f7c", + "reference": "ecc1e30d05fa99f25b504e2d6a8684555ae39f7c", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "twig/twig": "~1.34|~2.4" + }, + "conflict": { + "symfony/form": "<2.8.23" + }, + "require-dev": { + "symfony/asset": "~2.7|~3.0.0", + "symfony/console": "~2.8|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "symfony/finder": "~2.3|~3.0.0", + "symfony/form": "^2.8.23", + "symfony/http-foundation": "^2.8.29|~3.0.0", + "symfony/http-kernel": "~2.8|~3.0.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.2|~3.0.0", + "symfony/security": "^2.8.31|^3.3.13", + "symfony/security-acl": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.2|~3.0.0", + "symfony/templating": "~2.1|~3.0.0", + "symfony/translation": "~2.7|~3.0.0", + "symfony/var-dumper": "~2.7.16|~2.8.9|~3.0.9", + "symfony/yaml": "^2.0.5|~3.0.0" + }, + "suggest": { + "symfony/asset": "For using the AssetExtension", + "symfony/expression-language": "For using the ExpressionExtension", + "symfony/finder": "", + "symfony/form": "For using the FormExtension", + "symfony/http-kernel": "For using the HttpKernelExtension", + "symfony/routing": "For using the RoutingExtension", + "symfony/security": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/templating": "For using the TwigEngine", + "symfony/translation": "For using the TranslationExtension", + "symfony/var-dumper": "For using the DumpExtension", + "symfony/yaml": "For using the YamlExtension" + }, + "time": "2018-11-11T11:18:13+00:00", + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Twig Bridge", + "homepage": "https://symfony.com" + }, + { + "name": "symfony/yaml", + "version": "v2.8.49", + "version_normalized": "2.8.49.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b", + "reference": "02c1859112aa779d9ab394ae4f3381911d84052b", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "time": "2018-11-11T11:18:13+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com" + }, + { + "name": "twig/twig", + "version": "v1.39.1", + "version_normalized": "1.39.1.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec", + "reference": "23e7b6f0cfa1d7ba3de69f30d8e05cf957412fec", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/debug": "^2.7", + "symfony/phpunit-bridge": "^3.4.19|^4.1.8" + }, + "time": "2019-04-16T17:12:57+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.39-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Twig_": "lib/" + }, + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ] + }, + { + "name": "zendframework/zend-code", + "version": "2.5.1", + "version_normalized": "2.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-code.git", + "reference": "5d998f261ec2a55171c71da57a11622745680153" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-code/zipball/5d998f261ec2a55171c71da57a11622745680153", + "reference": "5d998f261ec2a55171c71da57a11622745680153", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-eventmanager": "~2.5" + }, + "require-dev": { + "doctrine/common": ">=2.1", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "zendframework/zend-stdlib": "~2.5", + "zendframework/zend-version": "~2.5" + }, + "suggest": { + "doctrine/common": "Doctrine\\Common >=2.1 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "time": "2015-06-03T15:31:59+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "homepage": "https://github.com/zendframework/zend-code", + "keywords": [ + "code", + "zf2" + ] + }, + { + "name": "zendframework/zend-eventmanager", + "version": "2.5.1", + "version_normalized": "2.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-eventmanager.git", + "reference": "d94a16039144936f107f906896349900fd634443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/d94a16039144936f107f906896349900fd634443", + "reference": "d94a16039144936f107f906896349900fd634443", + "shasum": "" + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-stdlib": "~2.5" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" + }, + "time": "2015-06-03T15:32:01+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Zend\\EventManager\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-eventmanager", + "keywords": [ + "eventmanager", + "zf2" + ] + }, + { + "name": "zendframework/zend-stdlib", + "version": "2.5.1", + "version_normalized": "2.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-stdlib.git", + "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cc8e90a60dd5d44b9730b77d07b97550091da1ae", + "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae", + "shasum": "" + }, + "require": { + "php": ">=5.3.23" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0", + "zendframework/zend-config": "~2.5", + "zendframework/zend-eventmanager": "~2.5", + "zendframework/zend-filter": "~2.5", + "zendframework/zend-inputfilter": "~2.5", + "zendframework/zend-serializer": "~2.5", + "zendframework/zend-servicemanager": "~2.5" + }, + "suggest": { + "zendframework/zend-eventmanager": "To support aggregate hydrator usage", + "zendframework/zend-filter": "To support naming strategy hydrator usage", + "zendframework/zend-serializer": "Zend\\Serializer component", + "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + }, + "time": "2015-06-03T15:32:03+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Zend\\Stdlib\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "homepage": "https://github.com/zendframework/zend-stdlib", + "keywords": [ + "stdlib", + "zf2" + ] + } +] diff --git a/vendor/google/recaptcha/LICENSE b/vendor/google/recaptcha/LICENSE new file mode 100644 index 0000000..f641232 --- /dev/null +++ b/vendor/google/recaptcha/LICENSE @@ -0,0 +1,29 @@ +Copyright 2014, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/google/recaptcha/composer.json b/vendor/google/recaptcha/composer.json new file mode 100644 index 0000000..63b751c --- /dev/null +++ b/vendor/google/recaptcha/composer.json @@ -0,0 +1,28 @@ +{ + "name": "google/recaptcha", + "description": "Client library for reCAPTCHA, a free service that protect websites from spam and abuse.", + "type": "library", + "keywords": ["recaptcha", "captcha", "spam", "abuse"], + "homepage": "http://www.google.com/recaptcha/", + "license": "BSD-3-Clause", + "support": { + "forum": "https://groups.google.com/forum/#!forum/recaptcha", + "source": "https://github.com/google/recaptcha" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.5.*" + }, + "autoload": { + "psr-4": { + "ReCaptcha\\": "src/ReCaptcha" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/vendor/google/recaptcha/src/ReCaptcha/ReCaptcha.php b/vendor/google/recaptcha/src/ReCaptcha/ReCaptcha.php new file mode 100644 index 0000000..e2f7c34 --- /dev/null +++ b/vendor/google/recaptcha/src/ReCaptcha/ReCaptcha.php @@ -0,0 +1,97 @@ +secret = $secret; + + if (!is_null($requestMethod)) { + $this->requestMethod = $requestMethod; + } else { + $this->requestMethod = new RequestMethod\Post(); + } + } + + /** + * Calls the reCAPTCHA siteverify API to verify whether the user passes + * CAPTCHA test. + * + * @param string $response The value of 'g-recaptcha-response' in the submitted form. + * @param string $remoteIp The end user's IP address. + * @return Response Response from the service. + */ + public function verify($response, $remoteIp = null) + { + // Discard empty solution submissions + if (empty($response)) { + $recaptchaResponse = new Response(false, array('missing-input-response')); + return $recaptchaResponse; + } + + $params = new RequestParameters($this->secret, $response, $remoteIp, self::VERSION); + $rawResponse = $this->requestMethod->submit($params); + return Response::fromJson($rawResponse); + } +} diff --git a/vendor/google/recaptcha/src/ReCaptcha/RequestMethod.php b/vendor/google/recaptcha/src/ReCaptcha/RequestMethod.php new file mode 100644 index 0000000..fc4dde5 --- /dev/null +++ b/vendor/google/recaptcha/src/ReCaptcha/RequestMethod.php @@ -0,0 +1,42 @@ +curl = $curl; + } else { + $this->curl = new Curl(); + } + } + + /** + * Submit the cURL request with the specified parameters. + * + * @param RequestParameters $params Request parameters + * @return string Body of the reCAPTCHA response + */ + public function submit(RequestParameters $params) + { + $handle = $this->curl->init(self::SITE_VERIFY_URL); + + $options = array( + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $params->toQueryString(), + CURLOPT_HTTPHEADER => array( + 'Content-Type: application/x-www-form-urlencoded' + ), + CURLINFO_HEADER_OUT => false, + CURLOPT_HEADER => false, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_SSL_VERIFYPEER => true + ); + $this->curl->setoptArray($handle, $options); + + $response = $this->curl->exec($handle); + $this->curl->close($handle); + + return $response; + } +} diff --git a/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php b/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php new file mode 100644 index 0000000..7770d90 --- /dev/null +++ b/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php @@ -0,0 +1,70 @@ + array( + 'header' => "Content-type: application/x-www-form-urlencoded\r\n", + 'method' => 'POST', + 'content' => $params->toQueryString(), + // Force the peer to validate (not needed in 5.6.0+, but still works + 'verify_peer' => true, + // Force the peer validation to use www.google.com + $peer_key => 'www.google.com', + ), + ); + $context = stream_context_create($options); + return file_get_contents(self::SITE_VERIFY_URL, false, $context); + } +} diff --git a/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php b/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php new file mode 100644 index 0000000..d3c8792 --- /dev/null +++ b/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php @@ -0,0 +1,105 @@ +handle = fsockopen($hostname, $port, $errno, $errstr, (is_null($timeout) ? ini_get("default_socket_timeout") : $timeout)); + + if ($this->handle != false && $errno === 0 && $errstr === '') { + return $this->handle; + } else { + return false; + } + } + + /** + * fwrite + * + * @see http://php.net/fwrite + * @param string $string + * @param int $length + * @return int | bool + */ + public function fwrite($string, $length = null) + { + return fwrite($this->handle, $string, (is_null($length) ? strlen($string) : $length)); + } + + /** + * fgets + * + * @see http://php.net/fgets + * @param int $length + * @return string + */ + public function fgets($length = null) + { + return fgets($this->handle, $length); + } + + /** + * feof + * + * @see http://php.net/feof + * @return bool + */ + public function feof() + { + return feof($this->handle); + } + + /** + * fclose + * + * @see http://php.net/fclose + * @return bool + */ + public function fclose() + { + return fclose($this->handle); + } +} diff --git a/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php b/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php new file mode 100644 index 0000000..4754121 --- /dev/null +++ b/vendor/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php @@ -0,0 +1,121 @@ +socket = $socket; + } else { + $this->socket = new Socket(); + } + } + + /** + * Submit the POST request with the specified parameters. + * + * @param RequestParameters $params Request parameters + * @return string Body of the reCAPTCHA response + */ + public function submit(RequestParameters $params) + { + $errno = 0; + $errstr = ''; + + if (false === $this->socket->fsockopen('ssl://' . self::RECAPTCHA_HOST, 443, $errno, $errstr, 30)) { + return self::BAD_REQUEST; + } + + $content = $params->toQueryString(); + + $request = "POST " . self::SITE_VERIFY_PATH . " HTTP/1.1\r\n"; + $request .= "Host: " . self::RECAPTCHA_HOST . "\r\n"; + $request .= "Content-Type: application/x-www-form-urlencoded\r\n"; + $request .= "Content-length: " . strlen($content) . "\r\n"; + $request .= "Connection: close\r\n\r\n"; + $request .= $content . "\r\n\r\n"; + + $this->socket->fwrite($request); + $response = ''; + + while (!$this->socket->feof()) { + $response .= $this->socket->fgets(4096); + } + + $this->socket->fclose(); + + if (0 !== strpos($response, 'HTTP/1.1 200 OK')) { + return self::BAD_RESPONSE; + } + + $parts = preg_split("#\n\s*\n#Uis", $response); + + return $parts[1]; + } +} diff --git a/vendor/google/recaptcha/src/ReCaptcha/RequestParameters.php b/vendor/google/recaptcha/src/ReCaptcha/RequestParameters.php new file mode 100644 index 0000000..cb66f26 --- /dev/null +++ b/vendor/google/recaptcha/src/ReCaptcha/RequestParameters.php @@ -0,0 +1,103 @@ +secret = $secret; + $this->response = $response; + $this->remoteIp = $remoteIp; + $this->version = $version; + } + + /** + * Array representation. + * + * @return array Array formatted parameters. + */ + public function toArray() + { + $params = array('secret' => $this->secret, 'response' => $this->response); + + if (!is_null($this->remoteIp)) { + $params['remoteip'] = $this->remoteIp; + } + + if (!is_null($this->version)) { + $params['version'] = $this->version; + } + + return $params; + } + + /** + * Query string representation for HTTP request. + * + * @return string Query string formatted parameters. + */ + public function toQueryString() + { + return http_build_query($this->toArray(), '', '&'); + } +} diff --git a/vendor/google/recaptcha/src/ReCaptcha/Response.php b/vendor/google/recaptcha/src/ReCaptcha/Response.php new file mode 100644 index 0000000..d2d8a8b --- /dev/null +++ b/vendor/google/recaptcha/src/ReCaptcha/Response.php @@ -0,0 +1,102 @@ +success = $success; + $this->errorCodes = $errorCodes; + } + + /** + * Is success? + * + * @return boolean + */ + public function isSuccess() + { + return $this->success; + } + + /** + * Get error codes. + * + * @return array + */ + public function getErrorCodes() + { + return $this->errorCodes; + } +} diff --git a/vendor/google/recaptcha/src/autoload.php b/vendor/google/recaptcha/src/autoload.php new file mode 100644 index 0000000..a53cbd7 --- /dev/null +++ b/vendor/google/recaptcha/src/autoload.php @@ -0,0 +1,38 @@ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/guzzle/composer.json b/vendor/guzzlehttp/guzzle/composer.json new file mode 100644 index 0000000..d8bb120 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/composer.json @@ -0,0 +1,38 @@ +{ + "name": "guzzlehttp/guzzle", + "type": "library", + "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", + "keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"], + "homepage": "http://guzzlephp.org/", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.4.0", + "guzzlehttp/ringphp": "^1.1", + "react/promise": "^2.2" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.0" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\": "tests/" + } + }, + "scripts": { + "test": "make test", + "test-ci": "make coverage" + } +} diff --git a/vendor/guzzlehttp/guzzle/src/BatchResults.php b/vendor/guzzlehttp/guzzle/src/BatchResults.php new file mode 100644 index 0000000..e5af433 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/BatchResults.php @@ -0,0 +1,148 @@ +hash = $hash; + } + + /** + * Get the keys that are available on the batch result. + * + * @return array + */ + public function getKeys() + { + return iterator_to_array($this->hash); + } + + /** + * Gets a result from the container for the given object. When getting + * results for a batch of requests, provide the request object. + * + * @param object $forObject Object to retrieve the result for. + * + * @return mixed|null + */ + public function getResult($forObject) + { + return isset($this->hash[$forObject]) ? $this->hash[$forObject] : null; + } + + /** + * Get an array of successful results. + * + * @return array + */ + public function getSuccessful() + { + $results = []; + foreach ($this->hash as $key) { + if (!($this->hash[$key] instanceof \Exception)) { + $results[] = $this->hash[$key]; + } + } + + return $results; + } + + /** + * Get an array of failed results. + * + * @return array + */ + public function getFailures() + { + $results = []; + foreach ($this->hash as $key) { + if ($this->hash[$key] instanceof \Exception) { + $results[] = $this->hash[$key]; + } + } + + return $results; + } + + /** + * Allows iteration over all batch result values. + * + * @return \ArrayIterator + */ + public function getIterator() + { + $results = []; + foreach ($this->hash as $key) { + $results[] = $this->hash[$key]; + } + + return new \ArrayIterator($results); + } + + /** + * Counts the number of elements in the batch result. + * + * @return int + */ + public function count() + { + return count($this->hash); + } + + /** + * Checks if the batch contains a specific numerical array index. + * + * @param int $key Index to access + * + * @return bool + */ + public function offsetExists($key) + { + return $key < count($this->hash); + } + + /** + * Allows access of the batch using a numerical array index. + * + * @param int $key Index to access. + * + * @return mixed|null + */ + public function offsetGet($key) + { + $i = -1; + foreach ($this->hash as $obj) { + if ($key === ++$i) { + return $this->hash[$obj]; + } + } + + return null; + } + + public function offsetUnset($key) + { + throw new \RuntimeException('Not implemented'); + } + + public function offsetSet($key, $value) + { + throw new \RuntimeException('Not implemented'); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Client.php b/vendor/guzzlehttp/guzzle/src/Client.php new file mode 100644 index 0000000..cf5dd46 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Client.php @@ -0,0 +1,362 @@ + [ + * 'http://www.foo.com/{version}/', + * ['version' => '123'] + * ], + * 'defaults' => [ + * 'timeout' => 10, + * 'allow_redirects' => false, + * 'proxy' => '192.168.16.1:10' + * ] + * ]); + * + * @param array $config Client configuration settings + * - base_url: Base URL of the client that is merged into relative URLs. + * Can be a string or an array that contains a URI template followed + * by an associative array of expansion variables to inject into the + * URI template. + * - handler: callable RingPHP handler used to transfer requests + * - message_factory: Factory used to create request and response object + * - defaults: Default request options to apply to each request + * - emitter: Event emitter used for request events + * - fsm: (internal use only) The request finite state machine. A + * function that accepts a transaction and optional final state. The + * function is responsible for transitioning a request through its + * lifecycle events. + */ + public function __construct(array $config = []) + { + $this->configureBaseUrl($config); + $this->configureDefaults($config); + + if (isset($config['emitter'])) { + $this->emitter = $config['emitter']; + } + + $this->messageFactory = isset($config['message_factory']) + ? $config['message_factory'] + : new MessageFactory(); + + if (isset($config['fsm'])) { + $this->fsm = $config['fsm']; + } else { + if (isset($config['handler'])) { + $handler = $config['handler']; + } elseif (isset($config['adapter'])) { + $handler = $config['adapter']; + } else { + $handler = Utils::getDefaultHandler(); + } + $this->fsm = new RequestFsm($handler, $this->messageFactory); + } + } + + public function getDefaultOption($keyOrPath = null) + { + return $keyOrPath === null + ? $this->defaults + : Utils::getPath($this->defaults, $keyOrPath); + } + + public function setDefaultOption($keyOrPath, $value) + { + Utils::setPath($this->defaults, $keyOrPath, $value); + } + + public function getBaseUrl() + { + return (string) $this->baseUrl; + } + + public function createRequest($method, $url = null, array $options = []) + { + $options = $this->mergeDefaults($options); + // Use a clone of the client's emitter + $options['config']['emitter'] = clone $this->getEmitter(); + $url = $url || (is_string($url) && strlen($url)) + ? $this->buildUrl($url) + : (string) $this->baseUrl; + + return $this->messageFactory->createRequest($method, $url, $options); + } + + public function get($url = null, $options = []) + { + return $this->send($this->createRequest('GET', $url, $options)); + } + + public function head($url = null, array $options = []) + { + return $this->send($this->createRequest('HEAD', $url, $options)); + } + + public function delete($url = null, array $options = []) + { + return $this->send($this->createRequest('DELETE', $url, $options)); + } + + public function put($url = null, array $options = []) + { + return $this->send($this->createRequest('PUT', $url, $options)); + } + + public function patch($url = null, array $options = []) + { + return $this->send($this->createRequest('PATCH', $url, $options)); + } + + public function post($url = null, array $options = []) + { + return $this->send($this->createRequest('POST', $url, $options)); + } + + public function options($url = null, array $options = []) + { + return $this->send($this->createRequest('OPTIONS', $url, $options)); + } + + public function send(RequestInterface $request) + { + $isFuture = $request->getConfig()->get('future'); + $trans = new Transaction($this, $request, $isFuture); + $fn = $this->fsm; + + try { + $fn($trans); + if ($isFuture) { + // Turn the normal response into a future if needed. + return $trans->response instanceof FutureInterface + ? $trans->response + : new FutureResponse(new FulfilledPromise($trans->response)); + } + // Resolve deep futures if this is not a future + // transaction. This accounts for things like retries + // that do not have an immediate side-effect. + while ($trans->response instanceof FutureInterface) { + $trans->response = $trans->response->wait(); + } + return $trans->response; + } catch (\Exception $e) { + if ($isFuture) { + // Wrap the exception in a promise + return new FutureResponse(new RejectedPromise($e)); + } + throw RequestException::wrapException($trans->request, $e); + } catch (\TypeError $error) { + $exception = new \Exception($error->getMessage(), $error->getCode(), $error); + if ($isFuture) { + // Wrap the exception in a promise + return new FutureResponse(new RejectedPromise($exception)); + } + throw RequestException::wrapException($trans->request, $exception); + } + } + + /** + * Get an array of default options to apply to the client + * + * @return array + */ + protected function getDefaultOptions() + { + $settings = [ + 'allow_redirects' => true, + 'exceptions' => true, + 'decode_content' => true, + 'verify' => true + ]; + + // Use the standard Linux HTTP_PROXY and HTTPS_PROXY if set. + // We can only trust the HTTP_PROXY environment variable in a CLI + // process due to the fact that PHP has no reliable mechanism to + // get environment variables that start with "HTTP_". + if (php_sapi_name() == 'cli' && getenv('HTTP_PROXY')) { + $settings['proxy']['http'] = getenv('HTTP_PROXY'); + } + + if ($proxy = getenv('HTTPS_PROXY')) { + $settings['proxy']['https'] = $proxy; + } + + return $settings; + } + + /** + * Expand a URI template and inherit from the base URL if it's relative + * + * @param string|array $url URL or an array of the URI template to expand + * followed by a hash of template varnames. + * @return string + * @throws \InvalidArgumentException + */ + private function buildUrl($url) + { + // URI template (absolute or relative) + if (!is_array($url)) { + return strpos($url, '://') + ? (string) $url + : (string) $this->baseUrl->combine($url); + } + + if (!isset($url[1])) { + throw new \InvalidArgumentException('You must provide a hash of ' + . 'varname options in the second element of a URL array.'); + } + + // Absolute URL + if (strpos($url[0], '://')) { + return Utils::uriTemplate($url[0], $url[1]); + } + + // Combine the relative URL with the base URL + return (string) $this->baseUrl->combine( + Utils::uriTemplate($url[0], $url[1]) + ); + } + + private function configureBaseUrl(&$config) + { + if (!isset($config['base_url'])) { + $this->baseUrl = new Url('', ''); + } elseif (!is_array($config['base_url'])) { + $this->baseUrl = Url::fromString($config['base_url']); + } elseif (count($config['base_url']) < 2) { + throw new \InvalidArgumentException('You must provide a hash of ' + . 'varname options in the second element of a base_url array.'); + } else { + $this->baseUrl = Url::fromString( + Utils::uriTemplate( + $config['base_url'][0], + $config['base_url'][1] + ) + ); + $config['base_url'] = (string) $this->baseUrl; + } + } + + private function configureDefaults($config) + { + if (!isset($config['defaults'])) { + $this->defaults = $this->getDefaultOptions(); + } else { + $this->defaults = array_replace( + $this->getDefaultOptions(), + $config['defaults'] + ); + } + + // Add the default user-agent header + if (!isset($this->defaults['headers'])) { + $this->defaults['headers'] = [ + 'User-Agent' => Utils::getDefaultUserAgent() + ]; + } elseif (!Core::hasHeader($this->defaults, 'User-Agent')) { + // Add the User-Agent header if one was not already set + $this->defaults['headers']['User-Agent'] = Utils::getDefaultUserAgent(); + } + } + + /** + * Merges default options into the array passed by reference. + * + * @param array $options Options to modify by reference + * + * @return array + */ + private function mergeDefaults($options) + { + $defaults = $this->defaults; + + // Case-insensitively merge in default headers if both defaults and + // options have headers specified. + if (!empty($defaults['headers']) && !empty($options['headers'])) { + // Create a set of lowercased keys that are present. + $lkeys = []; + foreach (array_keys($options['headers']) as $k) { + $lkeys[strtolower($k)] = true; + } + // Merge in lowercase default keys when not present in above set. + foreach ($defaults['headers'] as $key => $value) { + if (!isset($lkeys[strtolower($key)])) { + $options['headers'][$key] = $value; + } + } + // No longer need to merge in headers. + unset($defaults['headers']); + } + + $result = array_replace_recursive($defaults, $options); + foreach ($options as $k => $v) { + if ($v === null) { + unset($result[$k]); + } + } + + return $result; + } + + /** + * @deprecated Use {@see GuzzleHttp\Pool} instead. + * @see GuzzleHttp\Pool + */ + public function sendAll($requests, array $options = []) + { + Pool::send($this, $requests, $options); + } + + /** + * @deprecated Use GuzzleHttp\Utils::getDefaultHandler + */ + public static function getDefaultHandler() + { + return Utils::getDefaultHandler(); + } + + /** + * @deprecated Use GuzzleHttp\Utils::getDefaultUserAgent + */ + public static function getDefaultUserAgent() + { + return Utils::getDefaultUserAgent(); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/ClientInterface.php b/vendor/guzzlehttp/guzzle/src/ClientInterface.php new file mode 100644 index 0000000..6668597 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/ClientInterface.php @@ -0,0 +1,150 @@ +data = $data; + } + + /** + * Create a new collection from an array, validate the keys, and add default + * values where missing + * + * @param array $config Configuration values to apply. + * @param array $defaults Default parameters + * @param array $required Required parameter names + * + * @return self + * @throws \InvalidArgumentException if a parameter is missing + */ + public static function fromConfig( + array $config = [], + array $defaults = [], + array $required = [] + ) { + $data = $config + $defaults; + + if ($missing = array_diff($required, array_keys($data))) { + throw new \InvalidArgumentException( + 'Config is missing the following keys: ' . + implode(', ', $missing)); + } + + return new self($data); + } + + /** + * Removes all key value pairs + */ + public function clear() + { + $this->data = []; + } + + /** + * Get a specific key value. + * + * @param string $key Key to retrieve. + * + * @return mixed|null Value of the key or NULL + */ + public function get($key) + { + return isset($this->data[$key]) ? $this->data[$key] : null; + } + + /** + * Set a key value pair + * + * @param string $key Key to set + * @param mixed $value Value to set + */ + public function set($key, $value) + { + $this->data[$key] = $value; + } + + /** + * Add a value to a key. If a key of the same name has already been added, + * the key value will be converted into an array and the new value will be + * pushed to the end of the array. + * + * @param string $key Key to add + * @param mixed $value Value to add to the key + */ + public function add($key, $value) + { + if (!array_key_exists($key, $this->data)) { + $this->data[$key] = $value; + } elseif (is_array($this->data[$key])) { + $this->data[$key][] = $value; + } else { + $this->data[$key] = array($this->data[$key], $value); + } + } + + /** + * Remove a specific key value pair + * + * @param string $key A key to remove + */ + public function remove($key) + { + unset($this->data[$key]); + } + + /** + * Get all keys in the collection + * + * @return array + */ + public function getKeys() + { + return array_keys($this->data); + } + + /** + * Returns whether or not the specified key is present. + * + * @param string $key The key for which to check the existence. + * + * @return bool + */ + public function hasKey($key) + { + return array_key_exists($key, $this->data); + } + + /** + * Checks if any keys contains a certain value + * + * @param string $value Value to search for + * + * @return mixed Returns the key if the value was found FALSE if the value + * was not found. + */ + public function hasValue($value) + { + return array_search($value, $this->data, true); + } + + /** + * Replace the data of the object with the value of an array + * + * @param array $data Associative array of data + */ + public function replace(array $data) + { + $this->data = $data; + } + + /** + * Add and merge in a Collection or array of key value pair data. + * + * @param Collection|array $data Associative array of key value pair data + */ + public function merge($data) + { + foreach ($data as $key => $value) { + $this->add($key, $value); + } + } + + /** + * Overwrite key value pairs in this collection with all of the data from + * an array or collection. + * + * @param array|\Traversable $data Values to override over this config + */ + public function overwriteWith($data) + { + if (is_array($data)) { + $this->data = $data + $this->data; + } elseif ($data instanceof Collection) { + $this->data = $data->toArray() + $this->data; + } else { + foreach ($data as $key => $value) { + $this->data[$key] = $value; + } + } + } + + /** + * Returns a Collection containing all the elements of the collection after + * applying the callback function to each one. + * + * The callable should accept three arguments: + * - (string) $key + * - (string) $value + * - (array) $context + * + * The callable must return a the altered or unaltered value. + * + * @param callable $closure Map function to apply + * @param array $context Context to pass to the callable + * + * @return Collection + */ + public function map(callable $closure, array $context = []) + { + $collection = new static(); + foreach ($this as $key => $value) { + $collection[$key] = $closure($key, $value, $context); + } + + return $collection; + } + + /** + * Iterates over each key value pair in the collection passing them to the + * callable. If the callable returns true, the current value from input is + * returned into the result Collection. + * + * The callable must accept two arguments: + * - (string) $key + * - (string) $value + * + * @param callable $closure Evaluation function + * + * @return Collection + */ + public function filter(callable $closure) + { + $collection = new static(); + foreach ($this->data as $key => $value) { + if ($closure($key, $value)) { + $collection[$key] = $value; + } + } + + return $collection; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php new file mode 100644 index 0000000..f8ac7dd --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJar.php @@ -0,0 +1,248 @@ +strictMode = $strictMode; + + foreach ($cookieArray as $cookie) { + if (!($cookie instanceof SetCookie)) { + $cookie = new SetCookie($cookie); + } + $this->setCookie($cookie); + } + } + + /** + * Create a new Cookie jar from an associative array and domain. + * + * @param array $cookies Cookies to create the jar from + * @param string $domain Domain to set the cookies to + * + * @return self + */ + public static function fromArray(array $cookies, $domain) + { + $cookieJar = new self(); + foreach ($cookies as $name => $value) { + $cookieJar->setCookie(new SetCookie([ + 'Domain' => $domain, + 'Name' => $name, + 'Value' => $value, + 'Discard' => true + ])); + } + + return $cookieJar; + } + + /** + * Quote the cookie value if it is not already quoted and it contains + * problematic characters. + * + * @param string $value Value that may or may not need to be quoted + * + * @return string + */ + public static function getCookieValue($value) + { + if (substr($value, 0, 1) !== '"' && + substr($value, -1, 1) !== '"' && + strpbrk($value, ';,') + ) { + $value = '"' . $value . '"'; + } + + return $value; + } + + public function toArray() + { + return array_map(function (SetCookie $cookie) { + return $cookie->toArray(); + }, $this->getIterator()->getArrayCopy()); + } + + public function clear($domain = null, $path = null, $name = null) + { + if (!$domain) { + $this->cookies = []; + return; + } elseif (!$path) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !$cookie->matchesDomain($domain); + } + ); + } elseif (!$name) { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain) { + return !($cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } else { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) use ($path, $domain, $name) { + return !($cookie->getName() == $name && + $cookie->matchesPath($path) && + $cookie->matchesDomain($domain)); + } + ); + } + } + + public function clearSessionCookies() + { + $this->cookies = array_filter( + $this->cookies, + function (SetCookie $cookie) { + return !$cookie->getDiscard() && $cookie->getExpires(); + } + ); + } + + public function setCookie(SetCookie $cookie) + { + // Only allow cookies with set and valid domain, name, value + $result = $cookie->validate(); + if ($result !== true) { + if ($this->strictMode) { + throw new \RuntimeException('Invalid cookie: ' . $result); + } else { + $this->removeCookieIfEmpty($cookie); + return false; + } + } + + // Resolve conflicts with previously set cookies + foreach ($this->cookies as $i => $c) { + + // Two cookies are identical, when their path, and domain are + // identical. + if ($c->getPath() != $cookie->getPath() || + $c->getDomain() != $cookie->getDomain() || + $c->getName() != $cookie->getName() + ) { + continue; + } + + // The previously set cookie is a discard cookie and this one is + // not so allow the new cookie to be set + if (!$cookie->getDiscard() && $c->getDiscard()) { + unset($this->cookies[$i]); + continue; + } + + // If the new cookie's expiration is further into the future, then + // replace the old cookie + if ($cookie->getExpires() > $c->getExpires()) { + unset($this->cookies[$i]); + continue; + } + + // If the value has changed, we better change it + if ($cookie->getValue() !== $c->getValue()) { + unset($this->cookies[$i]); + continue; + } + + // The cookie exists, so no need to continue + return false; + } + + $this->cookies[] = $cookie; + + return true; + } + + public function count() + { + return count($this->cookies); + } + + public function getIterator() + { + return new \ArrayIterator(array_values($this->cookies)); + } + + public function extractCookies( + RequestInterface $request, + ResponseInterface $response + ) { + if ($cookieHeader = $response->getHeaderAsArray('Set-Cookie')) { + foreach ($cookieHeader as $cookie) { + $sc = SetCookie::fromString($cookie); + if (!$sc->getDomain()) { + $sc->setDomain($request->getHost()); + } + $this->setCookie($sc); + } + } + } + + public function addCookieHeader(RequestInterface $request) + { + $values = []; + $scheme = $request->getScheme(); + $host = $request->getHost(); + $path = $request->getPath(); + + foreach ($this->cookies as $cookie) { + if ($cookie->matchesPath($path) && + $cookie->matchesDomain($host) && + !$cookie->isExpired() && + (!$cookie->getSecure() || $scheme == 'https') + ) { + $values[] = $cookie->getName() . '=' + . self::getCookieValue($cookie->getValue()); + } + } + + if ($values) { + $request->setHeader('Cookie', implode('; ', $values)); + } + } + + /** + * If a cookie already exists and the server asks to set it again with a + * null value, the cookie must be deleted. + * + * @param SetCookie $cookie + */ + private function removeCookieIfEmpty(SetCookie $cookie) + { + $cookieValue = $cookie->getValue(); + if ($cookieValue === null || $cookieValue === '') { + $this->clear( + $cookie->getDomain(), + $cookie->getPath(), + $cookie->getName() + ); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php new file mode 100644 index 0000000..4ea8567 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/CookieJarInterface.php @@ -0,0 +1,75 @@ +filename = $cookieFile; + + if (file_exists($cookieFile)) { + $this->load($cookieFile); + } + } + + /** + * Saves the file when shutting down + */ + public function __destruct() + { + $this->save($this->filename); + } + + /** + * Saves the cookies to a file. + * + * @param string $filename File to save + * @throws \RuntimeException if the file cannot be found or created + */ + public function save($filename) + { + $json = []; + foreach ($this as $cookie) { + if ($cookie->getExpires() && !$cookie->getDiscard()) { + $json[] = $cookie->toArray(); + } + } + + if (false === file_put_contents($filename, json_encode($json))) { + // @codeCoverageIgnoreStart + throw new \RuntimeException("Unable to save file {$filename}"); + // @codeCoverageIgnoreEnd + } + } + + /** + * Load cookies from a JSON formatted file. + * + * Old cookies are kept unless overwritten by newly loaded ones. + * + * @param string $filename Cookie file to load. + * @throws \RuntimeException if the file cannot be loaded. + */ + public function load($filename) + { + $json = file_get_contents($filename); + if (false === $json) { + // @codeCoverageIgnoreStart + throw new \RuntimeException("Unable to load file {$filename}"); + // @codeCoverageIgnoreEnd + } + + $data = Utils::jsonDecode($json, true); + if (is_array($data)) { + foreach (Utils::jsonDecode($json, true) as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie file: {$filename}"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php new file mode 100644 index 0000000..71a02d5 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SessionCookieJar.php @@ -0,0 +1,66 @@ +sessionKey = $sessionKey; + $this->load(); + } + + /** + * Saves cookies to session when shutting down + */ + public function __destruct() + { + $this->save(); + } + + /** + * Save cookies to the client session + */ + public function save() + { + $json = []; + foreach ($this as $cookie) { + if ($cookie->getExpires() && !$cookie->getDiscard()) { + $json[] = $cookie->toArray(); + } + } + + $_SESSION[$this->sessionKey] = json_encode($json); + } + + /** + * Load the contents of the client session into the data array + */ + protected function load() + { + $cookieJar = isset($_SESSION[$this->sessionKey]) + ? $_SESSION[$this->sessionKey] + : null; + + $data = Utils::jsonDecode($cookieJar, true); + if (is_array($data)) { + foreach ($data as $cookie) { + $this->setCookie(new SetCookie($cookie)); + } + } elseif (strlen($data)) { + throw new \RuntimeException("Invalid cookie data"); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php new file mode 100644 index 0000000..6fdee0d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Cookie/SetCookie.php @@ -0,0 +1,373 @@ + null, + 'Value' => null, + 'Domain' => null, + 'Path' => '/', + 'Max-Age' => null, + 'Expires' => null, + 'Secure' => false, + 'Discard' => false, + 'HttpOnly' => false + ]; + + /** @var array Cookie data */ + private $data; + + /** + * Create a new SetCookie object from a string + * + * @param string $cookie Set-Cookie header string + * + * @return self + */ + public static function fromString($cookie) + { + // Create the default return array + $data = self::$defaults; + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + // The name of the cookie (first kvp) must include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return new self($data); + } + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + $value = isset($cookieParts[1]) + ? trim($cookieParts[1], " \n\r\t\0\x0B\"") + : true; + + // Only check for non-cookies when cookies have been found + if (empty($data['Name'])) { + $data['Name'] = $key; + $data['Value'] = $value; + } else { + foreach (array_keys(self::$defaults) as $search) { + if (!strcasecmp($search, $key)) { + $data[$search] = $value; + continue 2; + } + } + $data[$key] = $value; + } + } + + return new self($data); + } + + /** + * @param array $data Array of cookie data provided by a Cookie parser + */ + public function __construct(array $data = []) + { + $this->data = array_replace(self::$defaults, $data); + // Extract the Expires value and turn it into a UNIX timestamp if needed + if (!$this->getExpires() && $this->getMaxAge()) { + // Calculate the Expires date + $this->setExpires(time() + $this->getMaxAge()); + } elseif ($this->getExpires() && !is_numeric($this->getExpires())) { + $this->setExpires($this->getExpires()); + } + } + + public function __toString() + { + $str = $this->data['Name'] . '=' . $this->data['Value'] . '; '; + foreach ($this->data as $k => $v) { + if ($k != 'Name' && $k != 'Value' && $v !== null && $v !== false) { + if ($k == 'Expires') { + $str .= 'Expires=' . gmdate('D, d M Y H:i:s \G\M\T', $v) . '; '; + } else { + $str .= ($v === true ? $k : "{$k}={$v}") . '; '; + } + } + } + + return rtrim($str, '; '); + } + + public function toArray() + { + return $this->data; + } + + /** + * Get the cookie name + * + * @return string + */ + public function getName() + { + return $this->data['Name']; + } + + /** + * Set the cookie name + * + * @param string $name Cookie name + */ + public function setName($name) + { + $this->data['Name'] = $name; + } + + /** + * Get the cookie value + * + * @return string + */ + public function getValue() + { + return $this->data['Value']; + } + + /** + * Set the cookie value + * + * @param string $value Cookie value + */ + public function setValue($value) + { + $this->data['Value'] = $value; + } + + /** + * Get the domain + * + * @return string|null + */ + public function getDomain() + { + return $this->data['Domain']; + } + + /** + * Set the domain of the cookie + * + * @param string $domain + */ + public function setDomain($domain) + { + $this->data['Domain'] = $domain; + } + + /** + * Get the path + * + * @return string + */ + public function getPath() + { + return $this->data['Path']; + } + + /** + * Set the path of the cookie + * + * @param string $path Path of the cookie + */ + public function setPath($path) + { + $this->data['Path'] = $path; + } + + /** + * Maximum lifetime of the cookie in seconds + * + * @return int|null + */ + public function getMaxAge() + { + return $this->data['Max-Age']; + } + + /** + * Set the max-age of the cookie + * + * @param int $maxAge Max age of the cookie in seconds + */ + public function setMaxAge($maxAge) + { + $this->data['Max-Age'] = $maxAge; + } + + /** + * The UNIX timestamp when the cookie Expires + * + * @return mixed + */ + public function getExpires() + { + return $this->data['Expires']; + } + + /** + * Set the unix timestamp for which the cookie will expire + * + * @param int $timestamp Unix timestamp + */ + public function setExpires($timestamp) + { + $this->data['Expires'] = is_numeric($timestamp) + ? (int) $timestamp + : strtotime($timestamp); + } + + /** + * Get whether or not this is a secure cookie + * + * @return null|bool + */ + public function getSecure() + { + return $this->data['Secure']; + } + + /** + * Set whether or not the cookie is secure + * + * @param bool $secure Set to true or false if secure + */ + public function setSecure($secure) + { + $this->data['Secure'] = $secure; + } + + /** + * Get whether or not this is a session cookie + * + * @return null|bool + */ + public function getDiscard() + { + return $this->data['Discard']; + } + + /** + * Set whether or not this is a session cookie + * + * @param bool $discard Set to true or false if this is a session cookie + */ + public function setDiscard($discard) + { + $this->data['Discard'] = $discard; + } + + /** + * Get whether or not this is an HTTP only cookie + * + * @return bool + */ + public function getHttpOnly() + { + return $this->data['HttpOnly']; + } + + /** + * Set whether or not this is an HTTP only cookie + * + * @param bool $httpOnly Set to true or false if this is HTTP only + */ + public function setHttpOnly($httpOnly) + { + $this->data['HttpOnly'] = $httpOnly; + } + + /** + * Check if the cookie matches a path value + * + * @param string $path Path to check against + * + * @return bool + */ + public function matchesPath($path) + { + return !$this->getPath() || 0 === stripos($path, $this->getPath()); + } + + /** + * Check if the cookie matches a domain value + * + * @param string $domain Domain to check against + * + * @return bool + */ + public function matchesDomain($domain) + { + // Remove the leading '.' as per spec in RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.2.3 + $cookieDomain = ltrim($this->getDomain(), '.'); + + // Domain not set or exact match. + if (!$cookieDomain || !strcasecmp($domain, $cookieDomain)) { + return true; + } + + // Matching the subdomain according to RFC 6265. + // http://tools.ietf.org/html/rfc6265#section-5.1.3 + if (filter_var($domain, FILTER_VALIDATE_IP)) { + return false; + } + + return (bool) preg_match('/\.' . preg_quote($cookieDomain) . '$/i', $domain); + } + + /** + * Check if the cookie is expired + * + * @return bool + */ + public function isExpired() + { + return $this->getExpires() !== null && time() > $this->getExpires(); + } + + /** + * Check if the cookie is valid according to RFC 6265 + * + * @return bool|string Returns true if valid or an error message if invalid + */ + public function validate() + { + // Names must not be empty, but can be 0 + $name = $this->getName(); + if (empty($name) && !is_numeric($name)) { + return 'The cookie name must not be empty'; + } + + // Check if any of the invalid characters are present in the cookie name + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + return "Cookie name must not cannot invalid characters: =,; \\t\\r\\n\\013\\014"; + } + + // Value must not be empty, but can be 0 + $value = $this->getValue(); + if (empty($value) && !is_numeric($value)) { + return 'The cookie value must not be empty'; + } + + // Domains must not be empty, but can be 0 + // A "0" is not a valid internet domain, but may be used as server name + // in a private network. + $domain = $this->getDomain(); + if (empty($domain) && !is_numeric($domain)) { + return 'The cookie domain must not be empty'; + } + + return true; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/AbstractEvent.php b/vendor/guzzlehttp/guzzle/src/Event/AbstractEvent.php new file mode 100644 index 0000000..0d2f4db --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/AbstractEvent.php @@ -0,0 +1,20 @@ +propagationStopped; + } + + public function stopPropagation() + { + $this->propagationStopped = true; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php b/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php new file mode 100644 index 0000000..8a6ee47 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/AbstractRequestEvent.php @@ -0,0 +1,61 @@ +transaction = $transaction; + } + + /** + * Get the HTTP client associated with the event. + * + * @return ClientInterface + */ + public function getClient() + { + return $this->transaction->client; + } + + /** + * Get the request object + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->transaction->request; + } + + /** + * Get the number of transaction retries. + * + * @return int + */ + public function getRetryCount() + { + return $this->transaction->retries; + } + + /** + * @return Transaction + */ + public function getTransaction() + { + return $this->transaction; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php b/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php new file mode 100644 index 0000000..bbbdfaf --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/AbstractRetryableEvent.php @@ -0,0 +1,40 @@ +transaction->state = 'retry'; + + if ($afterDelay) { + $this->transaction->request->getConfig()->set('delay', $afterDelay); + } + + $this->stopPropagation(); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php b/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php new file mode 100644 index 0000000..3b106df --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/AbstractTransferEvent.php @@ -0,0 +1,63 @@ +transaction->transferInfo; + } + + return isset($this->transaction->transferInfo[$name]) + ? $this->transaction->transferInfo[$name] + : null; + } + + /** + * Returns true/false if a response is available. + * + * @return bool + */ + public function hasResponse() + { + return !($this->transaction->response instanceof FutureInterface); + } + + /** + * Get the response. + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->hasResponse() ? $this->transaction->response : null; + } + + /** + * Intercept the request and associate a response + * + * @param ResponseInterface $response Response to set + */ + public function intercept(ResponseInterface $response) + { + $this->transaction->response = $response; + $this->transaction->exception = null; + $this->stopPropagation(); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/BeforeEvent.php b/vendor/guzzlehttp/guzzle/src/Event/BeforeEvent.php new file mode 100644 index 0000000..f313c37 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/BeforeEvent.php @@ -0,0 +1,26 @@ +transaction->response = $response; + $this->transaction->exception = null; + $this->stopPropagation(); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/CompleteEvent.php b/vendor/guzzlehttp/guzzle/src/Event/CompleteEvent.php new file mode 100644 index 0000000..56cc557 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/CompleteEvent.php @@ -0,0 +1,14 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @link https://github.com/symfony/symfony/tree/master/src/Symfony/Component/EventDispatcher + */ +class Emitter implements EmitterInterface +{ + /** @var array */ + private $listeners = []; + + /** @var array */ + private $sorted = []; + + public function on($eventName, callable $listener, $priority = 0) + { + if ($priority === 'first') { + $priority = isset($this->listeners[$eventName]) + ? max(array_keys($this->listeners[$eventName])) + 1 + : 1; + } elseif ($priority === 'last') { + $priority = isset($this->listeners[$eventName]) + ? min(array_keys($this->listeners[$eventName])) - 1 + : -1; + } + + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + public function once($eventName, callable $listener, $priority = 0) + { + $onceListener = function ( + EventInterface $event + ) use (&$onceListener, $eventName, $listener, $priority) { + $this->removeListener($eventName, $onceListener); + $listener($event, $eventName); + }; + + $this->on($eventName, $onceListener, $priority); + } + + public function removeListener($eventName, callable $listener) + { + if (empty($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + unset( + $this->listeners[$eventName][$priority][$key], + $this->sorted[$eventName] + ); + } + } + } + + public function listeners($eventName = null) + { + // Return all events in a sorted priority order + if ($eventName === null) { + foreach (array_keys($this->listeners) as $eventName) { + if (empty($this->sorted[$eventName])) { + $this->listeners($eventName); + } + } + return $this->sorted; + } + + // Return the listeners for a specific event, sorted in priority order + if (empty($this->sorted[$eventName])) { + $this->sorted[$eventName] = []; + if (isset($this->listeners[$eventName])) { + krsort($this->listeners[$eventName], SORT_NUMERIC); + foreach ($this->listeners[$eventName] as $listeners) { + foreach ($listeners as $listener) { + $this->sorted[$eventName][] = $listener; + } + } + } + } + + return $this->sorted[$eventName]; + } + + public function hasListeners($eventName) + { + return !empty($this->listeners[$eventName]); + } + + public function emit($eventName, EventInterface $event) + { + if (isset($this->listeners[$eventName])) { + foreach ($this->listeners($eventName) as $listener) { + $listener($event, $eventName); + if ($event->isPropagationStopped()) { + break; + } + } + } + + return $event; + } + + public function attach(SubscriberInterface $subscriber) + { + foreach ($subscriber->getEvents() as $eventName => $listeners) { + if (is_array($listeners[0])) { + foreach ($listeners as $listener) { + $this->on( + $eventName, + [$subscriber, $listener[0]], + isset($listener[1]) ? $listener[1] : 0 + ); + } + } else { + $this->on( + $eventName, + [$subscriber, $listeners[0]], + isset($listeners[1]) ? $listeners[1] : 0 + ); + } + } + } + + public function detach(SubscriberInterface $subscriber) + { + foreach ($subscriber->getEvents() as $eventName => $listener) { + $this->removeListener($eventName, [$subscriber, $listener[0]]); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/EmitterInterface.php b/vendor/guzzlehttp/guzzle/src/Event/EmitterInterface.php new file mode 100644 index 0000000..9783efd --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/EmitterInterface.php @@ -0,0 +1,96 @@ +transaction->exception; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/ErrorEvent.php b/vendor/guzzlehttp/guzzle/src/Event/ErrorEvent.php new file mode 100644 index 0000000..7432134 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/ErrorEvent.php @@ -0,0 +1,27 @@ +transaction->exception; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/EventInterface.php b/vendor/guzzlehttp/guzzle/src/Event/EventInterface.php new file mode 100644 index 0000000..97247e8 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/EventInterface.php @@ -0,0 +1,23 @@ +emitter) { + $this->emitter = new Emitter(); + } + + return $this->emitter; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php b/vendor/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php new file mode 100644 index 0000000..407dc92 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/ListenerAttacherTrait.php @@ -0,0 +1,88 @@ +getEmitter(); + foreach ($listeners as $el) { + if ($el['once']) { + $emitter->once($el['name'], $el['fn'], $el['priority']); + } else { + $emitter->on($el['name'], $el['fn'], $el['priority']); + } + } + } + + /** + * Extracts the allowed events from the provided array, and ignores anything + * else in the array. The event listener must be specified as a callable or + * as an array of event listener data ("name", "fn", "priority", "once"). + * + * @param array $source Array containing callables or hashes of data to be + * prepared as event listeners. + * @param array $events Names of events to look for in the provided $source + * array. Other keys are ignored. + * @return array + */ + private function prepareListeners(array $source, array $events) + { + $listeners = []; + foreach ($events as $name) { + if (isset($source[$name])) { + $this->buildListener($name, $source[$name], $listeners); + } + } + + return $listeners; + } + + /** + * Creates a complete event listener definition from the provided array of + * listener data. Also works recursively if more than one listeners are + * contained in the provided array. + * + * @param string $name Name of the event the listener is for. + * @param array|callable $data Event listener data to prepare. + * @param array $listeners Array of listeners, passed by reference. + * + * @throws \InvalidArgumentException if the event data is malformed. + */ + private function buildListener($name, $data, &$listeners) + { + static $defaults = ['priority' => 0, 'once' => false]; + + // If a callable is provided, normalize it to the array format. + if (is_callable($data)) { + $data = ['fn' => $data]; + } + + // Prepare the listener and add it to the array, recursively. + if (isset($data['fn'])) { + $data['name'] = $name; + $listeners[] = $data + $defaults; + } elseif (is_array($data)) { + foreach ($data as $listenerData) { + $this->buildListener($name, $listenerData, $listeners); + } + } else { + throw new \InvalidArgumentException('Each event listener must be a ' + . 'callable or an associative array containing a "fn" key.'); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/ProgressEvent.php b/vendor/guzzlehttp/guzzle/src/Event/ProgressEvent.php new file mode 100644 index 0000000..3fd0de4 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/ProgressEvent.php @@ -0,0 +1,51 @@ +downloadSize = $downloadSize; + $this->downloaded = $downloaded; + $this->uploadSize = $uploadSize; + $this->uploaded = $uploaded; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Event/RequestEvents.php b/vendor/guzzlehttp/guzzle/src/Event/RequestEvents.php new file mode 100644 index 0000000..f51d420 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Event/RequestEvents.php @@ -0,0 +1,56 @@ + ['methodName']] + * - ['eventName' => ['methodName', $priority]] + * - ['eventName' => [['methodName'], ['otherMethod']] + * - ['eventName' => [['methodName'], ['otherMethod', $priority]] + * - ['eventName' => [['methodName', $priority], ['otherMethod', $priority]] + * + * @return array + */ + public function getEvents(); +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php new file mode 100644 index 0000000..fd78431 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/BadResponseException.php @@ -0,0 +1,7 @@ +response = $response; + } + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php b/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php new file mode 100644 index 0000000..3f052d3 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php @@ -0,0 +1,121 @@ +getStatusCode() + : 0; + parent::__construct($message, $code, $previous); + $this->request = $request; + $this->response = $response; + } + + /** + * Wrap non-RequestExceptions with a RequestException + * + * @param RequestInterface $request + * @param \Exception $e + * + * @return RequestException + */ + public static function wrapException(RequestInterface $request, \Exception $e) + { + if ($e instanceof RequestException) { + return $e; + } elseif ($e instanceof ConnectException) { + return new HttpConnectException($e->getMessage(), $request, null, $e); + } else { + return new RequestException($e->getMessage(), $request, null, $e); + } + } + + /** + * Factory method to create a new exception with a normalized error message + * + * @param RequestInterface $request Request + * @param ResponseInterface $response Response received + * @param \Exception $previous Previous exception + * + * @return self + */ + public static function create( + RequestInterface $request, + ResponseInterface $response = null, + \Exception $previous = null + ) { + if (!$response) { + return new self('Error completing request', $request, null, $previous); + } + + $level = floor($response->getStatusCode() / 100); + if ($level == '4') { + $label = 'Client error response'; + $className = __NAMESPACE__ . '\\ClientException'; + } elseif ($level == '5') { + $label = 'Server error response'; + $className = __NAMESPACE__ . '\\ServerException'; + } else { + $label = 'Unsuccessful response'; + $className = __CLASS__; + } + + $message = $label . ' [url] ' . $request->getUrl() + . ' [status code] ' . $response->getStatusCode() + . ' [reason phrase] ' . $response->getReasonPhrase(); + + return new $className($message, $request, $response, $previous); + } + + /** + * Get the request that caused the exception + * + * @return RequestInterface + */ + public function getRequest() + { + return $this->request; + } + + /** + * Get the associated response + * + * @return ResponseInterface|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Check if a response was received + * + * @return bool + */ + public function hasResponse() + { + return $this->response !== null; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php new file mode 100644 index 0000000..7cdd340 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Exception/ServerException.php @@ -0,0 +1,7 @@ +error = $error; + } + + /** + * Get the associated error + * + * @return \LibXMLError|null + */ + public function getError() + { + return $this->error; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/HasDataTrait.php b/vendor/guzzlehttp/guzzle/src/HasDataTrait.php new file mode 100644 index 0000000..020dfc9 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/HasDataTrait.php @@ -0,0 +1,75 @@ +data); + } + + public function offsetGet($offset) + { + return isset($this->data[$offset]) ? $this->data[$offset] : null; + } + + public function offsetSet($offset, $value) + { + $this->data[$offset] = $value; + } + + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } + + public function toArray() + { + return $this->data; + } + + public function count() + { + return count($this->data); + } + + /** + * Get a value from the collection using a path syntax to retrieve nested + * data. + * + * @param string $path Path to traverse and retrieve a value from + * + * @return mixed|null + */ + public function getPath($path) + { + return Utils::getPath($this->data, $path); + } + + /** + * Set a value into a nested array key. Keys will be created as needed to + * set the value. + * + * @param string $path Path to set + * @param mixed $value Value to set at the key + * + * @throws \RuntimeException when trying to setPath using a nested path + * that travels through a scalar value + */ + public function setPath($path, $value) + { + Utils::setPath($this->data, $path, $value); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Message/AbstractMessage.php b/vendor/guzzlehttp/guzzle/src/Message/AbstractMessage.php new file mode 100644 index 0000000..f118e0f --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Message/AbstractMessage.php @@ -0,0 +1,253 @@ +getBody(); + } + + public function getProtocolVersion() + { + return $this->protocolVersion; + } + + public function getBody() + { + return $this->body; + } + + public function setBody(StreamInterface $body = null) + { + if ($body === null) { + // Setting a null body will remove the body of the request + $this->removeHeader('Content-Length'); + $this->removeHeader('Transfer-Encoding'); + } + + $this->body = $body; + } + + public function addHeader($header, $value) + { + if (is_array($value)) { + $current = array_merge($this->getHeaderAsArray($header), $value); + } else { + $current = $this->getHeaderAsArray($header); + $current[] = (string) $value; + } + + $this->setHeader($header, $current); + } + + public function addHeaders(array $headers) + { + foreach ($headers as $name => $header) { + $this->addHeader($name, $header); + } + } + + public function getHeader($header) + { + $name = strtolower($header); + return isset($this->headers[$name]) + ? implode(', ', $this->headers[$name]) + : ''; + } + + public function getHeaderAsArray($header) + { + $name = strtolower($header); + return isset($this->headers[$name]) ? $this->headers[$name] : []; + } + + public function getHeaders() + { + $headers = []; + foreach ($this->headers as $name => $values) { + $headers[$this->headerNames[$name]] = $values; + } + + return $headers; + } + + public function setHeader($header, $value) + { + $header = trim($header); + $name = strtolower($header); + $this->headerNames[$name] = $header; + + if (is_array($value)) { + foreach ($value as &$v) { + $v = trim($v); + } + $this->headers[$name] = $value; + } else { + $this->headers[$name] = [trim($value)]; + } + } + + public function setHeaders(array $headers) + { + $this->headers = $this->headerNames = []; + foreach ($headers as $key => $value) { + $this->addHeader($key, $value); + } + } + + public function hasHeader($header) + { + return isset($this->headers[strtolower($header)]); + } + + public function removeHeader($header) + { + $name = strtolower($header); + unset($this->headers[$name], $this->headerNames[$name]); + } + + /** + * Parse an array of header values containing ";" separated data into an + * array of associative arrays representing the header key value pair + * data of the header. When a parameter does not contain a value, but just + * contains a key, this function will inject a key with a '' string value. + * + * @param MessageInterface $message That contains the header + * @param string $header Header to retrieve from the message + * + * @return array Returns the parsed header values. + */ + public static function parseHeader(MessageInterface $message, $header) + { + static $trimmed = "\"' \n\t\r"; + $params = $matches = []; + + foreach (self::normalizeHeader($message, $header) as $val) { + $part = []; + foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) as $kvp) { + if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) { + $m = $matches[0]; + if (isset($m[1])) { + $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed); + } else { + $part[] = trim($m[0], $trimmed); + } + } + } + if ($part) { + $params[] = $part; + } + } + + return $params; + } + + /** + * Converts an array of header values that may contain comma separated + * headers into an array of headers with no comma separated values. + * + * @param MessageInterface $message That contains the header + * @param string $header Header to retrieve from the message + * + * @return array Returns the normalized header field values. + */ + public static function normalizeHeader(MessageInterface $message, $header) + { + $h = $message->getHeaderAsArray($header); + for ($i = 0, $total = count($h); $i < $total; $i++) { + if (strpos($h[$i], ',') === false) { + continue; + } + foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $h[$i]) as $v) { + $h[] = trim($v); + } + unset($h[$i]); + } + + return $h; + } + + /** + * Gets the start-line and headers of a message as a string + * + * @param MessageInterface $message + * + * @return string + */ + public static function getStartLineAndHeaders(MessageInterface $message) + { + return static::getStartLine($message) + . self::getHeadersAsString($message); + } + + /** + * Gets the headers of a message as a string + * + * @param MessageInterface $message + * + * @return string + */ + public static function getHeadersAsString(MessageInterface $message) + { + $result = ''; + foreach ($message->getHeaders() as $name => $values) { + $result .= "\r\n{$name}: " . implode(', ', $values); + } + + return $result; + } + + /** + * Gets the start line of a message + * + * @param MessageInterface $message + * + * @return string + * @throws \InvalidArgumentException + */ + public static function getStartLine(MessageInterface $message) + { + if ($message instanceof RequestInterface) { + return trim($message->getMethod() . ' ' + . $message->getResource()) + . ' HTTP/' . $message->getProtocolVersion(); + } elseif ($message instanceof ResponseInterface) { + return 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } else { + throw new \InvalidArgumentException('Unknown message type'); + } + } + + /** + * Accepts and modifies the options provided to the message in the + * constructor. + * + * Can be overridden in subclasses as necessary. + * + * @param array $options Options array passed by reference. + */ + protected function handleOptions(array &$options) + { + if (isset($options['protocol_version'])) { + $this->protocolVersion = $options['protocol_version']; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php b/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php new file mode 100644 index 0000000..ca42f20 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Message/AppliesHeadersInterface.php @@ -0,0 +1,24 @@ +then($onFulfilled, $onRejected, $onProgress), + [$future, 'wait'], + [$future, 'cancel'] + ); + } + + public function getStatusCode() + { + return $this->_value->getStatusCode(); + } + + public function setStatusCode($code) + { + $this->_value->setStatusCode($code); + } + + public function getReasonPhrase() + { + return $this->_value->getReasonPhrase(); + } + + public function setReasonPhrase($phrase) + { + $this->_value->setReasonPhrase($phrase); + } + + public function getEffectiveUrl() + { + return $this->_value->getEffectiveUrl(); + } + + public function setEffectiveUrl($url) + { + $this->_value->setEffectiveUrl($url); + } + + public function json(array $config = []) + { + return $this->_value->json($config); + } + + public function xml(array $config = []) + { + return $this->_value->xml($config); + } + + public function __toString() + { + try { + return $this->_value->__toString(); + } catch (\Exception $e) { + trigger_error($e->getMessage(), E_USER_WARNING); + return ''; + } + } + + public function getProtocolVersion() + { + return $this->_value->getProtocolVersion(); + } + + public function setBody(StreamInterface $body = null) + { + $this->_value->setBody($body); + } + + public function getBody() + { + return $this->_value->getBody(); + } + + public function getHeaders() + { + return $this->_value->getHeaders(); + } + + public function getHeader($header) + { + return $this->_value->getHeader($header); + } + + public function getHeaderAsArray($header) + { + return $this->_value->getHeaderAsArray($header); + } + + public function hasHeader($header) + { + return $this->_value->hasHeader($header); + } + + public function removeHeader($header) + { + $this->_value->removeHeader($header); + } + + public function addHeader($header, $value) + { + $this->_value->addHeader($header, $value); + } + + public function addHeaders(array $headers) + { + $this->_value->addHeaders($headers); + } + + public function setHeader($header, $value) + { + $this->_value->setHeader($header, $value); + } + + public function setHeaders(array $headers) + { + $this->_value->setHeaders($headers); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Message/MessageFactory.php b/vendor/guzzlehttp/guzzle/src/Message/MessageFactory.php new file mode 100644 index 0000000..b366d6d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Message/MessageFactory.php @@ -0,0 +1,364 @@ + 1, 'timeout' => 1, 'verify' => 1, 'ssl_key' => 1, + 'cert' => 1, 'proxy' => 1, 'debug' => 1, 'save_to' => 1, 'stream' => 1, + 'expect' => 1, 'future' => 1 + ]; + + /** @var array Default allow_redirects request option settings */ + private static $defaultRedirect = [ + 'max' => 5, + 'strict' => false, + 'referer' => false, + 'protocols' => ['http', 'https'] + ]; + + /** + * @param array $customOptions Associative array of custom request option + * names mapping to functions used to apply + * the option. The function accepts the request + * and the option value to apply. + */ + public function __construct(array $customOptions = []) + { + $this->errorPlugin = new HttpError(); + $this->redirectPlugin = new Redirect(); + $this->customOptions = $customOptions; + } + + public function createResponse( + $statusCode, + array $headers = [], + $body = null, + array $options = [] + ) { + if (null !== $body) { + $body = Stream::factory($body); + } + + return new Response($statusCode, $headers, $body, $options); + } + + public function createRequest($method, $url, array $options = []) + { + // Handle the request protocol version option that needs to be + // specified in the request constructor. + if (isset($options['version'])) { + $options['config']['protocol_version'] = $options['version']; + unset($options['version']); + } + + $request = new Request($method, $url, [], null, + isset($options['config']) ? $options['config'] : []); + + unset($options['config']); + + // Use a POST body by default + if (strtoupper($method) == 'POST' + && !isset($options['body']) + && !isset($options['json']) + ) { + $options['body'] = []; + } + + if ($options) { + $this->applyOptions($request, $options); + } + + return $request; + } + + /** + * Create a request or response object from an HTTP message string + * + * @param string $message Message to parse + * + * @return RequestInterface|ResponseInterface + * @throws \InvalidArgumentException if unable to parse a message + */ + public function fromMessage($message) + { + static $parser; + if (!$parser) { + $parser = new MessageParser(); + } + + // Parse a response + if (strtoupper(substr($message, 0, 4)) == 'HTTP') { + $data = $parser->parseResponse($message); + return $this->createResponse( + $data['code'], + $data['headers'], + $data['body'] === '' ? null : $data['body'], + $data + ); + } + + // Parse a request + if (!($data = ($parser->parseRequest($message)))) { + throw new \InvalidArgumentException('Unable to parse request'); + } + + return $this->createRequest( + $data['method'], + Url::buildUrl($data['request_url']), + [ + 'headers' => $data['headers'], + 'body' => $data['body'] === '' ? null : $data['body'], + 'config' => [ + 'protocol_version' => $data['protocol_version'] + ] + ] + ); + } + + /** + * Apply POST fields and files to a request to attempt to give an accurate + * representation. + * + * @param RequestInterface $request Request to update + * @param array $body Body to apply + */ + protected function addPostData(RequestInterface $request, array $body) + { + static $fields = ['string' => true, 'array' => true, 'NULL' => true, + 'boolean' => true, 'double' => true, 'integer' => true]; + + $post = new PostBody(); + foreach ($body as $key => $value) { + if (isset($fields[gettype($value)])) { + $post->setField($key, $value); + } elseif ($value instanceof PostFileInterface) { + $post->addFile($value); + } else { + $post->addFile(new PostFile($key, $value)); + } + } + + if ($request->getHeader('Content-Type') == 'multipart/form-data') { + $post->forceMultipartUpload(true); + } + + $request->setBody($post); + } + + protected function applyOptions( + RequestInterface $request, + array $options = [] + ) { + $config = $request->getConfig(); + $emitter = $request->getEmitter(); + + foreach ($options as $key => $value) { + + if (isset(self::$configMap[$key])) { + $config[$key] = $value; + continue; + } + + switch ($key) { + + case 'allow_redirects': + + if ($value === false) { + continue 2; + } + + if ($value === true) { + $value = self::$defaultRedirect; + } elseif (!is_array($value)) { + throw new Iae('allow_redirects must be true, false, or array'); + } else { + // Merge the default settings with the provided settings + $value += self::$defaultRedirect; + } + + $config['redirect'] = $value; + $emitter->attach($this->redirectPlugin); + break; + + case 'decode_content': + + if ($value === false) { + continue 2; + } + + $config['decode_content'] = true; + if ($value !== true) { + $request->setHeader('Accept-Encoding', $value); + } + break; + + case 'headers': + + if (!is_array($value)) { + throw new Iae('header value must be an array'); + } + foreach ($value as $k => $v) { + $request->setHeader($k, $v); + } + break; + + case 'exceptions': + + if ($value === true) { + $emitter->attach($this->errorPlugin); + } + break; + + case 'body': + + if (is_array($value)) { + $this->addPostData($request, $value); + } elseif ($value !== null) { + $request->setBody(Stream::factory($value)); + } + break; + + case 'auth': + + if (!$value) { + continue 2; + } + + if (is_array($value)) { + $type = isset($value[2]) ? strtolower($value[2]) : 'basic'; + } else { + $type = strtolower($value); + } + + $config['auth'] = $value; + + if ($type == 'basic') { + $request->setHeader( + 'Authorization', + 'Basic ' . base64_encode("$value[0]:$value[1]") + ); + } elseif ($type == 'digest') { + // @todo: Do not rely on curl + $config->setPath('curl/' . CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); + $config->setPath('curl/' . CURLOPT_USERPWD, "$value[0]:$value[1]"); + } + break; + + case 'query': + + if ($value instanceof Query) { + $original = $request->getQuery(); + // Do not overwrite existing query string variables by + // overwriting the object with the query string data passed + // in the URL + $value->overwriteWith($original->toArray()); + $request->setQuery($value); + } elseif (is_array($value)) { + // Do not overwrite existing query string variables + $query = $request->getQuery(); + foreach ($value as $k => $v) { + if (!isset($query[$k])) { + $query[$k] = $v; + } + } + } else { + throw new Iae('query must be an array or Query object'); + } + break; + + case 'cookies': + + if ($value === true) { + static $cookie = null; + if (!$cookie) { + $cookie = new Cookie(); + } + $emitter->attach($cookie); + } elseif (is_array($value)) { + $emitter->attach( + new Cookie(CookieJar::fromArray($value, $request->getHost())) + ); + } elseif ($value instanceof CookieJarInterface) { + $emitter->attach(new Cookie($value)); + } elseif ($value !== false) { + throw new Iae('cookies must be an array, true, or CookieJarInterface'); + } + break; + + case 'events': + + if (!is_array($value)) { + throw new Iae('events must be an array'); + } + + $this->attachListeners($request, + $this->prepareListeners( + $value, + ['before', 'complete', 'error', 'progress', 'end'] + ) + ); + break; + + case 'subscribers': + + if (!is_array($value)) { + throw new Iae('subscribers must be an array'); + } + + foreach ($value as $subscribers) { + $emitter->attach($subscribers); + } + break; + + case 'json': + + $request->setBody(Stream::factory(json_encode($value))); + if (!$request->hasHeader('Content-Type')) { + $request->setHeader('Content-Type', 'application/json'); + } + break; + + default: + + // Check for custom handler functions. + if (isset($this->customOptions[$key])) { + $fn = $this->customOptions[$key]; + $fn($request, $value); + continue 2; + } + + throw new Iae("No method can handle the {$key} config key"); + } + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php b/vendor/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php new file mode 100644 index 0000000..86ae9c7 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Message/MessageFactoryInterface.php @@ -0,0 +1,71 @@ +getHeaders() as $name => $values) { + * echo $name . ": " . implode(", ", $values); + * } + * + * @return array Returns an associative array of the message's headers. + */ + public function getHeaders(); + + /** + * Retrieve a header by the given case-insensitive name. + * + * @param string $header Case-insensitive header name. + * + * @return string + */ + public function getHeader($header); + + /** + * Retrieves a header by the given case-insensitive name as an array of strings. + * + * @param string $header Case-insensitive header name. + * + * @return string[] + */ + public function getHeaderAsArray($header); + + /** + * Checks if a header exists by the given case-insensitive name. + * + * @param string $header Case-insensitive header name. + * + * @return bool Returns true if any header names match the given header + * name using a case-insensitive string comparison. Returns false if + * no matching header name is found in the message. + */ + public function hasHeader($header); + + /** + * Remove a specific header by case-insensitive name. + * + * @param string $header Case-insensitive header name. + */ + public function removeHeader($header); + + /** + * Appends a header value to any existing values associated with the + * given header name. + * + * @param string $header Header name to add + * @param string $value Value of the header + */ + public function addHeader($header, $value); + + /** + * Merges in an associative array of headers. + * + * Each array key MUST be a string representing the case-insensitive name + * of a header. Each value MUST be either a string or an array of strings. + * For each value, the value is appended to any existing header of the same + * name, or, if a header does not already exist by the given name, then the + * header is added. + * + * @param array $headers Associative array of headers to add to the message + */ + public function addHeaders(array $headers); + + /** + * Sets a header, replacing any existing values of any headers with the + * same case-insensitive name. + * + * The header values MUST be a string or an array of strings. + * + * @param string $header Header name + * @param string|array $value Header value(s) + */ + public function setHeader($header, $value); + + /** + * Sets headers, replacing any headers that have already been set on the + * message. + * + * The array keys MUST be a string. The array values must be either a + * string or an array of strings. + * + * @param array $headers Headers to set. + */ + public function setHeaders(array $headers); +} diff --git a/vendor/guzzlehttp/guzzle/src/Message/MessageParser.php b/vendor/guzzlehttp/guzzle/src/Message/MessageParser.php new file mode 100644 index 0000000..c3cc195 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Message/MessageParser.php @@ -0,0 +1,171 @@ +parseMessage($message))) { + return false; + } + + // Parse the protocol and protocol version + if (isset($parts['start_line'][2])) { + $startParts = explode('/', $parts['start_line'][2]); + $protocol = strtoupper($startParts[0]); + $version = isset($startParts[1]) ? $startParts[1] : '1.1'; + } else { + $protocol = 'HTTP'; + $version = '1.1'; + } + + $parsed = [ + 'method' => strtoupper($parts['start_line'][0]), + 'protocol' => $protocol, + 'protocol_version' => $version, + 'headers' => $parts['headers'], + 'body' => $parts['body'] + ]; + + $parsed['request_url'] = $this->getUrlPartsFromMessage( + (isset($parts['start_line'][1]) ? $parts['start_line'][1] : ''), $parsed); + + return $parsed; + } + + /** + * Parse an HTTP response message into an associative array of parts. + * + * @param string $message HTTP response to parse + * + * @return array|bool Returns false if the message is invalid + */ + public function parseResponse($message) + { + if (!($parts = $this->parseMessage($message))) { + return false; + } + + list($protocol, $version) = explode('/', trim($parts['start_line'][0])); + + return [ + 'protocol' => $protocol, + 'protocol_version' => $version, + 'code' => $parts['start_line'][1], + 'reason_phrase' => isset($parts['start_line'][2]) ? $parts['start_line'][2] : '', + 'headers' => $parts['headers'], + 'body' => $parts['body'] + ]; + } + + /** + * Parse a message into parts + * + * @param string $message Message to parse + * + * @return array|bool + */ + private function parseMessage($message) + { + if (!$message) { + return false; + } + + $startLine = null; + $headers = []; + $body = ''; + + // Iterate over each line in the message, accounting for line endings + $lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE); + for ($i = 0, $totalLines = count($lines); $i < $totalLines; $i += 2) { + + $line = $lines[$i]; + + // If two line breaks were encountered, then this is the end of body + if (empty($line)) { + if ($i < $totalLines - 1) { + $body = implode('', array_slice($lines, $i + 2)); + } + break; + } + + // Parse message headers + if (!$startLine) { + $startLine = explode(' ', $line, 3); + } elseif (strpos($line, ':')) { + $parts = explode(':', $line, 2); + $key = trim($parts[0]); + $value = isset($parts[1]) ? trim($parts[1]) : ''; + if (!isset($headers[$key])) { + $headers[$key] = $value; + } elseif (!is_array($headers[$key])) { + $headers[$key] = [$headers[$key], $value]; + } else { + $headers[$key][] = $value; + } + } + } + + return [ + 'start_line' => $startLine, + 'headers' => $headers, + 'body' => $body + ]; + } + + /** + * Create URL parts from HTTP message parts + * + * @param string $requestUrl Associated URL + * @param array $parts HTTP message parts + * + * @return array + */ + private function getUrlPartsFromMessage($requestUrl, array $parts) + { + // Parse the URL information from the message + $urlParts = ['path' => $requestUrl, 'scheme' => 'http']; + + // Check for the Host header + if (isset($parts['headers']['Host'])) { + $urlParts['host'] = $parts['headers']['Host']; + } elseif (isset($parts['headers']['host'])) { + $urlParts['host'] = $parts['headers']['host']; + } else { + $urlParts['host'] = null; + } + + if (false === strpos($urlParts['host'], ':')) { + $urlParts['port'] = ''; + } else { + $hostParts = explode(':', $urlParts['host']); + $urlParts['host'] = trim($hostParts[0]); + $urlParts['port'] = (int) trim($hostParts[1]); + if ($urlParts['port'] == 443) { + $urlParts['scheme'] = 'https'; + } + } + + // Check if a query is present + $path = $urlParts['path']; + $qpos = strpos($path, '?'); + if ($qpos) { + $urlParts['query'] = substr($path, $qpos + 1); + $urlParts['path'] = substr($path, 0, $qpos); + } else { + $urlParts['query'] = ''; + } + + return $urlParts; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Message/Request.php b/vendor/guzzlehttp/guzzle/src/Message/Request.php new file mode 100644 index 0000000..38714af --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Message/Request.php @@ -0,0 +1,195 @@ +setUrl($url); + $this->method = strtoupper($method); + $this->handleOptions($options); + $this->transferOptions = new Collection($options); + $this->addPrepareEvent(); + + if ($body !== null) { + $this->setBody($body); + } + + if ($headers) { + foreach ($headers as $key => $value) { + $this->addHeader($key, $value); + } + } + } + + public function __clone() + { + if ($this->emitter) { + $this->emitter = clone $this->emitter; + } + $this->transferOptions = clone $this->transferOptions; + $this->url = clone $this->url; + } + + public function setUrl($url) + { + $this->url = $url instanceof Url ? $url : Url::fromString($url); + $this->updateHostHeaderFromUrl(); + } + + public function getUrl() + { + return (string) $this->url; + } + + public function setQuery($query) + { + $this->url->setQuery($query); + } + + public function getQuery() + { + return $this->url->getQuery(); + } + + public function setMethod($method) + { + $this->method = strtoupper($method); + } + + public function getMethod() + { + return $this->method; + } + + public function getScheme() + { + return $this->url->getScheme(); + } + + public function setScheme($scheme) + { + $this->url->setScheme($scheme); + } + + public function getPort() + { + return $this->url->getPort(); + } + + public function setPort($port) + { + $this->url->setPort($port); + $this->updateHostHeaderFromUrl(); + } + + public function getHost() + { + return $this->url->getHost(); + } + + public function setHost($host) + { + $this->url->setHost($host); + $this->updateHostHeaderFromUrl(); + } + + public function getPath() + { + return '/' . ltrim($this->url->getPath(), '/'); + } + + public function setPath($path) + { + $this->url->setPath($path); + } + + public function getResource() + { + $resource = $this->getPath(); + if ($query = (string) $this->url->getQuery()) { + $resource .= '?' . $query; + } + + return $resource; + } + + public function getConfig() + { + return $this->transferOptions; + } + + protected function handleOptions(array &$options) + { + parent::handleOptions($options); + // Use a custom emitter if one is specified, and remove it from + // options that are exposed through getConfig() + if (isset($options['emitter'])) { + $this->emitter = $options['emitter']; + unset($options['emitter']); + } + } + + /** + * Adds a subscriber that ensures a request's body is prepared before + * sending. + */ + private function addPrepareEvent() + { + static $subscriber; + if (!$subscriber) { + $subscriber = new Prepare(); + } + + $this->getEmitter()->attach($subscriber); + } + + private function updateHostHeaderFromUrl() + { + $port = $this->url->getPort(); + $scheme = $this->url->getScheme(); + if ($host = $this->url->getHost()) { + if (($port == 80 && $scheme == 'http') || + ($port == 443 && $scheme == 'https') + ) { + $this->setHeader('Host', $host); + } else { + $this->setHeader('Host', "{$host}:{$port}"); + } + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Message/RequestInterface.php b/vendor/guzzlehttp/guzzle/src/Message/RequestInterface.php new file mode 100644 index 0000000..f6a69d1 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Message/RequestInterface.php @@ -0,0 +1,136 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Reserved for WebDAV advanced collections expired proposal', + 426 => 'Upgrade required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates (Experimental)', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** @var string The reason phrase of the response (human readable code) */ + private $reasonPhrase; + + /** @var string The status code of the response */ + private $statusCode; + + /** @var string The effective URL that returned this response */ + private $effectiveUrl; + + /** + * @param int|string $statusCode The response status code (e.g. 200) + * @param array $headers The response headers + * @param StreamInterface $body The body of the response + * @param array $options Response message options + * - reason_phrase: Set a custom reason phrase + * - protocol_version: Set a custom protocol version + */ + public function __construct( + $statusCode, + array $headers = [], + StreamInterface $body = null, + array $options = [] + ) { + $this->statusCode = (int) $statusCode; + $this->handleOptions($options); + + // Assume a reason phrase if one was not applied as an option + if (!$this->reasonPhrase && + isset(self::$statusTexts[$this->statusCode]) + ) { + $this->reasonPhrase = self::$statusTexts[$this->statusCode]; + } + + if ($headers) { + $this->setHeaders($headers); + } + + if ($body) { + $this->setBody($body); + } + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($code) + { + return $this->statusCode = (int) $code; + } + + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + public function setReasonPhrase($phrase) + { + return $this->reasonPhrase = $phrase; + } + + public function json(array $config = []) + { + try { + return Utils::jsonDecode( + (string) $this->getBody(), + isset($config['object']) ? !$config['object'] : true, + 512, + isset($config['big_int_strings']) ? JSON_BIGINT_AS_STRING : 0 + ); + } catch (\InvalidArgumentException $e) { + throw new ParseException( + $e->getMessage(), + $this + ); + } + } + + public function xml(array $config = []) + { + $disableEntities = libxml_disable_entity_loader(true); + $internalErrors = libxml_use_internal_errors(true); + + try { + // Allow XML to be retrieved even if there is no response body + $xml = new \SimpleXMLElement( + (string) $this->getBody() ?: '', + isset($config['libxml_options']) ? $config['libxml_options'] : LIBXML_NONET, + false, + isset($config['ns']) ? $config['ns'] : '', + isset($config['ns_is_prefix']) ? $config['ns_is_prefix'] : false + ); + libxml_disable_entity_loader($disableEntities); + libxml_use_internal_errors($internalErrors); + } catch (\Exception $e) { + libxml_disable_entity_loader($disableEntities); + libxml_use_internal_errors($internalErrors); + throw new XmlParseException( + 'Unable to parse response body into XML: ' . $e->getMessage(), + $this, + $e, + (libxml_get_last_error()) ?: null + ); + } + + return $xml; + } + + public function getEffectiveUrl() + { + return $this->effectiveUrl; + } + + public function setEffectiveUrl($url) + { + $this->effectiveUrl = $url; + } + + /** + * Accepts and modifies the options provided to the response in the + * constructor. + * + * @param array $options Options array passed by reference. + */ + protected function handleOptions(array &$options = []) + { + parent::handleOptions($options); + if (isset($options['reason_phrase'])) { + $this->reasonPhrase = $options['reason_phrase']; + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Message/ResponseInterface.php b/vendor/guzzlehttp/guzzle/src/Message/ResponseInterface.php new file mode 100644 index 0000000..c0ae9be --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Message/ResponseInterface.php @@ -0,0 +1,111 @@ + 'text/vnd.in3d.3dml', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'asa' => 'text/plain', + 'asax' => 'application/octet-stream', + 'asc' => 'application/pgp-signature', + 'ascx' => 'text/plain', + 'asf' => 'video/x-ms-asf', + 'ashx' => 'text/plain', + 'asm' => 'text/x-asm', + 'asmx' => 'text/plain', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asp' => 'text/plain', + 'aspx' => 'text/plain', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'axd' => 'text/plain', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'cab' => 'application/vnd.ms-cab-compressed', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfc' => 'application/x-coldfusion', + 'cfm' => 'application/x-coldfusion', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'cs' => 'text/plain', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxt' => 'application/vnd.geonext', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hta' => 'application/octet-stream', + 'htc' => 'text/html', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/octet-stream', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'text/javascript', + 'json' => 'application/json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/mp4', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nsf' => 'application/vnd.lotus-notes', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-font-otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp-encrypted', + 'php' => 'text/x-php', + 'phps' => 'application/x-httpd-phps', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rb' => 'text/plain', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'resx' => 'text/xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sig' => 'application/pgp-signature', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'src' => 'application/x-wais-source', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'image/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvx' => 'application/vnd.dece.unspecified', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-ms-wmz', + 'woff' => 'application/x-font-woff', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'application/vnd.hzn-3d-crossword', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml' + ); + + /** + * Get a singleton instance of the class + * + * @return self + * @codeCoverageIgnore + */ + public static function getInstance() + { + if (!self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Get a mimetype value from a file extension + * + * @param string $extension File extension + * + * @return string|null + * + */ + public function fromExtension($extension) + { + $extension = strtolower($extension); + + return isset($this->mimetypes[$extension]) + ? $this->mimetypes[$extension] + : null; + } + + /** + * Get a mimetype from a filename + * + * @param string $filename Filename to generate a mimetype from + * + * @return string|null + */ + public function fromFilename($filename) + { + return $this->fromExtension(pathinfo($filename, PATHINFO_EXTENSION)); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Pool.php b/vendor/guzzlehttp/guzzle/src/Pool.php new file mode 100644 index 0000000..7b9d83a --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Pool.php @@ -0,0 +1,333 @@ +client = $client; + $this->iter = $this->coerceIterable($requests); + $this->deferred = new Deferred(); + $this->promise = $this->deferred->promise(); + $this->poolSize = isset($options['pool_size']) + ? $options['pool_size'] : 25; + $this->eventListeners = $this->prepareListeners( + $options, + ['before', 'complete', 'error', 'end'] + ); + } + + /** + * Sends multiple requests in parallel and returns an array of responses + * and exceptions that uses the same ordering as the provided requests. + * + * IMPORTANT: This method keeps every request and response in memory, and + * as such, is NOT recommended when sending a large number or an + * indeterminate number of requests concurrently. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send in parallel + * @param array $options Passes through the options available in + * {@see GuzzleHttp\Pool::__construct} + * + * @return BatchResults Returns a container for the results. + * @throws \InvalidArgumentException if the event format is incorrect. + */ + public static function batch( + ClientInterface $client, + $requests, + array $options = [] + ) { + $hash = new \SplObjectStorage(); + foreach ($requests as $request) { + $hash->attach($request); + } + + // In addition to the normally run events when requests complete, add + // and event to continuously track the results of transfers in the hash. + (new self($client, $requests, RequestEvents::convertEventArray( + $options, + ['end'], + [ + 'priority' => RequestEvents::LATE, + 'fn' => function (EndEvent $e) use ($hash) { + $hash[$e->getRequest()] = $e->getException() + ? $e->getException() + : $e->getResponse(); + } + ] + )))->wait(); + + return new BatchResults($hash); + } + + /** + * Creates a Pool and immediately sends the requests. + * + * @param ClientInterface $client Client used to send the requests + * @param array|\Iterator $requests Requests to send in parallel + * @param array $options Passes through the options available in + * {@see GuzzleHttp\Pool::__construct} + */ + public static function send( + ClientInterface $client, + $requests, + array $options = [] + ) { + $pool = new self($client, $requests, $options); + $pool->wait(); + } + + private function getPoolSize() + { + return is_callable($this->poolSize) + ? call_user_func($this->poolSize, count($this->waitQueue)) + : $this->poolSize; + } + + /** + * Add as many requests as possible up to the current pool limit. + */ + private function addNextRequests() + { + $limit = max($this->getPoolSize() - count($this->waitQueue), 0); + while ($limit--) { + if (!$this->addNextRequest()) { + break; + } + } + } + + public function wait() + { + if ($this->isRealized) { + return false; + } + + // Seed the pool with N number of requests. + $this->addNextRequests(); + + // Stop if the pool was cancelled while transferring requests. + if ($this->isRealized) { + return false; + } + + // Wait on any outstanding FutureResponse objects. + while ($response = array_pop($this->waitQueue)) { + try { + $response->wait(); + } catch (\Exception $e) { + // Eat exceptions because they should be handled asynchronously + } + $this->addNextRequests(); + } + + // Clean up no longer needed state. + $this->isRealized = true; + $this->waitQueue = $this->eventListeners = []; + $this->client = $this->iter = null; + $this->deferred->resolve(true); + + return true; + } + + /** + * {@inheritdoc} + * + * Attempt to cancel all outstanding requests (requests that are queued for + * dereferencing). Returns true if all outstanding requests can be + * cancelled. + * + * @return bool + */ + public function cancel() + { + if ($this->isRealized) { + return false; + } + + $success = $this->isRealized = true; + foreach ($this->waitQueue as $response) { + if (!$response->cancel()) { + $success = false; + } + } + + return $success; + } + + /** + * Returns a promise that is invoked when the pool completed. There will be + * no passed value. + * + * {@inheritdoc} + */ + public function then( + callable $onFulfilled = null, + callable $onRejected = null, + callable $onProgress = null + ) { + return $this->promise->then($onFulfilled, $onRejected, $onProgress); + } + + public function promise() + { + return $this->promise; + } + + private function coerceIterable($requests) + { + if ($requests instanceof \Iterator) { + return $requests; + } elseif (is_array($requests)) { + return new \ArrayIterator($requests); + } + + throw new \InvalidArgumentException('Expected Iterator or array. ' + . 'Found ' . Core::describeType($requests)); + } + + /** + * Adds the next request to pool and tracks what requests need to be + * dereferenced when completing the pool. + */ + private function addNextRequest() + { + add_next: + + if ($this->isRealized || !$this->iter || !$this->iter->valid()) { + return false; + } + + $request = $this->iter->current(); + $this->iter->next(); + + if (!($request instanceof RequestInterface)) { + throw new \InvalidArgumentException(sprintf( + 'All requests in the provided iterator must implement ' + . 'RequestInterface. Found %s', + Core::describeType($request) + )); + } + + // Be sure to use "lazy" futures, meaning they do not send right away. + $request->getConfig()->set('future', 'lazy'); + $hash = spl_object_hash($request); + $this->attachListeners($request, $this->eventListeners); + $request->getEmitter()->on('before', [$this, '_trackRetries'], RequestEvents::EARLY); + $response = $this->client->send($request); + $this->waitQueue[$hash] = $response; + $promise = $response->promise(); + + // Don't recursively call itself for completed or rejected responses. + if ($promise instanceof FulfilledPromise + || $promise instanceof RejectedPromise + ) { + try { + $this->finishResponse($request, $response->wait(), $hash); + } catch (\Exception $e) { + $this->finishResponse($request, $e, $hash); + } + goto add_next; + } + + // Use this function for both resolution and rejection. + $thenFn = function ($value) use ($request, $hash) { + $this->finishResponse($request, $value, $hash); + if (!$request->getConfig()->get('_pool_retries')) { + $this->addNextRequests(); + } + }; + + $promise->then($thenFn, $thenFn); + + return true; + } + + public function _trackRetries(BeforeEvent $e) + { + $e->getRequest()->getConfig()->set('_pool_retries', $e->getRetryCount()); + } + + private function finishResponse($request, $value, $hash) + { + unset($this->waitQueue[$hash]); + $result = $value instanceof ResponseInterface + ? ['request' => $request, 'response' => $value, 'error' => null] + : ['request' => $request, 'response' => null, 'error' => $value]; + $this->deferred->notify($result); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Post/MultipartBody.php b/vendor/guzzlehttp/guzzle/src/Post/MultipartBody.php new file mode 100644 index 0000000..1149e62 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Post/MultipartBody.php @@ -0,0 +1,109 @@ +boundary = $boundary ?: uniqid(); + $this->stream = $this->createStream($fields, $files); + } + + /** + * Get the boundary + * + * @return string + */ + public function getBoundary() + { + return $this->boundary; + } + + public function isWritable() + { + return false; + } + + /** + * Get the string needed to transfer a POST field + */ + private function getFieldString($name, $value) + { + return sprintf( + "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", + $this->boundary, + $name, + $value + ); + } + + /** + * Get the headers needed before transferring the content of a POST file + */ + private function getFileHeaders(PostFileInterface $file) + { + $headers = ''; + foreach ($file->getHeaders() as $key => $value) { + $headers .= "{$key}: {$value}\r\n"; + } + + return "--{$this->boundary}\r\n" . trim($headers) . "\r\n\r\n"; + } + + /** + * Create the aggregate stream that will be used to upload the POST data + */ + protected function createStream(array $fields, array $files) + { + $stream = new AppendStream(); + + foreach ($fields as $name => $fieldValues) { + foreach ((array) $fieldValues as $value) { + $stream->addStream( + Stream::factory($this->getFieldString($name, $value)) + ); + } + } + + foreach ($files as $file) { + + if (!$file instanceof PostFileInterface) { + throw new \InvalidArgumentException('All POST fields must ' + . 'implement PostFieldInterface'); + } + + $stream->addStream( + Stream::factory($this->getFileHeaders($file)) + ); + $stream->addStream($file->getContent()); + $stream->addStream(Stream::factory("\r\n")); + } + + // Add the trailing boundary with CRLF + $stream->addStream(Stream::factory("--{$this->boundary}--\r\n")); + + return $stream; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Post/PostBody.php b/vendor/guzzlehttp/guzzle/src/Post/PostBody.php new file mode 100644 index 0000000..ed14d1f --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Post/PostBody.php @@ -0,0 +1,287 @@ +files || $this->forceMultipart) { + $request->setHeader( + 'Content-Type', + 'multipart/form-data; boundary=' . $this->getBody()->getBoundary() + ); + } elseif ($this->fields && !$request->hasHeader('Content-Type')) { + $request->setHeader( + 'Content-Type', + 'application/x-www-form-urlencoded' + ); + } + + if ($size = $this->getSize()) { + $request->setHeader('Content-Length', $size); + } + } + + public function forceMultipartUpload($force) + { + $this->forceMultipart = $force; + } + + public function setAggregator(callable $aggregator) + { + $this->aggregator = $aggregator; + } + + public function setField($name, $value) + { + $this->fields[$name] = $value; + $this->mutate(); + } + + public function replaceFields(array $fields) + { + $this->fields = $fields; + $this->mutate(); + } + + public function getField($name) + { + return isset($this->fields[$name]) ? $this->fields[$name] : null; + } + + public function removeField($name) + { + unset($this->fields[$name]); + $this->mutate(); + } + + public function getFields($asString = false) + { + if (!$asString) { + return $this->fields; + } + + $query = new Query($this->fields); + $query->setEncodingType(Query::RFC1738); + $query->setAggregator($this->getAggregator()); + + return (string) $query; + } + + public function hasField($name) + { + return isset($this->fields[$name]); + } + + public function getFile($name) + { + foreach ($this->files as $file) { + if ($file->getName() == $name) { + return $file; + } + } + + return null; + } + + public function getFiles() + { + return $this->files; + } + + public function addFile(PostFileInterface $file) + { + $this->files[] = $file; + $this->mutate(); + } + + public function clearFiles() + { + $this->files = []; + $this->mutate(); + } + + /** + * Returns the numbers of fields + files + * + * @return int + */ + public function count() + { + return count($this->files) + count($this->fields); + } + + public function __toString() + { + return (string) $this->getBody(); + } + + public function getContents($maxLength = -1) + { + return $this->getBody()->getContents(); + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->detached = true; + $this->fields = $this->files = []; + + if ($this->body) { + $this->body->close(); + $this->body = null; + } + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function eof() + { + return $this->getBody()->eof(); + } + + public function tell() + { + return $this->body ? $this->body->tell() : 0; + } + + public function isSeekable() + { + return true; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function getSize() + { + return $this->getBody()->getSize(); + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->getBody()->seek($offset, $whence); + } + + public function read($length) + { + return $this->getBody()->read($length); + } + + public function write($string) + { + return false; + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } + + /** + * Return a stream object that is built from the POST fields and files. + * + * If one has already been created, the previously created stream will be + * returned. + */ + private function getBody() + { + if ($this->body) { + return $this->body; + } elseif ($this->files || $this->forceMultipart) { + return $this->body = $this->createMultipart(); + } elseif ($this->fields) { + return $this->body = $this->createUrlEncoded(); + } else { + return $this->body = Stream::factory(); + } + } + + /** + * Get the aggregator used to join multi-valued field parameters + * + * @return callable + */ + final protected function getAggregator() + { + if (!$this->aggregator) { + $this->aggregator = Query::phpAggregator(); + } + + return $this->aggregator; + } + + /** + * Creates a multipart/form-data body stream + * + * @return MultipartBody + */ + private function createMultipart() + { + // Flatten the nested query string values using the correct aggregator + return new MultipartBody( + call_user_func($this->getAggregator(), $this->fields), + $this->files + ); + } + + /** + * Creates an application/x-www-form-urlencoded stream body + * + * @return StreamInterface + */ + private function createUrlEncoded() + { + return Stream::factory($this->getFields(true)); + } + + /** + * Get rid of any cached data + */ + private function mutate() + { + $this->body = null; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php b/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php new file mode 100644 index 0000000..c2ec9a6 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Post/PostBodyInterface.php @@ -0,0 +1,109 @@ +headers = $headers; + $this->name = $name; + $this->prepareContent($content); + $this->prepareFilename($filename); + $this->prepareDefaultHeaders(); + } + + public function getName() + { + return $this->name; + } + + public function getFilename() + { + return $this->filename; + } + + public function getContent() + { + return $this->content; + } + + public function getHeaders() + { + return $this->headers; + } + + /** + * Prepares the contents of a POST file. + * + * @param mixed $content Content of the POST file + */ + private function prepareContent($content) + { + $this->content = $content; + + if (!($this->content instanceof StreamInterface)) { + $this->content = Stream::factory($this->content); + } elseif ($this->content instanceof MultipartBody) { + if (!$this->hasHeader('Content-Disposition')) { + $disposition = 'form-data; name="' . $this->name .'"'; + $this->headers['Content-Disposition'] = $disposition; + } + + if (!$this->hasHeader('Content-Type')) { + $this->headers['Content-Type'] = sprintf( + "multipart/form-data; boundary=%s", + $this->content->getBoundary() + ); + } + } + } + + /** + * Applies a file name to the POST file based on various checks. + * + * @param string|null $filename Filename to apply (or null to guess) + */ + private function prepareFilename($filename) + { + $this->filename = $filename; + + if (!$this->filename) { + $this->filename = $this->content->getMetadata('uri'); + } + + if (!$this->filename || substr($this->filename, 0, 6) === 'php://') { + $this->filename = $this->name; + } + } + + /** + * Applies default Content-Disposition and Content-Type headers if needed. + */ + private function prepareDefaultHeaders() + { + // Set a default content-disposition header if one was no provided + if (!$this->hasHeader('Content-Disposition')) { + $this->headers['Content-Disposition'] = sprintf( + 'form-data; name="%s"; filename="%s"', + $this->name, + basename($this->filename) + ); + } + + // Set a default Content-Type if one was not supplied + if (!$this->hasHeader('Content-Type')) { + $this->headers['Content-Type'] = Mimetypes::getInstance() + ->fromFilename($this->filename) ?: 'text/plain'; + } + } + + /** + * Check if a specific header exists on the POST file by name. + * + * @param string $name Case-insensitive header to check + * + * @return bool + */ + private function hasHeader($name) + { + return isset(array_change_key_case($this->headers)[strtolower($name)]); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Post/PostFileInterface.php b/vendor/guzzlehttp/guzzle/src/Post/PostFileInterface.php new file mode 100644 index 0000000..2e816c0 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Post/PostFileInterface.php @@ -0,0 +1,41 @@ +setEncodingType($urlEncoding); + } + + $qp->parseInto($q, $query, $urlEncoding); + + return $q; + } + + /** + * Convert the query string parameters to a query string string + * + * @return string + */ + public function __toString() + { + if (!$this->data) { + return ''; + } + + // The default aggregator is statically cached + static $defaultAggregator; + + if (!$this->aggregator) { + if (!$defaultAggregator) { + $defaultAggregator = self::phpAggregator(); + } + $this->aggregator = $defaultAggregator; + } + + $result = ''; + $aggregator = $this->aggregator; + $encoder = $this->encoding; + + foreach ($aggregator($this->data) as $key => $values) { + foreach ($values as $value) { + if ($result) { + $result .= '&'; + } + $result .= $encoder($key); + if ($value !== null) { + $result .= '=' . $encoder($value); + } + } + } + + return $result; + } + + /** + * Controls how multi-valued query string parameters are aggregated into a + * string. + * + * $query->setAggregator($query::duplicateAggregator()); + * + * @param callable $aggregator Callable used to convert a deeply nested + * array of query string variables into a flattened array of key value + * pairs. The callable accepts an array of query data and returns a + * flattened array of key value pairs where each value is an array of + * strings. + */ + public function setAggregator(callable $aggregator) + { + $this->aggregator = $aggregator; + } + + /** + * Specify how values are URL encoded + * + * @param string|bool $type One of 'RFC1738', 'RFC3986', or false to disable encoding + * + * @throws \InvalidArgumentException + */ + public function setEncodingType($type) + { + switch ($type) { + case self::RFC3986: + $this->encoding = 'rawurlencode'; + break; + case self::RFC1738: + $this->encoding = 'urlencode'; + break; + case false: + $this->encoding = function ($v) { return $v; }; + break; + default: + throw new \InvalidArgumentException('Invalid URL encoding type'); + } + } + + /** + * Query string aggregator that does not aggregate nested query string + * values and allows duplicates in the resulting array. + * + * Example: http://test.com?q=1&q=2 + * + * @return callable + */ + public static function duplicateAggregator() + { + return function (array $data) { + return self::walkQuery($data, '', function ($key, $prefix) { + return is_int($key) ? $prefix : "{$prefix}[{$key}]"; + }); + }; + } + + /** + * Aggregates nested query string variables using the same technique as + * ``http_build_query()``. + * + * @param bool $numericIndices Pass false to not include numeric indices + * when multi-values query string parameters are present. + * + * @return callable + */ + public static function phpAggregator($numericIndices = true) + { + return function (array $data) use ($numericIndices) { + return self::walkQuery( + $data, + '', + function ($key, $prefix) use ($numericIndices) { + return !$numericIndices && is_int($key) + ? "{$prefix}[]" + : "{$prefix}[{$key}]"; + } + ); + }; + } + + /** + * Easily create query aggregation functions by providing a key prefix + * function to this query string array walker. + * + * @param array $query Query string to walk + * @param string $keyPrefix Key prefix (start with '') + * @param callable $prefixer Function used to create a key prefix + * + * @return array + */ + public static function walkQuery(array $query, $keyPrefix, callable $prefixer) + { + $result = []; + foreach ($query as $key => $value) { + if ($keyPrefix) { + $key = $prefixer($key, $keyPrefix); + } + if (is_array($value)) { + $result += self::walkQuery($value, $key, $prefixer); + } elseif (isset($result[$key])) { + $result[$key][] = $value; + } else { + $result[$key] = array($value); + } + } + + return $result; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/QueryParser.php b/vendor/guzzlehttp/guzzle/src/QueryParser.php new file mode 100644 index 0000000..90727cc --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/QueryParser.php @@ -0,0 +1,163 @@ +duplicates = false; + $this->numericIndices = true; + $decoder = self::getDecoder($urlEncoding); + + foreach (explode('&', $str) as $kvp) { + + $parts = explode('=', $kvp, 2); + $key = $decoder($parts[0]); + $value = isset($parts[1]) ? $decoder($parts[1]) : null; + + // Special handling needs to be taken for PHP nested array syntax + if (strpos($key, '[') !== false) { + $this->parsePhpValue($key, $value, $result); + continue; + } + + if (!isset($result[$key])) { + $result[$key] = $value; + } else { + $this->duplicates = true; + if (!is_array($result[$key])) { + $result[$key] = [$result[$key]]; + } + $result[$key][] = $value; + } + } + + $query->replace($result); + + if (!$this->numericIndices) { + $query->setAggregator(Query::phpAggregator(false)); + } elseif ($this->duplicates) { + $query->setAggregator(Query::duplicateAggregator()); + } + } + + /** + * Returns a callable that is used to URL decode query keys and values. + * + * @param string|bool $type One of true, false, RFC3986, and RFC1738 + * + * @return callable|string + */ + private static function getDecoder($type) + { + if ($type === true) { + return function ($value) { + return rawurldecode(str_replace('+', ' ', $value)); + }; + } elseif ($type == Query::RFC3986) { + return 'rawurldecode'; + } elseif ($type == Query::RFC1738) { + return 'urldecode'; + } else { + return function ($str) { return $str; }; + } + } + + /** + * Parses a PHP style key value pair. + * + * @param string $key Key to parse (e.g., "foo[a][b]") + * @param string|null $value Value to set + * @param array $result Result to modify by reference + */ + private function parsePhpValue($key, $value, array &$result) + { + $node =& $result; + $keyBuffer = ''; + + for ($i = 0, $t = strlen($key); $i < $t; $i++) { + switch ($key[$i]) { + case '[': + if ($keyBuffer) { + $this->prepareNode($node, $keyBuffer); + $node =& $node[$keyBuffer]; + $keyBuffer = ''; + } + break; + case ']': + $k = $this->cleanKey($node, $keyBuffer); + $this->prepareNode($node, $k); + $node =& $node[$k]; + $keyBuffer = ''; + break; + default: + $keyBuffer .= $key[$i]; + break; + } + } + + if (isset($node)) { + $this->duplicates = true; + $node[] = $value; + } else { + $node = $value; + } + } + + /** + * Prepares a value in the array at the given key. + * + * If the key already exists, the key value is converted into an array. + * + * @param array $node Result node to modify + * @param string $key Key to add or modify in the node + */ + private function prepareNode(&$node, $key) + { + if (!isset($node[$key])) { + $node[$key] = null; + } elseif (!is_array($node[$key])) { + $node[$key] = [$node[$key]]; + } + } + + /** + * Returns the appropriate key based on the node and key. + */ + private function cleanKey($node, $key) + { + if ($key === '') { + $key = $node ? (string) count($node) : 0; + // Found a [] key, so track this to ensure that we disable numeric + // indexing of keys in the resolved query aggregator. + $this->numericIndices = false; + } + + return $key; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RequestFsm.php b/vendor/guzzlehttp/guzzle/src/RequestFsm.php new file mode 100644 index 0000000..b37c190 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RequestFsm.php @@ -0,0 +1,153 @@ +mf = $messageFactory; + $this->maxTransitions = $maxTransitions; + $this->handler = $handler; + } + + /** + * Runs the state machine until a terminal state is entered or the + * optionally supplied $finalState is entered. + * + * @param Transaction $trans Transaction being transitioned. + * + * @throws \Exception if a terminal state throws an exception. + */ + public function __invoke(Transaction $trans) + { + $trans->_transitionCount = 0; + + if (!$trans->state) { + $trans->state = 'before'; + } + + transition: + + if (++$trans->_transitionCount > $this->maxTransitions) { + throw new StateException("Too many state transitions were " + . "encountered ({$trans->_transitionCount}). This likely " + . "means that a combination of event listeners are in an " + . "infinite loop."); + } + + switch ($trans->state) { + case 'before': goto before; + case 'complete': goto complete; + case 'error': goto error; + case 'retry': goto retry; + case 'send': goto send; + case 'end': goto end; + default: throw new StateException("Invalid state: {$trans->state}"); + } + + before: { + try { + $trans->request->getEmitter()->emit('before', new BeforeEvent($trans)); + $trans->state = 'send'; + if ((bool) $trans->response) { + $trans->state = 'complete'; + } + } catch (\Exception $e) { + $trans->state = 'error'; + $trans->exception = $e; + } + goto transition; + } + + complete: { + try { + if ($trans->response instanceof FutureInterface) { + // Futures will have their own end events emitted when + // dereferenced. + return; + } + $trans->state = 'end'; + $trans->response->setEffectiveUrl($trans->request->getUrl()); + $trans->request->getEmitter()->emit('complete', new CompleteEvent($trans)); + } catch (\Exception $e) { + $trans->state = 'error'; + $trans->exception = $e; + } + goto transition; + } + + error: { + try { + // Convert non-request exception to a wrapped exception + $trans->exception = RequestException::wrapException( + $trans->request, $trans->exception + ); + $trans->state = 'end'; + $trans->request->getEmitter()->emit('error', new ErrorEvent($trans)); + // An intercepted request (not retried) transitions to complete + if (!$trans->exception && $trans->state !== 'retry') { + $trans->state = 'complete'; + } + } catch (\Exception $e) { + $trans->state = 'end'; + $trans->exception = $e; + } + goto transition; + } + + retry: { + $trans->retries++; + $trans->response = null; + $trans->exception = null; + $trans->state = 'before'; + goto transition; + } + + send: { + $fn = $this->handler; + $trans->response = FutureResponse::proxy( + $fn(RingBridge::prepareRingRequest($trans)), + function ($value) use ($trans) { + RingBridge::completeRingResponse($trans, $value, $this->mf, $this); + $this($trans); + return $trans->response; + } + ); + return; + } + + end: { + $trans->request->getEmitter()->emit('end', new EndEvent($trans)); + // Throw exceptions in the terminal event if the exception + // was not handled by an "end" event listener. + if ($trans->exception) { + if (!($trans->exception instanceof RequestException)) { + $trans->exception = RequestException::wrapException( + $trans->request, $trans->exception + ); + } + throw $trans->exception; + } + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/RingBridge.php b/vendor/guzzlehttp/guzzle/src/RingBridge.php new file mode 100644 index 0000000..bc6841d --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/RingBridge.php @@ -0,0 +1,165 @@ +getConfig()->toArray(); + $url = $request->getUrl(); + // No need to calculate the query string twice (in URL and query). + $qs = ($pos = strpos($url, '?')) ? substr($url, $pos + 1) : null; + + return [ + 'scheme' => $request->getScheme(), + 'http_method' => $request->getMethod(), + 'url' => $url, + 'uri' => $request->getPath(), + 'headers' => $request->getHeaders(), + 'body' => $request->getBody(), + 'version' => $request->getProtocolVersion(), + 'client' => $options, + 'query_string' => $qs, + 'future' => isset($options['future']) ? $options['future'] : false + ]; + } + + /** + * Creates a Ring request from a request object AND prepares the callbacks. + * + * @param Transaction $trans Transaction to update. + * + * @return array Converted Guzzle Ring request. + */ + public static function prepareRingRequest(Transaction $trans) + { + // Clear out the transaction state when initiating. + $trans->exception = null; + $request = self::createRingRequest($trans->request); + + // Emit progress events if any progress listeners are registered. + if ($trans->request->getEmitter()->hasListeners('progress')) { + $emitter = $trans->request->getEmitter(); + $request['client']['progress'] = function ($a, $b, $c, $d) use ($trans, $emitter) { + $emitter->emit('progress', new ProgressEvent($trans, $a, $b, $c, $d)); + }; + } + + return $request; + } + + /** + * Handles the process of processing a response received from a ring + * handler. The created response is added to the transaction, and the + * transaction stat is set appropriately. + * + * @param Transaction $trans Owns request and response. + * @param array $response Ring response array + * @param MessageFactoryInterface $messageFactory Creates response objects. + */ + public static function completeRingResponse( + Transaction $trans, + array $response, + MessageFactoryInterface $messageFactory + ) { + $trans->state = 'complete'; + $trans->transferInfo = isset($response['transfer_stats']) + ? $response['transfer_stats'] : []; + + if (!empty($response['status'])) { + $options = []; + if (isset($response['version'])) { + $options['protocol_version'] = $response['version']; + } + if (isset($response['reason'])) { + $options['reason_phrase'] = $response['reason']; + } + $trans->response = $messageFactory->createResponse( + $response['status'], + isset($response['headers']) ? $response['headers'] : [], + isset($response['body']) ? $response['body'] : null, + $options + ); + if (isset($response['effective_url'])) { + $trans->response->setEffectiveUrl($response['effective_url']); + } + } elseif (empty($response['error'])) { + // When nothing was returned, then we need to add an error. + $response['error'] = self::getNoRingResponseException($trans->request); + } + + if (isset($response['error'])) { + $trans->state = 'error'; + $trans->exception = $response['error']; + } + } + + /** + * Creates a Guzzle request object using a ring request array. + * + * @param array $request Ring request + * + * @return Request + * @throws \InvalidArgumentException for incomplete requests. + */ + public static function fromRingRequest(array $request) + { + $options = []; + if (isset($request['version'])) { + $options['protocol_version'] = $request['version']; + } + + if (!isset($request['http_method'])) { + throw new \InvalidArgumentException('No http_method'); + } + + return new Request( + $request['http_method'], + Core::url($request), + isset($request['headers']) ? $request['headers'] : [], + isset($request['body']) ? Stream::factory($request['body']) : null, + $options + ); + } + + /** + * Get an exception that can be used when a RingPHP handler does not + * populate a response. + * + * @param RequestInterface $request + * + * @return RequestException + */ + public static function getNoRingResponseException(RequestInterface $request) + { + $message = <<cookieJar = $cookieJar ?: new CookieJar(); + } + + public function getEvents() + { + // Fire the cookie plugin complete event before redirecting + return [ + 'before' => ['onBefore'], + 'complete' => ['onComplete', RequestEvents::REDIRECT_RESPONSE + 10] + ]; + } + + /** + * Get the cookie cookieJar + * + * @return CookieJarInterface + */ + public function getCookieJar() + { + return $this->cookieJar; + } + + public function onBefore(BeforeEvent $event) + { + $this->cookieJar->addCookieHeader($event->getRequest()); + } + + public function onComplete(CompleteEvent $event) + { + $this->cookieJar->extractCookies( + $event->getRequest(), + $event->getResponse() + ); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Subscriber/History.php b/vendor/guzzlehttp/guzzle/src/Subscriber/History.php new file mode 100644 index 0000000..5cf0611 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Subscriber/History.php @@ -0,0 +1,172 @@ +limit = $limit; + } + + public function getEvents() + { + return [ + 'complete' => ['onComplete', RequestEvents::EARLY], + 'error' => ['onError', RequestEvents::EARLY], + ]; + } + + /** + * Convert to a string that contains all request and response headers + * + * @return string + */ + public function __toString() + { + $lines = array(); + foreach ($this->transactions as $entry) { + $response = isset($entry['response']) ? $entry['response'] : ''; + $lines[] = '> ' . trim($entry['sent_request']) + . "\n\n< " . trim($response) . "\n"; + } + + return implode("\n", $lines); + } + + public function onComplete(CompleteEvent $event) + { + $this->add($event->getRequest(), $event->getResponse()); + } + + public function onError(ErrorEvent $event) + { + // Only track when no response is present, meaning this didn't ever + // emit a complete event + if (!$event->getResponse()) { + $this->add($event->getRequest()); + } + } + + /** + * Returns an Iterator that yields associative array values where each + * associative array contains the following key value pairs: + * + * - request: Representing the actual request that was received. + * - sent_request: A clone of the request that will not be mutated. + * - response: The response that was received (if available). + * + * @return \Iterator + */ + public function getIterator() + { + return new \ArrayIterator($this->transactions); + } + + /** + * Get all of the requests sent through the plugin. + * + * Requests can be modified after they are logged by the history + * subscriber. By default this method will return the actual request + * instances that were received. Pass true to this method if you wish to + * get copies of the requests that represent the request state when it was + * initially logged by the history subscriber. + * + * @param bool $asSent Set to true to get clones of the requests that have + * not been mutated since the request was received by + * the history subscriber. + * + * @return RequestInterface[] + */ + public function getRequests($asSent = false) + { + return array_map(function ($t) use ($asSent) { + return $asSent ? $t['sent_request'] : $t['request']; + }, $this->transactions); + } + + /** + * Get the number of requests in the history + * + * @return int + */ + public function count() + { + return count($this->transactions); + } + + /** + * Get the last request sent. + * + * Requests can be modified after they are logged by the history + * subscriber. By default this method will return the actual request + * instance that was received. Pass true to this method if you wish to get + * a copy of the request that represents the request state when it was + * initially logged by the history subscriber. + * + * @param bool $asSent Set to true to get a clone of the last request that + * has not been mutated since the request was received + * by the history subscriber. + * + * @return RequestInterface + */ + public function getLastRequest($asSent = false) + { + return $asSent + ? end($this->transactions)['sent_request'] + : end($this->transactions)['request']; + } + + /** + * Get the last response in the history + * + * @return ResponseInterface|null + */ + public function getLastResponse() + { + return end($this->transactions)['response']; + } + + /** + * Clears the history + */ + public function clear() + { + $this->transactions = array(); + } + + /** + * Add a request to the history + * + * @param RequestInterface $request Request to add + * @param ResponseInterface $response Response of the request + */ + private function add( + RequestInterface $request, + ResponseInterface $response = null + ) { + $this->transactions[] = [ + 'request' => $request, + 'sent_request' => clone $request, + 'response' => $response + ]; + if (count($this->transactions) > $this->limit) { + array_shift($this->transactions); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Subscriber/HttpError.php b/vendor/guzzlehttp/guzzle/src/Subscriber/HttpError.php new file mode 100644 index 0000000..ed9de5b --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Subscriber/HttpError.php @@ -0,0 +1,36 @@ + ['onComplete', RequestEvents::VERIFY_RESPONSE]]; + } + + /** + * Throw a RequestException on an HTTP protocol error + * + * @param CompleteEvent $event Emitted event + * @throws RequestException + */ + public function onComplete(CompleteEvent $event) + { + $code = (string) $event->getResponse()->getStatusCode(); + // Throw an exception for an unsuccessful response + if ($code[0] >= 4) { + throw RequestException::create( + $event->getRequest(), + $event->getResponse() + ); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Subscriber/Mock.php b/vendor/guzzlehttp/guzzle/src/Subscriber/Mock.php new file mode 100644 index 0000000..2af4d37 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Subscriber/Mock.php @@ -0,0 +1,147 @@ +factory = new MessageFactory(); + $this->readBodies = $readBodies; + $this->addMultiple($items); + } + + public function getEvents() + { + // Fire the event last, after signing + return ['before' => ['onBefore', RequestEvents::SIGN_REQUEST - 10]]; + } + + /** + * @throws \OutOfBoundsException|\Exception + */ + public function onBefore(BeforeEvent $event) + { + if (!$item = array_shift($this->queue)) { + throw new \OutOfBoundsException('Mock queue is empty'); + } elseif ($item instanceof RequestException) { + throw $item; + } + + // Emulate reading a response body + $request = $event->getRequest(); + if ($this->readBodies && $request->getBody()) { + while (!$request->getBody()->eof()) { + $request->getBody()->read(8096); + } + } + + $saveTo = $event->getRequest()->getConfig()->get('save_to'); + + if (null !== $saveTo) { + $body = $item->getBody(); + + if (is_resource($saveTo)) { + fwrite($saveTo, $body); + } elseif (is_string($saveTo)) { + file_put_contents($saveTo, $body); + } elseif ($saveTo instanceof StreamInterface) { + $saveTo->write($body); + } + } + + $event->intercept($item); + } + + public function count() + { + return count($this->queue); + } + + /** + * Add a response to the end of the queue + * + * @param string|ResponseInterface $response Response or path to response file + * + * @return self + * @throws \InvalidArgumentException if a string or Response is not passed + */ + public function addResponse($response) + { + if (is_string($response)) { + $response = file_exists($response) + ? $this->factory->fromMessage(file_get_contents($response)) + : $this->factory->fromMessage($response); + } elseif (!($response instanceof ResponseInterface)) { + throw new \InvalidArgumentException('Response must a message ' + . 'string, response object, or path to a file'); + } + + $this->queue[] = $response; + + return $this; + } + + /** + * Add an exception to the end of the queue + * + * @param RequestException $e Exception to throw when the request is executed + * + * @return self + */ + public function addException(RequestException $e) + { + $this->queue[] = $e; + + return $this; + } + + /** + * Add multiple items to the queue + * + * @param array $items Items to add + */ + public function addMultiple(array $items) + { + foreach ($items as $item) { + if ($item instanceof RequestException) { + $this->addException($item); + } else { + $this->addResponse($item); + } + } + } + + /** + * Clear the queue + */ + public function clearQueue() + { + $this->queue = []; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php b/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php new file mode 100644 index 0000000..b5ed4e2 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Subscriber/Prepare.php @@ -0,0 +1,130 @@ + ['onBefore', RequestEvents::PREPARE_REQUEST]]; + } + + public function onBefore(BeforeEvent $event) + { + $request = $event->getRequest(); + + // Set the appropriate Content-Type for a request if one is not set and + // there are form fields + if (!($body = $request->getBody())) { + return; + } + + $this->addContentLength($request, $body); + + if ($body instanceof AppliesHeadersInterface) { + // Synchronize the body with the request headers + $body->applyRequestHeaders($request); + } elseif (!$request->hasHeader('Content-Type')) { + $this->addContentType($request, $body); + } + + $this->addExpectHeader($request, $body); + } + + private function addContentType( + RequestInterface $request, + StreamInterface $body + ) { + if (!($uri = $body->getMetadata('uri'))) { + return; + } + + // Guess the content-type based on the stream's "uri" metadata value. + // The file extension is used to determine the appropriate mime-type. + if ($contentType = Mimetypes::getInstance()->fromFilename($uri)) { + $request->setHeader('Content-Type', $contentType); + } + } + + private function addContentLength( + RequestInterface $request, + StreamInterface $body + ) { + // Set the Content-Length header if it can be determined, and never + // send a Transfer-Encoding: chunked and Content-Length header in + // the same request. + if ($request->hasHeader('Content-Length')) { + // Remove transfer-encoding if content-length is set. + $request->removeHeader('Transfer-Encoding'); + return; + } + + if ($request->hasHeader('Transfer-Encoding')) { + return; + } + + if (null !== ($size = $body->getSize())) { + $request->setHeader('Content-Length', $size); + $request->removeHeader('Transfer-Encoding'); + } elseif ('1.1' == $request->getProtocolVersion()) { + // Use chunked Transfer-Encoding if there is no determinable + // content-length header and we're using HTTP/1.1. + $request->setHeader('Transfer-Encoding', 'chunked'); + $request->removeHeader('Content-Length'); + } + } + + private function addExpectHeader( + RequestInterface $request, + StreamInterface $body + ) { + // Determine if the Expect header should be used + if ($request->hasHeader('Expect')) { + return; + } + + $expect = $request->getConfig()['expect']; + + // Return if disabled or if you're not using HTTP/1.1 + if ($expect === false || $request->getProtocolVersion() !== '1.1') { + return; + } + + // The expect header is unconditionally enabled + if ($expect === true) { + $request->setHeader('Expect', '100-Continue'); + return; + } + + // By default, send the expect header when the payload is > 1mb + if ($expect === null) { + $expect = 1048576; + } + + // Always add if the body cannot be rewound, the size cannot be + // determined, or the size is greater than the cutoff threshold + $size = $body->getSize(); + if ($size === null || $size >= (int) $expect || !$body->isSeekable()) { + $request->setHeader('Expect', '100-Continue'); + } + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php b/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php new file mode 100644 index 0000000..ff99226 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Subscriber/Redirect.php @@ -0,0 +1,176 @@ + ['onComplete', RequestEvents::REDIRECT_RESPONSE]]; + } + + /** + * Rewind the entity body of the request if needed + * + * @param RequestInterface $redirectRequest + * @throws CouldNotRewindStreamException + */ + public static function rewindEntityBody(RequestInterface $redirectRequest) + { + // Rewind the entity body of the request if needed + if ($body = $redirectRequest->getBody()) { + // Only rewind the body if some of it has been read already, and + // throw an exception if the rewind fails + if ($body->tell() && !$body->seek(0)) { + throw new CouldNotRewindStreamException( + 'Unable to rewind the non-seekable request body after redirecting', + $redirectRequest + ); + } + } + } + + /** + * Called when a request receives a redirect response + * + * @param CompleteEvent $event Event emitted + * @throws TooManyRedirectsException + */ + public function onComplete(CompleteEvent $event) + { + $response = $event->getResponse(); + + if (substr($response->getStatusCode(), 0, 1) != '3' + || !$response->hasHeader('Location') + ) { + return; + } + + $request = $event->getRequest(); + $config = $request->getConfig(); + + // Increment the redirect and initialize the redirect state. + if ($redirectCount = $config['redirect_count']) { + $config['redirect_count'] = ++$redirectCount; + } else { + $config['redirect_scheme'] = $request->getScheme(); + $config['redirect_count'] = $redirectCount = 1; + } + + $max = $config->getPath('redirect/max') ?: 5; + + if ($redirectCount > $max) { + throw new TooManyRedirectsException( + "Will not follow more than {$redirectCount} redirects", + $request + ); + } + + $this->modifyRedirectRequest($request, $response); + $event->retry(); + } + + private function modifyRedirectRequest( + RequestInterface $request, + ResponseInterface $response + ) { + $config = $request->getConfig(); + $protocols = $config->getPath('redirect/protocols') ?: ['http', 'https']; + + // Use a GET request if this is an entity enclosing request and we are + // not forcing RFC compliance, but rather emulating what all browsers + // would do. + $statusCode = $response->getStatusCode(); + if ($statusCode == 303 || + ($statusCode <= 302 && $request->getBody() && !$config->getPath('redirect/strict')) + ) { + $request->setMethod('GET'); + $request->setBody(null); + } + + $previousUrl = $request->getUrl(); + $this->setRedirectUrl($request, $response, $protocols); + $this->rewindEntityBody($request); + + // Add the Referer header if it is told to do so and only + // add the header if we are not redirecting from https to http. + if ($config->getPath('redirect/referer') + && ($request->getScheme() == 'https' || $request->getScheme() == $config['redirect_scheme']) + ) { + $url = Url::fromString($previousUrl); + $url->setUsername(null); + $url->setPassword(null); + $request->setHeader('Referer', (string) $url); + } else { + $request->removeHeader('Referer'); + } + } + + /** + * Set the appropriate URL on the request based on the location header + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @param array $protocols + */ + private function setRedirectUrl( + RequestInterface $request, + ResponseInterface $response, + array $protocols + ) { + $location = $response->getHeader('Location'); + $location = Url::fromString($location); + + // Combine location with the original URL if it is not absolute. + if (!$location->isAbsolute()) { + $originalUrl = Url::fromString($request->getUrl()); + // Remove query string parameters and just take what is present on + // the redirect Location header + $originalUrl->getQuery()->clear(); + $location = $originalUrl->combine($location); + } + + // Ensure that the redirect URL is allowed based on the protocols. + if (!in_array($location->getScheme(), $protocols)) { + throw new BadResponseException( + sprintf( + 'Redirect URL, %s, does not use one of the allowed redirect protocols: %s', + $location, + implode(', ', $protocols) + ), + $request, + $response + ); + } + + $request->setUrl($location); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/ToArrayInterface.php b/vendor/guzzlehttp/guzzle/src/ToArrayInterface.php new file mode 100644 index 0000000..d57c022 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/ToArrayInterface.php @@ -0,0 +1,15 @@ +client = $client; + $this->request = $request; + $this->_future = $future; + } +} diff --git a/vendor/guzzlehttp/guzzle/src/UriTemplate.php b/vendor/guzzlehttp/guzzle/src/UriTemplate.php new file mode 100644 index 0000000..55dfeb5 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/UriTemplate.php @@ -0,0 +1,241 @@ + array('prefix' => '', 'joiner' => ',', 'query' => false), + '+' => array('prefix' => '', 'joiner' => ',', 'query' => false), + '#' => array('prefix' => '#', 'joiner' => ',', 'query' => false), + '.' => array('prefix' => '.', 'joiner' => '.', 'query' => false), + '/' => array('prefix' => '/', 'joiner' => '/', 'query' => false), + ';' => array('prefix' => ';', 'joiner' => ';', 'query' => true), + '?' => array('prefix' => '?', 'joiner' => '&', 'query' => true), + '&' => array('prefix' => '&', 'joiner' => '&', 'query' => true) + ); + + /** @var array Delimiters */ + private static $delims = array(':', '/', '?', '#', '[', ']', '@', '!', '$', + '&', '\'', '(', ')', '*', '+', ',', ';', '='); + + /** @var array Percent encoded delimiters */ + private static $delimsPct = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D', + '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', + '%3B', '%3D'); + + public function expand($template, array $variables) + { + if (false === strpos($template, '{')) { + return $template; + } + + $this->template = $template; + $this->variables = $variables; + + return preg_replace_callback( + '/\{([^\}]+)\}/', + [$this, 'expandMatch'], + $this->template + ); + } + + /** + * Parse an expression into parts + * + * @param string $expression Expression to parse + * + * @return array Returns an associative array of parts + */ + private function parseExpression($expression) + { + $result = array(); + + if (isset(self::$operatorHash[$expression[0]])) { + $result['operator'] = $expression[0]; + $expression = substr($expression, 1); + } else { + $result['operator'] = ''; + } + + foreach (explode(',', $expression) as $value) { + $value = trim($value); + $varspec = array(); + if ($colonPos = strpos($value, ':')) { + $varspec['value'] = substr($value, 0, $colonPos); + $varspec['modifier'] = ':'; + $varspec['position'] = (int) substr($value, $colonPos + 1); + } elseif (substr($value, -1) == '*') { + $varspec['modifier'] = '*'; + $varspec['value'] = substr($value, 0, -1); + } else { + $varspec['value'] = (string) $value; + $varspec['modifier'] = ''; + } + $result['values'][] = $varspec; + } + + return $result; + } + + /** + * Process an expansion + * + * @param array $matches Matches met in the preg_replace_callback + * + * @return string Returns the replacement string + */ + private function expandMatch(array $matches) + { + static $rfc1738to3986 = array('+' => '%20', '%7e' => '~'); + + $replacements = array(); + $parsed = self::parseExpression($matches[1]); + $prefix = self::$operatorHash[$parsed['operator']]['prefix']; + $joiner = self::$operatorHash[$parsed['operator']]['joiner']; + $useQuery = self::$operatorHash[$parsed['operator']]['query']; + + foreach ($parsed['values'] as $value) { + + if (!isset($this->variables[$value['value']])) { + continue; + } + + $variable = $this->variables[$value['value']]; + $actuallyUseQuery = $useQuery; + $expanded = ''; + + if (is_array($variable)) { + + $isAssoc = $this->isAssoc($variable); + $kvp = array(); + foreach ($variable as $key => $var) { + + if ($isAssoc) { + $key = rawurlencode($key); + $isNestedArray = is_array($var); + } else { + $isNestedArray = false; + } + + if (!$isNestedArray) { + $var = rawurlencode($var); + if ($parsed['operator'] == '+' || + $parsed['operator'] == '#' + ) { + $var = $this->decodeReserved($var); + } + } + + if ($value['modifier'] == '*') { + if ($isAssoc) { + if ($isNestedArray) { + // Nested arrays must allow for deeply nested + // structures. + $var = strtr( + http_build_query([$key => $var]), + $rfc1738to3986 + ); + } else { + $var = $key . '=' . $var; + } + } elseif ($key > 0 && $actuallyUseQuery) { + $var = $value['value'] . '=' . $var; + } + } + + $kvp[$key] = $var; + } + + if (empty($variable)) { + $actuallyUseQuery = false; + } elseif ($value['modifier'] == '*') { + $expanded = implode($joiner, $kvp); + if ($isAssoc) { + // Don't prepend the value name when using the explode + // modifier with an associative array. + $actuallyUseQuery = false; + } + } else { + if ($isAssoc) { + // When an associative array is encountered and the + // explode modifier is not set, then the result must be + // a comma separated list of keys followed by their + // respective values. + foreach ($kvp as $k => &$v) { + $v = $k . ',' . $v; + } + } + $expanded = implode(',', $kvp); + } + + } else { + if ($value['modifier'] == ':') { + $variable = substr($variable, 0, $value['position']); + } + $expanded = rawurlencode($variable); + if ($parsed['operator'] == '+' || $parsed['operator'] == '#') { + $expanded = $this->decodeReserved($expanded); + } + } + + if ($actuallyUseQuery) { + if (!$expanded && $joiner != '&') { + $expanded = $value['value']; + } else { + $expanded = $value['value'] . '=' . $expanded; + } + } + + $replacements[] = $expanded; + } + + $ret = implode($joiner, $replacements); + if ($ret && $prefix) { + return $prefix . $ret; + } + + return $ret; + } + + /** + * Determines if an array is associative. + * + * This makes the assumption that input arrays are sequences or hashes. + * This assumption is a tradeoff for accuracy in favor of speed, but it + * should work in almost every case where input is supplied for a URI + * template. + * + * @param array $array Array to check + * + * @return bool + */ + private function isAssoc(array $array) + { + return $array && array_keys($array)[0] !== 0; + } + + /** + * Removes percent encoding on reserved characters (used with + and # + * modifiers). + * + * @param string $string String to fix + * + * @return string + */ + private function decodeReserved($string) + { + return str_replace(self::$delimsPct, self::$delims, $string); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Url.php b/vendor/guzzlehttp/guzzle/src/Url.php new file mode 100644 index 0000000..637f60c --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Url.php @@ -0,0 +1,595 @@ + 80, 'https' => 443, 'ftp' => 21]; + private static $pathPattern = '/[^a-zA-Z0-9\-\._~!\$&\'\(\)\*\+,;=%:@\/]+|%(?![A-Fa-f0-9]{2})/'; + private static $queryPattern = '/[^a-zA-Z0-9\-\._~!\$\'\(\)\*\+,;%:@\/\?=&]+|%(?![A-Fa-f0-9]{2})/'; + /** @var Query|string Query part of the URL */ + private $query; + + /** + * Factory method to create a new URL from a URL string + * + * @param string $url Full URL used to create a Url object + * + * @return Url + * @throws \InvalidArgumentException + */ + public static function fromString($url) + { + static $defaults = ['scheme' => null, 'host' => null, + 'path' => null, 'port' => null, 'query' => null, + 'user' => null, 'pass' => null, 'fragment' => null]; + + if (false === ($parts = parse_url($url))) { + throw new \InvalidArgumentException('Unable to parse malformed ' + . 'url: ' . $url); + } + + $parts += $defaults; + + // Convert the query string into a Query object + if ($parts['query'] || 0 !== strlen($parts['query'])) { + $parts['query'] = Query::fromString($parts['query']); + } + + return new static($parts['scheme'], $parts['host'], $parts['user'], + $parts['pass'], $parts['port'], $parts['path'], $parts['query'], + $parts['fragment']); + } + + /** + * Build a URL from parse_url parts. The generated URL will be a relative + * URL if a scheme or host are not provided. + * + * @param array $parts Array of parse_url parts + * + * @return string + */ + public static function buildUrl(array $parts) + { + $url = $scheme = ''; + + if (!empty($parts['scheme'])) { + $scheme = $parts['scheme']; + $url .= $scheme . ':'; + } + + if (!empty($parts['host'])) { + $url .= '//'; + if (isset($parts['user'])) { + $url .= $parts['user']; + if (isset($parts['pass'])) { + $url .= ':' . $parts['pass']; + } + $url .= '@'; + } + + $url .= $parts['host']; + + // Only include the port if it is not the default port of the scheme + if (isset($parts['port']) && + (!isset(self::$defaultPorts[$scheme]) || + $parts['port'] != self::$defaultPorts[$scheme]) + ) { + $url .= ':' . $parts['port']; + } + } + + // Add the path component if present + if (isset($parts['path']) && strlen($parts['path'])) { + // Always ensure that the path begins with '/' if set and something + // is before the path + if (!empty($parts['host']) && $parts['path'][0] != '/') { + $url .= '/'; + } + $url .= $parts['path']; + } + + // Add the query string if present + if (isset($parts['query'])) { + $queryStr = (string) $parts['query']; + if ($queryStr || $queryStr === '0') { + $url .= '?' . $queryStr; + } + } + + // Ensure that # is only added to the url if fragment contains anything. + if (isset($parts['fragment'])) { + $url .= '#' . $parts['fragment']; + } + + return $url; + } + + /** + * Create a new URL from URL parts + * + * @param string $scheme Scheme of the URL + * @param string $host Host of the URL + * @param string $username Username of the URL + * @param string $password Password of the URL + * @param int $port Port of the URL + * @param string $path Path of the URL + * @param Query|array|string $query Query string of the URL + * @param string $fragment Fragment of the URL + */ + public function __construct( + $scheme, + $host, + $username = null, + $password = null, + $port = null, + $path = null, + $query = null, + $fragment = null + ) { + $this->scheme = strtolower($scheme); + $this->host = $host; + $this->port = $port; + $this->username = $username; + $this->password = $password; + $this->fragment = $fragment; + + if ($query) { + $this->setQuery($query); + } + + $this->setPath($path); + } + + /** + * Clone the URL + */ + public function __clone() + { + if ($this->query instanceof Query) { + $this->query = clone $this->query; + } + } + + /** + * Returns the URL as a URL string + * + * @return string + */ + public function __toString() + { + return static::buildUrl($this->getParts()); + } + + /** + * Get the parts of the URL as an array + * + * @return array + */ + public function getParts() + { + return array( + 'scheme' => $this->scheme, + 'user' => $this->username, + 'pass' => $this->password, + 'host' => $this->host, + 'port' => $this->port, + 'path' => $this->path, + 'query' => $this->query, + 'fragment' => $this->fragment, + ); + } + + /** + * Set the host of the request. + * + * @param string $host Host to set (e.g. www.yahoo.com, yahoo.com) + * + * @return Url + */ + public function setHost($host) + { + if (strpos($host, ':') === false) { + $this->host = $host; + } else { + list($host, $port) = explode(':', $host); + $this->host = $host; + $this->setPort($port); + } + } + + /** + * Get the host part of the URL + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the scheme part of the URL (http, https, ftp, etc.) + * + * @param string $scheme Scheme to set + */ + public function setScheme($scheme) + { + // Remove the default port if one is specified + if ($this->port + && isset(self::$defaultPorts[$this->scheme]) + && self::$defaultPorts[$this->scheme] == $this->port + ) { + $this->port = null; + } + + $this->scheme = strtolower($scheme); + } + + /** + * Get the scheme part of the URL + * + * @return string + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Set the port part of the URL + * + * @param int $port Port to set + */ + public function setPort($port) + { + $this->port = $port; + } + + /** + * Get the port part of the URl. + * + * If no port was set, this method will return the default port for the + * scheme of the URI. + * + * @return int|null + */ + public function getPort() + { + if ($this->port) { + return $this->port; + } elseif (isset(self::$defaultPorts[$this->scheme])) { + return self::$defaultPorts[$this->scheme]; + } + + return null; + } + + /** + * Set the path part of the URL. + * + * The provided URL is URL encoded as necessary. + * + * @param string $path Path string to set + */ + public function setPath($path) + { + $this->path = self::encodePath($path); + } + + /** + * Removes dot segments from a URL + * @link http://tools.ietf.org/html/rfc3986#section-5.2.4 + */ + public function removeDotSegments() + { + static $noopPaths = ['' => true, '/' => true, '*' => true]; + static $ignoreSegments = ['.' => true, '..' => true]; + + if (isset($noopPaths[$this->path])) { + return; + } + + $results = []; + $segments = $this->getPathSegments(); + foreach ($segments as $segment) { + if ($segment == '..') { + array_pop($results); + } elseif (!isset($ignoreSegments[$segment])) { + $results[] = $segment; + } + } + + $newPath = implode('/', $results); + + // Add the leading slash if necessary + if (substr($this->path, 0, 1) === '/' && + substr($newPath, 0, 1) !== '/' + ) { + $newPath = '/' . $newPath; + } + + // Add the trailing slash if necessary + if ($newPath != '/' && isset($ignoreSegments[end($segments)])) { + $newPath .= '/'; + } + + $this->path = $newPath; + } + + /** + * Add a relative path to the currently set path. + * + * @param string $relativePath Relative path to add + */ + public function addPath($relativePath) + { + if ($relativePath != '/' && + is_string($relativePath) && + strlen($relativePath) > 0 + ) { + // Add a leading slash if needed + if ($relativePath[0] !== '/' && + substr($this->path, -1, 1) !== '/' + ) { + $relativePath = '/' . $relativePath; + } + + $this->setPath($this->path . $relativePath); + } + } + + /** + * Get the path part of the URL + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Get the path segments of the URL as an array + * + * @return array + */ + public function getPathSegments() + { + return explode('/', $this->path); + } + + /** + * Set the password part of the URL + * + * @param string $password Password to set + */ + public function setPassword($password) + { + $this->password = $password; + } + + /** + * Get the password part of the URL + * + * @return null|string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Set the username part of the URL + * + * @param string $username Username to set + */ + public function setUsername($username) + { + $this->username = $username; + } + + /** + * Get the username part of the URl + * + * @return null|string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Get the query part of the URL as a Query object + * + * @return Query + */ + public function getQuery() + { + // Convert the query string to a query object if not already done. + if (!$this->query instanceof Query) { + $this->query = $this->query === null + ? new Query() + : Query::fromString($this->query); + } + + return $this->query; + } + + /** + * Set the query part of the URL. + * + * You may provide a query string as a string and pass $rawString as true + * to provide a query string that is not parsed until a call to getQuery() + * is made. Setting a raw query string will still encode invalid characters + * in a query string. + * + * @param Query|string|array $query Query string value to set. Can + * be a string that will be parsed into a Query object, an array + * of key value pairs, or a Query object. + * @param bool $rawString Set to true when providing a raw query string. + * + * @throws \InvalidArgumentException + */ + public function setQuery($query, $rawString = false) + { + if ($query instanceof Query) { + $this->query = $query; + } elseif (is_string($query)) { + if (!$rawString) { + $this->query = Query::fromString($query); + } else { + // Ensure the query does not have illegal characters. + $this->query = preg_replace_callback( + self::$queryPattern, + [__CLASS__, 'encodeMatch'], + $query + ); + } + + } elseif (is_array($query)) { + $this->query = new Query($query); + } else { + throw new \InvalidArgumentException('Query must be a Query, ' + . 'array, or string. Got ' . Core::describeType($query)); + } + } + + /** + * Get the fragment part of the URL + * + * @return null|string + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * Set the fragment part of the URL + * + * @param string $fragment Fragment to set + */ + public function setFragment($fragment) + { + $this->fragment = $fragment; + } + + /** + * Check if this is an absolute URL + * + * @return bool + */ + public function isAbsolute() + { + return $this->scheme && $this->host; + } + + /** + * Combine the URL with another URL and return a new URL instance. + * + * Follows the rules specific in RFC 3986 section 5.4. + * + * @param string $url Relative URL to combine with + * + * @return Url + * @throws \InvalidArgumentException + * @link http://tools.ietf.org/html/rfc3986#section-5.4 + */ + public function combine($url) + { + $url = static::fromString($url); + + // Use the more absolute URL as the base URL + if (!$this->isAbsolute() && $url->isAbsolute()) { + $url = $url->combine($this); + } + + $parts = $url->getParts(); + + // Passing a URL with a scheme overrides everything + if ($parts['scheme']) { + return clone $url; + } + + // Setting a host overrides the entire rest of the URL + if ($parts['host']) { + return new static( + $this->scheme, + $parts['host'], + $parts['user'], + $parts['pass'], + $parts['port'], + $parts['path'], + $parts['query'] instanceof Query + ? clone $parts['query'] + : $parts['query'], + $parts['fragment'] + ); + } + + if (!$parts['path'] && $parts['path'] !== '0') { + // The relative URL has no path, so check if it is just a query + $path = $this->path ?: ''; + $query = $parts['query'] ?: $this->query; + } else { + $query = $parts['query']; + if ($parts['path'][0] == '/' || !$this->path) { + // Overwrite the existing path if the rel path starts with "/" + $path = $parts['path']; + } else { + // If the relative URL does not have a path or the base URL + // path does not end in a "/" then overwrite the existing path + // up to the last "/" + $path = substr($this->path, 0, strrpos($this->path, '/') + 1) . $parts['path']; + } + } + + $result = new self( + $this->scheme, + $this->host, + $this->username, + $this->password, + $this->port, + $path, + $query instanceof Query ? clone $query : $query, + $parts['fragment'] + ); + + if ($path) { + $result->removeDotSegments(); + } + + return $result; + } + + /** + * Encodes the path part of a URL without double-encoding percent-encoded + * key value pairs. + * + * @param string $path Path to encode + * + * @return string + */ + public static function encodePath($path) + { + static $cb = [__CLASS__, 'encodeMatch']; + return preg_replace_callback(self::$pathPattern, $cb, $path); + } + + private static function encodeMatch(array $match) + { + return rawurlencode($match[0]); + } +} diff --git a/vendor/guzzlehttp/guzzle/src/Utils.php b/vendor/guzzlehttp/guzzle/src/Utils.php new file mode 100644 index 0000000..1754719 --- /dev/null +++ b/vendor/guzzlehttp/guzzle/src/Utils.php @@ -0,0 +1,215 @@ +expand($template, $variables); + } + + /** + * Wrapper for JSON decode that implements error detection with helpful + * error messages. + * + * @param string $json JSON data to parse + * @param bool $assoc When true, returned objects will be converted + * into associative arrays. + * @param int $depth User specified recursion depth. + * @param int $options Bitmask of JSON decode options. + * + * @return mixed + * @throws \InvalidArgumentException if the JSON cannot be parsed. + * @link http://www.php.net/manual/en/function.json-decode.php + */ + public static function jsonDecode($json, $assoc = false, $depth = 512, $options = 0) + { + if ($json === '' || $json === null) { + return null; + } + + static $jsonErrors = [ + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ]; + + $data = \json_decode($json, $assoc, $depth, $options); + + if (JSON_ERROR_NONE !== json_last_error()) { + $last = json_last_error(); + throw new \InvalidArgumentException( + 'Unable to parse JSON data: ' + . (isset($jsonErrors[$last]) + ? $jsonErrors[$last] + : 'Unknown error') + ); + } + + return $data; + } + + /** + * Get the default User-Agent string to use with Guzzle + * + * @return string + */ + public static function getDefaultUserAgent() + { + static $defaultAgent = ''; + if (!$defaultAgent) { + $defaultAgent = 'Guzzle/' . ClientInterface::VERSION; + if (extension_loaded('curl')) { + $defaultAgent .= ' curl/' . curl_version()['version']; + } + $defaultAgent .= ' PHP/' . PHP_VERSION; + } + + return $defaultAgent; + } + + /** + * Create a default handler to use based on the environment + * + * @throws \RuntimeException if no viable Handler is available. + */ + public static function getDefaultHandler() + { + $default = $future = null; + + if (extension_loaded('curl')) { + $config = [ + 'select_timeout' => getenv('GUZZLE_CURL_SELECT_TIMEOUT') ?: 1 + ]; + if ($maxHandles = getenv('GUZZLE_CURL_MAX_HANDLES')) { + $config['max_handles'] = $maxHandles; + } + if (function_exists('curl_reset')) { + $default = new CurlHandler(); + $future = new CurlMultiHandler($config); + } else { + $default = new CurlMultiHandler($config); + } + } + + if (ini_get('allow_url_fopen')) { + $default = !$default + ? new StreamHandler() + : Middleware::wrapStreaming($default, new StreamHandler()); + } elseif (!$default) { + throw new \RuntimeException('Guzzle requires cURL, the ' + . 'allow_url_fopen ini setting, or a custom HTTP handler.'); + } + + return $future ? Middleware::wrapFuture($default, $future) : $default; + } +} diff --git a/vendor/guzzlehttp/ringphp/.editorconfig b/vendor/guzzlehttp/ringphp/.editorconfig new file mode 100644 index 0000000..70dabca --- /dev/null +++ b/vendor/guzzlehttp/ringphp/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[{Makefile,*.mk}] +indent_style = tab diff --git a/vendor/guzzlehttp/ringphp/LICENSE b/vendor/guzzlehttp/ringphp/LICENSE new file mode 100644 index 0000000..71d3b78 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/ringphp/composer.json b/vendor/guzzlehttp/ringphp/composer.json new file mode 100644 index 0000000..8df60ec --- /dev/null +++ b/vendor/guzzlehttp/ringphp/composer.json @@ -0,0 +1,43 @@ +{ + "name": "guzzlehttp/ringphp", + "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.4.0", + "guzzlehttp/streams": "~3.0", + "react/promise": "~2.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "GuzzleHttp\\Tests\\Ring\\": "tests/" + } + }, + "scripts": { + "test": "make test", + "test-ci": "make coverage" + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php b/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php new file mode 100644 index 0000000..27d5fe7 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Client/ClientUtils.php @@ -0,0 +1,74 @@ +getDefaultOptions($request, $headers); + $this->applyMethod($request, $options); + + if (isset($request['client'])) { + $this->applyHandlerOptions($request, $options); + } + + $this->applyHeaders($request, $options); + unset($options['_headers']); + + // Add handler options from the request's configuration options + if (isset($request['client']['curl'])) { + $options = $this->applyCustomCurlOptions( + $request['client']['curl'], + $options + ); + } + + if (!$handle) { + $handle = curl_init(); + } + + $body = $this->getOutputBody($request, $options); + curl_setopt_array($handle, $options); + + return [$handle, &$headers, $body]; + } + + /** + * Creates a response hash from a cURL result. + * + * @param callable $handler Handler that was used. + * @param array $request Request that sent. + * @param array $response Response hash to update. + * @param array $headers Headers received during transfer. + * @param resource $body Body fopen response. + * + * @return array + */ + public static function createResponse( + callable $handler, + array $request, + array $response, + array $headers, + $body + ) { + if (isset($response['transfer_stats']['url'])) { + $response['effective_url'] = $response['transfer_stats']['url']; + } + + if (!empty($headers)) { + $startLine = explode(' ', array_shift($headers), 3); + $headerList = Core::headersFromLines($headers); + $response['headers'] = $headerList; + $response['version'] = isset($startLine[0]) ? substr($startLine[0], 5) : null; + $response['status'] = isset($startLine[1]) ? (int) $startLine[1] : null; + $response['reason'] = isset($startLine[2]) ? $startLine[2] : null; + $response['body'] = $body; + Core::rewindBody($response); + } + + return !empty($response['curl']['errno']) || !isset($response['status']) + ? self::createErrorResponse($handler, $request, $response) + : $response; + } + + private static function createErrorResponse( + callable $handler, + array $request, + array $response + ) { + static $connectionErrors = [ + CURLE_OPERATION_TIMEOUTED => true, + CURLE_COULDNT_RESOLVE_HOST => true, + CURLE_COULDNT_CONNECT => true, + CURLE_SSL_CONNECT_ERROR => true, + CURLE_GOT_NOTHING => true, + ]; + + // Retry when nothing is present or when curl failed to rewind. + if (!isset($response['err_message']) + && (empty($response['curl']['errno']) + || $response['curl']['errno'] == 65) + ) { + return self::retryFailedRewind($handler, $request, $response); + } + + $message = isset($response['err_message']) + ? $response['err_message'] + : sprintf('cURL error %s: %s', + $response['curl']['errno'], + isset($response['curl']['error']) + ? $response['curl']['error'] + : 'See http://curl.haxx.se/libcurl/c/libcurl-errors.html'); + + $error = isset($response['curl']['errno']) + && isset($connectionErrors[$response['curl']['errno']]) + ? new ConnectException($message) + : new RingException($message); + + return $response + [ + 'status' => null, + 'reason' => null, + 'body' => null, + 'headers' => [], + 'error' => $error, + ]; + } + + private function getOutputBody(array $request, array &$options) + { + // Determine where the body of the response (if any) will be streamed. + if (isset($options[CURLOPT_WRITEFUNCTION])) { + return $request['client']['save_to']; + } + + if (isset($options[CURLOPT_FILE])) { + return $options[CURLOPT_FILE]; + } + + if ($request['http_method'] != 'HEAD') { + // Create a default body if one was not provided + return $options[CURLOPT_FILE] = fopen('php://temp', 'w+'); + } + + return null; + } + + private function getDefaultOptions(array $request, array &$headers) + { + $url = Core::url($request); + $startingResponse = false; + + $options = [ + '_headers' => $request['headers'], + CURLOPT_CUSTOMREQUEST => $request['http_method'], + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => false, + CURLOPT_HEADER => false, + CURLOPT_CONNECTTIMEOUT => 150, + CURLOPT_HEADERFUNCTION => function ($ch, $h) use (&$headers, &$startingResponse) { + $value = trim($h); + if ($value === '') { + $startingResponse = true; + } elseif ($startingResponse) { + $startingResponse = false; + $headers = [$value]; + } else { + $headers[] = $value; + } + return strlen($h); + }, + ]; + + if (isset($request['version'])) { + if ($request['version'] == 2.0) { + $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; + } else if ($request['version'] == 1.1) { + $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_1; + } else { + $options[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; + } + } + + if (defined('CURLOPT_PROTOCOLS')) { + $options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS; + } + + return $options; + } + + private function applyMethod(array $request, array &$options) + { + if (isset($request['body'])) { + $this->applyBody($request, $options); + return; + } + + switch ($request['http_method']) { + case 'PUT': + case 'POST': + // See http://tools.ietf.org/html/rfc7230#section-3.3.2 + if (!Core::hasHeader($request, 'Content-Length')) { + $options[CURLOPT_HTTPHEADER][] = 'Content-Length: 0'; + } + break; + case 'HEAD': + $options[CURLOPT_NOBODY] = true; + unset( + $options[CURLOPT_WRITEFUNCTION], + $options[CURLOPT_READFUNCTION], + $options[CURLOPT_FILE], + $options[CURLOPT_INFILE] + ); + } + } + + private function applyBody(array $request, array &$options) + { + $contentLength = Core::firstHeader($request, 'Content-Length'); + $size = $contentLength !== null ? (int) $contentLength : null; + + // Send the body as a string if the size is less than 1MB OR if the + // [client][curl][body_as_string] request value is set. + if (($size !== null && $size < 1000000) || + isset($request['client']['curl']['body_as_string']) || + is_string($request['body']) + ) { + $options[CURLOPT_POSTFIELDS] = Core::body($request); + // Don't duplicate the Content-Length header + $this->removeHeader('Content-Length', $options); + $this->removeHeader('Transfer-Encoding', $options); + } else { + $options[CURLOPT_UPLOAD] = true; + if ($size !== null) { + // Let cURL handle setting the Content-Length header + $options[CURLOPT_INFILESIZE] = $size; + $this->removeHeader('Content-Length', $options); + } + $this->addStreamingBody($request, $options); + } + + // If the Expect header is not present, prevent curl from adding it + if (!Core::hasHeader($request, 'Expect')) { + $options[CURLOPT_HTTPHEADER][] = 'Expect:'; + } + + // cURL sometimes adds a content-type by default. Prevent this. + if (!Core::hasHeader($request, 'Content-Type')) { + $options[CURLOPT_HTTPHEADER][] = 'Content-Type:'; + } + } + + private function addStreamingBody(array $request, array &$options) + { + $body = $request['body']; + + if ($body instanceof StreamInterface) { + $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body) { + return (string) $body->read($length); + }; + if (!isset($options[CURLOPT_INFILESIZE])) { + if ($size = $body->getSize()) { + $options[CURLOPT_INFILESIZE] = $size; + } + } + } elseif (is_resource($body)) { + $options[CURLOPT_INFILE] = $body; + } elseif ($body instanceof \Iterator) { + $buf = ''; + $options[CURLOPT_READFUNCTION] = function ($ch, $fd, $length) use ($body, &$buf) { + if ($body->valid()) { + $buf .= $body->current(); + $body->next(); + } + $result = (string) substr($buf, 0, $length); + $buf = substr($buf, $length); + return $result; + }; + } else { + throw new \InvalidArgumentException('Invalid request body provided'); + } + } + + private function applyHeaders(array $request, array &$options) + { + foreach ($options['_headers'] as $name => $values) { + foreach ($values as $value) { + $options[CURLOPT_HTTPHEADER][] = "$name: $value"; + } + } + + // Remove the Accept header if one was not set + if (!Core::hasHeader($request, 'Accept')) { + $options[CURLOPT_HTTPHEADER][] = 'Accept:'; + } + } + + /** + * Takes an array of curl options specified in the 'curl' option of a + * request's configuration array and maps them to CURLOPT_* options. + * + * This method is only called when a request has a 'curl' config setting. + * + * @param array $config Configuration array of custom curl option + * @param array $options Array of existing curl options + * + * @return array Returns a new array of curl options + */ + private function applyCustomCurlOptions(array $config, array $options) + { + $curlOptions = []; + foreach ($config as $key => $value) { + if (is_int($key)) { + $curlOptions[$key] = $value; + } + } + + return $curlOptions + $options; + } + + /** + * Remove a header from the options array. + * + * @param string $name Case-insensitive header to remove + * @param array $options Array of options to modify + */ + private function removeHeader($name, array &$options) + { + foreach (array_keys($options['_headers']) as $key) { + if (!strcasecmp($key, $name)) { + unset($options['_headers'][$key]); + return; + } + } + } + + /** + * Applies an array of request client options to a the options array. + * + * This method uses a large switch rather than double-dispatch to save on + * high overhead of calling functions in PHP. + */ + private function applyHandlerOptions(array $request, array &$options) + { + foreach ($request['client'] as $key => $value) { + switch ($key) { + // Violating PSR-4 to provide more room. + case 'verify': + + if ($value === false) { + unset($options[CURLOPT_CAINFO]); + $options[CURLOPT_SSL_VERIFYHOST] = 0; + $options[CURLOPT_SSL_VERIFYPEER] = false; + continue 2; + } + + $options[CURLOPT_SSL_VERIFYHOST] = 2; + $options[CURLOPT_SSL_VERIFYPEER] = true; + + if (is_string($value)) { + $options[CURLOPT_CAINFO] = $value; + if (!file_exists($value)) { + throw new \InvalidArgumentException( + "SSL CA bundle not found: $value" + ); + } + } + break; + + case 'decode_content': + + if ($value === false) { + continue 2; + } + + $accept = Core::firstHeader($request, 'Accept-Encoding'); + if ($accept) { + $options[CURLOPT_ENCODING] = $accept; + } else { + $options[CURLOPT_ENCODING] = ''; + // Don't let curl send the header over the wire + $options[CURLOPT_HTTPHEADER][] = 'Accept-Encoding:'; + } + break; + + case 'save_to': + + if (is_string($value)) { + if (!is_dir(dirname($value))) { + throw new \RuntimeException(sprintf( + 'Directory %s does not exist for save_to value of %s', + dirname($value), + $value + )); + } + $value = new LazyOpenStream($value, 'w+'); + } + + if ($value instanceof StreamInterface) { + $options[CURLOPT_WRITEFUNCTION] = + function ($ch, $write) use ($value) { + return $value->write($write); + }; + } elseif (is_resource($value)) { + $options[CURLOPT_FILE] = $value; + } else { + throw new \InvalidArgumentException('save_to must be a ' + . 'GuzzleHttp\Stream\StreamInterface or resource'); + } + break; + + case 'timeout': + + if (defined('CURLOPT_TIMEOUT_MS')) { + $options[CURLOPT_TIMEOUT_MS] = $value * 1000; + } else { + $options[CURLOPT_TIMEOUT] = $value; + } + break; + + case 'connect_timeout': + + if (defined('CURLOPT_CONNECTTIMEOUT_MS')) { + $options[CURLOPT_CONNECTTIMEOUT_MS] = $value * 1000; + } else { + $options[CURLOPT_CONNECTTIMEOUT] = $value; + } + break; + + case 'proxy': + + if (!is_array($value)) { + $options[CURLOPT_PROXY] = $value; + } elseif (isset($request['scheme'])) { + $scheme = $request['scheme']; + if (isset($value[$scheme])) { + $options[CURLOPT_PROXY] = $value[$scheme]; + } + } + break; + + case 'cert': + + if (is_array($value)) { + $options[CURLOPT_SSLCERTPASSWD] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new \InvalidArgumentException( + "SSL certificate not found: {$value}" + ); + } + + $options[CURLOPT_SSLCERT] = $value; + break; + + case 'ssl_key': + + if (is_array($value)) { + $options[CURLOPT_SSLKEYPASSWD] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new \InvalidArgumentException( + "SSL private key not found: {$value}" + ); + } + + $options[CURLOPT_SSLKEY] = $value; + break; + + case 'progress': + + if (!is_callable($value)) { + throw new \InvalidArgumentException( + 'progress client option must be callable' + ); + } + + $options[CURLOPT_NOPROGRESS] = false; + $options[CURLOPT_PROGRESSFUNCTION] = + function () use ($value) { + $args = func_get_args(); + // PHP 5.5 pushed the handle onto the start of the args + if (is_resource($args[0])) { + array_shift($args); + } + call_user_func_array($value, $args); + }; + break; + + case 'debug': + + if ($value) { + $options[CURLOPT_STDERR] = Core::getDebugResource($value); + $options[CURLOPT_VERBOSE] = true; + } + break; + } + } + } + + /** + * This function ensures that a response was set on a transaction. If one + * was not set, then the request is retried if possible. This error + * typically means you are sending a payload, curl encountered a + * "Connection died, retrying a fresh connect" error, tried to rewind the + * stream, and then encountered a "necessary data rewind wasn't possible" + * error, causing the request to be sent through curl_multi_info_read() + * without an error status. + */ + private static function retryFailedRewind( + callable $handler, + array $request, + array $response + ) { + // If there is no body, then there is some other kind of issue. This + // is weird and should probably never happen. + if (!isset($request['body'])) { + $response['err_message'] = 'No response was received for a request ' + . 'with no body. This could mean that you are saturating your ' + . 'network.'; + return self::createErrorResponse($handler, $request, $response); + } + + if (!Core::rewindBody($request)) { + $response['err_message'] = 'The connection unexpectedly failed ' + . 'without providing an error. The request would have been ' + . 'retried, but attempting to rewind the request body failed.'; + return self::createErrorResponse($handler, $request, $response); + } + + // Retry no more than 3 times before giving up. + if (!isset($request['curl']['retries'])) { + $request['curl']['retries'] = 1; + } elseif ($request['curl']['retries'] == 2) { + $response['err_message'] = 'The cURL request was retried 3 times ' + . 'and did no succeed. cURL was unable to rewind the body of ' + . 'the request and subsequent retries resulted in the same ' + . 'error. Turn on the debug option to see what went wrong. ' + . 'See https://bugs.php.net/bug.php?id=47204 for more information.'; + return self::createErrorResponse($handler, $request, $response); + } else { + $request['curl']['retries']++; + } + + return $handler($request); + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php b/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php new file mode 100644 index 0000000..e00aa4e --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Client/CurlHandler.php @@ -0,0 +1,135 @@ +handles = $this->ownedHandles = []; + $this->factory = isset($options['handle_factory']) + ? $options['handle_factory'] + : new CurlFactory(); + $this->maxHandles = isset($options['max_handles']) + ? $options['max_handles'] + : 5; + } + + public function __destruct() + { + foreach ($this->handles as $handle) { + if (is_resource($handle)) { + curl_close($handle); + } + } + } + + /** + * @param array $request + * + * @return CompletedFutureArray + */ + public function __invoke(array $request) + { + return new CompletedFutureArray( + $this->_invokeAsArray($request) + ); + } + + /** + * @internal + * + * @param array $request + * + * @return array + */ + public function _invokeAsArray(array $request) + { + $factory = $this->factory; + + // Ensure headers are by reference. They're updated elsewhere. + $result = $factory($request, $this->checkoutEasyHandle()); + $h = $result[0]; + $hd =& $result[1]; + $bd = $result[2]; + Core::doSleep($request); + curl_exec($h); + $response = ['transfer_stats' => curl_getinfo($h)]; + $response['curl']['error'] = curl_error($h); + $response['curl']['errno'] = curl_errno($h); + $response['transfer_stats'] = array_merge($response['transfer_stats'], $response['curl']); + $this->releaseEasyHandle($h); + + return CurlFactory::createResponse([$this, '_invokeAsArray'], $request, $response, $hd, $bd); + } + + private function checkoutEasyHandle() + { + // Find an unused handle in the cache + if (false !== ($key = array_search(false, $this->ownedHandles, true))) { + $this->ownedHandles[$key] = true; + return $this->handles[$key]; + } + + // Add a new handle + $handle = curl_init(); + $id = (int) $handle; + $this->handles[$id] = $handle; + $this->ownedHandles[$id] = true; + + return $handle; + } + + private function releaseEasyHandle($handle) + { + $id = (int) $handle; + if (count($this->ownedHandles) > $this->maxHandles) { + curl_close($this->handles[$id]); + unset($this->handles[$id], $this->ownedHandles[$id]); + } else { + // curl_reset doesn't clear these out for some reason + static $unsetValues = [ + CURLOPT_HEADERFUNCTION => null, + CURLOPT_WRITEFUNCTION => null, + CURLOPT_READFUNCTION => null, + CURLOPT_PROGRESSFUNCTION => null, + ]; + curl_setopt_array($handle, $unsetValues); + curl_reset($handle); + $this->ownedHandles[$id] = false; + } + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php b/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php new file mode 100644 index 0000000..f84cf19 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Client/CurlMultiHandler.php @@ -0,0 +1,248 @@ +_mh = $options['mh']; + } + $this->factory = isset($options['handle_factory']) + ? $options['handle_factory'] : new CurlFactory(); + $this->selectTimeout = isset($options['select_timeout']) + ? $options['select_timeout'] : 1; + $this->maxHandles = isset($options['max_handles']) + ? $options['max_handles'] : 100; + } + + public function __get($name) + { + if ($name === '_mh') { + return $this->_mh = curl_multi_init(); + } + + throw new \BadMethodCallException(); + } + + public function __destruct() + { + // Finish any open connections before terminating the script. + if ($this->handles) { + $this->execute(); + } + + if (isset($this->_mh)) { + curl_multi_close($this->_mh); + unset($this->_mh); + } + } + + public function __invoke(array $request) + { + $factory = $this->factory; + $result = $factory($request); + $entry = [ + 'request' => $request, + 'response' => [], + 'handle' => $result[0], + 'headers' => &$result[1], + 'body' => $result[2], + 'deferred' => new Deferred(), + ]; + + $id = (int) $result[0]; + + $future = new FutureArray( + $entry['deferred']->promise(), + [$this, 'execute'], + function () use ($id) { + return $this->cancel($id); + } + ); + + $this->addRequest($entry); + + // Transfer outstanding requests if there are too many open handles. + if (count($this->handles) >= $this->maxHandles) { + $this->execute(); + } + + return $future; + } + + /** + * Runs until all outstanding connections have completed. + */ + public function execute() + { + do { + + if ($this->active && + curl_multi_select($this->_mh, $this->selectTimeout) === -1 + ) { + // Perform a usleep if a select returns -1. + // See: https://bugs.php.net/bug.php?id=61141 + usleep(250); + } + + // Add any delayed futures if needed. + if ($this->delays) { + $this->addDelays(); + } + + do { + $mrc = curl_multi_exec($this->_mh, $this->active); + } while ($mrc === CURLM_CALL_MULTI_PERFORM); + + $this->processMessages(); + + // If there are delays but no transfers, then sleep for a bit. + if (!$this->active && $this->delays) { + usleep(500); + } + + } while ($this->active || $this->handles); + } + + private function addRequest(array &$entry) + { + $id = (int) $entry['handle']; + $this->handles[$id] = $entry; + + // If the request is a delay, then add the reques to the curl multi + // pool only after the specified delay. + if (isset($entry['request']['client']['delay'])) { + $this->delays[$id] = microtime(true) + ($entry['request']['client']['delay'] / 1000); + } elseif (empty($entry['request']['future'])) { + curl_multi_add_handle($this->_mh, $entry['handle']); + } else { + curl_multi_add_handle($this->_mh, $entry['handle']); + // "lazy" futures are only sent once the pool has many requests. + if ($entry['request']['future'] !== 'lazy') { + do { + $mrc = curl_multi_exec($this->_mh, $this->active); + } while ($mrc === CURLM_CALL_MULTI_PERFORM); + $this->processMessages(); + } + } + } + + private function removeProcessed($id) + { + if (isset($this->handles[$id])) { + curl_multi_remove_handle( + $this->_mh, + $this->handles[$id]['handle'] + ); + curl_close($this->handles[$id]['handle']); + unset($this->handles[$id], $this->delays[$id]); + } + } + + /** + * Cancels a handle from sending and removes references to it. + * + * @param int $id Handle ID to cancel and remove. + * + * @return bool True on success, false on failure. + */ + private function cancel($id) + { + // Cannot cancel if it has been processed. + if (!isset($this->handles[$id])) { + return false; + } + + $handle = $this->handles[$id]['handle']; + unset($this->delays[$id], $this->handles[$id]); + curl_multi_remove_handle($this->_mh, $handle); + curl_close($handle); + + return true; + } + + private function addDelays() + { + $currentTime = microtime(true); + + foreach ($this->delays as $id => $delay) { + if ($currentTime >= $delay) { + unset($this->delays[$id]); + curl_multi_add_handle( + $this->_mh, + $this->handles[$id]['handle'] + ); + } + } + } + + private function processMessages() + { + while ($done = curl_multi_info_read($this->_mh)) { + $id = (int) $done['handle']; + + if (!isset($this->handles[$id])) { + // Probably was cancelled. + continue; + } + + $entry = $this->handles[$id]; + $entry['response']['transfer_stats'] = curl_getinfo($done['handle']); + + if ($done['result'] !== CURLM_OK) { + $entry['response']['curl']['errno'] = $done['result']; + $entry['response']['curl']['error'] = curl_error($done['handle']); + } + + $result = CurlFactory::createResponse( + $this, + $entry['request'], + $entry['response'], + $entry['headers'], + $entry['body'] + ); + + $this->removeProcessed($id); + $entry['deferred']->resolve($result); + } + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Client/Middleware.php b/vendor/guzzlehttp/ringphp/src/Client/Middleware.php new file mode 100644 index 0000000..6fa7318 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Client/Middleware.php @@ -0,0 +1,58 @@ +result = $result; + } + + public function __invoke(array $request) + { + Core::doSleep($request); + $response = is_callable($this->result) + ? call_user_func($this->result, $request) + : $this->result; + + if (is_array($response)) { + $response = new CompletedFutureArray($response + [ + 'status' => null, + 'body' => null, + 'headers' => [], + 'reason' => null, + 'effective_url' => null, + ]); + } elseif (!$response instanceof FutureArrayInterface) { + throw new \InvalidArgumentException( + 'Response must be an array or FutureArrayInterface. Found ' + . Core::describeType($request) + ); + } + + return $response; + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php b/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php new file mode 100644 index 0000000..4bacec1 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Client/StreamHandler.php @@ -0,0 +1,414 @@ +options = $options; + } + + public function __invoke(array $request) + { + $url = Core::url($request); + Core::doSleep($request); + + try { + // Does not support the expect header. + $request = Core::removeHeader($request, 'Expect'); + $stream = $this->createStream($url, $request); + return $this->createResponse($request, $url, $stream); + } catch (RingException $e) { + return $this->createErrorResponse($url, $e); + } + } + + private function createResponse(array $request, $url, $stream) + { + $hdrs = $this->lastHeaders; + $this->lastHeaders = null; + $parts = explode(' ', array_shift($hdrs), 3); + $response = [ + 'version' => substr($parts[0], 5), + 'status' => $parts[1], + 'reason' => isset($parts[2]) ? $parts[2] : null, + 'headers' => Core::headersFromLines($hdrs), + 'effective_url' => $url, + ]; + + $stream = $this->checkDecode($request, $response, $stream); + + // If not streaming, then drain the response into a stream. + if (empty($request['client']['stream'])) { + $dest = isset($request['client']['save_to']) + ? $request['client']['save_to'] + : fopen('php://temp', 'r+'); + $stream = $this->drain($stream, $dest); + } + + $response['body'] = $stream; + + return new CompletedFutureArray($response); + } + + private function checkDecode(array $request, array $response, $stream) + { + // Automatically decode responses when instructed. + if (!empty($request['client']['decode_content'])) { + switch (Core::firstHeader($response, 'Content-Encoding', true)) { + case 'gzip': + case 'deflate': + $stream = new InflateStream(Stream::factory($stream)); + break; + } + } + + return $stream; + } + + /** + * Drains the stream into the "save_to" client option. + * + * @param resource $stream + * @param string|resource|StreamInterface $dest + * + * @return Stream + * @throws \RuntimeException when the save_to option is invalid. + */ + private function drain($stream, $dest) + { + if (is_resource($stream)) { + if (!is_resource($dest)) { + $stream = Stream::factory($stream); + } else { + stream_copy_to_stream($stream, $dest); + fclose($stream); + rewind($dest); + return $dest; + } + } + + // Stream the response into the destination stream + $dest = is_string($dest) + ? new Stream(Utils::open($dest, 'r+')) + : Stream::factory($dest); + + Utils::copyToStream($stream, $dest); + $dest->seek(0); + $stream->close(); + + return $dest; + } + + /** + * Creates an error response for the given stream. + * + * @param string $url + * @param RingException $e + * + * @return array + */ + private function createErrorResponse($url, RingException $e) + { + // Determine if the error was a networking error. + $message = $e->getMessage(); + + // This list can probably get more comprehensive. + if (strpos($message, 'getaddrinfo') // DNS lookup failed + || strpos($message, 'Connection refused') + ) { + $e = new ConnectException($e->getMessage(), 0, $e); + } + + return new CompletedFutureArray([ + 'status' => null, + 'body' => null, + 'headers' => [], + 'effective_url' => $url, + 'error' => $e + ]); + } + + /** + * Create a resource and check to ensure it was created successfully + * + * @param callable $callback Callable that returns stream resource + * + * @return resource + * @throws \RuntimeException on error + */ + private function createResource(callable $callback) + { + $errors = null; + set_error_handler(function ($_, $msg, $file, $line) use (&$errors) { + $errors[] = [ + 'message' => $msg, + 'file' => $file, + 'line' => $line + ]; + return true; + }); + + $resource = $callback(); + restore_error_handler(); + + if (!$resource) { + $message = 'Error creating resource: '; + foreach ($errors as $err) { + foreach ($err as $key => $value) { + $message .= "[$key] $value" . PHP_EOL; + } + } + throw new RingException(trim($message)); + } + + return $resource; + } + + private function createStream($url, array $request) + { + static $methods; + if (!$methods) { + $methods = array_flip(get_class_methods(__CLASS__)); + } + + // HTTP/1.1 streams using the PHP stream wrapper require a + // Connection: close header + if ((!isset($request['version']) || $request['version'] == '1.1') + && !Core::hasHeader($request, 'Connection') + ) { + $request['headers']['Connection'] = ['close']; + } + + // Ensure SSL is verified by default + if (!isset($request['client']['verify'])) { + $request['client']['verify'] = true; + } + + $params = []; + $options = $this->getDefaultOptions($request); + + if (isset($request['client'])) { + foreach ($request['client'] as $key => $value) { + $method = "add_{$key}"; + if (isset($methods[$method])) { + $this->{$method}($request, $options, $value, $params); + } + } + } + + return $this->createStreamResource( + $url, + $request, + $options, + $this->createContext($request, $options, $params) + ); + } + + private function getDefaultOptions(array $request) + { + $headers = ""; + foreach ($request['headers'] as $name => $value) { + foreach ((array) $value as $val) { + $headers .= "$name: $val\r\n"; + } + } + + $context = [ + 'http' => [ + 'method' => $request['http_method'], + 'header' => $headers, + 'protocol_version' => isset($request['version']) ? $request['version'] : 1.1, + 'ignore_errors' => true, + 'follow_location' => 0, + ], + ]; + + $body = Core::body($request); + if (isset($body)) { + $context['http']['content'] = $body; + // Prevent the HTTP handler from adding a Content-Type header. + if (!Core::hasHeader($request, 'Content-Type')) { + $context['http']['header'] .= "Content-Type:\r\n"; + } + } + + $context['http']['header'] = rtrim($context['http']['header']); + + return $context; + } + + private function add_proxy(array $request, &$options, $value, &$params) + { + if (!is_array($value)) { + $options['http']['proxy'] = $value; + } else { + $scheme = isset($request['scheme']) ? $request['scheme'] : 'http'; + if (isset($value[$scheme])) { + $options['http']['proxy'] = $value[$scheme]; + } + } + } + + private function add_timeout(array $request, &$options, $value, &$params) + { + $options['http']['timeout'] = $value; + } + + private function add_verify(array $request, &$options, $value, &$params) + { + if ($value === true) { + // PHP 5.6 or greater will find the system cert by default. When + // < 5.6, use the Guzzle bundled cacert. + if (PHP_VERSION_ID < 50600) { + $options['ssl']['cafile'] = ClientUtils::getDefaultCaBundle(); + } + } elseif (is_string($value)) { + $options['ssl']['cafile'] = $value; + if (!file_exists($value)) { + throw new RingException("SSL CA bundle not found: $value"); + } + } elseif ($value === false) { + $options['ssl']['verify_peer'] = false; + $options['ssl']['allow_self_signed'] = true; + return; + } else { + throw new RingException('Invalid verify request option'); + } + + $options['ssl']['verify_peer'] = true; + $options['ssl']['allow_self_signed'] = false; + } + + private function add_cert(array $request, &$options, $value, &$params) + { + if (is_array($value)) { + $options['ssl']['passphrase'] = $value[1]; + $value = $value[0]; + } + + if (!file_exists($value)) { + throw new RingException("SSL certificate not found: {$value}"); + } + + $options['ssl']['local_cert'] = $value; + } + + private function add_progress(array $request, &$options, $value, &$params) + { + $fn = function ($code, $_1, $_2, $_3, $transferred, $total) use ($value) { + if ($code == STREAM_NOTIFY_PROGRESS) { + $value($total, $transferred, null, null); + } + }; + + // Wrap the existing function if needed. + $params['notification'] = isset($params['notification']) + ? Core::callArray([$params['notification'], $fn]) + : $fn; + } + + private function add_debug(array $request, &$options, $value, &$params) + { + if ($value === false) { + return; + } + + static $map = [ + STREAM_NOTIFY_CONNECT => 'CONNECT', + STREAM_NOTIFY_AUTH_REQUIRED => 'AUTH_REQUIRED', + STREAM_NOTIFY_AUTH_RESULT => 'AUTH_RESULT', + STREAM_NOTIFY_MIME_TYPE_IS => 'MIME_TYPE_IS', + STREAM_NOTIFY_FILE_SIZE_IS => 'FILE_SIZE_IS', + STREAM_NOTIFY_REDIRECTED => 'REDIRECTED', + STREAM_NOTIFY_PROGRESS => 'PROGRESS', + STREAM_NOTIFY_FAILURE => 'FAILURE', + STREAM_NOTIFY_COMPLETED => 'COMPLETED', + STREAM_NOTIFY_RESOLVE => 'RESOLVE', + ]; + + static $args = ['severity', 'message', 'message_code', + 'bytes_transferred', 'bytes_max']; + + $value = Core::getDebugResource($value); + $ident = $request['http_method'] . ' ' . Core::url($request); + $fn = function () use ($ident, $value, $map, $args) { + $passed = func_get_args(); + $code = array_shift($passed); + fprintf($value, '<%s> [%s] ', $ident, $map[$code]); + foreach (array_filter($passed) as $i => $v) { + fwrite($value, $args[$i] . ': "' . $v . '" '); + } + fwrite($value, "\n"); + }; + + // Wrap the existing function if needed. + $params['notification'] = isset($params['notification']) + ? Core::callArray([$params['notification'], $fn]) + : $fn; + } + + private function applyCustomOptions(array $request, array &$options) + { + if (!isset($request['client']['stream_context'])) { + return; + } + + if (!is_array($request['client']['stream_context'])) { + throw new RingException('stream_context must be an array'); + } + + $options = array_replace_recursive( + $options, + $request['client']['stream_context'] + ); + } + + private function createContext(array $request, array $options, array $params) + { + $this->applyCustomOptions($request, $options); + return $this->createResource( + function () use ($request, $options, $params) { + return stream_context_create($options, $params); + }, + $request, + $options + ); + } + + private function createStreamResource( + $url, + array $request, + array $options, + $context + ) { + return $this->createResource( + function () use ($url, $context) { + if (false === strpos($url, 'http')) { + trigger_error("URL is invalid: {$url}", E_USER_WARNING); + return null; + } + $resource = fopen($url, 'r', null, $context); + $this->lastHeaders = $http_response_header; + return $resource; + }, + $request, + $options + ); + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Core.php b/vendor/guzzlehttp/ringphp/src/Core.php new file mode 100644 index 0000000..dd7d1a0 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Core.php @@ -0,0 +1,364 @@ + $value) { + if (!strcasecmp($name, $header)) { + $result = array_merge($result, $value); + } + } + } + + return $result; + } + + /** + * Gets a header value from a message as a string or null + * + * This method searches through the "headers" key of a message for a header + * using a case-insensitive search. The lines of the header are imploded + * using commas into a single string return value. + * + * @param array $message Request or response hash. + * @param string $header Header to retrieve + * + * @return string|null Returns the header string if found, or null if not. + */ + public static function header($message, $header) + { + $match = self::headerLines($message, $header); + return $match ? implode(', ', $match) : null; + } + + /** + * Returns the first header value from a message as a string or null. If + * a header line contains multiple values separated by a comma, then this + * function will return the first value in the list. + * + * @param array $message Request or response hash. + * @param string $header Header to retrieve + * + * @return string|null Returns the value as a string if found. + */ + public static function firstHeader($message, $header) + { + if (!empty($message['headers'])) { + foreach ($message['headers'] as $name => $value) { + if (!strcasecmp($name, $header)) { + // Return the match itself if it is a single value. + $pos = strpos($value[0], ','); + return $pos ? substr($value[0], 0, $pos) : $value[0]; + } + } + } + + return null; + } + + /** + * Returns true if a message has the provided case-insensitive header. + * + * @param array $message Request or response hash. + * @param string $header Header to check + * + * @return bool + */ + public static function hasHeader($message, $header) + { + if (!empty($message['headers'])) { + foreach ($message['headers'] as $name => $value) { + if (!strcasecmp($name, $header)) { + return true; + } + } + } + + return false; + } + + /** + * Parses an array of header lines into an associative array of headers. + * + * @param array $lines Header lines array of strings in the following + * format: "Name: Value" + * @return array + */ + public static function headersFromLines($lines) + { + $headers = []; + + foreach ($lines as $line) { + $parts = explode(':', $line, 2); + $headers[trim($parts[0])][] = isset($parts[1]) + ? trim($parts[1]) + : null; + } + + return $headers; + } + + /** + * Removes a header from a message using a case-insensitive comparison. + * + * @param array $message Message that contains 'headers' + * @param string $header Header to remove + * + * @return array + */ + public static function removeHeader(array $message, $header) + { + if (isset($message['headers'])) { + foreach (array_keys($message['headers']) as $key) { + if (!strcasecmp($header, $key)) { + unset($message['headers'][$key]); + } + } + } + + return $message; + } + + /** + * Replaces any existing case insensitive headers with the given value. + * + * @param array $message Message that contains 'headers' + * @param string $header Header to set. + * @param array $value Value to set. + * + * @return array + */ + public static function setHeader(array $message, $header, array $value) + { + $message = self::removeHeader($message, $header); + $message['headers'][$header] = $value; + + return $message; + } + + /** + * Creates a URL string from a request. + * + * If the "url" key is present on the request, it is returned, otherwise + * the url is built up based on the scheme, host, uri, and query_string + * request values. + * + * @param array $request Request to get the URL from + * + * @return string Returns the request URL as a string. + * @throws \InvalidArgumentException if no Host header is present. + */ + public static function url(array $request) + { + if (isset($request['url'])) { + return $request['url']; + } + + $uri = (isset($request['scheme']) + ? $request['scheme'] : 'http') . '://'; + + if ($host = self::header($request, 'host')) { + $uri .= $host; + } else { + throw new \InvalidArgumentException('No Host header was provided'); + } + + if (isset($request['uri'])) { + $uri .= $request['uri']; + } + + if (isset($request['query_string'])) { + $uri .= '?' . $request['query_string']; + } + + return $uri; + } + + /** + * Reads the body of a message into a string. + * + * @param array|FutureArrayInterface $message Array containing a "body" key + * + * @return null|string Returns the body as a string or null if not set. + * @throws \InvalidArgumentException if a request body is invalid. + */ + public static function body($message) + { + if (!isset($message['body'])) { + return null; + } + + if ($message['body'] instanceof StreamInterface) { + return (string) $message['body']; + } + + switch (gettype($message['body'])) { + case 'string': + return $message['body']; + case 'resource': + return stream_get_contents($message['body']); + case 'object': + if ($message['body'] instanceof \Iterator) { + return implode('', iterator_to_array($message['body'])); + } elseif (method_exists($message['body'], '__toString')) { + return (string) $message['body']; + } + default: + throw new \InvalidArgumentException('Invalid request body: ' + . self::describeType($message['body'])); + } + } + + /** + * Rewind the body of the provided message if possible. + * + * @param array $message Message that contains a 'body' field. + * + * @return bool Returns true on success, false on failure + */ + public static function rewindBody($message) + { + if ($message['body'] instanceof StreamInterface) { + return $message['body']->seek(0); + } + + if ($message['body'] instanceof \Generator) { + return false; + } + + if ($message['body'] instanceof \Iterator) { + $message['body']->rewind(); + return true; + } + + if (is_resource($message['body'])) { + return rewind($message['body']); + } + + return is_string($message['body']) + || (is_object($message['body']) + && method_exists($message['body'], '__toString')); + } + + /** + * Debug function used to describe the provided value type and class. + * + * @param mixed $input + * + * @return string Returns a string containing the type of the variable and + * if a class is provided, the class name. + */ + public static function describeType($input) + { + switch (gettype($input)) { + case 'object': + return 'object(' . get_class($input) . ')'; + case 'array': + return 'array(' . count($input) . ')'; + default: + ob_start(); + var_dump($input); + // normalize float vs double + return str_replace('double(', 'float(', rtrim(ob_get_clean())); + } + } + + /** + * Sleep for the specified amount of time specified in the request's + * ['client']['delay'] option if present. + * + * This function should only be used when a non-blocking sleep is not + * possible. + * + * @param array $request Request to sleep + */ + public static function doSleep(array $request) + { + if (isset($request['client']['delay'])) { + usleep($request['client']['delay'] * 1000); + } + } + + /** + * Returns a proxied future that modifies the dereferenced value of another + * future using a promise. + * + * @param FutureArrayInterface $future Future to wrap with a new future + * @param callable $onFulfilled Invoked when the future fulfilled + * @param callable $onRejected Invoked when the future rejected + * @param callable $onProgress Invoked when the future progresses + * + * @return FutureArray + */ + public static function proxy( + FutureArrayInterface $future, + callable $onFulfilled = null, + callable $onRejected = null, + callable $onProgress = null + ) { + return new FutureArray( + $future->then($onFulfilled, $onRejected, $onProgress), + [$future, 'wait'], + [$future, 'cancel'] + ); + } + + /** + * Returns a debug stream based on the provided variable. + * + * @param mixed $value Optional value + * + * @return resource + */ + public static function getDebugResource($value = null) + { + if (is_resource($value)) { + return $value; + } elseif (defined('STDOUT')) { + return STDOUT; + } else { + return fopen('php://output', 'w'); + } + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php b/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php new file mode 100644 index 0000000..95b353a --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Exception/CancelledException.php @@ -0,0 +1,7 @@ +wrappedPromise = $promise; + $this->waitfn = $wait; + $this->cancelfn = $cancel; + } + + public function wait() + { + if (!$this->isRealized) { + $this->addShadow(); + if (!$this->isRealized && $this->waitfn) { + $this->invokeWait(); + } + if (!$this->isRealized) { + $this->error = new RingException('Waiting did not resolve future'); + } + } + + if ($this->error) { + throw $this->error; + } + + return $this->result; + } + + public function promise() + { + return $this->wrappedPromise; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null, + callable $onProgress = null + ) { + return $this->wrappedPromise->then($onFulfilled, $onRejected, $onProgress); + } + + public function cancel() + { + if (!$this->isRealized) { + $cancelfn = $this->cancelfn; + $this->waitfn = $this->cancelfn = null; + $this->isRealized = true; + $this->error = new CancelledFutureAccessException(); + if ($cancelfn) { + $cancelfn($this); + } + } + } + + private function addShadow() + { + // Get the result and error when the promise is resolved. Note that + // calling this function might trigger the resolution immediately. + $this->wrappedPromise->then( + function ($value) { + $this->isRealized = true; + $this->result = $value; + $this->waitfn = $this->cancelfn = null; + }, + function ($error) { + $this->isRealized = true; + $this->error = $error; + $this->waitfn = $this->cancelfn = null; + } + ); + } + + private function invokeWait() + { + try { + $wait = $this->waitfn; + $this->waitfn = null; + $wait(); + } catch (\Exception $e) { + // Defer can throw to reject. + $this->error = $e; + $this->isRealized = true; + } + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php new file mode 100644 index 0000000..0a90c93 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureArray.php @@ -0,0 +1,43 @@ +result[$offset]); + } + + public function offsetGet($offset) + { + return $this->result[$offset]; + } + + public function offsetSet($offset, $value) + { + $this->result[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->result[$offset]); + } + + public function count() + { + return count($this->result); + } + + public function getIterator() + { + return new \ArrayIterator($this->result); + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php new file mode 100644 index 0000000..0d25af7 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Future/CompletedFutureValue.php @@ -0,0 +1,57 @@ +result = $result; + $this->error = $e; + } + + public function wait() + { + if ($this->error) { + throw $this->error; + } + + return $this->result; + } + + public function cancel() {} + + public function promise() + { + if (!$this->cachedPromise) { + $this->cachedPromise = $this->error + ? new RejectedPromise($this->error) + : new FulfilledPromise($this->result); + } + + return $this->cachedPromise; + } + + public function then( + callable $onFulfilled = null, + callable $onRejected = null, + callable $onProgress = null + ) { + return $this->promise()->then($onFulfilled, $onRejected, $onProgress); + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php b/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php new file mode 100644 index 0000000..3d64c96 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Future/FutureArray.php @@ -0,0 +1,40 @@ +_value[$offset]); + } + + public function offsetGet($offset) + { + return $this->_value[$offset]; + } + + public function offsetSet($offset, $value) + { + $this->_value[$offset] = $value; + } + + public function offsetUnset($offset) + { + unset($this->_value[$offset]); + } + + public function count() + { + return count($this->_value); + } + + public function getIterator() + { + return new \ArrayIterator($this->_value); + } +} diff --git a/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php b/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php new file mode 100644 index 0000000..58f5f73 --- /dev/null +++ b/vendor/guzzlehttp/ringphp/src/Future/FutureArrayInterface.php @@ -0,0 +1,11 @@ +_value = $this->wait(); + } +} diff --git a/vendor/guzzlehttp/streams/LICENSE b/vendor/guzzlehttp/streams/LICENSE new file mode 100644 index 0000000..71d3b78 --- /dev/null +++ b/vendor/guzzlehttp/streams/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014 Michael Dowling, https://github.com/mtdowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/guzzlehttp/streams/composer.json b/vendor/guzzlehttp/streams/composer.json new file mode 100644 index 0000000..6d70343 --- /dev/null +++ b/vendor/guzzlehttp/streams/composer.json @@ -0,0 +1,28 @@ +{ + "name": "guzzlehttp/streams", + "description": "Provides a simple abstraction over streams of data", + "homepage": "http://guzzlephp.org/", + "keywords": ["stream", "guzzle"], + "license": "MIT", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "autoload": { + "psr-4": { "GuzzleHttp\\Stream\\": "src/" } + }, + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + } +} diff --git a/vendor/guzzlehttp/streams/src/AppendStream.php b/vendor/guzzlehttp/streams/src/AppendStream.php new file mode 100644 index 0000000..94bda71 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/AppendStream.php @@ -0,0 +1,220 @@ +addStream($stream); + } + } + + public function __toString() + { + try { + $this->seek(0); + return $this->getContents(); + } catch (\Exception $e) { + return ''; + } + } + + /** + * Add a stream to the AppendStream + * + * @param StreamInterface $stream Stream to append. Must be readable. + * + * @throws \InvalidArgumentException if the stream is not readable + */ + public function addStream(StreamInterface $stream) + { + if (!$stream->isReadable()) { + throw new \InvalidArgumentException('Each stream must be readable'); + } + + // The stream is only seekable if all streams are seekable + if (!$stream->isSeekable()) { + $this->seekable = false; + } + + $this->streams[] = $stream; + } + + public function getContents() + { + return Utils::copyToString($this); + } + + /** + * Closes each attached stream. + * + * {@inheritdoc} + */ + public function close() + { + $this->pos = $this->current = 0; + + foreach ($this->streams as $stream) { + $stream->close(); + } + + $this->streams = []; + } + + /** + * Detaches each attached stream + * + * {@inheritdoc} + */ + public function detach() + { + $this->close(); + $this->detached = true; + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function tell() + { + return $this->pos; + } + + /** + * Tries to calculate the size by adding the size of each stream. + * + * If any of the streams do not return a valid number, then the size of the + * append stream cannot be determined and null is returned. + * + * {@inheritdoc} + */ + public function getSize() + { + $size = 0; + + foreach ($this->streams as $stream) { + $s = $stream->getSize(); + if ($s === null) { + return null; + } + $size += $s; + } + + return $size; + } + + public function eof() + { + return !$this->streams || + ($this->current >= count($this->streams) - 1 && + $this->streams[$this->current]->eof()); + } + + /** + * Attempts to seek to the given position. Only supports SEEK_SET. + * + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if (!$this->seekable || $whence !== SEEK_SET) { + return false; + } + + $success = true; + $this->pos = $this->current = 0; + + // Rewind each stream + foreach ($this->streams as $stream) { + if (!$stream->seek(0)) { + $success = false; + } + } + + if (!$success) { + return false; + } + + // Seek to the actual position by reading from each stream + while ($this->pos < $offset && !$this->eof()) { + $this->read(min(8096, $offset - $this->pos)); + } + + return $this->pos == $offset; + } + + /** + * Reads from all of the appended streams until the length is met or EOF. + * + * {@inheritdoc} + */ + public function read($length) + { + $buffer = ''; + $total = count($this->streams) - 1; + $remaining = $length; + + while ($remaining > 0) { + // Progress to the next stream if needed. + if ($this->streams[$this->current]->eof()) { + if ($this->current == $total) { + break; + } + $this->current++; + } + $buffer .= $this->streams[$this->current]->read($remaining); + $remaining = $length - strlen($buffer); + } + + $this->pos += strlen($buffer); + + return $buffer; + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return false; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function write($string) + { + return false; + } + + public function getMetadata($key = null) + { + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/streams/src/AsyncReadStream.php b/vendor/guzzlehttp/streams/src/AsyncReadStream.php new file mode 100644 index 0000000..25ad960 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/AsyncReadStream.php @@ -0,0 +1,207 @@ +isReadable() || !$buffer->isWritable()) { + throw new \InvalidArgumentException( + 'Buffer must be readable and writable' + ); + } + + if (isset($config['size'])) { + $this->size = $config['size']; + } + + static $callables = ['pump', 'drain']; + foreach ($callables as $check) { + if (isset($config[$check])) { + if (!is_callable($config[$check])) { + throw new \InvalidArgumentException( + $check . ' must be callable' + ); + } + $this->{$check} = $config[$check]; + } + } + + $this->hwm = $buffer->getMetadata('hwm'); + + // Cannot drain when there's no high water mark. + if ($this->hwm === null) { + $this->drain = null; + } + + $this->stream = $buffer; + } + + /** + * Factory method used to create new async stream and an underlying buffer + * if no buffer is provided. + * + * This function accepts the same options as AsyncReadStream::__construct, + * but added the following key value pairs: + * + * - buffer: (StreamInterface) Buffer used to buffer data. If none is + * provided, a default buffer is created. + * - hwm: (int) High water mark to use if a buffer is created on your + * behalf. + * - max_buffer: (int) If provided, wraps the utilized buffer in a + * DroppingStream decorator to ensure that buffer does not exceed a given + * length. When exceeded, the stream will begin dropping data. Set the + * max_buffer to 0, to use a NullStream which does not store data. + * - write: (callable) A function that is invoked when data is written + * to the underlying buffer. The function accepts the buffer as the first + * argument, and the data being written as the second. The function MUST + * return the number of bytes that were written or false to let writers + * know to slow down. + * - drain: (callable) See constructor documentation. + * - pump: (callable) See constructor documentation. + * + * @param array $options Associative array of options. + * + * @return array Returns an array containing the buffer used to buffer + * data, followed by the ready to use AsyncReadStream object. + */ + public static function create(array $options = []) + { + $maxBuffer = isset($options['max_buffer']) + ? $options['max_buffer'] + : null; + + if ($maxBuffer === 0) { + $buffer = new NullStream(); + } elseif (isset($options['buffer'])) { + $buffer = $options['buffer']; + } else { + $hwm = isset($options['hwm']) ? $options['hwm'] : 16384; + $buffer = new BufferStream($hwm); + } + + if ($maxBuffer > 0) { + $buffer = new DroppingStream($buffer, $options['max_buffer']); + } + + // Call the on_write callback if an on_write function was provided. + if (isset($options['write'])) { + $onWrite = $options['write']; + $buffer = FnStream::decorate($buffer, [ + 'write' => function ($string) use ($buffer, $onWrite) { + $result = $buffer->write($string); + $onWrite($buffer, $string); + return $result; + } + ]); + } + + return [$buffer, new self($buffer, $options)]; + } + + public function getSize() + { + return $this->size; + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + return false; + } + + public function read($length) + { + if (!$this->needsDrain && $this->drain) { + $this->needsDrain = $this->stream->getSize() >= $this->hwm; + } + + $result = $this->stream->read($length); + + // If we need to drain, then drain when the buffer is empty. + if ($this->needsDrain && $this->stream->getSize() === 0) { + $this->needsDrain = false; + $drainFn = $this->drain; + $drainFn($this->stream); + } + + $resultLen = strlen($result); + + // If a pump was provided, the buffer is still open, and not enough + // data was given, then block until the data is provided. + if ($this->pump && $resultLen < $length) { + $pumpFn = $this->pump; + $result .= $pumpFn($length - $resultLen); + } + + return $result; + } +} diff --git a/vendor/guzzlehttp/streams/src/BufferStream.php b/vendor/guzzlehttp/streams/src/BufferStream.php new file mode 100644 index 0000000..0fffbd6 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/BufferStream.php @@ -0,0 +1,138 @@ +hwm = $hwm; + } + + public function __toString() + { + return $this->getContents(); + } + + public function getContents() + { + $buffer = $this->buffer; + $this->buffer = ''; + + return $buffer; + } + + public function close() + { + $this->buffer = ''; + } + + public function detach() + { + $this->close(); + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function getSize() + { + return strlen($this->buffer); + } + + public function isReadable() + { + return true; + } + + public function isWritable() + { + return true; + } + + public function isSeekable() + { + return false; + } + + public function seek($offset, $whence = SEEK_SET) + { + return false; + } + + public function eof() + { + return strlen($this->buffer) === 0; + } + + public function tell() + { + return false; + } + + /** + * Reads data from the buffer. + */ + public function read($length) + { + $currentLength = strlen($this->buffer); + + if ($length >= $currentLength) { + // No need to slice the buffer because we don't have enough data. + $result = $this->buffer; + $this->buffer = ''; + } else { + // Slice up the result to provide a subset of the buffer. + $result = substr($this->buffer, 0, $length); + $this->buffer = substr($this->buffer, $length); + } + + return $result; + } + + /** + * Writes data to the buffer. + */ + public function write($string) + { + $this->buffer .= $string; + + if (strlen($this->buffer) >= $this->hwm) { + return false; + } + + return strlen($string); + } + + public function getMetadata($key = null) + { + if ($key == 'hwm') { + return $this->hwm; + } + + return $key ? null : []; + } +} diff --git a/vendor/guzzlehttp/streams/src/CachingStream.php b/vendor/guzzlehttp/streams/src/CachingStream.php new file mode 100644 index 0000000..60bb905 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/CachingStream.php @@ -0,0 +1,122 @@ +remoteStream = $stream; + $this->stream = $target ?: new Stream(fopen('php://temp', 'r+')); + } + + public function getSize() + { + return max($this->stream->getSize(), $this->remoteStream->getSize()); + } + + /** + * {@inheritdoc} + * @throws SeekException When seeking with SEEK_END or when seeking + * past the total size of the buffer stream + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence == SEEK_SET) { + $byte = $offset; + } elseif ($whence == SEEK_CUR) { + $byte = $offset + $this->tell(); + } else { + return false; + } + + // You cannot skip ahead past where you've read from the remote stream + if ($byte > $this->stream->getSize()) { + throw new SeekException( + $this, + $byte, + sprintf('Cannot seek to byte %d when the buffered stream only' + . ' contains %d bytes', $byte, $this->stream->getSize()) + ); + } + + return $this->stream->seek($byte); + } + + public function read($length) + { + // Perform a regular read on any previously read data from the buffer + $data = $this->stream->read($length); + $remaining = $length - strlen($data); + + // More data was requested so read from the remote stream + if ($remaining) { + // If data was written to the buffer in a position that would have + // been filled from the remote stream, then we must skip bytes on + // the remote stream to emulate overwriting bytes from that + // position. This mimics the behavior of other PHP stream wrappers. + $remoteData = $this->remoteStream->read( + $remaining + $this->skipReadBytes + ); + + if ($this->skipReadBytes) { + $len = strlen($remoteData); + $remoteData = substr($remoteData, $this->skipReadBytes); + $this->skipReadBytes = max(0, $this->skipReadBytes - $len); + } + + $data .= $remoteData; + $this->stream->write($remoteData); + } + + return $data; + } + + public function write($string) + { + // When appending to the end of the currently read stream, you'll want + // to skip bytes from being read from the remote stream to emulate + // other stream wrappers. Basically replacing bytes of data of a fixed + // length. + $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell(); + if ($overflow > 0) { + $this->skipReadBytes += $overflow; + } + + return $this->stream->write($string); + } + + public function eof() + { + return $this->stream->eof() && $this->remoteStream->eof(); + } + + /** + * Close both the remote stream and buffer stream + */ + public function close() + { + $this->remoteStream->close() && $this->stream->close(); + } +} diff --git a/vendor/guzzlehttp/streams/src/DroppingStream.php b/vendor/guzzlehttp/streams/src/DroppingStream.php new file mode 100644 index 0000000..56ee80c --- /dev/null +++ b/vendor/guzzlehttp/streams/src/DroppingStream.php @@ -0,0 +1,42 @@ +stream = $stream; + $this->maxLength = $maxLength; + } + + public function write($string) + { + $diff = $this->maxLength - $this->stream->getSize(); + + // Begin returning false when the underlying stream is too large. + if ($diff <= 0) { + return false; + } + + // Write the stream or a subset of the stream if needed. + if (strlen($string) < $diff) { + return $this->stream->write($string); + } + + $this->stream->write(substr($string, 0, $diff)); + + return false; + } +} diff --git a/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php b/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php new file mode 100644 index 0000000..e631b9f --- /dev/null +++ b/vendor/guzzlehttp/streams/src/Exception/CannotAttachException.php @@ -0,0 +1,4 @@ +stream = $stream; + $msg = $msg ?: 'Could not seek the stream to position ' . $pos; + parent::__construct($msg); + } + + /** + * @return StreamInterface + */ + public function getStream() + { + return $this->stream; + } +} diff --git a/vendor/guzzlehttp/streams/src/FnStream.php b/vendor/guzzlehttp/streams/src/FnStream.php new file mode 100644 index 0000000..6b5872d --- /dev/null +++ b/vendor/guzzlehttp/streams/src/FnStream.php @@ -0,0 +1,147 @@ +methods = $methods; + + // Create the functions on the class + foreach ($methods as $name => $fn) { + $this->{'_fn_' . $name} = $fn; + } + } + + /** + * Lazily determine which methods are not implemented. + * @throws \BadMethodCallException + */ + public function __get($name) + { + throw new \BadMethodCallException(str_replace('_fn_', '', $name) + . '() is not implemented in the FnStream'); + } + + /** + * The close method is called on the underlying stream only if possible. + */ + public function __destruct() + { + if (isset($this->_fn_close)) { + call_user_func($this->_fn_close); + } + } + + /** + * Adds custom functionality to an underlying stream by intercepting + * specific method calls. + * + * @param StreamInterface $stream Stream to decorate + * @param array $methods Hash of method name to a closure + * + * @return FnStream + */ + public static function decorate(StreamInterface $stream, array $methods) + { + // If any of the required methods were not provided, then simply + // proxy to the decorated stream. + foreach (array_diff(self::$slots, array_keys($methods)) as $diff) { + $methods[$diff] = [$stream, $diff]; + } + + return new self($methods); + } + + public function __toString() + { + return call_user_func($this->_fn___toString); + } + + public function close() + { + return call_user_func($this->_fn_close); + } + + public function detach() + { + return call_user_func($this->_fn_detach); + } + + public function attach($stream) + { + return call_user_func($this->_fn_attach, $stream); + } + + public function getSize() + { + return call_user_func($this->_fn_getSize); + } + + public function tell() + { + return call_user_func($this->_fn_tell); + } + + public function eof() + { + return call_user_func($this->_fn_eof); + } + + public function isSeekable() + { + return call_user_func($this->_fn_isSeekable); + } + + public function seek($offset, $whence = SEEK_SET) + { + return call_user_func($this->_fn_seek, $offset, $whence); + } + + public function isWritable() + { + return call_user_func($this->_fn_isWritable); + } + + public function write($string) + { + return call_user_func($this->_fn_write, $string); + } + + public function isReadable() + { + return call_user_func($this->_fn_isReadable); + } + + public function read($length) + { + return call_user_func($this->_fn_read, $length); + } + + public function getContents() + { + return call_user_func($this->_fn_getContents); + } + + public function getMetadata($key = null) + { + return call_user_func($this->_fn_getMetadata, $key); + } +} diff --git a/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php b/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php new file mode 100644 index 0000000..4d049a6 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/GuzzleStreamWrapper.php @@ -0,0 +1,117 @@ +isReadable()) { + $mode = $stream->isWritable() ? 'r+' : 'r'; + } elseif ($stream->isWritable()) { + $mode = 'w'; + } else { + throw new \InvalidArgumentException('The stream must be readable, ' + . 'writable, or both.'); + } + + return fopen('guzzle://stream', $mode, null, stream_context_create([ + 'guzzle' => ['stream' => $stream] + ])); + } + + /** + * Registers the stream wrapper if needed + */ + public static function register() + { + if (!in_array('guzzle', stream_get_wrappers())) { + stream_wrapper_register('guzzle', __CLASS__); + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $options = stream_context_get_options($this->context); + + if (!isset($options['guzzle']['stream'])) { + return false; + } + + $this->mode = $mode; + $this->stream = $options['guzzle']['stream']; + + return true; + } + + public function stream_read($count) + { + return $this->stream->read($count); + } + + public function stream_write($data) + { + return (int) $this->stream->write($data); + } + + public function stream_tell() + { + return $this->stream->tell(); + } + + public function stream_eof() + { + return $this->stream->eof(); + } + + public function stream_seek($offset, $whence) + { + return $this->stream->seek($offset, $whence); + } + + public function stream_stat() + { + static $modeMap = [ + 'r' => 33060, + 'r+' => 33206, + 'w' => 33188 + ]; + + return [ + 'dev' => 0, + 'ino' => 0, + 'mode' => $modeMap[$this->mode], + 'nlink' => 0, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->stream->getSize() ?: 0, + 'atime' => 0, + 'mtime' => 0, + 'ctime' => 0, + 'blksize' => 0, + 'blocks' => 0 + ]; + } +} diff --git a/vendor/guzzlehttp/streams/src/InflateStream.php b/vendor/guzzlehttp/streams/src/InflateStream.php new file mode 100644 index 0000000..978af21 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/InflateStream.php @@ -0,0 +1,27 @@ +stream = new Stream($resource); + } +} diff --git a/vendor/guzzlehttp/streams/src/LazyOpenStream.php b/vendor/guzzlehttp/streams/src/LazyOpenStream.php new file mode 100644 index 0000000..6242ee7 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/LazyOpenStream.php @@ -0,0 +1,37 @@ +filename = $filename; + $this->mode = $mode; + } + + /** + * Creates the underlying stream lazily when required. + * + * @return StreamInterface + */ + protected function createStream() + { + return Stream::factory(Utils::open($this->filename, $this->mode)); + } +} diff --git a/vendor/guzzlehttp/streams/src/LimitStream.php b/vendor/guzzlehttp/streams/src/LimitStream.php new file mode 100644 index 0000000..e9fad98 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/LimitStream.php @@ -0,0 +1,161 @@ +stream = $stream; + $this->setLimit($limit); + $this->setOffset($offset); + } + + public function eof() + { + // Always return true if the underlying stream is EOF + if ($this->stream->eof()) { + return true; + } + + // No limit and the underlying stream is not at EOF + if ($this->limit == -1) { + return false; + } + + $tell = $this->stream->tell(); + if ($tell === false) { + return false; + } + + return $tell >= $this->offset + $this->limit; + } + + /** + * Returns the size of the limited subset of data + * {@inheritdoc} + */ + public function getSize() + { + if (null === ($length = $this->stream->getSize())) { + return null; + } elseif ($this->limit == -1) { + return $length - $this->offset; + } else { + return min($this->limit, $length - $this->offset); + } + } + + /** + * Allow for a bounded seek on the read limited stream + * {@inheritdoc} + */ + public function seek($offset, $whence = SEEK_SET) + { + if ($whence !== SEEK_SET || $offset < 0) { + return false; + } + + $offset += $this->offset; + + if ($this->limit !== -1) { + if ($offset > $this->offset + $this->limit) { + $offset = $this->offset + $this->limit; + } + } + + return $this->stream->seek($offset); + } + + /** + * Give a relative tell() + * {@inheritdoc} + */ + public function tell() + { + return $this->stream->tell() - $this->offset; + } + + /** + * Set the offset to start limiting from + * + * @param int $offset Offset to seek to and begin byte limiting from + * + * @return self + * @throws SeekException + */ + public function setOffset($offset) + { + $current = $this->stream->tell(); + + if ($current !== $offset) { + // If the stream cannot seek to the offset position, then read to it + if (!$this->stream->seek($offset)) { + if ($current > $offset) { + throw new SeekException($this, $offset); + } else { + $this->stream->read($offset - $current); + } + } + } + + $this->offset = $offset; + + return $this; + } + + /** + * Set the limit of bytes that the decorator allows to be read from the + * stream. + * + * @param int $limit Number of bytes to allow to be read from the stream. + * Use -1 for no limit. + * @return self + */ + public function setLimit($limit) + { + $this->limit = $limit; + + return $this; + } + + public function read($length) + { + if ($this->limit == -1) { + return $this->stream->read($length); + } + + // Check if the current position is less than the total allowed + // bytes + original offset + $remaining = ($this->offset + $this->limit) - $this->stream->tell(); + if ($remaining > 0) { + // Only return the amount of requested data, ensuring that the byte + // limit is not exceeded + return $this->stream->read(min($remaining, $length)); + } else { + return false; + } + } +} diff --git a/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php b/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php new file mode 100644 index 0000000..c1433ad --- /dev/null +++ b/vendor/guzzlehttp/streams/src/MetadataStreamInterface.php @@ -0,0 +1,11 @@ +stream->attach($stream); + } +} diff --git a/vendor/guzzlehttp/streams/src/NullStream.php b/vendor/guzzlehttp/streams/src/NullStream.php new file mode 100644 index 0000000..41ee776 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/NullStream.php @@ -0,0 +1,78 @@ +source = $source; + $this->size = isset($options['size']) ? $options['size'] : null; + $this->metadata = isset($options['metadata']) ? $options['metadata'] : []; + $this->buffer = new BufferStream(); + } + + public function __toString() + { + return Utils::copyToString($this); + } + + public function close() + { + $this->detach(); + } + + public function detach() + { + $this->tellPos = false; + $this->source = null; + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function getSize() + { + return $this->size; + } + + public function tell() + { + return $this->tellPos; + } + + public function eof() + { + return !$this->source; + } + + public function isSeekable() + { + return false; + } + + public function seek($offset, $whence = SEEK_SET) + { + return false; + } + + public function isWritable() + { + return false; + } + + public function write($string) + { + return false; + } + + public function isReadable() + { + return true; + } + + public function read($length) + { + $data = $this->buffer->read($length); + $readLen = strlen($data); + $this->tellPos += $readLen; + $remaining = $length - $readLen; + + if ($remaining) { + $this->pump($remaining); + $data .= $this->buffer->read($remaining); + $this->tellPos += strlen($data) - $readLen; + } + + return $data; + } + + public function getContents() + { + $result = ''; + while (!$this->eof()) { + $result .= $this->read(1000000); + } + + return $result; + } + + public function getMetadata($key = null) + { + if (!$key) { + return $this->metadata; + } + + return isset($this->metadata[$key]) ? $this->metadata[$key] : null; + } + + private function pump($length) + { + if ($this->source) { + do { + $data = call_user_func($this->source, $length); + if ($data === false || $data === null) { + $this->source = null; + return; + } + $this->buffer->write($data); + $length -= strlen($data); + } while ($length > 0); + } + } +} diff --git a/vendor/guzzlehttp/streams/src/Stream.php b/vendor/guzzlehttp/streams/src/Stream.php new file mode 100644 index 0000000..7adbc5e --- /dev/null +++ b/vendor/guzzlehttp/streams/src/Stream.php @@ -0,0 +1,261 @@ + [ + 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, + 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, + 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a+' => true + ], + 'write' => [ + 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, + 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, + 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, + 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true + ] + ]; + + /** + * Create a new stream based on the input type. + * + * This factory accepts the same associative array of options as described + * in the constructor. + * + * @param resource|string|StreamInterface $resource Entity body data + * @param array $options Additional options + * + * @return Stream + * @throws \InvalidArgumentException if the $resource arg is not valid. + */ + public static function factory($resource = '', array $options = []) + { + $type = gettype($resource); + + if ($type == 'string') { + $stream = fopen('php://temp', 'r+'); + if ($resource !== '') { + fwrite($stream, $resource); + fseek($stream, 0); + } + return new self($stream, $options); + } + + if ($type == 'resource') { + return new self($resource, $options); + } + + if ($resource instanceof StreamInterface) { + return $resource; + } + + if ($type == 'object' && method_exists($resource, '__toString')) { + return self::factory((string) $resource, $options); + } + + if (is_callable($resource)) { + return new PumpStream($resource, $options); + } + + if ($resource instanceof \Iterator) { + return new PumpStream(function () use ($resource) { + if (!$resource->valid()) { + return false; + } + $result = $resource->current(); + $resource->next(); + return $result; + }, $options); + } + + throw new \InvalidArgumentException('Invalid resource type: ' . $type); + } + + /** + * This constructor accepts an associative array of options. + * + * - size: (int) If a read stream would otherwise have an indeterminate + * size, but the size is known due to foreknownledge, then you can + * provide that size, in bytes. + * - metadata: (array) Any additional metadata to return when the metadata + * of the stream is accessed. + * + * @param resource $stream Stream resource to wrap. + * @param array $options Associative array of options. + * + * @throws \InvalidArgumentException if the stream is not a stream resource + */ + public function __construct($stream, $options = []) + { + if (!is_resource($stream)) { + throw new \InvalidArgumentException('Stream must be a resource'); + } + + if (isset($options['size'])) { + $this->size = $options['size']; + } + + $this->customMetadata = isset($options['metadata']) + ? $options['metadata'] + : []; + + $this->attach($stream); + } + + /** + * Closes the stream when the destructed + */ + public function __destruct() + { + $this->close(); + } + + public function __toString() + { + if (!$this->stream) { + return ''; + } + + $this->seek(0); + + return (string) stream_get_contents($this->stream); + } + + public function getContents() + { + return $this->stream ? stream_get_contents($this->stream) : ''; + } + + public function close() + { + if (is_resource($this->stream)) { + fclose($this->stream); + } + + $this->detach(); + } + + public function detach() + { + $result = $this->stream; + $this->stream = $this->size = $this->uri = null; + $this->readable = $this->writable = $this->seekable = false; + + return $result; + } + + public function attach($stream) + { + $this->stream = $stream; + $meta = stream_get_meta_data($this->stream); + $this->seekable = $meta['seekable']; + $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]); + $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]); + $this->uri = $this->getMetadata('uri'); + } + + public function getSize() + { + if ($this->size !== null) { + return $this->size; + } + + if (!$this->stream) { + return null; + } + + // Clear the stat cache if the stream has a URI + if ($this->uri) { + clearstatcache(true, $this->uri); + } + + $stats = fstat($this->stream); + if (isset($stats['size'])) { + $this->size = $stats['size']; + return $this->size; + } + + return null; + } + + public function isReadable() + { + return $this->readable; + } + + public function isWritable() + { + return $this->writable; + } + + public function isSeekable() + { + return $this->seekable; + } + + public function eof() + { + return !$this->stream || feof($this->stream); + } + + public function tell() + { + return $this->stream ? ftell($this->stream) : false; + } + + public function setSize($size) + { + $this->size = $size; + + return $this; + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->seekable + ? fseek($this->stream, $offset, $whence) === 0 + : false; + } + + public function read($length) + { + return $this->readable ? fread($this->stream, $length) : false; + } + + public function write($string) + { + // We can't know the size after writing anything + $this->size = null; + + return $this->writable ? fwrite($this->stream, $string) : false; + } + + public function getMetadata($key = null) + { + if (!$this->stream) { + return $key ? null : []; + } elseif (!$key) { + return $this->customMetadata + stream_get_meta_data($this->stream); + } elseif (isset($this->customMetadata[$key])) { + return $this->customMetadata[$key]; + } + + $meta = stream_get_meta_data($this->stream); + + return isset($meta[$key]) ? $meta[$key] : null; + } +} diff --git a/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php new file mode 100644 index 0000000..39c19c5 --- /dev/null +++ b/vendor/guzzlehttp/streams/src/StreamDecoratorTrait.php @@ -0,0 +1,143 @@ +stream = $stream; + } + + /** + * Magic method used to create a new stream if streams are not added in + * the constructor of a decorator (e.g., LazyOpenStream). + */ + public function __get($name) + { + if ($name == 'stream') { + $this->stream = $this->createStream(); + return $this->stream; + } + + throw new \UnexpectedValueException("$name not found on class"); + } + + public function __toString() + { + try { + $this->seek(0); + return $this->getContents(); + } catch (\Exception $e) { + // Really, PHP? https://bugs.php.net/bug.php?id=53648 + trigger_error('StreamDecorator::__toString exception: ' + . (string) $e, E_USER_ERROR); + return ''; + } + } + + public function getContents() + { + return Utils::copyToString($this); + } + + /** + * Allow decorators to implement custom methods + * + * @param string $method Missing method name + * @param array $args Method arguments + * + * @return mixed + */ + public function __call($method, array $args) + { + $result = call_user_func_array(array($this->stream, $method), $args); + + // Always return the wrapped object if the result is a return $this + return $result === $this->stream ? $this : $result; + } + + public function close() + { + $this->stream->close(); + } + + public function getMetadata($key = null) + { + return $this->stream->getMetadata($key); + } + + public function detach() + { + return $this->stream->detach(); + } + + public function attach($stream) + { + throw new CannotAttachException(); + } + + public function getSize() + { + return $this->stream->getSize(); + } + + public function eof() + { + return $this->stream->eof(); + } + + public function tell() + { + return $this->stream->tell(); + } + + public function isReadable() + { + return $this->stream->isReadable(); + } + + public function isWritable() + { + return $this->stream->isWritable(); + } + + public function isSeekable() + { + return $this->stream->isSeekable(); + } + + public function seek($offset, $whence = SEEK_SET) + { + return $this->stream->seek($offset, $whence); + } + + public function read($length) + { + return $this->stream->read($length); + } + + public function write($string) + { + return $this->stream->write($string); + } + + /** + * Implement in subclasses to dynamically create streams when requested. + * + * @return StreamInterface + * @throws \BadMethodCallException + */ + protected function createStream() + { + throw new \BadMethodCallException('createStream() not implemented in ' + . get_class($this)); + } +} diff --git a/vendor/guzzlehttp/streams/src/StreamInterface.php b/vendor/guzzlehttp/streams/src/StreamInterface.php new file mode 100644 index 0000000..fd19c6f --- /dev/null +++ b/vendor/guzzlehttp/streams/src/StreamInterface.php @@ -0,0 +1,159 @@ +eof()) { + $buf = $stream->read(1048576); + if ($buf === false) { + break; + } + $buffer .= $buf; + } + return $buffer; + } + + $len = 0; + while (!$stream->eof() && $len < $maxLen) { + $buf = $stream->read($maxLen - $len); + if ($buf === false) { + break; + } + $buffer .= $buf; + $len = strlen($buffer); + } + + return $buffer; + } + + /** + * Copy the contents of a stream into another stream until the given number + * of bytes have been read. + * + * @param StreamInterface $source Stream to read from + * @param StreamInterface $dest Stream to write to + * @param int $maxLen Maximum number of bytes to read. Pass -1 + * to read the entire stream. + */ + public static function copyToStream( + StreamInterface $source, + StreamInterface $dest, + $maxLen = -1 + ) { + if ($maxLen === -1) { + while (!$source->eof()) { + if (!$dest->write($source->read(1048576))) { + break; + } + } + return; + } + + $bytes = 0; + while (!$source->eof()) { + $buf = $source->read($maxLen - $bytes); + if (!($len = strlen($buf))) { + break; + } + $bytes += $len; + $dest->write($buf); + if ($bytes == $maxLen) { + break; + } + } + } + + /** + * Calculate a hash of a Stream + * + * @param StreamInterface $stream Stream to calculate the hash for + * @param string $algo Hash algorithm (e.g. md5, crc32, etc) + * @param bool $rawOutput Whether or not to use raw output + * + * @return string Returns the hash of the stream + * @throws SeekException + */ + public static function hash( + StreamInterface $stream, + $algo, + $rawOutput = false + ) { + $pos = $stream->tell(); + + if ($pos > 0 && !$stream->seek(0)) { + throw new SeekException($stream); + } + + $ctx = hash_init($algo); + while (!$stream->eof()) { + hash_update($ctx, $stream->read(1048576)); + } + + $out = hash_final($ctx, (bool) $rawOutput); + $stream->seek($pos); + + return $out; + } + + /** + * Read a line from the stream up to the maximum allowed buffer length + * + * @param StreamInterface $stream Stream to read from + * @param int $maxLength Maximum buffer length + * + * @return string|bool + */ + public static function readline(StreamInterface $stream, $maxLength = null) + { + $buffer = ''; + $size = 0; + + while (!$stream->eof()) { + if (false === ($byte = $stream->read(1))) { + return $buffer; + } + $buffer .= $byte; + // Break when a new line is found or the max length - 1 is reached + if ($byte == PHP_EOL || ++$size == $maxLength - 1) { + break; + } + } + + return $buffer; + } + + /** + * Alias of GuzzleHttp\Stream\Stream::factory. + * + * @param mixed $resource Resource to create + * @param array $options Associative array of stream options defined in + * {@see \GuzzleHttp\Stream\Stream::__construct} + * + * @return StreamInterface + * + * @see GuzzleHttp\Stream\Stream::factory + * @see GuzzleHttp\Stream\Stream::__construct + */ + public static function create($resource, array $options = []) + { + return Stream::factory($resource, $options); + } +} diff --git a/vendor/ircmaxell/password-compat/LICENSE.md b/vendor/ircmaxell/password-compat/LICENSE.md new file mode 100644 index 0000000..1efc565 --- /dev/null +++ b/vendor/ircmaxell/password-compat/LICENSE.md @@ -0,0 +1,7 @@ +Copyright (c) 2012 Anthony Ferrara + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/ircmaxell/password-compat/composer.json b/vendor/ircmaxell/password-compat/composer.json new file mode 100644 index 0000000..822fd1f --- /dev/null +++ b/vendor/ircmaxell/password-compat/composer.json @@ -0,0 +1,20 @@ +{ + "name": "ircmaxell/password-compat", + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "keywords": ["password", "hashing"], + "homepage": "https://github.com/ircmaxell/password_compat", + "license": "MIT", + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "autoload": { + "files": ["lib/password.php"] + } +} diff --git a/vendor/ircmaxell/password-compat/lib/password.php b/vendor/ircmaxell/password-compat/lib/password.php new file mode 100644 index 0000000..cc6896c --- /dev/null +++ b/vendor/ircmaxell/password-compat/lib/password.php @@ -0,0 +1,314 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +namespace { + + if (!defined('PASSWORD_BCRYPT')) { + /** + * PHPUnit Process isolation caches constants, but not function declarations. + * So we need to check if the constants are defined separately from + * the functions to enable supporting process isolation in userland + * code. + */ + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + define('PASSWORD_BCRYPT_DEFAULT_COST', 10); + } + + if (!function_exists('password_hash')) { + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (is_null($password) || is_int($password)) { + $password = (string) $password; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + $resultLength = 0; + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = PASSWORD_BCRYPT_DEFAULT_COST; + if (isset($options['cost'])) { + $cost = $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + // The length of salt to generate + $raw_salt_len = 16; + // The length required in the final serialization + $required_salt_len = 22; + $hash_format = sprintf("$2y$%02d$", $cost); + // The expected length of the final crypt() output + $resultLength = 60; + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + $salt_requires_encoding = false; + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt_requires_encoding = true; + } + } else { + $buffer = ''; + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && @is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = PasswordCompat\binary\_strlen($buffer); + while ($read < $raw_salt_len) { + $buffer .= fread($f, $raw_salt_len - $read); + $read = PasswordCompat\binary\_strlen($buffer); + } + fclose($f); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + } + if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) { + $bl = PasswordCompat\binary\_strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = $buffer; + $salt_requires_encoding = true; + } + if ($salt_requires_encoding) { + // encode string with the Base64 variant used by crypt + $base64_digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + $bcrypt64_digits = + './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $base64_string = base64_encode($salt); + $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); + } + $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => PASSWORD_BCRYPT_DEFAULT_COST, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, "$2y$%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] != $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? $options['cost'] : PASSWORD_BCRYPT_DEFAULT_COST; + if ($cost != $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } + } + +} + +namespace PasswordCompat\binary { + + if (!function_exists('PasswordCompat\\binary\\_strlen')) { + + /** + * Count the number of bytes in a string + * + * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension. + * In this case, strlen() will count the number of *characters* based on the internal encoding. A + * sequence of bytes might be regarded as a single multibyte character. + * + * @param string $binary_string The input string + * + * @internal + * @return int The number of bytes + */ + function _strlen($binary_string) { + if (function_exists('mb_strlen')) { + return mb_strlen($binary_string, '8bit'); + } + return strlen($binary_string); + } + + /** + * Get a substring based on byte limits + * + * @see _strlen() + * + * @param string $binary_string The input string + * @param int $start + * @param int $length + * + * @internal + * @return string The substring + */ + function _substr($binary_string, $start, $length) { + if (function_exists('mb_substr')) { + return mb_substr($binary_string, $start, $length, '8bit'); + } + return substr($binary_string, $start, $length); + } + + /** + * Check if current PHP version is compatible with the library + * + * @return boolean the check result + */ + function check() { + static $pass = NULL; + + if (is_null($pass)) { + if (function_exists('crypt')) { + $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG'; + $test = crypt("password", $hash); + $pass = $test == $hash; + } else { + $pass = false; + } + } + return $pass; + } + + } +} \ No newline at end of file diff --git a/vendor/ircmaxell/password-compat/version-test.php b/vendor/ircmaxell/password-compat/version-test.php new file mode 100644 index 0000000..96f60ca --- /dev/null +++ b/vendor/ircmaxell/password-compat/version-test.php @@ -0,0 +1,6 @@ + +Contributions (c) 2013 Pieter Hordijk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/lusitanian/oauth/composer.json b/vendor/lusitanian/oauth/composer.json new file mode 100644 index 0000000..66fc46a --- /dev/null +++ b/vendor/lusitanian/oauth/composer.json @@ -0,0 +1,45 @@ +{ + "name": "lusitanian/oauth", + "description": "PHP 5.3+ oAuth 1/2 Library", + "keywords": ["oauth", "authentication", "authorization", "security"], + "license": "MIT", + "authors": [ + { + "name": "David Desberg", + "email": "david@daviddesberg.com" + }, + { + "name": "Pieter Hordijk", + "email": "info@pieterhordijk.com" + }, + { + "name": "Elliot Chance", + "email": "elliotchance@gmail.com" + } + ], + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "symfony/http-foundation": "~2.1", + "predis/predis": "0.8.*@dev", + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "2.*" + }, + "suggest": { + "symfony/http-foundation": "Allows using the Symfony Session storage backend.", + "predis/predis": "Allows using the Redis storage backend.", + "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client." + }, + "autoload": { + "psr-0": { + "OAuth": "src", + "OAuth\\Unit": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + } +} diff --git a/vendor/lusitanian/oauth/composer.lock b/vendor/lusitanian/oauth/composer.lock new file mode 100644 index 0000000..42ceafe --- /dev/null +++ b/vendor/lusitanian/oauth/composer.lock @@ -0,0 +1,610 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "734ee27aca2b4b8a33857520f518ef0c", + "packages": [], + "packages-dev": [ + { + "name": "phpunit/php-code-coverage", + "version": "1.2.18", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": ">=1.3.0@stable", + "phpunit/php-text-template": ">=1.2.0@stable", + "phpunit/php-token-stream": ">=1.1.3,<1.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*@dev" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.0.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2014-09-02 10:13:14" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2015-06-21 08:01:12" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32", + "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "PHP/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2014-03-03 05:10:30" + }, + { + "name": "phpunit/phpunit", + "version": "3.7.38", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6", + "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpunit/php-code-coverage": "~1.2", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.1", + "phpunit/php-timer": "~1.0", + "phpunit/phpunit-mock-objects": "~1.2", + "symfony/yaml": "~2.0" + }, + "require-dev": { + "pear-pear.php.net/pear": "1.9.4" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "composer/bin/phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.7.x-dev" + } + }, + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "", + "../../symfony/yaml/" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "http://www.phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2014-10-17 09:04:17" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-text-template": ">=1.1.1@stable" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "autoload": { + "classmap": [ + "PHPUnit/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "" + ], + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2013-01-13 10:24:48" + }, + { + "name": "predis/predis", + "version": "0.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/nrk/predis.git", + "reference": "192dfd61e54c3d32c9526bba03365fff818e17e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nrk/predis/zipball/192dfd61e54c3d32c9526bba03365fff818e17e4", + "reference": "192dfd61e54c3d32c9526bba03365fff818e17e4", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Allows access to Webdis when paired with phpiredis", + "ext-phpiredis": "Allows faster serialization and deserialization of the Redis protocol" + }, + "type": "library", + "autoload": { + "psr-0": { + "Predis": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniele Alessandri", + "email": "suppakilla@gmail.com", + "homepage": "http://clorophilla.net" + } + ], + "description": "Flexible and feature-complete PHP client library for Redis", + "homepage": "http://github.com/nrk/predis", + "keywords": [ + "nosql", + "predis", + "redis" + ], + "time": "2015-07-07 17:11:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "2.3.4", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "11a2545c44a5915f883e2e5ec12e14ed345e3ab2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/11a2545c44a5915f883e2e5ec12e14ed345e3ab2", + "reference": "11a2545c44a5915f883e2e5ec12e14ed345e3ab2", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.1.2" + }, + "bin": [ + "scripts/phpcs", + "scripts/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "CodeSniffer.php", + "CodeSniffer/CLI.php", + "CodeSniffer/Exception.php", + "CodeSniffer/File.php", + "CodeSniffer/Fixer.php", + "CodeSniffer/Report.php", + "CodeSniffer/Reporting.php", + "CodeSniffer/Sniff.php", + "CodeSniffer/Tokens.php", + "CodeSniffer/Reports/", + "CodeSniffer/Tokenizers/", + "CodeSniffer/DocGenerators/", + "CodeSniffer/Standards/AbstractPatternSniff.php", + "CodeSniffer/Standards/AbstractScopeSniff.php", + "CodeSniffer/Standards/AbstractVariableSniff.php", + "CodeSniffer/Standards/IncorrectPatternException.php", + "CodeSniffer/Standards/Generic/Sniffs/", + "CodeSniffer/Standards/MySource/Sniffs/", + "CodeSniffer/Standards/PEAR/Sniffs/", + "CodeSniffer/Standards/PSR1/Sniffs/", + "CodeSniffer/Standards/PSR2/Sniffs/", + "CodeSniffer/Standards/Squiz/Sniffs/", + "CodeSniffer/Standards/Zend/Sniffs/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2015-09-09 00:18:50" + }, + { + "name": "symfony/http-foundation", + "version": "v2.7.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpFoundation.git", + "reference": "7253c2041652353e71560bbd300d6256d170ddaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/7253c2041652353e71560bbd300d6256d170ddaf", + "reference": "7253c2041652353e71560bbd300d6256d170ddaf", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/expression-language": "~2.4", + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2015-08-27 06:45:45" + }, + { + "name": "symfony/yaml", + "version": "v2.7.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "2dc7b06c065df96cc686c66da2705e5e18aef661" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/2dc7b06c065df96cc686c66da2705e5e18aef661", + "reference": "2dc7b06c065df96cc686c66da2705e5e18aef661", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2015-08-24 07:13:45" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "predis/predis": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.0" + }, + "platform-dev": [] +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/AutoLoader.php b/vendor/lusitanian/oauth/src/OAuth/Common/AutoLoader.php new file mode 100644 index 0000000..9fe7951 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/AutoLoader.php @@ -0,0 +1,81 @@ + + */ +class AutoLoader +{ + /** + * @var string The namespace prefix for this instance. + */ + protected $namespace = ''; + + /** + * @var string The filesystem prefix to use for this instance + */ + protected $path = ''; + + /** + * Build the instance of the autoloader + * + * @param string $namespace The prefixed namespace this instance will load + * @param string $path The filesystem path to the root of the namespace + */ + public function __construct($namespace, $path) + { + $this->namespace = ltrim($namespace, '\\'); + $this->path = rtrim($path, '/\\') . DIRECTORY_SEPARATOR; + } + + /** + * Try to load a class + * + * @param string $class The class name to load + * + * @return boolean If the loading was successful + */ + public function load($class) + { + $class = ltrim($class, '\\'); + + if (strpos($class, $this->namespace) === 0) { + $nsparts = explode('\\', $class); + $class = array_pop($nsparts); + $nsparts[] = ''; + $path = $this->path . implode(DIRECTORY_SEPARATOR, $nsparts); + $path .= str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; + + if (file_exists($path)) { + require $path; + + return true; + } + } + + return false; + } + + /** + * Register the autoloader to PHP + * + * @return boolean The status of the registration + */ + public function register() + { + return spl_autoload_register(array($this, 'load')); + } + + /** + * Unregister the autoloader to PHP + * + * @return boolean The status of the unregistration + */ + public function unregister() + { + return spl_autoload_unregister(array($this, 'load')); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Consumer/Credentials.php b/vendor/lusitanian/oauth/src/OAuth/Common/Consumer/Credentials.php new file mode 100644 index 0000000..8e98e9f --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Consumer/Credentials.php @@ -0,0 +1,60 @@ +consumerId = $consumerId; + $this->consumerSecret = $consumerSecret; + $this->callbackUrl = $callbackUrl; + } + + /** + * @return string + */ + public function getCallbackUrl() + { + return $this->callbackUrl; + } + + /** + * @return string + */ + public function getConsumerId() + { + return $this->consumerId; + } + + /** + * @return string + */ + public function getConsumerSecret() + { + return $this->consumerSecret; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Consumer/CredentialsInterface.php b/vendor/lusitanian/oauth/src/OAuth/Common/Consumer/CredentialsInterface.php new file mode 100644 index 0000000..a33e54e --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Consumer/CredentialsInterface.php @@ -0,0 +1,24 @@ +userAgent = $userAgent; + } + + /** + * @param int $redirects Maximum redirects for client + * + * @return ClientInterface + */ + public function setMaxRedirects($redirects) + { + $this->maxRedirects = $redirects; + + return $this; + } + + /** + * @param int $timeout Request timeout time for client in seconds + * + * @return ClientInterface + */ + public function setTimeout($timeout) + { + $this->timeout = $timeout; + + return $this; + } + + /** + * @param array $headers + */ + public function normalizeHeaders(&$headers) + { + // Normalize headers + array_walk( + $headers, + function (&$val, &$key) { + $key = ucfirst(strtolower($key)); + $val = ucfirst(strtolower($key)) . ': ' . $val; + } + ); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Http/Client/ClientInterface.php b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Client/ClientInterface.php new file mode 100644 index 0000000..f9c2022 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Client/ClientInterface.php @@ -0,0 +1,32 @@ + value` pairs) to be passed to `curl_setopt` + * + * @var array + */ + private $parameters = array(); + + /** + * Additional `curl_setopt` parameters + * + * @param array $parameters + */ + public function setCurlParameters(array $parameters) + { + $this->parameters = $parameters; + } + + /** + * @param bool $force + * + * @return CurlClient + */ + public function setForceSSL3($force) + { + $this->forceSSL3 = $force; + + return $this; + } + + /** + * Any implementing HTTP providers should send a request to the provided endpoint with the parameters. + * They should return, in string form, the response body and throw an exception on error. + * + * @param UriInterface $endpoint + * @param mixed $requestBody + * @param array $extraHeaders + * @param string $method + * + * @return string + * + * @throws TokenResponseException + * @throws \InvalidArgumentException + */ + public function retrieveResponse( + UriInterface $endpoint, + $requestBody, + array $extraHeaders = array(), + $method = 'POST' + ) { + // Normalize method name + $method = strtoupper($method); + + $this->normalizeHeaders($extraHeaders); + + if ($method === 'GET' && !empty($requestBody)) { + throw new \InvalidArgumentException('No body expected for "GET" request.'); + } + + if (!isset($extraHeaders['Content-Type']) && $method === 'POST' && is_array($requestBody)) { + $extraHeaders['Content-Type'] = 'Content-Type: application/x-www-form-urlencoded'; + } + + $extraHeaders['Host'] = 'Host: '.$endpoint->getHost(); + $extraHeaders['Connection'] = 'Connection: close'; + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $endpoint->getAbsoluteUri()); + + if ($method === 'POST' || $method === 'PUT') { + if ($requestBody && is_array($requestBody)) { + $requestBody = http_build_query($requestBody, '', '&'); + } + + if ($method === 'PUT') { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); + } else { + curl_setopt($ch, CURLOPT_POST, true); + } + + curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody); + } else { + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + } + + if ($this->maxRedirects > 0) { + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_MAXREDIRS, $this->maxRedirects); + } + + curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, false); + curl_setopt($ch, CURLOPT_HTTPHEADER, $extraHeaders); + curl_setopt($ch, CURLOPT_USERAGENT, $this->userAgent); + + foreach ($this->parameters as $key => $value) { + curl_setopt($ch, $key, $value); + } + + if ($this->forceSSL3) { + curl_setopt($ch, CURLOPT_SSLVERSION, 3); + } + + $response = curl_exec($ch); + $responseCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + if (false === $response) { + $errNo = curl_errno($ch); + $errStr = curl_error($ch); + curl_close($ch); + if (empty($errStr)) { + throw new TokenResponseException('Failed to request resource.', $responseCode); + } + throw new TokenResponseException('cURL Error # '.$errNo.': '.$errStr, $responseCode); + } + + curl_close($ch); + + return $response; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Http/Client/StreamClient.php b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Client/StreamClient.php new file mode 100644 index 0000000..d81fee8 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Client/StreamClient.php @@ -0,0 +1,95 @@ +normalizeHeaders($extraHeaders); + + if ($method === 'GET' && !empty($requestBody)) { + throw new \InvalidArgumentException('No body expected for "GET" request.'); + } + + if (!isset($extraHeaders['Content-Type']) && $method === 'POST' && is_array($requestBody)) { + $extraHeaders['Content-Type'] = 'Content-Type: application/x-www-form-urlencoded'; + } + + $host = 'Host: '.$endpoint->getHost(); + // Append port to Host if it has been specified + if ($endpoint->hasExplicitPortSpecified()) { + $host .= ':'.$endpoint->getPort(); + } + + $extraHeaders['Host'] = $host; + $extraHeaders['Connection'] = 'Connection: close'; + + if (is_array($requestBody)) { + $requestBody = http_build_query($requestBody, '', '&'); + } + $extraHeaders['Content-length'] = 'Content-length: '.strlen($requestBody); + + $context = $this->generateStreamContext($requestBody, $extraHeaders, $method); + + $level = error_reporting(0); + $response = file_get_contents($endpoint->getAbsoluteUri(), false, $context); + error_reporting($level); + if (false === $response) { + $lastError = error_get_last(); + if (is_null($lastError)) { + throw new TokenResponseException( + 'Failed to request resource. HTTP Code: ' . + ((isset($http_response_header[0]))?$http_response_header[0]:'No response') + ); + } + throw new TokenResponseException($lastError['message']); + } + + return $response; + } + + private function generateStreamContext($body, $headers, $method) + { + return stream_context_create( + array( + 'http' => array( + 'method' => $method, + 'header' => implode("\r\n", array_values($headers)), + 'content' => $body, + 'protocol_version' => '1.1', + 'user_agent' => $this->userAgent, + 'max_redirects' => $this->maxRedirects, + 'timeout' => $this->timeout + ), + ) + ); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Http/Exception/TokenResponseException.php b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Exception/TokenResponseException.php new file mode 100644 index 0000000..c519a22 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Exception/TokenResponseException.php @@ -0,0 +1,12 @@ +parseUri($uri); + } + } + + /** + * @param string $uri + * + * @throws \InvalidArgumentException + */ + protected function parseUri($uri) + { + if (false === ($uriParts = parse_url($uri))) { + // congratulations if you've managed to get parse_url to fail, + // it seems to always return some semblance of a parsed url no matter what + throw new InvalidArgumentException("Invalid URI: $uri"); + } + + if (!isset($uriParts['scheme'])) { + throw new InvalidArgumentException('Invalid URI: http|https scheme required'); + } + + $this->scheme = $uriParts['scheme']; + $this->host = $uriParts['host']; + + if (isset($uriParts['port'])) { + $this->port = $uriParts['port']; + $this->explicitPortSpecified = true; + } else { + $this->port = strcmp('https', $uriParts['scheme']) ? 80 : 443; + $this->explicitPortSpecified = false; + } + + if (isset($uriParts['path'])) { + $this->path = $uriParts['path']; + if ('/' === $uriParts['path']) { + $this->explicitTrailingHostSlash = true; + } + } else { + $this->path = '/'; + } + + $this->query = isset($uriParts['query']) ? $uriParts['query'] : ''; + $this->fragment = isset($uriParts['fragment']) ? $uriParts['fragment'] : ''; + + $userInfo = ''; + if (!empty($uriParts['user'])) { + $userInfo .= $uriParts['user']; + } + if ($userInfo && !empty($uriParts['pass'])) { + $userInfo .= ':' . $uriParts['pass']; + } + + $this->setUserInfo($userInfo); + } + + /** + * @param string $rawUserInfo + * + * @return string + */ + protected function protectUserInfo($rawUserInfo) + { + $colonPos = strpos($rawUserInfo, ':'); + + // rfc3986-3.2.1 | http://tools.ietf.org/html/rfc3986#section-3.2 + // "Applications should not render as clear text any data + // after the first colon (":") character found within a userinfo + // subcomponent unless the data after the colon is the empty string + // (indicating no password)" + if ($colonPos !== false && strlen($rawUserInfo)-1 > $colonPos) { + return substr($rawUserInfo, 0, $colonPos) . ':********'; + } else { + return $rawUserInfo; + } + } + + /** + * @return string + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * @return string + */ + public function getUserInfo() + { + return $this->userInfo; + } + + /** + * @return string + */ + public function getRawUserInfo() + { + return $this->rawUserInfo; + } + + /** + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return string + */ + public function getQuery() + { + return $this->query; + } + + /** + * @return string + */ + public function getFragment() + { + return $this->fragment; + } + + /** + * Uses protected user info by default as per rfc3986-3.2.1 + * Uri::getRawAuthority() is available if plain-text password information is desirable. + * + * @return string + */ + public function getAuthority() + { + $authority = $this->userInfo ? $this->userInfo.'@' : ''; + $authority .= $this->host; + + if ($this->explicitPortSpecified) { + $authority .= ":{$this->port}"; + } + + return $authority; + } + + /** + * @return string + */ + public function getRawAuthority() + { + $authority = $this->rawUserInfo ? $this->rawUserInfo.'@' : ''; + $authority .= $this->host; + + if ($this->explicitPortSpecified) { + $authority .= ":{$this->port}"; + } + + return $authority; + } + + /** + * @return string + */ + public function getAbsoluteUri() + { + $uri = $this->scheme . '://' . $this->getRawAuthority(); + + if ('/' === $this->path) { + $uri .= $this->explicitTrailingHostSlash ? '/' : ''; + } else { + $uri .= $this->path; + } + + if (!empty($this->query)) { + $uri .= "?{$this->query}"; + } + + if (!empty($this->fragment)) { + $uri .= "#{$this->fragment}"; + } + + return $uri; + } + + /** + * @return string + */ + public function getRelativeUri() + { + $uri = ''; + + if ('/' === $this->path) { + $uri .= $this->explicitTrailingHostSlash ? '/' : ''; + } else { + $uri .= $this->path; + } + + return $uri; + } + + /** + * Uses protected user info by default as per rfc3986-3.2.1 + * Uri::getAbsoluteUri() is available if plain-text password information is desirable. + * + * @return string + */ + public function __toString() + { + $uri = $this->scheme . '://' . $this->getAuthority(); + + if ('/' === $this->path) { + $uri .= $this->explicitTrailingHostSlash ? '/' : ''; + } else { + $uri .= $this->path; + } + + if (!empty($this->query)) { + $uri .= "?{$this->query}"; + } + + if (!empty($this->fragment)) { + $uri .= "#{$this->fragment}"; + } + + return $uri; + } + + /** + * @param $path + */ + public function setPath($path) + { + if (empty($path)) { + $this->path = '/'; + $this->explicitTrailingHostSlash = false; + } else { + $this->path = $path; + if ('/' === $this->path) { + $this->explicitTrailingHostSlash = true; + } + } + } + + /** + * @param string $query + */ + public function setQuery($query) + { + $this->query = $query; + } + + /** + * @param string $var + * @param string $val + */ + public function addToQuery($var, $val) + { + if (strlen($this->query) > 0) { + $this->query .= '&'; + } + $this->query .= http_build_query(array($var => $val), '', '&'); + } + + /** + * @param string $fragment + */ + public function setFragment($fragment) + { + $this->fragment = $fragment; + } + + /** + * @param string $scheme + */ + public function setScheme($scheme) + { + $this->scheme = $scheme; + } + + + /** + * @param string $userInfo + */ + public function setUserInfo($userInfo) + { + $this->userInfo = $userInfo ? $this->protectUserInfo($userInfo) : ''; + $this->rawUserInfo = $userInfo; + } + + + /** + * @param int $port + */ + public function setPort($port) + { + $this->port = intval($port); + + if (('https' === $this->scheme && $this->port === 443) || ('http' === $this->scheme && $this->port === 80)) { + $this->explicitPortSpecified = false; + } else { + $this->explicitPortSpecified = true; + } + } + + /** + * @param string $host + */ + public function setHost($host) + { + $this->host = $host; + } + + /** + * @return bool + */ + public function hasExplicitTrailingHostSlash() + { + return $this->explicitTrailingHostSlash; + } + + /** + * @return bool + */ + public function hasExplicitPortSpecified() + { + return $this->explicitPortSpecified; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactory.php b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactory.php new file mode 100644 index 0000000..127aa20 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactory.php @@ -0,0 +1,168 @@ +attemptProxyStyleParse($_server)) { + return $uri; + } + + $scheme = $this->detectScheme($_server); + $host = $this->detectHost($_server); + $port = $this->detectPort($_server); + $path = $this->detectPath($_server); + $query = $this->detectQuery($_server); + + return $this->createFromParts($scheme, '', $host, $port, $path, $query); + } + + /** + * @param string $absoluteUri + * + * @return UriInterface + */ + public function createFromAbsolute($absoluteUri) + { + return new Uri($absoluteUri); + } + + /** + * Factory method to build a URI from parts + * + * @param string $scheme + * @param string $userInfo + * @param string $host + * @param string $port + * @param string $path + * @param string $query + * @param string $fragment + * + * @return UriInterface + */ + public function createFromParts($scheme, $userInfo, $host, $port, $path = '', $query = '', $fragment = '') + { + $uri = new Uri(); + $uri->setScheme($scheme); + $uri->setUserInfo($userInfo); + $uri->setHost($host); + $uri->setPort($port); + $uri->setPath($path); + $uri->setQuery($query); + $uri->setFragment($fragment); + + return $uri; + } + + /** + * @param array $_server + * + * @return UriInterface|null + */ + private function attemptProxyStyleParse($_server) + { + // If the raw HTTP request message arrives with a proxy-style absolute URI in the + // initial request line, the absolute URI is stored in $_SERVER['REQUEST_URI'] and + // we only need to parse that. + if (isset($_server['REQUEST_URI']) && parse_url($_server['REQUEST_URI'], PHP_URL_SCHEME)) { + return new Uri($_server['REQUEST_URI']); + } + + return null; + } + + /** + * @param array $_server + * + * @return string + * + * @throws RuntimeException + */ + private function detectPath($_server) + { + if (isset($_server['REQUEST_URI'])) { + $uri = $_server['REQUEST_URI']; + } elseif (isset($_server['REDIRECT_URL'])) { + $uri = $_server['REDIRECT_URL']; + } else { + throw new RuntimeException('Could not detect URI path from superglobal'); + } + + $queryStr = strpos($uri, '?'); + if ($queryStr !== false) { + $uri = substr($uri, 0, $queryStr); + } + + return $uri; + } + + /** + * @param array $_server + * + * @return string + */ + private function detectHost(array $_server) + { + $host = isset($_server['HTTP_HOST']) ? $_server['HTTP_HOST'] : ''; + + if (strstr($host, ':')) { + $host = parse_url($host, PHP_URL_HOST); + } + + return $host; + } + + /** + * @param array $_server + * + * @return string + */ + private function detectPort(array $_server) + { + return isset($_server['SERVER_PORT']) ? $_server['SERVER_PORT'] : 80; + } + + /** + * @param array $_server + * + * @return string + */ + private function detectQuery(array $_server) + { + return isset($_server['QUERY_STRING']) ? $_server['QUERY_STRING'] : ''; + } + + /** + * Determine URI scheme component from superglobal array + * + * When using ISAPI with IIS, the value will be "off" if the request was + * not made through the HTTPS protocol. As a result, we filter the + * value to a bool. + * + * @param array $_server A super-global $_SERVER array + * + * @return string Returns http or https depending on the URI scheme + */ + private function detectScheme(array $_server) + { + if (isset($_server['HTTPS']) && filter_var($_server['HTTPS'], FILTER_VALIDATE_BOOLEAN)) { + return 'https'; + } else { + return 'http'; + } + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactoryInterface.php b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactoryInterface.php new file mode 100644 index 0000000..2b157d8 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Http/Uri/UriFactoryInterface.php @@ -0,0 +1,42 @@ +credentials = $credentials; + $this->httpClient = $httpClient; + $this->storage = $storage; + } + + /** + * @param UriInterface|string $path + * @param UriInterface $baseApiUri + * + * @return UriInterface + * + * @throws Exception + */ + protected function determineRequestUriFromPath($path, UriInterface $baseApiUri = null) + { + if ($path instanceof UriInterface) { + $uri = $path; + } elseif (stripos($path, 'http://') === 0 || stripos($path, 'https://') === 0) { + $uri = new Uri($path); + } else { + if (null === $baseApiUri) { + throw new Exception( + 'An absolute URI must be passed to ServiceInterface::request as no baseApiUri is set.' + ); + } + + $uri = clone $baseApiUri; + if (false !== strpos($path, '?')) { + $parts = explode('?', $path, 2); + $path = $parts[0]; + $query = $parts[1]; + $uri->setQuery($query); + } + + if ($path[0] === '/') { + $path = substr($path, 1); + } + + $uri->setPath($uri->getPath() . $path); + } + + return $uri; + } + + /** + * Accessor to the storage adapter to be able to retrieve tokens + * + * @return TokenStorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * @return string + */ + public function service() + { + // get class name without backslashes + $classname = get_class($this); + + return preg_replace('/^.*\\\\/', '', $classname); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Service/ServiceInterface.php b/vendor/lusitanian/oauth/src/OAuth/Common/Service/ServiceInterface.php new file mode 100644 index 0000000..5856a03 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Service/ServiceInterface.php @@ -0,0 +1,49 @@ +tokens = array(); + $this->states = array(); + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if ($this->hasAccessToken($service)) { + return $this->tokens[$service]; + } + + throw new TokenNotFoundException('Token not stored'); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + $this->tokens[$service] = $token; + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + return isset($this->tokens[$service]) && $this->tokens[$service] instanceof TokenInterface; + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + if (array_key_exists($service, $this->tokens)) { + unset($this->tokens[$service]); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + $this->tokens = array(); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if ($this->hasAuthorizationState($service)) { + return $this->states[$service]; + } + + throw new AuthorizationStateNotFoundException('State not stored'); + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + $this->states[$service] = $state; + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + return isset($this->states[$service]) && null !== $this->states[$service]; + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + if (array_key_exists($service, $this->states)) { + unset($this->states[$service]); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + $this->states = array(); + + // allow chaining + return $this; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Storage/Redis.php b/vendor/lusitanian/oauth/src/OAuth/Common/Storage/Redis.php new file mode 100644 index 0000000..5d3d9ae --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Storage/Redis.php @@ -0,0 +1,230 @@ +redis = $redis; + $this->key = $key; + $this->stateKey = $stateKey; + $this->cachedTokens = array(); + $this->cachedStates = array(); + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if (!$this->hasAccessToken($service)) { + throw new TokenNotFoundException('Token not found in redis'); + } + + if (isset($this->cachedTokens[$service])) { + return $this->cachedTokens[$service]; + } + + $val = $this->redis->hget($this->key, $service); + + return $this->cachedTokens[$service] = unserialize($val); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + // (over)write the token + $this->redis->hset($this->key, $service, serialize($token)); + $this->cachedTokens[$service] = $token; + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + if (isset($this->cachedTokens[$service]) + && $this->cachedTokens[$service] instanceof TokenInterface + ) { + return true; + } + + return $this->redis->hexists($this->key, $service); + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + $this->redis->hdel($this->key, $service); + unset($this->cachedTokens[$service]); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + // memory + $this->cachedTokens = array(); + + // redis + $keys = $this->redis->hkeys($this->key); + $me = $this; // 5.3 compat + + // pipeline for performance + $this->redis->pipeline( + function ($pipe) use ($keys, $me) { + foreach ($keys as $k) { + $pipe->hdel($me->getKey(), $k); + } + } + ); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if (!$this->hasAuthorizationState($service)) { + throw new AuthorizationStateNotFoundException('State not found in redis'); + } + + if (isset($this->cachedStates[$service])) { + return $this->cachedStates[$service]; + } + + $val = $this->redis->hget($this->stateKey, $service); + + return $this->cachedStates[$service] = $val; + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + // (over)write the token + $this->redis->hset($this->stateKey, $service, $state); + $this->cachedStates[$service] = $state; + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + if (isset($this->cachedStates[$service]) + && null !== $this->cachedStates[$service] + ) { + return true; + } + + return $this->redis->hexists($this->stateKey, $service); + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + $this->redis->hdel($this->stateKey, $service); + unset($this->cachedStates[$service]); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + // memory + $this->cachedStates = array(); + + // redis + $keys = $this->redis->hkeys($this->stateKey); + $me = $this; // 5.3 compat + + // pipeline for performance + $this->redis->pipeline( + function ($pipe) use ($keys, $me) { + foreach ($keys as $k) { + $pipe->hdel($me->getKey(), $k); + } + } + ); + + // allow chaining + return $this; + } + + /** + * @return Predis $redis + */ + public function getRedis() + { + return $this->redis; + } + + /** + * @return string $key + */ + public function getKey() + { + return $this->key; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Storage/Session.php b/vendor/lusitanian/oauth/src/OAuth/Common/Storage/Session.php new file mode 100644 index 0000000..dd9aba7 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Storage/Session.php @@ -0,0 +1,204 @@ +sessionHasStarted()) { + session_start(); + } + + $this->startSession = $startSession; + $this->sessionVariableName = $sessionVariableName; + $this->stateVariableName = $stateVariableName; + if (!isset($_SESSION[$sessionVariableName])) { + $_SESSION[$sessionVariableName] = array(); + } + if (!isset($_SESSION[$stateVariableName])) { + $_SESSION[$stateVariableName] = array(); + } + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if ($this->hasAccessToken($service)) { + return unserialize($_SESSION[$this->sessionVariableName][$service]); + } + + throw new TokenNotFoundException('Token not found in session, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + $serializedToken = serialize($token); + + if (isset($_SESSION[$this->sessionVariableName]) + && is_array($_SESSION[$this->sessionVariableName]) + ) { + $_SESSION[$this->sessionVariableName][$service] = $serializedToken; + } else { + $_SESSION[$this->sessionVariableName] = array( + $service => $serializedToken, + ); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + return isset($_SESSION[$this->sessionVariableName], $_SESSION[$this->sessionVariableName][$service]); + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + if (array_key_exists($service, $_SESSION[$this->sessionVariableName])) { + unset($_SESSION[$this->sessionVariableName][$service]); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + unset($_SESSION[$this->sessionVariableName]); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + if (isset($_SESSION[$this->stateVariableName]) + && is_array($_SESSION[$this->stateVariableName]) + ) { + $_SESSION[$this->stateVariableName][$service] = $state; + } else { + $_SESSION[$this->stateVariableName] = array( + $service => $state, + ); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + return isset($_SESSION[$this->stateVariableName], $_SESSION[$this->stateVariableName][$service]); + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if ($this->hasAuthorizationState($service)) { + return $_SESSION[$this->stateVariableName][$service]; + } + + throw new AuthorizationStateNotFoundException('State not found in session, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + if (array_key_exists($service, $_SESSION[$this->stateVariableName])) { + unset($_SESSION[$this->stateVariableName][$service]); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + unset($_SESSION[$this->stateVariableName]); + + // allow chaining + return $this; + } + + public function __destruct() + { + if ($this->startSession) { + session_write_close(); + } + } + + /** + * Determine if the session has started. + * @url http://stackoverflow.com/a/18542272/1470961 + * @return bool + */ + protected function sessionHasStarted() + { + // For more modern PHP versions we use a more reliable method. + if (version_compare(PHP_VERSION, '5.4.0') >= 0) { + return session_status() != PHP_SESSION_NONE; + } + + // Below PHP 5.4 we should test for the current session ID. + return session_id() !== ''; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Storage/SymfonySession.php b/vendor/lusitanian/oauth/src/OAuth/Common/Storage/SymfonySession.php new file mode 100644 index 0000000..6c5fbf6 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Storage/SymfonySession.php @@ -0,0 +1,200 @@ +session = $session; + $this->sessionVariableName = $sessionVariableName; + $this->stateVariableName = $stateVariableName; + } + + /** + * {@inheritDoc} + */ + public function retrieveAccessToken($service) + { + if ($this->hasAccessToken($service)) { + // get from session + $tokens = $this->session->get($this->sessionVariableName); + + // one item + return $tokens[$service]; + } + + throw new TokenNotFoundException('Token not found in session, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function storeAccessToken($service, TokenInterface $token) + { + // get previously saved tokens + $tokens = $this->session->get($this->sessionVariableName); + + if (!is_array($tokens)) { + $tokens = array(); + } + + $tokens[$service] = $token; + + // save + $this->session->set($this->sessionVariableName, $tokens); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAccessToken($service) + { + // get from session + $tokens = $this->session->get($this->sessionVariableName); + + return is_array($tokens) + && isset($tokens[$service]) + && $tokens[$service] instanceof TokenInterface; + } + + /** + * {@inheritDoc} + */ + public function clearToken($service) + { + // get previously saved tokens + $tokens = $this->session->get($this->sessionVariableName); + + if (is_array($tokens) && array_key_exists($service, $tokens)) { + unset($tokens[$service]); + + // Replace the stored tokens array + $this->session->set($this->sessionVariableName, $tokens); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllTokens() + { + $this->session->remove($this->sessionVariableName); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function retrieveAuthorizationState($service) + { + if ($this->hasAuthorizationState($service)) { + // get from session + $states = $this->session->get($this->stateVariableName); + + // one item + return $states[$service]; + } + + throw new AuthorizationStateNotFoundException('State not found in session, are you sure you stored it?'); + } + + /** + * {@inheritDoc} + */ + public function storeAuthorizationState($service, $state) + { + // get previously saved tokens + $states = $this->session->get($this->stateVariableName); + + if (!is_array($states)) { + $states = array(); + } + + $states[$service] = $state; + + // save + $this->session->set($this->stateVariableName, $states); + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function hasAuthorizationState($service) + { + // get from session + $states = $this->session->get($this->stateVariableName); + + return is_array($states) + && isset($states[$service]) + && null !== $states[$service]; + } + + /** + * {@inheritDoc} + */ + public function clearAuthorizationState($service) + { + // get previously saved tokens + $states = $this->session->get($this->stateVariableName); + + if (is_array($states) && array_key_exists($service, $states)) { + unset($states[$service]); + + // Replace the stored tokens array + $this->session->set($this->stateVariableName, $states); + } + + // allow chaining + return $this; + } + + /** + * {@inheritDoc} + */ + public function clearAllAuthorizationStates() + { + $this->session->remove($this->stateVariableName); + + // allow chaining + return $this; + } + + /** + * @return Session + */ + public function getSession() + { + return $this->session; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Storage/TokenStorageInterface.php b/vendor/lusitanian/oauth/src/OAuth/Common/Storage/TokenStorageInterface.php new file mode 100644 index 0000000..46552ce --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Storage/TokenStorageInterface.php @@ -0,0 +1,98 @@ +accessToken = $accessToken; + $this->refreshToken = $refreshToken; + $this->setLifetime($lifetime); + $this->extraParams = $extraParams; + } + + /** + * @return string + */ + public function getAccessToken() + { + return $this->accessToken; + } + + /** + * @return string + */ + public function getRefreshToken() + { + return $this->refreshToken; + } + + /** + * @return int + */ + public function getEndOfLife() + { + return $this->endOfLife; + } + + /** + * @param array $extraParams + */ + public function setExtraParams(array $extraParams) + { + $this->extraParams = $extraParams; + } + + /** + * @return array + */ + public function getExtraParams() + { + return $this->extraParams; + } + + /** + * @param string $accessToken + */ + public function setAccessToken($accessToken) + { + $this->accessToken = $accessToken; + } + + /** + * @param int $endOfLife + */ + public function setEndOfLife($endOfLife) + { + $this->endOfLife = $endOfLife; + } + + /** + * @param int $lifetime + */ + public function setLifetime($lifetime) + { + if (0 === $lifetime || static::EOL_NEVER_EXPIRES === $lifetime) { + $this->endOfLife = static::EOL_NEVER_EXPIRES; + } elseif (null !== $lifetime) { + $this->endOfLife = intval($lifetime) + time(); + } else { + $this->endOfLife = static::EOL_UNKNOWN; + } + } + + /** + * @param string $refreshToken + */ + public function setRefreshToken($refreshToken) + { + $this->refreshToken = $refreshToken; + } + + public function isExpired() + { + return ($this->getEndOfLife() !== TokenInterface::EOL_NEVER_EXPIRES + && $this->getEndOfLife() !== TokenInterface::EOL_UNKNOWN + && time() > $this->getEndOfLife()); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/Common/Token/Exception/ExpiredTokenException.php b/vendor/lusitanian/oauth/src/OAuth/Common/Token/Exception/ExpiredTokenException.php new file mode 100644 index 0000000..26ad6cc --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/Common/Token/Exception/ExpiredTokenException.php @@ -0,0 +1,12 @@ +signature = $signature; + $this->baseApiUri = $baseApiUri; + + $this->signature->setHashingAlgorithm($this->getSignatureMethod()); + } + + /** + * {@inheritDoc} + */ + public function requestRequestToken() + { + $authorizationHeader = array('Authorization' => $this->buildAuthorizationHeaderForTokenRequest()); + $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders()); + + $responseBody = $this->httpClient->retrieveResponse($this->getRequestTokenEndpoint(), array(), $headers); + + $token = $this->parseRequestTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($additionalParameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritDoc} + */ + public function requestAccessToken($token, $verifier, $tokenSecret = null) + { + if (is_null($tokenSecret)) { + $storedRequestToken = $this->storage->retrieveAccessToken($this->service()); + $tokenSecret = $storedRequestToken->getRequestTokenSecret(); + } + $this->signature->setTokenSecret($tokenSecret); + + $bodyParams = array( + 'oauth_verifier' => $verifier, + ); + + $authorizationHeader = array( + 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest( + 'POST', + $this->getAccessTokenEndpoint(), + $this->storage->retrieveAccessToken($this->service()), + $bodyParams + ) + ); + + $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders()); + + $responseBody = $this->httpClient->retrieveResponse($this->getAccessTokenEndpoint(), $bodyParams, $headers); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * Refreshes an OAuth1 access token + * @param TokenInterface $token + * @return TokenInterface $token + */ + public function refreshAccessToken(TokenInterface $token) + { + } + + /** + * Sends an authenticated API request to the path provided. + * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used. + * + * @param string|UriInterface $path + * @param string $method HTTP method + * @param array $body Request body if applicable (key/value pairs) + * @param array $extraHeaders Extra headers if applicable. + * These will override service-specific any defaults. + * + * @return string + */ + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri); + + /** @var $token StdOAuth1Token */ + $token = $this->storage->retrieveAccessToken($this->service()); + $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders); + $authorizationHeader = array( + 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $body) + ); + $headers = array_merge($authorizationHeader, $extraHeaders); + + return $this->httpClient->retrieveResponse($uri, $body, $headers, $method); + } + + /** + * Return any additional headers always needed for this service implementation's OAuth calls. + * + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array(); + } + + /** + * Return any additional headers always needed for this service implementation's API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array(); + } + + /** + * Builds the authorization header for getting an access or request token. + * + * @param array $extraParameters + * + * @return string + */ + protected function buildAuthorizationHeaderForTokenRequest(array $extraParameters = array()) + { + $parameters = $this->getBasicAuthorizationHeaderInfo(); + $parameters = array_merge($parameters, $extraParameters); + $parameters['oauth_signature'] = $this->signature->getSignature( + $this->getRequestTokenEndpoint(), + $parameters, + 'POST' + ); + + $authorizationHeader = 'OAuth '; + $delimiter = ''; + foreach ($parameters as $key => $value) { + $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"'; + + $delimiter = ', '; + } + + return $authorizationHeader; + } + + /** + * Builds the authorization header for an authenticated API request + * + * @param string $method + * @param UriInterface $uri The uri the request is headed + * @param TokenInterface $token + * @param array $bodyParams Request body if applicable (key/value pairs) + * + * @return string + */ + protected function buildAuthorizationHeaderForAPIRequest( + $method, + UriInterface $uri, + TokenInterface $token, + $bodyParams = null + ) { + $this->signature->setTokenSecret($token->getAccessTokenSecret()); + $authParameters = $this->getBasicAuthorizationHeaderInfo(); + if (isset($authParameters['oauth_callback'])) { + unset($authParameters['oauth_callback']); + } + + $authParameters = array_merge($authParameters, array('oauth_token' => $token->getAccessToken())); + + $signatureParams = (is_array($bodyParams)) ? array_merge($authParameters, $bodyParams) : $authParameters; + $authParameters['oauth_signature'] = $this->signature->getSignature($uri, $signatureParams, $method); + + if (is_array($bodyParams) && isset($bodyParams['oauth_session_handle'])) { + $authParameters['oauth_session_handle'] = $bodyParams['oauth_session_handle']; + unset($bodyParams['oauth_session_handle']); + } + + $authorizationHeader = 'OAuth '; + $delimiter = ''; + + foreach ($authParameters as $key => $value) { + $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"'; + $delimiter = ', '; + } + + return $authorizationHeader; + } + + /** + * Builds the authorization header array. + * + * @return array + */ + protected function getBasicAuthorizationHeaderInfo() + { + $dateTime = new \DateTime(); + $headerParameters = array( + 'oauth_callback' => $this->credentials->getCallbackUrl(), + 'oauth_consumer_key' => $this->credentials->getConsumerId(), + 'oauth_nonce' => $this->generateNonce(), + 'oauth_signature_method' => $this->getSignatureMethod(), + 'oauth_timestamp' => $dateTime->format('U'), + 'oauth_version' => $this->getVersion(), + ); + + return $headerParameters; + } + + /** + * Pseudo random string generator used to build a unique string to sign each request + * + * @param int $length + * + * @return string + */ + protected function generateNonce($length = 32) + { + $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; + + $nonce = ''; + $maxRand = strlen($characters)-1; + for ($i = 0; $i < $length; $i++) { + $nonce.= $characters[rand(0, $maxRand)]; + } + + return $nonce; + } + + /** + * @return string + */ + protected function getSignatureMethod() + { + return 'HMAC-SHA1'; + } + + /** + * This returns the version used in the authorization header of the requests + * + * @return string + */ + protected function getVersion() + { + return '1.0'; + } + + /** + * Parses the request token response and returns a TokenInterface. + * This is only needed to verify the `oauth_callback_confirmed` parameter. The actual + * parsing logic is contained in the access token parser. + * + * @abstract + * + * @param string $responseBody + * + * @return TokenInterface + * + * @throws TokenResponseException + */ + abstract protected function parseRequestTokenResponse($responseBody); + + /** + * Parses the access token response and returns a TokenInterface. + * + * @abstract + * + * @param string $responseBody + * + * @return TokenInterface + * + * @throws TokenResponseException + */ + abstract protected function parseAccessTokenResponse($responseBody); +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/BitBucket.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/BitBucket.php new file mode 100644 index 0000000..f6d8edf --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/BitBucket.php @@ -0,0 +1,96 @@ +baseApiUri = new Uri('https://bitbucket.org/api/1.0/'); + } + } + + /** + * {@inheritDoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://bitbucket.org/!api/1.0/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://bitbucket.org/!api/1.0/oauth/authenticate'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://bitbucket.org/!api/1.0/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Etsy.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Etsy.php new file mode 100644 index 0000000..30dc331 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Etsy.php @@ -0,0 +1,132 @@ +baseApiUri = new Uri('https://openapi.etsy.com/v2/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + $uri = new Uri($this->baseApiUri . 'oauth/request_token'); + $scopes = $this->getScopes(); + + if (count($scopes)) { + $uri->setQuery('scope=' . implode('%20', $scopes)); + } + + return $uri; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri . 'oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } + + /** + * Set the scopes for permissions + * @see https://www.etsy.com/developers/documentation/getting_started/oauth#section_permission_scopes + * @param array $scopes + * + * @return $this + */ + public function setScopes(array $scopes) + { + if (!is_array($scopes)) { + $scopes = array(); + } + + $this->scopes = $scopes; + return $this; + } + + /** + * Return the defined scopes + * @return array + */ + public function getScopes() + { + return $this->scopes; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/FitBit.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/FitBit.php new file mode 100644 index 0000000..78032d7 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/FitBit.php @@ -0,0 +1,96 @@ +baseApiUri = new Uri('https://api.fitbit.com/1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.fitbit.com/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.fitbit.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.fitbit.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/FiveHundredPx.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/FiveHundredPx.php new file mode 100644 index 0000000..ea7f9b3 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/FiveHundredPx.php @@ -0,0 +1,120 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developers.500px.com/ + */ + +namespace OAuth\OAuth1\Service; + +use OAuth\OAuth1\Signature\SignatureInterface; +use OAuth\OAuth1\Token\StdOAuth1Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Client\ClientInterface; + +/** + * 500px service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developers.500px.com/ + */ +class FiveHundredPx extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + SignatureInterface $signature, + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $signature, + $baseApiUri + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.500px.com/v1/'); + } + } + + /** + * {@inheritDoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.500px.com/v1/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.500px.com/v1/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.500px.com/v1/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) + || $data['oauth_callback_confirmed'] !== 'true' + ) { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Flickr.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Flickr.php new file mode 100644 index 0000000..7ceee7d --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Flickr.php @@ -0,0 +1,133 @@ +baseApiUri = new Uri('https://api.flickr.com/services/rest/'); + } + } + + public function getRequestTokenEndpoint() + { + return new Uri('https://www.flickr.com/services/oauth/request_token'); + } + + public function getAuthorizationEndpoint() + { + return new Uri('https://www.flickr.com/services/oauth/authorize'); + } + + public function getAccessTokenEndpoint() + { + return new Uri('https://www.flickr.com/services/oauth/access_token'); + } + + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] != 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + return $this->parseAccessTokenResponse($responseBody); + } + + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + if ($data === null || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } + + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $uri = $this->determineRequestUriFromPath('/', $this->baseApiUri); + $uri->addToQuery('method', $path); + + if (!empty($this->format)) { + $uri->addToQuery('format', $this->format); + + if ($this->format === 'json') { + $uri->addToQuery('nojsoncallback', 1); + } + } + + $token = $this->storage->retrieveAccessToken($this->service()); + $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders); + $authorizationHeader = array( + 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $body) + ); + $headers = array_merge($authorizationHeader, $extraHeaders); + + return $this->httpClient->retrieveResponse($uri, $body, $headers, $method); + } + + public function requestRest($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + return $this->request($path, $method, $body, $extraHeaders); + } + + public function requestXmlrpc($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $this->format = 'xmlrpc'; + + return $this->request($path, $method, $body, $extraHeaders); + } + + public function requestSoap($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $this->format = 'soap'; + + return $this->request($path, $method, $body, $extraHeaders); + } + + public function requestJson($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $this->format = 'json'; + + return $this->request($path, $method, $body, $extraHeaders); + } + + public function requestPhp($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $this->format = 'php_serial'; + + return $this->request($path, $method, $body, $extraHeaders); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/QuickBooks.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/QuickBooks.php new file mode 100644 index 0000000..0014ca8 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/QuickBooks.php @@ -0,0 +1,120 @@ +baseApiUri = new Uri('https://quickbooks.api.intuit.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://oauth.intuit.com/oauth/v1/get_request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://appcenter.intuit.com/Connect/Begin'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://oauth.intuit.com/oauth/v1/get_access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) + || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + $message = 'Error in retrieving token: "' . $data['error'] . '"'; + throw new TokenResponseException($message); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritDoc} + */ + public function request( + $path, + $method = 'GET', + $body = null, + array $extraHeaders = array() + ) { + $extraHeaders['Accept'] = 'application/json'; + return parent::request($path, $method, $body, $extraHeaders); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Redmine.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Redmine.php new file mode 100644 index 0000000..55f89a2 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Redmine.php @@ -0,0 +1,96 @@ +baseApiUri->getAbsoluteUri() . '/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri->getAbsoluteUri() . '/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri->getAbsoluteUri() . '/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/ScoopIt.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/ScoopIt.php new file mode 100644 index 0000000..28bd250 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/ScoopIt.php @@ -0,0 +1,96 @@ +baseApiUri = new Uri('https://www.scoop.it/api/1/'); + } + } + + /** + * {@inheritDoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://www.scoop.it/oauth/request'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.scoop.it/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.scoop.it/oauth/access'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/ServiceInterface.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/ServiceInterface.php new file mode 100644 index 0000000..3f91fbf --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/ServiceInterface.php @@ -0,0 +1,45 @@ +baseApiUri = new Uri('https://api.tumblr.com/v2/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://www.tumblr.com/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.tumblr.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.tumblr.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Twitter.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Twitter.php new file mode 100644 index 0000000..dea680f --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Twitter.php @@ -0,0 +1,123 @@ +baseApiUri = new Uri('https://api.twitter.com/1.1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.twitter.com/oauth/request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + if ($this->authorizationEndpoint != self::ENDPOINT_AUTHENTICATE + && $this->authorizationEndpoint != self::ENDPOINT_AUTHORIZE) { + $this->authorizationEndpoint = self::ENDPOINT_AUTHENTICATE; + } + return new Uri($this->authorizationEndpoint); + } + + /** + * @param string $authorizationEndpoint + * + * @throws Exception + */ + public function setAuthorizationEndpoint($endpoint) + { + if ($endpoint != self::ENDPOINT_AUTHENTICATE && $endpoint != self::ENDPOINT_AUTHORIZE) { + throw new Exception( + sprintf("'%s' is not a correct Twitter authorization endpoint.", $endpoint) + ); + } + $this->authorizationEndpoint = $endpoint; + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.twitter.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response: ' . $responseBody); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } elseif (!isset($data["oauth_token"]) || !isset($data["oauth_token_secret"])) { + throw new TokenResponseException('Invalid response. OAuth Token data not set: ' . $responseBody); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Xing.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Xing.php new file mode 100644 index 0000000..e6824db --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Xing.php @@ -0,0 +1,97 @@ +baseApiUri = new Uri('https://api.xing.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.xing.com/v1/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.xing.com/v1/access_token'); + } + + /** + * {@inheritdoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.xing.com/v1/request_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + $errors = json_decode($responseBody); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif ($errors) { + throw new TokenResponseException('Error in retrieving token: "' . $errors->error_name . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Yahoo.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Yahoo.php new file mode 100644 index 0000000..50a825b --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Service/Yahoo.php @@ -0,0 +1,131 @@ +baseApiUri = new Uri('https://social.yahooapis.com/v1/'); + } + } + + /** + * {@inheritDoc} + */ + public function getRequestTokenEndpoint() + { + return new Uri('https://api.login.yahoo.com/oauth/v2/get_request_token'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.login.yahoo.com/oauth/v2/request_auth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.login.yahoo.com/oauth/v2/get_token'); + } + + /** + * {@inheritdoc} + */ + public function refreshAccessToken(TokenInterface $token) + { + $extraParams = $token->getExtraParams(); + $bodyParams = array('oauth_session_handle' => $extraParams['oauth_session_handle']); + + $authorizationHeader = array( + 'Authorization' => $this->buildAuthorizationHeaderForAPIRequest( + 'POST', + $this->getAccessTokenEndpoint(), + $this->storage->retrieveAccessToken($this->service()), + $bodyParams + ) + ); + + + + $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders(), array()); + + $responseBody = $this->httpClient->retrieveResponse($this->getAccessTokenEndpoint(), $bodyParams, $headers); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['oauth_callback_confirmed']) || $data['oauth_callback_confirmed'] !== 'true') { + throw new TokenResponseException('Error in retrieving token.'); + } + + return $this->parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth1Token(); + + $token->setRequestToken($data['oauth_token']); + $token->setRequestTokenSecret($data['oauth_token_secret']); + $token->setAccessToken($data['oauth_token']); + $token->setAccessTokenSecret($data['oauth_token_secret']); + + if (isset($data['oauth_expires_in'])) { + $token->setLifetime($data['oauth_expires_in']); + } else { + $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES); + } + + unset($data['oauth_token'], $data['oauth_token_secret']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php new file mode 100644 index 0000000..44c36ce --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Signature/Exception/UnsupportedHashAlgorithmException.php @@ -0,0 +1,12 @@ +credentials = $credentials; + } + + /** + * @param string $algorithm + */ + public function setHashingAlgorithm($algorithm) + { + $this->algorithm = $algorithm; + } + + /** + * @param string $token + */ + public function setTokenSecret($token) + { + $this->tokenSecret = $token; + } + + /** + * @param UriInterface $uri + * @param array $params + * @param string $method + * + * @return string + */ + public function getSignature(UriInterface $uri, array $params, $method = 'POST') + { + parse_str($uri->getQuery(), $queryStringData); + + foreach (array_merge($queryStringData, $params) as $key => $value) { + $signatureData[rawurlencode($key)] = rawurlencode($value); + } + + ksort($signatureData); + + // determine base uri + $baseUri = $uri->getScheme() . '://' . $uri->getRawAuthority(); + + if ('/' === $uri->getPath()) { + $baseUri .= $uri->hasExplicitTrailingHostSlash() ? '/' : ''; + } else { + $baseUri .= $uri->getPath(); + } + + $baseString = strtoupper($method) . '&'; + $baseString .= rawurlencode($baseUri) . '&'; + $baseString .= rawurlencode($this->buildSignatureDataString($signatureData)); + + return base64_encode($this->hash($baseString)); + } + + /** + * @param array $signatureData + * + * @return string + */ + protected function buildSignatureDataString(array $signatureData) + { + $signatureString = ''; + $delimiter = ''; + foreach ($signatureData as $key => $value) { + $signatureString .= $delimiter . $key . '=' . $value; + + $delimiter = '&'; + } + + return $signatureString; + } + + /** + * @return string + */ + protected function getSigningKey() + { + $signingKey = rawurlencode($this->credentials->getConsumerSecret()) . '&'; + if ($this->tokenSecret !== null) { + $signingKey .= rawurlencode($this->tokenSecret); + } + + return $signingKey; + } + + /** + * @param string $data + * + * @return string + * + * @throws UnsupportedHashAlgorithmException + */ + protected function hash($data) + { + switch (strtoupper($this->algorithm)) { + case 'HMAC-SHA1': + return hash_hmac('sha1', $data, $this->getSigningKey(), true); + default: + throw new UnsupportedHashAlgorithmException( + 'Unsupported hashing algorithm (' . $this->algorithm . ') used.' + ); + } + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Signature/SignatureInterface.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Signature/SignatureInterface.php new file mode 100644 index 0000000..da50ddb --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Signature/SignatureInterface.php @@ -0,0 +1,28 @@ +requestToken = $requestToken; + } + + /** + * @return string + */ + public function getRequestToken() + { + return $this->requestToken; + } + + /** + * @param string $requestTokenSecret + */ + public function setRequestTokenSecret($requestTokenSecret) + { + $this->requestTokenSecret = $requestTokenSecret; + } + + /** + * @return string + */ + public function getRequestTokenSecret() + { + return $this->requestTokenSecret; + } + + /** + * @param string $accessTokenSecret + */ + public function setAccessTokenSecret($accessTokenSecret) + { + $this->accessTokenSecret = $accessTokenSecret; + } + + /** + * @return string + */ + public function getAccessTokenSecret() + { + return $this->accessTokenSecret; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth1/Token/TokenInterface.php b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Token/TokenInterface.php new file mode 100644 index 0000000..0bc3f73 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth1/Token/TokenInterface.php @@ -0,0 +1,41 @@ +stateParameterInAuthUrl = $stateParameterInAutUrl; + + foreach ($scopes as $scope) { + if (!$this->isValidScope($scope)) { + throw new InvalidScopeException('Scope ' . $scope . ' is not valid for service ' . get_class($this)); + } + } + + $this->scopes = $scopes; + + $this->baseApiUri = $baseApiUri; + + $this->apiVersion = $apiVersion; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode($this->getScopesDelimiter(), $this->scopes); + + if ($this->needsStateParameterInAuthUrl()) { + if (!isset($parameters['state'])) { + $parameters['state'] = $this->generateAuthorizationState(); + } + $this->storeAuthorizationState($parameters['state']); + } + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + + $bodyParams = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'authorization_code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * Sends an authenticated API request to the path provided. + * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used. + * + * @param string|UriInterface $path + * @param string $method HTTP method + * @param array $body Request body if applicable. + * @param array $extraHeaders Extra headers if applicable. These will override service-specific + * any defaults. + * + * @return string + * + * @throws ExpiredTokenException + * @throws Exception + */ + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri); + $token = $this->storage->retrieveAccessToken($this->service()); + + if ($token->getEndOfLife() !== TokenInterface::EOL_NEVER_EXPIRES + && $token->getEndOfLife() !== TokenInterface::EOL_UNKNOWN + && time() > $token->getEndOfLife() + ) { + throw new ExpiredTokenException( + sprintf( + 'Token expired on %s at %s', + date('m/d/Y', $token->getEndOfLife()), + date('h:i:s A', $token->getEndOfLife()) + ) + ); + } + + // add the token where it may be needed + if (static::AUTHORIZATION_METHOD_HEADER_OAUTH === $this->getAuthorizationMethod()) { + $extraHeaders = array_merge(array('Authorization' => 'OAuth ' . $token->getAccessToken()), $extraHeaders); + } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING === $this->getAuthorizationMethod()) { + $uri->addToQuery('access_token', $token->getAccessToken()); + } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V2 === $this->getAuthorizationMethod()) { + $uri->addToQuery('oauth2_access_token', $token->getAccessToken()); + } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V3 === $this->getAuthorizationMethod()) { + $uri->addToQuery('apikey', $token->getAccessToken()); + } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V4 === $this->getAuthorizationMethod()) { + $uri->addToQuery('auth', $token->getAccessToken()); + } elseif (static::AUTHORIZATION_METHOD_HEADER_BEARER === $this->getAuthorizationMethod()) { + $extraHeaders = array_merge(array('Authorization' => 'Bearer ' . $token->getAccessToken()), $extraHeaders); + } + + $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders); + + return $this->httpClient->retrieveResponse($uri, $body, $extraHeaders, $method); + } + + /** + * Accessor to the storage adapter to be able to retrieve tokens + * + * @return TokenStorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * Refreshes an OAuth2 access token. + * + * @param TokenInterface $token + * + * @return TokenInterface $token + * + * @throws MissingRefreshTokenException + */ + public function refreshAccessToken(TokenInterface $token) + { + $refreshToken = $token->getRefreshToken(); + + if (empty($refreshToken)) { + throw new MissingRefreshTokenException(); + } + + $parameters = array( + 'grant_type' => 'refresh_token', + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'refresh_token' => $refreshToken, + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $parameters, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * Return whether or not the passed scope value is valid. + * + * @param string $scope + * + * @return bool + */ + public function isValidScope($scope) + { + $reflectionClass = new \ReflectionClass(get_class($this)); + + return in_array($scope, $reflectionClass->getConstants(), true); + } + + /** + * Check if the given service need to generate a unique state token to build the authorization url + * + * @return bool + */ + public function needsStateParameterInAuthUrl() + { + return $this->stateParameterInAuthUrl; + } + + /** + * Validates the authorization state against a given one + * + * @param string $state + * @throws InvalidAuthorizationStateException + */ + protected function validateAuthorizationState($state) + { + if ($this->retrieveAuthorizationState() !== $state) { + throw new InvalidAuthorizationStateException(); + } + } + + /** + * Generates a random string to be used as state + * + * @return string + */ + protected function generateAuthorizationState() + { + return md5(rand()); + } + + /** + * Retrieves the authorization state for the current service + * + * @return string + */ + protected function retrieveAuthorizationState() + { + return $this->storage->retrieveAuthorizationState($this->service()); + } + + /** + * Stores a given authorization state into the storage + * + * @param string $state + */ + protected function storeAuthorizationState($state) + { + $this->storage->storeAuthorizationState($this->service(), $state); + } + + /** + * Return any additional headers always needed for this service implementation's OAuth calls. + * + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array(); + } + + /** + * Return any additional headers always needed for this service implementation's API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array(); + } + + /** + * Parses the access token response and returns a TokenInterface. + * + * @abstract + * + * @param string $responseBody + * + * @return TokenInterface + * + * @throws TokenResponseException + */ + abstract protected function parseAccessTokenResponse($responseBody); + + /** + * Returns a class constant from ServiceInterface defining the authorization method used for the API + * Header is the sane default. + * + * @return int + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_OAUTH; + } + + /** + * Returns api version string if is set else retrun empty string + * + * @return string + */ + protected function getApiVersionString() + { + return !(empty($this->apiVersion)) ? "/".$this->apiVersion : "" ; + } + + /** + * Returns delimiter to scopes in getAuthorizationUri + * For services that do not fully respect the Oauth's RFC, + * and use scopes with commas as delimiter + * + * @return string + */ + protected function getScopesDelimiter() + { + return ' '; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Amazon.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Amazon.php new file mode 100644 index 0000000..035d1a5 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Amazon.php @@ -0,0 +1,97 @@ + + * @link https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf + */ +class Amazon extends AbstractService +{ + /** + * Defined scopes + * @link https://images-na.ssl-images-amazon.com/images/G/01/lwa/dev/docs/website-developer-guide._TTH_.pdf + */ + const SCOPE_PROFILE = 'profile'; + const SCOPE_POSTAL_CODE = 'postal_code'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.amazon.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.amazon.com/ap/oa'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.amazon.com/ap/oatoken'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error_description'] . '"'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/BattleNet.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/BattleNet.php new file mode 100644 index 0000000..ec3fdad --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/BattleNet.php @@ -0,0 +1,116 @@ +baseApiUri = new Uri( self::API_URI_US ); + } + } + + /** ----------------------------------------------------------------------- + * Translates the current base API URI into an OAuth base URI. + * + * @returns string Base URI of oauth services. + */ + private function GetOAuthBaseUri() { + + // i love china + switch( $this->baseApiUri ) { + case self::API_URI_US: return 'https://us.battle.net/oauth/'; + case self::API_URI_EU: return 'https://eu.battle.net/oauth/'; + case self::API_URI_KR: return 'https://kr.battle.net/oauth/'; + case self::API_URI_TW: return 'https://tw.battle.net/oauth/'; + case self::API_URI_CN: return 'https://www.battlenet.com.cn/oauth/'; + case self::API_URI_SEA: return 'https://sea.battle.net/oauth/'; + } + + } + + /** ----------------------------------------------------------------------- + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() { + return new Uri( $this->GetOAuthBaseUri() . 'authorize' ); + } + + /** ----------------------------------------------------------------------- + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() { + return new Uri( $this->GetOAuthBaseUri() . 'token' ); + } + + /** ----------------------------------------------------------------------- + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** ----------------------------------------------------------------------- + * {@inheritdoc} + */ + protected function parseAccessTokenResponse( $responseBody ) + { + $data = json_decode($responseBody, true); + if( $data === null || !is_array($data) ) { + throw new TokenResponseException( 'Unable to parse response.' ); + } elseif( isset($data['error']) ) { + $err = $data['error']; + throw new TokenResponseException( + "Error in retrieving token: \"$err\"" ); + } + + $token = new StdOAuth2Token( $data['access_token'], null, + $data['expires_in'] ); + + unset( $data['access_token'] ); + unset( $data['expires_in'] ); + + $token->setExtraParams( $data ); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitly.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitly.php new file mode 100644 index 0000000..e01cbc4 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitly.php @@ -0,0 +1,111 @@ +baseApiUri = new Uri('https://api-ssl.bitly.com/v3/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://bitly.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api-ssl.bitly.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // I'm invincible!!! + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + + $bodyParams = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'authorization_code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + + // we can scream what we want that we want bitly to return a json encoded string (format=json), but the + // WOAH WATCH YOUR LANGUAGE ;) service doesn't seem to like screaming, hence we need to manually + // parse the result + $parsedResult = array(); + parse_str($responseBody, $parsedResult); + + $token = $this->parseAccessTokenResponse(json_encode($parsedResult)); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitrix24.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitrix24.php new file mode 100644 index 0000000..b1fd45b --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Bitrix24.php @@ -0,0 +1,126 @@ +baseApiUri)); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri(sprintf('%s/oauth/token/', $this->baseApiUri)); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING_V4; + } + + /** + * {@inheritdoc} + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenUri($code), + array(), + $this->getExtraOAuthHeaders(), + 'GET' + ); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenUri($code) + { + $parameters = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'authorization_code', + 'scope' => $this->scopes + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = $this->getAccessTokenEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Box.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Box.php new file mode 100644 index 0000000..14696c5 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Box.php @@ -0,0 +1,88 @@ + + * @link https://developers.box.com/oauth/ + */ +class Box extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.box.com/2.0/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.box.com/api/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.box.com/api/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Buffer.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Buffer.php new file mode 100644 index 0000000..3f3ca61 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Buffer.php @@ -0,0 +1,151 @@ + + * @link https://bufferapp.com/developers/api + */ +class Buffer extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + if ($baseApiUri === null) { + $this->baseApiUri = new Uri('https://api.bufferapp.com/1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://bufferapp.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.bufferapp.com/1/oauth2/token.json'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function requestRequestToken() + { + $responseBody = $this->httpClient->retrieveResponse( + $this->getRequestTokenEndpoint(), + array( + 'client_key' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $code = $this->parseRequestTokenResponse($responseBody); + + return $code; + } + + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['code'])) { + throw new TokenResponseException('Error in retrieving code.'); + } + return $data['code']; + } + + public function requestAccessToken($code, $state = null) + { + $bodyParams = array( + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'code' => $code, + 'grant_type' => 'authorization_code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if ($data === null || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Dailymotion.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Dailymotion.php new file mode 100644 index 0000000..095a467 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Dailymotion.php @@ -0,0 +1,129 @@ + + * @link http://www.dailymotion.com/doc/api/authentication.html + */ +class Dailymotion extends AbstractService +{ + /** + * Scopes + * + * @var string + */ + const SCOPE_EMAIL = 'email', + SCOPE_PROFILE = 'userinfo', + SCOPE_VIDEOS = 'manage_videos', + SCOPE_COMMENTS = 'manage_comments', + SCOPE_PLAYLIST = 'manage_playlists', + SCOPE_TILES = 'manage_tiles', + SCOPE_SUBSCRIPTIONS = 'manage_subscriptions', + SCOPE_FRIENDS = 'manage_friends', + SCOPE_FAVORITES = 'manage_favorites', + SCOPE_GROUPS = 'manage_groups'; + + /** + * Dialog form factors + * + * @var string + */ + const DISPLAY_PAGE = 'page', + DISPLAY_POPUP = 'popup', + DISPLAY_MOBILE = 'mobile'; + + /** + * {@inheritdoc} + */ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.dailymotion.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.dailymotion.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.dailymotion.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_OAUTH; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description']) || isset($data['error'])) { + throw new TokenResponseException( + sprintf( + 'Error in retrieving token: "%s"', + isset($data['error_description']) ? $data['error_description'] : $data['error'] + ) + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/json'); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Deezer.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Deezer.php new file mode 100644 index 0000000..3e3965b --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Deezer.php @@ -0,0 +1,121 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://developers.deezer.com/api/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Deezer service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://developers.deezer.com/api/ + */ +class Deezer extends AbstractService +{ + /** + * Defined scopes + * http://developers.deezer.com/api/permissions + */ + const SCOPE_BASIC_ACCESS = 'basic_access'; // Access users basic information + const SCOPE_EMAIL = 'email'; // Get the user's email + const SCOPE_OFFLINE_ACCESS = 'offline_access'; // Access user data any time + const SCOPE_MANAGE_LIBRARY = 'manage_library'; // Manage users' library + const SCOPE_MANAGE_COMMUNITY = 'manage_community'; // Manage users' friends + const SCOPE_DELETE_LIBRARY = 'delete_library'; // Delete library items + const SCOPE_LISTENING_HISTORY = 'listening_history'; // Access the user's listening history + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.deezer.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://connect.deezer.com/oauth/auth.php'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://connect.deezer.com/oauth/access_token.php'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + if (null === $data || !is_array($data) || empty($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } elseif (isset($data['error_reason'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error_reason'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires']); + + // I hope one day Deezer add a refresh token :) + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Delicious.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Delicious.php new file mode 100644 index 0000000..eba6035 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Delicious.php @@ -0,0 +1,139 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://github.com/SciDevs/delicious-api/blob/master/api/oauth.md + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Delicious service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://github.com/SciDevs/delicious-api/blob/master/api/oauth.md + */ +class Delicious extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.del.icio.us/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://delicious.com/auth/authorize'); + + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://avosapi.delicious.com/api/v1/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifetime($data['expires_in']); + unset($data['expires_in']); + } + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + + // Special, delicious didn't respect the oauth2 RFC and need a grant_type='code' + /** + * {@inheritdoc} + */ + public function requestAccessToken($code, $state = null) + { + if (null !== $state) { + $this->validateAuthorizationState($state); + } + + $bodyParams = array( + 'code' => $code, + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'grant_type' => 'code', + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/DeviantArt.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/DeviantArt.php new file mode 100644 index 0000000..31e94b4 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/DeviantArt.php @@ -0,0 +1,99 @@ +baseApiUri = new Uri('https://www.deviantart.com/api/v1/oauth2/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.deviantart.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.deviantart.com/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifeTime($data['expires_in']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Dropbox.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Dropbox.php new file mode 100644 index 0000000..43ec6c7 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Dropbox.php @@ -0,0 +1,111 @@ + + * @link https://www.dropbox.com/developers/core/docs + */ +class Dropbox extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.dropbox.com/1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.dropbox.com/1/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.dropbox.com/1/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/EveOnline.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/EveOnline.php new file mode 100644 index 0000000..76fafa6 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/EveOnline.php @@ -0,0 +1,100 @@ + + */ +namespace OAuth\OAuth2\Service; + +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Token\TokenInterface; +use OAuth\OAuth2\Token\StdOAuth2Token; + +/** + * Class EveOnline + */ +class EveOnline extends AbstractService +{ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://login.eveonline.com'); + } + } + + /** + * Returns the authorization API endpoint. + * @return UriInterface + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri . '/oauth/authorize'); + } + + /** + * Returns the access token API endpoint. + * @return UriInterface + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri . '/oauth/token'); + } + + /** + * Parses the access token response and returns a TokenInterface. + * + * @param string $responseBody + * + * @return TokenInterface + * @throws TokenResponseException + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error_description'] . '"'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php new file mode 100644 index 0000000..398df2f --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/InvalidAccessTypeException.php @@ -0,0 +1,12 @@ + + * Released under the MIT license. + */ + +namespace OAuth\OAuth2\Service\Exception; + +use OAuth\Common\Exception\Exception; + +/** + * Exception thrown when a scope provided to a service is invalid. + */ +class InvalidScopeException extends Exception +{ +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php new file mode 100644 index 0000000..21eece6 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Exception/MissingRefreshTokenException.php @@ -0,0 +1,17 @@ + + * Released under the MIT license. + */ + +namespace OAuth\OAuth2\Service\Exception; + +use OAuth\Common\Exception\Exception; + +/** + * Exception thrown when service is requested to refresh the access token but no refresh token can be found. + */ +class MissingRefreshTokenException extends Exception +{ +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Facebook.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Facebook.php new file mode 100644 index 0000000..4202b8b --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Facebook.php @@ -0,0 +1,220 @@ +baseApiUri = new Uri('https://graph.facebook.com'.$this->getApiVersionString().'/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.facebook.com'.$this->getApiVersionString().'/dialog/oauth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://graph.facebook.com'.$this->getApiVersionString().'/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = @json_decode($responseBody, true); + + // Facebook gives us a query string on old api (v2.0) + if (!$data) { + parse_str($responseBody, $data); + } + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires'])) { + $token->setLifeTime($data['expires']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires']); + + $token->setExtraParams($data); + + return $token; + } + + public function getDialogUri($dialogPath, array $parameters) + { + if (!isset($parameters['redirect_uri'])) { + throw new Exception("Redirect uri is mandatory for this request"); + } + $parameters['app_id'] = $this->credentials->getConsumerId(); + $baseUrl = self::WWW_URL .$this->getApiVersionString(). '/dialog/' . $dialogPath; + $query = http_build_query($parameters); + return new Uri($baseUrl . '?' . $query); + } + + /** + * {@inheritdoc} + */ + protected function getApiVersionString() + { + return empty($this->apiVersion) ? '' : '/v' . $this->apiVersion; + } + + /** + * {@inheritdoc} + */ + protected function getScopesDelimiter() + { + return ','; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Foursquare.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Foursquare.php new file mode 100644 index 0000000..981ff44 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Foursquare.php @@ -0,0 +1,81 @@ +baseApiUri = new Uri('https://api.foursquare.com/v2/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://foursquare.com/oauth2/authenticate'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://foursquare.com/oauth2/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // Foursquare tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri); + $uri->addToQuery('v', $this->apiVersionDate); + + return parent::request($uri, $method, $body, $extraHeaders); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/GitHub.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/GitHub.php new file mode 100644 index 0000000..8d4d122 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/GitHub.php @@ -0,0 +1,216 @@ +baseApiUri = new Uri('https://api.github.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://github.com/login/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://github.com/login/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // Github tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * Used to configure response type -- we want JSON from github, default is query string format + * + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/json'); + } + + /** + * Required for GitHub API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array('Accept' => 'application/vnd.github.beta+json'); + } + + /** + * {@inheritdoc} + */ + protected function getScopesDelimiter() + { + return ','; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Google.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Google.php new file mode 100644 index 0000000..1b6555f --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Google.php @@ -0,0 +1,200 @@ +baseApiUri = new Uri('https://www.googleapis.com/oauth2/v1/'); + } + } + + public function setAccessType($accessType) + { + if (!in_array($accessType, array('online', 'offline'), true)) { + throw new InvalidAccessTypeException('Invalid accessType, expected either online or offline'); + } + $this->accessType = $accessType; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://accounts.google.com/o/oauth2/auth?access_type=' . $this->accessType); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://accounts.google.com/o/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Harvest.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Harvest.php new file mode 100644 index 0000000..96fb0f2 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Harvest.php @@ -0,0 +1,157 @@ +baseApiUri = new Uri('https://api.harvestapp.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'state' => 'optional-csrf-token', + 'response_type' => 'code', + ) + ); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.harvestapp.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.harvestapp.com/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || ! is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + $token->setRefreshToken($data['refresh_token']); + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * Refreshes an OAuth2 access token. + * + * @param TokenInterface $token + * + * @return TokenInterface $token + * + * @throws MissingRefreshTokenException + */ + public function refreshAccessToken(TokenInterface $token) + { + $refreshToken = $token->getRefreshToken(); + + if (empty($refreshToken)) { + throw new MissingRefreshTokenException(); + } + + $parameters = array( + 'grant_type' => 'refresh_token', + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'refresh_token' => $refreshToken, + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $parameters, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + /** + * @return array + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/json'); + } + + /** + * Return any additional headers always needed for this service implementation's API calls. + * + * @return array + */ + protected function getExtraApiHeaders() + { + return array('Accept' => 'application/json'); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Heroku.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Heroku.php new file mode 100644 index 0000000..470cedc --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Heroku.php @@ -0,0 +1,123 @@ + + * @link https://devcenter.heroku.com/articles/oauth + */ +class Heroku extends AbstractService +{ + /** + * Defined scopes + * @link https://devcenter.heroku.com/articles/oauth#scopes + */ + const SCOPE_GLOBAL = 'global'; + const SCOPE_IDENTITY = 'identity'; + const SCOPE_READ = 'read'; + const SCOPE_WRITE = 'write'; + const SCOPE_READ_PROTECTED = 'read-protected'; + const SCOPE_WRITE_PROTECTED = 'write-protected'; + + /** + * {@inheritdoc} + */ + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.heroku.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://id.heroku.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://id.heroku.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description']) || isset($data['error'])) { + throw new TokenResponseException( + sprintf( + 'Error in retrieving token: "%s"', + isset($data['error_description']) ? $data['error_description'] : $data['error'] + ) + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/vnd.heroku+json; version=3'); + } + + /** + * {@inheritdoc} + */ + protected function getExtraApiHeaders() + { + return array('Accept' => 'application/vnd.heroku+json; version=3', 'Content-Type' => 'application/json'); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Hubic.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Hubic.php new file mode 100644 index 0000000..b995450 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Hubic.php @@ -0,0 +1,155 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://api.hubic.com/docs/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Hubic service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://api.hubic.com/docs/ + */ +class Hubic extends AbstractService +{ + + // Scopes + const SCOPE_USAGE_GET = 'usage.r'; + const SCOPE_ACCOUNT_GET = 'account.r'; + const SCOPE_GETALLLINKS_GET = 'getAllLinks.r'; + const SCOPE_CREDENTIALS_GET = 'credentials.r'; + const SCOPE_SPONSORCODE_GET = 'sponsorCode.r'; + const SCOPE_ACTIVATE_POST = 'activate.w'; + const SCOPE_SPONSORED_GET = 'sponsored.r'; + const SCOPE_LINKS_GET = 'links.r'; + const SCOPE_LINKS_POST = 'links.rw'; + const SCOPE_LINKS_ALL = 'links.drw'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.hubic.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.hubic.com/oauth/auth'); + + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.hubic.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + // special, hubic use a param scope with commas + // between scopes instead of spaces + $parameters['scope'] = implode(',', $this->scopes); + + if ($this->needsStateParameterInAuthUrl()) { + if (!isset($parameters['state'])) { + $parameters['state'] = $this->generateAuthorizationState(); + } + $this->storeAuthorizationState($parameters['state']); + } + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Instagram.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Instagram.php new file mode 100644 index 0000000..43fe181 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Instagram.php @@ -0,0 +1,87 @@ +baseApiUri = new Uri('https://api.instagram.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.instagram.com/oauth/authorize/'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.instagram.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // Instagram tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/JawboneUP.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/JawboneUP.php new file mode 100644 index 0000000..fad1125 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/JawboneUP.php @@ -0,0 +1,144 @@ + + * @link https://jawbone.com/up/developer/authentication + */ +class JawboneUP extends AbstractService +{ + /** + * Defined scopes + * + * + * @link https://jawbone.com/up/developer/authentication + */ + // general information scopes + const SCOPE_BASIC_READ = 'basic_read'; + const SCOPE_EXTENDED_READ = 'extended_read'; + const SCOPE_LOCATION_READ = 'location_read'; + const SCOPE_FRIENDS_READ = 'friends_read'; + // mood scopes + const SCOPE_MOOD_READ = 'mood_read'; + const SCOPE_MOOD_WRITE = 'mood_write'; + // move scopes + const SCOPE_MOVE_READ = 'move_read'; + const SCOPE_MOVE_WRITE = 'move_write'; + // sleep scopes + const SCOPE_SLEEP_READ = 'sleep_read'; + const SCOPE_SLEEP_WRITE = 'sleep_write'; + // meal scopes + const SCOPE_MEAL_READ = 'meal_read'; + const SCOPE_MEAL_WRITE = 'meal_write'; + // weight scopes + const SCOPE_WEIGHT_READ = 'weight_read'; + const SCOPE_WEIGHT_WRITE = 'weight_write'; + // generic event scopes + const SCOPE_GENERIC_EVENT_READ = 'generic_event_read'; + const SCOPE_GENERIC_EVENT_WRITE = 'generic_event_write'; + + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://jawbone.com/nudge/api/v.1.1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://jawbone.com/auth/oauth2/auth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://jawbone.com/auth/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Linkedin.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Linkedin.php new file mode 100644 index 0000000..65cfd94 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Linkedin.php @@ -0,0 +1,103 @@ + + * @link http://developer.linkedin.com/documents/authentication + */ +class Linkedin extends AbstractService +{ + /** + * Defined scopes + * @link http://developer.linkedin.com/documents/authentication#granting + */ + const SCOPE_R_BASICPROFILE = 'r_basicprofile'; + const SCOPE_R_FULLPROFILE = 'r_fullprofile'; + const SCOPE_R_EMAILADDRESS = 'r_emailaddress'; + const SCOPE_R_NETWORK = 'r_network'; + const SCOPE_R_CONTACTINFO = 'r_contactinfo'; + const SCOPE_RW_NUS = 'rw_nus'; + const SCOPE_RW_COMPANY_ADMIN = 'rw_company_admin'; + const SCOPE_RW_GROUPS = 'rw_groups'; + const SCOPE_W_MESSAGES = 'w_messages'; + const SCOPE_W_SHARE = 'w_share'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri, true); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.linkedin.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.linkedin.com/uas/oauth2/authorization'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.linkedin.com/uas/oauth2/accessToken'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Mailchimp.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Mailchimp.php new file mode 100644 index 0000000..842153d --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Mailchimp.php @@ -0,0 +1,114 @@ +baseApiUri) && $storage->hasAccessToken($this->service())) { + $this->setBaseApiUri($storage->retrieveAccessToken($this->service())); + } + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING_V3; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://login.mailchimp.com/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://login.mailchimp.com/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + // Parse JSON + $data = json_decode($responseBody, true); + + // Do validation. + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + // Create token object. + $token = new StdOAuth2Token($data['access_token']); + + // Set the right API endpoint. + $this->setBaseApiUri($token); + + // Mailchimp tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + + return $token; + } + + /** + * {@inheritdoc} + */ + public function request($path, $method = 'GET', $body = null, array $extraHeaders = array()) + { + if (is_null($this->baseApiUri)) { + $this->setBaseApiUri($this->storage->retrieveAccessToken($this->service())); + } + + return parent::request($path, $method, $body, $extraHeaders); + } + + /** + * Set the right base endpoint. + * + * @param StdOAuth2Token $token + */ + protected function setBaseApiUri(StdOAuth2Token $token) + { + // Make request uri. + $endpoint = 'https://login.mailchimp.com/oauth2/metadata?oauth_token='. $token->getAccessToken(); + + // Grab meta data about the token. + $response = $this->httpClient->retrieveResponse(new Uri($endpoint), array(), array(), 'GET'); + + // Parse JSON. + $meta = json_decode($response, true); + + // Set base api uri. + $this->baseApiUri = new Uri('https://'. $meta['dc'] .'.api.mailchimp.com/2.0/'); + + // Allow chaining. + return $this; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Microsoft.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Microsoft.php new file mode 100644 index 0000000..c815b22 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Microsoft.php @@ -0,0 +1,120 @@ +baseApiUri = new Uri('https://apis.live.net/v5.0/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://login.live.com/oauth20_authorize.srf'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://login.live.com/oauth20_token.srf'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Mondo.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Mondo.php new file mode 100644 index 0000000..e063045 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Mondo.php @@ -0,0 +1,86 @@ +baseApiUri = new Uri('https://api.getmondo.co.uk'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://auth.getmondo.co.uk'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.getmondo.co.uk/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifetime($data['expires_in']); + unset($data['expires_in']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Nest.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Nest.php new file mode 100644 index 0000000..7659e4f --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Nest.php @@ -0,0 +1,106 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developer.nest.com/documentation + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Nest service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developer.nest.com/documentation + */ +class Nest extends AbstractService +{ + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://developer-api.nest.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://home.nest.com/login/oauth2'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.home.nest.com/oauth2/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING_V4; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Netatmo.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Netatmo.php new file mode 100644 index 0000000..eb5c68d --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Netatmo.php @@ -0,0 +1,117 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://dev.netatmo.com/doc/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Netatmo service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://dev.netatmo.com/doc/ + */ +class Netatmo extends AbstractService +{ + + // SCOPES + // @link https://dev.netatmo.com/doc/authentication/scopes + + // Used to read weather station's data (devicelist, getmeasure) + const SCOPE_STATION_READ = 'read_station'; + // Used to read thermostat's data (devicelist, getmeasure, getthermstate) + const SCOPE_THERMOSTAT_READ = 'read_thermostat'; + // Used to configure the thermostat (syncschedule, setthermpoint) + const SCOPE_THERMOSTAT_WRITE = 'write_thermostat'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true // use parameter state + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.netatmo.net/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri.'oauth2/authorize'); + + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri.'oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/ParrotFlowerPower.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/ParrotFlowerPower.php new file mode 100644 index 0000000..78ef942 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/ParrotFlowerPower.php @@ -0,0 +1,142 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://flowerpowerdev.parrot.com/projects/flower-power-web-service-api/wiki + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\OAuth2\Service\Exception\MissingRefreshTokenException; +use OAuth\Common\Token\TokenInterface; + +/** + * ParrotFlowerPower service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://flowerpowerdev.parrot.com/projects/flower-power-web-service-api/wiki + */ +class ParrotFlowerPower extends AbstractService +{ + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://apiflowerpower.parrot.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri($this->baseApiUri.'oauth2/v1/authorize'); + + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri($this->baseApiUri.'user/v1/authenticate'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + + /** + * Parrot use a different endpoint for refresh a token + * + * {@inheritdoc} + */ + public function refreshAccessToken(TokenInterface $token) + { + $refreshToken = $token->getRefreshToken(); + + if (empty($refreshToken)) { + throw new MissingRefreshTokenException(); + } + + $parameters = array( + 'grant_type' => 'refresh_token', + 'type' => 'web_server', + 'client_id' => $this->credentials->getConsumerId(), + 'client_secret' => $this->credentials->getConsumerSecret(), + 'refresh_token' => $refreshToken, + ); + + $responseBody = $this->httpClient->retrieveResponse( + new Uri($this->baseApiUri.'user/v1/refresh'), + $parameters, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Paypal.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Paypal.php new file mode 100644 index 0000000..761c09d --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Paypal.php @@ -0,0 +1,103 @@ + + * @link https://developer.paypal.com/webapps/developer/docs/integration/direct/log-in-with-paypal/detailed/ + */ +class Paypal extends AbstractService +{ + /** + * Defined scopes + * @link https://developer.paypal.com/webapps/developer/docs/integration/direct/log-in-with-paypal/detailed/ + * @see #attributes + */ + const SCOPE_OPENID = 'openid'; + const SCOPE_PROFILE = 'profile'; + const SCOPE_PAYPALATTRIBUTES = 'https://uri.paypal.com/services/paypalattributes'; + const SCOPE_EMAIL = 'email'; + const SCOPE_ADDRESS = 'address'; + const SCOPE_PHONE = 'phone'; + const SCOPE_EXPRESSCHECKOUT = 'https://uri.paypal.com/services/expresscheckout'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct($credentials, $httpClient, $storage, $scopes, $baseApiUri); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.paypal.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.paypal.com/v1/identity/openidconnect/tokenservice'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['message'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['message'] . '"'); + } elseif (isset($data['name'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['name'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Pinterest.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Pinterest.php new file mode 100644 index 0000000..e7ecd3c --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Pinterest.php @@ -0,0 +1,117 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developers.pinterest.com/docs/api/overview/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Pinterest service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developers.pinterest.com/docs/api/overview/ + */ +class Pinterest extends AbstractService +{ + /** + * Defined scopes - More scopes are listed here: + * https://developers.pinterest.com/docs/api/overview/ + */ + const SCOPE_READ_PUBLIC = 'read_public'; // read a user’s Pins, boards and likes + const SCOPE_WRITE_PUBLIC = 'write_public'; // write Pins, boards, likes + const SCOPE_READ_RELATIONSHIPS = 'read_relationships'; // read a user’s follows (boards, users, interests) + const SCOPE_WRITE_RELATIONSHIPS = 'write_relationships'; // follow boards, users and interests + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.pinterest.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.pinterest.com/oauth/'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.pinterest.com/v1/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifeTime($data['expires_in']); + unset($data['expires_in']); + } + // I hope one day Pinterest add a refresh token :) + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Pocket.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Pocket.php new file mode 100644 index 0000000..6c5cb7e --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Pocket.php @@ -0,0 +1,125 @@ +baseApiUri = new Uri('https://getpocket.com/v3/'); + } + } + + public function getRequestTokenEndpoint() + { + return new Uri('https://getpocket.com/v3/oauth/request'); + } + + public function getAuthorizationEndpoint() + { + return new Uri('https://getpocket.com/auth/authorize'); + } + + public function getAccessTokenEndpoint() + { + return new Uri('https://getpocket.com/v3/oauth/authorize'); + } + + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'redirect_uri' => $this->credentials->getCallbackUrl(), + ) + ); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + public function requestRequestToken() + { + $responseBody = $this->httpClient->retrieveResponse( + $this->getRequestTokenEndpoint(), + array( + 'consumer_key' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + ) + ); + + $code = $this->parseRequestTokenResponse($responseBody); + + return $code; + } + + protected function parseRequestTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (!isset($data['code'])) { + throw new TokenResponseException('Error in retrieving code.'); + } + return $data['code']; + } + + public function requestAccessToken($code, $state = null) + { + $bodyParams = array( + 'consumer_key' => $this->credentials->getConsumerId(), + 'code' => $code, + ); + + $responseBody = $this->httpClient->retrieveResponse( + $this->getAccessTokenEndpoint(), + $bodyParams, + $this->getExtraOAuthHeaders() + ); + $token = $this->parseAccessTokenResponse($responseBody); + $this->storage->storeAccessToken($this->service(), $token); + + return $token; + } + + protected function parseAccessTokenResponse($responseBody) + { + parse_str($responseBody, $data); + + if ($data === null || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + #$token->setRequestToken($data['access_token']); + $token->setAccessToken($data['access_token']); + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Reddit.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Reddit.php new file mode 100644 index 0000000..9e524d1 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Reddit.php @@ -0,0 +1,114 @@ +baseApiUri = new Uri('https://oauth.reddit.com'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://ssl.reddit.com/api/v1/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://ssl.reddit.com/api/v1/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + // Reddit uses a Basic OAuth header + return array('Authorization' => 'Basic ' . + base64_encode($this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret())); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/RunKeeper.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/RunKeeper.php new file mode 100644 index 0000000..7158407 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/RunKeeper.php @@ -0,0 +1,105 @@ +baseApiUri = new Uri('https://api.runkeeper.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationUri(array $additionalParameters = array()) + { + $parameters = array_merge( + $additionalParameters, + array( + 'client_id' => $this->credentials->getConsumerId(), + 'redirect_uri' => $this->credentials->getCallbackUrl(), + 'response_type' => 'code', + ) + ); + + $parameters['scope'] = implode(' ', $this->scopes); + + // Build the url + $url = clone $this->getAuthorizationEndpoint(); + foreach ($parameters as $key => $val) { + $url->addToQuery($key, $val); + } + + return $url; + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://runkeeper.com/apps/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://runkeeper.com/apps/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Salesforce.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Salesforce.php new file mode 100644 index 0000000..5edc42c --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Salesforce.php @@ -0,0 +1,92 @@ +parseAccessTokenResponse($responseBody); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + // Salesforce tokens evidently never expire... + $token->setEndOfLife(StdOAuth2Token::EOL_NEVER_EXPIRES); + unset($data['access_token']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => 'application/json'); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/ServiceInterface.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/ServiceInterface.php new file mode 100644 index 0000000..e7c2e76 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/ServiceInterface.php @@ -0,0 +1,39 @@ +baseApiUri = new Uri('https://api.soundcloud.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://soundcloud.com/connect'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.soundcloud.com/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifetime($data['expires_in']); + unset($data['expires_in']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Spotify.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Spotify.php new file mode 100644 index 0000000..22f645b --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Spotify.php @@ -0,0 +1,112 @@ +baseApiUri = new Uri('https://api.spotify.com/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://accounts.spotify.com/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://accounts.spotify.com/api/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifetime($data['expires_in']); + unset($data['expires_in']); + } + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Authorization' => 'Basic ' . + base64_encode($this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret())); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Strava.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Strava.php new file mode 100644 index 0000000..208ec63 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Strava.php @@ -0,0 +1,147 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://strava.github.io/ + * @link http://strava.github.io/api/v3/oauth/ + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\OAuth2\Service\Exception\InvalidAccessTypeException; + +/** + * Strava service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link http://strava.github.io/ + * @link http://strava.github.io/api/v3/oauth/ + */ +class Strava extends AbstractService +{ + /** + * Scopes + */ + // default + const SCOPE_PUBLIC = 'public'; + // Modify activities, upload on the user’s behalf + const SCOPE_WRITE = 'write'; + // View private activities and data within privacy zones + const SCOPE_VIEW_PRIVATE = 'view_private'; + + protected $approvalPrompt = 'auto'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + if (empty($scopes)) { + $scopes = array(self::SCOPE_PUBLIC); + } + + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://www.strava.com/api/v3/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.strava.com/oauth/authorize?approval_prompt=' . $this->approvalPrompt); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.strava.com/oauth/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error_description'] . '"' + ); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifeTime($data['expires_in']); + unset($data['expires_in']); + } + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + public function setApprouvalPrompt($prompt) + { + if (!in_array($prompt, array('auto', 'force'), true)) { + // @todo Maybe could we rename this exception + throw new InvalidAccessTypeException('Invalid approuvalPrompt, expected either auto or force.'); + } + $this->approvalPrompt = $prompt; + } + + /** + * {@inheritdoc} + */ + protected function getScopesDelimiter() + { + return ','; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Ustream.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Ustream.php new file mode 100644 index 0000000..7e55815 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Ustream.php @@ -0,0 +1,98 @@ +baseApiUri = new Uri('https://api.ustream.tv/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.ustream.tv/oauth2/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.ustream.tv/oauth2/token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Authorization' => 'Basic ' . $this->credentials->getConsumerSecret()); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Vimeo.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Vimeo.php new file mode 100644 index 0000000..2efe6b2 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Vimeo.php @@ -0,0 +1,156 @@ + + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developer.vimeo.com/ + * @link https://developer.vimeo.com/api/authentication + */ + +namespace OAuth\OAuth2\Service; + +use OAuth\OAuth2\Token\StdOAuth2Token; +use OAuth\Common\Http\Exception\TokenResponseException; +use OAuth\Common\Http\Uri\Uri; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Uri\UriInterface; + +/** + * Vimeo service. + * + * @author Pedro Amorim + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @link https://developer.vimeo.com/ + * @link https://developer.vimeo.com/api/authentication + */ +class Vimeo extends AbstractService +{ + // API version + const VERSION = '3.2'; + // API Header Accept + const HEADER_ACCEPT = 'application/vnd.vimeo.*+json;version=3.2'; + + /** + * Scopes + * @see https://developer.vimeo.com/api/authentication#scope + */ + // View public videos + const SCOPE_PUBLIC = 'public'; + // View private videos + const SCOPE_PRIVATE = 'private'; + // View Vimeo On Demand purchase history + const SCOPE_PURCHASED = 'purchased'; + // Create new videos, groups, albums, etc. + const SCOPE_CREATE = 'create'; + // Edit videos, groups, albums, etc. + const SCOPE_EDIT = 'edit'; + // Delete videos, groups, albums, etc. + const SCOPE_DELETE = 'delete'; + // Interact with a video on behalf of a user, such as liking + // a video or adding it to your watch later queue + const SCOPE_INTERACT = 'interact'; + // Upload a video + const SCOPE_UPLOAD = 'upload'; + + public function __construct( + CredentialsInterface $credentials, + ClientInterface $httpClient, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null + ) { + parent::__construct( + $credentials, + $httpClient, + $storage, + $scopes, + $baseApiUri, + true + ); + + if (null === $baseApiUri) { + $this->baseApiUri = new Uri('https://api.vimeo.com/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://api.vimeo.com/oauth/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://api.vimeo.com/oauth/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error_description'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error_description'] . '"' + ); + } elseif (isset($data['error'])) { + throw new TokenResponseException( + 'Error in retrieving token: "' . $data['error'] . '"' + ); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + + if (isset($data['expires_in'])) { + $token->setLifeTime($data['expires_in']); + unset($data['expires_in']); + } + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + return array('Accept' => self::HEADER_ACCEPT); + } + + /** + * {@inheritdoc} + */ + protected function getExtraApiHeaders() + { + return array('Accept' => self::HEADER_ACCEPT); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Vkontakte.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Vkontakte.php new file mode 100644 index 0000000..4a7744e --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Vkontakte.php @@ -0,0 +1,109 @@ +baseApiUri = new Uri('https://api.vk.com/method/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://oauth.vk.com/authorize'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://oauth.vk.com/access_token'); + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']); + $token->setLifeTime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_QUERY_STRING; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Yahoo.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Yahoo.php new file mode 100644 index 0000000..ee5e985 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Yahoo.php @@ -0,0 +1,76 @@ +setAccessToken($data['access_token']); + $token->setLifetime($data['expires_in']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } + + /** + * {@inheritdoc} + */ + protected function getExtraOAuthHeaders() + { + $encodedCredentials = base64_encode( + $this->credentials->getConsumerId() . ':' . $this->credentials->getConsumerSecret() + ); + return array('Authorization' => 'Basic ' . $encodedCredentials); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Yammer.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Yammer.php new file mode 100644 index 0000000..994a293 --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Service/Yammer.php @@ -0,0 +1,82 @@ +baseApiUri = new Uri('https://www.yammer.com/api/v1/'); + } + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationEndpoint() + { + return new Uri('https://www.yammer.com/dialog/oauth'); + } + + /** + * {@inheritdoc} + */ + public function getAccessTokenEndpoint() + { + return new Uri('https://www.yammer.com/oauth2/access_token.json'); + } + + /** + * {@inheritdoc} + */ + public function getAuthorizationMethod() + { + return static::AUTHORIZATION_METHOD_HEADER_BEARER; + } + + /** + * {@inheritdoc} + */ + protected function parseAccessTokenResponse($responseBody) + { + $data = json_decode($responseBody, true); + + if (null === $data || !is_array($data)) { + throw new TokenResponseException('Unable to parse response.'); + } elseif (isset($data['error'])) { + throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"'); + } + + $token = new StdOAuth2Token(); + $token->setAccessToken($data['access_token']['token']); + $token->setLifetime($data['access_token']['expires_at']); + + if (isset($data['refresh_token'])) { + $token->setRefreshToken($data['refresh_token']); + unset($data['refresh_token']); + } + + unset($data['access_token']); + unset($data['expires_in']); + + $token->setExtraParams($data); + + return $token; + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/OAuth2/Token/StdOAuth2Token.php b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Token/StdOAuth2Token.php new file mode 100644 index 0000000..eaaacac --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/OAuth2/Token/StdOAuth2Token.php @@ -0,0 +1,13 @@ + + * @author Pieter Hordijk + * @copyright Copyright (c) 2013 The authors + * @license http://www.opensource.org/licenses/mit-license.html MIT License + */ + +namespace OAuth; + +use OAuth\Common\Service\ServiceInterface; +use OAuth\Common\Consumer\CredentialsInterface; +use OAuth\Common\Storage\TokenStorageInterface; +use OAuth\Common\Http\Client\ClientInterface; +use OAuth\Common\Http\Client\StreamClient; +use OAuth\Common\Http\Uri\UriInterface; +use OAuth\Common\Exception\Exception; +use OAuth\OAuth1\Signature\Signature; + +class ServiceFactory +{ + /** + *@var ClientInterface + */ + protected $httpClient; + + /** + * @var array + */ + protected $serviceClassMap = array( + 'OAuth1' => array(), + 'OAuth2' => array() + ); + + /** + * @var array + */ + protected $serviceBuilders = array( + 'OAuth2' => 'buildV2Service', + 'OAuth1' => 'buildV1Service', + ); + + /** + * @param ClientInterface $httpClient + * + * @return ServiceFactory + */ + public function setHttpClient(ClientInterface $httpClient) + { + $this->httpClient = $httpClient; + + return $this; + } + + /** + * Register a custom service to classname mapping. + * + * @param string $serviceName Name of the service + * @param string $className Class to instantiate + * + * @return ServiceFactory + * + * @throws Exception If the class is nonexistent or does not implement a valid ServiceInterface + */ + public function registerService($serviceName, $className) + { + if (!class_exists($className)) { + throw new Exception(sprintf('Service class %s does not exist.', $className)); + } + + $reflClass = new \ReflectionClass($className); + + foreach (array('OAuth2', 'OAuth1') as $version) { + if ($reflClass->implementsInterface('OAuth\\' . $version . '\\Service\\ServiceInterface')) { + $this->serviceClassMap[$version][ucfirst($serviceName)] = $className; + + return $this; + } + } + + throw new Exception(sprintf('Service class %s must implement ServiceInterface.', $className)); + } + + /** + * Builds and returns oauth services + * + * It will first try to build an OAuth2 service and if none found it will try to build an OAuth1 service + * + * @param string $serviceName Name of service to create + * @param CredentialsInterface $credentials + * @param TokenStorageInterface $storage + * @param array|null $scopes If creating an oauth2 service, array of scopes + * @param UriInterface|null $baseApiUri + * @param string $apiVersion version of the api call + * + * @return ServiceInterface + */ + public function createService( + $serviceName, + CredentialsInterface $credentials, + TokenStorageInterface $storage, + $scopes = array(), + UriInterface $baseApiUri = null, + $apiVersion = "" + ) { + if (!$this->httpClient) { + // for backwards compatibility. + $this->httpClient = new StreamClient(); + } + + foreach ($this->serviceBuilders as $version => $buildMethod) { + $fullyQualifiedServiceName = $this->getFullyQualifiedServiceName($serviceName, $version); + + if (class_exists($fullyQualifiedServiceName)) { + return $this->$buildMethod( + $fullyQualifiedServiceName, + $credentials, + $storage, + $scopes, + $baseApiUri, + $apiVersion + ); + } + } + + return null; + } + + /** + * Gets the fully qualified name of the service + * + * @param string $serviceName The name of the service of which to get the fully qualified name + * @param string $type The type of the service to get (either OAuth1 or OAuth2) + * + * @return string The fully qualified name of the service + */ + private function getFullyQualifiedServiceName($serviceName, $type) + { + $serviceName = ucfirst($serviceName); + + if (isset($this->serviceClassMap[$type][$serviceName])) { + return $this->serviceClassMap[$type][$serviceName]; + } + + return '\\OAuth\\' . $type . '\\Service\\' . $serviceName; + } + + /** + * Builds v2 services + * + * @param string $serviceName The fully qualified service name + * @param CredentialsInterface $credentials + * @param TokenStorageInterface $storage + * @param array|null $scopes Array of scopes for the service + * @param UriInterface|null $baseApiUri + * + * @return ServiceInterface + * + * @throws Exception + */ + private function buildV2Service( + $serviceName, + CredentialsInterface $credentials, + TokenStorageInterface $storage, + array $scopes, + UriInterface $baseApiUri = null, + $apiVersion = "" + ) { + return new $serviceName( + $credentials, + $this->httpClient, + $storage, + $this->resolveScopes($serviceName, $scopes), + $baseApiUri, + $apiVersion + ); + } + + /** + * Resolves scopes for v2 services + * + * @param string $serviceName The fully qualified service name + * @param array $scopes List of scopes for the service + * + * @return array List of resolved scopes + */ + private function resolveScopes($serviceName, array $scopes) + { + $reflClass = new \ReflectionClass($serviceName); + $constants = $reflClass->getConstants(); + + $resolvedScopes = array(); + foreach ($scopes as $scope) { + $key = strtoupper('SCOPE_' . $scope); + + if (array_key_exists($key, $constants)) { + $resolvedScopes[] = $constants[$key]; + } else { + $resolvedScopes[] = $scope; + } + } + + return $resolvedScopes; + } + + /** + * Builds v1 services + * + * @param string $serviceName The fully qualified service name + * @param CredentialsInterface $credentials + * @param TokenStorageInterface $storage + * @param array $scopes + * @param UriInterface $baseApiUri + * + * @return ServiceInterface + * + * @throws Exception + */ + private function buildV1Service( + $serviceName, + CredentialsInterface $credentials, + TokenStorageInterface $storage, + $scopes, + UriInterface $baseApiUri = null + ) { + if (!empty($scopes)) { + throw new Exception( + 'Scopes passed to ServiceFactory::createService but an OAuth1 service was requested.' + ); + } + + return new $serviceName($credentials, $this->httpClient, $storage, new Signature($credentials), $baseApiUri); + } +} diff --git a/vendor/lusitanian/oauth/src/OAuth/bootstrap.php b/vendor/lusitanian/oauth/src/OAuth/bootstrap.php new file mode 100644 index 0000000..548678a --- /dev/null +++ b/vendor/lusitanian/oauth/src/OAuth/bootstrap.php @@ -0,0 +1,13 @@ +register(); diff --git a/vendor/marc1706/fast-image-size/LICENSE b/vendor/marc1706/fast-image-size/LICENSE new file mode 100644 index 0000000..2e607e9 --- /dev/null +++ b/vendor/marc1706/fast-image-size/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Marc Alexander + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/marc1706/fast-image-size/composer.json b/vendor/marc1706/fast-image-size/composer.json new file mode 100644 index 0000000..66e935b --- /dev/null +++ b/vendor/marc1706/fast-image-size/composer.json @@ -0,0 +1,29 @@ +{ + "name": "marc1706/fast-image-size", + "type": "library", + "description": "fast-image-size is a PHP library that does almost everything PHP's getimagesize() does but without the large overhead of downloading the complete file.", + "keywords": ["getimagesize", "fast", "PHP", "image", "size", "imagesize"], + "homepage": "https://www.m-a-styles.de", + "license": "MIT", + "authors": [ + { + "name": "Marc Alexander", + "email": "admin@m-a-styles.de", + "homepage": "https://www.m-a-styles.de", + "role": "Developer" + } + ], + "require": { + "php": ">=5.3.0", + "ext-mbstring": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "autoload": { + "psr-4": { + "FastImageSize\\": "lib", + "FastImageSize\\tests\\": "tests" + } + } +} diff --git a/vendor/marc1706/fast-image-size/composer.lock b/vendor/marc1706/fast-image-size/composer.lock new file mode 100644 index 0000000..a52f349 --- /dev/null +++ b/vendor/marc1706/fast-image-size/composer.lock @@ -0,0 +1,971 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "c43a2d7770299d097c55b9ce3c48f0cc", + "packages": [], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", + "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "phpDocumentor": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "time": "2015-02-03 12:10:50" + }, + { + "name": "phpspec/prophecy", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", + "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "phpdocumentor/reflection-docblock": "~2.0", + "sebastian/comparator": "~1.1" + }, + "require-dev": { + "phpspec/phpspec": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2015-08-13 10:07:40" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c", + "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2015-08-04 03:42:39" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2015-06-21 08:01:12" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3ab72c62e550370a6cd5dc873e1a04ab57562f5b", + "reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-08-16 08:51:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "9b7417edaf28059ea63d86be941e6004dbfcc0cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b7417edaf28059ea63d86be941e6004dbfcc0cc", + "reference": "9b7417edaf28059ea63d86be941e6004dbfcc0cc", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": ">=1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2015-08-19 09:20:57" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "5e2645ad49d196e020b85598d7c97e482725786a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5e2645ad49d196e020b85598d7c97e482725786a", + "reference": "5e2645ad49d196e020b85598d7c97e482725786a", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2015-08-19 09:14:08" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3", + "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "http://www.github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-02-22 15:13:53" + }, + { + "name": "sebastian/environment", + "version": "1.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", + "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2015-08-03 06:14:51" + }, + { + "name": "sebastian/exporter", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", + "reference": "7ae5513327cb536431847bcc0c10edba2701064e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2015-06-21 07:55:53" + }, + { + "name": "sebastian/global-state", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01", + "reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2014-10-06 09:23:50" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", + "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-06-21 08:04:50" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2015-06-21 13:59:46" + }, + { + "name": "symfony/yaml", + "version": "v2.7.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/Yaml.git", + "reference": "71340e996171474a53f3d29111d046be4ad8a0ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/71340e996171474a53f3d29111d046be4ad8a0ff", + "reference": "71340e996171474a53f3d29111d046be4ad8a0ff", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2015-07-28 14:07:07" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.0" + }, + "platform-dev": [] +} diff --git a/vendor/marc1706/fast-image-size/lib/FastImageSize.php b/vendor/marc1706/fast-image-size/lib/FastImageSize.php new file mode 100644 index 0000000..f904433 --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/FastImageSize.php @@ -0,0 +1,228 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize; + +class FastImageSize +{ + /** @var array Size info that is returned */ + protected $size = array(); + + /** @var string Data retrieved from remote */ + protected $data = ''; + + /** @var array List of supported image types and associated image types */ + protected $supportedTypes = array( + 'png' => array('png'), + 'gif' => array('gif'), + 'jpeg' => array( + 'jpeg', + 'jpg', + 'jpe', + 'jif', + 'jfif', + 'jfi', + ), + 'jp2' => array( + 'jp2', + 'j2k', + 'jpf', + 'jpg2', + 'jpx', + 'jpm', + ), + 'psd' => array( + 'psd', + 'photoshop', + ), + 'bmp' => array('bmp'), + 'tif' => array( + 'tif', + 'tiff', + ), + 'wbmp' => array( + 'wbm', + 'wbmp', + 'vnd.wap.wbmp', + ), + 'iff' => array( + 'iff', + 'x-iff', + ), + 'ico' => array( + 'ico', + 'vnd.microsoft.icon', + 'x-icon', + 'icon', + ), + 'webp' => array( + 'webp', + ) + ); + + /** @var array Class map that links image extensions/mime types to class */ + protected $classMap; + + /** @var array An array containing the classes of supported image types */ + protected $type; + + /** + * Constructor for fastImageSize class + */ + public function __construct() + { + foreach ($this->supportedTypes as $imageType => $extension) + { + $className = '\FastImageSize\Type\Type' . mb_convert_case(mb_strtolower($imageType), MB_CASE_TITLE); + $this->type[$imageType] = new $className($this); + + // Create class map + foreach ($extension as $ext) + { + /** @var Type\TypeInterface */ + $this->classMap[$ext] = $this->type[$imageType]; + } + } + } + + /** + * Get image dimensions of supplied image + * + * @param string $file Path to image that should be checked + * @param string $type Mimetype of image + * @return array|bool Array with image dimensions if successful, false if not + */ + public function getImageSize($file, $type = '') + { + // Reset values + $this->resetValues(); + + // Treat image type as unknown if extension or mime type is unknown + if (!preg_match('/\.([a-z0-9]+)$/i', $file, $match) && empty($type)) + { + $this->getImagesizeUnknownType($file); + } + else + { + $extension = (empty($type) && isset($match[1])) ? $match[1] : preg_replace('/.+\/([a-z0-9-.]+)$/i', '$1', $type); + + $this->getImageSizeByExtension($file, $extension); + } + + return sizeof($this->size) > 1 ? $this->size : false; + } + + /** + * Get dimensions of image if type is unknown + * + * @param string $filename Path to file + */ + protected function getImagesizeUnknownType($filename) + { + // Grab the maximum amount of bytes we might need + $data = $this->getImage($filename, 0, Type\TypeJpeg::JPEG_MAX_HEADER_SIZE, false); + + if ($data !== false) + { + foreach ($this->type as $imageType) + { + $imageType->getSize($filename); + + if (sizeof($this->size) > 1) + { + break; + } + } + } + } + + /** + * Get image size by file extension + * + * @param string $file Path to image that should be checked + * @param string $extension Extension/type of image + */ + protected function getImageSizeByExtension($file, $extension) + { + $extension = strtolower($extension); + if (isset($this->classMap[$extension])) + { + $this->classMap[$extension]->getSize($file); + } + } + + /** + * Reset values to default + */ + protected function resetValues() + { + $this->size = array(); + $this->data = ''; + } + + /** + * Set mime type based on supplied image + * + * @param int $type Type of image + */ + public function setImageType($type) + { + $this->size['type'] = $type; + } + + /** + * Set size info + * + * @param array $size Array containing size info for image + */ + public function setSize($size) + { + $this->size = $size; + } + + /** + * Get image from specified path/source + * + * @param string $filename Path to image + * @param int $offset Offset at which reading of the image should start + * @param int $length Maximum length that should be read + * @param bool $forceLength True if the length needs to be the specified + * length, false if not. Default: true + * + * @return false|string Image data or false if result was empty + */ + public function getImage($filename, $offset, $length, $forceLength = true) + { + if (empty($this->data)) + { + $this->data = @file_get_contents($filename, null, null, $offset, $length); + } + + // Force length to expected one. Return false if data length + // is smaller than expected length + if ($forceLength === true) + { + return (strlen($this->data) < $length) ? false : substr($this->data, $offset, $length) ; + } + + return empty($this->data) ? false : $this->data; + } + + /** + * Get return data + * + * @return array|bool Size array if dimensions could be found, false if not + */ + protected function getReturnData() + { + return sizeof($this->size) > 1 ? $this->size : false; + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeBase.php b/vendor/marc1706/fast-image-size/lib/Type/TypeBase.php new file mode 100644 index 0000000..11b44df --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeBase.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +use \FastImageSize\FastImageSize; + +abstract class TypeBase implements TypeInterface +{ + /** @var FastImageSize */ + protected $fastImageSize; + + /** + * Base constructor for image types + * + * @param FastImageSize $fastImageSize + */ + public function __construct(FastImageSize $fastImageSize) + { + $this->fastImageSize = $fastImageSize; + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeBmp.php b/vendor/marc1706/fast-image-size/lib/Type/TypeBmp.php new file mode 100644 index 0000000..26a7238 --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeBmp.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypeBmp extends TypeBase +{ + /** @var int BMP header size needed for retrieving dimensions */ + const BMP_HEADER_SIZE = 26; + + /** @var string BMP signature */ + const BMP_SIGNATURE = "\x42\x4D"; + + /** qvar int BMP dimensions offset */ + const BMP_DIMENSIONS_OFFSET = 18; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + $data = $this->fastImageSize->getImage($filename, 0, self::BMP_HEADER_SIZE); + + // Check if supplied file is a BMP file + if (substr($data, 0, 2) !== self::BMP_SIGNATURE) + { + return; + } + + $size = unpack('lwidth/lheight', substr($data, self::BMP_DIMENSIONS_OFFSET, 2 * self::LONG_SIZE)); + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_BMP); + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeGif.php b/vendor/marc1706/fast-image-size/lib/Type/TypeGif.php new file mode 100644 index 0000000..d2f74a0 --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeGif.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypeGif extends TypeBase +{ + /** @var string GIF87a header */ + const GIF87A_HEADER = "\x47\x49\x46\x38\x37\x61"; + + /** @var string GIF89a header */ + const GIF89A_HEADER = "\x47\x49\x46\x38\x39\x61"; + + /** @var int GIF header size */ + const GIF_HEADER_SIZE = 6; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + // Get data needed for reading image dimensions as outlined by GIF87a + // and GIF89a specifications + $data = $this->fastImageSize->getImage($filename, 0, self::GIF_HEADER_SIZE + self::SHORT_SIZE * 2); + + $type = substr($data, 0, self::GIF_HEADER_SIZE); + if ($type !== self::GIF87A_HEADER && $type !== self::GIF89A_HEADER) + { + return; + } + + $size = unpack('vwidth/vheight', substr($data, self::GIF_HEADER_SIZE, self::SHORT_SIZE * 2)); + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_GIF); + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeIco.php b/vendor/marc1706/fast-image-size/lib/Type/TypeIco.php new file mode 100644 index 0000000..663b50e --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeIco.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypeIco extends TypeBase +{ + /** @var string ICO reserved field */ + const ICO_RESERVED = 0; + + /** @var int ICO type field */ + const ICO_TYPE = 1; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + // Retrieve image data for ICO header and header of first entry. + // We assume the first entry to have the same size as the other ones. + $data = $this->fastImageSize->getImage($filename, 0, 2 * self::LONG_SIZE); + + if ($data === false) + { + return; + } + + // Check if header fits expected format + if (!$this->isValidIco($data)) + { + return; + } + + $size = unpack('Cwidth/Cheight', substr($data, self::LONG_SIZE + self::SHORT_SIZE, self::SHORT_SIZE)); + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_ICO); + } + + /** + * Return whether image is a valid ICO file + * + * @param string $data Image data string + * + * @return bool True if file is a valid ICO file, false if not + */ + protected function isValidIco($data) + { + // Get header + $header = unpack('vreserved/vtype/vimages', $data); + + return $header['reserved'] === self::ICO_RESERVED && $header['type'] === self::ICO_TYPE && $header['images'] > 0 && $header['images'] <= 255; + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeIff.php b/vendor/marc1706/fast-image-size/lib/Type/TypeIff.php new file mode 100644 index 0000000..bfdc0d4 --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeIff.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypeIff extends TypeBase +{ + /** @var int IFF header size. Grab more than what should be needed to make + * sure we have the necessary data */ + const IFF_HEADER_SIZE = 32; + + /** @var string IFF header for Amiga type */ + const IFF_HEADER_AMIGA = 'FORM'; + + /** @var string IFF header for Maya type */ + const IFF_HEADER_MAYA = 'FOR4'; + + /** @var string IFF BTMHD for Amiga type */ + const IFF_AMIGA_BTMHD = 'BMHD'; + + /** @var string IFF BTMHD for Maya type */ + const IFF_MAYA_BTMHD = 'BHD'; + + /** @var string PHP pack format for unsigned short */ + const PACK_UNSIGNED_SHORT = 'n'; + + /** @var string PHP pack format for unsigned long */ + const PACK_UNSIGNED_LONG = 'N'; + + /** @var string BTMHD of current image */ + protected $btmhd; + + /** @var int Size of current BTMHD */ + protected $btmhdSize; + + /** @var string Current byte type */ + protected $byteType; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + $data = $this->fastImageSize->getImage($filename, 0, self::IFF_HEADER_SIZE); + + $signature = $this->getIffSignature($data); + + // Check if image is IFF + if ($signature === false) + { + return; + } + + // Set type constraints + $this->setTypeConstraints($signature); + + // Get size from data + $btmhdPosition = strpos($data, $this->btmhd); + $size = unpack("{$this->byteType}width/{$this->byteType}height", substr($data, $btmhdPosition + self::LONG_SIZE + strlen($this->btmhd), $this->btmhdSize)); + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_IFF); + } + + /** + * Get IFF signature from data string + * + * @param string|bool $data Image data string + * + * @return false|string Signature if file is a valid IFF file, false if not + */ + protected function getIffSignature($data) + { + $signature = substr($data, 0, self::LONG_SIZE); + + // Check if image is IFF + if ($signature !== self::IFF_HEADER_AMIGA && $signature !== self::IFF_HEADER_MAYA) + { + return false; + } + else + { + return $signature; + } + } + + /** + * Set type constraints for current image + * + * @param string $signature IFF signature of image + */ + protected function setTypeConstraints($signature) + { + // Amiga version of IFF + if ($signature === 'FORM') + { + $this->btmhd = self::IFF_AMIGA_BTMHD; + $this->btmhdSize = self::LONG_SIZE; + $this->byteType = self::PACK_UNSIGNED_SHORT; + } + // Maya version + else + { + $this->btmhd = self::IFF_MAYA_BTMHD; + $this->btmhdSize = self::LONG_SIZE * 2; + $this->byteType = self::PACK_UNSIGNED_LONG; + } + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeInterface.php b/vendor/marc1706/fast-image-size/lib/Type/TypeInterface.php new file mode 100644 index 0000000..bcb23e2 --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +interface TypeInterface +{ + /** @var int 4-byte long size */ + const LONG_SIZE = 4; + + /** @var int 2-byte short size */ + const SHORT_SIZE = 2; + + /** + * Get size of supplied image + * + * @param string $filename File name of image + * + * @return null + */ + public function getSize($filename); +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeJp2.php b/vendor/marc1706/fast-image-size/lib/Type/TypeJp2.php new file mode 100644 index 0000000..4ccfffa --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeJp2.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypeJp2 extends TypeBase +{ + /** @var string JPEG 2000 signature */ + const JPEG_2000_SIGNATURE = "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A"; + + /** @var string JPEG 2000 SOC marker */ + const JPEG_2000_SOC_MARKER = "\xFF\x4F"; + + /** @var string JPEG 2000 SIZ marker */ + const JPEG_2000_SIZ_MARKER = "\xFF\x51"; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + $data = $this->fastImageSize->getImage($filename, 0, TypeJpeg::JPEG_MAX_HEADER_SIZE, false); + + // Check if file is jpeg 2000 + if (substr($data, 0, strlen(self::JPEG_2000_SIGNATURE)) !== self::JPEG_2000_SIGNATURE) + { + return; + } + + // Get SOC position before starting to search for SIZ. + // Make sure we do not get SIZ before SOC by cutting at SOC. + $data = substr($data, strpos($data, self::JPEG_2000_SOC_MARKER)); + + // Remove SIZ and everything before + $data = substr($data, strpos($data, self::JPEG_2000_SIZ_MARKER) + self::SHORT_SIZE); + + // Acquire size info from data + $size = unpack('Nwidth/Nheight', substr($data, self::LONG_SIZE, self::LONG_SIZE * 2)); + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_JPEG2000); + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeJpeg.php b/vendor/marc1706/fast-image-size/lib/Type/TypeJpeg.php new file mode 100644 index 0000000..9d7392c --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeJpeg.php @@ -0,0 +1,204 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypeJpeg extends TypeBase +{ + /** @var int JPEG max header size. Headers can be bigger, but we'll abort + * going through the header after this */ + const JPEG_MAX_HEADER_SIZE = 124576; + + /** @var string JPEG header */ + const JPEG_HEADER = "\xFF\xD8"; + + /** @var string Start of frame marker */ + const SOF_START_MARKER = "\xFF"; + + /** @var string End of image (EOI) marker */ + const JPEG_EOI_MARKER = "\xD9"; + + /** @var array JPEG SOF markers */ + protected $sofMarkers = array( + "\xC0", + "\xC1", + "\xC2", + "\xC3", + "\xC5", + "\xC6", + "\xC7", + "\xC9", + "\xCA", + "\xCB", + "\xCD", + "\xCE", + "\xCF" + ); + + /** @var array JPEG APP markers */ + protected $appMarkers = array( + "\xE0", + "\xE1", + "\xE2", + "\xE3", + "\xEC", + "\xED", + "\xEE", + ); + + /** @var string|bool JPEG data stream */ + protected $data = ''; + + /** @var int Data length */ + protected $dataLength = 0; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + // Do not force the data length + $this->data = $this->fastImageSize->getImage($filename, 0, self::JPEG_MAX_HEADER_SIZE, false); + + // Check if file is jpeg + if ($this->data === false || substr($this->data, 0, self::SHORT_SIZE) !== self::JPEG_HEADER) + { + return; + } + + // Look through file for SOF marker + $size = $this->getSizeInfo(); + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_JPEG); + } + + /** + * Get size info from image data + * + * @return array An array with the image's size info or an empty array if + * size info couldn't be found + */ + protected function getSizeInfo() + { + $size = array(); + // since we check $i + 1 we need to stop one step earlier + $this->dataLength = strlen($this->data) - 1; + + $sofStartRead = true; + + // Look through file for SOF marker + for ($i = 2; $i < $this->dataLength; $i++) + { + $marker = $this->getNextMarker($i, $sofStartRead); + + if (in_array($marker, $this->sofMarkers)) + { + // Extract size info from SOF marker + return $this->extractSizeInfo($i); + } + else + { + // Extract length only + $markerLength = $this->extractMarkerLength($i); + + if ($markerLength < 2) + { + return $size; + } + + $i += $markerLength - 1; + continue; + } + } + + return $size; + } + + /** + * Extract marker length from data + * + * @param int $i Current index + * @return int Length of current marker + */ + protected function extractMarkerLength($i) + { + // Extract length only + list(, $unpacked) = unpack("H*", substr($this->data, $i, self::LONG_SIZE)); + + // Get width and height from unpacked size info + $markerLength = hexdec(substr($unpacked, 0, 4)); + + return $markerLength; + } + + /** + * Extract size info from data + * + * @param int $i Current index + * @return array Size info of current marker + */ + protected function extractSizeInfo($i) + { + // Extract size info from SOF marker + list(, $unpacked) = unpack("H*", substr($this->data, $i - 1 + self::LONG_SIZE, self::LONG_SIZE)); + + // Get width and height from unpacked size info + $size = array( + 'width' => hexdec(substr($unpacked, 4, 4)), + 'height' => hexdec(substr($unpacked, 0, 4)), + ); + + return $size; + } + + /** + * Get next JPEG marker in file + * + * @param int $i Current index + * @param bool $sofStartRead Flag whether SOF start padding was already read + * + * @return string Next JPEG marker in file + */ + protected function getNextMarker(&$i, &$sofStartRead) + { + $this->skipStartPadding($i, $sofStartRead); + + do { + if ($i >= $this->dataLength) + { + return self::JPEG_EOI_MARKER; + } + $marker = $this->data[$i]; + $i++; + } while ($marker == self::SOF_START_MARKER); + + return $marker; + } + + /** + * Skip over any possible padding until we reach a byte without SOF start + * marker. Extraneous bytes might need to require proper treating. + * + * @param int $i Current index + * @param bool $sofStartRead Flag whether SOF start padding was already read + */ + protected function skipStartPadding(&$i, &$sofStartRead) + { + if (!$sofStartRead) + { + while ($this->data[$i] !== self::SOF_START_MARKER) + { + $i++; + } + } + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypePng.php b/vendor/marc1706/fast-image-size/lib/Type/TypePng.php new file mode 100644 index 0000000..b089a8b --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypePng.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypePng extends TypeBase +{ + /** @var string PNG header */ + const PNG_HEADER = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; + + /** @var int PNG IHDR offset */ + const PNG_IHDR_OFFSET = 12; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + // Retrieve image data including the header, the IHDR tag, and the + // following 2 chunks for the image width and height + $data = $this->fastImageSize->getImage($filename, 0, self::PNG_IHDR_OFFSET + 3 * self::LONG_SIZE); + + // Check if header fits expected format specified by RFC 2083 + if (substr($data, 0, self::PNG_IHDR_OFFSET - self::LONG_SIZE) !== self::PNG_HEADER || substr($data, self::PNG_IHDR_OFFSET, self::LONG_SIZE) !== 'IHDR') + { + return; + } + + $size = unpack('Nwidth/Nheight', substr($data, self::PNG_IHDR_OFFSET + self::LONG_SIZE, self::LONG_SIZE * 2)); + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_PNG); + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypePsd.php b/vendor/marc1706/fast-image-size/lib/Type/TypePsd.php new file mode 100644 index 0000000..85407f9 --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypePsd.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypePsd extends TypeBase +{ + /** @var string PSD signature */ + const PSD_SIGNATURE = "8BPS"; + + /** @var int PSD header size */ + const PSD_HEADER_SIZE = 22; + + /** @var int PSD dimensions info offset */ + const PSD_DIMENSIONS_OFFSET = 14; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + $data = $this->fastImageSize->getImage($filename, 0, self::PSD_HEADER_SIZE); + + if ($data === false) + { + return; + } + + // Offset for version info is length of header but version is only a + // 16-bit unsigned value + $version = unpack('n', substr($data, self::LONG_SIZE, 2)); + + // Check if supplied file is a PSD file + if (!$this->validPsd($data, $version)) + { + return; + } + + $size = unpack('Nheight/Nwidth', substr($data, self::PSD_DIMENSIONS_OFFSET, 2 * self::LONG_SIZE)); + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_PSD); + } + + /** + * Return whether file is a valid PSD file + * + * @param string $data Image data string + * @param array $version Version array + * + * @return bool True if image is a valid PSD file, false if not + */ + protected function validPsd($data, $version) + { + return substr($data, 0, self::LONG_SIZE) === self::PSD_SIGNATURE && $version[1] === 1; + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeTif.php b/vendor/marc1706/fast-image-size/lib/Type/TypeTif.php new file mode 100644 index 0000000..0e5c301 --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeTif.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypeTif extends TypeBase +{ + /** @var int TIF header size. The header might be larger but the dimensions + * should be in the first 51200 bytes */ + const TIF_HEADER_SIZE = 51200; + + /** @var int TIF tag for image height */ + const TIF_TAG_IMAGE_HEIGHT = 257; + + /** @var int TIF tag for image width */ + const TIF_TAG_IMAGE_WIDTH = 256; + + /** @var int TIF tag for exif IFD offset */ + const TIF_TAG_EXIF_OFFSET = 34665; + + /** @var int TIF tag for Image X resolution in pixels */ + const TIF_TAG_EXIF_IMAGE_WIDTH = 0xA002; + + /** @var int TIF tag for Image Y resolution in pixels */ + const TIF_TAG_EXIF_IMAGE_HEIGHT = 0xA003; + + /** @var int TIF tag type for short */ + const TIF_TAG_TYPE_SHORT = 3; + + /** @var int TIF IFD entry size */ + const TIF_IFD_ENTRY_SIZE = 12; + + /** @var string TIF signature of intel type */ + const TIF_SIGNATURE_INTEL = 'II'; + + /** @var string TIF signature of motorola type */ + const TIF_SIGNATURE_MOTOROLA = 'MM'; + + /** @var array Size info array */ + protected $size; + + /** @var string Bit type of long field */ + public $typeLong; + + /** @var string Bit type of short field */ + public $typeShort; + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + // Do not force length of header + $data = $this->fastImageSize->getImage($filename, 0, self::TIF_HEADER_SIZE, false); + + $this->size = array(); + + $signature = substr($data, 0, self::SHORT_SIZE); + + if (!in_array($signature, array(self::TIF_SIGNATURE_INTEL, self::TIF_SIGNATURE_MOTOROLA))) + { + return; + } + + // Set byte type + $this->setByteType($signature); + + // Get offset of IFD + list(, $offset) = unpack($this->typeLong, substr($data, self::LONG_SIZE, self::LONG_SIZE)); + + // Get size of IFD + list(, $sizeIfd) = unpack($this->typeShort, substr($data, $offset, self::SHORT_SIZE)); + + // Skip 2 bytes that define the IFD size + $offset += self::SHORT_SIZE; + + // Ensure size can't exceed data length + $sizeIfd = min($sizeIfd, floor((strlen($data) - $offset) / self::TIF_IFD_ENTRY_SIZE)); + + // Filter through IFD + for ($i = 0; $i < $sizeIfd; $i++) + { + // Get IFD tag + $type = unpack($this->typeShort, substr($data, $offset, self::SHORT_SIZE)); + + // Get field type of tag + $fieldType = unpack($this->typeShort . 'type', substr($data, $offset + self::SHORT_SIZE, self::SHORT_SIZE)); + + // Get IFD entry + $ifdValue = substr($data, $offset + 2 * self::LONG_SIZE, self::LONG_SIZE); + + // Set size of field + $this->setSizeInfo($type[1], $fieldType['type'], $ifdValue); + + $offset += self::TIF_IFD_ENTRY_SIZE; + } + + $this->fastImageSize->setSize($this->size); + } + + /** + * Set byte type based on signature in header + * + * @param string $signature Header signature + */ + public function setByteType($signature) + { + if ($signature === self::TIF_SIGNATURE_INTEL) + { + $this->typeLong = 'V'; + $this->typeShort = 'v'; + $this->size['type'] = IMAGETYPE_TIFF_II; + } + else + { + $this->typeLong = 'N'; + $this->typeShort = 'n'; + $this->size['type'] = IMAGETYPE_TIFF_MM; + } + } + + /** + * Set size info + * + * @param int $dimensionType Type of dimension. Either width or height + * @param int $fieldLength Length of field. Either short or long + * @param string $ifdValue String value of IFD field + */ + protected function setSizeInfo($dimensionType, $fieldLength, $ifdValue) + { + // Set size of field + $fieldSize = $fieldLength === self::TIF_TAG_TYPE_SHORT ? $this->typeShort : $this->typeLong; + + // Get actual dimensions from IFD + if ($dimensionType === self::TIF_TAG_IMAGE_HEIGHT) + { + $this->size = array_merge($this->size, unpack($fieldSize . 'height', $ifdValue)); + } + else if ($dimensionType === self::TIF_TAG_IMAGE_WIDTH) + { + $this->size = array_merge($this->size, unpack($fieldSize . 'width', $ifdValue)); + } + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeWbmp.php b/vendor/marc1706/fast-image-size/lib/Type/TypeWbmp.php new file mode 100644 index 0000000..f1e0f2c --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeWbmp.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +class TypeWbmp extends TypeBase +{ + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + $data = $this->fastImageSize->getImage($filename, 0, self::LONG_SIZE); + + // Check if image is WBMP + if ($data === false || !$this->validWBMP($data)) + { + return; + } + + $size = unpack('Cwidth/Cheight', substr($data, self::SHORT_SIZE, self::SHORT_SIZE)); + + // Check if dimensions are valid. A file might be recognised as WBMP + // rather easily (see extra check for JPEG2000). + if (!$this->validDimensions($size)) + { + return; + } + + $this->fastImageSize->setSize($size); + $this->fastImageSize->setImageType(IMAGETYPE_WBMP); + } + + /** + * Return if supplied data might be part of a valid WBMP file + * + * @param bool|string $data + * + * @return bool True if data might be part of a valid WBMP file, else false + */ + protected function validWBMP($data) + { + return ord($data[0]) === 0 && ord($data[1]) === 0 && $data !== substr(TypeJp2::JPEG_2000_SIGNATURE, 0, self::LONG_SIZE); + } + + /** + * Return whether dimensions are valid + * + * @param array $size Size array + * + * @return bool True if dimensions are valid, false if not + */ + protected function validDimensions($size) + { + return $size['height'] > 0 && $size['width'] > 0; + } +} diff --git a/vendor/marc1706/fast-image-size/lib/Type/TypeWebp.php b/vendor/marc1706/fast-image-size/lib/Type/TypeWebp.php new file mode 100644 index 0000000..a82e7f9 --- /dev/null +++ b/vendor/marc1706/fast-image-size/lib/Type/TypeWebp.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FastImageSize\Type; + +use \FastImageSize\FastImageSize; + +class TypeWebp extends TypeBase +{ + /** @var string RIFF header */ + const WEBP_RIFF_HEADER = "RIFF"; + + /** @var string Webp header */ + const WEBP_HEADER = "WEBP"; + + /** @var string VP8 chunk header */ + const VP8_HEADER = "VP8"; + + /** @var string Simple(lossy) webp format */ + const WEBP_FORMAT_SIMPLE = ' '; + + /** @var string Lossless webp format */ + const WEBP_FORMAT_LOSSLESS = 'L'; + + /** @var string Extended webp format */ + const WEBP_FORMAT_EXTENDED = 'X'; + + /** @var int WEBP header size needed for retrieving image size */ + const WEBP_HEADER_SIZE = 30; + + /** @var array Size info array */ + protected $size; + + /** + * Constructor for webp image type. Adds missing constant if necessary. + * + * @param FastImageSize $fastImageSize + */ + public function __construct(FastImageSize $fastImageSize) + { + parent::__construct($fastImageSize); + + if (!defined('IMAGETYPE_WEBP')) + { + define('IMAGETYPE_WEBP', 18); + } + } + + /** + * {@inheritdoc} + */ + public function getSize($filename) + { + // Do not force length of header + $data = $this->fastImageSize->getImage($filename, 0, self::WEBP_HEADER_SIZE); + + $this->size = array(); + + $webpFormat = substr($data, 15, 1); + + if (!$this->hasWebpHeader($data) || !$this->isValidFormat($webpFormat)) + { + return; + } + + $data = substr($data, 16, 14); + + $this->getWebpSize($data, $webpFormat); + + $this->fastImageSize->setSize($this->size); + $this->fastImageSize->setImageType(IMAGETYPE_WEBP); + } + + /** + * Check if $data has valid WebP header + * + * @param string $data Image data + * + * @return bool True if $data has valid WebP header, false if not + */ + protected function hasWebpHeader($data) + { + $riffSignature = substr($data, 0, self::LONG_SIZE); + $webpSignature = substr($data, 8, self::LONG_SIZE); + $vp8Signature = substr($data, 12, self::SHORT_SIZE + 1); + + return !empty($data) && $riffSignature === self::WEBP_RIFF_HEADER && + $webpSignature === self::WEBP_HEADER && $vp8Signature === self::VP8_HEADER; + } + + /** + * Check if $format is a valid WebP format + * + * @param string $format Format string + * @return bool True if format is valid WebP format, false if not + */ + protected function isValidFormat($format) + { + return in_array($format, array(self::WEBP_FORMAT_SIMPLE, self::WEBP_FORMAT_LOSSLESS, self::WEBP_FORMAT_EXTENDED)); + } + + /** + * Get webp size info depending on format type and set size array values + * + * @param string $data Data string + * @param string $format Format string + */ + protected function getWebpSize($data, $format) + { + switch ($format) + { + case self::WEBP_FORMAT_SIMPLE: + $this->size = unpack('vwidth/vheight', substr($data, 10, 4)); + break; + + case self::WEBP_FORMAT_LOSSLESS: + // Lossless uses 14-bit values so we'll have to use bitwise shifting + $this->size = array( + 'width' => ord($data[5]) + ((ord($data[6]) & 0x3F) << 8) + 1, + 'height' => (ord($data[6]) >> 6) + (ord($data[7]) << 2) + ((ord($data[8]) & 0xF) << 10) + 1, + ); + break; + + case self::WEBP_FORMAT_EXTENDED: + // Extended uses 24-bit values cause 14-bit for lossless wasn't weird enough + $this->size = array( + 'width' => ord($data[8]) + (ord($data[9]) << 8) + (ord($data[10]) << 16) + 1, + 'height' => ord($data[11]) + (ord($data[12]) << 8) + (ord($data[13]) << 16) + 1, + ); + break; + } + } +} diff --git a/vendor/ocramius/proxy-manager/.gitignore b/vendor/ocramius/proxy-manager/.gitignore new file mode 100644 index 0000000..7cf18fe --- /dev/null +++ b/vendor/ocramius/proxy-manager/.gitignore @@ -0,0 +1,7 @@ +vendor +composer.lock +composer.phar +phpunit.xml +phpmd.xml +phpdox.xml +clover.xml diff --git a/vendor/ocramius/proxy-manager/.scrutinizer.yml b/vendor/ocramius/proxy-manager/.scrutinizer.yml new file mode 100644 index 0000000..dbaa05c --- /dev/null +++ b/vendor/ocramius/proxy-manager/.scrutinizer.yml @@ -0,0 +1,45 @@ +before_commands: + - "composer install --no-dev --prefer-source" + +tools: + external_code_coverage: + timeout: 600 + php_code_coverage: + enabled: true + php_code_sniffer: + enabled: true + config: + standard: PSR2 + filter: + paths: ["src/*", "tests/*"] + php_cpd: + enabled: true + excluded_dirs: ["docs", "examples", "tests", "vendor"] + php_cs_fixer: + enabled: true + config: + level: all + filter: + paths: ["src/*", "tests/*"] + php_loc: + enabled: true + excluded_dirs: ["docs", "examples", "tests", "vendor"] + php_mess_detector: + enabled: true + config: + ruleset: phpmd.xml.dist + design_rules: { eval_expression: false } + filter: + paths: ["src/*"] + php_pdepend: + enabled: true + excluded_dirs: ["docs", "examples", "tests", "vendor"] + php_analyzer: + enabled: true + filter: + paths: ["src/*", "tests/*"] + php_hhvm: + enabled: true + filter: + paths: ["src/*", "tests/*"] + sensiolabs_security_checker: true diff --git a/vendor/ocramius/proxy-manager/.travis.install.sh b/vendor/ocramius/proxy-manager/.travis.install.sh new file mode 100644 index 0000000..373c5ef --- /dev/null +++ b/vendor/ocramius/proxy-manager/.travis.install.sh @@ -0,0 +1,11 @@ +set -x +if [ "$TRAVIS_PHP_VERSION" = 'hhvm' ] || [ "$TRAVIS_PHP_VERSION" = 'hhvm-nightly' ] ; then + curl -sS https://getcomposer.org/installer > composer-installer.php + hhvm composer-installer.php + hhvm -v ResourceLimit.SocketDefaultTimeout=30 -v Http.SlowQueryThreshold=30000 composer.phar update --prefer-source + hhvm -v ResourceLimit.SocketDefaultTimeout=30 -v Http.SlowQueryThreshold=30000 composer.phar install --dev --prefer-source +else + composer self-update + composer update --prefer-source + composer install --dev --prefer-source +fi diff --git a/vendor/ocramius/proxy-manager/.travis.yml b/vendor/ocramius/proxy-manager/.travis.yml new file mode 100644 index 0000000..5f42dac --- /dev/null +++ b/vendor/ocramius/proxy-manager/.travis.yml @@ -0,0 +1,28 @@ +language: php + +php: + - 5.3.3 + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm + - hhvm-nightly + +before_script: + - sh .travis.install.sh + +script: + - ./vendor/bin/phpunit --disallow-test-output --report-useless-tests --coverage-clover ./clover.xml --group=Coverage + - ./vendor/bin/phpunit --disallow-test-output --report-useless-tests --strict --exclude-group=Performance,Coverage + - php -n ./vendor/bin/phpunit --group=Performance + - ./vendor/bin/phpcs --standard=PSR2 ./src/ ./tests/ + +matrix: + allow_failures: + - php: hhvm + - php: hhvm-nightly + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover ./clover.xml diff --git a/vendor/ocramius/proxy-manager/CONTRIBUTING.md b/vendor/ocramius/proxy-manager/CONTRIBUTING.md new file mode 100644 index 0000000..d07d8dc --- /dev/null +++ b/vendor/ocramius/proxy-manager/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing + + * Coding standard for the project is [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) + * The project will follow strict [object calisthenics](http://www.slideshare.net/guilhermeblanco/object-calisthenics-applied-to-php) + * Any contribution must provide tests for additional introduced conditions + * Any un-confirmed issue needs a failing test case before being accepted + * Pull requests must be sent from a new hotfix/feature branch, not from `master`. + +## Installation + +To install the project and run the tests, you need to clone it first: + +```sh +$ git clone git://github.com/Ocramius/ProxyManager.git +``` + +You will then need to run a composer installation: + +```sh +$ cd ProxyManager +$ curl -s https://getcomposer.org/installer | php +$ php composer.phar update +``` + +## Testing + +The PHPUnit version to be used is the one installed as a dev- dependency via composer: + +```sh +$ ./vendor/bin/phpunit +``` + +Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement +won't be merged. + diff --git a/vendor/ocramius/proxy-manager/LICENSE b/vendor/ocramius/proxy-manager/LICENSE new file mode 100644 index 0000000..d7f7223 --- /dev/null +++ b/vendor/ocramius/proxy-manager/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Marco Pivetta + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/ocramius/proxy-manager/README.md b/vendor/ocramius/proxy-manager/README.md new file mode 100644 index 0000000..c377830 --- /dev/null +++ b/vendor/ocramius/proxy-manager/README.md @@ -0,0 +1,50 @@ +# Proxy Manager + +This library aims at providing abstraction for generating various kinds of [proxy classes](http://marco-pivetta.com/proxy-pattern-in-php/). + +![ProxyManager](proxy-manager.png) + +[![Build Status](https://travis-ci.org/Ocramius/ProxyManager.png?branch=master)](https://travis-ci.org/Ocramius/ProxyManager) +[![Code Coverage](https://scrutinizer-ci.com/g/Ocramius/ProxyManager/badges/coverage.png?s=ca3b9ceb9e36aeec0e57569cc8983394b7d2a59e)](https://scrutinizer-ci.com/g/Ocramius/ProxyManager/) +[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/Ocramius/ProxyManager/badges/quality-score.png?s=eaa858f876137ed281141b1d1e98acfa739729ed)](https://scrutinizer-ci.com/g/Ocramius/ProxyManager/) +[![SensioLabsInsight](https://insight.sensiolabs.com/projects/69fe5f97-b1c8-4ddd-93ce-900b8b788cf2/mini.png)](https://insight.sensiolabs.com/projects/69fe5f97-b1c8-4ddd-93ce-900b8b788cf2) +[![Dependency Status](https://www.versioneye.com/package/php--ocramius--proxy-manager/badge.png)](https://www.versioneye.com/package/php--ocramius--proxy-manager) +[![Reference Status](https://www.versioneye.com/php/ocramius:proxy-manager/reference_badge.svg)](https://www.versioneye.com/php/ocramius:proxy-manager/references) +[![HHVM Status](http://hhvm.h4cc.de/badge/ocramius/proxy-manager.png)](http://hhvm.h4cc.de/package/ocramius/proxy-manager) + +[![Total Downloads](https://poser.pugx.org/ocramius/proxy-manager/downloads.png)](https://packagist.org/packages/ocramius/proxy-manager) +[![Latest Stable Version](https://poser.pugx.org/ocramius/proxy-manager/v/stable.png)](https://packagist.org/packages/ocramius/proxy-manager) +[![Latest Unstable Version](https://poser.pugx.org/ocramius/proxy-manager/v/unstable.png)](https://packagist.org/packages/ocramius/proxy-manager) + + +## Documentation + +You can learn about the proxy pattern and how to use the **ProxyManager** on the [online documentation](http://ocramius.github.io/ProxyManager). + +## Installation + +The suggested installation method is via [composer](https://getcomposer.org/): + +```sh +php composer.phar require ocramius/proxy-manager:1.0.* +``` + +## Proxy example + +Here's how you build a lazy loadable object with ProxyManager using a *Virtual Proxy* + +```php +$factory = new \ProxyManager\Factory\LazyLoadingValueHolderFactory(); + +$proxy = $factory->createProxy( + 'MyApp\HeavyComplexObject', + function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) { + $wrappedObject = new HeavyComplexObject(); // instantiation logic here + $initializer = null; // turning off further lazy initialization + } +); + +$proxy->doFoo(); +``` + +See the [online documentation](http://ocramius.github.io/ProxyManager) for more supported proxy types and examples. diff --git a/vendor/ocramius/proxy-manager/STABILITY.md b/vendor/ocramius/proxy-manager/STABILITY.md new file mode 100644 index 0000000..6da8cb6 --- /dev/null +++ b/vendor/ocramius/proxy-manager/STABILITY.md @@ -0,0 +1,19 @@ +This is a list of supported versions, with their expected release/support time-frames: + +# 0.5.x + + * Release date: 2013-12-01 + * Bug fixes: till 2015-03-11 + * Security fixes: till 2015-12-11 + +# 1.0.x + + * Release date: 2014-12-12 + * Bug fixes: till 2015-12-11 + * Security fixes: till 2016-12-11 + +# 2.0.x + + * Release date: TBA + * Bug fixes: TBA + * Security fixes: TBA diff --git a/vendor/ocramius/proxy-manager/UPGRADE.md b/vendor/ocramius/proxy-manager/UPGRADE.md new file mode 100644 index 0000000..d0de61d --- /dev/null +++ b/vendor/ocramius/proxy-manager/UPGRADE.md @@ -0,0 +1,69 @@ +This is a list of backwards compatibility (BC) breaks introduced in ProxyManager: + +# 0.5.0 + + * The Generated Hydrator has been removed - it is now available as a separate project + at [Ocramius/GeneratedHydrator](https://github.com/Ocramius/GeneratedHydrator) [#65](https://github.com/Ocramius/ProxyManager/pull/65) + * When having a `public function __get($name)` defined (by-val) and public properties, it won't be possible to get public + properties by-ref while initializing the object. Either drop `__get()` or implement + a by-ref `& __get()` [#126](https://github.com/Ocramius/ProxyManager/pull/126) + * Proxies are now being always auto-generated if they could not be autoloaded by a factory. The methods + [`ProxyManager\Configuration#setAutoGenerateProxies()`](https://github.com/Ocramius/ProxyManager/blob/0.5.0-BETA2/src/ProxyManager/Configuration.php#L67) + and [`ProxyManager\Configuration#doesAutoGenerateProxies()`](https://github.com/Ocramius/ProxyManager/blob/0.5.0-BETA2/src/ProxyManager/Configuration.php#L75) + are now no-op and deprecated, and will be removed in the next minor + version [#87](https://github.com/Ocramius/ProxyManager/pull/87) [#90](https://github.com/Ocramius/ProxyManager/pull/90) + * Proxy public properties defaults are now set before initialization [#116](https://github.com/Ocramius/ProxyManager/pull/116) [#122](https://github.com/Ocramius/ProxyManager/pull/122) + +# 0.4.0 + + * An optional parameter `$options` was introduced + in [`ProxyManager\Inflector\ClassNameInflectorInterface#getProxyClassName($className, array $options = array())`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Inflector/ClassNameInflectorInterface.php) + parametrize the generated class name as of [#10](https://github.com/Ocramius/ProxyManager/pull/10) + and [#59](https://github.com/Ocramius/ProxyManager/pull/59) + * Generated hydrators no longer have constructor arguments. Any required reflection instantiation is now dealt with + in the hydrator internally as of [#63](https://github.com/Ocramius/ProxyManager/pull/63) + +# 0.3.4 + + * Interface names are also supported for proxy generation as of [#40](https://github.com/Ocramius/ProxyManager/pull/40) + +# 0.3.3 + + * [Generated hydrators](https://github.com/Ocramius/ProxyManager/tree/master/docs/generated-hydrator.md) were introduced + +# 0.3.2 + + * An additional (optional) [by-ref parameter was added](https://github.com/Ocramius/ProxyManager/pull/31) + to the lazy loading proxies' initializer to allow unsetting the initializer with less overhead. + +# 0.3.0 + + * Dependency to [jms/cg](https://github.com/schmittjoh/cg-library) removed + * Moved code generation logic to [`Zend\Code`](https://github.com/zendframework/zf2) + * Added method [`ProxyManager\Inflector\ClassNameInflectorInterface#isProxyClassName($className)`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Inflector/ClassNameInflectorInterface.php) + * The constructor of [`ProxyManager\Autoloader\Autoloader`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Autoloader/Autoloader.php) + changed from `__construct(\ProxyManager\FileLocator\FileLocatorInterface $fileLocator)` to + `__construct(\ProxyManager\FileLocator\FileLocatorInterface $fileLocator, \ProxyManager\Inflector\ClassNameInflectorInterface $classNameInflector)` + * Classes implementing `CG\Core\GeneratorStrategyInterface` now implement + [`ProxyManager\GeneratorStrategy\GeneratorStrategyInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/GeneratorStrategy/GeneratorStrategyInterface.php) + instead + * All code generation logic has been replaced - If you wrote any logic based on `ProxyManager\ProxyGenerator`, you will + have to rewrite it + +# 0.2.0 + + * The signature of initializers to be used with proxies implementing + [`ProxyManager\Proxy\LazyLoadingInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Proxy/LazyLoadingInterface.php) + changed from: + + ```php + $initializer = function ($proxy, & $wrappedObject, $method, $parameters) {}; + ``` + + to + + ```php + $initializer = function (& $wrappedObject, $proxy, $method, $parameters) {}; + ``` + + Only the order of parameters passed to the closures has been changed. diff --git a/vendor/ocramius/proxy-manager/composer.json b/vendor/ocramius/proxy-manager/composer.json new file mode 100644 index 0000000..0c5d206 --- /dev/null +++ b/vendor/ocramius/proxy-manager/composer.json @@ -0,0 +1,53 @@ +{ + "name": "ocramius/proxy-manager", + "description": "A library providing utilities to generate, instantiate and generally operate with Object Proxies", + "type": "library", + "license": "MIT", + "homepage": "https://github.com/Ocramius/ProxyManager", + "keywords": [ + "proxy", + "proxy pattern", + "service proxies", + "lazy loading", + "aop" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "require": { + "php": ">=5.3.3", + "zendframework/zend-code": ">2.2.5,<3.0" + }, + "require-dev": { + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "1.5.*" + }, + "suggest": { + "zendframework/zend-stdlib": "To use the hydrator proxy", + "ocramius/generated-hydrator": "To have very fast object to array to object conversion for ghost objects", + "zendframework/zend-xmlrpc": "To have the XmlRpc adapter (Remote Object feature)", + "zendframework/zend-json": "To have the JsonRpc adapter (Remote Object feature)", + "zendframework/zend-soap": "To have the Soap adapter (Remote Object feature)" + }, + "autoload": { + "psr-0": { + "ProxyManager\\": "src" + } + }, + "autoload-dev": { + "psr-0": { + "ProxyManagerTest\\": "tests", + "ProxyManagerTestAsset\\": "tests" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/vendor/ocramius/proxy-manager/docs/access-interceptor-scope-localizer.md b/vendor/ocramius/proxy-manager/docs/access-interceptor-scope-localizer.md new file mode 100644 index 0000000..a03ac09 --- /dev/null +++ b/vendor/ocramius/proxy-manager/docs/access-interceptor-scope-localizer.md @@ -0,0 +1,105 @@ +# Access Interceptor Scope Localizer Proxy + +An access interceptor scope localizer is a smart reference proxy that allows you to dynamically +define logic to be executed before or after any of the proxied object's methods' logic. + +It works exactly like the [access interceptor value holder](access-interceptor-value-holder.md), +with some minor differences in behavior. + +The working concept of an access interceptor scope localizer is to localize scope of a proxied object: + +```php +class Example +{ + protected $foo; + protected $bar; + protected $baz; + + public function doFoo() + { + // ... + } +} + +class ExampleProxy extends Example +{ + public function __construct(Example $example) + { + $this->foo = & $example->foo; + $this->bar = & $example->bar; + $this->baz = & $example->baz; + } + + public function doFoo() + { + return parent::doFoo(); + } +} +``` + +This allows to create a mirror copy of the real instance, where any change in the proxy or in the real +instance is reflected in both objects. + +The main advantage of this approach is that the proxy is now safe against fluent interfaces, which +would break an [access interceptor value holder](access-interceptor-value-holder.md) instead. + +## Differences with [access interceptor value holder](access-interceptor-value-holder.md): + + * It does **NOT** implement the `ProxyManager\Proxy\ValueHolderInterface`, since the proxy itself + does not keep a reference to the original object being proxied + * In all interceptor methods (see [access interceptor value holder](access-interceptor-value-holder.md)), + the `$instance` passed in is the proxy itself. There is no way to gather a reference to the + original object right now, and that's mainly to protect from misuse. + +## Known limitations + + * It is **NOT** possible to intercept access to public properties + * It is **NOT** possible to proxy interfaces, since this proxy relies on `parent::method()` calls. + Interfaces obviously don't provide a parent method implementation. + * calling `unset` on a property of an access interceptor scope localizer (or the real instance) + will cause the two objects to be un-synchronized, with possible unexpected behaviour. + * serializing or un-serializing an access interceptor scope localizer (or the real instance) + will not cause the real instance (or the proxy) to be serialized or un-serialized + * if a proxied object contains private properties, then an exception will be thrown if you use + PHP `< 5.4.0`. + +## Example + +Here's an example of how you can create and use an access interceptor scope localizer : + +```php +createProxy( + new Foo(), + array('doFoo' => function () { echo "PreFoo!\n"; }), + array('doFoo' => function () { echo "PostFoo!\n"; }) +); + +$proxy->doFoo(); +``` + +This send something like following to your output: + +``` +PreFoo! +Foo! +PostFoo! +``` + +This is pretty much the same logic that you can find +in [access interceptor value holder](access-interceptor-value-holder.md). diff --git a/vendor/ocramius/proxy-manager/docs/access-interceptor-value-holder.md b/vendor/ocramius/proxy-manager/docs/access-interceptor-value-holder.md new file mode 100644 index 0000000..e47e941 --- /dev/null +++ b/vendor/ocramius/proxy-manager/docs/access-interceptor-value-holder.md @@ -0,0 +1,108 @@ +# Access Interceptor Value Holder Proxy + +An access interceptor value holder is a smart reference proxy that allows you to dynamically +define logic to be executed before or after any of the wrapped object's methods +logic. + +It wraps around a real instance of the object to be proxied, and can be useful for things like: + + * caching execution of slow and heavy methods + * log method calls + * debugging + * event triggering + * handling of orthogonal logic, and [AOP](http://en.wikipedia.org/wiki/Aspect-oriented_programming) in general + +## Example + +Here's an example of how you can create and use an access interceptor value holder: + +```php +createProxy( + new Foo(), + array('doFoo' => function () { echo "PreFoo!\n"; }), + array('doFoo' => function () { echo "PostFoo!\n"; }) +); + +$proxy->doFoo(); +``` + +This send something like following to your output: + +``` +PreFoo! +Foo! +PostFoo! +``` + +## Implementing pre- and post- access interceptors + +A proxy produced by the +[`ProxyManager\Factory\AccessInterceptorValueHolderFactory`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Factory/AccessInterceptorValueHolderFactory.php) +implements both the +[`ProxyManager\Proxy\ValueHolderInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Proxy/ValueHolderInterface.php) +and the +[`ProxyManager\Proxy\AccessInterceptorInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Proxy/ValueHolderInterface.php). + +Therefore, you can set an access interceptor callback by calling: + +```php +$proxy->setMethodPrefixInterceptor('methodName', function () { echo 'pre'; }); +$proxy->setMethodSuffixInterceptor('methodName', function () { echo 'post'; }); +``` + +You can also listen to public properties access by attaching interceptors to `__get`, `__set`, `__isset` and `__unset`. + +A prefix interceptor (executed before method logic) should have following signature: + +```php +/** + * @var object $proxy the proxy that intercepted the method call + * @var object $instance the wrapped instance within the proxy + * @var string $method name of the called method + * @var array $params sorted array of parameters passed to the intercepted + * method, indexed by parameter name + * @var bool $returnEarly flag to tell the interceptor proxy to return early, returning + * the interceptor's return value instead of executing the method logic + * + * @return mixed + */ +$prefixInterceptor = function ($proxy, $instance, $method, $params, & $returnEarly) {}; +``` + +A suffix interceptor (executed after method logic) should have following signature: + +```php +/** + * @var object $proxy the proxy that intercepted the method call + * @var object $instance the wrapped instance within the proxy + * @var string $method name of the called method + * @var array $params sorted array of parameters passed to the intercepted + * method, indexed by parameter name + * @var mixed $returnValue the return value of the intercepted method + * @var bool $returnEarly flag to tell the proxy to return early, returning the interceptor's + * return value instead of the value produced by the method + * + * @return mixed + */ +$suffixInterceptor = function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) {}; +``` + +## Tuning performance for production + +See [Tuning ProxyManager for Production](https://github.com/Ocramius/ProxyManager/blob/master/docs/tuning-for-production.md). \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/docs/generator-strategies.md b/vendor/ocramius/proxy-manager/docs/generator-strategies.md new file mode 100644 index 0000000..e4022d5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/docs/generator-strategies.md @@ -0,0 +1,16 @@ +# Generator strategies + +ProxyManager allows you to generate classes based on generator strategies and a +given `Zend\Code\Generator\ClassGenerator` as of +the [interface of a generator strategy](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/GeneratorStrategy/GeneratorStrategyInterface.php). + +Currently, 3 generator strategies are shipped with ProxyManager: + + * [`ProxyManager\GeneratorStrategy\BaseGeneratorStrategy`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/GeneratorStrategy/BaseGeneratorStrategy.php), + which simply retrieves the string representation of the class from `ClassGenerator` + * [`ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/GeneratorStrategy/EvaluatingGeneratorStrategy.php), + which calls `eval()` upon the generated class code before returning it. This is useful in cases + where you want to generate multiple classes at runtime + * [`ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/GeneratorStrategy/FileWriterGeneratorStrategy.php), + which accepts a [`ProxyManager\FileLocator\FileLocatorInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/FileLocator/FileLocatorInterface.php) + instance as constructor parameter, and based on it, writes the generated class to a file before returning its code. \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/docs/lazy-loading-ghost-object.md b/vendor/ocramius/proxy-manager/docs/lazy-loading-ghost-object.md new file mode 100644 index 0000000..15ae426 --- /dev/null +++ b/vendor/ocramius/proxy-manager/docs/lazy-loading-ghost-object.md @@ -0,0 +1,206 @@ +# Lazy Loading Ghost Object Proxies + +A lazy loading ghost object proxy is a ghost proxy that looks exactly like the real instance of the proxied subject, +but which has all properties nulled before initialization. + +## Lazy loading with the Ghost Object + +In pseudo-code, in userland, [lazy loading](http://www.martinfowler.com/eaaCatalog/lazyLoad.html) in a ghost object +looks like following: + +```php +class MyObjectProxy +{ + private $initialized = false; + private $name; + private $surname; + + public function doFoo() + { + $this->init(); + + // Perform doFoo routine using loaded variables + } + + private function init() + { + if (! $this->initialized) { + $data = some_logic_that_loads_data(); + + $this->name = $data['name']; + $this->surname = $data['surname']; + + $this->initialized = true; + } + } +} +``` + +Ghost objects work similarly to virtual proxies, but since they don't wrap around a "real" instance of the proxied +subject, they are better suited for representing dataset rows. + +## When do I use a ghost object? + +You usually need a ghost object in cases where following applies + + * you are building a small data-mapper and want to lazily load data across associations in your object graph + * you want to initialize objects representing rows in a large dataset + * you want to compare instances of lazily initialized objects without the risk of comparing a proxy with a real subject + * you are aware of the internal state of the object and are confident in working with its internals via reflection + or direct property access + +## Usage examples + +[ProxyManager](https://github.com/Ocramius/ProxyManager) provides a factory that creates lazy loading ghost objects. +To use it, follow these steps: + +First of all, define your object's logic without taking care of lazy loading: + +```php +namespace MyApp; + +class Customer +{ + private $name; + private $surname; + + // just write your business logic or generally logic + // don't worry about how complex this object will be! + // don't code lazy-loading oriented optimizations in here! + public function getName() { return $this->name; } + public function setName($name) { $this->name = (string) $name; } + public function getSurname() { return $this->surname; } + public function setSurname($surname) { $this->surname = (string) $surname; } +} +``` + +Then use the proxy manager to create a ghost object of it. +You will be responsible of setting its state during lazy loading: + +```php +namespace MyApp; + +use ProxyManager\Factory\LazyLoadingGhostFactory; +use ProxyManager\Proxy\LazyLoadingInterface; + +require_once __DIR__ . '/vendor/autoload.php'; + +$factory = new LazyLoadingGhostFactory(); +$initializer = function (LazyLoadingInterface $proxy, $method, array $parameters, & $initializer) { + $initializer = null; // disable initialization + + // load data and modify the object here + $proxy->setName('Agent'); + $proxy->setSurname('Smith'); + + return true; // confirm that initialization occurred correctly +}; + +$instance = $factory->createProxy('MyApp\Customer', $initializer); +``` + +You can now simply use your object as before: + +```php +// this will just work as before +echo $proxy->getName() . ' ' . $proxy->getSurname(); // Agent Smith +``` + +## Lazy Initialization + +As you can see, we use a closure to handle lazy initialization of the proxy instance at runtime. +The initializer closure signature for ghost objects should be as following: + +```php +/** + * @var object $proxy the instance the ghost object proxy that is being initialized + * @var string $method the name of the method that triggered lazy initialization + * @var array $parameters an ordered list of parameters passed to the method that + * triggered initialization, indexed by parameter name + * @var Closure $initializer a reference to the property that is the initializer for the + * proxy. Set it to null to disable further initialization + * + * @return bool true on success + */ +$initializer = function ($proxy, $method, $parameters, & $initializer) {}; +``` + +The initializer closure should usually be coded like following: + +```php +$initializer = function ($proxy, $method, $parameters, & $initializer) { + $initializer = null; // disable initializer for this proxy instance + + // modify the object with loaded data + $proxy->setFoo(/* ... */); + $proxy->setBar(/* ... */); + + return true; // report success +}; +``` + +The +[`ProxyManager\Factory\LazyLoadingGhostFactory`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Factory/LazyLoadingGhostFactory.php) +produces proxies that implement both the +[`ProxyManager\Proxy\GhostObjectInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Proxy/GhostObjectInterface.php) +and the +[`ProxyManager\Proxy\LazyLoadingInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Proxy/LazyLoadingInterface.php). + +At any point in time, you can set a new initializer for the proxy: + +```php +$proxy->setProxyInitializer($initializer); +``` + +In your initializer, you **MUST** turn off any further initialization: + +```php +$proxy->setProxyInitializer(null); +``` + +or + +```php +$initializer = null; // if you use the initializer passed by reference to the closure +``` + +## Triggering Initialization + +A lazy loading ghost object is initialized whenever you access any property or method of it. +Any of the following interactions would trigger lazy initialization: + +```php +// calling a method +$proxy->someMethod(); + +// reading a property +echo $proxy->someProperty; + +// writing a property +$proxy->someProperty = 'foo'; + +// checking for existence of a property +isset($proxy->someProperty); + +// removing a property +unset($proxy->someProperty); + +// cloning the entire proxy +clone $proxy; + +// serializing the proxy +$unserialized = unserialize(serialize($proxy)); +``` + +Remember to call `$proxy->setProxyInitializer(null);` to disable initialization of your proxy, or it will happen more +than once. + +## Proxying interfaces + +You can also generate proxies from an interface FQCN. By proxying an interface, you will only be able to access the +methods defined by the interface itself, even if the `wrappedObject` implements more methods. This will anyway save +some memory since the proxy won't contain any properties. + +## Tuning performance for production + +See [Tuning ProxyManager for Production](https://github.com/Ocramius/ProxyManager/blob/master/docs/tuning-for-production.md). diff --git a/vendor/ocramius/proxy-manager/docs/lazy-loading-value-holder.md b/vendor/ocramius/proxy-manager/docs/lazy-loading-value-holder.md new file mode 100644 index 0000000..c6b503e --- /dev/null +++ b/vendor/ocramius/proxy-manager/docs/lazy-loading-value-holder.md @@ -0,0 +1,202 @@ +# Lazy Loading Value Holder Proxy + +A lazy loading value holder proxy is a virtual proxy that wraps and lazily initializes a "real" instance of the proxied +class. + +## What is lazy loading? + +In pseudo-code, in userland, [lazy loading](http://www.martinfowler.com/eaaCatalog/lazyLoad.html) looks like following: + +```php +class MyObjectProxy +{ + private $wrapped; + + public function doFoo() + { + $this->init(); + + return $this->wrapped->doFoo(); + } + + private function init() + { + if (null === $this->wrapped) { + $this->wrapped = new MyObject(); + } + } +} +``` + +This code is problematic, and adds a lot of complexity that makes your unit tests' code even worse. + +Also, this kind of usage often ends up in coupling your code with a particular +[Dependency Injection Container](http://martinfowler.com/articles/injection.html) +or a framework that fetches dependencies for you. +That way, further complexity is introduced, and some problems related +with service location raise, as I've explained +[in this article](http://ocramius.github.com/blog/zf2-and-symfony-service-proxies-with-doctrine-proxies/). + +Lazy loading value holders abstract this logic for you, hiding your complex, slow, performance-impacting objects behind +tiny wrappers that have their same API, and that get initialized at first usage. + +## When do I use a lazy value holder? + +You usually need a lazy value holder in cases where following applies + + * your object takes a lot of time and memory to be initialized (with all dependencies) + * your object is not always used, and the instantiation overhead can be avoided + +## Usage examples + +[ProxyManager](https://github.com/Ocramius/ProxyManager) provides a factory that eases instantiation of lazy loading +value holders. To use it, follow these steps: + +First of all, define your object's logic without taking care of lazy loading: + +```php +namespace MyApp; + +class HeavyComplexObject +{ + public function __construct() + { + // just write your business logic + // don't worry about how heavy initialization of this will be! + } + + public function doFoo() { + echo "OK!" + } +} +``` + +Then use the proxy manager to create a lazy version of the object (as a proxy): + +```php +namespace MyApp; + +use ProxyManager\Factory\LazyLoadingValueHolderFactory; +use ProxyManager\Proxy\LazyLoadingInterface; + +require_once __DIR__ . '/vendor/autoload.php'; + +$factory = new LazyLoadingValueHolderFactory(); +$initializer = function (& $wrappedObject, LazyLoadingInterface $proxy, $method, array $parameters, & $initializer) { + $initializer = null; // disable initialization + $wrappedObject = new HeavyComplexObject(); // fill your object with values here + + return true; // confirm that initialization occurred correctly +}; + +$instance = $factory->createProxy('MyApp\HeavyComplexObject', $initializer); +``` + +You can now simply use your object as before: + +```php +// this will just work as before +$proxy->doFoo(); // OK! +``` + +## Lazy Initialization + +As you can see, we use a closure to handle lazy initialization of the proxy instance at runtime. +The initializer closure signature should be as following: + +```php +/** + * @var object $wrappedObject the instance (passed by reference) of the wrapped object, + * set it to your real object + * @var object $proxy the instance proxy that is being initialized + * @var string $method the name of the method that triggered lazy initialization + * @var string $parameters an ordered list of parameters passed to the method that + * triggered initialization, indexed by parameter name + * @var Closure $initializer a reference to the property that is the initializer for the + * proxy. Set it to null to disable further initialization + * + * @return bool true on success + */ +$initializer = function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) {}; +``` + +The initializer closure should usually be coded like following: + +```php +$initializer = function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) { + $newlyCreatedObject = new Foo(); // instantiation logic + $newlyCreatedObject->setBar('baz') // instantiation logic + $newlyCreatedObject->setBat('bam') // instantiation logic + + $wrappedObject = $newlyCreatedObject; // set wrapped object in the proxy + $initializer = null; // disable initializer + + return true; // report success +}; +``` + +The +[`ProxyManager\Factory\LazyLoadingValueHolderFactory`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Factory/LazyLoadingValueHolderFactory.php) +produces proxies that implement both the +[`ProxyManager\Proxy\ValueHolderInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Proxy/ValueHolderInterface.php) +and the +[`ProxyManager\Proxy\LazyLoadingInterface`](https://github.com/Ocramius/ProxyManager/blob/master/src/ProxyManager/Proxy/LazyLoadingInterface.php). + +At any point in time, you can set a new initializer for the proxy: + +```php +$proxy->setProxyInitializer($initializer); +``` + +In your initializer, you currently **MUST** turn off any further initialization: + +```php +$proxy->setProxyInitializer(null); +``` + +or + +```php +$initializer = null; // if you use the initializer by reference +``` + +## Triggering Initialization + +A lazy loading proxy is initialized whenever you access any property or method of it. +Any of the following interactions would trigger lazy initialization: + +```php +// calling a method +$proxy->someMethod(); + +// reading a property +echo $proxy->someProperty; + +// writing a property +$proxy->someProperty = 'foo'; + +// checking for existence of a property +isset($proxy->someProperty); + +// removing a property +unset($proxy->someProperty); + +// cloning the entire proxy +clone $proxy; + +// serializing the proxy +$unserialized = serialize(unserialize($proxy)); +``` + +Remember to call `$proxy->setProxyInitializer(null);` to disable initialization of your proxy, or it will happen more +than once. + +## Proxying interfaces + +You can also generate proxies from an interface FQCN. By proxying an interface, you will only be able to access the +methods defined by the interface itself, even if the `wrappedObject` implements more methods. This will anyway save +some memory since the proxy won't contain useless inherited properties. + +## Tuning performance for production + +See [Tuning ProxyManager for Production](https://github.com/Ocramius/ProxyManager/blob/master/docs/tuning-for-production.md). diff --git a/vendor/ocramius/proxy-manager/docs/null-object.md b/vendor/ocramius/proxy-manager/docs/null-object.md new file mode 100644 index 0000000..aca1739 --- /dev/null +++ b/vendor/ocramius/proxy-manager/docs/null-object.md @@ -0,0 +1,89 @@ +# Null Object Proxy + +A Null Object proxy is a [null object pattern](http://en.wikipedia.org/wiki/Null_Object_pattern) implementation. +The proxy factory creates a new object with defined neutral behavior based on an other object, class name or interface. + +## What is null object proxy ? + +In your application, when you can't return the object related to the request, the consumer of the model must check +for the return value and handle the failing condition gracefully, thus generating an explosion of conditionals throughout your code. +Fortunately, this seemingly-tangled situation can be sorted out simply by creating a polymorphic implementation of the +domain object, which would implement the same interface as the one of the object in question, only that its methods +wouldn’t do anything, therefore offloading client code from doing repetitive checks for ugly null values when the operation + is executed. + +## Usage examples + +```php +class UserMapper +{ + private $adapter; + + public function __construct(DatabaseAdapterInterface $adapter) { + $this->adapter = $adapter; + } + + public function fetchById($id) { + $this->adapter->select("users", array("id" => $id)); + if (!$row = $this->adapter->fetch()) { + return null; + } + return $this->createUser($row); + } + + private function createUser(array $row) { + $user = new Entity\User($row["name"], $row["email"]); + $user->setId($row["id"]); + return $user; + } +} +``` + +If you want to remove conditionals from client code, you need to have a version of the entity conforming to the corresponding +interface. With the Null Object Proxy, you can build this object : + +```php +$factory = new \ProxyManager\Factory\NullObjectFactory(); + +$nullUser = $factory->createProxy('Entity\User'); + +var_dump($nullUser->getName()); // empty return +``` + +You can now return a valid entity : + +```php +class UserMapper +{ + private $adapter; + + public function __construct(DatabaseAdapterInterface $adapter) { + $this->adapter = $adapter; + } + + public function fetchById($id) { + $this->adapter->select("users", array("id" => $id)); + return $this->createUser($this->adapter->fetch()); + } + + private function createUser($row) { + if (!$row) { + $factory = new \ProxyManager\Factory\NullObjectFactory(); + + return $factory->createProxy('Entity\User'); + } + $user = new Entity\User($row["name"], $row["email"]); + $user->setId($row["id"]); + return $user; + } +} +``` + +## Proxying interfaces + +You can also generate proxies from an interface FQCN. By proxying an interface, you will only be able to access the +methods defined by the interface itself, and like with the object, the methods are empty. + +## Tuning performance for production + +See [Tuning ProxyManager for Production](https://github.com/Ocramius/ProxyManager/blob/master/docs/tuning-for-production.md). diff --git a/vendor/ocramius/proxy-manager/docs/remote-object.md b/vendor/ocramius/proxy-manager/docs/remote-object.md new file mode 100644 index 0000000..1e040bd --- /dev/null +++ b/vendor/ocramius/proxy-manager/docs/remote-object.md @@ -0,0 +1,100 @@ +# Remote Object Proxy + +The remote object implementation is a mechanism that enables an local object to control an other object on an other server. +Each call method on the local object will do a network call to get information or execute operations on the remote object. + +## What is remote object proxy ? + +A remote object is based on an interface. The remote interface defines the API that a consumer can call. This interface +must be implemented both by the client and the RPC server. + +## Adapters + +ZendFramework's RPC components (XmlRpc, JsonRpc & Soap) can be used easily with the remote object. +You will need to require the one you need via composer, though: + +```sh +$ php composer.phar require zendframework/zend-xmlrpc:2.* +$ php composer.phar require zendframework/zend-json:2.* +$ php composer.phar require zendframework/zend-soap:2.* +``` + +ProxyManager comes with 3 adapters: + + * `ProxyManager\Factory\RemoteObject\Adapter\XmlRpc` + * `ProxyManager\Factory\RemoteObject\Adapter\JsonRpc` + * `ProxyManager\Factory\RemoteObject\Adapter\Soap` + +## Usage examples + +RPC server side code (`xmlrpc.php` in your local webroot): + +```php +interface FooServiceInterface +{ + public function foo(); +} + +class Foo implements FooServiceInterface +{ + /** + * Foo function + * @return string + */ + public function foo() + { + return 'bar remote'; + } +} + +$server = new Zend\XmlRpc\Server(); +$server->setClass('Foo', 'FooServiceInterface'); // my FooServiceInterface implementation +$server->handle(); +``` + +Client side code (proxy) : + +```php + +interface FooServiceInterface +{ + public function foo(); +} + +$factory = new \ProxyManager\Factory\RemoteObjectFactory( + new \ProxyManager\Factory\RemoteObject\Adapter\XmlRpc( + new \Zend\XmlRpc\Client('https://localhost/xmlrpc.php') + ) +); + +$proxy = $factory->createProxy('FooServiceInterface'); + +var_dump($proxy->foo()); // "bar remote" +``` + +## Implementing custom adapters + +Your adapters must implement `ProxyManager\Factory\RemoteObject\AdapterInterface` : + +```php +interface AdapterInterface +{ + /** + * Call remote object + * + * @param string $wrappedClass + * @param string $method + * @param array $params + * + * @return mixed + */ + public function call($wrappedClass, $method, array $params = array()); +} +``` + +It is very easy to create your own implementation (for RESTful web services, for example). Simply pass +your own adapter instance to your factory at construction time + +## Tuning performance for production + +See [Tuning ProxyManager for Production](https://github.com/Ocramius/ProxyManager/blob/master/docs/tuning-for-production.md). diff --git a/vendor/ocramius/proxy-manager/docs/tuning-for-production.md b/vendor/ocramius/proxy-manager/docs/tuning-for-production.md new file mode 100644 index 0000000..48ccf9f --- /dev/null +++ b/vendor/ocramius/proxy-manager/docs/tuning-for-production.md @@ -0,0 +1,24 @@ +## Tuning the ProxyManager for production + +By default, all proxy factories generate the required proxy classes at runtime. + +Proxy generation causes I/O operations and uses a lot of reflection, so be sure to have +generated all of your proxies **before deploying your code on a live system**, or you +may experience poor performance. + +You can configure ProxyManager so that it will try autoloading the proxies first. +Generating them "bulk" is not yet implemented: + +```php +$config = new \ProxyManager\Configuration(); +$config->setProxiesTargetDir(__DIR__ . '/my/generated/classes/cache/dir'); + +// then register the autoloader +spl_autoload_register($config->getProxyAutoloader()); +``` + +Generating a classmap with all your proxy classes in it will also work perfectly. + +Please note that all the currently implemented `ProxyManager\Factory\*` classes accept +a `ProxyManager\Configuration` object as optional constructor parameter. This allows for +fine-tuning of ProxyManager according to your needs. diff --git a/vendor/ocramius/proxy-manager/examples/access-interceptor-scope-localizer.php b/vendor/ocramius/proxy-manager/examples/access-interceptor-scope-localizer.php new file mode 100644 index 0000000..eb1da5b --- /dev/null +++ b/vendor/ocramius/proxy-manager/examples/access-interceptor-scope-localizer.php @@ -0,0 +1,38 @@ +counter += 1; + + return $this; + } +} + +$factory = new AccessInterceptorScopeLocalizerFactory(); +$foo = new FluentCounter(); + +/* @var $proxy FluentCounter */ +$proxy = $factory->createProxy( + $foo, + array('fluentMethod' => function ($proxy) { echo "pre-fluentMethod #{$proxy->counter}!\n"; }), + array('fluentMethod' => function ($proxy) { echo "post-fluentMethod #{$proxy->counter}!\n"; }) +); + +$proxy->fluentMethod()->fluentMethod()->fluentMethod()->fluentMethod(); + +echo 'The proxy counter is now at ' . $proxy->counter . "\n"; +echo 'The real instance counter is now at ' . $foo->counter . "\n"; diff --git a/vendor/ocramius/proxy-manager/examples/ghost-object.php b/vendor/ocramius/proxy-manager/examples/ghost-object.php new file mode 100644 index 0000000..b4ec216 --- /dev/null +++ b/vendor/ocramius/proxy-manager/examples/ghost-object.php @@ -0,0 +1,46 @@ +foo = (string) $foo; + } + + public function getFoo() + { + return $this->foo; + } +} + +$startTime = microtime(true); +$factory = new LazyLoadingGhostFactory(); + +for ($i = 0; $i < 1000; $i += 1) { + $proxy = $factory->createProxy( + 'Foo', + function ($proxy, $method, $parameters, & $initializer) { + $initializer = null; + $proxy->setFoo('Hello World!'); + + return true; + } + ); +} + +var_dump('time after 1000 instantiations: ' . (microtime(true) - $startTime)); + +echo $proxy->getFoo() . "\n"; + +var_dump('time after single call to doFoo: ' . (microtime(true) - $startTime)); diff --git a/vendor/ocramius/proxy-manager/examples/remote-proxy.php b/vendor/ocramius/proxy-manager/examples/remote-proxy.php new file mode 100644 index 0000000..97025fc --- /dev/null +++ b/vendor/ocramius/proxy-manager/examples/remote-proxy.php @@ -0,0 +1,36 @@ +createProxy('Foo'); + +try { + var_dump($proxy->bar()); // bar remote ! +} catch (\Zend\Http\Client\Adapter\Exception\RuntimeException $error) { + echo "To run this example, please following before:\n\n\$ php -S localhost:9876 -t \"" . __DIR__ . "\"\n"; + + exit(2); +} diff --git a/vendor/ocramius/proxy-manager/examples/remote-proxy/remote-proxy-server.php b/vendor/ocramius/proxy-manager/examples/remote-proxy/remote-proxy-server.php new file mode 100644 index 0000000..81a742f --- /dev/null +++ b/vendor/ocramius/proxy-manager/examples/remote-proxy/remote-proxy-server.php @@ -0,0 +1,20 @@ +setClass(new Foo(), 'Foo'); +$server->setReturnResponse(false); + +$server->handle(); diff --git a/vendor/ocramius/proxy-manager/examples/smart-reference.php b/vendor/ocramius/proxy-manager/examples/smart-reference.php new file mode 100644 index 0000000..8c00a3a --- /dev/null +++ b/vendor/ocramius/proxy-manager/examples/smart-reference.php @@ -0,0 +1,23 @@ +createProxy( + new Foo(), + array('doFoo' => function () { echo "pre-foo!\n"; }), + array('doFoo' => function () { echo "post-foo!\n"; }) +); + +$proxy->doFoo(); diff --git a/vendor/ocramius/proxy-manager/examples/virtual-proxy.php b/vendor/ocramius/proxy-manager/examples/virtual-proxy.php new file mode 100644 index 0000000..7ee6b2e --- /dev/null +++ b/vendor/ocramius/proxy-manager/examples/virtual-proxy.php @@ -0,0 +1,39 @@ +createProxy( + 'Foo', + function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) { + $initializer = null; + $wrappedObject = new Foo(); + + return true; + } + ); +} + +var_dump('time after 1000 instantiations: ' . (microtime(true) - $startTime)); + +$proxy->doFoo(); + +var_dump('time after single call to doFoo: ' . (microtime(true) - $startTime)); diff --git a/vendor/ocramius/proxy-manager/html-docs/access-interceptor-scope-localizer-proxy.html b/vendor/ocramius/proxy-manager/html-docs/access-interceptor-scope-localizer-proxy.html new file mode 100644 index 0000000..dac8c93 --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/access-interceptor-scope-localizer-proxy.html @@ -0,0 +1,197 @@ + + + + ProxyManager - Tuning the ProxyManager for production + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Access Interceptor Scope Localizer Proxy

+ +

An access interceptor scope localizer is a smart reference proxy that allows you to dynamically define logic to be executed before or after any of the proxied object's methods' logic.

+ +

It works exactly like the access interceptor value holder, with some minor differences in behavior.

+ +

The working concept of an access interceptor scope localizer is to localize scope of a proxied object:

+ +
+        
+class Example
+{
+    protected $foo;
+    protected $bar;
+    protected $baz;
+
+    public function doFoo()
+    {
+        // ...
+    }
+}
+
+class ExampleProxy extends Example
+{
+    public function __construct(Example $example)
+    {
+        $this->foo = & $example->foo;
+        $this->bar = & $example->bar;
+        $this->baz = & $example->baz;
+    }
+
+    public function doFoo()
+    {
+        return parent::doFoo();
+    }
+}
+        
+    
+ +

This allows to create a mirror copy of the real instance, where any change in the proxy or in the real instance is reflected in both objects.

+ +

The main advantage of this approach is that the proxy is now safe against fluent interfaces, which would break an access interceptor value holder instead.

+
+

Differences with access interceptor value holder:

+ +
    +
  • It does NOT implement the ProxyManager\Proxy\ValueHolderInterface, since the proxy itself does not keep a reference to the original object being proxied
  • +
  • In all interceptor methods (see access interceptor value holder), the $instance passed in is the proxy itself. There is no way to gather a reference to the original object right now, and that's mainly to protect from misuse.
  • +
+
+ +

Known limitations

+ +
    +
  • It is NOT possible to intercept access to public properties
  • +
  • It is NOT possible to proxy interfaces, since this proxy relies on parent::method() calls. Interfaces obviously don't provide a parent method implementation.
  • +
  • calling unset on a property of an access interceptor scope localizer (or the real instance) will cause the two objects to be un-synchronized, with possible unexpected behaviour.
  • +
  • serializing or un-serializing an access interceptor scope localizer (or the real instance) will not cause the real instance (or the proxy) to be serialized or un-serialized
  • +
  • if a proxied object contains private properties, then an exception will be thrown if you use PHP < 5.4.0.
  • +
+
+

Example

+ +

Here's an example of how you can create and use an access interceptor scope localizer :

+ +
+        
+<?php
+
+use ProxyManager\Factory\AccessInterceptorScopeLocalizerFactory as Factory;
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+class Foo
+{
+    public function doFoo()
+    {
+        echo "Foo!\n";
+    }
+}
+
+$factory = new Factory();
+
+$proxy = $factory->createProxy(
+    new Foo(),
+    array('doFoo' => function () { echo "PreFoo!\n"; }),
+    array('doFoo' => function () { echo "PostFoo!\n"; })
+);
+
+$proxy->doFoo();
+        
+    
+ +

This send something like following to your output:

+ +
+        
+PreFoo!
+Foo!
+PostFoo!
+        
+    
+ +

This is pretty much the same logic that you can find in access interceptor value holder.

+ +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/access-interceptor-value-holder-proxy.html b/vendor/ocramius/proxy-manager/html-docs/access-interceptor-value-holder-proxy.html new file mode 100644 index 0000000..b98e95a --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/access-interceptor-value-holder-proxy.html @@ -0,0 +1,208 @@ + + + + ProxyManager - Tuning the ProxyManager for production + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Access Interceptor Value Holder Proxy

+ +

An access interceptor value holder is a smart reference proxy that allows you to dynamically define logic to be executed before or after any of the wrapped object's methods logic.

+ +

It wraps around a real instance of the object to be proxied, and can be useful for things like:

+ +
    +
  • caching execution of slow and heavy methods
  • +
  • log method calls
  • +
  • debugging
  • +
  • event triggering
  • +
  • handling of orthogonal logic, and AOP in general
  • +
+
+

Example

+ +

Here's an example of how you can create and use an access interceptor value holder:

+ +
+        
+<?php
+
+use ProxyManager\Factory\AccessInterceptorValueHolderFactory as Factory;
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+class Foo
+{
+    public function doFoo()
+    {
+        echo "Foo!\n";
+    }
+}
+
+$factory = new Factory();
+
+$proxy = $factory->createProxy(
+    new Foo(),
+    array('doFoo' => function () { echo "PreFoo!\n"; }),
+    array('doFoo' => function () { echo "PostFoo!\n"; })
+);
+
+$proxy->doFoo();
+        
+    
+ +

This send something like following to your output:

+ +
+        
+PreFoo!
+Foo!
+PostFoo!
+        
+    
+ +
+

Implementing pre- and post- access interceptors

+ +

A proxy produced by the ProxyManager\Factory\AccessInterceptorValueHolderFactory implements both the ProxyManager\Proxy\ValueHolderInterface and the ProxyManager\Proxy\AccessInterceptorInterface.

+ + +

Therefore, you can set an access interceptor callback by calling:

+ +
+        
+$proxy->setMethodPrefixInterceptor('methodName', function () { echo 'pre'; });
+$proxy->setMethodSuffixInterceptor('methodName', function () { echo 'post'; });
+        
+    
+ +

You can also listen to public properties access by attaching interceptors to __get, __set, __isset and __unset.

+ +

A prefix interceptor (executed before method logic) should have following signature:

+ +
+        
+/**
+ * @var object $proxy       the proxy that intercepted the method call
+ * @var object $instance    the wrapped instance within the proxy
+ * @var string $method      name of the called method
+ * @var array  $params      sorted array of parameters passed to the intercepted
+ *                          method, indexed by parameter name
+ * @var bool   $returnEarly flag to tell the interceptor proxy to return early, returning
+ *                          the interceptor's return value instead of executing the method logic
+ *
+ * @return mixed
+ */
+$prefixInterceptor = function ($proxy, $instance, $method, $params, & $returnEarly) {};
+        
+    
+ + A suffix interceptor (executed after method logic) should have following signature: + +
+        
+/**
+ * @var object $proxy       the proxy that intercepted the method call
+ * @var object $instance    the wrapped instance within the proxy
+ * @var string $method      name of the called method
+ * @var array  $params      sorted array of parameters passed to the intercepted
+ *                          method, indexed by parameter name
+ * @var mixed  $returnValue the return value of the intercepted method
+ * @var bool   $returnEarly flag to tell the proxy to return early, returning the interceptor's
+ *                          return value instead of the value produced by the method
+ *
+ * @return mixed
+ */
+$suffixInterceptor = function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) {};
+        
+    
+ +
+

Tuning performance for production

+ +

See Tuning ProxyManager for Production.

+
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/contributing.html b/vendor/ocramius/proxy-manager/html-docs/contributing.html new file mode 100644 index 0000000..aa34320 --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/contributing.html @@ -0,0 +1,139 @@ + + + + ProxyManager - Contributing + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Contributing

+ +
    +
  • Coding standard for the project is PSR-2
  • +
  • The project will follow strict object calisthenics
  • +
  • Any contribution must provide tests for additional introduced conditions
  • +
  • Any un-confirmed issue needs a failing test case before being accepted
  • +
  • Pull requests must be sent from a new hotfix/feature branch, not from master.
  • +
+ + +
+ +

Installation

+ +

To install the project and run the tests, you need to clone it first:

+ +
+        
+$ git clone git://github.com/Ocramius/ProxyManager.git
+            
+    
+ +

You will then need to run a composer installation:

+ +
+        
+$ cd ProxyManager
+$ curl -s https://getcomposer.org/installer | php
+$ php composer.phar update
+        
+    
+ +
+ +

Testing

+ +

The PHPUnit version to be used is the one installed as a dev- dependency via composer:

+ +
+        
+$ ./vendor/bin/phpunit
+        
+    
+ +

Accepted coverage for new contributions is 80%. Any contribution not satisfying this requirement won't be merged.

+ +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/copyright.html b/vendor/ocramius/proxy-manager/html-docs/copyright.html new file mode 100644 index 0000000..94f3fc7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/copyright.html @@ -0,0 +1,100 @@ + + + + ProxyManager + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

License

+

Copyright (c) 2013 Marco Pivetta

+ +

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

+ +

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+
+ +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/credits.html b/vendor/ocramius/proxy-manager/html-docs/credits.html new file mode 100644 index 0000000..33f78d2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/credits.html @@ -0,0 +1,113 @@ + + + + ProxyManager - Credits + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/css/styles.css b/vendor/ocramius/proxy-manager/html-docs/css/styles.css new file mode 100644 index 0000000..ca49812 --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/css/styles.css @@ -0,0 +1,203 @@ +html { font-family: sans-serif; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; } +body { margin: 0; } +article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { display: block; } +audio, canvas, progress, video { display: inline-block; vertical-align: baseline; } +audio:not([controls]) { display: none; height: 0; } +[hidden], template { display: none; } +a { background: transparent; } +a:active, a:hover { outline: 0; } +abbr[title] { border-bottom: 1px dotted; } +b, strong { font-weight: bold; } +dfn { font-style: italic; } +h1 { font-size: 2em; margin: 0.67em 0; } +mark { background: #ff0; color: #000; } +small { font-size: 80%; } +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } +sup { top: -0.5em; } +sub { bottom: -0.25em; } +img { border: 0; } +svg:not(:root) { overflow: hidden; } +figure { margin: 1em 40px; } +hr { -moz-box-sizing: content-box; box-sizing: content-box; height: 0; } +pre { overflow: auto; } +code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } +button, input, optgroup, select, textarea { color: inherit; font: inherit; margin: 0; } +button { overflow: visible; } +button, select { text-transform: none; } +button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; cursor: pointer; } +button[disabled], html input[disabled] { cursor: default; } +button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } +input { line-height: normal; } +input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; } +input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } +input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } +input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } +fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } +legend { border: 0; padding: 0; } +textarea { overflow: auto; } +optgroup { font-weight: bold; } +table { border-collapse: collapse; border-spacing: 0; } +td, th { padding: 0; } +h1, h2, h3, h4, h5, h6, p, ul, ol, li { margin: 0; padding: 0; line-height: normal; } +a { color: #31811D; text-decoration: none; } +a:hover { color: #2F611C; text-decoration: none; } +strong { font-weight: 600; } +h3 { margin-bottom: 1em; color: #17324f; font-weight: 400; font-size: 3em; } +h4 { margin-bottom: 1em; font-weight: 400; font-size: 1.375em; } +p { margin-bottom: 1.5em; font-size: 1.125em; } +hr { margin: 2.5em 0; padding: 0; width: 100%; height: 3px; border: 0; background: #e6eaef; } +pre code { padding: 20px; border: 1px solid #EBEBEB; background: #F5F5F5; color: #08468a; font-weight: 200; font-size: .875em; } +code { background: transparent; color: #08468a; font-size: .875em; } +form { margin: 0; } +label { display: block; color: #18324f; font-weight: 400; font-size: 1.5em; margin-bottom: 2em; } +legend { position: static; margin: 0; padding: 0; font-weight: normal; } +legend span { position: absolute; top: 0; right: 0; left: 0; display: block; padding: 7px 0; border-bottom: 1px solid #e0dede; color: #ed4a21; font-size: 18px; line-height: 1em; } + +/* Menu Sidebar*/ +.button-block { padding-top: 15px; } +.button-block .btn-1 { margin-right: 6px; } +.btn, .spy-nav a { position: relative; display: inline-block; margin: 0; padding: 0 20px; height: 57px; border: 0; vertical-align: top; text-align: center; text-transform: uppercase; font-weight: 400; font-size: 1.125em; transitionP: all .2s; line-height: 57px; } +.btn.btn-action, .spy-nav a.btn-action { background: #ee2d4d; color: #fff; } +.btn.btn-action:hover, .spy-nav a.btn-action:hover { background: #bf0f2d; } +.btn.btn-default, .spy-nav a { background: #31811D; color: #fff; } +.btn.btn-default:hover, .spy-nav a:hover { background: #2F611C; color: #fff; } +.btn.btn-text, .spy-nav a.btn-text { color: #18324f; font-weight: 600; } +.btn.btn-full, .spy-nav a { display: block; } +@media only screen and (max-width: 480px) { + .site-header{ position: fixed !important; top: 0; z-index: 999999} + .page-title-wrapper{ margin-top: 70px;} + .main-wrapper iframe{ width: 42% !important; float: left; margin-left: 8%;} + .content iframe{width: 100%; height: 100%;} +} + +.btn-top { position: relative; display: inline-block; float: right; margin: 20px 0 0; padding: 0 30px 0 50px; height: 57px; border: 2px solid ##31811D; color: #31811D; vertical-align: top; text-align: center; text-transform: uppercase; font-weight: 600; font-size: 1.125em; line-height: 53px; transition: all .2s; } +.btn-top:before { background-position: 0 -140px; width: 52px; height: 52px; position: absolute; top: 0; left: 0; content: ''; } +@media only screen and (min-resolution: 2dppx), (-webkit-min-device-pixel-ratio: 2) { .btn-top:before { background-position: 0 -140px; background-size: 152px auto; width: 52px; height: 52px; } } +.btn-top:hover { border-color: #2F611C; color: #2F611C; } +@media only screen and (max-width: 768px) { .btn-top { float: none; margin-top: 0; margin-bottom: 30px; white-space: nowrap; } } +@media only screen and (max-width: 480px) { .btn-top { font-size: 0.9em; line-height: 46px; height: 48px; padding-right: 22px; padding-left: 47px; } } + +.btn-download { float: right; padding-left: 50px; } +.btn-download:before { background-position: -18px -116px; width: 16px; height: 16px; position: absolute; top: 50%; left: 20px; margin-top: -7px; content: ''; } +@media only screen and (min-resolution: 2dppx), (-webkit-min-device-pixel-ratio: 2) { .btn-download:before { background-position: -18px -116px; background-size: 152px auto; width: 16px; height: 16px; } } + +.btn-done { float: right; padding-left: 50px; } +.btn-done:before { background-position: 0 -116px; width: 18px; height: 14px; position: absolute; top: 50%; margin-top: -7px; left: 20px; content: ''; } +@media only screen and (min-resolution: 2dppx), (-webkit-min-device-pixel-ratio: 2) { .btn-done:before { background-position: 0 -116px; background-size: 152px auto; width: 18px; height: 13.5px; margin-top: -6.75px; } } + +.btn-cancel { float: right; } + +*, *:before, *:after { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } + +html { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; min-width: 280px; } +@media only screen and (min-width: 64.063em) and (max-width: 90em) { html { min-width: 960px; } } + +body { font-size: 16px; font-family: 'Source Sans Pro', sans-serif; min-width: 290px; } +@media only screen and (max-width: 480px) { body.page-home { background-image: none; } } + +img { max-width: 100%; height: auto !important; } + +input { -webkit-appearance: none; -webkit-border-radius: 0; border-radius: 0; } + +main { overflow: hidden; } + +.clearfix:after { content: ""; display: table; clear: both; } + +.container { width: 60em; margin-left: auto; margin-right: auto; } +.container:after { content: " "; display: block; clear: both; } +@media only screen and (max-width: 1024px) { .container { margin-left: 15px; margin-right: 15px; width: auto; } } + +.content ul { padding-left: 19px; list-style-type: square; margin-top: 15px; } + +/* Menu top */ +.main-nav { float: right; } +.fixed-wrapper .main-nav { display: none; } +.active .main-nav { display: block; } +.main-nav > ul { list-style: none; } +.main-nav > ul > li { float: left; } +.main-nav > ul > li > a { display: block; padding: 22px 30px; color: #fff; text-decoration: none; text-transform: uppercase; font-weight: 600; font-size: 1.375em; line-height: 29px; transition: all .2s; } +.main-nav > ul > li > a:hover { color: #bfe5f1; background-color: #2F611C; } +.main-nav > ul > li > a.active { background-color: #2F611C; color: #fff; } +.main-nav > ul > li > a.opened { background-color: #18324f; } +@media only screen and (max-width: 600px) { .main-nav { font-size: 0.86em; } + .main-nav > ul > li > a { padding-left: 18px; padding-right: 18px; } } +@media only screen and (max-width: 480px) { .main-nav > ul > li > a { font-size: 0.95em; padding: 22px 6px; } } + +.site-header { position: relative; width: 100%; background: #31811D; } +.site-header h1 { float: left; margin: 15px 0; } +.site-header h1 a { display: block; } +.site-header h1 img { display: block; max-height: 42px; } +@media only screen and (max-width: 480px) { .site-header h1 img { max-height: 42px; } } + +.page-title-wrapper { position: relative; padding: 26px 0 55px; text-align: center; } +.page-title-wrapper .page-title { margin-top: 44px; color: #17324f; font-weight: 400; font-size: 3.75em; } +.page-title-wrapper .linguistics { padding: 0 2em 13px; border-bottom: 1px solid #D4DBE3; color: #18324f; text-transform: uppercase; font-weight: 400; font-size: 1.25em; } +@media only screen and (max-width: 768px) { .page-title-wrapper { font-size: 0.8em; } } +@media only screen and (max-width: 480px) { .page-title-wrapper { font-size: 0.65em; } } + +.site-footer { margin-top: 20px; padding-top: 50px; padding-bottom: 50px; background: #18324f; color: #7c8ea3; text-align: center; } +.site-footer .container { position: relative; } +.site-footer + .bcms-clearfix:after { content: ""; } +.about + .site-footer .main { padding-top: 150px; } +.footer-logos ul li{ list-style: none; display: inline-block;} +.footer-logos ul li a{ padding-left: 10px; padding-right: 10px} +.site-footer a{ color: white !important;} +.site-footer p { text-align: left; display: block; margin-bottom: 1.5em; font-size: 1.125em; } +@media only screen and (max-width: 1024px) { .site-footer p { margin-left: 0; } } +@media only screen and (max-width: 768px) { .site-footer p { text-align: center; margin-left: auto; } } +@media only screen and (max-width: 600px) { .site-footer { font-size: 0.9em; } } + +.main-logo { display: block; float: left; margin: 30px 0; } +.fixed-wrapper .main-logo { display: none; } +.main-logo img { display: block; } +.main-logo figure { position: relative; margin: 0; max-width: 56px; } +.main-logo figcaption { position: absolute; width: 160px; top: 20px; left: 68px; } +.active .main-logo { display: block; margin: 0; } +.active .main-logo figure { max-width: 42px; } +.active .main-logo figcaption { width: 120px; top: 15px; left: 55px; } +@media only screen and (max-width: 480px) { .main-logo figcaption { display: none; } } + +.container { width: 60em; margin-left: auto; margin-right: auto; } +.container:after { content: " "; display: block; clear: both; } +@media only screen and (max-width: 1024px) { .container { margin-left: 15px; margin-right: 15px; width: auto; } } + +.component-demo { position: relative; padding: 1px 0 0; background: #e6eaef; } +.component-demo:before { background-image: url("../img/enf.png"); } +@media only screen and (max-width: 480px) { .component-demo { padding: 40px 0 0; } } + +.component-info .container { position: relative; } +.component-info .sidebar { width: 220px; float: left; margin-top: 75px; } +.component-info .sidebar .component-meta { margin-top: 10px; font-size: 1.125em; } +.component-info .sidebar .component-meta span { white-space: nowrap; margin-bottom: 10px; padding-left: 25px; color: #18324f; } +.component-info .sidebar.sticky { position: fixed; margin-top: 38px; } +.component-info .content { width: 64.58333333%; float: right; margin-top: 60px; margin-bottom: 75px; } +.component-info .content h3 { margin-bottom: .75em; font-size: 2.75em; } +.component-info .content p { margin-bottom: 1.75em; line-height: 1.45; } +@media only screen and (max-width: 480px) { .component-info .content .section-title { font-size: 2.1em; } } +@media only screen and (max-width: 768px) { .component-info .sidebar { float: none; margin-left: auto; margin-right: auto; } + .component-info .sidebar .component-meta { margin-top: 30px; text-align: center; } + .component-info .sidebar.sticky { position: relative; margin-top: 75px; } + .component-info .content { float: none; width: 100%; } } +@media only screen and (max-width: 480px) { .component-info .content { margin-bottom: 0; } } + +.spy-nav { margin-bottom: 10px; } +.spy-nav ul { list-style: none; } +.spy-nav a { padding-top: 9px; padding-bottom: 10px; height: auto; border-bottom: 1px solid #4B8B20; text-align: left; text-transform: none; font-size: 1.375em; line-height: normal; } +.spy-nav .active a { border-bottom-color: #fff; background: #fff; color: #17324f; } +@media only screen and (max-width: 768px) { /*.spy-nav { display: none; } */} + +.main-wrapper { margin: 44px auto 44px; max-width: 600px; } +.main-wrapper label { display: block; margin-bottom: .75em; color: #3f4e5e; font-size: 1.25em; } +.main-wrapper .text-field { padding: 0 15px; width: 100%; height: 40px; border: 1px solid #CBD3DD; font-size: 1.125em; } +.main-wrapper ::-webkit-input-placeholder { color: #CBD3DD; font-style: italic; font-size: 18px; } +.main-wrapper :-moz-placeholder { color: #CBD3DD; font-style: italic; font-size: 18px; } +.main-wrapper ::-moz-placeholder { color: #CBD3DD; font-style: italic; font-size: 18px; } +.main-wrapper :-ms-input-placeholder { color: #CBD3DD; font-style: italic; font-size: 18px; } + +.page-icon-wrapper:before, .page-icon-wrapper .logo-asp:before, .page-icon-wrapper .logo-hibernate:before, .page-icon-wrapper .logo-angularjs:before, .page-icon-wrapper .logo-requirejs:before, .page-icon-wrapper .logo-reward:before, .component-demo:before {background-repeat: no-repeat; background-size: 100%; width: 92px; height: 108px; position: absolute; left: 50%; margin-left: -46px; top: 0; bottom: auto; margin-top: -28px; content: ''; } + +.container { width: 60em; margin-left: auto; margin-right: auto; } +.container:after { content: " "; display: block; clear: both; } +@media only screen and (max-width: 1024px) { .container { margin-left: 15px; margin-right: 15px; width: auto; } } + +.bcms-clearfix:after {content: ""; visibility: hidden; display: block; height: 0; clear: both; } diff --git a/vendor/ocramius/proxy-manager/html-docs/download.html b/vendor/ocramius/proxy-manager/html-docs/download.html new file mode 100644 index 0000000..4862ef3 --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/download.html @@ -0,0 +1,97 @@ + + + + ProxyManager + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Installation

+

The suggested installation method is via composer.

+
php composer.phar require ocramius/proxy-manager:1.0.*
+
+ +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/favicon.ico b/vendor/ocramius/proxy-manager/html-docs/favicon.ico new file mode 100644 index 0000000..797ca03 Binary files /dev/null and b/vendor/ocramius/proxy-manager/html-docs/favicon.ico differ diff --git a/vendor/ocramius/proxy-manager/html-docs/ghost-object.html b/vendor/ocramius/proxy-manager/html-docs/ghost-object.html new file mode 100644 index 0000000..ce7b2fc --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/ghost-object.html @@ -0,0 +1,314 @@ + + + + ProxyManager - Ghost object + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Lazy Loading Ghost Object Proxies

+ +

A lazy loading ghost object proxy is a ghost proxy that looks exactly like the real instance of the proxied subject, but which has all properties nulled before initialization.

+
+ +

Lazy loading with the Ghost Object

+ +

In pseudo-code, in userland, lazy loading in a ghost object looks like following:

+ +
+        
+class MyObjectProxy
+{
+    private $initialized = false;
+    private $name;
+    private $surname;
+
+    public function doFoo()
+    {
+        $this->init();
+
+        // Perform doFoo routine using loaded variables
+    }
+
+    private function init()
+    {
+        if (! $this->initialized) {
+            $data          = some_logic_that_loads_data();
+
+            $this->name    = $data['name'];
+            $this->surname = $data['surname'];
+
+            $this->initialized = true;
+        }
+    }
+}
+        
+    
+ +

Ghost objects work similarly to virtual proxies, but since they don't wrap around a "real" instance of the proxied subject, they are better suited for representing dataset rows.

+ +
+ +

When do I use a ghost object?

+ +

You usually need a ghost object in cases where following applies

+ +
    +
  • you are building a small data-mapper and want to lazily load data across associations in your object graph
  • +
  • you want to initialize objects representing rows in a large dataset
  • +
  • you want to compare instances of lazily initialized objects without the risk of comparing a proxy with a real subject
  • +
  • you are aware of the internal state of the object and are confident in working with its internals via reflection or direct property access
  • +
+ +
+ +

Usage examples

+ +

ProxyManager provides a factory that creates lazy loading ghost objects. To use it, follow these steps:

+ +

First of all, define your object's logic without taking care of lazy loading:

+ +
+        
+namespace MyApp;
+
+class Customer
+{
+    private $name;
+    private $surname;
+
+    // just write your business logic or generally logic
+    // don't worry about how complex this object will be!
+    // don't code lazy-loading oriented optimizations in here!
+    public function getName() { return $this->name; }
+    public function setName($name) { $this->name = (string) $name; }
+    public function getSurname() { return $this->surname; }
+    public function setSurname($surname) { $this->surname = (string) $surname; }
+}
+        
+    
+ +

Then use the proxy manager to create a ghost object of it. You will be responsible of setting its state during lazy loading:

+ +
+        
+namespace MyApp;
+
+use ProxyManager\Factory\LazyLoadingGhostFactory;
+use ProxyManager\Proxy\LazyLoadingInterface;
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+$factory     = new LazyLoadingGhostFactory();
+$initializer = function (LazyLoadingInterface $proxy, $method, array $parameters, & $initializer) {
+    $initializer   = null; // disable initialization
+
+    // load data and modify the object here
+    $proxy->setName('Agent');
+    $proxy->setSurname('Smith');
+
+    return true; // confirm that initialization occurred correctly
+};
+
+$instance = $factory->createProxy('MyApp\Customer', $initializer);
+        
+    
+ +

You can now simply use your object as before:

+ +
+        
+// this will just work as before
+echo $proxy->getName() . ' ' . $proxy->getSurname(); // Agent Smith
+        
+    
+
+ +

Lazy Initialization

+ +

As you can see, we use a closure to handle lazy initialization of the proxy instance at runtime. The initializer closure signature for ghost objects should be as following:

+ +
+        
+/**
+ * @var object  $proxy         the instance the ghost object proxy that is being initialized
+ * @var string  $method        the name of the method that triggered lazy initialization
+ * @var array   $parameters    an ordered list of parameters passed to the method that
+ *                             triggered initialization, indexed by parameter name
+ * @var Closure $initializer   a reference to the property that is the initializer for the
+ *                             proxy. Set it to null to disable further initialization
+ *
+ * @return bool true on success
+ */
+$initializer = function ($proxy, $method, $parameters, & $initializer) {};
+        
+    
+ +

The initializer closure should usually be coded like following:

+ +
+        
+$initializer = function ($proxy, $method, $parameters, & $initializer) {
+    $initializer = null; // disable initializer for this proxy instance
+
+    // modify the object with loaded data
+    $proxy->setFoo(/* ... */);
+    $proxy->setBar(/* ... */);
+
+    return true; // report success
+};
+        
+    
+ +

The ProxyManager\Factory\LazyLoadingGhostFactory produces proxies that implement both the ProxyManager\Proxy\GhostObjectInterface and the ProxyManager\Proxy\LazyLoadingInterface.

+ +

At any point in time, you can set a new initializer for the proxy:

+ +
+        
+$proxy->setProxyInitializer($initializer);
+        
+    
+ +

In your initializer, you MUST turn off any further initialization:

+ +
+        
+$proxy->setProxyInitializer(null);
+        
+    
+ +

or

+ +
+        
+$initializer = null; // if you use the initializer passed by reference to the closure
+        
+    
+
+ +

Triggering Initialization

+ +

A lazy loading ghost object is initialized whenever you access any property or method of it. Any of the following interactions would trigger lazy initialization:

+ +
+        
+// calling a method
+$proxy->someMethod();
+
+// reading a property
+echo $proxy->someProperty;
+
+// writing a property
+$proxy->someProperty = 'foo';
+
+// checking for existence of a property
+isset($proxy->someProperty);
+
+// removing a property
+unset($proxy->someProperty);
+
+// cloning the entire proxy
+clone $proxy;
+
+// serializing the proxy
+$unserialized = unserialize(serialize($proxy));
+        
+    
+ +

Remember to call $proxy->setProxyInitializer(null); to disable initialization of your proxy, or it will happen more than once.

+ +
+ +

Proxying interfaces

+ +

You can also generate proxies from an interface FQCN. By proxying an interface, you will only be able to access the methods defined by the interface itself, even if the wrappedObject implements more methods. This will anyway save some memory since the proxy won't contain any properties.

+ +

Tuning performance for production

+ +

See Tuning ProxyManager for Production.

+ +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/img/block.png b/vendor/ocramius/proxy-manager/html-docs/img/block.png new file mode 100644 index 0000000..fb8f17a Binary files /dev/null and b/vendor/ocramius/proxy-manager/html-docs/img/block.png differ diff --git a/vendor/ocramius/proxy-manager/html-docs/img/enf.png b/vendor/ocramius/proxy-manager/html-docs/img/enf.png new file mode 100644 index 0000000..7e15bdd Binary files /dev/null and b/vendor/ocramius/proxy-manager/html-docs/img/enf.png differ diff --git a/vendor/ocramius/proxy-manager/html-docs/index.html b/vendor/ocramius/proxy-manager/html-docs/index.html new file mode 100644 index 0000000..9c0ef23 --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/index.html @@ -0,0 +1,295 @@ + + + + ProxyManager + + + + + + + + + + + + + + +
+
+
+ +

Proxy Manager

+
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + + +
+

Proxy Manager

+

This library aims at providing abstraction for generating various kinds of proxy classes.

+

If you want to learn more about proxy pattern watch this video:

+ + +
+
+

Installation

+

The suggested installation method is via composer.

+
php composer.phar require ocramius/proxy-manager:1.0.*
+
+ +

Lazy Loading Value Holders (Virtual Proxy)

+ +

ProxyManager can generate + lazy loading value holders, + which are virtual proxies capable of saving performance and memory for objects that + require a lot of dependencies or CPU cycles to be loaded: + particularly useful when you may not always need the object, + but are constructing it anyways.

+ +
+        
+$factory = new \ProxyManager\Factory\LazyLoadingValueHolderFactory();
+
+$proxy = $factory->createProxy(
+    'MyApp\HeavyComplexObject',
+    function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) {
+        $wrappedObject = new HeavyComplexObject(); // instantiation logic here
+        $initializer   = null; // turning off further lazy initialization
+
+        return true;
+    }
+);
+
+$proxy->doFoo();
+        
+    
+ +

See the complete documentation about lazy loading value holders.

+
+ +

Access Interceptor Value Holder

+ +

An access interceptor value holder is a smart reference that allows you to execute + logic before and after a particular method is executed or a particular property is + accessed, and it allows to manipulate parameters and return values depending on + your needs.

+ +
+        
+$factory = new \ProxyManager\Factory\AccessInterceptorValueHolderFactory();
+
+$proxy = $factory->createProxy(
+    new \My\Db\Connection(),
+    array('query' => function () { echo "Query being executed!\n"; }),
+    array('query' => function () { echo "Query completed!\n"; })
+);
+
+$proxy->query(); // produces "Query being executed!\nQuery completed!\n"
+        
+    
+ +

See the complete documentation about access interceptor value holders.

+
+ +

Access Interceptor Scope Localizer

+ +

An access interceptor scope localizer works exactly like an access interceptor + value holder, but it is safe to use to proxy fluent interfaces.

+ +

See the complete documentation about access interceptor scope localizer.

+
+ + +

Null Objects

+ +

A Null Object proxy implements the null object pattern.

+ +

This kind of proxy allows you to have fallback logic in case loading of the wrapped value failed.

+
+        
+$factory = new \ProxyManager\Factory\NullObjectFactory();
+
+$proxy = $factory->createProxy('My\EntityObject');
+
+$proxy->getName(); // empty return
+        
+    
+ +

A Null Object Proxy can be created from an object, a class name or an interface name:

+
+        
+$factory = new \ProxyManager\Factory\NullObjectFactory();
+
+$proxy = $factory->createProxy('My\EntityObjectInterface');
+$proxy->getName(); // empty return
+
+$proxy = $factory->createProxy($entity); // created from object
+$proxy->getName(); // empty return
+        
+    
+ +

See the complete documentation about null object proxy.

+ +
+ +

Ghost Objects

+ +

Similar to value holder, a ghost object is usually created to handle lazy loading.

+ +

The difference between a value holder and a ghost object is that the ghost + object does not contain a real instance of the required object, but handles + lazy loading by initializing its own inherited properties.

+ +

ProxyManager can generate + lazy loading ghost objects, + which are proxies used to save performance and memory for large datasets and + graphs representing relational data. Ghost objects are particularly useful + when building data-mappers.

+ +

Additionally, the overhead introduced by ghost objects is very low when + compared to the memory and performance overhead caused by virtual proxies.

+ +
+        
+$factory = new \ProxyManager\Factory\LazyLoadingGhostFactory();
+
+$proxy = $factory->createProxy(
+    'MyApp\HeavyComplexObject',
+    function ($proxy, $method, $parameters, & $initializer) {
+        $initializer   = null; // turning off further lazy initialization
+
+        // modify the proxy instance
+        $proxy->setFoo('foo');
+        $proxy->setBar('bar');
+
+        return true;
+    }
+);
+
+$proxy->doFoo();
+        
+    
+ +

See the complete documentation about lazy loading ghost objects.

+ +
+ +

Remote Object

+ +

A remote object proxy is an object that is located on a different system, + but is used as if it was available locally. There's various possible + remote proxy implementations, which could be based on xmlrpc/jsonrpc/soap/dnode/etc.

+ +

This example uses the XML-RPC client of Zend Framework 2:

+ +
+        
+interface FooServiceInterface
+{
+    public function foo();
+}
+
+$factory = new \ProxyManager\Factory\RemoteObjectFactory(
+    new \ProxyManager\Factory\RemoteObject\Adapter\XmlRpc(
+        new \Zend\XmlRpc\Client('https://example.com/rpc-endpoint')
+    )
+);
+
+// proxy is your remote implementation
+$proxy = $factory->createProxy('FooServiceInterface');
+
+var_dump($proxy->foo());
+        
+    
+ +

See the complete documentation about remote objects.

+
+ + +

Contributing

+

Please read the CONTRIBUTING contents if you wish to help out!

+ +
+ +

Credits

+ +

The idea was originated by a talk about Proxies in PHP OOP that I gave at the @phpugffm in January 2013.

+ + +
+ +
+
+
+ + +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/null-object.html b/vendor/ocramius/proxy-manager/html-docs/null-object.html new file mode 100644 index 0000000..5c2476b --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/null-object.html @@ -0,0 +1,185 @@ + + + + ProxyManager - Null object + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Null Object Proxy

+

A Null Object proxy is a null object pattern implementation. The proxy factory creates a new object with defined neutral behavior based on an other object, class name or interface.

+
+ +

What is null object proxy ?

+ +

In your application, when you can't return the object related to the request, the consumer of the model must check for the return value and handle the failing condition gracefully, thus generating an explosion of conditionals throughout your code. Fortunately, this seemingly-tangled situation can be sorted out simply by creating a polymorphic implementation of the domain object, which would implement the same interface as the one of the object in question, only that its methods wouldn’t do anything, therefore offloading client code from doing repetitive checks for ugly null values when the operation is executed.

+ +
+ +

Usage examples

+ +
+        
+class UserMapper
+{   
+    private $adapter;
+
+    public function __construct(DatabaseAdapterInterface $adapter) {
+        $this->adapter = $adapter;
+    }
+
+    public function fetchById($id) {
+        $this->adapter->select("users", array("id" => $id));
+        if (!$row = $this->adapter->fetch()) {
+            return null;
+        }
+        return $this->createUser($row);
+    }
+
+    private function createUser(array $row) {
+        $user = new Entity\User($row["name"], $row["email"]);
+        $user->setId($row["id"]);
+        return $user;
+    }
+}
+        
+    
+ +

If you want to remove conditionals from client code, you need to have a version of the entity conforming to the corresponding interface. With the Null Object Proxy, you can build this object :

+ +
+        
+$factory = new \ProxyManager\Factory\NullObjectFactory();
+
+$nullUser = $factory->createProxy('Entity\User');
+
+var_dump($nullUser->getName()); // empty return
+        
+    
+ +

You can now return a valid entity :

+ +
+        
+class UserMapper
+{   
+    private $adapter;
+
+    public function __construct(DatabaseAdapterInterface $adapter) {
+        $this->adapter = $adapter;
+    }
+
+    public function fetchById($id) {
+        $this->adapter->select("users", array("id" => $id));
+        return $this->createUser($this->adapter->fetch());
+    }
+
+    private function createUser($row) {
+        if (!$row) {
+            $factory = new \ProxyManager\Factory\NullObjectFactory();
+
+            return $factory->createProxy('Entity\User');
+        }
+        $user = new Entity\User($row["name"], $row["email"]);
+        $user->setId($row["id"]);
+        return $user; 
+    }
+}
+        
+    
+ + +
+ +

Proxying interfaces

+ +

You can also generate proxies from an interface FQCN. By proxying an interface, you will only be able to access the methods defined by the interface itself, and like with the object, the methods are empty.

+ +

Tuning performance for production

+ +

See Tuning ProxyManager for Production.

+ +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/production.html b/vendor/ocramius/proxy-manager/html-docs/production.html new file mode 100644 index 0000000..39c086b --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/production.html @@ -0,0 +1,114 @@ + + + + ProxyManager - Tuning the ProxyManager for production + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Tuning the ProxyManager for production

+ +

By default, all proxy factories generate the required proxy classes at runtime.

+ +

Proxy generation causes I/O operations and uses a lot of reflection, so be sure to have generated all of your proxies before deploying your code on a live system, or you may experience poor performance.

+ +

You can configure ProxyManager so that it will try autoloading the proxies first. Generating them "bulk" is not yet implemented:

+ +
+        
+$config = new \ProxyManager\Configuration();
+$config->setProxiesTargetDir(__DIR__ . '/my/generated/classes/cache/dir');
+
+// then register the autoloader
+spl_autoload_register($config->getProxyAutoloader());
+        
+    
+ +

Generating a classmap with all your proxy classes in it will also work perfectly.

+ +

Please note that all the currently implemented ProxyManager\Factory\* classes accept a ProxyManager\Configuration object as optional constructor parameter. This allows for fine-tuning of ProxyManager according to your needs.

+ +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/remote-object.html b/vendor/ocramius/proxy-manager/html-docs/remote-object.html new file mode 100644 index 0000000..3fc627d --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/remote-object.html @@ -0,0 +1,203 @@ + + + + ProxyManager - Remote object + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Remote Object Proxy

+ +

The remote object implementation is a mechanism that enables an local object to control an other object on an other server. Each call method on the local object will do a network call to get information or execute operations on the remote object.

+
+ +

What is remote object proxy ?

+ +

A remote object is based on an interface. The remote interface defines the API that a consumer can call. This interface must be implemented both by the client and the RPC server.

+
+ +

Adapters

+ +

ZendFramework's RPC components (XmlRpc, JsonRpc & Soap) can be used easily with the remote object. You will need to require the one you need via composer, though:

+ +
+        
+$ php composer.phar require zendframework/zend-xmlrpc:2.*
+$ php composer.phar require zendframework/zend-json:2.*
+$ php composer.phar require zendframework/zend-soap:2.*
+        
+    
+ +

ProxyManager comes with 3 adapters:

+ +
    +
  • ProxyManager\Factory\RemoteObject\Adapter\XmlRpc
  • +
  • ProxyManager\Factory\RemoteObject\Adapter\JsonRpc
  • +
  • ProxyManager\Factory\RemoteObject\Adapter\Soap
  • +
+ +
+ +

Usage examples

+ +

RPC server side code (xmlrpc.php in your local webroot):

+ +
+        
+interface FooServiceInterface
+{
+    public function foo();
+}
+
+class Foo implements FooServiceInterface
+{
+    /**
+     * Foo function
+     * @return string
+     */
+    public function foo()
+    {
+        return 'bar remote';
+    }
+}
+
+$server = new Zend\XmlRpc\Server();
+$server->setClass('Foo', 'FooServiceInterface');  // my FooServiceInterface implementation
+$server->handle();
+        
+    
+ +

Client side code (proxy) :

+ +
+        
+interface FooServiceInterface
+{
+    public function foo();
+}
+
+$factory = new \ProxyManager\Factory\RemoteObjectFactory(
+    new \ProxyManager\Factory\RemoteObject\Adapter\XmlRpc(
+        new \Zend\XmlRpc\Client('https://localhost/xmlrpc.php')
+    )
+);
+
+$proxy = $factory->createProxy('FooServiceInterface');
+
+var_dump($proxy->foo()); // "bar remote"
+        
+    
+
+ +

Implementing custom adapters

+ +

Your adapters must implement ProxyManager\Factory\RemoteObject\AdapterInterface :

+ +
+        
+interface AdapterInterface
+{
+    /**
+     * Call remote object
+     *
+     * @param string $wrappedClass
+     * @param string $method
+     * @param array $params
+     *
+     * @return mixed
+     */
+    public function call($wrappedClass, $method, array $params = array());
+}
+        
+    
+ +

It is very easy to create your own implementation (for RESTful web services, for example). Simply pass your own adapter instance to your factory at construction time

+ +

Tuning performance for production

+ +

See Tuning ProxyManager for Production.

+ +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/html-docs/virtual-proxy.html b/vendor/ocramius/proxy-manager/html-docs/virtual-proxy.html new file mode 100644 index 0000000..5dbb038 --- /dev/null +++ b/vendor/ocramius/proxy-manager/html-docs/virtual-proxy.html @@ -0,0 +1,305 @@ + + + + ProxyManager - Virtual Proxy + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + +
+
+
+
+
+
+ + +
+
+

Lazy Loading Value Holder Proxy

+

A lazy loading value holder proxy is a virtual proxy that wraps and lazily initializes a "real" instance of the proxied class.

+
+ +

What is lazy loading?

+ +

In pseudo-code, in userland, lazy loading looks like following:

+ +
+        
+class MyObjectProxy
+{
+    private $wrapped;
+
+    public function doFoo()
+    {
+        $this->init();
+
+        return $this->wrapped->doFoo();
+    }
+
+    private function init()
+    {
+        if (null === $this->wrapped) {
+            $this->wrapped = new MyObject();
+        }
+    }
+}
+    
+
+ +

This code is problematic, and adds a lot of complexity that makes your unit tests' code even worse.

+ +

Also, this kind of usage often ends up in coupling your code with a particular Dependency Injection Container or a framework that fetches dependencies for you. That way, further complexity is introduced, and some problems related with service location raise, as I've explained in this article.

+ +

Lazy loading value holders abstract this logic for you, hiding your complex, slow, performance-impacting objects behind tiny wrappers that have their same API, and that get initialized at first usage.

+ +
+ +

When do I use a lazy value holder?

+ +

You usually need a lazy value holder in cases where following applies

+ +
    +
  • your object takes a lot of time and memory to be initialized (with all dependencies)
  • +
  • your object is not always used, and the instantiation overhead can be avoided
  • +
+ +
+ +

Usage examples

+ +

ProxyManager provides a factory that eases instantiation of lazy loading value holders. To use it, follow these steps:

+ +

First of all, define your object's logic without taking care of lazy loading:

+ +
+    
+namespace MyApp;
+
+class HeavyComplexObject
+{
+    public function __construct()
+    {
+        // just write your business logic
+        // don't worry about how heavy initialization of this will be!
+    }
+
+    public function doFoo() {
+        echo "OK!"
+    }
+}
+    
+
+ +

Then use the proxy manager to create a lazy version of the object (as a proxy):

+ +
+    
+namespace MyApp;
+
+use ProxyManager\Factory\LazyLoadingValueHolderFactory;
+use ProxyManager\Proxy\LazyLoadingInterface;
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+$factory     = new LazyLoadingValueHolderFactory();
+$initializer = function (& $wrappedObject, LazyLoadingInterface $proxy, $method, array $parameters, & $initializer) {
+    $initializer   = null; // disable initialization
+    $wrappedObject = new HeavyComplexObject(); // fill your object with values here
+
+    return true; // confirm that initialization occurred correctly
+};
+
+$instance = $factory->createProxy('MyApp\HeavyComplexObject', $initializer);
+    
+
+ +

You can now simply use your object as before:

+ +
+    
+// this will just work as before
+$proxy->doFoo(); // OK!
+    
+
+
+ + +

Lazy Initialization

+ +

As you can see, we use a closure to handle lazy initialization of the proxy instance at runtime. The initializer closure signature should be as following:

+ +
+    
+/**
+ * @var object  $wrappedObject the instance (passed by reference) of the wrapped object,
+ *                             set it to your real object
+ * @var object  $proxy         the instance proxy that is being initialized
+ * @var string  $method        the name of the method that triggered lazy initialization
+ * @var string  $parameters    an ordered list of parameters passed to the method that
+ *                             triggered initialization, indexed by parameter name
+ * @var Closure $initializer   a reference to the property that is the initializer for the
+ *                             proxy. Set it to null to disable further initialization
+ *
+ * @return bool true on success
+ */
+$initializer = function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) {};
+    
+
+ +

The initializer closure should usually be coded like following:

+ +
+    
+$initializer = function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) {
+    $newlyCreatedObject = new Foo(); // instantiation logic
+    $newlyCreatedObject->setBar('baz') // instantiation logic
+    $newlyCreatedObject->setBat('bam') // instantiation logic
+
+    $wrappedObject = $newlyCreatedObject; // set wrapped object in the proxy
+    $initializer   = null; // disable initializer
+
+    return true; // report success
+};
+    
+
+ +

The ProxyManager\Factory\LazyLoadingValueHolderFactory produces proxies that implement both the ProxyManager\Proxy\ValueHolderInterface and the ProxyManager\Proxy\LazyLoadingInterface.

+ +

At any point in time, you can set a new initializer for the proxy:

+ +
$proxy->setProxyInitializer($initializer);
+ +

In your initializer, you currently MUST turn off any further initialization:

+ +
$proxy->setProxyInitializer(null);
+ +

or

+ +
$initializer = null; // if you use the initializer by reference
+
+ +

Triggering Initialization

+ +

A lazy loading proxy is initialized whenever you access any property or method of it. Any of the following interactions would trigger lazy initialization:

+ +
+    
+// calling a method
+$proxy->someMethod();
+
+// reading a property
+echo $proxy->someProperty;
+
+// writing a property
+$proxy->someProperty = 'foo';
+
+// checking for existence of a property
+isset($proxy->someProperty);
+
+// removing a property
+unset($proxy->someProperty);
+
+// cloning the entire proxy
+clone $proxy;
+
+// serializing the proxy
+$unserialized = serialize(unserialize($proxy));
+    
+
+ +

Remember to call $proxy->setProxyInitializer(null); to disable initialization of your proxy, or it will happen more than once.

+ + + + + + + + +
+ +

Proxying interfaces

+ +

You can also generate proxies from an interface FQCN. By proxying an interface, you will only be able to access the methods defined by the interface itself, even if the wrappedObject implements more methods. This will anyway save some memory since the proxy won't contain useless inherited properties.

+ +

Tuning performance for production

+ +

See Tuning ProxyManager for Production.

+ + + +
+ + +
+ + diff --git a/vendor/ocramius/proxy-manager/index.html b/vendor/ocramius/proxy-manager/index.html new file mode 100644 index 0000000..da417f7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/index.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/vendor/ocramius/proxy-manager/phpdox.xml.dist b/vendor/ocramius/proxy-manager/phpdox.xml.dist new file mode 100644 index 0000000..6cbf78b --- /dev/null +++ b/vendor/ocramius/proxy-manager/phpdox.xml.dist @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/vendor/ocramius/proxy-manager/phpmd.xml.dist b/vendor/ocramius/proxy-manager/phpmd.xml.dist new file mode 100644 index 0000000..ae91e86 --- /dev/null +++ b/vendor/ocramius/proxy-manager/phpmd.xml.dist @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/vendor/ocramius/proxy-manager/phpunit.xml.dist b/vendor/ocramius/proxy-manager/phpunit.xml.dist new file mode 100644 index 0000000..b595427 --- /dev/null +++ b/vendor/ocramius/proxy-manager/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + ./tests/ProxyManagerTest + + + ./tests/language-feature-scripts + + + + ./src + + + diff --git a/vendor/ocramius/proxy-manager/proxy-manager.png b/vendor/ocramius/proxy-manager/proxy-manager.png new file mode 100644 index 0000000..0ab1c6e Binary files /dev/null and b/vendor/ocramius/proxy-manager/proxy-manager.png differ diff --git a/vendor/ocramius/proxy-manager/proxy-manager.svg b/vendor/ocramius/proxy-manager/proxy-manager.svg new file mode 100644 index 0000000..f74aa91 --- /dev/null +++ b/vendor/ocramius/proxy-manager/proxy-manager.svg @@ -0,0 +1,346 @@ + + + + + Logo - Ocramius Proxy Manager + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Logo - Ocramius Proxy Manager + November 2013 + + + Maestro Pivetta + + + + + MIT + + + + + Marco Pivetta + + + Logo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Autoloader/Autoloader.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Autoloader/Autoloader.php new file mode 100644 index 0000000..91a6fc2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Autoloader/Autoloader.php @@ -0,0 +1,69 @@ + + * @license MIT + */ +class Autoloader implements AutoloaderInterface +{ + /** + * @var \ProxyManager\FileLocator\FileLocatorInterface + */ + protected $fileLocator; + + /** + * @var \ProxyManager\Inflector\ClassNameInflectorInterface + */ + protected $classNameInflector; + + /** + * @param \ProxyManager\FileLocator\FileLocatorInterface $fileLocator + * @param \ProxyManager\Inflector\ClassNameInflectorInterface $classNameInflector + */ + public function __construct(FileLocatorInterface $fileLocator, ClassNameInflectorInterface $classNameInflector) + { + $this->fileLocator = $fileLocator; + $this->classNameInflector = $classNameInflector; + } + + /** + * {@inheritDoc} + */ + public function __invoke($className) + { + if (class_exists($className, false) || ! $this->classNameInflector->isProxyClassName($className)) { + return false; + } + + $file = $this->fileLocator->getProxyFileName($className); + + if (! file_exists($file)) { + return false; + } + + return (bool) require_once $file; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Autoloader/AutoloaderInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Autoloader/AutoloaderInterface.php new file mode 100644 index 0000000..9670f98 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Autoloader/AutoloaderInterface.php @@ -0,0 +1,37 @@ + + * @license MIT + */ +interface AutoloaderInterface +{ + /** + * Callback to allow the object to be handled as autoloader - tries to autoload the given class name + * + * @param string $className + * + * @return bool + */ + public function __invoke($className); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Configuration.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Configuration.php new file mode 100644 index 0000000..3bf1d9a --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Configuration.php @@ -0,0 +1,241 @@ + + * @license MIT + */ +class Configuration +{ + const DEFAULT_PROXY_NAMESPACE = 'ProxyManagerGeneratedProxy'; + + /** + * @var string|null + */ + protected $proxiesTargetDir; + + /** + * @var string + */ + protected $proxiesNamespace = self::DEFAULT_PROXY_NAMESPACE; + + /** + * @var GeneratorStrategyInterface|null + */ + protected $generatorStrategy; + + /** + * @var callable|null + */ + protected $proxyAutoloader; + + /** + * @var ClassNameInflectorInterface|null + */ + protected $classNameInflector; + + /** + * @var SignatureGeneratorInterface|null + */ + protected $signatureGenerator; + + /** + * @var SignatureCheckerInterface|null + */ + protected $signatureChecker; + + /** + * @var ClassSignatureGeneratorInterface|null + */ + protected $classSignatureGenerator; + + /** + * @deprecated deprecated since version 0.5 + * @codeCoverageIgnore + */ + public function setAutoGenerateProxies() + { + } + + /** + * @return bool + * + * @deprecated deprecated since version 0.5 + * @codeCoverageIgnore + */ + public function doesAutoGenerateProxies() + { + return true; + } + + /** + * @param AutoloaderInterface $proxyAutoloader + */ + public function setProxyAutoloader(AutoloaderInterface $proxyAutoloader) + { + $this->proxyAutoloader = $proxyAutoloader; + } + + /** + * @return AutoloaderInterface + */ + public function getProxyAutoloader() + { + return $this->proxyAutoloader + ?: $this->proxyAutoloader = new Autoloader( + new FileLocator($this->getProxiesTargetDir()), + $this->getClassNameInflector() + ); + } + + /** + * @param string $proxiesNamespace + */ + public function setProxiesNamespace($proxiesNamespace) + { + $this->proxiesNamespace = $proxiesNamespace; + } + + /** + * @return string + */ + public function getProxiesNamespace() + { + return $this->proxiesNamespace; + } + + /** + * @param string $proxiesTargetDir + */ + public function setProxiesTargetDir($proxiesTargetDir) + { + $this->proxiesTargetDir = (string) $proxiesTargetDir; + } + + /** + * @return string + */ + public function getProxiesTargetDir() + { + return $this->proxiesTargetDir ?: $this->proxiesTargetDir = sys_get_temp_dir(); + } + + /** + * @param GeneratorStrategyInterface $generatorStrategy + */ + public function setGeneratorStrategy(GeneratorStrategyInterface $generatorStrategy) + { + $this->generatorStrategy = $generatorStrategy; + } + + /** + * @return GeneratorStrategyInterface + */ + public function getGeneratorStrategy() + { + return $this->generatorStrategy + ?: $this->generatorStrategy = new FileWriterGeneratorStrategy( + new FileLocator($this->getProxiesTargetDir()) + ); + } + + /** + * @param ClassNameInflectorInterface $classNameInflector + */ + public function setClassNameInflector(ClassNameInflectorInterface $classNameInflector) + { + $this->classNameInflector = $classNameInflector; + } + + /** + * @return ClassNameInflectorInterface + */ + public function getClassNameInflector() + { + return $this->classNameInflector + ?: $this->classNameInflector = new ClassNameInflector($this->getProxiesNamespace()); + } + + /** + * @param SignatureGeneratorInterface $signatureGenerator + */ + public function setSignatureGenerator(SignatureGeneratorInterface $signatureGenerator) + { + $this->signatureGenerator = $signatureGenerator; + } + + /** + * @return SignatureGeneratorInterface + */ + public function getSignatureGenerator() + { + return $this->signatureGenerator ?: $this->signatureGenerator = new SignatureGenerator(); + } + + /** + * @param SignatureCheckerInterface $signatureChecker + */ + public function setSignatureChecker(SignatureCheckerInterface $signatureChecker) + { + $this->signatureChecker = $signatureChecker; + } + + /** + * @return SignatureCheckerInterface + */ + public function getSignatureChecker() + { + return $this->signatureChecker + ?: $this->signatureChecker = new SignatureChecker($this->getSignatureGenerator()); + } + + /** + * @param ClassSignatureGeneratorInterface $classSignatureGenerator + */ + public function setClassSignatureGenerator(ClassSignatureGeneratorInterface $classSignatureGenerator) + { + $this->classSignatureGenerator = $classSignatureGenerator; + } + + /** + * @return ClassSignatureGeneratorInterface + */ + public function getClassSignatureGenerator() + { + return $this->classSignatureGenerator + ?: new ClassSignatureGenerator($this->getSignatureGenerator()); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/DisabledMethodException.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/DisabledMethodException.php new file mode 100644 index 0000000..42f4eda --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/DisabledMethodException.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class DisabledMethodException extends BadMethodCallException implements ExceptionInterface +{ + const NAME = __CLASS__; + + /** + * @param string $method + * + * @return self + */ + public static function disabledMethod($method) + { + return new self(sprintf('Method "%s" is forcefully disabled', (string) $method)); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/ExceptionInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/ExceptionInterface.php new file mode 100644 index 0000000..34c7d86 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/ExceptionInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface ExceptionInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/FileNotWritableException.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/FileNotWritableException.php new file mode 100644 index 0000000..7d9a99f --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/FileNotWritableException.php @@ -0,0 +1,69 @@ + + * @license MIT + */ +class FileNotWritableException extends UnexpectedValueException implements ExceptionInterface +{ + /** + * @param string $fromPath + * @param string $toPath + * + * @return self + */ + public static function fromInvalidMoveOperation($fromPath, $toPath) + { + return new self(sprintf( + 'Could not move file "%s" to location "%s": ' + . 'either the source file is not readable, or the destination is not writable', + $fromPath, + $toPath + )); + } + + /** + * @param string $path + * + * @return self + */ + public static function fromNonWritableLocation($path) + { + $messages = array(); + + if (($destination = realpath($path)) && ! is_file($destination)) { + $messages[] = 'exists and is not a file'; + } + + if (! is_writable($destination)) { + $messages[] = 'is not writable'; + } + + return new self(sprintf('Could not write to path "%s": %s', $path, implode(', ', $messages))); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxiedClassException.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxiedClassException.php new file mode 100644 index 0000000..5dc08d1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxiedClassException.php @@ -0,0 +1,79 @@ + + * @license MIT + */ +class InvalidProxiedClassException extends InvalidArgumentException implements ExceptionInterface +{ + /** + * @param ReflectionClass $reflection + * + * @return self + */ + public static function interfaceNotSupported(ReflectionClass $reflection) + { + return new self(sprintf('Provided interface "%s" cannot be proxied', $reflection->getName())); + } + + /** + * @param ReflectionClass $reflection + * + * @return self + */ + public static function finalClassNotSupported(ReflectionClass $reflection) + { + return new self(sprintf('Provided class "%s" is final and cannot be proxied', $reflection->getName())); + } + + /** + * @param ReflectionClass $reflection + * + * @return self + */ + public static function abstractProtectedMethodsNotSupported(ReflectionClass $reflection) + { + return new self(sprintf( + 'Provided class "%s" has following protected abstract methods, and therefore cannot be proxied:' . "\n%s", + $reflection->getName(), + implode( + "\n", + array_map( + function (ReflectionMethod $reflectionMethod) { + return $reflectionMethod->getDeclaringClass()->getName() . '::' . $reflectionMethod->getName(); + }, + array_filter( + $reflection->getMethods(), + function (ReflectionMethod $method) { + return $method->isAbstract() && $method->isProtected(); + } + ) + ) + ) + )); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxyDirectoryException.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxyDirectoryException.php new file mode 100644 index 0000000..cb5d0ea --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/InvalidProxyDirectoryException.php @@ -0,0 +1,40 @@ + + * @license MIT + */ +class InvalidProxyDirectoryException extends InvalidArgumentException implements ExceptionInterface +{ + /** + * @param string $directory + * + * @return self + */ + public static function proxyDirectoryNotFound($directory) + { + return new self(sprintf('Provided directory "%s" does not exist', (string) $directory)); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/UnsupportedProxiedClassException.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/UnsupportedProxiedClassException.php new file mode 100644 index 0000000..68f8215 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Exception/UnsupportedProxiedClassException.php @@ -0,0 +1,47 @@ + + * @license MIT + */ +class UnsupportedProxiedClassException extends LogicException implements ExceptionInterface +{ + /** + * @param ReflectionProperty $property + * + * @return self + */ + public static function unsupportedLocalizedReflectionProperty(ReflectionProperty $property) + { + return new self( + sprintf( + 'Provided reflection property "%s" of class "%s" is private and cannot be localized in PHP 5.3', + $property->getName(), + $property->getDeclaringClass()->getName() + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractBaseFactory.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractBaseFactory.php new file mode 100644 index 0000000..cbc7b1a --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractBaseFactory.php @@ -0,0 +1,112 @@ + + * @license MIT + */ +abstract class AbstractBaseFactory +{ + /** + * @var \ProxyManager\Configuration + */ + protected $configuration; + + /** + * Cached checked class names + * + * @var string[] + */ + private $checkedClasses = array(); + + /** + * @param \ProxyManager\Configuration $configuration + */ + public function __construct(Configuration $configuration = null) + { + $this->configuration = $configuration ?: new Configuration(); + } + + /** + * Generate a proxy from a class name + * @param string $className + * @return string proxy class name + */ + protected function generateProxy($className) + { + if (isset($this->checkedClasses[$className])) { + return $this->checkedClasses[$className]; + } + + $proxyParameters = array( + 'className' => $className, + 'factory' => get_class($this), + 'proxyManagerVersion' => Version::VERSION + ); + $proxyClassName = $this + ->configuration + ->getClassNameInflector() + ->getProxyClassName($className, $proxyParameters); + + if (! class_exists($proxyClassName)) { + $this->generateProxyClass($proxyClassName, $className, $proxyParameters); + } + + $this + ->configuration + ->getSignatureChecker() + ->checkSignature(new ReflectionClass($proxyClassName), $proxyParameters); + + return $this->checkedClasses[$className] = $proxyClassName; + } + + /** + * @return \ProxyManager\ProxyGenerator\ProxyGeneratorInterface + */ + abstract protected function getGenerator(); + + /** + * Generates the provided `$proxyClassName` from the given `$className` and `$proxyParameters` + * @param string $proxyClassName + * @param string $className + * @param array $proxyParameters + * + * @return void + */ + private function generateProxyClass($proxyClassName, $className, array $proxyParameters) + { + $className = $this->configuration->getClassNameInflector()->getUserClassName($className); + $phpClass = new ClassGenerator($proxyClassName); + + $this->getGenerator()->generate(new ReflectionClass($className), $phpClass); + + $phpClass = $this->configuration->getClassSignatureGenerator()->addSignature($phpClass, $proxyParameters); + + $this->configuration->getGeneratorStrategy()->generate($phpClass); + $this->configuration->getProxyAutoloader()->__invoke($proxyClassName); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractLazyFactory.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractLazyFactory.php new file mode 100644 index 0000000..690bfa1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AbstractLazyFactory.php @@ -0,0 +1,46 @@ + + * @license MIT + */ +abstract class AbstractLazyFactory extends AbstractBaseFactory +{ + /** + * Creates a new lazy proxy instance of the given class with + * the given initializer + * + * @param string $className name of the class to be proxied + * @param \Closure $initializer initializer to be passed to the proxy + * + * @return \ProxyManager\Proxy\LazyLoadingInterface + */ + public function createProxy($className, Closure $initializer) + { + $proxyClassName = $this->generateProxy($className); + + return new $proxyClassName($initializer); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorScopeLocalizerFactory.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorScopeLocalizerFactory.php new file mode 100644 index 0000000..57d78e7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorScopeLocalizerFactory.php @@ -0,0 +1,59 @@ + + * @license MIT + */ +class AccessInterceptorScopeLocalizerFactory extends AbstractBaseFactory +{ + /** + * @var \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizerGenerator|null + */ + private $generator; + + /** + * @param object $instance the object to be localized within the access interceptor + * @param \Closure[] $prefixInterceptors an array (indexed by method name) of interceptor closures to be called + * before method logic is executed + * @param \Closure[] $suffixInterceptors an array (indexed by method name) of interceptor closures to be called + * after method logic is executed + * + * @return \ProxyManager\Proxy\AccessInterceptorInterface + */ + public function createProxy($instance, array $prefixInterceptors = array(), array $suffixInterceptors = array()) + { + $proxyClassName = $this->generateProxy(get_class($instance)); + + return new $proxyClassName($instance, $prefixInterceptors, $suffixInterceptors); + } + + /** + * {@inheritDoc} + */ + protected function getGenerator() + { + return $this->generator ?: $this->generator = new AccessInterceptorScopeLocalizerGenerator(); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorValueHolderFactory.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorValueHolderFactory.php new file mode 100644 index 0000000..d5ef504 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/AccessInterceptorValueHolderFactory.php @@ -0,0 +1,59 @@ + + * @license MIT + */ +class AccessInterceptorValueHolderFactory extends AbstractBaseFactory +{ + /** + * @var \ProxyManager\ProxyGenerator\AccessInterceptorValueHolderGenerator|null + */ + private $generator; + + /** + * @param object $instance the object to be wrapped within the value holder + * @param \Closure[] $prefixInterceptors an array (indexed by method name) of interceptor closures to be called + * before method logic is executed + * @param \Closure[] $suffixInterceptors an array (indexed by method name) of interceptor closures to be called + * after method logic is executed + * + * @return \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface + */ + public function createProxy($instance, array $prefixInterceptors = array(), array $suffixInterceptors = array()) + { + $proxyClassName = $this->generateProxy(get_class($instance)); + + return new $proxyClassName($instance, $prefixInterceptors, $suffixInterceptors); + } + + /** + * {@inheritDoc} + */ + protected function getGenerator() + { + return $this->generator ?: $this->generator = new AccessInterceptorValueHolderGenerator(); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingGhostFactory.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingGhostFactory.php new file mode 100644 index 0000000..73e030c --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingGhostFactory.php @@ -0,0 +1,45 @@ + + * @license MIT + * + * @method \ProxyManager\Proxy\GhostObjectInterface createProxy($className, \Closure $initializer) + */ +class LazyLoadingGhostFactory extends AbstractLazyFactory +{ + /** + * @var \ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator|null + */ + private $generator; + + /** + * {@inheritDoc} + */ + protected function getGenerator() + { + return $this->generator ?: $this->generator = new LazyLoadingGhostGenerator(); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingValueHolderFactory.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingValueHolderFactory.php new file mode 100644 index 0000000..c1449be --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/LazyLoadingValueHolderFactory.php @@ -0,0 +1,45 @@ + + * @license MIT + * + * @method \ProxyManager\Proxy\VirtualProxyInterface createProxy($className, \Closure $initializer) + */ +class LazyLoadingValueHolderFactory extends AbstractLazyFactory +{ + /** + * @var \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator|null + */ + private $generator; + + /** + * {@inheritDoc} + */ + protected function getGenerator() + { + return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator(); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/NullObjectFactory.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/NullObjectFactory.php new file mode 100644 index 0000000..9c605cb --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/NullObjectFactory.php @@ -0,0 +1,56 @@ + + * @license MIT + */ +class NullObjectFactory extends AbstractBaseFactory +{ + /** + * @var \ProxyManager\ProxyGenerator\NullObjectGenerator|null + */ + private $generator; + + /** + * @param object $instanceOrClassName the object to be wrapped or interface to transform to null object + * + * @return \ProxyManager\Proxy\NullobjectInterface + */ + public function createProxy($instanceOrClassName) + { + $className = is_object($instanceOrClassName) ? get_class($instanceOrClassName) : $instanceOrClassName; + $proxyClassName = $this->generateProxy($className); + + return new $proxyClassName(); + } + + /** + * {@inheritDoc} + */ + protected function getGenerator() + { + return $this->generator ?: $this->generator = new NullObjectGenerator(); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/BaseAdapter.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/BaseAdapter.php new file mode 100644 index 0000000..814d327 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/BaseAdapter.php @@ -0,0 +1,81 @@ + + * @license MIT + */ +abstract class BaseAdapter implements AdapterInterface +{ + /** + * Adapter client + * + * @var \Zend\Server\Client + */ + protected $client; + + /** + * Service name mapping + * + * @var string[] + */ + protected $map = array(); + + /** + * Constructor + * + * @param Client $client + * @param array $map map of service names to their aliases + */ + public function __construct(Client $client, array $map = array()) + { + $this->client = $client; + $this->map = $map; + } + + /** + * {@inheritDoc} + */ + public function call($wrappedClass, $method, array $params = array()) + { + $serviceName = $this->getServiceName($wrappedClass, $method); + + if (isset($this->map[$serviceName])) { + $serviceName = $this->map[$serviceName]; + } + + return $this->client->call($serviceName, $params); + } + + /** + * Get the service name will be used by the adapter + * + * @param string $wrappedClass + * @param string $method + * + * @return string Service name + */ + abstract protected function getServiceName($wrappedClass, $method); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/JsonRpc.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/JsonRpc.php new file mode 100644 index 0000000..aff2700 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/JsonRpc.php @@ -0,0 +1,36 @@ + + * @license MIT + */ +class JsonRpc extends BaseAdapter +{ + /** + * {@inheritDoc} + */ + protected function getServiceName($wrappedClass, $method) + { + return $wrappedClass . '.' . $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/Soap.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/Soap.php new file mode 100644 index 0000000..69de386 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/Soap.php @@ -0,0 +1,36 @@ + + * @license MIT + */ +class Soap extends BaseAdapter +{ + /** + * {@inheritDoc} + */ + protected function getServiceName($wrappedClass, $method) + { + return (string) $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/XmlRpc.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/XmlRpc.php new file mode 100644 index 0000000..0485ccd --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/Adapter/XmlRpc.php @@ -0,0 +1,36 @@ + + * @license MIT + */ +class XmlRpc extends BaseAdapter +{ + /** + * {@inheritDoc} + */ + protected function getServiceName($wrappedClass, $method) + { + return $wrappedClass . '.' . $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/AdapterInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/AdapterInterface.php new file mode 100644 index 0000000..4168f9d --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObject/AdapterInterface.php @@ -0,0 +1,37 @@ + + * @license MIT + */ +interface AdapterInterface +{ + /** + * Call remote object + * + * @param string $wrappedClass + * @param string $method + * @param array $params + */ + public function call($wrappedClass, $method, array $params = array()); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObjectFactory.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObjectFactory.php new file mode 100644 index 0000000..52778d3 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Factory/RemoteObjectFactory.php @@ -0,0 +1,76 @@ + + * @license MIT + */ +class RemoteObjectFactory extends AbstractBaseFactory +{ + /** + * @var AdapterInterface + */ + protected $adapter; + + /** + * @var \ProxyManager\ProxyGenerator\RemoteObjectGenerator|null + */ + private $generator; + + /** + * {@inheritDoc} + * + * @param AdapterInterface $adapter + * @param Configuration $configuration + */ + public function __construct(AdapterInterface $adapter, Configuration $configuration = null) + { + parent::__construct($configuration); + + $this->adapter = $adapter; + } + + /** + * @param string|object $instanceOrClassName + * + * @return \ProxyManager\Proxy\RemoteObjectInterface + */ + public function createProxy($instanceOrClassName) + { + $className = is_object($instanceOrClassName) ? get_class($instanceOrClassName) : $instanceOrClassName; + $proxyClassName = $this->generateProxy($className); + + return new $proxyClassName($this->adapter); + } + + /** + * {@inheritDoc} + */ + protected function getGenerator() + { + return $this->generator ?: $this->generator = new RemoteObjectGenerator(); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocator.php new file mode 100644 index 0000000..197595e --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocator.php @@ -0,0 +1,57 @@ + + * @license MIT + */ +class FileLocator implements FileLocatorInterface +{ + /** + * @var string + */ + protected $proxiesDirectory; + + /** + * @param string $proxiesDirectory + * + * @throws \ProxyManager\Exception\InvalidProxyDirectoryException + */ + public function __construct($proxiesDirectory) + { + $this->proxiesDirectory = realpath($proxiesDirectory); + + if (false === $this->proxiesDirectory) { + throw InvalidProxyDirectoryException::proxyDirectoryNotFound($proxiesDirectory); + } + } + + /** + * {@inheritDoc} + */ + public function getProxyFileName($className) + { + return $this->proxiesDirectory . DIRECTORY_SEPARATOR . str_replace('\\', '', $className) . '.php'; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocatorInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocatorInterface.php new file mode 100644 index 0000000..c52ac02 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/FileLocator/FileLocatorInterface.php @@ -0,0 +1,37 @@ + + * @license MIT + */ +interface FileLocatorInterface +{ + /** + * Retrieves the file name for the given proxy + * + * @param string $className + * + * @return string + */ + public function getProxyFileName($className); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/ClassGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/ClassGenerator.php new file mode 100644 index 0000000..0304c42 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/ClassGenerator.php @@ -0,0 +1,54 @@ + + * @license MIT + */ +class ClassGenerator extends ZendClassGenerator +{ + /** + * {@inheritDoc} + */ + public function setExtendedClass($extendedClass) + { + if ($extendedClass) { + $extendedClass = '\\' . trim($extendedClass, '\\'); + } + + return parent::setExtendedClass($extendedClass); + } + + /** + * {@inheritDoc} + */ + public function setImplementedInterfaces(array $interfaces) + { + foreach ($interfaces as & $interface) { + $interface = '\\' . trim($interface, '\\'); + } + + return parent::setImplementedInterfaces($interfaces); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/MagicMethodGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/MagicMethodGenerator.php new file mode 100644 index 0000000..462aa8f --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/MagicMethodGenerator.php @@ -0,0 +1,52 @@ + + * @license MIT + */ +class MagicMethodGenerator extends MethodGenerator +{ + /** + * @param ReflectionClass $originalClass + * @param string $name + * @param array $parameters + */ + public function __construct(ReflectionClass $originalClass, $name, array $parameters = array()) + { + parent::__construct( + $name, + $parameters, + static::FLAG_PUBLIC, + null, + $originalClass->hasMethod($name) ? '{@inheritDoc}' : null + ); + + $this->setReturnsReference(strtolower($name) === '__get'); + + if ($originalClass->hasMethod($name)) { + $this->setReturnsReference($originalClass->getMethod($name)->returnsReference()); + } + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/MethodGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/MethodGenerator.php new file mode 100644 index 0000000..cffd6af --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/MethodGenerator.php @@ -0,0 +1,161 @@ + + * @license MIT + */ +class MethodGenerator extends ZendMethodGenerator +{ + /** + * @var bool + */ + protected $returnsReference = false; + + /** + * @param boolean $returnsReference + */ + public function setReturnsReference($returnsReference) + { + $this->returnsReference = (bool) $returnsReference; + } + + /** + * @return boolean + */ + public function returnsReference() + { + return $this->returnsReference; + } + + /** + * @override enforces generation of \ProxyManager\Generator\MethodGenerator + * + * {@inheritDoc} + */ + public static function fromReflection(MethodReflection $reflectionMethod) + { + /* @var $method self */ + $method = new static(); + + $method->setSourceContent($reflectionMethod->getContents(false)); + $method->setSourceDirty(false); + + if ($reflectionMethod->getDocComment() != '') { + $method->setDocBlock(DocBlockGenerator::fromReflection($reflectionMethod->getDocBlock())); + } + + $method->setFinal($reflectionMethod->isFinal()); + $method->setVisibility(self::extractVisibility($reflectionMethod)); + + foreach ($reflectionMethod->getParameters() as $reflectionParameter) { + $method->setParameter(ParameterGenerator::fromReflection($reflectionParameter)); + } + + $method->setStatic($reflectionMethod->isStatic()); + $method->setName($reflectionMethod->getName()); + $method->setBody($reflectionMethod->getBody()); + $method->setReturnsReference($reflectionMethod->returnsReference()); + + return $method; + } + + /** + * Retrieves the visibility for the given method reflection + * + * @param MethodReflection $reflectionMethod + * + * @return string + */ + private static function extractVisibility(MethodReflection $reflectionMethod) + { + if ($reflectionMethod->isPrivate()) { + return static::VISIBILITY_PRIVATE; + } + + if ($reflectionMethod->isProtected()) { + return static::VISIBILITY_PROTECTED; + } + + return static::VISIBILITY_PUBLIC; + } + + /** + * @override fixes by-reference return value in zf2's method generator + * + * {@inheritDoc} + */ + public function generate() + { + $output = ''; + $indent = $this->getIndentation(); + + if (null !== ($docBlock = $this->getDocBlock())) { + $docBlock->setIndentation($indent); + + $output .= $docBlock->generate(); + } + + $output .= $indent . $this->generateMethodDeclaration() . self::LINE_FEED . $indent . '{' . self::LINE_FEED; + + if ($this->body) { + $output .= preg_replace('#^(.+?)$#m', $indent . $indent . '$1', trim($this->body)) + . self::LINE_FEED; + } + + $output .= $indent . '}' . self::LINE_FEED; + + return $output; + } + + /** + * @return string + */ + private function generateMethodDeclaration() + { + $output = $this->generateVisibility() + . ' function ' + . (($this->returnsReference()) ? '& ' : '') + . $this->getName() . '('; + + $parameterOutput = array(); + + foreach ($this->getParameters() as $parameter) { + $parameterOutput[] = $parameter->generate(); + } + + return $output . implode(', ', $parameterOutput) . ')'; + } + + /** + * @return string + */ + private function generateVisibility() + { + return $this->isAbstract() ? 'abstract ' : (($this->isFinal()) ? 'final ' : '') + . ($this->getVisibility() . (($this->isStatic()) ? ' static' : '')); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/ParameterGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/ParameterGenerator.php new file mode 100644 index 0000000..7960d42 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/ParameterGenerator.php @@ -0,0 +1,149 @@ + + * @license MIT + */ +class ParameterGenerator extends ZendParameterGenerator +{ + /** + * @override - uses `static` to instantiate the parameter + * + * {@inheritDoc} + */ + public static function fromReflection(ParameterReflection $reflectionParameter) + { + /* @var $param self */ + $param = new static(); + + $param->setName($reflectionParameter->getName()); + $param->setPosition($reflectionParameter->getPosition()); + + $type = self::extractParameterType($reflectionParameter); + + if (null !== $type) { + $param->setType($type); + } + + self::setOptionalParameter($param, $reflectionParameter); + + $param->setPassedByReference($reflectionParameter->isPassedByReference()); + + return $param; + } + + /** + * Retrieves the type of a reflection parameter (null if none is found) + * + * @param ParameterReflection $reflectionParameter + * + * @return string|null + */ + private static function extractParameterType(ParameterReflection $reflectionParameter) + { + if ($reflectionParameter->isArray()) { + return 'array'; + } + + if (method_exists($reflectionParameter, 'isCallable') && $reflectionParameter->isCallable()) { + return 'callable'; + } + + if ($typeClass = $reflectionParameter->getClass()) { + return $typeClass->getName(); + } + + return null; + } + + /** + * @return string + */ + public function generate() + { + return $this->getGeneratedType() + . (true === $this->passedByReference ? '&' : '') + . '$' . $this->name + . $this->generateDefaultValue(); + } + + /** + * @return string + */ + private function generateDefaultValue() + { + if (null === $this->defaultValue) { + return ''; + } + + $defaultValue = $this->defaultValue instanceof ValueGenerator + ? $this->defaultValue + : new ValueGenerator($this->defaultValue); + + $defaultValue->setOutputMode(ValueGenerator::OUTPUT_SINGLE_LINE); + + return ' = ' . $defaultValue; + } + + /** + * Retrieves the generated parameter type + * + * @return string + */ + private function getGeneratedType() + { + if (! $this->type || in_array($this->type, static::$simple)) { + return ''; + } + + if ('array' === $this->type || 'callable' === $this->type) { + return $this->type . ' '; + } + + return '\\' . trim($this->type, '\\') . ' '; + } + + /** + * Set the default value for a parameter (if it is optional) + * + * @param ZendParameterGenerator $parameterGenerator + * @param ParameterReflection $reflectionParameter + */ + private static function setOptionalParameter( + ZendParameterGenerator $parameterGenerator, + ParameterReflection $reflectionParameter + ) { + if ($reflectionParameter->isOptional()) { + try { + $parameterGenerator->setDefaultValue($reflectionParameter->getDefaultValue()); + } catch (ReflectionException $e) { + $parameterGenerator->setDefaultValue(null); + } + } + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/Util/ClassGeneratorUtils.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/Util/ClassGeneratorUtils.php new file mode 100644 index 0000000..62e130c --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/Util/ClassGeneratorUtils.php @@ -0,0 +1,53 @@ + + * @license MIT + */ +final class ClassGeneratorUtils +{ + /** + * @param ReflectionClass $originalClass + * @param ClassGenerator $classGenerator + * @param MethodGenerator $generatedMethod + * + * @return void|false + */ + public static function addMethodIfNotFinal( + ReflectionClass $originalClass, + ClassGenerator $classGenerator, + MethodGenerator $generatedMethod + ) { + $methodName = $generatedMethod->getName(); + + if ($originalClass->hasMethod($methodName) && $originalClass->getMethod($methodName)->isFinal()) { + return false; + } + + $classGenerator->addMethodFromGenerator($generatedMethod); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/Util/UniqueIdentifierGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/Util/UniqueIdentifierGenerator.php new file mode 100644 index 0000000..4bb2b77 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Generator/Util/UniqueIdentifierGenerator.php @@ -0,0 +1,53 @@ + + * @license MIT + */ +abstract class UniqueIdentifierGenerator +{ + const VALID_IDENTIFIER_FORMAT = '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]+$/'; + const DEFAULT_IDENTIFIER = 'g'; + + /** + * Generates a valid unique identifier from the given name + * + * @param string $name + * + * @return string + */ + public static function getIdentifier($name) + { + return str_replace( + '.', + '', + uniqid( + preg_match(static::VALID_IDENTIFIER_FORMAT, $name) + ? $name + : static::DEFAULT_IDENTIFIER, + true + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/BaseGeneratorStrategy.php b/vendor/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/BaseGeneratorStrategy.php new file mode 100644 index 0000000..1e77120 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/BaseGeneratorStrategy.php @@ -0,0 +1,38 @@ + + * @license MIT + */ +class BaseGeneratorStrategy implements GeneratorStrategyInterface +{ + /** + * {@inheritDoc} + */ + public function generate(ClassGenerator $classGenerator) + { + return $classGenerator->generate(); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/EvaluatingGeneratorStrategy.php b/vendor/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/EvaluatingGeneratorStrategy.php new file mode 100644 index 0000000..bdb4688 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/GeneratorStrategy/EvaluatingGeneratorStrategy.php @@ -0,0 +1,69 @@ + + * @license MIT + */ +class EvaluatingGeneratorStrategy implements GeneratorStrategyInterface +{ + /** + * @var bool flag indicating whether {@see eval} can be used + */ + private $canEval = true; + + /** + * Constructor + */ + public function __construct() + { + $this->canEval = ! ini_get('suhosin.executor.disable_eval'); + } + + /** + * Evaluates the generated code before returning it + * + * {@inheritDoc} + */ + public function generate(ClassGenerator $classGenerator) + { + $code = $classGenerator->generate(); + + if (! $this->canEval) { + // @codeCoverageIgnoreStart + $fileName = sys_get_temp_dir() . '/EvaluatingGeneratorStrategy.php.tmp.' . uniqid('', true); + + file_put_contents($fileName, " + * @license MIT + */ +class FileWriterGeneratorStrategy implements GeneratorStrategyInterface +{ + /** + * @var \ProxyManager\FileLocator\FileLocatorInterface + */ + protected $fileLocator; + + /** + * @var callable + */ + private $emptyErrorHandler; + + /** + * @param \ProxyManager\FileLocator\FileLocatorInterface $fileLocator + */ + public function __construct(FileLocatorInterface $fileLocator) + { + $this->fileLocator = $fileLocator; + $this->emptyErrorHandler = function () { + }; + } + + /** + * Write generated code to disk and return the class code + * + * {@inheritDoc} + */ + public function generate(ClassGenerator $classGenerator) + { + $className = trim($classGenerator->getNamespaceName(), '\\') + . '\\' . trim($classGenerator->getName(), '\\'); + $generatedCode = $classGenerator->generate(); + $fileName = $this->fileLocator->getProxyFileName($className); + + set_error_handler($this->emptyErrorHandler); + + try { + $this->writeFile(" + * @license MIT + */ +interface GeneratorStrategyInterface +{ + /** + * Generate the provided class + * + * @param ClassGenerator $classGenerator + * + * @return string the class body + */ + public function generate(ClassGenerator $classGenerator); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflector.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflector.php new file mode 100644 index 0000000..934d82c --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflector.php @@ -0,0 +1,98 @@ + + * @license MIT + */ +final class ClassNameInflector implements ClassNameInflectorInterface +{ + /** + * @var string + */ + protected $proxyNamespace; + + /** + * @var int + */ + private $proxyMarkerLength; + + /** + * @var string + */ + private $proxyMarker; + + /** + * @var \ProxyManager\Inflector\Util\ParameterHasher + */ + private $parameterHasher; + + /** + * @param string $proxyNamespace + */ + public function __construct($proxyNamespace) + { + $this->proxyNamespace = (string) $proxyNamespace; + $this->proxyMarker = '\\' . static::PROXY_MARKER . '\\'; + $this->proxyMarkerLength = strlen($this->proxyMarker); + $this->parameterHasher = new ParameterHasher(); + } + + /** + * {@inheritDoc} + */ + public function getUserClassName($className) + { + $className = ltrim($className, '\\'); + + if (false === $position = strrpos($className, $this->proxyMarker)) { + return $className; + } + + return substr( + $className, + $this->proxyMarkerLength + $position, + strrpos($className, '\\') - ($position + $this->proxyMarkerLength) + ); + } + + /** + * {@inheritDoc} + */ + public function getProxyClassName($className, array $options = array()) + { + return $this->proxyNamespace + . $this->proxyMarker + . $this->getUserClassName($className) + . '\\Generated' . $this->parameterHasher->hashParameters($options); + } + + /** + * {@inheritDoc} + */ + public function isProxyClassName($className) + { + return false !== strrpos($className, $this->proxyMarker); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflectorInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflectorInterface.php new file mode 100644 index 0000000..71fc432 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/ClassNameInflectorInterface.php @@ -0,0 +1,61 @@ + + * @license MIT + */ +interface ClassNameInflectorInterface +{ + /** + * Marker for proxy classes - classes containing this marker are considered proxies + */ + const PROXY_MARKER = '__PM__'; + + /** + * Retrieve the class name of a user-defined class + * + * @param string $className + * + * @return string + */ + public function getUserClassName($className); + + /** + * Retrieve the class name of the proxy for the given user-defined class name + * + * @param string $className + * @param array $options arbitrary options to be used for the generated class name + * + * @return string + */ + public function getProxyClassName($className, array $options = array()); + + /** + * Retrieve whether the provided class name is a proxy + * + * @param string $className + * + * @return bool + */ + public function isProxyClassName($className); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterEncoder.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterEncoder.php new file mode 100644 index 0000000..a1a4b17 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterEncoder.php @@ -0,0 +1,41 @@ + + * @license MIT + */ +class ParameterEncoder +{ + /** + * Converts the given parameters into a set of characters that are safe to + * use in a class name + * + * @param array $parameters + * + * @return string + */ + public function encodeParameters(array $parameters) + { + return base64_encode(serialize($parameters)); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterHasher.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterHasher.php new file mode 100644 index 0000000..970b158 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Inflector/Util/ParameterHasher.php @@ -0,0 +1,40 @@ + + * @license MIT + */ +class ParameterHasher +{ + /** + * Converts the given parameters into a likely-unique hash + * + * @param array $parameters + * + * @return string + */ + public function hashParameters(array $parameters) + { + return md5(serialize($parameters)); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/AccessInterceptorInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/AccessInterceptorInterface.php new file mode 100644 index 0000000..49d70f2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/AccessInterceptorInterface.php @@ -0,0 +1,64 @@ + + * @license MIT + */ +interface AccessInterceptorInterface extends ProxyInterface +{ + /** + * Set or remove the prefix interceptor for a method + * + * @link https://github.com/Ocramius/ProxyManager/blob/master/docs/access-interceptor-value-holder.md + * + * A prefix interceptor should have a signature like following: + * + * + * $prefixInterceptor = function ($proxy, $instance, $method, $params, & $returnEarly) {}; + * + * + * @param string $methodName name of the intercepted method + * @param \Closure|null $prefixInterceptor interceptor closure or null to unset the currently active interceptor + * + * @return void + */ + public function setMethodPrefixInterceptor($methodName, \Closure $prefixInterceptor = null); + + /** + * Set or remove the suffix interceptor for a method + * + * @link https://github.com/Ocramius/ProxyManager/blob/master/docs/access-interceptor-value-holder.md + * + * A prefix interceptor should have a signature like following: + * + * + * $suffixInterceptor = function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) {}; + * + * + * @param string $methodName name of the intercepted method + * @param \Closure|null $suffixInterceptor interceptor closure or null to unset the currently active interceptor + * + * @return void + */ + public function setMethodSuffixInterceptor($methodName, \Closure $suffixInterceptor = null); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/Exception/RemoteObjectException.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/Exception/RemoteObjectException.php new file mode 100644 index 0000000..636a029 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/Exception/RemoteObjectException.php @@ -0,0 +1,31 @@ + + * @license MIT + */ +class RemoteObjectException extends RuntimeException +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/FallbackValueHolderInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/FallbackValueHolderInterface.php new file mode 100644 index 0000000..bf4bc2a --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/FallbackValueHolderInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface FallbackValueHolderInterface extends ProxyInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/GhostObjectInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/GhostObjectInterface.php new file mode 100644 index 0000000..77df518 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/GhostObjectInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface GhostObjectInterface extends LazyLoadingInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/LazyLoadingInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/LazyLoadingInterface.php new file mode 100644 index 0000000..1da90e6 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/LazyLoadingInterface.php @@ -0,0 +1,64 @@ + + * @license MIT + */ +interface LazyLoadingInterface extends ProxyInterface +{ + /** + * Set or unset the initializer for the proxy instance + * + * @link https://github.com/Ocramius/ProxyManager/blob/master/docs/lazy-loading-value-holder.md#lazy-initialization + * + * An initializer should have a signature like following: + * + * + * $initializer = function (& $wrappedObject, $proxy, $method, $parameters, & $initializer) {}; + * + * + * @param \Closure|null $initializer + * + * @return mixed + */ + public function setProxyInitializer(\Closure $initializer = null); + + /** + * @return \Closure|null + */ + public function getProxyInitializer(); + + /** + * Force initialization of the proxy + * + * @return bool true if the proxy could be initialized + */ + public function initializeProxy(); + + /** + * Retrieves current initialization status of the proxy + * + * @return bool + */ + public function isProxyInitialized(); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/NullObjectInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/NullObjectInterface.php new file mode 100644 index 0000000..cd41115 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/NullObjectInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface NullObjectInterface extends ProxyInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/ProxyInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/ProxyInterface.php new file mode 100644 index 0000000..7d402b4 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/ProxyInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface ProxyInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/RemoteObjectInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/RemoteObjectInterface.php new file mode 100644 index 0000000..264fa5e --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/RemoteObjectInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface RemoteObjectInterface extends ProxyInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/SmartReferenceInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/SmartReferenceInterface.php new file mode 100644 index 0000000..2380b32 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/SmartReferenceInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface SmartReferenceInterface extends ProxyInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/ValueHolderInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/ValueHolderInterface.php new file mode 100644 index 0000000..126d543 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/ValueHolderInterface.php @@ -0,0 +1,33 @@ + + * @license MIT + */ +interface ValueHolderInterface extends ProxyInterface +{ + /** + * @return object|null the wrapped value + */ + public function getWrappedValueHolderValue(); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/VirtualProxyInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/VirtualProxyInterface.php new file mode 100644 index 0000000..fc108c7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Proxy/VirtualProxyInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface VirtualProxyInterface extends LazyLoadingInterface, ValueHolderInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/MagicWakeup.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/MagicWakeup.php new file mode 100644 index 0000000..5b7c19d --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/MagicWakeup.php @@ -0,0 +1,50 @@ + + * @license MIT + */ +class MagicWakeup extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct(ReflectionClass $originalClass) + { + parent::__construct($originalClass, '__wakeup'); + + /* @var $publicProperties \ReflectionProperty[] */ + $publicProperties = $originalClass->getProperties(ReflectionProperty::IS_PUBLIC); + $unsetProperties = array(); + + foreach ($publicProperties as $publicProperty) { + $unsetProperties[] = '$this->' . $publicProperty->getName(); + } + + $this->setBody($unsetProperties ? 'unset(' . implode(', ', $unsetProperties) . ");" : ''); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptor.php new file mode 100644 index 0000000..85ba613 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptor.php @@ -0,0 +1,50 @@ + + * @license MIT + */ +class SetMethodPrefixInterceptor extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $prefixInterceptor) + { + parent::__construct('setMethodPrefixInterceptor'); + + $interceptor = new ParameterGenerator('prefixInterceptor'); + + $interceptor->setType('Closure'); + $interceptor->setDefaultValue(null); + $this->setParameter(new ParameterGenerator('methodName')); + $this->setParameter($interceptor); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('$this->' . $prefixInterceptor->getName() . '[$methodName] = $prefixInterceptor;'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptor.php new file mode 100644 index 0000000..74cf81f --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptor.php @@ -0,0 +1,50 @@ + + * @license MIT + */ +class SetMethodSuffixInterceptor extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $suffixInterceptor) + { + parent::__construct('setMethodSuffixInterceptor'); + + $interceptor = new ParameterGenerator('suffixInterceptor'); + + $interceptor->setType('Closure'); + $interceptor->setDefaultValue(null); + $this->setParameter(new ParameterGenerator('methodName')); + $this->setParameter($interceptor); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('$this->' . $suffixInterceptor->getName() . '[$methodName] = $suffixInterceptor;'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptors.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptors.php new file mode 100644 index 0000000..b9c44a2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptors.php @@ -0,0 +1,43 @@ + + * @license MIT + */ +class MethodPrefixInterceptors extends PropertyGenerator +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('methodPrefixInterceptors')); + + $this->setDefaultValue(array()); + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setDocblock('@var \\Closure[] map of interceptors to be called per-method before execution'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptors.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptors.php new file mode 100644 index 0000000..a11d6c2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptors.php @@ -0,0 +1,43 @@ + + * @license MIT + */ +class MethodSuffixInterceptors extends PropertyGenerator +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('methodSuffixInterceptors')); + + $this->setDefaultValue(array()); + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setDocblock('@var \\Closure[] map of interceptors to be called per-method after execution'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Constructor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Constructor.php new file mode 100644 index 0000000..3770a16 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Constructor.php @@ -0,0 +1,92 @@ + + * @license MIT + */ +class Constructor extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct('__construct'); + + $localizedObject = new ParameterGenerator('localizedObject'); + $prefix = new ParameterGenerator('prefixInterceptors'); + $suffix = new ParameterGenerator('suffixInterceptors'); + + $localizedObject->setType($originalClass->getName()); + $prefix->setDefaultValue(array()); + $suffix->setDefaultValue(array()); + $prefix->setType('array'); + $suffix->setType('array'); + + $this->setParameter($localizedObject); + $this->setParameter($prefix); + $this->setParameter($suffix); + + $localizedProperties = array(); + + foreach ($originalClass->getProperties() as $originalProperty) { + if ((! method_exists('Closure', 'bind')) && $originalProperty->isPrivate()) { + // @codeCoverageIgnoreStart + throw UnsupportedProxiedClassException::unsupportedLocalizedReflectionProperty($originalProperty); + // @codeCoverageIgnoreEnd + } + + $propertyName = $originalProperty->getName(); + + if ($originalProperty->isPrivate()) { + $localizedProperties[] = "\\Closure::bind(function () use (\$localizedObject) {\n " + . '$this->' . $propertyName . ' = & $localizedObject->' . $propertyName . ";\n" + . '}, $this, ' . var_export($originalProperty->getDeclaringClass()->getName(), true) + . ')->__invoke();'; + } else { + $localizedProperties[] = '$this->' . $propertyName . ' = & $localizedObject->' . $propertyName . ";"; + } + } + + $this->setDocblock( + "@override constructor to setup interceptors\n\n" + . "@param \\" . $originalClass->getName() . " \$localizedObject\n" + . "@param \\Closure[] \$prefixInterceptors method interceptors to be used before method logic\n" + . "@param \\Closure[] \$suffixInterceptors method interceptors to be used before method logic" + ); + $this->setBody( + (empty($localizedProperties) ? '' : implode("\n\n", $localizedProperties) . "\n\n") + . '$this->' . $prefixInterceptors->getName() . " = \$prefixInterceptors;\n" + . '$this->' . $suffixInterceptors->getName() . " = \$suffixInterceptors;" + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethod.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethod.php new file mode 100644 index 0000000..22068f7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethod.php @@ -0,0 +1,67 @@ + + * @license MIT + */ +class InterceptedMethod extends MethodGenerator +{ + /** + * @param \Zend\Code\Reflection\MethodReflection $originalMethod + * @param \Zend\Code\Generator\PropertyGenerator $prefixInterceptors + * @param \Zend\Code\Generator\PropertyGenerator $suffixInterceptors + * + * @return self + */ + public static function generateMethod( + MethodReflection $originalMethod, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + /* @var $method self */ + $method = static::fromReflection($originalMethod); + $forwardedParams = array(); + + foreach ($originalMethod->getParameters() as $parameter) { + $forwardedParams[] = '$' . $parameter->getName(); + } + + $method->setDocblock('{@inheritDoc}'); + $method->setBody( + InterceptorGenerator::createInterceptedMethodBody( + '$returnValue = parent::' + . $originalMethod->getName() . '(' . implode(', ', $forwardedParams) . ');', + $method, + $prefixInterceptors, + $suffixInterceptors + ) + ); + + return $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicClone.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicClone.php new file mode 100644 index 0000000..98e9b19 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicClone.php @@ -0,0 +1,53 @@ + + * @license MIT + */ +class MagicClone extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct($originalClass, '__clone'); + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $originalClass->hasMethod('__clone') ? '$returnValue = parent::__clone();' : '$returnValue = null;', + $this, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGet.php new file mode 100644 index 0000000..930b9d7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGet.php @@ -0,0 +1,73 @@ + + * @license MIT + */ +class MagicGet extends MagicMethodGenerator +{ + /** + * @param ReflectionClass $originalClass + * @param PropertyGenerator $prefixInterceptors + * @param PropertyGenerator $suffixInterceptors + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct($originalClass, '__get', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__get'); + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if ($override) { + $callParent = '$returnValue = & parent::__get($name);'; + } else { + $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_GET, + 'name', + null, + null, + 'returnValue' + ); + } + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIsset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIsset.php new file mode 100644 index 0000000..26eb275 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIsset.php @@ -0,0 +1,73 @@ + + * @license MIT + */ +class MagicIsset extends MagicMethodGenerator +{ + /** + * @param ReflectionClass $originalClass + * @param PropertyGenerator $prefixInterceptors + * @param PropertyGenerator $suffixInterceptors + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct($originalClass, '__isset', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__isset'); + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if ($override) { + $callParent = '$returnValue = & parent::__isset($name);'; + } else { + $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_ISSET, + 'name', + null, + null, + 'returnValue' + ); + } + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSet.php new file mode 100644 index 0000000..e756207 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSet.php @@ -0,0 +1,77 @@ + + * @license MIT + */ +class MagicSet extends MagicMethodGenerator +{ + /** + * @param \ReflectionClass $originalClass + * @param \Zend\Code\Generator\PropertyGenerator $prefixInterceptors + * @param \Zend\Code\Generator\PropertyGenerator $suffixInterceptors + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct( + $originalClass, + '__set', + array(new ParameterGenerator('name'), new ParameterGenerator('value')) + ); + + $override = $originalClass->hasMethod('__set'); + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if ($override) { + $callParent = '$returnValue = & parent::__set($name, $value);'; + } else { + $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_SET, + 'name', + 'value', + null, + 'returnValue' + ); + } + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleep.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleep.php new file mode 100644 index 0000000..3ea4a25 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleep.php @@ -0,0 +1,57 @@ + + * @license MIT + */ +class MagicSleep extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct($originalClass, '__sleep'); + + $callParent = $originalClass->hasMethod('__sleep') + ? '$returnValue = & parent::__sleep();' + : '$returnValue = array_keys((array) $this);'; + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnset.php new file mode 100644 index 0000000..3d40420 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnset.php @@ -0,0 +1,73 @@ + + * @license MIT + */ +class MagicUnset extends MagicMethodGenerator +{ + /** + * @param ReflectionClass $originalClass + * @param PropertyGenerator $prefixInterceptors + * @param PropertyGenerator $suffixInterceptors + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct($originalClass, '__unset', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__unset'); + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if ($override) { + $callParent = '$returnValue = & parent::__unset($name);'; + } else { + $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_UNSET, + 'name', + null, + null, + 'returnValue' + ); + } + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGenerator.php new file mode 100644 index 0000000..c434635 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGenerator.php @@ -0,0 +1,82 @@ + + * @license MIT + * + * @internal - this class is just here as a small utility for this component, + * don't use it in your own code + */ +class InterceptorGenerator +{ + /** + * @param string $methodBody the body of the previously generated code. + * It MUST assign the return value to a variable + * `$returnValue` instead of directly returning + * @param \ProxyManager\Generator\MethodGenerator $method + * @param \Zend\Code\Generator\PropertyGenerator $prefixInterceptors + * @param \Zend\Code\Generator\PropertyGenerator $suffixInterceptors + * + * @return string + */ + public static function createInterceptedMethodBody( + $methodBody, + MethodGenerator $method, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + $name = var_export($method->getName(), true); + $prefixInterceptors = $prefixInterceptors->getName(); + $suffixInterceptors = $suffixInterceptors->getName(); + $params = array(); + + foreach ($method->getParameters() as $parameter) { + $parameterName = $parameter->getName(); + $params[] = var_export($parameterName, true) . ' => $' . $parameter->getName(); + } + + $paramsString = 'array(' . implode(', ', $params) . ')'; + + return "if (isset(\$this->$prefixInterceptors" . "[$name])) {\n" + . " \$returnEarly = false;\n" + . " \$prefixReturnValue = \$this->$prefixInterceptors" . "[$name]->__invoke(" + . "\$this, \$this, $name, $paramsString, \$returnEarly);\n\n" + . " if (\$returnEarly) {\n" + . " return \$prefixReturnValue;\n" + . " }\n" + . "}\n\n" + . $methodBody . "\n\n" + . "if (isset(\$this->$suffixInterceptors" . "[$name])) {\n" + . " \$returnEarly = false;\n" + . " \$suffixReturnValue = \$this->$suffixInterceptors" . "[$name]->__invoke(" + . "\$this, \$this, $name, $paramsString, \$returnValue, \$returnEarly);\n\n" + . " if (\$returnEarly) {\n" + . " return \$suffixReturnValue;\n" + . " }\n" + . "}\n\n" + . "return \$returnValue;"; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizerGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizerGenerator.php new file mode 100644 index 0000000..b6edee3 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorScopeLocalizerGenerator.php @@ -0,0 +1,96 @@ + + * @license MIT + */ +class AccessInterceptorScopeLocalizerGenerator implements ProxyGeneratorInterface +{ + /** + * {@inheritDoc} + */ + public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + CanProxyAssertion::assertClassCanBeProxied($originalClass, false); + + $classGenerator->setExtendedClass($originalClass->getName()); + $classGenerator->setImplementedInterfaces(array('ProxyManager\\Proxy\\AccessInterceptorInterface')); + $classGenerator->addPropertyFromGenerator($prefixInterceptors = new MethodPrefixInterceptors()); + $classGenerator->addPropertyFromGenerator($suffixInterceptors = new MethodPrefixInterceptors()); + + array_map( + function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator) { + ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod); + }, + array_merge( + array_map( + function (ReflectionMethod $method) use ($prefixInterceptors, $suffixInterceptors) { + return InterceptedMethod::generateMethod( + new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), + $prefixInterceptors, + $suffixInterceptors + ); + }, + ProxiedMethodsFilter::getProxiedMethods( + $originalClass, + array('__get', '__set', '__isset', '__unset', '__clone', '__sleep') + ) + ), + array( + new Constructor($originalClass, $prefixInterceptors, $suffixInterceptors), + new SetMethodPrefixInterceptor($prefixInterceptors), + new SetMethodSuffixInterceptor($suffixInterceptors), + new MagicGet($originalClass, $prefixInterceptors, $suffixInterceptors), + new MagicSet($originalClass, $prefixInterceptors, $suffixInterceptors), + new MagicIsset($originalClass, $prefixInterceptors, $suffixInterceptors), + new MagicUnset($originalClass, $prefixInterceptors, $suffixInterceptors), + new MagicSleep($originalClass, $prefixInterceptors, $suffixInterceptors), + new MagicClone($originalClass, $prefixInterceptors, $suffixInterceptors), + ) + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Constructor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Constructor.php new file mode 100644 index 0000000..925ee6b --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Constructor.php @@ -0,0 +1,79 @@ + + * @license MIT + */ +class Constructor extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $valueHolder, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct('__construct'); + + $prefix = new ParameterGenerator('prefixInterceptors'); + $suffix = new ParameterGenerator('suffixInterceptors'); + + $prefix->setDefaultValue(array()); + $suffix->setDefaultValue(array()); + $prefix->setType('array'); + $suffix->setType('array'); + + $this->setParameter(new ParameterGenerator('wrappedObject')); + $this->setParameter($prefix); + $this->setParameter($suffix); + + /* @var $publicProperties \ReflectionProperty[] */ + $publicProperties = $originalClass->getProperties(ReflectionProperty::IS_PUBLIC); + $unsetProperties = array(); + + foreach ($publicProperties as $publicProperty) { + $unsetProperties[] = '$this->' . $publicProperty->getName(); + } + + $this->setDocblock( + "@override constructor to setup interceptors\n\n" + . "@param \\" . $originalClass->getName() . " \$wrappedObject\n" + . "@param \\Closure[] \$prefixInterceptors method interceptors to be used before method logic\n" + . "@param \\Closure[] \$suffixInterceptors method interceptors to be used before method logic" + ); + $this->setBody( + ($unsetProperties ? 'unset(' . implode(', ', $unsetProperties) . ");\n\n" : '') + . '$this->' . $valueHolder->getName() . " = \$wrappedObject;\n" + . '$this->' . $prefixInterceptors->getName() . " = \$prefixInterceptors;\n" + . '$this->' . $suffixInterceptors->getName() . " = \$suffixInterceptors;" + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethod.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethod.php new file mode 100644 index 0000000..e882e32 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethod.php @@ -0,0 +1,70 @@ + + * @license MIT + */ +class InterceptedMethod extends MethodGenerator +{ + /** + * @param \Zend\Code\Reflection\MethodReflection $originalMethod + * @param \Zend\Code\Generator\PropertyGenerator $valueHolderProperty + * @param \Zend\Code\Generator\PropertyGenerator $prefixInterceptors + * @param \Zend\Code\Generator\PropertyGenerator $suffixInterceptors + * + * @return self + */ + public static function generateMethod( + MethodReflection $originalMethod, + PropertyGenerator $valueHolderProperty, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + /* @var $method self */ + $method = static::fromReflection($originalMethod); + $forwardedParams = array(); + + foreach ($originalMethod->getParameters() as $parameter) { + $forwardedParams[] = '$' . $parameter->getName(); + } + + $method->setDocblock('{@inheritDoc}'); + $method->setBody( + InterceptorGenerator::createInterceptedMethodBody( + '$returnValue = $this->' . $valueHolderProperty->getName() . '->' + . $originalMethod->getName() . '(' . implode(', ', $forwardedParams) . ');', + $method, + $valueHolderProperty, + $prefixInterceptors, + $suffixInterceptors + ) + ); + + return $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicClone.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicClone.php new file mode 100644 index 0000000..764ba77 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicClone.php @@ -0,0 +1,58 @@ + + * @license MIT + */ +class MagicClone extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $valueHolderProperty, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + parent::__construct($originalClass, '__clone'); + + $valueHolder = $valueHolderProperty->getName(); + $prefix = $prefixInterceptors->getName(); + $suffix = $suffixInterceptors->getName(); + + $this->setBody( + "\$this->$valueHolder = clone \$this->$valueHolder;\n\n" + . "foreach (\$this->$prefix as \$key => \$value) {\n" + . " \$this->$prefix" . "[\$key] = clone \$value;\n" + . "}\n\n" + . "foreach (\$this->$suffix as \$key => \$value) {\n" + . " \$this->$suffix" . "[\$key] = clone \$value;\n" + . "}" + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGet.php new file mode 100644 index 0000000..8096281 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGet.php @@ -0,0 +1,78 @@ + + * @license MIT + */ +class MagicGet extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $valueHolder, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__get', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__get'); + $valueHolderName = $valueHolder->getName(); + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_GET, + 'name', + 'value', + $valueHolder, + 'returnValue' + ); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' $returnValue = & $this->' . $valueHolderName . '->$name;' + . "\n} else {\n $callParent\n}\n\n"; + } + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIsset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIsset.php new file mode 100644 index 0000000..2647b35 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIsset.php @@ -0,0 +1,78 @@ + + * @license MIT + */ +class MagicIsset extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $valueHolder, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__isset', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__isset'); + $valueHolderName = $valueHolder->getName(); + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_ISSET, + 'name', + 'value', + $valueHolder, + 'returnValue' + ); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' $returnValue = isset($this->' . $valueHolderName . '->$name);' + . "\n} else {\n $callParent\n}\n\n"; + } + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSet.php new file mode 100644 index 0000000..b443b77 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSet.php @@ -0,0 +1,82 @@ + + * @license MIT + */ +class MagicSet extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $valueHolder, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors, + PublicPropertiesMap $publicProperties + ) { + parent::__construct( + $originalClass, + '__set', + array(new ParameterGenerator('name'), new ParameterGenerator('value')) + ); + + $override = $originalClass->hasMethod('__set'); + $valueHolderName = $valueHolder->getName(); + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_SET, + 'name', + 'value', + $valueHolder, + 'returnValue' + ); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' $returnValue = ($this->' . $valueHolderName . '->$name = $value);' + . "\n} else {\n $callParent\n}\n\n"; + } + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnset.php new file mode 100644 index 0000000..bdf3a1a --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnset.php @@ -0,0 +1,80 @@ + + * @license MIT + */ +class MagicUnset extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $valueHolder, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__unset', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__unset'); + $valueHolderName = $valueHolder->getName(); + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + $callParent = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_UNSET, + 'name', + 'value', + $valueHolder, + 'returnValue' + ); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' unset($this->' . $valueHolderName . '->$name);' + . "\n} else {\n $callParent\n}\n\n"; + } + + $callParent .= '$returnValue = false;'; + + $this->setBody( + InterceptorGenerator::createInterceptedMethodBody( + $callParent, + $this, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGenerator.php new file mode 100644 index 0000000..0a6f58a --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGenerator.php @@ -0,0 +1,85 @@ + + * @license MIT + * + * @internal - this class is just here as a small utility for this component, + * don't use it in your own code + */ +class InterceptorGenerator +{ + /** + * @param string $methodBody the body of the previously generated code. + * It MUST assign the return value to a variable + * `$returnValue` instead of directly returning + * @param \ProxyManager\Generator\MethodGenerator $method + * @param \Zend\Code\Generator\PropertyGenerator $valueHolder + * @param \Zend\Code\Generator\PropertyGenerator $prefixInterceptors + * @param \Zend\Code\Generator\PropertyGenerator $suffixInterceptors + * + * @return string + */ + public static function createInterceptedMethodBody( + $methodBody, + MethodGenerator $method, + PropertyGenerator $valueHolder, + PropertyGenerator $prefixInterceptors, + PropertyGenerator $suffixInterceptors + ) { + $name = var_export($method->getName(), true); + $valueHolder = $valueHolder->getName(); + $prefixInterceptors = $prefixInterceptors->getName(); + $suffixInterceptors = $suffixInterceptors->getName(); + $params = array(); + + foreach ($method->getParameters() as $parameter) { + $parameterName = $parameter->getName(); + $params[] = var_export($parameterName, true) . ' => $' . $parameter->getName(); + } + + $paramsString = 'array(' . implode(', ', $params) . ')'; + + return "if (isset(\$this->$prefixInterceptors" . "[$name])) {\n" + . " \$returnEarly = false;\n" + . " \$prefixReturnValue = \$this->$prefixInterceptors" . "[$name]->__invoke(" + . "\$this, \$this->$valueHolder, $name, $paramsString, \$returnEarly);\n\n" + . " if (\$returnEarly) {\n" + . " return \$prefixReturnValue;\n" + . " }\n" + . "}\n\n" + . $methodBody . "\n\n" + . "if (isset(\$this->$suffixInterceptors" . "[$name])) {\n" + . " \$returnEarly = false;\n" + . " \$suffixReturnValue = \$this->$suffixInterceptors" . "[$name]->__invoke(" + . "\$this, \$this->$valueHolder, $name, $paramsString, \$returnValue, \$returnEarly);\n\n" + . " if (\$returnEarly) {\n" + . " return \$suffixReturnValue;\n" + . " }\n" + . "}\n\n" + . "return \$returnValue;"; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolderGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolderGenerator.php new file mode 100644 index 0000000..4b05549 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/AccessInterceptorValueHolderGenerator.php @@ -0,0 +1,138 @@ + + * @license MIT + */ +class AccessInterceptorValueHolderGenerator implements ProxyGeneratorInterface +{ + /** + * {@inheritDoc} + */ + public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + CanProxyAssertion::assertClassCanBeProxied($originalClass); + + $publicProperties = new PublicPropertiesMap($originalClass); + $interfaces = array( + 'ProxyManager\\Proxy\\AccessInterceptorInterface', + 'ProxyManager\\Proxy\\ValueHolderInterface', + ); + + if ($originalClass->isInterface()) { + $interfaces[] = $originalClass->getName(); + } else { + $classGenerator->setExtendedClass($originalClass->getName()); + } + + $classGenerator->setImplementedInterfaces($interfaces); + $classGenerator->addPropertyFromGenerator($valueHolder = new ValueHolderProperty()); + $classGenerator->addPropertyFromGenerator($prefixInterceptors = new MethodPrefixInterceptors()); + $classGenerator->addPropertyFromGenerator($suffixInterceptors = new MethodSuffixInterceptors()); + $classGenerator->addPropertyFromGenerator($publicProperties); + + array_map( + function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator) { + ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod); + }, + array_merge( + array_map( + function (ReflectionMethod $method) use ($prefixInterceptors, $suffixInterceptors, $valueHolder) { + return InterceptedMethod::generateMethod( + new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), + $valueHolder, + $prefixInterceptors, + $suffixInterceptors + ); + }, + ProxiedMethodsFilter::getProxiedMethods($originalClass) + ), + array( + new Constructor($originalClass, $valueHolder, $prefixInterceptors, $suffixInterceptors), + new GetWrappedValueHolderValue($valueHolder), + new SetMethodPrefixInterceptor($prefixInterceptors), + new SetMethodSuffixInterceptor($suffixInterceptors), + new MagicGet( + $originalClass, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors, + $publicProperties + ), + new MagicSet( + $originalClass, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors, + $publicProperties + ), + new MagicIsset( + $originalClass, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors, + $publicProperties + ), + new MagicUnset( + $originalClass, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors, + $publicProperties + ), + new MagicClone($originalClass, $valueHolder, $prefixInterceptors, $suffixInterceptors), + new MagicSleep($originalClass, $valueHolder), + new MagicWakeup($originalClass, $valueHolder), + ) + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Assertion/CanProxyAssertion.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Assertion/CanProxyAssertion.php new file mode 100644 index 0000000..213e15d --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Assertion/CanProxyAssertion.php @@ -0,0 +1,102 @@ + + * @license MIT + */ +final class CanProxyAssertion +{ + /** + * Disabled constructor: not meant to be instantiated + * + * @throws BadMethodCallException + */ + public function __construct() + { + throw new BadMethodCallException('Unsupported constructor.'); + } + + /** + * @param ReflectionClass $originalClass + * @param bool $allowInterfaces + * + * @throws InvalidProxiedClassException + */ + public static function assertClassCanBeProxied(ReflectionClass $originalClass, $allowInterfaces = true) + { + self::isNotFinal($originalClass); + self::hasNoAbstractProtectedMethods($originalClass); + + if (! $allowInterfaces) { + self::isNotInterface($originalClass); + } + } + + /** + * @param ReflectionClass $originalClass + * + * @throws InvalidProxiedClassException + */ + private static function isNotFinal(ReflectionClass $originalClass) + { + if ($originalClass->isFinal()) { + throw InvalidProxiedClassException::finalClassNotSupported($originalClass); + } + } + + /** + * @param ReflectionClass $originalClass + * + * @throws InvalidProxiedClassException + */ + private static function hasNoAbstractProtectedMethods(ReflectionClass $originalClass) + { + $protectedAbstract = array_filter( + $originalClass->getMethods(), + function (ReflectionMethod $method) { + return $method->isAbstract() && $method->isProtected(); + } + ); + + if ($protectedAbstract) { + throw InvalidProxiedClassException::abstractProtectedMethodsNotSupported($originalClass); + } + } + + /** + * @param ReflectionClass $originalClass + * + * @throws InvalidProxiedClassException + */ + private static function isNotInterface(ReflectionClass $originalClass) + { + if ($originalClass->isInterface()) { + throw InvalidProxiedClassException::interfaceNotSupported($originalClass); + } + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoading/MethodGenerator/Constructor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoading/MethodGenerator/Constructor.php new file mode 100644 index 0000000..224370b --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoading/MethodGenerator/Constructor.php @@ -0,0 +1,58 @@ + + * @license MIT + */ +class Constructor extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(ReflectionClass $originalClass, PropertyGenerator $initializerProperty) + { + parent::__construct('__construct'); + + $this->setParameter(new ParameterGenerator('initializer')); + + /* @var $publicProperties \ReflectionProperty[] */ + $publicProperties = $originalClass->getProperties(ReflectionProperty::IS_PUBLIC); + $unsetProperties = array(); + + foreach ($publicProperties as $publicProperty) { + $unsetProperties[] = '$this->' . $publicProperty->getName(); + } + + $this->setDocblock("@override constructor for lazy initialization\n\n@param \\Closure|null \$initializer"); + $this->setBody( + ($unsetProperties ? 'unset(' . implode(', ', $unsetProperties) . ");\n\n" : '') + . '$this->' . $initializerProperty->getName() . ' = $initializer;' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php new file mode 100644 index 0000000..36f2794 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializer.php @@ -0,0 +1,67 @@ + + * @license MIT + */ +class CallInitializer extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct( + PropertyGenerator $initializerProperty, + PropertyGenerator $publicPropsDefaults, + PropertyGenerator $initTracker + ) { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('callInitializer')); + $this->setDocblock("Triggers initialization logic for this ghost object"); + + $this->setParameters(array( + new ParameterGenerator('methodName'), + new ParameterGenerator('parameters', 'array'), + )); + + $this->setVisibility(static::VISIBILITY_PRIVATE); + + $initializer = $initializerProperty->getName(); + $initialization = $initTracker->getName(); + + $this->setBody( + 'if ($this->' . $initialization . ' || ! $this->' . $initializer . ') {' . "\n return;\n}\n\n" + . "\$this->" . $initialization . " = true;\n\n" + . "foreach (self::\$" . $publicPropsDefaults->getName() . " as \$key => \$default) {\n" + . " \$this->\$key = \$default;\n" + . "}\n\n" + . '$this->' . $initializer . '->__invoke' + . '($this, $methodName, $parameters, $this->' . $initializer . ');' . "\n\n" + . "\$this->" . $initialization . " = false;" + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializer.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializer.php new file mode 100644 index 0000000..5b1d75a --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializer.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class GetProxyInitializer extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $initializerProperty) + { + parent::__construct('getProxyInitializer'); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('return $this->' . $initializerProperty->getName() . ';'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxy.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxy.php new file mode 100644 index 0000000..90723c3 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxy.php @@ -0,0 +1,47 @@ + + * @license MIT + */ +class InitializeProxy extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $initializerProperty, ZendMethodGenerator $callInitializer) + { + parent::__construct('initializeProxy'); + $this->setDocblock('{@inheritDoc}'); + + $this->setBody( + 'return $this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() + . '(\'initializeProxy\', array());' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitialized.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitialized.php new file mode 100644 index 0000000..110ef31 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitialized.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class IsProxyInitialized extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $initializerProperty) + { + parent::__construct('isProxyInitialized'); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('return ! $this->' . $initializerProperty->getName() . ';'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/LazyLoadingMethodInterceptor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/LazyLoadingMethodInterceptor.php new file mode 100644 index 0000000..d511a48 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/LazyLoadingMethodInterceptor.php @@ -0,0 +1,71 @@ + + * @license MIT + */ +class LazyLoadingMethodInterceptor extends MethodGenerator +{ + /** + * @param \Zend\Code\Reflection\MethodReflection $originalMethod + * @param \Zend\Code\Generator\PropertyGenerator $initializerProperty + * @param \Zend\Code\Generator\MethodGenerator $callInitializer + * + * @return LazyLoadingMethodInterceptor|static + */ + public static function generateMethod( + MethodReflection $originalMethod, + PropertyGenerator $initializerProperty, + ZendMethodGenerator $callInitializer + ) { + /* @var $method self */ + $method = static::fromReflection($originalMethod); + $parameters = $originalMethod->getParameters(); + $methodName = $originalMethod->getName(); + $initializerParams = array(); + $forwardedParams = array(); + + foreach ($parameters as $parameter) { + $parameterName = $parameter->getName(); + $initializerParams[] = var_export($parameterName, true) . ' => $' . $parameterName; + $forwardedParams[] = '$' . $parameterName; + } + + $method->setBody( + '$this->' . $initializerProperty->getName() + . ' && $this->' . $callInitializer->getName() + . '(' . var_export($methodName, true) + . ', array(' . implode(', ', $initializerParams) . "));\n\n" + . 'return parent::' + . $methodName . '(' . implode(', ', $forwardedParams) . ');' + ); + $method->setDocblock('{@inheritDoc}'); + + return $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicClone.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicClone.php new file mode 100644 index 0000000..34d999b --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicClone.php @@ -0,0 +1,50 @@ + + * @license MIT + */ +class MagicClone extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + MethodGenerator $callInitializer + ) { + parent::__construct($originalClass, '__clone'); + + $this->setBody( + '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() + . '(\'__clone\', array());' + . ($originalClass->hasMethod('__clone') ? "\n\nparent::__clone();" : '') + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicGet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicGet.php new file mode 100644 index 0000000..007b659 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicGet.php @@ -0,0 +1,77 @@ + + * @license MIT + */ +class MagicGet extends MagicMethodGenerator +{ + /** + * @param \ReflectionClass $originalClass + * @param \Zend\Code\Generator\PropertyGenerator $initializerProperty + * @param \Zend\Code\Generator\MethodGenerator $callInitializer + * @param \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap $publicProperties + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + MethodGenerator $callInitializer, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__get', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__get'); + $callParent = ''; + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' return $this->$name;' + . "\n}\n\n"; + } + + if ($override) { + $callParent .= 'return parent::__get($name);'; + } else { + $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_GET, + 'name' + ); + } + + $this->setBody( + '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() + . '(\'__get\', array(\'name\' => $name));' + . "\n\n" . $callParent + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIsset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIsset.php new file mode 100644 index 0000000..84c77fa --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIsset.php @@ -0,0 +1,77 @@ + + * @license MIT + */ +class MagicIsset extends MagicMethodGenerator +{ + /** + * @param \ReflectionClass $originalClass + * @param \Zend\Code\Generator\PropertyGenerator $initializerProperty + * @param \Zend\Code\Generator\MethodGenerator $callInitializer + * @param \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap $publicProperties + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + MethodGenerator $callInitializer, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__isset', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__isset'); + $callParent = ''; + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' return isset($this->$name);' + . "\n}\n\n"; + } + + if ($override) { + $callParent .= 'return parent::__isset($name);'; + } else { + $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_ISSET, + 'name' + ); + } + + $this->setBody( + '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() + . '(\'__isset\', array(\'name\' => $name));' + . "\n\n" . $callParent + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSet.php new file mode 100644 index 0000000..902a16d --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSet.php @@ -0,0 +1,81 @@ + + * @license MIT + */ +class MagicSet extends MagicMethodGenerator +{ + /** + * @param \ReflectionClass $originalClass + * @param \Zend\Code\Generator\PropertyGenerator $initializerProperty + * @param \Zend\Code\Generator\MethodGenerator $callInitializer + * @param \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap $publicProperties + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + MethodGenerator $callInitializer, + PublicPropertiesMap $publicProperties + ) { + parent::__construct( + $originalClass, + '__set', + array(new ParameterGenerator('name'), new ParameterGenerator('value')) + ); + + $override = $originalClass->hasMethod('__set'); + $callParent = ''; + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' return ($this->$name = $value);' + . "\n}\n\n"; + } + + if ($override) { + $callParent .= 'return parent::__set($name, $value);'; + } else { + $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_SET, + 'name', + 'value' + ); + } + + $this->setBody( + '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() + . '(\'__set\', array(\'name\' => $name, \'value\' => $value));' . "\n\n" . $callParent + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleep.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleep.php new file mode 100644 index 0000000..a8036b2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleep.php @@ -0,0 +1,50 @@ + + * @license MIT + */ +class MagicSleep extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + MethodGenerator $callInitializer + ) { + parent::__construct($originalClass, '__sleep'); + + $this->setBody( + '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() + . '(\'__sleep\', array());' . "\n\n" + . ($originalClass->hasMethod('__sleep') ? 'return parent::__sleep();' : 'return array_keys((array) $this);') + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnset.php new file mode 100644 index 0000000..5a5bd23 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnset.php @@ -0,0 +1,78 @@ + + * @license MIT + */ +class MagicUnset extends MagicMethodGenerator +{ + /** + * @param \ReflectionClass $originalClass + * @param \Zend\Code\Generator\PropertyGenerator $initializerProperty + * @param \Zend\Code\Generator\MethodGenerator $callInitializer + * @param \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap $publicProperties + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + MethodGenerator $callInitializer, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__unset', array(new ParameterGenerator('name'))); + + $override = $originalClass->hasMethod('__unset'); + $callParent = ''; + + $this->setDocblock(($override ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' unset($this->$name);' + . "\n\n return;" + . "\n}\n\n"; + } + + if ($override) { + $callParent .= "return parent::__unset(\$name);"; + } else { + $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_UNSET, + 'name' + ); + } + + $this->setBody( + '$this->' . $initializerProperty->getName() . ' && $this->' . $callInitializer->getName() + . '(\'__unset\', array(\'name\' => $name));' + . "\n\n" . $callParent + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializer.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializer.php new file mode 100644 index 0000000..30e37bb --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializer.php @@ -0,0 +1,49 @@ + + * @license MIT + */ +class SetProxyInitializer extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $initializerProperty) + { + parent::__construct('setProxyInitializer'); + + $initializerParameter = new ParameterGenerator('initializer'); + + $initializerParameter->setType('Closure'); + $initializerParameter->setDefaultValue(null); + $this->setParameter($initializerParameter); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('$this->' . $initializerProperty->getName() . ' = $initializer;'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTracker.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTracker.php new file mode 100644 index 0000000..c303f1c --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTracker.php @@ -0,0 +1,43 @@ + + * @license MIT + */ +class InitializationTracker extends PropertyGenerator +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('initializationTracker')); + + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setDocblock('@var bool tracks initialization status - true while the object is initializing'); + $this->setDefaultValue(false); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerProperty.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerProperty.php new file mode 100644 index 0000000..554889f --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerProperty.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class InitializerProperty extends PropertyGenerator +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('initializer')); + + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setDocblock('@var \\Closure|null initializer responsible for generating the wrapped object'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhostGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhostGenerator.php new file mode 100644 index 0000000..1f9d88e --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingGhostGenerator.php @@ -0,0 +1,114 @@ + + * @license MIT + */ +class LazyLoadingGhostGenerator implements ProxyGeneratorInterface +{ + /** + * {@inheritDoc} + */ + public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + CanProxyAssertion::assertClassCanBeProxied($originalClass); + + $interfaces = array('ProxyManager\\Proxy\\GhostObjectInterface'); + $publicProperties = new PublicPropertiesMap($originalClass); + $publicPropsDefaults = new PublicPropertiesDefaults($originalClass); + + if ($originalClass->isInterface()) { + $interfaces[] = $originalClass->getName(); + } else { + $classGenerator->setExtendedClass($originalClass->getName()); + } + + $classGenerator->setImplementedInterfaces($interfaces); + $classGenerator->addPropertyFromGenerator($initializer = new InitializerProperty()); + $classGenerator->addPropertyFromGenerator($initializationTracker = new InitializationTracker()); + $classGenerator->addPropertyFromGenerator($publicProperties); + $classGenerator->addPropertyFromGenerator($publicPropsDefaults); + + $init = new CallInitializer($initializer, $publicPropsDefaults, $initializationTracker); + + array_map( + function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator) { + ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod); + }, + array_merge( + array_map( + function (ReflectionMethod $method) use ($initializer, $init) { + return LazyLoadingMethodInterceptor::generateMethod( + new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), + $initializer, + $init + ); + }, + ProxiedMethodsFilter::getProxiedMethods($originalClass) + ), + array( + $init, + new Constructor($originalClass, $initializer), + new MagicGet($originalClass, $initializer, $init, $publicProperties), + new MagicSet($originalClass, $initializer, $init, $publicProperties), + new MagicIsset($originalClass, $initializer, $init, $publicProperties), + new MagicUnset($originalClass, $initializer, $init, $publicProperties), + new MagicClone($originalClass, $initializer, $init, $publicProperties), + new MagicSleep($originalClass, $initializer, $init, $publicProperties), + new SetProxyInitializer($initializer), + new GetProxyInitializer($initializer), + new InitializeProxy($initializer, $init), + new IsProxyInitialized($initializer), + ) + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializer.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializer.php new file mode 100644 index 0000000..3dd0854 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializer.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class GetProxyInitializer extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $initializerProperty) + { + parent::__construct('getProxyInitializer'); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('return $this->' . $initializerProperty->getName() . ';'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxy.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxy.php new file mode 100644 index 0000000..4c358b1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxy.php @@ -0,0 +1,49 @@ + + * @license MIT + */ +class InitializeProxy extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $initializerProperty, PropertyGenerator $valueHolderProperty) + { + parent::__construct('initializeProxy'); + $this->setDocblock('{@inheritDoc}'); + + $initializer = $initializerProperty->getName(); + + $this->setBody( + 'return $this->' . $initializer . ' && $this->' . $initializer + . '->__invoke($this->' . $valueHolderProperty->getName() + . ', $this, \'initializeProxy\', array(), $this->' . $initializer . ');' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitialized.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitialized.php new file mode 100644 index 0000000..34fdd0b --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitialized.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class IsProxyInitialized extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $valueHolderProperty) + { + parent::__construct('isProxyInitialized'); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('return null !== $this->' . $valueHolderProperty->getName() . ';'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptor.php new file mode 100644 index 0000000..b4ce370 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptor.php @@ -0,0 +1,72 @@ + + * @license MIT + */ +class LazyLoadingMethodInterceptor extends MethodGenerator +{ + /** + * @param \Zend\Code\Reflection\MethodReflection $originalMethod + * @param \Zend\Code\Generator\PropertyGenerator $initializerProperty + * @param \Zend\Code\Generator\PropertyGenerator $valueHolderProperty + * + * @return LazyLoadingMethodInterceptor|static + */ + public static function generateMethod( + MethodReflection $originalMethod, + PropertyGenerator $initializerProperty, + PropertyGenerator $valueHolderProperty + ) { + /* @var $method self */ + $method = static::fromReflection($originalMethod); + $initializerName = $initializerProperty->getName(); + $valueHolderName = $valueHolderProperty->getName(); + $parameters = $originalMethod->getParameters(); + $methodName = $originalMethod->getName(); + $initializerParams = array(); + $forwardedParams = array(); + + foreach ($parameters as $parameter) { + $parameterName = $parameter->getName(); + $initializerParams[] = var_export($parameterName, true) . ' => $' . $parameterName; + $forwardedParams[] = '$' . $parameterName; + } + + $method->setBody( + '$this->' . $initializerName + . ' && $this->' . $initializerName + . '->__invoke($this->' . $valueHolderName . ', $this, ' . var_export($methodName, true) + . ', array(' . implode(', ', $initializerParams) . '), $this->' . $initializerName . ");\n\n" + . 'return $this->' . $valueHolderName . '->' + . $methodName . '(' . implode(', ', $forwardedParams) . ');' + ); + $method->setDocblock('{@inheritDoc}'); + + return $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicClone.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicClone.php new file mode 100644 index 0000000..d62c46a --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicClone.php @@ -0,0 +1,53 @@ + + * @license MIT + */ +class MagicClone extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + PropertyGenerator $valueHolderProperty + ) { + parent::__construct($originalClass, '__clone'); + + $initializer = $initializerProperty->getName(); + $valueHolder = $valueHolderProperty->getName(); + + $this->setBody( + '$this->' . $initializer . ' && $this->' . $initializer + . '->__invoke($this->' . $valueHolder + . ', $this, \'__clone\', array(), $this->' . $initializer . ');' . "\n\n" + . '$this->' . $valueHolder . ' = clone $this->' . $valueHolder . ';' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGet.php new file mode 100644 index 0000000..43b3ec5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGet.php @@ -0,0 +1,69 @@ + + * @license MIT + */ +class MagicGet extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + PropertyGenerator $valueHolderProperty, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__get', array(new ParameterGenerator('name'))); + + $this->setDocblock(($originalClass->hasMethod('__get') ? "{@inheritDoc}\n" : '') . '@param string $name'); + + $initializer = $initializerProperty->getName(); + $valueHolder = $valueHolderProperty->getName(); + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' return $this->' . $valueHolder . '->$name;' + . "\n}\n\n"; + + $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_GET, + 'name', + null, + $valueHolderProperty + ); + + $this->setBody( + '$this->' . $initializer . ' && $this->' . $initializer + . '->__invoke($this->' . $valueHolder . ', $this, \'__get\', array(\'name\' => $name), $this->' + . $initializer . ');' + . "\n\n" . $callParent + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIsset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIsset.php new file mode 100644 index 0000000..11607a4 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIsset.php @@ -0,0 +1,72 @@ + + * @license MIT + */ +class MagicIsset extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + PropertyGenerator $valueHolderProperty, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__isset', array(new ParameterGenerator('name'))); + + $initializer = $initializerProperty->getName(); + $valueHolder = $valueHolderProperty->getName(); + $callParent = ''; + + $this->setDocblock(($originalClass->hasMethod('__isset') ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' return isset($this->' . $valueHolder . '->$name);' + . "\n}\n\n"; + } + + $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_ISSET, + 'name', + null, + $valueHolderProperty + ); + + $this->setBody( + '$this->' . $initializer . ' && $this->' . $initializer + . '->__invoke($this->' . $valueHolder . ', $this, \'__isset\', array(\'name\' => $name), $this->' + . $initializer . ');' . "\n\n" . $callParent + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSet.php new file mode 100644 index 0000000..4530319 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSet.php @@ -0,0 +1,79 @@ + + * @license MIT + */ +class MagicSet extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + PropertyGenerator $valueHolderProperty, + PublicPropertiesMap $publicProperties + ) { + parent::__construct( + $originalClass, + '__set', + array(new ParameterGenerator('name'), new ParameterGenerator('value')) + ); + + $initializer = $initializerProperty->getName(); + $valueHolder = $valueHolderProperty->getName(); + $callParent = ''; + + $this->setDocblock( + ($originalClass->hasMethod('__set') ? "{@inheritDoc}\n" : '') . "@param string \$name\n@param mixed \$value" + ); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' return ($this->' . $valueHolder . '->$name = $value);' + . "\n}\n\n"; + } + + $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_SET, + 'name', + 'value', + $valueHolderProperty + ); + + $this->setBody( + '$this->' . $initializer . ' && $this->' . $initializer + . '->__invoke($this->' . $valueHolder . ', $this, ' + . '\'__set\', array(\'name\' => $name, \'value\' => $value), $this->' . $initializer . ');' + . "\n\n" . $callParent + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleep.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleep.php new file mode 100644 index 0000000..20051c8 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleep.php @@ -0,0 +1,53 @@ + + * @license MIT + */ +class MagicSleep extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + PropertyGenerator $valueHolderProperty + ) { + parent::__construct($originalClass, '__sleep'); + + $initializer = $initializerProperty->getName(); + $valueHolder = $valueHolderProperty->getName(); + + $this->setBody( + '$this->' . $initializer . ' && $this->' . $initializer + . '->__invoke($this->' . $valueHolder . ', $this, \'__sleep\', array(), $this->' + . $initializer . ');' . "\n\n" + . 'return array(' . var_export($valueHolder, true) . ');' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnset.php new file mode 100644 index 0000000..0c33954 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnset.php @@ -0,0 +1,72 @@ + + * @license MIT + */ +class MagicUnset extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct( + ReflectionClass $originalClass, + PropertyGenerator $initializerProperty, + PropertyGenerator $valueHolderProperty, + PublicPropertiesMap $publicProperties + ) { + parent::__construct($originalClass, '__unset', array(new ParameterGenerator('name'))); + + $initializer = $initializerProperty->getName(); + $valueHolder = $valueHolderProperty->getName(); + $callParent = ''; + + $this->setDocblock(($originalClass->hasMethod('__isset') ? "{@inheritDoc}\n" : '') . '@param string $name'); + + if (! $publicProperties->isEmpty()) { + $callParent = 'if (isset(self::$' . $publicProperties->getName() . "[\$name])) {\n" + . ' unset($this->' . $valueHolder . '->$name);' . "\n\n return;" + . "\n}\n\n"; + } + + $callParent .= PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_UNSET, + 'name', + null, + $valueHolderProperty + ); + + $this->setBody( + '$this->' . $initializer . ' && $this->' . $initializer + . '->__invoke($this->' . $valueHolder . ', $this, \'__unset\', array(\'name\' => $name), $this->' + . $initializer . ');' . "\n\n" . $callParent + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializer.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializer.php new file mode 100644 index 0000000..db3400f --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializer.php @@ -0,0 +1,49 @@ + + * @license MIT + */ +class SetProxyInitializer extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $initializerProperty) + { + parent::__construct('setProxyInitializer'); + + $initializerParameter = new ParameterGenerator('initializer'); + + $initializerParameter->setType('Closure'); + $initializerParameter->setDefaultValue(null); + $this->setParameter($initializerParameter); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('$this->' . $initializerProperty->getName() . ' = $initializer;'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerProperty.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerProperty.php new file mode 100644 index 0000000..e7b436b --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerProperty.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class InitializerProperty extends PropertyGenerator +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('initializer')); + + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setDocblock('@var \\Closure|null initializer responsible for generating the wrapped object'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderProperty.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderProperty.php new file mode 100644 index 0000000..71895dc --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderProperty.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class ValueHolderProperty extends PropertyGenerator +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('valueHolder')); + + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setDocblock('@var \\Closure|null initializer responsible for generating the wrapped object'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolderGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolderGenerator.php new file mode 100644 index 0000000..0a9ab36 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/LazyLoadingValueHolderGenerator.php @@ -0,0 +1,111 @@ + + * @license MIT + */ +class LazyLoadingValueHolderGenerator implements ProxyGeneratorInterface +{ + /** + * {@inheritDoc} + */ + public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + CanProxyAssertion::assertClassCanBeProxied($originalClass); + + $interfaces = array('ProxyManager\\Proxy\\VirtualProxyInterface'); + $publicProperties = new PublicPropertiesMap($originalClass); + + if ($originalClass->isInterface()) { + $interfaces[] = $originalClass->getName(); + } else { + $classGenerator->setExtendedClass($originalClass->getName()); + } + + $classGenerator->setImplementedInterfaces($interfaces); + $classGenerator->addPropertyFromGenerator($valueHolder = new ValueHolderProperty()); + $classGenerator->addPropertyFromGenerator($initializer = new InitializerProperty()); + $classGenerator->addPropertyFromGenerator($publicProperties); + + array_map( + function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator) { + ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod); + }, + array_merge( + array_map( + function (ReflectionMethod $method) use ($initializer, $valueHolder) { + return LazyLoadingMethodInterceptor::generateMethod( + new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), + $initializer, + $valueHolder + ); + }, + ProxiedMethodsFilter::getProxiedMethods($originalClass) + ), + array( + new Constructor($originalClass, $initializer), + new MagicGet($originalClass, $initializer, $valueHolder, $publicProperties), + new MagicSet($originalClass, $initializer, $valueHolder, $publicProperties), + new MagicIsset($originalClass, $initializer, $valueHolder, $publicProperties), + new MagicUnset($originalClass, $initializer, $valueHolder, $publicProperties), + new MagicClone($originalClass, $initializer, $valueHolder), + new MagicSleep($originalClass, $initializer, $valueHolder), + new MagicWakeup($originalClass), + new SetProxyInitializer($initializer), + new GetProxyInitializer($initializer), + new InitializeProxy($initializer, $valueHolder), + new IsProxyInitialized($valueHolder), + new GetWrappedValueHolderValue($valueHolder), + ) + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/Constructor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/Constructor.php new file mode 100644 index 0000000..7f4c4ce --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/Constructor.php @@ -0,0 +1,55 @@ + + * @license MIT + */ +class Constructor extends MethodGenerator +{ + /** + * Constructor + * + * @param ReflectionClass $originalClass Reflection of the class to proxy + */ + public function __construct(ReflectionClass $originalClass) + { + parent::__construct('__construct'); + + /* @var $publicProperties \ReflectionProperty[] */ + $publicProperties = $originalClass->getProperties(ReflectionProperty::IS_PUBLIC); + $nullableProperties = array(); + + foreach ($publicProperties as $publicProperty) { + $nullableProperties[] = '$this->' . $publicProperty->getName() . ' = null;'; + } + + $this->setDocblock("@override constructor for null object initialization"); + if ($nullableProperties) { + $this->setBody(implode("\n", $nullableProperties)); + } + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptor.php new file mode 100644 index 0000000..2b63e50 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptor.php @@ -0,0 +1,53 @@ + + * @license MIT + */ +class NullObjectMethodInterceptor extends MethodGenerator +{ + /** + * @param \Zend\Code\Reflection\MethodReflection $originalMethod + * + * @return NullObjectMethodInterceptor|static + */ + public static function generateMethod(MethodReflection $originalMethod) + { + /* @var $method self */ + $method = static::fromReflection($originalMethod); + + if ($originalMethod->returnsReference()) { + $reference = UniqueIdentifierGenerator::getIdentifier('ref'); + + $method->setBody("\$$reference = null;\nreturn \$$reference;"); + } else { + $method->setBody(''); + } + + return $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObjectGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObjectGenerator.php new file mode 100644 index 0000000..db177ac --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/NullObjectGenerator.php @@ -0,0 +1,65 @@ + + * @license MIT + */ +class NullObjectGenerator implements ProxyGeneratorInterface +{ + /** + * {@inheritDoc} + */ + public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + CanProxyAssertion::assertClassCanBeProxied($originalClass); + + $interfaces = array('ProxyManager\\Proxy\\NullObjectInterface'); + + if ($originalClass->isInterface()) { + $interfaces[] = $originalClass->getName(); + } + + $classGenerator->setImplementedInterfaces($interfaces); + + foreach (ProxiedMethodsFilter::getProxiedMethods($originalClass) as $method) { + $classGenerator->addMethodFromGenerator( + NullObjectMethodInterceptor::generateMethod( + new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()) + ) + ); + } + + ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, new Constructor($originalClass)); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesDefaults.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesDefaults.php new file mode 100644 index 0000000..55822f3 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesDefaults.php @@ -0,0 +1,58 @@ + + * @license MIT + */ +class PublicPropertiesDefaults extends PropertyGenerator +{ + /** + * @var bool[] + */ + private $publicProperties = array(); + + /** + * @param \ReflectionClass $originalClass + */ + public function __construct(ReflectionClass $originalClass) + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('publicPropertiesDefaults')); + + $defaults = $originalClass->getDefaultProperties(); + + foreach ($originalClass->getProperties(ReflectionProperty::IS_PUBLIC) as $publicProperty) { + $name = $publicProperty->getName(); + $this->publicProperties[$name] = $defaults[$name]; + } + + $this->setDefaultValue($this->publicProperties); + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setStatic(true); + $this->setDocblock('@var mixed[] map of default property values of the parent class'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesMap.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesMap.php new file mode 100644 index 0000000..5682a4a --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/PropertyGenerator/PublicPropertiesMap.php @@ -0,0 +1,63 @@ + + * @license MIT + */ +class PublicPropertiesMap extends PropertyGenerator +{ + /** + * @var bool[] + */ + private $publicProperties = array(); + + /** + * @param \ReflectionClass $originalClass + */ + public function __construct(ReflectionClass $originalClass) + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('publicProperties')); + + foreach ($originalClass->getProperties(ReflectionProperty::IS_PUBLIC) as $publicProperty) { + $this->publicProperties[$publicProperty->getName()] = true; + } + + $this->setDefaultValue($this->publicProperties); + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setStatic(true); + $this->setDocblock('@var bool[] map of public properties of the parent class'); + } + + /** + * @return bool whether there are no public properties + */ + public function isEmpty() + { + return empty($this->publicProperties); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ProxyGeneratorInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ProxyGeneratorInterface.php new file mode 100644 index 0000000..df88aa8 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ProxyGeneratorInterface.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +interface ProxyGeneratorInterface +{ + /** + * Apply modifications to the provided $classGenerator to proxy logic from $originalClass + * + * @param \ReflectionClass $originalClass + * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * + * @return void + */ + public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/Constructor.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/Constructor.php new file mode 100644 index 0000000..d1fcb89 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/Constructor.php @@ -0,0 +1,63 @@ + + * @license MIT + */ +class Constructor extends MethodGenerator +{ + /** + * Constructor + * + * @param ReflectionClass $originalClass Reflection of the class to proxy + * @param PropertyGenerator $adapter Adapter property + */ + public function __construct(ReflectionClass $originalClass, PropertyGenerator $adapter) + { + parent::__construct('__construct'); + + $adapterName = $adapter->getName(); + + $this->setParameter(new ParameterGenerator($adapterName, 'ProxyManager\Factory\RemoteObject\AdapterInterface')); + + $this->setDocblock( + '@override constructor for remote object control\n\n' + . '@param \\ProxyManager\\Factory\\RemoteObject\\AdapterInterface \$adapter' + ); + + $body = '$this->' . $adapterName . ' = $' . $adapterName . ';'; + + foreach ($originalClass->getProperties() as $property) { + if ($property->isPublic() && ! $property->isStatic()) { + $body .= "\nunset(\$this->" . $property->getName() . ');'; + } + } + + $this->setBody($body); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicGet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicGet.php new file mode 100644 index 0000000..c1841dc --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicGet.php @@ -0,0 +1,49 @@ + + * @license MIT + */ +class MagicGet extends MagicMethodGenerator +{ + /** + * Constructor + * @param ReflectionClass $originalClass + * @param \Zend\Code\Generator\PropertyGenerator $adapterProperty + */ + public function __construct(ReflectionClass $originalClass, PropertyGenerator $adapterProperty) + { + parent::__construct($originalClass, '__get', array(new ParameterGenerator('name'))); + + $this->setDocblock('@param string $name'); + $this->setBody( + '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) + . ', \'__get\', array($name));' . "\n\n" . 'return $return;' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicIsset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicIsset.php new file mode 100644 index 0000000..254977c --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicIsset.php @@ -0,0 +1,50 @@ + + * @license MIT + */ +class MagicIsset extends MagicMethodGenerator +{ + /** + * Constructor + * @param ReflectionClass $originalClass + * @param \Zend\Code\Generator\PropertyGenerator $adapterProperty + */ + public function __construct(ReflectionClass $originalClass, PropertyGenerator $adapterProperty) + { + parent::__construct($originalClass, '__isset', array(new ParameterGenerator('name'))); + + $this->setDocblock('@param string $name'); + $this->setBody( + '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) + . ', \'__isset\', array($name));' . "\n\n" + . 'return $return;' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicSet.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicSet.php new file mode 100644 index 0000000..2b3c917 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicSet.php @@ -0,0 +1,54 @@ + + * @license MIT + */ +class MagicSet extends MagicMethodGenerator +{ + /** + * Constructor + * @param ReflectionClass $originalClass + * @param \Zend\Code\Generator\PropertyGenerator $adapterProperty + */ + public function __construct(ReflectionClass $originalClass, PropertyGenerator $adapterProperty) + { + parent::__construct( + $originalClass, + '__set', + array(new ParameterGenerator('name'), new ParameterGenerator('value')) + ); + + $this->setDocblock('@param string \$name\n@param mixed \$value'); + $this->setBody( + '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) + . ', \'__set\', array($name, $value));' . "\n\n" + . 'return $return;' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnset.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnset.php new file mode 100644 index 0000000..fa7aba8 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnset.php @@ -0,0 +1,51 @@ + + * @license MIT + */ +class MagicUnset extends MagicMethodGenerator +{ + /** + * Constructor + * + * @param ReflectionClass $originalClass + * @param PropertyGenerator $adapterProperty + */ + public function __construct(ReflectionClass $originalClass, PropertyGenerator $adapterProperty) + { + parent::__construct($originalClass, '__unset', array(new ParameterGenerator('name'))); + + $this->setDocblock('@param string $name'); + $this->setBody( + '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) + . ', \'__unset\', array($name));' . "\n\n" + . 'return $return;' + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php new file mode 100644 index 0000000..e8476a6 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethod.php @@ -0,0 +1,63 @@ + + * @license MIT + */ +class RemoteObjectMethod extends MethodGenerator +{ + /** + * @param \Zend\Code\Reflection\MethodReflection $originalMethod + * @param \Zend\Code\Generator\PropertyGenerator $adapterProperty + * @param \ReflectionClass $originalClass + * + * @return RemoteObjectMethod|static + */ + public static function generateMethod( + MethodReflection $originalMethod, + PropertyGenerator $adapterProperty, + ReflectionClass $originalClass + ) { + /* @var $method self */ + $method = static::fromReflection($originalMethod); + $parameters = $originalMethod->getParameters(); + $list = array(); + + foreach ($parameters as $parameter) { + $list[] = '$' . $parameter->getName(); + } + + $method->setBody( + '$return = $this->' . $adapterProperty->getName() . '->call(' . var_export($originalClass->getName(), true) + . ', ' . var_export($originalMethod->getName(), true) . ', array('. implode(', ', $list) .'));' . "\n\n" + . 'return $return;' + ); + + return $method; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterProperty.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterProperty.php new file mode 100644 index 0000000..5ab1dd1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterProperty.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class AdapterProperty extends PropertyGenerator +{ + /** + * Constructor + */ + public function __construct() + { + parent::__construct(UniqueIdentifierGenerator::getIdentifier('adapter')); + + $this->setVisibility(self::VISIBILITY_PRIVATE); + $this->setDocblock('@var \\ProxyManager\\Factory\\RemoteObject\\AdapterInterface Remote web service adapter'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObjectGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObjectGenerator.php new file mode 100644 index 0000000..8a4a4e9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/RemoteObjectGenerator.php @@ -0,0 +1,93 @@ + + * @license MIT + */ +class RemoteObjectGenerator implements ProxyGeneratorInterface +{ + /** + * {@inheritDoc} + */ + public function generate(ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + CanProxyAssertion::assertClassCanBeProxied($originalClass); + + $interfaces = array('ProxyManager\\Proxy\\RemoteObjectInterface'); + + if ($originalClass->isInterface()) { + $interfaces[] = $originalClass->getName(); + } else { + $classGenerator->setExtendedClass($originalClass->getName()); + } + + $classGenerator->setImplementedInterfaces($interfaces); + $classGenerator->addPropertyFromGenerator($adapter = new AdapterProperty()); + + array_map( + function (MethodGenerator $generatedMethod) use ($originalClass, $classGenerator) { + ClassGeneratorUtils::addMethodIfNotFinal($originalClass, $classGenerator, $generatedMethod); + }, + array_merge( + array_map( + function (ReflectionMethod $method) use ($adapter, $originalClass) { + return RemoteObjectMethod::generateMethod( + new MethodReflection($method->getDeclaringClass()->getName(), $method->getName()), + $adapter, + $originalClass + ); + }, + ProxiedMethodsFilter::getProxiedMethods( + $originalClass, + array('__get', '__set', '__isset', '__unset') + ) + ), + array( + new Constructor($originalClass, $adapter), + new MagicGet($originalClass, $adapter), + new MagicSet($originalClass, $adapter), + new MagicIsset($originalClass, $adapter), + new MagicUnset($originalClass, $adapter), + ) + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/ProxiedMethodsFilter.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/ProxiedMethodsFilter.php new file mode 100644 index 0000000..6024e33 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/ProxiedMethodsFilter.php @@ -0,0 +1,56 @@ + + * @license MIT + */ +class ProxiedMethodsFilter +{ + /** + * @param ReflectionClass $class reflection class from which methods should be extracted + * @param string[] $excluded methods to be ignored + * + * @return ReflectionMethod[] + */ + public static function getProxiedMethods( + ReflectionClass $class, + array $excluded = array('__get', '__set', '__isset', '__unset', '__clone', '__sleep', '__wakeup') + ) { + $ignored = array_flip(array_map('strtolower', $excluded)); + + return array_filter( + $class->getMethods(ReflectionMethod::IS_PUBLIC), + function (ReflectionMethod $method) use ($ignored) { + return ! ( + $method->isConstructor() + || isset($ignored[strtolower($method->getName())]) + || $method->isFinal() + || $method->isStatic() + ); + } + ); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/PublicScopeSimulator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/PublicScopeSimulator.php new file mode 100644 index 0000000..a908e58 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/Util/PublicScopeSimulator.php @@ -0,0 +1,190 @@ + + * @license MIT + */ +class PublicScopeSimulator +{ + const OPERATION_SET = 'set'; + const OPERATION_GET = 'get'; + const OPERATION_ISSET = 'isset'; + const OPERATION_UNSET = 'unset'; + + /** + * Generates code for simulating access to a property from the scope that is accessing a proxy. + * This is done by introspecting `debug_backtrace()` and then binding a closure to the scope + * of the parent caller. + * + * @param string $operationType operation to execute: one of 'get', 'set', 'isset' or 'unset' + * @param string $nameParameter name of the `name` parameter of the magic method + * @param string|null $valueParameter name of the `value` parameter of the magic method + * @param PropertyGenerator $valueHolder name of the property containing the target object from which + * to read the property. `$this` if none provided + * @param string|null $returnPropertyName name of the property to which we want to assign the result of + * the operation. Return directly if none provided + * + * @return string + * + * @throws \InvalidArgumentException + */ + public static function getPublicAccessSimulationCode( + $operationType, + $nameParameter, + $valueParameter = null, + PropertyGenerator $valueHolder = null, + $returnPropertyName = null + ) { + $byRef = self::getByRefReturnValue($operationType); + $value = static::OPERATION_SET === $operationType ? ', $value' : ''; + $target = '$this'; + + if ($valueHolder) { + $target = '$this->' . $valueHolder->getName(); + } + + return '$realInstanceReflection = new \\ReflectionClass(get_parent_class($this));' . "\n\n" + . 'if (! $realInstanceReflection->hasProperty($' . $nameParameter . ')) {' . "\n" + . ' $targetObject = ' . $target . ';' . "\n\n" + . self::getUndefinedPropertyNotice($operationType, $nameParameter) + . ' ' . self::getOperation($operationType, $nameParameter, $valueParameter) . ";\n" + . " return;\n" + . '}' . "\n\n" + . '$targetObject = ' . self::getTargetObject($valueHolder) . ";\n" + . '$accessor = function ' . $byRef . '() use ($targetObject, $name' . $value . ') {' . "\n" + . ' ' . self::getOperation($operationType, $nameParameter, $valueParameter) . "\n" + . "};\n" + . self::getScopeReBind() + . ( + $returnPropertyName + ? '$' . $returnPropertyName . ' = ' . $byRef . '$accessor();' + : '$returnValue = ' . $byRef . '$accessor();' . "\n\n" . 'return $returnValue;' + ); + } + + /** + * This will generate code that triggers a notice if access is attempted on a non-existing property + * + * @param string $operationType + * @param string $nameParameter + * + * @return string + */ + private static function getUndefinedPropertyNotice($operationType, $nameParameter) + { + if (static::OPERATION_GET !== $operationType) { + return ''; + } + + // + return ' $backtrace = debug_backtrace(false);' . "\n" + . ' trigger_error(\'Undefined property: \' . get_parent_class($this) . \'::$\' . $' + . $nameParameter + . ' . \' in \' . $backtrace[0][\'file\'] . \' on line \' . $backtrace[0][\'line\'], \E_USER_NOTICE);' + . "\n"; + } + + /** + * Defines whether the given operation produces a reference. + * + * Note: if the object is a wrapper, the wrapped instance is accessed directly. If the object + * is a ghost or the proxy has no wrapper, then an instance of the parent class is created via + * on-the-fly unserialization + * + * @param string $operationType + * + * @return string + */ + private static function getByRefReturnValue($operationType) + { + return (static::OPERATION_GET === $operationType || static::OPERATION_SET === $operationType) ? '& ' : ''; + } + + /** + * Retrieves the logic to fetch the object on which access should be attempted + * + * @param PropertyGenerator $valueHolder + * + * @return string + */ + private static function getTargetObject(PropertyGenerator $valueHolder = null) + { + if ($valueHolder) { + return '$this->' . $valueHolder->getName(); + } + + return 'unserialize(sprintf(\'O:%d:"%s":0:{}\', strlen(get_parent_class($this)), get_parent_class($this)))'; + } + + /** + * @param string $operationType + * @param string $nameParameter + * @param string|null $valueParameter + * + * @return string + * + * @throws \InvalidArgumentException + */ + private static function getOperation($operationType, $nameParameter, $valueParameter) + { + switch ($operationType) { + case static::OPERATION_GET: + return 'return $targetObject->$' . $nameParameter . ";"; + case static::OPERATION_SET: + if (! $valueParameter) { + throw new \InvalidArgumentException('Parameter $valueParameter not provided'); + } + + return 'return $targetObject->$' . $nameParameter . ' = $' . $valueParameter . ';'; + case static::OPERATION_ISSET: + return 'return isset($targetObject->$' . $nameParameter . ');'; + case static::OPERATION_UNSET: + return 'unset($targetObject->$' . $nameParameter . ');'; + } + + throw new \InvalidArgumentException(sprintf('Invalid operation "%s" provided', $operationType)); + } + + /** + * Generates code to bind operations to the parent scope if supported by the current PHP implementation + * + * @return string + */ + private static function getScopeReBind() + { + if (! method_exists('Closure', 'bind')) { + // @codeCoverageIgnoreStart + return ''; + // @codeCoverageIgnoreEnd + } + + return ' $backtrace = debug_backtrace(true);' . "\n" + . ' $scopeObject = isset($backtrace[1][\'object\'])' + . ' ? $backtrace[1][\'object\'] : new \stdClass();' . "\n" + . ' $accessor = $accessor->bindTo($scopeObject, get_class($scopeObject));' . "\n"; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValue.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValue.php new file mode 100644 index 0000000..7db81d3 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValue.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class GetWrappedValueHolderValue extends MethodGenerator +{ + /** + * Constructor + */ + public function __construct(PropertyGenerator $valueHolderProperty) + { + parent::__construct('getWrappedValueHolderValue'); + $this->setDocblock('{@inheritDoc}'); + $this->setBody('return $this->' . $valueHolderProperty->getName() . ';'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleep.php b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleep.php new file mode 100644 index 0000000..17aa0fd --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleep.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class MagicSleep extends MagicMethodGenerator +{ + /** + * Constructor + */ + public function __construct(ReflectionClass $originalClass, PropertyGenerator $valueHolderProperty) + { + parent::__construct($originalClass, '__sleep'); + + $this->setBody('return array(' . var_export($valueHolderProperty->getName(), true) . ');'); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGenerator.php new file mode 100644 index 0000000..70e90ac --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGenerator.php @@ -0,0 +1,58 @@ + + * @license MIT + */ +final class ClassSignatureGenerator implements ClassSignatureGeneratorInterface +{ + /** + * @var SignatureGeneratorInterface + */ + private $signatureGenerator; + + /** + * @param SignatureGeneratorInterface $signatureGenerator + */ + public function __construct(SignatureGeneratorInterface $signatureGenerator) + { + $this->signatureGenerator = $signatureGenerator; + } + + /** + * {@inheritDoc} + */ + public function addSignature(ClassGenerator $classGenerator, array $parameters) + { + $classGenerator->addPropertyFromGenerator(new PropertyGenerator( + 'signature' . $this->signatureGenerator->generateSignatureKey($parameters), + $this->signatureGenerator->generateSignature($parameters), + PropertyGenerator::FLAG_STATIC | PropertyGenerator::FLAG_PRIVATE + )); + + return $classGenerator; + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGeneratorInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGeneratorInterface.php new file mode 100644 index 0000000..a5553ed --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/ClassSignatureGeneratorInterface.php @@ -0,0 +1,40 @@ + + * @license MIT + */ +interface ClassSignatureGeneratorInterface +{ + /** + * Applies a signature to a given class generator + * + * @param ClassGenerator $classGenerator + * @param array $parameters + * + * @return ClassGenerator + */ + public function addSignature(ClassGenerator $classGenerator, array $parameters); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/ExceptionInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/ExceptionInterface.php new file mode 100644 index 0000000..c103153 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/ExceptionInterface.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +interface ExceptionInterface +{ +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/InvalidSignatureException.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/InvalidSignatureException.php new file mode 100644 index 0000000..7e974fc --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/InvalidSignatureException.php @@ -0,0 +1,50 @@ + + * @license MIT + */ +class InvalidSignatureException extends UnexpectedValueException implements ExceptionInterface +{ + /** + * @param ReflectionClass $class + * @param array $parameters + * @param string $signature + * @param string $expected + * + * @return self + */ + public static function fromInvalidSignature(ReflectionClass $class, array $parameters, $signature, $expected) + { + return new self(sprintf( + 'Found signature "%s" for class "%s" does not correspond to expected signature "%s" for %d parameters', + $signature, + $class->getName(), + $expected, + count($parameters) + )); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/MissingSignatureException.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/MissingSignatureException.php new file mode 100644 index 0000000..e53f527 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/Exception/MissingSignatureException.php @@ -0,0 +1,48 @@ + + * @license MIT + */ +class MissingSignatureException extends UnexpectedValueException implements ExceptionInterface +{ + /** + * @param ReflectionClass $class + * @param array $parameters + * @param string $expected + * + * @return self + */ + public static function fromMissingSignature(ReflectionClass $class, array $parameters, $expected) + { + return new self(sprintf( + 'No signature found for class "%s", expected signature "%s" for %d parameters', + $class->getName(), + $expected, + count($parameters) + )); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureChecker.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureChecker.php new file mode 100644 index 0000000..0a674ec --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureChecker.php @@ -0,0 +1,68 @@ + + * @license MIT + */ +final class SignatureChecker implements SignatureCheckerInterface +{ + /** + * @var SignatureGeneratorInterface + */ + private $signatureGenerator; + + /** + * @param SignatureGeneratorInterface $signatureGenerator + */ + public function __construct(SignatureGeneratorInterface $signatureGenerator) + { + $this->signatureGenerator = $signatureGenerator; + } + + /** + * {@inheritDoc} + */ + public function checkSignature(ReflectionClass $class, array $parameters) + { + $propertyName = 'signature' . $this->signatureGenerator->generateSignatureKey($parameters); + $signature = $this->signatureGenerator->generateSignature($parameters); + $defaultProperties = $class->getDefaultProperties(); + + if (! isset($defaultProperties[$propertyName])) { + throw MissingSignatureException::fromMissingSignature($class, $parameters, $signature); + } + + if ($defaultProperties[$propertyName] !== $signature) { + throw InvalidSignatureException::fromInvalidSignature( + $class, + $parameters, + $defaultProperties[$propertyName], + $signature + ); + } + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureCheckerInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureCheckerInterface.php new file mode 100644 index 0000000..ded6561 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureCheckerInterface.php @@ -0,0 +1,43 @@ + + * @license MIT + */ +interface SignatureCheckerInterface +{ + /** + * Checks whether the given signature is valid or not + * + * @param ReflectionClass $class + * @param array $parameters + * + * @return void + * + * @throws \ProxyManager\Signature\Exception\InvalidSignatureException + * @throws \ProxyManager\Signature\Exception\MissingSignatureException + */ + public function checkSignature(ReflectionClass $class, array $parameters); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGenerator.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGenerator.php new file mode 100644 index 0000000..473d3ea --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGenerator.php @@ -0,0 +1,66 @@ + + * @license MIT + */ +final class SignatureGenerator implements SignatureGeneratorInterface +{ + /** + * @var ParameterEncoder + */ + private $parameterEncoder; + + /** + * @var ParameterHasher + */ + private $parameterHasher; + + /** + * Constructor. + */ + public function __construct() + { + $this->parameterEncoder = new ParameterEncoder(); + $this->parameterHasher = new ParameterHasher(); + } + + /** + * {@inheritDoc} + */ + public function generateSignature(array $parameters) + { + return $this->parameterEncoder->encodeParameters($parameters); + } + + /** + * {@inheritDoc} + */ + public function generateSignatureKey(array $parameters) + { + return $this->parameterHasher->hashParameters($parameters); + } +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGeneratorInterface.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGeneratorInterface.php new file mode 100644 index 0000000..68ad30b --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Signature/SignatureGeneratorInterface.php @@ -0,0 +1,46 @@ + + * @license MIT + */ +interface SignatureGeneratorInterface +{ + /** + * Generates a signature to be used to verify generated code validity + * + * @param array $parameters + * + * @return string + */ + public function generateSignature(array $parameters); + + /** + * Generates a signature key to be looked up when verifying generated code validity + * + * @param array $parameters + * + * @return string + */ + public function generateSignatureKey(array $parameters); +} diff --git a/vendor/ocramius/proxy-manager/src/ProxyManager/Version.php b/vendor/ocramius/proxy-manager/src/ProxyManager/Version.php new file mode 100644 index 0000000..a036711 --- /dev/null +++ b/vendor/ocramius/proxy-manager/src/ProxyManager/Version.php @@ -0,0 +1,39 @@ + + * @license MIT + */ +final class Version +{ + const VERSION = '1.0.0'; + + /** + * Private constructor - this class is not meant to be instantiated + */ + private function __construct() + { + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Autoloader/AutoloaderTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Autoloader/AutoloaderTest.php new file mode 100644 index 0000000..aba8cf0 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Autoloader/AutoloaderTest.php @@ -0,0 +1,133 @@ + + * @license MIT + * + * @covers \ProxyManager\Autoloader\Autoloader + * @group Coverage + */ +class AutoloaderTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \ProxyManager\Autoloader\Autoloader + */ + protected $autoloader; + + /** + * @var \ProxyManager\FileLocator\FileLocatorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fileLocator; + + /** + * @var \ProxyManager\Inflector\ClassNameInflectorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $classNameInflector; + + /** + * @covers \ProxyManager\Autoloader\Autoloader::__construct + */ + public function setUp() + { + $this->fileLocator = $this->getMock('ProxyManager\\FileLocator\\FileLocatorInterface'); + $this->classNameInflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + $this->autoloader = new Autoloader($this->fileLocator, $this->classNameInflector); + } + + /** + * @covers \ProxyManager\Autoloader\Autoloader::__invoke + */ + public function testWillNotAutoloadUserClasses() + { + $className = 'Foo\\' . UniqueIdentifierGenerator::getIdentifier('Bar'); + $this + ->classNameInflector + ->expects($this->once()) + ->method('isProxyClassName') + ->with($className) + ->will($this->returnValue(false)); + + $this->assertFalse($this->autoloader->__invoke($className)); + } + + /** + * @covers \ProxyManager\Autoloader\Autoloader::__invoke + */ + public function testWillNotAutoloadNonExistingClass() + { + $className = 'Foo\\' . UniqueIdentifierGenerator::getIdentifier('Bar'); + $this + ->classNameInflector + ->expects($this->once()) + ->method('isProxyClassName') + ->with($className) + ->will($this->returnValue(true)); + $this + ->fileLocator + ->expects($this->once()) + ->method('getProxyFileName') + ->will($this->returnValue(__DIR__ . '/non-existing')); + + $this->assertFalse($this->autoloader->__invoke($className)); + } + + /** + * @covers \ProxyManager\Autoloader\Autoloader::__invoke + */ + public function testWillNotAutoloadExistingClass() + { + $this->assertFalse($this->autoloader->__invoke(__CLASS__)); + } + + /** + * @covers \ProxyManager\Autoloader\Autoloader::__invoke + */ + public function testWillAutoloadExistingFile() + { + $namespace = 'Foo'; + $className = UniqueIdentifierGenerator::getIdentifier('Bar'); + $fqcn = $namespace . '\\' . $className; + $fileName = sys_get_temp_dir() . '/foo_' . uniqid() . '.php'; + + file_put_contents($fileName, 'classNameInflector + ->expects($this->once()) + ->method('isProxyClassName') + ->with($fqcn) + ->will($this->returnValue(true)); + $this + ->fileLocator + ->expects($this->once()) + ->method('getProxyFileName') + ->will($this->returnValue($fileName)); + + $this->assertTrue($this->autoloader->__invoke($fqcn)); + $this->assertTrue(class_exists($fqcn, false)); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ConfigurationTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ConfigurationTest.php new file mode 100644 index 0000000..17ea451 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ConfigurationTest.php @@ -0,0 +1,183 @@ + + * @license MIT + * + * @group Coverage + */ +class ConfigurationTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \ProxyManager\Configuration + */ + protected $configuration; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->configuration = new Configuration(); + } + + /** + * @covers \ProxyManager\Configuration::getProxiesNamespace + * @covers \ProxyManager\Configuration::setProxiesNamespace + */ + public function testGetSetProxiesNamespace() + { + $this->assertSame( + 'ProxyManagerGeneratedProxy', + $this->configuration->getProxiesNamespace(), + 'Default setting check for BC' + ); + + $this->configuration->setProxiesNamespace('foo'); + $this->assertSame('foo', $this->configuration->getProxiesNamespace()); + } + + /** + * @covers \ProxyManager\Configuration::getClassNameInflector + * @covers \ProxyManager\Configuration::setClassNameInflector + */ + public function testSetGetClassNameInflector() + { + $this->assertInstanceOf( + 'ProxyManager\\Inflector\\ClassNameInflectorInterface', + $this->configuration->getClassNameInflector() + ); + + /* @var $inflector \ProxyManager\Inflector\ClassNameInflectorInterface */ + $inflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + + $this->configuration->setClassNameInflector($inflector); + $this->assertSame($inflector, $this->configuration->getClassNameInflector()); + } + + /** + * @covers \ProxyManager\Configuration::getGeneratorStrategy + * @covers \ProxyManager\Configuration::setGeneratorStrategy + */ + public function testSetGetGeneratorStrategy() + { + + $this->assertInstanceOf( + 'ProxyManager\\GeneratorStrategy\\GeneratorStrategyInterface', + $this->configuration->getGeneratorStrategy() + ); + + /* @var $strategy \ProxyManager\GeneratorStrategy\GeneratorStrategyInterface */ + $strategy = $this->getMock('ProxyManager\\GeneratorStrategy\\GeneratorStrategyInterface'); + + $this->configuration->setGeneratorStrategy($strategy); + $this->assertSame($strategy, $this->configuration->getGeneratorStrategy()); + } + + /** + * @covers \ProxyManager\Configuration::getProxiesTargetDir + * @covers \ProxyManager\Configuration::setProxiesTargetDir + */ + public function testSetGetProxiesTargetDir() + { + $this->assertTrue(is_dir($this->configuration->getProxiesTargetDir())); + + $this->configuration->setProxiesTargetDir(__DIR__); + $this->assertSame(__DIR__, $this->configuration->getProxiesTargetDir()); + } + + /** + * @covers \ProxyManager\Configuration::getProxyAutoloader + * @covers \ProxyManager\Configuration::setProxyAutoloader + */ + public function testSetGetProxyAutoloader() + { + $this->assertInstanceOf( + 'ProxyManager\\Autoloader\\AutoloaderInterface', + $this->configuration->getProxyAutoloader() + ); + + /* @var $autoloader \ProxyManager\Autoloader\AutoloaderInterface */ + $autoloader = $this->getMock('ProxyManager\\Autoloader\\AutoloaderInterface'); + + $this->configuration->setProxyAutoloader($autoloader); + $this->assertSame($autoloader, $this->configuration->getProxyAutoloader()); + } + + /** + * @covers \ProxyManager\Configuration::getSignatureGenerator + * @covers \ProxyManager\Configuration::setSignatureGenerator + */ + public function testSetGetSignatureGenerator() + { + $this->assertInstanceOf( + 'ProxyManager\\Signature\\SignatureGeneratorInterface', + $this->configuration->getSignatureGenerator() + ); + + /* @var $signatureGenerator \ProxyManager\Signature\SignatureGeneratorInterface */ + $signatureGenerator = $this->getMock('ProxyManager\\Signature\\SignatureGeneratorInterface'); + + $this->configuration->setSignatureGenerator($signatureGenerator); + $this->assertSame($signatureGenerator, $this->configuration->getSignatureGenerator()); + } + + /** + * @covers \ProxyManager\Configuration::getSignatureChecker + * @covers \ProxyManager\Configuration::setSignatureChecker + */ + public function testSetGetSignatureChecker() + { + $this->assertInstanceOf( + 'ProxyManager\\Signature\\SignatureCheckerInterface', + $this->configuration->getSignatureChecker() + ); + + /* @var $signatureChecker \ProxyManager\Signature\SignatureCheckerInterface */ + $signatureChecker = $this->getMock('ProxyManager\\Signature\\SignatureCheckerInterface'); + + $this->configuration->setSignatureChecker($signatureChecker); + $this->assertSame($signatureChecker, $this->configuration->getSignatureChecker()); + } + + /** + * @covers \ProxyManager\Configuration::getClassSignatureGenerator + * @covers \ProxyManager\Configuration::setClassSignatureGenerator + */ + public function testSetGetClassSignatureGenerator() + { + $this->assertInstanceOf( + 'ProxyManager\\Signature\\ClassSignatureGeneratorInterface', + $this->configuration->getClassSignatureGenerator() + ); + + /* @var $classSignatureGenerator \ProxyManager\Signature\ClassSignatureGeneratorInterface */ + $classSignatureGenerator = $this->getMock('ProxyManager\\Signature\\ClassSignatureGeneratorInterface'); + + $this->configuration->setClassSignatureGenerator($classSignatureGenerator); + $this->assertSame($classSignatureGenerator, $this->configuration->getClassSignatureGenerator()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/DisabledMethodExceptionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/DisabledMethodExceptionTest.php new file mode 100644 index 0000000..5dc574e --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/DisabledMethodExceptionTest.php @@ -0,0 +1,44 @@ + + * @license MIT + * + * @covers \ProxyManager\Exception\DisabledMethodException + * @group Coverage + */ +class DisabledMethodExceptionTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\Exception\DisabledMethodException::disabledMethod + */ + public function testProxyDirectoryNotFound() + { + $exception = DisabledMethodException::disabledMethod('foo::bar'); + + $this->assertSame('Method "foo::bar" is forcefully disabled', $exception->getMessage()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/FileNotWritableExceptionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/FileNotWritableExceptionTest.php new file mode 100644 index 0000000..915b8f1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/FileNotWritableExceptionTest.php @@ -0,0 +1,72 @@ + + * @license MIT + * + * @covers \ProxyManager\Exception\FileNotWritableException + * @group Coverage + */ +class FileNotWritableExceptionTest extends PHPUnit_Framework_TestCase +{ + public function testFromInvalidMoveOperation() + { + $exception = FileNotWritableException::fromInvalidMoveOperation('/tmp/a', '/tmp/b'); + + $this->assertInstanceOf('ProxyManager\\Exception\\FileNotWritableException', $exception); + $this->assertSame( + 'Could not move file "/tmp/a" to location "/tmp/b": either the source file is not readable,' + . ' or the destination is not writable', + $exception->getMessage() + ); + } + + public function testFromNotWritableLocationWithNonFilePath() + { + $exception = FileNotWritableException::fromNonWritableLocation(__DIR__); + + $this->assertInstanceOf('ProxyManager\\Exception\\FileNotWritableException', $exception); + $this->assertSame( + 'Could not write to path "' . __DIR__ . '": exists and is not a file', + $exception->getMessage() + ); + } + + public function testFromNotWritableLocationWithNonWritablePath() + { + $path = sys_get_temp_dir() . '/' . uniqid('FileNotWritableExceptionTestNonWritable', true); + + mkdir($path, 0555); + + $exception = FileNotWritableException::fromNonWritableLocation($path . '/foo'); + + $this->assertInstanceOf('ProxyManager\\Exception\\FileNotWritableException', $exception); + $this->assertSame( + 'Could not write to path "' . $path . '/foo": is not writable', + $exception->getMessage() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/InvalidProxiedClassExceptionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/InvalidProxiedClassExceptionTest.php new file mode 100644 index 0000000..0656dad --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/InvalidProxiedClassExceptionTest.php @@ -0,0 +1,67 @@ + + * @license MIT + * + * @covers \ProxyManager\Exception\InvalidProxiedClassException + * @group Coverage + */ +class InvalidProxiedClassExceptionTest extends PHPUnit_Framework_TestCase +{ + public function testInterfaceNotSupported() + { + $this->assertSame( + 'Provided interface "ProxyManagerTestAsset\BaseInterface" cannot be proxied', + InvalidProxiedClassException::interfaceNotSupported( + new ReflectionClass('ProxyManagerTestAsset\BaseInterface') + )->getMessage() + ); + } + + public function testFinalClassNotSupported() + { + $this->assertSame( + 'Provided class "ProxyManagerTestAsset\FinalClass" is final and cannot be proxied', + InvalidProxiedClassException::finalClassNotSupported( + new ReflectionClass('ProxyManagerTestAsset\FinalClass') + )->getMessage() + ); + } + + public function testAbstractProtectedMethodsNotSupported() + { + $this->assertSame( + 'Provided class "ProxyManagerTestAsset\ClassWithAbstractProtectedMethod" has following protected abstract' + . ' methods, and therefore cannot be proxied:' . "\n" + . 'ProxyManagerTestAsset\ClassWithAbstractProtectedMethod::protectedAbstractMethod', + InvalidProxiedClassException::abstractProtectedMethodsNotSupported( + new ReflectionClass('ProxyManagerTestAsset\ClassWithAbstractProtectedMethod') + )->getMessage() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/InvalidProxyDirectoryExceptionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/InvalidProxyDirectoryExceptionTest.php new file mode 100644 index 0000000..71edab1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/InvalidProxyDirectoryExceptionTest.php @@ -0,0 +1,44 @@ + + * @license MIT + * + * @covers \ProxyManager\Exception\InvalidProxyDirectoryException + * @group Coverage + */ +class InvalidProxyDirectoryExceptionTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\Exception\InvalidProxyDirectoryException::proxyDirectoryNotFound + */ + public function testProxyDirectoryNotFound() + { + $exception = InvalidProxyDirectoryException::proxyDirectoryNotFound('foo/bar'); + + $this->assertSame('Provided directory "foo/bar" does not exist', $exception->getMessage()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/UnsupportedProxiedClassExceptionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/UnsupportedProxiedClassExceptionTest.php new file mode 100644 index 0000000..68977c8 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Exception/UnsupportedProxiedClassExceptionTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @covers \ProxyManager\Exception\UnsupportedProxiedClassException + * @group Coverage + */ +class UnsupportedProxiedClassExceptionTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\Exception\UnsupportedProxiedClassException::unsupportedLocalizedReflectionProperty + */ + public function testUnsupportedLocalizedReflectionProperty() + { + $this->assertSame( + 'Provided reflection property "property0" of class "ProxyManagerTestAsset\ClassWithPrivateProperties" ' + . 'is private and cannot be localized in PHP 5.3', + UnsupportedProxiedClassException::unsupportedLocalizedReflectionProperty( + new ReflectionProperty('ProxyManagerTestAsset\ClassWithPrivateProperties', 'property0') + )->getMessage() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AbstractBaseFactoryTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AbstractBaseFactoryTest.php new file mode 100644 index 0000000..2c1b759 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AbstractBaseFactoryTest.php @@ -0,0 +1,158 @@ + + * @license MIT + * + * @covers \ProxyManager\Factory\AbstractBaseFactory + * @group Coverage + */ +class AbstractBaseFactoryTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \ProxyManager\Factory\AbstractBaseFactory + */ + private $factory; + + /** + * @var \ProxyManager\ProxyGenerator\ProxyGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $generator; + + /** + * @var \ProxyManager\Inflector\ClassNameInflectorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classNameInflector; + + /** + * @var \ProxyManager\GeneratorStrategy\GeneratorStrategyInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $generatorStrategy; + + /** + * @var \ProxyManager\Autoloader\AutoloaderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $proxyAutoloader; + + /** + * @var \ProxyManager\Signature\SignatureCheckerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $signatureChecker; + + /** + * @var \ProxyManager\Signature\ClassSignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classSignatureGenerator; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $configuration = $this->getMock('ProxyManager\\Configuration'); + $this->generator = $this->getMock('ProxyManager\\ProxyGenerator\\ProxyGeneratorInterface'); + $this->classNameInflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + $this->generatorStrategy = $this->getMock('ProxyManager\\GeneratorStrategy\\GeneratorStrategyInterface'); + $this->proxyAutoloader = $this->getMock('ProxyManager\\Autoloader\\AutoloaderInterface'); + $this->signatureChecker = $this->getMock('ProxyManager\\Signature\\SignatureCheckerInterface'); + $this->classSignatureGenerator = $this->getMock('ProxyManager\\Signature\\ClassSignatureGeneratorInterface'); + + $configuration + ->expects($this->any()) + ->method('getClassNameInflector') + ->will($this->returnValue($this->classNameInflector)); + + $configuration + ->expects($this->any()) + ->method('getGeneratorStrategy') + ->will($this->returnValue($this->generatorStrategy)); + + $configuration + ->expects($this->any()) + ->method('getProxyAutoloader') + ->will($this->returnValue($this->proxyAutoloader)); + + $configuration + ->expects($this->any()) + ->method('getSignatureChecker') + ->will($this->returnValue($this->signatureChecker)); + + $configuration + ->expects($this->any()) + ->method('getClassSignatureGenerator') + ->will($this->returnValue($this->classSignatureGenerator)); + + $this + ->classNameInflector + ->expects($this->any()) + ->method('getUserClassName') + ->will($this->returnValue('stdClass')); + + $this->factory = $this->getMockForAbstractClass( + 'ProxyManager\\Factory\\AbstractBaseFactory', + array($configuration) + ); + + $this->factory->expects($this->any())->method('getGenerator')->will($this->returnValue($this->generator)); + } + + public function testGeneratesClass() + { + $generateProxy = new ReflectionMethod($this->factory, 'generateProxy'); + + $generateProxy->setAccessible(true); + $generatedClass = UniqueIdentifierGenerator::getIdentifier('fooBar'); + + $this + ->classNameInflector + ->expects($this->any()) + ->method('getProxyClassName') + ->with('stdClass') + ->will($this->returnValue($generatedClass)); + + $this + ->generatorStrategy + ->expects($this->once()) + ->method('generate') + ->with($this->isInstanceOf('Zend\\Code\\Generator\\ClassGenerator')); + $this + ->proxyAutoloader + ->expects($this->once()) + ->method('__invoke') + ->with($generatedClass) + ->will($this->returnCallback(function ($className) { + eval('class ' . $className . ' {}'); + })); + + $this->signatureChecker->expects($this->atLeastOnce())->method('checkSignature'); + $this->classSignatureGenerator->expects($this->once())->method('addSignature')->will($this->returnArgument(0)); + + $this->assertSame($generatedClass, $generateProxy->invoke($this->factory, 'stdClass')); + $this->assertTrue(class_exists($generatedClass, false)); + $this->assertSame($generatedClass, $generateProxy->invoke($this->factory, 'stdClass')); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AccessInterceptorScopeLocalizerFactoryTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AccessInterceptorScopeLocalizerFactoryTest.php new file mode 100644 index 0000000..ce10d0c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AccessInterceptorScopeLocalizerFactoryTest.php @@ -0,0 +1,199 @@ + + * @license MIT + * + * @group Coverage + */ +class AccessInterceptorScopeLocalizerFactoryTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $inflector; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $signatureChecker; + + /** + * @var \ProxyManager\Signature\ClassSignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classSignatureGenerator; + + /** + * @var \ProxyManager\Configuration|\PHPUnit_Framework_MockObject_MockObject + */ + protected $config; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->config = $this->getMock('ProxyManager\\Configuration'); + $this->inflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + $this->signatureChecker = $this->getMock('ProxyManager\\Signature\\SignatureCheckerInterface'); + $this->classSignatureGenerator = $this->getMock('ProxyManager\\Signature\\ClassSignatureGeneratorInterface'); + + $this + ->config + ->expects($this->any()) + ->method('getClassNameInflector') + ->will($this->returnValue($this->inflector)); + + $this + ->config + ->expects($this->any()) + ->method('getSignatureChecker') + ->will($this->returnValue($this->signatureChecker)); + + $this + ->config + ->expects($this->any()) + ->method('getClassSignatureGenerator') + ->will($this->returnValue($this->classSignatureGenerator)); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\AccessInterceptorScopeLocalizerFactory::__construct + */ + public function testWithOptionalFactory() + { + $factory = new AccessInterceptorValueHolderFactory(); + $this->assertAttributeNotEmpty('configuration', $factory); + $this->assertAttributeInstanceOf('ProxyManager\Configuration', 'configuration', $factory); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\AccessInterceptorScopeLocalizerFactory::__construct + * @covers \ProxyManager\Factory\AccessInterceptorScopeLocalizerFactory::createProxy + * @covers \ProxyManager\Factory\AccessInterceptorScopeLocalizerFactory::getGenerator + */ + public function testWillSkipAutoGeneration() + { + $instance = new stdClass(); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with('stdClass') + ->will($this->returnValue('ProxyManagerTestAsset\\AccessInterceptorValueHolderMock')); + + $factory = new AccessInterceptorScopeLocalizerFactory($this->config); + /* @var $proxy \ProxyManagerTestAsset\AccessInterceptorValueHolderMock */ + $proxy = $factory->createProxy($instance, array('foo'), array('bar')); + + $this->assertInstanceOf('ProxyManagerTestAsset\\AccessInterceptorValueHolderMock', $proxy); + $this->assertSame($instance, $proxy->instance); + $this->assertSame(array('foo'), $proxy->prefixInterceptors); + $this->assertSame(array('bar'), $proxy->suffixInterceptors); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\AccessInterceptorScopeLocalizerFactory::__construct + * @covers \ProxyManager\Factory\AccessInterceptorScopeLocalizerFactory::createProxy + * @covers \ProxyManager\Factory\AccessInterceptorScopeLocalizerFactory::getGenerator + * + * NOTE: serious mocking going on in here (a class is generated on-the-fly) - careful + */ + public function testWillTryAutoGeneration() + { + $instance = new stdClass(); + $proxyClassName = UniqueIdentifierGenerator::getIdentifier('bar'); + $generator = $this->getMock('ProxyManager\GeneratorStrategy\\GeneratorStrategyInterface'); + $autoloader = $this->getMock('ProxyManager\\Autoloader\\AutoloaderInterface'); + + $this->config->expects($this->any())->method('getGeneratorStrategy')->will($this->returnValue($generator)); + $this->config->expects($this->any())->method('getProxyAutoloader')->will($this->returnValue($autoloader)); + + $generator + ->expects($this->once()) + ->method('generate') + ->with( + $this->callback( + function (ClassGenerator $targetClass) use ($proxyClassName) { + return $targetClass->getName() === $proxyClassName; + } + ) + ); + + // simulate autoloading + $autoloader + ->expects($this->once()) + ->method('__invoke') + ->with($proxyClassName) + ->will( + $this->returnCallback( + function () use ($proxyClassName) { + eval( + 'class ' . $proxyClassName + . ' extends \\ProxyManagerTestAsset\\AccessInterceptorValueHolderMock {}' + ); + } + ) + ); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with('stdClass') + ->will($this->returnValue($proxyClassName)); + + $this + ->inflector + ->expects($this->once()) + ->method('getUserClassName') + ->with('stdClass') + ->will($this->returnValue('ProxyManagerTestAsset\\LazyLoadingMock')); + + $this->signatureChecker->expects($this->atLeastOnce())->method('checkSignature'); + $this->classSignatureGenerator->expects($this->once())->method('addSignature')->will($this->returnArgument(0)); + + $factory = new AccessInterceptorScopeLocalizerFactory($this->config); + /* @var $proxy \ProxyManagerTestAsset\AccessInterceptorValueHolderMock */ + $proxy = $factory->createProxy($instance, array('foo'), array('bar')); + + $this->assertInstanceOf($proxyClassName, $proxy); + $this->assertSame($instance, $proxy->instance); + $this->assertSame(array('foo'), $proxy->prefixInterceptors); + $this->assertSame(array('bar'), $proxy->suffixInterceptors); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AccessInterceptorValueHolderFactoryTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AccessInterceptorValueHolderFactoryTest.php new file mode 100644 index 0000000..a9a881c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/AccessInterceptorValueHolderFactoryTest.php @@ -0,0 +1,198 @@ + + * @license MIT + * + * @group Coverage + */ +class AccessInterceptorValueHolderFactoryTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $inflector; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $signatureChecker; + + /** + * @var \ProxyManager\Signature\ClassSignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classSignatureGenerator; + + /** + * @var \ProxyManager\Configuration|\PHPUnit_Framework_MockObject_MockObject + */ + protected $config; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->config = $this->getMock('ProxyManager\\Configuration'); + $this->inflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + $this->signatureChecker = $this->getMock('ProxyManager\\Signature\\SignatureCheckerInterface'); + $this->classSignatureGenerator = $this->getMock('ProxyManager\\Signature\\ClassSignatureGeneratorInterface'); + + $this + ->config + ->expects($this->any()) + ->method('getClassNameInflector') + ->will($this->returnValue($this->inflector)); + + $this + ->config + ->expects($this->any()) + ->method('getSignatureChecker') + ->will($this->returnValue($this->signatureChecker)); + + $this + ->config + ->expects($this->any()) + ->method('getClassSignatureGenerator') + ->will($this->returnValue($this->classSignatureGenerator)); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\AccessInterceptorValueHolderFactory::__construct + */ + public function testWithOptionalFactory() + { + $factory = new AccessInterceptorValueHolderFactory(); + $this->assertAttributeNotEmpty('configuration', $factory); + $this->assertAttributeInstanceOf('ProxyManager\Configuration', 'configuration', $factory); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\AccessInterceptorValueHolderFactory::__construct + * @covers \ProxyManager\Factory\AccessInterceptorValueHolderFactory::createProxy + * @covers \ProxyManager\Factory\AccessInterceptorValueHolderFactory::getGenerator + */ + public function testWillSkipAutoGeneration() + { + $instance = new stdClass(); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with('stdClass') + ->will($this->returnValue('ProxyManagerTestAsset\\AccessInterceptorValueHolderMock')); + + $factory = new AccessInterceptorValueHolderFactory($this->config); + /* @var $proxy \ProxyManagerTestAsset\AccessInterceptorValueHolderMock */ + $proxy = $factory->createProxy($instance, array('foo'), array('bar')); + + $this->assertInstanceOf('ProxyManagerTestAsset\\AccessInterceptorValueHolderMock', $proxy); + $this->assertSame($instance, $proxy->instance); + $this->assertSame(array('foo'), $proxy->prefixInterceptors); + $this->assertSame(array('bar'), $proxy->suffixInterceptors); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\AccessInterceptorValueHolderFactory::__construct + * @covers \ProxyManager\Factory\AccessInterceptorValueHolderFactory::createProxy + * @covers \ProxyManager\Factory\AccessInterceptorValueHolderFactory::getGenerator + * + * NOTE: serious mocking going on in here (a class is generated on-the-fly) - careful + */ + public function testWillTryAutoGeneration() + { + $instance = new stdClass(); + $proxyClassName = UniqueIdentifierGenerator::getIdentifier('bar'); + $generator = $this->getMock('ProxyManager\GeneratorStrategy\\GeneratorStrategyInterface'); + $autoloader = $this->getMock('ProxyManager\\Autoloader\\AutoloaderInterface'); + + $this->config->expects($this->any())->method('getGeneratorStrategy')->will($this->returnValue($generator)); + $this->config->expects($this->any())->method('getProxyAutoloader')->will($this->returnValue($autoloader)); + + $generator + ->expects($this->once()) + ->method('generate') + ->with( + $this->callback( + function (ClassGenerator $targetClass) use ($proxyClassName) { + return $targetClass->getName() === $proxyClassName; + } + ) + ); + + // simulate autoloading + $autoloader + ->expects($this->once()) + ->method('__invoke') + ->with($proxyClassName) + ->will( + $this->returnCallback( + function () use ($proxyClassName) { + eval( + 'class ' . $proxyClassName + . ' extends \\ProxyManagerTestAsset\\AccessInterceptorValueHolderMock {}' + ); + } + ) + ); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with('stdClass') + ->will($this->returnValue($proxyClassName)); + + $this + ->inflector + ->expects($this->once()) + ->method('getUserClassName') + ->with('stdClass') + ->will($this->returnValue('ProxyManagerTestAsset\\LazyLoadingMock')); + + $this->signatureChecker->expects($this->atLeastOnce())->method('checkSignature'); + $this->classSignatureGenerator->expects($this->once())->method('addSignature')->will($this->returnArgument(0)); + + $factory = new AccessInterceptorValueHolderFactory($this->config); + /* @var $proxy \ProxyManagerTestAsset\AccessInterceptorValueHolderMock */ + $proxy = $factory->createProxy($instance, array('foo'), array('bar')); + + $this->assertInstanceOf($proxyClassName, $proxy); + $this->assertSame($instance, $proxy->instance); + $this->assertSame(array('foo'), $proxy->prefixInterceptors); + $this->assertSame(array('bar'), $proxy->suffixInterceptors); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/LazyLoadingGhostFactoryTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/LazyLoadingGhostFactoryTest.php new file mode 100644 index 0000000..2089f58 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/LazyLoadingGhostFactoryTest.php @@ -0,0 +1,193 @@ + + * @license MIT + * + * @group Coverage + */ +class LazyLoadingGhostFactoryTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $inflector; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $signatureChecker; + + /** + * @var \ProxyManager\Signature\ClassSignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classSignatureGenerator; + + /** + * @var \ProxyManager\Configuration|\PHPUnit_Framework_MockObject_MockObject + */ + protected $config; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->config = $this->getMock('ProxyManager\\Configuration'); + $this->inflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + $this->signatureChecker = $this->getMock('ProxyManager\\Signature\\SignatureCheckerInterface'); + $this->classSignatureGenerator = $this->getMock('ProxyManager\\Signature\\ClassSignatureGeneratorInterface'); + + $this + ->config + ->expects($this->any()) + ->method('getClassNameInflector') + ->will($this->returnValue($this->inflector)); + + $this + ->config + ->expects($this->any()) + ->method('getSignatureChecker') + ->will($this->returnValue($this->signatureChecker)); + + $this + ->config + ->expects($this->any()) + ->method('getClassSignatureGenerator') + ->will($this->returnValue($this->classSignatureGenerator)); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\LazyLoadingGhostFactory::__construct + */ + public function testWithOptionalFactory() + { + $factory = new LazyLoadingGhostFactory(); + $this->assertAttributeNotEmpty('configuration', $factory); + $this->assertAttributeInstanceOf('ProxyManager\Configuration', 'configuration', $factory); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\LazyLoadingGhostFactory::__construct + * @covers \ProxyManager\Factory\LazyLoadingGhostFactory::createProxy + */ + public function testWillSkipAutoGeneration() + { + $className = UniqueIdentifierGenerator::getIdentifier('foo'); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with($className) + ->will($this->returnValue('ProxyManagerTestAsset\\LazyLoadingMock')); + + $factory = new LazyLoadingGhostFactory($this->config); + $initializer = function () { + }; + /* @var $proxy \ProxyManagerTestAsset\LazyLoadingMock */ + $proxy = $factory->createProxy($className, $initializer); + + $this->assertInstanceOf('ProxyManagerTestAsset\\LazyLoadingMock', $proxy); + $this->assertSame($initializer, $proxy->initializer); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\LazyLoadingGhostFactory::__construct + * @covers \ProxyManager\Factory\LazyLoadingGhostFactory::createProxy + * @covers \ProxyManager\Factory\LazyLoadingGhostFactory::getGenerator + * + * NOTE: serious mocking going on in here (a class is generated on-the-fly) - careful + */ + public function testWillTryAutoGeneration() + { + $className = UniqueIdentifierGenerator::getIdentifier('foo'); + $proxyClassName = UniqueIdentifierGenerator::getIdentifier('bar'); + $generator = $this->getMock('ProxyManager\\GeneratorStrategy\\GeneratorStrategyInterface'); + $autoloader = $this->getMock('ProxyManager\\Autoloader\\AutoloaderInterface'); + + $this->config->expects($this->any())->method('getGeneratorStrategy')->will($this->returnValue($generator)); + $this->config->expects($this->any())->method('getProxyAutoloader')->will($this->returnValue($autoloader)); + + $generator + ->expects($this->once()) + ->method('generate') + ->with( + $this->callback( + function (ClassGenerator $targetClass) use ($proxyClassName) { + return $targetClass->getName() === $proxyClassName; + } + ) + ); + + // simulate autoloading + $autoloader + ->expects($this->once()) + ->method('__invoke') + ->with($proxyClassName) + ->will( + $this->returnCallback( + function () use ($proxyClassName) { + eval('class ' . $proxyClassName . ' extends \\ProxyManagerTestAsset\\LazyLoadingMock {}'); + } + ) + ); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with($className) + ->will($this->returnValue($proxyClassName)); + + $this + ->inflector + ->expects($this->once()) + ->method('getUserClassName') + ->with($className) + ->will($this->returnValue('ProxyManagerTestAsset\\LazyLoadingMock')); + + $this->signatureChecker->expects($this->atLeastOnce())->method('checkSignature'); + $this->classSignatureGenerator->expects($this->once())->method('addSignature')->will($this->returnArgument(0)); + + $factory = new LazyLoadingGhostFactory($this->config); + $initializer = function () { + }; + /* @var $proxy \ProxyManagerTestAsset\LazyLoadingMock */ + $proxy = $factory->createProxy($className, $initializer); + + $this->assertInstanceOf($proxyClassName, $proxy); + $this->assertSame($initializer, $proxy->initializer); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/LazyLoadingValueHolderFactoryTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/LazyLoadingValueHolderFactoryTest.php new file mode 100644 index 0000000..31c0f3b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/LazyLoadingValueHolderFactoryTest.php @@ -0,0 +1,193 @@ + + * @license MIT + * + * @group Coverage + */ +class LazyLoadingValueHolderFactoryTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $inflector; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $signatureChecker; + + /** + * @var \ProxyManager\Signature\ClassSignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classSignatureGenerator; + + /** + * @var \ProxyManager\Configuration|\PHPUnit_Framework_MockObject_MockObject + */ + protected $config; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->config = $this->getMock('ProxyManager\\Configuration'); + $this->inflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + $this->signatureChecker = $this->getMock('ProxyManager\\Signature\\SignatureCheckerInterface'); + $this->classSignatureGenerator = $this->getMock('ProxyManager\\Signature\\ClassSignatureGeneratorInterface'); + + $this + ->config + ->expects($this->any()) + ->method('getClassNameInflector') + ->will($this->returnValue($this->inflector)); + + $this + ->config + ->expects($this->any()) + ->method('getSignatureChecker') + ->will($this->returnValue($this->signatureChecker)); + + $this + ->config + ->expects($this->any()) + ->method('getClassSignatureGenerator') + ->will($this->returnValue($this->classSignatureGenerator)); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\LazyLoadingValueHolderFactory::__construct + */ + public function testWithOptionalFactory() + { + $factory = new LazyLoadingValueHolderFactory(); + $this->assertAttributeNotEmpty('configuration', $factory); + $this->assertAttributeInstanceOf('ProxyManager\Configuration', 'configuration', $factory); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\LazyLoadingValueHolderFactory::__construct + * @covers \ProxyManager\Factory\LazyLoadingValueHolderFactory::createProxy + */ + public function testWillSkipAutoGeneration() + { + $className = UniqueIdentifierGenerator::getIdentifier('foo'); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with($className) + ->will($this->returnValue('ProxyManagerTestAsset\\LazyLoadingMock')); + + $factory = new LazyLoadingValueHolderFactory($this->config); + $initializer = function () { + }; + /* @var $proxy \ProxyManagerTestAsset\LazyLoadingMock */ + $proxy = $factory->createProxy($className, $initializer); + + $this->assertInstanceOf('ProxyManagerTestAsset\\LazyLoadingMock', $proxy); + $this->assertSame($initializer, $proxy->initializer); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\LazyLoadingValueHolderFactory::__construct + * @covers \ProxyManager\Factory\LazyLoadingValueHolderFactory::createProxy + * @covers \ProxyManager\Factory\LazyLoadingValueHolderFactory::getGenerator + * + * NOTE: serious mocking going on in here (a class is generated on-the-fly) - careful + */ + public function testWillTryAutoGeneration() + { + $className = UniqueIdentifierGenerator::getIdentifier('foo'); + $proxyClassName = UniqueIdentifierGenerator::getIdentifier('bar'); + $generator = $this->getMock('ProxyManager\\GeneratorStrategy\\GeneratorStrategyInterface'); + $autoloader = $this->getMock('ProxyManager\\Autoloader\\AutoloaderInterface'); + + $this->config->expects($this->any())->method('getGeneratorStrategy')->will($this->returnValue($generator)); + $this->config->expects($this->any())->method('getProxyAutoloader')->will($this->returnValue($autoloader)); + + $generator + ->expects($this->once()) + ->method('generate') + ->with( + $this->callback( + function (ClassGenerator $targetClass) use ($proxyClassName) { + return $targetClass->getName() === $proxyClassName; + } + ) + ); + + // simulate autoloading + $autoloader + ->expects($this->once()) + ->method('__invoke') + ->with($proxyClassName) + ->will( + $this->returnCallback( + function () use ($proxyClassName) { + eval('class ' . $proxyClassName . ' extends \\ProxyManagerTestAsset\\LazyLoadingMock {}'); + } + ) + ); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with($className) + ->will($this->returnValue($proxyClassName)); + + $this + ->inflector + ->expects($this->once()) + ->method('getUserClassName') + ->with($className) + ->will($this->returnValue('ProxyManagerTestAsset\\LazyLoadingMock')); + + $this->signatureChecker->expects($this->atLeastOnce())->method('checkSignature'); + $this->classSignatureGenerator->expects($this->once())->method('addSignature')->will($this->returnArgument(0)); + + $factory = new LazyLoadingValueHolderFactory($this->config); + $initializer = function () { + }; + /* @var $proxy \ProxyManagerTestAsset\LazyLoadingMock */ + $proxy = $factory->createProxy($className, $initializer); + + $this->assertInstanceOf($proxyClassName, $proxy); + $this->assertSame($initializer, $proxy->initializer); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/NullObjectFactoryTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/NullObjectFactoryTest.php new file mode 100644 index 0000000..ef3f2a7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/NullObjectFactoryTest.php @@ -0,0 +1,180 @@ + + * @license MIT + * + * @group Coverage + */ +class NullObjectFactoryTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $inflector; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $signatureChecker; + + /** + * @var \ProxyManager\Signature\ClassSignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classSignatureGenerator; + + /** + * @var \ProxyManager\Configuration|\PHPUnit_Framework_MockObject_MockObject + */ + protected $config; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->config = $this->getMock('ProxyManager\\Configuration'); + $this->inflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + $this->signatureChecker = $this->getMock('ProxyManager\\Signature\\SignatureCheckerInterface'); + $this->classSignatureGenerator = $this->getMock('ProxyManager\\Signature\\ClassSignatureGeneratorInterface'); + + $this + ->config + ->expects($this->any()) + ->method('getClassNameInflector') + ->will($this->returnValue($this->inflector)); + + $this + ->config + ->expects($this->any()) + ->method('getSignatureChecker') + ->will($this->returnValue($this->signatureChecker)); + + $this + ->config + ->expects($this->any()) + ->method('getClassSignatureGenerator') + ->will($this->returnValue($this->classSignatureGenerator)); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\NullObjectFactory::__construct + * @covers \ProxyManager\Factory\NullObjectFactory::createProxy + * @covers \ProxyManager\Factory\NullObjectFactory::getGenerator + */ + public function testWillSkipAutoGeneration() + { + $instance = new stdClass(); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with('stdClass') + ->will($this->returnValue('ProxyManagerTestAsset\\NullObjectMock')); + + $factory = new NullObjectFactory($this->config); + /* @var $proxy \ProxyManagerTestAsset\NullObjectMock */ + $proxy = $factory->createProxy($instance); + + $this->assertInstanceOf('ProxyManagerTestAsset\\NullObjectMock', $proxy); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\NullObjectFactory::__construct + * @covers \ProxyManager\Factory\NullObjectFactory::createProxy + * @covers \ProxyManager\Factory\NullObjectFactory::getGenerator + * + * NOTE: serious mocking going on in here (a class is generated on-the-fly) - careful + */ + public function testWillTryAutoGeneration() + { + $instance = new stdClass(); + $proxyClassName = UniqueIdentifierGenerator::getIdentifier('bar'); + $generator = $this->getMock('ProxyManager\GeneratorStrategy\\GeneratorStrategyInterface'); + $autoloader = $this->getMock('ProxyManager\\Autoloader\\AutoloaderInterface'); + + $this->config->expects($this->any())->method('getGeneratorStrategy')->will($this->returnValue($generator)); + $this->config->expects($this->any())->method('getProxyAutoloader')->will($this->returnValue($autoloader)); + + $generator + ->expects($this->once()) + ->method('generate') + ->with( + $this->callback( + function (ClassGenerator $targetClass) use ($proxyClassName) { + return $targetClass->getName() === $proxyClassName; + } + ) + ); + + // simulate autoloading + $autoloader + ->expects($this->once()) + ->method('__invoke') + ->with($proxyClassName) + ->will( + $this->returnCallback( + function () use ($proxyClassName) { + eval( + 'class ' . $proxyClassName + . ' extends \\ProxyManagerTestAsset\\NullObjectMock {}' + ); + } + ) + ); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with('stdClass') + ->will($this->returnValue($proxyClassName)); + + $this + ->inflector + ->expects($this->once()) + ->method('getUserClassName') + ->with('stdClass') + ->will($this->returnValue('ProxyManagerTestAsset\\NullObjectMock')); + + $this->signatureChecker->expects($this->atLeastOnce())->method('checkSignature'); + $this->classSignatureGenerator->expects($this->once())->method('addSignature')->will($this->returnArgument(0)); + + $factory = new NullObjectFactory($this->config); + /* @var $proxy \ProxyManagerTestAsset\NullObjectMock */ + $proxy = $factory->createProxy($instance); + + $this->assertInstanceOf($proxyClassName, $proxy); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/BaseAdapterTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/BaseAdapterTest.php new file mode 100644 index 0000000..df70ce8 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/BaseAdapterTest.php @@ -0,0 +1,101 @@ + + * @license MIT + * + * @group Coverage + */ +class BaseAdapterTest extends PHPUnit_Framework_TestCase +{ + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\RemoteObject\Adapter\BaseAdapter::__construct + * @covers \ProxyManager\Factory\RemoteObject\Adapter\BaseAdapter::call + * @covers \ProxyManager\Factory\RemoteObject\Adapter\Soap::getServiceName + */ + public function testBaseAdapter() + { + $client = $this + ->getMockBuilder('Zend\Server\Client') + ->setMethods(array('call')) + ->getMock(); + + $adapter = $this->getMockForAbstractClass( + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\BaseAdapter', + array($client) + ); + + $client + ->expects($this->once()) + ->method('call') + ->with('foobarbaz', array('tab' => 'taz')) + ->will($this->returnValue('baz')); + + $adapter + ->expects($this->once()) + ->method('getServiceName') + ->with('foo', 'bar') + ->will($this->returnValue('foobarbaz')); + + $this->assertSame('baz', $adapter->call('foo', 'bar', array('tab' => 'taz'))); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\RemoteObject\Adapter\BaseAdapter::__construct + * @covers \ProxyManager\Factory\RemoteObject\Adapter\BaseAdapter::call + * @covers \ProxyManager\Factory\RemoteObject\Adapter\Soap::getServiceName + */ + public function testBaseAdapterWithServiceMap() + { + $client = $this + ->getMockBuilder('Zend\Server\Client') + ->setMethods(array('call')) + ->getMock(); + + $adapter = $this->getMockForAbstractClass( + 'ProxyManager\\Factory\\RemoteObject\\Adapter\\BaseAdapter', + array($client, array('foobarbaz' => 'mapped')) + ); + + $client + ->expects($this->once()) + ->method('call') + ->with('mapped', array('tab' => 'taz')) + ->will($this->returnValue('baz')); + + $adapter + ->expects($this->once()) + ->method('getServiceName') + ->with('foo', 'bar') + ->will($this->returnValue('foobarbaz')); + + $this->assertSame('baz', $adapter->call('foo', 'bar', array('tab' => 'taz'))); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/JsonRpcTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/JsonRpcTest.php new file mode 100644 index 0000000..66e80d7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/JsonRpcTest.php @@ -0,0 +1,57 @@ + + * @license MIT + * + * @group Coverage + */ +class JsonRpcTest extends PHPUnit_Framework_TestCase +{ + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\RemoteObject\Adapter\JsonRpc::__construct + * @covers \ProxyManager\Factory\RemoteObject\Adapter\JsonRpc::getServiceName + */ + public function testCanBuildAdapterWithJsonRpcClient() + { + $client = $this + ->getMockBuilder('Zend\Server\Client') + ->setMethods(array('call')) + ->getMock(); + + $adapter = new JsonRpc($client); + + $client + ->expects($this->once()) + ->method('call') + ->with('foo.bar', array('tab' => 'taz')) + ->will($this->returnValue('baz')); + + $this->assertSame('baz', $adapter->call('foo', 'bar', array('tab' => 'taz'))); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/SoapTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/SoapTest.php new file mode 100644 index 0000000..fe93e00 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/SoapTest.php @@ -0,0 +1,57 @@ + + * @license MIT + * + * @group Coverage + */ +class SoapTest extends PHPUnit_Framework_TestCase +{ + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\RemoteObject\Adapter\Soap::__construct + * @covers \ProxyManager\Factory\RemoteObject\Adapter\Soap::getServiceName + */ + public function testCanBuildAdapterWithSoapRpcClient() + { + $client = $this + ->getMockBuilder('Zend\Server\Client') + ->setMethods(array('call')) + ->getMock(); + + $adapter = new Soap($client); + + $client + ->expects($this->once()) + ->method('call') + ->with('bar', array('tab' => 'taz')) + ->will($this->returnValue('baz')); + + $this->assertSame('baz', $adapter->call('foo', 'bar', array('tab' => 'taz'))); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/XmlRpcTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/XmlRpcTest.php new file mode 100644 index 0000000..d795e40 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObject/Adapter/XmlRpcTest.php @@ -0,0 +1,57 @@ + + * @license MIT + * + * @group Coverage + */ +class XmlRpcTest extends PHPUnit_Framework_TestCase +{ + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\RemoteObject\Adapter\XmlRpc::__construct + * @covers \ProxyManager\Factory\RemoteObject\Adapter\XmlRpc::getServiceName + */ + public function testCanBuildAdapterWithXmlRpcClient() + { + $client = $this + ->getMockBuilder('Zend\Server\Client') + ->setMethods(array('call')) + ->getMock(); + + $adapter = new XmlRpc($client); + + $client + ->expects($this->once()) + ->method('call') + ->with('foo.bar', array('tab' => 'taz')) + ->will($this->returnValue('baz')); + + $this->assertSame('baz', $adapter->call('foo', 'bar', array('tab' => 'taz'))); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObjectFactoryTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObjectFactoryTest.php new file mode 100644 index 0000000..a7385e0 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Factory/RemoteObjectFactoryTest.php @@ -0,0 +1,178 @@ + + * @license MIT + * + * @group Coverage + */ +class RemoteObjectFactoryTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $inflector; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $signatureChecker; + + /** + * @var \ProxyManager\Signature\ClassSignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $classSignatureGenerator; + + /** + * @var \ProxyManager\Configuration|\PHPUnit_Framework_MockObject_MockObject + */ + protected $config; + + /** + * {@inheritDoc} + */ + public function setUp() + { + $this->config = $this->getMock('ProxyManager\\Configuration'); + $this->inflector = $this->getMock('ProxyManager\\Inflector\\ClassNameInflectorInterface'); + $this->signatureChecker = $this->getMock('ProxyManager\\Signature\\SignatureCheckerInterface'); + $this->classSignatureGenerator = $this->getMock('ProxyManager\\Signature\\ClassSignatureGeneratorInterface'); + + $this + ->config + ->expects($this->any()) + ->method('getClassNameInflector') + ->will($this->returnValue($this->inflector)); + + $this + ->config + ->expects($this->any()) + ->method('getSignatureChecker') + ->will($this->returnValue($this->signatureChecker)); + + $this + ->config + ->expects($this->any()) + ->method('getClassSignatureGenerator') + ->will($this->returnValue($this->classSignatureGenerator)); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\RemoteObjectFactory::__construct + * @covers \ProxyManager\Factory\RemoteObjectFactory::createProxy + * @covers \ProxyManager\Factory\RemoteObjectFactory::getGenerator + */ + public function testWillSkipAutoGeneration() + { + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with('ProxyManagerTestAsset\\BaseInterface') + ->will($this->returnValue('StdClass')); + + $adapter = $this->getMock('ProxyManager\Factory\RemoteObject\AdapterInterface'); + $factory = new RemoteObjectFactory($adapter, $this->config); + /* @var $proxy \stdClass */ + $proxy = $factory->createProxy('ProxyManagerTestAsset\\BaseInterface', $adapter); + + $this->assertInstanceOf('stdClass', $proxy); + } + + /** + * {@inheritDoc} + * + * @covers \ProxyManager\Factory\RemoteObjectFactory::__construct + * @covers \ProxyManager\Factory\RemoteObjectFactory::createProxy + * @covers \ProxyManager\Factory\RemoteObjectFactory::getGenerator + * + * NOTE: serious mocking going on in here (a class is generated on-the-fly) - careful + */ + public function testWillTryAutoGeneration() + { + $proxyClassName = UniqueIdentifierGenerator::getIdentifier('bar'); + $generator = $this->getMock('ProxyManager\GeneratorStrategy\\GeneratorStrategyInterface'); + $autoloader = $this->getMock('ProxyManager\\Autoloader\\AutoloaderInterface'); + + $this->config->expects($this->any())->method('getGeneratorStrategy')->will($this->returnValue($generator)); + $this->config->expects($this->any())->method('getProxyAutoloader')->will($this->returnValue($autoloader)); + + $generator + ->expects($this->once()) + ->method('generate') + ->with( + $this->callback( + function (ClassGenerator $targetClass) use ($proxyClassName) { + return $targetClass->getName() === $proxyClassName; + } + ) + ); + + // simulate autoloading + $autoloader + ->expects($this->once()) + ->method('__invoke') + ->with($proxyClassName) + ->will( + $this->returnCallback( + function () use ($proxyClassName) { + eval( + 'class ' . $proxyClassName + . ' extends stdClass {}' + ); + } + ) + ); + + $this + ->inflector + ->expects($this->once()) + ->method('getProxyClassName') + ->with('ProxyManagerTestAsset\\BaseInterface') + ->will($this->returnValue($proxyClassName)); + + $this + ->inflector + ->expects($this->once()) + ->method('getUserClassName') + ->with('ProxyManagerTestAsset\\BaseInterface') + ->will($this->returnValue('stdClass')); + + $this->signatureChecker->expects($this->atLeastOnce())->method('checkSignature'); + $this->classSignatureGenerator->expects($this->once())->method('addSignature')->will($this->returnArgument(0)); + + $adapter = $this->getMock('ProxyManager\Factory\RemoteObject\AdapterInterface'); + $factory = new RemoteObjectFactory($adapter, $this->config); + /* @var $proxy \stdClass */ + $proxy = $factory->createProxy('ProxyManagerTestAsset\\BaseInterface', $adapter); + + $this->assertInstanceOf($proxyClassName, $proxy); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/FileLocator/FileLocatorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/FileLocator/FileLocatorTest.php new file mode 100644 index 0000000..92991a0 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/FileLocator/FileLocatorTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class FileLocatorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\FileLocator\FileLocator::__construct + * @covers \ProxyManager\FileLocator\FileLocator::getProxyFileName + */ + public function testGetProxyFileName() + { + $locator = new FileLocator(__DIR__); + + $this->assertSame(__DIR__ . DIRECTORY_SEPARATOR . 'FooBarBaz.php', $locator->getProxyFileName('Foo\\Bar\\Baz')); + $this->assertSame(__DIR__ . DIRECTORY_SEPARATOR . 'Foo_Bar_Baz.php', $locator->getProxyFileName('Foo_Bar_Baz')); + } + + /** + * @covers \ProxyManager\FileLocator\FileLocator::__construct + */ + public function testRejectsNonExistingDirectory() + { + $this->setExpectedException('ProxyManager\\Exception\\InvalidProxyDirectoryException'); + new FileLocator(__DIR__ . '/non-existing'); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/AccessInterceptorScopeLocalizerFunctionalTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/AccessInterceptorScopeLocalizerFunctionalTest.php new file mode 100644 index 0000000..2e8984d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/AccessInterceptorScopeLocalizerFunctionalTest.php @@ -0,0 +1,388 @@ + + * @license MIT + * + * @group Functional + * @coversNothing + */ +class AccessInterceptorScopeLocalizerFunctionalTest extends PHPUnit_Framework_TestCase +{ + /** + * {@inheritDoc} + */ + public static function setUpBeforeClass() + { + if (! method_exists('Closure', 'bind')) { + throw new PHPUnit_Framework_SkippedTestError( + 'PHP 5.3 doesn\'t support scope localization of private properties' + ); + } + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCalls($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface */ + $proxy = new $proxyName($instance); + + $this->assertProxySynchronized($instance, $proxy); + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + + $listener = $this->getMock('stdClass', array('__invoke')); + $listener + ->expects($this->once()) + ->method('__invoke') + ->with($proxy, $proxy, $method, $params, false); + + $proxy->setMethodPrefixInterceptor( + $method, + function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) { + $listener->__invoke($proxy, $instance, $method, $params, $returnEarly); + } + ); + + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + + $random = uniqid(); + + $proxy->setMethodPrefixInterceptor( + $method, + function ($proxy, $instance, $method, $params, & $returnEarly) use ($random) { + $returnEarly = true; + + return $random; + } + ); + + $this->assertSame($random, call_user_func_array(array($proxy, $method), $params)); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsWithSuffixListener($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface */ + $proxy = new $proxyName($instance); + $listener = $this->getMock('stdClass', array('__invoke')); + $listener + ->expects($this->once()) + ->method('__invoke') + ->with($proxy, $proxy, $method, $params, $expectedValue, false); + + $proxy->setMethodSuffixInterceptor( + $method, + function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($listener) { + $listener->__invoke($proxy, $instance, $method, $params, $returnValue, $returnEarly); + } + ); + + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + + $random = uniqid(); + + $proxy->setMethodSuffixInterceptor( + $method, + function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($random) { + $returnEarly = true; + + return $random; + } + ); + + $this->assertSame($random, call_user_func_array(array($proxy, $method), $params)); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterUnSerialization($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface */ + $proxy = unserialize(serialize(new $proxyName($instance))); + + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterCloning($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface */ + $proxy = new $proxyName($instance); + $cloned = clone $proxy; + + $this->assertProxySynchronized($instance, $proxy); + $this->assertSame($expectedValue, call_user_func_array(array($cloned, $method), $params)); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyReadAccess($instance, $proxy, $publicProperty, $propertyValue) + { + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface */ + $this->assertSame($propertyValue, $proxy->$publicProperty); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyWriteAccess($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface */ + $newValue = uniqid(); + $proxy->$publicProperty = $newValue; + + $this->assertSame($newValue, $proxy->$publicProperty); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyExistence($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface */ + $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty)); + $this->assertProxySynchronized($instance, $proxy); + + $instance->$publicProperty = null; + $this->assertFalse(isset($proxy->$publicProperty)); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyUnset($instance, $proxy, $publicProperty) + { + $this->markTestSkipped('It is currently not possible to synchronize properties un-setting'); + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface */ + unset($proxy->$publicProperty); + + $this->assertFalse(isset($instance->$publicProperty)); + $this->assertFalse(isset($proxy->$publicProperty)); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * Verifies that accessing a public property containing an array behaves like in a normal context + */ + public function testCanWriteToArrayKeysInPublicProperty() + { + $instance = new ClassWithPublicArrayProperty(); + $className = get_class($instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicArrayProperty */ + $proxy = new $proxyName($instance); + + $proxy->arrayProperty['foo'] = 'bar'; + + $this->assertSame('bar', $proxy->arrayProperty['foo']); + + $proxy->arrayProperty = array('tab' => 'taz'); + + $this->assertSame(array('tab' => 'taz'), $proxy->arrayProperty); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * Verifies that public properties retrieved via `__get` don't get modified in the object state + */ + public function testWillNotModifyRetrievedPublicProperties() + { + $instance = new ClassWithPublicProperties(); + $className = get_class($instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName($instance); + $variable = $proxy->property0; + + $this->assertSame('property0', $variable); + + $variable = 'foo'; + + $this->assertSame('property0', $proxy->property0); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * Verifies that public properties references retrieved via `__get` modify in the object state + */ + public function testWillModifyByRefRetrievedPublicProperties() + { + $instance = new ClassWithPublicProperties(); + $className = get_class($instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName($instance); + $variable = & $proxy->property0; + + $this->assertSame('property0', $variable); + + $variable = 'foo'; + + $this->assertSame('foo', $proxy->property0); + $this->assertProxySynchronized($instance, $proxy); + } + + /** + * Generates a proxy for the given class name, and retrieves its class name + * + * @param string $parentClassName + * + * @return string + * + * @throws UnsupportedProxiedClassException + */ + private function generateProxy($parentClassName) + { + $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); + $generator = new AccessInterceptorScopeLocalizerGenerator(); + $generatedClass = new ClassGenerator($generatedClassName); + $strategy = new EvaluatingGeneratorStrategy(); + + $generator->generate(new ReflectionClass($parentClassName), $generatedClass); + $strategy->generate($generatedClass); + + return $generatedClassName; + } + + /** + * Generates a list of object | invoked method | parameters | expected result + * + * @return array + */ + public function getProxyMethods() + { + $selfHintParam = new ClassWithSelfHint(); + + $data = array( + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicMethod', + array(), + 'publicMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicTypeHintedMethod', + array('param' => new \stdClass()), + 'publicTypeHintedMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicByReferenceMethod', + array(), + 'publicByReferenceMethodDefault' + ), + ); + + if (PHP_VERSION_ID >= 50401) { + // PHP < 5.4.1 misbehaves, throwing strict standards, see https://bugs.php.net/bug.php?id=60573 + $data[] = array( + 'ProxyManagerTestAsset\\ClassWithSelfHint', + new ClassWithSelfHint(), + 'selfHintMethod', + array('parameter' => $selfHintParam), + $selfHintParam + ); + } + + return $data; + } + + /** + * Generates proxies and instances with a public property to feed to the property accessor methods + * + * @return array + */ + public function getPropertyAccessProxies() + { + $instance1 = new BaseClass(); + $proxyName1 = $this->generateProxy(get_class($instance1)); + + return array( + array( + $instance1, + new $proxyName1($instance1), + 'publicProperty', + 'publicPropertyDefault', + ), + ); + } + + /** + * @param object $instance + * @param AccessInterceptorInterface $proxy + */ + private function assertProxySynchronized($instance, AccessInterceptorInterface $proxy) + { + $reflectionClass = new ReflectionClass($instance); + + foreach ($reflectionClass->getProperties() as $property) { + $property->setAccessible(true); + + $this->assertSame( + $property->getValue($instance), + $property->getValue($proxy), + 'Property "' . $property->getName() . '" is synchronized between instance and proxy' + ); + } + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/AccessInterceptorValueHolderFunctionalTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/AccessInterceptorValueHolderFunctionalTest.php new file mode 100644 index 0000000..7740e2e --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/AccessInterceptorValueHolderFunctionalTest.php @@ -0,0 +1,360 @@ + + * @license MIT + * + * @group Functional + * @coversNothing + */ +class AccessInterceptorValueHolderFunctionalTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getProxyMethods + */ + public function testMethodCalls($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $proxy = new $proxyName($instance); + + $this->assertSame($instance, $proxy->getWrappedValueHolderValue()); + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + + $listener = $this->getMock('stdClass', array('__invoke')); + $listener + ->expects($this->once()) + ->method('__invoke') + ->with($proxy, $instance, $method, $params, false); + + $proxy->setMethodPrefixInterceptor( + $method, + function ($proxy, $instance, $method, $params, & $returnEarly) use ($listener) { + $listener->__invoke($proxy, $instance, $method, $params, $returnEarly); + } + ); + + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + + $random = uniqid(); + + $proxy->setMethodPrefixInterceptor( + $method, + function ($proxy, $instance, $method, $params, & $returnEarly) use ($random) { + $returnEarly = true; + + return $random; + } + ); + + $this->assertSame($random, call_user_func_array(array($proxy, $method), $params)); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsWithSuffixListener($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $proxy = new $proxyName($instance); + $listener = $this->getMock('stdClass', array('__invoke')); + $listener + ->expects($this->once()) + ->method('__invoke') + ->with($proxy, $instance, $method, $params, $expectedValue, false); + + $proxy->setMethodSuffixInterceptor( + $method, + function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($listener) { + $listener->__invoke($proxy, $instance, $method, $params, $returnValue, $returnEarly); + } + ); + + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + + $random = uniqid(); + + $proxy->setMethodSuffixInterceptor( + $method, + function ($proxy, $instance, $method, $params, $returnValue, & $returnEarly) use ($random) { + $returnEarly = true; + + return $random; + } + ); + + $this->assertSame($random, call_user_func_array(array($proxy, $method), $params)); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterUnSerialization($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $proxy = unserialize(serialize(new $proxyName($instance))); + + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + $this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterCloning($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $proxy = new $proxyName($instance); + $cloned = clone $proxy; + + $this->assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue()); + $this->assertSame($expectedValue, call_user_func_array(array($cloned, $method), $params)); + $this->assertEquals($instance, $cloned->getWrappedValueHolderValue()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyReadAccess($instance, $proxy, $publicProperty, $propertyValue) + { + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $this->assertSame($propertyValue, $proxy->$publicProperty); + $this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyWriteAccess($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $newValue = uniqid(); + $proxy->$publicProperty = $newValue; + + $this->assertSame($newValue, $proxy->$publicProperty); + $this->assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyExistence($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty)); + $this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); + + $proxy->getWrappedValueHolderValue()->$publicProperty = null; + $this->assertFalse(isset($proxy->$publicProperty)); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyUnset($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\AccessInterceptorInterface|\ProxyManager\Proxy\ValueHolderInterface */ + $instance = $proxy->getWrappedValueHolderValue() ? $proxy->getWrappedValueHolderValue() : $instance; + unset($proxy->$publicProperty); + + $this->assertFalse(isset($instance->$publicProperty)); + $this->assertFalse(isset($proxy->$publicProperty)); + } + + /** + * Verifies that accessing a public property containing an array behaves like in a normal context + */ + public function testCanWriteToArrayKeysInPublicProperty() + { + $instance = new ClassWithPublicArrayProperty(); + $className = get_class($instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicArrayProperty */ + $proxy = new $proxyName($instance); + + $proxy->arrayProperty['foo'] = 'bar'; + + $this->assertSame('bar', $proxy->arrayProperty['foo']); + + $proxy->arrayProperty = array('tab' => 'taz'); + + $this->assertSame(array('tab' => 'taz'), $proxy->arrayProperty); + } + + /** + * Verifies that public properties retrieved via `__get` don't get modified in the object state + */ + public function testWillNotModifyRetrievedPublicProperties() + { + $instance = new ClassWithPublicProperties(); + $className = get_class($instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName($instance); + $variable = $proxy->property0; + + $this->assertSame('property0', $variable); + + $variable = 'foo'; + + $this->assertSame('property0', $proxy->property0); + } + + /** + * Verifies that public properties references retrieved via `__get` modify in the object state + */ + public function testWillModifyByRefRetrievedPublicProperties() + { + $instance = new ClassWithPublicProperties(); + $className = get_class($instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName($instance); + $variable = & $proxy->property0; + + $this->assertSame('property0', $variable); + + $variable = 'foo'; + + $this->assertSame('foo', $proxy->property0); + } + + /** + * Generates a proxy for the given class name, and retrieves its class name + * + * @param string $parentClassName + * + * @return string + */ + private function generateProxy($parentClassName) + { + $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); + $generator = new AccessInterceptorValueHolderGenerator(); + $generatedClass = new ClassGenerator($generatedClassName); + $strategy = new EvaluatingGeneratorStrategy(); + + $generator->generate(new ReflectionClass($parentClassName), $generatedClass); + $strategy->generate($generatedClass); + + return $generatedClassName; + } + + /** + * Generates a list of object | invoked method | parameters | expected result + * + * @return array + */ + public function getProxyMethods() + { + $selfHintParam = new ClassWithSelfHint(); + + $data = array( + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicMethod', + array(), + 'publicMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicTypeHintedMethod', + array('param' => new \stdClass()), + 'publicTypeHintedMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicByReferenceMethod', + array(), + 'publicByReferenceMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseInterface', + new BaseClass(), + 'publicMethod', + array(), + 'publicMethodDefault' + ), + ); + + if (PHP_VERSION_ID >= 50401) { + // PHP < 5.4.1 misbehaves, throwing strict standards, see https://bugs.php.net/bug.php?id=60573 + $data[] = array( + 'ProxyManagerTestAsset\\ClassWithSelfHint', + new ClassWithSelfHint(), + 'selfHintMethod', + array('parameter' => $selfHintParam), + $selfHintParam + ); + } + + return $data; + } + + /** + * Generates proxies and instances with a public property to feed to the property accessor methods + * + * @return array + */ + public function getPropertyAccessProxies() + { + $instance1 = new BaseClass(); + $proxyName1 = $this->generateProxy(get_class($instance1)); + $instance2 = new BaseClass(); + $proxyName2 = $this->generateProxy(get_class($instance2)); + + return array( + array( + $instance1, + new $proxyName1($instance1), + 'publicProperty', + 'publicPropertyDefault', + ), + array( + $instance2, + unserialize(serialize(new $proxyName2($instance2))), + 'publicProperty', + 'publicPropertyDefault', + ), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/BaseLazyLoadingPerformanceTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/BaseLazyLoadingPerformanceTest.php new file mode 100644 index 0000000..d00fa41 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/BaseLazyLoadingPerformanceTest.php @@ -0,0 +1,196 @@ + + * @license MIT + * + * @group Performance + * @coversNothing + */ +abstract class BaseLazyLoadingPerformanceTest extends BasePerformanceTest +{ + /** + * @param string $className + * @param object[] $instances + * @param \ProxyManager\Proxy\LazyLoadingInterface[] $proxies + * @param string $methodName + * @param array $parameters + */ + protected function profileMethodAccess($className, array $instances, array $proxies, $methodName, array $parameters) + { + $iterations = count($instances); + + $this->startCapturing(); + + foreach ($instances as $instance) { + call_user_func_array(array($instance, $methodName), $parameters); + } + + $baseProfile = $this->endCapturing( + $iterations . ' calls to ' . $className . '#' . $methodName . ': %fms / %fKb' + ); + $this->startCapturing(); + + foreach ($proxies as $proxy) { + call_user_func_array(array($proxy, $methodName), $parameters); + } + + $proxyProfile = $this->endCapturing( + $iterations . ' calls to proxied ' . $className . '#' . $methodName . ': %fms / %fKb' + ); + $this->compareProfile($baseProfile, $proxyProfile); + } + + /** + * @param string $className + * @param object[] $instances + * @param \ProxyManager\Proxy\LazyLoadingInterface[] $proxies + * @param string $property + */ + protected function profilePropertyWrites($className, array $instances, array $proxies, $property) + { + $iterations = count($instances); + + $this->startCapturing(); + + foreach ($instances as $instance) { + $instance->$property = 'foo'; + } + + $baseProfile = $this->endCapturing( + $iterations . ' writes of ' . $className . '::' . $property . ': %fms / %fKb' + ); + $this->startCapturing(); + + foreach ($proxies as $proxy) { + $proxy->$property = 'foo'; + } + + $proxyProfile = $this->endCapturing( + $iterations . ' writes of proxied ' . $className . '::' . $property . ': %fms / %fKb' + ); + $this->compareProfile($baseProfile, $proxyProfile); + } + + /** + * @param string $className + * @param object[] $instances + * @param \ProxyManager\Proxy\LazyLoadingInterface[] $proxies + * @param string $property + */ + protected function profilePropertyReads($className, array $instances, array $proxies, $property) + { + $iterations = count($instances); + + $this->startCapturing(); + + foreach ($instances as $instance) { + $instance->$property; + } + + $baseProfile = $this->endCapturing( + $iterations . ' reads of ' . $className . '::' . $property . ': %fms / %fKb' + ); + $this->startCapturing(); + + foreach ($proxies as $proxy) { + $proxy->$property; + } + + $proxyProfile = $this->endCapturing( + $iterations . ' reads of proxied ' . $className . '::' . $property . ': %fms / %fKb' + ); + $this->compareProfile($baseProfile, $proxyProfile); + } + + /** + * @param string $className + * @param object[] $instances + * @param \ProxyManager\Proxy\LazyLoadingInterface[] $proxies + * @param string $property + */ + protected function profilePropertyIsset($className, array $instances, array $proxies, $property) + { + $iterations = count($instances); + + $this->startCapturing(); + + foreach ($instances as $instance) { + isset($instance->$property); + } + + $baseProfile = $this->endCapturing( + $iterations . ' isset of ' . $className . '::' . $property . ': %fms / %fKb' + ); + $this->startCapturing(); + + foreach ($proxies as $proxy) { + isset($proxy->$property); + } + + $proxyProfile = $this->endCapturing( + $iterations . ' isset of proxied ' . $className . '::' . $property . ': %fms / %fKb' + ); + $this->compareProfile($baseProfile, $proxyProfile); + } + + /** + * @param string $className + * @param object[] $instances + * @param \ProxyManager\Proxy\LazyLoadingInterface[] $proxies + * @param string $property + */ + protected function profilePropertyUnset($className, array $instances, array $proxies, $property) + { + $iterations = count($instances); + + $this->startCapturing(); + + foreach ($instances as $instance) { + unset($instance->$property); + } + + $baseProfile = $this->endCapturing( + $iterations . ' unset of ' . $className . '::' . $property . ': %fms / %fKb' + ); + $this->startCapturing(); + + foreach ($proxies as $proxy) { + unset($proxy->$property); + } + + $proxyProfile = $this->endCapturing( + $iterations . ' unset of proxied ' . $className . '::' . $property . ': %fms / %fKb' + ); + $this->compareProfile($baseProfile, $proxyProfile); + } + + /** + * Generates a proxy for the given class name, and retrieves its class name + * + * @param string $parentClassName + * + * @return string + */ + abstract protected function generateProxy($parentClassName); +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/BasePerformanceTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/BasePerformanceTest.php new file mode 100644 index 0000000..b9dc803 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/BasePerformanceTest.php @@ -0,0 +1,101 @@ + + * @license MIT + * + * @group Performance + * @coversNothing + */ +abstract class BasePerformanceTest extends PHPUnit_Framework_TestCase +{ + /** + * @var float time when last capture was started + */ + private $startTime = 0; + + /** + * @var int bytes when last capture was started + */ + private $startMemory = 0; + + /** + * {@inheritDoc} + */ + public static function setUpBeforeClass() + { + $header = "Performance test - " . get_called_class() . ":"; + + echo "\n\n" . str_repeat('=', strlen($header)) . "\n" . $header . "\n\n"; + } + + /** + * Start profiler snapshot + */ + protected function startCapturing() + { + $this->startMemory = memory_get_usage(); + $this->startTime = microtime(true); + } + + /** + * Echo current profiler output + * + * @param string $messageTemplate + * + * @return array + */ + protected function endCapturing($messageTemplate) + { + $time = microtime(true) - $this->startTime; + $memory = memory_get_usage() - $this->startMemory; + + if (gc_enable()) { + gc_collect_cycles(); + } + + echo sprintf($messageTemplate, $time, $memory / 1024) . "\n"; + + return array( + 'time' => $time, + 'memory' => $memory + ); + } + + /** + * Display comparison between two profiles + * + * @param array $baseProfile + * @param array $proxyProfile + */ + protected function compareProfile(array $baseProfile, array $proxyProfile) + { + $baseMemory = max(1, $baseProfile['memory']); + $timeOverhead = (($proxyProfile['time'] / $baseProfile['time']) - 1) * 100; + $memoryOverhead = (($proxyProfile['memory'] / $baseMemory) - 1) * 100; + + echo sprintf('Comparison time / memory: %.2f%% / %.2f%%', $timeOverhead, $memoryOverhead) . "\n\n"; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/FatalPreventionFunctionalTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/FatalPreventionFunctionalTest.php new file mode 100644 index 0000000..290d52b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/FatalPreventionFunctionalTest.php @@ -0,0 +1,171 @@ + + * @license MIT + * + * @group Functional + * @coversNothing + */ +class FatalPreventionFunctionalTest extends PHPUnit_Framework_TestCase +{ + private $template = <<<'PHP' +generate(new ReflectionClass($className), $generatedClass); + $classSignatureGenerator->addSignature($generatedClass, array('eval tests')); + $generatorStrategy->generate($generatedClass); +} catch (ProxyManager\Exception\ExceptionInterface $e) { +} catch (ReflectionException $e) { +} + +echo 'SUCCESS: ' . %s; +PHP; + + /** + * Verifies that code generation and evaluation will not cause fatals with any given class + * + * @param string $generatorClass an instantiable class (no arguments) implementing + * the {@see \ProxyManager\ProxyGenerator\ProxyGeneratorInterface} + * @param string $className a valid (existing/autoloadable) class name + * + * @dataProvider getTestedClasses + */ + public function testCodeGeneration($generatorClass, $className) + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM is just too slow for this kind of test right now.'); + } + + if (PHP_VERSION_ID < 50401) { + $this->markTestSkipped('Can\'t run this test suite on php < 5.4.1'); + } + + $runner = PHPUnit_Util_PHP::factory(); + + $code = sprintf( + $this->template, + var_export(realpath(__DIR__ . '/../../../vendor/autoload.php'), true), + var_export($className, true), + $generatorClass, + var_export($className, true) + ); + + $result = $runner->runJob($code, array('-n')); + + if (('SUCCESS: ' . $className) !== $result['stdout']) { + $this->fail(sprintf( + "Crashed with class '%s' and generator '%s'.\n\nStdout:\n%s\nStderr:\n%s\nGenerated code:\n%s'", + $generatorClass, + $className, + $result['stdout'], + $result['stderr'], + $code + )); + } + + $this->assertSame('SUCCESS: ' . $className, $result['stdout']); + } + + /** + * @return string[][] + */ + public function getTestedClasses() + { + $that = $this; + + return call_user_func_array( + 'array_merge', + array_map( + function ($generator) use ($that) { + return array_map( + function ($class) use ($generator) { + return array($generator, $class); + }, + $that->getProxyTestedClasses() + ); + }, + array( + 'ProxyManager\\ProxyGenerator\\AccessInterceptorScopeLocalizerGenerator', + 'ProxyManager\\ProxyGenerator\\AccessInterceptorValueHolderGenerator', + 'ProxyManager\\ProxyGenerator\\LazyLoadingGhostGenerator', + 'ProxyManager\\ProxyGenerator\\LazyLoadingValueHolderGenerator', + 'ProxyManager\\ProxyGenerator\\NullObjectGenerator', + 'ProxyManager\\ProxyGenerator\\RemoteObjectGenerator', + ) + ) + ); + } + + /** + * @private (public only for PHP 5.3 compatibility) + * + * @return string[] + */ + public function getProxyTestedClasses() + { + $skippedPaths = array( + realpath(__DIR__ . '/../../src'), + realpath(__DIR__ . '/../../vendor'), + realpath(__DIR__ . '/../../tests/ProxyManagerTest'), + ); + + return array_filter( + get_declared_classes(), + function ($className) use ($skippedPaths) { + $reflectionClass = new ReflectionClass($className); + $fileName = $reflectionClass->getFileName(); + + if (! $fileName) { + return false; + } + + $realPath = realpath($fileName); + + foreach ($skippedPaths as $skippedPath) { + if (0 === strpos($realPath, $skippedPath)) { + // skip classes defined within ProxyManager, vendor or the test suite + return false; + } + } + + return true; + } + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingGhostFunctionalTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingGhostFunctionalTest.php new file mode 100644 index 0000000..a6690f1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingGhostFunctionalTest.php @@ -0,0 +1,441 @@ + + * @license MIT + * + * @group Functional + * @coversNothing + */ +class LazyLoadingGhostFunctionalTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getProxyMethods + */ + public function testMethodCalls($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface|BaseClass */ + $proxy = new $proxyName($this->createInitializer($className, $instance)); + + $this->assertFalse($proxy->isProxyInitialized()); + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + $this->assertTrue($proxy->isProxyInitialized()); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterUnSerialization($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface|BaseClass */ + $proxy = unserialize(serialize(new $proxyName($this->createInitializer($className, $instance)))); + + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterCloning($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface|BaseClass */ + $proxy = new $proxyName($this->createInitializer($className, $instance)); + $cloned = clone $proxy; + + $this->assertTrue($cloned->isProxyInitialized()); + $this->assertSame($expectedValue, call_user_func_array(array($cloned, $method), $params)); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyReadAccess($instance, $proxy, $publicProperty, $propertyValue) + { + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface */ + $this->assertSame($propertyValue, $proxy->$publicProperty); + $this->assertTrue($proxy->isProxyInitialized()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyWriteAccess($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface */ + $newValue = uniqid(); + $proxy->$publicProperty = $newValue; + + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertSame($newValue, $proxy->$publicProperty); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyExistence($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface */ + $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty)); + $this->assertTrue($proxy->isProxyInitialized()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyAbsence($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface */ + $proxy->$publicProperty = null; + $this->assertFalse(isset($proxy->$publicProperty)); + $this->assertTrue($proxy->isProxyInitialized()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyUnset($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface */ + + unset($proxy->$publicProperty); + + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertTrue(isset($instance->$publicProperty)); + $this->assertFalse(isset($proxy->$publicProperty)); + } + + /** + * Verifies that accessing a public property containing an array behaves like in a normal context + */ + public function testCanWriteToArrayKeysInPublicProperty() + { + $instance = new ClassWithPublicArrayProperty(); + $className = get_class($instance); + $initializer = $this->createInitializer($className, $instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicArrayProperty */ + $proxy = new $proxyName($initializer); + + $proxy->arrayProperty['foo'] = 'bar'; + + $this->assertSame('bar', $proxy->arrayProperty['foo']); + + $proxy->arrayProperty = array('tab' => 'taz'); + + $this->assertSame(array('tab' => 'taz'), $proxy->arrayProperty); + } + + /** + * Verifies that public properties retrieved via `__get` don't get modified in the object itself + */ + public function testWillNotModifyRetrievedPublicProperties() + { + $instance = new ClassWithPublicProperties(); + $className = get_class($instance); + $initializer = $this->createInitializer($className, $instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName($initializer); + $variable = $proxy->property0; + + $this->assertSame('property0', $variable); + + $variable = 'foo'; + + $this->assertSame('property0', $proxy->property0); + } + + /** + * Verifies that public properties references retrieved via `__get` modify in the object state + */ + public function testWillModifyByRefRetrievedPublicProperties() + { + $instance = new ClassWithPublicProperties(); + $className = get_class($instance); + $initializer = $this->createInitializer($className, $instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName($initializer); + $variable = & $proxy->property0; + + $this->assertSame('property0', $variable); + + $variable = 'foo'; + + $this->assertSame('foo', $proxy->property0); + } + + public function testKeepsInitializerWhenNotOverwitten() + { + $instance = new BaseClass(); + $proxyName = $this->generateProxy(get_class($instance)); + $initializer = function () { + }; + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface */ + $proxy = new $proxyName($initializer); + + $proxy->initializeProxy(); + + $this->assertSame($initializer, $proxy->getProxyInitializer()); + } + + /** + * Verifies that public properties are not being initialized multiple times + */ + public function testKeepsInitializedPublicProperties() + { + $instance = new BaseClass(); + $proxyName = $this->generateProxy(get_class($instance)); + $initializer = function (BaseClass $proxy, $method, $parameters, & $initializer) { + $initializer = null; + $proxy->publicProperty = 'newValue'; + }; + /* @var $proxy \ProxyManager\Proxy\GhostObjectInterface|BaseClass */ + $proxy = new $proxyName($initializer); + + $proxy->initializeProxy(); + $this->assertSame('newValue', $proxy->publicProperty); + + $proxy->publicProperty = 'otherValue'; + + $proxy->initializeProxy(); + + $this->assertSame('otherValue', $proxy->publicProperty); + } + + /** + * Verifies that properties' default values are preserved + */ + public function testPublicPropertyDefaultWillBePreserved() + { + $instance = new ClassWithPublicProperties(); + $proxyName = $this->generateProxy(get_class($instance)); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName(function () { + }); + + $this->assertSame('property0', $proxy->property0); + } + + /** + * Verifies that protected properties' default values are preserved + */ + public function testProtectedPropertyDefaultWillBePreserved() + { + $instance = new ClassWithProtectedProperties(); + $proxyName = $this->generateProxy(get_class($instance)); + /* @var $proxy ClassWithProtectedProperties */ + $proxy = new $proxyName(function () { + }); + + // Check protected property via reflection + $reflectionProperty = new ReflectionProperty($instance, 'property0'); + $reflectionProperty->setAccessible(true); + + $this->assertSame('property0', $reflectionProperty->getValue($proxy)); + } + + /** + * Verifies that private properties' default values are preserved + */ + public function testPrivatePropertyDefaultWillBePreserved() + { + $instance = new ClassWithPrivateProperties(); + $proxyName = $this->generateProxy(get_class($instance)); + /* @var $proxy ClassWithPrivateProperties */ + $proxy = new $proxyName(function () { + }); + + // Check protected property via reflection + $reflectionProperty = new ReflectionProperty($instance, 'property0'); + $reflectionProperty->setAccessible(true); + + $this->assertSame('property0', $reflectionProperty->getValue($proxy)); + } + + /** + * Generates a proxy for the given class name, and retrieves its class name + * + * @param string $parentClassName + * + * @return string + */ + private function generateProxy($parentClassName) + { + $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); + $generator = new LazyLoadingGhostGenerator(); + $generatedClass = new ClassGenerator($generatedClassName); + $strategy = new EvaluatingGeneratorStrategy(); + + $generator->generate(new ReflectionClass($parentClassName), $generatedClass); + $strategy->generate($generatedClass); + + return $generatedClassName; + } + + /** + * @param string $className + * @param object $realInstance + * @param Mock $initializerMatcher + * + * @return \Closure + */ + private function createInitializer($className, $realInstance, Mock $initializerMatcher = null) + { + if (null === $initializerMatcher) { + $initializerMatcher = $this->getMock('stdClass', array('__invoke')); + + $initializerMatcher + ->expects($this->once()) + ->method('__invoke') + ->with( + $this->logicalAnd( + $this->isInstanceOf('ProxyManager\\Proxy\\GhostObjectInterface'), + $this->isInstanceOf($className) + ) + ); + } + + $initializerMatcher = $initializerMatcher ?: $this->getMock('stdClass', array('__invoke')); + + return function ( + GhostObjectInterface $proxy, + $method, + $params, + & $initializer + ) use ( + $initializerMatcher, + $realInstance + ) { + $initializer = null; + $reflectionClass = new ReflectionClass($realInstance); + + foreach ($reflectionClass->getProperties() as $property) { + $property->setAccessible(true); + $property->setValue($proxy, $property->getValue($realInstance)); + } + + $initializerMatcher->__invoke($proxy, $method, $params); + }; + } + + /** + * Generates a list of object | invoked method | parameters | expected result + * + * @return array + */ + public function getProxyMethods() + { + $selfHintParam = new ClassWithSelfHint(); + + $data = array( + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicMethod', + array(), + 'publicMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicTypeHintedMethod', + array(new \stdClass()), + 'publicTypeHintedMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicByReferenceMethod', + array(), + 'publicByReferenceMethodDefault' + ), + ); + + if (PHP_VERSION_ID >= 50401) { + // PHP < 5.4.1 misbehaves, throwing strict standards, see https://bugs.php.net/bug.php?id=60573 + $data[] = array( + 'ProxyManagerTestAsset\\ClassWithSelfHint', + new ClassWithSelfHint(), + 'selfHintMethod', + array('parameter' => $selfHintParam), + $selfHintParam + ); + } + + return $data; + } + + /** + * Generates proxies and instances with a public property to feed to the property accessor methods + * + * @return array + */ + public function getPropertyAccessProxies() + { + $instance1 = new BaseClass(); + $proxyName1 = $this->generateProxy(get_class($instance1)); + $instance2 = new BaseClass(); + $proxyName2 = $this->generateProxy(get_class($instance2)); + + return array( + array( + $instance1, + new $proxyName1($this->createInitializer('ProxyManagerTestAsset\\BaseClass', $instance1)), + 'publicProperty', + 'publicPropertyDefault', + ), + array( + $instance2, + unserialize( + serialize(new $proxyName2($this->createInitializer('ProxyManagerTestAsset\\BaseClass', $instance2))) + ), + 'publicProperty', + 'publicPropertyDefault', + ), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingGhostPerformanceTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingGhostPerformanceTest.php new file mode 100644 index 0000000..21b9f49 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingGhostPerformanceTest.php @@ -0,0 +1,160 @@ + + * @license MIT + * + * @group Performance + * @coversNothing + */ +class LazyLoadingGhostPerformanceTest extends BaseLazyLoadingPerformanceTest +{ + /** + * @outputBuffering + * @dataProvider getTestedClasses + * + * @param string $className + * @param array $methods + * @param array $properties + * @param \ReflectionProperty[] $reflectionProperties + * + * @return void + */ + public function testProxyInstantiationPerformance( + $className, + array $methods, + array $properties, + array $reflectionProperties + ) { + $proxyName = $this->generateProxy($className); + $iterations = 20000; + $instances = array(); + /* @var $proxies \ProxyManager\Proxy\GhostObjectInterface[] */ + $proxies = array(); + $realInstance = new $className(); + $initializer = function ( + GhostObjectInterface $proxy, + $method, + $params, + & $initializer + ) use ( + $reflectionProperties, + $realInstance + ) { + $initializer = null; + + foreach ($reflectionProperties as $reflectionProperty) { + $reflectionProperty->setValue($proxy, $reflectionProperty->getValue($realInstance)); + } + + return true; + }; + + $this->startCapturing(); + + for ($i = 0; $i < $iterations; $i += 1) { + $instances[] = new $className(); + } + + $baseProfile = $this->endCapturing( + 'Instantiation for ' . $iterations . ' objects of type ' . $className . ': %fms / %fKb' + ); + $this->startCapturing(); + + for ($i = 0; $i < $iterations; $i += 1) { + $proxies[] = new $proxyName($initializer); + } + + $proxyProfile = $this->endCapturing( + 'Instantiation for ' . $iterations . ' proxies of type ' . $className . ': %fms / %fKb' + ); + $this->compareProfile($baseProfile, $proxyProfile); + $this->startCapturing(); + + foreach ($proxies as $proxy) { + $proxy->initializeProxy(); + } + + $this->endCapturing('Initialization of ' . $iterations . ' proxies of type ' . $className . ': %fms / %fKb'); + + foreach ($methods as $methodName => $parameters) { + $this->profileMethodAccess($className, $instances, $proxies, $methodName, $parameters); + } + + foreach ($properties as $property) { + $this->profilePropertyWrites($className, $instances, $proxies, $property); + $this->profilePropertyReads($className, $instances, $proxies, $property); + $this->profilePropertyIsset($className, $instances, $proxies, $property); + $this->profilePropertyUnset($className, $instances, $proxies, $property); + } + } + + /** + * @return array + */ + public function getTestedClasses() + { + $testedClasses = array( + array('stdClass', array(), array()), + array('ProxyManagerTestAsset\\BaseClass', array('publicMethod' => array()), array('publicProperty')), + ); + + foreach ($testedClasses as $key => $testedClass) { + $reflectionProperties = array(); + $reflectionClass = new ReflectionClass($testedClass[0]); + + foreach ($reflectionClass->getProperties() as $property) { + $property->setAccessible(true); + + $reflectionProperties[$property->getName()] = $property; + } + + $testedClasses[$key][] = $reflectionProperties; + } + + return $testedClasses; + } + + /** + * {@inheritDoc} + */ + protected function generateProxy($parentClassName) + { + $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); + $generator = new LazyLoadingGhostGenerator(); + $generatedClass = new ClassGenerator($generatedClassName); + $strategy = new EvaluatingGeneratorStrategy(); + + $generator->generate(new ReflectionClass($parentClassName), $generatedClass); + $strategy->generate($generatedClass); + + return $generatedClassName; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderFunctionalTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderFunctionalTest.php new file mode 100644 index 0000000..a638760 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderFunctionalTest.php @@ -0,0 +1,386 @@ + + * @license MIT + * + * @group Functional + * @coversNothing + */ +class LazyLoadingValueHolderFunctionalTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getProxyMethods + */ + public function testMethodCalls($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\VirtualProxyInterface|BaseClass */ + $proxy = new $proxyName($this->createInitializer($className, $instance)); + + $this->assertFalse($proxy->isProxyInitialized()); + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertSame($instance, $proxy->getWrappedValueHolderValue()); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterUnSerialization($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\VirtualProxyInterface|BaseClass */ + $proxy = unserialize(serialize(new $proxyName($this->createInitializer($className, $instance)))); + + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + $this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterCloning($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\VirtualProxyInterface|BaseClass */ + $proxy = new $proxyName($this->createInitializer($className, $instance)); + $cloned = clone $proxy; + + $this->assertTrue($cloned->isProxyInitialized()); + $this->assertNotSame($proxy->getWrappedValueHolderValue(), $cloned->getWrappedValueHolderValue()); + $this->assertSame($expectedValue, call_user_func_array(array($cloned, $method), $params)); + $this->assertEquals($instance, $cloned->getWrappedValueHolderValue()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyReadAccess($instance, $proxy, $publicProperty, $propertyValue) + { + /* @var $proxy \ProxyManager\Proxy\VirtualProxyInterface|BaseClass */ + $this->assertSame($propertyValue, $proxy->$publicProperty); + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyWriteAccess($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\VirtualProxyInterface|BaseClass */ + $newValue = uniqid(); + $proxy->$publicProperty = $newValue; + + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertSame($newValue, $proxy->$publicProperty); + $this->assertSame($newValue, $proxy->getWrappedValueHolderValue()->$publicProperty); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyExistence($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\VirtualProxyInterface|BaseClass */ + $this->assertSame(isset($instance->$publicProperty), isset($proxy->$publicProperty)); + $this->assertTrue($proxy->isProxyInitialized()); + $this->assertEquals($instance, $proxy->getWrappedValueHolderValue()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyAbsence($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\VirtualProxyInterface|BaseClass */ + $instance = $proxy->getWrappedValueHolderValue() ? $proxy->getWrappedValueHolderValue() : $instance; + $instance->$publicProperty = null; + $this->assertFalse(isset($proxy->$publicProperty)); + $this->assertTrue($proxy->isProxyInitialized()); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyUnset($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\VirtualProxyInterface|BaseClass */ + $instance = $proxy->getWrappedValueHolderValue() ? $proxy->getWrappedValueHolderValue() : $instance; + unset($proxy->$publicProperty); + + $this->assertTrue($proxy->isProxyInitialized()); + + $this->assertFalse(isset($instance->$publicProperty)); + $this->assertFalse(isset($proxy->$publicProperty)); + } + + /** + * Verifies that accessing a public property containing an array behaves like in a normal context + */ + public function testCanWriteToArrayKeysInPublicProperty() + { + $instance = new ClassWithPublicArrayProperty(); + $className = get_class($instance); + $initializer = $this->createInitializer($className, $instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicArrayProperty */ + $proxy = new $proxyName($initializer); + + $proxy->arrayProperty['foo'] = 'bar'; + + $this->assertSame('bar', $proxy->arrayProperty['foo']); + + $proxy->arrayProperty = array('tab' => 'taz'); + + $this->assertSame(array('tab' => 'taz'), $proxy->arrayProperty); + } + + /** + * Verifies that public properties retrieved via `__get` don't get modified in the object itself + */ + public function testWillNotModifyRetrievedPublicProperties() + { + $instance = new ClassWithPublicProperties(); + $className = get_class($instance); + $initializer = $this->createInitializer($className, $instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName($initializer); + $variable = $proxy->property0; + + $this->assertSame('property0', $variable); + + $variable = 'foo'; + + $this->assertSame('property0', $proxy->property0); + } + + /** + * Verifies that public properties references retrieved via `__get` modify in the object state + */ + public function testWillModifyByRefRetrievedPublicProperties() + { + $instance = new ClassWithPublicProperties(); + $className = get_class($instance); + $initializer = $this->createInitializer($className, $instance); + $proxyName = $this->generateProxy($className); + /* @var $proxy ClassWithPublicProperties */ + $proxy = new $proxyName($initializer); + $variable = & $proxy->property0; + + $this->assertSame('property0', $variable); + + $variable = 'foo'; + + $this->assertSame('foo', $proxy->property0); + } + + /** + * @group 16 + * + * Verifies that initialization of a value holder proxy may happen multiple times + */ + public function testWillAllowMultipleProxyInitialization() + { + $proxyClass = $this->generateProxy('ProxyManagerTestAsset\\BaseClass'); + $counter = 0; + $initializer = function (& $wrappedInstance) use (& $counter) { + $wrappedInstance = new BaseClass(); + + $wrappedInstance->publicProperty = (string) ($counter += 1); + }; + + /* @var $proxy BaseClass */ + $proxy = new $proxyClass($initializer); + + $this->assertSame('1', $proxy->publicProperty); + $this->assertSame('2', $proxy->publicProperty); + $this->assertSame('3', $proxy->publicProperty); + } + + /** + * Generates a proxy for the given class name, and retrieves its class name + * + * @param string $parentClassName + * + * @return string + */ + private function generateProxy($parentClassName) + { + $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); + $generator = new LazyLoadingValueHolderGenerator(); + $generatedClass = new ClassGenerator($generatedClassName); + $strategy = new EvaluatingGeneratorStrategy(); + + $generator->generate(new ReflectionClass($parentClassName), $generatedClass); + $strategy->generate($generatedClass); + + return $generatedClassName; + } + + /** + * @param string $className + * @param object $realInstance + * @param Mock $initializerMatcher + * + * @return \Closure + */ + private function createInitializer($className, $realInstance, Mock $initializerMatcher = null) + { + if (null === $initializerMatcher) { + $initializerMatcher = $this->getMock('stdClass', array('__invoke')); + + $initializerMatcher + ->expects($this->once()) + ->method('__invoke') + ->with( + $this->logicalAnd( + $this->isInstanceOf('ProxyManager\\Proxy\\VirtualProxyInterface'), + $this->isInstanceOf($className) + ), + $realInstance + ); + } + + $initializerMatcher = $initializerMatcher ?: $this->getMock('stdClass', array('__invoke')); + + return function ( + & $wrappedObject, + VirtualProxyInterface $proxy, + $method, + $params, + & $initializer + ) use ( + $initializerMatcher, + $realInstance + ) { + $initializer = null; + $wrappedObject = $realInstance; + + $initializerMatcher->__invoke($proxy, $wrappedObject, $method, $params); + }; + } + + /** + * Generates a list of object | invoked method | parameters | expected result + * + * @return array + */ + public function getProxyMethods() + { + $selfHintParam = new ClassWithSelfHint(); + + $data = array( + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicMethod', + array(), + 'publicMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicTypeHintedMethod', + array(new \stdClass()), + 'publicTypeHintedMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicByReferenceMethod', + array(), + 'publicByReferenceMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseInterface', + new BaseClass(), + 'publicMethod', + array(), + 'publicMethodDefault' + ), + ); + + if (PHP_VERSION_ID >= 50401) { + // PHP < 5.4.1 misbehaves, throwing strict standards, see https://bugs.php.net/bug.php?id=60573 + $data[] = array( + 'ProxyManagerTestAsset\\ClassWithSelfHint', + new ClassWithSelfHint(), + 'selfHintMethod', + array('parameter' => $selfHintParam), + $selfHintParam + ); + } + + return $data; + } + + /** + * Generates proxies and instances with a public property to feed to the property accessor methods + * + * @return array + */ + public function getPropertyAccessProxies() + { + $instance1 = new BaseClass(); + $proxyName1 = $this->generateProxy(get_class($instance1)); + $instance2 = new BaseClass(); + $proxyName2 = $this->generateProxy(get_class($instance2)); + + return array( + array( + $instance1, + new $proxyName1($this->createInitializer('ProxyManagerTestAsset\\BaseClass', $instance1)), + 'publicProperty', + 'publicPropertyDefault', + ), + array( + $instance2, + unserialize( + serialize(new $proxyName2($this->createInitializer('ProxyManagerTestAsset\\BaseClass', $instance2))) + ), + 'publicProperty', + 'publicPropertyDefault', + ), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderPerformanceTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderPerformanceTest.php new file mode 100644 index 0000000..1279526 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/LazyLoadingValueHolderPerformanceTest.php @@ -0,0 +1,134 @@ + + * @license MIT + * + * @group Performance + * @coversNothing + */ +class LazyLoadingValueHolderPerformanceTest extends BaseLazyLoadingPerformanceTest +{ + /** + * @outputBuffering + * @dataProvider getTestedClasses + * + * @param string $className + * @param array $methods + * @param array $properties + * + * @return void + */ + public function testProxyInstantiationPerformance($className, array $methods, array $properties) + { + $proxyName = $this->generateProxy($className); + $iterations = 20000; + $instances = array(); + /* @var $proxies \ProxyManager\Proxy\VirtualProxyInterface[] */ + $proxies = array(); + $initializer = function ( + & $valueHolder, + VirtualProxyInterface $proxy, + $method, + $params, + & $initializer + ) use ($className) { + $initializer = null; + $valueHolder = new $className(); + + return true; + }; + + $this->startCapturing(); + + for ($i = 0; $i < $iterations; $i += 1) { + $instances[] = new $className(); + } + + $baseProfile = $this->endCapturing( + 'Instantiation for ' . $iterations . ' objects of type ' . $className . ': %fms / %fKb' + ); + $this->startCapturing(); + + for ($i = 0; $i < $iterations; $i += 1) { + $proxies[] = new $proxyName($initializer); + } + + $proxyProfile = $this->endCapturing( + 'Instantiation for ' . $iterations . ' proxies of type ' . $className . ': %fms / %fKb' + ); + $this->compareProfile($baseProfile, $proxyProfile); + $this->startCapturing(); + + foreach ($proxies as $proxy) { + $proxy->initializeProxy(); + } + + $this->endCapturing('Initialization of ' . $iterations . ' proxies of type ' . $className . ': %fms / %fKb'); + + foreach ($methods as $methodName => $parameters) { + $this->profileMethodAccess($className, $instances, $proxies, $methodName, $parameters); + } + + foreach ($properties as $property) { + $this->profilePropertyWrites($className, $instances, $proxies, $property); + $this->profilePropertyReads($className, $instances, $proxies, $property); + $this->profilePropertyIsset($className, $instances, $proxies, $property); + $this->profilePropertyUnset($className, $instances, $proxies, $property); + } + } + + /** + * @return array + */ + public function getTestedClasses() + { + return array( + array('stdClass', array(), array()), + array('ProxyManagerTestAsset\\BaseClass', array('publicMethod' => array()), array('publicProperty')), + ); + } + + /** + * {@inheritDoc} + */ + protected function generateProxy($parentClassName) + { + $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); + $generator = new LazyLoadingValueHolderGenerator(); + $generatedClass = new ClassGenerator($generatedClassName); + $strategy = new EvaluatingGeneratorStrategy(); + + $generator->generate(new ReflectionClass($parentClassName), $generatedClass); + $strategy->generate($generatedClass); + + return $generatedClassName; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/MultipleProxyGenerationTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/MultipleProxyGenerationTest.php new file mode 100644 index 0000000..00be47d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/MultipleProxyGenerationTest.php @@ -0,0 +1,123 @@ + + * @license MIT + * + * @link https://github.com/Ocramius/ProxyManager/issues/10 + * + * @group Functional + * @group issue-10 + * @coversNothing + */ +class MultipleProxyGenerationTest extends PHPUnit_Framework_TestCase +{ + /** + * Verifies that proxies generated from different factories will retain their specific implementation + * and won't conflict + * + * @dataProvider getTestedClasses + */ + public function testCanGenerateMultipleDifferentProxiesForSameClass($className) + { + $skipScopeLocalizerTests = false; + $ghostProxyFactory = new LazyLoadingGhostFactory(); + $virtualProxyFactory = new LazyLoadingValueHolderFactory(); + $accessInterceptorFactory = new AccessInterceptorValueHolderFactory(); + $accessInterceptorScopeLocalizerFactory = new AccessInterceptorScopeLocalizerFactory(); + $initializer = function () { + }; + + $reflectionClass = new ReflectionClass($className); + + if ((! method_exists('Closure', 'bind')) && $reflectionClass->getProperties(ReflectionProperty::IS_PRIVATE)) { + $skipScopeLocalizerTests = true; + } + + $generated = array( + $ghostProxyFactory->createProxy($className, $initializer), + $virtualProxyFactory->createProxy($className, $initializer), + $accessInterceptorFactory->createProxy(new $className()), + ); + + if (! $skipScopeLocalizerTests) { + $generated[] = $accessInterceptorScopeLocalizerFactory->createProxy(new $className()); + } + + foreach ($generated as $key => $proxy) { + $this->assertInstanceOf($className, $proxy); + + foreach ($generated as $comparedKey => $comparedProxy) { + if ($comparedKey === $key) { + continue; + } + + $this->assertNotSame(get_class($comparedProxy), get_class($proxy)); + } + } + + $this->assertInstanceOf('ProxyManager\Proxy\GhostObjectInterface', $generated[0]); + $this->assertInstanceOf('ProxyManager\Proxy\VirtualProxyInterface', $generated[1]); + $this->assertInstanceOf('ProxyManager\Proxy\AccessInterceptorInterface', $generated[2]); + $this->assertInstanceOf('ProxyManager\Proxy\ValueHolderInterface', $generated[2]); + + if (! $skipScopeLocalizerTests) { + $this->assertInstanceOf('ProxyManager\Proxy\AccessInterceptorInterface', $generated[3]); + } + } + + /** + * @return string[][] + */ + public function getTestedClasses() + { + $data = array( + array('ProxyManagerTestAsset\\BaseClass'), + array('ProxyManagerTestAsset\\ClassWithMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithFinalMethods'), + array('ProxyManagerTestAsset\\ClassWithFinalMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithByRefMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithMixedProperties'), + array('ProxyManagerTestAsset\\ClassWithPrivateProperties'), + array('ProxyManagerTestAsset\\ClassWithProtectedProperties'), + array('ProxyManagerTestAsset\\ClassWithPublicProperties'), + array('ProxyManagerTestAsset\\EmptyClass'), + array('ProxyManagerTestAsset\\HydratedObject'), + ); + + if (PHP_VERSION_ID >= 50401) { + // PHP < 5.4.1 misbehaves, throwing strict standards, see https://bugs.php.net/bug.php?id=60573 + $data[] = array('ProxyManagerTestAsset\\ClassWithSelfHint'); + } + + return $data; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/NullObjectFunctionalTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/NullObjectFunctionalTest.php new file mode 100644 index 0000000..adfeae5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/NullObjectFunctionalTest.php @@ -0,0 +1,223 @@ + + * @license MIT + * + * @group Functional + * @coversNothing + */ +class NullObjectFunctionalTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getProxyMethods + */ + public function testMethodCalls($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\NullObjectInterface */ + $proxy = new $proxyName(); + + $this->assertSame(null, call_user_func_array(array($proxy, $method), $params)); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterUnSerialization($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + /* @var $proxy \ProxyManager\Proxy\NullObjectInterface */ + $proxy = unserialize(serialize(new $proxyName())); + + $this->assertSame(null, call_user_func_array(array($proxy, $method), $params)); + } + + /** + * @dataProvider getProxyMethods + */ + public function testMethodCallsAfterCloning($className, $instance, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($className); + + /* @var $proxy \ProxyManager\Proxy\NullObjectInterface */ + $proxy = new $proxyName(); + $cloned = clone $proxy; + + $this->assertSame(null, call_user_func_array(array($cloned, $method), $params)); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyReadAccess($instance, $proxy, $publicProperty, $propertyValue) + { + /* @var $proxy \ProxyManager\Proxy\NullObjectInterface */ + $this->assertSame(null, $proxy->$publicProperty); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyWriteAccess($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\NullObjectInterface */ + $newValue = uniqid(); + $proxy->$publicProperty = $newValue; + + $this->assertSame($newValue, $proxy->$publicProperty); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyExistence($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\NullObjectInterface */ + $this->assertSame(null, $proxy->$publicProperty); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testPropertyUnset($instance, $proxy, $publicProperty) + { + /* @var $proxy \ProxyManager\Proxy\NullObjectInterface */ + unset($proxy->$publicProperty); + + $this->assertTrue(isset($instance->$publicProperty)); + $this->assertFalse(isset($proxy->$publicProperty)); + } + + /** + * Generates a proxy for the given class name, and retrieves its class name + * + * @param string $parentClassName + * + * @return string + */ + private function generateProxy($parentClassName) + { + $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); + $generator = new NullObjectGenerator(); + $generatedClass = new ClassGenerator($generatedClassName); + $strategy = new EvaluatingGeneratorStrategy(); + + $generator->generate(new ReflectionClass($parentClassName), $generatedClass); + $strategy->generate($generatedClass); + + return $generatedClassName; + } + + /** + * Generates a list of object | invoked method | parameters | expected result + * + * @return array + */ + public function getProxyMethods() + { + $selfHintParam = new ClassWithSelfHint(); + + $data = array( + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicMethod', + array(), + 'publicMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicTypeHintedMethod', + array('param' => new \stdClass()), + 'publicTypeHintedMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseClass', + new BaseClass(), + 'publicByReferenceMethod', + array(), + 'publicByReferenceMethodDefault' + ), + array( + 'ProxyManagerTestAsset\\BaseInterface', + new BaseClass(), + 'publicMethod', + array(), + 'publicMethodDefault' + ), + ); + + if (PHP_VERSION_ID >= 50401) { + // PHP < 5.4.1 misbehaves, throwing strict standards, see https://bugs.php.net/bug.php?id=60573 + $data[] = array( + 'ProxyManagerTestAsset\\ClassWithSelfHint', + new ClassWithSelfHint(), + 'selfHintMethod', + array('parameter' => $selfHintParam), + $selfHintParam + ); + } + + return $data; + } + + /** + * Generates proxies and instances with a public property to feed to the property accessor methods + * + * @return array + */ + public function getPropertyAccessProxies() + { + $instance1 = new BaseClass(); + $proxyName1 = $this->generateProxy(get_class($instance1)); + $instance2 = new BaseClass(); + $proxyName2 = $this->generateProxy(get_class($instance2)); + + return array( + array( + $instance1, + new $proxyName1($instance1), + 'publicProperty', + 'publicPropertyDefault', + ), + array( + $instance2, + unserialize(serialize(new $proxyName2($instance2))), + 'publicProperty', + 'publicPropertyDefault', + ), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/RemoteObjectFunctionalTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/RemoteObjectFunctionalTest.php new file mode 100644 index 0000000..b5d9c95 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Functional/RemoteObjectFunctionalTest.php @@ -0,0 +1,231 @@ + + * @license MIT + * + * @group Functional + * @coversNothing + */ +class RemoteObjectFunctionalTest extends PHPUnit_Framework_TestCase +{ + /** + * @param mixed $expectedValue + * @param string $method + * @param array $params + * + * @return XmlRpcAdapter + */ + protected function getXmlRpcAdapter($expectedValue, $method, array $params) + { + $client = $this + ->getMockBuilder('Zend\Server\Client') + ->setMethods(array('call')) + ->getMock(); + + $client + ->expects($this->any()) + ->method('call') + ->with($this->stringEndsWith($method), $params) + ->will($this->returnValue($expectedValue)); + + $adapter = new XmlRpcAdapter( + $client, + array( + 'ProxyManagerTestAsset\RemoteProxy\Foo.foo' + => 'ProxyManagerTestAsset\RemoteProxy\FooServiceInterface.foo' + ) + ); + + return $adapter; + } + + /** + * @param mixed $expectedValue + * @param string $method + * @param array $params + * + * @return JsonRpcAdapter + */ + protected function getJsonRpcAdapter($expectedValue, $method, array $params) + { + $client = $this + ->getMockBuilder('Zend\Server\Client') + ->setMethods(array('call')) + ->getMock(); + + $client + ->expects($this->any()) + ->method('call') + ->with($this->stringEndsWith($method), $params) + ->will($this->returnValue($expectedValue)); + + $adapter = new JsonRpcAdapter( + $client, + array( + 'ProxyManagerTestAsset\RemoteProxy\Foo.foo' + => 'ProxyManagerTestAsset\RemoteProxy\FooServiceInterface.foo' + ) + ); + + return $adapter; + } + + /** + * @dataProvider getProxyMethods + */ + public function testXmlRpcMethodCalls($instanceOrClassname, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($instanceOrClassname); + + /* @var $proxy \ProxyManager\Proxy\RemoteObjectInterface */ + $proxy = new $proxyName($this->getXmlRpcAdapter($expectedValue, $method, $params)); + + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + } + + /** + * @dataProvider getProxyMethods + */ + public function testJsonRpcMethodCalls($instanceOrClassname, $method, $params, $expectedValue) + { + $proxyName = $this->generateProxy($instanceOrClassname); + + /* @var $proxy \ProxyManager\Proxy\RemoteObjectInterface */ + $proxy = new $proxyName($this->getJsonRpcAdapter($expectedValue, $method, $params)); + + $this->assertSame($expectedValue, call_user_func_array(array($proxy, $method), $params)); + } + + /** + * @dataProvider getPropertyAccessProxies + */ + public function testJsonRpcPropertyReadAccess($instanceOrClassname, $publicProperty, $propertyValue) + { + $proxyName = $this->generateProxy($instanceOrClassname); + + /* @var $proxy \ProxyManager\Proxy\RemoteObjectInterface */ + $proxy = new $proxyName( + $this->getJsonRpcAdapter($propertyValue, '__get', array($publicProperty)) + ); + + /* @var $proxy \ProxyManager\Proxy\NullObjectInterface */ + $this->assertSame($propertyValue, $proxy->$publicProperty); + } + + /** + * Generates a proxy for the given class name, and retrieves its class name + * + * @param string $parentClassName + * + * @return string + */ + private function generateProxy($parentClassName) + { + $generatedClassName = __NAMESPACE__ . '\\' . UniqueIdentifierGenerator::getIdentifier('Foo'); + $generator = new RemoteObjectGenerator(); + $generatedClass = new ClassGenerator($generatedClassName); + $strategy = new EvaluatingGeneratorStrategy(); + + $generator->generate(new ReflectionClass($parentClassName), $generatedClass); + $strategy->generate($generatedClass); + + return $generatedClassName; + } + + /** + * Generates a list of object | invoked method | parameters | expected result + * + * @return array + */ + public function getProxyMethods() + { + $selfHintParam = new ClassWithSelfHint(); + + $data = array( + array( + 'ProxyManagerTestAsset\RemoteProxy\FooServiceInterface', + 'foo', + array(), + 'bar remote' + ), + array( + 'ProxyManagerTestAsset\RemoteProxy\Foo', + 'foo', + array(), + 'bar remote' + ), + array( + new Foo(), + 'foo', + array(), + 'bar remote' + ), + array( + 'ProxyManagerTestAsset\RemoteProxy\BazServiceInterface', + 'baz', + array('baz'), + 'baz remote' + ), + ); + + if (PHP_VERSION_ID >= 50401) { + // PHP < 5.4.1 misbehaves, throwing strict standards, see https://bugs.php.net/bug.php?id=60573 + $data[] = array( + new ClassWithSelfHint(), + 'selfHintMethod', + array($selfHintParam), + $selfHintParam + ); + } + + return $data; + } + + /** + * Generates proxies and instances with a public property to feed to the property accessor methods + * + * @return array + */ + public function getPropertyAccessProxies() + { + return array( + array( + 'ProxyManagerTestAsset\RemoteProxy\FooServiceInterface', + 'publicProperty', + 'publicProperty remote', + ), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/ClassGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/ClassGeneratorTest.php new file mode 100644 index 0000000..c00f849 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/ClassGeneratorTest.php @@ -0,0 +1,65 @@ + + * @license MIT + * + * @group Coverage + */ +class ClassGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\Generator\ClassGenerator::setExtendedClass + */ + public function testExtendedClassesAreFQCNs() + { + $desiredFqcn = '\\stdClass'; + $classNameInputs = array('stdClass', '\\stdClass\\'); + + foreach ($classNameInputs as $className) { + $classGenerator = new ClassGenerator(); + $classGenerator->setExtendedClass($className); + + $this->assertEquals($desiredFqcn, $classGenerator->getExtendedClass()); + } + } + + /** + * @covers \ProxyManager\Generator\ClassGenerator::setImplementedInterfaces + */ + public function testImplementedInterfacesAreFQCNs() + { + $desiredFqcns = array('\\Countable'); + $interfaceNameInputs = array(array('Countable'), array('\\Countable\\')); + + foreach ($interfaceNameInputs as $interfaceNames) { + $classGenerator = new ClassGenerator(); + $classGenerator->setImplementedInterfaces($interfaceNames); + + $this->assertEquals($desiredFqcns, $classGenerator->getImplementedInterfaces()); + } + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/MagicMethodGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/MagicMethodGeneratorTest.php new file mode 100644 index 0000000..c5ae31a --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/MagicMethodGeneratorTest.php @@ -0,0 +1,56 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicMethodGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\Generator\MagicMethodGenerator::__construct + */ + public function testGeneratesCorrectByRefReturnValue() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithByRefMagicMethods'); + $magicMethod = new MagicMethodGenerator($reflection, '__get', array('name')); + + $this->assertTrue($magicMethod->returnsReference()); + } + + /** + * @covers \ProxyManager\Generator\MagicMethodGenerator::__construct + */ + public function testGeneratesCorrectByValReturnValue() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $magicMethod = new MagicMethodGenerator($reflection, '__get', array('name')); + + $this->assertFalse($magicMethod->returnsReference()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/MethodGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/MethodGeneratorTest.php new file mode 100644 index 0000000..4c2306a --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/MethodGeneratorTest.php @@ -0,0 +1,97 @@ + + * @license MIT + * + * @covers \ProxyManager\Generator\MethodGenerator + * @group Coverage + */ +class MethodGeneratorTest extends PHPUnit_Framework_TestCase +{ + public function testGenerateSimpleMethod() + { + $methodGenerator = new MethodGenerator(); + + $methodGenerator->setReturnsReference(true); + $methodGenerator->setName('methodName'); + $methodGenerator->setVisibility('protected'); + $methodGenerator->setBody('/* body */'); + $methodGenerator->setDocBlock('docBlock'); + $methodGenerator->setParameter(new ParameterGenerator('foo')); + + $this->assertSame(true, $methodGenerator->returnsReference()); + $this->assertStringMatchesFormat( + '%a/**%adocBlock%a*/%aprotected function & methodName($foo)%a{%a/* body */%a}', + $methodGenerator->generate() + ); + } + + /** + * Verify that building from reflection works + */ + public function testGenerateFromReflection() + { + $method = MethodGenerator::fromReflection(new MethodReflection(__CLASS__, __FUNCTION__)); + + $this->assertSame(__FUNCTION__, $method->getName()); + $this->assertSame(MethodGenerator::VISIBILITY_PUBLIC, $method->getVisibility()); + $this->assertFalse($method->isStatic()); + $this->assertSame('Verify that building from reflection works', $method->getDocBlock()->getShortDescription()); + + $method = MethodGenerator::fromReflection( + new MethodReflection('ProxyManagerTestAsset\\BaseClass', 'protectedMethod') + ); + + $this->assertSame(MethodGenerator::VISIBILITY_PROTECTED, $method->getVisibility()); + + $method = MethodGenerator::fromReflection( + new MethodReflection('ProxyManagerTestAsset\\BaseClass', 'privateMethod') + ); + + $this->assertSame(MethodGenerator::VISIBILITY_PRIVATE, $method->getVisibility()); + } + + public function testGeneratedParametersFromReflection() + { + $method = MethodGenerator::fromReflection(new MethodReflection( + 'ProxyManagerTestAsset\\BaseClass', + 'publicTypeHintedMethod' + )); + + $this->assertSame('publicTypeHintedMethod', $method->getName()); + + $parameters = $method->getParameters(); + + $this->assertCount(1, $parameters); + + $param = $parameters['param']; + + $this->assertSame('stdClass', $param->getType()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/ParameterGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/ParameterGeneratorTest.php new file mode 100644 index 0000000..a67f225 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/ParameterGeneratorTest.php @@ -0,0 +1,146 @@ + + * @license MIT + * + * @covers \ProxyManager\Generator\ParameterGenerator + * @group Coverage + */ +class ParameterGeneratorTest extends PHPUnit_Framework_TestCase +{ + public function testGeneratesProperTypeHint() + { + $generator = new ParameterGenerator('foo'); + + $generator->setType('array'); + $this->assertSame('array $foo', $generator->generate()); + + $generator->setType('stdClass'); + $this->assertSame('\\stdClass $foo', $generator->generate()); + + $generator->setType('\\fooClass'); + $this->assertSame('\\fooClass $foo', $generator->generate()); + } + + public function testGeneratesMethodWithCallableType() + { + if (PHP_VERSION_ID < 50400) { + $this->markTestSkipped('`callable` is only supported in PHP >=5.4.0'); + } + + $generator = new ParameterGenerator(); + + $generator->setType('callable'); + $generator->setName('foo'); + + $this->assertSame('callable $foo', $generator->generate()); + } + + public function testVisitMethodWithCallable() + { + if (PHP_VERSION_ID < 50400) { + $this->markTestSkipped('`callable` is only supported in PHP >=5.4.0'); + } + + $parameter = new ParameterReflection( + array('ProxyManagerTestAsset\\CallableTypeHintClass', 'callableTypeHintMethod'), + 'parameter' + ); + + $generator = ParameterGenerator::fromReflection($parameter); + + $this->assertSame('callable', $generator->getType()); + } + + public function testReadsParameterDefaults() + { + $parameter = ParameterGenerator::fromReflection(new ParameterReflection( + array( + 'ProxyManagerTestAsset\\ClassWithMethodWithDefaultParameters', + 'publicMethodWithDefaults' + ), + 'parameter' + )); + + /* @var $defaultValue \Zend\Code\Generator\ValueGenerator */ + $defaultValue = $parameter->getDefaultValue(); + + $this->assertInstanceOf('Zend\\Code\\Generator\\ValueGenerator', $defaultValue); + $this->assertSame(array('foo'), $defaultValue->getValue()); + + $this->assertStringMatchesFormat('array%a$parameter%a=%aarray(\'foo\')', $parameter->generate()); + } + + public function testReadsParameterTypeHint() + { + $parameter = ParameterGenerator::fromReflection(new ParameterReflection( + array('ProxyManagerTestAsset\\BaseClass', 'publicTypeHintedMethod'), + 'param' + )); + + $this->assertSame('stdClass', $parameter->getType()); + } + + public function testGeneratesParameterPassedByReference() + { + $parameter = new ParameterGenerator('foo'); + + $parameter->setPassedByReference(true); + + $this->assertStringMatchesFormat('&%A$foo', $parameter->generate()); + } + + public function testGeneratesDefaultParameterForInternalPhpClasses() + { + $parameter = ParameterGenerator::fromReflection(new ParameterReflection( + array( + 'Phar', + 'compress' + ), + 1 + )); + + $this->assertSame('null', strtolower((string) $parameter->getDefaultValue())); + } + + public function testGeneratedParametersAreProperlyEscaped() + { + $parameter = new ParameterGenerator(); + + $parameter->setName('foo'); + $parameter->setDefaultValue('\'bar\\baz'); + + $this->assertThat( + $parameter->generate(), + $this->logicalOr( + $this->equalTo('$foo = \'\\\'bar\\baz\''), + $this->equalTo('$foo = \'\\\'bar\\\\baz\'') + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/Util/ClassGeneratorUtilsTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/Util/ClassGeneratorUtilsTest.php new file mode 100644 index 0000000..cf52546 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/Util/ClassGeneratorUtilsTest.php @@ -0,0 +1,72 @@ + + * @license MIT + * + * @covers ProxyManager\Generator\Util\ClassGeneratorUtils + */ +class ClassGeneratorUtilsTest extends PHPUnit_Framework_TestCase +{ + public function testCantAddAFinalMethod() + { + $classGenerator = $this->getMock('Zend\\Code\\Generator\\ClassGenerator'); + $methodGenerator = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + + $methodGenerator + ->expects($this->once()) + ->method('getName') + ->willReturn('foo'); + + $classGenerator + ->expects($this->never()) + ->method('addMethodFromGenerator'); + + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithFinalMethods'); + + ClassGeneratorUtils::addMethodIfNotFinal($reflection, $classGenerator, $methodGenerator); + } + + public function testCanAddANotFinalMethod() + { + $classGenerator = $this->getMock('Zend\\Code\\Generator\\ClassGenerator'); + $methodGenerator = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + + $methodGenerator + ->expects($this->once()) + ->method('getName') + ->willReturn('publicMethod'); + + $classGenerator + ->expects($this->once()) + ->method('addMethodFromGenerator'); + + $reflection = new ReflectionClass('ProxyManagerTestAsset\\BaseClass'); + + ClassGeneratorUtils::addMethodIfNotFinal($reflection, $classGenerator, $methodGenerator); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/Util/UniqueIdentifierGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/Util/UniqueIdentifierGeneratorTest.php new file mode 100644 index 0000000..5f4e8ec --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Generator/Util/UniqueIdentifierGeneratorTest.php @@ -0,0 +1,77 @@ + + * @license MIT + * + * @group Coverage + */ +class UniqueIdentifierGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getBaseIdentifierNames + * + * @covers \ProxyManager\Generator\Util\UniqueIdentifierGenerator::getIdentifier + */ + public function testGeneratesUniqueIdentifiers($name) + { + $this->assertNotSame( + UniqueIdentifierGenerator::getIdentifier($name), + UniqueIdentifierGenerator::getIdentifier($name) + ); + } + + /** + * @dataProvider getBaseIdentifierNames + * + * @covers \ProxyManager\Generator\Util\UniqueIdentifierGenerator::getIdentifier + */ + public function testGeneratesValidIdentifiers($name) + { + $this->assertRegExp( + '/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]+$/', + UniqueIdentifierGenerator::getIdentifier($name) + ); + } + + /** + * Data provider generating identifier names to be checked + * + * @return string[][] + */ + public function getBaseIdentifierNames() + { + return array( + array(''), + array('1'), + array('foo'), + array('Foo'), + array('bar'), + array('Bar'), + array('foo_bar'), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/BaseGeneratorStrategyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/BaseGeneratorStrategyTest.php new file mode 100644 index 0000000..d2366a2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/BaseGeneratorStrategyTest.php @@ -0,0 +1,48 @@ + + * @license MIT + * + * @group Coverage + */ +class BaseGeneratorStrategyTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\GeneratorStrategy\BaseGeneratorStrategy::generate + */ + public function testGenerate() + { + $strategy = new BaseGeneratorStrategy(); + $className = UniqueIdentifierGenerator::getIdentifier('Foo'); + $classGenerator = new ClassGenerator($className); + $generated = $strategy->generate($classGenerator); + + $this->assertGreaterThan(0, strpos($generated, $className)); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/EvaluatingGeneratorStrategyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/EvaluatingGeneratorStrategyTest.php new file mode 100644 index 0000000..90d22ce --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/EvaluatingGeneratorStrategyTest.php @@ -0,0 +1,69 @@ + + * @license MIT + * + * @group Coverage + */ +class EvaluatingGeneratorStrategyTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy::generate + * @covers \ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy::__construct + */ + public function testGenerate() + { + $strategy = new EvaluatingGeneratorStrategy(); + $className = UniqueIdentifierGenerator::getIdentifier('Foo'); + $classGenerator = new ClassGenerator($className); + $generated = $strategy->generate($classGenerator); + + $this->assertGreaterThan(0, strpos($generated, $className)); + $this->assertTrue(class_exists($className, false)); + } + + /** + * @covers \ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy::generate + * @covers \ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy::__construct + */ + public function testGenerateWithDisabledEval() + { + if (! ini_get('suhosin.executor.disable_eval')) { + $this->markTestSkipped('Ini setting "suhosin.executor.disable_eval" is needed to run this test'); + } + + $strategy = new EvaluatingGeneratorStrategy(); + $className = 'Foo' . uniqid(); + $classGenerator = new ClassGenerator($className); + $generated = $strategy->generate($classGenerator); + + $this->assertGreaterThan(0, strpos($generated, $className)); + $this->assertTrue(class_exists($className, false)); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/FileWriterGeneratorStrategyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/FileWriterGeneratorStrategyTest.php new file mode 100644 index 0000000..03eef8a --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/GeneratorStrategy/FileWriterGeneratorStrategyTest.php @@ -0,0 +1,148 @@ + + * @license MIT + * + * @group Coverage + * + * Note: this test generates temporary files that are not deleted + */ +class FileWriterGeneratorStrategyTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy::__construct + * @covers \ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy::generate + */ + public function testGenerate() + { + /* @var $locator \ProxyManager\FileLocator\FileLocatorInterface|\PHPUnit_Framework_MockObject_MockObject */ + $locator = $this->getMock('ProxyManager\\FileLocator\\FileLocatorInterface'); + $generator = new FileWriterGeneratorStrategy($locator); + $tmpFile = sys_get_temp_dir() . '/' . uniqid('FileWriterGeneratorStrategyTest', true) . '.php'; + $namespace = 'Foo'; + $className = UniqueIdentifierGenerator::getIdentifier('Bar'); + $fqcn = $namespace . '\\' . $className; + + $locator + ->expects($this->any()) + ->method('getProxyFileName') + ->with($fqcn) + ->will($this->returnValue($tmpFile)); + + $body = $generator->generate(new ClassGenerator($fqcn)); + + $this->assertGreaterThan(0, strpos($body, $className)); + $this->assertFalse(class_exists($fqcn, false)); + $this->assertTrue(file_exists($tmpFile)); + + require $tmpFile; + + $this->assertTrue(class_exists($fqcn, false)); + } + + public function testGenerateWillFailIfTmpFileCannotBeWrittenToDisk() + { + $tmpDirPath = sys_get_temp_dir() . '/' . uniqid('nonWritable', true); + + mkdir($tmpDirPath, 0555, true); + + /* @var $locator \ProxyManager\FileLocator\FileLocatorInterface|\PHPUnit_Framework_MockObject_MockObject */ + $locator = $this->getMock('ProxyManager\\FileLocator\\FileLocatorInterface'); + $generator = new FileWriterGeneratorStrategy($locator); + $tmpFile = $tmpDirPath . '/' . uniqid('FileWriterGeneratorStrategyFailedFileWriteTest', true) . '.php'; + $namespace = 'Foo'; + $className = UniqueIdentifierGenerator::getIdentifier('Bar'); + $fqcn = $namespace . '\\' . $className; + + $locator + ->expects($this->any()) + ->method('getProxyFileName') + ->with($fqcn) + ->will($this->returnValue($tmpFile)); + + $this->setExpectedException('ProxyManager\\Exception\\FileNotWritableException'); + $generator->generate(new ClassGenerator($fqcn)); + } + + public function testGenerateWillFailIfTmpFileCannotBeMovedToFinalDestination() + { + /* @var $locator \ProxyManager\FileLocator\FileLocatorInterface|\PHPUnit_Framework_MockObject_MockObject */ + $locator = $this->getMock('ProxyManager\\FileLocator\\FileLocatorInterface'); + $generator = new FileWriterGeneratorStrategy($locator); + $tmpFile = sys_get_temp_dir() . '/' . uniqid('FileWriterGeneratorStrategyFailedFileMoveTest', true) . '.php'; + $namespace = 'Foo'; + $className = UniqueIdentifierGenerator::getIdentifier('Bar'); + $fqcn = $namespace . '\\' . $className; + + $locator + ->expects($this->any()) + ->method('getProxyFileName') + ->with($fqcn) + ->will($this->returnValue($tmpFile)); + + mkdir($tmpFile); + + $this->setExpectedException('ProxyManager\\Exception\\FileNotWritableException'); + $generator->generate(new ClassGenerator($fqcn)); + } + + public function testWhenFailingAllTemporaryFilesAreRemoved() + { + $tmpDirPath = sys_get_temp_dir() . '/' . uniqid('noTempFilesLeftBehind', true); + + mkdir($tmpDirPath); + + /* @var $locator \ProxyManager\FileLocator\FileLocatorInterface|\PHPUnit_Framework_MockObject_MockObject */ + $locator = $this->getMock('ProxyManager\\FileLocator\\FileLocatorInterface'); + $generator = new FileWriterGeneratorStrategy($locator); + $tmpFile = $tmpDirPath . '/' . uniqid('FileWriterGeneratorStrategyFailedFileMoveTest', true) . '.php'; + $namespace = 'Foo'; + $className = UniqueIdentifierGenerator::getIdentifier('Bar'); + $fqcn = $namespace . '\\' . $className; + + $locator + ->expects($this->any()) + ->method('getProxyFileName') + ->with($fqcn) + ->will($this->returnValue($tmpFile)); + + mkdir($tmpFile); + + try { + $generator->generate(new ClassGenerator($fqcn)); + + $this->fail('An exception was supposed to be thrown'); + } catch (FileNotWritableException $exception) { + rmdir($tmpFile); + + $this->assertEquals(array('.', '..'), scandir($tmpDirPath)); + } + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/ClassNameInflectorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/ClassNameInflectorTest.php new file mode 100644 index 0000000..b95d7f2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/ClassNameInflectorTest.php @@ -0,0 +1,161 @@ + + * @license MIT + * + * @group Coverage + */ +class ClassNameInflectorTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getClassNames + * + * @covers \ProxyManager\Inflector\ClassNameInflector::__construct + * @covers \ProxyManager\Inflector\ClassNameInflector::getUserClassName + * @covers \ProxyManager\Inflector\ClassNameInflector::getProxyClassName + * @covers \ProxyManager\Inflector\ClassNameInflector::isProxyClassName + */ + public function testInflector($realClassName, $proxyClassName) + { + $inflector = new ClassNameInflector('ProxyNS'); + + $this->assertFalse($inflector->isProxyClassName($realClassName)); + $this->assertTrue($inflector->isProxyClassName($proxyClassName)); + $this->assertStringMatchesFormat($realClassName, $inflector->getUserClassName($realClassName)); + $this->assertStringMatchesFormat($proxyClassName, $inflector->getProxyClassName($proxyClassName)); + $this->assertStringMatchesFormat($proxyClassName, $inflector->getProxyClassName($realClassName)); + $this->assertStringMatchesFormat($realClassName, $inflector->getUserClassName($proxyClassName)); + } + + /** + * @covers \ProxyManager\Inflector\ClassNameInflector::getProxyClassName + */ + public function testGeneratesSameClassNameWithSameParameters() + { + $inflector = new ClassNameInflector('ProxyNS'); + + $this->assertSame($inflector->getProxyClassName('Foo\\Bar'), $inflector->getProxyClassName('Foo\\Bar')); + $this->assertSame( + $inflector->getProxyClassName('Foo\\Bar', array('baz' => 'tab')), + $inflector->getProxyClassName('Foo\\Bar', array('baz' => 'tab')) + ); + $this->assertSame( + $inflector->getProxyClassName('Foo\\Bar', array('tab' => 'baz')), + $inflector->getProxyClassName('Foo\\Bar', array('tab' => 'baz')) + ); + } + + /** + * @covers \ProxyManager\Inflector\ClassNameInflector::getProxyClassName + */ + public function testGeneratesDifferentClassNameWithDifferentParameters() + { + $inflector = new ClassNameInflector('ProxyNS'); + + $this->assertNotSame( + $inflector->getProxyClassName('Foo\\Bar'), + $inflector->getProxyClassName('Foo\\Bar', array('foo' => 'bar')) + ); + $this->assertNotSame( + $inflector->getProxyClassName('Foo\\Bar', array('baz' => 'tab')), + $inflector->getProxyClassName('Foo\\Bar', array('tab' => 'baz')) + ); + $this->assertNotSame( + $inflector->getProxyClassName('Foo\\Bar', array('foo' => 'bar', 'tab' => 'baz')), + $inflector->getProxyClassName('Foo\\Bar', array('foo' => 'bar')) + ); + $this->assertNotSame( + $inflector->getProxyClassName('Foo\\Bar', array('foo' => 'bar', 'tab' => 'baz')), + $inflector->getProxyClassName('Foo\\Bar', array('tab' => 'baz', 'foo' => 'bar')) + ); + } + + /** + * @covers \ProxyManager\Inflector\ClassNameInflector::getProxyClassName + */ + public function testGeneratesCorrectClassNameWhenGivenLeadingBackslash() + { + $inflector = new ClassNameInflector('ProxyNS'); + + $this->assertSame( + $inflector->getProxyClassName('\\Foo\\Bar', array('tab' => 'baz')), + $inflector->getProxyClassName('Foo\\Bar', array('tab' => 'baz')) + ); + } + + /** + * @covers \ProxyManager\Inflector\ClassNameInflector::getProxyClassName + * + * @dataProvider getClassAndParametersCombinations + * + * @param string $className + * @param array $parameters + */ + public function testClassNameIsValidClassIdentifier($className, array $parameters) + { + $inflector = new ClassNameInflector('ProxyNS'); + + $this->assertRegExp( + '/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]+)(\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]+)*/', + $inflector->getProxyClassName($className, $parameters), + 'Class name string is a valid class identifier' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function getClassNames() + { + return array( + array('Foo', 'ProxyNS\\' . ClassNameInflectorInterface::PROXY_MARKER . '\\Foo\\%s'), + array('Foo\\Bar', 'ProxyNS\\' . ClassNameInflectorInterface::PROXY_MARKER . '\\Foo\\Bar\\%s'), + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function getClassAndParametersCombinations() + { + return array( + array('Foo', array()), + array('Foo\\Bar', array()), + array('Foo', array(null)), + array('Foo\\Bar', array(null)), + array('Foo', array('foo' => 'bar')), + array('Foo\\Bar', array('foo' => 'bar')), + array('Foo', array("\0" => "very \0 bad")), + array('Foo\\Bar', array("\0" => "very \0 bad")), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/Util/ParameterEncoderTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/Util/ParameterEncoderTest.php new file mode 100644 index 0000000..1acd28e --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/Util/ParameterEncoderTest.php @@ -0,0 +1,66 @@ + + * @license MIT + * + * @group Coverage + */ +class ParameterEncoderTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getParameters + * + * @covers \ProxyManager\Inflector\Util\ParameterEncoder::encodeParameters + */ + public function testGeneratesValidClassName(array $parameters) + { + $encoder = new ParameterEncoder(); + + $this->assertRegExp( + '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]+/', + $encoder->encodeParameters($parameters), + 'Encoded string is a valid class identifier' + ); + } + + /** + * @return array + */ + public function getParameters() + { + return array( + array(array()), + array(array('foo' => 'bar')), + array(array('bar' => 'baz')), + array(array(null)), + array(array(null, null)), + array(array('bar' => null)), + array(array('bar' => 12345)), + array(array('foo' => 'bar', 'bar' => 'baz')), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/Util/ParameterHasherTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/Util/ParameterHasherTest.php new file mode 100644 index 0000000..22bf68d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Inflector/Util/ParameterHasherTest.php @@ -0,0 +1,62 @@ + + * @license MIT + * + * @group Coverage + */ +class ParameterHasherTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getParameters + * + * @covers \ProxyManager\Inflector\Util\ParameterHasher::hashParameters + */ + public function testGeneratesValidClassName(array $parameters, $expectedHash) + { + $encoder = new ParameterHasher(); + + $this->assertSame($expectedHash, $encoder->hashParameters($parameters)); + } + + /** + * @return array + */ + public function getParameters() + { + return array( + array(array(), '40cd750bba9870f18aada2478b24840a'), + array(array('foo' => 'bar'), '49a3696adf0fbfacc12383a2d7400d51'), + array(array('bar' => 'baz'), '6ed41c8a63c1571554ecaeb998198757'), + array(array(null), '38017a839aaeb8ff1a658fce9af6edd3'), + array(array(null, null), '12051f9a58288e5328ad748881cc4e00'), + array(array('bar' => null), '0dbb112e1c4e6e4126232de2daa2d660'), + array(array('bar' => 12345), 'eb6291ea4973741bf9b6571f49b4ffd2'), + array(array('foo' => 'bar', 'bar' => 'baz'), '4447ff857f244d24c31bd84d7a855eda'), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AbstractProxyGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AbstractProxyGeneratorTest.php new file mode 100644 index 0000000..307aafc --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AbstractProxyGeneratorTest.php @@ -0,0 +1,95 @@ + + * @license MIT + * + * @group Coverage + */ +abstract class AbstractProxyGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestedImplementations + * + * Verifies that generated code is valid and implements expected interfaces + */ + public function testGeneratesValidCode($className) + { + $generator = $this->getProxyGenerator(); + $generatedClassName = UniqueIdentifierGenerator::getIdentifier('AbstractProxyGeneratorTest'); + $generatedClass = new ClassGenerator($generatedClassName); + $originalClass = new ReflectionClass($className); + $generatorStrategy = new EvaluatingGeneratorStrategy(); + + $generator->generate($originalClass, $generatedClass); + $generatorStrategy->generate($generatedClass); + + $generatedReflection = new ReflectionClass($generatedClassName); + + if ($originalClass->isInterface()) { + $this->assertTrue($generatedReflection->implementsInterface($className)); + } else { + $this->assertSame($originalClass->getName(), $generatedReflection->getParentClass()->getName()); + } + + $this->assertSame($generatedClassName, $generatedReflection->getName()); + + foreach ($this->getExpectedImplementedInterfaces() as $interface) { + $this->assertTrue($generatedReflection->implementsInterface($interface)); + } + } + + /** + * Retrieve a new generator instance + * + * @return \ProxyManager\ProxyGenerator\ProxyGeneratorInterface + */ + abstract protected function getProxyGenerator(); + + /** + * Retrieve interfaces that should be implemented by the generated code + * + * @return string[] + */ + abstract protected function getExpectedImplementedInterfaces(); + + /** + * @return array + */ + public function getTestedImplementations() + { + return array( + array('ProxyManagerTestAsset\\BaseClass'), + array('ProxyManagerTestAsset\\ClassWithMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithByRefMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithMixedProperties'), + array('ProxyManagerTestAsset\\BaseInterface'), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/MagicWakeupTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/MagicWakeupTest.php new file mode 100644 index 0000000..a5c5bec --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/MagicWakeupTest.php @@ -0,0 +1,62 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicWakeupTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\MagicWakeup::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass( + 'ProxyManagerTestAsset\\ProxyGenerator\\LazyLoading\\MethodGenerator\\ClassWithTwoPublicProperties' + ); + + $magicWakeup = new MagicWakeup($reflection); + + $this->assertSame('__wakeup', $magicWakeup->getName()); + $this->assertCount(0, $magicWakeup->getParameters()); + $this->assertSame("unset(\$this->bar, \$this->baz);", $magicWakeup->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\MagicWakeup::__construct + */ + public function testBodyStructureWithoutPublicProperties() + { + $magicWakeup = new MagicWakeup(new ReflectionClass('ProxyManagerTestAsset\\EmptyClass')); + + $this->assertSame('__wakeup', $magicWakeup->getName()); + $this->assertCount(0, $magicWakeup->getParameters()); + $this->assertEmpty($magicWakeup->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptorTest.php new file mode 100644 index 0000000..e35c993 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodPrefixInterceptorTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @group Coverage + */ +class SetMethodPrefixInterceptorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\SetMethodPrefixInterceptor::__construct + */ + public function testBodyStructure() + { + $suffix = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $suffix->expects($this->once())->method('getName')->will($this->returnValue('foo')); + + $setter = new SetMethodPrefixInterceptor($suffix); + + $this->assertSame('setMethodPrefixInterceptor', $setter->getName()); + $this->assertCount(2, $setter->getParameters()); + $this->assertSame('$this->foo[$methodName] = $prefixInterceptor;', $setter->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptorTest.php new file mode 100644 index 0000000..db65647 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/MethodGenerator/SetMethodSuffixInterceptorTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @group Coverage + */ +class SetMethodSuffixInterceptorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptor\MethodGenerator\SetMethodSuffixInterceptor::__construct + */ + public function testBodyStructure() + { + $suffix = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $suffix->expects($this->once())->method('getName')->will($this->returnValue('foo')); + + $setter = new SetMethodSuffixInterceptor($suffix); + + $this->assertSame('setMethodSuffixInterceptor', $setter->getName()); + $this->assertCount(2, $setter->getParameters()); + $this->assertSame('$this->foo[$methodName] = $suffixInterceptor;', $setter->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptorsTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptorsTest.php new file mode 100644 index 0000000..9b6de3d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodPrefixInterceptorsTest.php @@ -0,0 +1,42 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator\MethodPrefixInterceptors + * @group Coverage + */ +class MethodPrefixInterceptorsTest extends AbstractUniquePropertyNameTest +{ + /** + * {@inheritDoc} + */ + protected function createProperty() + { + return new MethodPrefixInterceptors(); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptorsTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptorsTest.php new file mode 100644 index 0000000..7515bad --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptor/PropertyGenerator/MethodSuffixInterceptorsTest.php @@ -0,0 +1,42 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\AccessInterceptor\PropertyGenerator\MethodSuffixInterceptors + * @group Coverage + */ +class MethodSuffixInterceptorsTest extends AbstractUniquePropertyNameTest +{ + /** + * {@inheritDoc} + */ + protected function createProperty() + { + return new MethodSuffixInterceptors(); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/ConstructorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/ConstructorTest.php new file mode 100644 index 0000000..a1b5c12 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/ConstructorTest.php @@ -0,0 +1,207 @@ + + * @license MIT + * + * @group Coverage + */ +class ConstructorTest extends PHPUnit_Framework_TestCase +{ + private $prefixInterceptors; + private $suffixInterceptors; + public function setUp() + { + $this->prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $this->suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $this->prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $this->suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Constructor::__construct + */ + public function testSignature() + { + $constructor = new Constructor( + new ReflectionClass('ProxyManagerTestAsset\\ClassWithProtectedProperties'), + $this->prefixInterceptors, + $this->suffixInterceptors + ); + $this->assertSame('__construct', $constructor->getName()); + + $parameters = $constructor->getParameters(); + + $this->assertCount(3, $parameters); + + $this->assertSame( + 'ProxyManagerTestAsset\\ClassWithProtectedProperties', + $parameters['localizedObject']->getType() + ); + $this->assertSame('array', $parameters['prefixInterceptors']->getType()); + $this->assertSame('array', $parameters['suffixInterceptors']->getType()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Constructor::__construct + */ + public function testBodyStructure() + { + $constructor = new Constructor( + new ReflectionClass('ProxyManagerTestAsset\\ClassWithPublicProperties'), + $this->prefixInterceptors, + $this->suffixInterceptors + ); + + $this->assertSame( + '$this->property0 = & $localizedObject->property0; + +$this->property1 = & $localizedObject->property1; + +$this->property2 = & $localizedObject->property2; + +$this->property3 = & $localizedObject->property3; + +$this->property4 = & $localizedObject->property4; + +$this->property5 = & $localizedObject->property5; + +$this->property6 = & $localizedObject->property6; + +$this->property7 = & $localizedObject->property7; + +$this->property8 = & $localizedObject->property8; + +$this->property9 = & $localizedObject->property9; + +$this->pre = $prefixInterceptors; +$this->post = $suffixInterceptors;', + $constructor->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Constructor::__construct + */ + public function testBodyStructureWithProtectedProperties() + { + $constructor = new Constructor( + new ReflectionClass('ProxyManagerTestAsset\\ClassWithProtectedProperties'), + $this->prefixInterceptors, + $this->suffixInterceptors + ); + + $this->assertSame( + '$this->property0 = & $localizedObject->property0; + +$this->property1 = & $localizedObject->property1; + +$this->property2 = & $localizedObject->property2; + +$this->property3 = & $localizedObject->property3; + +$this->property4 = & $localizedObject->property4; + +$this->property5 = & $localizedObject->property5; + +$this->property6 = & $localizedObject->property6; + +$this->property7 = & $localizedObject->property7; + +$this->property8 = & $localizedObject->property8; + +$this->property9 = & $localizedObject->property9; + +$this->pre = $prefixInterceptors; +$this->post = $suffixInterceptors;', + $constructor->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Constructor::__construct + */ + public function testBodyStructureWithPrivateProperties() + { + if (! method_exists('Closure', 'bind')) { + $this->setExpectedException('ProxyManager\Exception\UnsupportedProxiedClassException'); + } + + $constructor = new Constructor( + new ReflectionClass('ProxyManagerTestAsset\\ClassWithPrivateProperties'), + $this->prefixInterceptors, + $this->suffixInterceptors + ); + + $this->assertSame( + '\Closure::bind(function () use ($localizedObject) { + $this->property0 = & $localizedObject->property0; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property1 = & $localizedObject->property1; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property2 = & $localizedObject->property2; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property3 = & $localizedObject->property3; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property4 = & $localizedObject->property4; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property5 = & $localizedObject->property5; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property6 = & $localizedObject->property6; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property7 = & $localizedObject->property7; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property8 = & $localizedObject->property8; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +\Closure::bind(function () use ($localizedObject) { + $this->property9 = & $localizedObject->property9; +}, $this, \'ProxyManagerTestAsset\\\\ClassWithPrivateProperties\')->__invoke(); + +$this->pre = $prefixInterceptors; +$this->post = $suffixInterceptors;', + $constructor->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethodTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethodTest.php new file mode 100644 index 0000000..207359f --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/InterceptedMethodTest.php @@ -0,0 +1,62 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\InterceptedMethod + * @group Coverage + */ +class InterceptedMethodTest extends PHPUnit_Framework_TestCase +{ + public function testBodyStructure() + { + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $method = InterceptedMethod::generateMethod( + new MethodReflection('ProxyManagerTestAsset\\BaseClass', 'publicByReferenceParameterMethod'), + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertInstanceOf('ProxyManager\\Generator\\MethodGenerator', $method); + + $this->assertSame('publicByReferenceParameterMethod', $method->getName()); + $this->assertCount(2, $method->getParameters()); + $this->assertGreaterThan( + 0, + strpos( + $method->getBody(), + '$returnValue = parent::publicByReferenceParameterMethod($param, $byRefParam);' + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicCloneTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicCloneTest.php new file mode 100644 index 0000000..df4f49e --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicCloneTest.php @@ -0,0 +1,72 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicCloneTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicClone::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicClone = new MagicClone($reflection, $prefixInterceptors, $suffixInterceptors); + + $this->assertSame('__clone', $magicClone->getName()); + $this->assertCount(0, $magicClone->getParameters()); + $this->assertStringMatchesFormat("%a\n\n\$returnValue = null;\n\n%a", $magicClone->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicClone::__construct + */ + public function testBodyStructureWithInheritedMethod() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicClone = new MagicClone($reflection, $prefixInterceptors, $suffixInterceptors); + + $this->assertSame('__clone', $magicClone->getName()); + $this->assertCount(0, $magicClone->getParameters()); + $this->assertStringMatchesFormat("%a\n\n\$returnValue = parent::__clone();\n\n%a", $magicClone->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGetTest.php new file mode 100644 index 0000000..ed67bb0 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicGetTest.php @@ -0,0 +1,80 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicGetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicGet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicGet( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__get', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = & $accessor();%a', $magicGet->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicGet::__construct + */ + public function testBodyStructureWithInheritedMethod() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicGet( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__get', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = & parent::__get($name);%a', $magicGet->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIssetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIssetTest.php new file mode 100644 index 0000000..0dabe4c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicIssetTest.php @@ -0,0 +1,80 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicIssetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicIsset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicIsset( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__isset', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = $accessor();%a', $magicGet->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicIsset::__construct + */ + public function testBodyStructureWithInheritedMethod() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicIsset( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__isset', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = & parent::__isset($name);%a', $magicGet->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSetTest.php new file mode 100644 index 0000000..25d3070 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSetTest.php @@ -0,0 +1,80 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicSet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicSet( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__set', $magicGet->getName()); + $this->assertCount(2, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = & $accessor();%a', $magicGet->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicSet::__construct + */ + public function testBodyStructureWithInheritedMethod() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicSet( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__set', $magicGet->getName()); + $this->assertCount(2, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = & parent::__set($name, $value);%a', $magicGet->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleepTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleepTest.php new file mode 100644 index 0000000..98e401b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicSleepTest.php @@ -0,0 +1,80 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSleepTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicSleep::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicSleep( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__sleep', $magicGet->getName()); + $this->assertEmpty($magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = array_keys((array) $this);%a', $magicGet->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicSleep::__construct + */ + public function testBodyStructureWithInheritedMethod() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicSleep( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__sleep', $magicGet->getName()); + $this->assertEmpty($magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = & parent::__sleep();%a', $magicGet->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnsetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnsetTest.php new file mode 100644 index 0000000..822fda5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/MagicUnsetTest.php @@ -0,0 +1,80 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicUnsetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicUnset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicUnset( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__unset', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = $accessor();%a', $magicGet->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\MagicUnset::__construct + */ + public function testBodyStructureWithInheritedMethod() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicGet = new MagicUnset( + $reflection, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame('__unset', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%a$returnValue = & parent::__unset($name);%a', $magicGet->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGeneratorTest.php new file mode 100644 index 0000000..7e54c70 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizer/MethodGenerator/Util/InterceptorGeneratorTest.php @@ -0,0 +1,81 @@ + + * @license MIT + * + * @group Coverage + */ +class InterceptorGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizer\MethodGenerator\Util\InterceptorGenerator + */ + public function testInterceptorGenerator() + { + $method = $this->getMock('ProxyManager\\Generator\\MethodGenerator'); + $bar = $this->getMock('ProxyManager\\Generator\\ParameterGenerator'); + $baz = $this->getMock('ProxyManager\\Generator\\ParameterGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $bar->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $baz->expects($this->any())->method('getName')->will($this->returnValue('baz')); + $method->expects($this->any())->method('getName')->will($this->returnValue('fooMethod')); + $method->expects($this->any())->method('getParameters')->will($this->returnValue(array($bar, $baz))); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $body = InterceptorGenerator::createInterceptedMethodBody( + '$returnValue = "foo";', + $method, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame( + 'if (isset($this->pre[\'fooMethod\'])) {' . "\n" + . ' $returnEarly = false;' . "\n" + . ' $prefixReturnValue = $this->pre[\'fooMethod\']->__invoke($this, $this, \'fooMethod\', ' + . 'array(\'bar\' => $bar, \'baz\' => $baz), $returnEarly);' . "\n\n" + . ' if ($returnEarly) {' . "\n" + . ' return $prefixReturnValue;' . "\n" + . ' }' . "\n" + . '}' . "\n\n" + . '$returnValue = "foo";' . "\n\n" + . 'if (isset($this->post[\'fooMethod\'])) {' . "\n" + . ' $returnEarly = false;' . "\n" + . ' $suffixReturnValue = $this->post[\'fooMethod\']->__invoke($this, $this, \'fooMethod\', ' + . 'array(\'bar\' => $bar, \'baz\' => $baz), $returnValue, $returnEarly);' . "\n\n" + . ' if ($returnEarly) {' . "\n" + . ' return $suffixReturnValue;' . "\n" + . ' }' . "\n" + . '}' . "\n\n" + . 'return $returnValue;', + $body + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizerTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizerTest.php new file mode 100644 index 0000000..bd6af22 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorScopeLocalizerTest.php @@ -0,0 +1,76 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorScopeLocalizerGenerator + * @group Coverage + */ +class AccessInterceptorScopeLocalizerTest extends AbstractProxyGeneratorTest +{ + /** + * @dataProvider getTestedImplementations + * + * {@inheritDoc} + */ + public function testGeneratesValidCode($className) + { + $reflectionClass = new ReflectionClass($className); + + if ($reflectionClass->isInterface()) { + // @todo interfaces *may* be proxied by deferring property localization to the constructor (no hardcoding) + $this->setExpectedException('ProxyManager\Exception\InvalidProxiedClassException'); + + return parent::testGeneratesValidCode($className); + } + + if ((! method_exists('Closure', 'bind')) + && $reflectionClass->getProperties(ReflectionProperty::IS_PRIVATE) + ) { + $this->setExpectedException('ProxyManager\Exception\UnsupportedProxiedClassException'); + } + + return parent::testGeneratesValidCode($className); + } + + /** + * {@inheritDoc} + */ + protected function getProxyGenerator() + { + return new AccessInterceptorScopeLocalizerGenerator(); + } + + /** + * {@inheritDoc} + */ + protected function getExpectedImplementedInterfaces() + { + return array('ProxyManager\\Proxy\\AccessInterceptorInterface'); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/LazyLoading/MethodGenerator/ConstructorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/LazyLoading/MethodGenerator/ConstructorTest.php new file mode 100644 index 0000000..e4209c1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/LazyLoading/MethodGenerator/ConstructorTest.php @@ -0,0 +1,69 @@ + + * @license MIT + * + * @group Coverage + */ +class ConstructorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoading\MethodGenerator\Constructor::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $reflection = new ReflectionClass( + 'ProxyManagerTestAsset\\ProxyGenerator\\LazyLoading\\MethodGenerator\\ClassWithTwoPublicProperties' + ); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $constructor = new Constructor($reflection, $initializer); + + $this->assertSame('__construct', $constructor->getName()); + $this->assertCount(1, $constructor->getParameters()); + $this->assertSame("unset(\$this->bar, \$this->baz);\n\n\$this->foo = \$initializer;", $constructor->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoading\MethodGenerator\Constructor::__construct + */ + public function testBodyStructureWithoutPublicProperties() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $constructor = new Constructor(new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'), $initializer); + + $this->assertSame('__construct', $constructor->getName()); + $this->assertCount(1, $constructor->getParameters()); + $this->assertSame("\$this->foo = \$initializer;", $constructor->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/ConstructorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/ConstructorTest.php new file mode 100644 index 0000000..97f7175 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/ConstructorTest.php @@ -0,0 +1,86 @@ + + * @license MIT + * + * @group Coverage + */ +class ConstructorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Constructor::__construct + */ + public function testBodyStructure() + { + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $reflection = new ReflectionClass( + 'ProxyManagerTestAsset\\ProxyGenerator\\LazyLoading\\MethodGenerator\\ClassWithTwoPublicProperties' + ); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $constructor = new Constructor($reflection, $valueHolder, $prefixInterceptors, $suffixInterceptors); + + $this->assertSame('__construct', $constructor->getName()); + $this->assertCount(3, $constructor->getParameters()); + $this->assertSame( + "unset(\$this->bar, \$this->baz);\n\n\$this->foo = \$wrappedObject;\n\$this->pre = \$prefixInterceptors;" + . "\n\$this->post = \$suffixInterceptors;", + $constructor->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Constructor::__construct + */ + public function testBodyStructureWithoutPublicProperties() + { + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $constructor = new Constructor($reflection, $valueHolder, $prefixInterceptors, $suffixInterceptors); + + $this->assertSame('__construct', $constructor->getName()); + $this->assertCount(3, $constructor->getParameters()); + $this->assertSame( + "\$this->foo = \$wrappedObject;\n\$this->pre = \$prefixInterceptors;" + . "\n\$this->post = \$suffixInterceptors;", + $constructor->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethodTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethodTest.php new file mode 100644 index 0000000..41b5bfb --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/InterceptedMethodTest.php @@ -0,0 +1,65 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\InterceptedMethod::generateMethod + * @group Coverage + */ +class InterceptedMethodTest extends PHPUnit_Framework_TestCase +{ + public function testBodyStructure() + { + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $method = InterceptedMethod::generateMethod( + new MethodReflection('ProxyManagerTestAsset\\BaseClass', 'publicByReferenceParameterMethod'), + $valueHolder, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertInstanceOf('ProxyManager\\Generator\\MethodGenerator', $method); + + $this->assertSame('publicByReferenceParameterMethod', $method->getName()); + $this->assertCount(2, $method->getParameters()); + $this->assertGreaterThan( + 0, + strpos( + $method->getBody(), + '$returnValue = $this->foo->publicByReferenceParameterMethod($param, $byRefParam);' + ) + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicCloneTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicCloneTest.php new file mode 100644 index 0000000..72a7763 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicCloneTest.php @@ -0,0 +1,64 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicCloneTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicClone::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $magicClone = new MagicClone($reflection, $valueHolder, $prefixInterceptors, $suffixInterceptors); + + $this->assertSame('__clone', $magicClone->getName()); + $this->assertCount(0, $magicClone->getParameters()); + $this->assertSame( + '$this->bar = clone $this->bar;' . "\n\n" + . 'foreach ($this->pre as $key => $value) {' . "\n" + . ' $this->pre[$key] = clone $value;' . "\n" + . '}' . "\n\n" + . 'foreach ($this->post as $key => $value) {' . "\n" + . ' $this->post[$key] = clone $value;' . "\n" + . '}', + $magicClone->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGetTest.php new file mode 100644 index 0000000..0ca38de --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicGetTest.php @@ -0,0 +1,66 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicGetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicGet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + $publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + + $magicGet = new MagicGet( + $reflection, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors, + $publicProperties + ); + + $this->assertSame('__get', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat('%A$returnValue = & $this->bar->$name;%A', $magicGet->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIssetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIssetTest.php new file mode 100644 index 0000000..5f34baf --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicIssetTest.php @@ -0,0 +1,66 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicIssetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicIsset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + $publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + + $magicIsset = new MagicIsset( + $reflection, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors, + $publicProperties + ); + + $this->assertSame('__isset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertGreaterThan(0, strpos($magicIsset->getBody(), '$returnValue = isset($this->bar->$name);')); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSetTest.php new file mode 100644 index 0000000..93da0b9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicSetTest.php @@ -0,0 +1,66 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicSet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + $publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + + $magicSet = new MagicSet( + $reflection, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors, + $publicProperties + ); + + $this->assertSame('__set', $magicSet->getName()); + $this->assertCount(2, $magicSet->getParameters()); + $this->assertGreaterThan(0, strpos($magicSet->getBody(), '$returnValue = ($this->bar->$name = $value);')); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnsetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnsetTest.php new file mode 100644 index 0000000..0433ff6 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/MagicUnsetTest.php @@ -0,0 +1,69 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicUnsetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\MagicUnset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + $publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + + $magicUnset = new MagicUnset( + $reflection, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors, + $publicProperties + ); + + $this->assertSame('__unset', $magicUnset->getName()); + $this->assertCount(1, $magicUnset->getParameters()); + $this->assertGreaterThan( + 0, + strpos($magicUnset->getBody(), 'unset($this->bar->$name);') + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGeneratorTest.php new file mode 100644 index 0000000..bb5c461 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolder/MethodGenerator/Util/InterceptorGeneratorTest.php @@ -0,0 +1,84 @@ + + * @license MIT + * + * @group Coverage + */ +class InterceptorGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolder\MethodGenerator\Util\InterceptorGenerator + */ + public function testInterceptorGenerator() + { + $method = $this->getMock('ProxyManager\\Generator\\MethodGenerator'); + $bar = $this->getMock('ProxyManager\\Generator\\ParameterGenerator'); + $baz = $this->getMock('ProxyManager\\Generator\\ParameterGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $prefixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $suffixInterceptors = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $bar->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $baz->expects($this->any())->method('getName')->will($this->returnValue('baz')); + $method->expects($this->any())->method('getName')->will($this->returnValue('fooMethod')); + $method->expects($this->any())->method('getParameters')->will($this->returnValue(array($bar, $baz))); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $prefixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('pre')); + $suffixInterceptors->expects($this->any())->method('getName')->will($this->returnValue('post')); + + $body = InterceptorGenerator::createInterceptedMethodBody( + '$returnValue = "foo";', + $method, + $valueHolder, + $prefixInterceptors, + $suffixInterceptors + ); + + $this->assertSame( + 'if (isset($this->pre[\'fooMethod\'])) {' . "\n" + . ' $returnEarly = false;' . "\n" + . ' $prefixReturnValue = $this->pre[\'fooMethod\']->__invoke($this, $this->foo, \'fooMethod\', ' + . 'array(\'bar\' => $bar, \'baz\' => $baz), $returnEarly);' . "\n\n" + . ' if ($returnEarly) {' . "\n" + . ' return $prefixReturnValue;' . "\n" + . ' }' . "\n" + . '}' . "\n\n" + . '$returnValue = "foo";' . "\n\n" + . 'if (isset($this->post[\'fooMethod\'])) {' . "\n" + . ' $returnEarly = false;' . "\n" + . ' $suffixReturnValue = $this->post[\'fooMethod\']->__invoke($this, $this->foo, \'fooMethod\', ' + . 'array(\'bar\' => $bar, \'baz\' => $baz), $returnValue, $returnEarly);' . "\n\n" + . ' if ($returnEarly) {' . "\n" + . ' return $suffixReturnValue;' . "\n" + . ' }' . "\n" + . '}' . "\n\n" + . 'return $returnValue;', + $body + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolderTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolderTest.php new file mode 100644 index 0000000..1b7d806 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/AccessInterceptorValueHolderTest.php @@ -0,0 +1,52 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\AccessInterceptorValueHolderGenerator + * @group Coverage + */ +class AccessInterceptorValueHolderTest extends AbstractProxyGeneratorTest +{ + /** + * {@inheritDoc} + */ + protected function getProxyGenerator() + { + return new AccessInterceptorValueHolderGenerator(); + } + + /** + * {@inheritDoc} + */ + protected function getExpectedImplementedInterfaces() + { + return array( + 'ProxyManager\\Proxy\\AccessInterceptorInterface', + 'ProxyManager\\Proxy\\ValueHolderInterface', + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Assertion/CanProxyAssertionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Assertion/CanProxyAssertionTest.php new file mode 100644 index 0000000..1c9003f --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Assertion/CanProxyAssertionTest.php @@ -0,0 +1,113 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\Assertion\CanProxyAssertion + * @group Coverage + */ +class CanProxyAssertionTest extends PHPUnit_Framework_TestCase +{ + public function testDeniesFinalClasses() + { + $this->setExpectedException('ProxyManager\Exception\InvalidProxiedClassException'); + + CanProxyAssertion::assertClassCanBeProxied(new ReflectionClass('ProxyManagerTestAsset\\FinalClass')); + } + + public function testDeniesClassesWithAbstractProtectedMethods() + { + $this->setExpectedException('ProxyManager\Exception\InvalidProxiedClassException'); + + CanProxyAssertion::assertClassCanBeProxied(new ReflectionClass( + 'ProxyManagerTestAsset\\ClassWithAbstractProtectedMethod' + )); + } + + public function testAllowsInterfaceByDefault() + { + CanProxyAssertion::assertClassCanBeProxied(new ReflectionClass( + 'ProxyManagerTestAsset\\BaseInterface' + )); + + $this->assertTrue(true); // not nice, but assertions are just fail-checks, no real code executed + } + + public function testDeniesInterfaceIfSpecified() + { + $this->setExpectedException('ProxyManager\Exception\InvalidProxiedClassException'); + + CanProxyAssertion::assertClassCanBeProxied(new ReflectionClass('ProxyManagerTestAsset\\BaseInterface'), false); + } + + /** + * @param string $className + * + * @dataProvider validClasses + */ + public function testAllowedClass($className) + { + CanProxyAssertion::assertClassCanBeProxied(new ReflectionClass($className)); + + $this->assertTrue(true); // not nice, but assertions are just fail-checks, no real code executed + } + + public function testDisallowsConstructor() + { + $this->setExpectedException('BadMethodCallException'); + + new CanProxyAssertion(); + } + + /** + * @return string[][] + */ + public function validClasses() + { + return array( + array('ProxyManagerTestAsset\AccessInterceptorValueHolderMock'), + array('ProxyManagerTestAsset\BaseClass'), + array('ProxyManagerTestAsset\BaseInterface'), + array('ProxyManagerTestAsset\CallableTypeHintClass'), + array('ProxyManagerTestAsset\ClassWithByRefMagicMethods'), + array('ProxyManagerTestAsset\ClassWithFinalMagicMethods'), + array('ProxyManagerTestAsset\ClassWithFinalMethods'), + array('ProxyManagerTestAsset\ClassWithMethodWithDefaultParameters'), + array('ProxyManagerTestAsset\ClassWithMixedProperties'), + array('ProxyManagerTestAsset\ClassWithPrivateProperties'), + array('ProxyManagerTestAsset\ClassWithProtectedProperties'), + array('ProxyManagerTestAsset\ClassWithPublicProperties'), + array('ProxyManagerTestAsset\ClassWithPublicArrayProperty'), + array('ProxyManagerTestAsset\ClassWithSelfHint'), + array('ProxyManagerTestAsset\EmptyClass'), + array('ProxyManagerTestAsset\HydratedObject'), + array('ProxyManagerTestAsset\LazyLoadingMock'), + array('ProxyManagerTestAsset\NullObjectMock'), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializerTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializerTest.php new file mode 100644 index 0000000..3fbd81d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/CallInitializerTest.php @@ -0,0 +1,59 @@ + + * @license MIT + * + * @group Coverage + */ +class CallInitializerTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\CallInitializer::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $propertiesDefaults = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $initializationTracker = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('init')); + $propertiesDefaults->expects($this->any())->method('getName')->will($this->returnValue('props')); + $initializationTracker->expects($this->any())->method('getName')->will($this->returnValue('track')); + + $callInitializer = new CallInitializer($initializer, $propertiesDefaults, $initializationTracker); + + $this->assertStringMatchesFormat( + '%Aif ($this->track || ! $this->init) {%areturn;%a}%a' + . '$this->track = true;%a' + . 'foreach (self::$props as $key => $default) {%a' + . '$this->$key = $default;%a' + . '$this->init->__invoke(%a);%a' + . '$this->track = false;', + $callInitializer->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializerTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializerTest.php new file mode 100644 index 0000000..32936b9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/GetProxyInitializerTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @group Coverage + */ +class GetProxyInitializerTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\GetProxyInitializer::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $getter = new GetProxyInitializer($initializer); + + $this->assertSame('getProxyInitializer', $getter->getName()); + $this->assertCount(0, $getter->getParameters()); + $this->assertSame('return $this->foo;', $getter->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxyTest.php new file mode 100644 index 0000000..27e1e2d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/InitializeProxyTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class InitializeProxyTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\InitializeProxy::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $initCall = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $initCall->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $initializeProxy = new InitializeProxy($initializer, $initCall); + + $this->assertSame('initializeProxy', $initializeProxy->getName()); + $this->assertCount(0, $initializeProxy->getParameters()); + $this->assertSame( + 'return $this->foo && $this->bar(\'initializeProxy\', array());', + $initializeProxy->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitializedTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitializedTest.php new file mode 100644 index 0000000..f82dae9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/IsProxyInitializedTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @group Coverage + */ +class IsProxyInitializedTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\IsProxyInitialized::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $isProxyInitialized = new IsProxyInitialized($initializer); + + $this->assertSame('isProxyInitialized', $isProxyInitialized->getName()); + $this->assertCount(0, $isProxyInitialized->getParameters()); + $this->assertSame('return ! $this->foo;', $isProxyInitialized->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/LazyLoadingMethodInterceptorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/LazyLoadingMethodInterceptorTest.php new file mode 100644 index 0000000..db5e3af --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/LazyLoadingMethodInterceptorTest.php @@ -0,0 +1,81 @@ + + * @license MIT + * + * @group Coverage + */ +class LazyLoadingMethodInterceptorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\LazyLoadingMethodInterceptor + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $initCall = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $initCall->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $reflection = new MethodReflection('ProxyManagerTestAsset\\BaseClass', 'publicByReferenceParameterMethod'); + $method = LazyLoadingMethodInterceptor::generateMethod($reflection, $initializer, $initCall); + + $this->assertSame('publicByReferenceParameterMethod', $method->getName()); + $this->assertCount(2, $method->getParameters()); + $this->assertSame( + "\$this->foo && \$this->bar('publicByReferenceParameterMethod', " + . "array('param' => \$param, 'byRefParam' => \$byRefParam));\n\n" + . "return parent::publicByReferenceParameterMethod(\$param, \$byRefParam);", + $method->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\LazyLoadingMethodInterceptor + */ + public function testBodyStructureWithoutParameters() + { + $reflectionMethod = new MethodReflection(__CLASS__, 'testBodyStructureWithoutParameters'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $initCall = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $initCall->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $method = LazyLoadingMethodInterceptor::generateMethod($reflectionMethod, $initializer, $initCall); + + $this->assertSame('testBodyStructureWithoutParameters', $method->getName()); + $this->assertCount(0, $method->getParameters()); + $this->assertSame( + "\$this->foo && \$this->bar('testBodyStructureWithoutParameters', array());\n\n" + . "return parent::testBodyStructureWithoutParameters();", + $method->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicCloneTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicCloneTest.php new file mode 100644 index 0000000..4cb2271 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicCloneTest.php @@ -0,0 +1,56 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicCloneTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicClone::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $initCall = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $initCall->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicClone = new MagicClone($reflection, $initializer, $initCall); + + $this->assertSame('__clone', $magicClone->getName()); + $this->assertCount(0, $magicClone->getParameters()); + $this->assertSame( + "\$this->foo && \$this->bar('__clone', array());", + $magicClone->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicGetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicGetTest.php new file mode 100644 index 0000000..6f2a7c0 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicGetTest.php @@ -0,0 +1,124 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicGetTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \Zend\Code\Generator\PropertyGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $initializer; + + /** + * @var \Zend\Code\Generator\MethodGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $initMethod; + + /** + * @var \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap|\PHPUnit_Framework_MockObject_MockObject + */ + protected $publicProperties; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $this->initMethod = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + $this->publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $this->initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $this->initMethod->expects($this->any())->method('getName')->will($this->returnValue('baz')); + $this->publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + $this->publicProperties->expects($this->any())->method('getName')->will($this->returnValue('bar')); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicGet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $magicGet = new MagicGet($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__get', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->baz('__get', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return \$this->\$name;\n}\n\n" + . "%a", + $magicGet->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicGet::__construct + */ + public function testBodyStructureWithPublicProperties() + { + $reflection = new ReflectionClass( + 'ProxyManagerTestAsset\\ProxyGenerator\\LazyLoading\\MethodGenerator\\ClassWithTwoPublicProperties' + ); + + $magicGet = new MagicGet($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__get', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->baz('__get', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return \$this->\$name;\n}\n\n" + . "%a", + $magicGet->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicGet::__construct + */ + public function testBodyStructureWithOverriddenMagicGet() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $magicGet = new MagicGet($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__get', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertSame( + "\$this->foo && \$this->baz('__get', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return \$this->\$name;\n}\n\n" + . "return parent::__get(\$name);", + $magicGet->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIssetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIssetTest.php new file mode 100644 index 0000000..fa9bb4b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicIssetTest.php @@ -0,0 +1,123 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicIssetTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \Zend\Code\Generator\PropertyGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $initializer; + + /** + * @var \Zend\Code\Generator\MethodGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $initMethod; + + /** + * @var \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap|\PHPUnit_Framework_MockObject_MockObject + */ + protected $publicProperties; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $this->initMethod = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + $this->publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $this->initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $this->initMethod->expects($this->any())->method('getName')->will($this->returnValue('baz')); + $this->publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + $this->publicProperties->expects($this->any())->method('getName')->will($this->returnValue('bar')); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicIsset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $magicIsset = new MagicIsset($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__isset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->baz('__isset', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return isset(\$this->\$name);\n}\n\n" + . "%areturn %s;", + $magicIsset->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicIsset::__construct + */ + public function testBodyStructureWithPublicProperties() + { + $reflection = new ReflectionClass( + 'ProxyManagerTestAsset\\ProxyGenerator\\LazyLoading\\MethodGenerator\\ClassWithTwoPublicProperties' + ); + $magicIsset = new MagicIsset($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__isset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->baz('__isset', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return isset(\$this->\$name);\n}\n\n" + . "%areturn %s;", + $magicIsset->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicIsset::__construct + */ + public function testBodyStructureWithOverriddenMagicGet() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $magicIsset = new MagicIsset($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__isset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertSame( + "\$this->foo && \$this->baz('__isset', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return isset(\$this->\$name);\n}\n\n" + . "return parent::__isset(\$name);", + $magicIsset->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSetTest.php new file mode 100644 index 0000000..00b7ad9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSetTest.php @@ -0,0 +1,124 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSetTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \Zend\Code\Generator\PropertyGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $initializer; + + /** + * @var \Zend\Code\Generator\MethodGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $initMethod; + + /** + * @var \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap|\PHPUnit_Framework_MockObject_MockObject + */ + protected $publicProperties; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $this->initMethod = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + $this->publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $this->initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $this->initMethod->expects($this->any())->method('getName')->will($this->returnValue('baz')); + $this->publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + $this->publicProperties->expects($this->any())->method('getName')->will($this->returnValue('bar')); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicSet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $magicSet = new MagicSet($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__set', $magicSet->getName()); + $this->assertCount(2, $magicSet->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->baz('__set', array('name' => \$name, 'value' => \$value));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return (\$this->\$name = \$value);\n}\n\n" + . "%areturn %s;", + $magicSet->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicSet::__construct + */ + public function testBodyStructureWithPublicProperties() + { + $reflection = new ReflectionClass( + 'ProxyManagerTestAsset\\ProxyGenerator\\LazyLoading\\MethodGenerator\\ClassWithTwoPublicProperties' + ); + + $magicSet = new MagicSet($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__set', $magicSet->getName()); + $this->assertCount(2, $magicSet->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->baz('__set', array('name' => \$name, 'value' => \$value));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return (\$this->\$name = \$value);\n}\n\n" + . "%areturn %s;", + $magicSet->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicSet::__construct + */ + public function testBodyStructureWithOverriddenMagicGet() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $magicSet = new MagicSet($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__set', $magicSet->getName()); + $this->assertCount(2, $magicSet->getParameters()); + $this->assertSame( + "\$this->foo && \$this->baz('__set', array('name' => \$name, 'value' => \$value));\n\n" + . "if (isset(self::\$bar[\$name])) {\n return (\$this->\$name = \$value);\n}\n\n" + . "return parent::__set(\$name, \$value);", + $magicSet->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleepTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleepTest.php new file mode 100644 index 0000000..7329e61 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicSleepTest.php @@ -0,0 +1,57 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSleepTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicSleep::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $initMethod = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $initMethod->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicSleep = new MagicSleep($reflection, $initializer, $initMethod); + + $this->assertSame('__sleep', $magicSleep->getName()); + $this->assertCount(0, $magicSleep->getParameters()); + $this->assertSame( + "\$this->foo && \$this->bar('__sleep', array());" + . "\n\nreturn array_keys((array) \$this);", + $magicSleep->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnsetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnsetTest.php new file mode 100644 index 0000000..5e3ba4e --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/MagicUnsetTest.php @@ -0,0 +1,124 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicUnsetTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \Zend\Code\Generator\PropertyGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $initializer; + + /** + * @var \Zend\Code\Generator\MethodGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + protected $initMethod; + + /** + * @var \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap|\PHPUnit_Framework_MockObject_MockObject + */ + protected $publicProperties; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $this->initMethod = $this->getMock('Zend\\Code\\Generator\\MethodGenerator'); + $this->publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $this->initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $this->initMethod->expects($this->any())->method('getName')->will($this->returnValue('baz')); + $this->publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + $this->publicProperties->expects($this->any())->method('getName')->will($this->returnValue('bar')); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicUnset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $magicIsset = new MagicUnset($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__unset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->baz('__unset', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n unset(\$this->\$name);\n\n return;\n}" + . "%areturn %s;", + $magicIsset->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicUnset::__construct + */ + public function testBodyStructureWithPublicProperties() + { + $reflection = new ReflectionClass( + 'ProxyManagerTestAsset\\ProxyGenerator\\LazyLoading\\MethodGenerator\\ClassWithTwoPublicProperties' + ); + + $magicIsset = new MagicUnset($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__unset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->baz('__unset', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n unset(\$this->\$name);\n\n return;\n}" + . "%areturn %s;", + $magicIsset->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\MagicUnset::__construct + */ + public function testBodyStructureWithOverriddenMagicGet() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMagicMethods'); + $magicIsset = new MagicUnset($reflection, $this->initializer, $this->initMethod, $this->publicProperties); + + $this->assertSame('__unset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertSame( + "\$this->foo && \$this->baz('__unset', array('name' => \$name));\n\n" + . "if (isset(self::\$bar[\$name])) {\n unset(\$this->\$name);\n\n return;\n}\n\n" + . "return parent::__unset(\$name);", + $magicIsset->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializerTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializerTest.php new file mode 100644 index 0000000..71fcdbf --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/MethodGenerator/SetProxyInitializerTest.php @@ -0,0 +1,56 @@ + + * @license MIT + * + * @group Coverage + */ +class SetProxyInitializerTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\MethodGenerator\SetProxyInitializer::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $setter = new SetProxyInitializer($initializer); + $parameters = $setter->getParameters(); + + $this->assertSame('setProxyInitializer', $setter->getName()); + $this->assertCount(1, $parameters); + + /* @var $initializer \ProxyManager\Generator\ParameterGenerator */ + $initializer = array_shift($parameters); + + $this->assertInstanceOf('ProxyManager\\Generator\\ParameterGenerator', $initializer); + $this->assertSame('initializer', $initializer->getName()); + $this->assertSame('$this->foo = $initializer;', $setter->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTrackerTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTrackerTest.php new file mode 100644 index 0000000..62caf49 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializationTrackerTest.php @@ -0,0 +1,42 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\InitializationTracker + * @group Coverage + */ +class InitializationTrackerTest extends AbstractUniquePropertyNameTest +{ + /** + * {@inheritDoc} + */ + protected function createProperty() + { + return new InitializationTracker(); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerPropertyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerPropertyTest.php new file mode 100644 index 0000000..c5f1ea1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhost/PropertyGenerator/InitializerPropertyTest.php @@ -0,0 +1,42 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhost\PropertyGenerator\InitializerProperty + * @group Coverage + */ +class InitializerPropertyTest extends AbstractUniquePropertyNameTest +{ + /** + * {@inheritDoc} + */ + protected function createProperty() + { + return new InitializerProperty(); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhostGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhostGeneratorTest.php new file mode 100644 index 0000000..8eb5443 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingGhostGeneratorTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\LazyLoadingGhostGenerator + * @group Coverage + */ +class LazyLoadingGhostGeneratorTest extends AbstractProxyGeneratorTest +{ + /** + * {@inheritDoc} + */ + protected function getProxyGenerator() + { + return new LazyLoadingGhostGenerator(); + } + + /** + * {@inheritDoc} + */ + protected function getExpectedImplementedInterfaces() + { + return array('ProxyManager\\Proxy\\GhostObjectInterface'); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializerTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializerTest.php new file mode 100644 index 0000000..31fd029 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/GetProxyInitializerTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @group Coverage + */ +class GetProxyInitializerTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\GetProxyInitializer::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $getter = new GetProxyInitializer($initializer); + + $this->assertSame('getProxyInitializer', $getter->getName()); + $this->assertCount(0, $getter->getParameters()); + $this->assertSame('return $this->foo;', $getter->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxyTest.php new file mode 100644 index 0000000..dfbcb4c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/InitializeProxyTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class InitializeProxyTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\InitializeProxy::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $initializeProxy = new InitializeProxy($initializer, $valueHolder); + + $this->assertSame('initializeProxy', $initializeProxy->getName()); + $this->assertCount(0, $initializeProxy->getParameters()); + $this->assertSame( + 'return $this->foo && $this->foo->__invoke($this->bar, $this, \'initializeProxy\', array(), $this->foo);', + $initializeProxy->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitializedTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitializedTest.php new file mode 100644 index 0000000..2b65f28 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/IsProxyInitializedTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @group Coverage + */ +class IsProxyInitializedTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\IsProxyInitialized::__construct + */ + public function testBodyStructure() + { + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $isProxyInitialized = new IsProxyInitialized($valueHolder); + + $this->assertSame('isProxyInitialized', $isProxyInitialized->getName()); + $this->assertCount(0, $isProxyInitialized->getParameters()); + $this->assertSame('return null !== $this->bar;', $isProxyInitialized->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptorTest.php new file mode 100644 index 0000000..f6f5d0c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/LazyLoadingMethodInterceptorTest.php @@ -0,0 +1,84 @@ + + * @license MIT + * + * @group Coverage + */ +class LazyLoadingMethodInterceptorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\LazyLoadingMethodInterceptor + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $reflection = new MethodReflection('ProxyManagerTestAsset\\BaseClass', 'publicByReferenceParameterMethod'); + $method = LazyLoadingMethodInterceptor::generateMethod($reflection, $initializer, $valueHolder); + + $this->assertSame('publicByReferenceParameterMethod', $method->getName()); + $this->assertCount(2, $method->getParameters()); + $this->assertSame( + "\$this->foo && \$this->foo->__invoke(\$this->bar, \$this, 'publicByReferenceParameterMethod', " + . "array('param' => \$param, 'byRefParam' => \$byRefParam), \$this->foo);\n\n" + . "return \$this->bar->publicByReferenceParameterMethod(\$param, \$byRefParam);", + $method->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\LazyLoadingMethodInterceptor + */ + public function testBodyStructureWithoutParameters() + { + $reflectionMethod = new MethodReflection(__CLASS__, 'testBodyStructureWithoutParameters'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $method = LazyLoadingMethodInterceptor::generateMethod($reflectionMethod, $initializer, $valueHolder); + + $this->assertSame('testBodyStructureWithoutParameters', $method->getName()); + $this->assertCount(0, $method->getParameters()); + $this->assertSame( + "\$this->foo && \$this->foo->__invoke(\$this->bar, \$this, " + . "'testBodyStructureWithoutParameters', array(), \$this->foo);\n\n" + . "return \$this->bar->testBodyStructureWithoutParameters();", + $method->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicCloneTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicCloneTest.php new file mode 100644 index 0000000..dea429c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicCloneTest.php @@ -0,0 +1,57 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicCloneTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\MagicClone::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicClone = new MagicClone($reflection, $initializer, $valueHolder); + + $this->assertSame('__clone', $magicClone->getName()); + $this->assertCount(0, $magicClone->getParameters()); + $this->assertSame( + "\$this->foo && \$this->foo->__invoke(\$this->bar, \$this, " + . "'__clone', array(), \$this->foo);\n\n\$this->bar = clone \$this->bar;", + $magicClone->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGetTest.php new file mode 100644 index 0000000..bc3a61c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicGetTest.php @@ -0,0 +1,65 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicGetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\MagicGet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + $publicProperties->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicGet = new MagicGet($reflection, $initializer, $valueHolder, $publicProperties); + + $this->assertSame('__get', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->foo->__invoke(\$this->bar, \$this, '__get', array('name' => \$name)" + . ", \$this->foo);\n\n" + . "if (isset(self::\$bar[\$name])) {\n return \$this->bar->\$name;\n}" + . "%areturn %s;", + $magicGet->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIssetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIssetTest.php new file mode 100644 index 0000000..751840d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicIssetTest.php @@ -0,0 +1,65 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicIssetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\MagicIsset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + $publicProperties->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicIsset = new MagicIsset($reflection, $initializer, $valueHolder, $publicProperties); + + $this->assertSame('__isset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->foo->__invoke(\$this->bar, \$this, '__isset', array('name' => \$name)" + . ", \$this->foo);\n\n" + . "if (isset(self::\$bar[\$name])) {\n return isset(\$this->bar->\$name);\n}" + . "%areturn %s;", + $magicIsset->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSetTest.php new file mode 100644 index 0000000..dba5b49 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSetTest.php @@ -0,0 +1,65 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\MagicSet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + $publicProperties->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicSet = new MagicSet($reflection, $initializer, $valueHolder, $publicProperties); + + $this->assertSame('__set', $magicSet->getName()); + $this->assertCount(2, $magicSet->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->foo->__invoke(\$this->bar, \$this, " + . "'__set', array('name' => \$name, 'value' => \$value), \$this->foo);\n\n" + . "if (isset(self::\$bar[\$name])) {\n return (\$this->bar->\$name = \$value);\n}" + . "%areturn %s;", + $magicSet->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleepTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleepTest.php new file mode 100644 index 0000000..978eeb7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicSleepTest.php @@ -0,0 +1,57 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSleepTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\MagicSleep::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicSleep = new MagicSleep($reflection, $initializer, $valueHolder); + + $this->assertSame('__sleep', $magicSleep->getName()); + $this->assertCount(0, $magicSleep->getParameters()); + $this->assertSame( + "\$this->foo && \$this->foo->__invoke(\$this->bar, \$this, '__sleep', array(), \$this->foo);" + . "\n\nreturn array('bar');", + $magicSleep->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnsetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnsetTest.php new file mode 100644 index 0000000..e94ee3b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/MagicUnsetTest.php @@ -0,0 +1,65 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicUnsetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\MagicUnset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $publicProperties = $this + ->getMockBuilder('ProxyManager\\ProxyGenerator\\PropertyGenerator\\PublicPropertiesMap') + ->disableOriginalConstructor() + ->getMock(); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + $publicProperties->expects($this->any())->method('isEmpty')->will($this->returnValue(false)); + $publicProperties->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicIsset = new MagicUnset($reflection, $initializer, $valueHolder, $publicProperties); + + $this->assertSame('__unset', $magicIsset->getName()); + $this->assertCount(1, $magicIsset->getParameters()); + $this->assertStringMatchesFormat( + "\$this->foo && \$this->foo->__invoke(\$this->bar, \$this, '__unset', array('name' => \$name)" + . ", \$this->foo);\n\n" + . "if (isset(self::\$bar[\$name])) {\n unset(\$this->bar->\$name);\n\n return;\n}" + . "%areturn %s;", + $magicIsset->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializerTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializerTest.php new file mode 100644 index 0000000..d70bf2c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/MethodGenerator/SetProxyInitializerTest.php @@ -0,0 +1,56 @@ + + * @license MIT + * + * @group Coverage + */ +class SetProxyInitializerTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\MethodGenerator\SetProxyInitializer::__construct + */ + public function testBodyStructure() + { + $initializer = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $initializer->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $setter = new SetProxyInitializer($initializer); + $parameters = $setter->getParameters(); + + $this->assertSame('setProxyInitializer', $setter->getName()); + $this->assertCount(1, $parameters); + + /* @var $initializer \ProxyManager\Generator\ParameterGenerator */ + $initializer = array_shift($parameters); + + $this->assertInstanceOf('ProxyManager\\Generator\\ParameterGenerator', $initializer); + $this->assertSame('initializer', $initializer->getName()); + $this->assertSame('$this->foo = $initializer;', $setter->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerPropertyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerPropertyTest.php new file mode 100644 index 0000000..c292e20 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/InitializerPropertyTest.php @@ -0,0 +1,42 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\PropertyGenerator\InitializerProperty + * @group Coverage + */ +class InitializerPropertyTest extends AbstractUniquePropertyNameTest +{ + /** + * {@inheritDoc} + */ + protected function createProperty() + { + return new InitializerProperty(); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderPropertyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderPropertyTest.php new file mode 100644 index 0000000..63a16a7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolder/PropertyGenerator/ValueHolderPropertyTest.php @@ -0,0 +1,42 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolder\PropertyGenerator\ValueHolderProperty + * @group Coverage + */ +class ValueHolderPropertyTest extends AbstractUniquePropertyNameTest +{ + /** + * {@inheritDoc} + */ + protected function createProperty() + { + return new ValueHolderProperty(); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolderGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolderGeneratorTest.php new file mode 100644 index 0000000..683e2c9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/LazyLoadingValueHolderGeneratorTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator + * @group Coverage + */ +class LazyLoadingValueHolderGeneratorTest extends AbstractProxyGeneratorTest +{ + /** + * {@inheritDoc} + */ + protected function getProxyGenerator() + { + return new LazyLoadingValueHolderGenerator(); + } + + /** + * {@inheritDoc} + */ + protected function getExpectedImplementedInterfaces() + { + return array('ProxyManager\\Proxy\\VirtualProxyInterface'); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObject/MethodGenerator/ConstructorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObject/MethodGenerator/ConstructorTest.php new file mode 100644 index 0000000..7fad56d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObject/MethodGenerator/ConstructorTest.php @@ -0,0 +1,64 @@ + + * @license MIT + * + * @group Coverage + */ +class ConstructorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\NullObject\MethodGenerator\Constructor::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMixedProperties'); + $constructor = new Constructor($reflection); + + $this->assertSame('__construct', $constructor->getName()); + $this->assertCount(0, $constructor->getParameters()); + $this->assertSame( + "\$this->publicProperty0 = null;\n\$this->publicProperty1 = null;\n\$this->publicProperty2 = null;", + $constructor->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\NullObject\MethodGenerator\Constructor::__construct + */ + public function testBodyStructureWithoutPublicProperties() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithPrivateProperties'); + $constructor = new Constructor($reflection); + + $this->assertSame('__construct', $constructor->getName()); + $this->assertCount(0, $constructor->getParameters()); + $body = $constructor->getBody(); + $this->assertTrue(empty($body)); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptorTest.php new file mode 100644 index 0000000..31202a1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObject/MethodGenerator/NullObjectMethodInterceptorTest.php @@ -0,0 +1,75 @@ + + * @license MIT + * + * @group Coverage + */ +class NullObjectMethodInterceptorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\NullObject\MethodGenerator\NullObjectMethodInterceptor + */ + public function testBodyStructure() + { + $reflection = new MethodReflection('ProxyManagerTestAsset\\BaseClass', 'publicByReferenceParameterMethod'); + $method = NullObjectMethodInterceptor::generateMethod($reflection); + + $this->assertSame('publicByReferenceParameterMethod', $method->getName()); + $this->assertCount(2, $method->getParameters()); + $this->assertSame("", $method->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\NullObject\MethodGenerator\NullObjectMethodInterceptor + */ + public function testBodyStructureWithoutParameters() + { + $reflectionMethod = new MethodReflection(__CLASS__, 'testBodyStructureWithoutParameters'); + + $method = NullObjectMethodInterceptor::generateMethod($reflectionMethod); + + $this->assertSame('testBodyStructureWithoutParameters', $method->getName()); + $this->assertCount(0, $method->getParameters()); + $this->assertSame("", $method->getBody()); + } + + /** + * @covers \ProxyManager\ProxyGenerator\NullObject\MethodGenerator\NullObjectMethodInterceptor + */ + public function testBodyStructureWithoutByRefReturn() + { + $reflectionMethod = new MethodReflection('ProxyManagerTestAsset\BaseClass', 'publicByReferenceMethod'); + + $method = NullObjectMethodInterceptor::generateMethod($reflectionMethod); + + $this->assertSame('publicByReferenceMethod', $method->getName()); + $this->assertCount(0, $method->getParameters()); + $this->assertStringMatchesFormat("\$ref%s = null;\nreturn \$ref%s;", $method->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObjectGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObjectGeneratorTest.php new file mode 100644 index 0000000..11a6cbd --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/NullObjectGeneratorTest.php @@ -0,0 +1,113 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\NullObjectGenerator + * @group Coverage + */ +class NullObjectGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestedImplementations + * + * Verifies that generated code is valid and implements expected interfaces + */ + public function testGeneratesValidCode($className) + { + $generator = $this->getProxyGenerator(); + $generatedClassName = UniqueIdentifierGenerator::getIdentifier('AbstractProxyGeneratorTest'); + $generatedClass = new ClassGenerator($generatedClassName); + $originalClass = new ReflectionClass($className); + $generatorStrategy = new EvaluatingGeneratorStrategy(); + + $generator->generate($originalClass, $generatedClass); + $generatorStrategy->generate($generatedClass); + + $generatedReflection = new ReflectionClass($generatedClassName); + + if ($originalClass->isInterface()) { + $this->assertTrue($generatedReflection->implementsInterface($className)); + } + + $this->assertSame($generatedClassName, $generatedReflection->getName()); + + foreach ($this->getExpectedImplementedInterfaces() as $interface) { + $this->assertTrue($generatedReflection->implementsInterface($interface)); + } + + $proxyGenerated = new $generatedClassName(); + + foreach ($generatedReflection->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { + $this->assertNull($proxyGenerated->$property); + } + + /** @var \ReflectionMethod $method */ + foreach ($generatedReflection->getMethods(ReflectionProperty::IS_PUBLIC) as $method) { + if ($method->getNumberOfParameters() == 0) { + $this->assertNull(call_user_func(array($proxyGenerated, $method->getName()))); + } + } + } + + /** + * {@inheritDoc} + */ + protected function getProxyGenerator() + { + return new NullObjectGenerator(); + } + + /** + * {@inheritDoc} + */ + protected function getExpectedImplementedInterfaces() + { + return array( + 'ProxyManager\\Proxy\\NullObjectInterface', + ); + } + + /** + * @return array + */ + public function getTestedImplementations() + { + return array( + array('ProxyManagerTestAsset\\BaseClass'), + array('ProxyManagerTestAsset\\ClassWithMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithByRefMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithMixedProperties'), + array('ProxyManagerTestAsset\\BaseInterface'), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/AbstractUniquePropertyNameTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/AbstractUniquePropertyNameTest.php new file mode 100644 index 0000000..f3cd8ad --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/AbstractUniquePropertyNameTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @group Coverage + */ +abstract class AbstractUniquePropertyNameTest extends PHPUnit_Framework_TestCase +{ + /** + * Verifies that a given property name is unique across two different instantiations of the property + */ + public function testUniqueProperty() + { + $property1 = $this->createProperty(); + $property2 = $this->createProperty(); + + $this->assertSame($property1->getName(), $property1->getName()); + $this->assertNotEquals($property1->getName(), $property2->getName()); + } + + /** + * @return \Zend\Code\Generator\PropertyGenerator + */ + abstract protected function createProperty(); +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/PublicPropertiesDefaultsTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/PublicPropertiesDefaultsTest.php new file mode 100644 index 0000000..c714675 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/PublicPropertiesDefaultsTest.php @@ -0,0 +1,72 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesDefaults + * @group Coverage + */ +class PublicPropertiesDefaultsTest extends PHPUnit_Framework_TestCase +{ + public function testEmptyClass() + { + $publicProperties = new PublicPropertiesDefaults(new ReflectionClass('ProxyManagerTestAsset\\EmptyClass')); + + $this->assertInternalType('array', $publicProperties->getDefaultValue()->getValue()); + $this->assertEmpty($publicProperties->getDefaultValue()->getValue()); + $this->assertTrue($publicProperties->isStatic()); + $this->assertSame(PublicPropertiesDefaults::VISIBILITY_PRIVATE, $publicProperties->getVisibility()); + } + + public function testClassWithPublicProperties() + { + $publicProperties = new PublicPropertiesDefaults( + new ReflectionClass('ProxyManagerTestAsset\\ClassWithPublicProperties') + ); + + $this->assertInternalType('array', $publicProperties->getDefaultValue()->getValue()); + $this->assertCount(10, $publicProperties->getDefaultValue()->getValue()); + $this->assertTrue($publicProperties->isStatic()); + $this->assertSame(PublicPropertiesDefaults::VISIBILITY_PRIVATE, $publicProperties->getVisibility()); + } + + public function testBaseClass() + { + $publicProperties = new PublicPropertiesDefaults( + new ReflectionClass('ProxyManagerTestAsset\\BaseClass') + ); + + $this->assertInternalType('array', $publicProperties->getDefaultValue()->getValue()); + $this->assertSame( + array('publicProperty' => 'publicPropertyDefault'), + $publicProperties->getDefaultValue()->getValue() + ); + $this->assertTrue($publicProperties->isStatic()); + $this->assertSame(PublicPropertiesDefaults::VISIBILITY_PRIVATE, $publicProperties->getVisibility()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/PublicPropertiesMapTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/PublicPropertiesMapTest.php new file mode 100644 index 0000000..a916762 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/PropertyGenerator/PublicPropertiesMapTest.php @@ -0,0 +1,59 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\PropertyGenerator\PublicPropertiesMap + * @group Coverage + */ +class PublicPropertiesMapTest extends PHPUnit_Framework_TestCase +{ + public function testEmptyClass() + { + $publicProperties = new PublicPropertiesMap(new ReflectionClass('ProxyManagerTestAsset\\EmptyClass')); + + $this->assertInternalType('array', $publicProperties->getDefaultValue()->getValue()); + $this->assertEmpty($publicProperties->getDefaultValue()->getValue()); + $this->assertTrue($publicProperties->isStatic()); + $this->assertSame('private', $publicProperties->getVisibility()); + $this->assertTrue($publicProperties->isEmpty()); + } + + public function testClassWithPublicProperties() + { + $publicProperties = new PublicPropertiesMap( + new ReflectionClass('ProxyManagerTestAsset\\ClassWithPublicProperties') + ); + + $this->assertInternalType('array', $publicProperties->getDefaultValue()->getValue()); + $this->assertCount(10, $publicProperties->getDefaultValue()->getValue()); + $this->assertTrue($publicProperties->isStatic()); + $this->assertSame('private', $publicProperties->getVisibility()); + $this->assertFalse($publicProperties->isEmpty()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/ConstructorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/ConstructorTest.php new file mode 100644 index 0000000..f1c9da0 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/ConstructorTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class ConstructorTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\RemoteObject\MethodGenerator\Constructor::__construct + */ + public function testBodyStructure() + { + $adapter = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $adapter->expects($this->any())->method('getName')->will($this->returnValue('adapter')); + + $reflection = new ReflectionClass('ProxyManagerTestAsset\\ClassWithMixedProperties'); + $constructor = new Constructor($reflection, $adapter); + + $this->assertSame('__construct', $constructor->getName()); + $this->assertCount(1, $constructor->getParameters()); + $this->assertSame( + "\$this->adapter = \$adapter;\nunset(\$this->publicProperty0);" + . "\nunset(\$this->publicProperty1);\nunset(\$this->publicProperty2);", + $constructor->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicGetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicGetTest.php new file mode 100644 index 0000000..096a891 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicGetTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicGetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\RemoteObject\MethodGenerator\MagicGet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $adapter = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $adapter->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $magicGet = new MagicGet($reflection, $adapter); + + $this->assertSame('__get', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat( + '$return = $this->foo->call(\'ProxyManagerTestAsset\\\EmptyClass\', \'__get\', array($name));' + . "\n\nreturn \$return;", + $magicGet->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicIssetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicIssetTest.php new file mode 100644 index 0000000..23ba337 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicIssetTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicIssetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\RemoteObject\MethodGenerator\MagicIsset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $adapter = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $adapter->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $magicGet = new MagicIsset($reflection, $adapter); + + $this->assertSame('__isset', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat( + '$return = $this->foo->call(\'ProxyManagerTestAsset\\\EmptyClass\', \'__isset\', array($name));' + . "\n\nreturn \$return;", + $magicGet->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicSetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicSetTest.php new file mode 100644 index 0000000..6034b99 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicSetTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\RemoteObject\MethodGenerator\MagicSet::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $adapter = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $adapter->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $magicGet = new MagicSet($reflection, $adapter); + + $this->assertSame('__set', $magicGet->getName()); + $this->assertCount(2, $magicGet->getParameters()); + $this->assertStringMatchesFormat( + '$return = $this->foo->call(\'ProxyManagerTestAsset\\\EmptyClass\', \'__set\', array($name, $value));' + . "\n\nreturn \$return;", + $magicGet->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnsetTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnsetTest.php new file mode 100644 index 0000000..a3f186f --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/MagicUnsetTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicUnsetTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\RemoteObject\MethodGenerator\MagicUnset::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $adapter = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $adapter->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $magicGet = new MagicUnset($reflection, $adapter); + + $this->assertSame('__unset', $magicGet->getName()); + $this->assertCount(1, $magicGet->getParameters()); + $this->assertStringMatchesFormat( + '$return = $this->foo->call(\'ProxyManagerTestAsset\\\EmptyClass\', \'__unset\', array($name));' + . "\n\nreturn \$return;", + $magicGet->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethodTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethodTest.php new file mode 100644 index 0000000..1c41a4f --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/MethodGenerator/RemoteObjectMethodTest.php @@ -0,0 +1,116 @@ + + * @license MIT + * + * @group Coverage + */ +class RemoteObjectMethodTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\RemoteObject\MethodGenerator\RemoteObjectMethod + */ + public function testBodyStructureWithParameters() + { + $adapter = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $adapter->expects($this->any())->method('getName')->will($this->returnValue('adapter')); + + $reflectionMethod = new MethodReflection( + 'ProxyManagerTestAsset\\BaseClass', + 'publicByReferenceParameterMethod' + ); + + $method = RemoteObjectMethod::generateMethod( + $reflectionMethod, + $adapter, + new ReflectionClass('Zend\\Code\\Generator\\PropertyGenerator') + ); + + $this->assertSame('publicByReferenceParameterMethod', $method->getName()); + $this->assertCount(2, $method->getParameters()); + $this->assertSame( + '$return = $this->adapter->call(\'Zend\\\Code\\\Generator\\\PropertyGenerator\', ' + . '\'publicByReferenceParameterMethod\', array($param, $byRefParam));' + . "\n\nreturn \$return;", + $method->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\RemoteObject\MethodGenerator\RemoteObjectMethod + */ + public function testBodyStructureWithArrayParameter() + { + $adapter = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $adapter->expects($this->any())->method('getName')->will($this->returnValue('adapter')); + + $reflectionMethod = new MethodReflection('ProxyManagerTestAsset\\BaseClass', 'publicArrayHintedMethod'); + + $method = RemoteObjectMethod::generateMethod( + $reflectionMethod, + $adapter, + new ReflectionClass('Zend\\Code\\Generator\\PropertyGenerator') + ); + + $this->assertSame('publicArrayHintedMethod', $method->getName()); + $this->assertCount(1, $method->getParameters()); + $this->assertSame( + '$return = $this->adapter->call(\'Zend\\\Code\\\Generator\\\PropertyGenerator\', ' + . '\'publicArrayHintedMethod\', array($param));' + . "\n\nreturn \$return;", + $method->getBody() + ); + } + + /** + * @covers \ProxyManager\ProxyGenerator\RemoteObject\MethodGenerator\RemoteObjectMethod + */ + public function testBodyStructureWithoutParameters() + { + $adapter = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + $adapter->expects($this->any())->method('getName')->will($this->returnValue('adapter')); + + $reflectionMethod = new MethodReflection(__CLASS__, 'testBodyStructureWithoutParameters'); + + $method = RemoteObjectMethod::generateMethod( + $reflectionMethod, + $adapter, + new ReflectionClass('Zend\\Code\\Generator\\PropertyGenerator') + ); + + $this->assertSame('testBodyStructureWithoutParameters', $method->getName()); + $this->assertCount(0, $method->getParameters()); + $this->assertSame( + '$return = $this->adapter->call(\'Zend\\\Code\\\Generator\\\PropertyGenerator\', ' + . '\'testBodyStructureWithoutParameters\', array());' + . "\n\nreturn \$return;", + $method->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterPropertyTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterPropertyTest.php new file mode 100644 index 0000000..d7e9cc9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObject/PropertyGenerator/AdapterPropertyTest.php @@ -0,0 +1,42 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\RemoteObject\PropertyGenerator\AdapterProperty + * @group Coverage + */ +class AdapterPropertyTest extends AbstractUniquePropertyNameTest +{ + /** + * {@inheritDoc} + */ + protected function createProperty() + { + return new AdapterProperty(); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObjectGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObjectGeneratorTest.php new file mode 100644 index 0000000..961c226 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/RemoteObjectGeneratorTest.php @@ -0,0 +1,103 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\RemoteObjectGenerator + * @group Coverage + */ +class RemoteObjectGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getTestedImplementations + * + * Verifies that generated code is valid and implements expected interfaces + */ + public function testGeneratesValidCode($className) + { + $generator = $this->getProxyGenerator(); + $generatedClassName = UniqueIdentifierGenerator::getIdentifier('AbstractProxyGeneratorTest'); + $generatedClass = new ClassGenerator($generatedClassName); + $originalClass = new ReflectionClass($className); + $generatorStrategy = new EvaluatingGeneratorStrategy(); + + $generator->generate($originalClass, $generatedClass); + $generatorStrategy->generate($generatedClass); + + $generatedReflection = new ReflectionClass($generatedClassName); + + if ($originalClass->isInterface()) { + $this->assertTrue($generatedReflection->implementsInterface($className)); + } else { + $this->assertEmpty( + array_diff($originalClass->getInterfaceNames(), $generatedReflection->getInterfaceNames()) + ); + } + + $this->assertSame($generatedClassName, $generatedReflection->getName()); + + foreach ($this->getExpectedImplementedInterfaces() as $interface) { + $this->assertTrue($generatedReflection->implementsInterface($interface)); + } + } + + /** + * {@inheritDoc} + */ + protected function getProxyGenerator() + { + return new RemoteObjectGenerator(); + } + + /** + * {@inheritDoc} + */ + protected function getExpectedImplementedInterfaces() + { + return array( + 'ProxyManager\\Proxy\\RemoteObjectInterface', + ); + } + + /** + * @return array + */ + public function getTestedImplementations() + { + return array( + array('ProxyManagerTestAsset\\BaseClass'), + array('ProxyManagerTestAsset\\ClassWithMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithByRefMagicMethods'), + array('ProxyManagerTestAsset\\ClassWithMixedProperties'), + array('ProxyManagerTestAsset\\BaseInterface'), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Util/ProxiedMethodsFilterTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Util/ProxiedMethodsFilterTest.php new file mode 100644 index 0000000..89bbb6a --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Util/ProxiedMethodsFilterTest.php @@ -0,0 +1,114 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\Util\ProxiedMethodsFilter + * @group Coverage + */ +class ProxiedMethodsFilterTest extends PHPUnit_Framework_TestCase +{ + /** + * @dataProvider expectedMethods + */ + public function testFiltering(ReflectionClass $reflectionClass, $excludes, array $expectedMethods) + { + if (is_array($excludes)) { + $filtered = ProxiedMethodsFilter::getProxiedMethods($reflectionClass, $excludes); + } else { + $filtered = ProxiedMethodsFilter::getProxiedMethods($reflectionClass); + } + + foreach ($filtered as $method) { + $this->assertInstanceOf('ReflectionMethod', $method); + } + + $keys = array_map( + function (ReflectionMethod $method) { + return $method->getName(); + }, + $filtered + ); + + sort($keys); + sort($expectedMethods); + + $this->assertSame($keys, $expectedMethods); + } + + /** + * @return array[][] + */ + public function expectedMethods() + { + return array( + array( + new ReflectionClass('ProxyManagerTestAsset\\BaseClass'), + null, + array( + 'publicArrayHintedMethod', + 'publicByReferenceMethod', + 'publicByReferenceParameterMethod', + 'publicMethod', + 'publicTypeHintedMethod', + ), + ), + array( + new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'), + null, + array(), + ), + array( + new ReflectionClass('ProxyManagerTestAsset\\LazyLoadingMock'), + null, + array(), + ), + array( + new ReflectionClass('ProxyManagerTestAsset\\LazyLoadingMock'), + array(), + array(), + ), + array( + new ReflectionClass('ProxyManagerTestAsset\\HydratedObject'), + array('doFoo'), + array('__get'), + ), + array( + new ReflectionClass('ProxyManagerTestAsset\\HydratedObject'), + array('Dofoo'), + array('__get'), + ), + array( + new ReflectionClass('ProxyManagerTestAsset\\HydratedObject'), + array(), + array('doFoo', '__get'), + ), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Util/PublicScopeSimulatorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Util/PublicScopeSimulatorTest.php new file mode 100644 index 0000000..e5271ff --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/Util/PublicScopeSimulatorTest.php @@ -0,0 +1,133 @@ + + * @license MIT + * + * @covers \ProxyManager\ProxyGenerator\Util\PublicScopeSimulator + * @group Coverage + */ +class PublicScopeSimulatorTest extends PHPUnit_Framework_TestCase +{ + public function testSimpleGet() + { + $code = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_GET, + 'foo', + null, + null, + 'bar' + ); + + $this->assertStringMatchesFormat('%a{%areturn $%s->$foo;%a}%a$bar = %s;', $code); + } + + public function testSimpleSet() + { + $code = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_SET, + 'foo', + 'baz', + null, + 'bar' + ); + + $this->assertStringMatchesFormat('%a{%areturn $%s->$foo = $baz;%a}%a$bar = %s;', $code); + } + + public function testSimpleIsset() + { + $code = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_ISSET, + 'foo', + null, + null, + 'bar' + ); + + $this->assertStringMatchesFormat('%a{%areturn isset($%s->$foo);%a}%a$bar = %s;', $code); + } + + public function testSimpleUnset() + { + $code = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_UNSET, + 'foo', + null, + null, + 'bar' + ); + + $this->assertStringMatchesFormat('%a{%aunset($%s->$foo);%a}%a$bar = %s;', $code); + } + + public function testSetRequiresValueParameterName() + { + $this->setExpectedException('InvalidArgumentException'); + + PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_SET, + 'foo', + null, + null, + 'bar' + ); + } + + public function testDelegatesToValueHolderWhenAvailable() + { + $code = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_SET, + 'foo', + 'baz', + new PropertyGenerator('valueHolder'), + 'bar' + ); + + $this->assertStringMatchesFormat( + '%A$targetObject = $this->valueHolder;%a{%areturn $%s->$foo = $baz;%a}%a$bar = %s;', + $code + ); + } + + public function testSetRequiresValidOperation() + { + $this->setExpectedException('InvalidArgumentException'); + + PublicScopeSimulator::getPublicAccessSimulationCode('invalid', 'foo'); + } + + public function testWillReturnDirectlyWithNoReturnParam() + { + $code = PublicScopeSimulator::getPublicAccessSimulationCode( + PublicScopeSimulator::OPERATION_GET, + 'foo' + ); + + $this->assertStringMatchesFormat('%a{%areturn $%s->$foo;%a}%areturn %s;', $code); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValueTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValueTest.php new file mode 100644 index 0000000..b7acb31 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/ValueHolder/MethodGenerator/GetWrappedValueHolderValueTest.php @@ -0,0 +1,49 @@ + + * @license MIT + * + * @group Coverage + */ +class GetWrappedValueHolderValueTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\ValueHolder\MethodGenerator\GetWrappedValueHolderValue::__construct + */ + public function testBodyStructure() + { + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('foo')); + + $getter = new GetWrappedValueHolderValue($valueHolder); + + $this->assertSame('getWrappedValueHolderValue', $getter->getName()); + $this->assertCount(0, $getter->getParameters()); + $this->assertSame('return $this->foo;', $getter->getBody()); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleepTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleepTest.php new file mode 100644 index 0000000..f2b1596 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/ProxyGenerator/ValueHolder/MethodGenerator/MagicSleepTest.php @@ -0,0 +1,54 @@ + + * @license MIT + * + * @group Coverage + */ +class MagicSleepTest extends PHPUnit_Framework_TestCase +{ + /** + * @covers \ProxyManager\ProxyGenerator\ValueHolder\MethodGenerator\MagicSleep::__construct + */ + public function testBodyStructure() + { + $reflection = new ReflectionClass('ProxyManagerTestAsset\\EmptyClass'); + $valueHolder = $this->getMock('Zend\\Code\\Generator\\PropertyGenerator'); + + $valueHolder->expects($this->any())->method('getName')->will($this->returnValue('bar')); + + $magicSleep = new MagicSleep($reflection, $valueHolder); + + $this->assertSame('__sleep', $magicSleep->getName()); + $this->assertCount(0, $magicSleep->getParameters()); + $this->assertSame( + "return array('bar');", + $magicSleep->getBody() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/ClassSignatureGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/ClassSignatureGeneratorTest.php new file mode 100644 index 0000000..95ffbdd --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/ClassSignatureGeneratorTest.php @@ -0,0 +1,87 @@ + + * @license MIT + * + * @covers \ProxyManager\Signature\ClassSignatureGenerator + * @group Coverage + */ +class ClassSignatureGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @var \ProxyManager\Signature\SignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $signatureGenerator; + + /** + * @var ClassSignatureGenerator + */ + private $classSignatureGenerator; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->signatureGenerator = $this->getMock('ProxyManager\\Signature\\SignatureGeneratorInterface'); + $this->classSignatureGenerator = new ClassSignatureGenerator($this->signatureGenerator); + } + + public function testAddSignature() + { + /* @var $classGenerator \PHPUnit_Framework_MockObject_MockObject|\Zend\Code\Generator\ClassGenerator */ + $classGenerator = $this->getMock('Zend\\Code\\Generator\\ClassGenerator'); + + $classGenerator + ->expects($this->once()) + ->method('addPropertyFromGenerator') + ->with($this->callback(function (PropertyGenerator $property) { + return $property->getName() === 'signaturePropertyName' + && $property->isStatic() + && $property->getVisibility() === 'private' + && $property->getDefaultValue()->getValue() === 'valid-signature'; + })); + + $this + ->signatureGenerator + ->expects($this->any()) + ->method('generateSignature') + ->with(array('foo' => 'bar')) + ->will($this->returnValue('valid-signature')); + + $this + ->signatureGenerator + ->expects($this->any()) + ->method('generateSignatureKey') + ->with(array('foo' => 'bar')) + ->will($this->returnValue('PropertyName')); + + + $this->classSignatureGenerator->addSignature($classGenerator, array('foo' => 'bar')); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/Exception/InvalidSignatureExceptionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/Exception/InvalidSignatureExceptionTest.php new file mode 100644 index 0000000..d587fc0 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/Exception/InvalidSignatureExceptionTest.php @@ -0,0 +1,57 @@ + + * @license MIT + * + * @covers \ProxyManager\Signature\Exception\InvalidSignatureException + * @group Coverage + */ +class InvalidSignatureExceptionTest extends PHPUnit_Framework_TestCase +{ + public function testFromInvalidSignature() + { + $exception = InvalidSignatureException::fromInvalidSignature( + new ReflectionClass(__CLASS__), + array('foo' => 'bar', 'baz' => 'tab'), + 'blah', + 'expected-signature' + ); + + $this->assertInstanceOf( + 'ProxyManager\Signature\Exception\InvalidSignatureException', + $exception + ); + + $this->assertSame( + 'Found signature "blah" for class "' + . __CLASS__ + . '" does not correspond to expected signature "expected-signature" for 2 parameters', + $exception->getMessage() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/Exception/MissingSignatureExceptionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/Exception/MissingSignatureExceptionTest.php new file mode 100644 index 0000000..2d8a0d8 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/Exception/MissingSignatureExceptionTest.php @@ -0,0 +1,56 @@ + + * @license MIT + * + * @covers \ProxyManager\Signature\Exception\MissingSignatureException + * @group Coverage + */ +class MissingSignatureExceptionTest extends PHPUnit_Framework_TestCase +{ + public function testFromMissingSignature() + { + $exception = MissingSignatureException::fromMissingSignature( + new ReflectionClass(__CLASS__), + array('foo' => 'bar', 'baz' => 'tab'), + 'expected-signature' + ); + + $this->assertInstanceOf( + 'ProxyManager\Signature\Exception\MissingSignatureException', + $exception + ); + + $this->assertSame( + 'No signature found for class "' + . __CLASS__ + . '", expected signature "expected-signature" for 2 parameters', + $exception->getMessage() + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/SignatureCheckerTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/SignatureCheckerTest.php new file mode 100644 index 0000000..0fa13f9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/SignatureCheckerTest.php @@ -0,0 +1,117 @@ + + * @license MIT + * + * @covers \ProxyManager\Signature\SignatureChecker + * @group Coverage + */ +class SignatureCheckerTest extends PHPUnit_Framework_TestCase +{ + /** + * @var string + */ + protected $signatureExample = 'valid-signature'; + + /** + * @var SignatureChecker + */ + private $signatureChecker; + + /** + * @var \ProxyManager\Signature\SignatureGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $signatureGenerator; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->signatureGenerator = $this->getMock('ProxyManager\Signature\SignatureGeneratorInterface'); + $this->signatureChecker = new SignatureChecker($this->signatureGenerator); + } + + public function testCheckSignatureWithValidKey() + { + $this + ->signatureGenerator + ->expects($this->atLeastOnce()) + ->method('generateSignatureKey') + ->with(array('foo' => 'bar')) + ->will($this->returnValue('Example')); + $this + ->signatureGenerator + ->expects($this->atLeastOnce()) + ->method('generateSignature') + ->with(array('foo' => 'bar')) + ->will($this->returnValue('valid-signature')); + + $this->signatureChecker->checkSignature(new ReflectionClass($this), array('foo' => 'bar')); + } + + public function testCheckSignatureWithInvalidKey() + { + $this + ->signatureGenerator + ->expects($this->any()) + ->method('generateSignatureKey') + ->with(array('foo' => 'bar')) + ->will($this->returnValue('InvalidKey')); + $this + ->signatureGenerator + ->expects($this->any()) + ->method('generateSignature') + ->with(array('foo' => 'bar')) + ->will($this->returnValue('valid-signature')); + + $this->setExpectedException('ProxyManager\Signature\Exception\MissingSignatureException'); + + $this->signatureChecker->checkSignature(new ReflectionClass($this), array('foo' => 'bar')); + } + + public function testCheckSignatureWithInvalidValue() + { + $this + ->signatureGenerator + ->expects($this->any()) + ->method('generateSignatureKey') + ->with(array('foo' => 'bar')) + ->will($this->returnValue('Example')); + $this + ->signatureGenerator + ->expects($this->any()) + ->method('generateSignature') + ->with(array('foo' => 'bar')) + ->will($this->returnValue('invalid-signature')); + + $this->setExpectedException('ProxyManager\Signature\Exception\InvalidSignatureException'); + + $this->signatureChecker->checkSignature(new ReflectionClass($this), array('foo' => 'bar')); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/SignatureGeneratorTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/SignatureGeneratorTest.php new file mode 100644 index 0000000..5ad93d6 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/Signature/SignatureGeneratorTest.php @@ -0,0 +1,116 @@ + + * @license MIT + * + * @covers \ProxyManager\Signature\SignatureGenerator + * @group Coverage + */ +class SignatureGeneratorTest extends PHPUnit_Framework_TestCase +{ + /** + * @var SignatureGenerator + */ + private $signatureGenerator; + + /** + * {@inheritDoc} + */ + protected function setUp() + { + $this->signatureGenerator = new SignatureGenerator; + } + + /** + * @param array $parameters + * @param string $expected + * + * @dataProvider signatures + */ + public function testGenerateSignature(array $parameters, $expected) + { + $this->assertSame($expected, $this->signatureGenerator->generateSignature($parameters)); + } + + /** + * @param array $parameters + * @param string $expected + * + * @dataProvider signatureKeys + */ + public function testGenerateSignatureKey(array $parameters, $expected) + { + $this->assertSame($expected, $this->signatureGenerator->generateSignatureKey($parameters)); + } + + /** + * Data provider. + * + * @return array[] + */ + public function signatures() + { + return array( + array( + array(), + 'YTowOnt9' + ), + array( + array('foo' => 'bar'), + 'YToxOntzOjM6ImZvbyI7czozOiJiYXIiO30=' + ), + array( + array('foo' => 'bar', 'baz' => 'tab'), + 'YToyOntzOjM6ImZvbyI7czozOiJiYXIiO3M6MzoiYmF6IjtzOjM6InRhYiI7fQ==' + ), + array( + array('bar'), + 'YToxOntpOjA7czozOiJiYXIiO30=' + ), + array( + array('bar', 'baz'), + 'YToyOntpOjA7czozOiJiYXIiO2k6MTtzOjM6ImJheiI7fQ==' + ), + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function signatureKeys() + { + return array( + array(array(), '40cd750bba9870f18aada2478b24840a'), + array(array('foo' => 'bar'), '49a3696adf0fbfacc12383a2d7400d51'), + array(array('foo' => 'bar', 'baz' => 'tab'), '3f3cabbf33bae82b0711205c913a8fa0'), + array(array('bar'), '6fc5f617053f53f56b4734453ec86daa'), + array(array('bar', 'baz'), 'b9f31192ffbb4aa958cd1c5f88540c1e'), + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/VersionTest.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/VersionTest.php new file mode 100644 index 0000000..84f1261 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTest/VersionTest.php @@ -0,0 +1,42 @@ + + * @license MIT + * + * @covers \ProxyManager\Version + * @group Coverage + */ +class VersionTest extends PHPUnit_Framework_TestCase +{ + public function testVersionNumberIsSemverCompliant() + { + $this->assertRegExp( + '/\d+\.\d+\.\d+(-(ALPHA|BETA|RC(\d+)?|DEV))?/i', + Version::VERSION + ); + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/AccessInterceptorValueHolderMock.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/AccessInterceptorValueHolderMock.php new file mode 100644 index 0000000..8324d97 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/AccessInterceptorValueHolderMock.php @@ -0,0 +1,55 @@ + + * @license MIT + */ +class AccessInterceptorValueHolderMock +{ + /** + * @var mixed + */ + public $instance; + + /** + * @var mixed + */ + public $prefixInterceptors; + + /** + * @var mixed + */ + public $suffixInterceptors; + + /** + * @param mixed $instance + * @param mixed $prefixInterceptors + * @param mixed $suffixInterceptors + */ + public function __construct($instance, $prefixInterceptors, $suffixInterceptors) + { + $this->instance = $instance; + $this->prefixInterceptors = $prefixInterceptors; + $this->suffixInterceptors = $suffixInterceptors; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/BaseClass.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/BaseClass.php new file mode 100644 index 0000000..4d58a11 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/BaseClass.php @@ -0,0 +1,108 @@ + + * @license MIT + */ +class BaseClass implements BaseInterface +{ + /** + * @var string + */ + public $publicProperty = 'publicPropertyDefault'; + + /** + * @var string + */ + protected $protectedProperty = 'protectedPropertyDefault'; + + /** + * @var string + */ + private $privateProperty = 'privatePropertyDefault'; + + /** + * @return string + */ + public function publicMethod() + { + return 'publicMethodDefault'; + } + + /** + * @return string + */ + protected function protectedMethod() + { + return 'protectedMethodDefault'; + } + + /** + * @return string + */ + private function privateMethod() + { + return 'privateMethodDefault'; + } + + /** + * @param \stdClass $param + * + * @return string + */ + public function publicTypeHintedMethod(\stdClass $param) + { + return 'publicTypeHintedMethodDefault'; + } + + /** + * @param array $param + * + * @return string + */ + public function publicArrayHintedMethod(array $param) + { + return 'publicArrayHintedMethodDefault'; + } + + /** + * @return string + */ + public function & publicByReferenceMethod() + { + $returnValue = 'publicByReferenceMethodDefault'; + + return $returnValue; + } + + /** + * @param mixed $param + * @param mixed $byRefParam + * + * @return string + */ + public function publicByReferenceParameterMethod($param, & $byRefParam) + { + return 'publicByReferenceParameterMethodDefault'; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/BaseInterface.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/BaseInterface.php new file mode 100644 index 0000000..27aeda5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/BaseInterface.php @@ -0,0 +1,33 @@ + + * @license MIT + */ +interface BaseInterface +{ + /** + * @return string + */ + public function publicMethod(); +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/CallableTypeHintClass.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/CallableTypeHintClass.php new file mode 100644 index 0000000..4a65595 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/CallableTypeHintClass.php @@ -0,0 +1,39 @@ + + * @license MIT + */ +class CallableTypeHintClass +{ + /** + * @param callable $parameter + * + * @return callable + */ + public function callableTypeHintMethod(callable $parameter) + { + return $parameter; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithAbstractProtectedMethod.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithAbstractProtectedMethod.php new file mode 100644 index 0000000..1ba3a5c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithAbstractProtectedMethod.php @@ -0,0 +1,33 @@ + + * @license MIT + */ +abstract class ClassWithAbstractProtectedMethod +{ + /** + * @return void + */ + abstract protected function protectedAbstractMethod(); +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithByRefMagicMethods.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithByRefMagicMethods.php new file mode 100644 index 0000000..6f0ab5d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithByRefMagicMethods.php @@ -0,0 +1,74 @@ + + * @license MIT + */ +class ClassWithByRefMagicMethods +{ + /** + * {@inheritDoc} + */ + public function & __set($name, $value) + { + return array($name => $value); + } + + /** + * {@inheritDoc} + */ + public function & __get($name) + { + return $name; + } + + /** + * {@inheritDoc} + */ + public function & __isset($name) + { + return (bool) $name; + } + + /** + * {@inheritDoc} + */ + public function & __unset($name) + { + return (bool) $name; + } + + /** + * {@inheritDoc} + */ + public function & __sleep() + { + } + + /** + * {@inheritDoc} + */ + public function & __wakeup() + { + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithFinalMagicMethods.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithFinalMagicMethods.php new file mode 100644 index 0000000..d42204b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithFinalMagicMethods.php @@ -0,0 +1,88 @@ + + * @license MIT + */ +class ClassWithFinalMagicMethods +{ + /** + * {@inheritDoc} + */ + final public function __construct() + { + } + + /** + * {@inheritDoc} + */ + final public function __set($name, $value) + { + return array($name => $value); + } + + /** + * {@inheritDoc} + */ + final public function __get($name) + { + return $name; + } + + /** + * {@inheritDoc} + */ + final public function __isset($name) + { + return (bool) $name; + } + + /** + * {@inheritDoc} + */ + final public function __unset($name) + { + return (bool) $name; + } + + /** + * {@inheritDoc} + */ + final public function __sleep() + { + } + + /** + * {@inheritDoc} + */ + final public function __wakeup() + { + } + + /** + * {@inheritDoc} + */ + final public function __clone() + { + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithFinalMethods.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithFinalMethods.php new file mode 100644 index 0000000..a7c0154 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithFinalMethods.php @@ -0,0 +1,42 @@ + + * @license MIT + */ +class ClassWithFinalMethods extends PHPUnit_Framework_TestCase +{ + final public function foo() + { + } + + final private function bar() + { + } + + final protected function baz() + { + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMagicMethods.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMagicMethods.php new file mode 100644 index 0000000..2bd26e5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMagicMethods.php @@ -0,0 +1,81 @@ + + * @license MIT + */ +class ClassWithMagicMethods +{ + /** + * {@inheritDoc} + */ + public function __set($name, $value) + { + return array($name => $value); + } + + /** + * {@inheritDoc} + */ + public function __get($name) + { + return $name; + } + + /** + * {@inheritDoc} + */ + public function __isset($name) + { + return (bool) $name; + } + + /** + * {@inheritDoc} + */ + public function __unset($name) + { + return (bool) $name; + } + + /** + * {@inheritDoc} + */ + public function __sleep() + { + } + + /** + * {@inheritDoc} + */ + public function __wakeup() + { + } + + /** + * {@inheritDoc} + */ + public function __clone() + { + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMethodWithDefaultParameters.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMethodWithDefaultParameters.php new file mode 100644 index 0000000..fa6134a --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMethodWithDefaultParameters.php @@ -0,0 +1,38 @@ + + * @license MIT + */ +class ClassWithMethodWithDefaultParameters +{ + /** + * @param array $parameter + * + * @return string + */ + public function publicMethodWithDefaults(array $parameter = array('foo')) + { + return 'defaultValue'; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMixedProperties.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMixedProperties.php new file mode 100644 index 0000000..3ebf27c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithMixedProperties.php @@ -0,0 +1,46 @@ + + * @license MIT + */ +class ClassWithMixedProperties +{ + public $publicProperty0 = 'publicProperty0'; + + public $publicProperty1 = 'publicProperty1'; + + public $publicProperty2 = 'publicProperty2'; + + protected $protectedProperty0 = 'protectedProperty0'; + + protected $protectedProperty1 = 'protectedProperty1'; + + protected $protectedProperty2 = 'protectedProperty2'; + + private $privateProperty0 = 'privateProperty0'; + + private $privateProperty1 = 'privateProperty1'; + + private $privateProperty2 = 'privateProperty2'; +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPrivateProperties.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPrivateProperties.php new file mode 100644 index 0000000..96a7a93 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPrivateProperties.php @@ -0,0 +1,48 @@ + + * @license MIT + */ +class ClassWithPrivateProperties +{ + private $property0 = 'property0'; + + private $property1 = 'property1'; + + private $property2 = 'property2'; + + private $property3 = 'property3'; + + private $property4 = 'property4'; + + private $property5 = 'property5'; + + private $property6 = 'property6'; + + private $property7 = 'property7'; + + private $property8 = 'property8'; + + private $property9 = 'property9'; +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithProtectedProperties.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithProtectedProperties.php new file mode 100644 index 0000000..ad600d1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithProtectedProperties.php @@ -0,0 +1,48 @@ + + * @license MIT + */ +class ClassWithProtectedProperties +{ + protected $property0 = 'property0'; + + protected $property1 = 'property1'; + + protected $property2 = 'property2'; + + protected $property3 = 'property3'; + + protected $property4 = 'property4'; + + protected $property5 = 'property5'; + + protected $property6 = 'property6'; + + protected $property7 = 'property7'; + + protected $property8 = 'property8'; + + protected $property9 = 'property9'; +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPublicArrayProperty.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPublicArrayProperty.php new file mode 100644 index 0000000..af68ceb --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPublicArrayProperty.php @@ -0,0 +1,31 @@ + + * @license MIT + */ +class ClassWithPublicArrayProperty +{ + public $arrayProperty = array(); +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPublicProperties.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPublicProperties.php new file mode 100644 index 0000000..d36ea8c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithPublicProperties.php @@ -0,0 +1,48 @@ + + * @license MIT + */ +class ClassWithPublicProperties +{ + public $property0 = 'property0'; + + public $property1 = 'property1'; + + public $property2 = 'property2'; + + public $property3 = 'property3'; + + public $property4 = 'property4'; + + public $property5 = 'property5'; + + public $property6 = 'property6'; + + public $property7 = 'property7'; + + public $property8 = 'property8'; + + public $property9 = 'property9'; +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithSelfHint.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithSelfHint.php new file mode 100644 index 0000000..37d342c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ClassWithSelfHint.php @@ -0,0 +1,38 @@ + + * @license MIT + */ +class ClassWithSelfHint +{ + /** + * @param self $parameter + * + * @return self + */ + public function selfHintMethod(self $parameter) + { + return $parameter; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/EmptyClass.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/EmptyClass.php new file mode 100644 index 0000000..e74a0f7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/EmptyClass.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +class EmptyClass +{ +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/FinalClass.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/FinalClass.php new file mode 100644 index 0000000..4b818d6 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/FinalClass.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +final class FinalClass +{ +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/HydratedObject.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/HydratedObject.php new file mode 100644 index 0000000..198bf6c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/HydratedObject.php @@ -0,0 +1,60 @@ + + * @license MIT + */ +class HydratedObject +{ + /** + * @var mixed + */ + public $foo = 1; + + /** + * @var mixed + */ + protected $bar = 2; + + /** + * @var mixed + */ + private $baz = 3; + + /** + * Method to be disabled + */ + public function doFoo() + { + } + + /** + * @param string $name + * + * @return mixed + */ + public function __get($name) + { + return $this->$name; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/LazyLoadingMock.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/LazyLoadingMock.php new file mode 100644 index 0000000..b199f8f --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/LazyLoadingMock.php @@ -0,0 +1,41 @@ + + * @license MIT + */ +class LazyLoadingMock +{ + /** + * @var mixed + */ + public $initializer; + + /** + * @param mixed $initializer + */ + public function __construct($initializer) + { + $this->initializer = $initializer; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/NullObjectMock.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/NullObjectMock.php new file mode 100644 index 0000000..b747f3b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/NullObjectMock.php @@ -0,0 +1,29 @@ + + * @license MIT + */ +class NullObjectMock +{ +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ProxyGenerator/LazyLoading/MethodGenerator/ClassWithTwoPublicProperties.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ProxyGenerator/LazyLoading/MethodGenerator/ClassWithTwoPublicProperties.php new file mode 100644 index 0000000..bb34b58 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/ProxyGenerator/LazyLoading/MethodGenerator/ClassWithTwoPublicProperties.php @@ -0,0 +1,38 @@ + + * @license MIT + */ +class ClassWithTwoPublicProperties +{ + /** + * @var mixed + */ + public $bar; + + /** + * @var mixed + */ + public $baz; +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/BazServiceInterface.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/BazServiceInterface.php new file mode 100644 index 0000000..08df058 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/BazServiceInterface.php @@ -0,0 +1,35 @@ + + * @license MIT + */ +interface BazServiceInterface +{ + /** + * @param string $param + * + * @return string + */ + public function baz($param); +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/Foo.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/Foo.php new file mode 100644 index 0000000..9304f70 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/Foo.php @@ -0,0 +1,56 @@ + + * @license MIT + */ +class Foo implements FooServiceInterface, BazServiceInterface +{ + /** + * @return string + */ + public function foo() + { + return 'bar remote'; + } + + /** + * @param string $param + * + * @return string + */ + public function baz($param) + { + return $param . ' remote'; + } + + /** + * @param string $name + * + * @return string + */ + public function __get($name) + { + return $name . ' remote'; + } +} diff --git a/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/FooServiceInterface.php b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/FooServiceInterface.php new file mode 100644 index 0000000..af0b764 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/ProxyManagerTestAsset/RemoteProxy/FooServiceInterface.php @@ -0,0 +1,33 @@ + + * @license MIT + */ +interface FooServiceInterface +{ + /** + * @return string + */ + public function foo(); +} diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/README.md b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/README.md new file mode 100644 index 0000000..f641e6d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/README.md @@ -0,0 +1,9 @@ +## Integration tests for PHP language features of proxies + +Since proxies are quire complex code, this directory is dedicated +to integration tests that are supposed to cause fatal errors or +failures in general that are hard to handle in traditional +PHPUnit test cases. + +You may find a guide on how to write `.phpt` tests on the +[PHP QA website](http://qa.php.net/write-test.php). \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-isset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-isset.phpt new file mode 100644 index 0000000..b820fa1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-isset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow private property direct isset check +--FILE-- +createProxy(new Kitchen()); + +var_dump(isset($proxy->sweets)); +?> +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-read.phpt new file mode 100644 index 0000000..0dff2c7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-read.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow private property direct read +--FILE-- +createProxy(new Kitchen()); + +$proxy->sweets; +?> +--EXPECTF-- +%SFatal error: Cannot access private property %s::$sweets in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-unset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-unset.phpt new file mode 100644 index 0000000..e6e6eeb --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-unset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow private property direct unset +--FILE-- +createProxy(new Kitchen()); + +unset($proxy->sweets); +?> +--EXPECTF-- +%SFatal error: Cannot %s property %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-write.phpt new file mode 100644 index 0000000..e87d520 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-private-property-write.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow private property direct write +--FILE-- +createProxy(new Kitchen()); + +$proxy->sweets = 'stolen'; +?> +--EXPECTF-- +%SFatal error: Cannot access %s property%S in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-isset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-isset.phpt new file mode 100644 index 0000000..e277887 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-isset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow protected property direct isset check +--FILE-- +createProxy(new Kitchen()); + +var_dump(isset($proxy->sweets)); +?> +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-read.phpt new file mode 100644 index 0000000..ed579f5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-read.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow protected property direct read +--FILE-- +createProxy(new Kitchen()); + +$proxy->sweets; +?> +--EXPECTF-- +%SFatal error: Cannot access protected property %s::$sweets in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-unset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-unset.phpt new file mode 100644 index 0000000..d8fcd49 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-unset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow protected property direct unset +--FILE-- +createProxy(new Kitchen()); + +unset($proxy->sweets); +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-write.phpt new file mode 100644 index 0000000..f288b0b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-denies-protected-property-write.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow protected property direct write +--FILE-- +createProxy(new Kitchen()); + +$proxy->sweets = 'stolen'; +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-isset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-isset.phpt new file mode 100644 index 0000000..d9eef67 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-isset.phpt @@ -0,0 +1,26 @@ +--TEST-- +Verifies that generated access interceptors disallow private property direct isset check +--SKIPIF-- + +--FILE-- +createProxy(new Kitchen()); + +var_dump(isset($proxy->sweets)); +?> +--EXPECT-- +bool(false) diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-read.phpt new file mode 100644 index 0000000..18a6c95 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-read.phpt @@ -0,0 +1,26 @@ +--TEST-- +Verifies that generated access interceptors disallow private property direct read +--SKIPIF-- + +--FILE-- +createProxy(new Kitchen()); + +$proxy->sweets; +?> +--EXPECTF-- +%SFatal error: Cannot access private property %s::$sweets in %s on line %d diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-unset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-unset.phpt new file mode 100644 index 0000000..92a64e2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-unset.phpt @@ -0,0 +1,26 @@ +--TEST-- +Verifies that generated access interceptors disallow private property direct unset +--SKIPIF-- + +--FILE-- +createProxy(new Kitchen()); + +unset($proxy->sweets); +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-write.phpt new file mode 100644 index 0000000..a0b3cc1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-private-property-write.phpt @@ -0,0 +1,26 @@ +--TEST-- +Verifies that generated access interceptors disallow private property direct write +--SKIPIF-- + +--FILE-- +createProxy(new Kitchen()); + +$proxy->sweets = 'stolen'; +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-isset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-isset.phpt new file mode 100644 index 0000000..f5de693 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-isset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow protected property direct isset check +--FILE-- +createProxy(new Kitchen()); + +var_dump(isset($proxy->sweets)); +?> +--EXPECT-- +bool(false) diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-read.phpt new file mode 100644 index 0000000..c97b091 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-read.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow protected property direct read +--FILE-- +createProxy(new Kitchen()); + +$proxy->sweets; +?> +--EXPECTF-- +%SFatal error: Cannot access protected property %s::$sweets in %s on line %d diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-unset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-unset.phpt new file mode 100644 index 0000000..91cc4f9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-unset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow protected property direct unset +--FILE-- +createProxy(new Kitchen()); + +unset($proxy->sweets); +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-write.phpt new file mode 100644 index 0000000..97da739 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-scope-localizer-denies-protected-property-write.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated access interceptors disallow protected property direct write +--FILE-- +createProxy(new Kitchen()); + +$proxy->sweets = 'stolen'; +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-with-cache.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-with-cache.phpt new file mode 100644 index 0000000..0a180b2 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/access-interceptor-with-cache.phpt @@ -0,0 +1,34 @@ +--TEST-- +Verifies that access interceptor proxy file is generated +--FILE-- +setProxiesTargetDir(__DIR__ . '/cache'); +$fileLocator = new \ProxyManager\FileLocator\FileLocator($configuration->getProxiesTargetDir()); +$configuration->setGeneratorStrategy( + new \ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy($fileLocator) +); + +$factory = new \ProxyManager\Factory\AccessInterceptorValueHolderFactory($configuration); + +$proxy = $factory->createProxy(new Kitchen()); + +$filename = $fileLocator->getProxyFileName(get_class($proxy)); +var_dump(file_exists($filename)); + +$proxy = $factory->createProxy(new Kitchen()); + +var_dump(file_exists($filename)); +@unlink($filename); + +?> +--EXPECT-- +bool(true) +bool(true) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/cache/README.md b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/cache/README.md new file mode 100644 index 0000000..dcb0d38 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/cache/README.md @@ -0,0 +1,3 @@ +## Integration tests for PHP language features of proxies + +This folder is used for the caches \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/init.php b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/init.php new file mode 100644 index 0000000..ce6cffc --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/init.php @@ -0,0 +1,10 @@ +setGeneratorStrategy(new EvaluatingGeneratorStrategy()); diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-allows-inexisting-magic-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-allows-inexisting-magic-property-read.phpt new file mode 100644 index 0000000..2f318c7 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-allows-inexisting-magic-property-read.phpt @@ -0,0 +1,25 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow reading non-existing properties via direct read +--FILE-- +createProxy('Kitchen', function () {}); + +echo $proxy->nonExisting; +?> +--EXPECTF-- +nonExisting \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-allows-inexisting-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-allows-inexisting-property-write.phpt new file mode 100644 index 0000000..e815d0f --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-allows-inexisting-property-write.phpt @@ -0,0 +1,21 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow reading non-existing properties via direct read +--FILE-- +createProxy('Kitchen', function () {}); + +$proxy->nonExisting = 'I do not exist'; +echo $proxy->nonExisting; +?> +--EXPECTF-- +I do not exist \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-inexisting-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-inexisting-property-read.phpt new file mode 100644 index 0000000..0b0267b --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-inexisting-property-read.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow reading non-existing properties via direct read +--FILE-- +createProxy('Kitchen', function () {}); + +$proxy->nonExisting; +?> +--EXPECTF-- +%SNotice: Undefined property: Kitchen::$nonExisting in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-isset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-isset.phpt new file mode 100644 index 0000000..05f741c --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-isset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow private property direct isset check +--FILE-- +createProxy('Kitchen', function () {}); + +var_dump(isset($proxy->sweets)); +?> +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-read.phpt new file mode 100644 index 0000000..cdb0d09 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-read.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow private property direct read +--FILE-- +createProxy('Kitchen', function () {}); + +$proxy->sweets; +?> +--EXPECTF-- +%SFatal error: Cannot access private property %s::$sweets in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-unset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-unset.phpt new file mode 100644 index 0000000..18580aa --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-unset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow private property direct unset +--FILE-- +createProxy('Kitchen', function () {}); + +unset($proxy->sweets); +?> +--EXPECTF-- +%SFatal error: Cannot %s property %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-write.phpt new file mode 100644 index 0000000..a081d71 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-private-property-write.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow private property direct write +--FILE-- +createProxy('Kitchen', function () {}); + +$proxy->sweets = 'stolen'; +?> +--EXPECTF-- +%SFatal error: Cannot access %s property%S in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-isset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-isset.phpt new file mode 100644 index 0000000..8fa8485 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-isset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow protected property direct isset check +--FILE-- +createProxy('Kitchen', function () {}); + +var_dump(isset($proxy->sweets)); +?> +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-read.phpt new file mode 100644 index 0000000..e71bb30 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-read.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow protected property direct read +--FILE-- +createProxy('Kitchen', function () {}); + +$proxy->sweets; +?> +--EXPECTF-- +%SFatal error: Cannot access protected property %s::$sweets in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-unset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-unset.phpt new file mode 100644 index 0000000..6e742a1 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-unset.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow protected property direct unset +--FILE-- +createProxy('Kitchen', function () {}); + +unset($proxy->sweets); +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-write.phpt new file mode 100644 index 0000000..580f5a5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-denies-protected-property-write.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated lazy loading ghost objects disallow protected property direct write +--FILE-- +createProxy('Kitchen', function () {}); + +$proxy->sweets = 'stolen'; +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-with-cache.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-with-cache.phpt new file mode 100644 index 0000000..7a60948 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-ghost-with-cache.phpt @@ -0,0 +1,34 @@ +--TEST-- +Verifies that lazy loading ghost proxy file is generated +--FILE-- +setProxiesTargetDir(__DIR__ . '/cache'); +$fileLocator = new \ProxyManager\FileLocator\FileLocator($configuration->getProxiesTargetDir()); +$configuration->setGeneratorStrategy( + new \ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy($fileLocator) +); + +$factory = new \ProxyManager\Factory\LazyLoadingGhostFactory($configuration); + +$proxy = $factory->createProxy('Kitchen', function () {}); + +$filename = $fileLocator->getProxyFileName(get_class($proxy)); +var_dump(file_exists($filename)); + +$proxy = $factory->createProxy('Kitchen', function () {}); + +var_dump(file_exists($filename)); +@unlink($filename); + +?> +--EXPECT-- +bool(true) +bool(true) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-isset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-isset.phpt new file mode 100644 index 0000000..14e7989 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-isset.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated lazy loading value holders disallow private property direct isset check +--FILE-- +createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +var_dump(isset($proxy->sweets)); +?> +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-read.phpt new file mode 100644 index 0000000..7631ec9 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-read.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated lazy loading value holders disallow private property direct read +--FILE-- +createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +$proxy->sweets; +?> +--EXPECTF-- +%SFatal error: Cannot access private property %s::$sweets in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-unset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-unset.phpt new file mode 100644 index 0000000..1454f78 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-unset.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated lazy loading value holders disallow private property direct unset +--FILE-- +createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +unset($proxy->sweets); +?> +--EXPECTF-- +%SFatal error: Cannot %s property %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-write.phpt new file mode 100644 index 0000000..6b90e2f --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-private-property-write.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated lazy loading value holders disallow private property direct write +--FILE-- +createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +$proxy->sweets = 'stolen'; +?> +--EXPECTF-- +%SFatal error: Cannot access %s property %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-isset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-isset.phpt new file mode 100644 index 0000000..d33c0ba --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-isset.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated lazy loading value holders disallow protected property direct isset check +--FILE-- +createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +var_dump(isset($proxy->sweets)); +?> +--EXPECT-- +bool(false) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-read.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-read.phpt new file mode 100644 index 0000000..6f2df1d --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-read.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated lazy loading value holders disallow protected property direct read +--FILE-- +createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +$proxy->sweets; +?> +--EXPECTF-- +%SFatal error: Cannot access protected property %s::$sweets in %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-unset.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-unset.phpt new file mode 100644 index 0000000..238a50f --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-unset.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated lazy loading value holders disallow protected property direct unset +--FILE-- +createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +unset($proxy->sweets); +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-write.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-write.phpt new file mode 100644 index 0000000..f8c2cac --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-denies-protected-property-write.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated lazy loading value holders disallow protected property direct write +--FILE-- +createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +$proxy->sweets = 'stolen'; +?> +--EXPECTF-- +%SFatal error: Cannot %s property%sin %s on line %d \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-internal-php-classes.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-internal-php-classes.phpt new file mode 100644 index 0000000..2a5a777 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-internal-php-classes.phpt @@ -0,0 +1,31 @@ +--TEST-- +Verifies that lazy loading value holder factory can generate proxy for PHP core classes. +--FILE-- +createProxy('Phar', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new PharMock(); + }) + ->compress('Lazy Loaded!'); + +?> +--EXPECT-- +Lazy Loaded! \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-with-cache.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-with-cache.phpt new file mode 100644 index 0000000..06afe47 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/lazy-loading-value-holder-with-cache.phpt @@ -0,0 +1,40 @@ +--TEST-- +Verifies that lazy loading value holder proxy file is generated +--FILE-- +setProxiesTargetDir(__DIR__ . '/cache'); +$fileLocator = new \ProxyManager\FileLocator\FileLocator($configuration->getProxiesTargetDir()); +$configuration->setGeneratorStrategy( + new \ProxyManager\GeneratorStrategy\FileWriterGeneratorStrategy($fileLocator) +); + +$factory = new \ProxyManager\Factory\LazyLoadingValueHolderFactory($configuration); + +$proxy = $factory->createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +$filename = $fileLocator->getProxyFileName(get_class($proxy)); +var_dump(file_exists($filename)); + +$proxy = $factory->createProxy('Kitchen', function (& $wrapped, $proxy, $method, array $parameters, & $initializer) { + $initializer = null; + $wrapped = new Kitchen(); +}); + +var_dump(file_exists($filename)); +@unlink($filename); + +?> +--EXPECT-- +bool(true) +bool(true) \ No newline at end of file diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/null-object-public-function-empty.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/null-object-public-function-empty.phpt new file mode 100644 index 0000000..2453ea5 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/null-object-public-function-empty.phpt @@ -0,0 +1,23 @@ +--TEST-- +Verifies that generated null object disallow public function +--FILE-- +createProxy('Kitchen'); + +var_dump($proxy->foo()); +?> +--EXPECT-- +NULL diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/null-object-public-property-empty.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/null-object-public-property-empty.phpt new file mode 100644 index 0000000..9da1a41 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/null-object-public-property-empty.phpt @@ -0,0 +1,20 @@ +--TEST-- +Verifies that generated null object disallow public function +--FILE-- +createProxy('Kitchen'); + +var_dump($proxy->foo); +?> +--EXPECT-- +NULL diff --git a/vendor/ocramius/proxy-manager/tests/language-feature-scripts/remote-object-json-adapter-denies-unknow-method.phpt b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/remote-object-json-adapter-denies-unknow-method.phpt new file mode 100644 index 0000000..1910a34 --- /dev/null +++ b/vendor/ocramius/proxy-manager/tests/language-feature-scripts/remote-object-json-adapter-denies-unknow-method.phpt @@ -0,0 +1,43 @@ +--TEST-- +Verifies that generated remote object can call public property +--FILE-- +createProxy('ProxyManagerTestAsset\RemoteProxy\FooServiceInterface'); + +var_dump($proxy->foo()); +var_dump($proxy->unknown()); +?> +--EXPECTF-- +string(3) "baz" + +%SFatal error: Call to undefined method %s::unknown%S in %s on line %d diff --git a/vendor/paragonie/random_compat/LICENSE b/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 0000000..45c7017 --- /dev/null +++ b/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/paragonie/random_compat/RATIONALE.md b/vendor/paragonie/random_compat/RATIONALE.md new file mode 100644 index 0000000..a6e7307 --- /dev/null +++ b/vendor/paragonie/random_compat/RATIONALE.md @@ -0,0 +1,42 @@ +## Rationale (Design Decisions) + +### Reasoning Behind the Order of Preferred Random Data Sources + +The order is: + + 1. `libsodium if available` + 2. `fread() /dev/urandom if available` + 3. `mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)` + 4. `COM('CAPICOM.Utilities.1')->GetRandom()` + 5. `openssl_random_pseudo_bytes()` + +If libsodium is available, we get random data from it. This is the preferred +method on all OSes, but libsodium is not very widely installed, so other +fallbacks are available. + +Next, we read `/dev/urandom` (if it exists). This is the preferred file to read +for random data for cryptographic purposes for BSD and Linux. This step +is skipped on Windows, because someone could create a `C:\dev\urandom` +file and PHP would helpfully (but insecurely) return bytes from it. + +Despite [strongly urging people not to use mcrypt in their projects](https://paragonie.com/blog/2015/05/if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong) +(because libmcrypt is abandonware and the API puts too much responsibility on the +implementor) we prioritize `mcrypt_create_iv()` with `MCRYPT_DEV_URANDOM` above +the remaining implementations. + +The reason is simple: `mcrypt_create_iv()` is part of PHP's `ext/mcrypt` code, +and is not part `libmcrypt`. It actually does the right thing: + + * On Unix-based operating systems, it reads from `/dev/urandom` which + (unlike `/dev/random`) is the sane and correct thing to do. + * On Windows, it reads from `CryptGenRandom`, which is an exclusively Windows + way to get random bytes. + +If we're on Windows and don't have access to `mcrypt`, we use `CAPICOM.Utilities.1`. + +Finally, we use `openssl_random_pseudo_bytes()` **as a last resort**, due to +[PHP bug #70014](https://bugs.php.net/bug.php?id=70014). Internally, this +function calls `RAND_pseudo_bytes()`, which has been [deprecated](https://github.com/paragonie/random_compat/issues/5) +by the OpenSSL team. Furthermore, [it might silently return weak random data](https://github.com/paragonie/random_compat/issues/6#issuecomment-119564973) +if it is called before OpenSSL's **userspace** CSPRNG is seeded. Also, +[you want the OS CSPRNG, not a userspace CSPRNG](http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/). diff --git a/vendor/paragonie/random_compat/build-phar.sh b/vendor/paragonie/random_compat/build-phar.sh new file mode 100644 index 0000000..b4a5ba3 --- /dev/null +++ b/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/vendor/paragonie/random_compat/composer.json b/vendor/paragonie/random_compat/composer.json new file mode 100644 index 0000000..d363f4c --- /dev/null +++ b/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,35 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "autoload": { + "files": ["lib/random.php"] + } +} diff --git a/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/vendor/paragonie/random_compat/lib/byte_safe_strings.php new file mode 100644 index 0000000..6de294f --- /dev/null +++ b/vendor/paragonie/random_compat/lib/byte_safe_strings.php @@ -0,0 +1,181 @@ + RandomCompat_strlen($binary_string)) { + return ''; + } + + return (string) mb_substr($binary_string, $start, $length, '8bit'); + } + + } else { + + /** + * substr() implementation that isn't brittle to mbstring.func_overload + * + * This version just uses the default substr() + * + * @param string $binary_string + * @param int $start + * @param int $length (optional) + * + * @throws TypeError + * + * @return string + */ + function RandomCompat_substr($binary_string, $start, $length = null) + { + if (!is_string($binary_string)) { + throw new TypeError( + 'RandomCompat_substr(): First argument should be a string' + ); + } + + if (!is_int($start)) { + throw new TypeError( + 'RandomCompat_substr(): Second argument should be an integer' + ); + } + + if ($length !== null) { + if (!is_int($length)) { + throw new TypeError( + 'RandomCompat_substr(): Third argument should be an integer, or omitted' + ); + } + + return (string) substr($binary_string, $start, $length); + } + + return (string) substr($binary_string, $start); + } + } +} diff --git a/vendor/paragonie/random_compat/lib/cast_to_int.php b/vendor/paragonie/random_compat/lib/cast_to_int.php new file mode 100644 index 0000000..dc4048c --- /dev/null +++ b/vendor/paragonie/random_compat/lib/cast_to_int.php @@ -0,0 +1,74 @@ + operators might accidentally let a float + * through. + * + * @param int|float $number The number we want to convert to an int + * @param boolean $fail_open Set to true to not throw an exception + * + * @return float|int + * + * @throws TypeError + */ + function RandomCompat_intval($number, $fail_open = false) + { + if (is_int($number) || is_float($number)) { + $number += 0; + } elseif (is_numeric($number)) { + $number += 0; + } + + if ( + is_float($number) + && + $number > ~PHP_INT_MAX + && + $number < PHP_INT_MAX + ) { + $number = (int) $number; + } + + if (is_int($number)) { + return (int) $number; + } elseif (!$fail_open) { + throw new TypeError( + 'Expected an integer.' + ); + } + return $number; + } +} diff --git a/vendor/paragonie/random_compat/lib/error_polyfill.php b/vendor/paragonie/random_compat/lib/error_polyfill.php new file mode 100644 index 0000000..17ece7b --- /dev/null +++ b/vendor/paragonie/random_compat/lib/error_polyfill.php @@ -0,0 +1,49 @@ += 70000) { + return; +} + +if (!defined('RANDOM_COMPAT_READ_BUFFER')) { + define('RANDOM_COMPAT_READ_BUFFER', 8); +} + +$RandomCompatDIR = dirname(__FILE__); + +require_once $RandomCompatDIR . '/byte_safe_strings.php'; +require_once $RandomCompatDIR . '/cast_to_int.php'; +require_once $RandomCompatDIR . '/error_polyfill.php'; + +if (!is_callable('random_bytes')) { + /** + * PHP 5.2.0 - 5.6.x way to implement random_bytes() + * + * We use conditional statements here to define the function in accordance + * to the operating environment. It's a micro-optimization. + * + * In order of preference: + * 1. Use libsodium if available. + * 2. fread() /dev/urandom if available (never on Windows) + * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) + * 4. COM('CAPICOM.Utilities.1')->GetRandom() + * + * See RATIONALE.md for our reasoning behind this particular order + */ + if (extension_loaded('libsodium')) { + // See random_bytes_libsodium.php + if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { + require_once $RandomCompatDIR . '/random_bytes_libsodium.php'; + } elseif (method_exists('Sodium', 'randombytes_buf')) { + require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php'; + } + } + + /** + * Reading directly from /dev/urandom: + */ + if (DIRECTORY_SEPARATOR === '/') { + // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast + // way to exclude Windows. + $RandomCompatUrandom = true; + $RandomCompat_basedir = ini_get('open_basedir'); + + if (!empty($RandomCompat_basedir)) { + $RandomCompat_open_basedir = explode( + PATH_SEPARATOR, + strtolower($RandomCompat_basedir) + ); + $RandomCompatUrandom = (array() !== array_intersect( + array('/dev', '/dev/', '/dev/urandom'), + $RandomCompat_open_basedir + )); + $RandomCompat_open_basedir = null; + } + + if ( + !is_callable('random_bytes') + && + $RandomCompatUrandom + && + @is_readable('/dev/urandom') + ) { + // Error suppression on is_readable() in case of an open_basedir + // or safe_mode failure. All we care about is whether or not we + // can read it at this point. If the PHP environment is going to + // panic over trying to see if the file can be read in the first + // place, that is not helpful to us here. + + // See random_bytes_dev_urandom.php + require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php'; + } + // Unset variables after use + $RandomCompat_basedir = null; + } else { + $RandomCompatUrandom = false; + } + + /** + * mcrypt_create_iv() + * + * We only want to use mcypt_create_iv() if: + * + * - random_bytes() hasn't already been defined + * - the mcrypt extensions is loaded + * - One of these two conditions is true: + * - We're on Windows (DIRECTORY_SEPARATOR !== '/') + * - We're not on Windows and /dev/urandom is readabale + * (i.e. we're not in a chroot jail) + * - Special case: + * - If we're not on Windows, but the PHP version is between + * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will + * hang indefinitely. This is bad. + * - If we're on Windows, we want to use PHP >= 5.3.7 or else + * we get insufficient entropy errors. + */ + if ( + !is_callable('random_bytes') + && + // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. + (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) + && + // Prevent this code from hanging indefinitely on non-Windows; + // see https://bugs.php.net/bug.php?id=69833 + ( + DIRECTORY_SEPARATOR !== '/' || + (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) + ) + && + extension_loaded('mcrypt') + ) { + // See random_bytes_mcrypt.php + require_once $RandomCompatDIR . '/random_bytes_mcrypt.php'; + } + $RandomCompatUrandom = null; + + /** + * This is a Windows-specific fallback, for when the mcrypt extension + * isn't loaded. + */ + if ( + !is_callable('random_bytes') + && + extension_loaded('com_dotnet') + && + class_exists('COM') + ) { + $RandomCompat_disabled_classes = preg_split( + '#\s*,\s*#', + strtolower(ini_get('disable_classes')) + ); + + if (!in_array('com', $RandomCompat_disabled_classes)) { + try { + $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); + if (method_exists($RandomCompatCOMtest, 'GetRandom')) { + // See random_bytes_com_dotnet.php + require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php'; + } + } catch (com_exception $e) { + // Don't try to use it. + } + } + $RandomCompat_disabled_classes = null; + $RandomCompatCOMtest = null; + } + + /** + * openssl_random_pseudo_bytes() + */ + if ( + ( + // Unix-like with PHP >= 5.3.0 or + ( + DIRECTORY_SEPARATOR === '/' + && + PHP_VERSION_ID >= 50300 + ) + || + // Windows with PHP >= 5.4.1 + PHP_VERSION_ID >= 50401 + ) + && + !function_exists('random_bytes') + && + extension_loaded('openssl') + ) { + // See random_bytes_openssl.php + require_once $RandomCompatDIR . '/random_bytes_openssl.php'; + } + + /** + * throw new Exception + */ + if (!is_callable('random_bytes')) { + /** + * We don't have any more options, so let's throw an exception right now + * and hope the developer won't let it fail silently. + * + * @param mixed $length + * @return void + * @throws Exception + */ + function random_bytes($length) + { + unset($length); // Suppress "variable not used" warnings. + throw new Exception( + 'There is no suitable CSPRNG installed on your system' + ); + } + } +} + +if (!is_callable('random_int')) { + require_once $RandomCompatDIR . '/random_int.php'; +} + +$RandomCompatDIR = null; diff --git a/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php new file mode 100644 index 0000000..28cc56a --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php @@ -0,0 +1,88 @@ +GetRandom($bytes, 0)); + if (RandomCompat_strlen($buf) >= $bytes) { + /** + * Return our random entropy buffer here: + */ + return RandomCompat_substr($buf, 0, $bytes); + } + ++$execCount; + } while ($execCount < $bytes); + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php new file mode 100644 index 0000000..8bf7034 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php @@ -0,0 +1,150 @@ + 0); + + /** + * Is our result valid? + */ + if ($buf !== false) { + if (RandomCompat_strlen($buf) === $bytes) { + /** + * Return our random entropy buffer here: + */ + return $buf; + } + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Error reading from source device' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php new file mode 100644 index 0000000..7d32b21 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php @@ -0,0 +1,88 @@ + 2147483647) { + $buf = ''; + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= \Sodium\randombytes_buf($n); + } + } else { + $buf = \Sodium\randombytes_buf($bytes); + } + + if ($buf !== false) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php new file mode 100644 index 0000000..ba93c40 --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php @@ -0,0 +1,92 @@ + 2147483647) { + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= Sodium::randombytes_buf($n); + } + } else { + $buf .= Sodium::randombytes_buf($bytes); + } + + if (is_string($buf)) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php new file mode 100644 index 0000000..3bce91a --- /dev/null +++ b/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php @@ -0,0 +1,77 @@ + operators might accidentally let a float + * through. + */ + + try { + $min = RandomCompat_intval($min); + } catch (TypeError $ex) { + throw new TypeError( + 'random_int(): $min must be an integer' + ); + } + + try { + $max = RandomCompat_intval($max); + } catch (TypeError $ex) { + throw new TypeError( + 'random_int(): $max must be an integer' + ); + } + + /** + * Now that we've verified our weak typing system has given us an integer, + * let's validate the logic then we can move forward with generating random + * integers along a given range. + */ + if ($min > $max) { + throw new Error( + 'Minimum value must be less than or equal to the maximum value' + ); + } + + if ($max === $min) { + return $min; + } + + /** + * Initialize variables to 0 + * + * We want to store: + * $bytes => the number of random bytes we need + * $mask => an integer bitmask (for use with the &) operator + * so we can minimize the number of discards + */ + $attempts = $bits = $bytes = $mask = $valueShift = 0; + + /** + * At this point, $range is a positive number greater than 0. It might + * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to + * a float and we will lose some precision. + */ + $range = $max - $min; + + /** + * Test for integer overflow: + */ + if (!is_int($range)) { + + /** + * Still safely calculate wider ranges. + * Provided by @CodesInChaos, @oittaa + * + * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 + * + * We use ~0 as a mask in this case because it generates all 1s + * + * @ref https://eval.in/400356 (32-bit) + * @ref http://3v4l.org/XX9r5 (64-bit) + */ + $bytes = PHP_INT_SIZE; + $mask = ~0; + + } else { + + /** + * $bits is effectively ceil(log($range, 2)) without dealing with + * type juggling + */ + while ($range > 0) { + if ($bits % 8 === 0) { + ++$bytes; + } + ++$bits; + $range >>= 1; + $mask = $mask << 1 | 1; + } + $valueShift = $min; + } + + $val = 0; + /** + * Now that we have our parameters set up, let's begin generating + * random integers until one falls between $min and $max + */ + do { + /** + * The rejection probability is at most 0.5, so this corresponds + * to a failure probability of 2^-128 for a working RNG + */ + if ($attempts > 128) { + throw new Exception( + 'random_int: RNG is broken - too many rejections' + ); + } + + /** + * Let's grab the necessary number of random bytes + */ + $randomByteString = random_bytes($bytes); + + /** + * Let's turn $randomByteString into an integer + * + * This uses bitwise operators (<< and |) to build an integer + * out of the values extracted from ord() + * + * Example: [9F] | [6D] | [32] | [0C] => + * 159 + 27904 + 3276800 + 201326592 => + * 204631455 + */ + $val &= 0; + for ($i = 0; $i < $bytes; ++$i) { + $val |= ord($randomByteString[$i]) << ($i * 8); + } + + /** + * Apply mask + */ + $val &= $mask; + $val += $valueShift; + + ++$attempts; + /** + * If $val overflows to a floating point number, + * ... or is larger than $max, + * ... or smaller than $min, + * then try again. + */ + } while (!is_int($val) || $val > $max || $val < $min); + + return (int)$val; + } +} diff --git a/vendor/paragonie/random_compat/psalm-autoload.php b/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000..d71d1b8 --- /dev/null +++ b/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + diff --git a/vendor/patchwork/utf8/.gitignore b/vendor/patchwork/utf8/.gitignore new file mode 100644 index 0000000..ff72e2d --- /dev/null +++ b/vendor/patchwork/utf8/.gitignore @@ -0,0 +1,2 @@ +/composer.lock +/vendor diff --git a/vendor/patchwork/utf8/LICENSE-APACHE b/vendor/patchwork/utf8/LICENSE-APACHE new file mode 100644 index 0000000..e57634b --- /dev/null +++ b/vendor/patchwork/utf8/LICENSE-APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2015 Nicolas Grekas + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/patchwork/utf8/LICENSE-GPL b/vendor/patchwork/utf8/LICENSE-GPL new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/vendor/patchwork/utf8/LICENSE-GPL @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/vendor/patchwork/utf8/appveyor.yml b/vendor/patchwork/utf8/appveyor.yml new file mode 100644 index 0000000..aba4afd --- /dev/null +++ b/vendor/patchwork/utf8/appveyor.yml @@ -0,0 +1,39 @@ +build: false +platform: x86 +clone_folder: c:\projects\utf8 + +cache: + - c:\php -> appveyor.yml + +branches: + only: + - master + +init: + - SET PATH=c:\php;%PATH% + - SET COMPOSER_NO_INTERACTION=1 + - SET PHP=1 + +install: + - IF EXIST c:\php (SET PHP=0) ELSE (mkdir c:\php) + - cd c:\php + - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/releases/archives/php-5.5.9-nts-Win32-VC11-x86.zip + - IF %PHP%==1 7z x php-5.5.9-nts-Win32-VC11-x86.zip -y >nul + - IF %PHP%==1 del /Q *.zip + - IF %PHP%==1 appveyor DownloadFile https://phar.phpunit.de/phpunit-4.8.15.phar + - IF %PHP%==1 echo @php %%~dp0composer.phar %%* > composer.bat + - IF %PHP%==1 echo @php %%~dp0phpunit-4.8.15.phar %%* > phpunit.bat + - IF %PHP%==1 copy /Y php.ini-development php.ini + - IF %PHP%==1 echo max_execution_time=1200 >> php.ini + - IF %PHP%==1 echo date.timezone="UTC" >> php.ini + - IF %PHP%==1 echo extension_dir=ext >> php.ini + - IF %PHP%==1 echo extension=php_openssl.dll >> php.ini + - IF %PHP%==1 echo extension=php_intl.dll >> php.ini + - IF %PHP%==1 echo extension=php_mbstring.dll >> php.ini + - appveyor DownloadFile https://getcomposer.org/composer.phar + - cd c:\projects\utf8 + - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) + - composer update --prefer-source --no-progress --ansi + +test_script: + - phpunit diff --git a/vendor/patchwork/utf8/composer.json b/vendor/patchwork/utf8/composer.json new file mode 100644 index 0000000..6e0e64b --- /dev/null +++ b/vendor/patchwork/utf8/composer.json @@ -0,0 +1,36 @@ +{ + "name": "patchwork/utf8", + "type": "library", + "description": "Portable and performant UTF-8, Unicode and Grapheme Clusters for PHP", + "keywords": ["utf8","utf-8","unicode","i18n","grapheme"], + "homepage": "https://github.com/tchwork/utf8", + "license": "(Apache-2.0 or GPL-2.0)", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + } + ], + "require": { + "php": ">=5.3.0", + "lib-pcre": ">=7.3" + }, + "suggest": { + "ext-wfio": "Use WFIO for UTF-8 filesystem access on Windows", + "ext-intl": "Use Intl for best performance", + "ext-iconv": "Use iconv for best performance", + "ext-mbstring": "Use Mbstring for best performance" + }, + "autoload": { + "psr-4": {"Patchwork\\": "src/Patchwork/"}, + "classmap": ["src/Normalizer.php"] + }, + "autoload-dev": { + "files": ["tests/bootstrap.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + } +} diff --git a/vendor/patchwork/utf8/src/Normalizer.php b/vendor/patchwork/utf8/src/Normalizer.php new file mode 100644 index 0000000..7c14662 --- /dev/null +++ b/vendor/patchwork/utf8/src/Normalizer.php @@ -0,0 +1,18 @@ + 'utf-8', + 'ascii' => 'us-ascii', + 'tis-620' => 'iso-8859-11', + 'cp1250' => 'windows-1250', + 'cp1251' => 'windows-1251', + 'cp1252' => 'windows-1252', + 'cp1253' => 'windows-1253', + 'cp1254' => 'windows-1254', + 'cp1255' => 'windows-1255', + 'cp1256' => 'windows-1256', + 'cp1257' => 'windows-1257', + 'cp1258' => 'windows-1258', + 'shift-jis' => 'cp932', + 'shift_jis' => 'cp932', + 'latin1' => 'iso-8859-1', + 'latin2' => 'iso-8859-2', + 'latin3' => 'iso-8859-3', + 'latin4' => 'iso-8859-4', + 'latin5' => 'iso-8859-9', + 'latin6' => 'iso-8859-10', + 'latin7' => 'iso-8859-13', + 'latin8' => 'iso-8859-14', + 'latin9' => 'iso-8859-15', + 'latin10' => 'iso-8859-16', + 'iso8859-1' => 'iso-8859-1', + 'iso8859-2' => 'iso-8859-2', + 'iso8859-3' => 'iso-8859-3', + 'iso8859-4' => 'iso-8859-4', + 'iso8859-5' => 'iso-8859-5', + 'iso8859-6' => 'iso-8859-6', + 'iso8859-7' => 'iso-8859-7', + 'iso8859-8' => 'iso-8859-8', + 'iso8859-9' => 'iso-8859-9', + 'iso8859-10' => 'iso-8859-10', + 'iso8859-11' => 'iso-8859-11', + 'iso8859-12' => 'iso-8859-12', + 'iso8859-13' => 'iso-8859-13', + 'iso8859-14' => 'iso-8859-14', + 'iso8859-15' => 'iso-8859-15', + 'iso8859-16' => 'iso-8859-16', + 'iso_8859-1' => 'iso-8859-1', + 'iso_8859-2' => 'iso-8859-2', + 'iso_8859-3' => 'iso-8859-3', + 'iso_8859-4' => 'iso-8859-4', + 'iso_8859-5' => 'iso-8859-5', + 'iso_8859-6' => 'iso-8859-6', + 'iso_8859-7' => 'iso-8859-7', + 'iso_8859-8' => 'iso-8859-8', + 'iso_8859-9' => 'iso-8859-9', + 'iso_8859-10' => 'iso-8859-10', + 'iso_8859-11' => 'iso-8859-11', + 'iso_8859-12' => 'iso-8859-12', + 'iso_8859-13' => 'iso-8859-13', + 'iso_8859-14' => 'iso-8859-14', + 'iso_8859-15' => 'iso-8859-15', + 'iso_8859-16' => 'iso-8859-16', + 'iso88591' => 'iso-8859-1', + 'iso88592' => 'iso-8859-2', + 'iso88593' => 'iso-8859-3', + 'iso88594' => 'iso-8859-4', + 'iso88595' => 'iso-8859-5', + 'iso88596' => 'iso-8859-6', + 'iso88597' => 'iso-8859-7', + 'iso88598' => 'iso-8859-8', + 'iso88599' => 'iso-8859-9', + 'iso885910' => 'iso-8859-10', + 'iso885911' => 'iso-8859-11', + 'iso885912' => 'iso-8859-12', + 'iso885913' => 'iso-8859-13', + 'iso885914' => 'iso-8859-14', + 'iso885915' => 'iso-8859-15', + 'iso885916' => 'iso-8859-16', + ); + private static $translitMap = array(); + private static $convertMap = array(); + private static $errorHandler; + private static $lastError; + + private static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + private static $isValidUtf8; + + public static function iconv($inCharset, $outCharset, $str) + { + if ('' === $str .= '') { + return ''; + } + + // Prepare for //IGNORE and //TRANSLIT + + $translit = $ignore = ''; + + $outCharset = strtolower($outCharset); + $inCharset = strtolower($inCharset); + + if ('' === $outCharset) { + $outCharset = 'iso-8859-1'; + } + if ('' === $inCharset) { + $inCharset = 'iso-8859-1'; + } + + if ('//translit' === substr($outCharset, -10)) { + $translit = '//TRANSLIT'; + $outCharset = substr($outCharset, 0, -10); + } + + if ('//ignore' === substr($outCharset, -8)) { + $ignore = '//IGNORE'; + $outCharset = substr($outCharset, 0, -8); + } + + if ('//translit' === substr($inCharset, -10)) { + $inCharset = substr($inCharset, 0, -10); + } + if ('//ignore' === substr($inCharset, -8)) { + $inCharset = substr($inCharset, 0, -8); + } + + if (isset(self::$alias[ $inCharset])) { + $inCharset = self::$alias[ $inCharset]; + } + if (isset(self::$alias[$outCharset])) { + $outCharset = self::$alias[$outCharset]; + } + + // Load charset maps + + if (('utf-8' !== $inCharset && !self::loadMap('from.', $inCharset, $inMap)) + || ('utf-8' !== $outCharset && !self::loadMap('to.', $outCharset, $outMap))) { + trigger_error(sprintf(self::ERROR_WRONG_CHARSET, $inCharset, $outCharset)); + + return false; + } + + if ('utf-8' !== $inCharset) { + // Convert input to UTF-8 + $result = ''; + if (self::mapToUtf8($result, $inMap, $str, $ignore)) { + $str = $result; + } else { + $str = false; + } + self::$isValidUtf8 = true; + } else { + self::$isValidUtf8 = preg_match('//u', $str); + + if (!self::$isValidUtf8 && !$ignore) { + trigger_error(self::ERROR_ILLEGAL_CHARACTER); + + return false; + } + + if ('utf-8' === $outCharset) { + // UTF-8 validation + $str = self::utf8ToUtf8($str, $ignore); + } + } + + if ('utf-8' !== $outCharset && false !== $str) { + // Convert output to UTF-8 + $result = ''; + if (self::mapFromUtf8($result, $outMap, $str, $ignore, $translit)) { + return $result; + } + + return false; + } + + return $str; + } + + public static function iconv_mime_decode_headers($str, $mode = 0, $charset = null) + { + if (null === $charset) { + $charset = self::$internalEncoding; + } + + if (false !== strpos($str, "\r")) { + $str = strtr(str_replace("\r\n", "\n", $str), "\r", "\n"); + } + $str = explode("\n\n", $str, 2); + + $headers = array(); + + $str = preg_split('/\n(?![ \t])/', $str[0]); + foreach ($str as $str) { + $str = self::iconv_mime_decode($str, $mode, $charset); + if (false === $str) { + return false; + } + $str = explode(':', $str, 2); + + if (2 === count($str)) { + if (isset($headers[$str[0]])) { + if (!is_array($headers[$str[0]])) { + $headers[$str[0]] = array($headers[$str[0]]); + } + $headers[$str[0]][] = ltrim($str[1]); + } else { + $headers[$str[0]] = ltrim($str[1]); + } + } + } + + return $headers; + } + + public static function iconv_mime_decode($str, $mode = 0, $charset = null) + { + if (null === $charset) { + $charset = self::$internalEncoding; + } + if (ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode) { + $charset .= '//IGNORE'; + } + + if (false !== strpos($str, "\r")) { + $str = strtr(str_replace("\r\n", "\n", $str), "\r", "\n"); + } + $str = preg_split('/\n(?![ \t])/', rtrim($str), 2); + $str = preg_replace('/[ \t]*\n[ \t]+/', ' ', rtrim($str[0])); + $str = preg_split('/=\?([^?]+)\?([bqBQ])\?(.*?)\?=/', $str, -1, PREG_SPLIT_DELIM_CAPTURE); + + $result = self::iconv('utf-8', $charset, $str[0]); + if (false === $result) { + return false; + } + + $i = 1; + $len = count($str); + + while ($i < $len) { + $c = strtolower($str[$i]); + if ((ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode) + && 'utf-8' !== $c + && !isset(self::$alias[$c]) + && !self::loadMap('from.', $c, $d)) { + $d = false; + } elseif ('B' === strtoupper($str[$i + 1])) { + $d = base64_decode($str[$i + 2]); + } else { + $d = rawurldecode(strtr(str_replace('%', '%25', $str[$i + 2]), '=_', '% ')); + } + + if (false !== $d) { + if ('' !== $d) { + if ('' === $d = self::iconv($c, $charset, $d)) { + $str[$i + 3] = substr($str[$i + 3], 1); + } else { + $result .= $d; + } + } + $d = self::iconv('utf-8', $charset, $str[$i + 3]); + if ('' !== trim($d)) { + $result .= $d; + } + } elseif (ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode) { + $result .= "=?{$str[$i]}?{$str[$i + 1]}?{$str[$i + 2]}?={$str[$i + 3]}"; + } else { + $result = false; + break; + } + + $i += 4; + } + + return $result; + } + + public static function iconv_get_encoding($type = 'all') + { + switch ($type) { + case 'input_encoding': return self::$inputEncoding; + case 'output_encoding': return self::$outputEncoding; + case 'internal_encoding': return self::$internalEncoding; + } + + return array( + 'input_encoding' => self::$inputEncoding, + 'output_encoding' => self::$outputEncoding, + 'internal_encoding' => self::$internalEncoding, + ); + } + + public static function iconv_set_encoding($type, $charset) + { + switch ($type) { + case 'input_encoding': self::$inputEncoding = $charset; break; + case 'output_encoding': self::$outputEncoding = $charset; break; + case 'internal_encoding': self::$internalEncoding = $charset; break; + + default: return false; + } + + return true; + } + + public static function iconv_mime_encode($fieldName, $fieldValue, $pref = null) + { + if (!is_array($pref)) { + $pref = array(); + } + + $pref += array( + 'scheme' => 'B', + 'input-charset' => self::$internalEncoding, + 'output-charset' => self::$internalEncoding, + 'line-length' => 76, + 'line-break-chars' => "\r\n", + ); + + if (preg_match('/[\x80-\xFF]/', $fieldName)) { + $fieldName = ''; + } + + $scheme = strtoupper(substr($pref['scheme'], 0, 1)); + $in = strtolower($pref['input-charset']); + $out = strtolower($pref['output-charset']); + + if ('utf-8' !== $in && false === $fieldValue = self::iconv($in, 'utf-8', $fieldValue)) { + return false; + } + + preg_match_all('/./us', $fieldValue, $chars); + + $chars = isset($chars[0]) ? $chars[0] : array(); + + $lineBreak = (int) $pref['line-length']; + $lineStart = "=?{$pref['output-charset']}?{$scheme}?"; + $lineLength = strlen($fieldName) + 2 + strlen($lineStart) + 2; + $lineOffset = strlen($lineStart) + 3; + $lineData = ''; + + $fieldValue = array(); + + $Q = 'Q' === $scheme; + + foreach ($chars as $c) { + if ('utf-8' !== $out && false === $c = self::iconv('utf-8', $out, $c)) { + return false; + } + + $o = $Q + ? $c = preg_replace_callback( + '/[=_\?\x00-\x1F\x80-\xFF]/', + array(__CLASS__, 'qpByteCallback'), + $c + ) + : base64_encode($lineData.$c); + + if (isset($o[$lineBreak - $lineLength])) { + if (!$Q) { + $lineData = base64_encode($lineData); + } + $fieldValue[] = $lineStart.$lineData.'?='; + $lineLength = $lineOffset; + $lineData = ''; + } + + $lineData .= $c; + $Q && $lineLength += strlen($c); + } + + if ('' !== $lineData) { + if (!$Q) { + $lineData = base64_encode($lineData); + } + $fieldValue[] = $lineStart.$lineData.'?='; + } + + return $fieldName.': '.implode($pref['line-break-chars'].' ', $fieldValue); + } + + public static function ob_iconv_handler($buffer, $mode) + { + return self::iconv(self::$internalEncoding, self::$outputEncoding, $buffer); + } + + public static function iconv_strlen($s, $encoding = null) + { + static $hasXml = null; + if (null === $hasXml) { + $hasXml = extension_loaded('xml'); + } + + if ($hasXml) { + return self::strlen1($s, $encoding); + } + + return self::strlen2($s, $encoding); + } + + public static function strlen1($s, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + if (0 !== stripos($encoding, 'utf-8') && false === $s = self::iconv($encoding, 'utf-8', $s)) { + return false; + } + + return strlen(utf8_decode($s)); + } + + public static function strlen2($s, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + if (0 !== stripos($encoding, 'utf-8') && false === $s = self::iconv($encoding, 'utf-8', $s)) { + return false; + } + + $ulenMask = self::$ulenMask; + + $i = 0; + $j = 0; + $len = strlen($s); + + while ($i < $len) { + $u = $s[$i] & "\xF0"; + $i += isset($ulenMask[$u]) ? $ulenMask[$u] : 1; + ++$j; + } + + return $j; + } + + public static function iconv_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + + if (0 !== stripos($encoding, 'utf-8')) { + if (false === $haystack = self::iconv($encoding, 'utf-8', $haystack)) { + return false; + } + if (false === $needle = self::iconv($encoding, 'utf-8', $needle)) { + return false; + } + } + + if ($offset = (int) $offset) { + $haystack = self::iconv_substr($haystack, $offset, 2147483647, 'utf-8'); + } + $pos = strpos($haystack, $needle); + + return false === $pos ? false : ($offset + ($pos ? self::iconv_strlen(substr($haystack, 0, $pos), 'utf-8') : 0)); + } + + public static function iconv_strrpos($haystack, $needle, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + + if (0 !== stripos($encoding, 'utf-8')) { + if (false === $haystack = self::iconv($encoding, 'utf-8', $haystack)) { + return false; + } + if (false === $needle = self::iconv($encoding, 'utf-8', $needle)) { + return false; + } + } + + $pos = isset($needle[0]) ? strrpos($haystack, $needle) : false; + + return false === $pos ? false : self::iconv_strlen($pos ? substr($haystack, 0, $pos) : $haystack, 'utf-8'); + } + + public static function iconv_substr($s, $start, $length = 2147483647, $encoding = null) + { + if (null === $encoding) { + $encoding = self::$internalEncoding; + } + if (0 !== stripos($encoding, 'utf-8')) { + $encoding = null; + } elseif (false === $s = self::iconv($encoding, 'utf-8', $s)) { + return false; + } + + $s .= ''; + $slen = self::iconv_strlen($s, 'utf-8'); + $start = (int) $start; + + if (0 > $start) { + $start += $slen; + } + if (0 > $start) { + return false; + } + if ($start >= $slen) { + return false; + } + + $rx = $slen - $start; + + if (0 > $length) { + $length += $rx; + } + if (0 === $length) { + return ''; + } + if (0 > $length) { + return false; + } + + if ($length > $rx) { + $length = $rx; + } + + $rx = '/^'.($start ? self::pregOffset($start) : '').'('.self::pregOffset($length).')/u'; + + $s = preg_match($rx, $s, $s) ? $s[1] : ''; + + if (null === $encoding) { + return $s; + } + + return self::iconv('utf-8', $encoding, $s); + } + + private static function loadMap($type, $charset, &$map) + { + if (!isset(self::$convertMap[$type.$charset])) { + if (false === $map = self::getData($type.$charset)) { + if ('to.' === $type && self::loadMap('from.', $charset, $map)) { + $map = array_flip($map); + } else { + return false; + } + } + + self::$convertMap[$type.$charset] = $map; + } else { + $map = self::$convertMap[$type.$charset]; + } + + return true; + } + + private static function utf8ToUtf8($str, $ignore) + { + $ulenMask = self::$ulenMask; + $valid = self::$isValidUtf8; + + $u = $str; + $i = $j = 0; + $len = strlen($str); + + while ($i < $len) { + if ($str[$i] < "\x80") { + $u[$j++] = $str[$i++]; + } else { + $ulen = $str[$i] & "\xF0"; + $ulen = isset($ulenMask[$ulen]) ? $ulenMask[$ulen] : 1; + $uchr = substr($str, $i, $ulen); + + if (1 === $ulen || !($valid || preg_match('/^.$/us', $uchr))) { + if ($ignore) { + ++$i; + continue; + } + + trigger_error(self::ERROR_ILLEGAL_CHARACTER); + + return false; + } else { + $i += $ulen; + } + + $u[$j++] = $uchr[0]; + + isset($uchr[1]) && 0 !== ($u[$j++] = $uchr[1]) + && isset($uchr[2]) && 0 !== ($u[$j++] = $uchr[2]) + && isset($uchr[3]) && 0 !== ($u[$j++] = $uchr[3]); + } + } + + return substr($u, 0, $j); + } + + private static function mapToUtf8(&$result, $map, $str, $ignore) + { + $len = strlen($str); + for ($i = 0; $i < $len; ++$i) { + if (isset($str[$i + 1], $map[$str[$i].$str[$i + 1]])) { + $result .= $map[$str[$i].$str[++$i]]; + } elseif (isset($map[$str[$i]])) { + $result .= $map[$str[$i]]; + } elseif (!$ignore) { + trigger_error(self::ERROR_ILLEGAL_CHARACTER); + + return false; + } + } + + return true; + } + + private static function mapFromUtf8(&$result, $map, $str, $ignore, $translit) + { + $ulenMask = self::$ulenMask; + $valid = self::$isValidUtf8; + + if ($translit && !self::$translitMap) { + self::$translitMap = self::getData('translit'); + } + + $i = 0; + $len = strlen($str); + + while ($i < $len) { + if ($str[$i] < "\x80") { + $uchr = $str[$i++]; + } else { + $ulen = $str[$i] & "\xF0"; + $ulen = isset($ulenMask[$ulen]) ? $ulenMask[$ulen] : 1; + $uchr = substr($str, $i, $ulen); + + if ($ignore && (1 === $ulen || !($valid || preg_match('/^.$/us', $uchr)))) { + ++$i; + continue; + } else { + $i += $ulen; + } + } + + if (isset($map[$uchr])) { + $result .= $map[$uchr]; + } elseif ($translit) { + if (isset(self::$translitMap[$uchr])) { + $uchr = self::$translitMap[$uchr]; + } elseif ($uchr >= "\xC3\x80") { + $uchr = \Normalizer::normalize($uchr, \Normalizer::NFD); + + if ($uchr[0] < "\x80") { + $uchr = $uchr[0]; + } elseif ($ignore) { + continue; + } else { + return false; + } + } + + $str = $uchr.substr($str, $i); + $len = strlen($str); + $i = 0; + } elseif (!$ignore) { + return false; + } + } + + return true; + } + + private static function qpByteCallback($m) + { + return '='.strtoupper(dechex(ord($m[0]))); + } + + private static function pregOffset($offset) + { + $rx = array(); + $offset = (int) $offset; + + while ($offset > 65535) { + $rx[] = '.{65535}'; + $offset -= 65535; + } + + return implode('', $rx).'.{'.$offset.'}'; + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/charset/'.$file.'.ser')) { + return unserialize(file_get_contents($file)); + } + + return false; + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Intl.php b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Intl.php new file mode 100644 index 0000000..4755184 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Intl.php @@ -0,0 +1,232 @@ + $size || 0 > $start || 0 > $type || 2 < $type) { + return false; + } + if (0 === $size) { + return ''; + } + + $next = $start; + + $s = preg_split('/('.GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + + if (!isset($s[1])) { + return false; + } + + $i = 1; + $ret = ''; + + do { + if (GRAPHEME_EXTR_COUNT === $type) { + --$size; + } elseif (GRAPHEME_EXTR_MAXBYTES === $type) { + $size -= strlen($s[$i]); + } else { + $size -= iconv_strlen($s[$i], 'UTF-8//IGNORE'); + } + + if ($size >= 0) { + $ret .= $s[$i]; + } + } while (isset($s[++$i]) && $size > 0); + + $next += strlen($ret); + + return $ret; + } + + public static function grapheme_strlen($s) + { + preg_replace('/'.GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len); + + return 0 === $len && '' !== $s ? null : $len; + } + + public static function grapheme_substr($s, $start, $len = 2147483647) + { + preg_match_all('/'.GRAPHEME_CLUSTER_RX.'/u', $s, $s); + + $slen = count($s[0]); + $start = (int) $start; + + if (0 > $start) { + $start += $slen; + } + if (0 > $start) { + return false; + } + if ($start >= $slen) { + return false; + } + + $rem = $slen - $start; + + if (0 > $len) { + $len += $rem; + } + if (0 === $len) { + return ''; + } + if (0 > $len) { + return false; + } + if ($len > $rem) { + $len = $rem; + } + + return implode('', array_slice($s[0], $start, $len)); + } + + public static function grapheme_substr_workaround62759($s, $start, $len) + { + // Intl based http://bugs.php.net/62759 and 55562 workaround + + if (2147483647 == $len) { + return grapheme_substr($s, $start); + } + + $s .= ''; + $slen = grapheme_strlen($s); + $start = (int) $start; + + if (0 > $start) { + $start += $slen; + } + if (0 > $start) { + return false; + } + if ($start >= $slen) { + return false; + } + + $rem = $slen - $start; + + if (0 > $len) { + $len += $rem; + } + if (0 === $len) { + return ''; + } + if (0 > $len) { + return false; + } + if ($len > $rem) { + $len = $rem; + } + + return grapheme_substr($s, $start, $len); + } + + public static function grapheme_strpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 0); + } + + public static function grapheme_stripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 1); + } + + public static function grapheme_strrpos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 2); + } + + public static function grapheme_strripos($s, $needle, $offset = 0) + { + return self::grapheme_position($s, $needle, $offset, 3); + } + + public static function grapheme_stristr($s, $needle, $beforeNeedle = false) + { + return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + public static function grapheme_strstr($s, $needle, $beforeNeedle = false) + { + return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8'); + } + + private static function grapheme_position($s, $needle, $offset, $mode) + { + if (!preg_match('/./us', $needle .= '')) { + return false; + } + if (!preg_match('/./us', $s .= '')) { + return false; + } + if ($offset > 0) { + $s = self::grapheme_substr($s, $offset); + } elseif ($offset < 0) { + if (defined('HHVM_VERSION_ID') || PHP_VERSION_ID < 50535 || (50600 <= PHP_VERSION_ID && PHP_VERSION_ID < 50621) || (70000 <= PHP_VERSION_ID && PHP_VERSION_ID < 70006)) { + $offset = 0; + } else { + return false; + } + } + + switch ($mode) { + case 0: $needle = iconv_strpos($s, $needle, 0, 'UTF-8'); break; + case 1: $needle = mb_stripos($s, $needle, 0, 'UTF-8'); break; + case 2: $needle = iconv_strrpos($s, $needle, 'UTF-8'); break; + default: $needle = mb_strripos($s, $needle, 0, 'UTF-8'); break; + } + + return $needle ? self::grapheme_strlen(iconv_substr($s, 0, $needle, 'UTF-8')) + $offset : $needle; + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Mbstring.php b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Mbstring.php new file mode 100644 index 0000000..4850cf4 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Mbstring.php @@ -0,0 +1,602 @@ + 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $i = 0; + $len = strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding, $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { + self::$internalEncoding = $encoding; + + return true; + } + + return false; + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($lang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $lang; + + return true; + } + + return false; + } + + public static function mb_list_encodings() + { + return array('UTF-8'); + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return array('utf8'); + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + return iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('' === $needle .= '') { + trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + + return false; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (0 === strcasecmp($c, 'none')) { + return true; + } + + return null !== $c ? false : 'none'; + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return iconv_substr($s, $start, $length, $encoding).''; + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = array( + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ); + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback($m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case_lower($s) + { + return self::mb_convert_case($s[0], MB_CASE_LOWER, 'UTF-8'); + } + + private static function title_case_upper($s) + { + return self::mb_convert_case($s[0], MB_CASE_UPPER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/unidata/'.$file.'.ser')) { + return unserialize(file_get_contents($file)); + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Normalizer.php b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Normalizer.php new file mode 100644 index 0000000..a097036 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Normalizer.php @@ -0,0 +1,301 @@ + 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"; + + public static function isNormalized($s, $form = self::NFC) + { + if ($form <= self::NONE || self::NFKC < $form) { + return false; + } + if (!isset($s[strspn($s .= '', self::$ASCII)])) { + return true; + } + if (self::NFC === $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) { + return true; + } + + return false; // Pretend false as quick checks implementented in PHP won't be so quick + } + + public static function normalize($s, $form = self::NFC) + { + if (!preg_match('//u', $s .= '')) { + return false; + } + + switch ($form) { + case self::NONE: return $s; + case self::NFC: $C = true; $K = false; break; + case self::NFD: $C = false; $K = false; break; + case self::NFKC: $C = true; $K = true; break; + case self::NFKD: $C = false; $K = true; break; + default: return false; + } + + if ('' === $s) { + return ''; + } + + if ($K && null === self::$KD) { + self::$KD = self::getData('compatibilityDecomposition'); + } + + if (null === self::$D) { + self::$D = self::getData('canonicalDecomposition'); + self::$cC = self::getData('combiningClass'); + } + + if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) { + mb_internal_encoding('8bit'); + } + + $r = self::decompose($s, $K); + + if ($C) { + if (null === self::$C) { + self::$C = self::getData('canonicalComposition'); + } + + $r = self::recompose($r); + } + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + return $r; + } + + private static function recompose($s) + { + $ASCII = self::$ASCII; + $compMap = self::$C; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + + $result = $tail = ''; + + $i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"]; + $len = strlen($s); + + $lastUchr = substr($s, 0, $i); + $lastUcls = isset($combClass[$lastUchr]) ? 256 : 0; + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + if ($j = strspn($s, $ASCII, $i + 1)) { + $lastUchr .= substr($s, $i, $j); + $i += $j; + } + + $result .= $lastUchr; + $lastUchr = $s[$i]; + $lastUcls = 0; + ++$i; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + + if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr + || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr + || $lastUcls) { + // Table lookup and combining chars composition + + $ucls = isset($combClass[$uchr]) ? $combClass[$uchr] : 0; + + if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) { + $lastUchr = $compMap[$lastUchr.$uchr]; + } elseif ($lastUcls = $ucls) { + $tail .= $uchr; + } else { + if ($tail) { + $lastUchr .= $tail; + $tail = ''; + } + + $result .= $lastUchr; + $lastUchr = $uchr; + } + } else { + // Hangul chars + + $L = ord($lastUchr[2]) - 0x80; + $V = ord($uchr[2]) - 0xA1; + $T = 0; + + $uchr = substr($s, $i + $ulen, 3); + + if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") { + $T = ord($uchr[2]) - 0xA7; + 0 > $T && $T += 0x40; + $ulen += 3; + } + + $L = 0xAC00 + ($L * 21 + $V) * 28 + $T; + $lastUchr = chr(0xE0 | $L >> 12).chr(0x80 | $L >> 6 & 0x3F).chr(0x80 | $L & 0x3F); + } + + $i += $ulen; + } + + return $result.$lastUchr.$tail; + } + + private static function decompose($s, $c) + { + $result = ''; + + $ASCII = self::$ASCII; + $decompMap = self::$D; + $combClass = self::$cC; + $ulenMask = self::$ulenMask; + if ($c) { + $compatMap = self::$KD; + } + + $c = array(); + $i = 0; + $len = strlen($s); + + while ($i < $len) { + if ($s[$i] < "\x80") { + // ASCII chars + + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = array(); + } + + $j = 1 + strspn($s, $ASCII, $i + 1); + $result .= substr($s, $i, $j); + $i += $j; + continue; + } + + $ulen = $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) { + // Table lookup + + if ($uchr !== $j = isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$uchr]) ? $decompMap[$uchr] : $uchr)) { + $uchr = $j; + + $j = strlen($uchr); + $ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"]; + + if ($ulen != $j) { + // Put trailing chars in $s + + $j -= $ulen; + $i -= $j; + + if (0 > $i) { + $s = str_repeat(' ', -$i).$s; + $len -= $i; + $i = 0; + } + + while ($j--) { + $s[$i + $j] = $uchr[$ulen + $j]; + } + + $uchr = substr($uchr, 0, $ulen); + } + } + if (isset($combClass[$uchr])) { + // Combining chars, for sorting + + if (!isset($c[$combClass[$uchr]])) { + $c[$combClass[$uchr]] = ''; + } + $c[$combClass[$uchr]] .= $uchr; + continue; + } + } else { + // Hangul chars + + $uchr = unpack('C*', $uchr); + $j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80; + + $uchr = "\xE1\x84".chr(0x80 + (int) ($j / 588)) + ."\xE1\x85".chr(0xA1 + (int) (($j % 588) / 28)); + + if ($j %= 28) { + $uchr .= $j < 25 + ? ("\xE1\x86".chr(0xA7 + $j)) + : ("\xE1\x87".chr(0x67 + $j)); + } + } + if ($c) { + ksort($c); + $result .= implode('', $c); + $c = array(); + } + + $result .= $uchr; + } + + if ($c) { + ksort($c); + $result .= implode('', $c); + } + + return $result; + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/unidata/'.$file.'.ser')) { + return unserialize(file_get_contents($file)); + } + + return false; + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Xml.php b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Xml.php new file mode 100644 index 0000000..bb028fa --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/Xml.php @@ -0,0 +1,61 @@ +> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = chr(ord($s[$i]) - 64); break; + } + } + + return substr($s, 0, $j); + } + + public static function utf8_decode($s) + { + $s .= ''; + $len = strlen($s); + + for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { + switch ($s[$i] & "\xF0") { + case "\xC0": + case "\xD0": + $c = (ord($s[$i] & "\x1F") << 6) | ord($s[++$i] & "\x3F"); + $s[$j] = $c < 256 ? chr($c) : '?'; + break; + + case "\xF0": ++$i; + case "\xE0": + $s[$j] = '?'; + $i += 2; + break; + + default: + $s[$j] = $s[$i]; + } + } + + return substr($s, 0, $j); + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.big5.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.big5.ser new file mode 100644 index 0000000..379bc53 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.big5.ser @@ -0,0 +1 @@ +a:13710:{s:2:"@";s:3:" ";s:2:"A";s:3:",";s:2:"B";s:3:"、";s:2:"C";s:3:"。";s:2:"D";s:3:".";s:2:"E";s:3:"•";s:2:"F";s:3:";";s:2:"G";s:3:":";s:2:"H";s:3:"?";s:2:"I";s:3:"!";s:2:"J";s:3:"︰";s:2:"K";s:3:"…";s:2:"L";s:3:"‥";s:2:"M";s:3:"﹐";s:2:"N";s:3:"、";s:2:"O";s:3:"﹒";s:2:"P";s:2:"·";s:2:"Q";s:3:"﹔";s:2:"R";s:3:"﹕";s:2:"S";s:3:"﹖";s:2:"T";s:3:"﹗";s:2:"U";s:3:"|";s:2:"V";s:3:"–";s:2:"W";s:3:"︱";s:2:"X";s:3:"—";s:2:"Y";s:3:"︳";s:2:"Z";s:3:"�";s:2:"[";s:3:"︴";s:2:"\";s:3:"﹏";s:2:"]";s:3:"(";s:2:"^";s:3:")";s:2:"_";s:3:"︵";s:2:"`";s:3:"︶";s:2:"a";s:3:"{";s:2:"b";s:3:"}";s:2:"c";s:3:"︷";s:2:"d";s:3:"︸";s:2:"e";s:3:"〔";s:2:"f";s:3:"〕";s:2:"g";s:3:"︹";s:2:"h";s:3:"︺";s:2:"i";s:3:"【";s:2:"j";s:3:"】";s:2:"k";s:3:"︻";s:2:"l";s:3:"︼";s:2:"m";s:3:"《";s:2:"n";s:3:"》";s:2:"o";s:3:"︽";s:2:"p";s:3:"︾";s:2:"q";s:3:"〈";s:2:"r";s:3:"〉";s:2:"s";s:3:"︿";s:2:"t";s:3:"﹀";s:2:"u";s:3:"「";s:2:"v";s:3:"」";s:2:"w";s:3:"﹁";s:2:"x";s:3:"﹂";s:2:"y";s:3:"『";s:2:"z";s:3:"』";s:2:"{";s:3:"﹃";s:2:"|";s:3:"﹄";s:2:"}";s:3:"﹙";s:2:"~";s:3:"﹚";s:2:"";s:3:"﹛";s:2:"";s:3:"﹜";s:2:"";s:3:"﹝";s:2:"";s:3:"﹞";s:2:"";s:3:"‘";s:2:"";s:3:"’";s:2:"";s:3:"“";s:2:"";s:3:"”";s:2:"";s:3:"〝";s:2:"";s:3:"〞";s:2:"";s:3:"‵";s:2:"";s:3:"′";s:2:"";s:3:"#";s:2:"";s:3:"&";s:2:"";s:3:"*";s:2:"";s:3:"※";s:2:"";s:2:"§";s:2:"";s:3:"〃";s:2:"";s:3:"○";s:2:"";s:3:"●";s:2:"";s:3:"△";s:2:"";s:3:"▲";s:2:"";s:3:"◎";s:2:"";s:3:"☆";s:2:"";s:3:"★";s:2:"";s:3:"◇";s:2:"";s:3:"◆";s:2:"";s:3:"□";s:2:"";s:3:"■";s:2:"";s:3:"▽";s:2:"";s:3:"▼";s:2:"";s:3:"㊣";s:2:"";s:3:"℅";s:2:"";s:3:"‾";s:2:"";s:3:"�";s:2:"";s:3:"_";s:2:"";s:3:"�";s:2:"";s:3:"﹉";s:2:"";s:3:"﹊";s:2:"";s:3:"﹍";s:2:"";s:3:"﹎";s:2:"";s:3:"﹋";s:2:"";s:3:"﹌";s:2:"";s:3:"﹟";s:2:"";s:3:"﹠";s:2:"";s:3:"﹡";s:2:"";s:3:"+";s:2:"";s:3:"-";s:2:"";s:2:"×";s:2:"";s:2:"÷";s:2:"";s:2:"±";s:2:"";s:3:"√";s:2:"";s:3:"<";s:2:"";s:3:">";s:2:"";s:3:"=";s:2:"";s:3:"≦";s:2:"";s:3:"≧";s:2:"";s:3:"≠";s:2:"";s:3:"∞";s:2:"";s:3:"≒";s:2:"";s:3:"≡";s:2:"";s:3:"﹢";s:2:"";s:3:"﹣";s:2:"";s:3:"﹤";s:2:"";s:3:"﹥";s:2:"";s:3:"﹦";s:2:"";s:3:"∼";s:2:"";s:3:"∩";s:2:"";s:3:"∪";s:2:"";s:3:"⊥";s:2:"";s:3:"∠";s:2:"";s:3:"∟";s:2:"";s:3:"⊿";s:2:"";s:3:"㏒";s:2:"";s:3:"㏑";s:2:"";s:3:"∫";s:2:"";s:3:"∮";s:2:"";s:3:"∵";s:2:"";s:3:"∴";s:2:"";s:3:"♀";s:2:"";s:3:"♂";s:2:"";s:3:"♁";s:2:"";s:3:"☉";s:2:"";s:3:"↑";s:2:"";s:3:"↓";s:2:"";s:3:"←";s:2:"";s:3:"→";s:2:"";s:3:"↖";s:2:"";s:3:"↗";s:2:"";s:3:"↙";s:2:"";s:3:"↘";s:2:"";s:3:"∥";s:2:"";s:3:"∣";s:2:"";s:3:"�";s:2:"@";s:3:"�";s:2:"A";s:3:"/";s:2:"B";s:3:"\";s:2:"C";s:3:"$";s:2:"D";s:2:"¥";s:2:"E";s:3:"〒";s:2:"F";s:2:"¢";s:2:"G";s:2:"£";s:2:"H";s:3:"%";s:2:"I";s:3:"@";s:2:"J";s:3:"℃";s:2:"K";s:3:"℉";s:2:"L";s:3:"﹩";s:2:"M";s:3:"﹪";s:2:"N";s:3:"﹫";s:2:"O";s:3:"㏕";s:2:"P";s:3:"㎜";s:2:"Q";s:3:"㎝";s:2:"R";s:3:"㎞";s:2:"S";s:3:"㏎";s:2:"T";s:3:"㎡";s:2:"U";s:3:"㎎";s:2:"V";s:3:"㎏";s:2:"W";s:3:"㏄";s:2:"X";s:2:"°";s:2:"Y";s:3:"兙";s:2:"Z";s:3:"兛";s:2:"[";s:3:"兞";s:2:"\";s:3:"兝";s:2:"]";s:3:"兡";s:2:"^";s:3:"兣";s:2:"_";s:3:"嗧";s:2:"`";s:3:"瓩";s:2:"a";s:3:"糎";s:2:"b";s:3:"▁";s:2:"c";s:3:"▂";s:2:"d";s:3:"▃";s:2:"e";s:3:"▄";s:2:"f";s:3:"▅";s:2:"g";s:3:"▆";s:2:"h";s:3:"▇";s:2:"i";s:3:"█";s:2:"j";s:3:"▏";s:2:"k";s:3:"▎";s:2:"l";s:3:"▍";s:2:"m";s:3:"▌";s:2:"n";s:3:"▋";s:2:"o";s:3:"▊";s:2:"p";s:3:"▉";s:2:"q";s:3:"┼";s:2:"r";s:3:"┴";s:2:"s";s:3:"┬";s:2:"t";s:3:"┤";s:2:"u";s:3:"├";s:2:"v";s:3:"▔";s:2:"w";s:3:"─";s:2:"x";s:3:"│";s:2:"y";s:3:"▕";s:2:"z";s:3:"┌";s:2:"{";s:3:"┐";s:2:"|";s:3:"└";s:2:"}";s:3:"┘";s:2:"~";s:3:"╭";s:2:"";s:3:"╮";s:2:"";s:3:"╰";s:2:"";s:3:"╯";s:2:"";s:3:"═";s:2:"";s:3:"╞";s:2:"";s:3:"╪";s:2:"";s:3:"╡";s:2:"";s:3:"◢";s:2:"";s:3:"◣";s:2:"";s:3:"◥";s:2:"";s:3:"◤";s:2:"";s:3:"╱";s:2:"";s:3:"╲";s:2:"";s:3:"╳";s:2:"";s:3:"0";s:2:"";s:3:"1";s:2:"";s:3:"2";s:2:"";s:3:"3";s:2:"";s:3:"4";s:2:"";s:3:"5";s:2:"";s:3:"6";s:2:"";s:3:"7";s:2:"";s:3:"8";s:2:"";s:3:"9";s:2:"";s:3:"Ⅰ";s:2:"";s:3:"Ⅱ";s:2:"";s:3:"Ⅲ";s:2:"";s:3:"Ⅳ";s:2:"";s:3:"Ⅴ";s:2:"";s:3:"Ⅵ";s:2:"";s:3:"Ⅶ";s:2:"";s:3:"Ⅷ";s:2:"";s:3:"Ⅸ";s:2:"";s:3:"Ⅹ";s:2:"";s:3:"〡";s:2:"";s:3:"〢";s:2:"";s:3:"〣";s:2:"";s:3:"〤";s:2:"";s:3:"〥";s:2:"";s:3:"〦";s:2:"";s:3:"〧";s:2:"";s:3:"〨";s:2:"";s:3:"〩";s:2:"";s:3:"�";s:2:"";s:3:"卄";s:2:"";s:3:"�";s:2:"";s:3:"A";s:2:"";s:3:"B";s:2:"";s:3:"C";s:2:"";s:3:"D";s:2:"";s:3:"E";s:2:"";s:3:"F";s:2:"";s:3:"G";s:2:"";s:3:"H";s:2:"";s:3:"I";s:2:"";s:3:"J";s:2:"";s:3:"K";s:2:"";s:3:"L";s:2:"";s:3:"M";s:2:"";s:3:"N";s:2:"";s:3:"O";s:2:"";s:3:"P";s:2:"";s:3:"Q";s:2:"";s:3:"R";s:2:"";s:3:"S";s:2:"";s:3:"T";s:2:"";s:3:"U";s:2:"";s:3:"V";s:2:"";s:3:"W";s:2:"";s:3:"X";s:2:"";s:3:"Y";s:2:"";s:3:"Z";s:2:"";s:3:"a";s:2:"";s:3:"b";s:2:"";s:3:"c";s:2:"";s:3:"d";s:2:"";s:3:"e";s:2:"";s:3:"f";s:2:"";s:3:"g";s:2:"";s:3:"h";s:2:"";s:3:"i";s:2:"";s:3:"j";s:2:"";s:3:"k";s:2:"";s:3:"l";s:2:"";s:3:"m";s:2:"";s:3:"n";s:2:"";s:3:"o";s:2:"";s:3:"p";s:2:"";s:3:"q";s:2:"";s:3:"r";s:2:"";s:3:"s";s:2:"";s:3:"t";s:2:"";s:3:"u";s:2:"";s:3:"v";s:2:"@";s:3:"w";s:2:"A";s:3:"x";s:2:"B";s:3:"y";s:2:"C";s:3:"z";s:2:"D";s:2:"Α";s:2:"E";s:2:"Β";s:2:"F";s:2:"Γ";s:2:"G";s:2:"Δ";s:2:"H";s:2:"Ε";s:2:"I";s:2:"Ζ";s:2:"J";s:2:"Η";s:2:"K";s:2:"Θ";s:2:"L";s:2:"Ι";s:2:"M";s:2:"Κ";s:2:"N";s:2:"Λ";s:2:"O";s:2:"Μ";s:2:"P";s:2:"Ν";s:2:"Q";s:2:"Ξ";s:2:"R";s:2:"Ο";s:2:"S";s:2:"Π";s:2:"T";s:2:"Ρ";s:2:"U";s:2:"Σ";s:2:"V";s:2:"Τ";s:2:"W";s:2:"Υ";s:2:"X";s:2:"Φ";s:2:"Y";s:2:"Χ";s:2:"Z";s:2:"Ψ";s:2:"[";s:2:"Ω";s:2:"\";s:2:"α";s:2:"]";s:2:"β";s:2:"^";s:2:"γ";s:2:"_";s:2:"δ";s:2:"`";s:2:"ε";s:2:"a";s:2:"ζ";s:2:"b";s:2:"η";s:2:"c";s:2:"θ";s:2:"d";s:2:"ι";s:2:"e";s:2:"κ";s:2:"f";s:2:"λ";s:2:"g";s:2:"μ";s:2:"h";s:2:"ν";s:2:"i";s:2:"ξ";s:2:"j";s:2:"ο";s:2:"k";s:2:"π";s:2:"l";s:2:"ρ";s:2:"m";s:2:"σ";s:2:"n";s:2:"τ";s:2:"o";s:2:"υ";s:2:"p";s:2:"φ";s:2:"q";s:2:"χ";s:2:"r";s:2:"ψ";s:2:"s";s:2:"ω";s:2:"t";s:3:"ㄅ";s:2:"u";s:3:"ㄆ";s:2:"v";s:3:"ㄇ";s:2:"w";s:3:"ㄈ";s:2:"x";s:3:"ㄉ";s:2:"y";s:3:"ㄊ";s:2:"z";s:3:"ㄋ";s:2:"{";s:3:"ㄌ";s:2:"|";s:3:"ㄍ";s:2:"}";s:3:"ㄎ";s:2:"~";s:3:"ㄏ";s:2:"";s:3:"ㄐ";s:2:"";s:3:"ㄑ";s:2:"";s:3:"ㄒ";s:2:"";s:3:"ㄓ";s:2:"";s:3:"ㄔ";s:2:"";s:3:"ㄕ";s:2:"";s:3:"ㄖ";s:2:"";s:3:"ㄗ";s:2:"";s:3:"ㄘ";s:2:"";s:3:"ㄙ";s:2:"";s:3:"ㄚ";s:2:"";s:3:"ㄛ";s:2:"";s:3:"ㄜ";s:2:"";s:3:"ㄝ";s:2:"";s:3:"ㄞ";s:2:"";s:3:"ㄟ";s:2:"";s:3:"ㄠ";s:2:"";s:3:"ㄡ";s:2:"";s:3:"ㄢ";s:2:"";s:3:"ㄣ";s:2:"";s:3:"ㄤ";s:2:"";s:3:"ㄥ";s:2:"";s:3:"ㄦ";s:2:"";s:3:"ㄧ";s:2:"";s:3:"ㄨ";s:2:"";s:3:"ㄩ";s:2:"";s:2:"˙";s:2:"";s:2:"ˉ";s:2:"";s:2:"ˊ";s:2:"";s:2:"ˇ";s:2:"";s:2:"ˋ";s:2:"@";s:3:"一";s:2:"A";s:3:"乙";s:2:"B";s:3:"丁";s:2:"C";s:3:"七";s:2:"D";s:3:"乃";s:2:"E";s:3:"九";s:2:"F";s:3:"了";s:2:"G";s:3:"二";s:2:"H";s:3:"人";s:2:"I";s:3:"儿";s:2:"J";s:3:"入";s:2:"K";s:3:"八";s:2:"L";s:3:"几";s:2:"M";s:3:"刀";s:2:"N";s:3:"刁";s:2:"O";s:3:"力";s:2:"P";s:3:"匕";s:2:"Q";s:3:"十";s:2:"R";s:3:"卜";s:2:"S";s:3:"又";s:2:"T";s:3:"三";s:2:"U";s:3:"下";s:2:"V";s:3:"丈";s:2:"W";s:3:"上";s:2:"X";s:3:"丫";s:2:"Y";s:3:"丸";s:2:"Z";s:3:"凡";s:2:"[";s:3:"久";s:2:"\";s:3:"么";s:2:"]";s:3:"也";s:2:"^";s:3:"乞";s:2:"_";s:3:"于";s:2:"`";s:3:"亡";s:2:"a";s:3:"兀";s:2:"b";s:3:"刃";s:2:"c";s:3:"勺";s:2:"d";s:3:"千";s:2:"e";s:3:"叉";s:2:"f";s:3:"口";s:2:"g";s:3:"土";s:2:"h";s:3:"士";s:2:"i";s:3:"夕";s:2:"j";s:3:"大";s:2:"k";s:3:"女";s:2:"l";s:3:"子";s:2:"m";s:3:"孑";s:2:"n";s:3:"孓";s:2:"o";s:3:"寸";s:2:"p";s:3:"小";s:2:"q";s:3:"尢";s:2:"r";s:3:"尸";s:2:"s";s:3:"山";s:2:"t";s:3:"川";s:2:"u";s:3:"工";s:2:"v";s:3:"己";s:2:"w";s:3:"已";s:2:"x";s:3:"巳";s:2:"y";s:3:"巾";s:2:"z";s:3:"干";s:2:"{";s:3:"廾";s:2:"|";s:3:"弋";s:2:"}";s:3:"弓";s:2:"~";s:3:"才";s:2:"";s:3:"丑";s:2:"";s:3:"丐";s:2:"";s:3:"不";s:2:"";s:3:"中";s:2:"";s:3:"丰";s:2:"";s:3:"丹";s:2:"";s:3:"之";s:2:"";s:3:"尹";s:2:"";s:3:"予";s:2:"";s:3:"云";s:2:"";s:3:"井";s:2:"";s:3:"互";s:2:"";s:3:"五";s:2:"";s:3:"亢";s:2:"";s:3:"仁";s:2:"";s:3:"什";s:2:"";s:3:"仃";s:2:"";s:3:"仆";s:2:"";s:3:"仇";s:2:"";s:3:"仍";s:2:"";s:3:"今";s:2:"";s:3:"介";s:2:"";s:3:"仄";s:2:"";s:3:"元";s:2:"";s:3:"允";s:2:"";s:3:"內";s:2:"";s:3:"六";s:2:"";s:3:"兮";s:2:"";s:3:"公";s:2:"";s:3:"冗";s:2:"";s:3:"凶";s:2:"";s:3:"分";s:2:"";s:3:"切";s:2:"";s:3:"刈";s:2:"";s:3:"勻";s:2:"";s:3:"勾";s:2:"";s:3:"勿";s:2:"";s:3:"化";s:2:"";s:3:"匹";s:2:"";s:3:"午";s:2:"";s:3:"升";s:2:"";s:3:"卅";s:2:"";s:3:"卞";s:2:"";s:3:"厄";s:2:"";s:3:"友";s:2:"";s:3:"及";s:2:"";s:3:"反";s:2:"";s:3:"壬";s:2:"";s:3:"天";s:2:"";s:3:"夫";s:2:"";s:3:"太";s:2:"";s:3:"夭";s:2:"";s:3:"孔";s:2:"";s:3:"少";s:2:"";s:3:"尤";s:2:"";s:3:"尺";s:2:"";s:3:"屯";s:2:"";s:3:"巴";s:2:"";s:3:"幻";s:2:"";s:3:"廿";s:2:"";s:3:"弔";s:2:"";s:3:"引";s:2:"";s:3:"心";s:2:"";s:3:"戈";s:2:"";s:3:"戶";s:2:"";s:3:"手";s:2:"";s:3:"扎";s:2:"";s:3:"支";s:2:"";s:3:"文";s:2:"";s:3:"斗";s:2:"";s:3:"斤";s:2:"";s:3:"方";s:2:"";s:3:"日";s:2:"";s:3:"曰";s:2:"";s:3:"月";s:2:"";s:3:"木";s:2:"";s:3:"欠";s:2:"";s:3:"止";s:2:"";s:3:"歹";s:2:"";s:3:"毋";s:2:"";s:3:"比";s:2:"";s:3:"毛";s:2:"";s:3:"氏";s:2:"";s:3:"水";s:2:"";s:3:"火";s:2:"";s:3:"爪";s:2:"";s:3:"父";s:2:"";s:3:"爻";s:2:"";s:3:"片";s:2:"";s:3:"牙";s:2:"";s:3:"牛";s:2:"";s:3:"犬";s:2:"";s:3:"王";s:2:"";s:3:"丙";s:2:"@";s:3:"世";s:2:"A";s:3:"丕";s:2:"B";s:3:"且";s:2:"C";s:3:"丘";s:2:"D";s:3:"主";s:2:"E";s:3:"乍";s:2:"F";s:3:"乏";s:2:"G";s:3:"乎";s:2:"H";s:3:"以";s:2:"I";s:3:"付";s:2:"J";s:3:"仔";s:2:"K";s:3:"仕";s:2:"L";s:3:"他";s:2:"M";s:3:"仗";s:2:"N";s:3:"代";s:2:"O";s:3:"令";s:2:"P";s:3:"仙";s:2:"Q";s:3:"仞";s:2:"R";s:3:"充";s:2:"S";s:3:"兄";s:2:"T";s:3:"冉";s:2:"U";s:3:"冊";s:2:"V";s:3:"冬";s:2:"W";s:3:"凹";s:2:"X";s:3:"出";s:2:"Y";s:3:"凸";s:2:"Z";s:3:"刊";s:2:"[";s:3:"加";s:2:"\";s:3:"功";s:2:"]";s:3:"包";s:2:"^";s:3:"匆";s:2:"_";s:3:"北";s:2:"`";s:3:"匝";s:2:"a";s:3:"仟";s:2:"b";s:3:"半";s:2:"c";s:3:"卉";s:2:"d";s:3:"卡";s:2:"e";s:3:"占";s:2:"f";s:3:"卯";s:2:"g";s:3:"卮";s:2:"h";s:3:"去";s:2:"i";s:3:"可";s:2:"j";s:3:"古";s:2:"k";s:3:"右";s:2:"l";s:3:"召";s:2:"m";s:3:"叮";s:2:"n";s:3:"叩";s:2:"o";s:3:"叨";s:2:"p";s:3:"叼";s:2:"q";s:3:"司";s:2:"r";s:3:"叵";s:2:"s";s:3:"叫";s:2:"t";s:3:"另";s:2:"u";s:3:"只";s:2:"v";s:3:"史";s:2:"w";s:3:"叱";s:2:"x";s:3:"台";s:2:"y";s:3:"句";s:2:"z";s:3:"叭";s:2:"{";s:3:"叻";s:2:"|";s:3:"四";s:2:"}";s:3:"囚";s:2:"~";s:3:"外";s:2:"";s:3:"央";s:2:"";s:3:"失";s:2:"";s:3:"奴";s:2:"";s:3:"奶";s:2:"";s:3:"孕";s:2:"";s:3:"它";s:2:"";s:3:"尼";s:2:"";s:3:"巨";s:2:"";s:3:"巧";s:2:"";s:3:"左";s:2:"";s:3:"市";s:2:"";s:3:"布";s:2:"";s:3:"平";s:2:"";s:3:"幼";s:2:"";s:3:"弁";s:2:"";s:3:"弘";s:2:"";s:3:"弗";s:2:"";s:3:"必";s:2:"";s:3:"戊";s:2:"";s:3:"打";s:2:"";s:3:"扔";s:2:"";s:3:"扒";s:2:"";s:3:"扑";s:2:"";s:3:"斥";s:2:"";s:3:"旦";s:2:"";s:3:"朮";s:2:"";s:3:"本";s:2:"";s:3:"未";s:2:"";s:3:"末";s:2:"";s:3:"札";s:2:"";s:3:"正";s:2:"";s:3:"母";s:2:"";s:3:"民";s:2:"";s:3:"氐";s:2:"";s:3:"永";s:2:"";s:3:"汁";s:2:"";s:3:"汀";s:2:"";s:3:"氾";s:2:"";s:3:"犯";s:2:"";s:3:"玄";s:2:"";s:3:"玉";s:2:"";s:3:"瓜";s:2:"";s:3:"瓦";s:2:"";s:3:"甘";s:2:"";s:3:"生";s:2:"";s:3:"用";s:2:"";s:3:"甩";s:2:"";s:3:"田";s:2:"";s:3:"由";s:2:"";s:3:"甲";s:2:"";s:3:"申";s:2:"";s:3:"疋";s:2:"";s:3:"白";s:2:"";s:3:"皮";s:2:"";s:3:"皿";s:2:"";s:3:"目";s:2:"";s:3:"矛";s:2:"";s:3:"矢";s:2:"";s:3:"石";s:2:"";s:3:"示";s:2:"";s:3:"禾";s:2:"";s:3:"穴";s:2:"";s:3:"立";s:2:"";s:3:"丞";s:2:"";s:3:"丟";s:2:"";s:3:"乒";s:2:"";s:3:"乓";s:2:"";s:3:"乩";s:2:"";s:3:"亙";s:2:"";s:3:"交";s:2:"";s:3:"亦";s:2:"";s:3:"亥";s:2:"";s:3:"仿";s:2:"";s:3:"伉";s:2:"";s:3:"伙";s:2:"";s:3:"伊";s:2:"";s:3:"伕";s:2:"";s:3:"伍";s:2:"";s:3:"伐";s:2:"";s:3:"休";s:2:"";s:3:"伏";s:2:"";s:3:"仲";s:2:"";s:3:"件";s:2:"";s:3:"任";s:2:"";s:3:"仰";s:2:"";s:3:"仳";s:2:"";s:3:"份";s:2:"";s:3:"企";s:2:"";s:3:"伋";s:2:"";s:3:"光";s:2:"";s:3:"兇";s:2:"";s:3:"兆";s:2:"";s:3:"先";s:2:"";s:3:"全";s:2:"@";s:3:"共";s:2:"A";s:3:"再";s:2:"B";s:3:"冰";s:2:"C";s:3:"列";s:2:"D";s:3:"刑";s:2:"E";s:3:"划";s:2:"F";s:3:"刎";s:2:"G";s:3:"刖";s:2:"H";s:3:"劣";s:2:"I";s:3:"匈";s:2:"J";s:3:"匡";s:2:"K";s:3:"匠";s:2:"L";s:3:"印";s:2:"M";s:3:"危";s:2:"N";s:3:"吉";s:2:"O";s:3:"吏";s:2:"P";s:3:"同";s:2:"Q";s:3:"吊";s:2:"R";s:3:"吐";s:2:"S";s:3:"吁";s:2:"T";s:3:"吋";s:2:"U";s:3:"各";s:2:"V";s:3:"向";s:2:"W";s:3:"名";s:2:"X";s:3:"合";s:2:"Y";s:3:"吃";s:2:"Z";s:3:"后";s:2:"[";s:3:"吆";s:2:"\";s:3:"吒";s:2:"]";s:3:"因";s:2:"^";s:3:"回";s:2:"_";s:3:"囝";s:2:"`";s:3:"圳";s:2:"a";s:3:"地";s:2:"b";s:3:"在";s:2:"c";s:3:"圭";s:2:"d";s:3:"圬";s:2:"e";s:3:"圯";s:2:"f";s:3:"圩";s:2:"g";s:3:"夙";s:2:"h";s:3:"多";s:2:"i";s:3:"夷";s:2:"j";s:3:"夸";s:2:"k";s:3:"妄";s:2:"l";s:3:"奸";s:2:"m";s:3:"妃";s:2:"n";s:3:"好";s:2:"o";s:3:"她";s:2:"p";s:3:"如";s:2:"q";s:3:"妁";s:2:"r";s:3:"字";s:2:"s";s:3:"存";s:2:"t";s:3:"宇";s:2:"u";s:3:"守";s:2:"v";s:3:"宅";s:2:"w";s:3:"安";s:2:"x";s:3:"寺";s:2:"y";s:3:"尖";s:2:"z";s:3:"屹";s:2:"{";s:3:"州";s:2:"|";s:3:"帆";s:2:"}";s:3:"并";s:2:"~";s:3:"年";s:2:"";s:3:"式";s:2:"";s:3:"弛";s:2:"";s:3:"忙";s:2:"";s:3:"忖";s:2:"";s:3:"戎";s:2:"";s:3:"戌";s:2:"";s:3:"戍";s:2:"";s:3:"成";s:2:"";s:3:"扣";s:2:"";s:3:"扛";s:2:"";s:3:"托";s:2:"";s:3:"收";s:2:"";s:3:"早";s:2:"";s:3:"旨";s:2:"";s:3:"旬";s:2:"";s:3:"旭";s:2:"";s:3:"曲";s:2:"";s:3:"曳";s:2:"";s:3:"有";s:2:"";s:3:"朽";s:2:"";s:3:"朴";s:2:"";s:3:"朱";s:2:"";s:3:"朵";s:2:"";s:3:"次";s:2:"";s:3:"此";s:2:"";s:3:"死";s:2:"";s:3:"氖";s:2:"";s:3:"汝";s:2:"";s:3:"汗";s:2:"";s:3:"汙";s:2:"";s:3:"江";s:2:"";s:3:"池";s:2:"";s:3:"汐";s:2:"";s:3:"汕";s:2:"";s:3:"污";s:2:"";s:3:"汛";s:2:"";s:3:"汍";s:2:"";s:3:"汎";s:2:"";s:3:"灰";s:2:"";s:3:"牟";s:2:"";s:3:"牝";s:2:"";s:3:"百";s:2:"";s:3:"竹";s:2:"";s:3:"米";s:2:"";s:3:"糸";s:2:"";s:3:"缶";s:2:"";s:3:"羊";s:2:"";s:3:"羽";s:2:"";s:3:"老";s:2:"";s:3:"考";s:2:"";s:3:"而";s:2:"";s:3:"耒";s:2:"";s:3:"耳";s:2:"";s:3:"聿";s:2:"";s:3:"肉";s:2:"";s:3:"肋";s:2:"";s:3:"肌";s:2:"";s:3:"臣";s:2:"";s:3:"自";s:2:"";s:3:"至";s:2:"";s:3:"臼";s:2:"";s:3:"舌";s:2:"";s:3:"舛";s:2:"";s:3:"舟";s:2:"";s:3:"艮";s:2:"";s:3:"色";s:2:"";s:3:"艾";s:2:"";s:3:"虫";s:2:"";s:3:"血";s:2:"";s:3:"行";s:2:"";s:3:"衣";s:2:"";s:3:"西";s:2:"";s:3:"阡";s:2:"";s:3:"串";s:2:"";s:3:"亨";s:2:"";s:3:"位";s:2:"";s:3:"住";s:2:"";s:3:"佇";s:2:"";s:3:"佗";s:2:"";s:3:"佞";s:2:"";s:3:"伴";s:2:"";s:3:"佛";s:2:"";s:3:"何";s:2:"";s:3:"估";s:2:"";s:3:"佐";s:2:"";s:3:"佑";s:2:"";s:3:"伽";s:2:"";s:3:"伺";s:2:"";s:3:"伸";s:2:"";s:3:"佃";s:2:"";s:3:"佔";s:2:"";s:3:"似";s:2:"";s:3:"但";s:2:"";s:3:"佣";s:2:"@";s:3:"作";s:2:"A";s:3:"你";s:2:"B";s:3:"伯";s:2:"C";s:3:"低";s:2:"D";s:3:"伶";s:2:"E";s:3:"余";s:2:"F";s:3:"佝";s:2:"G";s:3:"佈";s:2:"H";s:3:"佚";s:2:"I";s:3:"兌";s:2:"J";s:3:"克";s:2:"K";s:3:"免";s:2:"L";s:3:"兵";s:2:"M";s:3:"冶";s:2:"N";s:3:"冷";s:2:"O";s:3:"別";s:2:"P";s:3:"判";s:2:"Q";s:3:"利";s:2:"R";s:3:"刪";s:2:"S";s:3:"刨";s:2:"T";s:3:"劫";s:2:"U";s:3:"助";s:2:"V";s:3:"努";s:2:"W";s:3:"劬";s:2:"X";s:3:"匣";s:2:"Y";s:3:"即";s:2:"Z";s:3:"卵";s:2:"[";s:3:"吝";s:2:"\";s:3:"吭";s:2:"]";s:3:"吞";s:2:"^";s:3:"吾";s:2:"_";s:3:"否";s:2:"`";s:3:"呎";s:2:"a";s:3:"吧";s:2:"b";s:3:"呆";s:2:"c";s:3:"呃";s:2:"d";s:3:"吳";s:2:"e";s:3:"呈";s:2:"f";s:3:"呂";s:2:"g";s:3:"君";s:2:"h";s:3:"吩";s:2:"i";s:3:"告";s:2:"j";s:3:"吹";s:2:"k";s:3:"吻";s:2:"l";s:3:"吸";s:2:"m";s:3:"吮";s:2:"n";s:3:"吵";s:2:"o";s:3:"吶";s:2:"p";s:3:"吠";s:2:"q";s:3:"吼";s:2:"r";s:3:"呀";s:2:"s";s:3:"吱";s:2:"t";s:3:"含";s:2:"u";s:3:"吟";s:2:"v";s:3:"听";s:2:"w";s:3:"囪";s:2:"x";s:3:"困";s:2:"y";s:3:"囤";s:2:"z";s:3:"囫";s:2:"{";s:3:"坊";s:2:"|";s:3:"坑";s:2:"}";s:3:"址";s:2:"~";s:3:"坍";s:2:"";s:3:"均";s:2:"";s:3:"坎";s:2:"";s:3:"圾";s:2:"";s:3:"坐";s:2:"";s:3:"坏";s:2:"";s:3:"圻";s:2:"";s:3:"壯";s:2:"";s:3:"夾";s:2:"";s:3:"妝";s:2:"";s:3:"妒";s:2:"";s:3:"妨";s:2:"";s:3:"妞";s:2:"";s:3:"妣";s:2:"";s:3:"妙";s:2:"";s:3:"妖";s:2:"";s:3:"妍";s:2:"";s:3:"妤";s:2:"";s:3:"妓";s:2:"";s:3:"妊";s:2:"";s:3:"妥";s:2:"";s:3:"孝";s:2:"";s:3:"孜";s:2:"";s:3:"孚";s:2:"";s:3:"孛";s:2:"";s:3:"完";s:2:"";s:3:"宋";s:2:"";s:3:"宏";s:2:"";s:3:"尬";s:2:"";s:3:"局";s:2:"";s:3:"屁";s:2:"";s:3:"尿";s:2:"";s:3:"尾";s:2:"";s:3:"岐";s:2:"";s:3:"岑";s:2:"";s:3:"岔";s:2:"";s:3:"岌";s:2:"";s:3:"巫";s:2:"";s:3:"希";s:2:"";s:3:"序";s:2:"";s:3:"庇";s:2:"";s:3:"床";s:2:"";s:3:"廷";s:2:"";s:3:"弄";s:2:"";s:3:"弟";s:2:"";s:3:"彤";s:2:"";s:3:"形";s:2:"";s:3:"彷";s:2:"";s:3:"役";s:2:"";s:3:"忘";s:2:"";s:3:"忌";s:2:"";s:3:"志";s:2:"";s:3:"忍";s:2:"";s:3:"忱";s:2:"";s:3:"快";s:2:"";s:3:"忸";s:2:"";s:3:"忪";s:2:"";s:3:"戒";s:2:"";s:3:"我";s:2:"";s:3:"抄";s:2:"";s:3:"抗";s:2:"";s:3:"抖";s:2:"";s:3:"技";s:2:"";s:3:"扶";s:2:"";s:3:"抉";s:2:"";s:3:"扭";s:2:"";s:3:"把";s:2:"";s:3:"扼";s:2:"";s:3:"找";s:2:"";s:3:"批";s:2:"";s:3:"扳";s:2:"";s:3:"抒";s:2:"";s:3:"扯";s:2:"";s:3:"折";s:2:"";s:3:"扮";s:2:"";s:3:"投";s:2:"";s:3:"抓";s:2:"";s:3:"抑";s:2:"";s:3:"抆";s:2:"";s:3:"改";s:2:"";s:3:"攻";s:2:"";s:3:"攸";s:2:"";s:3:"旱";s:2:"";s:3:"更";s:2:"";s:3:"束";s:2:"";s:3:"李";s:2:"";s:3:"杏";s:2:"";s:3:"材";s:2:"";s:3:"村";s:2:"";s:3:"杜";s:2:"";s:3:"杖";s:2:"";s:3:"杞";s:2:"";s:3:"杉";s:2:"";s:3:"杆";s:2:"";s:3:"杠";s:2:"@";s:3:"杓";s:2:"A";s:3:"杗";s:2:"B";s:3:"步";s:2:"C";s:3:"每";s:2:"D";s:3:"求";s:2:"E";s:3:"汞";s:2:"F";s:3:"沙";s:2:"G";s:3:"沁";s:2:"H";s:3:"沈";s:2:"I";s:3:"沉";s:2:"J";s:3:"沅";s:2:"K";s:3:"沛";s:2:"L";s:3:"汪";s:2:"M";s:3:"決";s:2:"N";s:3:"沐";s:2:"O";s:3:"汰";s:2:"P";s:3:"沌";s:2:"Q";s:3:"汨";s:2:"R";s:3:"沖";s:2:"S";s:3:"沒";s:2:"T";s:3:"汽";s:2:"U";s:3:"沃";s:2:"V";s:3:"汲";s:2:"W";s:3:"汾";s:2:"X";s:3:"汴";s:2:"Y";s:3:"沆";s:2:"Z";s:3:"汶";s:2:"[";s:3:"沍";s:2:"\";s:3:"沔";s:2:"]";s:3:"沘";s:2:"^";s:3:"沂";s:2:"_";s:3:"灶";s:2:"`";s:3:"灼";s:2:"a";s:3:"災";s:2:"b";s:3:"灸";s:2:"c";s:3:"牢";s:2:"d";s:3:"牡";s:2:"e";s:3:"牠";s:2:"f";s:3:"狄";s:2:"g";s:3:"狂";s:2:"h";s:3:"玖";s:2:"i";s:3:"甬";s:2:"j";s:3:"甫";s:2:"k";s:3:"男";s:2:"l";s:3:"甸";s:2:"m";s:3:"皂";s:2:"n";s:3:"盯";s:2:"o";s:3:"矣";s:2:"p";s:3:"私";s:2:"q";s:3:"秀";s:2:"r";s:3:"禿";s:2:"s";s:3:"究";s:2:"t";s:3:"系";s:2:"u";s:3:"罕";s:2:"v";s:3:"肖";s:2:"w";s:3:"肓";s:2:"x";s:3:"肝";s:2:"y";s:3:"肘";s:2:"z";s:3:"肛";s:2:"{";s:3:"肚";s:2:"|";s:3:"育";s:2:"}";s:3:"良";s:2:"~";s:3:"芒";s:2:"";s:3:"芋";s:2:"";s:3:"芍";s:2:"";s:3:"見";s:2:"";s:3:"角";s:2:"";s:3:"言";s:2:"";s:3:"谷";s:2:"";s:3:"豆";s:2:"";s:3:"豕";s:2:"";s:3:"貝";s:2:"";s:3:"赤";s:2:"";s:3:"走";s:2:"";s:3:"足";s:2:"";s:3:"身";s:2:"";s:3:"車";s:2:"";s:3:"辛";s:2:"";s:3:"辰";s:2:"";s:3:"迂";s:2:"";s:3:"迆";s:2:"";s:3:"迅";s:2:"";s:3:"迄";s:2:"";s:3:"巡";s:2:"";s:3:"邑";s:2:"";s:3:"邢";s:2:"";s:3:"邪";s:2:"";s:3:"邦";s:2:"";s:3:"那";s:2:"";s:3:"酉";s:2:"";s:3:"釆";s:2:"";s:3:"里";s:2:"";s:3:"防";s:2:"";s:3:"阮";s:2:"";s:3:"阱";s:2:"";s:3:"阪";s:2:"";s:3:"阬";s:2:"";s:3:"並";s:2:"";s:3:"乖";s:2:"";s:3:"乳";s:2:"";s:3:"事";s:2:"";s:3:"些";s:2:"";s:3:"亞";s:2:"";s:3:"享";s:2:"";s:3:"京";s:2:"";s:3:"佯";s:2:"";s:3:"依";s:2:"";s:3:"侍";s:2:"";s:3:"佳";s:2:"";s:3:"使";s:2:"";s:3:"佬";s:2:"";s:3:"供";s:2:"";s:3:"例";s:2:"";s:3:"來";s:2:"";s:3:"侃";s:2:"";s:3:"佰";s:2:"";s:3:"併";s:2:"";s:3:"侈";s:2:"";s:3:"佩";s:2:"";s:3:"佻";s:2:"";s:3:"侖";s:2:"";s:3:"佾";s:2:"";s:3:"侏";s:2:"";s:3:"侑";s:2:"";s:3:"佺";s:2:"";s:3:"兔";s:2:"";s:3:"兒";s:2:"";s:3:"兕";s:2:"";s:3:"兩";s:2:"";s:3:"具";s:2:"";s:3:"其";s:2:"";s:3:"典";s:2:"";s:3:"冽";s:2:"";s:3:"函";s:2:"";s:3:"刻";s:2:"";s:3:"券";s:2:"";s:3:"刷";s:2:"";s:3:"刺";s:2:"";s:3:"到";s:2:"";s:3:"刮";s:2:"";s:3:"制";s:2:"";s:3:"剁";s:2:"";s:3:"劾";s:2:"";s:3:"劻";s:2:"";s:3:"卒";s:2:"";s:3:"協";s:2:"";s:3:"卓";s:2:"";s:3:"卑";s:2:"";s:3:"卦";s:2:"";s:3:"卷";s:2:"";s:3:"卸";s:2:"";s:3:"卹";s:2:"";s:3:"取";s:2:"";s:3:"叔";s:2:"";s:3:"受";s:2:"";s:3:"味";s:2:"";s:3:"呵";s:2:"@";s:3:"咖";s:2:"A";s:3:"呸";s:2:"B";s:3:"咕";s:2:"C";s:3:"咀";s:2:"D";s:3:"呻";s:2:"E";s:3:"呷";s:2:"F";s:3:"咄";s:2:"G";s:3:"咒";s:2:"H";s:3:"咆";s:2:"I";s:3:"呼";s:2:"J";s:3:"咐";s:2:"K";s:3:"呱";s:2:"L";s:3:"呶";s:2:"M";s:3:"和";s:2:"N";s:3:"咚";s:2:"O";s:3:"呢";s:2:"P";s:3:"周";s:2:"Q";s:3:"咋";s:2:"R";s:3:"命";s:2:"S";s:3:"咎";s:2:"T";s:3:"固";s:2:"U";s:3:"垃";s:2:"V";s:3:"坷";s:2:"W";s:3:"坪";s:2:"X";s:3:"坩";s:2:"Y";s:3:"坡";s:2:"Z";s:3:"坦";s:2:"[";s:3:"坤";s:2:"\";s:3:"坼";s:2:"]";s:3:"夜";s:2:"^";s:3:"奉";s:2:"_";s:3:"奇";s:2:"`";s:3:"奈";s:2:"a";s:3:"奄";s:2:"b";s:3:"奔";s:2:"c";s:3:"妾";s:2:"d";s:3:"妻";s:2:"e";s:3:"委";s:2:"f";s:3:"妹";s:2:"g";s:3:"妮";s:2:"h";s:3:"姑";s:2:"i";s:3:"姆";s:2:"j";s:3:"姐";s:2:"k";s:3:"姍";s:2:"l";s:3:"始";s:2:"m";s:3:"姓";s:2:"n";s:3:"姊";s:2:"o";s:3:"妯";s:2:"p";s:3:"妳";s:2:"q";s:3:"姒";s:2:"r";s:3:"姅";s:2:"s";s:3:"孟";s:2:"t";s:3:"孤";s:2:"u";s:3:"季";s:2:"v";s:3:"宗";s:2:"w";s:3:"定";s:2:"x";s:3:"官";s:2:"y";s:3:"宜";s:2:"z";s:3:"宙";s:2:"{";s:3:"宛";s:2:"|";s:3:"尚";s:2:"}";s:3:"屈";s:2:"~";s:3:"居";s:2:"";s:3:"屆";s:2:"";s:3:"岷";s:2:"";s:3:"岡";s:2:"";s:3:"岸";s:2:"";s:3:"岩";s:2:"";s:3:"岫";s:2:"";s:3:"岱";s:2:"";s:3:"岳";s:2:"";s:3:"帘";s:2:"";s:3:"帚";s:2:"";s:3:"帖";s:2:"";s:3:"帕";s:2:"";s:3:"帛";s:2:"";s:3:"帑";s:2:"";s:3:"幸";s:2:"";s:3:"庚";s:2:"";s:3:"店";s:2:"";s:3:"府";s:2:"";s:3:"底";s:2:"";s:3:"庖";s:2:"";s:3:"延";s:2:"";s:3:"弦";s:2:"";s:3:"弧";s:2:"";s:3:"弩";s:2:"";s:3:"往";s:2:"";s:3:"征";s:2:"";s:3:"彿";s:2:"";s:3:"彼";s:2:"";s:3:"忝";s:2:"";s:3:"忠";s:2:"";s:3:"忽";s:2:"";s:3:"念";s:2:"";s:3:"忿";s:2:"";s:3:"怏";s:2:"";s:3:"怔";s:2:"";s:3:"怯";s:2:"";s:3:"怵";s:2:"";s:3:"怖";s:2:"";s:3:"怪";s:2:"";s:3:"怕";s:2:"";s:3:"怡";s:2:"";s:3:"性";s:2:"";s:3:"怩";s:2:"";s:3:"怫";s:2:"";s:3:"怛";s:2:"";s:3:"或";s:2:"";s:3:"戕";s:2:"";s:3:"房";s:2:"";s:3:"戾";s:2:"";s:3:"所";s:2:"";s:3:"承";s:2:"";s:3:"拉";s:2:"";s:3:"拌";s:2:"";s:3:"拄";s:2:"";s:3:"抿";s:2:"";s:3:"拂";s:2:"";s:3:"抹";s:2:"";s:3:"拒";s:2:"";s:3:"招";s:2:"";s:3:"披";s:2:"";s:3:"拓";s:2:"";s:3:"拔";s:2:"";s:3:"拋";s:2:"";s:3:"拈";s:2:"";s:3:"抨";s:2:"";s:3:"抽";s:2:"";s:3:"押";s:2:"";s:3:"拐";s:2:"";s:3:"拙";s:2:"";s:3:"拇";s:2:"";s:3:"拍";s:2:"";s:3:"抵";s:2:"";s:3:"拚";s:2:"";s:3:"抱";s:2:"";s:3:"拘";s:2:"";s:3:"拖";s:2:"";s:3:"拗";s:2:"";s:3:"拆";s:2:"";s:3:"抬";s:2:"";s:3:"拎";s:2:"";s:3:"放";s:2:"";s:3:"斧";s:2:"";s:3:"於";s:2:"";s:3:"旺";s:2:"";s:3:"昔";s:2:"";s:3:"易";s:2:"";s:3:"昌";s:2:"";s:3:"昆";s:2:"";s:3:"昂";s:2:"";s:3:"明";s:2:"";s:3:"昀";s:2:"";s:3:"昏";s:2:"";s:3:"昕";s:2:"";s:3:"昊";s:2:"@";s:3:"昇";s:2:"A";s:3:"服";s:2:"B";s:3:"朋";s:2:"C";s:3:"杭";s:2:"D";s:3:"枋";s:2:"E";s:3:"枕";s:2:"F";s:3:"東";s:2:"G";s:3:"果";s:2:"H";s:3:"杳";s:2:"I";s:3:"杷";s:2:"J";s:3:"枇";s:2:"K";s:3:"枝";s:2:"L";s:3:"林";s:2:"M";s:3:"杯";s:2:"N";s:3:"杰";s:2:"O";s:3:"板";s:2:"P";s:3:"枉";s:2:"Q";s:3:"松";s:2:"R";s:3:"析";s:2:"S";s:3:"杵";s:2:"T";s:3:"枚";s:2:"U";s:3:"枓";s:2:"V";s:3:"杼";s:2:"W";s:3:"杪";s:2:"X";s:3:"杲";s:2:"Y";s:3:"欣";s:2:"Z";s:3:"武";s:2:"[";s:3:"歧";s:2:"\";s:3:"歿";s:2:"]";s:3:"氓";s:2:"^";s:3:"氛";s:2:"_";s:3:"泣";s:2:"`";s:3:"注";s:2:"a";s:3:"泳";s:2:"b";s:3:"沱";s:2:"c";s:3:"泌";s:2:"d";s:3:"泥";s:2:"e";s:3:"河";s:2:"f";s:3:"沽";s:2:"g";s:3:"沾";s:2:"h";s:3:"沼";s:2:"i";s:3:"波";s:2:"j";s:3:"沫";s:2:"k";s:3:"法";s:2:"l";s:3:"泓";s:2:"m";s:3:"沸";s:2:"n";s:3:"泄";s:2:"o";s:3:"油";s:2:"p";s:3:"況";s:2:"q";s:3:"沮";s:2:"r";s:3:"泗";s:2:"s";s:3:"泅";s:2:"t";s:3:"泱";s:2:"u";s:3:"沿";s:2:"v";s:3:"治";s:2:"w";s:3:"泡";s:2:"x";s:3:"泛";s:2:"y";s:3:"泊";s:2:"z";s:3:"沬";s:2:"{";s:3:"泯";s:2:"|";s:3:"泜";s:2:"}";s:3:"泖";s:2:"~";s:3:"泠";s:2:"";s:3:"炕";s:2:"";s:3:"炎";s:2:"";s:3:"炒";s:2:"";s:3:"炊";s:2:"";s:3:"炙";s:2:"";s:3:"爬";s:2:"";s:3:"爭";s:2:"";s:3:"爸";s:2:"";s:3:"版";s:2:"";s:3:"牧";s:2:"";s:3:"物";s:2:"";s:3:"狀";s:2:"";s:3:"狎";s:2:"";s:3:"狙";s:2:"";s:3:"狗";s:2:"";s:3:"狐";s:2:"";s:3:"玩";s:2:"";s:3:"玨";s:2:"";s:3:"玟";s:2:"";s:3:"玫";s:2:"";s:3:"玥";s:2:"";s:3:"甽";s:2:"";s:3:"疝";s:2:"";s:3:"疙";s:2:"";s:3:"疚";s:2:"";s:3:"的";s:2:"";s:3:"盂";s:2:"";s:3:"盲";s:2:"";s:3:"直";s:2:"";s:3:"知";s:2:"";s:3:"矽";s:2:"";s:3:"社";s:2:"";s:3:"祀";s:2:"";s:3:"祁";s:2:"";s:3:"秉";s:2:"";s:3:"秈";s:2:"";s:3:"空";s:2:"";s:3:"穹";s:2:"";s:3:"竺";s:2:"";s:3:"糾";s:2:"";s:3:"罔";s:2:"";s:3:"羌";s:2:"";s:3:"羋";s:2:"";s:3:"者";s:2:"";s:3:"肺";s:2:"";s:3:"肥";s:2:"";s:3:"肢";s:2:"";s:3:"肱";s:2:"";s:3:"股";s:2:"";s:3:"肫";s:2:"";s:3:"肩";s:2:"";s:3:"肴";s:2:"";s:3:"肪";s:2:"";s:3:"肯";s:2:"";s:3:"臥";s:2:"";s:3:"臾";s:2:"";s:3:"舍";s:2:"";s:3:"芳";s:2:"";s:3:"芝";s:2:"";s:3:"芙";s:2:"";s:3:"芭";s:2:"";s:3:"芽";s:2:"";s:3:"芟";s:2:"";s:3:"芹";s:2:"";s:3:"花";s:2:"";s:3:"芬";s:2:"";s:3:"芥";s:2:"";s:3:"芯";s:2:"";s:3:"芸";s:2:"";s:3:"芣";s:2:"";s:3:"芰";s:2:"";s:3:"芾";s:2:"";s:3:"芷";s:2:"";s:3:"虎";s:2:"";s:3:"虱";s:2:"";s:3:"初";s:2:"";s:3:"表";s:2:"";s:3:"軋";s:2:"";s:3:"迎";s:2:"";s:3:"返";s:2:"";s:3:"近";s:2:"";s:3:"邵";s:2:"";s:3:"邸";s:2:"";s:3:"邱";s:2:"";s:3:"邶";s:2:"";s:3:"采";s:2:"";s:3:"金";s:2:"";s:3:"長";s:2:"";s:3:"門";s:2:"";s:3:"阜";s:2:"";s:3:"陀";s:2:"";s:3:"阿";s:2:"";s:3:"阻";s:2:"";s:3:"附";s:2:"@";s:3:"陂";s:2:"A";s:3:"隹";s:2:"B";s:3:"雨";s:2:"C";s:3:"青";s:2:"D";s:3:"非";s:2:"E";s:3:"亟";s:2:"F";s:3:"亭";s:2:"G";s:3:"亮";s:2:"H";s:3:"信";s:2:"I";s:3:"侵";s:2:"J";s:3:"侯";s:2:"K";s:3:"便";s:2:"L";s:3:"俠";s:2:"M";s:3:"俑";s:2:"N";s:3:"俏";s:2:"O";s:3:"保";s:2:"P";s:3:"促";s:2:"Q";s:3:"侶";s:2:"R";s:3:"俘";s:2:"S";s:3:"俟";s:2:"T";s:3:"俊";s:2:"U";s:3:"俗";s:2:"V";s:3:"侮";s:2:"W";s:3:"俐";s:2:"X";s:3:"俄";s:2:"Y";s:3:"係";s:2:"Z";s:3:"俚";s:2:"[";s:3:"俎";s:2:"\";s:3:"俞";s:2:"]";s:3:"侷";s:2:"^";s:3:"兗";s:2:"_";s:3:"冒";s:2:"`";s:3:"冑";s:2:"a";s:3:"冠";s:2:"b";s:3:"剎";s:2:"c";s:3:"剃";s:2:"d";s:3:"削";s:2:"e";s:3:"前";s:2:"f";s:3:"剌";s:2:"g";s:3:"剋";s:2:"h";s:3:"則";s:2:"i";s:3:"勇";s:2:"j";s:3:"勉";s:2:"k";s:3:"勃";s:2:"l";s:3:"勁";s:2:"m";s:3:"匍";s:2:"n";s:3:"南";s:2:"o";s:3:"卻";s:2:"p";s:3:"厚";s:2:"q";s:3:"叛";s:2:"r";s:3:"咬";s:2:"s";s:3:"哀";s:2:"t";s:3:"咨";s:2:"u";s:3:"哎";s:2:"v";s:3:"哉";s:2:"w";s:3:"咸";s:2:"x";s:3:"咦";s:2:"y";s:3:"咳";s:2:"z";s:3:"哇";s:2:"{";s:3:"哂";s:2:"|";s:3:"咽";s:2:"}";s:3:"咪";s:2:"~";s:3:"品";s:2:"";s:3:"哄";s:2:"";s:3:"哈";s:2:"";s:3:"咯";s:2:"";s:3:"咫";s:2:"";s:3:"咱";s:2:"";s:3:"咻";s:2:"";s:3:"咩";s:2:"";s:3:"咧";s:2:"";s:3:"咿";s:2:"";s:3:"囿";s:2:"";s:3:"垂";s:2:"";s:3:"型";s:2:"";s:3:"垠";s:2:"";s:3:"垣";s:2:"";s:3:"垢";s:2:"";s:3:"城";s:2:"";s:3:"垮";s:2:"";s:3:"垓";s:2:"";s:3:"奕";s:2:"";s:3:"契";s:2:"";s:3:"奏";s:2:"";s:3:"奎";s:2:"";s:3:"奐";s:2:"";s:3:"姜";s:2:"";s:3:"姘";s:2:"";s:3:"姿";s:2:"";s:3:"姣";s:2:"";s:3:"姨";s:2:"";s:3:"娃";s:2:"";s:3:"姥";s:2:"";s:3:"姪";s:2:"";s:3:"姚";s:2:"";s:3:"姦";s:2:"";s:3:"威";s:2:"";s:3:"姻";s:2:"";s:3:"孩";s:2:"";s:3:"宣";s:2:"";s:3:"宦";s:2:"";s:3:"室";s:2:"";s:3:"客";s:2:"";s:3:"宥";s:2:"";s:3:"封";s:2:"";s:3:"屎";s:2:"";s:3:"屏";s:2:"";s:3:"屍";s:2:"";s:3:"屋";s:2:"";s:3:"峙";s:2:"";s:3:"峒";s:2:"";s:3:"巷";s:2:"";s:3:"帝";s:2:"";s:3:"帥";s:2:"";s:3:"帟";s:2:"";s:3:"幽";s:2:"";s:3:"庠";s:2:"";s:3:"度";s:2:"";s:3:"建";s:2:"";s:3:"弈";s:2:"";s:3:"弭";s:2:"";s:3:"彥";s:2:"";s:3:"很";s:2:"";s:3:"待";s:2:"";s:3:"徊";s:2:"";s:3:"律";s:2:"";s:3:"徇";s:2:"";s:3:"後";s:2:"";s:3:"徉";s:2:"";s:3:"怒";s:2:"";s:3:"思";s:2:"";s:3:"怠";s:2:"";s:3:"急";s:2:"";s:3:"怎";s:2:"";s:3:"怨";s:2:"";s:3:"恍";s:2:"";s:3:"恰";s:2:"";s:3:"恨";s:2:"";s:3:"恢";s:2:"";s:3:"恆";s:2:"";s:3:"恃";s:2:"";s:3:"恬";s:2:"";s:3:"恫";s:2:"";s:3:"恪";s:2:"";s:3:"恤";s:2:"";s:3:"扁";s:2:"";s:3:"拜";s:2:"";s:3:"挖";s:2:"";s:3:"按";s:2:"";s:3:"拼";s:2:"";s:3:"拭";s:2:"";s:3:"持";s:2:"";s:3:"拮";s:2:"";s:3:"拽";s:2:"";s:3:"指";s:2:"";s:3:"拱";s:2:"";s:3:"拷";s:2:"@";s:3:"拯";s:2:"A";s:3:"括";s:2:"B";s:3:"拾";s:2:"C";s:3:"拴";s:2:"D";s:3:"挑";s:2:"E";s:3:"挂";s:2:"F";s:3:"政";s:2:"G";s:3:"故";s:2:"H";s:3:"斫";s:2:"I";s:3:"施";s:2:"J";s:3:"既";s:2:"K";s:3:"春";s:2:"L";s:3:"昭";s:2:"M";s:3:"映";s:2:"N";s:3:"昧";s:2:"O";s:3:"是";s:2:"P";s:3:"星";s:2:"Q";s:3:"昨";s:2:"R";s:3:"昱";s:2:"S";s:3:"昤";s:2:"T";s:3:"曷";s:2:"U";s:3:"柿";s:2:"V";s:3:"染";s:2:"W";s:3:"柱";s:2:"X";s:3:"柔";s:2:"Y";s:3:"某";s:2:"Z";s:3:"柬";s:2:"[";s:3:"架";s:2:"\";s:3:"枯";s:2:"]";s:3:"柵";s:2:"^";s:3:"柩";s:2:"_";s:3:"柯";s:2:"`";s:3:"柄";s:2:"a";s:3:"柑";s:2:"b";s:3:"枴";s:2:"c";s:3:"柚";s:2:"d";s:3:"查";s:2:"e";s:3:"枸";s:2:"f";s:3:"柏";s:2:"g";s:3:"柞";s:2:"h";s:3:"柳";s:2:"i";s:3:"枰";s:2:"j";s:3:"柙";s:2:"k";s:3:"柢";s:2:"l";s:3:"柝";s:2:"m";s:3:"柒";s:2:"n";s:3:"歪";s:2:"o";s:3:"殃";s:2:"p";s:3:"殆";s:2:"q";s:3:"段";s:2:"r";s:3:"毒";s:2:"s";s:3:"毗";s:2:"t";s:3:"氟";s:2:"u";s:3:"泉";s:2:"v";s:3:"洋";s:2:"w";s:3:"洲";s:2:"x";s:3:"洪";s:2:"y";s:3:"流";s:2:"z";s:3:"津";s:2:"{";s:3:"洌";s:2:"|";s:3:"洱";s:2:"}";s:3:"洞";s:2:"~";s:3:"洗";s:2:"";s:3:"活";s:2:"";s:3:"洽";s:2:"";s:3:"派";s:2:"";s:3:"洶";s:2:"";s:3:"洛";s:2:"";s:3:"泵";s:2:"";s:3:"洹";s:2:"";s:3:"洧";s:2:"";s:3:"洸";s:2:"";s:3:"洩";s:2:"";s:3:"洮";s:2:"";s:3:"洵";s:2:"";s:3:"洎";s:2:"";s:3:"洫";s:2:"";s:3:"炫";s:2:"";s:3:"為";s:2:"";s:3:"炳";s:2:"";s:3:"炬";s:2:"";s:3:"炯";s:2:"";s:3:"炭";s:2:"";s:3:"炸";s:2:"";s:3:"炮";s:2:"";s:3:"炤";s:2:"";s:3:"爰";s:2:"";s:3:"牲";s:2:"";s:3:"牯";s:2:"";s:3:"牴";s:2:"";s:3:"狩";s:2:"";s:3:"狠";s:2:"";s:3:"狡";s:2:"";s:3:"玷";s:2:"";s:3:"珊";s:2:"";s:3:"玻";s:2:"";s:3:"玲";s:2:"";s:3:"珍";s:2:"";s:3:"珀";s:2:"";s:3:"玳";s:2:"";s:3:"甚";s:2:"";s:3:"甭";s:2:"";s:3:"畏";s:2:"";s:3:"界";s:2:"";s:3:"畎";s:2:"";s:3:"畋";s:2:"";s:3:"疫";s:2:"";s:3:"疤";s:2:"";s:3:"疥";s:2:"";s:3:"疢";s:2:"";s:3:"疣";s:2:"";s:3:"癸";s:2:"";s:3:"皆";s:2:"";s:3:"皇";s:2:"";s:3:"皈";s:2:"";s:3:"盈";s:2:"";s:3:"盆";s:2:"";s:3:"盃";s:2:"";s:3:"盅";s:2:"";s:3:"省";s:2:"";s:3:"盹";s:2:"";s:3:"相";s:2:"";s:3:"眉";s:2:"";s:3:"看";s:2:"";s:3:"盾";s:2:"";s:3:"盼";s:2:"";s:3:"眇";s:2:"";s:3:"矜";s:2:"";s:3:"砂";s:2:"";s:3:"研";s:2:"";s:3:"砌";s:2:"";s:3:"砍";s:2:"";s:3:"祆";s:2:"";s:3:"祉";s:2:"";s:3:"祈";s:2:"";s:3:"祇";s:2:"";s:3:"禹";s:2:"";s:3:"禺";s:2:"";s:3:"科";s:2:"";s:3:"秒";s:2:"";s:3:"秋";s:2:"";s:3:"穿";s:2:"";s:3:"突";s:2:"";s:3:"竿";s:2:"";s:3:"竽";s:2:"";s:3:"籽";s:2:"";s:3:"紂";s:2:"";s:3:"紅";s:2:"";s:3:"紀";s:2:"";s:3:"紉";s:2:"";s:3:"紇";s:2:"";s:3:"約";s:2:"";s:3:"紆";s:2:"";s:3:"缸";s:2:"";s:3:"美";s:2:"";s:3:"羿";s:2:"";s:3:"耄";s:2:"@";s:3:"耐";s:2:"A";s:3:"耍";s:2:"B";s:3:"耑";s:2:"C";s:3:"耶";s:2:"D";s:3:"胖";s:2:"E";s:3:"胥";s:2:"F";s:3:"胚";s:2:"G";s:3:"胃";s:2:"H";s:3:"胄";s:2:"I";s:3:"背";s:2:"J";s:3:"胡";s:2:"K";s:3:"胛";s:2:"L";s:3:"胎";s:2:"M";s:3:"胞";s:2:"N";s:3:"胤";s:2:"O";s:3:"胝";s:2:"P";s:3:"致";s:2:"Q";s:3:"舢";s:2:"R";s:3:"苧";s:2:"S";s:3:"范";s:2:"T";s:3:"茅";s:2:"U";s:3:"苣";s:2:"V";s:3:"苛";s:2:"W";s:3:"苦";s:2:"X";s:3:"茄";s:2:"Y";s:3:"若";s:2:"Z";s:3:"茂";s:2:"[";s:3:"茉";s:2:"\";s:3:"苒";s:2:"]";s:3:"苗";s:2:"^";s:3:"英";s:2:"_";s:3:"茁";s:2:"`";s:3:"苜";s:2:"a";s:3:"苔";s:2:"b";s:3:"苑";s:2:"c";s:3:"苞";s:2:"d";s:3:"苓";s:2:"e";s:3:"苟";s:2:"f";s:3:"苯";s:2:"g";s:3:"茆";s:2:"h";s:3:"虐";s:2:"i";s:3:"虹";s:2:"j";s:3:"虻";s:2:"k";s:3:"虺";s:2:"l";s:3:"衍";s:2:"m";s:3:"衫";s:2:"n";s:3:"要";s:2:"o";s:3:"觔";s:2:"p";s:3:"計";s:2:"q";s:3:"訂";s:2:"r";s:3:"訃";s:2:"s";s:3:"貞";s:2:"t";s:3:"負";s:2:"u";s:3:"赴";s:2:"v";s:3:"赳";s:2:"w";s:3:"趴";s:2:"x";s:3:"軍";s:2:"y";s:3:"軌";s:2:"z";s:3:"述";s:2:"{";s:3:"迦";s:2:"|";s:3:"迢";s:2:"}";s:3:"迪";s:2:"~";s:3:"迥";s:2:"";s:3:"迭";s:2:"";s:3:"迫";s:2:"";s:3:"迤";s:2:"";s:3:"迨";s:2:"";s:3:"郊";s:2:"";s:3:"郎";s:2:"";s:3:"郁";s:2:"";s:3:"郃";s:2:"";s:3:"酋";s:2:"";s:3:"酊";s:2:"";s:3:"重";s:2:"";s:3:"閂";s:2:"";s:3:"限";s:2:"";s:3:"陋";s:2:"";s:3:"陌";s:2:"";s:3:"降";s:2:"";s:3:"面";s:2:"";s:3:"革";s:2:"";s:3:"韋";s:2:"";s:3:"韭";s:2:"";s:3:"音";s:2:"";s:3:"頁";s:2:"";s:3:"風";s:2:"";s:3:"飛";s:2:"";s:3:"食";s:2:"";s:3:"首";s:2:"";s:3:"香";s:2:"";s:3:"乘";s:2:"";s:3:"亳";s:2:"";s:3:"倌";s:2:"";s:3:"倍";s:2:"";s:3:"倣";s:2:"";s:3:"俯";s:2:"";s:3:"倦";s:2:"";s:3:"倥";s:2:"";s:3:"俸";s:2:"";s:3:"倩";s:2:"";s:3:"倖";s:2:"";s:3:"倆";s:2:"";s:3:"值";s:2:"";s:3:"借";s:2:"";s:3:"倚";s:2:"";s:3:"倒";s:2:"";s:3:"們";s:2:"";s:3:"俺";s:2:"";s:3:"倀";s:2:"";s:3:"倔";s:2:"";s:3:"倨";s:2:"";s:3:"俱";s:2:"";s:3:"倡";s:2:"";s:3:"個";s:2:"";s:3:"候";s:2:"";s:3:"倘";s:2:"";s:3:"俳";s:2:"";s:3:"修";s:2:"";s:3:"倭";s:2:"";s:3:"倪";s:2:"";s:3:"俾";s:2:"";s:3:"倫";s:2:"";s:3:"倉";s:2:"";s:3:"兼";s:2:"";s:3:"冤";s:2:"";s:3:"冥";s:2:"";s:3:"冢";s:2:"";s:3:"凍";s:2:"";s:3:"凌";s:2:"";s:3:"准";s:2:"";s:3:"凋";s:2:"";s:3:"剖";s:2:"";s:3:"剜";s:2:"";s:3:"剔";s:2:"";s:3:"剛";s:2:"";s:3:"剝";s:2:"";s:3:"匪";s:2:"";s:3:"卿";s:2:"";s:3:"原";s:2:"";s:3:"厝";s:2:"";s:3:"叟";s:2:"";s:3:"哨";s:2:"";s:3:"唐";s:2:"";s:3:"唁";s:2:"";s:3:"唷";s:2:"";s:3:"哼";s:2:"";s:3:"哥";s:2:"";s:3:"哲";s:2:"";s:3:"唆";s:2:"";s:3:"哺";s:2:"";s:3:"唔";s:2:"";s:3:"哩";s:2:"";s:3:"哭";s:2:"";s:3:"員";s:2:"";s:3:"唉";s:2:"";s:3:"哮";s:2:"";s:3:"哪";s:2:"@";s:3:"哦";s:2:"A";s:3:"唧";s:2:"B";s:3:"唇";s:2:"C";s:3:"哽";s:2:"D";s:3:"唏";s:2:"E";s:3:"圃";s:2:"F";s:3:"圄";s:2:"G";s:3:"埂";s:2:"H";s:3:"埔";s:2:"I";s:3:"埋";s:2:"J";s:3:"埃";s:2:"K";s:3:"堉";s:2:"L";s:3:"夏";s:2:"M";s:3:"套";s:2:"N";s:3:"奘";s:2:"O";s:3:"奚";s:2:"P";s:3:"娑";s:2:"Q";s:3:"娘";s:2:"R";s:3:"娜";s:2:"S";s:3:"娟";s:2:"T";s:3:"娛";s:2:"U";s:3:"娓";s:2:"V";s:3:"姬";s:2:"W";s:3:"娠";s:2:"X";s:3:"娣";s:2:"Y";s:3:"娩";s:2:"Z";s:3:"娥";s:2:"[";s:3:"娌";s:2:"\";s:3:"娉";s:2:"]";s:3:"孫";s:2:"^";s:3:"屘";s:2:"_";s:3:"宰";s:2:"`";s:3:"害";s:2:"a";s:3:"家";s:2:"b";s:3:"宴";s:2:"c";s:3:"宮";s:2:"d";s:3:"宵";s:2:"e";s:3:"容";s:2:"f";s:3:"宸";s:2:"g";s:3:"射";s:2:"h";s:3:"屑";s:2:"i";s:3:"展";s:2:"j";s:3:"屐";s:2:"k";s:3:"峭";s:2:"l";s:3:"峽";s:2:"m";s:3:"峻";s:2:"n";s:3:"峪";s:2:"o";s:3:"峨";s:2:"p";s:3:"峰";s:2:"q";s:3:"島";s:2:"r";s:3:"崁";s:2:"s";s:3:"峴";s:2:"t";s:3:"差";s:2:"u";s:3:"席";s:2:"v";s:3:"師";s:2:"w";s:3:"庫";s:2:"x";s:3:"庭";s:2:"y";s:3:"座";s:2:"z";s:3:"弱";s:2:"{";s:3:"徒";s:2:"|";s:3:"徑";s:2:"}";s:3:"徐";s:2:"~";s:3:"恙";s:2:"";s:3:"恣";s:2:"";s:3:"恥";s:2:"";s:3:"恐";s:2:"";s:3:"恕";s:2:"";s:3:"恭";s:2:"";s:3:"恩";s:2:"";s:3:"息";s:2:"";s:3:"悄";s:2:"";s:3:"悟";s:2:"";s:3:"悚";s:2:"";s:3:"悍";s:2:"";s:3:"悔";s:2:"";s:3:"悌";s:2:"";s:3:"悅";s:2:"";s:3:"悖";s:2:"";s:3:"扇";s:2:"";s:3:"拳";s:2:"";s:3:"挈";s:2:"";s:3:"拿";s:2:"";s:3:"捎";s:2:"";s:3:"挾";s:2:"";s:3:"振";s:2:"";s:3:"捕";s:2:"";s:3:"捂";s:2:"";s:3:"捆";s:2:"";s:3:"捏";s:2:"";s:3:"捉";s:2:"";s:3:"挺";s:2:"";s:3:"捐";s:2:"";s:3:"挽";s:2:"";s:3:"挪";s:2:"";s:3:"挫";s:2:"";s:3:"挨";s:2:"";s:3:"捍";s:2:"";s:3:"捌";s:2:"";s:3:"效";s:2:"";s:3:"敉";s:2:"";s:3:"料";s:2:"";s:3:"旁";s:2:"";s:3:"旅";s:2:"";s:3:"時";s:2:"";s:3:"晉";s:2:"";s:3:"晏";s:2:"";s:3:"晃";s:2:"";s:3:"晒";s:2:"";s:3:"晌";s:2:"";s:3:"晅";s:2:"";s:3:"晁";s:2:"";s:3:"書";s:2:"";s:3:"朔";s:2:"";s:3:"朕";s:2:"";s:3:"朗";s:2:"";s:3:"校";s:2:"";s:3:"核";s:2:"";s:3:"案";s:2:"";s:3:"框";s:2:"";s:3:"桓";s:2:"";s:3:"根";s:2:"";s:3:"桂";s:2:"";s:3:"桔";s:2:"";s:3:"栩";s:2:"";s:3:"梳";s:2:"";s:3:"栗";s:2:"";s:3:"桌";s:2:"";s:3:"桑";s:2:"";s:3:"栽";s:2:"";s:3:"柴";s:2:"";s:3:"桐";s:2:"";s:3:"桀";s:2:"";s:3:"格";s:2:"";s:3:"桃";s:2:"";s:3:"株";s:2:"";s:3:"桅";s:2:"";s:3:"栓";s:2:"";s:3:"栘";s:2:"";s:3:"桁";s:2:"";s:3:"殊";s:2:"";s:3:"殉";s:2:"";s:3:"殷";s:2:"";s:3:"氣";s:2:"";s:3:"氧";s:2:"";s:3:"氨";s:2:"";s:3:"氦";s:2:"";s:3:"氤";s:2:"";s:3:"泰";s:2:"";s:3:"浪";s:2:"";s:3:"涕";s:2:"";s:3:"消";s:2:"";s:3:"涇";s:2:"";s:3:"浦";s:2:"";s:3:"浸";s:2:"";s:3:"海";s:2:"";s:3:"浙";s:2:"";s:3:"涓";s:2:"@";s:3:"浬";s:2:"A";s:3:"涉";s:2:"B";s:3:"浮";s:2:"C";s:3:"浚";s:2:"D";s:3:"浴";s:2:"E";s:3:"浩";s:2:"F";s:3:"涌";s:2:"G";s:3:"涊";s:2:"H";s:3:"浹";s:2:"I";s:3:"涅";s:2:"J";s:3:"浥";s:2:"K";s:3:"涔";s:2:"L";s:3:"烊";s:2:"M";s:3:"烘";s:2:"N";s:3:"烤";s:2:"O";s:3:"烙";s:2:"P";s:3:"烈";s:2:"Q";s:3:"烏";s:2:"R";s:3:"爹";s:2:"S";s:3:"特";s:2:"T";s:3:"狼";s:2:"U";s:3:"狹";s:2:"V";s:3:"狽";s:2:"W";s:3:"狸";s:2:"X";s:3:"狷";s:2:"Y";s:3:"玆";s:2:"Z";s:3:"班";s:2:"[";s:3:"琉";s:2:"\";s:3:"珮";s:2:"]";s:3:"珠";s:2:"^";s:3:"珪";s:2:"_";s:3:"珞";s:2:"`";s:3:"畔";s:2:"a";s:3:"畝";s:2:"b";s:3:"畜";s:2:"c";s:3:"畚";s:2:"d";s:3:"留";s:2:"e";s:3:"疾";s:2:"f";s:3:"病";s:2:"g";s:3:"症";s:2:"h";s:3:"疲";s:2:"i";s:3:"疳";s:2:"j";s:3:"疽";s:2:"k";s:3:"疼";s:2:"l";s:3:"疹";s:2:"m";s:3:"痂";s:2:"n";s:3:"疸";s:2:"o";s:3:"皋";s:2:"p";s:3:"皰";s:2:"q";s:3:"益";s:2:"r";s:3:"盍";s:2:"s";s:3:"盎";s:2:"t";s:3:"眩";s:2:"u";s:3:"真";s:2:"v";s:3:"眠";s:2:"w";s:3:"眨";s:2:"x";s:3:"矩";s:2:"y";s:3:"砰";s:2:"z";s:3:"砧";s:2:"{";s:3:"砸";s:2:"|";s:3:"砝";s:2:"}";s:3:"破";s:2:"~";s:3:"砷";s:2:"";s:3:"砥";s:2:"";s:3:"砭";s:2:"";s:3:"砠";s:2:"";s:3:"砟";s:2:"";s:3:"砲";s:2:"";s:3:"祕";s:2:"";s:3:"祐";s:2:"";s:3:"祠";s:2:"";s:3:"祟";s:2:"";s:3:"祖";s:2:"";s:3:"神";s:2:"";s:3:"祝";s:2:"";s:3:"祗";s:2:"";s:3:"祚";s:2:"";s:3:"秤";s:2:"";s:3:"秣";s:2:"";s:3:"秧";s:2:"";s:3:"租";s:2:"";s:3:"秦";s:2:"";s:3:"秩";s:2:"";s:3:"秘";s:2:"";s:3:"窄";s:2:"";s:3:"窈";s:2:"";s:3:"站";s:2:"";s:3:"笆";s:2:"";s:3:"笑";s:2:"";s:3:"粉";s:2:"";s:3:"紡";s:2:"";s:3:"紗";s:2:"";s:3:"紋";s:2:"";s:3:"紊";s:2:"";s:3:"素";s:2:"";s:3:"索";s:2:"";s:3:"純";s:2:"";s:3:"紐";s:2:"";s:3:"紕";s:2:"";s:3:"級";s:2:"";s:3:"紜";s:2:"";s:3:"納";s:2:"";s:3:"紙";s:2:"";s:3:"紛";s:2:"";s:3:"缺";s:2:"";s:3:"罟";s:2:"";s:3:"羔";s:2:"";s:3:"翅";s:2:"";s:3:"翁";s:2:"";s:3:"耆";s:2:"";s:3:"耘";s:2:"";s:3:"耕";s:2:"";s:3:"耙";s:2:"";s:3:"耗";s:2:"";s:3:"耽";s:2:"";s:3:"耿";s:2:"";s:3:"胱";s:2:"";s:3:"脂";s:2:"";s:3:"胰";s:2:"";s:3:"脅";s:2:"";s:3:"胭";s:2:"";s:3:"胴";s:2:"";s:3:"脆";s:2:"";s:3:"胸";s:2:"";s:3:"胳";s:2:"";s:3:"脈";s:2:"";s:3:"能";s:2:"";s:3:"脊";s:2:"";s:3:"胼";s:2:"";s:3:"胯";s:2:"";s:3:"臭";s:2:"";s:3:"臬";s:2:"";s:3:"舀";s:2:"";s:3:"舐";s:2:"";s:3:"航";s:2:"";s:3:"舫";s:2:"";s:3:"舨";s:2:"";s:3:"般";s:2:"";s:3:"芻";s:2:"";s:3:"茫";s:2:"";s:3:"荒";s:2:"";s:3:"荔";s:2:"";s:3:"荊";s:2:"";s:3:"茸";s:2:"";s:3:"荐";s:2:"";s:3:"草";s:2:"";s:3:"茵";s:2:"";s:3:"茴";s:2:"";s:3:"荏";s:2:"";s:3:"茲";s:2:"";s:3:"茹";s:2:"";s:3:"茶";s:2:"";s:3:"茗";s:2:"";s:3:"荀";s:2:"";s:3:"茱";s:2:"";s:3:"茨";s:2:"";s:3:"荃";s:2:"@";s:3:"虔";s:2:"A";s:3:"蚊";s:2:"B";s:3:"蚪";s:2:"C";s:3:"蚓";s:2:"D";s:3:"蚤";s:2:"E";s:3:"蚩";s:2:"F";s:3:"蚌";s:2:"G";s:3:"蚣";s:2:"H";s:3:"蚜";s:2:"I";s:3:"衰";s:2:"J";s:3:"衷";s:2:"K";s:3:"袁";s:2:"L";s:3:"袂";s:2:"M";s:3:"衽";s:2:"N";s:3:"衹";s:2:"O";s:3:"記";s:2:"P";s:3:"訐";s:2:"Q";s:3:"討";s:2:"R";s:3:"訌";s:2:"S";s:3:"訕";s:2:"T";s:3:"訊";s:2:"U";s:3:"託";s:2:"V";s:3:"訓";s:2:"W";s:3:"訖";s:2:"X";s:3:"訏";s:2:"Y";s:3:"訑";s:2:"Z";s:3:"豈";s:2:"[";s:3:"豺";s:2:"\";s:3:"豹";s:2:"]";s:3:"財";s:2:"^";s:3:"貢";s:2:"_";s:3:"起";s:2:"`";s:3:"躬";s:2:"a";s:3:"軒";s:2:"b";s:3:"軔";s:2:"c";s:3:"軏";s:2:"d";s:3:"辱";s:2:"e";s:3:"送";s:2:"f";s:3:"逆";s:2:"g";s:3:"迷";s:2:"h";s:3:"退";s:2:"i";s:3:"迺";s:2:"j";s:3:"迴";s:2:"k";s:3:"逃";s:2:"l";s:3:"追";s:2:"m";s:3:"逅";s:2:"n";s:3:"迸";s:2:"o";s:3:"邕";s:2:"p";s:3:"郡";s:2:"q";s:3:"郝";s:2:"r";s:3:"郢";s:2:"s";s:3:"酒";s:2:"t";s:3:"配";s:2:"u";s:3:"酌";s:2:"v";s:3:"釘";s:2:"w";s:3:"針";s:2:"x";s:3:"釗";s:2:"y";s:3:"釜";s:2:"z";s:3:"釙";s:2:"{";s:3:"閃";s:2:"|";s:3:"院";s:2:"}";s:3:"陣";s:2:"~";s:3:"陡";s:2:"";s:3:"陛";s:2:"";s:3:"陝";s:2:"";s:3:"除";s:2:"";s:3:"陘";s:2:"";s:3:"陞";s:2:"";s:3:"隻";s:2:"";s:3:"飢";s:2:"";s:3:"馬";s:2:"";s:3:"骨";s:2:"";s:3:"高";s:2:"";s:3:"鬥";s:2:"";s:3:"鬲";s:2:"";s:3:"鬼";s:2:"";s:3:"乾";s:2:"";s:3:"偺";s:2:"";s:3:"偽";s:2:"";s:3:"停";s:2:"";s:3:"假";s:2:"";s:3:"偃";s:2:"";s:3:"偌";s:2:"";s:3:"做";s:2:"";s:3:"偉";s:2:"";s:3:"健";s:2:"";s:3:"偶";s:2:"";s:3:"偎";s:2:"";s:3:"偕";s:2:"";s:3:"偵";s:2:"";s:3:"側";s:2:"";s:3:"偷";s:2:"";s:3:"偏";s:2:"";s:3:"倏";s:2:"";s:3:"偯";s:2:"";s:3:"偭";s:2:"";s:3:"兜";s:2:"";s:3:"冕";s:2:"";s:3:"凰";s:2:"";s:3:"剪";s:2:"";s:3:"副";s:2:"";s:3:"勒";s:2:"";s:3:"務";s:2:"";s:3:"勘";s:2:"";s:3:"動";s:2:"";s:3:"匐";s:2:"";s:3:"匏";s:2:"";s:3:"匙";s:2:"";s:3:"匿";s:2:"";s:3:"區";s:2:"";s:3:"匾";s:2:"";s:3:"參";s:2:"";s:3:"曼";s:2:"";s:3:"商";s:2:"";s:3:"啪";s:2:"";s:3:"啦";s:2:"";s:3:"啄";s:2:"";s:3:"啞";s:2:"";s:3:"啡";s:2:"";s:3:"啃";s:2:"";s:3:"啊";s:2:"";s:3:"唱";s:2:"";s:3:"啖";s:2:"";s:3:"問";s:2:"";s:3:"啕";s:2:"";s:3:"唯";s:2:"";s:3:"啤";s:2:"";s:3:"唸";s:2:"";s:3:"售";s:2:"";s:3:"啜";s:2:"";s:3:"唬";s:2:"";s:3:"啣";s:2:"";s:3:"唳";s:2:"";s:3:"啁";s:2:"";s:3:"啗";s:2:"";s:3:"圈";s:2:"";s:3:"國";s:2:"";s:3:"圉";s:2:"";s:3:"域";s:2:"";s:3:"堅";s:2:"";s:3:"堊";s:2:"";s:3:"堆";s:2:"";s:3:"埠";s:2:"";s:3:"埤";s:2:"";s:3:"基";s:2:"";s:3:"堂";s:2:"";s:3:"堵";s:2:"";s:3:"執";s:2:"";s:3:"培";s:2:"";s:3:"夠";s:2:"";s:3:"奢";s:2:"";s:3:"娶";s:2:"";s:3:"婁";s:2:"";s:3:"婉";s:2:"";s:3:"婦";s:2:"";s:3:"婪";s:2:"";s:3:"婀";s:2:"@";s:3:"娼";s:2:"A";s:3:"婢";s:2:"B";s:3:"婚";s:2:"C";s:3:"婆";s:2:"D";s:3:"婊";s:2:"E";s:3:"孰";s:2:"F";s:3:"寇";s:2:"G";s:3:"寅";s:2:"H";s:3:"寄";s:2:"I";s:3:"寂";s:2:"J";s:3:"宿";s:2:"K";s:3:"密";s:2:"L";s:3:"尉";s:2:"M";s:3:"專";s:2:"N";s:3:"將";s:2:"O";s:3:"屠";s:2:"P";s:3:"屜";s:2:"Q";s:3:"屝";s:2:"R";s:3:"崇";s:2:"S";s:3:"崆";s:2:"T";s:3:"崎";s:2:"U";s:3:"崛";s:2:"V";s:3:"崖";s:2:"W";s:3:"崢";s:2:"X";s:3:"崑";s:2:"Y";s:3:"崩";s:2:"Z";s:3:"崔";s:2:"[";s:3:"崙";s:2:"\";s:3:"崤";s:2:"]";s:3:"崧";s:2:"^";s:3:"崗";s:2:"_";s:3:"巢";s:2:"`";s:3:"常";s:2:"a";s:3:"帶";s:2:"b";s:3:"帳";s:2:"c";s:3:"帷";s:2:"d";s:3:"康";s:2:"e";s:3:"庸";s:2:"f";s:3:"庶";s:2:"g";s:3:"庵";s:2:"h";s:3:"庾";s:2:"i";s:3:"張";s:2:"j";s:3:"強";s:2:"k";s:3:"彗";s:2:"l";s:3:"彬";s:2:"m";s:3:"彩";s:2:"n";s:3:"彫";s:2:"o";s:3:"得";s:2:"p";s:3:"徙";s:2:"q";s:3:"從";s:2:"r";s:3:"徘";s:2:"s";s:3:"御";s:2:"t";s:3:"徠";s:2:"u";s:3:"徜";s:2:"v";s:3:"恿";s:2:"w";s:3:"患";s:2:"x";s:3:"悉";s:2:"y";s:3:"悠";s:2:"z";s:3:"您";s:2:"{";s:3:"惋";s:2:"|";s:3:"悴";s:2:"}";s:3:"惦";s:2:"~";s:3:"悽";s:2:"";s:3:"情";s:2:"";s:3:"悻";s:2:"";s:3:"悵";s:2:"";s:3:"惜";s:2:"";s:3:"悼";s:2:"";s:3:"惘";s:2:"";s:3:"惕";s:2:"";s:3:"惆";s:2:"";s:3:"惟";s:2:"";s:3:"悸";s:2:"";s:3:"惚";s:2:"";s:3:"惇";s:2:"";s:3:"戚";s:2:"";s:3:"戛";s:2:"";s:3:"扈";s:2:"";s:3:"掠";s:2:"";s:3:"控";s:2:"";s:3:"捲";s:2:"";s:3:"掖";s:2:"";s:3:"探";s:2:"";s:3:"接";s:2:"";s:3:"捷";s:2:"";s:3:"捧";s:2:"";s:3:"掘";s:2:"";s:3:"措";s:2:"";s:3:"捱";s:2:"";s:3:"掩";s:2:"";s:3:"掉";s:2:"";s:3:"掃";s:2:"";s:3:"掛";s:2:"";s:3:"捫";s:2:"";s:3:"推";s:2:"";s:3:"掄";s:2:"";s:3:"授";s:2:"";s:3:"掙";s:2:"";s:3:"採";s:2:"";s:3:"掬";s:2:"";s:3:"排";s:2:"";s:3:"掏";s:2:"";s:3:"掀";s:2:"";s:3:"捻";s:2:"";s:3:"捩";s:2:"";s:3:"捨";s:2:"";s:3:"捺";s:2:"";s:3:"敝";s:2:"";s:3:"敖";s:2:"";s:3:"救";s:2:"";s:3:"教";s:2:"";s:3:"敗";s:2:"";s:3:"啟";s:2:"";s:3:"敏";s:2:"";s:3:"敘";s:2:"";s:3:"敕";s:2:"";s:3:"敔";s:2:"";s:3:"斜";s:2:"";s:3:"斛";s:2:"";s:3:"斬";s:2:"";s:3:"族";s:2:"";s:3:"旋";s:2:"";s:3:"旌";s:2:"";s:3:"旎";s:2:"";s:3:"晝";s:2:"";s:3:"晚";s:2:"";s:3:"晤";s:2:"";s:3:"晨";s:2:"";s:3:"晦";s:2:"";s:3:"晞";s:2:"";s:3:"曹";s:2:"";s:3:"勗";s:2:"";s:3:"望";s:2:"";s:3:"梁";s:2:"";s:3:"梯";s:2:"";s:3:"梢";s:2:"";s:3:"梓";s:2:"";s:3:"梵";s:2:"";s:3:"桿";s:2:"";s:3:"桶";s:2:"";s:3:"梱";s:2:"";s:3:"梧";s:2:"";s:3:"梗";s:2:"";s:3:"械";s:2:"";s:3:"梃";s:2:"";s:3:"棄";s:2:"";s:3:"梭";s:2:"";s:3:"梆";s:2:"";s:3:"梅";s:2:"";s:3:"梔";s:2:"";s:3:"條";s:2:"";s:3:"梨";s:2:"";s:3:"梟";s:2:"";s:3:"梡";s:2:"";s:3:"梂";s:2:"";s:3:"欲";s:2:"";s:3:"殺";s:2:"@";s:3:"毫";s:2:"A";s:3:"毬";s:2:"B";s:3:"氫";s:2:"C";s:3:"涎";s:2:"D";s:3:"涼";s:2:"E";s:3:"淳";s:2:"F";s:3:"淙";s:2:"G";s:3:"液";s:2:"H";s:3:"淡";s:2:"I";s:3:"淌";s:2:"J";s:3:"淤";s:2:"K";s:3:"添";s:2:"L";s:3:"淺";s:2:"M";s:3:"清";s:2:"N";s:3:"淇";s:2:"O";s:3:"淋";s:2:"P";s:3:"涯";s:2:"Q";s:3:"淑";s:2:"R";s:3:"涮";s:2:"S";s:3:"淞";s:2:"T";s:3:"淹";s:2:"U";s:3:"涸";s:2:"V";s:3:"混";s:2:"W";s:3:"淵";s:2:"X";s:3:"淅";s:2:"Y";s:3:"淒";s:2:"Z";s:3:"渚";s:2:"[";s:3:"涵";s:2:"\";s:3:"淚";s:2:"]";s:3:"淫";s:2:"^";s:3:"淘";s:2:"_";s:3:"淪";s:2:"`";s:3:"深";s:2:"a";s:3:"淮";s:2:"b";s:3:"淨";s:2:"c";s:3:"淆";s:2:"d";s:3:"淄";s:2:"e";s:3:"涪";s:2:"f";s:3:"淬";s:2:"g";s:3:"涿";s:2:"h";s:3:"淦";s:2:"i";s:3:"烹";s:2:"j";s:3:"焉";s:2:"k";s:3:"焊";s:2:"l";s:3:"烽";s:2:"m";s:3:"烯";s:2:"n";s:3:"爽";s:2:"o";s:3:"牽";s:2:"p";s:3:"犁";s:2:"q";s:3:"猜";s:2:"r";s:3:"猛";s:2:"s";s:3:"猖";s:2:"t";s:3:"猓";s:2:"u";s:3:"猙";s:2:"v";s:3:"率";s:2:"w";s:3:"琅";s:2:"x";s:3:"琊";s:2:"y";s:3:"球";s:2:"z";s:3:"理";s:2:"{";s:3:"現";s:2:"|";s:3:"琍";s:2:"}";s:3:"瓠";s:2:"~";s:3:"瓶";s:2:"";s:3:"瓷";s:2:"";s:3:"甜";s:2:"";s:3:"產";s:2:"";s:3:"略";s:2:"";s:3:"畦";s:2:"";s:3:"畢";s:2:"";s:3:"異";s:2:"";s:3:"疏";s:2:"";s:3:"痔";s:2:"";s:3:"痕";s:2:"";s:3:"疵";s:2:"";s:3:"痊";s:2:"";s:3:"痍";s:2:"";s:3:"皎";s:2:"";s:3:"盔";s:2:"";s:3:"盒";s:2:"";s:3:"盛";s:2:"";s:3:"眷";s:2:"";s:3:"眾";s:2:"";s:3:"眼";s:2:"";s:3:"眶";s:2:"";s:3:"眸";s:2:"";s:3:"眺";s:2:"";s:3:"硫";s:2:"";s:3:"硃";s:2:"";s:3:"硎";s:2:"";s:3:"祥";s:2:"";s:3:"票";s:2:"";s:3:"祭";s:2:"";s:3:"移";s:2:"";s:3:"窒";s:2:"";s:3:"窕";s:2:"";s:3:"笠";s:2:"";s:3:"笨";s:2:"";s:3:"笛";s:2:"";s:3:"第";s:2:"";s:3:"符";s:2:"";s:3:"笙";s:2:"";s:3:"笞";s:2:"";s:3:"笮";s:2:"";s:3:"粒";s:2:"";s:3:"粗";s:2:"";s:3:"粕";s:2:"";s:3:"絆";s:2:"";s:3:"絃";s:2:"";s:3:"統";s:2:"";s:3:"紮";s:2:"";s:3:"紹";s:2:"";s:3:"紼";s:2:"";s:3:"絀";s:2:"";s:3:"細";s:2:"";s:3:"紳";s:2:"";s:3:"組";s:2:"";s:3:"累";s:2:"";s:3:"終";s:2:"";s:3:"紲";s:2:"";s:3:"紱";s:2:"";s:3:"缽";s:2:"";s:3:"羞";s:2:"";s:3:"羚";s:2:"";s:3:"翌";s:2:"";s:3:"翎";s:2:"";s:3:"習";s:2:"";s:3:"耜";s:2:"";s:3:"聊";s:2:"";s:3:"聆";s:2:"";s:3:"脯";s:2:"";s:3:"脖";s:2:"";s:3:"脣";s:2:"";s:3:"脫";s:2:"";s:3:"脩";s:2:"";s:3:"脰";s:2:"";s:3:"脤";s:2:"";s:3:"舂";s:2:"";s:3:"舵";s:2:"";s:3:"舷";s:2:"";s:3:"舶";s:2:"";s:3:"船";s:2:"";s:3:"莎";s:2:"";s:3:"莞";s:2:"";s:3:"莘";s:2:"";s:3:"荸";s:2:"";s:3:"莢";s:2:"";s:3:"莖";s:2:"";s:3:"莽";s:2:"";s:3:"莫";s:2:"";s:3:"莒";s:2:"";s:3:"莊";s:2:"";s:3:"莓";s:2:"";s:3:"莉";s:2:"";s:3:"莠";s:2:"";s:3:"荷";s:2:"";s:3:"荻";s:2:"";s:3:"荼";s:2:"@";s:3:"莆";s:2:"A";s:3:"莧";s:2:"B";s:3:"處";s:2:"C";s:3:"彪";s:2:"D";s:3:"蛇";s:2:"E";s:3:"蛀";s:2:"F";s:3:"蚶";s:2:"G";s:3:"蛄";s:2:"H";s:3:"蚵";s:2:"I";s:3:"蛆";s:2:"J";s:3:"蛋";s:2:"K";s:3:"蚱";s:2:"L";s:3:"蚯";s:2:"M";s:3:"蛉";s:2:"N";s:3:"術";s:2:"O";s:3:"袞";s:2:"P";s:3:"袈";s:2:"Q";s:3:"被";s:2:"R";s:3:"袒";s:2:"S";s:3:"袖";s:2:"T";s:3:"袍";s:2:"U";s:3:"袋";s:2:"V";s:3:"覓";s:2:"W";s:3:"規";s:2:"X";s:3:"訪";s:2:"Y";s:3:"訝";s:2:"Z";s:3:"訣";s:2:"[";s:3:"訥";s:2:"\";s:3:"許";s:2:"]";s:3:"設";s:2:"^";s:3:"訟";s:2:"_";s:3:"訛";s:2:"`";s:3:"訢";s:2:"a";s:3:"豉";s:2:"b";s:3:"豚";s:2:"c";s:3:"販";s:2:"d";s:3:"責";s:2:"e";s:3:"貫";s:2:"f";s:3:"貨";s:2:"g";s:3:"貪";s:2:"h";s:3:"貧";s:2:"i";s:3:"赧";s:2:"j";s:3:"赦";s:2:"k";s:3:"趾";s:2:"l";s:3:"趺";s:2:"m";s:3:"軛";s:2:"n";s:3:"軟";s:2:"o";s:3:"這";s:2:"p";s:3:"逍";s:2:"q";s:3:"通";s:2:"r";s:3:"逗";s:2:"s";s:3:"連";s:2:"t";s:3:"速";s:2:"u";s:3:"逝";s:2:"v";s:3:"逐";s:2:"w";s:3:"逕";s:2:"x";s:3:"逞";s:2:"y";s:3:"造";s:2:"z";s:3:"透";s:2:"{";s:3:"逢";s:2:"|";s:3:"逖";s:2:"}";s:3:"逛";s:2:"~";s:3:"途";s:2:"";s:3:"部";s:2:"";s:3:"郭";s:2:"";s:3:"都";s:2:"";s:3:"酗";s:2:"";s:3:"野";s:2:"";s:3:"釵";s:2:"";s:3:"釦";s:2:"";s:3:"釣";s:2:"";s:3:"釧";s:2:"";s:3:"釭";s:2:"";s:3:"釩";s:2:"";s:3:"閉";s:2:"";s:3:"陪";s:2:"";s:3:"陵";s:2:"";s:3:"陳";s:2:"";s:3:"陸";s:2:"";s:3:"陰";s:2:"";s:3:"陴";s:2:"";s:3:"陶";s:2:"";s:3:"陷";s:2:"";s:3:"陬";s:2:"";s:3:"雀";s:2:"";s:3:"雪";s:2:"";s:3:"雩";s:2:"";s:3:"章";s:2:"";s:3:"竟";s:2:"";s:3:"頂";s:2:"";s:3:"頃";s:2:"";s:3:"魚";s:2:"";s:3:"鳥";s:2:"";s:3:"鹵";s:2:"";s:3:"鹿";s:2:"";s:3:"麥";s:2:"";s:3:"麻";s:2:"";s:3:"傢";s:2:"";s:3:"傍";s:2:"";s:3:"傅";s:2:"";s:3:"備";s:2:"";s:3:"傑";s:2:"";s:3:"傀";s:2:"";s:3:"傖";s:2:"";s:3:"傘";s:2:"";s:3:"傚";s:2:"";s:3:"最";s:2:"";s:3:"凱";s:2:"";s:3:"割";s:2:"";s:3:"剴";s:2:"";s:3:"創";s:2:"";s:3:"剩";s:2:"";s:3:"勞";s:2:"";s:3:"勝";s:2:"";s:3:"勛";s:2:"";s:3:"博";s:2:"";s:3:"厥";s:2:"";s:3:"啻";s:2:"";s:3:"喀";s:2:"";s:3:"喧";s:2:"";s:3:"啼";s:2:"";s:3:"喊";s:2:"";s:3:"喝";s:2:"";s:3:"喘";s:2:"";s:3:"喂";s:2:"";s:3:"喜";s:2:"";s:3:"喪";s:2:"";s:3:"喔";s:2:"";s:3:"喇";s:2:"";s:3:"喋";s:2:"";s:3:"喃";s:2:"";s:3:"喳";s:2:"";s:3:"單";s:2:"";s:3:"喟";s:2:"";s:3:"唾";s:2:"";s:3:"喲";s:2:"";s:3:"喚";s:2:"";s:3:"喻";s:2:"";s:3:"喬";s:2:"";s:3:"喱";s:2:"";s:3:"啾";s:2:"";s:3:"喉";s:2:"";s:3:"喫";s:2:"";s:3:"喙";s:2:"";s:3:"圍";s:2:"";s:3:"堯";s:2:"";s:3:"堪";s:2:"";s:3:"場";s:2:"";s:3:"堤";s:2:"";s:3:"堰";s:2:"";s:3:"報";s:2:"";s:3:"堡";s:2:"";s:3:"堝";s:2:"";s:3:"堠";s:2:"";s:3:"壹";s:2:"";s:3:"壺";s:2:"";s:3:"奠";s:2:"@";s:3:"婷";s:2:"A";s:3:"媚";s:2:"B";s:3:"婿";s:2:"C";s:3:"媒";s:2:"D";s:3:"媛";s:2:"E";s:3:"媧";s:2:"F";s:3:"孳";s:2:"G";s:3:"孱";s:2:"H";s:3:"寒";s:2:"I";s:3:"富";s:2:"J";s:3:"寓";s:2:"K";s:3:"寐";s:2:"L";s:3:"尊";s:2:"M";s:3:"尋";s:2:"N";s:3:"就";s:2:"O";s:3:"嵌";s:2:"P";s:3:"嵐";s:2:"Q";s:3:"崴";s:2:"R";s:3:"嵇";s:2:"S";s:3:"巽";s:2:"T";s:3:"幅";s:2:"U";s:3:"帽";s:2:"V";s:3:"幀";s:2:"W";s:3:"幃";s:2:"X";s:3:"幾";s:2:"Y";s:3:"廊";s:2:"Z";s:3:"廁";s:2:"[";s:3:"廂";s:2:"\";s:3:"廄";s:2:"]";s:3:"弼";s:2:"^";s:3:"彭";s:2:"_";s:3:"復";s:2:"`";s:3:"循";s:2:"a";s:3:"徨";s:2:"b";s:3:"惑";s:2:"c";s:3:"惡";s:2:"d";s:3:"悲";s:2:"e";s:3:"悶";s:2:"f";s:3:"惠";s:2:"g";s:3:"愜";s:2:"h";s:3:"愣";s:2:"i";s:3:"惺";s:2:"j";s:3:"愕";s:2:"k";s:3:"惰";s:2:"l";s:3:"惻";s:2:"m";s:3:"惴";s:2:"n";s:3:"慨";s:2:"o";s:3:"惱";s:2:"p";s:3:"愎";s:2:"q";s:3:"惶";s:2:"r";s:3:"愉";s:2:"s";s:3:"愀";s:2:"t";s:3:"愒";s:2:"u";s:3:"戟";s:2:"v";s:3:"扉";s:2:"w";s:3:"掣";s:2:"x";s:3:"掌";s:2:"y";s:3:"描";s:2:"z";s:3:"揀";s:2:"{";s:3:"揩";s:2:"|";s:3:"揉";s:2:"}";s:3:"揆";s:2:"~";s:3:"揍";s:2:"";s:3:"插";s:2:"";s:3:"揣";s:2:"";s:3:"提";s:2:"";s:3:"握";s:2:"";s:3:"揖";s:2:"";s:3:"揭";s:2:"";s:3:"揮";s:2:"";s:3:"捶";s:2:"";s:3:"援";s:2:"";s:3:"揪";s:2:"";s:3:"換";s:2:"";s:3:"摒";s:2:"";s:3:"揚";s:2:"";s:3:"揹";s:2:"";s:3:"敞";s:2:"";s:3:"敦";s:2:"";s:3:"敢";s:2:"";s:3:"散";s:2:"";s:3:"斑";s:2:"";s:3:"斐";s:2:"";s:3:"斯";s:2:"";s:3:"普";s:2:"";s:3:"晰";s:2:"";s:3:"晴";s:2:"";s:3:"晶";s:2:"";s:3:"景";s:2:"";s:3:"暑";s:2:"";s:3:"智";s:2:"";s:3:"晾";s:2:"";s:3:"晷";s:2:"";s:3:"曾";s:2:"";s:3:"替";s:2:"";s:3:"期";s:2:"";s:3:"朝";s:2:"";s:3:"棺";s:2:"";s:3:"棕";s:2:"";s:3:"棠";s:2:"";s:3:"棘";s:2:"";s:3:"棗";s:2:"";s:3:"椅";s:2:"";s:3:"棟";s:2:"";s:3:"棵";s:2:"";s:3:"森";s:2:"";s:3:"棧";s:2:"";s:3:"棹";s:2:"";s:3:"棒";s:2:"";s:3:"棲";s:2:"";s:3:"棣";s:2:"";s:3:"棋";s:2:"";s:3:"棍";s:2:"";s:3:"植";s:2:"";s:3:"椒";s:2:"";s:3:"椎";s:2:"";s:3:"棉";s:2:"";s:3:"棚";s:2:"";s:3:"楮";s:2:"";s:3:"棻";s:2:"";s:3:"款";s:2:"";s:3:"欺";s:2:"";s:3:"欽";s:2:"";s:3:"殘";s:2:"";s:3:"殖";s:2:"";s:3:"殼";s:2:"";s:3:"毯";s:2:"";s:3:"氮";s:2:"";s:3:"氯";s:2:"";s:3:"氬";s:2:"";s:3:"港";s:2:"";s:3:"游";s:2:"";s:3:"湔";s:2:"";s:3:"渡";s:2:"";s:3:"渲";s:2:"";s:3:"湧";s:2:"";s:3:"湊";s:2:"";s:3:"渠";s:2:"";s:3:"渥";s:2:"";s:3:"渣";s:2:"";s:3:"減";s:2:"";s:3:"湛";s:2:"";s:3:"湘";s:2:"";s:3:"渤";s:2:"";s:3:"湖";s:2:"";s:3:"湮";s:2:"";s:3:"渭";s:2:"";s:3:"渦";s:2:"";s:3:"湯";s:2:"";s:3:"渴";s:2:"";s:3:"湍";s:2:"";s:3:"渺";s:2:"";s:3:"測";s:2:"";s:3:"湃";s:2:"";s:3:"渝";s:2:"";s:3:"渾";s:2:"";s:3:"滋";s:2:"@";s:3:"溉";s:2:"A";s:3:"渙";s:2:"B";s:3:"湎";s:2:"C";s:3:"湣";s:2:"D";s:3:"湄";s:2:"E";s:3:"湲";s:2:"F";s:3:"湩";s:2:"G";s:3:"湟";s:2:"H";s:3:"焙";s:2:"I";s:3:"焚";s:2:"J";s:3:"焦";s:2:"K";s:3:"焰";s:2:"L";s:3:"無";s:2:"M";s:3:"然";s:2:"N";s:3:"煮";s:2:"O";s:3:"焜";s:2:"P";s:3:"牌";s:2:"Q";s:3:"犄";s:2:"R";s:3:"犀";s:2:"S";s:3:"猶";s:2:"T";s:3:"猥";s:2:"U";s:3:"猴";s:2:"V";s:3:"猩";s:2:"W";s:3:"琺";s:2:"X";s:3:"琪";s:2:"Y";s:3:"琳";s:2:"Z";s:3:"琢";s:2:"[";s:3:"琥";s:2:"\";s:3:"琵";s:2:"]";s:3:"琶";s:2:"^";s:3:"琴";s:2:"_";s:3:"琯";s:2:"`";s:3:"琛";s:2:"a";s:3:"琦";s:2:"b";s:3:"琨";s:2:"c";s:3:"甥";s:2:"d";s:3:"甦";s:2:"e";s:3:"畫";s:2:"f";s:3:"番";s:2:"g";s:3:"痢";s:2:"h";s:3:"痛";s:2:"i";s:3:"痣";s:2:"j";s:3:"痙";s:2:"k";s:3:"痘";s:2:"l";s:3:"痞";s:2:"m";s:3:"痠";s:2:"n";s:3:"登";s:2:"o";s:3:"發";s:2:"p";s:3:"皖";s:2:"q";s:3:"皓";s:2:"r";s:3:"皴";s:2:"s";s:3:"盜";s:2:"t";s:3:"睏";s:2:"u";s:3:"短";s:2:"v";s:3:"硝";s:2:"w";s:3:"硬";s:2:"x";s:3:"硯";s:2:"y";s:3:"稍";s:2:"z";s:3:"稈";s:2:"{";s:3:"程";s:2:"|";s:3:"稅";s:2:"}";s:3:"稀";s:2:"~";s:3:"窘";s:2:"";s:3:"窗";s:2:"";s:3:"窖";s:2:"";s:3:"童";s:2:"";s:3:"竣";s:2:"";s:3:"等";s:2:"";s:3:"策";s:2:"";s:3:"筆";s:2:"";s:3:"筐";s:2:"";s:3:"筒";s:2:"";s:3:"答";s:2:"";s:3:"筍";s:2:"";s:3:"筋";s:2:"";s:3:"筏";s:2:"";s:3:"筑";s:2:"";s:3:"粟";s:2:"";s:3:"粥";s:2:"";s:3:"絞";s:2:"";s:3:"結";s:2:"";s:3:"絨";s:2:"";s:3:"絕";s:2:"";s:3:"紫";s:2:"";s:3:"絮";s:2:"";s:3:"絲";s:2:"";s:3:"絡";s:2:"";s:3:"給";s:2:"";s:3:"絢";s:2:"";s:3:"絰";s:2:"";s:3:"絳";s:2:"";s:3:"善";s:2:"";s:3:"翔";s:2:"";s:3:"翕";s:2:"";s:3:"耋";s:2:"";s:3:"聒";s:2:"";s:3:"肅";s:2:"";s:3:"腕";s:2:"";s:3:"腔";s:2:"";s:3:"腋";s:2:"";s:3:"腑";s:2:"";s:3:"腎";s:2:"";s:3:"脹";s:2:"";s:3:"腆";s:2:"";s:3:"脾";s:2:"";s:3:"腌";s:2:"";s:3:"腓";s:2:"";s:3:"腴";s:2:"";s:3:"舒";s:2:"";s:3:"舜";s:2:"";s:3:"菩";s:2:"";s:3:"萃";s:2:"";s:3:"菸";s:2:"";s:3:"萍";s:2:"";s:3:"菠";s:2:"";s:3:"菅";s:2:"";s:3:"萋";s:2:"";s:3:"菁";s:2:"";s:3:"華";s:2:"";s:3:"菱";s:2:"";s:3:"菴";s:2:"";s:3:"著";s:2:"";s:3:"萊";s:2:"";s:3:"菰";s:2:"";s:3:"萌";s:2:"";s:3:"菌";s:2:"";s:3:"菽";s:2:"";s:3:"菲";s:2:"";s:3:"菊";s:2:"";s:3:"萸";s:2:"";s:3:"萎";s:2:"";s:3:"萄";s:2:"";s:3:"菜";s:2:"";s:3:"萇";s:2:"";s:3:"菔";s:2:"";s:3:"菟";s:2:"";s:3:"虛";s:2:"";s:3:"蛟";s:2:"";s:3:"蛙";s:2:"";s:3:"蛭";s:2:"";s:3:"蛔";s:2:"";s:3:"蛛";s:2:"";s:3:"蛤";s:2:"";s:3:"蛐";s:2:"";s:3:"蛞";s:2:"";s:3:"街";s:2:"";s:3:"裁";s:2:"";s:3:"裂";s:2:"";s:3:"袱";s:2:"";s:3:"覃";s:2:"";s:3:"視";s:2:"";s:3:"註";s:2:"";s:3:"詠";s:2:"";s:3:"評";s:2:"";s:3:"詞";s:2:"";s:3:"証";s:2:"";s:3:"詁";s:2:"@";s:3:"詔";s:2:"A";s:3:"詛";s:2:"B";s:3:"詐";s:2:"C";s:3:"詆";s:2:"D";s:3:"訴";s:2:"E";s:3:"診";s:2:"F";s:3:"訶";s:2:"G";s:3:"詖";s:2:"H";s:3:"象";s:2:"I";s:3:"貂";s:2:"J";s:3:"貯";s:2:"K";s:3:"貼";s:2:"L";s:3:"貳";s:2:"M";s:3:"貽";s:2:"N";s:3:"賁";s:2:"O";s:3:"費";s:2:"P";s:3:"賀";s:2:"Q";s:3:"貴";s:2:"R";s:3:"買";s:2:"S";s:3:"貶";s:2:"T";s:3:"貿";s:2:"U";s:3:"貸";s:2:"V";s:3:"越";s:2:"W";s:3:"超";s:2:"X";s:3:"趁";s:2:"Y";s:3:"跎";s:2:"Z";s:3:"距";s:2:"[";s:3:"跋";s:2:"\";s:3:"跚";s:2:"]";s:3:"跑";s:2:"^";s:3:"跌";s:2:"_";s:3:"跛";s:2:"`";s:3:"跆";s:2:"a";s:3:"軻";s:2:"b";s:3:"軸";s:2:"c";s:3:"軼";s:2:"d";s:3:"辜";s:2:"e";s:3:"逮";s:2:"f";s:3:"逵";s:2:"g";s:3:"週";s:2:"h";s:3:"逸";s:2:"i";s:3:"進";s:2:"j";s:3:"逶";s:2:"k";s:3:"鄂";s:2:"l";s:3:"郵";s:2:"m";s:3:"鄉";s:2:"n";s:3:"郾";s:2:"o";s:3:"酣";s:2:"p";s:3:"酥";s:2:"q";s:3:"量";s:2:"r";s:3:"鈔";s:2:"s";s:3:"鈕";s:2:"t";s:3:"鈣";s:2:"u";s:3:"鈉";s:2:"v";s:3:"鈞";s:2:"w";s:3:"鈍";s:2:"x";s:3:"鈐";s:2:"y";s:3:"鈇";s:2:"z";s:3:"鈑";s:2:"{";s:3:"閔";s:2:"|";s:3:"閏";s:2:"}";s:3:"開";s:2:"~";s:3:"閑";s:2:"";s:3:"間";s:2:"";s:3:"閒";s:2:"";s:3:"閎";s:2:"";s:3:"隊";s:2:"";s:3:"階";s:2:"";s:3:"隋";s:2:"";s:3:"陽";s:2:"";s:3:"隅";s:2:"";s:3:"隆";s:2:"";s:3:"隍";s:2:"";s:3:"陲";s:2:"";s:3:"隄";s:2:"";s:3:"雁";s:2:"";s:3:"雅";s:2:"";s:3:"雄";s:2:"";s:3:"集";s:2:"";s:3:"雇";s:2:"";s:3:"雯";s:2:"";s:3:"雲";s:2:"";s:3:"韌";s:2:"";s:3:"項";s:2:"";s:3:"順";s:2:"";s:3:"須";s:2:"";s:3:"飧";s:2:"";s:3:"飪";s:2:"";s:3:"飯";s:2:"";s:3:"飩";s:2:"";s:3:"飲";s:2:"";s:3:"飭";s:2:"";s:3:"馮";s:2:"";s:3:"馭";s:2:"";s:3:"黃";s:2:"";s:3:"黍";s:2:"";s:3:"黑";s:2:"";s:3:"亂";s:2:"";s:3:"傭";s:2:"";s:3:"債";s:2:"";s:3:"傲";s:2:"";s:3:"傳";s:2:"";s:3:"僅";s:2:"";s:3:"傾";s:2:"";s:3:"催";s:2:"";s:3:"傷";s:2:"";s:3:"傻";s:2:"";s:3:"傯";s:2:"";s:3:"僇";s:2:"";s:3:"剿";s:2:"";s:3:"剷";s:2:"";s:3:"剽";s:2:"";s:3:"募";s:2:"";s:3:"勦";s:2:"";s:3:"勤";s:2:"";s:3:"勢";s:2:"";s:3:"勣";s:2:"";s:3:"匯";s:2:"";s:3:"嗟";s:2:"";s:3:"嗨";s:2:"";s:3:"嗓";s:2:"";s:3:"嗦";s:2:"";s:3:"嗎";s:2:"";s:3:"嗜";s:2:"";s:3:"嗇";s:2:"";s:3:"嗑";s:2:"";s:3:"嗣";s:2:"";s:3:"嗤";s:2:"";s:3:"嗯";s:2:"";s:3:"嗚";s:2:"";s:3:"嗡";s:2:"";s:3:"嗅";s:2:"";s:3:"嗆";s:2:"";s:3:"嗥";s:2:"";s:3:"嗉";s:2:"";s:3:"園";s:2:"";s:3:"圓";s:2:"";s:3:"塞";s:2:"";s:3:"塑";s:2:"";s:3:"塘";s:2:"";s:3:"塗";s:2:"";s:3:"塚";s:2:"";s:3:"塔";s:2:"";s:3:"填";s:2:"";s:3:"塌";s:2:"";s:3:"塭";s:2:"";s:3:"塊";s:2:"";s:3:"塢";s:2:"";s:3:"塒";s:2:"";s:3:"塋";s:2:"";s:3:"奧";s:2:"";s:3:"嫁";s:2:"";s:3:"嫉";s:2:"";s:3:"嫌";s:2:"";s:3:"媾";s:2:"";s:3:"媽";s:2:"";s:3:"媼";s:2:"@";s:3:"媳";s:2:"A";s:3:"嫂";s:2:"B";s:3:"媲";s:2:"C";s:3:"嵩";s:2:"D";s:3:"嵯";s:2:"E";s:3:"幌";s:2:"F";s:3:"幹";s:2:"G";s:3:"廉";s:2:"H";s:3:"廈";s:2:"I";s:3:"弒";s:2:"J";s:3:"彙";s:2:"K";s:3:"徬";s:2:"L";s:3:"微";s:2:"M";s:3:"愚";s:2:"N";s:3:"意";s:2:"O";s:3:"慈";s:2:"P";s:3:"感";s:2:"Q";s:3:"想";s:2:"R";s:3:"愛";s:2:"S";s:3:"惹";s:2:"T";s:3:"愁";s:2:"U";s:3:"愈";s:2:"V";s:3:"慎";s:2:"W";s:3:"慌";s:2:"X";s:3:"慄";s:2:"Y";s:3:"慍";s:2:"Z";s:3:"愾";s:2:"[";s:3:"愴";s:2:"\";s:3:"愧";s:2:"]";s:3:"愍";s:2:"^";s:3:"愆";s:2:"_";s:3:"愷";s:2:"`";s:3:"戡";s:2:"a";s:3:"戢";s:2:"b";s:3:"搓";s:2:"c";s:3:"搾";s:2:"d";s:3:"搞";s:2:"e";s:3:"搪";s:2:"f";s:3:"搭";s:2:"g";s:3:"搽";s:2:"h";s:3:"搬";s:2:"i";s:3:"搏";s:2:"j";s:3:"搜";s:2:"k";s:3:"搔";s:2:"l";s:3:"損";s:2:"m";s:3:"搶";s:2:"n";s:3:"搖";s:2:"o";s:3:"搗";s:2:"p";s:3:"搆";s:2:"q";s:3:"敬";s:2:"r";s:3:"斟";s:2:"s";s:3:"新";s:2:"t";s:3:"暗";s:2:"u";s:3:"暉";s:2:"v";s:3:"暇";s:2:"w";s:3:"暈";s:2:"x";s:3:"暖";s:2:"y";s:3:"暄";s:2:"z";s:3:"暘";s:2:"{";s:3:"暍";s:2:"|";s:3:"會";s:2:"}";s:3:"榔";s:2:"~";s:3:"業";s:2:"";s:3:"楚";s:2:"";s:3:"楷";s:2:"";s:3:"楠";s:2:"";s:3:"楔";s:2:"";s:3:"極";s:2:"";s:3:"椰";s:2:"";s:3:"概";s:2:"";s:3:"楊";s:2:"";s:3:"楨";s:2:"";s:3:"楫";s:2:"";s:3:"楞";s:2:"";s:3:"楓";s:2:"";s:3:"楹";s:2:"";s:3:"榆";s:2:"";s:3:"楝";s:2:"";s:3:"楣";s:2:"";s:3:"楛";s:2:"";s:3:"歇";s:2:"";s:3:"歲";s:2:"";s:3:"毀";s:2:"";s:3:"殿";s:2:"";s:3:"毓";s:2:"";s:3:"毽";s:2:"";s:3:"溢";s:2:"";s:3:"溯";s:2:"";s:3:"滓";s:2:"";s:3:"溶";s:2:"";s:3:"滂";s:2:"";s:3:"源";s:2:"";s:3:"溝";s:2:"";s:3:"滇";s:2:"";s:3:"滅";s:2:"";s:3:"溥";s:2:"";s:3:"溘";s:2:"";s:3:"溼";s:2:"";s:3:"溺";s:2:"";s:3:"溫";s:2:"";s:3:"滑";s:2:"";s:3:"準";s:2:"";s:3:"溜";s:2:"";s:3:"滄";s:2:"";s:3:"滔";s:2:"";s:3:"溪";s:2:"";s:3:"溧";s:2:"";s:3:"溴";s:2:"";s:3:"煎";s:2:"";s:3:"煙";s:2:"";s:3:"煩";s:2:"";s:3:"煤";s:2:"";s:3:"煉";s:2:"";s:3:"照";s:2:"";s:3:"煜";s:2:"";s:3:"煬";s:2:"";s:3:"煦";s:2:"";s:3:"煌";s:2:"";s:3:"煥";s:2:"";s:3:"煞";s:2:"";s:3:"煆";s:2:"";s:3:"煨";s:2:"";s:3:"煖";s:2:"";s:3:"爺";s:2:"";s:3:"牒";s:2:"";s:3:"猷";s:2:"";s:3:"獅";s:2:"";s:3:"猿";s:2:"";s:3:"猾";s:2:"";s:3:"瑯";s:2:"";s:3:"瑚";s:2:"";s:3:"瑕";s:2:"";s:3:"瑟";s:2:"";s:3:"瑞";s:2:"";s:3:"瑁";s:2:"";s:3:"琿";s:2:"";s:3:"瑙";s:2:"";s:3:"瑛";s:2:"";s:3:"瑜";s:2:"";s:3:"當";s:2:"";s:3:"畸";s:2:"";s:3:"瘀";s:2:"";s:3:"痰";s:2:"";s:3:"瘁";s:2:"";s:3:"痲";s:2:"";s:3:"痱";s:2:"";s:3:"痺";s:2:"";s:3:"痿";s:2:"";s:3:"痴";s:2:"";s:3:"痳";s:2:"";s:3:"盞";s:2:"";s:3:"盟";s:2:"";s:3:"睛";s:2:"";s:3:"睫";s:2:"";s:3:"睦";s:2:"";s:3:"睞";s:2:"";s:3:"督";s:2:"@";s:3:"睹";s:2:"A";s:3:"睪";s:2:"B";s:3:"睬";s:2:"C";s:3:"睜";s:2:"D";s:3:"睥";s:2:"E";s:3:"睨";s:2:"F";s:3:"睢";s:2:"G";s:3:"矮";s:2:"H";s:3:"碎";s:2:"I";s:3:"碰";s:2:"J";s:3:"碗";s:2:"K";s:3:"碘";s:2:"L";s:3:"碌";s:2:"M";s:3:"碉";s:2:"N";s:3:"硼";s:2:"O";s:3:"碑";s:2:"P";s:3:"碓";s:2:"Q";s:3:"硿";s:2:"R";s:3:"祺";s:2:"S";s:3:"祿";s:2:"T";s:3:"禁";s:2:"U";s:3:"萬";s:2:"V";s:3:"禽";s:2:"W";s:3:"稜";s:2:"X";s:3:"稚";s:2:"Y";s:3:"稠";s:2:"Z";s:3:"稔";s:2:"[";s:3:"稟";s:2:"\";s:3:"稞";s:2:"]";s:3:"窟";s:2:"^";s:3:"窠";s:2:"_";s:3:"筷";s:2:"`";s:3:"節";s:2:"a";s:3:"筠";s:2:"b";s:3:"筮";s:2:"c";s:3:"筧";s:2:"d";s:3:"粱";s:2:"e";s:3:"粳";s:2:"f";s:3:"粵";s:2:"g";s:3:"經";s:2:"h";s:3:"絹";s:2:"i";s:3:"綑";s:2:"j";s:3:"綁";s:2:"k";s:3:"綏";s:2:"l";s:3:"絛";s:2:"m";s:3:"置";s:2:"n";s:3:"罩";s:2:"o";s:3:"罪";s:2:"p";s:3:"署";s:2:"q";s:3:"義";s:2:"r";s:3:"羨";s:2:"s";s:3:"群";s:2:"t";s:3:"聖";s:2:"u";s:3:"聘";s:2:"v";s:3:"肆";s:2:"w";s:3:"肄";s:2:"x";s:3:"腱";s:2:"y";s:3:"腰";s:2:"z";s:3:"腸";s:2:"{";s:3:"腥";s:2:"|";s:3:"腮";s:2:"}";s:3:"腳";s:2:"~";s:3:"腫";s:2:"";s:3:"腹";s:2:"";s:3:"腺";s:2:"";s:3:"腦";s:2:"";s:3:"舅";s:2:"";s:3:"艇";s:2:"";s:3:"蒂";s:2:"";s:3:"葷";s:2:"";s:3:"落";s:2:"";s:3:"萱";s:2:"";s:3:"葵";s:2:"";s:3:"葦";s:2:"";s:3:"葫";s:2:"";s:3:"葉";s:2:"";s:3:"葬";s:2:"";s:3:"葛";s:2:"";s:3:"萼";s:2:"";s:3:"萵";s:2:"";s:3:"葡";s:2:"";s:3:"董";s:2:"";s:3:"葩";s:2:"";s:3:"葭";s:2:"";s:3:"葆";s:2:"";s:3:"虞";s:2:"";s:3:"虜";s:2:"";s:3:"號";s:2:"";s:3:"蛹";s:2:"";s:3:"蜓";s:2:"";s:3:"蜈";s:2:"";s:3:"蜇";s:2:"";s:3:"蜀";s:2:"";s:3:"蛾";s:2:"";s:3:"蛻";s:2:"";s:3:"蜂";s:2:"";s:3:"蜃";s:2:"";s:3:"蜆";s:2:"";s:3:"蜊";s:2:"";s:3:"衙";s:2:"";s:3:"裟";s:2:"";s:3:"裔";s:2:"";s:3:"裙";s:2:"";s:3:"補";s:2:"";s:3:"裘";s:2:"";s:3:"裝";s:2:"";s:3:"裡";s:2:"";s:3:"裊";s:2:"";s:3:"裕";s:2:"";s:3:"裒";s:2:"";s:3:"覜";s:2:"";s:3:"解";s:2:"";s:3:"詫";s:2:"";s:3:"該";s:2:"";s:3:"詳";s:2:"";s:3:"試";s:2:"";s:3:"詩";s:2:"";s:3:"詰";s:2:"";s:3:"誇";s:2:"";s:3:"詼";s:2:"";s:3:"詣";s:2:"";s:3:"誠";s:2:"";s:3:"話";s:2:"";s:3:"誅";s:2:"";s:3:"詭";s:2:"";s:3:"詢";s:2:"";s:3:"詮";s:2:"";s:3:"詬";s:2:"";s:3:"詹";s:2:"";s:3:"詻";s:2:"";s:3:"訾";s:2:"";s:3:"詨";s:2:"";s:3:"豢";s:2:"";s:3:"貊";s:2:"";s:3:"貉";s:2:"";s:3:"賊";s:2:"";s:3:"資";s:2:"";s:3:"賈";s:2:"";s:3:"賄";s:2:"";s:3:"貲";s:2:"";s:3:"賃";s:2:"";s:3:"賂";s:2:"";s:3:"賅";s:2:"";s:3:"跡";s:2:"";s:3:"跟";s:2:"";s:3:"跨";s:2:"";s:3:"路";s:2:"";s:3:"跳";s:2:"";s:3:"跺";s:2:"";s:3:"跪";s:2:"";s:3:"跤";s:2:"";s:3:"跦";s:2:"";s:3:"躲";s:2:"";s:3:"較";s:2:"";s:3:"載";s:2:"";s:3:"軾";s:2:"";s:3:"輊";s:2:"@";s:3:"辟";s:2:"A";s:3:"農";s:2:"B";s:3:"運";s:2:"C";s:3:"遊";s:2:"D";s:3:"道";s:2:"E";s:3:"遂";s:2:"F";s:3:"達";s:2:"G";s:3:"逼";s:2:"H";s:3:"違";s:2:"I";s:3:"遐";s:2:"J";s:3:"遇";s:2:"K";s:3:"遏";s:2:"L";s:3:"過";s:2:"M";s:3:"遍";s:2:"N";s:3:"遑";s:2:"O";s:3:"逾";s:2:"P";s:3:"遁";s:2:"Q";s:3:"鄒";s:2:"R";s:3:"鄗";s:2:"S";s:3:"酬";s:2:"T";s:3:"酪";s:2:"U";s:3:"酩";s:2:"V";s:3:"釉";s:2:"W";s:3:"鈷";s:2:"X";s:3:"鉗";s:2:"Y";s:3:"鈸";s:2:"Z";s:3:"鈽";s:2:"[";s:3:"鉀";s:2:"\";s:3:"鈾";s:2:"]";s:3:"鉛";s:2:"^";s:3:"鉋";s:2:"_";s:3:"鉤";s:2:"`";s:3:"鉑";s:2:"a";s:3:"鈴";s:2:"b";s:3:"鉉";s:2:"c";s:3:"鉍";s:2:"d";s:3:"鉅";s:2:"e";s:3:"鈹";s:2:"f";s:3:"鈿";s:2:"g";s:3:"鉚";s:2:"h";s:3:"閘";s:2:"i";s:3:"隘";s:2:"j";s:3:"隔";s:2:"k";s:3:"隕";s:2:"l";s:3:"雍";s:2:"m";s:3:"雋";s:2:"n";s:3:"雉";s:2:"o";s:3:"雊";s:2:"p";s:3:"雷";s:2:"q";s:3:"電";s:2:"r";s:3:"雹";s:2:"s";s:3:"零";s:2:"t";s:3:"靖";s:2:"u";s:3:"靴";s:2:"v";s:3:"靶";s:2:"w";s:3:"預";s:2:"x";s:3:"頑";s:2:"y";s:3:"頓";s:2:"z";s:3:"頊";s:2:"{";s:3:"頒";s:2:"|";s:3:"頌";s:2:"}";s:3:"飼";s:2:"~";s:3:"飴";s:2:"";s:3:"飽";s:2:"";s:3:"飾";s:2:"";s:3:"馳";s:2:"";s:3:"馱";s:2:"";s:3:"馴";s:2:"";s:3:"髡";s:2:"";s:3:"鳩";s:2:"";s:3:"麂";s:2:"";s:3:"鼎";s:2:"";s:3:"鼓";s:2:"";s:3:"鼠";s:2:"";s:3:"僧";s:2:"";s:3:"僮";s:2:"";s:3:"僥";s:2:"";s:3:"僖";s:2:"";s:3:"僭";s:2:"";s:3:"僚";s:2:"";s:3:"僕";s:2:"";s:3:"像";s:2:"";s:3:"僑";s:2:"";s:3:"僱";s:2:"";s:3:"僎";s:2:"";s:3:"僩";s:2:"";s:3:"兢";s:2:"";s:3:"凳";s:2:"";s:3:"劃";s:2:"";s:3:"劂";s:2:"";s:3:"匱";s:2:"";s:3:"厭";s:2:"";s:3:"嗾";s:2:"";s:3:"嘀";s:2:"";s:3:"嘛";s:2:"";s:3:"嘗";s:2:"";s:3:"嗽";s:2:"";s:3:"嘔";s:2:"";s:3:"嘆";s:2:"";s:3:"嘉";s:2:"";s:3:"嘍";s:2:"";s:3:"嘎";s:2:"";s:3:"嗷";s:2:"";s:3:"嘖";s:2:"";s:3:"嘟";s:2:"";s:3:"嘈";s:2:"";s:3:"嘐";s:2:"";s:3:"嗶";s:2:"";s:3:"團";s:2:"";s:3:"圖";s:2:"";s:3:"塵";s:2:"";s:3:"塾";s:2:"";s:3:"境";s:2:"";s:3:"墓";s:2:"";s:3:"墊";s:2:"";s:3:"塹";s:2:"";s:3:"墅";s:2:"";s:3:"塽";s:2:"";s:3:"壽";s:2:"";s:3:"夥";s:2:"";s:3:"夢";s:2:"";s:3:"夤";s:2:"";s:3:"奪";s:2:"";s:3:"奩";s:2:"";s:3:"嫡";s:2:"";s:3:"嫦";s:2:"";s:3:"嫩";s:2:"";s:3:"嫗";s:2:"";s:3:"嫖";s:2:"";s:3:"嫘";s:2:"";s:3:"嫣";s:2:"";s:3:"孵";s:2:"";s:3:"寞";s:2:"";s:3:"寧";s:2:"";s:3:"寡";s:2:"";s:3:"寥";s:2:"";s:3:"實";s:2:"";s:3:"寨";s:2:"";s:3:"寢";s:2:"";s:3:"寤";s:2:"";s:3:"察";s:2:"";s:3:"對";s:2:"";s:3:"屢";s:2:"";s:3:"嶄";s:2:"";s:3:"嶇";s:2:"";s:3:"幛";s:2:"";s:3:"幣";s:2:"";s:3:"幕";s:2:"";s:3:"幗";s:2:"";s:3:"幔";s:2:"";s:3:"廓";s:2:"";s:3:"廖";s:2:"";s:3:"弊";s:2:"";s:3:"彆";s:2:"";s:3:"彰";s:2:"";s:3:"徹";s:2:"";s:3:"慇";s:2:"@";s:3:"愿";s:2:"A";s:3:"態";s:2:"B";s:3:"慷";s:2:"C";s:3:"慢";s:2:"D";s:3:"慣";s:2:"E";s:3:"慟";s:2:"F";s:3:"慚";s:2:"G";s:3:"慘";s:2:"H";s:3:"慵";s:2:"I";s:3:"截";s:2:"J";s:3:"撇";s:2:"K";s:3:"摘";s:2:"L";s:3:"摔";s:2:"M";s:3:"撤";s:2:"N";s:3:"摸";s:2:"O";s:3:"摟";s:2:"P";s:3:"摺";s:2:"Q";s:3:"摑";s:2:"R";s:3:"摧";s:2:"S";s:3:"搴";s:2:"T";s:3:"摭";s:2:"U";s:3:"摻";s:2:"V";s:3:"敲";s:2:"W";s:3:"斡";s:2:"X";s:3:"旗";s:2:"Y";s:3:"旖";s:2:"Z";s:3:"暢";s:2:"[";s:3:"暨";s:2:"\";s:3:"暝";s:2:"]";s:3:"榜";s:2:"^";s:3:"榨";s:2:"_";s:3:"榕";s:2:"`";s:3:"槁";s:2:"a";s:3:"榮";s:2:"b";s:3:"槓";s:2:"c";s:3:"構";s:2:"d";s:3:"榛";s:2:"e";s:3:"榷";s:2:"f";s:3:"榻";s:2:"g";s:3:"榫";s:2:"h";s:3:"榴";s:2:"i";s:3:"槐";s:2:"j";s:3:"槍";s:2:"k";s:3:"榭";s:2:"l";s:3:"槌";s:2:"m";s:3:"榦";s:2:"n";s:3:"槃";s:2:"o";s:3:"榣";s:2:"p";s:3:"歉";s:2:"q";s:3:"歌";s:2:"r";s:3:"氳";s:2:"s";s:3:"漳";s:2:"t";s:3:"演";s:2:"u";s:3:"滾";s:2:"v";s:3:"漓";s:2:"w";s:3:"滴";s:2:"x";s:3:"漩";s:2:"y";s:3:"漾";s:2:"z";s:3:"漠";s:2:"{";s:3:"漬";s:2:"|";s:3:"漏";s:2:"}";s:3:"漂";s:2:"~";s:3:"漢";s:2:"";s:3:"滿";s:2:"";s:3:"滯";s:2:"";s:3:"漆";s:2:"";s:3:"漱";s:2:"";s:3:"漸";s:2:"";s:3:"漲";s:2:"";s:3:"漣";s:2:"";s:3:"漕";s:2:"";s:3:"漫";s:2:"";s:3:"漯";s:2:"";s:3:"澈";s:2:"";s:3:"漪";s:2:"";s:3:"滬";s:2:"";s:3:"漁";s:2:"";s:3:"滲";s:2:"";s:3:"滌";s:2:"";s:3:"滷";s:2:"";s:3:"熔";s:2:"";s:3:"熙";s:2:"";s:3:"煽";s:2:"";s:3:"熊";s:2:"";s:3:"熄";s:2:"";s:3:"熒";s:2:"";s:3:"爾";s:2:"";s:3:"犒";s:2:"";s:3:"犖";s:2:"";s:3:"獄";s:2:"";s:3:"獐";s:2:"";s:3:"瑤";s:2:"";s:3:"瑣";s:2:"";s:3:"瑪";s:2:"";s:3:"瑰";s:2:"";s:3:"瑭";s:2:"";s:3:"甄";s:2:"";s:3:"疑";s:2:"";s:3:"瘧";s:2:"";s:3:"瘍";s:2:"";s:3:"瘋";s:2:"";s:3:"瘉";s:2:"";s:3:"瘓";s:2:"";s:3:"盡";s:2:"";s:3:"監";s:2:"";s:3:"瞄";s:2:"";s:3:"睽";s:2:"";s:3:"睿";s:2:"";s:3:"睡";s:2:"";s:3:"磁";s:2:"";s:3:"碟";s:2:"";s:3:"碧";s:2:"";s:3:"碳";s:2:"";s:3:"碩";s:2:"";s:3:"碣";s:2:"";s:3:"禎";s:2:"";s:3:"福";s:2:"";s:3:"禍";s:2:"";s:3:"種";s:2:"";s:3:"稱";s:2:"";s:3:"窪";s:2:"";s:3:"窩";s:2:"";s:3:"竭";s:2:"";s:3:"端";s:2:"";s:3:"管";s:2:"";s:3:"箕";s:2:"";s:3:"箋";s:2:"";s:3:"筵";s:2:"";s:3:"算";s:2:"";s:3:"箝";s:2:"";s:3:"箔";s:2:"";s:3:"箏";s:2:"";s:3:"箸";s:2:"";s:3:"箇";s:2:"";s:3:"箄";s:2:"";s:3:"粹";s:2:"";s:3:"粽";s:2:"";s:3:"精";s:2:"";s:3:"綻";s:2:"";s:3:"綰";s:2:"";s:3:"綜";s:2:"";s:3:"綽";s:2:"";s:3:"綾";s:2:"";s:3:"綠";s:2:"";s:3:"緊";s:2:"";s:3:"綴";s:2:"";s:3:"網";s:2:"";s:3:"綱";s:2:"";s:3:"綺";s:2:"";s:3:"綢";s:2:"";s:3:"綿";s:2:"";s:3:"綵";s:2:"";s:3:"綸";s:2:"";s:3:"維";s:2:"";s:3:"緒";s:2:"";s:3:"緇";s:2:"";s:3:"綬";s:2:"@";s:3:"罰";s:2:"A";s:3:"翠";s:2:"B";s:3:"翡";s:2:"C";s:3:"翟";s:2:"D";s:3:"聞";s:2:"E";s:3:"聚";s:2:"F";s:3:"肇";s:2:"G";s:3:"腐";s:2:"H";s:3:"膀";s:2:"I";s:3:"膏";s:2:"J";s:3:"膈";s:2:"K";s:3:"膊";s:2:"L";s:3:"腿";s:2:"M";s:3:"膂";s:2:"N";s:3:"臧";s:2:"O";s:3:"臺";s:2:"P";s:3:"與";s:2:"Q";s:3:"舔";s:2:"R";s:3:"舞";s:2:"S";s:3:"艋";s:2:"T";s:3:"蓉";s:2:"U";s:3:"蒿";s:2:"V";s:3:"蓆";s:2:"W";s:3:"蓄";s:2:"X";s:3:"蒙";s:2:"Y";s:3:"蒞";s:2:"Z";s:3:"蒲";s:2:"[";s:3:"蒜";s:2:"\";s:3:"蓋";s:2:"]";s:3:"蒸";s:2:"^";s:3:"蓀";s:2:"_";s:3:"蓓";s:2:"`";s:3:"蒐";s:2:"a";s:3:"蒼";s:2:"b";s:3:"蓑";s:2:"c";s:3:"蓊";s:2:"d";s:3:"蜿";s:2:"e";s:3:"蜜";s:2:"f";s:3:"蜻";s:2:"g";s:3:"蜢";s:2:"h";s:3:"蜥";s:2:"i";s:3:"蜴";s:2:"j";s:3:"蜘";s:2:"k";s:3:"蝕";s:2:"l";s:3:"蜷";s:2:"m";s:3:"蜩";s:2:"n";s:3:"裳";s:2:"o";s:3:"褂";s:2:"p";s:3:"裴";s:2:"q";s:3:"裹";s:2:"r";s:3:"裸";s:2:"s";s:3:"製";s:2:"t";s:3:"裨";s:2:"u";s:3:"褚";s:2:"v";s:3:"裯";s:2:"w";s:3:"誦";s:2:"x";s:3:"誌";s:2:"y";s:3:"語";s:2:"z";s:3:"誣";s:2:"{";s:3:"認";s:2:"|";s:3:"誡";s:2:"}";s:3:"誓";s:2:"~";s:3:"誤";s:2:"";s:3:"說";s:2:"";s:3:"誥";s:2:"";s:3:"誨";s:2:"";s:3:"誘";s:2:"";s:3:"誑";s:2:"";s:3:"誚";s:2:"";s:3:"誧";s:2:"";s:3:"豪";s:2:"";s:3:"貍";s:2:"";s:3:"貌";s:2:"";s:3:"賓";s:2:"";s:3:"賑";s:2:"";s:3:"賒";s:2:"";s:3:"赫";s:2:"";s:3:"趙";s:2:"";s:3:"趕";s:2:"";s:3:"跼";s:2:"";s:3:"輔";s:2:"";s:3:"輒";s:2:"";s:3:"輕";s:2:"";s:3:"輓";s:2:"";s:3:"辣";s:2:"";s:3:"遠";s:2:"";s:3:"遘";s:2:"";s:3:"遜";s:2:"";s:3:"遣";s:2:"";s:3:"遙";s:2:"";s:3:"遞";s:2:"";s:3:"遢";s:2:"";s:3:"遝";s:2:"";s:3:"遛";s:2:"";s:3:"鄙";s:2:"";s:3:"鄘";s:2:"";s:3:"鄞";s:2:"";s:3:"酵";s:2:"";s:3:"酸";s:2:"";s:3:"酷";s:2:"";s:3:"酴";s:2:"";s:3:"鉸";s:2:"";s:3:"銀";s:2:"";s:3:"銅";s:2:"";s:3:"銘";s:2:"";s:3:"銖";s:2:"";s:3:"鉻";s:2:"";s:3:"銓";s:2:"";s:3:"銜";s:2:"";s:3:"銨";s:2:"";s:3:"鉼";s:2:"";s:3:"銑";s:2:"";s:3:"閡";s:2:"";s:3:"閨";s:2:"";s:3:"閩";s:2:"";s:3:"閣";s:2:"";s:3:"閥";s:2:"";s:3:"閤";s:2:"";s:3:"隙";s:2:"";s:3:"障";s:2:"";s:3:"際";s:2:"";s:3:"雌";s:2:"";s:3:"雒";s:2:"";s:3:"需";s:2:"";s:3:"靼";s:2:"";s:3:"鞅";s:2:"";s:3:"韶";s:2:"";s:3:"頗";s:2:"";s:3:"領";s:2:"";s:3:"颯";s:2:"";s:3:"颱";s:2:"";s:3:"餃";s:2:"";s:3:"餅";s:2:"";s:3:"餌";s:2:"";s:3:"餉";s:2:"";s:3:"駁";s:2:"";s:3:"骯";s:2:"";s:3:"骰";s:2:"";s:3:"髦";s:2:"";s:3:"魁";s:2:"";s:3:"魂";s:2:"";s:3:"鳴";s:2:"";s:3:"鳶";s:2:"";s:3:"鳳";s:2:"";s:3:"麼";s:2:"";s:3:"鼻";s:2:"";s:3:"齊";s:2:"";s:3:"億";s:2:"";s:3:"儀";s:2:"";s:3:"僻";s:2:"";s:3:"僵";s:2:"";s:3:"價";s:2:"";s:3:"儂";s:2:"";s:3:"儈";s:2:"";s:3:"儉";s:2:"";s:3:"儅";s:2:"";s:3:"凜";s:2:"@";s:3:"劇";s:2:"A";s:3:"劈";s:2:"B";s:3:"劉";s:2:"C";s:3:"劍";s:2:"D";s:3:"劊";s:2:"E";s:3:"勰";s:2:"F";s:3:"厲";s:2:"G";s:3:"嘮";s:2:"H";s:3:"嘻";s:2:"I";s:3:"嘹";s:2:"J";s:3:"嘲";s:2:"K";s:3:"嘿";s:2:"L";s:3:"嘴";s:2:"M";s:3:"嘩";s:2:"N";s:3:"噓";s:2:"O";s:3:"噎";s:2:"P";s:3:"噗";s:2:"Q";s:3:"噴";s:2:"R";s:3:"嘶";s:2:"S";s:3:"嘯";s:2:"T";s:3:"嘰";s:2:"U";s:3:"墀";s:2:"V";s:3:"墟";s:2:"W";s:3:"增";s:2:"X";s:3:"墳";s:2:"Y";s:3:"墜";s:2:"Z";s:3:"墮";s:2:"[";s:3:"墩";s:2:"\";s:3:"墦";s:2:"]";s:3:"奭";s:2:"^";s:3:"嬉";s:2:"_";s:3:"嫻";s:2:"`";s:3:"嬋";s:2:"a";s:3:"嫵";s:2:"b";s:3:"嬌";s:2:"c";s:3:"嬈";s:2:"d";s:3:"寮";s:2:"e";s:3:"寬";s:2:"f";s:3:"審";s:2:"g";s:3:"寫";s:2:"h";s:3:"層";s:2:"i";s:3:"履";s:2:"j";s:3:"嶝";s:2:"k";s:3:"嶔";s:2:"l";s:3:"幢";s:2:"m";s:3:"幟";s:2:"n";s:3:"幡";s:2:"o";s:3:"廢";s:2:"p";s:3:"廚";s:2:"q";s:3:"廟";s:2:"r";s:3:"廝";s:2:"s";s:3:"廣";s:2:"t";s:3:"廠";s:2:"u";s:3:"彈";s:2:"v";s:3:"影";s:2:"w";s:3:"德";s:2:"x";s:3:"徵";s:2:"y";s:3:"慶";s:2:"z";s:3:"慧";s:2:"{";s:3:"慮";s:2:"|";s:3:"慝";s:2:"}";s:3:"慕";s:2:"~";s:3:"憂";s:2:"";s:3:"慼";s:2:"";s:3:"慰";s:2:"";s:3:"慫";s:2:"";s:3:"慾";s:2:"";s:3:"憧";s:2:"";s:3:"憐";s:2:"";s:3:"憫";s:2:"";s:3:"憎";s:2:"";s:3:"憬";s:2:"";s:3:"憚";s:2:"";s:3:"憤";s:2:"";s:3:"憔";s:2:"";s:3:"憮";s:2:"";s:3:"戮";s:2:"";s:3:"摩";s:2:"";s:3:"摯";s:2:"";s:3:"摹";s:2:"";s:3:"撞";s:2:"";s:3:"撲";s:2:"";s:3:"撈";s:2:"";s:3:"撐";s:2:"";s:3:"撰";s:2:"";s:3:"撥";s:2:"";s:3:"撓";s:2:"";s:3:"撕";s:2:"";s:3:"撩";s:2:"";s:3:"撒";s:2:"";s:3:"撮";s:2:"";s:3:"播";s:2:"";s:3:"撫";s:2:"";s:3:"撚";s:2:"";s:3:"撬";s:2:"";s:3:"撙";s:2:"";s:3:"撢";s:2:"";s:3:"撳";s:2:"";s:3:"敵";s:2:"";s:3:"敷";s:2:"";s:3:"數";s:2:"";s:3:"暮";s:2:"";s:3:"暫";s:2:"";s:3:"暴";s:2:"";s:3:"暱";s:2:"";s:3:"樣";s:2:"";s:3:"樟";s:2:"";s:3:"槨";s:2:"";s:3:"樁";s:2:"";s:3:"樞";s:2:"";s:3:"標";s:2:"";s:3:"槽";s:2:"";s:3:"模";s:2:"";s:3:"樓";s:2:"";s:3:"樊";s:2:"";s:3:"槳";s:2:"";s:3:"樂";s:2:"";s:3:"樅";s:2:"";s:3:"槭";s:2:"";s:3:"樑";s:2:"";s:3:"歐";s:2:"";s:3:"歎";s:2:"";s:3:"殤";s:2:"";s:3:"毅";s:2:"";s:3:"毆";s:2:"";s:3:"漿";s:2:"";s:3:"潼";s:2:"";s:3:"澄";s:2:"";s:3:"潑";s:2:"";s:3:"潦";s:2:"";s:3:"潔";s:2:"";s:3:"澆";s:2:"";s:3:"潭";s:2:"";s:3:"潛";s:2:"";s:3:"潸";s:2:"";s:3:"潮";s:2:"";s:3:"澎";s:2:"";s:3:"潺";s:2:"";s:3:"潰";s:2:"";s:3:"潤";s:2:"";s:3:"澗";s:2:"";s:3:"潘";s:2:"";s:3:"滕";s:2:"";s:3:"潯";s:2:"";s:3:"潠";s:2:"";s:3:"潟";s:2:"";s:3:"熟";s:2:"";s:3:"熬";s:2:"";s:3:"熱";s:2:"";s:3:"熨";s:2:"";s:3:"牖";s:2:"";s:3:"犛";s:2:"";s:3:"獎";s:2:"";s:3:"獗";s:2:"";s:3:"瑩";s:2:"";s:3:"璋";s:2:"";s:3:"璃";s:2:"@";s:3:"瑾";s:2:"A";s:3:"璀";s:2:"B";s:3:"畿";s:2:"C";s:3:"瘠";s:2:"D";s:3:"瘩";s:2:"E";s:3:"瘟";s:2:"F";s:3:"瘤";s:2:"G";s:3:"瘦";s:2:"H";s:3:"瘡";s:2:"I";s:3:"瘢";s:2:"J";s:3:"皚";s:2:"K";s:3:"皺";s:2:"L";s:3:"盤";s:2:"M";s:3:"瞎";s:2:"N";s:3:"瞇";s:2:"O";s:3:"瞌";s:2:"P";s:3:"瞑";s:2:"Q";s:3:"瞋";s:2:"R";s:3:"磋";s:2:"S";s:3:"磅";s:2:"T";s:3:"確";s:2:"U";s:3:"磊";s:2:"V";s:3:"碾";s:2:"W";s:3:"磕";s:2:"X";s:3:"碼";s:2:"Y";s:3:"磐";s:2:"Z";s:3:"稿";s:2:"[";s:3:"稼";s:2:"\";s:3:"穀";s:2:"]";s:3:"稽";s:2:"^";s:3:"稷";s:2:"_";s:3:"稻";s:2:"`";s:3:"窯";s:2:"a";s:3:"窮";s:2:"b";s:3:"箭";s:2:"c";s:3:"箱";s:2:"d";s:3:"範";s:2:"e";s:3:"箴";s:2:"f";s:3:"篆";s:2:"g";s:3:"篇";s:2:"h";s:3:"篁";s:2:"i";s:3:"箠";s:2:"j";s:3:"篌";s:2:"k";s:3:"糊";s:2:"l";s:3:"締";s:2:"m";s:3:"練";s:2:"n";s:3:"緯";s:2:"o";s:3:"緻";s:2:"p";s:3:"緘";s:2:"q";s:3:"緬";s:2:"r";s:3:"緝";s:2:"s";s:3:"編";s:2:"t";s:3:"緣";s:2:"u";s:3:"線";s:2:"v";s:3:"緞";s:2:"w";s:3:"緩";s:2:"x";s:3:"綞";s:2:"y";s:3:"緙";s:2:"z";s:3:"緲";s:2:"{";s:3:"緹";s:2:"|";s:3:"罵";s:2:"}";s:3:"罷";s:2:"~";s:3:"羯";s:2:"";s:3:"翩";s:2:"";s:3:"耦";s:2:"";s:3:"膛";s:2:"";s:3:"膜";s:2:"";s:3:"膝";s:2:"";s:3:"膠";s:2:"";s:3:"膚";s:2:"";s:3:"膘";s:2:"";s:3:"蔗";s:2:"";s:3:"蔽";s:2:"";s:3:"蔚";s:2:"";s:3:"蓮";s:2:"";s:3:"蔬";s:2:"";s:3:"蔭";s:2:"";s:3:"蔓";s:2:"";s:3:"蔑";s:2:"";s:3:"蔣";s:2:"";s:3:"蔡";s:2:"";s:3:"蔔";s:2:"";s:3:"蓬";s:2:"";s:3:"蔥";s:2:"";s:3:"蓿";s:2:"";s:3:"蔆";s:2:"";s:3:"螂";s:2:"";s:3:"蝴";s:2:"";s:3:"蝶";s:2:"";s:3:"蝠";s:2:"";s:3:"蝦";s:2:"";s:3:"蝸";s:2:"";s:3:"蝨";s:2:"";s:3:"蝙";s:2:"";s:3:"蝗";s:2:"";s:3:"蝌";s:2:"";s:3:"蝓";s:2:"";s:3:"衛";s:2:"";s:3:"衝";s:2:"";s:3:"褐";s:2:"";s:3:"複";s:2:"";s:3:"褒";s:2:"";s:3:"褓";s:2:"";s:3:"褕";s:2:"";s:3:"褊";s:2:"";s:3:"誼";s:2:"";s:3:"諒";s:2:"";s:3:"談";s:2:"";s:3:"諄";s:2:"";s:3:"誕";s:2:"";s:3:"請";s:2:"";s:3:"諸";s:2:"";s:3:"課";s:2:"";s:3:"諉";s:2:"";s:3:"諂";s:2:"";s:3:"調";s:2:"";s:3:"誰";s:2:"";s:3:"論";s:2:"";s:3:"諍";s:2:"";s:3:"誶";s:2:"";s:3:"誹";s:2:"";s:3:"諛";s:2:"";s:3:"豌";s:2:"";s:3:"豎";s:2:"";s:3:"豬";s:2:"";s:3:"賠";s:2:"";s:3:"賞";s:2:"";s:3:"賦";s:2:"";s:3:"賤";s:2:"";s:3:"賬";s:2:"";s:3:"賭";s:2:"";s:3:"賢";s:2:"";s:3:"賣";s:2:"";s:3:"賜";s:2:"";s:3:"質";s:2:"";s:3:"賡";s:2:"";s:3:"赭";s:2:"";s:3:"趟";s:2:"";s:3:"趣";s:2:"";s:3:"踫";s:2:"";s:3:"踐";s:2:"";s:3:"踝";s:2:"";s:3:"踢";s:2:"";s:3:"踏";s:2:"";s:3:"踩";s:2:"";s:3:"踟";s:2:"";s:3:"踡";s:2:"";s:3:"踞";s:2:"";s:3:"躺";s:2:"";s:3:"輝";s:2:"";s:3:"輛";s:2:"";s:3:"輟";s:2:"";s:3:"輩";s:2:"";s:3:"輦";s:2:"";s:3:"輪";s:2:"";s:3:"輜";s:2:"";s:3:"輞";s:2:"@";s:3:"輥";s:2:"A";s:3:"適";s:2:"B";s:3:"遮";s:2:"C";s:3:"遨";s:2:"D";s:3:"遭";s:2:"E";s:3:"遷";s:2:"F";s:3:"鄰";s:2:"G";s:3:"鄭";s:2:"H";s:3:"鄧";s:2:"I";s:3:"鄱";s:2:"J";s:3:"醇";s:2:"K";s:3:"醉";s:2:"L";s:3:"醋";s:2:"M";s:3:"醃";s:2:"N";s:3:"鋅";s:2:"O";s:3:"銻";s:2:"P";s:3:"銷";s:2:"Q";s:3:"鋪";s:2:"R";s:3:"銬";s:2:"S";s:3:"鋤";s:2:"T";s:3:"鋁";s:2:"U";s:3:"銳";s:2:"V";s:3:"銼";s:2:"W";s:3:"鋒";s:2:"X";s:3:"鋇";s:2:"Y";s:3:"鋰";s:2:"Z";s:3:"銲";s:2:"[";s:3:"閭";s:2:"\";s:3:"閱";s:2:"]";s:3:"霄";s:2:"^";s:3:"霆";s:2:"_";s:3:"震";s:2:"`";s:3:"霉";s:2:"a";s:3:"靠";s:2:"b";s:3:"鞍";s:2:"c";s:3:"鞋";s:2:"d";s:3:"鞏";s:2:"e";s:3:"頡";s:2:"f";s:3:"頫";s:2:"g";s:3:"頜";s:2:"h";s:3:"颳";s:2:"i";s:3:"養";s:2:"j";s:3:"餓";s:2:"k";s:3:"餒";s:2:"l";s:3:"餘";s:2:"m";s:3:"駝";s:2:"n";s:3:"駐";s:2:"o";s:3:"駟";s:2:"p";s:3:"駛";s:2:"q";s:3:"駑";s:2:"r";s:3:"駕";s:2:"s";s:3:"駒";s:2:"t";s:3:"駙";s:2:"u";s:3:"骷";s:2:"v";s:3:"髮";s:2:"w";s:3:"髯";s:2:"x";s:3:"鬧";s:2:"y";s:3:"魅";s:2:"z";s:3:"魄";s:2:"{";s:3:"魷";s:2:"|";s:3:"魯";s:2:"}";s:3:"鴆";s:2:"~";s:3:"鴉";s:2:"";s:3:"鴃";s:2:"";s:3:"麩";s:2:"";s:3:"麾";s:2:"";s:3:"黎";s:2:"";s:3:"墨";s:2:"";s:3:"齒";s:2:"";s:3:"儒";s:2:"";s:3:"儘";s:2:"";s:3:"儔";s:2:"";s:3:"儐";s:2:"";s:3:"儕";s:2:"";s:3:"冀";s:2:"";s:3:"冪";s:2:"";s:3:"凝";s:2:"";s:3:"劑";s:2:"";s:3:"劓";s:2:"";s:3:"勳";s:2:"";s:3:"噙";s:2:"";s:3:"噫";s:2:"";s:3:"噹";s:2:"";s:3:"噩";s:2:"";s:3:"噤";s:2:"";s:3:"噸";s:2:"";s:3:"噪";s:2:"";s:3:"器";s:2:"";s:3:"噥";s:2:"";s:3:"噱";s:2:"";s:3:"噯";s:2:"";s:3:"噬";s:2:"";s:3:"噢";s:2:"";s:3:"噶";s:2:"";s:3:"壁";s:2:"";s:3:"墾";s:2:"";s:3:"壇";s:2:"";s:3:"壅";s:2:"";s:3:"奮";s:2:"";s:3:"嬝";s:2:"";s:3:"嬴";s:2:"";s:3:"學";s:2:"";s:3:"寰";s:2:"";s:3:"導";s:2:"";s:3:"彊";s:2:"";s:3:"憲";s:2:"";s:3:"憑";s:2:"";s:3:"憩";s:2:"";s:3:"憊";s:2:"";s:3:"懍";s:2:"";s:3:"憶";s:2:"";s:3:"憾";s:2:"";s:3:"懊";s:2:"";s:3:"懈";s:2:"";s:3:"戰";s:2:"";s:3:"擅";s:2:"";s:3:"擁";s:2:"";s:3:"擋";s:2:"";s:3:"撻";s:2:"";s:3:"撼";s:2:"";s:3:"據";s:2:"";s:3:"擄";s:2:"";s:3:"擇";s:2:"";s:3:"擂";s:2:"";s:3:"操";s:2:"";s:3:"撿";s:2:"";s:3:"擒";s:2:"";s:3:"擔";s:2:"";s:3:"撾";s:2:"";s:3:"整";s:2:"";s:3:"曆";s:2:"";s:3:"曉";s:2:"";s:3:"暹";s:2:"";s:3:"曄";s:2:"";s:3:"曇";s:2:"";s:3:"暸";s:2:"";s:3:"樽";s:2:"";s:3:"樸";s:2:"";s:3:"樺";s:2:"";s:3:"橙";s:2:"";s:3:"橫";s:2:"";s:3:"橘";s:2:"";s:3:"樹";s:2:"";s:3:"橄";s:2:"";s:3:"橢";s:2:"";s:3:"橡";s:2:"";s:3:"橋";s:2:"";s:3:"橇";s:2:"";s:3:"樵";s:2:"";s:3:"機";s:2:"";s:3:"橈";s:2:"";s:3:"歙";s:2:"";s:3:"歷";s:2:"";s:3:"氅";s:2:"";s:3:"濂";s:2:"";s:3:"澱";s:2:"";s:3:"澡";s:2:"@";s:3:"濃";s:2:"A";s:3:"澤";s:2:"B";s:3:"濁";s:2:"C";s:3:"澧";s:2:"D";s:3:"澳";s:2:"E";s:3:"激";s:2:"F";s:3:"澹";s:2:"G";s:3:"澶";s:2:"H";s:3:"澦";s:2:"I";s:3:"澠";s:2:"J";s:3:"澴";s:2:"K";s:3:"熾";s:2:"L";s:3:"燉";s:2:"M";s:3:"燐";s:2:"N";s:3:"燒";s:2:"O";s:3:"燈";s:2:"P";s:3:"燕";s:2:"Q";s:3:"熹";s:2:"R";s:3:"燎";s:2:"S";s:3:"燙";s:2:"T";s:3:"燜";s:2:"U";s:3:"燃";s:2:"V";s:3:"燄";s:2:"W";s:3:"獨";s:2:"X";s:3:"璜";s:2:"Y";s:3:"璣";s:2:"Z";s:3:"璘";s:2:"[";s:3:"璟";s:2:"\";s:3:"璞";s:2:"]";s:3:"瓢";s:2:"^";s:3:"甌";s:2:"_";s:3:"甍";s:2:"`";s:3:"瘴";s:2:"a";s:3:"瘸";s:2:"b";s:3:"瘺";s:2:"c";s:3:"盧";s:2:"d";s:3:"盥";s:2:"e";s:3:"瞠";s:2:"f";s:3:"瞞";s:2:"g";s:3:"瞟";s:2:"h";s:3:"瞥";s:2:"i";s:3:"磨";s:2:"j";s:3:"磚";s:2:"k";s:3:"磬";s:2:"l";s:3:"磧";s:2:"m";s:3:"禦";s:2:"n";s:3:"積";s:2:"o";s:3:"穎";s:2:"p";s:3:"穆";s:2:"q";s:3:"穌";s:2:"r";s:3:"穋";s:2:"s";s:3:"窺";s:2:"t";s:3:"篙";s:2:"u";s:3:"簑";s:2:"v";s:3:"築";s:2:"w";s:3:"篤";s:2:"x";s:3:"篛";s:2:"y";s:3:"篡";s:2:"z";s:3:"篩";s:2:"{";s:3:"篦";s:2:"|";s:3:"糕";s:2:"}";s:3:"糖";s:2:"~";s:3:"縊";s:2:"";s:3:"縑";s:2:"";s:3:"縈";s:2:"";s:3:"縛";s:2:"";s:3:"縣";s:2:"";s:3:"縞";s:2:"";s:3:"縝";s:2:"";s:3:"縉";s:2:"";s:3:"縐";s:2:"";s:3:"罹";s:2:"";s:3:"羲";s:2:"";s:3:"翰";s:2:"";s:3:"翱";s:2:"";s:3:"翮";s:2:"";s:3:"耨";s:2:"";s:3:"膳";s:2:"";s:3:"膩";s:2:"";s:3:"膨";s:2:"";s:3:"臻";s:2:"";s:3:"興";s:2:"";s:3:"艘";s:2:"";s:3:"艙";s:2:"";s:3:"蕊";s:2:"";s:3:"蕙";s:2:"";s:3:"蕈";s:2:"";s:3:"蕨";s:2:"";s:3:"蕩";s:2:"";s:3:"蕃";s:2:"";s:3:"蕉";s:2:"";s:3:"蕭";s:2:"";s:3:"蕪";s:2:"";s:3:"蕞";s:2:"";s:3:"螃";s:2:"";s:3:"螟";s:2:"";s:3:"螞";s:2:"";s:3:"螢";s:2:"";s:3:"融";s:2:"";s:3:"衡";s:2:"";s:3:"褪";s:2:"";s:3:"褲";s:2:"";s:3:"褥";s:2:"";s:3:"褫";s:2:"";s:3:"褡";s:2:"";s:3:"親";s:2:"";s:3:"覦";s:2:"";s:3:"諦";s:2:"";s:3:"諺";s:2:"";s:3:"諫";s:2:"";s:3:"諱";s:2:"";s:3:"謀";s:2:"";s:3:"諜";s:2:"";s:3:"諧";s:2:"";s:3:"諮";s:2:"";s:3:"諾";s:2:"";s:3:"謁";s:2:"";s:3:"謂";s:2:"";s:3:"諷";s:2:"";s:3:"諭";s:2:"";s:3:"諳";s:2:"";s:3:"諶";s:2:"";s:3:"諼";s:2:"";s:3:"豫";s:2:"";s:3:"豭";s:2:"";s:3:"貓";s:2:"";s:3:"賴";s:2:"";s:3:"蹄";s:2:"";s:3:"踱";s:2:"";s:3:"踴";s:2:"";s:3:"蹂";s:2:"";s:3:"踹";s:2:"";s:3:"踵";s:2:"";s:3:"輻";s:2:"";s:3:"輯";s:2:"";s:3:"輸";s:2:"";s:3:"輳";s:2:"";s:3:"辨";s:2:"";s:3:"辦";s:2:"";s:3:"遵";s:2:"";s:3:"遴";s:2:"";s:3:"選";s:2:"";s:3:"遲";s:2:"";s:3:"遼";s:2:"";s:3:"遺";s:2:"";s:3:"鄴";s:2:"";s:3:"醒";s:2:"";s:3:"錠";s:2:"";s:3:"錶";s:2:"";s:3:"鋸";s:2:"";s:3:"錳";s:2:"";s:3:"錯";s:2:"";s:3:"錢";s:2:"";s:3:"鋼";s:2:"";s:3:"錫";s:2:"";s:3:"錄";s:2:"";s:3:"錚";s:2:"@";s:3:"錐";s:2:"A";s:3:"錦";s:2:"B";s:3:"錡";s:2:"C";s:3:"錕";s:2:"D";s:3:"錮";s:2:"E";s:3:"錙";s:2:"F";s:3:"閻";s:2:"G";s:3:"隧";s:2:"H";s:3:"隨";s:2:"I";s:3:"險";s:2:"J";s:3:"雕";s:2:"K";s:3:"霎";s:2:"L";s:3:"霑";s:2:"M";s:3:"霖";s:2:"N";s:3:"霍";s:2:"O";s:3:"霓";s:2:"P";s:3:"霏";s:2:"Q";s:3:"靛";s:2:"R";s:3:"靜";s:2:"S";s:3:"靦";s:2:"T";s:3:"鞘";s:2:"U";s:3:"頰";s:2:"V";s:3:"頸";s:2:"W";s:3:"頻";s:2:"X";s:3:"頷";s:2:"Y";s:3:"頭";s:2:"Z";s:3:"頹";s:2:"[";s:3:"頤";s:2:"\";s:3:"餐";s:2:"]";s:3:"館";s:2:"^";s:3:"餞";s:2:"_";s:3:"餛";s:2:"`";s:3:"餡";s:2:"a";s:3:"餚";s:2:"b";s:3:"駭";s:2:"c";s:3:"駢";s:2:"d";s:3:"駱";s:2:"e";s:3:"骸";s:2:"f";s:3:"骼";s:2:"g";s:3:"髻";s:2:"h";s:3:"髭";s:2:"i";s:3:"鬨";s:2:"j";s:3:"鮑";s:2:"k";s:3:"鴕";s:2:"l";s:3:"鴣";s:2:"m";s:3:"鴦";s:2:"n";s:3:"鴨";s:2:"o";s:3:"鴒";s:2:"p";s:3:"鴛";s:2:"q";s:3:"默";s:2:"r";s:3:"黔";s:2:"s";s:3:"龍";s:2:"t";s:3:"龜";s:2:"u";s:3:"優";s:2:"v";s:3:"償";s:2:"w";s:3:"儡";s:2:"x";s:3:"儲";s:2:"y";s:3:"勵";s:2:"z";s:3:"嚎";s:2:"{";s:3:"嚀";s:2:"|";s:3:"嚐";s:2:"}";s:3:"嚅";s:2:"~";s:3:"嚇";s:2:"";s:3:"嚏";s:2:"";s:3:"壕";s:2:"";s:3:"壓";s:2:"";s:3:"壑";s:2:"";s:3:"壎";s:2:"";s:3:"嬰";s:2:"";s:3:"嬪";s:2:"";s:3:"嬤";s:2:"";s:3:"孺";s:2:"";s:3:"尷";s:2:"";s:3:"屨";s:2:"";s:3:"嶼";s:2:"";s:3:"嶺";s:2:"";s:3:"嶽";s:2:"";s:3:"嶸";s:2:"";s:3:"幫";s:2:"";s:3:"彌";s:2:"";s:3:"徽";s:2:"";s:3:"應";s:2:"";s:3:"懂";s:2:"";s:3:"懇";s:2:"";s:3:"懦";s:2:"";s:3:"懋";s:2:"";s:3:"戲";s:2:"";s:3:"戴";s:2:"";s:3:"擎";s:2:"";s:3:"擊";s:2:"";s:3:"擘";s:2:"";s:3:"擠";s:2:"";s:3:"擰";s:2:"";s:3:"擦";s:2:"";s:3:"擬";s:2:"";s:3:"擱";s:2:"";s:3:"擢";s:2:"";s:3:"擭";s:2:"";s:3:"斂";s:2:"";s:3:"斃";s:2:"";s:3:"曙";s:2:"";s:3:"曖";s:2:"";s:3:"檀";s:2:"";s:3:"檔";s:2:"";s:3:"檄";s:2:"";s:3:"檢";s:2:"";s:3:"檜";s:2:"";s:3:"櫛";s:2:"";s:3:"檣";s:2:"";s:3:"橾";s:2:"";s:3:"檗";s:2:"";s:3:"檐";s:2:"";s:3:"檠";s:2:"";s:3:"歜";s:2:"";s:3:"殮";s:2:"";s:3:"毚";s:2:"";s:3:"氈";s:2:"";s:3:"濘";s:2:"";s:3:"濱";s:2:"";s:3:"濟";s:2:"";s:3:"濠";s:2:"";s:3:"濛";s:2:"";s:3:"濤";s:2:"";s:3:"濫";s:2:"";s:3:"濯";s:2:"";s:3:"澀";s:2:"";s:3:"濬";s:2:"";s:3:"濡";s:2:"";s:3:"濩";s:2:"";s:3:"濕";s:2:"";s:3:"濮";s:2:"";s:3:"濰";s:2:"";s:3:"燧";s:2:"";s:3:"營";s:2:"";s:3:"燮";s:2:"";s:3:"燦";s:2:"";s:3:"燥";s:2:"";s:3:"燭";s:2:"";s:3:"燬";s:2:"";s:3:"燴";s:2:"";s:3:"燠";s:2:"";s:3:"爵";s:2:"";s:3:"牆";s:2:"";s:3:"獰";s:2:"";s:3:"獲";s:2:"";s:3:"璩";s:2:"";s:3:"環";s:2:"";s:3:"璦";s:2:"";s:3:"璨";s:2:"";s:3:"癆";s:2:"";s:3:"療";s:2:"";s:3:"癌";s:2:"";s:3:"盪";s:2:"";s:3:"瞳";s:2:"";s:3:"瞪";s:2:"";s:3:"瞰";s:2:"";s:3:"瞬";s:2:"@";s:3:"瞧";s:2:"A";s:3:"瞭";s:2:"B";s:3:"矯";s:2:"C";s:3:"磷";s:2:"D";s:3:"磺";s:2:"E";s:3:"磴";s:2:"F";s:3:"磯";s:2:"G";s:3:"礁";s:2:"H";s:3:"禧";s:2:"I";s:3:"禪";s:2:"J";s:3:"穗";s:2:"K";s:3:"窿";s:2:"L";s:3:"簇";s:2:"M";s:3:"簍";s:2:"N";s:3:"篾";s:2:"O";s:3:"篷";s:2:"P";s:3:"簌";s:2:"Q";s:3:"篠";s:2:"R";s:3:"糠";s:2:"S";s:3:"糜";s:2:"T";s:3:"糞";s:2:"U";s:3:"糢";s:2:"V";s:3:"糟";s:2:"W";s:3:"糙";s:2:"X";s:3:"糝";s:2:"Y";s:3:"縮";s:2:"Z";s:3:"績";s:2:"[";s:3:"繆";s:2:"\";s:3:"縷";s:2:"]";s:3:"縲";s:2:"^";s:3:"繃";s:2:"_";s:3:"縫";s:2:"`";s:3:"總";s:2:"a";s:3:"縱";s:2:"b";s:3:"繅";s:2:"c";s:3:"繁";s:2:"d";s:3:"縴";s:2:"e";s:3:"縹";s:2:"f";s:3:"繈";s:2:"g";s:3:"縵";s:2:"h";s:3:"縿";s:2:"i";s:3:"縯";s:2:"j";s:3:"罄";s:2:"k";s:3:"翳";s:2:"l";s:3:"翼";s:2:"m";s:3:"聱";s:2:"n";s:3:"聲";s:2:"o";s:3:"聰";s:2:"p";s:3:"聯";s:2:"q";s:3:"聳";s:2:"r";s:3:"臆";s:2:"s";s:3:"臃";s:2:"t";s:3:"膺";s:2:"u";s:3:"臂";s:2:"v";s:3:"臀";s:2:"w";s:3:"膿";s:2:"x";s:3:"膽";s:2:"y";s:3:"臉";s:2:"z";s:3:"膾";s:2:"{";s:3:"臨";s:2:"|";s:3:"舉";s:2:"}";s:3:"艱";s:2:"~";s:3:"薪";s:2:"";s:3:"薄";s:2:"";s:3:"蕾";s:2:"";s:3:"薜";s:2:"";s:3:"薑";s:2:"";s:3:"薔";s:2:"";s:3:"薯";s:2:"";s:3:"薛";s:2:"";s:3:"薇";s:2:"";s:3:"薨";s:2:"";s:3:"薊";s:2:"";s:3:"虧";s:2:"";s:3:"蟀";s:2:"";s:3:"蟑";s:2:"";s:3:"螳";s:2:"";s:3:"蟒";s:2:"";s:3:"蟆";s:2:"";s:3:"螫";s:2:"";s:3:"螻";s:2:"";s:3:"螺";s:2:"";s:3:"蟈";s:2:"";s:3:"蟋";s:2:"";s:3:"褻";s:2:"";s:3:"褶";s:2:"";s:3:"襄";s:2:"";s:3:"褸";s:2:"";s:3:"褽";s:2:"";s:3:"覬";s:2:"";s:3:"謎";s:2:"";s:3:"謗";s:2:"";s:3:"謙";s:2:"";s:3:"講";s:2:"";s:3:"謊";s:2:"";s:3:"謠";s:2:"";s:3:"謝";s:2:"";s:3:"謄";s:2:"";s:3:"謐";s:2:"";s:3:"豁";s:2:"";s:3:"谿";s:2:"";s:3:"豳";s:2:"";s:3:"賺";s:2:"";s:3:"賽";s:2:"";s:3:"購";s:2:"";s:3:"賸";s:2:"";s:3:"賻";s:2:"";s:3:"趨";s:2:"";s:3:"蹉";s:2:"";s:3:"蹋";s:2:"";s:3:"蹈";s:2:"";s:3:"蹊";s:2:"";s:3:"轄";s:2:"";s:3:"輾";s:2:"";s:3:"轂";s:2:"";s:3:"轅";s:2:"";s:3:"輿";s:2:"";s:3:"避";s:2:"";s:3:"遽";s:2:"";s:3:"還";s:2:"";s:3:"邁";s:2:"";s:3:"邂";s:2:"";s:3:"邀";s:2:"";s:3:"鄹";s:2:"";s:3:"醣";s:2:"";s:3:"醞";s:2:"";s:3:"醜";s:2:"";s:3:"鍍";s:2:"";s:3:"鎂";s:2:"";s:3:"錨";s:2:"";s:3:"鍵";s:2:"";s:3:"鍊";s:2:"";s:3:"鍥";s:2:"";s:3:"鍋";s:2:"";s:3:"錘";s:2:"";s:3:"鍾";s:2:"";s:3:"鍬";s:2:"";s:3:"鍛";s:2:"";s:3:"鍰";s:2:"";s:3:"鍚";s:2:"";s:3:"鍔";s:2:"";s:3:"闊";s:2:"";s:3:"闋";s:2:"";s:3:"闌";s:2:"";s:3:"闈";s:2:"";s:3:"闆";s:2:"";s:3:"隱";s:2:"";s:3:"隸";s:2:"";s:3:"雖";s:2:"";s:3:"霜";s:2:"";s:3:"霞";s:2:"";s:3:"鞠";s:2:"";s:3:"韓";s:2:"";s:3:"顆";s:2:"";s:3:"颶";s:2:"";s:3:"餵";s:2:"";s:3:"騁";s:2:"@";s:3:"駿";s:2:"A";s:3:"鮮";s:2:"B";s:3:"鮫";s:2:"C";s:3:"鮪";s:2:"D";s:3:"鮭";s:2:"E";s:3:"鴻";s:2:"F";s:3:"鴿";s:2:"G";s:3:"麋";s:2:"H";s:3:"黏";s:2:"I";s:3:"點";s:2:"J";s:3:"黜";s:2:"K";s:3:"黝";s:2:"L";s:3:"黛";s:2:"M";s:3:"鼾";s:2:"N";s:3:"齋";s:2:"O";s:3:"叢";s:2:"P";s:3:"嚕";s:2:"Q";s:3:"嚮";s:2:"R";s:3:"壙";s:2:"S";s:3:"壘";s:2:"T";s:3:"嬸";s:2:"U";s:3:"彝";s:2:"V";s:3:"懣";s:2:"W";s:3:"戳";s:2:"X";s:3:"擴";s:2:"Y";s:3:"擲";s:2:"Z";s:3:"擾";s:2:"[";s:3:"攆";s:2:"\";s:3:"擺";s:2:"]";s:3:"擻";s:2:"^";s:3:"擷";s:2:"_";s:3:"斷";s:2:"`";s:3:"曜";s:2:"a";s:3:"朦";s:2:"b";s:3:"檳";s:2:"c";s:3:"檬";s:2:"d";s:3:"櫃";s:2:"e";s:3:"檻";s:2:"f";s:3:"檸";s:2:"g";s:3:"櫂";s:2:"h";s:3:"檮";s:2:"i";s:3:"檯";s:2:"j";s:3:"歟";s:2:"k";s:3:"歸";s:2:"l";s:3:"殯";s:2:"m";s:3:"瀉";s:2:"n";s:3:"瀋";s:2:"o";s:3:"濾";s:2:"p";s:3:"瀆";s:2:"q";s:3:"濺";s:2:"r";s:3:"瀑";s:2:"s";s:3:"瀏";s:2:"t";s:3:"燻";s:2:"u";s:3:"燼";s:2:"v";s:3:"燾";s:2:"w";s:3:"燸";s:2:"x";s:3:"獷";s:2:"y";s:3:"獵";s:2:"z";s:3:"璧";s:2:"{";s:3:"璿";s:2:"|";s:3:"甕";s:2:"}";s:3:"癖";s:2:"~";s:3:"癘";s:2:"¡";s:3:"癒";s:2:"¢";s:3:"瞽";s:2:"£";s:3:"瞿";s:2:"¤";s:3:"瞻";s:2:"¥";s:3:"瞼";s:2:"¦";s:3:"礎";s:2:"§";s:3:"禮";s:2:"¨";s:3:"穡";s:2:"©";s:3:"穢";s:2:"ª";s:3:"穠";s:2:"«";s:3:"竄";s:2:"¬";s:3:"竅";s:2:"­";s:3:"簫";s:2:"®";s:3:"簧";s:2:"¯";s:3:"簪";s:2:"°";s:3:"簞";s:2:"±";s:3:"簣";s:2:"²";s:3:"簡";s:2:"³";s:3:"糧";s:2:"´";s:3:"織";s:2:"µ";s:3:"繕";s:2:"¶";s:3:"繞";s:2:"·";s:3:"繚";s:2:"¸";s:3:"繡";s:2:"¹";s:3:"繒";s:2:"º";s:3:"繙";s:2:"»";s:3:"罈";s:2:"¼";s:3:"翹";s:2:"½";s:3:"翻";s:2:"¾";s:3:"職";s:2:"¿";s:3:"聶";s:2:"";s:3:"臍";s:2:"";s:3:"臏";s:2:"";s:3:"舊";s:2:"";s:3:"藏";s:2:"";s:3:"薩";s:2:"";s:3:"藍";s:2:"";s:3:"藐";s:2:"";s:3:"藉";s:2:"";s:3:"薰";s:2:"";s:3:"薺";s:2:"";s:3:"薹";s:2:"";s:3:"薦";s:2:"";s:3:"蟯";s:2:"";s:3:"蟬";s:2:"";s:3:"蟲";s:2:"";s:3:"蟠";s:2:"";s:3:"覆";s:2:"";s:3:"覲";s:2:"";s:3:"觴";s:2:"";s:3:"謨";s:2:"";s:3:"謹";s:2:"";s:3:"謬";s:2:"";s:3:"謫";s:2:"";s:3:"豐";s:2:"";s:3:"贅";s:2:"";s:3:"蹙";s:2:"";s:3:"蹣";s:2:"";s:3:"蹦";s:2:"";s:3:"蹤";s:2:"";s:3:"蹟";s:2:"";s:3:"蹕";s:2:"";s:3:"軀";s:2:"";s:3:"轉";s:2:"";s:3:"轍";s:2:"";s:3:"邇";s:2:"";s:3:"邃";s:2:"";s:3:"邈";s:2:"";s:3:"醫";s:2:"";s:3:"醬";s:2:"";s:3:"釐";s:2:"";s:3:"鎔";s:2:"";s:3:"鎊";s:2:"";s:3:"鎖";s:2:"";s:3:"鎢";s:2:"";s:3:"鎳";s:2:"";s:3:"鎮";s:2:"";s:3:"鎬";s:2:"";s:3:"鎰";s:2:"";s:3:"鎘";s:2:"";s:3:"鎚";s:2:"";s:3:"鎗";s:2:"";s:3:"闔";s:2:"";s:3:"闖";s:2:"";s:3:"闐";s:2:"";s:3:"闕";s:2:"";s:3:"離";s:2:"";s:3:"雜";s:2:"";s:3:"雙";s:2:"";s:3:"雛";s:2:"";s:3:"雞";s:2:"";s:3:"霤";s:2:"";s:3:"鞣";s:2:"";s:3:"鞦";s:2:"@";s:3:"鞭";s:2:"A";s:3:"韹";s:2:"B";s:3:"額";s:2:"C";s:3:"顏";s:2:"D";s:3:"題";s:2:"E";s:3:"顎";s:2:"F";s:3:"顓";s:2:"G";s:3:"颺";s:2:"H";s:3:"餾";s:2:"I";s:3:"餿";s:2:"J";s:3:"餽";s:2:"K";s:3:"餮";s:2:"L";s:3:"馥";s:2:"M";s:3:"騎";s:2:"N";s:3:"髁";s:2:"O";s:3:"鬃";s:2:"P";s:3:"鬆";s:2:"Q";s:3:"魏";s:2:"R";s:3:"魎";s:2:"S";s:3:"魍";s:2:"T";s:3:"鯊";s:2:"U";s:3:"鯉";s:2:"V";s:3:"鯽";s:2:"W";s:3:"鯈";s:2:"X";s:3:"鯀";s:2:"Y";s:3:"鵑";s:2:"Z";s:3:"鵝";s:2:"[";s:3:"鵠";s:2:"\";s:3:"黠";s:2:"]";s:3:"鼕";s:2:"^";s:3:"鼬";s:2:"_";s:3:"儳";s:2:"`";s:3:"嚥";s:2:"a";s:3:"壞";s:2:"b";s:3:"壟";s:2:"c";s:3:"壢";s:2:"d";s:3:"寵";s:2:"e";s:3:"龐";s:2:"f";s:3:"廬";s:2:"g";s:3:"懲";s:2:"h";s:3:"懷";s:2:"i";s:3:"懶";s:2:"j";s:3:"懵";s:2:"k";s:3:"攀";s:2:"l";s:3:"攏";s:2:"m";s:3:"曠";s:2:"n";s:3:"曝";s:2:"o";s:3:"櫥";s:2:"p";s:3:"櫝";s:2:"q";s:3:"櫚";s:2:"r";s:3:"櫓";s:2:"s";s:3:"瀛";s:2:"t";s:3:"瀟";s:2:"u";s:3:"瀨";s:2:"v";s:3:"瀚";s:2:"w";s:3:"瀝";s:2:"x";s:3:"瀕";s:2:"y";s:3:"瀘";s:2:"z";s:3:"爆";s:2:"{";s:3:"爍";s:2:"|";s:3:"牘";s:2:"}";s:3:"犢";s:2:"~";s:3:"獸";s:2:"á";s:3:"獺";s:2:"â";s:3:"璽";s:2:"ã";s:3:"瓊";s:2:"ä";s:3:"瓣";s:2:"å";s:3:"疇";s:2:"æ";s:3:"疆";s:2:"ç";s:3:"癟";s:2:"è";s:3:"癡";s:2:"é";s:3:"矇";s:2:"ê";s:3:"礙";s:2:"ë";s:3:"禱";s:2:"ì";s:3:"穫";s:2:"í";s:3:"穩";s:2:"î";s:3:"簾";s:2:"ï";s:3:"簿";s:2:"ð";s:3:"簸";s:2:"ñ";s:3:"簽";s:2:"ò";s:3:"簷";s:2:"ó";s:3:"籀";s:2:"ô";s:3:"繫";s:2:"õ";s:3:"繭";s:2:"ö";s:3:"繹";s:2:"÷";s:3:"繩";s:2:"ø";s:3:"繪";s:2:"ù";s:3:"羅";s:2:"ú";s:3:"繳";s:2:"û";s:3:"羶";s:2:"ü";s:3:"羹";s:2:"ý";s:3:"羸";s:2:"þ";s:3:"臘";s:2:"ÿ";s:3:"藩";s:2:"";s:3:"藝";s:2:"";s:3:"藪";s:2:"";s:3:"藕";s:2:"";s:3:"藤";s:2:"";s:3:"藥";s:2:"";s:3:"藷";s:2:"";s:3:"蟻";s:2:"";s:3:"蠅";s:2:"";s:3:"蠍";s:2:"";s:3:"蟹";s:2:"";s:3:"蟾";s:2:"";s:3:"襠";s:2:"";s:3:"襟";s:2:"";s:3:"襖";s:2:"";s:3:"襞";s:2:"";s:3:"譁";s:2:"";s:3:"譜";s:2:"";s:3:"識";s:2:"";s:3:"證";s:2:"";s:3:"譚";s:2:"";s:3:"譎";s:2:"";s:3:"譏";s:2:"";s:3:"譆";s:2:"";s:3:"譙";s:2:"";s:3:"贈";s:2:"";s:3:"贊";s:2:"";s:3:"蹼";s:2:"";s:3:"蹲";s:2:"";s:3:"躇";s:2:"";s:3:"蹶";s:2:"";s:3:"蹬";s:2:"";s:3:"蹺";s:2:"";s:3:"蹴";s:2:"";s:3:"轔";s:2:"";s:3:"轎";s:2:"";s:3:"辭";s:2:"";s:3:"邊";s:2:"";s:3:"邋";s:2:"";s:3:"醱";s:2:"";s:3:"醮";s:2:"";s:3:"鏡";s:2:"";s:3:"鏑";s:2:"";s:3:"鏟";s:2:"";s:3:"鏃";s:2:"";s:3:"鏈";s:2:"";s:3:"鏜";s:2:"";s:3:"鏝";s:2:"";s:3:"鏖";s:2:"";s:3:"鏢";s:2:"";s:3:"鏍";s:2:"";s:3:"鏘";s:2:"";s:3:"鏤";s:2:"";s:3:"鏗";s:2:"";s:3:"鏨";s:2:"";s:3:"關";s:2:"";s:3:"隴";s:2:"";s:3:"難";s:2:"";s:3:"霪";s:2:"";s:3:"霧";s:2:"";s:3:"靡";s:2:"";s:3:"韜";s:2:"";s:3:"韻";s:2:"";s:3:"類";s:2:"@";s:3:"願";s:2:"A";s:3:"顛";s:2:"B";s:3:"颼";s:2:"C";s:3:"饅";s:2:"D";s:3:"饉";s:2:"E";s:3:"騖";s:2:"F";s:3:"騙";s:2:"G";s:3:"鬍";s:2:"H";s:3:"鯨";s:2:"I";s:3:"鯧";s:2:"J";s:3:"鯖";s:2:"K";s:3:"鯛";s:2:"L";s:3:"鶉";s:2:"M";s:3:"鵡";s:2:"N";s:3:"鵲";s:2:"O";s:3:"鵪";s:2:"P";s:3:"鵬";s:2:"Q";s:3:"麒";s:2:"R";s:3:"麗";s:2:"S";s:3:"麓";s:2:"T";s:3:"麴";s:2:"U";s:3:"勸";s:2:"V";s:3:"嚨";s:2:"W";s:3:"嚷";s:2:"X";s:3:"嚶";s:2:"Y";s:3:"嚴";s:2:"Z";s:3:"嚼";s:2:"[";s:3:"壤";s:2:"\";s:3:"孀";s:2:"]";s:3:"孃";s:2:"^";s:3:"孽";s:2:"_";s:3:"寶";s:2:"`";s:3:"巉";s:2:"a";s:3:"懸";s:2:"b";s:3:"懺";s:2:"c";s:3:"攘";s:2:"d";s:3:"攔";s:2:"e";s:3:"攙";s:2:"f";s:3:"曦";s:2:"g";s:3:"朧";s:2:"h";s:3:"櫬";s:2:"i";s:3:"瀾";s:2:"j";s:3:"瀰";s:2:"k";s:3:"瀲";s:2:"l";s:3:"爐";s:2:"m";s:3:"獻";s:2:"n";s:3:"瓏";s:2:"o";s:3:"癢";s:2:"p";s:3:"癥";s:2:"q";s:3:"礦";s:2:"r";s:3:"礪";s:2:"s";s:3:"礬";s:2:"t";s:3:"礫";s:2:"u";s:3:"竇";s:2:"v";s:3:"競";s:2:"w";s:3:"籌";s:2:"x";s:3:"籃";s:2:"y";s:3:"籍";s:2:"z";s:3:"糯";s:2:"{";s:3:"糰";s:2:"|";s:3:"辮";s:2:"}";s:3:"繽";s:2:"~";s:3:"繼";s:2:"ġ";s:3:"纂";s:2:"Ģ";s:3:"罌";s:2:"ģ";s:3:"耀";s:2:"Ĥ";s:3:"臚";s:2:"ĥ";s:3:"艦";s:2:"Ħ";s:3:"藻";s:2:"ħ";s:3:"藹";s:2:"Ĩ";s:3:"蘑";s:2:"ĩ";s:3:"藺";s:2:"Ī";s:3:"蘆";s:2:"ī";s:3:"蘋";s:2:"Ĭ";s:3:"蘇";s:2:"ĭ";s:3:"蘊";s:2:"Į";s:3:"蠔";s:2:"į";s:3:"蠕";s:2:"İ";s:3:"襤";s:2:"ı";s:3:"覺";s:2:"IJ";s:3:"觸";s:2:"ij";s:3:"議";s:2:"Ĵ";s:3:"譬";s:2:"ĵ";s:3:"警";s:2:"Ķ";s:3:"譯";s:2:"ķ";s:3:"譟";s:2:"ĸ";s:3:"譫";s:2:"Ĺ";s:3:"贏";s:2:"ĺ";s:3:"贍";s:2:"Ļ";s:3:"躉";s:2:"ļ";s:3:"躁";s:2:"Ľ";s:3:"躅";s:2:"ľ";s:3:"躂";s:2:"Ŀ";s:3:"醴";s:2:"";s:3:"釋";s:2:"";s:3:"鐘";s:2:"";s:3:"鐃";s:2:"";s:3:"鏽";s:2:"";s:3:"闡";s:2:"";s:3:"霰";s:2:"";s:3:"飄";s:2:"";s:3:"饒";s:2:"";s:3:"饑";s:2:"";s:3:"馨";s:2:"";s:3:"騫";s:2:"";s:3:"騰";s:2:"";s:3:"騷";s:2:"";s:3:"騵";s:2:"";s:3:"鰓";s:2:"";s:3:"鰍";s:2:"";s:3:"鹹";s:2:"";s:3:"麵";s:2:"";s:3:"黨";s:2:"";s:3:"鼯";s:2:"";s:3:"齟";s:2:"";s:3:"齣";s:2:"";s:3:"齡";s:2:"";s:3:"儷";s:2:"";s:3:"儸";s:2:"";s:3:"囁";s:2:"";s:3:"囀";s:2:"";s:3:"囂";s:2:"";s:3:"夔";s:2:"";s:3:"屬";s:2:"";s:3:"巍";s:2:"";s:3:"懼";s:2:"";s:3:"懾";s:2:"";s:3:"攝";s:2:"";s:3:"攜";s:2:"";s:3:"斕";s:2:"";s:3:"曩";s:2:"";s:3:"櫻";s:2:"";s:3:"欄";s:2:"";s:3:"櫺";s:2:"";s:3:"殲";s:2:"";s:3:"灌";s:2:"";s:3:"爛";s:2:"";s:3:"犧";s:2:"";s:3:"瓖";s:2:"";s:3:"瓔";s:2:"";s:3:"癩";s:2:"";s:3:"矓";s:2:"";s:3:"籐";s:2:"";s:3:"纏";s:2:"";s:3:"續";s:2:"";s:3:"羼";s:2:"";s:3:"蘗";s:2:"";s:3:"蘭";s:2:"";s:3:"蘚";s:2:"";s:3:"蠣";s:2:"";s:3:"蠢";s:2:"";s:3:"蠡";s:2:"";s:3:"蠟";s:2:"";s:3:"襪";s:2:"";s:3:"襬";s:2:"";s:3:"覽";s:2:"";s:3:"譴";s:2:"@";s:3:"護";s:2:"A";s:3:"譽";s:2:"B";s:3:"贓";s:2:"C";s:3:"躊";s:2:"D";s:3:"躍";s:2:"E";s:3:"躋";s:2:"F";s:3:"轟";s:2:"G";s:3:"辯";s:2:"H";s:3:"醺";s:2:"I";s:3:"鐮";s:2:"J";s:3:"鐳";s:2:"K";s:3:"鐵";s:2:"L";s:3:"鐺";s:2:"M";s:3:"鐸";s:2:"N";s:3:"鐲";s:2:"O";s:3:"鐫";s:2:"P";s:3:"闢";s:2:"Q";s:3:"霸";s:2:"R";s:3:"霹";s:2:"S";s:3:"露";s:2:"T";s:3:"響";s:2:"U";s:3:"顧";s:2:"V";s:3:"顥";s:2:"W";s:3:"饗";s:2:"X";s:3:"驅";s:2:"Y";s:3:"驃";s:2:"Z";s:3:"驀";s:2:"[";s:3:"騾";s:2:"\";s:3:"髏";s:2:"]";s:3:"魔";s:2:"^";s:3:"魑";s:2:"_";s:3:"鰭";s:2:"`";s:3:"鰥";s:2:"a";s:3:"鶯";s:2:"b";s:3:"鶴";s:2:"c";s:3:"鷂";s:2:"d";s:3:"鶸";s:2:"e";s:3:"麝";s:2:"f";s:3:"黯";s:2:"g";s:3:"鼙";s:2:"h";s:3:"齜";s:2:"i";s:3:"齦";s:2:"j";s:3:"齧";s:2:"k";s:3:"儼";s:2:"l";s:3:"儻";s:2:"m";s:3:"囈";s:2:"n";s:3:"囊";s:2:"o";s:3:"囉";s:2:"p";s:3:"孿";s:2:"q";s:3:"巔";s:2:"r";s:3:"巒";s:2:"s";s:3:"彎";s:2:"t";s:3:"懿";s:2:"u";s:3:"攤";s:2:"v";s:3:"權";s:2:"w";s:3:"歡";s:2:"x";s:3:"灑";s:2:"y";s:3:"灘";s:2:"z";s:3:"玀";s:2:"{";s:3:"瓤";s:2:"|";s:3:"疊";s:2:"}";s:3:"癮";s:2:"~";s:3:"癬";s:2:"š";s:3:"禳";s:2:"Ţ";s:3:"籠";s:2:"ţ";s:3:"籟";s:2:"Ť";s:3:"聾";s:2:"ť";s:3:"聽";s:2:"Ŧ";s:3:"臟";s:2:"ŧ";s:3:"襲";s:2:"Ũ";s:3:"襯";s:2:"ũ";s:3:"觼";s:2:"Ū";s:3:"讀";s:2:"ū";s:3:"贖";s:2:"Ŭ";s:3:"贗";s:2:"ŭ";s:3:"躑";s:2:"Ů";s:3:"躓";s:2:"ů";s:3:"轡";s:2:"Ű";s:3:"酈";s:2:"ű";s:3:"鑄";s:2:"Ų";s:3:"鑑";s:2:"ų";s:3:"鑒";s:2:"Ŵ";s:3:"霽";s:2:"ŵ";s:3:"霾";s:2:"Ŷ";s:3:"韃";s:2:"ŷ";s:3:"韁";s:2:"Ÿ";s:3:"顫";s:2:"Ź";s:3:"饕";s:2:"ź";s:3:"驕";s:2:"Ż";s:3:"驍";s:2:"ż";s:3:"髒";s:2:"Ž";s:3:"鬚";s:2:"ž";s:3:"鱉";s:2:"ſ";s:3:"鰱";s:2:"";s:3:"鰾";s:2:"";s:3:"鰻";s:2:"";s:3:"鷓";s:2:"";s:3:"鷗";s:2:"";s:3:"鼴";s:2:"";s:3:"齬";s:2:"";s:3:"齪";s:2:"";s:3:"龔";s:2:"";s:3:"囌";s:2:"";s:3:"巖";s:2:"";s:3:"戀";s:2:"";s:3:"攣";s:2:"";s:3:"攫";s:2:"";s:3:"攪";s:2:"";s:3:"曬";s:2:"";s:3:"欐";s:2:"";s:3:"瓚";s:2:"";s:3:"竊";s:2:"";s:3:"籤";s:2:"";s:3:"籣";s:2:"";s:3:"籥";s:2:"";s:3:"纓";s:2:"";s:3:"纖";s:2:"";s:3:"纔";s:2:"";s:3:"臢";s:2:"";s:3:"蘸";s:2:"";s:3:"蘿";s:2:"";s:3:"蠱";s:2:"";s:3:"變";s:2:"";s:3:"邐";s:2:"";s:3:"邏";s:2:"";s:3:"鑣";s:2:"";s:3:"鑠";s:2:"";s:3:"鑤";s:2:"";s:3:"靨";s:2:"";s:3:"顯";s:2:"";s:3:"饜";s:2:"";s:3:"驚";s:2:"";s:3:"驛";s:2:"";s:3:"驗";s:2:"";s:3:"髓";s:2:"";s:3:"體";s:2:"";s:3:"髑";s:2:"";s:3:"鱔";s:2:"";s:3:"鱗";s:2:"";s:3:"鱖";s:2:"";s:3:"鷥";s:2:"";s:3:"麟";s:2:"";s:3:"黴";s:2:"";s:3:"囑";s:2:"";s:3:"壩";s:2:"";s:3:"攬";s:2:"";s:3:"灞";s:2:"";s:3:"癱";s:2:"";s:3:"癲";s:2:"";s:3:"矗";s:2:"";s:3:"罐";s:2:"";s:3:"羈";s:2:"";s:3:"蠶";s:2:"";s:3:"蠹";s:2:"";s:3:"衢";s:2:"";s:3:"讓";s:2:"";s:3:"讒";s:2:"@";s:3:"讖";s:2:"A";s:3:"艷";s:2:"B";s:3:"贛";s:2:"C";s:3:"釀";s:2:"D";s:3:"鑪";s:2:"E";s:3:"靂";s:2:"F";s:3:"靈";s:2:"G";s:3:"靄";s:2:"H";s:3:"韆";s:2:"I";s:3:"顰";s:2:"J";s:3:"驟";s:2:"K";s:3:"鬢";s:2:"L";s:3:"魘";s:2:"M";s:3:"鱟";s:2:"N";s:3:"鷹";s:2:"O";s:3:"鷺";s:2:"P";s:3:"鹼";s:2:"Q";s:3:"鹽";s:2:"R";s:3:"鼇";s:2:"S";s:3:"齷";s:2:"T";s:3:"齲";s:2:"U";s:3:"廳";s:2:"V";s:3:"欖";s:2:"W";s:3:"灣";s:2:"X";s:3:"籬";s:2:"Y";s:3:"籮";s:2:"Z";s:3:"蠻";s:2:"[";s:3:"觀";s:2:"\";s:3:"躡";s:2:"]";s:3:"釁";s:2:"^";s:3:"鑲";s:2:"_";s:3:"鑰";s:2:"`";s:3:"顱";s:2:"a";s:3:"饞";s:2:"b";s:3:"髖";s:2:"c";s:3:"鬣";s:2:"d";s:3:"黌";s:2:"e";s:3:"灤";s:2:"f";s:3:"矚";s:2:"g";s:3:"讚";s:2:"h";s:3:"鑷";s:2:"i";s:3:"韉";s:2:"j";s:3:"驢";s:2:"k";s:3:"驥";s:2:"l";s:3:"纜";s:2:"m";s:3:"讜";s:2:"n";s:3:"躪";s:2:"o";s:3:"釅";s:2:"p";s:3:"鑽";s:2:"q";s:3:"鑾";s:2:"r";s:3:"鑼";s:2:"s";s:3:"鱷";s:2:"t";s:3:"鱸";s:2:"u";s:3:"黷";s:2:"v";s:3:"豔";s:2:"w";s:3:"鑿";s:2:"x";s:3:"鸚";s:2:"y";s:3:"爨";s:2:"z";s:3:"驪";s:2:"{";s:3:"鬱";s:2:"|";s:3:"鸛";s:2:"}";s:3:"鸞";s:2:"~";s:3:"籲";s:2:"ơ";s:3:"ヾ";s:2:"Ƣ";s:3:"ゝ";s:2:"ƣ";s:3:"ゞ";s:2:"Ƥ";s:3:"々";s:2:"ƥ";s:3:"ぁ";s:2:"Ʀ";s:3:"あ";s:2:"Ƨ";s:3:"ぃ";s:2:"ƨ";s:3:"い";s:2:"Ʃ";s:3:"ぅ";s:2:"ƪ";s:3:"う";s:2:"ƫ";s:3:"ぇ";s:2:"Ƭ";s:3:"え";s:2:"ƭ";s:3:"ぉ";s:2:"Ʈ";s:3:"お";s:2:"Ư";s:3:"か";s:2:"ư";s:3:"が";s:2:"Ʊ";s:3:"き";s:2:"Ʋ";s:3:"ぎ";s:2:"Ƴ";s:3:"く";s:2:"ƴ";s:3:"ぐ";s:2:"Ƶ";s:3:"け";s:2:"ƶ";s:3:"げ";s:2:"Ʒ";s:3:"こ";s:2:"Ƹ";s:3:"ご";s:2:"ƹ";s:3:"さ";s:2:"ƺ";s:3:"ざ";s:2:"ƻ";s:3:"し";s:2:"Ƽ";s:3:"じ";s:2:"ƽ";s:3:"す";s:2:"ƾ";s:3:"ず";s:2:"ƿ";s:3:"せ";s:2:"";s:3:"ぜ";s:2:"";s:3:"そ";s:2:"";s:3:"ぞ";s:2:"";s:3:"た";s:2:"";s:3:"だ";s:2:"";s:3:"ち";s:2:"";s:3:"ぢ";s:2:"";s:3:"っ";s:2:"";s:3:"つ";s:2:"";s:3:"づ";s:2:"";s:3:"て";s:2:"";s:3:"で";s:2:"";s:3:"と";s:2:"";s:3:"ど";s:2:"";s:3:"な";s:2:"";s:3:"に";s:2:"";s:3:"ぬ";s:2:"";s:3:"ね";s:2:"";s:3:"の";s:2:"";s:3:"は";s:2:"";s:3:"ば";s:2:"";s:3:"ぱ";s:2:"";s:3:"ひ";s:2:"";s:3:"び";s:2:"";s:3:"ぴ";s:2:"";s:3:"ふ";s:2:"";s:3:"ぶ";s:2:"";s:3:"ぷ";s:2:"";s:3:"へ";s:2:"";s:3:"べ";s:2:"";s:3:"ぺ";s:2:"";s:3:"ほ";s:2:"";s:3:"ぼ";s:2:"";s:3:"ぽ";s:2:"";s:3:"ま";s:2:"";s:3:"み";s:2:"";s:3:"む";s:2:"";s:3:"め";s:2:"";s:3:"も";s:2:"";s:3:"ゃ";s:2:"";s:3:"や";s:2:"";s:3:"ゅ";s:2:"";s:3:"ゆ";s:2:"";s:3:"ょ";s:2:"";s:3:"よ";s:2:"";s:3:"ら";s:2:"";s:3:"り";s:2:"";s:3:"る";s:2:"";s:3:"れ";s:2:"";s:3:"ろ";s:2:"";s:3:"ゎ";s:2:"";s:3:"わ";s:2:"";s:3:"ゐ";s:2:"";s:3:"ゑ";s:2:"";s:3:"を";s:2:"";s:3:"ん";s:2:"";s:3:"ァ";s:2:"";s:3:"ア";s:2:"";s:3:"ィ";s:2:"";s:3:"イ";s:2:"";s:3:"ゥ";s:2:"";s:3:"ウ";s:2:"";s:3:"ェ";s:2:"@";s:3:"エ";s:2:"A";s:3:"ォ";s:2:"B";s:3:"オ";s:2:"C";s:3:"カ";s:2:"D";s:3:"ガ";s:2:"E";s:3:"キ";s:2:"F";s:3:"ギ";s:2:"G";s:3:"ク";s:2:"H";s:3:"グ";s:2:"I";s:3:"ケ";s:2:"J";s:3:"ゲ";s:2:"K";s:3:"コ";s:2:"L";s:3:"ゴ";s:2:"M";s:3:"サ";s:2:"N";s:3:"ザ";s:2:"O";s:3:"シ";s:2:"P";s:3:"ジ";s:2:"Q";s:3:"ス";s:2:"R";s:3:"ズ";s:2:"S";s:3:"セ";s:2:"T";s:3:"ゼ";s:2:"U";s:3:"ソ";s:2:"V";s:3:"ゾ";s:2:"W";s:3:"タ";s:2:"X";s:3:"ダ";s:2:"Y";s:3:"チ";s:2:"Z";s:3:"ヂ";s:2:"[";s:3:"ッ";s:2:"\";s:3:"ツ";s:2:"]";s:3:"ヅ";s:2:"^";s:3:"テ";s:2:"_";s:3:"デ";s:2:"`";s:3:"ト";s:2:"a";s:3:"ド";s:2:"b";s:3:"ナ";s:2:"c";s:3:"ニ";s:2:"d";s:3:"ヌ";s:2:"e";s:3:"ネ";s:2:"f";s:3:"ノ";s:2:"g";s:3:"ハ";s:2:"h";s:3:"バ";s:2:"i";s:3:"パ";s:2:"j";s:3:"ヒ";s:2:"k";s:3:"ビ";s:2:"l";s:3:"ピ";s:2:"m";s:3:"フ";s:2:"n";s:3:"ブ";s:2:"o";s:3:"プ";s:2:"p";s:3:"ヘ";s:2:"q";s:3:"ベ";s:2:"r";s:3:"ペ";s:2:"s";s:3:"ホ";s:2:"t";s:3:"ボ";s:2:"u";s:3:"ポ";s:2:"v";s:3:"マ";s:2:"w";s:3:"ミ";s:2:"x";s:3:"ム";s:2:"y";s:3:"メ";s:2:"z";s:3:"モ";s:2:"{";s:3:"ャ";s:2:"|";s:3:"ヤ";s:2:"}";s:3:"ュ";s:2:"~";s:3:"ユ";s:2:"ǡ";s:3:"ョ";s:2:"Ǣ";s:3:"ヨ";s:2:"ǣ";s:3:"ラ";s:2:"Ǥ";s:3:"リ";s:2:"ǥ";s:3:"ル";s:2:"Ǧ";s:3:"レ";s:2:"ǧ";s:3:"ロ";s:2:"Ǩ";s:3:"ヮ";s:2:"ǩ";s:3:"ワ";s:2:"Ǫ";s:3:"ヰ";s:2:"ǫ";s:3:"ヱ";s:2:"Ǭ";s:3:"ヲ";s:2:"ǭ";s:3:"ン";s:2:"Ǯ";s:3:"ヴ";s:2:"ǯ";s:3:"ヵ";s:2:"ǰ";s:3:"ヶ";s:2:"DZ";s:2:"Д";s:2:"Dz";s:2:"Е";s:2:"dz";s:2:"Ё";s:2:"Ǵ";s:2:"Ж";s:2:"ǵ";s:2:"З";s:2:"Ƕ";s:2:"И";s:2:"Ƿ";s:2:"Й";s:2:"Ǹ";s:2:"К";s:2:"ǹ";s:2:"Л";s:2:"Ǻ";s:2:"М";s:2:"ǻ";s:2:"У";s:2:"Ǽ";s:2:"Ф";s:2:"ǽ";s:2:"Х";s:2:"Ǿ";s:2:"Ц";s:2:"ǿ";s:2:"Ч";s:2:"";s:2:"Ш";s:2:"";s:2:"Щ";s:2:"";s:2:"Ъ";s:2:"";s:2:"Ы";s:2:"";s:2:"Ь";s:2:"";s:2:"Э";s:2:"";s:2:"Ю";s:2:"";s:2:"Я";s:2:"";s:2:"а";s:2:"";s:2:"б";s:2:"";s:2:"в";s:2:"";s:2:"г";s:2:"";s:2:"д";s:2:"";s:2:"е";s:2:"";s:2:"ё";s:2:"";s:2:"ж";s:2:"";s:2:"з";s:2:"";s:2:"и";s:2:"";s:2:"й";s:2:"";s:2:"к";s:2:"";s:2:"л";s:2:"";s:2:"м";s:2:"";s:2:"н";s:2:"";s:2:"о";s:2:"";s:2:"п";s:2:"";s:2:"р";s:2:"";s:2:"с";s:2:"";s:2:"т";s:2:"";s:2:"у";s:2:"";s:2:"ф";s:2:"";s:2:"х";s:2:"";s:2:"ц";s:2:"";s:2:"ч";s:2:"";s:2:"ш";s:2:"";s:2:"щ";s:2:"";s:2:"ъ";s:2:"";s:2:"ы";s:2:"";s:2:"ь";s:2:"";s:2:"э";s:2:"";s:2:"ю";s:2:"";s:2:"я";s:2:"";s:3:"①";s:2:"";s:3:"②";s:2:"";s:3:"③";s:2:"";s:3:"④";s:2:"";s:3:"⑤";s:2:"";s:3:"⑥";s:2:"";s:3:"⑦";s:2:"";s:3:"⑧";s:2:"";s:3:"⑨";s:2:"";s:3:"⑩";s:2:"";s:3:"⑴";s:2:"";s:3:"⑵";s:2:"";s:3:"⑶";s:2:"";s:3:"⑷";s:2:"";s:3:"⑸";s:2:"";s:3:"⑹";s:2:"";s:3:"⑺";s:2:"";s:3:"⑻";s:2:"";s:3:"⑼";s:2:"";s:3:"⑽";s:2:"@";s:3:"乂";s:2:"A";s:3:"乜";s:2:"B";s:3:"凵";s:2:"C";s:3:"匚";s:2:"D";s:3:"厂";s:2:"E";s:3:"万";s:2:"F";s:3:"丌";s:2:"G";s:3:"乇";s:2:"H";s:3:"亍";s:2:"I";s:3:"囗";s:2:"J";s:3:"兀";s:2:"K";s:3:"屮";s:2:"L";s:3:"彳";s:2:"M";s:3:"丏";s:2:"N";s:3:"冇";s:2:"O";s:3:"与";s:2:"P";s:3:"丮";s:2:"Q";s:3:"亓";s:2:"R";s:3:"仂";s:2:"S";s:3:"仉";s:2:"T";s:3:"仈";s:2:"U";s:3:"冘";s:2:"V";s:3:"勼";s:2:"W";s:3:"卬";s:2:"X";s:3:"厹";s:2:"Y";s:3:"圠";s:2:"Z";s:3:"夃";s:2:"[";s:3:"夬";s:2:"\";s:3:"尐";s:2:"]";s:3:"巿";s:2:"^";s:3:"旡";s:2:"_";s:3:"殳";s:2:"`";s:3:"毌";s:2:"a";s:3:"气";s:2:"b";s:3:"爿";s:2:"c";s:3:"丱";s:2:"d";s:3:"丼";s:2:"e";s:3:"仨";s:2:"f";s:3:"仜";s:2:"g";s:3:"仩";s:2:"h";s:3:"仡";s:2:"i";s:3:"仝";s:2:"j";s:3:"仚";s:2:"k";s:3:"刌";s:2:"l";s:3:"匜";s:2:"m";s:3:"卌";s:2:"n";s:3:"圢";s:2:"o";s:3:"圣";s:2:"p";s:3:"夗";s:2:"q";s:3:"夯";s:2:"r";s:3:"宁";s:2:"s";s:3:"宄";s:2:"t";s:3:"尒";s:2:"u";s:3:"尻";s:2:"v";s:3:"屴";s:2:"w";s:3:"屳";s:2:"x";s:3:"帄";s:2:"y";s:3:"庀";s:2:"z";s:3:"庂";s:2:"{";s:3:"忉";s:2:"|";s:3:"戉";s:2:"}";s:3:"扐";s:2:"~";s:3:"氕";s:2:"ɡ";s:3:"氶";s:2:"ɢ";s:3:"汃";s:2:"ɣ";s:3:"氿";s:2:"ɤ";s:3:"氻";s:2:"ɥ";s:3:"犮";s:2:"ɦ";s:3:"犰";s:2:"ɧ";s:3:"玊";s:2:"ɨ";s:3:"禸";s:2:"ɩ";s:3:"肊";s:2:"ɪ";s:3:"阞";s:2:"ɫ";s:3:"伎";s:2:"ɬ";s:3:"优";s:2:"ɭ";s:3:"伬";s:2:"ɮ";s:3:"仵";s:2:"ɯ";s:3:"伔";s:2:"ɰ";s:3:"仱";s:2:"ɱ";s:3:"伀";s:2:"ɲ";s:3:"价";s:2:"ɳ";s:3:"伈";s:2:"ɴ";s:3:"伝";s:2:"ɵ";s:3:"伂";s:2:"ɶ";s:3:"伅";s:2:"ɷ";s:3:"伢";s:2:"ɸ";s:3:"伓";s:2:"ɹ";s:3:"伄";s:2:"ɺ";s:3:"仴";s:2:"ɻ";s:3:"伒";s:2:"ɼ";s:3:"冱";s:2:"ɽ";s:3:"刓";s:2:"ɾ";s:3:"刉";s:2:"ɿ";s:3:"刐";s:2:"";s:3:"劦";s:2:"";s:3:"匢";s:2:"";s:3:"匟";s:2:"";s:3:"卍";s:2:"";s:3:"厊";s:2:"";s:3:"吇";s:2:"";s:3:"囡";s:2:"";s:3:"囟";s:2:"";s:3:"圮";s:2:"";s:3:"圪";s:2:"";s:3:"圴";s:2:"";s:3:"夼";s:2:"";s:3:"妀";s:2:"";s:3:"奼";s:2:"";s:3:"妅";s:2:"";s:3:"奻";s:2:"";s:3:"奾";s:2:"";s:3:"奷";s:2:"";s:3:"奿";s:2:"";s:3:"孖";s:2:"";s:3:"尕";s:2:"";s:3:"尥";s:2:"";s:3:"屼";s:2:"";s:3:"屺";s:2:"";s:3:"屻";s:2:"";s:3:"屾";s:2:"";s:3:"巟";s:2:"";s:3:"幵";s:2:"";s:3:"庄";s:2:"";s:3:"异";s:2:"";s:3:"弚";s:2:"";s:3:"彴";s:2:"";s:3:"忕";s:2:"";s:3:"忔";s:2:"";s:3:"忏";s:2:"";s:3:"扜";s:2:"";s:3:"扞";s:2:"";s:3:"扤";s:2:"";s:3:"扡";s:2:"";s:3:"扦";s:2:"";s:3:"扢";s:2:"";s:3:"扙";s:2:"";s:3:"扠";s:2:"";s:3:"扚";s:2:"";s:3:"扥";s:2:"";s:3:"旯";s:2:"";s:3:"旮";s:2:"";s:3:"朾";s:2:"";s:3:"朹";s:2:"";s:3:"朸";s:2:"";s:3:"朻";s:2:"";s:3:"机";s:2:"";s:3:"朿";s:2:"";s:3:"朼";s:2:"";s:3:"朳";s:2:"";s:3:"氘";s:2:"";s:3:"汆";s:2:"";s:3:"汒";s:2:"";s:3:"汜";s:2:"";s:3:"汏";s:2:"";s:3:"汊";s:2:"";s:3:"汔";s:2:"";s:3:"汋";s:2:"@";s:3:"汌";s:2:"A";s:3:"灱";s:2:"B";s:3:"牞";s:2:"C";s:3:"犴";s:2:"D";s:3:"犵";s:2:"E";s:3:"玎";s:2:"F";s:3:"甪";s:2:"G";s:3:"癿";s:2:"H";s:3:"穵";s:2:"I";s:3:"网";s:2:"J";s:3:"艸";s:2:"K";s:3:"艼";s:2:"L";s:3:"芀";s:2:"M";s:3:"艽";s:2:"N";s:3:"艿";s:2:"O";s:3:"虍";s:2:"P";s:3:"襾";s:2:"Q";s:3:"邙";s:2:"R";s:3:"邗";s:2:"S";s:3:"邘";s:2:"T";s:3:"邛";s:2:"U";s:3:"邔";s:2:"V";s:3:"阢";s:2:"W";s:3:"阤";s:2:"X";s:3:"阠";s:2:"Y";s:3:"阣";s:2:"Z";s:3:"佖";s:2:"[";s:3:"伻";s:2:"\";s:3:"佢";s:2:"]";s:3:"佉";s:2:"^";s:3:"体";s:2:"_";s:3:"佤";s:2:"`";s:3:"伾";s:2:"a";s:3:"佧";s:2:"b";s:3:"佒";s:2:"c";s:3:"佟";s:2:"d";s:3:"佁";s:2:"e";s:3:"佘";s:2:"f";s:3:"伭";s:2:"g";s:3:"伳";s:2:"h";s:3:"伿";s:2:"i";s:3:"佡";s:2:"j";s:3:"冏";s:2:"k";s:3:"冹";s:2:"l";s:3:"刜";s:2:"m";s:3:"刞";s:2:"n";s:3:"刡";s:2:"o";s:3:"劭";s:2:"p";s:3:"劮";s:2:"q";s:3:"匉";s:2:"r";s:3:"卣";s:2:"s";s:3:"卲";s:2:"t";s:3:"厎";s:2:"u";s:3:"厏";s:2:"v";s:3:"吰";s:2:"w";s:3:"吷";s:2:"x";s:3:"吪";s:2:"y";s:3:"呔";s:2:"z";s:3:"呅";s:2:"{";s:3:"吙";s:2:"|";s:3:"吜";s:2:"}";s:3:"吥";s:2:"~";s:3:"吘";s:2:"ʡ";s:3:"吽";s:2:"ʢ";s:3:"呏";s:2:"ʣ";s:3:"呁";s:2:"ʤ";s:3:"吨";s:2:"ʥ";s:3:"吤";s:2:"ʦ";s:3:"呇";s:2:"ʧ";s:3:"囮";s:2:"ʨ";s:3:"囧";s:2:"ʩ";s:3:"囥";s:2:"ʪ";s:3:"坁";s:2:"ʫ";s:3:"坅";s:2:"ʬ";s:3:"坌";s:2:"ʭ";s:3:"坉";s:2:"ʮ";s:3:"坋";s:2:"ʯ";s:3:"坒";s:2:"ʰ";s:3:"夆";s:2:"ʱ";s:3:"奀";s:2:"ʲ";s:3:"妦";s:2:"ʳ";s:3:"妘";s:2:"ʴ";s:3:"妠";s:2:"ʵ";s:3:"妗";s:2:"ʶ";s:3:"妎";s:2:"ʷ";s:3:"妢";s:2:"ʸ";s:3:"妐";s:2:"ʹ";s:3:"妏";s:2:"ʺ";s:3:"妧";s:2:"ʻ";s:3:"妡";s:2:"ʼ";s:3:"宎";s:2:"ʽ";s:3:"宒";s:2:"ʾ";s:3:"尨";s:2:"ʿ";s:3:"尪";s:2:"";s:3:"岍";s:2:"";s:3:"岏";s:2:"";s:3:"岈";s:2:"";s:3:"岋";s:2:"";s:3:"岉";s:2:"";s:3:"岒";s:2:"";s:3:"岊";s:2:"";s:3:"岆";s:2:"";s:3:"岓";s:2:"";s:3:"岕";s:2:"";s:3:"巠";s:2:"";s:3:"帊";s:2:"";s:3:"帎";s:2:"";s:3:"庋";s:2:"";s:3:"庉";s:2:"";s:3:"庌";s:2:"";s:3:"庈";s:2:"";s:3:"庍";s:2:"";s:3:"弅";s:2:"";s:3:"弝";s:2:"";s:3:"彸";s:2:"";s:3:"彶";s:2:"";s:3:"忒";s:2:"";s:3:"忑";s:2:"";s:3:"忐";s:2:"";s:3:"忭";s:2:"";s:3:"忨";s:2:"";s:3:"忮";s:2:"";s:3:"忳";s:2:"";s:3:"忡";s:2:"";s:3:"忤";s:2:"";s:3:"忣";s:2:"";s:3:"忺";s:2:"";s:3:"忯";s:2:"";s:3:"忷";s:2:"";s:3:"忻";s:2:"";s:3:"怀";s:2:"";s:3:"忴";s:2:"";s:3:"戺";s:2:"";s:3:"抃";s:2:"";s:3:"抌";s:2:"";s:3:"抎";s:2:"";s:3:"抏";s:2:"";s:3:"抔";s:2:"";s:3:"抇";s:2:"";s:3:"扱";s:2:"";s:3:"扻";s:2:"";s:3:"扺";s:2:"";s:3:"扰";s:2:"";s:3:"抁";s:2:"";s:3:"抈";s:2:"";s:3:"扷";s:2:"";s:3:"扽";s:2:"";s:3:"扲";s:2:"";s:3:"扴";s:2:"";s:3:"攷";s:2:"";s:3:"旰";s:2:"";s:3:"旴";s:2:"";s:3:"旳";s:2:"";s:3:"旲";s:2:"";s:3:"旵";s:2:"";s:3:"杅";s:2:"";s:3:"杇";s:2:"@";s:3:"杙";s:2:"A";s:3:"杕";s:2:"B";s:3:"杌";s:2:"C";s:3:"杈";s:2:"D";s:3:"杝";s:2:"E";s:3:"杍";s:2:"F";s:3:"杚";s:2:"G";s:3:"杋";s:2:"H";s:3:"毐";s:2:"I";s:3:"氙";s:2:"J";s:3:"氚";s:2:"K";s:3:"汸";s:2:"L";s:3:"汧";s:2:"M";s:3:"汫";s:2:"N";s:3:"沄";s:2:"O";s:3:"沋";s:2:"P";s:3:"沏";s:2:"Q";s:3:"汱";s:2:"R";s:3:"汯";s:2:"S";s:3:"汩";s:2:"T";s:3:"沚";s:2:"U";s:3:"汭";s:2:"V";s:3:"沇";s:2:"W";s:3:"沕";s:2:"X";s:3:"沜";s:2:"Y";s:3:"汦";s:2:"Z";s:3:"汳";s:2:"[";s:3:"汥";s:2:"\";s:3:"汻";s:2:"]";s:3:"沎";s:2:"^";s:3:"灴";s:2:"_";s:3:"灺";s:2:"`";s:3:"牣";s:2:"a";s:3:"犿";s:2:"b";s:3:"犽";s:2:"c";s:3:"狃";s:2:"d";s:3:"狆";s:2:"e";s:3:"狁";s:2:"f";s:3:"犺";s:2:"g";s:3:"狅";s:2:"h";s:3:"玕";s:2:"i";s:3:"玗";s:2:"j";s:3:"玓";s:2:"k";s:3:"玔";s:2:"l";s:3:"玒";s:2:"m";s:3:"町";s:2:"n";s:3:"甹";s:2:"o";s:3:"疔";s:2:"p";s:3:"疕";s:2:"q";s:3:"皁";s:2:"r";s:3:"礽";s:2:"s";s:3:"耴";s:2:"t";s:3:"肕";s:2:"u";s:3:"肙";s:2:"v";s:3:"肐";s:2:"w";s:3:"肒";s:2:"x";s:3:"肜";s:2:"y";s:3:"芐";s:2:"z";s:3:"芏";s:2:"{";s:3:"芅";s:2:"|";s:3:"芎";s:2:"}";s:3:"芑";s:2:"~";s:3:"芓";s:2:"ˡ";s:3:"芊";s:2:"ˢ";s:3:"芃";s:2:"ˣ";s:3:"芄";s:2:"ˤ";s:3:"豸";s:2:"˥";s:3:"迉";s:2:"˦";s:3:"辿";s:2:"˧";s:3:"邟";s:2:"˨";s:3:"邡";s:2:"˩";s:3:"邥";s:2:"˪";s:3:"邞";s:2:"˫";s:3:"邧";s:2:"ˬ";s:3:"邠";s:2:"˭";s:3:"阰";s:2:"ˮ";s:3:"阨";s:2:"˯";s:3:"阯";s:2:"˰";s:3:"阭";s:2:"˱";s:3:"丳";s:2:"˲";s:3:"侘";s:2:"˳";s:3:"佼";s:2:"˴";s:3:"侅";s:2:"˵";s:3:"佽";s:2:"˶";s:3:"侀";s:2:"˷";s:3:"侇";s:2:"˸";s:3:"佶";s:2:"˹";s:3:"佴";s:2:"˺";s:3:"侉";s:2:"˻";s:3:"侄";s:2:"˼";s:3:"佷";s:2:"˽";s:3:"佌";s:2:"˾";s:3:"侗";s:2:"˿";s:3:"佪";s:2:"";s:3:"侚";s:2:"";s:3:"佹";s:2:"";s:3:"侁";s:2:"";s:3:"佸";s:2:"";s:3:"侐";s:2:"";s:3:"侜";s:2:"";s:3:"侔";s:2:"";s:3:"侞";s:2:"";s:3:"侒";s:2:"";s:3:"侂";s:2:"";s:3:"侕";s:2:"";s:3:"佫";s:2:"";s:3:"佮";s:2:"";s:3:"冞";s:2:"";s:3:"冼";s:2:"";s:3:"冾";s:2:"";s:3:"刵";s:2:"";s:3:"刲";s:2:"";s:3:"刳";s:2:"";s:3:"剆";s:2:"";s:3:"刱";s:2:"";s:3:"劼";s:2:"";s:3:"匊";s:2:"";s:3:"匋";s:2:"";s:3:"匼";s:2:"";s:3:"厒";s:2:"";s:3:"厔";s:2:"";s:3:"咇";s:2:"";s:3:"呿";s:2:"";s:3:"咁";s:2:"";s:3:"咑";s:2:"";s:3:"咂";s:2:"";s:3:"咈";s:2:"";s:3:"呫";s:2:"";s:3:"呺";s:2:"";s:3:"呾";s:2:"";s:3:"呥";s:2:"";s:3:"呬";s:2:"";s:3:"呴";s:2:"";s:3:"呦";s:2:"";s:3:"咍";s:2:"";s:3:"呯";s:2:"";s:3:"呡";s:2:"";s:3:"呠";s:2:"";s:3:"咘";s:2:"";s:3:"呣";s:2:"";s:3:"呧";s:2:"";s:3:"呤";s:2:"";s:3:"囷";s:2:"";s:3:"囹";s:2:"";s:3:"坯";s:2:"";s:3:"坲";s:2:"";s:3:"坭";s:2:"";s:3:"坫";s:2:"";s:3:"坱";s:2:"";s:3:"坰";s:2:"";s:3:"坶";s:2:"";s:3:"垀";s:2:"";s:3:"坵";s:2:"";s:3:"坻";s:2:"";s:3:"坳";s:2:"";s:3:"坴";s:2:"";s:3:"坢";s:2:"@";s:3:"坨";s:2:"A";s:3:"坽";s:2:"B";s:3:"夌";s:2:"C";s:3:"奅";s:2:"D";s:3:"妵";s:2:"E";s:3:"妺";s:2:"F";s:3:"姏";s:2:"G";s:3:"姎";s:2:"H";s:3:"妲";s:2:"I";s:3:"姌";s:2:"J";s:3:"姁";s:2:"K";s:3:"妶";s:2:"L";s:3:"妼";s:2:"M";s:3:"姃";s:2:"N";s:3:"姖";s:2:"O";s:3:"妱";s:2:"P";s:3:"妽";s:2:"Q";s:3:"姀";s:2:"R";s:3:"姈";s:2:"S";s:3:"妴";s:2:"T";s:3:"姇";s:2:"U";s:3:"孢";s:2:"V";s:3:"孥";s:2:"W";s:3:"宓";s:2:"X";s:3:"宕";s:2:"Y";s:3:"屄";s:2:"Z";s:3:"屇";s:2:"[";s:3:"岮";s:2:"\";s:3:"岤";s:2:"]";s:3:"岠";s:2:"^";s:3:"岵";s:2:"_";s:3:"岯";s:2:"`";s:3:"岨";s:2:"a";s:3:"岬";s:2:"b";s:3:"岟";s:2:"c";s:3:"岣";s:2:"d";s:3:"岭";s:2:"e";s:3:"岢";s:2:"f";s:3:"岪";s:2:"g";s:3:"岧";s:2:"h";s:3:"岝";s:2:"i";s:3:"岥";s:2:"j";s:3:"岶";s:2:"k";s:3:"岰";s:2:"l";s:3:"岦";s:2:"m";s:3:"帗";s:2:"n";s:3:"帔";s:2:"o";s:3:"帙";s:2:"p";s:3:"弨";s:2:"q";s:3:"弢";s:2:"r";s:3:"弣";s:2:"s";s:3:"弤";s:2:"t";s:3:"彔";s:2:"u";s:3:"徂";s:2:"v";s:3:"彾";s:2:"w";s:3:"彽";s:2:"x";s:3:"忞";s:2:"y";s:3:"忥";s:2:"z";s:3:"怭";s:2:"{";s:3:"怦";s:2:"|";s:3:"怙";s:2:"}";s:3:"怲";s:2:"~";s:3:"怋";s:2:"̡";s:3:"怴";s:2:"̢";s:3:"怊";s:2:"̣";s:3:"怗";s:2:"̤";s:3:"怳";s:2:"̥";s:3:"怚";s:2:"̦";s:3:"怞";s:2:"̧";s:3:"怬";s:2:"̨";s:3:"怢";s:2:"̩";s:3:"怍";s:2:"̪";s:3:"怐";s:2:"̫";s:3:"怮";s:2:"̬";s:3:"怓";s:2:"̭";s:3:"怑";s:2:"̮";s:3:"怌";s:2:"̯";s:3:"怉";s:2:"̰";s:3:"怜";s:2:"̱";s:3:"戔";s:2:"̲";s:3:"戽";s:2:"̳";s:3:"抭";s:2:"̴";s:3:"抴";s:2:"̵";s:3:"拑";s:2:"̶";s:3:"抾";s:2:"̷";s:3:"抪";s:2:"̸";s:3:"抶";s:2:"̹";s:3:"拊";s:2:"̺";s:3:"抮";s:2:"̻";s:3:"抳";s:2:"̼";s:3:"抯";s:2:"̽";s:3:"抻";s:2:"̾";s:3:"抩";s:2:"̿";s:3:"抰";s:2:"";s:3:"抸";s:2:"";s:3:"攽";s:2:"";s:3:"斨";s:2:"";s:3:"斻";s:2:"";s:3:"昉";s:2:"";s:3:"旼";s:2:"";s:3:"昄";s:2:"";s:3:"昒";s:2:"";s:3:"昈";s:2:"";s:3:"旻";s:2:"";s:3:"昃";s:2:"";s:3:"昋";s:2:"";s:3:"昍";s:2:"";s:3:"昅";s:2:"";s:3:"旽";s:2:"";s:3:"昑";s:2:"";s:3:"昐";s:2:"";s:3:"曶";s:2:"";s:3:"朊";s:2:"";s:3:"枅";s:2:"";s:3:"杬";s:2:"";s:3:"枎";s:2:"";s:3:"枒";s:2:"";s:3:"杶";s:2:"";s:3:"杻";s:2:"";s:3:"枘";s:2:"";s:3:"枆";s:2:"";s:3:"构";s:2:"";s:3:"杴";s:2:"";s:3:"枍";s:2:"";s:3:"枌";s:2:"";s:3:"杺";s:2:"";s:3:"枟";s:2:"";s:3:"枑";s:2:"";s:3:"枙";s:2:"";s:3:"枃";s:2:"";s:3:"杽";s:2:"";s:3:"极";s:2:"";s:3:"杸";s:2:"";s:3:"杹";s:2:"";s:3:"枔";s:2:"";s:3:"欥";s:2:"";s:3:"殀";s:2:"";s:3:"歾";s:2:"";s:3:"毞";s:2:"";s:3:"氝";s:2:"";s:3:"沓";s:2:"";s:3:"泬";s:2:"";s:3:"泫";s:2:"";s:3:"泮";s:2:"";s:3:"泙";s:2:"";s:3:"沶";s:2:"";s:3:"泔";s:2:"";s:3:"沭";s:2:"";s:3:"泧";s:2:"";s:3:"沷";s:2:"";s:3:"泐";s:2:"";s:3:"泂";s:2:"";s:3:"沺";s:2:"";s:3:"泃";s:2:"";s:3:"泆";s:2:"";s:3:"泭";s:2:"";s:3:"泲";s:2:"@";s:3:"泒";s:2:"A";s:3:"泝";s:2:"B";s:3:"沴";s:2:"C";s:3:"沊";s:2:"D";s:3:"沝";s:2:"E";s:3:"沀";s:2:"F";s:3:"泞";s:2:"G";s:3:"泀";s:2:"H";s:3:"洰";s:2:"I";s:3:"泍";s:2:"J";s:3:"泇";s:2:"K";s:3:"沰";s:2:"L";s:3:"泹";s:2:"M";s:3:"泏";s:2:"N";s:3:"泩";s:2:"O";s:3:"泑";s:2:"P";s:3:"炔";s:2:"Q";s:3:"炘";s:2:"R";s:3:"炅";s:2:"S";s:3:"炓";s:2:"T";s:3:"炆";s:2:"U";s:3:"炄";s:2:"V";s:3:"炑";s:2:"W";s:3:"炖";s:2:"X";s:3:"炂";s:2:"Y";s:3:"炚";s:2:"Z";s:3:"炃";s:2:"[";s:3:"牪";s:2:"\";s:3:"狖";s:2:"]";s:3:"狋";s:2:"^";s:3:"狘";s:2:"_";s:3:"狉";s:2:"`";s:3:"狜";s:2:"a";s:3:"狒";s:2:"b";s:3:"狔";s:2:"c";s:3:"狚";s:2:"d";s:3:"狌";s:2:"e";s:3:"狑";s:2:"f";s:3:"玤";s:2:"g";s:3:"玡";s:2:"h";s:3:"玭";s:2:"i";s:3:"玦";s:2:"j";s:3:"玢";s:2:"k";s:3:"玠";s:2:"l";s:3:"玬";s:2:"m";s:3:"玝";s:2:"n";s:3:"瓝";s:2:"o";s:3:"瓨";s:2:"p";s:3:"甿";s:2:"q";s:3:"畀";s:2:"r";s:3:"甾";s:2:"s";s:3:"疌";s:2:"t";s:3:"疘";s:2:"u";s:3:"皯";s:2:"v";s:3:"盳";s:2:"w";s:3:"盱";s:2:"x";s:3:"盰";s:2:"y";s:3:"盵";s:2:"z";s:3:"矸";s:2:"{";s:3:"矼";s:2:"|";s:3:"矹";s:2:"}";s:3:"矻";s:2:"~";s:3:"矺";s:2:"͡";s:3:"矷";s:2:"͢";s:3:"祂";s:2:"ͣ";s:3:"礿";s:2:"ͤ";s:3:"秅";s:2:"ͥ";s:3:"穸";s:2:"ͦ";s:3:"穻";s:2:"ͧ";s:3:"竻";s:2:"ͨ";s:3:"籵";s:2:"ͩ";s:3:"糽";s:2:"ͪ";s:3:"耵";s:2:"ͫ";s:3:"肏";s:2:"ͬ";s:3:"肮";s:2:"ͭ";s:3:"肣";s:2:"ͮ";s:3:"肸";s:2:"ͯ";s:3:"肵";s:2:"Ͱ";s:3:"肭";s:2:"ͱ";s:3:"舠";s:2:"Ͳ";s:3:"芠";s:2:"ͳ";s:3:"苀";s:2:"ʹ";s:3:"芫";s:2:"͵";s:3:"芚";s:2:"Ͷ";s:3:"芘";s:2:"ͷ";s:3:"芛";s:2:"͸";s:3:"芵";s:2:"͹";s:3:"芧";s:2:"ͺ";s:3:"芮";s:2:"ͻ";s:3:"芼";s:2:"ͼ";s:3:"芞";s:2:"ͽ";s:3:"芺";s:2:";";s:3:"芴";s:2:"Ϳ";s:3:"芨";s:2:"";s:3:"芡";s:2:"";s:3:"芩";s:2:"";s:3:"苂";s:2:"";s:3:"芤";s:2:"";s:3:"苃";s:2:"";s:3:"芶";s:2:"";s:3:"芢";s:2:"";s:3:"虰";s:2:"";s:3:"虯";s:2:"";s:3:"虭";s:2:"";s:3:"虮";s:2:"";s:3:"豖";s:2:"";s:3:"迒";s:2:"";s:3:"迋";s:2:"";s:3:"迓";s:2:"";s:3:"迍";s:2:"";s:3:"迖";s:2:"";s:3:"迕";s:2:"";s:3:"迗";s:2:"";s:3:"邲";s:2:"";s:3:"邴";s:2:"";s:3:"邯";s:2:"";s:3:"邳";s:2:"";s:3:"邰";s:2:"";s:3:"阹";s:2:"";s:3:"阽";s:2:"";s:3:"阼";s:2:"";s:3:"阺";s:2:"";s:3:"陃";s:2:"";s:3:"俍";s:2:"";s:3:"俅";s:2:"";s:3:"俓";s:2:"";s:3:"侲";s:2:"";s:3:"俉";s:2:"";s:3:"俋";s:2:"";s:3:"俁";s:2:"";s:3:"俔";s:2:"";s:3:"俜";s:2:"";s:3:"俙";s:2:"";s:3:"侻";s:2:"";s:3:"侳";s:2:"";s:3:"俛";s:2:"";s:3:"俇";s:2:"";s:3:"俖";s:2:"";s:3:"侺";s:2:"";s:3:"俀";s:2:"";s:3:"侹";s:2:"";s:3:"俬";s:2:"";s:3:"剄";s:2:"";s:3:"剉";s:2:"";s:3:"勀";s:2:"";s:3:"勂";s:2:"";s:3:"匽";s:2:"";s:3:"卼";s:2:"";s:3:"厗";s:2:"";s:3:"厖";s:2:"";s:3:"厙";s:2:"";s:3:"厘";s:2:"";s:3:"咺";s:2:"";s:3:"咡";s:2:"";s:3:"咭";s:2:"";s:3:"咥";s:2:"";s:3:"哏";s:2:"@";s:3:"哃";s:2:"A";s:3:"茍";s:2:"B";s:3:"咷";s:2:"C";s:3:"咮";s:2:"D";s:3:"哖";s:2:"E";s:3:"咶";s:2:"F";s:3:"哅";s:2:"G";s:3:"哆";s:2:"H";s:3:"咠";s:2:"I";s:3:"呰";s:2:"J";s:3:"咼";s:2:"K";s:3:"咢";s:2:"L";s:3:"咾";s:2:"M";s:3:"呲";s:2:"N";s:3:"哞";s:2:"O";s:3:"咰";s:2:"P";s:3:"垵";s:2:"Q";s:3:"垞";s:2:"R";s:3:"垟";s:2:"S";s:3:"垤";s:2:"T";s:3:"垌";s:2:"U";s:3:"垗";s:2:"V";s:3:"垝";s:2:"W";s:3:"垛";s:2:"X";s:3:"垔";s:2:"Y";s:3:"垘";s:2:"Z";s:3:"垏";s:2:"[";s:3:"垙";s:2:"\";s:3:"垥";s:2:"]";s:3:"垚";s:2:"^";s:3:"垕";s:2:"_";s:3:"壴";s:2:"`";s:3:"复";s:2:"a";s:3:"奓";s:2:"b";s:3:"姡";s:2:"c";s:3:"姞";s:2:"d";s:3:"姮";s:2:"e";s:3:"娀";s:2:"f";s:3:"姱";s:2:"g";s:3:"姝";s:2:"h";s:3:"姺";s:2:"i";s:3:"姽";s:2:"j";s:3:"姼";s:2:"k";s:3:"姶";s:2:"l";s:3:"姤";s:2:"m";s:3:"姲";s:2:"n";s:3:"姷";s:2:"o";s:3:"姛";s:2:"p";s:3:"姩";s:2:"q";s:3:"姳";s:2:"r";s:3:"姵";s:2:"s";s:3:"姠";s:2:"t";s:3:"姾";s:2:"u";s:3:"姴";s:2:"v";s:3:"姭";s:2:"w";s:3:"宨";s:2:"x";s:3:"屌";s:2:"y";s:3:"峐";s:2:"z";s:3:"峘";s:2:"{";s:3:"峌";s:2:"|";s:3:"峗";s:2:"}";s:3:"峋";s:2:"~";s:3:"峛";s:2:"Ρ";s:3:"峞";s:2:"΢";s:3:"峚";s:2:"Σ";s:3:"峉";s:2:"Τ";s:3:"峇";s:2:"Υ";s:3:"峊";s:2:"Φ";s:3:"峖";s:2:"Χ";s:3:"峓";s:2:"Ψ";s:3:"峔";s:2:"Ω";s:3:"峏";s:2:"Ϊ";s:3:"峈";s:2:"Ϋ";s:3:"峆";s:2:"ά";s:3:"峎";s:2:"έ";s:3:"峟";s:2:"ή";s:3:"峸";s:2:"ί";s:3:"巹";s:2:"ΰ";s:3:"帡";s:2:"α";s:3:"帢";s:2:"β";s:3:"帣";s:2:"γ";s:3:"帠";s:2:"δ";s:3:"帤";s:2:"ε";s:3:"庰";s:2:"ζ";s:3:"庤";s:2:"η";s:3:"庢";s:2:"θ";s:3:"庛";s:2:"ι";s:3:"庣";s:2:"κ";s:3:"庥";s:2:"λ";s:3:"弇";s:2:"μ";s:3:"弮";s:2:"ν";s:3:"彖";s:2:"ξ";s:3:"徆";s:2:"ο";s:3:"怷";s:2:"";s:3:"怹";s:2:"";s:3:"恔";s:2:"";s:3:"恲";s:2:"";s:3:"恞";s:2:"";s:3:"恅";s:2:"";s:3:"恓";s:2:"";s:3:"恇";s:2:"";s:3:"恉";s:2:"";s:3:"恛";s:2:"";s:3:"恌";s:2:"";s:3:"恀";s:2:"";s:3:"恂";s:2:"";s:3:"恟";s:2:"";s:3:"怤";s:2:"";s:3:"恄";s:2:"";s:3:"恘";s:2:"";s:3:"恦";s:2:"";s:3:"恮";s:2:"";s:3:"扂";s:2:"";s:3:"扃";s:2:"";s:3:"拏";s:2:"";s:3:"挍";s:2:"";s:3:"挋";s:2:"";s:3:"拵";s:2:"";s:3:"挎";s:2:"";s:3:"挃";s:2:"";s:3:"拫";s:2:"";s:3:"拹";s:2:"";s:3:"挏";s:2:"";s:3:"挌";s:2:"";s:3:"拸";s:2:"";s:3:"拶";s:2:"";s:3:"挀";s:2:"";s:3:"挓";s:2:"";s:3:"挔";s:2:"";s:3:"拺";s:2:"";s:3:"挕";s:2:"";s:3:"拻";s:2:"";s:3:"拰";s:2:"";s:3:"敁";s:2:"";s:3:"敃";s:2:"";s:3:"斪";s:2:"";s:3:"斿";s:2:"";s:3:"昶";s:2:"";s:3:"昡";s:2:"";s:3:"昲";s:2:"";s:3:"昵";s:2:"";s:3:"昜";s:2:"";s:3:"昦";s:2:"";s:3:"昢";s:2:"";s:3:"昳";s:2:"";s:3:"昫";s:2:"";s:3:"昺";s:2:"";s:3:"昝";s:2:"";s:3:"昴";s:2:"";s:3:"昹";s:2:"";s:3:"昮";s:2:"";s:3:"朏";s:2:"";s:3:"朐";s:2:"";s:3:"柁";s:2:"";s:3:"柲";s:2:"";s:3:"柈";s:2:"";s:3:"枺";s:2:"@";s:3:"柜";s:2:"A";s:3:"枻";s:2:"B";s:3:"柸";s:2:"C";s:3:"柘";s:2:"D";s:3:"柀";s:2:"E";s:3:"枷";s:2:"F";s:3:"柅";s:2:"G";s:3:"柫";s:2:"H";s:3:"柤";s:2:"I";s:3:"柟";s:2:"J";s:3:"枵";s:2:"K";s:3:"柍";s:2:"L";s:3:"枳";s:2:"M";s:3:"柷";s:2:"N";s:3:"柶";s:2:"O";s:3:"柮";s:2:"P";s:3:"柣";s:2:"Q";s:3:"柂";s:2:"R";s:3:"枹";s:2:"S";s:3:"柎";s:2:"T";s:3:"柧";s:2:"U";s:3:"柰";s:2:"V";s:3:"枲";s:2:"W";s:3:"柼";s:2:"X";s:3:"柆";s:2:"Y";s:3:"柭";s:2:"Z";s:3:"柌";s:2:"[";s:3:"枮";s:2:"\";s:3:"柦";s:2:"]";s:3:"柛";s:2:"^";s:3:"柺";s:2:"_";s:3:"柉";s:2:"`";s:3:"柊";s:2:"a";s:3:"柃";s:2:"b";s:3:"柪";s:2:"c";s:3:"柋";s:2:"d";s:3:"欨";s:2:"e";s:3:"殂";s:2:"f";s:3:"殄";s:2:"g";s:3:"殶";s:2:"h";s:3:"毖";s:2:"i";s:3:"毘";s:2:"j";s:3:"毠";s:2:"k";s:3:"氠";s:2:"l";s:3:"氡";s:2:"m";s:3:"洨";s:2:"n";s:3:"洴";s:2:"o";s:3:"洭";s:2:"p";s:3:"洟";s:2:"q";s:3:"洼";s:2:"r";s:3:"洿";s:2:"s";s:3:"洒";s:2:"t";s:3:"洊";s:2:"u";s:3:"泚";s:2:"v";s:3:"洳";s:2:"w";s:3:"洄";s:2:"x";s:3:"洙";s:2:"y";s:3:"洺";s:2:"z";s:3:"洚";s:2:"{";s:3:"洑";s:2:"|";s:3:"洀";s:2:"}";s:3:"洝";s:2:"~";s:3:"浂";s:2:"ϡ";s:3:"洁";s:2:"Ϣ";s:3:"洘";s:2:"ϣ";s:3:"洷";s:2:"Ϥ";s:3:"洃";s:2:"ϥ";s:3:"洏";s:2:"Ϧ";s:3:"浀";s:2:"ϧ";s:3:"洇";s:2:"Ϩ";s:3:"洠";s:2:"ϩ";s:3:"洬";s:2:"Ϫ";s:3:"洈";s:2:"ϫ";s:3:"洢";s:2:"Ϭ";s:3:"洉";s:2:"ϭ";s:3:"洐";s:2:"Ϯ";s:3:"炷";s:2:"ϯ";s:3:"炟";s:2:"ϰ";s:3:"炾";s:2:"ϱ";s:3:"炱";s:2:"ϲ";s:3:"炰";s:2:"ϳ";s:3:"炡";s:2:"ϴ";s:3:"炴";s:2:"ϵ";s:3:"炵";s:2:"϶";s:3:"炩";s:2:"Ϸ";s:3:"牁";s:2:"ϸ";s:3:"牉";s:2:"Ϲ";s:3:"牊";s:2:"Ϻ";s:3:"牬";s:2:"ϻ";s:3:"牰";s:2:"ϼ";s:3:"牳";s:2:"Ͻ";s:3:"牮";s:2:"Ͼ";s:3:"狊";s:2:"Ͽ";s:3:"狤";s:2:"";s:3:"狨";s:2:"";s:3:"狫";s:2:"";s:3:"狟";s:2:"";s:3:"狪";s:2:"";s:3:"狦";s:2:"";s:3:"狣";s:2:"";s:3:"玅";s:2:"";s:3:"珌";s:2:"";s:3:"珂";s:2:"";s:3:"珈";s:2:"";s:3:"珅";s:2:"";s:3:"玹";s:2:"";s:3:"玶";s:2:"";s:3:"玵";s:2:"";s:3:"玴";s:2:"";s:3:"珫";s:2:"";s:3:"玿";s:2:"";s:3:"珇";s:2:"";s:3:"玾";s:2:"";s:3:"珃";s:2:"";s:3:"珆";s:2:"";s:3:"玸";s:2:"";s:3:"珋";s:2:"";s:3:"瓬";s:2:"";s:3:"瓮";s:2:"";s:3:"甮";s:2:"";s:3:"畇";s:2:"";s:3:"畈";s:2:"";s:3:"疧";s:2:"";s:3:"疪";s:2:"";s:3:"癹";s:2:"";s:3:"盄";s:2:"";s:3:"眈";s:2:"";s:3:"眃";s:2:"";s:3:"眄";s:2:"";s:3:"眅";s:2:"";s:3:"眊";s:2:"";s:3:"盷";s:2:"";s:3:"盻";s:2:"";s:3:"盺";s:2:"";s:3:"矧";s:2:"";s:3:"矨";s:2:"";s:3:"砆";s:2:"";s:3:"砑";s:2:"";s:3:"砒";s:2:"";s:3:"砅";s:2:"";s:3:"砐";s:2:"";s:3:"砏";s:2:"";s:3:"砎";s:2:"";s:3:"砉";s:2:"";s:3:"砃";s:2:"";s:3:"砓";s:2:"";s:3:"祊";s:2:"";s:3:"祌";s:2:"";s:3:"祋";s:2:"";s:3:"祅";s:2:"";s:3:"祄";s:2:"";s:3:"秕";s:2:"";s:3:"种";s:2:"";s:3:"秏";s:2:"";s:3:"秖";s:2:"";s:3:"秎";s:2:"";s:3:"窀";s:2:"@";s:3:"穾";s:2:"A";s:3:"竑";s:2:"B";s:3:"笀";s:2:"C";s:3:"笁";s:2:"D";s:3:"籺";s:2:"E";s:3:"籸";s:2:"F";s:3:"籹";s:2:"G";s:3:"籿";s:2:"H";s:3:"粀";s:2:"I";s:3:"粁";s:2:"J";s:3:"紃";s:2:"K";s:3:"紈";s:2:"L";s:3:"紁";s:2:"M";s:3:"罘";s:2:"N";s:3:"羑";s:2:"O";s:3:"羍";s:2:"P";s:3:"羾";s:2:"Q";s:3:"耇";s:2:"R";s:3:"耎";s:2:"S";s:3:"耏";s:2:"T";s:3:"耔";s:2:"U";s:3:"耷";s:2:"V";s:3:"胘";s:2:"W";s:3:"胇";s:2:"X";s:3:"胠";s:2:"Y";s:3:"胑";s:2:"Z";s:3:"胈";s:2:"[";s:3:"胂";s:2:"\";s:3:"胐";s:2:"]";s:3:"胅";s:2:"^";s:3:"胣";s:2:"_";s:3:"胙";s:2:"`";s:3:"胜";s:2:"a";s:3:"胊";s:2:"b";s:3:"胕";s:2:"c";s:3:"胉";s:2:"d";s:3:"胏";s:2:"e";s:3:"胗";s:2:"f";s:3:"胦";s:2:"g";s:3:"胍";s:2:"h";s:3:"臿";s:2:"i";s:3:"舡";s:2:"j";s:3:"芔";s:2:"k";s:3:"苙";s:2:"l";s:3:"苾";s:2:"m";s:3:"苹";s:2:"n";s:3:"茇";s:2:"o";s:3:"苨";s:2:"p";s:3:"茀";s:2:"q";s:3:"苕";s:2:"r";s:3:"茺";s:2:"s";s:3:"苫";s:2:"t";s:3:"苖";s:2:"u";s:3:"苴";s:2:"v";s:3:"苬";s:2:"w";s:3:"苡";s:2:"x";s:3:"苲";s:2:"y";s:3:"苵";s:2:"z";s:3:"茌";s:2:"{";s:3:"苻";s:2:"|";s:3:"苶";s:2:"}";s:3:"苰";s:2:"~";s:3:"苪";s:2:"С";s:3:"苤";s:2:"Т";s:3:"苠";s:2:"У";s:3:"苺";s:2:"Ф";s:3:"苳";s:2:"Х";s:3:"苭";s:2:"Ц";s:3:"虷";s:2:"Ч";s:3:"虴";s:2:"Ш";s:3:"虼";s:2:"Щ";s:3:"虳";s:2:"Ъ";s:3:"衁";s:2:"Ы";s:3:"衎";s:2:"Ь";s:3:"衧";s:2:"Э";s:3:"衪";s:2:"Ю";s:3:"衩";s:2:"Я";s:3:"觓";s:2:"а";s:3:"訄";s:2:"б";s:3:"訇";s:2:"в";s:3:"赲";s:2:"г";s:3:"迣";s:2:"д";s:3:"迡";s:2:"е";s:3:"迮";s:2:"ж";s:3:"迠";s:2:"з";s:3:"郱";s:2:"и";s:3:"邽";s:2:"й";s:3:"邿";s:2:"к";s:3:"郕";s:2:"л";s:3:"郅";s:2:"м";s:3:"邾";s:2:"н";s:3:"郇";s:2:"о";s:3:"郋";s:2:"п";s:3:"郈";s:2:"";s:3:"釔";s:2:"";s:3:"釓";s:2:"";s:3:"陔";s:2:"";s:3:"陏";s:2:"";s:3:"陑";s:2:"";s:3:"陓";s:2:"";s:3:"陊";s:2:"";s:3:"陎";s:2:"";s:3:"倞";s:2:"";s:3:"倅";s:2:"";s:3:"倇";s:2:"";s:3:"倓";s:2:"";s:3:"倢";s:2:"";s:3:"倰";s:2:"";s:3:"倛";s:2:"";s:3:"俵";s:2:"";s:3:"俴";s:2:"";s:3:"倳";s:2:"";s:3:"倷";s:2:"";s:3:"倬";s:2:"";s:3:"俶";s:2:"";s:3:"俷";s:2:"";s:3:"倗";s:2:"";s:3:"倜";s:2:"";s:3:"倠";s:2:"";s:3:"倧";s:2:"";s:3:"倵";s:2:"";s:3:"倯";s:2:"";s:3:"倱";s:2:"";s:3:"倎";s:2:"";s:3:"党";s:2:"";s:3:"冔";s:2:"";s:3:"冓";s:2:"";s:3:"凊";s:2:"";s:3:"凄";s:2:"";s:3:"凅";s:2:"";s:3:"凈";s:2:"";s:3:"凎";s:2:"";s:3:"剡";s:2:"";s:3:"剚";s:2:"";s:3:"剒";s:2:"";s:3:"剞";s:2:"";s:3:"剟";s:2:"";s:3:"剕";s:2:"";s:3:"剢";s:2:"";s:3:"勍";s:2:"";s:3:"匎";s:2:"";s:3:"厞";s:2:"";s:3:"唦";s:2:"";s:3:"哢";s:2:"";s:3:"唗";s:2:"";s:3:"唒";s:2:"";s:3:"哧";s:2:"";s:3:"哳";s:2:"";s:3:"哤";s:2:"";s:3:"唚";s:2:"";s:3:"哿";s:2:"";s:3:"唄";s:2:"";s:3:"唈";s:2:"";s:3:"哫";s:2:"";s:3:"唑";s:2:"";s:3:"唅";s:2:"";s:3:"哱";s:2:"@";s:3:"唊";s:2:"A";s:3:"哻";s:2:"B";s:3:"哷";s:2:"C";s:3:"哸";s:2:"D";s:3:"哠";s:2:"E";s:3:"唎";s:2:"F";s:3:"唃";s:2:"G";s:3:"唋";s:2:"H";s:3:"圁";s:2:"I";s:3:"圂";s:2:"J";s:3:"埌";s:2:"K";s:3:"堲";s:2:"L";s:3:"埕";s:2:"M";s:3:"埒";s:2:"N";s:3:"垺";s:2:"O";s:3:"埆";s:2:"P";s:3:"垽";s:2:"Q";s:3:"垼";s:2:"R";s:3:"垸";s:2:"S";s:3:"垶";s:2:"T";s:3:"垿";s:2:"U";s:3:"埇";s:2:"V";s:3:"埐";s:2:"W";s:3:"垹";s:2:"X";s:3:"埁";s:2:"Y";s:3:"夎";s:2:"Z";s:3:"奊";s:2:"[";s:3:"娙";s:2:"\";s:3:"娖";s:2:"]";s:3:"娭";s:2:"^";s:3:"娮";s:2:"_";s:3:"娕";s:2:"`";s:3:"娏";s:2:"a";s:3:"娗";s:2:"b";s:3:"娊";s:2:"c";s:3:"娞";s:2:"d";s:3:"娳";s:2:"e";s:3:"孬";s:2:"f";s:3:"宧";s:2:"g";s:3:"宭";s:2:"h";s:3:"宬";s:2:"i";s:3:"尃";s:2:"j";s:3:"屖";s:2:"k";s:3:"屔";s:2:"l";s:3:"峬";s:2:"m";s:3:"峿";s:2:"n";s:3:"峮";s:2:"o";s:3:"峱";s:2:"p";s:3:"峷";s:2:"q";s:3:"崀";s:2:"r";s:3:"峹";s:2:"s";s:3:"帩";s:2:"t";s:3:"帨";s:2:"u";s:3:"庨";s:2:"v";s:3:"庮";s:2:"w";s:3:"庪";s:2:"x";s:3:"庬";s:2:"y";s:3:"弳";s:2:"z";s:3:"弰";s:2:"{";s:3:"彧";s:2:"|";s:3:"恝";s:2:"}";s:3:"恚";s:2:"~";s:3:"恧";s:2:"ѡ";s:3:"恁";s:2:"Ѣ";s:3:"悢";s:2:"ѣ";s:3:"悈";s:2:"Ѥ";s:3:"悀";s:2:"ѥ";s:3:"悒";s:2:"Ѧ";s:3:"悁";s:2:"ѧ";s:3:"悝";s:2:"Ѩ";s:3:"悃";s:2:"ѩ";s:3:"悕";s:2:"Ѫ";s:3:"悛";s:2:"ѫ";s:3:"悗";s:2:"Ѭ";s:3:"悇";s:2:"ѭ";s:3:"悜";s:2:"Ѯ";s:3:"悎";s:2:"ѯ";s:3:"戙";s:2:"Ѱ";s:3:"扆";s:2:"ѱ";s:3:"拲";s:2:"Ѳ";s:3:"挐";s:2:"ѳ";s:3:"捖";s:2:"Ѵ";s:3:"挬";s:2:"ѵ";s:3:"捄";s:2:"Ѷ";s:3:"捅";s:2:"ѷ";s:3:"挶";s:2:"Ѹ";s:3:"捃";s:2:"ѹ";s:3:"揤";s:2:"Ѻ";s:3:"挹";s:2:"ѻ";s:3:"捋";s:2:"Ѽ";s:3:"捊";s:2:"ѽ";s:3:"挼";s:2:"Ѿ";s:3:"挩";s:2:"ѿ";s:3:"捁";s:2:"";s:3:"挴";s:2:"";s:3:"捘";s:2:"";s:3:"捔";s:2:"";s:3:"捙";s:2:"";s:3:"挭";s:2:"";s:3:"捇";s:2:"";s:3:"挳";s:2:"";s:3:"捚";s:2:"";s:3:"捑";s:2:"";s:3:"挸";s:2:"";s:3:"捗";s:2:"";s:3:"捀";s:2:"";s:3:"捈";s:2:"";s:3:"敊";s:2:"";s:3:"敆";s:2:"";s:3:"旆";s:2:"";s:3:"旃";s:2:"";s:3:"旄";s:2:"";s:3:"旂";s:2:"";s:3:"晊";s:2:"";s:3:"晟";s:2:"";s:3:"晇";s:2:"";s:3:"晑";s:2:"";s:3:"朒";s:2:"";s:3:"朓";s:2:"";s:3:"栟";s:2:"";s:3:"栚";s:2:"";s:3:"桉";s:2:"";s:3:"栲";s:2:"";s:3:"栳";s:2:"";s:3:"栻";s:2:"";s:3:"桋";s:2:"";s:3:"桏";s:2:"";s:3:"栖";s:2:"";s:3:"栱";s:2:"";s:3:"栜";s:2:"";s:3:"栵";s:2:"";s:3:"栫";s:2:"";s:3:"栭";s:2:"";s:3:"栯";s:2:"";s:3:"桎";s:2:"";s:3:"桄";s:2:"";s:3:"栴";s:2:"";s:3:"栝";s:2:"";s:3:"栒";s:2:"";s:3:"栔";s:2:"";s:3:"栦";s:2:"";s:3:"栨";s:2:"";s:3:"栮";s:2:"";s:3:"桍";s:2:"";s:3:"栺";s:2:"";s:3:"栥";s:2:"";s:3:"栠";s:2:"";s:3:"欬";s:2:"";s:3:"欯";s:2:"";s:3:"欭";s:2:"";s:3:"欱";s:2:"";s:3:"欴";s:2:"";s:3:"歭";s:2:"";s:3:"肂";s:2:"";s:3:"殈";s:2:"";s:3:"毦";s:2:"";s:3:"毤";s:2:"@";s:3:"毨";s:2:"A";s:3:"毣";s:2:"B";s:3:"毢";s:2:"C";s:3:"毧";s:2:"D";s:3:"氥";s:2:"E";s:3:"浺";s:2:"F";s:3:"浣";s:2:"G";s:3:"浤";s:2:"H";s:3:"浶";s:2:"I";s:3:"洍";s:2:"J";s:3:"浡";s:2:"K";s:3:"涒";s:2:"L";s:3:"浘";s:2:"M";s:3:"浢";s:2:"N";s:3:"浭";s:2:"O";s:3:"浯";s:2:"P";s:3:"涑";s:2:"Q";s:3:"涍";s:2:"R";s:3:"淯";s:2:"S";s:3:"浿";s:2:"T";s:3:"涆";s:2:"U";s:3:"浞";s:2:"V";s:3:"浧";s:2:"W";s:3:"浠";s:2:"X";s:3:"涗";s:2:"Y";s:3:"浰";s:2:"Z";s:3:"浼";s:2:"[";s:3:"浟";s:2:"\";s:3:"涂";s:2:"]";s:3:"涘";s:2:"^";s:3:"洯";s:2:"_";s:3:"浨";s:2:"`";s:3:"涋";s:2:"a";s:3:"浾";s:2:"b";s:3:"涀";s:2:"c";s:3:"涄";s:2:"d";s:3:"洖";s:2:"e";s:3:"涃";s:2:"f";s:3:"浻";s:2:"g";s:3:"浽";s:2:"h";s:3:"浵";s:2:"i";s:3:"涐";s:2:"j";s:3:"烜";s:2:"k";s:3:"烓";s:2:"l";s:3:"烑";s:2:"m";s:3:"烝";s:2:"n";s:3:"烋";s:2:"o";s:3:"缹";s:2:"p";s:3:"烢";s:2:"q";s:3:"烗";s:2:"r";s:3:"烒";s:2:"s";s:3:"烞";s:2:"t";s:3:"烠";s:2:"u";s:3:"烔";s:2:"v";s:3:"烍";s:2:"w";s:3:"烅";s:2:"x";s:3:"烆";s:2:"y";s:3:"烇";s:2:"z";s:3:"烚";s:2:"{";s:3:"烎";s:2:"|";s:3:"烡";s:2:"}";s:3:"牂";s:2:"~";s:3:"牸";s:2:"ҡ";s:3:"牷";s:2:"Ң";s:3:"牶";s:2:"ң";s:3:"猀";s:2:"Ҥ";s:3:"狺";s:2:"ҥ";s:3:"狴";s:2:"Ҧ";s:3:"狾";s:2:"ҧ";s:3:"狶";s:2:"Ҩ";s:3:"狳";s:2:"ҩ";s:3:"狻";s:2:"Ҫ";s:3:"猁";s:2:"ҫ";s:3:"珓";s:2:"Ҭ";s:3:"珙";s:2:"ҭ";s:3:"珥";s:2:"Ү";s:3:"珖";s:2:"ү";s:3:"玼";s:2:"Ұ";s:3:"珧";s:2:"ұ";s:3:"珣";s:2:"Ҳ";s:3:"珩";s:2:"ҳ";s:3:"珜";s:2:"Ҵ";s:3:"珒";s:2:"ҵ";s:3:"珛";s:2:"Ҷ";s:3:"珔";s:2:"ҷ";s:3:"珝";s:2:"Ҹ";s:3:"珚";s:2:"ҹ";s:3:"珗";s:2:"Һ";s:3:"珘";s:2:"һ";s:3:"珨";s:2:"Ҽ";s:3:"瓞";s:2:"ҽ";s:3:"瓟";s:2:"Ҿ";s:3:"瓴";s:2:"ҿ";s:3:"瓵";s:2:"";s:3:"甡";s:2:"";s:3:"畛";s:2:"";s:3:"畟";s:2:"";s:3:"疰";s:2:"";s:3:"痁";s:2:"";s:3:"疻";s:2:"";s:3:"痄";s:2:"";s:3:"痀";s:2:"";s:3:"疿";s:2:"";s:3:"疶";s:2:"";s:3:"疺";s:2:"";s:3:"皊";s:2:"";s:3:"盉";s:2:"";s:3:"眝";s:2:"";s:3:"眛";s:2:"";s:3:"眐";s:2:"";s:3:"眓";s:2:"";s:3:"眒";s:2:"";s:3:"眣";s:2:"";s:3:"眑";s:2:"";s:3:"眕";s:2:"";s:3:"眙";s:2:"";s:3:"眚";s:2:"";s:3:"眢";s:2:"";s:3:"眧";s:2:"";s:3:"砣";s:2:"";s:3:"砬";s:2:"";s:3:"砢";s:2:"";s:3:"砵";s:2:"";s:3:"砯";s:2:"";s:3:"砨";s:2:"";s:3:"砮";s:2:"";s:3:"砫";s:2:"";s:3:"砡";s:2:"";s:3:"砩";s:2:"";s:3:"砳";s:2:"";s:3:"砪";s:2:"";s:3:"砱";s:2:"";s:3:"祔";s:2:"";s:3:"祛";s:2:"";s:3:"祏";s:2:"";s:3:"祜";s:2:"";s:3:"祓";s:2:"";s:3:"祒";s:2:"";s:3:"祑";s:2:"";s:3:"秫";s:2:"";s:3:"秬";s:2:"";s:3:"秠";s:2:"";s:3:"秮";s:2:"";s:3:"秭";s:2:"";s:3:"秪";s:2:"";s:3:"秜";s:2:"";s:3:"秞";s:2:"";s:3:"秝";s:2:"";s:3:"窆";s:2:"";s:3:"窉";s:2:"";s:3:"窅";s:2:"";s:3:"窋";s:2:"";s:3:"窌";s:2:"";s:3:"窊";s:2:"";s:3:"窇";s:2:"";s:3:"竘";s:2:"";s:3:"笐";s:2:"@";s:3:"笄";s:2:"A";s:3:"笓";s:2:"B";s:3:"笅";s:2:"C";s:3:"笏";s:2:"D";s:3:"笈";s:2:"E";s:3:"笊";s:2:"F";s:3:"笎";s:2:"G";s:3:"笉";s:2:"H";s:3:"笒";s:2:"I";s:3:"粄";s:2:"J";s:3:"粑";s:2:"K";s:3:"粊";s:2:"L";s:3:"粌";s:2:"M";s:3:"粈";s:2:"N";s:3:"粍";s:2:"O";s:3:"粅";s:2:"P";s:3:"紞";s:2:"Q";s:3:"紝";s:2:"R";s:3:"紑";s:2:"S";s:3:"紎";s:2:"T";s:3:"紘";s:2:"U";s:3:"紖";s:2:"V";s:3:"紓";s:2:"W";s:3:"紟";s:2:"X";s:3:"紒";s:2:"Y";s:3:"紏";s:2:"Z";s:3:"紌";s:2:"[";s:3:"罜";s:2:"\";s:3:"罡";s:2:"]";s:3:"罞";s:2:"^";s:3:"罠";s:2:"_";s:3:"罝";s:2:"`";s:3:"罛";s:2:"a";s:3:"羖";s:2:"b";s:3:"羒";s:2:"c";s:3:"翃";s:2:"d";s:3:"翂";s:2:"e";s:3:"翀";s:2:"f";s:3:"耖";s:2:"g";s:3:"耾";s:2:"h";s:3:"耹";s:2:"i";s:3:"胺";s:2:"j";s:3:"胲";s:2:"k";s:3:"胹";s:2:"l";s:3:"胵";s:2:"m";s:3:"脁";s:2:"n";s:3:"胻";s:2:"o";s:3:"脀";s:2:"p";s:3:"舁";s:2:"q";s:3:"舯";s:2:"r";s:3:"舥";s:2:"s";s:3:"茳";s:2:"t";s:3:"茭";s:2:"u";s:3:"荄";s:2:"v";s:3:"茙";s:2:"w";s:3:"荑";s:2:"x";s:3:"茥";s:2:"y";s:3:"荖";s:2:"z";s:3:"茿";s:2:"{";s:3:"荁";s:2:"|";s:3:"茦";s:2:"}";s:3:"茜";s:2:"~";s:3:"茢";s:2:"ӡ";s:3:"荂";s:2:"Ӣ";s:3:"荎";s:2:"ӣ";s:3:"茛";s:2:"Ӥ";s:3:"茪";s:2:"ӥ";s:3:"茈";s:2:"Ӧ";s:3:"茼";s:2:"ӧ";s:3:"荍";s:2:"Ө";s:3:"茖";s:2:"ө";s:3:"茤";s:2:"Ӫ";s:3:"茠";s:2:"ӫ";s:3:"茷";s:2:"Ӭ";s:3:"茯";s:2:"ӭ";s:3:"茩";s:2:"Ӯ";s:3:"荇";s:2:"ӯ";s:3:"荅";s:2:"Ӱ";s:3:"荌";s:2:"ӱ";s:3:"荓";s:2:"Ӳ";s:3:"茞";s:2:"ӳ";s:3:"茬";s:2:"Ӵ";s:3:"荋";s:2:"ӵ";s:3:"茧";s:2:"Ӷ";s:3:"荈";s:2:"ӷ";s:3:"虓";s:2:"Ӹ";s:3:"虒";s:2:"ӹ";s:3:"蚢";s:2:"Ӻ";s:3:"蚨";s:2:"ӻ";s:3:"蚖";s:2:"Ӽ";s:3:"蚍";s:2:"ӽ";s:3:"蚑";s:2:"Ӿ";s:3:"蚞";s:2:"ӿ";s:3:"蚇";s:2:"";s:3:"蚗";s:2:"";s:3:"蚆";s:2:"";s:3:"蚋";s:2:"";s:3:"蚚";s:2:"";s:3:"蚅";s:2:"";s:3:"蚥";s:2:"";s:3:"蚙";s:2:"";s:3:"蚡";s:2:"";s:3:"蚧";s:2:"";s:3:"蚕";s:2:"";s:3:"蚘";s:2:"";s:3:"蚎";s:2:"";s:3:"蚝";s:2:"";s:3:"蚐";s:2:"";s:3:"蚔";s:2:"";s:3:"衃";s:2:"";s:3:"衄";s:2:"";s:3:"衭";s:2:"";s:3:"衵";s:2:"";s:3:"衶";s:2:"";s:3:"衲";s:2:"";s:3:"袀";s:2:"";s:3:"衱";s:2:"";s:3:"衿";s:2:"";s:3:"衯";s:2:"";s:3:"袃";s:2:"";s:3:"衾";s:2:"";s:3:"衴";s:2:"";s:3:"衼";s:2:"";s:3:"訒";s:2:"";s:3:"豇";s:2:"";s:3:"豗";s:2:"";s:3:"豻";s:2:"";s:3:"貤";s:2:"";s:3:"貣";s:2:"";s:3:"赶";s:2:"";s:3:"赸";s:2:"";s:3:"趵";s:2:"";s:3:"趷";s:2:"";s:3:"趶";s:2:"";s:3:"軑";s:2:"";s:3:"軓";s:2:"";s:3:"迾";s:2:"";s:3:"迵";s:2:"";s:3:"适";s:2:"";s:3:"迿";s:2:"";s:3:"迻";s:2:"";s:3:"逄";s:2:"";s:3:"迼";s:2:"";s:3:"迶";s:2:"";s:3:"郖";s:2:"";s:3:"郠";s:2:"";s:3:"郙";s:2:"";s:3:"郚";s:2:"";s:3:"郣";s:2:"";s:3:"郟";s:2:"";s:3:"郥";s:2:"";s:3:"郘";s:2:"";s:3:"郛";s:2:"";s:3:"郗";s:2:"";s:3:"郜";s:2:"";s:3:"郤";s:2:"";s:3:"酐";s:2:"@";s:3:"酎";s:2:"A";s:3:"酏";s:2:"B";s:3:"釕";s:2:"C";s:3:"釢";s:2:"D";s:3:"釚";s:2:"E";s:3:"陜";s:2:"F";s:3:"陟";s:2:"G";s:3:"隼";s:2:"H";s:3:"飣";s:2:"I";s:3:"髟";s:2:"J";s:3:"鬯";s:2:"K";s:3:"乿";s:2:"L";s:3:"偰";s:2:"M";s:3:"偪";s:2:"N";s:3:"偡";s:2:"O";s:3:"偞";s:2:"P";s:3:"偠";s:2:"Q";s:3:"偓";s:2:"R";s:3:"偋";s:2:"S";s:3:"偝";s:2:"T";s:3:"偲";s:2:"U";s:3:"偈";s:2:"V";s:3:"偍";s:2:"W";s:3:"偁";s:2:"X";s:3:"偛";s:2:"Y";s:3:"偊";s:2:"Z";s:3:"偢";s:2:"[";s:3:"倕";s:2:"\";s:3:"偅";s:2:"]";s:3:"偟";s:2:"^";s:3:"偩";s:2:"_";s:3:"偫";s:2:"`";s:3:"偣";s:2:"a";s:3:"偤";s:2:"b";s:3:"偆";s:2:"c";s:3:"偀";s:2:"d";s:3:"偮";s:2:"e";s:3:"偳";s:2:"f";s:3:"偗";s:2:"g";s:3:"偑";s:2:"h";s:3:"凐";s:2:"i";s:3:"剫";s:2:"j";s:3:"剭";s:2:"k";s:3:"剬";s:2:"l";s:3:"剮";s:2:"m";s:3:"勖";s:2:"n";s:3:"勓";s:2:"o";s:3:"匭";s:2:"p";s:3:"厜";s:2:"q";s:3:"啵";s:2:"r";s:3:"啶";s:2:"s";s:3:"唼";s:2:"t";s:3:"啍";s:2:"u";s:3:"啐";s:2:"v";s:3:"唴";s:2:"w";s:3:"唪";s:2:"x";s:3:"啑";s:2:"y";s:3:"啢";s:2:"z";s:3:"唶";s:2:"{";s:3:"唵";s:2:"|";s:3:"唰";s:2:"}";s:3:"啒";s:2:"~";s:3:"啅";s:2:"ԡ";s:3:"唌";s:2:"Ԣ";s:3:"唲";s:2:"ԣ";s:3:"啥";s:2:"Ԥ";s:3:"啎";s:2:"ԥ";s:3:"唹";s:2:"Ԧ";s:3:"啈";s:2:"ԧ";s:3:"唭";s:2:"Ԩ";s:3:"唻";s:2:"ԩ";s:3:"啀";s:2:"Ԫ";s:3:"啋";s:2:"ԫ";s:3:"圊";s:2:"Ԭ";s:3:"圇";s:2:"ԭ";s:3:"埻";s:2:"Ԯ";s:3:"堔";s:2:"ԯ";s:3:"埢";s:2:"԰";s:3:"埶";s:2:"Ա";s:3:"埜";s:2:"Բ";s:3:"埴";s:2:"Գ";s:3:"堀";s:2:"Դ";s:3:"埭";s:2:"Ե";s:3:"埽";s:2:"Զ";s:3:"堈";s:2:"Է";s:3:"埸";s:2:"Ը";s:3:"堋";s:2:"Թ";s:3:"埳";s:2:"Ժ";s:3:"埏";s:2:"Ի";s:3:"堇";s:2:"Լ";s:3:"埮";s:2:"Խ";s:3:"埣";s:2:"Ծ";s:3:"埲";s:2:"Կ";s:3:"埥";s:2:"";s:3:"埬";s:2:"";s:3:"埡";s:2:"";s:3:"堎";s:2:"";s:3:"埼";s:2:"";s:3:"堐";s:2:"";s:3:"埧";s:2:"";s:3:"堁";s:2:"";s:3:"堌";s:2:"";s:3:"埱";s:2:"";s:3:"埩";s:2:"";s:3:"埰";s:2:"";s:3:"堍";s:2:"";s:3:"堄";s:2:"";s:3:"奜";s:2:"";s:3:"婠";s:2:"";s:3:"婘";s:2:"";s:3:"婕";s:2:"";s:3:"婧";s:2:"";s:3:"婞";s:2:"";s:3:"娸";s:2:"";s:3:"娵";s:2:"";s:3:"婭";s:2:"";s:3:"婐";s:2:"";s:3:"婟";s:2:"";s:3:"婥";s:2:"";s:3:"婬";s:2:"";s:3:"婓";s:2:"";s:3:"婤";s:2:"";s:3:"婗";s:2:"";s:3:"婃";s:2:"";s:3:"婝";s:2:"";s:3:"婒";s:2:"";s:3:"婄";s:2:"";s:3:"婛";s:2:"";s:3:"婈";s:2:"";s:3:"媎";s:2:"";s:3:"娾";s:2:"";s:3:"婍";s:2:"";s:3:"娹";s:2:"";s:3:"婌";s:2:"";s:3:"婰";s:2:"";s:3:"婩";s:2:"";s:3:"婇";s:2:"";s:3:"婑";s:2:"";s:3:"婖";s:2:"";s:3:"婂";s:2:"";s:3:"婜";s:2:"";s:3:"孲";s:2:"";s:3:"孮";s:2:"";s:3:"寁";s:2:"";s:3:"寀";s:2:"";s:3:"屙";s:2:"";s:3:"崞";s:2:"";s:3:"崋";s:2:"";s:3:"崝";s:2:"";s:3:"崚";s:2:"";s:3:"崠";s:2:"";s:3:"崌";s:2:"";s:3:"崨";s:2:"";s:3:"崍";s:2:"";s:3:"崦";s:2:"";s:3:"崥";s:2:"";s:3:"崏";s:2:"@";s:3:"崰";s:2:"A";s:3:"崒";s:2:"B";s:3:"崣";s:2:"C";s:3:"崟";s:2:"D";s:3:"崮";s:2:"E";s:3:"帾";s:2:"F";s:3:"帴";s:2:"G";s:3:"庱";s:2:"H";s:3:"庴";s:2:"I";s:3:"庹";s:2:"J";s:3:"庲";s:2:"K";s:3:"庳";s:2:"L";s:3:"弶";s:2:"M";s:3:"弸";s:2:"N";s:3:"徛";s:2:"O";s:3:"徖";s:2:"P";s:3:"徟";s:2:"Q";s:3:"悊";s:2:"R";s:3:"悐";s:2:"S";s:3:"悆";s:2:"T";s:3:"悾";s:2:"U";s:3:"悰";s:2:"V";s:3:"悺";s:2:"W";s:3:"惓";s:2:"X";s:3:"惔";s:2:"Y";s:3:"惏";s:2:"Z";s:3:"惤";s:2:"[";s:3:"惙";s:2:"\";s:3:"惝";s:2:"]";s:3:"惈";s:2:"^";s:3:"悱";s:2:"_";s:3:"惛";s:2:"`";s:3:"悷";s:2:"a";s:3:"惊";s:2:"b";s:3:"悿";s:2:"c";s:3:"惃";s:2:"d";s:3:"惍";s:2:"e";s:3:"惀";s:2:"f";s:3:"挲";s:2:"g";s:3:"捥";s:2:"h";s:3:"掊";s:2:"i";s:3:"掂";s:2:"j";s:3:"捽";s:2:"k";s:3:"掽";s:2:"l";s:3:"掞";s:2:"m";s:3:"掭";s:2:"n";s:3:"掝";s:2:"o";s:3:"掗";s:2:"p";s:3:"掫";s:2:"q";s:3:"掎";s:2:"r";s:3:"捯";s:2:"s";s:3:"掇";s:2:"t";s:3:"掐";s:2:"u";s:3:"据";s:2:"v";s:3:"掯";s:2:"w";s:3:"捵";s:2:"x";s:3:"掜";s:2:"y";s:3:"捭";s:2:"z";s:3:"掮";s:2:"{";s:3:"捼";s:2:"|";s:3:"掤";s:2:"}";s:3:"挻";s:2:"~";s:3:"掟";s:2:"ա";s:3:"捸";s:2:"բ";s:3:"掅";s:2:"գ";s:3:"掁";s:2:"դ";s:3:"掑";s:2:"ե";s:3:"掍";s:2:"զ";s:3:"捰";s:2:"է";s:3:"敓";s:2:"ը";s:3:"旍";s:2:"թ";s:3:"晥";s:2:"ժ";s:3:"晡";s:2:"ի";s:3:"晛";s:2:"լ";s:3:"晙";s:2:"խ";s:3:"晜";s:2:"ծ";s:3:"晢";s:2:"կ";s:3:"朘";s:2:"հ";s:3:"桹";s:2:"ձ";s:3:"梇";s:2:"ղ";s:3:"梐";s:2:"ճ";s:3:"梜";s:2:"մ";s:3:"桭";s:2:"յ";s:3:"桮";s:2:"ն";s:3:"梮";s:2:"շ";s:3:"梫";s:2:"ո";s:3:"楖";s:2:"չ";s:3:"桯";s:2:"պ";s:3:"梣";s:2:"ջ";s:3:"梬";s:2:"ռ";s:3:"梩";s:2:"ս";s:3:"桵";s:2:"վ";s:3:"桴";s:2:"տ";s:3:"梲";s:2:"";s:3:"梏";s:2:"";s:3:"桷";s:2:"";s:3:"梒";s:2:"";s:3:"桼";s:2:"";s:3:"桫";s:2:"";s:3:"桲";s:2:"";s:3:"梪";s:2:"";s:3:"梀";s:2:"";s:3:"桱";s:2:"";s:3:"桾";s:2:"";s:3:"梛";s:2:"";s:3:"梖";s:2:"";s:3:"梋";s:2:"";s:3:"梠";s:2:"";s:3:"梉";s:2:"";s:3:"梤";s:2:"";s:3:"桸";s:2:"";s:3:"桻";s:2:"";s:3:"梑";s:2:"";s:3:"梌";s:2:"";s:3:"梊";s:2:"";s:3:"桽";s:2:"";s:3:"欶";s:2:"";s:3:"欳";s:2:"";s:3:"欷";s:2:"";s:3:"欸";s:2:"";s:3:"殑";s:2:"";s:3:"殏";s:2:"";s:3:"殍";s:2:"";s:3:"殎";s:2:"";s:3:"殌";s:2:"";s:3:"氪";s:2:"";s:3:"淀";s:2:"";s:3:"涫";s:2:"";s:3:"涴";s:2:"";s:3:"涳";s:2:"";s:3:"湴";s:2:"";s:3:"涬";s:2:"";s:3:"淩";s:2:"";s:3:"淢";s:2:"";s:3:"涷";s:2:"";s:3:"淶";s:2:"";s:3:"淔";s:2:"";s:3:"渀";s:2:"";s:3:"淈";s:2:"";s:3:"淠";s:2:"";s:3:"淟";s:2:"";s:3:"淖";s:2:"";s:3:"涾";s:2:"";s:3:"淥";s:2:"";s:3:"淜";s:2:"";s:3:"淝";s:2:"";s:3:"淛";s:2:"";s:3:"淴";s:2:"";s:3:"淊";s:2:"";s:3:"涽";s:2:"";s:3:"淭";s:2:"";s:3:"淰";s:2:"";s:3:"涺";s:2:"";s:3:"淕";s:2:"";s:3:"淂";s:2:"";s:3:"淏";s:2:"";s:3:"淉";s:2:"@";s:3:"淐";s:2:"A";s:3:"淲";s:2:"B";s:3:"淓";s:2:"C";s:3:"淽";s:2:"D";s:3:"淗";s:2:"E";s:3:"淍";s:2:"F";s:3:"淣";s:2:"G";s:3:"涻";s:2:"H";s:3:"烺";s:2:"I";s:3:"焍";s:2:"J";s:3:"烷";s:2:"K";s:3:"焗";s:2:"L";s:3:"烴";s:2:"M";s:3:"焌";s:2:"N";s:3:"烰";s:2:"O";s:3:"焄";s:2:"P";s:3:"烳";s:2:"Q";s:3:"焐";s:2:"R";s:3:"烼";s:2:"S";s:3:"烿";s:2:"T";s:3:"焆";s:2:"U";s:3:"焓";s:2:"V";s:3:"焀";s:2:"W";s:3:"烸";s:2:"X";s:3:"烶";s:2:"Y";s:3:"焋";s:2:"Z";s:3:"焂";s:2:"[";s:3:"焎";s:2:"\";s:3:"牾";s:2:"]";s:3:"牻";s:2:"^";s:3:"牼";s:2:"_";s:3:"牿";s:2:"`";s:3:"猝";s:2:"a";s:3:"猗";s:2:"b";s:3:"猇";s:2:"c";s:3:"猑";s:2:"d";s:3:"猘";s:2:"e";s:3:"猊";s:2:"f";s:3:"猈";s:2:"g";s:3:"狿";s:2:"h";s:3:"猏";s:2:"i";s:3:"猞";s:2:"j";s:3:"玈";s:2:"k";s:3:"珶";s:2:"l";s:3:"珸";s:2:"m";s:3:"珵";s:2:"n";s:3:"琄";s:2:"o";s:3:"琁";s:2:"p";s:3:"珽";s:2:"q";s:3:"琇";s:2:"r";s:3:"琀";s:2:"s";s:3:"珺";s:2:"t";s:3:"珼";s:2:"u";s:3:"珿";s:2:"v";s:3:"琌";s:2:"w";s:3:"琋";s:2:"x";s:3:"珴";s:2:"y";s:3:"琈";s:2:"z";s:3:"畤";s:2:"{";s:3:"畣";s:2:"|";s:3:"痎";s:2:"}";s:3:"痒";s:2:"~";s:3:"痏";s:2:"֡";s:3:"痋";s:2:"֢";s:3:"痌";s:2:"֣";s:3:"痑";s:2:"֤";s:3:"痐";s:2:"֥";s:3:"皏";s:2:"֦";s:3:"皉";s:2:"֧";s:3:"盓";s:2:"֨";s:3:"眹";s:2:"֩";s:3:"眯";s:2:"֪";s:3:"眭";s:2:"֫";s:3:"眱";s:2:"֬";s:3:"眲";s:2:"֭";s:3:"眴";s:2:"֮";s:3:"眳";s:2:"֯";s:3:"眽";s:2:"ְ";s:3:"眥";s:2:"ֱ";s:3:"眻";s:2:"ֲ";s:3:"眵";s:2:"ֳ";s:3:"硈";s:2:"ִ";s:3:"硒";s:2:"ֵ";s:3:"硉";s:2:"ֶ";s:3:"硍";s:2:"ַ";s:3:"硊";s:2:"ָ";s:3:"硌";s:2:"ֹ";s:3:"砦";s:2:"ֺ";s:3:"硅";s:2:"ֻ";s:3:"硐";s:2:"ּ";s:3:"祤";s:2:"ֽ";s:3:"祧";s:2:"־";s:3:"祩";s:2:"ֿ";s:3:"祪";s:2:"";s:3:"祣";s:2:"";s:3:"祫";s:2:"";s:3:"祡";s:2:"";s:3:"离";s:2:"";s:3:"秺";s:2:"";s:3:"秸";s:2:"";s:3:"秶";s:2:"";s:3:"秷";s:2:"";s:3:"窏";s:2:"";s:3:"窔";s:2:"";s:3:"窐";s:2:"";s:3:"笵";s:2:"";s:3:"筇";s:2:"";s:3:"笴";s:2:"";s:3:"笥";s:2:"";s:3:"笰";s:2:"";s:3:"笢";s:2:"";s:3:"笤";s:2:"";s:3:"笳";s:2:"";s:3:"笘";s:2:"";s:3:"笪";s:2:"";s:3:"笝";s:2:"";s:3:"笱";s:2:"";s:3:"笫";s:2:"";s:3:"笭";s:2:"";s:3:"笯";s:2:"";s:3:"笲";s:2:"";s:3:"笸";s:2:"";s:3:"笚";s:2:"";s:3:"笣";s:2:"";s:3:"粔";s:2:"";s:3:"粘";s:2:"";s:3:"粖";s:2:"";s:3:"粣";s:2:"";s:3:"紵";s:2:"";s:3:"紽";s:2:"";s:3:"紸";s:2:"";s:3:"紶";s:2:"";s:3:"紺";s:2:"";s:3:"絅";s:2:"";s:3:"紬";s:2:"";s:3:"紩";s:2:"";s:3:"絁";s:2:"";s:3:"絇";s:2:"";s:3:"紾";s:2:"";s:3:"紿";s:2:"";s:3:"絊";s:2:"";s:3:"紻";s:2:"";s:3:"紨";s:2:"";s:3:"罣";s:2:"";s:3:"羕";s:2:"";s:3:"羜";s:2:"";s:3:"羝";s:2:"";s:3:"羛";s:2:"";s:3:"翊";s:2:"";s:3:"翋";s:2:"";s:3:"翍";s:2:"";s:3:"翐";s:2:"";s:3:"翑";s:2:"";s:3:"翇";s:2:"";s:3:"翏";s:2:"";s:3:"翉";s:2:"";s:3:"耟";s:2:"@";s:3:"耞";s:2:"A";s:3:"耛";s:2:"B";s:3:"聇";s:2:"C";s:3:"聃";s:2:"D";s:3:"聈";s:2:"E";s:3:"脘";s:2:"F";s:3:"脥";s:2:"G";s:3:"脙";s:2:"H";s:3:"脛";s:2:"I";s:3:"脭";s:2:"J";s:3:"脟";s:2:"K";s:3:"脬";s:2:"L";s:3:"脞";s:2:"M";s:3:"脡";s:2:"N";s:3:"脕";s:2:"O";s:3:"脧";s:2:"P";s:3:"脝";s:2:"Q";s:3:"脢";s:2:"R";s:3:"舑";s:2:"S";s:3:"舸";s:2:"T";s:3:"舳";s:2:"U";s:3:"舺";s:2:"V";s:3:"舴";s:2:"W";s:3:"舲";s:2:"X";s:3:"艴";s:2:"Y";s:3:"莐";s:2:"Z";s:3:"莣";s:2:"[";s:3:"莨";s:2:"\";s:3:"莍";s:2:"]";s:3:"荺";s:2:"^";s:3:"荳";s:2:"_";s:3:"莤";s:2:"`";s:3:"荴";s:2:"a";s:3:"莏";s:2:"b";s:3:"莁";s:2:"c";s:3:"莕";s:2:"d";s:3:"莙";s:2:"e";s:3:"荵";s:2:"f";s:3:"莔";s:2:"g";s:3:"莩";s:2:"h";s:3:"荽";s:2:"i";s:3:"莃";s:2:"j";s:3:"莌";s:2:"k";s:3:"莝";s:2:"l";s:3:"莛";s:2:"m";s:3:"莪";s:2:"n";s:3:"莋";s:2:"o";s:3:"荾";s:2:"p";s:3:"莥";s:2:"q";s:3:"莯";s:2:"r";s:3:"莈";s:2:"s";s:3:"莗";s:2:"t";s:3:"莰";s:2:"u";s:3:"荿";s:2:"v";s:3:"莦";s:2:"w";s:3:"莇";s:2:"x";s:3:"莮";s:2:"y";s:3:"荶";s:2:"z";s:3:"莚";s:2:"{";s:3:"虙";s:2:"|";s:3:"虖";s:2:"}";s:3:"蚿";s:2:"~";s:3:"蚷";s:2:"ס";s:3:"蛂";s:2:"ע";s:3:"蛁";s:2:"ף";s:3:"蛅";s:2:"פ";s:3:"蚺";s:2:"ץ";s:3:"蚰";s:2:"צ";s:3:"蛈";s:2:"ק";s:3:"蚹";s:2:"ר";s:3:"蚳";s:2:"ש";s:3:"蚸";s:2:"ת";s:3:"蛌";s:2:"׫";s:3:"蚴";s:2:"׬";s:3:"蚻";s:2:"׭";s:3:"蚼";s:2:"׮";s:3:"蛃";s:2:"ׯ";s:3:"蚽";s:2:"װ";s:3:"蚾";s:2:"ױ";s:3:"衒";s:2:"ײ";s:3:"袉";s:2:"׳";s:3:"袕";s:2:"״";s:3:"袨";s:2:"׵";s:3:"袢";s:2:"׶";s:3:"袪";s:2:"׷";s:3:"袚";s:2:"׸";s:3:"袑";s:2:"׹";s:3:"袡";s:2:"׺";s:3:"袟";s:2:"׻";s:3:"袘";s:2:"׼";s:3:"袧";s:2:"׽";s:3:"袙";s:2:"׾";s:3:"袛";s:2:"׿";s:3:"袗";s:2:"";s:3:"袤";s:2:"";s:3:"袬";s:2:"";s:3:"袌";s:2:"";s:3:"袓";s:2:"";s:3:"袎";s:2:"";s:3:"覂";s:2:"";s:3:"觖";s:2:"";s:3:"觙";s:2:"";s:3:"觕";s:2:"";s:3:"訰";s:2:"";s:3:"訧";s:2:"";s:3:"訬";s:2:"";s:3:"訞";s:2:"";s:3:"谹";s:2:"";s:3:"谻";s:2:"";s:3:"豜";s:2:"";s:3:"豝";s:2:"";s:3:"豽";s:2:"";s:3:"貥";s:2:"";s:3:"赽";s:2:"";s:3:"赻";s:2:"";s:3:"赹";s:2:"";s:3:"趼";s:2:"";s:3:"跂";s:2:"";s:3:"趹";s:2:"";s:3:"趿";s:2:"";s:3:"跁";s:2:"";s:3:"軘";s:2:"";s:3:"軞";s:2:"";s:3:"軝";s:2:"";s:3:"軜";s:2:"";s:3:"軗";s:2:"";s:3:"軠";s:2:"";s:3:"軡";s:2:"";s:3:"逤";s:2:"";s:3:"逋";s:2:"";s:3:"逑";s:2:"";s:3:"逜";s:2:"";s:3:"逌";s:2:"";s:3:"逡";s:2:"";s:3:"郯";s:2:"";s:3:"郪";s:2:"";s:3:"郰";s:2:"";s:3:"郴";s:2:"";s:3:"郲";s:2:"";s:3:"郳";s:2:"";s:3:"郔";s:2:"";s:3:"郫";s:2:"";s:3:"郬";s:2:"";s:3:"郩";s:2:"";s:3:"酖";s:2:"";s:3:"酘";s:2:"";s:3:"酚";s:2:"";s:3:"酓";s:2:"";s:3:"酕";s:2:"";s:3:"釬";s:2:"";s:3:"釴";s:2:"";s:3:"釱";s:2:"";s:3:"釳";s:2:"";s:3:"釸";s:2:"";s:3:"釤";s:2:"";s:3:"釹";s:2:"";s:3:"釪";s:2:"@";s:3:"釫";s:2:"A";s:3:"釷";s:2:"B";s:3:"釨";s:2:"C";s:3:"釮";s:2:"D";s:3:"镺";s:2:"E";s:3:"閆";s:2:"F";s:3:"閈";s:2:"G";s:3:"陼";s:2:"H";s:3:"陭";s:2:"I";s:3:"陫";s:2:"J";s:3:"陱";s:2:"K";s:3:"陯";s:2:"L";s:3:"隿";s:2:"M";s:3:"靪";s:2:"N";s:3:"頄";s:2:"O";s:3:"飥";s:2:"P";s:3:"馗";s:2:"Q";s:3:"傛";s:2:"R";s:3:"傕";s:2:"S";s:3:"傔";s:2:"T";s:3:"傞";s:2:"U";s:3:"傋";s:2:"V";s:3:"傣";s:2:"W";s:3:"傃";s:2:"X";s:3:"傌";s:2:"Y";s:3:"傎";s:2:"Z";s:3:"傝";s:2:"[";s:3:"偨";s:2:"\";s:3:"傜";s:2:"]";s:3:"傒";s:2:"^";s:3:"傂";s:2:"_";s:3:"傇";s:2:"`";s:3:"兟";s:2:"a";s:3:"凔";s:2:"b";s:3:"匒";s:2:"c";s:3:"匑";s:2:"d";s:3:"厤";s:2:"e";s:3:"厧";s:2:"f";s:3:"喑";s:2:"g";s:3:"喨";s:2:"h";s:3:"喥";s:2:"i";s:3:"喭";s:2:"j";s:3:"啷";s:2:"k";s:3:"噅";s:2:"l";s:3:"喢";s:2:"m";s:3:"喓";s:2:"n";s:3:"喈";s:2:"o";s:3:"喏";s:2:"p";s:3:"喵";s:2:"q";s:3:"喁";s:2:"r";s:3:"喣";s:2:"s";s:3:"喒";s:2:"t";s:3:"喤";s:2:"u";s:3:"啽";s:2:"v";s:3:"喌";s:2:"w";s:3:"喦";s:2:"x";s:3:"啿";s:2:"y";s:3:"喕";s:2:"z";s:3:"喡";s:2:"{";s:3:"喎";s:2:"|";s:3:"圌";s:2:"}";s:3:"堩";s:2:"~";s:3:"堷";s:2:"ء";s:3:"堙";s:2:"آ";s:3:"堞";s:2:"أ";s:3:"堧";s:2:"ؤ";s:3:"堣";s:2:"إ";s:3:"堨";s:2:"ئ";s:3:"埵";s:2:"ا";s:3:"塈";s:2:"ب";s:3:"堥";s:2:"ة";s:3:"堜";s:2:"ت";s:3:"堛";s:2:"ث";s:3:"堳";s:2:"ج";s:3:"堿";s:2:"ح";s:3:"堶";s:2:"خ";s:3:"堮";s:2:"د";s:3:"堹";s:2:"ذ";s:3:"堸";s:2:"ر";s:3:"堭";s:2:"ز";s:3:"堬";s:2:"س";s:3:"堻";s:2:"ش";s:3:"奡";s:2:"ص";s:3:"媯";s:2:"ض";s:3:"媔";s:2:"ط";s:3:"媟";s:2:"ظ";s:3:"婺";s:2:"ع";s:3:"媢";s:2:"غ";s:3:"媞";s:2:"ػ";s:3:"婸";s:2:"ؼ";s:3:"媦";s:2:"ؽ";s:3:"婼";s:2:"ؾ";s:3:"媥";s:2:"ؿ";s:3:"媬";s:2:"";s:3:"媕";s:2:"";s:3:"媮";s:2:"";s:3:"娷";s:2:"";s:3:"媄";s:2:"";s:3:"媊";s:2:"";s:3:"媗";s:2:"";s:3:"媃";s:2:"";s:3:"媋";s:2:"";s:3:"媩";s:2:"";s:3:"婻";s:2:"";s:3:"婽";s:2:"";s:3:"媌";s:2:"";s:3:"媜";s:2:"";s:3:"媏";s:2:"";s:3:"媓";s:2:"";s:3:"媝";s:2:"";s:3:"寪";s:2:"";s:3:"寍";s:2:"";s:3:"寋";s:2:"";s:3:"寔";s:2:"";s:3:"寑";s:2:"";s:3:"寊";s:2:"";s:3:"寎";s:2:"";s:3:"尌";s:2:"";s:3:"尰";s:2:"";s:3:"崷";s:2:"";s:3:"嵃";s:2:"";s:3:"嵫";s:2:"";s:3:"嵁";s:2:"";s:3:"嵋";s:2:"";s:3:"崿";s:2:"";s:3:"崵";s:2:"";s:3:"嵑";s:2:"";s:3:"嵎";s:2:"";s:3:"嵕";s:2:"";s:3:"崳";s:2:"";s:3:"崺";s:2:"";s:3:"嵒";s:2:"";s:3:"崽";s:2:"";s:3:"崱";s:2:"";s:3:"嵙";s:2:"";s:3:"嵂";s:2:"";s:3:"崹";s:2:"";s:3:"嵉";s:2:"";s:3:"崸";s:2:"";s:3:"崼";s:2:"";s:3:"崲";s:2:"";s:3:"崶";s:2:"";s:3:"嵀";s:2:"";s:3:"嵅";s:2:"";s:3:"幄";s:2:"";s:3:"幁";s:2:"";s:3:"彘";s:2:"";s:3:"徦";s:2:"";s:3:"徥";s:2:"";s:3:"徫";s:2:"";s:3:"惉";s:2:"";s:3:"悹";s:2:"";s:3:"惌";s:2:"";s:3:"惢";s:2:"";s:3:"惎";s:2:"";s:3:"惄";s:2:"";s:3:"愔";s:2:"@";s:3:"惲";s:2:"A";s:3:"愊";s:2:"B";s:3:"愖";s:2:"C";s:3:"愅";s:2:"D";s:3:"惵";s:2:"E";s:3:"愓";s:2:"F";s:3:"惸";s:2:"G";s:3:"惼";s:2:"H";s:3:"惾";s:2:"I";s:3:"惁";s:2:"J";s:3:"愃";s:2:"K";s:3:"愘";s:2:"L";s:3:"愝";s:2:"M";s:3:"愐";s:2:"N";s:3:"惿";s:2:"O";s:3:"愄";s:2:"P";s:3:"愋";s:2:"Q";s:3:"扊";s:2:"R";s:3:"掔";s:2:"S";s:3:"掱";s:2:"T";s:3:"掰";s:2:"U";s:3:"揎";s:2:"V";s:3:"揥";s:2:"W";s:3:"揨";s:2:"X";s:3:"揯";s:2:"Y";s:3:"揃";s:2:"Z";s:3:"撝";s:2:"[";s:3:"揳";s:2:"\";s:3:"揊";s:2:"]";s:3:"揠";s:2:"^";s:3:"揶";s:2:"_";s:3:"揕";s:2:"`";s:3:"揲";s:2:"a";s:3:"揵";s:2:"b";s:3:"摡";s:2:"c";s:3:"揟";s:2:"d";s:3:"掾";s:2:"e";s:3:"揝";s:2:"f";s:3:"揜";s:2:"g";s:3:"揄";s:2:"h";s:3:"揘";s:2:"i";s:3:"揓";s:2:"j";s:3:"揂";s:2:"k";s:3:"揇";s:2:"l";s:3:"揌";s:2:"m";s:3:"揋";s:2:"n";s:3:"揈";s:2:"o";s:3:"揰";s:2:"p";s:3:"揗";s:2:"q";s:3:"揙";s:2:"r";s:3:"攲";s:2:"s";s:3:"敧";s:2:"t";s:3:"敪";s:2:"u";s:3:"敤";s:2:"v";s:3:"敜";s:2:"w";s:3:"敨";s:2:"x";s:3:"敥";s:2:"y";s:3:"斌";s:2:"z";s:3:"斝";s:2:"{";s:3:"斞";s:2:"|";s:3:"斮";s:2:"}";s:3:"旐";s:2:"~";s:3:"旒";s:2:"١";s:3:"晼";s:2:"٢";s:3:"晬";s:2:"٣";s:3:"晻";s:2:"٤";s:3:"暀";s:2:"٥";s:3:"晱";s:2:"٦";s:3:"晹";s:2:"٧";s:3:"晪";s:2:"٨";s:3:"晲";s:2:"٩";s:3:"朁";s:2:"٪";s:3:"椌";s:2:"٫";s:3:"棓";s:2:"٬";s:3:"椄";s:2:"٭";s:3:"棜";s:2:"ٮ";s:3:"椪";s:2:"ٯ";s:3:"棬";s:2:"ٰ";s:3:"棪";s:2:"ٱ";s:3:"棱";s:2:"ٲ";s:3:"椏";s:2:"ٳ";s:3:"棖";s:2:"ٴ";s:3:"棷";s:2:"ٵ";s:3:"棫";s:2:"ٶ";s:3:"棤";s:2:"ٷ";s:3:"棶";s:2:"ٸ";s:3:"椓";s:2:"ٹ";s:3:"椐";s:2:"ٺ";s:3:"棳";s:2:"ٻ";s:3:"棡";s:2:"ټ";s:3:"椇";s:2:"ٽ";s:3:"棌";s:2:"پ";s:3:"椈";s:2:"ٿ";s:3:"楰";s:2:"";s:3:"梴";s:2:"";s:3:"椑";s:2:"";s:3:"棯";s:2:"";s:3:"棆";s:2:"";s:3:"椔";s:2:"";s:3:"棸";s:2:"";s:3:"棐";s:2:"";s:3:"棽";s:2:"";s:3:"棼";s:2:"";s:3:"棨";s:2:"";s:3:"椋";s:2:"";s:3:"椊";s:2:"";s:3:"椗";s:2:"";s:3:"棎";s:2:"";s:3:"棈";s:2:"";s:3:"棝";s:2:"";s:3:"棞";s:2:"";s:3:"棦";s:2:"";s:3:"棴";s:2:"";s:3:"棑";s:2:"";s:3:"椆";s:2:"";s:3:"棔";s:2:"";s:3:"棩";s:2:"";s:3:"椕";s:2:"";s:3:"椥";s:2:"";s:3:"棇";s:2:"";s:3:"欹";s:2:"";s:3:"欻";s:2:"";s:3:"欿";s:2:"";s:3:"欼";s:2:"";s:3:"殔";s:2:"";s:3:"殗";s:2:"";s:3:"殙";s:2:"";s:3:"殕";s:2:"";s:3:"殽";s:2:"";s:3:"毰";s:2:"";s:3:"毲";s:2:"";s:3:"毳";s:2:"";s:3:"氰";s:2:"";s:3:"淼";s:2:"";s:3:"湆";s:2:"";s:3:"湇";s:2:"";s:3:"渟";s:2:"";s:3:"湉";s:2:"";s:3:"溈";s:2:"";s:3:"渼";s:2:"";s:3:"渽";s:2:"";s:3:"湅";s:2:"";s:3:"湢";s:2:"";s:3:"渫";s:2:"";s:3:"渿";s:2:"";s:3:"湁";s:2:"";s:3:"湝";s:2:"";s:3:"湳";s:2:"";s:3:"渜";s:2:"";s:3:"渳";s:2:"";s:3:"湋";s:2:"";s:3:"湀";s:2:"";s:3:"湑";s:2:"";s:3:"渻";s:2:"";s:3:"渃";s:2:"";s:3:"渮";s:2:"";s:3:"湞";s:2:"@";s:3:"湨";s:2:"A";s:3:"湜";s:2:"B";s:3:"湡";s:2:"C";s:3:"渱";s:2:"D";s:3:"渨";s:2:"E";s:3:"湠";s:2:"F";s:3:"湱";s:2:"G";s:3:"湫";s:2:"H";s:3:"渹";s:2:"I";s:3:"渢";s:2:"J";s:3:"渰";s:2:"K";s:3:"湓";s:2:"L";s:3:"湥";s:2:"M";s:3:"渧";s:2:"N";s:3:"湸";s:2:"O";s:3:"湤";s:2:"P";s:3:"湷";s:2:"Q";s:3:"湕";s:2:"R";s:3:"湹";s:2:"S";s:3:"湒";s:2:"T";s:3:"湦";s:2:"U";s:3:"渵";s:2:"V";s:3:"渶";s:2:"W";s:3:"湚";s:2:"X";s:3:"焠";s:2:"Y";s:3:"焞";s:2:"Z";s:3:"焯";s:2:"[";s:3:"烻";s:2:"\";s:3:"焮";s:2:"]";s:3:"焱";s:2:"^";s:3:"焣";s:2:"_";s:3:"焥";s:2:"`";s:3:"焢";s:2:"a";s:3:"焲";s:2:"b";s:3:"焟";s:2:"c";s:3:"焨";s:2:"d";s:3:"焺";s:2:"e";s:3:"焛";s:2:"f";s:3:"牋";s:2:"g";s:3:"牚";s:2:"h";s:3:"犈";s:2:"i";s:3:"犉";s:2:"j";s:3:"犆";s:2:"k";s:3:"犅";s:2:"l";s:3:"犋";s:2:"m";s:3:"猒";s:2:"n";s:3:"猋";s:2:"o";s:3:"猰";s:2:"p";s:3:"猢";s:2:"q";s:3:"猱";s:2:"r";s:3:"猳";s:2:"s";s:3:"猧";s:2:"t";s:3:"猲";s:2:"u";s:3:"猭";s:2:"v";s:3:"猦";s:2:"w";s:3:"猣";s:2:"x";s:3:"猵";s:2:"y";s:3:"猌";s:2:"z";s:3:"琮";s:2:"{";s:3:"琬";s:2:"|";s:3:"琰";s:2:"}";s:3:"琫";s:2:"~";s:3:"琖";s:2:"ڡ";s:3:"琚";s:2:"ڢ";s:3:"琡";s:2:"ڣ";s:3:"琭";s:2:"ڤ";s:3:"琱";s:2:"ڥ";s:3:"琤";s:2:"ڦ";s:3:"琣";s:2:"ڧ";s:3:"琝";s:2:"ڨ";s:3:"琩";s:2:"ک";s:3:"琠";s:2:"ڪ";s:3:"琲";s:2:"ګ";s:3:"瓻";s:2:"ڬ";s:3:"甯";s:2:"ڭ";s:3:"畯";s:2:"ڮ";s:3:"畬";s:2:"گ";s:3:"痧";s:2:"ڰ";s:3:"痚";s:2:"ڱ";s:3:"痡";s:2:"ڲ";s:3:"痦";s:2:"ڳ";s:3:"痝";s:2:"ڴ";s:3:"痟";s:2:"ڵ";s:3:"痤";s:2:"ڶ";s:3:"痗";s:2:"ڷ";s:3:"皕";s:2:"ڸ";s:3:"皒";s:2:"ڹ";s:3:"盚";s:2:"ں";s:3:"睆";s:2:"ڻ";s:3:"睇";s:2:"ڼ";s:3:"睄";s:2:"ڽ";s:3:"睍";s:2:"ھ";s:3:"睅";s:2:"ڿ";s:3:"睊";s:2:"";s:3:"睎";s:2:"";s:3:"睋";s:2:"";s:3:"睌";s:2:"";s:3:"矞";s:2:"";s:3:"矬";s:2:"";s:3:"硠";s:2:"";s:3:"硤";s:2:"";s:3:"硥";s:2:"";s:3:"硜";s:2:"";s:3:"硭";s:2:"";s:3:"硱";s:2:"";s:3:"硪";s:2:"";s:3:"确";s:2:"";s:3:"硰";s:2:"";s:3:"硩";s:2:"";s:3:"硨";s:2:"";s:3:"硞";s:2:"";s:3:"硢";s:2:"";s:3:"祴";s:2:"";s:3:"祳";s:2:"";s:3:"祲";s:2:"";s:3:"祰";s:2:"";s:3:"稂";s:2:"";s:3:"稊";s:2:"";s:3:"稃";s:2:"";s:3:"稌";s:2:"";s:3:"稄";s:2:"";s:3:"窙";s:2:"";s:3:"竦";s:2:"";s:3:"竤";s:2:"";s:3:"筊";s:2:"";s:3:"笻";s:2:"";s:3:"筄";s:2:"";s:3:"筈";s:2:"";s:3:"筌";s:2:"";s:3:"筎";s:2:"";s:3:"筀";s:2:"";s:3:"筘";s:2:"";s:3:"筅";s:2:"";s:3:"粢";s:2:"";s:3:"粞";s:2:"";s:3:"粨";s:2:"";s:3:"粡";s:2:"";s:3:"絘";s:2:"";s:3:"絯";s:2:"";s:3:"絣";s:2:"";s:3:"絓";s:2:"";s:3:"絖";s:2:"";s:3:"絧";s:2:"";s:3:"絪";s:2:"";s:3:"絏";s:2:"";s:3:"絭";s:2:"";s:3:"絜";s:2:"";s:3:"絫";s:2:"";s:3:"絒";s:2:"";s:3:"絔";s:2:"";s:3:"絩";s:2:"";s:3:"絑";s:2:"";s:3:"絟";s:2:"";s:3:"絎";s:2:"";s:3:"缾";s:2:"";s:3:"缿";s:2:"";s:3:"罥";s:2:"@";s:3:"罦";s:2:"A";s:3:"羢";s:2:"B";s:3:"羠";s:2:"C";s:3:"羡";s:2:"D";s:3:"翗";s:2:"E";s:3:"聑";s:2:"F";s:3:"聏";s:2:"G";s:3:"聐";s:2:"H";s:3:"胾";s:2:"I";s:3:"胔";s:2:"J";s:3:"腃";s:2:"K";s:3:"腊";s:2:"L";s:3:"腒";s:2:"M";s:3:"腏";s:2:"N";s:3:"腇";s:2:"O";s:3:"脽";s:2:"P";s:3:"腍";s:2:"Q";s:3:"脺";s:2:"R";s:3:"臦";s:2:"S";s:3:"臮";s:2:"T";s:3:"臷";s:2:"U";s:3:"臸";s:2:"V";s:3:"臹";s:2:"W";s:3:"舄";s:2:"X";s:3:"舼";s:2:"Y";s:3:"舽";s:2:"Z";s:3:"舿";s:2:"[";s:3:"艵";s:2:"\";s:3:"茻";s:2:"]";s:3:"菏";s:2:"^";s:3:"菹";s:2:"_";s:3:"萣";s:2:"`";s:3:"菀";s:2:"a";s:3:"菨";s:2:"b";s:3:"萒";s:2:"c";s:3:"菧";s:2:"d";s:3:"菤";s:2:"e";s:3:"菼";s:2:"f";s:3:"菶";s:2:"g";s:3:"萐";s:2:"h";s:3:"菆";s:2:"i";s:3:"菈";s:2:"j";s:3:"菫";s:2:"k";s:3:"菣";s:2:"l";s:3:"莿";s:2:"m";s:3:"萁";s:2:"n";s:3:"菝";s:2:"o";s:3:"菥";s:2:"p";s:3:"菘";s:2:"q";s:3:"菿";s:2:"r";s:3:"菡";s:2:"s";s:3:"菋";s:2:"t";s:3:"菎";s:2:"u";s:3:"菖";s:2:"v";s:3:"菵";s:2:"w";s:3:"菉";s:2:"x";s:3:"萉";s:2:"y";s:3:"萏";s:2:"z";s:3:"菞";s:2:"{";s:3:"萑";s:2:"|";s:3:"萆";s:2:"}";s:3:"菂";s:2:"~";s:3:"菳";s:2:"ۡ";s:3:"菕";s:2:"ۢ";s:3:"菺";s:2:"ۣ";s:3:"菇";s:2:"ۤ";s:3:"菑";s:2:"ۥ";s:3:"菪";s:2:"ۦ";s:3:"萓";s:2:"ۧ";s:3:"菃";s:2:"ۨ";s:3:"菬";s:2:"۩";s:3:"菮";s:2:"۪";s:3:"菄";s:2:"۫";s:3:"菻";s:2:"۬";s:3:"菗";s:2:"ۭ";s:3:"菢";s:2:"ۮ";s:3:"萛";s:2:"ۯ";s:3:"菛";s:2:"۰";s:3:"菾";s:2:"۱";s:3:"蛘";s:2:"۲";s:3:"蛢";s:2:"۳";s:3:"蛦";s:2:"۴";s:3:"蛓";s:2:"۵";s:3:"蛣";s:2:"۶";s:3:"蛚";s:2:"۷";s:3:"蛪";s:2:"۸";s:3:"蛝";s:2:"۹";s:3:"蛫";s:2:"ۺ";s:3:"蛜";s:2:"ۻ";s:3:"蛬";s:2:"ۼ";s:3:"蛩";s:2:"۽";s:3:"蛗";s:2:"۾";s:3:"蛨";s:2:"ۿ";s:3:"蛑";s:2:"";s:3:"衈";s:2:"";s:3:"衖";s:2:"";s:3:"衕";s:2:"";s:3:"袺";s:2:"";s:3:"裗";s:2:"";s:3:"袹";s:2:"";s:3:"袸";s:2:"";s:3:"裀";s:2:"";s:3:"袾";s:2:"";s:3:"袶";s:2:"";s:3:"袼";s:2:"";s:3:"袷";s:2:"";s:3:"袽";s:2:"";s:3:"袲";s:2:"";s:3:"褁";s:2:"";s:3:"裉";s:2:"";s:3:"覕";s:2:"";s:3:"覘";s:2:"";s:3:"覗";s:2:"";s:3:"觝";s:2:"";s:3:"觚";s:2:"";s:3:"觛";s:2:"";s:3:"詎";s:2:"";s:3:"詍";s:2:"";s:3:"訹";s:2:"";s:3:"詙";s:2:"";s:3:"詀";s:2:"";s:3:"詗";s:2:"";s:3:"詘";s:2:"";s:3:"詄";s:2:"";s:3:"詅";s:2:"";s:3:"詒";s:2:"";s:3:"詈";s:2:"";s:3:"詑";s:2:"";s:3:"詊";s:2:"";s:3:"詌";s:2:"";s:3:"詏";s:2:"";s:3:"豟";s:2:"";s:3:"貁";s:2:"";s:3:"貀";s:2:"";s:3:"貺";s:2:"";s:3:"貾";s:2:"";s:3:"貰";s:2:"";s:3:"貹";s:2:"";s:3:"貵";s:2:"";s:3:"趄";s:2:"";s:3:"趀";s:2:"";s:3:"趉";s:2:"";s:3:"跘";s:2:"";s:3:"跓";s:2:"";s:3:"跍";s:2:"";s:3:"跇";s:2:"";s:3:"跖";s:2:"";s:3:"跜";s:2:"";s:3:"跏";s:2:"";s:3:"跕";s:2:"";s:3:"跙";s:2:"";s:3:"跈";s:2:"";s:3:"跗";s:2:"";s:3:"跅";s:2:"";s:3:"軯";s:2:"";s:3:"軷";s:2:"";s:3:"軺";s:2:"@";s:3:"軹";s:2:"A";s:3:"軦";s:2:"B";s:3:"軮";s:2:"C";s:3:"軥";s:2:"D";s:3:"軵";s:2:"E";s:3:"軧";s:2:"F";s:3:"軨";s:2:"G";s:3:"軶";s:2:"H";s:3:"軫";s:2:"I";s:3:"軱";s:2:"J";s:3:"軬";s:2:"K";s:3:"軴";s:2:"L";s:3:"軩";s:2:"M";s:3:"逭";s:2:"N";s:3:"逴";s:2:"O";s:3:"逯";s:2:"P";s:3:"鄆";s:2:"Q";s:3:"鄬";s:2:"R";s:3:"鄄";s:2:"S";s:3:"郿";s:2:"T";s:3:"郼";s:2:"U";s:3:"鄈";s:2:"V";s:3:"郹";s:2:"W";s:3:"郻";s:2:"X";s:3:"鄁";s:2:"Y";s:3:"鄀";s:2:"Z";s:3:"鄇";s:2:"[";s:3:"鄅";s:2:"\";s:3:"鄃";s:2:"]";s:3:"酡";s:2:"^";s:3:"酤";s:2:"_";s:3:"酟";s:2:"`";s:3:"酢";s:2:"a";s:3:"酠";s:2:"b";s:3:"鈁";s:2:"c";s:3:"鈊";s:2:"d";s:3:"鈥";s:2:"e";s:3:"鈃";s:2:"f";s:3:"鈚";s:2:"g";s:3:"鈦";s:2:"h";s:3:"鈏";s:2:"i";s:3:"鈌";s:2:"j";s:3:"鈀";s:2:"k";s:3:"鈒";s:2:"l";s:3:"釿";s:2:"m";s:3:"釽";s:2:"n";s:3:"鈆";s:2:"o";s:3:"鈄";s:2:"p";s:3:"鈧";s:2:"q";s:3:"鈂";s:2:"r";s:3:"鈜";s:2:"s";s:3:"鈤";s:2:"t";s:3:"鈙";s:2:"u";s:3:"鈗";s:2:"v";s:3:"鈅";s:2:"w";s:3:"鈖";s:2:"x";s:3:"镻";s:2:"y";s:3:"閍";s:2:"z";s:3:"閌";s:2:"{";s:3:"閐";s:2:"|";s:3:"隇";s:2:"}";s:3:"陾";s:2:"~";s:3:"隈";s:2:"ܡ";s:3:"隉";s:2:"ܢ";s:3:"隃";s:2:"ܣ";s:3:"隀";s:2:"ܤ";s:3:"雂";s:2:"ܥ";s:3:"雈";s:2:"ܦ";s:3:"雃";s:2:"ܧ";s:3:"雱";s:2:"ܨ";s:3:"雰";s:2:"ܩ";s:3:"靬";s:2:"ܪ";s:3:"靰";s:2:"ܫ";s:3:"靮";s:2:"ܬ";s:3:"頇";s:2:"ܭ";s:3:"颩";s:2:"ܮ";s:3:"飫";s:2:"ܯ";s:3:"鳦";s:2:"ܰ";s:3:"黹";s:2:"ܱ";s:3:"亃";s:2:"ܲ";s:3:"亄";s:2:"ܳ";s:3:"亶";s:2:"ܴ";s:3:"傽";s:2:"ܵ";s:3:"傿";s:2:"ܶ";s:3:"僆";s:2:"ܷ";s:3:"傮";s:2:"ܸ";s:3:"僄";s:2:"ܹ";s:3:"僊";s:2:"ܺ";s:3:"傴";s:2:"ܻ";s:3:"僈";s:2:"ܼ";s:3:"僂";s:2:"ܽ";s:3:"傰";s:2:"ܾ";s:3:"僁";s:2:"ܿ";s:3:"傺";s:2:"";s:3:"傱";s:2:"";s:3:"僋";s:2:"";s:3:"僉";s:2:"";s:3:"傶";s:2:"";s:3:"傸";s:2:"";s:3:"凗";s:2:"";s:3:"剺";s:2:"";s:3:"剸";s:2:"";s:3:"剻";s:2:"";s:3:"剼";s:2:"";s:3:"嗃";s:2:"";s:3:"嗛";s:2:"";s:3:"嗌";s:2:"";s:3:"嗐";s:2:"";s:3:"嗋";s:2:"";s:3:"嗊";s:2:"";s:3:"嗝";s:2:"";s:3:"嗀";s:2:"";s:3:"嗔";s:2:"";s:3:"嗄";s:2:"";s:3:"嗩";s:2:"";s:3:"喿";s:2:"";s:3:"嗒";s:2:"";s:3:"喍";s:2:"";s:3:"嗏";s:2:"";s:3:"嗕";s:2:"";s:3:"嗢";s:2:"";s:3:"嗖";s:2:"";s:3:"嗈";s:2:"";s:3:"嗲";s:2:"";s:3:"嗍";s:2:"";s:3:"嗙";s:2:"";s:3:"嗂";s:2:"";s:3:"圔";s:2:"";s:3:"塓";s:2:"";s:3:"塨";s:2:"";s:3:"塤";s:2:"";s:3:"塏";s:2:"";s:3:"塍";s:2:"";s:3:"塉";s:2:"";s:3:"塯";s:2:"";s:3:"塕";s:2:"";s:3:"塎";s:2:"";s:3:"塝";s:2:"";s:3:"塙";s:2:"";s:3:"塥";s:2:"";s:3:"塛";s:2:"";s:3:"堽";s:2:"";s:3:"塣";s:2:"";s:3:"塱";s:2:"";s:3:"壼";s:2:"";s:3:"嫇";s:2:"";s:3:"嫄";s:2:"";s:3:"嫋";s:2:"";s:3:"媺";s:2:"";s:3:"媸";s:2:"";s:3:"媱";s:2:"";s:3:"媵";s:2:"";s:3:"媰";s:2:"";s:3:"媿";s:2:"";s:3:"嫈";s:2:"";s:3:"媻";s:2:"";s:3:"嫆";s:2:"@";s:3:"媷";s:2:"A";s:3:"嫀";s:2:"B";s:3:"嫊";s:2:"C";s:3:"媴";s:2:"D";s:3:"媶";s:2:"E";s:3:"嫍";s:2:"F";s:3:"媹";s:2:"G";s:3:"媐";s:2:"H";s:3:"寖";s:2:"I";s:3:"寘";s:2:"J";s:3:"寙";s:2:"K";s:3:"尟";s:2:"L";s:3:"尳";s:2:"M";s:3:"嵱";s:2:"N";s:3:"嵣";s:2:"O";s:3:"嵊";s:2:"P";s:3:"嵥";s:2:"Q";s:3:"嵲";s:2:"R";s:3:"嵬";s:2:"S";s:3:"嵞";s:2:"T";s:3:"嵨";s:2:"U";s:3:"嵧";s:2:"V";s:3:"嵢";s:2:"W";s:3:"巰";s:2:"X";s:3:"幏";s:2:"Y";s:3:"幎";s:2:"Z";s:3:"幊";s:2:"[";s:3:"幍";s:2:"\";s:3:"幋";s:2:"]";s:3:"廅";s:2:"^";s:3:"廌";s:2:"_";s:3:"廆";s:2:"`";s:3:"廋";s:2:"a";s:3:"廇";s:2:"b";s:3:"彀";s:2:"c";s:3:"徯";s:2:"d";s:3:"徭";s:2:"e";s:3:"惷";s:2:"f";s:3:"慉";s:2:"g";s:3:"慊";s:2:"h";s:3:"愫";s:2:"i";s:3:"慅";s:2:"j";s:3:"愶";s:2:"k";s:3:"愲";s:2:"l";s:3:"愮";s:2:"m";s:3:"慆";s:2:"n";s:3:"愯";s:2:"o";s:3:"慏";s:2:"p";s:3:"愩";s:2:"q";s:3:"慀";s:2:"r";s:3:"戠";s:2:"s";s:3:"酨";s:2:"t";s:3:"戣";s:2:"u";s:3:"戥";s:2:"v";s:3:"戤";s:2:"w";s:3:"揅";s:2:"x";s:3:"揱";s:2:"y";s:3:"揫";s:2:"z";s:3:"搐";s:2:"{";s:3:"搒";s:2:"|";s:3:"搉";s:2:"}";s:3:"搠";s:2:"~";s:3:"搤";s:2:"ݡ";s:3:"搳";s:2:"ݢ";s:3:"摃";s:2:"ݣ";s:3:"搟";s:2:"ݤ";s:3:"搕";s:2:"ݥ";s:3:"搘";s:2:"ݦ";s:3:"搹";s:2:"ݧ";s:3:"搷";s:2:"ݨ";s:3:"搢";s:2:"ݩ";s:3:"搣";s:2:"ݪ";s:3:"搌";s:2:"ݫ";s:3:"搦";s:2:"ݬ";s:3:"搰";s:2:"ݭ";s:3:"搨";s:2:"ݮ";s:3:"摁";s:2:"ݯ";s:3:"搵";s:2:"ݰ";s:3:"搯";s:2:"ݱ";s:3:"搊";s:2:"ݲ";s:3:"搚";s:2:"ݳ";s:3:"摀";s:2:"ݴ";s:3:"搥";s:2:"ݵ";s:3:"搧";s:2:"ݶ";s:3:"搋";s:2:"ݷ";s:3:"揧";s:2:"ݸ";s:3:"搛";s:2:"ݹ";s:3:"搮";s:2:"ݺ";s:3:"搡";s:2:"ݻ";s:3:"搎";s:2:"ݼ";s:3:"敯";s:2:"ݽ";s:3:"斒";s:2:"ݾ";s:3:"旓";s:2:"ݿ";s:3:"暆";s:2:"";s:3:"暌";s:2:"";s:3:"暕";s:2:"";s:3:"暐";s:2:"";s:3:"暋";s:2:"";s:3:"暊";s:2:"";s:3:"暙";s:2:"";s:3:"暔";s:2:"";s:3:"晸";s:2:"";s:3:"朠";s:2:"";s:3:"楦";s:2:"";s:3:"楟";s:2:"";s:3:"椸";s:2:"";s:3:"楎";s:2:"";s:3:"楢";s:2:"";s:3:"楱";s:2:"";s:3:"椿";s:2:"";s:3:"楅";s:2:"";s:3:"楪";s:2:"";s:3:"椹";s:2:"";s:3:"楂";s:2:"";s:3:"楗";s:2:"";s:3:"楙";s:2:"";s:3:"楺";s:2:"";s:3:"楈";s:2:"";s:3:"楉";s:2:"";s:3:"椵";s:2:"";s:3:"楬";s:2:"";s:3:"椳";s:2:"";s:3:"椽";s:2:"";s:3:"楥";s:2:"";s:3:"棰";s:2:"";s:3:"楸";s:2:"";s:3:"椴";s:2:"";s:3:"楩";s:2:"";s:3:"楀";s:2:"";s:3:"楯";s:2:"";s:3:"楄";s:2:"";s:3:"楶";s:2:"";s:3:"楘";s:2:"";s:3:"楁";s:2:"";s:3:"楴";s:2:"";s:3:"楌";s:2:"";s:3:"椻";s:2:"";s:3:"楋";s:2:"";s:3:"椷";s:2:"";s:3:"楜";s:2:"";s:3:"楏";s:2:"";s:3:"楑";s:2:"";s:3:"椲";s:2:"";s:3:"楒";s:2:"";s:3:"椯";s:2:"";s:3:"楻";s:2:"";s:3:"椼";s:2:"";s:3:"歆";s:2:"";s:3:"歅";s:2:"";s:3:"歃";s:2:"";s:3:"歂";s:2:"";s:3:"歈";s:2:"";s:3:"歁";s:2:"";s:3:"殛";s:2:"";s:3:"嗀";s:2:"";s:3:"毻";s:2:"";s:3:"毼";s:2:"@";s:3:"毹";s:2:"A";s:3:"毷";s:2:"B";s:3:"毸";s:2:"C";s:3:"溛";s:2:"D";s:3:"滖";s:2:"E";s:3:"滈";s:2:"F";s:3:"溏";s:2:"G";s:3:"滀";s:2:"H";s:3:"溟";s:2:"I";s:3:"溓";s:2:"J";s:3:"溔";s:2:"K";s:3:"溠";s:2:"L";s:3:"溱";s:2:"M";s:3:"溹";s:2:"N";s:3:"滆";s:2:"O";s:3:"滒";s:2:"P";s:3:"溽";s:2:"Q";s:3:"滁";s:2:"R";s:3:"溞";s:2:"S";s:3:"滉";s:2:"T";s:3:"溷";s:2:"U";s:3:"溰";s:2:"V";s:3:"滍";s:2:"W";s:3:"溦";s:2:"X";s:3:"滏";s:2:"Y";s:3:"溲";s:2:"Z";s:3:"溾";s:2:"[";s:3:"滃";s:2:"\";s:3:"滜";s:2:"]";s:3:"滘";s:2:"^";s:3:"溙";s:2:"_";s:3:"溒";s:2:"`";s:3:"溎";s:2:"a";s:3:"溍";s:2:"b";s:3:"溤";s:2:"c";s:3:"溡";s:2:"d";s:3:"溿";s:2:"e";s:3:"溳";s:2:"f";s:3:"滐";s:2:"g";s:3:"滊";s:2:"h";s:3:"溗";s:2:"i";s:3:"溮";s:2:"j";s:3:"溣";s:2:"k";s:3:"煇";s:2:"l";s:3:"煔";s:2:"m";s:3:"煒";s:2:"n";s:3:"煣";s:2:"o";s:3:"煠";s:2:"p";s:3:"煁";s:2:"q";s:3:"煝";s:2:"r";s:3:"煢";s:2:"s";s:3:"煲";s:2:"t";s:3:"煸";s:2:"u";s:3:"煪";s:2:"v";s:3:"煡";s:2:"w";s:3:"煂";s:2:"x";s:3:"煘";s:2:"y";s:3:"煃";s:2:"z";s:3:"煋";s:2:"{";s:3:"煰";s:2:"|";s:3:"煟";s:2:"}";s:3:"煐";s:2:"~";s:3:"煓";s:2:"ޡ";s:3:"煄";s:2:"ޢ";s:3:"煍";s:2:"ޣ";s:3:"煚";s:2:"ޤ";s:3:"牏";s:2:"ޥ";s:3:"犍";s:2:"ަ";s:3:"犌";s:2:"ާ";s:3:"犑";s:2:"ި";s:3:"犐";s:2:"ީ";s:3:"犎";s:2:"ު";s:3:"猼";s:2:"ޫ";s:3:"獂";s:2:"ެ";s:3:"猻";s:2:"ޭ";s:3:"猺";s:2:"ޮ";s:3:"獀";s:2:"ޯ";s:3:"獊";s:2:"ް";s:3:"獉";s:2:"ޱ";s:3:"瑄";s:2:"޲";s:3:"瑊";s:2:"޳";s:3:"瑋";s:2:"޴";s:3:"瑒";s:2:"޵";s:3:"瑑";s:2:"޶";s:3:"瑗";s:2:"޷";s:3:"瑀";s:2:"޸";s:3:"瑏";s:2:"޹";s:3:"瑐";s:2:"޺";s:3:"瑎";s:2:"޻";s:3:"瑂";s:2:"޼";s:3:"瑆";s:2:"޽";s:3:"瑍";s:2:"޾";s:3:"瑔";s:2:"޿";s:3:"瓡";s:2:"";s:3:"瓿";s:2:"";s:3:"瓾";s:2:"";s:3:"瓽";s:2:"";s:3:"甝";s:2:"";s:3:"畹";s:2:"";s:3:"畷";s:2:"";s:3:"榃";s:2:"";s:3:"痯";s:2:"";s:3:"瘏";s:2:"";s:3:"瘃";s:2:"";s:3:"痷";s:2:"";s:3:"痾";s:2:"";s:3:"痼";s:2:"";s:3:"痹";s:2:"";s:3:"痸";s:2:"";s:3:"瘐";s:2:"";s:3:"痻";s:2:"";s:3:"痶";s:2:"";s:3:"痭";s:2:"";s:3:"痵";s:2:"";s:3:"痽";s:2:"";s:3:"皙";s:2:"";s:3:"皵";s:2:"";s:3:"盝";s:2:"";s:3:"睕";s:2:"";s:3:"睟";s:2:"";s:3:"睠";s:2:"";s:3:"睒";s:2:"";s:3:"睖";s:2:"";s:3:"睚";s:2:"";s:3:"睩";s:2:"";s:3:"睧";s:2:"";s:3:"睔";s:2:"";s:3:"睙";s:2:"";s:3:"睭";s:2:"";s:3:"矠";s:2:"";s:3:"碇";s:2:"";s:3:"碚";s:2:"";s:3:"碔";s:2:"";s:3:"碏";s:2:"";s:3:"碄";s:2:"";s:3:"碕";s:2:"";s:3:"碅";s:2:"";s:3:"碆";s:2:"";s:3:"碡";s:2:"";s:3:"碃";s:2:"";s:3:"硹";s:2:"";s:3:"碙";s:2:"";s:3:"碀";s:2:"";s:3:"碖";s:2:"";s:3:"硻";s:2:"";s:3:"祼";s:2:"";s:3:"禂";s:2:"";s:3:"祽";s:2:"";s:3:"祹";s:2:"";s:3:"稑";s:2:"";s:3:"稘";s:2:"";s:3:"稙";s:2:"";s:3:"稒";s:2:"";s:3:"稗";s:2:"";s:3:"稕";s:2:"";s:3:"稢";s:2:"";s:3:"稓";s:2:"@";s:3:"稛";s:2:"A";s:3:"稐";s:2:"B";s:3:"窣";s:2:"C";s:3:"窢";s:2:"D";s:3:"窞";s:2:"E";s:3:"竫";s:2:"F";s:3:"筦";s:2:"G";s:3:"筤";s:2:"H";s:3:"筭";s:2:"I";s:3:"筴";s:2:"J";s:3:"筩";s:2:"K";s:3:"筲";s:2:"L";s:3:"筥";s:2:"M";s:3:"筳";s:2:"N";s:3:"筱";s:2:"O";s:3:"筰";s:2:"P";s:3:"筡";s:2:"Q";s:3:"筸";s:2:"R";s:3:"筶";s:2:"S";s:3:"筣";s:2:"T";s:3:"粲";s:2:"U";s:3:"粴";s:2:"V";s:3:"粯";s:2:"W";s:3:"綈";s:2:"X";s:3:"綆";s:2:"Y";s:3:"綀";s:2:"Z";s:3:"綍";s:2:"[";s:3:"絿";s:2:"\";s:3:"綅";s:2:"]";s:3:"絺";s:2:"^";s:3:"綎";s:2:"_";s:3:"絻";s:2:"`";s:3:"綃";s:2:"a";s:3:"絼";s:2:"b";s:3:"綌";s:2:"c";s:3:"綔";s:2:"d";s:3:"綄";s:2:"e";s:3:"絽";s:2:"f";s:3:"綒";s:2:"g";s:3:"罭";s:2:"h";s:3:"罫";s:2:"i";s:3:"罧";s:2:"j";s:3:"罨";s:2:"k";s:3:"罬";s:2:"l";s:3:"羦";s:2:"m";s:3:"羥";s:2:"n";s:3:"羧";s:2:"o";s:3:"翛";s:2:"p";s:3:"翜";s:2:"q";s:3:"耡";s:2:"r";s:3:"腤";s:2:"s";s:3:"腠";s:2:"t";s:3:"腷";s:2:"u";s:3:"腜";s:2:"v";s:3:"腩";s:2:"w";s:3:"腛";s:2:"x";s:3:"腢";s:2:"y";s:3:"腲";s:2:"z";s:3:"朡";s:2:"{";s:3:"腞";s:2:"|";s:3:"腶";s:2:"}";s:3:"腧";s:2:"~";s:3:"腯";s:2:"ߡ";s:3:"腄";s:2:"ߢ";s:3:"腡";s:2:"ߣ";s:3:"舝";s:2:"ߤ";s:3:"艉";s:2:"ߥ";s:3:"艄";s:2:"ߦ";s:3:"艀";s:2:"ߧ";s:3:"艂";s:2:"ߨ";s:3:"艅";s:2:"ߩ";s:3:"蓱";s:2:"ߪ";s:3:"萿";s:2:"߫";s:3:"葖";s:2:"߬";s:3:"葶";s:2:"߭";s:3:"葹";s:2:"߮";s:3:"蒏";s:2:"߯";s:3:"蒍";s:2:"߰";s:3:"葥";s:2:"߱";s:3:"葑";s:2:"߲";s:3:"葀";s:2:"߳";s:3:"蒆";s:2:"ߴ";s:3:"葧";s:2:"ߵ";s:3:"萰";s:2:"߶";s:3:"葍";s:2:"߷";s:3:"葽";s:2:"߸";s:3:"葚";s:2:"߹";s:3:"葙";s:2:"ߺ";s:3:"葴";s:2:"߻";s:3:"葳";s:2:"߼";s:3:"葝";s:2:"߽";s:3:"蔇";s:2:"߾";s:3:"葞";s:2:"߿";s:3:"萷";s:2:"";s:3:"萺";s:2:"";s:3:"萴";s:2:"";s:3:"葺";s:2:"";s:3:"葃";s:2:"";s:3:"葸";s:2:"";s:3:"萲";s:2:"";s:3:"葅";s:2:"";s:3:"萩";s:2:"";s:3:"菙";s:2:"";s:3:"葋";s:2:"";s:3:"萯";s:2:"";s:3:"葂";s:2:"";s:3:"萭";s:2:"";s:3:"葟";s:2:"";s:3:"葰";s:2:"";s:3:"萹";s:2:"";s:3:"葎";s:2:"";s:3:"葌";s:2:"";s:3:"葒";s:2:"";s:3:"葯";s:2:"";s:3:"蓅";s:2:"";s:3:"蒎";s:2:"";s:3:"萻";s:2:"";s:3:"葇";s:2:"";s:3:"萶";s:2:"";s:3:"萳";s:2:"";s:3:"葨";s:2:"";s:3:"葾";s:2:"";s:3:"葄";s:2:"";s:3:"萫";s:2:"";s:3:"葠";s:2:"";s:3:"葔";s:2:"";s:3:"葮";s:2:"";s:3:"葐";s:2:"";s:3:"蜋";s:2:"";s:3:"蜄";s:2:"";s:3:"蛷";s:2:"";s:3:"蜌";s:2:"";s:3:"蛺";s:2:"";s:3:"蛖";s:2:"";s:3:"蛵";s:2:"";s:3:"蝍";s:2:"";s:3:"蛸";s:2:"";s:3:"蜎";s:2:"";s:3:"蜉";s:2:"";s:3:"蜁";s:2:"";s:3:"蛶";s:2:"";s:3:"蜍";s:2:"";s:3:"蜅";s:2:"";s:3:"裖";s:2:"";s:3:"裋";s:2:"";s:3:"裍";s:2:"";s:3:"裎";s:2:"";s:3:"裞";s:2:"";s:3:"裛";s:2:"";s:3:"裚";s:2:"";s:3:"裌";s:2:"";s:3:"裐";s:2:"";s:3:"覅";s:2:"";s:3:"覛";s:2:"";s:3:"觟";s:2:"";s:3:"觥";s:2:"";s:3:"觤";s:2:"@";s:3:"觡";s:2:"A";s:3:"觠";s:2:"B";s:3:"觢";s:2:"C";s:3:"觜";s:2:"D";s:3:"触";s:2:"E";s:3:"詶";s:2:"F";s:3:"誆";s:2:"G";s:3:"詿";s:2:"H";s:3:"詡";s:2:"I";s:3:"訿";s:2:"J";s:3:"詷";s:2:"K";s:3:"誂";s:2:"L";s:3:"誄";s:2:"M";s:3:"詵";s:2:"N";s:3:"誃";s:2:"O";s:3:"誁";s:2:"P";s:3:"詴";s:2:"Q";s:3:"詺";s:2:"R";s:3:"谼";s:2:"S";s:3:"豋";s:2:"T";s:3:"豊";s:2:"U";s:3:"豥";s:2:"V";s:3:"豤";s:2:"W";s:3:"豦";s:2:"X";s:3:"貆";s:2:"Y";s:3:"貄";s:2:"Z";s:3:"貅";s:2:"[";s:3:"賌";s:2:"\";s:3:"赨";s:2:"]";s:3:"赩";s:2:"^";s:3:"趑";s:2:"_";s:3:"趌";s:2:"`";s:3:"趎";s:2:"a";s:3:"趏";s:2:"b";s:3:"趍";s:2:"c";s:3:"趓";s:2:"d";s:3:"趔";s:2:"e";s:3:"趐";s:2:"f";s:3:"趒";s:2:"g";s:3:"跰";s:2:"h";s:3:"跠";s:2:"i";s:3:"跬";s:2:"j";s:3:"跱";s:2:"k";s:3:"跮";s:2:"l";s:3:"跐";s:2:"m";s:3:"跩";s:2:"n";s:3:"跣";s:2:"o";s:3:"跢";s:2:"p";s:3:"跧";s:2:"q";s:3:"跲";s:2:"r";s:3:"跫";s:2:"s";s:3:"跴";s:2:"t";s:3:"輆";s:2:"u";s:3:"軿";s:2:"v";s:3:"輁";s:2:"w";s:3:"輀";s:2:"x";s:3:"輅";s:2:"y";s:3:"輇";s:2:"z";s:3:"輈";s:2:"{";s:3:"輂";s:2:"|";s:3:"輋";s:2:"}";s:3:"遒";s:2:"~";s:3:"逿";s:2:"";s:3:"遄";s:2:"";s:3:"遉";s:2:"";s:3:"逽";s:2:"";s:3:"鄐";s:2:"";s:3:"鄍";s:2:"";s:3:"鄏";s:2:"";s:3:"鄑";s:2:"";s:3:"鄖";s:2:"";s:3:"鄔";s:2:"";s:3:"鄋";s:2:"";s:3:"鄎";s:2:"";s:3:"酮";s:2:"";s:3:"酯";s:2:"";s:3:"鉈";s:2:"";s:3:"鉒";s:2:"";s:3:"鈰";s:2:"";s:3:"鈺";s:2:"";s:3:"鉦";s:2:"";s:3:"鈳";s:2:"";s:3:"鉥";s:2:"";s:3:"鉞";s:2:"";s:3:"銃";s:2:"";s:3:"鈮";s:2:"";s:3:"鉊";s:2:"";s:3:"鉆";s:2:"";s:3:"鉭";s:2:"";s:3:"鉬";s:2:"";s:3:"鉏";s:2:"";s:3:"鉠";s:2:"";s:3:"鉧";s:2:"";s:3:"鉯";s:2:"";s:3:"鈶";s:2:"";s:3:"鉡";s:2:"";s:3:"鉰";s:2:"";s:3:"鈱";s:2:"";s:3:"鉔";s:2:"";s:3:"鉣";s:2:"";s:3:"鉐";s:2:"";s:3:"鉲";s:2:"";s:3:"鉎";s:2:"";s:3:"鉓";s:2:"";s:3:"鉌";s:2:"";s:3:"鉖";s:2:"";s:3:"鈲";s:2:"";s:3:"閟";s:2:"";s:3:"閜";s:2:"";s:3:"閞";s:2:"";s:3:"閛";s:2:"";s:3:"隒";s:2:"";s:3:"隓";s:2:"";s:3:"隑";s:2:"";s:3:"隗";s:2:"";s:3:"雎";s:2:"";s:3:"雺";s:2:"";s:3:"雽";s:2:"";s:3:"雸";s:2:"";s:3:"雵";s:2:"";s:3:"靳";s:2:"";s:3:"靷";s:2:"";s:3:"靸";s:2:"";s:3:"靲";s:2:"";s:3:"頏";s:2:"";s:3:"頍";s:2:"";s:3:"頎";s:2:"";s:3:"颬";s:2:"";s:3:"飶";s:2:"";s:3:"飹";s:2:"";s:3:"馯";s:2:"";s:3:"馲";s:2:"";s:3:"馰";s:2:"";s:3:"馵";s:2:"";s:3:"骭";s:2:"";s:3:"骫";s:2:"";s:3:"魛";s:2:"";s:3:"鳪";s:2:"";s:3:"鳭";s:2:"";s:3:"鳧";s:2:"";s:3:"麀";s:2:"";s:3:"黽";s:2:"";s:3:"僦";s:2:"";s:3:"僔";s:2:"";s:3:"僗";s:2:"";s:3:"僨";s:2:"";s:3:"僳";s:2:"";s:3:"僛";s:2:"";s:3:"僪";s:2:"";s:3:"僝";s:2:"";s:3:"僤";s:2:"";s:3:"僓";s:2:"";s:3:"僬";s:2:"";s:3:"僰";s:2:"";s:3:"僯";s:2:"";s:3:"僣";s:2:"";s:3:"僠";s:2:"@";s:3:"凘";s:2:"A";s:3:"劀";s:2:"B";s:3:"劁";s:2:"C";s:3:"勩";s:2:"D";s:3:"勫";s:2:"E";s:3:"匰";s:2:"F";s:3:"厬";s:2:"G";s:3:"嘧";s:2:"H";s:3:"嘕";s:2:"I";s:3:"嘌";s:2:"J";s:3:"嘒";s:2:"K";s:3:"嗼";s:2:"L";s:3:"嘏";s:2:"M";s:3:"嘜";s:2:"N";s:3:"嘁";s:2:"O";s:3:"嘓";s:2:"P";s:3:"嘂";s:2:"Q";s:3:"嗺";s:2:"R";s:3:"嘝";s:2:"S";s:3:"嘄";s:2:"T";s:3:"嗿";s:2:"U";s:3:"嗹";s:2:"V";s:3:"墉";s:2:"W";s:3:"塼";s:2:"X";s:3:"墐";s:2:"Y";s:3:"墘";s:2:"Z";s:3:"墆";s:2:"[";s:3:"墁";s:2:"\";s:3:"塿";s:2:"]";s:3:"塴";s:2:"^";s:3:"墋";s:2:"_";s:3:"塺";s:2:"`";s:3:"墇";s:2:"a";s:3:"墑";s:2:"b";s:3:"墎";s:2:"c";s:3:"塶";s:2:"d";s:3:"墂";s:2:"e";s:3:"墈";s:2:"f";s:3:"塻";s:2:"g";s:3:"墔";s:2:"h";s:3:"墏";s:2:"i";s:3:"壾";s:2:"j";s:3:"奫";s:2:"k";s:3:"嫜";s:2:"l";s:3:"嫮";s:2:"m";s:3:"嫥";s:2:"n";s:3:"嫕";s:2:"o";s:3:"嫪";s:2:"p";s:3:"嫚";s:2:"q";s:3:"嫭";s:2:"r";s:3:"嫫";s:2:"s";s:3:"嫳";s:2:"t";s:3:"嫢";s:2:"u";s:3:"嫠";s:2:"v";s:3:"嫛";s:2:"w";s:3:"嫬";s:2:"x";s:3:"嫞";s:2:"y";s:3:"嫝";s:2:"z";s:3:"嫙";s:2:"{";s:3:"嫨";s:2:"|";s:3:"嫟";s:2:"}";s:3:"孷";s:2:"~";s:3:"寠";s:2:"";s:3:"寣";s:2:"";s:3:"屣";s:2:"";s:3:"嶂";s:2:"";s:3:"嶀";s:2:"";s:3:"嵽";s:2:"";s:3:"嶆";s:2:"";s:3:"嵺";s:2:"";s:3:"嶁";s:2:"";s:3:"嵷";s:2:"";s:3:"嶊";s:2:"";s:3:"嶉";s:2:"";s:3:"嶈";s:2:"";s:3:"嵾";s:2:"";s:3:"嵼";s:2:"";s:3:"嶍";s:2:"";s:3:"嵹";s:2:"";s:3:"嵿";s:2:"";s:3:"幘";s:2:"";s:3:"幙";s:2:"";s:3:"幓";s:2:"";s:3:"廘";s:2:"";s:3:"廑";s:2:"";s:3:"廗";s:2:"";s:3:"廎";s:2:"";s:3:"廜";s:2:"";s:3:"廕";s:2:"";s:3:"廙";s:2:"";s:3:"廒";s:2:"";s:3:"廔";s:2:"";s:3:"彄";s:2:"";s:3:"彃";s:2:"";s:3:"彯";s:2:"";s:3:"徶";s:2:"";s:3:"愬";s:2:"";s:3:"愨";s:2:"";s:3:"慁";s:2:"";s:3:"慞";s:2:"";s:3:"慱";s:2:"";s:3:"慳";s:2:"";s:3:"慒";s:2:"";s:3:"慓";s:2:"";s:3:"慲";s:2:"";s:3:"慬";s:2:"";s:3:"憀";s:2:"";s:3:"慴";s:2:"";s:3:"慔";s:2:"";s:3:"慺";s:2:"";s:3:"慛";s:2:"";s:3:"慥";s:2:"";s:3:"愻";s:2:"";s:3:"慪";s:2:"";s:3:"慡";s:2:"";s:3:"慖";s:2:"";s:3:"戩";s:2:"";s:3:"戧";s:2:"";s:3:"戫";s:2:"";s:3:"搫";s:2:"";s:3:"摍";s:2:"";s:3:"摛";s:2:"";s:3:"摝";s:2:"";s:3:"摴";s:2:"";s:3:"摶";s:2:"";s:3:"摲";s:2:"";s:3:"摳";s:2:"";s:3:"摽";s:2:"";s:3:"摵";s:2:"";s:3:"摦";s:2:"";s:3:"撦";s:2:"";s:3:"摎";s:2:"";s:3:"撂";s:2:"";s:3:"摞";s:2:"";s:3:"摜";s:2:"";s:3:"摋";s:2:"";s:3:"摓";s:2:"";s:3:"摠";s:2:"";s:3:"摐";s:2:"";s:3:"摿";s:2:"";s:3:"搿";s:2:"";s:3:"摬";s:2:"";s:3:"摫";s:2:"";s:3:"摙";s:2:"";s:3:"摥";s:2:"";s:3:"摷";s:2:"";s:3:"敳";s:2:"";s:3:"斠";s:2:"";s:3:"暡";s:2:"";s:3:"暠";s:2:"";s:3:"暟";s:2:"";s:3:"朅";s:2:"";s:3:"朄";s:2:"";s:3:"朢";s:2:"";s:3:"榱";s:2:"";s:3:"榶";s:2:"";s:3:"槉";s:2:"@";s:3:"榠";s:2:"A";s:3:"槎";s:2:"B";s:3:"榖";s:2:"C";s:3:"榰";s:2:"D";s:3:"榬";s:2:"E";s:3:"榼";s:2:"F";s:3:"榑";s:2:"G";s:3:"榙";s:2:"H";s:3:"榎";s:2:"I";s:3:"榧";s:2:"J";s:3:"榍";s:2:"K";s:3:"榩";s:2:"L";s:3:"榾";s:2:"M";s:3:"榯";s:2:"N";s:3:"榿";s:2:"O";s:3:"槄";s:2:"P";s:3:"榽";s:2:"Q";s:3:"榤";s:2:"R";s:3:"槔";s:2:"S";s:3:"榹";s:2:"T";s:3:"槊";s:2:"U";s:3:"榚";s:2:"V";s:3:"槏";s:2:"W";s:3:"榳";s:2:"X";s:3:"榓";s:2:"Y";s:3:"榪";s:2:"Z";s:3:"榡";s:2:"[";s:3:"榞";s:2:"\";s:3:"槙";s:2:"]";s:3:"榗";s:2:"^";s:3:"榐";s:2:"_";s:3:"槂";s:2:"`";s:3:"榵";s:2:"a";s:3:"榥";s:2:"b";s:3:"槆";s:2:"c";s:3:"歊";s:2:"d";s:3:"歍";s:2:"e";s:3:"歋";s:2:"f";s:3:"殞";s:2:"g";s:3:"殟";s:2:"h";s:3:"殠";s:2:"i";s:3:"毃";s:2:"j";s:3:"毄";s:2:"k";s:3:"毾";s:2:"l";s:3:"滎";s:2:"m";s:3:"滵";s:2:"n";s:3:"滱";s:2:"o";s:3:"漃";s:2:"p";s:3:"漥";s:2:"q";s:3:"滸";s:2:"r";s:3:"漷";s:2:"s";s:3:"滻";s:2:"t";s:3:"漮";s:2:"u";s:3:"漉";s:2:"v";s:3:"潎";s:2:"w";s:3:"漙";s:2:"x";s:3:"漚";s:2:"y";s:3:"漧";s:2:"z";s:3:"漘";s:2:"{";s:3:"漻";s:2:"|";s:3:"漒";s:2:"}";s:3:"滭";s:2:"~";s:3:"漊";s:2:"";s:3:"漶";s:2:"";s:3:"潳";s:2:"";s:3:"滹";s:2:"";s:3:"滮";s:2:"";s:3:"漭";s:2:"";s:3:"潀";s:2:"";s:3:"漰";s:2:"";s:3:"漼";s:2:"";s:3:"漵";s:2:"";s:3:"滫";s:2:"";s:3:"漇";s:2:"";s:3:"漎";s:2:"";s:3:"潃";s:2:"";s:3:"漅";s:2:"";s:3:"滽";s:2:"";s:3:"滶";s:2:"";s:3:"漹";s:2:"";s:3:"漜";s:2:"";s:3:"滼";s:2:"";s:3:"漺";s:2:"";s:3:"漟";s:2:"";s:3:"漍";s:2:"";s:3:"漞";s:2:"";s:3:"漈";s:2:"";s:3:"漡";s:2:"";s:3:"熇";s:2:"";s:3:"熐";s:2:"";s:3:"熉";s:2:"";s:3:"熀";s:2:"";s:3:"熅";s:2:"";s:3:"熂";s:2:"";s:3:"熏";s:2:"";s:3:"煻";s:2:"";s:3:"熆";s:2:"";s:3:"熁";s:2:"";s:3:"熗";s:2:"";s:3:"牄";s:2:"";s:3:"牓";s:2:"";s:3:"犗";s:2:"";s:3:"犕";s:2:"";s:3:"犓";s:2:"";s:3:"獃";s:2:"";s:3:"獍";s:2:"";s:3:"獑";s:2:"";s:3:"獌";s:2:"";s:3:"瑢";s:2:"";s:3:"瑳";s:2:"";s:3:"瑱";s:2:"";s:3:"瑵";s:2:"";s:3:"瑲";s:2:"";s:3:"瑧";s:2:"";s:3:"瑮";s:2:"";s:3:"甀";s:2:"";s:3:"甂";s:2:"";s:3:"甃";s:2:"";s:3:"畽";s:2:"";s:3:"疐";s:2:"";s:3:"瘖";s:2:"";s:3:"瘈";s:2:"";s:3:"瘌";s:2:"";s:3:"瘕";s:2:"";s:3:"瘑";s:2:"";s:3:"瘊";s:2:"";s:3:"瘔";s:2:"";s:3:"皸";s:2:"";s:3:"瞁";s:2:"";s:3:"睼";s:2:"";s:3:"瞅";s:2:"";s:3:"瞂";s:2:"";s:3:"睮";s:2:"";s:3:"瞀";s:2:"";s:3:"睯";s:2:"";s:3:"睾";s:2:"";s:3:"瞃";s:2:"";s:3:"碲";s:2:"";s:3:"碪";s:2:"";s:3:"碴";s:2:"";s:3:"碭";s:2:"";s:3:"碨";s:2:"";s:3:"硾";s:2:"";s:3:"碫";s:2:"";s:3:"碞";s:2:"";s:3:"碥";s:2:"";s:3:"碠";s:2:"";s:3:"碬";s:2:"";s:3:"碢";s:2:"";s:3:"碤";s:2:"";s:3:"禘";s:2:"";s:3:"禊";s:2:"";s:3:"禋";s:2:"";s:3:"禖";s:2:"";s:3:"禕";s:2:"";s:3:"禔";s:2:"";s:3:"禓";s:2:"@";s:3:"禗";s:2:"A";s:3:"禈";s:2:"B";s:3:"禒";s:2:"C";s:3:"禐";s:2:"D";s:3:"稫";s:2:"E";s:3:"穊";s:2:"F";s:3:"稰";s:2:"G";s:3:"稯";s:2:"H";s:3:"稨";s:2:"I";s:3:"稦";s:2:"J";s:3:"窨";s:2:"K";s:3:"窫";s:2:"L";s:3:"窬";s:2:"M";s:3:"竮";s:2:"N";s:3:"箈";s:2:"O";s:3:"箜";s:2:"P";s:3:"箊";s:2:"Q";s:3:"箑";s:2:"R";s:3:"箐";s:2:"S";s:3:"箖";s:2:"T";s:3:"箍";s:2:"U";s:3:"箌";s:2:"V";s:3:"箛";s:2:"W";s:3:"箎";s:2:"X";s:3:"箅";s:2:"Y";s:3:"箘";s:2:"Z";s:3:"劄";s:2:"[";s:3:"箙";s:2:"\";s:3:"箤";s:2:"]";s:3:"箂";s:2:"^";s:3:"粻";s:2:"_";s:3:"粿";s:2:"`";s:3:"粼";s:2:"a";s:3:"粺";s:2:"b";s:3:"綧";s:2:"c";s:3:"綷";s:2:"d";s:3:"緂";s:2:"e";s:3:"綣";s:2:"f";s:3:"綪";s:2:"g";s:3:"緁";s:2:"h";s:3:"緀";s:2:"i";s:3:"緅";s:2:"j";s:3:"綝";s:2:"k";s:3:"緎";s:2:"l";s:3:"緄";s:2:"m";s:3:"緆";s:2:"n";s:3:"緋";s:2:"o";s:3:"緌";s:2:"p";s:3:"綯";s:2:"q";s:3:"綹";s:2:"r";s:3:"綖";s:2:"s";s:3:"綼";s:2:"t";s:3:"綟";s:2:"u";s:3:"綦";s:2:"v";s:3:"綮";s:2:"w";s:3:"綩";s:2:"x";s:3:"綡";s:2:"y";s:3:"緉";s:2:"z";s:3:"罳";s:2:"{";s:3:"翢";s:2:"|";s:3:"翣";s:2:"}";s:3:"翥";s:2:"~";s:3:"翞";s:2:"";s:3:"耤";s:2:"";s:3:"聝";s:2:"";s:3:"聜";s:2:"";s:3:"膉";s:2:"";s:3:"膆";s:2:"";s:3:"膃";s:2:"";s:3:"膇";s:2:"";s:3:"膍";s:2:"";s:3:"膌";s:2:"";s:3:"膋";s:2:"";s:3:"舕";s:2:"";s:3:"蒗";s:2:"";s:3:"蒤";s:2:"";s:3:"蒡";s:2:"";s:3:"蒟";s:2:"";s:3:"蒺";s:2:"";s:3:"蓎";s:2:"";s:3:"蓂";s:2:"";s:3:"蒬";s:2:"";s:3:"蒮";s:2:"";s:3:"蒫";s:2:"";s:3:"蒹";s:2:"";s:3:"蒴";s:2:"";s:3:"蓁";s:2:"";s:3:"蓍";s:2:"";s:3:"蒪";s:2:"";s:3:"蒚";s:2:"";s:3:"蒱";s:2:"";s:3:"蓐";s:2:"";s:3:"蒝";s:2:"";s:3:"蒧";s:2:"";s:3:"蒻";s:2:"";s:3:"蒢";s:2:"";s:3:"蒔";s:2:"";s:3:"蓇";s:2:"";s:3:"蓌";s:2:"";s:3:"蒛";s:2:"";s:3:"蒩";s:2:"";s:3:"蒯";s:2:"";s:3:"蒨";s:2:"";s:3:"蓖";s:2:"";s:3:"蒘";s:2:"";s:3:"蒶";s:2:"";s:3:"蓏";s:2:"";s:3:"蒠";s:2:"";s:3:"蓗";s:2:"";s:3:"蓔";s:2:"";s:3:"蓒";s:2:"";s:3:"蓛";s:2:"";s:3:"蒰";s:2:"";s:3:"蒑";s:2:"";s:3:"虡";s:2:"";s:3:"蜳";s:2:"";s:3:"蜣";s:2:"";s:3:"蜨";s:2:"";s:3:"蝫";s:2:"";s:3:"蝀";s:2:"";s:3:"蜮";s:2:"";s:3:"蜞";s:2:"";s:3:"蜡";s:2:"";s:3:"蜙";s:2:"";s:3:"蜛";s:2:"";s:3:"蝃";s:2:"";s:3:"蜬";s:2:"";s:3:"蝁";s:2:"";s:3:"蜾";s:2:"";s:3:"蝆";s:2:"";s:3:"蜠";s:2:"";s:3:"蜲";s:2:"";s:3:"蜪";s:2:"";s:3:"蜭";s:2:"";s:3:"蜼";s:2:"";s:3:"蜒";s:2:"";s:3:"蜺";s:2:"";s:3:"蜱";s:2:"";s:3:"蜵";s:2:"";s:3:"蝂";s:2:"";s:3:"蜦";s:2:"";s:3:"蜧";s:2:"";s:3:"蜸";s:2:"";s:3:"蜤";s:2:"";s:3:"蜚";s:2:"";s:3:"蜰";s:2:"";s:3:"蜑";s:2:"";s:3:"裷";s:2:"";s:3:"裧";s:2:"";s:3:"裱";s:2:"";s:3:"裲";s:2:"";s:3:"裺";s:2:"";s:3:"裾";s:2:"";s:3:"裮";s:2:"";s:3:"裼";s:2:"";s:3:"裶";s:2:"";s:3:"裻";s:2:"@";s:3:"裰";s:2:"A";s:3:"裬";s:2:"B";s:3:"裫";s:2:"C";s:3:"覝";s:2:"D";s:3:"覡";s:2:"E";s:3:"覟";s:2:"F";s:3:"覞";s:2:"G";s:3:"觩";s:2:"H";s:3:"觫";s:2:"I";s:3:"觨";s:2:"J";s:3:"誫";s:2:"K";s:3:"誙";s:2:"L";s:3:"誋";s:2:"M";s:3:"誒";s:2:"N";s:3:"誏";s:2:"O";s:3:"誖";s:2:"P";s:3:"谽";s:2:"Q";s:3:"豨";s:2:"R";s:3:"豩";s:2:"S";s:3:"賕";s:2:"T";s:3:"賏";s:2:"U";s:3:"賗";s:2:"V";s:3:"趖";s:2:"W";s:3:"踉";s:2:"X";s:3:"踂";s:2:"Y";s:3:"跿";s:2:"Z";s:3:"踍";s:2:"[";s:3:"跽";s:2:"\";s:3:"踊";s:2:"]";s:3:"踃";s:2:"^";s:3:"踇";s:2:"_";s:3:"踆";s:2:"`";s:3:"踅";s:2:"a";s:3:"跾";s:2:"b";s:3:"踀";s:2:"c";s:3:"踄";s:2:"d";s:3:"輐";s:2:"e";s:3:"輑";s:2:"f";s:3:"輎";s:2:"g";s:3:"輍";s:2:"h";s:3:"鄣";s:2:"i";s:3:"鄜";s:2:"j";s:3:"鄠";s:2:"k";s:3:"鄢";s:2:"l";s:3:"鄟";s:2:"m";s:3:"鄝";s:2:"n";s:3:"鄚";s:2:"o";s:3:"鄤";s:2:"p";s:3:"鄡";s:2:"q";s:3:"鄛";s:2:"r";s:3:"酺";s:2:"s";s:3:"酲";s:2:"t";s:3:"酹";s:2:"u";s:3:"酳";s:2:"v";s:3:"銥";s:2:"w";s:3:"銤";s:2:"x";s:3:"鉶";s:2:"y";s:3:"銛";s:2:"z";s:3:"鉺";s:2:"{";s:3:"銠";s:2:"|";s:3:"銔";s:2:"}";s:3:"銪";s:2:"~";s:3:"銍";s:2:"";s:3:"銦";s:2:"";s:3:"銚";s:2:"";s:3:"銫";s:2:"";s:3:"鉹";s:2:"";s:3:"銗";s:2:"";s:3:"鉿";s:2:"";s:3:"銣";s:2:"";s:3:"鋮";s:2:"";s:3:"銎";s:2:"";s:3:"銂";s:2:"";s:3:"銕";s:2:"";s:3:"銢";s:2:"";s:3:"鉽";s:2:"";s:3:"銈";s:2:"";s:3:"銡";s:2:"";s:3:"銊";s:2:"";s:3:"銆";s:2:"";s:3:"銌";s:2:"";s:3:"銙";s:2:"";s:3:"銧";s:2:"";s:3:"鉾";s:2:"";s:3:"銇";s:2:"";s:3:"銩";s:2:"";s:3:"銝";s:2:"";s:3:"銋";s:2:"";s:3:"鈭";s:2:"";s:3:"隞";s:2:"";s:3:"隡";s:2:"";s:3:"雿";s:2:"";s:3:"靘";s:2:"";s:3:"靽";s:2:"";s:3:"靺";s:2:"";s:3:"靾";s:2:"";s:3:"鞃";s:2:"";s:3:"鞀";s:2:"";s:3:"鞂";s:2:"";s:3:"靻";s:2:"";s:3:"鞄";s:2:"";s:3:"鞁";s:2:"";s:3:"靿";s:2:"";s:3:"韎";s:2:"";s:3:"韍";s:2:"";s:3:"頖";s:2:"";s:3:"颭";s:2:"";s:3:"颮";s:2:"";s:3:"餂";s:2:"";s:3:"餀";s:2:"";s:3:"餇";s:2:"";s:3:"馝";s:2:"";s:3:"馜";s:2:"";s:3:"駃";s:2:"";s:3:"馹";s:2:"";s:3:"馻";s:2:"";s:3:"馺";s:2:"";s:3:"駂";s:2:"";s:3:"馽";s:2:"";s:3:"駇";s:2:"";s:3:"骱";s:2:"";s:3:"髣";s:2:"";s:3:"髧";s:2:"";s:3:"鬾";s:2:"";s:3:"鬿";s:2:"";s:3:"魠";s:2:"";s:3:"魡";s:2:"";s:3:"魟";s:2:"";s:3:"鳱";s:2:"";s:3:"鳲";s:2:"";s:3:"鳵";s:2:"";s:3:"麧";s:2:"";s:3:"僿";s:2:"";s:3:"儃";s:2:"";s:3:"儰";s:2:"";s:3:"僸";s:2:"";s:3:"儆";s:2:"";s:3:"儇";s:2:"";s:3:"僶";s:2:"";s:3:"僾";s:2:"";s:3:"儋";s:2:"";s:3:"儌";s:2:"";s:3:"僽";s:2:"";s:3:"儊";s:2:"";s:3:"劋";s:2:"";s:3:"劌";s:2:"";s:3:"勱";s:2:"";s:3:"勯";s:2:"";s:3:"噈";s:2:"";s:3:"噂";s:2:"";s:3:"噌";s:2:"";s:3:"嘵";s:2:"";s:3:"噁";s:2:"";s:3:"噊";s:2:"";s:3:"噉";s:2:"";s:3:"噆";s:2:"";s:3:"噘";s:2:"@";s:3:"噚";s:2:"A";s:3:"噀";s:2:"B";s:3:"嘳";s:2:"C";s:3:"嘽";s:2:"D";s:3:"嘬";s:2:"E";s:3:"嘾";s:2:"F";s:3:"嘸";s:2:"G";s:3:"嘪";s:2:"H";s:3:"嘺";s:2:"I";s:3:"圚";s:2:"J";s:3:"墫";s:2:"K";s:3:"墝";s:2:"L";s:3:"墱";s:2:"M";s:3:"墠";s:2:"N";s:3:"墣";s:2:"O";s:3:"墯";s:2:"P";s:3:"墬";s:2:"Q";s:3:"墥";s:2:"R";s:3:"墡";s:2:"S";s:3:"壿";s:2:"T";s:3:"嫿";s:2:"U";s:3:"嫴";s:2:"V";s:3:"嫽";s:2:"W";s:3:"嫷";s:2:"X";s:3:"嫶";s:2:"Y";s:3:"嬃";s:2:"Z";s:3:"嫸";s:2:"[";s:3:"嬂";s:2:"\";s:3:"嫹";s:2:"]";s:3:"嬁";s:2:"^";s:3:"嬇";s:2:"_";s:3:"嬅";s:2:"`";s:3:"嬏";s:2:"a";s:3:"屧";s:2:"b";s:3:"嶙";s:2:"c";s:3:"嶗";s:2:"d";s:3:"嶟";s:2:"e";s:3:"嶒";s:2:"f";s:3:"嶢";s:2:"g";s:3:"嶓";s:2:"h";s:3:"嶕";s:2:"i";s:3:"嶠";s:2:"j";s:3:"嶜";s:2:"k";s:3:"嶡";s:2:"l";s:3:"嶚";s:2:"m";s:3:"嶞";s:2:"n";s:3:"幩";s:2:"o";s:3:"幝";s:2:"p";s:3:"幠";s:2:"q";s:3:"幜";s:2:"r";s:3:"緳";s:2:"s";s:3:"廛";s:2:"t";s:3:"廞";s:2:"u";s:3:"廡";s:2:"v";s:3:"彉";s:2:"w";s:3:"徲";s:2:"x";s:3:"憋";s:2:"y";s:3:"憃";s:2:"z";s:3:"慹";s:2:"{";s:3:"憱";s:2:"|";s:3:"憰";s:2:"}";s:3:"憢";s:2:"~";s:3:"憉";s:2:"";s:3:"憛";s:2:"";s:3:"憓";s:2:"";s:3:"憯";s:2:"";s:3:"憭";s:2:"";s:3:"憟";s:2:"";s:3:"憒";s:2:"";s:3:"憪";s:2:"";s:3:"憡";s:2:"";s:3:"憍";s:2:"";s:3:"慦";s:2:"";s:3:"憳";s:2:"";s:3:"戭";s:2:"";s:3:"摮";s:2:"";s:3:"摰";s:2:"";s:3:"撖";s:2:"";s:3:"撠";s:2:"";s:3:"撅";s:2:"";s:3:"撗";s:2:"";s:3:"撜";s:2:"";s:3:"撏";s:2:"";s:3:"撋";s:2:"";s:3:"撊";s:2:"";s:3:"撌";s:2:"";s:3:"撣";s:2:"";s:3:"撟";s:2:"";s:3:"摨";s:2:"";s:3:"撱";s:2:"";s:3:"撘";s:2:"";s:3:"敶";s:2:"";s:3:"敺";s:2:"";s:3:"敹";s:2:"";s:3:"敻";s:2:"";s:3:"斲";s:2:"";s:3:"斳";s:2:"";s:3:"暵";s:2:"";s:3:"暰";s:2:"";s:3:"暩";s:2:"";s:3:"暲";s:2:"";s:3:"暷";s:2:"";s:3:"暪";s:2:"";s:3:"暯";s:2:"";s:3:"樀";s:2:"";s:3:"樆";s:2:"";s:3:"樗";s:2:"";s:3:"槥";s:2:"";s:3:"槸";s:2:"";s:3:"樕";s:2:"";s:3:"槱";s:2:"";s:3:"槤";s:2:"";s:3:"樠";s:2:"";s:3:"槿";s:2:"";s:3:"槬";s:2:"";s:3:"槢";s:2:"";s:3:"樛";s:2:"";s:3:"樝";s:2:"";s:3:"槾";s:2:"";s:3:"樧";s:2:"";s:3:"槲";s:2:"";s:3:"槮";s:2:"";s:3:"樔";s:2:"";s:3:"槷";s:2:"";s:3:"槧";s:2:"";s:3:"橀";s:2:"";s:3:"樈";s:2:"";s:3:"槦";s:2:"";s:3:"槻";s:2:"";s:3:"樍";s:2:"";s:3:"槼";s:2:"";s:3:"槫";s:2:"";s:3:"樉";s:2:"";s:3:"樄";s:2:"";s:3:"樘";s:2:"";s:3:"樥";s:2:"";s:3:"樏";s:2:"";s:3:"槶";s:2:"";s:3:"樦";s:2:"";s:3:"樇";s:2:"";s:3:"槴";s:2:"";s:3:"樖";s:2:"";s:3:"歑";s:2:"";s:3:"殥";s:2:"";s:3:"殣";s:2:"";s:3:"殢";s:2:"";s:3:"殦";s:2:"";s:3:"氁";s:2:"";s:3:"氀";s:2:"";s:3:"毿";s:2:"";s:3:"氂";s:2:"";s:3:"潁";s:2:"";s:3:"漦";s:2:"";s:3:"潾";s:2:"";s:3:"澇";s:2:"";s:3:"濆";s:2:"";s:3:"澒";s:2:"@";s:3:"澍";s:2:"A";s:3:"澉";s:2:"B";s:3:"澌";s:2:"C";s:3:"潢";s:2:"D";s:3:"潏";s:2:"E";s:3:"澅";s:2:"F";s:3:"潚";s:2:"G";s:3:"澖";s:2:"H";s:3:"潶";s:2:"I";s:3:"潬";s:2:"J";s:3:"澂";s:2:"K";s:3:"潕";s:2:"L";s:3:"潲";s:2:"M";s:3:"潒";s:2:"N";s:3:"潐";s:2:"O";s:3:"潗";s:2:"P";s:3:"澔";s:2:"Q";s:3:"澓";s:2:"R";s:3:"潝";s:2:"S";s:3:"漀";s:2:"T";s:3:"潡";s:2:"U";s:3:"潫";s:2:"V";s:3:"潽";s:2:"W";s:3:"潧";s:2:"X";s:3:"澐";s:2:"Y";s:3:"潓";s:2:"Z";s:3:"澋";s:2:"[";s:3:"潩";s:2:"\";s:3:"潿";s:2:"]";s:3:"澕";s:2:"^";s:3:"潣";s:2:"_";s:3:"潷";s:2:"`";s:3:"潪";s:2:"a";s:3:"潻";s:2:"b";s:3:"熲";s:2:"c";s:3:"熯";s:2:"d";s:3:"熛";s:2:"e";s:3:"熰";s:2:"f";s:3:"熠";s:2:"g";s:3:"熚";s:2:"h";s:3:"熩";s:2:"i";s:3:"熵";s:2:"j";s:3:"熝";s:2:"k";s:3:"熥";s:2:"l";s:3:"熞";s:2:"m";s:3:"熤";s:2:"n";s:3:"熡";s:2:"o";s:3:"熪";s:2:"p";s:3:"熜";s:2:"q";s:3:"熧";s:2:"r";s:3:"熳";s:2:"s";s:3:"犘";s:2:"t";s:3:"犚";s:2:"u";s:3:"獘";s:2:"v";s:3:"獒";s:2:"w";s:3:"獞";s:2:"x";s:3:"獟";s:2:"y";s:3:"獠";s:2:"z";s:3:"獝";s:2:"{";s:3:"獛";s:2:"|";s:3:"獡";s:2:"}";s:3:"獚";s:2:"~";s:3:"獙";s:2:"";s:3:"獢";s:2:"";s:3:"璇";s:2:"";s:3:"璉";s:2:"";s:3:"璊";s:2:"";s:3:"璆";s:2:"";s:3:"璁";s:2:"";s:3:"瑽";s:2:"";s:3:"璅";s:2:"";s:3:"璈";s:2:"";s:3:"瑼";s:2:"";s:3:"瑹";s:2:"";s:3:"甈";s:2:"";s:3:"甇";s:2:"";s:3:"畾";s:2:"";s:3:"瘥";s:2:"";s:3:"瘞";s:2:"";s:3:"瘙";s:2:"";s:3:"瘝";s:2:"";s:3:"瘜";s:2:"";s:3:"瘣";s:2:"";s:3:"瘚";s:2:"";s:3:"瘨";s:2:"";s:3:"瘛";s:2:"";s:3:"皜";s:2:"";s:3:"皝";s:2:"";s:3:"皞";s:2:"";s:3:"皛";s:2:"";s:3:"瞍";s:2:"";s:3:"瞏";s:2:"";s:3:"瞉";s:2:"";s:3:"瞈";s:2:"";s:3:"磍";s:2:"";s:3:"碻";s:2:"";s:3:"磏";s:2:"";s:3:"磌";s:2:"";s:3:"磑";s:2:"";s:3:"磎";s:2:"";s:3:"磔";s:2:"";s:3:"磈";s:2:"";s:3:"磃";s:2:"";s:3:"磄";s:2:"";s:3:"磉";s:2:"";s:3:"禚";s:2:"";s:3:"禡";s:2:"";s:3:"禠";s:2:"";s:3:"禜";s:2:"";s:3:"禢";s:2:"";s:3:"禛";s:2:"";s:3:"歶";s:2:"";s:3:"稹";s:2:"";s:3:"窲";s:2:"";s:3:"窴";s:2:"";s:3:"窳";s:2:"";s:3:"箷";s:2:"";s:3:"篋";s:2:"";s:3:"箾";s:2:"";s:3:"箬";s:2:"";s:3:"篎";s:2:"";s:3:"箯";s:2:"";s:3:"箹";s:2:"";s:3:"篊";s:2:"";s:3:"箵";s:2:"";s:3:"糅";s:2:"";s:3:"糈";s:2:"";s:3:"糌";s:2:"";s:3:"糋";s:2:"";s:3:"緷";s:2:"";s:3:"緛";s:2:"";s:3:"緪";s:2:"";s:3:"緧";s:2:"";s:3:"緗";s:2:"";s:3:"緡";s:2:"";s:3:"縃";s:2:"";s:3:"緺";s:2:"";s:3:"緦";s:2:"";s:3:"緶";s:2:"";s:3:"緱";s:2:"";s:3:"緰";s:2:"";s:3:"緮";s:2:"";s:3:"緟";s:2:"";s:3:"罶";s:2:"";s:3:"羬";s:2:"";s:3:"羰";s:2:"";s:3:"羭";s:2:"";s:3:"翭";s:2:"";s:3:"翫";s:2:"";s:3:"翪";s:2:"";s:3:"翬";s:2:"";s:3:"翦";s:2:"";s:3:"翨";s:2:"";s:3:"聤";s:2:"";s:3:"聧";s:2:"";s:3:"膣";s:2:"";s:3:"膟";s:2:"@";s:3:"膞";s:2:"A";s:3:"膕";s:2:"B";s:3:"膢";s:2:"C";s:3:"膙";s:2:"D";s:3:"膗";s:2:"E";s:3:"舖";s:2:"F";s:3:"艏";s:2:"G";s:3:"艓";s:2:"H";s:3:"艒";s:2:"I";s:3:"艐";s:2:"J";s:3:"艎";s:2:"K";s:3:"艑";s:2:"L";s:3:"蔤";s:2:"M";s:3:"蔻";s:2:"N";s:3:"蔏";s:2:"O";s:3:"蔀";s:2:"P";s:3:"蔩";s:2:"Q";s:3:"蔎";s:2:"R";s:3:"蔉";s:2:"S";s:3:"蔍";s:2:"T";s:3:"蔟";s:2:"U";s:3:"蔊";s:2:"V";s:3:"蔧";s:2:"W";s:3:"蔜";s:2:"X";s:3:"蓻";s:2:"Y";s:3:"蔫";s:2:"Z";s:3:"蓺";s:2:"[";s:3:"蔈";s:2:"\";s:3:"蔌";s:2:"]";s:3:"蓴";s:2:"^";s:3:"蔪";s:2:"_";s:3:"蓲";s:2:"`";s:3:"蔕";s:2:"a";s:3:"蓷";s:2:"b";s:3:"蓫";s:2:"c";s:3:"蓳";s:2:"d";s:3:"蓼";s:2:"e";s:3:"蔒";s:2:"f";s:3:"蓪";s:2:"g";s:3:"蓩";s:2:"h";s:3:"蔖";s:2:"i";s:3:"蓾";s:2:"j";s:3:"蔨";s:2:"k";s:3:"蔝";s:2:"l";s:3:"蔮";s:2:"m";s:3:"蔂";s:2:"n";s:3:"蓽";s:2:"o";s:3:"蔞";s:2:"p";s:3:"蓶";s:2:"q";s:3:"蔱";s:2:"r";s:3:"蔦";s:2:"s";s:3:"蓧";s:2:"t";s:3:"蓨";s:2:"u";s:3:"蓰";s:2:"v";s:3:"蓯";s:2:"w";s:3:"蓹";s:2:"x";s:3:"蔘";s:2:"y";s:3:"蔠";s:2:"z";s:3:"蔰";s:2:"{";s:3:"蔋";s:2:"|";s:3:"蔙";s:2:"}";s:3:"蔯";s:2:"~";s:3:"虢";s:2:"";s:3:"蝖";s:2:"";s:3:"蝣";s:2:"";s:3:"蝤";s:2:"";s:3:"蝷";s:2:"";s:3:"蟡";s:2:"";s:3:"蝳";s:2:"";s:3:"蝘";s:2:"";s:3:"蝔";s:2:"";s:3:"蝛";s:2:"";s:3:"蝒";s:2:"";s:3:"蝡";s:2:"";s:3:"蝚";s:2:"";s:3:"蝑";s:2:"";s:3:"蝞";s:2:"";s:3:"蝭";s:2:"";s:3:"蝪";s:2:"";s:3:"蝐";s:2:"";s:3:"蝎";s:2:"";s:3:"蝟";s:2:"";s:3:"蝝";s:2:"";s:3:"蝯";s:2:"";s:3:"蝬";s:2:"";s:3:"蝺";s:2:"";s:3:"蝮";s:2:"";s:3:"蝜";s:2:"";s:3:"蝥";s:2:"";s:3:"蝏";s:2:"";s:3:"蝻";s:2:"";s:3:"蝵";s:2:"";s:3:"蝢";s:2:"";s:3:"蝧";s:2:"";s:3:"蝩";s:2:"";s:3:"衚";s:2:"";s:3:"褅";s:2:"";s:3:"褌";s:2:"";s:3:"褔";s:2:"";s:3:"褋";s:2:"";s:3:"褗";s:2:"";s:3:"褘";s:2:"";s:3:"褙";s:2:"";s:3:"褆";s:2:"";s:3:"褖";s:2:"";s:3:"褑";s:2:"";s:3:"褎";s:2:"";s:3:"褉";s:2:"";s:3:"覢";s:2:"";s:3:"覤";s:2:"";s:3:"覣";s:2:"";s:3:"觭";s:2:"";s:3:"觰";s:2:"";s:3:"觬";s:2:"";s:3:"諏";s:2:"";s:3:"諆";s:2:"";s:3:"誸";s:2:"";s:3:"諓";s:2:"";s:3:"諑";s:2:"";s:3:"諔";s:2:"";s:3:"諕";s:2:"";s:3:"誻";s:2:"";s:3:"諗";s:2:"";s:3:"誾";s:2:"";s:3:"諀";s:2:"";s:3:"諅";s:2:"";s:3:"諘";s:2:"";s:3:"諃";s:2:"";s:3:"誺";s:2:"";s:3:"誽";s:2:"";s:3:"諙";s:2:"";s:3:"谾";s:2:"";s:3:"豍";s:2:"";s:3:"貏";s:2:"";s:3:"賥";s:2:"";s:3:"賟";s:2:"";s:3:"賙";s:2:"";s:3:"賨";s:2:"";s:3:"賚";s:2:"";s:3:"賝";s:2:"";s:3:"賧";s:2:"";s:3:"趠";s:2:"";s:3:"趜";s:2:"";s:3:"趡";s:2:"";s:3:"趛";s:2:"";s:3:"踠";s:2:"";s:3:"踣";s:2:"";s:3:"踥";s:2:"";s:3:"踤";s:2:"";s:3:"踮";s:2:"";s:3:"踕";s:2:"";s:3:"踛";s:2:"";s:3:"踖";s:2:"";s:3:"踑";s:2:"";s:3:"踙";s:2:"";s:3:"踦";s:2:"";s:3:"踧";s:2:"@";s:3:"踔";s:2:"A";s:3:"踒";s:2:"B";s:3:"踘";s:2:"C";s:3:"踓";s:2:"D";s:3:"踜";s:2:"E";s:3:"踗";s:2:"F";s:3:"踚";s:2:"G";s:3:"輬";s:2:"H";s:3:"輤";s:2:"I";s:3:"輘";s:2:"J";s:3:"輚";s:2:"K";s:3:"輠";s:2:"L";s:3:"輣";s:2:"M";s:3:"輖";s:2:"N";s:3:"輗";s:2:"O";s:3:"遳";s:2:"P";s:3:"遰";s:2:"Q";s:3:"遯";s:2:"R";s:3:"遧";s:2:"S";s:3:"遫";s:2:"T";s:3:"鄯";s:2:"U";s:3:"鄫";s:2:"V";s:3:"鄩";s:2:"W";s:3:"鄪";s:2:"X";s:3:"鄲";s:2:"Y";s:3:"鄦";s:2:"Z";s:3:"鄮";s:2:"[";s:3:"醅";s:2:"\";s:3:"醆";s:2:"]";s:3:"醊";s:2:"^";s:3:"醁";s:2:"_";s:3:"醂";s:2:"`";s:3:"醄";s:2:"a";s:3:"醀";s:2:"b";s:3:"鋐";s:2:"c";s:3:"鋃";s:2:"d";s:3:"鋄";s:2:"e";s:3:"鋀";s:2:"f";s:3:"鋙";s:2:"g";s:3:"銶";s:2:"h";s:3:"鋏";s:2:"i";s:3:"鋱";s:2:"j";s:3:"鋟";s:2:"k";s:3:"鋘";s:2:"l";s:3:"鋩";s:2:"m";s:3:"鋗";s:2:"n";s:3:"鋝";s:2:"o";s:3:"鋌";s:2:"p";s:3:"鋯";s:2:"q";s:3:"鋂";s:2:"r";s:3:"鋨";s:2:"s";s:3:"鋊";s:2:"t";s:3:"鋈";s:2:"u";s:3:"鋎";s:2:"v";s:3:"鋦";s:2:"w";s:3:"鋍";s:2:"x";s:3:"鋕";s:2:"y";s:3:"鋉";s:2:"z";s:3:"鋠";s:2:"{";s:3:"鋞";s:2:"|";s:3:"鋧";s:2:"}";s:3:"鋑";s:2:"~";s:3:"鋓";s:2:"";s:3:"銵";s:2:"";s:3:"鋡";s:2:"";s:3:"鋆";s:2:"";s:3:"銴";s:2:"";s:3:"镼";s:2:"";s:3:"閬";s:2:"";s:3:"閫";s:2:"";s:3:"閮";s:2:"";s:3:"閰";s:2:"";s:3:"隤";s:2:"";s:3:"隢";s:2:"";s:3:"雓";s:2:"";s:3:"霅";s:2:"";s:3:"霈";s:2:"";s:3:"霂";s:2:"";s:3:"靚";s:2:"";s:3:"鞊";s:2:"";s:3:"鞎";s:2:"";s:3:"鞈";s:2:"";s:3:"韐";s:2:"";s:3:"韏";s:2:"";s:3:"頞";s:2:"";s:3:"頝";s:2:"";s:3:"頦";s:2:"";s:3:"頩";s:2:"";s:3:"頨";s:2:"";s:3:"頠";s:2:"";s:3:"頛";s:2:"";s:3:"頧";s:2:"";s:3:"颲";s:2:"";s:3:"餈";s:2:"";s:3:"飺";s:2:"";s:3:"餑";s:2:"";s:3:"餔";s:2:"";s:3:"餖";s:2:"";s:3:"餗";s:2:"";s:3:"餕";s:2:"";s:3:"駜";s:2:"";s:3:"駍";s:2:"";s:3:"駏";s:2:"";s:3:"駓";s:2:"";s:3:"駔";s:2:"";s:3:"駎";s:2:"";s:3:"駉";s:2:"";s:3:"駖";s:2:"";s:3:"駘";s:2:"";s:3:"駋";s:2:"";s:3:"駗";s:2:"";s:3:"駌";s:2:"";s:3:"骳";s:2:"";s:3:"髬";s:2:"";s:3:"髫";s:2:"";s:3:"髳";s:2:"";s:3:"髲";s:2:"";s:3:"髱";s:2:"";s:3:"魆";s:2:"";s:3:"魃";s:2:"";s:3:"魧";s:2:"";s:3:"魴";s:2:"";s:3:"魱";s:2:"";s:3:"魦";s:2:"";s:3:"魶";s:2:"";s:3:"魵";s:2:"";s:3:"魰";s:2:"";s:3:"魨";s:2:"";s:3:"魤";s:2:"";s:3:"魬";s:2:"";s:3:"鳼";s:2:"";s:3:"鳺";s:2:"";s:3:"鳽";s:2:"";s:3:"鳿";s:2:"";s:3:"鳷";s:2:"";s:3:"鴇";s:2:"";s:3:"鴀";s:2:"";s:3:"鳹";s:2:"";s:3:"鳻";s:2:"";s:3:"鴈";s:2:"";s:3:"鴅";s:2:"";s:3:"鴄";s:2:"";s:3:"麃";s:2:"";s:3:"黓";s:2:"";s:3:"鼏";s:2:"";s:3:"鼐";s:2:"";s:3:"儜";s:2:"";s:3:"儓";s:2:"";s:3:"儗";s:2:"";s:3:"儚";s:2:"";s:3:"儑";s:2:"";s:3:"凞";s:2:"";s:3:"匴";s:2:"";s:3:"叡";s:2:"";s:3:"噰";s:2:"";s:3:"噠";s:2:"";s:3:"噮";s:2:"@";s:3:"噳";s:2:"A";s:3:"噦";s:2:"B";s:3:"噣";s:2:"C";s:3:"噭";s:2:"D";s:3:"噲";s:2:"E";s:3:"噞";s:2:"F";s:3:"噷";s:2:"G";s:3:"圜";s:2:"H";s:3:"圛";s:2:"I";s:3:"壈";s:2:"J";s:3:"墽";s:2:"K";s:3:"壉";s:2:"L";s:3:"墿";s:2:"M";s:3:"墺";s:2:"N";s:3:"壂";s:2:"O";s:3:"墼";s:2:"P";s:3:"壆";s:2:"Q";s:3:"嬗";s:2:"R";s:3:"嬙";s:2:"S";s:3:"嬛";s:2:"T";s:3:"嬡";s:2:"U";s:3:"嬔";s:2:"V";s:3:"嬓";s:2:"W";s:3:"嬐";s:2:"X";s:3:"嬖";s:2:"Y";s:3:"嬨";s:2:"Z";s:3:"嬚";s:2:"[";s:3:"嬠";s:2:"\";s:3:"嬞";s:2:"]";s:3:"寯";s:2:"^";s:3:"嶬";s:2:"_";s:3:"嶱";s:2:"`";s:3:"嶩";s:2:"a";s:3:"嶧";s:2:"b";s:3:"嶵";s:2:"c";s:3:"嶰";s:2:"d";s:3:"嶮";s:2:"e";s:3:"嶪";s:2:"f";s:3:"嶨";s:2:"g";s:3:"嶲";s:2:"h";s:3:"嶭";s:2:"i";s:3:"嶯";s:2:"j";s:3:"嶴";s:2:"k";s:3:"幧";s:2:"l";s:3:"幨";s:2:"m";s:3:"幦";s:2:"n";s:3:"幯";s:2:"o";s:3:"廩";s:2:"p";s:3:"廧";s:2:"q";s:3:"廦";s:2:"r";s:3:"廨";s:2:"s";s:3:"廥";s:2:"t";s:3:"彋";s:2:"u";s:3:"徼";s:2:"v";s:3:"憝";s:2:"w";s:3:"憨";s:2:"x";s:3:"憖";s:2:"y";s:3:"懅";s:2:"z";s:3:"憴";s:2:"{";s:3:"懆";s:2:"|";s:3:"懁";s:2:"}";s:3:"懌";s:2:"~";s:3:"憺";s:2:"";s:3:"憿";s:2:"";s:3:"憸";s:2:"";s:3:"憌";s:2:"";s:3:"擗";s:2:"";s:3:"擖";s:2:"";s:3:"擐";s:2:"";s:3:"擏";s:2:"";s:3:"擉";s:2:"";s:3:"撽";s:2:"";s:3:"撉";s:2:"";s:3:"擃";s:2:"";s:3:"擛";s:2:"";s:3:"擳";s:2:"";s:3:"擙";s:2:"";s:3:"攳";s:2:"";s:3:"敿";s:2:"";s:3:"敼";s:2:"";s:3:"斢";s:2:"";s:3:"曈";s:2:"";s:3:"暾";s:2:"";s:3:"曀";s:2:"";s:3:"曊";s:2:"";s:3:"曋";s:2:"";s:3:"曏";s:2:"";s:3:"暽";s:2:"";s:3:"暻";s:2:"";s:3:"暺";s:2:"";s:3:"曌";s:2:"";s:3:"朣";s:2:"";s:3:"樴";s:2:"";s:3:"橦";s:2:"";s:3:"橉";s:2:"";s:3:"橧";s:2:"";s:3:"樲";s:2:"";s:3:"橨";s:2:"";s:3:"樾";s:2:"";s:3:"橝";s:2:"";s:3:"橭";s:2:"";s:3:"橶";s:2:"";s:3:"橛";s:2:"";s:3:"橑";s:2:"";s:3:"樨";s:2:"";s:3:"橚";s:2:"";s:3:"樻";s:2:"";s:3:"樿";s:2:"";s:3:"橁";s:2:"";s:3:"橪";s:2:"";s:3:"橤";s:2:"";s:3:"橐";s:2:"";s:3:"橏";s:2:"";s:3:"橔";s:2:"";s:3:"橯";s:2:"";s:3:"橩";s:2:"";s:3:"橠";s:2:"";s:3:"樼";s:2:"";s:3:"橞";s:2:"";s:3:"橖";s:2:"";s:3:"橕";s:2:"";s:3:"橍";s:2:"";s:3:"橎";s:2:"";s:3:"橆";s:2:"";s:3:"歕";s:2:"";s:3:"歔";s:2:"";s:3:"歖";s:2:"";s:3:"殧";s:2:"";s:3:"殪";s:2:"";s:3:"殫";s:2:"";s:3:"毈";s:2:"";s:3:"毇";s:2:"";s:3:"氄";s:2:"";s:3:"氃";s:2:"";s:3:"氆";s:2:"";s:3:"澭";s:2:"";s:3:"濋";s:2:"";s:3:"澣";s:2:"";s:3:"濇";s:2:"";s:3:"澼";s:2:"";s:3:"濎";s:2:"";s:3:"濈";s:2:"";s:3:"潞";s:2:"";s:3:"濄";s:2:"";s:3:"澽";s:2:"";s:3:"澞";s:2:"";s:3:"濊";s:2:"";s:3:"澨";s:2:"";s:3:"瀄";s:2:"";s:3:"澥";s:2:"";s:3:"澮";s:2:"";s:3:"澺";s:2:"";s:3:"澬";s:2:"";s:3:"澪";s:2:"";s:3:"濏";s:2:"";s:3:"澿";s:2:"";s:3:"澸";s:2:"@";s:3:"澢";s:2:"A";s:3:"濉";s:2:"B";s:3:"澫";s:2:"C";s:3:"濍";s:2:"D";s:3:"澯";s:2:"E";s:3:"澲";s:2:"F";s:3:"澰";s:2:"G";s:3:"燅";s:2:"H";s:3:"燂";s:2:"I";s:3:"熿";s:2:"J";s:3:"熸";s:2:"K";s:3:"燖";s:2:"L";s:3:"燀";s:2:"M";s:3:"燁";s:2:"N";s:3:"燋";s:2:"O";s:3:"燔";s:2:"P";s:3:"燊";s:2:"Q";s:3:"燇";s:2:"R";s:3:"燏";s:2:"S";s:3:"熽";s:2:"T";s:3:"燘";s:2:"U";s:3:"熼";s:2:"V";s:3:"燆";s:2:"W";s:3:"燚";s:2:"X";s:3:"燛";s:2:"Y";s:3:"犝";s:2:"Z";s:3:"犞";s:2:"[";s:3:"獩";s:2:"\";s:3:"獦";s:2:"]";s:3:"獧";s:2:"^";s:3:"獬";s:2:"_";s:3:"獥";s:2:"`";s:3:"獫";s:2:"a";s:3:"獪";s:2:"b";s:3:"瑿";s:2:"c";s:3:"璚";s:2:"d";s:3:"璠";s:2:"e";s:3:"璔";s:2:"f";s:3:"璒";s:2:"g";s:3:"璕";s:2:"h";s:3:"璡";s:2:"i";s:3:"甋";s:2:"j";s:3:"疀";s:2:"k";s:3:"瘯";s:2:"l";s:3:"瘭";s:2:"m";s:3:"瘱";s:2:"n";s:3:"瘽";s:2:"o";s:3:"瘳";s:2:"p";s:3:"瘼";s:2:"q";s:3:"瘵";s:2:"r";s:3:"瘲";s:2:"s";s:3:"瘰";s:2:"t";s:3:"皻";s:2:"u";s:3:"盦";s:2:"v";s:3:"瞚";s:2:"w";s:3:"瞝";s:2:"x";s:3:"瞡";s:2:"y";s:3:"瞜";s:2:"z";s:3:"瞛";s:2:"{";s:3:"瞢";s:2:"|";s:3:"瞣";s:2:"}";s:3:"瞕";s:2:"~";s:3:"瞙";s:2:"";s:3:"瞗";s:2:"";s:3:"磝";s:2:"";s:3:"磩";s:2:"";s:3:"磥";s:2:"";s:3:"磪";s:2:"";s:3:"磞";s:2:"";s:3:"磣";s:2:"";s:3:"磛";s:2:"";s:3:"磡";s:2:"";s:3:"磢";s:2:"";s:3:"磭";s:2:"";s:3:"磟";s:2:"";s:3:"磠";s:2:"";s:3:"禤";s:2:"";s:3:"穄";s:2:"";s:3:"穈";s:2:"";s:3:"穇";s:2:"";s:3:"窶";s:2:"";s:3:"窸";s:2:"";s:3:"窵";s:2:"";s:3:"窱";s:2:"";s:3:"窷";s:2:"";s:3:"篞";s:2:"";s:3:"篣";s:2:"";s:3:"篧";s:2:"";s:3:"篝";s:2:"";s:3:"篕";s:2:"";s:3:"篥";s:2:"";s:3:"篚";s:2:"";s:3:"篨";s:2:"";s:3:"篹";s:2:"";s:3:"篔";s:2:"";s:3:"篪";s:2:"";s:3:"篢";s:2:"";s:3:"篜";s:2:"";s:3:"篫";s:2:"";s:3:"篘";s:2:"";s:3:"篟";s:2:"";s:3:"糒";s:2:"";s:3:"糔";s:2:"";s:3:"糗";s:2:"";s:3:"糐";s:2:"";s:3:"糑";s:2:"";s:3:"縒";s:2:"";s:3:"縡";s:2:"";s:3:"縗";s:2:"";s:3:"縌";s:2:"";s:3:"縟";s:2:"";s:3:"縠";s:2:"";s:3:"縓";s:2:"";s:3:"縎";s:2:"";s:3:"縜";s:2:"";s:3:"縕";s:2:"";s:3:"縚";s:2:"";s:3:"縢";s:2:"";s:3:"縋";s:2:"";s:3:"縏";s:2:"";s:3:"縖";s:2:"";s:3:"縍";s:2:"";s:3:"縔";s:2:"";s:3:"縥";s:2:"";s:3:"縤";s:2:"";s:3:"罃";s:2:"";s:3:"罻";s:2:"";s:3:"罼";s:2:"";s:3:"罺";s:2:"";s:3:"羱";s:2:"";s:3:"翯";s:2:"";s:3:"耪";s:2:"";s:3:"耩";s:2:"";s:3:"聬";s:2:"";s:3:"膱";s:2:"";s:3:"膦";s:2:"";s:3:"膮";s:2:"";s:3:"膹";s:2:"";s:3:"膵";s:2:"";s:3:"膫";s:2:"";s:3:"膰";s:2:"";s:3:"膬";s:2:"";s:3:"膴";s:2:"";s:3:"膲";s:2:"";s:3:"膷";s:2:"";s:3:"膧";s:2:"";s:3:"臲";s:2:"";s:3:"艕";s:2:"";s:3:"艖";s:2:"";s:3:"艗";s:2:"";s:3:"蕖";s:2:"";s:3:"蕅";s:2:"";s:3:"蕫";s:2:"";s:3:"蕍";s:2:"";s:3:"蕓";s:2:"";s:3:"蕡";s:2:"";s:3:"蕘";s:2:"@";s:3:"蕀";s:2:"A";s:3:"蕆";s:2:"B";s:3:"蕤";s:2:"C";s:3:"蕁";s:2:"D";s:3:"蕢";s:2:"E";s:3:"蕄";s:2:"F";s:3:"蕑";s:2:"G";s:3:"蕇";s:2:"H";s:3:"蕣";s:2:"I";s:3:"蔾";s:2:"J";s:3:"蕛";s:2:"K";s:3:"蕱";s:2:"L";s:3:"蕎";s:2:"M";s:3:"蕮";s:2:"N";s:3:"蕵";s:2:"O";s:3:"蕕";s:2:"P";s:3:"蕧";s:2:"Q";s:3:"蕠";s:2:"R";s:3:"薌";s:2:"S";s:3:"蕦";s:2:"T";s:3:"蕝";s:2:"U";s:3:"蕔";s:2:"V";s:3:"蕥";s:2:"W";s:3:"蕬";s:2:"X";s:3:"虣";s:2:"Y";s:3:"虥";s:2:"Z";s:3:"虤";s:2:"[";s:3:"螛";s:2:"\";s:3:"螏";s:2:"]";s:3:"螗";s:2:"^";s:3:"螓";s:2:"_";s:3:"螒";s:2:"`";s:3:"螈";s:2:"a";s:3:"螁";s:2:"b";s:3:"螖";s:2:"c";s:3:"螘";s:2:"d";s:3:"蝹";s:2:"e";s:3:"螇";s:2:"f";s:3:"螣";s:2:"g";s:3:"螅";s:2:"h";s:3:"螐";s:2:"i";s:3:"螑";s:2:"j";s:3:"螝";s:2:"k";s:3:"螄";s:2:"l";s:3:"螔";s:2:"m";s:3:"螜";s:2:"n";s:3:"螚";s:2:"o";s:3:"螉";s:2:"p";s:3:"褞";s:2:"q";s:3:"褦";s:2:"r";s:3:"褰";s:2:"s";s:3:"褭";s:2:"t";s:3:"褮";s:2:"u";s:3:"褧";s:2:"v";s:3:"褱";s:2:"w";s:3:"褢";s:2:"x";s:3:"褩";s:2:"y";s:3:"褣";s:2:"z";s:3:"褯";s:2:"{";s:3:"褬";s:2:"|";s:3:"褟";s:2:"}";s:3:"觱";s:2:"~";s:3:"諠";s:2:"";s:3:"諢";s:2:"";s:3:"諲";s:2:"";s:3:"諴";s:2:"";s:3:"諵";s:2:"";s:3:"諝";s:2:"";s:3:"謔";s:2:"";s:3:"諤";s:2:"";s:3:"諟";s:2:"";s:3:"諰";s:2:"";s:3:"諈";s:2:"";s:3:"諞";s:2:"";s:3:"諡";s:2:"";s:3:"諨";s:2:"";s:3:"諿";s:2:"";s:3:"諯";s:2:"";s:3:"諻";s:2:"";s:3:"貑";s:2:"";s:3:"貒";s:2:"";s:3:"貐";s:2:"";s:3:"賵";s:2:"";s:3:"賮";s:2:"";s:3:"賱";s:2:"";s:3:"賰";s:2:"";s:3:"賳";s:2:"";s:3:"赬";s:2:"";s:3:"赮";s:2:"";s:3:"趥";s:2:"";s:3:"趧";s:2:"";s:3:"踳";s:2:"";s:3:"踾";s:2:"";s:3:"踸";s:2:"";s:3:"蹀";s:2:"";s:3:"蹅";s:2:"";s:3:"踶";s:2:"";s:3:"踼";s:2:"";s:3:"踽";s:2:"";s:3:"蹁";s:2:"";s:3:"踰";s:2:"";s:3:"踿";s:2:"";s:3:"躽";s:2:"";s:3:"輶";s:2:"";s:3:"輮";s:2:"";s:3:"輵";s:2:"";s:3:"輲";s:2:"";s:3:"輹";s:2:"";s:3:"輷";s:2:"";s:3:"輴";s:2:"";s:3:"遶";s:2:"";s:3:"遹";s:2:"";s:3:"遻";s:2:"";s:3:"邆";s:2:"";s:3:"郺";s:2:"";s:3:"鄳";s:2:"";s:3:"鄵";s:2:"";s:3:"鄶";s:2:"";s:3:"醓";s:2:"";s:3:"醐";s:2:"";s:3:"醑";s:2:"";s:3:"醍";s:2:"";s:3:"醏";s:2:"";s:3:"錧";s:2:"";s:3:"錞";s:2:"";s:3:"錈";s:2:"";s:3:"錟";s:2:"";s:3:"錆";s:2:"";s:3:"錏";s:2:"";s:3:"鍺";s:2:"";s:3:"錸";s:2:"";s:3:"錼";s:2:"";s:3:"錛";s:2:"";s:3:"錣";s:2:"";s:3:"錒";s:2:"";s:3:"錁";s:2:"";s:3:"鍆";s:2:"";s:3:"錭";s:2:"";s:3:"錎";s:2:"";s:3:"錍";s:2:"";s:3:"鋋";s:2:"";s:3:"錝";s:2:"";s:3:"鋺";s:2:"";s:3:"錥";s:2:"";s:3:"錓";s:2:"";s:3:"鋹";s:2:"";s:3:"鋷";s:2:"";s:3:"錴";s:2:"";s:3:"錂";s:2:"";s:3:"錤";s:2:"";s:3:"鋿";s:2:"";s:3:"錩";s:2:"";s:3:"錹";s:2:"";s:3:"錵";s:2:"";s:3:"錪";s:2:"";s:3:"錔";s:2:"";s:3:"錌";s:2:"@";s:3:"錋";s:2:"A";s:3:"鋾";s:2:"B";s:3:"錉";s:2:"C";s:3:"錀";s:2:"D";s:3:"鋻";s:2:"E";s:3:"錖";s:2:"F";s:3:"閼";s:2:"G";s:3:"闍";s:2:"H";s:3:"閾";s:2:"I";s:3:"閹";s:2:"J";s:3:"閺";s:2:"K";s:3:"閶";s:2:"L";s:3:"閿";s:2:"M";s:3:"閵";s:2:"N";s:3:"閽";s:2:"O";s:3:"隩";s:2:"P";s:3:"雔";s:2:"Q";s:3:"霋";s:2:"R";s:3:"霒";s:2:"S";s:3:"霐";s:2:"T";s:3:"鞙";s:2:"U";s:3:"鞗";s:2:"V";s:3:"鞔";s:2:"W";s:3:"韰";s:2:"X";s:3:"韸";s:2:"Y";s:3:"頵";s:2:"Z";s:3:"頯";s:2:"[";s:3:"頲";s:2:"\";s:3:"餤";s:2:"]";s:3:"餟";s:2:"^";s:3:"餧";s:2:"_";s:3:"餩";s:2:"`";s:3:"馞";s:2:"a";s:3:"駮";s:2:"b";s:3:"駬";s:2:"c";s:3:"駥";s:2:"d";s:3:"駤";s:2:"e";s:3:"駰";s:2:"f";s:3:"駣";s:2:"g";s:3:"駪";s:2:"h";s:3:"駩";s:2:"i";s:3:"駧";s:2:"j";s:3:"骹";s:2:"k";s:3:"骿";s:2:"l";s:3:"骴";s:2:"m";s:3:"骻";s:2:"n";s:3:"髶";s:2:"o";s:3:"髺";s:2:"p";s:3:"髹";s:2:"q";s:3:"髷";s:2:"r";s:3:"鬳";s:2:"s";s:3:"鮀";s:2:"t";s:3:"鮅";s:2:"u";s:3:"鮇";s:2:"v";s:3:"魼";s:2:"w";s:3:"魾";s:2:"x";s:3:"魻";s:2:"y";s:3:"鮂";s:2:"z";s:3:"鮓";s:2:"{";s:3:"鮒";s:2:"|";s:3:"鮐";s:2:"}";s:3:"魺";s:2:"~";s:3:"鮕";s:2:"";s:3:"魽";s:2:"";s:3:"鮈";s:2:"";s:3:"鴥";s:2:"";s:3:"鴗";s:2:"";s:3:"鴠";s:2:"";s:3:"鴞";s:2:"";s:3:"鴔";s:2:"";s:3:"鴩";s:2:"";s:3:"鴝";s:2:"";s:3:"鴘";s:2:"";s:3:"鴢";s:2:"";s:3:"鴐";s:2:"";s:3:"鴙";s:2:"";s:3:"鴟";s:2:"";s:3:"麈";s:2:"";s:3:"麆";s:2:"";s:3:"麇";s:2:"";s:3:"麮";s:2:"";s:3:"麭";s:2:"";s:3:"黕";s:2:"";s:3:"黖";s:2:"";s:3:"黺";s:2:"";s:3:"鼒";s:2:"";s:3:"鼽";s:2:"";s:3:"儦";s:2:"";s:3:"儥";s:2:"";s:3:"儢";s:2:"";s:3:"儤";s:2:"";s:3:"儠";s:2:"";s:3:"儩";s:2:"";s:3:"勴";s:2:"";s:3:"嚓";s:2:"";s:3:"嚌";s:2:"";s:3:"嚍";s:2:"";s:3:"嚆";s:2:"";s:3:"嚄";s:2:"";s:3:"嚃";s:2:"";s:3:"噾";s:2:"";s:3:"嚂";s:2:"";s:3:"噿";s:2:"";s:3:"嚁";s:2:"";s:3:"壖";s:2:"";s:3:"壔";s:2:"";s:3:"壏";s:2:"";s:3:"壒";s:2:"";s:3:"嬭";s:2:"";s:3:"嬥";s:2:"";s:3:"嬲";s:2:"";s:3:"嬣";s:2:"";s:3:"嬬";s:2:"";s:3:"嬧";s:2:"";s:3:"嬦";s:2:"";s:3:"嬯";s:2:"";s:3:"嬮";s:2:"";s:3:"孻";s:2:"";s:3:"寱";s:2:"";s:3:"寲";s:2:"";s:3:"嶷";s:2:"";s:3:"幬";s:2:"";s:3:"幪";s:2:"";s:3:"徾";s:2:"";s:3:"徻";s:2:"";s:3:"懃";s:2:"";s:3:"憵";s:2:"";s:3:"憼";s:2:"";s:3:"懧";s:2:"";s:3:"懠";s:2:"";s:3:"懥";s:2:"";s:3:"懤";s:2:"";s:3:"懨";s:2:"";s:3:"懞";s:2:"";s:3:"擯";s:2:"";s:3:"擩";s:2:"";s:3:"擣";s:2:"";s:3:"擫";s:2:"";s:3:"擤";s:2:"";s:3:"擨";s:2:"";s:3:"斁";s:2:"";s:3:"斀";s:2:"";s:3:"斶";s:2:"";s:3:"旚";s:2:"";s:3:"曒";s:2:"";s:3:"檍";s:2:"";s:3:"檖";s:2:"";s:3:"檁";s:2:"";s:3:"檥";s:2:"";s:3:"檉";s:2:"";s:3:"檟";s:2:"";s:3:"檛";s:2:"";s:3:"檡";s:2:"";s:3:"檞";s:2:"";s:3:"檇";s:2:"";s:3:"檓";s:2:"";s:3:"檎";s:2:"@";s:3:"檕";s:2:"A";s:3:"檃";s:2:"B";s:3:"檨";s:2:"C";s:3:"檤";s:2:"D";s:3:"檑";s:2:"E";s:3:"橿";s:2:"F";s:3:"檦";s:2:"G";s:3:"檚";s:2:"H";s:3:"檅";s:2:"I";s:3:"檌";s:2:"J";s:3:"檒";s:2:"K";s:3:"歛";s:2:"L";s:3:"殭";s:2:"M";s:3:"氉";s:2:"N";s:3:"濌";s:2:"O";s:3:"澩";s:2:"P";s:3:"濴";s:2:"Q";s:3:"濔";s:2:"R";s:3:"濣";s:2:"S";s:3:"濜";s:2:"T";s:3:"濭";s:2:"U";s:3:"濧";s:2:"V";s:3:"濦";s:2:"W";s:3:"濞";s:2:"X";s:3:"濲";s:2:"Y";s:3:"濝";s:2:"Z";s:3:"濢";s:2:"[";s:3:"濨";s:2:"\";s:3:"燡";s:2:"]";s:3:"燱";s:2:"^";s:3:"燨";s:2:"_";s:3:"燲";s:2:"`";s:3:"燤";s:2:"a";s:3:"燰";s:2:"b";s:3:"燢";s:2:"c";s:3:"獳";s:2:"d";s:3:"獮";s:2:"e";s:3:"獯";s:2:"f";s:3:"璗";s:2:"g";s:3:"璲";s:2:"h";s:3:"璫";s:2:"i";s:3:"璐";s:2:"j";s:3:"璪";s:2:"k";s:3:"璭";s:2:"l";s:3:"璱";s:2:"m";s:3:"璥";s:2:"n";s:3:"璯";s:2:"o";s:3:"甐";s:2:"p";s:3:"甑";s:2:"q";s:3:"甒";s:2:"r";s:3:"甏";s:2:"s";s:3:"疄";s:2:"t";s:3:"癃";s:2:"u";s:3:"癈";s:2:"v";s:3:"癉";s:2:"w";s:3:"癇";s:2:"x";s:3:"皤";s:2:"y";s:3:"盩";s:2:"z";s:3:"瞵";s:2:"{";s:3:"瞫";s:2:"|";s:3:"瞲";s:2:"}";s:3:"瞷";s:2:"~";s:3:"瞶";s:2:"";s:3:"瞴";s:2:"";s:3:"瞱";s:2:"";s:3:"瞨";s:2:"";s:3:"矰";s:2:"";s:3:"磳";s:2:"";s:3:"磽";s:2:"";s:3:"礂";s:2:"";s:3:"磻";s:2:"";s:3:"磼";s:2:"";s:3:"磲";s:2:"";s:3:"礅";s:2:"";s:3:"磹";s:2:"";s:3:"磾";s:2:"";s:3:"礄";s:2:"";s:3:"禫";s:2:"";s:3:"禨";s:2:"";s:3:"穜";s:2:"";s:3:"穛";s:2:"";s:3:"穖";s:2:"";s:3:"穘";s:2:"";s:3:"穔";s:2:"";s:3:"穚";s:2:"";s:3:"窾";s:2:"";s:3:"竀";s:2:"";s:3:"竁";s:2:"";s:3:"簅";s:2:"";s:3:"簏";s:2:"";s:3:"篲";s:2:"";s:3:"簀";s:2:"";s:3:"篿";s:2:"";s:3:"篻";s:2:"";s:3:"簎";s:2:"";s:3:"篴";s:2:"";s:3:"簋";s:2:"";s:3:"篳";s:2:"";s:3:"簂";s:2:"";s:3:"簉";s:2:"";s:3:"簃";s:2:"";s:3:"簁";s:2:"";s:3:"篸";s:2:"";s:3:"篽";s:2:"";s:3:"簆";s:2:"";s:3:"篰";s:2:"";s:3:"篱";s:2:"";s:3:"簐";s:2:"";s:3:"簊";s:2:"";s:3:"糨";s:2:"";s:3:"縭";s:2:"";s:3:"縼";s:2:"";s:3:"繂";s:2:"";s:3:"縳";s:2:"";s:3:"顈";s:2:"";s:3:"縸";s:2:"";s:3:"縪";s:2:"";s:3:"繉";s:2:"";s:3:"繀";s:2:"";s:3:"繇";s:2:"";s:3:"縩";s:2:"";s:3:"繌";s:2:"";s:3:"縰";s:2:"";s:3:"縻";s:2:"";s:3:"縶";s:2:"";s:3:"繄";s:2:"";s:3:"縺";s:2:"";s:3:"罅";s:2:"";s:3:"罿";s:2:"";s:3:"罾";s:2:"";s:3:"罽";s:2:"";s:3:"翴";s:2:"";s:3:"翲";s:2:"";s:3:"耬";s:2:"";s:3:"膻";s:2:"";s:3:"臄";s:2:"";s:3:"臌";s:2:"";s:3:"臊";s:2:"";s:3:"臅";s:2:"";s:3:"臇";s:2:"";s:3:"膼";s:2:"";s:3:"臩";s:2:"";s:3:"艛";s:2:"";s:3:"艚";s:2:"";s:3:"艜";s:2:"";s:3:"薃";s:2:"";s:3:"薀";s:2:"";s:3:"薏";s:2:"";s:3:"薧";s:2:"";s:3:"薕";s:2:"";s:3:"薠";s:2:"";s:3:"薋";s:2:"";s:3:"薣";s:2:"";s:3:"蕻";s:2:"";s:3:"薤";s:2:"";s:3:"薚";s:2:"";s:3:"薞";s:2:"@";s:3:"蕷";s:2:"A";s:3:"蕼";s:2:"B";s:3:"薉";s:2:"C";s:3:"薡";s:2:"D";s:3:"蕺";s:2:"E";s:3:"蕸";s:2:"F";s:3:"蕗";s:2:"G";s:3:"薎";s:2:"H";s:3:"薖";s:2:"I";s:3:"薆";s:2:"J";s:3:"薍";s:2:"K";s:3:"薙";s:2:"L";s:3:"薝";s:2:"M";s:3:"薁";s:2:"N";s:3:"薢";s:2:"O";s:3:"薂";s:2:"P";s:3:"薈";s:2:"Q";s:3:"薅";s:2:"R";s:3:"蕹";s:2:"S";s:3:"蕶";s:2:"T";s:3:"薘";s:2:"U";s:3:"薐";s:2:"V";s:3:"薟";s:2:"W";s:3:"虨";s:2:"X";s:3:"螾";s:2:"Y";s:3:"螪";s:2:"Z";s:3:"螭";s:2:"[";s:3:"蟅";s:2:"\";s:3:"螰";s:2:"]";s:3:"螬";s:2:"^";s:3:"螹";s:2:"_";s:3:"螵";s:2:"`";s:3:"螼";s:2:"a";s:3:"螮";s:2:"b";s:3:"蟉";s:2:"c";s:3:"蟃";s:2:"d";s:3:"蟂";s:2:"e";s:3:"蟌";s:2:"f";s:3:"螷";s:2:"g";s:3:"螯";s:2:"h";s:3:"蟄";s:2:"i";s:3:"蟊";s:2:"j";s:3:"螴";s:2:"k";s:3:"螶";s:2:"l";s:3:"螿";s:2:"m";s:3:"螸";s:2:"n";s:3:"螽";s:2:"o";s:3:"蟞";s:2:"p";s:3:"螲";s:2:"q";s:3:"褵";s:2:"r";s:3:"褳";s:2:"s";s:3:"褼";s:2:"t";s:3:"褾";s:2:"u";s:3:"襁";s:2:"v";s:3:"襒";s:2:"w";s:3:"褷";s:2:"x";s:3:"襂";s:2:"y";s:3:"覭";s:2:"z";s:3:"覯";s:2:"{";s:3:"覮";s:2:"|";s:3:"觲";s:2:"}";s:3:"觳";s:2:"~";s:3:"謞";s:2:"";s:3:"謘";s:2:"";s:3:"謖";s:2:"";s:3:"謑";s:2:"";s:3:"謅";s:2:"";s:3:"謋";s:2:"";s:3:"謢";s:2:"";s:3:"謏";s:2:"";s:3:"謒";s:2:"";s:3:"謕";s:2:"";s:3:"謇";s:2:"";s:3:"謍";s:2:"";s:3:"謈";s:2:"";s:3:"謆";s:2:"";s:3:"謜";s:2:"";s:3:"謓";s:2:"";s:3:"謚";s:2:"";s:3:"豏";s:2:"";s:3:"豰";s:2:"";s:3:"豲";s:2:"";s:3:"豱";s:2:"";s:3:"豯";s:2:"";s:3:"貕";s:2:"";s:3:"貔";s:2:"";s:3:"賹";s:2:"";s:3:"赯";s:2:"";s:3:"蹎";s:2:"";s:3:"蹍";s:2:"";s:3:"蹓";s:2:"";s:3:"蹐";s:2:"";s:3:"蹌";s:2:"";s:3:"蹇";s:2:"";s:3:"轃";s:2:"";s:3:"轀";s:2:"";s:3:"邅";s:2:"";s:3:"遾";s:2:"";s:3:"鄸";s:2:"";s:3:"醚";s:2:"";s:3:"醢";s:2:"";s:3:"醛";s:2:"";s:3:"醙";s:2:"";s:3:"醟";s:2:"";s:3:"醡";s:2:"";s:3:"醝";s:2:"";s:3:"醠";s:2:"";s:3:"鎡";s:2:"";s:3:"鎃";s:2:"";s:3:"鎯";s:2:"";s:3:"鍤";s:2:"";s:3:"鍖";s:2:"";s:3:"鍇";s:2:"";s:3:"鍼";s:2:"";s:3:"鍘";s:2:"";s:3:"鍜";s:2:"";s:3:"鍶";s:2:"";s:3:"鍉";s:2:"";s:3:"鍐";s:2:"";s:3:"鍑";s:2:"";s:3:"鍠";s:2:"";s:3:"鍭";s:2:"";s:3:"鎏";s:2:"";s:3:"鍌";s:2:"";s:3:"鍪";s:2:"";s:3:"鍹";s:2:"";s:3:"鍗";s:2:"";s:3:"鍕";s:2:"";s:3:"鍒";s:2:"";s:3:"鍏";s:2:"";s:3:"鍱";s:2:"";s:3:"鍷";s:2:"";s:3:"鍻";s:2:"";s:3:"鍡";s:2:"";s:3:"鍞";s:2:"";s:3:"鍣";s:2:"";s:3:"鍧";s:2:"";s:3:"鎀";s:2:"";s:3:"鍎";s:2:"";s:3:"鍙";s:2:"";s:3:"闇";s:2:"";s:3:"闀";s:2:"";s:3:"闉";s:2:"";s:3:"闃";s:2:"";s:3:"闅";s:2:"";s:3:"閷";s:2:"";s:3:"隮";s:2:"";s:3:"隰";s:2:"";s:3:"隬";s:2:"";s:3:"霠";s:2:"";s:3:"霟";s:2:"";s:3:"霘";s:2:"";s:3:"霝";s:2:"";s:3:"霙";s:2:"";s:3:"鞚";s:2:"";s:3:"鞡";s:2:"";s:3:"鞜";s:2:"@";s:3:"鞞";s:2:"A";s:3:"鞝";s:2:"B";s:3:"韕";s:2:"C";s:3:"韔";s:2:"D";s:3:"韱";s:2:"E";s:3:"顁";s:2:"F";s:3:"顄";s:2:"G";s:3:"顊";s:2:"H";s:3:"顉";s:2:"I";s:3:"顅";s:2:"J";s:3:"顃";s:2:"K";s:3:"餥";s:2:"L";s:3:"餫";s:2:"M";s:3:"餬";s:2:"N";s:3:"餪";s:2:"O";s:3:"餳";s:2:"P";s:3:"餲";s:2:"Q";s:3:"餯";s:2:"R";s:3:"餭";s:2:"S";s:3:"餱";s:2:"T";s:3:"餰";s:2:"U";s:3:"馘";s:2:"V";s:3:"馣";s:2:"W";s:3:"馡";s:2:"X";s:3:"騂";s:2:"Y";s:3:"駺";s:2:"Z";s:3:"駴";s:2:"[";s:3:"駷";s:2:"\";s:3:"駹";s:2:"]";s:3:"駸";s:2:"^";s:3:"駶";s:2:"_";s:3:"駻";s:2:"`";s:3:"駽";s:2:"a";s:3:"駾";s:2:"b";s:3:"駼";s:2:"c";s:3:"騃";s:2:"d";s:3:"骾";s:2:"e";s:3:"髾";s:2:"f";s:3:"髽";s:2:"g";s:3:"鬁";s:2:"h";s:3:"髼";s:2:"i";s:3:"魈";s:2:"j";s:3:"鮚";s:2:"k";s:3:"鮨";s:2:"l";s:3:"鮞";s:2:"m";s:3:"鮛";s:2:"n";s:3:"鮦";s:2:"o";s:3:"鮡";s:2:"p";s:3:"鮥";s:2:"q";s:3:"鮤";s:2:"r";s:3:"鮆";s:2:"s";s:3:"鮢";s:2:"t";s:3:"鮠";s:2:"u";s:3:"鮯";s:2:"v";s:3:"鴳";s:2:"w";s:3:"鵁";s:2:"x";s:3:"鵧";s:2:"y";s:3:"鴶";s:2:"z";s:3:"鴮";s:2:"{";s:3:"鴯";s:2:"|";s:3:"鴱";s:2:"}";s:3:"鴸";s:2:"~";s:3:"鴰";s:2:"";s:3:"鵅";s:2:"";s:3:"鵂";s:2:"";s:3:"鵃";s:2:"";s:3:"鴾";s:2:"";s:3:"鴷";s:2:"";s:3:"鵀";s:2:"";s:3:"鴽";s:2:"";s:3:"翵";s:2:"";s:3:"鴭";s:2:"";s:3:"麊";s:2:"";s:3:"麉";s:2:"";s:3:"麍";s:2:"";s:3:"麰";s:2:"";s:3:"黈";s:2:"";s:3:"黚";s:2:"";s:3:"黻";s:2:"";s:3:"黿";s:2:"";s:3:"鼤";s:2:"";s:3:"鼣";s:2:"";s:3:"鼢";s:2:"";s:3:"齔";s:2:"";s:3:"龠";s:2:"";s:3:"儱";s:2:"";s:3:"儭";s:2:"";s:3:"儮";s:2:"";s:3:"嚘";s:2:"";s:3:"嚜";s:2:"";s:3:"嚗";s:2:"";s:3:"嚚";s:2:"";s:3:"嚝";s:2:"";s:3:"嚙";s:2:"";s:3:"奰";s:2:"";s:3:"嬼";s:2:"";s:3:"屩";s:2:"";s:3:"屪";s:2:"";s:3:"巀";s:2:"";s:3:"幭";s:2:"";s:3:"幮";s:2:"";s:3:"懘";s:2:"";s:3:"懟";s:2:"";s:3:"懭";s:2:"";s:3:"懮";s:2:"";s:3:"懱";s:2:"";s:3:"懪";s:2:"";s:3:"懰";s:2:"";s:3:"懫";s:2:"";s:3:"懖";s:2:"";s:3:"懩";s:2:"";s:3:"擿";s:2:"";s:3:"攄";s:2:"";s:3:"擽";s:2:"";s:3:"擸";s:2:"";s:3:"攁";s:2:"";s:3:"攃";s:2:"";s:3:"擼";s:2:"";s:3:"斔";s:2:"";s:3:"旛";s:2:"";s:3:"曚";s:2:"";s:3:"曛";s:2:"";s:3:"曘";s:2:"";s:3:"櫅";s:2:"";s:3:"檹";s:2:"";s:3:"檽";s:2:"";s:3:"櫡";s:2:"";s:3:"櫆";s:2:"";s:3:"檺";s:2:"";s:3:"檶";s:2:"";s:3:"檷";s:2:"";s:3:"櫇";s:2:"";s:3:"檴";s:2:"";s:3:"檭";s:2:"";s:3:"歞";s:2:"";s:3:"毉";s:2:"";s:3:"氋";s:2:"";s:3:"瀇";s:2:"";s:3:"瀌";s:2:"";s:3:"瀍";s:2:"";s:3:"瀁";s:2:"";s:3:"瀅";s:2:"";s:3:"瀔";s:2:"";s:3:"瀎";s:2:"";s:3:"濿";s:2:"";s:3:"瀀";s:2:"";s:3:"濻";s:2:"";s:3:"瀦";s:2:"";s:3:"濼";s:2:"";s:3:"濷";s:2:"";s:3:"瀊";s:2:"";s:3:"爁";s:2:"";s:3:"燿";s:2:"";s:3:"燹";s:2:"";s:3:"爃";s:2:"";s:3:"燽";s:2:"";s:3:"獶";s:2:"@";s:3:"璸";s:2:"A";s:3:"瓀";s:2:"B";s:3:"璵";s:2:"C";s:3:"瓁";s:2:"D";s:3:"璾";s:2:"E";s:3:"璶";s:2:"F";s:3:"璻";s:2:"G";s:3:"瓂";s:2:"H";s:3:"甔";s:2:"I";s:3:"甓";s:2:"J";s:3:"癜";s:2:"K";s:3:"癤";s:2:"L";s:3:"癙";s:2:"M";s:3:"癐";s:2:"N";s:3:"癓";s:2:"O";s:3:"癗";s:2:"P";s:3:"癚";s:2:"Q";s:3:"皦";s:2:"R";s:3:"皽";s:2:"S";s:3:"盬";s:2:"T";s:3:"矂";s:2:"U";s:3:"瞺";s:2:"V";s:3:"磿";s:2:"W";s:3:"礌";s:2:"X";s:3:"礓";s:2:"Y";s:3:"礔";s:2:"Z";s:3:"礉";s:2:"[";s:3:"礐";s:2:"\";s:3:"礒";s:2:"]";s:3:"礑";s:2:"^";s:3:"禭";s:2:"_";s:3:"禬";s:2:"`";s:3:"穟";s:2:"a";s:3:"簜";s:2:"b";s:3:"簩";s:2:"c";s:3:"簙";s:2:"d";s:3:"簠";s:2:"e";s:3:"簟";s:2:"f";s:3:"簭";s:2:"g";s:3:"簝";s:2:"h";s:3:"簦";s:2:"i";s:3:"簨";s:2:"j";s:3:"簢";s:2:"k";s:3:"簥";s:2:"l";s:3:"簰";s:2:"m";s:3:"繜";s:2:"n";s:3:"繐";s:2:"o";s:3:"繖";s:2:"p";s:3:"繣";s:2:"q";s:3:"繘";s:2:"r";s:3:"繢";s:2:"s";s:3:"繟";s:2:"t";s:3:"繑";s:2:"u";s:3:"繠";s:2:"v";s:3:"繗";s:2:"w";s:3:"繓";s:2:"x";s:3:"羵";s:2:"y";s:3:"羳";s:2:"z";s:3:"翷";s:2:"{";s:3:"翸";s:2:"|";s:3:"聵";s:2:"}";s:3:"臑";s:2:"~";s:3:"臒";s:2:"";s:3:"臐";s:2:"";s:3:"艟";s:2:"";s:3:"艞";s:2:"";s:3:"薴";s:2:"";s:3:"藆";s:2:"";s:3:"藀";s:2:"";s:3:"藃";s:2:"";s:3:"藂";s:2:"";s:3:"薳";s:2:"";s:3:"薵";s:2:"";s:3:"薽";s:2:"";s:3:"藇";s:2:"";s:3:"藄";s:2:"";s:3:"薿";s:2:"";s:3:"藋";s:2:"";s:3:"藎";s:2:"";s:3:"藈";s:2:"";s:3:"藅";s:2:"";s:3:"薱";s:2:"";s:3:"薶";s:2:"";s:3:"藒";s:2:"";s:3:"蘤";s:2:"";s:3:"薸";s:2:"";s:3:"薷";s:2:"";s:3:"薾";s:2:"";s:3:"虩";s:2:"";s:3:"蟧";s:2:"";s:3:"蟦";s:2:"";s:3:"蟢";s:2:"";s:3:"蟛";s:2:"";s:3:"蟫";s:2:"";s:3:"蟪";s:2:"";s:3:"蟥";s:2:"";s:3:"蟟";s:2:"";s:3:"蟳";s:2:"";s:3:"蟤";s:2:"";s:3:"蟔";s:2:"";s:3:"蟜";s:2:"";s:3:"蟓";s:2:"";s:3:"蟭";s:2:"";s:3:"蟘";s:2:"";s:3:"蟣";s:2:"";s:3:"螤";s:2:"";s:3:"蟗";s:2:"";s:3:"蟙";s:2:"";s:3:"蠁";s:2:"";s:3:"蟴";s:2:"";s:3:"蟨";s:2:"";s:3:"蟝";s:2:"";s:3:"襓";s:2:"";s:3:"襋";s:2:"";s:3:"襏";s:2:"";s:3:"襌";s:2:"";s:3:"襆";s:2:"";s:3:"襐";s:2:"";s:3:"襑";s:2:"";s:3:"襉";s:2:"";s:3:"謪";s:2:"";s:3:"謧";s:2:"";s:3:"謣";s:2:"";s:3:"謳";s:2:"";s:3:"謰";s:2:"";s:3:"謵";s:2:"";s:3:"譇";s:2:"";s:3:"謯";s:2:"";s:3:"謼";s:2:"";s:3:"謾";s:2:"";s:3:"謱";s:2:"";s:3:"謥";s:2:"";s:3:"謷";s:2:"";s:3:"謦";s:2:"";s:3:"謶";s:2:"";s:3:"謮";s:2:"";s:3:"謤";s:2:"";s:3:"謻";s:2:"";s:3:"謽";s:2:"";s:3:"謺";s:2:"";s:3:"豂";s:2:"";s:3:"豵";s:2:"";s:3:"貙";s:2:"";s:3:"貘";s:2:"";s:3:"貗";s:2:"";s:3:"賾";s:2:"";s:3:"贄";s:2:"";s:3:"贂";s:2:"";s:3:"贀";s:2:"";s:3:"蹜";s:2:"";s:3:"蹢";s:2:"";s:3:"蹠";s:2:"";s:3:"蹗";s:2:"";s:3:"蹖";s:2:"";s:3:"蹞";s:2:"";s:3:"蹥";s:2:"";s:3:"蹧";s:2:"@";s:3:"蹛";s:2:"A";s:3:"蹚";s:2:"B";s:3:"蹡";s:2:"C";s:3:"蹝";s:2:"D";s:3:"蹩";s:2:"E";s:3:"蹔";s:2:"F";s:3:"轆";s:2:"G";s:3:"轇";s:2:"H";s:3:"轈";s:2:"I";s:3:"轋";s:2:"J";s:3:"鄨";s:2:"K";s:3:"鄺";s:2:"L";s:3:"鄻";s:2:"M";s:3:"鄾";s:2:"N";s:3:"醨";s:2:"O";s:3:"醥";s:2:"P";s:3:"醧";s:2:"Q";s:3:"醯";s:2:"R";s:3:"醪";s:2:"S";s:3:"鎵";s:2:"T";s:3:"鎌";s:2:"U";s:3:"鎒";s:2:"V";s:3:"鎷";s:2:"W";s:3:"鎛";s:2:"X";s:3:"鎝";s:2:"Y";s:3:"鎉";s:2:"Z";s:3:"鎧";s:2:"[";s:3:"鎎";s:2:"\";s:3:"鎪";s:2:"]";s:3:"鎞";s:2:"^";s:3:"鎦";s:2:"_";s:3:"鎕";s:2:"`";s:3:"鎈";s:2:"a";s:3:"鎙";s:2:"b";s:3:"鎟";s:2:"c";s:3:"鎍";s:2:"d";s:3:"鎱";s:2:"e";s:3:"鎑";s:2:"f";s:3:"鎲";s:2:"g";s:3:"鎤";s:2:"h";s:3:"鎨";s:2:"i";s:3:"鎴";s:2:"j";s:3:"鎣";s:2:"k";s:3:"鎥";s:2:"l";s:3:"闒";s:2:"m";s:3:"闓";s:2:"n";s:3:"闑";s:2:"o";s:3:"隳";s:2:"p";s:3:"雗";s:2:"q";s:3:"雚";s:2:"r";s:3:"巂";s:2:"s";s:3:"雟";s:2:"t";s:3:"雘";s:2:"u";s:3:"雝";s:2:"v";s:3:"霣";s:2:"w";s:3:"霢";s:2:"x";s:3:"霥";s:2:"y";s:3:"鞬";s:2:"z";s:3:"鞮";s:2:"{";s:3:"鞨";s:2:"|";s:3:"鞫";s:2:"}";s:3:"鞤";s:2:"~";s:3:"鞪";s:2:"";s:3:"鞢";s:2:"";s:3:"鞥";s:2:"";s:3:"韗";s:2:"";s:3:"韙";s:2:"";s:3:"韖";s:2:"";s:3:"韘";s:2:"";s:3:"韺";s:2:"";s:3:"顐";s:2:"";s:3:"顑";s:2:"";s:3:"顒";s:2:"";s:3:"颸";s:2:"";s:3:"饁";s:2:"";s:3:"餼";s:2:"";s:3:"餺";s:2:"";s:3:"騏";s:2:"";s:3:"騋";s:2:"";s:3:"騉";s:2:"";s:3:"騍";s:2:"";s:3:"騄";s:2:"";s:3:"騑";s:2:"";s:3:"騊";s:2:"";s:3:"騅";s:2:"";s:3:"騇";s:2:"";s:3:"騆";s:2:"";s:3:"髀";s:2:"";s:3:"髜";s:2:"";s:3:"鬈";s:2:"";s:3:"鬄";s:2:"";s:3:"鬅";s:2:"";s:3:"鬩";s:2:"";s:3:"鬵";s:2:"";s:3:"魊";s:2:"";s:3:"魌";s:2:"";s:3:"魋";s:2:"";s:3:"鯇";s:2:"";s:3:"鯆";s:2:"";s:3:"鯃";s:2:"";s:3:"鮿";s:2:"";s:3:"鯁";s:2:"";s:3:"鮵";s:2:"";s:3:"鮸";s:2:"";s:3:"鯓";s:2:"";s:3:"鮶";s:2:"";s:3:"鯄";s:2:"";s:3:"鮹";s:2:"";s:3:"鮽";s:2:"";s:3:"鵜";s:2:"";s:3:"鵓";s:2:"";s:3:"鵏";s:2:"";s:3:"鵊";s:2:"";s:3:"鵛";s:2:"";s:3:"鵋";s:2:"";s:3:"鵙";s:2:"";s:3:"鵖";s:2:"";s:3:"鵌";s:2:"";s:3:"鵗";s:2:"";s:3:"鵒";s:2:"";s:3:"鵔";s:2:"";s:3:"鵟";s:2:"";s:3:"鵘";s:2:"";s:3:"鵚";s:2:"";s:3:"麎";s:2:"";s:3:"麌";s:2:"";s:3:"黟";s:2:"";s:3:"鼁";s:2:"";s:3:"鼀";s:2:"";s:3:"鼖";s:2:"";s:3:"鼥";s:2:"";s:3:"鼫";s:2:"";s:3:"鼪";s:2:"";s:3:"鼩";s:2:"";s:3:"鼨";s:2:"";s:3:"齌";s:2:"";s:3:"齕";s:2:"";s:3:"儴";s:2:"";s:3:"儵";s:2:"";s:3:"劖";s:2:"";s:3:"勷";s:2:"";s:3:"厴";s:2:"";s:3:"嚫";s:2:"";s:3:"嚭";s:2:"";s:3:"嚦";s:2:"";s:3:"嚧";s:2:"";s:3:"嚪";s:2:"";s:3:"嚬";s:2:"";s:3:"壚";s:2:"";s:3:"壝";s:2:"";s:3:"壛";s:2:"";s:3:"夒";s:2:"";s:3:"嬽";s:2:"";s:3:"嬾";s:2:"";s:3:"嬿";s:2:"";s:3:"巃";s:2:"";s:3:"幰";s:2:"@";s:3:"徿";s:2:"A";s:3:"懻";s:2:"B";s:3:"攇";s:2:"C";s:3:"攐";s:2:"D";s:3:"攍";s:2:"E";s:3:"攉";s:2:"F";s:3:"攌";s:2:"G";s:3:"攎";s:2:"H";s:3:"斄";s:2:"I";s:3:"旞";s:2:"J";s:3:"旝";s:2:"K";s:3:"曞";s:2:"L";s:3:"櫧";s:2:"M";s:3:"櫠";s:2:"N";s:3:"櫌";s:2:"O";s:3:"櫑";s:2:"P";s:3:"櫙";s:2:"Q";s:3:"櫋";s:2:"R";s:3:"櫟";s:2:"S";s:3:"櫜";s:2:"T";s:3:"櫐";s:2:"U";s:3:"櫫";s:2:"V";s:3:"櫏";s:2:"W";s:3:"櫍";s:2:"X";s:3:"櫞";s:2:"Y";s:3:"歠";s:2:"Z";s:3:"殰";s:2:"[";s:3:"氌";s:2:"\";s:3:"瀙";s:2:"]";s:3:"瀧";s:2:"^";s:3:"瀠";s:2:"_";s:3:"瀖";s:2:"`";s:3:"瀫";s:2:"a";s:3:"瀡";s:2:"b";s:3:"瀢";s:2:"c";s:3:"瀣";s:2:"d";s:3:"瀩";s:2:"e";s:3:"瀗";s:2:"f";s:3:"瀤";s:2:"g";s:3:"瀜";s:2:"h";s:3:"瀪";s:2:"i";s:3:"爌";s:2:"j";s:3:"爊";s:2:"k";s:3:"爇";s:2:"l";s:3:"爂";s:2:"m";s:3:"爅";s:2:"n";s:3:"犥";s:2:"o";s:3:"犦";s:2:"p";s:3:"犤";s:2:"q";s:3:"犣";s:2:"r";s:3:"犡";s:2:"s";s:3:"瓋";s:2:"t";s:3:"瓅";s:2:"u";s:3:"璷";s:2:"v";s:3:"瓃";s:2:"w";s:3:"甖";s:2:"x";s:3:"癠";s:2:"y";s:3:"矉";s:2:"z";s:3:"矊";s:2:"{";s:3:"矄";s:2:"|";s:3:"矱";s:2:"}";s:3:"礝";s:2:"~";s:3:"礛";s:2:"";s:3:"礡";s:2:"";s:3:"礜";s:2:"";s:3:"礗";s:2:"";s:3:"礞";s:2:"";s:3:"禰";s:2:"";s:3:"穧";s:2:"";s:3:"穨";s:2:"";s:3:"簳";s:2:"";s:3:"簼";s:2:"";s:3:"簹";s:2:"";s:3:"簬";s:2:"";s:3:"簻";s:2:"";s:3:"糬";s:2:"";s:3:"糪";s:2:"";s:3:"繶";s:2:"";s:3:"繵";s:2:"";s:3:"繸";s:2:"";s:3:"繰";s:2:"";s:3:"繷";s:2:"";s:3:"繯";s:2:"";s:3:"繺";s:2:"";s:3:"繲";s:2:"";s:3:"繴";s:2:"";s:3:"繨";s:2:"";s:3:"罋";s:2:"";s:3:"罊";s:2:"";s:3:"羃";s:2:"";s:3:"羆";s:2:"";s:3:"羷";s:2:"";s:3:"翽";s:2:"";s:3:"翾";s:2:"";s:3:"聸";s:2:"";s:3:"臗";s:2:"";s:3:"臕";s:2:"";s:3:"艤";s:2:"";s:3:"艡";s:2:"";s:3:"艣";s:2:"";s:3:"藫";s:2:"";s:3:"藱";s:2:"";s:3:"藭";s:2:"";s:3:"藙";s:2:"";s:3:"藡";s:2:"";s:3:"藨";s:2:"";s:3:"藚";s:2:"";s:3:"藗";s:2:"";s:3:"藬";s:2:"";s:3:"藲";s:2:"";s:3:"藸";s:2:"";s:3:"藘";s:2:"";s:3:"藟";s:2:"";s:3:"藣";s:2:"";s:3:"藜";s:2:"";s:3:"藑";s:2:"";s:3:"藰";s:2:"";s:3:"藦";s:2:"";s:3:"藯";s:2:"";s:3:"藞";s:2:"";s:3:"藢";s:2:"";s:3:"蠀";s:2:"";s:3:"蟺";s:2:"";s:3:"蠃";s:2:"";s:3:"蟶";s:2:"";s:3:"蟷";s:2:"";s:3:"蠉";s:2:"";s:3:"蠌";s:2:"";s:3:"蠋";s:2:"";s:3:"蠆";s:2:"";s:3:"蟼";s:2:"";s:3:"蠈";s:2:"";s:3:"蟿";s:2:"";s:3:"蠊";s:2:"";s:3:"蠂";s:2:"";s:3:"襢";s:2:"";s:3:"襚";s:2:"";s:3:"襛";s:2:"";s:3:"襗";s:2:"";s:3:"襡";s:2:"";s:3:"襜";s:2:"";s:3:"襘";s:2:"";s:3:"襝";s:2:"";s:3:"襙";s:2:"";s:3:"覈";s:2:"";s:3:"覷";s:2:"";s:3:"覶";s:2:"";s:3:"觶";s:2:"";s:3:"譐";s:2:"";s:3:"譈";s:2:"";s:3:"譊";s:2:"";s:3:"譀";s:2:"";s:3:"譓";s:2:"";s:3:"譖";s:2:"";s:3:"譔";s:2:"";s:3:"譋";s:2:"";s:3:"譕";s:2:"@";s:3:"譑";s:2:"A";s:3:"譂";s:2:"B";s:3:"譒";s:2:"C";s:3:"譗";s:2:"D";s:3:"豃";s:2:"E";s:3:"豷";s:2:"F";s:3:"豶";s:2:"G";s:3:"貚";s:2:"H";s:3:"贆";s:2:"I";s:3:"贇";s:2:"J";s:3:"贉";s:2:"K";s:3:"趬";s:2:"L";s:3:"趪";s:2:"M";s:3:"趭";s:2:"N";s:3:"趫";s:2:"O";s:3:"蹭";s:2:"P";s:3:"蹸";s:2:"Q";s:3:"蹳";s:2:"R";s:3:"蹪";s:2:"S";s:3:"蹯";s:2:"T";s:3:"蹻";s:2:"U";s:3:"軂";s:2:"V";s:3:"轒";s:2:"W";s:3:"轑";s:2:"X";s:3:"轏";s:2:"Y";s:3:"轐";s:2:"Z";s:3:"轓";s:2:"[";s:3:"辴";s:2:"\";s:3:"酀";s:2:"]";s:3:"鄿";s:2:"^";s:3:"醰";s:2:"_";s:3:"醭";s:2:"`";s:3:"鏞";s:2:"a";s:3:"鏇";s:2:"b";s:3:"鏏";s:2:"c";s:3:"鏂";s:2:"d";s:3:"鏚";s:2:"e";s:3:"鏐";s:2:"f";s:3:"鏹";s:2:"g";s:3:"鏬";s:2:"h";s:3:"鏌";s:2:"i";s:3:"鏙";s:2:"j";s:3:"鎩";s:2:"k";s:3:"鏦";s:2:"l";s:3:"鏊";s:2:"m";s:3:"鏔";s:2:"n";s:3:"鏮";s:2:"o";s:3:"鏣";s:2:"p";s:3:"鏕";s:2:"q";s:3:"鏄";s:2:"r";s:3:"鏎";s:2:"s";s:3:"鏀";s:2:"t";s:3:"鏒";s:2:"u";s:3:"鏧";s:2:"v";s:3:"镽";s:2:"w";s:3:"闚";s:2:"x";s:3:"闛";s:2:"y";s:3:"雡";s:2:"z";s:3:"霩";s:2:"{";s:3:"霫";s:2:"|";s:3:"霬";s:2:"}";s:3:"霨";s:2:"~";s:3:"霦";s:2:"";s:3:"鞳";s:2:"";s:3:"鞷";s:2:"";s:3:"鞶";s:2:"";s:3:"韝";s:2:"";s:3:"韞";s:2:"";s:3:"韟";s:2:"";s:3:"顜";s:2:"";s:3:"顙";s:2:"";s:3:"顝";s:2:"";s:3:"顗";s:2:"";s:3:"颿";s:2:"";s:3:"颽";s:2:"";s:3:"颻";s:2:"";s:3:"颾";s:2:"";s:3:"饈";s:2:"";s:3:"饇";s:2:"";s:3:"饃";s:2:"";s:3:"馦";s:2:"";s:3:"馧";s:2:"";s:3:"騚";s:2:"";s:3:"騕";s:2:"";s:3:"騥";s:2:"";s:3:"騝";s:2:"";s:3:"騤";s:2:"";s:3:"騛";s:2:"";s:3:"騢";s:2:"";s:3:"騠";s:2:"";s:3:"騧";s:2:"";s:3:"騣";s:2:"";s:3:"騞";s:2:"";s:3:"騜";s:2:"";s:3:"騔";s:2:"";s:3:"髂";s:2:"";s:3:"鬋";s:2:"";s:3:"鬊";s:2:"";s:3:"鬎";s:2:"";s:3:"鬌";s:2:"";s:3:"鬷";s:2:"";s:3:"鯪";s:2:"";s:3:"鯫";s:2:"";s:3:"鯠";s:2:"";s:3:"鯞";s:2:"";s:3:"鯤";s:2:"";s:3:"鯦";s:2:"";s:3:"鯢";s:2:"";s:3:"鯰";s:2:"";s:3:"鯔";s:2:"";s:3:"鯗";s:2:"";s:3:"鯬";s:2:"";s:3:"鯜";s:2:"";s:3:"鯙";s:2:"";s:3:"鯥";s:2:"";s:3:"鯕";s:2:"";s:3:"鯡";s:2:"";s:3:"鯚";s:2:"";s:3:"鵷";s:2:"";s:3:"鶁";s:2:"";s:3:"鶊";s:2:"";s:3:"鶄";s:2:"";s:3:"鶈";s:2:"";s:3:"鵱";s:2:"";s:3:"鶀";s:2:"";s:3:"鵸";s:2:"";s:3:"鶆";s:2:"";s:3:"鶋";s:2:"";s:3:"鶌";s:2:"";s:3:"鵽";s:2:"";s:3:"鵫";s:2:"";s:3:"鵴";s:2:"";s:3:"鵵";s:2:"";s:3:"鵰";s:2:"";s:3:"鵩";s:2:"";s:3:"鶅";s:2:"";s:3:"鵳";s:2:"";s:3:"鵻";s:2:"";s:3:"鶂";s:2:"";s:3:"鵯";s:2:"";s:3:"鵹";s:2:"";s:3:"鵿";s:2:"";s:3:"鶇";s:2:"";s:3:"鵨";s:2:"";s:3:"麔";s:2:"";s:3:"麑";s:2:"";s:3:"黀";s:2:"";s:3:"黼";s:2:"";s:3:"鼭";s:2:"";s:3:"齀";s:2:"";s:3:"齁";s:2:"";s:3:"齍";s:2:"";s:3:"齖";s:2:"";s:3:"齗";s:2:"";s:3:"齘";s:2:"";s:3:"匷";s:2:"";s:3:"嚲";s:2:"@";s:3:"嚵";s:2:"A";s:3:"嚳";s:2:"B";s:3:"壣";s:2:"C";s:3:"孅";s:2:"D";s:3:"巆";s:2:"E";s:3:"巇";s:2:"F";s:3:"廮";s:2:"G";s:3:"廯";s:2:"H";s:3:"忀";s:2:"I";s:3:"忁";s:2:"J";s:3:"懹";s:2:"K";s:3:"攗";s:2:"L";s:3:"攖";s:2:"M";s:3:"攕";s:2:"N";s:3:"攓";s:2:"O";s:3:"旟";s:2:"P";s:3:"曨";s:2:"Q";s:3:"曣";s:2:"R";s:3:"曤";s:2:"S";s:3:"櫳";s:2:"T";s:3:"櫰";s:2:"U";s:3:"櫪";s:2:"V";s:3:"櫨";s:2:"W";s:3:"櫹";s:2:"X";s:3:"櫱";s:2:"Y";s:3:"櫮";s:2:"Z";s:3:"櫯";s:2:"[";s:3:"瀼";s:2:"\";s:3:"瀵";s:2:"]";s:3:"瀯";s:2:"^";s:3:"瀷";s:2:"_";s:3:"瀴";s:2:"`";s:3:"瀱";s:2:"a";s:3:"灂";s:2:"b";s:3:"瀸";s:2:"c";s:3:"瀿";s:2:"d";s:3:"瀺";s:2:"e";s:3:"瀹";s:2:"f";s:3:"灀";s:2:"g";s:3:"瀻";s:2:"h";s:3:"瀳";s:2:"i";s:3:"灁";s:2:"j";s:3:"爓";s:2:"k";s:3:"爔";s:2:"l";s:3:"犨";s:2:"m";s:3:"獽";s:2:"n";s:3:"獼";s:2:"o";s:3:"璺";s:2:"p";s:3:"皫";s:2:"q";s:3:"皪";s:2:"r";s:3:"皾";s:2:"s";s:3:"盭";s:2:"t";s:3:"矌";s:2:"u";s:3:"矎";s:2:"v";s:3:"矏";s:2:"w";s:3:"矍";s:2:"x";s:3:"矲";s:2:"y";s:3:"礥";s:2:"z";s:3:"礣";s:2:"{";s:3:"礧";s:2:"|";s:3:"礨";s:2:"}";s:3:"礤";s:2:"~";s:3:"礩";s:2:"";s:3:"禲";s:2:"";s:3:"穮";s:2:"";s:3:"穬";s:2:"";s:3:"穭";s:2:"";s:3:"竷";s:2:"";s:3:"籉";s:2:"";s:3:"籈";s:2:"";s:3:"籊";s:2:"";s:3:"籇";s:2:"";s:3:"籅";s:2:"";s:3:"糮";s:2:"";s:3:"繻";s:2:"";s:3:"繾";s:2:"";s:3:"纁";s:2:"";s:3:"纀";s:2:"";s:3:"羺";s:2:"";s:3:"翿";s:2:"";s:3:"聹";s:2:"";s:3:"臛";s:2:"";s:3:"臙";s:2:"";s:3:"舋";s:2:"";s:3:"艨";s:2:"";s:3:"艩";s:2:"";s:3:"蘢";s:2:"";s:3:"藿";s:2:"";s:3:"蘁";s:2:"";s:3:"藾";s:2:"";s:3:"蘛";s:2:"";s:3:"蘀";s:2:"";s:3:"藶";s:2:"";s:3:"蘄";s:2:"";s:3:"蘉";s:2:"";s:3:"蘅";s:2:"";s:3:"蘌";s:2:"";s:3:"藽";s:2:"";s:3:"蠙";s:2:"";s:3:"蠐";s:2:"";s:3:"蠑";s:2:"";s:3:"蠗";s:2:"";s:3:"蠓";s:2:"";s:3:"蠖";s:2:"";s:3:"襣";s:2:"";s:3:"襦";s:2:"";s:3:"覹";s:2:"";s:3:"觷";s:2:"";s:3:"譠";s:2:"";s:3:"譪";s:2:"";s:3:"譝";s:2:"";s:3:"譨";s:2:"";s:3:"譣";s:2:"";s:3:"譥";s:2:"";s:3:"譧";s:2:"";s:3:"譭";s:2:"";s:3:"趮";s:2:"";s:3:"躆";s:2:"";s:3:"躈";s:2:"";s:3:"躄";s:2:"";s:3:"轙";s:2:"";s:3:"轖";s:2:"";s:3:"轗";s:2:"";s:3:"轕";s:2:"";s:3:"轘";s:2:"";s:3:"轚";s:2:"";s:3:"邍";s:2:"";s:3:"酃";s:2:"";s:3:"酁";s:2:"";s:3:"醷";s:2:"";s:3:"醵";s:2:"";s:3:"醲";s:2:"";s:3:"醳";s:2:"";s:3:"鐋";s:2:"";s:3:"鐓";s:2:"";s:3:"鏻";s:2:"";s:3:"鐠";s:2:"";s:3:"鐏";s:2:"";s:3:"鐔";s:2:"";s:3:"鏾";s:2:"";s:3:"鐕";s:2:"";s:3:"鐐";s:2:"";s:3:"鐨";s:2:"";s:3:"鐙";s:2:"";s:3:"鐍";s:2:"";s:3:"鏵";s:2:"";s:3:"鐀";s:2:"";s:3:"鏷";s:2:"";s:3:"鐇";s:2:"";s:3:"鐎";s:2:"";s:3:"鐖";s:2:"";s:3:"鐒";s:2:"";s:3:"鏺";s:2:"";s:3:"鐉";s:2:"";s:3:"鏸";s:2:"";s:3:"鐊";s:2:"";s:3:"鏿";s:2:"@";s:3:"鏼";s:2:"A";s:3:"鐌";s:2:"B";s:3:"鏶";s:2:"C";s:3:"鐑";s:2:"D";s:3:"鐆";s:2:"E";s:3:"闞";s:2:"F";s:3:"闠";s:2:"G";s:3:"闟";s:2:"H";s:3:"霮";s:2:"I";s:3:"霯";s:2:"J";s:3:"鞹";s:2:"K";s:3:"鞻";s:2:"L";s:3:"韽";s:2:"M";s:3:"韾";s:2:"N";s:3:"顠";s:2:"O";s:3:"顢";s:2:"P";s:3:"顣";s:2:"Q";s:3:"顟";s:2:"R";s:3:"飁";s:2:"S";s:3:"飂";s:2:"T";s:3:"饐";s:2:"U";s:3:"饎";s:2:"V";s:3:"饙";s:2:"W";s:3:"饌";s:2:"X";s:3:"饋";s:2:"Y";s:3:"饓";s:2:"Z";s:3:"騲";s:2:"[";s:3:"騴";s:2:"\";s:3:"騱";s:2:"]";s:3:"騬";s:2:"^";s:3:"騪";s:2:"_";s:3:"騶";s:2:"`";s:3:"騩";s:2:"a";s:3:"騮";s:2:"b";s:3:"騸";s:2:"c";s:3:"騭";s:2:"d";s:3:"髇";s:2:"e";s:3:"髊";s:2:"f";s:3:"髆";s:2:"g";s:3:"鬐";s:2:"h";s:3:"鬒";s:2:"i";s:3:"鬑";s:2:"j";s:3:"鰋";s:2:"k";s:3:"鰈";s:2:"l";s:3:"鯷";s:2:"m";s:3:"鰅";s:2:"n";s:3:"鰒";s:2:"o";s:3:"鯸";s:2:"p";s:3:"鱀";s:2:"q";s:3:"鰇";s:2:"r";s:3:"鰎";s:2:"s";s:3:"鰆";s:2:"t";s:3:"鰗";s:2:"u";s:3:"鰔";s:2:"v";s:3:"鰉";s:2:"w";s:3:"鶟";s:2:"x";s:3:"鶙";s:2:"y";s:3:"鶤";s:2:"z";s:3:"鶝";s:2:"{";s:3:"鶒";s:2:"|";s:3:"鶘";s:2:"}";s:3:"鶐";s:2:"~";s:3:"鶛";s:2:"";s:3:"鶠";s:2:"";s:3:"鶔";s:2:"";s:3:"鶜";s:2:"";s:3:"鶪";s:2:"";s:3:"鶗";s:2:"";s:3:"鶡";s:2:"";s:3:"鶚";s:2:"";s:3:"鶢";s:2:"";s:3:"鶨";s:2:"";s:3:"鶞";s:2:"";s:3:"鶣";s:2:"";s:3:"鶿";s:2:"";s:3:"鶩";s:2:"";s:3:"鶖";s:2:"";s:3:"鶦";s:2:"";s:3:"鶧";s:2:"";s:3:"麙";s:2:"";s:3:"麛";s:2:"";s:3:"麚";s:2:"";s:3:"黥";s:2:"";s:3:"黤";s:2:"";s:3:"黧";s:2:"";s:3:"黦";s:2:"";s:3:"鼰";s:2:"";s:3:"鼮";s:2:"";s:3:"齛";s:2:"";s:3:"齠";s:2:"";s:3:"齞";s:2:"";s:3:"齝";s:2:"";s:3:"齙";s:2:"";s:3:"龑";s:2:"";s:3:"儺";s:2:"";s:3:"儹";s:2:"";s:3:"劘";s:2:"";s:3:"劗";s:2:"";s:3:"囃";s:2:"";s:3:"嚽";s:2:"";s:3:"嚾";s:2:"";s:3:"孈";s:2:"";s:3:"孇";s:2:"";s:3:"巋";s:2:"";s:3:"巏";s:2:"";s:3:"廱";s:2:"";s:3:"懽";s:2:"";s:3:"攛";s:2:"";s:3:"欂";s:2:"";s:3:"櫼";s:2:"";s:3:"欃";s:2:"";s:3:"櫸";s:2:"";s:3:"欀";s:2:"";s:3:"灃";s:2:"";s:3:"灄";s:2:"";s:3:"灊";s:2:"";s:3:"灈";s:2:"";s:3:"灉";s:2:"";s:3:"灅";s:2:"";s:3:"灆";s:2:"";s:3:"爝";s:2:"";s:3:"爚";s:2:"";s:3:"爙";s:2:"";s:3:"獾";s:2:"";s:3:"甗";s:2:"";s:3:"癪";s:2:"";s:3:"矐";s:2:"";s:3:"礭";s:2:"";s:3:"礱";s:2:"";s:3:"礯";s:2:"";s:3:"籔";s:2:"";s:3:"籓";s:2:"";s:3:"糲";s:2:"";s:3:"纊";s:2:"";s:3:"纇";s:2:"";s:3:"纈";s:2:"";s:3:"纋";s:2:"";s:3:"纆";s:2:"";s:3:"纍";s:2:"";s:3:"罍";s:2:"";s:3:"羻";s:2:"";s:3:"耰";s:2:"";s:3:"臝";s:2:"";s:3:"蘘";s:2:"";s:3:"蘪";s:2:"";s:3:"蘦";s:2:"";s:3:"蘟";s:2:"";s:3:"蘣";s:2:"";s:3:"蘜";s:2:"";s:3:"蘙";s:2:"";s:3:"蘧";s:2:"";s:3:"蘮";s:2:"";s:3:"蘡";s:2:"";s:3:"蘠";s:2:"";s:3:"蘩";s:2:"";s:3:"蘞";s:2:"";s:3:"蘥";s:2:"@";s:3:"蠩";s:2:"A";s:3:"蠝";s:2:"B";s:3:"蠛";s:2:"C";s:3:"蠠";s:2:"D";s:3:"蠤";s:2:"E";s:3:"蠜";s:2:"F";s:3:"蠫";s:2:"G";s:3:"衊";s:2:"H";s:3:"襭";s:2:"I";s:3:"襩";s:2:"J";s:3:"襮";s:2:"K";s:3:"襫";s:2:"L";s:3:"觺";s:2:"M";s:3:"譹";s:2:"N";s:3:"譸";s:2:"O";s:3:"譅";s:2:"P";s:3:"譺";s:2:"Q";s:3:"譻";s:2:"R";s:3:"贐";s:2:"S";s:3:"贔";s:2:"T";s:3:"趯";s:2:"U";s:3:"躎";s:2:"V";s:3:"躌";s:2:"W";s:3:"轞";s:2:"X";s:3:"轛";s:2:"Y";s:3:"轝";s:2:"Z";s:3:"酆";s:2:"[";s:3:"酄";s:2:"\";s:3:"酅";s:2:"]";s:3:"醹";s:2:"^";s:3:"鐿";s:2:"_";s:3:"鐻";s:2:"`";s:3:"鐶";s:2:"a";s:3:"鐩";s:2:"b";s:3:"鐽";s:2:"c";s:3:"鐼";s:2:"d";s:3:"鐰";s:2:"e";s:3:"鐹";s:2:"f";s:3:"鐪";s:2:"g";s:3:"鐷";s:2:"h";s:3:"鐬";s:2:"i";s:3:"鑀";s:2:"j";s:3:"鐱";s:2:"k";s:3:"闥";s:2:"l";s:3:"闤";s:2:"m";s:3:"闣";s:2:"n";s:3:"霵";s:2:"o";s:3:"霺";s:2:"p";s:3:"鞿";s:2:"q";s:3:"韡";s:2:"r";s:3:"顤";s:2:"s";s:3:"飉";s:2:"t";s:3:"飆";s:2:"u";s:3:"飀";s:2:"v";s:3:"饘";s:2:"w";s:3:"饖";s:2:"x";s:3:"騹";s:2:"y";s:3:"騽";s:2:"z";s:3:"驆";s:2:"{";s:3:"驄";s:2:"|";s:3:"驂";s:2:"}";s:3:"驁";s:2:"~";s:3:"騺";s:2:"";s:3:"騿";s:2:"";s:3:"髍";s:2:"";s:3:"鬕";s:2:"";s:3:"鬗";s:2:"";s:3:"鬘";s:2:"";s:3:"鬖";s:2:"";s:3:"鬺";s:2:"";s:3:"魒";s:2:"";s:3:"鰫";s:2:"";s:3:"鰝";s:2:"";s:3:"鰜";s:2:"";s:3:"鰬";s:2:"";s:3:"鰣";s:2:"";s:3:"鰨";s:2:"";s:3:"鰩";s:2:"";s:3:"鰤";s:2:"";s:3:"鰡";s:2:"";s:3:"鶷";s:2:"";s:3:"鶶";s:2:"";s:3:"鶼";s:2:"";s:3:"鷁";s:2:"";s:3:"鷇";s:2:"";s:3:"鷊";s:2:"";s:3:"鷏";s:2:"";s:3:"鶾";s:2:"";s:3:"鷅";s:2:"";s:3:"鷃";s:2:"";s:3:"鶻";s:2:"";s:3:"鶵";s:2:"";s:3:"鷎";s:2:"";s:3:"鶹";s:2:"";s:3:"鶺";s:2:"";s:3:"鶬";s:2:"";s:3:"鷈";s:2:"";s:3:"鶱";s:2:"";s:3:"鶭";s:2:"";s:3:"鷌";s:2:"";s:3:"鶳";s:2:"";s:3:"鷍";s:2:"";s:3:"鶲";s:2:"";s:3:"鹺";s:2:"";s:3:"麜";s:2:"";s:3:"黫";s:2:"";s:3:"黮";s:2:"";s:3:"黭";s:2:"";s:3:"鼛";s:2:"";s:3:"鼘";s:2:"";s:3:"鼚";s:2:"";s:3:"鼱";s:2:"";s:3:"齎";s:2:"";s:3:"齥";s:2:"";s:3:"齤";s:2:"";s:3:"龒";s:2:"";s:3:"亹";s:2:"";s:3:"囆";s:2:"";s:3:"囅";s:2:"";s:3:"囋";s:2:"";s:3:"奱";s:2:"";s:3:"孋";s:2:"";s:3:"孌";s:2:"";s:3:"巕";s:2:"";s:3:"巑";s:2:"";s:3:"廲";s:2:"";s:3:"攡";s:2:"";s:3:"攠";s:2:"";s:3:"攦";s:2:"";s:3:"攢";s:2:"";s:3:"欋";s:2:"";s:3:"欈";s:2:"";s:3:"欉";s:2:"";s:3:"氍";s:2:"";s:3:"灕";s:2:"";s:3:"灖";s:2:"";s:3:"灗";s:2:"";s:3:"灒";s:2:"";s:3:"爞";s:2:"";s:3:"爟";s:2:"";s:3:"犩";s:2:"";s:3:"獿";s:2:"";s:3:"瓘";s:2:"";s:3:"瓕";s:2:"";s:3:"瓙";s:2:"";s:3:"瓗";s:2:"";s:3:"癭";s:2:"";s:3:"皭";s:2:"";s:3:"礵";s:2:"";s:3:"禴";s:2:"";s:3:"穰";s:2:"";s:3:"穱";s:2:"";s:3:"籗";s:2:"";s:3:"籜";s:2:"";s:3:"籙";s:2:"";s:3:"籛";s:2:"";s:3:"籚";s:2:"@";s:3:"糴";s:2:"A";s:3:"糱";s:2:"B";s:3:"纑";s:2:"C";s:3:"罏";s:2:"D";s:3:"羇";s:2:"E";s:3:"臞";s:2:"F";s:3:"艫";s:2:"G";s:3:"蘴";s:2:"H";s:3:"蘵";s:2:"I";s:3:"蘳";s:2:"J";s:3:"蘬";s:2:"K";s:3:"蘲";s:2:"L";s:3:"蘶";s:2:"M";s:3:"蠬";s:2:"N";s:3:"蠨";s:2:"O";s:3:"蠦";s:2:"P";s:3:"蠪";s:2:"Q";s:3:"蠥";s:2:"R";s:3:"襱";s:2:"S";s:3:"覿";s:2:"T";s:3:"覾";s:2:"U";s:3:"觻";s:2:"V";s:3:"譾";s:2:"W";s:3:"讄";s:2:"X";s:3:"讂";s:2:"Y";s:3:"讆";s:2:"Z";s:3:"讅";s:2:"[";s:3:"譿";s:2:"\";s:3:"贕";s:2:"]";s:3:"躕";s:2:"^";s:3:"躔";s:2:"_";s:3:"躚";s:2:"`";s:3:"躒";s:2:"a";s:3:"躐";s:2:"b";s:3:"躖";s:2:"c";s:3:"躗";s:2:"d";s:3:"轠";s:2:"e";s:3:"轢";s:2:"f";s:3:"酇";s:2:"g";s:3:"鑌";s:2:"h";s:3:"鑐";s:2:"i";s:3:"鑊";s:2:"j";s:3:"鑋";s:2:"k";s:3:"鑏";s:2:"l";s:3:"鑇";s:2:"m";s:3:"鑅";s:2:"n";s:3:"鑈";s:2:"o";s:3:"鑉";s:2:"p";s:3:"鑆";s:2:"q";s:3:"霿";s:2:"r";s:3:"韣";s:2:"s";s:3:"顪";s:2:"t";s:3:"顩";s:2:"u";s:3:"飋";s:2:"v";s:3:"饔";s:2:"w";s:3:"饛";s:2:"x";s:3:"驎";s:2:"y";s:3:"驓";s:2:"z";s:3:"驔";s:2:"{";s:3:"驌";s:2:"|";s:3:"驏";s:2:"}";s:3:"驈";s:2:"~";s:3:"驊";s:2:"";s:3:"驉";s:2:"";s:3:"驒";s:2:"";s:3:"驐";s:2:"";s:3:"髐";s:2:"";s:3:"鬙";s:2:"";s:3:"鬫";s:2:"";s:3:"鬻";s:2:"";s:3:"魖";s:2:"";s:3:"魕";s:2:"";s:3:"鱆";s:2:"";s:3:"鱈";s:2:"";s:3:"鰿";s:2:"";s:3:"鱄";s:2:"";s:3:"鰹";s:2:"";s:3:"鰳";s:2:"";s:3:"鱁";s:2:"";s:3:"鰼";s:2:"";s:3:"鰷";s:2:"";s:3:"鰴";s:2:"";s:3:"鰲";s:2:"";s:3:"鰽";s:2:"";s:3:"鰶";s:2:"";s:3:"鷛";s:2:"";s:3:"鷒";s:2:"";s:3:"鷞";s:2:"";s:3:"鷚";s:2:"";s:3:"鷋";s:2:"";s:3:"鷐";s:2:"";s:3:"鷜";s:2:"";s:3:"鷑";s:2:"";s:3:"鷟";s:2:"";s:3:"鷩";s:2:"";s:3:"鷙";s:2:"";s:3:"鷘";s:2:"";s:3:"鷖";s:2:"";s:3:"鷵";s:2:"";s:3:"鷕";s:2:"";s:3:"鷝";s:2:"";s:3:"麶";s:2:"";s:3:"黰";s:2:"";s:3:"鼵";s:2:"";s:3:"鼳";s:2:"";s:3:"鼲";s:2:"";s:3:"齂";s:2:"";s:3:"齫";s:2:"";s:3:"龕";s:2:"";s:3:"龢";s:2:"";s:3:"儽";s:2:"";s:3:"劙";s:2:"";s:3:"壨";s:2:"";s:3:"壧";s:2:"";s:3:"奲";s:2:"";s:3:"孍";s:2:"";s:3:"巘";s:2:"";s:3:"蠯";s:2:"";s:3:"彏";s:2:"";s:3:"戁";s:2:"";s:3:"戃";s:2:"";s:3:"戄";s:2:"";s:3:"攩";s:2:"";s:3:"攥";s:2:"";s:3:"斖";s:2:"";s:3:"曫";s:2:"";s:3:"欑";s:2:"";s:3:"欒";s:2:"";s:3:"欏";s:2:"";s:3:"毊";s:2:"";s:3:"灛";s:2:"";s:3:"灚";s:2:"";s:3:"爢";s:2:"";s:3:"玂";s:2:"";s:3:"玁";s:2:"";s:3:"玃";s:2:"";s:3:"癰";s:2:"";s:3:"矔";s:2:"";s:3:"籧";s:2:"";s:3:"籦";s:2:"";s:3:"纕";s:2:"";s:3:"艬";s:2:"";s:3:"蘺";s:2:"";s:3:"虀";s:2:"";s:3:"蘹";s:2:"";s:3:"蘼";s:2:"";s:3:"蘱";s:2:"";s:3:"蘻";s:2:"";s:3:"蘾";s:2:"";s:3:"蠰";s:2:"";s:3:"蠲";s:2:"";s:3:"蠮";s:2:"";s:3:"蠳";s:2:"";s:3:"襶";s:2:"";s:3:"襴";s:2:"";s:3:"襳";s:2:"";s:3:"觾";s:2:"@";s:3:"讌";s:2:"A";s:3:"讎";s:2:"B";s:3:"讋";s:2:"C";s:3:"讈";s:2:"D";s:3:"豅";s:2:"E";s:3:"贙";s:2:"F";s:3:"躘";s:2:"G";s:3:"轤";s:2:"H";s:3:"轣";s:2:"I";s:3:"醼";s:2:"J";s:3:"鑢";s:2:"K";s:3:"鑕";s:2:"L";s:3:"鑝";s:2:"M";s:3:"鑗";s:2:"N";s:3:"鑞";s:2:"O";s:3:"韄";s:2:"P";s:3:"韅";s:2:"Q";s:3:"頀";s:2:"R";s:3:"驖";s:2:"S";s:3:"驙";s:2:"T";s:3:"鬞";s:2:"U";s:3:"鬟";s:2:"V";s:3:"鬠";s:2:"W";s:3:"鱒";s:2:"X";s:3:"鱘";s:2:"Y";s:3:"鱐";s:2:"Z";s:3:"鱊";s:2:"[";s:3:"鱍";s:2:"\";s:3:"鱋";s:2:"]";s:3:"鱕";s:2:"^";s:3:"鱙";s:2:"_";s:3:"鱌";s:2:"`";s:3:"鱎";s:2:"a";s:3:"鷻";s:2:"b";s:3:"鷷";s:2:"c";s:3:"鷯";s:2:"d";s:3:"鷣";s:2:"e";s:3:"鷫";s:2:"f";s:3:"鷸";s:2:"g";s:3:"鷤";s:2:"h";s:3:"鷶";s:2:"i";s:3:"鷡";s:2:"j";s:3:"鷮";s:2:"k";s:3:"鷦";s:2:"l";s:3:"鷲";s:2:"m";s:3:"鷰";s:2:"n";s:3:"鷢";s:2:"o";s:3:"鷬";s:2:"p";s:3:"鷴";s:2:"q";s:3:"鷳";s:2:"r";s:3:"鷨";s:2:"s";s:3:"鷭";s:2:"t";s:3:"黂";s:2:"u";s:3:"黐";s:2:"v";s:3:"黲";s:2:"w";s:3:"黳";s:2:"x";s:3:"鼆";s:2:"y";s:3:"鼜";s:2:"z";s:3:"鼸";s:2:"{";s:3:"鼷";s:2:"|";s:3:"鼶";s:2:"}";s:3:"齃";s:2:"~";s:3:"齏";s:2:"";s:3:"齱";s:2:"";s:3:"齰";s:2:"";s:3:"齮";s:2:"";s:3:"齯";s:2:"";s:3:"囓";s:2:"";s:3:"囍";s:2:"";s:3:"孎";s:2:"";s:3:"屭";s:2:"";s:3:"攭";s:2:"";s:3:"曭";s:2:"";s:3:"曮";s:2:"";s:3:"欓";s:2:"";s:3:"灟";s:2:"";s:3:"灡";s:2:"";s:3:"灝";s:2:"";s:3:"灠";s:2:"";s:3:"爣";s:2:"";s:3:"瓛";s:2:"";s:3:"瓥";s:2:"";s:3:"矕";s:2:"";s:3:"礸";s:2:"";s:3:"禷";s:2:"";s:3:"禶";s:2:"";s:3:"籪";s:2:"";s:3:"纗";s:2:"";s:3:"羉";s:2:"";s:3:"艭";s:2:"";s:3:"虃";s:2:"";s:3:"蠸";s:2:"";s:3:"蠷";s:2:"";s:3:"蠵";s:2:"";s:3:"衋";s:2:"";s:3:"讔";s:2:"";s:3:"讕";s:2:"";s:3:"躞";s:2:"";s:3:"躟";s:2:"";s:3:"躠";s:2:"";s:3:"躝";s:2:"";s:3:"醾";s:2:"";s:3:"醽";s:2:"";s:3:"釂";s:2:"";s:3:"鑫";s:2:"";s:3:"鑨";s:2:"";s:3:"鑩";s:2:"";s:3:"雥";s:2:"";s:3:"靆";s:2:"";s:3:"靃";s:2:"";s:3:"靇";s:2:"";s:3:"韇";s:2:"";s:3:"韥";s:2:"";s:3:"驞";s:2:"";s:3:"髕";s:2:"";s:3:"魙";s:2:"";s:3:"鱣";s:2:"";s:3:"鱧";s:2:"";s:3:"鱦";s:2:"";s:3:"鱢";s:2:"";s:3:"鱞";s:2:"";s:3:"鱠";s:2:"";s:3:"鸂";s:2:"";s:3:"鷾";s:2:"";s:3:"鸇";s:2:"";s:3:"鸃";s:2:"";s:3:"鸆";s:2:"";s:3:"鸅";s:2:"";s:3:"鸀";s:2:"";s:3:"鸁";s:2:"";s:3:"鸉";s:2:"";s:3:"鷿";s:2:"";s:3:"鷽";s:2:"";s:3:"鸄";s:2:"";s:3:"麠";s:2:"";s:3:"鼞";s:2:"";s:3:"齆";s:2:"";s:3:"齴";s:2:"";s:3:"齵";s:2:"";s:3:"齶";s:2:"";s:3:"囔";s:2:"";s:3:"攮";s:2:"";s:3:"斸";s:2:"";s:3:"欘";s:2:"";s:3:"欙";s:2:"";s:3:"欗";s:2:"";s:3:"欚";s:2:"";s:3:"灢";s:2:"";s:3:"爦";s:2:"";s:3:"犪";s:2:"";s:3:"矘";s:2:"";s:3:"矙";s:2:"";s:3:"礹";s:2:"";s:3:"籩";s:2:"";s:3:"籫";s:2:"";s:3:"糶";s:2:"";s:3:"纚";s:2:"@";s:3:"纘";s:2:"A";s:3:"纛";s:2:"B";s:3:"纙";s:2:"C";s:3:"臠";s:2:"D";s:3:"臡";s:2:"E";s:3:"虆";s:2:"F";s:3:"虇";s:2:"G";s:3:"虈";s:2:"H";s:3:"襹";s:2:"I";s:3:"襺";s:2:"J";s:3:"襼";s:2:"K";s:3:"襻";s:2:"L";s:3:"觿";s:2:"M";s:3:"讘";s:2:"N";s:3:"讙";s:2:"O";s:3:"躥";s:2:"P";s:3:"躤";s:2:"Q";s:3:"躣";s:2:"R";s:3:"鑮";s:2:"S";s:3:"鑭";s:2:"T";s:3:"鑯";s:2:"U";s:3:"鑱";s:2:"V";s:3:"鑳";s:2:"W";s:3:"靉";s:2:"X";s:3:"顲";s:2:"Y";s:3:"饟";s:2:"Z";s:3:"鱨";s:2:"[";s:3:"鱮";s:2:"\";s:3:"鱭";s:2:"]";s:3:"鸋";s:2:"^";s:3:"鸍";s:2:"_";s:3:"鸐";s:2:"`";s:3:"鸏";s:2:"a";s:3:"鸒";s:2:"b";s:3:"鸑";s:2:"c";s:3:"麡";s:2:"d";s:3:"黵";s:2:"e";s:3:"鼉";s:2:"f";s:3:"齇";s:2:"g";s:3:"齸";s:2:"h";s:3:"齻";s:2:"i";s:3:"齺";s:2:"j";s:3:"齹";s:2:"k";s:3:"圞";s:2:"l";s:3:"灦";s:2:"m";s:3:"籯";s:2:"n";s:3:"蠼";s:2:"o";s:3:"趲";s:2:"p";s:3:"躦";s:2:"q";s:3:"釃";s:2:"r";s:3:"鑴";s:2:"s";s:3:"鑸";s:2:"t";s:3:"鑶";s:2:"u";s:3:"鑵";s:2:"v";s:3:"驠";s:2:"w";s:3:"鱴";s:2:"x";s:3:"鱳";s:2:"y";s:3:"鱱";s:2:"z";s:3:"鱵";s:2:"{";s:3:"鸔";s:2:"|";s:3:"鸓";s:2:"}";s:3:"黶";s:2:"~";s:3:"鼊";s:2:"";s:3:"龤";s:2:"";s:3:"灨";s:2:"";s:3:"灥";s:2:"";s:3:"糷";s:2:"";s:3:"虪";s:2:"";s:3:"蠾";s:2:"";s:3:"蠽";s:2:"";s:3:"蠿";s:2:"";s:3:"讞";s:2:"";s:3:"貜";s:2:"";s:3:"躩";s:2:"";s:3:"軉";s:2:"";s:3:"靋";s:2:"";s:3:"顳";s:2:"";s:3:"顴";s:2:"";s:3:"飌";s:2:"";s:3:"饡";s:2:"";s:3:"馫";s:2:"";s:3:"驤";s:2:"";s:3:"驦";s:2:"";s:3:"驧";s:2:"";s:3:"鬤";s:2:"";s:3:"鸕";s:2:"";s:3:"鸗";s:2:"";s:3:"齈";s:2:"";s:3:"戇";s:2:"";s:3:"欞";s:2:"";s:3:"爧";s:2:"";s:3:"虌";s:2:"";s:3:"躨";s:2:"";s:3:"钂";s:2:"";s:3:"钀";s:2:"";s:3:"钁";s:2:"";s:3:"驩";s:2:"";s:3:"驨";s:2:"";s:3:"鬮";s:2:"";s:3:"鸙";s:2:"";s:3:"爩";s:2:"";s:3:"虋";s:2:"";s:3:"讟";s:2:"";s:3:"钃";s:2:"";s:3:"鱹";s:2:"";s:3:"麷";s:2:"";s:3:"癵";s:2:"";s:3:"驫";s:2:"";s:3:"鱺";s:2:"";s:3:"鸝";s:2:"";s:3:"灩";s:2:"";s:3:"灪";s:2:"";s:3:"麤";s:2:"";s:3:"齾";s:2:"";s:3:"齉";s:2:"";s:3:"龘";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp037.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp037.ser new file mode 100644 index 0000000..a629eb4 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp037.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp1006.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp1006.ser new file mode 100644 index 0000000..d3a6c4c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp1006.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp1026.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp1026.ser new file mode 100644 index 0000000..8ec9bea Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp1026.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp424.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp424.ser new file mode 100644 index 0000000..5e1168e Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp424.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp437.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp437.ser new file mode 100644 index 0000000..aa20fd8 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp437.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp500.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp500.ser new file mode 100644 index 0000000..9bdfdfb Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp500.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp737.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp737.ser new file mode 100644 index 0000000..d586ac9 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp737.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp775.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp775.ser new file mode 100644 index 0000000..c993ef5 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp775.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp850.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp850.ser new file mode 100644 index 0000000..8035a91 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp850.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp852.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp852.ser new file mode 100644 index 0000000..83d6b3c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp852.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp855.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp855.ser new file mode 100644 index 0000000..2b99b4c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp855.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp856.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp856.ser new file mode 100644 index 0000000..bcc21fe Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp856.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp857.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp857.ser new file mode 100644 index 0000000..a7074a4 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp857.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp860.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp860.ser new file mode 100644 index 0000000..848d9fe Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp860.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp861.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp861.ser new file mode 100644 index 0000000..e0469a6 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp861.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp862.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp862.ser new file mode 100644 index 0000000..eabcebf Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp862.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp863.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp863.ser new file mode 100644 index 0000000..a64d70a Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp863.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp864.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp864.ser new file mode 100644 index 0000000..1d9c371 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp864.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp865.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp865.ser new file mode 100644 index 0000000..816ddf3 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp865.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp866.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp866.ser new file mode 100644 index 0000000..8e0f7dc Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp866.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp869.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp869.ser new file mode 100644 index 0000000..32de1f2 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp869.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp874.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp874.ser new file mode 100644 index 0000000..030ce15 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp874.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp875.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp875.ser new file mode 100644 index 0000000..e42792a Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp875.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp932.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp932.ser new file mode 100644 index 0000000..5861d4c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp932.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp936.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp936.ser new file mode 100644 index 0000000..8fc183b Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp936.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp949.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp949.ser new file mode 100644 index 0000000..4447cdb Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp949.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp950.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp950.ser new file mode 100644 index 0000000..b2f11df Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.cp950.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.gsm0338.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.gsm0338.ser new file mode 100644 index 0000000..a877b83 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.gsm0338.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-1.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-1.ser new file mode 100644 index 0000000..5fecd4c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-1.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-10.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-10.ser new file mode 100644 index 0000000..3f0cce1 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-10.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-11.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-11.ser new file mode 100644 index 0000000..2757b1c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-11.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-13.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-13.ser new file mode 100644 index 0000000..55a7a3e Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-13.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-14.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-14.ser new file mode 100644 index 0000000..7b963ae Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-14.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-15.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-15.ser new file mode 100644 index 0000000..2ce20a1 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-15.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-16.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-16.ser new file mode 100644 index 0000000..8da2482 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-16.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-2.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-2.ser new file mode 100644 index 0000000..7dbeb25 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-2.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-3.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-3.ser new file mode 100644 index 0000000..626fffe Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-3.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-4.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-4.ser new file mode 100644 index 0000000..f750169 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-4.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-5.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-5.ser new file mode 100644 index 0000000..d89cc06 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-5.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-6.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-6.ser new file mode 100644 index 0000000..2dcfeb8 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-6.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-7.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-7.ser new file mode 100644 index 0000000..a137558 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-7.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-8.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-8.ser new file mode 100644 index 0000000..4179e15 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-8.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-9.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-9.ser new file mode 100644 index 0000000..be4c816 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.iso-8859-9.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.koi8-r.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.koi8-r.ser new file mode 100644 index 0000000..c46860b Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.koi8-r.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.koi8-u.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.koi8-u.ser new file mode 100644 index 0000000..6e5af63 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.koi8-u.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.mazovia.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.mazovia.ser new file mode 100644 index 0000000..05fa2bd Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.mazovia.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.nextstep.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.nextstep.ser new file mode 100644 index 0000000..476da9c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.nextstep.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.stdenc.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.stdenc.ser new file mode 100644 index 0000000..81edeb0 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.stdenc.ser @@ -0,0 +1 @@ +a:149:{s:1:" ";s:1:" ";s:1:"!";s:1:"!";s:1:""";s:1:""";s:1:"#";s:1:"#";s:1:"$";s:1:"$";s:1:"%";s:1:"%";s:1:"&";s:1:"&";s:1:"'";s:3:"’";s:1:"(";s:1:"(";s:1:")";s:1:")";s:1:"*";s:1:"*";s:1:"+";s:1:"+";s:1:",";s:1:",";s:1:"-";s:1:"-";s:1:".";s:1:".";s:1:"/";s:1:"/";i:0;s:1:"0";i:1;s:1:"1";i:2;s:1:"2";i:3;s:1:"3";i:4;s:1:"4";i:5;s:1:"5";i:6;s:1:"6";i:7;s:1:"7";i:8;s:1:"8";i:9;s:1:"9";s:1:":";s:1:":";s:1:";";s:1:";";s:1:"<";s:1:"<";s:1:"=";s:1:"=";s:1:">";s:1:">";s:1:"?";s:1:"?";s:1:"@";s:1:"@";s:1:"A";s:1:"A";s:1:"B";s:1:"B";s:1:"C";s:1:"C";s:1:"D";s:1:"D";s:1:"E";s:1:"E";s:1:"F";s:1:"F";s:1:"G";s:1:"G";s:1:"H";s:1:"H";s:1:"I";s:1:"I";s:1:"J";s:1:"J";s:1:"K";s:1:"K";s:1:"L";s:1:"L";s:1:"M";s:1:"M";s:1:"N";s:1:"N";s:1:"O";s:1:"O";s:1:"P";s:1:"P";s:1:"Q";s:1:"Q";s:1:"R";s:1:"R";s:1:"S";s:1:"S";s:1:"T";s:1:"T";s:1:"U";s:1:"U";s:1:"V";s:1:"V";s:1:"W";s:1:"W";s:1:"X";s:1:"X";s:1:"Y";s:1:"Y";s:1:"Z";s:1:"Z";s:1:"[";s:1:"[";s:1:"\";s:1:"\";s:1:"]";s:1:"]";s:1:"^";s:1:"^";s:1:"_";s:1:"_";s:1:"`";s:3:"‘";s:1:"a";s:1:"a";s:1:"b";s:1:"b";s:1:"c";s:1:"c";s:1:"d";s:1:"d";s:1:"e";s:1:"e";s:1:"f";s:1:"f";s:1:"g";s:1:"g";s:1:"h";s:1:"h";s:1:"i";s:1:"i";s:1:"j";s:1:"j";s:1:"k";s:1:"k";s:1:"l";s:1:"l";s:1:"m";s:1:"m";s:1:"n";s:1:"n";s:1:"o";s:1:"o";s:1:"p";s:1:"p";s:1:"q";s:1:"q";s:1:"r";s:1:"r";s:1:"s";s:1:"s";s:1:"t";s:1:"t";s:1:"u";s:1:"u";s:1:"v";s:1:"v";s:1:"w";s:1:"w";s:1:"x";s:1:"x";s:1:"y";s:1:"y";s:1:"z";s:1:"z";s:1:"{";s:1:"{";s:1:"|";s:1:"|";s:1:"}";s:1:"}";s:1:"~";s:1:"~";s:1:"";s:2:"¡";s:1:"";s:2:"¢";s:1:"";s:2:"£";s:1:"";s:3:"⁄";s:1:"";s:2:"¥";s:1:"";s:2:"ƒ";s:1:"";s:2:"§";s:1:"";s:2:"¤";s:1:"";s:1:"'";s:1:"";s:3:"“";s:1:"";s:2:"«";s:1:"";s:3:"‹";s:1:"";s:3:"›";s:1:"";s:3:"fi";s:1:"";s:3:"fl";s:1:"";s:3:"–";s:1:"";s:3:"†";s:1:"";s:3:"‡";s:1:"";s:2:"·";s:1:"";s:2:"¶";s:1:"";s:3:"•";s:1:"";s:3:"‚";s:1:"";s:3:"„";s:1:"";s:3:"”";s:1:"";s:2:"»";s:1:"";s:3:"…";s:1:"";s:3:"‰";s:1:"";s:2:"¿";s:1:"";s:1:"`";s:1:"";s:2:"´";s:1:"";s:2:"ˆ";s:1:"";s:2:"˜";s:1:"";s:2:"¯";s:1:"";s:2:"˘";s:1:"";s:2:"˙";s:1:"";s:2:"¨";s:1:"";s:2:"˚";s:1:"";s:2:"¸";s:1:"";s:2:"˝";s:1:"";s:2:"˛";s:1:"";s:2:"ˇ";s:1:"";s:3:"—";s:1:"";s:2:"Æ";s:1:"";s:2:"ª";s:1:"";s:2:"Ł";s:1:"";s:2:"Ø";s:1:"";s:2:"Œ";s:1:"";s:2:"º";s:1:"";s:2:"æ";s:1:"";s:2:"ı";s:1:"";s:2:"ł";s:1:"";s:2:"ø";s:1:"";s:2:"œ";s:1:"";s:2:"ß";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.symbol.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.symbol.ser new file mode 100644 index 0000000..889217b --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.symbol.ser @@ -0,0 +1 @@ +a:189:{s:1:" ";s:1:" ";s:1:"!";s:1:"!";s:1:""";s:3:"∀";s:1:"#";s:1:"#";s:1:"$";s:3:"∃";s:1:"%";s:1:"%";s:1:"&";s:1:"&";s:1:"'";s:3:"∋";s:1:"(";s:1:"(";s:1:")";s:1:")";s:1:"*";s:3:"∗";s:1:"+";s:1:"+";s:1:",";s:1:",";s:1:"-";s:3:"−";s:1:".";s:1:".";s:1:"/";s:1:"/";i:0;s:1:"0";i:1;s:1:"1";i:2;s:1:"2";i:3;s:1:"3";i:4;s:1:"4";i:5;s:1:"5";i:6;s:1:"6";i:7;s:1:"7";i:8;s:1:"8";i:9;s:1:"9";s:1:":";s:1:":";s:1:";";s:1:";";s:1:"<";s:1:"<";s:1:"=";s:1:"=";s:1:">";s:1:">";s:1:"?";s:1:"?";s:1:"@";s:3:"≅";s:1:"A";s:2:"Α";s:1:"B";s:2:"Β";s:1:"C";s:2:"Χ";s:1:"D";s:2:"Δ";s:1:"E";s:2:"Ε";s:1:"F";s:2:"Φ";s:1:"G";s:2:"Γ";s:1:"H";s:2:"Η";s:1:"I";s:2:"Ι";s:1:"J";s:2:"ϑ";s:1:"K";s:2:"Κ";s:1:"L";s:2:"Λ";s:1:"M";s:2:"Μ";s:1:"N";s:2:"Ν";s:1:"O";s:2:"Ο";s:1:"P";s:2:"Π";s:1:"Q";s:2:"Θ";s:1:"R";s:2:"Ρ";s:1:"S";s:2:"Σ";s:1:"T";s:2:"Τ";s:1:"U";s:2:"Υ";s:1:"V";s:2:"ς";s:1:"W";s:2:"Ω";s:1:"X";s:2:"Ξ";s:1:"Y";s:2:"Ψ";s:1:"Z";s:2:"Ζ";s:1:"[";s:1:"[";s:1:"\";s:3:"∴";s:1:"]";s:1:"]";s:1:"^";s:3:"⊥";s:1:"_";s:1:"_";s:1:"`";s:3:"";s:1:"a";s:2:"α";s:1:"b";s:2:"β";s:1:"c";s:2:"χ";s:1:"d";s:2:"δ";s:1:"e";s:2:"ε";s:1:"f";s:2:"φ";s:1:"g";s:2:"γ";s:1:"h";s:2:"η";s:1:"i";s:2:"ι";s:1:"j";s:2:"ϕ";s:1:"k";s:2:"κ";s:1:"l";s:2:"λ";s:1:"m";s:2:"µ";s:1:"n";s:2:"ν";s:1:"o";s:2:"ο";s:1:"p";s:2:"π";s:1:"q";s:2:"θ";s:1:"r";s:2:"ρ";s:1:"s";s:2:"σ";s:1:"t";s:2:"τ";s:1:"u";s:2:"υ";s:1:"v";s:2:"ϖ";s:1:"w";s:2:"ω";s:1:"x";s:2:"ξ";s:1:"y";s:2:"ψ";s:1:"z";s:2:"ζ";s:1:"{";s:1:"{";s:1:"|";s:1:"|";s:1:"}";s:1:"}";s:1:"~";s:3:"∼";s:1:"";s:3:"€";s:1:"";s:2:"ϒ";s:1:"";s:3:"′";s:1:"";s:3:"≤";s:1:"";s:3:"⁄";s:1:"";s:3:"∞";s:1:"";s:2:"ƒ";s:1:"";s:3:"♣";s:1:"";s:3:"♦";s:1:"";s:3:"♥";s:1:"";s:3:"♠";s:1:"";s:3:"↔";s:1:"";s:3:"←";s:1:"";s:3:"↑";s:1:"";s:3:"→";s:1:"";s:3:"↓";s:1:"";s:2:"°";s:1:"";s:2:"±";s:1:"";s:3:"″";s:1:"";s:3:"≥";s:1:"";s:2:"×";s:1:"";s:3:"∝";s:1:"";s:3:"∂";s:1:"";s:3:"•";s:1:"";s:2:"÷";s:1:"";s:3:"≠";s:1:"";s:3:"≡";s:1:"";s:3:"≈";s:1:"";s:3:"…";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"↵";s:1:"";s:3:"ℵ";s:1:"";s:3:"ℑ";s:1:"";s:3:"ℜ";s:1:"";s:3:"℘";s:1:"";s:3:"⊗";s:1:"";s:3:"⊕";s:1:"";s:3:"∅";s:1:"";s:3:"∩";s:1:"";s:3:"∪";s:1:"";s:3:"⊃";s:1:"";s:3:"⊇";s:1:"";s:3:"⊄";s:1:"";s:3:"⊂";s:1:"";s:3:"⊆";s:1:"";s:3:"∈";s:1:"";s:3:"∉";s:1:"";s:3:"∠";s:1:"";s:3:"∇";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"∏";s:1:"";s:3:"√";s:1:"";s:3:"⋅";s:1:"";s:2:"¬";s:1:"";s:3:"∧";s:1:"";s:3:"∨";s:1:"";s:3:"⇔";s:1:"";s:3:"⇐";s:1:"";s:3:"⇑";s:1:"";s:3:"⇒";s:1:"";s:3:"⇓";s:1:"";s:3:"◊";s:1:"";s:3:"〈";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"∑";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"〉";s:1:"";s:3:"∫";s:1:"";s:3:"⌠";s:1:"";s:3:"";s:1:"";s:3:"⌡";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.turkish.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.turkish.ser new file mode 100644 index 0000000..a3651e6 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.turkish.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.us-ascii-quotes.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.us-ascii-quotes.ser new file mode 100644 index 0000000..f10af33 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.us-ascii-quotes.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.us-ascii.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.us-ascii.ser new file mode 100644 index 0000000..3a2f7e4 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.us-ascii.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1250.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1250.ser new file mode 100644 index 0000000..9e799cb Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1250.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1251.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1251.ser new file mode 100644 index 0000000..6592885 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1251.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1252.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1252.ser new file mode 100644 index 0000000..cccc26c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1252.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1253.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1253.ser new file mode 100644 index 0000000..13c5a0b Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1253.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1254.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1254.ser new file mode 100644 index 0000000..96d6972 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1254.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1255.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1255.ser new file mode 100644 index 0000000..c366bfd Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1255.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1256.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1256.ser new file mode 100644 index 0000000..cc98d2c Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1256.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1257.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1257.ser new file mode 100644 index 0000000..2a52206 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1257.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1258.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1258.ser new file mode 100644 index 0000000..114dd84 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.windows-1258.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-ce.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-ce.ser new file mode 100644 index 0000000..246603d Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-ce.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-cyrillic.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-cyrillic.ser new file mode 100644 index 0000000..3f606d6 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-cyrillic.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-greek.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-greek.ser new file mode 100644 index 0000000..c4b66d9 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-greek.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-icelandic.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-icelandic.ser new file mode 100644 index 0000000..15b35b1 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-icelandic.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-roman.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-roman.ser new file mode 100644 index 0000000..a39e96a Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.x-mac-roman.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.zdingbat.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.zdingbat.ser new file mode 100644 index 0000000..3a894d2 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/from.zdingbat.ser @@ -0,0 +1 @@ +a:202:{s:1:" ";s:1:" ";s:1:"!";s:3:"✁";s:1:""";s:3:"✂";s:1:"#";s:3:"✃";s:1:"$";s:3:"✄";s:1:"%";s:3:"☎";s:1:"&";s:3:"✆";s:1:"'";s:3:"✇";s:1:"(";s:3:"✈";s:1:")";s:3:"✉";s:1:"*";s:3:"☛";s:1:"+";s:3:"☞";s:1:",";s:3:"✌";s:1:"-";s:3:"✍";s:1:".";s:3:"✎";s:1:"/";s:3:"✏";i:0;s:3:"✐";i:1;s:3:"✑";i:2;s:3:"✒";i:3;s:3:"✓";i:4;s:3:"✔";i:5;s:3:"✕";i:6;s:3:"✖";i:7;s:3:"✗";i:8;s:3:"✘";i:9;s:3:"✙";s:1:":";s:3:"✚";s:1:";";s:3:"✛";s:1:"<";s:3:"✜";s:1:"=";s:3:"✝";s:1:">";s:3:"✞";s:1:"?";s:3:"✟";s:1:"@";s:3:"✠";s:1:"A";s:3:"✡";s:1:"B";s:3:"✢";s:1:"C";s:3:"✣";s:1:"D";s:3:"✤";s:1:"E";s:3:"✥";s:1:"F";s:3:"✦";s:1:"G";s:3:"✧";s:1:"H";s:3:"★";s:1:"I";s:3:"✩";s:1:"J";s:3:"✪";s:1:"K";s:3:"✫";s:1:"L";s:3:"✬";s:1:"M";s:3:"✭";s:1:"N";s:3:"✮";s:1:"O";s:3:"✯";s:1:"P";s:3:"✰";s:1:"Q";s:3:"✱";s:1:"R";s:3:"✲";s:1:"S";s:3:"✳";s:1:"T";s:3:"✴";s:1:"U";s:3:"✵";s:1:"V";s:3:"✶";s:1:"W";s:3:"✷";s:1:"X";s:3:"✸";s:1:"Y";s:3:"✹";s:1:"Z";s:3:"✺";s:1:"[";s:3:"✻";s:1:"\";s:3:"✼";s:1:"]";s:3:"✽";s:1:"^";s:3:"✾";s:1:"_";s:3:"✿";s:1:"`";s:3:"❀";s:1:"a";s:3:"❁";s:1:"b";s:3:"❂";s:1:"c";s:3:"❃";s:1:"d";s:3:"❄";s:1:"e";s:3:"❅";s:1:"f";s:3:"❆";s:1:"g";s:3:"❇";s:1:"h";s:3:"❈";s:1:"i";s:3:"❉";s:1:"j";s:3:"❊";s:1:"k";s:3:"❋";s:1:"l";s:3:"●";s:1:"m";s:3:"❍";s:1:"n";s:3:"■";s:1:"o";s:3:"❏";s:1:"p";s:3:"❐";s:1:"q";s:3:"❑";s:1:"r";s:3:"❒";s:1:"s";s:3:"▲";s:1:"t";s:3:"▼";s:1:"u";s:3:"◆";s:1:"v";s:3:"❖";s:1:"w";s:3:"◗";s:1:"x";s:3:"❘";s:1:"y";s:3:"❙";s:1:"z";s:3:"❚";s:1:"{";s:3:"❛";s:1:"|";s:3:"❜";s:1:"}";s:3:"❝";s:1:"~";s:3:"❞";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"❡";s:1:"";s:3:"❢";s:1:"";s:3:"❣";s:1:"";s:3:"❤";s:1:"";s:3:"❥";s:1:"";s:3:"❦";s:1:"";s:3:"❧";s:1:"";s:3:"♣";s:1:"";s:3:"♦";s:1:"";s:3:"♥";s:1:"";s:3:"♠";s:1:"";s:3:"①";s:1:"";s:3:"②";s:1:"";s:3:"③";s:1:"";s:3:"④";s:1:"";s:3:"⑤";s:1:"";s:3:"⑥";s:1:"";s:3:"⑦";s:1:"";s:3:"⑧";s:1:"";s:3:"⑨";s:1:"";s:3:"⑩";s:1:"";s:3:"❶";s:1:"";s:3:"❷";s:1:"";s:3:"❸";s:1:"";s:3:"❹";s:1:"";s:3:"❺";s:1:"";s:3:"❻";s:1:"";s:3:"❼";s:1:"";s:3:"❽";s:1:"";s:3:"❾";s:1:"";s:3:"❿";s:1:"";s:3:"➀";s:1:"";s:3:"➁";s:1:"";s:3:"➂";s:1:"";s:3:"➃";s:1:"";s:3:"➄";s:1:"";s:3:"➅";s:1:"";s:3:"➆";s:1:"";s:3:"➇";s:1:"";s:3:"➈";s:1:"";s:3:"➉";s:1:"";s:3:"➊";s:1:"";s:3:"➋";s:1:"";s:3:"➌";s:1:"";s:3:"➍";s:1:"";s:3:"➎";s:1:"";s:3:"➏";s:1:"";s:3:"➐";s:1:"";s:3:"➑";s:1:"";s:3:"➒";s:1:"";s:3:"➓";s:1:"";s:3:"➔";s:1:"";s:3:"→";s:1:"";s:3:"↔";s:1:"";s:3:"↕";s:1:"";s:3:"➘";s:1:"";s:3:"➙";s:1:"";s:3:"➚";s:1:"";s:3:"➛";s:1:"";s:3:"➜";s:1:"";s:3:"➝";s:1:"";s:3:"➞";s:1:"";s:3:"➟";s:1:"";s:3:"➠";s:1:"";s:3:"➡";s:1:"";s:3:"➢";s:1:"";s:3:"➣";s:1:"";s:3:"➤";s:1:"";s:3:"➥";s:1:"";s:3:"➦";s:1:"";s:3:"➧";s:1:"";s:3:"➨";s:1:"";s:3:"➩";s:1:"";s:3:"➪";s:1:"";s:3:"➫";s:1:"";s:3:"➬";s:1:"";s:3:"➭";s:1:"";s:3:"➮";s:1:"";s:3:"➯";s:1:"";s:3:"➱";s:1:"";s:3:"➲";s:1:"";s:3:"➳";s:1:"";s:3:"➴";s:1:"";s:3:"➵";s:1:"";s:3:"➶";s:1:"";s:3:"➷";s:1:"";s:3:"➸";s:1:"";s:3:"➹";s:1:"";s:3:"➺";s:1:"";s:3:"➻";s:1:"";s:3:"➼";s:1:"";s:3:"➽";s:1:"";s:3:"➾";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.gsm0338.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.gsm0338.ser new file mode 100644 index 0000000..e675fc3 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.gsm0338.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.mazovia.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.mazovia.ser new file mode 100644 index 0000000..70a534f Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.mazovia.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.stdenc.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.stdenc.ser new file mode 100644 index 0000000..0cb4628 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.stdenc.ser @@ -0,0 +1 @@ +a:154:{s:2:" ";s:1:" ";s:2:"­";s:1:"-";s:3:"∕";s:1:"";s:3:"∙";s:1:"";s:2:"ˉ";s:1:"";s:1:" ";s:1:" ";s:1:"!";s:1:"!";s:1:""";s:1:""";s:1:"#";s:1:"#";s:1:"$";s:1:"$";s:1:"%";s:1:"%";s:1:"&";s:1:"&";s:3:"’";s:1:"'";s:1:"(";s:1:"(";s:1:")";s:1:")";s:1:"*";s:1:"*";s:1:"+";s:1:"+";s:1:",";s:1:",";s:1:"-";s:1:"-";s:1:".";s:1:".";s:1:"/";s:1:"/";i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;s:1:":";s:1:":";s:1:";";s:1:";";s:1:"<";s:1:"<";s:1:"=";s:1:"=";s:1:">";s:1:">";s:1:"?";s:1:"?";s:1:"@";s:1:"@";s:1:"A";s:1:"A";s:1:"B";s:1:"B";s:1:"C";s:1:"C";s:1:"D";s:1:"D";s:1:"E";s:1:"E";s:1:"F";s:1:"F";s:1:"G";s:1:"G";s:1:"H";s:1:"H";s:1:"I";s:1:"I";s:1:"J";s:1:"J";s:1:"K";s:1:"K";s:1:"L";s:1:"L";s:1:"M";s:1:"M";s:1:"N";s:1:"N";s:1:"O";s:1:"O";s:1:"P";s:1:"P";s:1:"Q";s:1:"Q";s:1:"R";s:1:"R";s:1:"S";s:1:"S";s:1:"T";s:1:"T";s:1:"U";s:1:"U";s:1:"V";s:1:"V";s:1:"W";s:1:"W";s:1:"X";s:1:"X";s:1:"Y";s:1:"Y";s:1:"Z";s:1:"Z";s:1:"[";s:1:"[";s:1:"\";s:1:"\";s:1:"]";s:1:"]";s:1:"^";s:1:"^";s:1:"_";s:1:"_";s:3:"‘";s:1:"`";s:1:"a";s:1:"a";s:1:"b";s:1:"b";s:1:"c";s:1:"c";s:1:"d";s:1:"d";s:1:"e";s:1:"e";s:1:"f";s:1:"f";s:1:"g";s:1:"g";s:1:"h";s:1:"h";s:1:"i";s:1:"i";s:1:"j";s:1:"j";s:1:"k";s:1:"k";s:1:"l";s:1:"l";s:1:"m";s:1:"m";s:1:"n";s:1:"n";s:1:"o";s:1:"o";s:1:"p";s:1:"p";s:1:"q";s:1:"q";s:1:"r";s:1:"r";s:1:"s";s:1:"s";s:1:"t";s:1:"t";s:1:"u";s:1:"u";s:1:"v";s:1:"v";s:1:"w";s:1:"w";s:1:"x";s:1:"x";s:1:"y";s:1:"y";s:1:"z";s:1:"z";s:1:"{";s:1:"{";s:1:"|";s:1:"|";s:1:"}";s:1:"}";s:1:"~";s:1:"~";s:2:"¡";s:1:"";s:2:"¢";s:1:"";s:2:"£";s:1:"";s:3:"⁄";s:1:"";s:2:"¥";s:1:"";s:2:"ƒ";s:1:"";s:2:"§";s:1:"";s:2:"¤";s:1:"";s:1:"'";s:1:"";s:3:"“";s:1:"";s:2:"«";s:1:"";s:3:"‹";s:1:"";s:3:"›";s:1:"";s:3:"fi";s:1:"";s:3:"fl";s:1:"";s:3:"–";s:1:"";s:3:"†";s:1:"";s:3:"‡";s:1:"";s:2:"·";s:1:"";s:2:"¶";s:1:"";s:3:"•";s:1:"";s:3:"‚";s:1:"";s:3:"„";s:1:"";s:3:"”";s:1:"";s:2:"»";s:1:"";s:3:"…";s:1:"";s:3:"‰";s:1:"";s:2:"¿";s:1:"";s:1:"`";s:1:"";s:2:"´";s:1:"";s:2:"ˆ";s:1:"";s:2:"˜";s:1:"";s:2:"¯";s:1:"";s:2:"˘";s:1:"";s:2:"˙";s:1:"";s:2:"¨";s:1:"";s:2:"˚";s:1:"";s:2:"¸";s:1:"";s:2:"˝";s:1:"";s:2:"˛";s:1:"";s:2:"ˇ";s:1:"";s:3:"—";s:1:"";s:2:"Æ";s:1:"";s:2:"ª";s:1:"";s:2:"Ł";s:1:"";s:2:"Ø";s:1:"";s:2:"Œ";s:1:"";s:2:"º";s:1:"";s:2:"æ";s:1:"";s:2:"ı";s:1:"";s:2:"ł";s:1:"";s:2:"ø";s:1:"";s:2:"œ";s:1:"";s:2:"ß";s:1:"";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.symbol.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.symbol.ser new file mode 100644 index 0000000..fc61505 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.symbol.ser @@ -0,0 +1 @@ +a:194:{s:2:" ";s:1:" ";s:3:"∆";s:1:"D";s:3:"Ω";s:1:"W";s:2:"μ";s:1:"m";s:3:"∕";s:1:"";s:1:" ";s:1:" ";s:1:"!";s:1:"!";s:3:"∀";s:1:""";s:1:"#";s:1:"#";s:3:"∃";s:1:"$";s:1:"%";s:1:"%";s:1:"&";s:1:"&";s:3:"∋";s:1:"'";s:1:"(";s:1:"(";s:1:")";s:1:")";s:3:"∗";s:1:"*";s:1:"+";s:1:"+";s:1:",";s:1:",";s:3:"−";s:1:"-";s:1:".";s:1:".";s:1:"/";s:1:"/";i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;s:1:":";s:1:":";s:1:";";s:1:";";s:1:"<";s:1:"<";s:1:"=";s:1:"=";s:1:">";s:1:">";s:1:"?";s:1:"?";s:3:"≅";s:1:"@";s:2:"Α";s:1:"A";s:2:"Β";s:1:"B";s:2:"Χ";s:1:"C";s:2:"Δ";s:1:"D";s:2:"Ε";s:1:"E";s:2:"Φ";s:1:"F";s:2:"Γ";s:1:"G";s:2:"Η";s:1:"H";s:2:"Ι";s:1:"I";s:2:"ϑ";s:1:"J";s:2:"Κ";s:1:"K";s:2:"Λ";s:1:"L";s:2:"Μ";s:1:"M";s:2:"Ν";s:1:"N";s:2:"Ο";s:1:"O";s:2:"Π";s:1:"P";s:2:"Θ";s:1:"Q";s:2:"Ρ";s:1:"R";s:2:"Σ";s:1:"S";s:2:"Τ";s:1:"T";s:2:"Υ";s:1:"U";s:2:"ς";s:1:"V";s:2:"Ω";s:1:"W";s:2:"Ξ";s:1:"X";s:2:"Ψ";s:1:"Y";s:2:"Ζ";s:1:"Z";s:1:"[";s:1:"[";s:3:"∴";s:1:"\";s:1:"]";s:1:"]";s:3:"⊥";s:1:"^";s:1:"_";s:1:"_";s:3:"";s:1:"`";s:2:"α";s:1:"a";s:2:"β";s:1:"b";s:2:"χ";s:1:"c";s:2:"δ";s:1:"d";s:2:"ε";s:1:"e";s:2:"φ";s:1:"f";s:2:"γ";s:1:"g";s:2:"η";s:1:"h";s:2:"ι";s:1:"i";s:2:"ϕ";s:1:"j";s:2:"κ";s:1:"k";s:2:"λ";s:1:"l";s:2:"µ";s:1:"m";s:2:"ν";s:1:"n";s:2:"ο";s:1:"o";s:2:"π";s:1:"p";s:2:"θ";s:1:"q";s:2:"ρ";s:1:"r";s:2:"σ";s:1:"s";s:2:"τ";s:1:"t";s:2:"υ";s:1:"u";s:2:"ϖ";s:1:"v";s:2:"ω";s:1:"w";s:2:"ξ";s:1:"x";s:2:"ψ";s:1:"y";s:2:"ζ";s:1:"z";s:1:"{";s:1:"{";s:1:"|";s:1:"|";s:1:"}";s:1:"}";s:3:"∼";s:1:"~";s:3:"€";s:1:"";s:2:"ϒ";s:1:"";s:3:"′";s:1:"";s:3:"≤";s:1:"";s:3:"⁄";s:1:"";s:3:"∞";s:1:"";s:2:"ƒ";s:1:"";s:3:"♣";s:1:"";s:3:"♦";s:1:"";s:3:"♥";s:1:"";s:3:"♠";s:1:"";s:3:"↔";s:1:"";s:3:"←";s:1:"";s:3:"↑";s:1:"";s:3:"→";s:1:"";s:3:"↓";s:1:"";s:2:"°";s:1:"";s:2:"±";s:1:"";s:3:"″";s:1:"";s:3:"≥";s:1:"";s:2:"×";s:1:"";s:3:"∝";s:1:"";s:3:"∂";s:1:"";s:3:"•";s:1:"";s:2:"÷";s:1:"";s:3:"≠";s:1:"";s:3:"≡";s:1:"";s:3:"≈";s:1:"";s:3:"…";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"↵";s:1:"";s:3:"ℵ";s:1:"";s:3:"ℑ";s:1:"";s:3:"ℜ";s:1:"";s:3:"℘";s:1:"";s:3:"⊗";s:1:"";s:3:"⊕";s:1:"";s:3:"∅";s:1:"";s:3:"∩";s:1:"";s:3:"∪";s:1:"";s:3:"⊃";s:1:"";s:3:"⊇";s:1:"";s:3:"⊄";s:1:"";s:3:"⊂";s:1:"";s:3:"⊆";s:1:"";s:3:"∈";s:1:"";s:3:"∉";s:1:"";s:3:"∠";s:1:"";s:3:"∇";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"∏";s:1:"";s:3:"√";s:1:"";s:3:"⋅";s:1:"";s:2:"¬";s:1:"";s:3:"∧";s:1:"";s:3:"∨";s:1:"";s:3:"⇔";s:1:"";s:3:"⇐";s:1:"";s:3:"⇑";s:1:"";s:3:"⇒";s:1:"";s:3:"⇓";s:1:"";s:3:"◊";s:1:"";s:3:"〈";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"∑";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"〉";s:1:"";s:3:"∫";s:1:"";s:3:"⌠";s:1:"";s:3:"";s:1:"";s:3:"⌡";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.zdingbat.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.zdingbat.ser new file mode 100644 index 0000000..1f293bf --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/to.zdingbat.ser @@ -0,0 +1 @@ +a:203:{s:2:" ";s:1:" ";s:1:" ";s:1:" ";s:3:"✁";s:1:"!";s:3:"✂";s:1:""";s:3:"✃";s:1:"#";s:3:"✄";s:1:"$";s:3:"☎";s:1:"%";s:3:"✆";s:1:"&";s:3:"✇";s:1:"'";s:3:"✈";s:1:"(";s:3:"✉";s:1:")";s:3:"☛";s:1:"*";s:3:"☞";s:1:"+";s:3:"✌";s:1:",";s:3:"✍";s:1:"-";s:3:"✎";s:1:".";s:3:"✏";s:1:"/";s:3:"✐";i:0;s:3:"✑";i:1;s:3:"✒";i:2;s:3:"✓";i:3;s:3:"✔";i:4;s:3:"✕";i:5;s:3:"✖";i:6;s:3:"✗";i:7;s:3:"✘";i:8;s:3:"✙";i:9;s:3:"✚";s:1:":";s:3:"✛";s:1:";";s:3:"✜";s:1:"<";s:3:"✝";s:1:"=";s:3:"✞";s:1:">";s:3:"✟";s:1:"?";s:3:"✠";s:1:"@";s:3:"✡";s:1:"A";s:3:"✢";s:1:"B";s:3:"✣";s:1:"C";s:3:"✤";s:1:"D";s:3:"✥";s:1:"E";s:3:"✦";s:1:"F";s:3:"✧";s:1:"G";s:3:"★";s:1:"H";s:3:"✩";s:1:"I";s:3:"✪";s:1:"J";s:3:"✫";s:1:"K";s:3:"✬";s:1:"L";s:3:"✭";s:1:"M";s:3:"✮";s:1:"N";s:3:"✯";s:1:"O";s:3:"✰";s:1:"P";s:3:"✱";s:1:"Q";s:3:"✲";s:1:"R";s:3:"✳";s:1:"S";s:3:"✴";s:1:"T";s:3:"✵";s:1:"U";s:3:"✶";s:1:"V";s:3:"✷";s:1:"W";s:3:"✸";s:1:"X";s:3:"✹";s:1:"Y";s:3:"✺";s:1:"Z";s:3:"✻";s:1:"[";s:3:"✼";s:1:"\";s:3:"✽";s:1:"]";s:3:"✾";s:1:"^";s:3:"✿";s:1:"_";s:3:"❀";s:1:"`";s:3:"❁";s:1:"a";s:3:"❂";s:1:"b";s:3:"❃";s:1:"c";s:3:"❄";s:1:"d";s:3:"❅";s:1:"e";s:3:"❆";s:1:"f";s:3:"❇";s:1:"g";s:3:"❈";s:1:"h";s:3:"❉";s:1:"i";s:3:"❊";s:1:"j";s:3:"❋";s:1:"k";s:3:"●";s:1:"l";s:3:"❍";s:1:"m";s:3:"■";s:1:"n";s:3:"❏";s:1:"o";s:3:"❐";s:1:"p";s:3:"❑";s:1:"q";s:3:"❒";s:1:"r";s:3:"▲";s:1:"s";s:3:"▼";s:1:"t";s:3:"◆";s:1:"u";s:3:"❖";s:1:"v";s:3:"◗";s:1:"w";s:3:"❘";s:1:"x";s:3:"❙";s:1:"y";s:3:"❚";s:1:"z";s:3:"❛";s:1:"{";s:3:"❜";s:1:"|";s:3:"❝";s:1:"}";s:3:"❞";s:1:"~";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"";s:1:"";s:3:"❡";s:1:"";s:3:"❢";s:1:"";s:3:"❣";s:1:"";s:3:"❤";s:1:"";s:3:"❥";s:1:"";s:3:"❦";s:1:"";s:3:"❧";s:1:"";s:3:"♣";s:1:"";s:3:"♦";s:1:"";s:3:"♥";s:1:"";s:3:"♠";s:1:"";s:3:"①";s:1:"";s:3:"②";s:1:"";s:3:"③";s:1:"";s:3:"④";s:1:"";s:3:"⑤";s:1:"";s:3:"⑥";s:1:"";s:3:"⑦";s:1:"";s:3:"⑧";s:1:"";s:3:"⑨";s:1:"";s:3:"⑩";s:1:"";s:3:"❶";s:1:"";s:3:"❷";s:1:"";s:3:"❸";s:1:"";s:3:"❹";s:1:"";s:3:"❺";s:1:"";s:3:"❻";s:1:"";s:3:"❼";s:1:"";s:3:"❽";s:1:"";s:3:"❾";s:1:"";s:3:"❿";s:1:"";s:3:"➀";s:1:"";s:3:"➁";s:1:"";s:3:"➂";s:1:"";s:3:"➃";s:1:"";s:3:"➄";s:1:"";s:3:"➅";s:1:"";s:3:"➆";s:1:"";s:3:"➇";s:1:"";s:3:"➈";s:1:"";s:3:"➉";s:1:"";s:3:"➊";s:1:"";s:3:"➋";s:1:"";s:3:"➌";s:1:"";s:3:"➍";s:1:"";s:3:"➎";s:1:"";s:3:"➏";s:1:"";s:3:"➐";s:1:"";s:3:"➑";s:1:"";s:3:"➒";s:1:"";s:3:"➓";s:1:"";s:3:"➔";s:1:"";s:3:"→";s:1:"";s:3:"↔";s:1:"";s:3:"↕";s:1:"";s:3:"➘";s:1:"";s:3:"➙";s:1:"";s:3:"➚";s:1:"";s:3:"➛";s:1:"";s:3:"➜";s:1:"";s:3:"➝";s:1:"";s:3:"➞";s:1:"";s:3:"➟";s:1:"";s:3:"➠";s:1:"";s:3:"➡";s:1:"";s:3:"➢";s:1:"";s:3:"➣";s:1:"";s:3:"➤";s:1:"";s:3:"➥";s:1:"";s:3:"➦";s:1:"";s:3:"➧";s:1:"";s:3:"➨";s:1:"";s:3:"➩";s:1:"";s:3:"➪";s:1:"";s:3:"➫";s:1:"";s:3:"➬";s:1:"";s:3:"➭";s:1:"";s:3:"➮";s:1:"";s:3:"➯";s:1:"";s:3:"➱";s:1:"";s:3:"➲";s:1:"";s:3:"➳";s:1:"";s:3:"➴";s:1:"";s:3:"➵";s:1:"";s:3:"➶";s:1:"";s:3:"➷";s:1:"";s:3:"➸";s:1:"";s:3:"➹";s:1:"";s:3:"➺";s:1:"";s:3:"➻";s:1:"";s:3:"➼";s:1:"";s:3:"➽";s:1:"";s:3:"➾";s:1:"";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/translit.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/translit.ser new file mode 100644 index 0000000..5e5652d --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/charset/translit.ser @@ -0,0 +1 @@ +a:3960:{s:2:"µ";s:2:"μ";s:2:"¼";s:7:" 1⁄4 ";s:2:"½";s:7:" 1⁄2 ";s:2:"¾";s:7:" 3⁄4 ";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ŀ";s:3:"L·";s:2:"ŀ";s:3:"l·";s:2:"ʼn";s:3:"ʼn";s:2:"ſ";s:1:"s";s:2:"DŽ";s:3:"DŽ";s:2:"Dž";s:3:"Dž";s:2:"dž";s:3:"dž";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"NJ";s:2:"NJ";s:2:"Nj";s:2:"Nj";s:2:"nj";s:2:"nj";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"ϐ";s:2:"β";s:2:"ϑ";s:2:"θ";s:2:"ϒ";s:2:"Υ";s:2:"ϕ";s:2:"φ";s:2:"ϖ";s:2:"π";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"ρ";s:2:"ϲ";s:2:"ς";s:2:"ϴ";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"և";s:4:"եւ";s:2:"ٵ";s:4:"اٴ";s:2:"ٶ";s:4:"وٴ";s:2:"ٷ";s:4:"ۇٴ";s:2:"ٸ";s:4:"يٴ";s:3:"ำ";s:6:"ํา";s:3:"ຳ";s:6:"ໍາ";s:3:"ໜ";s:6:"ຫນ";s:3:"ໝ";s:6:"ຫມ";s:3:"ཷ";s:6:"ྲཱྀ";s:3:"ཹ";s:6:"ླཱྀ";s:3:"ẚ";s:3:"aʾ";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"⁇";s:2:"??";s:3:"⁈";s:2:"?!";s:3:"⁉";s:2:"!?";s:3:"⁗";s:12:"′′′′";s:3:"₨";s:2:"Rs";s:3:"℀";s:3:"a/c";s:3:"℁";s:3:"a/s";s:3:"ℂ";s:1:"C";s:3:"℃";s:3:"°C";s:3:"℅";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Ɛ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"ℋ";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"ℍ";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"ℏ";s:2:"ħ";s:3:"ℐ";s:1:"I";s:3:"ℑ";s:1:"I";s:3:"ℒ";s:1:"L";s:3:"ℓ";s:1:"l";s:3:"ℕ";s:1:"N";s:3:"№";s:2:"No";s:3:"ℙ";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"ℛ";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"ℝ";s:1:"R";s:3:"℡";s:3:"TEL";s:3:"ℤ";s:1:"Z";s:3:"ℨ";s:1:"Z";s:3:"ℬ";s:1:"B";s:3:"ℭ";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"ℰ";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"ℴ";s:1:"o";s:3:"ℵ";s:2:"א";s:3:"ℶ";s:2:"ב";s:3:"ℷ";s:2:"ג";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"℻";s:3:"FAX";s:3:"ℼ";s:2:"π";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"ℿ";s:2:"Π";s:3:"⅀";s:3:"∑";s:3:"ⅅ";s:1:"D";s:3:"ⅆ";s:1:"d";s:3:"ⅇ";s:1:"e";s:3:"ⅈ";s:1:"i";s:3:"ⅉ";s:1:"j";s:3:"⅐";s:7:" 1⁄7 ";s:3:"⅑";s:7:" 1⁄9 ";s:3:"⅒";s:8:" 1⁄10 ";s:3:"⅓";s:7:" 1⁄3 ";s:3:"⅔";s:7:" 2⁄3 ";s:3:"⅕";s:7:" 1⁄5 ";s:3:"⅖";s:7:" 2⁄5 ";s:3:"⅗";s:7:" 3⁄5 ";s:3:"⅘";s:7:" 4⁄5 ";s:3:"⅙";s:7:" 1⁄6 ";s:3:"⅚";s:7:" 5⁄6 ";s:3:"⅛";s:7:" 1⁄8 ";s:3:"⅜";s:7:" 3⁄8 ";s:3:"⅝";s:7:" 5⁄8 ";s:3:"⅞";s:7:" 7⁄8 ";s:3:"⅟";s:6:" 1⁄ ";s:3:"Ⅰ";s:1:"I";s:3:"Ⅱ";s:2:"II";s:3:"Ⅲ";s:3:"III";s:3:"Ⅳ";s:2:"IV";s:3:"Ⅴ";s:1:"V";s:3:"Ⅵ";s:2:"VI";s:3:"Ⅶ";s:3:"VII";s:3:"Ⅷ";s:4:"VIII";s:3:"Ⅸ";s:2:"IX";s:3:"Ⅹ";s:1:"X";s:3:"Ⅺ";s:2:"XI";s:3:"Ⅻ";s:3:"XII";s:3:"Ⅼ";s:1:"L";s:3:"Ⅽ";s:1:"C";s:3:"Ⅾ";s:1:"D";s:3:"Ⅿ";s:1:"M";s:3:"ⅰ";s:1:"i";s:3:"ⅱ";s:2:"ii";s:3:"ⅲ";s:3:"iii";s:3:"ⅳ";s:2:"iv";s:3:"ⅴ";s:1:"v";s:3:"ⅵ";s:2:"vi";s:3:"ⅶ";s:3:"vii";s:3:"ⅷ";s:4:"viii";s:3:"ⅸ";s:2:"ix";s:3:"ⅹ";s:1:"x";s:3:"ⅺ";s:2:"xi";s:3:"ⅻ";s:3:"xii";s:3:"ⅼ";s:1:"l";s:3:"ⅽ";s:1:"c";s:3:"ⅾ";s:1:"d";s:3:"ⅿ";s:1:"m";s:3:"↉";s:7:" 0⁄3 ";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"①";s:3:"(1)";s:3:"②";s:3:"(2)";s:3:"③";s:3:"(3)";s:3:"④";s:3:"(4)";s:3:"⑤";s:3:"(5)";s:3:"⑥";s:3:"(6)";s:3:"⑦";s:3:"(7)";s:3:"⑧";s:3:"(8)";s:3:"⑨";s:3:"(9)";s:3:"⑩";s:4:"(10)";s:3:"⑪";s:4:"(11)";s:3:"⑫";s:4:"(12)";s:3:"⑬";s:4:"(13)";s:3:"⑭";s:4:"(14)";s:3:"⑮";s:4:"(15)";s:3:"⑯";s:4:"(16)";s:3:"⑰";s:4:"(17)";s:3:"⑱";s:4:"(18)";s:3:"⑲";s:4:"(19)";s:3:"⑳";s:4:"(20)";s:3:"⑴";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"⑶";s:3:"(3)";s:3:"⑷";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"⑻";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"⑿";s:4:"(12)";s:3:"⒀";s:4:"(13)";s:3:"⒁";s:4:"(14)";s:3:"⒂";s:4:"(15)";s:3:"⒃";s:4:"(16)";s:3:"⒄";s:4:"(17)";s:3:"⒅";s:4:"(18)";s:3:"⒆";s:4:"(19)";s:3:"⒇";s:4:"(20)";s:3:"⒈";s:2:"1.";s:3:"⒉";s:2:"2.";s:3:"⒊";s:2:"3.";s:3:"⒋";s:2:"4.";s:3:"⒌";s:2:"5.";s:3:"⒍";s:2:"6.";s:3:"⒎";s:2:"7.";s:3:"⒏";s:2:"8.";s:3:"⒐";s:2:"9.";s:3:"⒑";s:3:"10.";s:3:"⒒";s:3:"11.";s:3:"⒓";s:3:"12.";s:3:"⒔";s:3:"13.";s:3:"⒕";s:3:"14.";s:3:"⒖";s:3:"15.";s:3:"⒗";s:3:"16.";s:3:"⒘";s:3:"17.";s:3:"⒙";s:3:"18.";s:3:"⒚";s:3:"19.";s:3:"⒛";s:3:"20.";s:3:"⒜";s:3:"(a)";s:3:"⒝";s:3:"(b)";s:3:"⒞";s:3:"(c)";s:3:"⒟";s:3:"(d)";s:3:"⒠";s:3:"(e)";s:3:"⒡";s:3:"(f)";s:3:"⒢";s:3:"(g)";s:3:"⒣";s:3:"(h)";s:3:"⒤";s:3:"(i)";s:3:"⒥";s:3:"(j)";s:3:"⒦";s:3:"(k)";s:3:"⒧";s:3:"(l)";s:3:"⒨";s:3:"(m)";s:3:"⒩";s:3:"(n)";s:3:"⒪";s:3:"(o)";s:3:"⒫";s:3:"(p)";s:3:"⒬";s:3:"(q)";s:3:"⒭";s:3:"(r)";s:3:"⒮";s:3:"(s)";s:3:"⒯";s:3:"(t)";s:3:"⒰";s:3:"(u)";s:3:"⒱";s:3:"(v)";s:3:"⒲";s:3:"(w)";s:3:"⒳";s:3:"(x)";s:3:"⒴";s:3:"(y)";s:3:"⒵";s:3:"(z)";s:3:"Ⓐ";s:3:"(A)";s:3:"Ⓑ";s:3:"(B)";s:3:"Ⓒ";s:3:"(C)";s:3:"Ⓓ";s:3:"(D)";s:3:"Ⓔ";s:3:"(E)";s:3:"Ⓕ";s:3:"(F)";s:3:"Ⓖ";s:3:"(G)";s:3:"Ⓗ";s:3:"(H)";s:3:"Ⓘ";s:3:"(I)";s:3:"Ⓙ";s:3:"(J)";s:3:"Ⓚ";s:3:"(K)";s:3:"Ⓛ";s:3:"(L)";s:3:"Ⓜ";s:3:"(M)";s:3:"Ⓝ";s:3:"(N)";s:3:"Ⓞ";s:3:"(O)";s:3:"Ⓟ";s:3:"(P)";s:3:"Ⓠ";s:3:"(Q)";s:3:"Ⓡ";s:3:"(R)";s:3:"Ⓢ";s:3:"(S)";s:3:"Ⓣ";s:3:"(T)";s:3:"Ⓤ";s:3:"(U)";s:3:"Ⓥ";s:3:"(V)";s:3:"Ⓦ";s:3:"(W)";s:3:"Ⓧ";s:3:"(X)";s:3:"Ⓨ";s:3:"(Y)";s:3:"Ⓩ";s:3:"(Z)";s:3:"ⓐ";s:3:"(a)";s:3:"ⓑ";s:3:"(b)";s:3:"ⓒ";s:3:"(c)";s:3:"ⓓ";s:3:"(d)";s:3:"ⓔ";s:3:"(e)";s:3:"ⓕ";s:3:"(f)";s:3:"ⓖ";s:3:"(g)";s:3:"ⓗ";s:3:"(h)";s:3:"ⓘ";s:3:"(i)";s:3:"ⓙ";s:3:"(j)";s:3:"ⓚ";s:3:"(k)";s:3:"ⓛ";s:3:"(l)";s:3:"ⓜ";s:3:"(m)";s:3:"ⓝ";s:3:"(n)";s:3:"ⓞ";s:3:"(o)";s:3:"ⓟ";s:3:"(p)";s:3:"ⓠ";s:3:"(q)";s:3:"ⓡ";s:3:"(r)";s:3:"ⓢ";s:3:"(s)";s:3:"ⓣ";s:3:"(t)";s:3:"ⓤ";s:3:"(u)";s:3:"ⓥ";s:3:"(v)";s:3:"ⓦ";s:3:"(w)";s:3:"ⓧ";s:3:"(x)";s:3:"ⓨ";s:3:"(y)";s:3:"ⓩ";s:3:"(z)";s:3:"⓪";s:3:"(0)";s:3:"⨌";s:12:"∫∫∫∫";s:3:"⩴";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"⩶";s:3:"===";s:3:"⺟";s:3:"母";s:3:"⻳";s:3:"龟";s:3:"⼀";s:3:"一";s:3:"⼁";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"乙";s:3:"⼅";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"儿";s:3:"⼊";s:3:"入";s:3:"⼋";s:3:"八";s:3:"⼌";s:3:"冂";s:3:"⼍";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"⼏";s:3:"几";s:3:"⼐";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"⼒";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"⼔";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"⼖";s:3:"匸";s:3:"⼗";s:3:"十";s:3:"⼘";s:3:"卜";s:3:"⼙";s:3:"卩";s:3:"⼚";s:3:"厂";s:3:"⼛";s:3:"厶";s:3:"⼜";s:3:"又";s:3:"⼝";s:3:"口";s:3:"⼞";s:3:"囗";s:3:"⼟";s:3:"土";s:3:"⼠";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"⼢";s:3:"夊";s:3:"⼣";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"⼥";s:3:"女";s:3:"⼦";s:3:"子";s:3:"⼧";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"小";s:3:"⼪";s:3:"尢";s:3:"⼫";s:3:"尸";s:3:"⼬";s:3:"屮";s:3:"⼭";s:3:"山";s:3:"⼮";s:3:"巛";s:3:"⼯";s:3:"工";s:3:"⼰";s:3:"己";s:3:"⼱";s:3:"巾";s:3:"⼲";s:3:"干";s:3:"⼳";s:3:"幺";s:3:"⼴";s:3:"广";s:3:"⼵";s:3:"廴";s:3:"⼶";s:3:"廾";s:3:"⼷";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"⼹";s:3:"彐";s:3:"⼺";s:3:"彡";s:3:"⼻";s:3:"彳";s:3:"⼼";s:3:"心";s:3:"⼽";s:3:"戈";s:3:"⼾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"⽀";s:3:"支";s:3:"⽁";s:3:"攴";s:3:"⽂";s:3:"文";s:3:"⽃";s:3:"斗";s:3:"⽄";s:3:"斤";s:3:"⽅";s:3:"方";s:3:"⽆";s:3:"无";s:3:"⽇";s:3:"日";s:3:"⽈";s:3:"曰";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"止";s:3:"⽍";s:3:"歹";s:3:"⽎";s:3:"殳";s:3:"⽏";s:3:"毋";s:3:"⽐";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"⽒";s:3:"氏";s:3:"⽓";s:3:"气";s:3:"⽔";s:3:"水";s:3:"⽕";s:3:"火";s:3:"⽖";s:3:"爪";s:3:"⽗";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"⽙";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"⽛";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"⽝";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"⽠";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"⽢";s:3:"甘";s:3:"⽣";s:3:"生";s:3:"⽤";s:3:"用";s:3:"⽥";s:3:"田";s:3:"⽦";s:3:"疋";s:3:"⽧";s:3:"疒";s:3:"⽨";s:3:"癶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"皮";s:3:"⽫";s:3:"皿";s:3:"⽬";s:3:"目";s:3:"⽭";s:3:"矛";s:3:"⽮";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"⽰";s:3:"示";s:3:"⽱";s:3:"禸";s:3:"⽲";s:3:"禾";s:3:"⽳";s:3:"穴";s:3:"⽴";s:3:"立";s:3:"⽵";s:3:"竹";s:3:"⽶";s:3:"米";s:3:"⽷";s:3:"糸";s:3:"⽸";s:3:"缶";s:3:"⽹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"⽻";s:3:"羽";s:3:"⽼";s:3:"老";s:3:"⽽";s:3:"而";s:3:"⽾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"⾀";s:3:"聿";s:3:"⾁";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"⾅";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"虍";s:3:"⾍";s:3:"虫";s:3:"⾎";s:3:"血";s:3:"⾏";s:3:"行";s:3:"⾐";s:3:"衣";s:3:"⾑";s:3:"襾";s:3:"⾒";s:3:"見";s:3:"⾓";s:3:"角";s:3:"⾔";s:3:"言";s:3:"⾕";s:3:"谷";s:3:"⾖";s:3:"豆";s:3:"⾗";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"⾙";s:3:"貝";s:3:"⾚";s:3:"赤";s:3:"⾛";s:3:"走";s:3:"⾜";s:3:"足";s:3:"⾝";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"辛";s:3:"⾠";s:3:"辰";s:3:"⾡";s:3:"辵";s:3:"⾢";s:3:"邑";s:3:"⾣";s:3:"酉";s:3:"⾤";s:3:"釆";s:3:"⾥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"⾧";s:3:"長";s:3:"⾨";s:3:"門";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"隶";s:3:"⾫";s:3:"隹";s:3:"⾬";s:3:"雨";s:3:"⾭";s:3:"靑";s:3:"⾮";s:3:"非";s:3:"⾯";s:3:"面";s:3:"⾰";s:3:"革";s:3:"⾱";s:3:"韋";s:3:"⾲";s:3:"韭";s:3:"⾳";s:3:"音";s:3:"⾴";s:3:"頁";s:3:"⾵";s:3:"風";s:3:"⾶";s:3:"飛";s:3:"⾷";s:3:"食";s:3:"⾸";s:3:"首";s:3:"⾹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"⾻";s:3:"骨";s:3:"⾼";s:3:"高";s:3:"⾽";s:3:"髟";s:3:"⾾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"⿀";s:3:"鬲";s:3:"⿁";s:3:"鬼";s:3:"⿂";s:3:"魚";s:3:"⿃";s:3:"鳥";s:3:"⿄";s:3:"鹵";s:3:"⿅";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"黍";s:3:"⿊";s:3:"黑";s:3:"⿋";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"⿍";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"⿏";s:3:"鼠";s:3:"⿐";s:3:"鼻";s:3:"⿑";s:3:"齊";s:3:"⿒";s:3:"齒";s:3:"⿓";s:3:"龍";s:3:"⿔";s:3:"龜";s:3:"⿕";s:3:"龠";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"十";s:3:"〹";s:3:"卄";s:3:"〺";s:3:"卅";s:3:"ㄱ";s:3:"ᄀ";s:3:"ㄲ";s:3:"ᄁ";s:3:"ㄳ";s:3:"ᆪ";s:3:"ㄴ";s:3:"ᄂ";s:3:"ㄵ";s:3:"ᆬ";s:3:"ㄶ";s:3:"ᆭ";s:3:"ㄷ";s:3:"ᄃ";s:3:"ㄸ";s:3:"ᄄ";s:3:"ㄹ";s:3:"ᄅ";s:3:"ㄺ";s:3:"ᆰ";s:3:"ㄻ";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ㄿ";s:3:"ᆵ";s:3:"ㅀ";s:3:"ᄚ";s:3:"ㅁ";s:3:"ᄆ";s:3:"ㅂ";s:3:"ᄇ";s:3:"ㅃ";s:3:"ᄈ";s:3:"ㅄ";s:3:"ᄡ";s:3:"ㅅ";s:3:"ᄉ";s:3:"ㅆ";s:3:"ᄊ";s:3:"ㅇ";s:3:"ᄋ";s:3:"ㅈ";s:3:"ᄌ";s:3:"ㅉ";s:3:"ᄍ";s:3:"ㅊ";s:3:"ᄎ";s:3:"ㅋ";s:3:"ᄏ";s:3:"ㅌ";s:3:"ᄐ";s:3:"ㅍ";s:3:"ᄑ";s:3:"ㅎ";s:3:"ᄒ";s:3:"ㅏ";s:3:"ᅡ";s:3:"ㅐ";s:3:"ᅢ";s:3:"ㅑ";s:3:"ᅣ";s:3:"ㅒ";s:3:"ᅤ";s:3:"ㅓ";s:3:"ᅥ";s:3:"ㅔ";s:3:"ᅦ";s:3:"ㅕ";s:3:"ᅧ";s:3:"ㅖ";s:3:"ᅨ";s:3:"ㅗ";s:3:"ᅩ";s:3:"ㅘ";s:3:"ᅪ";s:3:"ㅙ";s:3:"ᅫ";s:3:"ㅚ";s:3:"ᅬ";s:3:"ㅛ";s:3:"ᅭ";s:3:"ㅜ";s:3:"ᅮ";s:3:"ㅝ";s:3:"ᅯ";s:3:"ㅞ";s:3:"ᅰ";s:3:"ㅟ";s:3:"ᅱ";s:3:"ㅠ";s:3:"ᅲ";s:3:"ㅡ";s:3:"ᅳ";s:3:"ㅢ";s:3:"ᅴ";s:3:"ㅣ";s:3:"ᅵ";s:3:"ㅤ";s:3:"ᅠ";s:3:"ㅥ";s:3:"ᄔ";s:3:"ㅦ";s:3:"ᄕ";s:3:"ㅧ";s:3:"ᇇ";s:3:"ㅨ";s:3:"ᇈ";s:3:"ㅩ";s:3:"ᇌ";s:3:"ㅪ";s:3:"ᇎ";s:3:"ㅫ";s:3:"ᇓ";s:3:"ㅬ";s:3:"ᇗ";s:3:"ㅭ";s:3:"ᇙ";s:3:"ㅮ";s:3:"ᄜ";s:3:"ㅯ";s:3:"ᇝ";s:3:"ㅰ";s:3:"ᇟ";s:3:"ㅱ";s:3:"ᄝ";s:3:"ㅲ";s:3:"ᄞ";s:3:"ㅳ";s:3:"ᄠ";s:3:"ㅴ";s:3:"ᄢ";s:3:"ㅵ";s:3:"ᄣ";s:3:"ㅶ";s:3:"ᄧ";s:3:"ㅷ";s:3:"ᄩ";s:3:"ㅸ";s:3:"ᄫ";s:3:"ㅹ";s:3:"ᄬ";s:3:"ㅺ";s:3:"ᄭ";s:3:"ㅻ";s:3:"ᄮ";s:3:"ㅼ";s:3:"ᄯ";s:3:"ㅽ";s:3:"ᄲ";s:3:"ㅾ";s:3:"ᄶ";s:3:"ㅿ";s:3:"ᅀ";s:3:"ㆀ";s:3:"ᅇ";s:3:"ㆁ";s:3:"ᅌ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"ᅗ";s:3:"ㆅ";s:3:"ᅘ";s:3:"ㆆ";s:3:"ᅙ";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ㆍ";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㈀";s:5:"(ᄀ)";s:3:"㈁";s:5:"(ᄂ)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(ᄅ)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(ᄋ)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(ᄏ)";s:3:"㈋";s:5:"(ᄐ)";s:3:"㈌";s:5:"(ᄑ)";s:3:"㈍";s:5:"(ᄒ)";s:3:"㈎";s:8:"(가)";s:3:"㈏";s:8:"(나)";s:3:"㈐";s:8:"(다)";s:3:"㈑";s:8:"(라)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(아)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(카)";s:3:"㈙";s:8:"(타)";s:3:"㈚";s:8:"(파)";s:3:"㈛";s:8:"(하)";s:3:"㈜";s:8:"(주)";s:3:"㈝";s:17:"(오전)";s:3:"㈞";s:14:"(오후)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(四)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(六)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(八)";s:3:"㈨";s:5:"(九)";s:3:"㈩";s:5:"(十)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(火)";s:3:"㈬";s:5:"(水)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(日)";s:3:"㈱";s:5:"(株)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(名)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(祝)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(学)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(企)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(協)";s:3:"㉀";s:5:"(祭)";s:3:"㉁";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉄";s:5:"(問)";s:3:"㉅";s:5:"(幼)";s:3:"㉆";s:5:"(文)";s:3:"㉇";s:5:"(箏)";s:3:"㉐";s:3:"PTE";s:3:"㉑";s:4:"(21)";s:3:"㉒";s:4:"(22)";s:3:"㉓";s:4:"(23)";s:3:"㉔";s:4:"(24)";s:3:"㉕";s:4:"(25)";s:3:"㉖";s:4:"(26)";s:3:"㉗";s:4:"(27)";s:3:"㉘";s:4:"(28)";s:3:"㉙";s:4:"(29)";s:3:"㉚";s:4:"(30)";s:3:"㉛";s:4:"(31)";s:3:"㉜";s:4:"(32)";s:3:"㉝";s:4:"(33)";s:3:"㉞";s:4:"(34)";s:3:"㉟";s:4:"(35)";s:3:"㉠";s:5:"(ᄀ)";s:3:"㉡";s:5:"(ᄂ)";s:3:"㉢";s:5:"(ᄃ)";s:3:"㉣";s:5:"(ᄅ)";s:3:"㉤";s:5:"(ᄆ)";s:3:"㉥";s:5:"(ᄇ)";s:3:"㉦";s:5:"(ᄉ)";s:3:"㉧";s:5:"(ᄋ)";s:3:"㉨";s:5:"(ᄌ)";s:3:"㉩";s:5:"(ᄎ)";s:3:"㉪";s:5:"(ᄏ)";s:3:"㉫";s:5:"(ᄐ)";s:3:"㉬";s:5:"(ᄑ)";s:3:"㉭";s:5:"(ᄒ)";s:3:"㉮";s:8:"(가)";s:3:"㉯";s:8:"(나)";s:3:"㉰";s:8:"(다)";s:3:"㉱";s:8:"(라)";s:3:"㉲";s:8:"(마)";s:3:"㉳";s:8:"(바)";s:3:"㉴";s:8:"(사)";s:3:"㉵";s:8:"(아)";s:3:"㉶";s:8:"(자)";s:3:"㉷";s:8:"(차)";s:3:"㉸";s:8:"(카)";s:3:"㉹";s:8:"(타)";s:3:"㉺";s:8:"(파)";s:3:"㉻";s:8:"(하)";s:3:"㉼";s:17:"(참고)";s:3:"㉽";s:14:"(주의)";s:3:"㉾";s:8:"(우)";s:3:"㊀";s:5:"(一)";s:3:"㊁";s:5:"(二)";s:3:"㊂";s:5:"(三)";s:3:"㊃";s:5:"(四)";s:3:"㊄";s:5:"(五)";s:3:"㊅";s:5:"(六)";s:3:"㊆";s:5:"(七)";s:3:"㊇";s:5:"(八)";s:3:"㊈";s:5:"(九)";s:3:"㊉";s:5:"(十)";s:3:"㊊";s:5:"(月)";s:3:"㊋";s:5:"(火)";s:3:"㊌";s:5:"(水)";s:3:"㊍";s:5:"(木)";s:3:"㊎";s:5:"(金)";s:3:"㊏";s:5:"(土)";s:3:"㊐";s:5:"(日)";s:3:"㊑";s:5:"(株)";s:3:"㊒";s:5:"(有)";s:3:"㊓";s:5:"(社)";s:3:"㊔";s:5:"(名)";s:3:"㊕";s:5:"(特)";s:3:"㊖";s:5:"(財)";s:3:"㊗";s:5:"(祝)";s:3:"㊘";s:5:"(労)";s:3:"㊙";s:5:"(秘)";s:3:"㊚";s:5:"(男)";s:3:"㊛";s:5:"(女)";s:3:"㊜";s:5:"(適)";s:3:"㊝";s:5:"(優)";s:3:"㊞";s:5:"(印)";s:3:"㊟";s:5:"(注)";s:3:"㊠";s:5:"(項)";s:3:"㊡";s:5:"(休)";s:3:"㊢";s:5:"(写)";s:3:"㊣";s:5:"(正)";s:3:"㊤";s:5:"(上)";s:3:"㊥";s:5:"(中)";s:3:"㊦";s:5:"(下)";s:3:"㊧";s:5:"(左)";s:3:"㊨";s:5:"(右)";s:3:"㊩";s:5:"(医)";s:3:"㊪";s:5:"(宗)";s:3:"㊫";s:5:"(学)";s:3:"㊬";s:5:"(監)";s:3:"㊭";s:5:"(企)";s:3:"㊮";s:5:"(資)";s:3:"㊯";s:5:"(協)";s:3:"㊰";s:5:"(夜)";s:3:"㊱";s:4:"(36)";s:3:"㊲";s:4:"(37)";s:3:"㊳";s:4:"(38)";s:3:"㊴";s:4:"(39)";s:3:"㊵";s:4:"(40)";s:3:"㊶";s:4:"(41)";s:3:"㊷";s:4:"(42)";s:3:"㊸";s:4:"(43)";s:3:"㊹";s:4:"(44)";s:3:"㊺";s:4:"(45)";s:3:"㊻";s:4:"(46)";s:3:"㊼";s:4:"(47)";s:3:"㊽";s:4:"(48)";s:3:"㊾";s:4:"(49)";s:3:"㊿";s:4:"(50)";s:3:"㋀";s:4:"1月";s:3:"㋁";s:4:"2月";s:3:"㋂";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"㋄";s:4:"5月";s:3:"㋅";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"㋋";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"㋍";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"㋏";s:3:"LTD";s:3:"㋐";s:5:"(ア)";s:3:"㋑";s:5:"(イ)";s:3:"㋒";s:5:"(ウ)";s:3:"㋓";s:5:"(エ)";s:3:"㋔";s:5:"(オ)";s:3:"㋕";s:5:"(カ)";s:3:"㋖";s:5:"(キ)";s:3:"㋗";s:5:"(ク)";s:3:"㋘";s:5:"(ケ)";s:3:"㋙";s:5:"(コ)";s:3:"㋚";s:5:"(サ)";s:3:"㋛";s:5:"(シ)";s:3:"㋜";s:5:"(ス)";s:3:"㋝";s:5:"(セ)";s:3:"㋞";s:5:"(ソ)";s:3:"㋟";s:5:"(タ)";s:3:"㋠";s:5:"(チ)";s:3:"㋡";s:5:"(ツ)";s:3:"㋢";s:5:"(テ)";s:3:"㋣";s:5:"(ト)";s:3:"㋤";s:5:"(ナ)";s:3:"㋥";s:5:"(ニ)";s:3:"㋦";s:5:"(ヌ)";s:3:"㋧";s:5:"(ネ)";s:3:"㋨";s:5:"(ノ)";s:3:"㋩";s:5:"(ハ)";s:3:"㋪";s:5:"(ヒ)";s:3:"㋫";s:5:"(フ)";s:3:"㋬";s:5:"(ヘ)";s:3:"㋭";s:5:"(ホ)";s:3:"㋮";s:5:"(マ)";s:3:"㋯";s:5:"(ミ)";s:3:"㋰";s:5:"(ム)";s:3:"㋱";s:5:"(メ)";s:3:"㋲";s:5:"(モ)";s:3:"㋳";s:5:"(ヤ)";s:3:"㋴";s:5:"(ユ)";s:3:"㋵";s:5:"(ヨ)";s:3:"㋶";s:5:"(ラ)";s:3:"㋷";s:5:"(リ)";s:3:"㋸";s:5:"(ル)";s:3:"㋹";s:5:"(レ)";s:3:"㋺";s:5:"(ロ)";s:3:"㋻";s:5:"(ワ)";s:3:"㋼";s:5:"(ヰ)";s:3:"㋽";s:5:"(ヱ)";s:3:"㋾";s:5:"(ヲ)";s:3:"㌀";s:12:"アパート";s:3:"㌁";s:12:"アルファ";s:3:"㌂";s:12:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:12:"イニング";s:3:"㌅";s:9:"インチ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:15:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"㌍";s:12:"カロリー";s:3:"㌎";s:9:"ガロン";s:3:"㌏";s:9:"ガンマ";s:3:"㌐";s:6:"ギガ";s:3:"㌑";s:9:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:12:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:15:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:9:"グラム";s:3:"㌙";s:15:"グラムトン";s:3:"㌚";s:15:"クルゼイロ";s:3:"㌛";s:12:"クローネ";s:3:"㌜";s:9:"ケース";s:3:"㌝";s:9:"コルナ";s:3:"㌞";s:9:"コーポ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンチーム";s:3:"㌡";s:12:"シリング";s:3:"㌢";s:9:"センチ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:9:"ダース";s:3:"㌥";s:6:"デシ";s:3:"㌦";s:6:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ハイツ";s:3:"㌫";s:15:"パーセント";s:3:"㌬";s:9:"パーツ";s:3:"㌭";s:12:"バーレル";s:3:"㌮";s:15:"ピアストル";s:3:"㌯";s:9:"ピクル";s:3:"㌰";s:6:"ピコ";s:3:"㌱";s:6:"ビル";s:3:"㌲";s:15:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:15:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:6:"ペソ";s:3:"㌸";s:9:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:9:"ペンス";s:3:"㌻";s:9:"ページ";s:3:"㌼";s:9:"ベータ";s:3:"㌽";s:12:"ポイント";s:3:"㌾";s:9:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"㍀";s:9:"ポンド";s:3:"㍁";s:9:"ホール";s:3:"㍂";s:9:"ホーン";s:3:"㍃";s:12:"マイクロ";s:3:"㍄";s:9:"マイル";s:3:"㍅";s:9:"マッハ";s:3:"㍆";s:9:"マルク";s:3:"㍇";s:15:"マンション";s:3:"㍈";s:12:"ミクロン";s:3:"㍉";s:6:"ミリ";s:3:"㍊";s:15:"ミリバール";s:3:"㍋";s:6:"メガ";s:3:"㍌";s:12:"メガトン";s:3:"㍍";s:12:"メートル";s:3:"㍎";s:9:"ヤード";s:3:"㍏";s:9:"ヤール";s:3:"㍐";s:9:"ユアン";s:3:"㍑";s:12:"リットル";s:3:"㍒";s:6:"リラ";s:3:"㍓";s:9:"ルピー";s:3:"㍔";s:12:"ルーブル";s:3:"㍕";s:6:"レム";s:3:"㍖";s:15:"レントゲン";s:3:"㍗";s:9:"ワット";s:3:"㍘";s:4:"0点";s:3:"㍙";s:4:"1点";s:3:"㍚";s:4:"2点";s:3:"㍛";s:4:"3点";s:3:"㍜";s:4:"4点";s:3:"㍝";s:4:"5点";s:3:"㍞";s:4:"6点";s:3:"㍟";s:4:"7点";s:3:"㍠";s:4:"8点";s:3:"㍡";s:4:"9点";s:3:"㍢";s:5:"10点";s:3:"㍣";s:5:"11点";s:3:"㍤";s:5:"12点";s:3:"㍥";s:5:"13点";s:3:"㍦";s:5:"14点";s:3:"㍧";s:5:"15点";s:3:"㍨";s:5:"16点";s:3:"㍩";s:5:"17点";s:3:"㍪";s:5:"18点";s:3:"㍫";s:5:"19点";s:3:"㍬";s:5:"20点";s:3:"㍭";s:5:"21点";s:3:"㍮";s:5:"22点";s:3:"㍯";s:5:"23点";s:3:"㍰";s:5:"24点";s:3:"㍱";s:3:"hPa";s:3:"㍲";s:2:"da";s:3:"㍳";s:2:"AU";s:3:"㍴";s:3:"bar";s:3:"㍵";s:2:"oV";s:3:"㍶";s:2:"pc";s:3:"㍷";s:2:"dm";s:3:"㍸";s:4:"dm²";s:3:"㍹";s:4:"dm³";s:3:"㍺";s:2:"IU";s:3:"㍻";s:6:"平成";s:3:"㍼";s:6:"昭和";s:3:"㍽";s:6:"大正";s:3:"㍾";s:6:"明治";s:3:"㍿";s:12:"株式会社";s:3:"㎀";s:2:"pA";s:3:"㎁";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"㎍";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"㎏";s:2:"kg";s:3:"㎐";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:5:"μℓ";s:3:"㎖";s:4:"mℓ";s:3:"㎗";s:4:"dℓ";s:3:"㎘";s:4:"kℓ";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"㎝";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:4:"mm²";s:3:"㎠";s:4:"cm²";s:3:"㎡";s:3:"m²";s:3:"㎢";s:4:"km²";s:3:"㎣";s:4:"mm³";s:3:"㎤";s:4:"cm³";s:3:"㎥";s:3:"m³";s:3:"㎦";s:4:"km³";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:7:"m∕s²";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:9:"rad∕s²";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"㏀";s:3:"kΩ";s:3:"㏁";s:3:"MΩ";s:3:"㏂";s:4:"a.m.";s:3:"㏃";s:2:"Bq";s:3:"㏄";s:2:"cc";s:3:"㏅";s:2:"cd";s:3:"㏆";s:6:"C∕kg";s:3:"㏇";s:3:"Co.";s:3:"㏈";s:2:"dB";s:3:"㏉";s:2:"Gy";s:3:"㏊";s:2:"ha";s:3:"㏋";s:2:"HP";s:3:"㏌";s:2:"in";s:3:"㏍";s:2:"KK";s:3:"㏎";s:2:"KM";s:3:"㏏";s:2:"kt";s:3:"㏐";s:2:"lm";s:3:"㏑";s:2:"ln";s:3:"㏒";s:3:"log";s:3:"㏓";s:2:"lx";s:3:"㏔";s:2:"mb";s:3:"㏕";s:3:"mil";s:3:"㏖";s:3:"mol";s:3:"㏗";s:2:"PH";s:3:"㏘";s:4:"p.m.";s:3:"㏙";s:3:"PPM";s:3:"㏚";s:2:"PR";s:3:"㏛";s:2:"sr";s:3:"㏜";s:2:"Sv";s:3:"㏝";s:2:"Wb";s:3:"㏞";s:5:"V∕m";s:3:"㏟";s:5:"A∕m";s:3:"㏠";s:4:"1日";s:3:"㏡";s:4:"2日";s:3:"㏢";s:4:"3日";s:3:"㏣";s:4:"4日";s:3:"㏤";s:4:"5日";s:3:"㏥";s:4:"6日";s:3:"㏦";s:4:"7日";s:3:"㏧";s:4:"8日";s:3:"㏨";s:4:"9日";s:3:"㏩";s:5:"10日";s:3:"㏪";s:5:"11日";s:3:"㏫";s:5:"12日";s:3:"㏬";s:5:"13日";s:3:"㏭";s:5:"14日";s:3:"㏮";s:5:"15日";s:3:"㏯";s:5:"16日";s:3:"㏰";s:5:"17日";s:3:"㏱";s:5:"18日";s:3:"㏲";s:5:"19日";s:3:"㏳";s:5:"20日";s:3:"㏴";s:5:"21日";s:3:"㏵";s:5:"22日";s:3:"㏶";s:5:"23日";s:3:"㏷";s:5:"24日";s:3:"㏸";s:5:"25日";s:3:"㏹";s:5:"26日";s:3:"㏺";s:5:"27日";s:3:"㏻";s:5:"28日";s:3:"㏼";s:5:"29日";s:3:"㏽";s:5:"30日";s:3:"㏾";s:5:"31日";s:3:"㏿";s:3:"gal";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"﨎";s:1:"";s:3:"﨏";s:1:"";s:3:"塚";s:3:"塚";s:3:"﨑";s:1:"";s:3:"晴";s:3:"晴";s:3:"﨓";s:1:"";s:3:"﨔";s:1:"";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"﨟";s:1:"";s:3:"蘒";s:3:"蘒";s:3:"﨡";s:1:"";s:3:"諸";s:3:"諸";s:3:"﨣";s:1:"";s:3:"﨤";s:1:"";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"﨧";s:1:"";s:3:"﨨";s:1:"";s:3:"﨩";s:1:"";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"郞";s:3:"郞";s:3:"隷";s:3:"隷";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:3:"𤋮";s:4:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"ff";s:2:"ff";s:3:"fi";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:3:"ſt";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"մն";s:3:"ﬔ";s:4:"մե";s:3:"ﬕ";s:4:"մի";s:3:"ﬖ";s:4:"վն";s:3:"ﬗ";s:4:"մխ";s:3:"ﬠ";s:2:"ע";s:3:"ﬡ";s:2:"א";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"ה";s:3:"ﬤ";s:2:"כ";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"ם";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"ﭏ";s:4:"אל";s:3:"﹉";s:3:"‾";s:3:"﹊";s:3:"‾";s:3:"﹋";s:3:"‾";s:3:"﹌";s:3:"‾";s:3:"﹍";s:1:"_";s:3:"﹎";s:1:"_";s:3:"﹏";s:1:"_";s:3:"﹐";s:1:",";s:3:"﹑";s:3:"、";s:3:"﹒";s:1:".";s:3:"﹔";s:1:";";s:3:"﹕";s:1:":";s:3:"﹖";s:1:"?";s:3:"﹗";s:1:"!";s:3:"﹘";s:3:"—";s:3:"﹙";s:1:"(";s:3:"﹚";s:1:")";s:3:"﹛";s:1:"{";s:3:"﹜";s:1:"}";s:3:"﹝";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"﹠";s:1:"&";s:3:"﹡";s:1:"*";s:3:"﹢";s:1:"+";s:3:"﹣";s:1:"-";s:3:"﹤";s:1:"<";s:3:"﹥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"!";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"%";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"-";s:1:"-";s:3:".";s:1:".";s:3:"/";s:1:"/";s:3:"0";s:1:"0";s:3:"1";s:1:"1";s:3:"2";s:1:"2";s:3:"3";s:1:"3";s:3:"4";s:1:"4";s:3:"5";s:1:"5";s:3:"6";s:1:"6";s:3:"7";s:1:"7";s:3:"8";s:1:"8";s:3:"9";s:1:"9";s:3:":";s:1:":";s:3:";";s:1:";";s:3:"<";s:1:"<";s:3:"=";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"@";s:1:"@";s:3:"A";s:1:"A";s:3:"B";s:1:"B";s:3:"C";s:1:"C";s:3:"D";s:1:"D";s:3:"E";s:1:"E";s:3:"F";s:1:"F";s:3:"G";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"M";s:1:"M";s:3:"N";s:1:"N";s:3:"O";s:1:"O";s:3:"P";s:1:"P";s:3:"Q";s:1:"Q";s:3:"R";s:1:"R";s:3:"S";s:1:"S";s:3:"T";s:1:"T";s:3:"U";s:1:"U";s:3:"V";s:1:"V";s:3:"W";s:1:"W";s:3:"X";s:1:"X";s:3:"Y";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"[";s:1:"[";s:3:"\";s:1:"\";s:3:"]";s:1:"]";s:3:"^";s:1:"^";s:3:"_";s:1:"_";s:3:"`";s:1:"`";s:3:"a";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"e";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"m";s:1:"m";s:3:"n";s:1:"n";s:3:"o";s:1:"o";s:3:"p";s:1:"p";s:3:"q";s:1:"q";s:3:"r";s:1:"r";s:3:"s";s:1:"s";s:3:"t";s:1:"t";s:3:"u";s:1:"u";s:3:"v";s:1:"v";s:3:"w";s:1:"w";s:3:"x";s:1:"x";s:3:"y";s:1:"y";s:3:"z";s:1:"z";s:3:"{";s:1:"{";s:3:"|";s:1:"|";s:3:"}";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"⦆";s:3:"⦆";s:3:"。";s:3:"。";s:3:"「";s:3:"「";s:3:"」";s:3:"」";s:3:"、";s:3:"、";s:3:"・";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ァ";s:3:"ァ";s:3:"ィ";s:3:"ィ";s:3:"ゥ";s:3:"ゥ";s:3:"ェ";s:3:"ェ";s:3:"ォ";s:3:"ォ";s:3:"ャ";s:3:"ャ";s:3:"ュ";s:3:"ュ";s:3:"ョ";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ー";s:3:"ー";s:3:"ア";s:3:"ア";s:3:"イ";s:3:"イ";s:3:"ウ";s:3:"ウ";s:3:"エ";s:3:"エ";s:3:"オ";s:3:"オ";s:3:"カ";s:3:"カ";s:3:"キ";s:3:"キ";s:3:"ク";s:3:"ク";s:3:"ケ";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"サ";s:3:"サ";s:3:"シ";s:3:"シ";s:3:"ス";s:3:"ス";s:3:"セ";s:3:"セ";s:3:"ソ";s:3:"ソ";s:3:"タ";s:3:"タ";s:3:"チ";s:3:"チ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ナ";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ネ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ハ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ヘ";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"マ";s:3:"マ";s:3:"ミ";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"メ";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ヤ";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ヨ";s:3:"ヨ";s:3:"ラ";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ル";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ロ";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ン";s:3:"ン";s:3:"゙";s:3:"゙";s:3:"゚";s:3:"゚";s:3:"ᅠ";s:3:"ㅤ";s:3:"ᄀ";s:3:"ㄱ";s:3:"ᄁ";s:3:"ㄲ";s:3:"ᆪ";s:3:"ㄳ";s:3:"ᄂ";s:3:"ㄴ";s:3:"ᆬ";s:3:"ㄵ";s:3:"ᆭ";s:3:"ㄶ";s:3:"ᄃ";s:3:"ㄷ";s:3:"ᄄ";s:3:"ㄸ";s:3:"ᄅ";s:3:"ㄹ";s:3:"ᆰ";s:3:"ㄺ";s:3:"ᆱ";s:3:"ㄻ";s:3:"ᆲ";s:3:"ㄼ";s:3:"ᆳ";s:3:"ㄽ";s:3:"ᆴ";s:3:"ㄾ";s:3:"ᆵ";s:3:"ㄿ";s:3:"ᄚ";s:3:"ㅀ";s:3:"ᄆ";s:3:"ㅁ";s:3:"ᄇ";s:3:"ㅂ";s:3:"ᄈ";s:3:"ㅃ";s:3:"ᄡ";s:3:"ㅄ";s:3:"ᄉ";s:3:"ㅅ";s:3:"ᄊ";s:3:"ㅆ";s:3:"ᄋ";s:3:"ㅇ";s:3:"ᄌ";s:3:"ㅈ";s:3:"ᄍ";s:3:"ㅉ";s:3:"ᄎ";s:3:"ㅊ";s:3:"ᄏ";s:3:"ㅋ";s:3:"ᄐ";s:3:"ㅌ";s:3:"ᄑ";s:3:"ㅍ";s:3:"ᄒ";s:3:"ㅎ";s:3:"ᅡ";s:3:"ㅏ";s:3:"ᅢ";s:3:"ㅐ";s:3:"ᅣ";s:3:"ㅑ";s:3:"ᅤ";s:3:"ㅒ";s:3:"ᅥ";s:3:"ㅓ";s:3:"ᅦ";s:3:"ㅔ";s:3:"ᅧ";s:3:"ㅕ";s:3:"ᅨ";s:3:"ㅖ";s:3:"ᅩ";s:3:"ㅗ";s:3:"ᅪ";s:3:"ㅘ";s:3:"ᅫ";s:3:"ㅙ";s:3:"ᅬ";s:3:"ㅚ";s:3:"ᅭ";s:3:"ㅛ";s:3:"ᅮ";s:3:"ㅜ";s:3:"ᅯ";s:3:"ㅝ";s:3:"ᅰ";s:3:"ㅞ";s:3:"ᅱ";s:3:"ㅟ";s:3:"ᅲ";s:3:"ㅠ";s:3:"ᅳ";s:3:"ㅡ";s:3:"ᅴ";s:3:"ㅢ";s:3:"ᅵ";s:3:"ㅣ";s:3:"¢";s:2:"¢";s:3:"£";s:2:"£";s:3:"¬";s:2:"¬";s:3:" ̄";s:2:"¯";s:3:"¦";s:2:"¦";s:3:"¥";s:2:"¥";s:3:"₩";s:3:"₩";s:3:"│";s:3:"│";s:3:"←";s:3:"←";s:3:"↑";s:3:"↑";s:3:"→";s:3:"→";s:3:"↓";s:3:"↓";s:3:"■";s:3:"■";s:3:"○";s:3:"○";s:4:"𝐀";s:1:"A";s:4:"𝐁";s:1:"B";s:4:"𝐂";s:1:"C";s:4:"𝐃";s:1:"D";s:4:"𝐄";s:1:"E";s:4:"𝐅";s:1:"F";s:4:"𝐆";s:1:"G";s:4:"𝐇";s:1:"H";s:4:"𝐈";s:1:"I";s:4:"𝐉";s:1:"J";s:4:"𝐊";s:1:"K";s:4:"𝐋";s:1:"L";s:4:"𝐌";s:1:"M";s:4:"𝐍";s:1:"N";s:4:"𝐎";s:1:"O";s:4:"𝐏";s:1:"P";s:4:"𝐐";s:1:"Q";s:4:"𝐑";s:1:"R";s:4:"𝐒";s:1:"S";s:4:"𝐓";s:1:"T";s:4:"𝐔";s:1:"U";s:4:"𝐕";s:1:"V";s:4:"𝐖";s:1:"W";s:4:"𝐗";s:1:"X";s:4:"𝐘";s:1:"Y";s:4:"𝐙";s:1:"Z";s:4:"𝐚";s:1:"a";s:4:"𝐛";s:1:"b";s:4:"𝐜";s:1:"c";s:4:"𝐝";s:1:"d";s:4:"𝐞";s:1:"e";s:4:"𝐟";s:1:"f";s:4:"𝐠";s:1:"g";s:4:"𝐡";s:1:"h";s:4:"𝐢";s:1:"i";s:4:"𝐣";s:1:"j";s:4:"𝐤";s:1:"k";s:4:"𝐥";s:1:"l";s:4:"𝐦";s:1:"m";s:4:"𝐧";s:1:"n";s:4:"𝐨";s:1:"o";s:4:"𝐩";s:1:"p";s:4:"𝐪";s:1:"q";s:4:"𝐫";s:1:"r";s:4:"𝐬";s:1:"s";s:4:"𝐭";s:1:"t";s:4:"𝐮";s:1:"u";s:4:"𝐯";s:1:"v";s:4:"𝐰";s:1:"w";s:4:"𝐱";s:1:"x";s:4:"𝐲";s:1:"y";s:4:"𝐳";s:1:"z";s:4:"𝐴";s:1:"A";s:4:"𝐵";s:1:"B";s:4:"𝐶";s:1:"C";s:4:"𝐷";s:1:"D";s:4:"𝐸";s:1:"E";s:4:"𝐹";s:1:"F";s:4:"𝐺";s:1:"G";s:4:"𝐻";s:1:"H";s:4:"𝐼";s:1:"I";s:4:"𝐽";s:1:"J";s:4:"𝐾";s:1:"K";s:4:"𝐿";s:1:"L";s:4:"𝑀";s:1:"M";s:4:"𝑁";s:1:"N";s:4:"𝑂";s:1:"O";s:4:"𝑃";s:1:"P";s:4:"𝑄";s:1:"Q";s:4:"𝑅";s:1:"R";s:4:"𝑆";s:1:"S";s:4:"𝑇";s:1:"T";s:4:"𝑈";s:1:"U";s:4:"𝑉";s:1:"V";s:4:"𝑊";s:1:"W";s:4:"𝑋";s:1:"X";s:4:"𝑌";s:1:"Y";s:4:"𝑍";s:1:"Z";s:4:"𝑎";s:1:"a";s:4:"𝑏";s:1:"b";s:4:"𝑐";s:1:"c";s:4:"𝑑";s:1:"d";s:4:"𝑒";s:1:"e";s:4:"𝑓";s:1:"f";s:4:"𝑔";s:1:"g";s:4:"𝑖";s:1:"i";s:4:"𝑗";s:1:"j";s:4:"𝑘";s:1:"k";s:4:"𝑙";s:1:"l";s:4:"𝑚";s:1:"m";s:4:"𝑛";s:1:"n";s:4:"𝑜";s:1:"o";s:4:"𝑝";s:1:"p";s:4:"𝑞";s:1:"q";s:4:"𝑟";s:1:"r";s:4:"𝑠";s:1:"s";s:4:"𝑡";s:1:"t";s:4:"𝑢";s:1:"u";s:4:"𝑣";s:1:"v";s:4:"𝑤";s:1:"w";s:4:"𝑥";s:1:"x";s:4:"𝑦";s:1:"y";s:4:"𝑧";s:1:"z";s:4:"𝑨";s:1:"A";s:4:"𝑩";s:1:"B";s:4:"𝑪";s:1:"C";s:4:"𝑫";s:1:"D";s:4:"𝑬";s:1:"E";s:4:"𝑭";s:1:"F";s:4:"𝑮";s:1:"G";s:4:"𝑯";s:1:"H";s:4:"𝑰";s:1:"I";s:4:"𝑱";s:1:"J";s:4:"𝑲";s:1:"K";s:4:"𝑳";s:1:"L";s:4:"𝑴";s:1:"M";s:4:"𝑵";s:1:"N";s:4:"𝑶";s:1:"O";s:4:"𝑷";s:1:"P";s:4:"𝑸";s:1:"Q";s:4:"𝑹";s:1:"R";s:4:"𝑺";s:1:"S";s:4:"𝑻";s:1:"T";s:4:"𝑼";s:1:"U";s:4:"𝑽";s:1:"V";s:4:"𝑾";s:1:"W";s:4:"𝑿";s:1:"X";s:4:"𝒀";s:1:"Y";s:4:"𝒁";s:1:"Z";s:4:"𝒂";s:1:"a";s:4:"𝒃";s:1:"b";s:4:"𝒄";s:1:"c";s:4:"𝒅";s:1:"d";s:4:"𝒆";s:1:"e";s:4:"𝒇";s:1:"f";s:4:"𝒈";s:1:"g";s:4:"𝒉";s:1:"h";s:4:"𝒊";s:1:"i";s:4:"𝒋";s:1:"j";s:4:"𝒌";s:1:"k";s:4:"𝒍";s:1:"l";s:4:"𝒎";s:1:"m";s:4:"𝒏";s:1:"n";s:4:"𝒐";s:1:"o";s:4:"𝒑";s:1:"p";s:4:"𝒒";s:1:"q";s:4:"𝒓";s:1:"r";s:4:"𝒔";s:1:"s";s:4:"𝒕";s:1:"t";s:4:"𝒖";s:1:"u";s:4:"𝒗";s:1:"v";s:4:"𝒘";s:1:"w";s:4:"𝒙";s:1:"x";s:4:"𝒚";s:1:"y";s:4:"𝒛";s:1:"z";s:4:"𝒜";s:1:"A";s:4:"𝒞";s:1:"C";s:4:"𝒟";s:1:"D";s:4:"𝒢";s:1:"G";s:4:"𝒥";s:1:"J";s:4:"𝒦";s:1:"K";s:4:"𝒩";s:1:"N";s:4:"𝒪";s:1:"O";s:4:"𝒫";s:1:"P";s:4:"𝒬";s:1:"Q";s:4:"𝒮";s:1:"S";s:4:"𝒯";s:1:"T";s:4:"𝒰";s:1:"U";s:4:"𝒱";s:1:"V";s:4:"𝒲";s:1:"W";s:4:"𝒳";s:1:"X";s:4:"𝒴";s:1:"Y";s:4:"𝒵";s:1:"Z";s:4:"𝒶";s:1:"a";s:4:"𝒷";s:1:"b";s:4:"𝒸";s:1:"c";s:4:"𝒹";s:1:"d";s:4:"𝒻";s:1:"f";s:4:"𝒽";s:1:"h";s:4:"𝒾";s:1:"i";s:4:"𝒿";s:1:"j";s:4:"𝓀";s:1:"k";s:4:"𝓁";s:1:"l";s:4:"𝓂";s:1:"m";s:4:"𝓃";s:1:"n";s:4:"𝓅";s:1:"p";s:4:"𝓆";s:1:"q";s:4:"𝓇";s:1:"r";s:4:"𝓈";s:1:"s";s:4:"𝓉";s:1:"t";s:4:"𝓊";s:1:"u";s:4:"𝓋";s:1:"v";s:4:"𝓌";s:1:"w";s:4:"𝓍";s:1:"x";s:4:"𝓎";s:1:"y";s:4:"𝓏";s:1:"z";s:4:"𝓐";s:1:"A";s:4:"𝓑";s:1:"B";s:4:"𝓒";s:1:"C";s:4:"𝓓";s:1:"D";s:4:"𝓔";s:1:"E";s:4:"𝓕";s:1:"F";s:4:"𝓖";s:1:"G";s:4:"𝓗";s:1:"H";s:4:"𝓘";s:1:"I";s:4:"𝓙";s:1:"J";s:4:"𝓚";s:1:"K";s:4:"𝓛";s:1:"L";s:4:"𝓜";s:1:"M";s:4:"𝓝";s:1:"N";s:4:"𝓞";s:1:"O";s:4:"𝓟";s:1:"P";s:4:"𝓠";s:1:"Q";s:4:"𝓡";s:1:"R";s:4:"𝓢";s:1:"S";s:4:"𝓣";s:1:"T";s:4:"𝓤";s:1:"U";s:4:"𝓥";s:1:"V";s:4:"𝓦";s:1:"W";s:4:"𝓧";s:1:"X";s:4:"𝓨";s:1:"Y";s:4:"𝓩";s:1:"Z";s:4:"𝓪";s:1:"a";s:4:"𝓫";s:1:"b";s:4:"𝓬";s:1:"c";s:4:"𝓭";s:1:"d";s:4:"𝓮";s:1:"e";s:4:"𝓯";s:1:"f";s:4:"𝓰";s:1:"g";s:4:"𝓱";s:1:"h";s:4:"𝓲";s:1:"i";s:4:"𝓳";s:1:"j";s:4:"𝓴";s:1:"k";s:4:"𝓵";s:1:"l";s:4:"𝓶";s:1:"m";s:4:"𝓷";s:1:"n";s:4:"𝓸";s:1:"o";s:4:"𝓹";s:1:"p";s:4:"𝓺";s:1:"q";s:4:"𝓻";s:1:"r";s:4:"𝓼";s:1:"s";s:4:"𝓽";s:1:"t";s:4:"𝓾";s:1:"u";s:4:"𝓿";s:1:"v";s:4:"𝔀";s:1:"w";s:4:"𝔁";s:1:"x";s:4:"𝔂";s:1:"y";s:4:"𝔃";s:1:"z";s:4:"𝔄";s:1:"A";s:4:"𝔅";s:1:"B";s:4:"𝔇";s:1:"D";s:4:"𝔈";s:1:"E";s:4:"𝔉";s:1:"F";s:4:"𝔊";s:1:"G";s:4:"𝔍";s:1:"J";s:4:"𝔎";s:1:"K";s:4:"𝔏";s:1:"L";s:4:"𝔐";s:1:"M";s:4:"𝔑";s:1:"N";s:4:"𝔒";s:1:"O";s:4:"𝔓";s:1:"P";s:4:"𝔔";s:1:"Q";s:4:"𝔖";s:1:"S";s:4:"𝔗";s:1:"T";s:4:"𝔘";s:1:"U";s:4:"𝔙";s:1:"V";s:4:"𝔚";s:1:"W";s:4:"𝔛";s:1:"X";s:4:"𝔜";s:1:"Y";s:4:"𝔞";s:1:"a";s:4:"𝔟";s:1:"b";s:4:"𝔠";s:1:"c";s:4:"𝔡";s:1:"d";s:4:"𝔢";s:1:"e";s:4:"𝔣";s:1:"f";s:4:"𝔤";s:1:"g";s:4:"𝔥";s:1:"h";s:4:"𝔦";s:1:"i";s:4:"𝔧";s:1:"j";s:4:"𝔨";s:1:"k";s:4:"𝔩";s:1:"l";s:4:"𝔪";s:1:"m";s:4:"𝔫";s:1:"n";s:4:"𝔬";s:1:"o";s:4:"𝔭";s:1:"p";s:4:"𝔮";s:1:"q";s:4:"𝔯";s:1:"r";s:4:"𝔰";s:1:"s";s:4:"𝔱";s:1:"t";s:4:"𝔲";s:1:"u";s:4:"𝔳";s:1:"v";s:4:"𝔴";s:1:"w";s:4:"𝔵";s:1:"x";s:4:"𝔶";s:1:"y";s:4:"𝔷";s:1:"z";s:4:"𝔸";s:1:"A";s:4:"𝔹";s:1:"B";s:4:"𝔻";s:1:"D";s:4:"𝔼";s:1:"E";s:4:"𝔽";s:1:"F";s:4:"𝔾";s:1:"G";s:4:"𝕀";s:1:"I";s:4:"𝕁";s:1:"J";s:4:"𝕂";s:1:"K";s:4:"𝕃";s:1:"L";s:4:"𝕄";s:1:"M";s:4:"𝕆";s:1:"O";s:4:"𝕊";s:1:"S";s:4:"𝕋";s:1:"T";s:4:"𝕌";s:1:"U";s:4:"𝕍";s:1:"V";s:4:"𝕎";s:1:"W";s:4:"𝕏";s:1:"X";s:4:"𝕐";s:1:"Y";s:4:"𝕒";s:1:"a";s:4:"𝕓";s:1:"b";s:4:"𝕔";s:1:"c";s:4:"𝕕";s:1:"d";s:4:"𝕖";s:1:"e";s:4:"𝕗";s:1:"f";s:4:"𝕘";s:1:"g";s:4:"𝕙";s:1:"h";s:4:"𝕚";s:1:"i";s:4:"𝕛";s:1:"j";s:4:"𝕜";s:1:"k";s:4:"𝕝";s:1:"l";s:4:"𝕞";s:1:"m";s:4:"𝕟";s:1:"n";s:4:"𝕠";s:1:"o";s:4:"𝕡";s:1:"p";s:4:"𝕢";s:1:"q";s:4:"𝕣";s:1:"r";s:4:"𝕤";s:1:"s";s:4:"𝕥";s:1:"t";s:4:"𝕦";s:1:"u";s:4:"𝕧";s:1:"v";s:4:"𝕨";s:1:"w";s:4:"𝕩";s:1:"x";s:4:"𝕪";s:1:"y";s:4:"𝕫";s:1:"z";s:4:"𝕬";s:1:"A";s:4:"𝕭";s:1:"B";s:4:"𝕮";s:1:"C";s:4:"𝕯";s:1:"D";s:4:"𝕰";s:1:"E";s:4:"𝕱";s:1:"F";s:4:"𝕲";s:1:"G";s:4:"𝕳";s:1:"H";s:4:"𝕴";s:1:"I";s:4:"𝕵";s:1:"J";s:4:"𝕶";s:1:"K";s:4:"𝕷";s:1:"L";s:4:"𝕸";s:1:"M";s:4:"𝕹";s:1:"N";s:4:"𝕺";s:1:"O";s:4:"𝕻";s:1:"P";s:4:"𝕼";s:1:"Q";s:4:"𝕽";s:1:"R";s:4:"𝕾";s:1:"S";s:4:"𝕿";s:1:"T";s:4:"𝖀";s:1:"U";s:4:"𝖁";s:1:"V";s:4:"𝖂";s:1:"W";s:4:"𝖃";s:1:"X";s:4:"𝖄";s:1:"Y";s:4:"𝖅";s:1:"Z";s:4:"𝖆";s:1:"a";s:4:"𝖇";s:1:"b";s:4:"𝖈";s:1:"c";s:4:"𝖉";s:1:"d";s:4:"𝖊";s:1:"e";s:4:"𝖋";s:1:"f";s:4:"𝖌";s:1:"g";s:4:"𝖍";s:1:"h";s:4:"𝖎";s:1:"i";s:4:"𝖏";s:1:"j";s:4:"𝖐";s:1:"k";s:4:"𝖑";s:1:"l";s:4:"𝖒";s:1:"m";s:4:"𝖓";s:1:"n";s:4:"𝖔";s:1:"o";s:4:"𝖕";s:1:"p";s:4:"𝖖";s:1:"q";s:4:"𝖗";s:1:"r";s:4:"𝖘";s:1:"s";s:4:"𝖙";s:1:"t";s:4:"𝖚";s:1:"u";s:4:"𝖛";s:1:"v";s:4:"𝖜";s:1:"w";s:4:"𝖝";s:1:"x";s:4:"𝖞";s:1:"y";s:4:"𝖟";s:1:"z";s:4:"𝖠";s:1:"A";s:4:"𝖡";s:1:"B";s:4:"𝖢";s:1:"C";s:4:"𝖣";s:1:"D";s:4:"𝖤";s:1:"E";s:4:"𝖥";s:1:"F";s:4:"𝖦";s:1:"G";s:4:"𝖧";s:1:"H";s:4:"𝖨";s:1:"I";s:4:"𝖩";s:1:"J";s:4:"𝖪";s:1:"K";s:4:"𝖫";s:1:"L";s:4:"𝖬";s:1:"M";s:4:"𝖭";s:1:"N";s:4:"𝖮";s:1:"O";s:4:"𝖯";s:1:"P";s:4:"𝖰";s:1:"Q";s:4:"𝖱";s:1:"R";s:4:"𝖲";s:1:"S";s:4:"𝖳";s:1:"T";s:4:"𝖴";s:1:"U";s:4:"𝖵";s:1:"V";s:4:"𝖶";s:1:"W";s:4:"𝖷";s:1:"X";s:4:"𝖸";s:1:"Y";s:4:"𝖹";s:1:"Z";s:4:"𝖺";s:1:"a";s:4:"𝖻";s:1:"b";s:4:"𝖼";s:1:"c";s:4:"𝖽";s:1:"d";s:4:"𝖾";s:1:"e";s:4:"𝖿";s:1:"f";s:4:"𝗀";s:1:"g";s:4:"𝗁";s:1:"h";s:4:"𝗂";s:1:"i";s:4:"𝗃";s:1:"j";s:4:"𝗄";s:1:"k";s:4:"𝗅";s:1:"l";s:4:"𝗆";s:1:"m";s:4:"𝗇";s:1:"n";s:4:"𝗈";s:1:"o";s:4:"𝗉";s:1:"p";s:4:"𝗊";s:1:"q";s:4:"𝗋";s:1:"r";s:4:"𝗌";s:1:"s";s:4:"𝗍";s:1:"t";s:4:"𝗎";s:1:"u";s:4:"𝗏";s:1:"v";s:4:"𝗐";s:1:"w";s:4:"𝗑";s:1:"x";s:4:"𝗒";s:1:"y";s:4:"𝗓";s:1:"z";s:4:"𝗔";s:1:"A";s:4:"𝗕";s:1:"B";s:4:"𝗖";s:1:"C";s:4:"𝗗";s:1:"D";s:4:"𝗘";s:1:"E";s:4:"𝗙";s:1:"F";s:4:"𝗚";s:1:"G";s:4:"𝗛";s:1:"H";s:4:"𝗜";s:1:"I";s:4:"𝗝";s:1:"J";s:4:"𝗞";s:1:"K";s:4:"𝗟";s:1:"L";s:4:"𝗠";s:1:"M";s:4:"𝗡";s:1:"N";s:4:"𝗢";s:1:"O";s:4:"𝗣";s:1:"P";s:4:"𝗤";s:1:"Q";s:4:"𝗥";s:1:"R";s:4:"𝗦";s:1:"S";s:4:"𝗧";s:1:"T";s:4:"𝗨";s:1:"U";s:4:"𝗩";s:1:"V";s:4:"𝗪";s:1:"W";s:4:"𝗫";s:1:"X";s:4:"𝗬";s:1:"Y";s:4:"𝗭";s:1:"Z";s:4:"𝗮";s:1:"a";s:4:"𝗯";s:1:"b";s:4:"𝗰";s:1:"c";s:4:"𝗱";s:1:"d";s:4:"𝗲";s:1:"e";s:4:"𝗳";s:1:"f";s:4:"𝗴";s:1:"g";s:4:"𝗵";s:1:"h";s:4:"𝗶";s:1:"i";s:4:"𝗷";s:1:"j";s:4:"𝗸";s:1:"k";s:4:"𝗹";s:1:"l";s:4:"𝗺";s:1:"m";s:4:"𝗻";s:1:"n";s:4:"𝗼";s:1:"o";s:4:"𝗽";s:1:"p";s:4:"𝗾";s:1:"q";s:4:"𝗿";s:1:"r";s:4:"𝘀";s:1:"s";s:4:"𝘁";s:1:"t";s:4:"𝘂";s:1:"u";s:4:"𝘃";s:1:"v";s:4:"𝘄";s:1:"w";s:4:"𝘅";s:1:"x";s:4:"𝘆";s:1:"y";s:4:"𝘇";s:1:"z";s:4:"𝘈";s:1:"A";s:4:"𝘉";s:1:"B";s:4:"𝘊";s:1:"C";s:4:"𝘋";s:1:"D";s:4:"𝘌";s:1:"E";s:4:"𝘍";s:1:"F";s:4:"𝘎";s:1:"G";s:4:"𝘏";s:1:"H";s:4:"𝘐";s:1:"I";s:4:"𝘑";s:1:"J";s:4:"𝘒";s:1:"K";s:4:"𝘓";s:1:"L";s:4:"𝘔";s:1:"M";s:4:"𝘕";s:1:"N";s:4:"𝘖";s:1:"O";s:4:"𝘗";s:1:"P";s:4:"𝘘";s:1:"Q";s:4:"𝘙";s:1:"R";s:4:"𝘚";s:1:"S";s:4:"𝘛";s:1:"T";s:4:"𝘜";s:1:"U";s:4:"𝘝";s:1:"V";s:4:"𝘞";s:1:"W";s:4:"𝘟";s:1:"X";s:4:"𝘠";s:1:"Y";s:4:"𝘡";s:1:"Z";s:4:"𝘢";s:1:"a";s:4:"𝘣";s:1:"b";s:4:"𝘤";s:1:"c";s:4:"𝘥";s:1:"d";s:4:"𝘦";s:1:"e";s:4:"𝘧";s:1:"f";s:4:"𝘨";s:1:"g";s:4:"𝘩";s:1:"h";s:4:"𝘪";s:1:"i";s:4:"𝘫";s:1:"j";s:4:"𝘬";s:1:"k";s:4:"𝘭";s:1:"l";s:4:"𝘮";s:1:"m";s:4:"𝘯";s:1:"n";s:4:"𝘰";s:1:"o";s:4:"𝘱";s:1:"p";s:4:"𝘲";s:1:"q";s:4:"𝘳";s:1:"r";s:4:"𝘴";s:1:"s";s:4:"𝘵";s:1:"t";s:4:"𝘶";s:1:"u";s:4:"𝘷";s:1:"v";s:4:"𝘸";s:1:"w";s:4:"𝘹";s:1:"x";s:4:"𝘺";s:1:"y";s:4:"𝘻";s:1:"z";s:4:"𝘼";s:1:"A";s:4:"𝘽";s:1:"B";s:4:"𝘾";s:1:"C";s:4:"𝘿";s:1:"D";s:4:"𝙀";s:1:"E";s:4:"𝙁";s:1:"F";s:4:"𝙂";s:1:"G";s:4:"𝙃";s:1:"H";s:4:"𝙄";s:1:"I";s:4:"𝙅";s:1:"J";s:4:"𝙆";s:1:"K";s:4:"𝙇";s:1:"L";s:4:"𝙈";s:1:"M";s:4:"𝙉";s:1:"N";s:4:"𝙊";s:1:"O";s:4:"𝙋";s:1:"P";s:4:"𝙌";s:1:"Q";s:4:"𝙍";s:1:"R";s:4:"𝙎";s:1:"S";s:4:"𝙏";s:1:"T";s:4:"𝙐";s:1:"U";s:4:"𝙑";s:1:"V";s:4:"𝙒";s:1:"W";s:4:"𝙓";s:1:"X";s:4:"𝙔";s:1:"Y";s:4:"𝙕";s:1:"Z";s:4:"𝙖";s:1:"a";s:4:"𝙗";s:1:"b";s:4:"𝙘";s:1:"c";s:4:"𝙙";s:1:"d";s:4:"𝙚";s:1:"e";s:4:"𝙛";s:1:"f";s:4:"𝙜";s:1:"g";s:4:"𝙝";s:1:"h";s:4:"𝙞";s:1:"i";s:4:"𝙟";s:1:"j";s:4:"𝙠";s:1:"k";s:4:"𝙡";s:1:"l";s:4:"𝙢";s:1:"m";s:4:"𝙣";s:1:"n";s:4:"𝙤";s:1:"o";s:4:"𝙥";s:1:"p";s:4:"𝙦";s:1:"q";s:4:"𝙧";s:1:"r";s:4:"𝙨";s:1:"s";s:4:"𝙩";s:1:"t";s:4:"𝙪";s:1:"u";s:4:"𝙫";s:1:"v";s:4:"𝙬";s:1:"w";s:4:"𝙭";s:1:"x";s:4:"𝙮";s:1:"y";s:4:"𝙯";s:1:"z";s:4:"𝙰";s:1:"A";s:4:"𝙱";s:1:"B";s:4:"𝙲";s:1:"C";s:4:"𝙳";s:1:"D";s:4:"𝙴";s:1:"E";s:4:"𝙵";s:1:"F";s:4:"𝙶";s:1:"G";s:4:"𝙷";s:1:"H";s:4:"𝙸";s:1:"I";s:4:"𝙹";s:1:"J";s:4:"𝙺";s:1:"K";s:4:"𝙻";s:1:"L";s:4:"𝙼";s:1:"M";s:4:"𝙽";s:1:"N";s:4:"𝙾";s:1:"O";s:4:"𝙿";s:1:"P";s:4:"𝚀";s:1:"Q";s:4:"𝚁";s:1:"R";s:4:"𝚂";s:1:"S";s:4:"𝚃";s:1:"T";s:4:"𝚄";s:1:"U";s:4:"𝚅";s:1:"V";s:4:"𝚆";s:1:"W";s:4:"𝚇";s:1:"X";s:4:"𝚈";s:1:"Y";s:4:"𝚉";s:1:"Z";s:4:"𝚊";s:1:"a";s:4:"𝚋";s:1:"b";s:4:"𝚌";s:1:"c";s:4:"𝚍";s:1:"d";s:4:"𝚎";s:1:"e";s:4:"𝚏";s:1:"f";s:4:"𝚐";s:1:"g";s:4:"𝚑";s:1:"h";s:4:"𝚒";s:1:"i";s:4:"𝚓";s:1:"j";s:4:"𝚔";s:1:"k";s:4:"𝚕";s:1:"l";s:4:"𝚖";s:1:"m";s:4:"𝚗";s:1:"n";s:4:"𝚘";s:1:"o";s:4:"𝚙";s:1:"p";s:4:"𝚚";s:1:"q";s:4:"𝚛";s:1:"r";s:4:"𝚜";s:1:"s";s:4:"𝚝";s:1:"t";s:4:"𝚞";s:1:"u";s:4:"𝚟";s:1:"v";s:4:"𝚠";s:1:"w";s:4:"𝚡";s:1:"x";s:4:"𝚢";s:1:"y";s:4:"𝚣";s:1:"z";s:4:"𝚤";s:2:"ı";s:4:"𝚥";s:2:"ȷ";s:4:"𝚨";s:2:"Α";s:4:"𝚩";s:2:"Β";s:4:"𝚪";s:2:"Γ";s:4:"𝚫";s:2:"Δ";s:4:"𝚬";s:2:"Ε";s:4:"𝚭";s:2:"Ζ";s:4:"𝚮";s:2:"Η";s:4:"𝚯";s:2:"Θ";s:4:"𝚰";s:2:"Ι";s:4:"𝚱";s:2:"Κ";s:4:"𝚲";s:2:"Λ";s:4:"𝚳";s:2:"Μ";s:4:"𝚴";s:2:"Ν";s:4:"𝚵";s:2:"Ξ";s:4:"𝚶";s:2:"Ο";s:4:"𝚷";s:2:"Π";s:4:"𝚸";s:2:"Ρ";s:4:"𝚹";s:2:"ϴ";s:4:"𝚺";s:2:"Σ";s:4:"𝚻";s:2:"Τ";s:4:"𝚼";s:2:"Υ";s:4:"𝚽";s:2:"Φ";s:4:"𝚾";s:2:"Χ";s:4:"𝚿";s:2:"Ψ";s:4:"𝛀";s:2:"Ω";s:4:"𝛁";s:3:"∇";s:4:"𝛂";s:2:"α";s:4:"𝛃";s:2:"β";s:4:"𝛄";s:2:"γ";s:4:"𝛅";s:2:"δ";s:4:"𝛆";s:2:"ε";s:4:"𝛇";s:2:"ζ";s:4:"𝛈";s:2:"η";s:4:"𝛉";s:2:"θ";s:4:"𝛊";s:2:"ι";s:4:"𝛋";s:2:"κ";s:4:"𝛌";s:2:"λ";s:4:"𝛍";s:2:"μ";s:4:"𝛎";s:2:"ν";s:4:"𝛏";s:2:"ξ";s:4:"𝛐";s:2:"ο";s:4:"𝛑";s:2:"π";s:4:"𝛒";s:2:"ρ";s:4:"𝛓";s:2:"ς";s:4:"𝛔";s:2:"σ";s:4:"𝛕";s:2:"τ";s:4:"𝛖";s:2:"υ";s:4:"𝛗";s:2:"φ";s:4:"𝛘";s:2:"χ";s:4:"𝛙";s:2:"ψ";s:4:"𝛚";s:2:"ω";s:4:"𝛛";s:3:"∂";s:4:"𝛜";s:2:"ϵ";s:4:"𝛝";s:2:"ϑ";s:4:"𝛞";s:2:"ϰ";s:4:"𝛟";s:2:"ϕ";s:4:"𝛠";s:2:"ϱ";s:4:"𝛡";s:2:"ϖ";s:4:"𝛢";s:2:"Α";s:4:"𝛣";s:2:"Β";s:4:"𝛤";s:2:"Γ";s:4:"𝛥";s:2:"Δ";s:4:"𝛦";s:2:"Ε";s:4:"𝛧";s:2:"Ζ";s:4:"𝛨";s:2:"Η";s:4:"𝛩";s:2:"Θ";s:4:"𝛪";s:2:"Ι";s:4:"𝛫";s:2:"Κ";s:4:"𝛬";s:2:"Λ";s:4:"𝛭";s:2:"Μ";s:4:"𝛮";s:2:"Ν";s:4:"𝛯";s:2:"Ξ";s:4:"𝛰";s:2:"Ο";s:4:"𝛱";s:2:"Π";s:4:"𝛲";s:2:"Ρ";s:4:"𝛳";s:2:"ϴ";s:4:"𝛴";s:2:"Σ";s:4:"𝛵";s:2:"Τ";s:4:"𝛶";s:2:"Υ";s:4:"𝛷";s:2:"Φ";s:4:"𝛸";s:2:"Χ";s:4:"𝛹";s:2:"Ψ";s:4:"𝛺";s:2:"Ω";s:4:"𝛻";s:3:"∇";s:4:"𝛼";s:2:"α";s:4:"𝛽";s:2:"β";s:4:"𝛾";s:2:"γ";s:4:"𝛿";s:2:"δ";s:4:"𝜀";s:2:"ε";s:4:"𝜁";s:2:"ζ";s:4:"𝜂";s:2:"η";s:4:"𝜃";s:2:"θ";s:4:"𝜄";s:2:"ι";s:4:"𝜅";s:2:"κ";s:4:"𝜆";s:2:"λ";s:4:"𝜇";s:2:"μ";s:4:"𝜈";s:2:"ν";s:4:"𝜉";s:2:"ξ";s:4:"𝜊";s:2:"ο";s:4:"𝜋";s:2:"π";s:4:"𝜌";s:2:"ρ";s:4:"𝜍";s:2:"ς";s:4:"𝜎";s:2:"σ";s:4:"𝜏";s:2:"τ";s:4:"𝜐";s:2:"υ";s:4:"𝜑";s:2:"φ";s:4:"𝜒";s:2:"χ";s:4:"𝜓";s:2:"ψ";s:4:"𝜔";s:2:"ω";s:4:"𝜕";s:3:"∂";s:4:"𝜖";s:2:"ϵ";s:4:"𝜗";s:2:"ϑ";s:4:"𝜘";s:2:"ϰ";s:4:"𝜙";s:2:"ϕ";s:4:"𝜚";s:2:"ϱ";s:4:"𝜛";s:2:"ϖ";s:4:"𝜜";s:2:"Α";s:4:"𝜝";s:2:"Β";s:4:"𝜞";s:2:"Γ";s:4:"𝜟";s:2:"Δ";s:4:"𝜠";s:2:"Ε";s:4:"𝜡";s:2:"Ζ";s:4:"𝜢";s:2:"Η";s:4:"𝜣";s:2:"Θ";s:4:"𝜤";s:2:"Ι";s:4:"𝜥";s:2:"Κ";s:4:"𝜦";s:2:"Λ";s:4:"𝜧";s:2:"Μ";s:4:"𝜨";s:2:"Ν";s:4:"𝜩";s:2:"Ξ";s:4:"𝜪";s:2:"Ο";s:4:"𝜫";s:2:"Π";s:4:"𝜬";s:2:"Ρ";s:4:"𝜭";s:2:"ϴ";s:4:"𝜮";s:2:"Σ";s:4:"𝜯";s:2:"Τ";s:4:"𝜰";s:2:"Υ";s:4:"𝜱";s:2:"Φ";s:4:"𝜲";s:2:"Χ";s:4:"𝜳";s:2:"Ψ";s:4:"𝜴";s:2:"Ω";s:4:"𝜵";s:3:"∇";s:4:"𝜶";s:2:"α";s:4:"𝜷";s:2:"β";s:4:"𝜸";s:2:"γ";s:4:"𝜹";s:2:"δ";s:4:"𝜺";s:2:"ε";s:4:"𝜻";s:2:"ζ";s:4:"𝜼";s:2:"η";s:4:"𝜽";s:2:"θ";s:4:"𝜾";s:2:"ι";s:4:"𝜿";s:2:"κ";s:4:"𝝀";s:2:"λ";s:4:"𝝁";s:2:"μ";s:4:"𝝂";s:2:"ν";s:4:"𝝃";s:2:"ξ";s:4:"𝝄";s:2:"ο";s:4:"𝝅";s:2:"π";s:4:"𝝆";s:2:"ρ";s:4:"𝝇";s:2:"ς";s:4:"𝝈";s:2:"σ";s:4:"𝝉";s:2:"τ";s:4:"𝝊";s:2:"υ";s:4:"𝝋";s:2:"φ";s:4:"𝝌";s:2:"χ";s:4:"𝝍";s:2:"ψ";s:4:"𝝎";s:2:"ω";s:4:"𝝏";s:3:"∂";s:4:"𝝐";s:2:"ϵ";s:4:"𝝑";s:2:"ϑ";s:4:"𝝒";s:2:"ϰ";s:4:"𝝓";s:2:"ϕ";s:4:"𝝔";s:2:"ϱ";s:4:"𝝕";s:2:"ϖ";s:4:"𝝖";s:2:"Α";s:4:"𝝗";s:2:"Β";s:4:"𝝘";s:2:"Γ";s:4:"𝝙";s:2:"Δ";s:4:"𝝚";s:2:"Ε";s:4:"𝝛";s:2:"Ζ";s:4:"𝝜";s:2:"Η";s:4:"𝝝";s:2:"Θ";s:4:"𝝞";s:2:"Ι";s:4:"𝝟";s:2:"Κ";s:4:"𝝠";s:2:"Λ";s:4:"𝝡";s:2:"Μ";s:4:"𝝢";s:2:"Ν";s:4:"𝝣";s:2:"Ξ";s:4:"𝝤";s:2:"Ο";s:4:"𝝥";s:2:"Π";s:4:"𝝦";s:2:"Ρ";s:4:"𝝧";s:2:"ϴ";s:4:"𝝨";s:2:"Σ";s:4:"𝝩";s:2:"Τ";s:4:"𝝪";s:2:"Υ";s:4:"𝝫";s:2:"Φ";s:4:"𝝬";s:2:"Χ";s:4:"𝝭";s:2:"Ψ";s:4:"𝝮";s:2:"Ω";s:4:"𝝯";s:3:"∇";s:4:"𝝰";s:2:"α";s:4:"𝝱";s:2:"β";s:4:"𝝲";s:2:"γ";s:4:"𝝳";s:2:"δ";s:4:"𝝴";s:2:"ε";s:4:"𝝵";s:2:"ζ";s:4:"𝝶";s:2:"η";s:4:"𝝷";s:2:"θ";s:4:"𝝸";s:2:"ι";s:4:"𝝹";s:2:"κ";s:4:"𝝺";s:2:"λ";s:4:"𝝻";s:2:"μ";s:4:"𝝼";s:2:"ν";s:4:"𝝽";s:2:"ξ";s:4:"𝝾";s:2:"ο";s:4:"𝝿";s:2:"π";s:4:"𝞀";s:2:"ρ";s:4:"𝞁";s:2:"ς";s:4:"𝞂";s:2:"σ";s:4:"𝞃";s:2:"τ";s:4:"𝞄";s:2:"υ";s:4:"𝞅";s:2:"φ";s:4:"𝞆";s:2:"χ";s:4:"𝞇";s:2:"ψ";s:4:"𝞈";s:2:"ω";s:4:"𝞉";s:3:"∂";s:4:"𝞊";s:2:"ϵ";s:4:"𝞋";s:2:"ϑ";s:4:"𝞌";s:2:"ϰ";s:4:"𝞍";s:2:"ϕ";s:4:"𝞎";s:2:"ϱ";s:4:"𝞏";s:2:"ϖ";s:4:"𝞐";s:2:"Α";s:4:"𝞑";s:2:"Β";s:4:"𝞒";s:2:"Γ";s:4:"𝞓";s:2:"Δ";s:4:"𝞔";s:2:"Ε";s:4:"𝞕";s:2:"Ζ";s:4:"𝞖";s:2:"Η";s:4:"𝞗";s:2:"Θ";s:4:"𝞘";s:2:"Ι";s:4:"𝞙";s:2:"Κ";s:4:"𝞚";s:2:"Λ";s:4:"𝞛";s:2:"Μ";s:4:"𝞜";s:2:"Ν";s:4:"𝞝";s:2:"Ξ";s:4:"𝞞";s:2:"Ο";s:4:"𝞟";s:2:"Π";s:4:"𝞠";s:2:"Ρ";s:4:"𝞡";s:2:"ϴ";s:4:"𝞢";s:2:"Σ";s:4:"𝞣";s:2:"Τ";s:4:"𝞤";s:2:"Υ";s:4:"𝞥";s:2:"Φ";s:4:"𝞦";s:2:"Χ";s:4:"𝞧";s:2:"Ψ";s:4:"𝞨";s:2:"Ω";s:4:"𝞩";s:3:"∇";s:4:"𝞪";s:2:"α";s:4:"𝞫";s:2:"β";s:4:"𝞬";s:2:"γ";s:4:"𝞭";s:2:"δ";s:4:"𝞮";s:2:"ε";s:4:"𝞯";s:2:"ζ";s:4:"𝞰";s:2:"η";s:4:"𝞱";s:2:"θ";s:4:"𝞲";s:2:"ι";s:4:"𝞳";s:2:"κ";s:4:"𝞴";s:2:"λ";s:4:"𝞵";s:2:"μ";s:4:"𝞶";s:2:"ν";s:4:"𝞷";s:2:"ξ";s:4:"𝞸";s:2:"ο";s:4:"𝞹";s:2:"π";s:4:"𝞺";s:2:"ρ";s:4:"𝞻";s:2:"ς";s:4:"𝞼";s:2:"σ";s:4:"𝞽";s:2:"τ";s:4:"𝞾";s:2:"υ";s:4:"𝞿";s:2:"φ";s:4:"𝟀";s:2:"χ";s:4:"𝟁";s:2:"ψ";s:4:"𝟂";s:2:"ω";s:4:"𝟃";s:3:"∂";s:4:"𝟄";s:2:"ϵ";s:4:"𝟅";s:2:"ϑ";s:4:"𝟆";s:2:"ϰ";s:4:"𝟇";s:2:"ϕ";s:4:"𝟈";s:2:"ϱ";s:4:"𝟉";s:2:"ϖ";s:4:"𝟊";s:2:"Ϝ";s:4:"𝟋";s:2:"ϝ";s:4:"𝟎";s:1:"0";s:4:"𝟏";s:1:"1";s:4:"𝟐";s:1:"2";s:4:"𝟑";s:1:"3";s:4:"𝟒";s:1:"4";s:4:"𝟓";s:1:"5";s:4:"𝟔";s:1:"6";s:4:"𝟕";s:1:"7";s:4:"𝟖";s:1:"8";s:4:"𝟗";s:1:"9";s:4:"𝟘";s:1:"0";s:4:"𝟙";s:1:"1";s:4:"𝟚";s:1:"2";s:4:"𝟛";s:1:"3";s:4:"𝟜";s:1:"4";s:4:"𝟝";s:1:"5";s:4:"𝟞";s:1:"6";s:4:"𝟟";s:1:"7";s:4:"𝟠";s:1:"8";s:4:"𝟡";s:1:"9";s:4:"𝟢";s:1:"0";s:4:"𝟣";s:1:"1";s:4:"𝟤";s:1:"2";s:4:"𝟥";s:1:"3";s:4:"𝟦";s:1:"4";s:4:"𝟧";s:1:"5";s:4:"𝟨";s:1:"6";s:4:"𝟩";s:1:"7";s:4:"𝟪";s:1:"8";s:4:"𝟫";s:1:"9";s:4:"𝟬";s:1:"0";s:4:"𝟭";s:1:"1";s:4:"𝟮";s:1:"2";s:4:"𝟯";s:1:"3";s:4:"𝟰";s:1:"4";s:4:"𝟱";s:1:"5";s:4:"𝟲";s:1:"6";s:4:"𝟳";s:1:"7";s:4:"𝟴";s:1:"8";s:4:"𝟵";s:1:"9";s:4:"𝟶";s:1:"0";s:4:"𝟷";s:1:"1";s:4:"𝟸";s:1:"2";s:4:"𝟹";s:1:"3";s:4:"𝟺";s:1:"4";s:4:"𝟻";s:1:"5";s:4:"𝟼";s:1:"6";s:4:"𝟽";s:1:"7";s:4:"𝟾";s:1:"8";s:4:"𝟿";s:1:"9";s:4:"𞸀";s:2:"ا";s:4:"𞸁";s:2:"ب";s:4:"𞸂";s:2:"ج";s:4:"𞸃";s:2:"د";s:4:"𞸅";s:2:"و";s:4:"𞸆";s:2:"ز";s:4:"𞸇";s:2:"ح";s:4:"𞸈";s:2:"ط";s:4:"𞸉";s:2:"ي";s:4:"𞸊";s:2:"ك";s:4:"𞸋";s:2:"ل";s:4:"𞸌";s:2:"م";s:4:"𞸍";s:2:"ن";s:4:"𞸎";s:2:"س";s:4:"𞸏";s:2:"ع";s:4:"𞸐";s:2:"ف";s:4:"𞸑";s:2:"ص";s:4:"𞸒";s:2:"ق";s:4:"𞸓";s:2:"ر";s:4:"𞸔";s:2:"ش";s:4:"𞸕";s:2:"ت";s:4:"𞸖";s:2:"ث";s:4:"𞸗";s:2:"خ";s:4:"𞸘";s:2:"ذ";s:4:"𞸙";s:2:"ض";s:4:"𞸚";s:2:"ظ";s:4:"𞸛";s:2:"غ";s:4:"𞸜";s:2:"ٮ";s:4:"𞸝";s:2:"ں";s:4:"𞸞";s:2:"ڡ";s:4:"𞸟";s:2:"ٯ";s:4:"𞸡";s:2:"ب";s:4:"𞸢";s:2:"ج";s:4:"𞸤";s:2:"ه";s:4:"𞸧";s:2:"ح";s:4:"𞸩";s:2:"ي";s:4:"𞸪";s:2:"ك";s:4:"𞸫";s:2:"ل";s:4:"𞸬";s:2:"م";s:4:"𞸭";s:2:"ن";s:4:"𞸮";s:2:"س";s:4:"𞸯";s:2:"ع";s:4:"𞸰";s:2:"ف";s:4:"𞸱";s:2:"ص";s:4:"𞸲";s:2:"ق";s:4:"𞸴";s:2:"ش";s:4:"𞸵";s:2:"ت";s:4:"𞸶";s:2:"ث";s:4:"𞸷";s:2:"خ";s:4:"𞸹";s:2:"ض";s:4:"𞸻";s:2:"غ";s:4:"𞹂";s:2:"ج";s:4:"𞹇";s:2:"ح";s:4:"𞹉";s:2:"ي";s:4:"𞹋";s:2:"ل";s:4:"𞹍";s:2:"ن";s:4:"𞹎";s:2:"س";s:4:"𞹏";s:2:"ع";s:4:"𞹑";s:2:"ص";s:4:"𞹒";s:2:"ق";s:4:"𞹔";s:2:"ش";s:4:"𞹗";s:2:"خ";s:4:"𞹙";s:2:"ض";s:4:"𞹛";s:2:"غ";s:4:"𞹝";s:2:"ں";s:4:"𞹟";s:2:"ٯ";s:4:"𞹡";s:2:"ب";s:4:"𞹢";s:2:"ج";s:4:"𞹤";s:2:"ه";s:4:"𞹧";s:2:"ح";s:4:"𞹨";s:2:"ط";s:4:"𞹩";s:2:"ي";s:4:"𞹪";s:2:"ك";s:4:"𞹬";s:2:"م";s:4:"𞹭";s:2:"ن";s:4:"𞹮";s:2:"س";s:4:"𞹯";s:2:"ع";s:4:"𞹰";s:2:"ف";s:4:"𞹱";s:2:"ص";s:4:"𞹲";s:2:"ق";s:4:"𞹴";s:2:"ش";s:4:"𞹵";s:2:"ت";s:4:"𞹶";s:2:"ث";s:4:"𞹷";s:2:"خ";s:4:"𞹹";s:2:"ض";s:4:"𞹺";s:2:"ظ";s:4:"𞹻";s:2:"غ";s:4:"𞹼";s:2:"ٮ";s:4:"𞹾";s:2:"ڡ";s:4:"𞺀";s:2:"ا";s:4:"𞺁";s:2:"ب";s:4:"𞺂";s:2:"ج";s:4:"𞺃";s:2:"د";s:4:"𞺄";s:2:"ه";s:4:"𞺅";s:2:"و";s:4:"𞺆";s:2:"ز";s:4:"𞺇";s:2:"ح";s:4:"𞺈";s:2:"ط";s:4:"𞺉";s:2:"ي";s:4:"𞺋";s:2:"ل";s:4:"𞺌";s:2:"م";s:4:"𞺍";s:2:"ن";s:4:"𞺎";s:2:"س";s:4:"𞺏";s:2:"ع";s:4:"𞺐";s:2:"ف";s:4:"𞺑";s:2:"ص";s:4:"𞺒";s:2:"ق";s:4:"𞺓";s:2:"ر";s:4:"𞺔";s:2:"ش";s:4:"𞺕";s:2:"ت";s:4:"𞺖";s:2:"ث";s:4:"𞺗";s:2:"خ";s:4:"𞺘";s:2:"ذ";s:4:"𞺙";s:2:"ض";s:4:"𞺚";s:2:"ظ";s:4:"𞺛";s:2:"غ";s:4:"𞺡";s:2:"ب";s:4:"𞺢";s:2:"ج";s:4:"𞺣";s:2:"د";s:4:"𞺥";s:2:"و";s:4:"𞺦";s:2:"ز";s:4:"𞺧";s:2:"ح";s:4:"𞺨";s:2:"ط";s:4:"𞺩";s:2:"ي";s:4:"𞺫";s:2:"ل";s:4:"𞺬";s:2:"م";s:4:"𞺭";s:2:"ن";s:4:"𞺮";s:2:"س";s:4:"𞺯";s:2:"ع";s:4:"𞺰";s:2:"ف";s:4:"𞺱";s:2:"ص";s:4:"𞺲";s:2:"ق";s:4:"𞺳";s:2:"ر";s:4:"𞺴";s:2:"ش";s:4:"𞺵";s:2:"ت";s:4:"𞺶";s:2:"ث";s:4:"𞺷";s:2:"خ";s:4:"𞺸";s:2:"ذ";s:4:"𞺹";s:2:"ض";s:4:"𞺺";s:2:"ظ";s:4:"𞺻";s:2:"غ";s:4:"🄀";s:2:"0.";s:4:"🄁";s:2:"0,";s:4:"🄂";s:2:"1,";s:4:"🄃";s:2:"2,";s:4:"🄄";s:2:"3,";s:4:"🄅";s:2:"4,";s:4:"🄆";s:2:"5,";s:4:"🄇";s:2:"6,";s:4:"🄈";s:2:"7,";s:4:"🄉";s:2:"8,";s:4:"🄊";s:2:"9,";s:4:"🄐";s:3:"(A)";s:4:"🄑";s:3:"(B)";s:4:"🄒";s:3:"(C)";s:4:"🄓";s:3:"(D)";s:4:"🄔";s:3:"(E)";s:4:"🄕";s:3:"(F)";s:4:"🄖";s:3:"(G)";s:4:"🄗";s:3:"(H)";s:4:"🄘";s:3:"(I)";s:4:"🄙";s:3:"(J)";s:4:"🄚";s:3:"(K)";s:4:"🄛";s:3:"(L)";s:4:"🄜";s:3:"(M)";s:4:"🄝";s:3:"(N)";s:4:"🄞";s:3:"(O)";s:4:"🄟";s:3:"(P)";s:4:"🄠";s:3:"(Q)";s:4:"🄡";s:3:"(R)";s:4:"🄢";s:3:"(S)";s:4:"🄣";s:3:"(T)";s:4:"🄤";s:3:"(U)";s:4:"🄥";s:3:"(V)";s:4:"🄦";s:3:"(W)";s:4:"🄧";s:3:"(X)";s:4:"🄨";s:3:"(Y)";s:4:"🄩";s:3:"(Z)";s:4:"🄪";s:7:"〔S〕";s:4:"🄫";s:3:"(C)";s:4:"🄬";s:3:"(R)";s:4:"🄭";s:4:"(CD)";s:4:"🄮";s:4:"(WZ)";s:4:"🄰";s:1:"A";s:4:"🄱";s:1:"B";s:4:"🄲";s:1:"C";s:4:"🄳";s:1:"D";s:4:"🄴";s:1:"E";s:4:"🄵";s:1:"F";s:4:"🄶";s:1:"G";s:4:"🄷";s:1:"H";s:4:"🄸";s:1:"I";s:4:"🄹";s:1:"J";s:4:"🄺";s:1:"K";s:4:"🄻";s:1:"L";s:4:"🄼";s:1:"M";s:4:"🄽";s:1:"N";s:4:"🄾";s:1:"O";s:4:"🄿";s:1:"P";s:4:"🅀";s:1:"Q";s:4:"🅁";s:1:"R";s:4:"🅂";s:1:"S";s:4:"🅃";s:1:"T";s:4:"🅄";s:1:"U";s:4:"🅅";s:1:"V";s:4:"🅆";s:1:"W";s:4:"🅇";s:1:"X";s:4:"🅈";s:1:"Y";s:4:"🅉";s:1:"Z";s:4:"🅊";s:2:"HV";s:4:"🅋";s:2:"MV";s:4:"🅌";s:2:"SD";s:4:"🅍";s:2:"SS";s:4:"🅎";s:3:"PPV";s:4:"🅏";s:2:"WC";s:4:"🆐";s:2:"DJ";s:4:"🈀";s:6:"ほか";s:4:"🈁";s:6:"ココ";s:4:"🈂";s:3:"サ";s:4:"🈐";s:3:"手";s:4:"🈑";s:3:"字";s:4:"🈒";s:3:"双";s:4:"🈓";s:3:"デ";s:4:"🈔";s:3:"二";s:4:"🈕";s:3:"多";s:4:"🈖";s:3:"解";s:4:"🈗";s:3:"天";s:4:"🈘";s:3:"交";s:4:"🈙";s:3:"映";s:4:"🈚";s:3:"無";s:4:"🈛";s:3:"料";s:4:"🈜";s:3:"前";s:4:"🈝";s:3:"後";s:4:"🈞";s:3:"再";s:4:"🈟";s:3:"新";s:4:"🈠";s:3:"初";s:4:"🈡";s:3:"終";s:4:"🈢";s:3:"生";s:4:"🈣";s:3:"販";s:4:"🈤";s:3:"声";s:4:"🈥";s:3:"吹";s:4:"🈦";s:3:"演";s:4:"🈧";s:3:"投";s:4:"🈨";s:3:"捕";s:4:"🈩";s:3:"一";s:4:"🈪";s:3:"三";s:4:"🈫";s:3:"遊";s:4:"🈬";s:3:"左";s:4:"🈭";s:3:"中";s:4:"🈮";s:3:"右";s:4:"🈯";s:3:"指";s:4:"🈰";s:3:"走";s:4:"🈱";s:3:"打";s:4:"🈲";s:3:"禁";s:4:"🈳";s:3:"空";s:4:"🈴";s:3:"合";s:4:"🈵";s:3:"満";s:4:"🈶";s:3:"有";s:4:"🈷";s:3:"月";s:4:"🈸";s:3:"申";s:4:"🈹";s:3:"割";s:4:"🈺";s:3:"営";s:4:"🉀";s:9:"〔本〕";s:4:"🉁";s:9:"〔三〕";s:4:"🉂";s:9:"〔二〕";s:4:"🉃";s:9:"〔安〕";s:4:"🉄";s:9:"〔点〕";s:4:"🉅";s:9:"〔打〕";s:4:"🉆";s:9:"〔盗〕";s:4:"🉇";s:9:"〔勝〕";s:4:"🉈";s:9:"〔敗〕";s:4:"🉐";s:5:"(得)";s:4:"🉑";s:5:"(可)";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";s:2:"Æ";s:2:"AE";s:2:"Ð";s:1:"D";s:2:"Ø";s:1:"O";s:2:"Þ";s:2:"TH";s:2:"ß";s:2:"ss";s:2:"æ";s:2:"ae";s:2:"ð";s:1:"d";s:2:"ø";s:1:"o";s:2:"þ";s:2:"th";s:2:"Đ";s:1:"D";s:2:"đ";s:1:"d";s:2:"Ħ";s:1:"H";s:2:"ħ";s:1:"h";s:2:"ı";s:1:"i";s:2:"ĸ";s:1:"q";s:2:"Ł";s:1:"L";s:2:"ł";s:1:"l";s:2:"Ŋ";s:1:"N";s:2:"ŋ";s:1:"n";s:2:"Œ";s:2:"OE";s:2:"œ";s:2:"oe";s:2:"Ŧ";s:1:"T";s:2:"ŧ";s:1:"t";s:2:"ƀ";s:1:"b";s:2:"Ɓ";s:1:"B";s:2:"Ƃ";s:1:"B";s:2:"ƃ";s:1:"b";s:2:"Ƈ";s:1:"C";s:2:"ƈ";s:1:"c";s:2:"Ɖ";s:1:"D";s:2:"Ɗ";s:1:"D";s:2:"Ƌ";s:1:"D";s:2:"ƌ";s:1:"d";s:2:"Ɛ";s:1:"E";s:2:"Ƒ";s:1:"F";s:2:"ƒ";s:1:"f";s:2:"Ɠ";s:1:"G";s:2:"ƕ";s:2:"hv";s:2:"Ɩ";s:1:"I";s:2:"Ɨ";s:1:"I";s:2:"Ƙ";s:1:"K";s:2:"ƙ";s:1:"k";s:2:"ƚ";s:1:"l";s:2:"Ɲ";s:1:"N";s:2:"ƞ";s:1:"n";s:2:"Ƣ";s:2:"OI";s:2:"ƣ";s:2:"oi";s:2:"Ƥ";s:1:"P";s:2:"ƥ";s:1:"p";s:2:"ƫ";s:1:"t";s:2:"Ƭ";s:1:"T";s:2:"ƭ";s:1:"t";s:2:"Ʈ";s:1:"T";s:2:"Ʋ";s:1:"V";s:2:"Ƴ";s:1:"Y";s:2:"ƴ";s:1:"y";s:2:"Ƶ";s:1:"Z";s:2:"ƶ";s:1:"z";s:2:"Ǥ";s:1:"G";s:2:"ǥ";s:1:"g";s:2:"ȡ";s:1:"d";s:2:"Ȥ";s:1:"Z";s:2:"ȥ";s:1:"z";s:2:"ȴ";s:1:"l";s:2:"ȵ";s:1:"n";s:2:"ȶ";s:1:"t";s:2:"ȷ";s:1:"j";s:2:"ȸ";s:2:"db";s:2:"ȹ";s:2:"qp";s:2:"Ⱥ";s:1:"A";s:2:"Ȼ";s:1:"C";s:2:"ȼ";s:1:"c";s:2:"Ƚ";s:1:"L";s:2:"Ⱦ";s:1:"T";s:2:"ȿ";s:1:"s";s:2:"ɀ";s:1:"z";s:2:"Ƀ";s:1:"B";s:2:"Ʉ";s:1:"U";s:2:"Ɇ";s:1:"E";s:2:"ɇ";s:1:"e";s:2:"Ɉ";s:1:"J";s:2:"ɉ";s:1:"j";s:2:"Ɍ";s:1:"R";s:2:"ɍ";s:1:"r";s:2:"Ɏ";s:1:"Y";s:2:"ɏ";s:1:"y";s:2:"ɓ";s:1:"b";s:2:"ɕ";s:1:"c";s:2:"ɖ";s:1:"d";s:2:"ɗ";s:1:"d";s:2:"ɛ";s:1:"e";s:2:"ɟ";s:1:"j";s:2:"ɠ";s:1:"g";s:2:"ɡ";s:1:"g";s:2:"ɢ";s:1:"G";s:2:"ɦ";s:1:"h";s:2:"ɧ";s:1:"h";s:2:"ɨ";s:1:"i";s:2:"ɪ";s:1:"I";s:2:"ɫ";s:1:"l";s:2:"ɬ";s:1:"l";s:2:"ɭ";s:1:"l";s:2:"ɱ";s:1:"m";s:2:"ɲ";s:1:"n";s:2:"ɳ";s:1:"n";s:2:"ɴ";s:1:"N";s:2:"ɶ";s:2:"OE";s:2:"ɼ";s:1:"r";s:2:"ɽ";s:1:"r";s:2:"ɾ";s:1:"r";s:2:"ʀ";s:1:"R";s:2:"ʂ";s:1:"s";s:2:"ʈ";s:1:"t";s:2:"ʉ";s:1:"u";s:2:"ʋ";s:1:"v";s:2:"ʏ";s:1:"Y";s:2:"ʐ";s:1:"z";s:2:"ʑ";s:1:"z";s:2:"ʙ";s:1:"B";s:2:"ʛ";s:1:"G";s:2:"ʜ";s:1:"H";s:2:"ʝ";s:1:"j";s:2:"ʟ";s:1:"L";s:2:"ʠ";s:1:"q";s:2:"ʣ";s:2:"dz";s:2:"ʥ";s:2:"dz";s:2:"ʦ";s:2:"ts";s:2:"ʪ";s:2:"ls";s:2:"ʫ";s:2:"lz";s:3:"ᴀ";s:1:"A";s:3:"ᴁ";s:2:"AE";s:3:"ᴃ";s:1:"B";s:3:"ᴄ";s:1:"C";s:3:"ᴅ";s:1:"D";s:3:"ᴆ";s:1:"D";s:3:"ᴇ";s:1:"E";s:3:"ᴊ";s:1:"J";s:3:"ᴋ";s:1:"K";s:3:"ᴌ";s:1:"L";s:3:"ᴍ";s:1:"M";s:3:"ᴏ";s:1:"O";s:3:"ᴘ";s:1:"P";s:3:"ᴛ";s:1:"T";s:3:"ᴜ";s:1:"U";s:3:"ᴠ";s:1:"V";s:3:"ᴡ";s:1:"W";s:3:"ᴢ";s:1:"Z";s:3:"ᵫ";s:2:"ue";s:3:"ᵬ";s:1:"b";s:3:"ᵭ";s:1:"d";s:3:"ᵮ";s:1:"f";s:3:"ᵯ";s:1:"m";s:3:"ᵰ";s:1:"n";s:3:"ᵱ";s:1:"p";s:3:"ᵲ";s:1:"r";s:3:"ᵳ";s:1:"r";s:3:"ᵴ";s:1:"s";s:3:"ᵵ";s:1:"t";s:3:"ᵶ";s:1:"z";s:3:"ᵺ";s:2:"th";s:3:"ᵻ";s:1:"I";s:3:"ᵽ";s:1:"p";s:3:"ᵾ";s:1:"U";s:3:"ᶀ";s:1:"b";s:3:"ᶁ";s:1:"d";s:3:"ᶂ";s:1:"f";s:3:"ᶃ";s:1:"g";s:3:"ᶄ";s:1:"k";s:3:"ᶅ";s:1:"l";s:3:"ᶆ";s:1:"m";s:3:"ᶇ";s:1:"n";s:3:"ᶈ";s:1:"p";s:3:"ᶉ";s:1:"r";s:3:"ᶊ";s:1:"s";s:3:"ᶌ";s:1:"v";s:3:"ᶍ";s:1:"x";s:3:"ᶎ";s:1:"z";s:3:"ᶏ";s:1:"a";s:3:"ᶑ";s:1:"d";s:3:"ᶒ";s:1:"e";s:3:"ᶓ";s:1:"e";s:3:"ᶖ";s:1:"i";s:3:"ᶙ";s:1:"u";s:3:"ẜ";s:1:"s";s:3:"ẝ";s:1:"s";s:3:"ẞ";s:2:"SS";s:3:"Ỻ";s:2:"LL";s:3:"ỻ";s:2:"ll";s:3:"Ỽ";s:1:"V";s:3:"ỽ";s:1:"v";s:3:"Ỿ";s:1:"Y";s:3:"ỿ";s:1:"y";s:2:"©";s:3:"(C)";s:2:"®";s:3:"(R)";s:3:"₠";s:2:"CE";s:3:"₢";s:2:"Cr";s:3:"₣";s:3:"Fr.";s:3:"₤";s:2:"L.";s:3:"₧";s:3:"Pts";s:3:"₺";s:2:"TL";s:3:"₹";s:2:"Rs";s:3:"℞";s:2:"Rx";s:3:"〇";s:1:"0";s:3:"‘";s:1:"'";s:3:"’";s:1:"'";s:3:"‚";s:1:",";s:3:"‛";s:1:"'";s:3:"“";s:1:""";s:3:"”";s:1:""";s:3:"„";s:2:",,";s:3:"‟";s:1:""";s:3:"′";s:1:"'";s:3:"〝";s:1:""";s:3:"〞";s:1:""";s:2:"«";s:2:"<<";s:2:"»";s:2:">>";s:3:"‹";s:1:"<";s:3:"›";s:1:">";s:3:"‐";s:1:"-";s:3:"‑";s:1:"-";s:3:"‒";s:1:"-";s:3:"–";s:1:"-";s:3:"—";s:1:"-";s:3:"―";s:1:"-";s:3:"︱";s:1:"-";s:3:"︲";s:1:"-";s:3:"‖";s:2:"||";s:3:"⁄";s:1:"/";s:3:"⁅";s:1:"[";s:3:"⁆";s:1:"]";s:3:"⁎";s:1:"*";s:3:"、";s:1:",";s:3:"。";s:1:".";s:3:"〈";s:1:"<";s:3:"〉";s:1:">";s:3:"《";s:2:"<<";s:3:"》";s:2:">>";s:3:"〔";s:1:"[";s:3:"〕";s:1:"]";s:3:"〘";s:1:"[";s:3:"〙";s:1:"]";s:3:"〚";s:1:"[";s:3:"〛";s:1:"]";s:3:"︐";s:1:",";s:3:"︑";s:1:",";s:3:"︒";s:1:".";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:1:"[";s:3:"︺";s:1:"]";s:3:"︽";s:2:"<<";s:3:"︾";s:2:">>";s:3:"︿";s:1:"<";s:3:"﹀";s:1:">";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:2:"×";s:1:"*";s:2:"÷";s:1:"/";s:3:"−";s:1:"-";s:3:"∕";s:1:"/";s:3:"∖";s:1:"\";s:3:"∣";s:1:"|";s:3:"∥";s:2:"||";s:3:"≪";s:2:"<<";s:3:"≫";s:2:">>";s:3:"⦅";s:2:"((";s:3:"⦆";s:2:"))";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/canonicalComposition.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/canonicalComposition.ser new file mode 100644 index 0000000..c548707 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/canonicalComposition.ser @@ -0,0 +1 @@ +a:940:{s:3:"À";s:2:"À";s:3:"Á";s:2:"Á";s:3:"Â";s:2:"Â";s:3:"Ã";s:2:"Ã";s:3:"Ä";s:2:"Ä";s:3:"Å";s:2:"Å";s:3:"Ç";s:2:"Ç";s:3:"È";s:2:"È";s:3:"É";s:2:"É";s:3:"Ê";s:2:"Ê";s:3:"Ë";s:2:"Ë";s:3:"Ì";s:2:"Ì";s:3:"Í";s:2:"Í";s:3:"Î";s:2:"Î";s:3:"Ï";s:2:"Ï";s:3:"Ñ";s:2:"Ñ";s:3:"Ò";s:2:"Ò";s:3:"Ó";s:2:"Ó";s:3:"Ô";s:2:"Ô";s:3:"Õ";s:2:"Õ";s:3:"Ö";s:2:"Ö";s:3:"Ù";s:2:"Ù";s:3:"Ú";s:2:"Ú";s:3:"Û";s:2:"Û";s:3:"Ü";s:2:"Ü";s:3:"Ý";s:2:"Ý";s:3:"à";s:2:"à";s:3:"á";s:2:"á";s:3:"â";s:2:"â";s:3:"ã";s:2:"ã";s:3:"ä";s:2:"ä";s:3:"å";s:2:"å";s:3:"ç";s:2:"ç";s:3:"è";s:2:"è";s:3:"é";s:2:"é";s:3:"ê";s:2:"ê";s:3:"ë";s:2:"ë";s:3:"ì";s:2:"ì";s:3:"í";s:2:"í";s:3:"î";s:2:"î";s:3:"ï";s:2:"ï";s:3:"ñ";s:2:"ñ";s:3:"ò";s:2:"ò";s:3:"ó";s:2:"ó";s:3:"ô";s:2:"ô";s:3:"õ";s:2:"õ";s:3:"ö";s:2:"ö";s:3:"ù";s:2:"ù";s:3:"ú";s:2:"ú";s:3:"û";s:2:"û";s:3:"ü";s:2:"ü";s:3:"ý";s:2:"ý";s:3:"ÿ";s:2:"ÿ";s:3:"Ā";s:2:"Ā";s:3:"ā";s:2:"ā";s:3:"Ă";s:2:"Ă";s:3:"ă";s:2:"ă";s:3:"Ą";s:2:"Ą";s:3:"ą";s:2:"ą";s:3:"Ć";s:2:"Ć";s:3:"ć";s:2:"ć";s:3:"Ĉ";s:2:"Ĉ";s:3:"ĉ";s:2:"ĉ";s:3:"Ċ";s:2:"Ċ";s:3:"ċ";s:2:"ċ";s:3:"Č";s:2:"Č";s:3:"č";s:2:"č";s:3:"Ď";s:2:"Ď";s:3:"ď";s:2:"ď";s:3:"Ē";s:2:"Ē";s:3:"ē";s:2:"ē";s:3:"Ĕ";s:2:"Ĕ";s:3:"ĕ";s:2:"ĕ";s:3:"Ė";s:2:"Ė";s:3:"ė";s:2:"ė";s:3:"Ę";s:2:"Ę";s:3:"ę";s:2:"ę";s:3:"Ě";s:2:"Ě";s:3:"ě";s:2:"ě";s:3:"Ĝ";s:2:"Ĝ";s:3:"ĝ";s:2:"ĝ";s:3:"Ğ";s:2:"Ğ";s:3:"ğ";s:2:"ğ";s:3:"Ġ";s:2:"Ġ";s:3:"ġ";s:2:"ġ";s:3:"Ģ";s:2:"Ģ";s:3:"ģ";s:2:"ģ";s:3:"Ĥ";s:2:"Ĥ";s:3:"ĥ";s:2:"ĥ";s:3:"Ĩ";s:2:"Ĩ";s:3:"ĩ";s:2:"ĩ";s:3:"Ī";s:2:"Ī";s:3:"ī";s:2:"ī";s:3:"Ĭ";s:2:"Ĭ";s:3:"ĭ";s:2:"ĭ";s:3:"Į";s:2:"Į";s:3:"į";s:2:"į";s:3:"İ";s:2:"İ";s:3:"Ĵ";s:2:"Ĵ";s:3:"ĵ";s:2:"ĵ";s:3:"Ķ";s:2:"Ķ";s:3:"ķ";s:2:"ķ";s:3:"Ĺ";s:2:"Ĺ";s:3:"ĺ";s:2:"ĺ";s:3:"Ļ";s:2:"Ļ";s:3:"ļ";s:2:"ļ";s:3:"Ľ";s:2:"Ľ";s:3:"ľ";s:2:"ľ";s:3:"Ń";s:2:"Ń";s:3:"ń";s:2:"ń";s:3:"Ņ";s:2:"Ņ";s:3:"ņ";s:2:"ņ";s:3:"Ň";s:2:"Ň";s:3:"ň";s:2:"ň";s:3:"Ō";s:2:"Ō";s:3:"ō";s:2:"ō";s:3:"Ŏ";s:2:"Ŏ";s:3:"ŏ";s:2:"ŏ";s:3:"Ő";s:2:"Ő";s:3:"ő";s:2:"ő";s:3:"Ŕ";s:2:"Ŕ";s:3:"ŕ";s:2:"ŕ";s:3:"Ŗ";s:2:"Ŗ";s:3:"ŗ";s:2:"ŗ";s:3:"Ř";s:2:"Ř";s:3:"ř";s:2:"ř";s:3:"Ś";s:2:"Ś";s:3:"ś";s:2:"ś";s:3:"Ŝ";s:2:"Ŝ";s:3:"ŝ";s:2:"ŝ";s:3:"Ş";s:2:"Ş";s:3:"ş";s:2:"ş";s:3:"Š";s:2:"Š";s:3:"š";s:2:"š";s:3:"Ţ";s:2:"Ţ";s:3:"ţ";s:2:"ţ";s:3:"Ť";s:2:"Ť";s:3:"ť";s:2:"ť";s:3:"Ũ";s:2:"Ũ";s:3:"ũ";s:2:"ũ";s:3:"Ū";s:2:"Ū";s:3:"ū";s:2:"ū";s:3:"Ŭ";s:2:"Ŭ";s:3:"ŭ";s:2:"ŭ";s:3:"Ů";s:2:"Ů";s:3:"ů";s:2:"ů";s:3:"Ű";s:2:"Ű";s:3:"ű";s:2:"ű";s:3:"Ų";s:2:"Ų";s:3:"ų";s:2:"ų";s:3:"Ŵ";s:2:"Ŵ";s:3:"ŵ";s:2:"ŵ";s:3:"Ŷ";s:2:"Ŷ";s:3:"ŷ";s:2:"ŷ";s:3:"Ÿ";s:2:"Ÿ";s:3:"Ź";s:2:"Ź";s:3:"ź";s:2:"ź";s:3:"Ż";s:2:"Ż";s:3:"ż";s:2:"ż";s:3:"Ž";s:2:"Ž";s:3:"ž";s:2:"ž";s:3:"Ơ";s:2:"Ơ";s:3:"ơ";s:2:"ơ";s:3:"Ư";s:2:"Ư";s:3:"ư";s:2:"ư";s:3:"Ǎ";s:2:"Ǎ";s:3:"ǎ";s:2:"ǎ";s:3:"Ǐ";s:2:"Ǐ";s:3:"ǐ";s:2:"ǐ";s:3:"Ǒ";s:2:"Ǒ";s:3:"ǒ";s:2:"ǒ";s:3:"Ǔ";s:2:"Ǔ";s:3:"ǔ";s:2:"ǔ";s:4:"Ǖ";s:2:"Ǖ";s:4:"ǖ";s:2:"ǖ";s:4:"Ǘ";s:2:"Ǘ";s:4:"ǘ";s:2:"ǘ";s:4:"Ǚ";s:2:"Ǚ";s:4:"ǚ";s:2:"ǚ";s:4:"Ǜ";s:2:"Ǜ";s:4:"ǜ";s:2:"ǜ";s:4:"Ǟ";s:2:"Ǟ";s:4:"ǟ";s:2:"ǟ";s:4:"Ǡ";s:2:"Ǡ";s:4:"ǡ";s:2:"ǡ";s:4:"Ǣ";s:2:"Ǣ";s:4:"ǣ";s:2:"ǣ";s:3:"Ǧ";s:2:"Ǧ";s:3:"ǧ";s:2:"ǧ";s:3:"Ǩ";s:2:"Ǩ";s:3:"ǩ";s:2:"ǩ";s:3:"Ǫ";s:2:"Ǫ";s:3:"ǫ";s:2:"ǫ";s:4:"Ǭ";s:2:"Ǭ";s:4:"ǭ";s:2:"ǭ";s:4:"Ǯ";s:2:"Ǯ";s:4:"ǯ";s:2:"ǯ";s:3:"ǰ";s:2:"ǰ";s:3:"Ǵ";s:2:"Ǵ";s:3:"ǵ";s:2:"ǵ";s:3:"Ǹ";s:2:"Ǹ";s:3:"ǹ";s:2:"ǹ";s:4:"Ǻ";s:2:"Ǻ";s:4:"ǻ";s:2:"ǻ";s:4:"Ǽ";s:2:"Ǽ";s:4:"ǽ";s:2:"ǽ";s:4:"Ǿ";s:2:"Ǿ";s:4:"ǿ";s:2:"ǿ";s:3:"Ȁ";s:2:"Ȁ";s:3:"ȁ";s:2:"ȁ";s:3:"Ȃ";s:2:"Ȃ";s:3:"ȃ";s:2:"ȃ";s:3:"Ȅ";s:2:"Ȅ";s:3:"ȅ";s:2:"ȅ";s:3:"Ȇ";s:2:"Ȇ";s:3:"ȇ";s:2:"ȇ";s:3:"Ȉ";s:2:"Ȉ";s:3:"ȉ";s:2:"ȉ";s:3:"Ȋ";s:2:"Ȋ";s:3:"ȋ";s:2:"ȋ";s:3:"Ȍ";s:2:"Ȍ";s:3:"ȍ";s:2:"ȍ";s:3:"Ȏ";s:2:"Ȏ";s:3:"ȏ";s:2:"ȏ";s:3:"Ȑ";s:2:"Ȑ";s:3:"ȑ";s:2:"ȑ";s:3:"Ȓ";s:2:"Ȓ";s:3:"ȓ";s:2:"ȓ";s:3:"Ȕ";s:2:"Ȕ";s:3:"ȕ";s:2:"ȕ";s:3:"Ȗ";s:2:"Ȗ";s:3:"ȗ";s:2:"ȗ";s:3:"Ș";s:2:"Ș";s:3:"ș";s:2:"ș";s:3:"Ț";s:2:"Ț";s:3:"ț";s:2:"ț";s:3:"Ȟ";s:2:"Ȟ";s:3:"ȟ";s:2:"ȟ";s:3:"Ȧ";s:2:"Ȧ";s:3:"ȧ";s:2:"ȧ";s:3:"Ȩ";s:2:"Ȩ";s:3:"ȩ";s:2:"ȩ";s:4:"Ȫ";s:2:"Ȫ";s:4:"ȫ";s:2:"ȫ";s:4:"Ȭ";s:2:"Ȭ";s:4:"ȭ";s:2:"ȭ";s:3:"Ȯ";s:2:"Ȯ";s:3:"ȯ";s:2:"ȯ";s:4:"Ȱ";s:2:"Ȱ";s:4:"ȱ";s:2:"ȱ";s:3:"Ȳ";s:2:"Ȳ";s:3:"ȳ";s:2:"ȳ";s:4:"΅";s:2:"΅";s:4:"Ά";s:2:"Ά";s:4:"Έ";s:2:"Έ";s:4:"Ή";s:2:"Ή";s:4:"Ί";s:2:"Ί";s:4:"Ό";s:2:"Ό";s:4:"Ύ";s:2:"Ύ";s:4:"Ώ";s:2:"Ώ";s:4:"ΐ";s:2:"ΐ";s:4:"Ϊ";s:2:"Ϊ";s:4:"Ϋ";s:2:"Ϋ";s:4:"ά";s:2:"ά";s:4:"έ";s:2:"έ";s:4:"ή";s:2:"ή";s:4:"ί";s:2:"ί";s:4:"ΰ";s:2:"ΰ";s:4:"ϊ";s:2:"ϊ";s:4:"ϋ";s:2:"ϋ";s:4:"ό";s:2:"ό";s:4:"ύ";s:2:"ύ";s:4:"ώ";s:2:"ώ";s:4:"ϓ";s:2:"ϓ";s:4:"ϔ";s:2:"ϔ";s:4:"Ѐ";s:2:"Ѐ";s:4:"Ё";s:2:"Ё";s:4:"Ѓ";s:2:"Ѓ";s:4:"Ї";s:2:"Ї";s:4:"Ќ";s:2:"Ќ";s:4:"Ѝ";s:2:"Ѝ";s:4:"Ў";s:2:"Ў";s:4:"Й";s:2:"Й";s:4:"й";s:2:"й";s:4:"ѐ";s:2:"ѐ";s:4:"ё";s:2:"ё";s:4:"ѓ";s:2:"ѓ";s:4:"ї";s:2:"ї";s:4:"ќ";s:2:"ќ";s:4:"ѝ";s:2:"ѝ";s:4:"ў";s:2:"ў";s:4:"Ѷ";s:2:"Ѷ";s:4:"ѷ";s:2:"ѷ";s:4:"Ӂ";s:2:"Ӂ";s:4:"ӂ";s:2:"ӂ";s:4:"Ӑ";s:2:"Ӑ";s:4:"ӑ";s:2:"ӑ";s:4:"Ӓ";s:2:"Ӓ";s:4:"ӓ";s:2:"ӓ";s:4:"Ӗ";s:2:"Ӗ";s:4:"ӗ";s:2:"ӗ";s:4:"Ӛ";s:2:"Ӛ";s:4:"ӛ";s:2:"ӛ";s:4:"Ӝ";s:2:"Ӝ";s:4:"ӝ";s:2:"ӝ";s:4:"Ӟ";s:2:"Ӟ";s:4:"ӟ";s:2:"ӟ";s:4:"Ӣ";s:2:"Ӣ";s:4:"ӣ";s:2:"ӣ";s:4:"Ӥ";s:2:"Ӥ";s:4:"ӥ";s:2:"ӥ";s:4:"Ӧ";s:2:"Ӧ";s:4:"ӧ";s:2:"ӧ";s:4:"Ӫ";s:2:"Ӫ";s:4:"ӫ";s:2:"ӫ";s:4:"Ӭ";s:2:"Ӭ";s:4:"ӭ";s:2:"ӭ";s:4:"Ӯ";s:2:"Ӯ";s:4:"ӯ";s:2:"ӯ";s:4:"Ӱ";s:2:"Ӱ";s:4:"ӱ";s:2:"ӱ";s:4:"Ӳ";s:2:"Ӳ";s:4:"ӳ";s:2:"ӳ";s:4:"Ӵ";s:2:"Ӵ";s:4:"ӵ";s:2:"ӵ";s:4:"Ӹ";s:2:"Ӹ";s:4:"ӹ";s:2:"ӹ";s:4:"آ";s:2:"آ";s:4:"أ";s:2:"أ";s:4:"ؤ";s:2:"ؤ";s:4:"إ";s:2:"إ";s:4:"ئ";s:2:"ئ";s:4:"ۀ";s:2:"ۀ";s:4:"ۂ";s:2:"ۂ";s:4:"ۓ";s:2:"ۓ";s:6:"ऩ";s:3:"ऩ";s:6:"ऱ";s:3:"ऱ";s:6:"ऴ";s:3:"ऴ";s:6:"ো";s:3:"ো";s:6:"ৌ";s:3:"ৌ";s:6:"ୈ";s:3:"ୈ";s:6:"ୋ";s:3:"ୋ";s:6:"ୌ";s:3:"ୌ";s:6:"ஔ";s:3:"ஔ";s:6:"ொ";s:3:"ொ";s:6:"ோ";s:3:"ோ";s:6:"ௌ";s:3:"ௌ";s:6:"ై";s:3:"ై";s:6:"ೀ";s:3:"ೀ";s:6:"ೇ";s:3:"ೇ";s:6:"ೈ";s:3:"ೈ";s:6:"ೊ";s:3:"ೊ";s:6:"ೋ";s:3:"ೋ";s:6:"ൊ";s:3:"ൊ";s:6:"ോ";s:3:"ോ";s:6:"ൌ";s:3:"ൌ";s:6:"ේ";s:3:"ේ";s:6:"ො";s:3:"ො";s:6:"ෝ";s:3:"ෝ";s:6:"ෞ";s:3:"ෞ";s:6:"ဦ";s:3:"ဦ";s:6:"ᬆ";s:3:"ᬆ";s:6:"ᬈ";s:3:"ᬈ";s:6:"ᬊ";s:3:"ᬊ";s:6:"ᬌ";s:3:"ᬌ";s:6:"ᬎ";s:3:"ᬎ";s:6:"ᬒ";s:3:"ᬒ";s:6:"ᬻ";s:3:"ᬻ";s:6:"ᬽ";s:3:"ᬽ";s:6:"ᭀ";s:3:"ᭀ";s:6:"ᭁ";s:3:"ᭁ";s:6:"ᭃ";s:3:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:4:"Ḉ";s:3:"Ḉ";s:4:"ḉ";s:3:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:4:"Ḕ";s:3:"Ḕ";s:4:"ḕ";s:3:"ḕ";s:4:"Ḗ";s:3:"Ḗ";s:4:"ḗ";s:3:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:4:"Ḝ";s:3:"Ḝ";s:4:"ḝ";s:3:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:4:"Ḯ";s:3:"Ḯ";s:4:"ḯ";s:3:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:5:"Ḹ";s:3:"Ḹ";s:5:"ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:4:"Ṍ";s:3:"Ṍ";s:4:"ṍ";s:3:"ṍ";s:4:"Ṏ";s:3:"Ṏ";s:4:"ṏ";s:3:"ṏ";s:4:"Ṑ";s:3:"Ṑ";s:4:"ṑ";s:3:"ṑ";s:4:"Ṓ";s:3:"Ṓ";s:4:"ṓ";s:3:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:5:"Ṝ";s:3:"Ṝ";s:5:"ṝ";s:3:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:4:"Ṥ";s:3:"Ṥ";s:4:"ṥ";s:3:"ṥ";s:4:"Ṧ";s:3:"Ṧ";s:4:"ṧ";s:3:"ṧ";s:5:"Ṩ";s:3:"Ṩ";s:5:"ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:4:"Ṹ";s:3:"Ṹ";s:4:"ṹ";s:3:"ṹ";s:4:"Ṻ";s:3:"Ṻ";s:4:"ṻ";s:3:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:4:"ẛ";s:3:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:4:"Ấ";s:3:"Ấ";s:4:"ấ";s:3:"ấ";s:4:"Ầ";s:3:"Ầ";s:4:"ầ";s:3:"ầ";s:4:"Ẩ";s:3:"Ẩ";s:4:"ẩ";s:3:"ẩ";s:4:"Ẫ";s:3:"Ẫ";s:4:"ẫ";s:3:"ẫ";s:5:"Ậ";s:3:"Ậ";s:5:"ậ";s:3:"ậ";s:4:"Ắ";s:3:"Ắ";s:4:"ắ";s:3:"ắ";s:4:"Ằ";s:3:"Ằ";s:4:"ằ";s:3:"ằ";s:4:"Ẳ";s:3:"Ẳ";s:4:"ẳ";s:3:"ẳ";s:4:"Ẵ";s:3:"Ẵ";s:4:"ẵ";s:3:"ẵ";s:5:"Ặ";s:3:"Ặ";s:5:"ặ";s:3:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:4:"Ế";s:3:"Ế";s:4:"ế";s:3:"ế";s:4:"Ề";s:3:"Ề";s:4:"ề";s:3:"ề";s:4:"Ể";s:3:"Ể";s:4:"ể";s:3:"ể";s:4:"Ễ";s:3:"Ễ";s:4:"ễ";s:3:"ễ";s:5:"Ệ";s:3:"Ệ";s:5:"ệ";s:3:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:4:"Ố";s:3:"Ố";s:4:"ố";s:3:"ố";s:4:"Ồ";s:3:"Ồ";s:4:"ồ";s:3:"ồ";s:4:"Ổ";s:3:"Ổ";s:4:"ổ";s:3:"ổ";s:4:"Ỗ";s:3:"Ỗ";s:4:"ỗ";s:3:"ỗ";s:5:"Ộ";s:3:"Ộ";s:5:"ộ";s:3:"ộ";s:4:"Ớ";s:3:"Ớ";s:4:"ớ";s:3:"ớ";s:4:"Ờ";s:3:"Ờ";s:4:"ờ";s:3:"ờ";s:4:"Ở";s:3:"Ở";s:4:"ở";s:3:"ở";s:4:"Ỡ";s:3:"Ỡ";s:4:"ỡ";s:3:"ỡ";s:4:"Ợ";s:3:"Ợ";s:4:"ợ";s:3:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:4:"Ứ";s:3:"Ứ";s:4:"ứ";s:3:"ứ";s:4:"Ừ";s:3:"Ừ";s:4:"ừ";s:3:"ừ";s:4:"Ử";s:3:"Ử";s:4:"ử";s:3:"ử";s:4:"Ữ";s:3:"Ữ";s:4:"ữ";s:3:"ữ";s:4:"Ự";s:3:"Ự";s:4:"ự";s:3:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:4:"ἀ";s:3:"ἀ";s:4:"ἁ";s:3:"ἁ";s:5:"ἂ";s:3:"ἂ";s:5:"ἃ";s:3:"ἃ";s:5:"ἄ";s:3:"ἄ";s:5:"ἅ";s:3:"ἅ";s:5:"ἆ";s:3:"ἆ";s:5:"ἇ";s:3:"ἇ";s:4:"Ἀ";s:3:"Ἀ";s:4:"Ἁ";s:3:"Ἁ";s:5:"Ἂ";s:3:"Ἂ";s:5:"Ἃ";s:3:"Ἃ";s:5:"Ἄ";s:3:"Ἄ";s:5:"Ἅ";s:3:"Ἅ";s:5:"Ἆ";s:3:"Ἆ";s:5:"Ἇ";s:3:"Ἇ";s:4:"ἐ";s:3:"ἐ";s:4:"ἑ";s:3:"ἑ";s:5:"ἒ";s:3:"ἒ";s:5:"ἓ";s:3:"ἓ";s:5:"ἔ";s:3:"ἔ";s:5:"ἕ";s:3:"ἕ";s:4:"Ἐ";s:3:"Ἐ";s:4:"Ἑ";s:3:"Ἑ";s:5:"Ἒ";s:3:"Ἒ";s:5:"Ἓ";s:3:"Ἓ";s:5:"Ἔ";s:3:"Ἔ";s:5:"Ἕ";s:3:"Ἕ";s:4:"ἠ";s:3:"ἠ";s:4:"ἡ";s:3:"ἡ";s:5:"ἢ";s:3:"ἢ";s:5:"ἣ";s:3:"ἣ";s:5:"ἤ";s:3:"ἤ";s:5:"ἥ";s:3:"ἥ";s:5:"ἦ";s:3:"ἦ";s:5:"ἧ";s:3:"ἧ";s:4:"Ἠ";s:3:"Ἠ";s:4:"Ἡ";s:3:"Ἡ";s:5:"Ἢ";s:3:"Ἢ";s:5:"Ἣ";s:3:"Ἣ";s:5:"Ἤ";s:3:"Ἤ";s:5:"Ἥ";s:3:"Ἥ";s:5:"Ἦ";s:3:"Ἦ";s:5:"Ἧ";s:3:"Ἧ";s:4:"ἰ";s:3:"ἰ";s:4:"ἱ";s:3:"ἱ";s:5:"ἲ";s:3:"ἲ";s:5:"ἳ";s:3:"ἳ";s:5:"ἴ";s:3:"ἴ";s:5:"ἵ";s:3:"ἵ";s:5:"ἶ";s:3:"ἶ";s:5:"ἷ";s:3:"ἷ";s:4:"Ἰ";s:3:"Ἰ";s:4:"Ἱ";s:3:"Ἱ";s:5:"Ἲ";s:3:"Ἲ";s:5:"Ἳ";s:3:"Ἳ";s:5:"Ἴ";s:3:"Ἴ";s:5:"Ἵ";s:3:"Ἵ";s:5:"Ἶ";s:3:"Ἶ";s:5:"Ἷ";s:3:"Ἷ";s:4:"ὀ";s:3:"ὀ";s:4:"ὁ";s:3:"ὁ";s:5:"ὂ";s:3:"ὂ";s:5:"ὃ";s:3:"ὃ";s:5:"ὄ";s:3:"ὄ";s:5:"ὅ";s:3:"ὅ";s:4:"Ὀ";s:3:"Ὀ";s:4:"Ὁ";s:3:"Ὁ";s:5:"Ὂ";s:3:"Ὂ";s:5:"Ὃ";s:3:"Ὃ";s:5:"Ὄ";s:3:"Ὄ";s:5:"Ὅ";s:3:"Ὅ";s:4:"ὐ";s:3:"ὐ";s:4:"ὑ";s:3:"ὑ";s:5:"ὒ";s:3:"ὒ";s:5:"ὓ";s:3:"ὓ";s:5:"ὔ";s:3:"ὔ";s:5:"ὕ";s:3:"ὕ";s:5:"ὖ";s:3:"ὖ";s:5:"ὗ";s:3:"ὗ";s:4:"Ὑ";s:3:"Ὑ";s:5:"Ὓ";s:3:"Ὓ";s:5:"Ὕ";s:3:"Ὕ";s:5:"Ὗ";s:3:"Ὗ";s:4:"ὠ";s:3:"ὠ";s:4:"ὡ";s:3:"ὡ";s:5:"ὢ";s:3:"ὢ";s:5:"ὣ";s:3:"ὣ";s:5:"ὤ";s:3:"ὤ";s:5:"ὥ";s:3:"ὥ";s:5:"ὦ";s:3:"ὦ";s:5:"ὧ";s:3:"ὧ";s:4:"Ὠ";s:3:"Ὠ";s:4:"Ὡ";s:3:"Ὡ";s:5:"Ὢ";s:3:"Ὢ";s:5:"Ὣ";s:3:"Ὣ";s:5:"Ὤ";s:3:"Ὤ";s:5:"Ὥ";s:3:"Ὥ";s:5:"Ὦ";s:3:"Ὦ";s:5:"Ὧ";s:3:"Ὧ";s:4:"ὰ";s:3:"ὰ";s:4:"ὲ";s:3:"ὲ";s:4:"ὴ";s:3:"ὴ";s:4:"ὶ";s:3:"ὶ";s:4:"ὸ";s:3:"ὸ";s:4:"ὺ";s:3:"ὺ";s:4:"ὼ";s:3:"ὼ";s:5:"ᾀ";s:3:"ᾀ";s:5:"ᾁ";s:3:"ᾁ";s:5:"ᾂ";s:3:"ᾂ";s:5:"ᾃ";s:3:"ᾃ";s:5:"ᾄ";s:3:"ᾄ";s:5:"ᾅ";s:3:"ᾅ";s:5:"ᾆ";s:3:"ᾆ";s:5:"ᾇ";s:3:"ᾇ";s:5:"ᾈ";s:3:"ᾈ";s:5:"ᾉ";s:3:"ᾉ";s:5:"ᾊ";s:3:"ᾊ";s:5:"ᾋ";s:3:"ᾋ";s:5:"ᾌ";s:3:"ᾌ";s:5:"ᾍ";s:3:"ᾍ";s:5:"ᾎ";s:3:"ᾎ";s:5:"ᾏ";s:3:"ᾏ";s:5:"ᾐ";s:3:"ᾐ";s:5:"ᾑ";s:3:"ᾑ";s:5:"ᾒ";s:3:"ᾒ";s:5:"ᾓ";s:3:"ᾓ";s:5:"ᾔ";s:3:"ᾔ";s:5:"ᾕ";s:3:"ᾕ";s:5:"ᾖ";s:3:"ᾖ";s:5:"ᾗ";s:3:"ᾗ";s:5:"ᾘ";s:3:"ᾘ";s:5:"ᾙ";s:3:"ᾙ";s:5:"ᾚ";s:3:"ᾚ";s:5:"ᾛ";s:3:"ᾛ";s:5:"ᾜ";s:3:"ᾜ";s:5:"ᾝ";s:3:"ᾝ";s:5:"ᾞ";s:3:"ᾞ";s:5:"ᾟ";s:3:"ᾟ";s:5:"ᾠ";s:3:"ᾠ";s:5:"ᾡ";s:3:"ᾡ";s:5:"ᾢ";s:3:"ᾢ";s:5:"ᾣ";s:3:"ᾣ";s:5:"ᾤ";s:3:"ᾤ";s:5:"ᾥ";s:3:"ᾥ";s:5:"ᾦ";s:3:"ᾦ";s:5:"ᾧ";s:3:"ᾧ";s:5:"ᾨ";s:3:"ᾨ";s:5:"ᾩ";s:3:"ᾩ";s:5:"ᾪ";s:3:"ᾪ";s:5:"ᾫ";s:3:"ᾫ";s:5:"ᾬ";s:3:"ᾬ";s:5:"ᾭ";s:3:"ᾭ";s:5:"ᾮ";s:3:"ᾮ";s:5:"ᾯ";s:3:"ᾯ";s:4:"ᾰ";s:3:"ᾰ";s:4:"ᾱ";s:3:"ᾱ";s:5:"ᾲ";s:3:"ᾲ";s:4:"ᾳ";s:3:"ᾳ";s:4:"ᾴ";s:3:"ᾴ";s:4:"ᾶ";s:3:"ᾶ";s:5:"ᾷ";s:3:"ᾷ";s:4:"Ᾰ";s:3:"Ᾰ";s:4:"Ᾱ";s:3:"Ᾱ";s:4:"Ὰ";s:3:"Ὰ";s:4:"ᾼ";s:3:"ᾼ";s:4:"῁";s:3:"῁";s:5:"ῂ";s:3:"ῂ";s:4:"ῃ";s:3:"ῃ";s:4:"ῄ";s:3:"ῄ";s:4:"ῆ";s:3:"ῆ";s:5:"ῇ";s:3:"ῇ";s:4:"Ὲ";s:3:"Ὲ";s:4:"Ὴ";s:3:"Ὴ";s:4:"ῌ";s:3:"ῌ";s:5:"῍";s:3:"῍";s:5:"῎";s:3:"῎";s:5:"῏";s:3:"῏";s:4:"ῐ";s:3:"ῐ";s:4:"ῑ";s:3:"ῑ";s:4:"ῒ";s:3:"ῒ";s:4:"ῖ";s:3:"ῖ";s:4:"ῗ";s:3:"ῗ";s:4:"Ῐ";s:3:"Ῐ";s:4:"Ῑ";s:3:"Ῑ";s:4:"Ὶ";s:3:"Ὶ";s:5:"῝";s:3:"῝";s:5:"῞";s:3:"῞";s:5:"῟";s:3:"῟";s:4:"ῠ";s:3:"ῠ";s:4:"ῡ";s:3:"ῡ";s:4:"ῢ";s:3:"ῢ";s:4:"ῤ";s:3:"ῤ";s:4:"ῥ";s:3:"ῥ";s:4:"ῦ";s:3:"ῦ";s:4:"ῧ";s:3:"ῧ";s:4:"Ῠ";s:3:"Ῠ";s:4:"Ῡ";s:3:"Ῡ";s:4:"Ὺ";s:3:"Ὺ";s:4:"Ῥ";s:3:"Ῥ";s:4:"῭";s:3:"῭";s:5:"ῲ";s:3:"ῲ";s:4:"ῳ";s:3:"ῳ";s:4:"ῴ";s:3:"ῴ";s:4:"ῶ";s:3:"ῶ";s:5:"ῷ";s:3:"ῷ";s:4:"Ὸ";s:3:"Ὸ";s:4:"Ὼ";s:3:"Ὼ";s:4:"ῼ";s:3:"ῼ";s:5:"↚";s:3:"↚";s:5:"↛";s:3:"↛";s:5:"↮";s:3:"↮";s:5:"⇍";s:3:"⇍";s:5:"⇎";s:3:"⇎";s:5:"⇏";s:3:"⇏";s:5:"∄";s:3:"∄";s:5:"∉";s:3:"∉";s:5:"∌";s:3:"∌";s:5:"∤";s:3:"∤";s:5:"∦";s:3:"∦";s:5:"≁";s:3:"≁";s:5:"≄";s:3:"≄";s:5:"≇";s:3:"≇";s:5:"≉";s:3:"≉";s:3:"≠";s:3:"≠";s:5:"≢";s:3:"≢";s:5:"≭";s:3:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:5:"≰";s:3:"≰";s:5:"≱";s:3:"≱";s:5:"≴";s:3:"≴";s:5:"≵";s:3:"≵";s:5:"≸";s:3:"≸";s:5:"≹";s:3:"≹";s:5:"⊀";s:3:"⊀";s:5:"⊁";s:3:"⊁";s:5:"⊄";s:3:"⊄";s:5:"⊅";s:3:"⊅";s:5:"⊈";s:3:"⊈";s:5:"⊉";s:3:"⊉";s:5:"⊬";s:3:"⊬";s:5:"⊭";s:3:"⊭";s:5:"⊮";s:3:"⊮";s:5:"⊯";s:3:"⊯";s:5:"⋠";s:3:"⋠";s:5:"⋡";s:3:"⋡";s:5:"⋢";s:3:"⋢";s:5:"⋣";s:3:"⋣";s:5:"⋪";s:3:"⋪";s:5:"⋫";s:3:"⋫";s:5:"⋬";s:3:"⋬";s:5:"⋭";s:3:"⋭";s:6:"が";s:3:"が";s:6:"ぎ";s:3:"ぎ";s:6:"ぐ";s:3:"ぐ";s:6:"げ";s:3:"げ";s:6:"ご";s:3:"ご";s:6:"ざ";s:3:"ざ";s:6:"じ";s:3:"じ";s:6:"ず";s:3:"ず";s:6:"ぜ";s:3:"ぜ";s:6:"ぞ";s:3:"ぞ";s:6:"だ";s:3:"だ";s:6:"ぢ";s:3:"ぢ";s:6:"づ";s:3:"づ";s:6:"で";s:3:"で";s:6:"ど";s:3:"ど";s:6:"ば";s:3:"ば";s:6:"ぱ";s:3:"ぱ";s:6:"び";s:3:"び";s:6:"ぴ";s:3:"ぴ";s:6:"ぶ";s:3:"ぶ";s:6:"ぷ";s:3:"ぷ";s:6:"べ";s:3:"べ";s:6:"ぺ";s:3:"ぺ";s:6:"ぼ";s:3:"ぼ";s:6:"ぽ";s:3:"ぽ";s:6:"ゔ";s:3:"ゔ";s:6:"ゞ";s:3:"ゞ";s:6:"ガ";s:3:"ガ";s:6:"ギ";s:3:"ギ";s:6:"グ";s:3:"グ";s:6:"ゲ";s:3:"ゲ";s:6:"ゴ";s:3:"ゴ";s:6:"ザ";s:3:"ザ";s:6:"ジ";s:3:"ジ";s:6:"ズ";s:3:"ズ";s:6:"ゼ";s:3:"ゼ";s:6:"ゾ";s:3:"ゾ";s:6:"ダ";s:3:"ダ";s:6:"ヂ";s:3:"ヂ";s:6:"ヅ";s:3:"ヅ";s:6:"デ";s:3:"デ";s:6:"ド";s:3:"ド";s:6:"バ";s:3:"バ";s:6:"パ";s:3:"パ";s:6:"ビ";s:3:"ビ";s:6:"ピ";s:3:"ピ";s:6:"ブ";s:3:"ブ";s:6:"プ";s:3:"プ";s:6:"ベ";s:3:"ベ";s:6:"ペ";s:3:"ペ";s:6:"ボ";s:3:"ボ";s:6:"ポ";s:3:"ポ";s:6:"ヴ";s:3:"ヴ";s:6:"ヷ";s:3:"ヷ";s:6:"ヸ";s:3:"ヸ";s:6:"ヹ";s:3:"ヹ";s:6:"ヺ";s:3:"ヺ";s:6:"ヾ";s:3:"ヾ";s:8:"𑂚";s:4:"𑂚";s:8:"𑂜";s:4:"𑂜";s:8:"𑂫";s:4:"𑂫";s:8:"𑄮";s:4:"𑄮";s:8:"𑄯";s:4:"𑄯";s:8:"𑍋";s:4:"𑍋";s:8:"𑍌";s:4:"𑍌";s:8:"𑒻";s:4:"𑒻";s:8:"𑒼";s:4:"𑒼";s:8:"𑒾";s:4:"𑒾";s:8:"𑖺";s:4:"𑖺";s:8:"𑖻";s:4:"𑖻";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/canonicalDecomposition.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/canonicalDecomposition.ser new file mode 100644 index 0000000..7adfb96 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/canonicalDecomposition.ser @@ -0,0 +1 @@ +a:2060:{s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:";";s:1:";";s:2:"΅";s:4:"΅";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϓ";s:4:"ϓ";s:2:"ϔ";s:4:"ϔ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẛ";s:4:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"ι";s:2:"ι";s:3:"῁";s:4:"῁";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:"῍";s:3:"῎";s:5:"῎";s:3:"῏";s:5:"῏";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:"῝";s:3:"῞";s:5:"῞";s:3:"῟";s:5:"῟";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:4:"῭";s:3:"΅";s:4:"΅";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:2:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:3:"Ω";s:2:"Ω";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"⫝̸";s:5:"⫝̸";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"ゞ";s:6:"ゞ";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"郞";s:3:"郞";s:3:"隷";s:3:"隷";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:3:"𤋮";s:4:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:4:"𑂚";s:8:"𑂚";s:4:"𑂜";s:8:"𑂜";s:4:"𑂫";s:8:"𑂫";s:4:"𑄮";s:8:"𑄮";s:4:"𑄯";s:8:"𑄯";s:4:"𑍋";s:8:"𑍋";s:4:"𑍌";s:8:"𑍌";s:4:"𑒻";s:8:"𑒻";s:4:"𑒼";s:8:"𑒼";s:4:"𑒾";s:8:"𑒾";s:4:"𑖺";s:8:"𑖺";s:4:"𑖻";s:8:"𑖻";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/combiningClass.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/combiningClass.ser new file mode 100644 index 0000000..9a52768 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/combiningClass.ser @@ -0,0 +1 @@ +a:745:{s:2:"̀";i:230;s:2:"́";i:230;s:2:"̂";i:230;s:2:"̃";i:230;s:2:"̄";i:230;s:2:"̅";i:230;s:2:"̆";i:230;s:2:"̇";i:230;s:2:"̈";i:230;s:2:"̉";i:230;s:2:"̊";i:230;s:2:"̋";i:230;s:2:"̌";i:230;s:2:"̍";i:230;s:2:"̎";i:230;s:2:"̏";i:230;s:2:"̐";i:230;s:2:"̑";i:230;s:2:"̒";i:230;s:2:"̓";i:230;s:2:"̔";i:230;s:2:"̕";i:232;s:2:"̖";i:220;s:2:"̗";i:220;s:2:"̘";i:220;s:2:"̙";i:220;s:2:"̚";i:232;s:2:"̛";i:216;s:2:"̜";i:220;s:2:"̝";i:220;s:2:"̞";i:220;s:2:"̟";i:220;s:2:"̠";i:220;s:2:"̡";i:202;s:2:"̢";i:202;s:2:"̣";i:220;s:2:"̤";i:220;s:2:"̥";i:220;s:2:"̦";i:220;s:2:"̧";i:202;s:2:"̨";i:202;s:2:"̩";i:220;s:2:"̪";i:220;s:2:"̫";i:220;s:2:"̬";i:220;s:2:"̭";i:220;s:2:"̮";i:220;s:2:"̯";i:220;s:2:"̰";i:220;s:2:"̱";i:220;s:2:"̲";i:220;s:2:"̳";i:220;s:2:"̴";i:1;s:2:"̵";i:1;s:2:"̶";i:1;s:2:"̷";i:1;s:2:"̸";i:1;s:2:"̹";i:220;s:2:"̺";i:220;s:2:"̻";i:220;s:2:"̼";i:220;s:2:"̽";i:230;s:2:"̾";i:230;s:2:"̿";i:230;s:2:"̀";i:230;s:2:"́";i:230;s:2:"͂";i:230;s:2:"̓";i:230;s:2:"̈́";i:230;s:2:"ͅ";i:240;s:2:"͆";i:230;s:2:"͇";i:220;s:2:"͈";i:220;s:2:"͉";i:220;s:2:"͊";i:230;s:2:"͋";i:230;s:2:"͌";i:230;s:2:"͍";i:220;s:2:"͎";i:220;s:2:"͐";i:230;s:2:"͑";i:230;s:2:"͒";i:230;s:2:"͓";i:220;s:2:"͔";i:220;s:2:"͕";i:220;s:2:"͖";i:220;s:2:"͗";i:230;s:2:"͘";i:232;s:2:"͙";i:220;s:2:"͚";i:220;s:2:"͛";i:230;s:2:"͜";i:233;s:2:"͝";i:234;s:2:"͞";i:234;s:2:"͟";i:233;s:2:"͠";i:234;s:2:"͡";i:234;s:2:"͢";i:233;s:2:"ͣ";i:230;s:2:"ͤ";i:230;s:2:"ͥ";i:230;s:2:"ͦ";i:230;s:2:"ͧ";i:230;s:2:"ͨ";i:230;s:2:"ͩ";i:230;s:2:"ͪ";i:230;s:2:"ͫ";i:230;s:2:"ͬ";i:230;s:2:"ͭ";i:230;s:2:"ͮ";i:230;s:2:"ͯ";i:230;s:2:"҃";i:230;s:2:"҄";i:230;s:2:"҅";i:230;s:2:"҆";i:230;s:2:"҇";i:230;s:2:"֑";i:220;s:2:"֒";i:230;s:2:"֓";i:230;s:2:"֔";i:230;s:2:"֕";i:230;s:2:"֖";i:220;s:2:"֗";i:230;s:2:"֘";i:230;s:2:"֙";i:230;s:2:"֚";i:222;s:2:"֛";i:220;s:2:"֜";i:230;s:2:"֝";i:230;s:2:"֞";i:230;s:2:"֟";i:230;s:2:"֠";i:230;s:2:"֡";i:230;s:2:"֢";i:220;s:2:"֣";i:220;s:2:"֤";i:220;s:2:"֥";i:220;s:2:"֦";i:220;s:2:"֧";i:220;s:2:"֨";i:230;s:2:"֩";i:230;s:2:"֪";i:220;s:2:"֫";i:230;s:2:"֬";i:230;s:2:"֭";i:222;s:2:"֮";i:228;s:2:"֯";i:230;s:2:"ְ";i:10;s:2:"ֱ";i:11;s:2:"ֲ";i:12;s:2:"ֳ";i:13;s:2:"ִ";i:14;s:2:"ֵ";i:15;s:2:"ֶ";i:16;s:2:"ַ";i:17;s:2:"ָ";i:18;s:2:"ֹ";i:19;s:2:"ֺ";i:19;s:2:"ֻ";i:20;s:2:"ּ";i:21;s:2:"ֽ";i:22;s:2:"ֿ";i:23;s:2:"ׁ";i:24;s:2:"ׂ";i:25;s:2:"ׄ";i:230;s:2:"ׅ";i:220;s:2:"ׇ";i:18;s:2:"ؐ";i:230;s:2:"ؑ";i:230;s:2:"ؒ";i:230;s:2:"ؓ";i:230;s:2:"ؔ";i:230;s:2:"ؕ";i:230;s:2:"ؖ";i:230;s:2:"ؗ";i:230;s:2:"ؘ";i:30;s:2:"ؙ";i:31;s:2:"ؚ";i:32;s:2:"ً";i:27;s:2:"ٌ";i:28;s:2:"ٍ";i:29;s:2:"َ";i:30;s:2:"ُ";i:31;s:2:"ِ";i:32;s:2:"ّ";i:33;s:2:"ْ";i:34;s:2:"ٓ";i:230;s:2:"ٔ";i:230;s:2:"ٕ";i:220;s:2:"ٖ";i:220;s:2:"ٗ";i:230;s:2:"٘";i:230;s:2:"ٙ";i:230;s:2:"ٚ";i:230;s:2:"ٛ";i:230;s:2:"ٜ";i:220;s:2:"ٝ";i:230;s:2:"ٞ";i:230;s:2:"ٟ";i:220;s:2:"ٰ";i:35;s:2:"ۖ";i:230;s:2:"ۗ";i:230;s:2:"ۘ";i:230;s:2:"ۙ";i:230;s:2:"ۚ";i:230;s:2:"ۛ";i:230;s:2:"ۜ";i:230;s:2:"۟";i:230;s:2:"۠";i:230;s:2:"ۡ";i:230;s:2:"ۢ";i:230;s:2:"ۣ";i:220;s:2:"ۤ";i:230;s:2:"ۧ";i:230;s:2:"ۨ";i:230;s:2:"۪";i:220;s:2:"۫";i:230;s:2:"۬";i:230;s:2:"ۭ";i:220;s:2:"ܑ";i:36;s:2:"ܰ";i:230;s:2:"ܱ";i:220;s:2:"ܲ";i:230;s:2:"ܳ";i:230;s:2:"ܴ";i:220;s:2:"ܵ";i:230;s:2:"ܶ";i:230;s:2:"ܷ";i:220;s:2:"ܸ";i:220;s:2:"ܹ";i:220;s:2:"ܺ";i:230;s:2:"ܻ";i:220;s:2:"ܼ";i:220;s:2:"ܽ";i:230;s:2:"ܾ";i:220;s:2:"ܿ";i:230;s:2:"݀";i:230;s:2:"݁";i:230;s:2:"݂";i:220;s:2:"݃";i:230;s:2:"݄";i:220;s:2:"݅";i:230;s:2:"݆";i:220;s:2:"݇";i:230;s:2:"݈";i:220;s:2:"݉";i:230;s:2:"݊";i:230;s:2:"߫";i:230;s:2:"߬";i:230;s:2:"߭";i:230;s:2:"߮";i:230;s:2:"߯";i:230;s:2:"߰";i:230;s:2:"߱";i:230;s:2:"߲";i:220;s:2:"߳";i:230;s:3:"ࠖ";i:230;s:3:"ࠗ";i:230;s:3:"࠘";i:230;s:3:"࠙";i:230;s:3:"ࠛ";i:230;s:3:"ࠜ";i:230;s:3:"ࠝ";i:230;s:3:"ࠞ";i:230;s:3:"ࠟ";i:230;s:3:"ࠠ";i:230;s:3:"ࠡ";i:230;s:3:"ࠢ";i:230;s:3:"ࠣ";i:230;s:3:"ࠥ";i:230;s:3:"ࠦ";i:230;s:3:"ࠧ";i:230;s:3:"ࠩ";i:230;s:3:"ࠪ";i:230;s:3:"ࠫ";i:230;s:3:"ࠬ";i:230;s:3:"࠭";i:230;s:3:"࡙";i:220;s:3:"࡚";i:220;s:3:"࡛";i:220;s:3:"ࣤ";i:230;s:3:"ࣥ";i:230;s:3:"ࣦ";i:220;s:3:"ࣧ";i:230;s:3:"ࣨ";i:230;s:3:"ࣩ";i:220;s:3:"࣪";i:230;s:3:"࣫";i:230;s:3:"࣬";i:230;s:3:"࣭";i:220;s:3:"࣮";i:220;s:3:"࣯";i:220;s:3:"ࣰ";i:27;s:3:"ࣱ";i:28;s:3:"ࣲ";i:29;s:3:"ࣳ";i:230;s:3:"ࣴ";i:230;s:3:"ࣵ";i:230;s:3:"ࣶ";i:220;s:3:"ࣷ";i:230;s:3:"ࣸ";i:230;s:3:"ࣹ";i:220;s:3:"ࣺ";i:220;s:3:"ࣻ";i:230;s:3:"ࣼ";i:230;s:3:"ࣽ";i:230;s:3:"ࣾ";i:230;s:3:"ࣿ";i:230;s:3:"़";i:7;s:3:"्";i:9;s:3:"॑";i:230;s:3:"॒";i:220;s:3:"॓";i:230;s:3:"॔";i:230;s:3:"়";i:7;s:3:"্";i:9;s:3:"਼";i:7;s:3:"੍";i:9;s:3:"઼";i:7;s:3:"્";i:9;s:3:"଼";i:7;s:3:"୍";i:9;s:3:"்";i:9;s:3:"్";i:9;s:3:"ౕ";i:84;s:3:"ౖ";i:91;s:3:"಼";i:7;s:3:"್";i:9;s:3:"്";i:9;s:3:"්";i:9;s:3:"ุ";i:103;s:3:"ู";i:103;s:3:"ฺ";i:9;s:3:"่";i:107;s:3:"้";i:107;s:3:"๊";i:107;s:3:"๋";i:107;s:3:"ຸ";i:118;s:3:"ູ";i:118;s:3:"່";i:122;s:3:"້";i:122;s:3:"໊";i:122;s:3:"໋";i:122;s:3:"༘";i:220;s:3:"༙";i:220;s:3:"༵";i:220;s:3:"༷";i:220;s:3:"༹";i:216;s:3:"ཱ";i:129;s:3:"ི";i:130;s:3:"ུ";i:132;s:3:"ེ";i:130;s:3:"ཻ";i:130;s:3:"ོ";i:130;s:3:"ཽ";i:130;s:3:"ྀ";i:130;s:3:"ྂ";i:230;s:3:"ྃ";i:230;s:3:"྄";i:9;s:3:"྆";i:230;s:3:"྇";i:230;s:3:"࿆";i:220;s:3:"့";i:7;s:3:"္";i:9;s:3:"်";i:9;s:3:"ႍ";i:220;s:3:"፝";i:230;s:3:"፞";i:230;s:3:"፟";i:230;s:3:"᜔";i:9;s:3:"᜴";i:9;s:3:"្";i:9;s:3:"៝";i:230;s:3:"ᢩ";i:228;s:3:"᤹";i:222;s:3:"᤺";i:230;s:3:"᤻";i:220;s:3:"ᨗ";i:230;s:3:"ᨘ";i:220;s:3:"᩠";i:9;s:3:"᩵";i:230;s:3:"᩶";i:230;s:3:"᩷";i:230;s:3:"᩸";i:230;s:3:"᩹";i:230;s:3:"᩺";i:230;s:3:"᩻";i:230;s:3:"᩼";i:230;s:3:"᩿";i:220;s:3:"᪰";i:230;s:3:"᪱";i:230;s:3:"᪲";i:230;s:3:"᪳";i:230;s:3:"᪴";i:230;s:3:"᪵";i:220;s:3:"᪶";i:220;s:3:"᪷";i:220;s:3:"᪸";i:220;s:3:"᪹";i:220;s:3:"᪺";i:220;s:3:"᪻";i:230;s:3:"᪼";i:230;s:3:"᪽";i:220;s:3:"᬴";i:7;s:3:"᭄";i:9;s:3:"᭫";i:230;s:3:"᭬";i:220;s:3:"᭭";i:230;s:3:"᭮";i:230;s:3:"᭯";i:230;s:3:"᭰";i:230;s:3:"᭱";i:230;s:3:"᭲";i:230;s:3:"᭳";i:230;s:3:"᮪";i:9;s:3:"᮫";i:9;s:3:"᯦";i:7;s:3:"᯲";i:9;s:3:"᯳";i:9;s:3:"᰷";i:7;s:3:"᳐";i:230;s:3:"᳑";i:230;s:3:"᳒";i:230;s:3:"᳔";i:1;s:3:"᳕";i:220;s:3:"᳖";i:220;s:3:"᳗";i:220;s:3:"᳘";i:220;s:3:"᳙";i:220;s:3:"᳚";i:230;s:3:"᳛";i:230;s:3:"᳜";i:220;s:3:"᳝";i:220;s:3:"᳞";i:220;s:3:"᳟";i:220;s:3:"᳠";i:230;s:3:"᳢";i:1;s:3:"᳣";i:1;s:3:"᳤";i:1;s:3:"᳥";i:1;s:3:"᳦";i:1;s:3:"᳧";i:1;s:3:"᳨";i:1;s:3:"᳭";i:220;s:3:"᳴";i:230;s:3:"᳸";i:230;s:3:"᳹";i:230;s:3:"᷀";i:230;s:3:"᷁";i:230;s:3:"᷂";i:220;s:3:"᷃";i:230;s:3:"᷄";i:230;s:3:"᷅";i:230;s:3:"᷆";i:230;s:3:"᷇";i:230;s:3:"᷈";i:230;s:3:"᷉";i:230;s:3:"᷊";i:220;s:3:"᷋";i:230;s:3:"᷌";i:230;s:3:"᷍";i:234;s:3:"᷎";i:214;s:3:"᷏";i:220;s:3:"᷐";i:202;s:3:"᷑";i:230;s:3:"᷒";i:230;s:3:"ᷓ";i:230;s:3:"ᷔ";i:230;s:3:"ᷕ";i:230;s:3:"ᷖ";i:230;s:3:"ᷗ";i:230;s:3:"ᷘ";i:230;s:3:"ᷙ";i:230;s:3:"ᷚ";i:230;s:3:"ᷛ";i:230;s:3:"ᷜ";i:230;s:3:"ᷝ";i:230;s:3:"ᷞ";i:230;s:3:"ᷟ";i:230;s:3:"ᷠ";i:230;s:3:"ᷡ";i:230;s:3:"ᷢ";i:230;s:3:"ᷣ";i:230;s:3:"ᷤ";i:230;s:3:"ᷥ";i:230;s:3:"ᷦ";i:230;s:3:"ᷧ";i:230;s:3:"ᷨ";i:230;s:3:"ᷩ";i:230;s:3:"ᷪ";i:230;s:3:"ᷫ";i:230;s:3:"ᷬ";i:230;s:3:"ᷭ";i:230;s:3:"ᷮ";i:230;s:3:"ᷯ";i:230;s:3:"ᷰ";i:230;s:3:"ᷱ";i:230;s:3:"ᷲ";i:230;s:3:"ᷳ";i:230;s:3:"ᷴ";i:230;s:3:"᷵";i:230;s:3:"᷼";i:233;s:3:"᷽";i:220;s:3:"᷾";i:230;s:3:"᷿";i:220;s:3:"⃐";i:230;s:3:"⃑";i:230;s:3:"⃒";i:1;s:3:"⃓";i:1;s:3:"⃔";i:230;s:3:"⃕";i:230;s:3:"⃖";i:230;s:3:"⃗";i:230;s:3:"⃘";i:1;s:3:"⃙";i:1;s:3:"⃚";i:1;s:3:"⃛";i:230;s:3:"⃜";i:230;s:3:"⃡";i:230;s:3:"⃥";i:1;s:3:"⃦";i:1;s:3:"⃧";i:230;s:3:"⃨";i:220;s:3:"⃩";i:230;s:3:"⃪";i:1;s:3:"⃫";i:1;s:3:"⃬";i:220;s:3:"⃭";i:220;s:3:"⃮";i:220;s:3:"⃯";i:220;s:3:"⃰";i:230;s:3:"⳯";i:230;s:3:"⳰";i:230;s:3:"⳱";i:230;s:3:"⵿";i:9;s:3:"ⷠ";i:230;s:3:"ⷡ";i:230;s:3:"ⷢ";i:230;s:3:"ⷣ";i:230;s:3:"ⷤ";i:230;s:3:"ⷥ";i:230;s:3:"ⷦ";i:230;s:3:"ⷧ";i:230;s:3:"ⷨ";i:230;s:3:"ⷩ";i:230;s:3:"ⷪ";i:230;s:3:"ⷫ";i:230;s:3:"ⷬ";i:230;s:3:"ⷭ";i:230;s:3:"ⷮ";i:230;s:3:"ⷯ";i:230;s:3:"ⷰ";i:230;s:3:"ⷱ";i:230;s:3:"ⷲ";i:230;s:3:"ⷳ";i:230;s:3:"ⷴ";i:230;s:3:"ⷵ";i:230;s:3:"ⷶ";i:230;s:3:"ⷷ";i:230;s:3:"ⷸ";i:230;s:3:"ⷹ";i:230;s:3:"ⷺ";i:230;s:3:"ⷻ";i:230;s:3:"ⷼ";i:230;s:3:"ⷽ";i:230;s:3:"ⷾ";i:230;s:3:"ⷿ";i:230;s:3:"〪";i:218;s:3:"〫";i:228;s:3:"〬";i:232;s:3:"〭";i:222;s:3:"〮";i:224;s:3:"〯";i:224;s:3:"゙";i:8;s:3:"゚";i:8;s:3:"꙯";i:230;s:3:"ꙴ";i:230;s:3:"ꙵ";i:230;s:3:"ꙶ";i:230;s:3:"ꙷ";i:230;s:3:"ꙸ";i:230;s:3:"ꙹ";i:230;s:3:"ꙺ";i:230;s:3:"ꙻ";i:230;s:3:"꙼";i:230;s:3:"꙽";i:230;s:3:"ꚟ";i:230;s:3:"꛰";i:230;s:3:"꛱";i:230;s:3:"꠆";i:9;s:3:"꣄";i:9;s:3:"꣠";i:230;s:3:"꣡";i:230;s:3:"꣢";i:230;s:3:"꣣";i:230;s:3:"꣤";i:230;s:3:"꣥";i:230;s:3:"꣦";i:230;s:3:"꣧";i:230;s:3:"꣨";i:230;s:3:"꣩";i:230;s:3:"꣪";i:230;s:3:"꣫";i:230;s:3:"꣬";i:230;s:3:"꣭";i:230;s:3:"꣮";i:230;s:3:"꣯";i:230;s:3:"꣰";i:230;s:3:"꣱";i:230;s:3:"꤫";i:220;s:3:"꤬";i:220;s:3:"꤭";i:220;s:3:"꥓";i:9;s:3:"꦳";i:7;s:3:"꧀";i:9;s:3:"ꪰ";i:230;s:3:"ꪲ";i:230;s:3:"ꪳ";i:230;s:3:"ꪴ";i:220;s:3:"ꪷ";i:230;s:3:"ꪸ";i:230;s:3:"ꪾ";i:230;s:3:"꪿";i:230;s:3:"꫁";i:230;s:3:"꫶";i:9;s:3:"꯭";i:9;s:3:"ﬞ";i:26;s:3:"︠";i:230;s:3:"︡";i:230;s:3:"︢";i:230;s:3:"︣";i:230;s:3:"︤";i:230;s:3:"︥";i:230;s:3:"︦";i:230;s:3:"︧";i:220;s:3:"︨";i:220;s:3:"︩";i:220;s:3:"︪";i:220;s:3:"︫";i:220;s:3:"︬";i:220;s:3:"︭";i:220;s:4:"𐇽";i:220;s:4:"𐋠";i:220;s:4:"𐍶";i:230;s:4:"𐍷";i:230;s:4:"𐍸";i:230;s:4:"𐍹";i:230;s:4:"𐍺";i:230;s:4:"𐨍";i:220;s:4:"𐨏";i:230;s:4:"𐨸";i:230;s:4:"𐨹";i:1;s:4:"𐨺";i:220;s:4:"𐨿";i:9;s:4:"𐫥";i:230;s:4:"𐫦";i:220;s:4:"𑁆";i:9;s:4:"𑁿";i:9;s:4:"𑂹";i:9;s:4:"𑂺";i:7;s:4:"𑄀";i:230;s:4:"𑄁";i:230;s:4:"𑄂";i:230;s:4:"𑄳";i:9;s:4:"𑄴";i:9;s:4:"𑅳";i:7;s:4:"𑇀";i:9;s:4:"𑈵";i:9;s:4:"𑈶";i:7;s:4:"𑋩";i:7;s:4:"𑋪";i:9;s:4:"𑌼";i:7;s:4:"𑍍";i:9;s:4:"𑍦";i:230;s:4:"𑍧";i:230;s:4:"𑍨";i:230;s:4:"𑍩";i:230;s:4:"𑍪";i:230;s:4:"𑍫";i:230;s:4:"𑍬";i:230;s:4:"𑍰";i:230;s:4:"𑍱";i:230;s:4:"𑍲";i:230;s:4:"𑍳";i:230;s:4:"𑍴";i:230;s:4:"𑓂";i:9;s:4:"𑓃";i:7;s:4:"𑖿";i:9;s:4:"𑗀";i:7;s:4:"𑘿";i:9;s:4:"𑚶";i:9;s:4:"𑚷";i:7;s:4:"𖫰";i:1;s:4:"𖫱";i:1;s:4:"𖫲";i:1;s:4:"𖫳";i:1;s:4:"𖫴";i:1;s:4:"𖬰";i:230;s:4:"𖬱";i:230;s:4:"𖬲";i:230;s:4:"𖬳";i:230;s:4:"𖬴";i:230;s:4:"𖬵";i:230;s:4:"𖬶";i:230;s:4:"𛲞";i:1;s:4:"𝅥";i:216;s:4:"𝅦";i:216;s:4:"𝅧";i:1;s:4:"𝅨";i:1;s:4:"𝅩";i:1;s:4:"𝅭";i:226;s:4:"𝅮";i:216;s:4:"𝅯";i:216;s:4:"𝅰";i:216;s:4:"𝅱";i:216;s:4:"𝅲";i:216;s:4:"𝅻";i:220;s:4:"𝅼";i:220;s:4:"𝅽";i:220;s:4:"𝅾";i:220;s:4:"𝅿";i:220;s:4:"𝆀";i:220;s:4:"𝆁";i:220;s:4:"𝆂";i:220;s:4:"𝆅";i:230;s:4:"𝆆";i:230;s:4:"𝆇";i:230;s:4:"𝆈";i:230;s:4:"𝆉";i:230;s:4:"𝆊";i:220;s:4:"𝆋";i:220;s:4:"𝆪";i:230;s:4:"𝆫";i:230;s:4:"𝆬";i:230;s:4:"𝆭";i:230;s:4:"𝉂";i:230;s:4:"𝉃";i:230;s:4:"𝉄";i:230;s:4:"𞣐";i:220;s:4:"𞣑";i:220;s:4:"𞣒";i:220;s:4:"𞣓";i:220;s:4:"𞣔";i:220;s:4:"𞣕";i:220;s:4:"𞣖";i:220;} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/compatibilityDecomposition.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/compatibilityDecomposition.ser new file mode 100644 index 0000000..9ec5bcd --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/compatibilityDecomposition.ser @@ -0,0 +1 @@ +a:3677:{s:2:" ";s:1:" ";s:2:"¨";s:3:" ̈";s:2:"ª";s:1:"a";s:2:"¯";s:3:" ̄";s:2:"²";s:1:"2";s:2:"³";s:1:"3";s:2:"´";s:3:" ́";s:2:"µ";s:2:"μ";s:2:"¸";s:3:" ̧";s:2:"¹";s:1:"1";s:2:"º";s:1:"o";s:2:"¼";s:5:"1⁄4";s:2:"½";s:5:"1⁄2";s:2:"¾";s:5:"3⁄4";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ŀ";s:3:"L·";s:2:"ŀ";s:3:"l·";s:2:"ʼn";s:3:"ʼn";s:2:"ſ";s:1:"s";s:2:"DŽ";s:4:"DŽ";s:2:"Dž";s:4:"Dž";s:2:"dž";s:4:"dž";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"NJ";s:2:"NJ";s:2:"Nj";s:2:"Nj";s:2:"nj";s:2:"nj";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"ʰ";s:1:"h";s:2:"ʱ";s:2:"ɦ";s:2:"ʲ";s:1:"j";s:2:"ʳ";s:1:"r";s:2:"ʴ";s:2:"ɹ";s:2:"ʵ";s:2:"ɻ";s:2:"ʶ";s:2:"ʁ";s:2:"ʷ";s:1:"w";s:2:"ʸ";s:1:"y";s:2:"˘";s:3:" ̆";s:2:"˙";s:3:" ̇";s:2:"˚";s:3:" ̊";s:2:"˛";s:3:" ̨";s:2:"˜";s:3:" ̃";s:2:"˝";s:3:" ̋";s:2:"ˠ";s:2:"ɣ";s:2:"ˡ";s:1:"l";s:2:"ˢ";s:1:"s";s:2:"ˣ";s:1:"x";s:2:"ˤ";s:2:"ʕ";s:2:"ͺ";s:3:" ͅ";s:2:"΄";s:3:" ́";s:2:"΅";s:5:" ̈́";s:2:"ϐ";s:2:"β";s:2:"ϑ";s:2:"θ";s:2:"ϒ";s:2:"Υ";s:2:"ϓ";s:4:"Ύ";s:2:"ϔ";s:4:"Ϋ";s:2:"ϕ";s:2:"φ";s:2:"ϖ";s:2:"π";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"ρ";s:2:"ϲ";s:2:"ς";s:2:"ϴ";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"և";s:4:"եւ";s:2:"ٵ";s:4:"اٴ";s:2:"ٶ";s:4:"وٴ";s:2:"ٷ";s:4:"ۇٴ";s:2:"ٸ";s:4:"يٴ";s:3:"ำ";s:6:"ํา";s:3:"ຳ";s:6:"ໍາ";s:3:"ໜ";s:6:"ຫນ";s:3:"ໝ";s:6:"ຫມ";s:3:"༌";s:3:"་";s:3:"ཷ";s:9:"ྲཱྀ";s:3:"ཹ";s:9:"ླཱྀ";s:3:"ჼ";s:3:"ნ";s:3:"ᴬ";s:1:"A";s:3:"ᴭ";s:2:"Æ";s:3:"ᴮ";s:1:"B";s:3:"ᴰ";s:1:"D";s:3:"ᴱ";s:1:"E";s:3:"ᴲ";s:2:"Ǝ";s:3:"ᴳ";s:1:"G";s:3:"ᴴ";s:1:"H";s:3:"ᴵ";s:1:"I";s:3:"ᴶ";s:1:"J";s:3:"ᴷ";s:1:"K";s:3:"ᴸ";s:1:"L";s:3:"ᴹ";s:1:"M";s:3:"ᴺ";s:1:"N";s:3:"ᴼ";s:1:"O";s:3:"ᴽ";s:2:"Ȣ";s:3:"ᴾ";s:1:"P";s:3:"ᴿ";s:1:"R";s:3:"ᵀ";s:1:"T";s:3:"ᵁ";s:1:"U";s:3:"ᵂ";s:1:"W";s:3:"ᵃ";s:1:"a";s:3:"ᵄ";s:2:"ɐ";s:3:"ᵅ";s:2:"ɑ";s:3:"ᵆ";s:3:"ᴂ";s:3:"ᵇ";s:1:"b";s:3:"ᵈ";s:1:"d";s:3:"ᵉ";s:1:"e";s:3:"ᵊ";s:2:"ə";s:3:"ᵋ";s:2:"ɛ";s:3:"ᵌ";s:2:"ɜ";s:3:"ᵍ";s:1:"g";s:3:"ᵏ";s:1:"k";s:3:"ᵐ";s:1:"m";s:3:"ᵑ";s:2:"ŋ";s:3:"ᵒ";s:1:"o";s:3:"ᵓ";s:2:"ɔ";s:3:"ᵔ";s:3:"ᴖ";s:3:"ᵕ";s:3:"ᴗ";s:3:"ᵖ";s:1:"p";s:3:"ᵗ";s:1:"t";s:3:"ᵘ";s:1:"u";s:3:"ᵙ";s:3:"ᴝ";s:3:"ᵚ";s:2:"ɯ";s:3:"ᵛ";s:1:"v";s:3:"ᵜ";s:3:"ᴥ";s:3:"ᵝ";s:2:"β";s:3:"ᵞ";s:2:"γ";s:3:"ᵟ";s:2:"δ";s:3:"ᵠ";s:2:"φ";s:3:"ᵡ";s:2:"χ";s:3:"ᵢ";s:1:"i";s:3:"ᵣ";s:1:"r";s:3:"ᵤ";s:1:"u";s:3:"ᵥ";s:1:"v";s:3:"ᵦ";s:2:"β";s:3:"ᵧ";s:2:"γ";s:3:"ᵨ";s:2:"ρ";s:3:"ᵩ";s:2:"φ";s:3:"ᵪ";s:2:"χ";s:3:"ᵸ";s:2:"н";s:3:"ᶛ";s:2:"ɒ";s:3:"ᶜ";s:1:"c";s:3:"ᶝ";s:2:"ɕ";s:3:"ᶞ";s:2:"ð";s:3:"ᶟ";s:2:"ɜ";s:3:"ᶠ";s:1:"f";s:3:"ᶡ";s:2:"ɟ";s:3:"ᶢ";s:2:"ɡ";s:3:"ᶣ";s:2:"ɥ";s:3:"ᶤ";s:2:"ɨ";s:3:"ᶥ";s:2:"ɩ";s:3:"ᶦ";s:2:"ɪ";s:3:"ᶧ";s:3:"ᵻ";s:3:"ᶨ";s:2:"ʝ";s:3:"ᶩ";s:2:"ɭ";s:3:"ᶪ";s:3:"ᶅ";s:3:"ᶫ";s:2:"ʟ";s:3:"ᶬ";s:2:"ɱ";s:3:"ᶭ";s:2:"ɰ";s:3:"ᶮ";s:2:"ɲ";s:3:"ᶯ";s:2:"ɳ";s:3:"ᶰ";s:2:"ɴ";s:3:"ᶱ";s:2:"ɵ";s:3:"ᶲ";s:2:"ɸ";s:3:"ᶳ";s:2:"ʂ";s:3:"ᶴ";s:2:"ʃ";s:3:"ᶵ";s:2:"ƫ";s:3:"ᶶ";s:2:"ʉ";s:3:"ᶷ";s:2:"ʊ";s:3:"ᶸ";s:3:"ᴜ";s:3:"ᶹ";s:2:"ʋ";s:3:"ᶺ";s:2:"ʌ";s:3:"ᶻ";s:1:"z";s:3:"ᶼ";s:2:"ʐ";s:3:"ᶽ";s:2:"ʑ";s:3:"ᶾ";s:2:"ʒ";s:3:"ᶿ";s:2:"θ";s:3:"ẚ";s:3:"aʾ";s:3:"ẛ";s:3:"ṡ";s:3:"᾽";s:3:" ̓";s:3:"᾿";s:3:" ̓";s:3:"῀";s:3:" ͂";s:3:"῁";s:5:" ̈͂";s:3:"῍";s:5:" ̓̀";s:3:"῎";s:5:" ̓́";s:3:"῏";s:5:" ̓͂";s:3:"῝";s:5:" ̔̀";s:3:"῞";s:5:" ̔́";s:3:"῟";s:5:" ̔͂";s:3:"῭";s:5:" ̈̀";s:3:"΅";s:5:" ̈́";s:3:"´";s:3:" ́";s:3:"῾";s:3:" ̔";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:"‑";s:3:"‐";s:3:"‗";s:3:" ̳";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:" ";s:1:" ";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"‾";s:3:" ̅";s:3:"⁇";s:2:"??";s:3:"⁈";s:2:"?!";s:3:"⁉";s:2:"!?";s:3:"⁗";s:12:"′′′′";s:3:" ";s:1:" ";s:3:"⁰";s:1:"0";s:3:"ⁱ";s:1:"i";s:3:"⁴";s:1:"4";s:3:"⁵";s:1:"5";s:3:"⁶";s:1:"6";s:3:"⁷";s:1:"7";s:3:"⁸";s:1:"8";s:3:"⁹";s:1:"9";s:3:"⁺";s:1:"+";s:3:"⁻";s:3:"−";s:3:"⁼";s:1:"=";s:3:"⁽";s:1:"(";s:3:"⁾";s:1:")";s:3:"ⁿ";s:1:"n";s:3:"₀";s:1:"0";s:3:"₁";s:1:"1";s:3:"₂";s:1:"2";s:3:"₃";s:1:"3";s:3:"₄";s:1:"4";s:3:"₅";s:1:"5";s:3:"₆";s:1:"6";s:3:"₇";s:1:"7";s:3:"₈";s:1:"8";s:3:"₉";s:1:"9";s:3:"₊";s:1:"+";s:3:"₋";s:3:"−";s:3:"₌";s:1:"=";s:3:"₍";s:1:"(";s:3:"₎";s:1:")";s:3:"ₐ";s:1:"a";s:3:"ₑ";s:1:"e";s:3:"ₒ";s:1:"o";s:3:"ₓ";s:1:"x";s:3:"ₔ";s:2:"ə";s:3:"ₕ";s:1:"h";s:3:"ₖ";s:1:"k";s:3:"ₗ";s:1:"l";s:3:"ₘ";s:1:"m";s:3:"ₙ";s:1:"n";s:3:"ₚ";s:1:"p";s:3:"ₛ";s:1:"s";s:3:"ₜ";s:1:"t";s:3:"₨";s:2:"Rs";s:3:"℀";s:3:"a/c";s:3:"℁";s:3:"a/s";s:3:"ℂ";s:1:"C";s:3:"℃";s:3:"°C";s:3:"℅";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Ɛ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"ℋ";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"ℍ";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"ℏ";s:2:"ħ";s:3:"ℐ";s:1:"I";s:3:"ℑ";s:1:"I";s:3:"ℒ";s:1:"L";s:3:"ℓ";s:1:"l";s:3:"ℕ";s:1:"N";s:3:"№";s:2:"No";s:3:"ℙ";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"ℛ";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"ℝ";s:1:"R";s:3:"℠";s:2:"SM";s:3:"℡";s:3:"TEL";s:3:"™";s:2:"TM";s:3:"ℤ";s:1:"Z";s:3:"ℨ";s:1:"Z";s:3:"ℬ";s:1:"B";s:3:"ℭ";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"ℰ";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"ℴ";s:1:"o";s:3:"ℵ";s:2:"א";s:3:"ℶ";s:2:"ב";s:3:"ℷ";s:2:"ג";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"℻";s:3:"FAX";s:3:"ℼ";s:2:"π";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"ℿ";s:2:"Π";s:3:"⅀";s:3:"∑";s:3:"ⅅ";s:1:"D";s:3:"ⅆ";s:1:"d";s:3:"ⅇ";s:1:"e";s:3:"ⅈ";s:1:"i";s:3:"ⅉ";s:1:"j";s:3:"⅐";s:5:"1⁄7";s:3:"⅑";s:5:"1⁄9";s:3:"⅒";s:6:"1⁄10";s:3:"⅓";s:5:"1⁄3";s:3:"⅔";s:5:"2⁄3";s:3:"⅕";s:5:"1⁄5";s:3:"⅖";s:5:"2⁄5";s:3:"⅗";s:5:"3⁄5";s:3:"⅘";s:5:"4⁄5";s:3:"⅙";s:5:"1⁄6";s:3:"⅚";s:5:"5⁄6";s:3:"⅛";s:5:"1⁄8";s:3:"⅜";s:5:"3⁄8";s:3:"⅝";s:5:"5⁄8";s:3:"⅞";s:5:"7⁄8";s:3:"⅟";s:4:"1⁄";s:3:"Ⅰ";s:1:"I";s:3:"Ⅱ";s:2:"II";s:3:"Ⅲ";s:3:"III";s:3:"Ⅳ";s:2:"IV";s:3:"Ⅴ";s:1:"V";s:3:"Ⅵ";s:2:"VI";s:3:"Ⅶ";s:3:"VII";s:3:"Ⅷ";s:4:"VIII";s:3:"Ⅸ";s:2:"IX";s:3:"Ⅹ";s:1:"X";s:3:"Ⅺ";s:2:"XI";s:3:"Ⅻ";s:3:"XII";s:3:"Ⅼ";s:1:"L";s:3:"Ⅽ";s:1:"C";s:3:"Ⅾ";s:1:"D";s:3:"Ⅿ";s:1:"M";s:3:"ⅰ";s:1:"i";s:3:"ⅱ";s:2:"ii";s:3:"ⅲ";s:3:"iii";s:3:"ⅳ";s:2:"iv";s:3:"ⅴ";s:1:"v";s:3:"ⅵ";s:2:"vi";s:3:"ⅶ";s:3:"vii";s:3:"ⅷ";s:4:"viii";s:3:"ⅸ";s:2:"ix";s:3:"ⅹ";s:1:"x";s:3:"ⅺ";s:2:"xi";s:3:"ⅻ";s:3:"xii";s:3:"ⅼ";s:1:"l";s:3:"ⅽ";s:1:"c";s:3:"ⅾ";s:1:"d";s:3:"ⅿ";s:1:"m";s:3:"↉";s:5:"0⁄3";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"①";s:1:"1";s:3:"②";s:1:"2";s:3:"③";s:1:"3";s:3:"④";s:1:"4";s:3:"⑤";s:1:"5";s:3:"⑥";s:1:"6";s:3:"⑦";s:1:"7";s:3:"⑧";s:1:"8";s:3:"⑨";s:1:"9";s:3:"⑩";s:2:"10";s:3:"⑪";s:2:"11";s:3:"⑫";s:2:"12";s:3:"⑬";s:2:"13";s:3:"⑭";s:2:"14";s:3:"⑮";s:2:"15";s:3:"⑯";s:2:"16";s:3:"⑰";s:2:"17";s:3:"⑱";s:2:"18";s:3:"⑲";s:2:"19";s:3:"⑳";s:2:"20";s:3:"⑴";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"⑶";s:3:"(3)";s:3:"⑷";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"⑻";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"⑿";s:4:"(12)";s:3:"⒀";s:4:"(13)";s:3:"⒁";s:4:"(14)";s:3:"⒂";s:4:"(15)";s:3:"⒃";s:4:"(16)";s:3:"⒄";s:4:"(17)";s:3:"⒅";s:4:"(18)";s:3:"⒆";s:4:"(19)";s:3:"⒇";s:4:"(20)";s:3:"⒈";s:2:"1.";s:3:"⒉";s:2:"2.";s:3:"⒊";s:2:"3.";s:3:"⒋";s:2:"4.";s:3:"⒌";s:2:"5.";s:3:"⒍";s:2:"6.";s:3:"⒎";s:2:"7.";s:3:"⒏";s:2:"8.";s:3:"⒐";s:2:"9.";s:3:"⒑";s:3:"10.";s:3:"⒒";s:3:"11.";s:3:"⒓";s:3:"12.";s:3:"⒔";s:3:"13.";s:3:"⒕";s:3:"14.";s:3:"⒖";s:3:"15.";s:3:"⒗";s:3:"16.";s:3:"⒘";s:3:"17.";s:3:"⒙";s:3:"18.";s:3:"⒚";s:3:"19.";s:3:"⒛";s:3:"20.";s:3:"⒜";s:3:"(a)";s:3:"⒝";s:3:"(b)";s:3:"⒞";s:3:"(c)";s:3:"⒟";s:3:"(d)";s:3:"⒠";s:3:"(e)";s:3:"⒡";s:3:"(f)";s:3:"⒢";s:3:"(g)";s:3:"⒣";s:3:"(h)";s:3:"⒤";s:3:"(i)";s:3:"⒥";s:3:"(j)";s:3:"⒦";s:3:"(k)";s:3:"⒧";s:3:"(l)";s:3:"⒨";s:3:"(m)";s:3:"⒩";s:3:"(n)";s:3:"⒪";s:3:"(o)";s:3:"⒫";s:3:"(p)";s:3:"⒬";s:3:"(q)";s:3:"⒭";s:3:"(r)";s:3:"⒮";s:3:"(s)";s:3:"⒯";s:3:"(t)";s:3:"⒰";s:3:"(u)";s:3:"⒱";s:3:"(v)";s:3:"⒲";s:3:"(w)";s:3:"⒳";s:3:"(x)";s:3:"⒴";s:3:"(y)";s:3:"⒵";s:3:"(z)";s:3:"Ⓐ";s:1:"A";s:3:"Ⓑ";s:1:"B";s:3:"Ⓒ";s:1:"C";s:3:"Ⓓ";s:1:"D";s:3:"Ⓔ";s:1:"E";s:3:"Ⓕ";s:1:"F";s:3:"Ⓖ";s:1:"G";s:3:"Ⓗ";s:1:"H";s:3:"Ⓘ";s:1:"I";s:3:"Ⓙ";s:1:"J";s:3:"Ⓚ";s:1:"K";s:3:"Ⓛ";s:1:"L";s:3:"Ⓜ";s:1:"M";s:3:"Ⓝ";s:1:"N";s:3:"Ⓞ";s:1:"O";s:3:"Ⓟ";s:1:"P";s:3:"Ⓠ";s:1:"Q";s:3:"Ⓡ";s:1:"R";s:3:"Ⓢ";s:1:"S";s:3:"Ⓣ";s:1:"T";s:3:"Ⓤ";s:1:"U";s:3:"Ⓥ";s:1:"V";s:3:"Ⓦ";s:1:"W";s:3:"Ⓧ";s:1:"X";s:3:"Ⓨ";s:1:"Y";s:3:"Ⓩ";s:1:"Z";s:3:"ⓐ";s:1:"a";s:3:"ⓑ";s:1:"b";s:3:"ⓒ";s:1:"c";s:3:"ⓓ";s:1:"d";s:3:"ⓔ";s:1:"e";s:3:"ⓕ";s:1:"f";s:3:"ⓖ";s:1:"g";s:3:"ⓗ";s:1:"h";s:3:"ⓘ";s:1:"i";s:3:"ⓙ";s:1:"j";s:3:"ⓚ";s:1:"k";s:3:"ⓛ";s:1:"l";s:3:"ⓜ";s:1:"m";s:3:"ⓝ";s:1:"n";s:3:"ⓞ";s:1:"o";s:3:"ⓟ";s:1:"p";s:3:"ⓠ";s:1:"q";s:3:"ⓡ";s:1:"r";s:3:"ⓢ";s:1:"s";s:3:"ⓣ";s:1:"t";s:3:"ⓤ";s:1:"u";s:3:"ⓥ";s:1:"v";s:3:"ⓦ";s:1:"w";s:3:"ⓧ";s:1:"x";s:3:"ⓨ";s:1:"y";s:3:"ⓩ";s:1:"z";s:3:"⓪";s:1:"0";s:3:"⨌";s:12:"∫∫∫∫";s:3:"⩴";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"⩶";s:3:"===";s:3:"ⱼ";s:1:"j";s:3:"ⱽ";s:1:"V";s:3:"ⵯ";s:3:"ⵡ";s:3:"⺟";s:3:"母";s:3:"⻳";s:3:"龟";s:3:"⼀";s:3:"一";s:3:"⼁";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"乙";s:3:"⼅";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"儿";s:3:"⼊";s:3:"入";s:3:"⼋";s:3:"八";s:3:"⼌";s:3:"冂";s:3:"⼍";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"⼏";s:3:"几";s:3:"⼐";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"⼒";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"⼔";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"⼖";s:3:"匸";s:3:"⼗";s:3:"十";s:3:"⼘";s:3:"卜";s:3:"⼙";s:3:"卩";s:3:"⼚";s:3:"厂";s:3:"⼛";s:3:"厶";s:3:"⼜";s:3:"又";s:3:"⼝";s:3:"口";s:3:"⼞";s:3:"囗";s:3:"⼟";s:3:"土";s:3:"⼠";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"⼢";s:3:"夊";s:3:"⼣";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"⼥";s:3:"女";s:3:"⼦";s:3:"子";s:3:"⼧";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"小";s:3:"⼪";s:3:"尢";s:3:"⼫";s:3:"尸";s:3:"⼬";s:3:"屮";s:3:"⼭";s:3:"山";s:3:"⼮";s:3:"巛";s:3:"⼯";s:3:"工";s:3:"⼰";s:3:"己";s:3:"⼱";s:3:"巾";s:3:"⼲";s:3:"干";s:3:"⼳";s:3:"幺";s:3:"⼴";s:3:"广";s:3:"⼵";s:3:"廴";s:3:"⼶";s:3:"廾";s:3:"⼷";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"⼹";s:3:"彐";s:3:"⼺";s:3:"彡";s:3:"⼻";s:3:"彳";s:3:"⼼";s:3:"心";s:3:"⼽";s:3:"戈";s:3:"⼾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"⽀";s:3:"支";s:3:"⽁";s:3:"攴";s:3:"⽂";s:3:"文";s:3:"⽃";s:3:"斗";s:3:"⽄";s:3:"斤";s:3:"⽅";s:3:"方";s:3:"⽆";s:3:"无";s:3:"⽇";s:3:"日";s:3:"⽈";s:3:"曰";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"止";s:3:"⽍";s:3:"歹";s:3:"⽎";s:3:"殳";s:3:"⽏";s:3:"毋";s:3:"⽐";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"⽒";s:3:"氏";s:3:"⽓";s:3:"气";s:3:"⽔";s:3:"水";s:3:"⽕";s:3:"火";s:3:"⽖";s:3:"爪";s:3:"⽗";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"⽙";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"⽛";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"⽝";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"⽠";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"⽢";s:3:"甘";s:3:"⽣";s:3:"生";s:3:"⽤";s:3:"用";s:3:"⽥";s:3:"田";s:3:"⽦";s:3:"疋";s:3:"⽧";s:3:"疒";s:3:"⽨";s:3:"癶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"皮";s:3:"⽫";s:3:"皿";s:3:"⽬";s:3:"目";s:3:"⽭";s:3:"矛";s:3:"⽮";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"⽰";s:3:"示";s:3:"⽱";s:3:"禸";s:3:"⽲";s:3:"禾";s:3:"⽳";s:3:"穴";s:3:"⽴";s:3:"立";s:3:"⽵";s:3:"竹";s:3:"⽶";s:3:"米";s:3:"⽷";s:3:"糸";s:3:"⽸";s:3:"缶";s:3:"⽹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"⽻";s:3:"羽";s:3:"⽼";s:3:"老";s:3:"⽽";s:3:"而";s:3:"⽾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"⾀";s:3:"聿";s:3:"⾁";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"⾅";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"虍";s:3:"⾍";s:3:"虫";s:3:"⾎";s:3:"血";s:3:"⾏";s:3:"行";s:3:"⾐";s:3:"衣";s:3:"⾑";s:3:"襾";s:3:"⾒";s:3:"見";s:3:"⾓";s:3:"角";s:3:"⾔";s:3:"言";s:3:"⾕";s:3:"谷";s:3:"⾖";s:3:"豆";s:3:"⾗";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"⾙";s:3:"貝";s:3:"⾚";s:3:"赤";s:3:"⾛";s:3:"走";s:3:"⾜";s:3:"足";s:3:"⾝";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"辛";s:3:"⾠";s:3:"辰";s:3:"⾡";s:3:"辵";s:3:"⾢";s:3:"邑";s:3:"⾣";s:3:"酉";s:3:"⾤";s:3:"釆";s:3:"⾥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"⾧";s:3:"長";s:3:"⾨";s:3:"門";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"隶";s:3:"⾫";s:3:"隹";s:3:"⾬";s:3:"雨";s:3:"⾭";s:3:"靑";s:3:"⾮";s:3:"非";s:3:"⾯";s:3:"面";s:3:"⾰";s:3:"革";s:3:"⾱";s:3:"韋";s:3:"⾲";s:3:"韭";s:3:"⾳";s:3:"音";s:3:"⾴";s:3:"頁";s:3:"⾵";s:3:"風";s:3:"⾶";s:3:"飛";s:3:"⾷";s:3:"食";s:3:"⾸";s:3:"首";s:3:"⾹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"⾻";s:3:"骨";s:3:"⾼";s:3:"高";s:3:"⾽";s:3:"髟";s:3:"⾾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"⿀";s:3:"鬲";s:3:"⿁";s:3:"鬼";s:3:"⿂";s:3:"魚";s:3:"⿃";s:3:"鳥";s:3:"⿄";s:3:"鹵";s:3:"⿅";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"黍";s:3:"⿊";s:3:"黑";s:3:"⿋";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"⿍";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"⿏";s:3:"鼠";s:3:"⿐";s:3:"鼻";s:3:"⿑";s:3:"齊";s:3:"⿒";s:3:"齒";s:3:"⿓";s:3:"龍";s:3:"⿔";s:3:"龜";s:3:"⿕";s:3:"龠";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"十";s:3:"〹";s:3:"卄";s:3:"〺";s:3:"卅";s:3:"゛";s:4:" ゙";s:3:"゜";s:4:" ゚";s:3:"ゟ";s:6:"より";s:3:"ヿ";s:6:"コト";s:3:"ㄱ";s:3:"ᄀ";s:3:"ㄲ";s:3:"ᄁ";s:3:"ㄳ";s:3:"ᆪ";s:3:"ㄴ";s:3:"ᄂ";s:3:"ㄵ";s:3:"ᆬ";s:3:"ㄶ";s:3:"ᆭ";s:3:"ㄷ";s:3:"ᄃ";s:3:"ㄸ";s:3:"ᄄ";s:3:"ㄹ";s:3:"ᄅ";s:3:"ㄺ";s:3:"ᆰ";s:3:"ㄻ";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ㄿ";s:3:"ᆵ";s:3:"ㅀ";s:3:"ᄚ";s:3:"ㅁ";s:3:"ᄆ";s:3:"ㅂ";s:3:"ᄇ";s:3:"ㅃ";s:3:"ᄈ";s:3:"ㅄ";s:3:"ᄡ";s:3:"ㅅ";s:3:"ᄉ";s:3:"ㅆ";s:3:"ᄊ";s:3:"ㅇ";s:3:"ᄋ";s:3:"ㅈ";s:3:"ᄌ";s:3:"ㅉ";s:3:"ᄍ";s:3:"ㅊ";s:3:"ᄎ";s:3:"ㅋ";s:3:"ᄏ";s:3:"ㅌ";s:3:"ᄐ";s:3:"ㅍ";s:3:"ᄑ";s:3:"ㅎ";s:3:"ᄒ";s:3:"ㅏ";s:3:"ᅡ";s:3:"ㅐ";s:3:"ᅢ";s:3:"ㅑ";s:3:"ᅣ";s:3:"ㅒ";s:3:"ᅤ";s:3:"ㅓ";s:3:"ᅥ";s:3:"ㅔ";s:3:"ᅦ";s:3:"ㅕ";s:3:"ᅧ";s:3:"ㅖ";s:3:"ᅨ";s:3:"ㅗ";s:3:"ᅩ";s:3:"ㅘ";s:3:"ᅪ";s:3:"ㅙ";s:3:"ᅫ";s:3:"ㅚ";s:3:"ᅬ";s:3:"ㅛ";s:3:"ᅭ";s:3:"ㅜ";s:3:"ᅮ";s:3:"ㅝ";s:3:"ᅯ";s:3:"ㅞ";s:3:"ᅰ";s:3:"ㅟ";s:3:"ᅱ";s:3:"ㅠ";s:3:"ᅲ";s:3:"ㅡ";s:3:"ᅳ";s:3:"ㅢ";s:3:"ᅴ";s:3:"ㅣ";s:3:"ᅵ";s:3:"ㅤ";s:3:"ᅠ";s:3:"ㅥ";s:3:"ᄔ";s:3:"ㅦ";s:3:"ᄕ";s:3:"ㅧ";s:3:"ᇇ";s:3:"ㅨ";s:3:"ᇈ";s:3:"ㅩ";s:3:"ᇌ";s:3:"ㅪ";s:3:"ᇎ";s:3:"ㅫ";s:3:"ᇓ";s:3:"ㅬ";s:3:"ᇗ";s:3:"ㅭ";s:3:"ᇙ";s:3:"ㅮ";s:3:"ᄜ";s:3:"ㅯ";s:3:"ᇝ";s:3:"ㅰ";s:3:"ᇟ";s:3:"ㅱ";s:3:"ᄝ";s:3:"ㅲ";s:3:"ᄞ";s:3:"ㅳ";s:3:"ᄠ";s:3:"ㅴ";s:3:"ᄢ";s:3:"ㅵ";s:3:"ᄣ";s:3:"ㅶ";s:3:"ᄧ";s:3:"ㅷ";s:3:"ᄩ";s:3:"ㅸ";s:3:"ᄫ";s:3:"ㅹ";s:3:"ᄬ";s:3:"ㅺ";s:3:"ᄭ";s:3:"ㅻ";s:3:"ᄮ";s:3:"ㅼ";s:3:"ᄯ";s:3:"ㅽ";s:3:"ᄲ";s:3:"ㅾ";s:3:"ᄶ";s:3:"ㅿ";s:3:"ᅀ";s:3:"ㆀ";s:3:"ᅇ";s:3:"ㆁ";s:3:"ᅌ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"ᅗ";s:3:"ㆅ";s:3:"ᅘ";s:3:"ㆆ";s:3:"ᅙ";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ㆍ";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㆒";s:3:"一";s:3:"㆓";s:3:"二";s:3:"㆔";s:3:"三";s:3:"㆕";s:3:"四";s:3:"㆖";s:3:"上";s:3:"㆗";s:3:"中";s:3:"㆘";s:3:"下";s:3:"㆙";s:3:"甲";s:3:"㆚";s:3:"乙";s:3:"㆛";s:3:"丙";s:3:"㆜";s:3:"丁";s:3:"㆝";s:3:"天";s:3:"㆞";s:3:"地";s:3:"㆟";s:3:"人";s:3:"㈀";s:5:"(ᄀ)";s:3:"㈁";s:5:"(ᄂ)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(ᄅ)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(ᄋ)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(ᄏ)";s:3:"㈋";s:5:"(ᄐ)";s:3:"㈌";s:5:"(ᄑ)";s:3:"㈍";s:5:"(ᄒ)";s:3:"㈎";s:8:"(가)";s:3:"㈏";s:8:"(나)";s:3:"㈐";s:8:"(다)";s:3:"㈑";s:8:"(라)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(아)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(카)";s:3:"㈙";s:8:"(타)";s:3:"㈚";s:8:"(파)";s:3:"㈛";s:8:"(하)";s:3:"㈜";s:8:"(주)";s:3:"㈝";s:17:"(오전)";s:3:"㈞";s:14:"(오후)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(四)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(六)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(八)";s:3:"㈨";s:5:"(九)";s:3:"㈩";s:5:"(十)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(火)";s:3:"㈬";s:5:"(水)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(日)";s:3:"㈱";s:5:"(株)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(名)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(祝)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(学)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(企)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(協)";s:3:"㉀";s:5:"(祭)";s:3:"㉁";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉄";s:3:"問";s:3:"㉅";s:3:"幼";s:3:"㉆";s:3:"文";s:3:"㉇";s:3:"箏";s:3:"㉐";s:3:"PTE";s:3:"㉑";s:2:"21";s:3:"㉒";s:2:"22";s:3:"㉓";s:2:"23";s:3:"㉔";s:2:"24";s:3:"㉕";s:2:"25";s:3:"㉖";s:2:"26";s:3:"㉗";s:2:"27";s:3:"㉘";s:2:"28";s:3:"㉙";s:2:"29";s:3:"㉚";s:2:"30";s:3:"㉛";s:2:"31";s:3:"㉜";s:2:"32";s:3:"㉝";s:2:"33";s:3:"㉞";s:2:"34";s:3:"㉟";s:2:"35";s:3:"㉠";s:3:"ᄀ";s:3:"㉡";s:3:"ᄂ";s:3:"㉢";s:3:"ᄃ";s:3:"㉣";s:3:"ᄅ";s:3:"㉤";s:3:"ᄆ";s:3:"㉥";s:3:"ᄇ";s:3:"㉦";s:3:"ᄉ";s:3:"㉧";s:3:"ᄋ";s:3:"㉨";s:3:"ᄌ";s:3:"㉩";s:3:"ᄎ";s:3:"㉪";s:3:"ᄏ";s:3:"㉫";s:3:"ᄐ";s:3:"㉬";s:3:"ᄑ";s:3:"㉭";s:3:"ᄒ";s:3:"㉮";s:6:"가";s:3:"㉯";s:6:"나";s:3:"㉰";s:6:"다";s:3:"㉱";s:6:"라";s:3:"㉲";s:6:"마";s:3:"㉳";s:6:"바";s:3:"㉴";s:6:"사";s:3:"㉵";s:6:"아";s:3:"㉶";s:6:"자";s:3:"㉷";s:6:"차";s:3:"㉸";s:6:"카";s:3:"㉹";s:6:"타";s:3:"㉺";s:6:"파";s:3:"㉻";s:6:"하";s:3:"㉼";s:15:"참고";s:3:"㉽";s:12:"주의";s:3:"㉾";s:6:"우";s:3:"㊀";s:3:"一";s:3:"㊁";s:3:"二";s:3:"㊂";s:3:"三";s:3:"㊃";s:3:"四";s:3:"㊄";s:3:"五";s:3:"㊅";s:3:"六";s:3:"㊆";s:3:"七";s:3:"㊇";s:3:"八";s:3:"㊈";s:3:"九";s:3:"㊉";s:3:"十";s:3:"㊊";s:3:"月";s:3:"㊋";s:3:"火";s:3:"㊌";s:3:"水";s:3:"㊍";s:3:"木";s:3:"㊎";s:3:"金";s:3:"㊏";s:3:"土";s:3:"㊐";s:3:"日";s:3:"㊑";s:3:"株";s:3:"㊒";s:3:"有";s:3:"㊓";s:3:"社";s:3:"㊔";s:3:"名";s:3:"㊕";s:3:"特";s:3:"㊖";s:3:"財";s:3:"㊗";s:3:"祝";s:3:"㊘";s:3:"労";s:3:"㊙";s:3:"秘";s:3:"㊚";s:3:"男";s:3:"㊛";s:3:"女";s:3:"㊜";s:3:"適";s:3:"㊝";s:3:"優";s:3:"㊞";s:3:"印";s:3:"㊟";s:3:"注";s:3:"㊠";s:3:"項";s:3:"㊡";s:3:"休";s:3:"㊢";s:3:"写";s:3:"㊣";s:3:"正";s:3:"㊤";s:3:"上";s:3:"㊥";s:3:"中";s:3:"㊦";s:3:"下";s:3:"㊧";s:3:"左";s:3:"㊨";s:3:"右";s:3:"㊩";s:3:"医";s:3:"㊪";s:3:"宗";s:3:"㊫";s:3:"学";s:3:"㊬";s:3:"監";s:3:"㊭";s:3:"企";s:3:"㊮";s:3:"資";s:3:"㊯";s:3:"協";s:3:"㊰";s:3:"夜";s:3:"㊱";s:2:"36";s:3:"㊲";s:2:"37";s:3:"㊳";s:2:"38";s:3:"㊴";s:2:"39";s:3:"㊵";s:2:"40";s:3:"㊶";s:2:"41";s:3:"㊷";s:2:"42";s:3:"㊸";s:2:"43";s:3:"㊹";s:2:"44";s:3:"㊺";s:2:"45";s:3:"㊻";s:2:"46";s:3:"㊼";s:2:"47";s:3:"㊽";s:2:"48";s:3:"㊾";s:2:"49";s:3:"㊿";s:2:"50";s:3:"㋀";s:4:"1月";s:3:"㋁";s:4:"2月";s:3:"㋂";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"㋄";s:4:"5月";s:3:"㋅";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"㋋";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"㋍";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"㋏";s:3:"LTD";s:3:"㋐";s:3:"ア";s:3:"㋑";s:3:"イ";s:3:"㋒";s:3:"ウ";s:3:"㋓";s:3:"エ";s:3:"㋔";s:3:"オ";s:3:"㋕";s:3:"カ";s:3:"㋖";s:3:"キ";s:3:"㋗";s:3:"ク";s:3:"㋘";s:3:"ケ";s:3:"㋙";s:3:"コ";s:3:"㋚";s:3:"サ";s:3:"㋛";s:3:"シ";s:3:"㋜";s:3:"ス";s:3:"㋝";s:3:"セ";s:3:"㋞";s:3:"ソ";s:3:"㋟";s:3:"タ";s:3:"㋠";s:3:"チ";s:3:"㋡";s:3:"ツ";s:3:"㋢";s:3:"テ";s:3:"㋣";s:3:"ト";s:3:"㋤";s:3:"ナ";s:3:"㋥";s:3:"ニ";s:3:"㋦";s:3:"ヌ";s:3:"㋧";s:3:"ネ";s:3:"㋨";s:3:"ノ";s:3:"㋩";s:3:"ハ";s:3:"㋪";s:3:"ヒ";s:3:"㋫";s:3:"フ";s:3:"㋬";s:3:"ヘ";s:3:"㋭";s:3:"ホ";s:3:"㋮";s:3:"マ";s:3:"㋯";s:3:"ミ";s:3:"㋰";s:3:"ム";s:3:"㋱";s:3:"メ";s:3:"㋲";s:3:"モ";s:3:"㋳";s:3:"ヤ";s:3:"㋴";s:3:"ユ";s:3:"㋵";s:3:"ヨ";s:3:"㋶";s:3:"ラ";s:3:"㋷";s:3:"リ";s:3:"㋸";s:3:"ル";s:3:"㋹";s:3:"レ";s:3:"㋺";s:3:"ロ";s:3:"㋻";s:3:"ワ";s:3:"㋼";s:3:"ヰ";s:3:"㋽";s:3:"ヱ";s:3:"㋾";s:3:"ヲ";s:3:"㌀";s:15:"アパート";s:3:"㌁";s:12:"アルファ";s:3:"㌂";s:15:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:15:"イニング";s:3:"㌅";s:9:"インチ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:18:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"㌍";s:12:"カロリー";s:3:"㌎";s:12:"ガロン";s:3:"㌏";s:12:"ガンマ";s:3:"㌐";s:12:"ギガ";s:3:"㌑";s:12:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:18:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:18:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:12:"グラム";s:3:"㌙";s:18:"グラムトン";s:3:"㌚";s:18:"クルゼイロ";s:3:"㌛";s:12:"クローネ";s:3:"㌜";s:9:"ケース";s:3:"㌝";s:9:"コルナ";s:3:"㌞";s:12:"コーポ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンチーム";s:3:"㌡";s:15:"シリング";s:3:"㌢";s:9:"センチ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:12:"ダース";s:3:"㌥";s:9:"デシ";s:3:"㌦";s:9:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ハイツ";s:3:"㌫";s:18:"パーセント";s:3:"㌬";s:12:"パーツ";s:3:"㌭";s:15:"バーレル";s:3:"㌮";s:18:"ピアストル";s:3:"㌯";s:12:"ピクル";s:3:"㌰";s:9:"ピコ";s:3:"㌱";s:9:"ビル";s:3:"㌲";s:18:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:18:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:9:"ペソ";s:3:"㌸";s:12:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:12:"ペンス";s:3:"㌻";s:15:"ページ";s:3:"㌼";s:12:"ベータ";s:3:"㌽";s:15:"ポイント";s:3:"㌾";s:12:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"㍀";s:15:"ポンド";s:3:"㍁";s:9:"ホール";s:3:"㍂";s:9:"ホーン";s:3:"㍃";s:12:"マイクロ";s:3:"㍄";s:9:"マイル";s:3:"㍅";s:9:"マッハ";s:3:"㍆";s:9:"マルク";s:3:"㍇";s:15:"マンション";s:3:"㍈";s:12:"ミクロン";s:3:"㍉";s:6:"ミリ";s:3:"㍊";s:18:"ミリバール";s:3:"㍋";s:9:"メガ";s:3:"㍌";s:15:"メガトン";s:3:"㍍";s:12:"メートル";s:3:"㍎";s:12:"ヤード";s:3:"㍏";s:9:"ヤール";s:3:"㍐";s:9:"ユアン";s:3:"㍑";s:12:"リットル";s:3:"㍒";s:6:"リラ";s:3:"㍓";s:12:"ルピー";s:3:"㍔";s:15:"ルーブル";s:3:"㍕";s:6:"レム";s:3:"㍖";s:18:"レントゲン";s:3:"㍗";s:9:"ワット";s:3:"㍘";s:4:"0点";s:3:"㍙";s:4:"1点";s:3:"㍚";s:4:"2点";s:3:"㍛";s:4:"3点";s:3:"㍜";s:4:"4点";s:3:"㍝";s:4:"5点";s:3:"㍞";s:4:"6点";s:3:"㍟";s:4:"7点";s:3:"㍠";s:4:"8点";s:3:"㍡";s:4:"9点";s:3:"㍢";s:5:"10点";s:3:"㍣";s:5:"11点";s:3:"㍤";s:5:"12点";s:3:"㍥";s:5:"13点";s:3:"㍦";s:5:"14点";s:3:"㍧";s:5:"15点";s:3:"㍨";s:5:"16点";s:3:"㍩";s:5:"17点";s:3:"㍪";s:5:"18点";s:3:"㍫";s:5:"19点";s:3:"㍬";s:5:"20点";s:3:"㍭";s:5:"21点";s:3:"㍮";s:5:"22点";s:3:"㍯";s:5:"23点";s:3:"㍰";s:5:"24点";s:3:"㍱";s:3:"hPa";s:3:"㍲";s:2:"da";s:3:"㍳";s:2:"AU";s:3:"㍴";s:3:"bar";s:3:"㍵";s:2:"oV";s:3:"㍶";s:2:"pc";s:3:"㍷";s:2:"dm";s:3:"㍸";s:3:"dm2";s:3:"㍹";s:3:"dm3";s:3:"㍺";s:2:"IU";s:3:"㍻";s:6:"平成";s:3:"㍼";s:6:"昭和";s:3:"㍽";s:6:"大正";s:3:"㍾";s:6:"明治";s:3:"㍿";s:12:"株式会社";s:3:"㎀";s:2:"pA";s:3:"㎁";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"㎍";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"㎏";s:2:"kg";s:3:"㎐";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:3:"μl";s:3:"㎖";s:2:"ml";s:3:"㎗";s:2:"dl";s:3:"㎘";s:2:"kl";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"㎝";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:3:"mm2";s:3:"㎠";s:3:"cm2";s:3:"㎡";s:2:"m2";s:3:"㎢";s:3:"km2";s:3:"㎣";s:3:"mm3";s:3:"㎤";s:3:"cm3";s:3:"㎥";s:2:"m3";s:3:"㎦";s:3:"km3";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:6:"m∕s2";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:8:"rad∕s2";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"㏀";s:3:"kΩ";s:3:"㏁";s:3:"MΩ";s:3:"㏂";s:4:"a.m.";s:3:"㏃";s:2:"Bq";s:3:"㏄";s:2:"cc";s:3:"㏅";s:2:"cd";s:3:"㏆";s:6:"C∕kg";s:3:"㏇";s:3:"Co.";s:3:"㏈";s:2:"dB";s:3:"㏉";s:2:"Gy";s:3:"㏊";s:2:"ha";s:3:"㏋";s:2:"HP";s:3:"㏌";s:2:"in";s:3:"㏍";s:2:"KK";s:3:"㏎";s:2:"KM";s:3:"㏏";s:2:"kt";s:3:"㏐";s:2:"lm";s:3:"㏑";s:2:"ln";s:3:"㏒";s:3:"log";s:3:"㏓";s:2:"lx";s:3:"㏔";s:2:"mb";s:3:"㏕";s:3:"mil";s:3:"㏖";s:3:"mol";s:3:"㏗";s:2:"PH";s:3:"㏘";s:4:"p.m.";s:3:"㏙";s:3:"PPM";s:3:"㏚";s:2:"PR";s:3:"㏛";s:2:"sr";s:3:"㏜";s:2:"Sv";s:3:"㏝";s:2:"Wb";s:3:"㏞";s:5:"V∕m";s:3:"㏟";s:5:"A∕m";s:3:"㏠";s:4:"1日";s:3:"㏡";s:4:"2日";s:3:"㏢";s:4:"3日";s:3:"㏣";s:4:"4日";s:3:"㏤";s:4:"5日";s:3:"㏥";s:4:"6日";s:3:"㏦";s:4:"7日";s:3:"㏧";s:4:"8日";s:3:"㏨";s:4:"9日";s:3:"㏩";s:5:"10日";s:3:"㏪";s:5:"11日";s:3:"㏫";s:5:"12日";s:3:"㏬";s:5:"13日";s:3:"㏭";s:5:"14日";s:3:"㏮";s:5:"15日";s:3:"㏯";s:5:"16日";s:3:"㏰";s:5:"17日";s:3:"㏱";s:5:"18日";s:3:"㏲";s:5:"19日";s:3:"㏳";s:5:"20日";s:3:"㏴";s:5:"21日";s:3:"㏵";s:5:"22日";s:3:"㏶";s:5:"23日";s:3:"㏷";s:5:"24日";s:3:"㏸";s:5:"25日";s:3:"㏹";s:5:"26日";s:3:"㏺";s:5:"27日";s:3:"㏻";s:5:"28日";s:3:"㏼";s:5:"29日";s:3:"㏽";s:5:"30日";s:3:"㏾";s:5:"31日";s:3:"㏿";s:3:"gal";s:3:"ꚜ";s:2:"ъ";s:3:"ꚝ";s:2:"ь";s:3:"ꝰ";s:3:"ꝯ";s:3:"ꟸ";s:2:"Ħ";s:3:"ꟹ";s:2:"œ";s:3:"ꭜ";s:3:"ꜧ";s:3:"ꭝ";s:3:"ꬷ";s:3:"ꭞ";s:2:"ɫ";s:3:"ꭟ";s:3:"ꭒ";s:3:"ff";s:2:"ff";s:3:"fi";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:2:"st";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"մն";s:3:"ﬔ";s:4:"մե";s:3:"ﬕ";s:4:"մի";s:3:"ﬖ";s:4:"վն";s:3:"ﬗ";s:4:"մխ";s:3:"ﬠ";s:2:"ע";s:3:"ﬡ";s:2:"א";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"ה";s:3:"ﬤ";s:2:"כ";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"ם";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"ﭏ";s:4:"אל";s:3:"ﭐ";s:2:"ٱ";s:3:"ﭑ";s:2:"ٱ";s:3:"ﭒ";s:2:"ٻ";s:3:"ﭓ";s:2:"ٻ";s:3:"ﭔ";s:2:"ٻ";s:3:"ﭕ";s:2:"ٻ";s:3:"ﭖ";s:2:"پ";s:3:"ﭗ";s:2:"پ";s:3:"ﭘ";s:2:"پ";s:3:"ﭙ";s:2:"پ";s:3:"ﭚ";s:2:"ڀ";s:3:"ﭛ";s:2:"ڀ";s:3:"ﭜ";s:2:"ڀ";s:3:"ﭝ";s:2:"ڀ";s:3:"ﭞ";s:2:"ٺ";s:3:"ﭟ";s:2:"ٺ";s:3:"ﭠ";s:2:"ٺ";s:3:"ﭡ";s:2:"ٺ";s:3:"ﭢ";s:2:"ٿ";s:3:"ﭣ";s:2:"ٿ";s:3:"ﭤ";s:2:"ٿ";s:3:"ﭥ";s:2:"ٿ";s:3:"ﭦ";s:2:"ٹ";s:3:"ﭧ";s:2:"ٹ";s:3:"ﭨ";s:2:"ٹ";s:3:"ﭩ";s:2:"ٹ";s:3:"ﭪ";s:2:"ڤ";s:3:"ﭫ";s:2:"ڤ";s:3:"ﭬ";s:2:"ڤ";s:3:"ﭭ";s:2:"ڤ";s:3:"ﭮ";s:2:"ڦ";s:3:"ﭯ";s:2:"ڦ";s:3:"ﭰ";s:2:"ڦ";s:3:"ﭱ";s:2:"ڦ";s:3:"ﭲ";s:2:"ڄ";s:3:"ﭳ";s:2:"ڄ";s:3:"ﭴ";s:2:"ڄ";s:3:"ﭵ";s:2:"ڄ";s:3:"ﭶ";s:2:"ڃ";s:3:"ﭷ";s:2:"ڃ";s:3:"ﭸ";s:2:"ڃ";s:3:"ﭹ";s:2:"ڃ";s:3:"ﭺ";s:2:"چ";s:3:"ﭻ";s:2:"چ";s:3:"ﭼ";s:2:"چ";s:3:"ﭽ";s:2:"چ";s:3:"ﭾ";s:2:"ڇ";s:3:"ﭿ";s:2:"ڇ";s:3:"ﮀ";s:2:"ڇ";s:3:"ﮁ";s:2:"ڇ";s:3:"ﮂ";s:2:"ڍ";s:3:"ﮃ";s:2:"ڍ";s:3:"ﮄ";s:2:"ڌ";s:3:"ﮅ";s:2:"ڌ";s:3:"ﮆ";s:2:"ڎ";s:3:"ﮇ";s:2:"ڎ";s:3:"ﮈ";s:2:"ڈ";s:3:"ﮉ";s:2:"ڈ";s:3:"ﮊ";s:2:"ژ";s:3:"ﮋ";s:2:"ژ";s:3:"ﮌ";s:2:"ڑ";s:3:"ﮍ";s:2:"ڑ";s:3:"ﮎ";s:2:"ک";s:3:"ﮏ";s:2:"ک";s:3:"ﮐ";s:2:"ک";s:3:"ﮑ";s:2:"ک";s:3:"ﮒ";s:2:"گ";s:3:"ﮓ";s:2:"گ";s:3:"ﮔ";s:2:"گ";s:3:"ﮕ";s:2:"گ";s:3:"ﮖ";s:2:"ڳ";s:3:"ﮗ";s:2:"ڳ";s:3:"ﮘ";s:2:"ڳ";s:3:"ﮙ";s:2:"ڳ";s:3:"ﮚ";s:2:"ڱ";s:3:"ﮛ";s:2:"ڱ";s:3:"ﮜ";s:2:"ڱ";s:3:"ﮝ";s:2:"ڱ";s:3:"ﮞ";s:2:"ں";s:3:"ﮟ";s:2:"ں";s:3:"ﮠ";s:2:"ڻ";s:3:"ﮡ";s:2:"ڻ";s:3:"ﮢ";s:2:"ڻ";s:3:"ﮣ";s:2:"ڻ";s:3:"ﮤ";s:4:"ۀ";s:3:"ﮥ";s:4:"ۀ";s:3:"ﮦ";s:2:"ہ";s:3:"ﮧ";s:2:"ہ";s:3:"ﮨ";s:2:"ہ";s:3:"ﮩ";s:2:"ہ";s:3:"ﮪ";s:2:"ھ";s:3:"ﮫ";s:2:"ھ";s:3:"ﮬ";s:2:"ھ";s:3:"ﮭ";s:2:"ھ";s:3:"ﮮ";s:2:"ے";s:3:"ﮯ";s:2:"ے";s:3:"ﮰ";s:4:"ۓ";s:3:"ﮱ";s:4:"ۓ";s:3:"ﯓ";s:2:"ڭ";s:3:"ﯔ";s:2:"ڭ";s:3:"ﯕ";s:2:"ڭ";s:3:"ﯖ";s:2:"ڭ";s:3:"ﯗ";s:2:"ۇ";s:3:"ﯘ";s:2:"ۇ";s:3:"ﯙ";s:2:"ۆ";s:3:"ﯚ";s:2:"ۆ";s:3:"ﯛ";s:2:"ۈ";s:3:"ﯜ";s:2:"ۈ";s:3:"ﯝ";s:4:"ۇٴ";s:3:"ﯞ";s:2:"ۋ";s:3:"ﯟ";s:2:"ۋ";s:3:"ﯠ";s:2:"ۅ";s:3:"ﯡ";s:2:"ۅ";s:3:"ﯢ";s:2:"ۉ";s:3:"ﯣ";s:2:"ۉ";s:3:"ﯤ";s:2:"ې";s:3:"ﯥ";s:2:"ې";s:3:"ﯦ";s:2:"ې";s:3:"ﯧ";s:2:"ې";s:3:"ﯨ";s:2:"ى";s:3:"ﯩ";s:2:"ى";s:3:"ﯪ";s:6:"ئا";s:3:"ﯫ";s:6:"ئا";s:3:"ﯬ";s:6:"ئە";s:3:"ﯭ";s:6:"ئە";s:3:"ﯮ";s:6:"ئو";s:3:"ﯯ";s:6:"ئو";s:3:"ﯰ";s:6:"ئۇ";s:3:"ﯱ";s:6:"ئۇ";s:3:"ﯲ";s:6:"ئۆ";s:3:"ﯳ";s:6:"ئۆ";s:3:"ﯴ";s:6:"ئۈ";s:3:"ﯵ";s:6:"ئۈ";s:3:"ﯶ";s:6:"ئې";s:3:"ﯷ";s:6:"ئې";s:3:"ﯸ";s:6:"ئې";s:3:"ﯹ";s:6:"ئى";s:3:"ﯺ";s:6:"ئى";s:3:"ﯻ";s:6:"ئى";s:3:"ﯼ";s:2:"ی";s:3:"ﯽ";s:2:"ی";s:3:"ﯾ";s:2:"ی";s:3:"ﯿ";s:2:"ی";s:3:"ﰀ";s:6:"ئج";s:3:"ﰁ";s:6:"ئح";s:3:"ﰂ";s:6:"ئم";s:3:"ﰃ";s:6:"ئى";s:3:"ﰄ";s:6:"ئي";s:3:"ﰅ";s:4:"بج";s:3:"ﰆ";s:4:"بح";s:3:"ﰇ";s:4:"بخ";s:3:"ﰈ";s:4:"بم";s:3:"ﰉ";s:4:"بى";s:3:"ﰊ";s:4:"بي";s:3:"ﰋ";s:4:"تج";s:3:"ﰌ";s:4:"تح";s:3:"ﰍ";s:4:"تخ";s:3:"ﰎ";s:4:"تم";s:3:"ﰏ";s:4:"تى";s:3:"ﰐ";s:4:"تي";s:3:"ﰑ";s:4:"ثج";s:3:"ﰒ";s:4:"ثم";s:3:"ﰓ";s:4:"ثى";s:3:"ﰔ";s:4:"ثي";s:3:"ﰕ";s:4:"جح";s:3:"ﰖ";s:4:"جم";s:3:"ﰗ";s:4:"حج";s:3:"ﰘ";s:4:"حم";s:3:"ﰙ";s:4:"خج";s:3:"ﰚ";s:4:"خح";s:3:"ﰛ";s:4:"خم";s:3:"ﰜ";s:4:"سج";s:3:"ﰝ";s:4:"سح";s:3:"ﰞ";s:4:"سخ";s:3:"ﰟ";s:4:"سم";s:3:"ﰠ";s:4:"صح";s:3:"ﰡ";s:4:"صم";s:3:"ﰢ";s:4:"ضج";s:3:"ﰣ";s:4:"ضح";s:3:"ﰤ";s:4:"ضخ";s:3:"ﰥ";s:4:"ضم";s:3:"ﰦ";s:4:"طح";s:3:"ﰧ";s:4:"طم";s:3:"ﰨ";s:4:"ظم";s:3:"ﰩ";s:4:"عج";s:3:"ﰪ";s:4:"عم";s:3:"ﰫ";s:4:"غج";s:3:"ﰬ";s:4:"غم";s:3:"ﰭ";s:4:"فج";s:3:"ﰮ";s:4:"فح";s:3:"ﰯ";s:4:"فخ";s:3:"ﰰ";s:4:"فم";s:3:"ﰱ";s:4:"فى";s:3:"ﰲ";s:4:"في";s:3:"ﰳ";s:4:"قح";s:3:"ﰴ";s:4:"قم";s:3:"ﰵ";s:4:"قى";s:3:"ﰶ";s:4:"قي";s:3:"ﰷ";s:4:"كا";s:3:"ﰸ";s:4:"كج";s:3:"ﰹ";s:4:"كح";s:3:"ﰺ";s:4:"كخ";s:3:"ﰻ";s:4:"كل";s:3:"ﰼ";s:4:"كم";s:3:"ﰽ";s:4:"كى";s:3:"ﰾ";s:4:"كي";s:3:"ﰿ";s:4:"لج";s:3:"ﱀ";s:4:"لح";s:3:"ﱁ";s:4:"لخ";s:3:"ﱂ";s:4:"لم";s:3:"ﱃ";s:4:"لى";s:3:"ﱄ";s:4:"لي";s:3:"ﱅ";s:4:"مج";s:3:"ﱆ";s:4:"مح";s:3:"ﱇ";s:4:"مخ";s:3:"ﱈ";s:4:"مم";s:3:"ﱉ";s:4:"مى";s:3:"ﱊ";s:4:"مي";s:3:"ﱋ";s:4:"نج";s:3:"ﱌ";s:4:"نح";s:3:"ﱍ";s:4:"نخ";s:3:"ﱎ";s:4:"نم";s:3:"ﱏ";s:4:"نى";s:3:"ﱐ";s:4:"ني";s:3:"ﱑ";s:4:"هج";s:3:"ﱒ";s:4:"هم";s:3:"ﱓ";s:4:"هى";s:3:"ﱔ";s:4:"هي";s:3:"ﱕ";s:4:"يج";s:3:"ﱖ";s:4:"يح";s:3:"ﱗ";s:4:"يخ";s:3:"ﱘ";s:4:"يم";s:3:"ﱙ";s:4:"يى";s:3:"ﱚ";s:4:"يي";s:3:"ﱛ";s:4:"ذٰ";s:3:"ﱜ";s:4:"رٰ";s:3:"ﱝ";s:4:"ىٰ";s:3:"ﱞ";s:5:" ٌّ";s:3:"ﱟ";s:5:" ٍّ";s:3:"ﱠ";s:5:" َّ";s:3:"ﱡ";s:5:" ُّ";s:3:"ﱢ";s:5:" ِّ";s:3:"ﱣ";s:5:" ّٰ";s:3:"ﱤ";s:6:"ئر";s:3:"ﱥ";s:6:"ئز";s:3:"ﱦ";s:6:"ئم";s:3:"ﱧ";s:6:"ئن";s:3:"ﱨ";s:6:"ئى";s:3:"ﱩ";s:6:"ئي";s:3:"ﱪ";s:4:"بر";s:3:"ﱫ";s:4:"بز";s:3:"ﱬ";s:4:"بم";s:3:"ﱭ";s:4:"بن";s:3:"ﱮ";s:4:"بى";s:3:"ﱯ";s:4:"بي";s:3:"ﱰ";s:4:"تر";s:3:"ﱱ";s:4:"تز";s:3:"ﱲ";s:4:"تم";s:3:"ﱳ";s:4:"تن";s:3:"ﱴ";s:4:"تى";s:3:"ﱵ";s:4:"تي";s:3:"ﱶ";s:4:"ثر";s:3:"ﱷ";s:4:"ثز";s:3:"ﱸ";s:4:"ثم";s:3:"ﱹ";s:4:"ثن";s:3:"ﱺ";s:4:"ثى";s:3:"ﱻ";s:4:"ثي";s:3:"ﱼ";s:4:"فى";s:3:"ﱽ";s:4:"في";s:3:"ﱾ";s:4:"قى";s:3:"ﱿ";s:4:"قي";s:3:"ﲀ";s:4:"كا";s:3:"ﲁ";s:4:"كل";s:3:"ﲂ";s:4:"كم";s:3:"ﲃ";s:4:"كى";s:3:"ﲄ";s:4:"كي";s:3:"ﲅ";s:4:"لم";s:3:"ﲆ";s:4:"لى";s:3:"ﲇ";s:4:"لي";s:3:"ﲈ";s:4:"ما";s:3:"ﲉ";s:4:"مم";s:3:"ﲊ";s:4:"نر";s:3:"ﲋ";s:4:"نز";s:3:"ﲌ";s:4:"نم";s:3:"ﲍ";s:4:"نن";s:3:"ﲎ";s:4:"نى";s:3:"ﲏ";s:4:"ني";s:3:"ﲐ";s:4:"ىٰ";s:3:"ﲑ";s:4:"ير";s:3:"ﲒ";s:4:"يز";s:3:"ﲓ";s:4:"يم";s:3:"ﲔ";s:4:"ين";s:3:"ﲕ";s:4:"يى";s:3:"ﲖ";s:4:"يي";s:3:"ﲗ";s:6:"ئج";s:3:"ﲘ";s:6:"ئح";s:3:"ﲙ";s:6:"ئخ";s:3:"ﲚ";s:6:"ئم";s:3:"ﲛ";s:6:"ئه";s:3:"ﲜ";s:4:"بج";s:3:"ﲝ";s:4:"بح";s:3:"ﲞ";s:4:"بخ";s:3:"ﲟ";s:4:"بم";s:3:"ﲠ";s:4:"به";s:3:"ﲡ";s:4:"تج";s:3:"ﲢ";s:4:"تح";s:3:"ﲣ";s:4:"تخ";s:3:"ﲤ";s:4:"تم";s:3:"ﲥ";s:4:"ته";s:3:"ﲦ";s:4:"ثم";s:3:"ﲧ";s:4:"جح";s:3:"ﲨ";s:4:"جم";s:3:"ﲩ";s:4:"حج";s:3:"ﲪ";s:4:"حم";s:3:"ﲫ";s:4:"خج";s:3:"ﲬ";s:4:"خم";s:3:"ﲭ";s:4:"سج";s:3:"ﲮ";s:4:"سح";s:3:"ﲯ";s:4:"سخ";s:3:"ﲰ";s:4:"سم";s:3:"ﲱ";s:4:"صح";s:3:"ﲲ";s:4:"صخ";s:3:"ﲳ";s:4:"صم";s:3:"ﲴ";s:4:"ضج";s:3:"ﲵ";s:4:"ضح";s:3:"ﲶ";s:4:"ضخ";s:3:"ﲷ";s:4:"ضم";s:3:"ﲸ";s:4:"طح";s:3:"ﲹ";s:4:"ظم";s:3:"ﲺ";s:4:"عج";s:3:"ﲻ";s:4:"عم";s:3:"ﲼ";s:4:"غج";s:3:"ﲽ";s:4:"غم";s:3:"ﲾ";s:4:"فج";s:3:"ﲿ";s:4:"فح";s:3:"ﳀ";s:4:"فخ";s:3:"ﳁ";s:4:"فم";s:3:"ﳂ";s:4:"قح";s:3:"ﳃ";s:4:"قم";s:3:"ﳄ";s:4:"كج";s:3:"ﳅ";s:4:"كح";s:3:"ﳆ";s:4:"كخ";s:3:"ﳇ";s:4:"كل";s:3:"ﳈ";s:4:"كم";s:3:"ﳉ";s:4:"لج";s:3:"ﳊ";s:4:"لح";s:3:"ﳋ";s:4:"لخ";s:3:"ﳌ";s:4:"لم";s:3:"ﳍ";s:4:"له";s:3:"ﳎ";s:4:"مج";s:3:"ﳏ";s:4:"مح";s:3:"ﳐ";s:4:"مخ";s:3:"ﳑ";s:4:"مم";s:3:"ﳒ";s:4:"نج";s:3:"ﳓ";s:4:"نح";s:3:"ﳔ";s:4:"نخ";s:3:"ﳕ";s:4:"نم";s:3:"ﳖ";s:4:"نه";s:3:"ﳗ";s:4:"هج";s:3:"ﳘ";s:4:"هم";s:3:"ﳙ";s:4:"هٰ";s:3:"ﳚ";s:4:"يج";s:3:"ﳛ";s:4:"يح";s:3:"ﳜ";s:4:"يخ";s:3:"ﳝ";s:4:"يم";s:3:"ﳞ";s:4:"يه";s:3:"ﳟ";s:6:"ئم";s:3:"ﳠ";s:6:"ئه";s:3:"ﳡ";s:4:"بم";s:3:"ﳢ";s:4:"به";s:3:"ﳣ";s:4:"تم";s:3:"ﳤ";s:4:"ته";s:3:"ﳥ";s:4:"ثم";s:3:"ﳦ";s:4:"ثه";s:3:"ﳧ";s:4:"سم";s:3:"ﳨ";s:4:"سه";s:3:"ﳩ";s:4:"شم";s:3:"ﳪ";s:4:"شه";s:3:"ﳫ";s:4:"كل";s:3:"ﳬ";s:4:"كم";s:3:"ﳭ";s:4:"لم";s:3:"ﳮ";s:4:"نم";s:3:"ﳯ";s:4:"نه";s:3:"ﳰ";s:4:"يم";s:3:"ﳱ";s:4:"يه";s:3:"ﳲ";s:6:"ـَّ";s:3:"ﳳ";s:6:"ـُّ";s:3:"ﳴ";s:6:"ـِّ";s:3:"ﳵ";s:4:"طى";s:3:"ﳶ";s:4:"طي";s:3:"ﳷ";s:4:"عى";s:3:"ﳸ";s:4:"عي";s:3:"ﳹ";s:4:"غى";s:3:"ﳺ";s:4:"غي";s:3:"ﳻ";s:4:"سى";s:3:"ﳼ";s:4:"سي";s:3:"ﳽ";s:4:"شى";s:3:"ﳾ";s:4:"شي";s:3:"ﳿ";s:4:"حى";s:3:"ﴀ";s:4:"حي";s:3:"ﴁ";s:4:"جى";s:3:"ﴂ";s:4:"جي";s:3:"ﴃ";s:4:"خى";s:3:"ﴄ";s:4:"خي";s:3:"ﴅ";s:4:"صى";s:3:"ﴆ";s:4:"صي";s:3:"ﴇ";s:4:"ضى";s:3:"ﴈ";s:4:"ضي";s:3:"ﴉ";s:4:"شج";s:3:"ﴊ";s:4:"شح";s:3:"ﴋ";s:4:"شخ";s:3:"ﴌ";s:4:"شم";s:3:"ﴍ";s:4:"شر";s:3:"ﴎ";s:4:"سر";s:3:"ﴏ";s:4:"صر";s:3:"ﴐ";s:4:"ضر";s:3:"ﴑ";s:4:"طى";s:3:"ﴒ";s:4:"طي";s:3:"ﴓ";s:4:"عى";s:3:"ﴔ";s:4:"عي";s:3:"ﴕ";s:4:"غى";s:3:"ﴖ";s:4:"غي";s:3:"ﴗ";s:4:"سى";s:3:"ﴘ";s:4:"سي";s:3:"ﴙ";s:4:"شى";s:3:"ﴚ";s:4:"شي";s:3:"ﴛ";s:4:"حى";s:3:"ﴜ";s:4:"حي";s:3:"ﴝ";s:4:"جى";s:3:"ﴞ";s:4:"جي";s:3:"ﴟ";s:4:"خى";s:3:"ﴠ";s:4:"خي";s:3:"ﴡ";s:4:"صى";s:3:"ﴢ";s:4:"صي";s:3:"ﴣ";s:4:"ضى";s:3:"ﴤ";s:4:"ضي";s:3:"ﴥ";s:4:"شج";s:3:"ﴦ";s:4:"شح";s:3:"ﴧ";s:4:"شخ";s:3:"ﴨ";s:4:"شم";s:3:"ﴩ";s:4:"شر";s:3:"ﴪ";s:4:"سر";s:3:"ﴫ";s:4:"صر";s:3:"ﴬ";s:4:"ضر";s:3:"ﴭ";s:4:"شج";s:3:"ﴮ";s:4:"شح";s:3:"ﴯ";s:4:"شخ";s:3:"ﴰ";s:4:"شم";s:3:"ﴱ";s:4:"سه";s:3:"ﴲ";s:4:"شه";s:3:"ﴳ";s:4:"طم";s:3:"ﴴ";s:4:"سج";s:3:"ﴵ";s:4:"سح";s:3:"ﴶ";s:4:"سخ";s:3:"ﴷ";s:4:"شج";s:3:"ﴸ";s:4:"شح";s:3:"ﴹ";s:4:"شخ";s:3:"ﴺ";s:4:"طم";s:3:"ﴻ";s:4:"ظم";s:3:"ﴼ";s:4:"اً";s:3:"ﴽ";s:4:"اً";s:3:"ﵐ";s:6:"تجم";s:3:"ﵑ";s:6:"تحج";s:3:"ﵒ";s:6:"تحج";s:3:"ﵓ";s:6:"تحم";s:3:"ﵔ";s:6:"تخم";s:3:"ﵕ";s:6:"تمج";s:3:"ﵖ";s:6:"تمح";s:3:"ﵗ";s:6:"تمخ";s:3:"ﵘ";s:6:"جمح";s:3:"ﵙ";s:6:"جمح";s:3:"ﵚ";s:6:"حمي";s:3:"ﵛ";s:6:"حمى";s:3:"ﵜ";s:6:"سحج";s:3:"ﵝ";s:6:"سجح";s:3:"ﵞ";s:6:"سجى";s:3:"ﵟ";s:6:"سمح";s:3:"ﵠ";s:6:"سمح";s:3:"ﵡ";s:6:"سمج";s:3:"ﵢ";s:6:"سمم";s:3:"ﵣ";s:6:"سمم";s:3:"ﵤ";s:6:"صحح";s:3:"ﵥ";s:6:"صحح";s:3:"ﵦ";s:6:"صمم";s:3:"ﵧ";s:6:"شحم";s:3:"ﵨ";s:6:"شحم";s:3:"ﵩ";s:6:"شجي";s:3:"ﵪ";s:6:"شمخ";s:3:"ﵫ";s:6:"شمخ";s:3:"ﵬ";s:6:"شمم";s:3:"ﵭ";s:6:"شمم";s:3:"ﵮ";s:6:"ضحى";s:3:"ﵯ";s:6:"ضخم";s:3:"ﵰ";s:6:"ضخم";s:3:"ﵱ";s:6:"طمح";s:3:"ﵲ";s:6:"طمح";s:3:"ﵳ";s:6:"طمم";s:3:"ﵴ";s:6:"طمي";s:3:"ﵵ";s:6:"عجم";s:3:"ﵶ";s:6:"عمم";s:3:"ﵷ";s:6:"عمم";s:3:"ﵸ";s:6:"عمى";s:3:"ﵹ";s:6:"غمم";s:3:"ﵺ";s:6:"غمي";s:3:"ﵻ";s:6:"غمى";s:3:"ﵼ";s:6:"فخم";s:3:"ﵽ";s:6:"فخم";s:3:"ﵾ";s:6:"قمح";s:3:"ﵿ";s:6:"قمم";s:3:"ﶀ";s:6:"لحم";s:3:"ﶁ";s:6:"لحي";s:3:"ﶂ";s:6:"لحى";s:3:"ﶃ";s:6:"لجج";s:3:"ﶄ";s:6:"لجج";s:3:"ﶅ";s:6:"لخم";s:3:"ﶆ";s:6:"لخم";s:3:"ﶇ";s:6:"لمح";s:3:"ﶈ";s:6:"لمح";s:3:"ﶉ";s:6:"محج";s:3:"ﶊ";s:6:"محم";s:3:"ﶋ";s:6:"محي";s:3:"ﶌ";s:6:"مجح";s:3:"ﶍ";s:6:"مجم";s:3:"ﶎ";s:6:"مخج";s:3:"ﶏ";s:6:"مخم";s:3:"ﶒ";s:6:"مجخ";s:3:"ﶓ";s:6:"همج";s:3:"ﶔ";s:6:"همم";s:3:"ﶕ";s:6:"نحم";s:3:"ﶖ";s:6:"نحى";s:3:"ﶗ";s:6:"نجم";s:3:"ﶘ";s:6:"نجم";s:3:"ﶙ";s:6:"نجى";s:3:"ﶚ";s:6:"نمي";s:3:"ﶛ";s:6:"نمى";s:3:"ﶜ";s:6:"يمم";s:3:"ﶝ";s:6:"يمم";s:3:"ﶞ";s:6:"بخي";s:3:"ﶟ";s:6:"تجي";s:3:"ﶠ";s:6:"تجى";s:3:"ﶡ";s:6:"تخي";s:3:"ﶢ";s:6:"تخى";s:3:"ﶣ";s:6:"تمي";s:3:"ﶤ";s:6:"تمى";s:3:"ﶥ";s:6:"جمي";s:3:"ﶦ";s:6:"جحى";s:3:"ﶧ";s:6:"جمى";s:3:"ﶨ";s:6:"سخى";s:3:"ﶩ";s:6:"صحي";s:3:"ﶪ";s:6:"شحي";s:3:"ﶫ";s:6:"ضحي";s:3:"ﶬ";s:6:"لجي";s:3:"ﶭ";s:6:"لمي";s:3:"ﶮ";s:6:"يحي";s:3:"ﶯ";s:6:"يجي";s:3:"ﶰ";s:6:"يمي";s:3:"ﶱ";s:6:"ممي";s:3:"ﶲ";s:6:"قمي";s:3:"ﶳ";s:6:"نحي";s:3:"ﶴ";s:6:"قمح";s:3:"ﶵ";s:6:"لحم";s:3:"ﶶ";s:6:"عمي";s:3:"ﶷ";s:6:"كمي";s:3:"ﶸ";s:6:"نجح";s:3:"ﶹ";s:6:"مخي";s:3:"ﶺ";s:6:"لجم";s:3:"ﶻ";s:6:"كمم";s:3:"ﶼ";s:6:"لجم";s:3:"ﶽ";s:6:"نجح";s:3:"ﶾ";s:6:"جحي";s:3:"ﶿ";s:6:"حجي";s:3:"ﷀ";s:6:"مجي";s:3:"ﷁ";s:6:"فمي";s:3:"ﷂ";s:6:"بحي";s:3:"ﷃ";s:6:"كمم";s:3:"ﷄ";s:6:"عجم";s:3:"ﷅ";s:6:"صمم";s:3:"ﷆ";s:6:"سخي";s:3:"ﷇ";s:6:"نجي";s:3:"ﷰ";s:6:"صلے";s:3:"ﷱ";s:6:"قلے";s:3:"ﷲ";s:8:"الله";s:3:"ﷳ";s:8:"اكبر";s:3:"ﷴ";s:8:"محمد";s:3:"ﷵ";s:8:"صلعم";s:3:"ﷶ";s:8:"رسول";s:3:"ﷷ";s:8:"عليه";s:3:"ﷸ";s:8:"وسلم";s:3:"ﷹ";s:6:"صلى";s:3:"ﷺ";s:33:"صلى الله عليه وسلم";s:3:"ﷻ";s:15:"جل جلاله";s:3:"﷼";s:8:"ریال";s:3:"︐";s:1:",";s:3:"︑";s:3:"、";s:3:"︒";s:3:"。";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︗";s:3:"〖";s:3:"︘";s:3:"〗";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︱";s:3:"—";s:3:"︲";s:3:"–";s:3:"︳";s:1:"_";s:3:"︴";s:1:"_";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:3:"〔";s:3:"︺";s:3:"〕";s:3:"︻";s:3:"【";s:3:"︼";s:3:"】";s:3:"︽";s:3:"《";s:3:"︾";s:3:"》";s:3:"︿";s:3:"〈";s:3:"﹀";s:3:"〉";s:3:"﹁";s:3:"「";s:3:"﹂";s:3:"」";s:3:"﹃";s:3:"『";s:3:"﹄";s:3:"』";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:3:"﹉";s:3:" ̅";s:3:"﹊";s:3:" ̅";s:3:"﹋";s:3:" ̅";s:3:"﹌";s:3:" ̅";s:3:"﹍";s:1:"_";s:3:"﹎";s:1:"_";s:3:"﹏";s:1:"_";s:3:"﹐";s:1:",";s:3:"﹑";s:3:"、";s:3:"﹒";s:1:".";s:3:"﹔";s:1:";";s:3:"﹕";s:1:":";s:3:"﹖";s:1:"?";s:3:"﹗";s:1:"!";s:3:"﹘";s:3:"—";s:3:"﹙";s:1:"(";s:3:"﹚";s:1:")";s:3:"﹛";s:1:"{";s:3:"﹜";s:1:"}";s:3:"﹝";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"﹠";s:1:"&";s:3:"﹡";s:1:"*";s:3:"﹢";s:1:"+";s:3:"﹣";s:1:"-";s:3:"﹤";s:1:"<";s:3:"﹥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"ﹰ";s:3:" ً";s:3:"ﹱ";s:4:"ـً";s:3:"ﹲ";s:3:" ٌ";s:3:"ﹴ";s:3:" ٍ";s:3:"ﹶ";s:3:" َ";s:3:"ﹷ";s:4:"ـَ";s:3:"ﹸ";s:3:" ُ";s:3:"ﹹ";s:4:"ـُ";s:3:"ﹺ";s:3:" ِ";s:3:"ﹻ";s:4:"ـِ";s:3:"ﹼ";s:3:" ّ";s:3:"ﹽ";s:4:"ـّ";s:3:"ﹾ";s:3:" ْ";s:3:"ﹿ";s:4:"ـْ";s:3:"ﺀ";s:2:"ء";s:3:"ﺁ";s:4:"آ";s:3:"ﺂ";s:4:"آ";s:3:"ﺃ";s:4:"أ";s:3:"ﺄ";s:4:"أ";s:3:"ﺅ";s:4:"ؤ";s:3:"ﺆ";s:4:"ؤ";s:3:"ﺇ";s:4:"إ";s:3:"ﺈ";s:4:"إ";s:3:"ﺉ";s:4:"ئ";s:3:"ﺊ";s:4:"ئ";s:3:"ﺋ";s:4:"ئ";s:3:"ﺌ";s:4:"ئ";s:3:"ﺍ";s:2:"ا";s:3:"ﺎ";s:2:"ا";s:3:"ﺏ";s:2:"ب";s:3:"ﺐ";s:2:"ب";s:3:"ﺑ";s:2:"ب";s:3:"ﺒ";s:2:"ب";s:3:"ﺓ";s:2:"ة";s:3:"ﺔ";s:2:"ة";s:3:"ﺕ";s:2:"ت";s:3:"ﺖ";s:2:"ت";s:3:"ﺗ";s:2:"ت";s:3:"ﺘ";s:2:"ت";s:3:"ﺙ";s:2:"ث";s:3:"ﺚ";s:2:"ث";s:3:"ﺛ";s:2:"ث";s:3:"ﺜ";s:2:"ث";s:3:"ﺝ";s:2:"ج";s:3:"ﺞ";s:2:"ج";s:3:"ﺟ";s:2:"ج";s:3:"ﺠ";s:2:"ج";s:3:"ﺡ";s:2:"ح";s:3:"ﺢ";s:2:"ح";s:3:"ﺣ";s:2:"ح";s:3:"ﺤ";s:2:"ح";s:3:"ﺥ";s:2:"خ";s:3:"ﺦ";s:2:"خ";s:3:"ﺧ";s:2:"خ";s:3:"ﺨ";s:2:"خ";s:3:"ﺩ";s:2:"د";s:3:"ﺪ";s:2:"د";s:3:"ﺫ";s:2:"ذ";s:3:"ﺬ";s:2:"ذ";s:3:"ﺭ";s:2:"ر";s:3:"ﺮ";s:2:"ر";s:3:"ﺯ";s:2:"ز";s:3:"ﺰ";s:2:"ز";s:3:"ﺱ";s:2:"س";s:3:"ﺲ";s:2:"س";s:3:"ﺳ";s:2:"س";s:3:"ﺴ";s:2:"س";s:3:"ﺵ";s:2:"ش";s:3:"ﺶ";s:2:"ش";s:3:"ﺷ";s:2:"ش";s:3:"ﺸ";s:2:"ش";s:3:"ﺹ";s:2:"ص";s:3:"ﺺ";s:2:"ص";s:3:"ﺻ";s:2:"ص";s:3:"ﺼ";s:2:"ص";s:3:"ﺽ";s:2:"ض";s:3:"ﺾ";s:2:"ض";s:3:"ﺿ";s:2:"ض";s:3:"ﻀ";s:2:"ض";s:3:"ﻁ";s:2:"ط";s:3:"ﻂ";s:2:"ط";s:3:"ﻃ";s:2:"ط";s:3:"ﻄ";s:2:"ط";s:3:"ﻅ";s:2:"ظ";s:3:"ﻆ";s:2:"ظ";s:3:"ﻇ";s:2:"ظ";s:3:"ﻈ";s:2:"ظ";s:3:"ﻉ";s:2:"ع";s:3:"ﻊ";s:2:"ع";s:3:"ﻋ";s:2:"ع";s:3:"ﻌ";s:2:"ع";s:3:"ﻍ";s:2:"غ";s:3:"ﻎ";s:2:"غ";s:3:"ﻏ";s:2:"غ";s:3:"ﻐ";s:2:"غ";s:3:"ﻑ";s:2:"ف";s:3:"ﻒ";s:2:"ف";s:3:"ﻓ";s:2:"ف";s:3:"ﻔ";s:2:"ف";s:3:"ﻕ";s:2:"ق";s:3:"ﻖ";s:2:"ق";s:3:"ﻗ";s:2:"ق";s:3:"ﻘ";s:2:"ق";s:3:"ﻙ";s:2:"ك";s:3:"ﻚ";s:2:"ك";s:3:"ﻛ";s:2:"ك";s:3:"ﻜ";s:2:"ك";s:3:"ﻝ";s:2:"ل";s:3:"ﻞ";s:2:"ل";s:3:"ﻟ";s:2:"ل";s:3:"ﻠ";s:2:"ل";s:3:"ﻡ";s:2:"م";s:3:"ﻢ";s:2:"م";s:3:"ﻣ";s:2:"م";s:3:"ﻤ";s:2:"م";s:3:"ﻥ";s:2:"ن";s:3:"ﻦ";s:2:"ن";s:3:"ﻧ";s:2:"ن";s:3:"ﻨ";s:2:"ن";s:3:"ﻩ";s:2:"ه";s:3:"ﻪ";s:2:"ه";s:3:"ﻫ";s:2:"ه";s:3:"ﻬ";s:2:"ه";s:3:"ﻭ";s:2:"و";s:3:"ﻮ";s:2:"و";s:3:"ﻯ";s:2:"ى";s:3:"ﻰ";s:2:"ى";s:3:"ﻱ";s:2:"ي";s:3:"ﻲ";s:2:"ي";s:3:"ﻳ";s:2:"ي";s:3:"ﻴ";s:2:"ي";s:3:"ﻵ";s:6:"لآ";s:3:"ﻶ";s:6:"لآ";s:3:"ﻷ";s:6:"لأ";s:3:"ﻸ";s:6:"لأ";s:3:"ﻹ";s:6:"لإ";s:3:"ﻺ";s:6:"لإ";s:3:"ﻻ";s:4:"لا";s:3:"ﻼ";s:4:"لا";s:3:"!";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"%";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"-";s:1:"-";s:3:".";s:1:".";s:3:"/";s:1:"/";s:3:"0";s:1:"0";s:3:"1";s:1:"1";s:3:"2";s:1:"2";s:3:"3";s:1:"3";s:3:"4";s:1:"4";s:3:"5";s:1:"5";s:3:"6";s:1:"6";s:3:"7";s:1:"7";s:3:"8";s:1:"8";s:3:"9";s:1:"9";s:3:":";s:1:":";s:3:";";s:1:";";s:3:"<";s:1:"<";s:3:"=";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"@";s:1:"@";s:3:"A";s:1:"A";s:3:"B";s:1:"B";s:3:"C";s:1:"C";s:3:"D";s:1:"D";s:3:"E";s:1:"E";s:3:"F";s:1:"F";s:3:"G";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"M";s:1:"M";s:3:"N";s:1:"N";s:3:"O";s:1:"O";s:3:"P";s:1:"P";s:3:"Q";s:1:"Q";s:3:"R";s:1:"R";s:3:"S";s:1:"S";s:3:"T";s:1:"T";s:3:"U";s:1:"U";s:3:"V";s:1:"V";s:3:"W";s:1:"W";s:3:"X";s:1:"X";s:3:"Y";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"[";s:1:"[";s:3:"\";s:1:"\";s:3:"]";s:1:"]";s:3:"^";s:1:"^";s:3:"_";s:1:"_";s:3:"`";s:1:"`";s:3:"a";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"e";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"m";s:1:"m";s:3:"n";s:1:"n";s:3:"o";s:1:"o";s:3:"p";s:1:"p";s:3:"q";s:1:"q";s:3:"r";s:1:"r";s:3:"s";s:1:"s";s:3:"t";s:1:"t";s:3:"u";s:1:"u";s:3:"v";s:1:"v";s:3:"w";s:1:"w";s:3:"x";s:1:"x";s:3:"y";s:1:"y";s:3:"z";s:1:"z";s:3:"{";s:1:"{";s:3:"|";s:1:"|";s:3:"}";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"⦆";s:3:"⦆";s:3:"。";s:3:"。";s:3:"「";s:3:"「";s:3:"」";s:3:"」";s:3:"、";s:3:"、";s:3:"・";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ァ";s:3:"ァ";s:3:"ィ";s:3:"ィ";s:3:"ゥ";s:3:"ゥ";s:3:"ェ";s:3:"ェ";s:3:"ォ";s:3:"ォ";s:3:"ャ";s:3:"ャ";s:3:"ュ";s:3:"ュ";s:3:"ョ";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ー";s:3:"ー";s:3:"ア";s:3:"ア";s:3:"イ";s:3:"イ";s:3:"ウ";s:3:"ウ";s:3:"エ";s:3:"エ";s:3:"オ";s:3:"オ";s:3:"カ";s:3:"カ";s:3:"キ";s:3:"キ";s:3:"ク";s:3:"ク";s:3:"ケ";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"サ";s:3:"サ";s:3:"シ";s:3:"シ";s:3:"ス";s:3:"ス";s:3:"セ";s:3:"セ";s:3:"ソ";s:3:"ソ";s:3:"タ";s:3:"タ";s:3:"チ";s:3:"チ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ナ";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ネ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ハ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ヘ";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"マ";s:3:"マ";s:3:"ミ";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"メ";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ヤ";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ヨ";s:3:"ヨ";s:3:"ラ";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ル";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ロ";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ン";s:3:"ン";s:3:"゙";s:3:"゙";s:3:"゚";s:3:"゚";s:3:"ᅠ";s:3:"ᅠ";s:3:"ᄀ";s:3:"ᄀ";s:3:"ᄁ";s:3:"ᄁ";s:3:"ᆪ";s:3:"ᆪ";s:3:"ᄂ";s:3:"ᄂ";s:3:"ᆬ";s:3:"ᆬ";s:3:"ᆭ";s:3:"ᆭ";s:3:"ᄃ";s:3:"ᄃ";s:3:"ᄄ";s:3:"ᄄ";s:3:"ᄅ";s:3:"ᄅ";s:3:"ᆰ";s:3:"ᆰ";s:3:"ᆱ";s:3:"ᆱ";s:3:"ᆲ";s:3:"ᆲ";s:3:"ᆳ";s:3:"ᆳ";s:3:"ᆴ";s:3:"ᆴ";s:3:"ᆵ";s:3:"ᆵ";s:3:"ᄚ";s:3:"ᄚ";s:3:"ᄆ";s:3:"ᄆ";s:3:"ᄇ";s:3:"ᄇ";s:3:"ᄈ";s:3:"ᄈ";s:3:"ᄡ";s:3:"ᄡ";s:3:"ᄉ";s:3:"ᄉ";s:3:"ᄊ";s:3:"ᄊ";s:3:"ᄋ";s:3:"ᄋ";s:3:"ᄌ";s:3:"ᄌ";s:3:"ᄍ";s:3:"ᄍ";s:3:"ᄎ";s:3:"ᄎ";s:3:"ᄏ";s:3:"ᄏ";s:3:"ᄐ";s:3:"ᄐ";s:3:"ᄑ";s:3:"ᄑ";s:3:"ᄒ";s:3:"ᄒ";s:3:"ᅡ";s:3:"ᅡ";s:3:"ᅢ";s:3:"ᅢ";s:3:"ᅣ";s:3:"ᅣ";s:3:"ᅤ";s:3:"ᅤ";s:3:"ᅥ";s:3:"ᅥ";s:3:"ᅦ";s:3:"ᅦ";s:3:"ᅧ";s:3:"ᅧ";s:3:"ᅨ";s:3:"ᅨ";s:3:"ᅩ";s:3:"ᅩ";s:3:"ᅪ";s:3:"ᅪ";s:3:"ᅫ";s:3:"ᅫ";s:3:"ᅬ";s:3:"ᅬ";s:3:"ᅭ";s:3:"ᅭ";s:3:"ᅮ";s:3:"ᅮ";s:3:"ᅯ";s:3:"ᅯ";s:3:"ᅰ";s:3:"ᅰ";s:3:"ᅱ";s:3:"ᅱ";s:3:"ᅲ";s:3:"ᅲ";s:3:"ᅳ";s:3:"ᅳ";s:3:"ᅴ";s:3:"ᅴ";s:3:"ᅵ";s:3:"ᅵ";s:3:"¢";s:2:"¢";s:3:"£";s:2:"£";s:3:"¬";s:2:"¬";s:3:" ̄";s:3:" ̄";s:3:"¦";s:2:"¦";s:3:"¥";s:2:"¥";s:3:"₩";s:3:"₩";s:3:"│";s:3:"│";s:3:"←";s:3:"←";s:3:"↑";s:3:"↑";s:3:"→";s:3:"→";s:3:"↓";s:3:"↓";s:3:"■";s:3:"■";s:3:"○";s:3:"○";s:4:"𝐀";s:1:"A";s:4:"𝐁";s:1:"B";s:4:"𝐂";s:1:"C";s:4:"𝐃";s:1:"D";s:4:"𝐄";s:1:"E";s:4:"𝐅";s:1:"F";s:4:"𝐆";s:1:"G";s:4:"𝐇";s:1:"H";s:4:"𝐈";s:1:"I";s:4:"𝐉";s:1:"J";s:4:"𝐊";s:1:"K";s:4:"𝐋";s:1:"L";s:4:"𝐌";s:1:"M";s:4:"𝐍";s:1:"N";s:4:"𝐎";s:1:"O";s:4:"𝐏";s:1:"P";s:4:"𝐐";s:1:"Q";s:4:"𝐑";s:1:"R";s:4:"𝐒";s:1:"S";s:4:"𝐓";s:1:"T";s:4:"𝐔";s:1:"U";s:4:"𝐕";s:1:"V";s:4:"𝐖";s:1:"W";s:4:"𝐗";s:1:"X";s:4:"𝐘";s:1:"Y";s:4:"𝐙";s:1:"Z";s:4:"𝐚";s:1:"a";s:4:"𝐛";s:1:"b";s:4:"𝐜";s:1:"c";s:4:"𝐝";s:1:"d";s:4:"𝐞";s:1:"e";s:4:"𝐟";s:1:"f";s:4:"𝐠";s:1:"g";s:4:"𝐡";s:1:"h";s:4:"𝐢";s:1:"i";s:4:"𝐣";s:1:"j";s:4:"𝐤";s:1:"k";s:4:"𝐥";s:1:"l";s:4:"𝐦";s:1:"m";s:4:"𝐧";s:1:"n";s:4:"𝐨";s:1:"o";s:4:"𝐩";s:1:"p";s:4:"𝐪";s:1:"q";s:4:"𝐫";s:1:"r";s:4:"𝐬";s:1:"s";s:4:"𝐭";s:1:"t";s:4:"𝐮";s:1:"u";s:4:"𝐯";s:1:"v";s:4:"𝐰";s:1:"w";s:4:"𝐱";s:1:"x";s:4:"𝐲";s:1:"y";s:4:"𝐳";s:1:"z";s:4:"𝐴";s:1:"A";s:4:"𝐵";s:1:"B";s:4:"𝐶";s:1:"C";s:4:"𝐷";s:1:"D";s:4:"𝐸";s:1:"E";s:4:"𝐹";s:1:"F";s:4:"𝐺";s:1:"G";s:4:"𝐻";s:1:"H";s:4:"𝐼";s:1:"I";s:4:"𝐽";s:1:"J";s:4:"𝐾";s:1:"K";s:4:"𝐿";s:1:"L";s:4:"𝑀";s:1:"M";s:4:"𝑁";s:1:"N";s:4:"𝑂";s:1:"O";s:4:"𝑃";s:1:"P";s:4:"𝑄";s:1:"Q";s:4:"𝑅";s:1:"R";s:4:"𝑆";s:1:"S";s:4:"𝑇";s:1:"T";s:4:"𝑈";s:1:"U";s:4:"𝑉";s:1:"V";s:4:"𝑊";s:1:"W";s:4:"𝑋";s:1:"X";s:4:"𝑌";s:1:"Y";s:4:"𝑍";s:1:"Z";s:4:"𝑎";s:1:"a";s:4:"𝑏";s:1:"b";s:4:"𝑐";s:1:"c";s:4:"𝑑";s:1:"d";s:4:"𝑒";s:1:"e";s:4:"𝑓";s:1:"f";s:4:"𝑔";s:1:"g";s:4:"𝑖";s:1:"i";s:4:"𝑗";s:1:"j";s:4:"𝑘";s:1:"k";s:4:"𝑙";s:1:"l";s:4:"𝑚";s:1:"m";s:4:"𝑛";s:1:"n";s:4:"𝑜";s:1:"o";s:4:"𝑝";s:1:"p";s:4:"𝑞";s:1:"q";s:4:"𝑟";s:1:"r";s:4:"𝑠";s:1:"s";s:4:"𝑡";s:1:"t";s:4:"𝑢";s:1:"u";s:4:"𝑣";s:1:"v";s:4:"𝑤";s:1:"w";s:4:"𝑥";s:1:"x";s:4:"𝑦";s:1:"y";s:4:"𝑧";s:1:"z";s:4:"𝑨";s:1:"A";s:4:"𝑩";s:1:"B";s:4:"𝑪";s:1:"C";s:4:"𝑫";s:1:"D";s:4:"𝑬";s:1:"E";s:4:"𝑭";s:1:"F";s:4:"𝑮";s:1:"G";s:4:"𝑯";s:1:"H";s:4:"𝑰";s:1:"I";s:4:"𝑱";s:1:"J";s:4:"𝑲";s:1:"K";s:4:"𝑳";s:1:"L";s:4:"𝑴";s:1:"M";s:4:"𝑵";s:1:"N";s:4:"𝑶";s:1:"O";s:4:"𝑷";s:1:"P";s:4:"𝑸";s:1:"Q";s:4:"𝑹";s:1:"R";s:4:"𝑺";s:1:"S";s:4:"𝑻";s:1:"T";s:4:"𝑼";s:1:"U";s:4:"𝑽";s:1:"V";s:4:"𝑾";s:1:"W";s:4:"𝑿";s:1:"X";s:4:"𝒀";s:1:"Y";s:4:"𝒁";s:1:"Z";s:4:"𝒂";s:1:"a";s:4:"𝒃";s:1:"b";s:4:"𝒄";s:1:"c";s:4:"𝒅";s:1:"d";s:4:"𝒆";s:1:"e";s:4:"𝒇";s:1:"f";s:4:"𝒈";s:1:"g";s:4:"𝒉";s:1:"h";s:4:"𝒊";s:1:"i";s:4:"𝒋";s:1:"j";s:4:"𝒌";s:1:"k";s:4:"𝒍";s:1:"l";s:4:"𝒎";s:1:"m";s:4:"𝒏";s:1:"n";s:4:"𝒐";s:1:"o";s:4:"𝒑";s:1:"p";s:4:"𝒒";s:1:"q";s:4:"𝒓";s:1:"r";s:4:"𝒔";s:1:"s";s:4:"𝒕";s:1:"t";s:4:"𝒖";s:1:"u";s:4:"𝒗";s:1:"v";s:4:"𝒘";s:1:"w";s:4:"𝒙";s:1:"x";s:4:"𝒚";s:1:"y";s:4:"𝒛";s:1:"z";s:4:"𝒜";s:1:"A";s:4:"𝒞";s:1:"C";s:4:"𝒟";s:1:"D";s:4:"𝒢";s:1:"G";s:4:"𝒥";s:1:"J";s:4:"𝒦";s:1:"K";s:4:"𝒩";s:1:"N";s:4:"𝒪";s:1:"O";s:4:"𝒫";s:1:"P";s:4:"𝒬";s:1:"Q";s:4:"𝒮";s:1:"S";s:4:"𝒯";s:1:"T";s:4:"𝒰";s:1:"U";s:4:"𝒱";s:1:"V";s:4:"𝒲";s:1:"W";s:4:"𝒳";s:1:"X";s:4:"𝒴";s:1:"Y";s:4:"𝒵";s:1:"Z";s:4:"𝒶";s:1:"a";s:4:"𝒷";s:1:"b";s:4:"𝒸";s:1:"c";s:4:"𝒹";s:1:"d";s:4:"𝒻";s:1:"f";s:4:"𝒽";s:1:"h";s:4:"𝒾";s:1:"i";s:4:"𝒿";s:1:"j";s:4:"𝓀";s:1:"k";s:4:"𝓁";s:1:"l";s:4:"𝓂";s:1:"m";s:4:"𝓃";s:1:"n";s:4:"𝓅";s:1:"p";s:4:"𝓆";s:1:"q";s:4:"𝓇";s:1:"r";s:4:"𝓈";s:1:"s";s:4:"𝓉";s:1:"t";s:4:"𝓊";s:1:"u";s:4:"𝓋";s:1:"v";s:4:"𝓌";s:1:"w";s:4:"𝓍";s:1:"x";s:4:"𝓎";s:1:"y";s:4:"𝓏";s:1:"z";s:4:"𝓐";s:1:"A";s:4:"𝓑";s:1:"B";s:4:"𝓒";s:1:"C";s:4:"𝓓";s:1:"D";s:4:"𝓔";s:1:"E";s:4:"𝓕";s:1:"F";s:4:"𝓖";s:1:"G";s:4:"𝓗";s:1:"H";s:4:"𝓘";s:1:"I";s:4:"𝓙";s:1:"J";s:4:"𝓚";s:1:"K";s:4:"𝓛";s:1:"L";s:4:"𝓜";s:1:"M";s:4:"𝓝";s:1:"N";s:4:"𝓞";s:1:"O";s:4:"𝓟";s:1:"P";s:4:"𝓠";s:1:"Q";s:4:"𝓡";s:1:"R";s:4:"𝓢";s:1:"S";s:4:"𝓣";s:1:"T";s:4:"𝓤";s:1:"U";s:4:"𝓥";s:1:"V";s:4:"𝓦";s:1:"W";s:4:"𝓧";s:1:"X";s:4:"𝓨";s:1:"Y";s:4:"𝓩";s:1:"Z";s:4:"𝓪";s:1:"a";s:4:"𝓫";s:1:"b";s:4:"𝓬";s:1:"c";s:4:"𝓭";s:1:"d";s:4:"𝓮";s:1:"e";s:4:"𝓯";s:1:"f";s:4:"𝓰";s:1:"g";s:4:"𝓱";s:1:"h";s:4:"𝓲";s:1:"i";s:4:"𝓳";s:1:"j";s:4:"𝓴";s:1:"k";s:4:"𝓵";s:1:"l";s:4:"𝓶";s:1:"m";s:4:"𝓷";s:1:"n";s:4:"𝓸";s:1:"o";s:4:"𝓹";s:1:"p";s:4:"𝓺";s:1:"q";s:4:"𝓻";s:1:"r";s:4:"𝓼";s:1:"s";s:4:"𝓽";s:1:"t";s:4:"𝓾";s:1:"u";s:4:"𝓿";s:1:"v";s:4:"𝔀";s:1:"w";s:4:"𝔁";s:1:"x";s:4:"𝔂";s:1:"y";s:4:"𝔃";s:1:"z";s:4:"𝔄";s:1:"A";s:4:"𝔅";s:1:"B";s:4:"𝔇";s:1:"D";s:4:"𝔈";s:1:"E";s:4:"𝔉";s:1:"F";s:4:"𝔊";s:1:"G";s:4:"𝔍";s:1:"J";s:4:"𝔎";s:1:"K";s:4:"𝔏";s:1:"L";s:4:"𝔐";s:1:"M";s:4:"𝔑";s:1:"N";s:4:"𝔒";s:1:"O";s:4:"𝔓";s:1:"P";s:4:"𝔔";s:1:"Q";s:4:"𝔖";s:1:"S";s:4:"𝔗";s:1:"T";s:4:"𝔘";s:1:"U";s:4:"𝔙";s:1:"V";s:4:"𝔚";s:1:"W";s:4:"𝔛";s:1:"X";s:4:"𝔜";s:1:"Y";s:4:"𝔞";s:1:"a";s:4:"𝔟";s:1:"b";s:4:"𝔠";s:1:"c";s:4:"𝔡";s:1:"d";s:4:"𝔢";s:1:"e";s:4:"𝔣";s:1:"f";s:4:"𝔤";s:1:"g";s:4:"𝔥";s:1:"h";s:4:"𝔦";s:1:"i";s:4:"𝔧";s:1:"j";s:4:"𝔨";s:1:"k";s:4:"𝔩";s:1:"l";s:4:"𝔪";s:1:"m";s:4:"𝔫";s:1:"n";s:4:"𝔬";s:1:"o";s:4:"𝔭";s:1:"p";s:4:"𝔮";s:1:"q";s:4:"𝔯";s:1:"r";s:4:"𝔰";s:1:"s";s:4:"𝔱";s:1:"t";s:4:"𝔲";s:1:"u";s:4:"𝔳";s:1:"v";s:4:"𝔴";s:1:"w";s:4:"𝔵";s:1:"x";s:4:"𝔶";s:1:"y";s:4:"𝔷";s:1:"z";s:4:"𝔸";s:1:"A";s:4:"𝔹";s:1:"B";s:4:"𝔻";s:1:"D";s:4:"𝔼";s:1:"E";s:4:"𝔽";s:1:"F";s:4:"𝔾";s:1:"G";s:4:"𝕀";s:1:"I";s:4:"𝕁";s:1:"J";s:4:"𝕂";s:1:"K";s:4:"𝕃";s:1:"L";s:4:"𝕄";s:1:"M";s:4:"𝕆";s:1:"O";s:4:"𝕊";s:1:"S";s:4:"𝕋";s:1:"T";s:4:"𝕌";s:1:"U";s:4:"𝕍";s:1:"V";s:4:"𝕎";s:1:"W";s:4:"𝕏";s:1:"X";s:4:"𝕐";s:1:"Y";s:4:"𝕒";s:1:"a";s:4:"𝕓";s:1:"b";s:4:"𝕔";s:1:"c";s:4:"𝕕";s:1:"d";s:4:"𝕖";s:1:"e";s:4:"𝕗";s:1:"f";s:4:"𝕘";s:1:"g";s:4:"𝕙";s:1:"h";s:4:"𝕚";s:1:"i";s:4:"𝕛";s:1:"j";s:4:"𝕜";s:1:"k";s:4:"𝕝";s:1:"l";s:4:"𝕞";s:1:"m";s:4:"𝕟";s:1:"n";s:4:"𝕠";s:1:"o";s:4:"𝕡";s:1:"p";s:4:"𝕢";s:1:"q";s:4:"𝕣";s:1:"r";s:4:"𝕤";s:1:"s";s:4:"𝕥";s:1:"t";s:4:"𝕦";s:1:"u";s:4:"𝕧";s:1:"v";s:4:"𝕨";s:1:"w";s:4:"𝕩";s:1:"x";s:4:"𝕪";s:1:"y";s:4:"𝕫";s:1:"z";s:4:"𝕬";s:1:"A";s:4:"𝕭";s:1:"B";s:4:"𝕮";s:1:"C";s:4:"𝕯";s:1:"D";s:4:"𝕰";s:1:"E";s:4:"𝕱";s:1:"F";s:4:"𝕲";s:1:"G";s:4:"𝕳";s:1:"H";s:4:"𝕴";s:1:"I";s:4:"𝕵";s:1:"J";s:4:"𝕶";s:1:"K";s:4:"𝕷";s:1:"L";s:4:"𝕸";s:1:"M";s:4:"𝕹";s:1:"N";s:4:"𝕺";s:1:"O";s:4:"𝕻";s:1:"P";s:4:"𝕼";s:1:"Q";s:4:"𝕽";s:1:"R";s:4:"𝕾";s:1:"S";s:4:"𝕿";s:1:"T";s:4:"𝖀";s:1:"U";s:4:"𝖁";s:1:"V";s:4:"𝖂";s:1:"W";s:4:"𝖃";s:1:"X";s:4:"𝖄";s:1:"Y";s:4:"𝖅";s:1:"Z";s:4:"𝖆";s:1:"a";s:4:"𝖇";s:1:"b";s:4:"𝖈";s:1:"c";s:4:"𝖉";s:1:"d";s:4:"𝖊";s:1:"e";s:4:"𝖋";s:1:"f";s:4:"𝖌";s:1:"g";s:4:"𝖍";s:1:"h";s:4:"𝖎";s:1:"i";s:4:"𝖏";s:1:"j";s:4:"𝖐";s:1:"k";s:4:"𝖑";s:1:"l";s:4:"𝖒";s:1:"m";s:4:"𝖓";s:1:"n";s:4:"𝖔";s:1:"o";s:4:"𝖕";s:1:"p";s:4:"𝖖";s:1:"q";s:4:"𝖗";s:1:"r";s:4:"𝖘";s:1:"s";s:4:"𝖙";s:1:"t";s:4:"𝖚";s:1:"u";s:4:"𝖛";s:1:"v";s:4:"𝖜";s:1:"w";s:4:"𝖝";s:1:"x";s:4:"𝖞";s:1:"y";s:4:"𝖟";s:1:"z";s:4:"𝖠";s:1:"A";s:4:"𝖡";s:1:"B";s:4:"𝖢";s:1:"C";s:4:"𝖣";s:1:"D";s:4:"𝖤";s:1:"E";s:4:"𝖥";s:1:"F";s:4:"𝖦";s:1:"G";s:4:"𝖧";s:1:"H";s:4:"𝖨";s:1:"I";s:4:"𝖩";s:1:"J";s:4:"𝖪";s:1:"K";s:4:"𝖫";s:1:"L";s:4:"𝖬";s:1:"M";s:4:"𝖭";s:1:"N";s:4:"𝖮";s:1:"O";s:4:"𝖯";s:1:"P";s:4:"𝖰";s:1:"Q";s:4:"𝖱";s:1:"R";s:4:"𝖲";s:1:"S";s:4:"𝖳";s:1:"T";s:4:"𝖴";s:1:"U";s:4:"𝖵";s:1:"V";s:4:"𝖶";s:1:"W";s:4:"𝖷";s:1:"X";s:4:"𝖸";s:1:"Y";s:4:"𝖹";s:1:"Z";s:4:"𝖺";s:1:"a";s:4:"𝖻";s:1:"b";s:4:"𝖼";s:1:"c";s:4:"𝖽";s:1:"d";s:4:"𝖾";s:1:"e";s:4:"𝖿";s:1:"f";s:4:"𝗀";s:1:"g";s:4:"𝗁";s:1:"h";s:4:"𝗂";s:1:"i";s:4:"𝗃";s:1:"j";s:4:"𝗄";s:1:"k";s:4:"𝗅";s:1:"l";s:4:"𝗆";s:1:"m";s:4:"𝗇";s:1:"n";s:4:"𝗈";s:1:"o";s:4:"𝗉";s:1:"p";s:4:"𝗊";s:1:"q";s:4:"𝗋";s:1:"r";s:4:"𝗌";s:1:"s";s:4:"𝗍";s:1:"t";s:4:"𝗎";s:1:"u";s:4:"𝗏";s:1:"v";s:4:"𝗐";s:1:"w";s:4:"𝗑";s:1:"x";s:4:"𝗒";s:1:"y";s:4:"𝗓";s:1:"z";s:4:"𝗔";s:1:"A";s:4:"𝗕";s:1:"B";s:4:"𝗖";s:1:"C";s:4:"𝗗";s:1:"D";s:4:"𝗘";s:1:"E";s:4:"𝗙";s:1:"F";s:4:"𝗚";s:1:"G";s:4:"𝗛";s:1:"H";s:4:"𝗜";s:1:"I";s:4:"𝗝";s:1:"J";s:4:"𝗞";s:1:"K";s:4:"𝗟";s:1:"L";s:4:"𝗠";s:1:"M";s:4:"𝗡";s:1:"N";s:4:"𝗢";s:1:"O";s:4:"𝗣";s:1:"P";s:4:"𝗤";s:1:"Q";s:4:"𝗥";s:1:"R";s:4:"𝗦";s:1:"S";s:4:"𝗧";s:1:"T";s:4:"𝗨";s:1:"U";s:4:"𝗩";s:1:"V";s:4:"𝗪";s:1:"W";s:4:"𝗫";s:1:"X";s:4:"𝗬";s:1:"Y";s:4:"𝗭";s:1:"Z";s:4:"𝗮";s:1:"a";s:4:"𝗯";s:1:"b";s:4:"𝗰";s:1:"c";s:4:"𝗱";s:1:"d";s:4:"𝗲";s:1:"e";s:4:"𝗳";s:1:"f";s:4:"𝗴";s:1:"g";s:4:"𝗵";s:1:"h";s:4:"𝗶";s:1:"i";s:4:"𝗷";s:1:"j";s:4:"𝗸";s:1:"k";s:4:"𝗹";s:1:"l";s:4:"𝗺";s:1:"m";s:4:"𝗻";s:1:"n";s:4:"𝗼";s:1:"o";s:4:"𝗽";s:1:"p";s:4:"𝗾";s:1:"q";s:4:"𝗿";s:1:"r";s:4:"𝘀";s:1:"s";s:4:"𝘁";s:1:"t";s:4:"𝘂";s:1:"u";s:4:"𝘃";s:1:"v";s:4:"𝘄";s:1:"w";s:4:"𝘅";s:1:"x";s:4:"𝘆";s:1:"y";s:4:"𝘇";s:1:"z";s:4:"𝘈";s:1:"A";s:4:"𝘉";s:1:"B";s:4:"𝘊";s:1:"C";s:4:"𝘋";s:1:"D";s:4:"𝘌";s:1:"E";s:4:"𝘍";s:1:"F";s:4:"𝘎";s:1:"G";s:4:"𝘏";s:1:"H";s:4:"𝘐";s:1:"I";s:4:"𝘑";s:1:"J";s:4:"𝘒";s:1:"K";s:4:"𝘓";s:1:"L";s:4:"𝘔";s:1:"M";s:4:"𝘕";s:1:"N";s:4:"𝘖";s:1:"O";s:4:"𝘗";s:1:"P";s:4:"𝘘";s:1:"Q";s:4:"𝘙";s:1:"R";s:4:"𝘚";s:1:"S";s:4:"𝘛";s:1:"T";s:4:"𝘜";s:1:"U";s:4:"𝘝";s:1:"V";s:4:"𝘞";s:1:"W";s:4:"𝘟";s:1:"X";s:4:"𝘠";s:1:"Y";s:4:"𝘡";s:1:"Z";s:4:"𝘢";s:1:"a";s:4:"𝘣";s:1:"b";s:4:"𝘤";s:1:"c";s:4:"𝘥";s:1:"d";s:4:"𝘦";s:1:"e";s:4:"𝘧";s:1:"f";s:4:"𝘨";s:1:"g";s:4:"𝘩";s:1:"h";s:4:"𝘪";s:1:"i";s:4:"𝘫";s:1:"j";s:4:"𝘬";s:1:"k";s:4:"𝘭";s:1:"l";s:4:"𝘮";s:1:"m";s:4:"𝘯";s:1:"n";s:4:"𝘰";s:1:"o";s:4:"𝘱";s:1:"p";s:4:"𝘲";s:1:"q";s:4:"𝘳";s:1:"r";s:4:"𝘴";s:1:"s";s:4:"𝘵";s:1:"t";s:4:"𝘶";s:1:"u";s:4:"𝘷";s:1:"v";s:4:"𝘸";s:1:"w";s:4:"𝘹";s:1:"x";s:4:"𝘺";s:1:"y";s:4:"𝘻";s:1:"z";s:4:"𝘼";s:1:"A";s:4:"𝘽";s:1:"B";s:4:"𝘾";s:1:"C";s:4:"𝘿";s:1:"D";s:4:"𝙀";s:1:"E";s:4:"𝙁";s:1:"F";s:4:"𝙂";s:1:"G";s:4:"𝙃";s:1:"H";s:4:"𝙄";s:1:"I";s:4:"𝙅";s:1:"J";s:4:"𝙆";s:1:"K";s:4:"𝙇";s:1:"L";s:4:"𝙈";s:1:"M";s:4:"𝙉";s:1:"N";s:4:"𝙊";s:1:"O";s:4:"𝙋";s:1:"P";s:4:"𝙌";s:1:"Q";s:4:"𝙍";s:1:"R";s:4:"𝙎";s:1:"S";s:4:"𝙏";s:1:"T";s:4:"𝙐";s:1:"U";s:4:"𝙑";s:1:"V";s:4:"𝙒";s:1:"W";s:4:"𝙓";s:1:"X";s:4:"𝙔";s:1:"Y";s:4:"𝙕";s:1:"Z";s:4:"𝙖";s:1:"a";s:4:"𝙗";s:1:"b";s:4:"𝙘";s:1:"c";s:4:"𝙙";s:1:"d";s:4:"𝙚";s:1:"e";s:4:"𝙛";s:1:"f";s:4:"𝙜";s:1:"g";s:4:"𝙝";s:1:"h";s:4:"𝙞";s:1:"i";s:4:"𝙟";s:1:"j";s:4:"𝙠";s:1:"k";s:4:"𝙡";s:1:"l";s:4:"𝙢";s:1:"m";s:4:"𝙣";s:1:"n";s:4:"𝙤";s:1:"o";s:4:"𝙥";s:1:"p";s:4:"𝙦";s:1:"q";s:4:"𝙧";s:1:"r";s:4:"𝙨";s:1:"s";s:4:"𝙩";s:1:"t";s:4:"𝙪";s:1:"u";s:4:"𝙫";s:1:"v";s:4:"𝙬";s:1:"w";s:4:"𝙭";s:1:"x";s:4:"𝙮";s:1:"y";s:4:"𝙯";s:1:"z";s:4:"𝙰";s:1:"A";s:4:"𝙱";s:1:"B";s:4:"𝙲";s:1:"C";s:4:"𝙳";s:1:"D";s:4:"𝙴";s:1:"E";s:4:"𝙵";s:1:"F";s:4:"𝙶";s:1:"G";s:4:"𝙷";s:1:"H";s:4:"𝙸";s:1:"I";s:4:"𝙹";s:1:"J";s:4:"𝙺";s:1:"K";s:4:"𝙻";s:1:"L";s:4:"𝙼";s:1:"M";s:4:"𝙽";s:1:"N";s:4:"𝙾";s:1:"O";s:4:"𝙿";s:1:"P";s:4:"𝚀";s:1:"Q";s:4:"𝚁";s:1:"R";s:4:"𝚂";s:1:"S";s:4:"𝚃";s:1:"T";s:4:"𝚄";s:1:"U";s:4:"𝚅";s:1:"V";s:4:"𝚆";s:1:"W";s:4:"𝚇";s:1:"X";s:4:"𝚈";s:1:"Y";s:4:"𝚉";s:1:"Z";s:4:"𝚊";s:1:"a";s:4:"𝚋";s:1:"b";s:4:"𝚌";s:1:"c";s:4:"𝚍";s:1:"d";s:4:"𝚎";s:1:"e";s:4:"𝚏";s:1:"f";s:4:"𝚐";s:1:"g";s:4:"𝚑";s:1:"h";s:4:"𝚒";s:1:"i";s:4:"𝚓";s:1:"j";s:4:"𝚔";s:1:"k";s:4:"𝚕";s:1:"l";s:4:"𝚖";s:1:"m";s:4:"𝚗";s:1:"n";s:4:"𝚘";s:1:"o";s:4:"𝚙";s:1:"p";s:4:"𝚚";s:1:"q";s:4:"𝚛";s:1:"r";s:4:"𝚜";s:1:"s";s:4:"𝚝";s:1:"t";s:4:"𝚞";s:1:"u";s:4:"𝚟";s:1:"v";s:4:"𝚠";s:1:"w";s:4:"𝚡";s:1:"x";s:4:"𝚢";s:1:"y";s:4:"𝚣";s:1:"z";s:4:"𝚤";s:2:"ı";s:4:"𝚥";s:2:"ȷ";s:4:"𝚨";s:2:"Α";s:4:"𝚩";s:2:"Β";s:4:"𝚪";s:2:"Γ";s:4:"𝚫";s:2:"Δ";s:4:"𝚬";s:2:"Ε";s:4:"𝚭";s:2:"Ζ";s:4:"𝚮";s:2:"Η";s:4:"𝚯";s:2:"Θ";s:4:"𝚰";s:2:"Ι";s:4:"𝚱";s:2:"Κ";s:4:"𝚲";s:2:"Λ";s:4:"𝚳";s:2:"Μ";s:4:"𝚴";s:2:"Ν";s:4:"𝚵";s:2:"Ξ";s:4:"𝚶";s:2:"Ο";s:4:"𝚷";s:2:"Π";s:4:"𝚸";s:2:"Ρ";s:4:"𝚹";s:2:"Θ";s:4:"𝚺";s:2:"Σ";s:4:"𝚻";s:2:"Τ";s:4:"𝚼";s:2:"Υ";s:4:"𝚽";s:2:"Φ";s:4:"𝚾";s:2:"Χ";s:4:"𝚿";s:2:"Ψ";s:4:"𝛀";s:2:"Ω";s:4:"𝛁";s:3:"∇";s:4:"𝛂";s:2:"α";s:4:"𝛃";s:2:"β";s:4:"𝛄";s:2:"γ";s:4:"𝛅";s:2:"δ";s:4:"𝛆";s:2:"ε";s:4:"𝛇";s:2:"ζ";s:4:"𝛈";s:2:"η";s:4:"𝛉";s:2:"θ";s:4:"𝛊";s:2:"ι";s:4:"𝛋";s:2:"κ";s:4:"𝛌";s:2:"λ";s:4:"𝛍";s:2:"μ";s:4:"𝛎";s:2:"ν";s:4:"𝛏";s:2:"ξ";s:4:"𝛐";s:2:"ο";s:4:"𝛑";s:2:"π";s:4:"𝛒";s:2:"ρ";s:4:"𝛓";s:2:"ς";s:4:"𝛔";s:2:"σ";s:4:"𝛕";s:2:"τ";s:4:"𝛖";s:2:"υ";s:4:"𝛗";s:2:"φ";s:4:"𝛘";s:2:"χ";s:4:"𝛙";s:2:"ψ";s:4:"𝛚";s:2:"ω";s:4:"𝛛";s:3:"∂";s:4:"𝛜";s:2:"ε";s:4:"𝛝";s:2:"θ";s:4:"𝛞";s:2:"κ";s:4:"𝛟";s:2:"φ";s:4:"𝛠";s:2:"ρ";s:4:"𝛡";s:2:"π";s:4:"𝛢";s:2:"Α";s:4:"𝛣";s:2:"Β";s:4:"𝛤";s:2:"Γ";s:4:"𝛥";s:2:"Δ";s:4:"𝛦";s:2:"Ε";s:4:"𝛧";s:2:"Ζ";s:4:"𝛨";s:2:"Η";s:4:"𝛩";s:2:"Θ";s:4:"𝛪";s:2:"Ι";s:4:"𝛫";s:2:"Κ";s:4:"𝛬";s:2:"Λ";s:4:"𝛭";s:2:"Μ";s:4:"𝛮";s:2:"Ν";s:4:"𝛯";s:2:"Ξ";s:4:"𝛰";s:2:"Ο";s:4:"𝛱";s:2:"Π";s:4:"𝛲";s:2:"Ρ";s:4:"𝛳";s:2:"Θ";s:4:"𝛴";s:2:"Σ";s:4:"𝛵";s:2:"Τ";s:4:"𝛶";s:2:"Υ";s:4:"𝛷";s:2:"Φ";s:4:"𝛸";s:2:"Χ";s:4:"𝛹";s:2:"Ψ";s:4:"𝛺";s:2:"Ω";s:4:"𝛻";s:3:"∇";s:4:"𝛼";s:2:"α";s:4:"𝛽";s:2:"β";s:4:"𝛾";s:2:"γ";s:4:"𝛿";s:2:"δ";s:4:"𝜀";s:2:"ε";s:4:"𝜁";s:2:"ζ";s:4:"𝜂";s:2:"η";s:4:"𝜃";s:2:"θ";s:4:"𝜄";s:2:"ι";s:4:"𝜅";s:2:"κ";s:4:"𝜆";s:2:"λ";s:4:"𝜇";s:2:"μ";s:4:"𝜈";s:2:"ν";s:4:"𝜉";s:2:"ξ";s:4:"𝜊";s:2:"ο";s:4:"𝜋";s:2:"π";s:4:"𝜌";s:2:"ρ";s:4:"𝜍";s:2:"ς";s:4:"𝜎";s:2:"σ";s:4:"𝜏";s:2:"τ";s:4:"𝜐";s:2:"υ";s:4:"𝜑";s:2:"φ";s:4:"𝜒";s:2:"χ";s:4:"𝜓";s:2:"ψ";s:4:"𝜔";s:2:"ω";s:4:"𝜕";s:3:"∂";s:4:"𝜖";s:2:"ε";s:4:"𝜗";s:2:"θ";s:4:"𝜘";s:2:"κ";s:4:"𝜙";s:2:"φ";s:4:"𝜚";s:2:"ρ";s:4:"𝜛";s:2:"π";s:4:"𝜜";s:2:"Α";s:4:"𝜝";s:2:"Β";s:4:"𝜞";s:2:"Γ";s:4:"𝜟";s:2:"Δ";s:4:"𝜠";s:2:"Ε";s:4:"𝜡";s:2:"Ζ";s:4:"𝜢";s:2:"Η";s:4:"𝜣";s:2:"Θ";s:4:"𝜤";s:2:"Ι";s:4:"𝜥";s:2:"Κ";s:4:"𝜦";s:2:"Λ";s:4:"𝜧";s:2:"Μ";s:4:"𝜨";s:2:"Ν";s:4:"𝜩";s:2:"Ξ";s:4:"𝜪";s:2:"Ο";s:4:"𝜫";s:2:"Π";s:4:"𝜬";s:2:"Ρ";s:4:"𝜭";s:2:"Θ";s:4:"𝜮";s:2:"Σ";s:4:"𝜯";s:2:"Τ";s:4:"𝜰";s:2:"Υ";s:4:"𝜱";s:2:"Φ";s:4:"𝜲";s:2:"Χ";s:4:"𝜳";s:2:"Ψ";s:4:"𝜴";s:2:"Ω";s:4:"𝜵";s:3:"∇";s:4:"𝜶";s:2:"α";s:4:"𝜷";s:2:"β";s:4:"𝜸";s:2:"γ";s:4:"𝜹";s:2:"δ";s:4:"𝜺";s:2:"ε";s:4:"𝜻";s:2:"ζ";s:4:"𝜼";s:2:"η";s:4:"𝜽";s:2:"θ";s:4:"𝜾";s:2:"ι";s:4:"𝜿";s:2:"κ";s:4:"𝝀";s:2:"λ";s:4:"𝝁";s:2:"μ";s:4:"𝝂";s:2:"ν";s:4:"𝝃";s:2:"ξ";s:4:"𝝄";s:2:"ο";s:4:"𝝅";s:2:"π";s:4:"𝝆";s:2:"ρ";s:4:"𝝇";s:2:"ς";s:4:"𝝈";s:2:"σ";s:4:"𝝉";s:2:"τ";s:4:"𝝊";s:2:"υ";s:4:"𝝋";s:2:"φ";s:4:"𝝌";s:2:"χ";s:4:"𝝍";s:2:"ψ";s:4:"𝝎";s:2:"ω";s:4:"𝝏";s:3:"∂";s:4:"𝝐";s:2:"ε";s:4:"𝝑";s:2:"θ";s:4:"𝝒";s:2:"κ";s:4:"𝝓";s:2:"φ";s:4:"𝝔";s:2:"ρ";s:4:"𝝕";s:2:"π";s:4:"𝝖";s:2:"Α";s:4:"𝝗";s:2:"Β";s:4:"𝝘";s:2:"Γ";s:4:"𝝙";s:2:"Δ";s:4:"𝝚";s:2:"Ε";s:4:"𝝛";s:2:"Ζ";s:4:"𝝜";s:2:"Η";s:4:"𝝝";s:2:"Θ";s:4:"𝝞";s:2:"Ι";s:4:"𝝟";s:2:"Κ";s:4:"𝝠";s:2:"Λ";s:4:"𝝡";s:2:"Μ";s:4:"𝝢";s:2:"Ν";s:4:"𝝣";s:2:"Ξ";s:4:"𝝤";s:2:"Ο";s:4:"𝝥";s:2:"Π";s:4:"𝝦";s:2:"Ρ";s:4:"𝝧";s:2:"Θ";s:4:"𝝨";s:2:"Σ";s:4:"𝝩";s:2:"Τ";s:4:"𝝪";s:2:"Υ";s:4:"𝝫";s:2:"Φ";s:4:"𝝬";s:2:"Χ";s:4:"𝝭";s:2:"Ψ";s:4:"𝝮";s:2:"Ω";s:4:"𝝯";s:3:"∇";s:4:"𝝰";s:2:"α";s:4:"𝝱";s:2:"β";s:4:"𝝲";s:2:"γ";s:4:"𝝳";s:2:"δ";s:4:"𝝴";s:2:"ε";s:4:"𝝵";s:2:"ζ";s:4:"𝝶";s:2:"η";s:4:"𝝷";s:2:"θ";s:4:"𝝸";s:2:"ι";s:4:"𝝹";s:2:"κ";s:4:"𝝺";s:2:"λ";s:4:"𝝻";s:2:"μ";s:4:"𝝼";s:2:"ν";s:4:"𝝽";s:2:"ξ";s:4:"𝝾";s:2:"ο";s:4:"𝝿";s:2:"π";s:4:"𝞀";s:2:"ρ";s:4:"𝞁";s:2:"ς";s:4:"𝞂";s:2:"σ";s:4:"𝞃";s:2:"τ";s:4:"𝞄";s:2:"υ";s:4:"𝞅";s:2:"φ";s:4:"𝞆";s:2:"χ";s:4:"𝞇";s:2:"ψ";s:4:"𝞈";s:2:"ω";s:4:"𝞉";s:3:"∂";s:4:"𝞊";s:2:"ε";s:4:"𝞋";s:2:"θ";s:4:"𝞌";s:2:"κ";s:4:"𝞍";s:2:"φ";s:4:"𝞎";s:2:"ρ";s:4:"𝞏";s:2:"π";s:4:"𝞐";s:2:"Α";s:4:"𝞑";s:2:"Β";s:4:"𝞒";s:2:"Γ";s:4:"𝞓";s:2:"Δ";s:4:"𝞔";s:2:"Ε";s:4:"𝞕";s:2:"Ζ";s:4:"𝞖";s:2:"Η";s:4:"𝞗";s:2:"Θ";s:4:"𝞘";s:2:"Ι";s:4:"𝞙";s:2:"Κ";s:4:"𝞚";s:2:"Λ";s:4:"𝞛";s:2:"Μ";s:4:"𝞜";s:2:"Ν";s:4:"𝞝";s:2:"Ξ";s:4:"𝞞";s:2:"Ο";s:4:"𝞟";s:2:"Π";s:4:"𝞠";s:2:"Ρ";s:4:"𝞡";s:2:"Θ";s:4:"𝞢";s:2:"Σ";s:4:"𝞣";s:2:"Τ";s:4:"𝞤";s:2:"Υ";s:4:"𝞥";s:2:"Φ";s:4:"𝞦";s:2:"Χ";s:4:"𝞧";s:2:"Ψ";s:4:"𝞨";s:2:"Ω";s:4:"𝞩";s:3:"∇";s:4:"𝞪";s:2:"α";s:4:"𝞫";s:2:"β";s:4:"𝞬";s:2:"γ";s:4:"𝞭";s:2:"δ";s:4:"𝞮";s:2:"ε";s:4:"𝞯";s:2:"ζ";s:4:"𝞰";s:2:"η";s:4:"𝞱";s:2:"θ";s:4:"𝞲";s:2:"ι";s:4:"𝞳";s:2:"κ";s:4:"𝞴";s:2:"λ";s:4:"𝞵";s:2:"μ";s:4:"𝞶";s:2:"ν";s:4:"𝞷";s:2:"ξ";s:4:"𝞸";s:2:"ο";s:4:"𝞹";s:2:"π";s:4:"𝞺";s:2:"ρ";s:4:"𝞻";s:2:"ς";s:4:"𝞼";s:2:"σ";s:4:"𝞽";s:2:"τ";s:4:"𝞾";s:2:"υ";s:4:"𝞿";s:2:"φ";s:4:"𝟀";s:2:"χ";s:4:"𝟁";s:2:"ψ";s:4:"𝟂";s:2:"ω";s:4:"𝟃";s:3:"∂";s:4:"𝟄";s:2:"ε";s:4:"𝟅";s:2:"θ";s:4:"𝟆";s:2:"κ";s:4:"𝟇";s:2:"φ";s:4:"𝟈";s:2:"ρ";s:4:"𝟉";s:2:"π";s:4:"𝟊";s:2:"Ϝ";s:4:"𝟋";s:2:"ϝ";s:4:"𝟎";s:1:"0";s:4:"𝟏";s:1:"1";s:4:"𝟐";s:1:"2";s:4:"𝟑";s:1:"3";s:4:"𝟒";s:1:"4";s:4:"𝟓";s:1:"5";s:4:"𝟔";s:1:"6";s:4:"𝟕";s:1:"7";s:4:"𝟖";s:1:"8";s:4:"𝟗";s:1:"9";s:4:"𝟘";s:1:"0";s:4:"𝟙";s:1:"1";s:4:"𝟚";s:1:"2";s:4:"𝟛";s:1:"3";s:4:"𝟜";s:1:"4";s:4:"𝟝";s:1:"5";s:4:"𝟞";s:1:"6";s:4:"𝟟";s:1:"7";s:4:"𝟠";s:1:"8";s:4:"𝟡";s:1:"9";s:4:"𝟢";s:1:"0";s:4:"𝟣";s:1:"1";s:4:"𝟤";s:1:"2";s:4:"𝟥";s:1:"3";s:4:"𝟦";s:1:"4";s:4:"𝟧";s:1:"5";s:4:"𝟨";s:1:"6";s:4:"𝟩";s:1:"7";s:4:"𝟪";s:1:"8";s:4:"𝟫";s:1:"9";s:4:"𝟬";s:1:"0";s:4:"𝟭";s:1:"1";s:4:"𝟮";s:1:"2";s:4:"𝟯";s:1:"3";s:4:"𝟰";s:1:"4";s:4:"𝟱";s:1:"5";s:4:"𝟲";s:1:"6";s:4:"𝟳";s:1:"7";s:4:"𝟴";s:1:"8";s:4:"𝟵";s:1:"9";s:4:"𝟶";s:1:"0";s:4:"𝟷";s:1:"1";s:4:"𝟸";s:1:"2";s:4:"𝟹";s:1:"3";s:4:"𝟺";s:1:"4";s:4:"𝟻";s:1:"5";s:4:"𝟼";s:1:"6";s:4:"𝟽";s:1:"7";s:4:"𝟾";s:1:"8";s:4:"𝟿";s:1:"9";s:4:"𞸀";s:2:"ا";s:4:"𞸁";s:2:"ب";s:4:"𞸂";s:2:"ج";s:4:"𞸃";s:2:"د";s:4:"𞸅";s:2:"و";s:4:"𞸆";s:2:"ز";s:4:"𞸇";s:2:"ح";s:4:"𞸈";s:2:"ط";s:4:"𞸉";s:2:"ي";s:4:"𞸊";s:2:"ك";s:4:"𞸋";s:2:"ل";s:4:"𞸌";s:2:"م";s:4:"𞸍";s:2:"ن";s:4:"𞸎";s:2:"س";s:4:"𞸏";s:2:"ع";s:4:"𞸐";s:2:"ف";s:4:"𞸑";s:2:"ص";s:4:"𞸒";s:2:"ق";s:4:"𞸓";s:2:"ر";s:4:"𞸔";s:2:"ش";s:4:"𞸕";s:2:"ت";s:4:"𞸖";s:2:"ث";s:4:"𞸗";s:2:"خ";s:4:"𞸘";s:2:"ذ";s:4:"𞸙";s:2:"ض";s:4:"𞸚";s:2:"ظ";s:4:"𞸛";s:2:"غ";s:4:"𞸜";s:2:"ٮ";s:4:"𞸝";s:2:"ں";s:4:"𞸞";s:2:"ڡ";s:4:"𞸟";s:2:"ٯ";s:4:"𞸡";s:2:"ب";s:4:"𞸢";s:2:"ج";s:4:"𞸤";s:2:"ه";s:4:"𞸧";s:2:"ح";s:4:"𞸩";s:2:"ي";s:4:"𞸪";s:2:"ك";s:4:"𞸫";s:2:"ل";s:4:"𞸬";s:2:"م";s:4:"𞸭";s:2:"ن";s:4:"𞸮";s:2:"س";s:4:"𞸯";s:2:"ع";s:4:"𞸰";s:2:"ف";s:4:"𞸱";s:2:"ص";s:4:"𞸲";s:2:"ق";s:4:"𞸴";s:2:"ش";s:4:"𞸵";s:2:"ت";s:4:"𞸶";s:2:"ث";s:4:"𞸷";s:2:"خ";s:4:"𞸹";s:2:"ض";s:4:"𞸻";s:2:"غ";s:4:"𞹂";s:2:"ج";s:4:"𞹇";s:2:"ح";s:4:"𞹉";s:2:"ي";s:4:"𞹋";s:2:"ل";s:4:"𞹍";s:2:"ن";s:4:"𞹎";s:2:"س";s:4:"𞹏";s:2:"ع";s:4:"𞹑";s:2:"ص";s:4:"𞹒";s:2:"ق";s:4:"𞹔";s:2:"ش";s:4:"𞹗";s:2:"خ";s:4:"𞹙";s:2:"ض";s:4:"𞹛";s:2:"غ";s:4:"𞹝";s:2:"ں";s:4:"𞹟";s:2:"ٯ";s:4:"𞹡";s:2:"ب";s:4:"𞹢";s:2:"ج";s:4:"𞹤";s:2:"ه";s:4:"𞹧";s:2:"ح";s:4:"𞹨";s:2:"ط";s:4:"𞹩";s:2:"ي";s:4:"𞹪";s:2:"ك";s:4:"𞹬";s:2:"م";s:4:"𞹭";s:2:"ن";s:4:"𞹮";s:2:"س";s:4:"𞹯";s:2:"ع";s:4:"𞹰";s:2:"ف";s:4:"𞹱";s:2:"ص";s:4:"𞹲";s:2:"ق";s:4:"𞹴";s:2:"ش";s:4:"𞹵";s:2:"ت";s:4:"𞹶";s:2:"ث";s:4:"𞹷";s:2:"خ";s:4:"𞹹";s:2:"ض";s:4:"𞹺";s:2:"ظ";s:4:"𞹻";s:2:"غ";s:4:"𞹼";s:2:"ٮ";s:4:"𞹾";s:2:"ڡ";s:4:"𞺀";s:2:"ا";s:4:"𞺁";s:2:"ب";s:4:"𞺂";s:2:"ج";s:4:"𞺃";s:2:"د";s:4:"𞺄";s:2:"ه";s:4:"𞺅";s:2:"و";s:4:"𞺆";s:2:"ز";s:4:"𞺇";s:2:"ح";s:4:"𞺈";s:2:"ط";s:4:"𞺉";s:2:"ي";s:4:"𞺋";s:2:"ل";s:4:"𞺌";s:2:"م";s:4:"𞺍";s:2:"ن";s:4:"𞺎";s:2:"س";s:4:"𞺏";s:2:"ع";s:4:"𞺐";s:2:"ف";s:4:"𞺑";s:2:"ص";s:4:"𞺒";s:2:"ق";s:4:"𞺓";s:2:"ر";s:4:"𞺔";s:2:"ش";s:4:"𞺕";s:2:"ت";s:4:"𞺖";s:2:"ث";s:4:"𞺗";s:2:"خ";s:4:"𞺘";s:2:"ذ";s:4:"𞺙";s:2:"ض";s:4:"𞺚";s:2:"ظ";s:4:"𞺛";s:2:"غ";s:4:"𞺡";s:2:"ب";s:4:"𞺢";s:2:"ج";s:4:"𞺣";s:2:"د";s:4:"𞺥";s:2:"و";s:4:"𞺦";s:2:"ز";s:4:"𞺧";s:2:"ح";s:4:"𞺨";s:2:"ط";s:4:"𞺩";s:2:"ي";s:4:"𞺫";s:2:"ل";s:4:"𞺬";s:2:"م";s:4:"𞺭";s:2:"ن";s:4:"𞺮";s:2:"س";s:4:"𞺯";s:2:"ع";s:4:"𞺰";s:2:"ف";s:4:"𞺱";s:2:"ص";s:4:"𞺲";s:2:"ق";s:4:"𞺳";s:2:"ر";s:4:"𞺴";s:2:"ش";s:4:"𞺵";s:2:"ت";s:4:"𞺶";s:2:"ث";s:4:"𞺷";s:2:"خ";s:4:"𞺸";s:2:"ذ";s:4:"𞺹";s:2:"ض";s:4:"𞺺";s:2:"ظ";s:4:"𞺻";s:2:"غ";s:4:"🄀";s:2:"0.";s:4:"🄁";s:2:"0,";s:4:"🄂";s:2:"1,";s:4:"🄃";s:2:"2,";s:4:"🄄";s:2:"3,";s:4:"🄅";s:2:"4,";s:4:"🄆";s:2:"5,";s:4:"🄇";s:2:"6,";s:4:"🄈";s:2:"7,";s:4:"🄉";s:2:"8,";s:4:"🄊";s:2:"9,";s:4:"🄐";s:3:"(A)";s:4:"🄑";s:3:"(B)";s:4:"🄒";s:3:"(C)";s:4:"🄓";s:3:"(D)";s:4:"🄔";s:3:"(E)";s:4:"🄕";s:3:"(F)";s:4:"🄖";s:3:"(G)";s:4:"🄗";s:3:"(H)";s:4:"🄘";s:3:"(I)";s:4:"🄙";s:3:"(J)";s:4:"🄚";s:3:"(K)";s:4:"🄛";s:3:"(L)";s:4:"🄜";s:3:"(M)";s:4:"🄝";s:3:"(N)";s:4:"🄞";s:3:"(O)";s:4:"🄟";s:3:"(P)";s:4:"🄠";s:3:"(Q)";s:4:"🄡";s:3:"(R)";s:4:"🄢";s:3:"(S)";s:4:"🄣";s:3:"(T)";s:4:"🄤";s:3:"(U)";s:4:"🄥";s:3:"(V)";s:4:"🄦";s:3:"(W)";s:4:"🄧";s:3:"(X)";s:4:"🄨";s:3:"(Y)";s:4:"🄩";s:3:"(Z)";s:4:"🄪";s:7:"〔S〕";s:4:"🄫";s:1:"C";s:4:"🄬";s:1:"R";s:4:"🄭";s:2:"CD";s:4:"🄮";s:2:"WZ";s:4:"🄰";s:1:"A";s:4:"🄱";s:1:"B";s:4:"🄲";s:1:"C";s:4:"🄳";s:1:"D";s:4:"🄴";s:1:"E";s:4:"🄵";s:1:"F";s:4:"🄶";s:1:"G";s:4:"🄷";s:1:"H";s:4:"🄸";s:1:"I";s:4:"🄹";s:1:"J";s:4:"🄺";s:1:"K";s:4:"🄻";s:1:"L";s:4:"🄼";s:1:"M";s:4:"🄽";s:1:"N";s:4:"🄾";s:1:"O";s:4:"🄿";s:1:"P";s:4:"🅀";s:1:"Q";s:4:"🅁";s:1:"R";s:4:"🅂";s:1:"S";s:4:"🅃";s:1:"T";s:4:"🅄";s:1:"U";s:4:"🅅";s:1:"V";s:4:"🅆";s:1:"W";s:4:"🅇";s:1:"X";s:4:"🅈";s:1:"Y";s:4:"🅉";s:1:"Z";s:4:"🅊";s:2:"HV";s:4:"🅋";s:2:"MV";s:4:"🅌";s:2:"SD";s:4:"🅍";s:2:"SS";s:4:"🅎";s:3:"PPV";s:4:"🅏";s:2:"WC";s:4:"🅪";s:2:"MC";s:4:"🅫";s:2:"MD";s:4:"🆐";s:2:"DJ";s:4:"🈀";s:6:"ほか";s:4:"🈁";s:6:"ココ";s:4:"🈂";s:3:"サ";s:4:"🈐";s:3:"手";s:4:"🈑";s:3:"字";s:4:"🈒";s:3:"双";s:4:"🈓";s:6:"デ";s:4:"🈔";s:3:"二";s:4:"🈕";s:3:"多";s:4:"🈖";s:3:"解";s:4:"🈗";s:3:"天";s:4:"🈘";s:3:"交";s:4:"🈙";s:3:"映";s:4:"🈚";s:3:"無";s:4:"🈛";s:3:"料";s:4:"🈜";s:3:"前";s:4:"🈝";s:3:"後";s:4:"🈞";s:3:"再";s:4:"🈟";s:3:"新";s:4:"🈠";s:3:"初";s:4:"🈡";s:3:"終";s:4:"🈢";s:3:"生";s:4:"🈣";s:3:"販";s:4:"🈤";s:3:"声";s:4:"🈥";s:3:"吹";s:4:"🈦";s:3:"演";s:4:"🈧";s:3:"投";s:4:"🈨";s:3:"捕";s:4:"🈩";s:3:"一";s:4:"🈪";s:3:"三";s:4:"🈫";s:3:"遊";s:4:"🈬";s:3:"左";s:4:"🈭";s:3:"中";s:4:"🈮";s:3:"右";s:4:"🈯";s:3:"指";s:4:"🈰";s:3:"走";s:4:"🈱";s:3:"打";s:4:"🈲";s:3:"禁";s:4:"🈳";s:3:"空";s:4:"🈴";s:3:"合";s:4:"🈵";s:3:"満";s:4:"🈶";s:3:"有";s:4:"🈷";s:3:"月";s:4:"🈸";s:3:"申";s:4:"🈹";s:3:"割";s:4:"🈺";s:3:"営";s:4:"🉀";s:9:"〔本〕";s:4:"🉁";s:9:"〔三〕";s:4:"🉂";s:9:"〔二〕";s:4:"🉃";s:9:"〔安〕";s:4:"🉄";s:9:"〔点〕";s:4:"🉅";s:9:"〔打〕";s:4:"🉆";s:9:"〔盗〕";s:4:"🉇";s:9:"〔勝〕";s:4:"🉈";s:9:"〔敗〕";s:4:"🉐";s:3:"得";s:4:"🉑";s:3:"可";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/lowerCase.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/lowerCase.ser new file mode 100644 index 0000000..13dabdd --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/lowerCase.ser @@ -0,0 +1 @@ +a:1092:{s:1:"A";s:1:"a";s:1:"B";s:1:"b";s:1:"C";s:1:"c";s:1:"D";s:1:"d";s:1:"E";s:1:"e";s:1:"F";s:1:"f";s:1:"G";s:1:"g";s:1:"H";s:1:"h";s:1:"I";s:1:"i";s:1:"J";s:1:"j";s:1:"K";s:1:"k";s:1:"L";s:1:"l";s:1:"M";s:1:"m";s:1:"N";s:1:"n";s:1:"O";s:1:"o";s:1:"P";s:1:"p";s:1:"Q";s:1:"q";s:1:"R";s:1:"r";s:1:"S";s:1:"s";s:1:"T";s:1:"t";s:1:"U";s:1:"u";s:1:"V";s:1:"v";s:1:"W";s:1:"w";s:1:"X";s:1:"x";s:1:"Y";s:1:"y";s:1:"Z";s:1:"z";s:2:"À";s:2:"à";s:2:"Á";s:2:"á";s:2:"Â";s:2:"â";s:2:"Ã";s:2:"ã";s:2:"Ä";s:2:"ä";s:2:"Å";s:2:"å";s:2:"Æ";s:2:"æ";s:2:"Ç";s:2:"ç";s:2:"È";s:2:"è";s:2:"É";s:2:"é";s:2:"Ê";s:2:"ê";s:2:"Ë";s:2:"ë";s:2:"Ì";s:2:"ì";s:2:"Í";s:2:"í";s:2:"Î";s:2:"î";s:2:"Ï";s:2:"ï";s:2:"Ð";s:2:"ð";s:2:"Ñ";s:2:"ñ";s:2:"Ò";s:2:"ò";s:2:"Ó";s:2:"ó";s:2:"Ô";s:2:"ô";s:2:"Õ";s:2:"õ";s:2:"Ö";s:2:"ö";s:2:"Ø";s:2:"ø";s:2:"Ù";s:2:"ù";s:2:"Ú";s:2:"ú";s:2:"Û";s:2:"û";s:2:"Ü";s:2:"ü";s:2:"Ý";s:2:"ý";s:2:"Þ";s:2:"þ";s:2:"Ā";s:2:"ā";s:2:"Ă";s:2:"ă";s:2:"Ą";s:2:"ą";s:2:"Ć";s:2:"ć";s:2:"Ĉ";s:2:"ĉ";s:2:"Ċ";s:2:"ċ";s:2:"Č";s:2:"č";s:2:"Ď";s:2:"ď";s:2:"Đ";s:2:"đ";s:2:"Ē";s:2:"ē";s:2:"Ĕ";s:2:"ĕ";s:2:"Ė";s:2:"ė";s:2:"Ę";s:2:"ę";s:2:"Ě";s:2:"ě";s:2:"Ĝ";s:2:"ĝ";s:2:"Ğ";s:2:"ğ";s:2:"Ġ";s:2:"ġ";s:2:"Ģ";s:2:"ģ";s:2:"Ĥ";s:2:"ĥ";s:2:"Ħ";s:2:"ħ";s:2:"Ĩ";s:2:"ĩ";s:2:"Ī";s:2:"ī";s:2:"Ĭ";s:2:"ĭ";s:2:"Į";s:2:"į";s:2:"İ";s:1:"i";s:2:"IJ";s:2:"ij";s:2:"Ĵ";s:2:"ĵ";s:2:"Ķ";s:2:"ķ";s:2:"Ĺ";s:2:"ĺ";s:2:"Ļ";s:2:"ļ";s:2:"Ľ";s:2:"ľ";s:2:"Ŀ";s:2:"ŀ";s:2:"Ł";s:2:"ł";s:2:"Ń";s:2:"ń";s:2:"Ņ";s:2:"ņ";s:2:"Ň";s:2:"ň";s:2:"Ŋ";s:2:"ŋ";s:2:"Ō";s:2:"ō";s:2:"Ŏ";s:2:"ŏ";s:2:"Ő";s:2:"ő";s:2:"Œ";s:2:"œ";s:2:"Ŕ";s:2:"ŕ";s:2:"Ŗ";s:2:"ŗ";s:2:"Ř";s:2:"ř";s:2:"Ś";s:2:"ś";s:2:"Ŝ";s:2:"ŝ";s:2:"Ş";s:2:"ş";s:2:"Š";s:2:"š";s:2:"Ţ";s:2:"ţ";s:2:"Ť";s:2:"ť";s:2:"Ŧ";s:2:"ŧ";s:2:"Ũ";s:2:"ũ";s:2:"Ū";s:2:"ū";s:2:"Ŭ";s:2:"ŭ";s:2:"Ů";s:2:"ů";s:2:"Ű";s:2:"ű";s:2:"Ų";s:2:"ų";s:2:"Ŵ";s:2:"ŵ";s:2:"Ŷ";s:2:"ŷ";s:2:"Ÿ";s:2:"ÿ";s:2:"Ź";s:2:"ź";s:2:"Ż";s:2:"ż";s:2:"Ž";s:2:"ž";s:2:"Ɓ";s:2:"ɓ";s:2:"Ƃ";s:2:"ƃ";s:2:"Ƅ";s:2:"ƅ";s:2:"Ɔ";s:2:"ɔ";s:2:"Ƈ";s:2:"ƈ";s:2:"Ɖ";s:2:"ɖ";s:2:"Ɗ";s:2:"ɗ";s:2:"Ƌ";s:2:"ƌ";s:2:"Ǝ";s:2:"ǝ";s:2:"Ə";s:2:"ə";s:2:"Ɛ";s:2:"ɛ";s:2:"Ƒ";s:2:"ƒ";s:2:"Ɠ";s:2:"ɠ";s:2:"Ɣ";s:2:"ɣ";s:2:"Ɩ";s:2:"ɩ";s:2:"Ɨ";s:2:"ɨ";s:2:"Ƙ";s:2:"ƙ";s:2:"Ɯ";s:2:"ɯ";s:2:"Ɲ";s:2:"ɲ";s:2:"Ɵ";s:2:"ɵ";s:2:"Ơ";s:2:"ơ";s:2:"Ƣ";s:2:"ƣ";s:2:"Ƥ";s:2:"ƥ";s:2:"Ʀ";s:2:"ʀ";s:2:"Ƨ";s:2:"ƨ";s:2:"Ʃ";s:2:"ʃ";s:2:"Ƭ";s:2:"ƭ";s:2:"Ʈ";s:2:"ʈ";s:2:"Ư";s:2:"ư";s:2:"Ʊ";s:2:"ʊ";s:2:"Ʋ";s:2:"ʋ";s:2:"Ƴ";s:2:"ƴ";s:2:"Ƶ";s:2:"ƶ";s:2:"Ʒ";s:2:"ʒ";s:2:"Ƹ";s:2:"ƹ";s:2:"Ƽ";s:2:"ƽ";s:2:"DŽ";s:2:"dž";s:2:"Dž";s:2:"dž";s:2:"LJ";s:2:"lj";s:2:"Lj";s:2:"lj";s:2:"NJ";s:2:"nj";s:2:"Nj";s:2:"nj";s:2:"Ǎ";s:2:"ǎ";s:2:"Ǐ";s:2:"ǐ";s:2:"Ǒ";s:2:"ǒ";s:2:"Ǔ";s:2:"ǔ";s:2:"Ǖ";s:2:"ǖ";s:2:"Ǘ";s:2:"ǘ";s:2:"Ǚ";s:2:"ǚ";s:2:"Ǜ";s:2:"ǜ";s:2:"Ǟ";s:2:"ǟ";s:2:"Ǡ";s:2:"ǡ";s:2:"Ǣ";s:2:"ǣ";s:2:"Ǥ";s:2:"ǥ";s:2:"Ǧ";s:2:"ǧ";s:2:"Ǩ";s:2:"ǩ";s:2:"Ǫ";s:2:"ǫ";s:2:"Ǭ";s:2:"ǭ";s:2:"Ǯ";s:2:"ǯ";s:2:"DZ";s:2:"dz";s:2:"Dz";s:2:"dz";s:2:"Ǵ";s:2:"ǵ";s:2:"Ƕ";s:2:"ƕ";s:2:"Ƿ";s:2:"ƿ";s:2:"Ǹ";s:2:"ǹ";s:2:"Ǻ";s:2:"ǻ";s:2:"Ǽ";s:2:"ǽ";s:2:"Ǿ";s:2:"ǿ";s:2:"Ȁ";s:2:"ȁ";s:2:"Ȃ";s:2:"ȃ";s:2:"Ȅ";s:2:"ȅ";s:2:"Ȇ";s:2:"ȇ";s:2:"Ȉ";s:2:"ȉ";s:2:"Ȋ";s:2:"ȋ";s:2:"Ȍ";s:2:"ȍ";s:2:"Ȏ";s:2:"ȏ";s:2:"Ȑ";s:2:"ȑ";s:2:"Ȓ";s:2:"ȓ";s:2:"Ȕ";s:2:"ȕ";s:2:"Ȗ";s:2:"ȗ";s:2:"Ș";s:2:"ș";s:2:"Ț";s:2:"ț";s:2:"Ȝ";s:2:"ȝ";s:2:"Ȟ";s:2:"ȟ";s:2:"Ƞ";s:2:"ƞ";s:2:"Ȣ";s:2:"ȣ";s:2:"Ȥ";s:2:"ȥ";s:2:"Ȧ";s:2:"ȧ";s:2:"Ȩ";s:2:"ȩ";s:2:"Ȫ";s:2:"ȫ";s:2:"Ȭ";s:2:"ȭ";s:2:"Ȯ";s:2:"ȯ";s:2:"Ȱ";s:2:"ȱ";s:2:"Ȳ";s:2:"ȳ";s:2:"Ⱥ";s:3:"ⱥ";s:2:"Ȼ";s:2:"ȼ";s:2:"Ƚ";s:2:"ƚ";s:2:"Ⱦ";s:3:"ⱦ";s:2:"Ɂ";s:2:"ɂ";s:2:"Ƀ";s:2:"ƀ";s:2:"Ʉ";s:2:"ʉ";s:2:"Ʌ";s:2:"ʌ";s:2:"Ɇ";s:2:"ɇ";s:2:"Ɉ";s:2:"ɉ";s:2:"Ɋ";s:2:"ɋ";s:2:"Ɍ";s:2:"ɍ";s:2:"Ɏ";s:2:"ɏ";s:2:"Ͱ";s:2:"ͱ";s:2:"Ͳ";s:2:"ͳ";s:2:"Ͷ";s:2:"ͷ";s:2:"Ϳ";s:2:"ϳ";s:2:"Ά";s:2:"ά";s:2:"Έ";s:2:"έ";s:2:"Ή";s:2:"ή";s:2:"Ί";s:2:"ί";s:2:"Ό";s:2:"ό";s:2:"Ύ";s:2:"ύ";s:2:"Ώ";s:2:"ώ";s:2:"Α";s:2:"α";s:2:"Β";s:2:"β";s:2:"Γ";s:2:"γ";s:2:"Δ";s:2:"δ";s:2:"Ε";s:2:"ε";s:2:"Ζ";s:2:"ζ";s:2:"Η";s:2:"η";s:2:"Θ";s:2:"θ";s:2:"Ι";s:2:"ι";s:2:"Κ";s:2:"κ";s:2:"Λ";s:2:"λ";s:2:"Μ";s:2:"μ";s:2:"Ν";s:2:"ν";s:2:"Ξ";s:2:"ξ";s:2:"Ο";s:2:"ο";s:2:"Π";s:2:"π";s:2:"Ρ";s:2:"ρ";s:2:"Σ";s:2:"σ";s:2:"Τ";s:2:"τ";s:2:"Υ";s:2:"υ";s:2:"Φ";s:2:"φ";s:2:"Χ";s:2:"χ";s:2:"Ψ";s:2:"ψ";s:2:"Ω";s:2:"ω";s:2:"Ϊ";s:2:"ϊ";s:2:"Ϋ";s:2:"ϋ";s:2:"Ϗ";s:2:"ϗ";s:2:"Ϙ";s:2:"ϙ";s:2:"Ϛ";s:2:"ϛ";s:2:"Ϝ";s:2:"ϝ";s:2:"Ϟ";s:2:"ϟ";s:2:"Ϡ";s:2:"ϡ";s:2:"Ϣ";s:2:"ϣ";s:2:"Ϥ";s:2:"ϥ";s:2:"Ϧ";s:2:"ϧ";s:2:"Ϩ";s:2:"ϩ";s:2:"Ϫ";s:2:"ϫ";s:2:"Ϭ";s:2:"ϭ";s:2:"Ϯ";s:2:"ϯ";s:2:"ϴ";s:2:"θ";s:2:"Ϸ";s:2:"ϸ";s:2:"Ϲ";s:2:"ϲ";s:2:"Ϻ";s:2:"ϻ";s:2:"Ͻ";s:2:"ͻ";s:2:"Ͼ";s:2:"ͼ";s:2:"Ͽ";s:2:"ͽ";s:2:"Ѐ";s:2:"ѐ";s:2:"Ё";s:2:"ё";s:2:"Ђ";s:2:"ђ";s:2:"Ѓ";s:2:"ѓ";s:2:"Є";s:2:"є";s:2:"Ѕ";s:2:"ѕ";s:2:"І";s:2:"і";s:2:"Ї";s:2:"ї";s:2:"Ј";s:2:"ј";s:2:"Љ";s:2:"љ";s:2:"Њ";s:2:"њ";s:2:"Ћ";s:2:"ћ";s:2:"Ќ";s:2:"ќ";s:2:"Ѝ";s:2:"ѝ";s:2:"Ў";s:2:"ў";s:2:"Џ";s:2:"џ";s:2:"А";s:2:"а";s:2:"Б";s:2:"б";s:2:"В";s:2:"в";s:2:"Г";s:2:"г";s:2:"Д";s:2:"д";s:2:"Е";s:2:"е";s:2:"Ж";s:2:"ж";s:2:"З";s:2:"з";s:2:"И";s:2:"и";s:2:"Й";s:2:"й";s:2:"К";s:2:"к";s:2:"Л";s:2:"л";s:2:"М";s:2:"м";s:2:"Н";s:2:"н";s:2:"О";s:2:"о";s:2:"П";s:2:"п";s:2:"Р";s:2:"р";s:2:"С";s:2:"с";s:2:"Т";s:2:"т";s:2:"У";s:2:"у";s:2:"Ф";s:2:"ф";s:2:"Х";s:2:"х";s:2:"Ц";s:2:"ц";s:2:"Ч";s:2:"ч";s:2:"Ш";s:2:"ш";s:2:"Щ";s:2:"щ";s:2:"Ъ";s:2:"ъ";s:2:"Ы";s:2:"ы";s:2:"Ь";s:2:"ь";s:2:"Э";s:2:"э";s:2:"Ю";s:2:"ю";s:2:"Я";s:2:"я";s:2:"Ѡ";s:2:"ѡ";s:2:"Ѣ";s:2:"ѣ";s:2:"Ѥ";s:2:"ѥ";s:2:"Ѧ";s:2:"ѧ";s:2:"Ѩ";s:2:"ѩ";s:2:"Ѫ";s:2:"ѫ";s:2:"Ѭ";s:2:"ѭ";s:2:"Ѯ";s:2:"ѯ";s:2:"Ѱ";s:2:"ѱ";s:2:"Ѳ";s:2:"ѳ";s:2:"Ѵ";s:2:"ѵ";s:2:"Ѷ";s:2:"ѷ";s:2:"Ѹ";s:2:"ѹ";s:2:"Ѻ";s:2:"ѻ";s:2:"Ѽ";s:2:"ѽ";s:2:"Ѿ";s:2:"ѿ";s:2:"Ҁ";s:2:"ҁ";s:2:"Ҋ";s:2:"ҋ";s:2:"Ҍ";s:2:"ҍ";s:2:"Ҏ";s:2:"ҏ";s:2:"Ґ";s:2:"ґ";s:2:"Ғ";s:2:"ғ";s:2:"Ҕ";s:2:"ҕ";s:2:"Җ";s:2:"җ";s:2:"Ҙ";s:2:"ҙ";s:2:"Қ";s:2:"қ";s:2:"Ҝ";s:2:"ҝ";s:2:"Ҟ";s:2:"ҟ";s:2:"Ҡ";s:2:"ҡ";s:2:"Ң";s:2:"ң";s:2:"Ҥ";s:2:"ҥ";s:2:"Ҧ";s:2:"ҧ";s:2:"Ҩ";s:2:"ҩ";s:2:"Ҫ";s:2:"ҫ";s:2:"Ҭ";s:2:"ҭ";s:2:"Ү";s:2:"ү";s:2:"Ұ";s:2:"ұ";s:2:"Ҳ";s:2:"ҳ";s:2:"Ҵ";s:2:"ҵ";s:2:"Ҷ";s:2:"ҷ";s:2:"Ҹ";s:2:"ҹ";s:2:"Һ";s:2:"һ";s:2:"Ҽ";s:2:"ҽ";s:2:"Ҿ";s:2:"ҿ";s:2:"Ӏ";s:2:"ӏ";s:2:"Ӂ";s:2:"ӂ";s:2:"Ӄ";s:2:"ӄ";s:2:"Ӆ";s:2:"ӆ";s:2:"Ӈ";s:2:"ӈ";s:2:"Ӊ";s:2:"ӊ";s:2:"Ӌ";s:2:"ӌ";s:2:"Ӎ";s:2:"ӎ";s:2:"Ӑ";s:2:"ӑ";s:2:"Ӓ";s:2:"ӓ";s:2:"Ӕ";s:2:"ӕ";s:2:"Ӗ";s:2:"ӗ";s:2:"Ә";s:2:"ә";s:2:"Ӛ";s:2:"ӛ";s:2:"Ӝ";s:2:"ӝ";s:2:"Ӟ";s:2:"ӟ";s:2:"Ӡ";s:2:"ӡ";s:2:"Ӣ";s:2:"ӣ";s:2:"Ӥ";s:2:"ӥ";s:2:"Ӧ";s:2:"ӧ";s:2:"Ө";s:2:"ө";s:2:"Ӫ";s:2:"ӫ";s:2:"Ӭ";s:2:"ӭ";s:2:"Ӯ";s:2:"ӯ";s:2:"Ӱ";s:2:"ӱ";s:2:"Ӳ";s:2:"ӳ";s:2:"Ӵ";s:2:"ӵ";s:2:"Ӷ";s:2:"ӷ";s:2:"Ӹ";s:2:"ӹ";s:2:"Ӻ";s:2:"ӻ";s:2:"Ӽ";s:2:"ӽ";s:2:"Ӿ";s:2:"ӿ";s:2:"Ԁ";s:2:"ԁ";s:2:"Ԃ";s:2:"ԃ";s:2:"Ԅ";s:2:"ԅ";s:2:"Ԇ";s:2:"ԇ";s:2:"Ԉ";s:2:"ԉ";s:2:"Ԋ";s:2:"ԋ";s:2:"Ԍ";s:2:"ԍ";s:2:"Ԏ";s:2:"ԏ";s:2:"Ԑ";s:2:"ԑ";s:2:"Ԓ";s:2:"ԓ";s:2:"Ԕ";s:2:"ԕ";s:2:"Ԗ";s:2:"ԗ";s:2:"Ԙ";s:2:"ԙ";s:2:"Ԛ";s:2:"ԛ";s:2:"Ԝ";s:2:"ԝ";s:2:"Ԟ";s:2:"ԟ";s:2:"Ԡ";s:2:"ԡ";s:2:"Ԣ";s:2:"ԣ";s:2:"Ԥ";s:2:"ԥ";s:2:"Ԧ";s:2:"ԧ";s:2:"Ԩ";s:2:"ԩ";s:2:"Ԫ";s:2:"ԫ";s:2:"Ԭ";s:2:"ԭ";s:2:"Ԯ";s:2:"ԯ";s:2:"Ա";s:2:"ա";s:2:"Բ";s:2:"բ";s:2:"Գ";s:2:"գ";s:2:"Դ";s:2:"դ";s:2:"Ե";s:2:"ե";s:2:"Զ";s:2:"զ";s:2:"Է";s:2:"է";s:2:"Ը";s:2:"ը";s:2:"Թ";s:2:"թ";s:2:"Ժ";s:2:"ժ";s:2:"Ի";s:2:"ի";s:2:"Լ";s:2:"լ";s:2:"Խ";s:2:"խ";s:2:"Ծ";s:2:"ծ";s:2:"Կ";s:2:"կ";s:2:"Հ";s:2:"հ";s:2:"Ձ";s:2:"ձ";s:2:"Ղ";s:2:"ղ";s:2:"Ճ";s:2:"ճ";s:2:"Մ";s:2:"մ";s:2:"Յ";s:2:"յ";s:2:"Ն";s:2:"ն";s:2:"Շ";s:2:"շ";s:2:"Ո";s:2:"ո";s:2:"Չ";s:2:"չ";s:2:"Պ";s:2:"պ";s:2:"Ջ";s:2:"ջ";s:2:"Ռ";s:2:"ռ";s:2:"Ս";s:2:"ս";s:2:"Վ";s:2:"վ";s:2:"Տ";s:2:"տ";s:2:"Ր";s:2:"ր";s:2:"Ց";s:2:"ց";s:2:"Ւ";s:2:"ւ";s:2:"Փ";s:2:"փ";s:2:"Ք";s:2:"ք";s:2:"Օ";s:2:"օ";s:2:"Ֆ";s:2:"ֆ";s:3:"Ⴀ";s:3:"ⴀ";s:3:"Ⴁ";s:3:"ⴁ";s:3:"Ⴂ";s:3:"ⴂ";s:3:"Ⴃ";s:3:"ⴃ";s:3:"Ⴄ";s:3:"ⴄ";s:3:"Ⴅ";s:3:"ⴅ";s:3:"Ⴆ";s:3:"ⴆ";s:3:"Ⴇ";s:3:"ⴇ";s:3:"Ⴈ";s:3:"ⴈ";s:3:"Ⴉ";s:3:"ⴉ";s:3:"Ⴊ";s:3:"ⴊ";s:3:"Ⴋ";s:3:"ⴋ";s:3:"Ⴌ";s:3:"ⴌ";s:3:"Ⴍ";s:3:"ⴍ";s:3:"Ⴎ";s:3:"ⴎ";s:3:"Ⴏ";s:3:"ⴏ";s:3:"Ⴐ";s:3:"ⴐ";s:3:"Ⴑ";s:3:"ⴑ";s:3:"Ⴒ";s:3:"ⴒ";s:3:"Ⴓ";s:3:"ⴓ";s:3:"Ⴔ";s:3:"ⴔ";s:3:"Ⴕ";s:3:"ⴕ";s:3:"Ⴖ";s:3:"ⴖ";s:3:"Ⴗ";s:3:"ⴗ";s:3:"Ⴘ";s:3:"ⴘ";s:3:"Ⴙ";s:3:"ⴙ";s:3:"Ⴚ";s:3:"ⴚ";s:3:"Ⴛ";s:3:"ⴛ";s:3:"Ⴜ";s:3:"ⴜ";s:3:"Ⴝ";s:3:"ⴝ";s:3:"Ⴞ";s:3:"ⴞ";s:3:"Ⴟ";s:3:"ⴟ";s:3:"Ⴠ";s:3:"ⴠ";s:3:"Ⴡ";s:3:"ⴡ";s:3:"Ⴢ";s:3:"ⴢ";s:3:"Ⴣ";s:3:"ⴣ";s:3:"Ⴤ";s:3:"ⴤ";s:3:"Ⴥ";s:3:"ⴥ";s:3:"Ⴧ";s:3:"ⴧ";s:3:"Ⴭ";s:3:"ⴭ";s:3:"Ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"ḇ";s:3:"Ḉ";s:3:"ḉ";s:3:"Ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"ḓ";s:3:"Ḕ";s:3:"ḕ";s:3:"Ḗ";s:3:"ḗ";s:3:"Ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"ḛ";s:3:"Ḝ";s:3:"ḝ";s:3:"Ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"ḭ";s:3:"Ḯ";s:3:"ḯ";s:3:"Ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"ḷ";s:3:"Ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"ṋ";s:3:"Ṍ";s:3:"ṍ";s:3:"Ṏ";s:3:"ṏ";s:3:"Ṑ";s:3:"ṑ";s:3:"Ṓ";s:3:"ṓ";s:3:"Ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"ṛ";s:3:"Ṝ";s:3:"ṝ";s:3:"Ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"ṣ";s:3:"Ṥ";s:3:"ṥ";s:3:"Ṧ";s:3:"ṧ";s:3:"Ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"ṷ";s:3:"Ṹ";s:3:"ṹ";s:3:"Ṻ";s:3:"ṻ";s:3:"Ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẞ";s:2:"ß";s:3:"Ạ";s:3:"ạ";s:3:"Ả";s:3:"ả";s:3:"Ấ";s:3:"ấ";s:3:"Ầ";s:3:"ầ";s:3:"Ẩ";s:3:"ẩ";s:3:"Ẫ";s:3:"ẫ";s:3:"Ậ";s:3:"ậ";s:3:"Ắ";s:3:"ắ";s:3:"Ằ";s:3:"ằ";s:3:"Ẳ";s:3:"ẳ";s:3:"Ẵ";s:3:"ẵ";s:3:"Ặ";s:3:"ặ";s:3:"Ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"ẽ";s:3:"Ế";s:3:"ế";s:3:"Ề";s:3:"ề";s:3:"Ể";s:3:"ể";s:3:"Ễ";s:3:"ễ";s:3:"Ệ";s:3:"ệ";s:3:"Ỉ";s:3:"ỉ";s:3:"Ị";s:3:"ị";s:3:"Ọ";s:3:"ọ";s:3:"Ỏ";s:3:"ỏ";s:3:"Ố";s:3:"ố";s:3:"Ồ";s:3:"ồ";s:3:"Ổ";s:3:"ổ";s:3:"Ỗ";s:3:"ỗ";s:3:"Ộ";s:3:"ộ";s:3:"Ớ";s:3:"ớ";s:3:"Ờ";s:3:"ờ";s:3:"Ở";s:3:"ở";s:3:"Ỡ";s:3:"ỡ";s:3:"Ợ";s:3:"ợ";s:3:"Ụ";s:3:"ụ";s:3:"Ủ";s:3:"ủ";s:3:"Ứ";s:3:"ứ";s:3:"Ừ";s:3:"ừ";s:3:"Ử";s:3:"ử";s:3:"Ữ";s:3:"ữ";s:3:"Ự";s:3:"ự";s:3:"Ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"ỹ";s:3:"Ỻ";s:3:"ỻ";s:3:"Ỽ";s:3:"ỽ";s:3:"Ỿ";s:3:"ỿ";s:3:"Ἀ";s:3:"ἀ";s:3:"Ἁ";s:3:"ἁ";s:3:"Ἂ";s:3:"ἂ";s:3:"Ἃ";s:3:"ἃ";s:3:"Ἄ";s:3:"ἄ";s:3:"Ἅ";s:3:"ἅ";s:3:"Ἆ";s:3:"ἆ";s:3:"Ἇ";s:3:"ἇ";s:3:"Ἐ";s:3:"ἐ";s:3:"Ἑ";s:3:"ἑ";s:3:"Ἒ";s:3:"ἒ";s:3:"Ἓ";s:3:"ἓ";s:3:"Ἔ";s:3:"ἔ";s:3:"Ἕ";s:3:"ἕ";s:3:"Ἠ";s:3:"ἠ";s:3:"Ἡ";s:3:"ἡ";s:3:"Ἢ";s:3:"ἢ";s:3:"Ἣ";s:3:"ἣ";s:3:"Ἤ";s:3:"ἤ";s:3:"Ἥ";s:3:"ἥ";s:3:"Ἦ";s:3:"ἦ";s:3:"Ἧ";s:3:"ἧ";s:3:"Ἰ";s:3:"ἰ";s:3:"Ἱ";s:3:"ἱ";s:3:"Ἲ";s:3:"ἲ";s:3:"Ἳ";s:3:"ἳ";s:3:"Ἴ";s:3:"ἴ";s:3:"Ἵ";s:3:"ἵ";s:3:"Ἶ";s:3:"ἶ";s:3:"Ἷ";s:3:"ἷ";s:3:"Ὀ";s:3:"ὀ";s:3:"Ὁ";s:3:"ὁ";s:3:"Ὂ";s:3:"ὂ";s:3:"Ὃ";s:3:"ὃ";s:3:"Ὄ";s:3:"ὄ";s:3:"Ὅ";s:3:"ὅ";s:3:"Ὑ";s:3:"ὑ";s:3:"Ὓ";s:3:"ὓ";s:3:"Ὕ";s:3:"ὕ";s:3:"Ὗ";s:3:"ὗ";s:3:"Ὠ";s:3:"ὠ";s:3:"Ὡ";s:3:"ὡ";s:3:"Ὢ";s:3:"ὢ";s:3:"Ὣ";s:3:"ὣ";s:3:"Ὤ";s:3:"ὤ";s:3:"Ὥ";s:3:"ὥ";s:3:"Ὦ";s:3:"ὦ";s:3:"Ὧ";s:3:"ὧ";s:3:"ᾈ";s:3:"ᾀ";s:3:"ᾉ";s:3:"ᾁ";s:3:"ᾊ";s:3:"ᾂ";s:3:"ᾋ";s:3:"ᾃ";s:3:"ᾌ";s:3:"ᾄ";s:3:"ᾍ";s:3:"ᾅ";s:3:"ᾎ";s:3:"ᾆ";s:3:"ᾏ";s:3:"ᾇ";s:3:"ᾘ";s:3:"ᾐ";s:3:"ᾙ";s:3:"ᾑ";s:3:"ᾚ";s:3:"ᾒ";s:3:"ᾛ";s:3:"ᾓ";s:3:"ᾜ";s:3:"ᾔ";s:3:"ᾝ";s:3:"ᾕ";s:3:"ᾞ";s:3:"ᾖ";s:3:"ᾟ";s:3:"ᾗ";s:3:"ᾨ";s:3:"ᾠ";s:3:"ᾩ";s:3:"ᾡ";s:3:"ᾪ";s:3:"ᾢ";s:3:"ᾫ";s:3:"ᾣ";s:3:"ᾬ";s:3:"ᾤ";s:3:"ᾭ";s:3:"ᾥ";s:3:"ᾮ";s:3:"ᾦ";s:3:"ᾯ";s:3:"ᾧ";s:3:"Ᾰ";s:3:"ᾰ";s:3:"Ᾱ";s:3:"ᾱ";s:3:"Ὰ";s:3:"ὰ";s:3:"Ά";s:3:"ά";s:3:"ᾼ";s:3:"ᾳ";s:3:"Ὲ";s:3:"ὲ";s:3:"Έ";s:3:"έ";s:3:"Ὴ";s:3:"ὴ";s:3:"Ή";s:3:"ή";s:3:"ῌ";s:3:"ῃ";s:3:"Ῐ";s:3:"ῐ";s:3:"Ῑ";s:3:"ῑ";s:3:"Ὶ";s:3:"ὶ";s:3:"Ί";s:3:"ί";s:3:"Ῠ";s:3:"ῠ";s:3:"Ῡ";s:3:"ῡ";s:3:"Ὺ";s:3:"ὺ";s:3:"Ύ";s:3:"ύ";s:3:"Ῥ";s:3:"ῥ";s:3:"Ὸ";s:3:"ὸ";s:3:"Ό";s:3:"ό";s:3:"Ὼ";s:3:"ὼ";s:3:"Ώ";s:3:"ώ";s:3:"ῼ";s:3:"ῳ";s:3:"Ω";s:2:"ω";s:3:"K";s:1:"k";s:3:"Å";s:2:"å";s:3:"Ⅎ";s:3:"ⅎ";s:3:"Ⅰ";s:3:"ⅰ";s:3:"Ⅱ";s:3:"ⅱ";s:3:"Ⅲ";s:3:"ⅲ";s:3:"Ⅳ";s:3:"ⅳ";s:3:"Ⅴ";s:3:"ⅴ";s:3:"Ⅵ";s:3:"ⅵ";s:3:"Ⅶ";s:3:"ⅶ";s:3:"Ⅷ";s:3:"ⅷ";s:3:"Ⅸ";s:3:"ⅸ";s:3:"Ⅹ";s:3:"ⅹ";s:3:"Ⅺ";s:3:"ⅺ";s:3:"Ⅻ";s:3:"ⅻ";s:3:"Ⅼ";s:3:"ⅼ";s:3:"Ⅽ";s:3:"ⅽ";s:3:"Ⅾ";s:3:"ⅾ";s:3:"Ⅿ";s:3:"ⅿ";s:3:"Ↄ";s:3:"ↄ";s:3:"Ⓐ";s:3:"ⓐ";s:3:"Ⓑ";s:3:"ⓑ";s:3:"Ⓒ";s:3:"ⓒ";s:3:"Ⓓ";s:3:"ⓓ";s:3:"Ⓔ";s:3:"ⓔ";s:3:"Ⓕ";s:3:"ⓕ";s:3:"Ⓖ";s:3:"ⓖ";s:3:"Ⓗ";s:3:"ⓗ";s:3:"Ⓘ";s:3:"ⓘ";s:3:"Ⓙ";s:3:"ⓙ";s:3:"Ⓚ";s:3:"ⓚ";s:3:"Ⓛ";s:3:"ⓛ";s:3:"Ⓜ";s:3:"ⓜ";s:3:"Ⓝ";s:3:"ⓝ";s:3:"Ⓞ";s:3:"ⓞ";s:3:"Ⓟ";s:3:"ⓟ";s:3:"Ⓠ";s:3:"ⓠ";s:3:"Ⓡ";s:3:"ⓡ";s:3:"Ⓢ";s:3:"ⓢ";s:3:"Ⓣ";s:3:"ⓣ";s:3:"Ⓤ";s:3:"ⓤ";s:3:"Ⓥ";s:3:"ⓥ";s:3:"Ⓦ";s:3:"ⓦ";s:3:"Ⓧ";s:3:"ⓧ";s:3:"Ⓨ";s:3:"ⓨ";s:3:"Ⓩ";s:3:"ⓩ";s:3:"Ⰰ";s:3:"ⰰ";s:3:"Ⰱ";s:3:"ⰱ";s:3:"Ⰲ";s:3:"ⰲ";s:3:"Ⰳ";s:3:"ⰳ";s:3:"Ⰴ";s:3:"ⰴ";s:3:"Ⰵ";s:3:"ⰵ";s:3:"Ⰶ";s:3:"ⰶ";s:3:"Ⰷ";s:3:"ⰷ";s:3:"Ⰸ";s:3:"ⰸ";s:3:"Ⰹ";s:3:"ⰹ";s:3:"Ⰺ";s:3:"ⰺ";s:3:"Ⰻ";s:3:"ⰻ";s:3:"Ⰼ";s:3:"ⰼ";s:3:"Ⰽ";s:3:"ⰽ";s:3:"Ⰾ";s:3:"ⰾ";s:3:"Ⰿ";s:3:"ⰿ";s:3:"Ⱀ";s:3:"ⱀ";s:3:"Ⱁ";s:3:"ⱁ";s:3:"Ⱂ";s:3:"ⱂ";s:3:"Ⱃ";s:3:"ⱃ";s:3:"Ⱄ";s:3:"ⱄ";s:3:"Ⱅ";s:3:"ⱅ";s:3:"Ⱆ";s:3:"ⱆ";s:3:"Ⱇ";s:3:"ⱇ";s:3:"Ⱈ";s:3:"ⱈ";s:3:"Ⱉ";s:3:"ⱉ";s:3:"Ⱊ";s:3:"ⱊ";s:3:"Ⱋ";s:3:"ⱋ";s:3:"Ⱌ";s:3:"ⱌ";s:3:"Ⱍ";s:3:"ⱍ";s:3:"Ⱎ";s:3:"ⱎ";s:3:"Ⱏ";s:3:"ⱏ";s:3:"Ⱐ";s:3:"ⱐ";s:3:"Ⱑ";s:3:"ⱑ";s:3:"Ⱒ";s:3:"ⱒ";s:3:"Ⱓ";s:3:"ⱓ";s:3:"Ⱔ";s:3:"ⱔ";s:3:"Ⱕ";s:3:"ⱕ";s:3:"Ⱖ";s:3:"ⱖ";s:3:"Ⱗ";s:3:"ⱗ";s:3:"Ⱘ";s:3:"ⱘ";s:3:"Ⱙ";s:3:"ⱙ";s:3:"Ⱚ";s:3:"ⱚ";s:3:"Ⱛ";s:3:"ⱛ";s:3:"Ⱜ";s:3:"ⱜ";s:3:"Ⱝ";s:3:"ⱝ";s:3:"Ⱞ";s:3:"ⱞ";s:3:"Ⱡ";s:3:"ⱡ";s:3:"Ɫ";s:2:"ɫ";s:3:"Ᵽ";s:3:"ᵽ";s:3:"Ɽ";s:2:"ɽ";s:3:"Ⱨ";s:3:"ⱨ";s:3:"Ⱪ";s:3:"ⱪ";s:3:"Ⱬ";s:3:"ⱬ";s:3:"Ɑ";s:2:"ɑ";s:3:"Ɱ";s:2:"ɱ";s:3:"Ɐ";s:2:"ɐ";s:3:"Ɒ";s:2:"ɒ";s:3:"Ⱳ";s:3:"ⱳ";s:3:"Ⱶ";s:3:"ⱶ";s:3:"Ȿ";s:2:"ȿ";s:3:"Ɀ";s:2:"ɀ";s:3:"Ⲁ";s:3:"ⲁ";s:3:"Ⲃ";s:3:"ⲃ";s:3:"Ⲅ";s:3:"ⲅ";s:3:"Ⲇ";s:3:"ⲇ";s:3:"Ⲉ";s:3:"ⲉ";s:3:"Ⲋ";s:3:"ⲋ";s:3:"Ⲍ";s:3:"ⲍ";s:3:"Ⲏ";s:3:"ⲏ";s:3:"Ⲑ";s:3:"ⲑ";s:3:"Ⲓ";s:3:"ⲓ";s:3:"Ⲕ";s:3:"ⲕ";s:3:"Ⲗ";s:3:"ⲗ";s:3:"Ⲙ";s:3:"ⲙ";s:3:"Ⲛ";s:3:"ⲛ";s:3:"Ⲝ";s:3:"ⲝ";s:3:"Ⲟ";s:3:"ⲟ";s:3:"Ⲡ";s:3:"ⲡ";s:3:"Ⲣ";s:3:"ⲣ";s:3:"Ⲥ";s:3:"ⲥ";s:3:"Ⲧ";s:3:"ⲧ";s:3:"Ⲩ";s:3:"ⲩ";s:3:"Ⲫ";s:3:"ⲫ";s:3:"Ⲭ";s:3:"ⲭ";s:3:"Ⲯ";s:3:"ⲯ";s:3:"Ⲱ";s:3:"ⲱ";s:3:"Ⲳ";s:3:"ⲳ";s:3:"Ⲵ";s:3:"ⲵ";s:3:"Ⲷ";s:3:"ⲷ";s:3:"Ⲹ";s:3:"ⲹ";s:3:"Ⲻ";s:3:"ⲻ";s:3:"Ⲽ";s:3:"ⲽ";s:3:"Ⲿ";s:3:"ⲿ";s:3:"Ⳁ";s:3:"ⳁ";s:3:"Ⳃ";s:3:"ⳃ";s:3:"Ⳅ";s:3:"ⳅ";s:3:"Ⳇ";s:3:"ⳇ";s:3:"Ⳉ";s:3:"ⳉ";s:3:"Ⳋ";s:3:"ⳋ";s:3:"Ⳍ";s:3:"ⳍ";s:3:"Ⳏ";s:3:"ⳏ";s:3:"Ⳑ";s:3:"ⳑ";s:3:"Ⳓ";s:3:"ⳓ";s:3:"Ⳕ";s:3:"ⳕ";s:3:"Ⳗ";s:3:"ⳗ";s:3:"Ⳙ";s:3:"ⳙ";s:3:"Ⳛ";s:3:"ⳛ";s:3:"Ⳝ";s:3:"ⳝ";s:3:"Ⳟ";s:3:"ⳟ";s:3:"Ⳡ";s:3:"ⳡ";s:3:"Ⳣ";s:3:"ⳣ";s:3:"Ⳬ";s:3:"ⳬ";s:3:"Ⳮ";s:3:"ⳮ";s:3:"Ⳳ";s:3:"ⳳ";s:3:"Ꙁ";s:3:"ꙁ";s:3:"Ꙃ";s:3:"ꙃ";s:3:"Ꙅ";s:3:"ꙅ";s:3:"Ꙇ";s:3:"ꙇ";s:3:"Ꙉ";s:3:"ꙉ";s:3:"Ꙋ";s:3:"ꙋ";s:3:"Ꙍ";s:3:"ꙍ";s:3:"Ꙏ";s:3:"ꙏ";s:3:"Ꙑ";s:3:"ꙑ";s:3:"Ꙓ";s:3:"ꙓ";s:3:"Ꙕ";s:3:"ꙕ";s:3:"Ꙗ";s:3:"ꙗ";s:3:"Ꙙ";s:3:"ꙙ";s:3:"Ꙛ";s:3:"ꙛ";s:3:"Ꙝ";s:3:"ꙝ";s:3:"Ꙟ";s:3:"ꙟ";s:3:"Ꙡ";s:3:"ꙡ";s:3:"Ꙣ";s:3:"ꙣ";s:3:"Ꙥ";s:3:"ꙥ";s:3:"Ꙧ";s:3:"ꙧ";s:3:"Ꙩ";s:3:"ꙩ";s:3:"Ꙫ";s:3:"ꙫ";s:3:"Ꙭ";s:3:"ꙭ";s:3:"Ꚁ";s:3:"ꚁ";s:3:"Ꚃ";s:3:"ꚃ";s:3:"Ꚅ";s:3:"ꚅ";s:3:"Ꚇ";s:3:"ꚇ";s:3:"Ꚉ";s:3:"ꚉ";s:3:"Ꚋ";s:3:"ꚋ";s:3:"Ꚍ";s:3:"ꚍ";s:3:"Ꚏ";s:3:"ꚏ";s:3:"Ꚑ";s:3:"ꚑ";s:3:"Ꚓ";s:3:"ꚓ";s:3:"Ꚕ";s:3:"ꚕ";s:3:"Ꚗ";s:3:"ꚗ";s:3:"Ꚙ";s:3:"ꚙ";s:3:"Ꚛ";s:3:"ꚛ";s:3:"Ꜣ";s:3:"ꜣ";s:3:"Ꜥ";s:3:"ꜥ";s:3:"Ꜧ";s:3:"ꜧ";s:3:"Ꜩ";s:3:"ꜩ";s:3:"Ꜫ";s:3:"ꜫ";s:3:"Ꜭ";s:3:"ꜭ";s:3:"Ꜯ";s:3:"ꜯ";s:3:"Ꜳ";s:3:"ꜳ";s:3:"Ꜵ";s:3:"ꜵ";s:3:"Ꜷ";s:3:"ꜷ";s:3:"Ꜹ";s:3:"ꜹ";s:3:"Ꜻ";s:3:"ꜻ";s:3:"Ꜽ";s:3:"ꜽ";s:3:"Ꜿ";s:3:"ꜿ";s:3:"Ꝁ";s:3:"ꝁ";s:3:"Ꝃ";s:3:"ꝃ";s:3:"Ꝅ";s:3:"ꝅ";s:3:"Ꝇ";s:3:"ꝇ";s:3:"Ꝉ";s:3:"ꝉ";s:3:"Ꝋ";s:3:"ꝋ";s:3:"Ꝍ";s:3:"ꝍ";s:3:"Ꝏ";s:3:"ꝏ";s:3:"Ꝑ";s:3:"ꝑ";s:3:"Ꝓ";s:3:"ꝓ";s:3:"Ꝕ";s:3:"ꝕ";s:3:"Ꝗ";s:3:"ꝗ";s:3:"Ꝙ";s:3:"ꝙ";s:3:"Ꝛ";s:3:"ꝛ";s:3:"Ꝝ";s:3:"ꝝ";s:3:"Ꝟ";s:3:"ꝟ";s:3:"Ꝡ";s:3:"ꝡ";s:3:"Ꝣ";s:3:"ꝣ";s:3:"Ꝥ";s:3:"ꝥ";s:3:"Ꝧ";s:3:"ꝧ";s:3:"Ꝩ";s:3:"ꝩ";s:3:"Ꝫ";s:3:"ꝫ";s:3:"Ꝭ";s:3:"ꝭ";s:3:"Ꝯ";s:3:"ꝯ";s:3:"Ꝺ";s:3:"ꝺ";s:3:"Ꝼ";s:3:"ꝼ";s:3:"Ᵹ";s:3:"ᵹ";s:3:"Ꝿ";s:3:"ꝿ";s:3:"Ꞁ";s:3:"ꞁ";s:3:"Ꞃ";s:3:"ꞃ";s:3:"Ꞅ";s:3:"ꞅ";s:3:"Ꞇ";s:3:"ꞇ";s:3:"Ꞌ";s:3:"ꞌ";s:3:"Ɥ";s:2:"ɥ";s:3:"Ꞑ";s:3:"ꞑ";s:3:"Ꞓ";s:3:"ꞓ";s:3:"Ꞗ";s:3:"ꞗ";s:3:"Ꞙ";s:3:"ꞙ";s:3:"Ꞛ";s:3:"ꞛ";s:3:"Ꞝ";s:3:"ꞝ";s:3:"Ꞟ";s:3:"ꞟ";s:3:"Ꞡ";s:3:"ꞡ";s:3:"Ꞣ";s:3:"ꞣ";s:3:"Ꞥ";s:3:"ꞥ";s:3:"Ꞧ";s:3:"ꞧ";s:3:"Ꞩ";s:3:"ꞩ";s:3:"Ɦ";s:2:"ɦ";s:3:"Ɜ";s:2:"ɜ";s:3:"Ɡ";s:2:"ɡ";s:3:"Ɬ";s:2:"ɬ";s:3:"Ʞ";s:2:"ʞ";s:3:"Ʇ";s:2:"ʇ";s:3:"A";s:3:"a";s:3:"B";s:3:"b";s:3:"C";s:3:"c";s:3:"D";s:3:"d";s:3:"E";s:3:"e";s:3:"F";s:3:"f";s:3:"G";s:3:"g";s:3:"H";s:3:"h";s:3:"I";s:3:"i";s:3:"J";s:3:"j";s:3:"K";s:3:"k";s:3:"L";s:3:"l";s:3:"M";s:3:"m";s:3:"N";s:3:"n";s:3:"O";s:3:"o";s:3:"P";s:3:"p";s:3:"Q";s:3:"q";s:3:"R";s:3:"r";s:3:"S";s:3:"s";s:3:"T";s:3:"t";s:3:"U";s:3:"u";s:3:"V";s:3:"v";s:3:"W";s:3:"w";s:3:"X";s:3:"x";s:3:"Y";s:3:"y";s:3:"Z";s:3:"z";s:4:"𐐀";s:4:"𐐨";s:4:"𐐁";s:4:"𐐩";s:4:"𐐂";s:4:"𐐪";s:4:"𐐃";s:4:"𐐫";s:4:"𐐄";s:4:"𐐬";s:4:"𐐅";s:4:"𐐭";s:4:"𐐆";s:4:"𐐮";s:4:"𐐇";s:4:"𐐯";s:4:"𐐈";s:4:"𐐰";s:4:"𐐉";s:4:"𐐱";s:4:"𐐊";s:4:"𐐲";s:4:"𐐋";s:4:"𐐳";s:4:"𐐌";s:4:"𐐴";s:4:"𐐍";s:4:"𐐵";s:4:"𐐎";s:4:"𐐶";s:4:"𐐏";s:4:"𐐷";s:4:"𐐐";s:4:"𐐸";s:4:"𐐑";s:4:"𐐹";s:4:"𐐒";s:4:"𐐺";s:4:"𐐓";s:4:"𐐻";s:4:"𐐔";s:4:"𐐼";s:4:"𐐕";s:4:"𐐽";s:4:"𐐖";s:4:"𐐾";s:4:"𐐗";s:4:"𐐿";s:4:"𐐘";s:4:"𐑀";s:4:"𐐙";s:4:"𐑁";s:4:"𐐚";s:4:"𐑂";s:4:"𐐛";s:4:"𐑃";s:4:"𐐜";s:4:"𐑄";s:4:"𐐝";s:4:"𐑅";s:4:"𐐞";s:4:"𐑆";s:4:"𐐟";s:4:"𐑇";s:4:"𐐠";s:4:"𐑈";s:4:"𐐡";s:4:"𐑉";s:4:"𐐢";s:4:"𐑊";s:4:"𐐣";s:4:"𐑋";s:4:"𐐤";s:4:"𐑌";s:4:"𐐥";s:4:"𐑍";s:4:"𐐦";s:4:"𐑎";s:4:"𐐧";s:4:"𐑏";s:4:"𑢠";s:4:"𑣀";s:4:"𑢡";s:4:"𑣁";s:4:"𑢢";s:4:"𑣂";s:4:"𑢣";s:4:"𑣃";s:4:"𑢤";s:4:"𑣄";s:4:"𑢥";s:4:"𑣅";s:4:"𑢦";s:4:"𑣆";s:4:"𑢧";s:4:"𑣇";s:4:"𑢨";s:4:"𑣈";s:4:"𑢩";s:4:"𑣉";s:4:"𑢪";s:4:"𑣊";s:4:"𑢫";s:4:"𑣋";s:4:"𑢬";s:4:"𑣌";s:4:"𑢭";s:4:"𑣍";s:4:"𑢮";s:4:"𑣎";s:4:"𑢯";s:4:"𑣏";s:4:"𑢰";s:4:"𑣐";s:4:"𑢱";s:4:"𑣑";s:4:"𑢲";s:4:"𑣒";s:4:"𑢳";s:4:"𑣓";s:4:"𑢴";s:4:"𑣔";s:4:"𑢵";s:4:"𑣕";s:4:"𑢶";s:4:"𑣖";s:4:"𑢷";s:4:"𑣗";s:4:"𑢸";s:4:"𑣘";s:4:"𑢹";s:4:"𑣙";s:4:"𑢺";s:4:"𑣚";s:4:"𑢻";s:4:"𑣛";s:4:"𑢼";s:4:"𑣜";s:4:"𑢽";s:4:"𑣝";s:4:"𑢾";s:4:"𑣞";s:4:"𑢿";s:4:"𑣟";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/upperCase.ser b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/upperCase.ser new file mode 100644 index 0000000..e9e0ec2 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/PHP/Shim/unidata/upperCase.ser @@ -0,0 +1 @@ +a:1100:{s:1:"a";s:1:"A";s:1:"b";s:1:"B";s:1:"c";s:1:"C";s:1:"d";s:1:"D";s:1:"e";s:1:"E";s:1:"f";s:1:"F";s:1:"g";s:1:"G";s:1:"h";s:1:"H";s:1:"i";s:1:"I";s:1:"j";s:1:"J";s:1:"k";s:1:"K";s:1:"l";s:1:"L";s:1:"m";s:1:"M";s:1:"n";s:1:"N";s:1:"o";s:1:"O";s:1:"p";s:1:"P";s:1:"q";s:1:"Q";s:1:"r";s:1:"R";s:1:"s";s:1:"S";s:1:"t";s:1:"T";s:1:"u";s:1:"U";s:1:"v";s:1:"V";s:1:"w";s:1:"W";s:1:"x";s:1:"X";s:1:"y";s:1:"Y";s:1:"z";s:1:"Z";s:2:"µ";s:2:"Μ";s:2:"à";s:2:"À";s:2:"á";s:2:"Á";s:2:"â";s:2:"Â";s:2:"ã";s:2:"Ã";s:2:"ä";s:2:"Ä";s:2:"å";s:2:"Å";s:2:"æ";s:2:"Æ";s:2:"ç";s:2:"Ç";s:2:"è";s:2:"È";s:2:"é";s:2:"É";s:2:"ê";s:2:"Ê";s:2:"ë";s:2:"Ë";s:2:"ì";s:2:"Ì";s:2:"í";s:2:"Í";s:2:"î";s:2:"Î";s:2:"ï";s:2:"Ï";s:2:"ð";s:2:"Ð";s:2:"ñ";s:2:"Ñ";s:2:"ò";s:2:"Ò";s:2:"ó";s:2:"Ó";s:2:"ô";s:2:"Ô";s:2:"õ";s:2:"Õ";s:2:"ö";s:2:"Ö";s:2:"ø";s:2:"Ø";s:2:"ù";s:2:"Ù";s:2:"ú";s:2:"Ú";s:2:"û";s:2:"Û";s:2:"ü";s:2:"Ü";s:2:"ý";s:2:"Ý";s:2:"þ";s:2:"Þ";s:2:"ÿ";s:2:"Ÿ";s:2:"ā";s:2:"Ā";s:2:"ă";s:2:"Ă";s:2:"ą";s:2:"Ą";s:2:"ć";s:2:"Ć";s:2:"ĉ";s:2:"Ĉ";s:2:"ċ";s:2:"Ċ";s:2:"č";s:2:"Č";s:2:"ď";s:2:"Ď";s:2:"đ";s:2:"Đ";s:2:"ē";s:2:"Ē";s:2:"ĕ";s:2:"Ĕ";s:2:"ė";s:2:"Ė";s:2:"ę";s:2:"Ę";s:2:"ě";s:2:"Ě";s:2:"ĝ";s:2:"Ĝ";s:2:"ğ";s:2:"Ğ";s:2:"ġ";s:2:"Ġ";s:2:"ģ";s:2:"Ģ";s:2:"ĥ";s:2:"Ĥ";s:2:"ħ";s:2:"Ħ";s:2:"ĩ";s:2:"Ĩ";s:2:"ī";s:2:"Ī";s:2:"ĭ";s:2:"Ĭ";s:2:"į";s:2:"Į";s:2:"ı";s:1:"I";s:2:"ij";s:2:"IJ";s:2:"ĵ";s:2:"Ĵ";s:2:"ķ";s:2:"Ķ";s:2:"ĺ";s:2:"Ĺ";s:2:"ļ";s:2:"Ļ";s:2:"ľ";s:2:"Ľ";s:2:"ŀ";s:2:"Ŀ";s:2:"ł";s:2:"Ł";s:2:"ń";s:2:"Ń";s:2:"ņ";s:2:"Ņ";s:2:"ň";s:2:"Ň";s:2:"ŋ";s:2:"Ŋ";s:2:"ō";s:2:"Ō";s:2:"ŏ";s:2:"Ŏ";s:2:"ő";s:2:"Ő";s:2:"œ";s:2:"Œ";s:2:"ŕ";s:2:"Ŕ";s:2:"ŗ";s:2:"Ŗ";s:2:"ř";s:2:"Ř";s:2:"ś";s:2:"Ś";s:2:"ŝ";s:2:"Ŝ";s:2:"ş";s:2:"Ş";s:2:"š";s:2:"Š";s:2:"ţ";s:2:"Ţ";s:2:"ť";s:2:"Ť";s:2:"ŧ";s:2:"Ŧ";s:2:"ũ";s:2:"Ũ";s:2:"ū";s:2:"Ū";s:2:"ŭ";s:2:"Ŭ";s:2:"ů";s:2:"Ů";s:2:"ű";s:2:"Ű";s:2:"ų";s:2:"Ų";s:2:"ŵ";s:2:"Ŵ";s:2:"ŷ";s:2:"Ŷ";s:2:"ź";s:2:"Ź";s:2:"ż";s:2:"Ż";s:2:"ž";s:2:"Ž";s:2:"ſ";s:1:"S";s:2:"ƀ";s:2:"Ƀ";s:2:"ƃ";s:2:"Ƃ";s:2:"ƅ";s:2:"Ƅ";s:2:"ƈ";s:2:"Ƈ";s:2:"ƌ";s:2:"Ƌ";s:2:"ƒ";s:2:"Ƒ";s:2:"ƕ";s:2:"Ƕ";s:2:"ƙ";s:2:"Ƙ";s:2:"ƚ";s:2:"Ƚ";s:2:"ƞ";s:2:"Ƞ";s:2:"ơ";s:2:"Ơ";s:2:"ƣ";s:2:"Ƣ";s:2:"ƥ";s:2:"Ƥ";s:2:"ƨ";s:2:"Ƨ";s:2:"ƭ";s:2:"Ƭ";s:2:"ư";s:2:"Ư";s:2:"ƴ";s:2:"Ƴ";s:2:"ƶ";s:2:"Ƶ";s:2:"ƹ";s:2:"Ƹ";s:2:"ƽ";s:2:"Ƽ";s:2:"ƿ";s:2:"Ƿ";s:2:"Dž";s:2:"DŽ";s:2:"dž";s:2:"DŽ";s:2:"Lj";s:2:"LJ";s:2:"lj";s:2:"LJ";s:2:"Nj";s:2:"NJ";s:2:"nj";s:2:"NJ";s:2:"ǎ";s:2:"Ǎ";s:2:"ǐ";s:2:"Ǐ";s:2:"ǒ";s:2:"Ǒ";s:2:"ǔ";s:2:"Ǔ";s:2:"ǖ";s:2:"Ǖ";s:2:"ǘ";s:2:"Ǘ";s:2:"ǚ";s:2:"Ǚ";s:2:"ǜ";s:2:"Ǜ";s:2:"ǝ";s:2:"Ǝ";s:2:"ǟ";s:2:"Ǟ";s:2:"ǡ";s:2:"Ǡ";s:2:"ǣ";s:2:"Ǣ";s:2:"ǥ";s:2:"Ǥ";s:2:"ǧ";s:2:"Ǧ";s:2:"ǩ";s:2:"Ǩ";s:2:"ǫ";s:2:"Ǫ";s:2:"ǭ";s:2:"Ǭ";s:2:"ǯ";s:2:"Ǯ";s:2:"Dz";s:2:"DZ";s:2:"dz";s:2:"DZ";s:2:"ǵ";s:2:"Ǵ";s:2:"ǹ";s:2:"Ǹ";s:2:"ǻ";s:2:"Ǻ";s:2:"ǽ";s:2:"Ǽ";s:2:"ǿ";s:2:"Ǿ";s:2:"ȁ";s:2:"Ȁ";s:2:"ȃ";s:2:"Ȃ";s:2:"ȅ";s:2:"Ȅ";s:2:"ȇ";s:2:"Ȇ";s:2:"ȉ";s:2:"Ȉ";s:2:"ȋ";s:2:"Ȋ";s:2:"ȍ";s:2:"Ȍ";s:2:"ȏ";s:2:"Ȏ";s:2:"ȑ";s:2:"Ȑ";s:2:"ȓ";s:2:"Ȓ";s:2:"ȕ";s:2:"Ȕ";s:2:"ȗ";s:2:"Ȗ";s:2:"ș";s:2:"Ș";s:2:"ț";s:2:"Ț";s:2:"ȝ";s:2:"Ȝ";s:2:"ȟ";s:2:"Ȟ";s:2:"ȣ";s:2:"Ȣ";s:2:"ȥ";s:2:"Ȥ";s:2:"ȧ";s:2:"Ȧ";s:2:"ȩ";s:2:"Ȩ";s:2:"ȫ";s:2:"Ȫ";s:2:"ȭ";s:2:"Ȭ";s:2:"ȯ";s:2:"Ȯ";s:2:"ȱ";s:2:"Ȱ";s:2:"ȳ";s:2:"Ȳ";s:2:"ȼ";s:2:"Ȼ";s:2:"ȿ";s:3:"Ȿ";s:2:"ɀ";s:3:"Ɀ";s:2:"ɂ";s:2:"Ɂ";s:2:"ɇ";s:2:"Ɇ";s:2:"ɉ";s:2:"Ɉ";s:2:"ɋ";s:2:"Ɋ";s:2:"ɍ";s:2:"Ɍ";s:2:"ɏ";s:2:"Ɏ";s:2:"ɐ";s:3:"Ɐ";s:2:"ɑ";s:3:"Ɑ";s:2:"ɒ";s:3:"Ɒ";s:2:"ɓ";s:2:"Ɓ";s:2:"ɔ";s:2:"Ɔ";s:2:"ɖ";s:2:"Ɖ";s:2:"ɗ";s:2:"Ɗ";s:2:"ə";s:2:"Ə";s:2:"ɛ";s:2:"Ɛ";s:2:"ɜ";s:3:"Ɜ";s:2:"ɠ";s:2:"Ɠ";s:2:"ɡ";s:3:"Ɡ";s:2:"ɣ";s:2:"Ɣ";s:2:"ɥ";s:3:"Ɥ";s:2:"ɦ";s:3:"Ɦ";s:2:"ɨ";s:2:"Ɨ";s:2:"ɩ";s:2:"Ɩ";s:2:"ɫ";s:3:"Ɫ";s:2:"ɬ";s:3:"Ɬ";s:2:"ɯ";s:2:"Ɯ";s:2:"ɱ";s:3:"Ɱ";s:2:"ɲ";s:2:"Ɲ";s:2:"ɵ";s:2:"Ɵ";s:2:"ɽ";s:3:"Ɽ";s:2:"ʀ";s:2:"Ʀ";s:2:"ʃ";s:2:"Ʃ";s:2:"ʇ";s:3:"Ʇ";s:2:"ʈ";s:2:"Ʈ";s:2:"ʉ";s:2:"Ʉ";s:2:"ʊ";s:2:"Ʊ";s:2:"ʋ";s:2:"Ʋ";s:2:"ʌ";s:2:"Ʌ";s:2:"ʒ";s:2:"Ʒ";s:2:"ʞ";s:3:"Ʞ";s:2:"ͅ";s:2:"Ι";s:2:"ͱ";s:2:"Ͱ";s:2:"ͳ";s:2:"Ͳ";s:2:"ͷ";s:2:"Ͷ";s:2:"ͻ";s:2:"Ͻ";s:2:"ͼ";s:2:"Ͼ";s:2:"ͽ";s:2:"Ͽ";s:2:"ά";s:2:"Ά";s:2:"έ";s:2:"Έ";s:2:"ή";s:2:"Ή";s:2:"ί";s:2:"Ί";s:2:"α";s:2:"Α";s:2:"β";s:2:"Β";s:2:"γ";s:2:"Γ";s:2:"δ";s:2:"Δ";s:2:"ε";s:2:"Ε";s:2:"ζ";s:2:"Ζ";s:2:"η";s:2:"Η";s:2:"θ";s:2:"Θ";s:2:"ι";s:2:"Ι";s:2:"κ";s:2:"Κ";s:2:"λ";s:2:"Λ";s:2:"μ";s:2:"Μ";s:2:"ν";s:2:"Ν";s:2:"ξ";s:2:"Ξ";s:2:"ο";s:2:"Ο";s:2:"π";s:2:"Π";s:2:"ρ";s:2:"Ρ";s:2:"ς";s:2:"Σ";s:2:"σ";s:2:"Σ";s:2:"τ";s:2:"Τ";s:2:"υ";s:2:"Υ";s:2:"φ";s:2:"Φ";s:2:"χ";s:2:"Χ";s:2:"ψ";s:2:"Ψ";s:2:"ω";s:2:"Ω";s:2:"ϊ";s:2:"Ϊ";s:2:"ϋ";s:2:"Ϋ";s:2:"ό";s:2:"Ό";s:2:"ύ";s:2:"Ύ";s:2:"ώ";s:2:"Ώ";s:2:"ϐ";s:2:"Β";s:2:"ϑ";s:2:"Θ";s:2:"ϕ";s:2:"Φ";s:2:"ϖ";s:2:"Π";s:2:"ϗ";s:2:"Ϗ";s:2:"ϙ";s:2:"Ϙ";s:2:"ϛ";s:2:"Ϛ";s:2:"ϝ";s:2:"Ϝ";s:2:"ϟ";s:2:"Ϟ";s:2:"ϡ";s:2:"Ϡ";s:2:"ϣ";s:2:"Ϣ";s:2:"ϥ";s:2:"Ϥ";s:2:"ϧ";s:2:"Ϧ";s:2:"ϩ";s:2:"Ϩ";s:2:"ϫ";s:2:"Ϫ";s:2:"ϭ";s:2:"Ϭ";s:2:"ϯ";s:2:"Ϯ";s:2:"ϰ";s:2:"Κ";s:2:"ϱ";s:2:"Ρ";s:2:"ϲ";s:2:"Ϲ";s:2:"ϳ";s:2:"Ϳ";s:2:"ϵ";s:2:"Ε";s:2:"ϸ";s:2:"Ϸ";s:2:"ϻ";s:2:"Ϻ";s:2:"а";s:2:"А";s:2:"б";s:2:"Б";s:2:"в";s:2:"В";s:2:"г";s:2:"Г";s:2:"д";s:2:"Д";s:2:"е";s:2:"Е";s:2:"ж";s:2:"Ж";s:2:"з";s:2:"З";s:2:"и";s:2:"И";s:2:"й";s:2:"Й";s:2:"к";s:2:"К";s:2:"л";s:2:"Л";s:2:"м";s:2:"М";s:2:"н";s:2:"Н";s:2:"о";s:2:"О";s:2:"п";s:2:"П";s:2:"р";s:2:"Р";s:2:"с";s:2:"С";s:2:"т";s:2:"Т";s:2:"у";s:2:"У";s:2:"ф";s:2:"Ф";s:2:"х";s:2:"Х";s:2:"ц";s:2:"Ц";s:2:"ч";s:2:"Ч";s:2:"ш";s:2:"Ш";s:2:"щ";s:2:"Щ";s:2:"ъ";s:2:"Ъ";s:2:"ы";s:2:"Ы";s:2:"ь";s:2:"Ь";s:2:"э";s:2:"Э";s:2:"ю";s:2:"Ю";s:2:"я";s:2:"Я";s:2:"ѐ";s:2:"Ѐ";s:2:"ё";s:2:"Ё";s:2:"ђ";s:2:"Ђ";s:2:"ѓ";s:2:"Ѓ";s:2:"є";s:2:"Є";s:2:"ѕ";s:2:"Ѕ";s:2:"і";s:2:"І";s:2:"ї";s:2:"Ї";s:2:"ј";s:2:"Ј";s:2:"љ";s:2:"Љ";s:2:"њ";s:2:"Њ";s:2:"ћ";s:2:"Ћ";s:2:"ќ";s:2:"Ќ";s:2:"ѝ";s:2:"Ѝ";s:2:"ў";s:2:"Ў";s:2:"џ";s:2:"Џ";s:2:"ѡ";s:2:"Ѡ";s:2:"ѣ";s:2:"Ѣ";s:2:"ѥ";s:2:"Ѥ";s:2:"ѧ";s:2:"Ѧ";s:2:"ѩ";s:2:"Ѩ";s:2:"ѫ";s:2:"Ѫ";s:2:"ѭ";s:2:"Ѭ";s:2:"ѯ";s:2:"Ѯ";s:2:"ѱ";s:2:"Ѱ";s:2:"ѳ";s:2:"Ѳ";s:2:"ѵ";s:2:"Ѵ";s:2:"ѷ";s:2:"Ѷ";s:2:"ѹ";s:2:"Ѹ";s:2:"ѻ";s:2:"Ѻ";s:2:"ѽ";s:2:"Ѽ";s:2:"ѿ";s:2:"Ѿ";s:2:"ҁ";s:2:"Ҁ";s:2:"ҋ";s:2:"Ҋ";s:2:"ҍ";s:2:"Ҍ";s:2:"ҏ";s:2:"Ҏ";s:2:"ґ";s:2:"Ґ";s:2:"ғ";s:2:"Ғ";s:2:"ҕ";s:2:"Ҕ";s:2:"җ";s:2:"Җ";s:2:"ҙ";s:2:"Ҙ";s:2:"қ";s:2:"Қ";s:2:"ҝ";s:2:"Ҝ";s:2:"ҟ";s:2:"Ҟ";s:2:"ҡ";s:2:"Ҡ";s:2:"ң";s:2:"Ң";s:2:"ҥ";s:2:"Ҥ";s:2:"ҧ";s:2:"Ҧ";s:2:"ҩ";s:2:"Ҩ";s:2:"ҫ";s:2:"Ҫ";s:2:"ҭ";s:2:"Ҭ";s:2:"ү";s:2:"Ү";s:2:"ұ";s:2:"Ұ";s:2:"ҳ";s:2:"Ҳ";s:2:"ҵ";s:2:"Ҵ";s:2:"ҷ";s:2:"Ҷ";s:2:"ҹ";s:2:"Ҹ";s:2:"һ";s:2:"Һ";s:2:"ҽ";s:2:"Ҽ";s:2:"ҿ";s:2:"Ҿ";s:2:"ӂ";s:2:"Ӂ";s:2:"ӄ";s:2:"Ӄ";s:2:"ӆ";s:2:"Ӆ";s:2:"ӈ";s:2:"Ӈ";s:2:"ӊ";s:2:"Ӊ";s:2:"ӌ";s:2:"Ӌ";s:2:"ӎ";s:2:"Ӎ";s:2:"ӏ";s:2:"Ӏ";s:2:"ӑ";s:2:"Ӑ";s:2:"ӓ";s:2:"Ӓ";s:2:"ӕ";s:2:"Ӕ";s:2:"ӗ";s:2:"Ӗ";s:2:"ә";s:2:"Ә";s:2:"ӛ";s:2:"Ӛ";s:2:"ӝ";s:2:"Ӝ";s:2:"ӟ";s:2:"Ӟ";s:2:"ӡ";s:2:"Ӡ";s:2:"ӣ";s:2:"Ӣ";s:2:"ӥ";s:2:"Ӥ";s:2:"ӧ";s:2:"Ӧ";s:2:"ө";s:2:"Ө";s:2:"ӫ";s:2:"Ӫ";s:2:"ӭ";s:2:"Ӭ";s:2:"ӯ";s:2:"Ӯ";s:2:"ӱ";s:2:"Ӱ";s:2:"ӳ";s:2:"Ӳ";s:2:"ӵ";s:2:"Ӵ";s:2:"ӷ";s:2:"Ӷ";s:2:"ӹ";s:2:"Ӹ";s:2:"ӻ";s:2:"Ӻ";s:2:"ӽ";s:2:"Ӽ";s:2:"ӿ";s:2:"Ӿ";s:2:"ԁ";s:2:"Ԁ";s:2:"ԃ";s:2:"Ԃ";s:2:"ԅ";s:2:"Ԅ";s:2:"ԇ";s:2:"Ԇ";s:2:"ԉ";s:2:"Ԉ";s:2:"ԋ";s:2:"Ԋ";s:2:"ԍ";s:2:"Ԍ";s:2:"ԏ";s:2:"Ԏ";s:2:"ԑ";s:2:"Ԑ";s:2:"ԓ";s:2:"Ԓ";s:2:"ԕ";s:2:"Ԕ";s:2:"ԗ";s:2:"Ԗ";s:2:"ԙ";s:2:"Ԙ";s:2:"ԛ";s:2:"Ԛ";s:2:"ԝ";s:2:"Ԝ";s:2:"ԟ";s:2:"Ԟ";s:2:"ԡ";s:2:"Ԡ";s:2:"ԣ";s:2:"Ԣ";s:2:"ԥ";s:2:"Ԥ";s:2:"ԧ";s:2:"Ԧ";s:2:"ԩ";s:2:"Ԩ";s:2:"ԫ";s:2:"Ԫ";s:2:"ԭ";s:2:"Ԭ";s:2:"ԯ";s:2:"Ԯ";s:2:"ա";s:2:"Ա";s:2:"բ";s:2:"Բ";s:2:"գ";s:2:"Գ";s:2:"դ";s:2:"Դ";s:2:"ե";s:2:"Ե";s:2:"զ";s:2:"Զ";s:2:"է";s:2:"Է";s:2:"ը";s:2:"Ը";s:2:"թ";s:2:"Թ";s:2:"ժ";s:2:"Ժ";s:2:"ի";s:2:"Ի";s:2:"լ";s:2:"Լ";s:2:"խ";s:2:"Խ";s:2:"ծ";s:2:"Ծ";s:2:"կ";s:2:"Կ";s:2:"հ";s:2:"Հ";s:2:"ձ";s:2:"Ձ";s:2:"ղ";s:2:"Ղ";s:2:"ճ";s:2:"Ճ";s:2:"մ";s:2:"Մ";s:2:"յ";s:2:"Յ";s:2:"ն";s:2:"Ն";s:2:"շ";s:2:"Շ";s:2:"ո";s:2:"Ո";s:2:"չ";s:2:"Չ";s:2:"պ";s:2:"Պ";s:2:"ջ";s:2:"Ջ";s:2:"ռ";s:2:"Ռ";s:2:"ս";s:2:"Ս";s:2:"վ";s:2:"Վ";s:2:"տ";s:2:"Տ";s:2:"ր";s:2:"Ր";s:2:"ց";s:2:"Ց";s:2:"ւ";s:2:"Ւ";s:2:"փ";s:2:"Փ";s:2:"ք";s:2:"Ք";s:2:"օ";s:2:"Օ";s:2:"ֆ";s:2:"Ֆ";s:3:"ᵹ";s:3:"Ᵹ";s:3:"ᵽ";s:3:"Ᵽ";s:3:"ḁ";s:3:"Ḁ";s:3:"ḃ";s:3:"Ḃ";s:3:"ḅ";s:3:"Ḅ";s:3:"ḇ";s:3:"Ḇ";s:3:"ḉ";s:3:"Ḉ";s:3:"ḋ";s:3:"Ḋ";s:3:"ḍ";s:3:"Ḍ";s:3:"ḏ";s:3:"Ḏ";s:3:"ḑ";s:3:"Ḑ";s:3:"ḓ";s:3:"Ḓ";s:3:"ḕ";s:3:"Ḕ";s:3:"ḗ";s:3:"Ḗ";s:3:"ḙ";s:3:"Ḙ";s:3:"ḛ";s:3:"Ḛ";s:3:"ḝ";s:3:"Ḝ";s:3:"ḟ";s:3:"Ḟ";s:3:"ḡ";s:3:"Ḡ";s:3:"ḣ";s:3:"Ḣ";s:3:"ḥ";s:3:"Ḥ";s:3:"ḧ";s:3:"Ḧ";s:3:"ḩ";s:3:"Ḩ";s:3:"ḫ";s:3:"Ḫ";s:3:"ḭ";s:3:"Ḭ";s:3:"ḯ";s:3:"Ḯ";s:3:"ḱ";s:3:"Ḱ";s:3:"ḳ";s:3:"Ḳ";s:3:"ḵ";s:3:"Ḵ";s:3:"ḷ";s:3:"Ḷ";s:3:"ḹ";s:3:"Ḹ";s:3:"ḻ";s:3:"Ḻ";s:3:"ḽ";s:3:"Ḽ";s:3:"ḿ";s:3:"Ḿ";s:3:"ṁ";s:3:"Ṁ";s:3:"ṃ";s:3:"Ṃ";s:3:"ṅ";s:3:"Ṅ";s:3:"ṇ";s:3:"Ṇ";s:3:"ṉ";s:3:"Ṉ";s:3:"ṋ";s:3:"Ṋ";s:3:"ṍ";s:3:"Ṍ";s:3:"ṏ";s:3:"Ṏ";s:3:"ṑ";s:3:"Ṑ";s:3:"ṓ";s:3:"Ṓ";s:3:"ṕ";s:3:"Ṕ";s:3:"ṗ";s:3:"Ṗ";s:3:"ṙ";s:3:"Ṙ";s:3:"ṛ";s:3:"Ṛ";s:3:"ṝ";s:3:"Ṝ";s:3:"ṟ";s:3:"Ṟ";s:3:"ṡ";s:3:"Ṡ";s:3:"ṣ";s:3:"Ṣ";s:3:"ṥ";s:3:"Ṥ";s:3:"ṧ";s:3:"Ṧ";s:3:"ṩ";s:3:"Ṩ";s:3:"ṫ";s:3:"Ṫ";s:3:"ṭ";s:3:"Ṭ";s:3:"ṯ";s:3:"Ṯ";s:3:"ṱ";s:3:"Ṱ";s:3:"ṳ";s:3:"Ṳ";s:3:"ṵ";s:3:"Ṵ";s:3:"ṷ";s:3:"Ṷ";s:3:"ṹ";s:3:"Ṹ";s:3:"ṻ";s:3:"Ṻ";s:3:"ṽ";s:3:"Ṽ";s:3:"ṿ";s:3:"Ṿ";s:3:"ẁ";s:3:"Ẁ";s:3:"ẃ";s:3:"Ẃ";s:3:"ẅ";s:3:"Ẅ";s:3:"ẇ";s:3:"Ẇ";s:3:"ẉ";s:3:"Ẉ";s:3:"ẋ";s:3:"Ẋ";s:3:"ẍ";s:3:"Ẍ";s:3:"ẏ";s:3:"Ẏ";s:3:"ẑ";s:3:"Ẑ";s:3:"ẓ";s:3:"Ẓ";s:3:"ẕ";s:3:"Ẕ";s:3:"ẛ";s:3:"Ṡ";s:3:"ạ";s:3:"Ạ";s:3:"ả";s:3:"Ả";s:3:"ấ";s:3:"Ấ";s:3:"ầ";s:3:"Ầ";s:3:"ẩ";s:3:"Ẩ";s:3:"ẫ";s:3:"Ẫ";s:3:"ậ";s:3:"Ậ";s:3:"ắ";s:3:"Ắ";s:3:"ằ";s:3:"Ằ";s:3:"ẳ";s:3:"Ẳ";s:3:"ẵ";s:3:"Ẵ";s:3:"ặ";s:3:"Ặ";s:3:"ẹ";s:3:"Ẹ";s:3:"ẻ";s:3:"Ẻ";s:3:"ẽ";s:3:"Ẽ";s:3:"ế";s:3:"Ế";s:3:"ề";s:3:"Ề";s:3:"ể";s:3:"Ể";s:3:"ễ";s:3:"Ễ";s:3:"ệ";s:3:"Ệ";s:3:"ỉ";s:3:"Ỉ";s:3:"ị";s:3:"Ị";s:3:"ọ";s:3:"Ọ";s:3:"ỏ";s:3:"Ỏ";s:3:"ố";s:3:"Ố";s:3:"ồ";s:3:"Ồ";s:3:"ổ";s:3:"Ổ";s:3:"ỗ";s:3:"Ỗ";s:3:"ộ";s:3:"Ộ";s:3:"ớ";s:3:"Ớ";s:3:"ờ";s:3:"Ờ";s:3:"ở";s:3:"Ở";s:3:"ỡ";s:3:"Ỡ";s:3:"ợ";s:3:"Ợ";s:3:"ụ";s:3:"Ụ";s:3:"ủ";s:3:"Ủ";s:3:"ứ";s:3:"Ứ";s:3:"ừ";s:3:"Ừ";s:3:"ử";s:3:"Ử";s:3:"ữ";s:3:"Ữ";s:3:"ự";s:3:"Ự";s:3:"ỳ";s:3:"Ỳ";s:3:"ỵ";s:3:"Ỵ";s:3:"ỷ";s:3:"Ỷ";s:3:"ỹ";s:3:"Ỹ";s:3:"ỻ";s:3:"Ỻ";s:3:"ỽ";s:3:"Ỽ";s:3:"ỿ";s:3:"Ỿ";s:3:"ἀ";s:3:"Ἀ";s:3:"ἁ";s:3:"Ἁ";s:3:"ἂ";s:3:"Ἂ";s:3:"ἃ";s:3:"Ἃ";s:3:"ἄ";s:3:"Ἄ";s:3:"ἅ";s:3:"Ἅ";s:3:"ἆ";s:3:"Ἆ";s:3:"ἇ";s:3:"Ἇ";s:3:"ἐ";s:3:"Ἐ";s:3:"ἑ";s:3:"Ἑ";s:3:"ἒ";s:3:"Ἒ";s:3:"ἓ";s:3:"Ἓ";s:3:"ἔ";s:3:"Ἔ";s:3:"ἕ";s:3:"Ἕ";s:3:"ἠ";s:3:"Ἠ";s:3:"ἡ";s:3:"Ἡ";s:3:"ἢ";s:3:"Ἢ";s:3:"ἣ";s:3:"Ἣ";s:3:"ἤ";s:3:"Ἤ";s:3:"ἥ";s:3:"Ἥ";s:3:"ἦ";s:3:"Ἦ";s:3:"ἧ";s:3:"Ἧ";s:3:"ἰ";s:3:"Ἰ";s:3:"ἱ";s:3:"Ἱ";s:3:"ἲ";s:3:"Ἲ";s:3:"ἳ";s:3:"Ἳ";s:3:"ἴ";s:3:"Ἴ";s:3:"ἵ";s:3:"Ἵ";s:3:"ἶ";s:3:"Ἶ";s:3:"ἷ";s:3:"Ἷ";s:3:"ὀ";s:3:"Ὀ";s:3:"ὁ";s:3:"Ὁ";s:3:"ὂ";s:3:"Ὂ";s:3:"ὃ";s:3:"Ὃ";s:3:"ὄ";s:3:"Ὄ";s:3:"ὅ";s:3:"Ὅ";s:3:"ὑ";s:3:"Ὑ";s:3:"ὓ";s:3:"Ὓ";s:3:"ὕ";s:3:"Ὕ";s:3:"ὗ";s:3:"Ὗ";s:3:"ὠ";s:3:"Ὠ";s:3:"ὡ";s:3:"Ὡ";s:3:"ὢ";s:3:"Ὢ";s:3:"ὣ";s:3:"Ὣ";s:3:"ὤ";s:3:"Ὤ";s:3:"ὥ";s:3:"Ὥ";s:3:"ὦ";s:3:"Ὦ";s:3:"ὧ";s:3:"Ὧ";s:3:"ὰ";s:3:"Ὰ";s:3:"ά";s:3:"Ά";s:3:"ὲ";s:3:"Ὲ";s:3:"έ";s:3:"Έ";s:3:"ὴ";s:3:"Ὴ";s:3:"ή";s:3:"Ή";s:3:"ὶ";s:3:"Ὶ";s:3:"ί";s:3:"Ί";s:3:"ὸ";s:3:"Ὸ";s:3:"ό";s:3:"Ό";s:3:"ὺ";s:3:"Ὺ";s:3:"ύ";s:3:"Ύ";s:3:"ὼ";s:3:"Ὼ";s:3:"ώ";s:3:"Ώ";s:3:"ᾀ";s:3:"ᾈ";s:3:"ᾁ";s:3:"ᾉ";s:3:"ᾂ";s:3:"ᾊ";s:3:"ᾃ";s:3:"ᾋ";s:3:"ᾄ";s:3:"ᾌ";s:3:"ᾅ";s:3:"ᾍ";s:3:"ᾆ";s:3:"ᾎ";s:3:"ᾇ";s:3:"ᾏ";s:3:"ᾐ";s:3:"ᾘ";s:3:"ᾑ";s:3:"ᾙ";s:3:"ᾒ";s:3:"ᾚ";s:3:"ᾓ";s:3:"ᾛ";s:3:"ᾔ";s:3:"ᾜ";s:3:"ᾕ";s:3:"ᾝ";s:3:"ᾖ";s:3:"ᾞ";s:3:"ᾗ";s:3:"ᾟ";s:3:"ᾠ";s:3:"ᾨ";s:3:"ᾡ";s:3:"ᾩ";s:3:"ᾢ";s:3:"ᾪ";s:3:"ᾣ";s:3:"ᾫ";s:3:"ᾤ";s:3:"ᾬ";s:3:"ᾥ";s:3:"ᾭ";s:3:"ᾦ";s:3:"ᾮ";s:3:"ᾧ";s:3:"ᾯ";s:3:"ᾰ";s:3:"Ᾰ";s:3:"ᾱ";s:3:"Ᾱ";s:3:"ᾳ";s:3:"ᾼ";s:3:"ι";s:2:"Ι";s:3:"ῃ";s:3:"ῌ";s:3:"ῐ";s:3:"Ῐ";s:3:"ῑ";s:3:"Ῑ";s:3:"ῠ";s:3:"Ῠ";s:3:"ῡ";s:3:"Ῡ";s:3:"ῥ";s:3:"Ῥ";s:3:"ῳ";s:3:"ῼ";s:3:"ⅎ";s:3:"Ⅎ";s:3:"ⅰ";s:3:"Ⅰ";s:3:"ⅱ";s:3:"Ⅱ";s:3:"ⅲ";s:3:"Ⅲ";s:3:"ⅳ";s:3:"Ⅳ";s:3:"ⅴ";s:3:"Ⅴ";s:3:"ⅵ";s:3:"Ⅵ";s:3:"ⅶ";s:3:"Ⅶ";s:3:"ⅷ";s:3:"Ⅷ";s:3:"ⅸ";s:3:"Ⅸ";s:3:"ⅹ";s:3:"Ⅹ";s:3:"ⅺ";s:3:"Ⅺ";s:3:"ⅻ";s:3:"Ⅻ";s:3:"ⅼ";s:3:"Ⅼ";s:3:"ⅽ";s:3:"Ⅽ";s:3:"ⅾ";s:3:"Ⅾ";s:3:"ⅿ";s:3:"Ⅿ";s:3:"ↄ";s:3:"Ↄ";s:3:"ⓐ";s:3:"Ⓐ";s:3:"ⓑ";s:3:"Ⓑ";s:3:"ⓒ";s:3:"Ⓒ";s:3:"ⓓ";s:3:"Ⓓ";s:3:"ⓔ";s:3:"Ⓔ";s:3:"ⓕ";s:3:"Ⓕ";s:3:"ⓖ";s:3:"Ⓖ";s:3:"ⓗ";s:3:"Ⓗ";s:3:"ⓘ";s:3:"Ⓘ";s:3:"ⓙ";s:3:"Ⓙ";s:3:"ⓚ";s:3:"Ⓚ";s:3:"ⓛ";s:3:"Ⓛ";s:3:"ⓜ";s:3:"Ⓜ";s:3:"ⓝ";s:3:"Ⓝ";s:3:"ⓞ";s:3:"Ⓞ";s:3:"ⓟ";s:3:"Ⓟ";s:3:"ⓠ";s:3:"Ⓠ";s:3:"ⓡ";s:3:"Ⓡ";s:3:"ⓢ";s:3:"Ⓢ";s:3:"ⓣ";s:3:"Ⓣ";s:3:"ⓤ";s:3:"Ⓤ";s:3:"ⓥ";s:3:"Ⓥ";s:3:"ⓦ";s:3:"Ⓦ";s:3:"ⓧ";s:3:"Ⓧ";s:3:"ⓨ";s:3:"Ⓨ";s:3:"ⓩ";s:3:"Ⓩ";s:3:"ⰰ";s:3:"Ⰰ";s:3:"ⰱ";s:3:"Ⰱ";s:3:"ⰲ";s:3:"Ⰲ";s:3:"ⰳ";s:3:"Ⰳ";s:3:"ⰴ";s:3:"Ⰴ";s:3:"ⰵ";s:3:"Ⰵ";s:3:"ⰶ";s:3:"Ⰶ";s:3:"ⰷ";s:3:"Ⰷ";s:3:"ⰸ";s:3:"Ⰸ";s:3:"ⰹ";s:3:"Ⰹ";s:3:"ⰺ";s:3:"Ⰺ";s:3:"ⰻ";s:3:"Ⰻ";s:3:"ⰼ";s:3:"Ⰼ";s:3:"ⰽ";s:3:"Ⰽ";s:3:"ⰾ";s:3:"Ⰾ";s:3:"ⰿ";s:3:"Ⰿ";s:3:"ⱀ";s:3:"Ⱀ";s:3:"ⱁ";s:3:"Ⱁ";s:3:"ⱂ";s:3:"Ⱂ";s:3:"ⱃ";s:3:"Ⱃ";s:3:"ⱄ";s:3:"Ⱄ";s:3:"ⱅ";s:3:"Ⱅ";s:3:"ⱆ";s:3:"Ⱆ";s:3:"ⱇ";s:3:"Ⱇ";s:3:"ⱈ";s:3:"Ⱈ";s:3:"ⱉ";s:3:"Ⱉ";s:3:"ⱊ";s:3:"Ⱊ";s:3:"ⱋ";s:3:"Ⱋ";s:3:"ⱌ";s:3:"Ⱌ";s:3:"ⱍ";s:3:"Ⱍ";s:3:"ⱎ";s:3:"Ⱎ";s:3:"ⱏ";s:3:"Ⱏ";s:3:"ⱐ";s:3:"Ⱐ";s:3:"ⱑ";s:3:"Ⱑ";s:3:"ⱒ";s:3:"Ⱒ";s:3:"ⱓ";s:3:"Ⱓ";s:3:"ⱔ";s:3:"Ⱔ";s:3:"ⱕ";s:3:"Ⱕ";s:3:"ⱖ";s:3:"Ⱖ";s:3:"ⱗ";s:3:"Ⱗ";s:3:"ⱘ";s:3:"Ⱘ";s:3:"ⱙ";s:3:"Ⱙ";s:3:"ⱚ";s:3:"Ⱚ";s:3:"ⱛ";s:3:"Ⱛ";s:3:"ⱜ";s:3:"Ⱜ";s:3:"ⱝ";s:3:"Ⱝ";s:3:"ⱞ";s:3:"Ⱞ";s:3:"ⱡ";s:3:"Ⱡ";s:3:"ⱥ";s:2:"Ⱥ";s:3:"ⱦ";s:2:"Ⱦ";s:3:"ⱨ";s:3:"Ⱨ";s:3:"ⱪ";s:3:"Ⱪ";s:3:"ⱬ";s:3:"Ⱬ";s:3:"ⱳ";s:3:"Ⱳ";s:3:"ⱶ";s:3:"Ⱶ";s:3:"ⲁ";s:3:"Ⲁ";s:3:"ⲃ";s:3:"Ⲃ";s:3:"ⲅ";s:3:"Ⲅ";s:3:"ⲇ";s:3:"Ⲇ";s:3:"ⲉ";s:3:"Ⲉ";s:3:"ⲋ";s:3:"Ⲋ";s:3:"ⲍ";s:3:"Ⲍ";s:3:"ⲏ";s:3:"Ⲏ";s:3:"ⲑ";s:3:"Ⲑ";s:3:"ⲓ";s:3:"Ⲓ";s:3:"ⲕ";s:3:"Ⲕ";s:3:"ⲗ";s:3:"Ⲗ";s:3:"ⲙ";s:3:"Ⲙ";s:3:"ⲛ";s:3:"Ⲛ";s:3:"ⲝ";s:3:"Ⲝ";s:3:"ⲟ";s:3:"Ⲟ";s:3:"ⲡ";s:3:"Ⲡ";s:3:"ⲣ";s:3:"Ⲣ";s:3:"ⲥ";s:3:"Ⲥ";s:3:"ⲧ";s:3:"Ⲧ";s:3:"ⲩ";s:3:"Ⲩ";s:3:"ⲫ";s:3:"Ⲫ";s:3:"ⲭ";s:3:"Ⲭ";s:3:"ⲯ";s:3:"Ⲯ";s:3:"ⲱ";s:3:"Ⲱ";s:3:"ⲳ";s:3:"Ⲳ";s:3:"ⲵ";s:3:"Ⲵ";s:3:"ⲷ";s:3:"Ⲷ";s:3:"ⲹ";s:3:"Ⲹ";s:3:"ⲻ";s:3:"Ⲻ";s:3:"ⲽ";s:3:"Ⲽ";s:3:"ⲿ";s:3:"Ⲿ";s:3:"ⳁ";s:3:"Ⳁ";s:3:"ⳃ";s:3:"Ⳃ";s:3:"ⳅ";s:3:"Ⳅ";s:3:"ⳇ";s:3:"Ⳇ";s:3:"ⳉ";s:3:"Ⳉ";s:3:"ⳋ";s:3:"Ⳋ";s:3:"ⳍ";s:3:"Ⳍ";s:3:"ⳏ";s:3:"Ⳏ";s:3:"ⳑ";s:3:"Ⳑ";s:3:"ⳓ";s:3:"Ⳓ";s:3:"ⳕ";s:3:"Ⳕ";s:3:"ⳗ";s:3:"Ⳗ";s:3:"ⳙ";s:3:"Ⳙ";s:3:"ⳛ";s:3:"Ⳛ";s:3:"ⳝ";s:3:"Ⳝ";s:3:"ⳟ";s:3:"Ⳟ";s:3:"ⳡ";s:3:"Ⳡ";s:3:"ⳣ";s:3:"Ⳣ";s:3:"ⳬ";s:3:"Ⳬ";s:3:"ⳮ";s:3:"Ⳮ";s:3:"ⳳ";s:3:"Ⳳ";s:3:"ⴀ";s:3:"Ⴀ";s:3:"ⴁ";s:3:"Ⴁ";s:3:"ⴂ";s:3:"Ⴂ";s:3:"ⴃ";s:3:"Ⴃ";s:3:"ⴄ";s:3:"Ⴄ";s:3:"ⴅ";s:3:"Ⴅ";s:3:"ⴆ";s:3:"Ⴆ";s:3:"ⴇ";s:3:"Ⴇ";s:3:"ⴈ";s:3:"Ⴈ";s:3:"ⴉ";s:3:"Ⴉ";s:3:"ⴊ";s:3:"Ⴊ";s:3:"ⴋ";s:3:"Ⴋ";s:3:"ⴌ";s:3:"Ⴌ";s:3:"ⴍ";s:3:"Ⴍ";s:3:"ⴎ";s:3:"Ⴎ";s:3:"ⴏ";s:3:"Ⴏ";s:3:"ⴐ";s:3:"Ⴐ";s:3:"ⴑ";s:3:"Ⴑ";s:3:"ⴒ";s:3:"Ⴒ";s:3:"ⴓ";s:3:"Ⴓ";s:3:"ⴔ";s:3:"Ⴔ";s:3:"ⴕ";s:3:"Ⴕ";s:3:"ⴖ";s:3:"Ⴖ";s:3:"ⴗ";s:3:"Ⴗ";s:3:"ⴘ";s:3:"Ⴘ";s:3:"ⴙ";s:3:"Ⴙ";s:3:"ⴚ";s:3:"Ⴚ";s:3:"ⴛ";s:3:"Ⴛ";s:3:"ⴜ";s:3:"Ⴜ";s:3:"ⴝ";s:3:"Ⴝ";s:3:"ⴞ";s:3:"Ⴞ";s:3:"ⴟ";s:3:"Ⴟ";s:3:"ⴠ";s:3:"Ⴠ";s:3:"ⴡ";s:3:"Ⴡ";s:3:"ⴢ";s:3:"Ⴢ";s:3:"ⴣ";s:3:"Ⴣ";s:3:"ⴤ";s:3:"Ⴤ";s:3:"ⴥ";s:3:"Ⴥ";s:3:"ⴧ";s:3:"Ⴧ";s:3:"ⴭ";s:3:"Ⴭ";s:3:"ꙁ";s:3:"Ꙁ";s:3:"ꙃ";s:3:"Ꙃ";s:3:"ꙅ";s:3:"Ꙅ";s:3:"ꙇ";s:3:"Ꙇ";s:3:"ꙉ";s:3:"Ꙉ";s:3:"ꙋ";s:3:"Ꙋ";s:3:"ꙍ";s:3:"Ꙍ";s:3:"ꙏ";s:3:"Ꙏ";s:3:"ꙑ";s:3:"Ꙑ";s:3:"ꙓ";s:3:"Ꙓ";s:3:"ꙕ";s:3:"Ꙕ";s:3:"ꙗ";s:3:"Ꙗ";s:3:"ꙙ";s:3:"Ꙙ";s:3:"ꙛ";s:3:"Ꙛ";s:3:"ꙝ";s:3:"Ꙝ";s:3:"ꙟ";s:3:"Ꙟ";s:3:"ꙡ";s:3:"Ꙡ";s:3:"ꙣ";s:3:"Ꙣ";s:3:"ꙥ";s:3:"Ꙥ";s:3:"ꙧ";s:3:"Ꙧ";s:3:"ꙩ";s:3:"Ꙩ";s:3:"ꙫ";s:3:"Ꙫ";s:3:"ꙭ";s:3:"Ꙭ";s:3:"ꚁ";s:3:"Ꚁ";s:3:"ꚃ";s:3:"Ꚃ";s:3:"ꚅ";s:3:"Ꚅ";s:3:"ꚇ";s:3:"Ꚇ";s:3:"ꚉ";s:3:"Ꚉ";s:3:"ꚋ";s:3:"Ꚋ";s:3:"ꚍ";s:3:"Ꚍ";s:3:"ꚏ";s:3:"Ꚏ";s:3:"ꚑ";s:3:"Ꚑ";s:3:"ꚓ";s:3:"Ꚓ";s:3:"ꚕ";s:3:"Ꚕ";s:3:"ꚗ";s:3:"Ꚗ";s:3:"ꚙ";s:3:"Ꚙ";s:3:"ꚛ";s:3:"Ꚛ";s:3:"ꜣ";s:3:"Ꜣ";s:3:"ꜥ";s:3:"Ꜥ";s:3:"ꜧ";s:3:"Ꜧ";s:3:"ꜩ";s:3:"Ꜩ";s:3:"ꜫ";s:3:"Ꜫ";s:3:"ꜭ";s:3:"Ꜭ";s:3:"ꜯ";s:3:"Ꜯ";s:3:"ꜳ";s:3:"Ꜳ";s:3:"ꜵ";s:3:"Ꜵ";s:3:"ꜷ";s:3:"Ꜷ";s:3:"ꜹ";s:3:"Ꜹ";s:3:"ꜻ";s:3:"Ꜻ";s:3:"ꜽ";s:3:"Ꜽ";s:3:"ꜿ";s:3:"Ꜿ";s:3:"ꝁ";s:3:"Ꝁ";s:3:"ꝃ";s:3:"Ꝃ";s:3:"ꝅ";s:3:"Ꝅ";s:3:"ꝇ";s:3:"Ꝇ";s:3:"ꝉ";s:3:"Ꝉ";s:3:"ꝋ";s:3:"Ꝋ";s:3:"ꝍ";s:3:"Ꝍ";s:3:"ꝏ";s:3:"Ꝏ";s:3:"ꝑ";s:3:"Ꝑ";s:3:"ꝓ";s:3:"Ꝓ";s:3:"ꝕ";s:3:"Ꝕ";s:3:"ꝗ";s:3:"Ꝗ";s:3:"ꝙ";s:3:"Ꝙ";s:3:"ꝛ";s:3:"Ꝛ";s:3:"ꝝ";s:3:"Ꝝ";s:3:"ꝟ";s:3:"Ꝟ";s:3:"ꝡ";s:3:"Ꝡ";s:3:"ꝣ";s:3:"Ꝣ";s:3:"ꝥ";s:3:"Ꝥ";s:3:"ꝧ";s:3:"Ꝧ";s:3:"ꝩ";s:3:"Ꝩ";s:3:"ꝫ";s:3:"Ꝫ";s:3:"ꝭ";s:3:"Ꝭ";s:3:"ꝯ";s:3:"Ꝯ";s:3:"ꝺ";s:3:"Ꝺ";s:3:"ꝼ";s:3:"Ꝼ";s:3:"ꝿ";s:3:"Ꝿ";s:3:"ꞁ";s:3:"Ꞁ";s:3:"ꞃ";s:3:"Ꞃ";s:3:"ꞅ";s:3:"Ꞅ";s:3:"ꞇ";s:3:"Ꞇ";s:3:"ꞌ";s:3:"Ꞌ";s:3:"ꞑ";s:3:"Ꞑ";s:3:"ꞓ";s:3:"Ꞓ";s:3:"ꞗ";s:3:"Ꞗ";s:3:"ꞙ";s:3:"Ꞙ";s:3:"ꞛ";s:3:"Ꞛ";s:3:"ꞝ";s:3:"Ꞝ";s:3:"ꞟ";s:3:"Ꞟ";s:3:"ꞡ";s:3:"Ꞡ";s:3:"ꞣ";s:3:"Ꞣ";s:3:"ꞥ";s:3:"Ꞥ";s:3:"ꞧ";s:3:"Ꞧ";s:3:"ꞩ";s:3:"Ꞩ";s:3:"a";s:3:"A";s:3:"b";s:3:"B";s:3:"c";s:3:"C";s:3:"d";s:3:"D";s:3:"e";s:3:"E";s:3:"f";s:3:"F";s:3:"g";s:3:"G";s:3:"h";s:3:"H";s:3:"i";s:3:"I";s:3:"j";s:3:"J";s:3:"k";s:3:"K";s:3:"l";s:3:"L";s:3:"m";s:3:"M";s:3:"n";s:3:"N";s:3:"o";s:3:"O";s:3:"p";s:3:"P";s:3:"q";s:3:"Q";s:3:"r";s:3:"R";s:3:"s";s:3:"S";s:3:"t";s:3:"T";s:3:"u";s:3:"U";s:3:"v";s:3:"V";s:3:"w";s:3:"W";s:3:"x";s:3:"X";s:3:"y";s:3:"Y";s:3:"z";s:3:"Z";s:4:"𐐨";s:4:"𐐀";s:4:"𐐩";s:4:"𐐁";s:4:"𐐪";s:4:"𐐂";s:4:"𐐫";s:4:"𐐃";s:4:"𐐬";s:4:"𐐄";s:4:"𐐭";s:4:"𐐅";s:4:"𐐮";s:4:"𐐆";s:4:"𐐯";s:4:"𐐇";s:4:"𐐰";s:4:"𐐈";s:4:"𐐱";s:4:"𐐉";s:4:"𐐲";s:4:"𐐊";s:4:"𐐳";s:4:"𐐋";s:4:"𐐴";s:4:"𐐌";s:4:"𐐵";s:4:"𐐍";s:4:"𐐶";s:4:"𐐎";s:4:"𐐷";s:4:"𐐏";s:4:"𐐸";s:4:"𐐐";s:4:"𐐹";s:4:"𐐑";s:4:"𐐺";s:4:"𐐒";s:4:"𐐻";s:4:"𐐓";s:4:"𐐼";s:4:"𐐔";s:4:"𐐽";s:4:"𐐕";s:4:"𐐾";s:4:"𐐖";s:4:"𐐿";s:4:"𐐗";s:4:"𐑀";s:4:"𐐘";s:4:"𐑁";s:4:"𐐙";s:4:"𐑂";s:4:"𐐚";s:4:"𐑃";s:4:"𐐛";s:4:"𐑄";s:4:"𐐜";s:4:"𐑅";s:4:"𐐝";s:4:"𐑆";s:4:"𐐞";s:4:"𐑇";s:4:"𐐟";s:4:"𐑈";s:4:"𐐠";s:4:"𐑉";s:4:"𐐡";s:4:"𐑊";s:4:"𐐢";s:4:"𐑋";s:4:"𐐣";s:4:"𐑌";s:4:"𐐤";s:4:"𐑍";s:4:"𐐥";s:4:"𐑎";s:4:"𐐦";s:4:"𐑏";s:4:"𐐧";s:4:"𑣀";s:4:"𑢠";s:4:"𑣁";s:4:"𑢡";s:4:"𑣂";s:4:"𑢢";s:4:"𑣃";s:4:"𑢣";s:4:"𑣄";s:4:"𑢤";s:4:"𑣅";s:4:"𑢥";s:4:"𑣆";s:4:"𑢦";s:4:"𑣇";s:4:"𑢧";s:4:"𑣈";s:4:"𑢨";s:4:"𑣉";s:4:"𑢩";s:4:"𑣊";s:4:"𑢪";s:4:"𑣋";s:4:"𑢫";s:4:"𑣌";s:4:"𑢬";s:4:"𑣍";s:4:"𑢭";s:4:"𑣎";s:4:"𑢮";s:4:"𑣏";s:4:"𑢯";s:4:"𑣐";s:4:"𑢰";s:4:"𑣑";s:4:"𑢱";s:4:"𑣒";s:4:"𑢲";s:4:"𑣓";s:4:"𑢳";s:4:"𑣔";s:4:"𑢴";s:4:"𑣕";s:4:"𑢵";s:4:"𑣖";s:4:"𑢶";s:4:"𑣗";s:4:"𑢷";s:4:"𑣘";s:4:"𑢸";s:4:"𑣙";s:4:"𑢹";s:4:"𑣚";s:4:"𑢺";s:4:"𑣛";s:4:"𑢻";s:4:"𑣜";s:4:"𑢼";s:4:"𑣝";s:4:"𑢽";s:4:"𑣞";s:4:"𑢾";s:4:"𑣟";s:4:"𑢿";} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/TurkishUtf8.php b/vendor/patchwork/utf8/src/Patchwork/TurkishUtf8.php new file mode 100644 index 0000000..97328fb --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/TurkishUtf8.php @@ -0,0 +1,155 @@ + $s) { + if ('' === $s .= '') { + $s = '/^(?<=.)$/'; + } else { + $s = preg_quote($s, '/'); + $s = strtr($s, array( + 'i' => '(?-i:[iİ])', + 'İ' => '(?-i:[iİ])', + 'ı' => '(?-i:[ıI])', + 'I' => '(?-i:[ıI])', + )); + $s = "/{$s}/ui"; + } + + $search[$i] = $s; + } + + $subject = preg_replace($search, $replace, $subject, -1, $replace); + $count = $replace; + + return $subject; + } + + public static function ucfirst($s) + { + if ('i' === substr($s, 0, 1)) { + return 'İ'.substr($s, 1); + } else { + return parent::ucfirst($s); + } + } + + public static function ucwords($s) + { + if (false !== strpos($s, 'i')) { + $s = preg_replace('/\bi/u', 'İ', $s); + } + + return parent::ucwords($s); + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8.php b/vendor/patchwork/utf8/src/Patchwork/Utf8.php new file mode 100644 index 0000000..4363a08 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/Utf8.php @@ -0,0 +1,775 @@ + $v) { + $var[$k] = static::filter($v, $normalization_form, $leading_combining); + } + break; + + case 'object': + foreach ($var as $k => $v) { + $var->$k = static::filter($v, $normalization_form, $leading_combining); + } + break; + + case 'string': + if (false !== strpos($var, "\r")) { + // Workaround https://bugs.php.net/65732 + $var = str_replace("\r\n", "\n", $var); + $var = strtr($var, "\r", "\n"); + } + + if (preg_match('/[\x80-\xFF]/', $var)) { + if (n::isNormalized($var, $normalization_form)) { + $n = '-'; + } else { + $n = n::normalize($var, $normalization_form); + if (isset($n[0])) { + $var = $n; + } else { + $var = static::utf8_encode($var); + } + } + + if ($var[0] >= "\x80" && isset($n[0], $leading_combining[0]) && preg_match('/^\p{Mn}/u', $var)) { + // Prevent leading combining chars + // for NFC-safe concatenations. + $var = $leading_combining.$var; + } + } + break; + } + + return $var; + } + + // Unicode transformation for caseless matching + // see http://unicode.org/reports/tr21/tr21-5.html + + public static function strtocasefold($s, $full = true) + { + $s = str_replace(self::$commonCaseFold[0], self::$commonCaseFold[1], $s); + + if ($full) { + static $fullCaseFold = false; + $fullCaseFold or $fullCaseFold = static::getData('caseFolding_full'); + + $s = str_replace($fullCaseFold[0], $fullCaseFold[1], $s); + } + + return static::strtolower($s); + } + + // Generic case sensitive collation support for self::strnatcmp() + + public static function strtonatfold($s) + { + $s = n::normalize($s, n::NFD); + + return preg_replace('/\p{Mn}+/u', '', $s); + } + + // PHP string functions that need UTF-8 awareness + + public static function filter_input($type, $var, $filter = FILTER_DEFAULT, $option = null) + { + if (4 > func_num_args()) { + $var = filter_input($type, $var, $filter); + } else { + $var = filter_input($type, $var, $filter, $option); + } + + return static::filter($var); + } + + public static function filter_input_array($type, $def = null, $add_empty = true) + { + if (2 > func_num_args()) { + $a = filter_input_array($type); + } else { + $a = filter_input_array($type, $def, $add_empty); + } + + return static::filter($a); + } + + public static function json_decode($json, $assoc = false, $depth = 512, $options = 0) + { + if (PHP_VERSION_ID < 50400) { + $json = json_decode($json, $assoc, $depth); + } else { + $json = json_decode($json, $assoc, $depth, $options); + } + + return static::filter($json); + } + + public static function substr($s, $start, $len = 2147483647) + { + static $bug62759; + isset($bug62759) or $bug62759 = extension_loaded('intl') && 'à' === @grapheme_substr('éà', 1, -2); + + if ($bug62759) { + return PHP\Shim\Intl::grapheme_substr_workaround62759($s, $start, $len); + } else { + return grapheme_substr($s, $start, $len); + } + } + + public static function strlen($s) + { + return grapheme_strlen($s); + } + public static function strpos($s, $needle, $offset = 0) + { + // ignore invalid negative offset to keep compatility + // with php < 5.5.35, < 5.6.21, < 7.0.6 + return grapheme_strpos($s, $needle, $offset > 0 ? $offset : 0); + } + public static function strrpos($s, $needle, $offset = 0) + { + return grapheme_strrpos($s, $needle, $offset); + } + + public static function stripos($s, $needle, $offset = 0) + { + if (50418 > PHP_VERSION_ID || 50500 == PHP_VERSION_ID) { + // Don't use grapheme_stripos because of https://bugs.php.net/61860 + if (!preg_match('//u', $s .= '')) { + return false; + } + if ($offset < 0) { + $offset = 0; + } + if (!$needle = mb_stripos($s, $needle .= '', $offset, 'UTF-8')) { + return $needle; + } + + return grapheme_strlen(iconv_substr($s, 0, $needle, 'UTF-8')); + } + + return grapheme_stripos($s, $needle, $offset); + } + + public static function strripos($s, $needle, $offset = 0) + { + if (50418 > PHP_VERSION_ID || 50500 == PHP_VERSION_ID) { + // Don't use grapheme_strripos because of https://bugs.php.net/61860 + if (!preg_match('//u', $s .= '')) { + return false; + } + if ($offset < 0) { + $offset = 0; + } + if (!$needle = mb_strripos($s, $needle .= '', $offset, 'UTF-8')) { + return $needle; + } + + return grapheme_strlen(iconv_substr($s, 0, $needle, 'UTF-8')); + } + + return grapheme_strripos($s, $needle, $offset); + } + + public static function stristr($s, $needle, $before_needle = false) + { + if ('' === $needle .= '') { + return false; + } + + return mb_stristr($s, $needle, $before_needle, 'UTF-8'); + } + + public static function strstr($s, $needle, $before_needle = false) + { + return grapheme_strstr($s, $needle, $before_needle); + } + public static function strrchr($s, $needle, $before_needle = false) + { + return mb_strrchr($s, $needle, $before_needle, 'UTF-8'); + } + public static function strrichr($s, $needle, $before_needle = false) + { + return mb_strrichr($s, $needle, $before_needle, 'UTF-8'); + } + + public static function strtolower($s) + { + return mb_strtolower($s, 'UTF-8'); + } + public static function strtoupper($s) + { + return mb_strtoupper($s, 'UTF-8'); + } + + public static function wordwrap($s, $width = 75, $break = "\n", $cut = false) + { + if (false === wordwrap('-', $width, $break, $cut)) { + return false; + } + + is_string($break) or $break = (string) $break; + + $w = ''; + $s = explode($break, $s); + $iLen = count($s); + $chars = array(); + + if (1 === $iLen && '' === $s[0]) { + return ''; + } + + for ($i = 0; $i < $iLen; ++$i) { + if ($i) { + $chars[] = $break; + $w .= '#'; + } + + $c = $s[$i]; + unset($s[$i]); + + foreach (self::str_split($c) as $c) { + $chars[] = $c; + $w .= ' ' === $c ? ' ' : '?'; + } + } + + $s = ''; + $j = 0; + $b = $i = -1; + $w = wordwrap($w, $width, '#', $cut); + + while (false !== $b = strpos($w, '#', $b + 1)) { + for (++$i; $i < $b; ++$i) { + $s .= $chars[$j]; + unset($chars[$j++]); + } + + if ($break === $chars[$j] || ' ' === $chars[$j]) { + unset($chars[$j++]); + } + $s .= $break; + } + + return $s.implode('', $chars); + } + + public static function chr($c) + { + if (0x80 > $c %= 0x200000) { + return chr($c); + } + if (0x800 > $c) { + return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); + } + + return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); + } + + public static function count_chars($s, $mode = 0) + { + if (1 != $mode) { + user_error(__METHOD__.'(): the only allowed $mode is 1', E_USER_WARNING); + } + $s = self::str_split($s); + + return array_count_values($s); + } + + public static function ltrim($s, $charlist = null) + { + $charlist = null === $charlist ? '\s' : self::rxClass($charlist); + + return preg_replace("/^{$charlist}+/u", '', $s); + } + + public static function ord($s) + { + $a = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $a) { + return (($a - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $a) { + return (($a - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $a) { + return (($a - 0xC0) << 6) + $s[2] - 0x80; + } + + return $a; + } + + public static function rtrim($s, $charlist = null) + { + $charlist = null === $charlist ? '\s' : self::rxClass($charlist); + + return preg_replace("/{$charlist}+$/u", '', $s); + } + + public static function trim($s, $charlist = null) + { + return self::rtrim(self::ltrim($s, $charlist), $charlist); + } + + public static function str_ireplace($search, $replace, $subject, &$count = null) + { + $search = (array) $search; + + foreach ($search as $i => $s) { + if ('' === $s .= '') { + $s = '/^(?<=.)$/'; + } else { + $s = '/'.preg_quote($s, '/').'/ui'; + } + + $search[$i] = $s; + } + + $subject = preg_replace($search, $replace, $subject, -1, $replace); + $count = $replace; + + return $subject; + } + + public static function str_pad($s, $len, $pad = ' ', $type = STR_PAD_RIGHT) + { + $slen = grapheme_strlen($s); + if ($len <= $slen) { + return $s; + } + + $padlen = grapheme_strlen($pad); + $freelen = $len - $slen; + $len = $freelen % $padlen; + + if (STR_PAD_RIGHT == $type) { + return $s.str_repeat($pad, $freelen / $padlen).($len ? grapheme_substr($pad, 0, $len) : ''); + } + if (STR_PAD_LEFT == $type) { + return str_repeat($pad, $freelen / $padlen).($len ? grapheme_substr($pad, 0, $len) : '').$s; + } + if (STR_PAD_BOTH == $type) { + $freelen /= 2; + + $type = ceil($freelen); + $len = $type % $padlen; + $s .= str_repeat($pad, $type / $padlen).($len ? grapheme_substr($pad, 0, $len) : ''); + + $type = floor($freelen); + $len = $type % $padlen; + + return str_repeat($pad, $type / $padlen).($len ? grapheme_substr($pad, 0, $len) : '').$s; + } + + user_error(__METHOD__.'(): Padding type has to be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH', E_USER_WARNING); + } + + public static function str_shuffle($s) + { + $s = self::str_split($s); + shuffle($s); + + return implode('', $s); + } + + public static function str_split($s, $len = 1) + { + if (1 > $len = (int) $len) { + $len = func_get_arg(1); + + return str_split($s, $len); + } + + static $hasIntl; + isset($hasIntl) or $hasIntl = extension_loaded('intl'); + + if ($hasIntl) { + $a = array(); + $p = 0; + $l = strlen($s); + + while ($p < $l) { + $a[] = grapheme_extract($s, 1, GRAPHEME_EXTR_COUNT, $p, $p); + } + } else { + preg_match_all('/'.GRAPHEME_CLUSTER_RX.'/u', $s, $a); + $a = $a[0]; + } + + if (1 == $len) { + return $a; + } + + $s = array(); + $p = -1; + + foreach ($a as $l => $a) { + if ($l % $len) { + $s[$p] .= $a; + } else { + $s[++$p] = $a; + } + } + + return $s; + } + + public static function str_word_count($s, $format = 0, $charlist = '') + { + $charlist = self::rxClass($charlist, '\pL'); + $s = preg_split("/({$charlist}+(?:[\p{Pd}’']{$charlist}+)*)/u", $s, -1, PREG_SPLIT_DELIM_CAPTURE); + + $charlist = array(); + $len = count($s); + + if (1 == $format) { + for ($i = 1; $i < $len; $i += 2) { + $charlist[] = $s[$i]; + } + } elseif (2 == $format) { + $offset = grapheme_strlen($s[0]); + for ($i = 1; $i < $len; $i += 2) { + $charlist[$offset] = $s[$i]; + $offset += grapheme_strlen($s[$i]) + grapheme_strlen($s[$i + 1]); + } + } else { + $charlist = ($len - 1) / 2; + } + + return $charlist; + } + + public static function strcmp($a, $b) + { + return $a.'' === $b.'' ? 0 : strcmp(n::normalize($a, n::NFD), n::normalize($b, n::NFD)); + } + public static function strnatcmp($a, $b) + { + return $a.'' === $b.'' ? 0 : strnatcmp(self::strtonatfold($a), self::strtonatfold($b)); + } + public static function strcasecmp($a, $b) + { + return self::strcmp(static::strtocasefold($a), static::strtocasefold($b)); + } + public static function strnatcasecmp($a, $b) + { + return self::strnatcmp(static::strtocasefold($a), static::strtocasefold($b)); + } + public static function strncasecmp($a, $b, $len) + { + return self::strncmp(static::strtocasefold($a), static::strtocasefold($b), $len); + } + public static function strncmp($a, $b, $len) + { + return self::strcmp(self::substr($a, 0, $len), self::substr($b, 0, $len)); + } + + public static function strcspn($s, $charlist, $start = 0, $len = 2147483647) + { + if ('' === $charlist .= '') { + return; + } + if ($start || 2147483647 != $len) { + $s = self::substr($s, $start, $len); + } + + return preg_match('/^(.*?)'.self::rxClass($charlist).'/us', $s, $len) ? grapheme_strlen($len[1]) : grapheme_strlen($s); + } + + public static function strpbrk($s, $charlist) + { + if (preg_match('/'.self::rxClass($charlist).'/us', $s, $m)) { + return substr($s, strpos($s, $m[0])); + } else { + return false; + } + } + + public static function strrev($s) + { + $s = self::str_split($s); + + return implode('', array_reverse($s)); + } + + public static function strspn($s, $mask, $start = 0, $len = 2147483647) + { + if ($start || 2147483647 != $len) { + $s = self::substr($s, $start, $len); + } + + return preg_match('/^'.self::rxClass($mask).'+/u', $s, $s) ? grapheme_strlen($s[0]) : 0; + } + + public static function strtr($s, $from, $to = null) + { + if (null !== $to) { + $from = self::str_split($from); + $to = self::str_split($to); + + $a = count($from); + $b = count($to); + + if ($a > $b) { + $from = array_slice($from, 0, $b); + } elseif ($a < $b) { + $to = array_slice($to, 0, $a); + } + + $from = array_combine($from, $to); + } + + return strtr($s, $from); + } + + public static function substr_compare($a, $b, $offset, $len = 2147483647, $i = 0) + { + $a = self::substr($a, $offset, $len); + + return $i ? static::strcasecmp($a, $b) : self::strcmp($a, $b); + } + + public static function substr_count($s, $needle, $offset = 0, $len = 2147483647) + { + return substr_count(self::substr($s, $offset, $len), $needle); + } + + public static function substr_replace($s, $replace, $start, $len = 2147483647) + { + $s = self::str_split($s); + $replace = self::str_split($replace); + array_splice($s, $start, $len, $replace); + + return implode('', $s); + } + + public static function ucfirst($s) + { + $c = iconv_substr($s, 0, 1, 'UTF-8'); + + return static::ucwords($c).substr($s, strlen($c)); + } + + public static function lcfirst($s) + { + $c = iconv_substr($s, 0, 1, 'UTF-8'); + + return static::strtolower($c).substr($s, strlen($c)); + } + + public static function ucwords($s) + { + return preg_replace_callback( + "/\b(.)/u", + function ($matches) { + return mb_convert_case($matches[1], MB_CASE_TITLE, 'UTF-8'); + }, + $s + ); + } + + public static function number_format($number, $decimals = 0, $dec_point = '.', $thousands_sep = ',') + { + if (PHP_VERSION_ID < 50400) { + if (isset($thousands_sep[1]) || isset($dec_point[1])) { + return str_replace( + array('.', ','), + array($dec_point, $thousands_sep), + number_format($number, $decimals, '.', ',') + ); + } + } + + return number_format($number, $decimals, $dec_point, $thousands_sep); + } + + public static function utf8_encode($s) + { + $s = utf8_encode($s); + if (false === strpos($s, "\xC2")) { + return $s; + } else { + return str_replace(self::$cp1252, self::$utf8, $s); + } + } + + public static function utf8_decode($s) + { + $s = str_replace(self::$utf8, self::$cp1252, $s); + + return utf8_decode($s); + } + + public static function strwidth($s) + { + if (false !== strpos($s, "\r")) { + $s = str_replace("\r\n", "\n", $s); + $s = strtr($s, "\r", "\n"); + } + $width = 0; + + foreach (explode("\n", $s) as $s) { + $s = preg_replace('/\x1B\[[\d;]*m/', '', $s); + $c = substr_count($s, "\xAD") - substr_count($s, "\x08"); + $s = preg_replace('/[\x00\x05\x07\p{Mn}\p{Me}\p{Cf}\x{1160}-\x{11FF}\x{200B}]+/u', '', $s); + preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + if ($width < $c = iconv_strlen($s, 'UTF-8') + $wide + $c) { + $width = $c; + } + } + + return $width; + } + + protected static function rxClass($s, $class = '') + { + $class = array($class); + + foreach (self::str_split($s) as $s) { + if ('-' === $s) { + $class[0] = '-'.$class[0]; + } elseif (!isset($s[2])) { + $class[0] .= preg_quote($s, '/'); + } elseif (1 === iconv_strlen($s, 'UTF-8')) { + $class[0] .= $s; + } else { + $class[] = $s; + } + } + + $class[0] = '['.$class[0].']'; + + if (1 === count($class)) { + return $class[0]; + } else { + return '(?:'.implode('|', $class).')'; + } + } + + protected static function getData($file) + { + $file = __DIR__.'/Utf8/data/'.$file.'.ser'; + if (file_exists($file)) { + return unserialize(file_get_contents($file)); + } else { + return false; + } + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/BestFit.php b/vendor/patchwork/utf8/src/Patchwork/Utf8/BestFit.php new file mode 100644 index 0000000..83d5acd --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/Utf8/BestFit.php @@ -0,0 +1,79 @@ + 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $s .= ''; + $cp = (string) (int) $cp; + $result = '9' === $cp[0] ? $s.$s : $s; + + if ('932' === $cp && 2 === func_num_args()) { + $placeholder = "\x81\x45"; // Katakana Middle Dot in CP932 + } + + if (!isset($map[$cp])) { + $i = static::getData('to.bestfit'.$cp); + if (false === $i) { + return false; + } + $map[$cp] = $i; + } + + $i = $j = 0; + $cp = $map[$cp]; + + while ($i < $len) { + if ($s[$i] < "\x80") { + $uchr = $s[$i++]; + } else { + $ulen = $ulen_mask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + } + + if (isset($cp[$uchr])) { + $uchr = $cp[$uchr]; + } else { + $uchr = $placeholder; + } + + isset($uchr[0]) and $result[$j++] = $uchr[0]; + isset($uchr[1]) and $result[$j++] = $uchr[1]; + } + + return substr($result, 0, $j); + } + + protected static function getData($file) + { + $file = __DIR__.'/data/'.$file.'.ser'; + if (file_exists($file)) { + return unserialize(file_get_contents($file)); + } else { + return false; + } + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup.php b/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup.php new file mode 100644 index 0000000..08cc5de --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup.php @@ -0,0 +1,230 @@ += '8.32' ? '\X' : s\Intl::GRAPHEME_CLUSTER_RX); + + if (!function_exists('grapheme_strlen')) { + extension_loaded('iconv') or static::initIconv(); + extension_loaded('mbstring') or static::initMbstring(); + + require __DIR__.'/Bootup/intl.php'; + } + } + + public static function initLocale() + { + // With non-UTF-8 locale, basename() bugs. + // Be aware that setlocale() can be slow. + // You'd better properly configure your LANG environment variable to an UTF-8 locale. + + if ('' === basename('§')) { + setlocale(LC_ALL, 'C.UTF-8', 'C'); + setlocale(LC_CTYPE, 'en_US.UTF-8', 'fr_FR.UTF-8', 'es_ES.UTF-8', 'de_DE.UTF-8', 'ru_RU.UTF-8', 'pt_BR.UTF-8', 'it_IT.UTF-8', 'ja_JP.UTF-8', 'zh_CN.UTF-8', '0'); + } + } + + public static function filterRequestUri($uri = null, $exit = true) + { + if (!isset($uri)) { + if (!isset($_SERVER['REQUEST_URI'])) { + return; + } else { + $uri = $_SERVER['REQUEST_URI']; + } + } + + // Ensures the URL is well formed UTF-8 + // When not, assumes Windows-1252 and redirects to the corresponding UTF-8 encoded URL + + if (!preg_match('//u', urldecode($uri))) { + $uri = preg_replace_callback( + '/[\x80-\xFF]+/', + function ($m) {return urlencode($m[0]);}, + $uri + ); + + $uri = preg_replace_callback( + '/(?:%[89A-F][0-9A-F])+/i', + function ($m) {return urlencode(u::utf8_encode(urldecode($m[0])));}, + $uri + ); + + if ($exit) { + header('HTTP/1.1 301 Moved Permanently'); + header('Location: '.$uri); + + exit; // TODO: remove this in 1.2 (BC) + } + } + + return $uri; + } + + public static function filterRequestInputs($normalization_form = 4 /* n::NFC */, $leading_combining = '◌') + { + // Ensures inputs are well formed UTF-8 + // When not, assumes Windows-1252 and converts to UTF-8 + // Tests only values, not keys + + $a = array(&$_FILES, &$_ENV, &$_GET, &$_POST, &$_COOKIE, &$_SERVER, &$_REQUEST); + + foreach ($a[0] as &$r) { + $a[] = array(&$r['name'], &$r['type']); + } + unset($a[0]); + + $len = count($a) + 1; + for ($i = 1; $i < $len; ++$i) { + foreach ($a[$i] as &$r) { + $s = $r; // $r is a ref, $s a copy + if (is_array($s)) { + $a[$len++] = &$r; + } else { + $r = static::filterString($s, $normalization_form, $leading_combining); + } + } + + unset($a[$i]); + } + } + + public static function filterString($s, $normalization_form = 4 /* n::NFC */, $leading_combining = '◌') + { + if (false !== strpos($s, "\r")) { + // Workaround https://bugs.php.net/65732 + $s = str_replace("\r\n", "\n", $s); + $s = strtr($s, "\r", "\n"); + } + + if (preg_match('/[\x80-\xFF]/', $s)) { + if (n::isNormalized($s, $normalization_form)) { + $n = '-'; + } else { + $n = n::normalize($s, $normalization_form); + if (isset($n[0])) { + $s = $n; + } else { + $s = u::utf8_encode($s); + } + } + + if ($s[0] >= "\x80" && isset($n[0], $leading_combining[0]) && preg_match('/^\p{Mn}/u', $s)) { + // Prevent leading combining chars + // for NFC-safe concatenations. + $s = $leading_combining.$s; + } + } + + return $s; + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup/iconv.php b/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup/iconv.php new file mode 100644 index 0000000..0730dda --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/Utf8/Bootup/iconv.php @@ -0,0 +1,51 @@ +FileExists($path)) { + $fs->GetFile($path)->Attributes |= 2; + } elseif ($fs->FolderExists($path)) { + $fs->GetFolder($path)->Attributes |= 2; + } else { + return false; + } + + return true; + } + + public static function fs($path, $is_utf8 = true) + { + static $fs; + + if (!class_exists('COM', false)) { + throw new \RuntimeException('The `wfio` or `com_dotnet` extension is required to handle UTF-8 filesystem access on Windows'); + } + + isset($fs) or $fs = new \COM('Scripting.FileSystemObject', null, CP_UTF8); + + $path = explode('://', $path, 2); + $path = $path[(int) isset($path[1])]; + $path = strtr($path, '/', '\\'); + $pre = ''; + + if (!isset($path[0]) || ('/' !== $path[0] && '\\' !== $path[0] && false === strpos($path, ':'))) { + $pre = getcwd().'\\'; + } + + $pre = new \VARIANT($pre); + + if ($is_utf8) { + $path = new \VARIANT($path, VT_BSTR, CP_UTF8); + } else { + $path = new \VARIANT($path); + } + + return array($fs, $fs->getAbsolutePathName(variant_cat($pre, $path))); + } + + public function dir_closedir() + { + $this->handle = null; + + return true; + } + + public function dir_opendir($path, $options) + { + list($fs, $path) = self::fs($path); + if (!$fs->FolderExists($path)) { + return false; + } + + $dir = $fs->GetFolder($path); + + try { + $f = array('.', '..'); + + foreach ($dir->SubFolders() as $v) { + $f[] = $v->Name; + } + foreach ($dir->Files as $v) { + $f[] = $v->Name; + } + } catch (\Exception $f) { + $f = array(); + } + + $this->handle = $f; + + return true; + } + + public function dir_readdir() + { + if (list(, $c) = each($this->handle)) { + return $c; + } + + return false; + } + + public function dir_rewinddir() + { + reset($this->handle); + + return true; + } + + public function mkdir($path, $mode, $options) + { + list($fs, $path) = self::fs($path); + + try { + if ($options & STREAM_MKDIR_RECURSIVE) { + $path = $fs->GetAbsolutePathName($path); + + $path = explode('\\', $path); + + if (isset($path[3]) && '' === $path[0].$path[1]) { + $pre = '\\\\'.$path[2].'\\'.$path[3].'\\'; + $i = 4; + } elseif (isset($path[1])) { + $pre = $path[0].'\\'; + $i = 1; + } else { + $pre = ''; + $i = 0; + } + + while (isset($path[$i]) && $fs->FolderExists($pre.$path[$i])) { + $pre .= $path[$i++].'\\'; + } + + if (!isset($path[$i])) { + return false; + } + + while (isset($path[$i])) { + $fs->CreateFolder($pre .= $path[$i++].'\\'); + } + + return true; + } else { + $fs->CreateFolder($path); + } + + return true; + } catch (\Exception $e) { + return false; + } + } + + public function rename($from, $to) + { + list($fs, $to) = self::fs($to); + + if ($fs->FileExists($to) || $fs->FolderExists($to)) { + return false; + } + + list(, $from) = self::fs($from); + + try { + if ($fs->FileExists($from)) { + $fs->MoveFile($from, $to); + + return true; + } + + if ($fs->FolderExists($from)) { + $fs->MoveFolder($from, $to); + + return true; + } + } catch (\Exception $e) { + } + + return false; + } + + public function rmdir($path, $options) + { + list($fs, $path) = self::fs($path); + + if ($fs->FolderExists($path)) { + return rmdir($fs->GetFolder($path)->ShortPath); + } + + return false; + } + + public function stream_close() + { + fclose($this->handle); + $this->handle = null; + } + + public function stream_eof() + { + return feof($this->handle); + } + + public function stream_flush() + { + return fflush($this->handle); + } + + public function stream_lock($operation) + { + return flock($this->handle, $operation); + } + + public function stream_metadata($path, $option, $value) + { + list($fs, $path) = self::fs($path); + + if ($fs->FileExists($path)) { + $f = $fs->GetFile($path); + } elseif ($fs->FileExists($path)) { + $f = $fs->GetFolder($path); + } else { + $f = false; + } + + if (STREAM_META_TOUCH === $option) { + if ($f) { + return touch($f->ShortPath); + } + + try { + $fs->OpenTextFile($path, 8, true, 0)->Close(); + + return true; + } catch (\Exception $e) { + } + } + + if (!$f) { + return false; + } + + switch ($option) { + case STREAM_META_ACCESS: return chmod($f->ShortPath, $value); + case STREAM_META_OWNER: + case STREAM_META_OWNER_NAME: return chown($f->ShortPath, $value); + case STREAM_META_GROUP: + case STREAM_META_GROUP_NAME: return chgrp($f->ShortPath, $value); + default: return false; + } + } + + public function stream_open($path, $mode, $options, &$opened_path) + { + $mode .= ''; + list($fs, $path) = self::fs($path); + + if ($fs->FolderExists($path)) { + return false; + } + + try { + if ('x' === $m = substr($mode, 0, 1)) { + $fs->CreateTextFile($path, false)->Close(); + $f = $fs->GetFile($path); + $mode[0] = 'w'; + } else { + $f = $fs->GetFile($path); + } + } catch (\Exception $f) { + try { + switch ($m) { + case 'w': + case 'c': + case 'a': + $h = $fs->CreateTextFile($path, true); + $f = $fs->GetFile($path); + $h->Close(); + break; + + default: return false; + } + } catch (\Exception $e) { + return false; + } + } + + if (!(STREAM_REPORT_ERRORS & $options)) { + set_error_handler('var_dump', 0); + $e = error_reporting(0); + } + + $this->handle = fopen($f->ShortPath, $mode); + + if (!(STREAM_REPORT_ERRORS & $options)) { + error_reporting($e); + restore_error_handler(); + } + + if ($this->handle) { + return true; + } + if (isset($h)) { + $f->Delete(true); + } + + return false; + } + + public function stream_read($count) + { + return fread($this->handle, $count); + } + + public function stream_seek($offset, $whence = SEEK_SET) + { + return fseek($this->handle, $offset, $whence); + } + + public function stream_set_option($option, $arg1, $arg2) + { + switch ($option) { + case STREAM_OPTION_BLOCKING: return stream_set_blocking($this->handle, $arg1); + case STREAM_OPTION_READ_TIMEOUT: return stream_set_timeout($this->handle, $arg1, $arg2); + case STREAM_OPTION_WRITE_BUFFER: return stream_set_write_buffer($this->handle, $arg1, $arg2); + default: return false; + } + } + + public function stream_stat() + { + return fstat($this->handle); + } + + public function stream_tell() + { + return ftell($this->handle); + } + + public function stream_truncate($new_size) + { + return ftruncate($this->handle, $new_size); + } + + public function stream_write($data) + { + return fwrite($this->handle, $data, strlen($data)); + } + + public function unlink($path) + { + list($fs, $path) = self::fs($path); + + if ($fs->FileExists($path)) { + return unlink($fs->GetFile($path)->ShortPath); + } + + return false; + } + + public function url_stat($path, $flags) + { + list($fs, $path) = self::fs($path); + + if ($fs->FileExists($path)) { + $f = $fs->GetFile($path); + } elseif ($fs->FolderExists($path)) { + $f = $fs->GetFolder($path); + } else { + return false; + } + + if (STREAM_URL_STAT_QUIET & $flags) { + set_error_handler('var_dump', 0); + $e = error_reporting(0); + } + + if (STREAM_URL_STAT_LINK & $flags) { + $f = @lstat($f->ShortPath) ?: stat($f->ShortPath); + } else { + $f = stat($f->ShortPath); + } + + if (STREAM_URL_STAT_QUIET & $flags) { + error_reporting($e); + restore_error_handler(); + } + + return $f; + } +} diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/caseFolding_full.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/caseFolding_full.ser new file mode 100644 index 0000000..b8cacc9 --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/caseFolding_full.ser @@ -0,0 +1 @@ +a:2:{i:0;a:104:{i:0;s:2:"ß";i:1;s:2:"İ";i:2;s:2:"ʼn";i:3;s:2:"ǰ";i:4;s:2:"ΐ";i:5;s:2:"ΰ";i:6;s:2:"և";i:7;s:3:"ẖ";i:8;s:3:"ẗ";i:9;s:3:"ẘ";i:10;s:3:"ẙ";i:11;s:3:"ẚ";i:12;s:3:"ẞ";i:13;s:3:"ὐ";i:14;s:3:"ὒ";i:15;s:3:"ὔ";i:16;s:3:"ὖ";i:17;s:3:"ᾀ";i:18;s:3:"ᾁ";i:19;s:3:"ᾂ";i:20;s:3:"ᾃ";i:21;s:3:"ᾄ";i:22;s:3:"ᾅ";i:23;s:3:"ᾆ";i:24;s:3:"ᾇ";i:25;s:3:"ᾈ";i:26;s:3:"ᾉ";i:27;s:3:"ᾊ";i:28;s:3:"ᾋ";i:29;s:3:"ᾌ";i:30;s:3:"ᾍ";i:31;s:3:"ᾎ";i:32;s:3:"ᾏ";i:33;s:3:"ᾐ";i:34;s:3:"ᾑ";i:35;s:3:"ᾒ";i:36;s:3:"ᾓ";i:37;s:3:"ᾔ";i:38;s:3:"ᾕ";i:39;s:3:"ᾖ";i:40;s:3:"ᾗ";i:41;s:3:"ᾘ";i:42;s:3:"ᾙ";i:43;s:3:"ᾚ";i:44;s:3:"ᾛ";i:45;s:3:"ᾜ";i:46;s:3:"ᾝ";i:47;s:3:"ᾞ";i:48;s:3:"ᾟ";i:49;s:3:"ᾠ";i:50;s:3:"ᾡ";i:51;s:3:"ᾢ";i:52;s:3:"ᾣ";i:53;s:3:"ᾤ";i:54;s:3:"ᾥ";i:55;s:3:"ᾦ";i:56;s:3:"ᾧ";i:57;s:3:"ᾨ";i:58;s:3:"ᾩ";i:59;s:3:"ᾪ";i:60;s:3:"ᾫ";i:61;s:3:"ᾬ";i:62;s:3:"ᾭ";i:63;s:3:"ᾮ";i:64;s:3:"ᾯ";i:65;s:3:"ᾲ";i:66;s:3:"ᾳ";i:67;s:3:"ᾴ";i:68;s:3:"ᾶ";i:69;s:3:"ᾷ";i:70;s:3:"ᾼ";i:71;s:3:"ῂ";i:72;s:3:"ῃ";i:73;s:3:"ῄ";i:74;s:3:"ῆ";i:75;s:3:"ῇ";i:76;s:3:"ῌ";i:77;s:3:"ῒ";i:78;s:3:"ΐ";i:79;s:3:"ῖ";i:80;s:3:"ῗ";i:81;s:3:"ῢ";i:82;s:3:"ΰ";i:83;s:3:"ῤ";i:84;s:3:"ῦ";i:85;s:3:"ῧ";i:86;s:3:"ῲ";i:87;s:3:"ῳ";i:88;s:3:"ῴ";i:89;s:3:"ῶ";i:90;s:3:"ῷ";i:91;s:3:"ῼ";i:92;s:3:"ff";i:93;s:3:"fi";i:94;s:3:"fl";i:95;s:3:"ffi";i:96;s:3:"ffl";i:97;s:3:"ſt";i:98;s:3:"st";i:99;s:3:"ﬓ";i:100;s:3:"ﬔ";i:101;s:3:"ﬕ";i:102;s:3:"ﬖ";i:103;s:3:"ﬗ";}i:1;a:104:{i:0;s:2:"ss";i:1;s:3:"i̇";i:2;s:3:"ʼn";i:3;s:3:"ǰ";i:4;s:6:"ΐ";i:5;s:6:"ΰ";i:6;s:4:"եւ";i:7;s:3:"ẖ";i:8;s:3:"ẗ";i:9;s:3:"ẘ";i:10;s:3:"ẙ";i:11;s:3:"aʾ";i:12;s:2:"ss";i:13;s:4:"ὐ";i:14;s:6:"ὒ";i:15;s:6:"ὔ";i:16;s:6:"ὖ";i:17;s:5:"ἀι";i:18;s:5:"ἁι";i:19;s:5:"ἂι";i:20;s:5:"ἃι";i:21;s:5:"ἄι";i:22;s:5:"ἅι";i:23;s:5:"ἆι";i:24;s:5:"ἇι";i:25;s:5:"ἀι";i:26;s:5:"ἁι";i:27;s:5:"ἂι";i:28;s:5:"ἃι";i:29;s:5:"ἄι";i:30;s:5:"ἅι";i:31;s:5:"ἆι";i:32;s:5:"ἇι";i:33;s:5:"ἠι";i:34;s:5:"ἡι";i:35;s:5:"ἢι";i:36;s:5:"ἣι";i:37;s:5:"ἤι";i:38;s:5:"ἥι";i:39;s:5:"ἦι";i:40;s:5:"ἧι";i:41;s:5:"ἠι";i:42;s:5:"ἡι";i:43;s:5:"ἢι";i:44;s:5:"ἣι";i:45;s:5:"ἤι";i:46;s:5:"ἥι";i:47;s:5:"ἦι";i:48;s:5:"ἧι";i:49;s:5:"ὠι";i:50;s:5:"ὡι";i:51;s:5:"ὢι";i:52;s:5:"ὣι";i:53;s:5:"ὤι";i:54;s:5:"ὥι";i:55;s:5:"ὦι";i:56;s:5:"ὧι";i:57;s:5:"ὠι";i:58;s:5:"ὡι";i:59;s:5:"ὢι";i:60;s:5:"ὣι";i:61;s:5:"ὤι";i:62;s:5:"ὥι";i:63;s:5:"ὦι";i:64;s:5:"ὧι";i:65;s:5:"ὰι";i:66;s:4:"αι";i:67;s:4:"άι";i:68;s:4:"ᾶ";i:69;s:6:"ᾶι";i:70;s:4:"αι";i:71;s:5:"ὴι";i:72;s:4:"ηι";i:73;s:4:"ήι";i:74;s:4:"ῆ";i:75;s:6:"ῆι";i:76;s:4:"ηι";i:77;s:6:"ῒ";i:78;s:6:"ΐ";i:79;s:4:"ῖ";i:80;s:6:"ῗ";i:81;s:6:"ῢ";i:82;s:6:"ΰ";i:83;s:4:"ῤ";i:84;s:4:"ῦ";i:85;s:6:"ῧ";i:86;s:5:"ὼι";i:87;s:4:"ωι";i:88;s:4:"ώι";i:89;s:4:"ῶ";i:90;s:6:"ῶι";i:91;s:4:"ωι";i:92;s:2:"ff";i:93;s:2:"fi";i:94;s:2:"fl";i:95;s:3:"ffi";i:96;s:3:"ffl";i:97;s:2:"st";i:98;s:2:"st";i:99;s:4:"մն";i:100;s:4:"մե";i:101;s:4:"մի";i:102;s:4:"վն";i:103;s:4:"մխ";}} \ No newline at end of file diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1250.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1250.ser new file mode 100644 index 0000000..6b90ecc Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1250.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1251.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1251.ser new file mode 100644 index 0000000..981e180 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1251.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1252.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1252.ser new file mode 100644 index 0000000..d43292b Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1252.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1253.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1253.ser new file mode 100644 index 0000000..4904d69 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1253.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1254.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1254.ser new file mode 100644 index 0000000..9b9c71d Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1254.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1255.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1255.ser new file mode 100644 index 0000000..6aa32b4 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1255.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1256.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1256.ser new file mode 100644 index 0000000..17ca822 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1256.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1257.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1257.ser new file mode 100644 index 0000000..3c3fa7d Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1257.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1258.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1258.ser new file mode 100644 index 0000000..4afc772 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit1258.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit874.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit874.ser new file mode 100644 index 0000000..f554c7e Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit874.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit932.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit932.ser new file mode 100644 index 0000000..bf0a160 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit932.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit936.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit936.ser new file mode 100644 index 0000000..683fc30 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit936.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit949.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit949.ser new file mode 100644 index 0000000..6afa623 Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit949.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit950.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit950.ser new file mode 100644 index 0000000..c2e828d Binary files /dev/null and b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/to.bestfit950.ser differ diff --git a/vendor/patchwork/utf8/src/Patchwork/Utf8/data/translit_extra.ser b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/translit_extra.ser new file mode 100644 index 0000000..ceb35db --- /dev/null +++ b/vendor/patchwork/utf8/src/Patchwork/Utf8/data/translit_extra.ser @@ -0,0 +1 @@ +a:224:{s:2:"Ð";s:1:"D";s:2:"Ø";s:1:"O";s:2:"Þ";s:2:"TH";s:2:"ð";s:1:"d";s:2:"ø";s:1:"o";s:2:"þ";s:2:"th";s:2:"Đ";s:1:"D";s:2:"đ";s:1:"d";s:2:"Ħ";s:1:"H";s:2:"ħ";s:1:"h";s:2:"ı";s:1:"i";s:2:"ĸ";s:1:"q";s:2:"Ŋ";s:1:"N";s:2:"ŋ";s:1:"n";s:2:"Ŧ";s:1:"T";s:2:"ŧ";s:1:"t";s:2:"ƀ";s:1:"b";s:2:"Ɓ";s:1:"B";s:2:"Ƃ";s:1:"B";s:2:"ƃ";s:1:"b";s:2:"Ƈ";s:1:"C";s:2:"ƈ";s:1:"c";s:2:"Ɖ";s:1:"D";s:2:"Ɗ";s:1:"D";s:2:"Ƌ";s:1:"D";s:2:"ƌ";s:1:"d";s:2:"Ɛ";s:1:"E";s:2:"Ƒ";s:1:"F";s:2:"ƒ";s:1:"f";s:2:"Ɠ";s:1:"G";s:2:"ƕ";s:2:"hv";s:2:"Ɩ";s:1:"I";s:2:"Ɨ";s:1:"I";s:2:"Ƙ";s:1:"K";s:2:"ƙ";s:1:"k";s:2:"ƚ";s:1:"l";s:2:"Ɲ";s:1:"N";s:2:"ƞ";s:1:"n";s:2:"Ƣ";s:2:"OI";s:2:"ƣ";s:2:"oi";s:2:"Ƥ";s:1:"P";s:2:"ƥ";s:1:"p";s:2:"ƫ";s:1:"t";s:2:"Ƭ";s:1:"T";s:2:"ƭ";s:1:"t";s:2:"Ʈ";s:1:"T";s:2:"Ʋ";s:1:"V";s:2:"Ƴ";s:1:"Y";s:2:"ƴ";s:1:"y";s:2:"Ƶ";s:1:"Z";s:2:"ƶ";s:1:"z";s:2:"Ǥ";s:1:"G";s:2:"ǥ";s:1:"g";s:2:"ȡ";s:1:"d";s:2:"Ȥ";s:1:"Z";s:2:"ȥ";s:1:"z";s:2:"ȴ";s:1:"l";s:2:"ȵ";s:1:"n";s:2:"ȶ";s:1:"t";s:2:"ȷ";s:1:"j";s:2:"ȸ";s:2:"db";s:2:"ȹ";s:2:"qp";s:2:"Ⱥ";s:1:"A";s:2:"Ȼ";s:1:"C";s:2:"ȼ";s:1:"c";s:2:"Ƚ";s:1:"L";s:2:"Ⱦ";s:1:"T";s:2:"ȿ";s:1:"s";s:2:"ɀ";s:1:"z";s:2:"Ƀ";s:1:"B";s:2:"Ʉ";s:1:"U";s:2:"Ɇ";s:1:"E";s:2:"ɇ";s:1:"e";s:2:"Ɉ";s:1:"J";s:2:"ɉ";s:1:"j";s:2:"Ɍ";s:1:"R";s:2:"ɍ";s:1:"r";s:2:"Ɏ";s:1:"Y";s:2:"ɏ";s:1:"y";s:2:"ɓ";s:1:"b";s:2:"ɕ";s:1:"c";s:2:"ɖ";s:1:"d";s:2:"ɗ";s:1:"d";s:2:"ɛ";s:1:"e";s:2:"ɟ";s:1:"j";s:2:"ɠ";s:1:"g";s:2:"ɡ";s:1:"g";s:2:"ɢ";s:1:"G";s:2:"ɦ";s:1:"h";s:2:"ɧ";s:1:"h";s:2:"ɨ";s:1:"i";s:2:"ɪ";s:1:"I";s:2:"ɫ";s:1:"l";s:2:"ɬ";s:1:"l";s:2:"ɭ";s:1:"l";s:2:"ɱ";s:1:"m";s:2:"ɲ";s:1:"n";s:2:"ɳ";s:1:"n";s:2:"ɴ";s:1:"N";s:2:"ɶ";s:2:"OE";s:2:"ɼ";s:1:"r";s:2:"ɽ";s:1:"r";s:2:"ɾ";s:1:"r";s:2:"ʀ";s:1:"R";s:2:"ʂ";s:1:"s";s:2:"ʈ";s:1:"t";s:2:"ʉ";s:1:"u";s:2:"ʋ";s:1:"v";s:2:"ʏ";s:1:"Y";s:2:"ʐ";s:1:"z";s:2:"ʑ";s:1:"z";s:2:"ʙ";s:1:"B";s:2:"ʛ";s:1:"G";s:2:"ʜ";s:1:"H";s:2:"ʝ";s:1:"j";s:2:"ʟ";s:1:"L";s:2:"ʠ";s:1:"q";s:2:"ʣ";s:2:"dz";s:2:"ʥ";s:2:"dz";s:2:"ʦ";s:2:"ts";s:2:"ʪ";s:2:"ls";s:2:"ʫ";s:2:"lz";s:3:"ᴀ";s:1:"A";s:3:"ᴁ";s:2:"AE";s:3:"ᴃ";s:1:"B";s:3:"ᴄ";s:1:"C";s:3:"ᴅ";s:1:"D";s:3:"ᴆ";s:1:"D";s:3:"ᴇ";s:1:"E";s:3:"ᴊ";s:1:"J";s:3:"ᴋ";s:1:"K";s:3:"ᴌ";s:1:"L";s:3:"ᴍ";s:1:"M";s:3:"ᴏ";s:1:"O";s:3:"ᴘ";s:1:"P";s:3:"ᴛ";s:1:"T";s:3:"ᴜ";s:1:"U";s:3:"ᴠ";s:1:"V";s:3:"ᴡ";s:1:"W";s:3:"ᴢ";s:1:"Z";s:3:"ᵫ";s:2:"ue";s:3:"ᵬ";s:1:"b";s:3:"ᵭ";s:1:"d";s:3:"ᵮ";s:1:"f";s:3:"ᵯ";s:1:"m";s:3:"ᵰ";s:1:"n";s:3:"ᵱ";s:1:"p";s:3:"ᵲ";s:1:"r";s:3:"ᵳ";s:1:"r";s:3:"ᵴ";s:1:"s";s:3:"ᵵ";s:1:"t";s:3:"ᵶ";s:1:"z";s:3:"ᵺ";s:2:"th";s:3:"ᵻ";s:1:"I";s:3:"ᵽ";s:1:"p";s:3:"ᵾ";s:1:"U";s:3:"ᶀ";s:1:"b";s:3:"ᶁ";s:1:"d";s:3:"ᶂ";s:1:"f";s:3:"ᶃ";s:1:"g";s:3:"ᶄ";s:1:"k";s:3:"ᶅ";s:1:"l";s:3:"ᶆ";s:1:"m";s:3:"ᶇ";s:1:"n";s:3:"ᶈ";s:1:"p";s:3:"ᶉ";s:1:"r";s:3:"ᶊ";s:1:"s";s:3:"ᶌ";s:1:"v";s:3:"ᶍ";s:1:"x";s:3:"ᶎ";s:1:"z";s:3:"ᶏ";s:1:"a";s:3:"ᶑ";s:1:"d";s:3:"ᶒ";s:1:"e";s:3:"ᶓ";s:1:"e";s:3:"ᶖ";s:1:"i";s:3:"ᶙ";s:1:"u";s:3:"ẜ";s:1:"s";s:3:"ẝ";s:1:"s";s:3:"ẞ";s:2:"SS";s:3:"Ỻ";s:2:"LL";s:3:"ỻ";s:2:"ll";s:3:"Ỽ";s:1:"V";s:3:"ỽ";s:1:"v";s:3:"Ỿ";s:1:"Y";s:3:"ỿ";s:1:"y";s:3:"₠";s:2:"CE";s:3:"₢";s:2:"Cr";s:3:"₣";s:3:"Fr.";s:3:"₤";s:2:"L.";s:3:"₧";s:3:"Pts";s:3:"₹";s:2:"Rs";s:3:"℞";s:2:"Rx";s:3:"〇";s:1:"0";s:3:"′";s:1:"'";s:3:"〝";s:1:""";s:3:"〞";s:1:""";s:3:"‖";s:2:"||";s:3:"⁅";s:1:"[";s:3:"⁆";s:1:"]";s:3:"⁎";s:1:"*";s:3:"、";s:1:",";s:3:"。";s:1:".";s:3:"〈";s:1:"<";s:3:"〉";s:1:">";s:3:"《";s:2:"<<";s:3:"》";s:2:">>";s:3:"〔";s:1:"[";s:3:"〕";s:1:"]";s:3:"〘";s:1:"[";s:3:"〙";s:1:"]";s:3:"〚";s:1:"[";s:3:"〛";s:1:"]";s:3:"︑";s:1:",";s:3:"︒";s:1:".";s:3:"︹";s:1:"[";s:3:"︺";s:1:"]";s:3:"︽";s:2:"<<";s:3:"︾";s:2:">>";s:3:"︿";s:1:"<";s:3:"﹀";s:1:">";s:2:"÷";s:1:"/";s:3:"∥";s:2:"||";s:3:"⦅";s:2:"((";s:3:"⦆";s:2:"))";} \ No newline at end of file diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE new file mode 100644 index 0000000..474c952 --- /dev/null +++ b/vendor/psr/log/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 PHP Framework Interoperability Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php new file mode 100644 index 0000000..90e721a --- /dev/null +++ b/vendor/psr/log/Psr/Log/AbstractLogger.php @@ -0,0 +1,128 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..5ea7243 --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,123 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..d8cd682 --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,28 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000..87934d7 --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/react/promise/LICENSE b/vendor/react/promise/LICENSE new file mode 100644 index 0000000..5919d20 --- /dev/null +++ b/vendor/react/promise/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012-2016 Jan Sorgalla + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/react/promise/composer.json b/vendor/react/promise/composer.json new file mode 100644 index 0000000..2fc4809 --- /dev/null +++ b/vendor/react/promise/composer.json @@ -0,0 +1,29 @@ +{ + "name": "react/promise", + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "license": "MIT", + "authors": [ + {"name": "Jan Sorgalla", "email": "jsorgalla@gmail.com"} + ], + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": ["src/functions_include.php"] + }, + "autoload-dev": { + "psr-4": { + "React\\Promise\\": "tests/fixtures" + } + }, + "keywords": [ + "promise", + "promises" + ] +} diff --git a/vendor/react/promise/src/CancellablePromiseInterface.php b/vendor/react/promise/src/CancellablePromiseInterface.php new file mode 100644 index 0000000..896db2d --- /dev/null +++ b/vendor/react/promise/src/CancellablePromiseInterface.php @@ -0,0 +1,11 @@ +started) { + return; + } + + $this->started = true; + $this->drain(); + } + + public function enqueue($cancellable) + { + if (!\method_exists($cancellable, 'then') || !\method_exists($cancellable, 'cancel')) { + return; + } + + $length = \array_push($this->queue, $cancellable); + + if ($this->started && 1 === $length) { + $this->drain(); + } + } + + private function drain() + { + for ($i = key($this->queue); isset($this->queue[$i]); $i++) { + $cancellable = $this->queue[$i]; + + $exception = null; + + try { + $cancellable->cancel(); + } catch (\Throwable $exception) { + } catch (\Exception $exception) { + } + + unset($this->queue[$i]); + + if ($exception) { + throw $exception; + } + } + + $this->queue = []; + } +} diff --git a/vendor/react/promise/src/Deferred.php b/vendor/react/promise/src/Deferred.php new file mode 100644 index 0000000..3ca034b --- /dev/null +++ b/vendor/react/promise/src/Deferred.php @@ -0,0 +1,65 @@ +canceller = $canceller; + } + + public function promise() + { + if (null === $this->promise) { + $this->promise = new Promise(function ($resolve, $reject, $notify) { + $this->resolveCallback = $resolve; + $this->rejectCallback = $reject; + $this->notifyCallback = $notify; + }, $this->canceller); + $this->canceller = null; + } + + return $this->promise; + } + + public function resolve($value = null) + { + $this->promise(); + + \call_user_func($this->resolveCallback, $value); + } + + public function reject($reason = null) + { + $this->promise(); + + \call_user_func($this->rejectCallback, $reason); + } + + /** + * @deprecated 2.6.0 Progress support is deprecated and should not be used anymore. + * @param mixed $update + */ + public function notify($update = null) + { + $this->promise(); + + \call_user_func($this->notifyCallback, $update); + } + + /** + * @deprecated 2.2.0 + * @see Deferred::notify() + */ + public function progress($update = null) + { + $this->notify($update); + } +} diff --git a/vendor/react/promise/src/Exception/LengthException.php b/vendor/react/promise/src/Exception/LengthException.php new file mode 100644 index 0000000..775c48d --- /dev/null +++ b/vendor/react/promise/src/Exception/LengthException.php @@ -0,0 +1,7 @@ +value = $value; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onFulfilled) { + return $this; + } + + try { + return resolve($onFulfilled($this->value)); + } catch (\Throwable $exception) { + return new RejectedPromise($exception); + } catch (\Exception $exception) { + return new RejectedPromise($exception); + } + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onFulfilled) { + return; + } + + $result = $onFulfilled($this->value); + + if ($result instanceof ExtendedPromiseInterface) { + $result->done(); + } + } + + public function otherwise(callable $onRejected) + { + return $this; + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(function ($value) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($value) { + return $value; + }); + }); + } + + public function progress(callable $onProgress) + { + return $this; + } + + public function cancel() + { + } +} diff --git a/vendor/react/promise/src/LazyPromise.php b/vendor/react/promise/src/LazyPromise.php new file mode 100644 index 0000000..7546524 --- /dev/null +++ b/vendor/react/promise/src/LazyPromise.php @@ -0,0 +1,63 @@ +factory = $factory; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return $this->promise()->then($onFulfilled, $onRejected, $onProgress); + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return $this->promise()->done($onFulfilled, $onRejected, $onProgress); + } + + public function otherwise(callable $onRejected) + { + return $this->promise()->otherwise($onRejected); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->promise()->always($onFulfilledOrRejected); + } + + public function progress(callable $onProgress) + { + return $this->promise()->progress($onProgress); + } + + public function cancel() + { + return $this->promise()->cancel(); + } + + /** + * @internal + * @see Promise::settle() + */ + public function promise() + { + if (null === $this->promise) { + try { + $this->promise = resolve(\call_user_func($this->factory)); + } catch (\Throwable $exception) { + $this->promise = new RejectedPromise($exception); + } catch (\Exception $exception) { + $this->promise = new RejectedPromise($exception); + } + } + + return $this->promise; + } +} diff --git a/vendor/react/promise/src/Promise.php b/vendor/react/promise/src/Promise.php new file mode 100644 index 0000000..33759e6 --- /dev/null +++ b/vendor/react/promise/src/Promise.php @@ -0,0 +1,256 @@ +canceller = $canceller; + + // Explicitly overwrite arguments with null values before invoking + // resolver function. This ensure that these arguments do not show up + // in the stack trace in PHP 7+ only. + $cb = $resolver; + $resolver = $canceller = null; + $this->call($cb); + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null !== $this->result) { + return $this->result->then($onFulfilled, $onRejected, $onProgress); + } + + if (null === $this->canceller) { + return new static($this->resolver($onFulfilled, $onRejected, $onProgress)); + } + + // This promise has a canceller, so we create a new child promise which + // has a canceller that invokes the parent canceller if all other + // followers are also cancelled. We keep a reference to this promise + // instance for the static canceller function and clear this to avoid + // keeping a cyclic reference between parent and follower. + $parent = $this; + ++$parent->requiredCancelRequests; + + return new static( + $this->resolver($onFulfilled, $onRejected, $onProgress), + static function () use (&$parent) { + if (++$parent->cancelRequests >= $parent->requiredCancelRequests) { + $parent->cancel(); + } + + $parent = null; + } + ); + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null !== $this->result) { + return $this->result->done($onFulfilled, $onRejected, $onProgress); + } + + $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected) { + $promise + ->done($onFulfilled, $onRejected); + }; + + if ($onProgress) { + $this->progressHandlers[] = $onProgress; + } + } + + public function otherwise(callable $onRejected) + { + return $this->then(null, static function ($reason) use ($onRejected) { + if (!_checkTypehint($onRejected, $reason)) { + return new RejectedPromise($reason); + } + + return $onRejected($reason); + }); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(static function ($value) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($value) { + return $value; + }); + }, static function ($reason) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($reason) { + return new RejectedPromise($reason); + }); + }); + } + + public function progress(callable $onProgress) + { + return $this->then(null, null, $onProgress); + } + + public function cancel() + { + if (null === $this->canceller || null !== $this->result) { + return; + } + + $canceller = $this->canceller; + $this->canceller = null; + + $this->call($canceller); + } + + private function resolver(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + return function ($resolve, $reject, $notify) use ($onFulfilled, $onRejected, $onProgress) { + if ($onProgress) { + $progressHandler = static function ($update) use ($notify, $onProgress) { + try { + $notify($onProgress($update)); + } catch (\Throwable $e) { + $notify($e); + } catch (\Exception $e) { + $notify($e); + } + }; + } else { + $progressHandler = $notify; + } + + $this->handlers[] = static function (ExtendedPromiseInterface $promise) use ($onFulfilled, $onRejected, $resolve, $reject, $progressHandler) { + $promise + ->then($onFulfilled, $onRejected) + ->done($resolve, $reject, $progressHandler); + }; + + $this->progressHandlers[] = $progressHandler; + }; + } + + private function reject($reason = null) + { + if (null !== $this->result) { + return; + } + + $this->settle(reject($reason)); + } + + private function settle(ExtendedPromiseInterface $promise) + { + $promise = $this->unwrap($promise); + + if ($promise === $this) { + $promise = new RejectedPromise( + new \LogicException('Cannot resolve a promise with itself.') + ); + } + + $handlers = $this->handlers; + + $this->progressHandlers = $this->handlers = []; + $this->result = $promise; + $this->canceller = null; + + foreach ($handlers as $handler) { + $handler($promise); + } + } + + private function unwrap($promise) + { + $promise = $this->extract($promise); + + while ($promise instanceof self && null !== $promise->result) { + $promise = $this->extract($promise->result); + } + + return $promise; + } + + private function extract($promise) + { + if ($promise instanceof LazyPromise) { + $promise = $promise->promise(); + } + + return $promise; + } + + private function call(callable $cb) + { + // Explicitly overwrite argument with null value. This ensure that this + // argument does not show up in the stack trace in PHP 7+ only. + $callback = $cb; + $cb = null; + + // Use reflection to inspect number of arguments expected by this callback. + // We did some careful benchmarking here: Using reflection to avoid unneeded + // function arguments is actually faster than blindly passing them. + // Also, this helps avoiding unnecessary function arguments in the call stack + // if the callback creates an Exception (creating garbage cycles). + if (\is_array($callback)) { + $ref = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (\is_object($callback) && !$callback instanceof \Closure) { + $ref = new \ReflectionMethod($callback, '__invoke'); + } else { + $ref = new \ReflectionFunction($callback); + } + $args = $ref->getNumberOfParameters(); + + try { + if ($args === 0) { + $callback(); + } else { + // Keep references to this promise instance for the static resolve/reject functions. + // By using static callbacks that are not bound to this instance + // and passing the target promise instance by reference, we can + // still execute its resolving logic and still clear this + // reference when settling the promise. This helps avoiding + // garbage cycles if any callback creates an Exception. + // These assumptions are covered by the test suite, so if you ever feel like + // refactoring this, go ahead, any alternative suggestions are welcome! + $target =& $this; + $progressHandlers =& $this->progressHandlers; + + $callback( + static function ($value = null) use (&$target) { + if ($target !== null) { + $target->settle(resolve($value)); + $target = null; + } + }, + static function ($reason = null) use (&$target) { + if ($target !== null) { + $target->reject($reason); + $target = null; + } + }, + static function ($update = null) use (&$progressHandlers) { + foreach ($progressHandlers as $handler) { + $handler($update); + } + } + ); + } + } catch (\Throwable $e) { + $target = null; + $this->reject($e); + } catch (\Exception $e) { + $target = null; + $this->reject($e); + } + } +} diff --git a/vendor/react/promise/src/PromiseInterface.php b/vendor/react/promise/src/PromiseInterface.php new file mode 100644 index 0000000..fcd763d --- /dev/null +++ b/vendor/react/promise/src/PromiseInterface.php @@ -0,0 +1,14 @@ +reason = $reason; + } + + public function then(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onRejected) { + return $this; + } + + try { + return resolve($onRejected($this->reason)); + } catch (\Throwable $exception) { + return new RejectedPromise($exception); + } catch (\Exception $exception) { + return new RejectedPromise($exception); + } + } + + public function done(callable $onFulfilled = null, callable $onRejected = null, callable $onProgress = null) + { + if (null === $onRejected) { + throw UnhandledRejectionException::resolve($this->reason); + } + + $result = $onRejected($this->reason); + + if ($result instanceof self) { + throw UnhandledRejectionException::resolve($result->reason); + } + + if ($result instanceof ExtendedPromiseInterface) { + $result->done(); + } + } + + public function otherwise(callable $onRejected) + { + if (!_checkTypehint($onRejected, $this->reason)) { + return $this; + } + + return $this->then(null, $onRejected); + } + + public function always(callable $onFulfilledOrRejected) + { + return $this->then(null, function ($reason) use ($onFulfilledOrRejected) { + return resolve($onFulfilledOrRejected())->then(function () use ($reason) { + return new RejectedPromise($reason); + }); + }); + } + + public function progress(callable $onProgress) + { + return $this; + } + + public function cancel() + { + } +} diff --git a/vendor/react/promise/src/UnhandledRejectionException.php b/vendor/react/promise/src/UnhandledRejectionException.php new file mode 100644 index 0000000..e7fe2f7 --- /dev/null +++ b/vendor/react/promise/src/UnhandledRejectionException.php @@ -0,0 +1,31 @@ +reason = $reason; + + $message = \sprintf('Unhandled Rejection: %s', \json_encode($reason)); + + parent::__construct($message, 0); + } + + public function getReason() + { + return $this->reason; + } +} diff --git a/vendor/react/promise/src/functions.php b/vendor/react/promise/src/functions.php new file mode 100644 index 0000000..c549e4e --- /dev/null +++ b/vendor/react/promise/src/functions.php @@ -0,0 +1,246 @@ +then($resolve, $reject, $notify); + }, $canceller); + } + + return new FulfilledPromise($promiseOrValue); +} + +function reject($promiseOrValue = null) +{ + if ($promiseOrValue instanceof PromiseInterface) { + return resolve($promiseOrValue)->then(function ($value) { + return new RejectedPromise($value); + }); + } + + return new RejectedPromise($promiseOrValue); +} + +function all($promisesOrValues) +{ + return map($promisesOrValues, function ($val) { + return $val; + }); +} + +function race($promisesOrValues) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($cancellationQueue, $resolve, $reject, $notify) { + if (!is_array($array) || !$array) { + $resolve(); + return; + } + + foreach ($array as $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + + resolve($promiseOrValue) + ->done($resolve, $reject, $notify); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function any($promisesOrValues) +{ + return some($promisesOrValues, 1) + ->then(function ($val) { + return \array_shift($val); + }); +} + +function some($promisesOrValues, $howMany) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $howMany, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($howMany, $cancellationQueue, $resolve, $reject, $notify) { + if (!\is_array($array) || $howMany < 1) { + $resolve([]); + return; + } + + $len = \count($array); + + if ($len < $howMany) { + throw new Exception\LengthException( + \sprintf( + 'Input array must contain at least %d item%s but contains only %s item%s.', + $howMany, + 1 === $howMany ? '' : 's', + $len, + 1 === $len ? '' : 's' + ) + ); + } + + $toResolve = $howMany; + $toReject = ($len - $toResolve) + 1; + $values = []; + $reasons = []; + + foreach ($array as $i => $promiseOrValue) { + $fulfiller = function ($val) use ($i, &$values, &$toResolve, $toReject, $resolve) { + if ($toResolve < 1 || $toReject < 1) { + return; + } + + $values[$i] = $val; + + if (0 === --$toResolve) { + $resolve($values); + } + }; + + $rejecter = function ($reason) use ($i, &$reasons, &$toReject, $toResolve, $reject) { + if ($toResolve < 1 || $toReject < 1) { + return; + } + + $reasons[$i] = $reason; + + if (0 === --$toReject) { + $reject($reasons); + } + }; + + $cancellationQueue->enqueue($promiseOrValue); + + resolve($promiseOrValue) + ->done($fulfiller, $rejecter, $notify); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function map($promisesOrValues, callable $mapFunc) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $mapFunc, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($mapFunc, $cancellationQueue, $resolve, $reject, $notify) { + if (!\is_array($array) || !$array) { + $resolve([]); + return; + } + + $toResolve = \count($array); + $values = []; + + foreach ($array as $i => $promiseOrValue) { + $cancellationQueue->enqueue($promiseOrValue); + $values[$i] = null; + + resolve($promiseOrValue) + ->then($mapFunc) + ->done( + function ($mapped) use ($i, &$values, &$toResolve, $resolve) { + $values[$i] = $mapped; + + if (0 === --$toResolve) { + $resolve($values); + } + }, + $reject, + $notify + ); + } + }, $reject, $notify); + }, $cancellationQueue); +} + +function reduce($promisesOrValues, callable $reduceFunc, $initialValue = null) +{ + $cancellationQueue = new CancellationQueue(); + $cancellationQueue->enqueue($promisesOrValues); + + return new Promise(function ($resolve, $reject, $notify) use ($promisesOrValues, $reduceFunc, $initialValue, $cancellationQueue) { + resolve($promisesOrValues) + ->done(function ($array) use ($reduceFunc, $initialValue, $cancellationQueue, $resolve, $reject, $notify) { + if (!\is_array($array)) { + $array = []; + } + + $total = \count($array); + $i = 0; + + // Wrap the supplied $reduceFunc with one that handles promises and then + // delegates to the supplied. + $wrappedReduceFunc = function ($current, $val) use ($reduceFunc, $cancellationQueue, $total, &$i) { + $cancellationQueue->enqueue($val); + + return $current + ->then(function ($c) use ($reduceFunc, $total, &$i, $val) { + return resolve($val) + ->then(function ($value) use ($reduceFunc, $total, &$i, $c) { + return $reduceFunc($c, $value, $i++, $total); + }); + }); + }; + + $cancellationQueue->enqueue($initialValue); + + \array_reduce($array, $wrappedReduceFunc, resolve($initialValue)) + ->done($resolve, $reject, $notify); + }, $reject, $notify); + }, $cancellationQueue); +} + +// Internal functions +function _checkTypehint(callable $callback, $object) +{ + if (!\is_object($object)) { + return true; + } + + if (\is_array($callback)) { + $callbackReflection = new \ReflectionMethod($callback[0], $callback[1]); + } elseif (\is_object($callback) && !$callback instanceof \Closure) { + $callbackReflection = new \ReflectionMethod($callback, '__invoke'); + } else { + $callbackReflection = new \ReflectionFunction($callback); + } + + $parameters = $callbackReflection->getParameters(); + + if (!isset($parameters[0])) { + return true; + } + + $expectedException = $parameters[0]; + + if (!$expectedException->getClass()) { + return true; + } + + return $expectedException->getClass()->isInstance($object); +} diff --git a/vendor/react/promise/src/functions_include.php b/vendor/react/promise/src/functions_include.php new file mode 100644 index 0000000..bd0c54f --- /dev/null +++ b/vendor/react/promise/src/functions_include.php @@ -0,0 +1,5 @@ +=5.4.7", + "ext-dom": "*", + "ext-filter": "*", + "lib-pcre": ">=7.2" + }, + "require-dev": { + "matthiasmullie/minify": "*", + "php-coveralls/php-coveralls": "*", + "s9e/regexp-builder": "1.*" + }, + "suggest": { + "ext-curl": "Improves the performance of the MediaEmbed plugin and some JavaScript minifiers", + "ext-intl": "Allows international URLs to be accepted by the URL filter", + "ext-json": "Enables the generation of a JavaScript parser", + "ext-mbstring": "Improves the performance of the PHP renderer", + "ext-tokenizer": "Improves the performance of the PHP renderer", + "ext-xsl": "Enables the XSLT renderer", + "ext-zlib": "Enables gzip compression when scraping content via the MediaEmbed plugin" + }, + "autoload": { + "psr-4": { + "s9e\\TextFormatter\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "s9e\\TextFormatter\\Tests\\": "tests" + } + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Bundle.php b/vendor/s9e/text-formatter/src/Bundle.php new file mode 100644 index 0000000..b4aad10 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Bundle.php @@ -0,0 +1,58 @@ +parse($text); + if (isset(static::$afterParse)) + $xml = \call_user_func(static::$afterParse, $xml); + return $xml; + } + public static function render($xml, array $params = []) + { + $renderer = static::getCachedRenderer(); + if (!empty($params)) + $renderer->setParameters($params); + if (isset(static::$beforeRender)) + $xml = \call_user_func(static::$beforeRender, $xml); + $output = $renderer->render($xml); + if (isset(static::$afterRender)) + $output = \call_user_func(static::$afterRender, $output); + return $output; + } + public static function reset() + { + static::$parser = \null; + static::$renderer = \null; + } + public static function unparse($xml) + { + if (isset(static::$beforeUnparse)) + $xml = \call_user_func(static::$beforeUnparse, $xml); + $text = Unparser::unparse($xml); + if (isset(static::$afterUnparse)) + $text = \call_user_func(static::$afterUnparse, $text); + return $text; + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Bundles/Fatdown.php b/vendor/s9e/text-formatter/src/Bundles/Fatdown.php new file mode 100644 index 0000000..b76c37f --- /dev/null +++ b/vendor/s9e/text-formatter/src/Bundles/Fatdown.php @@ -0,0 +1,21 @@ +[^\\s()\\[\\]\\x{FF01}-\\x{FF0F}\\x{FF1A}-\\x{FF20}\\x{FF3B}-\\x{FF40}\\x{FF5B}-\\x{FF65}]|\\([^\\s()]*\\)|\\[\\w*\\])++#Siu";s:7:"tagName";s:3:"URL";s:10:"quickMatch";s:3:"://";s:11:"regexpLimit";i:50000;}s:7:"Escaper";a:4:{s:10:"quickMatch";s:1:"\\";s:6:"regexp";s:29:"/\\\\[-!#()*+.:<>@[\\\\\\]^_`{|}]/";s:7:"tagName";s:3:"ESC";s:11:"regexpLimit";i:50000;}s:10:"FancyPants";a:2:{s:8:"attrName";s:4:"char";s:7:"tagName";s:2:"FP";}s:12:"HTMLComments";a:5:{s:8:"attrName";s:7:"content";s:10:"quickMatch";s:4:"/is";s:7:"tagName";s:2:"HC";s:11:"regexpLimit";i:50000;}s:12:"HTMLElements";a:5:{s:10:"quickMatch";s:1:"<";s:6:"prefix";s:4:"html";s:6:"regexp";s:393:"#<(?>/((?:a(?>bbr)?|br?|code|d(?>[dlt]|el|iv)|em|hr|i(?>mg|ns)?|li|ol|pre|r(?:[bp]|tc?|uby)|s(?>(?>pan|trong|u[bp]))?|t(?:[dr]|able|body|foot|h(?>ead)?)|ul?))|((?:a(?>bbr)?|br?|code|d(?>[dlt]|el|iv)|em|hr|i(?>mg|ns)?|li|ol|pre|r(?:[bp]|tc?|uby)|s(?>(?>pan|trong|u[bp]))?|t(?:[dr]|able|body|foot|h(?>ead)?)|ul?))((?>\\s+[a-z][-a-z0-9]*(?>\\s*=\\s*(?>"[^"]*"|\'[^\']*\'|[^\\s"\'=<>`]+))?)*+)\\s*/?)\\s*>#i";s:7:"aliases";a:6:{s:1:"a";a:2:{s:0:"";s:3:"URL";s:4:"href";s:3:"url";}s:2:"hr";a:1:{s:0:"";s:2:"HR";}s:2:"em";a:1:{s:0:"";s:2:"EM";}s:1:"s";a:1:{s:0:"";s:1:"S";}s:6:"strong";a:1:{s:0:"";s:6:"STRONG";}s:3:"sup";a:1:{s:0:"";s:3:"SUP";}}s:11:"regexpLimit";i:50000;}s:12:"HTMLEntities";a:5:{s:8:"attrName";s:4:"char";s:10:"quickMatch";s:1:"&";s:6:"regexp";s:38:"/&(?>[a-z]+|#(?>[0-9]+|x[0-9a-f]+));/i";s:7:"tagName";s:2:"HE";s:11:"regexpLimit";i:50000;}s:8:"Litedown";a:1:{s:18:"decodeHtmlEntities";b:1;}s:10:"MediaEmbed";a:4:{s:10:"quickMatch";s:3:"://";s:6:"regexp";s:26:"/\\bhttps?:\\/\\/[^["\'\\s]+/Si";s:7:"tagName";s:5:"MEDIA";s:11:"regexpLimit";i:50000;}s:10:"PipeTables";a:3:{s:16:"overwriteEscapes";b:1;s:17:"overwriteMarkdown";b:1;s:10:"quickMatch";s:1:"|";}}s:14:"registeredVars";a:3:{s:9:"urlConfig";a:1:{s:14:"allowedSchemes";s:20:"/^(?:ftp|https?)$/Di";}s:16:"MediaEmbed.hosts";a:13:{s:12:"bandcamp.com";s:8:"bandcamp";s:6:"dai.ly";s:11:"dailymotion";s:15:"dailymotion.com";s:11:"dailymotion";s:12:"facebook.com";s:8:"facebook";s:12:"liveleak.com";s:8:"liveleak";s:14:"soundcloud.com";s:10:"soundcloud";s:16:"open.spotify.com";s:7:"spotify";s:16:"play.spotify.com";s:7:"spotify";s:9:"twitch.tv";s:6:"twitch";s:9:"vimeo.com";s:5:"vimeo";s:7:"vine.co";s:4:"vine";s:11:"youtube.com";s:7:"youtube";s:8:"youtu.be";s:7:"youtube";}s:16:"MediaEmbed.sites";a:10:{s:8:"bandcamp";a:2:{i:0;a:0:{}i:1;a:2:{i:0;a:2:{s:7:"extract";a:1:{i:0;a:2:{i:0;s:25:"!/album=(?\'album_id\'\\d+)!";i:1;a:2:{i:0;s:0:"";i:1;s:8:"album_id";}}}s:5:"match";a:1:{i:0;a:2:{i:0;s:23:"!bandcamp\\.com/album/.!";i:1;a:1:{i:0;s:0:"";}}}}i:1;a:2:{s:7:"extract";a:3:{i:0;a:2:{i:0;s:29:"!"album_id":(?\'album_id\'\\d+)!";i:1;R:90;}i:1;a:2:{i:0;s:31:"!"track_num":(?\'track_num\'\\d+)!";i:1;a:2:{i:0;s:0:"";i:1;s:9:"track_num";}}i:2;a:2:{i:0;s:25:"!/track=(?\'track_id\'\\d+)!";i:1;a:2:{i:0;s:0:"";i:1;s:8:"track_id";}}}s:5:"match";a:1:{i:0;a:2:{i:0;s:23:"!bandcamp\\.com/track/.!";i:1;R:96;}}}}}s:11:"dailymotion";a:2:{i:0;a:3:{i:0;a:2:{i:0;s:27:"!dai\\.ly/(?\'id\'[a-z0-9]+)!i";i:1;a:2:{i:0;s:0:"";i:1;s:2:"id";}}i:1;a:2:{i:0;s:92:"!dailymotion\\.com/(?:live/|swf/|user/[^#]+#video=|(?:related/\\d+/)?video/)(?\'id\'[a-z0-9]+)!i";i:1;R:119;}i:2;a:2:{i:0;s:17:"!start=(?\'t\'\\d+)!";i:1;a:2:{i:0;s:0:"";i:1;s:1:"t";}}}i:1;R:84;}s:8:"facebook";a:2:{i:0;a:3:{i:0;a:2:{i:0;s:135:"@/(?!(?:apps|developers|graph)\\.)[-\\w.]*facebook\\.com/(?:[/\\w]+/permalink|(?!pages/|groups/).*?)(?:/|fbid=|\\?v=)(?\'id\'\\d+)(?=$|[/?&#])@";i:1;R:119;}i:1;a:2:{i:0;s:51:"@facebook\\.com/(?\'user\'\\w+)/(?\'type\'post|video)s?/@";i:1;a:3:{i:0;s:0:"";i:1;s:4:"user";i:2;s:4:"type";}}i:2;a:2:{i:0;s:46:"@facebook\\.com/video/(?\'type\'post|video)\\.php@";i:1;a:2:{i:0;s:0:"";i:1;s:4:"type";}}}i:1;R:84;}s:8:"liveleak";a:2:{i:0;a:1:{i:0;a:2:{i:0;s:41:"!liveleak\\.com/(?:e/|view\\?i=)(?\'id\'\\w+)!";i:1;R:119;}}i:1;a:1:{i:0;a:2:{s:7:"extract";a:1:{i:0;a:2:{i:0;s:28:"!liveleak\\.com/e/(?\'id\'\\w+)!";i:1;R:119;}}s:5:"match";a:1:{i:0;a:2:{i:0;s:24:"!liveleak\\.com/view\\?t=!";i:1;R:96;}}}}}s:10:"soundcloud";a:2:{i:0;a:4:{i:0;a:2:{i:0;s:84:"@https?://(?:api\\.)?soundcloud\\.com/(?!pages/)(?\'id\'[-/\\w]+/[-/\\w]+|^[^/]+/[^/]+$)@i";i:1;R:119;}i:1;a:2:{i:0;s:52:"@api\\.soundcloud\\.com/playlists/(?\'playlist_id\'\\d+)@";i:1;a:2:{i:0;s:0:"";i:1;s:11:"playlist_id";}}i:2;a:2:{i:0;s:89:"@api\\.soundcloud\\.com/tracks/(?\'track_id\'\\d+)(?:\\?secret_token=(?\'secret_token\'[-\\w]+))?@";i:1;a:3:{i:0;s:0:"";i:1;s:8:"track_id";i:2;s:12:"secret_token";}}i:3;a:2:{i:0;s:81:"@soundcloud\\.com/(?!playlists|tracks)[-\\w]+/[-\\w]+/(?=s-)(?\'secret_token\'[-\\w]+)@";i:1;a:2:{i:0;s:0:"";i:1;s:12:"secret_token";}}}i:1;a:2:{i:0;a:3:{s:7:"extract";a:1:{i:0;a:2:{i:0;s:36:"@soundcloud:tracks:(?\'track_id\'\\d+)@";i:1;R:109;}}s:6:"header";s:29:"User-agent: PHP (not Mozilla)";s:5:"match";a:1:{i:0;a:2:{i:0;s:56:"@soundcloud\\.com/(?!playlists/\\d|tracks/\\d)[-\\w]+/[-\\w]@";i:1;R:96;}}}i:1;a:3:{s:7:"extract";a:1:{i:0;a:2:{i:0;s:44:"@soundcloud://playlists:(?\'playlist_id\'\\d+)@";i:1;R:162;}}s:6:"header";s:29:"User-agent: PHP (not Mozilla)";s:5:"match";a:1:{i:0;a:2:{i:0;s:27:"@soundcloud\\.com/\\w+/sets/@";i:1;R:96;}}}}}s:7:"spotify";a:2:{i:0;a:1:{i:0;a:2:{i:0;s:102:"!(?:open|play)\\.spotify\\.com/(?\'id\'(?:user/[-.\\w]+/)?(?:album|artist|playlist|track)(?:[:/][-.\\w]+)+)!";i:1;R:119;}}i:1;R:84;}s:6:"twitch";a:2:{i:0;a:4:{i:0;a:2:{i:0;s:47:"#twitch\\.tv/(?:videos|\\w+/v)/(?\'video_id\'\\d+)?#";i:1;a:2:{i:0;s:0:"";i:1;s:8:"video_id";}}i:1;a:2:{i:0;s:44:"#www\\.twitch\\.tv/(?!videos/)(?\'channel\'\\w+)#";i:1;a:2:{i:0;s:0:"";i:1;s:7:"channel";}}i:2;a:2:{i:0;s:32:"#t=(?\'t\'(?:(?:\\d+h)?\\d+m)?\\d+s)#";i:1;R:126;}i:3;a:2:{i:0;s:56:"#clips\\.twitch\\.tv/(?:(?\'channel\'\\w+)/)?(?\'clip_id\'\\w+)#";i:1;a:3:{i:0;s:0:"";i:1;s:7:"channel";i:2;s:7:"clip_id";}}}i:1;R:84;}s:5:"vimeo";a:2:{i:0;a:2:{i:0;a:2:{i:0;s:50:"!vimeo\\.com/(?:channels/[^/]+/|video/)?(?\'id\'\\d+)!";i:1;R:119;}i:1;a:2:{i:0;s:19:"!#t=(?\'t\'[\\dhms]+)!";i:1;R:126;}}i:1;R:84;}s:4:"vine";a:2:{i:0;a:1:{i:0;a:2:{i:0;s:25:"!vine\\.co/v/(?\'id\'[^/]+)!";i:1;R:119;}}i:1;R:84;}s:7:"youtube";a:2:{i:0;a:4:{i:0;a:2:{i:0;s:69:"!youtube\\.com/(?:watch.*?v=|v/|attribution_link.*?v%3D)(?\'id\'[-\\w]+)!";i:1;R:119;}i:1;a:2:{i:0;s:25:"!youtu\\.be/(?\'id\'[-\\w]+)!";i:1;R:119;}i:2;a:2:{i:0;s:25:"@[#&?]t=(?\'t\'\\d[\\dhms]*)@";i:1;R:126;}i:3;a:2:{i:0;s:26:"![&?]list=(?\'list\'[-\\w]+)!";i:1;a:2:{i:0;s:0:"";i:1;s:4:"list";}}}i:1;a:1:{i:0;a:2:{s:7:"extract";a:1:{i:0;a:2:{i:0;s:19:"!/vi/(?\'id\'[-\\w]+)!";i:1;R:119;}}s:5:"match";a:1:{i:0;a:2:{i:0;s:14:"!/shared\\?ci=!";i:1;R:96;}}}}}}}s:14:"' . "\0" . '*' . "\0" . 'rootContext";a:2:{s:7:"allowed";a:2:{i:0;i:65527;i:1;i:65432;}s:5:"flags";i:8;}s:13:"' . "\0" . '*' . "\0" . 'tagsConfig";a:74:{s:8:"BANDCAMP";a:7:{s:10:"attributes";a:3:{s:8:"album_id";a:2:{s:8:"required";b:0;s:11:"filterChain";R:84;}s:8:"track_id";R:256;s:9:"track_num";R:256;}s:11:"filterChain";a:1:{i:0;a:2:{s:8:"callback";s:59:"s9e\\TextFormatter\\Parser\\FilterProcessing::filterAttributes";s:6:"params";a:4:{s:3:"tag";N;s:9:"tagConfig";N;s:14:"registeredVars";N;s:6:"logger";N;}}}s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:3089;}s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";a:2:{i:0;i:49360;i:1;i:32768;}}s:1:"C";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:66;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:0;s:7:"allowed";a:2:{i:0;i:0;i:1;i:0;}}s:4:"CODE";a:7:{s:10:"attributes";a:1:{s:4:"lang";a:2:{s:11:"filterChain";a:1:{i:0;a:2:{s:8:"callback";s:62:"s9e\\TextFormatter\\Parser\\AttributeFilters\\RegexpFilter::filter";s:6:"params";a:2:{s:9:"attrValue";N;i:0;s:23:"/^[- +,.0-9A-Za-z_]+$/D";}}}s:8:"required";b:0;}}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";a:10:{s:1:"C";i:1;s:2:"EM";i:1;s:6:"STRONG";i:1;s:3:"URL";i:1;s:5:"EMAIL";i:1;s:6:"html:b";i:1;s:9:"html:code";i:1;s:6:"html:i";i:1;s:11:"html:strong";i:1;s:6:"html:u";i:1;}s:12:"fosterParent";R:295;s:5:"flags";i:4436;}s:8:"tagLimit";i:5000;s:9:"bitNumber";i:1;s:7:"allowed";R:280;}s:11:"DAILYMOTION";a:7:{s:10:"attributes";a:2:{s:2:"id";R:256;s:1:"t";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";R:271;}s:3:"DEL";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:512;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:0;s:7:"allowed";R:249;}s:2:"EM";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:2;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:0;s:7:"allowed";a:2:{i:0;i:65521;i:1;i:65408;}}s:5:"EMAIL";a:7:{s:10:"attributes";a:1:{s:5:"email";a:2:{s:11:"filterChain";a:1:{i:0;a:2:{s:8:"callback";s:61:"s9e\\TextFormatter\\Parser\\AttributeFilters\\EmailFilter::filter";s:6:"params";a:1:{s:9:"attrValue";N;}}}s:8:"required";b:1;}}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:514;}s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";a:2:{i:0;i:53191;i:1;i:65432;}}s:3:"ESC";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:1616;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:6;s:7:"allowed";R:280;}s:8:"FACEBOOK";a:7:{s:10:"attributes";a:3:{s:2:"id";R:256;s:4:"type";R:256;s:4:"user";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";R:271;}s:2:"FP";a:7:{s:10:"attributes";a:1:{s:4:"char";a:2:{s:8:"required";b:1;s:11:"filterChain";R:84;}}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:7;s:7:"allowed";a:2:{i:0;i:49344;i:1;i:32896;}}s:2:"H1";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";R:295;s:12:"fosterParent";R:295;s:5:"flags";i:260;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:2;s:7:"allowed";R:326;}s:2:"H2";R:367;s:2:"H3";R:367;s:2:"H4";R:367;s:2:"H5";R:367;s:2:"H6";R:367;s:2:"HC";a:7:{s:10:"attributes";a:1:{s:7:"content";R:359;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:3153;}s:8:"tagLimit";i:5000;s:9:"bitNumber";i:6;s:7:"allowed";R:280;}s:2:"HE";R:357;s:2:"HR";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:2:{s:11:"closeParent";R:295;s:5:"flags";i:3349;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:1;s:7:"allowed";R:364;}s:3:"IMG";a:7:{s:10:"attributes";a:3:{s:3:"alt";R:256;s:3:"src";a:2:{s:11:"filterChain";a:1:{i:0;a:2:{s:8:"callback";s:59:"s9e\\TextFormatter\\Parser\\AttributeFilters\\UrlFilter::filter";s:6:"params";a:3:{s:9:"attrValue";N;s:9:"urlConfig";N;s:6:"logger";N;}}}s:8:"required";b:1;}s:5:"title";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:0;s:7:"allowed";R:364;}s:2:"LI";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";a:12:{s:1:"C";i:1;s:2:"EM";i:1;s:6:"STRONG";i:1;s:3:"URL";i:1;s:5:"EMAIL";i:1;s:6:"html:b";i:1;s:9:"html:code";i:1;s:6:"html:i";i:1;s:11:"html:strong";i:1;s:6:"html:u";i:1;s:2:"LI";i:1;s:7:"html:li";i:1;}s:12:"fosterParent";R:295;s:5:"flags";i:264;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:3;s:7:"allowed";a:2:{i:0;i:65527;i:1;i:65424;}}s:4:"LIST";a:7:{s:10:"attributes";a:2:{s:5:"start";a:2:{s:11:"filterChain";a:1:{i:0;a:2:{s:8:"callback";s:67:"s9e\\TextFormatter\\Parser\\AttributeFilters\\NumericFilter::filterUint";s:6:"params";R:335;}}s:8:"required";b:0;}s:4:"type";R:285;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";R:295;s:12:"fosterParent";R:295;s:5:"flags";i:3460;}s:8:"tagLimit";i:5000;s:9:"bitNumber";i:1;s:7:"allowed";a:2:{i:0;i:65352;i:1;i:65408;}}s:8:"LIVELEAK";a:7:{s:10:"attributes";a:1:{s:2:"id";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";R:271;}s:5:"MEDIA";a:7:{s:11:"filterChain";a:1:{i:0;a:2:{s:8:"callback";s:54:"s9e\\TextFormatter\\Plugins\\MediaEmbed\\Parser::filterTag";s:6:"params";a:5:{s:3:"tag";N;s:6:"parser";N;s:16:"MediaEmbed.hosts";N;s:16:"MediaEmbed.sites";N;s:8:"cacheDir";N;}}}s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:513;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:15;s:7:"allowed";a:2:{i:0;i:65527;i:1;i:65304;}}s:5:"QUOTE";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";R:295;s:12:"fosterParent";R:295;s:5:"flags";i:268;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:1;s:7:"allowed";R:419;}s:10:"SOUNDCLOUD";a:7:{s:10:"attributes";a:4:{s:2:"id";R:256;s:11:"playlist_id";R:256;s:12:"secret_token";R:256;s:8:"track_id";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";R:271;}s:7:"SPOTIFY";R:437;s:6:"STRONG";R:320;s:3:"SUB";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:0;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:0;s:7:"allowed";R:326;}s:3:"SUP";R:471;s:5:"TABLE";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:430;s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:1;s:7:"allowed";a:2:{i:0;i:65344;i:1;i:65413;}}s:5:"TBODY";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";a:20:{s:1:"C";i:1;s:2:"EM";i:1;s:6:"STRONG";i:1;s:3:"URL";i:1;s:5:"EMAIL";i:1;s:6:"html:b";i:1;s:9:"html:code";i:1;s:6:"html:i";i:1;s:11:"html:strong";i:1;s:6:"html:u";i:1;s:5:"TBODY";i:1;s:2:"TD";i:1;s:2:"TH";i:1;s:5:"THEAD";i:1;s:2:"TR";i:1;s:10:"html:tbody";i:1;s:7:"html:td";i:1;s:7:"html:th";i:1;s:10:"html:thead";i:1;s:7:"html:tr";i:1;}s:12:"fosterParent";R:295;s:5:"flags";i:3456;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:8;s:7:"allowed";a:2:{i:0;i:65344;i:1;i:65412;}}s:2:"TD";a:7:{s:10:"attributes";a:1:{s:5:"align";a:2:{s:11:"filterChain";a:2:{i:0;a:2:{s:8:"callback";s:10:"strtolower";s:6:"params";R:335;}i:1;a:2:{s:8:"callback";s:62:"s9e\\TextFormatter\\Parser\\AttributeFilters\\RegexpFilter::filter";s:6:"params";a:2:{s:9:"attrValue";N;i:0;s:34:"/^(?>center|justify|left|right)$/D";}}}s:8:"required";b:0;}}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";a:14:{s:1:"C";i:1;s:2:"EM";i:1;s:6:"STRONG";i:1;s:3:"URL";i:1;s:5:"EMAIL";i:1;s:6:"html:b";i:1;s:9:"html:code";i:1;s:6:"html:i";i:1;s:11:"html:strong";i:1;s:6:"html:u";i:1;s:2:"TD";i:1;s:2:"TH";i:1;s:7:"html:td";i:1;s:7:"html:th";i:1;}s:12:"fosterParent";R:295;s:5:"flags";i:256;}s:8:"tagLimit";i:5000;s:9:"bitNumber";i:9;s:7:"allowed";R:419;}s:2:"TH";a:7:{s:10:"attributes";R:515;s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:527;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:9;s:7:"allowed";a:2:{i:0;i:64499;i:1;i:65424;}}s:5:"THEAD";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";R:295;s:12:"fosterParent";R:295;s:5:"flags";i:3456;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:8;s:7:"allowed";R:511;}s:2:"TR";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";a:16:{s:1:"C";i:1;s:2:"EM";i:1;s:6:"STRONG";i:1;s:3:"URL";i:1;s:5:"EMAIL";i:1;s:6:"html:b";i:1;s:9:"html:code";i:1;s:6:"html:i";i:1;s:11:"html:strong";i:1;s:6:"html:u";i:1;s:2:"TD";i:1;s:2:"TH";i:1;s:2:"TR";i:1;s:7:"html:td";i:1;s:7:"html:th";i:1;s:7:"html:tr";i:1;}s:12:"fosterParent";R:295;s:5:"flags";i:3456;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:10;s:7:"allowed";a:2:{i:0;i:65344;i:1;i:65410;}}s:6:"TWITCH";a:7:{s:10:"attributes";a:4:{s:7:"channel";R:256;s:7:"clip_id";R:256;s:1:"t";R:256;s:8:"video_id";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";R:271;}s:3:"URL";a:7:{s:10:"attributes";a:2:{s:5:"title";R:256;s:3:"url";R:388;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:339;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:4;s:7:"allowed";R:343;}s:5:"VIMEO";a:7:{s:10:"attributes";a:2:{s:2:"id";R:256;s:1:"t";a:2:{s:11:"filterChain";a:1:{i:0;a:2:{s:8:"callback";s:65:"s9e\\TextFormatter\\Parser\\AttributeFilters\\TimestampFilter::filter";s:6:"params";R:335;}}s:8:"required";b:0;}}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";R:271;}s:4:"VINE";R:437;s:7:"YOUTUBE";a:7:{s:10:"attributes";a:3:{s:2:"id";a:2:{s:11:"filterChain";a:1:{i:0;a:2:{s:8:"callback";s:62:"s9e\\TextFormatter\\Parser\\AttributeFilters\\RegexpFilter::filter";s:6:"params";a:2:{s:9:"attrValue";N;i:0;s:19:"/^[-0-9A-Za-z_]+$/D";}}}s:8:"required";b:0;}s:4:"list";R:256;s:1:"t";R:597;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:267;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:5;s:7:"allowed";R:271;}s:9:"html:abbr";a:7:{s:10:"attributes";a:1:{s:5:"title";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:473;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:0;s:7:"allowed";R:326;}s:6:"html:b";R:320;s:7:"html:br";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:1:{s:5:"flags";i:3201;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:0;s:7:"allowed";a:2:{i:0;i:65344;i:1;i:65408;}}s:9:"html:code";R:274;s:7:"html:dd";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";a:12:{s:1:"C";i:1;s:2:"EM";i:1;s:6:"STRONG";i:1;s:3:"URL";i:1;s:5:"EMAIL";i:1;s:6:"html:b";i:1;s:9:"html:code";i:1;s:6:"html:i";i:1;s:11:"html:strong";i:1;s:6:"html:u";i:1;s:7:"html:dd";i:1;s:7:"html:dt";i:1;}s:12:"fosterParent";R:295;s:5:"flags";i:256;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:11;s:7:"allowed";R:419;}s:8:"html:del";R:314;s:8:"html:div";a:7:{s:10:"attributes";a:1:{s:5:"class";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:462;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:12;s:7:"allowed";R:249;}s:7:"html:dl";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:430;s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:1;s:7:"allowed";a:2:{i:0;i:65344;i:1;i:65432;}}s:7:"html:dt";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:634;s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:11;s:7:"allowed";R:550;}s:6:"html:i";R:320;s:8:"html:img";a:7:{s:10:"attributes";a:5:{s:3:"alt";R:256;s:6:"height";R:256;s:3:"src";a:2:{s:11:"filterChain";R:389;s:8:"required";b:0;}s:5:"title";R:256;s:5:"width";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:625;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:0;s:7:"allowed";R:629;}s:8:"html:ins";R:314;s:7:"html:li";R:400;s:7:"html:ol";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:430;s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:1;s:7:"allowed";R:434;}s:8:"html:pre";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";R:295;s:12:"fosterParent";R:295;s:5:"flags";i:276;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:1;s:7:"allowed";R:326;}s:7:"html:rb";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";R:295;s:12:"fosterParent";R:295;s:5:"flags";i:256;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:13;s:7:"allowed";R:326;}s:7:"html:rp";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";a:3:{s:11:"closeParent";a:12:{s:1:"C";i:1;s:2:"EM";i:1;s:6:"STRONG";i:1;s:3:"URL";i:1;s:5:"EMAIL";i:1;s:6:"html:b";i:1;s:9:"html:code";i:1;s:6:"html:i";i:1;s:11:"html:strong";i:1;s:6:"html:u";i:1;s:7:"html:rp";i:1;s:7:"html:rt";i:1;}s:12:"fosterParent";R:295;s:5:"flags";i:256;}s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:14;s:7:"allowed";R:326;}s:7:"html:rt";R:690;s:8:"html:rtc";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:686;s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:13;s:7:"allowed";a:2:{i:0;i:65521;i:1;i:65472;}}s:9:"html:ruby";a:7:{s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:473;s:8:"tagLimit";i:5000;s:10:"attributes";R:84;s:9:"bitNumber";i:0;s:7:"allowed";a:2:{i:0;i:65521;i:1;i:65504;}}s:9:"html:span";a:7:{s:10:"attributes";R:652;s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:473;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:0;s:7:"allowed";R:326;}s:11:"html:strong";R:320;s:8:"html:sub";R:471;s:8:"html:sup";R:471;s:10:"html:table";R:477;s:10:"html:tbody";R:484;s:7:"html:td";a:7:{s:10:"attributes";a:2:{s:7:"colspan";R:256;s:7:"rowspan";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:527;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:9;s:7:"allowed";R:419;}s:10:"html:tfoot";R:484;s:7:"html:th";a:7:{s:10:"attributes";a:3:{s:7:"colspan";R:256;s:7:"rowspan";R:256;s:5:"scope";R:256;}s:11:"filterChain";R:258;s:12:"nestingLimit";i:10;s:5:"rules";R:527;s:8:"tagLimit";i:5000;s:9:"bitNumber";i:9;s:7:"allowed";R:550;}s:10:"html:thead";R:553;s:7:"html:tr";R:559;s:6:"html:u";R:320;s:7:"html:ul";R:674;}}'); + } + public static function getRenderer() + { + return \unserialize('O:42:"s9e\\TextFormatter\\Bundles\\Fatdown\\Renderer":2:{s:19:"enableQuickRenderer";b:1;s:9:"' . "\0" . '*' . "\0" . 'params";a:0:{}}'); + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Bundles/Fatdown/Renderer.php b/vendor/s9e/text-formatter/src/Bundles/Fatdown/Renderer.php new file mode 100644 index 0000000..6d0cb1a --- /dev/null +++ b/vendor/s9e/text-formatter/src/Bundles/Fatdown/Renderer.php @@ -0,0 +1,26 @@ +nodeName){case'BANDCAMP':$this->out.='';break;case'C':case'html:code':$this->out.='';$this->at($node);$this->out.='';break;case'CODE':$this->out.='
hasAttribute('lang'))$this->out.=' class="language-'.\htmlspecialchars($node->getAttribute('lang'),2).'"';$this->out.='>';$this->at($node);$this->out.='
';break;case'DAILYMOTION':$this->out.='';break;case'DEL':case'html:del':$this->out.='';$this->at($node);$this->out.='';break;case'EM':$this->out.='';$this->at($node);$this->out.='';break;case'EMAIL':$this->out.='';$this->at($node);$this->out.='';break;case'ESC':$this->at($node);break;case'FACEBOOK':$this->out.='';break;case'FP':case'HE':$this->out.=\htmlspecialchars($node->getAttribute('char'),0);break;case'H1':$this->out.='

';$this->at($node);$this->out.='

';break;case'H2':$this->out.='

';$this->at($node);$this->out.='

';break;case'H3':$this->out.='

';$this->at($node);$this->out.='

';break;case'H4':$this->out.='

';$this->at($node);$this->out.='

';break;case'H5':$this->out.='
';$this->at($node);$this->out.='
';break;case'H6':$this->out.='
';$this->at($node);$this->out.='
';break;case'HC':$this->out.='';break;case'HR':$this->out.='
';break;case'IMG':$this->out.='hasAttribute('alt'))$this->out.=' alt="'.\htmlspecialchars($node->getAttribute('alt'),2).'"';if($node->hasAttribute('title'))$this->out.=' title="'.\htmlspecialchars($node->getAttribute('title'),2).'"';$this->out.='>';break;case'LI':case'html:li':$this->out.='
  • ';$this->at($node);$this->out.='
  • ';break;case'LIST':if(!$node->hasAttribute('type')){$this->out.='
      ';$this->at($node);$this->out.='
    ';}else{$this->out.='hasAttribute('start'))$this->out.=' start="'.\htmlspecialchars($node->getAttribute('start'),2).'"';$this->out.='>';$this->at($node);$this->out.='';}break;case'LIVELEAK':$this->out.='';break;case'QUOTE':$this->out.='
    ';$this->at($node);$this->out.='
    ';break;case'SOUNDCLOUD':$this->out.='';break;case'SPOTIFY':$this->out.='';break;case'STRONG':case'html:strong':$this->out.='';$this->at($node);$this->out.='';break;case'SUB':case'html:sub':$this->out.='';$this->at($node);$this->out.='';break;case'SUP':case'html:sup':$this->out.='';$this->at($node);$this->out.='';break;case'TABLE':case'html:table':$this->out.='';$this->at($node);$this->out.='
    ';break;case'TBODY':case'html:tbody':$this->out.='';$this->at($node);$this->out.='';break;case'TD':$this->out.='hasAttribute('align'))$this->out.=' style="text-align:'.\htmlspecialchars($node->getAttribute('align'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'TH':$this->out.='hasAttribute('align'))$this->out.=' style="text-align:'.\htmlspecialchars($node->getAttribute('align'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'THEAD':case'html:thead':$this->out.='';$this->at($node);$this->out.='';break;case'TR':case'html:tr':$this->out.='';$this->at($node);$this->out.='';break;case'TWITCH':$this->out.='';break;case'URL':$this->out.='hasAttribute('title'))$this->out.=' title="'.\htmlspecialchars($node->getAttribute('title'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'VIMEO':$this->out.='';break;case'VINE':$this->out.='';break;case'YOUTUBE':$this->out.='';break;case'br':case'html:br':$this->out.='
    ';break;case'e':case'i':case's':break;case'html:abbr':$this->out.='hasAttribute('title'))$this->out.=' title="'.\htmlspecialchars($node->getAttribute('title'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'html:b':$this->out.='';$this->at($node);$this->out.='';break;case'html:dd':$this->out.='
    ';$this->at($node);$this->out.='
    ';break;case'html:div':$this->out.='hasAttribute('class'))$this->out.=' class="'.\htmlspecialchars($node->getAttribute('class'),2).'"';$this->out.='>';$this->at($node);$this->out.='
    ';break;case'html:dl':$this->out.='
    ';$this->at($node);$this->out.='
    ';break;case'html:dt':$this->out.='
    ';$this->at($node);$this->out.='
    ';break;case'html:i':$this->out.='';$this->at($node);$this->out.='';break;case'html:img':$this->out.='hasAttribute('alt'))$this->out.=' alt="'.\htmlspecialchars($node->getAttribute('alt'),2).'"';if($node->hasAttribute('height'))$this->out.=' height="'.\htmlspecialchars($node->getAttribute('height'),2).'"';if($node->hasAttribute('src'))$this->out.=' src="'.\htmlspecialchars($node->getAttribute('src'),2).'"';if($node->hasAttribute('title'))$this->out.=' title="'.\htmlspecialchars($node->getAttribute('title'),2).'"';if($node->hasAttribute('width'))$this->out.=' width="'.\htmlspecialchars($node->getAttribute('width'),2).'"';$this->out.='>';break;case'html:ins':$this->out.='';$this->at($node);$this->out.='';break;case'html:ol':$this->out.='
      ';$this->at($node);$this->out.='
    ';break;case'html:pre':$this->out.='
    ';$this->at($node);$this->out.='
    ';break;case'html:rb':$this->out.='';$this->at($node);$this->out.='';break;case'html:rp':$this->out.='';$this->at($node);$this->out.='';break;case'html:rt':$this->out.='';$this->at($node);$this->out.='';break;case'html:rtc':$this->out.='';$this->at($node);$this->out.='';break;case'html:ruby':$this->out.='';$this->at($node);$this->out.='';break;case'html:span':$this->out.='hasAttribute('class'))$this->out.=' class="'.\htmlspecialchars($node->getAttribute('class'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'html:td':$this->out.='hasAttribute('colspan'))$this->out.=' colspan="'.\htmlspecialchars($node->getAttribute('colspan'),2).'"';if($node->hasAttribute('rowspan'))$this->out.=' rowspan="'.\htmlspecialchars($node->getAttribute('rowspan'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'html:tfoot':$this->out.='';$this->at($node);$this->out.='';break;case'html:th':$this->out.='hasAttribute('colspan'))$this->out.=' colspan="'.\htmlspecialchars($node->getAttribute('colspan'),2).'"';if($node->hasAttribute('rowspan'))$this->out.=' rowspan="'.\htmlspecialchars($node->getAttribute('rowspan'),2).'"';if($node->hasAttribute('scope'))$this->out.=' scope="'.\htmlspecialchars($node->getAttribute('scope'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'html:u':$this->out.='';$this->at($node);$this->out.='';break;case'html:ul':$this->out.='
      ';$this->at($node);$this->out.='
    ';break;case'p':$this->out.='

    ';$this->at($node);$this->out.='

    ';break;default:$this->at($node);} + } + public $enableQuickRenderer=\true; + protected $static=['/C'=>'','/CODE'=>'','/DEL'=>'','/EM'=>'','/EMAIL'=>'','/ESC'=>'','/H1'=>'','/H2'=>'','/H3'=>'','/H4'=>'','/H5'=>'','/H6'=>'','/LI'=>'','/QUOTE'=>'','/STRONG'=>'
    ','/SUB'=>'','/SUP'=>'','/TABLE'=>'','/TBODY'=>'','/TD'=>'','/TH'=>'','/THEAD'=>'','/TR'=>'','/URL'=>'','/html:abbr'=>'','/html:b'=>'','/html:code'=>'','/html:dd'=>'','/html:del'=>'','/html:div'=>'
    ','/html:dl'=>'','/html:dt'=>'','/html:i'=>'','/html:ins'=>'','/html:li'=>'','/html:ol'=>'','/html:pre'=>'','/html:rb'=>'','/html:rp'=>'','/html:rt'=>'','/html:rtc'=>'','/html:ruby'=>'','/html:span'=>'','/html:strong'=>'','/html:sub'=>'','/html:sup'=>'','/html:table'=>'','/html:tbody'=>'','/html:td'=>'','/html:tfoot'=>'','/html:th'=>'','/html:thead'=>'','/html:tr'=>'','/html:u'=>'','/html:ul'=>'','C'=>'','DEL'=>'','EM'=>'','ESC'=>'','H1'=>'

    ','H2'=>'

    ','H3'=>'

    ','H4'=>'

    ','H5'=>'

    ','H6'=>'
    ','HR'=>'
    ','LI'=>'
  • ','QUOTE'=>'
    ','STRONG'=>'','SUB'=>'','SUP'=>'','TABLE'=>'','TBODY'=>'','THEAD'=>'','TR'=>'','html:b'=>'','html:br'=>'
    ','html:code'=>'','html:dd'=>'
    ','html:del'=>'','html:dl'=>'
    ','html:dt'=>'
    ','html:i'=>'','html:ins'=>'','html:li'=>'
  • ','html:ol'=>'
      ','html:pre'=>'
      ','html:rb'=>'','html:rp'=>'','html:rt'=>'','html:rtc'=>'','html:ruby'=>'','html:strong'=>'','html:sub'=>'','html:sup'=>'','html:table'=>'
  • ','html:tbody'=>'','html:tfoot'=>'','html:thead'=>'','html:tr'=>'','html:u'=>'','html:ul'=>'';else$html.='';break;case'BANDCAMP':$attributes+=['track_num'=>\null,'track_id'=>\null];$html.='';break;case'CODE':$html.='
    \null];$html.='';break;case'FACEBOOK':$attributes+=['type'=>\null,'id'=>\null];$html.='';break;case'FP':case'HE':$attributes+=['char'=>\null];$html.=\str_replace('"','"',$attributes['char']);break;case'HC':$attributes+=['content'=>\null];$html.='';break;case'LIST':if(!isset($attributes['type']))$html.='
    ';$this->at($node);$this->out.='
    ';break;case'TD':$this->out.='hasAttribute('colspan'))$this->out.=' colspan="'.\htmlspecialchars($node->getAttribute('colspan'),2).'"';if($node->hasAttribute('rowspan'))$this->out.=' rowspan="'.\htmlspecialchars($node->getAttribute('rowspan'),2).'"';if($node->hasAttribute('align'))$this->out.=' style="text-align:'.\htmlspecialchars($node->getAttribute('align'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'TH':$this->out.='hasAttribute('colspan'))$this->out.=' colspan="'.\htmlspecialchars($node->getAttribute('colspan'),2).'"';if($node->hasAttribute('rowspan'))$this->out.=' rowspan="'.\htmlspecialchars($node->getAttribute('rowspan'),2).'"';if($node->hasAttribute('align'))$this->out.=' style="text-align:'.\htmlspecialchars($node->getAttribute('align'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'TR':$this->out.='';$this->at($node);$this->out.='';break;case'TWITCH':$this->out.='';break;case'TWITTER':$this->out.='';break;case'U':$this->out.='';$this->at($node);$this->out.='';break;case'UL':$this->out.='
      ';$this->at($node);$this->out.='
    ';break;case'URL':$this->out.='hasAttribute('title'))$this->out.=' title="'.\htmlspecialchars($node->getAttribute('title'),2).'"';$this->out.='>';$this->at($node);$this->out.='';break;case'VIMEO':$this->out.='';break;case'VINE':$this->out.='';break;case'WSHH':$this->out.='';break;case'YOUTUBE':$this->out.='';break;case'br':$this->out.='
    ';break;case'e':case'i':case's':break;case'p':$this->out.='

    ';$this->at($node);$this->out.='

    ';break;default:$this->at($node);} + } + public $enableQuickRenderer=\true; + protected $static=['/B'=>'
    ','/CENTER'=>'
  • ','/CODE'=>'','/COLOR'=>'','/EMAIL'=>'','/FONT'=>'','/I'=>'','/LI'=>'','/OL'=>'','/QUOTE'=>'
    ','/S'=>'','/SIZE'=>'','/SPOILER'=>'','/TABLE'=>'','/TD'=>'','/TH'=>'','/TR'=>'','/U'=>'','/UL'=>'','/URL'=>'','B'=>'','CENTER'=>'
    ','I'=>'','LI'=>'
  • ','OL'=>'
      ','S'=>'','TABLE'=>'','TR'=>'','U'=>'','UL'=>'';elseif((\strpos($attributes['type'],'decimal')===0)||(\strpos($attributes['type'],'lower')===0)||(\strpos($attributes['type'],'upper')===0))$html.='';else$html.='';break;case'BANDCAMP':$attributes+=['track_num'=>\null,'track_id'=>\null];$html.='';break;case'CODE':$html.='
      \null];$html.='';break;case'EMOJI':$attributes+=['seq'=>\null];$textContent=$this->getQuickTextContent($xml);$html.=''.\htmlspecialchars($textContent,2).'';break;case'FACEBOOK':$attributes+=['type'=>\null,'id'=>\null];$html.='';break;case'KICKSTARTER':$attributes+=['id'=>\null];$html.='';else$html.=' style="display:inline-block;width:100%;max-width:220px">';$html.='';break;case'LIST':$attributes+=['type'=>\null];if(!isset($attributes['type']))$html.='
      '], + 'TBODY' => ['template' => ''], + 'TD' => $this->generateCellTagConfig('td'), + 'TH' => $this->generateCellTagConfig('th'), + 'THEAD' => ['template' => ''], + 'TR' => ['template' => ''] + ]; + foreach ($tags as $tagName => $tagConfig) + if (!isset($this->configurator->tags[$tagName])) + $this->configurator->tags->add($tagName, $tagConfig); + } + protected function generateCellTagConfig($elName) + { + $alignFilter = new ChoiceFilter(['left', 'center', 'right', 'justify'], \true); + return [ + 'attributes' => [ + 'align' => [ + 'filterChain' => ['strtolower', $alignFilter], + 'required' => \false + ] + ], + 'rules' => ['createParagraphs' => \false], + 'template' => + '<' . $elName . '> + + text-align: + + + ' + ]; + } + public function asConfig() + { + return [ + 'overwriteEscapes' => isset($this->configurator->Escaper), + 'overwriteMarkdown' => isset($this->configurator->Litedown) + ]; + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Plugins/PipeTables/Parser.js b/vendor/s9e/text-formatter/src/Plugins/PipeTables/Parser.js new file mode 100644 index 0000000..5c4ff80 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Plugins/PipeTables/Parser.js @@ -0,0 +1,369 @@ +var pos, table = null, tableTag, tables, text; + +if (config.overwriteMarkdown) +{ + overwriteMarkdown(); +} +if (config.overwriteEscapes) +{ + overwriteEscapes(); +} + +captureTables(); +processTables(); + +/** +* Add current line to a table +* +* @param {!string} line Line of text +*/ +function addLine(line) +{ + var ignoreLen = 0; + + if (!table) + { + table = { rows: [] }; + + // Make the table start at the first non-space character + ignoreLen = /^ */.exec(line)[0].length; + line = line.substr(ignoreLen); + } + + // Overwrite the outermost pipes + line = line.replace(/^( *)\|/, '$1 ').replace(/\|( *)$/, ' $1'); + + table.rows.push({ line: line, pos: pos + ignoreLen }); +} + +/** +* Process current table's body +*/ +function addTableBody() +{ + var i = 1, + cnt = table.rows.length; + while (++i < cnt) + { + addTableRow('TD', table.rows[i]); + } + + createBodyTags(table.rows[2].pos, pos); +} + +/** +* Add a cell's tags for current table at current position +* +* @param {!string} tagName Either TD or TH +* @param {!string} align Either "left", "center", "right" or "" +*/ +function addTableCell(tagName, align, text) +{ + var startPos = pos, + endPos = startPos + text.length, + ignoreLen; + pos = endPos; + + var m = /^( *).*?( *)$/.exec(text); + if (m[1]) + { + ignoreLen = m[1].length; + createIgnoreTag(startPos, ignoreLen); + startPos += ignoreLen; + } + if (m[2]) + { + ignoreLen = m[2].length; + createIgnoreTag(endPos - ignoreLen, ignoreLen); + endPos -= ignoreLen; + } + + createCellTags(tagName, startPos, endPos, align); +} + +/** +* Process current table's head +*/ +function addTableHead() +{ + addTableRow('TH', table.rows[0]); + createHeadTags(table.rows[0].pos, pos); +} + +/** +* Process given table row +* +* @param {!string} tagName Either TD or TH +* @param {!Object} row +*/ +function addTableRow(tagName, row) +{ + pos = row.pos; + row.line.split('|').forEach(function(str, i) + { + if (i > 0) + { + createIgnoreTag(pos, 1); + ++pos; + } + + var align = (!table.cols[i]) ? '' : table.cols[i]; + addTableCell(tagName, align, str); + }); + + createRowTags(row.pos, pos); +} + +/** +* Capture all pipe tables in current text +*/ +function captureTables() +{ + table = null; + tables = []; + + pos = 0; + text.split("\n").forEach(function(line) + { + if (line.indexOf('|') < 0) + { + endTable(); + } + else + { + addLine(line); + } + pos += 1 + line.length; + }); + endTable(); +} + +/** +* Create a pair of TBODY tags for given text span +* +* @param {!number} startPos +* @param {!number} endPos +*/ +function createBodyTags(startPos, endPos) +{ + addTagPair('TBODY', startPos, 0, endPos, 0, -103); +} + +/** +* Create a pair of TD or TH tags for given text span +* +* @param {!string} tagName Either TD or TH +* @param {!number} startPos +* @param {!number} endPos +* @param {!string} align Either "left", "center", "right" or "" +*/ +function createCellTags(tagName, startPos, endPos, align) +{ + var tag; + if (startPos === endPos) + { + tag = addSelfClosingTag(tagName, startPos, 0, -101); + } + else + { + tag = addTagPair(tagName, startPos, 0, endPos, 0, -101); + } + if (align) + { + tag.setAttribute('align', align); + } +} + +/** +* Create a pair of THEAD tags for given text span +* +* @param {!number} startPos +* @param {!number} endPos +*/ +function createHeadTags(startPos, endPos) +{ + addTagPair('THEAD', startPos, 0, endPos, 0, -103); +} + +/** +* Create an ignore tag for given text span +* +* @param {!number} pos +* @param {!number} len +*/ +function createIgnoreTag(pos, len) +{ + tableTag.cascadeInvalidationTo(addIgnoreTag(pos, len, 1000)); +} + +/** +* Create a pair of TR tags for given text span +* +* @param {!number} startPos +* @param {!number} endPos +*/ +function createRowTags(startPos, endPos) +{ + addTagPair('TR', startPos, 0, endPos, 0, -102); +} + +/** +* Create an ignore tag for given separator row +* +* @param {!Object} row +*/ +function createSeparatorTag(row) +{ + createIgnoreTag(row.pos - 1, 1 + row.line.length); +} + +/** +* Create a pair of TABLE tags for given text span +* +* @param {!number} startPos +* @param {!number} endPos +*/ +function createTableTags(startPos, endPos) +{ + tableTag = addTagPair('TABLE', startPos, 0, endPos, 0, -104); +} + +/** +* End current buffered table +*/ +function endTable() +{ + if (hasValidTable()) + { + table.cols = parseColumnAlignments(table.rows[1].line); + tables.push(table); + } + table = null; +} + +/** +* Test whether a valid table is currently buffered +* +* @return {!boolean} +*/ +function hasValidTable() +{ + return (table && table.rows.length > 2 && isValidSeparator(table.rows[1].line)); +} + +/** +* Test whether given line is a valid separator +* +* @param {!string} line +* @return {!boolean} +*/ +function isValidSeparator(line) +{ + return /^ *:?-+:?(?:(?:\+| *\| *):?-+:?)+ */.test(line); +} + +/** +* Overwrite right angle brackets in given match +* +* @param {!string} str +* @return {!string} +*/ +function overwriteBlockquoteCallback(str) +{ + return str.replace(/>/g, ' '); +} + +/** +* Overwrite escape sequences in current text +*/ +function overwriteEscapes() +{ + if (text.indexOf('\\|') > -1) + { + text = text.replace(/\\[\\|]/g, '..'); + } +} + +/** +* Overwrite backticks in given match +* +* @param {!string} str +* @return string +*/ +function overwriteInlineCodeCallback(str) +{ + return str.replace(/\|/g, '.'); +} + +/** +* Overwrite Markdown-style markup in current text +*/ +function overwriteMarkdown() +{ + // Overwrite inline code spans + if (text.indexOf('`') > -1) + { + text = text.replace(/`[^`]*`/g, overwriteInlineCodeCallback); + } + + // Overwrite blockquotes + if (text.indexOf('>') > -1) + { + text = text.replace(/^(?:> ?)+/gm, overwriteBlockquoteCallback); + } +} + +/** +* Parse and return column alignments in given separator line +* +* @param {!string} line +* @return {!Array} +*/ +function parseColumnAlignments(line) +{ + // Use a bitfield to represent the colons' presence and map it to the CSS value + var align = [ + '', + 'right', + 'left', + 'center' + ], + cols = [], + regexp = /(:?)-+(:?)/g, + m; + + while (m = regexp.exec(line)) + { + var key = (m[1] ? 2 : 0) + (m[2] ? 1 : 0); + cols.push(align[key]); + } + + return cols; +} + +/** +* Process current table declaration +*/ +function processCurrentTable() +{ + var firstRow = table.rows[0], + lastRow = table.rows[table.rows.length - 1]; + createTableTags(firstRow.pos, lastRow.pos + lastRow.line.length); + + addTableHead(); + createSeparatorTag(table.rows[1]); + addTableBody(); +} + +/** +* Process all the captured tables +*/ +function processTables() +{ + var i = -1, cnt = tables.length; + while (++i < cnt) + { + table = tables[i]; + processCurrentTable(); + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Plugins/PipeTables/Parser.php b/vendor/s9e/text-formatter/src/Plugins/PipeTables/Parser.php new file mode 100644 index 0000000..a3ffd40 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Plugins/PipeTables/Parser.php @@ -0,0 +1,210 @@ +text = $text; + if ($this->config['overwriteMarkdown']) + $this->overwriteMarkdown(); + if ($this->config['overwriteEscapes']) + $this->overwriteEscapes(); + $this->captureTables(); + $this->processTables(); + unset($this->tables); + unset($this->text); + } + protected function addLine($line) + { + $ignoreLen = 0; + if (!isset($this->table)) + { + $this->table = []; + \preg_match('/^ */', $line, $m); + $ignoreLen = \strlen($m[0]); + $line = \substr($line, $ignoreLen); + } + $line = \preg_replace('/^( *)\\|/', '$1 ', $line); + $line = \preg_replace('/\\|( *)$/', ' $1', $line); + $this->table['rows'][] = ['line' => $line, 'pos' => $this->pos + $ignoreLen]; + } + protected function addTableBody() + { + $i = 1; + $cnt = \count($this->table['rows']); + while (++$i < $cnt) + $this->addTableRow('TD', $this->table['rows'][$i]); + $this->createBodyTags($this->table['rows'][2]['pos'], $this->pos); + } + protected function addTableCell($tagName, $align, $text) + { + $startPos = $this->pos; + $endPos = $startPos + \strlen($text); + $this->pos = $endPos; + \preg_match('/^( *).*?( *)$/', $text, $m); + if ($m[1]) + { + $ignoreLen = \strlen($m[1]); + $this->createIgnoreTag($startPos, $ignoreLen); + $startPos += $ignoreLen; + } + if ($m[2]) + { + $ignoreLen = \strlen($m[2]); + $this->createIgnoreTag($endPos - $ignoreLen, $ignoreLen); + $endPos -= $ignoreLen; + } + $this->createCellTags($tagName, $startPos, $endPos, $align); + } + protected function addTableHead() + { + $this->addTableRow('TH', $this->table['rows'][0]); + $this->createHeadTags($this->table['rows'][0]['pos'], $this->pos); + } + protected function addTableRow($tagName, $row) + { + $this->pos = $row['pos']; + foreach (\explode('|', $row['line']) as $i => $str) + { + if ($i > 0) + { + $this->createIgnoreTag($this->pos, 1); + ++$this->pos; + } + $align = (empty($this->table['cols'][$i])) ? '' : $this->table['cols'][$i]; + $this->addTableCell($tagName, $align, $str); + } + $this->createRowTags($row['pos'], $this->pos); + } + protected function captureTables() + { + unset($this->table); + $this->tables = []; + $this->pos = 0; + foreach (\explode("\n", $this->text) as $line) + { + if (\strpos($line, '|') === \false) + $this->endTable(); + else + $this->addLine($line); + $this->pos += 1 + \strlen($line); + } + $this->endTable(); + } + protected function createBodyTags($startPos, $endPos) + { + $this->parser->addTagPair('TBODY', $startPos, 0, $endPos, 0, -103); + } + protected function createCellTags($tagName, $startPos, $endPos, $align) + { + if ($startPos === $endPos) + $tag = $this->parser->addSelfClosingTag($tagName, $startPos, 0, -101); + else + $tag = $this->parser->addTagPair($tagName, $startPos, 0, $endPos, 0, -101); + if ($align) + $tag->setAttribute('align', $align); + } + protected function createHeadTags($startPos, $endPos) + { + $this->parser->addTagPair('THEAD', $startPos, 0, $endPos, 0, -103); + } + protected function createIgnoreTag($pos, $len) + { + $this->tableTag->cascadeInvalidationTo($this->parser->addIgnoreTag($pos, $len, 1000)); + } + protected function createRowTags($startPos, $endPos) + { + $this->parser->addTagPair('TR', $startPos, 0, $endPos, 0, -102); + } + protected function createSeparatorTag(array $row) + { + $this->createIgnoreTag($row['pos'] - 1, 1 + \strlen($row['line'])); + } + protected function createTableTags($startPos, $endPos) + { + $this->tableTag = $this->parser->addTagPair('TABLE', $startPos, 0, $endPos, 0, -104); + } + protected function endTable() + { + if ($this->hasValidTable()) + { + $this->table['cols'] = $this->parseColumnAlignments($this->table['rows'][1]['line']); + $this->tables[] = $this->table; + } + unset($this->table); + } + protected function hasValidTable() + { + return (isset($this->table) && \count($this->table['rows']) > 2 && $this->isValidSeparator($this->table['rows'][1]['line'])); + } + protected function isValidSeparator($line) + { + return (bool) \preg_match('/^ *:?-+:?(?:(?:\\+| *\\| *):?-+:?)+ *$/', $line); + } + protected function overwriteBlockquoteCallback(array $m) + { + return \strtr($m[0], '>', ' '); + } + protected function overwriteEscapes() + { + if (\strpos($this->text, '\\|') !== \false) + $this->text = \preg_replace('/\\\\[\\\\|]/', '..', $this->text); + } + protected function overwriteInlineCodeCallback(array $m) + { + return \strtr($m[0], '|', '.'); + } + protected function overwriteMarkdown() + { + if (\strpos($this->text, '`') !== \false) + $this->text = \preg_replace_callback('/`[^`]*`/', [$this, 'overwriteInlineCodeCallback'], $this->text); + if (\strpos($this->text, '>') !== \false) + $this->text = \preg_replace_callback('/^(?:> ?)+/m', [$this, 'overwriteBlockquoteCallback'], $this->text); + } + protected function parseColumnAlignments($line) + { + $align = [ + 0b00 => '', + 0b01 => 'right', + 0b10 => 'left', + 0b11 => 'center' + ]; + $cols = []; + \preg_match_all('/(:?)-+(:?)/', $line, $matches, \PREG_SET_ORDER); + foreach ($matches as $m) + { + $key = (!empty($m[1]) ? 2 : 0) + (!empty($m[2]) ? 1 : 0); + $cols[] = $align[$key]; + } + return $cols; + } + protected function processCurrentTable() + { + $firstRow = $this->table['rows'][0]; + $lastRow = \end($this->table['rows']); + $this->createTableTags($firstRow['pos'], $lastRow['pos'] + \strlen($lastRow['line'])); + $this->addTableHead(); + $this->createSeparatorTag($this->table['rows'][1]); + $this->addTableBody(); + } + protected function processTables() + { + foreach ($this->tables as $table) + { + $this->table = $table; + $this->processCurrentTable(); + } + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Plugins/Preg/Configurator.php b/vendor/s9e/text-formatter/src/Plugins/Preg/Configurator.php new file mode 100644 index 0000000..ad61727 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Plugins/Preg/Configurator.php @@ -0,0 +1,255 @@ +collection)) + return; + $pregs = []; + foreach ($this->collection as $_ca164be8) + { + list($tagName, $regexp, $passthroughIdx) = $_ca164be8; + $captures = RegexpParser::getCaptureNames($regexp); + $pregs[] = [$tagName, new Regexp($regexp, \true), $passthroughIdx, $captures]; + } + return ['generics' => $pregs]; + } + public function getJSHints() + { + $hasPassthrough = \false; + foreach ($this->collection as $_ca164be8) + { + list($tagName, $regexp, $passthroughIdx) = $_ca164be8; + if ($passthroughIdx) + { + $hasPassthrough = \true; + break; + } + } + return ['PREG_HAS_PASSTHROUGH' => $hasPassthrough]; + } + public function match($regexp, $tagName) + { + $tagName = TagName::normalize($tagName); + $passthroughIdx = 0; + $this->parseRegexp($regexp); + foreach ($this->captures as $i => $capture) + { + if (!$this->isCatchAll($capture['expr'])) + continue; + $passthroughIdx = $i; + } + $this->collection[] = [$tagName, $regexp, $passthroughIdx]; + } + public function replace($regexp, $template, $tagName = \null) + { + if (!isset($tagName)) + $tagName = 'PREG_' . \strtoupper(\dechex(\crc32($regexp))); + $this->parseRegexp($regexp); + $this->parseTemplate($template); + $passthroughIdx = $this->getPassthroughCapture(); + if ($passthroughIdx) + $this->captures[$passthroughIdx]['passthrough'] = \true; + $regexp = $this->fixUnnamedCaptures($regexp); + $template = $this->convertTemplate($template, $passthroughIdx); + $this->collection[] = [$tagName, $regexp, $passthroughIdx]; + return $this->createTag($tagName, $template); + } + protected function addAttribute(Tag $tag, $attrName) + { + $isUrl = \false; + $exprs = []; + foreach ($this->captures as $key => $capture) + { + if ($capture['name'] !== $attrName) + continue; + $exprs[] = $capture['expr']; + if (isset($this->references['asUrl'][$key])) + $isUrl = \true; + } + $exprs = \array_unique($exprs); + $regexp = $this->delimiter . '^'; + $regexp .= (\count($exprs) === 1) ? $exprs[0] : '(?:' . \implode('|', $exprs) . ')'; + $regexp .= '$' . $this->delimiter . 'D' . $this->modifiers; + $attribute = $tag->attributes->add($attrName); + $filter = $this->configurator->attributeFilters['#regexp']; + $filter->setRegexp($regexp); + $attribute->filterChain[] = $filter; + if ($isUrl) + { + $filter = $this->configurator->attributeFilters['#url']; + $attribute->filterChain[] = $filter; + } + } + protected function convertTemplate($template, $passthroughIdx) + { + $template = TemplateHelper::replaceTokens( + $template, + $this->referencesRegexp, + function ($m, $node) use ($passthroughIdx) + { + $key = (int) \trim($m[0], '\\${}'); + if ($key === 0) + return ['expression', '.']; + if ($key === $passthroughIdx && $node instanceof DOMText) + return ['passthrough']; + if (isset($this->captures[$key]['name'])) + return ['expression', '@' . $this->captures[$key]['name']]; + return ['literal', '']; + } + ); + $template = TemplateHelper::replaceTokens( + $template, + '(\\\\+[0-9${\\\\])', + function ($m) + { + return ['literal', \stripslashes($m[0])]; + } + ); + return $template; + } + protected function createTag($tagName, $template) + { + $tag = new Tag; + foreach ($this->captures as $key => $capture) + { + if (!isset($capture['name'])) + continue; + $attrName = $capture['name']; + if (isset($tag->attributes[$attrName])) + continue; + $this->addAttribute($tag, $attrName); + } + $tag->template = $template; + $this->configurator->templateNormalizer->normalizeTag($tag); + $this->configurator->templateChecker->checkTag($tag); + return $this->configurator->tags->add($tagName, $tag); + } + protected function fixUnnamedCaptures($regexp) + { + $keys = []; + foreach ($this->references['anywhere'] as $key) + { + $capture = $this->captures[$key]; + if (!$key || isset($capture['name'])) + continue; + if (isset($this->references['asUrl'][$key]) || !isset($capture['passthrough'])) + $keys[] = $key; + } + \rsort($keys); + foreach ($keys as $key) + { + $name = '_' . $key; + $pos = $this->captures[$key]['pos']; + $regexp = \substr_replace($regexp, "?'" . $name . "'", 2 + $pos, 0); + $this->captures[$key]['name'] = $name; + } + return $regexp; + } + protected function getPassthroughCapture() + { + $passthrough = 0; + foreach ($this->references['inText'] as $key) + { + if (!$this->isCatchAll($this->captures[$key]['expr'])) + continue; + if ($passthrough) + { + $passthrough = 0; + break; + } + $passthrough = (int) $key; + } + return $passthrough; + } + protected function getRegexpInfo($regexp) + { + if (@\preg_match_all($regexp, '') === \false) + throw new InvalidArgumentException('Invalid regexp'); + return RegexpParser::parse($regexp); + } + protected function isCatchAll($expr) + { + return (bool) \preg_match('(^\\.[*+]\\??$)D', $expr); + } + protected function parseRegexp($regexp) + { + $this->captures = [['name' => \null, 'expr' => \null]]; + $regexpInfo = $this->getRegexpInfo($regexp); + $this->delimiter = $regexpInfo['delimiter']; + $this->modifiers = \str_replace('D', '', $regexpInfo['modifiers']); + foreach ($regexpInfo['tokens'] as $token) + { + if ($token['type'] !== 'capturingSubpatternStart') + continue; + $this->captures[] = [ + 'pos' => $token['pos'], + 'name' => (isset($token['name'])) ? $token['name'] : \null, + 'expr' => $token['content'] + ]; + } + } + protected function parseTemplate($template) + { + $this->references = [ + 'anywhere' => [], + 'asUrl' => [], + 'inText' => [] + ]; + \preg_match_all($this->referencesRegexp, $template, $matches); + foreach ($matches[0] as $match) + { + $key = \trim($match, '\\${}'); + $this->references['anywhere'][$key] = $key; + } + $dom = TemplateHelper::loadTemplate($template); + $xpath = new DOMXPath($dom); + foreach ($xpath->query('//text()') as $node) + { + \preg_match_all($this->referencesRegexp, $node->textContent, $matches); + foreach ($matches[0] as $match) + { + $key = \trim($match, '\\${}'); + $this->references['inText'][$key] = $key; + } + } + foreach (TemplateHelper::getURLNodes($dom) as $node) + if ($node instanceof DOMAttr + && \preg_match('(^(?:[$\\\\]\\d+|\\$\\{\\d+\\}))', \trim($node->value), $m)) + { + $key = \trim($m[0], '\\${}'); + $this->references['asUrl'][$key] = $key; + } + $this->removeUnknownReferences(); + } + protected function removeUnknownReferences() + { + foreach ($this->references as &$references) + $references = \array_intersect_key($references, $this->captures); + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Plugins/Preg/Parser.js b/vendor/s9e/text-formatter/src/Plugins/Preg/Parser.js new file mode 100644 index 0000000..6b576f7 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Plugins/Preg/Parser.js @@ -0,0 +1,46 @@ +config.generics.forEach(function(entry) +{ + var tagName = entry[0], + regexp = entry[1], + passthroughIdx = entry[2], + map = entry[3], + m; + + // Reset the regexp + regexp.lastIndex = 0; + + while (m = regexp.exec(text)) + { + var startTagPos = m['index'], + matchLen = m[0].length, + tag; + + if (HINT.PREG_HAS_PASSTHROUGH && passthroughIdx && m[passthroughIdx] !== '') + { + // Compute the position and length of the start tag, end tag, and the content in + // between. m.index gives us the position of the start tag but we don't know its length. + // We use indexOf() to locate the content part so that we know how long the start tag + // is. It is an imperfect solution but it should work well enough in most cases. + var contentPos = text.indexOf(m[passthroughIdx], startTagPos), + contentLen = m[passthroughIdx].length, + startTagLen = contentPos - startTagPos, + endTagPos = contentPos + contentLen, + endTagLen = matchLen - (startTagLen + contentLen); + + tag = addTagPair(tagName, startTagPos, startTagLen, endTagPos, endTagLen, -100); + } + else + { + tag = addSelfClosingTag(tagName, startTagPos, matchLen, -100); + } + + map.forEach(function(attrName, i) + { + // NOTE: subpatterns with no name have an empty entry to preserve the array indices + if (attrName && typeof m[i] !== 'undefined') + { + tag.setAttribute(attrName, m[i]); + } + }); + } +}); \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Plugins/Preg/Parser.php b/vendor/s9e/text-formatter/src/Plugins/Preg/Parser.php new file mode 100644 index 0000000..1078d3d --- /dev/null +++ b/vendor/s9e/text-formatter/src/Plugins/Preg/Parser.php @@ -0,0 +1,39 @@ +config['generics'] as $_8d36e519) + { + list($tagName, $regexp, $passthroughIdx, $map) = $_8d36e519; + \preg_match_all($regexp, $text, $matches, \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE); + foreach ($matches as $m) + { + $startTagPos = $m[0][1]; + $matchLen = \strlen($m[0][0]); + if ($passthroughIdx && isset($m[$passthroughIdx]) && $m[$passthroughIdx][0] !== '') + { + $contentPos = $m[$passthroughIdx][1]; + $contentLen = \strlen($m[$passthroughIdx][0]); + $startTagLen = $contentPos - $startTagPos; + $endTagPos = $contentPos + $contentLen; + $endTagLen = $matchLen - ($startTagLen + $contentLen); + $tag = $this->parser->addTagPair($tagName, $startTagPos, $startTagLen, $endTagPos, $endTagLen, -100); + } + else + $tag = $this->parser->addSelfClosingTag($tagName, $startTagPos, $matchLen, -100); + foreach ($map as $i => $attrName) + if ($attrName && isset($m[$i]) && $m[$i][0] !== '') + $tag->setAttribute($attrName, $m[$i][0]); + } + } + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Renderer.php b/vendor/s9e/text-formatter/src/Renderer.php new file mode 100644 index 0000000..76aca2d --- /dev/null +++ b/vendor/s9e/text-formatter/src/Renderer.php @@ -0,0 +1,75 @@ +checkUnsupported($xml); + $flags = (\LIBXML_VERSION >= 20700) ? \LIBXML_COMPACT | \LIBXML_PARSEHUGE : 0; + $useErrors = \libxml_use_internal_errors(\true); + $dom = new DOMDocument; + $success = $dom->loadXML($xml, $flags); + \libxml_use_internal_errors($useErrors); + if (!$success) + throw new InvalidArgumentException('Cannot load XML: ' . \libxml_get_last_error()->message); + return $dom; + } + public function render($xml) + { + if (\substr($xml, 0, 3) === '' && \substr($xml, -4) === '') + return $this->renderPlainText($xml); + else + return $this->renderRichText(\preg_replace('(<[eis]>[^<]*)', '', $xml)); + } + protected function renderPlainText($xml) + { + $html = \substr($xml, 3, -4); + $html = \str_replace('
      ', '
      ', $html); + $html = $this->decodeSMP($html); + return $html; + } + abstract protected function renderRichText($xml); + public function getParameter($paramName) + { + return (isset($this->params[$paramName])) ? $this->params[$paramName] : ''; + } + public function getParameters() + { + return $this->params; + } + public function setParameter($paramName, $paramValue) + { + $this->params[$paramName] = (string) $paramValue; + } + public function setParameters(array $params) + { + foreach ($params as $paramName => $paramValue) + $this->setParameter($paramName, $paramValue); + } + protected function checkUnsupported($xml) + { + if (\strpos($xml, 'nodeType === \XML_TEXT_NODE) + $this->out .= \htmlspecialchars($root->textContent,0); + else + { + $nodes = (isset($query)) ? $this->xpath->query($query, $root) : $root->childNodes; + foreach ($nodes as $node) + $this->renderNode($node); + } + } + protected function canQuickRender($xml) + { + return ($this->enableQuickRenderer && !\preg_match($this->quickRenderingTest, $xml) && \substr($xml, -4) === ''); + } + protected function checkTagPairContent($id, $xml) + { + if (\strpos($xml, '<' . $id, 1) !== \false) + throw new RuntimeException; + } + protected function getParamAsXPath($paramName) + { + return (isset($this->params[$paramName])) ? XPath::export($this->params[$paramName]) : "''"; + } + protected function getQuickTextContent($xml) + { + return \htmlspecialchars_decode(\strip_tags($xml)); + } + protected function hasNonNullValues(array $array) + { + foreach ($array as $v) + if (isset($v)) + return \true; + return \false; + } + protected function matchAttributes($xml) + { + if (\strpos($xml, '="') === \false) + return []; + \preg_match_all('(([^ =]++)="([^"]*))S', \substr($xml, 0, \strpos($xml, '>')), $m); + return \array_combine($m[1], $m[2]); + } + protected function renderQuick($xml) + { + $this->attributes = []; + $xml = $this->decodeSMP($xml); + $html = \preg_replace_callback( + $this->quickRegexp, + [$this, 'renderQuickCallback'], + \preg_replace( + '(<[eis]>[^<]*)', + '', + \substr($xml, 1 + \strpos($xml, '>'), -4) + ) + ); + return \str_replace('
      ', '
      ', $html); + } + protected function renderQuickCallback(array $m) + { + if (isset($m[3])) + return $this->renderQuickSelfClosingTag($m); + if (isset($m[2])) + $id = $m[2]; + else + { + $id = $m[1]; + $this->checkTagPairContent($id, $m[0]); + } + if (isset($this->static[$id])) + return $this->static[$id]; + if (isset($this->dynamic[$id])) + return \preg_replace($this->dynamic[$id][0], $this->dynamic[$id][1], $m[0], 1); + return $this->renderQuickTemplate($id, $m[0]); + } + protected function renderQuickSelfClosingTag(array $m) + { + unset($m[3]); + $m[0] = \substr($m[0], 0, -2) . '>'; + $html = $this->renderQuickCallback($m); + $m[0] = ''; + $m[2] = '/' . $m[2]; + $html .= $this->renderQuickCallback($m); + return $html; + } + protected function renderQuickTemplate($id, $xml) + { + throw new RuntimeException('Not implemented'); + } + protected function renderRichText($xml) + { + try + { + if ($this->canQuickRender($xml)) + return $this->renderQuick($xml); + } + catch (RuntimeException $e) + { + } + $dom = $this->loadXML($xml); + $this->out = ''; + $this->xpath = new DOMXPath($dom); + $this->at($dom->documentElement); + $html = $this->out; + $this->reset(); + return $html; + } + protected function reset() + { + unset($this->attributes); + unset($this->out); + unset($this->xpath); + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Renderers/Unformatted.php b/vendor/s9e/text-formatter/src/Renderers/Unformatted.php new file mode 100644 index 0000000..8d0915e --- /dev/null +++ b/vendor/s9e/text-formatter/src/Renderers/Unformatted.php @@ -0,0 +1,16 @@ +\n", \htmlspecialchars(\strip_tags($xml), \ENT_COMPAT, 'UTF-8', \false)); + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Renderers/XSLT.php b/vendor/s9e/text-formatter/src/Renderers/XSLT.php new file mode 100644 index 0000000..c8b7e4e --- /dev/null +++ b/vendor/s9e/text-formatter/src/Renderers/XSLT.php @@ -0,0 +1,92 @@ +stylesheet = $stylesheet; + \preg_match_all('#/>|>([^<]+))#', $stylesheet, $matches); + foreach ($matches[1] as $k => $paramName) + $this->params[$paramName] = (isset($matches[2][$k])) + ? \htmlspecialchars_decode($matches[2][$k]) + : ''; + } + public function __sleep() + { + $props = \get_object_vars($this); + unset($props['proc']); + if (empty($props['reloadParams'])) + unset($props['reloadParams']); + return \array_keys($props); + } + public function __wakeup() + { + if (!empty($this->reloadParams)) + { + $this->setParameters($this->params); + $this->reloadParams = \false; + } + } + public function setParameter($paramName, $paramValue) + { + if (\strpos($paramValue, '"') !== \false && \strpos($paramValue, "'") !== \false) + $paramValue = \str_replace('"', "\xEF\xBC\x82", $paramValue); + else + $paramValue = (string) $paramValue; + if (!isset($this->params[$paramName]) || $this->params[$paramName] !== $paramValue) + { + $this->load(); + $this->proc->setParameter('', $paramName, $paramValue); + $this->params[$paramName] = $paramValue; + $this->reloadParams = \true; + } + } + protected function renderRichText($xml) + { + $dom = $this->loadXML($xml); + $this->load(); + $output = (string) $this->proc->transformToXml($dom); + $output = \str_replace('', '', $output); + if (\substr($output, -1) === "\n") + $output = \substr($output, 0, -1); + if (\strpos($output, "='") !== \false) + $output = $this->normalizeAttributes($output); + return $output; + } + protected function load() + { + if (!isset($this->proc)) + { + $xsl = $this->loadXML($this->stylesheet); + $this->proc = new XSLTProcessor; + $this->proc->importStylesheet($xsl); + } + } + protected function normalizeAttribute(array $m) + { + if ($m[0][0] === '"') + return $m[0]; + return '"' . \str_replace('"', '"', \substr($m[0], 1, -1)) . '"'; + } + protected function normalizeAttributes($html) + { + return \preg_replace_callback('(<\\S++ [^>]++>)', [$this, 'normalizeElement'], $html); + } + protected function normalizeElement(array $m) + { + if (\strpos($m[0], "='") === \false) + return $m[0]; + return \preg_replace_callback('((?:"[^"]*"|\'[^\']*\'))S', [$this, 'normalizeAttribute'], $m[0]); + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Unparser.php b/vendor/s9e/text-formatter/src/Unparser.php new file mode 100644 index 0000000..162cd72 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Unparser.php @@ -0,0 +1,16 @@ +]*? ' . \preg_quote($attrName) . '="([^"]*+))'; + \preg_match_all($regexp, $xml, $matches); + foreach ($matches[1] as $value) + $values[] = \html_entity_decode($value, \ENT_QUOTES, 'UTF-8'); + } + return $values; + } + public static function encodeUnicodeSupplementaryCharacters($str) + { + return \preg_replace_callback( + '([\\xF0-\\xF4]...)S', + __CLASS__ . '::encodeUnicodeSupplementaryCharactersCallback', + $str + ); + } + public static function removeFormatting($xml) + { + $dom = self::loadXML($xml); + $xpath = new DOMXPath($dom); + foreach ($xpath->query('//e | //s') as $node) + $node->parentNode->removeChild($node); + return $dom->documentElement->textContent; + } + public static function removeTag($xml, $tagName, $nestingLevel = 0) + { + if (\strpos($xml, '<' . $tagName) === \false) + return $xml; + $dom = self::loadXML($xml); + $xpath = new DOMXPath($dom); + $query = '//' . $tagName . '[count(ancestor::' . $tagName . ') >= ' . $nestingLevel . ']'; + $nodes = $xpath->query($query); + foreach ($nodes as $node) + $node->parentNode->removeChild($node); + return self::saveXML($dom); + } + public static function replaceAttributes($xml, $tagName, callable $callback) + { + if (\strpos($xml, '<' . $tagName) === \false) + return $xml; + return \preg_replace_callback( + '((<' . \preg_quote($tagName) . ')(?=[ />])[^>]*?(/?>))', + function ($m) use ($callback) + { + return $m[1] . self::serializeAttributes($callback(self::parseAttributes($m[0]))) . $m[2]; + }, + $xml + ); + } + protected static function encodeUnicodeSupplementaryCharactersCallback(array $m) + { + $utf8 = $m[0]; + $cp = (\ord($utf8[0]) << 18) + (\ord($utf8[1]) << 12) + (\ord($utf8[2]) << 6) + \ord($utf8[3]) - 0x3C82080; + return '&#' . $cp . ';'; + } + protected static function loadXML($xml) + { + $flags = (\LIBXML_VERSION >= 20700) ? \LIBXML_COMPACT | \LIBXML_PARSEHUGE : 0; + $dom = new DOMDocument; + $dom->loadXML($xml, $flags); + return $dom; + } + protected static function parseAttributes($xml) + { + $attributes = []; + if (\strpos($xml, '="') !== \false) + { + \preg_match_all('(([^ =]++)="([^"]*))S', $xml, $matches); + foreach ($matches[1] as $i => $attrName) + $attributes[$attrName] = \html_entity_decode($matches[2][$i], \ENT_QUOTES, 'UTF-8'); + } + return $attributes; + } + protected static function saveXML(DOMDocument $dom) + { + return self::encodeUnicodeSupplementaryCharacters($dom->saveXML($dom->documentElement)); + } + protected static function serializeAttributes(array $attributes) + { + $xml = ''; + \ksort($attributes); + foreach ($attributes as $attrName => $attrValue) + $xml .= ' ' . \htmlspecialchars($attrName, \ENT_QUOTES) . '="' . \htmlspecialchars($attrValue, \ENT_COMPAT) . '"'; + $xml = \preg_replace('/\\r\\n?/', "\n", $xml); + $xml = \preg_replace('/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]+/S', '', $xml); + $xml = \str_replace("\n", ' ', $xml); + return self::encodeUnicodeSupplementaryCharacters($xml); + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Utils/Http.php b/vendor/s9e/text-formatter/src/Utils/Http.php new file mode 100644 index 0000000..d779d64 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Utils/Http.php @@ -0,0 +1,24 @@ +cacheDir = (isset($cacheDir)) ? $cacheDir : \sys_get_temp_dir(); + return $client; + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Utils/Http/Client.php b/vendor/s9e/text-formatter/src/Utils/Http/Client.php new file mode 100644 index 0000000..ec8c1fa --- /dev/null +++ b/vendor/s9e/text-formatter/src/Utils/Http/Client.php @@ -0,0 +1,15 @@ +client = $client; + $this->timeout = $client->timeout; + $this->sslVerifyPeer = $client->sslVerifyPeer; + } + public function get($url, $headers = []) + { + $filepath = $this->getCachedFilepath([$url, $headers]); + if (isset($filepath) && \file_exists(\preg_replace('(^compress\\.zlib://)', '', $filepath))) + return \file_get_contents($filepath); + $content = $this->getClient()->get($url, $headers); + if (isset($filepath) && $content !== \false) + \file_put_contents($filepath, $content); + return $content; + } + public function post($url, $headers = [], $body = '') + { + return $this->getClient()->post($url, $headers, $body); + } + protected function getCachedFilepath(array $vars) + { + if (!isset($this->cacheDir)) + return \null; + $filepath = $this->cacheDir . '/http.' . $this->getCacheKey($vars); + if (\extension_loaded('zlib')) + $filepath = 'compress.zlib://' . $filepath . '.gz'; + return $filepath; + } + + protected function getCacheKey(array $vars) + { + return \strtr(\base64_encode(\sha1(\serialize($vars), \true)), '/', '_'); + } + protected function getClient() + { + $this->client->timeout = $this->timeout; + $this->client->sslVerifyPeer = $this->sslVerifyPeer; + return $this->client; + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Utils/Http/Clients/Curl.php b/vendor/s9e/text-formatter/src/Utils/Http/Clients/Curl.php new file mode 100644 index 0000000..20d1c22 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Utils/Http/Clients/Curl.php @@ -0,0 +1,48 @@ +getHandle(); + \curl_setopt($handle, \CURLOPT_HTTPGET, \true); + \curl_setopt($handle, \CURLOPT_HTTPHEADER, $headers); + \curl_setopt($handle, \CURLOPT_URL, $url); + return \curl_exec($handle); + } + public function post($url, $headers = [], $body = '') + { + $headers[] = 'Content-Length: ' . \strlen($body); + $handle = $this->getHandle(); + \curl_setopt($handle, \CURLOPT_HTTPHEADER, $headers); + \curl_setopt($handle, \CURLOPT_POST, \true); + \curl_setopt($handle, \CURLOPT_POSTFIELDS, $body); + \curl_setopt($handle, \CURLOPT_URL, $url); + return \curl_exec($handle); + } + protected function getHandle() + { + if (!isset(self::$handle)) + self::$handle = $this->getNewHandle(); + \curl_setopt(self::$handle, \CURLOPT_SSL_VERIFYPEER, $this->sslVerifyPeer); + \curl_setopt(self::$handle, \CURLOPT_TIMEOUT, $this->timeout); + return self::$handle; + } + protected function getNewHandle() + { + $handle = \curl_init(); + \curl_setopt($handle, \CURLOPT_ENCODING, ''); + \curl_setopt($handle, \CURLOPT_FAILONERROR, \true); + \curl_setopt($handle, \CURLOPT_FOLLOWLOCATION, \true); + \curl_setopt($handle, \CURLOPT_RETURNTRANSFER, \true); + return $handle; + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Utils/Http/Clients/Native.php b/vendor/s9e/text-formatter/src/Utils/Http/Clients/Native.php new file mode 100644 index 0000000..36673a3 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Utils/Http/Clients/Native.php @@ -0,0 +1,56 @@ +gzipEnabled = \extension_loaded('zlib'); + } + public function get($url, $headers = []) + { + return $this->request('GET', $url, $headers); + } + public function post($url, $headers = [], $body = '') + { + return $this->request('POST', $url, $headers, $body); + } + protected function createContext($method, array $headers, $body) + { + $contextOptions = [ + 'ssl' => ['verify_peer' => $this->sslVerifyPeer], + 'http' => [ + 'method' => $method, + 'timeout' => $this->timeout, + 'header' => $this->generateHeaders($headers, $body), + 'content' => $body + ] + ]; + return \stream_context_create($contextOptions); + } + protected function decompress($content) + { + if ($this->gzipEnabled && \substr($content, 0, 2) === "\x1f\x8b") + return \gzdecode($content); + return $content; + } + protected function generateHeaders(array $headers, $body) + { + if ($this->gzipEnabled) + $headers[] = 'Accept-Encoding: gzip'; + $headers[] = 'Content-Length: ' . \strlen($body); + return $headers; + } + protected function request($method, $url, $headers, $body = '') + { + $response = @\file_get_contents($url, \false, $this->createContext($method, $headers, $body)); + return (\is_string($response)) ? $this->decompress($response) : $response; + } +} \ No newline at end of file diff --git a/vendor/s9e/text-formatter/src/Utils/XPath.php b/vendor/s9e/text-formatter/src/Utils/XPath.php new file mode 100644 index 0000000..ee41e42 --- /dev/null +++ b/vendor/s9e/text-formatter/src/Utils/XPath.php @@ -0,0 +1,43 @@ +.*?<\/[eis]>/g, ''), targetDoc), + lastUpdated = target; + + // Apply post-processing + if (HINT.postProcessing) + { + var nodes = resultFragment['querySelectorAll']('[data-s9e-livepreview-postprocess]'), + i = nodes.length; + while (--i >= 0) + { + /** @type {!string} */ + var code = nodes[i]['getAttribute']('data-s9e-livepreview-postprocess'); + if (!postProcessFunctions[code]) + { + postProcessFunctions[code] = new Function(code); + } + + postProcessFunctions[code]['call'](nodes[i]); + } + } + + /** + * Update the content of given element oldEl to match element newEl + * + * @param {!HTMLElement} oldEl + * @param {!HTMLElement} newEl + */ + function refreshElementContent(oldEl, newEl) + { + var oldNodes = oldEl.childNodes, + newNodes = newEl.childNodes, + oldCnt = oldNodes.length, + newCnt = newNodes.length, + oldNode, + newNode, + left = 0, + right = 0; + + // Skip the leftmost matching nodes + while (left < oldCnt && left < newCnt) + { + oldNode = oldNodes[left]; + newNode = newNodes[left]; + if (!refreshNode(oldNode, newNode)) + { + break; + } + + ++left; + } + + // Skip the rightmost matching nodes + var maxRight = Math.min(oldCnt - left, newCnt - left); + while (right < maxRight) + { + oldNode = oldNodes[oldCnt - (right + 1)]; + newNode = newNodes[newCnt - (right + 1)]; + if (!refreshNode(oldNode, newNode)) + { + break; + } + + ++right; + } + + // Remove the old dirty nodes in the middle of the tree + var i = oldCnt - right; + while (--i >= left) + { + oldEl.removeChild(oldNodes[i]); + lastUpdated = oldEl; + } + + // Test whether there are any nodes in the new tree between the matching nodes at the left + // and the matching nodes at the right + var rightBoundary = newCnt - right; + if (left >= rightBoundary) + { + return; + } + + // Clone the new nodes + var newNodesFragment = targetDoc.createDocumentFragment(); + i = left; + do + { + lastUpdated = newNodesFragment.appendChild(newNodes[i]); + } + while (i < --rightBoundary); + + // If we haven't skipped any nodes to the right, we can just append the fragment + if (!right) + { + oldEl.appendChild(newNodesFragment); + } + else + { + oldEl.insertBefore(newNodesFragment, oldEl.childNodes[left]); + } + } + + /** + * Update given node oldNode to make it match newNode + * + * @param {!HTMLElement} oldNode + * @param {!HTMLElement} newNode + * @return boolean Whether the node can be skipped + */ + function refreshNode(oldNode, newNode) + { + if (oldNode.nodeName !== newNode.nodeName + || oldNode.nodeType !== newNode.nodeType) + { + return false; + } + + // Node.TEXT_NODE || Node.COMMENT_NODE + if (oldNode.nodeType === 3 || oldNode.nodeType === 8) + { + if (oldNode.nodeValue !== newNode.nodeValue) + { + oldNode.nodeValue = newNode.nodeValue; + lastUpdated = oldNode; + } + + return true; + } + + if (!oldNode.isEqualNode || !oldNode.isEqualNode(newNode)) + { + syncElementAttributes(oldNode, newNode); + refreshElementContent(oldNode, newNode); + } + + return true; + } + + /** + * Make the set of attributes of given element oldEl match newEl's + * + * @param {!HTMLElement} oldEl + * @param {!HTMLElement} newEl + */ + function syncElementAttributes(oldEl, newEl) + { + var oldAttributes = oldEl['attributes'], + newAttributes = newEl['attributes'], + oldCnt = oldAttributes.length, + newCnt = newAttributes.length, + i = oldCnt, + ignoreAttrs = ' ' + oldAttributes['data-s9e-livepreview-ignore-attrs'] + ' '; + + while (--i >= 0) + { + var oldAttr = oldAttributes[i], + namespaceURI = oldAttr['namespaceURI'], + attrName = oldAttr['name']; + + if (HINT.ignoreAttrs && ignoreAttrs.indexOf(' ' + attrName + ' ') > -1) + { + continue; + } + if (!newEl.hasAttributeNS(namespaceURI, attrName)) + { + oldEl.removeAttributeNS(namespaceURI, attrName); + lastUpdated = oldEl; + } + } + + i = newCnt; + while (--i >= 0) + { + var newAttr = newAttributes[i], + namespaceURI = newAttr['namespaceURI'], + attrName = newAttr['name'], + attrValue = newAttr['value']; + + if (HINT.ignoreAttrs && ignoreAttrs.indexOf(' ' + attrName + ' ') > -1) + { + continue; + } + if (attrValue !== oldEl.getAttributeNS(namespaceURI, attrName)) + { + oldEl.setAttributeNS(namespaceURI, attrName, attrValue); + lastUpdated = oldEl; + } + } + } + + refreshElementContent(target, resultFragment); + + return lastUpdated; +} + +/** +* Set the value of a stylesheet parameter +* +* @param {!string} paramName Parameter name +* @param {!string} paramValue Parameter's value +*/ +function setParameter(paramName, paramValue) +{ + xslt.setParameter(paramName, paramValue); +} \ No newline at end of file diff --git a/vendor/symfony/config/ConfigCache.php b/vendor/symfony/config/ConfigCache.php new file mode 100644 index 0000000..016e014 --- /dev/null +++ b/vendor/symfony/config/ConfigCache.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\BCResourceInterfaceChecker; +use Symfony\Component\Config\Resource\SelfCheckingResourceChecker; + +/** + * ConfigCache caches arbitrary content in files on disk. + * + * When in debug mode, those metadata resources that implement + * \Symfony\Component\Config\Resource\SelfCheckingResourceInterface will + * be used to check cache freshness. + * + * During a transition period, also instances of + * \Symfony\Component\Config\Resource\ResourceInterface will be checked + * by means of the isFresh() method. This behaviour is deprecated since 2.8 + * and will be removed in 3.0. + * + * @author Fabien Potencier + * @author Matthias Pigulla + */ +class ConfigCache extends ResourceCheckerConfigCache +{ + private $debug; + + /** + * @param string $file The absolute cache path + * @param bool $debug Whether debugging is enabled or not + */ + public function __construct($file, $debug) + { + parent::__construct($file, array( + new SelfCheckingResourceChecker(), + new BCResourceInterfaceChecker(), + )); + $this->debug = (bool) $debug; + } + + /** + * Gets the cache file path. + * + * @return string The cache file path + * + * @deprecated since 2.7, to be removed in 3.0. Use getPath() instead. + */ + public function __toString() + { + @trigger_error('ConfigCache::__toString() is deprecated since Symfony 2.7 and will be removed in 3.0. Use the getPath() method instead.', E_USER_DEPRECATED); + + return $this->getPath(); + } + + /** + * Checks if the cache is still fresh. + * + * This implementation always returns true when debug is off and the + * cache file exists. + * + * @return bool true if the cache is fresh, false otherwise + */ + public function isFresh() + { + if (!$this->debug && is_file($this->getPath())) { + return true; + } + + return parent::isFresh(); + } +} diff --git a/vendor/symfony/config/ConfigCacheFactory.php b/vendor/symfony/config/ConfigCacheFactory.php new file mode 100644 index 0000000..7903cca --- /dev/null +++ b/vendor/symfony/config/ConfigCacheFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * Basic implementation of ConfigCacheFactoryInterface that + * creates an instance of the default ConfigCache. + * + * This factory and/or cache do not support cache validation + * by means of ResourceChecker instances (that is, service-based). + * + * @author Matthias Pigulla + */ +class ConfigCacheFactory implements ConfigCacheFactoryInterface +{ + private $debug; + + /** + * @param bool $debug The debug flag to pass to ConfigCache + */ + public function __construct($debug) + { + $this->debug = $debug; + } + + /** + * {@inheritdoc} + */ + public function cache($file, $callback) + { + if (!\is_callable($callback)) { + throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', \gettype($callback))); + } + + $cache = new ConfigCache($file, $this->debug); + if (!$cache->isFresh()) { + \call_user_func($callback, $cache); + } + + return $cache; + } +} diff --git a/vendor/symfony/config/ConfigCacheFactoryInterface.php b/vendor/symfony/config/ConfigCacheFactoryInterface.php new file mode 100644 index 0000000..8e80142 --- /dev/null +++ b/vendor/symfony/config/ConfigCacheFactoryInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * Interface for a ConfigCache factory. This factory creates + * an instance of ConfigCacheInterface and initializes the + * cache if necessary. + * + * @author Matthias Pigulla + */ +interface ConfigCacheFactoryInterface +{ + /** + * Creates a cache instance and (re-)initializes it if necessary. + * + * @param string $file The absolute cache file path + * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback + * + * @return ConfigCacheInterface The cache instance + */ + public function cache($file, $callable); +} diff --git a/vendor/symfony/config/ConfigCacheInterface.php b/vendor/symfony/config/ConfigCacheInterface.php new file mode 100644 index 0000000..7c47ad7 --- /dev/null +++ b/vendor/symfony/config/ConfigCacheInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Interface for ConfigCache. + * + * @author Matthias Pigulla + */ +interface ConfigCacheInterface +{ + /** + * Gets the cache file path. + * + * @return string The cache file path + */ + public function getPath(); + + /** + * Checks if the cache is still fresh. + * + * This check should take the metadata passed to the write() method into consideration. + * + * @return bool Whether the cache is still fresh + */ + public function isFresh(); + + /** + * Writes the given content into the cache file. Metadata will be stored + * independently and can be used to check cache freshness at a later time. + * + * @param string $content The content to write into the cache + * @param ResourceInterface[]|null $metadata An array of ResourceInterface instances + * + * @throws \RuntimeException When the cache file cannot be written + */ + public function write($content, array $metadata = null); +} diff --git a/vendor/symfony/config/Definition/ArrayNode.php b/vendor/symfony/config/Definition/ArrayNode.php new file mode 100644 index 0000000..86eacae --- /dev/null +++ b/vendor/symfony/config/Definition/ArrayNode.php @@ -0,0 +1,381 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * Represents an Array node in the config tree. + * + * @author Johannes M. Schmitt + */ +class ArrayNode extends BaseNode implements PrototypeNodeInterface +{ + protected $xmlRemappings = array(); + protected $children = array(); + protected $allowFalse = false; + protected $allowNewKeys = true; + protected $addIfNotSet = false; + protected $performDeepMerging = true; + protected $ignoreExtraKeys = false; + protected $removeExtraKeys = true; + protected $normalizeKeys = true; + + public function setNormalizeKeys($normalizeKeys) + { + $this->normalizeKeys = (bool) $normalizeKeys; + } + + /** + * Normalizes keys between the different configuration formats. + * + * Namely, you mostly have foo_bar in YAML while you have foo-bar in XML. + * After running this method, all keys are normalized to foo_bar. + * + * If you have a mixed key like foo-bar_moo, it will not be altered. + * The key will also not be altered if the target key already exists. + * + * @param mixed $value + * + * @return array The value with normalized keys + */ + protected function preNormalize($value) + { + if (!$this->normalizeKeys || !\is_array($value)) { + return $value; + } + + $normalized = array(); + + foreach ($value as $k => $v) { + if (false !== strpos($k, '-') && false === strpos($k, '_') && !array_key_exists($normalizedKey = str_replace('-', '_', $k), $value)) { + $normalized[$normalizedKey] = $v; + } else { + $normalized[$k] = $v; + } + } + + return $normalized; + } + + /** + * Retrieves the children of this node. + * + * @return array The children + */ + public function getChildren() + { + return $this->children; + } + + /** + * Sets the xml remappings that should be performed. + * + * @param array $remappings An array of the form array(array(string, string)) + */ + public function setXmlRemappings(array $remappings) + { + $this->xmlRemappings = $remappings; + } + + /** + * Gets the xml remappings that should be performed. + * + * @return array an array of the form array(array(string, string)) + */ + public function getXmlRemappings() + { + return $this->xmlRemappings; + } + + /** + * Sets whether to add default values for this array if it has not been + * defined in any of the configuration files. + * + * @param bool $boolean + */ + public function setAddIfNotSet($boolean) + { + $this->addIfNotSet = (bool) $boolean; + } + + /** + * Sets whether false is allowed as value indicating that the array should be unset. + * + * @param bool $allow + */ + public function setAllowFalse($allow) + { + $this->allowFalse = (bool) $allow; + } + + /** + * Sets whether new keys can be defined in subsequent configurations. + * + * @param bool $allow + */ + public function setAllowNewKeys($allow) + { + $this->allowNewKeys = (bool) $allow; + } + + /** + * Sets if deep merging should occur. + * + * @param bool $boolean + */ + public function setPerformDeepMerging($boolean) + { + $this->performDeepMerging = (bool) $boolean; + } + + /** + * Whether extra keys should just be ignore without an exception. + * + * @param bool $boolean To allow extra keys + * @param bool $remove To remove extra keys + */ + public function setIgnoreExtraKeys($boolean, $remove = true) + { + $this->ignoreExtraKeys = (bool) $boolean; + $this->removeExtraKeys = $this->ignoreExtraKeys && $remove; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function hasDefaultValue() + { + return $this->addIfNotSet; + } + + /** + * {@inheritdoc} + */ + public function getDefaultValue() + { + if (!$this->hasDefaultValue()) { + throw new \RuntimeException(sprintf('The node at path "%s" has no default value.', $this->getPath())); + } + + $defaults = array(); + foreach ($this->children as $name => $child) { + if ($child->hasDefaultValue()) { + $defaults[$name] = $child->getDefaultValue(); + } + } + + return $defaults; + } + + /** + * Adds a child node. + * + * @throws \InvalidArgumentException when the child node has no name + * @throws \InvalidArgumentException when the child node's name is not unique + */ + public function addChild(NodeInterface $node) + { + $name = $node->getName(); + if (!\strlen($name)) { + throw new \InvalidArgumentException('Child nodes must be named.'); + } + if (isset($this->children[$name])) { + throw new \InvalidArgumentException(sprintf('A child node named "%s" already exists.', $name)); + } + + $this->children[$name] = $node; + } + + /** + * Finalizes the value of this node. + * + * @param mixed $value + * + * @return mixed The finalised value + * + * @throws UnsetKeyException + * @throws InvalidConfigurationException if the node doesn't have enough children + */ + protected function finalizeValue($value) + { + if (false === $value) { + throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value))); + } + + foreach ($this->children as $name => $child) { + if (!array_key_exists($name, $value)) { + if ($child->isRequired()) { + $ex = new InvalidConfigurationException(sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } + + if ($child->hasDefaultValue()) { + $value[$name] = $child->getDefaultValue(); + } + + continue; + } + + try { + $value[$name] = $child->finalize($value[$name]); + } catch (UnsetKeyException $e) { + unset($value[$name]); + } + } + + return $value; + } + + /** + * Validates the type of the value. + * + * @param mixed $value + * + * @throws InvalidTypeException + */ + protected function validateType($value) + { + if (!\is_array($value) && (!$this->allowFalse || false !== $value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected array, but got %s', $this->getPath(), \gettype($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + /** + * Normalizes the value. + * + * @param mixed $value The value to normalize + * + * @return mixed The normalized value + * + * @throws InvalidConfigurationException + */ + protected function normalizeValue($value) + { + if (false === $value) { + return $value; + } + + $value = $this->remapXml($value); + + $normalized = array(); + foreach ($value as $name => $val) { + if (isset($this->children[$name])) { + try { + $normalized[$name] = $this->children[$name]->normalize($val); + } catch (UnsetKeyException $e) { + } + unset($value[$name]); + } elseif (!$this->removeExtraKeys) { + $normalized[$name] = $val; + } + } + + // if extra fields are present, throw exception + if (\count($value) && !$this->ignoreExtraKeys) { + $ex = new InvalidConfigurationException(sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $normalized; + } + + /** + * Remaps multiple singular values to a single plural value. + * + * @param array $value The source values + * + * @return array The remapped values + */ + protected function remapXml($value) + { + foreach ($this->xmlRemappings as $transformation) { + list($singular, $plural) = $transformation; + + if (!isset($value[$singular])) { + continue; + } + + $value[$plural] = Processor::normalizeConfig($value, $singular, $plural); + unset($value[$singular]); + } + + return $value; + } + + /** + * Merges values together. + * + * @param mixed $leftSide The left side to merge + * @param mixed $rightSide The right side to merge + * + * @return mixed The merged values + * + * @throws InvalidConfigurationException + * @throws \RuntimeException + */ + protected function mergeValues($leftSide, $rightSide) + { + if (false === $rightSide) { + // if this is still false after the last config has been merged the + // finalization pass will take care of removing this key entirely + return false; + } + + if (false === $leftSide || !$this->performDeepMerging) { + return $rightSide; + } + + foreach ($rightSide as $k => $v) { + // no conflict + if (!array_key_exists($k, $leftSide)) { + if (!$this->allowNewKeys) { + $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } + + $leftSide[$k] = $v; + continue; + } + + if (!isset($this->children[$k])) { + throw new \RuntimeException('merge() expects a normalized config array.'); + } + + $leftSide[$k] = $this->children[$k]->merge($leftSide[$k], $v); + } + + return $leftSide; + } +} diff --git a/vendor/symfony/config/Definition/BaseNode.php b/vendor/symfony/config/Definition/BaseNode.php new file mode 100644 index 0000000..7ca956e --- /dev/null +++ b/vendor/symfony/config/Definition/BaseNode.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * The base node class. + * + * @author Johannes M. Schmitt + */ +abstract class BaseNode implements NodeInterface +{ + protected $name; + protected $parent; + protected $normalizationClosures = array(); + protected $finalValidationClosures = array(); + protected $allowOverwrite = true; + protected $required = false; + protected $equivalentValues = array(); + protected $attributes = array(); + + /** + * @param string|null $name The name of the node + * @param NodeInterface|null $parent The parent of this node + * + * @throws \InvalidArgumentException if the name contains a period + */ + public function __construct($name, NodeInterface $parent = null) + { + if (false !== strpos($name = (string) $name, '.')) { + throw new \InvalidArgumentException('The name must not contain ".".'); + } + + $this->name = $name; + $this->parent = $parent; + } + + public function setAttribute($key, $value) + { + $this->attributes[$key] = $value; + } + + public function getAttribute($key, $default = null) + { + return isset($this->attributes[$key]) ? $this->attributes[$key] : $default; + } + + public function hasAttribute($key) + { + return isset($this->attributes[$key]); + } + + public function getAttributes() + { + return $this->attributes; + } + + public function setAttributes(array $attributes) + { + $this->attributes = $attributes; + } + + public function removeAttribute($key) + { + unset($this->attributes[$key]); + } + + /** + * Sets an info message. + * + * @param string $info + */ + public function setInfo($info) + { + $this->setAttribute('info', $info); + } + + /** + * Returns info message. + * + * @return string The info text + */ + public function getInfo() + { + return $this->getAttribute('info'); + } + + /** + * Sets the example configuration for this node. + * + * @param string|array $example + */ + public function setExample($example) + { + $this->setAttribute('example', $example); + } + + /** + * Retrieves the example configuration for this node. + * + * @return string|array The example + */ + public function getExample() + { + return $this->getAttribute('example'); + } + + /** + * Adds an equivalent value. + * + * @param mixed $originalValue + * @param mixed $equivalentValue + */ + public function addEquivalentValue($originalValue, $equivalentValue) + { + $this->equivalentValues[] = array($originalValue, $equivalentValue); + } + + /** + * Set this node as required. + * + * @param bool $boolean Required node + */ + public function setRequired($boolean) + { + $this->required = (bool) $boolean; + } + + /** + * Sets if this node can be overridden. + * + * @param bool $allow + */ + public function setAllowOverwrite($allow) + { + $this->allowOverwrite = (bool) $allow; + } + + /** + * Sets the closures used for normalization. + * + * @param \Closure[] $closures An array of Closures used for normalization + */ + public function setNormalizationClosures(array $closures) + { + $this->normalizationClosures = $closures; + } + + /** + * Sets the closures used for final validation. + * + * @param \Closure[] $closures An array of Closures used for final validation + */ + public function setFinalValidationClosures(array $closures) + { + $this->finalValidationClosures = $closures; + } + + /** + * {@inheritdoc} + */ + public function isRequired() + { + return $this->required; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + $path = $this->name; + + if (null !== $this->parent) { + $path = $this->parent->getPath().'.'.$path; + } + + return $path; + } + + /** + * {@inheritdoc} + */ + final public function merge($leftSide, $rightSide) + { + if (!$this->allowOverwrite) { + throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath())); + } + + $this->validateType($leftSide); + $this->validateType($rightSide); + + return $this->mergeValues($leftSide, $rightSide); + } + + /** + * {@inheritdoc} + */ + final public function normalize($value) + { + $value = $this->preNormalize($value); + + // run custom normalization closures + foreach ($this->normalizationClosures as $closure) { + $value = $closure($value); + } + + // replace value with their equivalent + foreach ($this->equivalentValues as $data) { + if ($data[0] === $value) { + $value = $data[1]; + } + } + + // validate type + $this->validateType($value); + + // normalize value + return $this->normalizeValue($value); + } + + /** + * Normalizes the value before any other normalization is applied. + * + * @param $value + * + * @return The normalized array value + */ + protected function preNormalize($value) + { + return $value; + } + + /** + * Returns parent node for this node. + * + * @return NodeInterface|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * {@inheritdoc} + */ + final public function finalize($value) + { + $this->validateType($value); + + $value = $this->finalizeValue($value); + + // Perform validation on the final value if a closure has been set. + // The closure is also allowed to return another value. + foreach ($this->finalValidationClosures as $closure) { + try { + $value = $closure($value); + } catch (Exception $e) { + throw $e; + } catch (\Exception $e) { + throw new InvalidConfigurationException(sprintf('Invalid configuration for path "%s": %s', $this->getPath(), $e->getMessage()), $e->getCode(), $e); + } + } + + return $value; + } + + /** + * Validates the type of a Node. + * + * @param mixed $value The value to validate + * + * @throws InvalidTypeException when the value is invalid + */ + abstract protected function validateType($value); + + /** + * Normalizes the value. + * + * @param mixed $value The value to normalize + * + * @return mixed The normalized value + */ + abstract protected function normalizeValue($value); + + /** + * Merges two values together. + * + * @param mixed $leftSide + * @param mixed $rightSide + * + * @return mixed The merged value + */ + abstract protected function mergeValues($leftSide, $rightSide); + + /** + * Finalizes a value. + * + * @param mixed $value The value to finalize + * + * @return mixed The finalized value + */ + abstract protected function finalizeValue($value); +} diff --git a/vendor/symfony/config/Definition/BooleanNode.php b/vendor/symfony/config/Definition/BooleanNode.php new file mode 100644 index 0000000..85f467b --- /dev/null +++ b/vendor/symfony/config/Definition/BooleanNode.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a Boolean value in the config tree. + * + * @author Johannes M. Schmitt + */ +class BooleanNode extends ScalarNode +{ + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + if (!\is_bool($value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected boolean, but got %s.', $this->getPath(), \gettype($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + /** + * {@inheritdoc} + */ + protected function isValueEmpty($value) + { + // a boolean value cannot be empty + return false; + } +} diff --git a/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php new file mode 100644 index 0000000..31e918a --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php @@ -0,0 +1,457 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; +use Symfony\Component\Config\Definition\PrototypedArrayNode; + +/** + * This class provides a fluent interface for defining an array node. + * + * @author Johannes M. Schmitt + */ +class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinitionInterface +{ + protected $performDeepMerging = true; + protected $ignoreExtraKeys = false; + protected $removeExtraKeys = true; + protected $children = array(); + protected $prototype; + protected $atLeastOne = false; + protected $allowNewKeys = true; + protected $key; + protected $removeKeyItem; + protected $addDefaults = false; + protected $addDefaultChildren = false; + protected $nodeBuilder; + protected $normalizeKeys = true; + + /** + * {@inheritdoc} + */ + public function __construct($name, NodeParentInterface $parent = null) + { + parent::__construct($name, $parent); + + $this->nullEquivalent = array(); + $this->trueEquivalent = array(); + } + + /** + * {@inheritdoc} + */ + public function setBuilder(NodeBuilder $builder) + { + $this->nodeBuilder = $builder; + } + + /** + * {@inheritdoc} + */ + public function children() + { + return $this->getNodeBuilder(); + } + + /** + * Sets a prototype for child nodes. + * + * @param string $type The type of node + * + * @return NodeDefinition + */ + public function prototype($type) + { + return $this->prototype = $this->getNodeBuilder()->node(null, $type)->setParent($this); + } + + /** + * Adds the default value if the node is not set in the configuration. + * + * This method is applicable to concrete nodes only (not to prototype nodes). + * If this function has been called and the node is not set during the finalization + * phase, it's default value will be derived from its children default values. + * + * @return $this + */ + public function addDefaultsIfNotSet() + { + $this->addDefaults = true; + + return $this; + } + + /** + * Adds children with a default value when none are defined. + * + * This method is applicable to prototype nodes only. + * + * @param int|string|array|null $children The number of children|The child name|The children names to be added + * + * @return $this + */ + public function addDefaultChildrenIfNoneSet($children = null) + { + $this->addDefaultChildren = $children; + + return $this; + } + + /** + * Requires the node to have at least one element. + * + * This method is applicable to prototype nodes only. + * + * @return $this + */ + public function requiresAtLeastOneElement() + { + $this->atLeastOne = true; + + return $this; + } + + /** + * Disallows adding news keys in a subsequent configuration. + * + * If used all keys have to be defined in the same configuration file. + * + * @return $this + */ + public function disallowNewKeysInSubsequentConfigs() + { + $this->allowNewKeys = false; + + return $this; + } + + /** + * Sets a normalization rule for XML configurations. + * + * @param string $singular The key to remap + * @param string $plural The plural of the key for irregular plurals + * + * @return $this + */ + public function fixXmlConfig($singular, $plural = null) + { + $this->normalization()->remap($singular, $plural); + + return $this; + } + + /** + * Sets the attribute which value is to be used as key. + * + * This is useful when you have an indexed array that should be an + * associative array. You can select an item from within the array + * to be the key of the particular item. For example, if "id" is the + * "key", then: + * + * array( + * array('id' => 'my_name', 'foo' => 'bar'), + * ); + * + * becomes + * + * array( + * 'my_name' => array('foo' => 'bar'), + * ); + * + * If you'd like "'id' => 'my_name'" to still be present in the resulting + * array, then you can set the second argument of this method to false. + * + * This method is applicable to prototype nodes only. + * + * @param string $name The name of the key + * @param bool $removeKeyItem Whether or not the key item should be removed + * + * @return $this + */ + public function useAttributeAsKey($name, $removeKeyItem = true) + { + $this->key = $name; + $this->removeKeyItem = $removeKeyItem; + + return $this; + } + + /** + * Sets whether the node can be unset. + * + * @param bool $allow + * + * @return $this + */ + public function canBeUnset($allow = true) + { + $this->merge()->allowUnset($allow); + + return $this; + } + + /** + * Adds an "enabled" boolean to enable the current section. + * + * By default, the section is disabled. If any configuration is specified then + * the node will be automatically enabled: + * + * enableableArrayNode: {enabled: true, ...} # The config is enabled & default values get overridden + * enableableArrayNode: ~ # The config is enabled & use the default values + * enableableArrayNode: true # The config is enabled & use the default values + * enableableArrayNode: {other: value, ...} # The config is enabled & default values get overridden + * enableableArrayNode: {enabled: false, ...} # The config is disabled + * enableableArrayNode: false # The config is disabled + * + * @return $this + */ + public function canBeEnabled() + { + $this + ->addDefaultsIfNotSet() + ->treatFalseLike(array('enabled' => false)) + ->treatTrueLike(array('enabled' => true)) + ->treatNullLike(array('enabled' => true)) + ->beforeNormalization() + ->ifArray() + ->then(function ($v) { + $v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true; + + return $v; + }) + ->end() + ->children() + ->booleanNode('enabled') + ->defaultFalse() + ; + + return $this; + } + + /** + * Adds an "enabled" boolean to enable the current section. + * + * By default, the section is enabled. + * + * @return $this + */ + public function canBeDisabled() + { + $this + ->addDefaultsIfNotSet() + ->treatFalseLike(array('enabled' => false)) + ->treatTrueLike(array('enabled' => true)) + ->treatNullLike(array('enabled' => true)) + ->children() + ->booleanNode('enabled') + ->defaultTrue() + ; + + return $this; + } + + /** + * Disables the deep merging of the node. + * + * @return $this + */ + public function performNoDeepMerging() + { + $this->performDeepMerging = false; + + return $this; + } + + /** + * Allows extra config keys to be specified under an array without + * throwing an exception. + * + * Those config values are simply ignored and removed from the + * resulting array. This should be used only in special cases where + * you want to send an entire configuration array through a special + * tree that processes only part of the array. + * + * @param bool $remove Whether to remove the extra keys + * + * @return $this + */ + public function ignoreExtraKeys($remove = true) + { + $this->ignoreExtraKeys = true; + $this->removeExtraKeys = $remove; + + return $this; + } + + /** + * Sets key normalization. + * + * @param bool $bool Whether to enable key normalization + * + * @return $this + */ + public function normalizeKeys($bool) + { + $this->normalizeKeys = (bool) $bool; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function append(NodeDefinition $node) + { + $this->children[$node->name] = $node->setParent($this); + + return $this; + } + + /** + * Returns a node builder to be used to add children and prototype. + * + * @return NodeBuilder The node builder + */ + protected function getNodeBuilder() + { + if (null === $this->nodeBuilder) { + $this->nodeBuilder = new NodeBuilder(); + } + + return $this->nodeBuilder->setParent($this); + } + + /** + * {@inheritdoc} + */ + protected function createNode() + { + if (null === $this->prototype) { + $node = new ArrayNode($this->name, $this->parent); + + $this->validateConcreteNode($node); + + $node->setAddIfNotSet($this->addDefaults); + + foreach ($this->children as $child) { + $child->parent = $node; + $node->addChild($child->getNode()); + } + } else { + $node = new PrototypedArrayNode($this->name, $this->parent); + + $this->validatePrototypeNode($node); + + if (null !== $this->key) { + $node->setKeyAttribute($this->key, $this->removeKeyItem); + } + + if (true === $this->atLeastOne) { + $node->setMinNumberOfElements(1); + } + + if ($this->default) { + $node->setDefaultValue($this->defaultValue); + } + + if (false !== $this->addDefaultChildren) { + $node->setAddChildrenIfNoneSet($this->addDefaultChildren); + if ($this->prototype instanceof static && null === $this->prototype->prototype) { + $this->prototype->addDefaultsIfNotSet(); + } + } + + $this->prototype->parent = $node; + $node->setPrototype($this->prototype->getNode()); + } + + $node->setAllowNewKeys($this->allowNewKeys); + $node->addEquivalentValue(null, $this->nullEquivalent); + $node->addEquivalentValue(true, $this->trueEquivalent); + $node->addEquivalentValue(false, $this->falseEquivalent); + $node->setPerformDeepMerging($this->performDeepMerging); + $node->setRequired($this->required); + $node->setIgnoreExtraKeys($this->ignoreExtraKeys, $this->removeExtraKeys); + $node->setNormalizeKeys($this->normalizeKeys); + + if (null !== $this->normalization) { + $node->setNormalizationClosures($this->normalization->before); + $node->setXmlRemappings($this->normalization->remappings); + } + + if (null !== $this->merge) { + $node->setAllowOverwrite($this->merge->allowOverwrite); + $node->setAllowFalse($this->merge->allowFalse); + } + + if (null !== $this->validation) { + $node->setFinalValidationClosures($this->validation->rules); + } + + return $node; + } + + /** + * Validate the configuration of a concrete node. + * + * @throws InvalidDefinitionException + */ + protected function validateConcreteNode(ArrayNode $node) + { + $path = $node->getPath(); + + if (null !== $this->key) { + throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path)); + } + + if (true === $this->atLeastOne) { + throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path)); + } + + if ($this->default) { + throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path)); + } + + if (false !== $this->addDefaultChildren) { + throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path)); + } + } + + /** + * Validate the configuration of a prototype node. + * + * @throws InvalidDefinitionException + */ + protected function validatePrototypeNode(PrototypedArrayNode $node) + { + $path = $node->getPath(); + + if ($this->addDefaults) { + throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path)); + } + + if (false !== $this->addDefaultChildren) { + if ($this->default) { + throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s"', $path)); + } + + if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) { + throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path)); + } + + if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) { + throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path)); + } + } + } +} diff --git a/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php new file mode 100644 index 0000000..a6292f7 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/BooleanNodeDefinition.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\BooleanNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class BooleanNodeDefinition extends ScalarNodeDefinition +{ + /** + * {@inheritdoc} + */ + public function __construct($name, NodeParentInterface $parent = null) + { + parent::__construct($name, $parent); + + $this->nullEquivalent = true; + } + + /** + * {@inheritdoc} + * + * @deprecated Deprecated since version 2.8, to be removed in 3.0. + */ + public function cannotBeEmpty() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + return parent::cannotBeEmpty(); + } + + /** + * Instantiate a Node. + * + * @return BooleanNode The node + */ + protected function instantiateNode() + { + return new BooleanNode($this->name, $this->parent); + } +} diff --git a/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php new file mode 100644 index 0000000..817906f --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/EnumNodeDefinition.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\EnumNode; + +/** + * Enum Node Definition. + * + * @author Johannes M. Schmitt + */ +class EnumNodeDefinition extends ScalarNodeDefinition +{ + private $values; + + /** + * @return $this + */ + public function values(array $values) + { + $values = array_unique($values); + + if (empty($values)) { + throw new \InvalidArgumentException('->values() must be called with at least one value.'); + } + + $this->values = $values; + + return $this; + } + + /** + * Instantiate a Node. + * + * @return EnumNode The node + * + * @throws \RuntimeException + */ + protected function instantiateNode() + { + if (null === $this->values) { + throw new \RuntimeException('You must call ->values() on enum nodes.'); + } + + return new EnumNode($this->name, $this->parent, $this->values); + } +} diff --git a/vendor/symfony/config/Definition/Builder/ExprBuilder.php b/vendor/symfony/config/Definition/Builder/ExprBuilder.php new file mode 100644 index 0000000..ddbe5b0 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ExprBuilder.php @@ -0,0 +1,223 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * This class builds an if expression. + * + * @author Johannes M. Schmitt + * @author Christophe Coevoet + */ +class ExprBuilder +{ + protected $node; + public $ifPart; + public $thenPart; + + public function __construct(NodeDefinition $node) + { + $this->node = $node; + } + + /** + * Marks the expression as being always used. + * + * @return $this + */ + public function always(\Closure $then = null) + { + $this->ifPart = function ($v) { return true; }; + + if (null !== $then) { + $this->thenPart = $then; + } + + return $this; + } + + /** + * Sets a closure to use as tests. + * + * The default one tests if the value is true. + * + * @return $this + */ + public function ifTrue(\Closure $closure = null) + { + if (null === $closure) { + $closure = function ($v) { return true === $v; }; + } + + $this->ifPart = $closure; + + return $this; + } + + /** + * Tests if the value is a string. + * + * @return $this + */ + public function ifString() + { + $this->ifPart = function ($v) { return \is_string($v); }; + + return $this; + } + + /** + * Tests if the value is null. + * + * @return $this + */ + public function ifNull() + { + $this->ifPart = function ($v) { return null === $v; }; + + return $this; + } + + /** + * Tests if the value is an array. + * + * @return $this + */ + public function ifArray() + { + $this->ifPart = function ($v) { return \is_array($v); }; + + return $this; + } + + /** + * Tests if the value is in an array. + * + * @return $this + */ + public function ifInArray(array $array) + { + $this->ifPart = function ($v) use ($array) { return \in_array($v, $array, true); }; + + return $this; + } + + /** + * Tests if the value is not in an array. + * + * @return $this + */ + public function ifNotInArray(array $array) + { + $this->ifPart = function ($v) use ($array) { return !\in_array($v, $array, true); }; + + return $this; + } + + /** + * Sets the closure to run if the test pass. + * + * @return $this + */ + public function then(\Closure $closure) + { + $this->thenPart = $closure; + + return $this; + } + + /** + * Sets a closure returning an empty array. + * + * @return $this + */ + public function thenEmptyArray() + { + $this->thenPart = function ($v) { return array(); }; + + return $this; + } + + /** + * Sets a closure marking the value as invalid at processing time. + * + * if you want to add the value of the node in your message just use a %s placeholder. + * + * @param string $message + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function thenInvalid($message) + { + $this->thenPart = function ($v) use ($message) { throw new \InvalidArgumentException(sprintf($message, json_encode($v))); }; + + return $this; + } + + /** + * Sets a closure unsetting this key of the array at processing time. + * + * @return $this + * + * @throws UnsetKeyException + */ + public function thenUnset() + { + $this->thenPart = function ($v) { throw new UnsetKeyException('Unsetting key'); }; + + return $this; + } + + /** + * Returns the related node. + * + * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition + * + * @throws \RuntimeException + */ + public function end() + { + if (null === $this->ifPart) { + throw new \RuntimeException('You must specify an if part.'); + } + if (null === $this->thenPart) { + throw new \RuntimeException('You must specify a then part.'); + } + + return $this->node; + } + + /** + * Builds the expressions. + * + * @param ExprBuilder[] $expressions An array of ExprBuilder instances to build + * + * @return array + */ + public static function buildExpressions(array $expressions) + { + foreach ($expressions as $k => $expr) { + if ($expr instanceof self) { + $if = $expr->ifPart; + $then = $expr->thenPart; + $expressions[$k] = function ($v) use ($if, $then) { + return $if($v) ? $then($v) : $v; + }; + } + } + + return $expressions; + } +} diff --git a/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php new file mode 100644 index 0000000..c0bed46 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/FloatNodeDefinition.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\FloatNode; + +/** + * This class provides a fluent interface for defining a float node. + * + * @author Jeanmonod David + */ +class FloatNodeDefinition extends NumericNodeDefinition +{ + /** + * Instantiates a Node. + * + * @return FloatNode The node + */ + protected function instantiateNode() + { + return new FloatNode($this->name, $this->parent, $this->min, $this->max); + } +} diff --git a/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php new file mode 100644 index 0000000..f6c3c14 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/IntegerNodeDefinition.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\IntegerNode; + +/** + * This class provides a fluent interface for defining an integer node. + * + * @author Jeanmonod David + */ +class IntegerNodeDefinition extends NumericNodeDefinition +{ + /** + * Instantiates a Node. + * + * @return IntegerNode The node + */ + protected function instantiateNode() + { + return new IntegerNode($this->name, $this->parent, $this->min, $this->max); + } +} diff --git a/vendor/symfony/config/Definition/Builder/MergeBuilder.php b/vendor/symfony/config/Definition/Builder/MergeBuilder.php new file mode 100644 index 0000000..105e2d6 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/MergeBuilder.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * This class builds merge conditions. + * + * @author Johannes M. Schmitt + */ +class MergeBuilder +{ + protected $node; + public $allowFalse = false; + public $allowOverwrite = true; + + public function __construct(NodeDefinition $node) + { + $this->node = $node; + } + + /** + * Sets whether the node can be unset. + * + * @param bool $allow + * + * @return $this + */ + public function allowUnset($allow = true) + { + $this->allowFalse = $allow; + + return $this; + } + + /** + * Sets whether the node can be overwritten. + * + * @param bool $deny Whether the overwriting is forbidden or not + * + * @return $this + */ + public function denyOverwrite($deny = true) + { + $this->allowOverwrite = !$deny; + + return $this; + } + + /** + * Returns the related node. + * + * @return NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition + */ + public function end() + { + return $this->node; + } +} diff --git a/vendor/symfony/config/Definition/Builder/NodeBuilder.php b/vendor/symfony/config/Definition/Builder/NodeBuilder.php new file mode 100644 index 0000000..95863d6 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeBuilder.php @@ -0,0 +1,238 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * This class provides a fluent interface for building a node. + * + * @author Johannes M. Schmitt + */ +class NodeBuilder implements NodeParentInterface +{ + protected $parent; + protected $nodeMapping; + + public function __construct() + { + $this->nodeMapping = array( + 'variable' => __NAMESPACE__.'\\VariableNodeDefinition', + 'scalar' => __NAMESPACE__.'\\ScalarNodeDefinition', + 'boolean' => __NAMESPACE__.'\\BooleanNodeDefinition', + 'integer' => __NAMESPACE__.'\\IntegerNodeDefinition', + 'float' => __NAMESPACE__.'\\FloatNodeDefinition', + 'array' => __NAMESPACE__.'\\ArrayNodeDefinition', + 'enum' => __NAMESPACE__.'\\EnumNodeDefinition', + ); + } + + /** + * Set the parent node. + * + * @return $this + */ + public function setParent(ParentNodeDefinitionInterface $parent = null) + { + $this->parent = $parent; + + return $this; + } + + /** + * Creates a child array node. + * + * @param string $name The name of the node + * + * @return ArrayNodeDefinition The child node + */ + public function arrayNode($name) + { + return $this->node($name, 'array'); + } + + /** + * Creates a child scalar node. + * + * @param string $name The name of the node + * + * @return ScalarNodeDefinition The child node + */ + public function scalarNode($name) + { + return $this->node($name, 'scalar'); + } + + /** + * Creates a child Boolean node. + * + * @param string $name The name of the node + * + * @return BooleanNodeDefinition The child node + */ + public function booleanNode($name) + { + return $this->node($name, 'boolean'); + } + + /** + * Creates a child integer node. + * + * @param string $name The name of the node + * + * @return IntegerNodeDefinition The child node + */ + public function integerNode($name) + { + return $this->node($name, 'integer'); + } + + /** + * Creates a child float node. + * + * @param string $name The name of the node + * + * @return FloatNodeDefinition The child node + */ + public function floatNode($name) + { + return $this->node($name, 'float'); + } + + /** + * Creates a child EnumNode. + * + * @param string $name + * + * @return EnumNodeDefinition + */ + public function enumNode($name) + { + return $this->node($name, 'enum'); + } + + /** + * Creates a child variable node. + * + * @param string $name The name of the node + * + * @return VariableNodeDefinition The builder of the child node + */ + public function variableNode($name) + { + return $this->node($name, 'variable'); + } + + /** + * Returns the parent node. + * + * @return NodeDefinition&ParentNodeDefinitionInterface The parent node + */ + public function end() + { + return $this->parent; + } + + /** + * Creates a child node. + * + * @param string|null $name The name of the node + * @param string $type The type of the node + * + * @return NodeDefinition The child node + * + * @throws \RuntimeException When the node type is not registered + * @throws \RuntimeException When the node class is not found + */ + public function node($name, $type) + { + $class = $this->getNodeClass($type); + + $node = new $class($name); + + $this->append($node); + + return $node; + } + + /** + * Appends a node definition. + * + * Usage: + * + * $node = new ArrayNodeDefinition('name') + * ->children() + * ->scalarNode('foo')->end() + * ->scalarNode('baz')->end() + * ->append($this->getBarNodeDefinition()) + * ->end() + * ; + * + * @return $this + */ + public function append(NodeDefinition $node) + { + if ($node instanceof ParentNodeDefinitionInterface) { + $builder = clone $this; + $builder->setParent(null); + $node->setBuilder($builder); + } + + if (null !== $this->parent) { + $this->parent->append($node); + // Make this builder the node parent to allow for a fluid interface + $node->setParent($this); + } + + return $this; + } + + /** + * Adds or overrides a node Type. + * + * @param string $type The name of the type + * @param string $class The fully qualified name the node definition class + * + * @return $this + */ + public function setNodeClass($type, $class) + { + $this->nodeMapping[strtolower($type)] = $class; + + return $this; + } + + /** + * Returns the class name of the node definition. + * + * @param string $type The node type + * + * @return string The node definition class name + * + * @throws \RuntimeException When the node type is not registered + * @throws \RuntimeException When the node class is not found + */ + protected function getNodeClass($type) + { + $type = strtolower($type); + + if (!isset($this->nodeMapping[$type])) { + throw new \RuntimeException(sprintf('The node type "%s" is not registered.', $type)); + } + + $class = $this->nodeMapping[$type]; + + if (!class_exists($class)) { + throw new \RuntimeException(sprintf('The node class "%s" does not exist.', $class)); + } + + return $class; + } +} diff --git a/vendor/symfony/config/Definition/Builder/NodeDefinition.php b/vendor/symfony/config/Definition/Builder/NodeDefinition.php new file mode 100644 index 0000000..a14161f --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeDefinition.php @@ -0,0 +1,335 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\Exception\InvalidDefinitionException; +use Symfony\Component\Config\Definition\NodeInterface; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +abstract class NodeDefinition implements NodeParentInterface +{ + protected $name; + protected $normalization; + protected $validation; + protected $defaultValue; + protected $default = false; + protected $required = false; + protected $merge; + protected $allowEmptyValue = true; + protected $nullEquivalent; + protected $trueEquivalent = true; + protected $falseEquivalent = false; + protected $parent; + protected $attributes = array(); + + /** + * @param string|null $name The name of the node + * @param NodeParentInterface|null $parent The parent + */ + public function __construct($name, NodeParentInterface $parent = null) + { + $this->parent = $parent; + $this->name = $name; + } + + /** + * Sets the parent node. + * + * @return $this + */ + public function setParent(NodeParentInterface $parent) + { + $this->parent = $parent; + + return $this; + } + + /** + * Sets info message. + * + * @param string $info The info text + * + * @return $this + */ + public function info($info) + { + return $this->attribute('info', $info); + } + + /** + * Sets example configuration. + * + * @param string|array $example + * + * @return $this + */ + public function example($example) + { + return $this->attribute('example', $example); + } + + /** + * Sets an attribute on the node. + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function attribute($key, $value) + { + $this->attributes[$key] = $value; + + return $this; + } + + /** + * Returns the parent node. + * + * @return NodeParentInterface|NodeBuilder|NodeDefinition|ArrayNodeDefinition|VariableNodeDefinition|null The builder of the parent node + */ + public function end() + { + return $this->parent; + } + + /** + * Creates the node. + * + * @param bool $forceRootNode Whether to force this node as the root node + * + * @return NodeInterface + */ + public function getNode($forceRootNode = false) + { + if ($forceRootNode) { + $this->parent = null; + } + + if (null !== $this->normalization) { + $this->normalization->before = ExprBuilder::buildExpressions($this->normalization->before); + } + + if (null !== $this->validation) { + $this->validation->rules = ExprBuilder::buildExpressions($this->validation->rules); + } + + $node = $this->createNode(); + $node->setAttributes($this->attributes); + + return $node; + } + + /** + * Sets the default value. + * + * @param mixed $value The default value + * + * @return $this + */ + public function defaultValue($value) + { + $this->default = true; + $this->defaultValue = $value; + + return $this; + } + + /** + * Sets the node as required. + * + * @return $this + */ + public function isRequired() + { + $this->required = true; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains null. + * + * @param mixed $value + * + * @return $this + */ + public function treatNullLike($value) + { + $this->nullEquivalent = $value; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains true. + * + * @param mixed $value + * + * @return $this + */ + public function treatTrueLike($value) + { + $this->trueEquivalent = $value; + + return $this; + } + + /** + * Sets the equivalent value used when the node contains false. + * + * @param mixed $value + * + * @return $this + */ + public function treatFalseLike($value) + { + $this->falseEquivalent = $value; + + return $this; + } + + /** + * Sets null as the default value. + * + * @return $this + */ + public function defaultNull() + { + return $this->defaultValue(null); + } + + /** + * Sets true as the default value. + * + * @return $this + */ + public function defaultTrue() + { + return $this->defaultValue(true); + } + + /** + * Sets false as the default value. + * + * @return $this + */ + public function defaultFalse() + { + return $this->defaultValue(false); + } + + /** + * Sets an expression to run before the normalization. + * + * @return ExprBuilder + */ + public function beforeNormalization() + { + return $this->normalization()->before(); + } + + /** + * Denies the node value being empty. + * + * @return $this + */ + public function cannotBeEmpty() + { + $this->allowEmptyValue = false; + + return $this; + } + + /** + * Sets an expression to run for the validation. + * + * The expression receives the value of the node and must return it. It can + * modify it. + * An exception should be thrown when the node is not valid. + * + * @return ExprBuilder + */ + public function validate() + { + return $this->validation()->rule(); + } + + /** + * Sets whether the node can be overwritten. + * + * @param bool $deny Whether the overwriting is forbidden or not + * + * @return $this + */ + public function cannotBeOverwritten($deny = true) + { + $this->merge()->denyOverwrite($deny); + + return $this; + } + + /** + * Gets the builder for validation rules. + * + * @return ValidationBuilder + */ + protected function validation() + { + if (null === $this->validation) { + $this->validation = new ValidationBuilder($this); + } + + return $this->validation; + } + + /** + * Gets the builder for merging rules. + * + * @return MergeBuilder + */ + protected function merge() + { + if (null === $this->merge) { + $this->merge = new MergeBuilder($this); + } + + return $this->merge; + } + + /** + * Gets the builder for normalization rules. + * + * @return NormalizationBuilder + */ + protected function normalization() + { + if (null === $this->normalization) { + $this->normalization = new NormalizationBuilder($this); + } + + return $this->normalization; + } + + /** + * Instantiate and configure the node according to this definition. + * + * @return NodeInterface The node instance + * + * @throws InvalidDefinitionException When the definition is invalid + */ + abstract protected function createNode(); +} diff --git a/vendor/symfony/config/Definition/Builder/NodeParentInterface.php b/vendor/symfony/config/Definition/Builder/NodeParentInterface.php new file mode 100644 index 0000000..305e993 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NodeParentInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * An interface that must be implemented by all node parents. + * + * @author Victor Berchet + */ +interface NodeParentInterface +{ +} diff --git a/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php new file mode 100644 index 0000000..35e3048 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NormalizationBuilder.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * This class builds normalization conditions. + * + * @author Johannes M. Schmitt + */ +class NormalizationBuilder +{ + protected $node; + public $before = array(); + public $remappings = array(); + + public function __construct(NodeDefinition $node) + { + $this->node = $node; + } + + /** + * Registers a key to remap to its plural form. + * + * @param string $key The key to remap + * @param string $plural The plural of the key in case of irregular plural + * + * @return $this + */ + public function remap($key, $plural = null) + { + $this->remappings[] = array($key, null === $plural ? $key.'s' : $plural); + + return $this; + } + + /** + * Registers a closure to run before the normalization or an expression builder to build it if null is provided. + * + * @return ExprBuilder|$this + */ + public function before(\Closure $closure = null) + { + if (null !== $closure) { + $this->before[] = $closure; + + return $this; + } + + return $this->before[] = new ExprBuilder($this->node); + } +} diff --git a/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php new file mode 100644 index 0000000..40c0bfc --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/NumericNodeDefinition.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * Abstract class that contains common code of integer and float node definitions. + * + * @author David Jeanmonod + */ +abstract class NumericNodeDefinition extends ScalarNodeDefinition +{ + protected $min; + protected $max; + + /** + * Ensures that the value is smaller than the given reference. + * + * @param mixed $max + * + * @return $this + * + * @throws \InvalidArgumentException when the constraint is inconsistent + */ + public function max($max) + { + if (isset($this->min) && $this->min > $max) { + throw new \InvalidArgumentException(sprintf('You cannot define a max(%s) as you already have a min(%s)', $max, $this->min)); + } + $this->max = $max; + + return $this; + } + + /** + * Ensures that the value is bigger than the given reference. + * + * @param mixed $min + * + * @return $this + * + * @throws \InvalidArgumentException when the constraint is inconsistent + */ + public function min($min) + { + if (isset($this->max) && $this->max < $min) { + throw new \InvalidArgumentException(sprintf('You cannot define a min(%s) as you already have a max(%s)', $min, $this->max)); + } + $this->min = $min; + + return $this; + } + + /** + * {@inheritdoc} + * + * @deprecated Deprecated since version 2.8, to be removed in 3.0. + */ + public function cannotBeEmpty() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + return parent::cannotBeEmpty(); + } +} diff --git a/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php new file mode 100644 index 0000000..1bf2ad4 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ParentNodeDefinitionInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * An interface that must be implemented by nodes which can have children. + * + * @author Victor Berchet + */ +interface ParentNodeDefinitionInterface +{ + /** + * Returns a builder to add children nodes. + * + * @return NodeBuilder + */ + public function children(); + + /** + * Appends a node definition. + * + * Usage: + * + * $node = $parentNode + * ->children() + * ->scalarNode('foo')->end() + * ->scalarNode('baz')->end() + * ->append($this->getBarNodeDefinition()) + * ->end() + * ; + * + * @return $this + */ + public function append(NodeDefinition $node); + + /** + * Sets a custom children builder. + */ + public function setBuilder(NodeBuilder $builder); +} diff --git a/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php new file mode 100644 index 0000000..6170555 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ScalarNodeDefinition.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\ScalarNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class ScalarNodeDefinition extends VariableNodeDefinition +{ + /** + * Instantiate a Node. + * + * @return ScalarNode The node + */ + protected function instantiateNode() + { + return new ScalarNode($this->name, $this->parent); + } +} diff --git a/vendor/symfony/config/Definition/Builder/TreeBuilder.php b/vendor/symfony/config/Definition/Builder/TreeBuilder.php new file mode 100644 index 0000000..5d02848 --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/TreeBuilder.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\NodeInterface; + +/** + * This is the entry class for building a config tree. + * + * @author Johannes M. Schmitt + */ +class TreeBuilder implements NodeParentInterface +{ + protected $tree; + protected $root; + protected $builder; + + /** + * Creates the root node. + * + * @param string $name The name of the root node + * @param string $type The type of the root node + * @param NodeBuilder $builder A custom node builder instance + * + * @return ArrayNodeDefinition|NodeDefinition The root node (as an ArrayNodeDefinition when the type is 'array') + * + * @throws \RuntimeException When the node type is not supported + */ + public function root($name, $type = 'array', NodeBuilder $builder = null) + { + $builder = $builder ?: new NodeBuilder(); + + return $this->root = $builder->node($name, $type)->setParent($this); + } + + /** + * Builds the tree. + * + * @return NodeInterface + * + * @throws \RuntimeException + */ + public function buildTree() + { + if (null === $this->root) { + throw new \RuntimeException('The configuration tree has no root node.'); + } + if (null !== $this->tree) { + return $this->tree; + } + + return $this->tree = $this->root->getNode(true); + } +} diff --git a/vendor/symfony/config/Definition/Builder/ValidationBuilder.php b/vendor/symfony/config/Definition/Builder/ValidationBuilder.php new file mode 100644 index 0000000..bb2b9eb --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/ValidationBuilder.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +/** + * This class builds validation conditions. + * + * @author Christophe Coevoet + */ +class ValidationBuilder +{ + protected $node; + public $rules = array(); + + public function __construct(NodeDefinition $node) + { + $this->node = $node; + } + + /** + * Registers a closure to run as normalization or an expression builder to build it if null is provided. + * + * @return ExprBuilder|$this + */ + public function rule(\Closure $closure = null) + { + if (null !== $closure) { + $this->rules[] = $closure; + + return $this; + } + + return $this->rules[] = new ExprBuilder($this->node); + } +} diff --git a/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php new file mode 100644 index 0000000..a46b7ea --- /dev/null +++ b/vendor/symfony/config/Definition/Builder/VariableNodeDefinition.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Builder; + +use Symfony\Component\Config\Definition\VariableNode; + +/** + * This class provides a fluent interface for defining a node. + * + * @author Johannes M. Schmitt + */ +class VariableNodeDefinition extends NodeDefinition +{ + /** + * Instantiate a Node. + * + * @return VariableNode The node + */ + protected function instantiateNode() + { + return new VariableNode($this->name, $this->parent); + } + + /** + * {@inheritdoc} + */ + protected function createNode() + { + $node = $this->instantiateNode(); + + if (null !== $this->normalization) { + $node->setNormalizationClosures($this->normalization->before); + } + + if (null !== $this->merge) { + $node->setAllowOverwrite($this->merge->allowOverwrite); + } + + if (true === $this->default) { + $node->setDefaultValue($this->defaultValue); + } + + $node->setAllowEmptyValue($this->allowEmptyValue); + $node->addEquivalentValue(null, $this->nullEquivalent); + $node->addEquivalentValue(true, $this->trueEquivalent); + $node->addEquivalentValue(false, $this->falseEquivalent); + $node->setRequired($this->required); + + if (null !== $this->validation) { + $node->setFinalValidationClosures($this->validation->rules); + } + + return $node; + } +} diff --git a/vendor/symfony/config/Definition/ConfigurationInterface.php b/vendor/symfony/config/Definition/ConfigurationInterface.php new file mode 100644 index 0000000..d6456ed --- /dev/null +++ b/vendor/symfony/config/Definition/ConfigurationInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +/** + * Configuration interface. + * + * @author Victor Berchet + */ +interface ConfigurationInterface +{ + /** + * Generates the configuration tree builder. + * + * @return \Symfony\Component\Config\Definition\Builder\TreeBuilder The tree builder + */ + public function getConfigTreeBuilder(); +} diff --git a/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php new file mode 100644 index 0000000..0e29731 --- /dev/null +++ b/vendor/symfony/config/Definition/Dumper/XmlReferenceDumper.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Dumper; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\EnumNode; +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\PrototypedArrayNode; + +/** + * Dumps a XML reference configuration for the given configuration/node instance. + * + * @author Wouter J + */ +class XmlReferenceDumper +{ + private $reference; + + public function dump(ConfigurationInterface $configuration, $namespace = null) + { + return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree(), $namespace); + } + + public function dumpNode(NodeInterface $node, $namespace = null) + { + $this->reference = ''; + $this->writeNode($node, 0, true, $namespace); + $ref = $this->reference; + $this->reference = null; + + return $ref; + } + + /** + * @param NodeInterface $node + * @param int $depth + * @param bool $root If the node is the root node + * @param string $namespace The namespace of the node + */ + private function writeNode(NodeInterface $node, $depth = 0, $root = false, $namespace = null) + { + $rootName = ($root ? 'config' : $node->getName()); + $rootNamespace = ($namespace ?: ($root ? 'http://example.org/schema/dic/'.$node->getName() : null)); + + // xml remapping + if ($node->getParent()) { + $remapping = array_filter($node->getParent()->getXmlRemappings(), function ($mapping) use ($rootName) { + return $rootName === $mapping[1]; + }); + + if (\count($remapping)) { + list($singular) = current($remapping); + $rootName = $singular; + } + } + $rootName = str_replace('_', '-', $rootName); + + $rootAttributes = array(); + $rootAttributeComments = array(); + $rootChildren = array(); + $rootComments = array(); + + if ($node instanceof ArrayNode) { + $children = $node->getChildren(); + + // comments about the root node + if ($rootInfo = $node->getInfo()) { + $rootComments[] = $rootInfo; + } + + if ($rootNamespace) { + $rootComments[] = 'Namespace: '.$rootNamespace; + } + + // render prototyped nodes + if ($node instanceof PrototypedArrayNode) { + $prototype = $node->getPrototype(); + + $info = 'prototype'; + if (null !== $prototype->getInfo()) { + $info .= ': '.$prototype->getInfo(); + } + array_unshift($rootComments, $info); + + if ($key = $node->getKeyAttribute()) { + $rootAttributes[$key] = str_replace('-', ' ', $rootName).' '.$key; + } + + if ($prototype instanceof ArrayNode) { + $children = $prototype->getChildren(); + } else { + if ($prototype->hasDefaultValue()) { + $prototypeValue = $prototype->getDefaultValue(); + } else { + switch (\get_class($prototype)) { + case 'Symfony\Component\Config\Definition\ScalarNode': + $prototypeValue = 'scalar value'; + break; + + case 'Symfony\Component\Config\Definition\FloatNode': + case 'Symfony\Component\Config\Definition\IntegerNode': + $prototypeValue = 'numeric value'; + break; + + case 'Symfony\Component\Config\Definition\BooleanNode': + $prototypeValue = 'true|false'; + break; + + case 'Symfony\Component\Config\Definition\EnumNode': + $prototypeValue = implode('|', array_map('json_encode', $prototype->getValues())); + break; + + default: + $prototypeValue = 'value'; + } + } + } + } + + // get attributes and elements + foreach ($children as $child) { + if (!$child instanceof ArrayNode) { + // get attributes + + // metadata + $name = str_replace('_', '-', $child->getName()); + $value = '%%%%not_defined%%%%'; // use a string which isn't used in the normal world + + // comments + $comments = array(); + if ($info = $child->getInfo()) { + $comments[] = $info; + } + + if ($example = $child->getExample()) { + $comments[] = 'Example: '.$example; + } + + if ($child->isRequired()) { + $comments[] = 'Required'; + } + + if ($child instanceof EnumNode) { + $comments[] = 'One of '.implode('; ', array_map('json_encode', $child->getValues())); + } + + if (\count($comments)) { + $rootAttributeComments[$name] = implode(";\n", $comments); + } + + // default values + if ($child->hasDefaultValue()) { + $value = $child->getDefaultValue(); + } + + // append attribute + $rootAttributes[$name] = $value; + } else { + // get elements + $rootChildren[] = $child; + } + } + } + + // render comments + + // root node comment + if (\count($rootComments)) { + foreach ($rootComments as $comment) { + $this->writeLine('', $depth); + } + } + + // attribute comments + if (\count($rootAttributeComments)) { + foreach ($rootAttributeComments as $attrName => $comment) { + $commentDepth = $depth + 4 + \strlen($attrName) + 2; + $commentLines = explode("\n", $comment); + $multiline = (\count($commentLines) > 1); + $comment = implode(PHP_EOL.str_repeat(' ', $commentDepth), $commentLines); + + if ($multiline) { + $this->writeLine('', $depth); + } else { + $this->writeLine('', $depth); + } + } + } + + // render start tag + attributes + $rootIsVariablePrototype = isset($prototypeValue); + $rootIsEmptyTag = (0 === \count($rootChildren) && !$rootIsVariablePrototype); + $rootOpenTag = '<'.$rootName; + if (1 >= ($attributesCount = \count($rootAttributes))) { + if (1 === $attributesCount) { + $rootOpenTag .= sprintf(' %s="%s"', current(array_keys($rootAttributes)), $this->writeValue(current($rootAttributes))); + } + + $rootOpenTag .= $rootIsEmptyTag ? ' />' : '>'; + + if ($rootIsVariablePrototype) { + $rootOpenTag .= $prototypeValue.''; + } + + $this->writeLine($rootOpenTag, $depth); + } else { + $this->writeLine($rootOpenTag, $depth); + + $i = 1; + + foreach ($rootAttributes as $attrName => $attrValue) { + $attr = sprintf('%s="%s"', $attrName, $this->writeValue($attrValue)); + + $this->writeLine($attr, $depth + 4); + + if ($attributesCount === $i++) { + $this->writeLine($rootIsEmptyTag ? '/>' : '>', $depth); + + if ($rootIsVariablePrototype) { + $rootOpenTag .= $prototypeValue.''; + } + } + } + } + + // render children tags + foreach ($rootChildren as $child) { + $this->writeLine(''); + $this->writeNode($child, $depth + 4); + } + + // render end tag + if (!$rootIsEmptyTag && !$rootIsVariablePrototype) { + $this->writeLine(''); + + $rootEndTag = ''; + $this->writeLine($rootEndTag, $depth); + } + } + + /** + * Outputs a single config reference line. + * + * @param string $text + * @param int $indent + */ + private function writeLine($text, $indent = 0) + { + $indent = \strlen($text) + $indent; + $format = '%'.$indent.'s'; + + $this->reference .= sprintf($format, $text).PHP_EOL; + } + + /** + * Renders the string conversion of the value. + * + * @param mixed $value + * + * @return string + */ + private function writeValue($value) + { + if ('%%%%not_defined%%%%' === $value) { + return ''; + } + + if (\is_string($value) || is_numeric($value)) { + return $value; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + if (null === $value) { + return 'null'; + } + + if (empty($value)) { + return ''; + } + + if (\is_array($value)) { + return implode(',', $value); + } + } +} diff --git a/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php new file mode 100644 index 0000000..8945cba --- /dev/null +++ b/vendor/symfony/config/Definition/Dumper/YamlReferenceDumper.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Dumper; + +use Symfony\Component\Config\Definition\ArrayNode; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\EnumNode; +use Symfony\Component\Config\Definition\NodeInterface; +use Symfony\Component\Config\Definition\PrototypedArrayNode; +use Symfony\Component\Yaml\Inline; + +/** + * Dumps a Yaml reference configuration for the given configuration/node instance. + * + * @author Kevin Bond + */ +class YamlReferenceDumper +{ + private $reference; + + public function dump(ConfigurationInterface $configuration) + { + return $this->dumpNode($configuration->getConfigTreeBuilder()->buildTree()); + } + + public function dumpNode(NodeInterface $node) + { + $this->reference = ''; + $this->writeNode($node); + $ref = $this->reference; + $this->reference = null; + + return $ref; + } + + /** + * @param NodeInterface $node + * @param int $depth + */ + private function writeNode(NodeInterface $node, $depth = 0) + { + $comments = array(); + $default = ''; + $defaultArray = null; + $children = null; + $example = $node->getExample(); + + // defaults + if ($node instanceof ArrayNode) { + $children = $node->getChildren(); + + if ($node instanceof PrototypedArrayNode) { + $prototype = $node->getPrototype(); + + if ($prototype instanceof ArrayNode) { + $children = $prototype->getChildren(); + } + + // check for attribute as key + if ($key = $node->getKeyAttribute()) { + $keyNodeClass = 'Symfony\Component\Config\Definition\\'.($prototype instanceof ArrayNode ? 'ArrayNode' : 'ScalarNode'); + $keyNode = new $keyNodeClass($key, $node); + + $info = 'Prototype'; + if (null !== $prototype->getInfo()) { + $info .= ': '.$prototype->getInfo(); + } + $keyNode->setInfo($info); + + // add children + foreach ($children as $childNode) { + $keyNode->addChild($childNode); + } + $children = array($key => $keyNode); + } + } + + if (!$children) { + if ($node->hasDefaultValue() && \count($defaultArray = $node->getDefaultValue())) { + $default = ''; + } elseif (!\is_array($example)) { + $default = '[]'; + } + } + } elseif ($node instanceof EnumNode) { + $comments[] = 'One of '.implode('; ', array_map('json_encode', $node->getValues())); + $default = $node->hasDefaultValue() ? Inline::dump($node->getDefaultValue()) : '~'; + } else { + $default = '~'; + + if ($node->hasDefaultValue()) { + $default = $node->getDefaultValue(); + + if (\is_array($default)) { + if (\count($defaultArray = $node->getDefaultValue())) { + $default = ''; + } elseif (!\is_array($example)) { + $default = '[]'; + } + } else { + $default = Inline::dump($default); + } + } + } + + // required? + if ($node->isRequired()) { + $comments[] = 'Required'; + } + + // example + if ($example && !\is_array($example)) { + $comments[] = 'Example: '.$example; + } + + $default = '' != (string) $default ? ' '.$default : ''; + $comments = \count($comments) ? '# '.implode(', ', $comments) : ''; + + $text = rtrim(sprintf('%-21s%s %s', $node->getName().':', $default, $comments), ' '); + + if ($info = $node->getInfo()) { + $this->writeLine(''); + // indenting multi-line info + $info = str_replace("\n", sprintf("\n%".($depth * 4).'s# ', ' '), $info); + $this->writeLine('# '.$info, $depth * 4); + } + + $this->writeLine($text, $depth * 4); + + // output defaults + if ($defaultArray) { + $this->writeLine(''); + + $message = \count($defaultArray) > 1 ? 'Defaults' : 'Default'; + + $this->writeLine('# '.$message.':', $depth * 4 + 4); + + $this->writeArray($defaultArray, $depth + 1); + } + + if (\is_array($example)) { + $this->writeLine(''); + + $message = \count($example) > 1 ? 'Examples' : 'Example'; + + $this->writeLine('# '.$message.':', $depth * 4 + 4); + + $this->writeArray($example, $depth + 1); + } + + if ($children) { + foreach ($children as $childNode) { + $this->writeNode($childNode, $depth + 1); + } + } + } + + /** + * Outputs a single config reference line. + * + * @param string $text + * @param int $indent + */ + private function writeLine($text, $indent = 0) + { + $indent = \strlen($text) + $indent; + $format = '%'.$indent.'s'; + + $this->reference .= sprintf($format, $text)."\n"; + } + + private function writeArray(array $array, $depth) + { + $isIndexed = array_values($array) === $array; + + foreach ($array as $key => $value) { + if (\is_array($value)) { + $val = ''; + } else { + $val = $value; + } + + if ($isIndexed) { + $this->writeLine('- '.$val, $depth * 4); + } else { + $this->writeLine(sprintf('%-20s %s', $key.':', $val), $depth * 4); + } + + if (\is_array($value)) { + $this->writeArray($value, $depth + 1); + } + } + } +} diff --git a/vendor/symfony/config/Definition/EnumNode.php b/vendor/symfony/config/Definition/EnumNode.php new file mode 100644 index 0000000..a214a85 --- /dev/null +++ b/vendor/symfony/config/Definition/EnumNode.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * Node which only allows a finite set of values. + * + * @author Johannes M. Schmitt + */ +class EnumNode extends ScalarNode +{ + private $values; + + public function __construct($name, NodeInterface $parent = null, array $values = array()) + { + $values = array_unique($values); + if (empty($values)) { + throw new \InvalidArgumentException('$values must contain at least one element.'); + } + + parent::__construct($name, $parent); + $this->values = $values; + } + + public function getValues() + { + return $this->values; + } + + protected function finalizeValue($value) + { + $value = parent::finalizeValue($value); + + if (!\in_array($value, $this->values, true)) { + $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_map('json_encode', $this->values)))); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } +} diff --git a/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php new file mode 100644 index 0000000..48dd932 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/DuplicateKeyException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * This exception is thrown whenever the key of an array is not unique. This can + * only be the case if the configuration is coming from an XML file. + * + * @author Johannes M. Schmitt + */ +class DuplicateKeyException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/Exception.php b/vendor/symfony/config/Definition/Exception/Exception.php new file mode 100644 index 0000000..8933a49 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/Exception.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * Base exception for all configuration exceptions. + * + * @author Johannes M. Schmitt + */ +class Exception extends \RuntimeException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php new file mode 100644 index 0000000..726c07f --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/ForbiddenOverwriteException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * This exception is thrown when a configuration path is overwritten from a + * subsequent configuration file, but the entry node specifically forbids this. + * + * @author Johannes M. Schmitt + */ +class ForbiddenOverwriteException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php new file mode 100644 index 0000000..3dbc57b --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidConfigurationException.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * A very general exception which can be thrown whenever non of the more specific + * exceptions is suitable. + * + * @author Johannes M. Schmitt + */ +class InvalidConfigurationException extends Exception +{ + private $path; + private $containsHints = false; + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + /** + * Adds extra information that is suffixed to the original exception message. + * + * @param string $hint + */ + public function addHint($hint) + { + if (!$this->containsHints) { + $this->message .= "\nHint: ".$hint; + $this->containsHints = true; + } else { + $this->message .= ', '.$hint; + } + } +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php new file mode 100644 index 0000000..98310da --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidDefinitionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * Thrown when an error is detected in a node Definition. + * + * @author Victor Berchet + */ +class InvalidDefinitionException extends Exception +{ +} diff --git a/vendor/symfony/config/Definition/Exception/InvalidTypeException.php b/vendor/symfony/config/Definition/Exception/InvalidTypeException.php new file mode 100644 index 0000000..d7ca8c9 --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/InvalidTypeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * This exception is thrown if an invalid type is encountered. + * + * @author Johannes M. Schmitt + */ +class InvalidTypeException extends InvalidConfigurationException +{ +} diff --git a/vendor/symfony/config/Definition/Exception/UnsetKeyException.php b/vendor/symfony/config/Definition/Exception/UnsetKeyException.php new file mode 100644 index 0000000..863181a --- /dev/null +++ b/vendor/symfony/config/Definition/Exception/UnsetKeyException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition\Exception; + +/** + * This exception is usually not encountered by the end-user, but only used + * internally to signal the parent scope to unset a key. + * + * @author Johannes M. Schmitt + */ +class UnsetKeyException extends Exception +{ +} diff --git a/vendor/symfony/config/Definition/FloatNode.php b/vendor/symfony/config/Definition/FloatNode.php new file mode 100644 index 0000000..9eb8789 --- /dev/null +++ b/vendor/symfony/config/Definition/FloatNode.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a float value in the config tree. + * + * @author Jeanmonod David + */ +class FloatNode extends NumericNode +{ + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + // Integers are also accepted, we just cast them + if (\is_int($value)) { + $value = (float) $value; + } + + if (!\is_float($value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected float, but got %s.', $this->getPath(), \gettype($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } +} diff --git a/vendor/symfony/config/Definition/IntegerNode.php b/vendor/symfony/config/Definition/IntegerNode.php new file mode 100644 index 0000000..8ec068a --- /dev/null +++ b/vendor/symfony/config/Definition/IntegerNode.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents an integer value in the config tree. + * + * @author Jeanmonod David + */ +class IntegerNode extends NumericNode +{ + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + if (!\is_int($value)) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected int, but got %s.', $this->getPath(), \gettype($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } +} diff --git a/vendor/symfony/config/Definition/NodeInterface.php b/vendor/symfony/config/Definition/NodeInterface.php new file mode 100644 index 0000000..45f1f68 --- /dev/null +++ b/vendor/symfony/config/Definition/NodeInterface.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\ForbiddenOverwriteException; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * Common Interface among all nodes. + * + * In most cases, it is better to inherit from BaseNode instead of implementing + * this interface yourself. + * + * @author Johannes M. Schmitt + */ +interface NodeInterface +{ + /** + * Returns the name of the node. + * + * @return string The name of the node + */ + public function getName(); + + /** + * Returns the path of the node. + * + * @return string The node path + */ + public function getPath(); + + /** + * Returns true when the node is required. + * + * @return bool If the node is required + */ + public function isRequired(); + + /** + * Returns true when the node has a default value. + * + * @return bool If the node has a default value + */ + public function hasDefaultValue(); + + /** + * Returns the default value of the node. + * + * @return mixed The default value + * + * @throws \RuntimeException if the node has no default value + */ + public function getDefaultValue(); + + /** + * Normalizes a value. + * + * @param mixed $value The value to normalize + * + * @return mixed The normalized value + * + * @throws InvalidTypeException if the value type is invalid + */ + public function normalize($value); + + /** + * Merges two values together. + * + * @param mixed $leftSide + * @param mixed $rightSide + * + * @return mixed The merged value + * + * @throws ForbiddenOverwriteException if the configuration path cannot be overwritten + * @throws InvalidTypeException if the value type is invalid + */ + public function merge($leftSide, $rightSide); + + /** + * Finalizes a value. + * + * @param mixed $value The value to finalize + * + * @return mixed The finalized value + * + * @throws InvalidTypeException if the value type is invalid + * @throws InvalidConfigurationException if the value is invalid configuration + */ + public function finalize($value); +} diff --git a/vendor/symfony/config/Definition/NumericNode.php b/vendor/symfony/config/Definition/NumericNode.php new file mode 100644 index 0000000..439935e --- /dev/null +++ b/vendor/symfony/config/Definition/NumericNode.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * This node represents a numeric value in the config tree. + * + * @author David Jeanmonod + */ +class NumericNode extends ScalarNode +{ + protected $min; + protected $max; + + public function __construct($name, NodeInterface $parent = null, $min = null, $max = null) + { + parent::__construct($name, $parent); + $this->min = $min; + $this->max = $max; + } + + /** + * {@inheritdoc} + */ + protected function finalizeValue($value) + { + $value = parent::finalizeValue($value); + + $errorMsg = null; + if (isset($this->min) && $value < $this->min) { + $errorMsg = sprintf('The value %s is too small for path "%s". Should be greater than or equal to %s', $value, $this->getPath(), $this->min); + } + if (isset($this->max) && $value > $this->max) { + $errorMsg = sprintf('The value %s is too big for path "%s". Should be less than or equal to %s', $value, $this->getPath(), $this->max); + } + if (isset($errorMsg)) { + $ex = new InvalidConfigurationException($errorMsg); + $ex->setPath($this->getPath()); + throw $ex; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + protected function isValueEmpty($value) + { + // a numeric value cannot be empty + return false; + } +} diff --git a/vendor/symfony/config/Definition/Processor.php b/vendor/symfony/config/Definition/Processor.php new file mode 100644 index 0000000..3e0feab --- /dev/null +++ b/vendor/symfony/config/Definition/Processor.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +/** + * This class is the entry point for config normalization/merging/finalization. + * + * @author Johannes M. Schmitt + */ +class Processor +{ + /** + * Processes an array of configurations. + * + * @param NodeInterface $configTree The node tree describing the configuration + * @param array $configs An array of configuration items to process + * + * @return array The processed configuration + */ + public function process(NodeInterface $configTree, array $configs) + { + $currentConfig = array(); + foreach ($configs as $config) { + $config = $configTree->normalize($config); + $currentConfig = $configTree->merge($currentConfig, $config); + } + + return $configTree->finalize($currentConfig); + } + + /** + * Processes an array of configurations. + * + * @param ConfigurationInterface $configuration The configuration class + * @param array $configs An array of configuration items to process + * + * @return array The processed configuration + */ + public function processConfiguration(ConfigurationInterface $configuration, array $configs) + { + return $this->process($configuration->getConfigTreeBuilder()->buildTree(), $configs); + } + + /** + * Normalizes a configuration entry. + * + * This method returns a normalize configuration array for a given key + * to remove the differences due to the original format (YAML and XML mainly). + * + * Here is an example. + * + * The configuration in XML: + * + * twig.extension.foo + * twig.extension.bar + * + * And the same configuration in YAML: + * + * extensions: ['twig.extension.foo', 'twig.extension.bar'] + * + * @param array $config A config array + * @param string $key The key to normalize + * @param string $plural The plural form of the key if it is irregular + * + * @return array + */ + public static function normalizeConfig($config, $key, $plural = null) + { + if (null === $plural) { + $plural = $key.'s'; + } + + if (isset($config[$plural])) { + return $config[$plural]; + } + + if (isset($config[$key])) { + if (\is_string($config[$key]) || !\is_int(key($config[$key]))) { + // only one + return array($config[$key]); + } + + return $config[$key]; + } + + return array(); + } +} diff --git a/vendor/symfony/config/Definition/PrototypeNodeInterface.php b/vendor/symfony/config/Definition/PrototypeNodeInterface.php new file mode 100644 index 0000000..8bbb84d --- /dev/null +++ b/vendor/symfony/config/Definition/PrototypeNodeInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +/** + * This interface must be implemented by nodes which can be used as prototypes. + * + * @author Johannes M. Schmitt + */ +interface PrototypeNodeInterface extends NodeInterface +{ + /** + * Sets the name of the node. + * + * @param string $name The name of the node + */ + public function setName($name); +} diff --git a/vendor/symfony/config/Definition/PrototypedArrayNode.php b/vendor/symfony/config/Definition/PrototypedArrayNode.php new file mode 100644 index 0000000..eddcb32 --- /dev/null +++ b/vendor/symfony/config/Definition/PrototypedArrayNode.php @@ -0,0 +1,377 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\DuplicateKeyException; +use Symfony\Component\Config\Definition\Exception\Exception; +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; +use Symfony\Component\Config\Definition\Exception\UnsetKeyException; + +/** + * Represents a prototyped Array node in the config tree. + * + * @author Johannes M. Schmitt + */ +class PrototypedArrayNode extends ArrayNode +{ + protected $prototype; + protected $keyAttribute; + protected $removeKeyAttribute = false; + protected $minNumberOfElements = 0; + protected $defaultValue = array(); + protected $defaultChildren; + /** + * @var NodeInterface[] An array of the prototypes of the simplified value children + */ + private $valuePrototypes = array(); + + /** + * Sets the minimum number of elements that a prototype based node must + * contain. By default this is zero, meaning no elements. + * + * @param int $number + */ + public function setMinNumberOfElements($number) + { + $this->minNumberOfElements = $number; + } + + /** + * Sets the attribute which value is to be used as key. + * + * This is useful when you have an indexed array that should be an + * associative array. You can select an item from within the array + * to be the key of the particular item. For example, if "id" is the + * "key", then: + * + * array( + * array('id' => 'my_name', 'foo' => 'bar'), + * ); + * + * becomes + * + * array( + * 'my_name' => array('foo' => 'bar'), + * ); + * + * If you'd like "'id' => 'my_name'" to still be present in the resulting + * array, then you can set the second argument of this method to false. + * + * @param string $attribute The name of the attribute which value is to be used as a key + * @param bool $remove Whether or not to remove the key + */ + public function setKeyAttribute($attribute, $remove = true) + { + $this->keyAttribute = $attribute; + $this->removeKeyAttribute = $remove; + } + + /** + * Retrieves the name of the attribute which value should be used as key. + * + * @return string The name of the attribute + */ + public function getKeyAttribute() + { + return $this->keyAttribute; + } + + /** + * Sets the default value of this node. + * + * @param string $value + * + * @throws \InvalidArgumentException if the default value is not an array + */ + public function setDefaultValue($value) + { + if (!\is_array($value)) { + throw new \InvalidArgumentException($this->getPath().': the default value of an array node has to be an array.'); + } + + $this->defaultValue = $value; + } + + /** + * {@inheritdoc} + */ + public function hasDefaultValue() + { + return true; + } + + /** + * Adds default children when none are set. + * + * @param int|string|array|null $children The number of children|The child name|The children names to be added + */ + public function setAddChildrenIfNoneSet($children = array('defaults')) + { + if (null === $children) { + $this->defaultChildren = array('defaults'); + } else { + $this->defaultChildren = \is_int($children) && $children > 0 ? range(1, $children) : (array) $children; + } + } + + /** + * {@inheritdoc} + * + * The default value could be either explicited or derived from the prototype + * default value. + */ + public function getDefaultValue() + { + if (null !== $this->defaultChildren) { + $default = $this->prototype->hasDefaultValue() ? $this->prototype->getDefaultValue() : array(); + $defaults = array(); + foreach (array_values($this->defaultChildren) as $i => $name) { + $defaults[null === $this->keyAttribute ? $i : $name] = $default; + } + + return $defaults; + } + + return $this->defaultValue; + } + + /** + * Sets the node prototype. + */ + public function setPrototype(PrototypeNodeInterface $node) + { + $this->prototype = $node; + } + + /** + * Retrieves the prototype. + * + * @return PrototypeNodeInterface The prototype + */ + public function getPrototype() + { + return $this->prototype; + } + + /** + * Disable adding concrete children for prototyped nodes. + * + * @throws Exception + */ + public function addChild(NodeInterface $node) + { + throw new Exception('A prototyped array node can not have concrete children.'); + } + + /** + * Finalizes the value of this node. + * + * @param mixed $value + * + * @return mixed The finalized value + * + * @throws UnsetKeyException + * @throws InvalidConfigurationException if the node doesn't have enough children + */ + protected function finalizeValue($value) + { + if (false === $value) { + throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value))); + } + + foreach ($value as $k => $v) { + $prototype = $this->getPrototypeForChild($k); + try { + $value[$k] = $prototype->finalize($v); + } catch (UnsetKeyException $e) { + unset($value[$k]); + } + } + + if (\count($value) < $this->minNumberOfElements) { + $ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements)); + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } + + /** + * Normalizes the value. + * + * @param mixed $value The value to normalize + * + * @return mixed The normalized value + * + * @throws InvalidConfigurationException + * @throws DuplicateKeyException + */ + protected function normalizeValue($value) + { + if (false === $value) { + return $value; + } + + $value = $this->remapXml($value); + + $isAssoc = array_keys($value) !== range(0, \count($value) - 1); + $normalized = array(); + foreach ($value as $k => $v) { + if (null !== $this->keyAttribute && \is_array($v)) { + if (!isset($v[$this->keyAttribute]) && \is_int($k) && !$isAssoc) { + $ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } elseif (isset($v[$this->keyAttribute])) { + $k = $v[$this->keyAttribute]; + + // remove the key attribute when required + if ($this->removeKeyAttribute) { + unset($v[$this->keyAttribute]); + } + + // if only "value" is left + if (array_keys($v) === array('value')) { + $v = $v['value']; + if ($this->prototype instanceof ArrayNode && ($children = $this->prototype->getChildren()) && array_key_exists('value', $children)) { + $valuePrototype = current($this->valuePrototypes) ?: clone $children['value']; + $valuePrototype->parent = $this; + $originalClosures = $this->prototype->normalizationClosures; + if (\is_array($originalClosures)) { + $valuePrototypeClosures = $valuePrototype->normalizationClosures; + $valuePrototype->normalizationClosures = \is_array($valuePrototypeClosures) ? array_merge($originalClosures, $valuePrototypeClosures) : $originalClosures; + } + $this->valuePrototypes[$k] = $valuePrototype; + } + } + } + + if (array_key_exists($k, $normalized)) { + $ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + $prototype = $this->getPrototypeForChild($k); + if (null !== $this->keyAttribute || $isAssoc) { + $normalized[$k] = $prototype->normalize($v); + } else { + $normalized[] = $prototype->normalize($v); + } + } + + return $normalized; + } + + /** + * Merges values together. + * + * @param mixed $leftSide The left side to merge + * @param mixed $rightSide The right side to merge + * + * @return mixed The merged values + * + * @throws InvalidConfigurationException + * @throws \RuntimeException + */ + protected function mergeValues($leftSide, $rightSide) + { + if (false === $rightSide) { + // if this is still false after the last config has been merged the + // finalization pass will take care of removing this key entirely + return false; + } + + if (false === $leftSide || !$this->performDeepMerging) { + return $rightSide; + } + + foreach ($rightSide as $k => $v) { + // prototype, and key is irrelevant, so simply append the element + if (null === $this->keyAttribute) { + $leftSide[] = $v; + continue; + } + + // no conflict + if (!array_key_exists($k, $leftSide)) { + if (!$this->allowNewKeys) { + $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath())); + $ex->setPath($this->getPath()); + + throw $ex; + } + + $leftSide[$k] = $v; + continue; + } + + $prototype = $this->getPrototypeForChild($k); + $leftSide[$k] = $prototype->merge($leftSide[$k], $v); + } + + return $leftSide; + } + + /** + * Returns a prototype for the child node that is associated to $key in the value array. + * For general child nodes, this will be $this->prototype. + * But if $this->removeKeyAttribute is true and there are only two keys in the child node: + * one is same as this->keyAttribute and the other is 'value', then the prototype will be different. + * + * For example, assume $this->keyAttribute is 'name' and the value array is as follows: + * + * array( + * array( + * 'name' => 'name001', + * 'value' => 'value001' + * ) + * ) + * + * Now, the key is 0 and the child node is: + * + * array( + * 'name' => 'name001', + * 'value' => 'value001' + * ) + * + * When normalizing the value array, the 'name' element will removed from the child node + * and its value becomes the new key of the child node: + * + * array( + * 'name001' => array('value' => 'value001') + * ) + * + * Now only 'value' element is left in the child node which can be further simplified into a string: + * + * array('name001' => 'value001') + * + * Now, the key becomes 'name001' and the child node becomes 'value001' and + * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance. + * + * @param string $key The key of the child node + * + * @return mixed The prototype instance + */ + private function getPrototypeForChild($key) + { + $prototype = isset($this->valuePrototypes[$key]) ? $this->valuePrototypes[$key] : $this->prototype; + $prototype->setName($key); + + return $prototype; + } +} diff --git a/vendor/symfony/config/Definition/ReferenceDumper.php b/vendor/symfony/config/Definition/ReferenceDumper.php new file mode 100644 index 0000000..047b258 --- /dev/null +++ b/vendor/symfony/config/Definition/ReferenceDumper.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +@trigger_error('The '.__NAMESPACE__.'\ReferenceDumper class is deprecated since Symfony 2.4 and will be removed in 3.0. Use the Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper class instead.', E_USER_DEPRECATED); + +use Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper; + +/** + * @deprecated since version 2.4, to be removed in 3.0. + * Use {@link \Symfony\Component\Config\Definition\Dumper\YamlReferenceDumper} instead. + */ +class ReferenceDumper extends YamlReferenceDumper +{ +} diff --git a/vendor/symfony/config/Definition/ScalarNode.php b/vendor/symfony/config/Definition/ScalarNode.php new file mode 100644 index 0000000..53c1ed2 --- /dev/null +++ b/vendor/symfony/config/Definition/ScalarNode.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidTypeException; + +/** + * This node represents a scalar value in the config tree. + * + * The following values are considered scalars: + * * booleans + * * strings + * * null + * * integers + * * floats + * + * @author Johannes M. Schmitt + */ +class ScalarNode extends VariableNode +{ + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + if (!is_scalar($value) && null !== $value) { + $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected scalar, but got %s.', $this->getPath(), \gettype($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + } + + /** + * {@inheritdoc} + */ + protected function isValueEmpty($value) + { + return null === $value || '' === $value; + } +} diff --git a/vendor/symfony/config/Definition/VariableNode.php b/vendor/symfony/config/Definition/VariableNode.php new file mode 100644 index 0000000..1a3442d --- /dev/null +++ b/vendor/symfony/config/Definition/VariableNode.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Definition; + +use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; + +/** + * This node represents a value of variable type in the config tree. + * + * This node is intended for values of arbitrary type. + * Any PHP type is accepted as a value. + * + * @author Jeremy Mikola + */ +class VariableNode extends BaseNode implements PrototypeNodeInterface +{ + protected $defaultValueSet = false; + protected $defaultValue; + protected $allowEmptyValue = true; + + public function setDefaultValue($value) + { + $this->defaultValueSet = true; + $this->defaultValue = $value; + } + + /** + * {@inheritdoc} + */ + public function hasDefaultValue() + { + return $this->defaultValueSet; + } + + /** + * {@inheritdoc} + */ + public function getDefaultValue() + { + $v = $this->defaultValue; + + return $v instanceof \Closure ? $v() : $v; + } + + /** + * Sets if this node is allowed to have an empty value. + * + * @param bool $boolean True if this entity will accept empty values + */ + public function setAllowEmptyValue($boolean) + { + $this->allowEmptyValue = (bool) $boolean; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + protected function validateType($value) + { + } + + /** + * {@inheritdoc} + */ + protected function finalizeValue($value) + { + if (!$this->allowEmptyValue && $this->isValueEmpty($value)) { + $ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value))); + if ($hint = $this->getInfo()) { + $ex->addHint($hint); + } + $ex->setPath($this->getPath()); + + throw $ex; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + protected function normalizeValue($value) + { + return $value; + } + + /** + * {@inheritdoc} + */ + protected function mergeValues($leftSide, $rightSide) + { + return $rightSide; + } + + /** + * Evaluates if the given value is to be treated as empty. + * + * By default, PHP's empty() function is used to test for emptiness. This + * method may be overridden by subtypes to better match their understanding + * of empty data. + * + * @param mixed $value + * + * @return bool + */ + protected function isValueEmpty($value) + { + return empty($value); + } +} diff --git a/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php new file mode 100644 index 0000000..6a3b01c --- /dev/null +++ b/vendor/symfony/config/Exception/FileLoaderImportCircularReferenceException.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Exception; + +/** + * Exception class for when a circular reference is detected when importing resources. + * + * @author Fabien Potencier + */ +class FileLoaderImportCircularReferenceException extends FileLoaderLoadException +{ + public function __construct(array $resources, $code = null, $previous = null) + { + $message = sprintf('Circular reference detected in "%s" ("%s" > "%s").', $this->varToString($resources[0]), implode('" > "', $resources), $resources[0]); + + \Exception::__construct($message, $code, $previous); + } +} diff --git a/vendor/symfony/config/Exception/FileLoaderLoadException.php b/vendor/symfony/config/Exception/FileLoaderLoadException.php new file mode 100644 index 0000000..819abe6 --- /dev/null +++ b/vendor/symfony/config/Exception/FileLoaderLoadException.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Exception; + +/** + * Exception class for when a resource cannot be loaded or imported. + * + * @author Ryan Weaver + */ +class FileLoaderLoadException extends \Exception +{ + /** + * @param string $resource The resource that could not be imported + * @param string $sourceResource The original resource importing the new resource + * @param int $code The error code + * @param \Exception $previous A previous exception + */ + public function __construct($resource, $sourceResource = null, $code = null, $previous = null) + { + $message = ''; + if ($previous) { + // Include the previous exception, to help the user see what might be the underlying cause + + // Trim the trailing period of the previous message. We only want 1 period remove so no rtrim... + if ('.' === substr($previous->getMessage(), -1)) { + $trimmedMessage = substr($previous->getMessage(), 0, -1); + $message .= sprintf('%s', $trimmedMessage).' in '; + } else { + $message .= sprintf('%s', $previous->getMessage()).' in '; + } + $message .= $resource.' '; + + // show tweaked trace to complete the human readable sentence + if (null === $sourceResource) { + $message .= sprintf('(which is loaded in resource "%s")', $this->varToString($resource)); + } else { + $message .= sprintf('(which is being imported from "%s")', $this->varToString($sourceResource)); + } + $message .= '.'; + + // if there's no previous message, present it the default way + } elseif (null === $sourceResource) { + $message .= sprintf('Cannot load resource "%s".', $this->varToString($resource)); + } else { + $message .= sprintf('Cannot import resource "%s" from "%s".', $this->varToString($resource), $this->varToString($sourceResource)); + } + + // Is the resource located inside a bundle? + if ('@' === $resource[0]) { + $parts = explode(\DIRECTORY_SEPARATOR, $resource); + $bundle = substr($parts[0], 1); + $message .= sprintf(' Make sure the "%s" bundle is correctly registered and loaded in the application kernel class.', $bundle); + $message .= sprintf(' If the bundle is registered, make sure the bundle path "%s" is not empty.', $resource); + } + + parent::__construct($message, $code, $previous); + } + + protected function varToString($var) + { + if (\is_object($var)) { + return sprintf('Object(%s)', \get_class($var)); + } + + if (\is_array($var)) { + $a = array(); + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf('Array(%s)', implode(', ', $a)); + } + + if (\is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/config/FileLocator.php b/vendor/symfony/config/FileLocator.php new file mode 100644 index 0000000..368cc19 --- /dev/null +++ b/vendor/symfony/config/FileLocator.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * FileLocator uses an array of pre-defined paths to find files. + * + * @author Fabien Potencier + */ +class FileLocator implements FileLocatorInterface +{ + protected $paths; + + /** + * @param string|array $paths A path or an array of paths where to look for resources + */ + public function __construct($paths = array()) + { + $this->paths = (array) $paths; + } + + /** + * {@inheritdoc} + */ + public function locate($name, $currentPath = null, $first = true) + { + if ('' == $name) { + throw new \InvalidArgumentException('An empty file name is not valid to be located.'); + } + + if ($this->isAbsolutePath($name)) { + if (!file_exists($name)) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not exist.', $name)); + } + + return $name; + } + + $paths = $this->paths; + + if (null !== $currentPath) { + array_unshift($paths, $currentPath); + } + + $paths = array_unique($paths); + $filepaths = array(); + + foreach ($paths as $path) { + if (@file_exists($file = $path.\DIRECTORY_SEPARATOR.$name)) { + if (true === $first) { + return $file; + } + $filepaths[] = $file; + } + } + + if (!$filepaths) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not exist (in: %s).', $name, implode(', ', $paths))); + } + + return $filepaths; + } + + /** + * Returns whether the file path is an absolute path. + * + * @param string $file A file path + * + * @return bool + */ + private function isAbsolutePath($file) + { + if ('/' === $file[0] || '\\' === $file[0] + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === $file[1] + && ('\\' === $file[2] || '/' === $file[2]) + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ) { + return true; + } + + return false; + } +} diff --git a/vendor/symfony/config/FileLocatorInterface.php b/vendor/symfony/config/FileLocatorInterface.php new file mode 100644 index 0000000..6605798 --- /dev/null +++ b/vendor/symfony/config/FileLocatorInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * @author Fabien Potencier + */ +interface FileLocatorInterface +{ + /** + * Returns a full path for a given file name. + * + * @param string $name The file name to locate + * @param string|null $currentPath The current path + * @param bool $first Whether to return the first occurrence or an array of filenames + * + * @return string|array The full path to the file or an array of file paths + * + * @throws \InvalidArgumentException When file is not found + */ + public function locate($name, $currentPath = null, $first = true); +} diff --git a/vendor/symfony/config/LICENSE b/vendor/symfony/config/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/config/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/config/Loader/DelegatingLoader.php b/vendor/symfony/config/Loader/DelegatingLoader.php new file mode 100644 index 0000000..237009d --- /dev/null +++ b/vendor/symfony/config/Loader/DelegatingLoader.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; + +/** + * DelegatingLoader delegates loading to other loaders using a loader resolver. + * + * This loader acts as an array of LoaderInterface objects - each having + * a chance to load a given resource (handled by the resolver) + * + * @author Fabien Potencier + */ +class DelegatingLoader extends Loader +{ + public function __construct(LoaderResolverInterface $resolver) + { + $this->resolver = $resolver; + } + + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + if (false === $loader = $this->resolver->resolve($resource, $type)) { + throw new FileLoaderLoadException($resource); + } + + return $loader->load($resource, $type); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return false !== $this->resolver->resolve($resource, $type); + } +} diff --git a/vendor/symfony/config/Loader/FileLoader.php b/vendor/symfony/config/Loader/FileLoader.php new file mode 100644 index 0000000..506eb1b --- /dev/null +++ b/vendor/symfony/config/Loader/FileLoader.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +use Symfony\Component\Config\Exception\FileLoaderImportCircularReferenceException; +use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\FileLocatorInterface; + +/** + * FileLoader is the abstract class used by all built-in loaders that are file based. + * + * @author Fabien Potencier + */ +abstract class FileLoader extends Loader +{ + protected static $loading = array(); + + protected $locator; + + private $currentDir; + + public function __construct(FileLocatorInterface $locator) + { + $this->locator = $locator; + } + + /** + * Sets the current directory. + * + * @param string $dir + */ + public function setCurrentDir($dir) + { + $this->currentDir = $dir; + } + + /** + * Returns the file locator used by this loader. + * + * @return FileLocatorInterface + */ + public function getLocator() + { + return $this->locator; + } + + /** + * Imports a resource. + * + * @param mixed $resource A Resource + * @param string|null $type The resource type or null if unknown + * @param bool $ignoreErrors Whether to ignore import errors or not + * @param string|null $sourceResource The original resource importing the new resource + * + * @return mixed + * + * @throws FileLoaderLoadException + * @throws FileLoaderImportCircularReferenceException + */ + public function import($resource, $type = null, $ignoreErrors = false, $sourceResource = null) + { + try { + $loader = $this->resolve($resource, $type); + + if ($loader instanceof self && null !== $this->currentDir) { + // we fallback to the current locator to keep BC + // as some some loaders do not call the parent __construct() + // @deprecated should be removed in 3.0 + $locator = $loader->getLocator(); + if (null === $locator) { + @trigger_error('Not calling the parent constructor in '.\get_class($loader).' which extends '.__CLASS__.' is deprecated since Symfony 2.7 and will not be supported anymore in 3.0.', E_USER_DEPRECATED); + $locator = $this->locator; + } + + $resource = $locator->locate($resource, $this->currentDir, false); + } + + $resources = \is_array($resource) ? $resource : array($resource); + for ($i = 0; $i < $resourcesCount = \count($resources); ++$i) { + if (isset(self::$loading[$resources[$i]])) { + if ($i == $resourcesCount - 1) { + throw new FileLoaderImportCircularReferenceException(array_keys(self::$loading)); + } + } else { + $resource = $resources[$i]; + break; + } + } + self::$loading[$resource] = true; + + try { + $ret = $loader->load($resource, $type); + } catch (\Exception $e) { + unset(self::$loading[$resource]); + throw $e; + } catch (\Throwable $e) { + unset(self::$loading[$resource]); + throw $e; + } + + unset(self::$loading[$resource]); + + return $ret; + } catch (FileLoaderImportCircularReferenceException $e) { + throw $e; + } catch (\Exception $e) { + if (!$ignoreErrors) { + // prevent embedded imports from nesting multiple exceptions + if ($e instanceof FileLoaderLoadException) { + throw $e; + } + + throw new FileLoaderLoadException($resource, $sourceResource, null, $e); + } + } + } +} diff --git a/vendor/symfony/config/Loader/Loader.php b/vendor/symfony/config/Loader/Loader.php new file mode 100644 index 0000000..a6f8d9c --- /dev/null +++ b/vendor/symfony/config/Loader/Loader.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; + +/** + * Loader is the abstract class used by all built-in loaders. + * + * @author Fabien Potencier + */ +abstract class Loader implements LoaderInterface +{ + protected $resolver; + + /** + * {@inheritdoc} + */ + public function getResolver() + { + return $this->resolver; + } + + /** + * {@inheritdoc} + */ + public function setResolver(LoaderResolverInterface $resolver) + { + $this->resolver = $resolver; + } + + /** + * Imports a resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return mixed + */ + public function import($resource, $type = null) + { + return $this->resolve($resource, $type)->load($resource, $type); + } + + /** + * Finds a loader able to load an imported resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return $this|LoaderInterface + * + * @throws FileLoaderLoadException If no loader is found + */ + public function resolve($resource, $type = null) + { + if ($this->supports($resource, $type)) { + return $this; + } + + $loader = null === $this->resolver ? false : $this->resolver->resolve($resource, $type); + + if (false === $loader) { + throw new FileLoaderLoadException($resource); + } + + return $loader; + } +} diff --git a/vendor/symfony/config/Loader/LoaderInterface.php b/vendor/symfony/config/Loader/LoaderInterface.php new file mode 100644 index 0000000..dfca9dd --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * LoaderInterface is the interface implemented by all loader classes. + * + * @author Fabien Potencier + */ +interface LoaderInterface +{ + /** + * Loads a resource. + * + * @param mixed $resource The resource + * @param string|null $type The resource type or null if unknown + * + * @throws \Exception If something went wrong + */ + public function load($resource, $type = null); + + /** + * Returns whether this class supports the given resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return bool True if this class supports the given resource, false otherwise + */ + public function supports($resource, $type = null); + + /** + * Gets the loader resolver. + * + * @return LoaderResolverInterface A LoaderResolverInterface instance + */ + public function getResolver(); + + /** + * Sets the loader resolver. + */ + public function setResolver(LoaderResolverInterface $resolver); +} diff --git a/vendor/symfony/config/Loader/LoaderResolver.php b/vendor/symfony/config/Loader/LoaderResolver.php new file mode 100644 index 0000000..9299bc0 --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderResolver.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * LoaderResolver selects a loader for a given resource. + * + * A resource can be anything (e.g. a full path to a config file or a Closure). + * Each loader determines whether it can load a resource and how. + * + * @author Fabien Potencier + */ +class LoaderResolver implements LoaderResolverInterface +{ + /** + * @var LoaderInterface[] An array of LoaderInterface objects + */ + private $loaders = array(); + + /** + * @param LoaderInterface[] $loaders An array of loaders + */ + public function __construct(array $loaders = array()) + { + foreach ($loaders as $loader) { + $this->addLoader($loader); + } + } + + /** + * {@inheritdoc} + */ + public function resolve($resource, $type = null) + { + foreach ($this->loaders as $loader) { + if ($loader->supports($resource, $type)) { + return $loader; + } + } + + return false; + } + + public function addLoader(LoaderInterface $loader) + { + $this->loaders[] = $loader; + $loader->setResolver($this); + } + + /** + * Returns the registered loaders. + * + * @return LoaderInterface[] An array of LoaderInterface instances + */ + public function getLoaders() + { + return $this->loaders; + } +} diff --git a/vendor/symfony/config/Loader/LoaderResolverInterface.php b/vendor/symfony/config/Loader/LoaderResolverInterface.php new file mode 100644 index 0000000..40f1a1a --- /dev/null +++ b/vendor/symfony/config/Loader/LoaderResolverInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Loader; + +/** + * LoaderResolverInterface selects a loader for a given resource. + * + * @author Fabien Potencier + */ +interface LoaderResolverInterface +{ + /** + * Returns a loader able to load the resource. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return LoaderInterface|false The loader or false if none is able to load the resource + */ + public function resolve($resource, $type = null); +} diff --git a/vendor/symfony/config/Resource/BCResourceInterfaceChecker.php b/vendor/symfony/config/Resource/BCResourceInterfaceChecker.php new file mode 100644 index 0000000..64c9309 --- /dev/null +++ b/vendor/symfony/config/Resource/BCResourceInterfaceChecker.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * Resource checker for the ResourceInterface. Exists for BC. + * + * @author Matthias Pigulla + * + * @deprecated since 2.8, to be removed in 3.0. + */ +class BCResourceInterfaceChecker extends SelfCheckingResourceChecker +{ + public function supports(ResourceInterface $metadata) + { + /* As all resources must be instanceof ResourceInterface, + we support them all. */ + return true; + } + + public function isFresh(ResourceInterface $resource, $timestamp) + { + @trigger_error(sprintf('The class "%s" is performing resource checking through ResourceInterface::isFresh(), which is deprecated since Symfony 2.8 and will be removed in 3.0', \get_class($resource)), E_USER_DEPRECATED); + + return parent::isFresh($resource, $timestamp); // For now, $metadata features the isFresh() method, so off we go (quack quack) + } +} diff --git a/vendor/symfony/config/Resource/DirectoryResource.php b/vendor/symfony/config/Resource/DirectoryResource.php new file mode 100644 index 0000000..b65d40a --- /dev/null +++ b/vendor/symfony/config/Resource/DirectoryResource.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * DirectoryResource represents a resources stored in a subdirectory tree. + * + * @author Fabien Potencier + */ +class DirectoryResource implements SelfCheckingResourceInterface, \Serializable +{ + private $resource; + private $pattern; + + /** + * @param string $resource The file path to the resource + * @param string|null $pattern A pattern to restrict monitored files + */ + public function __construct($resource, $pattern = null) + { + $this->resource = $resource; + $this->pattern = $pattern; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return md5(serialize(array($this->resource, $this->pattern))); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return $this->resource; + } + + /** + * Returns the pattern to restrict monitored files. + * + * @return string|null + */ + public function getPattern() + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + if (!is_dir($this->resource)) { + return false; + } + + if ($timestamp < filemtime($this->resource)) { + return false; + } + + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->resource), \RecursiveIteratorIterator::SELF_FIRST) as $file) { + // if regex filtering is enabled only check matching files + if ($this->pattern && $file->isFile() && !preg_match($this->pattern, $file->getBasename())) { + continue; + } + + // always monitor directories for changes, except the .. entries + // (otherwise deleted files wouldn't get detected) + if ($file->isDir() && '/..' === substr($file, -3)) { + continue; + } + + // for broken links + try { + $fileMTime = $file->getMTime(); + } catch (\RuntimeException $e) { + continue; + } + + // early return if a file's mtime exceeds the passed timestamp + if ($timestamp < $fileMTime) { + return false; + } + } + + return true; + } + + public function serialize() + { + return serialize(array($this->resource, $this->pattern)); + } + + public function unserialize($serialized) + { + list($this->resource, $this->pattern) = unserialize($serialized); + } +} diff --git a/vendor/symfony/config/Resource/FileExistenceResource.php b/vendor/symfony/config/Resource/FileExistenceResource.php new file mode 100644 index 0000000..0e87aab --- /dev/null +++ b/vendor/symfony/config/Resource/FileExistenceResource.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * FileExistenceResource represents a resource stored on the filesystem. + * Freshness is only evaluated against resource creation or deletion. + * + * The resource can be a file or a directory. + * + * @author Charles-Henri Bruyand + */ +class FileExistenceResource implements SelfCheckingResourceInterface, \Serializable +{ + private $resource; + + private $exists; + + /** + * @param string $resource The file path to the resource + */ + public function __construct($resource) + { + $this->resource = (string) $resource; + $this->exists = file_exists($resource); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + return file_exists($this->resource) === $this->exists; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array($this->resource, $this->exists)); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + list($this->resource, $this->exists) = unserialize($serialized); + } +} diff --git a/vendor/symfony/config/Resource/FileResource.php b/vendor/symfony/config/Resource/FileResource.php new file mode 100644 index 0000000..1177010 --- /dev/null +++ b/vendor/symfony/config/Resource/FileResource.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * FileResource represents a resource stored on the filesystem. + * + * The resource can be a file or a directory. + * + * @author Fabien Potencier + */ +class FileResource implements SelfCheckingResourceInterface, \Serializable +{ + /** + * @var string|false + */ + private $resource; + + /** + * @param string $resource The file path to the resource + */ + public function __construct($resource) + { + $this->resource = realpath($resource) ?: (file_exists($resource) ? $resource : false); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return (string) $this->resource; + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return $this->resource; + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + if (false === $this->resource || !file_exists($this->resource)) { + return false; + } + + return filemtime($this->resource) <= $timestamp; + } + + public function serialize() + { + return serialize($this->resource); + } + + public function unserialize($serialized) + { + $this->resource = unserialize($serialized); + } +} diff --git a/vendor/symfony/config/Resource/ResourceInterface.php b/vendor/symfony/config/Resource/ResourceInterface.php new file mode 100644 index 0000000..55b3e09 --- /dev/null +++ b/vendor/symfony/config/Resource/ResourceInterface.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * ResourceInterface is the interface that must be implemented by all Resource classes. + * + * @author Fabien Potencier + */ +interface ResourceInterface +{ + /** + * Returns a string representation of the Resource. + * + * This method is necessary to allow for resource de-duplication, for example by means + * of array_unique(). The string returned need not have a particular meaning, but has + * to be identical for different ResourceInterface instances referring to the same + * resource; and it should be unlikely to collide with that of other, unrelated + * resource instances. + * + * @return string A string representation unique to the underlying Resource + */ + public function __toString(); + + /** + * Returns true if the resource has not been updated since the given timestamp. + * + * @param int $timestamp The last time the resource was loaded + * + * @return bool True if the resource has not been updated, false otherwise + * + * @deprecated since 2.8, to be removed in 3.0. If your resource can check itself for + * freshness implement the SelfCheckingResourceInterface instead. + */ + public function isFresh($timestamp); + + /** + * Returns the tied resource. + * + * @return mixed The resource + * + * @deprecated since 2.8, to be removed in 3.0. As there are many different kinds of resource, + * a single getResource() method does not make sense at the interface level. You + * can still call getResource() on implementing classes, probably after performing + * a type check. If you know the concrete type of Resource at hand, the return value + * of this method may make sense to you. + */ + public function getResource(); +} diff --git a/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php b/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php new file mode 100644 index 0000000..d72203b --- /dev/null +++ b/vendor/symfony/config/Resource/SelfCheckingResourceChecker.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +use Symfony\Component\Config\ResourceCheckerInterface; + +/** + * Resource checker for instances of SelfCheckingResourceInterface. + * + * As these resources perform the actual check themselves, we can provide + * this class as a standard way of validating them. + * + * @author Matthias Pigulla + */ +class SelfCheckingResourceChecker implements ResourceCheckerInterface +{ + public function supports(ResourceInterface $metadata) + { + return $metadata instanceof SelfCheckingResourceInterface; + } + + public function isFresh(ResourceInterface $resource, $timestamp) + { + /* @var SelfCheckingResourceInterface $resource */ + return $resource->isFresh($timestamp); + } +} diff --git a/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php b/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php new file mode 100644 index 0000000..b3260f2 --- /dev/null +++ b/vendor/symfony/config/Resource/SelfCheckingResourceInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Resource; + +/** + * Interface for Resources that can check for freshness autonomously, + * without special support from external services. + * + * @author Matthias Pigulla + */ +interface SelfCheckingResourceInterface extends ResourceInterface +{ + /** + * Returns true if the resource has not been updated since the given timestamp. + * + * @param int $timestamp The last time the resource was loaded + * + * @return bool True if the resource has not been updated, false otherwise + */ + public function isFresh($timestamp); +} diff --git a/vendor/symfony/config/ResourceCheckerConfigCache.php b/vendor/symfony/config/ResourceCheckerConfigCache.php new file mode 100644 index 0000000..81c1b97 --- /dev/null +++ b/vendor/symfony/config/ResourceCheckerConfigCache.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Filesystem; + +/** + * ResourceCheckerConfigCache uses instances of ResourceCheckerInterface + * to check whether cached data is still fresh. + * + * @author Matthias Pigulla + */ +class ResourceCheckerConfigCache implements ConfigCacheInterface +{ + /** + * @var string + */ + private $file; + + /** + * @var ResourceCheckerInterface[] + */ + private $resourceCheckers; + + /** + * @param string $file The absolute cache path + * @param ResourceCheckerInterface[] $resourceCheckers The ResourceCheckers to use for the freshness check + */ + public function __construct($file, array $resourceCheckers = array()) + { + $this->file = $file; + $this->resourceCheckers = $resourceCheckers; + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->file; + } + + /** + * Checks if the cache is still fresh. + * + * This implementation will make a decision solely based on the ResourceCheckers + * passed in the constructor. + * + * The first ResourceChecker that supports a given resource is considered authoritative. + * Resources with no matching ResourceChecker will silently be ignored and considered fresh. + * + * @return bool true if the cache is fresh, false otherwise + */ + public function isFresh() + { + if (!is_file($this->file)) { + return false; + } + + if (!$this->resourceCheckers) { + return true; // shortcut - if we don't have any checkers we don't need to bother with the meta file at all + } + + $metadata = $this->getMetaFile(); + if (!is_file($metadata)) { + return false; + } + + $e = null; + $meta = false; + $time = filemtime($this->file); + $signalingException = new \UnexpectedValueException(); + $prevUnserializeHandler = ini_set('unserialize_callback_func', ''); + $prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context) use (&$prevErrorHandler, $signalingException) { + if (E_WARNING === $type && 'Class __PHP_Incomplete_Class has no unserializer' === $msg) { + throw $signalingException; + } + + return $prevErrorHandler ? $prevErrorHandler($type, $msg, $file, $line, $context) : false; + }); + + try { + $meta = unserialize(file_get_contents($metadata)); + } catch (\Error $e) { + } catch (\Exception $e) { + } + restore_error_handler(); + ini_set('unserialize_callback_func', $prevUnserializeHandler); + if (null !== $e && $e !== $signalingException) { + throw $e; + } + if (false === $meta) { + return false; + } + + foreach ($meta as $resource) { + /* @var ResourceInterface $resource */ + foreach ($this->resourceCheckers as $checker) { + if (!$checker->supports($resource)) { + continue; // next checker + } + if ($checker->isFresh($resource, $time)) { + break; // no need to further check this resource + } + + return false; // cache is stale + } + // no suitable checker found, ignore this resource + } + + return true; + } + + /** + * Writes cache. + * + * @param string $content The content to write in the cache + * @param ResourceInterface[] $metadata An array of metadata + * + * @throws \RuntimeException When cache file can't be written + */ + public function write($content, array $metadata = null) + { + $mode = 0666; + $umask = umask(); + $filesystem = new Filesystem(); + $filesystem->dumpFile($this->file, $content, null); + try { + $filesystem->chmod($this->file, $mode, $umask); + } catch (IOException $e) { + // discard chmod failure (some filesystem may not support it) + } + + if (null !== $metadata) { + $filesystem->dumpFile($this->getMetaFile(), serialize($metadata), null); + try { + $filesystem->chmod($this->getMetaFile(), $mode, $umask); + } catch (IOException $e) { + // discard chmod failure (some filesystem may not support it) + } + } + + if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) { + @opcache_invalidate($this->file, true); + } + } + + /** + * Gets the meta file path. + * + * @return string The meta file path + */ + private function getMetaFile() + { + return $this->file.'.meta'; + } +} diff --git a/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php b/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php new file mode 100644 index 0000000..586c5bf --- /dev/null +++ b/vendor/symfony/config/ResourceCheckerConfigCacheFactory.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +/** + * A ConfigCacheFactory implementation that validates the + * cache with an arbitrary set of ResourceCheckers. + * + * @author Matthias Pigulla + */ +class ResourceCheckerConfigCacheFactory implements ConfigCacheFactoryInterface +{ + /** + * @var ResourceCheckerInterface[] + */ + private $resourceCheckers = array(); + + /** + * @param ResourceCheckerInterface[] $resourceCheckers + */ + public function __construct(array $resourceCheckers = array()) + { + $this->resourceCheckers = $resourceCheckers; + } + + /** + * {@inheritdoc} + */ + public function cache($file, $callback) + { + if (!\is_callable($callback)) { + throw new \InvalidArgumentException(sprintf('Invalid type for callback argument. Expected callable, but got "%s".', \gettype($callback))); + } + + $cache = new ResourceCheckerConfigCache($file, $this->resourceCheckers); + if (!$cache->isFresh()) { + \call_user_func($callback, $cache); + } + + return $cache; + } +} diff --git a/vendor/symfony/config/ResourceCheckerInterface.php b/vendor/symfony/config/ResourceCheckerInterface.php new file mode 100644 index 0000000..612d777 --- /dev/null +++ b/vendor/symfony/config/ResourceCheckerInterface.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Interface for ResourceCheckers. + * + * When a ResourceCheckerConfigCache instance is checked for freshness, all its associated + * metadata resources are passed to ResourceCheckers. The ResourceCheckers + * can then inspect the resources and decide whether the cache can be considered + * fresh or not. + * + * @author Matthias Pigulla + * @author Benjamin Klotz + */ +interface ResourceCheckerInterface +{ + /** + * Queries the ResourceChecker whether it can validate a given + * resource or not. + * + * @param ResourceInterface $metadata The resource to be checked for freshness + * + * @return bool True if the ResourceChecker can handle this resource type, false if not + */ + public function supports(ResourceInterface $metadata); + + /** + * Validates the resource. + * + * @param ResourceInterface $resource The resource to be validated + * @param int $timestamp The timestamp at which the cache associated with this resource was created + * + * @return bool True if the resource has not changed since the given timestamp, false otherwise + */ + public function isFresh(ResourceInterface $resource, $timestamp); +} diff --git a/vendor/symfony/config/Util/XmlUtils.php b/vendor/symfony/config/Util/XmlUtils.php new file mode 100644 index 0000000..5ecbd2e --- /dev/null +++ b/vendor/symfony/config/Util/XmlUtils.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Config\Util; + +/** + * XMLUtils is a bunch of utility methods to XML operations. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Fabien Potencier + * @author Martin Hasoň + */ +class XmlUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * @param string|callable|null $schemaOrCallable An XSD schema file path, a callable, or null to disable validation + * + * @return \DOMDocument + * + * @throws \InvalidArgumentException When loading of XML file returns error + * @throws \RuntimeException When DOM extension is missing + */ + public static function loadFile($file, $schemaOrCallable = null) + { + if (!\extension_loaded('dom')) { + throw new \RuntimeException('Extension DOM is required.'); + } + + $content = @file_get_contents($file); + if ('' === trim($content)) { + throw new \InvalidArgumentException(sprintf('File %s does not contain valid XML, it is empty.', $file)); + } + + $internalErrors = libxml_use_internal_errors(true); + $disableEntities = libxml_disable_entity_loader(true); + libxml_clear_errors(); + + $dom = new \DOMDocument(); + $dom->validateOnParse = true; + if (!$dom->loadXML($content, LIBXML_NONET | (\defined('LIBXML_COMPACT') ? LIBXML_COMPACT : 0))) { + libxml_disable_entity_loader($disableEntities); + + throw new \InvalidArgumentException(implode("\n", static::getXmlErrors($internalErrors))); + } + + $dom->normalizeDocument(); + + libxml_use_internal_errors($internalErrors); + libxml_disable_entity_loader($disableEntities); + + foreach ($dom->childNodes as $child) { + if (XML_DOCUMENT_TYPE_NODE === $child->nodeType) { + throw new \InvalidArgumentException('Document types are not allowed.'); + } + } + + if (null !== $schemaOrCallable) { + $internalErrors = libxml_use_internal_errors(true); + libxml_clear_errors(); + + $e = null; + if (\is_callable($schemaOrCallable)) { + try { + $valid = \call_user_func($schemaOrCallable, $dom, $internalErrors); + } catch (\Exception $e) { + $valid = false; + } + } elseif (!\is_array($schemaOrCallable) && is_file((string) $schemaOrCallable)) { + $schemaSource = file_get_contents((string) $schemaOrCallable); + $valid = @$dom->schemaValidateSource($schemaSource); + } else { + libxml_use_internal_errors($internalErrors); + + throw new \InvalidArgumentException('The schemaOrCallable argument has to be a valid path to XSD file or callable.'); + } + + if (!$valid) { + $messages = static::getXmlErrors($internalErrors); + if (empty($messages)) { + $messages = array(sprintf('The XML file "%s" is not valid.', $file)); + } + throw new \InvalidArgumentException(implode("\n", $messages), 0, $e); + } + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $dom; + } + + /** + * Converts a \DOMElement object to a PHP array. + * + * The following rules applies during the conversion: + * + * * Each tag is converted to a key value or an array + * if there is more than one "value" + * + * * The content of a tag is set under a "value" key (bar) + * if the tag also has some nested tags + * + * * The attributes are converted to keys () + * + * * The nested-tags are converted to keys (bar) + * + * @param \DOMElement $element A \DOMElement instance + * @param bool $checkPrefix Check prefix in an element or an attribute name + * + * @return array A PHP array + */ + public static function convertDomElementToArray(\DOMElement $element, $checkPrefix = true) + { + $prefix = (string) $element->prefix; + $empty = true; + $config = array(); + foreach ($element->attributes as $name => $node) { + if ($checkPrefix && !\in_array((string) $node->prefix, array('', $prefix), true)) { + continue; + } + $config[$name] = static::phpize($node->value); + $empty = false; + } + + $nodeValue = false; + foreach ($element->childNodes as $node) { + if ($node instanceof \DOMText) { + if ('' !== trim($node->nodeValue)) { + $nodeValue = trim($node->nodeValue); + $empty = false; + } + } elseif ($checkPrefix && $prefix != (string) $node->prefix) { + continue; + } elseif (!$node instanceof \DOMComment) { + $value = static::convertDomElementToArray($node, $checkPrefix); + + $key = $node->localName; + if (isset($config[$key])) { + if (!\is_array($config[$key]) || !\is_int(key($config[$key]))) { + $config[$key] = array($config[$key]); + } + $config[$key][] = $value; + } else { + $config[$key] = $value; + } + + $empty = false; + } + } + + if (false !== $nodeValue) { + $value = static::phpize($nodeValue); + if (\count($config)) { + $config['value'] = $value; + } else { + $config = $value; + } + } + + return !$empty ? $config : null; + } + + /** + * Converts an xml value to a PHP type. + * + * @param mixed $value + * + * @return mixed + */ + public static function phpize($value) + { + $value = (string) $value; + $lowercaseValue = strtolower($value); + + switch (true) { + case 'null' === $lowercaseValue: + return; + case ctype_digit($value): + $raw = $value; + $cast = (int) $value; + + return '0' == $value[0] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw); + case isset($value[1]) && '-' === $value[0] && ctype_digit(substr($value, 1)): + $raw = $value; + $cast = (int) $value; + + return '0' == $value[1] ? octdec($value) : (((string) $raw === (string) $cast) ? $cast : $raw); + case 'true' === $lowercaseValue: + return true; + case 'false' === $lowercaseValue: + return false; + case isset($value[1]) && '0b' == $value[0].$value[1]: + return bindec($value); + case is_numeric($value): + return '0x' === $value[0].$value[1] ? hexdec($value) : (float) $value; + case preg_match('/^0x[0-9a-f]++$/i', $value): + return hexdec($value); + case preg_match('/^(-|\+)?[0-9]+(\.[0-9]+)?$/', $value): + return (float) $value; + default: + return $value; + } + } + + protected static function getXmlErrors($internalErrors) + { + $errors = array(); + foreach (libxml_get_errors() as $error) { + $errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)', + LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', + $error->code, + trim($error->message), + $error->file ?: 'n/a', + $error->line, + $error->column + ); + } + + libxml_clear_errors(); + libxml_use_internal_errors($internalErrors); + + return $errors; + } +} diff --git a/vendor/symfony/config/composer.json b/vendor/symfony/config/composer.json new file mode 100644 index 0000000..ee1322c --- /dev/null +++ b/vendor/symfony/config/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/config", + "type": "library", + "description": "Symfony Config Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/filesystem": "~2.3|~3.0.0", + "symfony/polyfill-ctype": "~1.8" + }, + "require-dev": { + "symfony/yaml": "~2.7|~3.0.0" + }, + "suggest": { + "symfony/yaml": "To use the yaml reference dumper" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Config\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/console/Application.php b/vendor/symfony/console/Application.php new file mode 100644 index 0000000..fe62e8d --- /dev/null +++ b/vendor/symfony/console/Application.php @@ -0,0 +1,1164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Command\HelpCommand; +use Symfony\Component\Console\Command\ListCommand; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Event\ConsoleCommandEvent; +use Symfony\Component\Console\Event\ConsoleExceptionEvent; +use Symfony\Component\Console\Event\ConsoleTerminateEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\DebugFormatterHelper; +use Symfony\Component\Console\Helper\DialogHelper; +use Symfony\Component\Console\Helper\FormatterHelper; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Helper\ProcessHelper; +use Symfony\Component\Console\Helper\ProgressHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Helper\TableHelper; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Debug\Exception\FatalThrowableError; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * An Application is the container for a collection of commands. + * + * It is the main entry point of a Console application. + * + * This class is optimized for a standard CLI environment. + * + * Usage: + * + * $app = new Application('myapp', '1.0 (stable)'); + * $app->add(new SimpleCommand()); + * $app->run(); + * + * @author Fabien Potencier + */ +class Application +{ + private $commands = array(); + private $wantHelps = false; + private $runningCommand; + private $name; + private $version; + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $helperSet; + private $dispatcher; + private $terminalDimensions; + private $defaultCommand; + private $initialized; + + /** + * @param string $name The name of the application + * @param string $version The version of the application + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') + { + $this->name = $name; + $this->version = $version; + $this->defaultCommand = 'list'; + } + + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + * + * @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}. + */ + public function run(InputInterface $input = null, OutputInterface $output = null) + { + if (null === $input) { + $input = new ArgvInput(); + } + + if (null === $output) { + $output = new ConsoleOutput(); + } + + $this->configureIO($input, $output); + + try { + $e = null; + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + } + + if (null !== $e) { + if (!$this->catchExceptions) { + throw $e; + } + + if ($output instanceof ConsoleOutputInterface) { + $this->renderException($e, $output->getErrorOutput()); + } else { + $this->renderException($e, $output); + } + + $exitCode = $this->getExitCodeForThrowable($e); + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * Runs the current application. + * + * @return int 0 if everything went fine, or an error code + */ + public function doRun(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--version', '-V'))) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + if (true === $input->hasParameterOption(array('--help', '-h'))) { + if (!$name) { + $name = 'help'; + $input = new ArrayInput(array('command' => 'help')); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $definition = $this->getDefinition(); + $definition->setArguments(array_merge( + $definition->getArguments(), + array( + 'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name), + ) + )); + } + + $this->runningCommand = null; + // the command name MUST be the first element of the input + $command = $this->find($name); + + $this->runningCommand = $command; + $exitCode = $this->doRunCommand($command, $input, $output); + $this->runningCommand = null; + + return $exitCode; + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Get the helper set associated with the command. + * + * @return HelperSet The HelperSet instance associated with this command + */ + public function getHelperSet() + { + if (!$this->helperSet) { + $this->helperSet = $this->getDefaultHelperSet(); + } + + return $this->helperSet; + } + + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * Gets the InputDefinition related to this Application. + * + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + if (!$this->definition) { + $this->definition = $this->getDefaultInputDefinition(); + } + + return $this->definition; + } + + /** + * Gets the help message. + * + * @return string A help message + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * Sets whether to catch exceptions or not during commands execution. + * + * @param bool $boolean Whether to catch exceptions or not during commands execution + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + } + + /** + * Sets whether to automatically exit after a command execution or not. + * + * @param bool $boolean Whether to automatically exit after a command execution or not + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + } + + /** + * Gets the name of the application. + * + * @return string The application name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the application name. + * + * @param string $name The application name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Gets the application version. + * + * @return string The application version + */ + public function getVersion() + { + return $this->version; + } + + /** + * Sets the application version. + * + * @param string $version The application version + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * Returns the long version of the application. + * + * @return string The long application version + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName()) { + if ('UNKNOWN' !== $this->getVersion()) { + return sprintf('%s version %s', $this->getName(), $this->getVersion()); + } + + return sprintf('%s', $this->getName()); + } + + return 'Console Tool'; + } + + /** + * Registers a new command. + * + * @param string $name The command name + * + * @return Command The newly created command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * Adds an array of command objects. + * + * If a Command is not enabled it will not be added. + * + * @param Command[] $commands An array of commands + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) { + $this->add($command); + } + } + + /** + * Adds a command object. + * + * If a command with the same name already exists, it will be overridden. + * If the command is not enabled it will not be added. + * + * @return Command|null The registered command if enabled or null + */ + public function add(Command $command) + { + $this->init(); + + $command->setApplication($this); + + if (!$command->isEnabled()) { + $command->setApplication(null); + + return; + } + + if (null === $command->getDefinition()) { + throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', \get_class($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * Returns a registered command by name or alias. + * + * @param string $name The command name or alias + * + * @return Command A Command object + * + * @throws CommandNotFoundException When given command name does not exist + */ + public function get($name) + { + $this->init(); + + if (!isset($this->commands[$name])) { + throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * Returns true if the command exists, false otherwise. + * + * @param string $name The command name or alias + * + * @return bool true if the command exists, false otherwise + */ + public function has($name) + { + $this->init(); + + return isset($this->commands[$name]); + } + + /** + * Returns an array of all unique namespaces used by currently registered commands. + * + * It does not return the global namespace which always exists. + * + * @return string[] An array of namespaces + */ + public function getNamespaces() + { + $namespaces = array(); + foreach ($this->all() as $command) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * Finds a registered namespace by a name or an abbreviation. + * + * @param string $namespace A namespace or abbreviation to search for + * + * @return string A registered namespace + * + * @throws CommandNotFoundException When namespace is incorrect or ambiguous + */ + public function findNamespace($namespace) + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); + $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new CommandNotFoundException($message, $alternatives); + } + + $exact = \in_array($namespace, $namespaces, true); + if (\count($namespaces) > 1 && !$exact) { + throw new CommandNotFoundException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces)); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * Finds a command by name or alias. + * + * Contrary to get, this command tries to find the best + * match if you give it an abbreviation of a name or alias. + * + * @param string $name A command name or a command alias + * + * @return Command A Command instance + * + * @throws CommandNotFoundException When command name is incorrect or ambiguous + */ + public function find($name) + { + $this->init(); + $aliases = array(); + $allCommands = array_keys($this->commands); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); + $commands = preg_grep('{^'.$expr.'}', $allCommands); + + if (empty($commands) || \count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + // check if a namespace exists and contains commands + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == \count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new CommandNotFoundException($message, $alternatives); + } + + // filter out aliases for commands which are already on the list + if (\count($commands) > 1) { + $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) { + $commandName = $commandList[$nameOrAlias]->getName(); + $aliases[$nameOrAlias] = $commandName; + + return $commandName === $nameOrAlias || !\in_array($commandName, $commands); + }); + } + + $exact = \in_array($name, $commands, true) || isset($aliases[$name]); + if (!$exact && \count($commands) > 1) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new CommandNotFoundException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions), array_values($commands)); + } + + return $this->get($exact ? $name : reset($commands)); + } + + /** + * Gets the commands (registered in the given namespace if provided). + * + * The array keys are the full names and the values the command instances. + * + * @param string $namespace A namespace name + * + * @return Command[] An array of Command instances + */ + public function all($namespace = null) + { + $this->init(); + + if (null === $namespace) { + return $this->commands; + } + + $commands = array(); + foreach ($this->commands as $name => $command) { + if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * Returns an array of possible abbreviations given a set of names. + * + * @param array $names An array of names + * + * @return array An array of abbreviations + */ + public static function getAbbreviations($names) + { + $abbrevs = array(); + foreach ($names as $name) { + for ($len = \strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * Returns a text representation of the Application. + * + * @param string $namespace An optional namespace name + * @param bool $raw Whether to return raw command list + * + * @return string A string representing the Application + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asText($namespace = null, $raw = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw); + $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the Application. + * + * @param string $namespace An optional namespace name + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the Application + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($namespace = null, $asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getApplicationDocument($this, $namespace); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this, array('namespace' => $namespace)); + + return $output->fetch(); + } + + /** + * Renders a caught exception. + */ + public function renderException($e, $output) + { + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + + do { + $title = sprintf(' [%s] ', \get_class($e)); + + $len = Helper::strlen($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 + if (\defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = array(); + foreach (preg_split('/\r?\n/', trim($e->getMessage())) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + // pre-format lines to get the right string length + $lineLength = Helper::strlen($line) + 4; + $lines[] = array($line, $lineLength); + + $len = max($lineLength, $len); + } + } + + $messages = array(); + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - Helper::strlen($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + + $output->writeln($messages, OutputInterface::VERBOSITY_QUIET); + + if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { + $output->writeln('Exception trace:', OutputInterface::VERBOSITY_QUIET); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, array( + 'function' => '', + 'file' => null !== $e->getFile() ? $e->getFile() : 'n/a', + 'line' => null !== $e->getLine() ? $e->getLine() : 'n/a', + 'args' => array(), + )); + + for ($i = 0, $count = \count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), OutputInterface::VERBOSITY_QUIET); + } + + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } while ($e = $e->getPrevious()); + + if (null !== $this->runningCommand) { + $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET); + $output->writeln('', OutputInterface::VERBOSITY_QUIET); + } + } + + /** + * Tries to figure out the terminal width in which this application runs. + * + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * Tries to figure out the terminal height in which this application runs. + * + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * Tries to figure out the terminal dimensions based on the current environment. + * + * @return array Array containing width and height + */ + public function getTerminalDimensions() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + // extract [w, H] from "wxh (WxH)" + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return array((int) $matches[1], (int) $matches[2]); + } + // extract [w, h] from "wxh" + if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) { + return array((int) $matches[1], (int) $matches[2]); + } + } + + if ($sttyString = $this->getSttyColumns()) { + // extract [w, h] from "rows h; columns w;" + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return array((int) $matches[2], (int) $matches[1]); + } + // extract [w, h] from "; h rows; w columns" + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return array((int) $matches[2], (int) $matches[1]); + } + } + + return array(null, null); + } + + /** + * Sets terminal dimensions. + * + * Can be useful to force terminal dimensions for functional tests. + * + * @param int $width The width + * @param int $height The height + * + * @return $this + */ + public function setTerminalDimensions($width, $height) + { + $this->terminalDimensions = array($width, $height); + + return $this; + } + + /** + * Configures the input and output instances based on the user arguments and options. + */ + protected function configureIO(InputInterface $input, OutputInterface $output) + { + if (true === $input->hasParameterOption(array('--ansi'))) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(array('--no-ansi'))) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) { + $input->setInteractive(false); + } elseif (\function_exists('posix_isatty') && $this->getHelperSet()->has('question')) { + $inputStream = $this->getHelperSet()->get('question')->getInputStream(); + if (!@posix_isatty($inputStream) && false === getenv('SHELL_INTERACTIVE')) { + $input->setInteractive(false); + } + } + + if (true === $input->hasParameterOption(array('--quiet', '-q'))) { + $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); + $input->setInteractive(false); + } else { + if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || 3 === $input->getParameterOption('--verbose')) { + $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || 2 === $input->getParameterOption('--verbose')) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); + } + } + } + + /** + * Runs the current command. + * + * If an event dispatcher has been attached to the application, + * events are also dispatched during the life-cycle of the command. + * + * @return int 0 if everything went fine, or an error code + */ + protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) + { + foreach ($command->getHelperSet() as $helper) { + if ($helper instanceof InputAwareInterface) { + $helper->setInput($input); + } + } + + if (null === $this->dispatcher) { + return $command->run($input, $output); + } + + // bind before the console.command event, so the listeners have access to input options/arguments + try { + $command->mergeApplicationDefinition(); + $input->bind($command->getDefinition()); + } catch (ExceptionInterface $e) { + // ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition + } + + $event = new ConsoleCommandEvent($command, $input, $output); + $e = null; + + try { + $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); + + if ($event->commandShouldRun()) { + $exitCode = $command->run($input, $output); + } else { + $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; + } + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + if (null !== $e) { + $x = $e instanceof \Exception ? $e : new FatalThrowableError($e); + $event = new ConsoleExceptionEvent($command, $input, $output, $x, $x->getCode()); + $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); + + if ($x !== $event->getException()) { + $e = $event->getException(); + } + + $exitCode = $this->getExitCodeForThrowable($e); + } + + $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); + $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); + + if (null !== $e) { + throw $e; + } + + return $event->getExitCode(); + } + + /** + * Gets the name of the command based on input. + * + * @return string The command name + */ + protected function getCommandName(InputInterface $input) + { + return $input->getFirstArgument(); + } + + /** + * Gets the default input definition. + * + * @return InputDefinition An InputDefinition instance + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition(array( + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + )); + } + + /** + * Gets the default commands that should always be available. + * + * @return Command[] An array of default Command instances + */ + protected function getDefaultCommands() + { + return array(new HelpCommand(), new ListCommand()); + } + + /** + * Gets the default helper set with the helpers that should always be available. + * + * @return HelperSet A HelperSet instance + */ + protected function getDefaultHelperSet() + { + return new HelperSet(array( + new FormatterHelper(), + new DialogHelper(false), + new ProgressHelper(false), + new TableHelper(false), + new DebugFormatterHelper(), + new ProcessHelper(), + new QuestionHelper(), + )); + } + + /** + * Runs and parses stty -a if it's available, suppressing any error output. + * + * @return string + */ + private function getSttyColumns() + { + if (!\function_exists('proc_open')) { + return; + } + + $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); + if (\is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + } + + /** + * Runs and parses mode CON if it's available, suppressing any error output. + * + * @return string|null x or null if it could not be parsed + */ + private function getConsoleMode() + { + if (!\function_exists('proc_open')) { + return; + } + + $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); + if (\is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2].'x'.$matches[1]; + } + } + } + + /** + * Returns abbreviated suggestions in string format. + * + * @param array $abbrevs Abbreviated suggestions to convert + * + * @return string A formatted string of abbreviated suggestions + */ + private function getAbbreviationSuggestions($abbrevs) + { + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], \count($abbrevs) > 2 ? sprintf(' and %d more', \count($abbrevs) - 2) : ''); + } + + /** + * Returns the namespace part of the command name. + * + * This method is not part of public API and should not be used directly. + * + * @param string $name The full name of the command + * @param string $limit The maximum number of parts of the namespace + * + * @return string The namespace of the command + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit)); + } + + /** + * Finds alternative of $name among $collection, + * if nothing is found in $collection, try in $abbrevs. + * + * @param string $name The string + * @param iterable $collection The collection + * + * @return string[] A sorted array of similar string + */ + private function findAlternatives($name, $collection) + { + $threshold = 1e3; + $alternatives = array(); + + $collectionParts = array(); + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= \strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; }); + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * Sets the default Command name. + * + * @param string $commandName The Command name + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + } + + private function splitStringByWidth($string, $width) + { + // str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly. + // additionally, array_slice() is not enough as some character has doubled width. + // we need a function to split string not by character count but by string width + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = array(); + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + // test if $char could be appended to current line + if (mb_strwidth($line.$char, 'utf8') <= $width) { + $line .= $char; + continue; + } + // if not, push current line to array and make new line + $lines[] = str_pad($line, $width); + $line = $char; + } + + $lines[] = \count($lines) ? str_pad($line, $width) : $line; + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + /** + * Returns all namespaces of the command name. + * + * @param string $name The full name of the command + * + * @return string[] The namespaces of the command + */ + private function extractAllNamespaces($name) + { + // -1 as third argument is needed to skip the command short name when exploding + $parts = explode(':', $name, -1); + $namespaces = array(); + + foreach ($parts as $part) { + if (\count($namespaces)) { + $namespaces[] = end($namespaces).':'.$part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + + private function init() + { + if ($this->initialized) { + return; + } + $this->initialized = true; + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + + /** + * @param \Exception|\Throwable $throwable + * + * @return int + */ + private function getExitCodeForThrowable($throwable) + { + $exitCode = $throwable->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + + return $exitCode; + } +} diff --git a/vendor/symfony/console/Command/Command.php b/vendor/symfony/console/Command/Command.php new file mode 100644 index 0000000..75d062d --- /dev/null +++ b/vendor/symfony/console/Command/Command.php @@ -0,0 +1,680 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Exception\ExceptionInterface; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Base class for all commands. + * + * @author Fabien Potencier + */ +class Command +{ + private $application; + private $name; + private $processTitle; + private $aliases = array(); + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $applicationDefinitionMerged = false; + private $applicationDefinitionMergedWithArgs = false; + private $code; + private $synopsis = array(); + private $usages = array(); + private $helperSet; + + /** + * @param string|null $name The name of the command; passing null means it must be set in configure() + * + * @throws LogicException When the command name is empty + */ + public function __construct($name = null) + { + $this->definition = new InputDefinition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', \get_class($this))); + } + } + + /** + * Ignores validation errors. + * + * This is mainly useful for the help command. + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + public function setApplication(Application $application = null) + { + $this->application = $application; + if ($application) { + $this->setHelperSet($application->getHelperSet()); + } else { + $this->helperSet = null; + } + } + + public function setHelperSet(HelperSet $helperSet) + { + $this->helperSet = $helperSet; + } + + /** + * Gets the helper set. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Gets the application instance for this command. + * + * @return Application An Application instance + */ + public function getApplication() + { + return $this->application; + } + + /** + * Checks whether the command is enabled or not in the current environment. + * + * Override this to check for x or y and return false if the command can not + * run properly under the current conditions. + * + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * Configures the current command. + */ + protected function configure() + { + } + + /** + * Executes the current command. + * + * This method is not abstract because you can use this class + * as a concrete class. In this case, instead of defining the + * execute() method, you set the code to execute by passing + * a Closure to the setCode() method. + * + * @return int|null null or 0 if everything went fine, or an error code + * + * @throws LogicException When this abstract method is not implemented + * + * @see setCode() + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + throw new LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * Interacts with the user. + * + * This method is executed before the InputDefinition is validated. + * This means that this is the only place where the command can + * interactively ask for values of missing required arguments. + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + } + + /** + * Initializes the command after the input has been bound and before the input + * is validated. + * + * This is mainly useful when a lot of commands extends one main command + * where some things need to be initialized based on the input arguments and options. + * + * @see InputInterface::bind() + * @see InputInterface::validate() + */ + protected function initialize(InputInterface $input, OutputInterface $output) + { + } + + /** + * Runs the command. + * + * The code to execute is either defined directly with the + * setCode() method or by overriding the execute() method + * in a sub-class. + * + * @return int The command exit code + * + * @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}. + * + * @see setCode() + * @see execute() + */ + public function run(InputInterface $input, OutputInterface $output) + { + // force the creation of the synopsis before the merge with the app definition + $this->getSynopsis(true); + $this->getSynopsis(false); + + // add the application arguments and options + $this->mergeApplicationDefinition(); + + // bind the input against the command specific arguments/options + try { + $input->bind($this->definition); + } catch (ExceptionInterface $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if (null !== $this->processTitle) { + if (\function_exists('cli_set_process_title')) { + if (!@cli_set_process_title($this->processTitle)) { + if ('Darwin' === PHP_OS) { + $output->writeln('Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.', OutputInterface::VERBOSITY_VERY_VERBOSE); + } else { + cli_set_process_title($this->processTitle); + } + } + } elseif (\function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + // The command name argument is often omitted when a command is executed directly with its run() method. + // It would fail the validation if we didn't make sure the command argument is present, + // since it's required by the application. + if ($input->hasArgument('command') && null === $input->getArgument('command')) { + $input->setArgument('command', $this->getName()); + } + + $input->validate(); + + if ($this->code) { + $statusCode = \call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * Sets the code to execute when running this command. + * + * If this method is used, it overrides the code defined + * in the execute() method. + * + * @param callable $code A callable(InputInterface $input, OutputInterface $output) + * + * @return $this + * + * @throws InvalidArgumentException + * + * @see execute() + */ + public function setCode($code) + { + if (!\is_callable($code)) { + throw new InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + if (\PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + if (\PHP_VERSION_ID < 70000) { + // Bug in PHP5: https://bugs.php.net/bug.php?id=64761 + // This means that we cannot bind static closures and therefore we must + // ignore any errors here. There is no way to test if the closure is + // bindable. + $code = @\Closure::bind($code, $this); + } else { + $code = \Closure::bind($code, $this); + } + } + } + + $this->code = $code; + + return $this; + } + + /** + * Merges the application definition with the command definition. + * + * This method is not part of public API and should not be used directly. + * + * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments + */ + public function mergeApplicationDefinition($mergeArgs = true) + { + if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { + return; + } + + $this->definition->addOptions($this->application->getDefinition()->getOptions()); + + $this->applicationDefinitionMerged = true; + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->application->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + + $this->applicationDefinitionMergedWithArgs = true; + } + } + + /** + * Sets an array of argument and option instances. + * + * @param array|InputDefinition $definition An array of argument and option instances or a definition instance + * + * @return $this + */ + public function setDefinition($definition) + { + if ($definition instanceof InputDefinition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->applicationDefinitionMerged = false; + + return $this; + } + + /** + * Gets the InputDefinition attached to this Command. + * + * @return InputDefinition An InputDefinition instance + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * Gets the InputDefinition to be used to create XML and Text representations of this Command. + * + * Can be overridden to provide the original command representation when it would otherwise + * be changed by merging with the application InputDefinition. + * + * This method is not part of public API and should not be used directly. + * + * @return InputDefinition An InputDefinition instance + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * Adds an argument. + * + * @param string $name The argument name + * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param string|string[]|null $default The default value (for self::OPTIONAL mode only) + * + * @throws InvalidArgumentException When argument mode is not valid + * + * @return $this + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); + + return $this; + } + + /** + * Adds an option. + * + * @param string $name The option name + * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string $description A description text + * @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + * + * @return $this + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * Sets the name of the command. + * + * This method can set both the namespace and the name if + * you separate them by a colon (:) + * + * $command->setName('foo:bar'); + * + * @param string $name The command name + * + * @return $this + * + * @throws InvalidArgumentException When the name is invalid + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * Sets the process title of the command. + * + * This feature should be used only when creating a long process command, + * like a daemon. + * + * PHP 5.5+ or the proctitle PECL library is required + * + * @param string $title The process title + * + * @return $this + */ + public function setProcessTitle($title) + { + $this->processTitle = $title; + + return $this; + } + + /** + * Returns the command name. + * + * @return string The command name + */ + public function getName() + { + return $this->name; + } + + /** + * Sets the description for the command. + * + * @param string $description The description for the command + * + * @return $this + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * Returns the description for the command. + * + * @return string The description for the command + */ + public function getDescription() + { + return $this->description; + } + + /** + * Sets the help for the command. + * + * @param string $help The help for the command + * + * @return $this + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * Returns the help for the command. + * + * @return string The help for the command + */ + public function getHelp() + { + return $this->help; + } + + /** + * Returns the processed help for the command replacing the %command.name% and + * %command.full_name% patterns with the real values dynamically. + * + * @return string The processed help for the command + */ + public function getProcessedHelp() + { + $name = $this->name; + + $placeholders = array( + '%command.name%', + '%command.full_name%', + ); + $replacements = array( + $name, + $_SERVER['PHP_SELF'].' '.$name, + ); + + return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription()); + } + + /** + * Sets the aliases for the command. + * + * @param string[] $aliases An array of aliases for the command + * + * @return $this + * + * @throws InvalidArgumentException When an alias is invalid + */ + public function setAliases($aliases) + { + if (!\is_array($aliases) && !$aliases instanceof \Traversable) { + throw new InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * Returns the aliases for the command. + * + * @return array An array of aliases for the command + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * Returns the synopsis for the command. + * + * @param bool $short Whether to show the short version of the synopsis (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * Add a command usage example. + * + * @param string $usage The usage, it'll be prefixed with the command name + * + * @return $this + */ + public function addUsage($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * Returns alternative usages of the command. + * + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * Gets a helper instance by name. + * + * @param string $name The helper name + * + * @return mixed The helper value + * + * @throws LogicException if no HelperSet is defined + * @throws InvalidArgumentException if the helper is not defined + */ + public function getHelper($name) + { + if (null === $this->helperSet) { + throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name)); + } + + return $this->helperSet->get($name); + } + + /** + * Returns a text representation of the command. + * + * @return string A string representing the command + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $descriptor->describe($output, $this, array('raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the command. + * + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the command + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getCommandDocument($this); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this); + + return $output->fetch(); + } + + /** + * Validates a command name. + * + * It must be non-empty and parts can optionally be separated by ":". + * + * @param string $name + * + * @throws InvalidArgumentException When the name is invalid + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/vendor/symfony/console/Command/HelpCommand.php b/vendor/symfony/console/Command/HelpCommand.php new file mode 100644 index 0000000..83e1a7a --- /dev/null +++ b/vendor/symfony/console/Command/HelpCommand.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * HelpCommand displays the help for a given command. + * + * @author Fabien Potencier + */ +class HelpCommand extends Command +{ + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this + ->setName('help') + ->setDefinition(array( + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + )) + ->setDescription('Displays help for a command') + ->setHelp(<<<'EOF' +The %command.name% command displays help for a given command: + + php %command.full_name% list + +You can also output the help in other formats by using the --format option: + + php %command.full_name% --format=xml list + +To display the list of available commands, please use the list command. +EOF + ) + ; + } + + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if (null === $this->command) { + $this->command = $this->getApplication()->find($input->getArgument('command_name')); + } + + if ($input->getOption('xml')) { + @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); + + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->command, array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + )); + + $this->command = null; + } +} diff --git a/vendor/symfony/console/Command/ListCommand.php b/vendor/symfony/console/Command/ListCommand.php new file mode 100644 index 0000000..db40c73 --- /dev/null +++ b/vendor/symfony/console/Command/ListCommand.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Command; + +use Symfony\Component\Console\Helper\DescriptorHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * ListCommand displays the list of all available commands for the application. + * + * @author Fabien Potencier + */ +class ListCommand extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this + ->setName('list') + ->setDefinition($this->createDefinition()) + ->setDescription('Lists commands') + ->setHelp(<<<'EOF' +The %command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +You can also output the information in other formats by using the --format option: + + php %command.full_name% --format=xml + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ) + ; + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + if ($input->getOption('xml')) { + @trigger_error('The --xml option was deprecated in version 2.7 and will be removed in version 3.0. Use the --format option instead.', E_USER_DEPRECATED); + + $input->setOption('format', 'xml'); + } + + $helper = new DescriptorHelper(); + $helper->describe($output, $this->getApplication(), array( + 'format' => $input->getOption('format'), + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + )); + } + + /** + * {@inheritdoc} + */ + private function createDefinition() + { + return new InputDefinition(array( + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'), + )); + } +} diff --git a/vendor/symfony/console/ConsoleEvents.php b/vendor/symfony/console/ConsoleEvents.php new file mode 100644 index 0000000..6dae6ce --- /dev/null +++ b/vendor/symfony/console/ConsoleEvents.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +/** + * Contains all events dispatched by an Application. + * + * @author Francesco Levorato + */ +final class ConsoleEvents +{ + /** + * The COMMAND event allows you to attach listeners before any command is + * executed by the console. It also allows you to modify the command, input and output + * before they are handled to the command. + * + * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent + * instance. + * + * @Event + */ + const COMMAND = 'console.command'; + + /** + * The TERMINATE event allows you to attach listeners after a command is + * executed by the console. + * + * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent + * instance. + * + * @Event + */ + const TERMINATE = 'console.terminate'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears. + * + * This event allows you to deal with the exception or + * to modify the thrown exception. The event listener method receives + * a Symfony\Component\Console\Event\ConsoleExceptionEvent + * instance. + * + * @Event + */ + const EXCEPTION = 'console.exception'; +} diff --git a/vendor/symfony/console/Descriptor/ApplicationDescription.php b/vendor/symfony/console/Descriptor/ApplicationDescription.php new file mode 100644 index 0000000..7d18fe1 --- /dev/null +++ b/vendor/symfony/console/Descriptor/ApplicationDescription.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\CommandNotFoundException; + +/** + * @author Jean-François Simon + * + * @internal + */ +class ApplicationDescription +{ + const GLOBAL_NAMESPACE = '_global'; + + private $application; + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + public function __construct(Application $application, $namespace = null) + { + $this->application = $application; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces() + { + if (null === $this->namespaces) { + $this->inspectApplication(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectApplication(); + } + + return $this->commands; + } + + /** + * @param string $name + * + * @return Command + * + * @throws CommandNotFoundException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new CommandNotFoundException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectApplication() + { + $this->commands = array(); + $this->namespaces = array(); + + $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = array(); + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); + } + } + + /** + * @return array + */ + private function sortCommands(array $commands) + { + $namespacedCommands = array(); + $globalCommands = array(); + foreach ($commands as $name => $command) { + $key = $this->application->extractNamespace($name, 1); + if (!$key) { + $globalCommands['_global'][$name] = $command; + } else { + $namespacedCommands[$key][$name] = $command; + } + } + ksort($namespacedCommands); + $namespacedCommands = array_merge($globalCommands, $namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/vendor/symfony/console/Descriptor/Descriptor.php b/vendor/symfony/console/Descriptor/Descriptor.php new file mode 100644 index 0000000..eecf7fd --- /dev/null +++ b/vendor/symfony/console/Descriptor/Descriptor.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Jean-François Simon + * + * @internal + */ +abstract class Descriptor implements DescriptorInterface +{ + /** + * @var OutputInterface + */ + private $output; + + /** + * {@inheritdoc} + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Application: + $this->describeApplication($object, $options); + break; + default: + throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', \get_class($object))); + } + } + + /** + * Writes content to output. + * + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); + } + + /** + * Describes an InputArgument instance. + * + * @return string|mixed + */ + abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); + + /** + * Describes an InputOption instance. + * + * @return string|mixed + */ + abstract protected function describeInputOption(InputOption $option, array $options = array()); + + /** + * Describes an InputDefinition instance. + * + * @return string|mixed + */ + abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); + + /** + * Describes a Command instance. + * + * @return string|mixed + */ + abstract protected function describeCommand(Command $command, array $options = array()); + + /** + * Describes an Application instance. + * + * @return string|mixed + */ + abstract protected function describeApplication(Application $application, array $options = array()); +} diff --git a/vendor/symfony/console/Descriptor/DescriptorInterface.php b/vendor/symfony/console/Descriptor/DescriptorInterface.php new file mode 100644 index 0000000..5d3339a --- /dev/null +++ b/vendor/symfony/console/Descriptor/DescriptorInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Descriptor interface. + * + * @author Jean-François Simon + */ +interface DescriptorInterface +{ + /** + * Describes an object if supported. + * + * @param OutputInterface $output + * @param object $object + * @param array $options + */ + public function describe(OutputInterface $output, $object, array $options = array()); +} diff --git a/vendor/symfony/console/Descriptor/JsonDescriptor.php b/vendor/symfony/console/Descriptor/JsonDescriptor.php new file mode 100644 index 0000000..0a22274 --- /dev/null +++ b/vendor/symfony/console/Descriptor/JsonDescriptor.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * JSON descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class JsonDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->writeData($this->getInputArgumentData($argument), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->writeData($this->getInputOptionData($option), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $this->writeData($this->getInputDefinitionData($definition), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $this->writeData($this->getCommandData($command), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + $commands = array(); + + foreach ($description->getCommands() as $command) { + $commands[] = $this->getCommandData($command); + } + + $data = $describedNamespace + ? array('commands' => $commands, 'namespace' => $describedNamespace) + : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces())); + + $this->writeData($data, $options); + } + + /** + * Writes data as json. + * + * @return array|string + */ + private function writeData(array $data, array $options) + { + $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); + } + + /** + * @return array + */ + private function getInputArgumentData(InputArgument $argument) + { + return array( + 'name' => $argument->getName(), + 'is_required' => $argument->isRequired(), + 'is_array' => $argument->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()), + 'default' => INF === $argument->getDefault() ? 'INF' : $argument->getDefault(), + ); + } + + /** + * @return array + */ + private function getInputOptionData(InputOption $option) + { + return array( + 'name' => '--'.$option->getName(), + 'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '', + 'accept_value' => $option->acceptValue(), + 'is_value_required' => $option->isValueRequired(), + 'is_multiple' => $option->isArray(), + 'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()), + 'default' => INF === $option->getDefault() ? 'INF' : $option->getDefault(), + ); + } + + /** + * @return array + */ + private function getInputDefinitionData(InputDefinition $definition) + { + $inputArguments = array(); + foreach ($definition->getArguments() as $name => $argument) { + $inputArguments[$name] = $this->getInputArgumentData($argument); + } + + $inputOptions = array(); + foreach ($definition->getOptions() as $name => $option) { + $inputOptions[$name] = $this->getInputOptionData($option); + } + + return array('arguments' => $inputArguments, 'options' => $inputOptions); + } + + /** + * @return array + */ + private function getCommandData(Command $command) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + return array( + 'name' => $command->getName(), + 'usage' => array_merge(array($command->getSynopsis()), $command->getUsages(), $command->getAliases()), + 'description' => $command->getDescription(), + 'help' => $command->getProcessedHelp(), + 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), + ); + } +} diff --git a/vendor/symfony/console/Descriptor/MarkdownDescriptor.php b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php new file mode 100644 index 0000000..d3cac86 --- /dev/null +++ b/vendor/symfony/console/Descriptor/MarkdownDescriptor.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Markdown descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class MarkdownDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->write( + '**'.$argument->getName().':**'."\n\n" + .'* Name: '.($argument->getName() ?: '')."\n" + .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" + .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $argument->getDescription() ?: '')."\n" + .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->write( + '**'.$option->getName().':**'."\n\n" + .'* Name: `--'.$option->getName().'`'."\n" + .'* Shortcut: '.($option->getShortcut() ? '`-'.str_replace('|', '|-', $option->getShortcut()).'`' : '')."\n" + .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" + .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" + .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" + .'* Description: '.preg_replace('/\s*[\r\n]\s*/', "\n ", $option->getDescription() ?: '')."\n" + .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' + ); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + if ($showArguments = \count($definition->getArguments()) > 0) { + $this->write('### Arguments:'); + foreach ($definition->getArguments() as $argument) { + $this->write("\n\n"); + $this->write($this->describeInputArgument($argument)); + } + } + + if (\count($definition->getOptions()) > 0) { + if ($showArguments) { + $this->write("\n\n"); + } + + $this->write('### Options:'); + foreach ($definition->getOptions() as $option) { + $this->write("\n\n"); + $this->write($this->describeInputOption($option)); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $this->write( + $command->getName()."\n" + .str_repeat('-', Helper::strlen($command->getName()))."\n\n" + .'* Description: '.($command->getDescription() ?: '')."\n" + .'* Usage:'."\n\n" + .array_reduce(array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()), function ($carry, $usage) { + return $carry.' * `'.$usage.'`'."\n"; + }) + ); + + if ($help = $command->getProcessedHelp()) { + $this->write("\n"); + $this->write($help); + } + + if ($command->getNativeDefinition()) { + $this->write("\n\n"); + $this->describeInputDefinition($command->getNativeDefinition()); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + + $this->write($application->getName()."\n".str_repeat('=', Helper::strlen($application->getName()))); + + foreach ($description->getNamespaces() as $namespace) { + if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->write("\n\n"); + $this->write('**'.$namespace['id'].':**'); + } + + $this->write("\n\n"); + $this->write(implode("\n", array_map(function ($commandName) { + return '* '.$commandName; + }, $namespace['commands']))); + } + + foreach ($description->getCommands() as $command) { + $this->write("\n\n"); + $this->write($this->describeCommand($command)); + } + } +} diff --git a/vendor/symfony/console/Descriptor/TextDescriptor.php b/vendor/symfony/console/Descriptor/TextDescriptor.php new file mode 100644 index 0000000..671cb86 --- /dev/null +++ b/vendor/symfony/console/Descriptor/TextDescriptor.php @@ -0,0 +1,303 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * Text descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class TextDescriptor extends Descriptor +{ + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : Helper::strlen($argument->getName()); + $spacingWidth = $totalWidth - \strlen($argument->getName()); + + $this->writeText(sprintf(' %s %s%s%s', + $argument->getName(), + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()), + $default + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '='.strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '['.$value.']'; + } + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions(array($option)); + $synopsis = sprintf('%s%s', + $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', + sprintf('--%s%s', $option->getName(), $value) + ); + + $spacingWidth = $totalWidth - Helper::strlen($synopsis); + + $this->writeText(sprintf(' %s %s%s%s%s', + $synopsis, + str_repeat(' ', $spacingWidth), + // + 4 = 2 spaces before , 2 spaces after + preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()), + $default, + $option->isArray() ? ' (multiple values allowed)' : '' + ), $options); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, Helper::strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, array('total_width' => $totalWidth))); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = array(); + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (\strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, array('total_width' => $totalWidth))); + } + } + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeApplicationDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge(array($command->getSynopsis(true)), $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' '.OutputFormatter::escape($usage), $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' '.str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ApplicationDescription($application, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $application->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $width = $this->getColumnWidth($description->getCommands()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' '.$namespace['id'].'', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - Helper::strlen($name); + $this->writeText(sprintf(' %s%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = array()) + { + $this->write( + isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, + isset($options['raw_output']) ? !$options['raw_output'] : true + ); + } + + /** + * Formats input option/argument default value. + * + * @param mixed $default + * + * @return string + */ + private function formatDefaultValue($default) + { + if (INF === $default) { + return 'INF'; + } + + if (\is_string($default)) { + $default = OutputFormatter::escape($default); + } elseif (\is_array($default)) { + foreach ($default as $key => $value) { + if (\is_string($value)) { + $default[$key] = OutputFormatter::escape($value); + } + } + } + + if (\PHP_VERSION_ID < 50400) { + return str_replace(array('\/', '\\\\'), array('/', '\\'), json_encode($default)); + } + + return str_replace('\\\\', '\\', json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + } + + /** + * @param Command[] $commands + * + * @return int + */ + private function getColumnWidth(array $commands) + { + $widths = array(); + + foreach ($commands as $command) { + $widths[] = Helper::strlen($command->getName()); + foreach ($command->getAliases() as $alias) { + $widths[] = Helper::strlen($alias); + } + } + + return max($widths) + 2; + } + + /** + * @param InputOption[] $options + * + * @return int + */ + private function calculateTotalWidthForOptions(array $options) + { + $totalWidth = 0; + foreach ($options as $option) { + // "-" + shortcut + ", --" + name + $nameLength = 1 + max(\strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName()); + + if ($option->acceptValue()) { + $valueLength = 1 + Helper::strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/vendor/symfony/console/Descriptor/XmlDescriptor.php b/vendor/symfony/console/Descriptor/XmlDescriptor.php new file mode 100644 index 0000000..a0933f2 --- /dev/null +++ b/vendor/symfony/console/Descriptor/XmlDescriptor.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Descriptor; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputOption; + +/** + * XML descriptor. + * + * @author Jean-François Simon + * + * @internal + */ +class XmlDescriptor extends Descriptor +{ + /** + * @return \DOMDocument + */ + public function getInputDefinitionDocument(InputDefinition $definition) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($definitionXML = $dom->createElement('definition')); + + $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); + foreach ($definition->getArguments() as $argument) { + $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); + } + + $definitionXML->appendChild($optionsXML = $dom->createElement('options')); + foreach ($definition->getOptions() as $option) { + $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); + } + + return $dom; + } + + /** + * @return \DOMDocument + */ + public function getCommandDocument(Command $command) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($commandXML = $dom->createElement('command')); + + $command->getSynopsis(); + $command->mergeApplicationDefinition(false); + + $commandXML->setAttribute('id', $command->getName()); + $commandXML->setAttribute('name', $command->getName()); + + $commandXML->appendChild($usagesXML = $dom->createElement('usages')); + + foreach (array_merge(array($command->getSynopsis()), $command->getAliases(), $command->getUsages()) as $usage) { + $usagesXML->appendChild($dom->createElement('usage', $usage)); + } + + $commandXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); + + $commandXML->appendChild($helpXML = $dom->createElement('help')); + $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); + + $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); + $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); + + return $dom; + } + + /** + * @param Application $application + * @param string|null $namespace + * + * @return \DOMDocument + */ + public function getApplicationDocument(Application $application, $namespace = null) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->appendChild($rootXml = $dom->createElement('symfony')); + + if ('UNKNOWN' !== $application->getName()) { + $rootXml->setAttribute('name', $application->getName()); + if ('UNKNOWN' !== $application->getVersion()) { + $rootXml->setAttribute('version', $application->getVersion()); + } + } + + $rootXml->appendChild($commandsXML = $dom->createElement('commands')); + + $description = new ApplicationDescription($application, $namespace); + + if ($namespace) { + $commandsXML->setAttribute('namespace', $namespace); + } + + foreach ($description->getCommands() as $command) { + $this->appendDocument($commandsXML, $this->getCommandDocument($command)); + } + + if (!$namespace) { + $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); + + foreach ($description->getNamespaces() as $namespaceDescription) { + $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); + $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); + + foreach ($namespaceDescription['commands'] as $name) { + $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); + $commandXML->appendChild($dom->createTextNode($name)); + } + } + } + + return $dom; + } + + /** + * {@inheritdoc} + */ + protected function describeInputArgument(InputArgument $argument, array $options = array()) + { + $this->writeDocument($this->getInputArgumentDocument($argument)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputOption(InputOption $option, array $options = array()) + { + $this->writeDocument($this->getInputOptionDocument($option)); + } + + /** + * {@inheritdoc} + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = array()) + { + $this->writeDocument($this->getInputDefinitionDocument($definition)); + } + + /** + * {@inheritdoc} + */ + protected function describeCommand(Command $command, array $options = array()) + { + $this->writeDocument($this->getCommandDocument($command)); + } + + /** + * {@inheritdoc} + */ + protected function describeApplication(Application $application, array $options = array()) + { + $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); + } + + /** + * Appends document children to parent node. + */ + private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) + { + foreach ($importedParent->childNodes as $childNode) { + $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); + } + } + + /** + * Writes DOM document. + * + * @return \DOMDocument|string + */ + private function writeDocument(\DOMDocument $dom) + { + $dom->formatOutput = true; + $this->write($dom->saveXML()); + } + + /** + * @return \DOMDocument + */ + private function getInputArgumentDocument(InputArgument $argument) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('argument')); + $objectXML->setAttribute('name', $argument->getName()); + $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); + $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); + + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + $defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array())); + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + + return $dom; + } + + /** + * @return \DOMDocument + */ + private function getInputOptionDocument(InputOption $option) + { + $dom = new \DOMDocument('1.0', 'UTF-8'); + + $dom->appendChild($objectXML = $dom->createElement('option')); + $objectXML->setAttribute('name', '--'.$option->getName()); + $pos = strpos($option->getShortcut(), '|'); + if (false !== $pos) { + $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); + $objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut())); + } else { + $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); + } + $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); + $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); + $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); + $objectXML->appendChild($descriptionXML = $dom->createElement('description')); + $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); + + if ($option->acceptValue()) { + $defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array())); + $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); + + if (!empty($defaults)) { + foreach ($defaults as $default) { + $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); + $defaultXML->appendChild($dom->createTextNode($default)); + } + } + } + + return $dom; + } +} diff --git a/vendor/symfony/console/Event/ConsoleCommandEvent.php b/vendor/symfony/console/Event/ConsoleCommandEvent.php new file mode 100644 index 0000000..2f517c1 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleCommandEvent.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +/** + * Allows to do things before the command is executed, like skipping the command or changing the input. + * + * @author Fabien Potencier + */ +class ConsoleCommandEvent extends ConsoleEvent +{ + /** + * The return code for skipped commands, this will also be passed into the terminate event. + */ + const RETURN_CODE_DISABLED = 113; + + /** + * Indicates if the command should be run or skipped. + */ + private $commandShouldRun = true; + + /** + * Disables the command, so it won't be run. + * + * @return bool + */ + public function disableCommand() + { + return $this->commandShouldRun = false; + } + + /** + * Enables the command. + * + * @return bool + */ + public function enableCommand() + { + return $this->commandShouldRun = true; + } + + /** + * Returns true if the command is runnable, false otherwise. + * + * @return bool + */ + public function commandShouldRun() + { + return $this->commandShouldRun; + } +} diff --git a/vendor/symfony/console/Event/ConsoleEvent.php b/vendor/symfony/console/Event/ConsoleEvent.php new file mode 100644 index 0000000..ab620c4 --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\EventDispatcher\Event; + +/** + * Allows to inspect input and output of a command. + * + * @author Francesco Levorato + */ +class ConsoleEvent extends Event +{ + protected $command; + + private $input; + private $output; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output) + { + $this->command = $command; + $this->input = $input; + $this->output = $output; + } + + /** + * Gets the command that is executed. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * Gets the input instance. + * + * @return InputInterface An InputInterface instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance. + * + * @return OutputInterface An OutputInterface instance + */ + public function getOutput() + { + return $this->output; + } +} diff --git a/vendor/symfony/console/Event/ConsoleExceptionEvent.php b/vendor/symfony/console/Event/ConsoleExceptionEvent.php new file mode 100644 index 0000000..603b7ee --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleExceptionEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to handle exception thrown in a command. + * + * @author Fabien Potencier + */ +class ConsoleExceptionEvent extends ConsoleEvent +{ + private $exception; + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setException($exception); + $this->exitCode = (int) $exitCode; + } + + /** + * Returns the thrown exception. + * + * @return \Exception The thrown exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Event/ConsoleTerminateEvent.php b/vendor/symfony/console/Event/ConsoleTerminateEvent.php new file mode 100644 index 0000000..b6a5d7c --- /dev/null +++ b/vendor/symfony/console/Event/ConsoleTerminateEvent.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Event; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Allows to manipulate the exit code of a command after its execution. + * + * @author Francesco Levorato + */ +class ConsoleTerminateEvent extends ConsoleEvent +{ + /** + * The exit code of the command. + * + * @var int + */ + private $exitCode; + + public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode) + { + parent::__construct($command, $input, $output); + + $this->setExitCode($exitCode); + } + + /** + * Sets the exit code. + * + * @param int $exitCode The command exit code + */ + public function setExitCode($exitCode) + { + $this->exitCode = (int) $exitCode; + } + + /** + * Gets the exit code. + * + * @return int The command exit code + */ + public function getExitCode() + { + return $this->exitCode; + } +} diff --git a/vendor/symfony/console/Exception/CommandNotFoundException.php b/vendor/symfony/console/Exception/CommandNotFoundException.php new file mode 100644 index 0000000..54f1a5b --- /dev/null +++ b/vendor/symfony/console/Exception/CommandNotFoundException.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect command name typed in the console. + * + * @author Jérôme Tamarelle + */ +class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ + private $alternatives; + + /** + * @param string $message Exception message to throw + * @param array $alternatives List of similar defined names + * @param int $code Exception code + * @param Exception $previous previous exception used for the exception chaining + */ + public function __construct($message, array $alternatives = array(), $code = 0, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->alternatives = $alternatives; + } + + /** + * @return array A list of similar defined names + */ + public function getAlternatives() + { + return $this->alternatives; + } +} diff --git a/vendor/symfony/console/Exception/ExceptionInterface.php b/vendor/symfony/console/Exception/ExceptionInterface.php new file mode 100644 index 0000000..491cc4c --- /dev/null +++ b/vendor/symfony/console/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * ExceptionInterface. + * + * @author Jérôme Tamarelle + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/InvalidArgumentException.php b/vendor/symfony/console/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..07cc0b6 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/InvalidOptionException.php b/vendor/symfony/console/Exception/InvalidOptionException.php new file mode 100644 index 0000000..b2eec61 --- /dev/null +++ b/vendor/symfony/console/Exception/InvalidOptionException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * Represents an incorrect option name typed in the console. + * + * @author Jérôme Tamarelle + */ +class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/LogicException.php b/vendor/symfony/console/Exception/LogicException.php new file mode 100644 index 0000000..fc37b8d --- /dev/null +++ b/vendor/symfony/console/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Exception/RuntimeException.php b/vendor/symfony/console/Exception/RuntimeException.php new file mode 100644 index 0000000..51d7d80 --- /dev/null +++ b/vendor/symfony/console/Exception/RuntimeException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Exception; + +/** + * @author Jérôme Tamarelle + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/console/Formatter/OutputFormatter.php b/vendor/symfony/console/Formatter/OutputFormatter.php new file mode 100644 index 0000000..587f3d0 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatter.php @@ -0,0 +1,234 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Formatter class for console output. + * + * @author Konstantin Kudryashov + */ +class OutputFormatter implements OutputFormatterInterface +{ + private $decorated; + private $styles = array(); + private $styleStack; + + /** + * Escapes "<" special char in given text. + * + * @param string $text Text to escape + * + * @return string Escaped text + */ + public static function escape($text) + { + $text = preg_replace('/([^\\\\]?) FormatterStyle" instances + */ + public function __construct($decorated = false, array $styles = array()) + { + $this->decorated = (bool) $decorated; + + $this->setStyle('error', new OutputFormatterStyle('white', 'red')); + $this->setStyle('info', new OutputFormatterStyle('green')); + $this->setStyle('comment', new OutputFormatterStyle('yellow')); + $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); + + foreach ($styles as $name => $style) { + $this->setStyle($name, $style); + } + + $this->styleStack = new OutputFormatterStyleStack(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * {@inheritdoc} + */ + public function setStyle($name, OutputFormatterStyleInterface $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * {@inheritdoc} + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * {@inheritdoc} + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * {@inheritdoc} + */ + public function format($message) + { + $message = (string) $message; + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*+'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + // add the text up to the next tag + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + \strlen($text); + + // opening tag? + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + if (false !== strpos($output, "\0")) { + return strtr($output, array("\0" => '\\', '\\<' => '<')); + } + + return str_replace('\\<', '<', $output); + } + + /** + * @return OutputFormatterStyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * Tries to create new style instance from string. + * + * @param string $string + * + * @return OutputFormatterStyle|false false if string is not format string + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new OutputFormatterStyle(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + return $style; + } + + /** + * Applies current style from stack to text, if must be applied. + * + * @param string $text Input text + * + * @return string Styled text + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && \strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterInterface.php b/vendor/symfony/console/Formatter/OutputFormatterInterface.php new file mode 100644 index 0000000..281e240 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterInterface.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter interface for console output. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterInterface +{ + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages or not + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + /** + * Sets a new style. + * + * @param string $name The style name + * @param OutputFormatterStyleInterface $style The style instance + */ + public function setStyle($name, OutputFormatterStyleInterface $style); + + /** + * Checks if output formatter has style with specified name. + * + * @param string $name + * + * @return bool + */ + public function hasStyle($name); + + /** + * Gets style options from style with specified name. + * + * @param string $name + * + * @return OutputFormatterStyleInterface + * + * @throws \InvalidArgumentException When style isn't defined + */ + public function getStyle($name); + + /** + * Formats a message according to the given styles. + * + * @param string $message The message to style + * + * @return string The styled message + */ + public function format($message); +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyle.php b/vendor/symfony/console/Formatter/OutputFormatterStyle.php new file mode 100644 index 0000000..b884d71 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyle.php @@ -0,0 +1,203 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Formatter style class for defining styles. + * + * @author Konstantin Kudryashov + */ +class OutputFormatterStyle implements OutputFormatterStyleInterface +{ + private static $availableForegroundColors = array( + 'black' => array('set' => 30, 'unset' => 39), + 'red' => array('set' => 31, 'unset' => 39), + 'green' => array('set' => 32, 'unset' => 39), + 'yellow' => array('set' => 33, 'unset' => 39), + 'blue' => array('set' => 34, 'unset' => 39), + 'magenta' => array('set' => 35, 'unset' => 39), + 'cyan' => array('set' => 36, 'unset' => 39), + 'white' => array('set' => 37, 'unset' => 39), + 'default' => array('set' => 39, 'unset' => 39), + ); + private static $availableBackgroundColors = array( + 'black' => array('set' => 40, 'unset' => 49), + 'red' => array('set' => 41, 'unset' => 49), + 'green' => array('set' => 42, 'unset' => 49), + 'yellow' => array('set' => 43, 'unset' => 49), + 'blue' => array('set' => 44, 'unset' => 49), + 'magenta' => array('set' => 45, 'unset' => 49), + 'cyan' => array('set' => 46, 'unset' => 49), + 'white' => array('set' => 47, 'unset' => 49), + 'default' => array('set' => 49, 'unset' => 49), + ); + private static $availableOptions = array( + 'bold' => array('set' => 1, 'unset' => 22), + 'underscore' => array('set' => 4, 'unset' => 24), + 'blink' => array('set' => 5, 'unset' => 25), + 'reverse' => array('set' => 7, 'unset' => 27), + 'conceal' => array('set' => 8, 'unset' => 28), + ); + + private $foreground; + private $background; + private $options = array(); + + /** + * Initializes output formatter style. + * + * @param string|null $foreground The style foreground color name + * @param string|null $background The style background color name + * @param array $options The style options + */ + public function __construct($foreground = null, $background = null, array $options = array()) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (\count($options)) { + $this->setOptions($options); + } + } + + /** + * Sets style foreground color. + * + * @param string|null $color The color name + * + * @throws InvalidArgumentException When the color name isn't defined + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * Sets style background color. + * + * @param string|null $color The color name + * + * @throws InvalidArgumentException When the color name isn't defined + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * Sets some specific style option. + * + * @param string $option The option name + * + * @throws InvalidArgumentException When the option name isn't defined + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!\in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * Unsets some specific style option. + * + * @param string $option The option name + * + * @throws InvalidArgumentException When the option name isn't defined + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) + { + $this->options = array(); + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text) + { + $setCodes = array(); + $unsetCodes = array(); + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (\count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === \count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php new file mode 100644 index 0000000..4c7dc41 --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +/** + * Formatter style interface for defining styles. + * + * @author Konstantin Kudryashov + */ +interface OutputFormatterStyleInterface +{ + /** + * Sets style foreground color. + * + * @param string $color The color name + */ + public function setForeground($color = null); + + /** + * Sets style background color. + * + * @param string $color The color name + */ + public function setBackground($color = null); + + /** + * Sets some specific style option. + * + * @param string $option The option name + */ + public function setOption($option); + + /** + * Unsets some specific style option. + * + * @param string $option The option name + */ + public function unsetOption($option); + + /** + * Sets multiple style options at once. + */ + public function setOptions(array $options); + + /** + * Applies the style to a given text. + * + * @param string $text The text to style + * + * @return string + */ + public function apply($text); +} diff --git a/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php new file mode 100644 index 0000000..9e7283c --- /dev/null +++ b/vendor/symfony/console/Formatter/OutputFormatterStyleStack.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Formatter; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Jean-François Simon + */ +class OutputFormatterStyleStack +{ + /** + * @var OutputFormatterStyleInterface[] + */ + private $styles; + + private $emptyStyle; + + public function __construct(OutputFormatterStyleInterface $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); + $this->reset(); + } + + /** + * Resets stack (ie. empty internal arrays). + */ + public function reset() + { + $this->styles = array(); + } + + /** + * Pushes a style in the stack. + */ + public function push(OutputFormatterStyleInterface $style) + { + $this->styles[] = $style; + } + + /** + * Pops a style from the stack. + * + * @return OutputFormatterStyleInterface + * + * @throws InvalidArgumentException When style tags incorrectly nested + */ + public function pop(OutputFormatterStyleInterface $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = \array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * Computes current style with stacks top codes. + * + * @return OutputFormatterStyle + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[\count($this->styles) - 1]; + } + + /** + * @return $this + */ + public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return OutputFormatterStyleInterface + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/vendor/symfony/console/Helper/DebugFormatterHelper.php b/vendor/symfony/console/Helper/DebugFormatterHelper.php new file mode 100644 index 0000000..f5393ed --- /dev/null +++ b/vendor/symfony/console/Helper/DebugFormatterHelper.php @@ -0,0 +1,127 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Helps outputting debug information when running an external program from a command. + * + * An external program can be a Process, an HTTP request, or anything else. + * + * @author Fabien Potencier + */ +class DebugFormatterHelper extends Helper +{ + private $colors = array('black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'); + private $started = array(); + private $count = -1; + + /** + * Starts a debug formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param string $prefix The prefix to use + * + * @return string + */ + public function start($id, $message, $prefix = 'RUN') + { + $this->started[$id] = array('border' => ++$this->count % \count($this->colors)); + + return sprintf("%s %s %s\n", $this->getBorder($id), $prefix, $message); + } + + /** + * Adds progress to a formatting session. + * + * @param string $id The id of the formatting session + * @param string $buffer The message to display + * @param bool $error Whether to consider the buffer as error + * @param string $prefix The prefix for output + * @param string $errorPrefix The prefix for error output + * + * @return string + */ + public function progress($id, $buffer, $error = false, $prefix = 'OUT', $errorPrefix = 'ERR') + { + $message = ''; + + if ($error) { + if (isset($this->started[$id]['out'])) { + $message .= "\n"; + unset($this->started[$id]['out']); + } + if (!isset($this->started[$id]['err'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $errorPrefix); + $this->started[$id]['err'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $errorPrefix), $buffer); + } else { + if (isset($this->started[$id]['err'])) { + $message .= "\n"; + unset($this->started[$id]['err']); + } + if (!isset($this->started[$id]['out'])) { + $message .= sprintf('%s %s ', $this->getBorder($id), $prefix); + $this->started[$id]['out'] = true; + } + + $message .= str_replace("\n", sprintf("\n%s %s ", $this->getBorder($id), $prefix), $buffer); + } + + return $message; + } + + /** + * Stops a formatting session. + * + * @param string $id The id of the formatting session + * @param string $message The message to display + * @param bool $successful Whether to consider the result as success + * @param string $prefix The prefix for the end output + * + * @return string + */ + public function stop($id, $message, $successful, $prefix = 'RES') + { + $trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : ''; + + if ($successful) { + return sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + } + + $message = sprintf("%s%s %s %s\n", $trailingEOL, $this->getBorder($id), $prefix, $message); + + unset($this->started[$id]['out'], $this->started[$id]['err']); + + return $message; + } + + /** + * @param string $id The id of the formatting session + * + * @return string + */ + private function getBorder($id) + { + return sprintf(' ', $this->colors[$this->started[$id]['border']]); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'debug_formatter'; + } +} diff --git a/vendor/symfony/console/Helper/DescriptorHelper.php b/vendor/symfony/console/Helper/DescriptorHelper.php new file mode 100644 index 0000000..54d3b1a --- /dev/null +++ b/vendor/symfony/console/Helper/DescriptorHelper.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Descriptor\DescriptorInterface; +use Symfony\Component\Console\Descriptor\JsonDescriptor; +use Symfony\Component\Console\Descriptor\MarkdownDescriptor; +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * This class adds helper method to describe objects in various formats. + * + * @author Jean-François Simon + */ +class DescriptorHelper extends Helper +{ + /** + * @var DescriptorInterface[] + */ + private $descriptors = array(); + + public function __construct() + { + $this + ->register('txt', new TextDescriptor()) + ->register('xml', new XmlDescriptor()) + ->register('json', new JsonDescriptor()) + ->register('md', new MarkdownDescriptor()) + ; + } + + /** + * Describes an object if supported. + * + * Available options are: + * * format: string, the output format name + * * raw_text: boolean, sets output type as raw + * + * @param OutputInterface $output + * @param object $object + * @param array $options + * + * @throws InvalidArgumentException when the given format is not supported + */ + public function describe(OutputInterface $output, $object, array $options = array()) + { + $options = array_merge(array( + 'raw_text' => false, + 'format' => 'txt', + ), $options); + + if (!isset($this->descriptors[$options['format']])) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); + } + + $descriptor = $this->descriptors[$options['format']]; + $descriptor->describe($output, $object, $options); + } + + /** + * Registers a descriptor. + * + * @param string $format + * @param DescriptorInterface $descriptor + * + * @return $this + */ + public function register($format, DescriptorInterface $descriptor) + { + $this->descriptors[$format] = $descriptor; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'descriptor'; + } +} diff --git a/vendor/symfony/console/Helper/DialogHelper.php b/vendor/symfony/console/Helper/DialogHelper.php new file mode 100644 index 0000000..87a5a0c --- /dev/null +++ b/vendor/symfony/console/Helper/DialogHelper.php @@ -0,0 +1,502 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The Dialog class provides helpers to interact with the user. + * + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0. + * Use {@link \Symfony\Component\Console\Helper\QuestionHelper} instead. + */ +class DialogHelper extends InputAwareHelper +{ + private $inputStream; + private static $shell; + private static $stty; + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); + } + } + + /** + * Asks the user to select a value. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param array $choices List of choices to pick from + * @param bool|string $default The default answer if the user enters nothing + * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked + * @param bool $multiselect Select more than one value separated by comma + * + * @return int|string|array The selected value or values (the key of the choices array) + * + * @throws InvalidArgumentException + */ + public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $width = max(array_map('strlen', array_keys($choices))); + + $messages = (array) $question; + foreach ($choices as $key => $value) { + $messages[] = sprintf(" [%-{$width}s] %s", $key, $value); + } + + $output->writeln($messages); + + $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $picked); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new InvalidArgumentException(sprintf($errorMessage, $picked)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = array($picked); + } + + $multiselectChoices = array(); + + foreach ($selectedChoices as $value) { + if (empty($choices[$value])) { + throw new InvalidArgumentException(sprintf($errorMessage, $value)); + } + $multiselectChoices[] = $value; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return $picked; + }, $attempts, $default); + + return $result; + } + + /** + * Asks a question to the user. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param string $default The default answer if none is given by the user + * @param array $autocomplete List of values to autocomplete + * + * @return string The user answer + * + * @throws RuntimeException If there is no data to read in the input stream + */ + public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null) + { + if ($this->input && !$this->input->isInteractive()) { + return $default; + } + + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $output->write($question); + + $inputStream = $this->inputStream ?: STDIN; + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new RuntimeException('Aborted'); + } + $ret = trim($ret); + } else { + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = \count($matches); + + $sttyMode = shell_exec('stty -g'); + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + // Backspace Character + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + // Move cursor backwards + $output->write("\033[1D"); + } + + if (0 === $i) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = \count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (\ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + // Echo out remaining chars for current match + $output->write(substr($ret, $i)); + $i = \strlen($ret); + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (0 === strpos($value, $ret) && $i !== \strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + // Erase characters from cursor to end of line + $output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + // Save cursor position + $output->write("\0337"); + // Write highlighted text + $output->write(''.substr($matches[$ofs], $i).''); + // Restore cursor position + $output->write("\0338"); + } + } + + // Reset stty so it behaves normally again + shell_exec(sprintf('stty %s', $sttyMode)); + } + + return \strlen($ret) > 0 ? $ret : $default; + } + + /** + * Asks a confirmation to the user. + * + * The question will be asked until the user answers by nothing, yes, or no. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param bool $default The default answer if the user enters nothing + * + * @return bool true if the user has confirmed, false otherwise + */ + public function askConfirmation(OutputInterface $output, $question, $default = true) + { + $answer = 'z'; + while ($answer && !\in_array(strtolower($answer[0]), array('y', 'n'))) { + $answer = $this->ask($output, $question); + } + + if (false === $default) { + return $answer && 'y' == strtolower($answer[0]); + } + + return !$answer || 'y' == strtolower($answer[0]); + } + + /** + * Asks a question to the user, the response is hidden. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question + * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not + * + * @return string The answer + * + * @throws RuntimeException In case the fallback is deactivated and the response can not be hidden + */ + public function askHiddenResponse(OutputInterface $output, $question, $fallback = true) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if ('phar:' === substr(__FILE__, 0, 5)) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $output->write($question); + $value = rtrim(shell_exec($exe)); + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $output->write($question); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($this->inputStream ?: STDIN, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new RuntimeException('Aborted'); + } + + $value = trim($value); + $output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $output->write($question); + $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $output->writeln(''); + + return $value; + } + + if ($fallback) { + return $this->ask($output, $question); + } + + throw new RuntimeException('Unable to hide the response'); + } + + /** + * Asks for a value and validates the response. + * + * The validator receives the data to validate. It must return the + * validated data when the data is valid and throw an exception + * otherwise. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param callable $validator A PHP callback + * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param string $default The default answer if none is given by the user + * @param array $autocomplete List of values to autocomplete + * + * @return mixed + * + * @throws \Exception When any of the validators return an error + */ + public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null) + { + $that = $this; + + $interviewer = function () use ($output, $question, $default, $autocomplete, $that) { + return $that->ask($output, $question, $default, $autocomplete); + }; + + return $this->validateAttempts($interviewer, $output, $validator, $attempts); + } + + /** + * Asks for a value, hide and validates the response. + * + * The validator receives the data to validate. It must return the + * validated data when the data is valid and throw an exception + * otherwise. + * + * @param OutputInterface $output An Output instance + * @param string|array $question The question to ask + * @param callable $validator A PHP callback + * @param int|false $attempts Max number of times to ask before giving up (false by default, which means infinite) + * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not + * + * @return string The response + * + * @throws \Exception When any of the validators return an error + * @throws RuntimeException In case the fallback is deactivated and the response can not be hidden + */ + public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true) + { + $that = $this; + + $interviewer = function () use ($output, $question, $fallback, $that) { + return $that->askHiddenResponse($output, $question, $fallback); + }; + + return $this->validateAttempts($interviewer, $output, $validator, $attempts); + } + + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + */ + public function setInputStream($stream) + { + $this->inputStream = $stream; + } + + /** + * Returns the helper's input stream. + * + * @return resource|null The input stream or null if the default STDIN is used + */ + public function getInputStream() + { + return $this->inputStream; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'dialog'; + } + + /** + * Return a valid Unix shell. + * + * @return string|bool The valid shell name, false in case no valid shell is found + */ + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + // handle other OSs with bash/zsh/ksh/csh if available to hide the answer + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = 0 === $exitcode; + } + + /** + * Validate an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * @param OutputInterface $output An Output instance + * @param callable $validator A PHP callback + * @param int|false $attempts Max number of times to ask before giving up; false will ask infinitely + * + * @return string The validated response + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $e = null; + while (false === $attempts || $attempts--) { + if (null !== $e) { + $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($e->getMessage(), 'error')); + } + + try { + return \call_user_func($validator, $interviewer()); + } catch (\Exception $e) { + } + } + + throw $e; + } +} diff --git a/vendor/symfony/console/Helper/FormatterHelper.php b/vendor/symfony/console/Helper/FormatterHelper.php new file mode 100644 index 0000000..6f67924 --- /dev/null +++ b/vendor/symfony/console/Helper/FormatterHelper.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatter; + +/** + * The Formatter class provides helpers to format messages. + * + * @author Fabien Potencier + */ +class FormatterHelper extends Helper +{ + /** + * Formats a message within a section. + * + * @param string $section The section name + * @param string $message The message + * @param string $style The style to apply to the section + * + * @return string The format section + */ + public function formatSection($section, $message, $style = 'info') + { + return sprintf('<%s>[%s] %s', $style, $section, $style, $message); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string $style The style to apply to the whole block + * @param bool $large Whether to return a large block + * + * @return string The formatter message + */ + public function formatBlock($messages, $style, $large = false) + { + if (!\is_array($messages)) { + $messages = array($messages); + } + + $len = 0; + $lines = array(); + foreach ($messages as $message) { + $message = OutputFormatter::escape($message); + $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); + $len = max($this->strlen($message) + ($large ? 4 : 2), $len); + } + + $messages = $large ? array(str_repeat(' ', $len)) : array(); + for ($i = 0; isset($lines[$i]); ++$i) { + $messages[] = $lines[$i].str_repeat(' ', $len - $this->strlen($lines[$i])); + } + if ($large) { + $messages[] = str_repeat(' ', $len); + } + + for ($i = 0; isset($messages[$i]); ++$i) { + $messages[$i] = sprintf('<%s>%s', $style, $messages[$i], $style); + } + + return implode("\n", $messages); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'formatter'; + } +} diff --git a/vendor/symfony/console/Helper/Helper.php b/vendor/symfony/console/Helper/Helper.php new file mode 100644 index 0000000..f462efd --- /dev/null +++ b/vendor/symfony/console/Helper/Helper.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Helper is the base class for all helper classes. + * + * @author Fabien Potencier + */ +abstract class Helper implements HelperInterface +{ + protected $helperSet = null; + + /** + * {@inheritdoc} + */ + public function setHelperSet(HelperSet $helperSet = null) + { + $this->helperSet = $helperSet; + } + + /** + * {@inheritdoc} + */ + public function getHelperSet() + { + return $this->helperSet; + } + + /** + * Returns the length of a string, using mb_strwidth if it is available. + * + * @param string $string The string to check its length + * + * @return int The length of the string + */ + public static function strlen($string) + { + if (false === $encoding = mb_detect_encoding($string, null, true)) { + return \strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + public static function formatTime($secs) + { + static $timeFormats = array( + array(0, '< 1 sec'), + array(1, '1 sec'), + array(2, 'secs', 1), + array(60, '1 min'), + array(120, 'mins', 60), + array(3600, '1 hr'), + array(7200, 'hrs', 3600), + array(86400, '1 day'), + array(172800, 'days', 86400), + ); + + foreach ($timeFormats as $index => $format) { + if ($secs >= $format[0]) { + if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0]) + || $index == \count($timeFormats) - 1 + ) { + if (2 == \count($format)) { + return $format[1]; + } + + return floor($secs / $format[2]).' '.$format[1]; + } + } + } + } + + public static function formatMemory($memory) + { + if ($memory >= 1024 * 1024 * 1024) { + return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024); + } + + if ($memory >= 1024 * 1024) { + return sprintf('%.1f MiB', $memory / 1024 / 1024); + } + + if ($memory >= 1024) { + return sprintf('%d KiB', $memory / 1024); + } + + return sprintf('%d B', $memory); + } + + public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, $string) + { + return self::strlen(self::removeDecoration($formatter, $string)); + } + + public static function removeDecoration(OutputFormatterInterface $formatter, $string) + { + $isDecorated = $formatter->isDecorated(); + $formatter->setDecorated(false); + // remove <...> formatting + $string = $formatter->format($string); + // remove already formatted characters + $string = preg_replace("/\033\[[^m]*m/", '', $string); + $formatter->setDecorated($isDecorated); + + return $string; + } +} diff --git a/vendor/symfony/console/Helper/HelperInterface.php b/vendor/symfony/console/Helper/HelperInterface.php new file mode 100644 index 0000000..1ce8235 --- /dev/null +++ b/vendor/symfony/console/Helper/HelperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * HelperInterface is the interface all helpers must implement. + * + * @author Fabien Potencier + */ +interface HelperInterface +{ + /** + * Sets the helper set associated with this helper. + */ + public function setHelperSet(HelperSet $helperSet = null); + + /** + * Gets the helper set associated with this helper. + * + * @return HelperSet A HelperSet instance + */ + public function getHelperSet(); + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName(); +} diff --git a/vendor/symfony/console/Helper/HelperSet.php b/vendor/symfony/console/Helper/HelperSet.php new file mode 100644 index 0000000..62cce81 --- /dev/null +++ b/vendor/symfony/console/Helper/HelperSet.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * HelperSet represents a set of helpers to be used with a command. + * + * @author Fabien Potencier + */ +class HelperSet implements \IteratorAggregate +{ + /** + * @var Helper[] + */ + private $helpers = array(); + private $command; + + /** + * @param Helper[] $helpers An array of helper + */ + public function __construct(array $helpers = array()) + { + foreach ($helpers as $alias => $helper) { + $this->set($helper, \is_int($alias) ? null : $alias); + } + } + + /** + * Sets a helper. + * + * @param HelperInterface $helper The helper instance + * @param string $alias An alias + */ + public function set(HelperInterface $helper, $alias = null) + { + $this->helpers[$helper->getName()] = $helper; + if (null !== $alias) { + $this->helpers[$alias] = $helper; + } + + $helper->setHelperSet($this); + } + + /** + * Returns true if the helper if defined. + * + * @param string $name The helper name + * + * @return bool true if the helper is defined, false otherwise + */ + public function has($name) + { + return isset($this->helpers[$name]); + } + + /** + * Gets a helper value. + * + * @param string $name The helper name + * + * @return HelperInterface The helper instance + * + * @throws InvalidArgumentException if the helper is not defined + */ + public function get($name) + { + if (!$this->has($name)) { + throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); + } + + if ('dialog' === $name && $this->helpers[$name] instanceof DialogHelper) { + @trigger_error('"Symfony\Component\Console\Helper\DialogHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\QuestionHelper" instead.', E_USER_DEPRECATED); + } elseif ('progress' === $name && $this->helpers[$name] instanceof ProgressHelper) { + @trigger_error('"Symfony\Component\Console\Helper\ProgressHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\ProgressBar" instead.', E_USER_DEPRECATED); + } elseif ('table' === $name && $this->helpers[$name] instanceof TableHelper) { + @trigger_error('"Symfony\Component\Console\Helper\TableHelper" is deprecated since Symfony 2.5 and will be removed in 3.0. Use "Symfony\Component\Console\Helper\Table" instead.', E_USER_DEPRECATED); + } + + return $this->helpers[$name]; + } + + public function setCommand(Command $command = null) + { + $this->command = $command; + } + + /** + * Gets the command associated with this helper set. + * + * @return Command A Command instance + */ + public function getCommand() + { + return $this->command; + } + + /** + * @return Helper[] + */ + public function getIterator() + { + return new \ArrayIterator($this->helpers); + } +} diff --git a/vendor/symfony/console/Helper/InputAwareHelper.php b/vendor/symfony/console/Helper/InputAwareHelper.php new file mode 100644 index 0000000..0d0dba2 --- /dev/null +++ b/vendor/symfony/console/Helper/InputAwareHelper.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Input\InputAwareInterface; +use Symfony\Component\Console\Input\InputInterface; + +/** + * An implementation of InputAwareInterface for Helpers. + * + * @author Wouter J + */ +abstract class InputAwareHelper extends Helper implements InputAwareInterface +{ + protected $input; + + /** + * {@inheritdoc} + */ + public function setInput(InputInterface $input) + { + $this->input = $input; + } +} diff --git a/vendor/symfony/console/Helper/ProcessHelper.php b/vendor/symfony/console/Helper/ProcessHelper.php new file mode 100644 index 0000000..a150e5a --- /dev/null +++ b/vendor/symfony/console/Helper/ProcessHelper.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Process; +use Symfony\Component\Process\ProcessBuilder; + +/** + * The ProcessHelper class provides helpers to run external processes. + * + * @author Fabien Potencier + */ +class ProcessHelper extends Helper +{ + /** + * Runs an external process. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|array|Process $cmd An instance of Process or an array of arguments to escape and run or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param int $verbosity The threshold for verbosity + * + * @return Process The process that ran + */ + public function run(OutputInterface $output, $cmd, $error = null, $callback = null, $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + if (\is_array($cmd)) { + $process = ProcessBuilder::create($cmd)->getProcess(); + } elseif ($cmd instanceof Process) { + $process = $cmd; + } else { + $process = new Process($cmd); + } + + if ($verbosity <= $output->getVerbosity()) { + $output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine()))); + } + + if ($output->isDebug()) { + $callback = $this->wrapCallback($output, $process, $callback); + } + + $process->run($callback); + + if ($verbosity <= $output->getVerbosity()) { + $message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode()); + $output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful())); + } + + if (!$process->isSuccessful() && null !== $error) { + $output->writeln(sprintf('%s', $this->escapeString($error))); + } + + return $process; + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param OutputInterface $output An OutputInterface instance + * @param string|Process $cmd An instance of Process or a command to run + * @param string|null $error An error message that must be displayed if something went wrong + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * + * @return Process The process that ran + * + * @throws ProcessFailedException + * + * @see run() + */ + public function mustRun(OutputInterface $output, $cmd, $error = null, $callback = null) + { + $process = $this->run($output, $cmd, $error, $callback); + + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + return $process; + } + + /** + * Wraps a Process callback to add debugging output. + * + * @param OutputInterface $output An OutputInterface interface + * @param Process $process The Process + * @param callable|null $callback A PHP callable + * + * @return callable + */ + public function wrapCallback(OutputInterface $output, Process $process, $callback = null) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $formatter = $this->getHelperSet()->get('debug_formatter'); + + $that = $this; + + return function ($type, $buffer) use ($output, $process, $callback, $formatter, $that) { + $output->write($formatter->progress(spl_object_hash($process), $that->escapeString($buffer), Process::ERR === $type)); + + if (null !== $callback) { + \call_user_func($callback, $type, $buffer); + } + }; + } + + /** + * This method is public for PHP 5.3 compatibility, it should be private. + * + * @internal + */ + public function escapeString($str) + { + return str_replace('<', '\\<', $str); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'process'; + } +} diff --git a/vendor/symfony/console/Helper/ProgressBar.php b/vendor/symfony/console/Helper/ProgressBar.php new file mode 100644 index 0000000..5687cb6 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressBar.php @@ -0,0 +1,629 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The ProgressBar provides helpers to display progress output. + * + * @author Fabien Potencier + * @author Chris Jones + */ +class ProgressBar +{ + private $barWidth = 28; + private $barChar; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format; + private $internalFormat; + private $redrawFreq = 1; + private $output; + private $step = 0; + private $max; + private $startTime; + private $stepWidth; + private $percent = 0.0; + private $formatLineCount; + private $messages = array(); + private $overwrite = true; + private $firstRun = true; + + private static $formatters; + private static $formats; + + /** + * @param OutputInterface $output An OutputInterface instance + * @param int $max Maximum steps (0 if unknown) + */ + public function __construct(OutputInterface $output, $max = 0) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->output = $output; + $this->setMaxSteps($max); + + if (!$this->output->isDecorated()) { + // disable overwrite when output does not support ANSI codes. + $this->overwrite = false; + + // set a reasonable redraw frequency so output isn't flooded + $this->setRedrawFrequency($max / 10); + } + + $this->startTime = time(); + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition($name, $callable) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition($name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; + } + + /** + * Sets a format for a given name. + * + * This method also allow you to override an existing format. + * + * @param string $name The format name + * @param string $format A format string + */ + public static function setFormatDefinition($name, $format) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + self::$formats[$name] = $format; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition($name) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return isset(self::$formats[$name]) ? self::$formats[$name] : null; + } + + /** + * Associates a text with a named placeholder. + * + * The text is displayed when the progress bar is rendered but only + * when the corresponding placeholder is part of the custom format line + * (by wrapping the name with %). + * + * @param string $message The text to associate with the placeholder + * @param string $name The name of the placeholder + */ + public function setMessage($message, $name = 'message') + { + $this->messages[$name] = $message; + } + + public function getMessage($name = 'message') + { + return $this->messages[$name]; + } + + /** + * Gets the progress bar start time. + * + * @return int The progress bar start time + */ + public function getStartTime() + { + return $this->startTime; + } + + /** + * Gets the progress bar maximal steps. + * + * @return int The progress bar max steps + */ + public function getMaxSteps() + { + return $this->max; + } + + /** + * Gets the progress bar step. + * + * @deprecated since version 2.6, to be removed in 3.0. Use {@link getProgress()} instead. + * + * @return int The progress bar step + */ + public function getStep() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the getProgress() method instead.', E_USER_DEPRECATED); + + return $this->getProgress(); + } + + /** + * Gets the current step position. + * + * @return int The progress bar step + */ + public function getProgress() + { + return $this->step; + } + + /** + * Gets the progress bar step width. + * + * @internal This method is public for PHP 5.3 compatibility, it should not be used. + * + * @return int The progress bar step width + */ + public function getStepWidth() + { + return $this->stepWidth; + } + + /** + * Gets the current progress bar percent. + * + * @return float The current progress bar percent + */ + public function getProgressPercent() + { + return $this->percent; + } + + /** + * Sets the progress bar width. + * + * @param int $size The progress bar size + */ + public function setBarWidth($size) + { + $this->barWidth = (int) $size; + } + + /** + * Gets the progress bar width. + * + * @return int The progress bar size + */ + public function getBarWidth() + { + return $this->barWidth; + } + + /** + * Sets the bar character. + * + * @param string $char A character + */ + public function setBarCharacter($char) + { + $this->barChar = $char; + } + + /** + * Gets the bar character. + * + * @return string A character + */ + public function getBarCharacter() + { + if (null === $this->barChar) { + return $this->max ? '=' : $this->emptyBarChar; + } + + return $this->barChar; + } + + /** + * Sets the empty bar character. + * + * @param string $char A character + */ + public function setEmptyBarCharacter($char) + { + $this->emptyBarChar = $char; + } + + /** + * Gets the empty bar character. + * + * @return string A character + */ + public function getEmptyBarCharacter() + { + return $this->emptyBarChar; + } + + /** + * Sets the progress bar character. + * + * @param string $char A character + */ + public function setProgressCharacter($char) + { + $this->progressChar = $char; + } + + /** + * Gets the progress bar character. + * + * @return string A character + */ + public function getProgressCharacter() + { + return $this->progressChar; + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + public function setFormat($format) + { + $this->format = null; + $this->internalFormat = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int|float $freq The frequency in steps + */ + public function setRedrawFrequency($freq) + { + $this->redrawFreq = max((int) $freq, 1); + } + + /** + * Starts the progress output. + * + * @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged + */ + public function start($max = null) + { + $this->startTime = time(); + $this->step = 0; + $this->percent = 0.0; + + if (null !== $max) { + $this->setMaxSteps($max); + } + + $this->display(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + * + * @throws LogicException + */ + public function advance($step = 1) + { + $this->setProgress($this->step + $step); + } + + /** + * Sets the current progress. + * + * @deprecated since version 2.6, to be removed in 3.0. Use {@link setProgress()} instead. + * + * @param int $step The current progress + * + * @throws LogicException + */ + public function setCurrent($step) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the setProgress() method instead.', E_USER_DEPRECATED); + + $this->setProgress($step); + } + + /** + * Sets whether to overwrite the progressbar, false for new line. + * + * @param bool $overwrite + */ + public function setOverwrite($overwrite) + { + $this->overwrite = (bool) $overwrite; + } + + /** + * Sets the current progress. + * + * @param int $step The current progress + * + * @throws LogicException + */ + public function setProgress($step) + { + $step = (int) $step; + if ($step < $this->step) { + throw new LogicException('You can\'t regress the progress bar.'); + } + + if ($this->max && $step > $this->max) { + $this->max = $step; + } + + $prevPeriod = (int) ($this->step / $this->redrawFreq); + $currPeriod = (int) ($step / $this->redrawFreq); + $this->step = $step; + $this->percent = $this->max ? (float) $this->step / $this->max : 0; + if ($prevPeriod !== $currPeriod || $this->max === $step) { + $this->display(); + } + } + + /** + * Finishes the progress output. + */ + public function finish() + { + if (!$this->max) { + $this->max = $this->step; + } + + if ($this->step === $this->max && !$this->overwrite) { + // prevent double 100% output + return; + } + + $this->setProgress($this->max); + } + + /** + * Outputs the current progress string. + */ + public function display() + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + // these 3 variables can be removed in favor of using $this in the closure when support for PHP 5.3 will be dropped. + $self = $this; + $output = $this->output; + $messages = $this->messages; + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self, $output, $messages) { + if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { + $text = \call_user_func($formatter, $self, $output); + } elseif (isset($messages[$matches[1]])) { + $text = $messages[$matches[1]]; + } else { + return $matches[0]; + } + + if (isset($matches[2])) { + $text = sprintf('%'.$matches[2], $text); + } + + return $text; + }, $this->format)); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear() + { + if (!$this->overwrite) { + return; + } + + if (null === $this->format) { + $this->setRealFormat($this->internalFormat ?: $this->determineBestFormat()); + } + + $this->overwrite(''); + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + private function setRealFormat($format) + { + // try to use the _nomax variant if available + if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) { + $this->format = self::getFormatDefinition($format.'_nomax'); + } elseif (null !== self::getFormatDefinition($format)) { + $this->format = self::getFormatDefinition($format); + } else { + $this->format = $format; + } + + $this->formatLineCount = substr_count($this->format, "\n"); + } + + /** + * Sets the progress bar maximal steps. + * + * @param int $max The progress bar max steps + */ + private function setMaxSteps($max) + { + $this->max = max(0, (int) $max); + $this->stepWidth = $this->max ? Helper::strlen($this->max) : 4; + } + + /** + * Overwrites a previous message to the output. + * + * @param string $message The message + */ + private function overwrite($message) + { + if ($this->overwrite) { + if (!$this->firstRun) { + // Move the cursor to the beginning of the line + $this->output->write("\x0D"); + + // Erase the line + $this->output->write("\x1B[2K"); + + // Erase previous lines + if ($this->formatLineCount > 0) { + $this->output->write(str_repeat("\x1B[1A\x1B[2K", $this->formatLineCount)); + } + } + } elseif ($this->step > 0) { + $this->output->writeln(''); + } + + $this->firstRun = false; + + $this->output->write($message); + } + + private function determineBestFormat() + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->max ? 'verbose' : 'verbose_nomax'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + return $this->max ? 'very_verbose' : 'very_verbose_nomax'; + case OutputInterface::VERBOSITY_DEBUG: + return $this->max ? 'debug' : 'debug_nomax'; + default: + return $this->max ? 'normal' : 'normal_nomax'; + } + } + + private static function initPlaceholderFormatters() + { + return array( + 'bar' => function (ProgressBar $bar, OutputInterface $output) { + $completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth()); + $display = str_repeat($bar->getBarCharacter(), $completeBars); + if ($completeBars < $bar->getBarWidth()) { + $emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter()); + $display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars); + } + + return $display; + }, + 'elapsed' => function (ProgressBar $bar) { + return Helper::formatTime(time() - $bar->getStartTime()); + }, + 'remaining' => function (ProgressBar $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $remaining = 0; + } else { + $remaining = round((time() - $bar->getStartTime()) / $bar->getProgress() * ($bar->getMaxSteps() - $bar->getProgress())); + } + + return Helper::formatTime($remaining); + }, + 'estimated' => function (ProgressBar $bar) { + if (!$bar->getMaxSteps()) { + throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); + } + + if (!$bar->getProgress()) { + $estimated = 0; + } else { + $estimated = round((time() - $bar->getStartTime()) / $bar->getProgress() * $bar->getMaxSteps()); + } + + return Helper::formatTime($estimated); + }, + 'memory' => function (ProgressBar $bar) { + return Helper::formatMemory(memory_get_usage(true)); + }, + 'current' => function (ProgressBar $bar) { + return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', STR_PAD_LEFT); + }, + 'max' => function (ProgressBar $bar) { + return $bar->getMaxSteps(); + }, + 'percent' => function (ProgressBar $bar) { + return floor($bar->getProgressPercent() * 100); + }, + ); + } + + private static function initFormats() + { + return array( + 'normal' => ' %current%/%max% [%bar%] %percent:3s%%', + 'normal_nomax' => ' %current% [%bar%]', + + 'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%', + 'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', + 'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%', + + 'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%', + 'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%', + ); + } +} diff --git a/vendor/symfony/console/Helper/ProgressHelper.php b/vendor/symfony/console/Helper/ProgressHelper.php new file mode 100644 index 0000000..4596f17 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressHelper.php @@ -0,0 +1,469 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * The Progress class provides helpers to display progress output. + * + * @author Chris Jones + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0 + * Use {@link ProgressBar} instead. + */ +class ProgressHelper extends Helper +{ + const FORMAT_QUIET = ' %percent%%'; + const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%'; + const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%'; + const FORMAT_QUIET_NOMAX = ' %current%'; + const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]'; + const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%'; + + // options + private $barWidth = 28; + private $barChar = '='; + private $emptyBarChar = '-'; + private $progressChar = '>'; + private $format = null; + private $redrawFreq = 1; + + private $lastMessagesLength; + private $barCharOriginal; + + /** + * @var OutputInterface + */ + private $output; + + /** + * Current step. + * + * @var int + */ + private $current; + + /** + * Maximum number of steps. + * + * @var int + */ + private $max; + + /** + * Start time of the progress bar. + * + * @var int + */ + private $startTime; + + /** + * List of formatting variables. + * + * @var array + */ + private $defaultFormatVars = array( + 'current', + 'max', + 'bar', + 'percent', + 'elapsed', + ); + + /** + * Available formatting variables. + * + * @var array + */ + private $formatVars; + + /** + * Stored format part widths (used for padding). + * + * @var array + */ + private $widths = array( + 'current' => 4, + 'max' => 4, + 'percent' => 3, + 'elapsed' => 6, + ); + + /** + * Various time formats. + * + * @var array + */ + private $timeFormats = array( + array(0, '???'), + array(2, '1 sec'), + array(59, 'secs', 1), + array(60, '1 min'), + array(3600, 'mins', 60), + array(5400, '1 hr'), + array(86400, 'hrs', 3600), + array(129600, '1 day'), + array(604800, 'days', 86400), + ); + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\ProgressBar class instead.', E_USER_DEPRECATED); + } + } + + /** + * Sets the progress bar width. + * + * @param int $size The progress bar size + */ + public function setBarWidth($size) + { + $this->barWidth = (int) $size; + } + + /** + * Sets the bar character. + * + * @param string $char A character + */ + public function setBarCharacter($char) + { + $this->barChar = $char; + } + + /** + * Sets the empty bar character. + * + * @param string $char A character + */ + public function setEmptyBarCharacter($char) + { + $this->emptyBarChar = $char; + } + + /** + * Sets the progress bar character. + * + * @param string $char A character + */ + public function setProgressCharacter($char) + { + $this->progressChar = $char; + } + + /** + * Sets the progress bar format. + * + * @param string $format The format + */ + public function setFormat($format) + { + $this->format = $format; + } + + /** + * Sets the redraw frequency. + * + * @param int $freq The frequency in steps + */ + public function setRedrawFrequency($freq) + { + $this->redrawFreq = (int) $freq; + } + + /** + * Starts the progress output. + * + * @param OutputInterface $output An Output instance + * @param int|null $max Maximum steps + */ + public function start(OutputInterface $output, $max = null) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + $this->startTime = time(); + $this->current = 0; + $this->max = (int) $max; + + // Disabling output when it does not support ANSI codes as it would result in a broken display anyway. + $this->output = $output->isDecorated() ? $output : new NullOutput(); + $this->lastMessagesLength = 0; + $this->barCharOriginal = ''; + + if (null === $this->format) { + switch ($output->getVerbosity()) { + case OutputInterface::VERBOSITY_QUIET: + $this->format = self::FORMAT_QUIET_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_QUIET; + } + break; + case OutputInterface::VERBOSITY_VERBOSE: + case OutputInterface::VERBOSITY_VERY_VERBOSE: + case OutputInterface::VERBOSITY_DEBUG: + $this->format = self::FORMAT_VERBOSE_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_VERBOSE; + } + break; + default: + $this->format = self::FORMAT_NORMAL_NOMAX; + if ($this->max > 0) { + $this->format = self::FORMAT_NORMAL; + } + break; + } + } + + $this->initialize(); + } + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + * @param bool $redraw Whether to redraw or not + * + * @throws LogicException + */ + public function advance($step = 1, $redraw = false) + { + $this->setCurrent($this->current + $step, $redraw); + } + + /** + * Sets the current progress. + * + * @param int $current The current progress + * @param bool $redraw Whether to redraw or not + * + * @throws LogicException + */ + public function setCurrent($current, $redraw = false) + { + if (null === $this->startTime) { + throw new LogicException('You must start the progress bar before calling setCurrent().'); + } + + $current = (int) $current; + + if ($current < $this->current) { + throw new LogicException('You can\'t regress the progress bar'); + } + + if (0 === $this->current) { + $redraw = true; + } + + $prevPeriod = (int) ($this->current / $this->redrawFreq); + + $this->current = $current; + + $currPeriod = (int) ($this->current / $this->redrawFreq); + if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) { + $this->display(); + } + } + + /** + * Outputs the current progress string. + * + * @param bool $finish Forces the end result + * + * @throws LogicException + */ + public function display($finish = false) + { + if (null === $this->startTime) { + throw new LogicException('You must start the progress bar before calling display().'); + } + + $message = $this->format; + foreach ($this->generate($finish) as $name => $value) { + $message = str_replace("%{$name}%", $value, $message); + } + $this->overwrite($this->output, $message); + } + + /** + * Removes the progress bar from the current line. + * + * This is useful if you wish to write some output + * while a progress bar is running. + * Call display() to show the progress bar again. + */ + public function clear() + { + $this->overwrite($this->output, ''); + } + + /** + * Finishes the progress output. + */ + public function finish() + { + if (null === $this->startTime) { + throw new LogicException('You must start the progress bar before calling finish().'); + } + + if (null !== $this->startTime) { + if (!$this->max) { + $this->barChar = $this->barCharOriginal; + $this->display(true); + } + $this->startTime = null; + $this->output->writeln(''); + $this->output = null; + } + } + + /** + * Initializes the progress helper. + */ + private function initialize() + { + $this->formatVars = array(); + foreach ($this->defaultFormatVars as $var) { + if (false !== strpos($this->format, "%{$var}%")) { + $this->formatVars[$var] = true; + } + } + + if ($this->max > 0) { + $this->widths['max'] = $this->strlen($this->max); + $this->widths['current'] = $this->widths['max']; + } else { + $this->barCharOriginal = $this->barChar; + $this->barChar = $this->emptyBarChar; + } + } + + /** + * Generates the array map of format variables to values. + * + * @param bool $finish Forces the end result + * + * @return array Array of format vars and values + */ + private function generate($finish = false) + { + $vars = array(); + $percent = 0; + if ($this->max > 0) { + $percent = (float) $this->current / $this->max; + } + + if (isset($this->formatVars['bar'])) { + if ($this->max > 0) { + $completeBars = floor($percent * $this->barWidth); + } else { + if (!$finish) { + $completeBars = floor($this->current % $this->barWidth); + } else { + $completeBars = $this->barWidth; + } + } + + $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar); + $bar = str_repeat($this->barChar, $completeBars); + if ($completeBars < $this->barWidth) { + $bar .= $this->progressChar; + $bar .= str_repeat($this->emptyBarChar, $emptyBars); + } + + $vars['bar'] = $bar; + } + + if (isset($this->formatVars['elapsed'])) { + $elapsed = time() - $this->startTime; + $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT); + } + + if (isset($this->formatVars['current'])) { + $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT); + } + + if (isset($this->formatVars['max'])) { + $vars['max'] = $this->max; + } + + if (isset($this->formatVars['percent'])) { + $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT); + } + + return $vars; + } + + /** + * Converts seconds into human-readable format. + * + * @param int $secs Number of seconds + * + * @return string Time in readable format + */ + private function humaneTime($secs) + { + $text = ''; + foreach ($this->timeFormats as $format) { + if ($secs < $format[0]) { + if (2 == \count($format)) { + $text = $format[1]; + break; + } else { + $text = ceil($secs / $format[2]).' '.$format[1]; + break; + } + } + } + + return $text; + } + + /** + * Overwrites a previous message to the output. + * + * @param OutputInterface $output An Output instance + * @param string $message The message + */ + private function overwrite(OutputInterface $output, $message) + { + $length = $this->strlen($message); + + // append whitespace to match the last line's length + if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) { + $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); + } + + // carriage return + $output->write("\x0D"); + $output->write($message); + + $this->lastMessagesLength = $this->strlen($message); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'progress'; + } +} diff --git a/vendor/symfony/console/Helper/ProgressIndicator.php b/vendor/symfony/console/Helper/ProgressIndicator.php new file mode 100644 index 0000000..21c4800 --- /dev/null +++ b/vendor/symfony/console/Helper/ProgressIndicator.php @@ -0,0 +1,307 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @author Kevin Bond + */ +class ProgressIndicator +{ + private $output; + private $startTime; + private $format; + private $message; + private $indicatorValues; + private $indicatorCurrent; + private $indicatorChangeInterval; + private $indicatorUpdateTime; + private $started = false; + + private static $formatters; + private static $formats; + + /** + * @param OutputInterface $output + * @param string|null $format Indicator format + * @param int $indicatorChangeInterval Change interval in milliseconds + * @param array|null $indicatorValues Animated indicator characters + */ + public function __construct(OutputInterface $output, $format = null, $indicatorChangeInterval = 100, $indicatorValues = null) + { + $this->output = $output; + + if (null === $format) { + $format = $this->determineBestFormat(); + } + + if (null === $indicatorValues) { + $indicatorValues = array('-', '\\', '|', '/'); + } + + $indicatorValues = array_values($indicatorValues); + + if (2 > \count($indicatorValues)) { + throw new InvalidArgumentException('Must have at least 2 indicator value characters.'); + } + + $this->format = self::getFormatDefinition($format); + $this->indicatorChangeInterval = $indicatorChangeInterval; + $this->indicatorValues = $indicatorValues; + $this->startTime = time(); + } + + /** + * Sets the current indicator message. + * + * @param string|null $message + */ + public function setMessage($message) + { + $this->message = $message; + + $this->display(); + } + + /** + * Gets the current indicator message. + * + * @return string|null + * + * @internal for PHP 5.3 compatibility + */ + public function getMessage() + { + return $this->message; + } + + /** + * Gets the progress bar start time. + * + * @return int The progress bar start time + * + * @internal for PHP 5.3 compatibility + */ + public function getStartTime() + { + return $this->startTime; + } + + /** + * Gets the current animated indicator character. + * + * @return string + * + * @internal for PHP 5.3 compatibility + */ + public function getCurrentValue() + { + return $this->indicatorValues[$this->indicatorCurrent % \count($this->indicatorValues)]; + } + + /** + * Starts the indicator output. + * + * @param $message + */ + public function start($message) + { + if ($this->started) { + throw new LogicException('Progress indicator already started.'); + } + + $this->message = $message; + $this->started = true; + $this->startTime = time(); + $this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval; + $this->indicatorCurrent = 0; + + $this->display(); + } + + /** + * Advances the indicator. + */ + public function advance() + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + if (!$this->output->isDecorated()) { + return; + } + + $currentTime = $this->getCurrentTimeInMilliseconds(); + + if ($currentTime < $this->indicatorUpdateTime) { + return; + } + + $this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval; + ++$this->indicatorCurrent; + + $this->display(); + } + + /** + * Finish the indicator with message. + * + * @param $message + */ + public function finish($message) + { + if (!$this->started) { + throw new LogicException('Progress indicator has not yet been started.'); + } + + $this->message = $message; + $this->display(); + $this->output->writeln(''); + $this->started = false; + } + + /** + * Gets the format for a given name. + * + * @param string $name The format name + * + * @return string|null A format string + */ + public static function getFormatDefinition($name) + { + if (!self::$formats) { + self::$formats = self::initFormats(); + } + + return isset(self::$formats[$name]) ? self::$formats[$name] : null; + } + + /** + * Sets a placeholder formatter for a given name. + * + * This method also allow you to override an existing placeholder. + * + * @param string $name The placeholder name (including the delimiter char like %) + * @param callable $callable A PHP callable + */ + public static function setPlaceholderFormatterDefinition($name, $callable) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + self::$formatters[$name] = $callable; + } + + /** + * Gets the placeholder formatter for a given name. + * + * @param string $name The placeholder name (including the delimiter char like %) + * + * @return callable|null A PHP callable + */ + public static function getPlaceholderFormatterDefinition($name) + { + if (!self::$formatters) { + self::$formatters = self::initPlaceholderFormatters(); + } + + return isset(self::$formatters[$name]) ? self::$formatters[$name] : null; + } + + private function display() + { + if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $self = $this; + + $this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) use ($self) { + if ($formatter = $self::getPlaceholderFormatterDefinition($matches[1])) { + return \call_user_func($formatter, $self); + } + + return $matches[0]; + }, $this->format)); + } + + private function determineBestFormat() + { + switch ($this->output->getVerbosity()) { + // OutputInterface::VERBOSITY_QUIET: display is disabled anyway + case OutputInterface::VERBOSITY_VERBOSE: + return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi'; + case OutputInterface::VERBOSITY_VERY_VERBOSE: + case OutputInterface::VERBOSITY_DEBUG: + return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi'; + default: + return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi'; + } + } + + /** + * Overwrites a previous message to the output. + * + * @param string $message The message + */ + private function overwrite($message) + { + if ($this->output->isDecorated()) { + $this->output->write("\x0D\x1B[2K"); + $this->output->write($message); + } else { + $this->output->writeln($message); + } + } + + private function getCurrentTimeInMilliseconds() + { + return round(microtime(true) * 1000); + } + + private static function initPlaceholderFormatters() + { + return array( + 'indicator' => function (ProgressIndicator $indicator) { + return $indicator->getCurrentValue(); + }, + 'message' => function (ProgressIndicator $indicator) { + return $indicator->getMessage(); + }, + 'elapsed' => function (ProgressIndicator $indicator) { + return Helper::formatTime(time() - $indicator->getStartTime()); + }, + 'memory' => function () { + return Helper::formatMemory(memory_get_usage(true)); + }, + ); + } + + private static function initFormats() + { + return array( + 'normal' => ' %indicator% %message%', + 'normal_no_ansi' => ' %message%', + + 'verbose' => ' %indicator% %message% (%elapsed:6s%)', + 'verbose_no_ansi' => ' %message% (%elapsed:6s%)', + + 'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)', + 'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)', + ); + } +} diff --git a/vendor/symfony/console/Helper/QuestionHelper.php b/vendor/symfony/console/Helper/QuestionHelper.php new file mode 100644 index 0000000..8c219f1 --- /dev/null +++ b/vendor/symfony/console/Helper/QuestionHelper.php @@ -0,0 +1,456 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * The QuestionHelper class provides helpers to interact with the user. + * + * @author Fabien Potencier + */ +class QuestionHelper extends Helper +{ + private $inputStream; + private static $shell; + private static $stty; + + /** + * Asks a question to the user. + * + * @return mixed The user answer + * + * @throws RuntimeException If there is no data to read in the input stream + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + + if (!$input->isInteractive()) { + $default = $question->getDefault(); + + if (null !== $default && $question instanceof ChoiceQuestion) { + $choices = $question->getChoices(); + + if (!$question->isMultiselect()) { + return isset($choices[$default]) ? $choices[$default] : $default; + } + + $default = explode(',', $default); + foreach ($default as $k => $v) { + $v = trim($v); + $default[$k] = isset($choices[$v]) ? $choices[$v] : $v; + } + } + + return $default; + } + + if (!$question->getValidator()) { + return $this->doAsk($output, $question); + } + + $that = $this; + + $interviewer = function () use ($output, $question, $that) { + return $that->doAsk($output, $question); + }; + + return $this->validateAttempts($interviewer, $output, $question); + } + + /** + * Sets the input stream to read from when interacting with the user. + * + * This is mainly useful for testing purpose. + * + * @param resource $stream The input stream + * + * @throws InvalidArgumentException In case the stream is not a resource + */ + public function setInputStream($stream) + { + if (!\is_resource($stream)) { + throw new InvalidArgumentException('Input stream must be a valid resource.'); + } + + $this->inputStream = $stream; + } + + /** + * Returns the helper's input stream. + * + * @return resource + */ + public function getInputStream() + { + return $this->inputStream; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'question'; + } + + /** + * Asks the question to the user. + * + * This method is public for PHP 5.3 compatibility, it should be private. + * + * @return bool|mixed|string|null + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + public function doAsk(OutputInterface $output, Question $question) + { + $this->writePrompt($output, $question); + + $inputStream = $this->inputStream ?: STDIN; + $autocomplete = $question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($output, $inputStream)); + } catch (RuntimeException $e) { + if (!$question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false))); + } + + $ret = \strlen($ret) > 0 ? $ret : $question->getDefault(); + + if ($normalizer = $question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + /** + * Outputs the question prompt. + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $message = $question->getQuestion(); + + if ($question instanceof ChoiceQuestion) { + $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices()))); + + $messages = (array) $question->getQuestion(); + foreach ($question->getChoices() as $key => $value) { + $width = $maxWidth - $this->strlen($key); + $messages[] = ' ['.$key.str_repeat(' ', $width).'] '.$value; + } + + $output->writeln($messages); + + $message = $question->getPrompt(); + } + + $output->write($message); + } + + /** + * Outputs an error message. + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) { + $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'); + } else { + $message = ''.$error->getMessage().''; + } + + $output->writeln($message); + } + + /** + * Autocompletes a question. + * + * @param OutputInterface $output + * @param Question $question + * @param resource $inputStream + * @param array $autocomplete + * + * @return string + */ + private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete) + { + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = \count($matches); + + $sttyMode = shell_exec('stty -g'); + + // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) + shell_exec('stty -icanon -echo'); + + // Add highlighted text style + $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); + + // Read a keypress + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + // Backspace Character + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + // Move cursor backwards + $output->write("\033[1D"); + } + + if (0 === $i) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = \count($matches); + } else { + $numMatches = 0; + } + + // Pop the last character off the end of our string + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + // Did we read an escape sequence? + $c .= fread($inputStream, 2); + + // A = Up Arrow. B = Down Arrow + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (\ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + // Echo out remaining chars for current match + $output->write(substr($ret, $i)); + $i = \strlen($ret); + } + + if ("\n" === $c) { + $output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) + if (0 === strpos($value, $ret)) { + $matches[$numMatches++] = $value; + } + } + } + + // Erase characters from cursor to end of line + $output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + // Save cursor position + $output->write("\0337"); + // Write highlighted text + $output->write(''.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).''); + // Restore cursor position + $output->write("\0338"); + } + } + + // Reset stty so it behaves normally again + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + /** + * Gets a hidden response from user. + * + * @param OutputInterface $output An Output instance + * @param resource $inputStream The handler resource + * + * @return string The answer + * + * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden + */ + private function getHiddenResponse(OutputInterface $output, $inputStream) + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; + + // handle code running from a phar + if ('phar:' === substr(__FILE__, 0, 5)) { + $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; + copy($exe, $tmpExe); + $exe = $tmpExe; + } + + $value = rtrim(shell_exec($exe)); + $output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new RuntimeException('Aborted'); + } + + $value = trim($value); + $output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $output->writeln(''); + + return $value; + } + + throw new RuntimeException('Unable to hide the response.'); + } + + /** + * Validates an attempt. + * + * @param callable $interviewer A callable that will ask for a question and return the result + * @param OutputInterface $output An Output instance + * @param Question $question A Question instance + * + * @return mixed The validated response + * + * @throws \Exception In case the max number of attempts has been reached and no valid response has been given + */ + private function validateAttempts($interviewer, OutputInterface $output, Question $question) + { + $error = null; + $attempts = $question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->writeError($output, $error); + } + + try { + return \call_user_func($question->getValidator(), $interviewer()); + } catch (RuntimeException $e) { + throw $e; + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * Returns a valid unix shell. + * + * @return string|bool The valid shell name, false in case no valid shell is found + */ + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + // handle other OSs with bash/zsh/ksh/csh if available to hide the answer + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + /** + * Returns whether Stty is available or not. + * + * @return bool + */ + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = 0 === $exitcode; + } +} diff --git a/vendor/symfony/console/Helper/SymfonyQuestionHelper.php b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php new file mode 100644 index 0000000..cfcdf44 --- /dev/null +++ b/vendor/symfony/console/Helper/SymfonyQuestionHelper.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; +use Symfony\Component\Console\Style\SymfonyStyle; + +/** + * Symfony Style Guide compliant question helper. + * + * @author Kevin Bond + */ +class SymfonyQuestionHelper extends QuestionHelper +{ + /** + * {@inheritdoc} + */ + public function ask(InputInterface $input, OutputInterface $output, Question $question) + { + $validator = $question->getValidator(); + $question->setValidator(function ($value) use ($validator) { + if (null !== $validator) { + $value = $validator($value); + } else { + // make required + if (!\is_array($value) && !\is_bool($value) && 0 === \strlen($value)) { + throw new LogicException('A value is required.'); + } + } + + return $value; + }); + + return parent::ask($input, $output, $question); + } + + /** + * {@inheritdoc} + */ + protected function writePrompt(OutputInterface $output, Question $question) + { + $text = OutputFormatter::escapeTrailingBackslash($question->getQuestion()); + $default = $question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $question instanceof ConfirmationQuestion: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $question instanceof ChoiceQuestion && $question->isMultiselect(): + $choices = $question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(implode(', ', $default))); + + break; + + case $question instanceof ChoiceQuestion: + $choices = $question->getChoices(); + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape(isset($choices[$default]) ? $choices[$default] : $default)); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, OutputFormatter::escape($default)); + } + + $output->writeln($text); + + if ($question instanceof ChoiceQuestion) { + $width = max(array_map('strlen', array_keys($question->getChoices()))); + + foreach ($question->getChoices() as $key => $value) { + $output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $output->write(' > '); + } + + /** + * {@inheritdoc} + */ + protected function writeError(OutputInterface $output, \Exception $error) + { + if ($output instanceof SymfonyStyle) { + $output->newLine(); + $output->error($error->getMessage()); + + return; + } + + parent::writeError($output, $error); + } +} diff --git a/vendor/symfony/console/Helper/Table.php b/vendor/symfony/console/Helper/Table.php new file mode 100644 index 0000000..b5c0e91 --- /dev/null +++ b/vendor/symfony/console/Helper/Table.php @@ -0,0 +1,661 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Provides helpers to display a table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + * @author Abdellatif Ait boudad + * @author Max Grigorian + */ +class Table +{ + /** + * Table headers. + */ + private $headers = array(); + + /** + * Table rows. + */ + private $rows = array(); + + /** + * Column widths cache. + */ + private $columnWidths = array(); + + /** + * Number of columns cache. + * + * @var int + */ + private $numberOfColumns; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var TableStyle + */ + private $style; + + /** + * @var array + */ + private $columnStyles = array(); + + private static $styles; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + $this->setStyle('default'); + } + + /** + * Sets a style definition. + * + * @param string $name The style name + * @param TableStyle $style A TableStyle instance + */ + public static function setStyleDefinition($name, TableStyle $style) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + self::$styles[$name] = $style; + } + + /** + * Gets a style definition by name. + * + * @param string $name The style name + * + * @return TableStyle + */ + public static function getStyleDefinition($name) + { + if (!self::$styles) { + self::$styles = self::initStyles(); + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } + + /** + * Sets table style. + * + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setStyle($name) + { + $this->style = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current table style. + * + * @return TableStyle + */ + public function getStyle() + { + return $this->style; + } + + /** + * Sets table column style. + * + * @param int $columnIndex Column index + * @param TableStyle|string $name The style name or a TableStyle instance + * + * @return $this + */ + public function setColumnStyle($columnIndex, $name) + { + $columnIndex = (int) $columnIndex; + + $this->columnStyles[$columnIndex] = $this->resolveStyle($name); + + return $this; + } + + /** + * Gets the current style for a column. + * + * If style was not set, it returns the global table style. + * + * @param int $columnIndex Column index + * + * @return TableStyle + */ + public function getColumnStyle($columnIndex) + { + if (isset($this->columnStyles[$columnIndex])) { + return $this->columnStyles[$columnIndex]; + } + + return $this->getStyle(); + } + + public function setHeaders(array $headers) + { + $headers = array_values($headers); + if (!empty($headers) && !\is_array($headers[0])) { + $headers = array($headers); + } + + $this->headers = $headers; + + return $this; + } + + public function setRows(array $rows) + { + $this->rows = array(); + + return $this->addRows($rows); + } + + public function addRows(array $rows) + { + foreach ($rows as $row) { + $this->addRow($row); + } + + return $this; + } + + public function addRow($row) + { + if ($row instanceof TableSeparator) { + $this->rows[] = $row; + + return $this; + } + + if (!\is_array($row)) { + throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.'); + } + + $this->rows[] = array_values($row); + + return $this; + } + + public function setRow($column, array $row) + { + $this->rows[$column] = $row; + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + */ + public function render() + { + $this->calculateNumberOfColumns(); + $rows = $this->buildTableRows($this->rows); + $headers = $this->buildTableRows($this->headers); + + $this->calculateColumnsWidth(array_merge($headers, $rows)); + + $this->renderRowSeparator(); + if (!empty($headers)) { + foreach ($headers as $header) { + $this->renderRow($header, $this->style->getCellHeaderFormat()); + $this->renderRowSeparator(); + } + } + foreach ($rows as $row) { + if ($row instanceof TableSeparator) { + $this->renderRowSeparator(); + } else { + $this->renderRow($row, $this->style->getCellRowFormat()); + } + } + if (!empty($rows)) { + $this->renderRowSeparator(); + } + + $this->cleanup(); + } + + /** + * Renders horizontal header separator. + * + * Example: + * + * +-----+-----------+-------+ + */ + private function renderRowSeparator() + { + if (0 === $count = $this->numberOfColumns) { + return; + } + + if (!$this->style->getHorizontalBorderChar() && !$this->style->getCrossingChar()) { + return; + } + + $markup = $this->style->getCrossingChar(); + for ($column = 0; $column < $count; ++$column) { + $markup .= str_repeat($this->style->getHorizontalBorderChar(), $this->columnWidths[$column]).$this->style->getCrossingChar(); + } + + $this->output->writeln(sprintf($this->style->getBorderFormat(), $markup)); + } + + /** + * Renders vertical column separator. + */ + private function renderColumnSeparator() + { + return sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar()); + } + + /** + * Renders table row. + * + * Example: + * + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * + * @param array $row + * @param string $cellFormat + */ + private function renderRow(array $row, $cellFormat) + { + if (empty($row)) { + return; + } + + $rowContent = $this->renderColumnSeparator(); + foreach ($this->getRowColumns($row) as $column) { + $rowContent .= $this->renderCell($row, $column, $cellFormat); + $rowContent .= $this->renderColumnSeparator(); + } + $this->output->writeln($rowContent); + } + + /** + * Renders table cell with padding. + * + * @param array $row + * @param int $column + * @param string $cellFormat + */ + private function renderCell(array $row, $column, $cellFormat) + { + $cell = isset($row[$column]) ? $row[$column] : ''; + $width = $this->columnWidths[$column]; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // add the width of the following columns(numbers of colspan). + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) { + $width += $this->getColumnSeparatorWidth() + $this->columnWidths[$nextColumn]; + } + } + + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding($cell, null, true)) { + $width += \strlen($cell) - mb_strwidth($cell, $encoding); + } + + $style = $this->getColumnStyle($column); + + if ($cell instanceof TableSeparator) { + return sprintf($style->getBorderFormat(), str_repeat($style->getHorizontalBorderChar(), $width)); + } + + $width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + $content = sprintf($style->getCellRowContentFormat(), $cell); + + return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $style->getPadType())); + } + + /** + * Calculate number of columns for this table. + */ + private function calculateNumberOfColumns() + { + if (null !== $this->numberOfColumns) { + return; + } + + $columns = array(0); + foreach (array_merge($this->headers, $this->rows) as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + $columns[] = $this->getNumberOfColumns($row); + } + + $this->numberOfColumns = max($columns); + } + + private function buildTableRows($rows) + { + $unmergedRows = array(); + for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) { + $rows = $this->fillNextRows($rows, $rowKey); + + // Remove any new line breaks and replace it with a new line + foreach ($rows[$rowKey] as $column => $cell) { + if (!strstr($cell, "\n")) { + continue; + } + $lines = explode("\n", str_replace("\n", "\n", $cell)); + foreach ($lines as $lineKey => $line) { + if ($cell instanceof TableCell) { + $line = new TableCell($line, array('colspan' => $cell->getColspan())); + } + if (0 === $lineKey) { + $rows[$rowKey][$column] = $line; + } else { + $unmergedRows[$rowKey][$lineKey][$column] = $line; + } + } + } + } + + $tableRows = array(); + foreach ($rows as $rowKey => $row) { + $tableRows[] = $this->fillCells($row); + if (isset($unmergedRows[$rowKey])) { + $tableRows = array_merge($tableRows, $unmergedRows[$rowKey]); + } + } + + return $tableRows; + } + + /** + * fill rows that contains rowspan > 1. + * + * @param array $rows + * @param int $line + * + * @return array + */ + private function fillNextRows(array $rows, $line) + { + $unmergedRows = array(); + foreach ($rows[$line] as $column => $cell) { + if ($cell instanceof TableCell && $cell->getRowspan() > 1) { + $nbLines = $cell->getRowspan() - 1; + $lines = array($cell); + if (strstr($cell, "\n")) { + $lines = explode("\n", str_replace("\n", "\n", $cell)); + $nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines; + + $rows[$line][$column] = new TableCell($lines[0], array('colspan' => $cell->getColspan())); + unset($lines[0]); + } + + // create a two dimensional array (rowspan x colspan) + $unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, array()), $unmergedRows); + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + $value = isset($lines[$unmergedRowKey - $line]) ? $lines[$unmergedRowKey - $line] : ''; + $unmergedRows[$unmergedRowKey][$column] = new TableCell($value, array('colspan' => $cell->getColspan())); + if ($nbLines === $unmergedRowKey - $line) { + break; + } + } + } + } + + foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) { + // we need to know if $unmergedRow will be merged or inserted into $rows + if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) { + foreach ($unmergedRow as $cellKey => $cell) { + // insert cell into row at cellKey position + array_splice($rows[$unmergedRowKey], $cellKey, 0, array($cell)); + } + } else { + $row = $this->copyRow($rows, $unmergedRowKey - 1); + foreach ($unmergedRow as $column => $cell) { + if (!empty($cell)) { + $row[$column] = $unmergedRow[$column]; + } + } + array_splice($rows, $unmergedRowKey, 0, array($row)); + } + } + + return $rows; + } + + /** + * fill cells for a row that contains colspan > 1. + * + * @return array + */ + private function fillCells($row) + { + $newRow = array(); + foreach ($row as $column => $cell) { + $newRow[] = $cell; + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) { + // insert empty value at column position + $newRow[] = ''; + } + } + } + + return $newRow ?: $row; + } + + /** + * @param array $rows + * @param int $line + * + * @return array + */ + private function copyRow(array $rows, $line) + { + $row = $rows[$line]; + foreach ($row as $cellKey => $cellValue) { + $row[$cellKey] = ''; + if ($cellValue instanceof TableCell) { + $row[$cellKey] = new TableCell('', array('colspan' => $cellValue->getColspan())); + } + } + + return $row; + } + + /** + * Gets number of columns by row. + * + * @return int + */ + private function getNumberOfColumns(array $row) + { + $columns = \count($row); + foreach ($row as $column) { + $columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0; + } + + return $columns; + } + + /** + * Gets list of columns for the given row. + * + * @return array + */ + private function getRowColumns(array $row) + { + $columns = range(0, $this->numberOfColumns - 1); + foreach ($row as $cellKey => $cell) { + if ($cell instanceof TableCell && $cell->getColspan() > 1) { + // exclude grouped columns. + $columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1)); + } + } + + return $columns; + } + + /** + * Calculates columns widths. + * + * @param array $rows + */ + private function calculateColumnsWidth($rows) + { + for ($column = 0; $column < $this->numberOfColumns; ++$column) { + $lengths = array(); + foreach ($rows as $row) { + if ($row instanceof TableSeparator) { + continue; + } + + foreach ($row as $i => $cell) { + if ($cell instanceof TableCell) { + $textContent = Helper::removeDecoration($this->output->getFormatter(), $cell); + $textLength = Helper::strlen($textContent); + if ($textLength > 0) { + $contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan())); + foreach ($contentColumns as $position => $content) { + $row[$i + $position] = $content; + } + } + } + } + + $lengths[] = $this->getCellWidth($row, $column); + } + + $this->columnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2; + } + } + + /** + * Gets column width. + * + * @return int + */ + private function getColumnSeparatorWidth() + { + return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getVerticalBorderChar())); + } + + /** + * Gets cell width. + * + * @param array $row + * @param int $column + * + * @return int + */ + private function getCellWidth(array $row, $column) + { + if (isset($row[$column])) { + $cell = $row[$column]; + $cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell); + + return $cellWidth; + } + + return 0; + } + + /** + * Called after rendering to cleanup cache data. + */ + private function cleanup() + { + $this->columnWidths = array(); + $this->numberOfColumns = null; + } + + private static function initStyles() + { + $borderless = new TableStyle(); + $borderless + ->setHorizontalBorderChar('=') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ; + + $compact = new TableStyle(); + $compact + ->setHorizontalBorderChar('') + ->setVerticalBorderChar(' ') + ->setCrossingChar('') + ->setCellRowContentFormat('%s') + ; + + $styleGuide = new TableStyle(); + $styleGuide + ->setHorizontalBorderChar('-') + ->setVerticalBorderChar(' ') + ->setCrossingChar(' ') + ->setCellHeaderFormat('%s') + ; + + return array( + 'default' => new TableStyle(), + 'borderless' => $borderless, + 'compact' => $compact, + 'symfony-style-guide' => $styleGuide, + ); + } + + private function resolveStyle($name) + { + if ($name instanceof TableStyle) { + return $name; + } + + if (isset(self::$styles[$name])) { + return self::$styles[$name]; + } + + throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name)); + } +} diff --git a/vendor/symfony/console/Helper/TableCell.php b/vendor/symfony/console/Helper/TableCell.php new file mode 100644 index 0000000..fb6c9fd --- /dev/null +++ b/vendor/symfony/console/Helper/TableCell.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * @author Abdellatif Ait boudad + */ +class TableCell +{ + private $value; + private $options = array( + 'rowspan' => 1, + 'colspan' => 1, + ); + + /** + * @param string $value + * @param array $options + */ + public function __construct($value = '', array $options = array()) + { + if (is_numeric($value) && !\is_string($value)) { + $value = (string) $value; + } + + $this->value = $value; + + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * Returns the cell value. + * + * @return string + */ + public function __toString() + { + return $this->value; + } + + /** + * Gets number of colspan. + * + * @return int + */ + public function getColspan() + { + return (int) $this->options['colspan']; + } + + /** + * Gets number of rowspan. + * + * @return int + */ + public function getRowspan() + { + return (int) $this->options['rowspan']; + } +} diff --git a/vendor/symfony/console/Helper/TableHelper.php b/vendor/symfony/console/Helper/TableHelper.php new file mode 100644 index 0000000..3d2095a --- /dev/null +++ b/vendor/symfony/console/Helper/TableHelper.php @@ -0,0 +1,264 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Output\NullOutput; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Provides helpers to display table output. + * + * @author Саша Стаменковић + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0 + * Use {@link Table} instead. + */ +class TableHelper extends Helper +{ + const LAYOUT_DEFAULT = 0; + const LAYOUT_BORDERLESS = 1; + const LAYOUT_COMPACT = 2; + + private $table; + + public function __construct($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.5 and will be removed in 3.0. Use the Symfony\Component\Console\Helper\Table class instead.', E_USER_DEPRECATED); + } + + $this->table = new Table(new NullOutput()); + } + + /** + * Sets table layout type. + * + * @param int $layout self::LAYOUT_* + * + * @return $this + * + * @throws InvalidArgumentException when the table layout is not known + */ + public function setLayout($layout) + { + switch ($layout) { + case self::LAYOUT_BORDERLESS: + $this->table->setStyle('borderless'); + break; + + case self::LAYOUT_COMPACT: + $this->table->setStyle('compact'); + break; + + case self::LAYOUT_DEFAULT: + $this->table->setStyle('default'); + break; + + default: + throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout)); + } + + return $this; + } + + public function setHeaders(array $headers) + { + $this->table->setHeaders($headers); + + return $this; + } + + public function setRows(array $rows) + { + $this->table->setRows($rows); + + return $this; + } + + public function addRows(array $rows) + { + $this->table->addRows($rows); + + return $this; + } + + public function addRow(array $row) + { + $this->table->addRow($row); + + return $this; + } + + public function setRow($column, array $row) + { + $this->table->setRow($column, $row); + + return $this; + } + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return $this + */ + public function setPaddingChar($paddingChar) + { + $this->table->getStyle()->setPaddingChar($paddingChar); + + return $this; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return $this + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->table->getStyle()->setHorizontalBorderChar($horizontalBorderChar); + + return $this; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return $this + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->table->getStyle()->setVerticalBorderChar($verticalBorderChar); + + return $this; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return $this + */ + public function setCrossingChar($crossingChar) + { + $this->table->getStyle()->setCrossingChar($crossingChar); + + return $this; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return $this + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->table->getStyle()->setCellHeaderFormat($cellHeaderFormat); + + return $this; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return $this + */ + public function setCellRowFormat($cellRowFormat) + { + $this->table->getStyle()->setCellHeaderFormat($cellRowFormat); + + return $this; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return $this + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->table->getStyle()->setCellRowContentFormat($cellRowContentFormat); + + return $this; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return $this + */ + public function setBorderFormat($borderFormat) + { + $this->table->getStyle()->setBorderFormat($borderFormat); + + return $this; + } + + /** + * Sets cell padding type. + * + * @param int $padType STR_PAD_* + * + * @return $this + */ + public function setPadType($padType) + { + $this->table->getStyle()->setPadType($padType); + + return $this; + } + + /** + * Renders table to output. + * + * Example: + * +---------------+-----------------------+------------------+ + * | ISBN | Title | Author | + * +---------------+-----------------------+------------------+ + * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | + * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | + * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | + * +---------------+-----------------------+------------------+ + */ + public function render(OutputInterface $output) + { + $p = new \ReflectionProperty($this->table, 'output'); + $p->setAccessible(true); + $p->setValue($this->table, $output); + + $this->table->render(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'table'; + } +} diff --git a/vendor/symfony/console/Helper/TableSeparator.php b/vendor/symfony/console/Helper/TableSeparator.php new file mode 100644 index 0000000..c7b8dc9 --- /dev/null +++ b/vendor/symfony/console/Helper/TableSeparator.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +/** + * Marks a row as being a separator. + * + * @author Fabien Potencier + */ +class TableSeparator extends TableCell +{ + public function __construct(array $options = array()) + { + parent::__construct('', $options); + } +} diff --git a/vendor/symfony/console/Helper/TableStyle.php b/vendor/symfony/console/Helper/TableStyle.php new file mode 100644 index 0000000..0ee9dd1 --- /dev/null +++ b/vendor/symfony/console/Helper/TableStyle.php @@ -0,0 +1,258 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Helper; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Defines the styles for a Table. + * + * @author Fabien Potencier + * @author Саша Стаменковић + */ +class TableStyle +{ + private $paddingChar = ' '; + private $horizontalBorderChar = '-'; + private $verticalBorderChar = '|'; + private $crossingChar = '+'; + private $cellHeaderFormat = '%s'; + private $cellRowFormat = '%s'; + private $cellRowContentFormat = ' %s '; + private $borderFormat = '%s'; + private $padType = STR_PAD_RIGHT; + + /** + * Sets padding character, used for cell padding. + * + * @param string $paddingChar + * + * @return $this + */ + public function setPaddingChar($paddingChar) + { + if (!$paddingChar) { + throw new LogicException('The padding char must not be empty'); + } + + $this->paddingChar = $paddingChar; + + return $this; + } + + /** + * Gets padding character, used for cell padding. + * + * @return string + */ + public function getPaddingChar() + { + return $this->paddingChar; + } + + /** + * Sets horizontal border character. + * + * @param string $horizontalBorderChar + * + * @return $this + */ + public function setHorizontalBorderChar($horizontalBorderChar) + { + $this->horizontalBorderChar = $horizontalBorderChar; + + return $this; + } + + /** + * Gets horizontal border character. + * + * @return string + */ + public function getHorizontalBorderChar() + { + return $this->horizontalBorderChar; + } + + /** + * Sets vertical border character. + * + * @param string $verticalBorderChar + * + * @return $this + */ + public function setVerticalBorderChar($verticalBorderChar) + { + $this->verticalBorderChar = $verticalBorderChar; + + return $this; + } + + /** + * Gets vertical border character. + * + * @return string + */ + public function getVerticalBorderChar() + { + return $this->verticalBorderChar; + } + + /** + * Sets crossing character. + * + * @param string $crossingChar + * + * @return $this + */ + public function setCrossingChar($crossingChar) + { + $this->crossingChar = $crossingChar; + + return $this; + } + + /** + * Gets crossing character. + * + * @return string + */ + public function getCrossingChar() + { + return $this->crossingChar; + } + + /** + * Sets header cell format. + * + * @param string $cellHeaderFormat + * + * @return $this + */ + public function setCellHeaderFormat($cellHeaderFormat) + { + $this->cellHeaderFormat = $cellHeaderFormat; + + return $this; + } + + /** + * Gets header cell format. + * + * @return string + */ + public function getCellHeaderFormat() + { + return $this->cellHeaderFormat; + } + + /** + * Sets row cell format. + * + * @param string $cellRowFormat + * + * @return $this + */ + public function setCellRowFormat($cellRowFormat) + { + $this->cellRowFormat = $cellRowFormat; + + return $this; + } + + /** + * Gets row cell format. + * + * @return string + */ + public function getCellRowFormat() + { + return $this->cellRowFormat; + } + + /** + * Sets row cell content format. + * + * @param string $cellRowContentFormat + * + * @return $this + */ + public function setCellRowContentFormat($cellRowContentFormat) + { + $this->cellRowContentFormat = $cellRowContentFormat; + + return $this; + } + + /** + * Gets row cell content format. + * + * @return string + */ + public function getCellRowContentFormat() + { + return $this->cellRowContentFormat; + } + + /** + * Sets table border format. + * + * @param string $borderFormat + * + * @return $this + */ + public function setBorderFormat($borderFormat) + { + $this->borderFormat = $borderFormat; + + return $this; + } + + /** + * Gets table border format. + * + * @return string + */ + public function getBorderFormat() + { + return $this->borderFormat; + } + + /** + * Sets cell padding type. + * + * @param int $padType STR_PAD_* + * + * @return $this + */ + public function setPadType($padType) + { + if (!\in_array($padType, array(STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH), true)) { + throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).'); + } + + $this->padType = $padType; + + return $this; + } + + /** + * Gets cell padding type. + * + * @return int + */ + public function getPadType() + { + return $this->padType; + } +} diff --git a/vendor/symfony/console/Input/ArgvInput.php b/vendor/symfony/console/Input/ArgvInput.php new file mode 100644 index 0000000..2735fce --- /dev/null +++ b/vendor/symfony/console/Input/ArgvInput.php @@ -0,0 +1,348 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * ArgvInput represents an input coming from the CLI arguments. + * + * Usage: + * + * $input = new ArgvInput(); + * + * By default, the `$_SERVER['argv']` array is used for the input values. + * + * This can be overridden by explicitly passing the input values in the constructor: + * + * $input = new ArgvInput($_SERVER['argv']); + * + * If you pass it yourself, don't forget that the first element of the array + * is the name of the running application. + * + * When passing an argument to the constructor, be sure that it respects + * the same rules as the argv one. It's almost always better to use the + * `StringInput` when you want to provide your own input. + * + * @author Fabien Potencier + * + * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html + * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 + */ +class ArgvInput extends Input +{ + private $tokens; + private $parsed; + + /** + * @param array|null $argv An array of parameters from the CLI (in the argv format) + * @param InputDefinition|null $definition A InputDefinition instance + */ + public function __construct(array $argv = null, InputDefinition $definition = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + } + + // strip the application name + array_shift($argv); + + $this->tokens = $argv; + + parent::__construct($definition); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * Parses a short option. + * + * @param string $token The current token + */ + private function parseShortOption($token) + { + $name = substr($token, 1); + + if (\strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { + // an option with a value (with no space) + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * Parses a short option set. + * + * @param string $name The current token + * + * @throws RuntimeException When option given doesn't exist + */ + private function parseShortOptionSet($name) + { + $len = \strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + $encoding = mb_detect_encoding($name, null, true); + throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding))); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * Parses a long option. + * + * @param string $token The current token + */ + private function parseLongOption($token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + if (0 === \strlen($value = substr($name, $pos + 1))) { + array_unshift($this->parsed, null); + } + $this->addLongOption(substr($name, 0, $pos), $value); + } else { + $this->addLongOption($name, null); + } + } + + /** + * Parses an argument. + * + * @param string $token The current token + * + * @throws RuntimeException When too many arguments are given + */ + private function parseArgument($token) + { + $c = \count($this->arguments); + + // if input is expecting another argument, add it + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + $this->arguments[$arg->getName()] = $arg->isArray() ? array($token) : $token; + + // if last argument isArray(), append token to last argument + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + $this->arguments[$arg->getName()][] = $token; + + // unexpected argument + } else { + $all = $this->definition->getArguments(); + if (\count($all)) { + throw new RuntimeException(sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)))); + } + + throw new RuntimeException(sprintf('No arguments expected, got "%s".', $token)); + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws RuntimeException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws RuntimeException When option given doesn't exist + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + // Convert empty values to null + if (!isset($value[0])) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name)); + } + + if (null === $value && $option->acceptValue() && \count($this->parsed)) { + // if option accepts an optional or mandatory argument + // let's see if there is one provided + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = null; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = 0 === strpos($value, '--') ? $value.'=' : $value; + if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) { + return true; + } + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < \count($tokens)) { + $token = array_shift($tokens); + + foreach ($values as $value) { + if ($token === $value) { + return array_shift($tokens); + } + // Options with values: + // For long options, test for '--option=' at beginning + // For short options, test for '-o' at beginning + $leading = 0 === strpos($value, '--') ? $value.'=' : $value; + if ('' !== $leading && 0 === strpos($token, $leading)) { + return substr($token, \strlen($leading)); + } + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $self = $this; + $tokens = array_map(function ($token) use ($self) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1].$self->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $self->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/vendor/symfony/console/Input/ArrayInput.php b/vendor/symfony/console/Input/ArrayInput.php new file mode 100644 index 0000000..4f93605 --- /dev/null +++ b/vendor/symfony/console/Input/ArrayInput.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\InvalidOptionException; + +/** + * ArrayInput represents an input provided as an array. + * + * Usage: + * + * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); + * + * @author Fabien Potencier + */ +class ArrayInput extends Input +{ + private $parameters; + + public function __construct(array $parameters, InputDefinition $definition = null) + { + $this->parameters = $parameters; + + parent::__construct($definition); + } + + /** + * {@inheritdoc} + */ + public function getFirstArgument() + { + foreach ($this->parameters as $key => $value) { + if ($key && '-' === $key[0]) { + continue; + } + + return $value; + } + } + + /** + * {@inheritdoc} + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (!\is_int($k)) { + $v = $k; + } + + if (\in_array($v, $values)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + + foreach ($this->parameters as $k => $v) { + if (\is_int($k)) { + if (\in_array($v, $values)) { + return true; + } + } elseif (\in_array($k, $values)) { + return $v; + } + } + + return $default; + } + + /** + * Returns a stringified representation of the args passed to the command. + * + * @return string + */ + public function __toString() + { + $params = array(); + foreach ($this->parameters as $param => $val) { + if ($param && '-' === $param[0]) { + if (\is_array($val)) { + foreach ($val as $v) { + $params[] = $param.('' != $v ? '='.$this->escapeToken($v) : ''); + } + } else { + $params[] = $param.('' != $val ? '='.$this->escapeToken($val) : ''); + } + } else { + $params[] = \is_array($val) ? implode(' ', array_map(array($this, 'escapeToken'), $val)) : $this->escapeToken($val); + } + } + + return implode(' ', $params); + } + + /** + * {@inheritdoc} + */ + protected function parse() + { + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, '--')) { + $this->addLongOption(substr($key, 2), $value); + } elseif ('-' === $key[0]) { + $this->addShortOption(substr($key, 1), $value); + } else { + $this->addArgument($key, $value); + } + } + } + + /** + * Adds a short option value. + * + * @param string $shortcut The short option key + * @param mixed $value The value for the option + * + * @throws InvalidOptionException When option given doesn't exist + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * Adds a long option value. + * + * @param string $name The long option key + * @param mixed $value The value for the option + * + * @throws InvalidOptionException When option given doesn't exist + * @throws InvalidOptionException When a required value is missing + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (null === $value) { + if ($option->isValueRequired()) { + throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name)); + } + + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + + $this->options[$name] = $value; + } + + /** + * Adds an argument value. + * + * @param string $name The argument name + * @param mixed $value The value for the argument + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + private function addArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } +} diff --git a/vendor/symfony/console/Input/Input.php b/vendor/symfony/console/Input/Input.php new file mode 100644 index 0000000..1c1e2fb --- /dev/null +++ b/vendor/symfony/console/Input/Input.php @@ -0,0 +1,186 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * Input is the base class for all concrete Input classes. + * + * Three concrete classes are provided by default: + * + * * `ArgvInput`: The input comes from the CLI arguments (argv) + * * `StringInput`: The input is provided as a string + * * `ArrayInput`: The input is provided as an array + * + * @author Fabien Potencier + */ +abstract class Input implements InputInterface +{ + protected $definition; + protected $options = array(); + protected $arguments = array(); + protected $interactive = true; + + public function __construct(InputDefinition $definition = null) + { + if (null === $definition) { + $this->definition = new InputDefinition(); + } else { + $this->bind($definition); + $this->validate(); + } + } + + /** + * {@inheritdoc} + */ + public function bind(InputDefinition $definition) + { + $this->arguments = array(); + $this->options = array(); + $this->definition = $definition; + + $this->parse(); + } + + /** + * Processes command line arguments. + */ + abstract protected function parse(); + + /** + * {@inheritdoc} + */ + public function validate() + { + $definition = $this->definition; + $givenArguments = $this->arguments; + + $missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) { + return !array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired(); + }); + + if (\count($missingArguments) > 0) { + throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments))); + } + } + + /** + * {@inheritdoc} + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * {@inheritdoc} + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * {@inheritdoc} + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * {@inheritdoc} + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * {@inheritdoc} + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * {@inheritdoc} + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * {@inheritdoc} + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function hasOption($name) + { + return $this->definition->hasOption($name); + } + + /** + * Escapes a token through escapeshellarg if it contains unsafe chars. + * + * @param string $token + * + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } +} diff --git a/vendor/symfony/console/Input/InputArgument.php b/vendor/symfony/console/Input/InputArgument.php new file mode 100644 index 0000000..5b3b98b --- /dev/null +++ b/vendor/symfony/console/Input/InputArgument.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line argument. + * + * @author Fabien Potencier + */ +class InputArgument +{ + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * @param string $name The argument name + * @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL + * @param string $description A description text + * @param string|string[]|null $default The default value (for self::OPTIONAL mode only) + * + * @throws InvalidArgumentException When argument mode is not valid + */ + public function __construct($name, $mode = null, $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!\is_int($mode) || $mode > 7 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * Returns the argument name. + * + * @return string The argument name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the argument is required. + * + * @return bool true if parameter mode is self::REQUIRED, false otherwise + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * Returns true if the argument can take multiple values. + * + * @return bool true if mode is self::IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param string|string[]|null $default The default value + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = array(); + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * Returns the default value. + * + * @return string|string[]|null The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/vendor/symfony/console/Input/InputAwareInterface.php b/vendor/symfony/console/Input/InputAwareInterface.php new file mode 100644 index 0000000..5a288de --- /dev/null +++ b/vendor/symfony/console/Input/InputAwareInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +/** + * InputAwareInterface should be implemented by classes that depends on the + * Console Input. + * + * @author Wouter J + */ +interface InputAwareInterface +{ + /** + * Sets the Console Input. + */ + public function setInput(InputInterface $input); +} diff --git a/vendor/symfony/console/Input/InputDefinition.php b/vendor/symfony/console/Input/InputDefinition.php new file mode 100644 index 0000000..7d40881 --- /dev/null +++ b/vendor/symfony/console/Input/InputDefinition.php @@ -0,0 +1,448 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Descriptor\TextDescriptor; +use Symfony\Component\Console\Descriptor\XmlDescriptor; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; +use Symfony\Component\Console\Output\BufferedOutput; + +/** + * A InputDefinition represents a set of valid command line arguments and options. + * + * Usage: + * + * $definition = new InputDefinition(array( + * new InputArgument('name', InputArgument::REQUIRED), + * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), + * )); + * + * @author Fabien Potencier + */ +class InputDefinition +{ + private $arguments; + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + private $options; + private $shortcuts; + + /** + * @param array $definition An array of InputArgument and InputOption instance + */ + public function __construct(array $definition = array()) + { + $this->setDefinition($definition); + } + + /** + * Sets the definition of the input. + */ + public function setDefinition(array $definition) + { + $arguments = array(); + $options = array(); + foreach ($definition as $item) { + if ($item instanceof InputOption) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * Sets the InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function setArguments($arguments = array()) + { + $this->arguments = array(); + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * Adds an array of InputArgument objects. + * + * @param InputArgument[] $arguments An array of InputArgument objects + */ + public function addArguments($arguments = array()) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * @throws LogicException When incorrect argument is given + */ + public function addArgument(InputArgument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * Returns an InputArgument by name or by position. + * + * @param string|int $name The InputArgument name or position + * + * @return InputArgument An InputArgument object + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name) + { + $arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * Gets the array of InputArgument objects. + * + * @return InputArgument[] An array of InputArgument objects + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Returns the number of InputArguments. + * + * @return int The number of InputArguments + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : \count($this->arguments); + } + + /** + * Returns the number of required InputArguments. + * + * @return int The number of required InputArguments + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * Gets the default values. + * + * @return array An array of default values + */ + public function getArgumentDefaults() + { + $values = array(); + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * Sets the InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function setOptions($options = array()) + { + $this->options = array(); + $this->shortcuts = array(); + $this->addOptions($options); + } + + /** + * Adds an array of InputOption objects. + * + * @param InputOption[] $options An array of InputOption objects + */ + public function addOptions($options = array()) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * @throws LogicException When option given already exist + */ + public function addOption(InputOption $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { + throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * Returns an InputOption by name. + * + * @param string $name The InputOption name + * + * @return InputOption A InputOption object + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * Returns true if an InputOption object exists by name. + * + * This method can't be used to check if the user included the option when + * executing the command (use getOption() instead). + * + * @param string $name The InputOption name + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * Gets the array of InputOption objects. + * + * @return InputOption[] An array of InputOption objects + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns true if an InputOption object exists by shortcut. + * + * @param string $name The InputOption shortcut + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * Gets an InputOption by shortcut. + * + * @param string $shortcut The Shortcut name + * + * @return InputOption An InputOption object + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * Gets an array of default values. + * + * @return array An array of all default values + */ + public function getOptionDefaults() + { + $values = array(); + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * Returns the InputOption name given a shortcut. + * + * @param string $shortcut The shortcut + * + * @return string The InputOption name + * + * @throws InvalidArgumentException When option given does not exist + */ + private function shortcutToName($shortcut) + { + if (!isset($this->shortcuts[$shortcut])) { + throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * Gets the synopsis. + * + * @param bool $short Whether to return the short version (with options folded) or not + * + * @return string The synopsis + */ + public function getSynopsis($short = false) + { + $elements = array(); + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf( + ' %s%s%s', + $option->isValueOptional() ? '[' : '', + strtoupper($option->getName()), + $option->isValueOptional() ? ']' : '' + ); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (\count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<'.$argument->getName().'>'; + if (!$argument->isRequired()) { + $element = '['.$element.']'; + } elseif ($argument->isArray()) { + $element .= ' ('.$element.')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } + + /** + * Returns a textual representation of the InputDefinition. + * + * @return string A string representing the InputDefinition + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asText() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new TextDescriptor(); + $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); + $descriptor->describe($output, $this, array('raw_output' => true)); + + return $output->fetch(); + } + + /** + * Returns an XML representation of the InputDefinition. + * + * @param bool $asDom Whether to return a DOM or an XML string + * + * @return string|\DOMDocument An XML string representing the InputDefinition + * + * @deprecated since version 2.3, to be removed in 3.0. + */ + public function asXml($asDom = false) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0.', E_USER_DEPRECATED); + + $descriptor = new XmlDescriptor(); + + if ($asDom) { + return $descriptor->getInputDefinitionDocument($this); + } + + $output = new BufferedOutput(); + $descriptor->describe($output, $this); + + return $output->fetch(); + } +} diff --git a/vendor/symfony/console/Input/InputInterface.php b/vendor/symfony/console/Input/InputInterface.php new file mode 100644 index 0000000..7a4efbe --- /dev/null +++ b/vendor/symfony/console/Input/InputInterface.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; + +/** + * InputInterface is the interface implemented by all input classes. + * + * @author Fabien Potencier + */ +interface InputInterface +{ + /** + * Returns the first argument from the raw parameters (not parsed). + * + * @return string|null The value of the first argument or null otherwise + */ + public function getFirstArgument(); + + /** + * Returns true if the raw parameters (not parsed) contain a value. + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The values to look for in the raw parameters (can be an array) + * + * @return bool true if the value is contained in the raw parameters + */ + public function hasParameterOption($values); + + /** + * Returns the value of a raw option (not parsed). + * + * This method is to be used to introspect the input parameters + * before they have been validated. It must be used carefully. + * Does not necessarily return the correct result for short options + * when multiple flags are combined in the same option. + * + * @param string|array $values The value(s) to look for in the raw parameters (can be an array) + * @param mixed $default The default value to return if no result is found + * + * @return mixed The option value + */ + public function getParameterOption($values, $default = false); + + /** + * Binds the current Input instance with the given arguments and options. + * + * @throws RuntimeException + */ + public function bind(InputDefinition $definition); + + /** + * Validates the input. + * + * @throws RuntimeException When not enough arguments are given + */ + public function validate(); + + /** + * Returns all the given arguments merged with the default values. + * + * @return array + */ + public function getArguments(); + + /** + * Returns the argument value for a given argument name. + * + * @param string $name The argument name + * + * @return string|string[]|null The argument value + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function getArgument($name); + + /** + * Sets an argument value by name. + * + * @param string $name The argument name + * @param string|string[]|null $value The argument value + * + * @throws InvalidArgumentException When argument given doesn't exist + */ + public function setArgument($name, $value); + + /** + * Returns true if an InputArgument object exists by name or position. + * + * @param string|int $name The InputArgument name or position + * + * @return bool true if the InputArgument object exists, false otherwise + */ + public function hasArgument($name); + + /** + * Returns all the given options merged with the default values. + * + * @return array + */ + public function getOptions(); + + /** + * Returns the option value for a given option name. + * + * @param string $name The option name + * + * @return string|string[]|bool|null The option value + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function getOption($name); + + /** + * Sets an option value by name. + * + * @param string $name The option name + * @param string|string[]|bool|null $value The option value + * + * @throws InvalidArgumentException When option given doesn't exist + */ + public function setOption($name, $value); + + /** + * Returns true if an InputOption object exists by name. + * + * @param string $name The InputOption name + * + * @return bool true if the InputOption object exists, false otherwise + */ + public function hasOption($name); + + /** + * Is this input means interactive? + * + * @return bool + */ + public function isInteractive(); + + /** + * Sets the input interactivity. + * + * @param bool $interactive If the input should be interactive + */ + public function setInteractive($interactive); +} diff --git a/vendor/symfony/console/Input/InputOption.php b/vendor/symfony/console/Input/InputOption.php new file mode 100644 index 0000000..20c9d2e --- /dev/null +++ b/vendor/symfony/console/Input/InputOption.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a command line option. + * + * @author Fabien Potencier + */ +class InputOption +{ + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * @param string $name The option name + * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts + * @param int|null $mode The option mode: One of the VALUE_* constants + * @param string $description A description text + * @param string|string[]|int|bool|null $default The default value (must be null for self::VALUE_NONE) + * + * @throws InvalidArgumentException If option mode is invalid or incompatible + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (\is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!\is_int($mode) || $mode > 15 || $mode < 1) { + throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * Returns the option shortcut. + * + * @return string The shortcut + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * Returns the option name. + * + * @return string The name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns true if the option accepts a value. + * + * @return bool true if value mode is not self::VALUE_NONE, false otherwise + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * Returns true if the option requires a value. + * + * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * Returns true if the option takes an optional value. + * + * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * Returns true if the option can take multiple values. + * + * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * Sets the default value. + * + * @param string|string[]|int|bool|null $default The default value + * + * @throws LogicException When incorrect default value is given + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = array(); + } elseif (!\is_array($default)) { + throw new LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * Returns the default value. + * + * @return string|string[]|int|bool|null The default value + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns the description text. + * + * @return string The description text + */ + public function getDescription() + { + return $this->description; + } + + /** + * Checks whether the given option equals this one. + * + * @return bool + */ + public function equals(self $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional() + ; + } +} diff --git a/vendor/symfony/console/Input/StringInput.php b/vendor/symfony/console/Input/StringInput.php new file mode 100644 index 0000000..add2b98 --- /dev/null +++ b/vendor/symfony/console/Input/StringInput.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Input; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * StringInput represents an input provided as a string. + * + * Usage: + * + * $input = new StringInput('foo --bar="foobar"'); + * + * @author Fabien Potencier + */ +class StringInput extends ArgvInput +{ + const REGEX_STRING = '([^\s]+?)(?:\s|(?setTokens($this->tokenize($input)); + + if (null !== $definition) { + $this->bind($definition); + } + } + + /** + * Tokenizes a string. + * + * @param string $input The input to tokenize + * + * @return array An array of tokens + * + * @throws InvalidArgumentException When unable to parse input (should never happen) + */ + private function tokenize($input) + { + $tokens = array(); + $length = \strlen($input); + $cursor = 0; + while ($cursor < $length) { + if (preg_match('/\s+/A', $input, $match, null, $cursor)) { + } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { + $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, \strlen($match[3]) - 2))); + } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2)); + } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { + $tokens[] = stripcslashes($match[1]); + } else { + // should never happen + throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); + } + + $cursor += \strlen($match[0]); + } + + return $tokens; + } +} diff --git a/vendor/symfony/console/LICENSE b/vendor/symfony/console/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/console/Logger/ConsoleLogger.php b/vendor/symfony/console/Logger/ConsoleLogger.php new file mode 100644 index 0000000..498d783 --- /dev/null +++ b/vendor/symfony/console/Logger/ConsoleLogger.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * PSR-3 compliant console logger. + * + * @author Kévin Dunglas + * + * @see http://www.php-fig.org/psr/psr-3/ + */ +class ConsoleLogger extends AbstractLogger +{ + const INFO = 'info'; + const ERROR = 'error'; + + private $output; + private $verbosityLevelMap = array( + LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL, + LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL, + LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL, + LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL, + LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE, + LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE, + LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG, + ); + private $formatLevelMap = array( + LogLevel::EMERGENCY => self::ERROR, + LogLevel::ALERT => self::ERROR, + LogLevel::CRITICAL => self::ERROR, + LogLevel::ERROR => self::ERROR, + LogLevel::WARNING => self::INFO, + LogLevel::NOTICE => self::INFO, + LogLevel::INFO => self::INFO, + LogLevel::DEBUG => self::INFO, + ); + + public function __construct(OutputInterface $output, array $verbosityLevelMap = array(), array $formatLevelMap = array()) + { + $this->output = $output; + $this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap; + $this->formatLevelMap = $formatLevelMap + $this->formatLevelMap; + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = array()) + { + if (!isset($this->verbosityLevelMap[$level])) { + throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + } + + // Write to the error output if necessary and available + if (self::ERROR === $this->formatLevelMap[$level] && $this->output instanceof ConsoleOutputInterface) { + $output = $this->output->getErrorOutput(); + } else { + $output = $this->output; + } + + if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) { + $output->writeln(sprintf('<%1$s>[%2$s] %3$s', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context))); + } + } + + /** + * Interpolates context values into the message placeholders. + * + * @author PHP Framework Interoperability Group + * + * @param string $message + * @param array $context + * + * @return string + */ + private function interpolate($message, array $context) + { + // build a replacement array with braces around the context keys + $replace = array(); + foreach ($context as $key => $val) { + if (!\is_array($val) && (!\is_object($val) || method_exists($val, '__toString'))) { + $replace[sprintf('{%s}', $key)] = $val; + } + } + + // interpolate replacement values into the message and return + return strtr($message, $replace); + } +} diff --git a/vendor/symfony/console/Output/BufferedOutput.php b/vendor/symfony/console/Output/BufferedOutput.php new file mode 100644 index 0000000..8afc893 --- /dev/null +++ b/vendor/symfony/console/Output/BufferedOutput.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * @author Jean-François Simon + */ +class BufferedOutput extends Output +{ + private $buffer = ''; + + /** + * Empties buffer and returns its content. + * + * @return string + */ + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + + return $content; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + $this->buffer .= $message; + + if ($newline) { + $this->buffer .= PHP_EOL; + } + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutput.php b/vendor/symfony/console/Output/ConsoleOutput.php new file mode 100644 index 0000000..505c649 --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutput.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * ConsoleOutput is the default class for all CLI output. It uses STDOUT. + * + * This class is a convenient wrapper around `StreamOutput`. + * + * $output = new ConsoleOutput(); + * + * This is equivalent to: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * @author Fabien Potencier + */ +class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface +{ + private $stderr; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter); + + $actualDecorated = $this->isDecorated(); + $this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter()); + + if (null === $decorated) { + $this->setDecorated($actualDecorated && $this->stderr->isDecorated()); + } + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + parent::setDecorated($decorated); + $this->stderr->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->stderr->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + parent::setVerbosity($level); + $this->stderr->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getErrorOutput() + { + return $this->stderr; + } + + /** + * {@inheritdoc} + */ + public function setErrorOutput(OutputInterface $error) + { + $this->stderr = $error; + } + + /** + * Returns true if current environment supports writing console output to + * STDOUT. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Returns true if current environment supports writing console output to + * STDERR. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * Checks if current executing environment is IBM iSeries (OS400), which + * doesn't properly convert character-encodings between ASCII to EBCDIC. + * + * @return bool + */ + private function isRunningOS400() + { + $checks = array( + \function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ); + + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * @return resource + */ + private function openOutputStream() + { + $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output'; + + return @fopen($outputStream, 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output'; + + return fopen($errorStream, 'w'); + } +} diff --git a/vendor/symfony/console/Output/ConsoleOutputInterface.php b/vendor/symfony/console/Output/ConsoleOutputInterface.php new file mode 100644 index 0000000..b44ea7e --- /dev/null +++ b/vendor/symfony/console/Output/ConsoleOutputInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +/** + * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. + * This adds information about stderr output stream. + * + * @author Dariusz Górecki + */ +interface ConsoleOutputInterface extends OutputInterface +{ + /** + * Gets the OutputInterface for errors. + * + * @return OutputInterface + */ + public function getErrorOutput(); + + public function setErrorOutput(OutputInterface $error); +} diff --git a/vendor/symfony/console/Output/NullOutput.php b/vendor/symfony/console/Output/NullOutput.php new file mode 100644 index 0000000..218f285 --- /dev/null +++ b/vendor/symfony/console/Output/NullOutput.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * NullOutput suppresses all output. + * + * $output = new NullOutput(); + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class NullOutput implements OutputInterface +{ + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + // to comply with the interface we must return a OutputFormatterInterface + return new OutputFormatter(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return self::VERBOSITY_QUIET; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $options = self::OUTPUT_NORMAL) + { + // do nothing + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) + { + // do nothing + } +} diff --git a/vendor/symfony/console/Output/Output.php b/vendor/symfony/console/Output/Output.php new file mode 100644 index 0000000..c3856cc --- /dev/null +++ b/vendor/symfony/console/Output/Output.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * Base class for output classes. + * + * There are five levels of verbosity: + * + * * normal: no option passed (normal output) + * * verbose: -v (more output) + * * very verbose: -vv (highly extended output) + * * debug: -vvv (all debug output) + * * quiet: -q (no output) + * + * @author Fabien Potencier + */ +abstract class Output implements OutputInterface +{ + private $verbosity; + private $formatter; + + /** + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool $decorated Whether to decorate messages + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + */ + public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null) + { + $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; + $this->formatter = $formatter ?: new OutputFormatter(); + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->formatter = $formatter; + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->formatter; + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->formatter->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $options = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $options); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $options = self::OUTPUT_NORMAL) + { + $messages = (array) $messages; + + $types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN; + $type = $types & $options ?: self::OUTPUT_NORMAL; + + $verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG; + $verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL; + + if ($verbosity > $this->getVerbosity()) { + return; + } + + foreach ($messages as $message) { + switch ($type) { + case OutputInterface::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case OutputInterface::OUTPUT_RAW: + break; + case OutputInterface::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + } + + $this->doWrite($message, $newline); + } + } + + /** + * Writes a message to the output. + * + * @param string $message A message to write to the output + * @param bool $newline Whether to add a newline or not + */ + abstract protected function doWrite($message, $newline); +} diff --git a/vendor/symfony/console/Output/OutputInterface.php b/vendor/symfony/console/Output/OutputInterface.php new file mode 100644 index 0000000..3bf2157 --- /dev/null +++ b/vendor/symfony/console/Output/OutputInterface.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * OutputInterface is the interface implemented by all Output classes. + * + * @author Fabien Potencier + */ +interface OutputInterface +{ + const VERBOSITY_QUIET = 16; + const VERBOSITY_NORMAL = 32; + const VERBOSITY_VERBOSE = 64; + const VERBOSITY_VERY_VERBOSE = 128; + const VERBOSITY_DEBUG = 256; + + const OUTPUT_NORMAL = 1; + const OUTPUT_RAW = 2; + const OUTPUT_PLAIN = 4; + + /** + * Writes a message to the output. + * + * @param string|array $messages The message as an array of strings or a single string + * @param bool $newline Whether to add a newline + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function write($messages, $newline = false, $options = 0); + + /** + * Writes a message to the output and adds a newline at the end. + * + * @param string|array $messages The message as an array of strings or a single string + * @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL + */ + public function writeln($messages, $options = 0); + + /** + * Sets the verbosity of the output. + * + * @param int $level The level of verbosity (one of the VERBOSITY constants) + */ + public function setVerbosity($level); + + /** + * Gets the current verbosity of the output. + * + * @return int The current level of verbosity (one of the VERBOSITY constants) + */ + public function getVerbosity(); + + /** + * Sets the decorated flag. + * + * @param bool $decorated Whether to decorate the messages + */ + public function setDecorated($decorated); + + /** + * Gets the decorated flag. + * + * @return bool true if the output will decorate messages, false otherwise + */ + public function isDecorated(); + + public function setFormatter(OutputFormatterInterface $formatter); + + /** + * Returns current output formatter instance. + * + * @return OutputFormatterInterface + */ + public function getFormatter(); +} diff --git a/vendor/symfony/console/Output/StreamOutput.php b/vendor/symfony/console/Output/StreamOutput.php new file mode 100644 index 0000000..7ff6027 --- /dev/null +++ b/vendor/symfony/console/Output/StreamOutput.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Output; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatterInterface; + +/** + * StreamOutput writes the output to a given stream. + * + * Usage: + * + * $output = new StreamOutput(fopen('php://stdout', 'w')); + * + * As `StreamOutput` can use any stream, you can also use a file: + * + * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); + * + * @author Fabien Potencier + */ +class StreamOutput extends Output +{ + private $stream; + + /** + * @param resource $stream A stream resource + * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) + * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) + * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) + * + * @throws InvalidArgumentException When first argument is not a real stream + */ + public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); + } + + $this->stream = $stream; + + if (null === $decorated) { + $decorated = $this->hasColorSupport(); + } + + parent::__construct($verbosity, $decorated, $formatter); + } + + /** + * Gets the stream attached to this StreamOutput instance. + * + * @return resource A stream resource + */ + public function getStream() + { + return $this->stream; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($message, $newline) + { + if ($newline) { + $message .= PHP_EOL; + } + + if (false === @fwrite($this->stream, $message)) { + // should never happen + throw new RuntimeException('Unable to write output.'); + } + + fflush($this->stream); + } + + /** + * Returns true if the stream supports colorization. + * + * Colorization is disabled if not supported by the stream: + * + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @return bool true if the stream supports colorization, false otherwise + */ + protected function hasColorSupport() + { + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($this->stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (\function_exists('stream_isatty')) { + return @stream_isatty($this->stream); + } + + if (\function_exists('posix_isatty')) { + return @posix_isatty($this->stream); + } + + $stat = @fstat($this->stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } +} diff --git a/vendor/symfony/console/Question/ChoiceQuestion.php b/vendor/symfony/console/Question/ChoiceQuestion.php new file mode 100644 index 0000000..0a45d74 --- /dev/null +++ b/vendor/symfony/console/Question/ChoiceQuestion.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; + +/** + * Represents a choice question. + * + * @author Fabien Potencier + */ +class ChoiceQuestion extends Question +{ + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * @param string $question The question to ask to the user + * @param array $choices The list of available choices + * @param mixed $default The default answer to return + */ + public function __construct($question, array $choices, $default = null) + { + if (!$choices) { + throw new \LogicException('Choice question must have at least 1 choice available.'); + } + + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * Returns available choices. + * + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * Sets multiselect option. + * + * When multiselect is set to true, multiple choices can be answered. + * + * @param bool $multiselect + * + * @return $this + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns whether the choices are multiselect. + * + * @return bool + */ + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * Gets the prompt for choices. + * + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * Sets the prompt for choices. + * + * @param string $prompt + * + * @return $this + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * Sets the error message for invalid values. + * + * The error message has a string placeholder (%s) for the invalid value. + * + * @param string $errorMessage + * + * @return $this + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * Returns the default answer validator. + * + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selectedChoices, $matches)) { + throw new InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = array($selected); + } + + $multiselectChoices = array(); + foreach ($selectedChoices as $value) { + $results = array(); + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (\count($results) > 1) { + throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (false !== $result) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (false === $result && isset($choices[$value])) { + $result = $value; + } + + if (false === $result) { + throw new InvalidArgumentException(sprintf($errorMessage, $value)); + } + + $multiselectChoices[] = (string) $result; + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/symfony/console/Question/ConfirmationQuestion.php b/vendor/symfony/console/Question/ConfirmationQuestion.php new file mode 100644 index 0000000..150ab27 --- /dev/null +++ b/vendor/symfony/console/Question/ConfirmationQuestion.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +/** + * Represents a yes/no question. + * + * @author Fabien Potencier + */ +class ConfirmationQuestion extends Question +{ + private $trueAnswerRegex; + + /** + * @param string $question The question to ask to the user + * @param bool $default The default answer to return, true or false + * @param string $trueAnswerRegex A regex to match the "yes" answer + */ + public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * Returns the default answer normalizer. + * + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (\is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/vendor/symfony/console/Question/Question.php b/vendor/symfony/console/Question/Question.php new file mode 100644 index 0000000..61c769a --- /dev/null +++ b/vendor/symfony/console/Question/Question.php @@ -0,0 +1,246 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Question; + +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Exception\LogicException; + +/** + * Represents a Question. + * + * @author Fabien Potencier + */ +class Question +{ + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * @param string $question The question to ask to the user + * @param mixed $default The default answer to return if the user enters nothing + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * Returns the question. + * + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * Returns the default answer. + * + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * Returns whether the user response must be hidden. + * + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * Sets whether the user response must be hidden or not. + * + * @param bool $hidden + * + * @return $this + * + * @throws LogicException In case the autocompleter is also used + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * In case the response can not be hidden, whether to fallback on non-hidden question or not. + * + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * Sets whether to fallback on non-hidden question if the response can not be hidden. + * + * @param bool $fallback + * + * @return $this + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * Gets values for the autocompleter. + * + * @return iterable|null + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * Sets values for the autocompleter. + * + * @param iterable|null $values + * + * @return $this + * + * @throws InvalidArgumentException + * @throws LogicException + */ + public function setAutocompleterValues($values) + { + if (\is_array($values)) { + $values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values); + } + + if (null !== $values && !\is_array($values) && !$values instanceof \Traversable) { + throw new InvalidArgumentException('Autocompleter values can be either an array, `null` or a `Traversable` object.'); + } + + if ($this->hidden) { + throw new LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * Sets a validator for the question. + * + * @param callable|null $validator + * + * @return $this + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } + + /** + * Gets the validator for the question. + * + * @return callable|null + */ + public function getValidator() + { + return $this->validator; + } + + /** + * Sets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @param int|null $attempts + * + * @return $this + * + * @throws InvalidArgumentException in case the number of attempts is invalid + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * Gets the maximum number of attempts. + * + * Null means an unlimited number of attempts. + * + * @return int|null + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * Sets a normalizer for the response. + * + * The normalizer can be a callable (a string), a closure or a class implementing __invoke. + * + * @param callable $normalizer + * + * @return $this + */ + public function setNormalizer($normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * Gets the normalizer for the response. + * + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * + * @return callable + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) \count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/vendor/symfony/console/Resources/bin/hiddeninput.exe b/vendor/symfony/console/Resources/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/vendor/symfony/console/Resources/bin/hiddeninput.exe differ diff --git a/vendor/symfony/console/Shell.php b/vendor/symfony/console/Shell.php new file mode 100644 index 0000000..5d23e20 --- /dev/null +++ b/vendor/symfony/console/Shell.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console; + +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Input\StringInput; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\ProcessBuilder; + +/** + * A Shell wraps an Application to add shell capabilities to it. + * + * Support for history and completion only works with a PHP compiled + * with readline support (either --with-readline or --with-libedit) + * + * @deprecated since version 2.8, to be removed in 3.0. + * + * @author Fabien Potencier + * @author Martin Hasoň + */ +class Shell +{ + private $application; + private $history; + private $output; + private $hasReadline; + private $processIsolation = false; + + /** + * If there is no readline support for the current PHP executable + * a \RuntimeException exception is thrown. + */ + public function __construct(Application $application) + { + @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + $this->hasReadline = \function_exists('readline'); + $this->application = $application; + $this->history = getenv('HOME').'/.history_'.$application->getName(); + $this->output = new ConsoleOutput(); + } + + /** + * Runs the shell. + */ + public function run() + { + $this->application->setAutoExit(false); + $this->application->setCatchExceptions(true); + + if ($this->hasReadline) { + readline_read_history($this->history); + readline_completion_function(array($this, 'autocompleter')); + } + + $this->output->writeln($this->getHeader()); + $php = null; + if ($this->processIsolation) { + $finder = new PhpExecutableFinder(); + $php = $finder->find(); + $this->output->writeln(<<<'EOF' +Running with process isolation, you should consider this: + * each command is executed as separate process, + * commands don't support interactivity, all params must be passed explicitly, + * commands output is not colorized. + +EOF + ); + } + + while (true) { + $command = $this->readline(); + + if (false === $command) { + $this->output->writeln("\n"); + + break; + } + + if ($this->hasReadline) { + readline_add_history($command); + readline_write_history($this->history); + } + + if ($this->processIsolation) { + $pb = new ProcessBuilder(); + + $process = $pb + ->add($php) + ->add($_SERVER['argv'][0]) + ->add($command) + ->inheritEnvironmentVariables(true) + ->getProcess() + ; + + $output = $this->output; + $process->run(function ($type, $data) use ($output) { + $output->writeln($data); + }); + + $ret = $process->getExitCode(); + } else { + $ret = $this->application->run(new StringInput($command), $this->output); + } + + if (0 !== $ret) { + $this->output->writeln(sprintf('The command terminated with an error status (%s)', $ret)); + } + } + } + + /** + * Returns the shell header. + * + * @return string The header string + */ + protected function getHeader() + { + return <<{$this->application->getName()} shell ({$this->application->getVersion()}). + +At the prompt, type help for some help, +or list to get a list of available commands. + +To exit the shell, type ^D. + +EOF; + } + + /** + * Renders a prompt. + * + * @return string The prompt + */ + protected function getPrompt() + { + // using the formatter here is required when using readline + return $this->output->getFormatter()->format($this->application->getName().' > '); + } + + protected function getOutput() + { + return $this->output; + } + + protected function getApplication() + { + return $this->application; + } + + /** + * Tries to return autocompletion for the current entered text. + * + * @param string $text The last segment of the entered text + * + * @return bool|array A list of guessed strings or true + */ + private function autocompleter($text) + { + $info = readline_info(); + $text = substr($info['line_buffer'], 0, $info['end']); + + if ($info['point'] !== $info['end']) { + return true; + } + + // task name? + if (false === strpos($text, ' ') || !$text) { + return array_keys($this->application->all()); + } + + // options and arguments? + try { + $command = $this->application->find(substr($text, 0, strpos($text, ' '))); + } catch (\Exception $e) { + return true; + } + + $list = array('--help'); + foreach ($command->getDefinition()->getOptions() as $option) { + $list[] = '--'.$option->getName(); + } + + return $list; + } + + /** + * Reads a single line from standard input. + * + * @return string The single line from standard input + */ + private function readline() + { + if ($this->hasReadline) { + $line = readline($this->getPrompt()); + } else { + $this->output->write($this->getPrompt()); + $line = fgets(STDIN, 1024); + $line = (false === $line || '' === $line) ? false : rtrim($line); + } + + return $line; + } + + public function getProcessIsolation() + { + return $this->processIsolation; + } + + public function setProcessIsolation($processIsolation) + { + $this->processIsolation = (bool) $processIsolation; + + if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) { + throw new RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.'); + } + } +} diff --git a/vendor/symfony/console/Style/OutputStyle.php b/vendor/symfony/console/Style/OutputStyle.php new file mode 100644 index 0000000..f3bf291 --- /dev/null +++ b/vendor/symfony/console/Style/OutputStyle.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Formatter\OutputFormatterInterface; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * Decorates output to add console style guide helpers. + * + * @author Kevin Bond + */ +abstract class OutputStyle implements OutputInterface, StyleInterface +{ + private $output; + + public function __construct(OutputInterface $output) + { + $this->output = $output; + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + $this->output->write(str_repeat(PHP_EOL, $count)); + } + + /** + * @param int $max + * + * @return ProgressBar + */ + public function createProgressBar($max = 0) + { + return new ProgressBar($this->output, $max); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->output->write($messages, $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->output->writeln($messages, $type); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->output->setVerbosity($level); + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->output->getVerbosity(); + } + + /** + * {@inheritdoc} + */ + public function setDecorated($decorated) + { + $this->output->setDecorated($decorated); + } + + /** + * {@inheritdoc} + */ + public function isDecorated() + { + return $this->output->isDecorated(); + } + + /** + * {@inheritdoc} + */ + public function setFormatter(OutputFormatterInterface $formatter) + { + $this->output->setFormatter($formatter); + } + + /** + * {@inheritdoc} + */ + public function getFormatter() + { + return $this->output->getFormatter(); + } +} diff --git a/vendor/symfony/console/Style/StyleInterface.php b/vendor/symfony/console/Style/StyleInterface.php new file mode 100644 index 0000000..475c268 --- /dev/null +++ b/vendor/symfony/console/Style/StyleInterface.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +/** + * Output style helpers. + * + * @author Kevin Bond + */ +interface StyleInterface +{ + /** + * Formats a command title. + * + * @param string $message + */ + public function title($message); + + /** + * Formats a section title. + * + * @param string $message + */ + public function section($message); + + /** + * Formats a list. + */ + public function listing(array $elements); + + /** + * Formats informational text. + * + * @param string|array $message + */ + public function text($message); + + /** + * Formats a success result bar. + * + * @param string|array $message + */ + public function success($message); + + /** + * Formats an error result bar. + * + * @param string|array $message + */ + public function error($message); + + /** + * Formats an warning result bar. + * + * @param string|array $message + */ + public function warning($message); + + /** + * Formats a note admonition. + * + * @param string|array $message + */ + public function note($message); + + /** + * Formats a caution admonition. + * + * @param string|array $message + */ + public function caution($message); + + /** + * Formats a table. + */ + public function table(array $headers, array $rows); + + /** + * Asks a question. + * + * @param string $question + * @param string|null $default + * @param callable|null $validator + * + * @return mixed + */ + public function ask($question, $default = null, $validator = null); + + /** + * Asks a question with the user input hidden. + * + * @param string $question + * @param callable|null $validator + * + * @return mixed + */ + public function askHidden($question, $validator = null); + + /** + * Asks for confirmation. + * + * @param string $question + * @param bool $default + * + * @return bool + */ + public function confirm($question, $default = true); + + /** + * Asks a choice question. + * + * @param string $question + * @param array $choices + * @param string|int|null $default + * + * @return mixed + */ + public function choice($question, array $choices, $default = null); + + /** + * Add newline(s). + * + * @param int $count The number of newlines + */ + public function newLine($count = 1); + + /** + * Starts the progress output. + * + * @param int $max Maximum steps (0 if unknown) + */ + public function progressStart($max = 0); + + /** + * Advances the progress output X steps. + * + * @param int $step Number of steps to advance + */ + public function progressAdvance($step = 1); + + /** + * Finishes the progress output. + */ + public function progressFinish(); +} diff --git a/vendor/symfony/console/Style/SymfonyStyle.php b/vendor/symfony/console/Style/SymfonyStyle.php new file mode 100644 index 0000000..6a1501a --- /dev/null +++ b/vendor/symfony/console/Style/SymfonyStyle.php @@ -0,0 +1,429 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Style; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Exception\RuntimeException; +use Symfony\Component\Console\Formatter\OutputFormatter; +use Symfony\Component\Console\Helper\Helper; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Helper\SymfonyQuestionHelper; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\Question; + +/** + * Output decorator helpers for the Symfony Style Guide. + * + * @author Kevin Bond + */ +class SymfonyStyle extends OutputStyle +{ + const MAX_LINE_LENGTH = 120; + + private $input; + private $questionHelper; + private $progressBar; + private $lineLength; + private $bufferedOutput; + + public function __construct(InputInterface $input, OutputInterface $output) + { + $this->input = $input; + $this->bufferedOutput = new BufferedOutput($output->getVerbosity(), false, clone $output->getFormatter()); + // Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not. + $this->lineLength = min($this->getTerminalWidth() - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH); + + parent::__construct($output); + } + + /** + * Formats a message as a block of text. + * + * @param string|array $messages The message to write in the block + * @param string|null $type The block type (added in [] on first line) + * @param string|null $style The style to apply to the whole block + * @param string $prefix The prefix for the block + * @param bool $padding Whether to add vertical padding + */ + public function block($messages, $type = null, $style = null, $prefix = ' ', $padding = false) + { + $messages = \is_array($messages) ? array_values($messages) : array($messages); + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, true)); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function title($message) + { + $this->autoPrependBlock(); + $this->writeln(array( + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function section($message) + { + $this->autoPrependBlock(); + $this->writeln(array( + sprintf('%s', OutputFormatter::escapeTrailingBackslash($message)), + sprintf('%s', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))), + )); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function listing(array $elements) + { + $this->autoPrependText(); + $elements = array_map(function ($element) { + return sprintf(' * %s', $element); + }, $elements); + + $this->writeln($elements); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function text($message) + { + $this->autoPrependText(); + + $messages = \is_array($message) ? array_values($message) : array($message); + foreach ($messages as $message) { + $this->writeln(sprintf(' %s', $message)); + } + } + + /** + * Formats a command comment. + * + * @param string|array $message + */ + public function comment($message) + { + $messages = \is_array($message) ? array_values($message) : array($message); + + $this->autoPrependBlock(); + $this->writeln($this->createBlock($messages, null, null, ' // ')); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function success($message) + { + $this->block($message, 'OK', 'fg=black;bg=green', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function error($message) + { + $this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function warning($message) + { + $this->block($message, 'WARNING', 'fg=white;bg=red', ' ', true); + } + + /** + * {@inheritdoc} + */ + public function note($message) + { + $this->block($message, 'NOTE', 'fg=yellow', ' ! '); + } + + /** + * {@inheritdoc} + */ + public function caution($message) + { + $this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true); + } + + /** + * {@inheritdoc} + */ + public function table(array $headers, array $rows) + { + $style = clone Table::getStyleDefinition('symfony-style-guide'); + $style->setCellHeaderFormat('%s'); + + $table = new Table($this); + $table->setHeaders($headers); + $table->setRows($rows); + $table->setStyle($style); + + $table->render(); + $this->newLine(); + } + + /** + * {@inheritdoc} + */ + public function ask($question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function askHidden($question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($question); + } + + /** + * {@inheritdoc} + */ + public function confirm($question, $default = true) + { + return $this->askQuestion(new ConfirmationQuestion($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice($question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion(new ChoiceQuestion($question, $choices, $default)); + } + + /** + * {@inheritdoc} + */ + public function progressStart($max = 0) + { + $this->progressBar = $this->createProgressBar($max); + $this->progressBar->start(); + } + + /** + * {@inheritdoc} + */ + public function progressAdvance($step = 1) + { + $this->getProgressBar()->advance($step); + } + + /** + * {@inheritdoc} + */ + public function progressFinish() + { + $this->getProgressBar()->finish(); + $this->newLine(2); + $this->progressBar = null; + } + + /** + * {@inheritdoc} + */ + public function createProgressBar($max = 0) + { + $progressBar = parent::createProgressBar($max); + + if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) { + $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 + $progressBar->setProgressCharacter(''); + $progressBar->setBarCharacter('▓'); // dark shade character \u2593 + } + + return $progressBar; + } + + /** + * @return mixed + */ + public function askQuestion(Question $question) + { + if ($this->input->isInteractive()) { + $this->autoPrependBlock(); + } + + if (!$this->questionHelper) { + $this->questionHelper = new SymfonyQuestionHelper(); + } + + $answer = $this->questionHelper->ask($this->input, $this, $question); + + if ($this->input->isInteractive()) { + $this->newLine(); + $this->bufferedOutput->write("\n"); + } + + return $answer; + } + + /** + * {@inheritdoc} + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + parent::writeln($messages, $type); + $this->bufferedOutput->writeln($this->reduceBuffer($messages), $type); + } + + /** + * {@inheritdoc} + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + parent::write($messages, $newline, $type); + $this->bufferedOutput->write($this->reduceBuffer($messages), $newline, $type); + } + + /** + * {@inheritdoc} + */ + public function newLine($count = 1) + { + parent::newLine($count); + $this->bufferedOutput->write(str_repeat("\n", $count)); + } + + /** + * @return ProgressBar + */ + private function getProgressBar() + { + if (!$this->progressBar) { + throw new RuntimeException('The ProgressBar is not started.'); + } + + return $this->progressBar; + } + + private function getTerminalWidth() + { + $application = new Application(); + $dimensions = $application->getTerminalDimensions(); + + return $dimensions[0] ?: self::MAX_LINE_LENGTH; + } + + private function autoPrependBlock() + { + $chars = substr(str_replace(PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2); + + if (!isset($chars[0])) { + return $this->newLine(); //empty history, so we should start with a new line. + } + //Prepend new line for each non LF chars (This means no blank line was output before) + $this->newLine(2 - substr_count($chars, "\n")); + } + + private function autoPrependText() + { + $fetched = $this->bufferedOutput->fetch(); + //Prepend new line if last char isn't EOL: + if ("\n" !== substr($fetched, -1)) { + $this->newLine(); + } + } + + private function reduceBuffer($messages) + { + // We need to know if the two last chars are PHP_EOL + // Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer + return array_map(function ($value) { + return substr($value, -4); + }, array_merge(array($this->bufferedOutput->fetch()), (array) $messages)); + } + + private function createBlock($messages, $type = null, $style = null, $prefix = ' ', $padding = false, $escape = false) + { + $indentLength = 0; + $prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix); + $lines = array(); + + if (null !== $type) { + $type = sprintf('[%s] ', $type); + $indentLength = \strlen($type); + $lineIndentation = str_repeat(' ', $indentLength); + } + + // wrap and add newlines for each element + foreach ($messages as $key => $message) { + if ($escape) { + $message = OutputFormatter::escape($message); + } + + $lines = array_merge($lines, explode(PHP_EOL, wordwrap($message, $this->lineLength - $prefixLength - $indentLength, PHP_EOL, true))); + + if (\count($messages) > 1 && $key < \count($messages) - 1) { + $lines[] = ''; + } + } + + $firstLineIndex = 0; + if ($padding && $this->isDecorated()) { + $firstLineIndex = 1; + array_unshift($lines, ''); + $lines[] = ''; + } + + foreach ($lines as $i => &$line) { + if (null !== $type) { + $line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line; + } + + $line = $prefix.$line; + $line .= str_repeat(' ', $this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line)); + + if ($style) { + $line = sprintf('<%s>%s', $style, $line); + } + } + + return $lines; + } +} diff --git a/vendor/symfony/console/Tester/ApplicationTester.php b/vendor/symfony/console/Tester/ApplicationTester.php new file mode 100644 index 0000000..dc12164 --- /dev/null +++ b/vendor/symfony/console/Tester/ApplicationTester.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * Eases the testing of console applications. + * + * When testing an application, don't forget to disable the auto exit flag: + * + * $application = new Application(); + * $application->setAutoExit(false); + * + * @author Fabien Potencier + */ +class ApplicationTester +{ + private $application; + private $input; + private $output; + private $statusCode; + + public function __construct(Application $application) + { + $this->application = $application; + } + + /** + * Executes the application. + * + * Available options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of arguments and options + * @param array $options An array of options + * + * @return int The command exit code + */ + public function run(array $input, $options = array()) + { + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + if (isset($options['decorated'])) { + $this->output->setDecorated($options['decorated']); + } + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->statusCode = $this->application->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the application. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the application. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the application. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} diff --git a/vendor/symfony/console/Tester/CommandTester.php b/vendor/symfony/console/Tester/CommandTester.php new file mode 100644 index 0000000..c7a9b33 --- /dev/null +++ b/vendor/symfony/console/Tester/CommandTester.php @@ -0,0 +1,125 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Console\Tester; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Output\StreamOutput; + +/** + * Eases the testing of console commands. + * + * @author Fabien Potencier + */ +class CommandTester +{ + private $command; + private $input; + private $output; + private $statusCode; + + public function __construct(Command $command) + { + $this->command = $command; + } + + /** + * Executes the command. + * + * Available execution options: + * + * * interactive: Sets the input interactive flag + * * decorated: Sets the output decorated flag + * * verbosity: Sets the output verbosity flag + * + * @param array $input An array of command arguments and options + * @param array $options An array of execution options + * + * @return int The command exit code + */ + public function execute(array $input, array $options = array()) + { + // set the command name automatically if the application requires + // this argument and no command name was passed + if (!isset($input['command']) + && (null !== $application = $this->command->getApplication()) + && $application->getDefinition()->hasArgument('command') + ) { + $input = array_merge(array('command' => $this->command->getName()), $input); + } + + $this->input = new ArrayInput($input); + if (isset($options['interactive'])) { + $this->input->setInteractive($options['interactive']); + } + + $this->output = new StreamOutput(fopen('php://memory', 'w', false)); + $this->output->setDecorated(isset($options['decorated']) ? $options['decorated'] : false); + if (isset($options['verbosity'])) { + $this->output->setVerbosity($options['verbosity']); + } + + return $this->statusCode = $this->command->run($this->input, $this->output); + } + + /** + * Gets the display returned by the last execution of the command. + * + * @param bool $normalize Whether to normalize end of lines to \n or not + * + * @return string The display + */ + public function getDisplay($normalize = false) + { + rewind($this->output->getStream()); + + $display = stream_get_contents($this->output->getStream()); + + if ($normalize) { + $display = str_replace(PHP_EOL, "\n", $display); + } + + return $display; + } + + /** + * Gets the input instance used by the last execution of the command. + * + * @return InputInterface The current input instance + */ + public function getInput() + { + return $this->input; + } + + /** + * Gets the output instance used by the last execution of the command. + * + * @return OutputInterface The current output instance + */ + public function getOutput() + { + return $this->output; + } + + /** + * Gets the status code returned by the last execution of the application. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->statusCode; + } +} diff --git a/vendor/symfony/console/composer.json b/vendor/symfony/console/composer.json new file mode 100644 index 0000000..df26e4b --- /dev/null +++ b/vendor/symfony/console/composer.json @@ -0,0 +1,45 @@ +{ + "name": "symfony/console", + "type": "library", + "description": "Symfony Console Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.0", + "symfony/debug": "^2.7.2|~3.0.0" + }, + "require-dev": { + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/process": "~2.1|~3.0.0", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/event-dispatcher": "", + "symfony/process": "", + "psr/log-implementation": "For using the console logger" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Console\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/debug/BufferingLogger.php b/vendor/symfony/debug/BufferingLogger.php new file mode 100644 index 0000000..a2ed75b --- /dev/null +++ b/vendor/symfony/debug/BufferingLogger.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Psr\Log\AbstractLogger; + +/** + * A buffering logger that stacks logs for later. + * + * @author Nicolas Grekas + */ +class BufferingLogger extends AbstractLogger +{ + private $logs = array(); + + public function log($level, $message, array $context = array()) + { + $this->logs[] = array($level, $message, $context); + } + + public function cleanLogs() + { + $logs = $this->logs; + $this->logs = array(); + + return $logs; + } +} diff --git a/vendor/symfony/debug/Debug.php b/vendor/symfony/debug/Debug.php new file mode 100644 index 0000000..aafaf1d --- /dev/null +++ b/vendor/symfony/debug/Debug.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +/** + * Registers all the debug tools. + * + * @author Fabien Potencier + */ +class Debug +{ + private static $enabled = false; + + /** + * Enables the debug tools. + * + * This method registers an error handler, an exception handler and a special class loader. + * + * @param int $errorReportingLevel The level of error reporting you want + * @param bool $displayErrors Whether to display errors (for development) or just log them (for production) + */ + public static function enable($errorReportingLevel = null, $displayErrors = true) + { + if (static::$enabled) { + return; + } + + static::$enabled = true; + + if (null !== $errorReportingLevel) { + error_reporting($errorReportingLevel); + } else { + error_reporting(-1); + } + + if (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) { + ini_set('display_errors', 0); + ExceptionHandler::register(); + } elseif ($displayErrors && (!filter_var(ini_get('log_errors'), FILTER_VALIDATE_BOOLEAN) || ini_get('error_log'))) { + // CLI - display errors only if they're not already logged to STDERR + ini_set('display_errors', 1); + } + if ($displayErrors) { + ErrorHandler::register(new ErrorHandler(new BufferingLogger())); + } else { + ErrorHandler::register()->throwAt(0, true); + } + + DebugClassLoader::enable(); + } +} diff --git a/vendor/symfony/debug/DebugClassLoader.php b/vendor/symfony/debug/DebugClassLoader.php new file mode 100644 index 0000000..f9e8232 --- /dev/null +++ b/vendor/symfony/debug/DebugClassLoader.php @@ -0,0 +1,342 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +/** + * Autoloader checking if the class is really defined in the file found. + * + * The ClassLoader will wrap all registered autoloaders + * and will throw an exception if a file is found but does + * not declare the class. + * + * @author Fabien Potencier + * @author Christophe Coevoet + * @author Nicolas Grekas + */ +class DebugClassLoader +{ + private $classLoader; + private $isFinder; + private $loaded = array(); + private $wasFinder; + private static $caseCheck; + private static $deprecated = array(); + private static $php7Reserved = array('int', 'float', 'bool', 'string', 'true', 'false', 'null'); + private static $darwinCache = array('/' => array('/', array())); + + /** + * @param callable|object $classLoader Passing an object is @deprecated since version 2.5 and support for it will be removed in 3.0 + */ + public function __construct($classLoader) + { + $this->wasFinder = \is_object($classLoader) && method_exists($classLoader, 'findFile'); + + if ($this->wasFinder) { + @trigger_error('The '.__METHOD__.' method will no longer support receiving an object into its $classLoader argument in 3.0.', E_USER_DEPRECATED); + $this->classLoader = array($classLoader, 'loadClass'); + $this->isFinder = true; + } else { + $this->classLoader = $classLoader; + $this->isFinder = \is_array($classLoader) && method_exists($classLoader[0], 'findFile'); + } + + if (!isset(self::$caseCheck)) { + $file = file_exists(__FILE__) ? __FILE__ : rtrim(realpath('.'), \DIRECTORY_SEPARATOR); + $i = strrpos($file, \DIRECTORY_SEPARATOR); + $dir = substr($file, 0, 1 + $i); + $file = substr($file, 1 + $i); + $test = strtoupper($file) === $file ? strtolower($file) : strtoupper($file); + $test = realpath($dir.$test); + + if (false === $test || false === $i) { + // filesystem is case sensitive + self::$caseCheck = 0; + } elseif (substr($test, -\strlen($file)) === $file) { + // filesystem is case insensitive and realpath() normalizes the case of characters + self::$caseCheck = 1; + } elseif (false !== stripos(PHP_OS, 'darwin')) { + // on MacOSX, HFS+ is case insensitive but realpath() doesn't normalize the case of characters + self::$caseCheck = 2; + } else { + // filesystem case checks failed, fallback to disabling them + self::$caseCheck = 0; + } + } + } + + /** + * Gets the wrapped class loader. + * + * @return callable|object A class loader. Since version 2.5, returning an object is @deprecated and support for it will be removed in 3.0 + */ + public function getClassLoader() + { + return $this->wasFinder ? $this->classLoader[0] : $this->classLoader; + } + + /** + * Wraps all autoloaders. + */ + public static function enable() + { + // Ensures we don't hit https://bugs.php.net/42098 + class_exists('Symfony\Component\Debug\ErrorHandler'); + class_exists('Psr\Log\LogLevel'); + + if (!\is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (!\is_array($function) || !$function[0] instanceof self) { + $function = array(new static($function), 'loadClass'); + } + + spl_autoload_register($function); + } + } + + /** + * Disables the wrapping. + */ + public static function disable() + { + if (!\is_array($functions = spl_autoload_functions())) { + return; + } + + foreach ($functions as $function) { + spl_autoload_unregister($function); + } + + foreach ($functions as $function) { + if (\is_array($function) && $function[0] instanceof self) { + $function = $function[0]->getClassLoader(); + } + + spl_autoload_register($function); + } + } + + /** + * Finds a file by class name. + * + * @param string $class A class name to resolve to file + * + * @return string|null + * + * @deprecated since version 2.5, to be removed in 3.0. + */ + public function findFile($class) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); + + if ($this->wasFinder) { + return $this->classLoader[0]->findFile($class); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * + * @return bool|null True, if loaded + * + * @throws \RuntimeException + */ + public function loadClass($class) + { + ErrorHandler::stackErrors(); + + try { + if ($this->isFinder && !isset($this->loaded[$class])) { + $this->loaded[$class] = true; + if ($file = $this->classLoader[0]->findFile($class)) { + require $file; + } + } else { + \call_user_func($this->classLoader, $class); + $file = false; + } + } catch (\Exception $e) { + ErrorHandler::unstackErrors(); + + throw $e; + } catch (\Throwable $e) { + ErrorHandler::unstackErrors(); + + throw $e; + } + + ErrorHandler::unstackErrors(); + + $exists = class_exists($class, false) || interface_exists($class, false) || (\function_exists('trait_exists') && trait_exists($class, false)); + + if ($class && '\\' === $class[0]) { + $class = substr($class, 1); + } + + if ($exists) { + $refl = new \ReflectionClass($class); + $name = $refl->getName(); + + if ($name !== $class && 0 === strcasecmp($name, $class)) { + throw new \RuntimeException(sprintf('Case mismatch between loaded and declared class names: %s vs %s', $class, $name)); + } + + if (\in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { + @trigger_error(sprintf('%s uses a reserved class name (%s) that will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED); + } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { + self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); + } else { + if (2 > $len = 1 + (strpos($name, '\\') ?: strpos($name, '_'))) { + $len = 0; + $ns = ''; + } else { + $ns = substr($name, 0, $len); + } + $parent = get_parent_class($class); + + if (!$parent || strncmp($ns, $parent, $len)) { + if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { + @trigger_error(sprintf('The %s class extends %s that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); + } + + $parentInterfaces = array(); + $deprecatedInterfaces = array(); + if ($parent) { + foreach (class_implements($parent) as $interface) { + $parentInterfaces[$interface] = 1; + } + } + + foreach ($refl->getInterfaceNames() as $interface) { + if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { + $deprecatedInterfaces[] = $interface; + } + foreach (class_implements($interface) as $interface) { + $parentInterfaces[$interface] = 1; + } + } + + foreach ($deprecatedInterfaces as $interface) { + if (!isset($parentInterfaces[$interface])) { + @trigger_error(sprintf('The %s %s %s that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); + } + } + } + } + } + + if ($file) { + if (!$exists) { + if (false !== strpos($class, '/')) { + throw new \RuntimeException(sprintf('Trying to autoload a class with an invalid name "%s". Be careful that the namespace separator is "\" in PHP, not "/".', $class)); + } + + throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); + } + if (self::$caseCheck) { + $real = explode('\\', $class.strrchr($file, '.')); + $tail = explode(\DIRECTORY_SEPARATOR, str_replace('/', \DIRECTORY_SEPARATOR, $file)); + + $i = \count($tail) - 1; + $j = \count($real) - 1; + + while (isset($tail[$i], $real[$j]) && $tail[$i] === $real[$j]) { + --$i; + --$j; + } + + array_splice($tail, 0, $i + 1); + } + if (self::$caseCheck && $tail) { + $tail = \DIRECTORY_SEPARATOR.implode(\DIRECTORY_SEPARATOR, $tail); + $tailLen = \strlen($tail); + $real = $refl->getFileName(); + + if (2 === self::$caseCheck) { + // realpath() on MacOSX doesn't normalize the case of characters + + $i = 1 + strrpos($real, '/'); + $file = substr($real, $i); + $real = substr($real, 0, $i); + + if (isset(self::$darwinCache[$real])) { + $kDir = $real; + } else { + $kDir = strtolower($real); + + if (isset(self::$darwinCache[$kDir])) { + $real = self::$darwinCache[$kDir][0]; + } else { + $dir = getcwd(); + chdir($real); + $real = getcwd().'/'; + chdir($dir); + + $dir = $real; + $k = $kDir; + $i = \strlen($dir) - 1; + while (!isset(self::$darwinCache[$k])) { + self::$darwinCache[$k] = array($dir, array()); + self::$darwinCache[$dir] = &self::$darwinCache[$k]; + + while ('/' !== $dir[--$i]) { + } + $k = substr($k, 0, ++$i); + $dir = substr($dir, 0, $i--); + } + } + } + + $dirFiles = self::$darwinCache[$kDir][1]; + + if (isset($dirFiles[$file])) { + $kFile = $file; + } else { + $kFile = strtolower($file); + + if (!isset($dirFiles[$kFile])) { + foreach (scandir($real, 2) as $f) { + if ('.' !== $f[0]) { + $dirFiles[$f] = $f; + if ($f === $file) { + $kFile = $k = $file; + } elseif ($f !== $k = strtolower($f)) { + $dirFiles[$k] = $f; + } + } + } + self::$darwinCache[$kDir][1] = $dirFiles; + } + } + + $real .= $dirFiles[$kFile]; + } + + if (0 === substr_compare($real, $tail, -$tailLen, $tailLen, true) + && 0 !== substr_compare($real, $tail, -$tailLen, $tailLen, false) + ) { + throw new \RuntimeException(sprintf('Case mismatch between class and real file names: %s vs %s in %s', substr($tail, -$tailLen + 1), substr($real, -$tailLen + 1), substr($real, 0, -$tailLen + 1))); + } + } + + return true; + } + } +} diff --git a/vendor/symfony/debug/ErrorHandler.php b/vendor/symfony/debug/ErrorHandler.php new file mode 100644 index 0000000..43d09cc --- /dev/null +++ b/vendor/symfony/debug/ErrorHandler.php @@ -0,0 +1,894 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; +use Symfony\Component\Debug\Exception\ContextErrorException; +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\FatalThrowableError; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; +use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; + +/** + * A generic ErrorHandler for the PHP engine. + * + * Provides five bit fields that control how errors are handled: + * - thrownErrors: errors thrown as \ErrorException + * - loggedErrors: logged errors, when not @-silenced + * - scopedErrors: errors thrown or logged with their local context + * - tracedErrors: errors logged with their stack trace, only once for repeated errors + * - screamedErrors: never @-silenced errors + * + * Each error level can be logged by a dedicated PSR-3 logger object. + * Screaming only applies to logging. + * Throwing takes precedence over logging. + * Uncaught exceptions are logged as E_ERROR. + * E_DEPRECATED and E_USER_DEPRECATED levels never throw. + * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. + * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. + * As errors have a performance cost, repeated errors are all logged, so that the developer + * can see them and weight them as more important to fix than others of the same level. + * + * @author Nicolas Grekas + */ +class ErrorHandler +{ + /** + * @deprecated since version 2.6, to be removed in 3.0. + */ + const TYPE_DEPRECATION = -100; + + private $levels = array( + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + E_NOTICE => 'Notice', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_WARNING => 'Warning', + E_USER_WARNING => 'User Warning', + E_COMPILE_WARNING => 'Compile Warning', + E_CORE_WARNING => 'Core Warning', + E_USER_ERROR => 'User Error', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + E_COMPILE_ERROR => 'Compile Error', + E_PARSE => 'Parse Error', + E_ERROR => 'Error', + E_CORE_ERROR => 'Core Error', + ); + + private $loggers = array( + E_DEPRECATED => array(null, LogLevel::INFO), + E_USER_DEPRECATED => array(null, LogLevel::INFO), + E_NOTICE => array(null, LogLevel::WARNING), + E_USER_NOTICE => array(null, LogLevel::WARNING), + E_STRICT => array(null, LogLevel::WARNING), + E_WARNING => array(null, LogLevel::WARNING), + E_USER_WARNING => array(null, LogLevel::WARNING), + E_COMPILE_WARNING => array(null, LogLevel::WARNING), + E_CORE_WARNING => array(null, LogLevel::WARNING), + E_USER_ERROR => array(null, LogLevel::CRITICAL), + E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL), + E_COMPILE_ERROR => array(null, LogLevel::CRITICAL), + E_PARSE => array(null, LogLevel::CRITICAL), + E_ERROR => array(null, LogLevel::CRITICAL), + E_CORE_ERROR => array(null, LogLevel::CRITICAL), + ); + + private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED + private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE + private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE + private $loggedErrors = 0; + + private $loggedTraces = array(); + private $isRecursive = 0; + private $isRoot = false; + private $exceptionHandler; + private $bootstrappingLogger; + + private static $reservedMemory; + private static $stackedErrors = array(); + private static $stackedErrorLevels = array(); + private static $toStringException = null; + private static $exitCode = 0; + + /** + * Same init value as thrownErrors. + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + private $displayErrors = 0x1FFF; + + /** + * Registers the error handler. + * + * @param self|int|null $handler The handler to register, or @deprecated (since version 2.6, to be removed in 3.0) bit field of thrown levels + * @param bool $replace Whether to replace or not any existing handler + * + * @return self The registered error handler + */ + public static function register($handler = null, $replace = true) + { + if (null === self::$reservedMemory) { + self::$reservedMemory = str_repeat('x', 10240); + register_shutdown_function(__CLASS__.'::handleFatalError'); + } + + $levels = -1; + + if ($handlerIsNew = !$handler instanceof self) { + // @deprecated polymorphism, to be removed in 3.0 + if (null !== $handler) { + $levels = $replace ? $handler : 0; + $replace = true; + } + $handler = new static(); + } + + if (null === $prev = set_error_handler(array($handler, 'handleError'))) { + restore_error_handler(); + // Specifying the error types earlier would expose us to https://bugs.php.net/63206 + set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors); + $handler->isRoot = true; + } + + if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) { + $handler = $prev[0]; + $replace = false; + } + if (!$replace && $prev) { + restore_error_handler(); + $handlerIsRegistered = \is_array($prev) && $handler === $prev[0]; + } else { + $handlerIsRegistered = true; + } + if (\is_array($prev = set_exception_handler(array($handler, 'handleException'))) && $prev[0] instanceof self) { + restore_exception_handler(); + if (!$handlerIsRegistered) { + $handler = $prev[0]; + } elseif ($handler !== $prev[0] && $replace) { + set_exception_handler(array($handler, 'handleException')); + $p = $prev[0]->setExceptionHandler(null); + $handler->setExceptionHandler($p); + $prev[0]->setExceptionHandler($p); + } + } else { + $handler->setExceptionHandler($prev); + } + + $handler->throwAt($levels & $handler->thrownErrors, true); + + return $handler; + } + + public function __construct(BufferingLogger $bootstrappingLogger = null) + { + if ($bootstrappingLogger) { + $this->bootstrappingLogger = $bootstrappingLogger; + $this->setDefaultLogger($bootstrappingLogger); + } + } + + /** + * Sets a logger to non assigned errors levels. + * + * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param bool $replace Whether to replace or not any existing logger + */ + public function setDefaultLogger(LoggerInterface $logger, $levels = null, $replace = false) + { + $loggers = array(); + + if (\is_array($levels)) { + foreach ($levels as $type => $logLevel) { + if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { + $loggers[$type] = array($logger, $logLevel); + } + } + } else { + if (null === $levels) { + $levels = E_ALL | E_STRICT; + } + foreach ($this->loggers as $type => $log) { + if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { + $log[0] = $logger; + $loggers[$type] = $log; + } + } + } + + $this->setLoggers($loggers); + } + + /** + * Sets a logger for each error level. + * + * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map + * + * @return array The previous map + * + * @throws \InvalidArgumentException + */ + public function setLoggers(array $loggers) + { + $prevLogged = $this->loggedErrors; + $prev = $this->loggers; + $flush = array(); + + foreach ($loggers as $type => $log) { + if (!isset($prev[$type])) { + throw new \InvalidArgumentException('Unknown error type: '.$type); + } + if (!\is_array($log)) { + $log = array($log); + } elseif (!array_key_exists(0, $log)) { + throw new \InvalidArgumentException('No logger provided'); + } + if (null === $log[0]) { + $this->loggedErrors &= ~$type; + } elseif ($log[0] instanceof LoggerInterface) { + $this->loggedErrors |= $type; + } else { + throw new \InvalidArgumentException('Invalid logger provided'); + } + $this->loggers[$type] = $log + $prev[$type]; + + if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { + $flush[$type] = $type; + } + } + $this->reRegister($prevLogged | $this->thrownErrors); + + if ($flush) { + foreach ($this->bootstrappingLogger->cleanLogs() as $log) { + $type = $log[2]['type']; + if (!isset($flush[$type])) { + $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); + } elseif ($this->loggers[$type][0]) { + $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); + } + } + } + + return $prev; + } + + /** + * Sets a user exception handler. + * + * @param callable $handler A handler that will be called on Exception + * + * @return callable|null The previous exception handler + * + * @throws \InvalidArgumentException + */ + public function setExceptionHandler($handler) + { + if (null !== $handler && !\is_callable($handler)) { + throw new \LogicException('The exception handler must be a valid PHP callable.'); + } + $prev = $this->exceptionHandler; + $this->exceptionHandler = $handler; + + return $prev; + } + + /** + * Sets the PHP error levels that throw an exception when a PHP error occurs. + * + * @param int $levels A bit field of E_* constants for thrown errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function throwAt($levels, $replace = false) + { + $prev = $this->thrownErrors; + $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; + if (!$replace) { + $this->thrownErrors |= $prev; + } + $this->reRegister($prev | $this->loggedErrors); + + // $this->displayErrors is @deprecated since version 2.6 + $this->displayErrors = $this->thrownErrors; + + return $prev; + } + + /** + * Sets the PHP error levels for which local variables are preserved. + * + * @param int $levels A bit field of E_* constants for scoped errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function scopeAt($levels, $replace = false) + { + $prev = $this->scopedErrors; + $this->scopedErrors = (int) $levels; + if (!$replace) { + $this->scopedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the PHP error levels for which the stack trace is preserved. + * + * @param int $levels A bit field of E_* constants for traced errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function traceAt($levels, $replace = false) + { + $prev = $this->tracedErrors; + $this->tracedErrors = (int) $levels; + if (!$replace) { + $this->tracedErrors |= $prev; + } + + return $prev; + } + + /** + * Sets the error levels where the @-operator is ignored. + * + * @param int $levels A bit field of E_* constants for screamed errors + * @param bool $replace Replace or amend the previous value + * + * @return int The previous value + */ + public function screamAt($levels, $replace = false) + { + $prev = $this->screamedErrors; + $this->screamedErrors = (int) $levels; + if (!$replace) { + $this->screamedErrors |= $prev; + } + + return $prev; + } + + /** + * Re-registers as a PHP error handler if levels changed. + */ + private function reRegister($prev) + { + if ($prev !== $this->thrownErrors | $this->loggedErrors) { + $handler = set_error_handler('var_dump'); + $handler = \is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if ($handler === $this) { + restore_error_handler(); + if ($this->isRoot) { + set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors); + } else { + set_error_handler(array($this, 'handleError')); + } + } + } + } + + /** + * Handles errors by filtering then logging them according to the configured bit fields. + * + * @param int $type One of the E_* constants + * @param string $message + * @param string $file + * @param int $line + * + * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself + * + * @throws \ErrorException When $this->thrownErrors requests so + * + * @internal + */ + public function handleError($type, $message, $file, $line) + { + $level = error_reporting(); + $silenced = 0 === ($level & $type); + $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; + $log = $this->loggedErrors & $type; + $throw = $this->thrownErrors & $type & $level; + $type &= $level | $this->screamedErrors; + + if (!$type || (!$log && !$throw)) { + return !$silenced && $type && $log; + } + $scope = $this->scopedErrors & $type; + + if (4 < $numArgs = \func_num_args()) { + $context = $scope ? (func_get_arg(4) ?: array()) : array(); + $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM + } else { + $context = array(); + $backtrace = null; + } + + if (isset($context['GLOBALS']) && $scope) { + $e = $context; // Whatever the signature of the method, + unset($e['GLOBALS'], $context); // $context is always a reference in 5.3 + $context = $e; + } + + if (null !== $backtrace && $type & E_ERROR) { + // E_ERROR fatal errors are triggered on HHVM when + // hhvm.error_handling.call_user_handler_on_fatals=1 + // which is the way to get their backtrace. + $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace')); + + return true; + } + + if ($throw) { + if (null !== self::$toStringException) { + $throw = self::$toStringException; + self::$toStringException = null; + } elseif ($scope && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) { + // Checking for class existence is a work around for https://bugs.php.net/42098 + $throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context); + } else { + $throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line); + } + + if (\PHP_VERSION_ID <= 50407 && (\PHP_VERSION_ID >= 50400 || \PHP_VERSION_ID <= 50317)) { + // Exceptions thrown from error handlers are sometimes not caught by the exception + // handler and shutdown handlers are bypassed before 5.4.8/5.3.18. + // We temporarily re-enable display_errors to prevent any blank page related to this bug. + + $throw->errorHandlerCanary = new ErrorHandlerCanary(); + } + + if (E_USER_ERROR & $type) { + $backtrace = $backtrace ?: $throw->getTrace(); + + for ($i = 1; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) + && '__toString' === $backtrace[$i]['function'] + && '->' === $backtrace[$i]['type'] + && !isset($backtrace[$i - 1]['class']) + && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) + ) { + // Here, we know trigger_error() has been called from __toString(). + // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead. + // A small convention allows working around the limitation: + // given a caught $e exception in __toString(), quitting the method with + // `return trigger_error($e, E_USER_ERROR);` allows this error handler + // to make $e get through the __toString() barrier. + + foreach ($context as $e) { + if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) { + if (1 === $i) { + // On HHVM + $throw = $e; + break; + } + self::$toStringException = $e; + + return true; + } + } + + if (1 < $i) { + // On PHP (not on HHVM), display the original error message instead of the default one. + $this->handleException($throw); + + // Stop the process by giving back the error to the native handler. + return false; + } + } + } + } + + throw $throw; + } + + // For duplicated errors, log the trace only once + $e = md5("{$type}/{$line}/{$file}\x00{$message}", true); + $trace = true; + + if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) { + $trace = false; + } else { + $this->loggedTraces[$e] = 1; + } + + $e = compact('type', 'file', 'line', 'level'); + + if ($type & $level) { + if ($scope) { + $e['scope_vars'] = $context; + if ($trace) { + $e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT); + } + } elseif ($trace) { + if (null === $backtrace) { + $e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } else { + foreach ($backtrace as &$frame) { + unset($frame['args'], $frame); + } + $e['stack'] = $backtrace; + } + } + } + + if ($this->isRecursive) { + $log = 0; + } elseif (self::$stackedErrorLevels) { + self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e); + } else { + try { + $this->isRecursive = true; + $this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e); + $this->isRecursive = false; + } catch (\Exception $e) { + $this->isRecursive = false; + + throw $e; + } catch (\Throwable $e) { + $this->isRecursive = false; + + throw $e; + } + } + + return !$silenced && $type && $log; + } + + /** + * Handles an exception by logging then forwarding it to another handler. + * + * @param \Exception|\Throwable $exception An exception to handle + * @param array $error An array as returned by error_get_last() + * + * @internal + */ + public function handleException($exception, array $error = null) + { + if (null === $error) { + self::$exitCode = 255; + } + if (!$exception instanceof \Exception) { + $exception = new FatalThrowableError($exception); + } + $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; + $handlerException = null; + + if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { + $e = array( + 'type' => $type, + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'level' => error_reporting(), + 'stack' => $exception->getTrace(), + ); + if ($exception instanceof FatalErrorException) { + if ($exception instanceof FatalThrowableError) { + $error = array( + 'type' => $type, + 'message' => $message = $exception->getMessage(), + 'file' => $e['file'], + 'line' => $e['line'], + ); + } else { + $message = 'Fatal '.$exception->getMessage(); + } + } elseif ($exception instanceof \ErrorException) { + $message = 'Uncaught '.$exception->getMessage(); + if ($exception instanceof ContextErrorException) { + $e['context'] = $exception->getContext(); + } + } else { + $message = 'Uncaught Exception: '.$exception->getMessage(); + } + } + if ($this->loggedErrors & $type) { + try { + $this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e); + } catch (\Exception $handlerException) { + } catch (\Throwable $handlerException) { + } + } + if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { + foreach ($this->getFatalErrorHandlers() as $handler) { + if ($e = $handler->handleError($error, $exception)) { + $exception = $e; + break; + } + } + } + $exceptionHandler = $this->exceptionHandler; + $this->exceptionHandler = null; + try { + if (null !== $exceptionHandler) { + return \call_user_func($exceptionHandler, $exception); + } + $handlerException = $handlerException ?: $exception; + } catch (\Exception $handlerException) { + } catch (\Throwable $handlerException) { + } + if ($exception === $handlerException) { + self::$reservedMemory = null; // Disable the fatal error handler + throw $exception; // Give back $exception to the native handler + } + $this->handleException($handlerException); + } + + /** + * Shutdown registered function for handling PHP fatal errors. + * + * @param array $error An array as returned by error_get_last() + * + * @internal + */ + public static function handleFatalError(array $error = null) + { + if (null === self::$reservedMemory) { + return; + } + + $handler = self::$reservedMemory = null; + $handlers = array(); + $previousHandler = null; + $sameHandlerLimit = 10; + + while (!\is_array($handler) || !$handler[0] instanceof self) { + $handler = set_exception_handler('var_dump'); + restore_exception_handler(); + + if (!$handler) { + break; + } + restore_exception_handler(); + + if ($handler !== $previousHandler) { + array_unshift($handlers, $handler); + $previousHandler = $handler; + } elseif (0 === --$sameHandlerLimit) { + $handler = null; + break; + } + } + foreach ($handlers as $h) { + set_exception_handler($h); + } + if (!$handler) { + return; + } + if ($handler !== $h) { + $handler[0]->setExceptionHandler($h); + } + $handler = $handler[0]; + $handlers = array(); + + if ($exit = null === $error) { + $error = error_get_last(); + } + + try { + while (self::$stackedErrorLevels) { + static::unstackErrors(); + } + } catch (\Exception $exception) { + // Handled below + } catch (\Throwable $exception) { + // Handled below + } + + if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { + // Let's not throw anymore but keep logging + $handler->throwAt(0, true); + $trace = isset($error['backtrace']) ? $error['backtrace'] : null; + + if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { + $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); + } else { + $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); + } + } + + try { + if (isset($exception)) { + self::$exitCode = 255; + $handler->handleException($exception, $error); + } + } catch (FatalErrorException $e) { + // Ignore this re-throw + } + + if ($exit && self::$exitCode) { + $exitCode = self::$exitCode; + register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); + } + } + + /** + * Configures the error handler for delayed handling. + * Ensures also that non-catchable fatal errors are never silenced. + * + * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724 + * PHP has a compile stage where it behaves unusually. To workaround it, + * we plug an error handler that only stacks errors for later. + * + * The most important feature of this is to prevent + * autoloading until unstackErrors() is called. + */ + public static function stackErrors() + { + self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); + } + + /** + * Unstacks stacked errors and forwards to the logger. + */ + public static function unstackErrors() + { + $level = array_pop(self::$stackedErrorLevels); + + if (null !== $level) { + $e = error_reporting($level); + if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) { + // If the user changed the error level, do not overwrite it + error_reporting($e); + } + } + + if (empty(self::$stackedErrorLevels)) { + $errors = self::$stackedErrors; + self::$stackedErrors = array(); + + foreach ($errors as $e) { + $e[0]->log($e[1], $e[2], $e[3]); + } + } + } + + /** + * Gets the fatal error handlers. + * + * Override this method if you want to define more fatal error handlers. + * + * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface + */ + protected function getFatalErrorHandlers() + { + return array( + new UndefinedFunctionFatalErrorHandler(), + new UndefinedMethodFatalErrorHandler(), + new ClassNotFoundFatalErrorHandler(), + ); + } + + /** + * Sets the level at which the conversion to Exception is done. + * + * @param int|null $level The level (null to use the error_reporting() value and 0 to disable) + * + * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead. + */ + public function setLevel($level) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED); + + $level = null === $level ? error_reporting() : $level; + $this->throwAt($level, true); + } + + /** + * Sets the display_errors flag value. + * + * @param int $displayErrors The display_errors flag value + * + * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead. + */ + public function setDisplayErrors($displayErrors) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED); + + if ($displayErrors) { + $this->throwAt($this->displayErrors, true); + } else { + $displayErrors = $this->displayErrors; + $this->throwAt(0, true); + $this->displayErrors = $displayErrors; + } + } + + /** + * Sets a logger for the given channel. + * + * @param LoggerInterface $logger A logger interface + * @param string $channel The channel associated with the logger (deprecation, emergency or scream) + * + * @deprecated since version 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead. + */ + public static function setLogger(LoggerInterface $logger, $channel = 'deprecation') + { + @trigger_error('The '.__METHOD__.' static method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the setLoggers() or setDefaultLogger() methods instead.', E_USER_DEPRECATED); + + $handler = set_error_handler('var_dump'); + $handler = \is_array($handler) ? $handler[0] : null; + restore_error_handler(); + if (!$handler instanceof self) { + return; + } + if ('deprecation' === $channel) { + $handler->setDefaultLogger($logger, E_DEPRECATED | E_USER_DEPRECATED, true); + $handler->screamAt(E_DEPRECATED | E_USER_DEPRECATED); + } elseif ('scream' === $channel) { + $handler->setDefaultLogger($logger, E_ALL | E_STRICT, false); + $handler->screamAt(E_ALL | E_STRICT); + } elseif ('emergency' === $channel) { + $handler->setDefaultLogger($logger, E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR, true); + $handler->screamAt(E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR); + } + } + + /** + * @deprecated since version 2.6, to be removed in 3.0. Use handleError() instead. + */ + public function handle($level, $message, $file = 'unknown', $line = 0, $context = array()) + { + $this->handleError(E_USER_DEPRECATED, 'The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleError() method instead.', __FILE__, __LINE__, array()); + + return $this->handleError($level, $message, $file, $line, (array) $context); + } + + /** + * Handles PHP fatal errors. + * + * @deprecated since version 2.6, to be removed in 3.0. Use handleFatalError() instead. + */ + public function handleFatal() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleFatalError() method instead.', E_USER_DEPRECATED); + + static::handleFatalError(); + } +} + +/** + * Private class used to work around https://bugs.php.net/54275. + * + * @author Nicolas Grekas + * + * @internal + */ +class ErrorHandlerCanary +{ + private static $displayErrors = null; + + public function __construct() + { + if (null === self::$displayErrors) { + self::$displayErrors = ini_set('display_errors', 1); + } + } + + public function __destruct() + { + if (null !== self::$displayErrors) { + ini_set('display_errors', self::$displayErrors); + self::$displayErrors = null; + } + } +} diff --git a/vendor/symfony/debug/Exception/ClassNotFoundException.php b/vendor/symfony/debug/Exception/ClassNotFoundException.php new file mode 100644 index 0000000..de5c456 --- /dev/null +++ b/vendor/symfony/debug/Exception/ClassNotFoundException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Class (or Trait or Interface) Not Found Exception. + * + * @author Konstanton Myakshin + */ +class ClassNotFoundException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + null, + true, + null, + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/ContextErrorException.php b/vendor/symfony/debug/Exception/ContextErrorException.php new file mode 100644 index 0000000..54f0198 --- /dev/null +++ b/vendor/symfony/debug/Exception/ContextErrorException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Error Exception with Variable Context. + * + * @author Christian Sciberras + */ +class ContextErrorException extends \ErrorException +{ + private $context = array(); + + public function __construct($message, $code, $severity, $filename, $lineno, $context = array()) + { + parent::__construct($message, $code, $severity, $filename, $lineno); + $this->context = $context; + } + + /** + * @return array Array of variables that existed when the exception occurred + */ + public function getContext() + { + return $this->context; + } +} diff --git a/vendor/symfony/debug/Exception/DummyException.php b/vendor/symfony/debug/Exception/DummyException.php new file mode 100644 index 0000000..1b9082b --- /dev/null +++ b/vendor/symfony/debug/Exception/DummyException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +@trigger_error('The '.__NAMESPACE__.'\DummyException class is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); + +/** + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0. + */ +class DummyException extends \ErrorException +{ +} diff --git a/vendor/symfony/debug/Exception/FatalErrorException.php b/vendor/symfony/debug/Exception/FatalErrorException.php new file mode 100644 index 0000000..1aa87a5 --- /dev/null +++ b/vendor/symfony/debug/Exception/FatalErrorException.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Fatal Error Exception. + * + * @author Fabien Potencier + * @author Konstanton Myakshin + * @author Nicolas Grekas + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class FatalErrorException extends \ErrorException +{ +} + +namespace Symfony\Component\Debug\Exception; + +use Symfony\Component\HttpKernel\Exception\FatalErrorException as LegacyFatalErrorException; + +/** + * Fatal Error Exception. + * + * @author Konstanton Myakshin + */ +class FatalErrorException extends LegacyFatalErrorException +{ + public function __construct($message, $code, $severity, $filename, $lineno, $traceOffset = null, $traceArgs = true, array $trace = null, $previous = null) + { + parent::__construct($message, $code, $severity, $filename, $lineno, $previous); + + if (null !== $trace) { + if (!$traceArgs) { + foreach ($trace as &$frame) { + unset($frame['args'], $frame['this'], $frame); + } + } + + $this->setTrace($trace); + } elseif (null !== $traceOffset) { + if (\function_exists('xdebug_get_function_stack')) { + $trace = xdebug_get_function_stack(); + if (0 < $traceOffset) { + array_splice($trace, -$traceOffset); + } + + foreach ($trace as &$frame) { + if (!isset($frame['type'])) { + // XDebug pre 2.1.1 doesn't currently set the call type key http://bugs.xdebug.org/view.php?id=695 + if (isset($frame['class'])) { + $frame['type'] = '::'; + } + } elseif ('dynamic' === $frame['type']) { + $frame['type'] = '->'; + } elseif ('static' === $frame['type']) { + $frame['type'] = '::'; + } + + // XDebug also has a different name for the parameters array + if (!$traceArgs) { + unset($frame['params'], $frame['args']); + } elseif (isset($frame['params']) && !isset($frame['args'])) { + $frame['args'] = $frame['params']; + unset($frame['params']); + } + } + + unset($frame); + $trace = array_reverse($trace); + } elseif (\function_exists('symfony_debug_backtrace')) { + $trace = symfony_debug_backtrace(); + if (0 < $traceOffset) { + array_splice($trace, 0, $traceOffset); + } + } else { + $trace = array(); + } + + $this->setTrace($trace); + } + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/vendor/symfony/debug/Exception/FatalThrowableError.php b/vendor/symfony/debug/Exception/FatalThrowableError.php new file mode 100644 index 0000000..fafc922 --- /dev/null +++ b/vendor/symfony/debug/Exception/FatalThrowableError.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Fatal Throwable Error. + * + * @author Nicolas Grekas + */ +class FatalThrowableError extends FatalErrorException +{ + public function __construct(\Throwable $e) + { + if ($e instanceof \ParseError) { + $message = 'Parse error: '.$e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: '.$e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = $e->getMessage(); + $severity = E_ERROR; + } + + \ErrorException::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine(), + $e->getPrevious() + ); + + $this->setTrace($e->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/FlattenException.php b/vendor/symfony/debug/Exception/FlattenException.php new file mode 100644 index 0000000..f136417 --- /dev/null +++ b/vendor/symfony/debug/Exception/FlattenException.php @@ -0,0 +1,297 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +use Symfony\Component\Debug\Exception\FlattenException as DebugFlattenException; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + * + * @deprecated Deprecated in 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class FlattenException +{ + private $handler; + + public static function __callStatic($method, $args) + { + if (!method_exists('Symfony\Component\Debug\Exception\FlattenException', $method)) { + throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', \get_called_class(), $method)); + } + + return \call_user_func_array(array('Symfony\Component\Debug\Exception\FlattenException', $method), $args); + } + + public function __call($method, $args) + { + if (!isset($this->handler)) { + $this->handler = new DebugFlattenException(); + } + + if (!method_exists($this->handler, $method)) { + throw new \BadMethodCallException(sprintf('Call to undefined method %s::%s()', \get_class($this), $method)); + } + + return \call_user_func_array(array($this->handler, $method), $args); + } +} + +namespace Symfony\Component\Debug\Exception; + +use Symfony\Component\HttpKernel\Exception\FlattenException as LegacyFlattenException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + +/** + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + */ +class FlattenException extends LegacyFlattenException +{ + private $message; + private $code; + private $previous; + private $trace; + private $class; + private $statusCode; + private $headers; + private $file; + private $line; + + public static function create(\Exception $exception, $statusCode = null, array $headers = array()) + { + $e = new static(); + $e->setMessage($exception->getMessage()); + $e->setCode($exception->getCode()); + + if ($exception instanceof HttpExceptionInterface) { + $statusCode = $exception->getStatusCode(); + $headers = array_merge($headers, $exception->getHeaders()); + } + + if (null === $statusCode) { + $statusCode = 500; + } + + $e->setStatusCode($statusCode); + $e->setHeaders($headers); + $e->setTraceFromException($exception); + $e->setClass(\get_class($exception)); + $e->setFile($exception->getFile()); + $e->setLine($exception->getLine()); + + $previous = $exception->getPrevious(); + + if ($previous instanceof \Exception) { + $e->setPrevious(static::create($previous)); + } elseif ($previous instanceof \Throwable) { + $e->setPrevious(static::create(new FatalThrowableError($previous))); + } + + return $e; + } + + public function toArray() + { + $exceptions = array(); + foreach (array_merge(array($this), $this->getAllPrevious()) as $exception) { + $exceptions[] = array( + 'message' => $exception->getMessage(), + 'class' => $exception->getClass(), + 'trace' => $exception->getTrace(), + ); + } + + return $exceptions; + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function setStatusCode($code) + { + $this->statusCode = $code; + } + + public function getHeaders() + { + return $this->headers; + } + + public function setHeaders(array $headers) + { + $this->headers = $headers; + } + + public function getClass() + { + return $this->class; + } + + public function setClass($class) + { + $this->class = $class; + } + + public function getFile() + { + return $this->file; + } + + public function setFile($file) + { + $this->file = $file; + } + + public function getLine() + { + return $this->line; + } + + public function setLine($line) + { + $this->line = $line; + } + + public function getMessage() + { + return $this->message; + } + + public function setMessage($message) + { + $this->message = $message; + } + + public function getCode() + { + return $this->code; + } + + public function setCode($code) + { + $this->code = $code; + } + + public function getPrevious() + { + return $this->previous; + } + + public function setPrevious(FlattenException $previous) + { + $this->previous = $previous; + } + + public function getAllPrevious() + { + $exceptions = array(); + $e = $this; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + return $exceptions; + } + + public function getTrace() + { + return $this->trace; + } + + public function setTraceFromException(\Exception $exception) + { + $this->setTrace($exception->getTrace(), $exception->getFile(), $exception->getLine()); + } + + public function setTrace($trace, $file, $line) + { + $this->trace = array(); + $this->trace[] = array( + 'namespace' => '', + 'short_class' => '', + 'class' => '', + 'type' => '', + 'function' => '', + 'file' => $file, + 'line' => $line, + 'args' => array(), + ); + foreach ($trace as $entry) { + $class = ''; + $namespace = ''; + if (isset($entry['class'])) { + $parts = explode('\\', $entry['class']); + $class = array_pop($parts); + $namespace = implode('\\', $parts); + } + + $this->trace[] = array( + 'namespace' => $namespace, + 'short_class' => $class, + 'class' => isset($entry['class']) ? $entry['class'] : '', + 'type' => isset($entry['type']) ? $entry['type'] : '', + 'function' => isset($entry['function']) ? $entry['function'] : null, + 'file' => isset($entry['file']) ? $entry['file'] : null, + 'line' => isset($entry['line']) ? $entry['line'] : null, + 'args' => isset($entry['args']) ? $this->flattenArgs($entry['args']) : array(), + ); + } + } + + private function flattenArgs($args, $level = 0, &$count = 0) + { + $result = array(); + foreach ($args as $key => $value) { + if (++$count > 1e4) { + return array('array', '*SKIPPED over 10000 entries*'); + } + if ($value instanceof \__PHP_Incomplete_Class) { + // is_object() returns false on PHP<=7.1 + $result[$key] = array('incomplete-object', $this->getClassNameFromIncomplete($value)); + } elseif (\is_object($value)) { + $result[$key] = array('object', \get_class($value)); + } elseif (\is_array($value)) { + if ($level > 10) { + $result[$key] = array('array', '*DEEP NESTED ARRAY*'); + } else { + $result[$key] = array('array', $this->flattenArgs($value, $level + 1, $count)); + } + } elseif (null === $value) { + $result[$key] = array('null', null); + } elseif (\is_bool($value)) { + $result[$key] = array('boolean', $value); + } elseif (\is_resource($value)) { + $result[$key] = array('resource', get_resource_type($value)); + } else { + $result[$key] = array('string', (string) $value); + } + } + + return $result; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/vendor/symfony/debug/Exception/OutOfMemoryException.php b/vendor/symfony/debug/Exception/OutOfMemoryException.php new file mode 100644 index 0000000..fec1979 --- /dev/null +++ b/vendor/symfony/debug/Exception/OutOfMemoryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Out of memory exception. + * + * @author Nicolas Grekas + */ +class OutOfMemoryException extends FatalErrorException +{ +} diff --git a/vendor/symfony/debug/Exception/UndefinedFunctionException.php b/vendor/symfony/debug/Exception/UndefinedFunctionException.php new file mode 100644 index 0000000..8f5f454 --- /dev/null +++ b/vendor/symfony/debug/Exception/UndefinedFunctionException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Undefined Function Exception. + * + * @author Konstanton Myakshin + */ +class UndefinedFunctionException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + null, + true, + null, + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/Exception/UndefinedMethodException.php b/vendor/symfony/debug/Exception/UndefinedMethodException.php new file mode 100644 index 0000000..f7e340b --- /dev/null +++ b/vendor/symfony/debug/Exception/UndefinedMethodException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\Exception; + +/** + * Undefined Method Exception. + * + * @author Grégoire Pineau + */ +class UndefinedMethodException extends FatalErrorException +{ + public function __construct($message, \ErrorException $previous) + { + parent::__construct( + $message, + $previous->getCode(), + $previous->getSeverity(), + $previous->getFile(), + $previous->getLine(), + null, + true, + null, + $previous->getPrevious() + ); + $this->setTrace($previous->getTrace()); + } +} diff --git a/vendor/symfony/debug/ExceptionHandler.php b/vendor/symfony/debug/ExceptionHandler.php new file mode 100644 index 0000000..fd9af19 --- /dev/null +++ b/vendor/symfony/debug/ExceptionHandler.php @@ -0,0 +1,490 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug; + +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\Debug\Exception\OutOfMemoryException; +use Symfony\Component\HttpFoundation\Response; + +/** + * ExceptionHandler converts an exception to a Response object. + * + * It is mostly useful in debug mode to replace the default PHP/XDebug + * output with something prettier and more useful. + * + * As this class is mainly used during Kernel boot, where nothing is yet + * available, the Response content is always HTML. + * + * @author Fabien Potencier + * @author Nicolas Grekas + */ +class ExceptionHandler +{ + private $debug; + private $charset; + private $handler; + private $caughtBuffer; + private $caughtLength; + private $fileLinkFormat; + + public function __construct($debug = true, $charset = null, $fileLinkFormat = null) + { + if (false !== strpos($charset, '%')) { + @trigger_error('Providing $fileLinkFormat as second argument to '.__METHOD__.' is deprecated since Symfony 2.8 and will be unsupported in 3.0. Please provide it as third argument, after $charset.', E_USER_DEPRECATED); + + // Swap $charset and $fileLinkFormat for BC reasons + $pivot = $fileLinkFormat; + $fileLinkFormat = $charset; + $charset = $pivot; + } + $this->debug = $debug; + $this->charset = $charset ?: ini_get('default_charset') ?: 'UTF-8'; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + } + + /** + * Registers the exception handler. + * + * @param bool $debug Enable/disable debug mode, where the stack trace is displayed + * @param string|null $charset The charset used by exception messages + * @param string|null $fileLinkFormat The IDE link template + * + * @return static + */ + public static function register($debug = true, $charset = null, $fileLinkFormat = null) + { + $handler = new static($debug, $charset, $fileLinkFormat); + + $prev = set_exception_handler(array($handler, 'handle')); + if (\is_array($prev) && $prev[0] instanceof ErrorHandler) { + restore_exception_handler(); + $prev[0]->setExceptionHandler(array($handler, 'handle')); + } + + return $handler; + } + + /** + * Sets a user exception handler. + * + * @param callable $handler An handler that will be called on Exception + * + * @return callable|null The previous exception handler if any + */ + public function setHandler($handler) + { + if (null !== $handler && !\is_callable($handler)) { + throw new \LogicException('The exception handler must be a valid PHP callable.'); + } + $old = $this->handler; + $this->handler = $handler; + + return $old; + } + + /** + * Sets the format for links to source files. + * + * @param string $format The format for links to source files + * + * @return string The previous file link format + */ + public function setFileLinkFormat($format) + { + $old = $this->fileLinkFormat; + $this->fileLinkFormat = $format; + + return $old; + } + + /** + * Sends a response for the given Exception. + * + * To be as fail-safe as possible, the exception is first handled + * by our simple exception handler, then by the user exception handler. + * The latter takes precedence and any output from the former is cancelled, + * if and only if nothing bad happens in this handling path. + */ + public function handle(\Exception $exception) + { + if (null === $this->handler || $exception instanceof OutOfMemoryException) { + $this->failSafeHandle($exception); + + return; + } + + $caughtLength = $this->caughtLength = 0; + + ob_start(array($this, 'catchOutput')); + $this->failSafeHandle($exception); + while (null === $this->caughtBuffer && ob_end_flush()) { + // Empty loop, everything is in the condition + } + if (isset($this->caughtBuffer[0])) { + ob_start(array($this, 'cleanOutput')); + echo $this->caughtBuffer; + $caughtLength = ob_get_length(); + } + $this->caughtBuffer = null; + + try { + \call_user_func($this->handler, $exception); + $this->caughtLength = $caughtLength; + } catch (\Exception $e) { + if (!$caughtLength) { + // All handlers failed. Let PHP handle that now. + throw $exception; + } + } + } + + /** + * Sends a response for the given Exception. + * + * If you have the Symfony HttpFoundation component installed, + * this method will use it to create and send the response. If not, + * it will fallback to plain PHP functions. + */ + private function failSafeHandle(\Exception $exception) + { + if (class_exists('Symfony\Component\HttpFoundation\Response', false) + && __CLASS__ !== \get_class($this) + && ($reflector = new \ReflectionMethod($this, 'createResponse')) + && __CLASS__ !== $reflector->class + ) { + $response = $this->createResponse($exception); + $response->sendHeaders(); + $response->sendContent(); + @trigger_error(sprintf("The %s::createResponse method is deprecated since Symfony 2.8 and won't be called anymore when handling an exception in 3.0.", $reflector->class), E_USER_DEPRECATED); + + return; + } + + $this->sendPhpResponse($exception); + } + + /** + * Sends the error associated with the given Exception as a plain PHP response. + * + * This method uses plain PHP functions like header() and echo to output + * the response. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + */ + public function sendPhpResponse($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + if (!headers_sent()) { + header(sprintf('HTTP/1.0 %s', $exception->getStatusCode())); + foreach ($exception->getHeaders() as $name => $value) { + header($name.': '.$value, false); + } + header('Content-Type: text/html; charset='.$this->charset); + } + + echo $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + + /** + * Creates the error Response associated with the given Exception. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + * + * @return Response A Response instance + * + * @deprecated since 2.8, to be removed in 3.0. + */ + public function createResponse($exception) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + return Response::create($this->getHtml($exception), $exception->getStatusCode(), $exception->getHeaders())->setCharset($this->charset); + } + + /** + * Gets the full HTML content associated with the given exception. + * + * @param \Exception|FlattenException $exception An \Exception or FlattenException instance + * + * @return string The HTML content as a string + */ + public function getHtml($exception) + { + if (!$exception instanceof FlattenException) { + $exception = FlattenException::create($exception); + } + + return $this->decorate($this->getContent($exception), $this->getStylesheet($exception)); + } + + /** + * Gets the HTML content associated with the given exception. + * + * @return string The content as a string + */ + public function getContent(FlattenException $exception) + { + switch ($exception->getStatusCode()) { + case 404: + $title = 'Sorry, the page you are looking for could not be found.'; + break; + default: + $title = 'Whoops, looks like something went wrong.'; + } + + $content = ''; + if ($this->debug) { + try { + $count = \count($exception->getAllPrevious()); + $total = $count + 1; + foreach ($exception->toArray() as $position => $e) { + $ind = $count - $position + 1; + $class = $this->formatClass($e['class']); + $message = nl2br($this->escapeHtml($e['message'])); + $content .= sprintf(<<<'EOF' +

      + %d/%d + %s%s: + %s +

      +
      +
        + +EOF + , $ind, $total, $class, $this->formatPath($e['trace'][0]['file'], $e['trace'][0]['line']), $message); + foreach ($e['trace'] as $trace) { + $content .= '
      1. '; + if ($trace['function']) { + $content .= sprintf('at %s%s%s(%s)', $this->formatClass($trace['class']), $trace['type'], $trace['function'], $this->formatArgs($trace['args'])); + } + if (isset($trace['file']) && isset($trace['line'])) { + $content .= $this->formatPath($trace['file'], $trace['line']); + } + $content .= "
      2. \n"; + } + + $content .= "
      \n
      \n"; + } + } catch (\Exception $e) { + // something nasty happened and we cannot throw an exception anymore + if ($this->debug) { + $title = sprintf('Exception thrown when handling an exception (%s: %s)', \get_class($e), $this->escapeHtml($e->getMessage())); + } else { + $title = 'Whoops, looks like something went wrong.'; + } + } + } + + return << +

      $title

      + $content +
  • +EOF; + } + + /** + * Gets the stylesheet associated with the given exception. + * + * @return string The stylesheet as a string + */ + public function getStylesheet(FlattenException $exception) + { + return <<<'EOF' + .sf-reset { font: 11px Verdana, Arial, sans-serif; color: #333 } + .sf-reset .clear { clear:both; height:0; font-size:0; line-height:0; } + .sf-reset .clear_fix:after { display:block; height:0; clear:both; visibility:hidden; } + .sf-reset .clear_fix { display:inline-block; } + .sf-reset * html .clear_fix { height:1%; } + .sf-reset .clear_fix { display:block; } + .sf-reset, .sf-reset .block { margin: auto } + .sf-reset abbr { border-bottom: 1px dotted #000; cursor: help; } + .sf-reset p { font-size:14px; line-height:20px; color:#868686; padding-bottom:20px } + .sf-reset strong { font-weight:bold; } + .sf-reset a { color:#6c6159; cursor: default; } + .sf-reset a img { border:none; } + .sf-reset a:hover { text-decoration:underline; } + .sf-reset em { font-style:italic; } + .sf-reset h1, .sf-reset h2 { font: 20px Georgia, "Times New Roman", Times, serif } + .sf-reset .exception_counter { background-color: #fff; color: #333; padding: 6px; float: left; margin-right: 10px; float: left; display: block; } + .sf-reset .exception_title { margin-left: 3em; margin-bottom: 0.7em; display: block; } + .sf-reset .exception_message { margin-left: 3em; display: block; } + .sf-reset .traces li { font-size:12px; padding: 2px 4px; list-style-type:decimal; margin-left:20px; } + .sf-reset .block { background-color:#FFFFFF; padding:10px 28px; margin-bottom:20px; + -webkit-border-bottom-right-radius: 16px; + -webkit-border-bottom-left-radius: 16px; + -moz-border-radius-bottomright: 16px; + -moz-border-radius-bottomleft: 16px; + border-bottom-right-radius: 16px; + border-bottom-left-radius: 16px; + border-bottom:1px solid #ccc; + border-right:1px solid #ccc; + border-left:1px solid #ccc; + word-wrap: break-word; + } + .sf-reset .block_exception { background-color:#ddd; color: #333; padding:20px; + -webkit-border-top-left-radius: 16px; + -webkit-border-top-right-radius: 16px; + -moz-border-radius-topleft: 16px; + -moz-border-radius-topright: 16px; + border-top-left-radius: 16px; + border-top-right-radius: 16px; + border-top:1px solid #ccc; + border-right:1px solid #ccc; + border-left:1px solid #ccc; + overflow: hidden; + word-wrap: break-word; + } + .sf-reset a { background:none; color:#868686; text-decoration:none; } + .sf-reset a:hover { background:none; color:#313131; text-decoration:underline; } + .sf-reset ol { padding: 10px 0; } + .sf-reset h1 { background-color:#FFFFFF; padding: 15px 28px; margin-bottom: 20px; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + border: 1px solid #ccc; + } +EOF; + } + + private function decorate($content, $css) + { + return << + + + + + + + + $content + + +EOF; + } + + private function formatClass($class) + { + $parts = explode('\\', $class); + + return sprintf('%s', $class, array_pop($parts)); + } + + private function formatPath($path, $line) + { + $path = $this->escapeHtml($path); + $file = preg_match('#[^/\\\\]*$#', $path, $file) ? $file[0] : $path; + + if ($linkFormat = $this->fileLinkFormat) { + $link = strtr($this->escapeHtml($linkFormat), array('%f' => $path, '%l' => (int) $line)); + + return sprintf(' in %s line %d', $link, $file, $line); + } + + return sprintf(' in %s line %d', $path, $file, $line); + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + private function formatArgs(array $args) + { + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $formattedValue = sprintf('object(%s)', $this->formatClass($item[1])); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('string' === $item[0]) { + $formattedValue = sprintf("'%s'", $this->escapeHtml($item[1])); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', var_export($this->escapeHtml((string) $item[1]), true)); + } + + $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue); + } + + return implode(', ', $result); + } + + /** + * Returns an UTF-8 and HTML encoded string. + * + * @deprecated since version 2.7, to be removed in 3.0. + */ + protected static function utf8Htmlize($str) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + + return htmlspecialchars($str, ENT_QUOTES | (\PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), 'UTF-8'); + } + + /** + * HTML-encodes a string. + */ + private function escapeHtml($str) + { + return htmlspecialchars($str, ENT_QUOTES | (\PHP_VERSION_ID >= 50400 ? ENT_SUBSTITUTE : 0), $this->charset); + } + + /** + * @internal + */ + public function catchOutput($buffer) + { + $this->caughtBuffer = $buffer; + + return ''; + } + + /** + * @internal + */ + public function cleanOutput($buffer) + { + if ($this->caughtLength) { + // use substr_replace() instead of substr() for mbstring overloading resistance + $cleanBuffer = substr_replace($buffer, '', 0, $this->caughtLength); + if (isset($cleanBuffer[0])) { + $buffer = $cleanBuffer; + } + } + + return $buffer; + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php new file mode 100644 index 0000000..89e83be --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/ClassNotFoundFatalErrorHandler.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Composer\Autoload\ClassLoader as ComposerClassLoader; +use Symfony\Component\ClassLoader\ClassLoader as SymfonyClassLoader; +use Symfony\Component\ClassLoader\UniversalClassLoader as SymfonyUniversalClassLoader; +use Symfony\Component\Debug\DebugClassLoader; +use Symfony\Component\Debug\Exception\ClassNotFoundException; +use Symfony\Component\Debug\Exception\FatalErrorException; + +/** + * ErrorHandler for classes that do not exist. + * + * @author Fabien Potencier + */ +class ClassNotFoundFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + $messageLen = \strlen($error['message']); + $notFoundSuffix = '\' not found'; + $notFoundSuffixLen = \strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return; + } + + if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { + return; + } + + foreach (array('class', 'interface', 'trait') as $typeName) { + $prefix = ucfirst($typeName).' \''; + $prefixLen = \strlen($prefix); + if (0 !== strpos($error['message'], $prefix)) { + continue; + } + + $fullyQualifiedClassName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedClassName, '\\')) { + $className = substr($fullyQualifiedClassName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedClassName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to load %s "%s" from namespace "%s".', $typeName, $className, $namespacePrefix); + $tail = ' for another namespace?'; + } else { + $className = $fullyQualifiedClassName; + $message = sprintf('Attempted to load %s "%s" from the global namespace.', $typeName, $className); + $tail = '?'; + } + + if ($candidates = $this->getClassCandidates($className)) { + $tail = array_pop($candidates).'"?'; + if ($candidates) { + $tail = ' for e.g. "'.implode('", "', $candidates).'" or "'.$tail; + } else { + $tail = ' for "'.$tail; + } + } + $message .= "\nDid you forget a \"use\" statement".$tail; + + return new ClassNotFoundException($message, $exception); + } + } + + /** + * Tries to guess the full namespace for a given class name. + * + * By default, it looks for PSR-0 and PSR-4 classes registered via a Symfony or a Composer + * autoloader (that should cover all common cases). + * + * @param string $class A class name (without its namespace) + * + * @return array An array of possible fully qualified class names + */ + private function getClassCandidates($class) + { + if (!\is_array($functions = spl_autoload_functions())) { + return array(); + } + + // find Symfony and Composer autoloaders + $classes = array(); + + foreach ($functions as $function) { + if (!\is_array($function)) { + continue; + } + // get class loaders wrapped by DebugClassLoader + if ($function[0] instanceof DebugClassLoader) { + $function = $function[0]->getClassLoader(); + + // @deprecated since version 2.5. Returning an object from DebugClassLoader::getClassLoader() is deprecated. + if (\is_object($function)) { + $function = array($function); + } + + if (!\is_array($function)) { + continue; + } + } + + if ($function[0] instanceof ComposerClassLoader || $function[0] instanceof SymfonyClassLoader || $function[0] instanceof SymfonyUniversalClassLoader) { + foreach ($function[0]->getPrefixes() as $prefix => $paths) { + foreach ($paths as $path) { + $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); + } + } + } + if ($function[0] instanceof ComposerClassLoader) { + foreach ($function[0]->getPrefixesPsr4() as $prefix => $paths) { + foreach ($paths as $path) { + $classes = array_merge($classes, $this->findClassInPath($path, $class, $prefix)); + } + } + } + } + + return array_unique($classes); + } + + /** + * @param string $path + * @param string $class + * @param string $prefix + * + * @return array + */ + private function findClassInPath($path, $class, $prefix) + { + if (!$path = realpath($path.'/'.strtr($prefix, '\\_', '//')) ?: realpath($path.'/'.\dirname(strtr($prefix, '\\_', '//'))) ?: realpath($path)) { + return array(); + } + + $classes = array(); + $filename = $class.'.php'; + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($filename == $file->getFileName() && $class = $this->convertFileToClass($path, $file->getPathName(), $prefix)) { + $classes[] = $class; + } + } + + return $classes; + } + + /** + * @param string $path + * @param string $file + * @param string $prefix + * + * @return string|null + */ + private function convertFileToClass($path, $file, $prefix) + { + $candidates = array( + // namespaced class + $namespacedClass = str_replace(array($path.\DIRECTORY_SEPARATOR, '.php', '/'), array('', '', '\\'), $file), + // namespaced class (with target dir) + $prefix.$namespacedClass, + // namespaced class (with target dir and separator) + $prefix.'\\'.$namespacedClass, + // PEAR class + str_replace('\\', '_', $namespacedClass), + // PEAR class (with target dir) + str_replace('\\', '_', $prefix.$namespacedClass), + // PEAR class (with target dir and separator) + str_replace('\\', '_', $prefix.'\\'.$namespacedClass), + ); + + if ($prefix) { + $candidates = array_filter($candidates, function ($candidate) use ($prefix) { return 0 === strpos($candidate, $prefix); }); + } + + // We cannot use the autoloader here as most of them use require; but if the class + // is not found, the new autoloader call will require the file again leading to a + // "cannot redeclare class" error. + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + + require_once $file; + + foreach ($candidates as $candidate) { + if ($this->classExists($candidate)) { + return $candidate; + } + } + } + + /** + * @param string $class + * + * @return bool + */ + private function classExists($class) + { + return class_exists($class, false) || interface_exists($class, false) || (\function_exists('trait_exists') && trait_exists($class, false)); + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php b/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php new file mode 100644 index 0000000..6b87eb3 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/FatalErrorHandlerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; + +/** + * Attempts to convert fatal errors to exceptions. + * + * @author Fabien Potencier + */ +interface FatalErrorHandlerInterface +{ + /** + * Attempts to convert an error into an exception. + * + * @param array $error An array as returned by error_get_last() + * @param FatalErrorException $exception A FatalErrorException instance + * + * @return FatalErrorException|null A FatalErrorException instance if the class is able to convert the error, null otherwise + */ + public function handleError(array $error, FatalErrorException $exception); +} diff --git a/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php new file mode 100644 index 0000000..db24180 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/UndefinedFunctionFatalErrorHandler.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\UndefinedFunctionException; + +/** + * ErrorHandler for undefined functions. + * + * @author Fabien Potencier + */ +class UndefinedFunctionFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + $messageLen = \strlen($error['message']); + $notFoundSuffix = '()'; + $notFoundSuffixLen = \strlen($notFoundSuffix); + if ($notFoundSuffixLen > $messageLen) { + return; + } + + if (0 !== substr_compare($error['message'], $notFoundSuffix, -$notFoundSuffixLen)) { + return; + } + + $prefix = 'Call to undefined function '; + $prefixLen = \strlen($prefix); + if (0 !== strpos($error['message'], $prefix)) { + return; + } + + $fullyQualifiedFunctionName = substr($error['message'], $prefixLen, -$notFoundSuffixLen); + if (false !== $namespaceSeparatorIndex = strrpos($fullyQualifiedFunctionName, '\\')) { + $functionName = substr($fullyQualifiedFunctionName, $namespaceSeparatorIndex + 1); + $namespacePrefix = substr($fullyQualifiedFunctionName, 0, $namespaceSeparatorIndex); + $message = sprintf('Attempted to call function "%s" from namespace "%s".', $functionName, $namespacePrefix); + } else { + $functionName = $fullyQualifiedFunctionName; + $message = sprintf('Attempted to call function "%s" from the global namespace.', $functionName); + } + + $candidates = array(); + foreach (get_defined_functions() as $type => $definedFunctionNames) { + foreach ($definedFunctionNames as $definedFunctionName) { + if (false !== $namespaceSeparatorIndex = strrpos($definedFunctionName, '\\')) { + $definedFunctionNameBasename = substr($definedFunctionName, $namespaceSeparatorIndex + 1); + } else { + $definedFunctionNameBasename = $definedFunctionName; + } + + if ($definedFunctionNameBasename === $functionName) { + $candidates[] = '\\'.$definedFunctionName; + } + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedFunctionException($message, $exception); + } +} diff --git a/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php b/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php new file mode 100644 index 0000000..618a2c2 --- /dev/null +++ b/vendor/symfony/debug/FatalErrorHandler/UndefinedMethodFatalErrorHandler.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Debug\FatalErrorHandler; + +use Symfony\Component\Debug\Exception\FatalErrorException; +use Symfony\Component\Debug\Exception\UndefinedMethodException; + +/** + * ErrorHandler for undefined methods. + * + * @author Grégoire Pineau + */ +class UndefinedMethodFatalErrorHandler implements FatalErrorHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function handleError(array $error, FatalErrorException $exception) + { + preg_match('/^Call to undefined method (.*)::(.*)\(\)$/', $error['message'], $matches); + if (!$matches) { + return; + } + + $className = $matches[1]; + $methodName = $matches[2]; + + $message = sprintf('Attempted to call an undefined method named "%s" of class "%s".', $methodName, $className); + + if (!class_exists($className) || null === $methods = get_class_methods($className)) { + // failed to get the class or its methods on which an unknown method was called (for example on an anonymous class) + return new UndefinedMethodException($message, $exception); + } + + $candidates = array(); + foreach ($methods as $definedMethodName) { + $lev = levenshtein($methodName, $definedMethodName); + if ($lev <= \strlen($methodName) / 3 || false !== strpos($definedMethodName, $methodName)) { + $candidates[] = $definedMethodName; + } + } + + if ($candidates) { + sort($candidates); + $last = array_pop($candidates).'"?'; + if ($candidates) { + $candidates = 'e.g. "'.implode('", "', $candidates).'" or "'.$last; + } else { + $candidates = '"'.$last; + } + + $message .= "\nDid you mean to call ".$candidates; + } + + return new UndefinedMethodException($message, $exception); + } +} diff --git a/vendor/symfony/debug/LICENSE b/vendor/symfony/debug/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/debug/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/debug/Resources/ext/README.md b/vendor/symfony/debug/Resources/ext/README.md new file mode 100644 index 0000000..25dccf0 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/README.md @@ -0,0 +1,134 @@ +Symfony Debug Extension for PHP 5 +================================= + +This extension publishes several functions to help building powerful debugging tools. +It is compatible with PHP 5.3, 5.4, 5.5 and 5.6; with ZTS and non-ZTS modes. +It is not required thus not provided for PHP 7. + +symfony_zval_info() +------------------- + +- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP, +- does work with references, preventing memory copying. + +Its behavior is about the same as: + +```php + gettype($array[$key]), + 'zval_hash' => /* hashed memory address of $array[$key] */, + 'zval_refcount' => /* internal zval refcount of $array[$key] */, + 'zval_isref' => /* is_ref status of $array[$key] */, + ); + + switch ($info['type']) { + case 'object': + $info += array( + 'object_class' => get_class($array[$key]), + 'object_refcount' => /* internal object refcount of $array[$key] */, + 'object_hash' => spl_object_hash($array[$key]), + 'object_handle' => /* internal object handle $array[$key] */, + ); + break; + + case 'resource': + $info += array( + 'resource_handle' => (int) $array[$key], + 'resource_type' => get_resource_type($array[$key]), + 'resource_refcount' => /* internal resource refcount of $array[$key] */, + ); + break; + + case 'array': + $info += array( + 'array_count' => count($array[$key]), + ); + break; + + case 'string': + $info += array( + 'strlen' => strlen($array[$key]), + ); + break; + } + + return $info; +} +``` + +symfony_debug_backtrace() +------------------------- + +This function works like debug_backtrace(), except that it can fetch the full backtrace in case of fatal errors: + +```php +function foo() { fatal(); } +function bar() { foo(); } + +function sd() { var_dump(symfony_debug_backtrace()); } + +register_shutdown_function('sd'); + +bar(); + +/* Will output +Fatal error: Call to undefined function fatal() in foo.php on line 42 +array(3) { + [0]=> + array(2) { + ["function"]=> + string(2) "sd" + ["args"]=> + array(0) { + } + } + [1]=> + array(4) { + ["file"]=> + string(7) "foo.php" + ["line"]=> + int(1) + ["function"]=> + string(3) "foo" + ["args"]=> + array(0) { + } + } + [2]=> + array(4) { + ["file"]=> + string(102) "foo.php" + ["line"]=> + int(2) + ["function"]=> + string(3) "bar" + ["args"]=> + array(0) { + } + } +} +*/ +``` + +Usage +----- + +To enable the extension from source, run: + +``` + phpize + ./configure + make + sudo make install +``` diff --git a/vendor/symfony/debug/Resources/ext/config.m4 b/vendor/symfony/debug/Resources/ext/config.m4 new file mode 100644 index 0000000..3c56047 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension symfony_debug + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(symfony_debug, for symfony_debug support, +dnl Make sure that the comment is aligned: +dnl [ --with-symfony_debug Include symfony_debug support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(symfony_debug, whether to enable symfony_debug support, +dnl Make sure that the comment is aligned: +[ --enable-symfony_debug Enable symfony_debug support]) + +if test "$PHP_SYMFONY_DEBUG" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-symfony_debug -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/symfony_debug.h" # you most likely want to change this + dnl if test -r $PHP_SYMFONY_DEBUG/$SEARCH_FOR; then # path given as parameter + dnl SYMFONY_DEBUG_DIR=$PHP_SYMFONY_DEBUG + dnl else # search default path list + dnl AC_MSG_CHECKING([for symfony_debug files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl SYMFONY_DEBUG_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$SYMFONY_DEBUG_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the symfony_debug distribution]) + dnl fi + + dnl # --with-symfony_debug -> add include path + dnl PHP_ADD_INCLUDE($SYMFONY_DEBUG_DIR/include) + + dnl # --with-symfony_debug -> check for lib and symbol presence + dnl LIBNAME=symfony_debug # you may want to change this + dnl LIBSYMBOL=symfony_debug # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SYMFONY_DEBUG_DIR/lib, SYMFONY_DEBUG_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_SYMFONY_DEBUGLIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong symfony_debug lib version or lib not found]) + dnl ],[ + dnl -L$SYMFONY_DEBUG_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(SYMFONY_DEBUG_SHARED_LIBADD) + + PHP_NEW_EXTENSION(symfony_debug, symfony_debug.c, $ext_shared) +fi diff --git a/vendor/symfony/debug/Resources/ext/config.w32 b/vendor/symfony/debug/Resources/ext/config.w32 new file mode 100644 index 0000000..487e691 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("symfony_debug", "for symfony_debug support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("symfony_debug", "enable symfony_debug support", "no"); + +if (PHP_SYMFONY_DEBUG != "no") { + EXTENSION("symfony_debug", "symfony_debug.c"); +} + diff --git a/vendor/symfony/debug/Resources/ext/php_symfony_debug.h b/vendor/symfony/debug/Resources/ext/php_symfony_debug.h new file mode 100644 index 0000000..26d0e8c --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/php_symfony_debug.h @@ -0,0 +1,60 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifndef PHP_SYMFONY_DEBUG_H +#define PHP_SYMFONY_DEBUG_H + +extern zend_module_entry symfony_debug_module_entry; +#define phpext_symfony_debug_ptr &symfony_debug_module_entry + +#define PHP_SYMFONY_DEBUG_VERSION "2.7" + +#ifdef PHP_WIN32 +# define PHP_SYMFONY_DEBUG_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_SYMFONY_DEBUG_API __attribute__ ((visibility("default"))) +#else +# define PHP_SYMFONY_DEBUG_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +ZEND_BEGIN_MODULE_GLOBALS(symfony_debug) + intptr_t req_rand_init; + void (*old_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + zval *debug_bt; +ZEND_END_MODULE_GLOBALS(symfony_debug) + +PHP_MINIT_FUNCTION(symfony_debug); +PHP_MSHUTDOWN_FUNCTION(symfony_debug); +PHP_RINIT_FUNCTION(symfony_debug); +PHP_RSHUTDOWN_FUNCTION(symfony_debug); +PHP_MINFO_FUNCTION(symfony_debug); +PHP_GINIT_FUNCTION(symfony_debug); +PHP_GSHUTDOWN_FUNCTION(symfony_debug); + +PHP_FUNCTION(symfony_zval_info); +PHP_FUNCTION(symfony_debug_backtrace); + +static char *_symfony_debug_memory_address_hash(void * TSRMLS_DC); +static const char *_symfony_debug_zval_type(zval *); +static const char* _symfony_debug_get_resource_type(long TSRMLS_DC); +static int _symfony_debug_get_resource_refcount(long TSRMLS_DC); + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args); + +#ifdef ZTS +#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v) +#else +#define SYMFONY_DEBUG_G(v) (symfony_debug_globals.v) +#endif + +#endif /* PHP_SYMFONY_DEBUG_H */ diff --git a/vendor/symfony/debug/Resources/ext/symfony_debug.c b/vendor/symfony/debug/Resources/ext/symfony_debug.c new file mode 100644 index 0000000..0d7cb60 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/symfony_debug.c @@ -0,0 +1,283 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#ifdef ZTS +#include "TSRM.h" +#endif +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_symfony_debug.h" +#include "ext/standard/php_rand.h" +#include "ext/standard/php_lcg.h" +#include "ext/spl/php_spl.h" +#include "Zend/zend_gc.h" +#include "Zend/zend_builtin_functions.h" +#include "Zend/zend_extensions.h" /* for ZEND_EXTENSION_API_NO */ +#include "ext/standard/php_array.h" +#include "Zend/zend_interfaces.h" +#include "SAPI.h" + +#define IS_PHP_53 ZEND_EXTENSION_API_NO == 220090626 + +ZEND_DECLARE_MODULE_GLOBALS(symfony_debug) + +ZEND_BEGIN_ARG_INFO_EX(symfony_zval_arginfo, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_ARRAY_INFO(0, array, 0) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +const zend_function_entry symfony_debug_functions[] = { + PHP_FE(symfony_zval_info, symfony_zval_arginfo) + PHP_FE(symfony_debug_backtrace, NULL) + PHP_FE_END +}; + +PHP_FUNCTION(symfony_debug_backtrace) +{ + if (zend_parse_parameters_none() == FAILURE) { + return; + } +#if IS_PHP_53 + zend_fetch_debug_backtrace(return_value, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(return_value, 1, 0, 0 TSRMLS_CC); +#endif + + if (!SYMFONY_DEBUG_G(debug_bt)) { + return; + } + + php_array_merge(Z_ARRVAL_P(return_value), Z_ARRVAL_P(SYMFONY_DEBUG_G(debug_bt)), 0 TSRMLS_CC); +} + +PHP_FUNCTION(symfony_zval_info) +{ + zval *key = NULL, *arg = NULL; + zval **data = NULL; + HashTable *array = NULL; + long options = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zh|l", &key, &array, &options) == FAILURE) { + return; + } + + switch (Z_TYPE_P(key)) { + case IS_STRING: + if (zend_symtable_find(array, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&data) == FAILURE) { + return; + } + break; + case IS_LONG: + if (zend_hash_index_find(array, Z_LVAL_P(key), (void **)&data)) { + return; + } + break; + } + + arg = *data; + + array_init(return_value); + + add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1); + add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg TSRMLS_CC), 16, 0); + add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg)); + add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg)); + + if (Z_TYPE_P(arg) == IS_OBJECT) { + char hash[33] = {0}; + + php_spl_object_hash(arg, (char *)hash TSRMLS_CC); + add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1); + add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount); + add_assoc_string(return_value, "object_hash", hash, 1); + add_assoc_long(return_value, "object_handle", Z_OBJ_HANDLE_P(arg)); + } else if (Z_TYPE_P(arg) == IS_ARRAY) { + add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg))); + } else if(Z_TYPE_P(arg) == IS_RESOURCE) { + add_assoc_long(return_value, "resource_handle", Z_LVAL_P(arg)); + add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg) TSRMLS_CC), 1); + add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg) TSRMLS_CC)); + } else if (Z_TYPE_P(arg) == IS_STRING) { + add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg)); + } +} + +void symfony_debug_error_cb(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) +{ + TSRMLS_FETCH(); + zval *retval; + + switch (type) { + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_CORE_WARNING: + case E_COMPILE_ERROR: + case E_COMPILE_WARNING: + ALLOC_INIT_ZVAL(retval); +#if IS_PHP_53 + zend_fetch_debug_backtrace(retval, 1, 0 TSRMLS_CC); +#else + zend_fetch_debug_backtrace(retval, 1, 0, 0 TSRMLS_CC); +#endif + SYMFONY_DEBUG_G(debug_bt) = retval; + } + + SYMFONY_DEBUG_G(old_error_cb)(type, error_filename, error_lineno, format, args); +} + +static const char* _symfony_debug_get_resource_type(long rsid TSRMLS_DC) +{ + const char *res_type; + res_type = zend_rsrc_list_get_rsrc_type(rsid TSRMLS_CC); + + if (!res_type) { + return "Unknown"; + } + + return res_type; +} + +static int _symfony_debug_get_resource_refcount(long rsid TSRMLS_DC) +{ + zend_rsrc_list_entry *le; + + if (zend_hash_index_find(&EG(regular_list), rsid, (void **) &le)==SUCCESS) { + return le->refcount; + } + + return 0; +} + +static char *_symfony_debug_memory_address_hash(void *address TSRMLS_DC) +{ + char *result = NULL; + intptr_t address_rand; + + if (!SYMFONY_DEBUG_G(req_rand_init)) { + if (!BG(mt_rand_is_seeded)) { + php_mt_srand(GENERATE_SEED() TSRMLS_CC); + } + SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(TSRMLS_C); + } + + address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init); + + spprintf(&result, 17, "%016zx", address_rand); + + return result; +} + +static const char *_symfony_debug_zval_type(zval *zv) +{ + switch (Z_TYPE_P(zv)) { + case IS_NULL: + return "NULL"; + break; + + case IS_BOOL: + return "boolean"; + break; + + case IS_LONG: + return "integer"; + break; + + case IS_DOUBLE: + return "double"; + break; + + case IS_STRING: + return "string"; + break; + + case IS_ARRAY: + return "array"; + break; + + case IS_OBJECT: + return "object"; + + case IS_RESOURCE: + return "resource"; + + default: + return "unknown type"; + } +} + +zend_module_entry symfony_debug_module_entry = { + STANDARD_MODULE_HEADER, + "symfony_debug", + symfony_debug_functions, + PHP_MINIT(symfony_debug), + PHP_MSHUTDOWN(symfony_debug), + PHP_RINIT(symfony_debug), + PHP_RSHUTDOWN(symfony_debug), + PHP_MINFO(symfony_debug), + PHP_SYMFONY_DEBUG_VERSION, + PHP_MODULE_GLOBALS(symfony_debug), + PHP_GINIT(symfony_debug), + PHP_GSHUTDOWN(symfony_debug), + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +#ifdef COMPILE_DL_SYMFONY_DEBUG +ZEND_GET_MODULE(symfony_debug) +#endif + +PHP_GINIT_FUNCTION(symfony_debug) +{ + memset(symfony_debug_globals, 0 , sizeof(*symfony_debug_globals)); +} + +PHP_GSHUTDOWN_FUNCTION(symfony_debug) +{ + +} + +PHP_MINIT_FUNCTION(symfony_debug) +{ + SYMFONY_DEBUG_G(old_error_cb) = zend_error_cb; + zend_error_cb = symfony_debug_error_cb; + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(symfony_debug) +{ + zend_error_cb = SYMFONY_DEBUG_G(old_error_cb); + + return SUCCESS; +} + +PHP_RINIT_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_RSHUTDOWN_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_MINFO_FUNCTION(symfony_debug) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Symfony Debug support", "enabled"); + php_info_print_table_header(2, "Symfony Debug version", PHP_SYMFONY_DEBUG_VERSION); + php_info_print_table_end(); +} diff --git a/vendor/symfony/debug/Resources/ext/tests/001.phpt b/vendor/symfony/debug/Resources/ext/tests/001.phpt new file mode 100644 index 0000000..4a87cd3 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/001.phpt @@ -0,0 +1,155 @@ +--TEST-- +Test symfony_zval_info API +--SKIPIF-- + +--FILE-- + $int, + 'float' => $float, + 'str' => $str, + 'object' => $object, + 'array' => $array, + 'resource' => $resource, + 'null' => $null, + 'bool' => $bool, + 'refcount' => &$refcount2, +); + +var_dump(symfony_zval_info('int', $var)); +var_dump(symfony_zval_info('float', $var)); +var_dump(symfony_zval_info('str', $var)); +var_dump(symfony_zval_info('object', $var)); +var_dump(symfony_zval_info('array', $var)); +var_dump(symfony_zval_info('resource', $var)); +var_dump(symfony_zval_info('null', $var)); +var_dump(symfony_zval_info('bool', $var)); + +var_dump(symfony_zval_info('refcount', $var)); +var_dump(symfony_zval_info('not-exist', $var)); +?> +--EXPECTF-- +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(6) "double" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(5) { + ["type"]=> + string(6) "string" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["strlen"]=> + int(6) +} +array(8) { + ["type"]=> + string(6) "object" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["object_class"]=> + string(8) "stdClass" + ["object_refcount"]=> + int(1) + ["object_hash"]=> + string(32) "%s" + ["object_handle"]=> + int(%d) +} +array(5) { + ["type"]=> + string(5) "array" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["array_count"]=> + int(2) +} +array(7) { + ["type"]=> + string(8) "resource" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["resource_handle"]=> + int(%d) + ["resource_type"]=> + string(6) "stream" + ["resource_refcount"]=> + int(1) +} +array(4) { + ["type"]=> + string(4) "NULL" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "boolean" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(3) + ["zval_isref"]=> + bool(true) +} +NULL diff --git a/vendor/symfony/debug/Resources/ext/tests/002.phpt b/vendor/symfony/debug/Resources/ext/tests/002.phpt new file mode 100644 index 0000000..afc7bb4 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/002.phpt @@ -0,0 +1,65 @@ +--TEST-- +Test symfony_debug_backtrace in case of fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Call to undefined function notexist() in %s on line %d +Array +( + [0] => Array + ( + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => foo + [args] => Array + ( + ) + + ) + + [2] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/vendor/symfony/debug/Resources/ext/tests/002_1.phpt b/vendor/symfony/debug/Resources/ext/tests/002_1.phpt new file mode 100644 index 0000000..86de3e1 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/002_1.phpt @@ -0,0 +1,48 @@ +--TEST-- +Test symfony_debug_backtrace in case of non fatal error +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Array +( + [0] => Array + ( + [file] => %s + [line] => %d + [function] => bt + [args] => Array + ( + ) + + ) + + [1] => Array + ( + [file] => %s + [line] => %d + [function] => bar + [args] => Array + ( + ) + + ) + +) diff --git a/vendor/symfony/debug/Resources/ext/tests/003.phpt b/vendor/symfony/debug/Resources/ext/tests/003.phpt new file mode 100644 index 0000000..4e76450 --- /dev/null +++ b/vendor/symfony/debug/Resources/ext/tests/003.phpt @@ -0,0 +1,87 @@ +--TEST-- +Test ErrorHandler in case of fatal error +--SKIPIF-- + +--FILE-- +setExceptionHandler('print_r'); + +if (\function_exists('xdebug_disable')) { + xdebug_disable(); +} + +bar(); +?> +--EXPECTF-- +Fatal error: Call to undefined function Symfony\Component\Debug\notexist() in %s on line %d +Symfony\Component\Debug\Exception\UndefinedFunctionException Object +( + [message:protected] => Attempted to call function "notexist" from namespace "Symfony\Component\Debug". + [string:Exception:private] => + [code:protected] => 0 + [file:protected] => %s + [line:protected] => %d + [trace:Exception:private] => Array + ( + [0] => Array + ( +%A [function] => Symfony\Component\Debug\foo +%A [args] => Array + ( + ) + + ) + + [1] => Array + ( +%A [function] => Symfony\Component\Debug\bar +%A [args] => Array + ( + ) + + ) +%A + ) + + [previous:Exception:private] => + [severity:protected] => 1 +) diff --git a/vendor/symfony/debug/composer.json b/vendor/symfony/debug/composer.json new file mode 100644 index 0000000..acf0ecd --- /dev/null +++ b/vendor/symfony/debug/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/debug", + "type": "library", + "description": "Symfony Debug Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Debug\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/dependency-injection/Alias.php b/vendor/symfony/dependency-injection/Alias.php new file mode 100644 index 0000000..eaf7f00 --- /dev/null +++ b/vendor/symfony/dependency-injection/Alias.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +class Alias +{ + private $id; + private $public; + + /** + * @param string $id Alias identifier + * @param bool $public If this alias is public + */ + public function __construct($id, $public = true) + { + $this->id = strtolower($id); + $this->public = $public; + } + + /** + * Checks if this DI Alias should be public or not. + * + * @return bool + */ + public function isPublic() + { + return $this->public; + } + + /** + * Sets if this Alias is public. + * + * @param bool $boolean If this Alias should be public + */ + public function setPublic($boolean) + { + $this->public = (bool) $boolean; + } + + /** + * Returns the Id of this alias. + * + * @return string The alias id + */ + public function __toString() + { + return $this->id; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php b/vendor/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php new file mode 100644 index 0000000..ce9d556 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AnalyzeServiceReferencesPass.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Run this pass before passes that need to know more about the relation of + * your services. + * + * This class will populate the ServiceReferenceGraph with information. You can + * retrieve the graph in other passes from the compiler. + * + * @author Johannes M. Schmitt + */ +class AnalyzeServiceReferencesPass implements RepeatablePassInterface +{ + private $graph; + private $container; + private $currentId; + private $currentDefinition; + private $onlyConstructorArguments; + + /** + * @param bool $onlyConstructorArguments Sets this Service Reference pass to ignore method calls + */ + public function __construct($onlyConstructorArguments = false) + { + $this->onlyConstructorArguments = (bool) $onlyConstructorArguments; + } + + /** + * {@inheritdoc} + */ + public function setRepeatedPass(RepeatedPass $repeatedPass) + { + // no-op for BC + } + + /** + * Processes a ContainerBuilder object to populate the service reference graph. + */ + public function process(ContainerBuilder $container) + { + $this->container = $container; + $this->graph = $container->getCompiler()->getServiceReferenceGraph(); + $this->graph->clear(); + + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isSynthetic() || $definition->isAbstract()) { + continue; + } + + $this->currentId = $id; + $this->currentDefinition = $definition; + + $this->processArguments($definition->getArguments()); + if ($definition->getFactoryService(false)) { + $this->processArguments(array(new Reference($definition->getFactoryService(false)))); + } + if (\is_array($definition->getFactory())) { + $this->processArguments($definition->getFactory()); + } + + if (!$this->onlyConstructorArguments) { + $this->processArguments($definition->getMethodCalls()); + $this->processArguments($definition->getProperties()); + if ($definition->getConfigurator()) { + $this->processArguments(array($definition->getConfigurator())); + } + } + } + + foreach ($container->getAliases() as $id => $alias) { + $this->graph->connect($id, $alias, (string) $alias, $this->getDefinition((string) $alias), null); + } + } + + /** + * Processes service definitions for arguments to find relationships for the service graph. + * + * @param array $arguments An array of Reference or Definition objects relating to service definitions + */ + private function processArguments(array $arguments) + { + foreach ($arguments as $argument) { + if (\is_array($argument)) { + $this->processArguments($argument); + } elseif ($argument instanceof Reference) { + $this->graph->connect( + $this->currentId, + $this->currentDefinition, + $this->getDefinitionId((string) $argument), + $this->getDefinition((string) $argument), + $argument + ); + } elseif ($argument instanceof Definition) { + $this->processArguments($argument->getArguments()); + $this->processArguments($argument->getMethodCalls()); + $this->processArguments($argument->getProperties()); + + if (\is_array($argument->getFactory())) { + $this->processArguments($argument->getFactory()); + } + if ($argument->getFactoryService(false)) { + $this->processArguments(array(new Reference($argument->getFactoryService(false)))); + } + } + } + } + + /** + * Returns a service definition given the full name or an alias. + * + * @param string $id A full id or alias for a service definition + * + * @return Definition|null The definition related to the supplied id + */ + private function getDefinition($id) + { + $id = $this->getDefinitionId($id); + + return null === $id ? null : $this->container->getDefinition($id); + } + + private function getDefinitionId($id) + { + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + + if (!$this->container->hasDefinition($id)) { + return; + } + + return $id; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AutoAliasServicePass.php b/vendor/symfony/dependency-injection/Compiler/AutoAliasServicePass.php new file mode 100644 index 0000000..c1f05e0 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AutoAliasServicePass.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Sets a service to be an alias of another one, given a format pattern. + */ +class AutoAliasServicePass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + foreach ($container->findTaggedServiceIds('auto_alias') as $serviceId => $tags) { + foreach ($tags as $tag) { + if (!isset($tag['format'])) { + throw new InvalidArgumentException(sprintf('Missing tag information "format" on auto_alias service "%s".', $serviceId)); + } + + $aliasId = $container->getParameterBag()->resolveValue($tag['format']); + if ($container->hasDefinition($aliasId) || $container->hasAlias($aliasId)) { + $container->setAlias($serviceId, new Alias($aliasId)); + } + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/AutowirePass.php b/vendor/symfony/dependency-injection/Compiler/AutowirePass.php new file mode 100644 index 0000000..b279548 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/AutowirePass.php @@ -0,0 +1,334 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Guesses constructor arguments of services definitions and try to instantiate services if necessary. + * + * @author Kévin Dunglas + */ +class AutowirePass implements CompilerPassInterface +{ + private $container; + private $reflectionClasses = array(); + private $definedTypes = array(); + private $types; + private $notGuessableTypes = array(); + private $autowired = array(); + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); }; + spl_autoload_register($throwingAutoloader); + + try { + $this->container = $container; + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isAutowired()) { + $this->completeDefinition($id, $definition); + } + } + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + + spl_autoload_unregister($throwingAutoloader); + + // Free memory and remove circular reference to container + $this->container = null; + $this->reflectionClasses = array(); + $this->definedTypes = array(); + $this->types = null; + $this->notGuessableTypes = array(); + $this->autowired = array(); + + if (isset($e)) { + throw $e; + } + } + + /** + * Wires the given definition. + * + * @param string $id + * @param Definition $definition + * + * @throws RuntimeException + */ + private function completeDefinition($id, Definition $definition) + { + if ($definition->getFactory() || $definition->getFactoryClass(false) || $definition->getFactoryService(false)) { + throw new RuntimeException(sprintf('Service "%s" can use either autowiring or a factory, not both.', $id)); + } + + if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { + return; + } + + $this->container->addClassResource($reflectionClass); + + if (!$constructor = $reflectionClass->getConstructor()) { + return; + } + $parameters = $constructor->getParameters(); + if (method_exists('ReflectionMethod', 'isVariadic') && $constructor->isVariadic()) { + array_pop($parameters); + } + + $arguments = $definition->getArguments(); + foreach ($parameters as $index => $parameter) { + if (array_key_exists($index, $arguments) && '' !== $arguments[$index]) { + continue; + } + + try { + if (!$typeHint = $parameter->getClass()) { + if (isset($arguments[$index])) { + continue; + } + + // no default value? Then fail + if (!$parameter->isOptional()) { + throw new RuntimeException(sprintf('Unable to autowire argument index %d ($%s) for the service "%s". If this is an object, give it a type-hint. Otherwise, specify this argument\'s value explicitly.', $index, $parameter->name, $id)); + } + + // specifically pass the default value + $arguments[$index] = $parameter->getDefaultValue(); + + continue; + } + + if (isset($this->autowired[$typeHint->name])) { + $arguments[$index] = $this->autowired[$typeHint->name] ? new Reference($this->autowired[$typeHint->name]) : null; + continue; + } + + if (null === $this->types) { + $this->populateAvailableTypes(); + } + + if (isset($this->types[$typeHint->name]) && !isset($this->notGuessableTypes[$typeHint->name])) { + $value = new Reference($this->types[$typeHint->name]); + } else { + try { + $value = $this->createAutowiredDefinition($typeHint, $id); + } catch (RuntimeException $e) { + if ($parameter->isDefaultValueAvailable()) { + $value = $parameter->getDefaultValue(); + } elseif ($parameter->allowsNull()) { + $value = null; + } else { + throw $e; + } + $this->autowired[$typeHint->name] = false; + } + } + } catch (\ReflectionException $e) { + // Typehint against a non-existing class + + if (!$parameter->isDefaultValueAvailable()) { + throw new RuntimeException(sprintf('Cannot autowire argument %s for %s because the type-hinted class does not exist (%s).', $index + 1, $definition->getClass(), $e->getMessage()), 0, $e); + } + + $value = $parameter->getDefaultValue(); + } + + $arguments[$index] = $value; + } + + if ($parameters && !isset($arguments[++$index])) { + while (0 <= --$index) { + $parameter = $parameters[$index]; + if (!$parameter->isDefaultValueAvailable() || $parameter->getDefaultValue() !== $arguments[$index]) { + break; + } + unset($arguments[$index]); + } + } + + // it's possible index 1 was set, then index 0, then 2, etc + // make sure that we re-order so they're injected as expected + ksort($arguments); + $definition->setArguments($arguments); + } + + /** + * Populates the list of available types. + */ + private function populateAvailableTypes() + { + $this->types = array(); + + foreach ($this->container->getDefinitions() as $id => $definition) { + $this->populateAvailableType($id, $definition); + } + } + + /** + * Populates the list of available types for a given definition. + * + * @param string $id + * @param Definition $definition + */ + private function populateAvailableType($id, Definition $definition) + { + // Never use abstract services + if ($definition->isAbstract()) { + return; + } + + foreach ($definition->getAutowiringTypes() as $type) { + $this->definedTypes[$type] = true; + $this->types[$type] = $id; + unset($this->notGuessableTypes[$type]); + } + + if (!$reflectionClass = $this->getReflectionClass($id, $definition)) { + return; + } + + foreach ($reflectionClass->getInterfaces() as $reflectionInterface) { + $this->set($reflectionInterface->name, $id); + } + + do { + $this->set($reflectionClass->name, $id); + } while ($reflectionClass = $reflectionClass->getParentClass()); + } + + /** + * Associates a type and a service id if applicable. + * + * @param string $type + * @param string $id + */ + private function set($type, $id) + { + if (isset($this->definedTypes[$type])) { + return; + } + + if (!isset($this->types[$type])) { + $this->types[$type] = $id; + + return; + } + + if ($this->types[$type] === $id) { + return; + } + + if (!isset($this->notGuessableTypes[$type])) { + $this->notGuessableTypes[$type] = true; + $this->types[$type] = (array) $this->types[$type]; + } + + $this->types[$type][] = $id; + } + + /** + * Registers a definition for the type if possible or throws an exception. + * + * @param \ReflectionClass $typeHint + * @param string $id + * + * @return Reference A reference to the registered definition + * + * @throws RuntimeException + */ + private function createAutowiredDefinition(\ReflectionClass $typeHint, $id) + { + if (isset($this->notGuessableTypes[$typeHint->name])) { + $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; + $matchingServices = implode(', ', $this->types[$typeHint->name]); + + throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". Multiple services exist for this %s (%s).', $typeHint->name, $id, $classOrInterface, $matchingServices)); + } + + if (!$typeHint->isInstantiable()) { + $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; + throw new RuntimeException(sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface)); + } + + $this->autowired[$typeHint->name] = $argumentId = sprintf('autowired.%s', $typeHint->name); + + $argumentDefinition = $this->container->register($argumentId, $typeHint->name); + $argumentDefinition->setPublic(false); + + try { + $this->completeDefinition($argumentId, $argumentDefinition); + } catch (RuntimeException $e) { + $classOrInterface = $typeHint->isInterface() ? 'interface' : 'class'; + $message = sprintf('Unable to autowire argument of type "%s" for the service "%s". No services were found matching this %s and it cannot be auto-registered.', $typeHint->name, $id, $classOrInterface); + throw new RuntimeException($message, 0, $e); + } + + return new Reference($argumentId); + } + + /** + * Retrieves the reflection class associated with the given service. + * + * @param string $id + * @param Definition $definition + * + * @return \ReflectionClass|false + */ + private function getReflectionClass($id, Definition $definition) + { + if (isset($this->reflectionClasses[$id])) { + return $this->reflectionClasses[$id]; + } + + // Cannot use reflection if the class isn't set + if (!$class = $definition->getClass()) { + return false; + } + + $class = $this->container->getParameterBag()->resolveValue($class); + + if ($deprecated = $definition->isDeprecated()) { + $prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) { + return (E_USER_DEPRECATED === $level || !$prevErrorHandler) ? false : $prevErrorHandler($level, $message, $file, $line); + }); + } + + $e = null; + + try { + $reflector = new \ReflectionClass($class); + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + + if ($deprecated) { + restore_error_handler(); + } + + if (null !== $e) { + if (!$e instanceof \ReflectionException) { + throw $e; + } + $reflector = false; + } + + return $this->reflectionClasses[$id] = $reflector; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php b/vendor/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php new file mode 100644 index 0000000..6bde194 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckCircularReferencesPass.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; + +/** + * Checks your services for circular references. + * + * References from method calls are ignored since we might be able to resolve + * these references depending on the order in which services are called. + * + * Circular reference from method calls will only be detected at run-time. + * + * @author Johannes M. Schmitt + */ +class CheckCircularReferencesPass implements CompilerPassInterface +{ + private $currentPath; + private $checkedNodes; + + /** + * Checks the ContainerBuilder object for circular references. + */ + public function process(ContainerBuilder $container) + { + $graph = $container->getCompiler()->getServiceReferenceGraph(); + + $this->checkedNodes = array(); + foreach ($graph->getNodes() as $id => $node) { + $this->currentPath = array($id); + + $this->checkOutEdges($node->getOutEdges()); + } + } + + /** + * Checks for circular references. + * + * @param ServiceReferenceGraphEdge[] $edges An array of Edges + * + * @throws ServiceCircularReferenceException when a circular reference is found + */ + private function checkOutEdges(array $edges) + { + foreach ($edges as $edge) { + $node = $edge->getDestNode(); + $id = $node->getId(); + + if (empty($this->checkedNodes[$id])) { + // don't check circular dependencies for lazy services + if (!$node->getValue() || !$node->getValue()->isLazy()) { + $searchKey = array_search($id, $this->currentPath); + $this->currentPath[] = $id; + + if (false !== $searchKey) { + throw new ServiceCircularReferenceException($id, \array_slice($this->currentPath, $searchKey)); + } + + $this->checkOutEdges($node->getOutEdges()); + } + + $this->checkedNodes[$id] = true; + array_pop($this->currentPath); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php b/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php new file mode 100644 index 0000000..ea1e089 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * This pass validates each definition individually only taking the information + * into account which is contained in the definition itself. + * + * Later passes can rely on the following, and specifically do not need to + * perform these checks themselves: + * + * - non synthetic, non abstract services always have a class set + * - synthetic services are always public + * - synthetic services are always of non-prototype scope + * - shared services are always of non-prototype scope + * + * @author Johannes M. Schmitt + */ +class CheckDefinitionValidityPass implements CompilerPassInterface +{ + /** + * Processes the ContainerBuilder to validate the Definition. + * + * @throws RuntimeException When the Definition is invalid + */ + public function process(ContainerBuilder $container) + { + foreach ($container->getDefinitions() as $id => $definition) { + // synthetic service is public + if ($definition->isSynthetic() && !$definition->isPublic()) { + throw new RuntimeException(sprintf('A synthetic service ("%s") must be public.', $id)); + } + + // synthetic service has non-prototype scope + if ($definition->isSynthetic() && ContainerInterface::SCOPE_PROTOTYPE === $definition->getScope(false)) { + throw new RuntimeException(sprintf('A synthetic service ("%s") cannot be of scope "prototype".', $id)); + } + + // shared service has non-prototype scope + if ($definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE === $definition->getScope(false)) { + throw new RuntimeException(sprintf('A shared service ("%s") cannot be of scope "prototype".', $id)); + } + + if ($definition->getFactory() && ($definition->getFactoryClass(false) || $definition->getFactoryService(false) || $definition->getFactoryMethod(false))) { + throw new RuntimeException(sprintf('A service ("%s") can use either the old or the new factory syntax, not both.', $id)); + } + + // non-synthetic, non-abstract service has class + if (!$definition->isAbstract() && !$definition->isSynthetic() && !$definition->getClass()) { + if ($definition->getFactory() || $definition->getFactoryClass(false) || $definition->getFactoryService(false)) { + throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id)); + } + + throw new RuntimeException(sprintf('The definition for "%s" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error.', $id)); + } + + // tag attribute values must be scalars + foreach ($definition->getTags() as $name => $tags) { + foreach ($tags as $attributes) { + foreach ($attributes as $attribute => $value) { + if (!is_scalar($value) && null !== $value) { + throw new RuntimeException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s".', $id, $name, $attribute)); + } + } + } + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php b/vendor/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php new file mode 100644 index 0000000..f17096f --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckExceptionOnInvalidReferenceBehaviorPass.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Checks that all references are pointing to a valid service. + * + * @author Johannes M. Schmitt + */ +class CheckExceptionOnInvalidReferenceBehaviorPass implements CompilerPassInterface +{ + private $container; + private $sourceId; + + public function process(ContainerBuilder $container) + { + $this->container = $container; + + foreach ($container->getDefinitions() as $id => $definition) { + $this->sourceId = $id; + $this->processDefinition($definition); + } + } + + private function processDefinition(Definition $definition) + { + $this->processReferences($definition->getArguments()); + $this->processReferences($definition->getMethodCalls()); + $this->processReferences($definition->getProperties()); + } + + private function processReferences(array $arguments) + { + foreach ($arguments as $argument) { + if (\is_array($argument)) { + $this->processReferences($argument); + } elseif ($argument instanceof Definition) { + $this->processDefinition($argument); + } elseif ($argument instanceof Reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $argument->getInvalidBehavior()) { + $destId = (string) $argument; + + if (!$this->container->has($destId)) { + throw new ServiceNotFoundException($destId, $this->sourceId); + } + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php b/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php new file mode 100644 index 0000000..2b380dd --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ScopeCrossingInjectionException; +use Symfony\Component\DependencyInjection\Exception\ScopeWideningInjectionException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Checks the validity of references. + * + * The following checks are performed by this pass: + * - target definitions are not abstract + * - target definitions are of equal or wider scope + * - target definitions are in the same scope hierarchy + * + * @author Johannes M. Schmitt + */ +class CheckReferenceValidityPass implements CompilerPassInterface +{ + private $container; + private $currentId; + private $currentScope; + private $currentScopeAncestors; + private $currentScopeChildren; + + /** + * Processes the ContainerBuilder to validate References. + */ + public function process(ContainerBuilder $container) + { + $this->container = $container; + + $children = $this->container->getScopeChildren(false); + $ancestors = array(); + + $scopes = $this->container->getScopes(false); + foreach ($scopes as $name => $parent) { + $ancestors[$name] = array($parent); + + while (isset($scopes[$parent])) { + $ancestors[$name][] = $parent = $scopes[$parent]; + } + } + + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isSynthetic() || $definition->isAbstract()) { + continue; + } + + $this->currentId = $id; + $this->currentScope = $scope = $definition->getScope(false); + + if (ContainerInterface::SCOPE_CONTAINER === $scope) { + $this->currentScopeChildren = array_keys($scopes); + $this->currentScopeAncestors = array(); + } elseif (ContainerInterface::SCOPE_PROTOTYPE !== $scope) { + $this->currentScopeChildren = isset($children[$scope]) ? $children[$scope] : array(); + $this->currentScopeAncestors = isset($ancestors[$scope]) ? $ancestors[$scope] : array(); + } + + $this->validateReferences($definition->getArguments()); + $this->validateReferences($definition->getMethodCalls()); + $this->validateReferences($definition->getProperties()); + } + } + + /** + * Validates an array of References. + * + * @param array $arguments An array of Reference objects + * + * @throws RuntimeException when there is a reference to an abstract definition + */ + private function validateReferences(array $arguments) + { + foreach ($arguments as $argument) { + if (\is_array($argument)) { + $this->validateReferences($argument); + } elseif ($argument instanceof Reference) { + $targetDefinition = $this->getDefinition((string) $argument); + + if (null !== $targetDefinition && $targetDefinition->isAbstract()) { + throw new RuntimeException(sprintf('The definition "%s" has a reference to an abstract definition "%s". Abstract definitions cannot be the target of references.', $this->currentId, $argument)); + } + + $this->validateScope($argument, $targetDefinition); + } + } + } + + /** + * Validates the scope of a single Reference. + * + * @throws ScopeWideningInjectionException when the definition references a service of a narrower scope + * @throws ScopeCrossingInjectionException when the definition references a service of another scope hierarchy + */ + private function validateScope(Reference $reference, Definition $definition = null) + { + if (ContainerInterface::SCOPE_PROTOTYPE === $this->currentScope) { + return; + } + + if (!$reference->isStrict(false)) { + return; + } + + if (null === $definition) { + return; + } + + if ($this->currentScope === $scope = $definition->getScope(false)) { + return; + } + + $id = (string) $reference; + + if (\in_array($scope, $this->currentScopeChildren, true)) { + throw new ScopeWideningInjectionException($this->currentId, $this->currentScope, $id, $scope); + } + + if (!\in_array($scope, $this->currentScopeAncestors, true)) { + throw new ScopeCrossingInjectionException($this->currentId, $this->currentScope, $id, $scope); + } + } + + /** + * Returns the Definition given an id. + * + * @param string $id Definition identifier + * + * @return Definition + */ + private function getDefinition($id) + { + if (!$this->container->hasDefinition($id)) { + return; + } + + return $this->container->getDefinition($id); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/Compiler.php b/vendor/symfony/dependency-injection/Compiler/Compiler.php new file mode 100644 index 0000000..dd9539e --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/Compiler.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This class is used to remove circular dependencies between individual passes. + * + * @author Johannes M. Schmitt + */ +class Compiler +{ + private $passConfig; + private $log = array(); + private $loggingFormatter; + private $serviceReferenceGraph; + + public function __construct() + { + $this->passConfig = new PassConfig(); + $this->serviceReferenceGraph = new ServiceReferenceGraph(); + $this->loggingFormatter = new LoggingFormatter(); + } + + /** + * Returns the PassConfig. + * + * @return PassConfig The PassConfig instance + */ + public function getPassConfig() + { + return $this->passConfig; + } + + /** + * Returns the ServiceReferenceGraph. + * + * @return ServiceReferenceGraph The ServiceReferenceGraph instance + */ + public function getServiceReferenceGraph() + { + return $this->serviceReferenceGraph; + } + + /** + * Returns the logging formatter which can be used by compilation passes. + * + * @return LoggingFormatter + */ + public function getLoggingFormatter() + { + return $this->loggingFormatter; + } + + /** + * Adds a pass to the PassConfig. + * + * @param CompilerPassInterface $pass A compiler pass + * @param string $type The type of the pass + */ + public function addPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION) + { + $this->passConfig->addPass($pass, $type); + } + + /** + * Adds a log message. + * + * @param string $string The log message + */ + public function addLogMessage($string) + { + $this->log[] = $string; + } + + /** + * Returns the log. + * + * @return array Log array + */ + public function getLog() + { + return $this->log; + } + + /** + * Run the Compiler and process all Passes. + */ + public function compile(ContainerBuilder $container) + { + foreach ($this->passConfig->getPasses() as $pass) { + $pass->process($container); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/CompilerPassInterface.php b/vendor/symfony/dependency-injection/Compiler/CompilerPassInterface.php new file mode 100644 index 0000000..3085006 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/CompilerPassInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Interface that must be implemented by compilation passes. + * + * @author Johannes M. Schmitt + */ +interface CompilerPassInterface +{ + /** + * You can modify the container here before it is dumped to PHP code. + */ + public function process(ContainerBuilder $container); +} diff --git a/vendor/symfony/dependency-injection/Compiler/DecoratorServicePass.php b/vendor/symfony/dependency-injection/Compiler/DecoratorServicePass.php new file mode 100644 index 0000000..80eb67a --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/DecoratorServicePass.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Overwrites a service but keeps the overridden one. + * + * @author Christophe Coevoet + * @author Fabien Potencier + * @author Diego Saint Esteben + */ +class DecoratorServicePass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container) + { + $definitions = new \SplPriorityQueue(); + $order = PHP_INT_MAX; + + foreach ($container->getDefinitions() as $id => $definition) { + if (!$decorated = $definition->getDecoratedService()) { + continue; + } + $definitions->insert(array($id, $definition), array($decorated[2], --$order)); + } + + foreach ($definitions as $arr) { + list($id, $definition) = $arr; + list($inner, $renamedId) = $definition->getDecoratedService(); + + $definition->setDecoratedService(null); + + if (!$renamedId) { + $renamedId = $id.'.inner'; + } + + // we create a new alias/service for the service we are replacing + // to be able to reference it in the new one + if ($container->hasAlias($inner)) { + $alias = $container->getAlias($inner); + $public = $alias->isPublic(); + $container->setAlias($renamedId, new Alias((string) $alias, false)); + } else { + $decoratedDefinition = $container->getDefinition($inner); + $definition->setTags(array_merge($decoratedDefinition->getTags(), $definition->getTags())); + $definition->setAutowiringTypes(array_merge($decoratedDefinition->getAutowiringTypes(), $definition->getAutowiringTypes())); + $public = $decoratedDefinition->isPublic(); + $decoratedDefinition->setPublic(false); + $decoratedDefinition->setTags(array()); + $decoratedDefinition->setAutowiringTypes(array()); + $container->setDefinition($renamedId, $decoratedDefinition); + } + + $container->setAlias($inner, new Alias($id, $public)); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php b/vendor/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php new file mode 100644 index 0000000..27e5048 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ExtensionCompilerPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * A pass to automatically process extensions if they implement + * CompilerPassInterface. + * + * @author Wouter J + */ +class ExtensionCompilerPass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + foreach ($container->getExtensions() as $extension) { + if (!$extension instanceof CompilerPassInterface) { + continue; + } + + $extension->process($container); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php b/vendor/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php new file mode 100644 index 0000000..b57f488 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/InlineServiceDefinitionsPass.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Inline service definitions where this is possible. + * + * @author Johannes M. Schmitt + */ +class InlineServiceDefinitionsPass implements RepeatablePassInterface +{ + private $graph; + private $compiler; + private $formatter; + private $currentId; + + /** + * {@inheritdoc} + */ + public function setRepeatedPass(RepeatedPass $repeatedPass) + { + // no-op for BC + } + + /** + * Processes the ContainerBuilder for inline service definitions. + */ + public function process(ContainerBuilder $container) + { + $this->compiler = $container->getCompiler(); + $this->formatter = $this->compiler->getLoggingFormatter(); + $this->graph = $this->compiler->getServiceReferenceGraph(); + + $container->setDefinitions($this->inlineArguments($container, $container->getDefinitions(), true)); + } + + /** + * Processes inline arguments. + * + * @param ContainerBuilder $container The ContainerBuilder + * @param array $arguments An array of arguments + * @param bool $isRoot If we are processing the root definitions or not + * + * @return array + */ + private function inlineArguments(ContainerBuilder $container, array $arguments, $isRoot = false) + { + foreach ($arguments as $k => $argument) { + if ($isRoot) { + $this->currentId = $k; + } + if (\is_array($argument)) { + $arguments[$k] = $this->inlineArguments($container, $argument); + } elseif ($argument instanceof Reference) { + if (!$container->hasDefinition($id = (string) $argument)) { + continue; + } + + if ($this->isInlineableDefinition($container, $id, $definition = $container->getDefinition($id))) { + $this->compiler->addLogMessage($this->formatter->formatInlineService($this, $id, $this->currentId)); + + if ($definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE !== $definition->getScope(false)) { + $arguments[$k] = $definition; + } else { + $arguments[$k] = clone $definition; + } + } + } elseif ($argument instanceof Definition) { + $argument->setArguments($this->inlineArguments($container, $argument->getArguments())); + $argument->setMethodCalls($this->inlineArguments($container, $argument->getMethodCalls())); + $argument->setProperties($this->inlineArguments($container, $argument->getProperties())); + + $configurator = $this->inlineArguments($container, array($argument->getConfigurator())); + $argument->setConfigurator($configurator[0]); + + $factory = $this->inlineArguments($container, array($argument->getFactory())); + $argument->setFactory($factory[0]); + } + } + + return $arguments; + } + + /** + * Checks if the definition is inlineable. + * + * @param ContainerBuilder $container + * @param string $id + * @param Definition $definition + * + * @return bool If the definition is inlineable + */ + private function isInlineableDefinition(ContainerBuilder $container, $id, Definition $definition) + { + if ($definition->isDeprecated() || $definition->isLazy() || $definition->isSynthetic()) { + return false; + } + + if (!$definition->isShared() || ContainerInterface::SCOPE_PROTOTYPE === $definition->getScope(false)) { + return true; + } + + if ($definition->isPublic()) { + return false; + } + + if (!$this->graph->hasNode($id)) { + return true; + } + + if ($this->currentId == $id) { + return false; + } + + $ids = array(); + foreach ($this->graph->getNode($id)->getInEdges() as $edge) { + $ids[] = $edge->getSourceNode()->getId(); + } + + if (\count(array_unique($ids)) > 1) { + return false; + } + + if (\count($ids) > 1 && \is_array($factory = $definition->getFactory()) && ($factory[0] instanceof Reference || $factory[0] instanceof Definition)) { + return false; + } + + if (\count($ids) > 1 && $definition->getFactoryService(false)) { + return false; + } + + return $container->getDefinition(reset($ids))->getScope(false) === $definition->getScope(false); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/LoggingFormatter.php b/vendor/symfony/dependency-injection/Compiler/LoggingFormatter.php new file mode 100644 index 0000000..64ffc9e --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/LoggingFormatter.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +/** + * Used to format logging messages during the compilation. + * + * @author Johannes M. Schmitt + */ +class LoggingFormatter +{ + public function formatRemoveService(CompilerPassInterface $pass, $id, $reason) + { + return $this->format($pass, sprintf('Removed service "%s"; reason: %s.', $id, $reason)); + } + + public function formatInlineService(CompilerPassInterface $pass, $id, $target) + { + return $this->format($pass, sprintf('Inlined service "%s" to "%s".', $id, $target)); + } + + public function formatUpdateReference(CompilerPassInterface $pass, $serviceId, $oldDestId, $newDestId) + { + return $this->format($pass, sprintf('Changed reference of service "%s" previously pointing to "%s" to "%s".', $serviceId, $oldDestId, $newDestId)); + } + + public function formatResolveInheritance(CompilerPassInterface $pass, $childId, $parentId) + { + return $this->format($pass, sprintf('Resolving inheritance for "%s" (parent: %s).', $childId, $parentId)); + } + + public function format(CompilerPassInterface $pass, $message) + { + return sprintf('%s: %s', \get_class($pass), $message); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php b/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php new file mode 100644 index 0000000..9434ac7 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/MergeExtensionConfigurationPass.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ConfigurationExtensionInterface; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; + +/** + * Merges extension configs into the container builder. + * + * @author Fabien Potencier + */ +class MergeExtensionConfigurationPass implements CompilerPassInterface +{ + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $parameters = $container->getParameterBag()->all(); + $definitions = $container->getDefinitions(); + $aliases = $container->getAliases(); + $exprLangProviders = $container->getExpressionLanguageProviders(); + + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof PrependExtensionInterface) { + $extension->prepend($container); + } + } + + foreach ($container->getExtensions() as $name => $extension) { + if (!$config = $container->getExtensionConfig($name)) { + // this extension was not called + continue; + } + $config = $container->getParameterBag()->resolveValue($config); + + $tmpContainer = new ContainerBuilder($container->getParameterBag()); + $tmpContainer->setResourceTracking($container->isTrackingResources()); + $tmpContainer->addObjectResource($extension); + if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) { + $tmpContainer->addObjectResource($configuration); + } + + foreach ($exprLangProviders as $provider) { + $tmpContainer->addExpressionLanguageProvider($provider); + } + + $extension->load($config, $tmpContainer); + + $container->merge($tmpContainer); + $container->getParameterBag()->add($parameters); + } + + $container->addDefinitions($definitions); + $container->addAliases($aliases); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/PassConfig.php b/vendor/symfony/dependency-injection/Compiler/PassConfig.php new file mode 100644 index 0000000..8eabf35 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/PassConfig.php @@ -0,0 +1,219 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Compiler Pass Configuration. + * + * This class has a default configuration embedded. + * + * @author Johannes M. Schmitt + */ +class PassConfig +{ + const TYPE_AFTER_REMOVING = 'afterRemoving'; + const TYPE_BEFORE_OPTIMIZATION = 'beforeOptimization'; + const TYPE_BEFORE_REMOVING = 'beforeRemoving'; + const TYPE_OPTIMIZE = 'optimization'; + const TYPE_REMOVE = 'removing'; + + private $mergePass; + private $afterRemovingPasses = array(); + private $beforeOptimizationPasses = array(); + private $beforeRemovingPasses = array(); + private $optimizationPasses; + private $removingPasses; + + public function __construct() + { + $this->mergePass = new MergeExtensionConfigurationPass(); + + $this->optimizationPasses = array( + new ExtensionCompilerPass(), + new ResolveDefinitionTemplatesPass(), + new DecoratorServicePass(), + new ResolveParameterPlaceHoldersPass(), + new CheckDefinitionValidityPass(), + new ResolveReferencesToAliasesPass(), + new ResolveInvalidReferencesPass(), + new AutowirePass(), + new AnalyzeServiceReferencesPass(true), + new CheckCircularReferencesPass(), + new CheckReferenceValidityPass(), + ); + + $this->removingPasses = array( + new RemovePrivateAliasesPass(), + new ReplaceAliasByActualDefinitionPass(), + new RemoveAbstractDefinitionsPass(), + new RepeatedPass(array( + new AnalyzeServiceReferencesPass(), + new InlineServiceDefinitionsPass(), + new AnalyzeServiceReferencesPass(), + new RemoveUnusedDefinitionsPass(), + )), + new CheckExceptionOnInvalidReferenceBehaviorPass(), + ); + } + + /** + * Returns all passes in order to be processed. + * + * @return array An array of all passes to process + */ + public function getPasses() + { + return array_merge( + array($this->mergePass), + $this->beforeOptimizationPasses, + $this->optimizationPasses, + $this->beforeRemovingPasses, + $this->removingPasses, + $this->afterRemovingPasses + ); + } + + /** + * Adds a pass. + * + * @param CompilerPassInterface $pass A Compiler pass + * @param string $type The pass type + * + * @throws InvalidArgumentException when a pass type doesn't exist + */ + public function addPass(CompilerPassInterface $pass, $type = self::TYPE_BEFORE_OPTIMIZATION) + { + $property = $type.'Passes'; + if (!isset($this->$property)) { + throw new InvalidArgumentException(sprintf('Invalid type "%s".', $type)); + } + + $this->{$property}[] = $pass; + } + + /** + * Gets all passes for the AfterRemoving pass. + * + * @return array An array of passes + */ + public function getAfterRemovingPasses() + { + return $this->afterRemovingPasses; + } + + /** + * Gets all passes for the BeforeOptimization pass. + * + * @return array An array of passes + */ + public function getBeforeOptimizationPasses() + { + return $this->beforeOptimizationPasses; + } + + /** + * Gets all passes for the BeforeRemoving pass. + * + * @return array An array of passes + */ + public function getBeforeRemovingPasses() + { + return $this->beforeRemovingPasses; + } + + /** + * Gets all passes for the Optimization pass. + * + * @return array An array of passes + */ + public function getOptimizationPasses() + { + return $this->optimizationPasses; + } + + /** + * Gets all passes for the Removing pass. + * + * @return array An array of passes + */ + public function getRemovingPasses() + { + return $this->removingPasses; + } + + /** + * Gets the Merge pass. + * + * @return CompilerPassInterface The merge pass + */ + public function getMergePass() + { + return $this->mergePass; + } + + public function setMergePass(CompilerPassInterface $pass) + { + $this->mergePass = $pass; + } + + /** + * Sets the AfterRemoving passes. + * + * @param array $passes An array of passes + */ + public function setAfterRemovingPasses(array $passes) + { + $this->afterRemovingPasses = $passes; + } + + /** + * Sets the BeforeOptimization passes. + * + * @param array $passes An array of passes + */ + public function setBeforeOptimizationPasses(array $passes) + { + $this->beforeOptimizationPasses = $passes; + } + + /** + * Sets the BeforeRemoving passes. + * + * @param array $passes An array of passes + */ + public function setBeforeRemovingPasses(array $passes) + { + $this->beforeRemovingPasses = $passes; + } + + /** + * Sets the Optimization passes. + * + * @param array $passes An array of passes + */ + public function setOptimizationPasses(array $passes) + { + $this->optimizationPasses = $passes; + } + + /** + * Sets the Removing passes. + * + * @param array $passes An array of passes + */ + public function setRemovingPasses(array $passes) + { + $this->removingPasses = $passes; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php b/vendor/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php new file mode 100644 index 0000000..9999214 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RemoveAbstractDefinitionsPass.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Removes abstract Definitions. + */ +class RemoveAbstractDefinitionsPass implements CompilerPassInterface +{ + /** + * Removes abstract definitions from the ContainerBuilder. + */ + public function process(ContainerBuilder $container) + { + $compiler = $container->getCompiler(); + $formatter = $compiler->getLoggingFormatter(); + + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isAbstract()) { + $container->removeDefinition($id); + $compiler->addLogMessage($formatter->formatRemoveService($this, $id, 'abstract')); + } + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php b/vendor/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php new file mode 100644 index 0000000..36abc61 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RemovePrivateAliasesPass.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Remove private aliases from the container. They were only used to establish + * dependencies between services, and these dependencies have been resolved in + * one of the previous passes. + * + * @author Johannes M. Schmitt + */ +class RemovePrivateAliasesPass implements CompilerPassInterface +{ + /** + * Removes private aliases from the ContainerBuilder. + */ + public function process(ContainerBuilder $container) + { + $compiler = $container->getCompiler(); + $formatter = $compiler->getLoggingFormatter(); + + foreach ($container->getAliases() as $id => $alias) { + if ($alias->isPublic()) { + continue; + } + + $container->removeAlias($id); + $compiler->addLogMessage($formatter->formatRemoveService($this, $id, 'private alias')); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php b/vendor/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php new file mode 100644 index 0000000..911875c --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RemoveUnusedDefinitionsPass.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Removes unused service definitions from the container. + * + * @author Johannes M. Schmitt + */ +class RemoveUnusedDefinitionsPass implements RepeatablePassInterface +{ + private $repeatedPass; + + /** + * {@inheritdoc} + */ + public function setRepeatedPass(RepeatedPass $repeatedPass) + { + $this->repeatedPass = $repeatedPass; + } + + /** + * Processes the ContainerBuilder to remove unused definitions. + */ + public function process(ContainerBuilder $container) + { + $compiler = $container->getCompiler(); + $formatter = $compiler->getLoggingFormatter(); + $graph = $compiler->getServiceReferenceGraph(); + + $hasChanged = false; + foreach ($container->getDefinitions() as $id => $definition) { + if ($definition->isPublic()) { + continue; + } + + if ($graph->hasNode($id)) { + $edges = $graph->getNode($id)->getInEdges(); + $referencingAliases = array(); + $sourceIds = array(); + foreach ($edges as $edge) { + $node = $edge->getSourceNode(); + $sourceIds[] = $node->getId(); + + if ($node->isAlias()) { + $referencingAliases[] = $node->getValue(); + } + } + $isReferenced = (\count(array_unique($sourceIds)) - \count($referencingAliases)) > 0; + } else { + $referencingAliases = array(); + $isReferenced = false; + } + + if (1 === \count($referencingAliases) && false === $isReferenced) { + $container->setDefinition((string) reset($referencingAliases), $definition); + $definition->setPublic(true); + $container->removeDefinition($id); + $compiler->addLogMessage($formatter->formatRemoveService($this, $id, 'replaces alias '.reset($referencingAliases))); + } elseif (0 === \count($referencingAliases) && false === $isReferenced) { + $container->removeDefinition($id); + $compiler->addLogMessage($formatter->formatRemoveService($this, $id, 'unused')); + $hasChanged = true; + } + } + + if ($hasChanged) { + $this->repeatedPass->setRepeat(); + } + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/RepeatablePassInterface.php b/vendor/symfony/dependency-injection/Compiler/RepeatablePassInterface.php new file mode 100644 index 0000000..2b88bfb --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RepeatablePassInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +/** + * Interface that must be implemented by passes that are run as part of an + * RepeatedPass. + * + * @author Johannes M. Schmitt + */ +interface RepeatablePassInterface extends CompilerPassInterface +{ + public function setRepeatedPass(RepeatedPass $repeatedPass); +} diff --git a/vendor/symfony/dependency-injection/Compiler/RepeatedPass.php b/vendor/symfony/dependency-injection/Compiler/RepeatedPass.php new file mode 100644 index 0000000..3da1a0d --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/RepeatedPass.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * A pass that might be run repeatedly. + * + * @author Johannes M. Schmitt + */ +class RepeatedPass implements CompilerPassInterface +{ + /** + * @var bool + */ + private $repeat = false; + + private $passes; + + /** + * @param RepeatablePassInterface[] $passes An array of RepeatablePassInterface objects + * + * @throws InvalidArgumentException when the passes don't implement RepeatablePassInterface + */ + public function __construct(array $passes) + { + foreach ($passes as $pass) { + if (!$pass instanceof RepeatablePassInterface) { + throw new InvalidArgumentException('$passes must be an array of RepeatablePassInterface.'); + } + + $pass->setRepeatedPass($this); + } + + $this->passes = $passes; + } + + /** + * Process the repeatable passes that run more than once. + */ + public function process(ContainerBuilder $container) + { + do { + $this->repeat = false; + foreach ($this->passes as $pass) { + $pass->process($container); + } + } while ($this->repeat); + } + + /** + * Sets if the pass should repeat. + */ + public function setRepeat() + { + $this->repeat = true; + } + + /** + * Returns the passes. + * + * @return RepeatablePassInterface[] An array of RepeatablePassInterface objects + */ + public function getPasses() + { + return $this->passes; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php b/vendor/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php new file mode 100644 index 0000000..cf48c53 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ReplaceAliasByActualDefinitionPass.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Replaces aliases with actual service definitions, effectively removing these + * aliases. + * + * @author Johannes M. Schmitt + */ +class ReplaceAliasByActualDefinitionPass implements CompilerPassInterface +{ + private $compiler; + private $formatter; + + /** + * Process the Container to replace aliases with service definitions. + * + * @throws InvalidArgumentException if the service definition does not exist + */ + public function process(ContainerBuilder $container) + { + // Setup + $this->compiler = $container->getCompiler(); + $this->formatter = $this->compiler->getLoggingFormatter(); + // First collect all alias targets that need to be replaced + $seenAliasTargets = array(); + $replacements = array(); + foreach ($container->getAliases() as $definitionId => $target) { + $targetId = (string) $target; + // Special case: leave this target alone + if ('service_container' === $targetId) { + continue; + } + // Check if target needs to be replaces + if (isset($replacements[$targetId])) { + $container->setAlias($definitionId, $replacements[$targetId]); + } + // No need to process the same target twice + if (isset($seenAliasTargets[$targetId])) { + continue; + } + // Process new target + $seenAliasTargets[$targetId] = true; + try { + $definition = $container->getDefinition($targetId); + } catch (InvalidArgumentException $e) { + throw new InvalidArgumentException(sprintf('Unable to replace alias "%s" with actual definition "%s".', $definitionId, $targetId), null, $e); + } + if ($definition->isPublic()) { + continue; + } + // Remove private definition and schedule for replacement + $definition->setPublic(true); + $container->setDefinition($definitionId, $definition); + $container->removeDefinition($targetId); + $replacements[$targetId] = $definitionId; + } + + // Now replace target instances in all definitions + foreach ($container->getDefinitions() as $definitionId => $definition) { + $definition->setArguments($this->updateArgumentReferences($replacements, $definitionId, $definition->getArguments())); + $definition->setMethodCalls($this->updateArgumentReferences($replacements, $definitionId, $definition->getMethodCalls())); + $definition->setProperties($this->updateArgumentReferences($replacements, $definitionId, $definition->getProperties())); + $definition->setFactoryService($this->updateFactoryReferenceId($replacements, $definition->getFactoryService(false)), false); + $definition->setFactory($this->updateFactoryReference($replacements, $definition->getFactory())); + } + } + + /** + * Recursively updates references in an array. + * + * @param array $replacements Table of aliases to replace + * @param string $definitionId Identifier of this definition + * @param array $arguments Where to replace the aliases + * + * @return array + */ + private function updateArgumentReferences(array $replacements, $definitionId, array $arguments) + { + foreach ($arguments as $k => $argument) { + // Handle recursion step + if (\is_array($argument)) { + $arguments[$k] = $this->updateArgumentReferences($replacements, $definitionId, $argument); + continue; + } + // Skip arguments that don't need replacement + if (!$argument instanceof Reference) { + continue; + } + $referenceId = (string) $argument; + if (!isset($replacements[$referenceId])) { + continue; + } + // Perform the replacement + $newId = $replacements[$referenceId]; + $arguments[$k] = new Reference($newId, $argument->getInvalidBehavior()); + $this->compiler->addLogMessage($this->formatter->formatUpdateReference($this, $definitionId, $referenceId, $newId)); + } + + return $arguments; + } + + /** + * Returns the updated reference for the factory service. + * + * @param array $replacements Table of aliases to replace + * @param string|null $referenceId Factory service reference identifier + * + * @return string|null + */ + private function updateFactoryReferenceId(array $replacements, $referenceId) + { + if (null === $referenceId) { + return; + } + + return isset($replacements[$referenceId]) ? $replacements[$referenceId] : $referenceId; + } + + private function updateFactoryReference(array $replacements, $factory) + { + if (\is_array($factory) && $factory[0] instanceof Reference && isset($replacements[$referenceId = (string) $factory[0]])) { + $factory[0] = new Reference($replacements[$referenceId], $factory[0]->getInvalidBehavior()); + } + + return $factory; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php new file mode 100644 index 0000000..db1864e --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveDefinitionTemplatesPass.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * This replaces all DefinitionDecorator instances with their equivalent fully + * merged Definition instance. + * + * @author Johannes M. Schmitt + * @author Nicolas Grekas + */ +class ResolveDefinitionTemplatesPass implements CompilerPassInterface +{ + private $compiler; + private $formatter; + private $currentId; + + /** + * Process the ContainerBuilder to replace DefinitionDecorator instances with their real Definition instances. + */ + public function process(ContainerBuilder $container) + { + $this->compiler = $container->getCompiler(); + $this->formatter = $this->compiler->getLoggingFormatter(); + + $container->setDefinitions($this->resolveArguments($container, $container->getDefinitions(), true)); + } + + /** + * Resolves definition decorator arguments. + * + * @param ContainerBuilder $container The ContainerBuilder + * @param array $arguments An array of arguments + * @param bool $isRoot If we are processing the root definitions or not + * + * @return array + */ + private function resolveArguments(ContainerBuilder $container, array $arguments, $isRoot = false) + { + foreach ($arguments as $k => $argument) { + if ($isRoot) { + // yes, we are specifically fetching the definition from the + // container to ensure we are not operating on stale data + $arguments[$k] = $argument = $container->getDefinition($k); + $this->currentId = $k; + } + if (\is_array($argument)) { + $arguments[$k] = $this->resolveArguments($container, $argument); + } elseif ($argument instanceof Definition) { + if ($argument instanceof DefinitionDecorator) { + $arguments[$k] = $argument = $this->resolveDefinition($container, $argument); + if ($isRoot) { + $container->setDefinition($k, $argument); + } + } + $argument->setArguments($this->resolveArguments($container, $argument->getArguments())); + $argument->setMethodCalls($this->resolveArguments($container, $argument->getMethodCalls())); + $argument->setProperties($this->resolveArguments($container, $argument->getProperties())); + + $configurator = $this->resolveArguments($container, array($argument->getConfigurator())); + $argument->setConfigurator($configurator[0]); + + $factory = $this->resolveArguments($container, array($argument->getFactory())); + $argument->setFactory($factory[0]); + } + } + + return $arguments; + } + + /** + * Resolves the definition. + * + * @param ContainerBuilder $container The ContainerBuilder + * @param DefinitionDecorator $definition + * + * @return Definition + * + * @throws \RuntimeException When the definition is invalid + */ + private function resolveDefinition(ContainerBuilder $container, DefinitionDecorator $definition) + { + if (!$container->hasDefinition($parent = $definition->getParent())) { + throw new RuntimeException(sprintf('The parent definition "%s" defined for definition "%s" does not exist.', $parent, $this->currentId)); + } + + $parentDef = $container->getDefinition($parent); + if ($parentDef instanceof DefinitionDecorator) { + $id = $this->currentId; + $this->currentId = $parent; + $parentDef = $this->resolveDefinition($container, $parentDef); + $container->setDefinition($parent, $parentDef); + $this->currentId = $id; + } + + $this->compiler->addLogMessage($this->formatter->formatResolveInheritance($this, $this->currentId, $parent)); + $def = new Definition(); + + // merge in parent definition + // purposely ignored attributes: scope, abstract, tags + $def->setClass($parentDef->getClass()); + $def->setArguments($parentDef->getArguments()); + $def->setMethodCalls($parentDef->getMethodCalls()); + $def->setProperties($parentDef->getProperties()); + $def->setAutowiringTypes($parentDef->getAutowiringTypes()); + if ($parentDef->getFactoryClass(false)) { + $def->setFactoryClass($parentDef->getFactoryClass(false)); + } + if ($parentDef->getFactoryMethod(false)) { + $def->setFactoryMethod($parentDef->getFactoryMethod(false)); + } + if ($parentDef->getFactoryService(false)) { + $def->setFactoryService($parentDef->getFactoryService(false)); + } + if ($parentDef->isDeprecated()) { + $def->setDeprecated(true, $parentDef->getDeprecationMessage('%service_id%')); + } + $def->setFactory($parentDef->getFactory()); + $def->setConfigurator($parentDef->getConfigurator()); + $def->setFile($parentDef->getFile()); + $def->setPublic($parentDef->isPublic()); + $def->setLazy($parentDef->isLazy()); + $def->setAutowired($parentDef->isAutowired()); + + // overwrite with values specified in the decorator + $changes = $definition->getChanges(); + if (isset($changes['class'])) { + $def->setClass($definition->getClass()); + } + if (isset($changes['factory_class'])) { + $def->setFactoryClass($definition->getFactoryClass(false)); + } + if (isset($changes['factory_method'])) { + $def->setFactoryMethod($definition->getFactoryMethod(false)); + } + if (isset($changes['factory_service'])) { + $def->setFactoryService($definition->getFactoryService(false)); + } + if (isset($changes['factory'])) { + $def->setFactory($definition->getFactory()); + } + if (isset($changes['configurator'])) { + $def->setConfigurator($definition->getConfigurator()); + } + if (isset($changes['file'])) { + $def->setFile($definition->getFile()); + } + if (isset($changes['public'])) { + $def->setPublic($definition->isPublic()); + } + if (isset($changes['lazy'])) { + $def->setLazy($definition->isLazy()); + } + if (isset($changes['deprecated'])) { + $def->setDeprecated($definition->isDeprecated(), $definition->getDeprecationMessage('%service_id%')); + } + if (isset($changes['autowire'])) { + $def->setAutowired($definition->isAutowired()); + } + if (isset($changes['decorated_service'])) { + $decoratedService = $definition->getDecoratedService(); + if (null === $decoratedService) { + $def->setDecoratedService($decoratedService); + } else { + $def->setDecoratedService($decoratedService[0], $decoratedService[1], $decoratedService[2]); + } + } + + // merge arguments + foreach ($definition->getArguments() as $k => $v) { + if (is_numeric($k)) { + $def->addArgument($v); + continue; + } + + if (0 !== strpos($k, 'index_')) { + throw new RuntimeException(sprintf('Invalid argument key "%s" found.', $k)); + } + + $index = (int) substr($k, \strlen('index_')); + $def->replaceArgument($index, $v); + } + + // merge properties + foreach ($definition->getProperties() as $k => $v) { + $def->setProperty($k, $v); + } + + // append method calls + if (\count($calls = $definition->getMethodCalls()) > 0) { + $def->setMethodCalls(array_merge($def->getMethodCalls(), $calls)); + } + + // merge autowiring types + foreach ($definition->getAutowiringTypes() as $autowiringType) { + $def->addAutowiringType($autowiringType); + } + + // these attributes are always taken from the child + $def->setAbstract($definition->isAbstract()); + $def->setScope($definition->getScope(false), false); + $def->setShared($definition->isShared()); + $def->setTags($definition->getTags()); + + return $def; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php new file mode 100644 index 0000000..181c85f --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveInvalidReferencesPass.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Emulates the invalid behavior if the reference is not found within the + * container. + * + * @author Johannes M. Schmitt + */ +class ResolveInvalidReferencesPass implements CompilerPassInterface +{ + private $container; + + /** + * Process the ContainerBuilder to resolve invalid references. + */ + public function process(ContainerBuilder $container) + { + $this->container = $container; + foreach ($container->getDefinitions() as $definition) { + if ($definition->isSynthetic() || $definition->isAbstract()) { + continue; + } + + $definition->setArguments( + $this->processArguments($definition->getArguments()) + ); + + $calls = array(); + foreach ($definition->getMethodCalls() as $call) { + try { + $calls[] = array($call[0], $this->processArguments($call[1], true)); + } catch (RuntimeException $e) { + // this call is simply removed + } + } + $definition->setMethodCalls($calls); + + $properties = array(); + foreach ($definition->getProperties() as $name => $value) { + try { + $value = $this->processArguments(array($value), true); + $properties[$name] = reset($value); + } catch (RuntimeException $e) { + // ignore property + } + } + $definition->setProperties($properties); + } + } + + /** + * Processes arguments to determine invalid references. + * + * @param array $arguments An array of Reference objects + * @param bool $inMethodCall + * + * @return array + * + * @throws RuntimeException When the config is invalid + */ + private function processArguments(array $arguments, $inMethodCall = false) + { + foreach ($arguments as $k => $argument) { + if (\is_array($argument)) { + $arguments[$k] = $this->processArguments($argument, $inMethodCall); + } elseif ($argument instanceof Reference) { + $id = (string) $argument; + + $invalidBehavior = $argument->getInvalidBehavior(); + $exists = $this->container->has($id); + + // resolve invalid behavior + if (!$exists && ContainerInterface::NULL_ON_INVALID_REFERENCE === $invalidBehavior) { + $arguments[$k] = null; + } elseif (!$exists && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $invalidBehavior) { + if ($inMethodCall) { + throw new RuntimeException('Method shouldn\'t be called.'); + } + + $arguments[$k] = null; + } + } + } + + return $arguments; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php new file mode 100644 index 0000000..3a31dd5 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveParameterPlaceHoldersPass.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; + +/** + * Resolves all parameter placeholders "%somevalue%" to their real values. + * + * @author Johannes M. Schmitt + */ +class ResolveParameterPlaceHoldersPass implements CompilerPassInterface +{ + /** + * Processes the ContainerBuilder to resolve parameter placeholders. + * + * @throws ParameterNotFoundException + */ + public function process(ContainerBuilder $container) + { + $parameterBag = $container->getParameterBag(); + + foreach ($container->getDefinitions() as $id => $definition) { + try { + $definition->setClass($parameterBag->resolveValue($definition->getClass())); + $definition->setFile($parameterBag->resolveValue($definition->getFile())); + $definition->setArguments($parameterBag->resolveValue($definition->getArguments())); + if ($definition->getFactoryClass(false)) { + $definition->setFactoryClass($parameterBag->resolveValue($definition->getFactoryClass(false))); + } + + $factory = $definition->getFactory(); + + if (\is_array($factory) && isset($factory[0])) { + $factory[0] = $parameterBag->resolveValue($factory[0]); + $definition->setFactory($factory); + } + + $calls = array(); + foreach ($definition->getMethodCalls() as $name => $arguments) { + $calls[$parameterBag->resolveValue($name)] = $parameterBag->resolveValue($arguments); + } + $definition->setMethodCalls($calls); + + $definition->setProperties($parameterBag->resolveValue($definition->getProperties())); + } catch (ParameterNotFoundException $e) { + $e->setSourceId($id); + + throw $e; + } + } + + $aliases = array(); + foreach ($container->getAliases() as $name => $target) { + $aliases[$parameterBag->resolveValue($name)] = $target; + } + $container->setAliases($aliases); + + $parameterBag->resolve(); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php b/vendor/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php new file mode 100644 index 0000000..8a11137 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ResolveReferencesToAliasesPass.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Replaces all references to aliases with references to the actual service. + * + * @author Johannes M. Schmitt + */ +class ResolveReferencesToAliasesPass implements CompilerPassInterface +{ + private $container; + + /** + * Processes the ContainerBuilder to replace references to aliases with actual service references. + */ + public function process(ContainerBuilder $container) + { + $this->container = $container; + + foreach ($container->getDefinitions() as $definition) { + if ($definition->isSynthetic() || $definition->isAbstract()) { + continue; + } + + $definition->setArguments($this->processArguments($definition->getArguments())); + $definition->setMethodCalls($this->processArguments($definition->getMethodCalls())); + $definition->setProperties($this->processArguments($definition->getProperties())); + $definition->setFactory($this->processFactory($definition->getFactory())); + $definition->setFactoryService($this->processFactoryService($definition->getFactoryService(false)), false); + } + + foreach ($container->getAliases() as $id => $alias) { + $aliasId = (string) $alias; + if ($aliasId !== $defId = $this->getDefinitionId($aliasId)) { + $container->setAlias($id, new Alias($defId, $alias->isPublic())); + } + } + } + + /** + * Processes the arguments to replace aliases. + * + * @param array $arguments An array of References + * + * @return array An array of References + */ + private function processArguments(array $arguments) + { + foreach ($arguments as $k => $argument) { + if (\is_array($argument)) { + $arguments[$k] = $this->processArguments($argument); + } elseif ($argument instanceof Reference) { + $defId = $this->getDefinitionId($id = (string) $argument); + + if ($defId !== $id) { + $arguments[$k] = new Reference($defId, $argument->getInvalidBehavior(), $argument->isStrict(false)); + } + } + } + + return $arguments; + } + + private function processFactoryService($factoryService) + { + if (null === $factoryService) { + return; + } + + return $this->getDefinitionId($factoryService); + } + + private function processFactory($factory) + { + if (null === $factory || !\is_array($factory) || !$factory[0] instanceof Reference) { + return $factory; + } + + $defId = $this->getDefinitionId($id = (string) $factory[0]); + + if ($defId !== $id) { + $factory[0] = new Reference($defId, $factory[0]->getInvalidBehavior(), $factory[0]->isStrict(false)); + } + + return $factory; + } + + /** + * Resolves an alias into a definition id. + * + * @param string $id The definition or alias id to resolve + * + * @return string The definition id with aliases resolved + */ + private function getDefinitionId($id) + { + $seen = array(); + while ($this->container->hasAlias($id)) { + if (isset($seen[$id])) { + throw new ServiceCircularReferenceException($id, array_keys($seen)); + } + $seen[$id] = true; + $id = (string) $this->container->getAlias($id); + } + + return $id; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php new file mode 100644 index 0000000..43bac71 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraph.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * This is a directed graph of your services. + * + * This information can be used by your compiler passes instead of collecting + * it themselves which improves performance quite a lot. + * + * @author Johannes M. Schmitt + */ +class ServiceReferenceGraph +{ + /** + * @var ServiceReferenceGraphNode[] + */ + private $nodes = array(); + + /** + * Checks if the graph has a specific node. + * + * @param string $id Id to check + * + * @return bool + */ + public function hasNode($id) + { + return isset($this->nodes[$id]); + } + + /** + * Gets a node by identifier. + * + * @param string $id The id to retrieve + * + * @return ServiceReferenceGraphNode + * + * @throws InvalidArgumentException if no node matches the supplied identifier + */ + public function getNode($id) + { + if (!isset($this->nodes[$id])) { + throw new InvalidArgumentException(sprintf('There is no node with id "%s".', $id)); + } + + return $this->nodes[$id]; + } + + /** + * Returns all nodes. + * + * @return ServiceReferenceGraphNode[] + */ + public function getNodes() + { + return $this->nodes; + } + + /** + * Clears all nodes. + */ + public function clear() + { + $this->nodes = array(); + } + + /** + * Connects 2 nodes together in the Graph. + * + * @param string $sourceId + * @param mixed $sourceValue + * @param string $destId + * @param mixed $destValue + * @param string $reference + */ + public function connect($sourceId, $sourceValue, $destId, $destValue = null, $reference = null) + { + if (null === $sourceId || null === $destId) { + return; + } + $sourceNode = $this->createNode($sourceId, $sourceValue); + $destNode = $this->createNode($destId, $destValue); + $edge = new ServiceReferenceGraphEdge($sourceNode, $destNode, $reference); + + $sourceNode->addOutEdge($edge); + $destNode->addInEdge($edge); + } + + /** + * Creates a graph node. + * + * @param string $id + * @param mixed $value + * + * @return ServiceReferenceGraphNode + */ + private function createNode($id, $value) + { + if (isset($this->nodes[$id]) && $this->nodes[$id]->getValue() === $value) { + return $this->nodes[$id]; + } + + return $this->nodes[$id] = new ServiceReferenceGraphNode($id, $value); + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php new file mode 100644 index 0000000..7e8cf81 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphEdge.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +/** + * Represents an edge in your service graph. + * + * Value is typically a reference. + * + * @author Johannes M. Schmitt + */ +class ServiceReferenceGraphEdge +{ + private $sourceNode; + private $destNode; + private $value; + + /** + * @param ServiceReferenceGraphNode $sourceNode + * @param ServiceReferenceGraphNode $destNode + * @param mixed $value + */ + public function __construct(ServiceReferenceGraphNode $sourceNode, ServiceReferenceGraphNode $destNode, $value = null) + { + $this->sourceNode = $sourceNode; + $this->destNode = $destNode; + $this->value = $value; + } + + /** + * Returns the value of the edge. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the source node. + * + * @return ServiceReferenceGraphNode + */ + public function getSourceNode() + { + return $this->sourceNode; + } + + /** + * Returns the destination node. + * + * @return ServiceReferenceGraphNode + */ + public function getDestNode() + { + return $this->destNode; + } +} diff --git a/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php new file mode 100644 index 0000000..6a6d421 --- /dev/null +++ b/vendor/symfony/dependency-injection/Compiler/ServiceReferenceGraphNode.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Represents a node in your service graph. + * + * Value is typically a definition, or an alias. + * + * @author Johannes M. Schmitt + */ +class ServiceReferenceGraphNode +{ + private $id; + private $inEdges = array(); + private $outEdges = array(); + private $value; + + /** + * @param string $id The node identifier + * @param mixed $value The node value + */ + public function __construct($id, $value) + { + $this->id = $id; + $this->value = $value; + } + + public function addInEdge(ServiceReferenceGraphEdge $edge) + { + $this->inEdges[] = $edge; + } + + public function addOutEdge(ServiceReferenceGraphEdge $edge) + { + $this->outEdges[] = $edge; + } + + /** + * Checks if the value of this node is an Alias. + * + * @return bool True if the value is an Alias instance + */ + public function isAlias() + { + return $this->value instanceof Alias; + } + + /** + * Checks if the value of this node is a Definition. + * + * @return bool True if the value is a Definition instance + */ + public function isDefinition() + { + return $this->value instanceof Definition; + } + + /** + * Returns the identifier. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + /** + * Returns the in edges. + * + * @return array The in ServiceReferenceGraphEdge array + */ + public function getInEdges() + { + return $this->inEdges; + } + + /** + * Returns the out edges. + * + * @return array The out ServiceReferenceGraphEdge array + */ + public function getOutEdges() + { + return $this->outEdges; + } + + /** + * Returns the value of this Node. + * + * @return mixed The value + */ + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/symfony/dependency-injection/Container.php b/vendor/symfony/dependency-injection/Container.php new file mode 100644 index 0000000..25532ea --- /dev/null +++ b/vendor/symfony/dependency-injection/Container.php @@ -0,0 +1,574 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\InactiveScopeException; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; + +/** + * Container is a dependency injection container. + * + * It gives access to object instances (services). + * + * Services and parameters are simple key/pair stores. + * + * Parameter and service keys are case insensitive. + * + * A service can also be defined by creating a method named + * getXXXService(), where XXX is the camelized version of the id: + * + * * request -> getRequestService() + * * mysql_session_storage -> getMysqlSessionStorageService() + * * symfony.mysql_session_storage -> getSymfony_MysqlSessionStorageService() + * + * The container can have three possible behaviors when a service does not exist: + * + * * EXCEPTION_ON_INVALID_REFERENCE: Throws an exception (the default) + * * NULL_ON_INVALID_REFERENCE: Returns null + * * IGNORE_ON_INVALID_REFERENCE: Ignores the wrapping command asking for the reference + * (for instance, ignore a setter if the service does not exist) + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class Container implements IntrospectableContainerInterface, ResettableContainerInterface +{ + protected $parameterBag; + protected $services = array(); + protected $methodMap = array(); + protected $aliases = array(); + protected $scopes = array(); + protected $scopeChildren = array(); + protected $scopedServices = array(); + protected $scopeStacks = array(); + protected $loading = array(); + + private $underscoreMap = array('_' => '', '.' => '_', '\\' => '_'); + + public function __construct(ParameterBagInterface $parameterBag = null) + { + $this->parameterBag = $parameterBag ?: new ParameterBag(); + } + + /** + * Compiles the container. + * + * This method does two things: + * + * * Parameter values are resolved; + * * The parameter bag is frozen. + */ + public function compile() + { + $this->parameterBag->resolve(); + + $this->parameterBag = new FrozenParameterBag($this->parameterBag->all()); + } + + /** + * Returns true if the container parameter bag are frozen. + * + * @return bool true if the container parameter bag are frozen, false otherwise + */ + public function isFrozen() + { + return $this->parameterBag instanceof FrozenParameterBag; + } + + /** + * Gets the service container parameter bag. + * + * @return ParameterBagInterface A ParameterBagInterface instance + */ + public function getParameterBag() + { + return $this->parameterBag; + } + + /** + * Gets a parameter. + * + * @param string $name The parameter name + * + * @return mixed The parameter value + * + * @throws InvalidArgumentException if the parameter is not defined + */ + public function getParameter($name) + { + return $this->parameterBag->get($name); + } + + /** + * Checks if a parameter exists. + * + * @param string $name The parameter name + * + * @return bool The presence of parameter in container + */ + public function hasParameter($name) + { + return $this->parameterBag->has($name); + } + + /** + * Sets a parameter. + * + * @param string $name The parameter name + * @param mixed $value The parameter value + */ + public function setParameter($name, $value) + { + $this->parameterBag->set($name, $value); + } + + /** + * Sets a service. + * + * Setting a service to null resets the service: has() returns false and get() + * behaves in the same way as if the service was never created. + * + * Note: The $scope parameter is deprecated since version 2.8 and will be removed in 3.0. + * + * @param string $id The service identifier + * @param object $service The service instance + * @param string $scope The scope of the service + * + * @throws RuntimeException When trying to set a service in an inactive scope + * @throws InvalidArgumentException When trying to set a service in the prototype scope + */ + public function set($id, $service, $scope = self::SCOPE_CONTAINER) + { + if (!\in_array($scope, array('container', 'request')) || ('request' === $scope && 'request' !== $id)) { + @trigger_error('The concept of container scopes is deprecated since Symfony 2.8 and will be removed in 3.0. Omit the third parameter.', E_USER_DEPRECATED); + } + + if (self::SCOPE_PROTOTYPE === $scope) { + throw new InvalidArgumentException(sprintf('You cannot set service "%s" of scope "prototype".', $id)); + } + + $id = strtolower($id); + + if ('service_container' === $id) { + // BC: 'service_container' is no longer a self-reference but always + // $this, so ignore this call. + // @todo Throw InvalidArgumentException in next major release. + return; + } + if (self::SCOPE_CONTAINER !== $scope) { + if (!isset($this->scopedServices[$scope])) { + throw new RuntimeException(sprintf('You cannot set service "%s" of inactive scope.', $id)); + } + + $this->scopedServices[$scope][$id] = $service; + } + + if (isset($this->aliases[$id])) { + unset($this->aliases[$id]); + } + + $this->services[$id] = $service; + + if (method_exists($this, $method = 'synchronize'.strtr($id, $this->underscoreMap).'Service')) { + $this->$method(); + } + + if (null === $service) { + if (self::SCOPE_CONTAINER !== $scope) { + unset($this->scopedServices[$scope][$id]); + } + + unset($this->services[$id]); + } + } + + /** + * Returns true if the given service is defined. + * + * @param string $id The service identifier + * + * @return bool true if the service is defined, false otherwise + */ + public function has($id) + { + for ($i = 2;;) { + if ('service_container' === $id + || isset($this->aliases[$id]) + || isset($this->services[$id]) + || array_key_exists($id, $this->services) + ) { + return true; + } + if (--$i && $id !== $lcId = strtolower($id)) { + $id = $lcId; + } else { + return method_exists($this, 'get'.strtr($id, $this->underscoreMap).'Service'); + } + } + } + + /** + * Gets a service. + * + * If a service is defined both through a set() method and + * with a get{$id}Service() method, the former has always precedence. + * + * @param string $id The service identifier + * @param int $invalidBehavior The behavior when the service does not exist + * + * @return object The associated service + * + * @throws ServiceCircularReferenceException When a circular reference is detected + * @throws ServiceNotFoundException When the service is not defined + * @throws \Exception if an exception has been thrown when the service has been resolved + * + * @see Reference + */ + public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) + { + // Attempt to retrieve the service by checking first aliases then + // available services. Service IDs are case insensitive, however since + // this method can be called thousands of times during a request, avoid + // calling strtolower() unless necessary. + for ($i = 2;;) { + if (isset($this->aliases[$id])) { + $id = $this->aliases[$id]; + } + // Re-use shared service instance if it exists. + if (isset($this->services[$id]) || array_key_exists($id, $this->services)) { + return $this->services[$id]; + } + if ('service_container' === $id) { + return $this; + } + + if (isset($this->loading[$id])) { + throw new ServiceCircularReferenceException($id, array_keys($this->loading)); + } + + if (isset($this->methodMap[$id])) { + $method = $this->methodMap[$id]; + } elseif (--$i && $id !== $lcId = strtolower($id)) { + $id = $lcId; + continue; + } elseif (method_exists($this, $method = 'get'.strtr($id, $this->underscoreMap).'Service')) { + // $method is set to the right value, proceed + } else { + if (self::EXCEPTION_ON_INVALID_REFERENCE === $invalidBehavior) { + if (!$id) { + throw new ServiceNotFoundException($id); + } + + $alternatives = array(); + foreach ($this->getServiceIds() as $knownId) { + $lev = levenshtein($id, $knownId); + if ($lev <= \strlen($id) / 3 || false !== strpos($knownId, $id)) { + $alternatives[] = $knownId; + } + } + + throw new ServiceNotFoundException($id, null, null, $alternatives); + } + + return; + } + + $this->loading[$id] = true; + + try { + $service = $this->$method(); + } catch (\Exception $e) { + unset($this->loading[$id]); + unset($this->services[$id]); + + if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { + return; + } + + throw $e; + } catch (\Throwable $e) { + unset($this->loading[$id]); + unset($this->services[$id]); + + throw $e; + } + + unset($this->loading[$id]); + + return $service; + } + } + + /** + * Returns true if the given service has actually been initialized. + * + * @param string $id The service identifier + * + * @return bool true if service has already been initialized, false otherwise + */ + public function initialized($id) + { + $id = strtolower($id); + + if (isset($this->aliases[$id])) { + $id = $this->aliases[$id]; + } + + if ('service_container' === $id) { + // BC: 'service_container' was a synthetic service previously. + // @todo Change to false in next major release. + return true; + } + + return isset($this->services[$id]) || array_key_exists($id, $this->services); + } + + /** + * {@inheritdoc} + */ + public function reset() + { + if (!empty($this->scopedServices)) { + throw new LogicException('Resetting the container is not allowed when a scope is active.'); + } + + $this->services = array(); + } + + /** + * Gets all service ids. + * + * @return array An array of all defined service ids + */ + public function getServiceIds() + { + $ids = array(); + foreach (get_class_methods($this) as $method) { + if (preg_match('/^get(.+)Service$/', $method, $match)) { + $ids[] = self::underscore($match[1]); + } + } + $ids[] = 'service_container'; + + return array_unique(array_merge($ids, array_keys($this->services))); + } + + /** + * This is called when you enter a scope. + * + * @param string $name + * + * @throws RuntimeException When the parent scope is inactive + * @throws InvalidArgumentException When the scope does not exist + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function enterScope($name) + { + if ('request' !== $name) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + if (!isset($this->scopes[$name])) { + throw new InvalidArgumentException(sprintf('The scope "%s" does not exist.', $name)); + } + + if (self::SCOPE_CONTAINER !== $this->scopes[$name] && !isset($this->scopedServices[$this->scopes[$name]])) { + throw new RuntimeException(sprintf('The parent scope "%s" must be active when entering this scope.', $this->scopes[$name])); + } + + // check if a scope of this name is already active, if so we need to + // remove all services of this scope, and those of any of its child + // scopes from the global services map + if (isset($this->scopedServices[$name])) { + $services = array($this->services, $name => $this->scopedServices[$name]); + unset($this->scopedServices[$name]); + + foreach ($this->scopeChildren[$name] as $child) { + if (isset($this->scopedServices[$child])) { + $services[$child] = $this->scopedServices[$child]; + unset($this->scopedServices[$child]); + } + } + + // update global map + $this->services = \call_user_func_array('array_diff_key', $services); + array_shift($services); + + // add stack entry for this scope so we can restore the removed services later + if (!isset($this->scopeStacks[$name])) { + $this->scopeStacks[$name] = new \SplStack(); + } + $this->scopeStacks[$name]->push($services); + } + + $this->scopedServices[$name] = array(); + } + + /** + * This is called to leave the current scope, and move back to the parent + * scope. + * + * @param string $name The name of the scope to leave + * + * @throws InvalidArgumentException if the scope is not active + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function leaveScope($name) + { + if ('request' !== $name) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + if (!isset($this->scopedServices[$name])) { + throw new InvalidArgumentException(sprintf('The scope "%s" is not active.', $name)); + } + + // remove all services of this scope, or any of its child scopes from + // the global service map + $services = array($this->services, $this->scopedServices[$name]); + unset($this->scopedServices[$name]); + + foreach ($this->scopeChildren[$name] as $child) { + if (isset($this->scopedServices[$child])) { + $services[] = $this->scopedServices[$child]; + unset($this->scopedServices[$child]); + } + } + + // update global map + $this->services = \call_user_func_array('array_diff_key', $services); + + // check if we need to restore services of a previous scope of this type + if (isset($this->scopeStacks[$name]) && \count($this->scopeStacks[$name]) > 0) { + $services = $this->scopeStacks[$name]->pop(); + $this->scopedServices += $services; + + if ($this->scopeStacks[$name]->isEmpty()) { + unset($this->scopeStacks[$name]); + } + + foreach ($services as $array) { + foreach ($array as $id => $service) { + $this->set($id, $service, $name); + } + } + } + } + + /** + * Adds a scope to the container. + * + * @throws InvalidArgumentException + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function addScope(ScopeInterface $scope) + { + $name = $scope->getName(); + $parentScope = $scope->getParentName(); + + if ('request' !== $name) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + if (self::SCOPE_CONTAINER === $name || self::SCOPE_PROTOTYPE === $name) { + throw new InvalidArgumentException(sprintf('The scope "%s" is reserved.', $name)); + } + if (isset($this->scopes[$name])) { + throw new InvalidArgumentException(sprintf('A scope with name "%s" already exists.', $name)); + } + if (self::SCOPE_CONTAINER !== $parentScope && !isset($this->scopes[$parentScope])) { + throw new InvalidArgumentException(sprintf('The parent scope "%s" does not exist, or is invalid.', $parentScope)); + } + + $this->scopes[$name] = $parentScope; + $this->scopeChildren[$name] = array(); + + // normalize the child relations + while (self::SCOPE_CONTAINER !== $parentScope) { + $this->scopeChildren[$parentScope][] = $name; + $parentScope = $this->scopes[$parentScope]; + } + } + + /** + * Returns whether this container has a certain scope. + * + * @param string $name The name of the scope + * + * @return bool + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function hasScope($name) + { + if ('request' !== $name) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return isset($this->scopes[$name]); + } + + /** + * Returns whether this scope is currently active. + * + * This does not actually check if the passed scope actually exists. + * + * @param string $name + * + * @return bool + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function isScopeActive($name) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + return isset($this->scopedServices[$name]); + } + + /** + * Camelizes a string. + * + * @param string $id A string to camelize + * + * @return string The camelized string + */ + public static function camelize($id) + { + return strtr(ucwords(strtr($id, array('_' => ' ', '.' => '_ ', '\\' => '_ '))), array(' ' => '')); + } + + /** + * A string to underscore. + * + * @param string $id The string to underscore + * + * @return string The underscored string + */ + public static function underscore($id) + { + return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), str_replace('_', '.', $id))); + } + + private function __clone() + { + } +} diff --git a/vendor/symfony/dependency-injection/ContainerAware.php b/vendor/symfony/dependency-injection/ContainerAware.php new file mode 100644 index 0000000..f3f2a50 --- /dev/null +++ b/vendor/symfony/dependency-injection/ContainerAware.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * A simple implementation of ContainerAwareInterface. + * + * @author Fabien Potencier + * + * @deprecated since version 2.8, to be removed in 3.0. Use the ContainerAwareTrait instead. + */ +abstract class ContainerAware implements ContainerAwareInterface +{ + /** + * @var ContainerInterface + */ + protected $container; + + /** + * {@inheritdoc} + */ + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } +} diff --git a/vendor/symfony/dependency-injection/ContainerAwareInterface.php b/vendor/symfony/dependency-injection/ContainerAwareInterface.php new file mode 100644 index 0000000..e7b9d57 --- /dev/null +++ b/vendor/symfony/dependency-injection/ContainerAwareInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * ContainerAwareInterface should be implemented by classes that depends on a Container. + * + * @author Fabien Potencier + */ +interface ContainerAwareInterface +{ + /** + * Sets the container. + */ + public function setContainer(ContainerInterface $container = null); +} diff --git a/vendor/symfony/dependency-injection/ContainerAwareTrait.php b/vendor/symfony/dependency-injection/ContainerAwareTrait.php new file mode 100644 index 0000000..ee1ea2c --- /dev/null +++ b/vendor/symfony/dependency-injection/ContainerAwareTrait.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * ContainerAware trait. + * + * @author Fabien Potencier + */ +trait ContainerAwareTrait +{ + /** + * @var ContainerInterface + */ + protected $container; + + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } +} diff --git a/vendor/symfony/dependency-injection/ContainerBuilder.php b/vendor/symfony/dependency-injection/ContainerBuilder.php new file mode 100644 index 0000000..c31eeea --- /dev/null +++ b/vendor/symfony/dependency-injection/ContainerBuilder.php @@ -0,0 +1,1187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Resource\ResourceInterface; +use Symfony\Component\DependencyInjection\Compiler\Compiler; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PassConfig; +use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; +use Symfony\Component\DependencyInjection\Exception\InactiveScopeException; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\RealServiceInstantiator; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * ContainerBuilder is a DI container that provides an API to easily describe services. + * + * @author Fabien Potencier + */ +class ContainerBuilder extends Container implements TaggedContainerInterface +{ + /** + * @var ExtensionInterface[] + */ + private $extensions = array(); + + /** + * @var ExtensionInterface[] + */ + private $extensionsByNs = array(); + + /** + * @var Definition[] + */ + private $definitions = array(); + + /** + * @var Definition[] + */ + private $obsoleteDefinitions = array(); + + /** + * @var Alias[] + */ + private $aliasDefinitions = array(); + + /** + * @var ResourceInterface[] + */ + private $resources = array(); + + private $extensionConfigs = array(); + + /** + * @var Compiler + */ + private $compiler; + + private $trackResources; + + /** + * @var InstantiatorInterface|null + */ + private $proxyInstantiator; + + /** + * @var ExpressionLanguage|null + */ + private $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + public function __construct(ParameterBagInterface $parameterBag = null) + { + parent::__construct($parameterBag); + + $this->trackResources = interface_exists('Symfony\Component\Config\Resource\ResourceInterface'); + } + + /** + * @var string[] with tag names used by findTaggedServiceIds + */ + private $usedTags = array(); + + /** + * Sets the track resources flag. + * + * If you are not using the loaders and therefore don't want + * to depend on the Config component, set this flag to false. + * + * @param bool $track True if you want to track resources, false otherwise + */ + public function setResourceTracking($track) + { + $this->trackResources = (bool) $track; + } + + /** + * Checks if resources are tracked. + * + * @return bool true If resources are tracked, false otherwise + */ + public function isTrackingResources() + { + return $this->trackResources; + } + + /** + * Sets the instantiator to be used when fetching proxies. + */ + public function setProxyInstantiator(InstantiatorInterface $proxyInstantiator) + { + $this->proxyInstantiator = $proxyInstantiator; + } + + public function registerExtension(ExtensionInterface $extension) + { + $this->extensions[$extension->getAlias()] = $extension; + + if (false !== $extension->getNamespace()) { + $this->extensionsByNs[$extension->getNamespace()] = $extension; + } + } + + /** + * Returns an extension by alias or namespace. + * + * @param string $name An alias or a namespace + * + * @return ExtensionInterface An extension instance + * + * @throws LogicException if the extension is not registered + */ + public function getExtension($name) + { + if (isset($this->extensions[$name])) { + return $this->extensions[$name]; + } + + if (isset($this->extensionsByNs[$name])) { + return $this->extensionsByNs[$name]; + } + + throw new LogicException(sprintf('Container extension "%s" is not registered', $name)); + } + + /** + * Returns all registered extensions. + * + * @return ExtensionInterface[] An array of ExtensionInterface + */ + public function getExtensions() + { + return $this->extensions; + } + + /** + * Checks if we have an extension. + * + * @param string $name The name of the extension + * + * @return bool If the extension exists + */ + public function hasExtension($name) + { + return isset($this->extensions[$name]) || isset($this->extensionsByNs[$name]); + } + + /** + * Returns an array of resources loaded to build this configuration. + * + * @return ResourceInterface[] An array of resources + */ + public function getResources() + { + return array_unique($this->resources); + } + + /** + * @return $this + */ + public function addResource(ResourceInterface $resource) + { + if (!$this->trackResources) { + return $this; + } + + $this->resources[] = $resource; + + return $this; + } + + /** + * Sets the resources for this configuration. + * + * @param ResourceInterface[] $resources An array of resources + * + * @return $this + */ + public function setResources(array $resources) + { + if (!$this->trackResources) { + return $this; + } + + $this->resources = $resources; + + return $this; + } + + /** + * Adds the object class hierarchy as resources. + * + * @param object $object An object instance + * + * @return $this + */ + public function addObjectResource($object) + { + if ($this->trackResources) { + $this->addClassResource(new \ReflectionClass($object)); + } + + return $this; + } + + /** + * Adds the given class hierarchy as resources. + * + * @return $this + */ + public function addClassResource(\ReflectionClass $class) + { + if (!$this->trackResources) { + return $this; + } + + do { + if (is_file($class->getFileName())) { + $this->addResource(new FileResource($class->getFileName())); + } + } while ($class = $class->getParentClass()); + + return $this; + } + + /** + * Loads the configuration for an extension. + * + * @param string $extension The extension alias or namespace + * @param array $values An array of values that customizes the extension + * + * @return $this + * + * @throws BadMethodCallException When this ContainerBuilder is frozen + * @throws \LogicException if the container is frozen + */ + public function loadFromExtension($extension, array $values = null) + { + if ($this->isFrozen()) { + throw new BadMethodCallException('Cannot load from an extension on a frozen container.'); + } + + if (\func_num_args() < 2) { + $values = array(); + } + + $namespace = $this->getExtension($extension)->getAlias(); + + $this->extensionConfigs[$namespace][] = $values; + + return $this; + } + + /** + * Adds a compiler pass. + * + * @param CompilerPassInterface $pass A compiler pass + * @param string $type The type of compiler pass + * + * @return $this + */ + public function addCompilerPass(CompilerPassInterface $pass, $type = PassConfig::TYPE_BEFORE_OPTIMIZATION) + { + $this->getCompiler()->addPass($pass, $type); + + $this->addObjectResource($pass); + + return $this; + } + + /** + * Returns the compiler pass config which can then be modified. + * + * @return PassConfig The compiler pass config + */ + public function getCompilerPassConfig() + { + return $this->getCompiler()->getPassConfig(); + } + + /** + * Returns the compiler. + * + * @return Compiler The compiler + */ + public function getCompiler() + { + if (null === $this->compiler) { + $this->compiler = new Compiler(); + } + + return $this->compiler; + } + + /** + * Returns all Scopes. + * + * @return array An array of scopes + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function getScopes($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return $this->scopes; + } + + /** + * Returns all Scope children. + * + * @return array An array of scope children + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function getScopeChildren($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return $this->scopeChildren; + } + + /** + * Sets a service. + * + * Note: The $scope parameter is deprecated since version 2.8 and will be removed in 3.0. + * + * @param string $id The service identifier + * @param object $service The service instance + * @param string $scope The scope + * + * @throws BadMethodCallException When this ContainerBuilder is frozen + */ + public function set($id, $service, $scope = self::SCOPE_CONTAINER) + { + $id = strtolower($id); + $set = isset($this->definitions[$id]); + + if ($this->isFrozen() && ($set || isset($this->obsoleteDefinitions[$id])) && !$this->{$set ? 'definitions' : 'obsoleteDefinitions'}[$id]->isSynthetic()) { + // setting a synthetic service on a frozen container is alright + throw new BadMethodCallException(sprintf('Setting service "%s" on a frozen container is not allowed.', $id)); + } + + if ($set) { + $this->obsoleteDefinitions[$id] = $this->definitions[$id]; + } + + unset($this->definitions[$id], $this->aliasDefinitions[$id]); + + parent::set($id, $service, $scope); + + if (isset($this->obsoleteDefinitions[$id]) && $this->obsoleteDefinitions[$id]->isSynchronized(false)) { + $this->synchronize($id); + } + } + + /** + * Removes a service definition. + * + * @param string $id The service identifier + */ + public function removeDefinition($id) + { + unset($this->definitions[strtolower($id)]); + } + + /** + * Returns true if the given service is defined. + * + * @param string $id The service identifier + * + * @return bool true if the service is defined, false otherwise + */ + public function has($id) + { + $id = strtolower($id); + + return isset($this->definitions[$id]) || isset($this->aliasDefinitions[$id]) || parent::has($id); + } + + /** + * Gets a service. + * + * @param string $id The service identifier + * @param int $invalidBehavior The behavior when the service does not exist + * + * @return object The associated service + * + * @throws InvalidArgumentException when no definitions are available + * @throws ServiceCircularReferenceException When a circular reference is detected + * @throws ServiceNotFoundException When the service is not defined + * @throws \Exception + * + * @see Reference + */ + public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) + { + $id = strtolower($id); + + if ($service = parent::get($id, ContainerInterface::NULL_ON_INVALID_REFERENCE)) { + return $service; + } + + if (!array_key_exists($id, $this->definitions) && isset($this->aliasDefinitions[$id])) { + return $this->get((string) $this->aliasDefinitions[$id], $invalidBehavior); + } + + try { + $definition = $this->getDefinition($id); + } catch (ServiceNotFoundException $e) { + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { + return; + } + + throw $e; + } + + $this->loading[$id] = true; + + try { + $service = $this->createService($definition, new \SplObjectStorage(), $id); + } catch (\Exception $e) { + unset($this->loading[$id]); + + if ($e instanceof InactiveScopeException && self::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) { + return; + } + + throw $e; + } catch (\Throwable $e) { + unset($this->loading[$id]); + + throw $e; + } + + unset($this->loading[$id]); + + return $service; + } + + /** + * Merges a ContainerBuilder with the current ContainerBuilder configuration. + * + * Service definitions overrides the current defined ones. + * + * But for parameters, they are overridden by the current ones. It allows + * the parameters passed to the container constructor to have precedence + * over the loaded ones. + * + * $container = new ContainerBuilder(new ParameterBag(array('foo' => 'bar'))); + * $loader = new LoaderXXX($container); + * $loader->load('resource_name'); + * $container->register('foo', 'stdClass'); + * + * In the above example, even if the loaded resource defines a foo + * parameter, the value will still be 'bar' as defined in the ContainerBuilder + * constructor. + * + * @throws BadMethodCallException When this ContainerBuilder is frozen + */ + public function merge(ContainerBuilder $container) + { + if ($this->isFrozen()) { + throw new BadMethodCallException('Cannot merge on a frozen container.'); + } + + $this->addDefinitions($container->getDefinitions()); + $this->addAliases($container->getAliases()); + $this->getParameterBag()->add($container->getParameterBag()->all()); + + if ($this->trackResources) { + foreach ($container->getResources() as $resource) { + $this->addResource($resource); + } + } + + foreach ($this->extensions as $name => $extension) { + if (!isset($this->extensionConfigs[$name])) { + $this->extensionConfigs[$name] = array(); + } + + $this->extensionConfigs[$name] = array_merge($this->extensionConfigs[$name], $container->getExtensionConfig($name)); + } + } + + /** + * Returns the configuration array for the given extension. + * + * @param string $name The name of the extension + * + * @return array An array of configuration + */ + public function getExtensionConfig($name) + { + if (!isset($this->extensionConfigs[$name])) { + $this->extensionConfigs[$name] = array(); + } + + return $this->extensionConfigs[$name]; + } + + /** + * Prepends a config array to the configs of the given extension. + * + * @param string $name The name of the extension + * @param array $config The config to set + */ + public function prependExtensionConfig($name, array $config) + { + if (!isset($this->extensionConfigs[$name])) { + $this->extensionConfigs[$name] = array(); + } + + array_unshift($this->extensionConfigs[$name], $config); + } + + /** + * Compiles the container. + * + * This method passes the container to compiler + * passes whose job is to manipulate and optimize + * the container. + * + * The main compiler passes roughly do four things: + * + * * The extension configurations are merged; + * * Parameter values are resolved; + * * The parameter bag is frozen; + * * Extension loading is disabled. + */ + public function compile() + { + $compiler = $this->getCompiler(); + + if ($this->trackResources) { + foreach ($compiler->getPassConfig()->getPasses() as $pass) { + $this->addObjectResource($pass); + } + } + + $compiler->compile($this); + + if ($this->trackResources) { + foreach ($this->definitions as $definition) { + if ($definition->isLazy() && ($class = $definition->getClass()) && class_exists($class)) { + $this->addClassResource(new \ReflectionClass($class)); + } + } + } + + $this->extensionConfigs = array(); + + parent::compile(); + } + + /** + * Gets all service ids. + * + * @return array An array of all defined service ids + */ + public function getServiceIds() + { + return array_unique(array_merge(array_keys($this->getDefinitions()), array_keys($this->aliasDefinitions), parent::getServiceIds())); + } + + /** + * Adds the service aliases. + */ + public function addAliases(array $aliases) + { + foreach ($aliases as $alias => $id) { + $this->setAlias($alias, $id); + } + } + + /** + * Sets the service aliases. + */ + public function setAliases(array $aliases) + { + $this->aliasDefinitions = array(); + $this->addAliases($aliases); + } + + /** + * Sets an alias for an existing service. + * + * @param string $alias The alias to create + * @param string|Alias $id The service to alias + * + * @throws InvalidArgumentException if the id is not a string or an Alias + * @throws InvalidArgumentException if the alias is for itself + */ + public function setAlias($alias, $id) + { + $alias = strtolower($alias); + + if (\is_string($id)) { + $id = new Alias($id); + } elseif (!$id instanceof Alias) { + throw new InvalidArgumentException('$id must be a string, or an Alias object.'); + } + + if ($alias === (string) $id) { + throw new InvalidArgumentException(sprintf('An alias can not reference itself, got a circular reference on "%s".', $alias)); + } + + unset($this->definitions[$alias]); + + $this->aliasDefinitions[$alias] = $id; + } + + /** + * Removes an alias. + * + * @param string $alias The alias to remove + */ + public function removeAlias($alias) + { + unset($this->aliasDefinitions[strtolower($alias)]); + } + + /** + * Returns true if an alias exists under the given identifier. + * + * @param string $id The service identifier + * + * @return bool true if the alias exists, false otherwise + */ + public function hasAlias($id) + { + return isset($this->aliasDefinitions[strtolower($id)]); + } + + /** + * Gets all defined aliases. + * + * @return Alias[] An array of aliases + */ + public function getAliases() + { + return $this->aliasDefinitions; + } + + /** + * Gets an alias. + * + * @param string $id The service identifier + * + * @return Alias An Alias instance + * + * @throws InvalidArgumentException if the alias does not exist + */ + public function getAlias($id) + { + $id = strtolower($id); + + if (!isset($this->aliasDefinitions[$id])) { + throw new InvalidArgumentException(sprintf('The service alias "%s" does not exist.', $id)); + } + + return $this->aliasDefinitions[$id]; + } + + /** + * Registers a service definition. + * + * This methods allows for simple registration of service definition + * with a fluid interface. + * + * @param string $id The service identifier + * @param string $class|null The service class + * + * @return Definition A Definition instance + */ + public function register($id, $class = null) + { + return $this->setDefinition($id, new Definition($class)); + } + + /** + * Adds the service definitions. + * + * @param Definition[] $definitions An array of service definitions + */ + public function addDefinitions(array $definitions) + { + foreach ($definitions as $id => $definition) { + $this->setDefinition($id, $definition); + } + } + + /** + * Sets the service definitions. + * + * @param Definition[] $definitions An array of service definitions + */ + public function setDefinitions(array $definitions) + { + $this->definitions = array(); + $this->addDefinitions($definitions); + } + + /** + * Gets all service definitions. + * + * @return Definition[] An array of Definition instances + */ + public function getDefinitions() + { + return $this->definitions; + } + + /** + * Sets a service definition. + * + * @param string $id The service identifier + * @param Definition $definition A Definition instance + * + * @return Definition the service definition + * + * @throws BadMethodCallException When this ContainerBuilder is frozen + */ + public function setDefinition($id, Definition $definition) + { + if ($this->isFrozen()) { + throw new BadMethodCallException('Adding definition to a frozen container is not allowed'); + } + + $id = strtolower($id); + + unset($this->aliasDefinitions[$id]); + + return $this->definitions[$id] = $definition; + } + + /** + * Returns true if a service definition exists under the given identifier. + * + * @param string $id The service identifier + * + * @return bool true if the service definition exists, false otherwise + */ + public function hasDefinition($id) + { + return array_key_exists(strtolower($id), $this->definitions); + } + + /** + * Gets a service definition. + * + * @param string $id The service identifier + * + * @return Definition A Definition instance + * + * @throws ServiceNotFoundException if the service definition does not exist + */ + public function getDefinition($id) + { + $id = strtolower($id); + + if (!array_key_exists($id, $this->definitions)) { + throw new ServiceNotFoundException($id); + } + + return $this->definitions[$id]; + } + + /** + * Gets a service definition by id or alias. + * + * The method "unaliases" recursively to return a Definition instance. + * + * @param string $id The service identifier or alias + * + * @return Definition A Definition instance + * + * @throws ServiceNotFoundException if the service definition does not exist + */ + public function findDefinition($id) + { + $id = strtolower($id); + + while (isset($this->aliasDefinitions[$id])) { + $id = (string) $this->aliasDefinitions[$id]; + } + + return $this->getDefinition($id); + } + + /** + * Creates a service for a service definition. + * + * @param Definition $definition A service definition instance + * @param string $id The service identifier + * @param bool $tryProxy Whether to try proxying the service with a lazy proxy + * + * @return object The service described by the service definition + * + * @throws RuntimeException When the scope is inactive + * @throws RuntimeException When the factory definition is incomplete + * @throws RuntimeException When the service is a synthetic service + * @throws InvalidArgumentException When configure callable is not callable + * + * @internal this method is public because of PHP 5.3 limitations, do not use it explicitly in your code + */ + public function createService(Definition $definition, \SplObjectStorage $inlinedDefinitions, $id = null, $tryProxy = true) + { + if (null === $id && isset($inlinedDefinitions[$definition])) { + return $inlinedDefinitions[$definition]; + } + + if ($definition instanceof DefinitionDecorator) { + throw new RuntimeException(sprintf('Constructing service "%s" from a parent definition is not supported at build time.', $id)); + } + + if ($definition->isSynthetic()) { + throw new RuntimeException(sprintf('You have requested a synthetic service ("%s"). The DIC does not know how to construct this service.', $id)); + } + + if ($definition->isDeprecated()) { + @trigger_error($definition->getDeprecationMessage($id), E_USER_DEPRECATED); + } + + if ($tryProxy && $definition->isLazy()) { + $container = $this; + + $proxy = $this + ->getProxyInstantiator() + ->instantiateProxy( + $container, + $definition, + $id, function () use ($definition, $inlinedDefinitions, $id, $container) { + return $container->createService($definition, $inlinedDefinitions, $id, false); + } + ); + $this->shareService($definition, $proxy, $id, $inlinedDefinitions); + + return $proxy; + } + + $parameterBag = $this->getParameterBag(); + + if (null !== $definition->getFile()) { + require_once $parameterBag->resolveValue($definition->getFile()); + } + + $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlinedDefinitions); + + if (null !== $factory = $definition->getFactory()) { + if (\is_array($factory)) { + $factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlinedDefinitions), $factory[1]); + } elseif (!\is_string($factory)) { + throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); + } + + $service = \call_user_func_array($factory, $arguments); + + if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) { + $r = new \ReflectionClass($factory[0]); + + if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name), E_USER_DEPRECATED); + } + } + } elseif (null !== $definition->getFactoryMethod(false)) { + if (null !== $definition->getFactoryClass(false)) { + $factory = $parameterBag->resolveValue($definition->getFactoryClass(false)); + } elseif (null !== $definition->getFactoryService(false)) { + $factory = $this->get($parameterBag->resolveValue($definition->getFactoryService(false))); + } else { + throw new RuntimeException(sprintf('Cannot create service "%s" from factory method without a factory service or factory class.', $id)); + } + + $service = \call_user_func_array(array($factory, $definition->getFactoryMethod(false)), $arguments); + } else { + $r = new \ReflectionClass($parameterBag->resolveValue($definition->getClass())); + + $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments); + + if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + @trigger_error(sprintf('The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name), E_USER_DEPRECATED); + } + } + + if ($tryProxy || !$definition->isLazy()) { + // share only if proxying failed, or if not a proxy + $this->shareService($definition, $service, $id, $inlinedDefinitions); + } + + $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlinedDefinitions); + foreach ($properties as $name => $value) { + $service->$name = $value; + } + + foreach ($definition->getMethodCalls() as $call) { + $this->callMethod($service, $call, $inlinedDefinitions); + } + + if ($callable = $definition->getConfigurator()) { + if (\is_array($callable)) { + $callable[0] = $parameterBag->resolveValue($callable[0]); + + if ($callable[0] instanceof Reference) { + $callable[0] = $this->get((string) $callable[0], $callable[0]->getInvalidBehavior()); + } elseif ($callable[0] instanceof Definition) { + $callable[0] = $this->createService($callable[0], $inlinedDefinitions); + } + } + + if (!\is_callable($callable)) { + throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', \get_class($service))); + } + + \call_user_func($callable, $service); + } + + return $service; + } + + /** + * Replaces service references by the real service instance and evaluates expressions. + * + * @param mixed $value A value + * + * @return mixed The same value with all service references replaced by + * the real service instances and all expressions evaluated + */ + public function resolveServices($value) + { + return $this->doResolveServices($value, new \SplObjectStorage()); + } + + private function doResolveServices($value, \SplObjectStorage $inlinedDefinitions) + { + if (\is_array($value)) { + foreach ($value as $k => $v) { + $value[$k] = $this->doResolveServices($v, $inlinedDefinitions); + } + } elseif ($value instanceof Reference) { + $value = $this->get((string) $value, $value->getInvalidBehavior()); + } elseif ($value instanceof Definition) { + $value = $this->createService($value, $inlinedDefinitions); + } elseif ($value instanceof Expression) { + $value = $this->getExpressionLanguage()->evaluate($value, array('container' => $this)); + } + + return $value; + } + + /** + * Returns service ids for a given tag. + * + * Example: + * + * $container->register('foo')->addTag('my.tag', array('hello' => 'world')); + * + * $serviceIds = $container->findTaggedServiceIds('my.tag'); + * foreach ($serviceIds as $serviceId => $tags) { + * foreach ($tags as $tag) { + * echo $tag['hello']; + * } + * } + * + * @param string $name The tag name + * + * @return array An array of tags with the tagged service as key, holding a list of attribute arrays + */ + public function findTaggedServiceIds($name) + { + $this->usedTags[] = $name; + $tags = array(); + foreach ($this->getDefinitions() as $id => $definition) { + if ($definition->hasTag($name)) { + $tags[$id] = $definition->getTag($name); + } + } + + return $tags; + } + + /** + * Returns all tags the defined services use. + * + * @return array An array of tags + */ + public function findTags() + { + $tags = array(); + foreach ($this->getDefinitions() as $id => $definition) { + $tags = array_merge(array_keys($definition->getTags()), $tags); + } + + return array_unique($tags); + } + + /** + * Returns all tags not queried by findTaggedServiceIds. + * + * @return string[] An array of tags + */ + public function findUnusedTags() + { + return array_values(array_diff($this->findTags(), $this->usedTags)); + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * @return ExpressionFunctionProviderInterface[] + */ + public function getExpressionLanguageProviders() + { + return $this->expressionLanguageProviders; + } + + /** + * Returns the Service Conditionals. + * + * @param mixed $value An array of conditionals to return + * + * @return array An array of Service conditionals + */ + public static function getServiceConditionals($value) + { + $services = array(); + + if (\is_array($value)) { + foreach ($value as $v) { + $services = array_unique(array_merge($services, self::getServiceConditionals($v))); + } + } elseif ($value instanceof Reference && ContainerInterface::IGNORE_ON_INVALID_REFERENCE === $value->getInvalidBehavior()) { + $services[] = (string) $value; + } + + return $services; + } + + /** + * Retrieves the currently set proxy instantiator or instantiates one. + * + * @return InstantiatorInterface + */ + private function getProxyInstantiator() + { + if (!$this->proxyInstantiator) { + $this->proxyInstantiator = new RealServiceInstantiator(); + } + + return $this->proxyInstantiator; + } + + /** + * Synchronizes a service change. + * + * This method updates all services that depend on the given + * service by calling all methods referencing it. + * + * @param string $id A service id + * + * @deprecated since version 2.7, will be removed in 3.0. + */ + private function synchronize($id) + { + if ('request' !== $id) { + @trigger_error('The '.__METHOD__.' method is deprecated in version 2.7 and will be removed in version 3.0.', E_USER_DEPRECATED); + } + + foreach ($this->definitions as $definitionId => $definition) { + // only check initialized services + if (!$this->initialized($definitionId)) { + continue; + } + + foreach ($definition->getMethodCalls() as $call) { + foreach ($call[1] as $argument) { + if ($argument instanceof Reference && $id == (string) $argument) { + $this->callMethod($this->get($definitionId), $call, new \SplObjectStorage()); + } + } + } + } + } + + private function callMethod($service, $call, \SplObjectStorage $inlinedDefinitions) + { + $services = self::getServiceConditionals($call[1]); + + foreach ($services as $s) { + if (!$this->has($s)) { + return; + } + } + + \call_user_func_array(array($service, $call[0]), $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlinedDefinitions)); + } + + /** + * Shares a given service in the container. + * + * @param Definition $definition + * @param mixed $service + * @param string|null $id + * + * @throws InactiveScopeException + */ + private function shareService(Definition $definition, $service, $id, \SplObjectStorage $inlinedDefinitions) + { + if (!$definition->isShared() || self::SCOPE_PROTOTYPE === $scope = $definition->getScope(false)) { + return; + } + if (null === $id) { + $inlinedDefinitions[$definition] = $service; + } else { + if (self::SCOPE_CONTAINER !== $scope && !isset($this->scopedServices[$scope])) { + throw new InactiveScopeException($id, $scope); + } + + $this->services[$lowerId = strtolower($id)] = $service; + + if (self::SCOPE_CONTAINER !== $scope) { + $this->scopedServices[$scope][$lowerId] = $service; + } + } + } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } +} diff --git a/vendor/symfony/dependency-injection/ContainerInterface.php b/vendor/symfony/dependency-injection/ContainerInterface.php new file mode 100644 index 0000000..d9076eb --- /dev/null +++ b/vendor/symfony/dependency-injection/ContainerInterface.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException; + +/** + * ContainerInterface is the interface implemented by service container classes. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +interface ContainerInterface +{ + const EXCEPTION_ON_INVALID_REFERENCE = 1; + const NULL_ON_INVALID_REFERENCE = 2; + const IGNORE_ON_INVALID_REFERENCE = 3; + const SCOPE_CONTAINER = 'container'; + const SCOPE_PROTOTYPE = 'prototype'; + + /** + * Sets a service. + * + * Note: The $scope parameter is deprecated since version 2.8 and will be removed in 3.0. + * + * @param string $id The service identifier + * @param object $service The service instance + * @param string $scope The scope of the service + */ + public function set($id, $service, $scope = self::SCOPE_CONTAINER); + + /** + * Gets a service. + * + * @param string $id The service identifier + * @param int $invalidBehavior The behavior when the service does not exist + * + * @return object The associated service + * + * @throws ServiceCircularReferenceException When a circular reference is detected + * @throws ServiceNotFoundException When the service is not defined + * + * @see Reference + */ + public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); + + /** + * Returns true if the given service is defined. + * + * @param string $id The service identifier + * + * @return bool true if the service is defined, false otherwise + */ + public function has($id); + + /** + * Gets a parameter. + * + * @param string $name The parameter name + * + * @return mixed The parameter value + * + * @throws InvalidArgumentException if the parameter is not defined + */ + public function getParameter($name); + + /** + * Checks if a parameter exists. + * + * @param string $name The parameter name + * + * @return bool The presence of parameter in container + */ + public function hasParameter($name); + + /** + * Sets a parameter. + * + * @param string $name The parameter name + * @param mixed $value The parameter value + */ + public function setParameter($name, $value); + + /** + * Enters the given scope. + * + * @param string $name + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function enterScope($name); + + /** + * Leaves the current scope, and re-enters the parent scope. + * + * @param string $name + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function leaveScope($name); + + /** + * Adds a scope to the container. + * + * @param ScopeInterface $scope + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function addScope(ScopeInterface $scope); + + /** + * Whether this container has the given scope. + * + * @param string $name + * + * @return bool + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function hasScope($name); + + /** + * Determines whether the given scope is currently active. + * + * It does however not check if the scope actually exists. + * + * @param string $name + * + * @return bool + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function isScopeActive($name); +} diff --git a/vendor/symfony/dependency-injection/Definition.php b/vendor/symfony/dependency-injection/Definition.php new file mode 100644 index 0000000..d9c37ea --- /dev/null +++ b/vendor/symfony/dependency-injection/Definition.php @@ -0,0 +1,931 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; + +/** + * Definition represents a service definition. + * + * @author Fabien Potencier + */ +class Definition +{ + private $class; + private $file; + private $factory; + private $factoryClass; + private $factoryMethod; + private $factoryService; + private $shared = true; + private $deprecated = false; + private $deprecationTemplate; + private $scope = ContainerInterface::SCOPE_CONTAINER; + private $properties = array(); + private $calls = array(); + private $configurator; + private $tags = array(); + private $public = true; + private $synthetic = false; + private $abstract = false; + private $synchronized = false; + private $lazy = false; + private $decoratedService; + private $autowired = false; + private $autowiringTypes = array(); + + private static $defaultDeprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.'; + + protected $arguments; + + /** + * @param string|null $class The service class + * @param array $arguments An array of arguments to pass to the service constructor + */ + public function __construct($class = null, array $arguments = array()) + { + $this->class = $class; + $this->arguments = $arguments; + } + + /** + * Sets a factory. + * + * @param string|array $factory A PHP function or an array containing a class/Reference and a method to call + * + * @return $this + */ + public function setFactory($factory) + { + if (\is_string($factory) && false !== strpos($factory, '::')) { + $factory = explode('::', $factory, 2); + } + + $this->factory = $factory; + + return $this; + } + + /** + * Gets the factory. + * + * @return string|array|null The PHP function or an array containing a class/Reference and a method to call + */ + public function getFactory() + { + return $this->factory; + } + + /** + * Sets the name of the class that acts as a factory using the factory method, + * which will be invoked statically. + * + * @param string $factoryClass The factory class name + * + * @return $this + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + public function setFactoryClass($factoryClass) + { + @trigger_error(sprintf('%s(%s) is deprecated since Symfony 2.6 and will be removed in 3.0. Use Definition::setFactory() instead.', __METHOD__, $factoryClass), E_USER_DEPRECATED); + + $this->factoryClass = $factoryClass; + + return $this; + } + + /** + * Gets the factory class. + * + * @return string|null The factory class name + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + public function getFactoryClass($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return $this->factoryClass; + } + + /** + * Sets the factory method able to create an instance of this class. + * + * @param string $factoryMethod The factory method name + * + * @return $this + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + public function setFactoryMethod($factoryMethod) + { + @trigger_error(sprintf('%s(%s) is deprecated since Symfony 2.6 and will be removed in 3.0. Use Definition::setFactory() instead.', __METHOD__, $factoryMethod), E_USER_DEPRECATED); + + $this->factoryMethod = $factoryMethod; + + return $this; + } + + /** + * Sets the service that this service is decorating. + * + * @param string|null $id The decorated service id, use null to remove decoration + * @param string|null $renamedId The new decorated service id + * @param int $priority The priority of decoration + * + * @return $this + * + * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals + */ + public function setDecoratedService($id, $renamedId = null, $priority = 0) + { + if ($renamedId && $id === $renamedId) { + throw new \InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id)); + } + + if (null === $id) { + $this->decoratedService = null; + } else { + $this->decoratedService = array($id, $renamedId, (int) $priority); + } + + return $this; + } + + /** + * Gets the service that this service is decorating. + * + * @return array|null An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated + */ + public function getDecoratedService() + { + return $this->decoratedService; + } + + /** + * Gets the factory method. + * + * @return string|null The factory method name + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + public function getFactoryMethod($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return $this->factoryMethod; + } + + /** + * Sets the name of the service that acts as a factory using the factory method. + * + * @param string $factoryService The factory service id + * + * @return $this + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + public function setFactoryService($factoryService, $triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error(sprintf('%s(%s) is deprecated since Symfony 2.6 and will be removed in 3.0. Use Definition::setFactory() instead.', __METHOD__, $factoryService), E_USER_DEPRECATED); + } + + $this->factoryService = $factoryService; + + return $this; + } + + /** + * Gets the factory service id. + * + * @return string|null The factory service id + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + public function getFactoryService($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return $this->factoryService; + } + + /** + * Sets the service class. + * + * @param string $class The service class + * + * @return $this + */ + public function setClass($class) + { + $this->class = $class; + + return $this; + } + + /** + * Gets the service class. + * + * @return string|null The service class + */ + public function getClass() + { + return $this->class; + } + + /** + * Sets the arguments to pass to the service constructor/factory method. + * + * @return $this + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * Sets the properties to define when creating the service. + * + * @return $this + */ + public function setProperties(array $properties) + { + $this->properties = $properties; + + return $this; + } + + /** + * Gets the properties to define when creating the service. + * + * @return array + */ + public function getProperties() + { + return $this->properties; + } + + /** + * Sets a specific property. + * + * @param string $name + * @param mixed $value + * + * @return $this + */ + public function setProperty($name, $value) + { + $this->properties[$name] = $value; + + return $this; + } + + /** + * Adds an argument to pass to the service constructor/factory method. + * + * @param mixed $argument An argument + * + * @return $this + */ + public function addArgument($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * Replaces a specific argument. + * + * @param int $index + * @param mixed $argument + * + * @return $this + * + * @throws OutOfBoundsException When the replaced argument does not exist + */ + public function replaceArgument($index, $argument) + { + if (0 === \count($this->arguments)) { + throw new OutOfBoundsException('Cannot replace arguments if none have been configured yet.'); + } + + if ($index < 0 || $index > \count($this->arguments) - 1) { + throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d].', $index, \count($this->arguments) - 1)); + } + + $this->arguments[$index] = $argument; + + return $this; + } + + /** + * Gets the arguments to pass to the service constructor/factory method. + * + * @return array The array of arguments + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Gets an argument to pass to the service constructor/factory method. + * + * @param int $index + * + * @return mixed The argument value + * + * @throws OutOfBoundsException When the argument does not exist + */ + public function getArgument($index) + { + if ($index < 0 || $index > \count($this->arguments) - 1) { + throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d].', $index, \count($this->arguments) - 1)); + } + + return $this->arguments[$index]; + } + + /** + * Sets the methods to call after service initialization. + * + * @return $this + */ + public function setMethodCalls(array $calls = array()) + { + $this->calls = array(); + foreach ($calls as $call) { + $this->addMethodCall($call[0], $call[1]); + } + + return $this; + } + + /** + * Adds a method to call after service initialization. + * + * @param string $method The method name to call + * @param array $arguments An array of arguments to pass to the method call + * + * @return $this + * + * @throws InvalidArgumentException on empty $method param + */ + public function addMethodCall($method, array $arguments = array()) + { + if (empty($method)) { + throw new InvalidArgumentException('Method name cannot be empty.'); + } + $this->calls[] = array($method, $arguments); + + return $this; + } + + /** + * Removes a method to call after service initialization. + * + * @param string $method The method name to remove + * + * @return $this + */ + public function removeMethodCall($method) + { + foreach ($this->calls as $i => $call) { + if ($call[0] === $method) { + unset($this->calls[$i]); + break; + } + } + + return $this; + } + + /** + * Check if the current definition has a given method to call after service initialization. + * + * @param string $method The method name to search for + * + * @return bool + */ + public function hasMethodCall($method) + { + foreach ($this->calls as $call) { + if ($call[0] === $method) { + return true; + } + } + + return false; + } + + /** + * Gets the methods to call after service initialization. + * + * @return array An array of method calls + */ + public function getMethodCalls() + { + return $this->calls; + } + + /** + * Sets tags for this definition. + * + * @return $this + */ + public function setTags(array $tags) + { + $this->tags = $tags; + + return $this; + } + + /** + * Returns all tags. + * + * @return array An array of tags + */ + public function getTags() + { + return $this->tags; + } + + /** + * Gets a tag by name. + * + * @param string $name The tag name + * + * @return array An array of attributes + */ + public function getTag($name) + { + return isset($this->tags[$name]) ? $this->tags[$name] : array(); + } + + /** + * Adds a tag for this definition. + * + * @param string $name The tag name + * @param array $attributes An array of attributes + * + * @return $this + */ + public function addTag($name, array $attributes = array()) + { + $this->tags[$name][] = $attributes; + + return $this; + } + + /** + * Whether this definition has a tag with the given name. + * + * @param string $name + * + * @return bool + */ + public function hasTag($name) + { + return isset($this->tags[$name]); + } + + /** + * Clears all tags for a given name. + * + * @param string $name The tag name + * + * @return $this + */ + public function clearTag($name) + { + unset($this->tags[$name]); + + return $this; + } + + /** + * Clears the tags for this definition. + * + * @return $this + */ + public function clearTags() + { + $this->tags = array(); + + return $this; + } + + /** + * Sets a file to require before creating the service. + * + * @param string $file A full pathname to include + * + * @return $this + */ + public function setFile($file) + { + $this->file = $file; + + return $this; + } + + /** + * Gets the file to require before creating the service. + * + * @return string|null The full pathname to include + */ + public function getFile() + { + return $this->file; + } + + /** + * Sets if the service must be shared or not. + * + * @param bool $shared Whether the service must be shared or not + * + * @return $this + */ + public function setShared($shared) + { + $this->shared = (bool) $shared; + + return $this; + } + + /** + * Whether this service is shared. + * + * @return bool + */ + public function isShared() + { + return $this->shared; + } + + /** + * Sets the scope of the service. + * + * @param string $scope Whether the service must be shared or not + * + * @return $this + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function setScope($scope, $triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + if (ContainerInterface::SCOPE_PROTOTYPE === $scope) { + $this->setShared(false); + } + + $this->scope = $scope; + + return $this; + } + + /** + * Returns the scope of the service. + * + * @return string + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function getScope($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return $this->scope; + } + + /** + * Sets the visibility of this service. + * + * @param bool $boolean + * + * @return $this + */ + public function setPublic($boolean) + { + $this->public = (bool) $boolean; + + return $this; + } + + /** + * Whether this service is public facing. + * + * @return bool + */ + public function isPublic() + { + return $this->public; + } + + /** + * Sets the synchronized flag of this service. + * + * @param bool $boolean + * + * @return $this + * + * @deprecated since version 2.7, will be removed in 3.0. + */ + public function setSynchronized($boolean, $triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + $this->synchronized = (bool) $boolean; + + return $this; + } + + /** + * Whether this service is synchronized. + * + * @return bool + * + * @deprecated since version 2.7, will be removed in 3.0. + */ + public function isSynchronized($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return $this->synchronized; + } + + /** + * Sets the lazy flag of this service. + * + * @param bool $lazy + * + * @return $this + */ + public function setLazy($lazy) + { + $this->lazy = (bool) $lazy; + + return $this; + } + + /** + * Whether this service is lazy. + * + * @return bool + */ + public function isLazy() + { + return $this->lazy; + } + + /** + * Sets whether this definition is synthetic, that is not constructed by the + * container, but dynamically injected. + * + * @param bool $boolean + * + * @return $this + */ + public function setSynthetic($boolean) + { + $this->synthetic = (bool) $boolean; + + return $this; + } + + /** + * Whether this definition is synthetic, that is not constructed by the + * container, but dynamically injected. + * + * @return bool + */ + public function isSynthetic() + { + return $this->synthetic; + } + + /** + * Whether this definition is abstract, that means it merely serves as a + * template for other definitions. + * + * @param bool $boolean + * + * @return $this + */ + public function setAbstract($boolean) + { + $this->abstract = (bool) $boolean; + + return $this; + } + + /** + * Whether this definition is abstract, that means it merely serves as a + * template for other definitions. + * + * @return bool + */ + public function isAbstract() + { + return $this->abstract; + } + + /** + * Whether this definition is deprecated, that means it should not be called + * anymore. + * + * @param bool $status + * @param string $template Template message to use if the definition is deprecated + * + * @return $this + * + * @throws InvalidArgumentException when the message template is invalid + */ + public function setDeprecated($status = true, $template = null) + { + if (null !== $template) { + if (preg_match('#[\r\n]|\*/#', $template)) { + throw new InvalidArgumentException('Invalid characters found in deprecation template.'); + } + + if (false === strpos($template, '%service_id%')) { + throw new InvalidArgumentException('The deprecation template must contain the "%service_id%" placeholder.'); + } + + $this->deprecationTemplate = $template; + } + + $this->deprecated = (bool) $status; + + return $this; + } + + /** + * Whether this definition is deprecated, that means it should not be called + * anymore. + * + * @return bool + */ + public function isDeprecated() + { + return $this->deprecated; + } + + /** + * Message to use if this definition is deprecated. + * + * @param string $id Service id relying on this definition + * + * @return string + */ + public function getDeprecationMessage($id) + { + return str_replace('%service_id%', $id, $this->deprecationTemplate ?: self::$defaultDeprecationTemplate); + } + + /** + * Sets a configurator to call after the service is fully initialized. + * + * @param callable $callable A PHP callable + * + * @return $this + */ + public function setConfigurator($callable) + { + $this->configurator = $callable; + + return $this; + } + + /** + * Gets the configurator to call after the service is fully initialized. + * + * @return callable|null The PHP callable to call + */ + public function getConfigurator() + { + return $this->configurator; + } + + /** + * Sets types that will default to this definition. + * + * @param string[] $types + * + * @return $this + */ + public function setAutowiringTypes(array $types) + { + $this->autowiringTypes = array(); + + foreach ($types as $type) { + $this->autowiringTypes[$type] = true; + } + + return $this; + } + + /** + * Is the definition autowired? + * + * @return bool + */ + public function isAutowired() + { + return $this->autowired; + } + + /** + * Enables/disables autowiring. + * + * @param bool $autowired + * + * @return $this + */ + public function setAutowired($autowired) + { + $this->autowired = $autowired; + + return $this; + } + + /** + * Gets autowiring types that will default to this definition. + * + * @return string[] + */ + public function getAutowiringTypes() + { + return array_keys($this->autowiringTypes); + } + + /** + * Adds a type that will default to this definition. + * + * @param string $type + * + * @return $this + */ + public function addAutowiringType($type) + { + $this->autowiringTypes[$type] = true; + + return $this; + } + + /** + * Removes a type. + * + * @param string $type + * + * @return $this + */ + public function removeAutowiringType($type) + { + unset($this->autowiringTypes[$type]); + + return $this; + } + + /** + * Will this definition default for the given type? + * + * @param string $type + * + * @return bool + */ + public function hasAutowiringType($type) + { + return isset($this->autowiringTypes[$type]); + } +} diff --git a/vendor/symfony/dependency-injection/DefinitionDecorator.php b/vendor/symfony/dependency-injection/DefinitionDecorator.php new file mode 100644 index 0000000..44e9c0f --- /dev/null +++ b/vendor/symfony/dependency-injection/DefinitionDecorator.php @@ -0,0 +1,229 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException; + +/** + * This definition decorates another definition. + * + * @author Johannes M. Schmitt + */ +class DefinitionDecorator extends Definition +{ + private $parent; + private $changes = array(); + + /** + * @param string $parent The id of Definition instance to decorate + */ + public function __construct($parent) + { + parent::__construct(); + + $this->parent = $parent; + } + + /** + * Returns the Definition being decorated. + * + * @return string + */ + public function getParent() + { + return $this->parent; + } + + /** + * Returns all changes tracked for the Definition object. + * + * @return array An array of changes for this Definition + */ + public function getChanges() + { + return $this->changes; + } + + /** + * {@inheritdoc} + */ + public function setClass($class) + { + $this->changes['class'] = true; + + return parent::setClass($class); + } + + /** + * {@inheritdoc} + */ + public function setFactory($callable) + { + $this->changes['factory'] = true; + + return parent::setFactory($callable); + } + + /** + * {@inheritdoc} + */ + public function setFactoryClass($class) + { + $this->changes['factory_class'] = true; + + return parent::setFactoryClass($class); + } + + /** + * {@inheritdoc} + */ + public function setFactoryMethod($method) + { + $this->changes['factory_method'] = true; + + return parent::setFactoryMethod($method); + } + + /** + * {@inheritdoc} + */ + public function setFactoryService($service, $triggerDeprecationError = true) + { + $this->changes['factory_service'] = true; + + return parent::setFactoryService($service, $triggerDeprecationError); + } + + /** + * {@inheritdoc} + */ + public function setConfigurator($callable) + { + $this->changes['configurator'] = true; + + return parent::setConfigurator($callable); + } + + /** + * {@inheritdoc} + */ + public function setFile($file) + { + $this->changes['file'] = true; + + return parent::setFile($file); + } + + /** + * {@inheritdoc} + */ + public function setPublic($boolean) + { + $this->changes['public'] = true; + + return parent::setPublic($boolean); + } + + /** + * {@inheritdoc} + */ + public function setLazy($boolean) + { + $this->changes['lazy'] = true; + + return parent::setLazy($boolean); + } + + /** + * {@inheritdoc} + */ + public function setDecoratedService($id, $renamedId = null, $priority = 0) + { + $this->changes['decorated_service'] = true; + + return parent::setDecoratedService($id, $renamedId, $priority); + } + + /** + * {@inheritdoc} + */ + public function setDeprecated($boolean = true, $template = null) + { + $this->changes['deprecated'] = true; + + return parent::setDeprecated($boolean, $template); + } + + /** + * {@inheritdoc} + */ + public function setAutowired($autowired) + { + $this->changes['autowire'] = true; + + return parent::setAutowired($autowired); + } + + /** + * Gets an argument to pass to the service constructor/factory method. + * + * If replaceArgument() has been used to replace an argument, this method + * will return the replacement value. + * + * @param int $index + * + * @return mixed The argument value + * + * @throws OutOfBoundsException When the argument does not exist + */ + public function getArgument($index) + { + if (array_key_exists('index_'.$index, $this->arguments)) { + return $this->arguments['index_'.$index]; + } + + $lastIndex = \count(array_filter(array_keys($this->arguments), 'is_int')) - 1; + + if ($index < 0 || $index > $lastIndex) { + throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d].', $index, $lastIndex)); + } + + return $this->arguments[$index]; + } + + /** + * You should always use this method when overwriting existing arguments + * of the parent definition. + * + * If you directly call setArguments() keep in mind that you must follow + * certain conventions when you want to overwrite the arguments of the + * parent definition, otherwise your arguments will only be appended. + * + * @param int $index + * @param mixed $value + * + * @return $this + * + * @throws InvalidArgumentException when $index isn't an integer + */ + public function replaceArgument($index, $value) + { + if (!\is_int($index)) { + throw new InvalidArgumentException('$index must be an integer.'); + } + + $this->arguments['index_'.$index] = $value; + + return $this; + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/Dumper.php b/vendor/symfony/dependency-injection/Dumper/Dumper.php new file mode 100644 index 0000000..e7407b0 --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/Dumper.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Dumper is the abstract class for all built-in dumpers. + * + * @author Fabien Potencier + */ +abstract class Dumper implements DumperInterface +{ + protected $container; + + public function __construct(ContainerBuilder $container) + { + $this->container = $container; + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/DumperInterface.php b/vendor/symfony/dependency-injection/Dumper/DumperInterface.php new file mode 100644 index 0000000..dd001e4 --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/DumperInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +/** + * DumperInterface is the interface implemented by service container dumper classes. + * + * @author Fabien Potencier + */ +interface DumperInterface +{ + /** + * Dumps the service container. + * + * @param array $options An array of options + * + * @return string The representation of the service container + */ + public function dump(array $options = array()); +} diff --git a/vendor/symfony/dependency-injection/Dumper/GraphvizDumper.php b/vendor/symfony/dependency-injection/Dumper/GraphvizDumper.php new file mode 100644 index 0000000..dfb0dee --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/GraphvizDumper.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Scope; + +/** + * GraphvizDumper dumps a service container as a graphviz file. + * + * You can convert the generated dot file with the dot utility (http://www.graphviz.org/): + * + * dot -Tpng container.dot > foo.png + * + * @author Fabien Potencier + */ +class GraphvizDumper extends Dumper +{ + private $nodes; + private $edges; + private $options = array( + 'graph' => array('ratio' => 'compress'), + 'node' => array('fontsize' => 11, 'fontname' => 'Arial', 'shape' => 'record'), + 'edge' => array('fontsize' => 9, 'fontname' => 'Arial', 'color' => 'grey', 'arrowhead' => 'open', 'arrowsize' => 0.5), + 'node.instance' => array('fillcolor' => '#9999ff', 'style' => 'filled'), + 'node.definition' => array('fillcolor' => '#eeeeee'), + 'node.missing' => array('fillcolor' => '#ff9999', 'style' => 'filled'), + ); + + /** + * Dumps the service container as a graphviz graph. + * + * Available options: + * + * * graph: The default options for the whole graph + * * node: The default options for nodes + * * edge: The default options for edges + * * node.instance: The default options for services that are defined directly by object instances + * * node.definition: The default options for services that are defined via service definition instances + * * node.missing: The default options for missing services + * + * @return string The dot representation of the service container + */ + public function dump(array $options = array()) + { + foreach (array('graph', 'node', 'edge', 'node.instance', 'node.definition', 'node.missing') as $key) { + if (isset($options[$key])) { + $this->options[$key] = array_merge($this->options[$key], $options[$key]); + } + } + + $this->nodes = $this->findNodes(); + + $this->edges = array(); + foreach ($this->container->getDefinitions() as $id => $definition) { + $this->edges[$id] = array_merge( + $this->findEdges($id, $definition->getArguments(), true, ''), + $this->findEdges($id, $definition->getProperties(), false, '') + ); + + foreach ($definition->getMethodCalls() as $call) { + $this->edges[$id] = array_merge( + $this->edges[$id], + $this->findEdges($id, $call[1], false, $call[0].'()') + ); + } + } + + return $this->startDot().$this->addNodes().$this->addEdges().$this->endDot(); + } + + /** + * Returns all nodes. + * + * @return string A string representation of all nodes + */ + private function addNodes() + { + $code = ''; + foreach ($this->nodes as $id => $node) { + $aliases = $this->getAliases($id); + + $code .= sprintf(" node_%s [label=\"%s\\n%s\\n\", shape=%s%s];\n", $this->dotize($id), $id.($aliases ? ' ('.implode(', ', $aliases).')' : ''), $node['class'], $this->options['node']['shape'], $this->addAttributes($node['attributes'])); + } + + return $code; + } + + /** + * Returns all edges. + * + * @return string A string representation of all edges + */ + private function addEdges() + { + $code = ''; + foreach ($this->edges as $id => $edges) { + foreach ($edges as $edge) { + $code .= sprintf(" node_%s -> node_%s [label=\"%s\" style=\"%s\"];\n", $this->dotize($id), $this->dotize($edge['to']), $edge['name'], $edge['required'] ? 'filled' : 'dashed'); + } + } + + return $code; + } + + /** + * Finds all edges belonging to a specific service id. + * + * @param string $id The service id used to find edges + * @param array $arguments An array of arguments + * @param bool $required + * @param string $name + * + * @return array An array of edges + */ + private function findEdges($id, array $arguments, $required, $name) + { + $edges = array(); + foreach ($arguments as $argument) { + if ($argument instanceof Parameter) { + $argument = $this->container->hasParameter($argument) ? $this->container->getParameter($argument) : null; + } elseif (\is_string($argument) && preg_match('/^%([^%]+)%$/', $argument, $match)) { + $argument = $this->container->hasParameter($match[1]) ? $this->container->getParameter($match[1]) : null; + } + + if ($argument instanceof Reference) { + if (!$this->container->has((string) $argument)) { + $this->nodes[(string) $argument] = array('name' => $name, 'required' => $required, 'class' => '', 'attributes' => $this->options['node.missing']); + } + + $edges[] = array('name' => $name, 'required' => $required, 'to' => $argument); + } elseif (\is_array($argument)) { + $edges = array_merge($edges, $this->findEdges($id, $argument, $required, $name)); + } + } + + return $edges; + } + + /** + * Finds all nodes. + * + * @return array An array of all nodes + */ + private function findNodes() + { + $nodes = array(); + + $container = $this->cloneContainer(); + + foreach ($container->getDefinitions() as $id => $definition) { + $class = $definition->getClass(); + + if ('\\' === substr($class, 0, 1)) { + $class = substr($class, 1); + } + + try { + $class = $this->container->getParameterBag()->resolveValue($class); + } catch (ParameterNotFoundException $e) { + } + + $nodes[$id] = array('class' => str_replace('\\', '\\\\', $class), 'attributes' => array_merge($this->options['node.definition'], array('style' => $definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE !== $definition->getScope(false) ? 'filled' : 'dotted'))); + $container->setDefinition($id, new Definition('stdClass')); + } + + foreach ($container->getServiceIds() as $id) { + $service = $container->get($id); + + if (array_key_exists($id, $container->getAliases())) { + continue; + } + + if (!$container->hasDefinition($id)) { + $class = ('service_container' === $id) ? \get_class($this->container) : \get_class($service); + $nodes[$id] = array('class' => str_replace('\\', '\\\\', $class), 'attributes' => $this->options['node.instance']); + } + } + + return $nodes; + } + + private function cloneContainer() + { + $parameterBag = new ParameterBag($this->container->getParameterBag()->all()); + + $container = new ContainerBuilder($parameterBag); + $container->setDefinitions($this->container->getDefinitions()); + $container->setAliases($this->container->getAliases()); + $container->setResources($this->container->getResources()); + foreach ($this->container->getScopes(false) as $scope => $parentScope) { + $container->addScope(new Scope($scope, $parentScope)); + } + foreach ($this->container->getExtensions() as $extension) { + $container->registerExtension($extension); + } + + return $container; + } + + /** + * Returns the start dot. + * + * @return string The string representation of a start dot + */ + private function startDot() + { + return sprintf("digraph sc {\n %s\n node [%s];\n edge [%s];\n\n", + $this->addOptions($this->options['graph']), + $this->addOptions($this->options['node']), + $this->addOptions($this->options['edge']) + ); + } + + /** + * Returns the end dot. + * + * @return string + */ + private function endDot() + { + return "}\n"; + } + + /** + * Adds attributes. + * + * @param array $attributes An array of attributes + * + * @return string A comma separated list of attributes + */ + private function addAttributes(array $attributes) + { + $code = array(); + foreach ($attributes as $k => $v) { + $code[] = sprintf('%s="%s"', $k, $v); + } + + return $code ? ', '.implode(', ', $code) : ''; + } + + /** + * Adds options. + * + * @param array $options An array of options + * + * @return string A space separated list of options + */ + private function addOptions(array $options) + { + $code = array(); + foreach ($options as $k => $v) { + $code[] = sprintf('%s="%s"', $k, $v); + } + + return implode(' ', $code); + } + + /** + * Dotizes an identifier. + * + * @param string $id The identifier to dotize + * + * @return string A dotized string + */ + private function dotize($id) + { + return strtolower(preg_replace('/\W/i', '_', $id)); + } + + /** + * Compiles an array of aliases for a specified service id. + * + * @param string $id A service id + * + * @return array An array of aliases + */ + private function getAliases($id) + { + $aliases = array(); + foreach ($this->container->getAliases() as $alias => $origin) { + if ($id == $origin) { + $aliases[] = $alias; + } + } + + return $aliases; + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/PhpDumper.php b/vendor/symfony/dependency-injection/Dumper/PhpDumper.php new file mode 100644 index 0000000..2e2de19 --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/PhpDumper.php @@ -0,0 +1,1583 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\Variable; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\HttpKernel\Kernel; + +/** + * PhpDumper dumps a service container as a PHP class. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpDumper extends Dumper +{ + /** + * Characters that might appear in the generated variable name as first character. + */ + const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz'; + + /** + * Characters that might appear in the generated variable name as any but the first character. + */ + const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; + + private $inlinedDefinitions; + private $definitionVariables; + private $referenceVariables; + private $variableCount; + private $reservedVariables = array('instance', 'class'); + private $expressionLanguage; + private $targetDirRegex; + private $targetDirMaxMatches; + private $docStar; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * @var ProxyDumper + */ + private $proxyDumper; + + /** + * {@inheritdoc} + */ + public function __construct(ContainerBuilder $container) + { + parent::__construct($container); + + $this->inlinedDefinitions = new \SplObjectStorage(); + } + + /** + * Sets the dumper to be used when dumping proxies in the generated container. + */ + public function setProxyDumper(ProxyDumper $proxyDumper) + { + $this->proxyDumper = $proxyDumper; + } + + /** + * Dumps the service container as a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * * namespace: The class namespace + * + * @return string A PHP class representing of the service container + */ + public function dump(array $options = array()) + { + $this->targetDirRegex = null; + $options = array_merge(array( + 'class' => 'ProjectServiceContainer', + 'base_class' => 'Container', + 'namespace' => '', + 'debug' => true, + ), $options); + $this->docStar = $options['debug'] ? '*' : ''; + + if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) { + // Build a regexp where the first root dirs are mandatory, + // but every other sub-dir is optional up to the full path in $dir + // Mandate at least 2 root dirs and not more that 5 optional dirs. + + $dir = explode(\DIRECTORY_SEPARATOR, realpath($dir)); + $i = \count($dir); + + if (3 <= $i) { + $regex = ''; + $lastOptionalDir = $i > 8 ? $i - 5 : 3; + $this->targetDirMaxMatches = $i - $lastOptionalDir; + + while (--$i >= $lastOptionalDir) { + $regex = sprintf('(%s%s)?', preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#'), $regex); + } + + do { + $regex = preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#').$regex; + } while (0 < --$i); + + $this->targetDirRegex = '#'.preg_quote($dir[0], '#').$regex.'#'; + } + } + + $code = $this->startClass($options['class'], $options['base_class'], $options['namespace']); + + if ($this->container->isFrozen()) { + $code .= $this->addFrozenConstructor(); + $code .= $this->addFrozenCompile(); + $code .= $this->addIsFrozenMethod(); + } else { + $code .= $this->addConstructor(); + } + + $code .= + $this->addServices(). + $this->addDefaultParametersMethod(). + $this->endClass(). + $this->addProxyClasses() + ; + $this->targetDirRegex = null; + + return $code; + } + + /** + * Retrieves the currently set proxy dumper or instantiates one. + * + * @return ProxyDumper + */ + private function getProxyDumper() + { + if (!$this->proxyDumper) { + $this->proxyDumper = new NullDumper(); + } + + return $this->proxyDumper; + } + + /** + * Generates Service local temp variables. + * + * @param string $cId + * @param string $definition + * + * @return string + */ + private function addServiceLocalTempVariables($cId, $definition) + { + static $template = " \$%s = %s;\n"; + + $localDefinitions = array_merge( + array($definition), + $this->getInlinedDefinitions($definition) + ); + + $calls = $behavior = array(); + foreach ($localDefinitions as $iDefinition) { + $this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior); + $this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior); + $this->getServiceCallsFromArguments($iDefinition->getProperties(), $calls, $behavior); + $this->getServiceCallsFromArguments(array($iDefinition->getConfigurator()), $calls, $behavior); + $this->getServiceCallsFromArguments(array($iDefinition->getFactory()), $calls, $behavior); + } + + $code = ''; + foreach ($calls as $id => $callCount) { + if ('service_container' === $id || $id === $cId) { + continue; + } + + if ($callCount > 1) { + $name = $this->getNextVariableName(); + $this->referenceVariables[$id] = new Variable($name); + + if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id]) { + $code .= sprintf($template, $name, $this->getServiceCall($id)); + } else { + $code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, ContainerInterface::NULL_ON_INVALID_REFERENCE))); + } + } + } + + if ('' !== $code) { + $code .= "\n"; + } + + return $code; + } + + /** + * Generates code for the proxies to be attached after the container class. + * + * @return string + */ + private function addProxyClasses() + { + /* @var $definitions Definition[] */ + $definitions = array_filter( + $this->container->getDefinitions(), + array($this->getProxyDumper(), 'isProxyCandidate') + ); + $code = ''; + $strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments'); + + foreach ($definitions as $definition) { + if ("\n" === $proxyCode = "\n".$this->getProxyDumper()->getProxyCode($definition)) { + continue; + } + if ($strip) { + $proxyCode = "getFile()) { + $code .= sprintf($template, $this->dumpValue($file)); + } + + foreach ($this->getInlinedDefinitions($definition) as $definition) { + if (null !== $file = $definition->getFile()) { + $code .= sprintf($template, $this->dumpValue($file)); + } + } + + if ('' !== $code) { + $code .= "\n"; + } + + return $code; + } + + /** + * Generates the inline definition of a service. + * + * @param string $id + * @param Definition $definition + * + * @return string + * + * @throws RuntimeException When the factory definition is incomplete + * @throws ServiceCircularReferenceException When a circular reference is detected + */ + private function addServiceInlinedDefinitions($id, Definition $definition) + { + $code = ''; + $variableMap = $this->definitionVariables; + $nbOccurrences = new \SplObjectStorage(); + $processed = new \SplObjectStorage(); + $inlinedDefinitions = $this->getInlinedDefinitions($definition); + + foreach ($inlinedDefinitions as $definition) { + if (false === $nbOccurrences->contains($definition)) { + $nbOccurrences->offsetSet($definition, 1); + } else { + $i = $nbOccurrences->offsetGet($definition); + $nbOccurrences->offsetSet($definition, $i + 1); + } + } + + foreach ($inlinedDefinitions as $sDefinition) { + if ($processed->contains($sDefinition)) { + continue; + } + $processed->offsetSet($sDefinition); + + $class = $this->dumpValue($sDefinition->getClass()); + if ($nbOccurrences->offsetGet($sDefinition) > 1 || $sDefinition->getMethodCalls() || $sDefinition->getProperties() || null !== $sDefinition->getConfigurator() || false !== strpos($class, '$')) { + $name = $this->getNextVariableName(); + $variableMap->offsetSet($sDefinition, new Variable($name)); + + // a construct like: + // $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a); + // this is an indication for a wrong implementation, you can circumvent this problem + // by setting up your service structure like this: + // $b = new ServiceB(); + // $a = new ServiceA(ServiceB $b); + // $b->setServiceA(ServiceA $a); + if ($this->hasReference($id, $sDefinition->getArguments())) { + throw new ServiceCircularReferenceException($id, array($id)); + } + + $code .= $this->addNewInstance($id, $sDefinition, '$'.$name, ' = '); + + if (!$this->hasReference($id, $sDefinition->getMethodCalls(), true) && !$this->hasReference($id, $sDefinition->getProperties(), true)) { + $code .= $this->addServiceProperties($sDefinition, $name); + $code .= $this->addServiceMethodCalls($sDefinition, $name); + $code .= $this->addServiceConfigurator($sDefinition, $name); + } + + $code .= "\n"; + } + } + + return $code; + } + + /** + * Adds the service return statement. + * + * @param string $id Service id + * @param Definition $definition + * + * @return string + */ + private function addServiceReturn($id, Definition $definition) + { + if ($this->isSimpleInstance($id, $definition)) { + return " }\n"; + } + + return "\n return \$instance;\n }\n"; + } + + /** + * Generates the service instance. + * + * @param string $id + * @param Definition $definition + * + * @return string + * + * @throws InvalidArgumentException + * @throws RuntimeException + */ + private function addServiceInstance($id, Definition $definition) + { + $class = $this->dumpValue($definition->getClass()); + + if (0 === strpos($class, "'") && !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); + } + + $simple = $this->isSimpleInstance($id, $definition); + $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); + $instantiation = ''; + + if (!$isProxyCandidate && $definition->isShared() && ContainerInterface::SCOPE_CONTAINER === $definition->getScope(false)) { + $instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance'); + } elseif (!$isProxyCandidate && $definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope(false)) { + $instantiation = "\$this->services['$id'] = \$this->scopedServices['$scope']['$id'] = ".($simple ? '' : '$instance'); + } elseif (!$simple) { + $instantiation = '$instance'; + } + + $return = ''; + if ($simple) { + $return = 'return '; + } else { + $instantiation .= ' = '; + } + + $code = $this->addNewInstance($id, $definition, $return, $instantiation); + + if (!$simple) { + $code .= "\n"; + } + + return $code; + } + + /** + * Checks if the definition is a simple instance. + * + * @param string $id + * @param Definition $definition + * + * @return bool + */ + private function isSimpleInstance($id, Definition $definition) + { + foreach (array_merge(array($definition), $this->getInlinedDefinitions($definition)) as $sDefinition) { + if ($definition !== $sDefinition && !$this->hasReference($id, $sDefinition->getMethodCalls())) { + continue; + } + + if ($sDefinition->getMethodCalls() || $sDefinition->getProperties() || $sDefinition->getConfigurator()) { + return false; + } + } + + return true; + } + + /** + * Adds method calls to a service definition. + * + * @param Definition $definition + * @param string $variableName + * + * @return string + */ + private function addServiceMethodCalls(Definition $definition, $variableName = 'instance') + { + $calls = ''; + foreach ($definition->getMethodCalls() as $call) { + $arguments = array(); + foreach ($call[1] as $value) { + $arguments[] = $this->dumpValue($value); + } + + $calls .= $this->wrapServiceConditionals($call[1], sprintf(" \$%s->%s(%s);\n", $variableName, $call[0], implode(', ', $arguments))); + } + + return $calls; + } + + private function addServiceProperties(Definition $definition, $variableName = 'instance') + { + $code = ''; + foreach ($definition->getProperties() as $name => $value) { + $code .= sprintf(" \$%s->%s = %s;\n", $variableName, $name, $this->dumpValue($value)); + } + + return $code; + } + + /** + * Generates the inline definition setup. + * + * @param string $id + * @param Definition $definition + * + * @return string + * + * @throws ServiceCircularReferenceException when the container contains a circular reference + */ + private function addServiceInlinedDefinitionsSetup($id, Definition $definition) + { + $this->referenceVariables[$id] = new Variable('instance'); + + $code = ''; + $processed = new \SplObjectStorage(); + foreach ($this->getInlinedDefinitions($definition) as $iDefinition) { + if ($processed->contains($iDefinition)) { + continue; + } + $processed->offsetSet($iDefinition); + + if (!$this->hasReference($id, $iDefinition->getMethodCalls(), true) && !$this->hasReference($id, $iDefinition->getProperties(), true)) { + continue; + } + + // if the instance is simple, the return statement has already been generated + // so, the only possible way to get there is because of a circular reference + if ($this->isSimpleInstance($id, $definition)) { + throw new ServiceCircularReferenceException($id, array($id)); + } + + $name = (string) $this->definitionVariables->offsetGet($iDefinition); + $code .= $this->addServiceProperties($iDefinition, $name); + $code .= $this->addServiceMethodCalls($iDefinition, $name); + $code .= $this->addServiceConfigurator($iDefinition, $name); + } + + if ('' !== $code) { + $code .= "\n"; + } + + return $code; + } + + /** + * Adds configurator definition. + * + * @param Definition $definition + * @param string $variableName + * + * @return string + */ + private function addServiceConfigurator(Definition $definition, $variableName = 'instance') + { + if (!$callable = $definition->getConfigurator()) { + return ''; + } + + if (\is_array($callable)) { + if ($callable[0] instanceof Reference + || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { + return sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + } + + $class = $this->dumpValue($callable[0]); + // If the class is a string we can optimize call_user_func away + if (0 === strpos($class, "'")) { + return sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName); + } + + return sprintf(" call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + } + + return sprintf(" %s(\$%s);\n", $callable, $variableName); + } + + /** + * Adds a service. + * + * @param string $id + * @param Definition $definition + * + * @return string + */ + private function addService($id, Definition $definition) + { + $this->definitionVariables = new \SplObjectStorage(); + $this->referenceVariables = array(); + $this->variableCount = 0; + + $return = array(); + + if ($definition->isSynthetic()) { + $return[] = '@throws RuntimeException always since this service is expected to be injected dynamically'; + } elseif ($class = $definition->getClass()) { + $return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); + } elseif ($definition->getFactory()) { + $factory = $definition->getFactory(); + if (\is_string($factory)) { + $return[] = sprintf('@return object An instance returned by %s()', $factory); + } elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { + if (\is_string($factory[0]) || $factory[0] instanceof Reference) { + $return[] = sprintf('@return object An instance returned by %s::%s()', (string) $factory[0], $factory[1]); + } elseif ($factory[0] instanceof Definition) { + $return[] = sprintf('@return object An instance returned by %s::%s()', $factory[0]->getClass(), $factory[1]); + } + } + } elseif ($definition->getFactoryClass(false)) { + $return[] = sprintf('@return object An instance returned by %s::%s()', $definition->getFactoryClass(false), $definition->getFactoryMethod(false)); + } elseif ($definition->getFactoryService(false)) { + $return[] = sprintf('@return object An instance returned by %s::%s()', $definition->getFactoryService(false), $definition->getFactoryMethod(false)); + } + + $scope = $definition->getScope(false); + if (!\in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) { + if ($return && 0 === strpos($return[\count($return) - 1], '@return')) { + $return[] = ''; + } + $return[] = sprintf("@throws InactiveScopeException when the '%s' service is requested while the '%s' scope is not active", $id, $scope); + } + + if ($definition->isDeprecated()) { + if ($return && 0 === strpos($return[\count($return) - 1], '@return')) { + $return[] = ''; + } + + $return[] = sprintf('@deprecated %s', $definition->getDeprecationMessage($id)); + } + + $return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return)); + + $shared = $definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE !== $scope ? ' shared' : ''; + $public = $definition->isPublic() ? 'public' : 'private'; + $autowired = $definition->isAutowired() ? ' autowired' : ''; + + if ($definition->isLazy()) { + $lazyInitialization = '$lazyLoad = true'; + } else { + $lazyInitialization = ''; + } + + // with proxies, for 5.3.3 compatibility, the getter must be public to be accessible to the initializer + $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); + $visibility = $isProxyCandidate ? 'public' : 'protected'; + $code = <<docStar} + * Gets the $public '$id'$shared$autowired service. + * + * $return + */ + {$visibility} function get{$this->camelize($id)}Service($lazyInitialization) + { + +EOF; + + $code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id) : ''; + + if (!\in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) { + $code .= <<scopedServices['$scope'])) { + throw new InactiveScopeException('$id', '$scope'); + } + + +EOF; + } + + if ($definition->isSynthetic()) { + $code .= sprintf(" throw new RuntimeException('You have requested a synthetic service (\"%s\"). The DIC does not know how to construct this service.');\n }\n", $id); + } else { + if ($definition->isDeprecated()) { + $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", var_export($definition->getDeprecationMessage($id), true)); + } + + $code .= + $this->addServiceInclude($definition). + $this->addServiceLocalTempVariables($id, $definition). + $this->addServiceInlinedDefinitions($id, $definition). + $this->addServiceInstance($id, $definition). + $this->addServiceInlinedDefinitionsSetup($id, $definition). + $this->addServiceProperties($definition). + $this->addServiceMethodCalls($definition). + $this->addServiceConfigurator($definition). + $this->addServiceReturn($id, $definition) + ; + } + + $this->definitionVariables = null; + $this->referenceVariables = null; + + return $code; + } + + /** + * Adds multiple services. + * + * @return string + */ + private function addServices() + { + $publicServices = $privateServices = $synchronizers = ''; + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if ($definition->isPublic()) { + $publicServices .= $this->addService($id, $definition); + } else { + $privateServices .= $this->addService($id, $definition); + } + + $synchronizers .= $this->addServiceSynchronizer($id, $definition); + } + + return $publicServices.$synchronizers.$privateServices; + } + + /** + * Adds synchronizer methods. + * + * @param string $id A service identifier + * @param Definition $definition A Definition instance + * + * @return string|null + * + * @deprecated since version 2.7, will be removed in 3.0. + */ + private function addServiceSynchronizer($id, Definition $definition) + { + if (!$definition->isSynchronized(false)) { + return; + } + + if ('request' !== $id) { + @trigger_error('Synchronized services were deprecated in version 2.7 and won\'t work anymore in 3.0.', E_USER_DEPRECATED); + } + + $code = ''; + foreach ($this->container->getDefinitions() as $definitionId => $definition) { + foreach ($definition->getMethodCalls() as $call) { + foreach ($call[1] as $argument) { + if ($argument instanceof Reference && $id == (string) $argument) { + $arguments = array(); + foreach ($call[1] as $value) { + $arguments[] = $this->dumpValue($value); + } + + $call = $this->wrapServiceConditionals($call[1], sprintf("\$this->get('%s')->%s(%s);", $definitionId, $call[0], implode(', ', $arguments))); + + $code .= <<initialized('$definitionId')) { + $call + } + +EOF; + } + } + } + } + + if (!$code) { + return; + } + + return <<docStar} + * Updates the '$id' service. + */ + protected function synchronize{$this->camelize($id)}Service() + { +$code } + +EOF; + } + + private function addNewInstance($id, Definition $definition, $return, $instantiation) + { + $class = $this->dumpValue($definition->getClass()); + + $arguments = array(); + foreach ($definition->getArguments() as $value) { + $arguments[] = $this->dumpValue($value); + } + + if (null !== $definition->getFactory()) { + $callable = $definition->getFactory(); + if (\is_array($callable)) { + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $callable[1] ?: 'n/a')); + } + + if ($callable[0] instanceof Reference + || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { + return sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); + } + + $class = $this->dumpValue($callable[0]); + // If the class is a string we can optimize call_user_func away + if (0 === strpos($class, "'")) { + return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); + } + + return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); + } + + return sprintf(" $return{$instantiation}\\%s(%s);\n", $callable, $arguments ? implode(', ', $arguments) : ''); + } elseif (null !== $definition->getFactoryMethod(false)) { + if (null !== $definition->getFactoryClass(false)) { + $class = $this->dumpValue($definition->getFactoryClass(false)); + + // If the class is a string we can optimize call_user_func away + if (0 === strpos($class, "'")) { + return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($class), $definition->getFactoryMethod(false), $arguments ? implode(', ', $arguments) : ''); + } + + return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($definition->getFactoryClass(false)), $definition->getFactoryMethod(false), $arguments ? ', '.implode(', ', $arguments) : ''); + } + + if (null !== $definition->getFactoryService(false)) { + return sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->getServiceCall($definition->getFactoryService(false)), $definition->getFactoryMethod(false), implode(', ', $arguments)); + } + + throw new RuntimeException(sprintf('Factory method requires a factory service or factory class in service definition for %s', $id)); + } + + if (false !== strpos($class, '$')) { + return sprintf(" \$class = %s;\n\n $return{$instantiation}new \$class(%s);\n", $class, implode(', ', $arguments)); + } + + return sprintf(" $return{$instantiation}new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments)); + } + + /** + * Adds the class headers. + * + * @param string $class Class name + * @param string $baseClass The name of the base class + * @param string $namespace The class namespace + * + * @return string + */ + private function startClass($class, $baseClass, $namespace) + { + $bagClass = $this->container->isFrozen() ? 'use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;' : 'use Symfony\Component\DependencyInjection\ParameterBag\\ParameterBag;'; + $namespaceLine = $namespace ? "\nnamespace $namespace;\n" : ''; + + return <<docStar} + * This class has been auto-generated + * by the Symfony Dependency Injection Component. + */ +class $class extends $baseClass +{ + private \$parameters; + private \$targetDirs = array(); + +EOF; + } + + /** + * Adds the constructor. + * + * @return string + */ + private function addConstructor() + { + $targetDirs = $this->exportTargetDirs(); + $arguments = $this->container->getParameterBag()->all() ? 'new ParameterBag($this->getDefaultParameters())' : null; + + $code = <<container->getScopes(false)) > 0) { + $code .= "\n"; + $code .= ' $this->scopes = '.$this->dumpValue($scopes).";\n"; + $code .= ' $this->scopeChildren = '.$this->dumpValue($this->container->getScopeChildren(false)).";\n"; + } + + $code .= $this->addMethodMap(); + $code .= $this->addAliases(); + + $code .= <<<'EOF' + } + +EOF; + + return $code; + } + + /** + * Adds the constructor for a frozen container. + * + * @return string + */ + private function addFrozenConstructor() + { + $targetDirs = $this->exportTargetDirs(); + + $code = <<container->getParameterBag()->all()) { + $code .= "\n \$this->parameters = \$this->getDefaultParameters();\n"; + } + + $code .= <<<'EOF' + + $this->services = + $this->scopedServices = + $this->scopeStacks = array(); +EOF; + + $code .= "\n"; + if (\count($scopes = $this->container->getScopes(false)) > 0) { + $code .= ' $this->scopes = '.$this->dumpValue($scopes).";\n"; + $code .= ' $this->scopeChildren = '.$this->dumpValue($this->container->getScopeChildren(false)).";\n"; + } else { + $code .= " \$this->scopes = array();\n"; + $code .= " \$this->scopeChildren = array();\n"; + } + + $code .= $this->addMethodMap(); + $code .= $this->addAliases(); + + $code .= <<<'EOF' + } + +EOF; + + return $code; + } + + /** + * Adds the constructor for a frozen container. + * + * @return string + */ + private function addFrozenCompile() + { + return <<docStar} + * {@inheritdoc} + */ + public function compile() + { + throw new LogicException('You cannot compile a dumped frozen container.'); + } + +EOF; + } + + /** + * Adds the isFrozen method for a frozen container. + * + * @return string + */ + private function addIsFrozenMethod() + { + return <<docStar} + * {@inheritdoc} + */ + public function isFrozen() + { + return true; + } + +EOF; + } + + /** + * Adds the methodMap property definition. + * + * @return string + */ + private function addMethodMap() + { + if (!$definitions = $this->container->getDefinitions()) { + return ''; + } + + $code = " \$this->methodMap = array(\n"; + ksort($definitions); + foreach ($definitions as $id => $definition) { + $code .= ' '.var_export($id, true).' => '.var_export('get'.$this->camelize($id).'Service', true).",\n"; + } + + return $code." );\n"; + } + + /** + * Adds the aliases property definition. + * + * @return string + */ + private function addAliases() + { + if (!$aliases = $this->container->getAliases()) { + return $this->container->isFrozen() ? "\n \$this->aliases = array();\n" : ''; + } + + $code = " \$this->aliases = array(\n"; + ksort($aliases); + foreach ($aliases as $alias => $id) { + $id = (string) $id; + while (isset($aliases[$id])) { + $id = (string) $aliases[$id]; + } + $code .= ' '.var_export($alias, true).' => '.var_export($id, true).",\n"; + } + + return $code." );\n"; + } + + /** + * Adds default parameters method. + * + * @return string + */ + private function addDefaultParametersMethod() + { + if (!$this->container->getParameterBag()->all()) { + return ''; + } + + $parameters = $this->exportParameters($this->container->getParameterBag()->all()); + + $code = ''; + if ($this->container->isFrozen()) { + $code .= <<<'EOF' + + /** + * {@inheritdoc} + */ + public function getParameter($name) + { + $name = strtolower($name); + + if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters))) { + throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); + } + + return $this->parameters[$name]; + } + + /** + * {@inheritdoc} + */ + public function hasParameter($name) + { + $name = strtolower($name); + + return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters); + } + + /** + * {@inheritdoc} + */ + public function setParameter($name, $value) + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + /** + * {@inheritdoc} + */ + public function getParameterBag() + { + if (null === $this->parameterBag) { + $this->parameterBag = new FrozenParameterBag($this->parameters); + } + + return $this->parameterBag; + } + +EOF; + if ('' === $this->docStar) { + $code = str_replace('/**', '/*', $code); + } + } + + $code .= <<docStar} + * Gets the default parameters. + * + * @return array An array of the default parameters + */ + protected function getDefaultParameters() + { + return $parameters; + } + +EOF; + + return $code; + } + + /** + * Exports parameters. + * + * @param array $parameters + * @param string $path + * @param int $indent + * + * @return string + * + * @throws InvalidArgumentException + */ + private function exportParameters(array $parameters, $path = '', $indent = 12) + { + $php = array(); + foreach ($parameters as $key => $value) { + if (\is_array($value)) { + $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4); + } elseif ($value instanceof Variable) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key)); + } elseif ($value instanceof Definition) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain service definitions. Definition for "%s" found in "%s".', $value->getClass(), $path.'/'.$key)); + } elseif ($value instanceof Reference) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path.'/'.$key)); + } elseif ($value instanceof Expression) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain expressions. Expression "%s" found in "%s".', $value, $path.'/'.$key)); + } else { + $value = $this->export($value); + } + + $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value); + } + + return sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', $indent - 4)); + } + + /** + * Ends the class definition. + * + * @return string + */ + private function endClass() + { + return <<<'EOF' +} + +EOF; + } + + /** + * Wraps the service conditionals. + * + * @param string $value + * @param string $code + * + * @return string + */ + private function wrapServiceConditionals($value, $code) + { + if (!$services = ContainerBuilder::getServiceConditionals($value)) { + return $code; + } + + $conditions = array(); + foreach ($services as $service) { + $conditions[] = sprintf("\$this->has('%s')", $service); + } + + // re-indent the wrapped code + $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); + + return sprintf(" if (%s) {\n%s }\n", implode(' && ', $conditions), $code); + } + + /** + * Builds service calls from arguments. + */ + private function getServiceCallsFromArguments(array $arguments, array &$calls, array &$behavior) + { + foreach ($arguments as $argument) { + if (\is_array($argument)) { + $this->getServiceCallsFromArguments($argument, $calls, $behavior); + } elseif ($argument instanceof Reference) { + $id = (string) $argument; + + if (!isset($calls[$id])) { + $calls[$id] = 0; + } + if (!isset($behavior[$id])) { + $behavior[$id] = $argument->getInvalidBehavior(); + } elseif (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $behavior[$id]) { + $behavior[$id] = $argument->getInvalidBehavior(); + } + + ++$calls[$id]; + } + } + } + + /** + * Returns the inline definition. + * + * @return array + */ + private function getInlinedDefinitions(Definition $definition) + { + if (false === $this->inlinedDefinitions->contains($definition)) { + $definitions = array_merge( + $this->getDefinitionsFromArguments($definition->getArguments()), + $this->getDefinitionsFromArguments($definition->getMethodCalls()), + $this->getDefinitionsFromArguments($definition->getProperties()), + $this->getDefinitionsFromArguments(array($definition->getConfigurator())), + $this->getDefinitionsFromArguments(array($definition->getFactory())) + ); + + $this->inlinedDefinitions->offsetSet($definition, $definitions); + + return $definitions; + } + + return $this->inlinedDefinitions->offsetGet($definition); + } + + /** + * Gets the definition from arguments. + * + * @return array + */ + private function getDefinitionsFromArguments(array $arguments) + { + $definitions = array(); + foreach ($arguments as $argument) { + if (\is_array($argument)) { + $definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument)); + } elseif ($argument instanceof Definition) { + $definitions = array_merge( + $definitions, + $this->getInlinedDefinitions($argument), + array($argument) + ); + } + } + + return $definitions; + } + + /** + * Checks if a service id has a reference. + * + * @param string $id + * @param array $arguments + * @param bool $deep + * @param array $visited + * + * @return bool + */ + private function hasReference($id, array $arguments, $deep = false, array &$visited = array()) + { + foreach ($arguments as $argument) { + if (\is_array($argument)) { + if ($this->hasReference($id, $argument, $deep, $visited)) { + return true; + } + } elseif ($argument instanceof Reference) { + $argumentId = (string) $argument; + if ($id === $argumentId) { + return true; + } + + if ($deep && !isset($visited[$argumentId]) && 'service_container' !== $argumentId) { + $visited[$argumentId] = true; + + $service = $this->container->getDefinition($argumentId); + + // if the proxy manager is enabled, disable searching for references in lazy services, + // as these services will be instantiated lazily and don't have direct related references. + if ($service->isLazy() && !$this->getProxyDumper() instanceof NullDumper) { + continue; + } + + $arguments = array_merge($service->getMethodCalls(), $service->getArguments(), $service->getProperties()); + + if ($this->hasReference($id, $arguments, $deep, $visited)) { + return true; + } + } + } + } + + return false; + } + + /** + * Dumps values. + * + * @param mixed $value + * @param bool $interpolate + * + * @return string + * + * @throws RuntimeException + */ + private function dumpValue($value, $interpolate = true) + { + if (\is_array($value)) { + $code = array(); + foreach ($value as $k => $v) { + $code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)); + } + + return sprintf('array(%s)', implode(', ', $code)); + } elseif ($value instanceof Definition) { + if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) { + return $this->dumpValue($this->definitionVariables->offsetGet($value), $interpolate); + } + if ($value->getMethodCalls()) { + throw new RuntimeException('Cannot dump definitions which have method calls.'); + } + if ($value->getProperties()) { + throw new RuntimeException('Cannot dump definitions which have properties.'); + } + if (null !== $value->getConfigurator()) { + throw new RuntimeException('Cannot dump definitions which have a configurator.'); + } + + $arguments = array(); + foreach ($value->getArguments() as $argument) { + $arguments[] = $this->dumpValue($argument); + } + + if (null !== $value->getFactory()) { + $factory = $value->getFactory(); + + if (\is_string($factory)) { + return sprintf('\\%s(%s)', $factory, implode(', ', $arguments)); + } + + if (\is_array($factory)) { + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $factory[1])) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $factory[1] ?: 'n/a')); + } + + if (\is_string($factory[0])) { + return sprintf('%s::%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory[0])), $factory[1], implode(', ', $arguments)); + } + + if ($factory[0] instanceof Definition) { + return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($factory[0]), $factory[1], \count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + } + + if ($factory[0] instanceof Reference) { + return sprintf('%s->%s(%s)', $this->dumpValue($factory[0]), $factory[1], implode(', ', $arguments)); + } + } + + throw new RuntimeException('Cannot dump definition because of invalid factory'); + } + + if (null !== $value->getFactoryMethod(false)) { + if (null !== $value->getFactoryClass(false)) { + return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($value->getFactoryClass(false)), $value->getFactoryMethod(false), \count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + } elseif (null !== $value->getFactoryService(false)) { + $service = $this->dumpValue($value->getFactoryService(false)); + + return sprintf('%s->%s(%s)', 0 === strpos($service, '$') ? sprintf('$this->get(%s)', $service) : $this->getServiceCall($value->getFactoryService(false)), $value->getFactoryMethod(false), implode(', ', $arguments)); + } + + throw new RuntimeException('Cannot dump definitions which have factory method without factory service or factory class.'); + } + + $class = $value->getClass(); + if (null === $class) { + throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); + } + + return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)); + } elseif ($value instanceof Variable) { + return '$'.$value; + } elseif ($value instanceof Reference) { + if (null !== $this->referenceVariables && isset($this->referenceVariables[$id = (string) $value])) { + return $this->dumpValue($this->referenceVariables[$id], $interpolate); + } + + return $this->getServiceCall((string) $value, $value); + } elseif ($value instanceof Expression) { + return $this->getExpressionLanguage()->compile((string) $value, array('this' => 'container')); + } elseif ($value instanceof Parameter) { + return $this->dumpParameter($value); + } elseif (true === $interpolate && \is_string($value)) { + if (preg_match('/^%([^%]+)%$/', $value, $match)) { + // we do this to deal with non string values (Boolean, integer, ...) + // the preg_replace_callback converts them to strings + return $this->dumpParameter(strtolower($match[1])); + } else { + $that = $this; + $replaceParameters = function ($match) use ($that) { + return "'.".$that->dumpParameter(strtolower($match[2])).".'"; + }; + + $code = str_replace('%%', '%', preg_replace_callback('/(?export($value))); + + return $code; + } + } elseif (\is_object($value) || \is_resource($value)) { + throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); + } + + return $this->export($value); + } + + /** + * Dumps a string to a literal (aka PHP Code) class value. + * + * @param string $class + * + * @return string + * + * @throws RuntimeException + */ + private function dumpLiteralClass($class) + { + if (false !== strpos($class, '$')) { + throw new RuntimeException('Cannot dump definitions which have a variable class name.'); + } + if (0 !== strpos($class, "'") || !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s)', $class ?: 'n/a')); + } + + $class = substr(str_replace('\\\\', '\\', $class), 1, -1); + + return 0 === strpos($class, '\\') ? $class : '\\'.$class; + } + + /** + * Dumps a parameter. + * + * @param string $name + * + * @return string + */ + public function dumpParameter($name) + { + if ($this->container->isFrozen() && $this->container->hasParameter($name)) { + return $this->dumpValue($this->container->getParameter($name), false); + } + + return sprintf("\$this->getParameter('%s')", strtolower($name)); + } + + /** + * @deprecated since version 2.6.2, to be removed in 3.0. + * Use \Symfony\Component\DependencyInjection\ContainerBuilder::addExpressionLanguageProvider instead. + * + * @param ExpressionFunctionProviderInterface $provider + */ + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6.2 and will be removed in 3.0. Use the Symfony\Component\DependencyInjection\ContainerBuilder::addExpressionLanguageProvider method instead.', E_USER_DEPRECATED); + + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Gets a service call. + * + * @param string $id + * @param Reference $reference + * + * @return string + */ + private function getServiceCall($id, Reference $reference = null) + { + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + + if ('service_container' === $id) { + return '$this'; + } + + if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { + return sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id); + } + + return sprintf('$this->get(\'%s\')', $id); + } + + /** + * Convert a service id to a valid PHP method name. + * + * @param string $id + * + * @return string + * + * @throws InvalidArgumentException + */ + private function camelize($id) + { + $name = Container::camelize($id); + + if (!preg_match('/^[a-zA-Z0-9_\x7f-\xff]+$/', $name)) { + throw new InvalidArgumentException(sprintf('Service id "%s" cannot be converted to a valid PHP method name.', $id)); + } + + return $name; + } + + /** + * Returns the next name to use. + * + * @return string + */ + private function getNextVariableName() + { + $firstChars = self::FIRST_CHARS; + $firstCharsLength = \strlen($firstChars); + $nonFirstChars = self::NON_FIRST_CHARS; + $nonFirstCharsLength = \strlen($nonFirstChars); + + while (true) { + $name = ''; + $i = $this->variableCount; + + if ('' === $name) { + $name .= $firstChars[$i % $firstCharsLength]; + $i = (int) ($i / $firstCharsLength); + } + + while ($i > 0) { + --$i; + $name .= $nonFirstChars[$i % $nonFirstCharsLength]; + $i = (int) ($i / $nonFirstCharsLength); + } + + ++$this->variableCount; + + // check that the name is not reserved + if (\in_array($name, $this->reservedVariables, true)) { + continue; + } + + return $name; + } + } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $providers = array_merge($this->container->getExpressionLanguageProviders(), $this->expressionLanguageProviders); + $this->expressionLanguage = new ExpressionLanguage(null, $providers); + + if ($this->container->isTrackingResources()) { + foreach ($providers as $provider) { + $this->container->addObjectResource($provider); + } + } + } + + return $this->expressionLanguage; + } + + private function exportTargetDirs() + { + return null === $this->targetDirRegex ? '' : <<targetDirMaxMatches}; ++\$i) { + \$this->targetDirs[\$i] = \$dir = dirname(\$dir); + } +EOF; + } + + private function export($value) + { + if (null !== $this->targetDirRegex && \is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) { + $prefix = $matches[0][1] ? var_export(substr($value, 0, $matches[0][1]), true).'.' : ''; + $suffix = $matches[0][1] + \strlen($matches[0][0]); + $suffix = isset($value[$suffix]) ? '.'.var_export(substr($value, $suffix), true) : ''; + $dirname = '__DIR__'; + + if (0 < $offset = 1 + $this->targetDirMaxMatches - \count($matches)) { + $dirname = sprintf('$this->targetDirs[%d]', $offset); + } + + if ($prefix || $suffix) { + return sprintf('(%s%s%s)', $prefix, $dirname, $suffix); + } + + return $dirname; + } + + if (\is_string($value) && false !== strpos($value, "\n")) { + $cleanParts = explode("\n", $value); + $cleanParts = array_map(function ($part) { return var_export($part, true); }, $cleanParts); + + return implode('."\n".', $cleanParts); + } + + return var_export($value, true); + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/XmlDumper.php b/vendor/symfony/dependency-injection/Dumper/XmlDumper.php new file mode 100644 index 0000000..5a85d67 --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/XmlDumper.php @@ -0,0 +1,364 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * XmlDumper dumps a service container as an XML string. + * + * @author Fabien Potencier + * @author Martin Hasoň + */ +class XmlDumper extends Dumper +{ + /** + * @var \DOMDocument + */ + private $document; + + /** + * Dumps the service container as an XML string. + * + * @return string An xml string representing of the service container + */ + public function dump(array $options = array()) + { + $this->document = new \DOMDocument('1.0', 'utf-8'); + $this->document->formatOutput = true; + + $container = $this->document->createElementNS('http://symfony.com/schema/dic/services', 'container'); + $container->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $container->setAttribute('xsi:schemaLocation', 'http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd'); + + $this->addParameters($container); + $this->addServices($container); + + $this->document->appendChild($container); + $xml = $this->document->saveXML(); + $this->document = null; + + return $xml; + } + + private function addParameters(\DOMElement $parent) + { + $data = $this->container->getParameterBag()->all(); + if (!$data) { + return; + } + + if ($this->container->isFrozen()) { + $data = $this->escape($data); + } + + $parameters = $this->document->createElement('parameters'); + $parent->appendChild($parameters); + $this->convertParameters($data, 'parameter', $parameters); + } + + private function addMethodCalls(array $methodcalls, \DOMElement $parent) + { + foreach ($methodcalls as $methodcall) { + $call = $this->document->createElement('call'); + $call->setAttribute('method', $methodcall[0]); + if (\count($methodcall[1])) { + $this->convertParameters($methodcall[1], 'argument', $call); + } + $parent->appendChild($call); + } + } + + /** + * Adds a service. + * + * @param Definition $definition + * @param string $id + * @param \DOMElement $parent + */ + private function addService($definition, $id, \DOMElement $parent) + { + $service = $this->document->createElement('service'); + if (null !== $id) { + $service->setAttribute('id', $id); + } + if ($class = $definition->getClass()) { + if ('\\' === substr($class, 0, 1)) { + $class = substr($class, 1); + } + + $service->setAttribute('class', $class); + } + if ($definition->getFactoryMethod(false)) { + $service->setAttribute('factory-method', $definition->getFactoryMethod(false)); + } + if ($definition->getFactoryClass(false)) { + $service->setAttribute('factory-class', $definition->getFactoryClass(false)); + } + if ($definition->getFactoryService(false)) { + $service->setAttribute('factory-service', $definition->getFactoryService(false)); + } + if (!$definition->isShared()) { + $service->setAttribute('shared', 'false'); + } + if (ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope(false)) { + $service->setAttribute('scope', $scope); + } + if (!$definition->isPublic()) { + $service->setAttribute('public', 'false'); + } + if ($definition->isSynthetic()) { + $service->setAttribute('synthetic', 'true'); + } + if ($definition->isSynchronized(false)) { + $service->setAttribute('synchronized', 'true'); + } + if ($definition->isLazy()) { + $service->setAttribute('lazy', 'true'); + } + if (null !== $decorated = $definition->getDecoratedService()) { + list($decorated, $renamedId, $priority) = $decorated; + $service->setAttribute('decorates', $decorated); + if (null !== $renamedId) { + $service->setAttribute('decoration-inner-name', $renamedId); + } + if (0 !== $priority) { + $service->setAttribute('decoration-priority', $priority); + } + } + + foreach ($definition->getTags() as $name => $tags) { + foreach ($tags as $attributes) { + $tag = $this->document->createElement('tag'); + $tag->setAttribute('name', $name); + foreach ($attributes as $key => $value) { + $tag->setAttribute($key, $value); + } + $service->appendChild($tag); + } + } + + if ($definition->getFile()) { + $file = $this->document->createElement('file'); + $file->appendChild($this->document->createTextNode($definition->getFile())); + $service->appendChild($file); + } + + if ($parameters = $definition->getArguments()) { + $this->convertParameters($parameters, 'argument', $service); + } + + if ($parameters = $definition->getProperties()) { + $this->convertParameters($parameters, 'property', $service, 'name'); + } + + $this->addMethodCalls($definition->getMethodCalls(), $service); + + if ($callable = $definition->getFactory()) { + $factory = $this->document->createElement('factory'); + + if (\is_array($callable) && $callable[0] instanceof Definition) { + $this->addService($callable[0], null, $factory); + $factory->setAttribute('method', $callable[1]); + } elseif (\is_array($callable)) { + $factory->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); + $factory->setAttribute('method', $callable[1]); + } else { + $factory->setAttribute('function', $callable); + } + $service->appendChild($factory); + } + + if ($definition->isDeprecated()) { + $deprecated = $this->document->createElement('deprecated'); + $deprecated->appendChild($this->document->createTextNode($definition->getDeprecationMessage('%service_id%'))); + + $service->appendChild($deprecated); + } + + if ($definition->isAutowired()) { + $service->setAttribute('autowire', 'true'); + } + + foreach ($definition->getAutowiringTypes() as $autowiringTypeValue) { + $autowiringType = $this->document->createElement('autowiring-type'); + $autowiringType->appendChild($this->document->createTextNode($autowiringTypeValue)); + + $service->appendChild($autowiringType); + } + + if ($definition->isAbstract()) { + $service->setAttribute('abstract', 'true'); + } + + if ($callable = $definition->getConfigurator()) { + $configurator = $this->document->createElement('configurator'); + + if (\is_array($callable) && $callable[0] instanceof Definition) { + $this->addService($callable[0], null, $configurator); + $configurator->setAttribute('method', $callable[1]); + } elseif (\is_array($callable)) { + $configurator->setAttribute($callable[0] instanceof Reference ? 'service' : 'class', $callable[0]); + $configurator->setAttribute('method', $callable[1]); + } else { + $configurator->setAttribute('function', $callable); + } + $service->appendChild($configurator); + } + + $parent->appendChild($service); + } + + /** + * Adds a service alias. + * + * @param string $alias + * @param Alias $id + * @param \DOMElement $parent + */ + private function addServiceAlias($alias, Alias $id, \DOMElement $parent) + { + $service = $this->document->createElement('service'); + $service->setAttribute('id', $alias); + $service->setAttribute('alias', $id); + if (!$id->isPublic()) { + $service->setAttribute('public', 'false'); + } + $parent->appendChild($service); + } + + private function addServices(\DOMElement $parent) + { + $definitions = $this->container->getDefinitions(); + if (!$definitions) { + return; + } + + $services = $this->document->createElement('services'); + foreach ($definitions as $id => $definition) { + $this->addService($definition, $id, $services); + } + + $aliases = $this->container->getAliases(); + foreach ($aliases as $alias => $id) { + while (isset($aliases[(string) $id])) { + $id = $aliases[(string) $id]; + } + $this->addServiceAlias($alias, $id, $services); + } + $parent->appendChild($services); + } + + /** + * Converts parameters. + * + * @param array $parameters + * @param string $type + * @param \DOMElement $parent + * @param string $keyAttribute + */ + private function convertParameters(array $parameters, $type, \DOMElement $parent, $keyAttribute = 'key') + { + $withKeys = array_keys($parameters) !== range(0, \count($parameters) - 1); + foreach ($parameters as $key => $value) { + $element = $this->document->createElement($type); + if ($withKeys) { + $element->setAttribute($keyAttribute, $key); + } + + if (\is_array($value)) { + $element->setAttribute('type', 'collection'); + $this->convertParameters($value, $type, $element, 'key'); + } elseif ($value instanceof Reference) { + $element->setAttribute('type', 'service'); + $element->setAttribute('id', (string) $value); + $behaviour = $value->getInvalidBehavior(); + if (ContainerInterface::NULL_ON_INVALID_REFERENCE == $behaviour) { + $element->setAttribute('on-invalid', 'null'); + } elseif (ContainerInterface::IGNORE_ON_INVALID_REFERENCE == $behaviour) { + $element->setAttribute('on-invalid', 'ignore'); + } + if (!$value->isStrict(false)) { + $element->setAttribute('strict', 'false'); + } + } elseif ($value instanceof Definition) { + $element->setAttribute('type', 'service'); + $this->addService($value, null, $element); + } elseif ($value instanceof Expression) { + $element->setAttribute('type', 'expression'); + $text = $this->document->createTextNode(self::phpToXml((string) $value)); + $element->appendChild($text); + } else { + if (\in_array($value, array('null', 'true', 'false'), true)) { + $element->setAttribute('type', 'string'); + } + $text = $this->document->createTextNode(self::phpToXml($value)); + $element->appendChild($text); + } + $parent->appendChild($element); + } + } + + /** + * Escapes arguments. + * + * @return array + */ + private function escape(array $arguments) + { + $args = array(); + foreach ($arguments as $k => $v) { + if (\is_array($v)) { + $args[$k] = $this->escape($v); + } elseif (\is_string($v)) { + $args[$k] = str_replace('%', '%%', $v); + } else { + $args[$k] = $v; + } + } + + return $args; + } + + /** + * Converts php types to xml types. + * + * @param mixed $value Value to convert + * + * @return string + * + * @throws RuntimeException When trying to dump object or resource + */ + public static function phpToXml($value) + { + switch (true) { + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case $value instanceof Parameter: + return '%'.$value.'%'; + case \is_object($value) || \is_resource($value): + throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); + default: + return (string) $value; + } + } +} diff --git a/vendor/symfony/dependency-injection/Dumper/YamlDumper.php b/vendor/symfony/dependency-injection/Dumper/YamlDumper.php new file mode 100644 index 0000000..35d1218 --- /dev/null +++ b/vendor/symfony/dependency-injection/Dumper/YamlDumper.php @@ -0,0 +1,368 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Dumper; + +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Yaml\Dumper as YmlDumper; + +/** + * YamlDumper dumps a service container as a YAML string. + * + * @author Fabien Potencier + */ +class YamlDumper extends Dumper +{ + private $dumper; + + /** + * Dumps the service container as an YAML string. + * + * @return string A YAML string representing of the service container + */ + public function dump(array $options = array()) + { + if (!class_exists('Symfony\Component\Yaml\Dumper')) { + throw new RuntimeException('Unable to dump the container as the Symfony Yaml Component is not installed.'); + } + + if (null === $this->dumper) { + $this->dumper = new YmlDumper(); + } + + return $this->addParameters()."\n".$this->addServices(); + } + + /** + * Adds a service. + * + * @param string $id + * @param Definition $definition + * + * @return string + */ + private function addService($id, Definition $definition) + { + $code = " $id:\n"; + if ($class = $definition->getClass()) { + if ('\\' === substr($class, 0, 1)) { + $class = substr($class, 1); + } + + $code .= sprintf(" class: %s\n", $this->dumper->dump($class)); + } + + if (!$definition->isPublic()) { + $code .= " public: false\n"; + } + + $tagsCode = ''; + foreach ($definition->getTags() as $name => $tags) { + foreach ($tags as $attributes) { + $att = array(); + foreach ($attributes as $key => $value) { + $att[] = sprintf('%s: %s', $this->dumper->dump($key), $this->dumper->dump($value)); + } + $att = $att ? ', '.implode(', ', $att) : ''; + + $tagsCode .= sprintf(" - { name: %s%s }\n", $this->dumper->dump($name), $att); + } + } + if ($tagsCode) { + $code .= " tags:\n".$tagsCode; + } + + if ($definition->getFile()) { + $code .= sprintf(" file: %s\n", $this->dumper->dump($definition->getFile())); + } + + if ($definition->isSynthetic()) { + $code .= " synthetic: true\n"; + } + + if ($definition->isSynchronized(false)) { + $code .= " synchronized: true\n"; + } + + if ($definition->isDeprecated()) { + $code .= sprintf(" deprecated: %s\n", $this->dumper->dump($definition->getDeprecationMessage('%service_id%'))); + } + + if ($definition->isAutowired()) { + $code .= " autowire: true\n"; + } + + $autowiringTypesCode = ''; + foreach ($definition->getAutowiringTypes() as $autowiringType) { + $autowiringTypesCode .= sprintf(" - %s\n", $this->dumper->dump($autowiringType)); + } + if ($autowiringTypesCode) { + $code .= sprintf(" autowiring_types:\n%s", $autowiringTypesCode); + } + + if ($definition->getFactoryClass(false)) { + $code .= sprintf(" factory_class: %s\n", $this->dumper->dump($definition->getFactoryClass(false))); + } + + if ($definition->isAbstract()) { + $code .= " abstract: true\n"; + } + + if ($definition->isLazy()) { + $code .= " lazy: true\n"; + } + + if ($definition->getFactoryMethod(false)) { + $code .= sprintf(" factory_method: %s\n", $this->dumper->dump($definition->getFactoryMethod(false))); + } + + if ($definition->getFactoryService(false)) { + $code .= sprintf(" factory_service: %s\n", $this->dumper->dump($definition->getFactoryService(false))); + } + + if ($definition->getArguments()) { + $code .= sprintf(" arguments: %s\n", $this->dumper->dump($this->dumpValue($definition->getArguments()), 0)); + } + + if ($definition->getProperties()) { + $code .= sprintf(" properties: %s\n", $this->dumper->dump($this->dumpValue($definition->getProperties()), 0)); + } + + if ($definition->getMethodCalls()) { + $code .= sprintf(" calls:\n%s\n", $this->dumper->dump($this->dumpValue($definition->getMethodCalls()), 1, 12)); + } + + if (!$definition->isShared()) { + $code .= " shared: false\n"; + } + + if (ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope(false)) { + $code .= sprintf(" scope: %s\n", $this->dumper->dump($scope)); + } + + if (null !== $decorated = $definition->getDecoratedService()) { + list($decorated, $renamedId, $priority) = $decorated; + $code .= sprintf(" decorates: %s\n", $decorated); + if (null !== $renamedId) { + $code .= sprintf(" decoration_inner_name: %s\n", $renamedId); + } + if (0 !== $priority) { + $code .= sprintf(" decoration_priority: %s\n", $priority); + } + } + + if ($callable = $definition->getFactory()) { + $code .= sprintf(" factory: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); + } + + if ($callable = $definition->getConfigurator()) { + $code .= sprintf(" configurator: %s\n", $this->dumper->dump($this->dumpCallable($callable), 0)); + } + + return $code; + } + + /** + * Adds a service alias. + * + * @param string $alias + * @param Alias $id + * + * @return string + */ + private function addServiceAlias($alias, Alias $id) + { + if ($id->isPublic()) { + return sprintf(" %s: '@%s'\n", $alias, $id); + } + + return sprintf(" %s:\n alias: %s\n public: false\n", $alias, $id); + } + + /** + * Adds services. + * + * @return string + */ + private function addServices() + { + if (!$this->container->getDefinitions()) { + return ''; + } + + $code = "services:\n"; + foreach ($this->container->getDefinitions() as $id => $definition) { + $code .= $this->addService($id, $definition); + } + + $aliases = $this->container->getAliases(); + foreach ($aliases as $alias => $id) { + while (isset($aliases[(string) $id])) { + $id = $aliases[(string) $id]; + } + $code .= $this->addServiceAlias($alias, $id); + } + + return $code; + } + + /** + * Adds parameters. + * + * @return string + */ + private function addParameters() + { + if (!$this->container->getParameterBag()->all()) { + return ''; + } + + $parameters = $this->prepareParameters($this->container->getParameterBag()->all(), $this->container->isFrozen()); + + return $this->dumper->dump(array('parameters' => $parameters), 2); + } + + /** + * Dumps callable to YAML format. + * + * @param callable $callable + * + * @return callable + */ + private function dumpCallable($callable) + { + if (\is_array($callable)) { + if ($callable[0] instanceof Reference) { + $callable = array($this->getServiceCall((string) $callable[0], $callable[0]), $callable[1]); + } else { + $callable = array($callable[0], $callable[1]); + } + } + + return $callable; + } + + /** + * Dumps the value to YAML format. + * + * @param mixed $value + * + * @return mixed + * + * @throws RuntimeException When trying to dump object or resource + */ + private function dumpValue($value) + { + if (\is_array($value)) { + $code = array(); + foreach ($value as $k => $v) { + $code[$k] = $this->dumpValue($v); + } + + return $code; + } elseif ($value instanceof Reference) { + return $this->getServiceCall((string) $value, $value); + } elseif ($value instanceof Parameter) { + return $this->getParameterCall((string) $value); + } elseif ($value instanceof Expression) { + return $this->getExpressionCall((string) $value); + } elseif (\is_object($value) || \is_resource($value)) { + throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); + } + + return $value; + } + + /** + * Gets the service call. + * + * @param string $id + * @param Reference $reference + * + * @return string + */ + private function getServiceCall($id, Reference $reference = null) + { + if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { + return sprintf('@?%s', $id); + } + + return sprintf('@%s', $id); + } + + /** + * Gets parameter call. + * + * @param string $id + * + * @return string + */ + private function getParameterCall($id) + { + return sprintf('%%%s%%', $id); + } + + private function getExpressionCall($expression) + { + return sprintf('@=%s', $expression); + } + + /** + * Prepares parameters. + * + * @param array $parameters + * @param bool $escape + * + * @return array + */ + private function prepareParameters(array $parameters, $escape = true) + { + $filtered = array(); + foreach ($parameters as $key => $value) { + if (\is_array($value)) { + $value = $this->prepareParameters($value, $escape); + } elseif ($value instanceof Reference || \is_string($value) && 0 === strpos($value, '@')) { + $value = '@'.$value; + } + + $filtered[$key] = $value; + } + + return $escape ? $this->escape($filtered) : $filtered; + } + + /** + * Escapes arguments. + * + * @return array + */ + private function escape(array $arguments) + { + $args = array(); + foreach ($arguments as $k => $v) { + if (\is_array($v)) { + $args[$k] = $this->escape($v); + } elseif (\is_string($v)) { + $args[$k] = str_replace('%', '%%', $v); + } else { + $args[$k] = $v; + } + } + + return $args; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/BadMethodCallException.php b/vendor/symfony/dependency-injection/Exception/BadMethodCallException.php new file mode 100644 index 0000000..959238e --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/BadMethodCallException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base BadMethodCallException for Dependency Injection component. + */ +class BadMethodCallException extends \BadMethodCallException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/ExceptionInterface.php b/vendor/symfony/dependency-injection/Exception/ExceptionInterface.php new file mode 100644 index 0000000..f5e9099 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ExceptionInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base ExceptionInterface for Dependency Injection component. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/InactiveScopeException.php b/vendor/symfony/dependency-injection/Exception/InactiveScopeException.php new file mode 100644 index 0000000..6b3dd3e --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/InactiveScopeException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when you try to create a service of an inactive scope. + * + * @author Johannes M. Schmitt + */ +class InactiveScopeException extends RuntimeException +{ + private $serviceId; + private $scope; + + public function __construct($serviceId, $scope, \Exception $previous = null) + { + parent::__construct(sprintf('You cannot create a service ("%s") of an inactive scope ("%s").', $serviceId, $scope), 0, $previous); + + $this->serviceId = $serviceId; + $this->scope = $scope; + } + + public function getServiceId() + { + return $this->serviceId; + } + + public function getScope() + { + return $this->scope; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/InvalidArgumentException.php b/vendor/symfony/dependency-injection/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..119bb7d --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base InvalidArgumentException for Dependency Injection component. + * + * @author Bulat Shakirzyanov + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/LogicException.php b/vendor/symfony/dependency-injection/Exception/LogicException.php new file mode 100644 index 0000000..17a070c --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/LogicException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base LogicException for Dependency Injection component. + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/OutOfBoundsException.php b/vendor/symfony/dependency-injection/Exception/OutOfBoundsException.php new file mode 100644 index 0000000..a61f143 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/OutOfBoundsException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base OutOfBoundsException for Dependency Injection component. + */ +class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php b/vendor/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php new file mode 100644 index 0000000..2915176 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ParameterCircularReferenceException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when a circular reference in a parameter is detected. + * + * @author Fabien Potencier + */ +class ParameterCircularReferenceException extends RuntimeException +{ + private $parameters; + + public function __construct($parameters, \Exception $previous = null) + { + parent::__construct(sprintf('Circular reference detected for parameter "%s" ("%s" > "%s").', $parameters[0], implode('" > "', $parameters), $parameters[0]), 0, $previous); + + $this->parameters = $parameters; + } + + public function getParameters() + { + return $this->parameters; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/ParameterNotFoundException.php b/vendor/symfony/dependency-injection/Exception/ParameterNotFoundException.php new file mode 100644 index 0000000..1d97830 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ParameterNotFoundException.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when a non-existent parameter is used. + * + * @author Fabien Potencier + */ +class ParameterNotFoundException extends InvalidArgumentException +{ + private $key; + private $sourceId; + private $sourceKey; + private $alternatives; + + /** + * @param string $key The requested parameter key + * @param string $sourceId The service id that references the non-existent parameter + * @param string $sourceKey The parameter key that references the non-existent parameter + * @param \Exception $previous The previous exception + * @param string[] $alternatives Some parameter name alternatives + */ + public function __construct($key, $sourceId = null, $sourceKey = null, \Exception $previous = null, array $alternatives = array()) + { + $this->key = $key; + $this->sourceId = $sourceId; + $this->sourceKey = $sourceKey; + $this->alternatives = $alternatives; + + parent::__construct('', 0, $previous); + + $this->updateRepr(); + } + + public function updateRepr() + { + if (null !== $this->sourceId) { + $this->message = sprintf('The service "%s" has a dependency on a non-existent parameter "%s".', $this->sourceId, $this->key); + } elseif (null !== $this->sourceKey) { + $this->message = sprintf('The parameter "%s" has a dependency on a non-existent parameter "%s".', $this->sourceKey, $this->key); + } else { + $this->message = sprintf('You have requested a non-existent parameter "%s".', $this->key); + } + + if ($this->alternatives) { + if (1 == \count($this->alternatives)) { + $this->message .= ' Did you mean this: "'; + } else { + $this->message .= ' Did you mean one of these: "'; + } + $this->message .= implode('", "', $this->alternatives).'"?'; + } + } + + public function getKey() + { + return $this->key; + } + + public function getSourceId() + { + return $this->sourceId; + } + + public function getSourceKey() + { + return $this->sourceKey; + } + + public function setSourceId($sourceId) + { + $this->sourceId = $sourceId; + + $this->updateRepr(); + } + + public function setSourceKey($sourceKey) + { + $this->sourceKey = $sourceKey; + + $this->updateRepr(); + } +} diff --git a/vendor/symfony/dependency-injection/Exception/RuntimeException.php b/vendor/symfony/dependency-injection/Exception/RuntimeException.php new file mode 100644 index 0000000..5c24541 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Base RuntimeException for Dependency Injection component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/dependency-injection/Exception/ScopeCrossingInjectionException.php b/vendor/symfony/dependency-injection/Exception/ScopeCrossingInjectionException.php new file mode 100644 index 0000000..661fbab --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ScopeCrossingInjectionException.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when the a scope crossing injection is detected. + * + * @author Johannes M. Schmitt + */ +class ScopeCrossingInjectionException extends RuntimeException +{ + private $sourceServiceId; + private $sourceScope; + private $destServiceId; + private $destScope; + + public function __construct($sourceServiceId, $sourceScope, $destServiceId, $destScope, \Exception $previous = null) + { + parent::__construct(sprintf( + 'Scope Crossing Injection detected: The definition "%s" references the service "%s" which belongs to another scope hierarchy. ' + .'This service might not be available consistently. Generally, it is safer to either move the definition "%s" to scope "%s", or ' + .'declare "%s" as a child scope of "%s". If you can be sure that the other scope is always active, you can set the reference to strict=false to get rid of this error.', + $sourceServiceId, + $destServiceId, + $sourceServiceId, + $destScope, + $sourceScope, + $destScope + ), 0, $previous); + + $this->sourceServiceId = $sourceServiceId; + $this->sourceScope = $sourceScope; + $this->destServiceId = $destServiceId; + $this->destScope = $destScope; + } + + public function getSourceServiceId() + { + return $this->sourceServiceId; + } + + public function getSourceScope() + { + return $this->sourceScope; + } + + public function getDestServiceId() + { + return $this->destServiceId; + } + + public function getDestScope() + { + return $this->destScope; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/ScopeWideningInjectionException.php b/vendor/symfony/dependency-injection/Exception/ScopeWideningInjectionException.php new file mode 100644 index 0000000..86a6684 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ScopeWideningInjectionException.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * Thrown when a scope widening injection is detected. + * + * @author Johannes M. Schmitt + */ +class ScopeWideningInjectionException extends RuntimeException +{ + private $sourceServiceId; + private $sourceScope; + private $destServiceId; + private $destScope; + + public function __construct($sourceServiceId, $sourceScope, $destServiceId, $destScope, \Exception $previous = null) + { + parent::__construct(sprintf( + 'Scope Widening Injection detected: The definition "%s" references the service "%s" which belongs to a narrower scope. ' + .'Generally, it is safer to either move "%s" to scope "%s" or alternatively rely on the provider pattern by injecting the container itself, and requesting the service "%s" each time it is needed. ' + .'In rare, special cases however that might not be necessary, then you can set the reference to strict=false to get rid of this error.', + $sourceServiceId, + $destServiceId, + $sourceServiceId, + $destScope, + $destServiceId + ), 0, $previous); + + $this->sourceServiceId = $sourceServiceId; + $this->sourceScope = $sourceScope; + $this->destServiceId = $destServiceId; + $this->destScope = $destScope; + } + + public function getSourceServiceId() + { + return $this->sourceServiceId; + } + + public function getSourceScope() + { + return $this->sourceScope; + } + + public function getDestServiceId() + { + return $this->destServiceId; + } + + public function getDestScope() + { + return $this->destScope; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php b/vendor/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php new file mode 100644 index 0000000..26e3fb3 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ServiceCircularReferenceException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when a circular reference is detected. + * + * @author Johannes M. Schmitt + */ +class ServiceCircularReferenceException extends RuntimeException +{ + private $serviceId; + private $path; + + public function __construct($serviceId, array $path, \Exception $previous = null) + { + parent::__construct(sprintf('Circular reference detected for service "%s", path: "%s".', $serviceId, implode(' -> ', $path)), 0, $previous); + + $this->serviceId = $serviceId; + $this->path = $path; + } + + public function getServiceId() + { + return $this->serviceId; + } + + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/symfony/dependency-injection/Exception/ServiceNotFoundException.php b/vendor/symfony/dependency-injection/Exception/ServiceNotFoundException.php new file mode 100644 index 0000000..620ed98 --- /dev/null +++ b/vendor/symfony/dependency-injection/Exception/ServiceNotFoundException.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Exception; + +/** + * This exception is thrown when a non-existent service is requested. + * + * @author Johannes M. Schmitt + */ +class ServiceNotFoundException extends InvalidArgumentException +{ + private $id; + private $sourceId; + + public function __construct($id, $sourceId = null, \Exception $previous = null, array $alternatives = array()) + { + if (null === $sourceId) { + $msg = sprintf('You have requested a non-existent service "%s".', $id); + } else { + $msg = sprintf('The service "%s" has a dependency on a non-existent service "%s".', $sourceId, $id); + } + + if ($alternatives) { + if (1 == \count($alternatives)) { + $msg .= ' Did you mean this: "'; + } else { + $msg .= ' Did you mean one of these: "'; + } + $msg .= implode('", "', $alternatives).'"?'; + } + + parent::__construct($msg, 0, $previous); + + $this->id = $id; + $this->sourceId = $sourceId; + } + + public function getId() + { + return $this->id; + } + + public function getSourceId() + { + return $this->sourceId; + } +} diff --git a/vendor/symfony/dependency-injection/ExpressionLanguage.php b/vendor/symfony/dependency-injection/ExpressionLanguage.php new file mode 100644 index 0000000..acc97bc --- /dev/null +++ b/vendor/symfony/dependency-injection/ExpressionLanguage.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage as BaseExpressionLanguage; +use Symfony\Component\ExpressionLanguage\ParserCache\ParserCacheInterface; + +/** + * Adds some function to the default ExpressionLanguage. + * + * @author Fabien Potencier + * + * @see ExpressionLanguageProvider + */ +class ExpressionLanguage extends BaseExpressionLanguage +{ + public function __construct(ParserCacheInterface $cache = null, array $providers = array()) + { + // prepend the default provider to let users override it easily + array_unshift($providers, new ExpressionLanguageProvider()); + + parent::__construct($cache, $providers); + } +} diff --git a/vendor/symfony/dependency-injection/ExpressionLanguageProvider.php b/vendor/symfony/dependency-injection/ExpressionLanguageProvider.php new file mode 100644 index 0000000..ce6d695 --- /dev/null +++ b/vendor/symfony/dependency-injection/ExpressionLanguageProvider.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +use Symfony\Component\ExpressionLanguage\ExpressionFunction; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; + +/** + * Define some ExpressionLanguage functions. + * + * To get a service, use service('request'). + * To get a parameter, use parameter('kernel.debug'). + * + * @author Fabien Potencier + */ +class ExpressionLanguageProvider implements ExpressionFunctionProviderInterface +{ + public function getFunctions() + { + return array( + new ExpressionFunction('service', function ($arg) { + return sprintf('$this->get(%s)', $arg); + }, function (array $variables, $value) { + return $variables['container']->get($value); + }), + + new ExpressionFunction('parameter', function ($arg) { + return sprintf('$this->getParameter(%s)', $arg); + }, function (array $variables, $value) { + return $variables['container']->getParameter($value); + }), + ); + } +} diff --git a/vendor/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php b/vendor/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php new file mode 100644 index 0000000..c3bd842 --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/ConfigurationExtensionInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * ConfigurationExtensionInterface is the interface implemented by container extension classes. + * + * @author Kevin Bond + */ +interface ConfigurationExtensionInterface +{ + /** + * Returns extension configuration. + * + * @return ConfigurationInterface|null The configuration or null + */ + public function getConfiguration(array $config, ContainerBuilder $container); +} diff --git a/vendor/symfony/dependency-injection/Extension/Extension.php b/vendor/symfony/dependency-injection/Extension/Extension.php new file mode 100644 index 0000000..b56a2cc --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/Extension.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Symfony\Component\Config\Definition\Processor; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * Provides useful features shared by many extensions. + * + * @author Fabien Potencier + */ +abstract class Extension implements ExtensionInterface, ConfigurationExtensionInterface +{ + /** + * {@inheritdoc} + */ + public function getXsdValidationBasePath() + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getNamespace() + { + return 'http://example.org/schema/dic/'.$this->getAlias(); + } + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * This convention is to remove the "Extension" postfix from the class + * name and then lowercase and underscore the result. So: + * + * AcmeHelloExtension + * + * becomes + * + * acme_hello + * + * This can be overridden in a sub-class to specify the alias manually. + * + * @return string The alias + * + * @throws BadMethodCallException When the extension name does not follow conventions + */ + public function getAlias() + { + $className = \get_class($this); + if ('Extension' != substr($className, -9)) { + throw new BadMethodCallException('This extension does not follow the naming convention; you must overwrite the getAlias() method.'); + } + $classBaseName = substr(strrchr($className, '\\'), 1, -9); + + return Container::underscore($classBaseName); + } + + /** + * {@inheritdoc} + */ + public function getConfiguration(array $config, ContainerBuilder $container) + { + $reflected = new \ReflectionClass($this); + $namespace = $reflected->getNamespaceName(); + + $class = $namespace.'\\Configuration'; + if (class_exists($class)) { + $r = new \ReflectionClass($class); + $container->addResource(new FileResource($r->getFileName())); + + if (!method_exists($class, '__construct')) { + return new $class(); + } + } + } + + final protected function processConfiguration(ConfigurationInterface $configuration, array $configs) + { + $processor = new Processor(); + + return $processor->processConfiguration($configuration, $configs); + } + + /** + * @return bool Whether the configuration is enabled + * + * @throws InvalidArgumentException When the config is not enableable + */ + protected function isConfigEnabled(ContainerBuilder $container, array $config) + { + if (!array_key_exists('enabled', $config)) { + throw new InvalidArgumentException("The config array has no 'enabled' key."); + } + + return (bool) $container->getParameterBag()->resolveValue($config['enabled']); + } +} diff --git a/vendor/symfony/dependency-injection/Extension/ExtensionInterface.php b/vendor/symfony/dependency-injection/Extension/ExtensionInterface.php new file mode 100644 index 0000000..18de312 --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/ExtensionInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * ExtensionInterface is the interface implemented by container extension classes. + * + * @author Fabien Potencier + */ +interface ExtensionInterface +{ + /** + * Loads a specific configuration. + * + * @throws \InvalidArgumentException When provided tag is not defined in this extension + */ + public function load(array $configs, ContainerBuilder $container); + + /** + * Returns the namespace to be used for this extension (XML namespace). + * + * @return string The XML namespace + */ + public function getNamespace(); + + /** + * Returns the base path for the XSD files. + * + * @return string The XSD base path + */ + public function getXsdValidationBasePath(); + + /** + * Returns the recommended alias to use in XML. + * + * This alias is also the mandatory prefix to use when using YAML. + * + * @return string The alias + */ + public function getAlias(); +} diff --git a/vendor/symfony/dependency-injection/Extension/PrependExtensionInterface.php b/vendor/symfony/dependency-injection/Extension/PrependExtensionInterface.php new file mode 100644 index 0000000..5bd18d7 --- /dev/null +++ b/vendor/symfony/dependency-injection/Extension/PrependExtensionInterface.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Extension; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +interface PrependExtensionInterface +{ + /** + * Allow an extension to prepend the extension configurations. + */ + public function prepend(ContainerBuilder $container); +} diff --git a/vendor/symfony/dependency-injection/IntrospectableContainerInterface.php b/vendor/symfony/dependency-injection/IntrospectableContainerInterface.php new file mode 100644 index 0000000..4aa0059 --- /dev/null +++ b/vendor/symfony/dependency-injection/IntrospectableContainerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * IntrospectableContainerInterface defines additional introspection functionality + * for containers, allowing logic to be implemented based on a Container's state. + * + * @author Evan Villemez + * + * @deprecated since version 2.8, to be merged with ContainerInterface in 3.0. + */ +interface IntrospectableContainerInterface extends ContainerInterface +{ + /** + * Check for whether or not a service has been initialized. + * + * @param string $id + * + * @return bool true if the service has been initialized, false otherwise + */ + public function initialized($id); +} diff --git a/vendor/symfony/dependency-injection/LICENSE b/vendor/symfony/dependency-injection/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/dependency-injection/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php new file mode 100644 index 0000000..417ab90 --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/InstantiatorInterface.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; + +/** + * Lazy proxy instantiator, capable of instantiating a proxy given a container, the + * service definitions and a callback that produces the real service instance. + * + * @author Marco Pivetta + */ +interface InstantiatorInterface +{ + /** + * Instantiates a proxy object. + * + * @param ContainerInterface $container The container from which the service is being requested + * @param Definition $definition The definition of the requested service + * @param string $id Identifier of the requested service + * @param callable $realInstantiator Zero-argument callback that is capable of producing the real service instance + * + * @return object + */ + public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator); +} diff --git a/vendor/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php new file mode 100644 index 0000000..3b0b57e --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/Instantiator/RealServiceInstantiator.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\Instantiator; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; + +/** + * {@inheritdoc} + * + * Noop proxy instantiator - simply produces the real service instead of a proxy instance. + * + * @author Marco Pivetta + */ +class RealServiceInstantiator implements InstantiatorInterface +{ + /** + * {@inheritdoc} + */ + public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator) + { + return \call_user_func($realInstantiator); + } +} diff --git a/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php new file mode 100644 index 0000000..502c72a --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/DumperInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * Lazy proxy dumper capable of generating the instantiation logic PHP code for proxied services. + * + * @author Marco Pivetta + */ +interface DumperInterface +{ + /** + * Inspects whether the given definitions should produce proxy instantiation logic in the dumped container. + * + * @return bool + */ + public function isProxyCandidate(Definition $definition); + + /** + * Generates the code to be used to instantiate a proxy in the dumped factory code. + * + * @param Definition $definition + * @param string $id Service identifier + * + * @return string + */ + public function getProxyFactoryCode(Definition $definition, $id); + + /** + * Generates the code for the lazy proxy. + * + * @return string + */ + public function getProxyCode(Definition $definition); +} diff --git a/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php new file mode 100644 index 0000000..30911d3 --- /dev/null +++ b/vendor/symfony/dependency-injection/LazyProxy/PhpDumper/NullDumper.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\LazyProxy\PhpDumper; + +use Symfony\Component\DependencyInjection\Definition; + +/** + * Null dumper, negates any proxy code generation for any given service definition. + * + * @author Marco Pivetta + */ +class NullDumper implements DumperInterface +{ + /** + * {@inheritdoc} + */ + public function isProxyCandidate(Definition $definition) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function getProxyFactoryCode(Definition $definition, $id) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function getProxyCode(Definition $definition) + { + return ''; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/ClosureLoader.php b/vendor/symfony/dependency-injection/Loader/ClosureLoader.php new file mode 100644 index 0000000..183cacc --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/ClosureLoader.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * ClosureLoader loads service definitions from a PHP closure. + * + * The Closure has access to the container as its first argument. + * + * @author Fabien Potencier + */ +class ClosureLoader extends Loader +{ + private $container; + + public function __construct(ContainerBuilder $container) + { + $this->container = $container; + } + + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + \call_user_func($resource, $this->container); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return $resource instanceof \Closure; + } +} diff --git a/vendor/symfony/dependency-injection/Loader/DirectoryLoader.php b/vendor/symfony/dependency-injection/Loader/DirectoryLoader.php new file mode 100644 index 0000000..05a3ba2 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/DirectoryLoader.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Resource\DirectoryResource; + +/** + * DirectoryLoader is a recursive loader to go through directories. + * + * @author Sebastien Lavoie + */ +class DirectoryLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($file, $type = null) + { + $file = rtrim($file, '/'); + $path = $this->locator->locate($file); + $this->container->addResource(new DirectoryResource($path)); + + foreach (scandir($path) as $dir) { + if ('.' !== $dir[0]) { + if (is_dir($path.'/'.$dir)) { + $dir .= '/'; // append / to allow recursion + } + + $this->setCurrentDir($path); + + $this->import($dir, null, false, $path); + } + } + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + if ('directory' === $type) { + return true; + } + + return null === $type && \is_string($resource) && '/' === substr($resource, -1); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/FileLoader.php b/vendor/symfony/dependency-injection/Loader/FileLoader.php new file mode 100644 index 0000000..9fc297b --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/FileLoader.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Loader\FileLoader as BaseFileLoader; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * FileLoader is the abstract class used by all built-in loaders that are file based. + * + * @author Fabien Potencier + */ +abstract class FileLoader extends BaseFileLoader +{ + protected $container; + + public function __construct(ContainerBuilder $container, FileLocatorInterface $locator) + { + $this->container = $container; + + parent::__construct($locator); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/IniFileLoader.php b/vendor/symfony/dependency-injection/Loader/IniFileLoader.php new file mode 100644 index 0000000..fa8cbbf --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/IniFileLoader.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; + +/** + * IniFileLoader loads parameters from INI files. + * + * @author Fabien Potencier + */ +class IniFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + $path = $this->locator->locate($resource); + + $this->container->addResource(new FileResource($path)); + + $result = parse_ini_file($path, true); + if (false === $result || array() === $result) { + throw new InvalidArgumentException(sprintf('The "%s" file is not valid.', $resource)); + } + + if (isset($result['parameters']) && \is_array($result['parameters'])) { + foreach ($result['parameters'] as $key => $value) { + $this->container->setParameter($key, $value); + } + } + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && 'ini' === pathinfo($resource, PATHINFO_EXTENSION); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/PhpFileLoader.php b/vendor/symfony/dependency-injection/Loader/PhpFileLoader.php new file mode 100644 index 0000000..c52bd8c --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/PhpFileLoader.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Resource\FileResource; + +/** + * PhpFileLoader loads service definitions from a PHP file. + * + * The PHP file is required and the $container variable can be + * used within the file to change the container. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + // the container and loader variables are exposed to the included file below + $container = $this->container; + $loader = $this; + + $path = $this->locator->locate($resource); + $this->setCurrentDir(\dirname($path)); + $this->container->addResource(new FileResource($path)); + + include $path; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php b/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php new file mode 100644 index 0000000..ecebe01 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php @@ -0,0 +1,595 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * XmlFileLoader loads XML files service definitions. + * + * @author Fabien Potencier + */ +class XmlFileLoader extends FileLoader +{ + const NS = 'http://symfony.com/schema/dic/services'; + + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + $path = $this->locator->locate($resource); + + $xml = $this->parseFileToDOM($path); + + $this->container->addResource(new FileResource($path)); + + // anonymous services + $this->processAnonymousServices($xml, $path); + + // imports + $this->parseImports($xml, $path); + + // parameters + $this->parseParameters($xml); + + // extensions + $this->loadFromExtensions($xml); + + // services + $this->parseDefinitions($xml, $path); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION); + } + + /** + * Parses parameters. + * + * @param \DOMDocument $xml + */ + private function parseParameters(\DOMDocument $xml) + { + if ($parameters = $this->getChildren($xml->documentElement, 'parameters')) { + $this->container->getParameterBag()->add($this->getArgumentsAsPhp($parameters[0], 'parameter')); + } + } + + /** + * Parses imports. + * + * @param \DOMDocument $xml + * @param string $file + */ + private function parseImports(\DOMDocument $xml, $file) + { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + if (false === $imports = $xpath->query('//container:imports/container:import')) { + return; + } + + $defaultDirectory = \dirname($file); + foreach ($imports as $import) { + $this->setCurrentDir($defaultDirectory); + $this->import($import->getAttribute('resource'), null, (bool) XmlUtils::phpize($import->getAttribute('ignore-errors')), $file); + } + } + + /** + * Parses multiple definitions. + * + * @param \DOMDocument $xml + * @param string $file + */ + private function parseDefinitions(\DOMDocument $xml, $file) + { + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + if (false === $services = $xpath->query('//container:services/container:service')) { + return; + } + + foreach ($services as $service) { + if (null !== $definition = $this->parseDefinition($service, $file)) { + $this->container->setDefinition((string) $service->getAttribute('id'), $definition); + } + } + } + + /** + * Parses an individual Definition. + * + * @param \DOMElement $service + * @param string $file + * + * @return Definition|null + */ + private function parseDefinition(\DOMElement $service, $file) + { + if ($alias = $service->getAttribute('alias')) { + $public = true; + if ($publicAttr = $service->getAttribute('public')) { + $public = XmlUtils::phpize($publicAttr); + } + $this->container->setAlias((string) $service->getAttribute('id'), new Alias($alias, $public)); + + return; + } + + if ($parent = $service->getAttribute('parent')) { + $definition = new DefinitionDecorator($parent); + } else { + $definition = new Definition(); + } + + foreach (array('class', 'shared', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'lazy', 'abstract') as $key) { + if ($value = $service->getAttribute($key)) { + if (\in_array($key, array('factory-class', 'factory-method', 'factory-service'))) { + @trigger_error(sprintf('The "%s" attribute of service "%s" in file "%s" is deprecated since Symfony 2.6 and will be removed in 3.0. Use the "factory" element instead.', $key, (string) $service->getAttribute('id'), $file), E_USER_DEPRECATED); + } + $method = 'set'.str_replace('-', '', $key); + $definition->$method(XmlUtils::phpize($value)); + } + } + + if ($value = $service->getAttribute('autowire')) { + $definition->setAutowired(XmlUtils::phpize($value)); + } + + if ($value = $service->getAttribute('scope')) { + $triggerDeprecation = 'request' !== (string) $service->getAttribute('id'); + + if ($triggerDeprecation) { + @trigger_error(sprintf('The "scope" attribute of service "%s" in file "%s" is deprecated since Symfony 2.8 and will be removed in 3.0.', (string) $service->getAttribute('id'), $file), E_USER_DEPRECATED); + } + + $definition->setScope(XmlUtils::phpize($value), false); + } + + if ($value = $service->getAttribute('synchronized')) { + $triggerDeprecation = 'request' !== (string) $service->getAttribute('id'); + + if ($triggerDeprecation) { + @trigger_error(sprintf('The "synchronized" attribute of service "%s" in file "%s" is deprecated since Symfony 2.7 and will be removed in 3.0.', (string) $service->getAttribute('id'), $file), E_USER_DEPRECATED); + } + + $definition->setSynchronized(XmlUtils::phpize($value), $triggerDeprecation); + } + + if ($files = $this->getChildren($service, 'file')) { + $definition->setFile($files[0]->nodeValue); + } + + if ($deprecated = $this->getChildren($service, 'deprecated')) { + $definition->setDeprecated(true, $deprecated[0]->nodeValue ?: null); + } + + $definition->setArguments($this->getArgumentsAsPhp($service, 'argument')); + $definition->setProperties($this->getArgumentsAsPhp($service, 'property')); + + if ($factories = $this->getChildren($service, 'factory')) { + $factory = $factories[0]; + if ($function = $factory->getAttribute('function')) { + $definition->setFactory($function); + } else { + $factoryService = $this->getChildren($factory, 'service'); + + if (isset($factoryService[0])) { + $class = $this->parseDefinition($factoryService[0], $file); + } elseif ($childService = $factory->getAttribute('service')) { + $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false); + } else { + $class = $factory->getAttribute('class'); + } + + $definition->setFactory(array($class, $factory->getAttribute('method'))); + } + } + + if ($configurators = $this->getChildren($service, 'configurator')) { + $configurator = $configurators[0]; + if ($function = $configurator->getAttribute('function')) { + $definition->setConfigurator($function); + } else { + $configuratorService = $this->getChildren($configurator, 'service'); + + if (isset($configuratorService[0])) { + $class = $this->parseDefinition($configuratorService[0], $file); + } elseif ($childService = $configurator->getAttribute('service')) { + $class = new Reference($childService, ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, false); + } else { + $class = $configurator->getAttribute('class'); + } + + $definition->setConfigurator(array($class, $configurator->getAttribute('method'))); + } + } + + foreach ($this->getChildren($service, 'call') as $call) { + $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument')); + } + + foreach ($this->getChildren($service, 'tag') as $tag) { + $parameters = array(); + foreach ($tag->attributes as $name => $node) { + if ('name' === $name) { + continue; + } + + if (false !== strpos($name, '-') && false === strpos($name, '_') && !array_key_exists($normalizedName = str_replace('-', '_', $name), $parameters)) { + $parameters[$normalizedName] = XmlUtils::phpize($node->nodeValue); + } + // keep not normalized key for BC too + $parameters[$name] = XmlUtils::phpize($node->nodeValue); + } + + if ('' === $tag->getAttribute('name')) { + throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', (string) $service->getAttribute('id'), $file)); + } + + $definition->addTag($tag->getAttribute('name'), $parameters); + } + + foreach ($this->getChildren($service, 'autowiring-type') as $type) { + $definition->addAutowiringType($type->textContent); + } + + if ($value = $service->getAttribute('decorates')) { + $renameId = $service->hasAttribute('decoration-inner-name') ? $service->getAttribute('decoration-inner-name') : null; + $priority = $service->hasAttribute('decoration-priority') ? $service->getAttribute('decoration-priority') : 0; + $definition->setDecoratedService($value, $renameId, $priority); + } + + return $definition; + } + + /** + * Parses a XML file to a \DOMDocument. + * + * @param string $file Path to a file + * + * @return \DOMDocument + * + * @throws InvalidArgumentException When loading of XML file returns error + */ + private function parseFileToDOM($file) + { + try { + $dom = XmlUtils::loadFile($file, array($this, 'validateSchema')); + } catch (\InvalidArgumentException $e) { + throw new InvalidArgumentException(sprintf('Unable to parse file "%s".', $file), $e->getCode(), $e); + } + + $this->validateExtensions($dom, $file); + + return $dom; + } + + /** + * Processes anonymous services. + * + * @param \DOMDocument $xml + * @param string $file + */ + private function processAnonymousServices(\DOMDocument $xml, $file) + { + $definitions = array(); + $count = 0; + + $xpath = new \DOMXPath($xml); + $xpath->registerNamespace('container', self::NS); + + // anonymous services as arguments/properties + if (false !== $nodes = $xpath->query('//container:argument[@type="service"][not(@id)]|//container:property[@type="service"][not(@id)]')) { + foreach ($nodes as $node) { + // give it a unique name + $id = sprintf('%s_%d', hash('sha256', $file), ++$count); + $node->setAttribute('id', $id); + + if ($services = $this->getChildren($node, 'service')) { + $definitions[$id] = array($services[0], $file, false); + $services[0]->setAttribute('id', $id); + + // anonymous services are always private + // we could not use the constant false here, because of XML parsing + $services[0]->setAttribute('public', 'false'); + } + } + } + + // anonymous services "in the wild" + if (false !== $nodes = $xpath->query('//container:services/container:service[not(@id)]')) { + foreach ($nodes as $node) { + // give it a unique name + $id = sprintf('%s_%d', hash('sha256', $file), ++$count); + $node->setAttribute('id', $id); + $definitions[$id] = array($node, $file, true); + } + } + + // resolve definitions + krsort($definitions); + foreach ($definitions as $id => $def) { + list($domElement, $file, $wild) = $def; + + if (null !== $definition = $this->parseDefinition($domElement, $file)) { + $this->container->setDefinition($id, $definition); + } + + if (true === $wild) { + $tmpDomElement = new \DOMElement('_services', null, self::NS); + $domElement->parentNode->replaceChild($tmpDomElement, $domElement); + $tmpDomElement->setAttribute('id', $id); + } else { + if (null !== $domElement->parentNode) { + $domElement->parentNode->removeChild($domElement); + } + } + } + } + + /** + * Returns arguments as valid php types. + * + * @param \DOMElement $node + * @param string $name + * @param bool $lowercase + * + * @return mixed + */ + private function getArgumentsAsPhp(\DOMElement $node, $name, $lowercase = true) + { + $arguments = array(); + foreach ($this->getChildren($node, $name) as $arg) { + if ($arg->hasAttribute('name')) { + $arg->setAttribute('key', $arg->getAttribute('name')); + } + + // this is used by DefinitionDecorator to overwrite a specific + // argument of the parent definition + if ($arg->hasAttribute('index')) { + $key = 'index_'.$arg->getAttribute('index'); + } elseif (!$arg->hasAttribute('key')) { + // Append an empty argument, then fetch its key to overwrite it later + $arguments[] = null; + $keys = array_keys($arguments); + $key = array_pop($keys); + } else { + $key = $arg->getAttribute('key'); + + // parameter keys are case insensitive + if ('parameter' == $name && $lowercase) { + $key = strtolower($key); + } + } + + switch ($arg->getAttribute('type')) { + case 'service': + $onInvalid = $arg->getAttribute('on-invalid'); + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if ('ignore' == $onInvalid) { + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif ('null' == $onInvalid) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } + + if ($strict = $arg->getAttribute('strict')) { + $strict = XmlUtils::phpize($strict); + } else { + $strict = true; + } + + $arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior, $strict); + break; + case 'expression': + $arguments[$key] = new Expression($arg->nodeValue); + break; + case 'collection': + $arguments[$key] = $this->getArgumentsAsPhp($arg, $name, false); + break; + case 'string': + $arguments[$key] = $arg->nodeValue; + break; + case 'constant': + $arguments[$key] = \constant(trim($arg->nodeValue)); + break; + default: + $arguments[$key] = XmlUtils::phpize($arg->nodeValue); + } + } + + return $arguments; + } + + /** + * Get child elements by name. + * + * @param \DOMNode $node + * @param mixed $name + * + * @return array + */ + private function getChildren(\DOMNode $node, $name) + { + $children = array(); + foreach ($node->childNodes as $child) { + if ($child instanceof \DOMElement && $child->localName === $name && self::NS === $child->namespaceURI) { + $children[] = $child; + } + } + + return $children; + } + + /** + * Validates a documents XML schema. + * + * @param \DOMDocument $dom + * + * @return bool + * + * @throws RuntimeException When extension references a non-existent XSD file + */ + public function validateSchema(\DOMDocument $dom) + { + $schemaLocations = array('http://symfony.com/schema/dic/services' => str_replace('\\', '/', __DIR__.'/schema/dic/services/services-1.0.xsd')); + + if ($element = $dom->documentElement->getAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'schemaLocation')) { + $items = preg_split('/\s+/', $element); + for ($i = 0, $nb = \count($items); $i < $nb; $i += 2) { + if (!$this->container->hasExtension($items[$i])) { + continue; + } + + if (($extension = $this->container->getExtension($items[$i])) && false !== $extension->getXsdValidationBasePath()) { + $path = str_replace($extension->getNamespace(), str_replace('\\', '/', $extension->getXsdValidationBasePath()).'/', $items[$i + 1]); + + if (!is_file($path)) { + throw new RuntimeException(sprintf('Extension "%s" references a non-existent XSD file "%s"', \get_class($extension), $path)); + } + + $schemaLocations[$items[$i]] = $path; + } + } + } + + $tmpfiles = array(); + $imports = ''; + foreach ($schemaLocations as $namespace => $location) { + $parts = explode('/', $location); + $locationstart = 'file:///'; + if (0 === stripos($location, 'phar://')) { + $tmpfile = tempnam(sys_get_temp_dir(), 'sf2'); + if ($tmpfile) { + copy($location, $tmpfile); + $tmpfiles[] = $tmpfile; + $parts = explode('/', str_replace('\\', '/', $tmpfile)); + } else { + array_shift($parts); + $locationstart = 'phar:///'; + } + } + $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : ''; + $location = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts)); + + $imports .= sprintf(' '."\n", $namespace, $location); + } + + $source = << + + + +$imports + +EOF + ; + + $disableEntities = libxml_disable_entity_loader(false); + $valid = @$dom->schemaValidateSource($source); + libxml_disable_entity_loader($disableEntities); + + foreach ($tmpfiles as $tmpfile) { + @unlink($tmpfile); + } + + return $valid; + } + + /** + * Validates an extension. + * + * @param \DOMDocument $dom + * @param string $file + * + * @throws InvalidArgumentException When no extension is found corresponding to a tag + */ + private function validateExtensions(\DOMDocument $dom, $file) + { + foreach ($dom->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement || 'http://symfony.com/schema/dic/services' === $node->namespaceURI) { + continue; + } + + // can it be handled by an extension? + if (!$this->container->hasExtension($node->namespaceURI)) { + $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getNamespace(); }, $this->container->getExtensions())); + throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none')); + } + } + } + + /** + * Loads from an extension. + * + * @param \DOMDocument $xml + */ + private function loadFromExtensions(\DOMDocument $xml) + { + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement || self::NS === $node->namespaceURI) { + continue; + } + + $values = static::convertDomElementToArray($node); + if (!\is_array($values)) { + $values = array(); + } + + $this->container->loadFromExtension($node->namespaceURI, $values); + } + } + + /** + * Converts a \DOMElement object to a PHP array. + * + * The following rules applies during the conversion: + * + * * Each tag is converted to a key value or an array + * if there is more than one "value" + * + * * The content of a tag is set under a "value" key (bar) + * if the tag also has some nested tags + * + * * The attributes are converted to keys () + * + * * The nested-tags are converted to keys (bar) + * + * @param \DOMElement $element A \DOMElement instance + * + * @return array A PHP array + */ + public static function convertDomElementToArray(\DOMElement $element) + { + return XmlUtils::convertDomElementToArray($element); + } +} diff --git a/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php b/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php new file mode 100644 index 0000000..d25f654 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php @@ -0,0 +1,463 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Loader; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; +use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\ExpressionLanguage\Expression; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; + +/** + * YamlFileLoader loads YAML files service definitions. + * + * The YAML format does not support anonymous services (cf. the XML loader). + * + * @author Fabien Potencier + */ +class YamlFileLoader extends FileLoader +{ + private $yamlParser; + + /** + * {@inheritdoc} + */ + public function load($resource, $type = null) + { + $path = $this->locator->locate($resource); + + $content = $this->loadFile($path); + + $this->container->addResource(new FileResource($path)); + + // empty file + if (null === $content) { + return; + } + + // imports + $this->parseImports($content, $path); + + // parameters + if (isset($content['parameters'])) { + if (!\is_array($content['parameters'])) { + throw new InvalidArgumentException(sprintf('The "parameters" key should contain an array in %s. Check your YAML syntax.', $resource)); + } + + foreach ($content['parameters'] as $key => $value) { + $this->container->setParameter($key, $this->resolveServices($value)); + } + } + + // extensions + $this->loadFromExtensions($content); + + // services + $this->parseDefinitions($content, $resource); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && \in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true); + } + + /** + * Parses all imports. + * + * @param array $content + * @param string $file + */ + private function parseImports(array $content, $file) + { + if (!isset($content['imports'])) { + return; + } + + if (!\is_array($content['imports'])) { + throw new InvalidArgumentException(sprintf('The "imports" key should contain an array in %s. Check your YAML syntax.', $file)); + } + + $defaultDirectory = \dirname($file); + foreach ($content['imports'] as $import) { + if (!\is_array($import)) { + throw new InvalidArgumentException(sprintf('The values in the "imports" key should be arrays in %s. Check your YAML syntax.', $file)); + } + + $this->setCurrentDir($defaultDirectory); + $this->import($import['resource'], null, isset($import['ignore_errors']) ? (bool) $import['ignore_errors'] : false, $file); + } + } + + /** + * Parses definitions. + * + * @param array $content + * @param string $file + */ + private function parseDefinitions(array $content, $file) + { + if (!isset($content['services'])) { + return; + } + + if (!\is_array($content['services'])) { + throw new InvalidArgumentException(sprintf('The "services" key should contain an array in %s. Check your YAML syntax.', $file)); + } + + foreach ($content['services'] as $id => $service) { + $this->parseDefinition($id, $service, $file); + } + } + + /** + * Parses a definition. + * + * @param string $id + * @param array|string $service + * @param string $file + * + * @throws InvalidArgumentException When tags are invalid + */ + private function parseDefinition($id, $service, $file) + { + if (\is_string($service) && 0 === strpos($service, '@')) { + $this->container->setAlias($id, substr($service, 1)); + + return; + } + + if (!\is_array($service)) { + throw new InvalidArgumentException(sprintf('A service definition must be an array or a string starting with "@" but %s found for service "%s" in %s. Check your YAML syntax.', \gettype($service), $id, $file)); + } + + if (isset($service['alias'])) { + $public = !array_key_exists('public', $service) || (bool) $service['public']; + $this->container->setAlias($id, new Alias($service['alias'], $public)); + + return; + } + + if (isset($service['parent'])) { + $definition = new DefinitionDecorator($service['parent']); + } else { + $definition = new Definition(); + } + + if (isset($service['class'])) { + $definition->setClass($service['class']); + } + + if (isset($service['shared'])) { + $definition->setShared($service['shared']); + } + + if (isset($service['scope'])) { + if ('request' !== $id) { + @trigger_error(sprintf('The "scope" key of service "%s" in file "%s" is deprecated since Symfony 2.8 and will be removed in 3.0.', $id, $file), E_USER_DEPRECATED); + } + $definition->setScope($service['scope'], false); + } + + if (isset($service['synthetic'])) { + $definition->setSynthetic($service['synthetic']); + } + + if (isset($service['synchronized'])) { + @trigger_error(sprintf('The "synchronized" key of service "%s" in file "%s" is deprecated since Symfony 2.7 and will be removed in 3.0.', $id, $file), E_USER_DEPRECATED); + $definition->setSynchronized($service['synchronized'], 'request' !== $id); + } + + if (isset($service['lazy'])) { + $definition->setLazy($service['lazy']); + } + + if (isset($service['public'])) { + $definition->setPublic($service['public']); + } + + if (isset($service['abstract'])) { + $definition->setAbstract($service['abstract']); + } + + if (array_key_exists('deprecated', $service)) { + $definition->setDeprecated(true, $service['deprecated']); + } + + if (isset($service['factory'])) { + if (\is_string($service['factory'])) { + if (false !== strpos($service['factory'], ':') && false === strpos($service['factory'], '::')) { + $parts = explode(':', $service['factory']); + $definition->setFactory(array($this->resolveServices('@'.$parts[0]), $parts[1])); + } else { + $definition->setFactory($service['factory']); + } + } else { + $definition->setFactory(array($this->resolveServices($service['factory'][0]), $service['factory'][1])); + } + } + + if (isset($service['factory_class'])) { + @trigger_error(sprintf('The "factory_class" key of service "%s" in file "%s" is deprecated since Symfony 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED); + $definition->setFactoryClass($service['factory_class']); + } + + if (isset($service['factory_method'])) { + @trigger_error(sprintf('The "factory_method" key of service "%s" in file "%s" is deprecated since Symfony 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED); + $definition->setFactoryMethod($service['factory_method']); + } + + if (isset($service['factory_service'])) { + @trigger_error(sprintf('The "factory_service" key of service "%s" in file "%s" is deprecated since Symfony 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED); + $definition->setFactoryService($service['factory_service']); + } + + if (isset($service['file'])) { + $definition->setFile($service['file']); + } + + if (isset($service['arguments'])) { + $definition->setArguments($this->resolveServices($service['arguments'])); + } + + if (isset($service['properties'])) { + $definition->setProperties($this->resolveServices($service['properties'])); + } + + if (isset($service['configurator'])) { + if (\is_string($service['configurator'])) { + $definition->setConfigurator($service['configurator']); + } else { + $definition->setConfigurator(array($this->resolveServices($service['configurator'][0]), $service['configurator'][1])); + } + } + + if (isset($service['calls'])) { + if (!\is_array($service['calls'])) { + throw new InvalidArgumentException(sprintf('Parameter "calls" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + + foreach ($service['calls'] as $call) { + if (isset($call['method'])) { + $method = $call['method']; + $args = isset($call['arguments']) ? $this->resolveServices($call['arguments']) : array(); + } else { + $method = $call[0]; + $args = isset($call[1]) ? $this->resolveServices($call[1]) : array(); + } + + $definition->addMethodCall($method, $args); + } + } + + if (isset($service['tags'])) { + if (!\is_array($service['tags'])) { + throw new InvalidArgumentException(sprintf('Parameter "tags" must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + + foreach ($service['tags'] as $tag) { + if (!\is_array($tag)) { + throw new InvalidArgumentException(sprintf('A "tags" entry must be an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + + if (!isset($tag['name'])) { + throw new InvalidArgumentException(sprintf('A "tags" entry is missing a "name" key for service "%s" in %s.', $id, $file)); + } + + if (!\is_string($tag['name']) || '' === $tag['name']) { + throw new InvalidArgumentException(sprintf('The tag name for service "%s" in %s must be a non-empty string.', $id, $file)); + } + + $name = $tag['name']; + unset($tag['name']); + + foreach ($tag as $attribute => $value) { + if (!is_scalar($value) && null !== $value) { + throw new InvalidArgumentException(sprintf('A "tags" attribute must be of a scalar-type for service "%s", tag "%s", attribute "%s" in %s. Check your YAML syntax.', $id, $name, $attribute, $file)); + } + } + + $definition->addTag($name, $tag); + } + } + + if (isset($service['decorates'])) { + if ('' !== $service['decorates'] && '@' === $service['decorates'][0]) { + throw new InvalidArgumentException(sprintf('The value of the "decorates" option for the "%s" service must be the id of the service without the "@" prefix (replace "%s" with "%s").', $id, $service['decorates'], substr($service['decorates'], 1))); + } + + $renameId = isset($service['decoration_inner_name']) ? $service['decoration_inner_name'] : null; + $priority = isset($service['decoration_priority']) ? $service['decoration_priority'] : 0; + $definition->setDecoratedService($service['decorates'], $renameId, $priority); + } + + if (isset($service['autowire'])) { + $definition->setAutowired($service['autowire']); + } + + if (isset($service['autowiring_types'])) { + if (\is_string($service['autowiring_types'])) { + $definition->addAutowiringType($service['autowiring_types']); + } else { + if (!\is_array($service['autowiring_types'])) { + throw new InvalidArgumentException(sprintf('Parameter "autowiring_types" must be a string or an array for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + + foreach ($service['autowiring_types'] as $autowiringType) { + if (!\is_string($autowiringType)) { + throw new InvalidArgumentException(sprintf('A "autowiring_types" attribute must be of type string for service "%s" in %s. Check your YAML syntax.', $id, $file)); + } + + $definition->addAutowiringType($autowiringType); + } + } + } + + $this->container->setDefinition($id, $definition); + } + + /** + * Loads a YAML file. + * + * @param string $file + * + * @return array The file content + * + * @throws InvalidArgumentException when the given file is not a local file or when it does not exist + */ + protected function loadFile($file) + { + if (!class_exists('Symfony\Component\Yaml\Parser')) { + throw new RuntimeException('Unable to load YAML config files as the Symfony Yaml Component is not installed.'); + } + + if (!stream_is_local($file)) { + throw new InvalidArgumentException(sprintf('This is not a local file "%s".', $file)); + } + + if (!file_exists($file)) { + throw new InvalidArgumentException(sprintf('The file "%s" does not exist.', $file)); + } + + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + try { + $configuration = $this->yamlParser->parse(file_get_contents($file)); + } catch (ParseException $e) { + throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e); + } + + return $this->validate($configuration, $file); + } + + /** + * Validates a YAML file. + * + * @param mixed $content + * @param string $file + * + * @return array + * + * @throws InvalidArgumentException When service file is not valid + */ + private function validate($content, $file) + { + if (null === $content) { + return $content; + } + + if (!\is_array($content)) { + throw new InvalidArgumentException(sprintf('The service file "%s" is not valid. It should contain an array. Check your YAML syntax.', $file)); + } + + foreach ($content as $namespace => $data) { + if (\in_array($namespace, array('imports', 'parameters', 'services'))) { + continue; + } + + if (!$this->container->hasExtension($namespace)) { + $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions())); + throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', $namespace, $file, $namespace, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none')); + } + } + + return $content; + } + + /** + * Resolves services. + * + * @param string|array $value + * + * @return array|string|Reference + */ + private function resolveServices($value) + { + if (\is_array($value)) { + $value = array_map(array($this, 'resolveServices'), $value); + } elseif (\is_string($value) && 0 === strpos($value, '@=')) { + return new Expression(substr($value, 2)); + } elseif (\is_string($value) && 0 === strpos($value, '@')) { + if (0 === strpos($value, '@@')) { + $value = substr($value, 1); + $invalidBehavior = null; + } elseif (0 === strpos($value, '@?')) { + $value = substr($value, 2); + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } else { + $value = substr($value, 1); + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + } + + if ('=' === substr($value, -1)) { + $value = substr($value, 0, -1); + $strict = false; + } else { + $strict = true; + } + + if (null !== $invalidBehavior) { + $value = new Reference($value, $invalidBehavior, $strict); + } + } + + return $value; + } + + /** + * Loads from Extensions. + */ + private function loadFromExtensions(array $content) + { + foreach ($content as $namespace => $values) { + if (\in_array($namespace, array('imports', 'parameters', 'services'))) { + continue; + } + + if (!\is_array($values) && null !== $values) { + $values = array(); + } + + $this->container->loadFromExtension($namespace, $values); + } + } +} diff --git a/vendor/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd b/vendor/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd new file mode 100644 index 0000000..a8d7cf8 --- /dev/null +++ b/vendor/symfony/dependency-injection/Loader/schema/dic/services/services-1.0.xsd @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/dependency-injection/Parameter.php b/vendor/symfony/dependency-injection/Parameter.php new file mode 100644 index 0000000..cac6f6c --- /dev/null +++ b/vendor/symfony/dependency-injection/Parameter.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Parameter represents a parameter reference. + * + * @author Fabien Potencier + */ +class Parameter +{ + private $id; + + /** + * @param string $id The parameter key + */ + public function __construct($id) + { + $this->id = $id; + } + + /** + * @return string The parameter key + */ + public function __toString() + { + return (string) $this->id; + } +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php b/vendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php new file mode 100644 index 0000000..ad65ad9 --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/FrozenParameterBag.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * Holds read-only parameters. + * + * @author Fabien Potencier + */ +class FrozenParameterBag extends ParameterBag +{ + /** + * For performance reasons, the constructor assumes that + * all keys are already lowercased. + * + * This is always the case when used internally. + * + * @param array $parameters An array of parameters + */ + public function __construct(array $parameters = array()) + { + $this->parameters = $parameters; + $this->resolved = true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + throw new LogicException('Impossible to call clear() on a frozen ParameterBag.'); + } + + /** + * {@inheritdoc} + */ + public function add(array $parameters) + { + throw new LogicException('Impossible to call add() on a frozen ParameterBag.'); + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + throw new LogicException('Impossible to call remove() on a frozen ParameterBag.'); + } +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/ParameterBag.php b/vendor/symfony/dependency-injection/ParameterBag/ParameterBag.php new file mode 100644 index 0000000..c17029b --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/ParameterBag.php @@ -0,0 +1,275 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; +use Symfony\Component\DependencyInjection\Exception\RuntimeException; + +/** + * Holds parameters. + * + * @author Fabien Potencier + */ +class ParameterBag implements ParameterBagInterface +{ + protected $parameters = array(); + protected $resolved = false; + + /** + * @param array $parameters An array of parameters + */ + public function __construct(array $parameters = array()) + { + $this->add($parameters); + } + + /** + * Clears all parameters. + */ + public function clear() + { + $this->parameters = array(); + } + + /** + * Adds parameters to the service container parameters. + * + * @param array $parameters An array of parameters + */ + public function add(array $parameters) + { + foreach ($parameters as $key => $value) { + $this->parameters[strtolower($key)] = $value; + } + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->parameters; + } + + /** + * {@inheritdoc} + */ + public function get($name) + { + $name = strtolower($name); + + if (!array_key_exists($name, $this->parameters)) { + if (!$name) { + throw new ParameterNotFoundException($name); + } + + $alternatives = array(); + foreach ($this->parameters as $key => $parameterValue) { + $lev = levenshtein($name, $key); + if ($lev <= \strlen($name) / 3 || false !== strpos($key, $name)) { + $alternatives[] = $key; + } + } + + throw new ParameterNotFoundException($name, null, null, null, $alternatives); + } + + return $this->parameters[$name]; + } + + /** + * Sets a service container parameter. + * + * @param string $name The parameter name + * @param mixed $value The parameter value + */ + public function set($name, $value) + { + $this->parameters[strtolower($name)] = $value; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return array_key_exists(strtolower($name), $this->parameters); + } + + /** + * Removes a parameter. + * + * @param string $name The parameter name + */ + public function remove($name) + { + unset($this->parameters[strtolower($name)]); + } + + /** + * {@inheritdoc} + */ + public function resolve() + { + if ($this->resolved) { + return; + } + + $parameters = array(); + foreach ($this->parameters as $key => $value) { + try { + $value = $this->resolveValue($value); + $parameters[$key] = $this->unescapeValue($value); + } catch (ParameterNotFoundException $e) { + $e->setSourceKey($key); + + throw $e; + } + } + + $this->parameters = $parameters; + $this->resolved = true; + } + + /** + * Replaces parameter placeholders (%name%) by their values. + * + * @param mixed $value A value + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * + * @return mixed The resolved value + * + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + * @throws ParameterCircularReferenceException if a circular reference if detected + * @throws RuntimeException when a given parameter has a type problem + */ + public function resolveValue($value, array $resolving = array()) + { + if (\is_array($value)) { + $args = array(); + foreach ($value as $k => $v) { + $args[$this->resolveValue($k, $resolving)] = $this->resolveValue($v, $resolving); + } + + return $args; + } + + if (!\is_string($value)) { + return $value; + } + + return $this->resolveString($value, $resolving); + } + + /** + * Resolves parameters inside a string. + * + * @param string $value The string to resolve + * @param array $resolving An array of keys that are being resolved (used internally to detect circular references) + * + * @return string The resolved string + * + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + * @throws ParameterCircularReferenceException if a circular reference if detected + * @throws RuntimeException when a given parameter has a type problem + */ + public function resolveString($value, array $resolving = array()) + { + // we do this to deal with non string values (Boolean, integer, ...) + // as the preg_replace_callback throw an exception when trying + // a non-string in a parameter value + if (preg_match('/^%([^%\s]+)%$/', $value, $match)) { + $key = strtolower($match[1]); + + if (isset($resolving[$key])) { + throw new ParameterCircularReferenceException(array_keys($resolving)); + } + + $resolving[$key] = true; + + return $this->resolved ? $this->get($key) : $this->resolveValue($this->get($key), $resolving); + } + + $self = $this; + + return preg_replace_callback('/%%|%([^%\s]+)%/', function ($match) use ($self, $resolving, $value) { + // skip %% + if (!isset($match[1])) { + return '%%'; + } + + $key = strtolower($match[1]); + if (isset($resolving[$key])) { + throw new ParameterCircularReferenceException(array_keys($resolving)); + } + + $resolved = $self->get($key); + + if (!\is_string($resolved) && !is_numeric($resolved)) { + throw new RuntimeException(sprintf('A string value must be composed of strings and/or numbers, but found parameter "%s" of type %s inside string value "%s".', $key, \gettype($resolved), $value)); + } + + $resolved = (string) $resolved; + $resolving[$key] = true; + + return $self->isResolved() ? $resolved : $self->resolveString($resolved, $resolving); + }, $value); + } + + public function isResolved() + { + return $this->resolved; + } + + /** + * {@inheritdoc} + */ + public function escapeValue($value) + { + if (\is_string($value)) { + return str_replace('%', '%%', $value); + } + + if (\is_array($value)) { + $result = array(); + foreach ($value as $k => $v) { + $result[$k] = $this->escapeValue($v); + } + + return $result; + } + + return $value; + } + + /** + * {@inheritdoc} + */ + public function unescapeValue($value) + { + if (\is_string($value)) { + return str_replace('%%', '%', $value); + } + + if (\is_array($value)) { + $result = array(); + foreach ($value as $k => $v) { + $result[$k] = $this->unescapeValue($v); + } + + return $result; + } + + return $value; + } +} diff --git a/vendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php b/vendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php new file mode 100644 index 0000000..3291b37 --- /dev/null +++ b/vendor/symfony/dependency-injection/ParameterBag/ParameterBagInterface.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\ParameterBag; + +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; + +/** + * ParameterBagInterface. + * + * @author Fabien Potencier + */ +interface ParameterBagInterface +{ + /** + * Clears all parameters. + * + * @throws LogicException if the ParameterBagInterface can not be cleared + */ + public function clear(); + + /** + * Adds parameters to the service container parameters. + * + * @param array $parameters An array of parameters + * + * @throws LogicException if the parameter can not be added + */ + public function add(array $parameters); + + /** + * Gets the service container parameters. + * + * @return array An array of parameters + */ + public function all(); + + /** + * Gets a service container parameter. + * + * @param string $name The parameter name + * + * @return mixed The parameter value + * + * @throws ParameterNotFoundException if the parameter is not defined + */ + public function get($name); + + /** + * Sets a service container parameter. + * + * @param string $name The parameter name + * @param mixed $value The parameter value + * + * @throws LogicException if the parameter can not be set + */ + public function set($name, $value); + + /** + * Returns true if a parameter name is defined. + * + * @param string $name The parameter name + * + * @return bool true if the parameter name is defined, false otherwise + */ + public function has($name); + + /** + * Replaces parameter placeholders (%name%) by their values for all parameters. + */ + public function resolve(); + + /** + * Replaces parameter placeholders (%name%) by their values. + * + * @param mixed $value A value + * + * @throws ParameterNotFoundException if a placeholder references a parameter that does not exist + */ + public function resolveValue($value); + + /** + * Escape parameter placeholders %. + * + * @param mixed $value + * + * @return mixed + */ + public function escapeValue($value); + + /** + * Unescape parameter placeholders %. + * + * @param mixed $value + * + * @return mixed + */ + public function unescapeValue($value); +} diff --git a/vendor/symfony/dependency-injection/Reference.php b/vendor/symfony/dependency-injection/Reference.php new file mode 100644 index 0000000..869dfae --- /dev/null +++ b/vendor/symfony/dependency-injection/Reference.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Reference represents a service reference. + * + * @author Fabien Potencier + */ +class Reference +{ + private $id; + private $invalidBehavior; + private $strict; + + /** + * Note: The $strict parameter is deprecated since version 2.8 and will be removed in 3.0. + * + * @param string $id The service identifier + * @param int $invalidBehavior The behavior when the service does not exist + * @param bool $strict Sets how this reference is validated + * + * @see Container + */ + public function __construct($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $strict = true) + { + $this->id = strtolower($id); + $this->invalidBehavior = $invalidBehavior; + $this->strict = $strict; + } + + /** + * @return string The service identifier + */ + public function __toString() + { + return $this->id; + } + + /** + * Returns the behavior to be used when the service does not exist. + * + * @return int + */ + public function getInvalidBehavior() + { + return $this->invalidBehavior; + } + + /** + * Returns true when this Reference is strict. + * + * @return bool + * + * @deprecated since version 2.8, to be removed in 3.0. + */ + public function isStrict($triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + return $this->strict; + } +} diff --git a/vendor/symfony/dependency-injection/ResettableContainerInterface.php b/vendor/symfony/dependency-injection/ResettableContainerInterface.php new file mode 100644 index 0000000..b74e676 --- /dev/null +++ b/vendor/symfony/dependency-injection/ResettableContainerInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * ResettableContainerInterface defines additional resetting functionality + * for containers, allowing to release shared services when the container is + * not needed anymore. + * + * @author Christophe Coevoet + */ +interface ResettableContainerInterface extends ContainerInterface +{ + /** + * Resets shared services from the container. + * + * The container is not intended to be used again after being reset in a normal workflow. This method is + * meant as a way to release references for ref-counting. + * A subsequent call to ContainerInterface::get will recreate a new instance of the shared service. + */ + public function reset(); +} diff --git a/vendor/symfony/dependency-injection/Scope.php b/vendor/symfony/dependency-injection/Scope.php new file mode 100644 index 0000000..b0b8ed6 --- /dev/null +++ b/vendor/symfony/dependency-injection/Scope.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Scope class. + * + * @author Johannes M. Schmitt + * + * @deprecated since version 2.8, to be removed in 3.0. + */ +class Scope implements ScopeInterface +{ + private $name; + private $parentName; + + public function __construct($name, $parentName = ContainerInterface::SCOPE_CONTAINER) + { + $this->name = $name; + $this->parentName = $parentName; + } + + public function getName() + { + return $this->name; + } + + public function getParentName() + { + return $this->parentName; + } +} diff --git a/vendor/symfony/dependency-injection/ScopeInterface.php b/vendor/symfony/dependency-injection/ScopeInterface.php new file mode 100644 index 0000000..11b1097 --- /dev/null +++ b/vendor/symfony/dependency-injection/ScopeInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Scope Interface. + * + * @author Johannes M. Schmitt + * + * @deprecated since version 2.8, to be removed in 3.0. + */ +interface ScopeInterface +{ + public function getName(); + + public function getParentName(); +} diff --git a/vendor/symfony/dependency-injection/SimpleXMLElement.php b/vendor/symfony/dependency-injection/SimpleXMLElement.php new file mode 100644 index 0000000..bb985ac --- /dev/null +++ b/vendor/symfony/dependency-injection/SimpleXMLElement.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +@trigger_error('The '.__NAMESPACE__.'\SimpleXMLElement class is deprecated since Symfony 2.5 and will be removed in 3.0.', E_USER_DEPRECATED); + +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\ExpressionLanguage\Expression; + +/** + * SimpleXMLElement class. + * + * @author Fabien Potencier + * + * @deprecated since version 2.5, to be removed in 3.0. + */ +class SimpleXMLElement extends \SimpleXMLElement +{ + /** + * Converts an attribute as a PHP type. + * + * @param string $name + * + * @return mixed + */ + public function getAttributeAsPhp($name) + { + return self::phpize($this[$name]); + } + + /** + * Returns arguments as valid PHP types. + * + * @param string $name + * @param bool $lowercase + * + * @return mixed + */ + public function getArgumentsAsPhp($name, $lowercase = true) + { + $arguments = array(); + foreach ($this->$name as $arg) { + if (isset($arg['name'])) { + $arg['key'] = (string) $arg['name']; + } + $key = isset($arg['key']) ? (string) $arg['key'] : (!$arguments ? 0 : max(array_keys($arguments)) + 1); + + // parameter keys are case insensitive + if ('parameter' == $name && $lowercase) { + $key = strtolower($key); + } + + // this is used by DefinitionDecorator to overwrite a specific + // argument of the parent definition + if (isset($arg['index'])) { + $key = 'index_'.$arg['index']; + } + + switch ($arg['type']) { + case 'service': + $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE; + if (isset($arg['on-invalid']) && 'ignore' == $arg['on-invalid']) { + $invalidBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; + } elseif (isset($arg['on-invalid']) && 'null' == $arg['on-invalid']) { + $invalidBehavior = ContainerInterface::NULL_ON_INVALID_REFERENCE; + } + + if (isset($arg['strict'])) { + $strict = self::phpize($arg['strict']); + } else { + $strict = true; + } + + $arguments[$key] = new Reference((string) $arg['id'], $invalidBehavior, $strict); + break; + case 'expression': + $arguments[$key] = new Expression((string) $arg); + break; + case 'collection': + $arguments[$key] = $arg->getArgumentsAsPhp($name, false); + break; + case 'string': + $arguments[$key] = (string) $arg; + break; + case 'constant': + $arguments[$key] = \constant((string) $arg); + break; + default: + $arguments[$key] = self::phpize($arg); + } + } + + return $arguments; + } + + /** + * Converts an xml value to a PHP type. + * + * @param mixed $value + * + * @return mixed + */ + public static function phpize($value) + { + return XmlUtils::phpize($value); + } +} diff --git a/vendor/symfony/dependency-injection/TaggedContainerInterface.php b/vendor/symfony/dependency-injection/TaggedContainerInterface.php new file mode 100644 index 0000000..90b297f --- /dev/null +++ b/vendor/symfony/dependency-injection/TaggedContainerInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * TaggedContainerInterface is the interface implemented when a container knows how to deals with tags. + * + * @author Fabien Potencier + */ +interface TaggedContainerInterface extends ContainerInterface +{ + /** + * Returns service ids for a given tag. + * + * @param string $name The tag name + * + * @return array An array of tags + */ + public function findTaggedServiceIds($name); +} diff --git a/vendor/symfony/dependency-injection/Variable.php b/vendor/symfony/dependency-injection/Variable.php new file mode 100644 index 0000000..9654ee4 --- /dev/null +++ b/vendor/symfony/dependency-injection/Variable.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection; + +/** + * Represents a variable. + * + * $var = new Variable('a'); + * + * will be dumped as + * + * $a + * + * by the PHP dumper. + * + * @author Johannes M. Schmitt + */ +class Variable +{ + private $name; + + /** + * @param string $name + */ + public function __construct($name) + { + $this->name = $name; + } + + public function __toString() + { + return $this->name; + } +} diff --git a/vendor/symfony/dependency-injection/composer.json b/vendor/symfony/dependency-injection/composer.json new file mode 100644 index 0000000..2506690 --- /dev/null +++ b/vendor/symfony/dependency-injection/composer.json @@ -0,0 +1,47 @@ +{ + "name": "symfony/dependency-injection", + "type": "library", + "description": "Symfony DependencyInjection Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/yaml": "~2.3.42|~2.7.14|~2.8.7|~3.0.7", + "symfony/config": "~2.2|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0" + }, + "conflict": { + "symfony/expression-language": "<2.6" + }, + "suggest": { + "symfony/yaml": "", + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\DependencyInjection\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php new file mode 100644 index 0000000..4dcede7 --- /dev/null +++ b/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php @@ -0,0 +1,183 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Lazily loads listeners and subscribers from the dependency injection + * container. + * + * @author Fabien Potencier + * @author Bernhard Schussek + * @author Jordan Alliot + */ +class ContainerAwareEventDispatcher extends EventDispatcher +{ + private $container; + + /** + * The service IDs of the event listeners and subscribers. + */ + private $listenerIds = array(); + + /** + * The services registered as listeners. + */ + private $listeners = array(); + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + /** + * Adds a service as event listener. + * + * @param string $eventName Event for which the listener is added + * @param array $callback The service ID of the listener service & the method + * name that has to be called + * @param int $priority The higher this value, the earlier an event listener + * will be triggered in the chain. + * Defaults to 0. + * + * @throws \InvalidArgumentException + */ + public function addListenerService($eventName, $callback, $priority = 0) + { + if (!\is_array($callback) || 2 !== \count($callback)) { + throw new \InvalidArgumentException('Expected an array("service", "method") argument'); + } + + $this->listenerIds[$eventName][] = array($callback[0], $callback[1], $priority); + } + + public function removeListener($eventName, $listener) + { + $this->lazyLoad($eventName); + + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $i => $args) { + list($serviceId, $method) = $args; + $key = $serviceId.'.'.$method; + if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { + unset($this->listeners[$eventName][$key]); + if (empty($this->listeners[$eventName])) { + unset($this->listeners[$eventName]); + } + unset($this->listenerIds[$eventName][$i]); + if (empty($this->listenerIds[$eventName])) { + unset($this->listenerIds[$eventName]); + } + } + } + } + + parent::removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + if (null === $eventName) { + return $this->listenerIds || $this->listeners || parent::hasListeners(); + } + + if (isset($this->listenerIds[$eventName])) { + return true; + } + + return parent::hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null === $eventName) { + foreach ($this->listenerIds as $serviceEventName => $args) { + $this->lazyLoad($serviceEventName); + } + } else { + $this->lazyLoad($eventName); + } + + return parent::getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + $this->lazyLoad($eventName); + + return parent::getListenerPriority($eventName, $listener); + } + + /** + * Adds a service as event subscriber. + * + * @param string $serviceId The service ID of the subscriber service + * @param string $class The service's class name (which must implement EventSubscriberInterface) + */ + public function addSubscriberService($serviceId, $class) + { + foreach ($class::getSubscribedEvents() as $eventName => $params) { + if (\is_string($params)) { + $this->listenerIds[$eventName][] = array($serviceId, $params, 0); + } elseif (\is_string($params[0])) { + $this->listenerIds[$eventName][] = array($serviceId, $params[0], isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->listenerIds[$eventName][] = array($serviceId, $listener[0], isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + public function getContainer() + { + return $this->container; + } + + /** + * Lazily loads listeners for this event from the dependency injection + * container. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + */ + protected function lazyLoad($eventName) + { + if (isset($this->listenerIds[$eventName])) { + foreach ($this->listenerIds[$eventName] as $args) { + list($serviceId, $method, $priority) = $args; + $listener = $this->container->get($serviceId); + + $key = $serviceId.'.'.$method; + if (!isset($this->listeners[$eventName][$key])) { + $this->addListener($eventName, array($listener, $method), $priority); + } elseif ($this->listeners[$eventName][$key] !== $listener) { + parent::removeListener($eventName, array($this->listeners[$eventName][$key], $method)); + $this->addListener($eventName, array($listener, $method), $priority); + } + + $this->listeners[$eventName][$key] = $listener; + } + } + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000..53d7c5d --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -0,0 +1,375 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher implements TraceableEventDispatcherInterface +{ + protected $logger; + protected $stopwatch; + + private $called; + private $dispatcher; + private $wrappedListeners; + + public function __construct(EventDispatcherInterface $dispatcher, Stopwatch $stopwatch, LoggerInterface $logger = null) + { + $this->dispatcher = $dispatcher; + $this->stopwatch = $stopwatch; + $this->logger = $logger; + $this->called = array(); + $this->wrappedListeners = array(); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->dispatcher->addListener($eventName, $listener, $priority); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + $this->dispatcher->addSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (isset($this->wrappedListeners[$eventName])) { + foreach ($this->wrappedListeners[$eventName] as $index => $wrappedListener) { + if ($wrappedListener->getWrappedListener() === $listener) { + $listener = $wrappedListener; + unset($this->wrappedListeners[$eventName][$index]); + break; + } + } + } + + return $this->dispatcher->removeListener($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + return $this->dispatcher->removeSubscriber($subscriber); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + if (!method_exists($this->dispatcher, 'getListenerPriority')) { + return 0; + } + + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + if (null !== $this->logger && $event->isPropagationStopped()) { + $this->logger->debug(sprintf('The "%s" event is already stopped. No listeners have been called.', $eventName)); + } + + $this->preProcess($eventName); + $this->preDispatch($eventName, $event); + + $e = $this->stopwatch->start($eventName, 'section'); + + $this->dispatcher->dispatch($eventName, $event); + + if ($e->isStarted()) { + $e->stop(); + } + + $this->postDispatch($eventName, $event); + $this->postProcess($eventName); + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getCalledListeners() + { + $called = array(); + foreach ($this->called as $eventName => $listeners) { + foreach ($listeners as $listener) { + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + $called[$eventName.'.'.$info['pretty']] = $info; + } + } + + return $called; + } + + /** + * {@inheritdoc} + */ + public function getNotCalledListeners() + { + try { + $allListeners = $this->getListeners(); + } catch (\Exception $e) { + if (null !== $this->logger) { + $this->logger->info('An exception was thrown while getting the uncalled listeners.', array('exception' => $e)); + } + + // unable to retrieve the uncalled listeners + return array(); + } + + $notCalled = array(); + foreach ($allListeners as $eventName => $listeners) { + foreach ($listeners as $listener) { + $called = false; + if (isset($this->called[$eventName])) { + foreach ($this->called[$eventName] as $l) { + if ($l->getWrappedListener() === $listener) { + $called = true; + + break; + } + } + } + + if (!$called) { + $info = $this->getListenerInfo($listener, $eventName); + $notCalled[$eventName.'.'.$info['pretty']] = $info; + } + } + } + + uasort($notCalled, array($this, 'sortListenersByPriority')); + + return $notCalled; + } + + /** + * Proxies all method calls to the original event dispatcher. + * + * @param string $method The method name + * @param array $arguments The method arguments + * + * @return mixed + */ + public function __call($method, $arguments) + { + return \call_user_func_array(array($this->dispatcher, $method), $arguments); + } + + /** + * Called before dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function preDispatch($eventName, Event $event) + { + } + + /** + * Called after dispatching the event. + * + * @param string $eventName The event name + * @param Event $event The event + */ + protected function postDispatch($eventName, Event $event) + { + } + + private function preProcess($eventName) + { + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + $info = $this->getListenerInfo($listener, $eventName); + $name = isset($info['class']) ? $info['class'] : $info['type']; + $wrappedListener = new WrappedListener($listener, $name, $this->stopwatch, $this); + $this->wrappedListeners[$eventName][] = $wrappedListener; + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $wrappedListener, $info['priority']); + } + } + + private function postProcess($eventName) + { + unset($this->wrappedListeners[$eventName]); + $skipped = false; + foreach ($this->dispatcher->getListeners($eventName) as $listener) { + if (!$listener instanceof WrappedListener) { // #12845: a new listener was added during dispatch. + continue; + } + // Unwrap listener + $priority = $this->getListenerPriority($eventName, $listener); + $this->dispatcher->removeListener($eventName, $listener); + $this->dispatcher->addListener($eventName, $listener->getWrappedListener(), $priority); + + $info = $this->getListenerInfo($listener->getWrappedListener(), $eventName); + if ($listener->wasCalled()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Notified event "%s" to listener "%s".', $eventName, $info['pretty'])); + } + + if (!isset($this->called[$eventName])) { + $this->called[$eventName] = new \SplObjectStorage(); + } + + $this->called[$eventName]->attach($listener); + } + + if (null !== $this->logger && $skipped) { + $this->logger->debug(sprintf('Listener "%s" was not called for event "%s".', $info['pretty'], $eventName)); + } + + if ($listener->stoppedPropagation()) { + if (null !== $this->logger) { + $this->logger->debug(sprintf('Listener "%s" stopped propagation of the event "%s".', $info['pretty'], $eventName)); + } + + $skipped = true; + } + } + } + + /** + * Returns information about the listener. + * + * @param object $listener The listener + * @param string $eventName The event name + * + * @return array Information about the listener + */ + private function getListenerInfo($listener, $eventName) + { + $info = array( + 'event' => $eventName, + 'priority' => $this->getListenerPriority($eventName, $listener), + ); + + // unwrap for correct listener info + if ($listener instanceof WrappedListener) { + $listener = $listener->getWrappedListener(); + } + + if ($listener instanceof \Closure) { + $info += array( + 'type' => 'Closure', + 'pretty' => 'closure', + ); + } elseif (\is_string($listener)) { + try { + $r = new \ReflectionFunction($listener); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Function', + 'function' => $listener, + 'file' => $file, + 'line' => $line, + 'pretty' => $listener, + ); + } elseif (\is_array($listener) || (\is_object($listener) && \is_callable($listener))) { + if (!\is_array($listener)) { + $listener = array($listener, '__invoke'); + } + $class = \is_object($listener[0]) ? \get_class($listener[0]) : $listener[0]; + try { + $r = new \ReflectionMethod($class, $listener[1]); + $file = $r->getFileName(); + $line = $r->getStartLine(); + } catch (\ReflectionException $e) { + $file = null; + $line = null; + } + $info += array( + 'type' => 'Method', + 'class' => $class, + 'method' => $listener[1], + 'file' => $file, + 'line' => $line, + 'pretty' => $class.'::'.$listener[1], + ); + } + + return $info; + } + + private function sortListenersByPriority($a, $b) + { + if (\is_int($a['priority']) && !\is_int($b['priority'])) { + return 1; + } + + if (!\is_int($a['priority']) && \is_int($b['priority'])) { + return -1; + } + + if ($a['priority'] === $b['priority']) { + return 0; + } + + if ($a['priority'] > $b['priority']) { + return -1; + } + + return 1; + } +} diff --git a/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php new file mode 100644 index 0000000..5483e81 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +/** + * @author Fabien Potencier + */ +interface TraceableEventDispatcherInterface extends EventDispatcherInterface +{ + /** + * Gets the called listeners. + * + * @return array An array of called listeners + */ + public function getCalledListeners(); + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + */ + public function getNotCalledListeners(); +} diff --git a/vendor/symfony/event-dispatcher/Debug/WrappedListener.php b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php new file mode 100644 index 0000000..1552af0 --- /dev/null +++ b/vendor/symfony/event-dispatcher/Debug/WrappedListener.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\Debug; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Fabien Potencier + */ +class WrappedListener +{ + private $listener; + private $name; + private $called; + private $stoppedPropagation; + private $stopwatch; + private $dispatcher; + + public function __construct($listener, $name, Stopwatch $stopwatch, EventDispatcherInterface $dispatcher = null) + { + $this->listener = $listener; + $this->name = $name; + $this->stopwatch = $stopwatch; + $this->dispatcher = $dispatcher; + $this->called = false; + $this->stoppedPropagation = false; + } + + public function getWrappedListener() + { + return $this->listener; + } + + public function wasCalled() + { + return $this->called; + } + + public function stoppedPropagation() + { + return $this->stoppedPropagation; + } + + public function __invoke(Event $event, $eventName, EventDispatcherInterface $dispatcher) + { + $this->called = true; + + $e = $this->stopwatch->start($this->name, 'event_listener'); + + \call_user_func($this->listener, $event, $eventName, $this->dispatcher ?: $dispatcher); + + if ($e->isStarted()) { + $e->stop(); + } + + if ($event->isPropagationStopped()) { + $this->stoppedPropagation = true; + } + } +} diff --git a/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 0000000..5a94ae8 --- /dev/null +++ b/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Compiler pass to register tagged services for an event dispatcher. + */ +class RegisterListenersPass implements CompilerPassInterface +{ + protected $dispatcherService; + protected $listenerTag; + protected $subscriberTag; + + /** + * @param string $dispatcherService Service name of the event dispatcher in processed container + * @param string $listenerTag Tag name used for listener + * @param string $subscriberTag Tag name used for subscribers + */ + public function __construct($dispatcherService = 'event_dispatcher', $listenerTag = 'kernel.event_listener', $subscriberTag = 'kernel.event_subscriber') + { + $this->dispatcherService = $dispatcherService; + $this->listenerTag = $listenerTag; + $this->subscriberTag = $subscriberTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { + return; + } + + $definition = $container->findDefinition($this->dispatcherService); + + foreach ($container->findTaggedServiceIds($this->listenerTag) as $id => $events) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event listeners are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event listeners are lazy-loaded.', $id)); + } + + foreach ($events as $event) { + $priority = isset($event['priority']) ? $event['priority'] : 0; + + if (!isset($event['event'])) { + throw new \InvalidArgumentException(sprintf('Service "%s" must define the "event" attribute on "%s" tags.', $id, $this->listenerTag)); + } + + if (!isset($event['method'])) { + $event['method'] = 'on'.preg_replace_callback(array( + '/(?<=\b)[a-z]/i', + '/[^a-z0-9]/i', + ), function ($matches) { return strtoupper($matches[0]); }, $event['event']); + $event['method'] = preg_replace('/[^a-z0-9]/i', '', $event['method']); + } + + $definition->addMethodCall('addListenerService', array($event['event'], array($id, $event['method']), $priority)); + } + } + + foreach ($container->findTaggedServiceIds($this->subscriberTag) as $id => $attributes) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as event subscribers are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as event subscribers are lazy-loaded.', $id)); + } + + // We must assume that the class value has been correctly filled, even if the service is created by a factory + $class = $container->getParameterBag()->resolveValue($def->getClass()); + $interface = 'Symfony\Component\EventDispatcher\EventSubscriberInterface'; + + if (!is_subclass_of($class, $interface)) { + if (!class_exists($class, false)) { + throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + $definition->addMethodCall('addSubscriberService', array($id, $class)); + } + } +} diff --git a/vendor/symfony/event-dispatcher/Event.php b/vendor/symfony/event-dispatcher/Event.php new file mode 100644 index 0000000..320919a --- /dev/null +++ b/vendor/symfony/event-dispatcher/Event.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event is the base class for classes containing event data. + * + * This class contains no event data. It is used by events that do not pass + * state information to an event handler when an event is raised. + * + * You can call the method stopPropagation() to abort the execution of + * further listeners in your event listener. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +class Event +{ + /** + * @var bool Whether no further event listeners should be triggered + */ + private $propagationStopped = false; + + /** + * @var EventDispatcherInterface Dispatcher that dispatched this event + */ + private $dispatcher; + + /** + * @var string This event's name + */ + private $name; + + /** + * Returns whether further event listeners should be triggered. + * + * @see Event::stopPropagation() + * + * @return bool Whether propagation was already stopped for this event + */ + public function isPropagationStopped() + { + return $this->propagationStopped; + } + + /** + * Stops the propagation of the event to further event listeners. + * + * If multiple event listeners are connected to the same event, no + * further event listener will be triggered once any trigger calls + * stopPropagation(). + */ + public function stopPropagation() + { + $this->propagationStopped = true; + } + + /** + * Stores the EventDispatcher that dispatches this Event. + * + * @param EventDispatcherInterface $dispatcher + * + * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call. + */ + public function setDispatcher(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * Returns the EventDispatcher that dispatches this Event. + * + * @return EventDispatcherInterface + * + * @deprecated since version 2.4, to be removed in 3.0. The event dispatcher is passed to the listener call. + */ + public function getDispatcher() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0. The event dispatcher instance can be received in the listener call instead.', E_USER_DEPRECATED); + + return $this->dispatcher; + } + + /** + * Gets the event's name. + * + * @return string + * + * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call. + */ + public function getName() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0. The event name can be received in the listener call instead.', E_USER_DEPRECATED); + + return $this->name; + } + + /** + * Sets the event's name property. + * + * @param string $name The event name + * + * @deprecated since version 2.4, to be removed in 3.0. The event name is passed to the listener call. + */ + public function setName($name) + { + $this->name = $name; + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcher.php b/vendor/symfony/event-dispatcher/EventDispatcher.php new file mode 100644 index 0000000..b41b98e --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -0,0 +1,198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + * @author Fabien Potencier + * @author Jordi Boggiano + * @author Jordan Alliot + */ +class EventDispatcher implements EventDispatcherInterface +{ + private $listeners = array(); + private $sorted = array(); + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + if (null === $event) { + $event = new Event(); + } + + $event->setDispatcher($this); + $event->setName($eventName); + + if ($listeners = $this->getListeners($eventName)) { + $this->doDispatch($listeners, $eventName, $event); + } + + return $event; + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + if (null !== $eventName) { + if (!isset($this->listeners[$eventName])) { + return array(); + } + + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + + return $this->sorted[$eventName]; + } + + foreach ($this->listeners as $eventName => $eventListeners) { + if (!isset($this->sorted[$eventName])) { + $this->sortListeners($eventName); + } + } + + return array_filter($this->sorted); + } + + /** + * Gets the listener priority for a specific event. + * + * Returns null if the event or the listener does not exist. + * + * @param string $eventName The name of the event + * @param callable $listener The listener + * + * @return int|null The event listener priority + */ + public function getListenerPriority($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== \in_array($listener, $listeners, true)) { + return $priority; + } + } + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return (bool) $this->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + $this->listeners[$eventName][$priority][] = $listener; + unset($this->sorted[$eventName]); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + if (!isset($this->listeners[$eventName])) { + return; + } + + foreach ($this->listeners[$eventName] as $priority => $listeners) { + if (false !== ($key = array_search($listener, $listeners, true))) { + unset($this->listeners[$eventName][$priority][$key], $this->sorted[$eventName]); + } + } + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_string($params)) { + $this->addListener($eventName, array($subscriber, $params)); + } elseif (\is_string($params[0])) { + $this->addListener($eventName, array($subscriber, $params[0]), isset($params[1]) ? $params[1] : 0); + } else { + foreach ($params as $listener) { + $this->addListener($eventName, array($subscriber, $listener[0]), isset($listener[1]) ? $listener[1] : 0); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + foreach ($subscriber->getSubscribedEvents() as $eventName => $params) { + if (\is_array($params) && \is_array($params[0])) { + foreach ($params as $listener) { + $this->removeListener($eventName, array($subscriber, $listener[0])); + } + } else { + $this->removeListener($eventName, array($subscriber, \is_string($params) ? $params : $params[0])); + } + } + } + + /** + * Triggers the listeners of an event. + * + * This method can be overridden to add functionality that is executed + * for each listener. + * + * @param callable[] $listeners The event listeners + * @param string $eventName The name of the event to dispatch + * @param Event $event The event object to pass to the event handlers/listeners + */ + protected function doDispatch($listeners, $eventName, Event $event) + { + foreach ($listeners as $listener) { + if ($event->isPropagationStopped()) { + break; + } + \call_user_func($listener, $event, $eventName, $this); + } + } + + /** + * Sorts the internal list of listeners for the given event by priority. + * + * @param string $eventName The name of the event + */ + private function sortListeners($eventName) + { + krsort($this->listeners[$eventName]); + $this->sorted[$eventName] = \call_user_func_array('array_merge', $this->listeners[$eventName]); + } +} diff --git a/vendor/symfony/event-dispatcher/EventDispatcherInterface.php b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php new file mode 100644 index 0000000..60160a9 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventDispatcherInterface.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * The EventDispatcherInterface is the central point of Symfony's event listener system. + * Listeners are registered on the manager and events are dispatched through the + * manager. + * + * @author Bernhard Schussek + */ +interface EventDispatcherInterface +{ + /** + * Dispatches an event to all registered listeners. + * + * @param string $eventName The name of the event to dispatch. The name of + * the event is the name of the method that is + * invoked on listeners. + * @param Event $event The event to pass to the event handlers/listeners + * If not supplied, an empty Event instance is created + * + * @return Event + */ + public function dispatch($eventName, Event $event = null); + + /** + * Adds an event listener that listens on the specified events. + * + * @param string $eventName The event to listen on + * @param callable $listener The listener + * @param int $priority The higher this value, the earlier an event + * listener will be triggered in the chain (defaults to 0) + */ + public function addListener($eventName, $listener, $priority = 0); + + /** + * Adds an event subscriber. + * + * The subscriber is asked for all the events he is + * interested in and added as a listener for these events. + */ + public function addSubscriber(EventSubscriberInterface $subscriber); + + /** + * Removes an event listener from the specified events. + * + * @param string $eventName The event to remove a listener from + * @param callable $listener The listener to remove + */ + public function removeListener($eventName, $listener); + + public function removeSubscriber(EventSubscriberInterface $subscriber); + + /** + * Gets the listeners of a specific event or all listeners sorted by descending priority. + * + * @param string $eventName The name of the event + * + * @return array The event listeners for the specified event, or all event listeners by event name + */ + public function getListeners($eventName = null); + + /** + * Checks whether an event has any registered listeners. + * + * @param string $eventName The name of the event + * + * @return bool true if the specified event has any listeners, false otherwise + */ + public function hasListeners($eventName = null); +} diff --git a/vendor/symfony/event-dispatcher/EventSubscriberInterface.php b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php new file mode 100644 index 0000000..8af7789 --- /dev/null +++ b/vendor/symfony/event-dispatcher/EventSubscriberInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * An EventSubscriber knows himself what events he is interested in. + * If an EventSubscriber is added to an EventDispatcherInterface, the manager invokes + * {@link getSubscribedEvents} and registers the subscriber as a listener for all + * returned events. + * + * @author Guilherme Blanco + * @author Jonathan Wage + * @author Roman Borschel + * @author Bernhard Schussek + */ +interface EventSubscriberInterface +{ + /** + * Returns an array of event names this subscriber wants to listen to. + * + * The array keys are event names and the value can be: + * + * * The method name to call (priority defaults to 0) + * * An array composed of the method name to call and the priority + * * An array of arrays composed of the method names to call and respective + * priorities, or 0 if unset + * + * For instance: + * + * * array('eventName' => 'methodName') + * * array('eventName' => array('methodName', $priority)) + * * array('eventName' => array(array('methodName1', $priority), array('methodName2'))) + * + * @return array The event names to listen to + */ + public static function getSubscribedEvents(); +} diff --git a/vendor/symfony/event-dispatcher/GenericEvent.php b/vendor/symfony/event-dispatcher/GenericEvent.php new file mode 100644 index 0000000..f0be7e1 --- /dev/null +++ b/vendor/symfony/event-dispatcher/GenericEvent.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * Event encapsulation class. + * + * Encapsulates events thus decoupling the observer from the subject they encapsulate. + * + * @author Drak + */ +class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate +{ + protected $subject; + protected $arguments; + + /** + * Encapsulate an event with $subject and $args. + * + * @param mixed $subject The subject of the event, usually an object or a callable + * @param array $arguments Arguments to store in the event + */ + public function __construct($subject = null, array $arguments = array()) + { + $this->subject = $subject; + $this->arguments = $arguments; + } + + /** + * Getter for subject property. + * + * @return mixed The observer subject + */ + public function getSubject() + { + return $this->subject; + } + + /** + * Get argument by key. + * + * @param string $key Key + * + * @return mixed Contents of array key + * + * @throws \InvalidArgumentException if key is not found + */ + public function getArgument($key) + { + if ($this->hasArgument($key)) { + return $this->arguments[$key]; + } + + throw new \InvalidArgumentException(sprintf('Argument "%s" not found.', $key)); + } + + /** + * Add argument to event. + * + * @param string $key Argument name + * @param mixed $value Value + * + * @return $this + */ + public function setArgument($key, $value) + { + $this->arguments[$key] = $value; + + return $this; + } + + /** + * Getter for all arguments. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Set args property. + * + * @param array $args Arguments + * + * @return $this + */ + public function setArguments(array $args = array()) + { + $this->arguments = $args; + + return $this; + } + + /** + * Has argument. + * + * @param string $key Key of arguments array + * + * @return bool + */ + public function hasArgument($key) + { + return array_key_exists($key, $this->arguments); + } + + /** + * ArrayAccess for argument getter. + * + * @param string $key Array key + * + * @return mixed + * + * @throws \InvalidArgumentException if key does not exist in $this->args + */ + public function offsetGet($key) + { + return $this->getArgument($key); + } + + /** + * ArrayAccess for argument setter. + * + * @param string $key Array key to set + * @param mixed $value Value + */ + public function offsetSet($key, $value) + { + $this->setArgument($key, $value); + } + + /** + * ArrayAccess for unset argument. + * + * @param string $key Array key + */ + public function offsetUnset($key) + { + if ($this->hasArgument($key)) { + unset($this->arguments[$key]); + } + } + + /** + * ArrayAccess has argument. + * + * @param string $key Array key + * + * @return bool + */ + public function offsetExists($key) + { + return $this->hasArgument($key); + } + + /** + * IteratorAggregate for iterating over the object like an array. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return new \ArrayIterator($this->arguments); + } +} diff --git a/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php new file mode 100644 index 0000000..b3cf56c --- /dev/null +++ b/vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\EventDispatcher; + +/** + * A read-only proxy for an event dispatcher. + * + * @author Bernhard Schussek + */ +class ImmutableEventDispatcher implements EventDispatcherInterface +{ + private $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function dispatch($eventName, Event $event = null) + { + return $this->dispatcher->dispatch($eventName, $event); + } + + /** + * {@inheritdoc} + */ + public function addListener($eventName, $listener, $priority = 0) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function addSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeListener($eventName, $listener) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function removeSubscriber(EventSubscriberInterface $subscriber) + { + throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); + } + + /** + * {@inheritdoc} + */ + public function getListeners($eventName = null) + { + return $this->dispatcher->getListeners($eventName); + } + + /** + * {@inheritdoc} + */ + public function getListenerPriority($eventName, $listener) + { + return $this->dispatcher->getListenerPriority($eventName, $listener); + } + + /** + * {@inheritdoc} + */ + public function hasListeners($eventName = null) + { + return $this->dispatcher->hasListeners($eventName); + } +} diff --git a/vendor/symfony/event-dispatcher/LICENSE b/vendor/symfony/event-dispatcher/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/event-dispatcher/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/event-dispatcher/composer.json b/vendor/symfony/event-dispatcher/composer.json new file mode 100644 index 0000000..14fc24b --- /dev/null +++ b/vendor/symfony/event-dispatcher/composer.json @@ -0,0 +1,44 @@ +{ + "name": "symfony/event-dispatcher", + "type": "library", + "description": "Symfony EventDispatcher Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/dependency-injection": "~2.6|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/config": "^2.0.5|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0", + "psr/log": "~1.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\EventDispatcher\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/filesystem/Exception/ExceptionInterface.php b/vendor/symfony/filesystem/Exception/ExceptionInterface.php new file mode 100644 index 0000000..8f4f10a --- /dev/null +++ b/vendor/symfony/filesystem/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Romain Neutron + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/filesystem/Exception/FileNotFoundException.php b/vendor/symfony/filesystem/Exception/FileNotFoundException.php new file mode 100644 index 0000000..bcc8fe8 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/FileNotFoundException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a file couldn't be found. + * + * @author Fabien Potencier + * @author Christian Gärtner + */ +class FileNotFoundException extends IOException +{ + public function __construct($message = null, $code = 0, \Exception $previous = null, $path = null) + { + if (null === $message) { + if (null === $path) { + $message = 'File could not be found.'; + } else { + $message = sprintf('File "%s" could not be found.', $path); + } + } + + parent::__construct($message, $code, $previous, $path); + } +} diff --git a/vendor/symfony/filesystem/Exception/IOException.php b/vendor/symfony/filesystem/Exception/IOException.php new file mode 100644 index 0000000..144e0e6 --- /dev/null +++ b/vendor/symfony/filesystem/Exception/IOException.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a filesystem operation failure happens. + * + * @author Romain Neutron + * @author Christian Gärtner + * @author Fabien Potencier + */ +class IOException extends \RuntimeException implements IOExceptionInterface +{ + private $path; + + public function __construct($message, $code = 0, \Exception $previous = null, $path = null) + { + $this->path = $path; + + parent::__construct($message, $code, $previous); + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/symfony/filesystem/Exception/IOExceptionInterface.php b/vendor/symfony/filesystem/Exception/IOExceptionInterface.php new file mode 100644 index 0000000..c11965a --- /dev/null +++ b/vendor/symfony/filesystem/Exception/IOExceptionInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * IOException interface for file and input/output stream related exceptions thrown by the component. + * + * @author Christian Gärtner + */ +interface IOExceptionInterface extends ExceptionInterface +{ + /** + * Returns the associated path for the exception. + * + * @return string The path + */ + public function getPath(); +} diff --git a/vendor/symfony/filesystem/Filesystem.php b/vendor/symfony/filesystem/Filesystem.php new file mode 100644 index 0000000..df31620 --- /dev/null +++ b/vendor/symfony/filesystem/Filesystem.php @@ -0,0 +1,669 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\FileNotFoundException; +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * Provides basic utility to manipulate the file system. + * + * @author Fabien Potencier + */ +class Filesystem +{ + private static $lastError; + + /** + * Copies a file. + * + * If the target file is older than the origin file, it's always overwritten. + * If the target file is newer, it is overwritten only when the + * $overwriteNewerFiles option is set to true. + * + * @param string $originFile The original filename + * @param string $targetFile The target filename + * @param bool $overwriteNewerFiles If true, target files newer than origin files are overwritten + * + * @throws FileNotFoundException When originFile doesn't exist + * @throws IOException When copy fails + */ + public function copy($originFile, $targetFile, $overwriteNewerFiles = false) + { + $originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://'); + if ($originIsLocal && !is_file($originFile)) { + throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile); + } + + $this->mkdir(\dirname($targetFile)); + + $doCopy = true; + if (!$overwriteNewerFiles && null === parse_url($originFile, PHP_URL_HOST) && is_file($targetFile)) { + $doCopy = filemtime($originFile) > filemtime($targetFile); + } + + if ($doCopy) { + // https://bugs.php.net/bug.php?id=64634 + if (false === $source = @fopen($originFile, 'r')) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile); + } + + // Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default + if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(array('ftp' => array('overwrite' => true))))) { + throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile); + } + + $bytesCopied = stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + unset($source, $target); + + if (!is_file($targetFile)) { + throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile); + } + + if ($originIsLocal) { + // Like `cp`, preserve executable permission bits + @chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111)); + + if ($bytesCopied !== $bytesOrigin = filesize($originFile)) { + throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile); + } + } + } + } + + /** + * Creates a directory recursively. + * + * @param string|iterable $dirs The directory path + * @param int $mode The directory mode + * + * @throws IOException On any directory creation failure + */ + public function mkdir($dirs, $mode = 0777) + { + foreach ($this->toIterator($dirs) as $dir) { + if (is_dir($dir)) { + continue; + } + + if (!self::box('mkdir', $dir, $mode, true)) { + if (!is_dir($dir)) { + // The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one + if (self::$lastError) { + throw new IOException(sprintf('Failed to create "%s": %s.', $dir, self::$lastError), 0, null, $dir); + } + throw new IOException(sprintf('Failed to create "%s"', $dir), 0, null, $dir); + } + } + } + } + + /** + * Checks the existence of files or directories. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to check + * + * @return bool true if the file exists, false otherwise + */ + public function exists($files) + { + $maxPathLength = PHP_MAXPATHLEN - 2; + + foreach ($this->toIterator($files) as $file) { + if (\strlen($file) > $maxPathLength) { + throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file); + } + + if (!file_exists($file)) { + return false; + } + } + + return true; + } + + /** + * Sets access and modification time of file. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to create + * @param int $time The touch time as a Unix timestamp + * @param int $atime The access time as a Unix timestamp + * + * @throws IOException When touch fails + */ + public function touch($files, $time = null, $atime = null) + { + foreach ($this->toIterator($files) as $file) { + $touch = $time ? @touch($file, $time, $atime) : @touch($file); + if (true !== $touch) { + throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file); + } + } + } + + /** + * Removes files or directories. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove + * + * @throws IOException When removal fails + */ + public function remove($files) + { + if ($files instanceof \Traversable) { + $files = iterator_to_array($files, false); + } elseif (!\is_array($files)) { + $files = array($files); + } + $files = array_reverse($files); + foreach ($files as $file) { + if (is_link($file)) { + // See https://bugs.php.net/52176 + if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove symlink "%s": %s.', $file, self::$lastError)); + } + } elseif (is_dir($file)) { + $this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); + + if (!self::box('rmdir', $file) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove directory "%s": %s.', $file, self::$lastError)); + } + } elseif (!self::box('unlink', $file) && file_exists($file)) { + throw new IOException(sprintf('Failed to remove file "%s": %s.', $file, self::$lastError)); + } + } + } + + /** + * Change mode for an array of files or directories. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode + * @param int $mode The new mode (octal) + * @param int $umask The mode mask (octal) + * @param bool $recursive Whether change the mod recursively or not + * + * @throws IOException When the change fail + */ + public function chmod($files, $mode, $umask = 0000, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if (true !== @chmod($file, $mode & ~$umask)) { + throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file); + } + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); + } + } + } + + /** + * Change the owner of an array of files or directories. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner + * @param string $user The new owner user name + * @param bool $recursive Whether change the owner recursively or not + * + * @throws IOException When the change fail + */ + public function chown($files, $user, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chown(new \FilesystemIterator($file), $user, true); + } + if (is_link($file) && \function_exists('lchown')) { + if (true !== @lchown($file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @chown($file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); + } + } + } + } + + /** + * Change the group of an array of files or directories. + * + * @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group + * @param string $group The group name + * @param bool $recursive Whether change the group recursively or not + * + * @throws IOException When the change fail + */ + public function chgrp($files, $group, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chgrp(new \FilesystemIterator($file), $group, true); + } + if (is_link($file) && \function_exists('lchgrp')) { + if (true !== @lchgrp($file, $group) || (\defined('HHVM_VERSION') && !posix_getgrnam($group))) { + throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @chgrp($file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + } + } + } + } + + /** + * Renames a file or a directory. + * + * @param string $origin The origin filename or directory + * @param string $target The new filename or directory + * @param bool $overwrite Whether to overwrite the target if it already exists + * + * @throws IOException When target file or directory already exists + * @throws IOException When origin cannot be renamed + */ + public function rename($origin, $target, $overwrite = false) + { + // we check that target does not exist + if (!$overwrite && $this->isReadable($target)) { + throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target); + } + + if (true !== @rename($origin, $target)) { + if (is_dir($origin)) { + // See https://bugs.php.net/bug.php?id=54097 & http://php.net/manual/en/function.rename.php#113943 + $this->mirror($origin, $target, null, array('override' => $overwrite, 'delete' => $overwrite)); + $this->remove($origin); + + return; + } + throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target); + } + } + + /** + * Tells whether a file exists and is readable. + * + * @param string $filename Path to the file + * + * @return bool + * + * @throws IOException When windows path is longer than 258 characters + */ + private function isReadable($filename) + { + $maxPathLength = PHP_MAXPATHLEN - 2; + + if (\strlen($filename) > $maxPathLength) { + throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename); + } + + return is_readable($filename); + } + + /** + * Creates a symbolic link or copy a directory. + * + * @param string $originDir The origin directory path + * @param string $targetDir The symbolic link name + * @param bool $copyOnWindows Whether to copy files if on Windows + * + * @throws IOException When symlink fails + */ + public function symlink($originDir, $targetDir, $copyOnWindows = false) + { + if ('\\' === \DIRECTORY_SEPARATOR) { + $originDir = strtr($originDir, '/', '\\'); + $targetDir = strtr($targetDir, '/', '\\'); + + if ($copyOnWindows) { + $this->mirror($originDir, $targetDir); + + return; + } + } + + $this->mkdir(\dirname($targetDir)); + + if (is_link($targetDir)) { + if (readlink($targetDir) === $originDir) { + return; + } + $this->remove($targetDir); + } + + if (!self::box('symlink', $originDir, $targetDir)) { + if (null !== self::$lastError) { + if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) { + throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', 0, null, $targetDir); + } + } + throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir); + } + } + + /** + * Given an existing path, convert it to a path relative to a given starting path. + * + * @param string $endPath Absolute path of target + * @param string $startPath Absolute path where traversal begins + * + * @return string Path of target relative to starting path + */ + public function makePathRelative($endPath, $startPath) + { + // Normalize separators on Windows + if ('\\' === \DIRECTORY_SEPARATOR) { + $endPath = str_replace('\\', '/', $endPath); + $startPath = str_replace('\\', '/', $startPath); + } + + $stripDriveLetter = function ($path) { + if (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0])) { + return substr($path, 2); + } + + return $path; + }; + + $endPath = $stripDriveLetter($endPath); + $startPath = $stripDriveLetter($startPath); + + // Split the paths into arrays + $startPathArr = explode('/', trim($startPath, '/')); + $endPathArr = explode('/', trim($endPath, '/')); + + $normalizePathArray = function ($pathSegments, $absolute) { + $result = array(); + + foreach ($pathSegments as $segment) { + if ('..' === $segment && ($absolute || \count($result))) { + array_pop($result); + } elseif ('.' !== $segment) { + $result[] = $segment; + } + } + + return $result; + }; + + $startPathArr = $normalizePathArray($startPathArr, static::isAbsolutePath($startPath)); + $endPathArr = $normalizePathArray($endPathArr, static::isAbsolutePath($endPath)); + + // Find for which directory the common path stops + $index = 0; + while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { + ++$index; + } + + // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) + if (1 === \count($startPathArr) && '' === $startPathArr[0]) { + $depth = 0; + } else { + $depth = \count($startPathArr) - $index; + } + + // Repeated "../" for each level need to reach the common path + $traverser = str_repeat('../', $depth); + + $endPathRemainder = implode('/', \array_slice($endPathArr, $index)); + + // Construct $endPath from traversing to the common path, then to the remaining $endPath + $relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : ''); + + return '' === $relativePath ? './' : $relativePath; + } + + /** + * Mirrors a directory to another. + * + * Copies files and directories from the origin directory into the target directory. By default: + * + * - existing files in the target directory will be overwritten, except if they are newer (see the `override` option) + * - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option) + * + * @param string $originDir The origin directory + * @param string $targetDir The target directory + * @param \Traversable $iterator Iterator that filters which files and directories to copy + * @param array $options An array of boolean options + * Valid options are: + * - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false) + * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false) + * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) + * + * @throws IOException When file type is unknown + */ + public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array()) + { + $targetDir = rtrim($targetDir, '/\\'); + $originDir = rtrim($originDir, '/\\'); + $originDirLen = \strlen($originDir); + + // Iterate in destination folder to remove obsolete entries + if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { + $deleteIterator = $iterator; + if (null === $deleteIterator) { + $flags = \FilesystemIterator::SKIP_DOTS; + $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST); + } + $targetDirLen = \strlen($targetDir); + foreach ($deleteIterator as $file) { + $origin = $originDir.substr($file->getPathname(), $targetDirLen); + if (!$this->exists($origin)) { + $this->remove($file); + } + } + } + + $copyOnWindows = false; + if (isset($options['copy_on_windows'])) { + $copyOnWindows = $options['copy_on_windows']; + } + + if (null === $iterator) { + $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); + } + + if ($this->exists($originDir)) { + $this->mkdir($targetDir); + } + + foreach ($iterator as $file) { + $target = $targetDir.substr($file->getPathname(), $originDirLen); + + if ($copyOnWindows) { + if (is_file($file)) { + $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); + } elseif (is_dir($file)) { + $this->mkdir($target); + } else { + throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); + } + } else { + if (is_link($file)) { + $this->symlink($file->getLinkTarget(), $target); + } elseif (is_dir($file)) { + $this->mkdir($target); + } elseif (is_file($file)) { + $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); + } else { + throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); + } + } + } + } + + /** + * Returns whether the file path is an absolute path. + * + * @param string $file A file path + * + * @return bool + */ + public function isAbsolutePath($file) + { + return strspn($file, '/\\', 0, 1) + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === substr($file, 1, 1) + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ; + } + + /** + * Creates a temporary file with support for custom stream wrappers. + * + * @param string $dir The directory where the temporary filename will be created + * @param string $prefix The prefix of the generated temporary filename + * Note: Windows uses only the first three characters of prefix + * + * @return string The new temporary filename (with path), or throw an exception on failure + */ + public function tempnam($dir, $prefix) + { + list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir); + + // If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem + if (null === $scheme || 'file' === $scheme || 'gs' === $scheme) { + $tmpFile = @tempnam($hierarchy, $prefix); + + // If tempnam failed or no scheme return the filename otherwise prepend the scheme + if (false !== $tmpFile) { + if (null !== $scheme && 'gs' !== $scheme) { + return $scheme.'://'.$tmpFile; + } + + return $tmpFile; + } + + throw new IOException('A temporary file could not be created.'); + } + + // Loop until we create a valid temp file or have reached 10 attempts + for ($i = 0; $i < 10; ++$i) { + // Create a unique filename + $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true); + + // Use fopen instead of file_exists as some streams do not support stat + // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability + $handle = @fopen($tmpFile, 'x+'); + + // If unsuccessful restart the loop + if (false === $handle) { + continue; + } + + // Close the file if it was successfully opened + @fclose($handle); + + return $tmpFile; + } + + throw new IOException('A temporary file could not be created.'); + } + + /** + * Atomically dumps content into a file. + * + * @param string $filename The file to be written to + * @param string $content The data to write into the file + * @param int|null $mode The file mode (octal). If null, file permissions are not modified + * Deprecated since version 2.3.12, to be removed in 3.0. + * + * @throws IOException if the file cannot be written to + */ + public function dumpFile($filename, $content, $mode = 0666) + { + $dir = \dirname($filename); + + if (!is_dir($dir)) { + $this->mkdir($dir); + } + + if (!is_writable($dir)) { + throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir); + } + + $tmpFile = $this->tempnam($dir, basename($filename)); + + if (false === @file_put_contents($tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); + } + + if (null !== $mode) { + if (\func_num_args() > 2) { + @trigger_error('Support for modifying file permissions is deprecated since Symfony 2.3.12 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + $this->chmod($tmpFile, $mode); + } elseif (file_exists($filename)) { + @chmod($tmpFile, fileperms($filename)); + } + + $this->rename($tmpFile, $filename, true); + } + + /** + * @param mixed $files + * + * @return \Traversable + */ + private function toIterator($files) + { + if (!$files instanceof \Traversable) { + $files = new \ArrayObject(\is_array($files) ? $files : array($files)); + } + + return $files; + } + + /** + * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)). + * + * @param string $filename The filename to be parsed + * + * @return array The filename scheme and hierarchical part + */ + private function getSchemeAndHierarchy($filename) + { + $components = explode('://', $filename, 2); + + return 2 === \count($components) ? array($components[0], $components[1]) : array(null, $components[0]); + } + + private static function box($func) + { + self::$lastError = null; + \set_error_handler(__CLASS__.'::handleError'); + try { + $result = \call_user_func_array($func, \array_slice(\func_get_args(), 1)); + \restore_error_handler(); + + return $result; + } catch (\Throwable $e) { + } catch (\Exception $e) { + } + \restore_error_handler(); + + throw $e; + } + + /** + * @internal + */ + public static function handleError($type, $msg) + { + self::$lastError = $msg; + } +} diff --git a/vendor/symfony/filesystem/LICENSE b/vendor/symfony/filesystem/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/filesystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/filesystem/LockHandler.php b/vendor/symfony/filesystem/LockHandler.php new file mode 100644 index 0000000..6d85c51 --- /dev/null +++ b/vendor/symfony/filesystem/LockHandler.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\IOException; + +/** + * LockHandler class provides a simple abstraction to lock anything by means of + * a file lock. + * + * A locked file is created based on the lock name when calling lock(). Other + * lock handlers will not be able to lock the same name until it is released + * (explicitly by calling release() or implicitly when the instance holding the + * lock is destroyed). + * + * @author Grégoire Pineau + * @author Romain Neutron + * @author Nicolas Grekas + */ +class LockHandler +{ + private $file; + private $handle; + + /** + * @param string $name The lock name + * @param string|null $lockPath The directory to store the lock. Default values will use temporary directory + * + * @throws IOException If the lock directory could not be created or is not writable + */ + public function __construct($name, $lockPath = null) + { + $lockPath = $lockPath ?: sys_get_temp_dir(); + + if (!is_dir($lockPath)) { + $fs = new Filesystem(); + $fs->mkdir($lockPath); + } + + if (!is_writable($lockPath)) { + throw new IOException(sprintf('The directory "%s" is not writable.', $lockPath), 0, null, $lockPath); + } + + $this->file = sprintf('%s/sf.%s.%s.lock', $lockPath, preg_replace('/[^a-z0-9\._-]+/i', '-', $name), hash('sha256', $name)); + } + + /** + * Lock the resource. + * + * @param bool $blocking Wait until the lock is released + * + * @return bool Returns true if the lock was acquired, false otherwise + * + * @throws IOException If the lock file could not be created or opened + */ + public function lock($blocking = false) + { + if ($this->handle) { + return true; + } + + $error = null; + + // Silence error reporting + set_error_handler(function ($errno, $msg) use (&$error) { + $error = $msg; + }); + + if (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) { + if ($this->handle = fopen($this->file, 'x')) { + chmod($this->file, 0666); + } elseif (!$this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r')) { + usleep(100); // Give some time for chmod() to complete + $this->handle = fopen($this->file, 'r+') ?: fopen($this->file, 'r'); + } + } + restore_error_handler(); + + if (!$this->handle) { + throw new IOException($error, 0, null, $this->file); + } + + // On Windows, even if PHP doc says the contrary, LOCK_NB works, see + // https://bugs.php.net/54129 + if (!flock($this->handle, LOCK_EX | ($blocking ? 0 : LOCK_NB))) { + fclose($this->handle); + $this->handle = null; + + return false; + } + + return true; + } + + /** + * Release the resource. + */ + public function release() + { + if ($this->handle) { + flock($this->handle, LOCK_UN | LOCK_NB); + fclose($this->handle); + $this->handle = null; + } + } +} diff --git a/vendor/symfony/filesystem/composer.json b/vendor/symfony/filesystem/composer.json new file mode 100644 index 0000000..3a2db52 --- /dev/null +++ b/vendor/symfony/filesystem/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/filesystem", + "type": "library", + "description": "Symfony Filesystem Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Filesystem\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/finder/Adapter/AbstractAdapter.php b/vendor/symfony/finder/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..1a1647c --- /dev/null +++ b/vendor/symfony/finder/Adapter/AbstractAdapter.php @@ -0,0 +1,240 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +@trigger_error('The '.__NAMESPACE__.'\AbstractAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); + +/** + * Interface for finder engine implementations. + * + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. + */ +abstract class AbstractAdapter implements AdapterInterface +{ + protected $followLinks = false; + protected $mode = 0; + protected $minDepth = 0; + protected $maxDepth = PHP_INT_MAX; + protected $exclude = array(); + protected $names = array(); + protected $notNames = array(); + protected $contains = array(); + protected $notContains = array(); + protected $sizes = array(); + protected $dates = array(); + protected $filters = array(); + protected $sort = false; + protected $paths = array(); + protected $notPaths = array(); + protected $ignoreUnreadableDirs = false; + + private static $areSupported = array(); + + /** + * {@inheritdoc} + */ + public function isSupported() + { + $name = $this->getName(); + + if (!array_key_exists($name, self::$areSupported)) { + self::$areSupported[$name] = $this->canBeUsed(); + } + + return self::$areSupported[$name]; + } + + /** + * {@inheritdoc} + */ + public function setFollowLinks($followLinks) + { + $this->followLinks = $followLinks; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setMode($mode) + { + $this->mode = $mode; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setDepths(array $depths) + { + $this->minDepth = 0; + $this->maxDepth = PHP_INT_MAX; + + foreach ($depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $this->minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $this->minDepth = $comparator->getTarget(); + break; + case '<': + $this->maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $this->maxDepth = $comparator->getTarget(); + break; + default: + $this->minDepth = $this->maxDepth = $comparator->getTarget(); + } + } + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setExclude(array $exclude) + { + $this->exclude = $exclude; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNames(array $names) + { + $this->names = $names; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotNames(array $notNames) + { + $this->notNames = $notNames; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setContains(array $contains) + { + $this->contains = $contains; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotContains(array $notContains) + { + $this->notContains = $notContains; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setSizes(array $sizes) + { + $this->sizes = $sizes; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setDates(array $dates) + { + $this->dates = $dates; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setFilters(array $filters) + { + $this->filters = $filters; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setSort($sort) + { + $this->sort = $sort; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setPath(array $paths) + { + $this->paths = $paths; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setNotPath(array $notPaths) + { + $this->notPaths = $notPaths; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (bool) $ignore; + + return $this; + } + + /** + * Returns whether the adapter is supported in the current environment. + * + * This method should be implemented in all adapters. Do not implement + * isSupported in the adapters as the generic implementation provides a cache + * layer. + * + * @see isSupported() + * + * @return bool Whether the adapter is supported + */ + abstract protected function canBeUsed(); +} diff --git a/vendor/symfony/finder/Adapter/AbstractFindAdapter.php b/vendor/symfony/finder/Adapter/AbstractFindAdapter.php new file mode 100644 index 0000000..ef45869 --- /dev/null +++ b/vendor/symfony/finder/Adapter/AbstractFindAdapter.php @@ -0,0 +1,325 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +@trigger_error('The '.__NAMESPACE__.'\AbstractFindAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); + +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\Expression\Expression; +use Symfony\Component\Finder\Iterator; +use Symfony\Component\Finder\Shell\Command; +use Symfony\Component\Finder\Shell\Shell; + +/** + * Shell engine implementation using GNU find command. + * + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. + */ +abstract class AbstractFindAdapter extends AbstractAdapter +{ + protected $shell; + + public function __construct() + { + $this->shell = new Shell(); + } + + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + // having "/../" in path make find fail + $dir = realpath($dir); + + // searching directories containing or not containing strings leads to no result + if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode && ($this->contains || $this->notContains)) { + return new Iterator\FilePathsIterator(array(), $dir); + } + + $command = Command::create(); + $find = $this->buildFindCommand($command, $dir); + + if ($this->followLinks) { + $find->add('-follow'); + } + + $find->add('-mindepth')->add($this->minDepth + 1); + + if (PHP_INT_MAX !== $this->maxDepth) { + $find->add('-maxdepth')->add($this->maxDepth + 1); + } + + if (Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES === $this->mode) { + $find->add('-type d'); + } elseif (Iterator\FileTypeFilterIterator::ONLY_FILES === $this->mode) { + $find->add('-type f'); + } + + $this->buildNamesFiltering($find, $this->names); + $this->buildNamesFiltering($find, $this->notNames, true); + $this->buildPathsFiltering($find, $dir, $this->paths); + $this->buildPathsFiltering($find, $dir, $this->notPaths, true); + $this->buildSizesFiltering($find, $this->sizes); + $this->buildDatesFiltering($find, $this->dates); + + $useGrep = $this->shell->testCommand('grep') && $this->shell->testCommand('xargs'); + $useSort = \is_int($this->sort) && $this->shell->testCommand('sort') && $this->shell->testCommand('cut'); + + if ($useGrep && ($this->contains || $this->notContains)) { + $grep = $command->ins('grep'); + $this->buildContentFiltering($grep, $this->contains); + $this->buildContentFiltering($grep, $this->notContains, true); + } + + if ($useSort) { + $this->buildSorting($command, $this->sort); + } + + $command->setErrorHandler( + $this->ignoreUnreadableDirs + // If directory is unreadable and finder is set to ignore it, `stderr` is ignored. + ? function ($stderr) { } + : function ($stderr) { throw new AccessDeniedException($stderr); } + ); + + $paths = $this->shell->testCommand('uniq') ? $command->add('| uniq')->execute() : array_unique($command->execute()); + $iterator = new Iterator\FilePathsIterator($paths, $dir); + + if ($this->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + if (!$useGrep && ($this->contains || $this->notContains)) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if (!$useSort && $this->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return $this->shell->testCommand('find'); + } + + /** + * @param Command $command + * @param string $dir + * + * @return Command + */ + protected function buildFindCommand(Command $command, $dir) + { + return $command + ->ins('find') + ->add('find ') + ->arg($dir) + ->add('-noleaf'); // the -noleaf option is required for filesystems that don't follow the '.' and '..' conventions + } + + /** + * @param Command $command + * @param string[] $names + * @param bool $not + */ + private function buildNamesFiltering(Command $command, array $names, $not = false) + { + if (0 === \count($names)) { + return; + } + + $command->add($not ? '-not' : null)->cmd('('); + + foreach ($names as $i => $name) { + $expr = Expression::create($name); + + // Find does not support expandable globs ("*.{a,b}" syntax). + if ($expr->isGlob() && $expr->getGlob()->isExpandable()) { + $expr = Expression::create($expr->getGlob()->toRegex(false)); + } + + // Fixes 'not search' and 'full path matching' regex problems. + // - Jokers '.' are replaced by [^/]. + // - We add '[^/]*' before and after regex (if no ^|$ flags are present). + if ($expr->isRegex()) { + $regex = $expr->getRegex(); + $regex->prepend($regex->hasStartFlag() ? '/' : '/[^/]*') + ->setStartFlag(false) + ->setStartJoker(true) + ->replaceJokers('[^/]'); + if (!$regex->hasEndFlag() || $regex->hasEndJoker()) { + $regex->setEndJoker(false)->append('[^/]*'); + } + } + + $command + ->add($i > 0 ? '-or' : null) + ->add($expr->isRegex() + ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') + : ($expr->isCaseSensitive() ? '-name' : '-iname') + ) + ->arg($expr->renderPattern()); + } + + $command->cmd(')'); + } + + /** + * @param Command $command + * @param string $dir + * @param string[] $paths + * @param bool $not + */ + private function buildPathsFiltering(Command $command, $dir, array $paths, $not = false) + { + if (0 === \count($paths)) { + return; + } + + $command->add($not ? '-not' : null)->cmd('('); + + foreach ($paths as $i => $path) { + $expr = Expression::create($path); + + // Find does not support expandable globs ("*.{a,b}" syntax). + if ($expr->isGlob() && $expr->getGlob()->isExpandable()) { + $expr = Expression::create($expr->getGlob()->toRegex(false)); + } + + // Fixes 'not search' regex problems. + if ($expr->isRegex()) { + $regex = $expr->getRegex(); + $regex->prepend($regex->hasStartFlag() ? preg_quote($dir).\DIRECTORY_SEPARATOR : '.*')->setEndJoker(!$regex->hasEndFlag()); + } else { + $expr->prepend('*')->append('*'); + } + + $command + ->add($i > 0 ? '-or' : null) + ->add($expr->isRegex() + ? ($expr->isCaseSensitive() ? '-regex' : '-iregex') + : ($expr->isCaseSensitive() ? '-path' : '-ipath') + ) + ->arg($expr->renderPattern()); + } + + $command->cmd(')'); + } + + /** + * @param Command $command + * @param NumberComparator[] $sizes + */ + private function buildSizesFiltering(Command $command, array $sizes) + { + foreach ($sizes as $i => $size) { + $command->add($i > 0 ? '-and' : null); + + switch ($size->getOperator()) { + case '<=': + $command->add('-size -'.($size->getTarget() + 1).'c'); + break; + case '>=': + $command->add('-size +'.($size->getTarget() - 1).'c'); + break; + case '>': + $command->add('-size +'.$size->getTarget().'c'); + break; + case '!=': + $command->add('-size -'.$size->getTarget().'c'); + $command->add('-size +'.$size->getTarget().'c'); + break; + case '<': + default: + $command->add('-size -'.$size->getTarget().'c'); + } + } + } + + /** + * @param Command $command + * @param DateComparator[] $dates + */ + private function buildDatesFiltering(Command $command, array $dates) + { + foreach ($dates as $i => $date) { + $command->add($i > 0 ? '-and' : null); + + $mins = (int) round((time() - $date->getTarget()) / 60); + + if (0 > $mins) { + // mtime is in the future + $command->add(' -mmin -0'); + // we will have no result so we don't need to continue + return; + } + + switch ($date->getOperator()) { + case '<=': + $command->add('-mmin +'.($mins - 1)); + break; + case '>=': + $command->add('-mmin -'.($mins + 1)); + break; + case '>': + $command->add('-mmin -'.$mins); + break; + case '!=': + $command->add('-mmin +'.$mins.' -or -mmin -'.$mins); + break; + case '<': + default: + $command->add('-mmin +'.$mins); + } + } + } + + /** + * @param Command $command + * @param string $sort + * + * @throws \InvalidArgumentException + */ + private function buildSorting(Command $command, $sort) + { + $this->buildFormatSorting($command, $sort); + } + + /** + * @param Command $command + * @param string $sort + */ + abstract protected function buildFormatSorting(Command $command, $sort); + + /** + * @param Command $command + * @param array $contains + * @param bool $not + */ + abstract protected function buildContentFiltering(Command $command, array $contains, $not = false); +} diff --git a/vendor/symfony/finder/Adapter/AdapterInterface.php b/vendor/symfony/finder/Adapter/AdapterInterface.php new file mode 100644 index 0000000..8d8fb07 --- /dev/null +++ b/vendor/symfony/finder/Adapter/AdapterInterface.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +/** + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. + */ +interface AdapterInterface +{ + /** + * @param bool $followLinks + * + * @return $this + */ + public function setFollowLinks($followLinks); + + /** + * @param int $mode + * + * @return $this + */ + public function setMode($mode); + + /** + * @return $this + */ + public function setExclude(array $exclude); + + /** + * @return $this + */ + public function setDepths(array $depths); + + /** + * @return $this + */ + public function setNames(array $names); + + /** + * @return $this + */ + public function setNotNames(array $notNames); + + /** + * @return $this + */ + public function setContains(array $contains); + + /** + * @return $this + */ + public function setNotContains(array $notContains); + + /** + * @return $this + */ + public function setSizes(array $sizes); + + /** + * @return $this + */ + public function setDates(array $dates); + + /** + * @return $this + */ + public function setFilters(array $filters); + + /** + * @param \Closure|int $sort + * + * @return $this + */ + public function setSort($sort); + + /** + * @return $this + */ + public function setPath(array $paths); + + /** + * @return $this + */ + public function setNotPath(array $notPaths); + + /** + * @param bool $ignore + * + * @return $this + */ + public function ignoreUnreadableDirs($ignore = true); + + /** + * @param string $dir + * + * @return \Iterator Result iterator + */ + public function searchInDirectory($dir); + + /** + * Tests adapter support for current platform. + * + * @return bool + */ + public function isSupported(); + + /** + * Returns adapter name. + * + * @return string + */ + public function getName(); +} diff --git a/vendor/symfony/finder/Adapter/BsdFindAdapter.php b/vendor/symfony/finder/Adapter/BsdFindAdapter.php new file mode 100644 index 0000000..f6e4303 --- /dev/null +++ b/vendor/symfony/finder/Adapter/BsdFindAdapter.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +@trigger_error('The '.__NAMESPACE__.'\BsdFindAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); + +use Symfony\Component\Finder\Expression\Expression; +use Symfony\Component\Finder\Iterator\SortableIterator; +use Symfony\Component\Finder\Shell\Command; +use Symfony\Component\Finder\Shell\Shell; + +/** + * Shell engine implementation using BSD find command. + * + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. + */ +class BsdFindAdapter extends AbstractFindAdapter +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'bsd_find'; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return \in_array($this->shell->getType(), array(Shell::TYPE_BSD, Shell::TYPE_DARWIN)) && parent::canBeUsed(); + } + + /** + * {@inheritdoc} + */ + protected function buildFormatSorting(Command $command, $sort) + { + switch ($sort) { + case SortableIterator::SORT_BY_NAME: + $command->ins('sort')->add('| sort'); + + return; + case SortableIterator::SORT_BY_TYPE: + $format = '%HT'; + break; + case SortableIterator::SORT_BY_ACCESSED_TIME: + $format = '%a'; + break; + case SortableIterator::SORT_BY_CHANGED_TIME: + $format = '%c'; + break; + case SortableIterator::SORT_BY_MODIFIED_TIME: + $format = '%m'; + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort)); + } + + $command + ->add('-print0 | xargs -0 stat -f') + ->arg($format.'%t%N') + ->add('| sort | cut -f 2'); + } + + /** + * {@inheritdoc} + */ + protected function buildFindCommand(Command $command, $dir) + { + parent::buildFindCommand($command, $dir)->addAtIndex('-E', 1); + + return $command; + } + + /** + * {@inheritdoc} + */ + protected function buildContentFiltering(Command $command, array $contains, $not = false) + { + foreach ($contains as $contain) { + $expr = Expression::create($contain); + + // todo: avoid forking process for each $pattern by using multiple -e options + $command + ->add('| grep -v \'^$\'') + ->add('| xargs -I{} grep -I') + ->add($expr->isCaseSensitive() ? null : '-i') + ->add($not ? '-L' : '-l') + ->add('-Ee')->arg($expr->renderPattern()) + ->add('{}') + ; + } + } +} diff --git a/vendor/symfony/finder/Adapter/GnuFindAdapter.php b/vendor/symfony/finder/Adapter/GnuFindAdapter.php new file mode 100644 index 0000000..140c566 --- /dev/null +++ b/vendor/symfony/finder/Adapter/GnuFindAdapter.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +@trigger_error('The '.__NAMESPACE__.'\GnuFindAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); + +use Symfony\Component\Finder\Expression\Expression; +use Symfony\Component\Finder\Iterator\SortableIterator; +use Symfony\Component\Finder\Shell\Command; +use Symfony\Component\Finder\Shell\Shell; + +/** + * Shell engine implementation using GNU find command. + * + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. + */ +class GnuFindAdapter extends AbstractFindAdapter +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'gnu_find'; + } + + /** + * {@inheritdoc} + */ + protected function buildFormatSorting(Command $command, $sort) + { + switch ($sort) { + case SortableIterator::SORT_BY_NAME: + $command->ins('sort')->add('| sort'); + + return; + case SortableIterator::SORT_BY_TYPE: + $format = '%y'; + break; + case SortableIterator::SORT_BY_ACCESSED_TIME: + $format = '%A@'; + break; + case SortableIterator::SORT_BY_CHANGED_TIME: + $format = '%C@'; + break; + case SortableIterator::SORT_BY_MODIFIED_TIME: + $format = '%T@'; + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown sort options: %s.', $sort)); + } + + $command + ->get('find') + ->add('-printf') + ->arg($format.' %h/%f\\n') + ->add('| sort | cut') + ->arg('-d ') + ->arg('-f2-') + ; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return Shell::TYPE_UNIX === $this->shell->getType() && parent::canBeUsed(); + } + + /** + * {@inheritdoc} + */ + protected function buildFindCommand(Command $command, $dir) + { + return parent::buildFindCommand($command, $dir)->add('-regextype posix-extended'); + } + + /** + * {@inheritdoc} + */ + protected function buildContentFiltering(Command $command, array $contains, $not = false) + { + foreach ($contains as $contain) { + $expr = Expression::create($contain); + + // todo: avoid forking process for each $pattern by using multiple -e options + $command + ->add('| xargs -I{} -r grep -I') + ->add($expr->isCaseSensitive() ? null : '-i') + ->add($not ? '-L' : '-l') + ->add('-Ee')->arg($expr->renderPattern()) + ->add('{}') + ; + } + } +} diff --git a/vendor/symfony/finder/Adapter/PhpAdapter.php b/vendor/symfony/finder/Adapter/PhpAdapter.php new file mode 100644 index 0000000..c2fb66c --- /dev/null +++ b/vendor/symfony/finder/Adapter/PhpAdapter.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Adapter; + +@trigger_error('The '.__NAMESPACE__.'\PhpAdapter class is deprecated since Symfony 2.8 and will be removed in 3.0. Use directly the Finder class instead.', E_USER_DEPRECATED); + +use Symfony\Component\Finder\Iterator; + +/** + * PHP finder engine implementation. + * + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. Use Finder instead. + */ +class PhpAdapter extends AbstractAdapter +{ + /** + * {@inheritdoc} + */ + public function searchInDirectory($dir) + { + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($this->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($this->minDepth > 0 || $this->maxDepth < PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $this->minDepth, $this->maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $this->notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); + } + + if ($this->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'php'; + } + + /** + * {@inheritdoc} + */ + protected function canBeUsed() + { + return true; + } +} diff --git a/vendor/symfony/finder/Comparator/Comparator.php b/vendor/symfony/finder/Comparator/Comparator.php new file mode 100644 index 0000000..ea37566 --- /dev/null +++ b/vendor/symfony/finder/Comparator/Comparator.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * Comparator. + * + * @author Fabien Potencier + */ +class Comparator +{ + private $target; + private $operator = '=='; + + /** + * Gets the target value. + * + * @return string The target value + */ + public function getTarget() + { + return $this->target; + } + + /** + * Sets the target value. + * + * @param string $target The target value + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Gets the comparison operator. + * + * @return string The operator + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Sets the comparison operator. + * + * @param string $operator A valid operator + * + * @throws \InvalidArgumentException + */ + public function setOperator($operator) + { + if (!$operator) { + $operator = '=='; + } + + if (!\in_array($operator, array('>', '<', '>=', '<=', '==', '!='))) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; + } + + /** + * Tests against the target. + * + * @param mixed $test A test value + * + * @return bool + */ + public function test($test) + { + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; + } +} diff --git a/vendor/symfony/finder/Comparator/DateComparator.php b/vendor/symfony/finder/Comparator/DateComparator.php new file mode 100644 index 0000000..3de43ef --- /dev/null +++ b/vendor/symfony/finder/Comparator/DateComparator.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * DateCompare compiles date comparisons. + * + * @author Fabien Potencier + */ +class DateComparator extends Comparator +{ + /** + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct($test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + } + + try { + $date = new \DateTime($matches[2]); + $target = $date->format('U'); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = isset($matches[1]) ? $matches[1] : '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + $this->setOperator($operator); + $this->setTarget($target); + } +} diff --git a/vendor/symfony/finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Comparator/NumberComparator.php new file mode 100644 index 0000000..f62c0e5 --- /dev/null +++ b/vendor/symfony/finder/Comparator/NumberComparator.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * NumberComparator compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * @param string|int $test A comparison string or an integer + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct($test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test)); + } + + $target = $matches[2]; + if (!is_numeric($target)) { + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + } + if (isset($matches[3])) { + // magnitude + switch (strtolower($matches[3])) { + case 'k': + $target *= 1000; + break; + case 'ki': + $target *= 1024; + break; + case 'm': + $target *= 1000000; + break; + case 'mi': + $target *= 1024 * 1024; + break; + case 'g': + $target *= 1000000000; + break; + case 'gi': + $target *= 1024 * 1024 * 1024; + break; + } + } + + $this->setTarget($target); + $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); + } +} diff --git a/vendor/symfony/finder/Exception/AccessDeniedException.php b/vendor/symfony/finder/Exception/AccessDeniedException.php new file mode 100644 index 0000000..ee195ea --- /dev/null +++ b/vendor/symfony/finder/Exception/AccessDeniedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} diff --git a/vendor/symfony/finder/Exception/AdapterFailureException.php b/vendor/symfony/finder/Exception/AdapterFailureException.php new file mode 100644 index 0000000..594940a --- /dev/null +++ b/vendor/symfony/finder/Exception/AdapterFailureException.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +@trigger_error('The '.__NAMESPACE__.'\AdapterFailureException class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +use Symfony\Component\Finder\Adapter\AdapterInterface; + +/** + * Base exception for all adapter failures. + * + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. + */ +class AdapterFailureException extends \RuntimeException implements ExceptionInterface +{ + private $adapter; + + /** + * @param AdapterInterface $adapter + * @param string|null $message + * @param \Exception|null $previous + */ + public function __construct(AdapterInterface $adapter, $message = null, \Exception $previous = null) + { + $this->adapter = $adapter; + parent::__construct($message ?: 'Search failed with "'.$adapter->getName().'" adapter.', $previous); + } + + /** + * {@inheritdoc} + */ + public function getAdapter() + { + return $this->adapter; + } +} diff --git a/vendor/symfony/finder/Exception/ExceptionInterface.php b/vendor/symfony/finder/Exception/ExceptionInterface.php new file mode 100644 index 0000000..bff0214 --- /dev/null +++ b/vendor/symfony/finder/Exception/ExceptionInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +interface ExceptionInterface +{ + /** + * @return \Symfony\Component\Finder\Adapter\AdapterInterface + */ + public function getAdapter(); +} diff --git a/vendor/symfony/finder/Exception/OperationNotPermitedException.php b/vendor/symfony/finder/Exception/OperationNotPermitedException.php new file mode 100644 index 0000000..4c66858 --- /dev/null +++ b/vendor/symfony/finder/Exception/OperationNotPermitedException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +@trigger_error('The '.__NAMESPACE__.'\OperationNotPermitedException class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +/** + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. + */ +class OperationNotPermitedException extends AdapterFailureException +{ +} diff --git a/vendor/symfony/finder/Exception/ShellCommandFailureException.php b/vendor/symfony/finder/Exception/ShellCommandFailureException.php new file mode 100644 index 0000000..db85e68 --- /dev/null +++ b/vendor/symfony/finder/Exception/ShellCommandFailureException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +@trigger_error('The '.__NAMESPACE__.'\ShellCommandFailureException class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +use Symfony\Component\Finder\Adapter\AdapterInterface; +use Symfony\Component\Finder\Shell\Command; + +/** + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. + */ +class ShellCommandFailureException extends AdapterFailureException +{ + private $command; + + public function __construct(AdapterInterface $adapter, Command $command, \Exception $previous = null) + { + $this->command = $command; + parent::__construct($adapter, 'Shell command failed: "'.$command->join().'".', $previous); + } + + /** + * @return Command + */ + public function getCommand() + { + return $this->command; + } +} diff --git a/vendor/symfony/finder/Expression/Expression.php b/vendor/symfony/finder/Expression/Expression.php new file mode 100644 index 0000000..e83c771 --- /dev/null +++ b/vendor/symfony/finder/Expression/Expression.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +@trigger_error('The '.__NAMESPACE__.'\Expression class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +/** + * @author Jean-François Simon + */ +class Expression implements ValueInterface +{ + const TYPE_REGEX = 1; + const TYPE_GLOB = 2; + + /** + * @var ValueInterface + */ + private $value; + + /** + * @param string $expr + * + * @return self + */ + public static function create($expr) + { + return new self($expr); + } + + /** + * @param string $expr + */ + public function __construct($expr) + { + try { + $this->value = Regex::create($expr); + } catch (\InvalidArgumentException $e) { + $this->value = new Glob($expr); + } + } + + /** + * @return string + */ + public function __toString() + { + return $this->render(); + } + + /** + * {@inheritdoc} + */ + public function render() + { + return $this->value->render(); + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return $this->value->renderPattern(); + } + + /** + * @return bool + */ + public function isCaseSensitive() + { + return $this->value->isCaseSensitive(); + } + + /** + * @return int + */ + public function getType() + { + return $this->value->getType(); + } + + /** + * {@inheritdoc} + */ + public function prepend($expr) + { + $this->value->prepend($expr); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function append($expr) + { + $this->value->append($expr); + + return $this; + } + + /** + * @return bool + */ + public function isRegex() + { + return self::TYPE_REGEX === $this->value->getType(); + } + + /** + * @return bool + */ + public function isGlob() + { + return self::TYPE_GLOB === $this->value->getType(); + } + + /** + * @return Glob + * + * @throws \LogicException + */ + public function getGlob() + { + if (self::TYPE_GLOB !== $this->value->getType()) { + throw new \LogicException('Regex can\'t be transformed to glob.'); + } + + return $this->value; + } + + /** + * @return Regex + */ + public function getRegex() + { + return self::TYPE_REGEX === $this->value->getType() ? $this->value : $this->value->toRegex(); + } +} diff --git a/vendor/symfony/finder/Expression/Glob.php b/vendor/symfony/finder/Expression/Glob.php new file mode 100644 index 0000000..e80578e --- /dev/null +++ b/vendor/symfony/finder/Expression/Glob.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +@trigger_error('The '.__NAMESPACE__.'\Glob class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +use Symfony\Component\Finder\Glob as FinderGlob; + +/** + * @author Jean-François Simon + */ +class Glob implements ValueInterface +{ + private $pattern; + + /** + * @param string $pattern + */ + public function __construct($pattern) + { + $this->pattern = $pattern; + } + + /** + * {@inheritdoc} + */ + public function render() + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return $this->pattern; + } + + /** + * {@inheritdoc} + */ + public function getType() + { + return Expression::TYPE_GLOB; + } + + /** + * {@inheritdoc} + */ + public function isCaseSensitive() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function prepend($expr) + { + $this->pattern = $expr.$this->pattern; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function append($expr) + { + $this->pattern .= $expr; + + return $this; + } + + /** + * Tests if glob is expandable ("*.{a,b}" syntax). + * + * @return bool + */ + public function isExpandable() + { + return false !== strpos($this->pattern, '{') + && false !== strpos($this->pattern, '}'); + } + + /** + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * + * @return Regex + */ + public function toRegex($strictLeadingDot = true, $strictWildcardSlash = true) + { + $regex = FinderGlob::toRegex($this->pattern, $strictLeadingDot, $strictWildcardSlash, ''); + + return new Regex($regex); + } +} diff --git a/vendor/symfony/finder/Expression/Regex.php b/vendor/symfony/finder/Expression/Regex.php new file mode 100644 index 0000000..2f1ab3d --- /dev/null +++ b/vendor/symfony/finder/Expression/Regex.php @@ -0,0 +1,321 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +@trigger_error('The '.__NAMESPACE__.'\Regex class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +/** + * @author Jean-François Simon + */ +class Regex implements ValueInterface +{ + const START_FLAG = '^'; + const END_FLAG = '$'; + const BOUNDARY = '~'; + const JOKER = '.*'; + const ESCAPING = '\\'; + + /** + * @var string + */ + private $pattern; + + /** + * @var string + */ + private $options; + + /** + * @var bool + */ + private $startFlag; + + /** + * @var bool + */ + private $endFlag; + + /** + * @var bool + */ + private $startJoker; + + /** + * @var bool + */ + private $endJoker; + + /** + * @param string $expr + * + * @return self + * + * @throws \InvalidArgumentException + */ + public static function create($expr) + { + if (preg_match('/^(.{3,}?)([imsxuADU]*)$/', $expr, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ( + ($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) + || ('{' === $start && '}' === $end) + || ('(' === $start && ')' === $end) + ) { + return new self(substr($m[1], 1, -1), $m[2], $end); + } + } + + throw new \InvalidArgumentException('Given expression is not a regex.'); + } + + /** + * @param string $pattern + * @param string $options + * @param string $delimiter + */ + public function __construct($pattern, $options = '', $delimiter = null) + { + if (null !== $delimiter) { + // removes delimiter escaping + $pattern = str_replace('\\'.$delimiter, $delimiter, $pattern); + } + + $this->parsePattern($pattern); + $this->options = $options; + } + + /** + * @return string + */ + public function __toString() + { + return $this->render(); + } + + /** + * {@inheritdoc} + */ + public function render() + { + return self::BOUNDARY + .$this->renderPattern() + .self::BOUNDARY + .$this->options; + } + + /** + * {@inheritdoc} + */ + public function renderPattern() + { + return ($this->startFlag ? self::START_FLAG : '') + .($this->startJoker ? self::JOKER : '') + .str_replace(self::BOUNDARY, '\\'.self::BOUNDARY, $this->pattern) + .($this->endJoker ? self::JOKER : '') + .($this->endFlag ? self::END_FLAG : ''); + } + + /** + * {@inheritdoc} + */ + public function isCaseSensitive() + { + return !$this->hasOption('i'); + } + + /** + * {@inheritdoc} + */ + public function getType() + { + return Expression::TYPE_REGEX; + } + + /** + * {@inheritdoc} + */ + public function prepend($expr) + { + $this->pattern = $expr.$this->pattern; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function append($expr) + { + $this->pattern .= $expr; + + return $this; + } + + /** + * @param string $option + * + * @return bool + */ + public function hasOption($option) + { + return false !== strpos($this->options, $option); + } + + /** + * @param string $option + * + * @return $this + */ + public function addOption($option) + { + if (!$this->hasOption($option)) { + $this->options .= $option; + } + + return $this; + } + + /** + * @param string $option + * + * @return $this + */ + public function removeOption($option) + { + $this->options = str_replace($option, '', $this->options); + + return $this; + } + + /** + * @param bool $startFlag + * + * @return $this + */ + public function setStartFlag($startFlag) + { + $this->startFlag = $startFlag; + + return $this; + } + + /** + * @return bool + */ + public function hasStartFlag() + { + return $this->startFlag; + } + + /** + * @param bool $endFlag + * + * @return $this + */ + public function setEndFlag($endFlag) + { + $this->endFlag = (bool) $endFlag; + + return $this; + } + + /** + * @return bool + */ + public function hasEndFlag() + { + return $this->endFlag; + } + + /** + * @param bool $startJoker + * + * @return $this + */ + public function setStartJoker($startJoker) + { + $this->startJoker = $startJoker; + + return $this; + } + + /** + * @return bool + */ + public function hasStartJoker() + { + return $this->startJoker; + } + + /** + * @param bool $endJoker + * + * @return $this + */ + public function setEndJoker($endJoker) + { + $this->endJoker = (bool) $endJoker; + + return $this; + } + + /** + * @return bool + */ + public function hasEndJoker() + { + return $this->endJoker; + } + + /** + * @return $this + */ + public function replaceJokers($replacement) + { + $replace = function ($subject) use ($replacement) { + $subject = $subject[0]; + $replace = 0 === substr_count($subject, '\\') % 2; + + return $replace ? str_replace('.', $replacement, $subject) : $subject; + }; + + $this->pattern = preg_replace_callback('~[\\\\]*\\.~', $replace, $this->pattern); + + return $this; + } + + /** + * @param string $pattern + */ + private function parsePattern($pattern) + { + if ($this->startFlag = self::START_FLAG === substr($pattern, 0, 1)) { + $pattern = substr($pattern, 1); + } + + if ($this->startJoker = self::JOKER === substr($pattern, 0, 2)) { + $pattern = substr($pattern, 2); + } + + if ($this->endFlag = (self::END_FLAG === substr($pattern, -1) && self::ESCAPING !== substr($pattern, -2, -1))) { + $pattern = substr($pattern, 0, -1); + } + + if ($this->endJoker = (self::JOKER === substr($pattern, -2) && self::ESCAPING !== substr($pattern, -3, -2))) { + $pattern = substr($pattern, 0, -2); + } + + $this->pattern = $pattern; + } +} diff --git a/vendor/symfony/finder/Expression/ValueInterface.php b/vendor/symfony/finder/Expression/ValueInterface.php new file mode 100644 index 0000000..08ede14 --- /dev/null +++ b/vendor/symfony/finder/Expression/ValueInterface.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Expression; + +@trigger_error('The '.__NAMESPACE__.'\ValueInterface interface is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +/** + * @author Jean-François Simon + */ +interface ValueInterface +{ + /** + * Renders string representation of expression. + * + * @return string + */ + public function render(); + + /** + * Renders string representation of pattern. + * + * @return string + */ + public function renderPattern(); + + /** + * Returns value case sensitivity. + * + * @return bool + */ + public function isCaseSensitive(); + + /** + * Returns expression type. + * + * @return int + */ + public function getType(); + + /** + * @param string $expr + * + * @return $this + */ + public function prepend($expr); + + /** + * @param string $expr + * + * @return $this + */ + public function append($expr); +} diff --git a/vendor/symfony/finder/Finder.php b/vendor/symfony/finder/Finder.php new file mode 100644 index 0000000..1ee9017 --- /dev/null +++ b/vendor/symfony/finder/Finder.php @@ -0,0 +1,916 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Adapter\AdapterInterface; +use Symfony\Component\Finder\Adapter\BsdFindAdapter; +use Symfony\Component\Finder\Adapter\GnuFindAdapter; +use Symfony\Component\Finder\Adapter\PhpAdapter; +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\ExceptionInterface; +use Symfony\Component\Finder\Iterator\CustomFilterIterator; +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Iterator\SortableIterator; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow easy chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier + */ +class Finder implements \IteratorAggregate, \Countable +{ + const IGNORE_VCS_FILES = 1; + const IGNORE_DOT_FILES = 2; + + private $mode = 0; + private $names = array(); + private $notNames = array(); + private $exclude = array(); + private $filters = array(); + private $depths = array(); + private $sizes = array(); + private $followLinks = false; + private $sort = false; + private $ignore = 0; + private $dirs = array(); + private $dates = array(); + private $iterators = array(); + private $contains = array(); + private $notContains = array(); + private $adapters = null; + private $paths = array(); + private $notPaths = array(); + private $ignoreUnreadableDirs = false; + + private static $vcsPatterns = array('.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'); + + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + } + + /** + * Creates a new Finder. + * + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * Registers a finder engine implementation. + * + * @param AdapterInterface $adapter An adapter instance + * @param int $priority Highest is selected first + * + * @return $this + * + * @deprecated since 2.8, to be removed in 3.0. + */ + public function addAdapter(AdapterInterface $adapter, $priority = 0) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + $this->initDefaultAdapters(); + + $this->adapters[$adapter->getName()] = array( + 'adapter' => $adapter, + 'priority' => $priority, + 'selected' => false, + ); + + return $this->sortAdapters(); + } + + /** + * Sets the selected adapter to the best one according to the current platform the code is run on. + * + * @return $this + * + * @deprecated since 2.8, to be removed in 3.0. + */ + public function useBestAdapter() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + $this->initDefaultAdapters(); + + $this->resetAdapterSelection(); + + return $this->sortAdapters(); + } + + /** + * Selects the adapter to use. + * + * @param string $name + * + * @return $this + * + * @throws \InvalidArgumentException + * + * @deprecated since 2.8, to be removed in 3.0. + */ + public function setAdapter($name) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + $this->initDefaultAdapters(); + + if (!isset($this->adapters[$name])) { + throw new \InvalidArgumentException(sprintf('Adapter "%s" does not exist.', $name)); + } + + $this->resetAdapterSelection(); + $this->adapters[$name]['selected'] = true; + + return $this->sortAdapters(); + } + + /** + * Removes all adapters registered in the finder. + * + * @return $this + * + * @deprecated since 2.8, to be removed in 3.0. + */ + public function removeAdapters() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + $this->adapters = array(); + + return $this; + } + + /** + * Returns registered adapters ordered by priority without extra information. + * + * @return AdapterInterface[] + * + * @deprecated since 2.8, to be removed in 3.0. + */ + public function getAdapters() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + $this->initDefaultAdapters(); + + return array_values(array_map(function (array $adapter) { + return $adapter['adapter']; + }, $this->adapters)); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * + * @param string|int $level The depth level expression + * + * @return $this + * + * @see DepthRangeFilterIterator + * @see NumberComparator + */ + public function depth($level) + { + $this->depths[] = new Comparator\NumberComparator($level); + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * + * @param string $date A date range string + * + * @return $this + * + * @see strtotime + * @see DateRangeFilterIterator + * @see DateComparator + */ + public function date($date) + { + $this->dates[] = new Comparator\DateComparator($date); + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above + * $finder->name('test.php') + * + * @param string $pattern A pattern (a regexp, a glob, or a string) + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function name($pattern) + { + $this->names[] = $pattern; + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string $pattern A pattern (a regexp, a glob, or a string) + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notName($pattern) + { + $this->notNames[] = $pattern; + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * + * @param string $pattern A pattern (string or regexp) + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function contains($pattern) + { + $this->contains[] = $pattern; + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * + * @param string $pattern A pattern (string or regexp) + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function notContains($pattern) + { + $this->notContains[] = $pattern; + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * + * Use only / as dirname separator. + * + * @param string $pattern A pattern (a regexp or a string) + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function path($pattern) + { + $this->paths[] = $pattern; + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * + * Use only / as dirname separator. + * + * @param string $pattern A pattern (a regexp or a string) + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notPath($pattern) + { + $this->notPaths[] = $pattern; + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * + * @param string|int $size A size range string or an integer + * + * @return $this + * + * @see SizeRangeFilterIterator + * @see NumberComparator + */ + public function size($size) + { + $this->sizes[] = new Comparator\NumberComparator($size); + + return $this; + } + + /** + * Excludes directories. + * + * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: + * + * $finder->in(__DIR__)->exclude('ruby'); + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function exclude($dirs) + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * This option is enabled by default. + * + * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreDotFiles($ignoreDotFiles) + { + if ($ignoreDotFiles) { + $this->ignore |= static::IGNORE_DOT_FILES; + } else { + $this->ignore &= ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * This option is enabled by default. + * + * @param bool $ignoreVCS Whether to exclude VCS files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreVCS($ignoreVCS) + { + if ($ignoreVCS) { + $this->ignore |= static::IGNORE_VCS_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS() + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern($pattern) + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sort(\Closure $closure) + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByName() + { + $this->sort = Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByType() + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByAccessedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByChangedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByModifiedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks() + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @param bool $ignore + * + * @return $this + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (bool) $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @throws \InvalidArgumentException if one of the directories does not exist + */ + public function in($dirs) + { + $resolvedDirs = array(); + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = $this->normalizeDir($dir); + } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? GLOB_BRACE : 0) | GLOB_ONLYDIR)) { + $resolvedDirs = array_merge($resolvedDirs, array_map(array($this, 'normalizeDir'), $glob)); + } else { + throw new \InvalidArgumentException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, $resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator|SplFileInfo[] An iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator() + { + if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + return $this->searchInDirectory($this->dirs[0]); + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append($this->searchInDirectory($dir)); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @param iterable $iterator + * + * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable + */ + public function append($iterator) + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif ($iterator instanceof \Traversable || \is_array($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Counts all the results collected by the iterators. + * + * @return int + */ + public function count() + { + return iterator_count($this->getIterator()); + } + + /** + * @return $this + */ + private function sortAdapters() + { + uasort($this->adapters, function (array $a, array $b) { + if ($a['selected'] || $b['selected']) { + return $a['selected'] ? -1 : 1; + } + + return $a['priority'] > $b['priority'] ? -1 : 1; + }); + + return $this; + } + + /** + * @param string $dir + * + * @return \Iterator + */ + private function searchInDirectory($dir) + { + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $this->exclude = array_merge($this->exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $this->notPaths[] = '#(^|/)\..+(/|$)#'; + } + + if ($this->adapters) { + foreach ($this->adapters as $adapter) { + if ($adapter['adapter']->isSupported()) { + try { + return $this + ->buildAdapter($adapter['adapter']) + ->searchInDirectory($dir); + } catch (ExceptionInterface $e) { + } + } + } + } + + $minDepth = 0; + $maxDepth = PHP_INT_MAX; + + foreach ($this->depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $minDepth = $comparator->getTarget(); + break; + case '<': + $maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $maxDepth = $comparator->getTarget(); + break; + default: + $minDepth = $maxDepth = $comparator->getTarget(); + } + } + + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($this->exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $this->exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($minDepth > 0 || $maxDepth < PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $this->notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $this->notPaths); + } + + if ($this->sort) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } + + /** + * @return AdapterInterface + */ + private function buildAdapter(AdapterInterface $adapter) + { + return $adapter + ->setFollowLinks($this->followLinks) + ->setDepths($this->depths) + ->setMode($this->mode) + ->setExclude($this->exclude) + ->setNames($this->names) + ->setNotNames($this->notNames) + ->setContains($this->contains) + ->setNotContains($this->notContains) + ->setSizes($this->sizes) + ->setDates($this->dates) + ->setFilters($this->filters) + ->setSort($this->sort) + ->setPath($this->paths) + ->setNotPath($this->notPaths) + ->ignoreUnreadableDirs($this->ignoreUnreadableDirs); + } + + /** + * Unselects all adapters. + */ + private function resetAdapterSelection() + { + $this->adapters = array_map(function (array $properties) { + $properties['selected'] = false; + + return $properties; + }, $this->adapters); + } + + private function initDefaultAdapters() + { + if (null === $this->adapters) { + $this->adapters = array(); + $this + ->addAdapter(new GnuFindAdapter()) + ->addAdapter(new BsdFindAdapter()) + ->addAdapter(new PhpAdapter(), -50) + ->setAdapter('php') + ; + } + } + + /** + * Normalizes given directory names by removing trailing slashes. + * + * @param string $dir + * + * @return string + */ + private function normalizeDir($dir) + { + return rtrim($dir, '/'.\DIRECTORY_SEPARATOR); + } +} diff --git a/vendor/symfony/finder/Glob.php b/vendor/symfony/finder/Glob.php new file mode 100644 index 0000000..e2988f2 --- /dev/null +++ b/vendor/symfony/finder/Glob.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Glob matches globbing patterns against text. + * + * if match_glob("foo.*", "foo.bar") echo "matched\n"; + * + * // prints foo.bar and foo.baz + * $regex = glob_to_regex("foo.*"); + * for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t) + * { + * if (/$regex/) echo "matched: $car\n"; + * } + * + * Glob implements glob(3) style matching that can be used to match + * against text, rather than fetching names from a filesystem. + * + * Based on the Perl Text::Glob module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + * + * @param string $glob The glob pattern + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * @param string $delimiter Optional delimiter + * + * @return string regex The regexp + */ + public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#') + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = \strlen($glob); + for ($i = 0; $i < $sizeGlob; ++$i) { + $car = $glob[$i]; + if ($firstByte) { + if ($strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = false; + } + + if ('/' === $car) { + $firstByte = true; + } + + if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return $delimiter.'^'.$regex.'$'.$delimiter; + } +} diff --git a/vendor/symfony/finder/Iterator/CustomFilterIterator.php b/vendor/symfony/finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000..6666e07 --- /dev/null +++ b/vendor/symfony/finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @author Fabien Potencier + */ +class CustomFilterIterator extends FilterIterator +{ + private $filters = array(); + + /** + * @param \Iterator $iterator The Iterator to filter + * @param callable[] $filters An array of PHP callbacks + * + * @throws \InvalidArgumentException + */ + public function __construct(\Iterator $iterator, array $filters) + { + foreach ($filters as $filter) { + if (!\is_callable($filter)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + } + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === \call_user_func($filter, $fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000..b01e5e3 --- /dev/null +++ b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier + */ +class DateRangeFilterIterator extends FilterIterator +{ + private $comparators = array(); + + /** + * @param \Iterator $iterator The Iterator to filter + * @param DateComparator[] $comparators An array of DateComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + if (!file_exists($fileinfo->getPathname())) { + return false; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000..ce9d3aa --- /dev/null +++ b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + */ +class DepthRangeFilterIterator extends FilterIterator +{ + private $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, $minDepth = 0, $maxDepth = PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000..c57128c --- /dev/null +++ b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + */ +class ExcludeDirectoryFilterIterator extends FilterIterator implements \RecursiveIterator +{ + private $iterator; + private $isRecursive; + private $excludedDirs = array(); + private $excludedPattern; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param array $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->iterator = $iterator; + $this->isRecursive = $iterator instanceof \RecursiveIterator; + $patterns = array(); + foreach ($directories as $directory) { + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || false !== strpos($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool True if the value should be kept, false otherwise + */ + public function accept() + { + if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { + return false; + } + + if ($this->excludedPattern) { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = str_replace('\\', '/', $path); + + return !preg_match($this->excludedPattern, $path); + } + + return true; + } + + public function hasChildren() + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + public function getChildren() + { + $children = new self($this->iterator->getChildren(), array()); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/vendor/symfony/finder/Iterator/FilePathsIterator.php b/vendor/symfony/finder/Iterator/FilePathsIterator.php new file mode 100644 index 0000000..9c4e5c4 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilePathsIterator.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +@trigger_error('The '.__NAMESPACE__.'\FilePathsIterator class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +use Symfony\Component\Finder\SplFileInfo; + +/** + * Iterate over shell command result. + * + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. + */ +class FilePathsIterator extends \ArrayIterator +{ + /** + * @var string + */ + private $baseDir; + + /** + * @var int + */ + private $baseDirLength; + + /** + * @var string + */ + private $subPath; + + /** + * @var string + */ + private $subPathname; + + /** + * @var SplFileInfo + */ + private $current; + + /** + * @param array $paths List of paths returned by shell command + * @param string $baseDir Base dir for relative path building + */ + public function __construct(array $paths, $baseDir) + { + $this->baseDir = $baseDir; + $this->baseDirLength = \strlen($baseDir); + + parent::__construct($paths); + } + + /** + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public function __call($name, array $arguments) + { + return \call_user_func_array(array($this->current(), $name), $arguments); + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo File information + */ + public function current() + { + return $this->current; + } + + /** + * @return string + */ + public function key() + { + return $this->current->getPathname(); + } + + public function next() + { + parent::next(); + $this->buildProperties(); + } + + public function rewind() + { + parent::rewind(); + $this->buildProperties(); + } + + /** + * @return string + */ + public function getSubPath() + { + return $this->subPath; + } + + /** + * @return string + */ + public function getSubPathname() + { + return $this->subPathname; + } + + private function buildProperties() + { + $absolutePath = parent::current(); + + if ($this->baseDir === substr($absolutePath, 0, $this->baseDirLength)) { + $this->subPathname = ltrim(substr($absolutePath, $this->baseDirLength), '/\\'); + $dir = \dirname($this->subPathname); + $this->subPath = '.' === $dir ? '' : $dir; + } else { + $this->subPath = $this->subPathname = ''; + } + + $this->current = new SplFileInfo(parent::current(), $this->subPath, $this->subPathname); + } +} diff --git a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000..e9811d4 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier + */ +class FileTypeFilterIterator extends FilterIterator +{ + const ONLY_FILES = 1; + const ONLY_DIRECTORIES = 2; + + private $mode; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 0000000..81594b8 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + if (!$this->matchRegexps && !$this->noMatchRegexps) { + return true; + } + + $fileinfo = $this->current(); + + if ($fileinfo->isDir() || !$fileinfo->isReadable()) { + return false; + } + + $content = $fileinfo->getContents(); + if (!$content) { + return false; + } + + return $this->isAccepted($content); + } + + /** + * Converts string to regexp if necessary. + * + * @param string $str Pattern: string or regexp + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000..e168cd8 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->isAccepted($this->current()->getFilename()); + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + * + * @return string regexp corresponding to a given glob or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/vendor/symfony/finder/Iterator/FilterIterator.php b/vendor/symfony/finder/Iterator/FilterIterator.php new file mode 100644 index 0000000..acb9836 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * This iterator just overrides the rewind method in order to correct a PHP bug, + * which existed before version 5.5.23/5.6.7. + * + * @see https://bugs.php.net/68557 + * + * @author Alex Bogomazov + */ +abstract class FilterIterator extends \FilterIterator +{ + /** + * This is a workaround for the problem with \FilterIterator leaving inner \FilesystemIterator in wrong state after + * rewind in some cases. + * + * @see FilterIterator::rewind() + */ + public function rewind() + { + if (\PHP_VERSION_ID > 50607 || (\PHP_VERSION_ID > 50523 && \PHP_VERSION_ID < 50600)) { + parent::rewind(); + + return; + } + + $iterator = $this; + while ($iterator instanceof \OuterIterator) { + $innerIterator = $iterator->getInnerIterator(); + + if ($innerIterator instanceof RecursiveDirectoryIterator) { + // this condition is necessary for iterators to work properly with non-local filesystems like ftp + if ($innerIterator->isRewindable()) { + $innerIterator->next(); + $innerIterator->rewind(); + } + } elseif ($innerIterator instanceof \FilesystemIterator) { + $innerIterator->next(); + $innerIterator->rewind(); + } + + $iterator = $innerIterator; + } + + parent::rewind(); + } +} diff --git a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 0000000..fc88540 --- /dev/null +++ b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + */ +abstract class MultiplePcreFilterIterator extends FilterIterator +{ + protected $matchRegexps = array(); + protected $noMatchRegexps = array(); + + /** + * @param \Iterator $iterator The Iterator to filter + * @param array $matchPatterns An array of patterns that need to match + * @param array $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + foreach ($noMatchPatterns as $pattern) { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Checks whether the string is accepted by the regex filters. + * + * If there is no regexps defined in the class, this method will accept the string. + * Such case can be handled by child classes before calling the method if they want to + * apply a different behavior. + * + * @param string $string The string to be matched against filters + * + * @return bool + */ + protected function isAccepted($string) + { + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $string)) { + return false; + } + } + + // should at least match one rule + if ($this->matchRegexps) { + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $string)) { + return true; + } + } + + return false; + } + + // If there is no match rules, the file is accepted + return true; + } + + /** + * Checks whether the string is a regex. + * + * @param string $str + * + * @return bool Whether the given string is a regex + */ + protected function isRegex($str) + { + if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ($start === $end) { + return !preg_match('/[*?[:alnum:] \\\\]/', $start); + } + + foreach (array(array('{', '}'), array('(', ')'), array('[', ']'), array('<', '>')) as $delimiters) { + if ($start === $delimiters[0] && $end === $delimiters[1]) { + return true; + } + } + } + + return false; + } + + /** + * Converts string into regexp. + * + * @param string $str Pattern + * + * @return string regexp corresponding to a given string + */ + abstract protected function toRegex($str); +} diff --git a/vendor/symfony/finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000..3fda557 --- /dev/null +++ b/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $filename = $this->current()->getRelativePathname(); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $filename = str_replace('\\', '/', $filename); + } + + return $this->isAccepted($filename); + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000..cb2964f --- /dev/null +++ b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var bool + */ + private $ignoreUnreadableDirs; + + /** + * @var bool + */ + private $rewindable; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private $rootPath; + private $subPath; + private $directorySeparator = '/'; + + /** + * @param string $path + * @param int $flags + * @param bool $ignoreUnreadableDirs + * + * @throws \RuntimeException + */ + public function __construct($path, $flags, $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + $this->rootPath = (string) $path; + if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { + $this->directorySeparator = \DIRECTORY_SEPARATOR; + } + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo File information + */ + public function current() + { + // the logic here avoids redoing the same work in all iterations + + if (null === $subPathname = $this->subPath) { + $subPathname = $this->subPath = (string) $this->getSubPath(); + } + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + + return new SplFileInfo($this->rootPath.$this->directorySeparator.$subPathname, $this->subPath, $subPathname); + } + + /** + * @return \RecursiveIterator + * + * @throws AccessDeniedException + */ + public function getChildren() + { + try { + $children = parent::getChildren(); + + if ($children instanceof self) { + // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore + $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; + + // performance optimization to avoid redoing the same work in all children + $children->rewindable = &$this->rewindable; + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. + return new \RecursiveArrayIterator(array()); + } else { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Do nothing for non rewindable stream. + */ + public function rewind() + { + if (false === $this->isRewindable()) { + return; + } + + // @see https://bugs.php.net/68557 + if (\PHP_VERSION_ID < 50523 || \PHP_VERSION_ID >= 50600 && \PHP_VERSION_ID < 50607) { + parent::next(); + } + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return bool true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + // workaround for an HHVM bug, should be removed when https://github.com/facebook/hhvm/issues/7281 is fixed + if ('' === $this->getPath()) { + return $this->rewindable = false; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} diff --git a/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000..bd1a7fb --- /dev/null +++ b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @author Fabien Potencier + */ +class SizeRangeFilterIterator extends FilterIterator +{ + private $comparators = array(); + + /** + * @param \Iterator $iterator The Iterator to filter + * @param NumberComparator[] $comparators An array of NumberComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + if (!$fileinfo->isFile()) { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filesize)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 0000000..53f8e31 --- /dev/null +++ b/vendor/symfony/finder/Iterator/SortableIterator.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier + */ +class SortableIterator implements \IteratorAggregate +{ + const SORT_BY_NAME = 1; + const SORT_BY_TYPE = 2; + const SORT_BY_ACCESSED_TIME = 3; + const SORT_BY_CHANGED_TIME = 4; + const SORT_BY_MODIFIED_TIME = 5; + + private $iterator; + private $sort; + + /** + * @param \Traversable $iterator The Iterator to filter + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, $sort) + { + $this->iterator = $iterator; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = function ($a, $b) { + return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = function ($a, $b) { + if ($a->isDir() && $b->isFile()) { + return -1; + } elseif ($a->isFile() && $b->isDir()) { + return 1; + } + + return strcmp($a->getRealpath() ?: $a->getPathname(), $b->getRealpath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = function ($a, $b) { + return $a->getATime() - $b->getATime(); + }; + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = function ($a, $b) { + return $a->getCTime() - $b->getCTime(); + }; + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = function ($a, $b) { + return $a->getMTime() - $b->getMTime(); + }; + } elseif (\is_callable($sort)) { + $this->sort = $sort; + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + public function getIterator() + { + $array = iterator_to_array($this->iterator, true); + uasort($array, $this->sort); + + return new \ArrayIterator($array); + } +} diff --git a/vendor/symfony/finder/LICENSE b/vendor/symfony/finder/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/finder/Shell/Command.php b/vendor/symfony/finder/Shell/Command.php new file mode 100644 index 0000000..8219078 --- /dev/null +++ b/vendor/symfony/finder/Shell/Command.php @@ -0,0 +1,278 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Shell; + +@trigger_error('The '.__NAMESPACE__.'\Command class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +/** + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. + */ +class Command +{ + private $parent; + private $bits = array(); + private $labels = array(); + + /** + * @var \Closure|null + */ + private $errorHandler; + + public function __construct(Command $parent = null) + { + $this->parent = $parent; + } + + /** + * Returns command as string. + * + * @return string + */ + public function __toString() + { + return $this->join(); + } + + /** + * Creates a new Command instance. + * + * @return self + */ + public static function create(Command $parent = null) + { + return new self($parent); + } + + /** + * Escapes special chars from input. + * + * @param string $input A string to escape + * + * @return string The escaped string + */ + public static function escape($input) + { + return escapeshellcmd($input); + } + + /** + * Quotes input. + * + * @param string $input An argument string + * + * @return string The quoted string + */ + public static function quote($input) + { + return escapeshellarg($input); + } + + /** + * Appends a string or a Command instance. + * + * @param string|Command $bit + * + * @return $this + */ + public function add($bit) + { + $this->bits[] = $bit; + + return $this; + } + + /** + * Prepends a string or a command instance. + * + * @param string|Command $bit + * + * @return $this + */ + public function top($bit) + { + array_unshift($this->bits, $bit); + + foreach ($this->labels as $label => $index) { + ++$this->labels[$label]; + } + + return $this; + } + + /** + * Appends an argument, will be quoted. + * + * @param string $arg + * + * @return $this + */ + public function arg($arg) + { + $this->bits[] = self::quote($arg); + + return $this; + } + + /** + * Appends escaped special command chars. + * + * @param string $esc + * + * @return $this + */ + public function cmd($esc) + { + $this->bits[] = self::escape($esc); + + return $this; + } + + /** + * Inserts a labeled command to feed later. + * + * @param string $label The unique label + * + * @return self|string + * + * @throws \RuntimeException If label already exists + */ + public function ins($label) + { + if (isset($this->labels[$label])) { + throw new \RuntimeException(sprintf('Label "%s" already exists.', $label)); + } + + $this->bits[] = self::create($this); + $this->labels[$label] = \count($this->bits) - 1; + + return $this->bits[$this->labels[$label]]; + } + + /** + * Retrieves a previously labeled command. + * + * @param string $label + * + * @return self|string + * + * @throws \RuntimeException + */ + public function get($label) + { + if (!isset($this->labels[$label])) { + throw new \RuntimeException(sprintf('Label "%s" does not exist.', $label)); + } + + return $this->bits[$this->labels[$label]]; + } + + /** + * Returns parent command (if any). + * + * @return self + * + * @throws \RuntimeException If command has no parent + */ + public function end() + { + if (null === $this->parent) { + throw new \RuntimeException('Calling end on root command doesn\'t make sense.'); + } + + return $this->parent; + } + + /** + * Counts bits stored in command. + * + * @return int The bits count + */ + public function length() + { + return \count($this->bits); + } + + /** + * @return $this + */ + public function setErrorHandler(\Closure $errorHandler) + { + $this->errorHandler = $errorHandler; + + return $this; + } + + /** + * @return \Closure|null + */ + public function getErrorHandler() + { + return $this->errorHandler; + } + + /** + * Executes current command. + * + * @return array The command result + * + * @throws \RuntimeException + */ + public function execute() + { + if (null === $errorHandler = $this->errorHandler) { + exec($this->join(), $output); + } else { + $process = proc_open($this->join(), array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes); + $output = preg_split('~(\r\n|\r|\n)~', stream_get_contents($pipes[1]), -1, PREG_SPLIT_NO_EMPTY); + + if ($error = stream_get_contents($pipes[2])) { + $errorHandler($error); + } + + proc_close($process); + } + + return $output ?: array(); + } + + /** + * Joins bits. + * + * @return string + */ + public function join() + { + return implode(' ', array_filter( + array_map(function ($bit) { + return $bit instanceof Command ? $bit->join() : ($bit ?: null); + }, $this->bits), + function ($bit) { return null !== $bit; } + )); + } + + /** + * Insert a string or a Command instance before the bit at given position $index (index starts from 0). + * + * @param string|Command $bit + * @param int $index + * + * @return $this + */ + public function addAtIndex($bit, $index) + { + array_splice($this->bits, $index, 0, $bit instanceof self ? array($bit) : $bit); + + return $this; + } +} diff --git a/vendor/symfony/finder/Shell/Shell.php b/vendor/symfony/finder/Shell/Shell.php new file mode 100644 index 0000000..fa6ca4b --- /dev/null +++ b/vendor/symfony/finder/Shell/Shell.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Shell; + +@trigger_error('The '.__NAMESPACE__.'\Shell class is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + +/** + * @author Jean-François Simon + * + * @deprecated since 2.8, to be removed in 3.0. + */ +class Shell +{ + const TYPE_UNIX = 1; + const TYPE_DARWIN = 2; + const TYPE_CYGWIN = 3; + const TYPE_WINDOWS = 4; + const TYPE_BSD = 5; + + /** + * @var string|null + */ + private $type; + + /** + * Returns guessed OS type. + * + * @return int + */ + public function getType() + { + if (null === $this->type) { + $this->type = $this->guessType(); + } + + return $this->type; + } + + /** + * Tests if a command is available. + * + * @param string $command + * + * @return bool + */ + public function testCommand($command) + { + if (!\function_exists('exec')) { + return false; + } + + // todo: find a better way (command could not be available) + $testCommand = 'which '; + if (self::TYPE_WINDOWS === $this->type) { + $testCommand = 'where '; + } + + $command = escapeshellcmd($command); + + exec($testCommand.$command, $output, $code); + + return 0 === $code && \count($output) > 0; + } + + /** + * Guesses OS type. + * + * @return int + */ + private function guessType() + { + $os = strtolower(PHP_OS); + + if (false !== strpos($os, 'cygwin')) { + return self::TYPE_CYGWIN; + } + + if (false !== strpos($os, 'darwin')) { + return self::TYPE_DARWIN; + } + + if (false !== strpos($os, 'bsd')) { + return self::TYPE_BSD; + } + + if (0 === strpos($os, 'win')) { + return self::TYPE_WINDOWS; + } + + return self::TYPE_UNIX; + } +} diff --git a/vendor/symfony/finder/SplFileInfo.php b/vendor/symfony/finder/SplFileInfo.php new file mode 100644 index 0000000..0f4e025 --- /dev/null +++ b/vendor/symfony/finder/SplFileInfo.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Extends \SplFileInfo to support relative paths. + * + * @author Fabien Potencier + */ +class SplFileInfo extends \SplFileInfo +{ + private $relativePath; + private $relativePathname; + + /** + * @param string $file The file name + * @param string $relativePath The relative path + * @param string $relativePathname The relative path name + */ + public function __construct($file, $relativePath, $relativePathname) + { + parent::__construct($file); + $this->relativePath = $relativePath; + $this->relativePathname = $relativePathname; + } + + /** + * Returns the relative path. + * + * This path does not contain the file name. + * + * @return string the relative path + */ + public function getRelativePath() + { + return $this->relativePath; + } + + /** + * Returns the relative path name. + * + * This path contains the file name. + * + * @return string the relative path name + */ + public function getRelativePathname() + { + return $this->relativePathname; + } + + /** + * Returns the contents of the file. + * + * @return string the contents of the file + * + * @throws \RuntimeException + */ + public function getContents() + { + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $content = file_get_contents($this->getPathname()); + restore_error_handler(); + if (false === $content) { + throw new \RuntimeException($error); + } + + return $content; + } +} diff --git a/vendor/symfony/finder/composer.json b/vendor/symfony/finder/composer.json new file mode 100644 index 0000000..53dc98a --- /dev/null +++ b/vendor/symfony/finder/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Symfony Finder Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Finder\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/http-foundation/AcceptHeader.php b/vendor/symfony/http-foundation/AcceptHeader.php new file mode 100644 index 0000000..d174026 --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeader.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header. + * + * An accept header is compound with a list of items, + * sorted by descending quality. + * + * @author Jean-François Simon + */ +class AcceptHeader +{ + /** + * @var AcceptHeaderItem[] + */ + private $items = array(); + + /** + * @var bool + */ + private $sorted = true; + + /** + * @param AcceptHeaderItem[] $items + */ + public function __construct(array $items) + { + foreach ($items as $item) { + $this->add($item); + } + } + + /** + * Builds an AcceptHeader instance from a string. + * + * @param string $headerValue + * + * @return self + */ + public static function fromString($headerValue) + { + $index = 0; + + return new self(array_map(function ($itemValue) use (&$index) { + $item = AcceptHeaderItem::fromString($itemValue); + $item->setIndex($index++); + + return $item; + }, preg_split('/\s*(?:,*("[^"]+"),*|,*(\'[^\']+\'),*|,+)\s*/', $headerValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE))); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + return implode(',', $this->items); + } + + /** + * Tests if header has given value. + * + * @param string $value + * + * @return bool + */ + public function has($value) + { + return isset($this->items[$value]); + } + + /** + * Returns given value's item, if exists. + * + * @param string $value + * + * @return AcceptHeaderItem|null + */ + public function get($value) + { + return isset($this->items[$value]) ? $this->items[$value] : null; + } + + /** + * Adds an item. + * + * @return $this + */ + public function add(AcceptHeaderItem $item) + { + $this->items[$item->getValue()] = $item; + $this->sorted = false; + + return $this; + } + + /** + * Returns all items. + * + * @return AcceptHeaderItem[] + */ + public function all() + { + $this->sort(); + + return $this->items; + } + + /** + * Filters items on their value using given regex. + * + * @param string $pattern + * + * @return self + */ + public function filter($pattern) + { + return new self(array_filter($this->items, function (AcceptHeaderItem $item) use ($pattern) { + return preg_match($pattern, $item->getValue()); + })); + } + + /** + * Returns first item. + * + * @return AcceptHeaderItem|null + */ + public function first() + { + $this->sort(); + + return !empty($this->items) ? reset($this->items) : null; + } + + /** + * Sorts items by descending quality. + */ + private function sort() + { + if (!$this->sorted) { + uasort($this->items, function (AcceptHeaderItem $a, AcceptHeaderItem $b) { + $qA = $a->getQuality(); + $qB = $b->getQuality(); + + if ($qA === $qB) { + return $a->getIndex() > $b->getIndex() ? 1 : -1; + } + + return $qA > $qB ? -1 : 1; + }); + + $this->sorted = true; + } + } +} diff --git a/vendor/symfony/http-foundation/AcceptHeaderItem.php b/vendor/symfony/http-foundation/AcceptHeaderItem.php new file mode 100644 index 0000000..c950657 --- /dev/null +++ b/vendor/symfony/http-foundation/AcceptHeaderItem.php @@ -0,0 +1,209 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents an Accept-* header item. + * + * @author Jean-François Simon + */ +class AcceptHeaderItem +{ + private $value; + private $quality = 1.0; + private $index = 0; + private $attributes = array(); + + /** + * @param string $value + * @param array $attributes + */ + public function __construct($value, array $attributes = array()) + { + $this->value = $value; + foreach ($attributes as $name => $value) { + $this->setAttribute($name, $value); + } + } + + /** + * Builds an AcceptHeaderInstance instance from a string. + * + * @param string $itemValue + * + * @return self + */ + public static function fromString($itemValue) + { + $bits = preg_split('/\s*(?:;*("[^"]+");*|;*(\'[^\']+\');*|;+)\s*/', $itemValue, 0, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); + $value = array_shift($bits); + $attributes = array(); + + $lastNullAttribute = null; + foreach ($bits as $bit) { + if (($start = substr($bit, 0, 1)) === ($end = substr($bit, -1)) && ('"' === $start || '\'' === $start)) { + $attributes[$lastNullAttribute] = substr($bit, 1, -1); + } elseif ('=' === $end) { + $lastNullAttribute = $bit = substr($bit, 0, -1); + $attributes[$bit] = null; + } else { + $parts = explode('=', $bit); + $attributes[$parts[0]] = isset($parts[1]) && \strlen($parts[1]) > 0 ? $parts[1] : ''; + } + } + + return new self(($start = substr($value, 0, 1)) === ($end = substr($value, -1)) && ('"' === $start || '\'' === $start) ? substr($value, 1, -1) : $value, $attributes); + } + + /** + * Returns header value's string representation. + * + * @return string + */ + public function __toString() + { + $string = $this->value.($this->quality < 1 ? ';q='.$this->quality : ''); + if (\count($this->attributes) > 0) { + $string .= ';'.implode(';', array_map(function ($name, $value) { + return sprintf(preg_match('/[,;=]/', $value) ? '%s="%s"' : '%s=%s', $name, $value); + }, array_keys($this->attributes), $this->attributes)); + } + + return $string; + } + + /** + * Set the item value. + * + * @param string $value + * + * @return $this + */ + public function setValue($value) + { + $this->value = $value; + + return $this; + } + + /** + * Returns the item value. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Set the item quality. + * + * @param float $quality + * + * @return $this + */ + public function setQuality($quality) + { + $this->quality = $quality; + + return $this; + } + + /** + * Returns the item quality. + * + * @return float + */ + public function getQuality() + { + return $this->quality; + } + + /** + * Set the item index. + * + * @param int $index + * + * @return $this + */ + public function setIndex($index) + { + $this->index = $index; + + return $this; + } + + /** + * Returns the item index. + * + * @return int + */ + public function getIndex() + { + return $this->index; + } + + /** + * Tests if an attribute exists. + * + * @param string $name + * + * @return bool + */ + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } + + /** + * Returns an attribute by its name. + * + * @param string $name + * @param mixed $default + * + * @return mixed + */ + public function getAttribute($name, $default = null) + { + return isset($this->attributes[$name]) ? $this->attributes[$name] : $default; + } + + /** + * Returns all attributes. + * + * @return array + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * Set an attribute. + * + * @param string $name + * @param string $value + * + * @return $this + */ + public function setAttribute($name, $value) + { + if ('q' === $name) { + $this->quality = (float) $value; + } else { + $this->attributes[$name] = (string) $value; + } + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/ApacheRequest.php b/vendor/symfony/http-foundation/ApacheRequest.php new file mode 100644 index 0000000..4e99186 --- /dev/null +++ b/vendor/symfony/http-foundation/ApacheRequest.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Request represents an HTTP request from an Apache server. + * + * @author Fabien Potencier + */ +class ApacheRequest extends Request +{ + /** + * {@inheritdoc} + */ + protected function prepareRequestUri() + { + return $this->server->get('REQUEST_URI'); + } + + /** + * {@inheritdoc} + */ + protected function prepareBaseUrl() + { + $baseUrl = $this->server->get('SCRIPT_NAME'); + + if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) { + // assume mod_rewrite + return rtrim(\dirname($baseUrl), '/\\'); + } + + return $baseUrl; + } +} diff --git a/vendor/symfony/http-foundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/BinaryFileResponse.php new file mode 100644 index 0000000..f115159 --- /dev/null +++ b/vendor/symfony/http-foundation/BinaryFileResponse.php @@ -0,0 +1,357 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\File; + +/** + * BinaryFileResponse represents an HTTP response delivering a file. + * + * @author Niklas Fiekas + * @author stealth35 + * @author Igor Wiedler + * @author Jordan Alliot + * @author Sergey Linnik + */ +class BinaryFileResponse extends Response +{ + protected static $trustXSendfileTypeHeader = false; + + /** + * @var File + */ + protected $file; + protected $offset = 0; + protected $maxlen = -1; + protected $deleteFileAfterSend = false; + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + */ + public function __construct($file, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + parent::__construct(null, $status, $headers); + + $this->setFile($file, $contentDisposition, $autoEtag, $autoLastModified); + + if ($public) { + $this->setPublic(); + } + } + + /** + * @param \SplFileInfo|string $file The file to stream + * @param int $status The response status code + * @param array $headers An array of response headers + * @param bool $public Files are public by default + * @param string|null $contentDisposition The type of Content-Disposition to set automatically with the filename + * @param bool $autoEtag Whether the ETag header should be automatically set + * @param bool $autoLastModified Whether the Last-Modified header should be automatically set + * + * @return static + */ + public static function create($file = null, $status = 200, $headers = array(), $public = true, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + return new static($file, $status, $headers, $public, $contentDisposition, $autoEtag, $autoLastModified); + } + + /** + * Sets the file to stream. + * + * @param \SplFileInfo|string $file The file to stream + * @param string $contentDisposition + * @param bool $autoEtag + * @param bool $autoLastModified + * + * @return $this + * + * @throws FileException + */ + public function setFile($file, $contentDisposition = null, $autoEtag = false, $autoLastModified = true) + { + if (!$file instanceof File) { + if ($file instanceof \SplFileInfo) { + $file = new File($file->getPathname()); + } else { + $file = new File((string) $file); + } + } + + if (!$file->isReadable()) { + throw new FileException('File must be readable.'); + } + + $this->file = $file; + + if ($autoEtag) { + $this->setAutoEtag(); + } + + if ($autoLastModified) { + $this->setAutoLastModified(); + } + + if ($contentDisposition) { + $this->setContentDisposition($contentDisposition); + } + + return $this; + } + + /** + * Gets the file. + * + * @return File The file to stream + */ + public function getFile() + { + return $this->file; + } + + /** + * Automatically sets the Last-Modified header according the file modification date. + */ + public function setAutoLastModified() + { + $this->setLastModified(\DateTime::createFromFormat('U', $this->file->getMTime())); + + return $this; + } + + /** + * Automatically sets the ETag header according to the checksum of the file. + */ + public function setAutoEtag() + { + $this->setEtag(sha1_file($this->file->getPathname())); + + return $this; + } + + /** + * Sets the Content-Disposition header with the given filename. + * + * @param string $disposition ResponseHeaderBag::DISPOSITION_INLINE or ResponseHeaderBag::DISPOSITION_ATTACHMENT + * @param string $filename Optionally use this UTF-8 encoded filename instead of the real name of the file + * @param string $filenameFallback A fallback filename, containing only ASCII characters. Defaults to an automatically encoded filename + * + * @return $this + */ + public function setContentDisposition($disposition, $filename = '', $filenameFallback = '') + { + if ('' === $filename) { + $filename = $this->file->getFilename(); + } + + if ('' === $filenameFallback && (!preg_match('/^[\x20-\x7e]*$/', $filename) || false !== strpos($filename, '%'))) { + $encoding = mb_detect_encoding($filename, null, true) ?: '8bit'; + + for ($i = 0, $filenameLength = mb_strlen($filename, $encoding); $i < $filenameLength; ++$i) { + $char = mb_substr($filename, $i, 1, $encoding); + + if ('%' === $char || \ord($char) < 32 || \ord($char) > 126) { + $filenameFallback .= '_'; + } else { + $filenameFallback .= $char; + } + } + } + + $dispositionHeader = $this->headers->makeDisposition($disposition, $filename, $filenameFallback); + $this->headers->set('Content-Disposition', $dispositionHeader); + + return $this; + } + + /** + * {@inheritdoc} + */ + public function prepare(Request $request) + { + $this->headers->set('Content-Length', $this->file->getSize()); + + if (!$this->headers->has('Accept-Ranges')) { + // Only accept ranges on safe HTTP methods + $this->headers->set('Accept-Ranges', $request->isMethodSafe(false) ? 'bytes' : 'none'); + } + + if (!$this->headers->has('Content-Type')) { + $this->headers->set('Content-Type', $this->file->getMimeType() ?: 'application/octet-stream'); + } + + if ('HTTP/1.0' !== $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + $this->ensureIEOverSSLCompatibility($request); + + $this->offset = 0; + $this->maxlen = -1; + + if (self::$trustXSendfileTypeHeader && $request->headers->has('X-Sendfile-Type')) { + // Use X-Sendfile, do not send any content. + $type = $request->headers->get('X-Sendfile-Type'); + $path = $this->file->getRealPath(); + // Fall back to scheme://path for stream wrapped locations. + if (false === $path) { + $path = $this->file->getPathname(); + } + if ('x-accel-redirect' === strtolower($type)) { + // Do X-Accel-Mapping substitutions. + // @link http://wiki.nginx.org/X-accel#X-Accel-Redirect + foreach (explode(',', $request->headers->get('X-Accel-Mapping', '')) as $mapping) { + $mapping = explode('=', $mapping, 2); + + if (2 === \count($mapping)) { + $pathPrefix = trim($mapping[0]); + $location = trim($mapping[1]); + + if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) { + $path = $location.substr($path, \strlen($pathPrefix)); + break; + } + } + } + } + $this->headers->set($type, $path); + $this->maxlen = 0; + } elseif ($request->headers->has('Range')) { + // Process the range headers. + if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { + $range = $request->headers->get('Range'); + $fileSize = $this->file->getSize(); + + list($start, $end) = explode('-', substr($range, 6), 2) + array(0); + + $end = ('' === $end) ? $fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $fileSize - $end; + $end = $fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start <= $end) { + if ($start < 0 || $end > $fileSize - 1) { + $this->setStatusCode(416); + $this->headers->set('Content-Range', sprintf('bytes */%s', $fileSize)); + } elseif (0 !== $start || $end !== $fileSize - 1) { + $this->maxlen = $end < $fileSize ? $end - $start + 1 : -1; + $this->offset = $start; + + $this->setStatusCode(206); + $this->headers->set('Content-Range', sprintf('bytes %s-%s/%s', $start, $end, $fileSize)); + $this->headers->set('Content-Length', $end - $start + 1); + } + } + } + } + + return $this; + } + + private function hasValidIfRangeHeader($header) + { + if ($this->getEtag() === $header) { + return true; + } + + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; + } + + /** + * Sends the file. + * + * {@inheritdoc} + */ + public function sendContent() + { + if (!$this->isSuccessful()) { + return parent::sendContent(); + } + + if (0 === $this->maxlen) { + return $this; + } + + $out = fopen('php://output', 'wb'); + $file = fopen($this->file->getPathname(), 'rb'); + + stream_copy_to_stream($file, $out, $this->maxlen, $this->offset); + + fclose($out); + fclose($file); + + if ($this->deleteFileAfterSend) { + unlink($this->file->getPathname()); + } + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent($content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a BinaryFileResponse instance.'); + } + } + + /** + * {@inheritdoc} + * + * @return false + */ + public function getContent() + { + return false; + } + + /** + * Trust X-Sendfile-Type header. + */ + public static function trustXSendfileTypeHeader() + { + self::$trustXSendfileTypeHeader = true; + } + + /** + * If this is set to true, the file will be unlinked after the request is send + * Note: If the X-Sendfile header is used, the deleteFileAfterSend setting will not be used. + * + * @param bool $shouldDelete + * + * @return $this + */ + public function deleteFileAfterSend($shouldDelete) + { + $this->deleteFileAfterSend = $shouldDelete; + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/Cookie.php b/vendor/symfony/http-foundation/Cookie.php new file mode 100644 index 0000000..d31f087 --- /dev/null +++ b/vendor/symfony/http-foundation/Cookie.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Represents a cookie. + * + * @author Johannes M. Schmitt + */ +class Cookie +{ + protected $name; + protected $value; + protected $domain; + protected $expire; + protected $path; + protected $secure; + protected $httpOnly; + + /** + * @param string $name The name of the cookie + * @param string $value The value of the cookie + * @param int|string|\DateTime|\DateTimeInterface $expire The time the cookie expires + * @param string $path The path on the server in which the cookie will be available on + * @param string $domain The domain that the cookie is available to + * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client + * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol + * + * @throws \InvalidArgumentException + */ + public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + // from PHP source code + if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { + throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); + } + + if (empty($name)) { + throw new \InvalidArgumentException('The cookie name cannot be empty.'); + } + + // convert expiration time to a Unix timestamp + if ($expire instanceof \DateTime || $expire instanceof \DateTimeInterface) { + $expire = $expire->format('U'); + } elseif (!is_numeric($expire)) { + $expire = strtotime($expire); + + if (false === $expire) { + throw new \InvalidArgumentException('The cookie expiration time is not valid.'); + } + } + + $this->name = $name; + $this->value = $value; + $this->domain = $domain; + $this->expire = 0 < $expire ? (int) $expire : 0; + $this->path = empty($path) ? '/' : $path; + $this->secure = (bool) $secure; + $this->httpOnly = (bool) $httpOnly; + } + + /** + * Returns the cookie as a string. + * + * @return string The cookie + */ + public function __toString() + { + $str = urlencode($this->getName()).'='; + + if ('' === (string) $this->getValue()) { + $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001); + } else { + $str .= rawurlencode($this->getValue()); + + if (0 !== $this->getExpiresTime()) { + $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()); + } + } + + if ($this->path) { + $str .= '; path='.$this->path; + } + + if ($this->getDomain()) { + $str .= '; domain='.$this->getDomain(); + } + + if (true === $this->isSecure()) { + $str .= '; secure'; + } + + if (true === $this->isHttpOnly()) { + $str .= '; httponly'; + } + + return $str; + } + + /** + * Gets the name of the cookie. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Gets the value of the cookie. + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Gets the domain that the cookie is available to. + * + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * Gets the time the cookie expires. + * + * @return int + */ + public function getExpiresTime() + { + return $this->expire; + } + + /** + * Gets the path on the server in which the cookie will be available on. + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. + * + * @return bool + */ + public function isSecure() + { + return $this->secure; + } + + /** + * Checks whether the cookie will be made accessible only through the HTTP protocol. + * + * @return bool + */ + public function isHttpOnly() + { + return $this->httpOnly; + } + + /** + * Whether this cookie is about to be cleared. + * + * @return bool + */ + public function isCleared() + { + return 0 !== $this->expire && $this->expire < time(); + } +} diff --git a/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php new file mode 100644 index 0000000..fa5f1c7 --- /dev/null +++ b/vendor/symfony/http-foundation/Exception/ConflictingHeadersException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Exception; + +/** + * The HTTP request contains headers with conflicting information. + * + * This exception should trigger an HTTP 400 response in your application code. + * + * @author Magnus Nordlander + */ +class ConflictingHeadersException extends \RuntimeException +{ +} diff --git a/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php new file mode 100644 index 0000000..e9c8441 --- /dev/null +++ b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; + +/** + * ExpressionRequestMatcher uses an expression to match a Request. + * + * @author Fabien Potencier + */ +class ExpressionRequestMatcher extends RequestMatcher +{ + private $language; + private $expression; + + public function setExpression(ExpressionLanguage $language, $expression) + { + $this->language = $language; + $this->expression = $expression; + } + + public function matches(Request $request) + { + if (!$this->language) { + throw new \LogicException('Unable to match the request as the expression language is not available.'); + } + + return $this->language->evaluate($this->expression, array( + 'request' => $request, + 'method' => $request->getMethod(), + 'path' => rawurldecode($request->getPathInfo()), + 'host' => $request->getHost(), + 'ip' => $request->getClientIp(), + 'attributes' => $request->attributes->all(), + )) && parent::matches($request); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php new file mode 100644 index 0000000..3b8e41d --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/AccessDeniedException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when the access on a file was denied. + * + * @author Bernhard Schussek + */ +class AccessDeniedException extends FileException +{ + /** + * @param string $path The path to the accessed file + */ + public function __construct($path) + { + parent::__construct(sprintf('The file %s could not be accessed', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileException.php b/vendor/symfony/http-foundation/File/Exception/FileException.php new file mode 100644 index 0000000..fad5133 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred in the component File. + * + * @author Bernhard Schussek + */ +class FileException extends \RuntimeException +{ +} diff --git a/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php new file mode 100644 index 0000000..bfcc37e --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/FileNotFoundException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when a file was not found. + * + * @author Bernhard Schussek + */ +class FileNotFoundException extends FileException +{ + /** + * @param string $path The path to the file that was not found + */ + public function __construct($path) + { + parent::__construct(sprintf('The file "%s" does not exist', $path)); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000..62005d3 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UnexpectedTypeException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +class UnexpectedTypeException extends FileException +{ + public function __construct($value, $expectedType) + { + parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, \is_object($value) ? \get_class($value) : \gettype($value))); + } +} diff --git a/vendor/symfony/http-foundation/File/Exception/UploadException.php b/vendor/symfony/http-foundation/File/Exception/UploadException.php new file mode 100644 index 0000000..7074e76 --- /dev/null +++ b/vendor/symfony/http-foundation/File/Exception/UploadException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\Exception; + +/** + * Thrown when an error occurred during file upload. + * + * @author Bernhard Schussek + */ +class UploadException extends FileException +{ +} diff --git a/vendor/symfony/http-foundation/File/File.php b/vendor/symfony/http-foundation/File/File.php new file mode 100644 index 0000000..3422058 --- /dev/null +++ b/vendor/symfony/http-foundation/File/File.php @@ -0,0 +1,138 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; +use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; + +/** + * A file in the file system. + * + * @author Bernhard Schussek + */ +class File extends \SplFileInfo +{ + /** + * Constructs a new file from the given path. + * + * @param string $path The path to the file + * @param bool $checkPath Whether to check the path or not + * + * @throws FileNotFoundException If the given path is not a file + */ + public function __construct($path, $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileNotFoundException($path); + } + + parent::__construct($path); + } + + /** + * Returns the extension based on the mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getMimeType() + * to guess the file extension. + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see ExtensionGuesser + * @see getMimeType() + */ + public function guessExtension() + { + $type = $this->getMimeType(); + $guesser = ExtensionGuesser::getInstance(); + + return $guesser->guess($type); + } + + /** + * Returns the mime type of the file. + * + * The mime type is guessed using a MimeTypeGuesser instance, which uses finfo(), + * mime_content_type() and the system binary "file" (in this order), depending on + * which of those are available. + * + * @return string|null The guessed mime type (e.g. "application/pdf") + * + * @see MimeTypeGuesser + */ + public function getMimeType() + { + $guesser = MimeTypeGuesser::getInstance(); + + return $guesser->guess($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @param string $directory The destination folder + * @param string $name The new file name + * + * @return self A File object representing the new file + * + * @throws FileException if the target file could not be created + */ + public function move($directory, $name = null) + { + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $renamed = rename($this->getPathname(), $target); + restore_error_handler(); + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + protected function getTargetFile($directory, $name = null) + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); + } + + $target = rtrim($directory, '/\\').\DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * Returns locale independent base name of the given path. + * + * @param string $name The new file name + * + * @return string containing + */ + protected function getName($name) + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php new file mode 100644 index 0000000..263fb32 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesser.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * A singleton mime type to file extension guesser. + * + * A default guesser is provided. + * You can register custom guessers by calling the register() + * method on the singleton instance: + * + * $guesser = ExtensionGuesser::getInstance(); + * $guesser->register(new MyCustomExtensionGuesser()); + * + * The last registered guesser is preferred over previously registered ones. + */ +class ExtensionGuesser implements ExtensionGuesserInterface +{ + /** + * The singleton instance. + * + * @var ExtensionGuesser + */ + private static $instance = null; + + /** + * All registered ExtensionGuesserInterface instances. + * + * @var array + */ + protected $guessers = array(); + + /** + * Returns the singleton instance. + * + * @return self + */ + public static function getInstance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Registers all natively provided extension guessers. + */ + private function __construct() + { + $this->register(new MimeTypeExtensionGuesser()); + } + + /** + * Registers a new extension guesser. + * + * When guessing, this guesser is preferred over previously registered ones. + */ + public function register(ExtensionGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * Tries to guess the extension. + * + * The mime type is passed to each registered mime type guesser in reverse order + * of their registration (last registered is queried first). Once a guesser + * returns a value that is not NULL, this method terminates and returns the + * value. + * + * @param string $mimeType The mime type + * + * @return string The guessed extension or NULL, if none could be guessed + */ + public function guess($mimeType) + { + foreach ($this->guessers as $guesser) { + if (null !== $extension = $guesser->guess($mimeType)) { + return $extension; + } + } + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php new file mode 100644 index 0000000..d19a0e5 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/ExtensionGuesserInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * Guesses the file extension corresponding to a given mime type. + */ +interface ExtensionGuesserInterface +{ + /** + * Makes a best guess for a file extension, given a mime type. + * + * @param string $mimeType The mime type + * + * @return string The guessed extension or NULL, if none could be guessed + */ + public function guess($mimeType); +} diff --git a/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php new file mode 100644 index 0000000..34e015e --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/FileBinaryMimeTypeGuesser.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; + +/** + * Guesses the mime type with the binary "file" (only available on *nix). + * + * @author Bernhard Schussek + */ +class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $cmd; + + /** + * The $cmd pattern must contain a "%s" string that will be replaced + * with the file name to guess. + * + * The command output must start with the mime type of the file. + * + * @param string $cmd The command to run to get the mime type of a file + */ + public function __construct($cmd = 'file -b --mime %s 2>/dev/null') + { + $this->cmd = $cmd; + } + + /** + * Returns whether this guesser is supported on the current OS. + * + * @return bool + */ + public static function isSupported() + { + static $supported = null; + + if (null !== $supported) { + return $supported; + } + + if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) { + return $supported = false; + } + + ob_start(); + passthru('command -v file', $exitStatus); + $binPath = trim(ob_get_clean()); + + return $supported = 0 === $exitStatus && '' !== $binPath; + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return; + } + + ob_start(); + + // need to use --mime instead of -i. see #6641 + passthru(sprintf($this->cmd, escapeshellarg($path)), $return); + if ($return > 0) { + ob_end_clean(); + + return; + } + + $type = trim(ob_get_clean()); + + if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) { + // it's not a type, but an error message + return; + } + + return $match[1]; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php new file mode 100644 index 0000000..bf1ee9f --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/FileinfoMimeTypeGuesser.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; + +/** + * Guesses the mime type using the PECL extension FileInfo. + * + * @author Bernhard Schussek + */ +class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface +{ + private $magicFile; + + /** + * @param string $magicFile A magic file to use with the finfo instance + * + * @see http://www.php.net/manual/en/function.finfo-open.php + */ + public function __construct($magicFile = null) + { + $this->magicFile = $magicFile; + } + + /** + * Returns whether this guesser is supported on the current OS/PHP setup. + * + * @return bool + */ + public static function isSupported() + { + return \function_exists('finfo_open'); + } + + /** + * {@inheritdoc} + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + if (!self::isSupported()) { + return; + } + + if (!$finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) { + return; + } + + return $finfo->file($path); + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php new file mode 100644 index 0000000..36271af --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeExtensionGuesser.php @@ -0,0 +1,807 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +/** + * Provides a best-guess mapping of mime type to file extension. + */ +class MimeTypeExtensionGuesser implements ExtensionGuesserInterface +{ + /** + * A map of mime types and their default extensions. + * + * This list has been placed under the public domain by the Apache HTTPD project. + * This list has been updated from upstream on 2013-04-23. + * + * @see http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types + */ + protected $defaultExtensions = array( + 'application/andrew-inset' => 'ez', + 'application/applixware' => 'aw', + 'application/atom+xml' => 'atom', + 'application/atomcat+xml' => 'atomcat', + 'application/atomsvc+xml' => 'atomsvc', + 'application/ccxml+xml' => 'ccxml', + 'application/cdmi-capability' => 'cdmia', + 'application/cdmi-container' => 'cdmic', + 'application/cdmi-domain' => 'cdmid', + 'application/cdmi-object' => 'cdmio', + 'application/cdmi-queue' => 'cdmiq', + 'application/cu-seeme' => 'cu', + 'application/davmount+xml' => 'davmount', + 'application/docbook+xml' => 'dbk', + 'application/dssc+der' => 'dssc', + 'application/dssc+xml' => 'xdssc', + 'application/ecmascript' => 'ecma', + 'application/emma+xml' => 'emma', + 'application/epub+zip' => 'epub', + 'application/exi' => 'exi', + 'application/font-tdpfr' => 'pfr', + 'application/gml+xml' => 'gml', + 'application/gpx+xml' => 'gpx', + 'application/gxf' => 'gxf', + 'application/hyperstudio' => 'stk', + 'application/inkml+xml' => 'ink', + 'application/ipfix' => 'ipfix', + 'application/java-archive' => 'jar', + 'application/java-serialized-object' => 'ser', + 'application/java-vm' => 'class', + 'application/javascript' => 'js', + 'application/json' => 'json', + 'application/jsonml+json' => 'jsonml', + 'application/lost+xml' => 'lostxml', + 'application/mac-binhex40' => 'hqx', + 'application/mac-compactpro' => 'cpt', + 'application/mads+xml' => 'mads', + 'application/marc' => 'mrc', + 'application/marcxml+xml' => 'mrcx', + 'application/mathematica' => 'ma', + 'application/mathml+xml' => 'mathml', + 'application/mbox' => 'mbox', + 'application/mediaservercontrol+xml' => 'mscml', + 'application/metalink+xml' => 'metalink', + 'application/metalink4+xml' => 'meta4', + 'application/mets+xml' => 'mets', + 'application/mods+xml' => 'mods', + 'application/mp21' => 'm21', + 'application/mp4' => 'mp4s', + 'application/msword' => 'doc', + 'application/mxf' => 'mxf', + 'application/octet-stream' => 'bin', + 'application/oda' => 'oda', + 'application/oebps-package+xml' => 'opf', + 'application/ogg' => 'ogx', + 'application/omdoc+xml' => 'omdoc', + 'application/onenote' => 'onetoc', + 'application/oxps' => 'oxps', + 'application/patch-ops-error+xml' => 'xer', + 'application/pdf' => 'pdf', + 'application/pgp-encrypted' => 'pgp', + 'application/pgp-signature' => 'asc', + 'application/pics-rules' => 'prf', + 'application/pkcs10' => 'p10', + 'application/pkcs7-mime' => 'p7m', + 'application/pkcs7-signature' => 'p7s', + 'application/pkcs8' => 'p8', + 'application/pkix-attr-cert' => 'ac', + 'application/pkix-cert' => 'cer', + 'application/pkix-crl' => 'crl', + 'application/pkix-pkipath' => 'pkipath', + 'application/pkixcmp' => 'pki', + 'application/pls+xml' => 'pls', + 'application/postscript' => 'ai', + 'application/prs.cww' => 'cww', + 'application/pskc+xml' => 'pskcxml', + 'application/rdf+xml' => 'rdf', + 'application/reginfo+xml' => 'rif', + 'application/relax-ng-compact-syntax' => 'rnc', + 'application/resource-lists+xml' => 'rl', + 'application/resource-lists-diff+xml' => 'rld', + 'application/rls-services+xml' => 'rs', + 'application/rpki-ghostbusters' => 'gbr', + 'application/rpki-manifest' => 'mft', + 'application/rpki-roa' => 'roa', + 'application/rsd+xml' => 'rsd', + 'application/rss+xml' => 'rss', + 'application/rtf' => 'rtf', + 'application/sbml+xml' => 'sbml', + 'application/scvp-cv-request' => 'scq', + 'application/scvp-cv-response' => 'scs', + 'application/scvp-vp-request' => 'spq', + 'application/scvp-vp-response' => 'spp', + 'application/sdp' => 'sdp', + 'application/set-payment-initiation' => 'setpay', + 'application/set-registration-initiation' => 'setreg', + 'application/shf+xml' => 'shf', + 'application/smil+xml' => 'smi', + 'application/sparql-query' => 'rq', + 'application/sparql-results+xml' => 'srx', + 'application/srgs' => 'gram', + 'application/srgs+xml' => 'grxml', + 'application/sru+xml' => 'sru', + 'application/ssdl+xml' => 'ssdl', + 'application/ssml+xml' => 'ssml', + 'application/tei+xml' => 'tei', + 'application/thraud+xml' => 'tfi', + 'application/timestamped-data' => 'tsd', + 'application/vnd.3gpp.pic-bw-large' => 'plb', + 'application/vnd.3gpp.pic-bw-small' => 'psb', + 'application/vnd.3gpp.pic-bw-var' => 'pvb', + 'application/vnd.3gpp2.tcap' => 'tcap', + 'application/vnd.3m.post-it-notes' => 'pwn', + 'application/vnd.accpac.simply.aso' => 'aso', + 'application/vnd.accpac.simply.imp' => 'imp', + 'application/vnd.acucobol' => 'acu', + 'application/vnd.acucorp' => 'atc', + 'application/vnd.adobe.air-application-installer-package+zip' => 'air', + 'application/vnd.adobe.formscentral.fcdt' => 'fcdt', + 'application/vnd.adobe.fxp' => 'fxp', + 'application/vnd.adobe.xdp+xml' => 'xdp', + 'application/vnd.adobe.xfdf' => 'xfdf', + 'application/vnd.ahead.space' => 'ahead', + 'application/vnd.airzip.filesecure.azf' => 'azf', + 'application/vnd.airzip.filesecure.azs' => 'azs', + 'application/vnd.amazon.ebook' => 'azw', + 'application/vnd.americandynamics.acc' => 'acc', + 'application/vnd.amiga.ami' => 'ami', + 'application/vnd.android.package-archive' => 'apk', + 'application/vnd.anser-web-certificate-issue-initiation' => 'cii', + 'application/vnd.anser-web-funds-transfer-initiation' => 'fti', + 'application/vnd.antix.game-component' => 'atx', + 'application/vnd.apple.installer+xml' => 'mpkg', + 'application/vnd.apple.mpegurl' => 'm3u8', + 'application/vnd.aristanetworks.swi' => 'swi', + 'application/vnd.astraea-software.iota' => 'iota', + 'application/vnd.audiograph' => 'aep', + 'application/vnd.blueice.multipass' => 'mpm', + 'application/vnd.bmi' => 'bmi', + 'application/vnd.businessobjects' => 'rep', + 'application/vnd.chemdraw+xml' => 'cdxml', + 'application/vnd.chipnuts.karaoke-mmd' => 'mmd', + 'application/vnd.cinderella' => 'cdy', + 'application/vnd.claymore' => 'cla', + 'application/vnd.cloanto.rp9' => 'rp9', + 'application/vnd.clonk.c4group' => 'c4g', + 'application/vnd.cluetrust.cartomobile-config' => 'c11amc', + 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz', + 'application/vnd.commonspace' => 'csp', + 'application/vnd.contact.cmsg' => 'cdbcmsg', + 'application/vnd.cosmocaller' => 'cmc', + 'application/vnd.crick.clicker' => 'clkx', + 'application/vnd.crick.clicker.keyboard' => 'clkk', + 'application/vnd.crick.clicker.palette' => 'clkp', + 'application/vnd.crick.clicker.template' => 'clkt', + 'application/vnd.crick.clicker.wordbank' => 'clkw', + 'application/vnd.criticaltools.wbs+xml' => 'wbs', + 'application/vnd.ctc-posml' => 'pml', + 'application/vnd.cups-ppd' => 'ppd', + 'application/vnd.curl.car' => 'car', + 'application/vnd.curl.pcurl' => 'pcurl', + 'application/vnd.dart' => 'dart', + 'application/vnd.data-vision.rdz' => 'rdz', + 'application/vnd.dece.data' => 'uvf', + 'application/vnd.dece.ttml+xml' => 'uvt', + 'application/vnd.dece.unspecified' => 'uvx', + 'application/vnd.dece.zip' => 'uvz', + 'application/vnd.denovo.fcselayout-link' => 'fe_launch', + 'application/vnd.dna' => 'dna', + 'application/vnd.dolby.mlp' => 'mlp', + 'application/vnd.dpgraph' => 'dpg', + 'application/vnd.dreamfactory' => 'dfac', + 'application/vnd.ds-keypoint' => 'kpxx', + 'application/vnd.dvb.ait' => 'ait', + 'application/vnd.dvb.service' => 'svc', + 'application/vnd.dynageo' => 'geo', + 'application/vnd.ecowin.chart' => 'mag', + 'application/vnd.enliven' => 'nml', + 'application/vnd.epson.esf' => 'esf', + 'application/vnd.epson.msf' => 'msf', + 'application/vnd.epson.quickanime' => 'qam', + 'application/vnd.epson.salt' => 'slt', + 'application/vnd.epson.ssf' => 'ssf', + 'application/vnd.eszigno3+xml' => 'es3', + 'application/vnd.ezpix-album' => 'ez2', + 'application/vnd.ezpix-package' => 'ez3', + 'application/vnd.fdf' => 'fdf', + 'application/vnd.fdsn.mseed' => 'mseed', + 'application/vnd.fdsn.seed' => 'seed', + 'application/vnd.flographit' => 'gph', + 'application/vnd.fluxtime.clip' => 'ftc', + 'application/vnd.framemaker' => 'fm', + 'application/vnd.frogans.fnc' => 'fnc', + 'application/vnd.frogans.ltf' => 'ltf', + 'application/vnd.fsc.weblaunch' => 'fsc', + 'application/vnd.fujitsu.oasys' => 'oas', + 'application/vnd.fujitsu.oasys2' => 'oa2', + 'application/vnd.fujitsu.oasys3' => 'oa3', + 'application/vnd.fujitsu.oasysgp' => 'fg5', + 'application/vnd.fujitsu.oasysprs' => 'bh2', + 'application/vnd.fujixerox.ddd' => 'ddd', + 'application/vnd.fujixerox.docuworks' => 'xdw', + 'application/vnd.fujixerox.docuworks.binder' => 'xbd', + 'application/vnd.fuzzysheet' => 'fzs', + 'application/vnd.genomatix.tuxedo' => 'txd', + 'application/vnd.geogebra.file' => 'ggb', + 'application/vnd.geogebra.tool' => 'ggt', + 'application/vnd.geometry-explorer' => 'gex', + 'application/vnd.geonext' => 'gxt', + 'application/vnd.geoplan' => 'g2w', + 'application/vnd.geospace' => 'g3w', + 'application/vnd.gmx' => 'gmx', + 'application/vnd.google-earth.kml+xml' => 'kml', + 'application/vnd.google-earth.kmz' => 'kmz', + 'application/vnd.grafeq' => 'gqf', + 'application/vnd.groove-account' => 'gac', + 'application/vnd.groove-help' => 'ghf', + 'application/vnd.groove-identity-message' => 'gim', + 'application/vnd.groove-injector' => 'grv', + 'application/vnd.groove-tool-message' => 'gtm', + 'application/vnd.groove-tool-template' => 'tpl', + 'application/vnd.groove-vcard' => 'vcg', + 'application/vnd.hal+xml' => 'hal', + 'application/vnd.handheld-entertainment+xml' => 'zmm', + 'application/vnd.hbci' => 'hbci', + 'application/vnd.hhe.lesson-player' => 'les', + 'application/vnd.hp-hpgl' => 'hpgl', + 'application/vnd.hp-hpid' => 'hpid', + 'application/vnd.hp-hps' => 'hps', + 'application/vnd.hp-jlyt' => 'jlt', + 'application/vnd.hp-pcl' => 'pcl', + 'application/vnd.hp-pclxl' => 'pclxl', + 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', + 'application/vnd.ibm.minipay' => 'mpy', + 'application/vnd.ibm.modcap' => 'afp', + 'application/vnd.ibm.rights-management' => 'irm', + 'application/vnd.ibm.secure-container' => 'sc', + 'application/vnd.iccprofile' => 'icc', + 'application/vnd.igloader' => 'igl', + 'application/vnd.immervision-ivp' => 'ivp', + 'application/vnd.immervision-ivu' => 'ivu', + 'application/vnd.insors.igm' => 'igm', + 'application/vnd.intercon.formnet' => 'xpw', + 'application/vnd.intergeo' => 'i2g', + 'application/vnd.intu.qbo' => 'qbo', + 'application/vnd.intu.qfx' => 'qfx', + 'application/vnd.ipunplugged.rcprofile' => 'rcprofile', + 'application/vnd.irepository.package+xml' => 'irp', + 'application/vnd.is-xpr' => 'xpr', + 'application/vnd.isac.fcs' => 'fcs', + 'application/vnd.jam' => 'jam', + 'application/vnd.jcp.javame.midlet-rms' => 'rms', + 'application/vnd.jisp' => 'jisp', + 'application/vnd.joost.joda-archive' => 'joda', + 'application/vnd.kahootz' => 'ktz', + 'application/vnd.kde.karbon' => 'karbon', + 'application/vnd.kde.kchart' => 'chrt', + 'application/vnd.kde.kformula' => 'kfo', + 'application/vnd.kde.kivio' => 'flw', + 'application/vnd.kde.kontour' => 'kon', + 'application/vnd.kde.kpresenter' => 'kpr', + 'application/vnd.kde.kspread' => 'ksp', + 'application/vnd.kde.kword' => 'kwd', + 'application/vnd.kenameaapp' => 'htke', + 'application/vnd.kidspiration' => 'kia', + 'application/vnd.kinar' => 'kne', + 'application/vnd.koan' => 'skp', + 'application/vnd.kodak-descriptor' => 'sse', + 'application/vnd.las.las+xml' => 'lasxml', + 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', + 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', + 'application/vnd.lotus-1-2-3' => '123', + 'application/vnd.lotus-approach' => 'apr', + 'application/vnd.lotus-freelance' => 'pre', + 'application/vnd.lotus-notes' => 'nsf', + 'application/vnd.lotus-organizer' => 'org', + 'application/vnd.lotus-screencam' => 'scm', + 'application/vnd.lotus-wordpro' => 'lwp', + 'application/vnd.macports.portpkg' => 'portpkg', + 'application/vnd.mcd' => 'mcd', + 'application/vnd.medcalcdata' => 'mc1', + 'application/vnd.mediastation.cdkey' => 'cdkey', + 'application/vnd.mfer' => 'mwf', + 'application/vnd.mfmp' => 'mfm', + 'application/vnd.micrografx.flo' => 'flo', + 'application/vnd.micrografx.igx' => 'igx', + 'application/vnd.mif' => 'mif', + 'application/vnd.mobius.daf' => 'daf', + 'application/vnd.mobius.dis' => 'dis', + 'application/vnd.mobius.mbk' => 'mbk', + 'application/vnd.mobius.mqy' => 'mqy', + 'application/vnd.mobius.msl' => 'msl', + 'application/vnd.mobius.plc' => 'plc', + 'application/vnd.mobius.txf' => 'txf', + 'application/vnd.mophun.application' => 'mpn', + 'application/vnd.mophun.certificate' => 'mpc', + 'application/vnd.mozilla.xul+xml' => 'xul', + 'application/vnd.ms-artgalry' => 'cil', + 'application/vnd.ms-cab-compressed' => 'cab', + 'application/vnd.ms-excel' => 'xls', + 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', + 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', + 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', + 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', + 'application/vnd.ms-fontobject' => 'eot', + 'application/vnd.ms-htmlhelp' => 'chm', + 'application/vnd.ms-ims' => 'ims', + 'application/vnd.ms-lrm' => 'lrm', + 'application/vnd.ms-officetheme' => 'thmx', + 'application/vnd.ms-pki.seccat' => 'cat', + 'application/vnd.ms-pki.stl' => 'stl', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', + 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', + 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', + 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', + 'application/vnd.ms-project' => 'mpp', + 'application/vnd.ms-word.document.macroenabled.12' => 'docm', + 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', + 'application/vnd.ms-works' => 'wps', + 'application/vnd.ms-wpl' => 'wpl', + 'application/vnd.ms-xpsdocument' => 'xps', + 'application/vnd.mseq' => 'mseq', + 'application/vnd.musician' => 'mus', + 'application/vnd.muvee.style' => 'msty', + 'application/vnd.mynfc' => 'taglet', + 'application/vnd.neurolanguage.nlu' => 'nlu', + 'application/vnd.nitf' => 'ntf', + 'application/vnd.noblenet-directory' => 'nnd', + 'application/vnd.noblenet-sealer' => 'nns', + 'application/vnd.noblenet-web' => 'nnw', + 'application/vnd.nokia.n-gage.data' => 'ngdat', + 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage', + 'application/vnd.nokia.radio-preset' => 'rpst', + 'application/vnd.nokia.radio-presets' => 'rpss', + 'application/vnd.novadigm.edm' => 'edm', + 'application/vnd.novadigm.edx' => 'edx', + 'application/vnd.novadigm.ext' => 'ext', + 'application/vnd.oasis.opendocument.chart' => 'odc', + 'application/vnd.oasis.opendocument.chart-template' => 'otc', + 'application/vnd.oasis.opendocument.database' => 'odb', + 'application/vnd.oasis.opendocument.formula' => 'odf', + 'application/vnd.oasis.opendocument.formula-template' => 'odft', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + 'application/vnd.oasis.opendocument.graphics-template' => 'otg', + 'application/vnd.oasis.opendocument.image' => 'odi', + 'application/vnd.oasis.opendocument.image-template' => 'oti', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/vnd.oasis.opendocument.presentation-template' => 'otp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.text-master' => 'odm', + 'application/vnd.oasis.opendocument.text-template' => 'ott', + 'application/vnd.oasis.opendocument.text-web' => 'oth', + 'application/vnd.olpc-sugar' => 'xo', + 'application/vnd.oma.dd2+xml' => 'dd2', + 'application/vnd.openofficeorg.extension' => 'oxt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', + 'application/vnd.osgeo.mapguide.package' => 'mgp', + 'application/vnd.osgi.dp' => 'dp', + 'application/vnd.osgi.subsystem' => 'esa', + 'application/vnd.palm' => 'pdb', + 'application/vnd.pawaafile' => 'paw', + 'application/vnd.pg.format' => 'str', + 'application/vnd.pg.osasli' => 'ei6', + 'application/vnd.picsel' => 'efif', + 'application/vnd.pmi.widget' => 'wg', + 'application/vnd.pocketlearn' => 'plf', + 'application/vnd.powerbuilder6' => 'pbd', + 'application/vnd.previewsystems.box' => 'box', + 'application/vnd.proteus.magazine' => 'mgz', + 'application/vnd.publishare-delta-tree' => 'qps', + 'application/vnd.pvi.ptid1' => 'ptid', + 'application/vnd.quark.quarkxpress' => 'qxd', + 'application/vnd.realvnc.bed' => 'bed', + 'application/vnd.recordare.musicxml' => 'mxl', + 'application/vnd.recordare.musicxml+xml' => 'musicxml', + 'application/vnd.rig.cryptonote' => 'cryptonote', + 'application/vnd.rim.cod' => 'cod', + 'application/vnd.rn-realmedia' => 'rm', + 'application/vnd.rn-realmedia-vbr' => 'rmvb', + 'application/vnd.route66.link66+xml' => 'link66', + 'application/vnd.sailingtracker.track' => 'st', + 'application/vnd.seemail' => 'see', + 'application/vnd.sema' => 'sema', + 'application/vnd.semd' => 'semd', + 'application/vnd.semf' => 'semf', + 'application/vnd.shana.informed.formdata' => 'ifm', + 'application/vnd.shana.informed.formtemplate' => 'itp', + 'application/vnd.shana.informed.interchange' => 'iif', + 'application/vnd.shana.informed.package' => 'ipk', + 'application/vnd.simtech-mindmapper' => 'twd', + 'application/vnd.smaf' => 'mmf', + 'application/vnd.smart.teacher' => 'teacher', + 'application/vnd.solent.sdkm+xml' => 'sdkm', + 'application/vnd.spotfire.dxp' => 'dxp', + 'application/vnd.spotfire.sfs' => 'sfs', + 'application/vnd.stardivision.calc' => 'sdc', + 'application/vnd.stardivision.draw' => 'sda', + 'application/vnd.stardivision.impress' => 'sdd', + 'application/vnd.stardivision.math' => 'smf', + 'application/vnd.stardivision.writer' => 'sdw', + 'application/vnd.stardivision.writer-global' => 'sgl', + 'application/vnd.stepmania.package' => 'smzip', + 'application/vnd.stepmania.stepchart' => 'sm', + 'application/vnd.sun.xml.calc' => 'sxc', + 'application/vnd.sun.xml.calc.template' => 'stc', + 'application/vnd.sun.xml.draw' => 'sxd', + 'application/vnd.sun.xml.draw.template' => 'std', + 'application/vnd.sun.xml.impress' => 'sxi', + 'application/vnd.sun.xml.impress.template' => 'sti', + 'application/vnd.sun.xml.math' => 'sxm', + 'application/vnd.sun.xml.writer' => 'sxw', + 'application/vnd.sun.xml.writer.global' => 'sxg', + 'application/vnd.sun.xml.writer.template' => 'stw', + 'application/vnd.sus-calendar' => 'sus', + 'application/vnd.svd' => 'svd', + 'application/vnd.symbian.install' => 'sis', + 'application/vnd.syncml+xml' => 'xsm', + 'application/vnd.syncml.dm+wbxml' => 'bdm', + 'application/vnd.syncml.dm+xml' => 'xdm', + 'application/vnd.tao.intent-module-archive' => 'tao', + 'application/vnd.tcpdump.pcap' => 'pcap', + 'application/vnd.tmobile-livetv' => 'tmo', + 'application/vnd.trid.tpt' => 'tpt', + 'application/vnd.triscape.mxs' => 'mxs', + 'application/vnd.trueapp' => 'tra', + 'application/vnd.ufdl' => 'ufd', + 'application/vnd.uiq.theme' => 'utz', + 'application/vnd.umajin' => 'umj', + 'application/vnd.unity' => 'unityweb', + 'application/vnd.uoml+xml' => 'uoml', + 'application/vnd.vcx' => 'vcx', + 'application/vnd.visio' => 'vsd', + 'application/vnd.visionary' => 'vis', + 'application/vnd.vsf' => 'vsf', + 'application/vnd.wap.wbxml' => 'wbxml', + 'application/vnd.wap.wmlc' => 'wmlc', + 'application/vnd.wap.wmlscriptc' => 'wmlsc', + 'application/vnd.webturbo' => 'wtb', + 'application/vnd.wolfram.player' => 'nbp', + 'application/vnd.wordperfect' => 'wpd', + 'application/vnd.wqd' => 'wqd', + 'application/vnd.wt.stf' => 'stf', + 'application/vnd.xara' => 'xar', + 'application/vnd.xfdl' => 'xfdl', + 'application/vnd.yamaha.hv-dic' => 'hvd', + 'application/vnd.yamaha.hv-script' => 'hvs', + 'application/vnd.yamaha.hv-voice' => 'hvp', + 'application/vnd.yamaha.openscoreformat' => 'osf', + 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg', + 'application/vnd.yamaha.smaf-audio' => 'saf', + 'application/vnd.yamaha.smaf-phrase' => 'spf', + 'application/vnd.yellowriver-custom-menu' => 'cmp', + 'application/vnd.zul' => 'zir', + 'application/vnd.zzazz.deck+xml' => 'zaz', + 'application/voicexml+xml' => 'vxml', + 'application/widget' => 'wgt', + 'application/winhlp' => 'hlp', + 'application/wsdl+xml' => 'wsdl', + 'application/wspolicy+xml' => 'wspolicy', + 'application/x-7z-compressed' => '7z', + 'application/x-abiword' => 'abw', + 'application/x-ace-compressed' => 'ace', + 'application/x-apple-diskimage' => 'dmg', + 'application/x-authorware-bin' => 'aab', + 'application/x-authorware-map' => 'aam', + 'application/x-authorware-seg' => 'aas', + 'application/x-bcpio' => 'bcpio', + 'application/x-bittorrent' => 'torrent', + 'application/x-blorb' => 'blb', + 'application/x-bzip' => 'bz', + 'application/x-bzip2' => 'bz2', + 'application/x-cbr' => 'cbr', + 'application/x-cdlink' => 'vcd', + 'application/x-cfs-compressed' => 'cfs', + 'application/x-chat' => 'chat', + 'application/x-chess-pgn' => 'pgn', + 'application/x-conference' => 'nsc', + 'application/x-cpio' => 'cpio', + 'application/x-csh' => 'csh', + 'application/x-debian-package' => 'deb', + 'application/x-dgc-compressed' => 'dgc', + 'application/x-director' => 'dir', + 'application/x-doom' => 'wad', + 'application/x-dtbncx+xml' => 'ncx', + 'application/x-dtbook+xml' => 'dtb', + 'application/x-dtbresource+xml' => 'res', + 'application/x-dvi' => 'dvi', + 'application/x-envoy' => 'evy', + 'application/x-eva' => 'eva', + 'application/x-font-bdf' => 'bdf', + 'application/x-font-ghostscript' => 'gsf', + 'application/x-font-linux-psf' => 'psf', + 'application/x-font-otf' => 'otf', + 'application/x-font-pcf' => 'pcf', + 'application/x-font-snf' => 'snf', + 'application/x-font-ttf' => 'ttf', + 'application/x-font-type1' => 'pfa', + 'application/x-font-woff' => 'woff', + 'application/x-freearc' => 'arc', + 'application/x-futuresplash' => 'spl', + 'application/x-gca-compressed' => 'gca', + 'application/x-glulx' => 'ulx', + 'application/x-gnumeric' => 'gnumeric', + 'application/x-gramps-xml' => 'gramps', + 'application/x-gtar' => 'gtar', + 'application/x-hdf' => 'hdf', + 'application/x-install-instructions' => 'install', + 'application/x-iso9660-image' => 'iso', + 'application/x-java-jnlp-file' => 'jnlp', + 'application/x-latex' => 'latex', + 'application/x-lzh-compressed' => 'lzh', + 'application/x-mie' => 'mie', + 'application/x-mobipocket-ebook' => 'prc', + 'application/x-ms-application' => 'application', + 'application/x-ms-shortcut' => 'lnk', + 'application/x-ms-wmd' => 'wmd', + 'application/x-ms-wmz' => 'wmz', + 'application/x-ms-xbap' => 'xbap', + 'application/x-msaccess' => 'mdb', + 'application/x-msbinder' => 'obd', + 'application/x-mscardfile' => 'crd', + 'application/x-msclip' => 'clp', + 'application/x-msdownload' => 'exe', + 'application/x-msmediaview' => 'mvb', + 'application/x-msmetafile' => 'wmf', + 'application/x-msmoney' => 'mny', + 'application/x-mspublisher' => 'pub', + 'application/x-msschedule' => 'scd', + 'application/x-msterminal' => 'trm', + 'application/x-mswrite' => 'wri', + 'application/x-netcdf' => 'nc', + 'application/x-nzb' => 'nzb', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-certificates' => 'p7b', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/x-rar-compressed' => 'rar', + 'application/x-rar' => 'rar', + 'application/x-research-info-systems' => 'ris', + 'application/x-sh' => 'sh', + 'application/x-shar' => 'shar', + 'application/x-shockwave-flash' => 'swf', + 'application/x-silverlight-app' => 'xap', + 'application/x-sql' => 'sql', + 'application/x-stuffit' => 'sit', + 'application/x-stuffitx' => 'sitx', + 'application/x-subrip' => 'srt', + 'application/x-sv4cpio' => 'sv4cpio', + 'application/x-sv4crc' => 'sv4crc', + 'application/x-t3vm-image' => 't3', + 'application/x-tads' => 'gam', + 'application/x-tar' => 'tar', + 'application/x-tcl' => 'tcl', + 'application/x-tex' => 'tex', + 'application/x-tex-tfm' => 'tfm', + 'application/x-texinfo' => 'texinfo', + 'application/x-tgif' => 'obj', + 'application/x-ustar' => 'ustar', + 'application/x-wais-source' => 'src', + 'application/x-x509-ca-cert' => 'der', + 'application/x-xfig' => 'fig', + 'application/x-xliff+xml' => 'xlf', + 'application/x-xpinstall' => 'xpi', + 'application/x-xz' => 'xz', + 'application/x-zip-compressed' => 'zip', + 'application/x-zmachine' => 'z1', + 'application/xaml+xml' => 'xaml', + 'application/xcap-diff+xml' => 'xdf', + 'application/xenc+xml' => 'xenc', + 'application/xhtml+xml' => 'xhtml', + 'application/xml' => 'xml', + 'application/xml-dtd' => 'dtd', + 'application/xop+xml' => 'xop', + 'application/xproc+xml' => 'xpl', + 'application/xslt+xml' => 'xslt', + 'application/xspf+xml' => 'xspf', + 'application/xv+xml' => 'mxml', + 'application/yang' => 'yang', + 'application/yin+xml' => 'yin', + 'application/zip' => 'zip', + 'audio/adpcm' => 'adp', + 'audio/basic' => 'au', + 'audio/midi' => 'mid', + 'audio/mp4' => 'mp4a', + 'audio/mpeg' => 'mpga', + 'audio/ogg' => 'oga', + 'audio/s3m' => 's3m', + 'audio/silk' => 'sil', + 'audio/vnd.dece.audio' => 'uva', + 'audio/vnd.digital-winds' => 'eol', + 'audio/vnd.dra' => 'dra', + 'audio/vnd.dts' => 'dts', + 'audio/vnd.dts.hd' => 'dtshd', + 'audio/vnd.lucent.voice' => 'lvp', + 'audio/vnd.ms-playready.media.pya' => 'pya', + 'audio/vnd.nuera.ecelp4800' => 'ecelp4800', + 'audio/vnd.nuera.ecelp7470' => 'ecelp7470', + 'audio/vnd.nuera.ecelp9600' => 'ecelp9600', + 'audio/vnd.rip' => 'rip', + 'audio/webm' => 'weba', + 'audio/x-aac' => 'aac', + 'audio/x-aiff' => 'aif', + 'audio/x-caf' => 'caf', + 'audio/x-flac' => 'flac', + 'audio/x-matroska' => 'mka', + 'audio/x-mpegurl' => 'm3u', + 'audio/x-ms-wax' => 'wax', + 'audio/x-ms-wma' => 'wma', + 'audio/x-pn-realaudio' => 'ram', + 'audio/x-pn-realaudio-plugin' => 'rmp', + 'audio/x-wav' => 'wav', + 'audio/xm' => 'xm', + 'chemical/x-cdx' => 'cdx', + 'chemical/x-cif' => 'cif', + 'chemical/x-cmdf' => 'cmdf', + 'chemical/x-cml' => 'cml', + 'chemical/x-csml' => 'csml', + 'chemical/x-xyz' => 'xyz', + 'image/bmp' => 'bmp', + 'image/x-ms-bmp' => 'bmp', + 'image/cgm' => 'cgm', + 'image/g3fax' => 'g3', + 'image/gif' => 'gif', + 'image/ief' => 'ief', + 'image/jpeg' => 'jpeg', + 'image/pjpeg' => 'jpeg', + 'image/ktx' => 'ktx', + 'image/png' => 'png', + 'image/prs.btif' => 'btif', + 'image/sgi' => 'sgi', + 'image/svg+xml' => 'svg', + 'image/tiff' => 'tiff', + 'image/vnd.adobe.photoshop' => 'psd', + 'image/vnd.dece.graphic' => 'uvi', + 'image/vnd.dvb.subtitle' => 'sub', + 'image/vnd.djvu' => 'djvu', + 'image/vnd.dwg' => 'dwg', + 'image/vnd.dxf' => 'dxf', + 'image/vnd.fastbidsheet' => 'fbs', + 'image/vnd.fpx' => 'fpx', + 'image/vnd.fst' => 'fst', + 'image/vnd.fujixerox.edmics-mmr' => 'mmr', + 'image/vnd.fujixerox.edmics-rlc' => 'rlc', + 'image/vnd.ms-modi' => 'mdi', + 'image/vnd.ms-photo' => 'wdp', + 'image/vnd.net-fpx' => 'npx', + 'image/vnd.wap.wbmp' => 'wbmp', + 'image/vnd.xiff' => 'xif', + 'image/webp' => 'webp', + 'image/x-3ds' => '3ds', + 'image/x-cmu-raster' => 'ras', + 'image/x-cmx' => 'cmx', + 'image/x-freehand' => 'fh', + 'image/x-icon' => 'ico', + 'image/x-mrsid-image' => 'sid', + 'image/x-pcx' => 'pcx', + 'image/x-pict' => 'pic', + 'image/x-portable-anymap' => 'pnm', + 'image/x-portable-bitmap' => 'pbm', + 'image/x-portable-graymap' => 'pgm', + 'image/x-portable-pixmap' => 'ppm', + 'image/x-rgb' => 'rgb', + 'image/x-tga' => 'tga', + 'image/x-xbitmap' => 'xbm', + 'image/x-xpixmap' => 'xpm', + 'image/x-xwindowdump' => 'xwd', + 'message/rfc822' => 'eml', + 'model/iges' => 'igs', + 'model/mesh' => 'msh', + 'model/vnd.collada+xml' => 'dae', + 'model/vnd.dwf' => 'dwf', + 'model/vnd.gdl' => 'gdl', + 'model/vnd.gtw' => 'gtw', + 'model/vnd.mts' => 'mts', + 'model/vnd.vtu' => 'vtu', + 'model/vrml' => 'wrl', + 'model/x3d+binary' => 'x3db', + 'model/x3d+vrml' => 'x3dv', + 'model/x3d+xml' => 'x3d', + 'text/cache-manifest' => 'appcache', + 'text/calendar' => 'ics', + 'text/css' => 'css', + 'text/csv' => 'csv', + 'text/html' => 'html', + 'text/n3' => 'n3', + 'text/plain' => 'txt', + 'text/prs.lines.tag' => 'dsc', + 'text/richtext' => 'rtx', + 'text/rtf' => 'rtf', + 'text/sgml' => 'sgml', + 'text/tab-separated-values' => 'tsv', + 'text/troff' => 't', + 'text/turtle' => 'ttl', + 'text/uri-list' => 'uri', + 'text/vcard' => 'vcard', + 'text/vnd.curl' => 'curl', + 'text/vnd.curl.dcurl' => 'dcurl', + 'text/vnd.curl.scurl' => 'scurl', + 'text/vnd.curl.mcurl' => 'mcurl', + 'text/vnd.dvb.subtitle' => 'sub', + 'text/vnd.fly' => 'fly', + 'text/vnd.fmi.flexstor' => 'flx', + 'text/vnd.graphviz' => 'gv', + 'text/vnd.in3d.3dml' => '3dml', + 'text/vnd.in3d.spot' => 'spot', + 'text/vnd.sun.j2me.app-descriptor' => 'jad', + 'text/vnd.wap.wml' => 'wml', + 'text/vnd.wap.wmlscript' => 'wmls', + 'text/x-asm' => 's', + 'text/x-c' => 'c', + 'text/x-fortran' => 'f', + 'text/x-pascal' => 'p', + 'text/x-java-source' => 'java', + 'text/x-opml' => 'opml', + 'text/x-nfo' => 'nfo', + 'text/x-setext' => 'etx', + 'text/x-sfv' => 'sfv', + 'text/x-uuencode' => 'uu', + 'text/x-vcalendar' => 'vcs', + 'text/x-vcard' => 'vcf', + 'video/3gpp' => '3gp', + 'video/3gpp2' => '3g2', + 'video/h261' => 'h261', + 'video/h263' => 'h263', + 'video/h264' => 'h264', + 'video/jpeg' => 'jpgv', + 'video/jpm' => 'jpm', + 'video/mj2' => 'mj2', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'video/ogg' => 'ogv', + 'video/quicktime' => 'qt', + 'video/vnd.dece.hd' => 'uvh', + 'video/vnd.dece.mobile' => 'uvm', + 'video/vnd.dece.pd' => 'uvp', + 'video/vnd.dece.sd' => 'uvs', + 'video/vnd.dece.video' => 'uvv', + 'video/vnd.dvb.file' => 'dvb', + 'video/vnd.fvt' => 'fvt', + 'video/vnd.mpegurl' => 'mxu', + 'video/vnd.ms-playready.media.pyv' => 'pyv', + 'video/vnd.uvvu.mp4' => 'uvu', + 'video/vnd.vivo' => 'viv', + 'video/webm' => 'webm', + 'video/x-f4v' => 'f4v', + 'video/x-fli' => 'fli', + 'video/x-flv' => 'flv', + 'video/x-m4v' => 'm4v', + 'video/x-matroska' => 'mkv', + 'video/x-mng' => 'mng', + 'video/x-ms-asf' => 'asf', + 'video/x-ms-vob' => 'vob', + 'video/x-ms-wm' => 'wm', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-wmx' => 'wmx', + 'video/x-ms-wvx' => 'wvx', + 'video/x-msvideo' => 'avi', + 'video/x-sgi-movie' => 'movie', + 'video/x-smv' => 'smv', + 'x-conference/x-cooltalk' => 'ice', + ); + + /** + * {@inheritdoc} + */ + public function guess($mimeType) + { + return isset($this->defaultExtensions[$mimeType]) ? $this->defaultExtensions[$mimeType] : null; + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php new file mode 100644 index 0000000..dae47a7 --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesser.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; + +/** + * A singleton mime type guesser. + * + * By default, all mime type guessers provided by the framework are installed + * (if available on the current OS/PHP setup). + * + * You can register custom guessers by calling the register() method on the + * singleton instance. Custom guessers are always called before any default ones. + * + * $guesser = MimeTypeGuesser::getInstance(); + * $guesser->register(new MyCustomMimeTypeGuesser()); + * + * If you want to change the order of the default guessers, just re-register your + * preferred one as a custom one. The last registered guesser is preferred over + * previously registered ones. + * + * Re-registering a built-in guesser also allows you to configure it: + * + * $guesser = MimeTypeGuesser::getInstance(); + * $guesser->register(new FileinfoMimeTypeGuesser('/path/to/magic/file')); + * + * @author Bernhard Schussek + */ +class MimeTypeGuesser implements MimeTypeGuesserInterface +{ + /** + * The singleton instance. + * + * @var MimeTypeGuesser + */ + private static $instance = null; + + /** + * All registered MimeTypeGuesserInterface instances. + * + * @var array + */ + protected $guessers = array(); + + /** + * Returns the singleton instance. + * + * @return self + */ + public static function getInstance() + { + if (null === self::$instance) { + self::$instance = new self(); + } + + return self::$instance; + } + + /** + * Resets the singleton instance. + */ + public static function reset() + { + self::$instance = null; + } + + /** + * Registers all natively provided mime type guessers. + */ + private function __construct() + { + $this->register(new FileBinaryMimeTypeGuesser()); + $this->register(new FileinfoMimeTypeGuesser()); + } + + /** + * Registers a new mime type guesser. + * + * When guessing, this guesser is preferred over previously registered ones. + */ + public function register(MimeTypeGuesserInterface $guesser) + { + array_unshift($this->guessers, $guesser); + } + + /** + * Tries to guess the mime type of the given file. + * + * The file is passed to each registered mime type guesser in reverse order + * of their registration (last registered is queried first). Once a guesser + * returns a value that is not NULL, this method terminates and returns the + * value. + * + * @param string $path The path to the file + * + * @return string The mime type or NULL, if none could be guessed + * + * @throws \LogicException + * @throws FileNotFoundException + * @throws AccessDeniedException + */ + public function guess($path) + { + if (!is_file($path)) { + throw new FileNotFoundException($path); + } + + if (!is_readable($path)) { + throw new AccessDeniedException($path); + } + + foreach ($this->guessers as $guesser) { + if (null !== $mimeType = $guesser->guess($path)) { + return $mimeType; + } + } + + if (2 === \count($this->guessers) && !FileBinaryMimeTypeGuesser::isSupported() && !FileinfoMimeTypeGuesser::isSupported()) { + throw new \LogicException('Unable to guess the mime type as no guessers are available (Did you enable the php_fileinfo extension?)'); + } + } +} diff --git a/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php new file mode 100644 index 0000000..5ac1acb --- /dev/null +++ b/vendor/symfony/http-foundation/File/MimeType/MimeTypeGuesserInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File\MimeType; + +use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; + +/** + * Guesses the mime type of a file. + * + * @author Bernhard Schussek + */ +interface MimeTypeGuesserInterface +{ + /** + * Guesses the mime type of the file with the given path. + * + * @param string $path The path to the file + * + * @return string The mime type or NULL, if none could be guessed + * + * @throws FileNotFoundException If the file does not exist + * @throws AccessDeniedException If the file could not be read + */ + public function guess($path); +} diff --git a/vendor/symfony/http-foundation/File/UploadedFile.php b/vendor/symfony/http-foundation/File/UploadedFile.php new file mode 100644 index 0000000..de6ce75 --- /dev/null +++ b/vendor/symfony/http-foundation/File/UploadedFile.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\File; + +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; +use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser; + +/** + * A file uploaded through a form. + * + * @author Bernhard Schussek + * @author Florian Eckerstorfer + * @author Fabien Potencier + */ +class UploadedFile extends File +{ + private $test = false; + private $originalName; + private $mimeType; + private $size; + private $error; + + /** + * Accepts the information of the uploaded file as provided by the PHP global $_FILES. + * + * The file object is only created when the uploaded file is valid (i.e. when the + * isValid() method returns true). Otherwise the only methods that could be called + * on an UploadedFile instance are: + * + * * getClientOriginalName, + * * getClientMimeType, + * * isValid, + * * getError. + * + * Calling any other method on an non-valid instance will cause an unpredictable result. + * + * @param string $path The full temporary path to the file + * @param string $originalName The original file name of the uploaded file + * @param string|null $mimeType The type of the file as provided by PHP; null defaults to application/octet-stream + * @param int|null $size The file size provided by the uploader + * @param int|null $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants); null defaults to UPLOAD_ERR_OK + * @param bool $test Whether the test mode is active + * Local files are used in test mode hence the code should not enforce HTTP uploads + * + * @throws FileException If file_uploads is disabled + * @throws FileNotFoundException If the file does not exist + */ + public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false) + { + $this->originalName = $this->getName($originalName); + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->size = $size; + $this->error = $error ?: UPLOAD_ERR_OK; + $this->test = (bool) $test; + + parent::__construct($path, UPLOAD_ERR_OK === $this->error); + } + + /** + * Returns the original file name. + * + * It is extracted from the request from which the file has been uploaded. + * Then it should not be considered as a safe value. + * + * @return string|null The original name + */ + public function getClientOriginalName() + { + return $this->originalName; + } + + /** + * Returns the original file extension. + * + * It is extracted from the original file name that was uploaded. + * Then it should not be considered as a safe value. + * + * @return string The extension + */ + public function getClientOriginalExtension() + { + return pathinfo($this->originalName, PATHINFO_EXTENSION); + } + + /** + * Returns the file mime type. + * + * The client mime type is extracted from the request from which the file + * was uploaded, so it should not be considered as a safe value. + * + * For a trusted mime type, use getMimeType() instead (which guesses the mime + * type based on the file content). + * + * @return string|null The mime type + * + * @see getMimeType() + */ + public function getClientMimeType() + { + return $this->mimeType; + } + + /** + * Returns the extension based on the client mime type. + * + * If the mime type is unknown, returns null. + * + * This method uses the mime type as guessed by getClientMimeType() + * to guess the file extension. As such, the extension returned + * by this method cannot be trusted. + * + * For a trusted extension, use guessExtension() instead (which guesses + * the extension based on the guessed mime type for the file). + * + * @return string|null The guessed extension or null if it cannot be guessed + * + * @see guessExtension() + * @see getClientMimeType() + */ + public function guessClientExtension() + { + $type = $this->getClientMimeType(); + $guesser = ExtensionGuesser::getInstance(); + + return $guesser->guess($type); + } + + /** + * Returns the file size. + * + * It is extracted from the request from which the file has been uploaded. + * Then it should not be considered as a safe value. + * + * @return int|null The file size + */ + public function getClientSize() + { + return $this->size; + } + + /** + * Returns the upload error. + * + * If the upload was successful, the constant UPLOAD_ERR_OK is returned. + * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. + * + * @return int The upload error + */ + public function getError() + { + return $this->error; + } + + /** + * Returns whether the file was uploaded successfully. + * + * @return bool True if the file has been uploaded with HTTP and no error occurred + */ + public function isValid() + { + $isOk = UPLOAD_ERR_OK === $this->error; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * Moves the file to a new location. + * + * @param string $directory The destination folder + * @param string $name The new file name + * + * @return File A File object representing the new file + * + * @throws FileException if, for any reason, the file could not have been moved + */ + public function move($directory, $name = null) + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $moved = move_uploaded_file($this->getPathname(), $target); + restore_error_handler(); + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod($target, 0666 & ~umask()); + + return $target; + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * Returns the maximum size of an uploaded file as configured in php.ini. + * + * @return int The maximum size of an uploaded file in bytes + */ + public static function getMaxFilesize() + { + $iniMax = strtolower(ini_get('upload_max_filesize')); + + if ('' === $iniMax) { + return PHP_INT_MAX; + } + + $max = ltrim($iniMax, '+'); + if (0 === strpos($max, '0x')) { + $max = \intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($iniMax, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } + + /** + * Returns an informative upload error message. + * + * @return string The error message regarding the specified error code + */ + public function getErrorMessage() + { + static $errors = array( + UPLOAD_ERR_INI_SIZE => 'The file "%s" exceeds your upload_max_filesize ini directive (limit is %d KiB).', + UPLOAD_ERR_FORM_SIZE => 'The file "%s" exceeds the upload limit defined in your form.', + UPLOAD_ERR_PARTIAL => 'The file "%s" was only partially uploaded.', + UPLOAD_ERR_NO_FILE => 'No file was uploaded.', + UPLOAD_ERR_CANT_WRITE => 'The file "%s" could not be written on disk.', + UPLOAD_ERR_NO_TMP_DIR => 'File could not be uploaded: missing temporary directory.', + UPLOAD_ERR_EXTENSION => 'File upload was stopped by a PHP extension.', + ); + + $errorCode = $this->error; + $maxFilesize = UPLOAD_ERR_INI_SIZE === $errorCode ? self::getMaxFilesize() / 1024 : 0; + $message = isset($errors[$errorCode]) ? $errors[$errorCode] : 'The file "%s" was not uploaded due to an unknown error.'; + + return sprintf($message, $this->getClientOriginalName(), $maxFilesize); + } +} diff --git a/vendor/symfony/http-foundation/FileBag.php b/vendor/symfony/http-foundation/FileBag.php new file mode 100644 index 0000000..c135ad6 --- /dev/null +++ b/vendor/symfony/http-foundation/FileBag.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\File\UploadedFile; + +/** + * FileBag is a container for uploaded files. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + */ +class FileBag extends ParameterBag +{ + private static $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type'); + + /** + * @param array $parameters An array of HTTP files + */ + public function __construct(array $parameters = array()) + { + $this->replace($parameters); + } + + /** + * {@inheritdoc} + */ + public function replace(array $files = array()) + { + $this->parameters = array(); + $this->add($files); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + if (!\is_array($value) && !$value instanceof UploadedFile) { + throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); + } + + parent::set($key, $this->convertFileInformation($value)); + } + + /** + * {@inheritdoc} + */ + public function add(array $files = array()) + { + foreach ($files as $key => $file) { + $this->set($key, $file); + } + } + + /** + * Converts uploaded files to UploadedFile instances. + * + * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information + * + * @return UploadedFile[]|UploadedFile|null A (multi-dimensional) array of UploadedFile instances + */ + protected function convertFileInformation($file) + { + if ($file instanceof UploadedFile) { + return $file; + } + + $file = $this->fixPhpFilesArray($file); + if (\is_array($file)) { + $keys = array_keys($file); + sort($keys); + + if ($keys == self::$fileKeys) { + if (UPLOAD_ERR_NO_FILE == $file['error']) { + $file = null; + } else { + $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']); + } + } else { + $file = array_map(array($this, 'convertFileInformation'), $file); + if (array_keys($keys) === $keys) { + $file = array_filter($file); + } + } + } + + return $file; + } + + /** + * Fixes a malformed PHP $_FILES array. + * + * PHP has a bug that the format of the $_FILES array differs, depending on + * whether the uploaded file fields had normal field names or array-like + * field names ("normal" vs. "parent[child]"). + * + * This method fixes the array to look like the "normal" $_FILES array. + * + * It's safe to pass an already converted array, in which case this method + * just returns the original array unmodified. + * + * @return array + */ + protected function fixPhpFilesArray($data) + { + if (!\is_array($data)) { + return $data; + } + + $keys = array_keys($data); + sort($keys); + + if (self::$fileKeys != $keys || !isset($data['name']) || !\is_array($data['name'])) { + return $data; + } + + $files = $data; + foreach (self::$fileKeys as $k) { + unset($files[$k]); + } + + foreach ($data['name'] as $key => $name) { + $files[$key] = $this->fixPhpFilesArray(array( + 'error' => $data['error'][$key], + 'name' => $name, + 'type' => $data['type'][$key], + 'tmp_name' => $data['tmp_name'][$key], + 'size' => $data['size'][$key], + )); + } + + return $files; + } +} diff --git a/vendor/symfony/http-foundation/HeaderBag.php b/vendor/symfony/http-foundation/HeaderBag.php new file mode 100644 index 0000000..0ef7428 --- /dev/null +++ b/vendor/symfony/http-foundation/HeaderBag.php @@ -0,0 +1,322 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * HeaderBag is a container for HTTP headers. + * + * @author Fabien Potencier + */ +class HeaderBag implements \IteratorAggregate, \Countable +{ + protected $headers = array(); + protected $cacheControl = array(); + + /** + * @param array $headers An array of HTTP headers + */ + public function __construct(array $headers = array()) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns the headers as a string. + * + * @return string The headers + */ + public function __toString() + { + if (!$this->headers) { + return ''; + } + + $max = max(array_map('strlen', array_keys($this->headers))) + 1; + $content = ''; + ksort($this->headers); + foreach ($this->headers as $name => $values) { + $name = implode('-', array_map('ucfirst', explode('-', $name))); + foreach ($values as $value) { + $content .= sprintf("%-{$max}s %s\r\n", $name.':', $value); + } + } + + return $content; + } + + /** + * Returns the headers. + * + * @return array An array of headers + */ + public function all() + { + return $this->headers; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->headers); + } + + /** + * Replaces the current HTTP headers by a new set. + * + * @param array $headers An array of HTTP headers + */ + public function replace(array $headers = array()) + { + $this->headers = array(); + $this->add($headers); + } + + /** + * Adds new headers the current HTTP headers set. + * + * @param array $headers An array of HTTP headers + */ + public function add(array $headers) + { + foreach ($headers as $key => $values) { + $this->set($key, $values); + } + } + + /** + * Returns a header value by name. + * + * @param string $key The header name + * @param string|string[]|null $default The default value + * @param bool $first Whether to return the first value or all header values + * + * @return string|string[]|null The first header value or default value if $first is true, an array of values otherwise + */ + public function get($key, $default = null, $first = true) + { + $key = str_replace('_', '-', strtolower($key)); + + if (!array_key_exists($key, $this->headers)) { + if (null === $default) { + return $first ? null : array(); + } + + return $first ? $default : array($default); + } + + if ($first) { + return \count($this->headers[$key]) ? $this->headers[$key][0] : $default; + } + + return $this->headers[$key]; + } + + /** + * Sets a header by name. + * + * @param string $key The key + * @param string|string[] $values The value or an array of values + * @param bool $replace Whether to replace the actual value or not (true by default) + */ + public function set($key, $values, $replace = true) + { + $key = str_replace('_', '-', strtolower($key)); + + $values = array_values((array) $values); + + if (true === $replace || !isset($this->headers[$key])) { + $this->headers[$key] = $values; + } else { + $this->headers[$key] = array_merge($this->headers[$key], $values); + } + + if ('cache-control' === $key) { + $this->cacheControl = $this->parseCacheControl(implode(', ', $this->headers[$key])); + } + } + + /** + * Returns true if the HTTP header is defined. + * + * @param string $key The HTTP header + * + * @return bool true if the parameter exists, false otherwise + */ + public function has($key) + { + return array_key_exists(str_replace('_', '-', strtolower($key)), $this->headers); + } + + /** + * Returns true if the given HTTP header contains the given value. + * + * @param string $key The HTTP header name + * @param string $value The HTTP value + * + * @return bool true if the value is contained in the header, false otherwise + */ + public function contains($key, $value) + { + return \in_array($value, $this->get($key, null, false)); + } + + /** + * Removes a header. + * + * @param string $key The HTTP header name + */ + public function remove($key) + { + $key = str_replace('_', '-', strtolower($key)); + + unset($this->headers[$key]); + + if ('cache-control' === $key) { + $this->cacheControl = array(); + } + } + + /** + * Returns the HTTP header value converted to a date. + * + * @param string $key The parameter key + * @param \DateTime $default The default value + * + * @return \DateTime|null The parsed DateTime or the default value if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getDate($key, \DateTime $default = null) + { + if (null === $value = $this->get($key)) { + return $default; + } + + if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) { + throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value)); + } + + return $date; + } + + /** + * Adds a custom Cache-Control directive. + * + * @param string $key The Cache-Control directive name + * @param mixed $value The Cache-Control directive value + */ + public function addCacheControlDirective($key, $value = true) + { + $this->cacheControl[$key] = $value; + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns true if the Cache-Control directive is defined. + * + * @param string $key The Cache-Control directive + * + * @return bool true if the directive exists, false otherwise + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl); + } + + /** + * Returns a Cache-Control directive value by name. + * + * @param string $key The directive name + * + * @return mixed|null The directive value if defined, null otherwise + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; + } + + /** + * Removes a Cache-Control directive. + * + * @param string $key The Cache-Control directive + */ + public function removeCacheControlDirective($key) + { + unset($this->cacheControl[$key]); + + $this->set('Cache-Control', $this->getCacheControlHeader()); + } + + /** + * Returns an iterator for headers. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->headers); + } + + /** + * Returns the number of headers. + * + * @return int The number of headers + */ + public function count() + { + return \count($this->headers); + } + + protected function getCacheControlHeader() + { + $parts = array(); + ksort($this->cacheControl); + foreach ($this->cacheControl as $key => $value) { + if (true === $value) { + $parts[] = $key; + } else { + if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { + $value = '"'.$value.'"'; + } + + $parts[] = "$key=$value"; + } + } + + return implode(', ', $parts); + } + + /** + * Parses a Cache-Control HTTP header. + * + * @param string $header The value of the Cache-Control HTTP header + * + * @return array An array representing the attribute values + */ + protected function parseCacheControl($header) + { + $cacheControl = array(); + preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $cacheControl[strtolower($match[1])] = isset($match[3]) ? $match[3] : (isset($match[2]) ? $match[2] : true); + } + + return $cacheControl; + } +} diff --git a/vendor/symfony/http-foundation/IpUtils.php b/vendor/symfony/http-foundation/IpUtils.php new file mode 100644 index 0000000..a1bfa90 --- /dev/null +++ b/vendor/symfony/http-foundation/IpUtils.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Http utility functions. + * + * @author Fabien Potencier + */ +class IpUtils +{ + private static $checkedIps = array(); + + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Checks if an IPv4 or IPv6 address is contained in the list of given IPs or subnets. + * + * @param string $requestIp IP to check + * @param string|array $ips List of IPs or subnets (can be a string if only a single one) + * + * @return bool Whether the IP is valid + */ + public static function checkIp($requestIp, $ips) + { + if (!\is_array($ips)) { + $ips = array($ips); + } + + $method = substr_count($requestIp, ':') > 1 ? 'checkIp6' : 'checkIp4'; + + foreach ($ips as $ip) { + if (self::$method($requestIp, $ip)) { + return true; + } + } + + return false; + } + + /** + * Compares two IPv4 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @param string $requestIp IPv4 address to check + * @param string $ip IPv4 address or subnet in CIDR notation + * + * @return bool Whether the request IP matches the IP, or whether the request IP is within the CIDR subnet + */ + public static function checkIp4($requestIp, $ip) + { + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!filter_var($requestIp, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return self::$checkedIps[$cacheKey] = false; + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ('0' === $netmask) { + return self::$checkedIps[$cacheKey] = filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + + if ($netmask < 0 || $netmask > 32) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 32; + } + + if (false === ip2long($address)) { + return self::$checkedIps[$cacheKey] = false; + } + + return self::$checkedIps[$cacheKey] = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); + } + + /** + * Compares two IPv6 addresses. + * In case a subnet is given, it checks if it contains the request IP. + * + * @author David Soria Parra + * + * @see https://github.com/dsp/v6tools + * + * @param string $requestIp IPv6 address to check + * @param string $ip IPv6 address or subnet in CIDR notation + * + * @return bool Whether the IP is valid + * + * @throws \RuntimeException When IPV6 support is not enabled + */ + public static function checkIp6($requestIp, $ip) + { + $cacheKey = $requestIp.'-'.$ip; + if (isset(self::$checkedIps[$cacheKey])) { + return self::$checkedIps[$cacheKey]; + } + + if (!((\extension_loaded('sockets') && \defined('AF_INET6')) || @inet_pton('::1'))) { + throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); + } + + if (false !== strpos($ip, '/')) { + list($address, $netmask) = explode('/', $ip, 2); + + if ('0' === $netmask) { + return (bool) unpack('n*', @inet_pton($address)); + } + + if ($netmask < 1 || $netmask > 128) { + return self::$checkedIps[$cacheKey] = false; + } + } else { + $address = $ip; + $netmask = 128; + } + + $bytesAddr = unpack('n*', @inet_pton($address)); + $bytesTest = unpack('n*', @inet_pton($requestIp)); + + if (!$bytesAddr || !$bytesTest) { + return self::$checkedIps[$cacheKey] = false; + } + + for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) { + $left = $netmask - 16 * ($i - 1); + $left = ($left <= 16) ? $left : 16; + $mask = ~(0xffff >> $left) & 0xffff; + if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) { + return self::$checkedIps[$cacheKey] = false; + } + } + + return self::$checkedIps[$cacheKey] = true; + } +} diff --git a/vendor/symfony/http-foundation/JsonResponse.php b/vendor/symfony/http-foundation/JsonResponse.php new file mode 100644 index 0000000..5a9e755 --- /dev/null +++ b/vendor/symfony/http-foundation/JsonResponse.php @@ -0,0 +1,218 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response in JSON format. + * + * Note that this class does not force the returned JSON content to be an + * object. It is however recommended that you do return an object as it + * protects yourself against XSSI and JSON-JavaScript Hijacking. + * + * @see https://www.owasp.org/index.php/OWASP_AJAX_Security_Guidelines#Always_return_JSON_with_an_Object_on_the_outside + * + * @author Igor Wiedler + */ +class JsonResponse extends Response +{ + protected $data; + protected $callback; + + // Encode <, >, ', &, and " characters in the JSON, making it also safe to be embedded into HTML. + // 15 === JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT + protected $encodingOptions = 15; + + /** + * @param mixed $data The response data + * @param int $status The response status code + * @param array $headers An array of response headers + */ + public function __construct($data = null, $status = 200, $headers = array()) + { + parent::__construct('', $status, $headers); + + if (null === $data) { + $data = new \ArrayObject(); + } + + $this->setData($data); + } + + /** + * Factory method for chainability. + * + * Example: + * + * return JsonResponse::create($data, 200) + * ->setSharedMaxAge(300); + * + * @param mixed $data The json response data + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($data = null, $status = 200, $headers = array()) + { + return new static($data, $status, $headers); + } + + /** + * Sets the JSONP callback. + * + * @param string|null $callback The JSONP callback or null to use none + * + * @return $this + * + * @throws \InvalidArgumentException When the callback name is not valid + */ + public function setCallback($callback = null) + { + if (null !== $callback) { + // partially token from http://www.geekality.net/2011/08/03/valid-javascript-identifier/ + // partially token from https://github.com/willdurand/JsonpCallbackValidator + // JsonpCallbackValidator is released under the MIT License. See https://github.com/willdurand/JsonpCallbackValidator/blob/v1.1.0/LICENSE for details. + // (c) William Durand + $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*(?:\[(?:"(?:\\\.|[^"\\\])*"|\'(?:\\\.|[^\'\\\])*\'|\d+)\])*?$/u'; + $reserved = array( + 'break', 'do', 'instanceof', 'typeof', 'case', 'else', 'new', 'var', 'catch', 'finally', 'return', 'void', 'continue', 'for', 'switch', 'while', + 'debugger', 'function', 'this', 'with', 'default', 'if', 'throw', 'delete', 'in', 'try', 'class', 'enum', 'extends', 'super', 'const', 'export', + 'import', 'implements', 'let', 'private', 'public', 'yield', 'interface', 'package', 'protected', 'static', 'null', 'true', 'false', + ); + $parts = explode('.', $callback); + foreach ($parts as $part) { + if (!preg_match($pattern, $part) || \in_array($part, $reserved, true)) { + throw new \InvalidArgumentException('The callback name is not valid.'); + } + } + } + + $this->callback = $callback; + + return $this->update(); + } + + /** + * Sets the data to be sent as JSON. + * + * @param mixed $data + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setData($data = array()) + { + if (\defined('HHVM_VERSION')) { + // HHVM does not trigger any warnings and let exceptions + // thrown from a JsonSerializable object pass through. + // If only PHP did the same... + $data = json_encode($data, $this->encodingOptions); + } else { + try { + if (!interface_exists('JsonSerializable', false)) { + // PHP 5.3 triggers annoying warnings for some + // types that can't be serialized as JSON (INF, resources, etc.) + // but doesn't provide the JsonSerializable interface. + set_error_handler(function () { return false; }); + $data = @json_encode($data, $this->encodingOptions); + restore_error_handler(); + } elseif (\PHP_VERSION_ID < 50500) { + // PHP 5.4 and up wrap exceptions thrown by JsonSerializable + // objects in a new exception that needs to be removed. + // Fortunately, PHP 5.5 and up do not trigger any warning anymore. + // Clear json_last_error() + json_encode(null); + $errorHandler = set_error_handler('var_dump'); + restore_error_handler(); + set_error_handler(function () use ($errorHandler) { + if (JSON_ERROR_NONE === json_last_error()) { + return $errorHandler && false !== \call_user_func_array($errorHandler, \func_get_args()); + } + }); + $data = json_encode($data, $this->encodingOptions); + restore_error_handler(); + } else { + $data = json_encode($data, $this->encodingOptions); + } + } catch (\Error $e) { + if (\PHP_VERSION_ID < 50500 || !interface_exists('JsonSerializable', false)) { + restore_error_handler(); + } + throw $e; + } catch (\Exception $e) { + if (\PHP_VERSION_ID < 50500 || !interface_exists('JsonSerializable', false)) { + restore_error_handler(); + } + if (interface_exists('JsonSerializable', false) && 'Exception' === \get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) { + throw $e->getPrevious() ?: $e; + } + throw $e; + } + } + + if (JSON_ERROR_NONE !== json_last_error()) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $this->data = $data; + + return $this->update(); + } + + /** + * Returns options used while encoding data to JSON. + * + * @return int + */ + public function getEncodingOptions() + { + return $this->encodingOptions; + } + + /** + * Sets options used while encoding data to JSON. + * + * @param int $encodingOptions + * + * @return $this + */ + public function setEncodingOptions($encodingOptions) + { + $this->encodingOptions = (int) $encodingOptions; + + return $this->setData(json_decode($this->data)); + } + + /** + * Updates the content and headers according to the JSON data and callback. + * + * @return $this + */ + protected function update() + { + if (null !== $this->callback) { + // Not using application/javascript for compatibility reasons with older browsers. + $this->headers->set('Content-Type', 'text/javascript'); + + return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data)); + } + + // Only set the header when there is none or when it equals 'text/javascript' (from a previous update with callback) + // in order to not overwrite a custom definition. + if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/json'); + } + + return $this->setContent($this->data); + } +} diff --git a/vendor/symfony/http-foundation/LICENSE b/vendor/symfony/http-foundation/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/http-foundation/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/http-foundation/ParameterBag.php b/vendor/symfony/http-foundation/ParameterBag.php new file mode 100644 index 0000000..6141b1b --- /dev/null +++ b/vendor/symfony/http-foundation/ParameterBag.php @@ -0,0 +1,308 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ParameterBag is a container for key/value pairs. + * + * @author Fabien Potencier + */ +class ParameterBag implements \IteratorAggregate, \Countable +{ + /** + * Parameter storage. + */ + protected $parameters; + + /** + * @param array $parameters An array of parameters + */ + public function __construct(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Returns the parameters. + * + * @return array An array of parameters + */ + public function all() + { + return $this->parameters; + } + + /** + * Returns the parameter keys. + * + * @return array An array of parameter keys + */ + public function keys() + { + return array_keys($this->parameters); + } + + /** + * Replaces the current parameters by a new set. + * + * @param array $parameters An array of parameters + */ + public function replace(array $parameters = array()) + { + $this->parameters = $parameters; + } + + /** + * Adds parameters. + * + * @param array $parameters An array of parameters + */ + public function add(array $parameters = array()) + { + $this->parameters = array_replace($this->parameters, $parameters); + } + + /** + * Returns a parameter by name. + * + * Note: Finding deep items is deprecated since version 2.8, to be removed in 3.0. + * + * @param string $key The key + * @param mixed $default The default value if the parameter key does not exist + * @param bool $deep If true, a path like foo[bar] will find deeper items + * + * @return mixed + * + * @throws \InvalidArgumentException + */ + public function get($key, $default = null, $deep = false) + { + if ($deep) { + @trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since Symfony 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED); + } + + if (!$deep || false === $pos = strpos($key, '[')) { + return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : $default; + } + + $root = substr($key, 0, $pos); + if (!array_key_exists($root, $this->parameters)) { + return $default; + } + + $value = $this->parameters[$root]; + $currentKey = null; + for ($i = $pos, $c = \strlen($key); $i < $c; ++$i) { + $char = $key[$i]; + + if ('[' === $char) { + if (null !== $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i)); + } + + $currentKey = ''; + } elseif (']' === $char) { + if (null === $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i)); + } + + if (!\is_array($value) || !array_key_exists($currentKey, $value)) { + return $default; + } + + $value = $value[$currentKey]; + $currentKey = null; + } else { + if (null === $currentKey) { + throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i)); + } + + $currentKey .= $char; + } + } + + if (null !== $currentKey) { + throw new \InvalidArgumentException('Malformed path. Path must end with "]".'); + } + + return $value; + } + + /** + * Sets a parameter by name. + * + * @param string $key The key + * @param mixed $value The value + */ + public function set($key, $value) + { + $this->parameters[$key] = $value; + } + + /** + * Returns true if the parameter is defined. + * + * @param string $key The key + * + * @return bool true if the parameter exists, false otherwise + */ + public function has($key) + { + return array_key_exists($key, $this->parameters); + } + + /** + * Removes a parameter. + * + * @param string $key The key + */ + public function remove($key) + { + unset($this->parameters[$key]); + } + + /** + * Returns the alphabetic characters of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * @param bool $deep If true, a path like foo[bar] will find deeper items + * + * @return string The filtered value + */ + public function getAlpha($key, $default = '', $deep = false) + { + return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep)); + } + + /** + * Returns the alphabetic characters and digits of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * @param bool $deep If true, a path like foo[bar] will find deeper items + * + * @return string The filtered value + */ + public function getAlnum($key, $default = '', $deep = false) + { + return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep)); + } + + /** + * Returns the digits of the parameter value. + * + * @param string $key The parameter key + * @param string $default The default value if the parameter key does not exist + * @param bool $deep If true, a path like foo[bar] will find deeper items + * + * @return string The filtered value + */ + public function getDigits($key, $default = '', $deep = false) + { + // we need to remove - and + because they're allowed in the filter + return str_replace(array('-', '+'), '', $this->filter($key, $default, FILTER_SANITIZE_NUMBER_INT, array(), $deep)); + } + + /** + * Returns the parameter value converted to integer. + * + * @param string $key The parameter key + * @param int $default The default value if the parameter key does not exist + * @param bool $deep If true, a path like foo[bar] will find deeper items + * + * @return int The filtered value + */ + public function getInt($key, $default = 0, $deep = false) + { + return (int) $this->get($key, $default, $deep); + } + + /** + * Returns the parameter value converted to boolean. + * + * @param string $key The parameter key + * @param bool $default The default value if the parameter key does not exist + * @param bool $deep If true, a path like foo[bar] will find deeper items + * + * @return bool The filtered value + */ + public function getBoolean($key, $default = false, $deep = false) + { + return $this->filter($key, $default, FILTER_VALIDATE_BOOLEAN, array(), $deep); + } + + /** + * Filter key. + * + * @param string $key Key + * @param mixed $default Default = null + * @param int $filter FILTER_* constant + * @param mixed $options Filter options + * @param bool $deep Default = false + * + * @see http://php.net/manual/en/function.filter-var.php + * + * @return mixed + */ + public function filter($key, $default = null, $filter = FILTER_DEFAULT, $options = array(), $deep = false) + { + static $filters = null; + + if (null === $filters) { + foreach (filter_list() as $tmp) { + $filters[filter_id($tmp)] = 1; + } + } + if (\is_bool($filter) || !isset($filters[$filter]) || \is_array($deep)) { + @trigger_error('Passing the $deep boolean as 3rd argument to the '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0. Remove it altogether as the $deep argument will be removed in 3.0.', E_USER_DEPRECATED); + $tmp = $deep; + $deep = $filter; + $filter = $options; + $options = $tmp; + } + + $value = $this->get($key, $default, $deep); + + // Always turn $options into an array - this allows filter_var option shortcuts. + if (!\is_array($options) && $options) { + $options = array('flags' => $options); + } + + // Add a convenience check for arrays. + if (\is_array($value) && !isset($options['flags'])) { + $options['flags'] = FILTER_REQUIRE_ARRAY; + } + + return filter_var($value, $filter, $options); + } + + /** + * Returns an iterator for parameters. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->parameters); + } + + /** + * Returns the number of parameters. + * + * @return int The number of parameters + */ + public function count() + { + return \count($this->parameters); + } +} diff --git a/vendor/symfony/http-foundation/RedirectResponse.php b/vendor/symfony/http-foundation/RedirectResponse.php new file mode 100644 index 0000000..23eb04a --- /dev/null +++ b/vendor/symfony/http-foundation/RedirectResponse.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RedirectResponse represents an HTTP response doing a redirect. + * + * @author Fabien Potencier + */ +class RedirectResponse extends Response +{ + protected $targetUrl; + + /** + * Creates a redirect response so that it conforms to the rules defined for a redirect status code. + * + * @param string $url The URL to redirect to. The URL should be a full URL, with schema etc., + * but practically every browser redirects on paths only as well + * @param int $status The status code (302 by default) + * @param array $headers The headers (Location is always set to the given URL) + * + * @throws \InvalidArgumentException + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3 + */ + public function __construct($url, $status = 302, $headers = array()) + { + parent::__construct('', $status, $headers); + + $this->setTargetUrl($url); + + if (!$this->isRedirect()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); + } + } + + /** + * Factory method for chainability. + * + * @param string $url The url to redirect to + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($url = '', $status = 302, $headers = array()) + { + return new static($url, $status, $headers); + } + + /** + * Returns the target URL. + * + * @return string target URL + */ + public function getTargetUrl() + { + return $this->targetUrl; + } + + /** + * Sets the redirect target of this response. + * + * @param string $url The URL to redirect to + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setTargetUrl($url) + { + if (empty($url)) { + throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); + } + + $this->targetUrl = $url; + + $this->setContent( + sprintf(' + + + + + + Redirecting to %1$s + + + Redirecting to %1$s. + +', htmlspecialchars($url, ENT_QUOTES, 'UTF-8'))); + + $this->headers->set('Location', $url); + + return $this; + } +} diff --git a/vendor/symfony/http-foundation/Request.php b/vendor/symfony/http-foundation/Request.php new file mode 100644 index 0000000..e025993 --- /dev/null +++ b/vendor/symfony/http-foundation/Request.php @@ -0,0 +1,2019 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Request represents an HTTP request. + * + * The methods dealing with URL accept / return a raw path (% encoded): + * * getBasePath + * * getBaseUrl + * * getPathInfo + * * getRequestUri + * * getUri + * * getUriForPath + * + * @author Fabien Potencier + */ +class Request +{ + const HEADER_FORWARDED = 'forwarded'; + const HEADER_CLIENT_IP = 'client_ip'; + const HEADER_CLIENT_HOST = 'client_host'; + const HEADER_CLIENT_PROTO = 'client_proto'; + const HEADER_CLIENT_PORT = 'client_port'; + + const METHOD_HEAD = 'HEAD'; + const METHOD_GET = 'GET'; + const METHOD_POST = 'POST'; + const METHOD_PUT = 'PUT'; + const METHOD_PATCH = 'PATCH'; + const METHOD_DELETE = 'DELETE'; + const METHOD_PURGE = 'PURGE'; + const METHOD_OPTIONS = 'OPTIONS'; + const METHOD_TRACE = 'TRACE'; + const METHOD_CONNECT = 'CONNECT'; + + /** + * @var string[] + */ + protected static $trustedProxies = array(); + + /** + * @var string[] + */ + protected static $trustedHostPatterns = array(); + + /** + * @var string[] + */ + protected static $trustedHosts = array(); + + /** + * Names for headers that can be trusted when + * using trusted proxies. + * + * The FORWARDED header is the standard as of rfc7239. + * + * The other headers are non-standard, but widely used + * by popular reverse proxies (like Apache mod_proxy or Amazon EC2). + */ + protected static $trustedHeaders = array( + self::HEADER_FORWARDED => 'FORWARDED', + self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR', + self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST', + self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO', + self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT', + ); + + protected static $httpMethodParameterOverride = false; + + /** + * Custom parameters. + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $attributes; + + /** + * Request body parameters ($_POST). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $request; + + /** + * Query string parameters ($_GET). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $query; + + /** + * Server and execution environment parameters ($_SERVER). + * + * @var \Symfony\Component\HttpFoundation\ServerBag + */ + public $server; + + /** + * Uploaded files ($_FILES). + * + * @var \Symfony\Component\HttpFoundation\FileBag + */ + public $files; + + /** + * Cookies ($_COOKIE). + * + * @var \Symfony\Component\HttpFoundation\ParameterBag + */ + public $cookies; + + /** + * Headers (taken from the $_SERVER). + * + * @var \Symfony\Component\HttpFoundation\HeaderBag + */ + public $headers; + + /** + * @var string|resource|false|null + */ + protected $content; + + /** + * @var array + */ + protected $languages; + + /** + * @var array + */ + protected $charsets; + + /** + * @var array + */ + protected $encodings; + + /** + * @var array + */ + protected $acceptableContentTypes; + + /** + * @var string + */ + protected $pathInfo; + + /** + * @var string + */ + protected $requestUri; + + /** + * @var string + */ + protected $baseUrl; + + /** + * @var string + */ + protected $basePath; + + /** + * @var string + */ + protected $method; + + /** + * @var string + */ + protected $format; + + /** + * @var \Symfony\Component\HttpFoundation\Session\SessionInterface + */ + protected $session; + + /** + * @var string + */ + protected $locale; + + /** + * @var string + */ + protected $defaultLocale = 'en'; + + /** + * @var array + */ + protected static $formats; + + protected static $requestFactory; + + private $isForwardedValid = true; + + private static $forwardedParams = array( + self::HEADER_CLIENT_IP => 'for', + self::HEADER_CLIENT_HOST => 'host', + self::HEADER_CLIENT_PROTO => 'proto', + self::HEADER_CLIENT_PORT => 'host', + ); + + /** + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); + } + + /** + * Sets the parameters for this request. + * + * This method also re-initializes all properties. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * @param string|resource|null $content The raw body data + */ + public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + $this->request = new ParameterBag($request); + $this->query = new ParameterBag($query); + $this->attributes = new ParameterBag($attributes); + $this->cookies = new ParameterBag($cookies); + $this->files = new FileBag($files); + $this->server = new ServerBag($server); + $this->headers = new HeaderBag($this->server->getHeaders()); + + $this->content = $content; + $this->languages = null; + $this->charsets = null; + $this->encodings = null; + $this->acceptableContentTypes = null; + $this->pathInfo = null; + $this->requestUri = null; + $this->baseUrl = null; + $this->basePath = null; + $this->method = null; + $this->format = null; + } + + /** + * Creates a new request with values from PHP's super globals. + * + * @return static + */ + public static function createFromGlobals() + { + // With the php's bug #66606, the php's built-in web server + // stores the Content-Type and Content-Length header values in + // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields. + $server = $_SERVER; + if ('cli-server' === \PHP_SAPI) { + if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) { + $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH']; + } + if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) { + $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE']; + } + } + + $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server); + + if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') + && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH')) + ) { + parse_str($request->getContent(), $data); + $request->request = new ParameterBag($data); + } + + return $request; + } + + /** + * Creates a Request based on a given URI and configuration. + * + * The information contained in the URI always take precedence + * over the other information (server and parameters). + * + * @param string $uri The URI + * @param string $method The HTTP method + * @param array $parameters The query (GET) or request (POST) parameters + * @param array $cookies The request cookies ($_COOKIE) + * @param array $files The request files ($_FILES) + * @param array $server The server parameters ($_SERVER) + * @param string|resource|null $content The raw body data + * + * @return static + */ + public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null) + { + $server = array_replace(array( + 'SERVER_NAME' => 'localhost', + 'SERVER_PORT' => 80, + 'HTTP_HOST' => 'localhost', + 'HTTP_USER_AGENT' => 'Symfony/2.X', + 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', + 'REMOTE_ADDR' => '127.0.0.1', + 'SCRIPT_NAME' => '', + 'SCRIPT_FILENAME' => '', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'REQUEST_TIME' => time(), + ), $server); + + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + + $components = parse_url($uri); + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] .= ':'.$components['port']; + } + + if (isset($components['user'])) { + $server['PHP_AUTH_USER'] = $components['user']; + } + + if (isset($components['pass'])) { + $server['PHP_AUTH_PW'] = $components['pass']; + } + + if (!isset($components['path'])) { + $components['path'] = '/'; + } + + switch (strtoupper($method)) { + case 'POST': + case 'PUT': + case 'DELETE': + if (!isset($server['CONTENT_TYPE'])) { + $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; + } + // no break + case 'PATCH': + $request = $parameters; + $query = array(); + break; + default: + $request = array(); + $query = $parameters; + break; + } + + $queryString = ''; + if (isset($components['query'])) { + parse_str(html_entity_decode($components['query']), $qs); + + if ($query) { + $query = array_replace($qs, $query); + $queryString = http_build_query($query, '', '&'); + } else { + $query = $qs; + $queryString = $components['query']; + } + } elseif ($query) { + $queryString = http_build_query($query, '', '&'); + } + + $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : ''); + $server['QUERY_STRING'] = $queryString; + + return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content); + } + + /** + * Sets a callable able to create a Request instance. + * + * This is mainly useful when you need to override the Request class + * to keep BC with an existing system. It should not be used for any + * other purpose. + * + * @param callable|null $callable A PHP callable + */ + public static function setFactory($callable) + { + self::$requestFactory = $callable; + } + + /** + * Clones a request and overrides some of its parameters. + * + * @param array $query The GET parameters + * @param array $request The POST parameters + * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) + * @param array $cookies The COOKIE parameters + * @param array $files The FILES parameters + * @param array $server The SERVER parameters + * + * @return static + */ + public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) + { + $dup = clone $this; + if (null !== $query) { + $dup->query = new ParameterBag($query); + } + if (null !== $request) { + $dup->request = new ParameterBag($request); + } + if (null !== $attributes) { + $dup->attributes = new ParameterBag($attributes); + } + if (null !== $cookies) { + $dup->cookies = new ParameterBag($cookies); + } + if (null !== $files) { + $dup->files = new FileBag($files); + } + if (null !== $server) { + $dup->server = new ServerBag($server); + $dup->headers = new HeaderBag($dup->server->getHeaders()); + } + $dup->languages = null; + $dup->charsets = null; + $dup->encodings = null; + $dup->acceptableContentTypes = null; + $dup->pathInfo = null; + $dup->requestUri = null; + $dup->baseUrl = null; + $dup->basePath = null; + $dup->method = null; + $dup->format = null; + + if (!$dup->get('_format') && $this->get('_format')) { + $dup->attributes->set('_format', $this->get('_format')); + } + + if (!$dup->getRequestFormat(null)) { + $dup->setRequestFormat($this->getRequestFormat(null)); + } + + return $dup; + } + + /** + * Clones the current request. + * + * Note that the session is not cloned as duplicated requests + * are most of the time sub-requests of the main one. + */ + public function __clone() + { + $this->query = clone $this->query; + $this->request = clone $this->request; + $this->attributes = clone $this->attributes; + $this->cookies = clone $this->cookies; + $this->files = clone $this->files; + $this->server = clone $this->server; + $this->headers = clone $this->headers; + } + + /** + * Returns the request as a string. + * + * @return string The request + */ + public function __toString() + { + try { + $content = $this->getContent(); + } catch (\LogicException $e) { + return trigger_error($e, E_USER_ERROR); + } + + $cookieHeader = ''; + $cookies = array(); + + foreach ($this->cookies as $k => $v) { + $cookies[] = $k.'='.$v; + } + + if (!empty($cookies)) { + $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n"; + } + + return + sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". + $this->headers. + $cookieHeader."\r\n". + $content; + } + + /** + * Overrides the PHP global variables according to this request instance. + * + * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE. + * $_FILES is never overridden, see rfc1867 + */ + public function overrideGlobals() + { + $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&'))); + + $_GET = $this->query->all(); + $_POST = $this->request->all(); + $_SERVER = $this->server->all(); + $_COOKIE = $this->cookies->all(); + + foreach ($this->headers->all() as $key => $value) { + $key = strtoupper(str_replace('-', '_', $key)); + if (\in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { + $_SERVER[$key] = implode(', ', $value); + } else { + $_SERVER['HTTP_'.$key] = implode(', ', $value); + } + } + + $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE); + + $requestOrder = ini_get('request_order') ?: ini_get('variables_order'); + $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp'; + + $_REQUEST = array(); + foreach (str_split($requestOrder) as $order) { + $_REQUEST = array_merge($_REQUEST, $request[$order]); + } + } + + /** + * Sets a list of trusted proxies. + * + * You should only list the reverse proxies that you manage directly. + * + * @param array $proxies A list of trusted proxies + */ + public static function setTrustedProxies(array $proxies) + { + self::$trustedProxies = $proxies; + } + + /** + * Gets the list of trusted proxies. + * + * @return array An array of trusted proxies + */ + public static function getTrustedProxies() + { + return self::$trustedProxies; + } + + /** + * Sets a list of trusted host patterns. + * + * You should only list the hosts you manage using regexs. + * + * @param array $hostPatterns A list of trusted host patterns + */ + public static function setTrustedHosts(array $hostPatterns) + { + self::$trustedHostPatterns = array_map(function ($hostPattern) { + return sprintf('{%s}i', $hostPattern); + }, $hostPatterns); + // we need to reset trusted hosts on trusted host patterns change + self::$trustedHosts = array(); + } + + /** + * Gets the list of trusted host patterns. + * + * @return array An array of trusted host patterns + */ + public static function getTrustedHosts() + { + return self::$trustedHostPatterns; + } + + /** + * Sets the name for trusted headers. + * + * The following header keys are supported: + * + * * Request::HEADER_CLIENT_IP: defaults to X-Forwarded-For (see getClientIp()) + * * Request::HEADER_CLIENT_HOST: defaults to X-Forwarded-Host (see getHost()) + * * Request::HEADER_CLIENT_PORT: defaults to X-Forwarded-Port (see getPort()) + * * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure()) + * * Request::HEADER_FORWARDED: defaults to Forwarded (see RFC 7239) + * + * Setting an empty value allows to disable the trusted header for the given key. + * + * @param string $key The header key + * @param string $value The header name + * + * @throws \InvalidArgumentException + */ + public static function setTrustedHeaderName($key, $value) + { + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key)); + } + + self::$trustedHeaders[$key] = $value; + } + + /** + * Gets the trusted proxy header name. + * + * @param string $key The header key + * + * @return string The header name + * + * @throws \InvalidArgumentException + */ + public static function getTrustedHeaderName($key) + { + if (!array_key_exists($key, self::$trustedHeaders)) { + throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key)); + } + + return self::$trustedHeaders[$key]; + } + + /** + * Normalizes a query string. + * + * It builds a normalized query string, where keys/value pairs are alphabetized, + * have consistent escaping and unneeded delimiters are removed. + * + * @param string $qs Query string + * + * @return string A normalized query string for the Request + */ + public static function normalizeQueryString($qs) + { + if ('' == $qs) { + return ''; + } + + $parts = array(); + $order = array(); + + foreach (explode('&', $qs) as $param) { + if ('' === $param || '=' === $param[0]) { + // Ignore useless delimiters, e.g. "x=y&". + // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway. + // PHP also does not include them when building _GET. + continue; + } + + $keyValuePair = explode('=', $param, 2); + + // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded). + // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to + // RFC 3986 with rawurlencode. + $parts[] = isset($keyValuePair[1]) ? + rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) : + rawurlencode(urldecode($keyValuePair[0])); + $order[] = urldecode($keyValuePair[0]); + } + + array_multisort($order, SORT_ASC, $parts); + + return implode('&', $parts); + } + + /** + * Enables support for the _method request parameter to determine the intended HTTP method. + * + * Be warned that enabling this feature might lead to CSRF issues in your code. + * Check that you are using CSRF tokens when required. + * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered + * and used to send a "PUT" or "DELETE" request via the _method request parameter. + * If these methods are not protected against CSRF, this presents a possible vulnerability. + * + * The HTTP method can only be overridden when the real HTTP method is POST. + */ + public static function enableHttpMethodParameterOverride() + { + self::$httpMethodParameterOverride = true; + } + + /** + * Checks whether support for the _method request parameter is enabled. + * + * @return bool True when the _method request parameter is enabled, false otherwise + */ + public static function getHttpMethodParameterOverride() + { + return self::$httpMethodParameterOverride; + } + + /** + * Gets a "parameter" value. + * + * This method is mainly useful for libraries that want to provide some flexibility. + * + * Order of precedence: GET, PATH, POST + * + * Avoid using this method in controllers: + * + * * slow + * * prefer to get from a "named" source + * + * It is better to explicitly get request parameters from the appropriate + * public property instead (query, attributes, request). + * + * Note: Finding deep items is deprecated since version 2.8, to be removed in 3.0. + * + * @param string $key The key + * @param mixed $default The default value if the parameter key does not exist + * @param bool $deep Is parameter deep in multidimensional array + * + * @return mixed + */ + public function get($key, $default = null, $deep = false) + { + if ($deep) { + @trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since Symfony 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED); + } + + if ($this !== $result = $this->query->get($key, $this, $deep)) { + return $result; + } + + if ($this !== $result = $this->attributes->get($key, $this, $deep)) { + return $result; + } + + if ($this !== $result = $this->request->get($key, $this, $deep)) { + return $result; + } + + return $default; + } + + /** + * Gets the Session. + * + * @return SessionInterface|null The session + */ + public function getSession() + { + return $this->session; + } + + /** + * Whether the request contains a Session which was started in one of the + * previous requests. + * + * @return bool + */ + public function hasPreviousSession() + { + // the check for $this->session avoids malicious users trying to fake a session cookie with proper name + return $this->hasSession() && $this->cookies->has($this->session->getName()); + } + + /** + * Whether the request contains a Session object. + * + * This method does not give any information about the state of the session object, + * like whether the session is started or not. It is just a way to check if this Request + * is associated with a Session instance. + * + * @return bool true when the Request contains a Session object, false otherwise + */ + public function hasSession() + { + return null !== $this->session; + } + + /** + * Sets the Session. + * + * @param SessionInterface $session The Session + */ + public function setSession(SessionInterface $session) + { + $this->session = $session; + } + + /** + * Returns the client IP addresses. + * + * In the returned array the most trusted IP address is first, and the + * least trusted one last. The "real" client IP address is the last one, + * but this is also the least trusted one. Trusted proxies are stripped. + * + * Use this method carefully; you should use getClientIp() instead. + * + * @return array The client IP addresses + * + * @see getClientIp() + */ + public function getClientIps() + { + $ip = $this->server->get('REMOTE_ADDR'); + + if (!$this->isFromTrustedProxy()) { + return array($ip); + } + + return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip); + } + + /** + * Returns the client IP address. + * + * This method can read the client IP address from the "X-Forwarded-For" header + * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For" + * header value is a comma+space separated list of IP addresses, the left-most + * being the original client, and each successive proxy that passed the request + * adding the IP address where it received the request from. + * + * If your reverse proxy uses a different header name than "X-Forwarded-For", + * ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with + * the "client-ip" key. + * + * @return string|null The client IP address + * + * @see getClientIps() + * @see http://en.wikipedia.org/wiki/X-Forwarded-For + */ + public function getClientIp() + { + $ipAddresses = $this->getClientIps(); + + return $ipAddresses[0]; + } + + /** + * Returns current script name. + * + * @return string + */ + public function getScriptName() + { + return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); + } + + /** + * Returns the path being requested relative to the executed script. + * + * The path info always starts with a /. + * + * Suppose this request is instantiated from /mysite on localhost: + * + * * http://localhost/mysite returns an empty string + * * http://localhost/mysite/about returns '/about' + * * http://localhost/mysite/enco%20ded returns '/enco%20ded' + * * http://localhost/mysite/about?var=1 returns '/about' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getPathInfo() + { + if (null === $this->pathInfo) { + $this->pathInfo = $this->preparePathInfo(); + } + + return $this->pathInfo; + } + + /** + * Returns the root path from which this request is executed. + * + * Suppose that an index.php file instantiates this request object: + * + * * http://localhost/index.php returns an empty string + * * http://localhost/index.php/page returns an empty string + * * http://localhost/web/index.php returns '/web' + * * http://localhost/we%20b/index.php returns '/we%20b' + * + * @return string The raw path (i.e. not urldecoded) + */ + public function getBasePath() + { + if (null === $this->basePath) { + $this->basePath = $this->prepareBasePath(); + } + + return $this->basePath; + } + + /** + * Returns the root URL from which this request is executed. + * + * The base URL never ends with a /. + * + * This is similar to getBasePath(), except that it also includes the + * script filename (e.g. index.php) if one exists. + * + * @return string The raw URL (i.e. not urldecoded) + */ + public function getBaseUrl() + { + if (null === $this->baseUrl) { + $this->baseUrl = $this->prepareBaseUrl(); + } + + return $this->baseUrl; + } + + /** + * Gets the request's scheme. + * + * @return string + */ + public function getScheme() + { + return $this->isSecure() ? 'https' : 'http'; + } + + /** + * Returns the port on which the request is made. + * + * This method can read the client port from the "X-Forwarded-Port" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Port" header must contain the client port. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Port", + * configure it via "setTrustedHeaderName()" with the "client-port" key. + * + * @return int|string can be a string if fetched from the server bag + */ + public function getPort() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) { + $host = $host[0]; + } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + return $this->server->get('SERVER_PORT'); + } + + if ('[' === $host[0]) { + $pos = strpos($host, ':', strrpos($host, ']')); + } else { + $pos = strrpos($host, ':'); + } + + if (false !== $pos) { + return (int) substr($host, $pos + 1); + } + + return 'https' === $this->getScheme() ? 443 : 80; + } + + /** + * Returns the user. + * + * @return string|null + */ + public function getUser() + { + return $this->headers->get('PHP_AUTH_USER'); + } + + /** + * Returns the password. + * + * @return string|null + */ + public function getPassword() + { + return $this->headers->get('PHP_AUTH_PW'); + } + + /** + * Gets the user info. + * + * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server + */ + public function getUserInfo() + { + $userinfo = $this->getUser(); + + $pass = $this->getPassword(); + if ('' != $pass) { + $userinfo .= ":$pass"; + } + + return $userinfo; + } + + /** + * Returns the HTTP host being requested. + * + * The port name will be appended to the host if it's non-standard. + * + * @return string + */ + public function getHttpHost() + { + $scheme = $this->getScheme(); + $port = $this->getPort(); + + if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) { + return $this->getHost(); + } + + return $this->getHost().':'.$port; + } + + /** + * Returns the requested URI (path and query string). + * + * @return string The raw URI (i.e. not URI decoded) + */ + public function getRequestUri() + { + if (null === $this->requestUri) { + $this->requestUri = $this->prepareRequestUri(); + } + + return $this->requestUri; + } + + /** + * Gets the scheme and HTTP host. + * + * If the URL was called with basic authentication, the user + * and the password are not added to the generated string. + * + * @return string The scheme and HTTP host + */ + public function getSchemeAndHttpHost() + { + return $this->getScheme().'://'.$this->getHttpHost(); + } + + /** + * Generates a normalized URI (URL) for the Request. + * + * @return string A normalized URI (URL) for the Request + * + * @see getQueryString() + */ + public function getUri() + { + if (null !== $qs = $this->getQueryString()) { + $qs = '?'.$qs; + } + + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; + } + + /** + * Generates a normalized URI for the given path. + * + * @param string $path A path to use instead of the current one + * + * @return string The normalized URI for the path + */ + public function getUriForPath($path) + { + return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path; + } + + /** + * Returns the path as relative reference from the current Request path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $path The target path + * + * @return string The relative target path + */ + public function getRelativeUriForPath($path) + { + // be sure that we are dealing with an absolute path + if (!isset($path[0]) || '/' !== $path[0]) { + return $path; + } + + if ($path === $basePath = $this->getPathInfo()) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', substr($path, 1)); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return !isset($path[0]) || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } + + /** + * Generates the normalized query string for the Request. + * + * It builds a normalized query string, where keys/value pairs are alphabetized + * and have consistent escaping. + * + * @return string|null A normalized query string for the Request + */ + public function getQueryString() + { + $qs = static::normalizeQueryString($this->server->get('QUERY_STRING')); + + return '' === $qs ? null : $qs; + } + + /** + * Checks whether the request is secure or not. + * + * This method can read the client protocol from the "X-Forwarded-Proto" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http". + * + * If your reverse proxy uses a different header name than "X-Forwarded-Proto" + * ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with + * the "client-proto" key. + * + * @return bool + */ + public function isSecure() + { + if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) { + return \in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true); + } + + $https = $this->server->get('HTTPS'); + + return !empty($https) && 'off' !== strtolower($https); + } + + /** + * Returns the host name. + * + * This method can read the client host name from the "X-Forwarded-Host" header + * when trusted proxies were set via "setTrustedProxies()". + * + * The "X-Forwarded-Host" header must contain the client host name. + * + * If your reverse proxy uses a different header name than "X-Forwarded-Host", + * configure it via "setTrustedHeaderName()" with the "client-host" key. + * + * @return string + * + * @throws \UnexpectedValueException when the host name is invalid + */ + public function getHost() + { + if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) { + $host = $host[0]; + } elseif (!$host = $this->headers->get('HOST')) { + if (!$host = $this->server->get('SERVER_NAME')) { + $host = $this->server->get('SERVER_ADDR', ''); + } + } + + // trim and remove port number from host + // host is lowercase as per RFC 952/2181 + $host = strtolower(preg_replace('/:\d+$/', '', trim($host))); + + // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user) + // check that it does not contain forbidden characters (see RFC 952 and RFC 2181) + // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names + if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { + throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host)); + } + + if (\count(self::$trustedHostPatterns) > 0) { + // to avoid host header injection attacks, you should provide a list of trusted host patterns + + if (\in_array($host, self::$trustedHosts)) { + return $host; + } + + foreach (self::$trustedHostPatterns as $pattern) { + if (preg_match($pattern, $host)) { + self::$trustedHosts[] = $host; + + return $host; + } + } + + throw new \UnexpectedValueException(sprintf('Untrusted Host "%s"', $host)); + } + + return $host; + } + + /** + * Sets the request method. + * + * @param string $method + */ + public function setMethod($method) + { + $this->method = null; + $this->server->set('REQUEST_METHOD', $method); + } + + /** + * Gets the request "intended" method. + * + * If the X-HTTP-Method-Override header is set, and if the method is a POST, + * then it is used to determine the "real" intended HTTP method. + * + * The _method request parameter can also be used to determine the HTTP method, + * but only if enableHttpMethodParameterOverride() has been called. + * + * The method is always an uppercased string. + * + * @return string The request method + * + * @see getRealMethod() + */ + public function getMethod() + { + if (null === $this->method) { + $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + + if ('POST' === $this->method) { + if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) { + $this->method = strtoupper($method); + } elseif (self::$httpMethodParameterOverride) { + $method = $this->request->get('_method', $this->query->get('_method', 'POST')); + if (\is_string($method)) { + $this->method = strtoupper($method); + } + } + } + } + + return $this->method; + } + + /** + * Gets the "real" request method. + * + * @return string The request method + * + * @see getMethod() + */ + public function getRealMethod() + { + return strtoupper($this->server->get('REQUEST_METHOD', 'GET')); + } + + /** + * Gets the mime type associated with the format. + * + * @param string $format The format + * + * @return string|null The associated mime type (null if not found) + */ + public function getMimeType($format) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; + } + + /** + * Gets the format associated with the mime type. + * + * @param string $mimeType The associated mime type + * + * @return string|null The format (null if not found) + */ + public function getFormat($mimeType) + { + $canonicalMimeType = null; + if (false !== $pos = strpos($mimeType, ';')) { + $canonicalMimeType = trim(substr($mimeType, 0, $pos)); + } + + if (null === static::$formats) { + static::initializeFormats(); + } + + foreach (static::$formats as $format => $mimeTypes) { + if (\in_array($mimeType, (array) $mimeTypes)) { + return $format; + } + if (null !== $canonicalMimeType && \in_array($canonicalMimeType, (array) $mimeTypes)) { + return $format; + } + } + } + + /** + * Associates a format with mime types. + * + * @param string $format The format + * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) + */ + public function setFormat($format, $mimeTypes) + { + if (null === static::$formats) { + static::initializeFormats(); + } + + static::$formats[$format] = \is_array($mimeTypes) ? $mimeTypes : array($mimeTypes); + } + + /** + * Gets the request format. + * + * Here is the process to determine the format: + * + * * format defined by the user (with setRequestFormat()) + * * _format request parameter + * * $default + * + * @param string|null $default The default format + * + * @return string The request format + */ + public function getRequestFormat($default = 'html') + { + if (null === $this->format) { + $this->format = $this->get('_format'); + } + + return null === $this->format ? $default : $this->format; + } + + /** + * Sets the request format. + * + * @param string $format The request format + */ + public function setRequestFormat($format) + { + $this->format = $format; + } + + /** + * Gets the format associated with the request. + * + * @return string|null The format (null if no content type is present) + */ + public function getContentType() + { + return $this->getFormat($this->headers->get('CONTENT_TYPE')); + } + + /** + * Sets the default locale. + * + * @param string $locale + */ + public function setDefaultLocale($locale) + { + $this->defaultLocale = $locale; + + if (null === $this->locale) { + $this->setPhpDefaultLocale($locale); + } + } + + /** + * Get the default locale. + * + * @return string + */ + public function getDefaultLocale() + { + return $this->defaultLocale; + } + + /** + * Sets the locale. + * + * @param string $locale + */ + public function setLocale($locale) + { + $this->setPhpDefaultLocale($this->locale = $locale); + } + + /** + * Get the locale. + * + * @return string + */ + public function getLocale() + { + return null === $this->locale ? $this->defaultLocale : $this->locale; + } + + /** + * Checks if the request method is of specified type. + * + * @param string $method Uppercase request method (GET, POST etc) + * + * @return bool + */ + public function isMethod($method) + { + return $this->getMethod() === strtoupper($method); + } + + /** + * Checks whether the method is safe or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.1 + * + * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default. + * + * @return bool + */ + public function isMethodSafe(/* $andCacheable = true */) + { + return \in_array($this->getMethod(), 0 < \func_num_args() && !func_get_arg(0) ? array('GET', 'HEAD', 'OPTIONS', 'TRACE') : array('GET', 'HEAD')); + } + + /** + * Checks whether the method is cacheable or not. + * + * @see https://tools.ietf.org/html/rfc7231#section-4.2.3 + * + * @return bool True for GET and HEAD, false otherwise + */ + public function isMethodCacheable() + { + return \in_array($this->getMethod(), array('GET', 'HEAD')); + } + + /** + * Returns the request body content. + * + * @param bool $asResource If true, a resource will be returned + * + * @return string|resource The request body content or a resource to read the body stream + * + * @throws \LogicException + */ + public function getContent($asResource = false) + { + $currentContentIsResource = \is_resource($this->content); + if (\PHP_VERSION_ID < 50600 && false === $this->content) { + throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.'); + } + + if (true === $asResource) { + if ($currentContentIsResource) { + rewind($this->content); + + return $this->content; + } + + // Content passed in parameter (test) + if (\is_string($this->content)) { + $resource = fopen('php://temp', 'r+'); + fwrite($resource, $this->content); + rewind($resource); + + return $resource; + } + + $this->content = false; + + return fopen('php://input', 'rb'); + } + + if ($currentContentIsResource) { + rewind($this->content); + + return stream_get_contents($this->content); + } + + if (null === $this->content || false === $this->content) { + $this->content = file_get_contents('php://input'); + } + + return $this->content; + } + + /** + * Gets the Etags. + * + * @return array The entity tags + */ + public function getETags() + { + return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY); + } + + /** + * @return bool + */ + public function isNoCache() + { + return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); + } + + /** + * Returns the preferred language. + * + * @param array $locales An array of ordered available locales + * + * @return string|null The preferred locale + */ + public function getPreferredLanguage(array $locales = null) + { + $preferredLanguages = $this->getLanguages(); + + if (empty($locales)) { + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; + } + + if (!$preferredLanguages) { + return $locales[0]; + } + + $extendedPreferredLanguages = array(); + foreach ($preferredLanguages as $language) { + $extendedPreferredLanguages[] = $language; + if (false !== $position = strpos($language, '_')) { + $superLanguage = substr($language, 0, $position); + if (!\in_array($superLanguage, $preferredLanguages)) { + $extendedPreferredLanguages[] = $superLanguage; + } + } + } + + $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales)); + + return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; + } + + /** + * Gets a list of languages acceptable by the client browser. + * + * @return array Languages ordered in the user browser preferences + */ + public function getLanguages() + { + if (null !== $this->languages) { + return $this->languages; + } + + $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all(); + $this->languages = array(); + foreach ($languages as $lang => $acceptHeaderItem) { + if (false !== strpos($lang, '-')) { + $codes = explode('-', $lang); + if ('i' === $codes[0]) { + // Language not listed in ISO 639 that are not variants + // of any listed language, which can be registered with the + // i-prefix, such as i-cherokee + if (\count($codes) > 1) { + $lang = $codes[1]; + } + } else { + for ($i = 0, $max = \count($codes); $i < $max; ++$i) { + if (0 === $i) { + $lang = strtolower($codes[0]); + } else { + $lang .= '_'.strtoupper($codes[$i]); + } + } + } + } + + $this->languages[] = $lang; + } + + return $this->languages; + } + + /** + * Gets a list of charsets acceptable by the client browser. + * + * @return array List of charsets in preferable order + */ + public function getCharsets() + { + if (null !== $this->charsets) { + return $this->charsets; + } + + return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all()); + } + + /** + * Gets a list of encodings acceptable by the client browser. + * + * @return array List of encodings in preferable order + */ + public function getEncodings() + { + if (null !== $this->encodings) { + return $this->encodings; + } + + return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all()); + } + + /** + * Gets a list of content types acceptable by the client browser. + * + * @return array List of content types in preferable order + */ + public function getAcceptableContentTypes() + { + if (null !== $this->acceptableContentTypes) { + return $this->acceptableContentTypes; + } + + return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all()); + } + + /** + * Returns true if the request is a XMLHttpRequest. + * + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: + * + * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript + * + * @return bool true if the request is an XMLHttpRequest, false otherwise + */ + public function isXmlHttpRequest() + { + return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); + } + + /* + * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) + * + * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd). + * + * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) + */ + + protected function prepareRequestUri() + { + $requestUri = ''; + + if ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) { + // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem) + $requestUri = $this->server->get('UNENCODED_URL'); + $this->server->remove('UNENCODED_URL'); + $this->server->remove('IIS_WasUrlRewritten'); + } elseif ($this->server->has('REQUEST_URI')) { + $requestUri = $this->server->get('REQUEST_URI'); + // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path + $schemeAndHttpHost = $this->getSchemeAndHttpHost(); + if (0 === strpos($requestUri, $schemeAndHttpHost)) { + $requestUri = substr($requestUri, \strlen($schemeAndHttpHost)); + } + } elseif ($this->server->has('ORIG_PATH_INFO')) { + // IIS 5.0, PHP as CGI + $requestUri = $this->server->get('ORIG_PATH_INFO'); + if ('' != $this->server->get('QUERY_STRING')) { + $requestUri .= '?'.$this->server->get('QUERY_STRING'); + } + $this->server->remove('ORIG_PATH_INFO'); + } + + // normalize the request URI to ease creating sub-requests from this request + $this->server->set('REQUEST_URI', $requestUri); + + return $requestUri; + } + + /** + * Prepares the base URL. + * + * @return string + */ + protected function prepareBaseUrl() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + + if (basename($this->server->get('SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('SCRIPT_NAME'); + } elseif (basename($this->server->get('PHP_SELF')) === $filename) { + $baseUrl = $this->server->get('PHP_SELF'); + } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { + $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility + } else { + // Backtrack up the script_filename to find the portion matching + // php_self + $path = $this->server->get('PHP_SELF', ''); + $file = $this->server->get('SCRIPT_FILENAME', ''); + $segs = explode('/', trim($file, '/')); + $segs = array_reverse($segs); + $index = 0; + $last = \count($segs); + $baseUrl = ''; + do { + $seg = $segs[$index]; + $baseUrl = '/'.$seg.$baseUrl; + ++$index; + } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos); + } + + // Does the baseUrl have anything in common with the request_uri? + $requestUri = $this->getRequestUri(); + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) { + // full $baseUrl matches + return $prefix; + } + + if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(\dirname($baseUrl), '/'.\DIRECTORY_SEPARATOR).'/')) { + // directory portion of $baseUrl matches + return rtrim($prefix, '/'.\DIRECTORY_SEPARATOR); + } + + $truncatedRequestUri = $requestUri; + if (false !== $pos = strpos($requestUri, '?')) { + $truncatedRequestUri = substr($requestUri, 0, $pos); + } + + $basename = basename($baseUrl); + if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) { + // no match whatsoever; set it blank + return ''; + } + + // If using mod_rewrite or ISAPI_Rewrite strip the script filename + // out of baseUrl. $pos !== 0 makes sure it is not matching a value + // from PATH_INFO or QUERY_STRING + if (\strlen($requestUri) >= \strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) { + $baseUrl = substr($requestUri, 0, $pos + \strlen($baseUrl)); + } + + return rtrim($baseUrl, '/'.\DIRECTORY_SEPARATOR); + } + + /** + * Prepares the base path. + * + * @return string base path + */ + protected function prepareBasePath() + { + $filename = basename($this->server->get('SCRIPT_FILENAME')); + $baseUrl = $this->getBaseUrl(); + if (empty($baseUrl)) { + return ''; + } + + if (basename($baseUrl) === $filename) { + $basePath = \dirname($baseUrl); + } else { + $basePath = $baseUrl; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $basePath = str_replace('\\', '/', $basePath); + } + + return rtrim($basePath, '/'); + } + + /** + * Prepares the path info. + * + * @return string path info + */ + protected function preparePathInfo() + { + $baseUrl = $this->getBaseUrl(); + + if (null === ($requestUri = $this->getRequestUri())) { + return '/'; + } + + // Remove the query string from REQUEST_URI + if (false !== $pos = strpos($requestUri, '?')) { + $requestUri = substr($requestUri, 0, $pos); + } + if ('' !== $requestUri && '/' !== $requestUri[0]) { + $requestUri = '/'.$requestUri; + } + + $pathInfo = substr($requestUri, \strlen($baseUrl)); + if (null !== $baseUrl && (false === $pathInfo || '' === $pathInfo)) { + // If substr() returns false then PATH_INFO is set to an empty string + return '/'; + } elseif (null === $baseUrl) { + return $requestUri; + } + + return (string) $pathInfo; + } + + /** + * Initializes HTTP request formats. + */ + protected static function initializeFormats() + { + static::$formats = array( + 'html' => array('text/html', 'application/xhtml+xml'), + 'txt' => array('text/plain'), + 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'), + 'css' => array('text/css'), + 'json' => array('application/json', 'application/x-json'), + 'jsonld' => array('application/ld+json'), + 'xml' => array('text/xml', 'application/xml', 'application/x-xml'), + 'rdf' => array('application/rdf+xml'), + 'atom' => array('application/atom+xml'), + 'rss' => array('application/rss+xml'), + 'form' => array('application/x-www-form-urlencoded'), + ); + } + + /** + * Sets the default PHP locale. + * + * @param string $locale + */ + private function setPhpDefaultLocale($locale) + { + // if either the class Locale doesn't exist, or an exception is thrown when + // setting the default locale, the intl module is not installed, and + // the call can be ignored: + try { + if (class_exists('Locale', false)) { + \Locale::setDefault($locale); + } + } catch (\Exception $e) { + } + } + + /* + * Returns the prefix as encoded in the string when the string starts with + * the given prefix, false otherwise. + * + * @param string $string The urlencoded string + * @param string $prefix The prefix not encoded + * + * @return string|false The prefix as it is encoded in $string, or false + */ + private function getUrlencodedPrefix($string, $prefix) + { + if (0 !== strpos(rawurldecode($string), $prefix)) { + return false; + } + + $len = \strlen($prefix); + + if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) { + return $match[0]; + } + + return false; + } + + private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) + { + if (self::$requestFactory) { + $request = \call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content); + + if (!$request instanceof self) { + throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); + } + + return $request; + } + + return new static($query, $request, $attributes, $cookies, $files, $server, $content); + } + + private function isFromTrustedProxy() + { + return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies); + } + + private function getTrustedValues($type, $ip = null) + { + $clientValues = array(); + $forwardedValues = array(); + + if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) { + foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) { + $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v); + } + } + + if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) { + $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]); + $forwardedValues = preg_match_all(sprintf('{(?:%s)="?([a-zA-Z0-9\.:_\-/\[\]]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array(); + if (self::HEADER_CLIENT_PORT === $type) { + foreach ($forwardedValues as $k => $v) { + if (']' === substr($v, -1) || false === $v = strrchr($v, ':')) { + $v = $this->isSecure() ? ':443' : ':80'; + } + $forwardedValues[$k] = '0.0.0.0'.$v; + } + } + } + + if (null !== $ip) { + $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip); + $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip); + } + + if ($forwardedValues === $clientValues || !$clientValues) { + return $forwardedValues; + } + + if (!$forwardedValues) { + return $clientValues; + } + + if (!$this->isForwardedValid) { + return null !== $ip ? array('0.0.0.0', $ip) : array(); + } + $this->isForwardedValid = false; + + throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type])); + } + + private function normalizeAndFilterClientIps(array $clientIps, $ip) + { + if (!$clientIps) { + return array(); + } + $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from + $firstTrustedIp = null; + + foreach ($clientIps as $key => $clientIp) { + if (strpos($clientIp, '.')) { + // Strip :port from IPv4 addresses. This is allowed in Forwarded + // and may occur in X-Forwarded-For. + $i = strpos($clientIp, ':'); + if ($i) { + $clientIps[$key] = $clientIp = substr($clientIp, 0, $i); + } + } elseif (0 === strpos($clientIp, '[')) { + // Strip brackets and :port from IPv6 addresses. + $i = strpos($clientIp, ']', 1); + $clientIps[$key] = $clientIp = substr($clientIp, 1, $i - 1); + } + + if (!filter_var($clientIp, FILTER_VALIDATE_IP)) { + unset($clientIps[$key]); + + continue; + } + + if (IpUtils::checkIp($clientIp, self::$trustedProxies)) { + unset($clientIps[$key]); + + // Fallback to this when the client IP falls into the range of trusted proxies + if (null === $firstTrustedIp) { + $firstTrustedIp = $clientIp; + } + } + } + + // Now the IP chain contains only untrusted proxies and the client IP + return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcher.php b/vendor/symfony/http-foundation/RequestMatcher.php new file mode 100644 index 0000000..6b4cef1 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcher.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcher compares a pre-defined set of checks against a Request instance. + * + * @author Fabien Potencier + */ +class RequestMatcher implements RequestMatcherInterface +{ + /** + * @var string|null + */ + private $path; + + /** + * @var string|null + */ + private $host; + + /** + * @var string[] + */ + private $methods = array(); + + /** + * @var string[] + */ + private $ips = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * @var string[] + */ + private $schemes = array(); + + /** + * @param string|null $path + * @param string|null $host + * @param string|string[]|null $methods + * @param string|string[]|null $ips + * @param array $attributes + * @param string|string[]|null $schemes + */ + public function __construct($path = null, $host = null, $methods = null, $ips = null, array $attributes = array(), $schemes = null) + { + $this->matchPath($path); + $this->matchHost($host); + $this->matchMethod($methods); + $this->matchIps($ips); + $this->matchScheme($schemes); + + foreach ($attributes as $k => $v) { + $this->matchAttribute($k, $v); + } + } + + /** + * Adds a check for the HTTP scheme. + * + * @param string|string[]|null $scheme An HTTP scheme or an array of HTTP schemes + */ + public function matchScheme($scheme) + { + $this->schemes = null !== $scheme ? array_map('strtolower', (array) $scheme) : array(); + } + + /** + * Adds a check for the URL host name. + * + * @param string|null $regexp A Regexp + */ + public function matchHost($regexp) + { + $this->host = $regexp; + } + + /** + * Adds a check for the URL path info. + * + * @param string|null $regexp A Regexp + */ + public function matchPath($regexp) + { + $this->path = $regexp; + } + + /** + * Adds a check for the client IP. + * + * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIp($ip) + { + $this->matchIps($ip); + } + + /** + * Adds a check for the client IP. + * + * @param string|string[]|null $ips A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 + */ + public function matchIps($ips) + { + $this->ips = null !== $ips ? (array) $ips : array(); + } + + /** + * Adds a check for the HTTP method. + * + * @param string|string[]|null $method An HTTP method or an array of HTTP methods + */ + public function matchMethod($method) + { + $this->methods = null !== $method ? array_map('strtoupper', (array) $method) : array(); + } + + /** + * Adds a check for request attribute. + * + * @param string $key The request attribute name + * @param string $regexp A Regexp + */ + public function matchAttribute($key, $regexp) + { + $this->attributes[$key] = $regexp; + } + + /** + * {@inheritdoc} + */ + public function matches(Request $request) + { + if ($this->schemes && !\in_array($request->getScheme(), $this->schemes, true)) { + return false; + } + + if ($this->methods && !\in_array($request->getMethod(), $this->methods, true)) { + return false; + } + + foreach ($this->attributes as $key => $pattern) { + if (!preg_match('{'.$pattern.'}', $request->attributes->get($key))) { + return false; + } + } + + if (null !== $this->path && !preg_match('{'.$this->path.'}', rawurldecode($request->getPathInfo()))) { + return false; + } + + if (null !== $this->host && !preg_match('{'.$this->host.'}i', $request->getHost())) { + return false; + } + + if (IpUtils::checkIp($request->getClientIp(), $this->ips)) { + return true; + } + + // Note to future implementors: add additional checks above the + // foreach above or else your check might not be run! + return 0 === \count($this->ips); + } +} diff --git a/vendor/symfony/http-foundation/RequestMatcherInterface.php b/vendor/symfony/http-foundation/RequestMatcherInterface.php new file mode 100644 index 0000000..c26db3e --- /dev/null +++ b/vendor/symfony/http-foundation/RequestMatcherInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * RequestMatcherInterface is an interface for strategies to match a Request. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Decides whether the rule(s) implemented by the strategy matches the supplied request. + * + * @return bool true if the request matches, false otherwise + */ + public function matches(Request $request); +} diff --git a/vendor/symfony/http-foundation/RequestStack.php b/vendor/symfony/http-foundation/RequestStack.php new file mode 100644 index 0000000..40123f6 --- /dev/null +++ b/vendor/symfony/http-foundation/RequestStack.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Request stack that controls the lifecycle of requests. + * + * @author Benjamin Eberlei + */ +class RequestStack +{ + /** + * @var Request[] + */ + private $requests = array(); + + /** + * Pushes a Request on the stack. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + */ + public function push(Request $request) + { + $this->requests[] = $request; + } + + /** + * Pops the current request from the stack. + * + * This operation lets the current request go out of scope. + * + * This method should generally not be called directly as the stack + * management should be taken care of by the application itself. + * + * @return Request|null + */ + public function pop() + { + if (!$this->requests) { + return; + } + + return array_pop($this->requests); + } + + /** + * @return Request|null + */ + public function getCurrentRequest() + { + return end($this->requests) ?: null; + } + + /** + * Gets the master Request. + * + * Be warned that making your code aware of the master request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * @return Request|null + */ + public function getMasterRequest() + { + if (!$this->requests) { + return; + } + + return $this->requests[0]; + } + + /** + * Returns the parent request of the current. + * + * Be warned that making your code aware of the parent request + * might make it un-compatible with other features of your framework + * like ESI support. + * + * If current Request is the master request, it returns null. + * + * @return Request|null + */ + public function getParentRequest() + { + $pos = \count($this->requests) - 2; + + if (!isset($this->requests[$pos])) { + return; + } + + return $this->requests[$pos]; + } +} diff --git a/vendor/symfony/http-foundation/Response.php b/vendor/symfony/http-foundation/Response.php new file mode 100644 index 0000000..a4ad0e6 --- /dev/null +++ b/vendor/symfony/http-foundation/Response.php @@ -0,0 +1,1198 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * Response represents an HTTP response. + * + * @author Fabien Potencier + */ +class Response +{ + const HTTP_CONTINUE = 100; + const HTTP_SWITCHING_PROTOCOLS = 101; + const HTTP_PROCESSING = 102; // RFC2518 + const HTTP_EARLY_HINTS = 103; // RFC8297 + const HTTP_OK = 200; + const HTTP_CREATED = 201; + const HTTP_ACCEPTED = 202; + const HTTP_NON_AUTHORITATIVE_INFORMATION = 203; + const HTTP_NO_CONTENT = 204; + const HTTP_RESET_CONTENT = 205; + const HTTP_PARTIAL_CONTENT = 206; + const HTTP_MULTI_STATUS = 207; // RFC4918 + const HTTP_ALREADY_REPORTED = 208; // RFC5842 + const HTTP_IM_USED = 226; // RFC3229 + const HTTP_MULTIPLE_CHOICES = 300; + const HTTP_MOVED_PERMANENTLY = 301; + const HTTP_FOUND = 302; + const HTTP_SEE_OTHER = 303; + const HTTP_NOT_MODIFIED = 304; + const HTTP_USE_PROXY = 305; + const HTTP_RESERVED = 306; + const HTTP_TEMPORARY_REDIRECT = 307; + const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238 + const HTTP_BAD_REQUEST = 400; + const HTTP_UNAUTHORIZED = 401; + const HTTP_PAYMENT_REQUIRED = 402; + const HTTP_FORBIDDEN = 403; + const HTTP_NOT_FOUND = 404; + const HTTP_METHOD_NOT_ALLOWED = 405; + const HTTP_NOT_ACCEPTABLE = 406; + const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; + const HTTP_REQUEST_TIMEOUT = 408; + const HTTP_CONFLICT = 409; + const HTTP_GONE = 410; + const HTTP_LENGTH_REQUIRED = 411; + const HTTP_PRECONDITION_FAILED = 412; + const HTTP_REQUEST_ENTITY_TOO_LARGE = 413; + const HTTP_REQUEST_URI_TOO_LONG = 414; + const HTTP_UNSUPPORTED_MEDIA_TYPE = 415; + const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; + const HTTP_EXPECTATION_FAILED = 417; + const HTTP_I_AM_A_TEAPOT = 418; // RFC2324 + const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540 + const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918 + const HTTP_LOCKED = 423; // RFC4918 + const HTTP_FAILED_DEPENDENCY = 424; // RFC4918 + + /** + * @deprecated + */ + const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817 + const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04 + const HTTP_UPGRADE_REQUIRED = 426; // RFC2817 + const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585 + const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585 + const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585 + const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; + const HTTP_INTERNAL_SERVER_ERROR = 500; + const HTTP_NOT_IMPLEMENTED = 501; + const HTTP_BAD_GATEWAY = 502; + const HTTP_SERVICE_UNAVAILABLE = 503; + const HTTP_GATEWAY_TIMEOUT = 504; + const HTTP_VERSION_NOT_SUPPORTED = 505; + const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295 + const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918 + const HTTP_LOOP_DETECTED = 508; // RFC5842 + const HTTP_NOT_EXTENDED = 510; // RFC2774 + const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585 + + /** + * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag + */ + public $headers; + + /** + * @var string + */ + protected $content; + + /** + * @var string + */ + protected $version; + + /** + * @var int + */ + protected $statusCode; + + /** + * @var string + */ + protected $statusText; + + /** + * @var string + */ + protected $charset; + + /** + * Status codes translation table. + * + * The list of codes is complete according to the + * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry} + * (last updated 2016-03-01). + * + * Unless otherwise noted, the status code is defined in RFC2616. + * + * @var array + */ + public static $statusTexts = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ); + + /** + * @param mixed $content The response content, see setContent() + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function __construct($content = '', $status = 200, $headers = array()) + { + $this->headers = new ResponseHeaderBag($headers); + $this->setContent($content); + $this->setStatusCode($status); + $this->setProtocolVersion('1.0'); + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + } + + /** + * Factory method for chainability. + * + * Example: + * + * return Response::create($body, 200) + * ->setSharedMaxAge(300); + * + * @param mixed $content The response content, see setContent() + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($content = '', $status = 200, $headers = array()) + { + return new static($content, $status, $headers); + } + + /** + * Returns the Response as an HTTP string. + * + * The string representation of the Response is the same as the + * one that will be sent to the client only if the prepare() method + * has been called before. + * + * @return string The Response as an HTTP string + * + * @see prepare() + */ + public function __toString() + { + return + sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". + $this->headers."\r\n". + $this->getContent(); + } + + /** + * Clones the current Response instance. + */ + public function __clone() + { + $this->headers = clone $this->headers; + } + + /** + * Prepares the Response before it is sent to the client. + * + * This method tweaks the Response to ensure that it is + * compliant with RFC 2616. Most of the changes are based on + * the Request that is "associated" with this Response. + * + * @return $this + */ + public function prepare(Request $request) + { + $headers = $this->headers; + + if ($this->isInformational() || $this->isEmpty()) { + $this->setContent(null); + $headers->remove('Content-Type'); + $headers->remove('Content-Length'); + } else { + // Content-type based on the Request + if (!$headers->has('Content-Type')) { + $format = $request->getRequestFormat(); + if (null !== $format && $mimeType = $request->getMimeType($format)) { + $headers->set('Content-Type', $mimeType); + } + } + + // Fix Content-Type + $charset = $this->charset ?: 'UTF-8'; + if (!$headers->has('Content-Type')) { + $headers->set('Content-Type', 'text/html; charset='.$charset); + } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) { + // add the charset + $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset); + } + + // Fix Content-Length + if ($headers->has('Transfer-Encoding')) { + $headers->remove('Content-Length'); + } + + if ($request->isMethod('HEAD')) { + // cf. RFC2616 14.13 + $length = $headers->get('Content-Length'); + $this->setContent(null); + if ($length) { + $headers->set('Content-Length', $length); + } + } + } + + // Fix protocol + if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) { + $this->setProtocolVersion('1.1'); + } + + // Check if we need to send extra expire info headers + if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) { + $this->headers->set('pragma', 'no-cache'); + $this->headers->set('expires', -1); + } + + $this->ensureIEOverSSLCompatibility($request); + + return $this; + } + + /** + * Sends HTTP headers. + * + * @return $this + */ + public function sendHeaders() + { + // headers have already been sent by the developer + if (headers_sent()) { + return $this; + } + + /* RFC2616 - 14.18 says all Responses need to have a Date */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + + // headers + foreach ($this->headers->allPreserveCase() as $name => $values) { + $replace = 0 === strcasecmp($name, 'Content-Type'); + foreach ($values as $value) { + header($name.': '.$value, $replace, $this->statusCode); + } + } + + // status + header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode); + + // cookies + foreach ($this->headers->getCookies() as $cookie) { + setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + + return $this; + } + + /** + * Sends content for the current web response. + * + * @return $this + */ + public function sendContent() + { + echo $this->content; + + return $this; + } + + /** + * Sends HTTP headers and content. + * + * @return $this + */ + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + + if (\function_exists('fastcgi_finish_request')) { + fastcgi_finish_request(); + } elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) { + static::closeOutputBuffers(0, true); + } + + return $this; + } + + /** + * Sets the response content. + * + * Valid types are strings, numbers, null, and objects that implement a __toString() method. + * + * @param mixed $content Content that can be cast to string + * + * @return $this + * + * @throws \UnexpectedValueException + */ + public function setContent($content) + { + if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable(array($content, '__toString'))) { + throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * Gets the current response content. + * + * @return string Content + */ + public function getContent() + { + return $this->content; + } + + /** + * Sets the HTTP protocol version (1.0 or 1.1). + * + * @param string $version The HTTP protocol version + * + * @return $this + */ + public function setProtocolVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * Gets the HTTP protocol version. + * + * @return string The HTTP protocol version + */ + public function getProtocolVersion() + { + return $this->version; + } + + /** + * Sets the response status code. + * + * If the status text is null it will be automatically populated for the known + * status codes and left empty otherwise. + * + * @param int $code HTTP status code + * @param mixed $text HTTP status text + * + * @return $this + * + * @throws \InvalidArgumentException When the HTTP status code is not valid + */ + public function setStatusCode($code, $text = null) + { + $this->statusCode = $code = (int) $code; + if ($this->isInvalid()) { + throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); + } + + if (null === $text) { + $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'; + + return $this; + } + + if (false === $text) { + $this->statusText = ''; + + return $this; + } + + $this->statusText = $text; + + return $this; + } + + /** + * Retrieves the status code for the current web response. + * + * @return int Status code + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Sets the response charset. + * + * @param string $charset Character set + * + * @return $this + */ + public function setCharset($charset) + { + $this->charset = $charset; + + return $this; + } + + /** + * Retrieves the response charset. + * + * @return string Character set + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Returns true if the response may safely be kept in a shared (surrogate) cache. + * + * Responses marked "private" with an explicit Cache-Control directive are + * considered uncacheable. + * + * Responses with neither a freshness lifetime (Expires, max-age) nor cache + * validator (Last-Modified, ETag) are considered uncacheable because there is + * no way to tell when or how to remove them from the cache. + * + * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation, + * for example "status codes that are defined as cacheable by default [...] + * can be reused by a cache with heuristic expiration unless otherwise indicated" + * (https://tools.ietf.org/html/rfc7231#section-6.1) + * + * @return bool true if the response is worth caching, false otherwise + */ + public function isCacheable() + { + if (!\in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) { + return false; + } + + if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { + return false; + } + + return $this->isValidateable() || $this->isFresh(); + } + + /** + * Returns true if the response is "fresh". + * + * Fresh responses may be served from cache without any interaction with the + * origin. A response is considered fresh when it includes a Cache-Control/max-age + * indicator or Expires header and the calculated age is less than the freshness lifetime. + * + * @return bool true if the response is fresh, false otherwise + */ + public function isFresh() + { + return $this->getTtl() > 0; + } + + /** + * Returns true if the response includes headers that can be used to validate + * the response with the origin server using a conditional GET request. + * + * @return bool true if the response is validateable, false otherwise + */ + public function isValidateable() + { + return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); + } + + /** + * Marks the response as "private". + * + * It makes the response ineligible for serving other clients. + * + * @return $this + */ + public function setPrivate() + { + $this->headers->removeCacheControlDirective('public'); + $this->headers->addCacheControlDirective('private'); + + return $this; + } + + /** + * Marks the response as "public". + * + * It makes the response eligible for serving other clients. + * + * @return $this + */ + public function setPublic() + { + $this->headers->addCacheControlDirective('public'); + $this->headers->removeCacheControlDirective('private'); + + return $this; + } + + /** + * Returns true if the response must be revalidated by caches. + * + * This method indicates that the response must not be served stale by a + * cache in any circumstance without first revalidating with the origin. + * When present, the TTL of the response should not be overridden to be + * greater than the value provided by the origin. + * + * @return bool true if the response must be revalidated by a cache, false otherwise + */ + public function mustRevalidate() + { + return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate'); + } + + /** + * Returns the Date header as a DateTime instance. + * + * @return \DateTime A \DateTime instance + * + * @throws \RuntimeException When the header is not parseable + */ + public function getDate() + { + /* + RFC2616 - 14.18 says all Responses need to have a Date. + Make sure we provide one even if it the header + has been removed in the meantime. + */ + if (!$this->headers->has('Date')) { + $this->setDate(\DateTime::createFromFormat('U', time())); + } + + return $this->headers->getDate('Date'); + } + + /** + * Sets the Date header. + * + * @return $this + */ + public function setDate(\DateTime $date) + { + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); + + return $this; + } + + /** + * Returns the age of the response. + * + * @return int The age of the response in seconds + */ + public function getAge() + { + if (null !== $age = $this->headers->get('Age')) { + return (int) $age; + } + + return max(time() - $this->getDate()->format('U'), 0); + } + + /** + * Marks the response stale by setting the Age header to be equal to the maximum age of the response. + * + * @return $this + */ + public function expire() + { + if ($this->isFresh()) { + $this->headers->set('Age', $this->getMaxAge()); + $this->headers->remove('Expires'); + } + + return $this; + } + + /** + * Returns the value of the Expires header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + */ + public function getExpires() + { + try { + return $this->headers->getDate('Expires'); + } catch (\RuntimeException $e) { + // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past + return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000'); + } + } + + /** + * Sets the Expires HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return $this + */ + public function setExpires(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Expires'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the number of seconds after the time specified in the response's Date + * header when the response should no longer be considered fresh. + * + * First, it checks for a s-maxage directive, then a max-age directive, and then it falls + * back on an expires header. It returns null when no maximum age can be established. + * + * @return int|null Number of seconds + */ + public function getMaxAge() + { + if ($this->headers->hasCacheControlDirective('s-maxage')) { + return (int) $this->headers->getCacheControlDirective('s-maxage'); + } + + if ($this->headers->hasCacheControlDirective('max-age')) { + return (int) $this->headers->getCacheControlDirective('max-age'); + } + + if (null !== $this->getExpires()) { + return $this->getExpires()->format('U') - $this->getDate()->format('U'); + } + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh. + * + * This methods sets the Cache-Control max-age directive. + * + * @param int $value Number of seconds + * + * @return $this + */ + public function setMaxAge($value) + { + $this->headers->addCacheControlDirective('max-age', $value); + + return $this; + } + + /** + * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. + * + * This methods sets the Cache-Control s-maxage directive. + * + * @param int $value Number of seconds + * + * @return $this + */ + public function setSharedMaxAge($value) + { + $this->setPublic(); + $this->headers->addCacheControlDirective('s-maxage', $value); + + return $this; + } + + /** + * Returns the response's time-to-live in seconds. + * + * It returns null when no freshness information is present in the response. + * + * When the responses TTL is <= 0, the response may not be served from cache without first + * revalidating with the origin. + * + * @return int|null The TTL in seconds + */ + public function getTtl() + { + if (null !== $maxAge = $this->getMaxAge()) { + return $maxAge - $this->getAge(); + } + } + + /** + * Sets the response's time-to-live for shared caches. + * + * This method adjusts the Cache-Control/s-maxage directive. + * + * @param int $seconds Number of seconds + * + * @return $this + */ + public function setTtl($seconds) + { + $this->setSharedMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Sets the response's time-to-live for private/client caches. + * + * This method adjusts the Cache-Control/max-age directive. + * + * @param int $seconds Number of seconds + * + * @return $this + */ + public function setClientTtl($seconds) + { + $this->setMaxAge($this->getAge() + $seconds); + + return $this; + } + + /** + * Returns the Last-Modified HTTP header as a DateTime instance. + * + * @return \DateTime|null A DateTime instance or null if the header does not exist + * + * @throws \RuntimeException When the HTTP header is not parseable + */ + public function getLastModified() + { + return $this->headers->getDate('Last-Modified'); + } + + /** + * Sets the Last-Modified HTTP header with a DateTime instance. + * + * Passing null as value will remove the header. + * + * @param \DateTime|null $date A \DateTime instance or null to remove the header + * + * @return $this + */ + public function setLastModified(\DateTime $date = null) + { + if (null === $date) { + $this->headers->remove('Last-Modified'); + } else { + $date = clone $date; + $date->setTimezone(new \DateTimeZone('UTC')); + $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); + } + + return $this; + } + + /** + * Returns the literal value of the ETag HTTP header. + * + * @return string|null The ETag HTTP header or null if it does not exist + */ + public function getEtag() + { + return $this->headers->get('ETag'); + } + + /** + * Sets the ETag value. + * + * @param string|null $etag The ETag unique identifier or null to remove the header + * @param bool $weak Whether you want a weak ETag or not + * + * @return $this + */ + public function setEtag($etag = null, $weak = false) + { + if (null === $etag) { + $this->headers->remove('Etag'); + } else { + if (0 !== strpos($etag, '"')) { + $etag = '"'.$etag.'"'; + } + + $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); + } + + return $this; + } + + /** + * Sets the response's cache headers (validation and/or expiration). + * + * Available options are: etag, last_modified, max_age, s_maxage, private, and public. + * + * @param array $options An array of cache options + * + * @return $this + * + * @throws \InvalidArgumentException + */ + public function setCache(array $options) + { + if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) { + throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff)))); + } + + if (isset($options['etag'])) { + $this->setEtag($options['etag']); + } + + if (isset($options['last_modified'])) { + $this->setLastModified($options['last_modified']); + } + + if (isset($options['max_age'])) { + $this->setMaxAge($options['max_age']); + } + + if (isset($options['s_maxage'])) { + $this->setSharedMaxAge($options['s_maxage']); + } + + if (isset($options['public'])) { + if ($options['public']) { + $this->setPublic(); + } else { + $this->setPrivate(); + } + } + + if (isset($options['private'])) { + if ($options['private']) { + $this->setPrivate(); + } else { + $this->setPublic(); + } + } + + return $this; + } + + /** + * Modifies the response so that it conforms to the rules defined for a 304 status code. + * + * This sets the status, removes the body, and discards any headers + * that MUST NOT be included in 304 responses. + * + * @return $this + * + * @see http://tools.ietf.org/html/rfc2616#section-10.3.5 + */ + public function setNotModified() + { + $this->setStatusCode(304); + $this->setContent(null); + + // remove headers that MUST NOT be included with 304 Not Modified responses + foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) { + $this->headers->remove($header); + } + + return $this; + } + + /** + * Returns true if the response includes a Vary header. + * + * @return bool true if the response includes a Vary header, false otherwise + */ + public function hasVary() + { + return null !== $this->headers->get('Vary'); + } + + /** + * Returns an array of header names given in the Vary header. + * + * @return array An array of Vary names + */ + public function getVary() + { + if (!$vary = $this->headers->get('Vary', null, false)) { + return array(); + } + + $ret = array(); + foreach ($vary as $item) { + $ret = array_merge($ret, preg_split('/[\s,]+/', $item)); + } + + return $ret; + } + + /** + * Sets the Vary header. + * + * @param string|array $headers + * @param bool $replace Whether to replace the actual value or not (true by default) + * + * @return $this + */ + public function setVary($headers, $replace = true) + { + $this->headers->set('Vary', $headers, $replace); + + return $this; + } + + /** + * Determines if the Response validators (ETag, Last-Modified) match + * a conditional value specified in the Request. + * + * If the Response is not modified, it sets the status code to 304 and + * removes the actual content by calling the setNotModified() method. + * + * @return bool true if the Response validators match the Request, false otherwise + */ + public function isNotModified(Request $request) + { + if (!$request->isMethodCacheable()) { + return false; + } + + $notModified = false; + $lastModified = $this->headers->get('Last-Modified'); + $modifiedSince = $request->headers->get('If-Modified-Since'); + + if ($etags = $request->getETags()) { + $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags); + } + + if ($modifiedSince && $lastModified) { + $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified); + } + + if ($notModified) { + $this->setNotModified(); + } + + return $notModified; + } + + /** + * Is response invalid? + * + * @return bool + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + */ + public function isInvalid() + { + return $this->statusCode < 100 || $this->statusCode >= 600; + } + + /** + * Is response informative? + * + * @return bool + */ + public function isInformational() + { + return $this->statusCode >= 100 && $this->statusCode < 200; + } + + /** + * Is response successful? + * + * @return bool + */ + public function isSuccessful() + { + return $this->statusCode >= 200 && $this->statusCode < 300; + } + + /** + * Is the response a redirect? + * + * @return bool + */ + public function isRedirection() + { + return $this->statusCode >= 300 && $this->statusCode < 400; + } + + /** + * Is there a client error? + * + * @return bool + */ + public function isClientError() + { + return $this->statusCode >= 400 && $this->statusCode < 500; + } + + /** + * Was there a server side error? + * + * @return bool + */ + public function isServerError() + { + return $this->statusCode >= 500 && $this->statusCode < 600; + } + + /** + * Is the response OK? + * + * @return bool + */ + public function isOk() + { + return 200 === $this->statusCode; + } + + /** + * Is the response forbidden? + * + * @return bool + */ + public function isForbidden() + { + return 403 === $this->statusCode; + } + + /** + * Is the response a not found error? + * + * @return bool + */ + public function isNotFound() + { + return 404 === $this->statusCode; + } + + /** + * Is the response a redirect of some form? + * + * @param string $location + * + * @return bool + */ + public function isRedirect($location = null) + { + return \in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location')); + } + + /** + * Is the response empty? + * + * @return bool + */ + public function isEmpty() + { + return \in_array($this->statusCode, array(204, 304)); + } + + /** + * Cleans or flushes output buffers up to target level. + * + * Resulting level can be greater than target level if a non-removable buffer has been encountered. + * + * @param int $targetLevel The target output buffering level + * @param bool $flush Whether to flush or clean the buffers + */ + public static function closeOutputBuffers($targetLevel, $flush) + { + $status = ob_get_status(true); + $level = \count($status); + $flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1; + + while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) { + if ($flush) { + ob_end_flush(); + } else { + ob_end_clean(); + } + } + } + + /** + * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9. + * + * @see http://support.microsoft.com/kb/323308 + */ + protected function ensureIEOverSSLCompatibility(Request $request) + { + if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) { + if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) { + $this->headers->remove('Cache-Control'); + } + } + } +} diff --git a/vendor/symfony/http-foundation/ResponseHeaderBag.php b/vendor/symfony/http-foundation/ResponseHeaderBag.php new file mode 100644 index 0000000..e97eefc --- /dev/null +++ b/vendor/symfony/http-foundation/ResponseHeaderBag.php @@ -0,0 +1,283 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ResponseHeaderBag is a container for Response HTTP headers. + * + * @author Fabien Potencier + */ +class ResponseHeaderBag extends HeaderBag +{ + const COOKIES_FLAT = 'flat'; + const COOKIES_ARRAY = 'array'; + + const DISPOSITION_ATTACHMENT = 'attachment'; + const DISPOSITION_INLINE = 'inline'; + + protected $computedCacheControl = array(); + protected $cookies = array(); + protected $headerNames = array(); + + public function __construct(array $headers = array()) + { + parent::__construct($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + $cookies = ''; + foreach ($this->getCookies() as $cookie) { + $cookies .= 'Set-Cookie: '.$cookie."\r\n"; + } + + ksort($this->headerNames); + + return parent::__toString().$cookies; + } + + /** + * Returns the headers, with original capitalizations. + * + * @return array An array of headers + */ + public function allPreserveCase() + { + return array_combine($this->headerNames, $this->headers); + } + + /** + * {@inheritdoc} + */ + public function replace(array $headers = array()) + { + $this->headerNames = array(); + + parent::replace($headers); + + if (!isset($this->headers['cache-control'])) { + $this->set('Cache-Control', ''); + } + } + + /** + * {@inheritdoc} + */ + public function set($key, $values, $replace = true) + { + parent::set($key, $values, $replace); + + $uniqueKey = str_replace('_', '-', strtolower($key)); + $this->headerNames[$uniqueKey] = $key; + + // ensure the cache-control header has sensible defaults + if (\in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) { + $computed = $this->computeCacheControlValue(); + $this->headers['cache-control'] = array($computed); + $this->headerNames['cache-control'] = 'Cache-Control'; + $this->computedCacheControl = $this->parseCacheControl($computed); + } + } + + /** + * {@inheritdoc} + */ + public function remove($key) + { + parent::remove($key); + + $uniqueKey = str_replace('_', '-', strtolower($key)); + unset($this->headerNames[$uniqueKey]); + + if ('cache-control' === $uniqueKey) { + $this->computedCacheControl = array(); + } + } + + /** + * {@inheritdoc} + */ + public function hasCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl); + } + + /** + * {@inheritdoc} + */ + public function getCacheControlDirective($key) + { + return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; + } + + public function setCookie(Cookie $cookie) + { + $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; + } + + /** + * Removes a cookie from the array, but does not unset it in the browser. + * + * @param string $name + * @param string $path + * @param string $domain + */ + public function removeCookie($name, $path = '/', $domain = null) + { + if (null === $path) { + $path = '/'; + } + + unset($this->cookies[$domain][$path][$name]); + + if (empty($this->cookies[$domain][$path])) { + unset($this->cookies[$domain][$path]); + + if (empty($this->cookies[$domain])) { + unset($this->cookies[$domain]); + } + } + } + + /** + * Returns an array with all cookies. + * + * @param string $format + * + * @return Cookie[] + * + * @throws \InvalidArgumentException When the $format is invalid + */ + public function getCookies($format = self::COOKIES_FLAT) + { + if (!\in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) { + throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY)))); + } + + if (self::COOKIES_ARRAY === $format) { + return $this->cookies; + } + + $flattenedCookies = array(); + foreach ($this->cookies as $path) { + foreach ($path as $cookies) { + foreach ($cookies as $cookie) { + $flattenedCookies[] = $cookie; + } + } + } + + return $flattenedCookies; + } + + /** + * Clears a cookie in the browser. + * + * @param string $name + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httpOnly + */ + public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true) + { + $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly)); + } + + /** + * Generates a HTTP Content-Disposition field-value. + * + * @param string $disposition One of "inline" or "attachment" + * @param string $filename A unicode string + * @param string $filenameFallback A string containing only ASCII characters that + * is semantically equivalent to $filename. If the filename is already ASCII, + * it can be omitted, or just copied from $filename + * + * @return string A string suitable for use as a Content-Disposition field-value + * + * @throws \InvalidArgumentException + * + * @see RFC 6266 + */ + public function makeDisposition($disposition, $filename, $filenameFallback = '') + { + if (!\in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) { + throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); + } + + if ('' == $filenameFallback) { + $filenameFallback = $filename; + } + + // filenameFallback is not ASCII. + if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { + throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); + } + + // percent characters aren't safe in fallback. + if (false !== strpos($filenameFallback, '%')) { + throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); + } + + // path separators aren't allowed in either. + if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) { + throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); + } + + $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback)); + + if ($filename !== $filenameFallback) { + $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename)); + } + + return $output; + } + + /** + * Returns the calculated value of the cache-control header. + * + * This considers several other headers and calculates or modifies the + * cache-control header to a sensible, conservative value. + * + * @return string + */ + protected function computeCacheControlValue() + { + if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { + return 'no-cache'; + } + + if (!$this->cacheControl) { + // conservative by default + return 'private, must-revalidate'; + } + + $header = $this->getCacheControlHeader(); + if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { + return $header; + } + + // public if s-maxage is defined, private otherwise + if (!isset($this->cacheControl['s-maxage'])) { + return $header.', private'; + } + + return $header; + } +} diff --git a/vendor/symfony/http-foundation/ServerBag.php b/vendor/symfony/http-foundation/ServerBag.php new file mode 100644 index 0000000..d8ab561 --- /dev/null +++ b/vendor/symfony/http-foundation/ServerBag.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * ServerBag is a container for HTTP headers from the $_SERVER variable. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Robert Kiss + */ +class ServerBag extends ParameterBag +{ + /** + * Gets the HTTP headers. + * + * @return array + */ + public function getHeaders() + { + $headers = array(); + $contentHeaders = array('CONTENT_LENGTH' => true, 'CONTENT_MD5' => true, 'CONTENT_TYPE' => true); + foreach ($this->parameters as $key => $value) { + if (0 === strpos($key, 'HTTP_')) { + $headers[substr($key, 5)] = $value; + } + // CONTENT_* are not prefixed with HTTP_ + elseif (isset($contentHeaders[$key])) { + $headers[$key] = $value; + } + } + + if (isset($this->parameters['PHP_AUTH_USER'])) { + $headers['PHP_AUTH_USER'] = $this->parameters['PHP_AUTH_USER']; + $headers['PHP_AUTH_PW'] = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : ''; + } else { + /* + * php-cgi under Apache does not pass HTTP Basic user/pass to PHP by default + * For this workaround to work, add these lines to your .htaccess file: + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * + * A sample .htaccess file: + * RewriteEngine On + * RewriteCond %{HTTP:Authorization} ^(.+)$ + * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + * RewriteCond %{REQUEST_FILENAME} !-f + * RewriteRule ^(.*)$ app.php [QSA,L] + */ + + $authorizationHeader = null; + if (isset($this->parameters['HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['HTTP_AUTHORIZATION']; + } elseif (isset($this->parameters['REDIRECT_HTTP_AUTHORIZATION'])) { + $authorizationHeader = $this->parameters['REDIRECT_HTTP_AUTHORIZATION']; + } + + if (null !== $authorizationHeader) { + if (0 === stripos($authorizationHeader, 'basic ')) { + // Decode AUTHORIZATION header into PHP_AUTH_USER and PHP_AUTH_PW when authorization header is basic + $exploded = explode(':', base64_decode(substr($authorizationHeader, 6)), 2); + if (2 == \count($exploded)) { + list($headers['PHP_AUTH_USER'], $headers['PHP_AUTH_PW']) = $exploded; + } + } elseif (empty($this->parameters['PHP_AUTH_DIGEST']) && (0 === stripos($authorizationHeader, 'digest '))) { + // In some circumstances PHP_AUTH_DIGEST needs to be set + $headers['PHP_AUTH_DIGEST'] = $authorizationHeader; + $this->parameters['PHP_AUTH_DIGEST'] = $authorizationHeader; + } elseif (0 === stripos($authorizationHeader, 'bearer ')) { + /* + * XXX: Since there is no PHP_AUTH_BEARER in PHP predefined variables, + * I'll just set $headers['AUTHORIZATION'] here. + * http://php.net/manual/en/reserved.variables.server.php + */ + $headers['AUTHORIZATION'] = $authorizationHeader; + } + } + } + + if (isset($headers['AUTHORIZATION'])) { + return $headers; + } + + // PHP_AUTH_USER/PHP_AUTH_PW + if (isset($headers['PHP_AUTH_USER'])) { + $headers['AUTHORIZATION'] = 'Basic '.base64_encode($headers['PHP_AUTH_USER'].':'.$headers['PHP_AUTH_PW']); + } elseif (isset($headers['PHP_AUTH_DIGEST'])) { + $headers['AUTHORIZATION'] = $headers['PHP_AUTH_DIGEST']; + } + + return $headers; + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php new file mode 100644 index 0000000..fc5fb14 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBag.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class relates to session attribute storage. + */ +class AttributeBag implements AttributeBagInterface, \IteratorAggregate, \Countable +{ + private $name = 'attributes'; + private $storageKey; + + protected $attributes = array(); + + /** + * @param string $storageKey The key used to store attributes in the session + */ + public function __construct($storageKey = '_sf2_attributes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$attributes) + { + $this->attributes = &$attributes; + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->attributes; + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->attributes = array(); + foreach ($attributes as $key => $value) { + $this->set($key, $value); + } + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + if (array_key_exists($name, $this->attributes)) { + $retval = $this->attributes[$name]; + unset($this->attributes[$name]); + } + + return $retval; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $return = $this->attributes; + $this->attributes = array(); + + return $return; + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->attributes); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return \count($this->attributes); + } +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php new file mode 100644 index 0000000..0d8d179 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/AttributeBagInterface.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Attributes store. + * + * @author Drak + */ +interface AttributeBagInterface extends SessionBagInterface +{ + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value or null when it does not exist + */ + public function remove($name); +} diff --git a/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php new file mode 100644 index 0000000..2f1e01a --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Attribute/NamespacedAttributeBag.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Attribute; + +/** + * This class provides structured storage of session attributes using + * a name spacing character in the key. + * + * @author Drak + */ +class NamespacedAttributeBag extends AttributeBag +{ + private $namespaceCharacter; + + /** + * @param string $storageKey Session storage key + * @param string $namespaceCharacter Namespace character to use in keys + */ + public function __construct($storageKey = '_sf2_attributes', $namespaceCharacter = '/') + { + $this->namespaceCharacter = $namespaceCharacter; + parent::__construct($storageKey); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return false; + } + + return array_key_exists($name, $attributes); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + // reference mismatch: if fixed, re-introduced in array_key_exists; keep as it is + $attributes = $this->resolveAttributePath($name); + $name = $this->resolveKey($name); + + if (null === $attributes) { + return $default; + } + + return array_key_exists($name, $attributes) ? $attributes[$name] : $default; + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $attributes = &$this->resolveAttributePath($name, true); + $name = $this->resolveKey($name); + $attributes[$name] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + $retval = null; + $attributes = &$this->resolveAttributePath($name); + $name = $this->resolveKey($name); + if (null !== $attributes && array_key_exists($name, $attributes)) { + $retval = $attributes[$name]; + unset($attributes[$name]); + } + + return $retval; + } + + /** + * Resolves a path in attributes property and returns it as a reference. + * + * This method allows structured namespacing of session attributes. + * + * @param string $name Key name + * @param bool $writeContext Write context, default false + * + * @return array + */ + protected function &resolveAttributePath($name, $writeContext = false) + { + $array = &$this->attributes; + $name = (0 === strpos($name, $this->namespaceCharacter)) ? substr($name, 1) : $name; + + // Check if there is anything to do, else return + if (!$name) { + return $array; + } + + $parts = explode($this->namespaceCharacter, $name); + if (\count($parts) < 2) { + if (!$writeContext) { + return $array; + } + + $array[$parts[0]] = array(); + + return $array; + } + + unset($parts[\count($parts) - 1]); + + foreach ($parts as $part) { + if (null !== $array && !array_key_exists($part, $array)) { + if (!$writeContext) { + $null = null; + + return $null; + } + + $array[$part] = array(); + } + + $array = &$array[$part]; + } + + return $array; + } + + /** + * Resolves the key from the name. + * + * This is the last part in a dot separated string. + * + * @param string $name + * + * @return string + */ + protected function resolveKey($name) + { + if (false !== $pos = strrpos($name, $this->namespaceCharacter)) { + $name = substr($name, $pos + 1); + } + + return $name; + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php new file mode 100644 index 0000000..0ed6600 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/AutoExpireFlashBag.php @@ -0,0 +1,161 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * AutoExpireFlashBag flash message container. + * + * @author Drak + */ +class AutoExpireFlashBag implements FlashBagInterface +{ + private $name = 'flashes'; + private $flashes = array('display' => array(), 'new' => array()); + private $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + + // The logic: messages from the last request will be stored in new, so we move them to previous + // This request we will show what is in 'display'. What is placed into 'new' this time round will + // be moved to display next time round. + $this->flashes['display'] = array_key_exists('new', $this->flashes) ? $this->flashes['new'] : array(); + $this->flashes['new'] = array(); + } + + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->flashes['new'][$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default = array()) + { + return $this->has($type) ? $this->flashes['display'][$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return array_key_exists('display', $this->flashes) ? (array) $this->flashes['display'] : array(); + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + $return = $default; + + if (!$this->has($type)) { + return $return; + } + + if (isset($this->flashes['display'][$type])) { + $return = $this->flashes['display'][$type]; + unset($this->flashes['display'][$type]); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->flashes['display']; + $this->flashes['display'] = array(); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes['new'] = $messages; + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->flashes['new'][$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes['display']) && $this->flashes['display'][$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes['display']); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBag.php b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php new file mode 100644 index 0000000..bc1f8f6 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBag.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +/** + * FlashBag flash message container. + * + * \IteratorAggregate implementation is deprecated and will be removed in 3.0. + * + * @author Drak + */ +class FlashBag implements FlashBagInterface, \IteratorAggregate +{ + private $name = 'flashes'; + private $flashes = array(); + private $storageKey; + + /** + * @param string $storageKey The key used to store flashes in the session + */ + public function __construct($storageKey = '_sf2_flashes') + { + $this->storageKey = $storageKey; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$flashes) + { + $this->flashes = &$flashes; + } + + /** + * {@inheritdoc} + */ + public function add($type, $message) + { + $this->flashes[$type][] = $message; + } + + /** + * {@inheritdoc} + */ + public function peek($type, array $default = array()) + { + return $this->has($type) ? $this->flashes[$type] : $default; + } + + /** + * {@inheritdoc} + */ + public function peekAll() + { + return $this->flashes; + } + + /** + * {@inheritdoc} + */ + public function get($type, array $default = array()) + { + if (!$this->has($type)) { + return $default; + } + + $return = $this->flashes[$type]; + + unset($this->flashes[$type]); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function all() + { + $return = $this->peekAll(); + $this->flashes = array(); + + return $return; + } + + /** + * {@inheritdoc} + */ + public function set($type, $messages) + { + $this->flashes[$type] = (array) $messages; + } + + /** + * {@inheritdoc} + */ + public function setAll(array $messages) + { + $this->flashes = $messages; + } + + /** + * {@inheritdoc} + */ + public function has($type) + { + return array_key_exists($type, $this->flashes) && $this->flashes[$type]; + } + + /** + * {@inheritdoc} + */ + public function keys() + { + return array_keys($this->flashes); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + return $this->all(); + } + + /** + * Returns an iterator for flashes. + * + * @deprecated since version 2.4, to be removed in 3.0. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED); + + return new \ArrayIterator($this->all()); + } +} diff --git a/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php new file mode 100644 index 0000000..f53c9da --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Flash/FlashBagInterface.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Flash; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * FlashBagInterface. + * + * @author Drak + */ +interface FlashBagInterface extends SessionBagInterface +{ + /** + * Adds a flash message for type. + * + * @param string $type + * @param mixed $message + */ + public function add($type, $message); + + /** + * Registers a message for a given type. + * + * @param string $type + * @param string|array $message + */ + public function set($type, $message); + + /** + * Gets flash messages for a given type. + * + * @param string $type Message category type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function peek($type, array $default = array()); + + /** + * Gets all flash messages. + * + * @return array + */ + public function peekAll(); + + /** + * Gets and clears flash from the stack. + * + * @param string $type + * @param array $default Default value if $type does not exist + * + * @return array + */ + public function get($type, array $default = array()); + + /** + * Gets and clears flashes from the stack. + * + * @return array + */ + public function all(); + + /** + * Sets all flash messages. + */ + public function setAll(array $messages); + + /** + * Has flash messages for a given type? + * + * @param string $type + * + * @return bool + */ + public function has($type); + + /** + * Returns a list of all defined types. + * + * @return array + */ + public function keys(); +} diff --git a/vendor/symfony/http-foundation/Session/Session.php b/vendor/symfony/http-foundation/Session/Session.php new file mode 100644 index 0000000..8293cf2 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Session.php @@ -0,0 +1,235 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; +use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; +use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; + +/** + * @author Fabien Potencier + * @author Drak + */ +class Session implements SessionInterface, \IteratorAggregate, \Countable +{ + protected $storage; + + private $flashName; + private $attributeName; + + /** + * @param SessionStorageInterface $storage A SessionStorageInterface instance + * @param AttributeBagInterface $attributes An AttributeBagInterface instance, (defaults null for default AttributeBag) + * @param FlashBagInterface $flashes A FlashBagInterface instance (defaults null for default FlashBag) + */ + public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null) + { + $this->storage = $storage ?: new NativeSessionStorage(); + + $attributes = $attributes ?: new AttributeBag(); + $this->attributeName = $attributes->getName(); + $this->registerBag($attributes); + + $flashes = $flashes ?: new FlashBag(); + $this->flashName = $flashes->getName(); + $this->registerBag($flashes); + } + + /** + * {@inheritdoc} + */ + public function start() + { + return $this->storage->start(); + } + + /** + * {@inheritdoc} + */ + public function has($name) + { + return $this->storage->getBag($this->attributeName)->has($name); + } + + /** + * {@inheritdoc} + */ + public function get($name, $default = null) + { + return $this->storage->getBag($this->attributeName)->get($name, $default); + } + + /** + * {@inheritdoc} + */ + public function set($name, $value) + { + $this->storage->getBag($this->attributeName)->set($name, $value); + } + + /** + * {@inheritdoc} + */ + public function all() + { + return $this->storage->getBag($this->attributeName)->all(); + } + + /** + * {@inheritdoc} + */ + public function replace(array $attributes) + { + $this->storage->getBag($this->attributeName)->replace($attributes); + } + + /** + * {@inheritdoc} + */ + public function remove($name) + { + return $this->storage->getBag($this->attributeName)->remove($name); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + $this->storage->getBag($this->attributeName)->clear(); + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->storage->isStarted(); + } + + /** + * Returns an iterator for attributes. + * + * @return \ArrayIterator An \ArrayIterator instance + */ + public function getIterator() + { + return new \ArrayIterator($this->storage->getBag($this->attributeName)->all()); + } + + /** + * Returns the number of attributes. + * + * @return int The number of attributes + */ + public function count() + { + return \count($this->storage->getBag($this->attributeName)->all()); + } + + /** + * {@inheritdoc} + */ + public function invalidate($lifetime = null) + { + $this->storage->clear(); + + return $this->migrate(true, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function migrate($destroy = false, $lifetime = null) + { + return $this->storage->regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $this->storage->save(); + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->storage->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + if ($this->storage->getId() !== $id) { + $this->storage->setId($id); + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->storage->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->storage->setName($name); + } + + /** + * {@inheritdoc} + */ + public function getMetadataBag() + { + return $this->storage->getMetadataBag(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->storage->registerBag($bag); + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + return $this->storage->getBag($name); + } + + /** + * Gets the flashbag interface. + * + * @return FlashBagInterface + */ + public function getFlashBag() + { + return $this->getBag($this->flashName); + } +} diff --git a/vendor/symfony/http-foundation/Session/SessionBagInterface.php b/vendor/symfony/http-foundation/Session/SessionBagInterface.php new file mode 100644 index 0000000..8e37d06 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionBagInterface.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * Session Bag store. + * + * @author Drak + */ +interface SessionBagInterface +{ + /** + * Gets this bag's name. + * + * @return string + */ + public function getName(); + + /** + * Initializes the Bag. + */ + public function initialize(array &$array); + + /** + * Gets the storage key for this bag. + * + * @return string + */ + public function getStorageKey(); + + /** + * Clears out data from bag. + * + * @return mixed Whatever data was contained + */ + public function clear(); +} diff --git a/vendor/symfony/http-foundation/Session/SessionInterface.php b/vendor/symfony/http-foundation/Session/SessionInterface.php new file mode 100644 index 0000000..95fca85 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/SessionInterface.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +use Symfony\Component\HttpFoundation\Session\Storage\MetadataBag; + +/** + * Interface for the session. + * + * @author Drak + */ +interface SessionInterface +{ + /** + * Starts the session storage. + * + * @return bool True if session started + * + * @throws \RuntimeException if session fails to start + */ + public function start(); + + /** + * Returns the session ID. + * + * @return string The session ID + */ + public function getId(); + + /** + * Sets the session ID. + * + * @param string $id + */ + public function setId($id); + + /** + * Returns the session name. + * + * @return mixed The session name + */ + public function getName(); + + /** + * Sets the session name. + * + * @param string $name + */ + public function setName($name); + + /** + * Invalidates the current session. + * + * Clears all session attributes and flashes and regenerates the + * session and deletes the old session from persistence. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session invalidated, false if error + */ + public function invalidate($lifetime = null); + + /** + * Migrates the current session to a new session id while maintaining all + * session attributes. + * + * @param bool $destroy Whether to delete the old session or leave it to garbage collection + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session migrated, false if error + */ + public function migrate($destroy = false, $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method is generally not required for real sessions as + * the session will be automatically saved at the end of + * code execution. + */ + public function save(); + + /** + * Checks if an attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function has($name); + + /** + * Returns an attribute. + * + * @param string $name The attribute name + * @param mixed $default The default value if not found + * + * @return mixed + */ + public function get($name, $default = null); + + /** + * Sets an attribute. + * + * @param string $name + * @param mixed $value + */ + public function set($name, $value); + + /** + * Returns attributes. + * + * @return array Attributes + */ + public function all(); + + /** + * Sets attributes. + * + * @param array $attributes Attributes + */ + public function replace(array $attributes); + + /** + * Removes an attribute. + * + * @param string $name + * + * @return mixed The removed value or null when it does not exist + */ + public function remove($name); + + /** + * Clears all attributes. + */ + public function clear(); + + /** + * Checks if the session was started. + * + * @return bool + */ + public function isStarted(); + + /** + * Registers a SessionBagInterface with the session. + */ + public function registerBag(SessionBagInterface $bag); + + /** + * Gets a bag instance by name. + * + * @param string $name + * + * @return SessionBagInterface + */ + public function getBag($name); + + /** + * Gets session meta. + * + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/LegacyPdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/LegacyPdoSessionHandler.php new file mode 100644 index 0000000..ea11d60 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/LegacyPdoSessionHandler.php @@ -0,0 +1,268 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +@trigger_error('The '.__NAMESPACE__.'\LegacyPdoSessionHandler class is deprecated since Symfony 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler class instead.', E_USER_DEPRECATED); + +/** + * Session handler using a PDO connection to read and write data. + * + * Session data is a binary string that can contain non-printable characters like the null byte. + * For this reason this handler base64 encodes the data to be able to save it in a character column. + * + * This version of the PdoSessionHandler does NOT implement locking. So concurrent requests to the + * same session can result in data loss due to race conditions. + * + * @author Fabien Potencier + * @author Michael Williams + * @author Tobias Schultze + * + * @deprecated since version 2.6, to be removed in 3.0. Use + * {@link PdoSessionHandler} instead. + */ +class LegacyPdoSessionHandler implements \SessionHandlerInterface +{ + private $pdo; + + /** + * @var string Table name + */ + private $table; + + /** + * @var string Column for session id + */ + private $idCol; + + /** + * @var string Column for session data + */ + private $dataCol; + + /** + * @var string Column for timestamp + */ + private $timeCol; + + /** + * Constructor. + * + * List of available options: + * * db_table: The name of the table [required] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_time_col: The column where to store the timestamp [default: sess_time] + * + * @param \PDO $pdo A \PDO instance + * @param array $dbOptions An associative array of DB options + * + * @throws \InvalidArgumentException When "db_table" option is not provided + */ + public function __construct(\PDO $pdo, array $dbOptions = array()) + { + if (!array_key_exists('db_table', $dbOptions)) { + throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.'); + } + if (\PDO::ERRMODE_EXCEPTION !== $pdo->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__)); + } + + $this->pdo = $pdo; + $dbOptions = array_merge(array( + 'db_id_col' => 'sess_id', + 'db_data_col' => 'sess_data', + 'db_time_col' => 'sess_time', + ), $dbOptions); + + $this->table = $dbOptions['db_table']; + $this->idCol = $dbOptions['db_id_col']; + $this->dataCol = $dbOptions['db_data_col']; + $this->timeCol = $dbOptions['db_time_col']; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + // delete the record associated with this id + $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete a session: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // delete the session records that have expired + $sql = "DELETE FROM $this->table WHERE $this->timeCol < :time"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time() - $maxlifetime, \PDO::PARAM_INT); + $stmt->execute(); + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to delete expired sessions: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $sql = "SELECT $this->dataCol FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + + // We use fetchAll instead of fetchColumn to make sure the DB cursor gets closed + $sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM); + + if ($sessionRows) { + return base64_decode($sessionRows[0][0]); + } + + return ''; + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to read the session data: %s', $e->getMessage()), 0, $e); + } + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $encoded = base64_encode($data); + + try { + // We use a single MERGE SQL query when supported by the database. + $mergeSql = $this->getMergeSql(); + + if (null !== $mergeSql) { + $mergeStmt = $this->pdo->prepare($mergeSql); + $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(':data', $encoded, \PDO::PARAM_STR); + $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $mergeStmt->execute(); + + return true; + } + + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->dataCol = :data, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindParam(':data', $encoded, \PDO::PARAM_STR); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + + // When MERGE is not supported, like in Postgres, we have to use this approach that can result in + // duplicate key errors when the same session is written simultaneously. We can just catch such an + // error and re-execute the update. This is similar to a serializable transaction with retry logic + // on serialization failures but without the overhead and without possible false positives due to + // longer gap locking. + if (!$updateStmt->rowCount()) { + try { + $insertStmt = $this->pdo->prepare( + "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)" + ); + $insertStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt->bindParam(':data', $encoded, \PDO::PARAM_STR); + $insertStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys + if (0 === strpos($e->getCode(), '23')) { + $updateStmt->execute(); + } else { + throw $e; + } + } + } + } catch (\PDOException $e) { + throw new \RuntimeException(sprintf('PDOException was thrown when trying to write the session data: %s', $e->getMessage()), 0, $e); + } + + return true; + } + + /** + * Returns a merge/upsert (i.e. insert or update) SQL query when supported by the database. + * + * @return string|null The SQL string or null when not supported + */ + private function getMergeSql() + { + $driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + + switch ($driver) { + case 'mysql': + return "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". + "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->timeCol = VALUES($this->timeCol)"; + case 'oci': + // DUAL is Oracle specific dummy table + return "MERGE INTO $this->table USING DUAL ON ($this->idCol = :id) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time"; + case 'sqlsrv' === $driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + return "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = :id) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = :data, $this->timeCol = :time;"; + case 'sqlite': + return "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->timeCol) VALUES (:id, :data, :time)"; + } + } + + /** + * Return a PDO instance. + * + * @return \PDO + */ + protected function getConnection() + { + return $this->pdo; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php new file mode 100644 index 0000000..ad54b6c --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcacheSessionHandler.php @@ -0,0 +1,114 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * @author Drak + */ +class MemcacheSessionHandler implements \SessionHandlerInterface +{ + private $memcache; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcache keys in order to avoid collision + * * expiretime: The time to live in seconds + * + * @param \Memcache $memcache A \Memcache instance + * @param array $options An associative array of Memcache options + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcache $memcache, array $options = array()) + { + if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff))); + } + + $this->memcache = $memcache; + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return $this->memcache->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return $this->memcache->set($this->prefix.$sessionId, $data, 0, time() + $this->ttl); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $this->memcache->delete($this->prefix.$sessionId); + + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // not required here because memcache will auto expire the records anyhow. + return true; + } + + /** + * Return a Memcache instance. + * + * @return \Memcache + */ + protected function getMemcache() + { + return $this->memcache; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php new file mode 100644 index 0000000..1191a41 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Memcached based session storage handler based on the Memcached class + * provided by the PHP memcached extension. + * + * @see http://php.net/memcached + * + * @author Drak + */ +class MemcachedSessionHandler implements \SessionHandlerInterface +{ + private $memcached; + + /** + * @var int Time to live in seconds + */ + private $ttl; + + /** + * @var string Key prefix for shared environments + */ + private $prefix; + + /** + * Constructor. + * + * List of available options: + * * prefix: The prefix to use for the memcached keys in order to avoid collision + * * expiretime: The time to live in seconds + * + * @param \Memcached $memcached A \Memcached instance + * @param array $options An associative array of Memcached options + * + * @throws \InvalidArgumentException When unsupported options are passed + */ + public function __construct(\Memcached $memcached, array $options = array()) + { + $this->memcached = $memcached; + + if ($diff = array_diff(array_keys($options), array('prefix', 'expiretime'))) { + throw new \InvalidArgumentException(sprintf('The following options are not supported "%s"', implode(', ', $diff))); + } + + $this->ttl = isset($options['expiretime']) ? (int) $options['expiretime'] : 86400; + $this->prefix = isset($options['prefix']) ? $options['prefix'] : 'sf2s'; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return $this->memcached->get($this->prefix.$sessionId) ?: ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $result = $this->memcached->delete($this->prefix.$sessionId); + + return $result || \Memcached::RES_NOTFOUND == $this->memcached->getResultCode(); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // not required here because memcached will auto expire the records anyhow. + return true; + } + + /** + * Return a Memcached instance. + * + * @return \Memcached + */ + protected function getMemcached() + { + return $this->memcached; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php new file mode 100644 index 0000000..0d8e2dd --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * @author Markus Bachmann + */ +class MongoDbSessionHandler implements \SessionHandlerInterface +{ + private $mongo; + + /** + * @var \MongoCollection + */ + private $collection; + + /** + * @var array + */ + private $options; + + /** + * Constructor. + * + * List of available options: + * * database: The name of the database [required] + * * collection: The name of the collection [required] + * * id_field: The field name for storing the session id [default: _id] + * * data_field: The field name for storing the session data [default: data] + * * time_field: The field name for storing the timestamp [default: time] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at] + * + * It is strongly recommended to put an index on the `expiry_field` for + * garbage-collection. Alternatively it's possible to automatically expire + * the sessions in the database as described below: + * + * A TTL collections can be used on MongoDB 2.2+ to cleanup expired sessions + * automatically. Such an index can for example look like this: + * + * db..ensureIndex( + * { "": 1 }, + * { "expireAfterSeconds": 0 } + * ) + * + * More details on: http://docs.mongodb.org/manual/tutorial/expire-data/ + * + * If you use such an index, you can drop `gc_probability` to 0 since + * no garbage-collection is required. + * + * @param \Mongo|\MongoClient|\MongoDB\Client $mongo A MongoDB\Client, MongoClient or Mongo instance + * @param array $options An associative array of field options + * + * @throws \InvalidArgumentException When MongoClient or Mongo instance not provided + * @throws \InvalidArgumentException When "database" or "collection" not provided + */ + public function __construct($mongo, array $options) + { + if (!($mongo instanceof \MongoDB\Client || $mongo instanceof \MongoClient || $mongo instanceof \Mongo)) { + throw new \InvalidArgumentException('MongoClient or Mongo instance required'); + } + + if (!isset($options['database']) || !isset($options['collection'])) { + throw new \InvalidArgumentException('You must provide the "database" and "collection" option for MongoDBSessionHandler'); + } + + $this->mongo = $mongo; + + $this->options = array_merge(array( + 'id_field' => '_id', + 'data_field' => 'data', + 'time_field' => 'time', + 'expiry_field' => 'expires_at', + ), $options); + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; + + $this->getCollection()->$methodName(array( + $this->options['id_field'] => $sessionId, + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteMany' : 'remove'; + + $this->getCollection()->$methodName(array( + $this->options['expiry_field'] => array('$lt' => $this->createDateTime()), + )); + + return true; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); + + $fields = array( + $this->options['time_field'] => $this->createDateTime(), + $this->options['expiry_field'] => $expiry, + ); + + $options = array('upsert' => true); + + if ($this->mongo instanceof \MongoDB\Client) { + $fields[$this->options['data_field']] = new \MongoDB\BSON\Binary($data, \MongoDB\BSON\Binary::TYPE_OLD_BINARY); + } else { + $fields[$this->options['data_field']] = new \MongoBinData($data, \MongoBinData::BYTE_ARRAY); + $options['multiple'] = false; + } + + $methodName = $this->mongo instanceof \MongoDB\Client ? 'updateOne' : 'update'; + + $this->getCollection()->$methodName( + array($this->options['id_field'] => $sessionId), + array('$set' => $fields), + $options + ); + + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $dbData = $this->getCollection()->findOne(array( + $this->options['id_field'] => $sessionId, + $this->options['expiry_field'] => array('$gte' => $this->createDateTime()), + )); + + if (null === $dbData) { + return ''; + } + + if ($dbData[$this->options['data_field']] instanceof \MongoDB\BSON\Binary) { + return $dbData[$this->options['data_field']]->getData(); + } + + return $dbData[$this->options['data_field']]->bin; + } + + /** + * Return a "MongoCollection" instance. + * + * @return \MongoCollection + */ + private function getCollection() + { + if (null === $this->collection) { + $this->collection = $this->mongo->selectCollection($this->options['database'], $this->options['collection']); + } + + return $this->collection; + } + + /** + * Return a Mongo instance. + * + * @return \Mongo|\MongoClient|\MongoDB\Client + */ + protected function getMongo() + { + return $this->mongo; + } + + /** + * Create a date object using the class appropriate for the current mongo connection. + * + * Return an instance of a MongoDate or \MongoDB\BSON\UTCDateTime + * + * @param int $seconds An integer representing UTC seconds since Jan 1 1970. Defaults to now. + * + * @return \MongoDate|\MongoDB\BSON\UTCDateTime + */ + private function createDateTime($seconds = null) + { + if (null === $seconds) { + $seconds = time(); + } + + if ($this->mongo instanceof \MongoDB\Client) { + return new \MongoDB\BSON\UTCDateTime($seconds * 1000); + } + + return new \MongoDate($seconds); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php new file mode 100644 index 0000000..d6ad937 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeFileSessionHandler.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Native session handler using PHP's built in file storage. + * + * @author Drak + */ +class NativeFileSessionHandler extends NativeSessionHandler +{ + /** + * @param string $savePath Path of directory to save session files + * Default null will leave setting as defined by PHP. + * '/path', 'N;/path', or 'N;octal-mode;/path + * + * @see http://php.net/session.configuration.php#ini.session.save-path for further details. + * + * @throws \InvalidArgumentException On invalid $savePath + */ + public function __construct($savePath = null) + { + if (null === $savePath) { + $savePath = ini_get('session.save_path'); + } + + $baseDir = $savePath; + + if ($count = substr_count($savePath, ';')) { + if ($count > 2) { + throw new \InvalidArgumentException(sprintf('Invalid argument $savePath \'%s\'', $savePath)); + } + + // characters after last ';' are the path + $baseDir = ltrim(strrchr($savePath, ';'), ';'); + } + + if ($baseDir && !is_dir($baseDir) && !@mkdir($baseDir, 0777, true) && !is_dir($baseDir)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $baseDir)); + } + + ini_set('session.save_path', $savePath); + ini_set('session.save_handler', 'files'); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php new file mode 100644 index 0000000..03acbd9 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NativeSessionHandler.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +// Adds SessionHandler functionality if available. +// @see http://php.net/sessionhandler +if (\PHP_VERSION_ID >= 50400) { + class NativeSessionHandler extends \SessionHandler + { + } +} else { + class NativeSessionHandler + { + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php new file mode 100644 index 0000000..1516d43 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/NullSessionHandler.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * NullSessionHandler. + * + * Can be used in unit testing or in a situations where persisted sessions are not desired. + * + * @author Drak + */ +class NullSessionHandler implements \SessionHandlerInterface +{ + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return ''; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return true; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php new file mode 100644 index 0000000..aed6903 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php @@ -0,0 +1,794 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Session handler using a PDO connection to read and write data. + * + * It works with MySQL, PostgreSQL, Oracle, SQL Server and SQLite and implements + * different locking strategies to handle concurrent access to the same session. + * Locking is necessary to prevent loss of data due to race conditions and to keep + * the session data consistent between read() and write(). With locking, requests + * for the same session will wait until the other one finished writing. For this + * reason it's best practice to close a session as early as possible to improve + * concurrency. PHPs internal files session handler also implements locking. + * + * Attention: Since SQLite does not support row level locks but locks the whole database, + * it means only one session can be accessed at a time. Even different sessions would wait + * for another to finish. So saving session in SQLite should only be considered for + * development or prototypes. + * + * Session data is a binary string that can contain non-printable characters like the null byte. + * For this reason it must be saved in a binary column in the database like BLOB in MySQL. + * Saving it in a character column could corrupt the data. You can use createTable() + * to initialize a correctly defined table. + * + * @see http://php.net/sessionhandlerinterface + * + * @author Fabien Potencier + * @author Michael Williams + * @author Tobias Schultze + */ +class PdoSessionHandler implements \SessionHandlerInterface +{ + /** + * No locking is done. This means sessions are prone to loss of data due to + * race conditions of concurrent requests to the same session. The last session + * write will win in this case. It might be useful when you implement your own + * logic to deal with this like an optimistic approach. + */ + const LOCK_NONE = 0; + + /** + * Creates an application-level lock on a session. The disadvantage is that the + * lock is not enforced by the database and thus other, unaware parts of the + * application could still concurrently modify the session. The advantage is it + * does not require a transaction. + * This mode is not available for SQLite and not yet implemented for oci and sqlsrv. + */ + const LOCK_ADVISORY = 1; + + /** + * Issues a real row lock. Since it uses a transaction between opening and + * closing a session, you have to be careful when you use same database connection + * that you also use for your application logic. This mode is the default because + * it's the only reliable solution across DBMSs. + */ + const LOCK_TRANSACTIONAL = 2; + + /** + * @var \PDO|null PDO instance or null when not connected yet + */ + private $pdo; + + /** + * @var string|false|null DSN string or null for session.save_path or false when lazy connection disabled + */ + private $dsn = false; + + /** + * @var string Database driver + */ + private $driver; + + /** + * @var string Table name + */ + private $table = 'sessions'; + + /** + * @var string Column for session id + */ + private $idCol = 'sess_id'; + + /** + * @var string Column for session data + */ + private $dataCol = 'sess_data'; + + /** + * @var string Column for lifetime + */ + private $lifetimeCol = 'sess_lifetime'; + + /** + * @var string Column for timestamp + */ + private $timeCol = 'sess_time'; + + /** + * @var string Username when lazy-connect + */ + private $username = ''; + + /** + * @var string Password when lazy-connect + */ + private $password = ''; + + /** + * @var array Connection options when lazy-connect + */ + private $connectionOptions = array(); + + /** + * @var int The strategy for locking, see constants + */ + private $lockMode = self::LOCK_TRANSACTIONAL; + + /** + * It's an array to support multiple reads before closing which is manual, non-standard usage. + * + * @var \PDOStatement[] An array of statements to release advisory locks + */ + private $unlockStatements = array(); + + /** + * @var bool True when the current session exists but expired according to session.gc_maxlifetime + */ + private $sessionExpired = false; + + /** + * @var bool Whether a transaction is active + */ + private $inTransaction = false; + + /** + * @var bool Whether gc() has been called + */ + private $gcCalled = false; + + /** + * You can either pass an existing database connection as PDO instance or + * pass a DSN string that will be used to lazy-connect to the database + * when the session is actually used. Furthermore it's possible to pass null + * which will then use the session.save_path ini setting as PDO DSN parameter. + * + * List of available options: + * * db_table: The name of the table [default: sessions] + * * db_id_col: The column where to store the session id [default: sess_id] + * * db_data_col: The column where to store the session data [default: sess_data] + * * db_lifetime_col: The column where to store the lifetime [default: sess_lifetime] + * * db_time_col: The column where to store the timestamp [default: sess_time] + * * db_username: The username when lazy-connect [default: ''] + * * db_password: The password when lazy-connect [default: ''] + * * db_connection_options: An array of driver-specific connection options [default: array()] + * * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL] + * + * @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or null + * @param array $options An associative array of options + * + * @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION + */ + public function __construct($pdoOrDsn = null, array $options = array()) + { + if ($pdoOrDsn instanceof \PDO) { + if (\PDO::ERRMODE_EXCEPTION !== $pdoOrDsn->getAttribute(\PDO::ATTR_ERRMODE)) { + throw new \InvalidArgumentException(sprintf('"%s" requires PDO error mode attribute be set to throw Exceptions (i.e. $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION))', __CLASS__)); + } + + $this->pdo = $pdoOrDsn; + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } else { + $this->dsn = $pdoOrDsn; + } + + $this->table = isset($options['db_table']) ? $options['db_table'] : $this->table; + $this->idCol = isset($options['db_id_col']) ? $options['db_id_col'] : $this->idCol; + $this->dataCol = isset($options['db_data_col']) ? $options['db_data_col'] : $this->dataCol; + $this->lifetimeCol = isset($options['db_lifetime_col']) ? $options['db_lifetime_col'] : $this->lifetimeCol; + $this->timeCol = isset($options['db_time_col']) ? $options['db_time_col'] : $this->timeCol; + $this->username = isset($options['db_username']) ? $options['db_username'] : $this->username; + $this->password = isset($options['db_password']) ? $options['db_password'] : $this->password; + $this->connectionOptions = isset($options['db_connection_options']) ? $options['db_connection_options'] : $this->connectionOptions; + $this->lockMode = isset($options['lock_mode']) ? $options['lock_mode'] : $this->lockMode; + } + + /** + * Creates the table to store sessions which can be called once for setup. + * + * Session ID is saved in a column of maximum length 128 because that is enough even + * for a 512 bit configured session.hash_function like Whirlpool. Session data is + * saved in a BLOB. One could also use a shorter inlined varbinary column + * if one was sure the data fits into it. + * + * @throws \PDOException When the table already exists + * @throws \DomainException When an unsupported PDO driver is used + */ + public function createTable() + { + // connect if we are not yet + $this->getConnection(); + + switch ($this->driver) { + case 'mysql': + // We use varbinary for the ID column because it prevents unwanted conversions: + // - character set conversions between server and client + // - trailing space removal + // - case-insensitivity + // - language processing like é == e + $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol MEDIUMINT NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB"; + break; + case 'sqlite': + $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'pgsql': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'oci': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + case 'sqlsrv': + $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)"; + break; + default: + throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)); + } + + try { + $this->pdo->exec($sql); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * Returns true when the current session exists but expired according to session.gc_maxlifetime. + * + * Can be used to distinguish between a new session and one that expired due to inactivity. + * + * @return bool Whether current session expired + */ + public function isSessionExpired() + { + return $this->sessionExpired; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: $savePath); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + try { + return $this->doRead($sessionId); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + // We delay gc() to close() so that it is executed outside the transactional and blocking read-write process. + // This way, pruning expired sessions does not block them from being started while the current session is used. + $this->gcCalled = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + // delete the record associated with this id + $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; + + try { + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + + try { + // We use a single MERGE SQL query when supported by the database. + $mergeStmt = $this->getMergeStatement($sessionId, $data, $maxlifetime); + if (null !== $mergeStmt) { + $mergeStmt->execute(); + + return true; + } + + $updateStmt = $this->getUpdateStatement($sessionId, $data, $maxlifetime); + $updateStmt->execute(); + + // When MERGE is not supported, like in Postgres < 9.5, we have to use this approach that can result in + // duplicate key errors when the same session is written simultaneously (given the LOCK_NONE behavior). + // We can just catch such an error and re-execute the update. This is similar to a serializable + // transaction with retry logic on serialization failures but without the overhead and without possible + // false positives due to longer gap locking. + if (!$updateStmt->rowCount()) { + try { + $insertStmt = $this->getInsertStatement($sessionId, $data, $maxlifetime); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Handle integrity violation SQLSTATE 23000 (or a subclass like 23505 in Postgres) for duplicate keys + if (0 === strpos($e->getCode(), '23')) { + $updateStmt->execute(); + } else { + throw $e; + } + } + } + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->commit(); + + while ($unlockStmt = array_shift($this->unlockStatements)) { + $unlockStmt->execute(); + } + + if ($this->gcCalled) { + $this->gcCalled = false; + + // delete the session records that have expired + if ('mysql' === $this->driver) { + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol + $this->timeCol < :time"; + } else { + $sql = "DELETE FROM $this->table WHERE $this->lifetimeCol < :time - $this->timeCol"; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + $stmt->execute(); + } + + if (false !== $this->dsn) { + $this->pdo = null; // only close lazy-connection + } + + return true; + } + + /** + * Lazy-connects to the database. + * + * @param string $dsn DSN string + */ + private function connect($dsn) + { + $this->pdo = new \PDO($dsn, $this->username, $this->password, $this->connectionOptions); + $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + $this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); + } + + /** + * Helper method to begin a transaction. + * + * Since SQLite does not support row level locks, we have to acquire a reserved lock + * on the database immediately. Because of https://bugs.php.net/42766 we have to create + * such a transaction manually which also means we cannot use PDO::commit or + * PDO::rollback or PDO::inTransaction for SQLite. + * + * Also MySQLs default isolation, REPEATABLE READ, causes deadlock for different sessions + * due to http://www.mysqlperformanceblog.com/2013/12/12/one-more-innodb-gap-lock-to-avoid/ . + * So we change it to READ COMMITTED. + */ + private function beginTransaction() + { + if (!$this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('BEGIN IMMEDIATE TRANSACTION'); + } else { + if ('mysql' === $this->driver) { + $this->pdo->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED'); + } + $this->pdo->beginTransaction(); + } + $this->inTransaction = true; + } + } + + /** + * Helper method to commit a transaction. + */ + private function commit() + { + if ($this->inTransaction) { + try { + // commit read-write transaction which also releases the lock + if ('sqlite' === $this->driver) { + $this->pdo->exec('COMMIT'); + } else { + $this->pdo->commit(); + } + $this->inTransaction = false; + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + } + } + + /** + * Helper method to rollback a transaction. + */ + private function rollback() + { + // We only need to rollback if we are in a transaction. Otherwise the resulting + // error would hide the real problem why rollback was called. We might not be + // in a transaction when not using the transactional locking behavior or when + // two callbacks (e.g. destroy and write) are invoked that both fail. + if ($this->inTransaction) { + if ('sqlite' === $this->driver) { + $this->pdo->exec('ROLLBACK'); + } else { + $this->pdo->rollBack(); + } + $this->inTransaction = false; + } + } + + /** + * Reads the session data in respect to the different locking strategies. + * + * We need to make sure we do not return session data that is already considered garbage according + * to the session.gc_maxlifetime setting because gc() is called after read() and only sometimes. + * + * @param string $sessionId Session ID + * + * @return string The session data + */ + private function doRead($sessionId) + { + $this->sessionExpired = false; + + if (self::LOCK_ADVISORY === $this->lockMode) { + $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); + } + + $selectSql = $this->getSelectSql(); + $selectStmt = $this->pdo->prepare($selectSql); + $selectStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $insertStmt = null; + + do { + $selectStmt->execute(); + $sessionRows = $selectStmt->fetchAll(\PDO::FETCH_NUM); + + if ($sessionRows) { + if ($sessionRows[0][1] + $sessionRows[0][2] < time()) { + $this->sessionExpired = true; + + return ''; + } + + return \is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; + } + + if (null !== $insertStmt) { + $this->rollback(); + throw new \RuntimeException('Failed to read session: INSERT reported a duplicate id but next SELECT did not return any data.'); + } + + if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block + // until other connections to the session are committed. + try { + $insertStmt = $this->getInsertStatement($sessionId, '', 0); + $insertStmt->execute(); + } catch (\PDOException $e) { + // Catch duplicate key error because other connection created the session already. + // It would only not be the case when the other connection destroyed the session. + if (0 === strpos($e->getCode(), '23')) { + // Retrieve finished session data written by concurrent connection by restarting the loop. + // We have to start a new transaction as a failed query will mark the current transaction as + // aborted in PostgreSQL and disallow further queries within it. + $this->rollback(); + $this->beginTransaction(); + continue; + } + + throw $e; + } + } + + return ''; + } while (true); + } + + /** + * Executes an application-level lock on the database. + * + * @param string $sessionId Session ID + * + * @return \PDOStatement The statement that needs to be executed later to release the lock + * + * @throws \DomainException When an unsupported PDO driver is used + * + * @todo implement missing advisory locks + * - for oci using DBMS_LOCK.REQUEST + * - for sqlsrv using sp_getapplock with LockOwner = Session + */ + private function doAdvisoryLock($sessionId) + { + switch ($this->driver) { + case 'mysql': + // MySQL 5.7.5 and later enforces a maximum length on lock names of 64 characters. Previously, no limit was enforced. + $lockId = \substr($sessionId, 0, 64); + // should we handle the return value? 0 on timeout, null on error + // we use a timeout of 50 seconds which is also the default for innodb_lock_wait_timeout + $stmt = $this->pdo->prepare('SELECT GET_LOCK(:key, 50)'); + $stmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('DO RELEASE_LOCK(:key)'); + $releaseStmt->bindValue(':key', $lockId, \PDO::PARAM_STR); + + return $releaseStmt; + case 'pgsql': + // Obtaining an exclusive session level advisory lock requires an integer key. + // When session.sid_bits_per_character > 4, the session id can contain non-hex-characters. + // So we cannot just use hexdec(). + if (4 === \PHP_INT_SIZE) { + $sessionInt1 = $this->convertStringToInt($sessionId); + $sessionInt2 = $this->convertStringToInt(substr($sessionId, 4, 4)); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key1, :key2)'); + $stmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $stmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key1, :key2)'); + $releaseStmt->bindValue(':key1', $sessionInt1, \PDO::PARAM_INT); + $releaseStmt->bindValue(':key2', $sessionInt2, \PDO::PARAM_INT); + } else { + $sessionBigInt = $this->convertStringToInt($sessionId); + + $stmt = $this->pdo->prepare('SELECT pg_advisory_lock(:key)'); + $stmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + $stmt->execute(); + + $releaseStmt = $this->pdo->prepare('SELECT pg_advisory_unlock(:key)'); + $releaseStmt->bindValue(':key', $sessionBigInt, \PDO::PARAM_INT); + } + + return $releaseStmt; + case 'sqlite': + throw new \DomainException('SQLite does not support advisory locks.'); + default: + throw new \DomainException(sprintf('Advisory locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + /** + * Encodes the first 4 (when PHP_INT_SIZE == 4) or 8 characters of the string as an integer. + * + * Keep in mind, PHP integers are signed. + * + * @param string $string + * + * @return int + */ + private function convertStringToInt($string) + { + if (4 === \PHP_INT_SIZE) { + return (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + } + + $int1 = (\ord($string[7]) << 24) + (\ord($string[6]) << 16) + (\ord($string[5]) << 8) + \ord($string[4]); + $int2 = (\ord($string[3]) << 24) + (\ord($string[2]) << 16) + (\ord($string[1]) << 8) + \ord($string[0]); + + return $int2 + ($int1 << 32); + } + + /** + * Return a locking or nonlocking SQL query to read session information. + * + * @return string The SQL string + * + * @throws \DomainException When an unsupported PDO driver is used + */ + private function getSelectSql() + { + if (self::LOCK_TRANSACTIONAL === $this->lockMode) { + $this->beginTransaction(); + + switch ($this->driver) { + case 'mysql': + case 'oci': + case 'pgsql': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id FOR UPDATE"; + case 'sqlsrv': + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WITH (UPDLOCK, ROWLOCK) WHERE $this->idCol = :id"; + case 'sqlite': + // we already locked when starting transaction + break; + default: + throw new \DomainException(sprintf('Transactional locks are currently not implemented for PDO driver "%s".', $this->driver)); + } + } + + return "SELECT $this->dataCol, $this->lifetimeCol, $this->timeCol FROM $this->table WHERE $this->idCol = :id"; + } + + /** + * Returns an insert statement supported by the database for writing session data. + * + * @param string $sessionId Session ID + * @param string $sessionData Encoded session data + * @param int $maxlifetime session.gc_maxlifetime + * + * @return \PDOStatement The insert statement + */ + private function getInsertStatement($sessionId, $sessionData, $maxlifetime) + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, EMPTY_BLOB(), :lifetime, :time) RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns an update statement supported by the database for writing session data. + * + * @param string $sessionId Session ID + * @param string $sessionData Encoded session data + * @param int $maxlifetime session.gc_maxlifetime + * + * @return \PDOStatement The update statement + */ + private function getUpdateStatement($sessionId, $sessionData, $maxlifetime) + { + switch ($this->driver) { + case 'oci': + $data = fopen('php://memory', 'r+'); + fwrite($data, $sessionData); + rewind($data); + $sql = "UPDATE $this->table SET $this->dataCol = EMPTY_BLOB(), $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id RETURNING $this->dataCol into :data"; + break; + default: + $data = $sessionData; + $sql = "UPDATE $this->table SET $this->dataCol = :data, $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id"; + break; + } + + $stmt = $this->pdo->prepare($sql); + $stmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $stmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $stmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $stmt->bindValue(':time', time(), \PDO::PARAM_INT); + + return $stmt; + } + + /** + * Returns a merge/upsert (i.e. insert or update) statement when supported by the database for writing session data. + * + * @param string $sessionId Session ID + * @param string $data Encoded session data + * @param int $maxlifetime session.gc_maxlifetime + * + * @return \PDOStatement|null The merge statement or null when not supported + */ + private function getMergeStatement($sessionId, $data, $maxlifetime) + { + switch (true) { + case 'mysql' === $this->driver: + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + "ON DUPLICATE KEY UPDATE $this->dataCol = VALUES($this->dataCol), $this->lifetimeCol = VALUES($this->lifetimeCol), $this->timeCol = VALUES($this->timeCol)"; + break; + case 'sqlsrv' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '10', '>='): + // MERGE is only available since SQL Server 2008 and must be terminated by semicolon + // It also requires HOLDLOCK according to http://weblogs.sqlteam.com/dang/archive/2009/01/31/UPSERT-Race-Condition-With-MERGE.aspx + $mergeSql = "MERGE INTO $this->table WITH (HOLDLOCK) USING (SELECT 1 AS dummy) AS src ON ($this->idCol = ?) ". + "WHEN NOT MATCHED THEN INSERT ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (?, ?, ?, ?) ". + "WHEN MATCHED THEN UPDATE SET $this->dataCol = ?, $this->lifetimeCol = ?, $this->timeCol = ?;"; + break; + case 'sqlite' === $this->driver: + $mergeSql = "INSERT OR REPLACE INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time)"; + break; + case 'pgsql' === $this->driver && version_compare($this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), '9.5', '>='): + $mergeSql = "INSERT INTO $this->table ($this->idCol, $this->dataCol, $this->lifetimeCol, $this->timeCol) VALUES (:id, :data, :lifetime, :time) ". + "ON CONFLICT ($this->idCol) DO UPDATE SET ($this->dataCol, $this->lifetimeCol, $this->timeCol) = (EXCLUDED.$this->dataCol, EXCLUDED.$this->lifetimeCol, EXCLUDED.$this->timeCol)"; + break; + default: + // MERGE is not supported with LOBs: http://www.oracle.com/technetwork/articles/fuecks-lobs-095315.html + return null; + } + + $mergeStmt = $this->pdo->prepare($mergeSql); + + if ('sqlsrv' === $this->driver) { + $mergeStmt->bindParam(1, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(2, $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(3, $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(4, $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(5, time(), \PDO::PARAM_INT); + $mergeStmt->bindParam(6, $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(7, $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(8, time(), \PDO::PARAM_INT); + } else { + $mergeStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $mergeStmt->bindParam(':data', $data, \PDO::PARAM_LOB); + $mergeStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $mergeStmt->bindValue(':time', time(), \PDO::PARAM_INT); + } + + return $mergeStmt; + } + + /** + * Return a PDO instance. + * + * @return \PDO + */ + protected function getConnection() + { + if (null === $this->pdo) { + $this->connect($this->dsn ?: ini_get('session.save_path')); + } + + return $this->pdo; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php new file mode 100644 index 0000000..7f4b6bc --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Handler/WriteCheckSessionHandler.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Wraps another SessionHandlerInterface to only write the session when it has been modified. + * + * @author Adrien Brault + */ +class WriteCheckSessionHandler implements \SessionHandlerInterface +{ + private $wrappedSessionHandler; + + /** + * @var array sessionId => session + */ + private $readSessions; + + public function __construct(\SessionHandlerInterface $wrappedSessionHandler) + { + $this->wrappedSessionHandler = $wrappedSessionHandler; + } + + /** + * {@inheritdoc} + */ + public function close() + { + return $this->wrappedSessionHandler->close(); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return $this->wrappedSessionHandler->destroy($sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return $this->wrappedSessionHandler->gc($maxlifetime); + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + return $this->wrappedSessionHandler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + $session = $this->wrappedSessionHandler->read($sessionId); + + $this->readSessions[$sessionId] = $session; + + return $session; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + if (isset($this->readSessions[$sessionId]) && $data === $this->readSessions[$sessionId]) { + return true; + } + + return $this->wrappedSessionHandler->write($sessionId, $data); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php new file mode 100644 index 0000000..6f59af4 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MetadataBag.php @@ -0,0 +1,168 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * Metadata container. + * + * Adds metadata to the session. + * + * @author Drak + */ +class MetadataBag implements SessionBagInterface +{ + const CREATED = 'c'; + const UPDATED = 'u'; + const LIFETIME = 'l'; + + /** + * @var string + */ + private $name = '__metadata'; + + /** + * @var string + */ + private $storageKey; + + /** + * @var array + */ + protected $meta = array(self::CREATED => 0, self::UPDATED => 0, self::LIFETIME => 0); + + /** + * Unix timestamp. + * + * @var int + */ + private $lastUsed; + + /** + * @var int + */ + private $updateThreshold; + + /** + * @param string $storageKey The key used to store bag in the session + * @param int $updateThreshold The time to wait between two UPDATED updates + */ + public function __construct($storageKey = '_sf2_meta', $updateThreshold = 0) + { + $this->storageKey = $storageKey; + $this->updateThreshold = $updateThreshold; + } + + /** + * {@inheritdoc} + */ + public function initialize(array &$array) + { + $this->meta = &$array; + + if (isset($array[self::CREATED])) { + $this->lastUsed = $this->meta[self::UPDATED]; + + $timeStamp = time(); + if ($timeStamp - $array[self::UPDATED] >= $this->updateThreshold) { + $this->meta[self::UPDATED] = $timeStamp; + } + } else { + $this->stampCreated(); + } + } + + /** + * Gets the lifetime that the session cookie was set with. + * + * @return int + */ + public function getLifetime() + { + return $this->meta[self::LIFETIME]; + } + + /** + * Stamps a new session's metadata. + * + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + */ + public function stampNew($lifetime = null) + { + $this->stampCreated($lifetime); + } + + /** + * {@inheritdoc} + */ + public function getStorageKey() + { + return $this->storageKey; + } + + /** + * Gets the created timestamp metadata. + * + * @return int Unix timestamp + */ + public function getCreated() + { + return $this->meta[self::CREATED]; + } + + /** + * Gets the last used metadata. + * + * @return int Unix timestamp + */ + public function getLastUsed() + { + return $this->lastUsed; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // nothing to do + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * Sets name. + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + private function stampCreated($lifetime = null) + { + $timeStamp = time(); + $this->meta[self::CREATED] = $this->meta[self::UPDATED] = $this->lastUsed = $timeStamp; + $this->meta[self::LIFETIME] = (null === $lifetime) ? ini_get('session.cookie_lifetime') : $lifetime; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php new file mode 100644 index 0000000..027f4ef --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockArraySessionStorage.php @@ -0,0 +1,256 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * MockArraySessionStorage mocks the session for unit tests. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle. + * + * When doing functional testing, you should use MockFileSessionStorage instead. + * + * @author Fabien Potencier + * @author Bulat Shakirzyanov + * @author Drak + */ +class MockArraySessionStorage implements SessionStorageInterface +{ + /** + * @var string + */ + protected $id = ''; + + /** + * @var string + */ + protected $name; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var array + */ + protected $data = array(); + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * @var array|SessionBagInterface[] + */ + protected $bags = array(); + + /** + * @param string $name Session name + * @param MetadataBag $metaBag MetadataBag instance + */ + public function __construct($name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + $this->name = $name; + $this->setMetadataBag($metaBag); + } + + public function setSessionData(array $array) + { + $this->data = $array; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (empty($this->id)) { + $this->id = $this->generateId(); + } + + $this->loadSession(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + $this->metadataBag->stampNew($lifetime); + $this->id = $this->generateId(); + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + if ($this->started) { + throw new \LogicException('Cannot set session ID after the session has started.'); + } + + $this->id = $id; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started || $this->closed) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed'); + } + // nothing to do since we don't persist the session data + $this->closed = false; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $this->data = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + public function setMetadataBag(MetadataBag $bag = null) + { + if (null === $bag) { + $bag = new MetadataBag(); + } + + $this->metadataBag = $bag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * Generates a session ID. + * + * This doesn't need to be particularly cryptographically secure since this is just + * a mock. + * + * @return string + */ + protected function generateId() + { + return hash('sha256', uniqid('ss_mock_', true)); + } + + protected function loadSession() + { + $bags = array_merge($this->bags, array($this->metadataBag)); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $this->data[$key] = isset($this->data[$key]) ? $this->data[$key] : array(); + $bag->initialize($this->data[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php new file mode 100644 index 0000000..0a580d6 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorage.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +/** + * MockFileSessionStorage is used to mock sessions for + * functional testing when done in a single PHP process. + * + * No PHP session is actually started since a session can be initialized + * and shutdown only once per PHP execution cycle and this class does + * not pollute any session related globals, including session_*() functions + * or session.* PHP ini directives. + * + * @author Drak + */ +class MockFileSessionStorage extends MockArraySessionStorage +{ + private $savePath; + + /** + * @param string $savePath Path of directory to save session files + * @param string $name Session name + * @param MetadataBag $metaBag MetadataBag instance + */ + public function __construct($savePath = null, $name = 'MOCKSESSID', MetadataBag $metaBag = null) + { + if (null === $savePath) { + $savePath = sys_get_temp_dir(); + } + + if (!is_dir($savePath) && !@mkdir($savePath, 0777, true) && !is_dir($savePath)) { + throw new \RuntimeException(sprintf('Session Storage was not able to create directory "%s"', $savePath)); + } + + $this->savePath = $savePath; + + parent::__construct($name, $metaBag); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (!$this->id) { + $this->id = $this->generateId(); + } + + $this->read(); + + $this->started = true; + + return true; + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + if (!$this->started) { + $this->start(); + } + + if ($destroy) { + $this->destroy(); + } + + return parent::regenerate($destroy, $lifetime); + } + + /** + * {@inheritdoc} + */ + public function save() + { + if (!$this->started) { + throw new \RuntimeException('Trying to save a session that was not started yet or was already closed'); + } + + file_put_contents($this->getFilePath(), serialize($this->data)); + + // this is needed for Silex, where the session object is re-used across requests + // in functional tests. In Symfony, the container is rebooted, so we don't have + // this issue + $this->started = false; + } + + /** + * Deletes a session from persistent storage. + * Deliberately leaves session data in memory intact. + */ + private function destroy() + { + if (is_file($this->getFilePath())) { + unlink($this->getFilePath()); + } + } + + /** + * Calculate path to file. + * + * @return string File path + */ + private function getFilePath() + { + return $this->savePath.'/'.$this->id.'.mocksess'; + } + + /** + * Reads session from storage and loads session. + */ + private function read() + { + $filePath = $this->getFilePath(); + $this->data = is_readable($filePath) && is_file($filePath) ? unserialize(file_get_contents($filePath)) : array(); + + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php new file mode 100644 index 0000000..8dd3912 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorage.php @@ -0,0 +1,447 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; + +/** + * This provides a base class for session attribute storage. + * + * @author Drak + */ +class NativeSessionStorage implements SessionStorageInterface +{ + /** + * Array of SessionBagInterface. + * + * @var SessionBagInterface[] + */ + protected $bags; + + /** + * @var bool + */ + protected $started = false; + + /** + * @var bool + */ + protected $closed = false; + + /** + * @var AbstractProxy + */ + protected $saveHandler; + + /** + * @var MetadataBag + */ + protected $metadataBag; + + /** + * Depending on how you want the storage driver to behave you probably + * want to override this constructor entirely. + * + * List of options for $options array with their defaults. + * + * @see http://php.net/session.configuration for options + * but we omit 'session.' from the beginning of the keys for convenience. + * + * ("auto_start", is not supported as it tells PHP to start a session before + * PHP starts to execute user-land code. Setting during runtime has no effect). + * + * cache_limiter, "" (use "0" to prevent headers from being sent entirely). + * cookie_domain, "" + * cookie_httponly, "" + * cookie_lifetime, "0" + * cookie_path, "/" + * cookie_secure, "" + * entropy_file, "" + * entropy_length, "0" + * gc_divisor, "100" + * gc_maxlifetime, "1440" + * gc_probability, "1" + * hash_bits_per_character, "4" + * hash_function, "0" + * lazy_write, "1" + * name, "PHPSESSID" + * referer_check, "" + * serialize_handler, "php" + * use_strict_mode, "0" + * use_cookies, "1" + * use_only_cookies, "1" + * use_trans_sid, "0" + * upload_progress.enabled, "1" + * upload_progress.cleanup, "1" + * upload_progress.prefix, "upload_progress_" + * upload_progress.name, "PHP_SESSION_UPLOAD_PROGRESS" + * upload_progress.freq, "1%" + * upload_progress.min-freq, "1" + * url_rewriter.tags, "a=href,area=href,frame=src,form=,fieldset=" + * sid_length, "32" + * sid_bits_per_character, "5" + * trans_sid_hosts, $_SERVER['HTTP_HOST'] + * trans_sid_tags, "a=href,area=href,frame=src,form=" + * + * @param array $options Session configuration options + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler + * @param MetadataBag $metaBag MetadataBag + */ + public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null) + { + $options += array( + // disable by default because it's managed by HeaderBag (if used) + 'cache_limiter' => '', + 'use_cookies' => 1, + ); + + if (\PHP_VERSION_ID >= 50400) { + session_register_shutdown(); + } else { + register_shutdown_function('session_write_close'); + } + + $this->setMetadataBag($metaBag); + $this->setOptions($options); + $this->setSaveHandler($handler); + } + + /** + * Gets the save handler instance. + * + * @return AbstractProxy + */ + public function getSaveHandler() + { + return $this->saveHandler; + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status()) { + throw new \RuntimeException('Failed to start the session: already started by PHP.'); + } + + if (\PHP_VERSION_ID < 50400 && !$this->closed && isset($_SESSION) && session_id()) { + // not 100% fool-proof, but is the most reliable way to determine if a session is active in PHP 5.3 + throw new \RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).'); + } + + if (ini_get('session.use_cookies') && headers_sent($file, $line)) { + throw new \RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line)); + } + + // ok to try and start the session + if (!session_start()) { + throw new \RuntimeException('Failed to start the session'); + } + + $this->loadSession(); + if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) { + // This condition matches only PHP 5.3 with internal save handlers + $this->saveHandler->setActive(true); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->saveHandler->getId(); + } + + /** + * {@inheritdoc} + */ + public function setId($id) + { + $this->saveHandler->setId($id); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return $this->saveHandler->getName(); + } + + /** + * {@inheritdoc} + */ + public function setName($name) + { + $this->saveHandler->setName($name); + } + + /** + * {@inheritdoc} + */ + public function regenerate($destroy = false, $lifetime = null) + { + // Cannot regenerate the session ID for non-active sessions. + if (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE !== session_status()) { + return false; + } + + // Check if session ID exists in PHP 5.3 + if (\PHP_VERSION_ID < 50400 && '' === session_id()) { + return false; + } + + if (headers_sent()) { + return false; + } + + if (null !== $lifetime) { + ini_set('session.cookie_lifetime', $lifetime); + } + + if ($destroy) { + $this->metadataBag->stampNew(); + } + + $isRegenerated = session_regenerate_id($destroy); + + // The reference to $_SESSION in session bags is lost in PHP7 and we need to re-create it. + // @see https://bugs.php.net/bug.php?id=70013 + $this->loadSession(); + + return $isRegenerated; + } + + /** + * {@inheritdoc} + */ + public function save() + { + session_write_close(); + + if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) { + // This condition matches only PHP 5.3 with internal save handlers + $this->saveHandler->setActive(false); + } + + $this->closed = true; + $this->started = false; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // clear out the session + $_SESSION = array(); + + // reconnect the bags to the session + $this->loadSession(); + } + + /** + * {@inheritdoc} + */ + public function registerBag(SessionBagInterface $bag) + { + if ($this->started) { + throw new \LogicException('Cannot register a bag when the session is already started.'); + } + + $this->bags[$bag->getName()] = $bag; + } + + /** + * {@inheritdoc} + */ + public function getBag($name) + { + if (!isset($this->bags[$name])) { + throw new \InvalidArgumentException(sprintf('The SessionBagInterface %s is not registered.', $name)); + } + + if (!$this->started && $this->saveHandler->isActive()) { + $this->loadSession(); + } elseif (!$this->started) { + $this->start(); + } + + return $this->bags[$name]; + } + + public function setMetadataBag(MetadataBag $metaBag = null) + { + if (null === $metaBag) { + $metaBag = new MetadataBag(); + } + + $this->metadataBag = $metaBag; + } + + /** + * Gets the MetadataBag. + * + * @return MetadataBag + */ + public function getMetadataBag() + { + return $this->metadataBag; + } + + /** + * {@inheritdoc} + */ + public function isStarted() + { + return $this->started; + } + + /** + * Sets session.* ini variables. + * + * For convenience we omit 'session.' from the beginning of the keys. + * Explicitly ignores other ini keys. + * + * @param array $options Session ini directives array(key => value) + * + * @see http://php.net/session.configuration + */ + public function setOptions(array $options) + { + if (headers_sent() || (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status())) { + return; + } + + $validOptions = array_flip(array( + 'cache_expire', 'cache_limiter', 'cookie_domain', 'cookie_httponly', + 'cookie_lifetime', 'cookie_path', 'cookie_secure', + 'entropy_file', 'entropy_length', 'gc_divisor', + 'gc_maxlifetime', 'gc_probability', 'hash_bits_per_character', + 'hash_function', 'lazy_write', 'name', 'referer_check', + 'serialize_handler', 'use_strict_mode', 'use_cookies', + 'use_only_cookies', 'use_trans_sid', 'upload_progress.enabled', + 'upload_progress.cleanup', 'upload_progress.prefix', 'upload_progress.name', + 'upload_progress.freq', 'upload_progress.min_freq', 'url_rewriter.tags', + 'sid_length', 'sid_bits_per_character', 'trans_sid_hosts', 'trans_sid_tags', + )); + + foreach ($options as $key => $value) { + if (isset($validOptions[$key])) { + ini_set('url_rewriter.tags' !== $key ? 'session.'.$key : $key, $value); + } + } + } + + /** + * Registers session save handler as a PHP session handler. + * + * To use internal PHP session save handlers, override this method using ini_set with + * session.save_handler and session.save_path e.g. + * + * ini_set('session.save_handler', 'files'); + * ini_set('session.save_path', '/tmp'); + * + * or pass in a NativeSessionHandler instance which configures session.save_handler in the + * constructor, for a template see NativeFileSessionHandler or use handlers in + * composer package drak/native-session + * + * @see http://php.net/session-set-save-handler + * @see http://php.net/sessionhandlerinterface + * @see http://php.net/sessionhandler + * @see http://github.com/drak/NativeSession + * + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $saveHandler + * + * @throws \InvalidArgumentException + */ + public function setSaveHandler($saveHandler = null) + { + if (!$saveHandler instanceof AbstractProxy && + !$saveHandler instanceof NativeSessionHandler && + !$saveHandler instanceof \SessionHandlerInterface && + null !== $saveHandler) { + throw new \InvalidArgumentException('Must be instance of AbstractProxy or NativeSessionHandler; implement \SessionHandlerInterface; or be null.'); + } + + // Wrap $saveHandler in proxy and prevent double wrapping of proxy + if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { + $saveHandler = new SessionHandlerProxy($saveHandler); + } elseif (!$saveHandler instanceof AbstractProxy) { + $saveHandler = \PHP_VERSION_ID >= 50400 ? + new SessionHandlerProxy(new \SessionHandler()) : new NativeProxy(); + } + $this->saveHandler = $saveHandler; + + if (headers_sent() || (\PHP_VERSION_ID >= 50400 && \PHP_SESSION_ACTIVE === session_status())) { + return; + } + + if ($this->saveHandler instanceof \SessionHandlerInterface) { + if (\PHP_VERSION_ID >= 50400) { + session_set_save_handler($this->saveHandler, false); + } else { + session_set_save_handler( + array($this->saveHandler, 'open'), + array($this->saveHandler, 'close'), + array($this->saveHandler, 'read'), + array($this->saveHandler, 'write'), + array($this->saveHandler, 'destroy'), + array($this->saveHandler, 'gc') + ); + } + } + } + + /** + * Load the session with attributes. + * + * After starting the session, PHP retrieves the session from whatever handlers + * are set to (either PHP's internal, or a custom save handler set with session_set_save_handler()). + * PHP takes the return value from the read() handler, unserializes it + * and populates $_SESSION with the result automatically. + */ + protected function loadSession(array &$session = null) + { + if (null === $session) { + $session = &$_SESSION; + } + + $bags = array_merge($this->bags, array($this->metadataBag)); + + foreach ($bags as $bag) { + $key = $bag->getStorageKey(); + $session[$key] = isset($session[$key]) ? $session[$key] : array(); + $bag->initialize($session[$key]); + } + + $this->started = true; + $this->closed = false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php new file mode 100644 index 0000000..78098fb --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorage.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; + +/** + * Allows session to be started by PHP and managed by Symfony. + * + * @author Drak + */ +class PhpBridgeSessionStorage extends NativeSessionStorage +{ + /** + * @param AbstractProxy|NativeSessionHandler|\SessionHandlerInterface|null $handler + * @param MetadataBag $metaBag MetadataBag + */ + public function __construct($handler = null, MetadataBag $metaBag = null) + { + $this->setMetadataBag($metaBag); + $this->setSaveHandler($handler); + } + + /** + * {@inheritdoc} + */ + public function start() + { + if ($this->started) { + return true; + } + + $this->loadSession(); + if (!$this->saveHandler->isWrapper() && !$this->saveHandler->isSessionHandlerInterface()) { + // This condition matches only PHP 5.3 + internal save handlers + $this->saveHandler->setActive(true); + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + // clear out the bags and nothing else that may be set + // since the purpose of this driver is to share a handler + foreach ($this->bags as $bag) { + $bag->clear(); + } + + // reconnect the bags to the session + $this->loadSession(); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php new file mode 100644 index 0000000..405e60b --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/AbstractProxy.php @@ -0,0 +1,154 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * AbstractProxy. + * + * @author Drak + */ +abstract class AbstractProxy +{ + /** + * Flag if handler wraps an internal PHP session handler (using \SessionHandler). + * + * @var bool + */ + protected $wrapper = false; + + /** + * @var bool + */ + protected $active = false; + + /** + * @var string + */ + protected $saveHandlerName; + + /** + * Gets the session.save_handler name. + * + * @return string + */ + public function getSaveHandlerName() + { + return $this->saveHandlerName; + } + + /** + * Is this proxy handler and instance of \SessionHandlerInterface. + * + * @return bool + */ + public function isSessionHandlerInterface() + { + return $this instanceof \SessionHandlerInterface; + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool + */ + public function isWrapper() + { + return $this->wrapper; + } + + /** + * Has a session started? + * + * @return bool + */ + public function isActive() + { + if (\PHP_VERSION_ID >= 50400) { + return $this->active = \PHP_SESSION_ACTIVE === session_status(); + } + + return $this->active; + } + + /** + * Sets the active flag. + * + * Has no effect under PHP 5.4+ as status is detected + * automatically in isActive() + * + * @internal + * + * @param bool $flag + * + * @throws \LogicException + */ + public function setActive($flag) + { + if (\PHP_VERSION_ID >= 50400) { + throw new \LogicException('This method is disabled in PHP 5.4.0+'); + } + + $this->active = (bool) $flag; + } + + /** + * Gets the session ID. + * + * @return string + */ + public function getId() + { + return session_id(); + } + + /** + * Sets the session ID. + * + * @param string $id + * + * @throws \LogicException + */ + public function setId($id) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the ID of an active session'); + } + + session_id($id); + } + + /** + * Gets the session name. + * + * @return string + */ + public function getName() + { + return session_name(); + } + + /** + * Sets the session name. + * + * @param string $name + * + * @throws \LogicException + */ + public function setName($name) + { + if ($this->isActive()) { + throw new \LogicException('Cannot change the name of an active session'); + } + + session_name($name); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php new file mode 100644 index 0000000..a0f48c1 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/NativeProxy.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * This proxy is built-in session handlers in PHP 5.3.x. + * + * @author Drak + */ +class NativeProxy extends AbstractProxy +{ + public function __construct() + { + // this makes an educated guess as to what the handler is since it should already be set. + $this->saveHandlerName = ini_get('session.save_handler'); + } + + /** + * Returns true if this handler wraps an internal PHP session save handler using \SessionHandler. + * + * @return bool False + */ + public function isWrapper() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php new file mode 100644 index 0000000..6190d3e --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; + +/** + * @author Drak + */ +class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface +{ + protected $handler; + + /** + * @param \SessionHandlerInterface $handler + */ + public function __construct(\SessionHandlerInterface $handler) + { + $this->handler = $handler; + $this->wrapper = ($handler instanceof \SessionHandler); + $this->saveHandlerName = $this->wrapper ? ini_get('session.save_handler') : 'user'; + } + + // \SessionHandlerInterface + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + $return = (bool) $this->handler->open($savePath, $sessionName); + + if (true === $return) { + $this->active = true; + } + + return $return; + } + + /** + * {@inheritdoc} + */ + public function close() + { + $this->active = false; + + return (bool) $this->handler->close(); + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + return (string) $this->handler->read($sessionId); + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + return (bool) $this->handler->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + return (bool) $this->handler->destroy($sessionId); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return (bool) $this->handler->gc($maxlifetime); + } +} diff --git a/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php new file mode 100644 index 0000000..66e8b33 --- /dev/null +++ b/vendor/symfony/http-foundation/Session/Storage/SessionStorageInterface.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage; + +use Symfony\Component\HttpFoundation\Session\SessionBagInterface; + +/** + * StorageInterface. + * + * @author Fabien Potencier + * @author Drak + */ +interface SessionStorageInterface +{ + /** + * Starts the session. + * + * @return bool True if started + * + * @throws \RuntimeException if something goes wrong starting the session + */ + public function start(); + + /** + * Checks if the session is started. + * + * @return bool True if started, false otherwise + */ + public function isStarted(); + + /** + * Returns the session ID. + * + * @return string The session ID or empty + */ + public function getId(); + + /** + * Sets the session ID. + * + * @param string $id + */ + public function setId($id); + + /** + * Returns the session name. + * + * @return mixed The session name + */ + public function getName(); + + /** + * Sets the session name. + * + * @param string $name + */ + public function setName($name); + + /** + * Regenerates id that represents this storage. + * + * This method must invoke session_regenerate_id($destroy) unless + * this interface is used for a storage object designed for unit + * or functional testing where a real PHP session would interfere + * with testing. + * + * Note regenerate+destroy should not clear the session data in memory + * only delete the session data from persistent storage. + * + * Care: When regenerating the session ID no locking is involved in PHP's + * session design. See https://bugs.php.net/bug.php?id=61470 for a discussion. + * So you must make sure the regenerated session is saved BEFORE sending the + * headers with the new ID. Symfony's HttpKernel offers a listener for this. + * See Symfony\Component\HttpKernel\EventListener\SaveSessionListener. + * Otherwise session data could get lost again for concurrent requests with the + * new ID. One result could be that you get logged out after just logging in. + * + * @param bool $destroy Destroy session when regenerating? + * @param int $lifetime Sets the cookie lifetime for the session cookie. A null value + * will leave the system settings unchanged, 0 sets the cookie + * to expire with browser session. Time is in seconds, and is + * not a Unix timestamp. + * + * @return bool True if session regenerated, false if error + * + * @throws \RuntimeException If an error occurs while regenerating this storage + */ + public function regenerate($destroy = false, $lifetime = null); + + /** + * Force the session to be saved and closed. + * + * This method must invoke session_write_close() unless this interface is + * used for a storage object design for unit or functional testing where + * a real PHP session would interfere with testing, in which case + * it should actually persist the session data if required. + * + * @throws \RuntimeException if the session is saved without being started, or if the session + * is already closed + */ + public function save(); + + /** + * Clear all session data in memory. + */ + public function clear(); + + /** + * Gets a SessionBagInterface by name. + * + * @param string $name + * + * @return SessionBagInterface + * + * @throws \InvalidArgumentException If the bag does not exist + */ + public function getBag($name); + + /** + * Registers a SessionBagInterface for use. + */ + public function registerBag(SessionBagInterface $bag); + + /** + * @return MetadataBag + */ + public function getMetadataBag(); +} diff --git a/vendor/symfony/http-foundation/StreamedResponse.php b/vendor/symfony/http-foundation/StreamedResponse.php new file mode 100644 index 0000000..44f654a --- /dev/null +++ b/vendor/symfony/http-foundation/StreamedResponse.php @@ -0,0 +1,139 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation; + +/** + * StreamedResponse represents a streamed HTTP response. + * + * A StreamedResponse uses a callback for its content. + * + * The callback should use the standard PHP functions like echo + * to stream the response back to the client. The flush() function + * can also be used if needed. + * + * @see flush() + * + * @author Fabien Potencier + */ +class StreamedResponse extends Response +{ + protected $callback; + protected $streamed; + private $headersSent; + + /** + * @param callable|null $callback A valid PHP callback or null to set it later + * @param int $status The response status code + * @param array $headers An array of response headers + */ + public function __construct($callback = null, $status = 200, $headers = array()) + { + parent::__construct(null, $status, $headers); + + if (null !== $callback) { + $this->setCallback($callback); + } + $this->streamed = false; + $this->headersSent = false; + } + + /** + * Factory method for chainability. + * + * @param callable|null $callback A valid PHP callback or null to set it later + * @param int $status The response status code + * @param array $headers An array of response headers + * + * @return static + */ + public static function create($callback = null, $status = 200, $headers = array()) + { + return new static($callback, $status, $headers); + } + + /** + * Sets the PHP callback associated with this Response. + * + * @param callable $callback A valid PHP callback + * + * @throws \LogicException + */ + public function setCallback($callback) + { + if (!\is_callable($callback)) { + throw new \LogicException('The Response callback must be a valid PHP callable.'); + } + $this->callback = $callback; + } + + /** + * {@inheritdoc} + * + * This method only sends the headers once. + */ + public function sendHeaders() + { + if ($this->headersSent) { + return $this; + } + + $this->headersSent = true; + + return parent::sendHeaders(); + } + + /** + * {@inheritdoc} + * + * This method only sends the content once. + */ + public function sendContent() + { + if ($this->streamed) { + return $this; + } + + $this->streamed = true; + + if (null === $this->callback) { + throw new \LogicException('The Response callback must not be null.'); + } + + \call_user_func($this->callback); + + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException when the content is not null + */ + public function setContent($content) + { + if (null !== $content) { + throw new \LogicException('The content cannot be set on a StreamedResponse instance.'); + } + + $this->streamed = true; + } + + /** + * {@inheritdoc} + * + * @return false + */ + public function getContent() + { + return false; + } +} diff --git a/vendor/symfony/http-foundation/composer.json b/vendor/symfony/http-foundation/composer.json new file mode 100644 index 0000000..fe9b9ed --- /dev/null +++ b/vendor/symfony/http-foundation/composer.json @@ -0,0 +1,39 @@ +{ + "name": "symfony/http-foundation", + "type": "library", + "description": "Symfony HttpFoundation Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/polyfill-php54": "~1.0", + "symfony/polyfill-php55": "~1.0", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "symfony/expression-language": "~2.4|~3.0.0" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpFoundation\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/http-kernel/Bundle/Bundle.php b/vendor/symfony/http-kernel/Bundle/Bundle.php new file mode 100644 index 0000000..11d93d7 --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/Bundle.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\Console\Application; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; +use Symfony\Component\Finder\Finder; + +/** + * An implementation of BundleInterface that adds a few conventions + * for DependencyInjection extensions and Console commands. + * + * @author Fabien Potencier + */ +abstract class Bundle implements BundleInterface +{ + /** + * @var ContainerInterface + */ + protected $container; + protected $name; + protected $extension; + protected $path; + + /** + * {@inheritdoc} + */ + public function boot() + { + } + + /** + * {@inheritdoc} + */ + public function shutdown() + { + } + + /** + * {@inheritdoc} + * + * This method can be overridden to register compilation passes, + * other extensions, ... + */ + public function build(ContainerBuilder $container) + { + } + + /** + * {@inheritdoc} + */ + public function setContainer(ContainerInterface $container = null) + { + $this->container = $container; + } + + /** + * {@inheritdoc} + * + * @throws \LogicException + */ + public function getContainerExtension() + { + if (null === $this->extension) { + $extension = $this->createContainerExtension(); + + if (null !== $extension) { + if (!$extension instanceof ExtensionInterface) { + throw new \LogicException(sprintf('Extension %s must implement Symfony\Component\DependencyInjection\Extension\ExtensionInterface.', \get_class($extension))); + } + + // check naming convention + $basename = preg_replace('/Bundle$/', '', $this->getName()); + $expectedAlias = Container::underscore($basename); + + if ($expectedAlias != $extension->getAlias()) { + throw new \LogicException(sprintf('Users will expect the alias of the default extension of a bundle to be the underscored version of the bundle name ("%s"). You can override "Bundle::getContainerExtension()" if you want to use "%s" or another alias.', $expectedAlias, $extension->getAlias())); + } + + $this->extension = $extension; + } else { + $this->extension = false; + } + } + + if ($this->extension) { + return $this->extension; + } + } + + /** + * {@inheritdoc} + */ + public function getNamespace() + { + $class = \get_class($this); + + return substr($class, 0, strrpos($class, '\\')); + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + if (null === $this->path) { + $reflected = new \ReflectionObject($this); + $this->path = \dirname($reflected->getFileName()); + } + + return $this->path; + } + + /** + * {@inheritdoc} + */ + public function getParent() + { + } + + /** + * {@inheritdoc} + */ + final public function getName() + { + if (null !== $this->name) { + return $this->name; + } + + $name = \get_class($this); + $pos = strrpos($name, '\\'); + + return $this->name = false === $pos ? $name : substr($name, $pos + 1); + } + + /** + * Finds and registers Commands. + * + * Override this method if your bundle commands do not follow the conventions: + * + * * Commands are in the 'Command' sub-directory + * * Commands extend Symfony\Component\Console\Command\Command + */ + public function registerCommands(Application $application) + { + if (!is_dir($dir = $this->getPath().'/Command')) { + return; + } + + if (!class_exists('Symfony\Component\Finder\Finder')) { + throw new \RuntimeException('You need the symfony/finder component to register bundle commands.'); + } + + $finder = new Finder(); + $finder->files()->name('*Command.php')->in($dir); + + $prefix = $this->getNamespace().'\\Command'; + foreach ($finder as $file) { + $ns = $prefix; + if ($relativePath = $file->getRelativePath()) { + $ns .= '\\'.str_replace('/', '\\', $relativePath); + } + $class = $ns.'\\'.$file->getBasename('.php'); + if ($this->container) { + $alias = 'console.command.'.strtolower(str_replace('\\', '_', $class)); + if ($this->container->has($alias)) { + continue; + } + } + $r = new \ReflectionClass($class); + if ($r->isSubclassOf('Symfony\\Component\\Console\\Command\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) { + $application->add($r->newInstance()); + } + } + } + + /** + * Returns the bundle's container extension class. + * + * @return string + */ + protected function getContainerExtensionClass() + { + $basename = preg_replace('/Bundle$/', '', $this->getName()); + + return $this->getNamespace().'\\DependencyInjection\\'.$basename.'Extension'; + } + + /** + * Creates the bundle's container extension. + * + * @return ExtensionInterface|null + */ + protected function createContainerExtension() + { + if (class_exists($class = $this->getContainerExtensionClass())) { + return new $class(); + } + } +} diff --git a/vendor/symfony/http-kernel/Bundle/BundleInterface.php b/vendor/symfony/http-kernel/Bundle/BundleInterface.php new file mode 100644 index 0000000..ade098f --- /dev/null +++ b/vendor/symfony/http-kernel/Bundle/BundleInterface.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Bundle; + +use Symfony\Component\DependencyInjection\ContainerAwareInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; + +/** + * BundleInterface. + * + * @author Fabien Potencier + */ +interface BundleInterface extends ContainerAwareInterface +{ + /** + * Boots the Bundle. + */ + public function boot(); + + /** + * Shutdowns the Bundle. + */ + public function shutdown(); + + /** + * Builds the bundle. + * + * It is only ever called once when the cache is empty. + */ + public function build(ContainerBuilder $container); + + /** + * Returns the container extension that should be implicitly loaded. + * + * @return ExtensionInterface|null The default extension or null if there is none + */ + public function getContainerExtension(); + + /** + * Returns the bundle name that this bundle overrides. + * + * Despite its name, this method does not imply any parent/child relationship + * between the bundles, just a way to extend and override an existing + * bundle. + * + * @return string The Bundle name it overrides or null if no parent + */ + public function getParent(); + + /** + * Returns the bundle name (the class short name). + * + * @return string The Bundle name + */ + public function getName(); + + /** + * Gets the Bundle namespace. + * + * @return string The Bundle namespace + */ + public function getNamespace(); + + /** + * Gets the Bundle directory path. + * + * The path should always be returned as a Unix path (with /). + * + * @return string The Bundle absolute path + */ + public function getPath(); +} diff --git a/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php new file mode 100644 index 0000000..675c584 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/CacheClearerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * CacheClearerInterface. + * + * @author Dustin Dobervich + */ +interface CacheClearerInterface +{ + /** + * Clears any caches necessary. + * + * @param string $cacheDir The cache directory + */ + public function clear($cacheDir); +} diff --git a/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php new file mode 100644 index 0000000..2316531 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheClearer/ChainCacheClearer.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheClearer; + +/** + * ChainCacheClearer. + * + * @author Dustin Dobervich + */ +class ChainCacheClearer implements CacheClearerInterface +{ + protected $clearers; + + /** + * Constructs a new instance of ChainCacheClearer. + * + * @param array $clearers The initial clearers + */ + public function __construct(array $clearers = array()) + { + $this->clearers = $clearers; + } + + /** + * {@inheritdoc} + */ + public function clear($cacheDir) + { + foreach ($this->clearers as $clearer) { + $clearer->clear($cacheDir); + } + } + + /** + * Adds a cache clearer to the aggregate. + */ + public function add(CacheClearerInterface $clearer) + { + $this->clearers[] = $clearer; + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php new file mode 100644 index 0000000..52dc2ad --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmer.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Abstract cache warmer that knows how to write a file to the cache. + * + * @author Fabien Potencier + */ +abstract class CacheWarmer implements CacheWarmerInterface +{ + protected function writeCacheFile($file, $content) + { + $tmpFile = @tempnam(\dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { + @chmod($file, 0666 & ~umask()); + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php new file mode 100644 index 0000000..e5f4e4f --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Aggregates several cache warmers into a single one. + * + * @author Fabien Potencier + */ +class CacheWarmerAggregate implements CacheWarmerInterface +{ + protected $warmers = array(); + protected $optionalsEnabled = false; + + public function __construct(array $warmers = array()) + { + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function enableOptionalWarmers() + { + $this->optionalsEnabled = true; + } + + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir) + { + foreach ($this->warmers as $warmer) { + if (!$this->optionalsEnabled && $warmer->isOptional()) { + continue; + } + + $warmer->warmUp($cacheDir); + } + } + + /** + * Checks whether this warmer is optional or not. + * + * @return bool always false + */ + public function isOptional() + { + return false; + } + + public function setWarmers(array $warmers) + { + $this->warmers = array(); + foreach ($warmers as $warmer) { + $this->add($warmer); + } + } + + public function add(CacheWarmerInterface $warmer) + { + $this->warmers[] = $warmer; + } +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php new file mode 100644 index 0000000..8fece5e --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/CacheWarmerInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes able to warm up the cache. + * + * @author Fabien Potencier + */ +interface CacheWarmerInterface extends WarmableInterface +{ + /** + * Checks whether this warmer is optional or not. + * + * Optional warmers can be ignored on certain conditions. + * + * A warmer should return true if the cache can be + * generated incrementally and on-demand. + * + * @return bool true if the warmer is optional, false otherwise + */ + public function isOptional(); +} diff --git a/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php new file mode 100644 index 0000000..25d8ee8 --- /dev/null +++ b/vendor/symfony/http-kernel/CacheWarmer/WarmableInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\CacheWarmer; + +/** + * Interface for classes that support warming their cache. + * + * @author Fabien Potencier + */ +interface WarmableInterface +{ + /** + * Warms up the cache. + * + * @param string $cacheDir The cache directory + */ + public function warmUp($cacheDir); +} diff --git a/vendor/symfony/http-kernel/Client.php b/vendor/symfony/http-kernel/Client.php new file mode 100644 index 0000000..2175b56 --- /dev/null +++ b/vendor/symfony/http-kernel/Client.php @@ -0,0 +1,197 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\BrowserKit\Client as BaseClient; +use Symfony\Component\BrowserKit\Cookie as DomCookie; +use Symfony\Component\BrowserKit\CookieJar; +use Symfony\Component\BrowserKit\History; +use Symfony\Component\BrowserKit\Request as DomRequest; +use Symfony\Component\BrowserKit\Response as DomResponse; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Client simulates a browser and makes requests to a Kernel object. + * + * @author Fabien Potencier + * + * @method Request|null getRequest() A Request instance + * @method Response|null getResponse() A Response instance + */ +class Client extends BaseClient +{ + protected $kernel; + + /** + * @param HttpKernelInterface $kernel An HttpKernel instance + * @param array $server The server parameters (equivalent of $_SERVER) + * @param History $history A History instance to store the browser history + * @param CookieJar $cookieJar A CookieJar instance to store the cookies + */ + public function __construct(HttpKernelInterface $kernel, array $server = array(), History $history = null, CookieJar $cookieJar = null) + { + // These class properties must be set before calling the parent constructor, as it may depend on it. + $this->kernel = $kernel; + $this->followRedirects = false; + + parent::__construct($server, $history, $cookieJar); + } + + /** + * Makes a request. + * + * @return Response A Response instance + */ + protected function doRequest($request) + { + $response = $this->kernel->handle($request); + + if ($this->kernel instanceof TerminableInterface) { + $this->kernel->terminate($request, $response); + } + + return $response; + } + + /** + * Returns the script to execute when the request must be insulated. + * + * @return string + */ + protected function getScript($request) + { + $kernel = var_export(serialize($this->kernel), true); + $request = var_export(serialize($request), true); + + $r = new \ReflectionClass('\\Symfony\\Component\\ClassLoader\\ClassLoader'); + $requirePath = var_export($r->getFileName(), true); + $symfonyPath = var_export(\dirname(\dirname(\dirname(__DIR__))), true); + $errorReporting = error_reporting(); + + $code = <<addPrefix('Symfony', $symfonyPath); +\$loader->register(); + +\$kernel = unserialize($kernel); +\$request = unserialize($request); +EOF; + + return $code.$this->getHandleScript(); + } + + protected function getHandleScript() + { + return <<<'EOF' +$response = $kernel->handle($request); + +if ($kernel instanceof Symfony\Component\HttpKernel\TerminableInterface) { + $kernel->terminate($request, $response); +} + +echo serialize($response); +EOF; + } + + /** + * Converts the BrowserKit request to a HttpKernel request. + * + * @return Request A Request instance + */ + protected function filterRequest(DomRequest $request) + { + $httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $request->getServer(), $request->getContent()); + + foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) { + $httpRequest->files->set($key, $value); + } + + return $httpRequest; + } + + /** + * Filters an array of files. + * + * This method created test instances of UploadedFile so that the move() + * method can be called on those instances. + * + * If the size of a file is greater than the allowed size (from php.ini) then + * an invalid UploadedFile is returned with an error set to UPLOAD_ERR_INI_SIZE. + * + * @see UploadedFile + * + * @return array An array with all uploaded files marked as already moved + */ + protected function filterFiles(array $files) + { + $filtered = array(); + foreach ($files as $key => $value) { + if (\is_array($value)) { + $filtered[$key] = $this->filterFiles($value); + } elseif ($value instanceof UploadedFile) { + if ($value->isValid() && $value->getSize() > UploadedFile::getMaxFilesize()) { + $filtered[$key] = new UploadedFile( + '', + $value->getClientOriginalName(), + $value->getClientMimeType(), + 0, + UPLOAD_ERR_INI_SIZE, + true + ); + } else { + $filtered[$key] = new UploadedFile( + $value->getPathname(), + $value->getClientOriginalName(), + $value->getClientMimeType(), + $value->getClientSize(), + $value->getError(), + true + ); + } + } + } + + return $filtered; + } + + /** + * Converts the HttpKernel response to a BrowserKit response. + * + * @return DomResponse A DomResponse instance + */ + protected function filterResponse($response) + { + $headers = $response->headers->all(); + if ($response->headers->getCookies()) { + $cookies = array(); + foreach ($response->headers->getCookies() as $cookie) { + $cookies[] = new DomCookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); + } + $headers['Set-Cookie'] = $cookies; + } + + // this is needed to support StreamedResponse + ob_start(); + $response->sendContent(); + $content = ob_get_clean(); + + return new DomResponse($content, $response->getStatusCode(), $headers); + } +} diff --git a/vendor/symfony/http-kernel/Config/EnvParametersResource.php b/vendor/symfony/http-kernel/Config/EnvParametersResource.php new file mode 100644 index 0000000..4029125 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/EnvParametersResource.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Config; + +use Symfony\Component\Config\Resource\SelfCheckingResourceInterface; + +/** + * EnvParametersResource represents resources stored in prefixed environment variables. + * + * @author Chris Wilkinson + */ +class EnvParametersResource implements SelfCheckingResourceInterface, \Serializable +{ + /** + * @var string + */ + private $prefix; + + /** + * @var string + */ + private $variables; + + /** + * @param string $prefix + */ + public function __construct($prefix) + { + $this->prefix = $prefix; + $this->variables = $this->findVariables(); + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return serialize($this->getResource()); + } + + /** + * {@inheritdoc} + */ + public function getResource() + { + return array('prefix' => $this->prefix, 'variables' => $this->variables); + } + + /** + * {@inheritdoc} + */ + public function isFresh($timestamp) + { + return $this->findVariables() === $this->variables; + } + + public function serialize() + { + return serialize(array('prefix' => $this->prefix, 'variables' => $this->variables)); + } + + public function unserialize($serialized) + { + $unserialized = unserialize($serialized); + + $this->prefix = $unserialized['prefix']; + $this->variables = $unserialized['variables']; + } + + private function findVariables() + { + $variables = array(); + + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, $this->prefix)) { + $variables[$key] = $value; + } + } + + ksort($variables); + + return $variables; + } +} diff --git a/vendor/symfony/http-kernel/Config/FileLocator.php b/vendor/symfony/http-kernel/Config/FileLocator.php new file mode 100644 index 0000000..c047ba3 --- /dev/null +++ b/vendor/symfony/http-kernel/Config/FileLocator.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Config; + +use Symfony\Component\Config\FileLocator as BaseFileLocator; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * FileLocator uses the KernelInterface to locate resources in bundles. + * + * @author Fabien Potencier + */ +class FileLocator extends BaseFileLocator +{ + private $kernel; + private $path; + + /** + * @param KernelInterface $kernel A KernelInterface instance + * @param string|null $path The path the global resource directory + * @param array $paths An array of paths where to look for resources + */ + public function __construct(KernelInterface $kernel, $path = null, array $paths = array()) + { + $this->kernel = $kernel; + if (null !== $path) { + $this->path = $path; + $paths[] = $path; + } + + parent::__construct($paths); + } + + /** + * {@inheritdoc} + */ + public function locate($file, $currentPath = null, $first = true) + { + if (isset($file[0]) && '@' === $file[0]) { + return $this->kernel->locateResource($file, $this->path, $first); + } + + return parent::locate($file, $currentPath, $first); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerReference.php b/vendor/symfony/http-kernel/Controller/ControllerReference.php new file mode 100644 index 0000000..fae4e7f --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerReference.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface; + +/** + * Acts as a marker and a data holder for a Controller. + * + * Some methods in Symfony accept both a URI (as a string) or a controller as + * an argument. In the latter case, instead of passing an array representing + * the controller, you can use an instance of this class. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class ControllerReference +{ + public $controller; + public $attributes = array(); + public $query = array(); + + /** + * @param string $controller The controller name + * @param array $attributes An array of parameters to add to the Request attributes + * @param array $query An array of parameters to add to the Request query string + */ + public function __construct($controller, array $attributes = array(), array $query = array()) + { + $this->controller = $controller; + $this->attributes = $attributes; + $this->query = $query; + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolver.php b/vendor/symfony/http-kernel/Controller/ControllerResolver.php new file mode 100644 index 0000000..8e547a4 --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolver.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; + +/** + * This implementation uses the '_controller' request attribute to determine + * the controller to execute and uses the request attributes to determine + * the controller method arguments. + * + * @author Fabien Potencier + */ +class ControllerResolver implements ControllerResolverInterface +{ + private $logger; + + /** + * If the ...$arg functionality is available. + * + * Requires at least PHP 5.6.0 or HHVM 3.9.1 + * + * @var bool + */ + private $supportsVariadic; + + /** + * If scalar types exists. + * + * @var bool + */ + private $supportsScalarTypes; + + public function __construct(LoggerInterface $logger = null) + { + $this->logger = $logger; + + $this->supportsVariadic = method_exists('ReflectionParameter', 'isVariadic'); + $this->supportsScalarTypes = method_exists('ReflectionParameter', 'getType'); + } + + /** + * {@inheritdoc} + * + * This method looks for a '_controller' request attribute that represents + * the controller name (a string like ClassName::MethodName). + */ + public function getController(Request $request) + { + if (!$controller = $request->attributes->get('_controller')) { + if (null !== $this->logger) { + $this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing.'); + } + + return false; + } + + if (\is_array($controller)) { + return $controller; + } + + if (\is_object($controller)) { + if (method_exists($controller, '__invoke')) { + return $controller; + } + + throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', \get_class($controller), $request->getPathInfo())); + } + + if (false === strpos($controller, ':')) { + if (method_exists($controller, '__invoke')) { + return $this->instantiateController($controller); + } elseif (\function_exists($controller)) { + return $controller; + } + } + + $callable = $this->createController($controller); + + if (!\is_callable($callable)) { + throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', $controller, $request->getPathInfo())); + } + + return $callable; + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + if (\is_array($controller)) { + $r = new \ReflectionMethod($controller[0], $controller[1]); + } elseif (\is_object($controller) && !$controller instanceof \Closure) { + $r = new \ReflectionObject($controller); + $r = $r->getMethod('__invoke'); + } else { + $r = new \ReflectionFunction($controller); + } + + return $this->doGetArguments($request, $controller, $r->getParameters()); + } + + /** + * @param Request $request + * @param callable $controller + * @param \ReflectionParameter[] $parameters + * + * @return array The arguments to use when calling the action + */ + protected function doGetArguments(Request $request, $controller, array $parameters) + { + $attributes = $request->attributes->all(); + $arguments = array(); + foreach ($parameters as $param) { + if (array_key_exists($param->name, $attributes)) { + if ($this->supportsVariadic && $param->isVariadic() && \is_array($attributes[$param->name])) { + $arguments = array_merge($arguments, array_values($attributes[$param->name])); + } else { + $arguments[] = $attributes[$param->name]; + } + } elseif ($param->getClass() && $param->getClass()->isInstance($request)) { + $arguments[] = $request; + } elseif ($param->isDefaultValueAvailable()) { + $arguments[] = $param->getDefaultValue(); + } elseif ($this->supportsScalarTypes && $param->hasType() && $param->allowsNull()) { + $arguments[] = null; + } else { + if (\is_array($controller)) { + $repr = sprintf('%s::%s()', \get_class($controller[0]), $controller[1]); + } elseif (\is_object($controller)) { + $repr = \get_class($controller); + } else { + $repr = $controller; + } + + throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $repr, $param->name)); + } + } + + return $arguments; + } + + /** + * Returns a callable for the given controller. + * + * @param string $controller A Controller string + * + * @return callable A PHP callable + * + * @throws \InvalidArgumentException + */ + protected function createController($controller) + { + if (false === strpos($controller, '::')) { + throw new \InvalidArgumentException(sprintf('Unable to find controller "%s".', $controller)); + } + + list($class, $method) = explode('::', $controller, 2); + + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + return array($this->instantiateController($class), $method); + } + + /** + * Returns an instantiated controller. + * + * @param string $class A class name + * + * @return object + */ + protected function instantiateController($class) + { + return new $class(); + } +} diff --git a/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php new file mode 100644 index 0000000..7adda4e --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/ControllerResolverInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; + +/** + * A ControllerResolverInterface implementation knows how to determine the + * controller to execute based on a Request object. + * + * It can also determine the arguments to pass to the Controller. + * + * A Controller can be any valid PHP callable. + * + * @author Fabien Potencier + */ +interface ControllerResolverInterface +{ + /** + * Returns the Controller instance associated with a Request. + * + * As several resolvers can exist for a single application, a resolver must + * return false when it is not able to determine the controller. + * + * The resolver must only throw an exception when it should be able to load + * controller but cannot because of some errors made by the developer. + * + * @return callable|false A PHP callable representing the Controller, + * or false if this resolver is not able to determine the controller + * + * @throws \LogicException If the controller can't be found + */ + public function getController(Request $request); + + /** + * Returns the arguments to pass to the controller. + * + * @param Request $request A Request instance + * @param callable $controller A PHP callable + * + * @return array An array of arguments to pass to the controller + * + * @throws \RuntimeException When value for argument given is not provided + */ + public function getArguments(Request $request, $controller); +} diff --git a/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php new file mode 100644 index 0000000..f1c03aa --- /dev/null +++ b/vendor/symfony/http-kernel/Controller/TraceableControllerResolver.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Controller; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Stopwatch\Stopwatch; + +/** + * @author Fabien Potencier + */ +class TraceableControllerResolver implements ControllerResolverInterface +{ + private $resolver; + private $stopwatch; + + public function __construct(ControllerResolverInterface $resolver, Stopwatch $stopwatch) + { + $this->resolver = $resolver; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function getController(Request $request) + { + $e = $this->stopwatch->start('controller.get_callable'); + + $ret = $this->resolver->getController($request); + + $e->stop(); + + return $ret; + } + + /** + * {@inheritdoc} + */ + public function getArguments(Request $request, $controller) + { + $e = $this->stopwatch->start('controller.get_arguments'); + + $ret = $this->resolver->getArguments($request, $controller); + + $e->stop(); + + return $ret; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php new file mode 100644 index 0000000..b8405d5 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/AjaxDataCollector.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * AjaxDataCollector. + * + * @author Bart van den Burg + */ +class AjaxDataCollector extends DataCollector +{ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // all collecting is done client side + } + + public function getName() + { + return 'ajax'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php new file mode 100644 index 0000000..851875c --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ConfigDataCollector.php @@ -0,0 +1,284 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * @author Fabien Potencier + */ +class ConfigDataCollector extends DataCollector +{ + /** + * @var KernelInterface + */ + private $kernel; + private $name; + private $version; + + /** + * @param string $name The name of the application using the web profiler + * @param string $version The version of the application using the web profiler + */ + public function __construct($name = null, $version = null) + { + $this->name = $name; + $this->version = $version; + } + + /** + * Sets the Kernel associated with this Request. + */ + public function setKernel(KernelInterface $kernel = null) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'app_name' => $this->name, + 'app_version' => $this->version, + 'token' => $response->headers->get('X-Debug-Token'), + 'symfony_version' => Kernel::VERSION, + 'symfony_state' => 'unknown', + 'name' => isset($this->kernel) ? $this->kernel->getName() : 'n/a', + 'env' => isset($this->kernel) ? $this->kernel->getEnvironment() : 'n/a', + 'debug' => isset($this->kernel) ? $this->kernel->isDebug() : 'n/a', + 'php_version' => PHP_VERSION, + 'xdebug_enabled' => \extension_loaded('xdebug'), + 'eaccel_enabled' => \extension_loaded('eaccelerator') && ini_get('eaccelerator.enable'), + 'apc_enabled' => \extension_loaded('apc') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN), + 'xcache_enabled' => \extension_loaded('xcache') && filter_var(ini_get('xcache.cacher'), FILTER_VALIDATE_BOOLEAN), + 'wincache_enabled' => \extension_loaded('wincache') && filter_var(ini_get('wincache.ocenabled'), FILTER_VALIDATE_BOOLEAN), + 'zend_opcache_enabled' => \extension_loaded('Zend OPcache') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN), + 'bundles' => array(), + 'sapi_name' => \PHP_SAPI, + ); + + if (isset($this->kernel)) { + foreach ($this->kernel->getBundles() as $name => $bundle) { + $this->data['bundles'][$name] = $bundle->getPath(); + } + + $this->data['symfony_state'] = $this->determineSymfonyState(); + } + } + + public function getApplicationName() + { + return $this->data['app_name']; + } + + public function getApplicationVersion() + { + return $this->data['app_version']; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->data['token']; + } + + /** + * Gets the Symfony version. + * + * @return string The Symfony version + */ + public function getSymfonyVersion() + { + return $this->data['symfony_version']; + } + + /** + * Returns the state of the current Symfony release. + * + * @return string One of: unknown, dev, stable, eom, eol + */ + public function getSymfonyState() + { + return $this->data['symfony_state']; + } + + public function setCacheVersionInfo($cacheVersionInfo) + { + // no-op for BC + } + + /** + * Gets the PHP version. + * + * @return string The PHP version + */ + public function getPhpVersion() + { + return $this->data['php_version']; + } + + /** + * Gets the application name. + * + * @return string The application name + */ + public function getAppName() + { + return $this->data['name']; + } + + /** + * Gets the environment. + * + * @return string The environment + */ + public function getEnv() + { + return $this->data['env']; + } + + /** + * Returns true if the debug is enabled. + * + * @return bool true if debug is enabled, false otherwise + */ + public function isDebug() + { + return $this->data['debug']; + } + + /** + * Returns true if the XDebug is enabled. + * + * @return bool true if XDebug is enabled, false otherwise + */ + public function hasXDebug() + { + return $this->data['xdebug_enabled']; + } + + /** + * Returns true if EAccelerator is enabled. + * + * @return bool true if EAccelerator is enabled, false otherwise + */ + public function hasEAccelerator() + { + return $this->data['eaccel_enabled']; + } + + /** + * Returns true if APC is enabled. + * + * @return bool true if APC is enabled, false otherwise + */ + public function hasApc() + { + return $this->data['apc_enabled']; + } + + /** + * Returns true if Zend OPcache is enabled. + * + * @return bool true if Zend OPcache is enabled, false otherwise + */ + public function hasZendOpcache() + { + return $this->data['zend_opcache_enabled']; + } + + /** + * Returns true if XCache is enabled. + * + * @return bool true if XCache is enabled, false otherwise + */ + public function hasXCache() + { + return $this->data['xcache_enabled']; + } + + /** + * Returns true if WinCache is enabled. + * + * @return bool true if WinCache is enabled, false otherwise + */ + public function hasWinCache() + { + return $this->data['wincache_enabled']; + } + + /** + * Returns true if any accelerator is enabled. + * + * @return bool true if any accelerator is enabled, false otherwise + */ + public function hasAccelerator() + { + return $this->hasApc() || $this->hasZendOpcache() || $this->hasEAccelerator() || $this->hasXCache() || $this->hasWinCache(); + } + + public function getBundles() + { + return $this->data['bundles']; + } + + /** + * Gets the PHP SAPI name. + * + * @return string The environment + */ + public function getSapiName() + { + return $this->data['sapi_name']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'config'; + } + + /** + * Tries to retrieve information about the current Symfony version. + * + * @return string One of: dev, stable, eom, eol + */ + private function determineSymfonyState() + { + $now = new \DateTime(); + $eom = \DateTime::createFromFormat('m/Y', Kernel::END_OF_MAINTENANCE)->modify('last day of this month'); + $eol = \DateTime::createFromFormat('m/Y', Kernel::END_OF_LIFE)->modify('last day of this month'); + + if ($now > $eol) { + $versionState = 'eol'; + } elseif ($now > $eom) { + $versionState = 'eom'; + } elseif ('' !== Kernel::EXTRA_VERSION) { + $versionState = 'dev'; + } else { + $versionState = 'stable'; + } + + return $versionState; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollector.php b/vendor/symfony/http-kernel/DataCollector/DataCollector.php new file mode 100644 index 0000000..5dca652 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollector.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; + +/** + * DataCollector. + * + * Children of this class must store the collected data in the data property. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +abstract class DataCollector implements DataCollectorInterface, \Serializable +{ + protected $data = array(); + + /** + * @var ValueExporter + */ + private $valueExporter; + + public function serialize() + { + return serialize($this->data); + } + + public function unserialize($data) + { + $this->data = unserialize($data); + } + + /** + * Converts a PHP variable to a string. + * + * @param mixed $var A PHP variable + * + * @return string The string representation of the variable + */ + protected function varToString($var) + { + if (null === $this->valueExporter) { + $this->valueExporter = new ValueExporter(); + } + + return $this->valueExporter->exportValue($var); + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php new file mode 100644 index 0000000..963732b --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DataCollectorInterface.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * DataCollectorInterface. + * + * @author Fabien Potencier + */ +interface DataCollectorInterface +{ + /** + * Collects data for the given Request and Response. + */ + public function collect(Request $request, Response $response, \Exception $exception = null); + + /** + * Returns the name of the collector. + * + * @return string The collector name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php new file mode 100644 index 0000000..dbec7ea --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/DumpDataCollector.php @@ -0,0 +1,311 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Twig\Template; + +/** + * @author Nicolas Grekas + */ +class DumpDataCollector extends DataCollector implements DataDumperInterface +{ + private $stopwatch; + private $fileLinkFormat; + private $dataCount = 0; + private $isCollected = true; + private $clonesCount = 0; + private $clonesIndex = 0; + private $rootRefs; + private $charset; + private $requestStack; + private $dumper; + private $dumperIsInjected; + + public function __construct(Stopwatch $stopwatch = null, $fileLinkFormat = null, $charset = null, RequestStack $requestStack = null, DataDumperInterface $dumper = null) + { + $this->stopwatch = $stopwatch; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->charset = $charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'; + $this->requestStack = $requestStack; + $this->dumper = $dumper; + $this->dumperIsInjected = null !== $dumper; + + // All clones share these properties by reference: + $this->rootRefs = array( + &$this->data, + &$this->dataCount, + &$this->isCollected, + &$this->clonesCount, + ); + } + + public function __clone() + { + $this->clonesIndex = ++$this->clonesCount; + } + + public function dump(Data $data) + { + if ($this->stopwatch) { + $this->stopwatch->start('dump'); + } + if ($this->isCollected && !$this->dumper) { + $this->isCollected = false; + } + + $trace = DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS; + if (\PHP_VERSION_ID >= 50400) { + $trace = debug_backtrace($trace, 7); + } else { + $trace = debug_backtrace($trace); + } + + $file = $trace[0]['file']; + $line = $trace[0]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 1; $i < 7; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class'] + ) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + while (++$i < 7) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = array(); + + for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; + } + + $fileExcerpt = '
      '.implode("\n", $fileExcerpt).'
    '; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + if ($this->dumper) { + $this->doDump($data, $name, $file, $line); + } + + $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); + ++$this->dataCount; + + if ($this->stopwatch) { + $this->stopwatch->stop('dump'); + } + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // Sub-requests and programmatic calls stay in the collected profile. + if ($this->dumper || ($this->requestStack && $this->requestStack->getMasterRequest() !== $request) || $request->isXmlHttpRequest() || $request->headers->has('Origin')) { + return; + } + + // In all other conditions that remove the web debug toolbar, dumps are written on the output. + if (!$this->requestStack + || !$response->headers->has('X-Debug-Token') + || $response->isRedirection() + || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) + || 'html' !== $request->getRequestFormat() + || false === strripos($response->getContent(), '') + ) { + if ($response->headers->has('Content-Type') && false !== strpos($response->headers->get('Content-Type'), 'html')) { + $this->dumper = new HtmlDumper('php://output', $this->charset); + } else { + $this->dumper = new CliDumper('php://output', $this->charset); + } + + foreach ($this->data as $dump) { + $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + } + } + } + + public function serialize() + { + if ($this->clonesCount !== $this->clonesIndex) { + return 'a:0:{}'; + } + + $this->data[] = $this->fileLinkFormat; + $this->data[] = $this->charset; + $ser = serialize($this->data); + $this->data = array(); + $this->dataCount = 0; + $this->isCollected = true; + if (!$this->dumperIsInjected) { + $this->dumper = null; + } + + return $ser; + } + + public function unserialize($data) + { + parent::unserialize($data); + $charset = array_pop($this->data); + $fileLinkFormat = array_pop($this->data); + $this->dataCount = \count($this->data); + self::__construct($this->stopwatch, $fileLinkFormat, $charset); + } + + public function getDumpsCount() + { + return $this->dataCount; + } + + public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) + { + $data = fopen('php://memory', 'r+b'); + + if ('html' === $format) { + $dumper = new HtmlDumper($data, $this->charset); + } else { + throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format)); + } + $dumps = array(); + + foreach ($this->data as $dump) { + if (method_exists($dump['data'], 'withMaxDepth')) { + $dumper->dump($dump['data']->withMaxDepth($maxDepthLimit)->withMaxItemsPerDepth($maxItemsPerDepth)); + } else { + // getLimitedClone is @deprecated, to be removed in 3.0 + $dumper->dump($dump['data']->getLimitedClone($maxDepthLimit, $maxItemsPerDepth)); + } + $dump['data'] = stream_get_contents($data, -1, 0); + ftruncate($data, 0); + rewind($data); + $dumps[] = $dump; + } + + return $dumps; + } + + public function getName() + { + return 'dump'; + } + + public function __destruct() + { + if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) { + $this->clonesCount = 0; + $this->isCollected = true; + + $h = headers_list(); + $i = \count($h); + array_unshift($h, 'Content-Type: '.ini_get('default_mimetype')); + while (0 !== stripos($h[$i], 'Content-Type:')) { + --$i; + } + + if (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true) && stripos($h[$i], 'html')) { + $this->dumper = new HtmlDumper('php://output', $this->charset); + } else { + $this->dumper = new CliDumper('php://output', $this->charset); + } + + foreach ($this->data as $i => $dump) { + $this->data[$i] = null; + $this->doDump($dump['data'], $dump['name'], $dump['file'], $dump['line']); + } + + $this->data = array(); + $this->dataCount = 0; + } + } + + private function doDump($data, $name, $file, $line) + { + if (\PHP_VERSION_ID >= 50400 && $this->dumper instanceof CliDumper) { + $contextDumper = function ($name, $file, $line, $fileLinkFormat) { + if ($this instanceof HtmlDumper) { + if ($file) { + $s = $this->style('meta', '%s'); + $name = strip_tags($this->style('', $name)); + $file = strip_tags($this->style('', $file)); + if ($fileLinkFormat) { + $link = strtr(strip_tags($this->style('', $fileLinkFormat)), array('%f' => $file, '%l' => (int) $line)); + $name = sprintf(''.$s.'', $link, $file, $name); + } else { + $name = sprintf(''.$s.'', $file, $name); + } + } else { + $name = $this->style('meta', $name); + } + $this->line = $name.' on line '.$this->style('meta', $line).':'; + } else { + $this->line = $this->style('meta', $name).' on line '.$this->style('meta', $line).':'; + } + $this->dumpLine(0); + }; + $contextDumper = $contextDumper->bindTo($this->dumper, $this->dumper); + $contextDumper($name, $file, $line, $this->fileLinkFormat); + } else { + $cloner = new VarCloner(); + $this->dumper->dump($cloner->cloneVar($name.' on line '.$line.':')); + } + $this->dumper->dump($data); + } + + private function htmlEncode($s) + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php new file mode 100644 index 0000000..b91b0ba --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/EventDataCollector.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * EventDataCollector. + * + * @author Fabien Potencier + */ +class EventDataCollector extends DataCollector implements LateDataCollectorInterface +{ + protected $dispatcher; + + public function __construct(EventDispatcherInterface $dispatcher = null) + { + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->data = array( + 'called_listeners' => array(), + 'not_called_listeners' => array(), + ); + } + + public function lateCollect() + { + if ($this->dispatcher instanceof TraceableEventDispatcherInterface) { + $this->setCalledListeners($this->dispatcher->getCalledListeners()); + $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners()); + } + } + + /** + * Sets the called listeners. + * + * @param array $listeners An array of called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function setCalledListeners(array $listeners) + { + $this->data['called_listeners'] = $listeners; + } + + /** + * Gets the called listeners. + * + * @return array An array of called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function getCalledListeners() + { + return $this->data['called_listeners']; + } + + /** + * Sets the not called listeners. + * + * @param array $listeners An array of not called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function setNotCalledListeners(array $listeners) + { + $this->data['not_called_listeners'] = $listeners; + } + + /** + * Gets the not called listeners. + * + * @return array An array of not called listeners + * + * @see TraceableEventDispatcherInterface + */ + public function getNotCalledListeners() + { + return $this->data['not_called_listeners']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'events'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php new file mode 100644 index 0000000..9fe8264 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/ExceptionDataCollector.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * ExceptionDataCollector. + * + * @author Fabien Potencier + */ +class ExceptionDataCollector extends DataCollector +{ + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $exception) { + $this->data = array( + 'exception' => FlattenException::create($exception), + ); + } + } + + /** + * Checks if the exception is not null. + * + * @return bool true if the exception is not null, false otherwise + */ + public function hasException() + { + return isset($this->data['exception']); + } + + /** + * Gets the exception. + * + * @return \Exception The exception + */ + public function getException() + { + return $this->data['exception']; + } + + /** + * Gets the exception message. + * + * @return string The exception message + */ + public function getMessage() + { + return $this->data['exception']->getMessage(); + } + + /** + * Gets the exception code. + * + * @return int The exception code + */ + public function getCode() + { + return $this->data['exception']->getCode(); + } + + /** + * Gets the status code. + * + * @return int The status code + */ + public function getStatusCode() + { + return $this->data['exception']->getStatusCode(); + } + + /** + * Gets the exception trace. + * + * @return array The exception trace + */ + public function getTrace() + { + return $this->data['exception']->getTrace(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'exception'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php new file mode 100644 index 0000000..012332d --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LateDataCollectorInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +/** + * LateDataCollectorInterface. + * + * @author Fabien Potencier + */ +interface LateDataCollectorInterface +{ + /** + * Collects data as late as possible. + */ + public function lateCollect(); +} diff --git a/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php new file mode 100644 index 0000000..150e746 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/LoggerDataCollector.php @@ -0,0 +1,213 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +/** + * LogDataCollector. + * + * @author Fabien Potencier + */ +class LoggerDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $errorNames = array( + E_DEPRECATED => 'E_DEPRECATED', + E_USER_DEPRECATED => 'E_USER_DEPRECATED', + E_NOTICE => 'E_NOTICE', + E_USER_NOTICE => 'E_USER_NOTICE', + E_STRICT => 'E_STRICT', + E_WARNING => 'E_WARNING', + E_USER_WARNING => 'E_USER_WARNING', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_CORE_WARNING => 'E_CORE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_PARSE => 'E_PARSE', + E_ERROR => 'E_ERROR', + E_CORE_ERROR => 'E_CORE_ERROR', + ); + + private $logger; + + public function __construct($logger = null) + { + if (null !== $logger && $logger instanceof DebugLoggerInterface) { + $this->logger = $logger; + } + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + // everything is done as late as possible + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + if (null !== $this->logger) { + $this->data = $this->computeErrorsCount(); + $this->data['logs'] = $this->sanitizeLogs($this->logger->getLogs()); + } + } + + /** + * Gets the logs. + * + * @return array An array of logs + */ + public function getLogs() + { + return isset($this->data['logs']) ? $this->data['logs'] : array(); + } + + public function getPriorities() + { + return isset($this->data['priorities']) ? $this->data['priorities'] : array(); + } + + public function countErrors() + { + return isset($this->data['error_count']) ? $this->data['error_count'] : 0; + } + + public function countDeprecations() + { + return isset($this->data['deprecation_count']) ? $this->data['deprecation_count'] : 0; + } + + public function countScreams() + { + return isset($this->data['scream_count']) ? $this->data['scream_count'] : 0; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'logger'; + } + + private function sanitizeLogs($logs) + { + $errorContextById = array(); + $sanitizedLogs = array(); + + foreach ($logs as $log) { + $context = $this->sanitizeContext($log['context']); + + if (isset($context['type'], $context['file'], $context['line'], $context['level'])) { + $errorId = md5("{$context['type']}/{$context['line']}/{$context['file']}\x00{$log['message']}", true); + $silenced = !($context['type'] & $context['level']); + if (isset($this->errorNames[$context['type']])) { + $context = array_merge(array('name' => $this->errorNames[$context['type']]), $context); + } + + if (isset($errorContextById[$errorId])) { + if (isset($errorContextById[$errorId]['errorCount'])) { + ++$errorContextById[$errorId]['errorCount']; + } else { + $errorContextById[$errorId]['errorCount'] = 2; + } + + if (!$silenced && isset($errorContextById[$errorId]['scream'])) { + unset($errorContextById[$errorId]['scream']); + $errorContextById[$errorId]['level'] = $context['level']; + } + + continue; + } + + $errorContextById[$errorId] = &$context; + if ($silenced) { + $context['scream'] = true; + } + + $log['context'] = &$context; + unset($context); + } else { + $log['context'] = $context; + } + + $sanitizedLogs[] = $log; + } + + return $sanitizedLogs; + } + + private function sanitizeContext($context) + { + if (\is_array($context)) { + foreach ($context as $key => $value) { + $context[$key] = $this->sanitizeContext($value); + } + + return $context; + } + + if (\is_resource($context)) { + return sprintf('Resource(%s)', get_resource_type($context)); + } + + if (\is_object($context)) { + if ($context instanceof \Exception) { + return sprintf('Exception(%s): %s', \get_class($context), $context->getMessage()); + } + + return sprintf('Object(%s)', \get_class($context)); + } + + return $context; + } + + private function computeErrorsCount() + { + $count = array( + 'error_count' => $this->logger->countErrors(), + 'deprecation_count' => 0, + 'scream_count' => 0, + 'priorities' => array(), + ); + + foreach ($this->logger->getLogs() as $log) { + if (isset($count['priorities'][$log['priority']])) { + ++$count['priorities'][$log['priority']]['count']; + } else { + $count['priorities'][$log['priority']] = array( + 'count' => 1, + 'name' => $log['priorityName'], + ); + } + + if (isset($log['context']['type'], $log['context']['level'])) { + if (E_DEPRECATED === $log['context']['type'] || E_USER_DEPRECATED === $log['context']['type']) { + ++$count['deprecation_count']; + } elseif (!($log['context']['type'] & $log['context']['level'])) { + ++$count['scream_count']; + } + } + } + + ksort($count['priorities']); + + return $count; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php new file mode 100644 index 0000000..0d9b562 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/MemoryDataCollector.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * MemoryDataCollector. + * + * @author Fabien Potencier + */ +class MemoryDataCollector extends DataCollector implements LateDataCollectorInterface +{ + public function __construct() + { + $this->data = array( + 'memory' => 0, + 'memory_limit' => $this->convertToBytes(ini_get('memory_limit')), + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $this->updateMemoryUsage(); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $this->updateMemoryUsage(); + } + + /** + * Gets the memory. + * + * @return int The memory + */ + public function getMemory() + { + return $this->data['memory']; + } + + /** + * Gets the PHP memory limit. + * + * @return int The memory limit + */ + public function getMemoryLimit() + { + return $this->data['memory_limit']; + } + + /** + * Updates the memory usage data. + */ + public function updateMemoryUsage() + { + $this->data['memory'] = memory_get_peak_usage(true); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'memory'; + } + + private function convertToBytes($memoryLimit) + { + if ('-1' === $memoryLimit) { + return -1; + } + + $memoryLimit = strtolower($memoryLimit); + $max = strtolower(ltrim($memoryLimit, '+')); + if (0 === strpos($max, '0x')) { + $max = \intval($max, 16); + } elseif (0 === strpos($max, '0')) { + $max = \intval($max, 8); + } else { + $max = (int) $max; + } + + switch (substr($memoryLimit, -1)) { + case 't': $max *= 1024; + // no break + case 'g': $max *= 1024; + // no break + case 'm': $max *= 1024; + // no break + case 'k': $max *= 1024; + } + + return $max; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php new file mode 100644 index 0000000..373bed3 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RequestDataCollector.php @@ -0,0 +1,309 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * @author Fabien Potencier + */ +class RequestDataCollector extends DataCollector implements EventSubscriberInterface +{ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + $responseHeaders = $response->headers->all(); + foreach ($response->headers->getCookies() as $cookie) { + $responseHeaders['set-cookie'][] = (string) $cookie; + } + + // attributes are serialized and as they can be anything, they need to be converted to strings. + $attributes = array(); + foreach ($request->attributes->all() as $key => $value) { + if ('_route' === $key && \is_object($value)) { + $attributes[$key] = $this->varToString($value->getPath()); + } elseif ('_route_params' === $key) { + // we need to keep route params as an array (see getRouteParams()) + foreach ($value as $k => $v) { + $value[$k] = $this->varToString($v); + } + $attributes[$key] = $value; + } else { + $attributes[$key] = $this->varToString($value); + } + } + + $content = null; + try { + $content = $request->getContent(); + } catch (\LogicException $e) { + // the user already got the request content as a resource + $content = false; + } + + $sessionMetadata = array(); + $sessionAttributes = array(); + $flashes = array(); + if ($request->hasSession()) { + $session = $request->getSession(); + if ($session->isStarted()) { + $sessionMetadata['Created'] = date(DATE_RFC822, $session->getMetadataBag()->getCreated()); + $sessionMetadata['Last used'] = date(DATE_RFC822, $session->getMetadataBag()->getLastUsed()); + $sessionMetadata['Lifetime'] = $session->getMetadataBag()->getLifetime(); + $sessionAttributes = $session->all(); + $flashes = $session->getFlashBag()->peekAll(); + } + } + + $statusCode = $response->getStatusCode(); + + $this->data = array( + 'format' => $request->getRequestFormat(), + 'content' => $content, + 'content_type' => $response->headers->get('Content-Type', 'text/html'), + 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', + 'status_code' => $statusCode, + 'request_query' => $request->query->all(), + 'request_request' => $request->request->all(), + 'request_headers' => $request->headers->all(), + 'request_server' => $request->server->all(), + 'request_cookies' => $request->cookies->all(), + 'request_attributes' => $attributes, + 'response_headers' => $responseHeaders, + 'session_metadata' => $sessionMetadata, + 'session_attributes' => $sessionAttributes, + 'flashes' => $flashes, + 'path_info' => $request->getPathInfo(), + 'controller' => 'n/a', + 'locale' => $request->getLocale(), + ); + + if (isset($this->data['request_headers']['php-auth-pw'])) { + $this->data['request_headers']['php-auth-pw'] = '******'; + } + + if (isset($this->data['request_server']['PHP_AUTH_PW'])) { + $this->data['request_server']['PHP_AUTH_PW'] = '******'; + } + + if (isset($this->data['request_request']['_password'])) { + $this->data['request_request']['_password'] = '******'; + } + + foreach ($this->data as $key => $value) { + if (!\is_array($value)) { + continue; + } + if ('request_headers' === $key || 'response_headers' === $key) { + $value = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); + } + if ('request_server' !== $key && 'request_cookies' !== $key) { + $this->data[$key] = $value; + } + } + + if (isset($this->controllers[$request])) { + $controller = $this->controllers[$request]; + if (\is_array($controller)) { + try { + $r = new \ReflectionMethod($controller[0], $controller[1]); + $this->data['controller'] = array( + 'class' => \is_object($controller[0]) ? \get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } catch (\ReflectionException $e) { + if (\is_callable($controller)) { + // using __call or __callStatic + $this->data['controller'] = array( + 'class' => \is_object($controller[0]) ? \get_class($controller[0]) : $controller[0], + 'method' => $controller[1], + 'file' => 'n/a', + 'line' => 'n/a', + ); + } + } + } elseif ($controller instanceof \Closure) { + $r = new \ReflectionFunction($controller); + $this->data['controller'] = array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } elseif (\is_object($controller)) { + $r = new \ReflectionClass($controller); + $this->data['controller'] = array( + 'class' => $r->getName(), + 'method' => null, + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ); + } else { + $this->data['controller'] = (string) $controller ?: 'n/a'; + } + unset($this->controllers[$request]); + } + } + + public function getPathInfo() + { + return $this->data['path_info']; + } + + public function getRequestRequest() + { + return new ParameterBag($this->data['request_request']); + } + + public function getRequestQuery() + { + return new ParameterBag($this->data['request_query']); + } + + public function getRequestHeaders() + { + return new ParameterBag($this->data['request_headers']); + } + + public function getRequestServer() + { + return new ParameterBag($this->data['request_server']); + } + + public function getRequestCookies() + { + return new ParameterBag($this->data['request_cookies']); + } + + public function getRequestAttributes() + { + return new ParameterBag($this->data['request_attributes']); + } + + public function getResponseHeaders() + { + return new ParameterBag($this->data['response_headers']); + } + + public function getSessionMetadata() + { + return $this->data['session_metadata']; + } + + public function getSessionAttributes() + { + return $this->data['session_attributes']; + } + + public function getFlashes() + { + return $this->data['flashes']; + } + + public function getContent() + { + return $this->data['content']; + } + + public function getContentType() + { + return $this->data['content_type']; + } + + public function getStatusText() + { + return $this->data['status_text']; + } + + public function getStatusCode() + { + return $this->data['status_code']; + } + + public function getFormat() + { + return $this->data['format']; + } + + public function getLocale() + { + return $this->data['locale']; + } + + /** + * Gets the route name. + * + * The _route request attributes is automatically set by the Router Matcher. + * + * @return string The route + */ + public function getRoute() + { + return isset($this->data['request_attributes']['_route']) ? $this->data['request_attributes']['_route'] : ''; + } + + /** + * Gets the route parameters. + * + * The _route_params request attributes is automatically set by the RouterListener. + * + * @return array The parameters + */ + public function getRouteParams() + { + return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params'] : array(); + } + + /** + * Gets the controller. + * + * @return string The controller as a string + */ + public function getController() + { + return $this->data['controller']; + } + + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + public static function getSubscribedEvents() + { + return array(KernelEvents::CONTROLLER => 'onKernelController'); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'request'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php new file mode 100644 index 0000000..5d024e3 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/RouterDataCollector.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\RedirectResponse; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; + +/** + * RouterDataCollector. + * + * @author Fabien Potencier + */ +class RouterDataCollector extends DataCollector +{ + protected $controllers; + + public function __construct() + { + $this->controllers = new \SplObjectStorage(); + + $this->data = array( + 'redirect' => false, + 'url' => null, + 'route' => null, + ); + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if ($response instanceof RedirectResponse) { + $this->data['redirect'] = true; + $this->data['url'] = $response->getTargetUrl(); + + if ($this->controllers->contains($request)) { + $this->data['route'] = $this->guessRoute($request, $this->controllers[$request]); + } + } + + unset($this->controllers[$request]); + } + + protected function guessRoute(Request $request, $controller) + { + return 'n/a'; + } + + /** + * Remembers the controller associated to each request. + */ + public function onKernelController(FilterControllerEvent $event) + { + $this->controllers[$event->getRequest()] = $event->getController(); + } + + /** + * @return bool Whether this request will result in a redirect + */ + public function getRedirect() + { + return $this->data['redirect']; + } + + /** + * @return string|null The target URL + */ + public function getTargetUrl() + { + return $this->data['url']; + } + + /** + * @return string|null The target route + */ + public function getTargetRoute() + { + return $this->data['route']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'router'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php new file mode 100644 index 0000000..4ccaafa --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/TimeDataCollector.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\KernelInterface; + +/** + * TimeDataCollector. + * + * @author Fabien Potencier + */ +class TimeDataCollector extends DataCollector implements LateDataCollectorInterface +{ + protected $kernel; + protected $stopwatch; + + public function __construct(KernelInterface $kernel = null, $stopwatch = null) + { + $this->kernel = $kernel; + $this->stopwatch = $stopwatch; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (null !== $this->kernel) { + $startTime = $this->kernel->getStartTime(); + } else { + $startTime = $request->server->get('REQUEST_TIME_FLOAT', $request->server->get('REQUEST_TIME')); + } + + $this->data = array( + 'token' => $response->headers->get('X-Debug-Token'), + 'start_time' => $startTime * 1000, + 'events' => array(), + ); + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + if (null !== $this->stopwatch && isset($this->data['token'])) { + $this->setEvents($this->stopwatch->getSectionEvents($this->data['token'])); + } + unset($this->data['token']); + } + + /** + * Sets the request events. + * + * @param array $events The request events + */ + public function setEvents(array $events) + { + foreach ($events as $event) { + $event->ensureStopped(); + } + + $this->data['events'] = $events; + } + + /** + * Gets the request events. + * + * @return array The request events + */ + public function getEvents() + { + return $this->data['events']; + } + + /** + * Gets the request elapsed time. + * + * @return float The elapsed time + */ + public function getDuration() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + $lastEvent = $this->data['events']['__section__']; + + return $lastEvent->getOrigin() + $lastEvent->getDuration() - $this->getStartTime(); + } + + /** + * Gets the initialization time. + * + * This is the time spent until the beginning of the request handling. + * + * @return float The elapsed time + */ + public function getInitTime() + { + if (!isset($this->data['events']['__section__'])) { + return 0; + } + + return $this->data['events']['__section__']->getOrigin() - $this->getStartTime(); + } + + /** + * Gets the request time. + * + * @return int The time + */ + public function getStartTime() + { + return $this->data['start_time']; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'time'; + } +} diff --git a/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php new file mode 100644 index 0000000..5eb5057 --- /dev/null +++ b/vendor/symfony/http-kernel/DataCollector/Util/ValueExporter.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector\Util; + +/** + * @author Bernhard Schussek + */ +class ValueExporter +{ + /** + * Converts a PHP value to a string. + * + * @param mixed $value The PHP value + * @param int $depth Only for internal usage + * @param bool $deep Only for internal usage + * + * @return string The string representation of the given value + */ + public function exportValue($value, $depth = 1, $deep = false) + { + if ($value instanceof \__PHP_Incomplete_Class) { + return sprintf('__PHP_Incomplete_Class(%s)', $this->getClassNameFromIncomplete($value)); + } + + if (\is_object($value)) { + if ($value instanceof \DateTime || $value instanceof \DateTimeInterface) { + return sprintf('Object(%s) - %s', \get_class($value), $value->format(\DateTime::ATOM)); + } + + return sprintf('Object(%s)', \get_class($value)); + } + + if (\is_array($value)) { + if (empty($value)) { + return '[]'; + } + + $indent = str_repeat(' ', $depth); + + $a = array(); + foreach ($value as $k => $v) { + if (\is_array($v)) { + $deep = true; + } + $a[] = sprintf('%s => %s', $k, $this->exportValue($v, $depth + 1, $deep)); + } + + if ($deep) { + return sprintf("[\n%s%s\n%s]", $indent, implode(sprintf(", \n%s", $indent), $a), str_repeat(' ', $depth - 1)); + } + + return sprintf('[%s]', implode(', ', $a)); + } + + if (\is_resource($value)) { + return sprintf('Resource(%s#%d)', get_resource_type($value), $value); + } + + if (null === $value) { + return 'null'; + } + + if (false === $value) { + return 'false'; + } + + if (true === $value) { + return 'true'; + } + + return (string) $value; + } + + private function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value) + { + $array = new \ArrayObject($value); + + return $array['__PHP_Incomplete_Class_Name']; + } +} diff --git a/vendor/symfony/http-kernel/Debug/ErrorHandler.php b/vendor/symfony/http-kernel/Debug/ErrorHandler.php new file mode 100644 index 0000000..a9c0178 --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/ErrorHandler.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +@trigger_error('The '.__NAMESPACE__.'\ErrorHandler class is deprecated since Symfony 2.3 and will be removed in 3.0. Use the Symfony\Component\Debug\ErrorHandler class instead.', E_USER_DEPRECATED); + +use Symfony\Component\Debug\ErrorHandler as DebugErrorHandler; + +/** + * ErrorHandler. + * + * @author Fabien Potencier + * + * @deprecated since version 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class ErrorHandler extends DebugErrorHandler +{ +} diff --git a/vendor/symfony/http-kernel/Debug/ExceptionHandler.php b/vendor/symfony/http-kernel/Debug/ExceptionHandler.php new file mode 100644 index 0000000..7a8955d --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/ExceptionHandler.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +@trigger_error('The '.__NAMESPACE__.'\ExceptionHandler class is deprecated since Symfony 2.3 and will be removed in 3.0. Use the Symfony\Component\Debug\ExceptionHandler class instead.', E_USER_DEPRECATED); + +use Symfony\Component\Debug\ExceptionHandler as DebugExceptionHandler; + +/** + * ExceptionHandler converts an exception to a Response object. + * + * @author Fabien Potencier + * + * @deprecated since version 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class ExceptionHandler extends DebugExceptionHandler +{ +} diff --git a/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php new file mode 100644 index 0000000..0d6cf7d --- /dev/null +++ b/vendor/symfony/http-kernel/Debug/TraceableEventDispatcher.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Debug; + +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as BaseTraceableEventDispatcher; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * Collects some data about event listeners. + * + * This event dispatcher delegates the dispatching to another one. + * + * @author Fabien Potencier + */ +class TraceableEventDispatcher extends BaseTraceableEventDispatcher +{ + /** + * Sets the profiler. + * + * The traceable event dispatcher does not use the profiler anymore. + * The job is now done directly by the Profiler listener and the + * data collectors themselves. + * + * @param Profiler|null $profiler A Profiler instance + * + * @deprecated since version 2.4, to be removed in 3.0. + */ + public function setProfiler(Profiler $profiler = null) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + /** + * {@inheritdoc} + */ + protected function preDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::REQUEST: + $this->stopwatch->openSection(); + break; + case KernelEvents::VIEW: + case KernelEvents::RESPONSE: + // stop only if a controller has been executed + if ($this->stopwatch->isStarted('controller')) { + $this->stopwatch->stop('controller'); + } + break; + case KernelEvents::TERMINATE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + // There is a very special case when using built-in AppCache class as kernel wrapper, in the case + // of an ESI request leading to a `stale` response [B] inside a `fresh` cached response [A]. + // In this case, `$token` contains the [B] debug token, but the open `stopwatch` section ID + // is equal to the [A] debug token. Trying to reopen section with the [B] token throws an exception + // which must be caught. + try { + $this->stopwatch->openSection($token); + } catch (\LogicException $e) { + } + break; + } + } + + /** + * {@inheritdoc} + */ + protected function postDispatch($eventName, Event $event) + { + switch ($eventName) { + case KernelEvents::CONTROLLER: + $this->stopwatch->start('controller', 'section'); + break; + case KernelEvents::RESPONSE: + $token = $event->getResponse()->headers->get('X-Debug-Token'); + $this->stopwatch->stopSection($token); + break; + case KernelEvents::TERMINATE: + // In the special case described in the `preDispatch` method above, the `$token` section + // does not exist, then closing it throws an exception which must be caught. + $token = $event->getResponse()->headers->get('X-Debug-Token'); + try { + $this->stopwatch->stopSection($token); + } catch (\LogicException $e) { + } + break; + } + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php new file mode 100644 index 0000000..a01d361 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/AddClassesToCachePass.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\Kernel; + +/** + * Sets the classes to compile in the cache for the container. + * + * @author Fabien Potencier + */ +class AddClassesToCachePass implements CompilerPassInterface +{ + private $kernel; + + public function __construct(Kernel $kernel) + { + $this->kernel = $kernel; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $classes = array(); + foreach ($container->getExtensions() as $extension) { + if ($extension instanceof Extension) { + $classes = array_merge($classes, $extension->getClassesToCompile()); + } + } + + $this->kernel->setClassCache(array_unique($container->getParameterBag()->resolveValue($classes))); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php new file mode 100644 index 0000000..072c35f --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ConfigurableExtension.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * This extension sub-class provides first-class integration with the + * Config/Definition Component. + * + * You can use this as base class if + * + * a) you use the Config/Definition component for configuration, + * b) your configuration class is named "Configuration", and + * c) the configuration class resides in the DependencyInjection sub-folder. + * + * @author Johannes M. Schmitt + */ +abstract class ConfigurableExtension extends Extension +{ + /** + * {@inheritdoc} + */ + final public function load(array $configs, ContainerBuilder $container) + { + $this->loadInternal($this->processConfiguration($this->getConfiguration($configs, $container), $configs), $container); + } + + /** + * Configures the passed container according to the merged configuration. + */ + abstract protected function loadInternal(array $mergedConfig, ContainerBuilder $container); +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/ContainerAwareHttpKernel.php b/vendor/symfony/http-kernel/DependencyInjection/ContainerAwareHttpKernel.php new file mode 100644 index 0000000..b1db5f3 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/ContainerAwareHttpKernel.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Scope; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\HttpKernel; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Adds a managed request scope. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + * + * @deprecated since version 2.7, to be removed in 3.0. + */ +class ContainerAwareHttpKernel extends HttpKernel +{ + protected $container; + + /** + * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance + * @param ContainerInterface $container A ContainerInterface instance + * @param ControllerResolverInterface $controllerResolver A ControllerResolverInterface instance + * @param RequestStack $requestStack A stack for master/sub requests + * @param bool $triggerDeprecation Whether or not to trigger the deprecation warning for the ContainerAwareHttpKernel + */ + public function __construct(EventDispatcherInterface $dispatcher, ContainerInterface $container, ControllerResolverInterface $controllerResolver, RequestStack $requestStack = null, $triggerDeprecation = true) + { + parent::__construct($dispatcher, $controllerResolver, $requestStack); + + if ($triggerDeprecation) { + @trigger_error('The '.__CLASS__.' class is deprecated since Symfony 2.7 and will be removed in 3.0. Use the Symfony\Component\HttpKernel\HttpKernel class instead.', E_USER_DEPRECATED); + } + + $this->container = $container; + + // the request scope might have been created before (see FrameworkBundle) + if (!$container->hasScope('request')) { + $container->addScope(new Scope('request')); + } + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $this->container->enterScope('request'); + $this->container->set('request', $request, 'request'); + + try { + $response = parent::handle($request, $type, $catch); + } catch (\Exception $e) { + $this->container->set('request', null, 'request'); + $this->container->leaveScope('request'); + + throw $e; + } catch (\Throwable $e) { + $this->container->set('request', null, 'request'); + $this->container->leaveScope('request'); + + throw $e; + } + + $this->container->set('request', null, 'request'); + $this->container->leaveScope('request'); + + return $response; + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/Extension.php b/vendor/symfony/http-kernel/DependencyInjection/Extension.php new file mode 100644 index 0000000..2ca0f13 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/Extension.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Extension\Extension as BaseExtension; + +/** + * Allow adding classes to the class cache. + * + * @author Fabien Potencier + */ +abstract class Extension extends BaseExtension +{ + private $classes = array(); + + /** + * Gets the classes to cache. + * + * @return array An array of classes + */ + public function getClassesToCompile() + { + return $this->classes; + } + + /** + * Adds classes to the class cache. + * + * @param array $classes An array of classes + */ + public function addClassesToCompile(array $classes) + { + $this->classes = array_merge($this->classes, $classes); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php new file mode 100644 index 0000000..2e15f9a --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/FragmentRendererPass.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +/** + * Adds services tagged kernel.fragment_renderer as HTTP content rendering strategies. + * + * @author Fabien Potencier + */ +class FragmentRendererPass implements CompilerPassInterface +{ + private $handlerService; + private $rendererTag; + + /** + * @param string $handlerService Service name of the fragment handler in the container + * @param string $rendererTag Tag name used for fragments + */ + public function __construct($handlerService = 'fragment.handler', $rendererTag = 'kernel.fragment_renderer') + { + $this->handlerService = $handlerService; + $this->rendererTag = $rendererTag; + } + + public function process(ContainerBuilder $container) + { + if (!$container->hasDefinition($this->handlerService)) { + return; + } + + $definition = $container->getDefinition($this->handlerService); + foreach ($container->findTaggedServiceIds($this->rendererTag) as $id => $tags) { + $def = $container->getDefinition($id); + if (!$def->isPublic()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must be public as fragment renderer are lazy-loaded.', $id)); + } + + if ($def->isAbstract()) { + throw new \InvalidArgumentException(sprintf('The service "%s" must not be abstract as fragment renderer are lazy-loaded.', $id)); + } + + $class = $container->getParameterBag()->resolveValue($def->getClass()); + $interface = 'Symfony\Component\HttpKernel\Fragment\FragmentRendererInterface'; + + if (!is_subclass_of($class, $interface)) { + if (!class_exists($class, false)) { + throw new \InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id)); + } + + throw new \InvalidArgumentException(sprintf('Service "%s" must implement interface "%s".', $id, $interface)); + } + + foreach ($tags as $tag) { + if (!isset($tag['alias'])) { + @trigger_error(sprintf('Service "%s" will have to define the "alias" attribute on the "%s" tag as of Symfony 3.0.', $id, $this->rendererTag), E_USER_DEPRECATED); + + // register the handler as a non-lazy-loaded one + $definition->addMethodCall('addRenderer', array(new Reference($id))); + } else { + $definition->addMethodCall('addRendererService', array($tag['alias'], $id)); + } + } + } + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php new file mode 100644 index 0000000..f13aec6 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/LazyLoadingFragmentHandler.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; + +/** + * Lazily loads fragment renderers from the dependency injection container. + * + * @author Fabien Potencier + */ +class LazyLoadingFragmentHandler extends FragmentHandler +{ + private $container; + private $rendererIds = array(); + + /** + * RequestStack will become required in 3.0. + * + * @param ContainerInterface $container A container + * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct(ContainerInterface $container, $requestStack = null, $debug = false) + { + $this->container = $container; + + if ((null !== $requestStack && !$requestStack instanceof RequestStack) || $debug instanceof RequestStack) { + $tmp = $debug; + $debug = $requestStack; + $requestStack = \func_num_args() < 3 ? null : $tmp; + + @trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as second argument as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } elseif (!$requestStack instanceof RequestStack) { + @trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } + + parent::__construct($requestStack, array(), $debug); + } + + /** + * Adds a service as a fragment renderer. + * + * @param string $name The service name + * @param string $renderer The render service id + */ + public function addRendererService($name, $renderer) + { + $this->rendererIds[$name] = $renderer; + } + + /** + * {@inheritdoc} + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + if (isset($this->rendererIds[$renderer])) { + $this->addRenderer($this->container->get($this->rendererIds[$renderer])); + + unset($this->rendererIds[$renderer]); + } + + return parent::render($uri, $renderer, $options); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php new file mode 100644 index 0000000..1dbf7f7 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/MergeExtensionConfigurationPass.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass as BaseMergeExtensionConfigurationPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Ensures certain extensions are always loaded. + * + * @author Kris Wallsmith + */ +class MergeExtensionConfigurationPass extends BaseMergeExtensionConfigurationPass +{ + private $extensions; + + public function __construct(array $extensions) + { + $this->extensions = $extensions; + } + + public function process(ContainerBuilder $container) + { + foreach ($this->extensions as $extension) { + if (!\count($container->getExtensionConfig($extension))) { + $container->loadFromExtension($extension, array()); + } + } + + parent::process($container); + } +} diff --git a/vendor/symfony/http-kernel/DependencyInjection/RegisterListenersPass.php b/vendor/symfony/http-kernel/DependencyInjection/RegisterListenersPass.php new file mode 100644 index 0000000..06a2e40 --- /dev/null +++ b/vendor/symfony/http-kernel/DependencyInjection/RegisterListenersPass.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DependencyInjection; + +@trigger_error('The '.__NAMESPACE__.'\RegisterListenersPass is deprecated since Symfony 2.5 and will be removed in 3.0. Use the Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass class instead.', E_USER_DEPRECATED); + +use Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass as BaseRegisterListenersPass; + +/** + * Compiler pass to register tagged services for an event dispatcher. + * + * @deprecated since version 2.5, to be removed in 3.0. Use the Symfony\Component\EventDispatcher\DependencyInjection\RegisterListenersPass class instead. + */ +class RegisterListenersPass extends BaseRegisterListenersPass +{ +} diff --git a/vendor/symfony/http-kernel/Event/FilterControllerEvent.php b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php new file mode 100644 index 0000000..3f29a67 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterControllerEvent.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows filtering of a controller callable. + * + * You can call getController() to retrieve the current controller. With + * setController() you can set a new controller that is used in the processing + * of the request. + * + * Controllers should be callables. + * + * @author Bernhard Schussek + */ +class FilterControllerEvent extends KernelEvent +{ + private $controller; + + public function __construct(HttpKernelInterface $kernel, $controller, Request $request, $requestType) + { + parent::__construct($kernel, $request, $requestType); + + $this->setController($controller); + } + + /** + * Returns the current controller. + * + * @return callable + */ + public function getController() + { + return $this->controller; + } + + /** + * Sets a new controller. + * + * @param callable $controller + * + * @throws \LogicException + */ + public function setController($controller) + { + // controller must be a callable + if (!\is_callable($controller)) { + throw new \LogicException(sprintf('The controller must be a callable (%s given).', $this->varToString($controller))); + } + + $this->controller = $controller; + } + + private function varToString($var) + { + if (\is_object($var)) { + return sprintf('Object(%s)', \get_class($var)); + } + + if (\is_array($var)) { + $a = array(); + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf('Array(%s)', implode(', ', $a)); + } + + if (\is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/http-kernel/Event/FilterResponseEvent.php b/vendor/symfony/http-kernel/Event/FilterResponseEvent.php new file mode 100644 index 0000000..1b80e34 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FilterResponseEvent.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to filter a Response object. + * + * You can call getResponse() to retrieve the current response. With + * setResponse() you can set a new response that will be returned to the + * browser. + * + * @author Bernhard Schussek + */ +class FilterResponseEvent extends KernelEvent +{ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, Response $response) + { + parent::__construct($kernel, $request, $requestType); + + $this->setResponse($response); + } + + /** + * Returns the current response object. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a new response object. + */ + public function setResponse(Response $response) + { + $this->response = $response; + } +} diff --git a/vendor/symfony/http-kernel/Event/FinishRequestEvent.php b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php new file mode 100644 index 0000000..ee72484 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/FinishRequestEvent.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +/** + * Triggered whenever a request is fully processed. + * + * @author Benjamin Eberlei + */ +class FinishRequestEvent extends KernelEvent +{ +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseEvent.php b/vendor/symfony/http-kernel/Event/GetResponseEvent.php new file mode 100644 index 0000000..c25a0f1 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseEvent.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Response; + +/** + * Allows to create a response for a request. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +class GetResponseEvent extends KernelEvent +{ + private $response; + + /** + * Returns the response object. + * + * @return Response|null + */ + public function getResponse() + { + return $this->response; + } + + /** + * Sets a response and stops event propagation. + */ + public function setResponse(Response $response) + { + $this->response = $response; + + $this->stopPropagation(); + } + + /** + * Returns whether a response was set. + * + * @return bool Whether a response was set + */ + public function hasResponse() + { + return null !== $this->response; + } +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php b/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php new file mode 100644 index 0000000..d68eaa1 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForControllerResultEvent.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to create a response for the return value of a controller. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * @author Bernhard Schussek + */ +class GetResponseForControllerResultEvent extends GetResponseEvent +{ + /** + * The return value of the controller. + * + * @var mixed + */ + private $controllerResult; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, $controllerResult) + { + parent::__construct($kernel, $request, $requestType); + + $this->controllerResult = $controllerResult; + } + + /** + * Returns the return value of the controller. + * + * @return mixed The controller return value + */ + public function getControllerResult() + { + return $this->controllerResult; + } + + /** + * Assigns the return value of the controller. + * + * @param mixed $controllerResult The controller return value + */ + public function setControllerResult($controllerResult) + { + $this->controllerResult = $controllerResult; + } +} diff --git a/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php new file mode 100644 index 0000000..7d8a1d3 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/GetResponseForExceptionEvent.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to create a response for a thrown exception. + * + * Call setResponse() to set the response that will be returned for the + * current request. The propagation of this event is stopped as soon as a + * response is set. + * + * You can also call setException() to replace the thrown exception. This + * exception will be thrown if no response is set during processing of this + * event. + * + * @author Bernhard Schussek + */ +class GetResponseForExceptionEvent extends GetResponseEvent +{ + /** + * The exception object. + * + * @var \Exception + */ + private $exception; + + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType, \Exception $e) + { + parent::__construct($kernel, $request, $requestType); + + $this->setException($e); + } + + /** + * Returns the thrown exception. + * + * @return \Exception The thrown exception + */ + public function getException() + { + return $this->exception; + } + + /** + * Replaces the thrown exception. + * + * This exception will be thrown if no response is set in the event. + * + * @param \Exception $exception The thrown exception + */ + public function setException(\Exception $exception) + { + $this->exception = $exception; + } +} diff --git a/vendor/symfony/http-kernel/Event/KernelEvent.php b/vendor/symfony/http-kernel/Event/KernelEvent.php new file mode 100644 index 0000000..cccf01f --- /dev/null +++ b/vendor/symfony/http-kernel/Event/KernelEvent.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Base class for events thrown in the HttpKernel component. + * + * @author Bernhard Schussek + */ +class KernelEvent extends Event +{ + private $kernel; + private $request; + private $requestType; + + /** + * @param HttpKernelInterface $kernel The kernel in which this event was thrown + * @param Request $request The request the kernel is currently processing + * @param int $requestType The request type the kernel is currently processing; one of + * HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST + */ + public function __construct(HttpKernelInterface $kernel, Request $request, $requestType) + { + $this->kernel = $kernel; + $this->request = $request; + $this->requestType = $requestType; + } + + /** + * Returns the kernel in which this event was thrown. + * + * @return HttpKernelInterface + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Returns the request the kernel is currently processing. + * + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * Returns the request type the kernel is currently processing. + * + * @return int One of HttpKernelInterface::MASTER_REQUEST and + * HttpKernelInterface::SUB_REQUEST + */ + public function getRequestType() + { + return $this->requestType; + } + + /** + * Checks if this is a master request. + * + * @return bool True if the request is a master request + */ + public function isMasterRequest() + { + return HttpKernelInterface::MASTER_REQUEST === $this->requestType; + } +} diff --git a/vendor/symfony/http-kernel/Event/PostResponseEvent.php b/vendor/symfony/http-kernel/Event/PostResponseEvent.php new file mode 100644 index 0000000..0981e64 --- /dev/null +++ b/vendor/symfony/http-kernel/Event/PostResponseEvent.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Event; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Allows to execute logic after a response was sent. + * + * Since it's only triggered on master requests, the `getRequestType()` method + * will always return the value of `HttpKernelInterface::MASTER_REQUEST`. + * + * @author Jordi Boggiano + */ +class PostResponseEvent extends KernelEvent +{ + private $response; + + public function __construct(HttpKernelInterface $kernel, Request $request, Response $response) + { + parent::__construct($kernel, $request, HttpKernelInterface::MASTER_REQUEST); + + $this->response = $response; + } + + /** + * Returns the response for which this event was thrown. + * + * @return Response + */ + public function getResponse() + { + return $this->response; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php new file mode 100644 index 0000000..5ec5284 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/AddRequestFormatsListener.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Adds configured formats to each request. + * + * @author Gildas Quemener + */ +class AddRequestFormatsListener implements EventSubscriberInterface +{ + protected $formats; + + public function __construct(array $formats) + { + $this->formats = $formats; + } + + /** + * Adds request formats. + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + foreach ($this->formats as $format => $mimeTypes) { + $request->setFormat($format, $mimeTypes); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => array('onKernelRequest', 1)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php new file mode 100644 index 0000000..9c0b561 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DebugHandlersListener.php @@ -0,0 +1,148 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleEvent; +use Symfony\Component\Console\Output\ConsoleOutputInterface; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\Debug\ExceptionHandler; +use Symfony\Component\EventDispatcher\Event; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\KernelEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Configures errors and exceptions handlers. + * + * @author Nicolas Grekas + */ +class DebugHandlersListener implements EventSubscriberInterface +{ + private $exceptionHandler; + private $logger; + private $levels; + private $throwAt; + private $scream; + private $fileLinkFormat; + private $firstCall = true; + private $hasTerminatedWithException; + + /** + * @param callable|null $exceptionHandler A handler that will be called on Exception + * @param LoggerInterface|null $logger A PSR-3 logger + * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants + * @param int|null $throwAt Thrown errors in a bit field of E_* constants, or null to keep the current value + * @param bool $scream Enables/disables screaming mode, where even silenced errors are logged + * @param string $fileLinkFormat The format for links to source files + */ + public function __construct($exceptionHandler, LoggerInterface $logger = null, $levels = null, $throwAt = -1, $scream = true, $fileLinkFormat = null) + { + $this->exceptionHandler = $exceptionHandler; + $this->logger = $logger; + $this->levels = $levels; + $this->throwAt = is_numeric($throwAt) ? (int) $throwAt : (null === $throwAt ? null : ($throwAt ? -1 : null)); + $this->scream = (bool) $scream; + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + } + + /** + * Configures the error handler. + */ + public function configure(Event $event = null) + { + if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMasterRequest()) { + return; + } + $this->firstCall = $this->hasTerminatedWithException = false; + + $handler = set_exception_handler('var_dump'); + $handler = \is_array($handler) ? $handler[0] : null; + restore_exception_handler(); + + if ($this->logger || null !== $this->throwAt) { + if ($handler instanceof ErrorHandler) { + if ($this->logger) { + $handler->setDefaultLogger($this->logger, $this->levels); + if (\is_array($this->levels)) { + $scream = 0; + foreach ($this->levels as $type => $log) { + $scream |= $type; + } + } else { + $scream = null === $this->levels ? E_ALL | E_STRICT : $this->levels; + } + if ($this->scream) { + $handler->screamAt($scream); + } + $this->logger = $this->levels = null; + } + if (null !== $this->throwAt) { + $handler->throwAt($this->throwAt, true); + } + } + } + if (!$this->exceptionHandler) { + if ($event instanceof KernelEvent) { + if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) { + $request = $event->getRequest(); + $hasRun = &$this->hasTerminatedWithException; + $this->exceptionHandler = function (\Exception $e) use ($kernel, $request, &$hasRun) { + if ($hasRun) { + throw $e; + } + $hasRun = true; + $kernel->terminateWithException($e, $request); + }; + } + } elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) { + $output = $event->getOutput(); + if ($output instanceof ConsoleOutputInterface) { + $output = $output->getErrorOutput(); + } + $this->exceptionHandler = function ($e) use ($app, $output) { + $app->renderException($e, $output); + }; + } + } + if ($this->exceptionHandler) { + if ($handler instanceof ErrorHandler) { + $h = $handler->setExceptionHandler('var_dump'); + if (\is_array($h) && $h[0] instanceof ExceptionHandler) { + $handler->setExceptionHandler($h); + $handler = $h[0]; + } else { + $handler->setExceptionHandler($this->exceptionHandler); + } + } + if ($handler instanceof ExceptionHandler) { + $handler->setHandler($this->exceptionHandler); + if (null !== $this->fileLinkFormat) { + $handler->setFileLinkFormat($this->fileLinkFormat); + } + } + $this->exceptionHandler = null; + } + } + + public static function getSubscribedEvents() + { + $events = array(KernelEvents::REQUEST => array('configure', 2048)); + + if (\defined('Symfony\Component\Console\ConsoleEvents::COMMAND')) { + $events[ConsoleEvents::COMMAND] = array('configure', 2048); + } + + return $events; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/DumpListener.php b/vendor/symfony/http-kernel/EventListener/DumpListener.php new file mode 100644 index 0000000..783ae25 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/DumpListener.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; +use Symfony\Component\VarDumper\VarDumper; + +/** + * Configures dump() handler. + * + * @author Nicolas Grekas + */ +class DumpListener implements EventSubscriberInterface +{ + private $cloner; + private $dumper; + + public function __construct(ClonerInterface $cloner, DataDumperInterface $dumper) + { + $this->cloner = $cloner; + $this->dumper = $dumper; + } + + public function configure() + { + $cloner = $this->cloner; + $dumper = $this->dumper; + + VarDumper::setHandler(function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }); + } + + public static function getSubscribedEvents() + { + // Register early to have a working dump() as early as possible + return array(ConsoleEvents::COMMAND => array('configure', 1024)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ErrorsLoggerListener.php b/vendor/symfony/http-kernel/EventListener/ErrorsLoggerListener.php new file mode 100644 index 0000000..10f2d6c --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ErrorsLoggerListener.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +@trigger_error('The '.__NAMESPACE__.'\ErrorsLoggerListener class is deprecated since Symfony 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpKernel\EventListener\DebugHandlersListener class instead.', E_USER_DEPRECATED); + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\ErrorHandler; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Injects the logger into the ErrorHandler, so that it can log various errors. + * + * @author Colin Frei + * @author Konstantin Myakshin + * + * @deprecated since version 2.6, to be removed in 3.0. Use the DebugHandlersListener class instead. + */ +class ErrorsLoggerListener implements EventSubscriberInterface +{ + private $channel; + private $logger; + + public function __construct($channel, LoggerInterface $logger = null) + { + $this->channel = $channel; + $this->logger = $logger; + } + + public function injectLogger() + { + if (null !== $this->logger) { + ErrorHandler::setLogger($this->logger, $this->channel); + $this->logger = null; + } + } + + public static function getSubscribedEvents() + { + return array(KernelEvents::REQUEST => array('injectLogger', 2048)); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/EsiListener.php b/vendor/symfony/http-kernel/EventListener/EsiListener.php new file mode 100644 index 0000000..3deaf65 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/EsiListener.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +@trigger_error('The '.__NAMESPACE__.'\EsiListener class is deprecated since Symfony 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpKernel\EventListener\SurrogateListener class instead.', E_USER_DEPRECATED); + +/** + * EsiListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for ESI. + * + * @author Fabien Potencier + * + * @deprecated since version 2.6, to be removed in 3.0. Use SurrogateListener instead + */ +class EsiListener extends SurrogateListener +{ +} diff --git a/vendor/symfony/http-kernel/EventListener/ExceptionListener.php b/vendor/symfony/http-kernel/EventListener/ExceptionListener.php new file mode 100644 index 0000000..16de178 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ExceptionListener.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Debug\Exception\FlattenException; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Log\DebugLoggerInterface; + +/** + * ExceptionListener. + * + * @author Fabien Potencier + */ +class ExceptionListener implements EventSubscriberInterface +{ + protected $controller; + protected $logger; + protected $debug; + + public function __construct($controller, LoggerInterface $logger = null, $debug = false) + { + $this->controller = $controller; + $this->logger = $logger; + $this->debug = $debug; + } + + public function onKernelException(GetResponseForExceptionEvent $event) + { + $exception = $event->getException(); + $request = $event->getRequest(); + $eventDispatcher = \func_num_args() > 2 ? func_get_arg(2) : null; + + $this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', \get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine())); + + $request = $this->duplicateRequest($exception, $request); + + try { + $response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + $this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', \get_class($e), $e->getMessage(), $e->getFile(), $e->getLine())); + + $wrapper = $e; + + while ($prev = $wrapper->getPrevious()) { + if ($exception === $wrapper = $prev) { + throw $e; + } + } + + $prev = new \ReflectionProperty('Exception', 'previous'); + $prev->setAccessible(true); + $prev->setValue($wrapper, $exception); + + throw $e; + } + + $event->setResponse($response); + + if ($this->debug && $eventDispatcher instanceof EventDispatcherInterface) { + $cspRemovalListener = function (FilterResponseEvent $event) use (&$cspRemovalListener, $eventDispatcher) { + $event->getResponse()->headers->remove('Content-Security-Policy'); + $eventDispatcher->removeListener(KernelEvents::RESPONSE, $cspRemovalListener); + }; + $eventDispatcher->addListener(KernelEvents::RESPONSE, $cspRemovalListener, -128); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::EXCEPTION => array('onKernelException', -128), + ); + } + + /** + * Logs an exception. + * + * @param \Exception $exception The \Exception instance + * @param string $message The error message to log + */ + protected function logException(\Exception $exception, $message) + { + if (null !== $this->logger) { + if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) { + $this->logger->critical($message, array('exception' => $exception)); + } else { + $this->logger->error($message, array('exception' => $exception)); + } + } + } + + /** + * Clones the request for the exception. + * + * @param \Exception $exception The thrown exception + * @param Request $request The original request + * + * @return Request The cloned request + */ + protected function duplicateRequest(\Exception $exception, Request $request) + { + $attributes = array( + '_controller' => $this->controller, + 'exception' => FlattenException::create($exception), + 'logger' => $this->logger instanceof DebugLoggerInterface ? $this->logger : null, + // keep for BC -- as $format can be an argument of the controller callable + // see src/Symfony/Bundle/TwigBundle/Controller/ExceptionController.php + // @deprecated since version 2.4, to be removed in 3.0 + 'format' => $request->getRequestFormat(), + ); + $request = $request->duplicate(null, null, $attributes); + $request->setMethod('GET'); + + return $request; + } +} diff --git a/vendor/symfony/http-kernel/EventListener/FragmentListener.php b/vendor/symfony/http-kernel/EventListener/FragmentListener.php new file mode 100644 index 0000000..ecba72b --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/FragmentListener.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * Handles content fragments represented by special URIs. + * + * All URL paths starting with /_fragment are handled as + * content fragments by this listener. + * + * If throws an AccessDeniedHttpException exception if the request + * is not signed or if it is not an internal sub-request. + * + * @author Fabien Potencier + */ +class FragmentListener implements EventSubscriberInterface +{ + private $signer; + private $fragmentPath; + + /** + * @param UriSigner $signer A UriSigner instance + * @param string $fragmentPath The path that triggers this listener + */ + public function __construct(UriSigner $signer, $fragmentPath = '/_fragment') + { + $this->signer = $signer; + $this->fragmentPath = $fragmentPath; + } + + /** + * Fixes request attributes when the path is '/_fragment'. + * + * @throws AccessDeniedHttpException if the request does not come from a trusted IP + */ + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + if ($this->fragmentPath !== rawurldecode($request->getPathInfo())) { + return; + } + + if ($request->attributes->has('_controller')) { + // Is a sub-request: no need to parse _path but it should still be removed from query parameters as below. + $request->query->remove('_path'); + + return; + } + + if ($event->isMasterRequest()) { + $this->validateRequest($request); + } + + parse_str($request->query->get('_path', ''), $attributes); + $request->attributes->add($attributes); + $request->attributes->set('_route_params', array_replace($request->attributes->get('_route_params', array()), $attributes)); + $request->query->remove('_path'); + } + + protected function validateRequest(Request $request) + { + // is the Request safe? + if (!$request->isMethodSafe(false)) { + throw new AccessDeniedHttpException(); + } + + // is the Request signed? + // we cannot use $request->getUri() here as we want to work with the original URI (no query string reordering) + if ($this->signer->check($request->getSchemeAndHttpHost().$request->getBaseUrl().$request->getPathInfo().(null !== ($qs = $request->server->get('QUERY_STRING')) ? '?'.$qs : ''))) { + return; + } + + throw new AccessDeniedHttpException(); + } + + /** + * @deprecated since version 2.3.19, to be removed in 3.0. + * + * @return string[] + */ + protected function getLocalIpAddresses() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3.19 and will be removed in 3.0.', E_USER_DEPRECATED); + + return array('127.0.0.1', 'fe80::1', '::1'); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 48)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/LocaleListener.php b/vendor/symfony/http-kernel/EventListener/LocaleListener.php new file mode 100644 index 0000000..de7f370 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/LocaleListener.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * Initializes the locale based on the current request. + * + * This listener works in 2 modes: + * + * * 2.3 compatibility mode where you must call setRequest whenever the Request changes. + * * 2.4+ mode where you must pass a RequestStack instance in the constructor. + * + * @author Fabien Potencier + */ +class LocaleListener implements EventSubscriberInterface +{ + private $router; + private $defaultLocale; + private $requestStack; + + /** + * RequestStack will become required in 3.0. + * + * @param RequestStack $requestStack A RequestStack instance + * @param string $defaultLocale The default locale + * @param RequestContextAwareInterface|null $router The router + * + * @throws \InvalidArgumentException + */ + public function __construct($requestStack = null, $defaultLocale = 'en', $router = null) + { + if ((null !== $requestStack && !$requestStack instanceof RequestStack) || $defaultLocale instanceof RequestContextAwareInterface || $router instanceof RequestStack) { + $tmp = $router; + $router = \func_num_args() < 2 ? null : $defaultLocale; + $defaultLocale = $requestStack; + $requestStack = \func_num_args() < 3 ? null : $tmp; + + @trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as first argument as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } elseif (!$requestStack instanceof RequestStack) { + @trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } + + if (null !== $requestStack && !$requestStack instanceof RequestStack) { + throw new \InvalidArgumentException('RequestStack instance expected.'); + } + if (null !== $router && !$router instanceof RequestContextAwareInterface) { + throw new \InvalidArgumentException('Router must implement RequestContextAwareInterface.'); + } + + $this->defaultLocale = $defaultLocale; + $this->requestStack = $requestStack; + $this->router = $router; + } + + /** + * Sets the current Request. + * + * This method was used to synchronize the Request, but as the HttpKernel + * is doing that automatically now, you should never call it directly. + * It is kept public for BC with the 2.3 version. + * + * @param Request|null $request A Request instance + * + * @deprecated since version 2.4, to be removed in 3.0. + */ + public function setRequest(Request $request = null) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED); + + if (null === $request) { + return; + } + + $this->setLocale($request); + $this->setRouterContext($request); + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + $request->setDefaultLocale($this->defaultLocale); + + $this->setLocale($request); + $this->setRouterContext($request); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null === $this->requestStack) { + return; // removed when requestStack is required + } + + if (null !== $parentRequest = $this->requestStack->getParentRequest()) { + $this->setRouterContext($parentRequest); + } + } + + private function setLocale(Request $request) + { + if ($locale = $request->attributes->get('_locale')) { + $request->setLocale($locale); + } + } + + private function setRouterContext(Request $request) + { + if (null !== $this->router) { + $this->router->getContext()->setParameter('_locale', $request->getLocale()); + } + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Router to have access to the _locale + KernelEvents::REQUEST => array(array('onKernelRequest', 16)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ProfilerListener.php b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php new file mode 100644 index 0000000..9592c9f --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ProfilerListener.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\RequestMatcherInterface; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\HttpKernel\Profiler\Profiler; + +/** + * ProfilerListener collects data for the current request by listening to the kernel events. + * + * @author Fabien Potencier + */ +class ProfilerListener implements EventSubscriberInterface +{ + protected $profiler; + protected $matcher; + protected $onlyException; + protected $onlyMasterRequests; + protected $exception; + protected $requests = array(); + protected $profiles; + protected $requestStack; + protected $parents; + + /** + * @param Profiler $profiler A Profiler instance + * @param RequestStack $requestStack A RequestStack instance + * @param RequestMatcherInterface|null $matcher A RequestMatcher instance + * @param bool $onlyException True if the profiler only collects data when an exception occurs, false otherwise + * @param bool $onlyMasterRequests True if the profiler only collects data when the request is a master request, false otherwise + */ + public function __construct(Profiler $profiler, $requestStack = null, $matcher = null, $onlyException = false, $onlyMasterRequests = false) + { + if ($requestStack instanceof RequestMatcherInterface || (null !== $matcher && !$matcher instanceof RequestMatcherInterface) || $onlyMasterRequests instanceof RequestStack) { + $tmp = $onlyMasterRequests; + $onlyMasterRequests = $onlyException; + $onlyException = $matcher; + $matcher = $requestStack; + $requestStack = \func_num_args() < 5 ? null : $tmp; + + @trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as second argument as '.__CLASS__.'::onKernelRequest method will be removed in 3.0.', E_USER_DEPRECATED); + } elseif (!$requestStack instanceof RequestStack) { + @trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::onKernelRequest method will be removed in 3.0.', E_USER_DEPRECATED); + } + + if (null !== $requestStack && !$requestStack instanceof RequestStack) { + throw new \InvalidArgumentException('RequestStack instance expected.'); + } + if (null !== $matcher && !$matcher instanceof RequestMatcherInterface) { + throw new \InvalidArgumentException('Matcher must implement RequestMatcherInterface.'); + } + + $this->profiler = $profiler; + $this->matcher = $matcher; + $this->onlyException = (bool) $onlyException; + $this->onlyMasterRequests = (bool) $onlyMasterRequests; + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + $this->requestStack = $requestStack; + } + + /** + * Handles the onKernelException event. + */ + public function onKernelException(GetResponseForExceptionEvent $event) + { + if ($this->onlyMasterRequests && !$event->isMasterRequest()) { + return; + } + + $this->exception = $event->getException(); + } + + /** + * @deprecated since version 2.4, to be removed in 3.0. + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (null === $this->requestStack) { + $this->requests[] = $event->getRequest(); + } + } + + /** + * Handles the onKernelResponse event. + */ + public function onKernelResponse(FilterResponseEvent $event) + { + $master = $event->isMasterRequest(); + if ($this->onlyMasterRequests && !$master) { + return; + } + + if ($this->onlyException && null === $this->exception) { + return; + } + + $request = $event->getRequest(); + $exception = $this->exception; + $this->exception = null; + + if (null !== $this->matcher && !$this->matcher->matches($request)) { + return; + } + + if (!$profile = $this->profiler->collect($request, $event->getResponse(), $exception)) { + return; + } + + $this->profiles[$request] = $profile; + + if (null !== $this->requestStack) { + $this->parents[$request] = $this->requestStack->getParentRequest(); + } elseif (!$master) { + // to be removed when requestStack is required + array_pop($this->requests); + + $this->parents[$request] = end($this->requests); + } + } + + public function onKernelTerminate(PostResponseEvent $event) + { + // attach children to parents + foreach ($this->profiles as $request) { + // isset call should be removed when requestStack is required + if (isset($this->parents[$request]) && null !== $parentRequest = $this->parents[$request]) { + if (isset($this->profiles[$parentRequest])) { + $this->profiles[$parentRequest]->addChild($this->profiles[$request]); + } + } + } + + // save profiles + foreach ($this->profiles as $request) { + $this->profiler->saveProfile($this->profiles[$request]); + } + + $this->profiles = new \SplObjectStorage(); + $this->parents = new \SplObjectStorage(); + $this->requests = array(); + } + + public static function getSubscribedEvents() + { + return array( + // kernel.request must be registered as early as possible to not break + // when an exception is thrown in any other kernel.request listener + KernelEvents::REQUEST => array('onKernelRequest', 1024), + KernelEvents::RESPONSE => array('onKernelResponse', -100), + KernelEvents::EXCEPTION => 'onKernelException', + KernelEvents::TERMINATE => array('onKernelTerminate', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ResponseListener.php b/vendor/symfony/http-kernel/EventListener/ResponseListener.php new file mode 100644 index 0000000..f247845 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ResponseListener.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * ResponseListener fixes the Response headers based on the Request. + * + * @author Fabien Potencier + */ +class ResponseListener implements EventSubscriberInterface +{ + private $charset; + + public function __construct($charset) + { + $this->charset = $charset; + } + + /** + * Filters the Response. + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + + if (null === $response->getCharset()) { + $response->setCharset($this->charset); + } + + $response->prepare($event->getRequest()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/RouterListener.php b/vendor/symfony/http-kernel/EventListener/RouterListener.php new file mode 100644 index 0000000..63c9d20 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/RouterListener.php @@ -0,0 +1,194 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * Initializes the context from the request and sets request attributes based on a matching route. + * + * This listener works in 2 modes: + * + * * 2.3 compatibility mode where you must call setRequest whenever the Request changes. + * * 2.4+ mode where you must pass a RequestStack instance in the constructor. + * + * @author Fabien Potencier + */ +class RouterListener implements EventSubscriberInterface +{ + private $matcher; + private $context; + private $logger; + private $request; + private $requestStack; + + /** + * RequestStack will become required in 3.0. + * + * @param UrlMatcherInterface|RequestMatcherInterface $matcher The Url or Request matcher + * @param RequestStack $requestStack A RequestStack instance + * @param RequestContext|null $context The RequestContext (can be null when $matcher implements RequestContextAwareInterface) + * @param LoggerInterface|null $logger The logger + * + * @throws \InvalidArgumentException + */ + public function __construct($matcher, $requestStack = null, $context = null, $logger = null) + { + if ($requestStack instanceof RequestContext || $context instanceof LoggerInterface || $logger instanceof RequestStack) { + $tmp = $requestStack; + $requestStack = $logger; + $logger = $context; + $context = $tmp; + + @trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as second argument as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } elseif (!$requestStack instanceof RequestStack) { + @trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } + + if (null !== $requestStack && !$requestStack instanceof RequestStack) { + throw new \InvalidArgumentException('RequestStack instance expected.'); + } + if (null !== $context && !$context instanceof RequestContext) { + throw new \InvalidArgumentException('RequestContext instance expected.'); + } + if (null !== $logger && !$logger instanceof LoggerInterface) { + throw new \InvalidArgumentException('Logger must implement LoggerInterface.'); + } + + if (!$matcher instanceof UrlMatcherInterface && !$matcher instanceof RequestMatcherInterface) { + throw new \InvalidArgumentException('Matcher must either implement UrlMatcherInterface or RequestMatcherInterface.'); + } + + if (null === $context && !$matcher instanceof RequestContextAwareInterface) { + throw new \InvalidArgumentException('You must either pass a RequestContext or the matcher must implement RequestContextAwareInterface.'); + } + + $this->matcher = $matcher; + $this->context = $context ?: $matcher->getContext(); + $this->requestStack = $requestStack; + $this->logger = $logger; + } + + /** + * Sets the current Request. + * + * This method was used to synchronize the Request, but as the HttpKernel + * is doing that automatically now, you should never call it directly. + * It is kept public for BC with the 2.3 version. + * + * @param Request|null $request A Request instance + * + * @deprecated since version 2.4, to be removed in 3.0. + */ + public function setRequest(Request $request = null) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be made private in 3.0.', E_USER_DEPRECATED); + + $this->setCurrentRequest($request); + } + + private function setCurrentRequest(Request $request = null) + { + if (null !== $request && $this->request !== $request) { + try { + $this->context->fromRequest($request); + } catch (\UnexpectedValueException $e) { + throw new BadRequestHttpException($e->getMessage(), $e, $e->getCode()); + } + } + + $this->request = $request; + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null === $this->requestStack) { + return; // removed when requestStack is required + } + + $this->setCurrentRequest($this->requestStack->getParentRequest()); + } + + public function onKernelRequest(GetResponseEvent $event) + { + $request = $event->getRequest(); + + // initialize the context that is also used by the generator (assuming matcher and generator share the same context instance) + // we call setCurrentRequest even if most of the time, it has already been done to keep compatibility + // with frameworks which do not use the Symfony service container + // when we have a RequestStack, no need to do it + if (null !== $this->requestStack) { + $this->setCurrentRequest($request); + } + + if ($request->attributes->has('_controller')) { + // routing is already done + return; + } + + // add attributes based on the request (routing) + try { + // matching a request is more powerful than matching a URL path + context, so try that first + if ($this->matcher instanceof RequestMatcherInterface) { + $parameters = $this->matcher->matchRequest($request); + } else { + $parameters = $this->matcher->match($request->getPathInfo()); + } + + if (null !== $this->logger) { + $this->logger->info(sprintf('Matched route "%s".', isset($parameters['_route']) ? $parameters['_route'] : 'n/a'), array( + 'route_parameters' => $parameters, + 'request_uri' => $request->getUri(), + )); + } + + $request->attributes->add($parameters); + unset($parameters['_route'], $parameters['_controller']); + $request->attributes->set('_route_params', $parameters); + } catch (ResourceNotFoundException $e) { + $message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo()); + + if ($referer = $request->headers->get('referer')) { + $message .= sprintf(' (from "%s")', $referer); + } + + throw new NotFoundHttpException($message, $e); + } catch (MethodNotAllowedException $e) { + $message = sprintf('No route found for "%s %s": Method Not Allowed (Allow: %s)', $request->getMethod(), $request->getPathInfo(), implode(', ', $e->getAllowedMethods())); + + throw new MethodNotAllowedHttpException($e->getAllowedMethods(), $message, $e); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array(array('onKernelRequest', 32)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php b/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php new file mode 100644 index 0000000..36809b5 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SaveSessionListener.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Saves the session, in case it is still open, before sending the response/headers. + * + * This ensures several things in case the developer did not save the session explicitly: + * + * * If a session save handler without locking is used, it ensures the data is available + * on the next request, e.g. after a redirect. PHPs auto-save at script end via + * session_register_shutdown is executed after fastcgi_finish_request. So in this case + * the data could be missing the next request because it might not be saved the moment + * the new request is processed. + * * A locking save handler (e.g. the native 'files') circumvents concurrency problems like + * the one above. But by saving the session before long-running things in the terminate event, + * we ensure the session is not blocked longer than needed. + * * When regenerating the session ID no locking is involved in PHPs session design. See + * https://bugs.php.net/bug.php?id=61470 for a discussion. So in this case, the session must + * be saved anyway before sending the headers with the new session ID. Otherwise session + * data could get lost again for concurrent requests with the new ID. One result could be + * that you get logged out after just logging in. + * + * This listener should be executed as one of the last listeners, so that previous listeners + * can still operate on the open session. This prevents the overhead of restarting it. + * Listeners after closing the session can still work with the session as usual because + * Symfonys session implementation starts the session on demand. So writing to it after + * it is saved will just restart it. + * + * @author Tobias Schultze + */ +class SaveSessionListener implements EventSubscriberInterface +{ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $session = $event->getRequest()->getSession(); + if ($session && $session->isStarted()) { + $session->save(); + } + } + + public static function getSubscribedEvents() + { + return array( + // low priority but higher than StreamedResponseListener + KernelEvents::RESPONSE => array(array('onKernelResponse', -1000)), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SessionListener.php b/vendor/symfony/http-kernel/EventListener/SessionListener.php new file mode 100644 index 0000000..a198e1a --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SessionListener.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Sets the session in the request. + * + * @author Johannes M. Schmitt + */ +abstract class SessionListener implements EventSubscriberInterface +{ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $request = $event->getRequest(); + $session = $this->getSession(); + if (null === $session || $request->hasSession()) { + return; + } + + $request->setSession($session); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 128), + ); + } + + /** + * Gets the session object. + * + * @return SessionInterface|null A SessionInterface instance or null if no session is available + */ + abstract protected function getSession(); +} diff --git a/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php b/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php new file mode 100644 index 0000000..2c616b9 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/StreamedResponseListener.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * StreamedResponseListener is responsible for sending the Response + * to the client. + * + * @author Fabien Potencier + */ +class StreamedResponseListener implements EventSubscriberInterface +{ + /** + * Filters the Response. + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $response = $event->getResponse(); + + if ($response instanceof StreamedResponse) { + $response->send(); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => array('onKernelResponse', -1024), + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/SurrogateListener.php b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php new file mode 100644 index 0000000..8426cf2 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/SurrogateListener.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * SurrogateListener adds a Surrogate-Control HTTP header when the Response needs to be parsed for Surrogates. + * + * @author Fabien Potencier + */ +class SurrogateListener implements EventSubscriberInterface +{ + private $surrogate; + + public function __construct(SurrogateInterface $surrogate = null) + { + $this->surrogate = $surrogate; + } + + /** + * Filters the Response. + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest() || null === $this->surrogate) { + return; + } + + $this->surrogate->addSurrogateControl($event->getResponse()); + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::RESPONSE => 'onKernelResponse', + ); + } +} diff --git a/vendor/symfony/http-kernel/EventListener/TestSessionListener.php b/vendor/symfony/http-kernel/EventListener/TestSessionListener.php new file mode 100644 index 0000000..a3e974e --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TestSessionListener.php @@ -0,0 +1,82 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * TestSessionListener. + * + * Saves session in test environment. + * + * @author Bulat Shakirzyanov + * @author Fabien Potencier + */ +abstract class TestSessionListener implements EventSubscriberInterface +{ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + // bootstrap the session + $session = $this->getSession(); + if (!$session) { + return; + } + + $cookies = $event->getRequest()->cookies; + + if ($cookies->has($session->getName())) { + $session->setId($cookies->get($session->getName())); + } + } + + /** + * Checks if session was initialized and saves if current request is master + * Runs on 'kernel.response' in test environment. + */ + public function onKernelResponse(FilterResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + + $session = $event->getRequest()->getSession(); + if ($session && $session->isStarted()) { + $session->save(); + $params = session_get_cookie_params(); + $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly'])); + } + } + + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array('onKernelRequest', 192), + KernelEvents::RESPONSE => array('onKernelResponse', -128), + ); + } + + /** + * Gets the session object. + * + * @return SessionInterface|null A SessionInterface instance or null if no session is available + */ + abstract protected function getSession(); +} diff --git a/vendor/symfony/http-kernel/EventListener/TranslatorListener.php b/vendor/symfony/http-kernel/EventListener/TranslatorListener.php new file mode 100644 index 0000000..2a5fc71 --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/TranslatorListener.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\Translation\TranslatorInterface; + +/** + * Synchronizes the locale between the request and the translator. + * + * @author Fabien Potencier + */ +class TranslatorListener implements EventSubscriberInterface +{ + private $translator; + private $requestStack; + + public function __construct(TranslatorInterface $translator, RequestStack $requestStack) + { + $this->translator = $translator; + $this->requestStack = $requestStack; + } + + public function onKernelRequest(GetResponseEvent $event) + { + $this->setLocale($event->getRequest()); + } + + public function onKernelFinishRequest(FinishRequestEvent $event) + { + if (null === $parentRequest = $this->requestStack->getParentRequest()) { + return; + } + + $this->setLocale($parentRequest); + } + + public static function getSubscribedEvents() + { + return array( + // must be registered after the Locale listener + KernelEvents::REQUEST => array(array('onKernelRequest', 10)), + KernelEvents::FINISH_REQUEST => array(array('onKernelFinishRequest', 0)), + ); + } + + private function setLocale(Request $request) + { + try { + $this->translator->setLocale($request->getLocale()); + } catch (\InvalidArgumentException $e) { + $this->translator->setLocale($request->getDefaultLocale()); + } + } +} diff --git a/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php new file mode 100644 index 0000000..cbeee3f --- /dev/null +++ b/vendor/symfony/http-kernel/EventListener/ValidateRequestListener.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Validates that the headers and other information indicating the + * client IP address of a request are consistent. + * + * @author Magnus Nordlander + */ +class ValidateRequestListener implements EventSubscriberInterface +{ + /** + * Performs the validation. + */ + public function onKernelRequest(GetResponseEvent $event) + { + if (!$event->isMasterRequest()) { + return; + } + $request = $event->getRequest(); + + if ($request::getTrustedProxies()) { + // This will throw an exception if the headers are inconsistent. + $request->getClientIps(); + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return array( + KernelEvents::REQUEST => array( + array('onKernelRequest', 256), + ), + ); + } +} diff --git a/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php new file mode 100644 index 0000000..3941884 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/AccessDeniedHttpException.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Fabien Potencier + * @author Christophe Coevoet + */ +class AccessDeniedHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(403, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php new file mode 100644 index 0000000..c28d837 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/BadRequestHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class BadRequestHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(400, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ConflictHttpException.php b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php new file mode 100644 index 0000000..79f24f2 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ConflictHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class ConflictHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(409, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/FatalErrorException.php b/vendor/symfony/http-kernel/Exception/FatalErrorException.php new file mode 100644 index 0000000..0360ac0 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/FatalErrorException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +@trigger_error('The '.__NAMESPACE__.'\FatalErrorException class is deprecated since Symfony 2.3 and will be removed in 3.0. Use the Symfony\Component\Debug\Exception\FatalErrorException class instead.', E_USER_DEPRECATED); + +/* + * Fatal Error Exception. + * + * @author Konstanton Myakshin + * + * @deprecated since version 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class_exists('Symfony\Component\Debug\Exception\FatalErrorException'); diff --git a/vendor/symfony/http-kernel/Exception/FlattenException.php b/vendor/symfony/http-kernel/Exception/FlattenException.php new file mode 100644 index 0000000..9ef7abb --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/FlattenException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +@trigger_error('The '.__NAMESPACE__.'\FlattenException class is deprecated since Symfony 2.3 and will be removed in 3.0. Use the Symfony\Component\Debug\Exception\FlattenException class instead.', E_USER_DEPRECATED); + +/* + * FlattenException wraps a PHP Exception to be able to serialize it. + * + * Basically, this class removes all objects from the trace. + * + * @author Fabien Potencier + * + * @deprecated since version 2.3, to be removed in 3.0. Use the same class from the Debug component instead. + */ +class_exists('Symfony\Component\Debug\Exception\FlattenException'); diff --git a/vendor/symfony/http-kernel/Exception/GoneHttpException.php b/vendor/symfony/http-kernel/Exception/GoneHttpException.php new file mode 100644 index 0000000..84e6915 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/GoneHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class GoneHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(410, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpException.php b/vendor/symfony/http-kernel/Exception/HttpException.php new file mode 100644 index 0000000..4e1b526 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * HttpException. + * + * @author Kris Wallsmith + */ +class HttpException extends \RuntimeException implements HttpExceptionInterface +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = array(), $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php new file mode 100644 index 0000000..8aa50a9 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/HttpExceptionInterface.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * Interface for HTTP error exceptions. + * + * @author Kris Wallsmith + */ +interface HttpExceptionInterface +{ + /** + * Returns the status code. + * + * @return int An HTTP response status code + */ + public function getStatusCode(); + + /** + * Returns response headers. + * + * @return array Response headers + */ + public function getHeaders(); +} diff --git a/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php new file mode 100644 index 0000000..645efe8 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/LengthRequiredHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class LengthRequiredHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(411, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php new file mode 100644 index 0000000..e308a5f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/MethodNotAllowedHttpException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Kris Wallsmith + */ +class MethodNotAllowedHttpException extends HttpException +{ + /** + * @param array $allow An array of allowed methods + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct(array $allow, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('Allow' => strtoupper(implode(', ', $allow))); + + parent::__construct(405, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php new file mode 100644 index 0000000..097a13f --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotAcceptableHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class NotAcceptableHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(406, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php new file mode 100644 index 0000000..878173c --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/NotFoundHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Fabien Potencier + */ +class NotFoundHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(404, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php new file mode 100644 index 0000000..9f13a62 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionFailedHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class PreconditionFailedHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(412, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php new file mode 100644 index 0000000..9d0a36d --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/PreconditionRequiredHttpException.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class PreconditionRequiredHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(428, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php new file mode 100644 index 0000000..b2767c3 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/ServiceUnavailableHttpException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class ServiceUnavailableHttpException extends HttpException +{ + /** + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(503, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php new file mode 100644 index 0000000..7d8a803 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/TooManyRequestsHttpException.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + * + * @see http://tools.ietf.org/html/rfc6585 + */ +class TooManyRequestsHttpException extends HttpException +{ + /** + * @param int|string $retryAfter The number of seconds or HTTP-date after which the request may be retried + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($retryAfter = null, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array(); + if ($retryAfter) { + $headers = array('Retry-After' => $retryAfter); + } + + parent::__construct(429, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php new file mode 100644 index 0000000..05ac875 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnauthorizedHttpException.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class UnauthorizedHttpException extends HttpException +{ + /** + * @param string $challenge WWW-Authenticate challenge string + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($challenge, $message = null, \Exception $previous = null, $code = 0) + { + $headers = array('WWW-Authenticate' => $challenge); + + parent::__construct(401, $message, $previous, $headers, $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php new file mode 100644 index 0000000..01b8b84 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnprocessableEntityHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Steve Hutchins + */ +class UnprocessableEntityHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(422, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php new file mode 100644 index 0000000..6913504 --- /dev/null +++ b/vendor/symfony/http-kernel/Exception/UnsupportedMediaTypeHttpException.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Exception; + +/** + * @author Ben Ramsey + */ +class UnsupportedMediaTypeHttpException extends HttpException +{ + /** + * @param string $message The internal exception message + * @param \Exception $previous The previous exception + * @param int $code The internal exception code + */ + public function __construct($message = null, \Exception $previous = null, $code = 0) + { + parent::__construct(415, $message, $previous, array(), $code); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php new file mode 100644 index 0000000..65474aa --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/AbstractSurrogateFragmentRenderer.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\HttpCache\SurrogateInterface; +use Symfony\Component\HttpKernel\UriSigner; + +/** + * Implements Surrogate rendering strategy. + * + * @author Fabien Potencier + */ +abstract class AbstractSurrogateFragmentRenderer extends RoutableFragmentRenderer +{ + private $surrogate; + private $inlineStrategy; + private $signer; + + /** + * The "fallback" strategy when surrogate is not available should always be an + * instance of InlineFragmentRenderer. + * + * @param SurrogateInterface $surrogate An Surrogate instance + * @param FragmentRendererInterface $inlineStrategy The inline strategy to use when the surrogate is not supported + * @param UriSigner $signer + */ + public function __construct(SurrogateInterface $surrogate = null, FragmentRendererInterface $inlineStrategy, UriSigner $signer = null) + { + $this->surrogate = $surrogate; + $this->inlineStrategy = $inlineStrategy; + $this->signer = $signer; + } + + /** + * {@inheritdoc} + * + * Note that if the current Request has no surrogate capability, this method + * falls back to use the inline rendering strategy. + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + * * comment: a comment to add when returning the surrogate tag + * + * Note, that not all surrogate strategies support all options. For now + * 'alt' and 'comment' are only supported by ESI. + * + * @see Symfony\Component\HttpKernel\HttpCache\SurrogateInterface + */ + public function render($uri, Request $request, array $options = array()) + { + if (!$this->surrogate || !$this->surrogate->hasSurrogateCapability($request)) { + return $this->inlineStrategy->render($uri, $request, $options); + } + + if ($uri instanceof ControllerReference) { + $uri = $this->generateSignedFragmentUri($uri, $request); + } + + $alt = isset($options['alt']) ? $options['alt'] : null; + if ($alt instanceof ControllerReference) { + $alt = $this->generateSignedFragmentUri($alt, $request); + } + + $tag = $this->surrogate->renderIncludeTag($uri, $alt, isset($options['ignore_errors']) ? $options['ignore_errors'] : false, isset($options['comment']) ? $options['comment'] : ''); + + return new Response($tag); + } + + private function generateSignedFragmentUri($uri, Request $request) + { + if (null === $this->signer) { + throw new \LogicException('You must use a URI when using the ESI rendering strategy or set a URL signer.'); + } + + // we need to sign the absolute URI, but want to return the path only. + $fragmentUri = $this->signer->sign($this->generateFragmentUri($uri, $request, true)); + + return substr($fragmentUri, \strlen($request->getSchemeAndHttpHost())); + } +} diff --git a/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php new file mode 100644 index 0000000..a4570e3 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/EsiFragmentRenderer.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +/** + * Implements the ESI rendering strategy. + * + * @author Fabien Potencier + */ +class EsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'esi'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentHandler.php b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php new file mode 100644 index 0000000..7eebd37 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentHandler.php @@ -0,0 +1,162 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Renders a URI that represents a resource fragment. + * + * This class handles the rendering of resource fragments that are included into + * a main resource. The handling of the rendering is managed by specialized renderers. + * + * This listener works in 2 modes: + * + * * 2.3 compatibility mode where you must call setRequest whenever the Request changes. + * * 2.4+ mode where you must pass a RequestStack instance in the constructor. + * + * @author Fabien Potencier + * + * @see FragmentRendererInterface + */ +class FragmentHandler +{ + private $debug; + private $renderers = array(); + private $request; + private $requestStack; + + /** + * RequestStack will become required in 3.0. + * + * @param RequestStack $requestStack The Request stack that controls the lifecycle of requests + * @param FragmentRendererInterface[] $renderers An array of FragmentRendererInterface instances + * @param bool $debug Whether the debug mode is enabled or not + */ + public function __construct($requestStack = null, $renderers = array(), $debug = false) + { + if (\is_array($requestStack)) { + $tmp = $debug; + $debug = \func_num_args() < 2 ? false : $renderers; + $renderers = $requestStack; + $requestStack = \func_num_args() < 3 ? null : $tmp; + + @trigger_error('The '.__METHOD__.' method now requires a RequestStack to be given as first argument as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } elseif (!$requestStack instanceof RequestStack) { + @trigger_error('The '.__METHOD__.' method now requires a RequestStack instance as '.__CLASS__.'::setRequest method will not be supported anymore in 3.0.', E_USER_DEPRECATED); + } + + if (null !== $requestStack && !$requestStack instanceof RequestStack) { + throw new \InvalidArgumentException('RequestStack instance expected.'); + } + if (!\is_array($renderers)) { + throw new \InvalidArgumentException('Renderers must be an array.'); + } + + $this->requestStack = $requestStack; + foreach ($renderers as $renderer) { + $this->addRenderer($renderer); + } + $this->debug = $debug; + } + + /** + * Adds a renderer. + */ + public function addRenderer(FragmentRendererInterface $renderer) + { + $this->renderers[$renderer->getName()] = $renderer; + } + + /** + * Sets the current Request. + * + * This method was used to synchronize the Request, but as the HttpKernel + * is doing that automatically now, you should never call it directly. + * It is kept public for BC with the 2.3 version. + * + * @param Request|null $request A Request instance + * + * @deprecated since version 2.4, to be removed in 3.0. + */ + public function setRequest(Request $request = null) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.4 and will be removed in 3.0.', E_USER_DEPRECATED); + + $this->request = $request; + } + + /** + * Renders a URI and returns the Response content. + * + * Available options: + * + * * ignore_errors: true to return an empty string in case of an error + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param string $renderer The renderer name + * @param array $options An array of options + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \InvalidArgumentException when the renderer does not exist + * @throws \LogicException when no master request is being handled + */ + public function render($uri, $renderer = 'inline', array $options = array()) + { + if (!isset($options['ignore_errors'])) { + $options['ignore_errors'] = !$this->debug; + } + + if (!isset($this->renderers[$renderer])) { + throw new \InvalidArgumentException(sprintf('The "%s" renderer does not exist.', $renderer)); + } + + if (!$request = $this->getRequest()) { + throw new \LogicException('Rendering a fragment can only be done when handling a Request.'); + } + + return $this->deliver($this->renderers[$renderer]->render($uri, $request, $options)); + } + + /** + * Delivers the Response as a string. + * + * When the Response is a StreamedResponse, the content is streamed immediately + * instead of being returned. + * + * @return string|null The Response content or null when the Response is streamed + * + * @throws \RuntimeException when the Response is not successful + */ + protected function deliver(Response $response) + { + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $this->getRequest()->getUri(), $response->getStatusCode())); + } + + if (!$response instanceof StreamedResponse) { + return $response->getContent(); + } + + $response->sendContent(); + } + + private function getRequest() + { + return $this->requestStack ? $this->requestStack->getCurrentRequest() : $this->request; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php new file mode 100644 index 0000000..bcf4e99 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/FragmentRendererInterface.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; + +/** + * Interface implemented by all rendering strategies. + * + * @author Fabien Potencier + */ +interface FragmentRendererInterface +{ + /** + * Renders a URI and returns the Response content. + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param Request $request A Request instance + * @param array $options An array of options + * + * @return Response A Response instance + */ + public function render($uri, Request $request, array $options = array()); + + /** + * Gets the name of the strategy. + * + * @return string The strategy name + */ + public function getName(); +} diff --git a/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php new file mode 100644 index 0000000..b9c8cb9 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/HIncludeFragmentRenderer.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\UriSigner; +use Symfony\Component\Templating\EngineInterface; +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Loader\ExistsLoaderInterface; + +/** + * Implements the Hinclude rendering strategy. + * + * @author Fabien Potencier + */ +class HIncludeFragmentRenderer extends RoutableFragmentRenderer +{ + private $globalDefaultTemplate; + private $signer; + private $templating; + private $charset; + + /** + * @param EngineInterface|Environment $templating An EngineInterface or a Twig instance + * @param UriSigner $signer A UriSigner instance + * @param string $globalDefaultTemplate The global default content (it can be a template name or the content) + * @param string $charset + */ + public function __construct($templating = null, UriSigner $signer = null, $globalDefaultTemplate = null, $charset = 'utf-8') + { + $this->setTemplating($templating); + $this->globalDefaultTemplate = $globalDefaultTemplate; + $this->signer = $signer; + $this->charset = $charset; + } + + /** + * Sets the templating engine to use to render the default content. + * + * @param EngineInterface|Environment|null $templating An EngineInterface or an Environment instance + * + * @throws \InvalidArgumentException + */ + public function setTemplating($templating) + { + if (null !== $templating && !$templating instanceof EngineInterface && !$templating instanceof Environment) { + throw new \InvalidArgumentException('The hinclude rendering strategy needs an instance of Twig\Environment or Symfony\Component\Templating\EngineInterface'); + } + + $this->templating = $templating; + } + + /** + * Checks if a templating engine has been set. + * + * @return bool true if the templating engine has been set, false otherwise + */ + public function hasTemplating() + { + return null !== $this->templating; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * default: The default content (it can be a template name or the content) + * * id: An optional hx:include tag id attribute + * * attributes: An optional array of hx:include tag attributes + */ + public function render($uri, Request $request, array $options = array()) + { + if ($uri instanceof ControllerReference) { + if (null === $this->signer) { + throw new \LogicException('You must use a proper URI when using the Hinclude rendering strategy or set a URL signer.'); + } + + // we need to sign the absolute URI, but want to return the path only. + $uri = substr($this->signer->sign($this->generateFragmentUri($uri, $request, true)), \strlen($request->getSchemeAndHttpHost())); + } + + // We need to replace ampersands in the URI with the encoded form in order to return valid html/xml content. + $uri = str_replace('&', '&', $uri); + + $template = isset($options['default']) ? $options['default'] : $this->globalDefaultTemplate; + if (null !== $this->templating && $template && $this->templateExists($template)) { + $content = $this->templating->render($template); + } else { + $content = $template; + } + + $attributes = isset($options['attributes']) && \is_array($options['attributes']) ? $options['attributes'] : array(); + if (isset($options['id']) && $options['id']) { + $attributes['id'] = $options['id']; + } + $renderedAttributes = ''; + if (\count($attributes) > 0) { + if (\PHP_VERSION_ID >= 50400) { + $flags = ENT_QUOTES | ENT_SUBSTITUTE; + } else { + $flags = ENT_QUOTES; + } + foreach ($attributes as $attribute => $value) { + $renderedAttributes .= sprintf( + ' %s="%s"', + htmlspecialchars($attribute, $flags, $this->charset, false), + htmlspecialchars($value, $flags, $this->charset, false) + ); + } + } + + return new Response(sprintf('%s', $uri, $renderedAttributes, $content)); + } + + /** + * @param string $template + * + * @return bool + */ + private function templateExists($template) + { + if ($this->templating instanceof EngineInterface) { + try { + return $this->templating->exists($template); + } catch (\Exception $e) { + return false; + } + } + + $loader = $this->templating->getLoader(); + if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) { + return $loader->exists($template); + } + + try { + if (method_exists($loader, 'getSourceContext')) { + $loader->getSourceContext($template); + } else { + $loader->getSource($template); + } + + return true; + } catch (LoaderError $e) { + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'hinclude'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php new file mode 100644 index 0000000..13fc335 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/InlineFragmentRenderer.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\HttpCache\SubRequestHandler; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\KernelEvents; + +/** + * Implements the inline rendering strategy where the Request is rendered by the current HTTP kernel. + * + * @author Fabien Potencier + */ +class InlineFragmentRenderer extends RoutableFragmentRenderer +{ + private $kernel; + private $dispatcher; + + public function __construct(HttpKernelInterface $kernel, EventDispatcherInterface $dispatcher = null) + { + $this->kernel = $kernel; + $this->dispatcher = $dispatcher; + } + + /** + * {@inheritdoc} + * + * Additional available options: + * + * * alt: an alternative URI to render in case of an error + */ + public function render($uri, Request $request, array $options = array()) + { + $reference = null; + if ($uri instanceof ControllerReference) { + $reference = $uri; + + // Remove attributes from the generated URI because if not, the Symfony + // routing system will use them to populate the Request attributes. We don't + // want that as we want to preserve objects (so we manually set Request attributes + // below instead) + $attributes = $reference->attributes; + $reference->attributes = array(); + + // The request format and locale might have been overridden by the user + foreach (array('_format', '_locale') as $key) { + if (isset($attributes[$key])) { + $reference->attributes[$key] = $attributes[$key]; + } + } + + $uri = $this->generateFragmentUri($uri, $request, false, false); + + $reference->attributes = array_merge($attributes, $reference->attributes); + } + + $subRequest = $this->createSubRequest($uri, $request); + + // override Request attributes as they can be objects (which are not supported by the generated URI) + if (null !== $reference) { + $subRequest->attributes->add($reference->attributes); + } + + $level = ob_get_level(); + try { + return SubRequestHandler::handle($this->kernel, $subRequest, HttpKernelInterface::SUB_REQUEST, false); + } catch (\Exception $e) { + // we dispatch the exception event to trigger the logging + // the response that comes back is simply ignored + if (isset($options['ignore_errors']) && $options['ignore_errors'] && $this->dispatcher) { + $event = new GetResponseForExceptionEvent($this->kernel, $request, HttpKernelInterface::SUB_REQUEST, $e); + + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + } + + // let's clean up the output buffers that were created by the sub-request + Response::closeOutputBuffers($level, false); + + if (isset($options['alt'])) { + $alt = $options['alt']; + unset($options['alt']); + + return $this->render($alt, $request, $options); + } + + if (!isset($options['ignore_errors']) || !$options['ignore_errors']) { + throw $e; + } + + return new Response(); + } + } + + protected function createSubRequest($uri, Request $request) + { + $cookies = $request->cookies->all(); + $server = $request->server->all(); + + unset($server['HTTP_IF_MODIFIED_SINCE']); + unset($server['HTTP_IF_NONE_MATCH']); + + $subRequest = Request::create($uri, 'get', array(), $cookies, array(), $server); + if ($request->headers->has('Surrogate-Capability')) { + $subRequest->headers->set('Surrogate-Capability', $request->headers->get('Surrogate-Capability')); + } + + if ($session = $request->getSession()) { + $subRequest->setSession($session); + } + + if ($request->get('_format')) { + $subRequest->attributes->set('_format', $request->get('_format')); + } + if ($request->getDefaultLocale() !== $request->getLocale()) { + $subRequest->setLocale($request->getLocale()); + } + + return $subRequest; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'inline'; + } +} diff --git a/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php new file mode 100644 index 0000000..0c1b95d --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/RoutableFragmentRenderer.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\EventListener\FragmentListener; + +/** + * Adds the possibility to generate a fragment URI for a given Controller. + * + * @author Fabien Potencier + */ +abstract class RoutableFragmentRenderer implements FragmentRendererInterface +{ + private $fragmentPath = '/_fragment'; + + /** + * Sets the fragment path that triggers the fragment listener. + * + * @param string $path The path + * + * @see FragmentListener + */ + public function setFragmentPath($path) + { + $this->fragmentPath = $path; + } + + /** + * Generates a fragment URI for a given controller. + * + * @param ControllerReference $reference A ControllerReference instance + * @param Request $request A Request instance + * @param bool $absolute Whether to generate an absolute URL or not + * @param bool $strict Whether to allow non-scalar attributes or not + * + * @return string A fragment URI + */ + protected function generateFragmentUri(ControllerReference $reference, Request $request, $absolute = false, $strict = true) + { + if ($strict) { + $this->checkNonScalar($reference->attributes); + } + + // We need to forward the current _format and _locale values as we don't have + // a proper routing pattern to do the job for us. + // This makes things inconsistent if you switch from rendering a controller + // to rendering a route if the route pattern does not contain the special + // _format and _locale placeholders. + if (!isset($reference->attributes['_format'])) { + $reference->attributes['_format'] = $request->getRequestFormat(); + } + if (!isset($reference->attributes['_locale'])) { + $reference->attributes['_locale'] = $request->getLocale(); + } + + $reference->attributes['_controller'] = $reference->controller; + + $reference->query['_path'] = http_build_query($reference->attributes, '', '&'); + + $path = $this->fragmentPath.'?'.http_build_query($reference->query, '', '&'); + + if ($absolute) { + return $request->getUriForPath($path); + } + + return $request->getBaseUrl().$path; + } + + private function checkNonScalar($values) + { + foreach ($values as $key => $value) { + if (\is_array($value)) { + $this->checkNonScalar($value); + } elseif (!is_scalar($value) && null !== $value) { + throw new \LogicException(sprintf('Controller attributes cannot contain non-scalar/non-null values (value for key "%s" is not a scalar or null).', $key)); + } + } + } +} diff --git a/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php new file mode 100644 index 0000000..45e7122 --- /dev/null +++ b/vendor/symfony/http-kernel/Fragment/SsiFragmentRenderer.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Fragment; + +/** + * Implements the SSI rendering strategy. + * + * @author Sebastian Krebs + */ +class SsiFragmentRenderer extends AbstractSurrogateFragmentRenderer +{ + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Esi.php b/vendor/symfony/http-kernel/HttpCache/Esi.php new file mode 100644 index 0000000..d49c63c --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Esi.php @@ -0,0 +1,282 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Esi implements the ESI capabilities to Request and Response instances. + * + * For more information, read the following W3C notes: + * + * * ESI Language Specification 1.0 (http://www.w3.org/TR/esi-lang) + * + * * Edge Architecture Specification (http://www.w3.org/TR/edge-arch) + * + * @author Fabien Potencier + */ +class Esi implements SurrogateInterface +{ + private $contentTypes; + private $phpEscapeMap = array( + array('', '', '', ''), + ); + + /** + * @param array $contentTypes An array of content-type that should be parsed for ESI information + * (default: text/html, text/xml, application/xhtml+xml, and application/xml) + */ + public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml')) + { + $this->contentTypes = $contentTypes; + } + + public function getName() + { + return 'esi'; + } + + /** + * Returns a new cache strategy instance. + * + * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy() + { + return new ResponseCacheStrategy(); + } + + /** + * Checks that at least one surrogate has ESI/1.0 capability. + * + * @return bool true if one surrogate has ESI/1.0 capability, false otherwise + */ + public function hasSurrogateCapability(Request $request) + { + if (null === $value = $request->headers->get('Surrogate-Capability')) { + return false; + } + + return false !== strpos($value, 'ESI/1.0'); + } + + /** + * Checks that at least one surrogate has ESI/1.0 capability. + * + * @param Request $request A Request instance + * + * @return bool true if one surrogate has ESI/1.0 capability, false otherwise + * + * @deprecated since version 2.6, to be removed in 3.0. Use hasSurrogateCapability() instead + */ + public function hasSurrogateEsiCapability(Request $request) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the hasSurrogateCapability() method instead.', E_USER_DEPRECATED); + + return $this->hasSurrogateCapability($request); + } + + /** + * Adds ESI/1.0 capability to the given Request. + */ + public function addSurrogateCapability(Request $request) + { + $current = $request->headers->get('Surrogate-Capability'); + $new = 'symfony2="ESI/1.0"'; + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + /** + * Adds ESI/1.0 capability to the given Request. + * + * @param Request $request A Request instance + * + * @deprecated since version 2.6, to be removed in 3.0. Use addSurrogateCapability() instead + */ + public function addSurrogateEsiCapability(Request $request) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the addSurrogateCapability() method instead.', E_USER_DEPRECATED); + + $this->addSurrogateCapability($request); + } + + /** + * Adds HTTP headers to specify that the Response needs to be parsed for ESI. + * + * This method only adds an ESI HTTP header if the Response has some ESI tags. + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), 'headers->set('Surrogate-Control', 'content="ESI/1.0"'); + } + } + + /** + * Checks that the Response needs to be parsed for ESI tags. + * + * @return bool true if the Response needs to be parsed, false otherwise + */ + public function needsParsing(Response $response) + { + if (!$control = $response->headers->get('Surrogate-Control')) { + return false; + } + + return (bool) preg_match('#content="[^"]*ESI/1.0[^"]*"#', $control); + } + + /** + * Checks that the Response needs to be parsed for ESI tags. + * + * @param Response $response A Response instance + * + * @return bool true if the Response needs to be parsed, false otherwise + * + * @deprecated since version 2.6, to be removed in 3.0. Use needsParsing() instead + */ + public function needsEsiParsing(Response $response) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the needsParsing() method instead.', E_USER_DEPRECATED); + + return $this->needsParsing($response); + } + + /** + * Renders an ESI tag. + * + * @param string $uri A URI + * @param string $alt An alternate URI + * @param bool $ignoreErrors Whether to ignore errors or not + * @param string $comment A comment to add as an esi:include tag + * + * @return string + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = '') + { + $html = sprintf('', + $uri, + $ignoreErrors ? ' onerror="continue"' : '', + $alt ? sprintf(' alt="%s"', $alt) : '' + ); + + if (!empty($comment)) { + return sprintf("\n%s", $comment, $html); + } + + return $html; + } + + /** + * Replaces a Response ESI tags with the included resource content. + * + * @return Response + */ + public function process(Request $request, Response $response) + { + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!\in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have ESI tags in a plain text response + $content = $response->getContent(); + $content = preg_replace('#.*?#s', '', $content); + $content = preg_replace('#]+>#s', '', $content); + + $chunks = preg_split('##', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['src'])) { + throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.'); + } + + $chunks[$i] = sprintf('surrogate->handle($this, %s, %s, %s) ?>'."\n", + var_export($options['src'], true), + var_export(isset($options['alt']) ? $options['alt'] : '', true), + isset($options['onerror']) && 'continue' === $options['onerror'] ? 'true' : 'false' + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'ESI'); + + // remove ESI/1.0 from the Surrogate-Control header + if ($response->headers->has('Surrogate-Control')) { + $value = $response->headers->get('Surrogate-Control'); + if ('content="ESI/1.0"' == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match('#,\s*content="ESI/1.0"#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#,\s*content="ESI/1.0"#', '', $value)); + } elseif (preg_match('#content="ESI/1.0",\s*#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#content="ESI/1.0",\s*#', '', $value)); + } + } + } + + /** + * Handles an ESI from the cache. + * + * @param HttpCache $cache An HttpCache instance + * @param string $uri The main URI + * @param string $alt An alternative URI + * @param bool $ignoreErrors Whether to ignore errors or not + * + * @return string + * + * @throws \RuntimeException + * @throws \Exception + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors) + { + $subRequest = Request::create($uri, 'get', array(), $cache->getRequest()->cookies->all(), array(), $cache->getRequest()->server->all()); + + try { + $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); + + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode())); + } + + return $response->getContent(); + } catch (\Exception $e) { + if ($alt) { + return $this->handle($cache, $alt, '', $ignoreErrors); + } + + if (!$ignoreErrors) { + throw $e; + } + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/EsiResponseCacheStrategy.php b/vendor/symfony/http-kernel/HttpCache/EsiResponseCacheStrategy.php new file mode 100644 index 0000000..41096bb --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/EsiResponseCacheStrategy.php @@ -0,0 +1,33 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +@trigger_error('The '.__NAMESPACE__.'\EsiResponseCacheStrategy class is deprecated since Symfony 2.6 and will be removed in 3.0. Use the Symfony\Component\HttpKernel\HttpCache\ResponseCacheStrategy class instead.', E_USER_DEPRECATED); + +/** + * EsiResponseCacheStrategy knows how to compute the Response cache HTTP header + * based on the different ESI response cache headers. + * + * This implementation changes the master response TTL to the smallest TTL received + * or force validation if one of the ESI has validation cache strategy. + * + * @author Fabien Potencier + * + * @deprecated since version 2.6, to be removed in 3.0. Use ResponseCacheStrategy instead + */ +class EsiResponseCacheStrategy extends ResponseCacheStrategy implements EsiResponseCacheStrategyInterface +{ +} diff --git a/vendor/symfony/http-kernel/HttpCache/EsiResponseCacheStrategyInterface.php b/vendor/symfony/http-kernel/HttpCache/EsiResponseCacheStrategyInterface.php new file mode 100644 index 0000000..5388e99 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/EsiResponseCacheStrategyInterface.php @@ -0,0 +1,28 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +/** + * ResponseCacheStrategyInterface implementations know how to compute the + * Response cache HTTP header based on the different response cache headers. + * + * @author Fabien Potencier + * + * @deprecated since version 2.6, to be removed in 3.0. Use ResponseCacheStrategyInterface instead. + */ +interface EsiResponseCacheStrategyInterface extends ResponseCacheStrategyInterface +{ +} diff --git a/vendor/symfony/http-kernel/HttpCache/HttpCache.php b/vendor/symfony/http-kernel/HttpCache/HttpCache.php new file mode 100644 index 0000000..1ccef00 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/HttpCache.php @@ -0,0 +1,675 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\TerminableInterface; + +/** + * Cache provides HTTP caching. + * + * @author Fabien Potencier + */ +class HttpCache implements HttpKernelInterface, TerminableInterface +{ + private $kernel; + private $store; + private $request; + private $surrogate; + private $surrogateCacheStrategy; + private $options = array(); + private $traces = array(); + + /** + * Constructor. + * + * The available options are: + * + * * debug: If true, the traces are added as a HTTP header to ease debugging + * + * * default_ttl The number of seconds that a cache entry should be considered + * fresh when no explicit freshness information is provided in + * a response. Explicit Cache-Control or Expires headers + * override this value. (default: 0) + * + * * private_headers Set of request headers that trigger "private" cache-control behavior + * on responses that don't explicitly state whether the response is + * public or private via a Cache-Control directive. (default: Authorization and Cookie) + * + * * allow_reload Specifies whether the client can force a cache reload by including a + * Cache-Control "no-cache" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * allow_revalidate Specifies whether the client can force a cache revalidate by including + * a Cache-Control "max-age=0" directive in the request. Set it to ``true`` + * for compliance with RFC 2616. (default: false) + * + * * stale_while_revalidate Specifies the default number of seconds (the granularity is the second as the + * Response TTL precision is a second) during which the cache can immediately return + * a stale response while it revalidates it in the background (default: 2). + * This setting is overridden by the stale-while-revalidate HTTP Cache-Control + * extension (see RFC 5861). + * + * * stale_if_error Specifies the default number of seconds (the granularity is the second) during which + * the cache can serve a stale response when an error is encountered (default: 60). + * This setting is overridden by the stale-if-error HTTP Cache-Control extension + * (see RFC 5861). + */ + public function __construct(HttpKernelInterface $kernel, StoreInterface $store, SurrogateInterface $surrogate = null, array $options = array()) + { + $this->store = $store; + $this->kernel = $kernel; + $this->surrogate = $surrogate; + + // needed in case there is a fatal error because the backend is too slow to respond + register_shutdown_function(array($this->store, 'cleanup')); + + $this->options = array_merge(array( + 'debug' => false, + 'default_ttl' => 0, + 'private_headers' => array('Authorization', 'Cookie'), + 'allow_reload' => false, + 'allow_revalidate' => false, + 'stale_while_revalidate' => 2, + 'stale_if_error' => 60, + ), $options); + } + + /** + * Gets the current store. + * + * @return StoreInterface A StoreInterface instance + */ + public function getStore() + { + return $this->store; + } + + /** + * Returns an array of events that took place during processing of the last request. + * + * @return array An array of events + */ + public function getTraces() + { + return $this->traces; + } + + /** + * Returns a log message for the events of the last request processing. + * + * @return string A log message + */ + public function getLog() + { + $log = array(); + foreach ($this->traces as $request => $traces) { + $log[] = sprintf('%s: %s', $request, implode(', ', $traces)); + } + + return implode('; ', $log); + } + + /** + * Gets the Request instance associated with the master request. + * + * @return Request A Request instance + */ + public function getRequest() + { + return $this->request; + } + + /** + * Gets the Kernel instance. + * + * @return HttpKernelInterface An HttpKernelInterface instance + */ + public function getKernel() + { + return $this->kernel; + } + + /** + * Gets the Surrogate instance. + * + * @return SurrogateInterface A Surrogate instance + * + * @throws \LogicException + */ + public function getSurrogate() + { + if (!$this->surrogate instanceof Esi) { + throw new \LogicException('This instance of HttpCache was not set up to use ESI as surrogate handler. You must overwrite and use createSurrogate'); + } + + return $this->surrogate; + } + + /** + * Gets the Esi instance. + * + * @return Esi An Esi instance + * + * @throws \LogicException + * + * @deprecated since version 2.6, to be removed in 3.0. Use getSurrogate() instead + */ + public function getEsi() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the getSurrogate() method instead.', E_USER_DEPRECATED); + + return $this->getSurrogate(); + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + // FIXME: catch exceptions and implement a 500 error page here? -> in Varnish, there is a built-in error page mechanism + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->traces = array(); + // Keep a clone of the original request for surrogates so they can access it. + // We must clone here to get a separate instance because the application will modify the request during + // the application flow (we know it always does because we do ourselves by setting REMOTE_ADDR to 127.0.0.1 + // and adding the X-Forwarded-For header, see HttpCache::forward()). + $this->request = clone $request; + if (null !== $this->surrogate) { + $this->surrogateCacheStrategy = $this->surrogate->createCacheStrategy(); + } + } + + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + $this->traces[$request->getMethod().' '.$path] = array(); + + if (!$request->isMethodSafe(false)) { + $response = $this->invalidate($request, $catch); + } elseif ($request->headers->has('expect') || !$request->isMethodCacheable()) { + $response = $this->pass($request, $catch); + } else { + $response = $this->lookup($request, $catch); + } + + $this->restoreResponseBody($request, $response); + + $response->setDate(\DateTime::createFromFormat('U', time(), new \DateTimeZone('UTC'))); + + if (HttpKernelInterface::MASTER_REQUEST === $type && $this->options['debug']) { + $response->headers->set('X-Symfony-Cache', $this->getLog()); + } + + if (null !== $this->surrogate) { + if (HttpKernelInterface::MASTER_REQUEST === $type) { + $this->surrogateCacheStrategy->update($response); + } else { + $this->surrogateCacheStrategy->add($response); + } + } + + $response->prepare($request); + + $response->isNotModified($request); + + return $response; + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + if ($this->getKernel() instanceof TerminableInterface) { + $this->getKernel()->terminate($request, $response); + } + } + + /** + * Forwards the Request to the backend without storing the Response in the cache. + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function pass(Request $request, $catch = false) + { + $this->record($request, 'pass'); + + return $this->forward($request, $catch); + } + + /** + * Invalidates non-safe methods (like POST, PUT, and DELETE). + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + * + * @see RFC2616 13.10 + */ + protected function invalidate(Request $request, $catch = false) + { + $response = $this->pass($request, $catch); + + // invalidate only when the response is successful + if ($response->isSuccessful() || $response->isRedirect()) { + try { + $this->store->invalidate($request); + + // As per the RFC, invalidate Location and Content-Location URLs if present + foreach (array('Location', 'Content-Location') as $header) { + if ($uri = $response->headers->get($header)) { + $subRequest = Request::create($uri, 'get', array(), array(), array(), $request->server->all()); + + $this->store->invalidate($subRequest); + } + } + + $this->record($request, 'invalidate'); + } catch (\Exception $e) { + $this->record($request, 'invalidate-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + } + + return $response; + } + + /** + * Lookups a Response from the cache for the given Request. + * + * When a matching cache entry is found and is fresh, it uses it as the + * response without forwarding any request to the backend. When a matching + * cache entry is found but is stale, it attempts to "validate" the entry with + * the backend using conditional GET. When no matching cache entry is found, + * it triggers "miss" processing. + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + * + * @throws \Exception + */ + protected function lookup(Request $request, $catch = false) + { + // if allow_reload and no-cache Cache-Control, allow a cache reload + if ($this->options['allow_reload'] && $request->isNoCache()) { + $this->record($request, 'reload'); + + return $this->fetch($request, $catch); + } + + try { + $entry = $this->store->lookup($request); + } catch (\Exception $e) { + $this->record($request, 'lookup-failed'); + + if ($this->options['debug']) { + throw $e; + } + + return $this->pass($request, $catch); + } + + if (null === $entry) { + $this->record($request, 'miss'); + + return $this->fetch($request, $catch); + } + + if (!$this->isFreshEnough($request, $entry)) { + $this->record($request, 'stale'); + + return $this->validate($request, $entry, $catch); + } + + $this->record($request, 'fresh'); + + $entry->headers->set('Age', $entry->getAge()); + + return $entry; + } + + /** + * Validates that a cache entry is fresh. + * + * The original request is used as a template for a conditional + * GET request with the backend. + * + * @param Request $request A Request instance + * @param Response $entry A Response instance to validate + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function validate(Request $request, Response $entry, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // add our cached last-modified validator + $subRequest->headers->set('if_modified_since', $entry->headers->get('Last-Modified')); + + // Add our cached etag validator to the environment. + // We keep the etags from the client to handle the case when the client + // has a different private valid entry which is not cached here. + $cachedEtags = $entry->getEtag() ? array($entry->getEtag()) : array(); + $requestEtags = $request->getETags(); + if ($etags = array_unique(array_merge($cachedEtags, $requestEtags))) { + $subRequest->headers->set('if_none_match', implode(', ', $etags)); + } + + $response = $this->forward($subRequest, $catch, $entry); + + if (304 == $response->getStatusCode()) { + $this->record($request, 'valid'); + + // return the response and not the cache entry if the response is valid but not cached + $etag = $response->getEtag(); + if ($etag && \in_array($etag, $requestEtags) && !\in_array($etag, $cachedEtags)) { + return $response; + } + + $entry = clone $entry; + $entry->headers->remove('Date'); + + foreach (array('Date', 'Expires', 'Cache-Control', 'ETag', 'Last-Modified') as $name) { + if ($response->headers->has($name)) { + $entry->headers->set($name, $response->headers->get($name)); + } + } + + $response = $entry; + } else { + $this->record($request, 'invalid'); + } + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Forwards the Request to the backend and determines whether the response should be stored. + * + * This methods is triggered when the cache missed or a reload is required. + * + * @param Request $request A Request instance + * @param bool $catch Whether to process exceptions + * + * @return Response A Response instance + */ + protected function fetch(Request $request, $catch = false) + { + $subRequest = clone $request; + + // send no head requests because we want content + if ('HEAD' === $request->getMethod()) { + $subRequest->setMethod('GET'); + } + + // avoid that the backend sends no content + $subRequest->headers->remove('if_modified_since'); + $subRequest->headers->remove('if_none_match'); + + $response = $this->forward($subRequest, $catch); + + if ($response->isCacheable()) { + $this->store($request, $response); + } + + return $response; + } + + /** + * Forwards the Request to the backend and returns the Response. + * + * @param Request $request A Request instance + * @param bool $catch Whether to catch exceptions or not + * @param Response $entry A Response instance (the stale entry if present, null otherwise) + * + * @return Response A Response instance + */ + protected function forward(Request $request, $catch = false, Response $entry = null) + { + if ($this->surrogate) { + $this->surrogate->addSurrogateCapability($request); + } + + // always a "master" request (as the real master request can be in cache) + $response = SubRequestHandler::handle($this->kernel, $request, HttpKernelInterface::MASTER_REQUEST, $catch); + + // we don't implement the stale-if-error on Requests, which is nonetheless part of the RFC + if (null !== $entry && \in_array($response->getStatusCode(), array(500, 502, 503, 504))) { + if (null === $age = $entry->headers->getCacheControlDirective('stale-if-error')) { + $age = $this->options['stale_if_error']; + } + + if (abs($entry->getTtl()) < $age) { + $this->record($request, 'stale-if-error'); + + return $entry; + } + } + + $this->processResponseBody($request, $response); + + if ($this->isPrivateRequest($request) && !$response->headers->hasCacheControlDirective('public')) { + $response->setPrivate(); + } elseif ($this->options['default_ttl'] > 0 && null === $response->getTtl() && !$response->headers->getCacheControlDirective('must-revalidate')) { + $response->setTtl($this->options['default_ttl']); + } + + return $response; + } + + /** + * Checks whether the cache entry is "fresh enough" to satisfy the Request. + * + * @return bool true if the cache entry if fresh enough, false otherwise + */ + protected function isFreshEnough(Request $request, Response $entry) + { + if (!$entry->isFresh()) { + return $this->lock($request, $entry); + } + + if ($this->options['allow_revalidate'] && null !== $maxAge = $request->headers->getCacheControlDirective('max-age')) { + return $maxAge > 0 && $maxAge >= $entry->getAge(); + } + + return true; + } + + /** + * Locks a Request during the call to the backend. + * + * @return bool true if the cache entry can be returned even if it is staled, false otherwise + */ + protected function lock(Request $request, Response $entry) + { + // try to acquire a lock to call the backend + $lock = $this->store->lock($request); + + // there is already another process calling the backend + if (true !== $lock) { + // check if we can serve the stale entry + if (null === $age = $entry->headers->getCacheControlDirective('stale-while-revalidate')) { + $age = $this->options['stale_while_revalidate']; + } + + if (abs($entry->getTtl()) < $age) { + $this->record($request, 'stale-while-revalidate'); + + // server the stale response while there is a revalidation + return true; + } + + // wait for the lock to be released + $wait = 0; + while ($this->store->isLocked($request) && $wait < 5000000) { + usleep(50000); + $wait += 50000; + } + + if ($wait < 5000000) { + // replace the current entry with the fresh one + $new = $this->lookup($request); + $entry->headers = $new->headers; + $entry->setContent($new->getContent()); + $entry->setStatusCode($new->getStatusCode()); + $entry->setProtocolVersion($new->getProtocolVersion()); + foreach ($new->headers->getCookies() as $cookie) { + $entry->headers->setCookie($cookie); + } + } else { + // backend is slow as hell, send a 503 response (to avoid the dog pile effect) + $entry->setStatusCode(503); + $entry->setContent('503 Service Unavailable'); + $entry->headers->set('Retry-After', 10); + } + + return true; + } + + // we have the lock, call the backend + return false; + } + + /** + * Writes the Response to the cache. + * + * @throws \Exception + */ + protected function store(Request $request, Response $response) + { + if (!$response->headers->has('Date')) { + $response->setDate(\DateTime::createFromFormat('U', time())); + } + try { + $this->store->write($request, $response); + + $this->record($request, 'store'); + + $response->headers->set('Age', $response->getAge()); + } catch (\Exception $e) { + $this->record($request, 'store-failed'); + + if ($this->options['debug']) { + throw $e; + } + } + + // now that the response is cached, release the lock + $this->store->unlock($request); + } + + /** + * Restores the Response body. + */ + private function restoreResponseBody(Request $request, Response $response) + { + if ($response->headers->has('X-Body-Eval')) { + ob_start(); + + if ($response->headers->has('X-Body-File')) { + include $response->headers->get('X-Body-File'); + } else { + eval('; ?>'.$response->getContent().'setContent(ob_get_clean()); + $response->headers->remove('X-Body-Eval'); + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); + } + } elseif ($response->headers->has('X-Body-File')) { + // Response does not include possibly dynamic content (ESI, SSI), so we need + // not handle the content for HEAD requests + if (!$request->isMethod('HEAD')) { + $response->setContent(file_get_contents($response->headers->get('X-Body-File'))); + } + } else { + return; + } + + $response->headers->remove('X-Body-File'); + } + + protected function processResponseBody(Request $request, Response $response) + { + if (null !== $this->surrogate && $this->surrogate->needsParsing($response)) { + $this->surrogate->process($request, $response); + } + } + + /** + * Checks if the Request includes authorization or other sensitive information + * that should cause the Response to be considered private by default. + * + * @return bool true if the Request is private, false otherwise + */ + private function isPrivateRequest(Request $request) + { + foreach ($this->options['private_headers'] as $key) { + $key = strtolower(str_replace('HTTP_', '', $key)); + + if ('cookie' === $key) { + if (\count($request->cookies->all())) { + return true; + } + } elseif ($request->headers->has($key)) { + return true; + } + } + + return false; + } + + /** + * Records that an event took place. + * + * @param Request $request A Request instance + * @param string $event The event name + */ + private function record(Request $request, $event) + { + $path = $request->getPathInfo(); + if ($qs = $request->getQueryString()) { + $path .= '?'.$qs; + } + $this->traces[$request->getMethod().' '.$path][] = $event; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php new file mode 100644 index 0000000..672cc89 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategy.php @@ -0,0 +1,96 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategy knows how to compute the Response cache HTTP header + * based on the different response cache headers. + * + * This implementation changes the master response TTL to the smallest TTL received + * or force validation if one of the surrogates has validation cache strategy. + * + * @author Fabien Potencier + */ +class ResponseCacheStrategy implements ResponseCacheStrategyInterface +{ + private $cacheable = true; + private $embeddedResponses = 0; + private $ttls = array(); + private $maxAges = array(); + private $isNotCacheableResponseEmbedded = false; + + /** + * {@inheritdoc} + */ + public function add(Response $response) + { + if (!$response->isFresh() || !$response->isCacheable()) { + $this->cacheable = false; + } else { + $maxAge = $response->getMaxAge(); + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $maxAge; + + if (null === $maxAge) { + $this->isNotCacheableResponseEmbedded = true; + } + } + + ++$this->embeddedResponses; + } + + /** + * {@inheritdoc} + */ + public function update(Response $response) + { + // if we have no embedded Response, do nothing + if (0 === $this->embeddedResponses) { + return; + } + + // Remove validation related headers in order to avoid browsers using + // their own cache, because some of the response content comes from + // at least one embedded response (which likely has a different caching strategy). + if ($response->isValidateable()) { + $response->setEtag(null); + $response->setLastModified(null); + } + + if (!$response->isFresh() || !$response->isCacheable()) { + $this->cacheable = false; + } + + if (!$this->cacheable) { + $response->headers->set('Cache-Control', 'no-cache, must-revalidate'); + + return; + } + + $this->ttls[] = $response->getTtl(); + $this->maxAges[] = $response->getMaxAge(); + + if ($this->isNotCacheableResponseEmbedded) { + $response->headers->removeCacheControlDirective('s-maxage'); + } elseif (null !== $maxAge = min($this->maxAges)) { + $response->setSharedMaxAge($maxAge); + $response->headers->set('Age', $maxAge - min($this->ttls)); + } + $response->setMaxAge(0); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php new file mode 100644 index 0000000..e282299 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/ResponseCacheStrategyInterface.php @@ -0,0 +1,37 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * (based on commit 02d2b48d75bcb63cf1c0c7149c077ad256542801) + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Response; + +/** + * ResponseCacheStrategyInterface implementations know how to compute the + * Response cache HTTP header based on the different response cache headers. + * + * @author Fabien Potencier + */ +interface ResponseCacheStrategyInterface +{ + /** + * Adds a Response. + */ + public function add(Response $response); + + /** + * Updates the Response HTTP headers based on the embedded Responses. + */ + public function update(Response $response); +} diff --git a/vendor/symfony/http-kernel/HttpCache/Ssi.php b/vendor/symfony/http-kernel/HttpCache/Ssi.php new file mode 100644 index 0000000..7306767 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Ssi.php @@ -0,0 +1,192 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * Ssi implements the SSI capabilities to Request and Response instances. + * + * @author Sebastian Krebs + */ +class Ssi implements SurrogateInterface +{ + private $contentTypes; + private $phpEscapeMap = array( + array('', '', '', ''), + ); + + /** + * @param array $contentTypes An array of content-type that should be parsed for SSI information + * (default: text/html, text/xml, application/xhtml+xml, and application/xml) + */ + public function __construct(array $contentTypes = array('text/html', 'text/xml', 'application/xhtml+xml', 'application/xml')) + { + $this->contentTypes = $contentTypes; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'ssi'; + } + + /** + * {@inheritdoc} + */ + public function createCacheStrategy() + { + return new ResponseCacheStrategy(); + } + + /** + * {@inheritdoc} + */ + public function hasSurrogateCapability(Request $request) + { + if (null === $value = $request->headers->get('Surrogate-Capability')) { + return false; + } + + return false !== strpos($value, 'SSI/1.0'); + } + + /** + * {@inheritdoc} + */ + public function addSurrogateCapability(Request $request) + { + $current = $request->headers->get('Surrogate-Capability'); + $new = 'symfony2="SSI/1.0"'; + + $request->headers->set('Surrogate-Capability', $current ? $current.', '.$new : $new); + } + + /** + * {@inheritdoc} + */ + public function addSurrogateControl(Response $response) + { + if (false !== strpos($response->getContent(), '', $uri); + } + + /** + * {@inheritdoc} + */ + public function process(Request $request, Response $response) + { + $type = $response->headers->get('Content-Type'); + if (empty($type)) { + $type = 'text/html'; + } + + $parts = explode(';', $type); + if (!\in_array($parts[0], $this->contentTypes)) { + return $response; + } + + // we don't use a proper XML parser here as we can have SSI tags in a plain text response + $content = $response->getContent(); + + $chunks = preg_split('##', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]); + + $i = 1; + while (isset($chunks[$i])) { + $options = array(); + preg_match_all('/(virtual)="([^"]*?)"/', $chunks[$i], $matches, PREG_SET_ORDER); + foreach ($matches as $set) { + $options[$set[1]] = $set[2]; + } + + if (!isset($options['virtual'])) { + throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.'); + } + + $chunks[$i] = sprintf('surrogate->handle($this, %s, \'\', false) ?>'."\n", + var_export($options['virtual'], true) + ); + ++$i; + $chunks[$i] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[$i]); + ++$i; + } + $content = implode('', $chunks); + + $response->setContent($content); + $response->headers->set('X-Body-Eval', 'SSI'); + + // remove SSI/1.0 from the Surrogate-Control header + if ($response->headers->has('Surrogate-Control')) { + $value = $response->headers->get('Surrogate-Control'); + if ('content="SSI/1.0"' == $value) { + $response->headers->remove('Surrogate-Control'); + } elseif (preg_match('#,\s*content="SSI/1.0"#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#,\s*content="SSI/1.0"#', '', $value)); + } elseif (preg_match('#content="SSI/1.0",\s*#', $value)) { + $response->headers->set('Surrogate-Control', preg_replace('#content="SSI/1.0",\s*#', '', $value)); + } + } + } + + /** + * {@inheritdoc} + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors) + { + $subRequest = Request::create($uri, 'get', array(), $cache->getRequest()->cookies->all(), array(), $cache->getRequest()->server->all()); + + try { + $response = $cache->handle($subRequest, HttpKernelInterface::SUB_REQUEST, true); + + if (!$response->isSuccessful()) { + throw new \RuntimeException(sprintf('Error when rendering "%s" (Status code is %s).', $subRequest->getUri(), $response->getStatusCode())); + } + + return $response->getContent(); + } catch (\Exception $e) { + if ($alt) { + return $this->handle($cache, $alt, '', $ignoreErrors); + } + + if (!$ignoreErrors) { + throw $e; + } + } + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/Store.php b/vendor/symfony/http-kernel/HttpCache/Store.php new file mode 100644 index 0000000..990091d --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/Store.php @@ -0,0 +1,491 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Store implements all the logic for storing cache metadata (Request and Response headers). + * + * @author Fabien Potencier + */ +class Store implements StoreInterface +{ + protected $root; + private $keyCache; + private $locks; + + /** + * @param string $root The path to the cache directory + * + * @throws \RuntimeException + */ + public function __construct($root) + { + $this->root = $root; + if (!file_exists($this->root) && !@mkdir($this->root, 0777, true) && !is_dir($this->root)) { + throw new \RuntimeException(sprintf('Unable to create the store directory (%s).', $this->root)); + } + $this->keyCache = new \SplObjectStorage(); + $this->locks = array(); + } + + /** + * Cleanups storage. + */ + public function cleanup() + { + // unlock everything + foreach ($this->locks as $lock) { + flock($lock, LOCK_UN); + fclose($lock); + } + + $this->locks = array(); + } + + /** + * Tries to lock the cache for a given Request, without blocking. + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request) + { + $key = $this->getCacheKey($request); + + if (!isset($this->locks[$key])) { + $path = $this->getPath($key); + if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { + return $path; + } + $h = fopen($path, 'cb'); + if (!flock($h, LOCK_EX | LOCK_NB)) { + fclose($h); + + return $path; + } + + $this->locks[$key] = $h; + } + + return true; + } + + /** + * Releases the lock for the given Request. + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + + return true; + } + + return false; + } + + public function isLocked(Request $request) + { + $key = $this->getCacheKey($request); + + if (isset($this->locks[$key])) { + return true; // shortcut if lock held by this process + } + + if (!file_exists($path = $this->getPath($key))) { + return false; + } + + $h = fopen($path, 'rb'); + flock($h, LOCK_EX | LOCK_NB, $wouldBlock); + flock($h, LOCK_UN); // release the lock we just acquired + fclose($h); + + return (bool) $wouldBlock; + } + + /** + * Locates a cached Response for the Request provided. + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request) + { + $key = $this->getCacheKey($request); + + if (!$entries = $this->getMetadata($key)) { + return; + } + + // find a cached entry that matches the request. + $match = null; + foreach ($entries as $entry) { + if ($this->requestsMatch(isset($entry[1]['vary'][0]) ? implode(', ', $entry[1]['vary']) : '', $request->headers->all(), $entry[0])) { + $match = $entry; + + break; + } + } + + if (null === $match) { + return; + } + + $headers = $match[1]; + if (file_exists($body = $this->getPath($headers['x-content-digest'][0]))) { + return $this->restoreResponse($headers, $body); + } + + // TODO the metaStore referenced an entity that doesn't exist in + // the entityStore. We definitely want to return nil but we should + // also purge the entry from the meta-store when this is detected. + } + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @return string The key under which the response is stored + * + * @throws \RuntimeException + */ + public function write(Request $request, Response $response) + { + $key = $this->getCacheKey($request); + $storedEnv = $this->persistRequest($request); + + // write the response body to the entity store if this is the original response + if (!$response->headers->has('X-Content-Digest')) { + $digest = $this->generateContentDigest($response); + + if (false === $this->save($digest, $response->getContent())) { + throw new \RuntimeException('Unable to store the entity.'); + } + + $response->headers->set('X-Content-Digest', $digest); + + if (!$response->headers->has('Transfer-Encoding')) { + $response->headers->set('Content-Length', \strlen($response->getContent())); + } + } + + // read existing cache entries, remove non-varying, and add this one to the list + $entries = array(); + $vary = $response->headers->get('vary'); + foreach ($this->getMetadata($key) as $entry) { + if (!isset($entry[1]['vary'][0])) { + $entry[1]['vary'] = array(''); + } + + if ($entry[1]['vary'][0] != $vary || !$this->requestsMatch($vary, $entry[0], $storedEnv)) { + $entries[] = $entry; + } + } + + $headers = $this->persistResponse($response); + unset($headers['age']); + + array_unshift($entries, array($storedEnv, $headers)); + + if (false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + + return $key; + } + + /** + * Returns content digest for $response. + * + * @return string + */ + protected function generateContentDigest(Response $response) + { + return 'en'.hash('sha256', $response->getContent()); + } + + /** + * Invalidates all cache entries that match the request. + * + * @throws \RuntimeException + */ + public function invalidate(Request $request) + { + $modified = false; + $key = $this->getCacheKey($request); + + $entries = array(); + foreach ($this->getMetadata($key) as $entry) { + $response = $this->restoreResponse($entry[1]); + if ($response->isFresh()) { + $response->expire(); + $modified = true; + $entries[] = array($entry[0], $this->persistResponse($response)); + } else { + $entries[] = $entry; + } + } + + if ($modified && false === $this->save($key, serialize($entries))) { + throw new \RuntimeException('Unable to store the metadata.'); + } + } + + /** + * Determines whether two Request HTTP header sets are non-varying based on + * the vary response header value provided. + * + * @param string $vary A Response vary header + * @param array $env1 A Request HTTP header array + * @param array $env2 A Request HTTP header array + * + * @return bool true if the two environments match, false otherwise + */ + private function requestsMatch($vary, $env1, $env2) + { + if (empty($vary)) { + return true; + } + + foreach (preg_split('/[\s,]+/', $vary) as $header) { + $key = str_replace('_', '-', strtolower($header)); + $v1 = isset($env1[$key]) ? $env1[$key] : null; + $v2 = isset($env2[$key]) ? $env2[$key] : null; + if ($v1 !== $v2) { + return false; + } + } + + return true; + } + + /** + * Gets all data associated with the given key. + * + * Use this method only if you know what you are doing. + * + * @param string $key The store key + * + * @return array An array of data associated with the key + */ + private function getMetadata($key) + { + if (!$entries = $this->load($key)) { + return array(); + } + + return unserialize($entries); + } + + /** + * Purges data for the given URL. + * + * This method purges both the HTTP and the HTTPS version of the cache entry. + * + * @param string $url A URL + * + * @return bool true if the URL exists with either HTTP or HTTPS scheme and has been purged, false otherwise + */ + public function purge($url) + { + $http = preg_replace('#^https:#', 'http:', $url); + $https = preg_replace('#^http:#', 'https:', $url); + + $purgedHttp = $this->doPurge($http); + $purgedHttps = $this->doPurge($https); + + return $purgedHttp || $purgedHttps; + } + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + private function doPurge($url) + { + $key = $this->getCacheKey(Request::create($url)); + if (isset($this->locks[$key])) { + flock($this->locks[$key], LOCK_UN); + fclose($this->locks[$key]); + unset($this->locks[$key]); + } + + if (file_exists($path = $this->getPath($key))) { + unlink($path); + + return true; + } + + return false; + } + + /** + * Loads data for the given key. + * + * @param string $key The store key + * + * @return string The data associated with the key + */ + private function load($key) + { + $path = $this->getPath($key); + + return file_exists($path) ? file_get_contents($path) : false; + } + + /** + * Save data for the given key. + * + * @param string $key The store key + * @param string $data The data to store + * + * @return bool + */ + private function save($key, $data) + { + $path = $this->getPath($key); + + if (isset($this->locks[$key])) { + $fp = $this->locks[$key]; + @ftruncate($fp, 0); + @fseek($fp, 0); + $len = @fwrite($fp, $data); + if (\strlen($data) !== $len) { + @ftruncate($fp, 0); + + return false; + } + } else { + if (!file_exists(\dirname($path)) && false === @mkdir(\dirname($path), 0777, true) && !is_dir(\dirname($path))) { + return false; + } + + $tmpFile = tempnam(\dirname($path), basename($path)); + if (false === $fp = @fopen($tmpFile, 'wb')) { + @unlink($tmpFile); + + return false; + } + @fwrite($fp, $data); + @fclose($fp); + + if ($data != file_get_contents($tmpFile)) { + @unlink($tmpFile); + + return false; + } + + if (false === @rename($tmpFile, $path)) { + @unlink($tmpFile); + + return false; + } + } + + @chmod($path, 0666 & ~umask()); + } + + public function getPath($key) + { + return $this->root.\DIRECTORY_SEPARATOR.substr($key, 0, 2).\DIRECTORY_SEPARATOR.substr($key, 2, 2).\DIRECTORY_SEPARATOR.substr($key, 4, 2).\DIRECTORY_SEPARATOR.substr($key, 6); + } + + /** + * Generates a cache key for the given Request. + * + * This method should return a key that must only depend on a + * normalized version of the request URI. + * + * If the same URI can have more than one representation, based on some + * headers, use a Vary header to indicate them, and each representation will + * be stored independently under the same cache key. + * + * @return string A key for the given Request + */ + protected function generateCacheKey(Request $request) + { + return 'md'.hash('sha256', $request->getUri()); + } + + /** + * Returns a cache key for the given Request. + * + * @return string A key for the given Request + */ + private function getCacheKey(Request $request) + { + if (isset($this->keyCache[$request])) { + return $this->keyCache[$request]; + } + + return $this->keyCache[$request] = $this->generateCacheKey($request); + } + + /** + * Persists the Request HTTP headers. + * + * @return array An array of HTTP headers + */ + private function persistRequest(Request $request) + { + return $request->headers->all(); + } + + /** + * Persists the Response HTTP headers. + * + * @return array An array of HTTP headers + */ + private function persistResponse(Response $response) + { + $headers = $response->headers->all(); + $headers['X-Status'] = array($response->getStatusCode()); + + return $headers; + } + + /** + * Restores a Response from the HTTP headers and body. + * + * @param array $headers An array of HTTP headers for the Response + * @param string $body The Response body + * + * @return Response + */ + private function restoreResponse($headers, $body = null) + { + $status = $headers['X-Status'][0]; + unset($headers['X-Status']); + + if (null !== $body) { + $headers['X-Body-File'] = array($body); + } + + return new Response($body, $status, $headers); + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/StoreInterface.php b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php new file mode 100644 index 0000000..8f1cf44 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/StoreInterface.php @@ -0,0 +1,83 @@ + + * + * This code is partially based on the Rack-Cache library by Ryan Tomayko, + * which is released under the MIT license. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Interface implemented by HTTP cache stores. + * + * @author Fabien Potencier + */ +interface StoreInterface +{ + /** + * Locates a cached Response for the Request provided. + * + * @return Response|null A Response instance, or null if no cache entry was found + */ + public function lookup(Request $request); + + /** + * Writes a cache entry to the store for the given Request and Response. + * + * Existing entries are read and any that match the response are removed. This + * method calls write with the new list of cache entries. + * + * @return string The key under which the response is stored + */ + public function write(Request $request, Response $response); + + /** + * Invalidates all cache entries that match the request. + */ + public function invalidate(Request $request); + + /** + * Locks the cache for a given Request. + * + * @return bool|string true if the lock is acquired, the path to the current lock otherwise + */ + public function lock(Request $request); + + /** + * Releases the lock for the given Request. + * + * @return bool False if the lock file does not exist or cannot be unlocked, true otherwise + */ + public function unlock(Request $request); + + /** + * Returns whether or not a lock exists. + * + * @return bool true if lock exists, false otherwise + */ + public function isLocked(Request $request); + + /** + * Purges data for the given URL. + * + * @param string $url A URL + * + * @return bool true if the URL exists and has been purged, false otherwise + */ + public function purge($url); + + /** + * Cleanups storage. + */ + public function cleanup(); +} diff --git a/vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php b/vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php new file mode 100644 index 0000000..21928c1 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/SubRequestHandler.php @@ -0,0 +1,104 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\IpUtils; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; + +/** + * @author Nicolas Grekas + * + * @internal + */ +class SubRequestHandler +{ + /** + * @return Response + */ + public static function handle(HttpKernelInterface $kernel, Request $request, $type, $catch) + { + // save global state related to trusted headers and proxies + $trustedProxies = Request::getTrustedProxies(); + $trustedHeaders = array( + Request::HEADER_FORWARDED => Request::getTrustedHeaderName(Request::HEADER_FORWARDED), + Request::HEADER_CLIENT_IP => Request::getTrustedHeaderName(Request::HEADER_CLIENT_IP), + Request::HEADER_CLIENT_HOST => Request::getTrustedHeaderName(Request::HEADER_CLIENT_HOST), + Request::HEADER_CLIENT_PROTO => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PROTO), + Request::HEADER_CLIENT_PORT => Request::getTrustedHeaderName(Request::HEADER_CLIENT_PORT), + ); + + // remove untrusted values + $remoteAddr = $request->server->get('REMOTE_ADDR'); + if (!IpUtils::checkIp($remoteAddr, $trustedProxies)) { + foreach (array_filter($trustedHeaders) as $name) { + $request->headers->remove($name); + $request->server->remove('HTTP_'.strtoupper(str_replace('-', '_', $name))); + } + } + + // compute trusted values, taking any trusted proxies into account + $trustedIps = array(); + $trustedValues = array(); + foreach (array_reverse($request->getClientIps()) as $ip) { + $trustedIps[] = $ip; + $trustedValues[] = sprintf('for="%s"', $ip); + } + if ($ip !== $remoteAddr) { + $trustedIps[] = $remoteAddr; + $trustedValues[] = sprintf('for="%s"', $remoteAddr); + } + + // set trusted values, reusing as much as possible the global trusted settings + if ($name = $trustedHeaders[Request::HEADER_FORWARDED]) { + $trustedValues[0] .= sprintf(';host="%s";proto=%s', $request->getHttpHost(), $request->getScheme()); + $request->headers->set($name, $v = implode(', ', $trustedValues)); + $request->server->set('HTTP_'.strtoupper(str_replace('-', '_', $name)), $v); + } + if ($name = $trustedHeaders[Request::HEADER_CLIENT_IP]) { + $request->headers->set($name, $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_'.strtoupper(str_replace('-', '_', $name)), $v); + } + if (!$name && !$trustedHeaders[Request::HEADER_FORWARDED]) { + $request->headers->set('X-Forwarded-For', $v = implode(', ', $trustedIps)); + $request->server->set('HTTP_X_FORWARDED_FOR', $v); + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X_FORWARDED_FOR'); + } + + // fix the client IP address by setting it to 127.0.0.1, + // which is the core responsibility of this method + $request->server->set('REMOTE_ADDR', '127.0.0.1'); + + // ensure 127.0.0.1 is set as trusted proxy + if (!IpUtils::checkIp('127.0.0.1', $trustedProxies)) { + Request::setTrustedProxies(array_merge($trustedProxies, array('127.0.0.1'))); + } + + try { + $e = null; + $response = $kernel->handle($request, $type, $catch); + } catch (\Throwable $e) { + } catch (\Exception $e) { + } + + // restore global state + Request::setTrustedHeaderName(Request::HEADER_CLIENT_IP, $trustedHeaders[Request::HEADER_CLIENT_IP]); + Request::setTrustedProxies($trustedProxies); + + if (null !== $e) { + throw $e; + } + + return $response; + } +} diff --git a/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php new file mode 100644 index 0000000..85391f8 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpCache/SurrogateInterface.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\HttpCache; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +interface SurrogateInterface +{ + /** + * Returns surrogate name. + * + * @return string + */ + public function getName(); + + /** + * Returns a new cache strategy instance. + * + * @return ResponseCacheStrategyInterface A ResponseCacheStrategyInterface instance + */ + public function createCacheStrategy(); + + /** + * Checks that at least one surrogate has Surrogate capability. + * + * @return bool true if one surrogate has Surrogate capability, false otherwise + */ + public function hasSurrogateCapability(Request $request); + + /** + * Adds Surrogate-capability to the given Request. + */ + public function addSurrogateCapability(Request $request); + + /** + * Adds HTTP headers to specify that the Response needs to be parsed for Surrogate. + * + * This method only adds an Surrogate HTTP header if the Response has some Surrogate tags. + */ + public function addSurrogateControl(Response $response); + + /** + * Checks that the Response needs to be parsed for Surrogate tags. + * + * @return bool true if the Response needs to be parsed, false otherwise + */ + public function needsParsing(Response $response); + + /** + * Renders a Surrogate tag. + * + * @param string $uri A URI + * @param string $alt An alternate URI + * @param bool $ignoreErrors Whether to ignore errors or not + * @param string $comment A comment to add as an esi:include tag + * + * @return string + */ + public function renderIncludeTag($uri, $alt = null, $ignoreErrors = true, $comment = ''); + + /** + * Replaces a Response Surrogate tags with the included resource content. + * + * @return Response + */ + public function process(Request $request, Response $response); + + /** + * Handles a Surrogate from the cache. + * + * @param HttpCache $cache An HttpCache instance + * @param string $uri The main URI + * @param string $alt An alternative URI + * @param bool $ignoreErrors Whether to ignore errors or not + * + * @return string + * + * @throws \RuntimeException + * @throws \Exception + */ + public function handle(HttpCache $cache, $uri, $alt, $ignoreErrors); +} diff --git a/vendor/symfony/http-kernel/HttpKernel.php b/vendor/symfony/http-kernel/HttpKernel.php new file mode 100644 index 0000000..5571aa4 --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernel.php @@ -0,0 +1,281 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface; +use Symfony\Component\HttpKernel\Event\FilterControllerEvent; +use Symfony\Component\HttpKernel\Event\FilterResponseEvent; +use Symfony\Component\HttpKernel\Event\FinishRequestEvent; +use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; +use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; +use Symfony\Component\HttpKernel\Event\PostResponseEvent; +use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + +/** + * HttpKernel notifies events to convert a Request object to a Response one. + * + * @author Fabien Potencier + */ +class HttpKernel implements HttpKernelInterface, TerminableInterface +{ + protected $dispatcher; + protected $resolver; + protected $requestStack; + + public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, RequestStack $requestStack = null) + { + $this->dispatcher = $dispatcher; + $this->resolver = $resolver; + $this->requestStack = $requestStack ?: new RequestStack(); + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + $request->headers->set('X-Php-Ob-Level', ob_get_level()); + + try { + return $this->handleRaw($request, $type); + } catch (\Exception $e) { + if ($e instanceof ConflictingHeadersException) { + $e = new BadRequestHttpException('The request headers contain conflicting information regarding the origin of this request.', $e); + } + if (false === $catch) { + $this->finishRequest($request, $type); + + throw $e; + } + + return $this->handleException($e, $request, $type); + } + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response)); + } + + /** + * @internal + */ + public function terminateWithException(\Exception $exception, Request $request = null) + { + if (!$request = $request ?: $this->requestStack->getMasterRequest()) { + throw $exception; + } + + $response = $this->handleException($exception, $request, self::MASTER_REQUEST); + + $response->sendHeaders(); + $response->sendContent(); + + $this->terminate($request, $response); + } + + /** + * Handles a request to convert it to a response. + * + * Exceptions are not caught. + * + * @param Request $request A Request instance + * @param int $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * + * @return Response A Response instance + * + * @throws \LogicException If one of the listener does not behave as expected + * @throws NotFoundHttpException When controller cannot be found + */ + private function handleRaw(Request $request, $type = self::MASTER_REQUEST) + { + $this->requestStack->push($request); + + // request + $event = new GetResponseEvent($this, $request, $type); + $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); + + if ($event->hasResponse()) { + return $this->filterResponse($event->getResponse(), $request, $type); + } + + // load controller + if (false === $controller = $this->resolver->getController($request)) { + throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); + } + + $event = new FilterControllerEvent($this, $controller, $request, $type); + $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); + $controller = $event->getController(); + + // controller arguments + $arguments = $this->resolver->getArguments($request, $controller); + + // call controller + $response = \call_user_func_array($controller, $arguments); + + // view + if (!$response instanceof Response) { + $event = new GetResponseForControllerResultEvent($this, $request, $type, $response); + $this->dispatcher->dispatch(KernelEvents::VIEW, $event); + + if ($event->hasResponse()) { + $response = $event->getResponse(); + } + + if (!$response instanceof Response) { + $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response)); + + // the user may have forgotten to return something + if (null === $response) { + $msg .= ' Did you forget to add a return statement somewhere in your controller?'; + } + throw new \LogicException($msg); + } + } + + return $this->filterResponse($response, $request, $type); + } + + /** + * Filters a response object. + * + * @param Response $response A Response instance + * @param Request $request An error message in case the response is not a Response object + * @param int $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * + * @return Response The filtered Response instance + * + * @throws \RuntimeException if the passed object is not a Response instance + */ + private function filterResponse(Response $response, Request $request, $type) + { + $event = new FilterResponseEvent($this, $request, $type, $response); + + $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); + + $this->finishRequest($request, $type); + + return $event->getResponse(); + } + + /** + * Publishes the finish request event, then pop the request from the stack. + * + * Note that the order of the operations is important here, otherwise + * operations such as {@link RequestStack::getParentRequest()} can lead to + * weird results. + * + * @param Request $request + * @param int $type + */ + private function finishRequest(Request $request, $type) + { + $this->dispatcher->dispatch(KernelEvents::FINISH_REQUEST, new FinishRequestEvent($this, $request, $type)); + $this->requestStack->pop(); + } + + /** + * Handles an exception by trying to convert it to a Response. + * + * @param \Exception $e An \Exception instance + * @param Request $request A Request instance + * @param int $type The type of the request + * + * @return Response A Response instance + * + * @throws \Exception + */ + private function handleException(\Exception $e, $request, $type) + { + $event = new GetResponseForExceptionEvent($this, $request, $type, $e); + $this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event); + + // a listener might have replaced the exception + $e = $event->getException(); + + if (!$event->hasResponse()) { + $this->finishRequest($request, $type); + + throw $e; + } + + $response = $event->getResponse(); + + // the developer asked for a specific status code + if ($response->headers->has('X-Status-Code')) { + $response->setStatusCode($response->headers->get('X-Status-Code')); + + $response->headers->remove('X-Status-Code'); + } elseif (!$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) { + // ensure that we actually have an error response + if ($e instanceof HttpExceptionInterface) { + // keep the HTTP status code and headers + $response->setStatusCode($e->getStatusCode()); + $response->headers->add($e->getHeaders()); + } else { + $response->setStatusCode(500); + } + } + + try { + return $this->filterResponse($response, $request, $type); + } catch (\Exception $e) { + return $response; + } + } + + private function varToString($var) + { + if (\is_object($var)) { + return sprintf('Object(%s)', \get_class($var)); + } + + if (\is_array($var)) { + $a = array(); + foreach ($var as $k => $v) { + $a[] = sprintf('%s => %s', $k, $this->varToString($v)); + } + + return sprintf('Array(%s)', implode(', ', $a)); + } + + if (\is_resource($var)) { + return sprintf('Resource(%s)', get_resource_type($var)); + } + + if (null === $var) { + return 'null'; + } + + if (false === $var) { + return 'false'; + } + + if (true === $var) { + return 'true'; + } + + return (string) $var; + } +} diff --git a/vendor/symfony/http-kernel/HttpKernelInterface.php b/vendor/symfony/http-kernel/HttpKernelInterface.php new file mode 100644 index 0000000..5050bfc --- /dev/null +++ b/vendor/symfony/http-kernel/HttpKernelInterface.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * HttpKernelInterface handles a Request to convert it to a Response. + * + * @author Fabien Potencier + */ +interface HttpKernelInterface +{ + const MASTER_REQUEST = 1; + const SUB_REQUEST = 2; + + /** + * Handles a Request to convert it to a Response. + * + * When $catch is true, the implementation must catch all exceptions + * and do its best to convert them to a Response instance. + * + * @param Request $request A Request instance + * @param int $type The type of the request + * (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST) + * @param bool $catch Whether to catch exceptions or not + * + * @return Response A Response instance + * + * @throws \Exception When an Exception occurs during processing + */ + public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); +} diff --git a/vendor/symfony/http-kernel/Kernel.php b/vendor/symfony/http-kernel/Kernel.php new file mode 100644 index 0000000..e49e4b7 --- /dev/null +++ b/vendor/symfony/http-kernel/Kernel.php @@ -0,0 +1,768 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; +use Symfony\Component\ClassLoader\ClassCollectionLoader; +use Symfony\Component\Config\ConfigCache; +use Symfony\Component\Config\Loader\DelegatingLoader; +use Symfony\Component\Config\Loader\LoaderResolver; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Dumper\PhpDumper; +use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\DependencyInjection\Loader\DirectoryLoader; +use Symfony\Component\DependencyInjection\Loader\IniFileLoader; +use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; +use Symfony\Component\HttpKernel\Config\EnvParametersResource; +use Symfony\Component\HttpKernel\Config\FileLocator; +use Symfony\Component\HttpKernel\DependencyInjection\AddClassesToCachePass; +use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * @author Fabien Potencier + */ +abstract class Kernel implements KernelInterface, TerminableInterface +{ + /** + * @var BundleInterface[] + */ + protected $bundles = array(); + + protected $bundleMap; + protected $container; + protected $rootDir; + protected $environment; + protected $debug; + protected $booted = false; + protected $name; + protected $startTime; + protected $loadClassCache; + + const VERSION = '2.8.49'; + const VERSION_ID = 20849; + const MAJOR_VERSION = 2; + const MINOR_VERSION = 8; + const RELEASE_VERSION = 49; + const EXTRA_VERSION = ''; + + const END_OF_MAINTENANCE = '11/2018'; + const END_OF_LIFE = '11/2019'; + + /** + * @param string $environment The environment + * @param bool $debug Whether to enable debugging or not + */ + public function __construct($environment, $debug) + { + $this->environment = $environment; + $this->debug = (bool) $debug; + $this->rootDir = $this->getRootDir(); + $this->name = $this->getName(); + + if ($this->debug) { + $this->startTime = microtime(true); + } + + $defClass = new \ReflectionMethod($this, 'init'); + $defClass = $defClass->getDeclaringClass()->name; + + if (__CLASS__ !== $defClass) { + @trigger_error(sprintf('Calling the %s::init() method is deprecated since Symfony 2.3 and will be removed in 3.0. Move your logic to the constructor method instead.', $defClass), E_USER_DEPRECATED); + $this->init(); + } + } + + /** + * @deprecated since version 2.3, to be removed in 3.0. Move your logic in the constructor instead. + */ + public function init() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.3 and will be removed in 3.0. Move your logic to the constructor method instead.', E_USER_DEPRECATED); + } + + public function __clone() + { + if ($this->debug) { + $this->startTime = microtime(true); + } + + $this->booted = false; + $this->container = null; + } + + /** + * {@inheritdoc} + */ + public function boot() + { + if (true === $this->booted) { + return; + } + + if ($this->loadClassCache) { + $this->doLoadClassCache($this->loadClassCache[0], $this->loadClassCache[1]); + } + + // init bundles + $this->initializeBundles(); + + // init container + $this->initializeContainer(); + + foreach ($this->getBundles() as $bundle) { + $bundle->setContainer($this->container); + $bundle->boot(); + } + + $this->booted = true; + } + + /** + * {@inheritdoc} + */ + public function terminate(Request $request, Response $response) + { + if (false === $this->booted) { + return; + } + + if ($this->getHttpKernel() instanceof TerminableInterface) { + $this->getHttpKernel()->terminate($request, $response); + } + } + + /** + * {@inheritdoc} + */ + public function shutdown() + { + if (false === $this->booted) { + return; + } + + $this->booted = false; + + foreach ($this->getBundles() as $bundle) { + $bundle->shutdown(); + $bundle->setContainer(null); + } + + $this->container = null; + } + + /** + * {@inheritdoc} + */ + public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) + { + if (false === $this->booted) { + $this->boot(); + } + + return $this->getHttpKernel()->handle($request, $type, $catch); + } + + /** + * Gets a HTTP kernel from the container. + * + * @return HttpKernel + */ + protected function getHttpKernel() + { + return $this->container->get('http_kernel'); + } + + /** + * {@inheritdoc} + */ + public function getBundles() + { + return $this->bundles; + } + + /** + * {@inheritdoc} + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + public function isClassInActiveBundle($class) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in version 3.0.', E_USER_DEPRECATED); + + foreach ($this->getBundles() as $bundle) { + if (0 === strpos($class, $bundle->getNamespace())) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getBundle($name, $first = true) + { + if (!isset($this->bundleMap[$name])) { + throw new \InvalidArgumentException(sprintf('Bundle "%s" does not exist or it is not enabled. Maybe you forgot to add it in the registerBundles() method of your %s.php file?', $name, \get_class($this))); + } + + if (true === $first) { + return $this->bundleMap[$name][0]; + } + + return $this->bundleMap[$name]; + } + + /** + * {@inheritdoc} + * + * @throws \RuntimeException if a custom resource is hidden by a resource in a derived bundle + */ + public function locateResource($name, $dir = null, $first = true) + { + if ('@' !== $name[0]) { + throw new \InvalidArgumentException(sprintf('A resource name must start with @ ("%s" given).', $name)); + } + + if (false !== strpos($name, '..')) { + throw new \RuntimeException(sprintf('File name "%s" contains invalid characters (..).', $name)); + } + + $bundleName = substr($name, 1); + $path = ''; + if (false !== strpos($bundleName, '/')) { + list($bundleName, $path) = explode('/', $bundleName, 2); + } + + $isResource = 0 === strpos($path, 'Resources') && null !== $dir; + $overridePath = substr($path, 9); + $resourceBundle = null; + $bundles = $this->getBundle($bundleName, false); + $files = array(); + + foreach ($bundles as $bundle) { + if ($isResource && file_exists($file = $dir.'/'.$bundle->getName().$overridePath)) { + if (null !== $resourceBundle) { + throw new \RuntimeException(sprintf('"%s" resource is hidden by a resource from the "%s" derived bundle. Create a "%s" file to override the bundle resource.', $file, $resourceBundle, $dir.'/'.$bundles[0]->getName().$overridePath)); + } + + if ($first) { + return $file; + } + $files[] = $file; + } + + if (file_exists($file = $bundle->getPath().'/'.$path)) { + if ($first && !$isResource) { + return $file; + } + $files[] = $file; + $resourceBundle = $bundle->getName(); + } + } + + if (\count($files) > 0) { + return $first && $isResource ? $files[0] : $files; + } + + throw new \InvalidArgumentException(sprintf('Unable to find file "%s".', $name)); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + if (null === $this->name) { + $this->name = preg_replace('/[^a-zA-Z0-9_]+/', '', basename($this->rootDir)); + if (ctype_digit($this->name[0])) { + $this->name = '_'.$this->name; + } + } + + return $this->name; + } + + /** + * {@inheritdoc} + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * {@inheritdoc} + */ + public function isDebug() + { + return $this->debug; + } + + /** + * {@inheritdoc} + */ + public function getRootDir() + { + if (null === $this->rootDir) { + $r = new \ReflectionObject($this); + $this->rootDir = \dirname($r->getFileName()); + } + + return $this->rootDir; + } + + /** + * {@inheritdoc} + */ + public function getContainer() + { + return $this->container; + } + + /** + * Loads the PHP class cache. + * + * This methods only registers the fact that you want to load the cache classes. + * The cache will actually only be loaded when the Kernel is booted. + * + * That optimization is mainly useful when using the HttpCache class in which + * case the class cache is not loaded if the Response is in the cache. + * + * @param string $name The cache name prefix + * @param string $extension File extension of the resulting file + */ + public function loadClassCache($name = 'classes', $extension = '.php') + { + $this->loadClassCache = array($name, $extension); + } + + /** + * Used internally. + */ + public function setClassCache(array $classes) + { + file_put_contents($this->getCacheDir().'/classes.map', sprintf('debug ? $this->startTime : -INF; + } + + /** + * {@inheritdoc} + */ + public function getCacheDir() + { + return $this->rootDir.'/cache/'.$this->environment; + } + + /** + * {@inheritdoc} + */ + public function getLogDir() + { + return $this->rootDir.'/logs'; + } + + /** + * {@inheritdoc} + */ + public function getCharset() + { + return 'UTF-8'; + } + + protected function doLoadClassCache($name, $extension) + { + if (!$this->booted && is_file($this->getCacheDir().'/classes.map')) { + ClassCollectionLoader::load(include($this->getCacheDir().'/classes.map'), $this->getCacheDir(), $name, $this->debug, false, $extension); + } + } + + /** + * Initializes the data structures related to the bundle management. + * + * - the bundles property maps a bundle name to the bundle instance, + * - the bundleMap property maps a bundle name to the bundle inheritance hierarchy (most derived bundle first). + * + * @throws \LogicException if two bundles share a common name + * @throws \LogicException if a bundle tries to extend a non-registered bundle + * @throws \LogicException if a bundle tries to extend itself + * @throws \LogicException if two bundles extend the same ancestor + */ + protected function initializeBundles() + { + // init bundles + $this->bundles = array(); + $topMostBundles = array(); + $directChildren = array(); + + foreach ($this->registerBundles() as $bundle) { + $name = $bundle->getName(); + if (isset($this->bundles[$name])) { + throw new \LogicException(sprintf('Trying to register two bundles with the same name "%s"', $name)); + } + $this->bundles[$name] = $bundle; + + if ($parentName = $bundle->getParent()) { + if (isset($directChildren[$parentName])) { + throw new \LogicException(sprintf('Bundle "%s" is directly extended by two bundles "%s" and "%s".', $parentName, $name, $directChildren[$parentName])); + } + if ($parentName == $name) { + throw new \LogicException(sprintf('Bundle "%s" can not extend itself.', $name)); + } + $directChildren[$parentName] = $name; + } else { + $topMostBundles[$name] = $bundle; + } + } + + // look for orphans + if (!empty($directChildren) && \count($diff = array_diff_key($directChildren, $this->bundles))) { + $diff = array_keys($diff); + + throw new \LogicException(sprintf('Bundle "%s" extends bundle "%s", which is not registered.', $directChildren[$diff[0]], $diff[0])); + } + + // inheritance + $this->bundleMap = array(); + foreach ($topMostBundles as $name => $bundle) { + $bundleMap = array($bundle); + $hierarchy = array($name); + + while (isset($directChildren[$name])) { + $name = $directChildren[$name]; + array_unshift($bundleMap, $this->bundles[$name]); + $hierarchy[] = $name; + } + + foreach ($hierarchy as $hierarchyBundle) { + $this->bundleMap[$hierarchyBundle] = $bundleMap; + array_pop($bundleMap); + } + } + } + + /** + * Gets the container class. + * + * @return string The container class + */ + protected function getContainerClass() + { + return $this->name.ucfirst($this->environment).($this->debug ? 'Debug' : '').'ProjectContainer'; + } + + /** + * Gets the container's base class. + * + * All names except Container must be fully qualified. + * + * @return string + */ + protected function getContainerBaseClass() + { + return 'Container'; + } + + /** + * Initializes the service container. + * + * The cached version of the service container is used when fresh, otherwise the + * container is built. + */ + protected function initializeContainer() + { + $class = $this->getContainerClass(); + $cache = new ConfigCache($this->getCacheDir().'/'.$class.'.php', $this->debug); + $fresh = true; + if (!$cache->isFresh()) { + $container = $this->buildContainer(); + $container->compile(); + $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); + + $fresh = false; + } + + require_once $cache->getPath(); + + $this->container = new $class(); + $this->container->set('kernel', $this); + + if (!$fresh && $this->container->has('cache_warmer')) { + $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir')); + } + } + + /** + * Returns the kernel parameters. + * + * @return array An array of kernel parameters + */ + protected function getKernelParameters() + { + $bundles = array(); + $bundlesMetadata = array(); + + foreach ($this->bundles as $name => $bundle) { + $bundles[$name] = \get_class($bundle); + $bundlesMetadata[$name] = array( + 'parent' => $bundle->getParent(), + 'path' => $bundle->getPath(), + 'namespace' => $bundle->getNamespace(), + ); + } + + return array_merge( + array( + 'kernel.root_dir' => realpath($this->rootDir) ?: $this->rootDir, + 'kernel.environment' => $this->environment, + 'kernel.debug' => $this->debug, + 'kernel.name' => $this->name, + 'kernel.cache_dir' => realpath($this->getCacheDir()) ?: $this->getCacheDir(), + 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), + 'kernel.bundles' => $bundles, + 'kernel.bundles_metadata' => $bundlesMetadata, + 'kernel.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ), + $this->getEnvParameters() + ); + } + + /** + * Gets the environment parameters. + * + * Only the parameters starting with "SYMFONY__" are considered. + * + * @return array An array of parameters + */ + protected function getEnvParameters() + { + $parameters = array(); + foreach ($_SERVER as $key => $value) { + if (0 === strpos($key, 'SYMFONY__')) { + $parameters[strtolower(str_replace('__', '.', substr($key, 9)))] = $value; + } + } + + return $parameters; + } + + /** + * Builds the service container. + * + * @return ContainerBuilder The compiled service container + * + * @throws \RuntimeException + */ + protected function buildContainer() + { + foreach (array('cache' => $this->getCacheDir(), 'logs' => $this->getLogDir()) as $name => $dir) { + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf("Unable to create the %s directory (%s)\n", $name, $dir)); + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf("Unable to write in the %s directory (%s)\n", $name, $dir)); + } + } + + $container = $this->getContainerBuilder(); + $container->addObjectResource($this); + $this->prepareContainer($container); + + if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { + $container->merge($cont); + } + + $container->addCompilerPass(new AddClassesToCachePass($this)); + $container->addResource(new EnvParametersResource('SYMFONY__')); + + return $container; + } + + /** + * Prepares the ContainerBuilder before it is compiled. + */ + protected function prepareContainer(ContainerBuilder $container) + { + $extensions = array(); + foreach ($this->bundles as $bundle) { + if ($extension = $bundle->getContainerExtension()) { + $container->registerExtension($extension); + $extensions[] = $extension->getAlias(); + } + + if ($this->debug) { + $container->addObjectResource($bundle); + } + } + foreach ($this->bundles as $bundle) { + $bundle->build($container); + } + + // ensure these extensions are implicitly loaded + $container->getCompilerPassConfig()->setMergePass(new MergeExtensionConfigurationPass($extensions)); + } + + /** + * Gets a new ContainerBuilder instance used to build the service container. + * + * @return ContainerBuilder + */ + protected function getContainerBuilder() + { + $container = new ContainerBuilder(new ParameterBag($this->getKernelParameters())); + + if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\Instantiator\RuntimeInstantiator')) { + $container->setProxyInstantiator(new RuntimeInstantiator()); + } + + return $container; + } + + /** + * Dumps the service container to PHP code in the cache. + * + * @param ConfigCache $cache The config cache + * @param ContainerBuilder $container The service container + * @param string $class The name of the class to generate + * @param string $baseClass The name of the container's base class + */ + protected function dumpContainer(ConfigCache $cache, ContainerBuilder $container, $class, $baseClass) + { + // cache the container + $dumper = new PhpDumper($container); + + if (class_exists('ProxyManager\Configuration') && class_exists('Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper')) { + $dumper->setProxyDumper(new ProxyDumper(md5($cache->getPath()))); + } + + $content = $dumper->dump(array('class' => $class, 'base_class' => $baseClass, 'file' => $cache->getPath(), 'debug' => $this->debug)); + + $cache->write($content, $container->getResources()); + } + + /** + * Returns a loader for the container. + * + * @return DelegatingLoader The loader + */ + protected function getContainerLoader(ContainerInterface $container) + { + $locator = new FileLocator($this); + $resolver = new LoaderResolver(array( + new XmlFileLoader($container, $locator), + new YamlFileLoader($container, $locator), + new IniFileLoader($container, $locator), + new PhpFileLoader($container, $locator), + new DirectoryLoader($container, $locator), + new ClosureLoader($container), + )); + + return new DelegatingLoader($resolver); + } + + /** + * Removes comments from a PHP source string. + * + * We don't use the PHP php_strip_whitespace() function + * as we want the content to be readable and well-formatted. + * + * @param string $source A PHP string + * + * @return string The PHP string with the comments removed + */ + public static function stripComments($source) + { + if (!\function_exists('token_get_all')) { + return $source; + } + + $rawChunk = ''; + $output = ''; + $tokens = token_get_all($source); + $ignoreSpace = false; + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1]) || 'b"' === $token) { + $rawChunk .= $token; + } elseif (T_START_HEREDOC === $token[0]) { + $output .= $rawChunk.$token[1]; + do { + $token = $tokens[++$i]; + $output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; + } while (T_END_HEREDOC !== $token[0]); + $rawChunk = ''; + } elseif (T_WHITESPACE === $token[0]) { + if ($ignoreSpace) { + $ignoreSpace = false; + + continue; + } + + // replace multiple new lines with a single newline + $rawChunk .= preg_replace(array('/\n{2,}/S'), "\n", $token[1]); + } elseif (\in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { + $ignoreSpace = true; + } else { + $rawChunk .= $token[1]; + + // The PHP-open tag already has a new-line + if (T_OPEN_TAG === $token[0]) { + $ignoreSpace = true; + } + } + } + + $output .= $rawChunk; + + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + unset($tokens, $rawChunk); + gc_mem_caches(); + } + + return $output; + } + + public function serialize() + { + return serialize(array($this->environment, $this->debug)); + } + + public function unserialize($data) + { + list($environment, $debug) = unserialize($data); + + $this->__construct($environment, $debug); + } +} diff --git a/vendor/symfony/http-kernel/KernelEvents.php b/vendor/symfony/http-kernel/KernelEvents.php new file mode 100644 index 0000000..8f86c56 --- /dev/null +++ b/vendor/symfony/http-kernel/KernelEvents.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Contains all events thrown in the HttpKernel component. + * + * @author Bernhard Schussek + */ +final class KernelEvents +{ + /** + * The REQUEST event occurs at the very beginning of request + * dispatching. + * + * This event allows you to create a response for a request before any + * other code in the framework is executed. The event listener method + * receives a Symfony\Component\HttpKernel\Event\GetResponseEvent + * instance. + * + * @Event + */ + const REQUEST = 'kernel.request'; + + /** + * The EXCEPTION event occurs when an uncaught exception appears. + * + * This event allows you to create a response for a thrown exception or + * to modify the thrown exception. The event listener method receives + * a Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent + * instance. + * + * @Event + */ + const EXCEPTION = 'kernel.exception'; + + /** + * The VIEW event occurs when the return value of a controller + * is not a Response instance. + * + * This event allows you to create a response for the return value of the + * controller. The event listener method receives a + * Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent + * instance. + * + * @Event + */ + const VIEW = 'kernel.view'; + + /** + * The CONTROLLER event occurs once a controller was found for + * handling a request. + * + * This event allows you to change the controller that will handle the + * request. The event listener method receives a + * Symfony\Component\HttpKernel\Event\FilterControllerEvent instance. + * + * @Event + */ + const CONTROLLER = 'kernel.controller'; + + /** + * The RESPONSE event occurs once a response was created for + * replying to a request. + * + * This event allows you to modify or replace the response that will be + * replied. The event listener method receives a + * Symfony\Component\HttpKernel\Event\FilterResponseEvent instance. + * + * @Event + */ + const RESPONSE = 'kernel.response'; + + /** + * The TERMINATE event occurs once a response was sent. + * + * This event allows you to run expensive post-response jobs. + * The event listener method receives a + * Symfony\Component\HttpKernel\Event\PostResponseEvent instance. + * + * @Event + */ + const TERMINATE = 'kernel.terminate'; + + /** + * The FINISH_REQUEST event occurs when a response was generated for a request. + * + * This event allows you to reset the global and environmental state of + * the application, when it was changed during the request. + * The event listener method receives a + * Symfony\Component\HttpKernel\Event\FinishRequestEvent instance. + * + * @Event + */ + const FINISH_REQUEST = 'kernel.finish_request'; +} diff --git a/vendor/symfony/http-kernel/KernelInterface.php b/vendor/symfony/http-kernel/KernelInterface.php new file mode 100644 index 0000000..670e026 --- /dev/null +++ b/vendor/symfony/http-kernel/KernelInterface.php @@ -0,0 +1,173 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Bundle\BundleInterface; + +/** + * The Kernel is the heart of the Symfony system. + * + * It manages an environment made of bundles. + * + * @author Fabien Potencier + */ +interface KernelInterface extends HttpKernelInterface, \Serializable +{ + /** + * Returns an array of bundles to register. + * + * @return BundleInterface[] An array of bundle instances + */ + public function registerBundles(); + + /** + * Loads the container configuration. + */ + public function registerContainerConfiguration(LoaderInterface $loader); + + /** + * Boots the current kernel. + */ + public function boot(); + + /** + * Shutdowns the kernel. + * + * This method is mainly useful when doing functional testing. + */ + public function shutdown(); + + /** + * Gets the registered bundle instances. + * + * @return BundleInterface[] An array of registered bundle instances + */ + public function getBundles(); + + /** + * Checks if a given class name belongs to an active bundle. + * + * @param string $class A class name + * + * @return bool true if the class belongs to an active bundle, false otherwise + * + * @deprecated since version 2.6, to be removed in 3.0. + */ + public function isClassInActiveBundle($class); + + /** + * Returns a bundle and optionally its descendants by its name. + * + * @param string $name Bundle name + * @param bool $first Whether to return the first bundle only or together with its descendants + * + * @return BundleInterface|BundleInterface[] A BundleInterface instance or an array of BundleInterface instances if $first is false + * + * @throws \InvalidArgumentException when the bundle is not enabled + */ + public function getBundle($name, $first = true); + + /** + * Returns the file path for a given resource. + * + * A Resource can be a file or a directory. + * + * The resource name must follow the following pattern: + * + * "@BundleName/path/to/a/file.something" + * + * where BundleName is the name of the bundle + * and the remaining part is the relative path in the bundle. + * + * If $dir is passed, and the first segment of the path is "Resources", + * this method will look for a file named: + * + * $dir//path/without/Resources + * + * before looking in the bundle resource folder. + * + * @param string $name A resource name to locate + * @param string $dir A directory where to look for the resource first + * @param bool $first Whether to return the first path or paths for all matching bundles + * + * @return string|array The absolute path of the resource or an array if $first is false + * + * @throws \InvalidArgumentException if the file cannot be found or the name is not valid + * @throws \RuntimeException if the name contains invalid/unsafe characters + */ + public function locateResource($name, $dir = null, $first = true); + + /** + * Gets the name of the kernel. + * + * @return string The kernel name + */ + public function getName(); + + /** + * Gets the environment. + * + * @return string The current environment + */ + public function getEnvironment(); + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug(); + + /** + * Gets the application root dir. + * + * @return string The application root dir + */ + public function getRootDir(); + + /** + * Gets the current container. + * + * @return ContainerInterface|null A ContainerInterface instance or null when the Kernel is shutdown + */ + public function getContainer(); + + /** + * Gets the request start time (not available if debug is disabled). + * + * @return int The request start timestamp + */ + public function getStartTime(); + + /** + * Gets the cache directory. + * + * @return string The cache directory + */ + public function getCacheDir(); + + /** + * Gets the log directory. + * + * @return string The log directory + */ + public function getLogDir(); + + /** + * Gets the charset of the application. + * + * @return string The charset + */ + public function getCharset(); +} diff --git a/vendor/symfony/http-kernel/LICENSE b/vendor/symfony/http-kernel/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/http-kernel/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php new file mode 100644 index 0000000..5635a21 --- /dev/null +++ b/vendor/symfony/http-kernel/Log/DebugLoggerInterface.php @@ -0,0 +1,38 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +/** + * DebugLoggerInterface. + * + * @author Fabien Potencier + */ +interface DebugLoggerInterface +{ + /** + * Returns an array of logs. + * + * A log is an array with the following mandatory keys: + * timestamp, message, priority, and priorityName. + * It can also have an optional context key containing an array. + * + * @return array An array of logs + */ + public function getLogs(); + + /** + * Returns the number of errors. + * + * @return int The number of errors + */ + public function countErrors(); +} diff --git a/vendor/symfony/http-kernel/Log/LoggerInterface.php b/vendor/symfony/http-kernel/Log/LoggerInterface.php new file mode 100644 index 0000000..cad38e5 --- /dev/null +++ b/vendor/symfony/http-kernel/Log/LoggerInterface.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +use Psr\Log\LoggerInterface as PsrLogger; + +/** + * LoggerInterface. + * + * @author Fabien Potencier + * + * @deprecated since version 2.2, to be removed in 3.0. Type-hint \Psr\Log\LoggerInterface instead. + */ +interface LoggerInterface extends PsrLogger +{ + /** + * @deprecated since version 2.2, to be removed in 3.0. Use emergency() which is PSR-3 compatible. + */ + public function emerg($message, array $context = array()); + + /** + * @deprecated since version 2.2, to be removed in 3.0. Use critical() which is PSR-3 compatible. + */ + public function crit($message, array $context = array()); + + /** + * @deprecated since version 2.2, to be removed in 3.0. Use error() which is PSR-3 compatible. + */ + public function err($message, array $context = array()); + + /** + * @deprecated since version 2.2, to be removed in 3.0. Use warning() which is PSR-3 compatible. + */ + public function warn($message, array $context = array()); +} diff --git a/vendor/symfony/http-kernel/Log/NullLogger.php b/vendor/symfony/http-kernel/Log/NullLogger.php new file mode 100644 index 0000000..18b938d --- /dev/null +++ b/vendor/symfony/http-kernel/Log/NullLogger.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Log; + +@trigger_error('The '.__NAMESPACE__.'\NullLogger class is deprecated since Symfony 2.2 and will be removed in 3.0. Use the Psr\Log\NullLogger class instead from the psr/log Composer package.', E_USER_DEPRECATED); + +use Psr\Log\NullLogger as PsrNullLogger; + +/** + * NullLogger. + * + * @author Fabien Potencier + */ +class NullLogger extends PsrNullLogger implements LoggerInterface +{ + public function emerg($message, array $context = array()) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. You should use the new emergency() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); + } + + public function crit($message, array $context = array()) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. You should use the new critical() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); + } + + public function err($message, array $context = array()) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. You should use the new error() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); + } + + public function warn($message, array $context = array()) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. You should use the new warning() method instead, which is PSR-3 compatible.', E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/BaseMemcacheProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/BaseMemcacheProfilerStorage.php new file mode 100644 index 0000000..d990e98 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/BaseMemcacheProfilerStorage.php @@ -0,0 +1,313 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +@trigger_error('The '.__NAMESPACE__.'\BaseMemcacheProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED); + +/** + * Base Memcache storage for profiling information in a Memcache. + * + * @author Andrej Hudec + * + * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0. + * Use {@link FileProfilerStorage} instead. + */ +abstract class BaseMemcacheProfilerStorage implements ProfilerStorageInterface +{ + const TOKEN_PREFIX = 'sf_profiler_'; + + protected $dsn; + protected $lifetime; + + /** + * @param string $dsn A data source name + * @param string $username + * @param string $password + * @param int $lifetime The lifetime to use for the purge + */ + public function __construct($dsn, $username = '', $password = '', $lifetime = 86400) + { + $this->dsn = $dsn; + $this->lifetime = (int) $lifetime; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + $indexName = $this->getIndexName(); + + $indexContent = $this->getValue($indexName); + if (!$indexContent) { + return array(); + } + + $profileList = explode("\n", $indexContent); + $result = array(); + + foreach ($profileList as $item) { + if (0 === $limit) { + break; + } + + if ('' == $item) { + continue; + } + + $values = explode("\t", $item, 7); + list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = $values; + $statusCode = isset($values[6]) ? $values[6] : null; + + $itemTime = (int) $itemTime; + + if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) { + continue; + } + + if (!empty($start) && $itemTime < $start) { + continue; + } + + if (!empty($end) && $itemTime > $end) { + continue; + } + + $result[$itemToken] = array( + 'token' => $itemToken, + 'ip' => $itemIp, + 'method' => $itemMethod, + 'url' => $itemUrl, + 'time' => $itemTime, + 'parent' => $itemParent, + 'status_code' => $statusCode, + ); + --$limit; + } + + usort($result, function ($a, $b) { + if ($a['time'] === $b['time']) { + return 0; + } + + return $a['time'] > $b['time'] ? -1 : 1; + }); + + return $result; + } + + /** + * {@inheritdoc} + */ + public function purge() + { + // delete only items from index + $indexName = $this->getIndexName(); + + $indexContent = $this->getValue($indexName); + + if (!$indexContent) { + return false; + } + + $profileList = explode("\n", $indexContent); + + foreach ($profileList as $item) { + if ('' == $item) { + continue; + } + + if (false !== $pos = strpos($item, "\t")) { + $this->delete($this->getItemName(substr($item, 0, $pos))); + } + } + + return $this->delete($indexName); + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + if (empty($token)) { + return false; + } + + $profile = $this->getValue($this->getItemName($token)); + + if (false !== $profile) { + $profile = $this->createProfileFromData($token, $profile); + } + + return $profile; + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $data = array( + 'token' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()), + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + ); + + $profileIndexed = false !== $this->getValue($this->getItemName($profile->getToken())); + + if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime)) { + if (!$profileIndexed) { + // Add to index + $indexName = $this->getIndexName(); + + $indexRow = implode("\t", array( + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + $profile->getStatusCode(), + ))."\n"; + + return $this->appendValue($indexName, $indexRow, $this->lifetime); + } + + return true; + } + + return false; + } + + /** + * Retrieve item from the memcache server. + * + * @param string $key + * + * @return mixed + */ + abstract protected function getValue($key); + + /** + * Store an item on the memcache server under the specified key. + * + * @param string $key + * @param mixed $value + * @param int $expiration + * + * @return bool + */ + abstract protected function setValue($key, $value, $expiration = 0); + + /** + * Delete item from the memcache server. + * + * @param string $key + * + * @return bool + */ + abstract protected function delete($key); + + /** + * Append data to an existing item on the memcache server. + * + * @param string $key + * @param string $value + * @param int $expiration + * + * @return bool + */ + abstract protected function appendValue($key, $value, $expiration = 0); + + private function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token) { + continue; + } + + if (!$childProfileData = $this->getValue($this->getItemName($token))) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, $childProfileData, $profile)); + } + + return $profile; + } + + /** + * Get item name. + * + * @param string $token + * + * @return string + */ + private function getItemName($token) + { + $name = self::TOKEN_PREFIX.$token; + + if ($this->isItemNameValid($name)) { + return $name; + } + + return false; + } + + /** + * Get name of index. + * + * @return string + */ + private function getIndexName() + { + $name = self::TOKEN_PREFIX.'index'; + + if ($this->isItemNameValid($name)) { + return $name; + } + + return false; + } + + private function isItemNameValid($name) + { + $length = \strlen($name); + + if ($length > 250) { + throw new \RuntimeException(sprintf('The memcache item key "%s" is too long (%s bytes). Allowed maximum size is 250 bytes.', $name, $length)); + } + + return true; + } +} diff --git a/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php new file mode 100644 index 0000000..0d7e395 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/FileProfilerStorage.php @@ -0,0 +1,292 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * Storage for profiler using files. + * + * @author Alexandre Salomé + */ +class FileProfilerStorage implements ProfilerStorageInterface +{ + /** + * Folder where profiler data are stored. + * + * @var string + */ + private $folder; + + /** + * Constructs the file storage using a "dsn-like" path. + * + * Example : "file:/path/to/the/storage/folder" + * + * @param string $dsn The DSN + * + * @throws \RuntimeException + */ + public function __construct($dsn) + { + if (0 !== strpos($dsn, 'file:')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use FileStorage with an invalid dsn "%s". The expected format is "file:/path/to/the/storage/folder".', $dsn)); + } + $this->folder = substr($dsn, 5); + + if (!is_dir($this->folder) && false === @mkdir($this->folder, 0777, true) && !is_dir($this->folder)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder)); + } + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + $file = $this->getIndexFilename(); + + if (!file_exists($file)) { + return array(); + } + + $file = fopen($file, 'r'); + fseek($file, 0, SEEK_END); + + $result = array(); + while (\count($result) < $limit && $line = $this->readLineFromFile($file)) { + $values = str_getcsv($line); + list($csvToken, $csvIp, $csvMethod, $csvUrl, $csvTime, $csvParent) = $values; + $csvStatusCode = isset($values[6]) ? $values[6] : null; + + $csvTime = (int) $csvTime; + + if ($ip && false === strpos($csvIp, $ip) || $url && false === strpos($csvUrl, $url) || $method && false === strpos($csvMethod, $method)) { + continue; + } + + if (!empty($start) && $csvTime < $start) { + continue; + } + + if (!empty($end) && $csvTime > $end) { + continue; + } + + $result[$csvToken] = array( + 'token' => $csvToken, + 'ip' => $csvIp, + 'method' => $csvMethod, + 'url' => $csvUrl, + 'time' => $csvTime, + 'parent' => $csvParent, + 'status_code' => $csvStatusCode, + ); + } + + fclose($file); + + return array_values($result); + } + + /** + * {@inheritdoc} + */ + public function purge() + { + $flags = \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveDirectoryIterator($this->folder, $flags); + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST); + + foreach ($iterator as $file) { + if (is_file($file)) { + unlink($file); + } else { + rmdir($file); + } + } + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + if (!$token || !file_exists($file = $this->getFilename($token))) { + return; + } + + return $this->createProfileFromData($token, unserialize(file_get_contents($file))); + } + + /** + * {@inheritdoc} + * + * @throws \RuntimeException + */ + public function write(Profile $profile) + { + $file = $this->getFilename($profile->getToken()); + + $profileIndexed = is_file($file); + if (!$profileIndexed) { + // Create directory + $dir = \dirname($file); + if (!is_dir($dir) && false === @mkdir($dir, 0777, true) && !is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir)); + } + } + + $profileToken = $profile->getToken(); + // when there are errors in sub-requests, the parent and/or children tokens + // may equal the profile token, resulting in infinite loops + $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null; + $childrenToken = array_filter(array_map(function ($p) use ($profileToken) { + return $profileToken !== $p->getToken() ? $p->getToken() : null; + }, $profile->getChildren())); + + // Store profile + $data = array( + 'token' => $profileToken, + 'parent' => $parentToken, + 'children' => $childrenToken, + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + ); + + if (false === file_put_contents($file, serialize($data))) { + return false; + } + + if (!$profileIndexed) { + // Add to index + if (false === $file = fopen($this->getIndexFilename(), 'a')) { + return false; + } + + fputcsv($file, array( + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + $profile->getStatusCode(), + )); + fclose($file); + } + + return true; + } + + /** + * Gets filename to store data, associated to the token. + * + * @param string $token + * + * @return string The profile filename + */ + protected function getFilename($token) + { + // Uses 4 last characters, because first are mostly the same. + $folderA = substr($token, -2, 2); + $folderB = substr($token, -4, 2); + + return $this->folder.'/'.$folderA.'/'.$folderB.'/'.$token; + } + + /** + * Gets the index filename. + * + * @return string The index filename + */ + protected function getIndexFilename() + { + return $this->folder.'/index.csv'; + } + + /** + * Reads a line in the file, backward. + * + * This function automatically skips the empty lines and do not include the line return in result value. + * + * @param resource $file The file resource, with the pointer placed at the end of the line to read + * + * @return mixed A string representing the line or null if beginning of file is reached + */ + protected function readLineFromFile($file) + { + $line = ''; + $position = ftell($file); + + if (0 === $position) { + return; + } + + while (true) { + $chunkSize = min($position, 1024); + $position -= $chunkSize; + fseek($file, $position); + + if (0 === $chunkSize) { + // bof reached + break; + } + + $buffer = fread($file, $chunkSize); + + if (false === ($upTo = strrpos($buffer, "\n"))) { + $line = $buffer.$line; + continue; + } + + $position += $upTo; + $line = substr($buffer, $upTo + 1).$line; + fseek($file, max(0, $position), SEEK_SET); + + if ('' !== $line) { + break; + } + } + + return '' === $line ? null : $line; + } + + protected function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token || !file_exists($file = $this->getFilename($token))) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile)); + } + + return $profile; + } +} diff --git a/vendor/symfony/http-kernel/Profiler/MemcacheProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/MemcacheProfilerStorage.php new file mode 100644 index 0000000..6578ebf --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/MemcacheProfilerStorage.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +@trigger_error('The '.__NAMESPACE__.'\MemcacheProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED); + +/** + * Memcache Profiler Storage. + * + * @author Andrej Hudec + * + * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0. + * Use {@link FileProfilerStorage} instead. + */ +class MemcacheProfilerStorage extends BaseMemcacheProfilerStorage +{ + /** + * @var \Memcache + */ + private $memcache; + + /** + * Internal convenience method that returns the instance of the Memcache. + * + * @return \Memcache + * + * @throws \RuntimeException + */ + protected function getMemcache() + { + if (null === $this->memcache) { + if (!preg_match('#^memcache://(?(?=\[.*\])\[(.*)\]|(.*)):(.*)$#', $this->dsn, $matches)) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Memcache with an invalid dsn "%s". The expected format is "memcache://[host]:port".', $this->dsn)); + } + + $host = $matches[1] ?: $matches[2]; + $port = $matches[3]; + + $memcache = new \Memcache(); + $memcache->addServer($host, $port); + + $this->memcache = $memcache; + } + + return $this->memcache; + } + + /** + * Set instance of the Memcache. + * + * @param \Memcache $memcache + */ + public function setMemcache($memcache) + { + $this->memcache = $memcache; + } + + /** + * {@inheritdoc} + */ + protected function getValue($key) + { + return $this->getMemcache()->get($key); + } + + /** + * {@inheritdoc} + */ + protected function setValue($key, $value, $expiration = 0) + { + return $this->getMemcache()->set($key, $value, false, time() + $expiration); + } + + /** + * {@inheritdoc} + */ + protected function delete($key) + { + return $this->getMemcache()->delete($key); + } + + /** + * {@inheritdoc} + */ + protected function appendValue($key, $value, $expiration = 0) + { + $memcache = $this->getMemcache(); + + if (method_exists($memcache, 'append')) { + // Memcache v3.0 + if (!$result = $memcache->append($key, $value, false, $expiration)) { + return $memcache->set($key, $value, false, $expiration); + } + + return $result; + } + + // simulate append in Memcache <3.0 + $content = $memcache->get($key); + + return $memcache->set($key, $content.$value, false, $expiration); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/MemcachedProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/MemcachedProfilerStorage.php new file mode 100644 index 0000000..edf6ff1 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/MemcachedProfilerStorage.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +@trigger_error('The '.__NAMESPACE__.'\MemcachedProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED); + +/** + * Memcached Profiler Storage. + * + * @author Andrej Hudec + * + * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0. + * Use {@link FileProfilerStorage} instead. + */ +class MemcachedProfilerStorage extends BaseMemcacheProfilerStorage +{ + /** + * @var \Memcached + */ + private $memcached; + + /** + * Internal convenience method that returns the instance of the Memcached. + * + * @return \Memcached + * + * @throws \RuntimeException + */ + protected function getMemcached() + { + if (null === $this->memcached) { + if (!preg_match('#^memcached://(?(?=\[.*\])\[(.*)\]|(.*)):(.*)$#', $this->dsn, $matches)) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Memcached with an invalid dsn "%s". The expected format is "memcached://[host]:port".', $this->dsn)); + } + + $host = $matches[1] ?: $matches[2]; + $port = $matches[3]; + + $memcached = new \Memcached(); + + // disable compression to allow appending + $memcached->setOption(\Memcached::OPT_COMPRESSION, false); + + $memcached->addServer($host, $port); + + $this->memcached = $memcached; + } + + return $this->memcached; + } + + /** + * Set instance of the Memcached. + * + * @param \Memcached $memcached + */ + public function setMemcached($memcached) + { + $this->memcached = $memcached; + } + + /** + * {@inheritdoc} + */ + protected function getValue($key) + { + return $this->getMemcached()->get($key); + } + + /** + * {@inheritdoc} + */ + protected function setValue($key, $value, $expiration = 0) + { + return $this->getMemcached()->set($key, $value, time() + $expiration); + } + + /** + * {@inheritdoc} + */ + protected function delete($key) + { + return $this->getMemcached()->delete($key); + } + + /** + * {@inheritdoc} + */ + protected function appendValue($key, $value, $expiration = 0) + { + $memcached = $this->getMemcached(); + + if (!$result = $memcached->append($key, $value)) { + return $memcached->set($key, $value, $expiration); + } + + return $result; + } +} diff --git a/vendor/symfony/http-kernel/Profiler/MongoDbProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/MongoDbProfilerStorage.php new file mode 100644 index 0000000..19970f9 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/MongoDbProfilerStorage.php @@ -0,0 +1,257 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +@trigger_error('The '.__NAMESPACE__.'\MongoDbProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED); + +/** + * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0. + * Use {@link FileProfilerStorage} instead. + */ +class MongoDbProfilerStorage implements ProfilerStorageInterface +{ + protected $dsn; + protected $lifetime; + private $mongo; + + /** + * @param string $dsn A data source name + * @param string $username Not used + * @param string $password Not used + * @param int $lifetime The lifetime to use for the purge + */ + public function __construct($dsn, $username = '', $password = '', $lifetime = 86400) + { + $this->dsn = $dsn; + $this->lifetime = (int) $lifetime; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + $cursor = $this->getMongo()->find($this->buildQuery($ip, $url, $method, $start, $end), array('_id', 'parent', 'ip', 'method', 'url', 'time', 'status_code'))->sort(array('time' => -1))->limit($limit); + + $tokens = array(); + foreach ($cursor as $profile) { + $tokens[] = $this->getData($profile); + } + + return $tokens; + } + + /** + * {@inheritdoc} + */ + public function purge() + { + $this->getMongo()->remove(array()); + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + $profile = $this->getMongo()->findOne(array('_id' => $token, 'data' => array('$exists' => true))); + + if (null !== $profile) { + $profile = $this->createProfileFromData($this->getData($profile)); + } + + return $profile; + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $this->cleanup(); + + $record = array( + '_id' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'data' => base64_encode(serialize($profile->getCollectors())), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + 'status_code' => $profile->getStatusCode(), + ); + + $result = $this->getMongo()->update(array('_id' => $profile->getToken()), array_filter($record, function ($v) { return !empty($v); }), array('upsert' => true)); + + return (bool) (isset($result['ok']) ? $result['ok'] : $result); + } + + /** + * Internal convenience method that returns the instance of the MongoDB Collection. + * + * @return \MongoCollection + * + * @throws \RuntimeException + */ + protected function getMongo() + { + if (null !== $this->mongo) { + return $this->mongo; + } + + if (!$parsedDsn = $this->parseDsn($this->dsn)) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use MongoDB with an invalid dsn "%s". The expected format is "mongodb://[user:pass@]host/database/collection"', $this->dsn)); + } + + list($server, $database, $collection) = $parsedDsn; + $mongoClass = version_compare(phpversion('mongo'), '1.3.0', '<') ? '\Mongo' : '\MongoClient'; + $mongo = new $mongoClass($server); + + return $this->mongo = $mongo->selectCollection($database, $collection); + } + + /** + * @return Profile + */ + protected function createProfileFromData(array $data) + { + $profile = $this->getProfile($data); + + if ($data['parent']) { + $parent = $this->getMongo()->findOne(array('_id' => $data['parent'], 'data' => array('$exists' => true))); + if ($parent) { + $profile->setParent($this->getProfile($this->getData($parent))); + } + } + + $profile->setChildren($this->readChildren($data['token'])); + + return $profile; + } + + /** + * @param string $token + * + * @return Profile[] An array of Profile instances + */ + protected function readChildren($token) + { + $profiles = array(); + + $cursor = $this->getMongo()->find(array('parent' => $token, 'data' => array('$exists' => true))); + foreach ($cursor as $d) { + $profiles[] = $this->getProfile($this->getData($d)); + } + + return $profiles; + } + + protected function cleanup() + { + $this->getMongo()->remove(array('time' => array('$lt' => time() - $this->lifetime))); + } + + /** + * @param string $ip + * @param string $url + * @param string $method + * @param int $start + * @param int $end + * + * @return array + */ + private function buildQuery($ip, $url, $method, $start, $end) + { + $query = array(); + + if (!empty($ip)) { + $query['ip'] = $ip; + } + + if (!empty($url)) { + $query['url'] = $url; + } + + if (!empty($method)) { + $query['method'] = $method; + } + + if (!empty($start) || !empty($end)) { + $query['time'] = array(); + } + + if (!empty($start)) { + $query['time']['$gte'] = $start; + } + + if (!empty($end)) { + $query['time']['$lte'] = $end; + } + + return $query; + } + + /** + * @return array + */ + private function getData(array $data) + { + return array( + 'token' => $data['_id'], + 'parent' => isset($data['parent']) ? $data['parent'] : null, + 'ip' => isset($data['ip']) ? $data['ip'] : null, + 'method' => isset($data['method']) ? $data['method'] : null, + 'url' => isset($data['url']) ? $data['url'] : null, + 'time' => isset($data['time']) ? $data['time'] : null, + 'data' => isset($data['data']) ? $data['data'] : null, + 'status_code' => isset($data['status_code']) ? $data['status_code'] : null, + ); + } + + /** + * @return Profile + */ + private function getProfile(array $data) + { + $profile = new Profile($data['token']); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors(unserialize(base64_decode($data['data']))); + + return $profile; + } + + /** + * @param string $dsn + * + * @return array|null Array($server, $database, $collection) + */ + private function parseDsn($dsn) + { + if (!preg_match('#^(mongodb://.*)/(.*)/(.*)$#', $dsn, $matches)) { + return; + } + + $server = $matches[1]; + $database = $matches[2]; + $collection = $matches[3]; + preg_match('#^mongodb://(([^:]+):?(.*)(?=@))?@?([^/]*)(.*)$#', $server, $matchesServer); + + if ('' == $matchesServer[5] && '' != $matches[2]) { + $server .= '/'.$matches[2]; + } + + return array($server, $database, $collection); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/MysqlProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/MysqlProfilerStorage.php new file mode 100644 index 0000000..086aa87 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/MysqlProfilerStorage.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +@trigger_error('The '.__NAMESPACE__.'\MysqlProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED); + +/** + * A ProfilerStorage for Mysql. + * + * @author Jan Schumann + * + * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0. + * Use {@link FileProfilerStorage} instead. + */ +class MysqlProfilerStorage extends PdoProfilerStorage +{ + /** + * {@inheritdoc} + */ + protected function initDb() + { + if (null === $this->db) { + if (0 !== strpos($this->dsn, 'mysql')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Mysql with an invalid dsn "%s". The expected format is "mysql:dbname=database_name;host=host_name".', $this->dsn)); + } + + if (!class_exists('PDO') || !\in_array('mysql', \PDO::getAvailableDrivers(), true)) { + throw new \RuntimeException('You need to enable PDO_Mysql extension for the profiler to run properly.'); + } + + $db = new \PDO($this->dsn, $this->username, $this->password); + $db->exec('CREATE TABLE IF NOT EXISTS sf_profiler_data (token VARCHAR(255) PRIMARY KEY, data LONGTEXT, ip VARCHAR(64), method VARCHAR(6), url VARCHAR(255), time INTEGER UNSIGNED, parent VARCHAR(255), created_at INTEGER UNSIGNED, status_code SMALLINT UNSIGNED, KEY (created_at), KEY (ip), KEY (method), KEY (url), KEY (parent))'); + + $this->db = $db; + } + + return $this->db; + } + + /** + * {@inheritdoc} + */ + protected function buildCriteria($ip, $url, $start, $end, $limit, $method) + { + $criteria = array(); + $args = array(); + + if ($ip = preg_replace('/[^\d\.]/', '', $ip)) { + $criteria[] = 'ip LIKE :ip'; + $args[':ip'] = '%'.$ip.'%'; + } + + if ($url) { + $criteria[] = 'url LIKE :url'; + $args[':url'] = '%'.addcslashes($url, '%_\\').'%'; + } + + if ($method) { + $criteria[] = 'method = :method'; + $args[':method'] = $method; + } + + if (!empty($start)) { + $criteria[] = 'time >= :start'; + $args[':start'] = $start; + } + + if (!empty($end)) { + $criteria[] = 'time <= :end'; + $args[':end'] = $end; + } + + return array($criteria, $args); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/PdoProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/PdoProfilerStorage.php new file mode 100644 index 0000000..e5f8bc7 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/PdoProfilerStorage.php @@ -0,0 +1,265 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +@trigger_error('The '.__NAMESPACE__.'\PdoProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED); + +/** + * Base PDO storage for profiling information in a PDO database. + * + * @author Fabien Potencier + * @author Jan Schumann + * + * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0. + * Use {@link FileProfilerStorage} instead. + */ +abstract class PdoProfilerStorage implements ProfilerStorageInterface +{ + protected $dsn; + protected $username; + protected $password; + protected $lifetime; + protected $db; + + /** + * @param string $dsn A data source name + * @param string $username The username for the database + * @param string $password The password for the database + * @param int $lifetime The lifetime to use for the purge + */ + public function __construct($dsn, $username = '', $password = '', $lifetime = 86400) + { + $this->dsn = $dsn; + $this->username = $username; + $this->password = $password; + $this->lifetime = (int) $lifetime; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + if (null === $start) { + $start = 0; + } + + if (null === $end) { + $end = time(); + } + + list($criteria, $args) = $this->buildCriteria($ip, $url, $start, $end, $limit, $method); + + $criteria = $criteria ? 'WHERE '.implode(' AND ', $criteria) : ''; + + $db = $this->initDb(); + $tokens = $this->fetch($db, 'SELECT token, ip, method, url, time, parent, status_code FROM sf_profiler_data '.$criteria.' ORDER BY time DESC LIMIT '.((int) $limit), $args); + $this->close($db); + + return $tokens; + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + $db = $this->initDb(); + $args = array(':token' => $token); + $data = $this->fetch($db, 'SELECT data, parent, ip, method, url, time FROM sf_profiler_data WHERE token = :token LIMIT 1', $args); + $this->close($db); + if (isset($data[0]['data'])) { + return $this->createProfileFromData($token, $data[0]); + } + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $db = $this->initDb(); + $args = array( + ':token' => $profile->getToken(), + ':parent' => $profile->getParentToken(), + ':data' => base64_encode(serialize($profile->getCollectors())), + ':ip' => $profile->getIp(), + ':method' => $profile->getMethod(), + ':url' => $profile->getUrl(), + ':time' => $profile->getTime(), + ':created_at' => time(), + ':status_code' => $profile->getStatusCode(), + ); + + try { + if ($this->has($profile->getToken())) { + $this->exec($db, 'UPDATE sf_profiler_data SET parent = :parent, data = :data, ip = :ip, method = :method, url = :url, time = :time, created_at = :created_at, status_code = :status_code WHERE token = :token', $args); + } else { + $this->exec($db, 'INSERT INTO sf_profiler_data (token, parent, data, ip, method, url, time, created_at, status_code) VALUES (:token, :parent, :data, :ip, :method, :url, :time, :created_at, :status_code)', $args); + } + $this->cleanup(); + $status = true; + } catch (\Exception $e) { + $status = false; + } + + $this->close($db); + + return $status; + } + + /** + * {@inheritdoc} + */ + public function purge() + { + $db = $this->initDb(); + $this->exec($db, 'DELETE FROM sf_profiler_data'); + $this->close($db); + } + + /** + * Build SQL criteria to fetch records by ip and url. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $start The start period to search from + * @param string $end The end period to search to + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * + * @return array An array with (criteria, args) + */ + abstract protected function buildCriteria($ip, $url, $start, $end, $limit, $method); + + /** + * Initializes the database. + * + * @throws \RuntimeException When the requested database driver is not installed + */ + abstract protected function initDb(); + + protected function cleanup() + { + $db = $this->initDb(); + $this->exec($db, 'DELETE FROM sf_profiler_data WHERE created_at < :time', array(':time' => time() - $this->lifetime)); + $this->close($db); + } + + protected function exec($db, $query, array $args = array()) + { + $stmt = $this->prepareStatement($db, $query); + + foreach ($args as $arg => $val) { + $stmt->bindValue($arg, $val, \is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR); + } + $success = $stmt->execute(); + if (!$success) { + throw new \RuntimeException(sprintf('Error executing query "%s"', $query)); + } + } + + protected function prepareStatement($db, $query) + { + try { + $stmt = $db->prepare($query); + } catch (\Exception $e) { + $stmt = false; + } + + if (false === $stmt) { + throw new \RuntimeException('The database cannot successfully prepare the statement'); + } + + return $stmt; + } + + protected function fetch($db, $query, array $args = array()) + { + $stmt = $this->prepareStatement($db, $query); + + foreach ($args as $arg => $val) { + $stmt->bindValue($arg, $val, \is_int($val) ? \PDO::PARAM_INT : \PDO::PARAM_STR); + } + $stmt->execute(); + + return $stmt->fetchAll(\PDO::FETCH_ASSOC); + } + + protected function close($db) + { + } + + protected function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors(unserialize(base64_decode($data['data']))); + + if (!$parent && !empty($data['parent'])) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + $profile->setChildren($this->readChildren($token, $profile)); + + return $profile; + } + + /** + * Reads the child profiles for the given token. + * + * @param string $token The parent token + * @param string $parent The parent instance + * + * @return Profile[] An array of Profile instance + */ + protected function readChildren($token, $parent) + { + $db = $this->initDb(); + $data = $this->fetch($db, 'SELECT token, data, ip, method, url, time FROM sf_profiler_data WHERE parent = :token', array(':token' => $token)); + $this->close($db); + + if (!$data) { + return array(); + } + + $profiles = array(); + foreach ($data as $d) { + $profiles[] = $this->createProfileFromData($d['token'], $d, $parent); + } + + return $profiles; + } + + /** + * Returns whether data for the given token already exists in storage. + * + * @param string $token The profile token + * + * @return string + */ + protected function has($token) + { + $db = $this->initDb(); + $tokenExists = $this->fetch($db, 'SELECT 1 FROM sf_profiler_data WHERE token = :token LIMIT 1', array(':token' => $token)); + $this->close($db); + + return !empty($tokenExists); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profile.php b/vendor/symfony/http-kernel/Profiler/Profile.php new file mode 100644 index 0000000..1f2796d --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profile.php @@ -0,0 +1,287 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; + +/** + * Profile. + * + * @author Fabien Potencier + */ +class Profile +{ + private $token; + + /** + * @var DataCollectorInterface[] + */ + private $collectors = array(); + + private $ip; + private $method; + private $url; + private $time; + private $statusCode; + + /** + * @var Profile + */ + private $parent; + + /** + * @var Profile[] + */ + private $children = array(); + + /** + * @param string $token The token + */ + public function __construct($token) + { + $this->token = $token; + } + + /** + * Sets the token. + * + * @param string $token The token + */ + public function setToken($token) + { + $this->token = $token; + } + + /** + * Gets the token. + * + * @return string The token + */ + public function getToken() + { + return $this->token; + } + + /** + * Sets the parent token. + */ + public function setParent(Profile $parent) + { + $this->parent = $parent; + } + + /** + * Returns the parent profile. + * + * @return self + */ + public function getParent() + { + return $this->parent; + } + + /** + * Returns the parent token. + * + * @return string|null The parent token + */ + public function getParentToken() + { + return $this->parent ? $this->parent->getToken() : null; + } + + /** + * Returns the IP. + * + * @return string The IP + */ + public function getIp() + { + return $this->ip; + } + + /** + * Sets the IP. + * + * @param string $ip + */ + public function setIp($ip) + { + $this->ip = $ip; + } + + /** + * Returns the request method. + * + * @return string The request method + */ + public function getMethod() + { + return $this->method; + } + + public function setMethod($method) + { + $this->method = $method; + } + + /** + * Returns the URL. + * + * @return string The URL + */ + public function getUrl() + { + return $this->url; + } + + public function setUrl($url) + { + $this->url = $url; + } + + /** + * Returns the time. + * + * @return int The time + */ + public function getTime() + { + if (null === $this->time) { + return 0; + } + + return $this->time; + } + + /** + * @param int $time The time + */ + public function setTime($time) + { + $this->time = $time; + } + + /** + * @param int $statusCode + */ + public function setStatusCode($statusCode) + { + $this->statusCode = $statusCode; + } + + /** + * @return int + */ + public function getStatusCode() + { + return $this->statusCode; + } + + /** + * Finds children profilers. + * + * @return self[] + */ + public function getChildren() + { + return $this->children; + } + + /** + * Sets children profiler. + * + * @param Profile[] $children + */ + public function setChildren(array $children) + { + $this->children = array(); + foreach ($children as $child) { + $this->addChild($child); + } + } + + /** + * Adds the child token. + */ + public function addChild(Profile $child) + { + $this->children[] = $child; + $child->setParent($this); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @return DataCollectorInterface A DataCollectorInterface instance + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function getCollector($name) + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + /** + * Gets the Collectors associated with this profile. + * + * @return DataCollectorInterface[] + */ + public function getCollectors() + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profile. + * + * @param DataCollectorInterface[] $collectors + */ + public function setCollectors(array $collectors) + { + $this->collectors = array(); + foreach ($collectors as $collector) { + $this->addCollector($collector); + } + } + + /** + * Adds a Collector. + */ + public function addCollector(DataCollectorInterface $collector) + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + * + * @return bool + */ + public function hasCollector($name) + { + return isset($this->collectors[$name]); + } + + public function __sleep() + { + return array('token', 'parent', 'children', 'collectors', 'ip', 'method', 'url', 'time'); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/Profiler.php b/vendor/symfony/http-kernel/Profiler/Profiler.php new file mode 100644 index 0000000..1ccfccf --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/Profiler.php @@ -0,0 +1,285 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; + +/** + * Profiler. + * + * @author Fabien Potencier + */ +class Profiler +{ + private $storage; + + /** + * @var DataCollectorInterface[] + */ + private $collectors = array(); + + private $logger; + + /** + * @var bool + */ + private $enabled = true; + + public function __construct(ProfilerStorageInterface $storage, LoggerInterface $logger = null) + { + $this->storage = $storage; + $this->logger = $logger; + } + + /** + * Disables the profiler. + */ + public function disable() + { + $this->enabled = false; + } + + /** + * Enables the profiler. + */ + public function enable() + { + $this->enabled = true; + } + + /** + * Loads the Profile for the given Response. + * + * @return Profile|false A Profile instance + */ + public function loadProfileFromResponse(Response $response) + { + if (!$token = $response->headers->get('X-Debug-Token')) { + return false; + } + + return $this->loadProfile($token); + } + + /** + * Loads the Profile for the given token. + * + * @param string $token A token + * + * @return Profile A Profile instance + */ + public function loadProfile($token) + { + return $this->storage->read($token); + } + + /** + * Saves a Profile. + * + * @return bool + */ + public function saveProfile(Profile $profile) + { + // late collect + foreach ($profile->getCollectors() as $collector) { + if ($collector instanceof LateDataCollectorInterface) { + $collector->lateCollect(); + } + } + + if (!($ret = $this->storage->write($profile)) && null !== $this->logger) { + $this->logger->warning('Unable to store the profiler information.', array('configured_storage' => \get_class($this->storage))); + } + + return $ret; + } + + /** + * Purges all data from the storage. + */ + public function purge() + { + $this->storage->purge(); + } + + /** + * Exports the current profiler data. + * + * @return string The exported data + * + * @deprecated since Symfony 2.8, to be removed in 3.0. + */ + public function export(Profile $profile) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + return base64_encode(serialize($profile)); + } + + /** + * Imports data into the profiler storage. + * + * @param string $data A data string as exported by the export() method + * + * @return Profile|false A Profile instance + * + * @deprecated since Symfony 2.8, to be removed in 3.0. + */ + public function import($data) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); + + $profile = unserialize(base64_decode($data)); + + if ($this->storage->read($profile->getToken())) { + return false; + } + + $this->saveProfile($profile); + + return $profile; + } + + /** + * Finds profiler tokens for the given criteria. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * @param string $start The start date to search from + * @param string $end The end date to search to + * + * @return array An array of tokens + * + * @see http://php.net/manual/en/datetime.formats.php for the supported date/time formats + */ + public function find($ip, $url, $limit, $method, $start, $end) + { + return $this->storage->find($ip, $url, $limit, $method, $this->getTimestamp($start), $this->getTimestamp($end)); + } + + /** + * Collects data for the given Response. + * + * @return Profile|null A Profile instance or null if the profiler is disabled + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + if (false === $this->enabled) { + return; + } + + $profile = new Profile(substr(hash('sha256', uniqid(mt_rand(), true)), 0, 6)); + $profile->setTime(time()); + $profile->setUrl($request->getUri()); + $profile->setMethod($request->getMethod()); + $profile->setStatusCode($response->getStatusCode()); + try { + $profile->setIp($request->getClientIp()); + } catch (ConflictingHeadersException $e) { + $profile->setIp('Unknown'); + } + + $response->headers->set('X-Debug-Token', $profile->getToken()); + + foreach ($this->collectors as $collector) { + $collector->collect($request, $response, $exception); + + // we need to clone for sub-requests + $profile->addCollector(clone $collector); + } + + return $profile; + } + + /** + * Gets the Collectors associated with this profiler. + * + * @return array An array of collectors + */ + public function all() + { + return $this->collectors; + } + + /** + * Sets the Collectors associated with this profiler. + * + * @param DataCollectorInterface[] $collectors An array of collectors + */ + public function set(array $collectors = array()) + { + $this->collectors = array(); + foreach ($collectors as $collector) { + $this->add($collector); + } + } + + /** + * Adds a Collector. + */ + public function add(DataCollectorInterface $collector) + { + $this->collectors[$collector->getName()] = $collector; + } + + /** + * Returns true if a Collector for the given name exists. + * + * @param string $name A collector name + * + * @return bool + */ + public function has($name) + { + return isset($this->collectors[$name]); + } + + /** + * Gets a Collector by name. + * + * @param string $name A collector name + * + * @return DataCollectorInterface A DataCollectorInterface instance + * + * @throws \InvalidArgumentException if the collector does not exist + */ + public function get($name) + { + if (!isset($this->collectors[$name])) { + throw new \InvalidArgumentException(sprintf('Collector "%s" does not exist.', $name)); + } + + return $this->collectors[$name]; + } + + private function getTimestamp($value) + { + if (null === $value || '' == $value) { + return; + } + + try { + $value = new \DateTime(is_numeric($value) ? '@'.$value : $value); + } catch (\Exception $e) { + return; + } + + return $value->getTimestamp(); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php new file mode 100644 index 0000000..544fb1f --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/ProfilerStorageInterface.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +/** + * ProfilerStorageInterface. + * + * @author Fabien Potencier + */ +interface ProfilerStorageInterface +{ + /** + * Finds profiler tokens for the given criteria. + * + * @param string $ip The IP + * @param string $url The URL + * @param string $limit The maximum number of tokens to return + * @param string $method The request method + * @param int|null $start The start date to search from + * @param int|null $end The end date to search to + * + * @return array An array of tokens + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null); + + /** + * Reads data associated with the given token. + * + * The method returns false if the token does not exist in the storage. + * + * @param string $token A token + * + * @return Profile The profile associated with token + */ + public function read($token); + + /** + * Saves a Profile. + * + * @return bool Write operation successful + */ + public function write(Profile $profile); + + /** + * Purges all data from the database. + */ + public function purge(); +} diff --git a/vendor/symfony/http-kernel/Profiler/RedisProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/RedisProfilerStorage.php new file mode 100644 index 0000000..6d3d967 --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/RedisProfilerStorage.php @@ -0,0 +1,395 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +@trigger_error('The '.__NAMESPACE__.'\RedisProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED); + +/** + * RedisProfilerStorage stores profiling information in Redis. + * + * @author Andrej Hudec + * @author Stephane PY + * + * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0. + * Use {@link FileProfilerStorage} instead. + */ +class RedisProfilerStorage implements ProfilerStorageInterface +{ + const TOKEN_PREFIX = 'sf_profiler_'; + + const REDIS_OPT_SERIALIZER = 1; + const REDIS_OPT_PREFIX = 2; + const REDIS_SERIALIZER_NONE = 0; + const REDIS_SERIALIZER_PHP = 1; + + protected $dsn; + protected $lifetime; + + /** + * @var \Redis + */ + private $redis; + + /** + * @param string $dsn A data source name + * @param string $username Not used + * @param string $password Not used + * @param int $lifetime The lifetime to use for the purge + */ + public function __construct($dsn, $username = '', $password = '', $lifetime = 86400) + { + $this->dsn = $dsn; + $this->lifetime = (int) $lifetime; + } + + /** + * {@inheritdoc} + */ + public function find($ip, $url, $limit, $method, $start = null, $end = null) + { + $indexName = $this->getIndexName(); + + if (!$indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE)) { + return array(); + } + + $profileList = array_reverse(explode("\n", $indexContent)); + $result = array(); + + foreach ($profileList as $item) { + if (0 === $limit) { + break; + } + + if ('' == $item) { + continue; + } + + $values = explode("\t", $item, 7); + list($itemToken, $itemIp, $itemMethod, $itemUrl, $itemTime, $itemParent) = $values; + $statusCode = isset($values[6]) ? $values[6] : null; + + $itemTime = (int) $itemTime; + + if ($ip && false === strpos($itemIp, $ip) || $url && false === strpos($itemUrl, $url) || $method && false === strpos($itemMethod, $method)) { + continue; + } + + if (!empty($start) && $itemTime < $start) { + continue; + } + + if (!empty($end) && $itemTime > $end) { + continue; + } + + $result[] = array( + 'token' => $itemToken, + 'ip' => $itemIp, + 'method' => $itemMethod, + 'url' => $itemUrl, + 'time' => $itemTime, + 'parent' => $itemParent, + 'status_code' => $statusCode, + ); + --$limit; + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function purge() + { + // delete only items from index + $indexName = $this->getIndexName(); + + $indexContent = $this->getValue($indexName, self::REDIS_SERIALIZER_NONE); + + if (!$indexContent) { + return false; + } + + $profileList = explode("\n", $indexContent); + + $result = array(); + + foreach ($profileList as $item) { + if ('' == $item) { + continue; + } + + if (false !== $pos = strpos($item, "\t")) { + $result[] = $this->getItemName(substr($item, 0, $pos)); + } + } + + $result[] = $indexName; + + return $this->delete($result); + } + + /** + * {@inheritdoc} + */ + public function read($token) + { + if (empty($token)) { + return false; + } + + $profile = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP); + + if (false !== $profile) { + $profile = $this->createProfileFromData($token, $profile); + } + + return $profile; + } + + /** + * {@inheritdoc} + */ + public function write(Profile $profile) + { + $data = array( + 'token' => $profile->getToken(), + 'parent' => $profile->getParentToken(), + 'children' => array_map(function ($p) { return $p->getToken(); }, $profile->getChildren()), + 'data' => $profile->getCollectors(), + 'ip' => $profile->getIp(), + 'method' => $profile->getMethod(), + 'url' => $profile->getUrl(), + 'time' => $profile->getTime(), + ); + + $profileIndexed = false !== $this->getValue($this->getItemName($profile->getToken())); + + if ($this->setValue($this->getItemName($profile->getToken()), $data, $this->lifetime, self::REDIS_SERIALIZER_PHP)) { + if (!$profileIndexed) { + // Add to index + $indexName = $this->getIndexName(); + + $indexRow = implode("\t", array( + $profile->getToken(), + $profile->getIp(), + $profile->getMethod(), + $profile->getUrl(), + $profile->getTime(), + $profile->getParentToken(), + $profile->getStatusCode(), + ))."\n"; + + return $this->appendValue($indexName, $indexRow, $this->lifetime); + } + + return true; + } + + return false; + } + + /** + * Internal convenience method that returns the instance of Redis. + * + * @return \Redis + * + * @throws \RuntimeException + */ + protected function getRedis() + { + if (null === $this->redis) { + $data = parse_url($this->dsn); + + if (false === $data || !isset($data['scheme']) || 'redis' !== $data['scheme'] || !isset($data['host']) || !isset($data['port'])) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Redis with an invalid dsn "%s". The minimal expected format is "redis://[host]:port".', $this->dsn)); + } + + if (!\extension_loaded('redis')) { + throw new \RuntimeException('RedisProfilerStorage requires that the redis extension is loaded.'); + } + + $redis = new \Redis(); + $redis->connect($data['host'], $data['port']); + + if (isset($data['path'])) { + $redis->select(substr($data['path'], 1)); + } + + if (isset($data['pass'])) { + $redis->auth($data['pass']); + } + + $redis->setOption(self::REDIS_OPT_PREFIX, self::TOKEN_PREFIX); + + $this->redis = $redis; + } + + return $this->redis; + } + + /** + * Set instance of the Redis. + * + * @param \Redis $redis + */ + public function setRedis($redis) + { + $this->redis = $redis; + } + + private function createProfileFromData($token, $data, $parent = null) + { + $profile = new Profile($token); + $profile->setIp($data['ip']); + $profile->setMethod($data['method']); + $profile->setUrl($data['url']); + $profile->setTime($data['time']); + $profile->setCollectors($data['data']); + + if (!$parent && $data['parent']) { + $parent = $this->read($data['parent']); + } + + if ($parent) { + $profile->setParent($parent); + } + + foreach ($data['children'] as $token) { + if (!$token) { + continue; + } + + if (!$childProfileData = $this->getValue($this->getItemName($token), self::REDIS_SERIALIZER_PHP)) { + continue; + } + + $profile->addChild($this->createProfileFromData($token, $childProfileData, $profile)); + } + + return $profile; + } + + /** + * Gets the item name. + * + * @param string $token + * + * @return string + */ + private function getItemName($token) + { + $name = $token; + + if ($this->isItemNameValid($name)) { + return $name; + } + + return false; + } + + /** + * Gets the name of the index. + * + * @return string + */ + private function getIndexName() + { + $name = 'index'; + + if ($this->isItemNameValid($name)) { + return $name; + } + + return false; + } + + private function isItemNameValid($name) + { + $length = \strlen($name); + + if ($length > 2147483648) { + throw new \RuntimeException(sprintf('The Redis item key "%s" is too long (%s bytes). Allowed maximum size is 2^31 bytes.', $name, $length)); + } + + return true; + } + + /** + * Retrieves an item from the Redis server. + * + * @param string $key + * @param int $serializer + * + * @return mixed + */ + private function getValue($key, $serializer = self::REDIS_SERIALIZER_NONE) + { + $redis = $this->getRedis(); + $redis->setOption(self::REDIS_OPT_SERIALIZER, $serializer); + + return $redis->get($key); + } + + /** + * Stores an item on the Redis server under the specified key. + * + * @param string $key + * @param mixed $value + * @param int $expiration + * @param int $serializer + * + * @return bool + */ + private function setValue($key, $value, $expiration = 0, $serializer = self::REDIS_SERIALIZER_NONE) + { + $redis = $this->getRedis(); + $redis->setOption(self::REDIS_OPT_SERIALIZER, $serializer); + + return $redis->setex($key, $expiration, $value); + } + + /** + * Appends data to an existing item on the Redis server. + * + * @param string $key + * @param string $value + * @param int $expiration + * + * @return bool + */ + private function appendValue($key, $value, $expiration = 0) + { + $redis = $this->getRedis(); + $redis->setOption(self::REDIS_OPT_SERIALIZER, self::REDIS_SERIALIZER_NONE); + + if ($redis->exists($key)) { + $redis->append($key, $value); + + return $redis->setTimeout($key, $expiration); + } + + return $redis->setex($key, $expiration, $value); + } + + /** + * Removes the specified keys. + * + * @return bool + */ + private function delete(array $keys) + { + return (bool) $this->getRedis()->delete($keys); + } +} diff --git a/vendor/symfony/http-kernel/Profiler/SqliteProfilerStorage.php b/vendor/symfony/http-kernel/Profiler/SqliteProfilerStorage.php new file mode 100644 index 0000000..549c27e --- /dev/null +++ b/vendor/symfony/http-kernel/Profiler/SqliteProfilerStorage.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Profiler; + +@trigger_error('The '.__NAMESPACE__.'\SqliteProfilerStorage class is deprecated since Symfony 2.8 and will be removed in 3.0. Use FileProfilerStorage instead.', E_USER_DEPRECATED); + +/** + * SqliteProfilerStorage stores profiling information in a SQLite database. + * + * @author Fabien Potencier + * + * @deprecated Deprecated since Symfony 2.8, to be removed in Symfony 3.0. + * Use {@link FileProfilerStorage} instead. + */ +class SqliteProfilerStorage extends PdoProfilerStorage +{ + /** + * @throws \RuntimeException When neither of SQLite3 or PDO_SQLite extension is enabled + */ + protected function initDb() + { + if (null === $this->db || $this->db instanceof \SQLite3) { + if (0 !== strpos($this->dsn, 'sqlite')) { + throw new \RuntimeException(sprintf('Please check your configuration. You are trying to use Sqlite with an invalid dsn "%s". The expected format is "sqlite:/path/to/the/db/file".', $this->dsn)); + } + if (class_exists('SQLite3')) { + $db = new \SQLite3(substr($this->dsn, 7, \strlen($this->dsn)), \SQLITE3_OPEN_READWRITE | \SQLITE3_OPEN_CREATE); + if (method_exists($db, 'busyTimeout')) { + // busyTimeout only exists for PHP >= 5.3.3 + $db->busyTimeout(1000); + } + } elseif (class_exists('PDO') && \in_array('sqlite', \PDO::getAvailableDrivers(), true)) { + $db = new \PDO($this->dsn); + } else { + throw new \RuntimeException('You need to enable either the SQLite3 or PDO_SQLite extension for the profiler to run properly.'); + } + + $db->exec('PRAGMA temp_store=MEMORY; PRAGMA journal_mode=MEMORY;'); + $db->exec('CREATE TABLE IF NOT EXISTS sf_profiler_data (token STRING, data STRING, ip STRING, method STRING, url STRING, time INTEGER, parent STRING, created_at INTEGER, status_code INTEGER)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_created_at ON sf_profiler_data (created_at)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_ip ON sf_profiler_data (ip)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_method ON sf_profiler_data (method)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_url ON sf_profiler_data (url)'); + $db->exec('CREATE INDEX IF NOT EXISTS data_parent ON sf_profiler_data (parent)'); + $db->exec('CREATE UNIQUE INDEX IF NOT EXISTS data_token ON sf_profiler_data (token)'); + + $this->db = $db; + } + + return $this->db; + } + + protected function exec($db, $query, array $args = array()) + { + if ($db instanceof \SQLite3) { + $stmt = $this->prepareStatement($db, $query); + foreach ($args as $arg => $val) { + $stmt->bindValue($arg, $val, \is_int($val) ? \SQLITE3_INTEGER : \SQLITE3_TEXT); + } + + $res = $stmt->execute(); + if (false === $res) { + throw new \RuntimeException(sprintf('Error executing SQLite query "%s"', $query)); + } + $res->finalize(); + } else { + parent::exec($db, $query, $args); + } + } + + protected function fetch($db, $query, array $args = array()) + { + $return = array(); + + if ($db instanceof \SQLite3) { + $stmt = $this->prepareStatement($db, $query); + foreach ($args as $arg => $val) { + $stmt->bindValue($arg, $val, \is_int($val) ? \SQLITE3_INTEGER : \SQLITE3_TEXT); + } + $res = $stmt->execute(); + while ($row = $res->fetchArray(\SQLITE3_ASSOC)) { + $return[] = $row; + } + $res->finalize(); + $stmt->close(); + } else { + $return = parent::fetch($db, $query, $args); + } + + return $return; + } + + /** + * {@inheritdoc} + */ + protected function buildCriteria($ip, $url, $start, $end, $limit, $method) + { + $criteria = array(); + $args = array(); + + if ($ip = preg_replace('/[^\d\.]/', '', $ip)) { + $criteria[] = 'ip LIKE :ip'; + $args[':ip'] = '%'.$ip.'%'; + } + + if ($url) { + $criteria[] = 'url LIKE :url ESCAPE "\"'; + $args[':url'] = '%'.addcslashes($url, '%_\\').'%'; + } + + if ($method) { + $criteria[] = 'method = :method'; + $args[':method'] = $method; + } + + if (!empty($start)) { + $criteria[] = 'time >= :start'; + $args[':start'] = $start; + } + + if (!empty($end)) { + $criteria[] = 'time <= :end'; + $args[':end'] = $end; + } + + return array($criteria, $args); + } + + protected function close($db) + { + if ($db instanceof \SQLite3) { + $db->close(); + } + } +} diff --git a/vendor/symfony/http-kernel/TerminableInterface.php b/vendor/symfony/http-kernel/TerminableInterface.php new file mode 100644 index 0000000..8aa3319 --- /dev/null +++ b/vendor/symfony/http-kernel/TerminableInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +/** + * Terminable extends the Kernel request/response cycle with dispatching a post + * response event after sending the response and before shutting down the kernel. + * + * @author Jordi Boggiano + * @author Pierre Minnieur + */ +interface TerminableInterface +{ + /** + * Terminates a request/response cycle. + * + * Should be called after sending the response and before shutting down the kernel. + */ + public function terminate(Request $request, Response $response); +} diff --git a/vendor/symfony/http-kernel/UriSigner.php b/vendor/symfony/http-kernel/UriSigner.php new file mode 100644 index 0000000..526a919 --- /dev/null +++ b/vendor/symfony/http-kernel/UriSigner.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel; + +/** + * Signs URIs. + * + * @author Fabien Potencier + */ +class UriSigner +{ + private $secret; + + /** + * @param string $secret A secret + */ + public function __construct($secret) + { + $this->secret = $secret; + } + + /** + * Signs a URI. + * + * The given URI is signed by adding a _hash query string parameter + * which value depends on the URI and the secret. + * + * @param string $uri A URI to sign + * + * @return string The signed URI + */ + public function sign($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + $uri = $this->buildUrl($url, $params); + + return $uri.(false === strpos($uri, '?') ? '?' : '&').'_hash='.$this->computeHash($uri); + } + + /** + * Checks that a URI contains the correct hash. + * + * @param string $uri A signed URI + * + * @return bool True if the URI is signed correctly, false otherwise + */ + public function check($uri) + { + $url = parse_url($uri); + if (isset($url['query'])) { + parse_str($url['query'], $params); + } else { + $params = array(); + } + + if (empty($params['_hash'])) { + return false; + } + + $hash = urlencode($params['_hash']); + unset($params['_hash']); + + return $this->computeHash($this->buildUrl($url, $params)) === $hash; + } + + private function computeHash($uri) + { + return urlencode(base64_encode(hash_hmac('sha256', $uri, $this->secret, true))); + } + + private function buildUrl(array $url, array $params = array()) + { + ksort($params, SORT_STRING); + $url['query'] = http_build_query($params, '', '&'); + + $scheme = isset($url['scheme']) ? $url['scheme'].'://' : ''; + $host = isset($url['host']) ? $url['host'] : ''; + $port = isset($url['port']) ? ':'.$url['port'] : ''; + $user = isset($url['user']) ? $url['user'] : ''; + $pass = isset($url['pass']) ? ':'.$url['pass'] : ''; + $pass = ($user || $pass) ? "$pass@" : ''; + $path = isset($url['path']) ? $url['path'] : ''; + $query = isset($url['query']) && $url['query'] ? '?'.$url['query'] : ''; + $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : ''; + + return $scheme.$user.$pass.$host.$port.$path.$query.$fragment; + } +} diff --git a/vendor/symfony/http-kernel/composer.json b/vendor/symfony/http-kernel/composer.json new file mode 100644 index 0000000..e082b2a --- /dev/null +++ b/vendor/symfony/http-kernel/composer.json @@ -0,0 +1,68 @@ +{ + "name": "symfony/http-kernel", + "type": "library", + "description": "Symfony HttpKernel Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/event-dispatcher": "^2.6.7|~3.0.0", + "symfony/http-foundation": "~2.7.36|~2.8.29|~3.1.6", + "symfony/debug": "^2.6.2", + "symfony/polyfill-ctype": "~1.8", + "psr/log": "~1.0" + }, + "require-dev": { + "symfony/browser-kit": "~2.3|~3.0.0", + "symfony/class-loader": "~2.1|~3.0.0", + "symfony/config": "~2.8", + "symfony/console": "~2.3|~3.0.0", + "symfony/css-selector": "^2.0.5|~3.0.0", + "symfony/dependency-injection": "~2.8|~3.0.0", + "symfony/dom-crawler": "^2.0.5|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "symfony/finder": "^2.0.5|~3.0.0", + "symfony/process": "^2.0.5|~3.0.0", + "symfony/routing": "~2.8|~3.0.0", + "symfony/stopwatch": "~2.3|~3.0.0", + "symfony/templating": "~2.2|~3.0.0", + "symfony/translation": "^2.0.5|~3.0.0", + "symfony/var-dumper": "~2.6|~3.0.0" + }, + "conflict": { + "symfony/config": "<2.7", + "twig/twig": "<1.34|<2.4,>=2" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\HttpKernel\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/polyfill-ctype/Ctype.php b/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000..58414dc --- /dev/null +++ b/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,227 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param string|int $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param string|int $int + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/vendor/symfony/polyfill-ctype/LICENSE b/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-ctype/README.md b/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 0000000..8add1ab --- /dev/null +++ b/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-ctype/bootstrap.php b/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000..14d1d0f --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } + function ctype_print($text) { return p\Ctype::ctype_print($text); } + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } + function ctype_space($text) { return p\Ctype::ctype_space($text); } + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/composer.json b/vendor/symfony/polyfill-ctype/composer.json new file mode 100644 index 0000000..c24e20c --- /dev/null +++ b/vendor/symfony/polyfill-ctype/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + } +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..a5e4a8f --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,800 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + const MB_CASE_FOLD = PHP_INT_MAX; + + private static $encodingList = array('ASCII', 'UTF-8'); + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = array( + array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"), + array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'), + ); + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) + { + $vars = array(&$a, &$b, &$c, &$d, &$e, &$f); + + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || !$convmap) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return Mbstring::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || !$convmap) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s); + } else { + if (MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { + self::$internalEncoding = $encoding; + + return true; + } + + return false; + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($lang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $lang; + + return true; + } + + return false; + } + + public static function mb_list_encodings() + { + return array('UTF-8'); + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return array('utf8'); + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + + return false; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (0 === strcasecmp($c, 'none')) { + return true; + } + + return null !== $c ? false : 'none'; + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrchr($haystack, $needle, $part); + } + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = array( + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ); + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..342e828 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](http://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..e6fbfa6 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1096 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ᾼ', + 'ι' => 'Ι', + 'ῃ' => 'ῌ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ῼ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..2fdcc5a --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_strlen')) { + define('MB_CASE_UPPER', 0); + define('MB_CASE_LOWER', 1); + define('MB_CASE_TITLE', 2); + + function mb_convert_encoding($s, $to, $from = null) { return p\Mbstring::mb_convert_encoding($s, $to, $from); } + function mb_decode_mimeheader($s) { return p\Mbstring::mb_decode_mimeheader($s); } + function mb_encode_mimeheader($s, $charset = null, $transferEnc = null, $lf = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($s, $charset, $transferEnc, $lf, $indent); } + function mb_decode_numericentity($s, $convmap, $enc = null) { return p\Mbstring::mb_decode_numericentity($s, $convmap, $enc); } + function mb_encode_numericentity($s, $convmap, $enc = null, $is_hex = false) { return p\Mbstring::mb_encode_numericentity($s, $convmap, $enc, $is_hex); } + function mb_convert_case($s, $mode, $enc = null) { return p\Mbstring::mb_convert_case($s, $mode, $enc); } + function mb_internal_encoding($enc = null) { return p\Mbstring::mb_internal_encoding($enc); } + function mb_language($lang = null) { return p\Mbstring::mb_language($lang); } + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } + function mb_check_encoding($var = null, $encoding = null) { return p\Mbstring::mb_check_encoding($var, $encoding); } + function mb_detect_encoding($str, $encodingList = null, $strict = false) { return p\Mbstring::mb_detect_encoding($str, $encodingList, $strict); } + function mb_detect_order($encodingList = null) { return p\Mbstring::mb_detect_order($encodingList); } + function mb_parse_str($s, &$result = array()) { parse_str($s, $result); } + function mb_strlen($s, $enc = null) { return p\Mbstring::mb_strlen($s, $enc); } + function mb_strpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strpos($s, $needle, $offset, $enc); } + function mb_strtolower($s, $enc = null) { return p\Mbstring::mb_strtolower($s, $enc); } + function mb_strtoupper($s, $enc = null) { return p\Mbstring::mb_strtoupper($s, $enc); } + function mb_substitute_character($char = null) { return p\Mbstring::mb_substitute_character($char); } + function mb_substr($s, $start, $length = 2147483647, $enc = null) { return p\Mbstring::mb_substr($s, $start, $length, $enc); } + function mb_stripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_stripos($s, $needle, $offset, $enc); } + function mb_stristr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_stristr($s, $needle, $part, $enc); } + function mb_strrchr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrchr($s, $needle, $part, $enc); } + function mb_strrichr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strrichr($s, $needle, $part, $enc); } + function mb_strripos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strripos($s, $needle, $offset, $enc); } + function mb_strrpos($s, $needle, $offset = 0, $enc = null) { return p\Mbstring::mb_strrpos($s, $needle, $offset, $enc); } + function mb_strstr($s, $needle, $part = false, $enc = null) { return p\Mbstring::mb_strstr($s, $needle, $part, $enc); } + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } + function mb_http_output($enc = null) { return p\Mbstring::mb_http_output($enc); } + function mb_strwidth($s, $enc = null) { return p\Mbstring::mb_strwidth($s, $enc); } + function mb_substr_count($haystack, $needle, $enc = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $enc); } + function mb_output_handler($contents, $status) { return p\Mbstring::mb_output_handler($contents, $status); } + function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } + function mb_convert_variables($toEncoding, $fromEncoding, &$a = null, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null) { return p\Mbstring::mb_convert_variables($toEncoding, $fromEncoding, $a, $b, $c, $d, $e, $f); } +} +if (!function_exists('mb_chr')) { + function mb_ord($s, $enc = null) { return p\Mbstring::mb_ord($s, $enc); } + function mb_chr($code, $enc = null) { return p\Mbstring::mb_chr($code, $enc); } + function mb_scrub($s, $enc = null) { $enc = null === $enc ? mb_internal_encoding() : $enc; return mb_convert_encoding($s, $enc, $enc); } +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..0bce087 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + } +} diff --git a/vendor/symfony/polyfill-php54/LICENSE b/vendor/symfony/polyfill-php54/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-php54/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php54/Php54.php b/vendor/symfony/polyfill-php54/Php54.php new file mode 100644 index 0000000..5fe232c --- /dev/null +++ b/vendor/symfony/polyfill-php54/Php54.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php54; + +/** + * @author Nicolas Grekas + * + * @internal + */ +final class Php54 +{ + public static function hex2bin($data) + { + $len = \strlen($data); + + if (null === $len) { + return; + } + if ($len % 2) { + trigger_error('hex2bin(): Hexadecimal input string must have an even length', E_USER_WARNING); + + return false; + } + + return pack('H*', $data); + } +} diff --git a/vendor/symfony/polyfill-php54/README.md b/vendor/symfony/polyfill-php54/README.md new file mode 100644 index 0000000..d5dd463 --- /dev/null +++ b/vendor/symfony/polyfill-php54/README.md @@ -0,0 +1,17 @@ +Symfony Polyfill / Php54 +======================== + +This component provides functions unavailable in releases prior to PHP 5.4: + +- [`trait_exists`](http://php.net/trait_exists) +- [`class_uses`](http://php.net/class_uses) +- [`hex2bin`](http://php.net/hex2bin) +- [`session_register_shutdown`](http://php.net/session_register_shutdown) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php54/Resources/stubs/CallbackFilterIterator.php b/vendor/symfony/polyfill-php54/Resources/stubs/CallbackFilterIterator.php new file mode 100644 index 0000000..b69db3d --- /dev/null +++ b/vendor/symfony/polyfill-php54/Resources/stubs/CallbackFilterIterator.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class CallbackFilterIterator extends FilterIterator +{ + private $iterator; + private $callback; + + public function __construct(Iterator $iterator, $callback) + { + $this->iterator = $iterator; + $this->callback = $callback; + parent::__construct($iterator); + } + + public function accept() + { + return call_user_func($this->callback, $this->current(), $this->key(), $this->iterator); + } +} diff --git a/vendor/symfony/polyfill-php54/Resources/stubs/RecursiveCallbackFilterIterator.php b/vendor/symfony/polyfill-php54/Resources/stubs/RecursiveCallbackFilterIterator.php new file mode 100644 index 0000000..63afd38 --- /dev/null +++ b/vendor/symfony/polyfill-php54/Resources/stubs/RecursiveCallbackFilterIterator.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +class RecursiveCallbackFilterIterator extends CallbackFilterIterator implements RecursiveIterator +{ + private $iterator; + private $callback; + + public function __construct(RecursiveIterator $iterator, $callback) + { + $this->iterator = $iterator; + $this->callback = $callback; + parent::__construct($iterator, $callback); + } + + public function hasChildren() + { + return $this->iterator->hasChildren(); + } + + public function getChildren() + { + return new static($this->iterator->getChildren(), $this->callback); + } +} diff --git a/vendor/symfony/polyfill-php54/Resources/stubs/SessionHandlerInterface.php b/vendor/symfony/polyfill-php54/Resources/stubs/SessionHandlerInterface.php new file mode 100644 index 0000000..9baa7bc --- /dev/null +++ b/vendor/symfony/polyfill-php54/Resources/stubs/SessionHandlerInterface.php @@ -0,0 +1,102 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * SessionHandlerInterface for PHP < 5.4. + * + * The order in which these methods are invoked by PHP are: + * 1. open [session_start] + * 2. read + * 3. gc [optional depending on probability settings: gc_probability / gc_divisor] + * 4. destroy [optional when session_regenerate_id(true) is used] + * 5. write [session_write_close] or destroy [session_destroy] + * 6. close + * + * Extensive documentation can be found at php.net, see links: + * + * @see http://php.net/sessionhandlerinterface + * @see http://php.net/session.customhandler + * @see http://php.net/session-set-save-handler + * + * @author Drak + * @author Tobias Schultze + */ +interface SessionHandlerInterface +{ + /** + * Re-initializes existing session, or creates a new one. + * + * @see http://php.net/sessionhandlerinterface.open + * + * @param string $savePath Save path + * @param string $sessionName Session name, see http://php.net/function.session-name.php + * + * @return bool true on success, false on failure + */ + public function open($savePath, $sessionName); + + /** + * Closes the current session. + * + * @see http://php.net/sessionhandlerinterface.close + * + * @return bool true on success, false on failure + */ + public function close(); + + /** + * Reads the session data. + * + * @see http://php.net/sessionhandlerinterface.read + * + * @param string $sessionId Session ID, see http://php.net/function.session-id + * + * @return string Same session data as passed in write() or empty string when non-existent or on failure + */ + public function read($sessionId); + + /** + * Writes the session data to the storage. + * + * Care, the session ID passed to write() can be different from the one previously + * received in read() when the session ID changed due to session_regenerate_id(). + * + * @see http://php.net/sessionhandlerinterface.write + * + * @param string $sessionId Session ID , see http://php.net/function.session-id + * @param string $data Serialized session data to save + * + * @return bool true on success, false on failure + */ + public function write($sessionId, $data); + + /** + * Destroys a session. + * + * @see http://php.net/sessionhandlerinterface.destroy + * + * @param string $sessionId Session ID, see http://php.net/function.session-id + * + * @return bool true on success, false on failure + */ + public function destroy($sessionId); + + /** + * Cleans up expired sessions (garbage collection). + * + * @see http://php.net/sessionhandlerinterface.gc + * + * @param string|int $maxlifetime Sessions that have not updated for the last maxlifetime seconds will be removed + * + * @return bool true on success, false on failure + */ + public function gc($maxlifetime); +} diff --git a/vendor/symfony/polyfill-php54/bootstrap.php b/vendor/symfony/polyfill-php54/bootstrap.php new file mode 100644 index 0000000..d72c199 --- /dev/null +++ b/vendor/symfony/polyfill-php54/bootstrap.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php54 as p; + +if (PHP_VERSION_ID < 50400) { + if (!function_exists('trait_exists')) { + function trait_exists($class, $autoload = true) { return $autoload && \class_exists($class, $autoload) && false; } + } + if (!function_exists('class_uses')) { + function class_uses($class, $autoload = true) + { + if (\is_object($class) || \class_exists($class, $autoload) || \interface_exists($class, false)) { + return array(); + } + + return false; + } + } + if (!function_exists('hex2bin')) { + function hex2bin($data) { return p\Php54::hex2bin($data); } + } + if (!function_exists('session_register_shutdown')) { + function session_register_shutdown() { register_shutdown_function('session_write_close'); } + } +} diff --git a/vendor/symfony/polyfill-php54/composer.json b/vendor/symfony/polyfill-php54/composer.json new file mode 100644 index 0000000..fd2ec53 --- /dev/null +++ b/vendor/symfony/polyfill-php54/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/polyfill-php54", + "type": "library", + "description": "Symfony polyfill backporting some PHP 5.4+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php54\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + } +} diff --git a/vendor/symfony/polyfill-php55/LICENSE b/vendor/symfony/polyfill-php55/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-php55/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/polyfill-php55/Php55.php b/vendor/symfony/polyfill-php55/Php55.php new file mode 100644 index 0000000..f5cb483 --- /dev/null +++ b/vendor/symfony/polyfill-php55/Php55.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php55; + +/** + * @internal + */ +final class Php55 +{ + public static function boolval($val) + { + return (bool) $val; + } + + public static function json_last_error_msg() + { + switch (json_last_error()) { + case JSON_ERROR_NONE: return 'No error'; + case JSON_ERROR_DEPTH: return 'Maximum stack depth exceeded'; + case JSON_ERROR_STATE_MISMATCH: return 'State mismatch (invalid or malformed JSON)'; + case JSON_ERROR_CTRL_CHAR: return 'Control character error, possibly incorrectly encoded'; + case JSON_ERROR_SYNTAX: return 'Syntax error'; + case JSON_ERROR_UTF8: return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + default: return 'Unknown error'; + } + } + + /** + * @author Sebastiaan Stok + * @author Scott + */ + public static function hash_pbkdf2($algorithm, $password, $salt, $iterations, $length = 0, $rawOutput = false) + { + // Pre-hash for optimization if password length > hash length + $hashLength = \strlen(hash($algorithm, '', true)); + switch ($algorithm) { + case 'sha224': + case 'sha256': + $blockSize = 64; + break; + case 'sha384': + case 'sha512': + $blockSize = 128; + break; + default: + $blockSize = $hashLength; + break; + } + if ($length < 1) { + $length = $hashLength; + if (!$rawOutput) { + $length <<= 1; + } + } + + // Number of blocks needed to create the derived key + $blocks = ceil($length / $hashLength); + $digest = ''; + if (\strlen($password) > $blockSize) { + $password = hash($algorithm, $password, true); + } + + for ($i = 1; $i <= $blocks; ++$i) { + $ib = $block = hash_hmac($algorithm, $salt.pack('N', $i), $password, true); + + // Iterations + for ($j = 1; $j < $iterations; ++$j) { + $ib ^= ($block = hash_hmac($algorithm, $block, $password, true)); + } + + $digest .= $ib; + } + + if (!$rawOutput) { + $digest = bin2hex($digest); + } + + return substr($digest, 0, $length); + } +} diff --git a/vendor/symfony/polyfill-php55/Php55ArrayColumn.php b/vendor/symfony/polyfill-php55/Php55ArrayColumn.php new file mode 100644 index 0000000..bb9b36a --- /dev/null +++ b/vendor/symfony/polyfill-php55/Php55ArrayColumn.php @@ -0,0 +1,64 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +namespace Symfony\Polyfill\Php55; + +/** + * @internal + */ +final class Php55ArrayColumn +{ + public static function array_column(array $input, $columnKey, $indexKey = null) + { + $output = array(); + + foreach ($input as $row) { + $key = $value = null; + $keySet = $valueSet = false; + + if (null !== $indexKey && array_key_exists($indexKey, $row)) { + $keySet = true; + $key = (string) $row[$indexKey]; + } + + if (null === $columnKey) { + $valueSet = true; + $value = $row; + } elseif (\is_array($row) && \array_key_exists($columnKey, $row)) { + $valueSet = true; + $value = $row[$columnKey]; + } + + if ($valueSet) { + if ($keySet) { + $output[$key] = $value; + } else { + $output[] = $value; + } + } + } + + return $output; + } +} diff --git a/vendor/symfony/polyfill-php55/README.md b/vendor/symfony/polyfill-php55/README.md new file mode 100644 index 0000000..3e432e4 --- /dev/null +++ b/vendor/symfony/polyfill-php55/README.md @@ -0,0 +1,18 @@ +Symfony Polyfill / Php55 +======================== + +This component provides functions unavailable in releases prior to PHP 5.5: + +- [`boolval`](http://php.net/boolval) +- [`json_last_error_msg`](http://php.net/json_last_error_msg) +- [`array_column`](http://php.net/array_column) +- [`hash_pbkdf2`](http://php.net/hash_pbkdf2) +- `password_*` functions (from [ircmaxell/password_compat](https://github.com/ircmaxell/password_compat)) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php55/bootstrap.php b/vendor/symfony/polyfill-php55/bootstrap.php new file mode 100644 index 0000000..5e634fe --- /dev/null +++ b/vendor/symfony/polyfill-php55/bootstrap.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php55 as p; + +if (PHP_VERSION_ID < 50500) { + if (!function_exists('boolval')) { + function boolval($val) { return p\Php55::boolval($val); } + } + if (!function_exists('json_last_error_msg')) { + function json_last_error_msg() { return p\Php55::json_last_error_msg(); } + } + if (!function_exists('array_column')) { + function array_column($array, $columnKey, $indexKey = null) { return p\Php55ArrayColumn::array_column($array, $columnKey, $indexKey); } + } + if (!function_exists('hash_pbkdf2')) { + function hash_pbkdf2($algorithm, $password, $salt, $iterations, $length = 0, $rawOutput = false) { return p\Php55::hash_pbkdf2($algorithm, $password, $salt, $iterations, $length, $rawOutput); } + } +} diff --git a/vendor/symfony/polyfill-php55/composer.json b/vendor/symfony/polyfill-php55/composer.json new file mode 100644 index 0000000..c2dfd80 --- /dev/null +++ b/vendor/symfony/polyfill-php55/composer.json @@ -0,0 +1,32 @@ +{ + "name": "symfony/polyfill-php55", + "type": "library", + "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "ircmaxell/password-compat": "~1.0" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php55\\": "" }, + "files": [ "bootstrap.php" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "1.11-dev" + } + } +} diff --git a/vendor/symfony/proxy-manager-bridge/LICENSE b/vendor/symfony/proxy-manager-bridge/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/proxy-manager-bridge/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php b/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php new file mode 100644 index 0000000..3298b84 --- /dev/null +++ b/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV1.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; + +use ProxyManager\Factory\LazyLoadingValueHolderFactory as BaseFactory; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderFactoryV1 extends BaseFactory +{ + private $generatorV1; + + /** + * {@inheritdoc} + */ + protected function getGenerator() + { + return $this->generatorV1 ?: $this->generatorV1 = new LazyLoadingValueHolderGenerator(); + } +} diff --git a/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php b/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php new file mode 100644 index 0000000..a643f33 --- /dev/null +++ b/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/LazyLoadingValueHolderFactoryV2.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; + +use ProxyManager\Factory\LazyLoadingValueHolderFactory as BaseFactory; +use ProxyManager\ProxyGenerator\ProxyGeneratorInterface; +use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\LazyLoadingValueHolderGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderFactoryV2 extends BaseFactory +{ + private $generator; + + /** + * {@inheritdoc} + */ + protected function getGenerator(): ProxyGeneratorInterface + { + return $this->generator ?: $this->generator = new LazyLoadingValueHolderGenerator(); + } +} diff --git a/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/RuntimeInstantiator.php b/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/RuntimeInstantiator.php new file mode 100644 index 0000000..7d083a6 --- /dev/null +++ b/vendor/symfony/proxy-manager-bridge/LazyProxy/Instantiator/RuntimeInstantiator.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\Instantiator; + +use ProxyManager\Configuration; +use ProxyManager\Factory\LazyLoadingValueHolderFactory; +use ProxyManager\GeneratorStrategy\EvaluatingGeneratorStrategy; +use ProxyManager\Proxy\LazyLoadingInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\InstantiatorInterface; + +/** + * Runtime lazy loading proxy generator. + * + * @author Marco Pivetta + */ +class RuntimeInstantiator implements InstantiatorInterface +{ + /** + * @var LazyLoadingValueHolderFactory + */ + private $factory; + + public function __construct() + { + $config = new Configuration(); + $config->setGeneratorStrategy(new EvaluatingGeneratorStrategy()); + + if (method_exists('ProxyManager\Version', 'getVersion')) { + $this->factory = new LazyLoadingValueHolderFactoryV2($config); + } else { + $this->factory = new LazyLoadingValueHolderFactoryV1($config); + } + } + + /** + * {@inheritdoc} + */ + public function instantiateProxy(ContainerInterface $container, Definition $definition, $id, $realInstantiator) + { + return $this->factory->createProxy( + $definition->getClass(), + function (&$wrappedInstance, LazyLoadingInterface $proxy) use ($realInstantiator) { + $wrappedInstance = \call_user_func($realInstantiator); + + $proxy->setProxyInitializer(null); + + return true; + } + ); + } +} diff --git a/vendor/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php b/vendor/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php new file mode 100644 index 0000000..1d9432f --- /dev/null +++ b/vendor/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/LazyLoadingValueHolderGenerator.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper; + +use ProxyManager\ProxyGenerator\LazyLoadingValueHolderGenerator as BaseGenerator; +use Zend\Code\Generator\ClassGenerator; + +/** + * @internal + */ +class LazyLoadingValueHolderGenerator extends BaseGenerator +{ + /** + * {@inheritdoc} + */ + public function generate(\ReflectionClass $originalClass, ClassGenerator $classGenerator) + { + parent::generate($originalClass, $classGenerator); + + if ($classGenerator->hasMethod('__destruct')) { + $destructor = $classGenerator->getMethod('__destruct'); + $body = $destructor->getBody(); + $newBody = preg_replace('/^(\$this->initializer[a-zA-Z0-9]++) && .*;\n\nreturn (\$this->valueHolder)/', '$1 || $2', $body); + + if ($body === $newBody) { + throw new \UnexpectedValueException(sprintf('Unexpected lazy-proxy format generated for method %s::__destruct()', $originalClass->name)); + } + + $destructor->setBody($newBody); + } + } +} diff --git a/vendor/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/ProxyDumper.php b/vendor/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/ProxyDumper.php new file mode 100644 index 0000000..662c274 --- /dev/null +++ b/vendor/symfony/proxy-manager-bridge/LazyProxy/PhpDumper/ProxyDumper.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper; + +use ProxyManager\Generator\ClassGenerator; +use ProxyManager\GeneratorStrategy\BaseGeneratorStrategy; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; + +/** + * Generates dumped PHP code of proxies via reflection. + * + * @author Marco Pivetta + */ +class ProxyDumper implements DumperInterface +{ + private $salt; + private $proxyGenerator; + private $classGenerator; + + /** + * @param string $salt + */ + public function __construct($salt = '') + { + $this->salt = $salt; + $this->proxyGenerator = new LazyLoadingValueHolderGenerator(); + $this->classGenerator = new BaseGeneratorStrategy(); + } + + /** + * {@inheritdoc} + */ + public function isProxyCandidate(Definition $definition) + { + return $definition->isLazy() && ($class = $definition->getClass()) && class_exists($class); + } + + /** + * {@inheritdoc} + */ + public function getProxyFactoryCode(Definition $definition, $id) + { + $instantiation = 'return'; + + if ($definition->isShared()) { + $instantiation .= " \$this->services['$id'] ="; + + if (\defined('Symfony\Component\DependencyInjection\ContainerInterface::SCOPE_CONTAINER') && ContainerInterface::SCOPE_CONTAINER !== $scope = $definition->getScope(false)) { + $instantiation .= " \$this->scopedServices['$scope']['$id'] ="; + } + } + + $methodName = 'get'.Container::camelize($id).'Service'; + $proxyClass = $this->getProxyClassName($definition); + + $generatedClass = $this->generateProxyClass($definition); + + $constructorCall = $generatedClass->hasMethod('staticProxyConstructor') + ? $proxyClass.'::staticProxyConstructor' + : 'new '.$proxyClass; + + return <<$methodName(false); + + \$proxy->setProxyInitializer(null); + + return true; + } + ); + } + + +EOF; + } + + /** + * {@inheritdoc} + */ + public function getProxyCode(Definition $definition) + { + return preg_replace( + '/(\$this->initializer[0-9a-f]++) && \1->__invoke\(\$this->(valueHolder[0-9a-f]++), (.*?), \1\);/', + '$1 && ($1->__invoke(\$$2, $3, $1) || 1) && $this->$2 = \$$2;', + $this->classGenerator->generate($this->generateProxyClass($definition)) + ); + } + + /** + * Produces the proxy class name for the given definition. + * + * @return string + */ + private function getProxyClassName(Definition $definition) + { + return str_replace('\\', '', $definition->getClass()).'_'.spl_object_hash($definition).$this->salt; + } + + /** + * @return ClassGenerator + */ + private function generateProxyClass(Definition $definition) + { + $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); + + $this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass); + + return $generatedClass; + } +} diff --git a/vendor/symfony/proxy-manager-bridge/composer.json b/vendor/symfony/proxy-manager-bridge/composer.json new file mode 100644 index 0000000..7b560bd --- /dev/null +++ b/vendor/symfony/proxy-manager-bridge/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/proxy-manager-bridge", + "type": "symfony-bridge", + "description": "Symfony ProxyManager Bridge", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/dependency-injection": "~2.8|~3.0.0", + "ocramius/proxy-manager": "~0.4|~1.0|~2.0" + }, + "require-dev": { + "symfony/config": "~2.3|~3.0.0" + }, + "autoload": { + "psr-4": { "Symfony\\Bridge\\ProxyManager\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/routing/Annotation/Route.php b/vendor/symfony/routing/Annotation/Route.php new file mode 100644 index 0000000..4e6b2c5 --- /dev/null +++ b/vendor/symfony/routing/Annotation/Route.php @@ -0,0 +1,180 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Annotation; + +/** + * Annotation class for @Route(). + * + * @Annotation + * @Target({"CLASS", "METHOD"}) + * + * @author Fabien Potencier + */ +class Route +{ + private $path; + private $name; + private $requirements = array(); + private $options = array(); + private $defaults = array(); + private $host; + private $methods = array(); + private $schemes = array(); + private $condition; + + /** + * @param array $data An array of key/value parameters + * + * @throws \BadMethodCallException + */ + public function __construct(array $data) + { + if (isset($data['value'])) { + $data['path'] = $data['value']; + unset($data['value']); + } + + foreach ($data as $key => $value) { + $method = 'set'.str_replace('_', '', $key); + if (!method_exists($this, $method)) { + throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, \get_class($this))); + } + $this->$method($value); + } + } + + /** + * @deprecated since version 2.2, to be removed in 3.0. Use setPath instead. + */ + public function setPattern($pattern) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. Use the setPath() method instead and use the "path" option instead of the "pattern" option in the route definition.', E_USER_DEPRECATED); + + $this->path = $pattern; + } + + /** + * @deprecated since version 2.2, to be removed in 3.0. Use getPath instead. + */ + public function getPattern() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. Use the getPath() method instead and use the "path" option instead of the "pattern" option in the route definition.', E_USER_DEPRECATED); + + return $this->path; + } + + public function setPath($path) + { + $this->path = $path; + } + + public function getPath() + { + return $this->path; + } + + public function setHost($pattern) + { + $this->host = $pattern; + } + + public function getHost() + { + return $this->host; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getName() + { + return $this->name; + } + + public function setRequirements($requirements) + { + if (isset($requirements['_method'])) { + if (0 === \count($this->methods)) { + $this->methods = explode('|', $requirements['_method']); + } + + @trigger_error('The "_method" requirement is deprecated since Symfony 2.2 and will be removed in 3.0. Use the "methods" option instead.', E_USER_DEPRECATED); + } + + if (isset($requirements['_scheme'])) { + if (0 === \count($this->schemes)) { + $this->schemes = explode('|', $requirements['_scheme']); + } + + @trigger_error('The "_scheme" requirement is deprecated since Symfony 2.2 and will be removed in 3.0. Use the "schemes" option instead.', E_USER_DEPRECATED); + } + + $this->requirements = $requirements; + } + + public function getRequirements() + { + return $this->requirements; + } + + public function setOptions($options) + { + $this->options = $options; + } + + public function getOptions() + { + return $this->options; + } + + public function setDefaults($defaults) + { + $this->defaults = $defaults; + } + + public function getDefaults() + { + return $this->defaults; + } + + public function setSchemes($schemes) + { + $this->schemes = \is_array($schemes) ? $schemes : array($schemes); + } + + public function getSchemes() + { + return $this->schemes; + } + + public function setMethods($methods) + { + $this->methods = \is_array($methods) ? $methods : array($methods); + } + + public function getMethods() + { + return $this->methods; + } + + public function setCondition($condition) + { + $this->condition = $condition; + } + + public function getCondition() + { + return $this->condition; + } +} diff --git a/vendor/symfony/routing/CompiledRoute.php b/vendor/symfony/routing/CompiledRoute.php new file mode 100644 index 0000000..c95477a --- /dev/null +++ b/vendor/symfony/routing/CompiledRoute.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * CompiledRoutes are returned by the RouteCompiler class. + * + * @author Fabien Potencier + */ +class CompiledRoute implements \Serializable +{ + private $variables; + private $tokens; + private $staticPrefix; + private $regex; + private $pathVariables; + private $hostVariables; + private $hostRegex; + private $hostTokens; + + /** + * @param string $staticPrefix The static prefix of the compiled route + * @param string $regex The regular expression to use to match this route + * @param array $tokens An array of tokens to use to generate URL for this route + * @param array $pathVariables An array of path variables + * @param string|null $hostRegex Host regex + * @param array $hostTokens Host tokens + * @param array $hostVariables An array of host variables + * @param array $variables An array of variables (variables defined in the path and in the host patterns) + */ + public function __construct($staticPrefix, $regex, array $tokens, array $pathVariables, $hostRegex = null, array $hostTokens = array(), array $hostVariables = array(), array $variables = array()) + { + $this->staticPrefix = (string) $staticPrefix; + $this->regex = $regex; + $this->tokens = $tokens; + $this->pathVariables = $pathVariables; + $this->hostRegex = $hostRegex; + $this->hostTokens = $hostTokens; + $this->hostVariables = $hostVariables; + $this->variables = $variables; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'vars' => $this->variables, + 'path_prefix' => $this->staticPrefix, + 'path_regex' => $this->regex, + 'path_tokens' => $this->tokens, + 'path_vars' => $this->pathVariables, + 'host_regex' => $this->hostRegex, + 'host_tokens' => $this->hostTokens, + 'host_vars' => $this->hostVariables, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $data = unserialize($serialized); + $this->variables = $data['vars']; + $this->staticPrefix = $data['path_prefix']; + $this->regex = $data['path_regex']; + $this->tokens = $data['path_tokens']; + $this->pathVariables = $data['path_vars']; + $this->hostRegex = $data['host_regex']; + $this->hostTokens = $data['host_tokens']; + $this->hostVariables = $data['host_vars']; + } + + /** + * Returns the static prefix. + * + * @return string The static prefix + */ + public function getStaticPrefix() + { + return $this->staticPrefix; + } + + /** + * Returns the regex. + * + * @return string The regex + */ + public function getRegex() + { + return $this->regex; + } + + /** + * Returns the host regex. + * + * @return string|null The host regex or null + */ + public function getHostRegex() + { + return $this->hostRegex; + } + + /** + * Returns the tokens. + * + * @return array The tokens + */ + public function getTokens() + { + return $this->tokens; + } + + /** + * Returns the host tokens. + * + * @return array The tokens + */ + public function getHostTokens() + { + return $this->hostTokens; + } + + /** + * Returns the variables. + * + * @return array The variables + */ + public function getVariables() + { + return $this->variables; + } + + /** + * Returns the path variables. + * + * @return array The variables + */ + public function getPathVariables() + { + return $this->pathVariables; + } + + /** + * Returns the host variables. + * + * @return array The variables + */ + public function getHostVariables() + { + return $this->hostVariables; + } +} diff --git a/vendor/symfony/routing/Exception/ExceptionInterface.php b/vendor/symfony/routing/Exception/ExceptionInterface.php new file mode 100644 index 0000000..db76362 --- /dev/null +++ b/vendor/symfony/routing/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * ExceptionInterface. + * + * @author Alexandre Salomé + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/InvalidParameterException.php b/vendor/symfony/routing/Exception/InvalidParameterException.php new file mode 100644 index 0000000..94d841f --- /dev/null +++ b/vendor/symfony/routing/Exception/InvalidParameterException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a parameter is not valid. + * + * @author Alexandre Salomé + */ +class InvalidParameterException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/MethodNotAllowedException.php b/vendor/symfony/routing/Exception/MethodNotAllowedException.php new file mode 100644 index 0000000..712412f --- /dev/null +++ b/vendor/symfony/routing/Exception/MethodNotAllowedException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was found but the request method is not allowed. + * + * This exception should trigger an HTTP 405 response in your application code. + * + * @author Kris Wallsmith + */ +class MethodNotAllowedException extends \RuntimeException implements ExceptionInterface +{ + protected $allowedMethods = array(); + + public function __construct(array $allowedMethods, $message = null, $code = 0, \Exception $previous = null) + { + $this->allowedMethods = array_map('strtoupper', $allowedMethods); + + parent::__construct($message, $code, $previous); + } + + /** + * Gets the allowed HTTP methods. + * + * @return array + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } +} diff --git a/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php b/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php new file mode 100644 index 0000000..57f3a40 --- /dev/null +++ b/vendor/symfony/routing/Exception/MissingMandatoryParametersException.php @@ -0,0 +1,22 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route cannot be generated because of missing + * mandatory parameters. + * + * @author Alexandre Salomé + */ +class MissingMandatoryParametersException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/ResourceNotFoundException.php b/vendor/symfony/routing/Exception/ResourceNotFoundException.php new file mode 100644 index 0000000..ccbca15 --- /dev/null +++ b/vendor/symfony/routing/Exception/ResourceNotFoundException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * The resource was not found. + * + * This exception should trigger an HTTP 404 response in your application code. + * + * @author Kris Wallsmith + */ +class ResourceNotFoundException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Exception/RouteNotFoundException.php b/vendor/symfony/routing/Exception/RouteNotFoundException.php new file mode 100644 index 0000000..24ab0b4 --- /dev/null +++ b/vendor/symfony/routing/Exception/RouteNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Exception; + +/** + * Exception thrown when a route does not exist. + * + * @author Alexandre Salomé + */ +class RouteNotFoundException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php b/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php new file mode 100644 index 0000000..dc97b7e --- /dev/null +++ b/vendor/symfony/routing/Generator/ConfigurableRequirementsInterface.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +/** + * ConfigurableRequirementsInterface must be implemented by URL generators that + * can be configured whether an exception should be generated when the parameters + * do not match the requirements. It is also possible to disable the requirements + * check for URL generation completely. + * + * The possible configurations and use-cases: + * - setStrictRequirements(true): Throw an exception for mismatching requirements. This + * is mostly useful in development environment. + * - setStrictRequirements(false): Don't throw an exception but return null as URL for + * mismatching requirements and log the problem. Useful when you cannot control all + * params because they come from third party libs but don't want to have a 404 in + * production environment. It should log the mismatch so one can review it. + * - setStrictRequirements(null): Return the URL with the given parameters without + * checking the requirements at all. When generating a URL you should either trust + * your params or you validated them beforehand because otherwise it would break your + * link anyway. So in production environment you should know that params always pass + * the requirements. Thus this option allows to disable the check on URL generation for + * performance reasons (saving a preg_match for each requirement every time a URL is + * generated). + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface ConfigurableRequirementsInterface +{ + /** + * Enables or disables the exception on incorrect parameters. + * Passing null will deactivate the requirements check completely. + * + * @param bool|null $enabled + */ + public function setStrictRequirements($enabled); + + /** + * Returns whether to throw an exception on incorrect parameters. + * Null means the requirements check is deactivated completely. + * + * @return bool|null + */ + public function isStrictRequirements(); +} diff --git a/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php b/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php new file mode 100644 index 0000000..659c5ba --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/GeneratorDumper.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumper is the base class for all built-in generator dumpers. + * + * @author Fabien Potencier + */ +abstract class GeneratorDumper implements GeneratorDumperInterface +{ + private $routes; + + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php b/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php new file mode 100644 index 0000000..fed3472 --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/GeneratorDumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * GeneratorDumperInterface is the interface that all generator dumper classes must implement. + * + * @author Fabien Potencier + */ +interface GeneratorDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to generate a URL of such a route. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php b/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php new file mode 100644 index 0000000..60bdf1d --- /dev/null +++ b/vendor/symfony/routing/Generator/Dumper/PhpGeneratorDumper.php @@ -0,0 +1,118 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator\Dumper; + +/** + * PhpGeneratorDumper creates a PHP class able to generate URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class PhpGeneratorDumper extends GeneratorDumper +{ + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the generator class + */ + public function dump(array $options = array()) + { + $options = array_merge(array( + 'class' => 'ProjectUrlGenerator', + 'base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + ), $options); + + return <<context = \$context; + \$this->logger = \$logger; + if (null === self::\$declaredRoutes) { + self::\$declaredRoutes = {$this->generateDeclaredRoutes()}; + } + } + +{$this->generateGenerateMethod()} +} + +EOF; + } + + /** + * Generates PHP code representing an array of defined routes + * together with the routes properties (e.g. requirements). + * + * @return string PHP code + */ + private function generateDeclaredRoutes() + { + $routes = "array(\n"; + foreach ($this->getRoutes()->all() as $name => $route) { + $compiledRoute = $route->compile(); + + $properties = array(); + $properties[] = $compiledRoute->getVariables(); + $properties[] = $route->getDefaults(); + $properties[] = $route->getRequirements(); + $properties[] = $compiledRoute->getTokens(); + $properties[] = $compiledRoute->getHostTokens(); + $properties[] = $route->getSchemes(); + + $routes .= sprintf(" '%s' => %s,\n", $name, str_replace("\n", '', var_export($properties, true))); + } + $routes .= ' )'; + + return $routes; + } + + /** + * Generates PHP code representing the `generate` method that implements the UrlGeneratorInterface. + * + * @return string PHP code + */ + private function generateGenerateMethod() + { + return <<<'EOF' + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (!isset(self::$declaredRoutes[$name])) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + list($variables, $defaults, $requirements, $tokens, $hostTokens, $requiredSchemes) = self::$declaredRoutes[$name]; + + return $this->doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, $requiredSchemes); + } +EOF; + } +} diff --git a/vendor/symfony/routing/Generator/UrlGenerator.php b/vendor/symfony/routing/Generator/UrlGenerator.php new file mode 100644 index 0000000..a7ca382 --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGenerator.php @@ -0,0 +1,326 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; + +/** + * UrlGenerator can generate a URL or a path for any route in the RouteCollection + * based on the passed parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class UrlGenerator implements UrlGeneratorInterface, ConfigurableRequirementsInterface +{ + protected $routes; + protected $context; + + /** + * @var bool|null + */ + protected $strictRequirements = true; + + protected $logger; + + /** + * This array defines the characters (besides alphanumeric ones) that will not be percent-encoded in the path segment of the generated URL. + * + * PHP's rawurlencode() encodes all chars except "a-zA-Z0-9-._~" according to RFC 3986. But we want to allow some chars + * to be used in their literal form (reasons below). Other chars inside the path must of course be encoded, e.g. + * "?" and "#" (would be interpreted wrongly as query and fragment identifier), + * "'" and """ (are used as delimiters in HTML). + */ + protected $decodedChars = array( + // the slash can be used to designate a hierarchical structure and we want allow using it with this meaning + // some webservers don't allow the slash in encoded form in the path for security reasons anyway + // see http://stackoverflow.com/questions/4069002/http-400-if-2f-part-of-get-url-in-jboss + '%2F' => '/', + // the following chars are general delimiters in the URI specification but have only special meaning in the authority component + // so they can safely be used in the path in unencoded form + '%40' => '@', + '%3A' => ':', + // these chars are only sub-delimiters that have no predefined meaning and can therefore be used literally + // so URI producing applications can use these chars to delimit subcomponents in a path segment without being encoded for better readability + '%3B' => ';', + '%2C' => ',', + '%3D' => '=', + '%2B' => '+', + '%21' => '!', + '%2A' => '*', + '%7C' => '|', + ); + + public function __construct(RouteCollection $routes, RequestContext $context, LoggerInterface $logger = null) + { + $this->routes = $routes; + $this->context = $context; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function setStrictRequirements($enabled) + { + $this->strictRequirements = null === $enabled ? null : (bool) $enabled; + } + + /** + * {@inheritdoc} + */ + public function isStrictRequirements() + { + return $this->strictRequirements; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + if (null === $route = $this->routes->get($name)) { + throw new RouteNotFoundException(sprintf('Unable to generate a URL for the named route "%s" as such route does not exist.', $name)); + } + + // the Route has a cache of its own and is not recompiled as long as it does not get modified + $compiledRoute = $route->compile(); + + return $this->doGenerate($compiledRoute->getVariables(), $route->getDefaults(), $route->getRequirements(), $compiledRoute->getTokens(), $parameters, $name, $referenceType, $compiledRoute->getHostTokens(), $route->getSchemes()); + } + + /** + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + protected function doGenerate($variables, $defaults, $requirements, $tokens, $parameters, $name, $referenceType, $hostTokens, array $requiredSchemes = array()) + { + if (\is_bool($referenceType) || \is_string($referenceType)) { + @trigger_error('The hardcoded value you are using for the $referenceType argument of the '.__CLASS__.'::generate method is deprecated since Symfony 2.8 and will not be supported anymore in 3.0. Use the constants defined in the UrlGeneratorInterface instead.', E_USER_DEPRECATED); + + if (true === $referenceType) { + $referenceType = self::ABSOLUTE_URL; + } elseif (false === $referenceType) { + $referenceType = self::ABSOLUTE_PATH; + } elseif ('relative' === $referenceType) { + $referenceType = self::RELATIVE_PATH; + } elseif ('network' === $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + + $variables = array_flip($variables); + $mergedParams = array_replace($defaults, $this->context->getParameters(), $parameters); + + // all params must be given + if ($diff = array_diff_key($variables, $mergedParams)) { + throw new MissingMandatoryParametersException(sprintf('Some mandatory parameters are missing ("%s") to generate a URL for route "%s".', implode('", "', array_keys($diff)), $name)); + } + + $url = ''; + $optional = true; + foreach ($tokens as $token) { + if ('variable' === $token[0]) { + if (!$optional || !array_key_exists($token[3], $defaults) || null !== $mergedParams[$token[3]] && (string) $mergedParams[$token[3]] !== (string) $defaults[$token[3]]) { + // check requirement + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#', $mergedParams[$token[3]])) { + $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); + if ($this->strictRequirements) { + throw new InvalidParameterException($message); + } + + if ($this->logger) { + $this->logger->error($message); + } + + return; + } + + $url = $token[1].$mergedParams[$token[3]].$url; + $optional = false; + } + } else { + // static text + $url = $token[1].$url; + $optional = false; + } + } + + if ('' === $url) { + $url = '/'; + } + + // the contexts base URL is already encoded (see Symfony\Component\HttpFoundation\Request) + $url = strtr(rawurlencode($url), $this->decodedChars); + + // the path segments "." and ".." are interpreted as relative reference when resolving a URI; see http://tools.ietf.org/html/rfc3986#section-3.3 + // so we need to encode them as they are not used for this purpose here + // otherwise we would generate a URI that, when followed by a user agent (e.g. browser), does not match this route + $url = strtr($url, array('/../' => '/%2E%2E/', '/./' => '/%2E/')); + if ('/..' === substr($url, -3)) { + $url = substr($url, 0, -2).'%2E%2E'; + } elseif ('/.' === substr($url, -2)) { + $url = substr($url, 0, -1).'%2E'; + } + + $schemeAuthority = ''; + $host = $this->context->getHost(); + $scheme = $this->context->getScheme(); + + if ($requiredSchemes) { + if (!\in_array($scheme, $requiredSchemes, true)) { + $referenceType = self::ABSOLUTE_URL; + $scheme = current($requiredSchemes); + } + } elseif (isset($requirements['_scheme']) && ($req = strtolower($requirements['_scheme'])) && $scheme !== $req) { + // We do this for BC; to be removed if _scheme is not supported anymore + $referenceType = self::ABSOLUTE_URL; + $scheme = $req; + } + + if ($hostTokens) { + $routeHost = ''; + foreach ($hostTokens as $token) { + if ('variable' === $token[0]) { + if (null !== $this->strictRequirements && !preg_match('#^'.$token[2].'$#i', $mergedParams[$token[3]])) { + $message = sprintf('Parameter "%s" for route "%s" must match "%s" ("%s" given) to generate a corresponding URL.', $token[3], $name, $token[2], $mergedParams[$token[3]]); + + if ($this->strictRequirements) { + throw new InvalidParameterException($message); + } + + if ($this->logger) { + $this->logger->error($message); + } + + return; + } + + $routeHost = $token[1].$mergedParams[$token[3]].$routeHost; + } else { + $routeHost = $token[1].$routeHost; + } + } + + if ($routeHost !== $host) { + $host = $routeHost; + if (self::ABSOLUTE_URL !== $referenceType) { + $referenceType = self::NETWORK_PATH; + } + } + } + + if ((self::ABSOLUTE_URL === $referenceType || self::NETWORK_PATH === $referenceType) && !empty($host)) { + $port = ''; + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + $schemeAuthority = self::NETWORK_PATH === $referenceType ? '//' : "$scheme://"; + $schemeAuthority .= $host.$port; + } + + if (self::RELATIVE_PATH === $referenceType) { + $url = self::getRelativePath($this->context->getPathInfo(), $url); + } else { + $url = $schemeAuthority.$this->context->getBaseUrl().$url; + } + + // add a query string if needed + $extra = array_udiff_assoc(array_diff_key($parameters, $variables), $defaults, function ($a, $b) { + return $a == $b ? 0 : 1; + }); + + if ($extra && $query = http_build_query($extra, '', '&')) { + // "/" and "?" can be left decoded for better user experience, see + // http://tools.ietf.org/html/rfc3986#section-3.4 + $url .= '?'.strtr($query, array('%2F' => '/')); + } + + return $url; + } + + /** + * Returns the target path as relative reference from the base path. + * + * Only the URIs path component (no schema, host etc.) is relevant and must be given, starting with a slash. + * Both paths must be absolute and not contain relative parts. + * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives. + * Furthermore, they can be used to reduce the link size in documents. + * + * Example target paths, given a base path of "/a/b/c/d": + * - "/a/b/c/d" -> "" + * - "/a/b/c/" -> "./" + * - "/a/b/" -> "../" + * - "/a/b/c/other" -> "other" + * - "/a/x/y" -> "../../x/y" + * + * @param string $basePath The base path + * @param string $targetPath The target path + * + * @return string The relative target path + */ + public static function getRelativePath($basePath, $targetPath) + { + if ($basePath === $targetPath) { + return ''; + } + + $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath); + $targetDirs = explode('/', isset($targetPath[0]) && '/' === $targetPath[0] ? substr($targetPath, 1) : $targetPath); + array_pop($sourceDirs); + $targetFile = array_pop($targetDirs); + + foreach ($sourceDirs as $i => $dir) { + if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) { + unset($sourceDirs[$i], $targetDirs[$i]); + } else { + break; + } + } + + $targetDirs[] = $targetFile; + $path = str_repeat('../', \count($sourceDirs)).implode('/', $targetDirs); + + // A reference to the same base directory or an empty subdirectory must be prefixed with "./". + // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used + // as the first segment of a relative-path reference, as it would be mistaken for a scheme name + // (see http://tools.ietf.org/html/rfc3986#section-4.2). + return '' === $path || '/' === $path[0] + || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos) + ? "./$path" : $path; + } +} diff --git a/vendor/symfony/routing/Generator/UrlGeneratorInterface.php b/vendor/symfony/routing/Generator/UrlGeneratorInterface.php new file mode 100644 index 0000000..f501ebd --- /dev/null +++ b/vendor/symfony/routing/Generator/UrlGeneratorInterface.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Generator; + +use Symfony\Component\Routing\Exception\InvalidParameterException; +use Symfony\Component\Routing\Exception\MissingMandatoryParametersException; +use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * UrlGeneratorInterface is the interface that all URL generator classes must implement. + * + * The constants in this interface define the different types of resource references that + * are declared in RFC 3986: http://tools.ietf.org/html/rfc3986 + * We are using the term "URL" instead of "URI" as this is more common in web applications + * and we do not need to distinguish them as the difference is mostly semantical and + * less technical. Generating URIs, i.e. representation-independent resource identifiers, + * is also possible. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +interface UrlGeneratorInterface extends RequestContextAwareInterface +{ + /** + * Generates an absolute URL, e.g. "http://example.com/dir/file". + */ + const ABSOLUTE_URL = 0; + + /** + * Generates an absolute path, e.g. "/dir/file". + */ + const ABSOLUTE_PATH = 1; + + /** + * Generates a relative path based on the current request path, e.g. "../parent-file". + * + * @see UrlGenerator::getRelativePath() + */ + const RELATIVE_PATH = 2; + + /** + * Generates a network path, e.g. "//example.com/dir/file". + * Such reference reuses the current scheme but specifies the host. + */ + const NETWORK_PATH = 3; + + /** + * Generates a URL or path for a specific route based on the given parameters. + * + * Parameters that reference placeholders in the route pattern will substitute them in the + * path or host. Extra params are added as query string to the URL. + * + * When the passed reference type cannot be generated for the route because it requires a different + * host or scheme than the current one, the method will return a more comprehensive reference + * that includes the required params. For example, when you call this method with $referenceType = ABSOLUTE_PATH + * but the route requires the https scheme whereas the current scheme is http, it will instead return an + * ABSOLUTE_URL with the https scheme and the current host. This makes sure the generated URL matches + * the route in any case. + * + * If there is no route with the given name, the generator must throw the RouteNotFoundException. + * + * @param string $name The name of the route + * @param mixed $parameters An array of parameters + * @param int $referenceType The type of reference to be generated (one of the constants) + * + * @return string The generated URL + * + * @throws RouteNotFoundException If the named route doesn't exist + * @throws MissingMandatoryParametersException When some parameters are missing that are mandatory for the route + * @throws InvalidParameterException When a parameter value for a placeholder is not correct because + * it does not match the requirement + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH); +} diff --git a/vendor/symfony/routing/LICENSE b/vendor/symfony/routing/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/routing/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/routing/Loader/AnnotationClassLoader.php b/vendor/symfony/routing/Loader/AnnotationClassLoader.php new file mode 100644 index 0000000..6373a6a --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationClassLoader.php @@ -0,0 +1,260 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Doctrine\Common\Annotations\Reader; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Loader\LoaderResolverInterface; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * AnnotationClassLoader loads routing information from a PHP class and its methods. + * + * You need to define an implementation for the getRouteDefaults() method. Most of the + * time, this method should define some PHP callable to be called for the route + * (a controller in MVC speak). + * + * The @Route annotation can be set on the class (for global parameters), + * and on each method. + * + * The @Route annotation main value is the route path. The annotation also + * recognizes several parameters: requirements, options, defaults, schemes, + * methods, host, and name. The name parameter is mandatory. + * Here is an example of how you should be able to use it: + * + * /** + * * @Route("/Blog") + * * / + * class Blog + * { + * /** + * * @Route("/", name="blog_index") + * * / + * public function index() + * { + * } + * + * /** + * * @Route("/{id}", name="blog_post", requirements = {"id" = "\d+"}) + * * / + * public function show() + * { + * } + * } + * + * @author Fabien Potencier + */ +abstract class AnnotationClassLoader implements LoaderInterface +{ + protected $reader; + + /** + * @var string + */ + protected $routeAnnotationClass = 'Symfony\\Component\\Routing\\Annotation\\Route'; + + /** + * @var int + */ + protected $defaultRouteIndex = 0; + + public function __construct(Reader $reader) + { + $this->reader = $reader; + } + + /** + * Sets the annotation class to read route properties from. + * + * @param string $class A fully-qualified class name + */ + public function setRouteAnnotationClass($class) + { + $this->routeAnnotationClass = $class; + } + + /** + * Loads from annotations from a class. + * + * @param string $class A class name + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When route can't be parsed + */ + public function load($class, $type = null) + { + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + $class = new \ReflectionClass($class); + if ($class->isAbstract()) { + throw new \InvalidArgumentException(sprintf('Annotations from class "%s" cannot be read as it is abstract.', $class->getName())); + } + + $globals = $this->getGlobals($class); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($class->getFileName())); + + foreach ($class->getMethods() as $method) { + $this->defaultRouteIndex = 0; + foreach ($this->reader->getMethodAnnotations($method) as $annot) { + if ($annot instanceof $this->routeAnnotationClass) { + $this->addRoute($collection, $annot, $globals, $class, $method); + } + } + } + + return $collection; + } + + protected function addRoute(RouteCollection $collection, $annot, $globals, \ReflectionClass $class, \ReflectionMethod $method) + { + $name = $annot->getName(); + if (null === $name) { + $name = $this->getDefaultRouteName($class, $method); + } + + $defaults = array_replace($globals['defaults'], $annot->getDefaults()); + foreach ($method->getParameters() as $param) { + if (false !== strpos($globals['path'].$annot->getPath(), sprintf('{%s}', $param->getName())) && !isset($defaults[$param->getName()]) && $param->isDefaultValueAvailable()) { + $defaults[$param->getName()] = $param->getDefaultValue(); + } + } + $requirements = array_replace($globals['requirements'], $annot->getRequirements()); + $options = array_replace($globals['options'], $annot->getOptions()); + $schemes = array_merge($globals['schemes'], $annot->getSchemes()); + $methods = array_merge($globals['methods'], $annot->getMethods()); + + $host = $annot->getHost(); + if (null === $host) { + $host = $globals['host']; + } + + $condition = $annot->getCondition(); + if (null === $condition) { + $condition = $globals['condition']; + } + + $route = $this->createRoute($globals['path'].$annot->getPath(), $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + + $this->configureRoute($route, $class, $method, $annot); + + $collection->add($name, $route); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && preg_match('/^(?:\\\\?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)+$/', $resource) && (!$type || 'annotation' === $type); + } + + /** + * {@inheritdoc} + */ + public function setResolver(LoaderResolverInterface $resolver) + { + } + + /** + * {@inheritdoc} + */ + public function getResolver() + { + } + + /** + * Gets the default route name for a class method. + * + * @param \ReflectionClass $class + * @param \ReflectionMethod $method + * + * @return string + */ + protected function getDefaultRouteName(\ReflectionClass $class, \ReflectionMethod $method) + { + $name = strtolower(str_replace('\\', '_', $class->name).'_'.$method->name); + if ($this->defaultRouteIndex > 0) { + $name .= '_'.$this->defaultRouteIndex; + } + ++$this->defaultRouteIndex; + + return $name; + } + + protected function getGlobals(\ReflectionClass $class) + { + $globals = array( + 'path' => '', + 'requirements' => array(), + 'options' => array(), + 'defaults' => array(), + 'schemes' => array(), + 'methods' => array(), + 'host' => '', + 'condition' => '', + ); + + if ($annot = $this->reader->getClassAnnotation($class, $this->routeAnnotationClass)) { + // for BC reasons + if (null !== $annot->getPath()) { + $globals['path'] = $annot->getPath(); + } elseif (null !== $annot->getPattern()) { + $globals['path'] = $annot->getPattern(); + } + + if (null !== $annot->getRequirements()) { + $globals['requirements'] = $annot->getRequirements(); + } + + if (null !== $annot->getOptions()) { + $globals['options'] = $annot->getOptions(); + } + + if (null !== $annot->getDefaults()) { + $globals['defaults'] = $annot->getDefaults(); + } + + if (null !== $annot->getSchemes()) { + $globals['schemes'] = $annot->getSchemes(); + } + + if (null !== $annot->getMethods()) { + $globals['methods'] = $annot->getMethods(); + } + + if (null !== $annot->getHost()) { + $globals['host'] = $annot->getHost(); + } + + if (null !== $annot->getCondition()) { + $globals['condition'] = $annot->getCondition(); + } + } + + return $globals; + } + + protected function createRoute($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition) + { + return new Route($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + } + + abstract protected function configureRoute(Route $route, \ReflectionClass $class, \ReflectionMethod $method, $annot); +} diff --git a/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php b/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php new file mode 100644 index 0000000..e61693e --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationDirectoryLoader.php @@ -0,0 +1,120 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * AnnotationDirectoryLoader loads routing information from annotations set + * on PHP classes and methods. + * + * @author Fabien Potencier + */ +class AnnotationDirectoryLoader extends AnnotationFileLoader +{ + /** + * Loads from annotations from a directory. + * + * @param string $path A directory path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the directory does not exist or its routes cannot be parsed + */ + public function load($path, $type = null) + { + $dir = $this->locator->locate($path); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($dir, '/\.php$/')); + $files = iterator_to_array(new \RecursiveIteratorIterator( + new RecursiveCallbackFilterIterator( + new \RecursiveDirectoryIterator($dir), + function (\SplFileInfo $current) { + return '.' !== substr($current->getBasename(), 0, 1); + } + ), + \RecursiveIteratorIterator::LEAVES_ONLY + )); + usort($files, function (\SplFileInfo $a, \SplFileInfo $b) { + return (string) $a > (string) $b ? 1 : -1; + }); + + foreach ($files as $file) { + if (!$file->isFile() || '.php' !== substr($file->getFilename(), -4)) { + continue; + } + + if ($class = $this->findClass($file)) { + $refl = new \ReflectionClass($class); + if ($refl->isAbstract()) { + continue; + } + + $collection->addCollection($this->loader->load($class, $type)); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + if (!\is_string($resource)) { + return false; + } + + try { + $path = $this->locator->locate($resource); + } catch (\Exception $e) { + return false; + } + + return is_dir($path) && (!$type || 'annotation' === $type); + } +} + +/** + * @internal To be removed as RecursiveCallbackFilterIterator is available since PHP 5.4 + */ +class RecursiveCallbackFilterIterator extends \FilterIterator implements \RecursiveIterator +{ + private $iterator; + private $callback; + + public function __construct(\RecursiveIterator $iterator, $callback) + { + $this->iterator = $iterator; + $this->callback = $callback; + parent::__construct($iterator); + } + + public function accept() + { + return \call_user_func($this->callback, $this->current(), $this->key(), $this->iterator); + } + + public function hasChildren() + { + return $this->iterator->hasChildren(); + } + + public function getChildren() + { + return new static($this->iterator->getChildren(), $this->callback); + } +} diff --git a/vendor/symfony/routing/Loader/AnnotationFileLoader.php b/vendor/symfony/routing/Loader/AnnotationFileLoader.php new file mode 100644 index 0000000..7bb1112 --- /dev/null +++ b/vendor/symfony/routing/Loader/AnnotationFileLoader.php @@ -0,0 +1,137 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\FileLocatorInterface; +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * AnnotationFileLoader loads routing information from annotations set + * on a PHP class and its methods. + * + * @author Fabien Potencier + */ +class AnnotationFileLoader extends FileLoader +{ + protected $loader; + + /** + * @throws \RuntimeException + */ + public function __construct(FileLocatorInterface $locator, AnnotationClassLoader $loader) + { + if (!\function_exists('token_get_all')) { + throw new \RuntimeException('The Tokenizer extension is required for the routing annotation loaders.'); + } + + parent::__construct($locator); + + $this->loader = $loader; + } + + /** + * Loads from annotations from a file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When the file does not exist or its routes cannot be parsed + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + if ($class = $this->findClass($path)) { + $collection->addResource(new FileResource($path)); + $collection->addCollection($this->loader->load($class, $type)); + } + if (\PHP_VERSION_ID >= 70000) { + // PHP 7 memory manager will not release after token_get_all(), see https://bugs.php.net/70098 + gc_mem_caches(); + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'annotation' === $type); + } + + /** + * Returns the full class name for the first class in the file. + * + * @param string $file A PHP file path + * + * @return string|false Full class name if found, false otherwise + */ + protected function findClass($file) + { + $class = false; + $namespace = false; + $tokens = token_get_all(file_get_contents($file)); + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + + if (!isset($token[1])) { + continue; + } + + if (true === $class && T_STRING === $token[0]) { + return $namespace.'\\'.$token[1]; + } + + if (true === $namespace && T_STRING === $token[0]) { + $namespace = $token[1]; + while (isset($tokens[++$i][1]) && \in_array($tokens[$i][0], array(T_NS_SEPARATOR, T_STRING))) { + $namespace .= $tokens[$i][1]; + } + $token = $tokens[$i]; + } + + if (T_CLASS === $token[0]) { + // Skip usage of ::class constant and anonymous classes + $skipClassToken = false; + for ($j = $i - 1; $j > 0; --$j) { + if (!isset($tokens[$j][1])) { + break; + } + + if (T_DOUBLE_COLON === $tokens[$j][0] || T_NEW === $tokens[$j][0]) { + $skipClassToken = true; + break; + } elseif (!\in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) { + break; + } + } + + if (!$skipClassToken) { + $class = true; + } + } + + if (T_NAMESPACE === $token[0]) { + $namespace = true; + } + } + + return false; + } +} diff --git a/vendor/symfony/routing/Loader/ClosureLoader.php b/vendor/symfony/routing/Loader/ClosureLoader.php new file mode 100644 index 0000000..5df9f6a --- /dev/null +++ b/vendor/symfony/routing/Loader/ClosureLoader.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Routing\RouteCollection; + +/** + * ClosureLoader loads routes from a PHP closure. + * + * The Closure must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class ClosureLoader extends Loader +{ + /** + * Loads a Closure. + * + * @param \Closure $closure A Closure + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + */ + public function load($closure, $type = null) + { + return $closure(); + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return $resource instanceof \Closure && (!$type || 'closure' === $type); + } +} diff --git a/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php b/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php new file mode 100644 index 0000000..6938257 --- /dev/null +++ b/vendor/symfony/routing/Loader/DependencyInjection/ServiceRouterLoader.php @@ -0,0 +1,40 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader\DependencyInjection; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Routing\Loader\ObjectRouteLoader; + +/** + * A route loader that executes a service to load the routes. + * + * This depends on the DependencyInjection component. + * + * @author Ryan Weaver + */ +class ServiceRouterLoader extends ObjectRouteLoader +{ + /** + * @var ContainerInterface + */ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + protected function getServiceObject($id) + { + return $this->container->get($id); + } +} diff --git a/vendor/symfony/routing/Loader/DirectoryLoader.php b/vendor/symfony/routing/Loader/DirectoryLoader.php new file mode 100644 index 0000000..08e833e --- /dev/null +++ b/vendor/symfony/routing/Loader/DirectoryLoader.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\DirectoryResource; +use Symfony\Component\Routing\RouteCollection; + +class DirectoryLoader extends FileLoader +{ + /** + * {@inheritdoc} + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $collection = new RouteCollection(); + $collection->addResource(new DirectoryResource($path)); + + foreach (scandir($path) as $dir) { + if ('.' !== $dir[0]) { + $this->setCurrentDir($path); + $subPath = $path.'/'.$dir; + $subType = null; + + if (is_dir($subPath)) { + $subPath .= '/'; + $subType = 'directory'; + } + + $subCollection = $this->import($subPath, $subType, false, $path); + $collection->addCollection($subCollection); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + // only when type is forced to directory, not to conflict with AnnotationLoader + + return 'directory' === $type; + } +} diff --git a/vendor/symfony/routing/Loader/ObjectRouteLoader.php b/vendor/symfony/routing/Loader/ObjectRouteLoader.php new file mode 100644 index 0000000..12fc2bb --- /dev/null +++ b/vendor/symfony/routing/Loader/ObjectRouteLoader.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\Loader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * A route loader that calls a method on an object to load the routes. + * + * @author Ryan Weaver + */ +abstract class ObjectRouteLoader extends Loader +{ + /** + * Returns the object that the method will be called on to load routes. + * + * For example, if your application uses a service container, + * the $id may be a service id. + * + * @param string $id + * + * @return object + */ + abstract protected function getServiceObject($id); + + /** + * Calls the service that will load the routes. + * + * @param mixed $resource Some value that will resolve to a callable + * @param string|null $type The resource type + * + * @return RouteCollection + */ + public function load($resource, $type = null) + { + $parts = explode(':', $resource); + if (2 != \count($parts)) { + throw new \InvalidArgumentException(sprintf('Invalid resource "%s" passed to the "service" route loader: use the format "service_name:methodName"', $resource)); + } + + $serviceString = $parts[0]; + $method = $parts[1]; + + $loaderObject = $this->getServiceObject($serviceString); + + if (!\is_object($loaderObject)) { + throw new \LogicException(sprintf('%s:getServiceObject() must return an object: %s returned', \get_class($this), \gettype($loaderObject))); + } + + if (!method_exists($loaderObject, $method)) { + throw new \BadMethodCallException(sprintf('Method "%s" not found on "%s" when importing routing resource "%s"', $method, \get_class($loaderObject), $resource)); + } + + $routeCollection = \call_user_func(array($loaderObject, $method), $this); + + if (!$routeCollection instanceof RouteCollection) { + $type = \is_object($routeCollection) ? \get_class($routeCollection) : \gettype($routeCollection); + + throw new \LogicException(sprintf('The %s::%s method must return a RouteCollection: %s returned', \get_class($loaderObject), $method, $type)); + } + + // make the service file tracked so that if it changes, the cache rebuilds + $this->addClassResource(new \ReflectionClass($loaderObject), $routeCollection); + + return $routeCollection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return 'service' === $type; + } + + private function addClassResource(\ReflectionClass $class, RouteCollection $collection) + { + do { + if (is_file($class->getFileName())) { + $collection->addResource(new FileResource($class->getFileName())); + } + } while ($class = $class->getParentClass()); + } +} diff --git a/vendor/symfony/routing/Loader/PhpFileLoader.php b/vendor/symfony/routing/Loader/PhpFileLoader.php new file mode 100644 index 0000000..3892511 --- /dev/null +++ b/vendor/symfony/routing/Loader/PhpFileLoader.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\RouteCollection; + +/** + * PhpFileLoader loads routes from a PHP file. + * + * The file must return a RouteCollection instance. + * + * @author Fabien Potencier + */ +class PhpFileLoader extends FileLoader +{ + /** + * Loads a PHP file. + * + * @param string $file A PHP file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + $this->setCurrentDir(\dirname($path)); + + $collection = self::includeFile($path, $this); + $collection->addResource(new FileResource($path)); + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && 'php' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'php' === $type); + } + + /** + * Safe include. Used for scope isolation. + * + * @param string $file File to include + * @param PhpFileLoader $loader The loader variable is exposed to the included file below + * + * @return RouteCollection + */ + private static function includeFile($file, PhpFileLoader $loader) + { + return include $file; + } +} diff --git a/vendor/symfony/routing/Loader/XmlFileLoader.php b/vendor/symfony/routing/Loader/XmlFileLoader.php new file mode 100644 index 0000000..92764f4 --- /dev/null +++ b/vendor/symfony/routing/Loader/XmlFileLoader.php @@ -0,0 +1,270 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Config\Util\XmlUtils; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * XmlFileLoader loads XML routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class XmlFileLoader extends FileLoader +{ + const NAMESPACE_URI = 'http://symfony.com/schema/routing'; + const SCHEME_PATH = '/schema/routing/routing-1.0.xsd'; + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException when the file cannot be loaded or when the XML cannot be + * parsed because it does not validate against the scheme + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + $xml = $this->loadFile($path); + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // process routes and imports + foreach ($xml->documentElement->childNodes as $node) { + if (!$node instanceof \DOMElement) { + continue; + } + + $this->parseNode($collection, $node, $path, $file); + } + + return $collection; + } + + /** + * Parses a node from a loaded XML file. + * + * @param RouteCollection $collection Collection to associate with the node + * @param \DOMElement $node Element to parse + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseNode(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if (self::NAMESPACE_URI !== $node->namespaceURI) { + return; + } + + switch ($node->localName) { + case 'route': + $this->parseRoute($collection, $node, $path); + break; + case 'import': + $this->parseImport($collection, $node, $path, $file); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "route" or "import".', $node->localName, $path)); + } + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && 'xml' === pathinfo($resource, PATHINFO_EXTENSION) && (!$type || 'xml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseRoute(RouteCollection $collection, \DOMElement $node, $path) + { + if ('' === ($id = $node->getAttribute('id')) || (!$node->hasAttribute('pattern') && !$node->hasAttribute('path'))) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have an "id" and a "path" attribute.', $path)); + } + + if ($node->hasAttribute('pattern')) { + if ($node->hasAttribute('path')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" cannot define both a "path" and a "pattern" attribute. Use only "path".', $path)); + } + + @trigger_error(sprintf('The "pattern" option in file "%s" is deprecated since Symfony 2.2 and will be removed in 3.0. Use the "path" option in the route definition instead.', $path), E_USER_DEPRECATED); + + $node->setAttribute('path', $node->getAttribute('pattern')); + $node->removeAttribute('pattern'); + } + + $schemes = preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY); + $methods = preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY); + + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + + if (isset($requirements['_method'])) { + if (0 === \count($methods)) { + $methods = explode('|', $requirements['_method']); + } + + unset($requirements['_method']); + @trigger_error(sprintf('The "_method" requirement of route "%s" in file "%s" is deprecated since Symfony 2.2 and will be removed in 3.0. Use the "methods" attribute instead.', $id, $path), E_USER_DEPRECATED); + } + + if (isset($requirements['_scheme'])) { + if (0 === \count($schemes)) { + $schemes = explode('|', $requirements['_scheme']); + } + + unset($requirements['_scheme']); + @trigger_error(sprintf('The "_scheme" requirement of route "%s" in file "%s" is deprecated since Symfony 2.2 and will be removed in 3.0. Use the "schemes" attribute instead.', $id, $path), E_USER_DEPRECATED); + } + + $route = new Route($node->getAttribute('path'), $defaults, $requirements, $options, $node->getAttribute('host'), $schemes, $methods, $condition); + $collection->add($id, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection RouteCollection instance + * @param \DOMElement $node Element to parse that represents a Route + * @param string $path Full path of the XML file being processed + * @param string $file Loaded file name + * + * @throws \InvalidArgumentException When the XML is invalid + */ + protected function parseImport(RouteCollection $collection, \DOMElement $node, $path, $file) + { + if ('' === $resource = $node->getAttribute('resource')) { + throw new \InvalidArgumentException(sprintf('The element in file "%s" must have a "resource" attribute.', $path)); + } + + $type = $node->getAttribute('type'); + $prefix = $node->getAttribute('prefix'); + $host = $node->hasAttribute('host') ? $node->getAttribute('host') : null; + $schemes = $node->hasAttribute('schemes') ? preg_split('/[\s,\|]++/', $node->getAttribute('schemes'), -1, PREG_SPLIT_NO_EMPTY) : null; + $methods = $node->hasAttribute('methods') ? preg_split('/[\s,\|]++/', $node->getAttribute('methods'), -1, PREG_SPLIT_NO_EMPTY) : null; + + list($defaults, $requirements, $options, $condition) = $this->parseConfigs($node, $path); + + $this->setCurrentDir(\dirname($path)); + + $subCollection = $this->import($resource, ('' !== $type ? $type : null), false, $file); + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + + /** + * Loads an XML file. + * + * @param string $file An XML file path + * + * @return \DOMDocument + * + * @throws \InvalidArgumentException When loading of XML file fails because of syntax errors + * or when the XML structure is not as expected by the scheme - + * see validate() + */ + protected function loadFile($file) + { + return XmlUtils::loadFile($file, __DIR__.static::SCHEME_PATH); + } + + /** + * Parses the config elements (default, requirement, option). + * + * @param \DOMElement $node Element to parse that contains the configs + * @param string $path Full path of the XML file being processed + * + * @return array An array with the defaults as first item, requirements as second and options as third + * + * @throws \InvalidArgumentException When the XML is invalid + */ + private function parseConfigs(\DOMElement $node, $path) + { + $defaults = array(); + $requirements = array(); + $options = array(); + $condition = null; + + foreach ($node->getElementsByTagNameNS(self::NAMESPACE_URI, '*') as $n) { + switch ($n->localName) { + case 'default': + if ($this->isElementValueNull($n)) { + $defaults[$n->getAttribute('key')] = null; + } else { + $defaults[$n->getAttribute('key')] = trim($n->textContent); + } + + break; + case 'requirement': + $requirements[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'option': + $options[$n->getAttribute('key')] = trim($n->textContent); + break; + case 'condition': + $condition = trim($n->textContent); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown tag "%s" used in file "%s". Expected "default", "requirement", "option" or "condition".', $n->localName, $path)); + } + } + + return array($defaults, $requirements, $options, $condition); + } + + private function isElementValueNull(\DOMElement $element) + { + $namespaceUri = 'http://www.w3.org/2001/XMLSchema-instance'; + + if (!$element->hasAttributeNS($namespaceUri, 'nil')) { + return false; + } + + return 'true' === $element->getAttributeNS($namespaceUri, 'nil') || '1' === $element->getAttributeNS($namespaceUri, 'nil'); + } +} diff --git a/vendor/symfony/routing/Loader/YamlFileLoader.php b/vendor/symfony/routing/Loader/YamlFileLoader.php new file mode 100644 index 0000000..47fc8cf --- /dev/null +++ b/vendor/symfony/routing/Loader/YamlFileLoader.php @@ -0,0 +1,224 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Loader; + +use Symfony\Component\Config\Loader\FileLoader; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; +use Symfony\Component\Yaml\Exception\ParseException; +use Symfony\Component\Yaml\Parser as YamlParser; + +/** + * YamlFileLoader loads Yaml routing files. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class YamlFileLoader extends FileLoader +{ + private static $availableKeys = array( + 'resource', 'type', 'prefix', 'pattern', 'path', 'host', 'schemes', 'methods', 'defaults', 'requirements', 'options', 'condition', + ); + private $yamlParser; + + /** + * Loads a Yaml file. + * + * @param string $file A Yaml file path + * @param string|null $type The resource type + * + * @return RouteCollection A RouteCollection instance + * + * @throws \InvalidArgumentException When a route can't be parsed because YAML is invalid + */ + public function load($file, $type = null) + { + $path = $this->locator->locate($file); + + if (!stream_is_local($path)) { + throw new \InvalidArgumentException(sprintf('This is not a local file "%s".', $path)); + } + + if (!file_exists($path)) { + throw new \InvalidArgumentException(sprintf('File "%s" not found.', $path)); + } + + if (null === $this->yamlParser) { + $this->yamlParser = new YamlParser(); + } + + try { + $parsedConfig = $this->yamlParser->parse(file_get_contents($path)); + } catch (ParseException $e) { + throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e); + } + + $collection = new RouteCollection(); + $collection->addResource(new FileResource($path)); + + // empty file + if (null === $parsedConfig) { + return $collection; + } + + // not an array + if (!\is_array($parsedConfig)) { + throw new \InvalidArgumentException(sprintf('The file "%s" must contain a YAML array.', $path)); + } + + foreach ($parsedConfig as $name => $config) { + if (isset($config['pattern'])) { + if (isset($config['path'])) { + throw new \InvalidArgumentException(sprintf('The file "%s" cannot define both a "path" and a "pattern" attribute. Use only "path".', $path)); + } + + @trigger_error(sprintf('The "pattern" option in file "%s" is deprecated since Symfony 2.2 and will be removed in 3.0. Use the "path" option in the route definition instead.', $path), E_USER_DEPRECATED); + + $config['path'] = $config['pattern']; + unset($config['pattern']); + } + + $this->validate($config, $name, $path); + + if (isset($config['resource'])) { + $this->parseImport($collection, $config, $path, $file); + } else { + $this->parseRoute($collection, $name, $config, $path); + } + } + + return $collection; + } + + /** + * {@inheritdoc} + */ + public function supports($resource, $type = null) + { + return \is_string($resource) && \in_array(pathinfo($resource, PATHINFO_EXTENSION), array('yml', 'yaml'), true) && (!$type || 'yaml' === $type); + } + + /** + * Parses a route and adds it to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param string $name Route name + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + */ + protected function parseRoute(RouteCollection $collection, $name, array $config, $path) + { + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : ''; + $schemes = isset($config['schemes']) ? $config['schemes'] : array(); + $methods = isset($config['methods']) ? $config['methods'] : array(); + $condition = isset($config['condition']) ? $config['condition'] : null; + + if (isset($requirements['_method'])) { + if (0 === \count($methods)) { + $methods = explode('|', $requirements['_method']); + } + + unset($requirements['_method']); + @trigger_error(sprintf('The "_method" requirement of route "%s" in file "%s" is deprecated since Symfony 2.2 and will be removed in 3.0. Use the "methods" option instead.', $name, $path), E_USER_DEPRECATED); + } + + if (isset($requirements['_scheme'])) { + if (0 === \count($schemes)) { + $schemes = explode('|', $requirements['_scheme']); + } + + unset($requirements['_scheme']); + @trigger_error(sprintf('The "_scheme" requirement of route "%s" in file "%s" is deprecated since Symfony 2.2 and will be removed in 3.0. Use the "schemes" option instead.', $name, $path), E_USER_DEPRECATED); + } + + $route = new Route($config['path'], $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + + $collection->add($name, $route); + } + + /** + * Parses an import and adds the routes in the resource to the RouteCollection. + * + * @param RouteCollection $collection A RouteCollection instance + * @param array $config Route definition + * @param string $path Full path of the YAML file being processed + * @param string $file Loaded file name + */ + protected function parseImport(RouteCollection $collection, array $config, $path, $file) + { + $type = isset($config['type']) ? $config['type'] : null; + $prefix = isset($config['prefix']) ? $config['prefix'] : ''; + $defaults = isset($config['defaults']) ? $config['defaults'] : array(); + $requirements = isset($config['requirements']) ? $config['requirements'] : array(); + $options = isset($config['options']) ? $config['options'] : array(); + $host = isset($config['host']) ? $config['host'] : null; + $condition = isset($config['condition']) ? $config['condition'] : null; + $schemes = isset($config['schemes']) ? $config['schemes'] : null; + $methods = isset($config['methods']) ? $config['methods'] : null; + + $this->setCurrentDir(\dirname($path)); + + $subCollection = $this->import($config['resource'], $type, false, $file); + /* @var $subCollection RouteCollection */ + $subCollection->addPrefix($prefix); + if (null !== $host) { + $subCollection->setHost($host); + } + if (null !== $condition) { + $subCollection->setCondition($condition); + } + if (null !== $schemes) { + $subCollection->setSchemes($schemes); + } + if (null !== $methods) { + $subCollection->setMethods($methods); + } + $subCollection->addDefaults($defaults); + $subCollection->addRequirements($requirements); + $subCollection->addOptions($options); + + $collection->addCollection($subCollection); + } + + /** + * Validates the route configuration. + * + * @param array $config A resource config + * @param string $name The config key + * @param string $path The loaded file path + * + * @throws \InvalidArgumentException If one of the provided config keys is not supported, + * something is missing or the combination is nonsense + */ + protected function validate($config, $name, $path) + { + if (!\is_array($config)) { + throw new \InvalidArgumentException(sprintf('The definition of "%s" in "%s" must be a YAML array.', $name, $path)); + } + if ($extraKeys = array_diff(array_keys($config), self::$availableKeys)) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" contains unsupported keys for "%s": "%s". Expected one of: "%s".', $path, $name, implode('", "', $extraKeys), implode('", "', self::$availableKeys))); + } + if (isset($config['resource']) && isset($config['path'])) { + throw new \InvalidArgumentException(sprintf('The routing file "%s" must not specify both the "resource" key and the "path" key for "%s". Choose between an import and a route definition.', $path, $name)); + } + if (!isset($config['resource']) && isset($config['type'])) { + throw new \InvalidArgumentException(sprintf('The "type" key for the route definition "%s" in "%s" is unsupported. It is only available for imports in combination with the "resource" key.', $name, $path)); + } + if (!isset($config['resource']) && !isset($config['path'])) { + throw new \InvalidArgumentException(sprintf('You must define a "path" for the route "%s" in file "%s".', $name, $path)); + } + } +} diff --git a/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd new file mode 100644 index 0000000..d40aa42 --- /dev/null +++ b/vendor/symfony/routing/Loader/schema/routing/routing-1.0.xsd @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vendor/symfony/routing/Matcher/ApacheUrlMatcher.php b/vendor/symfony/routing/Matcher/ApacheUrlMatcher.php new file mode 100644 index 0000000..1f17cee --- /dev/null +++ b/vendor/symfony/routing/Matcher/ApacheUrlMatcher.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +@trigger_error('The '.__NAMESPACE__.'\ApacheUrlMatcher class is deprecated since Symfony 2.5 and will be removed in 3.0. It\'s hard to replicate the behaviour of the PHP implementation and the performance gains are minimal.', E_USER_DEPRECATED); + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; + +/** + * ApacheUrlMatcher matches URL based on Apache mod_rewrite matching (see ApacheMatcherDumper). + * + * @deprecated since version 2.5, to be removed in 3.0. + * The performance gains are minimal and it's very hard to replicate + * the behavior of PHP implementation. + * + * @author Fabien Potencier + * @author Arnaud Le Blanc + */ +class ApacheUrlMatcher extends UrlMatcher +{ + /** + * Tries to match a URL based on Apache mod_rewrite matching. + * + * Returns false if no route matches the URL. + * + * @param string $pathinfo The pathinfo to be parsed + * + * @return array An array of parameters + * + * @throws MethodNotAllowedException If the current method is not allowed + */ + public function match($pathinfo) + { + $parameters = array(); + $defaults = array(); + $allow = array(); + $route = null; + + foreach ($this->denormalizeValues($_SERVER) as $key => $value) { + $name = $key; + + // skip non-routing variables + // this improves performance when $_SERVER contains many usual + // variables like HTTP_*, DOCUMENT_ROOT, REQUEST_URI, ... + if (false === strpos($name, '_ROUTING_')) { + continue; + } + + while (0 === strpos($name, 'REDIRECT_')) { + $name = substr($name, 9); + } + + // expect _ROUTING__ + // or _ROUTING_ + + if (0 !== strpos($name, '_ROUTING_')) { + continue; + } + if (false !== $pos = strpos($name, '_', 9)) { + $type = substr($name, 9, $pos - 9); + $name = substr($name, $pos + 1); + } else { + $type = substr($name, 9); + } + + if ('param' === $type) { + if ('' !== $value) { + $parameters[$name] = $value; + } + } elseif ('default' === $type) { + $defaults[$name] = $value; + } elseif ('route' === $type) { + $route = $value; + } elseif ('allow' === $type) { + $allow[] = $name; + } + + unset($_SERVER[$key]); + } + + if (null !== $route) { + $parameters['_route'] = $route; + + return $this->mergeDefaults($parameters, $defaults); + } elseif (0 < \count($allow)) { + throw new MethodNotAllowedException($allow); + } else { + return parent::match($pathinfo); + } + } + + /** + * Denormalizes an array of values. + * + * @param string[] $values + * + * @return array + */ + private function denormalizeValues(array $values) + { + $normalizedValues = array(); + foreach ($values as $key => $value) { + if (preg_match('~^(.*)\[(\d+)\]$~', $key, $matches)) { + if (!isset($normalizedValues[$matches[1]])) { + $normalizedValues[$matches[1]] = array(); + } + $normalizedValues[$matches[1]][(int) $matches[2]] = $value; + } else { + $normalizedValues[$key] = $value; + } + } + + return $normalizedValues; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/ApacheMatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/ApacheMatcherDumper.php new file mode 100644 index 0000000..6027069 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/ApacheMatcherDumper.php @@ -0,0 +1,272 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +@trigger_error('The '.__NAMESPACE__.'\ApacheMatcherDumper class is deprecated since Symfony 2.5 and will be removed in 3.0. It\'s hard to replicate the behaviour of the PHP implementation and the performance gains are minimal.', E_USER_DEPRECATED); + +use Symfony\Component\Routing\Route; + +/** + * Dumps a set of Apache mod_rewrite rules. + * + * @deprecated since version 2.5, to be removed in 3.0. + * The performance gains are minimal and it's very hard to replicate + * the behavior of PHP implementation. + * + * @author Fabien Potencier + * @author Kris Wallsmith + */ +class ApacheMatcherDumper extends MatcherDumper +{ + /** + * Dumps a set of Apache mod_rewrite rules. + * + * Available options: + * + * * script_name: The script name (app.php by default) + * * base_uri: The base URI ("" by default) + * + * @return string A string to be used as Apache rewrite rules + * + * @throws \LogicException When the route regex is invalid + */ + public function dump(array $options = array()) + { + $options = array_merge(array( + 'script_name' => 'app.php', + 'base_uri' => '', + ), $options); + + $options['script_name'] = self::escape($options['script_name'], ' ', '\\'); + + $rules = array("# skip \"real\" requests\nRewriteCond %{REQUEST_FILENAME} -f\nRewriteRule .* - [QSA,L]"); + $methodVars = array(); + $hostRegexUnique = 0; + $prevHostRegex = ''; + + foreach ($this->getRoutes()->all() as $name => $route) { + if ($route->getCondition()) { + throw new \LogicException(sprintf('Unable to dump the routes for Apache as route "%s" has a condition.', $name)); + } + + $compiledRoute = $route->compile(); + $hostRegex = $compiledRoute->getHostRegex(); + + if (null !== $hostRegex && $prevHostRegex !== $hostRegex) { + $prevHostRegex = $hostRegex; + ++$hostRegexUnique; + + $rule = array(); + + $regex = $this->regexToApacheRegex($hostRegex); + $regex = self::escape($regex, ' ', '\\'); + + $rule[] = sprintf('RewriteCond %%{HTTP:Host} %s', $regex); + + $variables = array(); + $variables[] = sprintf('E=__ROUTING_host_%s:1', $hostRegexUnique); + + foreach ($compiledRoute->getHostVariables() as $i => $variable) { + $variables[] = sprintf('E=__ROUTING_host_%s_%s:%%%d', $hostRegexUnique, $variable, $i + 1); + } + + $variables = implode(',', $variables); + + $rule[] = sprintf('RewriteRule .? - [%s]', $variables); + + $rules[] = implode("\n", $rule); + } + + $rules[] = $this->dumpRoute($name, $route, $options, $hostRegexUnique); + + $methodVars = array_merge($methodVars, $route->getMethods()); + } + if (0 < \count($methodVars)) { + $rule = array('# 405 Method Not Allowed'); + $methodVars = array_values(array_unique($methodVars)); + if (\in_array('GET', $methodVars) && !\in_array('HEAD', $methodVars)) { + $methodVars[] = 'HEAD'; + } + foreach ($methodVars as $i => $methodVar) { + $rule[] = sprintf('RewriteCond %%{ENV:_ROUTING__allow_%s} =1%s', $methodVar, isset($methodVars[$i + 1]) ? ' [OR]' : ''); + } + $rule[] = sprintf('RewriteRule .* %s [QSA,L]', $options['script_name']); + + $rules[] = implode("\n", $rule); + } + + return implode("\n\n", $rules)."\n"; + } + + /** + * Dumps a single route. + * + * @param string $name Route name + * @param Route $route The route + * @param array $options Options + * @param bool $hostRegexUnique Unique identifier for the host regex + * + * @return string The compiled route + */ + private function dumpRoute($name, $route, array $options, $hostRegexUnique) + { + $compiledRoute = $route->compile(); + + // prepare the apache regex + $regex = $this->regexToApacheRegex($compiledRoute->getRegex()); + $regex = '^'.self::escape(preg_quote($options['base_uri']).substr($regex, 1), ' ', '\\'); + + $methods = $this->getRouteMethods($route); + + $hasTrailingSlash = (!$methods || \in_array('HEAD', $methods)) && '/$' === substr($regex, -2) && '^/$' !== $regex; + + $variables = array('E=_ROUTING_route:'.$name); + foreach ($compiledRoute->getHostVariables() as $variable) { + $variables[] = sprintf('E=_ROUTING_param_%s:%%{ENV:__ROUTING_host_%s_%s}', $variable, $hostRegexUnique, $variable); + } + foreach ($compiledRoute->getPathVariables() as $i => $variable) { + $variables[] = 'E=_ROUTING_param_'.$variable.':%'.($i + 1); + } + foreach ($this->normalizeValues($route->getDefaults()) as $key => $value) { + $variables[] = 'E=_ROUTING_default_'.$key.':'.strtr($value, array( + ':' => '\\:', + '=' => '\\=', + '\\' => '\\\\', + ' ' => '\\ ', + )); + } + $variables = implode(',', $variables); + + $rule = array("# $name"); + + // method mismatch + if (0 < \count($methods)) { + $allow = array(); + foreach ($methods as $method) { + $allow[] = 'E=_ROUTING_allow_'.$method.':1'; + } + + if ($compiledRoute->getHostRegex()) { + $rule[] = sprintf('RewriteCond %%{ENV:__ROUTING_host_%s} =1', $hostRegexUnique); + } + + $rule[] = "RewriteCond %{REQUEST_URI} $regex"; + $rule[] = sprintf('RewriteCond %%{REQUEST_METHOD} !^(%s)$ [NC]', implode('|', $methods)); + $rule[] = sprintf('RewriteRule .* - [S=%d,%s]', $hasTrailingSlash ? 2 : 1, implode(',', $allow)); + } + + // redirect with trailing slash appended + if ($hasTrailingSlash) { + if ($compiledRoute->getHostRegex()) { + $rule[] = sprintf('RewriteCond %%{ENV:__ROUTING_host_%s} =1', $hostRegexUnique); + } + + $rule[] = 'RewriteCond %{REQUEST_URI} '.substr($regex, 0, -2).'$'; + $rule[] = 'RewriteRule .* $0/ [QSA,L,R=301]'; + } + + // the main rule + + if ($compiledRoute->getHostRegex()) { + $rule[] = sprintf('RewriteCond %%{ENV:__ROUTING_host_%s} =1', $hostRegexUnique); + } + + $rule[] = "RewriteCond %{REQUEST_URI} $regex"; + $rule[] = "RewriteRule .* {$options['script_name']} [QSA,L,$variables]"; + + return implode("\n", $rule); + } + + /** + * Returns methods allowed for a route. + * + * @return array The methods + */ + private function getRouteMethods(Route $route) + { + $methods = $route->getMethods(); + + // GET and HEAD are equivalent + if (\in_array('GET', $methods) && !\in_array('HEAD', $methods)) { + $methods[] = 'HEAD'; + } + + return $methods; + } + + /** + * Converts a regex to make it suitable for mod_rewrite. + * + * @param string $regex The regex + * + * @return string The converted regex + */ + private function regexToApacheRegex($regex) + { + $regexPatternEnd = strrpos($regex, $regex[0]); + + return preg_replace('/\?P<.+?>/', '', substr($regex, 1, $regexPatternEnd - 1)); + } + + /** + * Escapes a string. + * + * @param string $string The string to be escaped + * @param string $char The character to be escaped + * @param string $with The character to be used for escaping + * + * @return string The escaped string + */ + private static function escape($string, $char, $with) + { + $escaped = false; + $output = ''; + foreach (str_split($string) as $symbol) { + if ($escaped) { + $output .= $symbol; + $escaped = false; + continue; + } + if ($symbol === $char) { + $output .= $with.$char; + continue; + } + if ($symbol === $with) { + $escaped = true; + } + $output .= $symbol; + } + + return $output; + } + + /** + * Normalizes an array of values. + * + * @return string[] + */ + private function normalizeValues(array $values) + { + $normalizedValues = array(); + foreach ($values as $key => $value) { + if (\is_array($value)) { + foreach ($value as $index => $bit) { + $normalizedValues[sprintf('%s[%s]', $key, $index)] = $bit; + } + } else { + $normalizedValues[$key] = (string) $value; + } + } + + return $normalizedValues; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php b/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php new file mode 100644 index 0000000..6916297 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/DumperCollection.php @@ -0,0 +1,159 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Collection of routes. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperCollection implements \IteratorAggregate +{ + /** + * @var DumperCollection|null + */ + private $parent; + + /** + * @var DumperCollection[]|DumperRoute[] + */ + private $children = array(); + + /** + * @var array + */ + private $attributes = array(); + + /** + * Returns the children routes and collections. + * + * @return self[]|DumperRoute[] + */ + public function all() + { + return $this->children; + } + + /** + * Adds a route or collection. + * + * @param DumperRoute|DumperCollection The route or collection + */ + public function add($child) + { + if ($child instanceof self) { + $child->setParent($this); + } + $this->children[] = $child; + } + + /** + * Sets children. + * + * @param array $children The children + */ + public function setAll(array $children) + { + foreach ($children as $child) { + if ($child instanceof self) { + $child->setParent($this); + } + } + $this->children = $children; + } + + /** + * Returns an iterator over the children. + * + * @return \Iterator|DumperCollection[]|DumperRoute[] The iterator + */ + public function getIterator() + { + return new \ArrayIterator($this->children); + } + + /** + * Returns the root of the collection. + * + * @return self The root collection + */ + public function getRoot() + { + return (null !== $this->parent) ? $this->parent->getRoot() : $this; + } + + /** + * Returns the parent collection. + * + * @return self|null The parent collection or null if the collection has no parent + */ + protected function getParent() + { + return $this->parent; + } + + /** + * Sets the parent collection. + */ + protected function setParent(DumperCollection $parent) + { + $this->parent = $parent; + } + + /** + * Returns true if the attribute is defined. + * + * @param string $name The attribute name + * + * @return bool true if the attribute is defined, false otherwise + */ + public function hasAttribute($name) + { + return array_key_exists($name, $this->attributes); + } + + /** + * Returns an attribute by name. + * + * @param string $name The attribute name + * @param mixed $default Default value is the attribute doesn't exist + * + * @return mixed The attribute value + */ + public function getAttribute($name, $default = null) + { + return $this->hasAttribute($name) ? $this->attributes[$name] : $default; + } + + /** + * Sets an attribute by name. + * + * @param string $name The attribute name + * @param mixed $value The attribute value + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + /** + * Sets multiple attributes. + * + * @param array $attributes The attributes + */ + public function setAttributes($attributes) + { + $this->attributes = $attributes; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/DumperPrefixCollection.php b/vendor/symfony/routing/Matcher/Dumper/DumperPrefixCollection.php new file mode 100644 index 0000000..f736f75 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/DumperPrefixCollection.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +/** + * Prefix tree of routes preserving routes order. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperPrefixCollection extends DumperCollection +{ + /** + * @var string + */ + private $prefix = ''; + + /** + * Returns the prefix. + * + * @return string The prefix + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * Sets the prefix. + * + * @param string $prefix The prefix + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Adds a route in the tree. + * + * @return self + * + * @throws \LogicException + */ + public function addPrefixRoute(DumperRoute $route) + { + $prefix = $route->getRoute()->compile()->getStaticPrefix(); + + for ($collection = $this; null !== $collection; $collection = $collection->getParent()) { + // Same prefix, add to current leave + if ($collection->prefix === $prefix) { + $collection->add($route); + + return $collection; + } + + // Prefix starts with route's prefix + if ('' === $collection->prefix || 0 === strpos($prefix, $collection->prefix)) { + $child = new self(); + $child->setPrefix(substr($prefix, 0, \strlen($collection->prefix) + 1)); + $collection->add($child); + + return $child->addPrefixRoute($route); + } + } + + // Reached only if the root has a non empty prefix + throw new \LogicException('The collection root must not have a prefix'); + } + + /** + * Merges nodes whose prefix ends with a slash. + * + * Children of a node whose prefix ends with a slash are moved to the parent node + */ + public function mergeSlashNodes() + { + $children = array(); + + foreach ($this as $child) { + if ($child instanceof self) { + $child->mergeSlashNodes(); + if ('/' === substr($child->prefix, -1)) { + $children = array_merge($children, $child->all()); + } else { + $children[] = $child; + } + } else { + $children[] = $child; + } + } + + $this->setAll($children); + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php b/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php new file mode 100644 index 0000000..c71989a --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/DumperRoute.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\Route; + +/** + * Container for a Route. + * + * @author Arnaud Le Blanc + * + * @internal + */ +class DumperRoute +{ + private $name; + private $route; + + /** + * @param string $name The route name + * @param Route $route The route + */ + public function __construct($name, Route $route) + { + $this->name = $name; + $this->route = $route; + } + + /** + * Returns the route name. + * + * @return string The route name + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the route. + * + * @return Route The route + */ + public function getRoute() + { + return $this->route; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php new file mode 100644 index 0000000..ea51ab4 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/MatcherDumper.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumper is the abstract class for all built-in matcher dumpers. + * + * @author Fabien Potencier + */ +abstract class MatcherDumper implements MatcherDumperInterface +{ + private $routes; + + public function __construct(RouteCollection $routes) + { + $this->routes = $routes; + } + + /** + * {@inheritdoc} + */ + public function getRoutes() + { + return $this->routes; + } +} diff --git a/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php b/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php new file mode 100644 index 0000000..5e7c134 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/MatcherDumperInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\Routing\RouteCollection; + +/** + * MatcherDumperInterface is the interface that all matcher dumper classes must implement. + * + * @author Fabien Potencier + */ +interface MatcherDumperInterface +{ + /** + * Dumps a set of routes to a string representation of executable code + * that can then be used to match a request against these routes. + * + * @param array $options An array of options + * + * @return string Executable code + */ + public function dump(array $options = array()); + + /** + * Gets the routes to dump. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRoutes(); +} diff --git a/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php b/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php new file mode 100644 index 0000000..dc83232 --- /dev/null +++ b/vendor/symfony/routing/Matcher/Dumper/PhpMatcherDumper.php @@ -0,0 +1,432 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher\Dumper; + +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * PhpMatcherDumper creates a PHP class able to match URLs for a given set of routes. + * + * @author Fabien Potencier + * @author Tobias Schultze + * @author Arnaud Le Blanc + */ +class PhpMatcherDumper extends MatcherDumper +{ + private $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * Dumps a set of routes to a PHP class. + * + * Available options: + * + * * class: The class name + * * base_class: The base class name + * + * @param array $options An array of options + * + * @return string A PHP class representing the matcher class + */ + public function dump(array $options = array()) + { + $options = array_replace(array( + 'class' => 'ProjectUrlMatcher', + 'base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + ), $options); + + // trailing slash support is only enabled if we know how to redirect the user + $interfaces = class_implements($options['base_class']); + $supportsRedirections = isset($interfaces['Symfony\\Component\\Routing\\Matcher\\RedirectableUrlMatcherInterface']); + + return <<context = \$context; + } + +{$this->generateMatchMethod($supportsRedirections)} +} + +EOF; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Generates the code for the match method implementing UrlMatcherInterface. + * + * @param bool $supportsRedirections Whether redirections are supported by the base class + * + * @return string Match method as PHP code + */ + private function generateMatchMethod($supportsRedirections) + { + $code = rtrim($this->compileRoutes($this->getRoutes(), $supportsRedirections), "\n"); + + return <<context; + \$request = \$this->request ?: \$this->createRequest(\$pathinfo); + +$code + + throw 0 < count(\$allow) ? new MethodNotAllowedException(array_unique(\$allow)) : new ResourceNotFoundException(); + } +EOF; + } + + /** + * Generates PHP code to match a RouteCollection with all its routes. + * + * @param RouteCollection $routes A RouteCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * + * @return string PHP code + */ + private function compileRoutes(RouteCollection $routes, $supportsRedirections) + { + $fetchedHost = false; + + $groups = $this->groupRoutesByHostRegex($routes); + $code = ''; + + foreach ($groups as $collection) { + if (null !== $regex = $collection->getAttribute('host_regex')) { + if (!$fetchedHost) { + $code .= " \$host = \$this->context->getHost();\n\n"; + $fetchedHost = true; + } + + $code .= sprintf(" if (preg_match(%s, \$host, \$hostMatches)) {\n", var_export($regex, true)); + } + + $tree = $this->buildPrefixTree($collection); + $groupCode = $this->compilePrefixRoutes($tree, $supportsRedirections); + + if (null !== $regex) { + // apply extra indention at each line (except empty ones) + $groupCode = preg_replace('/^.{2,}$/m', ' $0', $groupCode); + $code .= $groupCode; + $code .= " }\n\n"; + } else { + $code .= $groupCode; + } + } + + return $code; + } + + /** + * Generates PHP code recursively to match a tree of routes. + * + * @param DumperPrefixCollection $collection A DumperPrefixCollection instance + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string $parentPrefix Prefix of the parent collection + * + * @return string PHP code + */ + private function compilePrefixRoutes(DumperPrefixCollection $collection, $supportsRedirections, $parentPrefix = '') + { + $code = ''; + $prefix = $collection->getPrefix(); + $optimizable = 1 < \strlen($prefix) && 1 < \count($collection->all()); + $optimizedPrefix = $parentPrefix; + + if ($optimizable) { + $optimizedPrefix = $prefix; + + $code .= sprintf(" if (0 === strpos(\$pathinfo, %s)) {\n", var_export($prefix, true)); + } + + foreach ($collection as $route) { + if ($route instanceof DumperCollection) { + $code .= $this->compilePrefixRoutes($route, $supportsRedirections, $optimizedPrefix); + } else { + $code .= $this->compileRoute($route->getRoute(), $route->getName(), $supportsRedirections, $optimizedPrefix)."\n"; + } + } + + if ($optimizable) { + $code .= " }\n\n"; + // apply extra indention at each line (except empty ones) + $code = preg_replace('/^.{2,}$/m', ' $0', $code); + } + + return $code; + } + + /** + * Compiles a single Route to PHP code used to match it against the path info. + * + * @param Route $route A Route instance + * @param string $name The name of the Route + * @param bool $supportsRedirections Whether redirections are supported by the base class + * @param string|null $parentPrefix The prefix of the parent collection used to optimize the code + * + * @return string PHP code + * + * @throws \LogicException + */ + private function compileRoute(Route $route, $name, $supportsRedirections, $parentPrefix = null) + { + $code = ''; + $compiledRoute = $route->compile(); + $conditions = array(); + $hasTrailingSlash = false; + $matches = false; + $hostMatches = false; + $methods = $route->getMethods(); + + // GET and HEAD are equivalent + if (\in_array('GET', $methods) && !\in_array('HEAD', $methods)) { + $methods[] = 'HEAD'; + } + + $supportsTrailingSlash = $supportsRedirections && (!$methods || \in_array('GET', $methods)); + + if (!\count($compiledRoute->getPathVariables()) && false !== preg_match('#^(.)\^(?P.*?)\$\1#', $compiledRoute->getRegex(), $m)) { + if ($supportsTrailingSlash && '/' === substr($m['url'], -1)) { + $conditions[] = sprintf("%s === rtrim(\$pathinfo, '/')", var_export(rtrim(str_replace('\\', '', $m['url']), '/'), true)); + $hasTrailingSlash = true; + } else { + $conditions[] = sprintf('%s === $pathinfo', var_export(str_replace('\\', '', $m['url']), true)); + } + } else { + if ($compiledRoute->getStaticPrefix() && $compiledRoute->getStaticPrefix() !== $parentPrefix) { + $conditions[] = sprintf('0 === strpos($pathinfo, %s)', var_export($compiledRoute->getStaticPrefix(), true)); + } + + $regex = $compiledRoute->getRegex(); + if ($supportsTrailingSlash && $pos = strpos($regex, '/$')) { + $regex = substr($regex, 0, $pos).'/?$'.substr($regex, $pos + 2); + $hasTrailingSlash = true; + } + $conditions[] = sprintf('preg_match(%s, $pathinfo, $matches)', var_export($regex, true)); + + $matches = true; + } + + if ($compiledRoute->getHostVariables()) { + $hostMatches = true; + } + + if ($route->getCondition()) { + $conditions[] = $this->getExpressionLanguage()->compile($route->getCondition(), array('context', 'request')); + } + + $conditions = implode(' && ', $conditions); + + $code .= <<context->getMethod(), array('HEAD', 'GET'))) { + goto $gotoname; + } else { + return \$this->redirect(\$rawPathinfo.'/', '$name'); + } + + +EOF; + } + + if ($schemes = $route->getSchemes()) { + if (!$supportsRedirections) { + throw new \LogicException('The "schemes" requirement is only supported for URL matchers that implement RedirectableUrlMatcherInterface.'); + } + $schemes = str_replace("\n", '', var_export(array_flip($schemes), true)); + if ($methods) { + $methods = implode("', '", $methods); + $code .= <<context->getScheme()]); + if (!in_array(\$this->context->getMethod(), array('$methods'))) { + if (\$hasRequiredScheme) { + \$allow = array_merge(\$allow, array('$methods')); + } + goto $gotoname; + } + if (!\$hasRequiredScheme) { + if (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) { + goto $gotoname; + } + + return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)); + } + + +EOF; + } else { + $code .= <<context->getScheme()])) { + if (!in_array(\$this->context->getMethod(), array('HEAD', 'GET'))) { + goto $gotoname; + } + + return \$this->redirect(\$rawPathinfo, '$name', key(\$requiredSchemes)); + } + + +EOF; + } + } elseif ($methods) { + if (1 === \count($methods)) { + $code .= <<context->getMethod() != '$methods[0]') { + \$allow[] = '$methods[0]'; + goto $gotoname; + } + + +EOF; + } else { + $methods = implode("', '", $methods); + $code .= <<context->getMethod(), array('$methods'))) { + \$allow = array_merge(\$allow, array('$methods')); + goto $gotoname; + } + + +EOF; + } + } + + // optimize parameters array + if ($matches || $hostMatches) { + $vars = array(); + if ($hostMatches) { + $vars[] = '$hostMatches'; + } + if ($matches) { + $vars[] = '$matches'; + } + $vars[] = "array('_route' => '$name')"; + + $code .= sprintf( + " return \$this->mergeDefaults(array_replace(%s), %s);\n", + implode(', ', $vars), + str_replace("\n", '', var_export($route->getDefaults(), true)) + ); + } elseif ($route->getDefaults()) { + $code .= sprintf(" return %s;\n", str_replace("\n", '', var_export(array_replace($route->getDefaults(), array('_route' => $name)), true))); + } else { + $code .= sprintf(" return array('_route' => '%s');\n", $name); + } + $code .= " }\n"; + + if ($hasTrailingSlash || $schemes || $methods) { + $code .= " $gotoname:\n"; + } + + return $code; + } + + /** + * Groups consecutive routes having the same host regex. + * + * The result is a collection of collections of routes having the same host regex. + * + * @param RouteCollection $routes A flat RouteCollection + * + * @return DumperCollection A collection with routes grouped by host regex in sub-collections + */ + private function groupRoutesByHostRegex(RouteCollection $routes) + { + $groups = new DumperCollection(); + + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', null); + $groups->add($currentGroup); + + foreach ($routes as $name => $route) { + $hostRegex = $route->compile()->getHostRegex(); + if ($currentGroup->getAttribute('host_regex') !== $hostRegex) { + $currentGroup = new DumperCollection(); + $currentGroup->setAttribute('host_regex', $hostRegex); + $groups->add($currentGroup); + } + $currentGroup->add(new DumperRoute($name, $route)); + } + + return $groups; + } + + /** + * Organizes the routes into a prefix tree. + * + * Routes order is preserved such that traversing the tree will traverse the + * routes in the origin order. + * + * @return DumperPrefixCollection + */ + private function buildPrefixTree(DumperCollection $collection) + { + $tree = new DumperPrefixCollection(); + $current = $tree; + + foreach ($collection as $route) { + $current = $current->addPrefixRoute($route); + } + + $tree->mergeSlashNodes(); + + return $tree; + } + + private function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } +} diff --git a/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php b/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php new file mode 100644 index 0000000..db21fa0 --- /dev/null +++ b/vendor/symfony/routing/Matcher/RedirectableUrlMatcher.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Route; + +/** + * @author Fabien Potencier + */ +abstract class RedirectableUrlMatcher extends UrlMatcher implements RedirectableUrlMatcherInterface +{ + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + try { + $parameters = parent::match($pathinfo); + } catch (ResourceNotFoundException $e) { + if ('/' === substr($pathinfo, -1) || !\in_array($this->context->getMethod(), array('HEAD', 'GET'))) { + throw $e; + } + + try { + parent::match($pathinfo.'/'); + + return $this->redirect($pathinfo.'/', null); + } catch (ResourceNotFoundException $e2) { + throw $e; + } + } + + return $parameters; + } + + /** + * {@inheritdoc} + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + + // check HTTP scheme requirement + $scheme = $this->context->getScheme(); + $schemes = $route->getSchemes(); + if ($schemes && !$route->hasScheme($scheme)) { + return array(self::ROUTE_MATCH, $this->redirect($pathinfo, $name, current($schemes))); + } + + return array(self::REQUIREMENT_MATCH, null); + } +} diff --git a/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php b/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php new file mode 100644 index 0000000..7c27bc8 --- /dev/null +++ b/vendor/symfony/routing/Matcher/RedirectableUrlMatcherInterface.php @@ -0,0 +1,31 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +/** + * RedirectableUrlMatcherInterface knows how to redirect the user. + * + * @author Fabien Potencier + */ +interface RedirectableUrlMatcherInterface +{ + /** + * Redirects the user to another URL. + * + * @param string $path The path info to redirect to + * @param string $route The route name that matched + * @param string|null $scheme The URL scheme (null to keep the current one) + * + * @return array An array of parameters + */ + public function redirect($path, $route, $scheme = null); +} diff --git a/vendor/symfony/routing/Matcher/RequestMatcherInterface.php b/vendor/symfony/routing/Matcher/RequestMatcherInterface.php new file mode 100644 index 0000000..a2f40df --- /dev/null +++ b/vendor/symfony/routing/Matcher/RequestMatcherInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; + +/** + * RequestMatcherInterface is the interface that all request matcher classes must implement. + * + * @author Fabien Potencier + */ +interface RequestMatcherInterface +{ + /** + * Tries to match a request with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If no matching resource could be found + * @throws MethodNotAllowedException If a matching resource was found but the request method is not allowed + */ + public function matchRequest(Request $request); +} diff --git a/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php b/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php new file mode 100644 index 0000000..61cad8e --- /dev/null +++ b/vendor/symfony/routing/Matcher/TraceableUrlMatcher.php @@ -0,0 +1,141 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\ExceptionInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * TraceableUrlMatcher helps debug path info matching by tracing the match. + * + * @author Fabien Potencier + */ +class TraceableUrlMatcher extends UrlMatcher +{ + const ROUTE_DOES_NOT_MATCH = 0; + const ROUTE_ALMOST_MATCHES = 1; + const ROUTE_MATCHES = 2; + + protected $traces; + + public function getTraces($pathinfo) + { + $this->traces = array(); + + try { + $this->match($pathinfo); + } catch (ExceptionInterface $e) { + } + + return $this->traces; + } + + public function getTracesForRequest(Request $request) + { + $this->request = $request; + $traces = $this->getTraces($request->getPathInfo()); + $this->request = null; + + return $traces; + } + + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + // does it match without any requirements? + $r = new Route($route->getPath(), $route->getDefaults(), array(), $route->getOptions()); + $cr = $r->compile(); + if (!preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Path "%s" does not match', $route->getPath()), self::ROUTE_DOES_NOT_MATCH, $name, $route); + + continue; + } + + foreach ($route->getRequirements() as $n => $regex) { + $r = new Route($route->getPath(), $route->getDefaults(), array($n => $regex), $route->getOptions()); + $cr = $r->compile(); + + if (\in_array($n, $cr->getVariables()) && !preg_match($cr->getRegex(), $pathinfo)) { + $this->addTrace(sprintf('Requirement for "%s" does not match (%s)', $n, $regex), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue 2; + } + } + + continue; + } + + // check host requirement + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + $this->addTrace(sprintf('Host "%s" does not match the requirement ("%s")', $this->context->getHost(), $route->getHost()), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + + // check HTTP method requirement + if ($requiredMethods = $route->getMethods()) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!\in_array($method, $requiredMethods)) { + $this->allow = array_merge($this->allow, $requiredMethods); + + $this->addTrace(sprintf('Method "%s" does not match any of the required methods (%s)', $this->context->getMethod(), implode(', ', $requiredMethods)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check condition + if ($condition = $route->getCondition()) { + if (!$this->getExpressionLanguage()->evaluate($condition, array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + $this->addTrace(sprintf('Condition "%s" does not evaluate to "true"', $condition), self::ROUTE_ALMOST_MATCHES, $name, $route); + + continue; + } + } + + // check HTTP scheme requirement + if ($requiredSchemes = $route->getSchemes()) { + $scheme = $this->context->getScheme(); + + if (!$route->hasScheme($scheme)) { + $this->addTrace(sprintf('Scheme "%s" does not match any of the required schemes (%s); the user will be redirected to first required scheme', $scheme, implode(', ', $requiredSchemes)), self::ROUTE_ALMOST_MATCHES, $name, $route); + + return true; + } + } + + $this->addTrace('Route matches!', self::ROUTE_MATCHES, $name, $route); + + return true; + } + } + + private function addTrace($log, $level = self::ROUTE_DOES_NOT_MATCH, $name = null, $route = null) + { + $this->traces[] = array( + 'log' => $log, + 'name' => $name, + 'level' => $level, + 'path' => null !== $route ? $route->getPath() : null, + ); + } +} diff --git a/vendor/symfony/routing/Matcher/UrlMatcher.php b/vendor/symfony/routing/Matcher/UrlMatcher.php new file mode 100644 index 0000000..9226c8a --- /dev/null +++ b/vendor/symfony/routing/Matcher/UrlMatcher.php @@ -0,0 +1,250 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\ExpressionLanguage\ExpressionLanguage; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\Route; +use Symfony\Component\Routing\RouteCollection; + +/** + * UrlMatcher matches URL based on a set of routes. + * + * @author Fabien Potencier + */ +class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface +{ + const REQUIREMENT_MATCH = 0; + const REQUIREMENT_MISMATCH = 1; + const ROUTE_MATCH = 2; + + protected $context; + protected $allow = array(); + protected $routes; + protected $request; + protected $expressionLanguage; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + protected $expressionLanguageProviders = array(); + + public function __construct(RouteCollection $routes, RequestContext $context) + { + $this->routes = $routes; + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + $this->allow = array(); + + if ($ret = $this->matchCollection(rawurldecode($pathinfo), $this->routes)) { + return $ret; + } + + throw 0 < \count($this->allow) + ? new MethodNotAllowedException(array_unique($this->allow)) + : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo)); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $this->request = $request; + + $ret = $this->match($request->getPathInfo()); + + $this->request = null; + + return $ret; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * Tries to match a URL with a set of routes. + * + * @param string $pathinfo The path info to be parsed + * @param RouteCollection $routes The set of routes + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + protected function matchCollection($pathinfo, RouteCollection $routes) + { + foreach ($routes as $name => $route) { + $compiledRoute = $route->compile(); + + // check the static prefix of the URL first. Only use the more expensive preg_match when it matches + if ('' !== $compiledRoute->getStaticPrefix() && 0 !== strpos($pathinfo, $compiledRoute->getStaticPrefix())) { + continue; + } + + if (!preg_match($compiledRoute->getRegex(), $pathinfo, $matches)) { + continue; + } + + $hostMatches = array(); + if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) { + continue; + } + + $status = $this->handleRouteRequirements($pathinfo, $name, $route); + + if (self::REQUIREMENT_MISMATCH === $status[0]) { + continue; + } + + // check HTTP method requirement + if ($requiredMethods = $route->getMethods()) { + // HEAD and GET are equivalent as per RFC + if ('HEAD' === $method = $this->context->getMethod()) { + $method = 'GET'; + } + + if (!\in_array($method, $requiredMethods)) { + if (self::REQUIREMENT_MATCH === $status[0]) { + $this->allow = array_merge($this->allow, $requiredMethods); + } + + continue; + } + } + + if (self::ROUTE_MATCH === $status[0]) { + return $status[1]; + } + + return $this->getAttributes($route, $name, array_replace($matches, $hostMatches)); + } + } + + /** + * Returns an array of values to use as request attributes. + * + * As this method requires the Route object, it is not available + * in matchers that do not have access to the matched Route instance + * (like the PHP and Apache matcher dumpers). + * + * @param Route $route The route we are matching against + * @param string $name The name of the route + * @param array $attributes An array of attributes from the matcher + * + * @return array An array of parameters + */ + protected function getAttributes(Route $route, $name, array $attributes) + { + $attributes['_route'] = $name; + + return $this->mergeDefaults($attributes, $route->getDefaults()); + } + + /** + * Handles specific route requirements. + * + * @param string $pathinfo The path + * @param string $name The route name + * @param Route $route The route + * + * @return array The first element represents the status, the second contains additional information + */ + protected function handleRouteRequirements($pathinfo, $name, Route $route) + { + // expression condition + if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), array('context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)))) { + return array(self::REQUIREMENT_MISMATCH, null); + } + + // check HTTP scheme requirement + $scheme = $this->context->getScheme(); + $status = $route->getSchemes() && !$route->hasScheme($scheme) ? self::REQUIREMENT_MISMATCH : self::REQUIREMENT_MATCH; + + return array($status, null); + } + + /** + * Get merged default parameters. + * + * @param array $params The parameters + * @param array $defaults The defaults + * + * @return array Merged default parameters + */ + protected function mergeDefaults($params, $defaults) + { + foreach ($params as $key => $value) { + if (!\is_int($key)) { + $defaults[$key] = $value; + } + } + + return $defaults; + } + + protected function getExpressionLanguage() + { + if (null === $this->expressionLanguage) { + if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { + throw new \RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); + } + $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders); + } + + return $this->expressionLanguage; + } + + /** + * @internal + */ + protected function createRequest($pathinfo) + { + if (!class_exists('Symfony\Component\HttpFoundation\Request')) { + return null; + } + + return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), array(), array(), array( + 'SCRIPT_FILENAME' => $this->context->getBaseUrl(), + 'SCRIPT_NAME' => $this->context->getBaseUrl(), + )); + } +} diff --git a/vendor/symfony/routing/Matcher/UrlMatcherInterface.php b/vendor/symfony/routing/Matcher/UrlMatcherInterface.php new file mode 100644 index 0000000..0639182 --- /dev/null +++ b/vendor/symfony/routing/Matcher/UrlMatcherInterface.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing\Matcher; + +use Symfony\Component\Routing\Exception\MethodNotAllowedException; +use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\RequestContextAwareInterface; + +/** + * UrlMatcherInterface is the interface that all URL matcher classes must implement. + * + * @author Fabien Potencier + */ +interface UrlMatcherInterface extends RequestContextAwareInterface +{ + /** + * Tries to match a URL path with a set of routes. + * + * If the matcher can not find information, it must throw one of the exceptions documented + * below. + * + * @param string $pathinfo The path info to be parsed (raw format, i.e. not urldecoded) + * + * @return array An array of parameters + * + * @throws ResourceNotFoundException If the resource could not be found + * @throws MethodNotAllowedException If the resource was found but the request method is not allowed + */ + public function match($pathinfo); +} diff --git a/vendor/symfony/routing/RequestContext.php b/vendor/symfony/routing/RequestContext.php new file mode 100644 index 0000000..d62a776 --- /dev/null +++ b/vendor/symfony/routing/RequestContext.php @@ -0,0 +1,336 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\HttpFoundation\Request; + +/** + * Holds information about the current request. + * + * This class implements a fluent interface. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RequestContext +{ + private $baseUrl; + private $pathInfo; + private $method; + private $host; + private $scheme; + private $httpPort; + private $httpsPort; + private $queryString; + private $parameters = array(); + + /** + * @param string $baseUrl The base URL + * @param string $method The HTTP method + * @param string $host The HTTP host name + * @param string $scheme The HTTP scheme + * @param int $httpPort The HTTP port + * @param int $httpsPort The HTTPS port + * @param string $path The path + * @param string $queryString The query string + */ + public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443, $path = '/', $queryString = '') + { + $this->setBaseUrl($baseUrl); + $this->setMethod($method); + $this->setHost($host); + $this->setScheme($scheme); + $this->setHttpPort($httpPort); + $this->setHttpsPort($httpsPort); + $this->setPathInfo($path); + $this->setQueryString($queryString); + } + + /** + * Updates the RequestContext information based on a HttpFoundation Request. + * + * @return $this + */ + public function fromRequest(Request $request) + { + $this->setBaseUrl($request->getBaseUrl()); + $this->setPathInfo($request->getPathInfo()); + $this->setMethod($request->getMethod()); + $this->setHost($request->getHost()); + $this->setScheme($request->getScheme()); + $this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort()); + $this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort); + $this->setQueryString($request->server->get('QUERY_STRING', '')); + + return $this; + } + + /** + * Gets the base URL. + * + * @return string The base URL + */ + public function getBaseUrl() + { + return $this->baseUrl; + } + + /** + * Sets the base URL. + * + * @param string $baseUrl The base URL + * + * @return $this + */ + public function setBaseUrl($baseUrl) + { + $this->baseUrl = $baseUrl; + + return $this; + } + + /** + * Gets the path info. + * + * @return string The path info + */ + public function getPathInfo() + { + return $this->pathInfo; + } + + /** + * Sets the path info. + * + * @param string $pathInfo The path info + * + * @return $this + */ + public function setPathInfo($pathInfo) + { + $this->pathInfo = $pathInfo; + + return $this; + } + + /** + * Gets the HTTP method. + * + * The method is always an uppercased string. + * + * @return string The HTTP method + */ + public function getMethod() + { + return $this->method; + } + + /** + * Sets the HTTP method. + * + * @param string $method The HTTP method + * + * @return $this + */ + public function setMethod($method) + { + $this->method = strtoupper($method); + + return $this; + } + + /** + * Gets the HTTP host. + * + * The host is always lowercased because it must be treated case-insensitive. + * + * @return string The HTTP host + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the HTTP host. + * + * @param string $host The HTTP host + * + * @return $this + */ + public function setHost($host) + { + $this->host = strtolower($host); + + return $this; + } + + /** + * Gets the HTTP scheme. + * + * @return string The HTTP scheme + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Sets the HTTP scheme. + * + * @param string $scheme The HTTP scheme + * + * @return $this + */ + public function setScheme($scheme) + { + $this->scheme = strtolower($scheme); + + return $this; + } + + /** + * Gets the HTTP port. + * + * @return int The HTTP port + */ + public function getHttpPort() + { + return $this->httpPort; + } + + /** + * Sets the HTTP port. + * + * @param int $httpPort The HTTP port + * + * @return $this + */ + public function setHttpPort($httpPort) + { + $this->httpPort = (int) $httpPort; + + return $this; + } + + /** + * Gets the HTTPS port. + * + * @return int The HTTPS port + */ + public function getHttpsPort() + { + return $this->httpsPort; + } + + /** + * Sets the HTTPS port. + * + * @param int $httpsPort The HTTPS port + * + * @return $this + */ + public function setHttpsPort($httpsPort) + { + $this->httpsPort = (int) $httpsPort; + + return $this; + } + + /** + * Gets the query string. + * + * @return string The query string without the "?" + */ + public function getQueryString() + { + return $this->queryString; + } + + /** + * Sets the query string. + * + * @param string $queryString The query string (after "?") + * + * @return $this + */ + public function setQueryString($queryString) + { + // string cast to be fault-tolerant, accepting null + $this->queryString = (string) $queryString; + + return $this; + } + + /** + * Returns the parameters. + * + * @return array The parameters + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Sets the parameters. + * + * @param array $parameters The parameters + * + * @return $this + */ + public function setParameters(array $parameters) + { + $this->parameters = $parameters; + + return $this; + } + + /** + * Gets a parameter value. + * + * @param string $name A parameter name + * + * @return mixed The parameter value or null if nonexistent + */ + public function getParameter($name) + { + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; + } + + /** + * Checks if a parameter value is set for the given parameter. + * + * @param string $name A parameter name + * + * @return bool True if the parameter value is set, false otherwise + */ + public function hasParameter($name) + { + return array_key_exists($name, $this->parameters); + } + + /** + * Sets a parameter value. + * + * @param string $name A parameter name + * @param mixed $parameter The parameter value + * + * @return $this + */ + public function setParameter($name, $parameter) + { + $this->parameters[$name] = $parameter; + + return $this; + } +} diff --git a/vendor/symfony/routing/RequestContextAwareInterface.php b/vendor/symfony/routing/RequestContextAwareInterface.php new file mode 100644 index 0000000..df5b9fc --- /dev/null +++ b/vendor/symfony/routing/RequestContextAwareInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +interface RequestContextAwareInterface +{ + /** + * Sets the request context. + */ + public function setContext(RequestContext $context); + + /** + * Gets the request context. + * + * @return RequestContext The context + */ + public function getContext(); +} diff --git a/vendor/symfony/routing/Route.php b/vendor/symfony/routing/Route.php new file mode 100644 index 0000000..50400f8 --- /dev/null +++ b/vendor/symfony/routing/Route.php @@ -0,0 +1,628 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * A Route describes a route and its parameters. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class Route implements \Serializable +{ + private $path = '/'; + private $host = ''; + private $schemes = array(); + private $methods = array(); + private $defaults = array(); + private $requirements = array(); + private $options = array(); + private $condition = ''; + + /** + * @var CompiledRoute|null + */ + private $compiled; + + /** + * Constructor. + * + * Available options: + * + * * compiler_class: A class name able to compile this route instance (RouteCompiler by default) + * + * @param string $path The path pattern to match + * @param array $defaults An array of default parameter values + * @param array $requirements An array of requirements for parameters (regexes) + * @param array $options An array of options + * @param string $host The host pattern to match + * @param string|string[] $schemes A required URI scheme or an array of restricted schemes + * @param string|string[] $methods A required HTTP method or an array of restricted methods + * @param string $condition A condition that should evaluate to true for the route to match + */ + public function __construct($path, array $defaults = array(), array $requirements = array(), array $options = array(), $host = '', $schemes = array(), $methods = array(), $condition = '') + { + $this->setPath($path); + $this->setDefaults($defaults); + $this->setRequirements($requirements); + $this->setOptions($options); + $this->setHost($host); + // The conditions make sure that an initial empty $schemes/$methods does not override the corresponding requirement. + // They can be removed when the BC layer is removed. + if ($schemes) { + $this->setSchemes($schemes); + } + if ($methods) { + $this->setMethods($methods); + } + $this->setCondition($condition); + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + 'path' => $this->path, + 'host' => $this->host, + 'defaults' => $this->defaults, + 'requirements' => $this->requirements, + 'options' => $this->options, + 'schemes' => $this->schemes, + 'methods' => $this->methods, + 'condition' => $this->condition, + 'compiled' => $this->compiled, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + $data = unserialize($serialized); + $this->path = $data['path']; + $this->host = $data['host']; + $this->defaults = $data['defaults']; + $this->requirements = $data['requirements']; + $this->options = $data['options']; + $this->schemes = $data['schemes']; + $this->methods = $data['methods']; + + if (isset($data['condition'])) { + $this->condition = $data['condition']; + } + if (isset($data['compiled'])) { + $this->compiled = $data['compiled']; + } + } + + /** + * Returns the pattern for the path. + * + * @return string The pattern + * + * @deprecated since version 2.2, to be removed in 3.0. Use getPath instead. + */ + public function getPattern() + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. Use the getPath() method instead.', E_USER_DEPRECATED); + + return $this->path; + } + + /** + * Sets the pattern for the path. + * + * This method implements a fluent interface. + * + * @param string $pattern The path pattern + * + * @return $this + * + * @deprecated since version 2.2, to be removed in 3.0. Use setPath instead. + */ + public function setPattern($pattern) + { + @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. Use the setPath() method instead.', E_USER_DEPRECATED); + + return $this->setPath($pattern); + } + + /** + * Returns the pattern for the path. + * + * @return string The path pattern + */ + public function getPath() + { + return $this->path; + } + + /** + * Sets the pattern for the path. + * + * This method implements a fluent interface. + * + * @param string $pattern The path pattern + * + * @return $this + */ + public function setPath($pattern) + { + // A pattern must start with a slash and must not have multiple slashes at the beginning because the + // generated path for this route would be confused with a network path, e.g. '//domain.com/path'. + $this->path = '/'.ltrim(trim($pattern), '/'); + $this->compiled = null; + + return $this; + } + + /** + * Returns the pattern for the host. + * + * @return string The host pattern + */ + public function getHost() + { + return $this->host; + } + + /** + * Sets the pattern for the host. + * + * This method implements a fluent interface. + * + * @param string $pattern The host pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = (string) $pattern; + $this->compiled = null; + + return $this; + } + + /** + * Returns the lowercased schemes this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * @return string[] The schemes + */ + public function getSchemes() + { + return $this->schemes; + } + + /** + * Sets the schemes (e.g. 'https') this route is restricted to. + * So an empty array means that any scheme is allowed. + * + * This method implements a fluent interface. + * + * @param string|string[] $schemes The scheme or an array of schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = array_map('strtolower', (array) $schemes); + + // this is to keep BC and will be removed in a future version + if ($this->schemes) { + $this->requirements['_scheme'] = implode('|', $this->schemes); + } else { + unset($this->requirements['_scheme']); + } + + $this->compiled = null; + + return $this; + } + + /** + * Checks if a scheme requirement has been set. + * + * @param string $scheme + * + * @return bool true if the scheme requirement exists, otherwise false + */ + public function hasScheme($scheme) + { + return \in_array(strtolower($scheme), $this->schemes, true); + } + + /** + * Returns the uppercased HTTP methods this route is restricted to. + * So an empty array means that any method is allowed. + * + * @return string[] The methods + */ + public function getMethods() + { + return $this->methods; + } + + /** + * Sets the HTTP methods (e.g. 'POST') this route is restricted to. + * So an empty array means that any method is allowed. + * + * This method implements a fluent interface. + * + * @param string|string[] $methods The method or an array of methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = array_map('strtoupper', (array) $methods); + + // this is to keep BC and will be removed in a future version + if ($this->methods) { + $this->requirements['_method'] = implode('|', $this->methods); + } else { + unset($this->requirements['_method']); + } + + $this->compiled = null; + + return $this; + } + + /** + * Returns the options. + * + * @return array The options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Sets the options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function setOptions(array $options) + { + $this->options = array( + 'compiler_class' => 'Symfony\\Component\\Routing\\RouteCompiler', + ); + + return $this->addOptions($options); + } + + /** + * Adds options. + * + * This method implements a fluent interface. + * + * @param array $options The options + * + * @return $this + */ + public function addOptions(array $options) + { + foreach ($options as $name => $option) { + $this->options[$name] = $option; + } + $this->compiled = null; + + return $this; + } + + /** + * Sets an option value. + * + * This method implements a fluent interface. + * + * @param string $name An option name + * @param mixed $value The option value + * + * @return $this + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + $this->compiled = null; + + return $this; + } + + /** + * Get an option value. + * + * @param string $name An option name + * + * @return mixed The option value or null when not given + */ + public function getOption($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * Checks if an option has been set. + * + * @param string $name An option name + * + * @return bool true if the option is set, false otherwise + */ + public function hasOption($name) + { + return array_key_exists($name, $this->options); + } + + /** + * Returns the defaults. + * + * @return array The defaults + */ + public function getDefaults() + { + return $this->defaults; + } + + /** + * Sets the defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function setDefaults(array $defaults) + { + $this->defaults = array(); + + return $this->addDefaults($defaults); + } + + /** + * Adds defaults. + * + * This method implements a fluent interface. + * + * @param array $defaults The defaults + * + * @return $this + */ + public function addDefaults(array $defaults) + { + foreach ($defaults as $name => $default) { + $this->defaults[$name] = $default; + } + $this->compiled = null; + + return $this; + } + + /** + * Gets a default value. + * + * @param string $name A variable name + * + * @return mixed The default value or null when not given + */ + public function getDefault($name) + { + return isset($this->defaults[$name]) ? $this->defaults[$name] : null; + } + + /** + * Checks if a default value is set for the given variable. + * + * @param string $name A variable name + * + * @return bool true if the default value is set, false otherwise + */ + public function hasDefault($name) + { + return array_key_exists($name, $this->defaults); + } + + /** + * Sets a default value. + * + * @param string $name A variable name + * @param mixed $default The default value + * + * @return $this + */ + public function setDefault($name, $default) + { + $this->defaults[$name] = $default; + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirements. + * + * @return array The requirements + */ + public function getRequirements() + { + return $this->requirements; + } + + /** + * Sets the requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function setRequirements(array $requirements) + { + $this->requirements = array(); + + return $this->addRequirements($requirements); + } + + /** + * Adds requirements. + * + * This method implements a fluent interface. + * + * @param array $requirements The requirements + * + * @return $this + */ + public function addRequirements(array $requirements) + { + foreach ($requirements as $key => $regex) { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + } + $this->compiled = null; + + return $this; + } + + /** + * Returns the requirement for the given key. + * + * @param string $key The key + * + * @return string|null The regex or null when not given + */ + public function getRequirement($key) + { + if ('_scheme' === $key) { + @trigger_error('The "_scheme" requirement is deprecated since Symfony 2.2 and will be removed in 3.0. Use getSchemes() instead.', E_USER_DEPRECATED); + } elseif ('_method' === $key) { + @trigger_error('The "_method" requirement is deprecated since Symfony 2.2 and will be removed in 3.0. Use getMethods() instead.', E_USER_DEPRECATED); + } + + return isset($this->requirements[$key]) ? $this->requirements[$key] : null; + } + + /** + * Checks if a requirement is set for the given key. + * + * @param string $key A variable name + * + * @return bool true if a requirement is specified, false otherwise + */ + public function hasRequirement($key) + { + return array_key_exists($key, $this->requirements); + } + + /** + * Sets a requirement for the given key. + * + * @param string $key The key + * @param string $regex The regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $this->sanitizeRequirement($key, $regex); + $this->compiled = null; + + return $this; + } + + /** + * Returns the condition. + * + * @return string The condition + */ + public function getCondition() + { + return $this->condition; + } + + /** + * Sets the condition. + * + * This method implements a fluent interface. + * + * @param string $condition The condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = (string) $condition; + $this->compiled = null; + + return $this; + } + + /** + * Compiles the route. + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + * + * @see RouteCompiler which is responsible for the compilation process + */ + public function compile() + { + if (null !== $this->compiled) { + return $this->compiled; + } + + $class = $this->getOption('compiler_class'); + + return $this->compiled = $class::compile($this); + } + + private function sanitizeRequirement($key, $regex) + { + if (!\is_string($regex)) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" must be a string.', $key)); + } + + if ('' !== $regex && '^' === $regex[0]) { + $regex = (string) substr($regex, 1); // returns false for a single character + } + + if ('$' === substr($regex, -1)) { + $regex = substr($regex, 0, -1); + } + + if ('' === $regex) { + throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.', $key)); + } + + // this is to keep BC and will be removed in a future version + if ('_scheme' === $key) { + @trigger_error('The "_scheme" requirement is deprecated since Symfony 2.2 and will be removed in 3.0. Use the setSchemes() method instead.', E_USER_DEPRECATED); + + $this->setSchemes(explode('|', $regex)); + } elseif ('_method' === $key) { + @trigger_error('The "_method" requirement is deprecated since Symfony 2.2 and will be removed in 3.0. Use the setMethods() method instead.', E_USER_DEPRECATED); + + $this->setMethods(explode('|', $regex)); + } + + return $regex; + } +} diff --git a/vendor/symfony/routing/RouteCollection.php b/vendor/symfony/routing/RouteCollection.php new file mode 100644 index 0000000..2615b1c --- /dev/null +++ b/vendor/symfony/routing/RouteCollection.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * A RouteCollection represents a set of Route instances. + * + * When adding a route at the end of the collection, an existing route + * with the same name is removed first. So there can only be one route + * with a given name. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCollection implements \IteratorAggregate, \Countable +{ + /** + * @var Route[] + */ + private $routes = array(); + + /** + * @var array + */ + private $resources = array(); + + public function __clone() + { + foreach ($this->routes as $name => $route) { + $this->routes[$name] = clone $route; + } + } + + /** + * Gets the current RouteCollection as an Iterator that includes all routes. + * + * It implements \IteratorAggregate. + * + * @see all() + * + * @return \ArrayIterator|Route[] An \ArrayIterator object for iterating over routes + */ + public function getIterator() + { + return new \ArrayIterator($this->routes); + } + + /** + * Gets the number of Routes in this collection. + * + * @return int The number of routes + */ + public function count() + { + return \count($this->routes); + } + + /** + * Adds a route. + * + * @param string $name The route name + * @param Route $route A Route instance + */ + public function add($name, Route $route) + { + unset($this->routes[$name]); + + $this->routes[$name] = $route; + } + + /** + * Returns all routes in this collection. + * + * @return Route[] An array of routes + */ + public function all() + { + return $this->routes; + } + + /** + * Gets a route by name. + * + * @param string $name The route name + * + * @return Route|null A Route instance or null when not found + */ + public function get($name) + { + return isset($this->routes[$name]) ? $this->routes[$name] : null; + } + + /** + * Removes a route or an array of routes by name from the collection. + * + * @param string|string[] $name The route name or an array of route names + */ + public function remove($name) + { + foreach ((array) $name as $n) { + unset($this->routes[$n]); + } + } + + /** + * Adds a route collection at the end of the current set by appending all + * routes of the added collection. + */ + public function addCollection(self $collection) + { + // we need to remove all routes with the same names first because just replacing them + // would not place the new route at the end of the merged array + foreach ($collection->all() as $name => $route) { + unset($this->routes[$name]); + $this->routes[$name] = $route; + } + + $this->resources = array_merge($this->resources, $collection->getResources()); + } + + /** + * Adds a prefix to the path of all child routes. + * + * @param string $prefix An optional prefix to add before each pattern of the route collection + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function addPrefix($prefix, array $defaults = array(), array $requirements = array()) + { + $prefix = trim(trim($prefix), '/'); + + if ('' === $prefix) { + return; + } + + foreach ($this->routes as $route) { + $route->setPath('/'.$prefix.$route->getPath()); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets the host pattern on all routes. + * + * @param string $pattern The pattern + * @param array $defaults An array of default values + * @param array $requirements An array of requirements + */ + public function setHost($pattern, array $defaults = array(), array $requirements = array()) + { + foreach ($this->routes as $route) { + $route->setHost($pattern); + $route->addDefaults($defaults); + $route->addRequirements($requirements); + } + } + + /** + * Sets a condition on all routes. + * + * Existing conditions will be overridden. + * + * @param string $condition The condition + */ + public function setCondition($condition) + { + foreach ($this->routes as $route) { + $route->setCondition($condition); + } + } + + /** + * Adds defaults to all routes. + * + * An existing default value under the same name in a route will be overridden. + * + * @param array $defaults An array of default values + */ + public function addDefaults(array $defaults) + { + if ($defaults) { + foreach ($this->routes as $route) { + $route->addDefaults($defaults); + } + } + } + + /** + * Adds requirements to all routes. + * + * An existing requirement under the same name in a route will be overridden. + * + * @param array $requirements An array of requirements + */ + public function addRequirements(array $requirements) + { + if ($requirements) { + foreach ($this->routes as $route) { + $route->addRequirements($requirements); + } + } + } + + /** + * Adds options to all routes. + * + * An existing option value under the same name in a route will be overridden. + * + * @param array $options An array of options + */ + public function addOptions(array $options) + { + if ($options) { + foreach ($this->routes as $route) { + $route->addOptions($options); + } + } + } + + /** + * Sets the schemes (e.g. 'https') all child routes are restricted to. + * + * @param string|string[] $schemes The scheme or an array of schemes + */ + public function setSchemes($schemes) + { + foreach ($this->routes as $route) { + $route->setSchemes($schemes); + } + } + + /** + * Sets the HTTP methods (e.g. 'POST') all child routes are restricted to. + * + * @param string|string[] $methods The method or an array of methods + */ + public function setMethods($methods) + { + foreach ($this->routes as $route) { + $route->setMethods($methods); + } + } + + /** + * Returns an array of resources loaded to build this collection. + * + * @return ResourceInterface[] An array of resources + */ + public function getResources() + { + return array_unique($this->resources); + } + + /** + * Adds a resource for this collection. + */ + public function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + } +} diff --git a/vendor/symfony/routing/RouteCollectionBuilder.php b/vendor/symfony/routing/RouteCollectionBuilder.php new file mode 100644 index 0000000..34b2abc --- /dev/null +++ b/vendor/symfony/routing/RouteCollectionBuilder.php @@ -0,0 +1,370 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Config\Exception\FileLoaderLoadException; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\Config\Resource\ResourceInterface; + +/** + * Helps add and import routes into a RouteCollection. + * + * @author Ryan Weaver + */ +class RouteCollectionBuilder +{ + /** + * @var Route[]|RouteCollectionBuilder[] + */ + private $routes = array(); + + private $loader; + private $defaults = array(); + private $prefix; + private $host; + private $condition; + private $requirements = array(); + private $options = array(); + private $schemes; + private $methods; + private $resources = array(); + + public function __construct(LoaderInterface $loader = null) + { + $this->loader = $loader; + } + + /** + * Import an external routing resource and returns the RouteCollectionBuilder. + * + * $routes->import('blog.yml', '/blog'); + * + * @param mixed $resource + * @param string|null $prefix + * @param string $type + * + * @return self + * + * @throws FileLoaderLoadException + */ + public function import($resource, $prefix = '/', $type = null) + { + /** @var RouteCollection $collection */ + $collection = $this->load($resource, $type); + + // create a builder from the RouteCollection + $builder = $this->createBuilder(); + foreach ($collection->all() as $name => $route) { + $builder->addRoute($route, $name); + } + + foreach ($collection->getResources() as $resource) { + $builder->addResource($resource); + } + + // mount into this builder + $this->mount($prefix, $builder); + + return $builder; + } + + /** + * Adds a route and returns it for future modification. + * + * @param string $path The route path + * @param string $controller The route's controller + * @param string|null $name The name to give this route + * + * @return Route + */ + public function add($path, $controller, $name = null) + { + $route = new Route($path); + $route->setDefault('_controller', $controller); + $this->addRoute($route, $name); + + return $route; + } + + /** + * Returns a RouteCollectionBuilder that can be configured and then added with mount(). + * + * @return self + */ + public function createBuilder() + { + return new self($this->loader); + } + + /** + * Add a RouteCollectionBuilder. + * + * @param string $prefix + * @param RouteCollectionBuilder $builder + */ + public function mount($prefix, RouteCollectionBuilder $builder) + { + $builder->prefix = trim(trim($prefix), '/'); + $this->routes[] = $builder; + } + + /** + * Adds a Route object to the builder. + * + * @param Route $route + * @param string|null $name + * + * @return $this + */ + public function addRoute(Route $route, $name = null) + { + if (null === $name) { + // used as a flag to know which routes will need a name later + $name = '_unnamed_route_'.spl_object_hash($route); + } + + $this->routes[$name] = $route; + + return $this; + } + + /** + * Sets the host on all embedded routes (unless already set). + * + * @param string $pattern + * + * @return $this + */ + public function setHost($pattern) + { + $this->host = $pattern; + + return $this; + } + + /** + * Sets a condition on all embedded routes (unless already set). + * + * @param string $condition + * + * @return $this + */ + public function setCondition($condition) + { + $this->condition = $condition; + + return $this; + } + + /** + * Sets a default value that will be added to all embedded routes (unless that + * default value is already set). + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setDefault($key, $value) + { + $this->defaults[$key] = $value; + + return $this; + } + + /** + * Sets a requirement that will be added to all embedded routes (unless that + * requirement is already set). + * + * @param string $key + * @param mixed $regex + * + * @return $this + */ + public function setRequirement($key, $regex) + { + $this->requirements[$key] = $regex; + + return $this; + } + + /** + * Sets an opiton that will be added to all embedded routes (unless that + * option is already set). + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function setOption($key, $value) + { + $this->options[$key] = $value; + + return $this; + } + + /** + * Sets the schemes on all embedded routes (unless already set). + * + * @param array|string $schemes + * + * @return $this + */ + public function setSchemes($schemes) + { + $this->schemes = $schemes; + + return $this; + } + + /** + * Sets the methods on all embedded routes (unless already set). + * + * @param array|string $methods + * + * @return $this + */ + public function setMethods($methods) + { + $this->methods = $methods; + + return $this; + } + + /** + * Adds a resource for this collection. + * + * @param ResourceInterface $resource + * + * @return $this + */ + private function addResource(ResourceInterface $resource) + { + $this->resources[] = $resource; + + return $this; + } + + /** + * Creates the final RouteCollection and returns it. + * + * @return RouteCollection + */ + public function build() + { + $routeCollection = new RouteCollection(); + + foreach ($this->routes as $name => $route) { + if ($route instanceof Route) { + $route->setDefaults(array_merge($this->defaults, $route->getDefaults())); + $route->setOptions(array_merge($this->options, $route->getOptions())); + + // we're extra careful here to avoid re-setting deprecated _method and _scheme + foreach ($this->requirements as $key => $val) { + if (!$route->hasRequirement($key)) { + $route->setRequirement($key, $val); + } + } + + if (null !== $this->prefix) { + $route->setPath('/'.$this->prefix.$route->getPath()); + } + + if (!$route->getHost()) { + $route->setHost($this->host); + } + + if (!$route->getCondition()) { + $route->setCondition($this->condition); + } + + if (!$route->getSchemes()) { + $route->setSchemes($this->schemes); + } + + if (!$route->getMethods()) { + $route->setMethods($this->methods); + } + + // auto-generate the route name if it's been marked + if ('_unnamed_route_' === substr($name, 0, 15)) { + $name = $this->generateRouteName($route); + } + + $routeCollection->add($name, $route); + } else { + /* @var self $route */ + $subCollection = $route->build(); + $subCollection->addPrefix($this->prefix); + + $routeCollection->addCollection($subCollection); + } + } + + foreach ($this->resources as $resource) { + $routeCollection->addResource($resource); + } + + return $routeCollection; + } + + /** + * Generates a route name based on details of this route. + * + * @return string + */ + private function generateRouteName(Route $route) + { + $methods = implode('_', $route->getMethods()).'_'; + + $routeName = $methods.$route->getPath(); + $routeName = str_replace(array('/', ':', '|', '-'), '_', $routeName); + $routeName = preg_replace('/[^a-z0-9A-Z_.]+/', '', $routeName); + + // Collapse consecutive underscores down into a single underscore. + $routeName = preg_replace('/_+/', '_', $routeName); + + return $routeName; + } + + /** + * Finds a loader able to load an imported resource and loads it. + * + * @param mixed $resource A resource + * @param string|null $type The resource type or null if unknown + * + * @return RouteCollection + * + * @throws FileLoaderLoadException If no loader is found + */ + private function load($resource, $type = null) + { + if (null === $this->loader) { + throw new \BadMethodCallException('Cannot import other routing resources: you must pass a LoaderInterface when constructing RouteCollectionBuilder.'); + } + + if ($this->loader->supports($resource, $type)) { + return $this->loader->load($resource, $type); + } + + if (null === $resolver = $this->loader->getResolver()) { + throw new FileLoaderLoadException($resource); + } + + if (false === $loader = $resolver->resolve($resource, $type)) { + throw new FileLoaderLoadException($resource); + } + + return $loader->load($resource, $type); + } +} diff --git a/vendor/symfony/routing/RouteCompiler.php b/vendor/symfony/routing/RouteCompiler.php new file mode 100644 index 0000000..bebd82b --- /dev/null +++ b/vendor/symfony/routing/RouteCompiler.php @@ -0,0 +1,243 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompiler compiles Route instances to CompiledRoute instances. + * + * @author Fabien Potencier + * @author Tobias Schultze + */ +class RouteCompiler implements RouteCompilerInterface +{ + const REGEX_DELIMITER = '#'; + + /** + * This string defines the characters that are automatically considered separators in front of + * optional placeholders (with default and no static text following). Such a single separator + * can be left out together with the optional placeholder from matching and generating URLs. + */ + const SEPARATORS = '/,;.:-_~+*=@|'; + + /** + * The maximum supported length of a PCRE subpattern name + * http://pcre.org/current/doc/html/pcre2pattern.html#SEC16. + * + * @internal + */ + const VARIABLE_MAXIMUM_LENGTH = 32; + + /** + * {@inheritdoc} + * + * @throws \LogicException If a variable is referenced more than once + * @throws \DomainException if a variable name starts with a digit or if it is too long to be successfully used as + * a PCRE subpattern + */ + public static function compile(Route $route) + { + $hostVariables = array(); + $variables = array(); + $hostRegex = null; + $hostTokens = array(); + + if ('' !== $host = $route->getHost()) { + $result = self::compilePattern($route, $host, true); + + $hostVariables = $result['variables']; + $variables = $hostVariables; + + $hostTokens = $result['tokens']; + $hostRegex = $result['regex']; + } + + $path = $route->getPath(); + + $result = self::compilePattern($route, $path, false); + + $staticPrefix = $result['staticPrefix']; + + $pathVariables = $result['variables']; + $variables = array_merge($variables, $pathVariables); + + $tokens = $result['tokens']; + $regex = $result['regex']; + + return new CompiledRoute( + $staticPrefix, + $regex, + $tokens, + $pathVariables, + $hostRegex, + $hostTokens, + $hostVariables, + array_unique($variables) + ); + } + + private static function compilePattern(Route $route, $pattern, $isHost) + { + $tokens = array(); + $variables = array(); + $matches = array(); + $pos = 0; + $defaultSeparator = $isHost ? '.' : '/'; + + // Match all variables enclosed in "{}" and iterate over them. But we only want to match the innermost variable + // in case of nested "{}", e.g. {foo{bar}}. This in ensured because \w does not match "{" or "}" itself. + preg_match_all('#\{\w+\}#', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); + foreach ($matches as $match) { + $varName = substr($match[0][0], 1, -1); + // get all static text preceding the current variable + $precedingText = substr($pattern, $pos, $match[0][1] - $pos); + $pos = $match[0][1] + \strlen($match[0][0]); + $precedingChar = \strlen($precedingText) > 0 ? substr($precedingText, -1) : ''; + $isSeparator = '' !== $precedingChar && false !== strpos(static::SEPARATORS, $precedingChar); + + // A PCRE subpattern name must start with a non-digit. Also a PHP variable cannot start with a digit so the + // variable would not be usable as a Controller action argument. + if (preg_match('/^\d/', $varName)) { + throw new \DomainException(sprintf('Variable name "%s" cannot start with a digit in route pattern "%s". Please use a different name.', $varName, $pattern)); + } + if (\in_array($varName, $variables)) { + throw new \LogicException(sprintf('Route pattern "%s" cannot reference variable name "%s" more than once.', $pattern, $varName)); + } + + if (\strlen($varName) > self::VARIABLE_MAXIMUM_LENGTH) { + throw new \DomainException(sprintf('Variable name "%s" cannot be longer than %s characters in route pattern "%s". Please use a shorter name.', $varName, self::VARIABLE_MAXIMUM_LENGTH, $pattern)); + } + + if ($isSeparator && \strlen($precedingText) > 1) { + $tokens[] = array('text', substr($precedingText, 0, -1)); + } elseif (!$isSeparator && \strlen($precedingText) > 0) { + $tokens[] = array('text', $precedingText); + } + + $regexp = $route->getRequirement($varName); + if (null === $regexp) { + $followingPattern = (string) substr($pattern, $pos); + // Find the next static character after the variable that functions as a separator. By default, this separator and '/' + // are disallowed for the variable. This default requirement makes sure that optional variables can be matched at all + // and that the generating-matching-combination of URLs unambiguous, i.e. the params used for generating the URL are + // the same that will be matched. Example: new Route('/{page}.{_format}', array('_format' => 'html')) + // If {page} would also match the separating dot, {_format} would never match as {page} will eagerly consume everything. + // Also even if {_format} was not optional the requirement prevents that {page} matches something that was originally + // part of {_format} when generating the URL, e.g. _format = 'mobile.html'. + $nextSeparator = self::findNextSeparator($followingPattern); + $regexp = sprintf( + '[^%s%s]+', + preg_quote($defaultSeparator, self::REGEX_DELIMITER), + $defaultSeparator !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '' + ); + if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) { + // When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive + // quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns. + // Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow + // after it. This optimization cannot be applied when the next char is no real separator or when the next variable is + // directly adjacent, e.g. '/{x}{y}'. + $regexp .= '+'; + } + } + + $tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName); + $variables[] = $varName; + } + + if ($pos < \strlen($pattern)) { + $tokens[] = array('text', substr($pattern, $pos)); + } + + // find the first optional token + $firstOptional = PHP_INT_MAX; + if (!$isHost) { + for ($i = \count($tokens) - 1; $i >= 0; --$i) { + $token = $tokens[$i]; + if ('variable' === $token[0] && $route->hasDefault($token[3])) { + $firstOptional = $i; + } else { + break; + } + } + } + + // compute the matching regexp + $regexp = ''; + for ($i = 0, $nbToken = \count($tokens); $i < $nbToken; ++$i) { + $regexp .= self::computeRegexp($tokens, $i, $firstOptional); + } + + return array( + 'staticPrefix' => 'text' === $tokens[0][0] ? $tokens[0][1] : '', + 'regex' => self::REGEX_DELIMITER.'^'.$regexp.'$'.self::REGEX_DELIMITER.'sD'.($isHost ? 'i' : ''), + 'tokens' => array_reverse($tokens), + 'variables' => $variables, + ); + } + + /** + * Returns the next static character in the Route pattern that will serve as a separator. + * + * @param string $pattern The route pattern + * + * @return string The next static character that functions as separator (or empty string when none available) + */ + private static function findNextSeparator($pattern) + { + if ('' == $pattern) { + // return empty string if pattern is empty or false (false which can be returned by substr) + return ''; + } + // first remove all placeholders from the pattern so we can find the next real static character + $pattern = preg_replace('#\{\w+\}#', '', $pattern); + + return isset($pattern[0]) && false !== strpos(static::SEPARATORS, $pattern[0]) ? $pattern[0] : ''; + } + + /** + * Computes the regexp used to match a specific token. It can be static text or a subpattern. + * + * @param array $tokens The route tokens + * @param int $index The index of the current token + * @param int $firstOptional The index of the first optional token + * + * @return string The regexp pattern for a single token + */ + private static function computeRegexp(array $tokens, $index, $firstOptional) + { + $token = $tokens[$index]; + if ('text' === $token[0]) { + // Text tokens + return preg_quote($token[1], self::REGEX_DELIMITER); + } else { + // Variable tokens + if (0 === $index && 0 === $firstOptional) { + // When the only token is an optional variable token, the separator is required + return sprintf('%s(?P<%s>%s)?', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + } else { + $regexp = sprintf('%s(?P<%s>%s)', preg_quote($token[1], self::REGEX_DELIMITER), $token[3], $token[2]); + if ($index >= $firstOptional) { + // Enclose each optional token in a subpattern to make it optional. + // "?:" means it is non-capturing, i.e. the portion of the subject string that + // matched the optional subpattern is not passed back. + $regexp = "(?:$regexp"; + $nbTokens = \count($tokens); + if ($nbTokens - 1 == $index) { + // Close the optional subpatterns + $regexp .= str_repeat(')?', $nbTokens - $firstOptional - (0 === $firstOptional ? 1 : 0)); + } + } + + return $regexp; + } + } + } +} diff --git a/vendor/symfony/routing/RouteCompilerInterface.php b/vendor/symfony/routing/RouteCompilerInterface.php new file mode 100644 index 0000000..ddfa7ca --- /dev/null +++ b/vendor/symfony/routing/RouteCompilerInterface.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +/** + * RouteCompilerInterface is the interface that all RouteCompiler classes must implement. + * + * @author Fabien Potencier + */ +interface RouteCompilerInterface +{ + /** + * Compiles the current route instance. + * + * @return CompiledRoute A CompiledRoute instance + * + * @throws \LogicException If the Route cannot be compiled because the + * path or host pattern is invalid + */ + public static function compile(Route $route); +} diff --git a/vendor/symfony/routing/Router.php b/vendor/symfony/routing/Router.php new file mode 100644 index 0000000..6de154d --- /dev/null +++ b/vendor/symfony/routing/Router.php @@ -0,0 +1,400 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Psr\Log\LoggerInterface; +use Symfony\Component\Config\ConfigCacheFactory; +use Symfony\Component\Config\ConfigCacheFactoryInterface; +use Symfony\Component\Config\ConfigCacheInterface; +use Symfony\Component\Config\Loader\LoaderInterface; +use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Generator\ConfigurableRequirementsInterface; +use Symfony\Component\Routing\Generator\Dumper\GeneratorDumperInterface; +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\Dumper\MatcherDumperInterface; +use Symfony\Component\Routing\Matcher\RequestMatcherInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * The Router class is an example of the integration of all pieces of the + * routing system for easier use. + * + * @author Fabien Potencier + */ +class Router implements RouterInterface, RequestMatcherInterface +{ + /** + * @var UrlMatcherInterface|null + */ + protected $matcher; + + /** + * @var UrlGeneratorInterface|null + */ + protected $generator; + + /** + * @var RequestContext + */ + protected $context; + + /** + * @var LoaderInterface + */ + protected $loader; + + /** + * @var RouteCollection|null + */ + protected $collection; + + /** + * @var mixed + */ + protected $resource; + + /** + * @var array + */ + protected $options = array(); + + /** + * @var LoggerInterface|null + */ + protected $logger; + + /** + * @var ConfigCacheFactoryInterface|null + */ + private $configCacheFactory; + + /** + * @var ExpressionFunctionProviderInterface[] + */ + private $expressionLanguageProviders = array(); + + /** + * @param LoaderInterface $loader A LoaderInterface instance + * @param mixed $resource The main resource to load + * @param array $options An array of options + * @param RequestContext $context The context + * @param LoggerInterface $logger A logger instance + */ + public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, LoggerInterface $logger = null) + { + $this->loader = $loader; + $this->resource = $resource; + $this->logger = $logger; + $this->context = $context ?: new RequestContext(); + $this->setOptions($options); + } + + /** + * Sets options. + * + * Available options: + * + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * * generator_class: The name of a UrlGeneratorInterface implementation + * * generator_base_class: The base class for the dumped generator class + * * generator_cache_class: The class name for the dumped generator class + * * generator_dumper_class: The name of a GeneratorDumperInterface implementation + * * matcher_class: The name of a UrlMatcherInterface implementation + * * matcher_base_class: The base class for the dumped matcher class + * * matcher_dumper_class: The class name for the dumped matcher class + * * matcher_cache_class: The name of a MatcherDumperInterface implementation + * * resource_type: Type hint for the main resource (optional) + * * strict_requirements: Configure strict requirement checking for generators + * implementing ConfigurableRequirementsInterface (default is true) + * + * @param array $options An array of options + * + * @throws \InvalidArgumentException When unsupported option is provided + */ + public function setOptions(array $options) + { + $this->options = array( + 'cache_dir' => null, + 'debug' => false, + 'generator_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_base_class' => 'Symfony\\Component\\Routing\\Generator\\UrlGenerator', + 'generator_dumper_class' => 'Symfony\\Component\\Routing\\Generator\\Dumper\\PhpGeneratorDumper', + 'generator_cache_class' => 'ProjectUrlGenerator', + 'matcher_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_base_class' => 'Symfony\\Component\\Routing\\Matcher\\UrlMatcher', + 'matcher_dumper_class' => 'Symfony\\Component\\Routing\\Matcher\\Dumper\\PhpMatcherDumper', + 'matcher_cache_class' => 'ProjectUrlMatcher', + 'resource_type' => null, + 'strict_requirements' => true, + ); + + // check option names and live merge, if errors are encountered Exception will be thrown + $invalid = array(); + foreach ($options as $key => $value) { + if (array_key_exists($key, $this->options)) { + $this->options[$key] = $value; + } else { + $invalid[] = $key; + } + } + + if ($invalid) { + throw new \InvalidArgumentException(sprintf('The Router does not support the following options: "%s".', implode('", "', $invalid))); + } + } + + /** + * Sets an option. + * + * @param string $key The key + * @param mixed $value The value + * + * @throws \InvalidArgumentException + */ + public function setOption($key, $value) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + $this->options[$key] = $value; + } + + /** + * Gets an option value. + * + * @param string $key The key + * + * @return mixed The value + * + * @throws \InvalidArgumentException + */ + public function getOption($key) + { + if (!array_key_exists($key, $this->options)) { + throw new \InvalidArgumentException(sprintf('The Router does not support the "%s" option.', $key)); + } + + return $this->options[$key]; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollection() + { + if (null === $this->collection) { + $this->collection = $this->loader->load($this->resource, $this->options['resource_type']); + } + + return $this->collection; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + + if (null !== $this->matcher) { + $this->getMatcher()->setContext($context); + } + if (null !== $this->generator) { + $this->getGenerator()->setContext($context); + } + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * Sets the ConfigCache factory to use. + */ + public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory) + { + $this->configCacheFactory = $configCacheFactory; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + return $this->getMatcher()->match($pathinfo); + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + $matcher = $this->getMatcher(); + if (!$matcher instanceof RequestMatcherInterface) { + // fallback to the default UrlMatcherInterface + return $matcher->match($request->getPathInfo()); + } + + return $matcher->matchRequest($request); + } + + /** + * Gets the UrlMatcher instance associated with this Router. + * + * @return UrlMatcherInterface A UrlMatcherInterface instance + */ + public function getMatcher() + { + if (null !== $this->matcher) { + return $this->matcher; + } + + if (null === $this->options['cache_dir'] || null === $this->options['matcher_cache_class']) { + $this->matcher = new $this->options['matcher_class']($this->getRouteCollection(), $this->context); + if (method_exists($this->matcher, 'addExpressionLanguageProvider')) { + foreach ($this->expressionLanguageProviders as $provider) { + $this->matcher->addExpressionLanguageProvider($provider); + } + } + + return $this->matcher; + } + + $class = $this->options['matcher_cache_class']; + $baseClass = $this->options['matcher_base_class']; + $expressionLanguageProviders = $this->expressionLanguageProviders; + $that = $this; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0. + + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$class.'.php', + function (ConfigCacheInterface $cache) use ($that, $class, $baseClass, $expressionLanguageProviders) { + $dumper = $that->getMatcherDumperInstance(); + if (method_exists($dumper, 'addExpressionLanguageProvider')) { + foreach ($expressionLanguageProviders as $provider) { + $dumper->addExpressionLanguageProvider($provider); + } + } + + $options = array( + 'class' => $class, + 'base_class' => $baseClass, + ); + + $cache->write($dumper->dump($options), $that->getRouteCollection()->getResources()); + } + ); + + require_once $cache->getPath(); + + return $this->matcher = new $class($this->context); + } + + /** + * Gets the UrlGenerator instance associated with this Router. + * + * @return UrlGeneratorInterface A UrlGeneratorInterface instance + */ + public function getGenerator() + { + if (null !== $this->generator) { + return $this->generator; + } + + if (null === $this->options['cache_dir'] || null === $this->options['generator_cache_class']) { + $this->generator = new $this->options['generator_class']($this->getRouteCollection(), $this->context, $this->logger); + } else { + $class = $this->options['generator_cache_class']; + $baseClass = $this->options['generator_base_class']; + $that = $this; // required for PHP 5.3 where "$this" cannot be use()d in anonymous functions. Change in Symfony 3.0. + $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$class.'.php', + function (ConfigCacheInterface $cache) use ($that, $class, $baseClass) { + $dumper = $that->getGeneratorDumperInstance(); + + $options = array( + 'class' => $class, + 'base_class' => $baseClass, + ); + + $cache->write($dumper->dump($options), $that->getRouteCollection()->getResources()); + } + ); + + require_once $cache->getPath(); + + $this->generator = new $class($this->context, $this->logger); + } + + if ($this->generator instanceof ConfigurableRequirementsInterface) { + $this->generator->setStrictRequirements($this->options['strict_requirements']); + } + + return $this->generator; + } + + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) + { + $this->expressionLanguageProviders[] = $provider; + } + + /** + * This method is public because it needs to be callable from a closure in PHP 5.3. It should be converted back to protected in 3.0. + * + * @internal + * + * @return GeneratorDumperInterface + */ + public function getGeneratorDumperInstance() + { + return new $this->options['generator_dumper_class']($this->getRouteCollection()); + } + + /** + * This method is public because it needs to be callable from a closure in PHP 5.3. It should be converted back to protected in 3.0. + * + * @internal + * + * @return MatcherDumperInterface + */ + public function getMatcherDumperInstance() + { + return new $this->options['matcher_dumper_class']($this->getRouteCollection()); + } + + /** + * Provides the ConfigCache factory implementation, falling back to a + * default implementation if necessary. + * + * @return ConfigCacheFactoryInterface + */ + private function getConfigCacheFactory() + { + if (null === $this->configCacheFactory) { + $this->configCacheFactory = new ConfigCacheFactory($this->options['debug']); + } + + return $this->configCacheFactory; + } +} diff --git a/vendor/symfony/routing/RouterInterface.php b/vendor/symfony/routing/RouterInterface.php new file mode 100644 index 0000000..a10ae34 --- /dev/null +++ b/vendor/symfony/routing/RouterInterface.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Routing; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Routing\Matcher\UrlMatcherInterface; + +/** + * RouterInterface is the interface that all Router classes must implement. + * + * This interface is the concatenation of UrlMatcherInterface and UrlGeneratorInterface. + * + * @author Fabien Potencier + */ +interface RouterInterface extends UrlMatcherInterface, UrlGeneratorInterface +{ + /** + * Gets the RouteCollection instance associated with this Router. + * + * @return RouteCollection A RouteCollection instance + */ + public function getRouteCollection(); +} diff --git a/vendor/symfony/routing/composer.json b/vendor/symfony/routing/composer.json new file mode 100644 index 0000000..9638491 --- /dev/null +++ b/vendor/symfony/routing/composer.json @@ -0,0 +1,52 @@ +{ + "name": "symfony/routing", + "type": "library", + "description": "Symfony Routing Component", + "keywords": ["routing", "router", "URL", "URI"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "symfony/config": "~2.7|~3.0.0", + "symfony/http-foundation": "~2.3|~3.0.0", + "symfony/yaml": "^2.0.5|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "doctrine/annotations": "~1.0", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/config": "<2.7" + }, + "suggest": { + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/yaml": "For using the YAML loader", + "symfony/expression-language": "For using expression matching", + "doctrine/annotations": "For using the annotation loader", + "symfony/dependency-injection": "For loading routes from a service" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Routing\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/twig-bridge/AppVariable.php b/vendor/symfony/twig-bridge/AppVariable.php new file mode 100644 index 0000000..5f8be80 --- /dev/null +++ b/vendor/symfony/twig-bridge/AppVariable.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig; + +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\SecurityContext; + +/** + * Exposes some Symfony parameters and services as an "app" global variable. + * + * @author Fabien Potencier + */ +class AppVariable +{ + private $container; + private $tokenStorage; + private $requestStack; + private $environment; + private $debug; + + /** + * @deprecated since version 2.7, to be removed in 3.0. + */ + public function setContainer(ContainerInterface $container) + { + $this->container = $container; + } + + public function setTokenStorage(TokenStorageInterface $tokenStorage) + { + $this->tokenStorage = $tokenStorage; + } + + public function setRequestStack(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + } + + public function setEnvironment($environment) + { + $this->environment = $environment; + } + + public function setDebug($debug) + { + $this->debug = (bool) $debug; + } + + /** + * Returns the security context service. + * + * @deprecated since version 2.6, to be removed in 3.0. + * + * @return SecurityContext|null The security context + */ + public function getSecurity() + { + @trigger_error('The "app.security" variable is deprecated since Symfony 2.6 and will be removed in 3.0.', E_USER_DEPRECATED); + + if (null === $this->container) { + throw new \RuntimeException('The "app.security" variable is not available.'); + } + + if ($this->container->has('security.context')) { + return $this->container->get('security.context'); + } + } + + /** + * Returns the current user. + * + * @return mixed + * + * @see TokenInterface::getUser() + */ + public function getUser() + { + if (null === $this->tokenStorage) { + if (null === $this->container) { + throw new \RuntimeException('The "app.user" variable is not available.'); + } elseif (!$this->container->has('security.context')) { + return; + } + + $this->tokenStorage = $this->container->get('security.context'); + } + + if (!$token = $this->tokenStorage->getToken()) { + return; + } + + $user = $token->getUser(); + if (\is_object($user)) { + return $user; + } + } + + /** + * Returns the current request. + * + * @return Request|null The HTTP request object + */ + public function getRequest() + { + if (null === $this->requestStack) { + if (null === $this->container) { + throw new \RuntimeException('The "app.request" variable is not available.'); + } + + $this->requestStack = $this->container->get('request_stack'); + } + + return $this->requestStack->getCurrentRequest(); + } + + /** + * Returns the current session. + * + * @return Session|null The session + */ + public function getSession() + { + if (null === $this->requestStack && null === $this->container) { + throw new \RuntimeException('The "app.session" variable is not available.'); + } + + if ($request = $this->getRequest()) { + return $request->getSession(); + } + } + + /** + * Returns the current app environment. + * + * @return string The current environment string (e.g 'dev') + */ + public function getEnvironment() + { + if (null === $this->environment) { + throw new \RuntimeException('The "app.environment" variable is not available.'); + } + + return $this->environment; + } + + /** + * Returns the current app debug mode. + * + * @return bool The current debug mode + */ + public function getDebug() + { + if (null === $this->debug) { + throw new \RuntimeException('The "app.debug" variable is not available.'); + } + + return $this->debug; + } +} diff --git a/vendor/symfony/twig-bridge/Command/DebugCommand.php b/vendor/symfony/twig-bridge/Command/DebugCommand.php new file mode 100644 index 0000000..58b429c --- /dev/null +++ b/vendor/symfony/twig-bridge/Command/DebugCommand.php @@ -0,0 +1,216 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Twig\Environment; + +/** + * Lists twig functions, filters, globals and tests present in the current project. + * + * @author Jordi Boggiano + */ +class DebugCommand extends Command +{ + private $twig; + + /** + * {@inheritdoc} + */ + public function __construct($name = 'debug:twig') + { + parent::__construct($name); + } + + public function setTwigEnvironment(Environment $twig) + { + $this->twig = $twig; + } + + /** + * @return Environment $twig + */ + protected function getTwigEnvironment() + { + return $this->twig; + } + + protected function configure() + { + $this + ->setDefinition(array( + new InputArgument('filter', InputArgument::OPTIONAL, 'Show details for all entries matching this filter'), + new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (text or json)', 'text'), + )) + ->setDescription('Shows a list of twig functions, filters, globals and tests') + ->setHelp(<<<'EOF' +The %command.name% command outputs a list of twig functions, +filters, globals and tests. Output can be filtered with an optional argument. + + php %command.full_name% + +The command lists all functions, filters, etc. + + php %command.full_name% date + +The command lists everything that contains the word date. + + php %command.full_name% --format=json + +The command lists everything in a machine readable json format. +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + $twig = $this->getTwigEnvironment(); + + if (null === $twig) { + $io->error('The Twig environment needs to be set.'); + + return 1; + } + + $types = array('functions', 'filters', 'tests', 'globals'); + + if ('json' === $input->getOption('format')) { + $data = array(); + foreach ($types as $type) { + foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) { + $data[$type][$name] = $this->getMetadata($type, $entity); + } + } + $data['tests'] = array_keys($data['tests']); + $io->writeln(json_encode($data)); + + return 0; + } + + $filter = $input->getArgument('filter'); + + foreach ($types as $index => $type) { + $items = array(); + foreach ($twig->{'get'.ucfirst($type)}() as $name => $entity) { + if (!$filter || false !== strpos($name, $filter)) { + $items[$name] = $name.$this->getPrettyMetadata($type, $entity); + } + } + + if (!$items) { + continue; + } + + $io->section(ucfirst($type)); + + ksort($items); + $io->listing($items); + } + + return 0; + } + + private function getMetadata($type, $entity) + { + if ('globals' === $type) { + return $entity; + } + if ('tests' === $type) { + return; + } + if ('functions' === $type || 'filters' === $type) { + $cb = $entity->getCallable(); + if (null === $cb) { + return; + } + if (\is_array($cb)) { + if (!method_exists($cb[0], $cb[1])) { + return; + } + $refl = new \ReflectionMethod($cb[0], $cb[1]); + } elseif (\is_object($cb) && method_exists($cb, '__invoke')) { + $refl = new \ReflectionMethod($cb, '__invoke'); + } elseif (\function_exists($cb)) { + $refl = new \ReflectionFunction($cb); + } elseif (\is_string($cb) && preg_match('{^(.+)::(.+)$}', $cb, $m) && method_exists($m[1], $m[2])) { + $refl = new \ReflectionMethod($m[1], $m[2]); + } else { + throw new \UnexpectedValueException('Unsupported callback type'); + } + + $args = $refl->getParameters(); + + // filter out context/environment args + if ($entity->needsEnvironment()) { + array_shift($args); + } + if ($entity->needsContext()) { + array_shift($args); + } + + if ('filters' === $type) { + // remove the value the filter is applied on + array_shift($args); + } + + // format args + $args = array_map(function ($param) { + if ($param->isDefaultValueAvailable()) { + return $param->getName().' = '.json_encode($param->getDefaultValue()); + } + + return $param->getName(); + }, $args); + + return $args; + } + } + + private function getPrettyMetadata($type, $entity) + { + if ('tests' === $type) { + return ''; + } + + try { + $meta = $this->getMetadata($type, $entity); + if (null === $meta) { + return '(unknown?)'; + } + } catch (\UnexpectedValueException $e) { + return ' '.$e->getMessage().''; + } + + if ('globals' === $type) { + if (\is_object($meta)) { + return ' = object('.\get_class($meta).')'; + } + + return ' = '.substr(@json_encode($meta), 0, 50); + } + + if ('functions' === $type) { + return '('.implode(', ', $meta).')'; + } + + if ('filters' === $type) { + return $meta ? '('.implode(', ', $meta).')' : ''; + } + } +} diff --git a/vendor/symfony/twig-bridge/Command/LintCommand.php b/vendor/symfony/twig-bridge/Command/LintCommand.php new file mode 100644 index 0000000..3a0fffc --- /dev/null +++ b/vendor/symfony/twig-bridge/Command/LintCommand.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\Finder\Finder; +use Twig\Environment; +use Twig\Error\Error; +use Twig\Loader\ArrayLoader; +use Twig\Source; + +/** + * Command that will validate your template syntax and output encountered errors. + * + * @author Marc Weistroff + * @author Jérôme Tamarelle + */ +class LintCommand extends Command +{ + private $twig; + + /** + * {@inheritdoc} + */ + public function __construct($name = 'lint:twig') + { + parent::__construct($name); + } + + public function setTwigEnvironment(Environment $twig) + { + $this->twig = $twig; + } + + /** + * @return Environment $twig + */ + protected function getTwigEnvironment() + { + return $this->twig; + } + + protected function configure() + { + $this + ->setAliases(array('twig:lint')) + ->setDescription('Lints a template and outputs encountered errors') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt') + ->addArgument('filename', InputArgument::IS_ARRAY) + ->setHelp(<<<'EOF' +The %command.name% command lints a template and outputs to STDOUT +the first encountered syntax error. + +You can validate the syntax of contents passed from STDIN: + + cat filename | php %command.full_name% + +Or the syntax of a file: + + php %command.full_name% filename + +Or of a whole directory: + + php %command.full_name% dirname + php %command.full_name% dirname --format=json + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $io = new SymfonyStyle($input, $output); + + if (false !== strpos($input->getFirstArgument(), ':l')) { + $io->caution('The use of "twig:lint" command is deprecated since version 2.7 and will be removed in 3.0. Use the "lint:twig" instead.'); + } + + if (null === $twig = $this->getTwigEnvironment()) { + $io->error('The Twig environment needs to be set.'); + + return 1; + } + + $filenames = $input->getArgument('filename'); + + if (0 === \count($filenames)) { + if (0 !== ftell(STDIN)) { + throw new \RuntimeException('Please provide a filename or pipe template content to STDIN.'); + } + + $template = ''; + while (!feof(STDIN)) { + $template .= fread(STDIN, 1024); + } + + return $this->display($input, $output, $io, array($this->validate($twig, $template, uniqid('sf_', true)))); + } + + $filesInfo = $this->getFilesInfo($twig, $filenames); + + return $this->display($input, $output, $io, $filesInfo); + } + + private function getFilesInfo(Environment $twig, array $filenames) + { + $filesInfo = array(); + foreach ($filenames as $filename) { + foreach ($this->findFiles($filename) as $file) { + $filesInfo[] = $this->validate($twig, file_get_contents($file), $file); + } + } + + return $filesInfo; + } + + protected function findFiles($filename) + { + if (is_file($filename)) { + return array($filename); + } elseif (is_dir($filename)) { + return Finder::create()->files()->in($filename)->name('*.twig'); + } + + throw new \RuntimeException(sprintf('File or directory "%s" is not readable', $filename)); + } + + private function validate(Environment $twig, $template, $file) + { + $realLoader = $twig->getLoader(); + try { + $temporaryLoader = new ArrayLoader(array((string) $file => $template)); + $twig->setLoader($temporaryLoader); + $nodeTree = $twig->parse($twig->tokenize(new Source($template, (string) $file))); + $twig->compile($nodeTree); + $twig->setLoader($realLoader); + } catch (Error $e) { + $twig->setLoader($realLoader); + + return array('template' => $template, 'file' => $file, 'valid' => false, 'exception' => $e); + } + + return array('template' => $template, 'file' => $file, 'valid' => true); + } + + private function display(InputInterface $input, OutputInterface $output, SymfonyStyle $io, $files) + { + switch ($input->getOption('format')) { + case 'txt': + return $this->displayTxt($output, $io, $files); + case 'json': + return $this->displayJson($output, $files); + default: + throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $input->getOption('format'))); + } + } + + private function displayTxt(OutputInterface $output, SymfonyStyle $io, $filesInfo) + { + $errors = 0; + + foreach ($filesInfo as $info) { + if ($info['valid'] && $output->isVerbose()) { + $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : '')); + } elseif (!$info['valid']) { + ++$errors; + $this->renderException($io, $info['template'], $info['exception'], $info['file']); + } + } + + if (0 === $errors) { + $io->success(sprintf('All %d Twig files contain valid syntax.', \count($filesInfo))); + } else { + $io->warning(sprintf('%d Twig files have valid syntax and %d contain errors.', \count($filesInfo) - $errors, $errors)); + } + + return min($errors, 1); + } + + private function displayJson(OutputInterface $output, $filesInfo) + { + $errors = 0; + + array_walk($filesInfo, function (&$v) use (&$errors) { + $v['file'] = (string) $v['file']; + unset($v['template']); + if (!$v['valid']) { + $v['message'] = $v['exception']->getMessage(); + unset($v['exception']); + ++$errors; + } + }); + + $output->writeln(json_encode($filesInfo, \defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES : 0)); + + return min($errors, 1); + } + + private function renderException(OutputInterface $output, $template, Error $exception, $file = null) + { + $line = $exception->getTemplateLine(); + + if ($file) { + $output->text(sprintf(' ERROR in %s (line %s)', $file, $line)); + } else { + $output->text(sprintf(' ERROR (line %s)', $line)); + } + + foreach ($this->getContext($template, $line) as $lineNumber => $code) { + $output->text(sprintf( + '%s %-6s %s', + $lineNumber === $line ? ' >> ' : ' ', + $lineNumber, + $code + )); + if ($lineNumber === $line) { + $output->text(sprintf(' >> %s ', $exception->getRawMessage())); + } + } + } + + private function getContext($template, $line, $context = 3) + { + $lines = explode("\n", $template); + + $position = max(0, $line - $context); + $max = min(\count($lines), $line - 1 + $context); + + $result = array(); + while ($position < $max) { + $result[$position + 1] = $lines[$position]; + ++$position; + } + + return $result; + } +} diff --git a/vendor/symfony/twig-bridge/DataCollector/TwigDataCollector.php b/vendor/symfony/twig-bridge/DataCollector/TwigDataCollector.php new file mode 100644 index 0000000..4496a54 --- /dev/null +++ b/vendor/symfony/twig-bridge/DataCollector/TwigDataCollector.php @@ -0,0 +1,158 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Twig\Markup; +use Twig\Profiler\Dumper\HtmlDumper; +use Twig\Profiler\Profile; + +/** + * TwigDataCollector. + * + * @author Fabien Potencier + */ +class TwigDataCollector extends DataCollector implements LateDataCollectorInterface +{ + private $profile; + private $computed; + + public function __construct(Profile $profile) + { + $this->profile = $profile; + } + + /** + * {@inheritdoc} + */ + public function collect(Request $request, Response $response, \Exception $exception = null) + { + } + + /** + * {@inheritdoc} + */ + public function lateCollect() + { + $this->data['profile'] = serialize($this->profile); + } + + public function getTime() + { + return $this->getProfile()->getDuration() * 1000; + } + + public function getTemplateCount() + { + return $this->getComputedData('template_count'); + } + + public function getTemplates() + { + return $this->getComputedData('templates'); + } + + public function getBlockCount() + { + return $this->getComputedData('block_count'); + } + + public function getMacroCount() + { + return $this->getComputedData('macro_count'); + } + + public function getHtmlCallGraph() + { + $dumper = new HtmlDumper(); + $dump = $dumper->dump($this->getProfile()); + + // needed to remove the hardcoded CSS styles + $dump = str_replace(array( + '', + '', + '', + ), array( + '', + '', + '', + ), $dump); + + return new Markup($dump, 'UTF-8'); + } + + public function getProfile() + { + if (null === $this->profile) { + $this->profile = unserialize($this->data['profile']); + } + + return $this->profile; + } + + private function getComputedData($index) + { + if (null === $this->computed) { + $this->computed = $this->computeData($this->getProfile()); + } + + return $this->computed[$index]; + } + + private function computeData(Profile $profile) + { + $data = array( + 'template_count' => 0, + 'block_count' => 0, + 'macro_count' => 0, + ); + + $templates = array(); + foreach ($profile as $p) { + $d = $this->computeData($p); + + $data['template_count'] += ($p->isTemplate() ? 1 : 0) + $d['template_count']; + $data['block_count'] += ($p->isBlock() ? 1 : 0) + $d['block_count']; + $data['macro_count'] += ($p->isMacro() ? 1 : 0) + $d['macro_count']; + + if ($p->isTemplate()) { + if (!isset($templates[$p->getTemplate()])) { + $templates[$p->getTemplate()] = 1; + } else { + ++$templates[$p->getTemplate()]; + } + } + + foreach ($d['templates'] as $template => $count) { + if (!isset($templates[$template])) { + $templates[$template] = $count; + } else { + $templates[$template] += $count; + } + } + } + $data['templates'] = $templates; + + return $data; + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'twig'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/AssetExtension.php b/vendor/symfony/twig-bridge/Extension/AssetExtension.php new file mode 100644 index 0000000..271d71a --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/AssetExtension.php @@ -0,0 +1,151 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Asset\Packages; +use Symfony\Component\Asset\VersionStrategy\StaticVersionStrategy; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * Twig extension for the Symfony Asset component. + * + * @author Fabien Potencier + */ +class AssetExtension extends AbstractExtension +{ + private $packages; + private $foundationExtension; + + /** + * Passing an HttpFoundationExtension instance as a second argument must not be relied on + * as it's only there to maintain BC with older Symfony version. It will be removed in 3.0. + */ + public function __construct(Packages $packages, HttpFoundationExtension $foundationExtension = null) + { + $this->packages = $packages; + $this->foundationExtension = $foundationExtension; + } + + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return array( + new TwigFunction('asset', array($this, 'getAssetUrl')), + new TwigFunction('asset_version', array($this, 'getAssetVersion')), + new TwigFunction('assets_version', array($this, 'getAssetsVersion'), array('deprecated' => true, 'alternative' => 'asset_version')), + ); + } + + /** + * Returns the public url/path of an asset. + * + * If the package used to generate the path is an instance of + * UrlPackage, you will always get a URL and not a path. + * + * @param string $path A public path + * @param string $packageName The name of the asset package to use + * + * @return string The public path of the asset + */ + public function getAssetUrl($path, $packageName = null, $absolute = false, $version = null) + { + // BC layer to be removed in 3.0 + if (2 < $count = \func_num_args()) { + @trigger_error('Generating absolute URLs with the Twig asset() function was deprecated in 2.7 and will be removed in 3.0. Please use absolute_url() instead.', E_USER_DEPRECATED); + if (4 === $count) { + @trigger_error('Forcing a version with the Twig asset() function was deprecated in 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + } + + $args = \func_get_args(); + + return $this->getLegacyAssetUrl($path, $packageName, $args[2], isset($args[3]) ? $args[3] : null); + } + + return $this->packages->getUrl($path, $packageName); + } + + /** + * Returns the version of an asset. + * + * @param string $path A public path + * @param string $packageName The name of the asset package to use + * + * @return string The asset version + */ + public function getAssetVersion($path, $packageName = null) + { + return $this->packages->getVersion($path, $packageName); + } + + public function getAssetsVersion($packageName = null) + { + @trigger_error('The Twig assets_version() function was deprecated in 2.7 and will be removed in 3.0. Please use asset_version() instead.', E_USER_DEPRECATED); + + return $this->packages->getVersion('/', $packageName); + } + + private function getLegacyAssetUrl($path, $packageName = null, $absolute = false, $version = null) + { + if ($version) { + $package = $this->packages->getPackage($packageName); + + $v = new \ReflectionProperty('Symfony\Component\Asset\Package', 'versionStrategy'); + $v->setAccessible(true); + + $currentVersionStrategy = $v->getValue($package); + + if (property_exists($currentVersionStrategy, 'format')) { + $f = new \ReflectionProperty($currentVersionStrategy, 'format'); + $f->setAccessible(true); + + $format = $f->getValue($currentVersionStrategy); + + $v->setValue($package, new StaticVersionStrategy($version, $format)); + } else { + $v->setValue($package, new StaticVersionStrategy($version)); + } + } + + try { + $url = $this->packages->getUrl($path, $packageName); + } catch (\Exception $e) { + if ($version) { + $v->setValue($package, $currentVersionStrategy); + } + + throw $e; + } + + if ($version) { + $v->setValue($package, $currentVersionStrategy); + } + + if ($absolute) { + return $this->foundationExtension->generateAbsoluteUrl($url); + } + + return $url; + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'asset'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/CodeExtension.php b/vendor/symfony/twig-bridge/Extension/CodeExtension.php new file mode 100644 index 0000000..4e85fe1 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/CodeExtension.php @@ -0,0 +1,242 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * Twig extension relate to PHP code and used by the profiler and the default exception templates. + * + * @author Fabien Potencier + */ +class CodeExtension extends AbstractExtension +{ + private $fileLinkFormat; + private $rootDir; + private $charset; + + /** + * @param string $fileLinkFormat The format for links to source files + * @param string $rootDir The project root directory + * @param string $charset The charset + */ + public function __construct($fileLinkFormat, $rootDir, $charset) + { + $this->fileLinkFormat = $fileLinkFormat ?: ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->rootDir = str_replace('/', \DIRECTORY_SEPARATOR, \dirname($rootDir)).\DIRECTORY_SEPARATOR; + $this->charset = $charset; + } + + /** + * {@inheritdoc} + */ + public function getFilters() + { + return array( + new TwigFilter('abbr_class', array($this, 'abbrClass'), array('is_safe' => array('html'))), + new TwigFilter('abbr_method', array($this, 'abbrMethod'), array('is_safe' => array('html'))), + new TwigFilter('format_args', array($this, 'formatArgs'), array('is_safe' => array('html'))), + new TwigFilter('format_args_as_text', array($this, 'formatArgsAsText')), + new TwigFilter('file_excerpt', array($this, 'fileExcerpt'), array('is_safe' => array('html'))), + new TwigFilter('format_file', array($this, 'formatFile'), array('is_safe' => array('html'))), + new TwigFilter('format_file_from_text', array($this, 'formatFileFromText'), array('is_safe' => array('html'))), + new TwigFilter('file_link', array($this, 'getFileLink')), + ); + } + + public function abbrClass($class) + { + $parts = explode('\\', $class); + $short = array_pop($parts); + + return sprintf('%s', $class, $short); + } + + public function abbrMethod($method) + { + if (false !== strpos($method, '::')) { + list($class, $method) = explode('::', $method, 2); + $result = sprintf('%s::%s()', $this->abbrClass($class), $method); + } elseif ('Closure' === $method) { + $result = sprintf('%1$s', $method); + } else { + $result = sprintf('%1$s()', $method); + } + + return $result; + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + public function formatArgs($args) + { + $result = array(); + foreach ($args as $key => $item) { + if ('object' === $item[0]) { + $parts = explode('\\', $item[1]); + $short = array_pop($parts); + $formattedValue = sprintf('object(%s)', $item[1], $short); + } elseif ('array' === $item[0]) { + $formattedValue = sprintf('array(%s)', \is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]); + } elseif ('string' === $item[0]) { + $formattedValue = sprintf("'%s'", htmlspecialchars($item[1], ENT_QUOTES, $this->charset)); + } elseif ('null' === $item[0]) { + $formattedValue = 'null'; + } elseif ('boolean' === $item[0]) { + $formattedValue = ''.strtolower(var_export($item[1], true)).''; + } elseif ('resource' === $item[0]) { + $formattedValue = 'resource'; + } else { + $formattedValue = str_replace("\n", '', var_export(htmlspecialchars((string) $item[1], ENT_QUOTES, $this->charset), true)); + } + + $result[] = \is_int($key) ? $formattedValue : sprintf("'%s' => %s", $key, $formattedValue); + } + + return implode(', ', $result); + } + + /** + * Formats an array as a string. + * + * @param array $args The argument array + * + * @return string + */ + public function formatArgsAsText($args) + { + return strip_tags($this->formatArgs($args)); + } + + /** + * Returns an excerpt of a code file around the given line number. + * + * @param string $file A file path + * @param int $line The selected line number + * + * @return string An HTML string + */ + public function fileExcerpt($file, $line) + { + if (is_readable($file)) { + // highlight_file could throw warnings + // see https://bugs.php.net/bug.php?id=25725 + $code = @highlight_file($file, true); + // remove main code/span tags + $code = preg_replace('#^\s*(.*)\s*#s', '\\1', $code); + $content = explode('
    ', $code); + + $lines = array(); + for ($i = max($line - 3, 1), $max = min($line + 3, \count($content)); $i <= $max; ++$i) { + $lines[] = ''.self::fixCodeMarkup($content[$i - 1]).''; + } + + return '
      '.implode("\n", $lines).'
    '; + } + } + + /** + * Formats a file path. + * + * @param string $file An absolute file path + * @param int $line The line number + * @param string $text Use this text for the link rather than the file path + * + * @return string + */ + public function formatFile($file, $line, $text = null) + { + $file = trim($file); + + if (null === $text) { + $text = str_replace('/', \DIRECTORY_SEPARATOR, $file); + if (0 === strpos($text, $this->rootDir)) { + $text = substr($text, \strlen($this->rootDir)); + $text = explode(\DIRECTORY_SEPARATOR, $text, 2); + $text = sprintf('%s%s', $this->rootDir, $text[0], isset($text[1]) ? \DIRECTORY_SEPARATOR.$text[1] : ''); + } + } + + $text = "$text at line $line"; + + if (false !== $link = $this->getFileLink($file, $line)) { + if (\PHP_VERSION_ID >= 50400) { + $flags = ENT_QUOTES | ENT_SUBSTITUTE; + } else { + $flags = ENT_QUOTES; + } + + return sprintf('%s', htmlspecialchars($link, $flags, $this->charset), $text); + } + + return $text; + } + + /** + * Returns the link for a given file/line pair. + * + * @param string $file An absolute file path + * @param int $line The line number + * + * @return string|false A link or false + */ + public function getFileLink($file, $line) + { + if ($this->fileLinkFormat && is_file($file)) { + return strtr($this->fileLinkFormat, array('%f' => $file, '%l' => $line)); + } + + return false; + } + + public function formatFileFromText($text) + { + $that = $this; + + return preg_replace_callback('/in ("|")?(.+?)\1(?: +(?:on|at))? +line (\d+)/s', function ($match) use ($that) { + return 'in '.$that->formatFile($match[2], $match[3]); + }, $text); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'code'; + } + + protected static function fixCodeMarkup($line) + { + //
    ending tag from previous line + $opening = strpos($line, ''); + if (false !== $closing && (false === $opening || $closing < $opening)) { + $line = substr_replace($line, '', $closing, 7); + } + + // missing
    tag at the end of line + $opening = strpos($line, ''); + if (false !== $opening && (false === $closing || $closing > $opening)) { + $line .= '
    '; + } + + return $line; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/DumpExtension.php b/vendor/symfony/twig-bridge/Extension/DumpExtension.php new file mode 100644 index 0000000..70be3e9 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/DumpExtension.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\TokenParser\DumpTokenParser; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Twig\Environment; +use Twig\Extension\AbstractExtension; +use Twig\Template; +use Twig\TwigFunction; + +/** + * Provides integration of the dump() function with Twig. + * + * @author Nicolas Grekas + */ +class DumpExtension extends AbstractExtension +{ + private $cloner; + + public function __construct(ClonerInterface $cloner) + { + $this->cloner = $cloner; + } + + public function getFunctions() + { + return array( + new TwigFunction('dump', array($this, 'dump'), array('is_safe' => array('html'), 'needs_context' => true, 'needs_environment' => true)), + ); + } + + public function getTokenParsers() + { + return array(new DumpTokenParser()); + } + + public function getName() + { + return 'dump'; + } + + public function dump(Environment $env, $context) + { + if (!$env->isDebug()) { + return; + } + + if (2 === \func_num_args()) { + $vars = array(); + foreach ($context as $key => $value) { + if (!$value instanceof Template) { + $vars[$key] = $value; + } + } + + $vars = array($vars); + } else { + $vars = \func_get_args(); + unset($vars[0], $vars[1]); + } + + $dump = fopen('php://memory', 'r+b'); + $dumper = new HtmlDumper($dump, $env->getCharset()); + + foreach ($vars as $value) { + $dumper->dump($this->cloner->cloneVar($value)); + } + rewind($dump); + + return stream_get_contents($dump); + } +} diff --git a/vendor/symfony/twig-bridge/Extension/ExpressionExtension.php b/vendor/symfony/twig-bridge/Extension/ExpressionExtension.php new file mode 100644 index 0000000..fc64fa3 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/ExpressionExtension.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\ExpressionLanguage\Expression; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * ExpressionExtension gives a way to create Expressions from a template. + * + * @author Fabien Potencier + */ +class ExpressionExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return array( + new TwigFunction('expression', array($this, 'createExpression')), + ); + } + + public function createExpression($expression) + { + return new Expression($expression); + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'expression'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/FormExtension.php b/vendor/symfony/twig-bridge/Extension/FormExtension.php new file mode 100644 index 0000000..ca5e716 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/FormExtension.php @@ -0,0 +1,193 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\Form\TwigRendererInterface; +use Symfony\Bridge\Twig\TokenParser\FormThemeTokenParser; +use Symfony\Component\Form\Extension\Core\View\ChoiceView; +use Symfony\Component\Form\FormView; +use Twig\Environment; +use Twig\Extension\AbstractExtension; +use Twig\Extension\InitRuntimeInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; + +/** + * FormExtension extends Twig with form capabilities. + * + * @author Fabien Potencier + * @author Bernhard Schussek + */ +class FormExtension extends AbstractExtension implements InitRuntimeInterface +{ + /** + * This property is public so that it can be accessed directly from compiled + * templates without having to call a getter, which slightly decreases performance. + * + * @var TwigRendererInterface + */ + public $renderer; + + public function __construct(TwigRendererInterface $renderer) + { + $this->renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public function initRuntime(Environment $environment) + { + $this->renderer->setEnvironment($environment); + } + + /** + * {@inheritdoc} + */ + public function getTokenParsers() + { + return array( + // {% form_theme form "SomeBundle::widgets.twig" %} + new FormThemeTokenParser(), + ); + } + + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return array( + new TwigFunction('form_enctype', null, array('node_class' => 'Symfony\Bridge\Twig\Node\FormEnctypeNode', 'is_safe' => array('html'), 'deprecated' => true, 'alternative' => 'form_start')), + new TwigFunction('form_widget', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_errors', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_label', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_row', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_rest', null, array('node_class' => 'Symfony\Bridge\Twig\Node\SearchAndRenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_start', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('form_end', null, array('node_class' => 'Symfony\Bridge\Twig\Node\RenderBlockNode', 'is_safe' => array('html'))), + new TwigFunction('csrf_token', array($this, 'renderCsrfToken')), + ); + } + + /** + * {@inheritdoc} + */ + public function getFilters() + { + return array( + new TwigFilter('humanize', array($this, 'humanize')), + new TwigFilter('form_encode_currency', array($this, 'encodeCurrency'), array('is_safe' => array('html'), 'needs_environment' => true)), + ); + } + + /** + * {@inheritdoc} + */ + public function getTests() + { + return array( + new TwigTest('selectedchoice', array($this, 'isSelectedChoice')), + new TwigTest('rootform', array($this, 'isRootForm')), + ); + } + + /** + * Renders a CSRF token. + * + * @param string $intention The intention of the protected action + * + * @return string A CSRF token + */ + public function renderCsrfToken($intention) + { + return $this->renderer->renderCsrfToken($intention); + } + + /** + * Makes a technical name human readable. + * + * @param string $text The text to humanize + * + * @return string The humanized text + */ + public function humanize($text) + { + return $this->renderer->humanize($text); + } + + /** + * Returns whether a choice is selected for a given form value. + * + * Unfortunately Twig does not support an efficient way to execute the + * "is_selected" closure passed to the template by ChoiceType. It is faster + * to implement the logic here (around 65ms for a specific form). + * + * Directly implementing the logic here is also faster than doing so in + * ChoiceView (around 30ms). + * + * The worst option tested so far is to implement the logic in ChoiceView + * and access the ChoiceView method directly in the template. Doing so is + * around 220ms slower than doing the method call here in the filter. Twig + * seems to be much more efficient at executing filters than at executing + * methods of an object. + * + * @param ChoiceView $choice The choice to check + * @param string|array $selectedValue The selected value to compare + * + * @return bool Whether the choice is selected + * + * @see ChoiceView::isSelected() + */ + public function isSelectedChoice(ChoiceView $choice, $selectedValue) + { + if (\is_array($selectedValue)) { + return \in_array($choice->value, $selectedValue, true); + } + + return $choice->value === $selectedValue; + } + + /** + * @internal + */ + public function isRootForm(FormView $formView) + { + return null === $formView->parent; + } + + /** + * @internal + */ + public function encodeCurrency(Environment $environment, $text, $widget = '') + { + if ('UTF-8' === $charset = $environment->getCharset()) { + $text = htmlspecialchars($text, ENT_QUOTES | (\defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0), 'UTF-8'); + } else { + $text = htmlentities($text, ENT_QUOTES | (\defined('ENT_SUBSTITUTE') ? ENT_SUBSTITUTE : 0), 'UTF-8'); + $text = iconv('UTF-8', $charset, $text); + $widget = iconv('UTF-8', $charset, $widget); + } + + return str_replace('{{ widget }}', $widget, $text); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'form'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/HttpFoundationExtension.php b/vendor/symfony/twig-bridge/Extension/HttpFoundationExtension.php new file mode 100644 index 0000000..fe27783 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/HttpFoundationExtension.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Routing\RequestContext; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * Twig extension for the Symfony HttpFoundation component. + * + * @author Fabien Potencier + */ +class HttpFoundationExtension extends AbstractExtension +{ + private $requestStack; + private $requestContext; + + public function __construct(RequestStack $requestStack, RequestContext $requestContext = null) + { + $this->requestStack = $requestStack; + $this->requestContext = $requestContext; + } + + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return array( + new TwigFunction('absolute_url', array($this, 'generateAbsoluteUrl')), + new TwigFunction('relative_path', array($this, 'generateRelativePath')), + ); + } + + /** + * Returns the absolute URL for the given absolute or relative path. + * + * This method returns the path unchanged if no request is available. + * + * @param string $path The path + * + * @return string The absolute URL + * + * @see Request::getUriForPath() + */ + public function generateAbsoluteUrl($path) + { + if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (!$request = $this->requestStack->getMasterRequest()) { + if (null !== $this->requestContext && '' !== $host = $this->requestContext->getHost()) { + $scheme = $this->requestContext->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 != $this->requestContext->getHttpPort()) { + $port = ':'.$this->requestContext->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->requestContext->getHttpsPort()) { + $port = ':'.$this->requestContext->getHttpsPort(); + } + + if ('#' === $path[0]) { + $queryString = $this->requestContext->getQueryString(); + $path = $this->requestContext->getPathInfo().($queryString ? '?'.$queryString : '').$path; + } elseif ('?' === $path[0]) { + $path = $this->requestContext->getPathInfo().$path; + } + + if ('/' !== $path[0]) { + $path = rtrim($this->requestContext->getBaseUrl(), '/').'/'.$path; + } + + return $scheme.'://'.$host.$port.$path; + } + + return $path; + } + + if ('#' === $path[0]) { + $path = $request->getRequestUri().$path; + } elseif ('?' === $path[0]) { + $path = $request->getPathInfo().$path; + } + + if (!$path || '/' !== $path[0]) { + $prefix = $request->getPathInfo(); + $last = \strlen($prefix) - 1; + if ($last !== $pos = strrpos($prefix, '/')) { + $prefix = substr($prefix, 0, $pos).'/'; + } + + return $request->getUriForPath($prefix.$path); + } + + return $request->getSchemeAndHttpHost().$path; + } + + /** + * Returns a relative path based on the current Request. + * + * This method returns the path unchanged if no request is available. + * + * @param string $path The path + * + * @return string The relative path + * + * @see Request::getRelativeUriForPath() + */ + public function generateRelativePath($path) + { + if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) { + return $path; + } + + if (!$request = $this->requestStack->getMasterRequest()) { + return $path; + } + + return $request->getRelativeUriForPath($path); + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'request'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/HttpKernelExtension.php b/vendor/symfony/twig-bridge/Extension/HttpKernelExtension.php new file mode 100644 index 0000000..74449a3 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/HttpKernelExtension.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\HttpKernel\Controller\ControllerReference; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * Provides integration with the HttpKernel component. + * + * @author Fabien Potencier + */ +class HttpKernelExtension extends AbstractExtension +{ + private $handler; + + public function __construct(FragmentHandler $handler) + { + $this->handler = $handler; + } + + public function getFunctions() + { + return array( + new TwigFunction('render', array($this, 'renderFragment'), array('is_safe' => array('html'))), + new TwigFunction('render_*', array($this, 'renderFragmentStrategy'), array('is_safe' => array('html'))), + new TwigFunction('controller', array($this, 'controller')), + ); + } + + /** + * Renders a fragment. + * + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param array $options An array of options + * + * @return string The fragment content + * + * @see FragmentHandler::render() + */ + public function renderFragment($uri, $options = array()) + { + $strategy = isset($options['strategy']) ? $options['strategy'] : 'inline'; + unset($options['strategy']); + + return $this->handler->render($uri, $strategy, $options); + } + + /** + * Renders a fragment. + * + * @param string $strategy A strategy name + * @param string|ControllerReference $uri A URI as a string or a ControllerReference instance + * @param array $options An array of options + * + * @return string The fragment content + * + * @see FragmentHandler::render() + */ + public function renderFragmentStrategy($strategy, $uri, $options = array()) + { + return $this->handler->render($uri, $strategy, $options); + } + + public function controller($controller, $attributes = array(), $query = array()) + { + return new ControllerReference($controller, $attributes, $query); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'http_kernel'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/LogoutUrlExtension.php b/vendor/symfony/twig-bridge/Extension/LogoutUrlExtension.php new file mode 100644 index 0000000..17abb77 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/LogoutUrlExtension.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * LogoutUrlHelper provides generator functions for the logout URL to Twig. + * + * @author Jeremy Mikola + */ +class LogoutUrlExtension extends AbstractExtension +{ + private $generator; + + public function __construct(LogoutUrlGenerator $generator) + { + $this->generator = $generator; + } + + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return array( + new TwigFunction('logout_url', array($this, 'getLogoutUrl')), + new TwigFunction('logout_path', array($this, 'getLogoutPath')), + ); + } + + /** + * Generates the relative logout URL for the firewall. + * + * @param string|null $key The firewall key or null to use the current firewall key + * + * @return string The relative logout URL + */ + public function getLogoutPath($key = null) + { + return $this->generator->getLogoutPath($key); + } + + /** + * Generates the absolute logout URL for the firewall. + * + * @param string|null $key The firewall key or null to use the current firewall key + * + * @return string The absolute logout URL + */ + public function getLogoutUrl($key = null) + { + return $this->generator->getLogoutUrl($key); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'logout_url'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/ProfilerExtension.php b/vendor/symfony/twig-bridge/Extension/ProfilerExtension.php new file mode 100644 index 0000000..21214f8 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/ProfilerExtension.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Stopwatch\Stopwatch; +use Twig\Extension\ProfilerExtension as BaseProfilerExtension; +use Twig\Profiler\Profile; + +/** + * @author Fabien Potencier + */ +class ProfilerExtension extends BaseProfilerExtension +{ + private $stopwatch; + private $events; + + public function __construct(Profile $profile, Stopwatch $stopwatch = null) + { + parent::__construct($profile); + + $this->stopwatch = $stopwatch; + $this->events = new \SplObjectStorage(); + } + + public function enter(Profile $profile) + { + if ($this->stopwatch && $profile->isTemplate()) { + $this->events[$profile] = $this->stopwatch->start($profile->getName(), 'template'); + } + + parent::enter($profile); + } + + public function leave(Profile $profile) + { + parent::leave($profile); + + if ($this->stopwatch && $profile->isTemplate()) { + $this->events[$profile]->stop(); + unset($this->events[$profile]); + } + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'native_profiler'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/RoutingExtension.php b/vendor/symfony/twig-bridge/Extension/RoutingExtension.php new file mode 100644 index 0000000..f347239 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/RoutingExtension.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Twig\Extension\AbstractExtension; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; +use Twig\TwigFunction; + +/** + * Provides integration of the Routing component with Twig. + * + * @author Fabien Potencier + */ +class RoutingExtension extends AbstractExtension +{ + private $generator; + + public function __construct(UrlGeneratorInterface $generator) + { + $this->generator = $generator; + } + + /** + * Returns a list of functions to add to the existing list. + * + * @return array An array of functions + */ + public function getFunctions() + { + return array( + new TwigFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + new TwigFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + ); + } + + /** + * @param string $name + * @param array $parameters + * @param bool $relative + * + * @return string + */ + public function getPath($name, $parameters = array(), $relative = false) + { + return $this->generator->generate($name, $parameters, $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH); + } + + /** + * @param string $name + * @param array $parameters + * @param bool $schemeRelative + * + * @return string + */ + public function getUrl($name, $parameters = array(), $schemeRelative = false) + { + return $this->generator->generate($name, $parameters, $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL); + } + + /** + * Determines at compile time whether the generated URL will be safe and thus + * saving the unneeded automatic escaping for performance reasons. + * + * The URL generation process percent encodes non-alphanumeric characters. So there is no risk + * that malicious/invalid characters are part of the URL. The only character within an URL that + * must be escaped in html is the ampersand ("&") which separates query params. So we cannot mark + * the URL generation as always safe, but only when we are sure there won't be multiple query + * params. This is the case when there are none or only one constant parameter given. + * E.g. we know beforehand this will be safe: + * - path('route') + * - path('route', {'param': 'value'}) + * But the following may not: + * - path('route', var) + * - path('route', {'param': ['val1', 'val2'] }) // a sub-array + * - path('route', {'param1': 'value1', 'param2': 'value2'}) + * If param1 and param2 reference placeholder in the route, it would still be safe. But we don't know. + * + * @param Node $argsNode The arguments of the path/url function + * + * @return array An array with the contexts the URL is safe + * + * To be made @final in 3.4, and the type-hint be changed to "\Twig\Node\Node" in 4.0. + */ + public function isUrlGenerationSafe(\Twig_Node $argsNode) + { + // support named arguments + $paramsNode = $argsNode->hasNode('parameters') ? $argsNode->getNode('parameters') : ( + $argsNode->hasNode(1) ? $argsNode->getNode(1) : null + ); + + if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 && + (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression) + ) { + return array('html'); + } + + return array(); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'routing'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/SecurityExtension.php b/vendor/symfony/twig-bridge/Extension/SecurityExtension.php new file mode 100644 index 0000000..193726a --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/SecurityExtension.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Security\Acl\Voter\FieldVote; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException; +use Twig\Extension\AbstractExtension; +use Twig\TwigFunction; + +/** + * SecurityExtension exposes security context features. + * + * @author Fabien Potencier + */ +class SecurityExtension extends AbstractExtension +{ + private $securityChecker; + + public function __construct(AuthorizationCheckerInterface $securityChecker = null) + { + $this->securityChecker = $securityChecker; + } + + public function isGranted($role, $object = null, $field = null) + { + if (null === $this->securityChecker) { + return false; + } + + if (null !== $field) { + $object = new FieldVote($object, $field); + } + + try { + return $this->securityChecker->isGranted($role, $object); + } catch (AuthenticationCredentialsNotFoundException $e) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return array( + new TwigFunction('is_granted', array($this, 'isGranted')), + ); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'security'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/StopwatchExtension.php b/vendor/symfony/twig-bridge/Extension/StopwatchExtension.php new file mode 100644 index 0000000..ffbfcf8 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/StopwatchExtension.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\TokenParser\StopwatchTokenParser; +use Symfony\Component\Stopwatch\Stopwatch; +use Twig\Extension\AbstractExtension; + +/** + * Twig extension for the stopwatch helper. + * + * @author Wouter J + */ +class StopwatchExtension extends AbstractExtension +{ + private $stopwatch; + private $enabled; + + public function __construct(Stopwatch $stopwatch = null, $enabled = true) + { + $this->stopwatch = $stopwatch; + $this->enabled = $enabled; + } + + public function getStopwatch() + { + return $this->stopwatch; + } + + public function getTokenParsers() + { + return array( + /* + * {% stopwatch foo %} + * Some stuff which will be recorded on the timeline + * {% endstopwatch %} + */ + new StopwatchTokenParser(null !== $this->stopwatch && $this->enabled), + ); + } + + public function getName() + { + return 'stopwatch'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/TranslationExtension.php b/vendor/symfony/twig-bridge/Extension/TranslationExtension.php new file mode 100644 index 0000000..d10b5ff --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/TranslationExtension.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\NodeVisitor\TranslationDefaultDomainNodeVisitor; +use Symfony\Bridge\Twig\NodeVisitor\TranslationNodeVisitor; +use Symfony\Bridge\Twig\TokenParser\TransChoiceTokenParser; +use Symfony\Bridge\Twig\TokenParser\TransDefaultDomainTokenParser; +use Symfony\Bridge\Twig\TokenParser\TransTokenParser; +use Symfony\Component\Translation\TranslatorInterface; +use Twig\Extension\AbstractExtension; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\TokenParser\AbstractTokenParser; +use Twig\TwigFilter; + +/** + * Provides integration of the Translation component with Twig. + * + * @author Fabien Potencier + */ +class TranslationExtension extends AbstractExtension +{ + private $translator; + private $translationNodeVisitor; + + public function __construct(TranslatorInterface $translator, NodeVisitorInterface $translationNodeVisitor = null) + { + if (!$translationNodeVisitor) { + $translationNodeVisitor = new TranslationNodeVisitor(); + } + + $this->translator = $translator; + $this->translationNodeVisitor = $translationNodeVisitor; + } + + public function getTranslator() + { + return $this->translator; + } + + /** + * {@inheritdoc} + */ + public function getFilters() + { + return array( + new TwigFilter('trans', array($this, 'trans')), + new TwigFilter('transchoice', array($this, 'transchoice')), + ); + } + + /** + * Returns the token parser instance to add to the existing list. + * + * @return AbstractTokenParser[] + */ + public function getTokenParsers() + { + return array( + // {% trans %}Symfony is great!{% endtrans %} + new TransTokenParser(), + + // {% transchoice count %} + // {0} There is no apples|{1} There is one apple|]1,Inf] There is {{ count }} apples + // {% endtranschoice %} + new TransChoiceTokenParser(), + + // {% trans_default_domain "foobar" %} + new TransDefaultDomainTokenParser(), + ); + } + + /** + * {@inheritdoc} + */ + public function getNodeVisitors() + { + return array($this->translationNodeVisitor, new TranslationDefaultDomainNodeVisitor()); + } + + public function getTranslationNodeVisitor() + { + return $this->translationNodeVisitor; + } + + public function trans($message, array $arguments = array(), $domain = null, $locale = null) + { + return $this->translator->trans($message, $arguments, $domain, $locale); + } + + public function transchoice($message, $count, array $arguments = array(), $domain = null, $locale = null) + { + return $this->translator->transChoice($message, $count, array_merge(array('%count%' => $count), $arguments), $domain, $locale); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'translator'; + } +} diff --git a/vendor/symfony/twig-bridge/Extension/YamlExtension.php b/vendor/symfony/twig-bridge/Extension/YamlExtension.php new file mode 100644 index 0000000..cb0f9e0 --- /dev/null +++ b/vendor/symfony/twig-bridge/Extension/YamlExtension.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Component\Yaml\Dumper as YamlDumper; +use Symfony\Component\Yaml\Yaml; +use Twig\Extension\AbstractExtension; +use Twig\TwigFilter; + +/** + * Provides integration of the Yaml component with Twig. + * + * @author Fabien Potencier + */ +class YamlExtension extends AbstractExtension +{ + /** + * {@inheritdoc} + */ + public function getFilters() + { + return array( + new TwigFilter('yaml_encode', array($this, 'encode')), + new TwigFilter('yaml_dump', array($this, 'dump')), + ); + } + + public function encode($input, $inline = 0, $dumpObjects = false) + { + static $dumper; + + if (null === $dumper) { + $dumper = new YamlDumper(); + } + + if (\defined('Symfony\Component\Yaml\Yaml::DUMP_OBJECT')) { + return $dumper->dump($input, $inline, 0, \is_bool($dumpObjects) ? Yaml::DUMP_OBJECT : 0); + } + + return $dumper->dump($input, $inline, 0, false, $dumpObjects); + } + + public function dump($value, $inline = 0, $dumpObjects = false) + { + if (\is_resource($value)) { + return '%Resource%'; + } + + if (\is_array($value) || \is_object($value)) { + return '%'.\gettype($value).'% '.$this->encode($value, $inline, $dumpObjects); + } + + return $this->encode($value, $inline, $dumpObjects); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'yaml'; + } +} diff --git a/vendor/symfony/twig-bridge/Form/TwigRenderer.php b/vendor/symfony/twig-bridge/Form/TwigRenderer.php new file mode 100644 index 0000000..9f4d7c5 --- /dev/null +++ b/vendor/symfony/twig-bridge/Form/TwigRenderer.php @@ -0,0 +1,44 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\FormRenderer; +use Twig\Environment; + +/** + * @author Bernhard Schussek + */ +class TwigRenderer extends FormRenderer implements TwigRendererInterface +{ + public function __construct(TwigRendererEngineInterface $engine, $csrfTokenManager = null) + { + parent::__construct($engine, $csrfTokenManager); + } + + /** + * Returns the engine used by this renderer. + * + * @return TwigRendererEngineInterface The renderer engine + */ + public function getEngine() + { + return parent::getEngine(); + } + + /** + * {@inheritdoc} + */ + public function setEnvironment(Environment $environment) + { + $this->getEngine()->setEnvironment($environment); + } +} diff --git a/vendor/symfony/twig-bridge/Form/TwigRendererEngine.php b/vendor/symfony/twig-bridge/Form/TwigRendererEngine.php new file mode 100644 index 0000000..74b2e51 --- /dev/null +++ b/vendor/symfony/twig-bridge/Form/TwigRendererEngine.php @@ -0,0 +1,187 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\AbstractRendererEngine; +use Symfony\Component\Form\FormView; +use Twig\Environment; +use Twig\Template; + +/** + * @author Bernhard Schussek + */ +class TwigRendererEngine extends AbstractRendererEngine implements TwigRendererEngineInterface +{ + /** + * @var Environment + */ + private $environment; + + /** + * @var Template + */ + private $template; + + /** + * {@inheritdoc} + */ + public function setEnvironment(Environment $environment) + { + $this->environment = $environment; + } + + /** + * {@inheritdoc} + */ + public function renderBlock(FormView $view, $resource, $blockName, array $variables = array()) + { + $cacheKey = $view->vars[self::CACHE_KEY_VAR]; + + $context = $this->environment->mergeGlobals($variables); + + ob_start(); + + // By contract,This method can only be called after getting the resource + // (which is passed to the method). Getting a resource for the first time + // (with an empty cache) is guaranteed to invoke loadResourcesFromTheme(), + // where the property $template is initialized. + + // We do not call renderBlock here to avoid too many nested level calls + // (XDebug limits the level to 100 by default) + $this->template->displayBlock($blockName, $context, $this->resources[$cacheKey]); + + return ob_get_clean(); + } + + /** + * Loads the cache with the resource for a given block name. + * + * This implementation eagerly loads all blocks of the themes assigned to the given view + * and all of its ancestors views. This is necessary, because Twig receives the + * list of blocks later. At that point, all blocks must already be loaded, for the + * case that the function "block()" is used in the Twig template. + * + * @see getResourceForBlock() + * + * @param string $cacheKey The cache key of the form view + * @param FormView $view The form view for finding the applying themes + * @param string $blockName The name of the block to load + * + * @return bool True if the resource could be loaded, false otherwise + */ + protected function loadResourceForBlockName($cacheKey, FormView $view, $blockName) + { + // The caller guarantees that $this->resources[$cacheKey][$block] is + // not set, but it doesn't have to check whether $this->resources[$cacheKey] + // is set. If $this->resources[$cacheKey] is set, all themes for this + // $cacheKey are already loaded (due to the eager population, see doc comment). + if (isset($this->resources[$cacheKey])) { + // As said in the previous, the caller guarantees that + // $this->resources[$cacheKey][$block] is not set. Since the themes are + // already loaded, it can only be a non-existing block. + $this->resources[$cacheKey][$blockName] = false; + + return false; + } + + // Recursively try to find the block in the themes assigned to $view, + // then of its parent view, then of the parent view of the parent and so on. + // When the root view is reached in this recursion, also the default + // themes are taken into account. + + // Check each theme whether it contains the searched block + if (isset($this->themes[$cacheKey])) { + for ($i = \count($this->themes[$cacheKey]) - 1; $i >= 0; --$i) { + $this->loadResourcesFromTheme($cacheKey, $this->themes[$cacheKey][$i]); + // CONTINUE LOADING (see doc comment) + } + } + + // Check the default themes once we reach the root view without success + if (!$view->parent) { + for ($i = \count($this->defaultThemes) - 1; $i >= 0; --$i) { + $this->loadResourcesFromTheme($cacheKey, $this->defaultThemes[$i]); + // CONTINUE LOADING (see doc comment) + } + } + + // Proceed with the themes of the parent view + if ($view->parent) { + $parentCacheKey = $view->parent->vars[self::CACHE_KEY_VAR]; + + if (!isset($this->resources[$parentCacheKey])) { + $this->loadResourceForBlockName($parentCacheKey, $view->parent, $blockName); + } + + // EAGER CACHE POPULATION (see doc comment) + foreach ($this->resources[$parentCacheKey] as $nestedBlockName => $resource) { + if (!isset($this->resources[$cacheKey][$nestedBlockName])) { + $this->resources[$cacheKey][$nestedBlockName] = $resource; + } + } + } + + // Even though we loaded the themes, it can happen that none of them + // contains the searched block + if (!isset($this->resources[$cacheKey][$blockName])) { + // Cache that we didn't find anything to speed up further accesses + $this->resources[$cacheKey][$blockName] = false; + } + + return false !== $this->resources[$cacheKey][$blockName]; + } + + /** + * Loads the resources for all blocks in a theme. + * + * @param string $cacheKey The cache key for storing the resource + * @param mixed $theme The theme to load the block from. This parameter + * is passed by reference, because it might be necessary + * to initialize the theme first. Any changes made to + * this variable will be kept and be available upon + * further calls to this method using the same theme. + */ + protected function loadResourcesFromTheme($cacheKey, &$theme) + { + if (!$theme instanceof Template) { + /* @var Template $theme */ + $theme = $this->environment->loadTemplate($theme); + } + + if (null === $this->template) { + // Store the first Template instance that we find so that + // we can call displayBlock() later on. It doesn't matter *which* + // template we use for that, since we pass the used blocks manually + // anyway. + $this->template = $theme; + } + + // Use a separate variable for the inheritance traversal, because + // theme is a reference and we don't want to change it. + $currentTheme = $theme; + + $context = $this->environment->mergeGlobals(array()); + + // The do loop takes care of template inheritance. + // Add blocks from all templates in the inheritance tree, but avoid + // overriding blocks already set. + do { + foreach ($currentTheme->getBlocks() as $block => $blockData) { + if (!isset($this->resources[$cacheKey][$block])) { + // The resource given back is the key to the bucket that + // contains this block. + $this->resources[$cacheKey][$block] = $blockData; + } + } + } while (false !== $currentTheme = $currentTheme->getParent($context)); + } +} diff --git a/vendor/symfony/twig-bridge/Form/TwigRendererEngineInterface.php b/vendor/symfony/twig-bridge/Form/TwigRendererEngineInterface.php new file mode 100644 index 0000000..3e8a8e1 --- /dev/null +++ b/vendor/symfony/twig-bridge/Form/TwigRendererEngineInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\FormRendererEngineInterface; +use Twig\Environment; + +// BC/FC with namespaced Twig +class_exists('Twig\Environment'); + +/** + * @author Bernhard Schussek + */ +interface TwigRendererEngineInterface extends FormRendererEngineInterface +{ + public function setEnvironment(Environment $environment); +} diff --git a/vendor/symfony/twig-bridge/Form/TwigRendererInterface.php b/vendor/symfony/twig-bridge/Form/TwigRendererInterface.php new file mode 100644 index 0000000..cb45c17 --- /dev/null +++ b/vendor/symfony/twig-bridge/Form/TwigRendererInterface.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Form; + +use Symfony\Component\Form\FormRendererInterface; +use Twig\Environment; + +// BC/FC with namespaced Twig +class_exists('Twig\Environment'); + +/** + * @author Bernhard Schussek + */ +interface TwigRendererInterface extends FormRendererInterface +{ + public function setEnvironment(Environment $environment); +} diff --git a/vendor/symfony/twig-bridge/LICENSE b/vendor/symfony/twig-bridge/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/twig-bridge/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/twig-bridge/Node/DumpNode.php b/vendor/symfony/twig-bridge/Node/DumpNode.php new file mode 100644 index 0000000..d820d75 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/DumpNode.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Node\Node; + +/** + * @author Julien Galenski + */ +class DumpNode extends Node +{ + private $varPrefix; + + public function __construct($varPrefix, Node $values = null, $lineno, $tag = null) + { + $nodes = array(); + if (null !== $values) { + $nodes['values'] = $values; + } + + parent::__construct($nodes, array(), $lineno, $tag); + $this->varPrefix = $varPrefix; + } + + /** + * {@inheritdoc} + */ + public function compile(Compiler $compiler) + { + $compiler + ->write("if (\$this->env->isDebug()) {\n") + ->indent(); + + if (!$this->hasNode('values')) { + // remove embedded templates (macros) from the context + $compiler + ->write(sprintf('$%svars = array();'."\n", $this->varPrefix)) + ->write(sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix)) + ->indent() + ->write(sprintf('if (!$%sval instanceof \Twig\Template) {'."\n", $this->varPrefix)) + ->indent() + ->write(sprintf('$%1$svars[$%1$skey] = $%1$sval;'."\n", $this->varPrefix)) + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ->addDebugInfo($this) + ->write(sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)); + } elseif (($values = $this->getNode('values')) && 1 === $values->count()) { + $compiler + ->addDebugInfo($this) + ->write('\Symfony\Component\VarDumper\VarDumper::dump(') + ->subcompile($values->getNode(0)) + ->raw(");\n"); + } else { + $compiler + ->addDebugInfo($this) + ->write('\Symfony\Component\VarDumper\VarDumper::dump(array('."\n") + ->indent(); + foreach ($values as $node) { + $compiler->write(''); + if ($node->hasAttribute('name')) { + $compiler + ->string($node->getAttribute('name')) + ->raw(' => '); + } + $compiler + ->subcompile($node) + ->raw(",\n"); + } + $compiler + ->outdent() + ->write("));\n"); + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/vendor/symfony/twig-bridge/Node/FormEnctypeNode.php b/vendor/symfony/twig-bridge/Node/FormEnctypeNode.php new file mode 100644 index 0000000..14811e6 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/FormEnctypeNode.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +/** + * @author Bernhard Schussek + * + * @deprecated since version 2.3, to be removed in 3.0. Use the helper "form_start()" instead. + */ +class FormEnctypeNode extends SearchAndRenderBlockNode +{ +} diff --git a/vendor/symfony/twig-bridge/Node/FormThemeNode.php b/vendor/symfony/twig-bridge/Node/FormThemeNode.php new file mode 100644 index 0000000..b2eb100 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/FormThemeNode.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Node\Node; + +/** + * @author Fabien Potencier + */ +class FormThemeNode extends Node +{ + public function __construct(Node $form, Node $resources, $lineno, $tag = null) + { + parent::__construct(array('form' => $form, 'resources' => $resources), array(), $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->setTheme(') + ->subcompile($this->getNode('form')) + ->raw(', ') + ->subcompile($this->getNode('resources')) + ->raw(");\n"); + } +} diff --git a/vendor/symfony/twig-bridge/Node/RenderBlockNode.php b/vendor/symfony/twig-bridge/Node/RenderBlockNode.php new file mode 100644 index 0000000..4a06907 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/RenderBlockNode.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\FunctionExpression; + +/** + * Compiles a call to {@link \Symfony\Component\Form\FormRendererInterface::renderBlock()}. + * + * The function name is used as block name. For example, if the function name + * is "foo", the block "foo" will be rendered. + * + * @author Bernhard Schussek + */ +class RenderBlockNode extends FunctionExpression +{ + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + $arguments = iterator_to_array($this->getNode('arguments')); + $compiler->write('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->renderBlock('); + + if (isset($arguments[0])) { + $compiler->subcompile($arguments[0]); + $compiler->raw(', \''.$this->getAttribute('name').'\''); + + if (isset($arguments[1])) { + $compiler->raw(', '); + $compiler->subcompile($arguments[1]); + } + } + + $compiler->raw(')'); + } +} diff --git a/vendor/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php b/vendor/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php new file mode 100644 index 0000000..7c95199 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/SearchAndRenderBlockNode.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FunctionExpression; + +/** + * @author Bernhard Schussek + */ +class SearchAndRenderBlockNode extends FunctionExpression +{ + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + $compiler->raw('$this->env->getExtension(\'Symfony\Bridge\Twig\Extension\FormExtension\')->renderer->searchAndRenderBlock('); + + preg_match('/_([^_]+)$/', $this->getAttribute('name'), $matches); + + $label = null; + $arguments = iterator_to_array($this->getNode('arguments')); + $blockNameSuffix = $matches[1]; + + if (isset($arguments[0])) { + $compiler->subcompile($arguments[0]); + $compiler->raw(', \''.$blockNameSuffix.'\''); + + if (isset($arguments[1])) { + if ('label' === $blockNameSuffix) { + // The "label" function expects the label in the second and + // the variables in the third argument + $label = $arguments[1]; + $variables = isset($arguments[2]) ? $arguments[2] : null; + $lineno = $label->getTemplateLine(); + + if ($label instanceof ConstantExpression) { + // If the label argument is given as a constant, we can either + // strip it away if it is empty, or integrate it into the array + // of variables at compile time. + $labelIsExpression = false; + + // Only insert the label into the array if it is not empty + if (!twig_test_empty($label->getAttribute('value'))) { + $originalVariables = $variables; + $variables = new ArrayExpression(array(), $lineno); + $labelKey = new ConstantExpression('label', $lineno); + + if (null !== $originalVariables) { + foreach ($originalVariables->getKeyValuePairs() as $pair) { + // Don't copy the original label attribute over if it exists + if ((string) $labelKey !== (string) $pair['key']) { + $variables->addElement($pair['value'], $pair['key']); + } + } + } + + // Insert the label argument into the array + $variables->addElement($label, $labelKey); + } + } else { + // The label argument is not a constant, but some kind of + // expression. This expression needs to be evaluated at runtime. + // Depending on the result (whether it is null or not), the + // label in the arguments should take precedence over the label + // in the attributes or not. + $labelIsExpression = true; + } + } else { + // All other functions than "label" expect the variables + // in the second argument + $label = null; + $variables = $arguments[1]; + $labelIsExpression = false; + } + + if (null !== $variables || $labelIsExpression) { + $compiler->raw(', '); + + if (null !== $variables) { + $compiler->subcompile($variables); + } + + if ($labelIsExpression) { + if (null !== $variables) { + $compiler->raw(' + '); + } + + // Check at runtime whether the label is empty. + // If not, add it to the array at runtime. + $compiler->raw('(twig_test_empty($_label_ = '); + $compiler->subcompile($label); + $compiler->raw(') ? array() : array("label" => $_label_))'); + } + } + } + } + + $compiler->raw(')'); + } +} diff --git a/vendor/symfony/twig-bridge/Node/StopwatchNode.php b/vendor/symfony/twig-bridge/Node/StopwatchNode.php new file mode 100644 index 0000000..fac770c --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/StopwatchNode.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Node; + +/** + * Represents a stopwatch node. + * + * @author Wouter J + */ +class StopwatchNode extends Node +{ + public function __construct(Node $name, Node $body, AssignNameExpression $var, $lineno = 0, $tag = null) + { + parent::__construct(array('body' => $body, 'name' => $name, 'var' => $var), array(), $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('var')) + ->raw(' = ') + ->subcompile($this->getNode('name')) + ->write(";\n") + ->write("\$this->env->getExtension('Symfony\Bridge\Twig\Extension\StopwatchExtension')->getStopwatch()->start(") + ->subcompile($this->getNode('var')) + ->raw(", 'template');\n") + ->subcompile($this->getNode('body')) + ->write("\$this->env->getExtension('Symfony\Bridge\Twig\Extension\StopwatchExtension')->getStopwatch()->stop(") + ->subcompile($this->getNode('var')) + ->raw(");\n") + ; + } +} diff --git a/vendor/symfony/twig-bridge/Node/TransDefaultDomainNode.php b/vendor/symfony/twig-bridge/Node/TransDefaultDomainNode.php new file mode 100644 index 0000000..c9c82b3 --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/TransDefaultDomainNode.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Node; + +/** + * @author Fabien Potencier + */ +class TransDefaultDomainNode extends Node +{ + public function __construct(AbstractExpression $expr, $lineno = 0, $tag = null) + { + parent::__construct(array('expr' => $expr), array(), $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + // noop as this node is just a marker for TranslationDefaultDomainNodeVisitor + } +} diff --git a/vendor/symfony/twig-bridge/Node/TransNode.php b/vendor/symfony/twig-bridge/Node/TransNode.php new file mode 100644 index 0000000..020810f --- /dev/null +++ b/vendor/symfony/twig-bridge/Node/TransNode.php @@ -0,0 +1,132 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\Node; +use Twig\Node\TextNode; + +// BC/FC with namespaced Twig +class_exists('Twig\Node\Expression\ArrayExpression'); + +/** + * @author Fabien Potencier + */ +class TransNode extends Node +{ + public function __construct(Node $body, Node $domain = null, AbstractExpression $count = null, AbstractExpression $vars = null, AbstractExpression $locale = null, $lineno = 0, $tag = null) + { + $nodes = array('body' => $body); + if (null !== $domain) { + $nodes['domain'] = $domain; + } + if (null !== $count) { + $nodes['count'] = $count; + } + if (null !== $vars) { + $nodes['vars'] = $vars; + } + if (null !== $locale) { + $nodes['locale'] = $locale; + } + + parent::__construct($nodes, array(), $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $defaults = new ArrayExpression(array(), -1); + if ($this->hasNode('vars') && ($vars = $this->getNode('vars')) instanceof ArrayExpression) { + $defaults = $this->getNode('vars'); + $vars = null; + } + list($msg, $defaults) = $this->compileString($this->getNode('body'), $defaults, (bool) $vars); + + $method = !$this->hasNode('count') ? 'trans' : 'transChoice'; + + $compiler + ->write('echo $this->env->getExtension(\'Symfony\Bridge\Twig\Extension\TranslationExtension\')->getTranslator()->'.$method.'(') + ->subcompile($msg) + ; + + $compiler->raw(', '); + + if ($this->hasNode('count')) { + $compiler + ->subcompile($this->getNode('count')) + ->raw(', ') + ; + } + + if (null !== $vars) { + $compiler + ->raw('array_merge(') + ->subcompile($defaults) + ->raw(', ') + ->subcompile($this->getNode('vars')) + ->raw(')') + ; + } else { + $compiler->subcompile($defaults); + } + + $compiler->raw(', '); + + if (!$this->hasNode('domain')) { + $compiler->repr('messages'); + } else { + $compiler->subcompile($this->getNode('domain')); + } + + if ($this->hasNode('locale')) { + $compiler + ->raw(', ') + ->subcompile($this->getNode('locale')) + ; + } + $compiler->raw(");\n"); + } + + protected function compileString(Node $body, ArrayExpression $vars, $ignoreStrictCheck = false) + { + if ($body instanceof ConstantExpression) { + $msg = $body->getAttribute('value'); + } elseif ($body instanceof TextNode) { + $msg = $body->getAttribute('data'); + } else { + return array($body, $vars); + } + + preg_match_all('/(?getTemplateLine()); + if (!$vars->hasElement($key)) { + if ('count' === $var && $this->hasNode('count')) { + $vars->addElement($this->getNode('count'), $key); + } else { + $varExpr = new NameExpression($var, $body->getTemplateLine()); + $varExpr->setAttribute('ignore_strict_check', $ignoreStrictCheck); + $vars->addElement($varExpr, $key); + } + } + } + + return array(new ConstantExpression(str_replace('%%', '%', trim($msg)), $body->getTemplateLine()), $vars); + } +} diff --git a/vendor/symfony/twig-bridge/NodeVisitor/Scope.php b/vendor/symfony/twig-bridge/NodeVisitor/Scope.php new file mode 100644 index 0000000..1c3451b --- /dev/null +++ b/vendor/symfony/twig-bridge/NodeVisitor/Scope.php @@ -0,0 +1,111 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\NodeVisitor; + +/** + * @author Jean-François Simon + */ +class Scope +{ + private $parent; + private $data = array(); + private $left = false; + + public function __construct(Scope $parent = null) + { + $this->parent = $parent; + } + + /** + * Opens a new child scope. + * + * @return self + */ + public function enter() + { + return new self($this); + } + + /** + * Closes current scope and returns parent one. + * + * @return self|null + */ + public function leave() + { + $this->left = true; + + return $this->parent; + } + + /** + * Stores data into current scope. + * + * @param string $key + * @param mixed $value + * + * @return $this + * + * @throws \LogicException + */ + public function set($key, $value) + { + if ($this->left) { + throw new \LogicException('Left scope is not mutable.'); + } + + $this->data[$key] = $value; + + return $this; + } + + /** + * Tests if a data is visible from current scope. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + if (array_key_exists($key, $this->data)) { + return true; + } + + if (null === $this->parent) { + return false; + } + + return $this->parent->has($key); + } + + /** + * Returns data visible from current scope. + * + * @param string $key + * @param mixed $default + * + * @return mixed + */ + public function get($key, $default = null) + { + if (array_key_exists($key, $this->data)) { + return $this->data[$key]; + } + + if (null === $this->parent) { + return $default; + } + + return $this->parent->get($key, $default); + } +} diff --git a/vendor/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php b/vendor/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php new file mode 100644 index 0000000..6a34a03 --- /dev/null +++ b/vendor/symfony/twig-bridge/NodeVisitor/TranslationDefaultDomainNodeVisitor.php @@ -0,0 +1,134 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\NodeVisitor; + +use Symfony\Bridge\Twig\Node\TransDefaultDomainNode; +use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Environment; +use Twig\Node\BlockNode; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\Node\SetNode; +use Twig\NodeVisitor\AbstractNodeVisitor; + +/** + * @author Fabien Potencier + */ +class TranslationDefaultDomainNodeVisitor extends AbstractNodeVisitor +{ + private $scope; + + public function __construct() + { + $this->scope = new Scope(); + } + + /** + * {@inheritdoc} + */ + protected function doEnterNode(Node $node, Environment $env) + { + if ($node instanceof BlockNode || $node instanceof ModuleNode) { + $this->scope = $this->scope->enter(); + } + + if ($node instanceof TransDefaultDomainNode) { + if ($node->getNode('expr') instanceof ConstantExpression) { + $this->scope->set('domain', $node->getNode('expr')); + + return $node; + } else { + $var = $this->getVarName(); + $name = new AssignNameExpression($var, $node->getTemplateLine()); + $this->scope->set('domain', new NameExpression($var, $node->getTemplateLine())); + + return new SetNode(false, new Node(array($name)), new Node(array($node->getNode('expr'))), $node->getTemplateLine()); + } + } + + if (!$this->scope->has('domain')) { + return $node; + } + + if ($node instanceof FilterExpression && \in_array($node->getNode('filter')->getAttribute('value'), array('trans', 'transchoice'))) { + $arguments = $node->getNode('arguments'); + $ind = 'trans' === $node->getNode('filter')->getAttribute('value') ? 1 : 2; + if ($this->isNamedArguments($arguments)) { + if (!$arguments->hasNode('domain') && !$arguments->hasNode($ind)) { + $arguments->setNode('domain', $this->scope->get('domain')); + } + } else { + if (!$arguments->hasNode($ind)) { + if (!$arguments->hasNode($ind - 1)) { + $arguments->setNode($ind - 1, new ArrayExpression(array(), $node->getTemplateLine())); + } + + $arguments->setNode($ind, $this->scope->get('domain')); + } + } + } elseif ($node instanceof TransNode) { + if (!$node->hasNode('domain')) { + $node->setNode('domain', $this->scope->get('domain')); + } + } + + return $node; + } + + /** + * {@inheritdoc} + */ + protected function doLeaveNode(Node $node, Environment $env) + { + if ($node instanceof TransDefaultDomainNode) { + return false; + } + + if ($node instanceof BlockNode || $node instanceof ModuleNode) { + $this->scope = $this->scope->leave(); + } + + return $node; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return -10; + } + + /** + * @return bool + */ + private function isNamedArguments($arguments) + { + foreach ($arguments as $name => $node) { + if (!\is_int($name)) { + return true; + } + } + + return false; + } + + private function getVarName() + { + return sprintf('__internal_%s', hash('sha256', uniqid(mt_rand(), true), false)); + } +} diff --git a/vendor/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php b/vendor/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php new file mode 100644 index 0000000..1fbce9c --- /dev/null +++ b/vendor/symfony/twig-bridge/NodeVisitor/TranslationNodeVisitor.php @@ -0,0 +1,136 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\NodeVisitor; + +use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Environment; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Node; +use Twig\NodeVisitor\AbstractNodeVisitor; + +/** + * TranslationNodeVisitor extracts translation messages. + * + * @author Fabien Potencier + */ +class TranslationNodeVisitor extends AbstractNodeVisitor +{ + const UNDEFINED_DOMAIN = '_undefined'; + + private $enabled = false; + private $messages = array(); + + public function enable() + { + $this->enabled = true; + $this->messages = array(); + } + + public function disable() + { + $this->enabled = false; + $this->messages = array(); + } + + public function getMessages() + { + return $this->messages; + } + + /** + * {@inheritdoc} + */ + protected function doEnterNode(Node $node, Environment $env) + { + if (!$this->enabled) { + return $node; + } + + if ( + $node instanceof FilterExpression && + 'trans' === $node->getNode('filter')->getAttribute('value') && + $node->getNode('node') instanceof ConstantExpression + ) { + // extract constant nodes with a trans filter + $this->messages[] = array( + $node->getNode('node')->getAttribute('value'), + $this->getReadDomainFromArguments($node->getNode('arguments'), 1), + ); + } elseif ( + $node instanceof FilterExpression && + 'transchoice' === $node->getNode('filter')->getAttribute('value') && + $node->getNode('node') instanceof ConstantExpression + ) { + // extract constant nodes with a trans filter + $this->messages[] = array( + $node->getNode('node')->getAttribute('value'), + $this->getReadDomainFromArguments($node->getNode('arguments'), 2), + ); + } elseif ($node instanceof TransNode) { + // extract trans nodes + $this->messages[] = array( + $node->getNode('body')->getAttribute('data'), + $node->hasNode('domain') ? $this->getReadDomainFromNode($node->getNode('domain')) : null, + ); + } + + return $node; + } + + /** + * {@inheritdoc} + */ + protected function doLeaveNode(Node $node, Environment $env) + { + return $node; + } + + /** + * {@inheritdoc} + */ + public function getPriority() + { + return 0; + } + + /** + * @param Node $arguments + * @param int $index + * + * @return string|null + */ + private function getReadDomainFromArguments(Node $arguments, $index) + { + if ($arguments->hasNode('domain')) { + $argument = $arguments->getNode('domain'); + } elseif ($arguments->hasNode($index)) { + $argument = $arguments->getNode($index); + } else { + return; + } + + return $this->getReadDomainFromNode($argument); + } + + /** + * @return string|null + */ + private function getReadDomainFromNode(Node $node) + { + if ($node instanceof ConstantExpression) { + return $node->getAttribute('value'); + } + + return self::UNDEFINED_DOMAIN; + } +} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig new file mode 100644 index 0000000..5de20b1 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_horizontal_layout.html.twig @@ -0,0 +1,81 @@ +{% use "bootstrap_3_layout.html.twig" %} + +{% block form_start -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-horizontal')|trim}) %} + {{- parent() -}} +{%- endblock form_start %} + +{# Labels #} + +{% block form_label -%} +{% spaceless %} + {% if label is same as(false) %} +
    + {% else %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ block('form_label_class'))|trim}) %} + {{- parent() -}} + {% endif %} +{% endspaceless %} +{%- endblock form_label %} + +{% block form_label_class -%} +col-sm-2 +{%- endblock form_label_class %} + +{# Rows #} + +{% block form_row -%} +
    + {{- form_label(form) -}} +
    + {{- form_widget(form) -}} + {{- form_errors(form) -}} +
    +{##}
    +{%- endblock form_row %} + +{% block checkbox_row -%} + {{- block('checkbox_radio_row') -}} +{%- endblock checkbox_row %} + +{% block radio_row -%} + {{- block('checkbox_radio_row') -}} +{%- endblock radio_row %} + +{% block checkbox_radio_row -%} +{% spaceless %} +
    +
    +
    + {{ form_widget(form) }} + {{ form_errors(form) }} +
    +
    +{% endspaceless %} +{%- endblock checkbox_radio_row %} + +{% block submit_row -%} +{% spaceless %} +
    +
    +
    + {{ form_widget(form) }} +
    +
    +{% endspaceless %} +{% endblock submit_row %} + +{% block reset_row -%} +{% spaceless %} +
    +
    +
    + {{ form_widget(form) }} +
    +
    +{% endspaceless %} +{% endblock reset_row %} + +{% block form_group_class -%} +col-sm-10 +{%- endblock form_group_class %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig new file mode 100644 index 0000000..215f0ce --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/bootstrap_3_layout.html.twig @@ -0,0 +1,254 @@ +{% use "form_div_layout.html.twig" %} + +{# Widgets #} + +{% block form_widget_simple -%} + {% if type is not defined or type not in ['file', 'hidden'] %} + {%- set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) -%} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block textarea_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %} + {{- parent() -}} +{%- endblock textarea_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('btn-default') ~ ' btn')|trim}) %} + {{- parent() -}} +{%- endblock %} + +{% block money_widget -%} + {% set prepend = not (money_pattern starts with '{{') %} + {% set append = not (money_pattern ends with '}}') %} + {% if prepend or append %} +
    + {% if prepend %} + {{ money_pattern|form_encode_currency }} + {% endif %} + {{- block('form_widget_simple') -}} + {% if append %} + {{ money_pattern|form_encode_currency }} + {% endif %} +
    + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock money_widget %} + +{% block percent_widget -%} +
    + {{- block('form_widget_simple') -}} + % +
    +{%- endblock percent_widget %} + +{% block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} +
    + {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + {{- form_widget(form.date, { datetime: true } ) -}} + {{- form_widget(form.time, { datetime: true } ) -}} +
    + {%- endif %} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} + {% if datetime is not defined or not datetime -%} +
    + {%- endif %} + {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} + {% if datetime is not defined or not datetime -%} +
    + {%- endif -%} + {% endif %} +{%- endblock date_widget %} + +{% block time_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-inline')|trim}) -%} + {% if datetime is not defined or false == datetime -%} +
    + {%- endif -%} + {{- form_widget(form.hour) }}{% if with_minutes %}:{{ form_widget(form.minute) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second) }}{% endif %} + {% if datetime is not defined or false == datetime -%} +
    + {%- endif -%} + {% endif %} +{%- endblock time_widget %} + +{% block choice_widget_collapsed -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' form-control')|trim}) %} + {{- parent() -}} +{%- endblock %} + +{% block choice_widget_expanded -%} + {% if '-inline' in label_attr.class|default('') -%} + {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + }) -}} + {% endfor -%} + {%- else -%} +
    + {%- for child in form %} + {{- form_widget(child, { + parent_label_class: label_attr.class|default(''), + translation_domain: choice_translation_domain, + }) -}} + {% endfor -%} +
    + {%- endif %} +{%- endblock choice_widget_expanded %} + +{% block checkbox_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {% if 'checkbox-inline' in parent_label_class %} + {{- form_label(form, null, { widget: parent() }) -}} + {% else -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- endif %} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {%- set parent_label_class = parent_label_class|default(label_attr.class|default('')) -%} + {% if 'radio-inline' in parent_label_class %} + {{- form_label(form, null, { widget: parent() }) -}} + {% else -%} +
    + {{- form_label(form, null, { widget: parent() }) -}} +
    + {%- endif %} +{%- endblock radio_widget %} + +{# Labels #} + +{% block form_label -%} + {%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' control-label')|trim}) -%} + {{- parent() -}} +{%- endblock form_label %} + +{% block choice_label -%} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {%- set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) -%} + {{- block('form_label') -}} +{% endblock %} + +{% block checkbox_label -%} + {%- set label_attr = label_attr|merge({'for': id}) -%} + + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {%- set label_attr = label_attr|merge({'for': id}) -%} + + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{% block checkbox_radio_label %} + {# Do not display the label if widget is not defined in order to prevent double label rendering #} + {% if widget is defined %} + {% if required %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {% endif %} + {% if parent_label_class is defined %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' ' ~ parent_label_class)|trim}) %} + {% endif %} + {% if label is not same as(false) and label is empty %} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {% endif %} + + {{- widget|raw }} {{ label is not same as(false) ? (translation_domain is same as(false) ? label : label|trans({}, translation_domain)) -}} + + {% endif %} +{% endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} +
    + {{- form_label(form) -}} + {{- form_widget(form) -}} + {{- form_errors(form) -}} +
    +{%- endblock form_row %} + +{% block button_row -%} +
    + {{- form_widget(form) -}} +
    +{%- endblock button_row %} + +{% block choice_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock choice_row %} + +{% block date_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock date_row %} + +{% block time_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock time_row %} + +{% block datetime_row -%} + {% set force_error = true %} + {{- block('form_row') }} +{%- endblock datetime_row %} + +{% block checkbox_row -%} +
    + {{- form_widget(form) -}} + {{- form_errors(form) -}} +
    +{%- endblock checkbox_row %} + +{% block radio_row -%} +
    + {{- form_widget(form) -}} + {{- form_errors(form) -}} +
    +{%- endblock radio_row %} + +{# Errors #} + +{% block form_errors -%} + {% if errors|length > 0 -%} + {% if form is not rootform %}{% else %}
    {% endif %} +
      + {%- for error in errors -%} +
    • {{ error.message }}
    • + {%- endfor -%} +
    + {% if form is not rootform %}{% else %}
    {% endif %} + {%- endif %} +{%- endblock form_errors %} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig new file mode 100644 index 0000000..e424998 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/form_div_layout.html.twig @@ -0,0 +1,396 @@ +{# Widgets #} + +{%- block form_widget -%} + {% if compound %} + {{- block('form_widget_compound') -}} + {% else %} + {{- block('form_widget_simple') -}} + {% endif %} +{%- endblock form_widget -%} + +{%- block form_widget_simple -%} + {%- set type = type|default('text') -%} + +{%- endblock form_widget_simple -%} + +{%- block form_widget_compound -%} +
    + {%- if form is rootform -%} + {{ form_errors(form) }} + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}} +
    +{%- endblock form_widget_compound -%} + +{%- block collection_widget -%} + {% if prototype is defined %} + {%- set attr = attr|merge({'data-prototype': form_row(prototype) }) -%} + {% endif %} + {{- block('form_widget') -}} +{%- endblock collection_widget -%} + +{%- block textarea_widget -%} + +{%- endblock textarea_widget -%} + +{%- block choice_widget -%} + {% if expanded %} + {{- block('choice_widget_expanded') -}} + {% else %} + {{- block('choice_widget_collapsed') -}} + {% endif %} +{%- endblock choice_widget -%} + +{%- block choice_widget_expanded -%} +
    + {%- for child in form %} + {{- form_widget(child) -}} + {{- form_label(child, null, {translation_domain: choice_translation_domain}) -}} + {% endfor -%} +
    +{%- endblock choice_widget_expanded -%} + +{%- block choice_widget_collapsed -%} + {%- if required and placeholder is none and not placeholder_in_choices and not multiple and (attr.size is not defined or attr.size <= 1) -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed -%} + +{%- block choice_widget_options -%} + {% for group_label, choice in options %} + {%- if choice is iterable -%} + + {% set options = choice %} + {{- block('choice_widget_options') -}} + + {%- else -%} + + {%- endif -%} + {% endfor %} +{%- endblock choice_widget_options -%} + +{%- block checkbox_widget -%} + +{%- endblock checkbox_widget -%} + +{%- block radio_widget -%} + +{%- endblock radio_widget -%} + +{%- block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {%- else -%} +
    + {{- form_errors(form.date) -}} + {{- form_errors(form.time) -}} + {{- form_widget(form.date) -}} + {{- form_widget(form.time) -}} +
    + {%- endif -%} +{%- endblock datetime_widget -%} + +{%- block date_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} +
    + {{- date_pattern|replace({ + '{{ year }}': form_widget(form.year), + '{{ month }}': form_widget(form.month), + '{{ day }}': form_widget(form.day), + })|raw -}} +
    + {%- endif -%} +{%- endblock date_widget -%} + +{%- block time_widget -%} + {%- if widget == 'single_text' -%} + {{ block('form_widget_simple') }} + {%- else -%} + {%- set vars = widget == 'text' ? { 'attr': { 'size': 1 }} : {} -%} +
    + {{ form_widget(form.hour, vars) }}{% if with_minutes %}:{{ form_widget(form.minute, vars) }}{% endif %}{% if with_seconds %}:{{ form_widget(form.second, vars) }}{% endif %} +
    + {%- endif -%} +{%- endblock time_widget -%} + +{%- block number_widget -%} + {# type="number" doesn't work with floats #} + {%- set type = type|default('text') -%} + {{ block('form_widget_simple') }} +{%- endblock number_widget -%} + +{%- block integer_widget -%} + {%- set type = type|default('number') -%} + {{ block('form_widget_simple') }} +{%- endblock integer_widget -%} + +{%- block money_widget -%} + {{ money_pattern|form_encode_currency(block('form_widget_simple')) }} +{%- endblock money_widget -%} + +{%- block url_widget -%} + {%- set type = type|default('url') -%} + {{ block('form_widget_simple') }} +{%- endblock url_widget -%} + +{%- block search_widget -%} + {%- set type = type|default('search') -%} + {{ block('form_widget_simple') }} +{%- endblock search_widget -%} + +{%- block percent_widget -%} + {%- set type = type|default('text') -%} + {{ block('form_widget_simple') }} % +{%- endblock percent_widget -%} + +{%- block password_widget -%} + {%- set type = type|default('password') -%} + {{ block('form_widget_simple') }} +{%- endblock password_widget -%} + +{%- block hidden_widget -%} + {%- set type = type|default('hidden') -%} + {{ block('form_widget_simple') }} +{%- endblock hidden_widget -%} + +{%- block email_widget -%} + {%- set type = type|default('email') -%} + {{ block('form_widget_simple') }} +{%- endblock email_widget -%} + +{%- block range_widget -%} + {% set type = type|default('range') %} + {{- block('form_widget_simple') -}} +{%- endblock range_widget %} + +{%- block button_widget -%} + {%- if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + +{%- endblock button_widget -%} + +{%- block submit_widget -%} + {%- set type = type|default('submit') -%} + {{ block('button_widget') }} +{%- endblock submit_widget -%} + +{%- block reset_widget -%} + {%- set type = type|default('reset') -%} + {{ block('button_widget') }} +{%- endblock reset_widget -%} + +{# Labels #} + +{%- block form_label -%} + {% if label is not same as(false) -%} + {% if not compound -%} + {% set label_attr = label_attr|merge({'for': id}) %} + {%- endif -%} + {% if required -%} + {% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required')|trim}) %} + {%- endif -%} + {% if label is empty -%} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {%- endif -%} + + {%- if translation_domain is same as(false) -%} + {{- label -}} + {%- else -%} + {{- label|trans({}, translation_domain) -}} + {%- endif -%} + + {%- endif -%} +{%- endblock form_label -%} + +{%- block button_label -%}{%- endblock -%} + +{# Rows #} + +{%- block repeated_row -%} + {# + No need to render the errors here, as all errors are mapped + to the first child (see RepeatedTypeValidatorExtension). + #} + {{- block('form_rows') -}} +{%- endblock repeated_row -%} + +{%- block form_row -%} +
    + {{- form_label(form) -}} + {{- form_errors(form) -}} + {{- form_widget(form) -}} +
    +{%- endblock form_row -%} + +{%- block button_row -%} +
    + {{- form_widget(form) -}} +
    +{%- endblock button_row -%} + +{%- block hidden_row -%} + {{ form_widget(form) }} +{%- endblock hidden_row -%} + +{# Misc #} + +{%- block form -%} + {{ form_start(form) }} + {{- form_widget(form) -}} + {{ form_end(form) }} +{%- endblock form -%} + +{%- block form_start -%} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ["GET", "POST"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = "POST" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} +{%- endblock form_start -%} + +{%- block form_end -%} + {%- if not render_rest is defined or render_rest -%} + {{ form_rest(form) }} + {%- endif -%} + +{%- endblock form_end -%} + +{%- block form_enctype -%} + {% if multipart %}enctype="multipart/form-data"{% endif %} +{%- endblock form_enctype -%} + +{%- block form_errors -%} + {%- if errors|length > 0 -%} +
      + {%- for error in errors -%} +
    • {{ error.message }}
    • + {%- endfor -%} +
    + {%- endif -%} +{%- endblock form_errors -%} + +{%- block form_rest -%} + {% for child in form -%} + {% if not child.rendered %} + {{- form_row(child) -}} + {% endif %} + {%- endfor -%} + + {% if not form.methodRendered and form is rootform %} + {%- do form.setMethodRendered() -%} + {% set method = method|upper %} + {%- if method in ["GET", "POST"] -%} + {% set form_method = method %} + {%- else -%} + {% set form_method = "POST" %} + {%- endif -%} + + {%- if form_method != method -%} + + {%- endif -%} + {% endif -%} +{% endblock form_rest %} + +{# Support #} + +{%- block form_rows -%} + {% for child in form %} + {{- form_row(child) -}} + {% endfor %} +{%- endblock form_rows -%} + +{%- block widget_attributes -%} + id="{{ id }}" name="{{ full_name }}" + {%- if read_only %} readonly="readonly"{% endif -%} + {%- if disabled %} disabled="disabled"{% endif -%} + {%- if required %} required="required"{% endif -%} + {%- for attrname, attrvalue in attr if 'readonly' != attrname or not read_only -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" + {%- elseif attrvalue is same as(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not same as(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} +{%- endblock widget_attributes -%} + +{%- block widget_container_attributes -%} + {%- if id is not empty %}id="{{ id }}"{% endif -%} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" + {%- elseif attrvalue is same as(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not same as(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} +{%- endblock widget_container_attributes -%} + +{%- block button_attributes -%} + id="{{ id }}" name="{{ full_name }}"{% if disabled %} disabled="disabled"{% endif -%} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" + {%- elseif attrvalue is same as(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not same as(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} +{%- endblock button_attributes -%} + +{% block attributes -%} + {%- for attrname, attrvalue in attr -%} + {{- " " -}} + {%- if attrname in ['placeholder', 'title'] -%} + {{- attrname }}="{{ translation_domain is same as(false) ? attrvalue : attrvalue|trans({}, translation_domain) }}" + {%- elseif attrvalue is same as(true) -%} + {{- attrname }}="{{ attrname }}" + {%- elseif attrvalue is not same as(false) -%} + {{- attrname }}="{{ attrvalue }}" + {%- endif -%} + {%- endfor -%} +{%- endblock attributes -%} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig new file mode 100644 index 0000000..39274c6 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/form_table_layout.html.twig @@ -0,0 +1,44 @@ +{% use "form_div_layout.html.twig" %} + +{%- block form_row -%} + + + {{- form_label(form) -}} + + + {{- form_errors(form) -}} + {{- form_widget(form) -}} + + +{%- endblock form_row -%} + +{%- block button_row -%} + + + + {{- form_widget(form) -}} + + +{%- endblock button_row -%} + +{%- block hidden_row -%} + + + {{- form_widget(form) -}} + + +{%- endblock hidden_row -%} + +{%- block form_widget_compound -%} + + {%- if form is rootform and errors|length > 0 -%} + + + + {%- endif -%} + {{- block('form_rows') -}} + {{- form_rest(form) -}} +
    + {{- form_errors(form) -}} +
    +{%- endblock form_widget_compound -%} diff --git a/vendor/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig b/vendor/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig new file mode 100644 index 0000000..dc7bec9 --- /dev/null +++ b/vendor/symfony/twig-bridge/Resources/views/Form/foundation_5_layout.html.twig @@ -0,0 +1,328 @@ +{% extends "form_div_layout.html.twig" %} + +{# Based on Foundation 5 Doc #} +{# Widgets #} + +{% block form_widget_simple -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_widget_simple %} + +{% block textarea_widget -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock textarea_widget %} + +{% block button_widget -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' button')|trim}) %} + {{- parent() -}} +{%- endblock %} + +{% block money_widget -%} +
    + {% set prepend = '{{' == money_pattern[0:2] %} + {% if not prepend %} +
    + {{ money_pattern|replace({ '{{ widget }}':''}) }} +
    + {% endif %} +
    + {{- block('form_widget_simple') -}} +
    + {% if prepend %} +
    + {{ money_pattern|replace({ '{{ widget }}':''}) }} +
    + {% endif %} +
    +{%- endblock money_widget %} + +{% block percent_widget -%} +
    +
    + {{- block('form_widget_simple') -}} +
    +
    + % +
    +
    +{%- endblock percent_widget %} + +{% block datetime_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} +
    +
    {{ form_errors(form.date) }}
    +
    {{ form_errors(form.time) }}
    +
    +
    +
    {{ form_widget(form.date, { datetime: true } ) }}
    +
    {{ form_widget(form.time, { datetime: true } ) }}
    +
    + {% endif %} +{%- endblock datetime_widget %} + +{% block date_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or not datetime %} +
    + {% endif %} + {{- date_pattern|replace({ + '{{ year }}': '
    ' ~ form_widget(form.year) ~ '
    ', + '{{ month }}': '
    ' ~ form_widget(form.month) ~ '
    ', + '{{ day }}': '
    ' ~ form_widget(form.day) ~ '
    ', + })|raw -}} + {% if datetime is not defined or not datetime %} +
    + {% endif %} + {% endif %} +{%- endblock date_widget %} + +{% block time_widget -%} + {% if widget == 'single_text' %} + {{- block('form_widget_simple') -}} + {% else %} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' row')|trim}) %} + {% if datetime is not defined or false == datetime %} +
    + {% endif %} + {% if with_seconds %} +
    {{ form_widget(form.hour) }}
    +
    +
    +
    + : +
    +
    + {{ form_widget(form.minute) }} +
    +
    +
    +
    +
    +
    + : +
    +
    + {{ form_widget(form.second) }} +
    +
    +
    + {% else %} +
    {{ form_widget(form.hour) }}
    +
    +
    +
    + : +
    +
    + {{ form_widget(form.minute) }} +
    +
    +
    + {% endif %} + {% if datetime is not defined or false == datetime %} +
    + {% endif %} + {% endif %} +{%- endblock time_widget %} + +{% block choice_widget_collapsed -%} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + + {% if multiple -%} + {% set attr = attr|merge({style: (attr.style|default('') ~ ' height: auto; background-image: none;')|trim}) %} + {% endif %} + + {% if required and placeholder is none and not placeholder_in_choices and not multiple -%} + {% set required = false %} + {%- endif -%} + +{%- endblock choice_widget_collapsed %} + +{% block choice_widget_expanded -%} + {% if '-inline' in label_attr.class|default('') %} +
      + {% for child in form %} +
    • {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }}
    • + {% endfor %} +
    + {% else %} +
    + {% for child in form %} + {{ form_widget(child, { + parent_label_class: label_attr.class|default(''), + }) }} + {% endfor %} +
    + {% endif %} +{%- endblock choice_widget_expanded %} + +{% block checkbox_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {% if 'checkbox-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} +
    + {{ form_label(form, null, { widget: parent() }) }} +
    + {% endif %} +{%- endblock checkbox_widget %} + +{% block radio_widget -%} + {% set parent_label_class = parent_label_class|default('') %} + {% if 'radio-inline' in parent_label_class %} + {{ form_label(form, null, { widget: parent() }) }} + {% else %} + {% if errors|length > 0 -%} + {% set attr = attr|merge({class: (attr.class|default('') ~ ' error')|trim}) %} + {% endif %} +
    + {{ form_label(form, null, { widget: parent() }) }} +
    + {% endif %} +{%- endblock radio_widget %} + +{# Labels #} + +{% block form_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {{- parent() -}} +{%- endblock form_label %} + +{% block choice_label -%} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {# remove the checkbox-inline and radio-inline class, it's only useful for embed labels #} + {% set label_attr = label_attr|merge({class: label_attr.class|default('')|replace({'checkbox-inline': '', 'radio-inline': ''})|trim}) %} + {{- block('form_label') -}} +{%- endblock %} + +{% block checkbox_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock checkbox_label %} + +{% block radio_label -%} + {{- block('checkbox_radio_label') -}} +{%- endblock radio_label %} + +{% block checkbox_radio_label -%} + {% if required %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} + {% endif %} + {% if errors|length > 0 -%} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' error')|trim}) %} + {% endif %} + {% if parent_label_class is defined %} + {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ parent_label_class)|trim}) %} + {% endif %} + {% if label is empty %} + {%- if label_format is not empty -%} + {% set label = label_format|replace({ + '%name%': name, + '%id%': id, + }) %} + {%- else -%} + {% set label = name|humanize %} + {%- endif -%} + {% endif %} + + {{ widget|raw }} + {{ translation_domain is same as(false) ? label : label|trans({}, translation_domain) }} + +{%- endblock checkbox_radio_label %} + +{# Rows #} + +{% block form_row -%} +
    +
    + {{ form_label(form) }} + {{ form_widget(form) }} + {{ form_errors(form) }} +
    +
    +{%- endblock form_row %} + +{% block choice_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock choice_row %} + +{% block date_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock date_row %} + +{% block time_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock time_row %} + +{% block datetime_row -%} + {% set force_error = true %} + {{ block('form_row') }} +{%- endblock datetime_row %} + +{% block checkbox_row -%} +
    +
    + {{ form_widget(form) }} + {{ form_errors(form) }} +
    +
    +{%- endblock checkbox_row %} + +{% block radio_row -%} +
    +
    + {{ form_widget(form) }} + {{ form_errors(form) }} +
    +
    +{%- endblock radio_row %} + +{# Errors #} + +{% block form_errors -%} + {% if errors|length > 0 -%} + {% if form.parent %}{% else %}
    {% endif %} + {%- for error in errors -%} + {{ error.message }} + {% if not loop.last %}, {% endif %} + {%- endfor -%} + {% if form.parent %}{% else %}
    {% endif %} + {%- endif %} +{%- endblock form_errors %} diff --git a/vendor/symfony/twig-bridge/TokenParser/DumpTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/DumpTokenParser.php new file mode 100644 index 0000000..a4d7d6f --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/DumpTokenParser.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\DumpNode; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the 'dump' tag. + * + * Dump variables with: + * + * {% dump %} + * {% dump foo %} + * {% dump foo, bar %} + * + * @author Julien Galenski + */ +class DumpTokenParser extends AbstractTokenParser +{ + /** + * {@inheritdoc} + */ + public function parse(Token $token) + { + $values = null; + if (!$this->parser->getStream()->test(Token::BLOCK_END_TYPE)) { + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + } + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new DumpNode($this->parser->getVarName(), $values, $token->getLine(), $this->getTag()); + } + + /** + * {@inheritdoc} + */ + public function getTag() + { + return 'dump'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php new file mode 100644 index 0000000..7301813 --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/FormThemeTokenParser.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\FormThemeNode; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Node; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the 'form_theme' tag. + * + * @author Fabien Potencier + */ +class FormThemeTokenParser extends AbstractTokenParser +{ + /** + * Parses a token and returns a node. + * + * @return Node + */ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + $form = $this->parser->getExpressionParser()->parseExpression(); + + if ($this->parser->getStream()->test(Token::NAME_TYPE, 'with')) { + $this->parser->getStream()->next(); + $resources = $this->parser->getExpressionParser()->parseExpression(); + } else { + $resources = new ArrayExpression(array(), $stream->getCurrent()->getLine()); + do { + $resources->addElement($this->parser->getExpressionParser()->parseExpression()); + } while (!$stream->test(Token::BLOCK_END_TYPE)); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new FormThemeNode($form, $resources, $lineno, $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'form_theme'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php new file mode 100644 index 0000000..82c58d4 --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/StopwatchTokenParser.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\StopwatchNode; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the stopwatch tag. + * + * @author Wouter J + */ +class StopwatchTokenParser extends AbstractTokenParser +{ + protected $stopwatchIsAvailable; + + public function __construct($stopwatchIsAvailable) + { + $this->stopwatchIsAvailable = $stopwatchIsAvailable; + } + + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + // {% stopwatch 'bar' %} + $name = $this->parser->getExpressionParser()->parseExpression(); + + $stream->expect(Token::BLOCK_END_TYPE); + + // {% endstopwatch %} + $body = $this->parser->subparse(array($this, 'decideStopwatchEnd'), true); + $stream->expect(Token::BLOCK_END_TYPE); + + if ($this->stopwatchIsAvailable) { + return new StopwatchNode($name, $body, new AssignNameExpression($this->parser->getVarName(), $token->getLine()), $lineno, $this->getTag()); + } + + return $body; + } + + public function decideStopwatchEnd(Token $token) + { + return $token->test('endstopwatch'); + } + + public function getTag() + { + return 'stopwatch'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php new file mode 100644 index 0000000..5261d7a --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/TransChoiceTokenParser.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Error\SyntaxError; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Node; +use Twig\Node\TextNode; +use Twig\Token; + +/** + * Token Parser for the 'transchoice' tag. + * + * @author Fabien Potencier + */ +class TransChoiceTokenParser extends TransTokenParser +{ + /** + * Parses a token and returns a node. + * + * @return Node + * + * @throws SyntaxError + */ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + $vars = new ArrayExpression(array(), $lineno); + + $count = $this->parser->getExpressionParser()->parseExpression(); + + $domain = null; + $locale = null; + + if ($stream->test('with')) { + // {% transchoice count with vars %} + $stream->next(); + $vars = $this->parser->getExpressionParser()->parseExpression(); + } + + if ($stream->test('from')) { + // {% transchoice count from "messages" %} + $stream->next(); + $domain = $this->parser->getExpressionParser()->parseExpression(); + } + + if ($stream->test('into')) { + // {% transchoice count into "fr" %} + $stream->next(); + $locale = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse(array($this, 'decideTransChoiceFork'), true); + + if (!$body instanceof TextNode && !$body instanceof AbstractExpression) { + throw new SyntaxError('A message inside a transchoice tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext()->getName()); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new TransNode($body, $domain, $count, $vars, $locale, $lineno, $this->getTag()); + } + + public function decideTransChoiceFork($token) + { + return $token->test(array('endtranschoice')); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'transchoice'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php new file mode 100644 index 0000000..ee546e0 --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/TransDefaultDomainTokenParser.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\TransDefaultDomainNode; +use Twig\Node\Node; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the 'trans_default_domain' tag. + * + * @author Fabien Potencier + */ +class TransDefaultDomainTokenParser extends AbstractTokenParser +{ + /** + * Parses a token and returns a node. + * + * @return Node + */ + public function parse(Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new TransDefaultDomainNode($expr, $token->getLine(), $this->getTag()); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'trans_default_domain'; + } +} diff --git a/vendor/symfony/twig-bridge/TokenParser/TransTokenParser.php b/vendor/symfony/twig-bridge/TokenParser/TransTokenParser.php new file mode 100644 index 0000000..76c8dc0 --- /dev/null +++ b/vendor/symfony/twig-bridge/TokenParser/TransTokenParser.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\TransNode; +use Twig\Error\SyntaxError; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Node; +use Twig\Node\TextNode; +use Twig\Token; +use Twig\TokenParser\AbstractTokenParser; + +/** + * Token Parser for the 'trans' tag. + * + * @author Fabien Potencier + */ +class TransTokenParser extends AbstractTokenParser +{ + /** + * Parses a token and returns a node. + * + * @return Node + * + * @throws SyntaxError + */ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + $vars = new ArrayExpression(array(), $lineno); + $domain = null; + $locale = null; + if (!$stream->test(Token::BLOCK_END_TYPE)) { + if ($stream->test('with')) { + // {% trans with vars %} + $stream->next(); + $vars = $this->parser->getExpressionParser()->parseExpression(); + } + + if ($stream->test('from')) { + // {% trans from "messages" %} + $stream->next(); + $domain = $this->parser->getExpressionParser()->parseExpression(); + } + + if ($stream->test('into')) { + // {% trans into "fr" %} + $stream->next(); + $locale = $this->parser->getExpressionParser()->parseExpression(); + } elseif (!$stream->test(Token::BLOCK_END_TYPE)) { + throw new SyntaxError('Unexpected token. Twig was looking for the "with", "from", or "into" keyword.', $stream->getCurrent()->getLine(), $stream->getSourceContext()->getName()); + } + } + + // {% trans %}message{% endtrans %} + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse(array($this, 'decideTransFork'), true); + + if (!$body instanceof TextNode && !$body instanceof AbstractExpression) { + throw new SyntaxError('A message inside a trans tag must be a simple text.', $body->getTemplateLine(), $stream->getSourceContext()->getName()); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new TransNode($body, $domain, null, $vars, $locale, $lineno, $this->getTag()); + } + + public function decideTransFork($token) + { + return $token->test(array('endtrans')); + } + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag() + { + return 'trans'; + } +} diff --git a/vendor/symfony/twig-bridge/Translation/TwigExtractor.php b/vendor/symfony/twig-bridge/Translation/TwigExtractor.php new file mode 100644 index 0000000..a921582 --- /dev/null +++ b/vendor/symfony/twig-bridge/Translation/TwigExtractor.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Translation; + +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; +use Symfony\Component\Translation\Extractor\AbstractFileExtractor; +use Symfony\Component\Translation\Extractor\ExtractorInterface; +use Symfony\Component\Translation\MessageCatalogue; +use Twig\Environment; +use Twig\Error\Error; +use Twig\Source; + +/** + * TwigExtractor extracts translation messages from a twig template. + * + * @author Michel Salib + * @author Fabien Potencier + */ +class TwigExtractor extends AbstractFileExtractor implements ExtractorInterface +{ + /** + * Default domain for found messages. + * + * @var string + */ + private $defaultDomain = 'messages'; + + /** + * Prefix for found message. + * + * @var string + */ + private $prefix = ''; + + private $twig; + + public function __construct(Environment $twig) + { + $this->twig = $twig; + } + + /** + * {@inheritdoc} + */ + public function extract($resource, MessageCatalogue $catalogue) + { + foreach ($this->extractFiles($resource) as $file) { + try { + $this->extractTemplate(file_get_contents($file->getPathname()), $catalogue); + } catch (Error $e) { + if ($file instanceof \SplFileInfo) { + $path = $file->getRealPath() ?: $file->getPathname(); + $name = $file instanceof SplFileInfo ? $file->getRelativePathname() : $path; + if (method_exists($e, 'setSourceContext')) { + $e->setSourceContext(new Source('', $name, $path)); + } else { + $e->setTemplateName($name); + } + } + + throw $e; + } + } + } + + /** + * {@inheritdoc} + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + protected function extractTemplate($template, MessageCatalogue $catalogue) + { + $visitor = $this->twig->getExtension('Symfony\Bridge\Twig\Extension\TranslationExtension')->getTranslationNodeVisitor(); + $visitor->enable(); + + $this->twig->parse($this->twig->tokenize(new Source($template, ''))); + + foreach ($visitor->getMessages() as $message) { + $catalogue->set(trim($message[0]), $this->prefix.trim($message[0]), $message[1] ?: $this->defaultDomain); + } + + $visitor->disable(); + } + + /** + * @param string $file + * + * @return bool + */ + protected function canBeExtracted($file) + { + return $this->isFile($file) && 'twig' === pathinfo($file, PATHINFO_EXTENSION); + } + + /** + * @param string|array $directory + * + * @return array + */ + protected function extractFromDirectory($directory) + { + $finder = new Finder(); + + return $finder->files()->name('*.twig')->in($directory); + } +} diff --git a/vendor/symfony/twig-bridge/TwigEngine.php b/vendor/symfony/twig-bridge/TwigEngine.php new file mode 100644 index 0000000..bc0a4fe --- /dev/null +++ b/vendor/symfony/twig-bridge/TwigEngine.php @@ -0,0 +1,130 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig; + +use Symfony\Component\Templating\EngineInterface; +use Symfony\Component\Templating\StreamingEngineInterface; +use Symfony\Component\Templating\TemplateNameParserInterface; +use Symfony\Component\Templating\TemplateReferenceInterface; +use Twig\Environment; +use Twig\Error\Error; +use Twig\Error\LoaderError; +use Twig\Loader\ExistsLoaderInterface; +use Twig\Template; + +/** + * This engine knows how to render Twig templates. + * + * @author Fabien Potencier + */ +class TwigEngine implements EngineInterface, StreamingEngineInterface +{ + protected $environment; + protected $parser; + + public function __construct(Environment $environment, TemplateNameParserInterface $parser) + { + $this->environment = $environment; + $this->parser = $parser; + } + + /** + * {@inheritdoc} + * + * It also supports Template as name parameter. + * + * @throws Error if something went wrong like a thrown exception while rendering the template + */ + public function render($name, array $parameters = array()) + { + return $this->load($name)->render($parameters); + } + + /** + * {@inheritdoc} + * + * It also supports Template as name parameter. + * + * @throws Error if something went wrong like a thrown exception while rendering the template + */ + public function stream($name, array $parameters = array()) + { + $this->load($name)->display($parameters); + } + + /** + * {@inheritdoc} + * + * It also supports Template as name parameter. + */ + public function exists($name) + { + if ($name instanceof Template) { + return true; + } + + $loader = $this->environment->getLoader(); + + if ($loader instanceof ExistsLoaderInterface || method_exists($loader, 'exists')) { + return $loader->exists((string) $name); + } + + try { + // cast possible TemplateReferenceInterface to string because the + // EngineInterface supports them but LoaderInterface does not + $loader->getSourceContext((string) $name)->getCode(); + } catch (LoaderError $e) { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + * + * It also supports Template as name parameter. + */ + public function supports($name) + { + if ($name instanceof Template) { + return true; + } + + $template = $this->parser->parse($name); + + return 'twig' === $template->get('engine'); + } + + /** + * Loads the given template. + * + * @param string|TemplateReferenceInterface|Template $name A template name or an instance of + * TemplateReferenceInterface or Template + * + * @return Template + * + * @throws \InvalidArgumentException if the template does not exist + */ + protected function load($name) + { + if ($name instanceof Template) { + return $name; + } + + try { + return $this->environment->loadTemplate((string) $name); + } catch (LoaderError $e) { + throw new \InvalidArgumentException($e->getMessage(), $e->getCode(), $e); + } + } +} diff --git a/vendor/symfony/twig-bridge/composer.json b/vendor/symfony/twig-bridge/composer.json new file mode 100644 index 0000000..48af8a7 --- /dev/null +++ b/vendor/symfony/twig-bridge/composer.json @@ -0,0 +1,69 @@ +{ + "name": "symfony/twig-bridge", + "type": "symfony-bridge", + "description": "Symfony Twig Bridge", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "twig/twig": "~1.34|~2.4" + }, + "require-dev": { + "symfony/asset": "~2.7|~3.0.0", + "symfony/finder": "~2.3|~3.0.0", + "symfony/form": "^2.8.23", + "symfony/http-foundation": "^2.8.29|~3.0.0", + "symfony/http-kernel": "~2.8|~3.0.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.2|~3.0.0", + "symfony/templating": "~2.1|~3.0.0", + "symfony/translation": "~2.7|~3.0.0", + "symfony/yaml": "^2.0.5|~3.0.0", + "symfony/security": "^2.8.31|^3.3.13", + "symfony/security-acl": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.2|~3.0.0", + "symfony/console": "~2.8|~3.0.0", + "symfony/var-dumper": "~2.7.16|~2.8.9|~3.0.9", + "symfony/expression-language": "~2.4|~3.0.0" + }, + "conflict": { + "symfony/form": "<2.8.23" + }, + "suggest": { + "symfony/finder": "", + "symfony/asset": "For using the AssetExtension", + "symfony/form": "For using the FormExtension", + "symfony/http-kernel": "For using the HttpKernelExtension", + "symfony/routing": "For using the RoutingExtension", + "symfony/templating": "For using the TwigEngine", + "symfony/translation": "For using the TranslationExtension", + "symfony/yaml": "For using the YamlExtension", + "symfony/security": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/var-dumper": "For using the DumpExtension", + "symfony/expression-language": "For using the ExpressionExtension" + }, + "autoload": { + "psr-4": { "Symfony\\Bridge\\Twig\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/symfony/yaml/Dumper.php b/vendor/symfony/yaml/Dumper.php new file mode 100644 index 0000000..8b523d1 --- /dev/null +++ b/vendor/symfony/yaml/Dumper.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +/** + * Dumper dumps PHP variables to YAML strings. + * + * @author Fabien Potencier + */ +class Dumper +{ + /** + * The amount of spaces to use for indentation of nested nodes. + * + * @var int + */ + protected $indentation = 4; + + /** + * Sets the indentation. + * + * @param int $num The amount of spaces to use for indentation of nested nodes + */ + public function setIndentation($num) + { + if ($num < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $this->indentation = (int) $num; + } + + /** + * Dumps a PHP value to YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The level of indentation (used internally) + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * + * @return string The YAML representation of the PHP value + */ + public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false) + { + $output = ''; + $prefix = $indent ? str_repeat(' ', $indent) : ''; + + if ($inline <= 0 || !\is_array($input) || empty($input)) { + $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport); + } else { + $isAHash = Inline::isHash($input); + + foreach ($input as $key => $value) { + $willBeInlined = $inline - 1 <= 0 || !\is_array($value) || empty($value); + + $output .= sprintf('%s%s%s%s', + $prefix, + $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-', + $willBeInlined ? ' ' : "\n", + $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport) + ).($willBeInlined ? "\n" : ''); + } + } + + return $output; + } +} diff --git a/vendor/symfony/yaml/Escaper.php b/vendor/symfony/yaml/Escaper.php new file mode 100644 index 0000000..2b1321f --- /dev/null +++ b/vendor/symfony/yaml/Escaper.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +/** + * Escaper encapsulates escaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Escaper +{ + // Characters that would cause a dumped string to require double quoting. + const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; + + // Mapping arrays for escaping a double quoted string. The backslash is + // first to ensure proper escaping because str_replace operates iteratively + // on the input arrays. This ordering of the characters avoids the use of strtr, + // which performs more slowly. + private static $escapees = array('\\', '\\\\', '\\"', '"', + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", + "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9", + ); + private static $escaped = array('\\\\', '\\"', '\\\\', '\\"', + '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a', + '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f', + '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17', + '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f', + '\\N', '\\_', '\\L', '\\P', + ); + + /** + * Determines if a PHP value would require double quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require double quotes + */ + public static function requiresDoubleQuoting($value) + { + return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); + } + + /** + * Escapes and surrounds a PHP value with double quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithDoubleQuotes($value) + { + return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value)); + } + + /** + * Determines if a PHP value would require single quoting in YAML. + * + * @param string $value A PHP value + * + * @return bool True if the value would require single quotes + */ + public static function requiresSingleQuoting($value) + { + // Determines if a PHP value is entirely composed of a value that would + // require single quoting in YAML. + if (\in_array(strtolower($value), array('null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'))) { + return true; + } + + // Determines if the PHP value contains any single characters that would + // cause it to require single quoting in YAML. + return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value); + } + + /** + * Escapes and surrounds a PHP value with single quotes. + * + * @param string $value A PHP value + * + * @return string The quoted, escaped string + */ + public static function escapeWithSingleQuotes($value) + { + return sprintf("'%s'", str_replace('\'', '\'\'', $value)); + } +} diff --git a/vendor/symfony/yaml/Exception/DumpException.php b/vendor/symfony/yaml/Exception/DumpException.php new file mode 100644 index 0000000..cce972f --- /dev/null +++ b/vendor/symfony/yaml/Exception/DumpException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during dumping. + * + * @author Fabien Potencier + */ +class DumpException extends RuntimeException +{ +} diff --git a/vendor/symfony/yaml/Exception/ExceptionInterface.php b/vendor/symfony/yaml/Exception/ExceptionInterface.php new file mode 100644 index 0000000..ad850ee --- /dev/null +++ b/vendor/symfony/yaml/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Fabien Potencier + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/yaml/Exception/ParseException.php b/vendor/symfony/yaml/Exception/ParseException.php new file mode 100644 index 0000000..60802b6 --- /dev/null +++ b/vendor/symfony/yaml/Exception/ParseException.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Fabien Potencier + */ +class ParseException extends RuntimeException +{ + private $parsedFile; + private $parsedLine; + private $snippet; + private $rawMessage; + + /** + * @param string $message The error message + * @param int $parsedLine The line where the error occurred + * @param string|null $snippet The snippet of code near the problem + * @param string|null $parsedFile The file name where the error occurred + * @param \Exception|null $previous The previous exception + */ + public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null) + { + $this->parsedFile = $parsedFile; + $this->parsedLine = $parsedLine; + $this->snippet = $snippet; + $this->rawMessage = $message; + + $this->updateRepr(); + + parent::__construct($this->message, 0, $previous); + } + + /** + * Gets the snippet of code near the error. + * + * @return string The snippet of code + */ + public function getSnippet() + { + return $this->snippet; + } + + /** + * Sets the snippet of code near the error. + * + * @param string $snippet The code snippet + */ + public function setSnippet($snippet) + { + $this->snippet = $snippet; + + $this->updateRepr(); + } + + /** + * Gets the filename where the error occurred. + * + * This method returns null if a string is parsed. + * + * @return string The filename + */ + public function getParsedFile() + { + return $this->parsedFile; + } + + /** + * Sets the filename where the error occurred. + * + * @param string $parsedFile The filename + */ + public function setParsedFile($parsedFile) + { + $this->parsedFile = $parsedFile; + + $this->updateRepr(); + } + + /** + * Gets the line where the error occurred. + * + * @return int The file line + */ + public function getParsedLine() + { + return $this->parsedLine; + } + + /** + * Sets the line where the error occurred. + * + * @param int $parsedLine The file line + */ + public function setParsedLine($parsedLine) + { + $this->parsedLine = $parsedLine; + + $this->updateRepr(); + } + + private function updateRepr() + { + $this->message = $this->rawMessage; + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + if (null !== $this->parsedFile) { + if (\PHP_VERSION_ID >= 50400) { + $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; + } else { + $jsonOptions = 0; + } + $this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions)); + } + + if ($this->parsedLine >= 0) { + $this->message .= sprintf(' at line %d', $this->parsedLine); + } + + if ($this->snippet) { + $this->message .= sprintf(' (near "%s")', $this->snippet); + } + + if ($dot) { + $this->message .= '.'; + } + } +} diff --git a/vendor/symfony/yaml/Exception/RuntimeException.php b/vendor/symfony/yaml/Exception/RuntimeException.php new file mode 100644 index 0000000..3f36b73 --- /dev/null +++ b/vendor/symfony/yaml/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml\Exception; + +/** + * Exception class thrown when an error occurs during parsing. + * + * @author Romain Neutron + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/yaml/Inline.php b/vendor/symfony/yaml/Inline.php new file mode 100644 index 0000000..639ff4a --- /dev/null +++ b/vendor/symfony/yaml/Inline.php @@ -0,0 +1,609 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\DumpException; +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Inline implements a YAML parser/dumper for the YAML inline syntax. + * + * @author Fabien Potencier + */ +class Inline +{ + const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')'; + + private static $exceptionOnInvalidType = false; + private static $objectSupport = false; + private static $objectForMap = false; + + /** + * Converts a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * @param array $references Mapping of variable names to values + * + * @return mixed A PHP value + * + * @throws ParseException + */ + public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array()) + { + self::$exceptionOnInvalidType = $exceptionOnInvalidType; + self::$objectSupport = $objectSupport; + self::$objectForMap = $objectForMap; + + $value = trim($value); + + if ('' === $value) { + return ''; + } + + if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + $i = 0; + switch ($value[0]) { + case '[': + $result = self::parseSequence($value, $i, $references); + ++$i; + break; + case '{': + $result = self::parseMapping($value, $i, $references); + ++$i; + break; + default: + $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references); + } + + // some comments are allowed at the end + if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { + throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); + } + + if (isset($mbEncoding)) { + mb_internal_encoding($mbEncoding); + } + + return $result; + } + + /** + * Dumps a given PHP variable to a YAML string. + * + * @param mixed $value The PHP variable to convert + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * + * @return string The YAML string representing the PHP value + * + * @throws DumpException When trying to dump PHP resource + */ + public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) + { + switch (true) { + case \is_resource($value): + if ($exceptionOnInvalidType) { + throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); + } + + return 'null'; + case \is_object($value): + if ($objectSupport) { + return '!php/object:'.serialize($value); + } + + if ($exceptionOnInvalidType) { + throw new DumpException('Object support when dumping a YAML file has been disabled.'); + } + + return 'null'; + case \is_array($value): + return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); + case null === $value: + return 'null'; + case true === $value: + return 'true'; + case false === $value: + return 'false'; + case ctype_digit($value): + return \is_string($value) ? "'$value'" : (int) $value; + case is_numeric($value): + $locale = setlocale(LC_NUMERIC, 0); + if (false !== $locale) { + setlocale(LC_NUMERIC, 'C'); + } + if (\is_float($value)) { + $repr = (string) $value; + if (is_infinite($value)) { + $repr = str_ireplace('INF', '.Inf', $repr); + } elseif (floor($value) == $value && $repr == $value) { + // Preserve float data type since storing a whole number will result in integer value. + $repr = '!!float '.$repr; + } + } else { + $repr = \is_string($value) ? "'$value'" : (string) $value; + } + if (false !== $locale) { + setlocale(LC_NUMERIC, $locale); + } + + return $repr; + case '' == $value: + return "''"; + case Escaper::requiresDoubleQuoting($value): + return Escaper::escapeWithDoubleQuotes($value); + case Escaper::requiresSingleQuoting($value): + case Parser::preg_match(self::getHexRegex(), $value): + case Parser::preg_match(self::getTimestampRegex(), $value): + return Escaper::escapeWithSingleQuotes($value); + default: + return $value; + } + } + + /** + * Check if given array is hash or just normal indexed array. + * + * @internal + * + * @param array $value The PHP array to check + * + * @return bool true if value is hash array, false otherwise + */ + public static function isHash(array $value) + { + $expectedKey = 0; + + foreach ($value as $key => $val) { + if ($key !== $expectedKey++) { + return true; + } + } + + return false; + } + + /** + * Dumps a PHP array to a YAML string. + * + * @param array $value The PHP array to dump + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * + * @return string The YAML string representing the PHP array + */ + private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport) + { + // array + if ($value && !self::isHash($value)) { + $output = array(); + foreach ($value as $val) { + $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport); + } + + return sprintf('[%s]', implode(', ', $output)); + } + + // hash + $output = array(); + foreach ($value as $key => $val) { + $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport)); + } + + return sprintf('{ %s }', implode(', ', $output)); + } + + /** + * Parses a YAML scalar. + * + * @param string $scalar + * @param string[] $delimiters + * @param string[] $stringDelimiters + * @param int &$i + * @param bool $evaluate + * @param array $references + * + * @return string + * + * @throws ParseException When malformed inline YAML string is parsed + * + * @internal + */ + public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array()) + { + if (\in_array($scalar[$i], $stringDelimiters)) { + // quoted scalar + $output = self::parseQuotedScalar($scalar, $i); + + if (null !== $delimiters) { + $tmp = ltrim(substr($scalar, $i), ' '); + if ('' === $tmp) { + throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters))); + } + if (!\in_array($tmp[0], $delimiters)) { + throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); + } + } + } else { + // "normal" string + if (!$delimiters) { + $output = substr($scalar, $i); + $i += \strlen($output); + + // remove comments + if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) { + $output = substr($output, 0, $match[0][1]); + } + } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { + $output = $match[1]; + $i += \strlen($output); + } else { + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar)); + } + + // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >) + if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) { + @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED); + + // to be thrown in 3.0 + // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0])); + } + + if ($evaluate) { + $output = self::evaluateScalar($output, $references); + } + } + + return $output; + } + + /** + * Parses a YAML quoted scalar. + * + * @param string $scalar + * @param int &$i + * + * @return string + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseQuotedScalar($scalar, &$i) + { + if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { + throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i))); + } + + $output = substr($match[0], 1, \strlen($match[0]) - 2); + + $unescaper = new Unescaper(); + if ('"' == $scalar[$i]) { + $output = $unescaper->unescapeDoubleQuotedString($output); + } else { + $output = $unescaper->unescapeSingleQuotedString($output); + } + + $i += \strlen($match[0]); + + return $output; + } + + /** + * Parses a YAML sequence. + * + * @param string $sequence + * @param int &$i + * @param array $references + * + * @return array + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseSequence($sequence, &$i = 0, $references = array()) + { + $output = array(); + $len = \strlen($sequence); + ++$i; + + // [foo, bar, ...] + while ($i < $len) { + switch ($sequence[$i]) { + case '[': + // nested sequence + $output[] = self::parseSequence($sequence, $i, $references); + break; + case '{': + // nested mapping + $output[] = self::parseMapping($sequence, $i, $references); + break; + case ']': + return $output; + case ',': + case ' ': + break; + default: + $isQuoted = \in_array($sequence[$i], array('"', "'")); + $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references); + + // the value can be an array if a reference has been resolved to an array var + if (!\is_array($value) && !$isQuoted && false !== strpos($value, ': ')) { + // embedded mapping? + try { + $pos = 0; + $value = self::parseMapping('{'.$value.'}', $pos, $references); + } catch (\InvalidArgumentException $e) { + // no, it's not + } + } + + $output[] = $value; + + --$i; + } + + ++$i; + } + + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence)); + } + + /** + * Parses a YAML mapping. + * + * @param string $mapping + * @param int &$i + * @param array $references + * + * @return array|\stdClass + * + * @throws ParseException When malformed inline YAML string is parsed + */ + private static function parseMapping($mapping, &$i = 0, $references = array()) + { + $output = array(); + $len = \strlen($mapping); + ++$i; + $allowOverwrite = false; + + // {foo: bar, bar:foo, ...} + while ($i < $len) { + switch ($mapping[$i]) { + case ' ': + case ',': + ++$i; + continue 2; + case '}': + if (self::$objectForMap) { + return (object) $output; + } + + return $output; + } + + // key + $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + + if ('<<' === $key) { + $allowOverwrite = true; + } + + // value + $done = false; + + while ($i < $len) { + switch ($mapping[$i]) { + case '[': + // nested sequence + $value = self::parseSequence($mapping, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + break; + case '{': + // nested mapping + $value = self::parseMapping($mapping, $i, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + break; + case ':': + case ' ': + break; + default: + $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references); + // Spec: Keys MUST be unique; first one wins. + // Parser cannot abort this mapping earlier, since lines + // are processed sequentially. + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { + $output[$key] = $value; + } + $done = true; + --$i; + } + + ++$i; + + if ($done) { + continue 2; + } + } + } + + throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping)); + } + + /** + * Evaluates scalars and replaces magic values. + * + * @param string $scalar + * @param array $references + * + * @return mixed The evaluated YAML string + * + * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved + */ + private static function evaluateScalar($scalar, $references = array()) + { + $scalar = trim($scalar); + $scalarLower = strtolower($scalar); + + if (0 === strpos($scalar, '*')) { + if (false !== $pos = strpos($scalar, '#')) { + $value = substr($scalar, 1, $pos - 2); + } else { + $value = substr($scalar, 1); + } + + // an unquoted * + if (false === $value || '' === $value) { + throw new ParseException('A reference must contain at least one character.'); + } + + if (!array_key_exists($value, $references)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value)); + } + + return $references[$value]; + } + + switch (true) { + case 'null' === $scalarLower: + case '' === $scalar: + case '~' === $scalar: + return; + case 'true' === $scalarLower: + return true; + case 'false' === $scalarLower: + return false; + // Optimise for returning strings. + case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]): + switch (true) { + case 0 === strpos($scalar, '!str'): + return (string) substr($scalar, 5); + case 0 === strpos($scalar, '! '): + return (int) self::parseScalar(substr($scalar, 2)); + case 0 === strpos($scalar, '!php/object:'): + if (self::$objectSupport) { + return unserialize(substr($scalar, 12)); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.'); + } + + return; + case 0 === strpos($scalar, '!!php/object:'): + if (self::$objectSupport) { + return unserialize(substr($scalar, 13)); + } + + if (self::$exceptionOnInvalidType) { + throw new ParseException('Object support when parsing a YAML file has been disabled.'); + } + + return; + case 0 === strpos($scalar, '!!float '): + return (float) substr($scalar, 8); + case ctype_digit($scalar): + $raw = $scalar; + $cast = (int) $scalar; + + return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); + case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): + $raw = $scalar; + $cast = (int) $scalar; + + return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw); + case is_numeric($scalar): + case Parser::preg_match(self::getHexRegex(), $scalar): + return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar; + case '.inf' === $scalarLower: + case '.nan' === $scalarLower: + return -log(0); + case '-.inf' === $scalarLower: + return log(0); + case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): + return (float) str_replace(',', '', $scalar); + case Parser::preg_match(self::getTimestampRegex(), $scalar): + $timeZone = date_default_timezone_get(); + date_default_timezone_set('UTC'); + $time = strtotime($scalar); + date_default_timezone_set($timeZone); + + return $time; + } + // no break + default: + return (string) $scalar; + } + } + + /** + * Gets a regex that matches a YAML date. + * + * @return string The regular expression + * + * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 + */ + private static function getTimestampRegex() + { + return <<[0-9][0-9][0-9][0-9]) + -(?P[0-9][0-9]?) + -(?P[0-9][0-9]?) + (?:(?:[Tt]|[ \t]+) + (?P[0-9][0-9]?) + :(?P[0-9][0-9]) + :(?P[0-9][0-9]) + (?:\.(?P[0-9]*))? + (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) + (?::(?P[0-9][0-9]))?))?)? + $~x +EOF; + } + + /** + * Gets a regex that matches a YAML number in hexadecimal notation. + * + * @return string + */ + private static function getHexRegex() + { + return '~^0x[0-9a-f]++$~i'; + } +} diff --git a/vendor/symfony/yaml/LICENSE b/vendor/symfony/yaml/LICENSE new file mode 100644 index 0000000..21d7fb9 --- /dev/null +++ b/vendor/symfony/yaml/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2018 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/symfony/yaml/Parser.php b/vendor/symfony/yaml/Parser.php new file mode 100644 index 0000000..cb0d8f1 --- /dev/null +++ b/vendor/symfony/yaml/Parser.php @@ -0,0 +1,852 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Parser parses YAML strings to convert them to PHP arrays. + * + * @author Fabien Potencier + */ +class Parser +{ + const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; + // BC - wrongly named + const FOLDED_SCALAR_PATTERN = self::BLOCK_SCALAR_HEADER_PATTERN; + + private $offset = 0; + private $totalNumberOfLines; + private $lines = array(); + private $currentLineNb = -1; + private $currentLine = ''; + private $refs = array(); + private $skippedLineNumbers = array(); + private $locallySkippedLineNumbers = array(); + + /** + * @param int $offset The offset of YAML document (used for line numbers in error messages) + * @param int|null $totalNumberOfLines The overall number of lines being parsed + * @param int[] $skippedLineNumbers Number of comment lines that have been skipped by the parser + */ + public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array()) + { + $this->offset = $offset; + $this->totalNumberOfLines = $totalNumberOfLines; + $this->skippedLineNumbers = $skippedLineNumbers; + } + + /** + * Parses a YAML string to a PHP value. + * + * @param string $value A YAML string + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * + * @return mixed A PHP value + * + * @throws ParseException If the YAML is not valid + */ + public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) + { + if (false === preg_match('//u', $value)) { + throw new ParseException('The YAML value does not appear to be valid UTF-8.'); + } + + $this->refs = array(); + + $mbEncoding = null; + $e = null; + $data = null; + + if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('UTF-8'); + } + + try { + $data = $this->doParse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } catch (\Exception $e) { + } catch (\Throwable $e) { + } + + if (null !== $mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + $this->lines = array(); + $this->currentLine = ''; + $this->refs = array(); + $this->skippedLineNumbers = array(); + $this->locallySkippedLineNumbers = array(); + + if (null !== $e) { + throw $e; + } + + return $data; + } + + private function doParse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) + { + $this->currentLineNb = -1; + $this->currentLine = ''; + $value = $this->cleanup($value); + $this->lines = explode("\n", $value); + $this->locallySkippedLineNumbers = array(); + + if (null === $this->totalNumberOfLines) { + $this->totalNumberOfLines = \count($this->lines); + } + + $data = array(); + $context = null; + $allowOverwrite = false; + + while ($this->moveToNextLine()) { + if ($this->isCurrentLineEmpty()) { + continue; + } + + // tab? + if ("\t" === $this->currentLine[0]) { + throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + $isRef = $mergeNode = false; + if (self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) { + if ($context && 'mapping' == $context) { + throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + $context = 'sequence'; + + if (isset($values['value']) && self::preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + // array + if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { + $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap); + } else { + if (isset($values['leadspaces']) + && self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+))?$#u', rtrim($values['value']), $matches) + ) { + // this is a compact notation element, add to next block and parse + $block = $values['value']; + if ($this->isNextLineIndented()) { + $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1); + } + + $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } else { + $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context); + } + } + if ($isRef) { + $this->refs[$isRef] = end($data); + } + } elseif ( + self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+))?$#u', rtrim($this->currentLine), $values) + && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], array('"', "'"))) + ) { + if ($context && 'sequence' == $context) { + throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine); + } + $context = 'mapping'; + + // force correct settings + Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + try { + $key = Inline::parseScalar($values['key']); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + // Convert float keys to strings, to avoid being converted to integers by PHP + if (\is_float($key)) { + $key = (string) $key; + } + + if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P[^ ]+)#u', $values['value'], $refMatches))) { + $mergeNode = true; + $allowOverwrite = true; + if (isset($values['value']) && 0 === strpos($values['value'], '*')) { + $refName = substr($values['value'], 1); + if (!array_key_exists($refName, $this->refs)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + $refValue = $this->refs[$refName]; + + if (!\is_array($refValue)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + $data += $refValue; // array union + } else { + if (isset($values['value']) && '' !== $values['value']) { + $value = $values['value']; + } else { + $value = $this->getNextEmbedBlock(); + } + $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap); + + if (!\is_array($parsed)) { + throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + + if (isset($parsed[0])) { + // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes + // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier + // in the sequence override keys specified in later mapping nodes. + foreach ($parsed as $parsedItem) { + if (!\is_array($parsedItem)) { + throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); + } + + $data += $parsedItem; // array union + } + } else { + // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the + // current mapping, unless the key already exists in it. + $data += $parsed; // array union + } + } + } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { + $isRef = $matches['ref']; + $values['value'] = $matches['value']; + } + + if ($mergeNode) { + // Merge keys + } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#') || '<<' === $key) { + // hash + // if next line is less indented or equal, then it means that the current value is null + if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = null; + } + } else { + $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap); + + if ('<<' === $key) { + $this->refs[$refMatches['ref']] = $value; + $data += $value; + } elseif ($allowOverwrite || !isset($data[$key])) { + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + $data[$key] = $value; + } + } + } else { + $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context); + // Spec: Keys MUST be unique; first one wins. + // But overwriting is allowed when a merge node is used in current block. + if ($allowOverwrite || !isset($data[$key])) { + $data[$key] = $value; + } + } + if ($isRef) { + $this->refs[$isRef] = $data[$key]; + } + } else { + // multiple documents are not supported + if ('---' === $this->currentLine) { + throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine); + } + + // 1-liner optionally followed by newline(s) + if (\is_string($value) && $this->lines[0] === trim($value)) { + try { + $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + + return $value; + } + + throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + + if ($objectForMap && !\is_object($data) && 'mapping' === $context) { + $object = new \stdClass(); + + foreach ($data as $key => $value) { + $object->$key = $value; + } + + $data = $object; + } + + return empty($data) ? null : $data; + } + + private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap) + { + $skippedLineNumbers = $this->skippedLineNumbers; + + foreach ($this->locallySkippedLineNumbers as $lineNumber) { + if ($lineNumber < $offset) { + continue; + } + + $skippedLineNumbers[] = $lineNumber; + } + + $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers); + $parser->refs = &$this->refs; + + return $parser->doParse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } + + /** + * Returns the current line number (takes the offset into account). + * + * @return int The current line number + */ + private function getRealCurrentLineNb() + { + $realCurrentLineNumber = $this->currentLineNb + $this->offset; + + foreach ($this->skippedLineNumbers as $skippedLineNumber) { + if ($skippedLineNumber > $realCurrentLineNumber) { + break; + } + + ++$realCurrentLineNumber; + } + + return $realCurrentLineNumber; + } + + /** + * Returns the current line indentation. + * + * @return int The current line indentation + */ + private function getCurrentLineIndentation() + { + return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' ')); + } + + /** + * Returns the next embed block of YAML. + * + * @param int $indentation The indent level at which the block is to be read, or null for default + * @param bool $inSequence True if the enclosing data structure is a sequence + * + * @return string A YAML string + * + * @throws ParseException When indentation problem are detected + */ + private function getNextEmbedBlock($indentation = null, $inSequence = false) + { + $oldLineIndentation = $this->getCurrentLineIndentation(); + $blockScalarIndentations = array(); + + if ($this->isBlockScalarHeader()) { + $blockScalarIndentations[] = $this->getCurrentLineIndentation(); + } + + if (!$this->moveToNextLine()) { + return; + } + + if (null === $indentation) { + $newIndent = $this->getCurrentLineIndentation(); + + $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem(); + + if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } else { + $newIndent = $indentation; + } + + $data = array(); + if ($this->getCurrentLineIndentation() >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } else { + $this->moveToPreviousLine(); + + return; + } + + if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) { + // the previous line contained a dash but no item content, this line is a sequence item with the same indentation + // and therefore no nested list or mapping + $this->moveToPreviousLine(); + + return; + } + + $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem(); + + if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) { + $blockScalarIndentations[] = $this->getCurrentLineIndentation(); + } + + $previousLineIndentation = $this->getCurrentLineIndentation(); + + while ($this->moveToNextLine()) { + $indent = $this->getCurrentLineIndentation(); + + // terminate all block scalars that are more indented than the current line + if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) { + foreach ($blockScalarIndentations as $key => $blockScalarIndentation) { + if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) { + unset($blockScalarIndentations[$key]); + } + } + } + + if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) { + $blockScalarIndentations[] = $this->getCurrentLineIndentation(); + } + + $previousLineIndentation = $indent; + + if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) { + $this->moveToPreviousLine(); + break; + } + + if ($this->isCurrentLineBlank()) { + $data[] = substr($this->currentLine, $newIndent); + continue; + } + + // we ignore "comment" lines only when we are not inside a scalar block + if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) { + // remember ignored comment lines (they are used later in nested + // parser calls to determine real line numbers) + // + // CAUTION: beware to not populate the global property here as it + // will otherwise influence the getRealCurrentLineNb() call here + // for consecutive comment lines and subsequent embedded blocks + $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb(); + + continue; + } + + if ($indent >= $newIndent) { + $data[] = substr($this->currentLine, $newIndent); + } elseif (0 == $indent) { + $this->moveToPreviousLine(); + + break; + } else { + throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); + } + } + + return implode("\n", $data); + } + + /** + * Moves the parser to the next line. + * + * @return bool + */ + private function moveToNextLine() + { + if ($this->currentLineNb >= \count($this->lines) - 1) { + return false; + } + + $this->currentLine = $this->lines[++$this->currentLineNb]; + + return true; + } + + /** + * Moves the parser to the previous line. + * + * @return bool + */ + private function moveToPreviousLine() + { + if ($this->currentLineNb < 1) { + return false; + } + + $this->currentLine = $this->lines[--$this->currentLineNb]; + + return true; + } + + /** + * Parses a YAML value. + * + * @param string $value A YAML value + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * @param string $context The parser context (either sequence or mapping) + * + * @return mixed A PHP value + * + * @throws ParseException When reference does not exist + */ + private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context) + { + if (0 === strpos($value, '*')) { + if (false !== $pos = strpos($value, '#')) { + $value = substr($value, 1, $pos - 2); + } else { + $value = substr($value, 1); + } + + if (!array_key_exists($value, $this->refs)) { + throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine); + } + + return $this->refs[$value]; + } + + if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) { + $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; + + return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers)); + } + + try { + $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs); + + if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) { + @trigger_error(sprintf('Using a colon in the unquoted mapping value "%s" in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $value, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED); + + // to be thrown in 3.0 + // throw new ParseException('A colon cannot be used in an unquoted mapping value.'); + } + + return $parsedValue; + } catch (ParseException $e) { + $e->setParsedLine($this->getRealCurrentLineNb() + 1); + $e->setSnippet($this->currentLine); + + throw $e; + } + } + + /** + * Parses a block scalar. + * + * @param string $style The style indicator that was used to begin this block scalar (| or >) + * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -) + * @param int $indentation The indentation indicator that was used to begin this block scalar + * + * @return string The text value + */ + private function parseBlockScalar($style, $chomping = '', $indentation = 0) + { + $notEOF = $this->moveToNextLine(); + if (!$notEOF) { + return ''; + } + + $isCurrentLineBlank = $this->isCurrentLineBlank(); + $blockLines = array(); + + // leading blank lines are consumed before determining indentation + while ($notEOF && $isCurrentLineBlank) { + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $blockLines[] = ''; + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + + // determine indentation if not specified + if (0 === $indentation) { + if (self::preg_match('/^ +/', $this->currentLine, $matches)) { + $indentation = \strlen($matches[0]); + } + } + + if ($indentation > 0) { + $pattern = sprintf('/^ {%d}(.*)$/', $indentation); + + while ( + $notEOF && ( + $isCurrentLineBlank || + self::preg_match($pattern, $this->currentLine, $matches) + ) + ) { + if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) { + $blockLines[] = substr($this->currentLine, $indentation); + } elseif ($isCurrentLineBlank) { + $blockLines[] = ''; + } else { + $blockLines[] = $matches[1]; + } + + // newline only if not EOF + if ($notEOF = $this->moveToNextLine()) { + $isCurrentLineBlank = $this->isCurrentLineBlank(); + } + } + } elseif ($notEOF) { + $blockLines[] = ''; + } + + if ($notEOF) { + $blockLines[] = ''; + $this->moveToPreviousLine(); + } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) { + $blockLines[] = ''; + } + + // folded style + if ('>' === $style) { + $text = ''; + $previousLineIndented = false; + $previousLineBlank = false; + + for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) { + if ('' === $blockLines[$i]) { + $text .= "\n"; + $previousLineIndented = false; + $previousLineBlank = true; + } elseif (' ' === $blockLines[$i][0]) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = true; + $previousLineBlank = false; + } elseif ($previousLineIndented) { + $text .= "\n".$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } elseif ($previousLineBlank || 0 === $i) { + $text .= $blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } else { + $text .= ' '.$blockLines[$i]; + $previousLineIndented = false; + $previousLineBlank = false; + } + } + } else { + $text = implode("\n", $blockLines); + } + + // deal with trailing newlines + if ('' === $chomping) { + $text = preg_replace('/\n+$/', "\n", $text); + } elseif ('-' === $chomping) { + $text = preg_replace('/\n+$/', '', $text); + } + + return $text; + } + + /** + * Returns true if the next line is indented. + * + * @return bool Returns true if the next line is indented, false otherwise + */ + private function isNextLineIndented() + { + $currentIndentation = $this->getCurrentLineIndentation(); + $EOF = !$this->moveToNextLine(); + + while (!$EOF && $this->isCurrentLineEmpty()) { + $EOF = !$this->moveToNextLine(); + } + + if ($EOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() > $currentIndentation; + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the current line is blank or if it is a comment line. + * + * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise + */ + private function isCurrentLineEmpty() + { + return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); + } + + /** + * Returns true if the current line is blank. + * + * @return bool Returns true if the current line is blank, false otherwise + */ + private function isCurrentLineBlank() + { + return '' == trim($this->currentLine, ' '); + } + + /** + * Returns true if the current line is a comment line. + * + * @return bool Returns true if the current line is a comment line, false otherwise + */ + private function isCurrentLineComment() + { + //checking explicitly the first char of the trim is faster than loops or strpos + $ltrimmedLine = ltrim($this->currentLine, ' '); + + return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0]; + } + + private function isCurrentLineLastLineInDocument() + { + return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1); + } + + /** + * Cleanups a YAML string to be parsed. + * + * @param string $value The input YAML string + * + * @return string A cleaned up YAML string + */ + private function cleanup($value) + { + $value = str_replace(array("\r\n", "\r"), "\n", $value); + + // strip YAML header + $count = 0; + $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count); + $this->offset += $count; + + // remove leading comments + $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); + if (1 == $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + } + + // remove start of the document marker (---) + $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); + if (1 == $count) { + // items have been removed, update the offset + $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); + $value = $trimmedValue; + + // remove end of the document marker (...) + $value = preg_replace('#\.\.\.\s*$#', '', $value); + } + + return $value; + } + + /** + * Returns true if the next line starts unindented collection. + * + * @return bool Returns true if the next line starts unindented collection, false otherwise + */ + private function isNextLineUnIndentedCollection() + { + $currentIndentation = $this->getCurrentLineIndentation(); + $notEOF = $this->moveToNextLine(); + + while ($notEOF && $this->isCurrentLineEmpty()) { + $notEOF = $this->moveToNextLine(); + } + + if (false === $notEOF) { + return false; + } + + $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem(); + + $this->moveToPreviousLine(); + + return $ret; + } + + /** + * Returns true if the string is un-indented collection item. + * + * @return bool Returns true if the string is un-indented collection item, false otherwise + */ + private function isStringUnIndentedCollectionItem() + { + return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- '); + } + + /** + * Tests whether or not the current line is the header of a block scalar. + * + * @return bool + */ + private function isBlockScalarHeader() + { + return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine); + } + + /** + * A local wrapper for `preg_match` which will throw a ParseException if there + * is an internal error in the PCRE engine. + * + * This avoids us needing to check for "false" every time PCRE is used + * in the YAML engine + * + * @throws ParseException on a PCRE internal error + * + * @see preg_last_error() + * + * @internal + */ + public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0) + { + if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) { + switch (preg_last_error()) { + case PREG_INTERNAL_ERROR: + $error = 'Internal PCRE error.'; + break; + case PREG_BACKTRACK_LIMIT_ERROR: + $error = 'pcre.backtrack_limit reached.'; + break; + case PREG_RECURSION_LIMIT_ERROR: + $error = 'pcre.recursion_limit reached.'; + break; + case PREG_BAD_UTF8_ERROR: + $error = 'Malformed UTF-8 data.'; + break; + case PREG_BAD_UTF8_OFFSET_ERROR: + $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; + break; + default: + $error = 'Error.'; + } + + throw new ParseException($error); + } + + return $ret; + } +} diff --git a/vendor/symfony/yaml/Unescaper.php b/vendor/symfony/yaml/Unescaper.php new file mode 100644 index 0000000..d0dbcfa --- /dev/null +++ b/vendor/symfony/yaml/Unescaper.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +/** + * Unescaper encapsulates unescaping rules for single and double-quoted + * YAML strings. + * + * @author Matthew Lewinski + * + * @internal + */ +class Unescaper +{ + /** + * Parser and Inline assume UTF-8 encoding, so escaped Unicode characters + * must be converted to that encoding. + * + * @deprecated since version 2.5, to be removed in 3.0 + * + * @internal + */ + const ENCODING = 'UTF-8'; + + /** + * Regex fragment that matches an escaped character in a double quoted string. + */ + const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)'; + + /** + * Unescapes a single quoted string. + * + * @param string $value A single quoted string + * + * @return string The unescaped string + */ + public function unescapeSingleQuotedString($value) + { + return str_replace('\'\'', '\'', $value); + } + + /** + * Unescapes a double quoted string. + * + * @param string $value A double quoted string + * + * @return string The unescaped string + */ + public function unescapeDoubleQuotedString($value) + { + $self = $this; + $callback = function ($match) use ($self) { + return $self->unescapeCharacter($match[0]); + }; + + // evaluate the string + return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); + } + + /** + * Unescapes a character that was found in a double-quoted string. + * + * @param string $value An escaped character + * + * @return string The unescaped character + * + * @internal This method is public to be usable as callback. It should not + * be used in user code. Should be changed in 3.0. + */ + public function unescapeCharacter($value) + { + switch ($value[1]) { + case '0': + return "\x0"; + case 'a': + return "\x7"; + case 'b': + return "\x8"; + case 't': + return "\t"; + case "\t": + return "\t"; + case 'n': + return "\n"; + case 'v': + return "\xB"; + case 'f': + return "\xC"; + case 'r': + return "\r"; + case 'e': + return "\x1B"; + case ' ': + return ' '; + case '"': + return '"'; + case '/': + return '/'; + case '\\': + return '\\'; + case 'N': + // U+0085 NEXT LINE + return "\xC2\x85"; + case '_': + // U+00A0 NO-BREAK SPACE + return "\xC2\xA0"; + case 'L': + // U+2028 LINE SEPARATOR + return "\xE2\x80\xA8"; + case 'P': + // U+2029 PARAGRAPH SEPARATOR + return "\xE2\x80\xA9"; + case 'x': + return self::utf8chr(hexdec(substr($value, 2, 2))); + case 'u': + return self::utf8chr(hexdec(substr($value, 2, 4))); + case 'U': + return self::utf8chr(hexdec(substr($value, 2, 8))); + default: + @trigger_error('Not escaping a backslash in a double-quoted string is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', E_USER_DEPRECATED); + + return $value; + } + } + + /** + * Get the UTF-8 character for the given code point. + * + * @param int $c The unicode code point + * + * @return string The corresponding UTF-8 character + */ + private static function utf8chr($c) + { + if (0x80 > $c %= 0x200000) { + return \chr($c); + } + if (0x800 > $c) { + return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); + } + if (0x10000 > $c) { + return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } + + return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); + } +} diff --git a/vendor/symfony/yaml/Yaml.php b/vendor/symfony/yaml/Yaml.php new file mode 100644 index 0000000..3f93cba --- /dev/null +++ b/vendor/symfony/yaml/Yaml.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Yaml; + +use Symfony\Component\Yaml\Exception\ParseException; + +/** + * Yaml offers convenience methods to load and dump YAML. + * + * @author Fabien Potencier + */ +class Yaml +{ + /** + * Parses YAML into a PHP value. + * + * Usage: + * + * $array = Yaml::parse(file_get_contents('config.yml')); + * print_r($array); + * + * As this method accepts both plain strings and file names as an input, + * you must validate the input before calling this method. Passing a file + * as an input is a deprecated feature and will be removed in 3.0. + * + * Note: the ability to pass file names to the Yaml::parse method is deprecated since Symfony 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead. + * + * @param string $input Path to a YAML file or a string containing YAML + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * @param bool $objectForMap True if maps should return a stdClass instead of array() + * + * @return mixed The YAML converted to a PHP value + * + * @throws ParseException If the YAML is not valid + */ + public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false) + { + // if input is a file, process it + $file = ''; + if (false === strpos($input, "\n") && is_file($input)) { + @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); + + if (false === is_readable($input)) { + throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input)); + } + + $file = $input; + $input = file_get_contents($file); + } + + $yaml = new Parser(); + + try { + return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap); + } catch (ParseException $e) { + if ($file) { + $e->setParsedFile($file); + } + + throw $e; + } + } + + /** + * Dumps a PHP value to a YAML string. + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. + * + * @param mixed $input The PHP value + * @param int $inline The level where you switch to inline YAML + * @param int $indent The amount of spaces to use for indentation of nested nodes + * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise + * @param bool $objectSupport True if object support is enabled, false otherwise + * + * @return string A YAML string representing the original PHP value + */ + public static function dump($input, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false) + { + if ($indent < 1) { + throw new \InvalidArgumentException('The indentation must be greater than zero.'); + } + + $yaml = new Dumper(); + $yaml->setIndentation($indent); + + return $yaml->dump($input, $inline, 0, $exceptionOnInvalidType, $objectSupport); + } +} diff --git a/vendor/symfony/yaml/composer.json b/vendor/symfony/yaml/composer.json new file mode 100644 index 0000000..d073236 --- /dev/null +++ b/vendor/symfony/yaml/composer.json @@ -0,0 +1,34 @@ +{ + "name": "symfony/yaml", + "type": "library", + "description": "Symfony Yaml Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.9", + "symfony/polyfill-ctype": "~1.8" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Yaml\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + } +} diff --git a/vendor/twig/twig/.php_cs.dist b/vendor/twig/twig/.php_cs.dist new file mode 100644 index 0000000..1b31c0a --- /dev/null +++ b/vendor/twig/twig/.php_cs.dist @@ -0,0 +1,18 @@ +setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + 'array_syntax' => ['syntax' => 'short'], + 'php_unit_fqcn_annotation' => true, + 'no_unreachable_default_argument_value' => false, + 'braces' => ['allow_single_line_closure' => true], + 'heredoc_to_nowdoc' => false, + 'ordered_imports' => true, + 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'], + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'all'], + ]) + ->setRiskyAllowed(true) + ->setFinder(PhpCsFixer\Finder::create()->in(__DIR__)) +; diff --git a/vendor/twig/twig/LICENSE b/vendor/twig/twig/LICENSE new file mode 100644 index 0000000..d06ced2 --- /dev/null +++ b/vendor/twig/twig/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2009-2019 by the Twig Team. + +Some rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/twig/twig/composer.json b/vendor/twig/twig/composer.json new file mode 100644 index 0000000..84a08a7 --- /dev/null +++ b/vendor/twig/twig/composer.json @@ -0,0 +1,48 @@ +{ + "name": "twig/twig", + "type": "library", + "description": "Twig, the flexible, fast, and secure template language for PHP", + "keywords": ["templating"], + "homepage": "https://twig.symfony.com", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "homepage": "https://twig.symfony.com/contributors", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "require": { + "php": ">=5.4.0", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.4.19|^4.1.8", + "symfony/debug": "^2.7", + "psr/container": "^1.0" + }, + "autoload": { + "psr-0" : { + "Twig_" : "lib/" + }, + "psr-4" : { + "Twig\\" : "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.39-dev" + } + } +} diff --git a/vendor/twig/twig/lib/Twig/Autoloader.php b/vendor/twig/twig/lib/Twig/Autoloader.php new file mode 100644 index 0000000..e34e214 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Autoloader.php @@ -0,0 +1,52 @@ + + * + * @deprecated since 1.21 and will be removed in 2.0. Use Composer instead. 2.0. + */ +class Twig_Autoloader +{ + /** + * Registers Twig_Autoloader as an SPL autoloader. + * + * @param bool $prepend whether to prepend the autoloader or not + */ + public static function register($prepend = false) + { + @trigger_error('Using Twig_Autoloader is deprecated since version 1.21. Use Composer instead.', E_USER_DEPRECATED); + + spl_autoload_register([__CLASS__, 'autoload'], true, $prepend); + } + + /** + * Handles autoloading of classes. + * + * @param string $class a class name + */ + public static function autoload($class) + { + if (0 !== strpos($class, 'Twig')) { + return; + } + + if (is_file($file = __DIR__.'/../'.str_replace(['_', "\0"], ['/', ''], $class).'.php')) { + require $file; + } elseif (is_file($file = __DIR__.'/../../src/'.str_replace(['Twig\\', '\\', "\0"], ['', '/', ''], $class).'.php')) { + require $file; + } + } +} diff --git a/vendor/twig/twig/lib/Twig/BaseNodeVisitor.php b/vendor/twig/twig/lib/Twig/BaseNodeVisitor.php new file mode 100644 index 0000000..fe99b25 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/BaseNodeVisitor.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_CompilerInterface +{ + /** + * Compiles a node. + * + * @return $this + */ + public function compile(Twig_NodeInterface $node); + + /** + * Gets the current PHP code after compilation. + * + * @return string The PHP code + */ + public function getSource(); +} diff --git a/vendor/twig/twig/lib/Twig/ContainerRuntimeLoader.php b/vendor/twig/twig/lib/Twig/ContainerRuntimeLoader.php new file mode 100644 index 0000000..b1f32f6 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/ContainerRuntimeLoader.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface +{ + protected $options; + protected $arguments = []; + + public function __construct(array $options = []) + { + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'pre_escape' => null, + 'preserves_safety' => null, + 'callable' => null, + ], $options); + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Node $filterArgs) + { + if (isset($this->options['is_safe'])) { + return $this->options['is_safe']; + } + + if (isset($this->options['is_safe_callback'])) { + return \call_user_func($this->options['is_safe_callback'], $filterArgs); + } + } + + public function getPreservesSafety() + { + return $this->options['preserves_safety']; + } + + public function getPreEscape() + { + return $this->options['pre_escape']; + } + + public function getCallable() + { + return $this->options['callable']; + } +} diff --git a/vendor/twig/twig/lib/Twig/Filter/Function.php b/vendor/twig/twig/lib/Twig/Filter/Function.php new file mode 100644 index 0000000..011d4cc --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Filter/Function.php @@ -0,0 +1,40 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Filter_Function extends Twig_Filter +{ + protected $function; + + public function __construct($function, array $options = []) + { + $options['callable'] = $function; + + parent::__construct($options); + + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/vendor/twig/twig/lib/Twig/Filter/Method.php b/vendor/twig/twig/lib/Twig/Filter/Method.php new file mode 100644 index 0000000..5cd0628 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Filter/Method.php @@ -0,0 +1,44 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Filter_Method extends Twig_Filter +{ + protected $extension; + protected $method; + + public function __construct(ExtensionInterface $extension, $method, array $options = []) + { + $options['callable'] = [$extension, $method]; + + parent::__construct($options); + + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($this->extension), $this->method); + } +} diff --git a/vendor/twig/twig/lib/Twig/Filter/Node.php b/vendor/twig/twig/lib/Twig/Filter/Node.php new file mode 100644 index 0000000..8bb2899 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Filter/Node.php @@ -0,0 +1,42 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Filter_Node extends Twig_Filter +{ + protected $class; + + public function __construct($class, array $options = []) + { + parent::__construct($options); + + $this->class = $class; + } + + public function getClass() + { + return $this->class; + } + + public function compile() + { + } +} diff --git a/vendor/twig/twig/lib/Twig/FilterCallableInterface.php b/vendor/twig/twig/lib/Twig/FilterCallableInterface.php new file mode 100644 index 0000000..091ca97 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/FilterCallableInterface.php @@ -0,0 +1,24 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_FilterCallableInterface +{ + public function getCallable(); +} diff --git a/vendor/twig/twig/lib/Twig/FilterInterface.php b/vendor/twig/twig/lib/Twig/FilterInterface.php new file mode 100644 index 0000000..9b85f97 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/FilterInterface.php @@ -0,0 +1,45 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_FilterInterface +{ + /** + * Compiles a filter. + * + * @return string The PHP code for the filter + */ + public function compile(); + + public function needsEnvironment(); + + public function needsContext(); + + public function getSafe(Node $filterArgs); + + public function getPreservesSafety(); + + public function getPreEscape(); + + public function setArguments($arguments); + + public function getArguments(); +} diff --git a/vendor/twig/twig/lib/Twig/Function.php b/vendor/twig/twig/lib/Twig/Function.php new file mode 100644 index 0000000..6646e74 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Function.php @@ -0,0 +1,76 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface +{ + protected $options; + protected $arguments = []; + + public function __construct(array $options = []) + { + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'callable' => null, + ], $options); + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Node $functionArgs) + { + if (isset($this->options['is_safe'])) { + return $this->options['is_safe']; + } + + if (isset($this->options['is_safe_callback'])) { + return \call_user_func($this->options['is_safe_callback'], $functionArgs); + } + + return []; + } + + public function getCallable() + { + return $this->options['callable']; + } +} diff --git a/vendor/twig/twig/lib/Twig/Function/Function.php b/vendor/twig/twig/lib/Twig/Function/Function.php new file mode 100644 index 0000000..605d8d3 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Function/Function.php @@ -0,0 +1,41 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Function_Function extends Twig_Function +{ + protected $function; + + public function __construct($function, array $options = []) + { + $options['callable'] = $function; + + parent::__construct($options); + + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/vendor/twig/twig/lib/Twig/Function/Method.php b/vendor/twig/twig/lib/Twig/Function/Method.php new file mode 100644 index 0000000..9e472c5 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Function/Method.php @@ -0,0 +1,45 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Function_Method extends Twig_Function +{ + protected $extension; + protected $method; + + public function __construct(ExtensionInterface $extension, $method, array $options = []) + { + $options['callable'] = [$extension, $method]; + + parent::__construct($options); + + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($this->extension), $this->method); + } +} diff --git a/vendor/twig/twig/lib/Twig/Function/Node.php b/vendor/twig/twig/lib/Twig/Function/Node.php new file mode 100644 index 0000000..8148ec3 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Function/Node.php @@ -0,0 +1,42 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Function_Node extends Twig_Function +{ + protected $class; + + public function __construct($class, array $options = []) + { + parent::__construct($options); + + $this->class = $class; + } + + public function getClass() + { + return $this->class; + } + + public function compile() + { + } +} diff --git a/vendor/twig/twig/lib/Twig/FunctionCallableInterface.php b/vendor/twig/twig/lib/Twig/FunctionCallableInterface.php new file mode 100644 index 0000000..abc83ea --- /dev/null +++ b/vendor/twig/twig/lib/Twig/FunctionCallableInterface.php @@ -0,0 +1,24 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_FunctionCallableInterface +{ + public function getCallable(); +} diff --git a/vendor/twig/twig/lib/Twig/FunctionInterface.php b/vendor/twig/twig/lib/Twig/FunctionInterface.php new file mode 100644 index 0000000..915d6cc --- /dev/null +++ b/vendor/twig/twig/lib/Twig/FunctionInterface.php @@ -0,0 +1,42 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_FunctionInterface +{ + /** + * Compiles a function. + * + * @return string The PHP code for the function + */ + public function compile(); + + public function needsEnvironment(); + + public function needsContext(); + + public function getSafe(Node $filterArgs); + + public function setArguments($arguments); + + public function getArguments(); +} diff --git a/vendor/twig/twig/lib/Twig/Lexer.php b/vendor/twig/twig/lib/Twig/Lexer.php new file mode 100644 index 0000000..00d74cc --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Lexer.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_LexerInterface +{ + /** + * Tokenizes a source code. + * + * @param string|Source $code The source code + * @param string $name A unique identifier for the source code + * + * @return TokenStream + * + * @throws SyntaxError When the code is syntactically wrong + */ + public function tokenize($code, $name = null); +} diff --git a/vendor/twig/twig/lib/Twig/Loader/Array.php b/vendor/twig/twig/lib/Twig/Loader/Array.php new file mode 100644 index 0000000..13f915c --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Loader/Array.php @@ -0,0 +1,11 @@ + + */ +class Twig_Loader_String implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface +{ + public function getSource($name) + { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED); + + return $name; + } + + public function getSourceContext($name) + { + return new Source($name, $name); + } + + public function exists($name) + { + return true; + } + + public function getCacheKey($name) + { + return $name; + } + + public function isFresh($name, $time) + { + return true; + } +} diff --git a/vendor/twig/twig/lib/Twig/LoaderInterface.php b/vendor/twig/twig/lib/Twig/LoaderInterface.php new file mode 100644 index 0000000..db515bb --- /dev/null +++ b/vendor/twig/twig/lib/Twig/LoaderInterface.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.23 and will be removed in 2.0. + */ +class Twig_Node_Expression_ExtensionReference extends AbstractExpression +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct([], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name'))); + } +} diff --git a/vendor/twig/twig/lib/Twig/Node/Expression/Filter.php b/vendor/twig/twig/lib/Twig/Node/Expression/Filter.php new file mode 100644 index 0000000..8570571 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Node/Expression/Filter.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_NodeInterface extends \Countable, \IteratorAggregate +{ + /** + * Compiles the node to PHP. + */ + public function compile(Compiler $compiler); + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getLine(); + + public function getNodeTag(); +} diff --git a/vendor/twig/twig/lib/Twig/NodeOutputInterface.php b/vendor/twig/twig/lib/Twig/NodeOutputInterface.php new file mode 100644 index 0000000..54009c0 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/NodeOutputInterface.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_ParserInterface +{ + /** + * Converts a token stream to a node tree. + * + * @return ModuleNode + * + * @throws SyntaxError When the token stream is syntactically or semantically wrong + */ + public function parse(TokenStream $stream); +} diff --git a/vendor/twig/twig/lib/Twig/Profiler/Dumper/Base.php b/vendor/twig/twig/lib/Twig/Profiler/Dumper/Base.php new file mode 100644 index 0000000..5bcb1ba --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Profiler/Dumper/Base.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface Twig_TemplateInterface +{ + const ANY_CALL = 'any'; + const ARRAY_CALL = 'array'; + const METHOD_CALL = 'method'; + + /** + * Renders the template with the given context and returns it as string. + * + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + */ + public function render(array $context); + + /** + * Displays the template with the given context. + * + * @param array $context An array of parameters to pass to the template + * @param array $blocks An array of blocks to pass to the template + */ + public function display(array $context, array $blocks = []); + + /** + * Returns the bound environment for this template. + * + * @return Environment + */ + public function getEnvironment(); +} diff --git a/vendor/twig/twig/lib/Twig/TemplateWrapper.php b/vendor/twig/twig/lib/Twig/TemplateWrapper.php new file mode 100644 index 0000000..6cb4ce4 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/TemplateWrapper.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface +{ + protected $options; + protected $arguments = []; + + public function __construct(array $options = []) + { + $this->options = array_merge([ + 'callable' => null, + ], $options); + } + + public function getCallable() + { + return $this->options['callable']; + } +} diff --git a/vendor/twig/twig/lib/Twig/Test/Function.php b/vendor/twig/twig/lib/Twig/Test/Function.php new file mode 100644 index 0000000..a789c40 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Test/Function.php @@ -0,0 +1,38 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Test_Function extends Twig_Test +{ + protected $function; + + public function __construct($function, array $options = []) + { + $options['callable'] = $function; + + parent::__construct($options); + + $this->function = $function; + } + + public function compile() + { + return $this->function; + } +} diff --git a/vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php b/vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php new file mode 100644 index 0000000..e302bdb --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Test/IntegrationTestCase.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Test_Method extends Twig_Test +{ + protected $extension; + protected $method; + + public function __construct(ExtensionInterface $extension, $method, array $options = []) + { + $options['callable'] = [$extension, $method]; + + parent::__construct($options); + + $this->extension = $extension; + $this->method = $method; + } + + public function compile() + { + return sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($this->extension), $this->method); + } +} diff --git a/vendor/twig/twig/lib/Twig/Test/Node.php b/vendor/twig/twig/lib/Twig/Test/Node.php new file mode 100644 index 0000000..6b5de15 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Test/Node.php @@ -0,0 +1,40 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_Test_Node extends Twig_Test +{ + protected $class; + + public function __construct($class, array $options = []) + { + parent::__construct($options); + + $this->class = $class; + } + + public function getClass() + { + return $this->class; + } + + public function compile() + { + } +} diff --git a/vendor/twig/twig/lib/Twig/Test/NodeTestCase.php b/vendor/twig/twig/lib/Twig/Test/NodeTestCase.php new file mode 100644 index 0000000..62aaaaf --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Test/NodeTestCase.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_TestCallableInterface +{ + public function getCallable(); +} diff --git a/vendor/twig/twig/lib/Twig/TestInterface.php b/vendor/twig/twig/lib/Twig/TestInterface.php new file mode 100644 index 0000000..9166407 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/TestInterface.php @@ -0,0 +1,27 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_TestInterface +{ + /** + * Compiles a test. + * + * @return string The PHP code for the test + */ + public function compile(); +} diff --git a/vendor/twig/twig/lib/Twig/Token.php b/vendor/twig/twig/lib/Twig/Token.php new file mode 100644 index 0000000..e4d1806 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/Token.php @@ -0,0 +1,11 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface +{ + protected $parser; + protected $parsers = []; + protected $brokers = []; + + /** + * @param array|\Traversable $parsers A \Traversable of Twig_TokenParserInterface instances + * @param array|\Traversable $brokers A \Traversable of Twig_TokenParserBrokerInterface instances + * @param bool $triggerDeprecationError + */ + public function __construct($parsers = [], $brokers = [], $triggerDeprecationError = true) + { + if ($triggerDeprecationError) { + @trigger_error('The '.__CLASS__.' class is deprecated since version 1.12 and will be removed in 2.0.', E_USER_DEPRECATED); + } + + foreach ($parsers as $parser) { + if (!$parser instanceof TokenParserInterface) { + throw new \LogicException('$parsers must a an array of Twig_TokenParserInterface.'); + } + $this->parsers[$parser->getTag()] = $parser; + } + foreach ($brokers as $broker) { + if (!$broker instanceof Twig_TokenParserBrokerInterface) { + throw new \LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface.'); + } + $this->brokers[] = $broker; + } + } + + public function addTokenParser(TokenParserInterface $parser) + { + $this->parsers[$parser->getTag()] = $parser; + } + + public function removeTokenParser(TokenParserInterface $parser) + { + $name = $parser->getTag(); + if (isset($this->parsers[$name]) && $parser === $this->parsers[$name]) { + unset($this->parsers[$name]); + } + } + + public function addTokenParserBroker(self $broker) + { + $this->brokers[] = $broker; + } + + public function removeTokenParserBroker(self $broker) + { + if (false !== $pos = array_search($broker, $this->brokers)) { + unset($this->brokers[$pos]); + } + } + + /** + * Gets a suitable TokenParser for a tag. + * + * First looks in parsers, then in brokers. + * + * @param string $tag A tag name + * + * @return TokenParserInterface|null A Twig_TokenParserInterface or null if no suitable TokenParser was found + */ + public function getTokenParser($tag) + { + if (isset($this->parsers[$tag])) { + return $this->parsers[$tag]; + } + $broker = end($this->brokers); + while (false !== $broker) { + $parser = $broker->getTokenParser($tag); + if (null !== $parser) { + return $parser; + } + $broker = prev($this->brokers); + } + } + + public function getParsers() + { + return $this->parsers; + } + + public function getParser() + { + return $this->parser; + } + + public function setParser(Twig_ParserInterface $parser) + { + $this->parser = $parser; + foreach ($this->parsers as $tokenParser) { + $tokenParser->setParser($parser); + } + foreach ($this->brokers as $broker) { + $broker->setParser($parser); + } + } +} diff --git a/vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php b/vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php new file mode 100644 index 0000000..f369264 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/TokenParserBrokerInterface.php @@ -0,0 +1,46 @@ + + * + * @deprecated since 1.12 (to be removed in 2.0) + */ +interface Twig_TokenParserBrokerInterface +{ + /** + * Gets a TokenParser suitable for a tag. + * + * @param string $tag A tag name + * + * @return TokenParserInterface|null A Twig_TokenParserInterface or null if no suitable TokenParser was found + */ + public function getTokenParser($tag); + + /** + * Calls Twig\TokenParser\TokenParserInterface::setParser on all parsers the implementation knows of. + */ + public function setParser(Twig_ParserInterface $parser); + + /** + * Gets the Twig_ParserInterface. + * + * @return Twig_ParserInterface|null A Twig_ParserInterface instance or null + */ + public function getParser(); +} diff --git a/vendor/twig/twig/lib/Twig/TokenParserInterface.php b/vendor/twig/twig/lib/Twig/TokenParserInterface.php new file mode 100644 index 0000000..800c971 --- /dev/null +++ b/vendor/twig/twig/lib/Twig/TokenParserInterface.php @@ -0,0 +1,11 @@ + + */ +interface CacheInterface +{ + /** + * Generates a cache key for the given template class name. + * + * @param string $name The template name + * @param string $className The template class name + * + * @return string + */ + public function generateKey($name, $className); + + /** + * Writes the compiled template to cache. + * + * @param string $key The cache key + * @param string $content The template representation as a PHP class + */ + public function write($key, $content); + + /** + * Loads a template from the cache. + * + * @param string $key The cache key + */ + public function load($key); + + /** + * Returns the modification timestamp of a key. + * + * @param string $key The cache key + * + * @return int + */ + public function getTimestamp($key); +} + +class_alias('Twig\Cache\CacheInterface', 'Twig_CacheInterface'); diff --git a/vendor/twig/twig/src/Cache/FilesystemCache.php b/vendor/twig/twig/src/Cache/FilesystemCache.php new file mode 100644 index 0000000..7e22879 --- /dev/null +++ b/vendor/twig/twig/src/Cache/FilesystemCache.php @@ -0,0 +1,93 @@ + + */ +class FilesystemCache implements CacheInterface +{ + const FORCE_BYTECODE_INVALIDATION = 1; + + private $directory; + private $options; + + /** + * @param string $directory The root cache directory + * @param int $options A set of options + */ + public function __construct($directory, $options = 0) + { + $this->directory = rtrim($directory, '\/').'/'; + $this->options = $options; + } + + public function generateKey($name, $className) + { + $hash = hash('sha256', $className); + + return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; + } + + public function load($key) + { + if (file_exists($key)) { + @include_once $key; + } + } + + public function write($key, $content) + { + $dir = \dirname($key); + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true)) { + clearstatcache(true, $dir); + if (!is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); + } + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); + } + + $tmpFile = tempnam($dir, basename($key)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) { + @chmod($key, 0666 & ~umask()); + + if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { + // Compile cached file into bytecode cache + if (\function_exists('opcache_invalidate')) { + opcache_invalidate($key, true); + } elseif (\function_exists('apc_compile_file')) { + apc_compile_file($key); + } + } + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $key)); + } + + public function getTimestamp($key) + { + if (!file_exists($key)) { + return 0; + } + + return (int) @filemtime($key); + } +} + +class_alias('Twig\Cache\FilesystemCache', 'Twig_Cache_Filesystem'); diff --git a/vendor/twig/twig/src/Cache/NullCache.php b/vendor/twig/twig/src/Cache/NullCache.php new file mode 100644 index 0000000..c1b37c1 --- /dev/null +++ b/vendor/twig/twig/src/Cache/NullCache.php @@ -0,0 +1,42 @@ + + */ +class NullCache implements CacheInterface +{ + public function generateKey($name, $className) + { + return ''; + } + + public function write($key, $content) + { + } + + public function load($key) + { + } + + public function getTimestamp($key) + { + return 0; + } +} + +class_alias('Twig\Cache\NullCache', 'Twig_Cache_Null'); diff --git a/vendor/twig/twig/src/Compiler.php b/vendor/twig/twig/src/Compiler.php new file mode 100644 index 0000000..e47003a --- /dev/null +++ b/vendor/twig/twig/src/Compiler.php @@ -0,0 +1,288 @@ + + */ +class Compiler implements \Twig_CompilerInterface +{ + protected $lastLine; + protected $source; + protected $indentation; + protected $env; + protected $debugInfo = []; + protected $sourceOffset; + protected $sourceLine; + protected $filename; + private $varNameSalt = 0; + + public function __construct(Environment $env) + { + $this->env = $env; + } + + /** + * @deprecated since 1.25 (to be removed in 2.0) + */ + public function getFilename() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + + return $this->filename; + } + + /** + * Returns the environment instance related to this compiler. + * + * @return Environment + */ + public function getEnvironment() + { + return $this->env; + } + + /** + * Gets the current PHP code after compilation. + * + * @return string The PHP code + */ + public function getSource() + { + return $this->source; + } + + /** + * Compiles a node. + * + * @param int $indentation The current indentation + * + * @return $this + */ + public function compile(\Twig_NodeInterface $node, $indentation = 0) + { + $this->lastLine = null; + $this->source = ''; + $this->debugInfo = []; + $this->sourceOffset = 0; + // source code starts at 1 (as we then increment it when we encounter new lines) + $this->sourceLine = 1; + $this->indentation = $indentation; + $this->varNameSalt = 0; + + if ($node instanceof ModuleNode) { + // to be removed in 2.0 + $this->filename = $node->getTemplateName(); + } + + $node->compile($this); + + return $this; + } + + public function subcompile(\Twig_NodeInterface $node, $raw = true) + { + if (false === $raw) { + $this->source .= str_repeat(' ', $this->indentation * 4); + } + + $node->compile($this); + + return $this; + } + + /** + * Adds a raw string to the compiled code. + * + * @param string $string The string + * + * @return $this + */ + public function raw($string) + { + $this->source .= $string; + + return $this; + } + + /** + * Writes a string to the compiled code by adding indentation. + * + * @return $this + */ + public function write() + { + $strings = \func_get_args(); + foreach ($strings as $string) { + $this->source .= str_repeat(' ', $this->indentation * 4).$string; + } + + return $this; + } + + /** + * Appends an indentation to the current PHP code after compilation. + * + * @return $this + * + * @deprecated since 1.27 (to be removed in 2.0). + */ + public function addIndentation() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use write(\'\') instead.', E_USER_DEPRECATED); + + $this->source .= str_repeat(' ', $this->indentation * 4); + + return $this; + } + + /** + * Adds a quoted string to the compiled code. + * + * @param string $value The string + * + * @return $this + */ + public function string($value) + { + $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + + return $this; + } + + /** + * Returns a PHP representation of a given value. + * + * @param mixed $value The value to convert + * + * @return $this + */ + public function repr($value) + { + if (\is_int($value) || \is_float($value)) { + if (false !== $locale = setlocale(LC_NUMERIC, '0')) { + setlocale(LC_NUMERIC, 'C'); + } + + $this->raw(var_export($value, true)); + + if (false !== $locale) { + setlocale(LC_NUMERIC, $locale); + } + } elseif (null === $value) { + $this->raw('null'); + } elseif (\is_bool($value)) { + $this->raw($value ? 'true' : 'false'); + } elseif (\is_array($value)) { + $this->raw('['); + $first = true; + foreach ($value as $key => $v) { + if (!$first) { + $this->raw(', '); + } + $first = false; + $this->repr($key); + $this->raw(' => '); + $this->repr($v); + } + $this->raw(']'); + } else { + $this->string($value); + } + + return $this; + } + + /** + * Adds debugging information. + * + * @return $this + */ + public function addDebugInfo(\Twig_NodeInterface $node) + { + if ($node->getTemplateLine() != $this->lastLine) { + $this->write(sprintf("// line %d\n", $node->getTemplateLine())); + + // when mbstring.func_overload is set to 2 + // mb_substr_count() replaces substr_count() + // but they have different signatures! + if (((int) ini_get('mbstring.func_overload')) & 2) { + @trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED); + + // this is much slower than the "right" version + $this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n"); + } else { + $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); + } + $this->sourceOffset = \strlen($this->source); + $this->debugInfo[$this->sourceLine] = $node->getTemplateLine(); + + $this->lastLine = $node->getTemplateLine(); + } + + return $this; + } + + public function getDebugInfo() + { + ksort($this->debugInfo); + + return $this->debugInfo; + } + + /** + * Indents the generated code. + * + * @param int $step The number of indentation to add + * + * @return $this + */ + public function indent($step = 1) + { + $this->indentation += $step; + + return $this; + } + + /** + * Outdents the generated code. + * + * @param int $step The number of indentation to remove + * + * @return $this + * + * @throws \LogicException When trying to outdent too much so the indentation would become negative + */ + public function outdent($step = 1) + { + // can't outdent by more steps than the current indentation level + if ($this->indentation < $step) { + throw new \LogicException('Unable to call outdent() as the indentation would become negative.'); + } + + $this->indentation -= $step; + + return $this; + } + + public function getVarName() + { + return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++)); + } +} + +class_alias('Twig\Compiler', 'Twig_Compiler'); diff --git a/vendor/twig/twig/src/Environment.php b/vendor/twig/twig/src/Environment.php new file mode 100644 index 0000000..fe08185 --- /dev/null +++ b/vendor/twig/twig/src/Environment.php @@ -0,0 +1,1638 @@ + + */ +class Environment +{ + const VERSION = '1.39.1'; + const VERSION_ID = 13901; + const MAJOR_VERSION = 1; + const MINOR_VERSION = 39; + const RELEASE_VERSION = 1; + const EXTRA_VERSION = ''; + + protected $charset; + protected $loader; + protected $debug; + protected $autoReload; + protected $cache; + protected $lexer; + protected $parser; + protected $compiler; + protected $baseTemplateClass; + protected $extensions; + protected $parsers; + protected $visitors; + protected $filters; + protected $tests; + protected $functions; + protected $globals; + protected $runtimeInitialized = false; + protected $extensionInitialized = false; + protected $loadedTemplates; + protected $strictVariables; + protected $unaryOperators; + protected $binaryOperators; + protected $templateClassPrefix = '__TwigTemplate_'; + protected $functionCallbacks = []; + protected $filterCallbacks = []; + protected $staging; + + private $originalCache; + private $bcWriteCacheFile = false; + private $bcGetCacheFilename = false; + private $lastModifiedExtension = 0; + private $extensionsByClass = []; + private $runtimeLoaders = []; + private $runtimes = []; + private $optionsHash; + + /** + * Constructor. + * + * Available options: + * + * * debug: When set to true, it automatically set "auto_reload" to true as + * well (default to false). + * + * * charset: The charset used by the templates (default to UTF-8). + * + * * base_template_class: The base template class to use for generated + * templates (default to \Twig\Template). + * + * * cache: An absolute path where to store the compiled templates, + * a \Twig\Cache\CacheInterface implementation, + * or false to disable compilation cache (default). + * + * * auto_reload: Whether to reload the template if the original source changed. + * If you don't provide the auto_reload option, it will be + * determined automatically based on the debug value. + * + * * strict_variables: Whether to ignore invalid variables in templates + * (default to false). + * + * * autoescape: Whether to enable auto-escaping (default to html): + * * false: disable auto-escaping + * * true: equivalent to html + * * html, js: set the autoescaping to one of the supported strategies + * * name: set the autoescaping strategy based on the template name extension + * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" + * + * * optimizations: A flag that indicates which optimizations to apply + * (default to -1 which means that all optimizations are enabled; + * set it to 0 to disable). + */ + public function __construct(LoaderInterface $loader = null, $options = []) + { + if (null !== $loader) { + $this->setLoader($loader); + } else { + @trigger_error('Not passing a "Twig\Lodaer\LoaderInterface" as the first constructor argument of "Twig\Environment" is deprecated since version 1.21.', E_USER_DEPRECATED); + } + + $options = array_merge([ + 'debug' => false, + 'charset' => 'UTF-8', + 'base_template_class' => '\Twig\Template', + 'strict_variables' => false, + 'autoescape' => 'html', + 'cache' => false, + 'auto_reload' => null, + 'optimizations' => -1, + ], $options); + + $this->debug = (bool) $options['debug']; + $this->charset = strtoupper($options['charset']); + $this->baseTemplateClass = $options['base_template_class']; + $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; + $this->strictVariables = (bool) $options['strict_variables']; + $this->setCache($options['cache']); + + $this->addExtension(new CoreExtension()); + $this->addExtension(new EscaperExtension($options['autoescape'])); + $this->addExtension(new OptimizerExtension($options['optimizations'])); + $this->staging = new StagingExtension(); + + // For BC + if (\is_string($this->originalCache)) { + $r = new \ReflectionMethod($this, 'writeCacheFile'); + if (__CLASS__ !== $r->getDeclaringClass()->getName()) { + @trigger_error('The Twig\Environment::writeCacheFile method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcWriteCacheFile = true; + } + + $r = new \ReflectionMethod($this, 'getCacheFilename'); + if (__CLASS__ !== $r->getDeclaringClass()->getName()) { + @trigger_error('The Twig\Environment::getCacheFilename method is deprecated since version 1.22 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + + $this->bcGetCacheFilename = true; + } + } + } + + /** + * Gets the base template class for compiled templates. + * + * @return string The base template class name + */ + public function getBaseTemplateClass() + { + return $this->baseTemplateClass; + } + + /** + * Sets the base template class for compiled templates. + * + * @param string $class The base template class name + */ + public function setBaseTemplateClass($class) + { + $this->baseTemplateClass = $class; + $this->updateOptionsHash(); + } + + /** + * Enables debugging mode. + */ + public function enableDebug() + { + $this->debug = true; + $this->updateOptionsHash(); + } + + /** + * Disables debugging mode. + */ + public function disableDebug() + { + $this->debug = false; + $this->updateOptionsHash(); + } + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug() + { + return $this->debug; + } + + /** + * Enables the auto_reload option. + */ + public function enableAutoReload() + { + $this->autoReload = true; + } + + /** + * Disables the auto_reload option. + */ + public function disableAutoReload() + { + $this->autoReload = false; + } + + /** + * Checks if the auto_reload option is enabled. + * + * @return bool true if auto_reload is enabled, false otherwise + */ + public function isAutoReload() + { + return $this->autoReload; + } + + /** + * Enables the strict_variables option. + */ + public function enableStrictVariables() + { + $this->strictVariables = true; + $this->updateOptionsHash(); + } + + /** + * Disables the strict_variables option. + */ + public function disableStrictVariables() + { + $this->strictVariables = false; + $this->updateOptionsHash(); + } + + /** + * Checks if the strict_variables option is enabled. + * + * @return bool true if strict_variables is enabled, false otherwise + */ + public function isStrictVariables() + { + return $this->strictVariables; + } + + /** + * Gets the current cache implementation. + * + * @param bool $original Whether to return the original cache option or the real cache instance + * + * @return CacheInterface|string|false A Twig\Cache\CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function getCache($original = true) + { + return $original ? $this->originalCache : $this->cache; + } + + /** + * Sets the current cache implementation. + * + * @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function setCache($cache) + { + if (\is_string($cache)) { + $this->originalCache = $cache; + $this->cache = new FilesystemCache($cache); + } elseif (false === $cache) { + $this->originalCache = $cache; + $this->cache = new NullCache(); + } elseif (null === $cache) { + @trigger_error('Using "null" as the cache strategy is deprecated since version 1.23 and will be removed in Twig 2.0.', E_USER_DEPRECATED); + $this->originalCache = false; + $this->cache = new NullCache(); + } elseif ($cache instanceof CacheInterface) { + $this->originalCache = $this->cache = $cache; + } else { + throw new \LogicException(sprintf('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.')); + } + } + + /** + * Gets the cache filename for a given template. + * + * @param string $name The template name + * + * @return string|false The cache file name or false when caching is disabled + * + * @deprecated since 1.22 (to be removed in 2.0) + */ + public function getCacheFilename($name) + { + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + $key = $this->cache->generateKey($name, $this->getTemplateClass($name)); + + return !$key ? false : $key; + } + + /** + * Gets the template class associated with the given string. + * + * The generated template class is based on the following parameters: + * + * * The cache key for the given template; + * * The currently enabled extensions; + * * Whether the Twig C extension is available or not; + * * PHP version; + * * Twig version; + * * Options with what environment was created. + * + * @param string $name The name for which to calculate the template class name + * @param int|null $index The index if it is an embedded template + * + * @return string The template class name + */ + public function getTemplateClass($name, $index = null) + { + $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; + + return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '___'.$index); + } + + /** + * Gets the template class prefix. + * + * @return string The template class prefix + * + * @deprecated since 1.22 (to be removed in 2.0) + */ + public function getTemplateClassPrefix() + { + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + return $this->templateClassPrefix; + } + + /** + * Renders a template. + * + * @param string|TemplateWrapper $name The template name + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + * @throws RuntimeError When an error occurred during rendering + */ + public function render($name, array $context = []) + { + return $this->load($name)->render($context); + } + + /** + * Displays a template. + * + * @param string|TemplateWrapper $name The template name + * @param array $context An array of parameters to pass to the template + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + * @throws RuntimeError When an error occurred during rendering + */ + public function display($name, array $context = []) + { + $this->load($name)->display($context); + } + + /** + * Loads a template. + * + * @param string|TemplateWrapper|\Twig\Template $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws RuntimeError When a previously generated cache is corrupted + * @throws SyntaxError When an error occurred during compilation + * + * @return TemplateWrapper + */ + public function load($name) + { + if ($name instanceof TemplateWrapper) { + return $name; + } + + if ($name instanceof Template) { + return new TemplateWrapper($this, $name); + } + + return new TemplateWrapper($this, $this->loadTemplate($name)); + } + + /** + * Loads a template internal representation. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The template name + * @param int $index The index if it is an embedded template + * + * @return \Twig_TemplateInterface A template instance representing the given template name + * + * @throws LoaderError When the template cannot be found + * @throws RuntimeError When a previously generated cache is corrupted + * @throws SyntaxError When an error occurred during compilation + * + * @internal + */ + public function loadTemplate($name, $index = null) + { + return $this->loadClass($this->getTemplateClass($name), $name, $index); + } + + /** + * @internal + */ + public function loadClass($cls, $name, $index = null) + { + $mainCls = $cls; + if (null !== $index) { + $cls .= '___'.$index; + } + + if (isset($this->loadedTemplates[$cls])) { + return $this->loadedTemplates[$cls]; + } + + if (!class_exists($cls, false)) { + if ($this->bcGetCacheFilename) { + $key = $this->getCacheFilename($name); + } else { + $key = $this->cache->generateKey($name, $mainCls); + } + + if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { + $this->cache->load($key); + } + + $source = null; + if (!class_exists($cls, false)) { + $loader = $this->getLoader(); + if (!$loader instanceof SourceContextLoaderInterface) { + $source = new Source($loader->getSource($name), $name); + } else { + $source = $loader->getSourceContext($name); + } + + $content = $this->compileSource($source); + + if ($this->bcWriteCacheFile) { + $this->writeCacheFile($key, $content); + } else { + $this->cache->write($key, $content); + $this->cache->load($key); + } + + if (!class_exists($mainCls, false)) { + /* Last line of defense if either $this->bcWriteCacheFile was used, + * $this->cache is implemented as a no-op or we have a race condition + * where the cache was cleared between the above calls to write to and load from + * the cache. + */ + eval('?>'.$content); + } + } + + if (!class_exists($cls, false)) { + throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source); + } + } + + if (!$this->runtimeInitialized) { + $this->initRuntime(); + } + + return $this->loadedTemplates[$cls] = new $cls($this); + } + + /** + * Creates a template from source. + * + * This method should not be used as a generic way to load templates. + * + * @param string $template The template name + * @param string $name An optional name of the template to be used in error messages + * + * @return TemplateWrapper A template instance representing the given template name + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + */ + public function createTemplate($template, $name = null) + { + $hash = hash('sha256', $template, false); + if (null !== $name) { + $name = sprintf('%s (string template %s)', $name, $hash); + } else { + $name = sprintf('__string_template__%s', $hash); + } + + $loader = new ChainLoader([ + new ArrayLoader([$name => $template]), + $current = $this->getLoader(), + ]); + + $this->setLoader($loader); + try { + $template = new TemplateWrapper($this, $this->loadTemplate($name)); + } catch (\Exception $e) { + $this->setLoader($current); + + throw $e; + } catch (\Throwable $e) { + $this->setLoader($current); + + throw $e; + } + $this->setLoader($current); + + return $template; + } + + /** + * Returns true if the template is still fresh. + * + * Besides checking the loader for freshness information, + * this method also checks if the enabled extensions have + * not changed. + * + * @param string $name The template name + * @param int $time The last modification time of the cached template + * + * @return bool true if the template is fresh, false otherwise + */ + public function isTemplateFresh($name, $time) + { + if (0 === $this->lastModifiedExtension) { + foreach ($this->extensions as $extension) { + $r = new \ReflectionObject($extension); + if (file_exists($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModifiedExtension) { + $this->lastModifiedExtension = $extensionTime; + } + } + } + + return $this->lastModifiedExtension <= $time && $this->getLoader()->isFresh($name, $time); + } + + /** + * Tries to load a template consecutively from an array. + * + * Similar to load() but it also accepts instances of \Twig\Template and + * \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded. + * + * @param string|Template|\Twig\TemplateWrapper|array $names A template or an array of templates to try consecutively + * + * @return TemplateWrapper|Template + * + * @throws LoaderError When none of the templates can be found + * @throws SyntaxError When an error occurred during compilation + */ + public function resolveTemplate($names) + { + if (!\is_array($names)) { + $names = [$names]; + } + + foreach ($names as $name) { + if ($name instanceof Template) { + return $name; + } + if ($name instanceof TemplateWrapper) { + return $name; + } + + try { + return $this->loadTemplate($name); + } catch (LoaderError $e) { + if (1 === \count($names)) { + throw $e; + } + } + } + + throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); + } + + /** + * Clears the internal template cache. + * + * @deprecated since 1.18.3 (to be removed in 2.0) + */ + public function clearTemplateCache() + { + @trigger_error(sprintf('The %s method is deprecated since version 1.18.3 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + $this->loadedTemplates = []; + } + + /** + * Clears the template cache files on the filesystem. + * + * @deprecated since 1.22 (to be removed in 2.0) + */ + public function clearCacheFiles() + { + @trigger_error(sprintf('The %s method is deprecated since version 1.22 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + if (\is_string($this->originalCache)) { + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->originalCache), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if ($file->isFile()) { + @unlink($file->getPathname()); + } + } + } + } + + /** + * Gets the Lexer instance. + * + * @return \Twig_LexerInterface + * + * @deprecated since 1.25 (to be removed in 2.0) + */ + public function getLexer() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + + if (null === $this->lexer) { + $this->lexer = new Lexer($this); + } + + return $this->lexer; + } + + public function setLexer(\Twig_LexerInterface $lexer) + { + $this->lexer = $lexer; + } + + /** + * Tokenizes a source code. + * + * @param string|Source $source The template source code + * @param string $name The template name (deprecated) + * + * @return TokenStream + * + * @throws SyntaxError When the code is syntactically wrong + */ + public function tokenize($source, $name = null) + { + if (!$source instanceof Source) { + @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $source = new Source($source, $name); + } + + if (null === $this->lexer) { + $this->lexer = new Lexer($this); + } + + return $this->lexer->tokenize($source); + } + + /** + * Gets the Parser instance. + * + * @return \Twig_ParserInterface + * + * @deprecated since 1.25 (to be removed in 2.0) + */ + public function getParser() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + + if (null === $this->parser) { + $this->parser = new Parser($this); + } + + return $this->parser; + } + + public function setParser(\Twig_ParserInterface $parser) + { + $this->parser = $parser; + } + + /** + * Converts a token stream to a node tree. + * + * @return ModuleNode + * + * @throws SyntaxError When the token stream is syntactically or semantically wrong + */ + public function parse(TokenStream $stream) + { + if (null === $this->parser) { + $this->parser = new Parser($this); + } + + return $this->parser->parse($stream); + } + + /** + * Gets the Compiler instance. + * + * @return \Twig_CompilerInterface + * + * @deprecated since 1.25 (to be removed in 2.0) + */ + public function getCompiler() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.25 and will be removed in 2.0.', __FUNCTION__), E_USER_DEPRECATED); + + if (null === $this->compiler) { + $this->compiler = new Compiler($this); + } + + return $this->compiler; + } + + public function setCompiler(\Twig_CompilerInterface $compiler) + { + $this->compiler = $compiler; + } + + /** + * Compiles a node and returns the PHP code. + * + * @return string The compiled PHP source code + */ + public function compile(\Twig_NodeInterface $node) + { + if (null === $this->compiler) { + $this->compiler = new Compiler($this); + } + + return $this->compiler->compile($node)->getSource(); + } + + /** + * Compiles a template source code. + * + * @param string|Source $source The template source code + * @param string $name The template name (deprecated) + * + * @return string The compiled PHP source code + * + * @throws SyntaxError When there was an error during tokenizing, parsing or compiling + */ + public function compileSource($source, $name = null) + { + if (!$source instanceof Source) { + @trigger_error(sprintf('Passing a string as the $source argument of %s() is deprecated since version 1.27. Pass a Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $source = new Source($source, $name); + } + + try { + return $this->compile($this->parse($this->tokenize($source))); + } catch (Error $e) { + $e->setSourceContext($source); + throw $e; + } catch (\Exception $e) { + throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); + } + } + + public function setLoader(LoaderInterface $loader) + { + if (!$loader instanceof SourceContextLoaderInterface && 0 !== strpos(\get_class($loader), 'Mock_')) { + @trigger_error(sprintf('Twig loader "%s" should implement Twig\Loader\SourceContextLoaderInterface since version 1.27.', \get_class($loader)), E_USER_DEPRECATED); + } + + $this->loader = $loader; + } + + /** + * Gets the Loader instance. + * + * @return LoaderInterface + */ + public function getLoader() + { + if (null === $this->loader) { + throw new \LogicException('You must set a loader first.'); + } + + return $this->loader; + } + + /** + * Sets the default template charset. + * + * @param string $charset The default charset + */ + public function setCharset($charset) + { + $this->charset = strtoupper($charset); + } + + /** + * Gets the default template charset. + * + * @return string The default charset + */ + public function getCharset() + { + return $this->charset; + } + + /** + * Initializes the runtime environment. + * + * @deprecated since 1.23 (to be removed in 2.0) + */ + public function initRuntime() + { + $this->runtimeInitialized = true; + + foreach ($this->getExtensions() as $name => $extension) { + if (!$extension instanceof InitRuntimeInterface) { + $m = new \ReflectionMethod($extension, 'initRuntime'); + + $parentClass = $m->getDeclaringClass()->getName(); + if ('Twig_Extension' !== $parentClass && 'Twig\Extension\AbstractExtension' !== $parentClass) { + @trigger_error(sprintf('Defining the initRuntime() method in the "%s" extension is deprecated since version 1.23. Use the `needs_environment` option to get the \Twig_Environment instance in filters, functions, or tests; or explicitly implement Twig\Extension\InitRuntimeInterface if needed (not recommended).', $name), E_USER_DEPRECATED); + } + } + + $extension->initRuntime($this); + } + } + + /** + * Returns true if the given extension is registered. + * + * @param string $class The extension class name + * + * @return bool Whether the extension is registered or not + */ + public function hasExtension($class) + { + $class = ltrim($class, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new \ReflectionClass($class); + $class = $class->name; + } + + if (isset($this->extensions[$class])) { + if ($class !== \get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + return true; + } + + return isset($this->extensionsByClass[$class]); + } + + /** + * Adds a runtime loader. + */ + public function addRuntimeLoader(RuntimeLoaderInterface $loader) + { + $this->runtimeLoaders[] = $loader; + } + + /** + * Gets an extension by class name. + * + * @param string $class The extension class name + * + * @return ExtensionInterface + */ + public function getExtension($class) + { + $class = ltrim($class, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new \ReflectionClass($class); + $class = $class->name; + } + + if (isset($this->extensions[$class])) { + if ($class !== \get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + return $this->extensions[$class]; + } + + if (!isset($this->extensionsByClass[$class])) { + throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class)); + } + + return $this->extensionsByClass[$class]; + } + + /** + * Returns the runtime implementation of a Twig element (filter/function/test). + * + * @param string $class A runtime class name + * + * @return object The runtime implementation + * + * @throws RuntimeError When the template cannot be found + */ + public function getRuntime($class) + { + if (isset($this->runtimes[$class])) { + return $this->runtimes[$class]; + } + + foreach ($this->runtimeLoaders as $loader) { + if (null !== $runtime = $loader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + } + + throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class)); + } + + public function addExtension(ExtensionInterface $extension) + { + if ($this->extensionInitialized) { + throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName())); + } + + $class = \get_class($extension); + if ($class !== $extension->getName()) { + if (isset($this->extensions[$extension->getName()])) { + unset($this->extensions[$extension->getName()], $this->extensionsByClass[$class]); + @trigger_error(sprintf('The possibility to register the same extension twice ("%s") is deprecated since version 1.23 and will be removed in Twig 2.0. Use proper PHP inheritance instead.', $extension->getName()), E_USER_DEPRECATED); + } + } + + $this->lastModifiedExtension = 0; + $this->extensionsByClass[$class] = $extension; + $this->extensions[$extension->getName()] = $extension; + $this->updateOptionsHash(); + } + + /** + * Removes an extension by name. + * + * This method is deprecated and you should not use it. + * + * @param string $name The extension name + * + * @deprecated since 1.12 (to be removed in 2.0) + */ + public function removeExtension($name) + { + @trigger_error(sprintf('The %s method is deprecated since version 1.12 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + if ($this->extensionInitialized) { + throw new \LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name)); + } + + $class = ltrim($name, '\\'); + if (!isset($this->extensionsByClass[$class]) && class_exists($class, false)) { + // For BC/FC with namespaced aliases + $class = new \ReflectionClass($class); + $class = $class->name; + } + + if (isset($this->extensions[$class])) { + if ($class !== \get_class($this->extensions[$class])) { + @trigger_error(sprintf('Referencing the "%s" extension by its name (defined by getName()) is deprecated since 1.26 and will be removed in Twig 2.0. Use the Fully Qualified Extension Class Name instead.', $class), E_USER_DEPRECATED); + } + + unset($this->extensions[$class]); + } + + unset($this->extensions[$class]); + $this->updateOptionsHash(); + } + + /** + * Registers an array of extensions. + * + * @param array $extensions An array of extensions + */ + public function setExtensions(array $extensions) + { + foreach ($extensions as $extension) { + $this->addExtension($extension); + } + } + + /** + * Returns all registered extensions. + * + * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) + */ + public function getExtensions() + { + return $this->extensions; + } + + public function addTokenParser(TokenParserInterface $parser) + { + if ($this->extensionInitialized) { + throw new \LogicException('Unable to add a token parser as extensions have already been initialized.'); + } + + $this->staging->addTokenParser($parser); + } + + /** + * Gets the registered Token Parsers. + * + * @return \Twig_TokenParserBrokerInterface + * + * @internal + */ + public function getTokenParsers() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->parsers; + } + + /** + * Gets registered tags. + * + * Be warned that this method cannot return tags defined by \Twig_TokenParserBrokerInterface classes. + * + * @return TokenParserInterface[] + * + * @internal + */ + public function getTags() + { + $tags = []; + foreach ($this->getTokenParsers()->getParsers() as $parser) { + if ($parser instanceof TokenParserInterface) { + $tags[$parser->getTag()] = $parser; + } + } + + return $tags; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor) + { + if ($this->extensionInitialized) { + throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.'); + } + + $this->staging->addNodeVisitor($visitor); + } + + /** + * Gets the registered Node Visitors. + * + * @return NodeVisitorInterface[] + * + * @internal + */ + public function getNodeVisitors() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->visitors; + } + + /** + * Registers a Filter. + * + * @param string|TwigFilter $name The filter name or a \Twig_SimpleFilter instance + * @param \Twig_FilterInterface|TwigFilter $filter + */ + public function addFilter($name, $filter = null) + { + if (!$name instanceof TwigFilter && !($filter instanceof TwigFilter || $filter instanceof \Twig_FilterInterface)) { + throw new \LogicException('A filter must be an instance of \Twig_FilterInterface or \Twig_SimpleFilter.'); + } + + if ($name instanceof TwigFilter) { + $filter = $name; + $name = $filter->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFilter" instead when defining filter "%s".', __METHOD__, $name), E_USER_DEPRECATED); + } + + if ($this->extensionInitialized) { + throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name)); + } + + $this->staging->addFilter($name, $filter); + } + + /** + * Get a filter by name. + * + * Subclasses may override this method and load filters differently; + * so no list of filters is available. + * + * @param string $name The filter name + * + * @return \Twig_Filter|false + * + * @internal + */ + public function getFilter($name) + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + if (isset($this->filters[$name])) { + return $this->filters[$name]; + } + + foreach ($this->filters as $pattern => $filter) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count) { + if (preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $filter->setArguments($matches); + + return $filter; + } + } + } + + foreach ($this->filterCallbacks as $callback) { + if (false !== $filter = \call_user_func($callback, $name)) { + return $filter; + } + } + + return false; + } + + public function registerUndefinedFilterCallback($callable) + { + $this->filterCallbacks[] = $callable; + } + + /** + * Gets the registered Filters. + * + * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. + * + * @return \Twig_FilterInterface[] + * + * @see registerUndefinedFilterCallback + * + * @internal + */ + public function getFilters() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->filters; + } + + /** + * Registers a Test. + * + * @param string|TwigTest $name The test name or a \Twig_SimpleTest instance + * @param \Twig_TestInterface|TwigTest $test A \Twig_TestInterface instance or a \Twig_SimpleTest instance + */ + public function addTest($name, $test = null) + { + if (!$name instanceof TwigTest && !($test instanceof TwigTest || $test instanceof \Twig_TestInterface)) { + throw new \LogicException('A test must be an instance of \Twig_TestInterface or \Twig_SimpleTest.'); + } + + if ($name instanceof TwigTest) { + $test = $name; + $name = $test->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleTest" instead when defining test "%s".', __METHOD__, $name), E_USER_DEPRECATED); + } + + if ($this->extensionInitialized) { + throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name)); + } + + $this->staging->addTest($name, $test); + } + + /** + * Gets the registered Tests. + * + * @return \Twig_TestInterface[] + * + * @internal + */ + public function getTests() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->tests; + } + + /** + * Gets a test by name. + * + * @param string $name The test name + * + * @return \Twig_Test|false + * + * @internal + */ + public function getTest($name) + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + if (isset($this->tests[$name])) { + return $this->tests[$name]; + } + + foreach ($this->tests as $pattern => $test) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count) { + if (preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $test->setArguments($matches); + + return $test; + } + } + } + + return false; + } + + /** + * Registers a Function. + * + * @param string|TwigFunction $name The function name or a \Twig_SimpleFunction instance + * @param \Twig_FunctionInterface|TwigFunction $function + */ + public function addFunction($name, $function = null) + { + if (!$name instanceof TwigFunction && !($function instanceof TwigFunction || $function instanceof \Twig_FunctionInterface)) { + throw new \LogicException('A function must be an instance of \Twig_FunctionInterface or \Twig_SimpleFunction.'); + } + + if ($name instanceof TwigFunction) { + $function = $name; + $name = $function->getName(); + } else { + @trigger_error(sprintf('Passing a name as a first argument to the %s method is deprecated since version 1.21. Pass an instance of "Twig_SimpleFunction" instead when defining function "%s".', __METHOD__, $name), E_USER_DEPRECATED); + } + + if ($this->extensionInitialized) { + throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name)); + } + + $this->staging->addFunction($name, $function); + } + + /** + * Get a function by name. + * + * Subclasses may override this method and load functions differently; + * so no list of functions is available. + * + * @param string $name function name + * + * @return \Twig_Function|false + * + * @internal + */ + public function getFunction($name) + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + if (isset($this->functions[$name])) { + return $this->functions[$name]; + } + + foreach ($this->functions as $pattern => $function) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count) { + if (preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $function->setArguments($matches); + + return $function; + } + } + } + + foreach ($this->functionCallbacks as $callback) { + if (false !== $function = \call_user_func($callback, $name)) { + return $function; + } + } + + return false; + } + + public function registerUndefinedFunctionCallback($callable) + { + $this->functionCallbacks[] = $callable; + } + + /** + * Gets registered functions. + * + * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. + * + * @return \Twig_FunctionInterface[] + * + * @see registerUndefinedFunctionCallback + * + * @internal + */ + public function getFunctions() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->functions; + } + + /** + * Registers a Global. + * + * New globals can be added before compiling or rendering a template; + * but after, you can only update existing globals. + * + * @param string $name The global name + * @param mixed $value The global value + */ + public function addGlobal($name, $value) + { + if ($this->extensionInitialized || $this->runtimeInitialized) { + if (null === $this->globals) { + $this->globals = $this->initGlobals(); + } + + if (!\array_key_exists($name, $this->globals)) { + // The deprecation notice must be turned into the following exception in Twig 2.0 + @trigger_error(sprintf('Registering global variable "%s" at runtime or when the extensions have already been initialized is deprecated since version 1.21.', $name), E_USER_DEPRECATED); + //throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); + } + } + + if ($this->extensionInitialized || $this->runtimeInitialized) { + // update the value + $this->globals[$name] = $value; + } else { + $this->staging->addGlobal($name, $value); + } + } + + /** + * Gets the registered Globals. + * + * @return array An array of globals + * + * @internal + */ + public function getGlobals() + { + if (!$this->runtimeInitialized && !$this->extensionInitialized) { + return $this->initGlobals(); + } + + if (null === $this->globals) { + $this->globals = $this->initGlobals(); + } + + return $this->globals; + } + + /** + * Merges a context with the defined globals. + * + * @param array $context An array representing the context + * + * @return array The context merged with the globals + */ + public function mergeGlobals(array $context) + { + // we don't use array_merge as the context being generally + // bigger than globals, this code is faster. + foreach ($this->getGlobals() as $key => $value) { + if (!\array_key_exists($key, $context)) { + $context[$key] = $value; + } + } + + return $context; + } + + /** + * Gets the registered unary Operators. + * + * @return array An array of unary operators + * + * @internal + */ + public function getUnaryOperators() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->unaryOperators; + } + + /** + * Gets the registered binary Operators. + * + * @return array An array of binary operators + * + * @internal + */ + public function getBinaryOperators() + { + if (!$this->extensionInitialized) { + $this->initExtensions(); + } + + return $this->binaryOperators; + } + + /** + * @deprecated since 1.23 (to be removed in 2.0) + */ + public function computeAlternatives($name, $items) + { + @trigger_error(sprintf('The %s method is deprecated since version 1.23 and will be removed in Twig 2.0.', __METHOD__), E_USER_DEPRECATED); + + return SyntaxError::computeAlternatives($name, $items); + } + + /** + * @internal + */ + protected function initGlobals() + { + $globals = []; + foreach ($this->extensions as $name => $extension) { + if (!$extension instanceof GlobalsInterface) { + $m = new \ReflectionMethod($extension, 'getGlobals'); + + $parentClass = $m->getDeclaringClass()->getName(); + if ('Twig_Extension' !== $parentClass && 'Twig\Extension\AbstractExtension' !== $parentClass) { + @trigger_error(sprintf('Defining the getGlobals() method in the "%s" extension without explicitly implementing Twig\Extension\GlobalsInterface is deprecated since version 1.23.', $name), E_USER_DEPRECATED); + } + } + + $extGlob = $extension->getGlobals(); + if (!\is_array($extGlob)) { + throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension))); + } + + $globals[] = $extGlob; + } + + $globals[] = $this->staging->getGlobals(); + + return \call_user_func_array('array_merge', $globals); + } + + /** + * @internal + */ + protected function initExtensions() + { + if ($this->extensionInitialized) { + return; + } + + $this->parsers = new \Twig_TokenParserBroker([], [], false); + $this->filters = []; + $this->functions = []; + $this->tests = []; + $this->visitors = []; + $this->unaryOperators = []; + $this->binaryOperators = []; + + foreach ($this->extensions as $extension) { + $this->initExtension($extension); + } + $this->initExtension($this->staging); + // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception + $this->extensionInitialized = true; + } + + /** + * @internal + */ + protected function initExtension(ExtensionInterface $extension) + { + // filters + foreach ($extension->getFilters() as $name => $filter) { + if ($filter instanceof TwigFilter) { + $name = $filter->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for filter "%s" is deprecated since version 1.21. Use \Twig_SimpleFilter instead.', \get_class($filter), $name), E_USER_DEPRECATED); + } + + $this->filters[$name] = $filter; + } + + // functions + foreach ($extension->getFunctions() as $name => $function) { + if ($function instanceof TwigFunction) { + $name = $function->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for function "%s" is deprecated since version 1.21. Use \Twig_SimpleFunction instead.', \get_class($function), $name), E_USER_DEPRECATED); + } + + $this->functions[$name] = $function; + } + + // tests + foreach ($extension->getTests() as $name => $test) { + if ($test instanceof TwigTest) { + $name = $test->getName(); + } else { + @trigger_error(sprintf('Using an instance of "%s" for test "%s" is deprecated since version 1.21. Use \Twig_SimpleTest instead.', \get_class($test), $name), E_USER_DEPRECATED); + } + + $this->tests[$name] = $test; + } + + // token parsers + foreach ($extension->getTokenParsers() as $parser) { + if ($parser instanceof TokenParserInterface) { + $this->parsers->addTokenParser($parser); + } elseif ($parser instanceof \Twig_TokenParserBrokerInterface) { + @trigger_error('Registering a \Twig_TokenParserBrokerInterface instance is deprecated since version 1.21.', E_USER_DEPRECATED); + + $this->parsers->addTokenParserBroker($parser); + } else { + throw new \LogicException('getTokenParsers() must return an array of \Twig_TokenParserInterface or \Twig_TokenParserBrokerInterface instances.'); + } + } + + // node visitors + foreach ($extension->getNodeVisitors() as $visitor) { + $this->visitors[] = $visitor; + } + + // operators + if ($operators = $extension->getOperators()) { + if (!\is_array($operators)) { + throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators))); + } + + if (2 !== \count($operators)) { + throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); + } + + $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); + $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); + } + } + + /** + * @deprecated since 1.22 (to be removed in 2.0) + */ + protected function writeCacheFile($file, $content) + { + $this->cache->write($file, $content); + } + + private function updateOptionsHash() + { + $hashParts = array_merge( + array_keys($this->extensions), + [ + (int) \function_exists('twig_template_get_attributes'), + PHP_MAJOR_VERSION, + PHP_MINOR_VERSION, + self::VERSION, + (int) $this->debug, + $this->baseTemplateClass, + (int) $this->strictVariables, + ] + ); + $this->optionsHash = implode(':', $hashParts); + } +} + +class_alias('Twig\Environment', 'Twig_Environment'); diff --git a/vendor/twig/twig/src/Error/Error.php b/vendor/twig/twig/src/Error/Error.php new file mode 100644 index 0000000..6dd9b9d --- /dev/null +++ b/vendor/twig/twig/src/Error/Error.php @@ -0,0 +1,331 @@ + + */ +class Error extends \Exception +{ + protected $lineno; + // to be renamed to name in 2.0 + protected $filename; + protected $rawMessage; + + private $sourcePath; + private $sourceCode; + + /** + * Constructor. + * + * Set both the line number and the name to false to + * disable automatic guessing of the original template name + * and line number. + * + * Set the line number to -1 to enable its automatic guessing. + * Set the name to null to enable its automatic guessing. + * + * By default, automatic guessing is enabled. + * + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Source|string|null $source The source context where the error occurred + * @param \Exception $previous The previous exception + */ + public function __construct($message, $lineno = -1, $source = null, \Exception $previous = null) + { + if (null === $source) { + $name = null; + } elseif (!$source instanceof Source) { + // for compat with the Twig C ext., passing the template name as string is accepted + $name = $source; + } else { + $name = $source->getName(); + $this->sourceCode = $source->getCode(); + $this->sourcePath = $source->getPath(); + } + parent::__construct('', 0, $previous); + + $this->lineno = $lineno; + $this->filename = $name; + $this->rawMessage = $message; + $this->updateRepr(); + } + + /** + * Gets the raw message. + * + * @return string The raw message + */ + public function getRawMessage() + { + return $this->rawMessage; + } + + /** + * Gets the logical name where the error occurred. + * + * @return string The name + * + * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead. + */ + public function getTemplateFile() + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->filename; + } + + /** + * Sets the logical name where the error occurred. + * + * @param string $name The name + * + * @deprecated since 1.27 (to be removed in 2.0). Use setSourceContext() instead. + */ + public function setTemplateFile($name) + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + $this->filename = $name; + + $this->updateRepr(); + } + + /** + * Gets the logical name where the error occurred. + * + * @return string The name + * + * @deprecated since 1.29 (to be removed in 2.0). Use getSourceContext() instead. + */ + public function getTemplateName() + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->filename; + } + + /** + * Sets the logical name where the error occurred. + * + * @param string $name The name + * + * @deprecated since 1.29 (to be removed in 2.0). Use setSourceContext() instead. + */ + public function setTemplateName($name) + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.29 and will be removed in 2.0. Use setSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + $this->filename = $name; + $this->sourceCode = $this->sourcePath = null; + + $this->updateRepr(); + } + + /** + * Gets the template line where the error occurred. + * + * @return int The template line + */ + public function getTemplateLine() + { + return $this->lineno; + } + + /** + * Sets the template line where the error occurred. + * + * @param int $lineno The template line + */ + public function setTemplateLine($lineno) + { + $this->lineno = $lineno; + + $this->updateRepr(); + } + + /** + * Gets the source context of the Twig template where the error occurred. + * + * @return Source|null + */ + public function getSourceContext() + { + return $this->filename ? new Source($this->sourceCode, $this->filename, $this->sourcePath) : null; + } + + /** + * Sets the source context of the Twig template where the error occurred. + */ + public function setSourceContext(Source $source = null) + { + if (null === $source) { + $this->sourceCode = $this->filename = $this->sourcePath = null; + } else { + $this->sourceCode = $source->getCode(); + $this->filename = $source->getName(); + $this->sourcePath = $source->getPath(); + } + + $this->updateRepr(); + } + + public function guess() + { + $this->guessTemplateInfo(); + $this->updateRepr(); + } + + public function appendMessage($rawMessage) + { + $this->rawMessage .= $rawMessage; + $this->updateRepr(); + } + + /** + * @internal + */ + protected function updateRepr() + { + $this->message = $this->rawMessage; + + if ($this->sourcePath && $this->lineno > 0) { + $this->file = $this->sourcePath; + $this->line = $this->lineno; + + return; + } + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + $questionMark = false; + if ('?' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $questionMark = true; + } + + if ($this->filename) { + if (\is_string($this->filename) || (\is_object($this->filename) && method_exists($this->filename, '__toString'))) { + $name = sprintf('"%s"', $this->filename); + } else { + $name = json_encode($this->filename); + } + $this->message .= sprintf(' in %s', $name); + } + + if ($this->lineno && $this->lineno >= 0) { + $this->message .= sprintf(' at line %d', $this->lineno); + } + + if ($dot) { + $this->message .= '.'; + } + + if ($questionMark) { + $this->message .= '?'; + } + } + + /** + * @internal + */ + protected function guessTemplateInfo() + { + $template = null; + $templateClass = null; + + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT); + foreach ($backtrace as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Template && 'Twig_Template' !== \get_class($trace['object'])) { + $currentClass = \get_class($trace['object']); + $isEmbedContainer = 0 === strpos($templateClass, $currentClass); + if (null === $this->filename || ($this->filename == $trace['object']->getTemplateName() && !$isEmbedContainer)) { + $template = $trace['object']; + $templateClass = \get_class($trace['object']); + } + } + } + + // update template name + if (null !== $template && null === $this->filename) { + $this->filename = $template->getTemplateName(); + } + + // update template path if any + if (null !== $template && null === $this->sourcePath) { + $src = $template->getSourceContext(); + $this->sourceCode = $src->getCode(); + $this->sourcePath = $src->getPath(); + } + + if (null === $template || $this->lineno > -1) { + return; + } + + $r = new \ReflectionObject($template); + $file = $r->getFileName(); + + $exceptions = [$e = $this]; + while ($e instanceof self && $e = $e->getPrevious()) { + $exceptions[] = $e; + } + + while ($e = array_pop($exceptions)) { + $traces = $e->getTrace(); + array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]); + + while ($trace = array_shift($traces)) { + if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { + continue; + } + + foreach ($template->getDebugInfo() as $codeLine => $templateLine) { + if ($codeLine <= $trace['line']) { + // update template line + $this->lineno = $templateLine; + + return; + } + } + } + } + } +} + +class_alias('Twig\Error\Error', 'Twig_Error'); diff --git a/vendor/twig/twig/src/Error/LoaderError.php b/vendor/twig/twig/src/Error/LoaderError.php new file mode 100644 index 0000000..dc5a9f1 --- /dev/null +++ b/vendor/twig/twig/src/Error/LoaderError.php @@ -0,0 +1,23 @@ + + */ +class LoaderError extends Error +{ +} + +class_alias('Twig\Error\LoaderError', 'Twig_Error_Loader'); diff --git a/vendor/twig/twig/src/Error/RuntimeError.php b/vendor/twig/twig/src/Error/RuntimeError.php new file mode 100644 index 0000000..9b3f36e --- /dev/null +++ b/vendor/twig/twig/src/Error/RuntimeError.php @@ -0,0 +1,24 @@ + + */ +class RuntimeError extends Error +{ +} + +class_alias('Twig\Error\RuntimeError', 'Twig_Error_Runtime'); diff --git a/vendor/twig/twig/src/Error/SyntaxError.php b/vendor/twig/twig/src/Error/SyntaxError.php new file mode 100644 index 0000000..480e660 --- /dev/null +++ b/vendor/twig/twig/src/Error/SyntaxError.php @@ -0,0 +1,57 @@ + + */ +class SyntaxError extends Error +{ + /** + * Tweaks the error message to include suggestions. + * + * @param string $name The original name of the item that does not exist + * @param array $items An array of possible items + */ + public function addSuggestions($name, array $items) + { + if (!$alternatives = self::computeAlternatives($name, $items)) { + return; + } + + $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', $alternatives))); + } + + /** + * @internal + * + * To be merged with the addSuggestions() method in 2.0. + */ + public static function computeAlternatives($name, $items) + { + $alternatives = []; + foreach ($items as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = $lev; + } + } + asort($alternatives); + + return array_keys($alternatives); + } +} + +class_alias('Twig\Error\SyntaxError', 'Twig_Error_Syntax'); diff --git a/vendor/twig/twig/src/ExpressionParser.php b/vendor/twig/twig/src/ExpressionParser.php new file mode 100644 index 0000000..a786e6f --- /dev/null +++ b/vendor/twig/twig/src/ExpressionParser.php @@ -0,0 +1,765 @@ + + * + * @internal + */ +class ExpressionParser +{ + const OPERATOR_LEFT = 1; + const OPERATOR_RIGHT = 2; + + protected $parser; + protected $unaryOperators; + protected $binaryOperators; + + private $env; + + public function __construct(Parser $parser, $env = null) + { + $this->parser = $parser; + + if ($env instanceof Environment) { + $this->env = $env; + $this->unaryOperators = $env->getUnaryOperators(); + $this->binaryOperators = $env->getBinaryOperators(); + } else { + @trigger_error('Passing the operators as constructor arguments to '.__METHOD__.' is deprecated since version 1.27. Pass the environment instead.', E_USER_DEPRECATED); + + $this->env = $parser->getEnvironment(); + $this->unaryOperators = func_get_arg(1); + $this->binaryOperators = func_get_arg(2); + } + } + + public function parseExpression($precedence = 0) + { + $expr = $this->getPrimary(); + $token = $this->parser->getCurrentToken(); + while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + + if ('is not' === $token->getValue()) { + $expr = $this->parseNotTestExpression($expr); + } elseif ('is' === $token->getValue()) { + $expr = $this->parseTestExpression($expr); + } elseif (isset($op['callable'])) { + $expr = \call_user_func($op['callable'], $this->parser, $expr); + } else { + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']); + $class = $op['class']; + $expr = new $class($expr, $expr1, $token->getLine()); + } + + $token = $this->parser->getCurrentToken(); + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + protected function getPrimary() + { + $token = $this->parser->getCurrentToken(); + + if ($this->isUnary($token)) { + $operator = $this->unaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + $expr = $this->parseExpression($operator['precedence']); + $class = $operator['class']; + + return $this->parsePostfixExpression(new $class($expr, $token->getLine())); + } elseif ($token->test(Token::PUNCTUATION_TYPE, '(')) { + $this->parser->getStream()->next(); + $expr = $this->parseExpression(); + $this->parser->getStream()->expect(Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + protected function parseConditionalExpression($expr) + { + while ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, '?')) { + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { + $expr2 = $this->parseExpression(); + if ($this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ':')) { + $expr3 = $this->parseExpression(); + } else { + $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); + } + } else { + $expr2 = $expr; + $expr3 = $this->parseExpression(); + } + + $expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); + } + + return $expr; + } + + protected function isUnary(Token $token) + { + return $token->test(Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]); + } + + protected function isBinary(Token $token) + { + return $token->test(Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]); + } + + public function parsePrimaryExpression() + { + $token = $this->parser->getCurrentToken(); + switch ($token->getType()) { + case Token::NAME_TYPE: + $this->parser->getStream()->next(); + switch ($token->getValue()) { + case 'true': + case 'TRUE': + $node = new ConstantExpression(true, $token->getLine()); + break; + + case 'false': + case 'FALSE': + $node = new ConstantExpression(false, $token->getLine()); + break; + + case 'none': + case 'NONE': + case 'null': + case 'NULL': + $node = new ConstantExpression(null, $token->getLine()); + break; + + default: + if ('(' === $this->parser->getCurrentToken()->getValue()) { + $node = $this->getFunctionNode($token->getValue(), $token->getLine()); + } else { + $node = new NameExpression($token->getValue(), $token->getLine()); + } + } + break; + + case Token::NUMBER_TYPE: + $this->parser->getStream()->next(); + $node = new ConstantExpression($token->getValue(), $token->getLine()); + break; + + case Token::STRING_TYPE: + case Token::INTERPOLATION_START_TYPE: + $node = $this->parseStringExpression(); + break; + + case Token::OPERATOR_TYPE: + if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + $node = new NameExpression($token->getValue(), $token->getLine()); + break; + } elseif (isset($this->unaryOperators[$token->getValue()])) { + $class = $this->unaryOperators[$token->getValue()]['class']; + + $ref = new \ReflectionClass($class); + $negClass = 'Twig\Node\Expression\Unary\NegUnary'; + $posClass = 'Twig\Node\Expression\Unary\PosUnary'; + if (!(\in_array($ref->getName(), [$negClass, $posClass, 'Twig_Node_Expression_Unary_Neg', 'Twig_Node_Expression_Unary_Pos']) + || $ref->isSubclassOf($negClass) || $ref->isSubclassOf($posClass) + || $ref->isSubclassOf('Twig_Node_Expression_Unary_Neg') || $ref->isSubclassOf('Twig_Node_Expression_Unary_Pos')) + ) { + throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + + $this->parser->getStream()->next(); + $expr = $this->parsePrimaryExpression(); + + $node = new $class($expr, $token->getLine()); + break; + } + + // no break + default: + if ($token->test(Token::PUNCTUATION_TYPE, '[')) { + $node = $this->parseArrayExpression(); + } elseif ($token->test(Token::PUNCTUATION_TYPE, '{')) { + $node = $this->parseHashExpression(); + } elseif ($token->test(Token::OPERATOR_TYPE, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { + throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } else { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + } + + return $this->parsePostfixExpression($node); + } + + public function parseStringExpression() + { + $stream = $this->parser->getStream(); + + $nodes = []; + // a string cannot be followed by another string in a single expression + $nextCanBeString = true; + while (true) { + if ($nextCanBeString && $token = $stream->nextIf(Token::STRING_TYPE)) { + $nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); + $nextCanBeString = false; + } elseif ($stream->nextIf(Token::INTERPOLATION_START_TYPE)) { + $nodes[] = $this->parseExpression(); + $stream->expect(Token::INTERPOLATION_END_TYPE); + $nextCanBeString = true; + } else { + break; + } + } + + $expr = array_shift($nodes); + foreach ($nodes as $node) { + $expr = new ConcatBinary($expr, $node, $node->getTemplateLine()); + } + + return $expr; + } + + public function parseArrayExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Token::PUNCTUATION_TYPE, '[', 'An array element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(Token::PUNCTUATION_TYPE, ']')) { + if (!$first) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma'); + + // trailing ,? + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + break; + } + } + $first = false; + + $node->addElement($this->parseExpression()); + } + $stream->expect(Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed'); + + return $node; + } + + public function parseHashExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(Token::PUNCTUATION_TYPE, '{', 'A hash element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(Token::PUNCTUATION_TYPE, '}')) { + if (!$first) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma'); + + // trailing ,? + if ($stream->test(Token::PUNCTUATION_TYPE, '}')) { + break; + } + } + $first = false; + + // a hash key can be: + // + // * a number -- 12 + // * a string -- 'a' + // * a name, which is equivalent to a string -- a + // * an expression, which must be enclosed in parentheses -- (1 + 2) + if (($token = $stream->nextIf(Token::STRING_TYPE)) || ($token = $stream->nextIf(Token::NAME_TYPE)) || $token = $stream->nextIf(Token::NUMBER_TYPE)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + } elseif ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + $key = $this->parseExpression(); + } else { + $current = $stream->getCurrent(); + + throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); + } + + $stream->expect(Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)'); + $value = $this->parseExpression(); + + $node->addElement($value, $key); + } + $stream->expect(Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed'); + + return $node; + } + + public function parsePostfixExpression($node) + { + while (true) { + $token = $this->parser->getCurrentToken(); + if (Token::PUNCTUATION_TYPE == $token->getType()) { + if ('.' == $token->getValue() || '[' == $token->getValue()) { + $node = $this->parseSubscriptExpression($node); + } elseif ('|' == $token->getValue()) { + $node = $this->parseFilterExpression($node); + } else { + break; + } + } else { + break; + } + } + + return $node; + } + + public function getFunctionNode($name, $line) + { + switch ($name) { + case 'parent': + $this->parseArguments(); + if (!\count($this->parser->getBlockStack())) { + throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()); + } + + if (!$this->parser->getParent() && !$this->parser->hasTraits()) { + throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()); + } + + return new ParentExpression($this->parser->peekBlockStack(), $line); + case 'block': + $args = $this->parseArguments(); + if (\count($args) < 1) { + throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()); + } + + return new BlockReferenceExpression($args->getNode(0), \count($args) > 1 ? $args->getNode(1) : null, $line); + case 'attribute': + $args = $this->parseArguments(); + if (\count($args) < 2) { + throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()); + } + + return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > 2 ? $args->getNode(2) : null, Template::ANY_CALL, $line); + default: + if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { + $arguments = new ArrayExpression([], $line); + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + + $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line); + $node->setAttribute('safe', true); + + return $node; + } + + $args = $this->parseArguments(true); + $class = $this->getFunctionNodeClass($name, $line); + + return new $class($name, $args, $line); + } + } + + public function parseSubscriptExpression($node) + { + $stream = $this->parser->getStream(); + $token = $stream->next(); + $lineno = $token->getLine(); + $arguments = new ArrayExpression([], $lineno); + $type = Template::ANY_CALL; + if ('.' == $token->getValue()) { + $token = $stream->next(); + if ( + Token::NAME_TYPE == $token->getType() + || + Token::NUMBER_TYPE == $token->getType() + || + (Token::OPERATOR_TYPE == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) + ) { + $arg = new ConstantExpression($token->getValue(), $lineno); + + if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + $type = Template::METHOD_CALL; + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + } + } else { + throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext()); + } + + if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { + if (!$arg instanceof ConstantExpression) { + throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()); + } + + $name = $arg->getAttribute('value'); + + if ($this->parser->isReservedMacroName($name)) { + throw new SyntaxError(sprintf('"%s" cannot be called as macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()); + } + + $node = new MethodCallExpression($node, 'get'.$name, $arguments, $lineno); + $node->setAttribute('safe', true); + + return $node; + } + } else { + $type = Template::ARRAY_CALL; + + // slice? + $slice = false; + if ($stream->test(Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + $arg = new ConstantExpression(0, $token->getLine()); + } else { + $arg = $this->parseExpression(); + } + + if ($stream->nextIf(Token::PUNCTUATION_TYPE, ':')) { + $slice = true; + } + + if ($slice) { + if ($stream->test(Token::PUNCTUATION_TYPE, ']')) { + $length = new ConstantExpression(null, $token->getLine()); + } else { + $length = $this->parseExpression(); + } + + $class = $this->getFilterNodeClass('slice', $token->getLine()); + $arguments = new Node([$arg, $length]); + $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine()); + + $stream->expect(Token::PUNCTUATION_TYPE, ']'); + + return $filter; + } + + $stream->expect(Token::PUNCTUATION_TYPE, ']'); + } + + return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); + } + + public function parseFilterExpression($node) + { + $this->parser->getStream()->next(); + + return $this->parseFilterExpressionRaw($node); + } + + public function parseFilterExpressionRaw($node, $tag = null) + { + while (true) { + $token = $this->parser->getStream()->expect(Token::NAME_TYPE); + + $name = new ConstantExpression($token->getValue(), $token->getLine()); + if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '(')) { + $arguments = new Node(); + } else { + $arguments = $this->parseArguments(true); + } + + $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine()); + + $node = new $class($node, $name, $arguments, $token->getLine(), $tag); + + if (!$this->parser->getStream()->test(Token::PUNCTUATION_TYPE, '|')) { + break; + } + + $this->parser->getStream()->next(); + } + + return $node; + } + + /** + * Parses arguments. + * + * @param bool $namedArguments Whether to allow named arguments or not + * @param bool $definition Whether we are parsing arguments for a function definition + * + * @return Node + * + * @throws SyntaxError + */ + public function parseArguments($namedArguments = false, $definition = false) + { + $args = []; + $stream = $this->parser->getStream(); + + $stream->expect(Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$stream->test(Token::PUNCTUATION_TYPE, ')')) { + if (!empty($args)) { + $stream->expect(Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma'); + } + + if ($definition) { + $token = $stream->expect(Token::NAME_TYPE, null, 'An argument must be a name'); + $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); + } else { + $value = $this->parseExpression(); + } + + $name = null; + if ($namedArguments && $token = $stream->nextIf(Token::OPERATOR_TYPE, '=')) { + if (!$value instanceof NameExpression) { + throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); + } + $name = $value->getAttribute('name'); + + if ($definition) { + $value = $this->parsePrimaryExpression(); + + if (!$this->checkConstantExpression($value)) { + throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext()); + } + } else { + $value = $this->parseExpression(); + } + } + + if ($definition) { + if (null === $name) { + $name = $value->getAttribute('name'); + $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine()); + } + $args[$name] = $value; + } else { + if (null === $name) { + $args[] = $value; + } else { + $args[$name] = $value; + } + } + } + $stream->expect(Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Node($args); + } + + public function parseAssignmentExpression() + { + $stream = $this->parser->getStream(); + $targets = []; + while (true) { + $token = $stream->expect(Token::NAME_TYPE, null, 'Only variables can be assigned to'); + $value = $token->getValue(); + if (\in_array(strtolower($value), ['true', 'false', 'none', 'null'])) { + throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); + } + $targets[] = new AssignNameExpression($value, $token->getLine()); + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Node($targets); + } + + public function parseMultitargetExpression() + { + $targets = []; + while (true) { + $targets[] = $this->parseExpression(); + if (!$this->parser->getStream()->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } + + return new Node($targets); + } + + private function parseNotTestExpression(\Twig_NodeInterface $node) + { + return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); + } + + private function parseTestExpression(\Twig_NodeInterface $node) + { + $stream = $this->parser->getStream(); + list($name, $test) = $this->getTest($node->getTemplateLine()); + + $class = $this->getTestNodeClass($test); + $arguments = null; + if ($stream->test(Token::PUNCTUATION_TYPE, '(')) { + $arguments = $this->parser->getExpressionParser()->parseArguments(true); + } + + return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); + } + + private function getTest($line) + { + $stream = $this->parser->getStream(); + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + if ($test = $this->env->getTest($name)) { + return [$name, $test]; + } + + if ($stream->test(Token::NAME_TYPE)) { + // try 2-words tests + $name = $name.' '.$this->parser->getCurrentToken()->getValue(); + + if ($test = $this->env->getTest($name)) { + $stream->next(); + + return [$name, $test]; + } + } + + $e = new SyntaxError(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getTests())); + + throw $e; + } + + private function getTestNodeClass($test) + { + if ($test instanceof TwigTest && $test->isDeprecated()) { + $stream = $this->parser->getStream(); + $message = sprintf('Twig Test "%s" is deprecated', $test->getName()); + if (!\is_bool($test->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); + } + if ($test->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $test->getAlternative()); + } + $src = $stream->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $stream->getCurrent()->getLine()); + + @trigger_error($message, E_USER_DEPRECATED); + } + + if ($test instanceof TwigTest) { + return $test->getNodeClass(); + } + + return $test instanceof \Twig_Test_Node ? $test->getClass() : 'Twig\Node\Expression\TestExpression'; + } + + protected function getFunctionNodeClass($name, $line) + { + if (false === $function = $this->env->getFunction($name)) { + $e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFunctions())); + + throw $e; + } + + if ($function instanceof TwigFunction && $function->isDeprecated()) { + $message = sprintf('Twig Function "%s" is deprecated', $function->getName()); + if (!\is_bool($function->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $function->getDeprecatedVersion()); + } + if ($function->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $function->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); + + @trigger_error($message, E_USER_DEPRECATED); + } + + if ($function instanceof TwigFunction) { + return $function->getNodeClass(); + } + + return $function instanceof \Twig_Function_Node ? $function->getClass() : 'Twig\Node\Expression\FunctionExpression'; + } + + protected function getFilterNodeClass($name, $line) + { + if (false === $filter = $this->env->getFilter($name)) { + $e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFilters())); + + throw $e; + } + + if ($filter instanceof TwigFilter && $filter->isDeprecated()) { + $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); + if (!\is_bool($filter->getDeprecatedVersion())) { + $message .= sprintf(' since version %s', $filter->getDeprecatedVersion()); + } + if ($filter->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ? $src->getPath() : $src->getName(), $line); + + @trigger_error($message, E_USER_DEPRECATED); + } + + if ($filter instanceof TwigFilter) { + return $filter->getNodeClass(); + } + + return $filter instanceof \Twig_Filter_Node ? $filter->getClass() : 'Twig\Node\Expression\FilterExpression'; + } + + // checks that the node only contains "constant" elements + protected function checkConstantExpression(\Twig_NodeInterface $node) + { + if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression + || $node instanceof NegUnary || $node instanceof PosUnary + )) { + return false; + } + + foreach ($node as $n) { + if (!$this->checkConstantExpression($n)) { + return false; + } + } + + return true; + } +} + +class_alias('Twig\ExpressionParser', 'Twig_ExpressionParser'); diff --git a/vendor/twig/twig/src/Extension/AbstractExtension.php b/vendor/twig/twig/src/Extension/AbstractExtension.php new file mode 100644 index 0000000..fa3245b --- /dev/null +++ b/vendor/twig/twig/src/Extension/AbstractExtension.php @@ -0,0 +1,72 @@ +escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return array An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } + + /** + * Sets the default format to be used by the date filter. + * + * @param string $format The default date format string + * @param string $dateIntervalFormat The default date interval format string + */ + public function setDateFormat($format = null, $dateIntervalFormat = null) + { + if (null !== $format) { + $this->dateFormats[0] = $format; + } + + if (null !== $dateIntervalFormat) { + $this->dateFormats[1] = $dateIntervalFormat; + } + } + + /** + * Gets the default format to be used by the date filter. + * + * @return array The default date format string and the default date interval format string + */ + public function getDateFormat() + { + return $this->dateFormats; + } + + /** + * Sets the default timezone to be used by the date filter. + * + * @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object + */ + public function setTimezone($timezone) + { + $this->timezone = $timezone instanceof \DateTimeZone ? $timezone : new \DateTimeZone($timezone); + } + + /** + * Gets the default timezone to be used by the date filter. + * + * @return \DateTimeZone The default timezone currently in use + */ + public function getTimezone() + { + if (null === $this->timezone) { + $this->timezone = new \DateTimeZone(date_default_timezone_get()); + } + + return $this->timezone; + } + + /** + * Sets the default format to be used by the number_format filter. + * + * @param int $decimal the number of decimal places to use + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + */ + public function setNumberFormat($decimal, $decimalPoint, $thousandSep) + { + $this->numberFormat = [$decimal, $decimalPoint, $thousandSep]; + } + + /** + * Get the default format used by the number_format filter. + * + * @return array The arguments for number_format() + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + public function getTokenParsers() + { + return [ + new ForTokenParser(), + new IfTokenParser(), + new ExtendsTokenParser(), + new IncludeTokenParser(), + new BlockTokenParser(), + new UseTokenParser(), + new FilterTokenParser(), + new MacroTokenParser(), + new ImportTokenParser(), + new FromTokenParser(), + new SetTokenParser(), + new SpacelessTokenParser(), + new FlushTokenParser(), + new DoTokenParser(), + new EmbedTokenParser(), + new WithTokenParser(), + new DeprecatedTokenParser(), + ]; + } + + public function getFilters() + { + $filters = [ + // formatting filters + new TwigFilter('date', 'twig_date_format_filter', ['needs_environment' => true]), + new TwigFilter('date_modify', 'twig_date_modify_filter', ['needs_environment' => true]), + new TwigFilter('format', 'sprintf'), + new TwigFilter('replace', 'twig_replace_filter'), + new TwigFilter('number_format', 'twig_number_format_filter', ['needs_environment' => true]), + new TwigFilter('abs', 'abs'), + new TwigFilter('round', 'twig_round'), + + // encoding + new TwigFilter('url_encode', 'twig_urlencode_filter'), + new TwigFilter('json_encode', 'twig_jsonencode_filter'), + new TwigFilter('convert_encoding', 'twig_convert_encoding'), + + // string filters + new TwigFilter('title', 'twig_title_string_filter', ['needs_environment' => true]), + new TwigFilter('capitalize', 'twig_capitalize_string_filter', ['needs_environment' => true]), + new TwigFilter('upper', 'strtoupper'), + new TwigFilter('lower', 'strtolower'), + new TwigFilter('striptags', 'strip_tags'), + new TwigFilter('trim', 'twig_trim_filter'), + new TwigFilter('nl2br', 'nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]), + new TwigFilter('spaceless', 'twig_spaceless', ['is_safe' => ['html']]), + + // array helpers + new TwigFilter('join', 'twig_join_filter'), + new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]), + new TwigFilter('sort', 'twig_sort_filter'), + new TwigFilter('merge', 'twig_array_merge'), + new TwigFilter('batch', 'twig_array_batch'), + + // string/array filters + new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]), + new TwigFilter('length', 'twig_length_filter', ['needs_environment' => true]), + new TwigFilter('slice', 'twig_slice', ['needs_environment' => true]), + new TwigFilter('first', 'twig_first', ['needs_environment' => true]), + new TwigFilter('last', 'twig_last', ['needs_environment' => true]), + + // iteration and runtime + new TwigFilter('default', '_twig_default_filter', ['node_class' => '\Twig\Node\Expression\Filter\DefaultFilter']), + new TwigFilter('keys', 'twig_get_array_keys_filter'), + + // escaping + new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), + new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), + ]; + + if (\function_exists('mb_get_info')) { + $filters[] = new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]); + $filters[] = new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]); + } + + return $filters; + } + + public function getFunctions() + { + return [ + new TwigFunction('max', 'max'), + new TwigFunction('min', 'min'), + new TwigFunction('range', 'range'), + new TwigFunction('constant', 'twig_constant'), + new TwigFunction('cycle', 'twig_cycle'), + new TwigFunction('random', 'twig_random', ['needs_environment' => true]), + new TwigFunction('date', 'twig_date_converter', ['needs_environment' => true]), + new TwigFunction('include', 'twig_include', ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]), + new TwigFunction('source', 'twig_source', ['needs_environment' => true, 'is_safe' => ['all']]), + ]; + } + + public function getTests() + { + return [ + new TwigTest('even', null, ['node_class' => '\Twig\Node\Expression\Test\EvenTest']), + new TwigTest('odd', null, ['node_class' => '\Twig\Node\Expression\Test\OddTest']), + new TwigTest('defined', null, ['node_class' => '\Twig\Node\Expression\Test\DefinedTest']), + new TwigTest('sameas', null, ['node_class' => '\Twig\Node\Expression\Test\SameasTest', 'deprecated' => '1.21', 'alternative' => 'same as']), + new TwigTest('same as', null, ['node_class' => '\Twig\Node\Expression\Test\SameasTest']), + new TwigTest('none', null, ['node_class' => '\Twig\Node\Expression\Test\NullTest']), + new TwigTest('null', null, ['node_class' => '\Twig\Node\Expression\Test\NullTest']), + new TwigTest('divisibleby', null, ['node_class' => '\Twig\Node\Expression\Test\DivisiblebyTest', 'deprecated' => '1.21', 'alternative' => 'divisible by']), + new TwigTest('divisible by', null, ['node_class' => '\Twig\Node\Expression\Test\DivisiblebyTest']), + new TwigTest('constant', null, ['node_class' => '\Twig\Node\Expression\Test\ConstantTest']), + new TwigTest('empty', 'twig_test_empty'), + new TwigTest('iterable', 'twig_test_iterable'), + ]; + } + + public function getOperators() + { + return [ + [ + 'not' => ['precedence' => 50, 'class' => '\Twig\Node\Expression\Unary\NotUnary'], + '-' => ['precedence' => 500, 'class' => '\Twig\Node\Expression\Unary\NegUnary'], + '+' => ['precedence' => 500, 'class' => '\Twig\Node\Expression\Unary\PosUnary'], + ], + [ + 'or' => ['precedence' => 10, 'class' => '\Twig\Node\Expression\Binary\OrBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'and' => ['precedence' => 15, 'class' => '\Twig\Node\Expression\Binary\AndBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-or' => ['precedence' => 16, 'class' => '\Twig\Node\Expression\Binary\BitwiseOrBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-xor' => ['precedence' => 17, 'class' => '\Twig\Node\Expression\Binary\BitwiseXorBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-and' => ['precedence' => 18, 'class' => '\Twig\Node\Expression\Binary\BitwiseAndBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '==' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\EqualBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '!=' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\NotEqualBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\LessBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '>' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\GreaterBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '>=' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\GreaterEqualBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<=' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\LessEqualBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'not in' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\NotInBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'in' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\InBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'matches' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\MatchesBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'starts with' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\StartsWithBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'ends with' => ['precedence' => 20, 'class' => '\Twig\Node\Expression\Binary\EndsWithBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '..' => ['precedence' => 25, 'class' => '\Twig\Node\Expression\Binary\RangeBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '+' => ['precedence' => 30, 'class' => '\Twig\Node\Expression\Binary\AddBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '-' => ['precedence' => 30, 'class' => '\Twig\Node\Expression\Binary\SubBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '~' => ['precedence' => 40, 'class' => '\Twig\Node\Expression\Binary\ConcatBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '*' => ['precedence' => 60, 'class' => '\Twig\Node\Expression\Binary\MulBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '/' => ['precedence' => 60, 'class' => '\Twig\Node\Expression\Binary\DivBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '//' => ['precedence' => 60, 'class' => '\Twig\Node\Expression\Binary\FloorDivBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + '%' => ['precedence' => 60, 'class' => '\Twig\Node\Expression\Binary\ModBinary', 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'is' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'is not' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '**' => ['precedence' => 200, 'class' => '\Twig\Node\Expression\Binary\PowerBinary', 'associativity' => ExpressionParser::OPERATOR_RIGHT], + '??' => ['precedence' => 300, 'class' => '\Twig\Node\Expression\NullCoalesceExpression', 'associativity' => ExpressionParser::OPERATOR_RIGHT], + ], + ]; + } + + public function getName() + { + return 'core'; + } +} + +class_alias('Twig\Extension\CoreExtension', 'Twig_Extension_Core'); +} + +namespace { +use Twig\Environment; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Loader\SourceContextLoaderInterface; +use Twig\Markup; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; + +/** + * Cycles over a value. + * + * @param \ArrayAccess|array $values + * @param int $position The cycle position + * + * @return string The next value in the cycle + */ +function twig_cycle($values, $position) +{ + if (!\is_array($values) && !$values instanceof \ArrayAccess) { + return $values; + } + + return $values[$position % \count($values)]; +} + +/** + * Returns a random value depending on the supplied parameter type: + * - a random item from a \Traversable or array + * - a random character from a string + * - a random integer between 0 and the integer parameter. + * + * @param \Traversable|array|int|float|string $values The values to pick a random item from + * @param int|null $max Maximum value used when $values is an int + * + * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) + * + * @return mixed A random value from the given sequence + */ +function twig_random(Environment $env, $values = null, $max = null) +{ + if (null === $values) { + return null === $max ? mt_rand() : mt_rand(0, $max); + } + + if (\is_int($values) || \is_float($values)) { + if (null === $max) { + if ($values < 0) { + $max = 0; + $min = $values; + } else { + $max = $values; + $min = 0; + } + } else { + $min = $values; + $max = $max; + } + + return mt_rand($min, $max); + } + + if (\is_string($values)) { + if ('' === $values) { + return ''; + } + if (null !== $charset = $env->getCharset()) { + if ('UTF-8' !== $charset) { + $values = twig_convert_encoding($values, 'UTF-8', $charset); + } + + // unicode version of str_split() + // split at all positions, but not after the start and not before the end + $values = preg_split('/(? $value) { + $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); + } + } + } else { + return $values[mt_rand(0, \strlen($values) - 1)]; + } + } + + if (!twig_test_iterable($values)) { + return $values; + } + + $values = twig_to_array($values); + + if (0 === \count($values)) { + throw new RuntimeError('The random function cannot pick from an empty array.'); + } + + return $values[array_rand($values, 1)]; +} + +/** + * Converts a date to the given format. + * + * {{ post.published_at|date("m/d/Y") }} + * + * @param \DateTime|\DateTimeInterface|\DateInterval|string $date A date + * @param string|null $format The target format, null to use the default + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return string The formatted date + */ +function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null) +{ + if (null === $format) { + $formats = $env->getExtension('\Twig\Extension\CoreExtension')->getDateFormat(); + $format = $date instanceof \DateInterval ? $formats[1] : $formats[0]; + } + + if ($date instanceof \DateInterval) { + return $date->format($format); + } + + return twig_date_converter($env, $date, $timezone)->format($format); +} + +/** + * Returns a new date object modified. + * + * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} + * + * @param \DateTime|string $date A date + * @param string $modifier A modifier string + * + * @return \DateTime + */ +function twig_date_modify_filter(Environment $env, $date, $modifier) +{ + $date = twig_date_converter($env, $date, false); + $resultDate = $date->modify($modifier); + + // This is a hack to ensure PHP 5.2 support and support for \DateTimeImmutable + // \DateTime::modify does not return the modified \DateTime object < 5.3.0 + // and \DateTimeImmutable does not modify $date. + return null === $resultDate ? $date : $resultDate; +} + +/** + * Converts an input to a \DateTime instance. + * + * {% if date(user.created_at) < date('+2days') %} + * {# do something #} + * {% endif %} + * + * @param \DateTime|\DateTimeInterface|string|null $date A date + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return \DateTime + */ +function twig_date_converter(Environment $env, $date = null, $timezone = null) +{ + // determine the timezone + if (false !== $timezone) { + if (null === $timezone) { + $timezone = $env->getExtension('\Twig\Extension\CoreExtension')->getTimezone(); + } elseif (!$timezone instanceof \DateTimeZone) { + $timezone = new \DateTimeZone($timezone); + } + } + + // immutable dates + if ($date instanceof \DateTimeImmutable) { + return false !== $timezone ? $date->setTimezone($timezone) : $date; + } + + if ($date instanceof \DateTime || $date instanceof \DateTimeInterface) { + $date = clone $date; + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; + } + + if (null === $date || 'now' === $date) { + return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension('\Twig\Extension\CoreExtension')->getTimezone()); + } + + $asString = (string) $date; + if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { + $date = new \DateTime('@'.$date); + } else { + $date = new \DateTime($date, $env->getExtension('\Twig\Extension\CoreExtension')->getTimezone()); + } + + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; +} + +/** + * Replaces strings within a string. + * + * @param string $str String to replace in + * @param array|\Traversable $from Replace values + * @param string|null $to Replace to, deprecated (@see https://secure.php.net/manual/en/function.strtr.php) + * + * @return string + */ +function twig_replace_filter($str, $from, $to = null) +{ + if (\is_string($from) && \is_string($to)) { + @trigger_error('Using "replace" with character by character replacement is deprecated since version 1.22 and will be removed in Twig 2.0', E_USER_DEPRECATED); + + return strtr($str, $from, $to); + } + + if (!twig_test_iterable($from)) { + throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); + } + + return strtr($str, twig_to_array($from)); +} + +/** + * Rounds a number. + * + * @param int|float $value The value to round + * @param int|float $precision The rounding precision + * @param string $method The method to use for rounding + * + * @return int|float The rounded number + */ +function twig_round($value, $precision = 0, $method = 'common') +{ + if ('common' == $method) { + return round($value, $precision); + } + + if ('ceil' != $method && 'floor' != $method) { + throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); + } + + return $method($value * pow(10, $precision)) / pow(10, $precision); +} + +/** + * Number format filter. + * + * All of the formatting options can be left null, in that case the defaults will + * be used. Supplying any of the parameters will override the defaults set in the + * environment object. + * + * @param mixed $number A float/int/string of the number to format + * @param int $decimal the number of decimal points to display + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + * + * @return string The formatted number + */ +function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) +{ + $defaults = $env->getExtension('\Twig\Extension\CoreExtension')->getNumberFormat(); + if (null === $decimal) { + $decimal = $defaults[0]; + } + + if (null === $decimalPoint) { + $decimalPoint = $defaults[1]; + } + + if (null === $thousandSep) { + $thousandSep = $defaults[2]; + } + + return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); +} + +/** + * URL encodes (RFC 3986) a string as a path segment or an array as a query string. + * + * @param string|array $url A URL or an array of query parameters + * + * @return string The URL encoded value + */ +function twig_urlencode_filter($url) +{ + if (\is_array($url)) { + if (\defined('PHP_QUERY_RFC3986')) { + return http_build_query($url, '', '&', PHP_QUERY_RFC3986); + } + + return http_build_query($url, '', '&'); + } + + return rawurlencode($url); +} + +/** + * JSON encodes a variable. + * + * @param mixed $value the value to encode + * @param int $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT + * + * @return mixed The JSON encoded value + */ +function twig_jsonencode_filter($value, $options = 0) +{ + if ($value instanceof Markup) { + $value = (string) $value; + } elseif (\is_array($value)) { + array_walk_recursive($value, '_twig_markup2string'); + } + + return json_encode($value, $options); +} + +function _twig_markup2string(&$value) +{ + if ($value instanceof Markup) { + $value = (string) $value; + } +} + +/** + * Merges an array with another one. + * + * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} + * + * {% set items = items|merge({ 'peugeot': 'car' }) %} + * + * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} + * + * @param array|\Traversable $arr1 An array + * @param array|\Traversable $arr2 An array + * + * @return array The merged array + */ +function twig_array_merge($arr1, $arr2) +{ + if (!twig_test_iterable($arr1)) { + throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1))); + } + + if (!twig_test_iterable($arr2)) { + throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2))); + } + + return array_merge(twig_to_array($arr1), twig_to_array($arr2)); +} + +/** + * Slices a variable. + * + * @param mixed $item A variable + * @param int $start Start of the slice + * @param int $length Size of the slice + * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + */ +function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false) +{ + if ($item instanceof \Traversable) { + while ($item instanceof \IteratorAggregate) { + $item = $item->getIterator(); + } + + if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) { + try { + return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys); + } catch (\OutOfBoundsException $e) { + return []; + } + } + + $item = iterator_to_array($item, $preserveKeys); + } + + if (\is_array($item)) { + return \array_slice($item, $start, $length, $preserveKeys); + } + + $item = (string) $item; + + if (\function_exists('mb_get_info') && null !== $charset = $env->getCharset()) { + return (string) mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset); + } + + return (string) (null === $length ? substr($item, $start) : substr($item, $start, $length)); +} + +/** + * Returns the first element of the item. + * + * @param mixed $item A variable + * + * @return mixed The first element of the item + */ +function twig_first(Environment $env, $item) +{ + $elements = twig_slice($env, $item, 0, 1, false); + + return \is_string($elements) ? $elements : current($elements); +} + +/** + * Returns the last element of the item. + * + * @param mixed $item A variable + * + * @return mixed The last element of the item + */ +function twig_last(Environment $env, $item) +{ + $elements = twig_slice($env, $item, -1, 1, false); + + return \is_string($elements) ? $elements : current($elements); +} + +/** + * Joins the values to a string. + * + * The separators between elements are empty strings per default, you can define them with the optional parameters. + * + * {{ [1, 2, 3]|join(', ', ' and ') }} + * {# returns 1, 2 and 3 #} + * + * {{ [1, 2, 3]|join('|') }} + * {# returns 1|2|3 #} + * + * {{ [1, 2, 3]|join }} + * {# returns 123 #} + * + * @param array $value An array + * @param string $glue The separator + * @param string|null $and The separator for the last pair + * + * @return string The concatenated string + */ +function twig_join_filter($value, $glue = '', $and = null) +{ + if (!twig_test_iterable($value)) { + $value = (array) $value; + } + + $value = twig_to_array($value, false); + + if (0 === \count($value)) { + return ''; + } + + if (null === $and || $and === $glue) { + return implode($glue, $value); + } + + if (1 === \count($value)) { + return $value[0]; + } + + return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1]; +} + +/** + * Splits the string into an array. + * + * {{ "one,two,three"|split(',') }} + * {# returns [one, two, three] #} + * + * {{ "one,two,three,four,five"|split(',', 3) }} + * {# returns [one, two, "three,four,five"] #} + * + * {{ "123"|split('') }} + * {# returns [1, 2, 3] #} + * + * {{ "aabbcc"|split('', 2) }} + * {# returns [aa, bb, cc] #} + * + * @param string $value A string + * @param string $delimiter The delimiter + * @param int $limit The limit + * + * @return array The split string as an array + */ +function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) +{ + if (!empty($delimiter)) { + return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); + } + + if (!\function_exists('mb_get_info') || null === $charset = $env->getCharset()) { + return str_split($value, null === $limit ? 1 : $limit); + } + + if ($limit <= 1) { + return preg_split('/(?getIterator(); + } + + if ($array instanceof \Iterator) { + $keys = []; + $array->rewind(); + while ($array->valid()) { + $keys[] = $array->key(); + $array->next(); + } + + return $keys; + } + + $keys = []; + foreach ($array as $key => $item) { + $keys[] = $key; + } + + return $keys; + } + + if (!\is_array($array)) { + return []; + } + + return array_keys($array); +} + +/** + * Reverses a variable. + * + * @param array|\Traversable|string $item An array, a \Traversable instance, or a string + * @param bool $preserveKeys Whether to preserve key or not + * + * @return mixed The reversed input + */ +function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) +{ + if ($item instanceof \Traversable) { + return array_reverse(iterator_to_array($item), $preserveKeys); + } + + if (\is_array($item)) { + return array_reverse($item, $preserveKeys); + } + + if (null !== $charset = $env->getCharset()) { + $string = (string) $item; + + if ('UTF-8' !== $charset) { + $item = twig_convert_encoding($string, 'UTF-8', $charset); + } + + preg_match_all('/./us', $item, $matches); + + $string = implode('', array_reverse($matches[0])); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + } + + return strrev((string) $item); +} + +/** + * Sorts an array. + * + * @param array|\Traversable $array + * + * @return array + */ +function twig_sort_filter($array) +{ + if ($array instanceof \Traversable) { + $array = iterator_to_array($array); + } elseif (!\is_array($array)) { + throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); + } + + asort($array); + + return $array; +} + +/** + * @internal + */ +function twig_in_filter($value, $compare) +{ + if (\is_array($compare)) { + return \in_array($value, $compare, \is_object($value) || \is_resource($value)); + } elseif (\is_string($compare) && (\is_string($value) || \is_int($value) || \is_float($value))) { + return '' === $value || false !== strpos($compare, (string) $value); + } elseif ($compare instanceof \Traversable) { + if (\is_object($value) || \is_resource($value)) { + foreach ($compare as $item) { + if ($item === $value) { + return true; + } + } + } else { + foreach ($compare as $item) { + if ($item == $value) { + return true; + } + } + } + + return false; + } + + return false; +} + +/** + * Returns a trimmed string. + * + * @return string + * + * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') + */ +function twig_trim_filter($string, $characterMask = null, $side = 'both') +{ + if (null === $characterMask) { + $characterMask = " \t\n\r\0\x0B"; + } + + switch ($side) { + case 'both': + return trim($string, $characterMask); + case 'left': + return ltrim($string, $characterMask); + case 'right': + return rtrim($string, $characterMask); + default: + throw new RuntimeError('Trimming side must be "left", "right" or "both".'); + } +} + +/** + * Removes whitespaces between HTML tags. + * + * @return string + */ +function twig_spaceless($content) +{ + return trim(preg_replace('/>\s+<', $content)); +} + +/** + * Escapes a string. + * + * @param mixed $string The value to be escaped + * @param string $strategy The escaping strategy + * @param string $charset The charset + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @return string + */ +function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) +{ + if ($autoescape && $string instanceof Markup) { + return $string; + } + + if (!\is_string($string)) { + if (\is_object($string) && method_exists($string, '__toString')) { + $string = (string) $string; + } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { + return $string; + } + } + + if ('' === $string) { + return ''; + } + + if (null === $charset) { + $charset = $env->getCharset(); + } + + switch ($strategy) { + case 'html': + // see https://secure.php.net/htmlspecialchars + + // Using a static variable to avoid initializing the array + // each time the function is called. Moving the declaration on the + // top of the function slow downs other escaping strategies. + static $htmlspecialcharsCharsets = [ + 'ISO-8859-1' => true, 'ISO8859-1' => true, + 'ISO-8859-15' => true, 'ISO8859-15' => true, + 'utf-8' => true, 'UTF-8' => true, + 'CP866' => true, 'IBM866' => true, '866' => true, + 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, + '1251' => true, + 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, + 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, + 'BIG5' => true, '950' => true, + 'GB2312' => true, '936' => true, + 'BIG5-HKSCS' => true, + 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, + 'EUC-JP' => true, 'EUCJP' => true, + 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, + ]; + + if (isset($htmlspecialcharsCharsets[$charset])) { + return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + } + + if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { + // cache the lowercase variant for future iterations + $htmlspecialcharsCharsets[$charset] = true; + + return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); + } + + $string = twig_convert_encoding($string, 'UTF-8', $charset); + $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); + + return twig_convert_encoding($string, $charset, 'UTF-8'); + + case 'js': + // escape all non-alphanumeric characters + // into their \x or \uHHHH representations + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + + case 'css': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + + case 'html_attr': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; + + case 'url': + return rawurlencode($string); + + default: + static $escapers; + + if (null === $escapers) { + $escapers = $env->getExtension('\Twig\Extension\CoreExtension')->getEscapers(); + } + + if (isset($escapers[$strategy])) { + return \call_user_func($escapers[$strategy], $env, $string, $charset); + } + + $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers))); + + throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); + } +} + +/** + * @internal + */ +function twig_escape_filter_is_safe(Node $filterArgs) +{ + foreach ($filterArgs as $arg) { + if ($arg instanceof ConstantExpression) { + return [$arg->getAttribute('value')]; + } + + return []; + } + + return ['html']; +} + +if (\function_exists('mb_convert_encoding')) { + function twig_convert_encoding($string, $to, $from) + { + return mb_convert_encoding($string, $to, $from); + } +} elseif (\function_exists('iconv')) { + function twig_convert_encoding($string, $to, $from) + { + return iconv($from, $to, $string); + } +} else { + function twig_convert_encoding($string, $to, $from) + { + throw new RuntimeError('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); + } +} + +if (\function_exists('mb_ord')) { + function twig_ord($string) + { + return mb_ord($string, 'UTF-8'); + } +} else { + function twig_ord($string) + { + $code = ($string = unpack('C*', substr($string, 0, 4))) ? $string[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($string[2] - 0x80) << 12) + (($string[3] - 0x80) << 6) + $string[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($string[2] - 0x80) << 6) + $string[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $string[2] - 0x80; + } + + return $code; + } +} + +function _twig_escape_js_callback($matches) +{ + $char = $matches[0]; + + /* + * A few characters have short escape sequences in JSON and JavaScript. + * Escape sequences supported only by JavaScript, not JSON, are ommitted. + * \" is also supported but omitted, because the resulting string is not HTML safe. + */ + static $shortMap = [ + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ]; + + if (isset($shortMap[$char])) { + return $shortMap[$char]; + } + + // \uHHHH + $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); + $char = strtoupper(bin2hex($char)); + + if (4 >= \strlen($char)) { + return sprintf('\u%04s', $char); + } + + return sprintf('\u%04s\u%04s', substr($char, 0, -4), substr($char, -4)); +} + +function _twig_escape_css_callback($matches) +{ + $char = $matches[0]; + + return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : twig_ord($char)); +} + +/** + * This function is adapted from code coming from Zend Framework. + * + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) + * @license https://framework.zend.com/license/new-bsd New BSD License + */ +function _twig_escape_html_attr_callback($matches) +{ + $chr = $matches[0]; + $ord = \ord($chr); + + /* + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { + return '�'; + } + + /* + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the hex value of the character. + */ + if (1 == \strlen($chr)) { + /* + * While HTML supports far more named entities, the lowest common denominator + * has become HTML5's XML Serialisation which is restricted to the those named + * entities that XML supports. Using HTML entities would result in this error: + * XML Parsing Error: undefined entity + */ + static $entityMap = [ + 34 => '"', /* quotation mark */ + 38 => '&', /* ampersand */ + 60 => '<', /* less-than sign */ + 62 => '>', /* greater-than sign */ + ]; + + if (isset($entityMap[$ord])) { + return $entityMap[$ord]; + } + + return sprintf('&#x%02X;', $ord); + } + + /* + * Per OWASP recommendations, we'll use hex entities for any other + * characters where a named entity does not exist. + */ + return sprintf('&#x%04X;', twig_ord($chr)); +} + +// add multibyte extensions if possible +if (\function_exists('mb_get_info')) { + /** + * Returns the length of a variable. + * + * @param mixed $thing A variable + * + * @return int The length of the value + */ + function twig_length_filter(Environment $env, $thing) + { + if (null === $thing) { + return 0; + } + + if (is_scalar($thing)) { + return mb_strlen($thing, $env->getCharset()); + } + + if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { + return \count($thing); + } + + if ($thing instanceof \Traversable) { + return iterator_count($thing); + } + + if (\is_object($thing) && method_exists($thing, '__toString')) { + return mb_strlen((string) $thing, $env->getCharset()); + } + + return 1; + } + + /** + * Converts a string to uppercase. + * + * @param string $string A string + * + * @return string The uppercased string + */ + function twig_upper_filter(Environment $env, $string) + { + if (null !== $charset = $env->getCharset()) { + return mb_strtoupper($string, $charset); + } + + return strtoupper($string); + } + + /** + * Converts a string to lowercase. + * + * @param string $string A string + * + * @return string The lowercased string + */ + function twig_lower_filter(Environment $env, $string) + { + if (null !== $charset = $env->getCharset()) { + return mb_strtolower($string, $charset); + } + + return strtolower($string); + } + + /** + * Returns a titlecased string. + * + * @param string $string A string + * + * @return string The titlecased string + */ + function twig_title_string_filter(Environment $env, $string) + { + if (null !== $charset = $env->getCharset()) { + return mb_convert_case($string, MB_CASE_TITLE, $charset); + } + + return ucwords(strtolower($string)); + } + + /** + * Returns a capitalized string. + * + * @param string $string A string + * + * @return string The capitalized string + */ + function twig_capitalize_string_filter(Environment $env, $string) + { + if (null !== $charset = $env->getCharset()) { + return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset); + } + + return ucfirst(strtolower($string)); + } +} +// and byte fallback +else { + /** + * Returns the length of a variable. + * + * @param mixed $thing A variable + * + * @return int The length of the value + */ + function twig_length_filter(Environment $env, $thing) + { + if (null === $thing) { + return 0; + } + + if (is_scalar($thing)) { + return \strlen($thing); + } + + if ($thing instanceof \SimpleXMLElement) { + return \count($thing); + } + + if (\is_object($thing) && method_exists($thing, '__toString') && !$thing instanceof \Countable) { + return \strlen((string) $thing); + } + + if ($thing instanceof \Countable || \is_array($thing)) { + return \count($thing); + } + + if ($thing instanceof \IteratorAggregate) { + return iterator_count($thing); + } + + return 1; + } + + /** + * Returns a titlecased string. + * + * @param string $string A string + * + * @return string The titlecased string + */ + function twig_title_string_filter(Environment $env, $string) + { + return ucwords(strtolower($string)); + } + + /** + * Returns a capitalized string. + * + * @param string $string A string + * + * @return string The capitalized string + */ + function twig_capitalize_string_filter(Environment $env, $string) + { + return ucfirst(strtolower($string)); + } +} + +/** + * @internal + */ +function twig_ensure_traversable($seq) +{ + if ($seq instanceof \Traversable || \is_array($seq)) { + return $seq; + } + + return []; +} + +/** + * @internal + */ +function twig_to_array($seq, $preserveKeys = true) +{ + if ($seq instanceof \Traversable) { + return iterator_to_array($seq, $preserveKeys); + } + + if (!\is_array($seq)) { + return $seq; + } + + return $preserveKeys ? $seq : array_values($seq); +} + +/** + * Checks if a variable is empty. + * + * {# evaluates to true if the foo variable is null, false, or the empty string #} + * {% if foo is empty %} + * {# ... #} + * {% endif %} + * + * @param mixed $value A variable + * + * @return bool true if the value is empty, false otherwise + */ +function twig_test_empty($value) +{ + if ($value instanceof \Countable) { + return 0 == \count($value); + } + + if (\is_object($value) && method_exists($value, '__toString')) { + return '' === (string) $value; + } + + return '' === $value || false === $value || null === $value || [] === $value; +} + +/** + * Checks if a variable is traversable. + * + * {# evaluates to true if the foo variable is an array or a traversable object #} + * {% if foo is iterable %} + * {# ... #} + * {% endif %} + * + * @param mixed $value A variable + * + * @return bool true if the value is traversable + */ +function twig_test_iterable($value) +{ + return $value instanceof \Traversable || \is_array($value); +} + +/** + * Renders a template. + * + * @param array $context + * @param string|array $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $withContext + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not + * + * @return string The rendered template + */ +function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false) +{ + $alreadySandboxed = false; + $sandbox = null; + if ($withContext) { + $variables = array_merge($context, $variables); + } + + if ($isSandboxed = $sandboxed && $env->hasExtension('\Twig\Extension\SandboxExtension')) { + $sandbox = $env->getExtension('\Twig\Extension\SandboxExtension'); + if (!$alreadySandboxed = $sandbox->isSandboxed()) { + $sandbox->enableSandbox(); + } + } + + $loaded = null; + try { + $loaded = $env->resolveTemplate($template); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; + } + } catch (\Throwable $e) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; + } catch (\Exception $e) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; + } + + try { + $ret = $loaded ? $loaded->render($variables) : ''; + } catch (\Exception $e) { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + throw $e; + } + + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + + return $ret; +} + +/** + * Returns a template content without rendering it. + * + * @param string $name The template name + * @param bool $ignoreMissing Whether to ignore missing templates or not + * + * @return string The template source + */ +function twig_source(Environment $env, $name, $ignoreMissing = false) +{ + $loader = $env->getLoader(); + try { + if (!$loader instanceof SourceContextLoaderInterface) { + return $loader->getSource($name); + } else { + return $loader->getSourceContext($name)->getCode(); + } + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + } +} + +/** + * Provides the ability to get constants from instances as well as class/global constants. + * + * @param string $constant The name of the constant + * @param object|null $object The object to get the constant from + * + * @return string + */ +function twig_constant($constant, $object = null) +{ + if (null !== $object) { + $constant = \get_class($object).'::'.$constant; + } + + return \constant($constant); +} + +/** + * Checks if a constant exists. + * + * @param string $constant The name of the constant + * @param object|null $object The object to get the constant from + * + * @return bool + */ +function twig_constant_is_defined($constant, $object = null) +{ + if (null !== $object) { + $constant = \get_class($object).'::'.$constant; + } + + return \defined($constant); +} + +/** + * Batches item. + * + * @param array $items An array of items + * @param int $size The size of the batch + * @param mixed $fill A value used to fill missing items + * + * @return array + */ +function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) +{ + if (!twig_test_iterable($items)) { + throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); + } + + $size = ceil($size); + + $result = array_chunk(twig_to_array($items, $preserveKeys), $size, $preserveKeys); + + if (null !== $fill && $result) { + $last = \count($result) - 1; + if ($fillCount = $size - \count($result[$last])) { + for ($i = 0; $i < $fillCount; ++$i) { + $result[$last][] = $fill; + } + } + } + + return $result; +} +} diff --git a/vendor/twig/twig/src/Extension/DebugExtension.php b/vendor/twig/twig/src/Extension/DebugExtension.php new file mode 100644 index 0000000..09b0223 --- /dev/null +++ b/vendor/twig/twig/src/Extension/DebugExtension.php @@ -0,0 +1,76 @@ + $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]), + ]; + } + + public function getName() + { + return 'debug'; + } +} + +class_alias('Twig\Extension\DebugExtension', 'Twig_Extension_Debug'); +} + +namespace { +use Twig\Environment; +use Twig\Template; +use Twig\TemplateWrapper; + +function twig_var_dump(Environment $env, $context, array $vars = []) +{ + if (!$env->isDebug()) { + return; + } + + ob_start(); + + if (!$vars) { + $vars = []; + foreach ($context as $key => $value) { + if (!$value instanceof Template && !$value instanceof TemplateWrapper) { + $vars[$key] = $value; + } + } + + var_dump($vars); + } else { + foreach ($vars as $var) { + var_dump($var); + } + } + + return ob_get_clean(); +} +} diff --git a/vendor/twig/twig/src/Extension/EscaperExtension.php b/vendor/twig/twig/src/Extension/EscaperExtension.php new file mode 100644 index 0000000..fc7f6df --- /dev/null +++ b/vendor/twig/twig/src/Extension/EscaperExtension.php @@ -0,0 +1,120 @@ +setDefaultStrategy($defaultStrategy); + } + + public function getTokenParsers() + { + return [new AutoEscapeTokenParser()]; + } + + public function getNodeVisitors() + { + return [new EscaperNodeVisitor()]; + } + + public function getFilters() + { + return [ + new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]), + ]; + } + + /** + * Sets the default strategy to use when not defined by the user. + * + * The strategy can be a valid PHP callback that takes the template + * name as an argument and returns the strategy to use. + * + * @param string|false|callable $defaultStrategy An escaping strategy + */ + public function setDefaultStrategy($defaultStrategy) + { + // for BC + if (true === $defaultStrategy) { + @trigger_error('Using "true" as the default strategy is deprecated since version 1.21. Use "html" instead.', E_USER_DEPRECATED); + + $defaultStrategy = 'html'; + } + + if ('filename' === $defaultStrategy) { + @trigger_error('Using "filename" as the default strategy is deprecated since version 1.27. Use "name" instead.', E_USER_DEPRECATED); + + $defaultStrategy = 'name'; + } + + if ('name' === $defaultStrategy) { + $defaultStrategy = ['\Twig\FileExtensionEscapingStrategy', 'guess']; + } + + $this->defaultStrategy = $defaultStrategy; + } + + /** + * Gets the default strategy to use when not defined by the user. + * + * @param string $name The template name + * + * @return string|false The default strategy to use for the template + */ + public function getDefaultStrategy($name) + { + // disable string callables to avoid calling a function named html or js, + // or any other upcoming escaping strategy + if (!\is_string($this->defaultStrategy) && false !== $this->defaultStrategy) { + return \call_user_func($this->defaultStrategy, $name); + } + + return $this->defaultStrategy; + } + + public function getName() + { + return 'escaper'; + } +} + +class_alias('Twig\Extension\EscaperExtension', 'Twig_Extension_Escaper'); +} + +namespace { +/** + * Marks a variable as being safe. + * + * @param string $string A PHP variable + * + * @return string + */ +function twig_raw_filter($string) +{ + return $string; +} +} diff --git a/vendor/twig/twig/src/Extension/ExtensionInterface.php b/vendor/twig/twig/src/Extension/ExtensionInterface.php new file mode 100644 index 0000000..72b31e9 --- /dev/null +++ b/vendor/twig/twig/src/Extension/ExtensionInterface.php @@ -0,0 +1,101 @@ + + */ +interface ExtensionInterface +{ + /** + * Initializes the runtime environment. + * + * This is where you can load some file that contains filter functions for instance. + * + * @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_InitRuntimeInterface instead + */ + public function initRuntime(Environment $environment); + + /** + * Returns the token parser instances to add to the existing list. + * + * @return TokenParserInterface[] + */ + public function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return NodeVisitorInterface[] + */ + public function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return TwigFilter[] + */ + public function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return TwigTest[] + */ + public function getTests(); + + /** + * Returns a list of functions to add to the existing list. + * + * @return TwigFunction[] + */ + public function getFunctions(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array First array of unary operators, second array of binary operators + */ + public function getOperators(); + + /** + * Returns a list of global variables to add to the existing list. + * + * @return array An array of global variables + * + * @deprecated since 1.23 (to be removed in 2.0), implement \Twig_Extension_GlobalsInterface instead + */ + public function getGlobals(); + + /** + * Returns the name of the extension. + * + * @return string The extension name + * + * @deprecated since 1.26 (to be removed in 2.0), not used anymore internally + */ + public function getName(); +} + +class_alias('Twig\Extension\ExtensionInterface', 'Twig_ExtensionInterface'); + +// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. +class_exists('Twig\Environment'); diff --git a/vendor/twig/twig/src/Extension/GlobalsInterface.php b/vendor/twig/twig/src/Extension/GlobalsInterface.php new file mode 100644 index 0000000..1f54e25 --- /dev/null +++ b/vendor/twig/twig/src/Extension/GlobalsInterface.php @@ -0,0 +1,26 @@ + + */ +interface GlobalsInterface +{ +} + +class_alias('Twig\Extension\GlobalsInterface', 'Twig_Extension_GlobalsInterface'); diff --git a/vendor/twig/twig/src/Extension/InitRuntimeInterface.php b/vendor/twig/twig/src/Extension/InitRuntimeInterface.php new file mode 100644 index 0000000..f71d9cb --- /dev/null +++ b/vendor/twig/twig/src/Extension/InitRuntimeInterface.php @@ -0,0 +1,26 @@ + + */ +interface InitRuntimeInterface +{ +} + +class_alias('Twig\Extension\InitRuntimeInterface', 'Twig_Extension_InitRuntimeInterface'); diff --git a/vendor/twig/twig/src/Extension/OptimizerExtension.php b/vendor/twig/twig/src/Extension/OptimizerExtension.php new file mode 100644 index 0000000..3e13740 --- /dev/null +++ b/vendor/twig/twig/src/Extension/OptimizerExtension.php @@ -0,0 +1,39 @@ +optimizers = $optimizers; + } + + public function getNodeVisitors() + { + return [new OptimizerNodeVisitor($this->optimizers)]; + } + + public function getName() + { + return 'optimizer'; + } +} + +class_alias('Twig\Extension\OptimizerExtension', 'Twig_Extension_Optimizer'); diff --git a/vendor/twig/twig/src/Extension/ProfilerExtension.php b/vendor/twig/twig/src/Extension/ProfilerExtension.php new file mode 100644 index 0000000..7b21b9f --- /dev/null +++ b/vendor/twig/twig/src/Extension/ProfilerExtension.php @@ -0,0 +1,53 @@ +actives[] = $profile; + } + + public function enter(Profile $profile) + { + $this->actives[0]->addProfile($profile); + array_unshift($this->actives, $profile); + } + + public function leave(Profile $profile) + { + $profile->leave(); + array_shift($this->actives); + + if (1 === \count($this->actives)) { + $this->actives[0]->leave(); + } + } + + public function getNodeVisitors() + { + return [new ProfilerNodeVisitor(\get_class($this))]; + } + + public function getName() + { + return 'profiler'; + } +} + +class_alias('Twig\Extension\ProfilerExtension', 'Twig_Extension_Profiler'); diff --git a/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php b/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php new file mode 100644 index 0000000..63bc3b1 --- /dev/null +++ b/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php @@ -0,0 +1,19 @@ + + */ +interface RuntimeExtensionInterface +{ +} diff --git a/vendor/twig/twig/src/Extension/SandboxExtension.php b/vendor/twig/twig/src/Extension/SandboxExtension.php new file mode 100644 index 0000000..818c8c9 --- /dev/null +++ b/vendor/twig/twig/src/Extension/SandboxExtension.php @@ -0,0 +1,109 @@ +policy = $policy; + $this->sandboxedGlobally = $sandboxed; + } + + public function getTokenParsers() + { + return [new SandboxTokenParser()]; + } + + public function getNodeVisitors() + { + return [new SandboxNodeVisitor()]; + } + + public function enableSandbox() + { + $this->sandboxed = true; + } + + public function disableSandbox() + { + $this->sandboxed = false; + } + + public function isSandboxed() + { + return $this->sandboxedGlobally || $this->sandboxed; + } + + public function isSandboxedGlobally() + { + return $this->sandboxedGlobally; + } + + public function setSecurityPolicy(SecurityPolicyInterface $policy) + { + $this->policy = $policy; + } + + public function getSecurityPolicy() + { + return $this->policy; + } + + public function checkSecurity($tags, $filters, $functions) + { + if ($this->isSandboxed()) { + $this->policy->checkSecurity($tags, $filters, $functions); + } + } + + public function checkMethodAllowed($obj, $method) + { + if ($this->isSandboxed()) { + $this->policy->checkMethodAllowed($obj, $method); + } + } + + public function checkPropertyAllowed($obj, $method) + { + if ($this->isSandboxed()) { + $this->policy->checkPropertyAllowed($obj, $method); + } + } + + public function ensureToStringAllowed($obj) + { + if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) { + $this->policy->checkMethodAllowed($obj, '__toString'); + } + + return $obj; + } + + public function getName() + { + return 'sandbox'; + } +} + +class_alias('Twig\Extension\SandboxExtension', 'Twig_Extension_Sandbox'); diff --git a/vendor/twig/twig/src/Extension/StagingExtension.php b/vendor/twig/twig/src/Extension/StagingExtension.php new file mode 100644 index 0000000..049c5c7 --- /dev/null +++ b/vendor/twig/twig/src/Extension/StagingExtension.php @@ -0,0 +1,117 @@ + + * + * @internal + */ +class StagingExtension extends AbstractExtension +{ + protected $functions = []; + protected $filters = []; + protected $visitors = []; + protected $tokenParsers = []; + protected $globals = []; + protected $tests = []; + + public function addFunction($name, $function) + { + if (isset($this->functions[$name])) { + @trigger_error(sprintf('Overriding function "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + } + + $this->functions[$name] = $function; + } + + public function getFunctions() + { + return $this->functions; + } + + public function addFilter($name, $filter) + { + if (isset($this->filters[$name])) { + @trigger_error(sprintf('Overriding filter "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + } + + $this->filters[$name] = $filter; + } + + public function getFilters() + { + return $this->filters; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor) + { + $this->visitors[] = $visitor; + } + + public function getNodeVisitors() + { + return $this->visitors; + } + + public function addTokenParser(TokenParserInterface $parser) + { + if (isset($this->tokenParsers[$parser->getTag()])) { + @trigger_error(sprintf('Overriding tag "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $parser->getTag()), E_USER_DEPRECATED); + } + + $this->tokenParsers[$parser->getTag()] = $parser; + } + + public function getTokenParsers() + { + return $this->tokenParsers; + } + + public function addGlobal($name, $value) + { + $this->globals[$name] = $value; + } + + public function getGlobals() + { + return $this->globals; + } + + public function addTest($name, $test) + { + if (isset($this->tests[$name])) { + @trigger_error(sprintf('Overriding test "%s" that is already registered is deprecated since version 1.30 and won\'t be possible anymore in 2.0.', $name), E_USER_DEPRECATED); + } + + $this->tests[$name] = $test; + } + + public function getTests() + { + return $this->tests; + } + + public function getName() + { + return 'staging'; + } +} + +class_alias('Twig\Extension\StagingExtension', 'Twig_Extension_Staging'); diff --git a/vendor/twig/twig/src/Extension/StringLoaderExtension.php b/vendor/twig/twig/src/Extension/StringLoaderExtension.php new file mode 100644 index 0000000..93ac834 --- /dev/null +++ b/vendor/twig/twig/src/Extension/StringLoaderExtension.php @@ -0,0 +1,54 @@ + true]), + ]; + } + + public function getName() + { + return 'string_loader'; + } +} + +class_alias('Twig\Extension\StringLoaderExtension', 'Twig_Extension_StringLoader'); +} + +namespace { +use Twig\Environment; +use Twig\TemplateWrapper; + +/** + * Loads a template from a string. + * + * {{ include(template_from_string("Hello {{ name }}")) }} + * + * @param string $template A template as a string or object implementing __toString() + * @param string $name An optional name of the template to be used in error messages + * + * @return TemplateWrapper + */ +function twig_template_from_string(Environment $env, $template, $name = null) +{ + return $env->createTemplate((string) $template, $name); +} +} diff --git a/vendor/twig/twig/src/FileExtensionEscapingStrategy.php b/vendor/twig/twig/src/FileExtensionEscapingStrategy.php new file mode 100644 index 0000000..bc95f33 --- /dev/null +++ b/vendor/twig/twig/src/FileExtensionEscapingStrategy.php @@ -0,0 +1,62 @@ + + */ +class FileExtensionEscapingStrategy +{ + /** + * Guesses the best autoescaping strategy based on the file name. + * + * @param string $name The template name + * + * @return string|false The escaping strategy name to use or false to disable + */ + public static function guess($name) + { + if (\in_array(substr($name, -1), ['/', '\\'])) { + return 'html'; // return html for directories + } + + if ('.twig' === substr($name, -5)) { + $name = substr($name, 0, -5); + } + + $extension = pathinfo($name, PATHINFO_EXTENSION); + + switch ($extension) { + case 'js': + return 'js'; + + case 'css': + return 'css'; + + case 'txt': + return false; + + default: + return 'html'; + } + } +} + +class_alias('Twig\FileExtensionEscapingStrategy', 'Twig_FileExtensionEscapingStrategy'); diff --git a/vendor/twig/twig/src/Lexer.php b/vendor/twig/twig/src/Lexer.php new file mode 100644 index 0000000..8cb63f3 --- /dev/null +++ b/vendor/twig/twig/src/Lexer.php @@ -0,0 +1,528 @@ + + */ +class Lexer implements \Twig_LexerInterface +{ + protected $tokens; + protected $code; + protected $cursor; + protected $lineno; + protected $end; + protected $state; + protected $states; + protected $brackets; + protected $env; + // to be renamed to $name in 2.0 (where it is private) + protected $filename; + protected $options; + protected $regexes; + protected $position; + protected $positions; + protected $currentVarBlockLine; + + private $source; + + const STATE_DATA = 0; + const STATE_BLOCK = 1; + const STATE_VAR = 2; + const STATE_STRING = 3; + const STATE_INTERPOLATION = 4; + + const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; + const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?/A'; + const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + const REGEX_DQ_STRING_DELIM = '/"/A'; + const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + const PUNCTUATION = '()[]{}?:.,|'; + + public function __construct(Environment $env, array $options = []) + { + $this->env = $env; + + $this->options = array_merge([ + 'tag_comment' => ['{#', '#}'], + 'tag_block' => ['{%', '%}'], + 'tag_variable' => ['{{', '}}'], + 'whitespace_trim' => '-', + 'whitespace_line_trim' => '~', + 'whitespace_line_chars' => ' \t\0\x0B', + 'interpolation' => ['#{', '}'], + ], $options); + + $this->regexes = [ + // }} + 'lex_var' => '{ + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1]).'\s*'. // -}}\s* + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_variable'][1]). // }} + ') + }Ax', + + // %} + 'lex_block' => '{ + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*\n?'. // -%}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1]).'\n?'. // %}\n? + ') + }Ax', + + // {% endverbatim %} + 'lex_raw_data' => '{'. + preg_quote($this->options['tag_block'][0]). // {% + '('. + $this->options['whitespace_trim']. // - + '|'. + $this->options['whitespace_line_trim']. // ~ + ')?\s*'. + '(?:end%s)'. // endraw or endverbatim + '\s*'. + '(?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*'. // -%} + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1]). // %} + ') + }sx', + + 'operator' => $this->getOperatorRegex(), + + // #} + 'lex_comment' => '{ + (?:'. + preg_quote($this->options['whitespace_trim']).preg_quote($this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n? + ') + }sx', + + // verbatim %} + 'lex_block_raw' => '{ + \s* + (raw|verbatim) + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1]).'\s*'. // -%}\s* + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1]).'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1]). // %} + ') + }Asx', + + 'lex_block_line' => '{\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1]).'}As', + + // {{ or {% or {# + 'lex_tokens_start' => '{ + ('. + preg_quote($this->options['tag_variable'][0]). // {{ + '|'. + preg_quote($this->options['tag_block'][0]). // {% + '|'. + preg_quote($this->options['tag_comment'][0], '#'). // {# + ')('. + preg_quote($this->options['whitespace_trim']). // - + '|'. + preg_quote($this->options['whitespace_line_trim']). // ~ + ')? + }sx', + 'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0]).'\s*}A', + 'interpolation_end' => '{\s*'.preg_quote($this->options['interpolation'][1]).'}A', + ]; + } + + public function tokenize($code, $name = null) + { + if (!$code instanceof Source) { + @trigger_error(sprintf('Passing a string as the $code argument of %s() is deprecated since version 1.27 and will be removed in 2.0. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $this->source = new Source($code, $name); + } else { + $this->source = $code; + } + + if (((int) ini_get('mbstring.func_overload')) & 2) { + @trigger_error('Support for having "mbstring.func_overload" different from 0 is deprecated version 1.29 and will be removed in 2.0.', E_USER_DEPRECATED); + } + + if (\function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } else { + $mbEncoding = null; + } + + $this->code = str_replace(["\r\n", "\r"], "\n", $this->source->getCode()); + $this->filename = $this->source->getName(); + $this->cursor = 0; + $this->lineno = 1; + $this->end = \strlen($this->code); + $this->tokens = []; + $this->state = self::STATE_DATA; + $this->states = []; + $this->brackets = []; + $this->position = -1; + + // find all token starts in one go + preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE); + $this->positions = $matches; + + while ($this->cursor < $this->end) { + // dispatch to the lexing functions depending + // on the current state + switch ($this->state) { + case self::STATE_DATA: + $this->lexData(); + break; + + case self::STATE_BLOCK: + $this->lexBlock(); + break; + + case self::STATE_VAR: + $this->lexVar(); + break; + + case self::STATE_STRING: + $this->lexString(); + break; + + case self::STATE_INTERPOLATION: + $this->lexInterpolation(); + break; + } + } + + $this->pushToken(Token::EOF_TYPE); + + if (!empty($this->brackets)) { + list($expect, $lineno) = array_pop($this->brackets); + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + if ($mbEncoding) { + mb_internal_encoding($mbEncoding); + } + + return new TokenStream($this->tokens, $this->source); + } + + protected function lexData() + { + // if no matches are left we return the rest of the template as simple text token + if ($this->position == \count($this->positions[0]) - 1) { + $this->pushToken(Token::TEXT_TYPE, substr($this->code, $this->cursor)); + $this->cursor = $this->end; + + return; + } + + // Find the first token after the current cursor + $position = $this->positions[0][++$this->position]; + while ($position[1] < $this->cursor) { + if ($this->position == \count($this->positions[0]) - 1) { + return; + } + $position = $this->positions[0][++$this->position]; + } + + // push the template text first + $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor); + + // trim? + if (isset($this->positions[2][$this->position][0])) { + if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } else { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } + } + $this->pushToken(Token::TEXT_TYPE, $text); + $this->moveCursor($textContent.$position[0]); + + switch ($this->positions[1][$this->position][0]) { + case $this->options['tag_comment'][0]: + $this->lexComment(); + break; + + case $this->options['tag_block'][0]: + // raw data? + if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lexRawData($match[1]); + // {% line \d+ %} + } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lineno = (int) $match[1]; + } else { + $this->pushToken(Token::BLOCK_START_TYPE); + $this->pushState(self::STATE_BLOCK); + $this->currentVarBlockLine = $this->lineno; + } + break; + + case $this->options['tag_variable'][0]: + $this->pushToken(Token::VAR_START_TYPE); + $this->pushState(self::STATE_VAR); + $this->currentVarBlockLine = $this->lineno; + break; + } + } + + protected function lexBlock() + { + if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::BLOCK_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + protected function lexVar() + { + if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::VAR_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + protected function lexExpression() + { + // whitespace + if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + + if ($this->cursor >= $this->end) { + throw new SyntaxError(sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); + } + } + + // operators + if (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::OPERATOR_TYPE, preg_replace('/\s+/', ' ', $match[0])); + $this->moveCursor($match[0]); + } + // names + elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::NAME_TYPE, $match[0]); + $this->moveCursor($match[0]); + } + // numbers + elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) { + $number = (float) $match[0]; // floats + if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) { + $number = (int) $match[0]; // integers lower than the maximum + } + $this->pushToken(Token::NUMBER_TYPE, $number); + $this->moveCursor($match[0]); + } + // punctuation + elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { + // opening bracket + if (false !== strpos('([{', $this->code[$this->cursor])) { + $this->brackets[] = [$this->code[$this->cursor], $this->lineno]; + } + // closing bracket + elseif (false !== strpos(')]}', $this->code[$this->cursor])) { + if (empty($this->brackets)) { + throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + + list($expect, $lineno) = array_pop($this->brackets); + if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + } + + $this->pushToken(Token::PUNCTUATION_TYPE, $this->code[$this->cursor]); + ++$this->cursor; + } + // strings + elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { + $this->pushToken(Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1))); + $this->moveCursor($match[0]); + } + // opening double quoted string + elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { + $this->brackets[] = ['"', $this->lineno]; + $this->pushState(self::STATE_STRING); + $this->moveCursor($match[0]); + } + // unlexable + else { + throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + protected function lexRawData($tag) + { + if ('raw' === $tag) { + @trigger_error(sprintf('Twig Tag "raw" is deprecated since version 1.21. Use "verbatim" instead in %s at line %d.', $this->filename, $this->lineno), E_USER_DEPRECATED); + } + + if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError(sprintf('Unexpected end of file: Unclosed "%s" block.', $tag), $this->lineno, $this->source); + } + + $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); + $this->moveCursor($text.$match[0][0]); + + // trim? + if (isset($match[1][0])) { + if ($this->options['whitespace_trim'] === $match[1][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } else { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } + } + + $this->pushToken(Token::TEXT_TYPE, $text); + } + + protected function lexComment() + { + if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source); + } + + $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); + } + + protected function lexString() + { + if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) { + $this->brackets[] = [$this->options['interpolation'][0], $this->lineno]; + $this->pushToken(Token::INTERPOLATION_START_TYPE); + $this->moveCursor($match[0]); + $this->pushState(self::STATE_INTERPOLATION); + } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) { + $this->pushToken(Token::STRING_TYPE, stripcslashes($match[0])); + $this->moveCursor($match[0]); + } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { + list($expect, $lineno) = array_pop($this->brackets); + if ('"' != $this->code[$this->cursor]) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + $this->popState(); + ++$this->cursor; + } else { + // unlexable + throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + protected function lexInterpolation() + { + $bracket = end($this->brackets); + if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) { + array_pop($this->brackets); + $this->pushToken(Token::INTERPOLATION_END_TYPE); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + protected function pushToken($type, $value = '') + { + // do not push empty text tokens + if (Token::TEXT_TYPE === $type && '' === $value) { + return; + } + + $this->tokens[] = new Token($type, $value, $this->lineno); + } + + protected function moveCursor($text) + { + $this->cursor += \strlen($text); + $this->lineno += substr_count($text, "\n"); + } + + protected function getOperatorRegex() + { + $operators = array_merge( + ['='], + array_keys($this->env->getUnaryOperators()), + array_keys($this->env->getBinaryOperators()) + ); + + $operators = array_combine($operators, array_map('strlen', $operators)); + arsort($operators); + + $regex = []; + foreach ($operators as $operator => $length) { + // an operator that ends with a character must be followed by + // a whitespace or a parenthesis + if (ctype_alpha($operator[$length - 1])) { + $r = preg_quote($operator, '/').'(?=[\s()])'; + } else { + $r = preg_quote($operator, '/'); + } + + // an operator with a space can be any amount of whitespaces + $r = preg_replace('/\s+/', '\s+', $r); + + $regex[] = $r; + } + + return '/'.implode('|', $regex).'/A'; + } + + protected function pushState($state) + { + $this->states[] = $this->state; + $this->state = $state; + } + + protected function popState() + { + if (0 === \count($this->states)) { + throw new \LogicException('Cannot pop state without a previous state.'); + } + + $this->state = array_pop($this->states); + } +} + +class_alias('Twig\Lexer', 'Twig_Lexer'); diff --git a/vendor/twig/twig/src/Loader/ArrayLoader.php b/vendor/twig/twig/src/Loader/ArrayLoader.php new file mode 100644 index 0000000..6bc430f --- /dev/null +++ b/vendor/twig/twig/src/Loader/ArrayLoader.php @@ -0,0 +1,102 @@ + + */ +class ArrayLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface +{ + protected $templates = []; + + /** + * @param array $templates An array of templates (keys are the names, and values are the source code) + */ + public function __construct(array $templates = []) + { + $this->templates = $templates; + } + + /** + * Adds or overrides a template. + * + * @param string $name The template name + * @param string $template The template source + */ + public function setTemplate($name, $template) + { + $this->templates[(string) $name] = $template; + } + + public function getSource($name) + { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED); + + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return $this->templates[$name]; + } + + public function getSourceContext($name) + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return new Source($this->templates[$name], $name); + } + + public function exists($name) + { + return isset($this->templates[(string) $name]); + } + + public function getCacheKey($name) + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return $name.':'.$this->templates[$name]; + } + + public function isFresh($name, $time) + { + $name = (string) $name; + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return true; + } +} + +class_alias('Twig\Loader\ArrayLoader', 'Twig_Loader_Array'); diff --git a/vendor/twig/twig/src/Loader/ChainLoader.php b/vendor/twig/twig/src/Loader/ChainLoader.php new file mode 100644 index 0000000..25ac55a --- /dev/null +++ b/vendor/twig/twig/src/Loader/ChainLoader.php @@ -0,0 +1,164 @@ + + */ +class ChainLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface +{ + private $hasSourceCache = []; + protected $loaders = []; + + /** + * @param LoaderInterface[] $loaders + */ + public function __construct(array $loaders = []) + { + foreach ($loaders as $loader) { + $this->addLoader($loader); + } + } + + public function addLoader(LoaderInterface $loader) + { + $this->loaders[] = $loader; + $this->hasSourceCache = []; + } + + /** + * @return LoaderInterface[] + */ + public function getLoaders() + { + return $this->loaders; + } + + public function getSource($name) + { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED); + + $exceptions = []; + foreach ($this->loaders as $loader) { + if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + return $loader->getSource($name); + } catch (LoaderError $e) { + $exceptions[] = $e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function getSourceContext($name) + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + if ($loader instanceof SourceContextLoaderInterface) { + return $loader->getSourceContext($name); + } + + return new Source($loader->getSource($name), $name); + } catch (LoaderError $e) { + $exceptions[] = $e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function exists($name) + { + $name = (string) $name; + + if (isset($this->hasSourceCache[$name])) { + return $this->hasSourceCache[$name]; + } + + foreach ($this->loaders as $loader) { + if ($loader instanceof ExistsLoaderInterface) { + if ($loader->exists($name)) { + return $this->hasSourceCache[$name] = true; + } + + continue; + } + + try { + if ($loader instanceof SourceContextLoaderInterface) { + $loader->getSourceContext($name); + } else { + $loader->getSource($name); + } + + return $this->hasSourceCache[$name] = true; + } catch (LoaderError $e) { + } + } + + return $this->hasSourceCache[$name] = false; + } + + public function getCacheKey($name) + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + return $loader->getCacheKey($name); + } catch (LoaderError $e) { + $exceptions[] = \get_class($loader).': '.$e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function isFresh($name, $time) + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if ($loader instanceof ExistsLoaderInterface && !$loader->exists($name)) { + continue; + } + + try { + return $loader->isFresh($name, $time); + } catch (LoaderError $e) { + $exceptions[] = \get_class($loader).': '.$e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } +} + +class_alias('Twig\Loader\ChainLoader', 'Twig_Loader_Chain'); diff --git a/vendor/twig/twig/src/Loader/ExistsLoaderInterface.php b/vendor/twig/twig/src/Loader/ExistsLoaderInterface.php new file mode 100644 index 0000000..940d876 --- /dev/null +++ b/vendor/twig/twig/src/Loader/ExistsLoaderInterface.php @@ -0,0 +1,33 @@ + + * + * @deprecated since 1.12 (to be removed in 3.0) + */ +interface ExistsLoaderInterface +{ + /** + * Check if we have the source code of a template, given its name. + * + * @param string $name The name of the template to check if we can load + * + * @return bool If the template source code is handled by this loader or not + */ + public function exists($name); +} + +class_alias('Twig\Loader\ExistsLoaderInterface', 'Twig_ExistsLoaderInterface'); diff --git a/vendor/twig/twig/src/Loader/FilesystemLoader.php b/vendor/twig/twig/src/Loader/FilesystemLoader.php new file mode 100644 index 0000000..ae9c979 --- /dev/null +++ b/vendor/twig/twig/src/Loader/FilesystemLoader.php @@ -0,0 +1,303 @@ + + */ +class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface +{ + /** Identifier of the main namespace. */ + const MAIN_NAMESPACE = '__main__'; + + protected $paths = []; + protected $cache = []; + protected $errorCache = []; + + private $rootPath; + + /** + * @param string|array $paths A path or an array of paths where to look for templates + * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) + */ + public function __construct($paths = [], $rootPath = null) + { + $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR; + if (false !== $realPath = realpath($rootPath)) { + $this->rootPath = $realPath.\DIRECTORY_SEPARATOR; + } + + if ($paths) { + $this->setPaths($paths); + } + } + + /** + * Returns the paths to the templates. + * + * @param string $namespace A path namespace + * + * @return array The array of paths where to look for templates + */ + public function getPaths($namespace = self::MAIN_NAMESPACE) + { + return isset($this->paths[$namespace]) ? $this->paths[$namespace] : []; + } + + /** + * Returns the path namespaces. + * + * The main namespace is always defined. + * + * @return array The array of defined namespaces + */ + public function getNamespaces() + { + return array_keys($this->paths); + } + + /** + * Sets the paths where templates are stored. + * + * @param string|array $paths A path or an array of paths where to look for templates + * @param string $namespace A path namespace + */ + public function setPaths($paths, $namespace = self::MAIN_NAMESPACE) + { + if (!\is_array($paths)) { + $paths = [$paths]; + } + + $this->paths[$namespace] = []; + foreach ($paths as $path) { + $this->addPath($path, $namespace); + } + } + + /** + * Adds a path where templates are stored. + * + * @param string $path A path where to look for templates + * @param string $namespace A path namespace + * + * @throws LoaderError + */ + public function addPath($path, $namespace = self::MAIN_NAMESPACE) + { + // invalidate the cache + $this->cache = $this->errorCache = []; + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $this->paths[$namespace][] = rtrim($path, '/\\'); + } + + /** + * Prepends a path where templates are stored. + * + * @param string $path A path where to look for templates + * @param string $namespace A path namespace + * + * @throws LoaderError + */ + public function prependPath($path, $namespace = self::MAIN_NAMESPACE) + { + // invalidate the cache + $this->cache = $this->errorCache = []; + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $path = rtrim($path, '/\\'); + + if (!isset($this->paths[$namespace])) { + $this->paths[$namespace][] = $path; + } else { + array_unshift($this->paths[$namespace], $path); + } + } + + public function getSource($name) + { + @trigger_error(sprintf('Calling "getSource" on "%s" is deprecated since 1.27. Use getSourceContext() instead.', \get_class($this)), E_USER_DEPRECATED); + + return file_get_contents($this->findTemplate($name)); + } + + public function getSourceContext($name) + { + $path = $this->findTemplate($name); + + return new Source(file_get_contents($path), $name, $path); + } + + public function getCacheKey($name) + { + $path = $this->findTemplate($name); + $len = \strlen($this->rootPath); + if (0 === strncmp($this->rootPath, $path, $len)) { + return substr($path, $len); + } + + return $path; + } + + public function exists($name) + { + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return true; + } + + try { + return false !== $this->findTemplate($name, false); + } catch (LoaderError $e) { + @trigger_error(sprintf('In %s::findTemplate(), you must accept a second argument that when set to "false" returns "false" instead of throwing an exception. Not supporting this argument is deprecated since version 1.27.', \get_class($this)), E_USER_DEPRECATED); + + return false; + } + } + + public function isFresh($name, $time) + { + return filemtime($this->findTemplate($name)) < $time; + } + + protected function findTemplate($name) + { + $throw = \func_num_args() > 1 ? func_get_arg(1) : true; + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return $this->cache[$name]; + } + + if (isset($this->errorCache[$name])) { + if (!$throw) { + return false; + } + + throw new LoaderError($this->errorCache[$name]); + } + + try { + $this->validateName($name); + + list($namespace, $shortname) = $this->parseName($name); + } catch (LoaderError $e) { + if (!$throw) { + return false; + } + + throw $e; + } + + if (!isset($this->paths[$namespace])) { + $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace); + + if (!$throw) { + return false; + } + + throw new LoaderError($this->errorCache[$name]); + } + + foreach ($this->paths[$namespace] as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->rootPath.$path; + } + + if (is_file($path.'/'.$shortname)) { + if (false !== $realpath = realpath($path.'/'.$shortname)) { + return $this->cache[$name] = $realpath; + } + + return $this->cache[$name] = $path.'/'.$shortname; + } + } + + $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); + + if (!$throw) { + return false; + } + + throw new LoaderError($this->errorCache[$name]); + } + + protected function parseName($name, $default = self::MAIN_NAMESPACE) + { + if (isset($name[0]) && '@' == $name[0]) { + if (false === $pos = strpos($name, '/')) { + throw new LoaderError(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + } + + $namespace = substr($name, 1, $pos - 1); + $shortname = substr($name, $pos + 1); + + return [$namespace, $shortname]; + } + + return [$default, $name]; + } + + protected function normalizeName($name) + { + return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name)); + } + + protected function validateName($name) + { + if (false !== strpos($name, "\0")) { + throw new LoaderError('A template name cannot contain NUL bytes.'); + } + + $name = ltrim($name, '/'); + $parts = explode('/', $name); + $level = 0; + foreach ($parts as $part) { + if ('..' === $part) { + --$level; + } elseif ('.' !== $part) { + ++$level; + } + + if ($level < 0) { + throw new LoaderError(sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); + } + } + } + + private function isAbsolutePath($file) + { + return strspn($file, '/\\', 0, 1) + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === substr($file, 1, 1) + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ; + } +} + +class_alias('Twig\Loader\FilesystemLoader', 'Twig_Loader_Filesystem'); diff --git a/vendor/twig/twig/src/Loader/LoaderInterface.php b/vendor/twig/twig/src/Loader/LoaderInterface.php new file mode 100644 index 0000000..15be7a8 --- /dev/null +++ b/vendor/twig/twig/src/Loader/LoaderInterface.php @@ -0,0 +1,61 @@ + + */ +interface LoaderInterface +{ + /** + * Gets the source code of a template, given its name. + * + * @param string $name The name of the template to load + * + * @return string The template source code + * + * @throws LoaderError When $name is not found + * + * @deprecated since 1.27 (to be removed in 2.0), implement Twig\Loader\SourceContextLoaderInterface + */ + public function getSource($name); + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @param string $name The name of the template to load + * + * @return string The cache key + * + * @throws LoaderError When $name is not found + */ + public function getCacheKey($name); + + /** + * Returns true if the template is still fresh. + * + * @param string $name The template name + * @param int $time Timestamp of the last modification time of the + * cached template + * + * @return bool true if the template is fresh, false otherwise + * + * @throws LoaderError When $name is not found + */ + public function isFresh($name, $time); +} + +class_alias('Twig\Loader\LoaderInterface', 'Twig_LoaderInterface'); diff --git a/vendor/twig/twig/src/Loader/SourceContextLoaderInterface.php b/vendor/twig/twig/src/Loader/SourceContextLoaderInterface.php new file mode 100644 index 0000000..78b1fcd --- /dev/null +++ b/vendor/twig/twig/src/Loader/SourceContextLoaderInterface.php @@ -0,0 +1,38 @@ + + * + * @deprecated since 1.27 (to be removed in 3.0) + */ +interface SourceContextLoaderInterface +{ + /** + * Returns the source context for a given template logical name. + * + * @param string $name The template logical name + * + * @return Source + * + * @throws LoaderError When $name is not found + */ + public function getSourceContext($name); +} + +class_alias('Twig\Loader\SourceContextLoaderInterface', 'Twig_SourceContextLoaderInterface'); diff --git a/vendor/twig/twig/src/Markup.php b/vendor/twig/twig/src/Markup.php new file mode 100644 index 0000000..107941c --- /dev/null +++ b/vendor/twig/twig/src/Markup.php @@ -0,0 +1,41 @@ + + */ +class Markup implements \Countable +{ + protected $content; + protected $charset; + + public function __construct($content, $charset) + { + $this->content = (string) $content; + $this->charset = $charset; + } + + public function __toString() + { + return $this->content; + } + + public function count() + { + return \function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : \strlen($this->content); + } +} + +class_alias('Twig\Markup', 'Twig_Markup'); diff --git a/vendor/twig/twig/src/Node/AutoEscapeNode.php b/vendor/twig/twig/src/Node/AutoEscapeNode.php new file mode 100644 index 0000000..a940306 --- /dev/null +++ b/vendor/twig/twig/src/Node/AutoEscapeNode.php @@ -0,0 +1,40 @@ + + */ +class AutoEscapeNode extends Node +{ + public function __construct($value, \Twig_NodeInterface $body, $lineno, $tag = 'autoescape') + { + parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler->subcompile($this->getNode('body')); + } +} + +class_alias('Twig\Node\AutoEscapeNode', 'Twig_Node_AutoEscape'); diff --git a/vendor/twig/twig/src/Node/BlockNode.php b/vendor/twig/twig/src/Node/BlockNode.php new file mode 100644 index 0000000..1ffc8ca --- /dev/null +++ b/vendor/twig/twig/src/Node/BlockNode.php @@ -0,0 +1,45 @@ + + */ +class BlockNode extends Node +{ + public function __construct($name, \Twig_NodeInterface $body, $lineno, $tag = null) + { + parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n") + ->indent() + ; + + $compiler + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n\n") + ; + } +} + +class_alias('Twig\Node\BlockNode', 'Twig_Node_Block'); diff --git a/vendor/twig/twig/src/Node/BlockReferenceNode.php b/vendor/twig/twig/src/Node/BlockReferenceNode.php new file mode 100644 index 0000000..de06909 --- /dev/null +++ b/vendor/twig/twig/src/Node/BlockReferenceNode.php @@ -0,0 +1,38 @@ + + */ +class BlockReferenceNode extends Node implements NodeOutputInterface +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct([], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) + ; + } +} + +class_alias('Twig\Node\BlockReferenceNode', 'Twig_Node_BlockReference'); diff --git a/vendor/twig/twig/src/Node/BodyNode.php b/vendor/twig/twig/src/Node/BodyNode.php new file mode 100644 index 0000000..5290be5 --- /dev/null +++ b/vendor/twig/twig/src/Node/BodyNode.php @@ -0,0 +1,23 @@ + + */ +class BodyNode extends Node +{ +} + +class_alias('Twig\Node\BodyNode', 'Twig_Node_Body'); diff --git a/vendor/twig/twig/src/Node/CheckSecurityNode.php b/vendor/twig/twig/src/Node/CheckSecurityNode.php new file mode 100644 index 0000000..cf0a7a1 --- /dev/null +++ b/vendor/twig/twig/src/Node/CheckSecurityNode.php @@ -0,0 +1,85 @@ + + */ +class CheckSecurityNode extends Node +{ + protected $usedFilters; + protected $usedTags; + protected $usedFunctions; + + public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) + { + $this->usedFilters = $usedFilters; + $this->usedTags = $usedTags; + $this->usedFunctions = $usedFunctions; + + parent::__construct(); + } + + public function compile(Compiler $compiler) + { + $tags = $filters = $functions = []; + foreach (['tags', 'filters', 'functions'] as $type) { + foreach ($this->{'used'.ucfirst($type)} as $name => $node) { + if ($node instanceof Node) { + ${$type}[$name] = $node->getTemplateLine(); + } else { + ${$type}[$node] = null; + } + } + } + + $compiler + ->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n") + ->write('$tags = ')->repr(array_filter($tags))->raw(";\n") + ->write('$filters = ')->repr(array_filter($filters))->raw(";\n") + ->write('$functions = ')->repr(array_filter($functions))->raw(";\n\n") + ->write("try {\n") + ->indent() + ->write("\$this->sandbox->checkSecurity(\n") + ->indent() + ->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n") + ->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n") + ->write(!$functions ? "[]\n" : "['".implode("', '", array_keys($functions))."']\n") + ->outdent() + ->write(");\n") + ->outdent() + ->write("} catch (SecurityError \$e) {\n") + ->indent() + ->write("\$e->setSourceContext(\$this->getSourceContext());\n\n") + ->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n") + ->outdent() + ->write("}\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n\n") + ; + } +} + +class_alias('Twig\Node\CheckSecurityNode', 'Twig_Node_CheckSecurity'); diff --git a/vendor/twig/twig/src/Node/CheckToStringNode.php b/vendor/twig/twig/src/Node/CheckToStringNode.php new file mode 100644 index 0000000..5d67467 --- /dev/null +++ b/vendor/twig/twig/src/Node/CheckToStringNode.php @@ -0,0 +1,42 @@ + + */ +class CheckToStringNode extends AbstractExpression +{ + public function __construct(AbstractExpression $expr) + { + parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag()); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('$this->sandbox->ensureToStringAllowed(') + ->subcompile($this->getNode('expr')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/DeprecatedNode.php b/vendor/twig/twig/src/Node/DeprecatedNode.php new file mode 100644 index 0000000..62c0dd4 --- /dev/null +++ b/vendor/twig/twig/src/Node/DeprecatedNode.php @@ -0,0 +1,55 @@ + + */ +class DeprecatedNode extends Node +{ + public function __construct(AbstractExpression $expr, $lineno, $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + + $expr = $this->getNode('expr'); + + if ($expr instanceof ConstantExpression) { + $compiler->write('@trigger_error(') + ->subcompile($expr); + } else { + $varName = $compiler->getVarName(); + $compiler->write(sprintf('$%s = ', $varName)) + ->subcompile($expr) + ->raw(";\n") + ->write(sprintf('@trigger_error($%s', $varName)); + } + + $compiler + ->raw('.') + ->string(sprintf(' ("%s" at line %d).', $this->getTemplateName(), $this->getTemplateLine())) + ->raw(", E_USER_DEPRECATED);\n") + ; + } +} + +class_alias('Twig\Node\DeprecatedNode', 'Twig_Node_Deprecated'); diff --git a/vendor/twig/twig/src/Node/DoNode.php b/vendor/twig/twig/src/Node/DoNode.php new file mode 100644 index 0000000..80c4cea --- /dev/null +++ b/vendor/twig/twig/src/Node/DoNode.php @@ -0,0 +1,40 @@ + + */ +class DoNode extends Node +{ + public function __construct(AbstractExpression $expr, $lineno, $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} + +class_alias('Twig\Node\DoNode', 'Twig_Node_Do'); diff --git a/vendor/twig/twig/src/Node/EmbedNode.php b/vendor/twig/twig/src/Node/EmbedNode.php new file mode 100644 index 0000000..05051ec --- /dev/null +++ b/vendor/twig/twig/src/Node/EmbedNode.php @@ -0,0 +1,52 @@ + + */ +class EmbedNode extends IncludeNode +{ + // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) + public function __construct($name, $index, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) + { + parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); + + $this->setAttribute('name', $name); + // to be removed in 2.0, used name instead + $this->setAttribute('filename', $name); + $this->setAttribute('index', $index); + } + + protected function addGetTemplate(Compiler $compiler) + { + $compiler + ->write('$this->loadTemplate(') + ->string($this->getAttribute('name')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(', ') + ->string($this->getAttribute('index')) + ->raw(')') + ; + } +} + +class_alias('Twig\Node\EmbedNode', 'Twig_Node_Embed'); diff --git a/vendor/twig/twig/src/Node/Expression/AbstractExpression.php b/vendor/twig/twig/src/Node/Expression/AbstractExpression.php new file mode 100644 index 0000000..a352892 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/AbstractExpression.php @@ -0,0 +1,26 @@ + + */ +abstract class AbstractExpression extends Node +{ +} + +class_alias('Twig\Node\Expression\AbstractExpression', 'Twig_Node_Expression'); diff --git a/vendor/twig/twig/src/Node/Expression/ArrayExpression.php b/vendor/twig/twig/src/Node/Expression/ArrayExpression.php new file mode 100644 index 0000000..cd63f93 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ArrayExpression.php @@ -0,0 +1,88 @@ +index = -1; + foreach ($this->getKeyValuePairs() as $pair) { + if ($pair['key'] instanceof ConstantExpression && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) { + $this->index = $pair['key']->getAttribute('value'); + } + } + } + + public function getKeyValuePairs() + { + $pairs = []; + + foreach (array_chunk($this->nodes, 2) as $pair) { + $pairs[] = [ + 'key' => $pair[0], + 'value' => $pair[1], + ]; + } + + return $pairs; + } + + public function hasElement(AbstractExpression $key) + { + foreach ($this->getKeyValuePairs() as $pair) { + // we compare the string representation of the keys + // to avoid comparing the line numbers which are not relevant here. + if ((string) $key === (string) $pair['key']) { + return true; + } + } + + return false; + } + + public function addElement(AbstractExpression $value, AbstractExpression $key = null) + { + if (null === $key) { + $key = new ConstantExpression(++$this->index, $value->getTemplateLine()); + } + + array_push($this->nodes, $key, $value); + } + + public function compile(Compiler $compiler) + { + $compiler->raw('['); + $first = true; + foreach ($this->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler + ->subcompile($pair['key']) + ->raw(' => ') + ->subcompile($pair['value']) + ; + } + $compiler->raw(']'); + } +} + +class_alias('Twig\Node\Expression\ArrayExpression', 'Twig_Node_Expression_Array'); diff --git a/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php b/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php new file mode 100644 index 0000000..62c4ac0 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php @@ -0,0 +1,29 @@ +raw('$context[') + ->string($this->getAttribute('name')) + ->raw(']') + ; + } +} + +class_alias('Twig\Node\Expression\AssignNameExpression', 'Twig_Node_Expression_AssignName'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php new file mode 100644 index 0000000..0600aee --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php @@ -0,0 +1,43 @@ + $left, 'right' => $right], [], $lineno); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('left')) + ->raw(' ') + ; + $this->operator($compiler); + $compiler + ->raw(' ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + abstract public function operator(Compiler $compiler); +} + +class_alias('Twig\Node\Expression\Binary\AbstractBinary', 'Twig_Node_Expression_Binary'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php new file mode 100644 index 0000000..f7719a1 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php @@ -0,0 +1,25 @@ +raw('+'); + } +} + +class_alias('Twig\Node\Expression\Binary\AddBinary', 'Twig_Node_Expression_Binary_Add'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php new file mode 100644 index 0000000..484597d --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php @@ -0,0 +1,25 @@ +raw('&&'); + } +} + +class_alias('Twig\Node\Expression\Binary\AndBinary', 'Twig_Node_Expression_Binary_And'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php new file mode 100644 index 0000000..cf28691 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php @@ -0,0 +1,25 @@ +raw('&'); + } +} + +class_alias('Twig\Node\Expression\Binary\BitwiseAndBinary', 'Twig_Node_Expression_Binary_BitwiseAnd'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php new file mode 100644 index 0000000..7d5d260 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php @@ -0,0 +1,25 @@ +raw('|'); + } +} + +class_alias('Twig\Node\Expression\Binary\BitwiseOrBinary', 'Twig_Node_Expression_Binary_BitwiseOr'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php new file mode 100644 index 0000000..7291987 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php @@ -0,0 +1,25 @@ +raw('^'); + } +} + +class_alias('Twig\Node\Expression\Binary\BitwiseXorBinary', 'Twig_Node_Expression_Binary_BitwiseXor'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php new file mode 100644 index 0000000..f6e5938 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php @@ -0,0 +1,25 @@ +raw('.'); + } +} + +class_alias('Twig\Node\Expression\Binary\ConcatBinary', 'Twig_Node_Expression_Binary_Concat'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php new file mode 100644 index 0000000..ebfcc75 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php @@ -0,0 +1,25 @@ +raw('/'); + } +} + +class_alias('Twig\Node\Expression\Binary\DivBinary', 'Twig_Node_Expression_Binary_Div'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php new file mode 100644 index 0000000..41a0065 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php @@ -0,0 +1,37 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right)) + ; + } + + public function operator(Compiler $compiler) + { + return $compiler->raw(''); + } +} + +class_alias('Twig\Node\Expression\Binary\EndsWithBinary', 'Twig_Node_Expression_Binary_EndsWith'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php new file mode 100644 index 0000000..84904c3 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php @@ -0,0 +1,24 @@ +raw('=='); + } +} + +class_alias('Twig\Node\Expression\Binary\EqualBinary', 'Twig_Node_Expression_Binary_Equal'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php new file mode 100644 index 0000000..4dd5e3d --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php @@ -0,0 +1,31 @@ +raw('(int) floor('); + parent::compile($compiler); + $compiler->raw(')'); + } + + public function operator(Compiler $compiler) + { + return $compiler->raw('/'); + } +} + +class_alias('Twig\Node\Expression\Binary\FloorDivBinary', 'Twig_Node_Expression_Binary_FloorDiv'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php new file mode 100644 index 0000000..be73001 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php @@ -0,0 +1,24 @@ +raw('>'); + } +} + +class_alias('Twig\Node\Expression\Binary\GreaterBinary', 'Twig_Node_Expression_Binary_Greater'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php new file mode 100644 index 0000000..5c2ae72 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php @@ -0,0 +1,24 @@ +raw('>='); + } +} + +class_alias('Twig\Node\Expression\Binary\GreaterEqualBinary', 'Twig_Node_Expression_Binary_GreaterEqual'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php new file mode 100644 index 0000000..f00b230 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php @@ -0,0 +1,35 @@ +raw('twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler) + { + return $compiler->raw('in'); + } +} + +class_alias('Twig\Node\Expression\Binary\InBinary', 'Twig_Node_Expression_Binary_In'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php new file mode 100644 index 0000000..2b202da --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php @@ -0,0 +1,24 @@ +raw('<'); + } +} + +class_alias('Twig\Node\Expression\Binary\LessBinary', 'Twig_Node_Expression_Binary_Less'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php new file mode 100644 index 0000000..4fffafe --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php @@ -0,0 +1,24 @@ +raw('<='); + } +} + +class_alias('Twig\Node\Expression\Binary\LessEqualBinary', 'Twig_Node_Expression_Binary_LessEqual'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php new file mode 100644 index 0000000..ae810b2 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php @@ -0,0 +1,35 @@ +raw('preg_match(') + ->subcompile($this->getNode('right')) + ->raw(', ') + ->subcompile($this->getNode('left')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler) + { + return $compiler->raw(''); + } +} + +class_alias('Twig\Node\Expression\Binary\MatchesBinary', 'Twig_Node_Expression_Binary_Matches'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php new file mode 100644 index 0000000..e6a2b36 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php @@ -0,0 +1,25 @@ +raw('%'); + } +} + +class_alias('Twig\Node\Expression\Binary\ModBinary', 'Twig_Node_Expression_Binary_Mod'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php new file mode 100644 index 0000000..cd65f5d --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php @@ -0,0 +1,25 @@ +raw('*'); + } +} + +class_alias('Twig\Node\Expression\Binary\MulBinary', 'Twig_Node_Expression_Binary_Mul'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php new file mode 100644 index 0000000..df5c6a2 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php @@ -0,0 +1,24 @@ +raw('!='); + } +} + +class_alias('Twig\Node\Expression\Binary\NotEqualBinary', 'Twig_Node_Expression_Binary_NotEqual'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php new file mode 100644 index 0000000..ed2034e --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php @@ -0,0 +1,35 @@ +raw('!twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler) + { + return $compiler->raw('not in'); + } +} + +class_alias('Twig\Node\Expression\Binary\NotInBinary', 'Twig_Node_Expression_Binary_NotIn'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php new file mode 100644 index 0000000..8f9da43 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php @@ -0,0 +1,25 @@ +raw('||'); + } +} + +class_alias('Twig\Node\Expression\Binary\OrBinary', 'Twig_Node_Expression_Binary_Or'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php new file mode 100644 index 0000000..10a8d94 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php @@ -0,0 +1,39 @@ += 50600) { + return parent::compile($compiler); + } + + $compiler + ->raw('pow(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler) + { + return $compiler->raw('**'); + } +} + +class_alias('Twig\Node\Expression\Binary\PowerBinary', 'Twig_Node_Expression_Binary_Power'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php new file mode 100644 index 0000000..e9c0cdf --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php @@ -0,0 +1,35 @@ +raw('range(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler) + { + return $compiler->raw('..'); + } +} + +class_alias('Twig\Node\Expression\Binary\RangeBinary', 'Twig_Node_Expression_Binary_Range'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php new file mode 100644 index 0000000..1fe59fb --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php @@ -0,0 +1,37 @@ +getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right)) + ; + } + + public function operator(Compiler $compiler) + { + return $compiler->raw(''); + } +} + +class_alias('Twig\Node\Expression\Binary\StartsWithBinary', 'Twig_Node_Expression_Binary_StartsWith'); diff --git a/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php new file mode 100644 index 0000000..2546975 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php @@ -0,0 +1,25 @@ +raw('-'); + } +} + +class_alias('Twig\Node\Expression\Binary\SubBinary', 'Twig_Node_Expression_Binary_Sub'); diff --git a/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php b/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php new file mode 100644 index 0000000..0a56849 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php @@ -0,0 +1,98 @@ + + */ +class BlockReferenceExpression extends AbstractExpression +{ + /** + * @param Node|null $template + */ + public function __construct(\Twig_NodeInterface $name, $template = null, $lineno, $tag = null) + { + if (\is_bool($template)) { + @trigger_error(sprintf('The %s method "$asString" argument is deprecated since version 1.28 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED); + + $template = null; + } + + $nodes = ['name' => $name]; + if (null !== $template) { + $nodes['template'] = $template; + } + + parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + if ($this->getAttribute('is_defined_test')) { + $this->compileTemplateCall($compiler, 'hasBlock'); + } else { + if ($this->getAttribute('output')) { + $compiler->addDebugInfo($this); + + $this + ->compileTemplateCall($compiler, 'displayBlock') + ->raw(";\n"); + } else { + $this->compileTemplateCall($compiler, 'renderBlock'); + } + } + } + + private function compileTemplateCall(Compiler $compiler, $method) + { + if (!$this->hasNode('template')) { + $compiler->write('$this'); + } else { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('template')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + $compiler->raw(sprintf('->%s', $method)); + $this->compileBlockArguments($compiler); + + return $compiler; + } + + private function compileBlockArguments(Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('name')) + ->raw(', $context'); + + if (!$this->hasNode('template')) { + $compiler->raw(', $blocks'); + } + + return $compiler->raw(')'); + } +} + +class_alias('Twig\Node\Expression\BlockReferenceExpression', 'Twig_Node_Expression_BlockReference'); diff --git a/vendor/twig/twig/src/Node/Expression/CallExpression.php b/vendor/twig/twig/src/Node/Expression/CallExpression.php new file mode 100644 index 0000000..d202a73 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/CallExpression.php @@ -0,0 +1,305 @@ +hasAttribute('callable') && $callable = $this->getAttribute('callable')) { + if (\is_string($callable) && false === strpos($callable, '::')) { + $compiler->raw($callable); + } else { + list($r, $callable) = $this->reflectCallable($callable); + if ($r instanceof \ReflectionMethod && \is_string($callable[0])) { + if ($r->isStatic()) { + $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); + } else { + $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); + } + } elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) { + $compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', \get_class($callable[0]), $callable[1])); + } else { + $type = ucfirst($this->getAttribute('type')); + $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', $type, $this->getAttribute('name'))); + $closingParenthesis = true; + $isArray = true; + } + } + } else { + $compiler->raw($this->getAttribute('thing')->compile()); + } + + $this->compileArguments($compiler, $isArray); + + if ($closingParenthesis) { + $compiler->raw(')'); + } + } + + protected function compileArguments(Compiler $compiler, $isArray = false) + { + $compiler->raw($isArray ? '[' : '('); + + $first = true; + + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + $compiler->raw('$this->env'); + $first = false; + } + + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->raw('$context'); + $first = false; + } + + if ($this->hasAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->string($argument); + $first = false; + } + } + + if ($this->hasNode('node')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($this->getNode('node')); + $first = false; + } + + if ($this->hasNode('arguments')) { + $callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null; + + $arguments = $this->getArguments($callable, $this->getNode('arguments')); + + foreach ($arguments as $node) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($node); + $first = false; + } + } + + $compiler->raw($isArray ? ']' : ')'); + } + + protected function getArguments($callable, $arguments) + { + $callType = $this->getAttribute('type'); + $callName = $this->getAttribute('name'); + + $parameters = []; + $named = false; + foreach ($arguments as $name => $node) { + if (!\is_int($name)) { + $named = true; + $name = $this->normalizeName($name); + } elseif ($named) { + throw new SyntaxError(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + + $parameters[$name] = $node; + } + + $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic'); + if (!$named && !$isVariadic) { + return $parameters; + } + + if (!$callable) { + if ($named) { + $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); + } else { + $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); + } + + throw new \LogicException($message); + } + + $callableParameters = $this->getCallableParameters($callable, $isVariadic); + $arguments = []; + $names = []; + $missingArguments = []; + $optionalArguments = []; + $pos = 0; + foreach ($callableParameters as $callableParameter) { + $names[] = $name = $this->normalizeName($callableParameter->name); + + if (\array_key_exists($name, $parameters)) { + if (\array_key_exists($pos, $parameters)) { + throw new SyntaxError(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + + if (\count($missingArguments)) { + throw new SyntaxError(sprintf( + 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', + $name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) + ), $this->getTemplateLine(), $this->getSourceContext()); + } + + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$name]; + unset($parameters[$name]); + $optionalArguments = []; + } elseif (\array_key_exists($pos, $parameters)) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$pos]; + unset($parameters[$pos]); + $optionalArguments = []; + ++$pos; + } elseif ($callableParameter->isDefaultValueAvailable()) { + $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1); + } elseif ($callableParameter->isOptional()) { + if (empty($parameters)) { + break; + } else { + $missingArguments[] = $name; + } + } else { + throw new SyntaxError(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + } + + if ($isVariadic) { + $arbitraryArguments = new ArrayExpression([], -1); + foreach ($parameters as $key => $value) { + if (\is_int($key)) { + $arbitraryArguments->addElement($value); + } else { + $arbitraryArguments->addElement($value, new ConstantExpression($key, -1)); + } + unset($parameters[$key]); + } + + if ($arbitraryArguments->count()) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $arbitraryArguments; + } + } + + if (!empty($parameters)) { + $unknownParameter = null; + foreach ($parameters as $parameter) { + if ($parameter instanceof Node) { + $unknownParameter = $parameter; + break; + } + } + + throw new SyntaxError( + sprintf( + 'Unknown argument%s "%s" for %s "%s(%s)".', + \count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names) + ), + $unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(), + $unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext() + ); + } + + return $arguments; + } + + protected function normalizeName($name) + { + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); + } + + private function getCallableParameters($callable, $isVariadic) + { + list($r) = $this->reflectCallable($callable); + if (null === $r) { + return []; + } + + $parameters = $r->getParameters(); + if ($this->hasNode('node')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + array_shift($parameters); + } + if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + array_shift($parameters); + } + } + if ($isVariadic) { + $argument = end($parameters); + if ($argument && $argument->isArray() && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { + array_pop($parameters); + } else { + $callableName = $r->name; + if ($r instanceof \ReflectionMethod) { + $callableName = $r->getDeclaringClass()->name.'::'.$callableName; + } + + throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name'))); + } + } + + return $parameters; + } + + private function reflectCallable($callable) + { + if (null !== $this->reflector) { + return $this->reflector; + } + + if (\is_array($callable)) { + if (!method_exists($callable[0], $callable[1])) { + // __call() + return [null, []]; + } + $r = new \ReflectionMethod($callable[0], $callable[1]); + } elseif (\is_object($callable) && !$callable instanceof \Closure) { + $r = new \ReflectionObject($callable); + $r = $r->getMethod('__invoke'); + $callable = [$callable, '__invoke']; + } elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) { + $class = substr($callable, 0, $pos); + $method = substr($callable, $pos + 2); + if (!method_exists($class, $method)) { + // __staticCall() + return [null, []]; + } + $r = new \ReflectionMethod($callable); + $callable = [$class, $method]; + } else { + $r = new \ReflectionFunction($callable); + } + + return $this->reflector = [$r, $callable]; + } +} + +class_alias('Twig\Node\Expression\CallExpression', 'Twig_Node_Expression_Call'); diff --git a/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php b/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php new file mode 100644 index 0000000..b611218 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php @@ -0,0 +1,38 @@ + $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ? (') + ->subcompile($this->getNode('expr2')) + ->raw(') : (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } +} + +class_alias('Twig\Node\Expression\ConditionalExpression', 'Twig_Node_Expression_Conditional'); diff --git a/vendor/twig/twig/src/Node/Expression/ConstantExpression.php b/vendor/twig/twig/src/Node/Expression/ConstantExpression.php new file mode 100644 index 0000000..fd58264 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ConstantExpression.php @@ -0,0 +1,30 @@ + $value], $lineno); + } + + public function compile(Compiler $compiler) + { + $compiler->repr($this->getAttribute('value')); + } +} + +class_alias('Twig\Node\Expression\ConstantExpression', 'Twig_Node_Expression_Constant'); diff --git a/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php b/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php new file mode 100644 index 0000000..7c5e2d2 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php @@ -0,0 +1,54 @@ + + */ +class DefaultFilter extends FilterExpression +{ + public function __construct(\Twig_NodeInterface $node, ConstantExpression $filterName, \Twig_NodeInterface $arguments, $lineno, $tag = null) + { + $default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine()); + + if ('default' === $filterName->getAttribute('value') && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) { + $test = new DefinedTest(clone $node, 'defined', new Node(), $node->getTemplateLine()); + $false = \count($arguments) ? $arguments->getNode(0) : new ConstantExpression('', $node->getTemplateLine()); + + $node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine()); + } else { + $node = $default; + } + + parent::__construct($node, $filterName, $arguments, $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler->subcompile($this->getNode('node')); + } +} + +class_alias('Twig\Node\Expression\Filter\DefaultFilter', 'Twig_Node_Expression_Filter_Default'); diff --git a/vendor/twig/twig/src/Node/Expression/FilterExpression.php b/vendor/twig/twig/src/Node/Expression/FilterExpression.php new file mode 100644 index 0000000..6131c2f --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/FilterExpression.php @@ -0,0 +1,47 @@ + $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $name = $this->getNode('filter')->getAttribute('value'); + $filter = $compiler->getEnvironment()->getFilter($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'filter'); + $this->setAttribute('thing', $filter); + $this->setAttribute('needs_environment', $filter->needsEnvironment()); + $this->setAttribute('needs_context', $filter->needsContext()); + $this->setAttribute('arguments', $filter->getArguments()); + if ($filter instanceof \Twig_FilterCallableInterface || $filter instanceof TwigFilter) { + $this->setAttribute('callable', $filter->getCallable()); + } + if ($filter instanceof TwigFilter) { + $this->setAttribute('is_variadic', $filter->isVariadic()); + } + + $this->compileCallable($compiler); + } +} + +class_alias('Twig\Node\Expression\FilterExpression', 'Twig_Node_Expression_Filter'); diff --git a/vendor/twig/twig/src/Node/Expression/FunctionExpression.php b/vendor/twig/twig/src/Node/Expression/FunctionExpression.php new file mode 100644 index 0000000..cf2c72b --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/FunctionExpression.php @@ -0,0 +1,51 @@ + $arguments], ['name' => $name, 'is_defined_test' => false], $lineno); + } + + public function compile(Compiler $compiler) + { + $name = $this->getAttribute('name'); + $function = $compiler->getEnvironment()->getFunction($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'function'); + $this->setAttribute('thing', $function); + $this->setAttribute('needs_environment', $function->needsEnvironment()); + $this->setAttribute('needs_context', $function->needsContext()); + $this->setAttribute('arguments', $function->getArguments()); + if ($function instanceof \Twig_FunctionCallableInterface || $function instanceof TwigFunction) { + $callable = $function->getCallable(); + if ('constant' === $name && $this->getAttribute('is_defined_test')) { + $callable = 'twig_constant_is_defined'; + } + + $this->setAttribute('callable', $callable); + } + if ($function instanceof TwigFunction) { + $this->setAttribute('is_variadic', $function->isVariadic()); + } + + $this->compileCallable($compiler); + } +} + +class_alias('Twig\Node\Expression\FunctionExpression', 'Twig_Node_Expression_Function'); diff --git a/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php b/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php new file mode 100644 index 0000000..b790bf7 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php @@ -0,0 +1,80 @@ + $node, 'attribute' => $attribute]; + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false], $lineno); + } + + public function compile(Compiler $compiler) + { + if ($this->getAttribute('disable_c_ext')) { + @trigger_error(sprintf('Using the "disable_c_ext" attribute on %s is deprecated since version 1.30 and will be removed in 2.0.', __CLASS__), E_USER_DEPRECATED); + } + + if (\function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) { + $compiler->raw('twig_template_get_attributes($this, '); + } else { + $compiler->raw('$this->getAttribute('); + } + + if ($this->getAttribute('ignore_strict_check')) { + $this->getNode('node')->setAttribute('ignore_strict_check', true); + } + + $compiler->subcompile($this->getNode('node')); + + $compiler->raw(', ')->subcompile($this->getNode('attribute')); + + // only generate optional arguments when needed (to make generated code more readable) + $needFourth = $this->getAttribute('ignore_strict_check'); + $needThird = $needFourth || $this->getAttribute('is_defined_test'); + $needSecond = $needThird || Template::ANY_CALL !== $this->getAttribute('type'); + $needFirst = $needSecond || $this->hasNode('arguments'); + + if ($needFirst) { + if ($this->hasNode('arguments')) { + $compiler->raw(', ')->subcompile($this->getNode('arguments')); + } else { + $compiler->raw(', []'); + } + } + + if ($needSecond) { + $compiler->raw(', ')->repr($this->getAttribute('type')); + } + + if ($needThird) { + $compiler->raw(', ')->repr($this->getAttribute('is_defined_test')); + } + + if ($needFourth) { + $compiler->raw(', ')->repr($this->getAttribute('ignore_strict_check')); + } + + $compiler->raw(')'); + } +} + +class_alias('Twig\Node\Expression\GetAttrExpression', 'Twig_Node_Expression_GetAttr'); diff --git a/vendor/twig/twig/src/Node/Expression/InlinePrint.php b/vendor/twig/twig/src/Node/Expression/InlinePrint.php new file mode 100644 index 0000000..469e736 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/InlinePrint.php @@ -0,0 +1,35 @@ + $node], [], $lineno); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('print (') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php b/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php new file mode 100644 index 0000000..f631124 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php @@ -0,0 +1,48 @@ + $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false], $lineno); + + if ($node instanceof NameExpression) { + $node->setAttribute('always_defined', true); + } + } + + public function compile(Compiler $compiler) + { + $compiler + ->subcompile($this->getNode('node')) + ->raw('->') + ->raw($this->getAttribute('method')) + ->raw('(') + ; + $first = true; + foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler->subcompile($pair['value']); + } + $compiler->raw(')'); + } +} + +class_alias('Twig\Node\Expression\MethodCallExpression', 'Twig_Node_Expression_MethodCall'); diff --git a/vendor/twig/twig/src/Node/Expression/NameExpression.php b/vendor/twig/twig/src/Node/Expression/NameExpression.php new file mode 100644 index 0000000..516ba28 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/NameExpression.php @@ -0,0 +1,112 @@ + '$this', + '_context' => '$context', + '_charset' => '$this->env->getCharset()', + ]; + + public function __construct($name, $lineno) + { + parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); + } + + public function compile(Compiler $compiler) + { + $name = $this->getAttribute('name'); + + $compiler->addDebugInfo($this); + + if ($this->getAttribute('is_defined_test')) { + if ($this->isSpecial()) { + $compiler->repr(true); + } else { + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) || array_key_exists(') + ->string($name) + ->raw(', $context))'); + } + } elseif ($this->isSpecial()) { + $compiler->raw($this->specialVars[$name]); + } elseif ($this->getAttribute('always_defined')) { + $compiler + ->raw('$context[') + ->string($name) + ->raw(']') + ; + } else { + if (\PHP_VERSION_ID >= 70000) { + // use PHP 7 null coalescing operator + $compiler + ->raw('($context[') + ->string($name) + ->raw('] ?? ') + ; + + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { + $compiler->raw('null)'); + } else { + $compiler->raw('$this->getContext($context, ')->string($name)->raw('))'); + } + } elseif (\PHP_VERSION_ID >= 50400) { + // PHP 5.4 ternary operator performance was optimized + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) ? $context[') + ->string($name) + ->raw('] : ') + ; + + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { + $compiler->raw('null)'); + } else { + $compiler->raw('$this->getContext($context, ')->string($name)->raw('))'); + } + } else { + $compiler + ->raw('$this->getContext($context, ') + ->string($name) + ; + + if ($this->getAttribute('ignore_strict_check')) { + $compiler->raw(', true'); + } + + $compiler + ->raw(')') + ; + } + } + } + + public function isSpecial() + { + return isset($this->specialVars[$this->getAttribute('name')]); + } + + public function isSimple() + { + return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); + } +} + +class_alias('Twig\Node\Expression\NameExpression', 'Twig_Node_Expression_Name'); diff --git a/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php b/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php new file mode 100644 index 0000000..49326d4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php @@ -0,0 +1,58 @@ +getTemplateLine()), + new NotUnary(new NullTest($left, 'null', new Node(), $left->getTemplateLine()), $left->getTemplateLine()), + $left->getTemplateLine() + ); + + parent::__construct($test, $left, $right, $lineno); + } + + public function compile(Compiler $compiler) + { + /* + * This optimizes only one case. PHP 7 also supports more complex expressions + * that can return null. So, for instance, if log is defined, log("foo") ?? "..." works, + * but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced + * cases might be implemented as an optimizer node visitor, but has not been done + * as benefits are probably not worth the added complexity. + */ + if (\PHP_VERSION_ID >= 70000 && $this->getNode('expr2') instanceof NameExpression) { + $this->getNode('expr2')->setAttribute('always_defined', true); + $compiler + ->raw('((') + ->subcompile($this->getNode('expr2')) + ->raw(') ?? (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } else { + parent::compile($compiler); + } + } +} + +class_alias('Twig\Node\Expression\NullCoalesceExpression', 'Twig_Node_Expression_NullCoalesce'); diff --git a/vendor/twig/twig/src/Node/Expression/ParentExpression.php b/vendor/twig/twig/src/Node/Expression/ParentExpression.php new file mode 100644 index 0000000..1247283 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ParentExpression.php @@ -0,0 +1,48 @@ + + */ +class ParentExpression extends AbstractExpression +{ + public function __construct($name, $lineno, $tag = null) + { + parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + if ($this->getAttribute('output')) { + $compiler + ->addDebugInfo($this) + ->write('$this->displayParentBlock(') + ->string($this->getAttribute('name')) + ->raw(", \$context, \$blocks);\n") + ; + } else { + $compiler + ->raw('$this->renderParentBlock(') + ->string($this->getAttribute('name')) + ->raw(', $context, $blocks)') + ; + } + } +} + +class_alias('Twig\Node\Expression\ParentExpression', 'Twig_Node_Expression_Parent'); diff --git a/vendor/twig/twig/src/Node/Expression/TempNameExpression.php b/vendor/twig/twig/src/Node/Expression/TempNameExpression.php new file mode 100644 index 0000000..ce0a158 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/TempNameExpression.php @@ -0,0 +1,33 @@ + $name], $lineno); + } + + public function compile(Compiler $compiler) + { + $compiler + ->raw('$_') + ->raw($this->getAttribute('name')) + ->raw('_') + ; + } +} + +class_alias('Twig\Node\Expression\TempNameExpression', 'Twig_Node_Expression_TempName'); diff --git a/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php b/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php new file mode 100644 index 0000000..78353a8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php @@ -0,0 +1,51 @@ + + */ +class ConstantTest extends TestExpression +{ + public function compile(Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === constant(') + ; + + if ($this->getNode('arguments')->hasNode(1)) { + $compiler + ->raw('get_class(') + ->subcompile($this->getNode('arguments')->getNode(1)) + ->raw(')."::".') + ; + } + + $compiler + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw('))') + ; + } +} + +class_alias('Twig\Node\Expression\Test\ConstantTest', 'Twig_Node_Expression_Test_Constant'); diff --git a/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php b/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php new file mode 100644 index 0000000..2222e11 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php @@ -0,0 +1,71 @@ + + */ +class DefinedTest extends TestExpression +{ + public function __construct(\Twig_NodeInterface $node, $name, \Twig_NodeInterface $arguments = null, $lineno) + { + if ($node instanceof NameExpression) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof GetAttrExpression) { + $node->setAttribute('is_defined_test', true); + $this->changeIgnoreStrictCheck($node); + } elseif ($node instanceof BlockReferenceExpression) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { + $node = new ConstantExpression(true, $node->getTemplateLine()); + } else { + throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); + } + + parent::__construct($node, $name, $arguments, $lineno); + } + + protected function changeIgnoreStrictCheck(GetAttrExpression $node) + { + $node->setAttribute('ignore_strict_check', true); + + if ($node->getNode('node') instanceof GetAttrExpression) { + $this->changeIgnoreStrictCheck($node->getNode('node')); + } + } + + public function compile(Compiler $compiler) + { + $compiler->subcompile($this->getNode('node')); + } +} + +class_alias('Twig\Node\Expression\Test\DefinedTest', 'Twig_Node_Expression_Test_Defined'); diff --git a/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php b/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php new file mode 100644 index 0000000..05c8ad8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php @@ -0,0 +1,38 @@ + + */ +class DivisiblebyTest extends TestExpression +{ + public function compile(Compiler $compiler) + { + $compiler + ->raw('(0 == ') + ->subcompile($this->getNode('node')) + ->raw(' % ') + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw(')') + ; + } +} + +class_alias('Twig\Node\Expression\Test\DivisiblebyTest', 'Twig_Node_Expression_Test_Divisibleby'); diff --git a/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php b/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php new file mode 100644 index 0000000..3b955d2 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php @@ -0,0 +1,37 @@ + + */ +class EvenTest extends TestExpression +{ + public function compile(Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 == 0') + ->raw(')') + ; + } +} + +class_alias('Twig\Node\Expression\Test\EvenTest', 'Twig_Node_Expression_Test_Even'); diff --git a/vendor/twig/twig/src/Node/Expression/Test/NullTest.php b/vendor/twig/twig/src/Node/Expression/Test/NullTest.php new file mode 100644 index 0000000..24d3997 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/NullTest.php @@ -0,0 +1,36 @@ + + */ +class NullTest extends TestExpression +{ + public function compile(Compiler $compiler) + { + $compiler + ->raw('(null === ') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} + +class_alias('Twig\Node\Expression\Test\NullTest', 'Twig_Node_Expression_Test_Null'); diff --git a/vendor/twig/twig/src/Node/Expression/Test/OddTest.php b/vendor/twig/twig/src/Node/Expression/Test/OddTest.php new file mode 100644 index 0000000..2dc693a --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/OddTest.php @@ -0,0 +1,37 @@ + + */ +class OddTest extends TestExpression +{ + public function compile(Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 == 1') + ->raw(')') + ; + } +} + +class_alias('Twig\Node\Expression\Test\OddTest', 'Twig_Node_Expression_Test_Odd'); diff --git a/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php b/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php new file mode 100644 index 0000000..75f2b82 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php @@ -0,0 +1,36 @@ + + */ +class SameasTest extends TestExpression +{ + public function compile(Compiler $compiler) + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === ') + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw(')') + ; + } +} + +class_alias('Twig\Node\Expression\Test\SameasTest', 'Twig_Node_Expression_Test_Sameas'); diff --git a/vendor/twig/twig/src/Node/Expression/TestExpression.php b/vendor/twig/twig/src/Node/Expression/TestExpression.php new file mode 100644 index 0000000..8fc31d3 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/TestExpression.php @@ -0,0 +1,51 @@ + $node]; + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, ['name' => $name], $lineno); + } + + public function compile(Compiler $compiler) + { + $name = $this->getAttribute('name'); + $test = $compiler->getEnvironment()->getTest($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'test'); + $this->setAttribute('thing', $test); + if ($test instanceof TwigTest) { + $this->setAttribute('arguments', $test->getArguments()); + } + if ($test instanceof \Twig_TestCallableInterface || $test instanceof TwigTest) { + $this->setAttribute('callable', $test->getCallable()); + } + if ($test instanceof TwigTest) { + $this->setAttribute('is_variadic', $test->isVariadic()); + } + + $this->compileCallable($compiler); + } +} + +class_alias('Twig\Node\Expression\TestExpression', 'Twig_Node_Expression_Test'); diff --git a/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php new file mode 100644 index 0000000..415c3d4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php @@ -0,0 +1,35 @@ + $node], [], $lineno); + } + + public function compile(Compiler $compiler) + { + $compiler->raw(' '); + $this->operator($compiler); + $compiler->subcompile($this->getNode('node')); + } + + abstract public function operator(Compiler $compiler); +} + +class_alias('Twig\Node\Expression\Unary\AbstractUnary', 'Twig_Node_Expression_Unary'); diff --git a/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php new file mode 100644 index 0000000..dfb6f54 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php @@ -0,0 +1,25 @@ +raw('-'); + } +} + +class_alias('Twig\Node\Expression\Unary\NegUnary', 'Twig_Node_Expression_Unary_Neg'); diff --git a/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php new file mode 100644 index 0000000..7bdde96 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php @@ -0,0 +1,25 @@ +raw('!'); + } +} + +class_alias('Twig\Node\Expression\Unary\NotUnary', 'Twig_Node_Expression_Unary_Not'); diff --git a/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php new file mode 100644 index 0000000..52d5d0c --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php @@ -0,0 +1,25 @@ +raw('+'); + } +} + +class_alias('Twig\Node\Expression\Unary\PosUnary', 'Twig_Node_Expression_Unary_Pos'); diff --git a/vendor/twig/twig/src/Node/FlushNode.php b/vendor/twig/twig/src/Node/FlushNode.php new file mode 100644 index 0000000..6cbc489 --- /dev/null +++ b/vendor/twig/twig/src/Node/FlushNode.php @@ -0,0 +1,37 @@ + + */ +class FlushNode extends Node +{ + public function __construct($lineno, $tag) + { + parent::__construct([], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("flush();\n") + ; + } +} + +class_alias('Twig\Node\FlushNode', 'Twig_Node_Flush'); diff --git a/vendor/twig/twig/src/Node/ForLoopNode.php b/vendor/twig/twig/src/Node/ForLoopNode.php new file mode 100644 index 0000000..3902093 --- /dev/null +++ b/vendor/twig/twig/src/Node/ForLoopNode.php @@ -0,0 +1,56 @@ + + */ +class ForLoopNode extends Node +{ + public function __construct($lineno, $tag = null) + { + parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + if ($this->getAttribute('else')) { + $compiler->write("\$context['_iterated'] = true;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("++\$context['loop']['index0'];\n") + ->write("++\$context['loop']['index'];\n") + ->write("\$context['loop']['first'] = false;\n") + ; + + if (!$this->getAttribute('ifexpr')) { + $compiler + ->write("if (isset(\$context['loop']['length'])) {\n") + ->indent() + ->write("--\$context['loop']['revindex0'];\n") + ->write("--\$context['loop']['revindex'];\n") + ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") + ->outdent() + ->write("}\n") + ; + } + } + } +} + +class_alias('Twig\Node\ForLoopNode', 'Twig_Node_ForLoop'); diff --git a/vendor/twig/twig/src/Node/ForNode.php b/vendor/twig/twig/src/Node/ForNode.php new file mode 100644 index 0000000..49409a3 --- /dev/null +++ b/vendor/twig/twig/src/Node/ForNode.php @@ -0,0 +1,119 @@ + + */ +class ForNode extends Node +{ + protected $loop; + + public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, AbstractExpression $ifexpr = null, \Twig_NodeInterface $body, \Twig_NodeInterface $else = null, $lineno, $tag = null) + { + $body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]); + + if (null !== $ifexpr) { + $body = new IfNode(new Node([$ifexpr, $body]), null, $lineno, $tag); + } + + $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body]; + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, ['with_loop' => true, 'ifexpr' => null !== $ifexpr], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("\$context['_parent'] = \$context;\n") + ->write("\$context['_seq'] = twig_ensure_traversable(") + ->subcompile($this->getNode('seq')) + ->raw(");\n") + ; + + if ($this->hasNode('else')) { + $compiler->write("\$context['_iterated'] = false;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("\$context['loop'] = [\n") + ->write(" 'parent' => \$context['_parent'],\n") + ->write(" 'index0' => 0,\n") + ->write(" 'index' => 1,\n") + ->write(" 'first' => true,\n") + ->write("];\n") + ; + + if (!$this->getAttribute('ifexpr')) { + $compiler + ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof \Countable)) {\n") + ->indent() + ->write("\$length = count(\$context['_seq']);\n") + ->write("\$context['loop']['revindex0'] = \$length - 1;\n") + ->write("\$context['loop']['revindex'] = \$length;\n") + ->write("\$context['loop']['length'] = \$length;\n") + ->write("\$context['loop']['last'] = 1 === \$length;\n") + ->outdent() + ->write("}\n") + ; + } + } + + $this->loop->setAttribute('else', $this->hasNode('else')); + $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop')); + $this->loop->setAttribute('ifexpr', $this->getAttribute('ifexpr')); + + $compiler + ->write("foreach (\$context['_seq'] as ") + ->subcompile($this->getNode('key_target')) + ->raw(' => ') + ->subcompile($this->getNode('value_target')) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n") + ; + + if ($this->hasNode('else')) { + $compiler + ->write("if (!\$context['_iterated']) {\n") + ->indent() + ->subcompile($this->getNode('else')) + ->outdent() + ->write("}\n") + ; + } + + $compiler->write("\$_parent = \$context['_parent'];\n"); + + // remove some "private" loop variables (needed for nested loops) + $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n"); + + // keep the values set in the inner context for variables defined in the outer context + $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); + } +} + +class_alias('Twig\Node\ForNode', 'Twig_Node_For'); diff --git a/vendor/twig/twig/src/Node/IfNode.php b/vendor/twig/twig/src/Node/IfNode.php new file mode 100644 index 0000000..4836d91 --- /dev/null +++ b/vendor/twig/twig/src/Node/IfNode.php @@ -0,0 +1,72 @@ + + */ +class IfNode extends Node +{ + public function __construct(\Twig_NodeInterface $tests, \Twig_NodeInterface $else = null, $lineno, $tag = null) + { + $nodes = ['tests' => $tests]; + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + for ($i = 0, $count = \count($this->getNode('tests')); $i < $count; $i += 2) { + if ($i > 0) { + $compiler + ->outdent() + ->write('} elseif (') + ; + } else { + $compiler + ->write('if (') + ; + } + + $compiler + ->subcompile($this->getNode('tests')->getNode($i)) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('tests')->getNode($i + 1)) + ; + } + + if ($this->hasNode('else')) { + $compiler + ->outdent() + ->write("} else {\n") + ->indent() + ->subcompile($this->getNode('else')) + ; + } + + $compiler + ->outdent() + ->write("}\n"); + } +} + +class_alias('Twig\Node\IfNode', 'Twig_Node_If'); diff --git a/vendor/twig/twig/src/Node/ImportNode.php b/vendor/twig/twig/src/Node/ImportNode.php new file mode 100644 index 0000000..309bd1a --- /dev/null +++ b/vendor/twig/twig/src/Node/ImportNode.php @@ -0,0 +1,57 @@ + + */ +class ImportNode extends Node +{ + public function __construct(AbstractExpression $expr, AbstractExpression $var, $lineno, $tag = null) + { + parent::__construct(['expr' => $expr, 'var' => $var], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('var')) + ->raw(' = ') + ; + + if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) { + $compiler->raw('$this'); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + $compiler->raw(";\n"); + } +} + +class_alias('Twig\Node\ImportNode', 'Twig_Node_Import'); diff --git a/vendor/twig/twig/src/Node/IncludeNode.php b/vendor/twig/twig/src/Node/IncludeNode.php new file mode 100644 index 0000000..544db81 --- /dev/null +++ b/vendor/twig/twig/src/Node/IncludeNode.php @@ -0,0 +1,108 @@ + + */ +class IncludeNode extends Node implements NodeOutputInterface +{ + public function __construct(AbstractExpression $expr, AbstractExpression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null) + { + $nodes = ['expr' => $expr]; + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, ['only' => (bool) $only, 'ignore_missing' => (bool) $ignoreMissing], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->getAttribute('ignore_missing')) { + $template = $compiler->getVarName(); + + $compiler + ->write(sprintf("$%s = null;\n", $template)) + ->write("try {\n") + ->indent() + ->write(sprintf('$%s = ', $template)) + ; + + $this->addGetTemplate($compiler); + + $compiler + ->raw(";\n") + ->outdent() + ->write("} catch (LoaderError \$e) {\n") + ->indent() + ->write("// ignore missing template\n") + ->outdent() + ->write("}\n") + ->write(sprintf("if ($%s) {\n", $template)) + ->indent() + ->write(sprintf('$%s->display(', $template)) + ; + $this->addTemplateArguments($compiler); + $compiler + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } else { + $this->addGetTemplate($compiler); + $compiler->raw('->display('); + $this->addTemplateArguments($compiler); + $compiler->raw(");\n"); + } + } + + protected function addGetTemplate(Compiler $compiler) + { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + protected function addTemplateArguments(Compiler $compiler) + { + if (!$this->hasNode('variables')) { + $compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]'); + } elseif (false === $this->getAttribute('only')) { + $compiler + ->raw('twig_array_merge($context, ') + ->subcompile($this->getNode('variables')) + ->raw(')') + ; + } else { + $compiler->raw('twig_to_array('); + $compiler->subcompile($this->getNode('variables')); + $compiler->raw(')'); + } + } +} + +class_alias('Twig\Node\IncludeNode', 'Twig_Node_Include'); diff --git a/vendor/twig/twig/src/Node/MacroNode.php b/vendor/twig/twig/src/Node/MacroNode.php new file mode 100644 index 0000000..8e3cc76 --- /dev/null +++ b/vendor/twig/twig/src/Node/MacroNode.php @@ -0,0 +1,130 @@ + + */ +class MacroNode extends Node +{ + const VARARGS_NAME = 'varargs'; + + public function __construct($name, \Twig_NodeInterface $body, \Twig_NodeInterface $arguments, $lineno, $tag = null) + { + foreach ($arguments as $argumentName => $argument) { + if (self::VARARGS_NAME === $argumentName) { + throw new SyntaxError(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext()); + } + } + + parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write(sprintf('public function get%s(', $this->getAttribute('name'))) + ; + + $count = \count($this->getNode('arguments')); + $pos = 0; + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->raw('$__'.$name.'__ = ') + ->subcompile($default) + ; + + if (++$pos < $count) { + $compiler->raw(', '); + } + } + + if (\PHP_VERSION_ID >= 50600) { + if ($count) { + $compiler->raw(', '); + } + + $compiler->raw('...$__varargs__'); + } + + $compiler + ->raw(")\n") + ->write("{\n") + ->indent() + ; + + $compiler + ->write("\$context = \$this->env->mergeGlobals([\n") + ->indent() + ; + + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->write('') + ->string($name) + ->raw(' => $__'.$name.'__') + ->raw(",\n") + ; + } + + $compiler + ->write('') + ->string(self::VARARGS_NAME) + ->raw(' => ') + ; + + if (\PHP_VERSION_ID >= 50600) { + $compiler->raw("\$__varargs__,\n"); + } else { + $compiler + ->raw('func_num_args() > ') + ->repr($count) + ->raw(' ? array_slice(func_get_args(), ') + ->repr($count) + ->raw(") : [],\n") + ; + } + + $compiler + ->outdent() + ->write("]);\n\n") + ->write("\$blocks = [];\n\n") + ->write("ob_start();\n") + ->write("try {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("} catch (\Exception \$e) {\n") + ->indent() + ->write("ob_end_clean();\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("} catch (\Throwable \$e) {\n") + ->indent() + ->write("ob_end_clean();\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n\n") + ->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n") + ->outdent() + ->write("}\n\n") + ; + } +} + +class_alias('Twig\Node\MacroNode', 'Twig_Node_Macro'); diff --git a/vendor/twig/twig/src/Node/ModuleNode.php b/vendor/twig/twig/src/Node/ModuleNode.php new file mode 100644 index 0000000..aab2aa3 --- /dev/null +++ b/vendor/twig/twig/src/Node/ModuleNode.php @@ -0,0 +1,492 @@ + + */ +class ModuleNode extends Node +{ + public function __construct(\Twig_NodeInterface $body, AbstractExpression $parent = null, \Twig_NodeInterface $blocks, \Twig_NodeInterface $macros, \Twig_NodeInterface $traits, $embeddedTemplates, $name, $source = '') + { + if (!$name instanceof Source) { + @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); + $source = new Source($source, $name); + } else { + $source = $name; + } + + $nodes = [ + 'body' => $body, + 'blocks' => $blocks, + 'macros' => $macros, + 'traits' => $traits, + 'display_start' => new Node(), + 'display_end' => new Node(), + 'constructor_start' => new Node(), + 'constructor_end' => new Node(), + 'class_end' => new Node(), + ]; + if (null !== $parent) { + $nodes['parent'] = $parent; + } + + // embedded templates are set as attributes so that they are only visited once by the visitors + parent::__construct($nodes, [ + // source to be remove in 2.0 + 'source' => $source->getCode(), + // filename to be remove in 2.0 (use getTemplateName() instead) + 'filename' => $source->getName(), + 'index' => null, + 'embedded_templates' => $embeddedTemplates, + ], 1); + + // populate the template name of all node children + $this->setTemplateName($source->getName()); + $this->setSourceContext($source); + } + + public function setIndex($index) + { + $this->setAttribute('index', $index); + } + + public function compile(Compiler $compiler) + { + $this->compileTemplate($compiler); + + foreach ($this->getAttribute('embedded_templates') as $template) { + $compiler->subcompile($template); + } + } + + protected function compileTemplate(Compiler $compiler) + { + if (!$this->getAttribute('index')) { + $compiler->write('compileClassHeader($compiler); + + if ( + \count($this->getNode('blocks')) + || \count($this->getNode('traits')) + || !$this->hasNode('parent') + || $this->getNode('parent') instanceof ConstantExpression + || \count($this->getNode('constructor_start')) + || \count($this->getNode('constructor_end')) + ) { + $this->compileConstructor($compiler); + } + + $this->compileGetParent($compiler); + + $this->compileDisplay($compiler); + + $compiler->subcompile($this->getNode('blocks')); + + $this->compileMacros($compiler); + + $this->compileGetTemplateName($compiler); + + $this->compileIsTraitable($compiler); + + $this->compileDebugInfo($compiler); + + $this->compileGetSource($compiler); + + $this->compileGetSourceContext($compiler); + + $this->compileClassFooter($compiler); + } + + protected function compileGetParent(Compiler $compiler) + { + if (!$this->hasNode('parent')) { + return; + } + $parent = $this->getNode('parent'); + + $compiler + ->write("protected function doGetParent(array \$context)\n", "{\n") + ->indent() + ->addDebugInfo($parent) + ->write('return ') + ; + + if ($parent instanceof ConstantExpression) { + $compiler->subcompile($parent); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->getSourceContext()->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(')') + ; + } + + $compiler + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassHeader(Compiler $compiler) + { + $compiler + ->write("\n\n") + ; + if (!$this->getAttribute('index')) { + $compiler + ->write("use Twig\Environment;\n") + ->write("use Twig\Error\LoaderError;\n") + ->write("use Twig\Error\RuntimeError;\n") + ->write("use Twig\Markup;\n") + ->write("use Twig\Sandbox\SecurityError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n") + ->write("use Twig\Source;\n") + ->write("use Twig\Template;\n\n") + ; + } + $compiler + // if the template name contains */, add a blank to avoid a PHP parse error + ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n") + ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index'))) + ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass())) + ->write("{\n") + ->indent() + ; + } + + protected function compileConstructor(Compiler $compiler) + { + $compiler + ->write("public function __construct(Environment \$env)\n", "{\n") + ->indent() + ->subcompile($this->getNode('constructor_start')) + ->write("parent::__construct(\$env);\n\n") + ; + + // parent + if (!$this->hasNode('parent')) { + $compiler->write("\$this->parent = false;\n\n"); + } + + $countTraits = \count($this->getNode('traits')); + if ($countTraits) { + // traits + foreach ($this->getNode('traits') as $i => $trait) { + $this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i)); + + $node = $trait->getNode('template'); + $compiler + ->addDebugInfo($node) + ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) + ->indent() + ->write("throw new RuntimeError('Template \"'.") + ->subcompile($trait->getNode('template')) + ->raw(".'\" cannot be used as a trait.', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->getSourceContext());\n") + ->outdent() + ->write("}\n") + ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) + ; + + foreach ($trait->getNode('targets') as $key => $value) { + $compiler + ->write(sprintf('if (!isset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("])) {\n") + ->indent() + ->write("throw new RuntimeError(sprintf('Block ") + ->string($key) + ->raw(' is not defined in trait ') + ->subcompile($trait->getNode('template')) + ->raw(".'), ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->getSourceContext());\n") + ->outdent() + ->write("}\n\n") + + ->write(sprintf('$_trait_%s_blocks[', $i)) + ->subcompile($value) + ->raw(sprintf('] = $_trait_%s_blocks[', $i)) + ->string($key) + ->raw(sprintf(']; unset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("]);\n\n") + ; + } + } + + if ($countTraits > 1) { + $compiler + ->write("\$this->traits = array_merge(\n") + ->indent() + ; + + for ($i = 0; $i < $countTraits; ++$i) { + $compiler + ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i)) + ; + } + + $compiler + ->outdent() + ->write(");\n\n") + ; + } else { + $compiler + ->write("\$this->traits = \$_trait_0_blocks;\n\n") + ; + } + + $compiler + ->write("\$this->blocks = array_merge(\n") + ->indent() + ->write("\$this->traits,\n") + ->write("[\n") + ; + } else { + $compiler + ->write("\$this->blocks = [\n") + ; + } + + // blocks + $compiler + ->indent() + ; + + foreach ($this->getNode('blocks') as $name => $node) { + $compiler + ->write(sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name)) + ; + } + + if ($countTraits) { + $compiler + ->outdent() + ->write("]\n") + ->outdent() + ->write(");\n") + ; + } else { + $compiler + ->outdent() + ->write("];\n") + ; + } + + $compiler + ->subcompile($this->getNode('constructor_end')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDisplay(Compiler $compiler) + { + $compiler + ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n") + ->indent() + ->subcompile($this->getNode('display_start')) + ->subcompile($this->getNode('body')) + ; + + if ($this->hasNode('parent')) { + $parent = $this->getNode('parent'); + + $compiler->addDebugInfo($parent); + if ($parent instanceof ConstantExpression) { + $compiler + ->write('$this->parent = $this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->getSourceContext()->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(");\n") + ; + $compiler->write('$this->parent'); + } else { + $compiler->write('$this->getParent($context)'); + } + $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); + } + + $compiler + ->subcompile($this->getNode('display_end')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassFooter(Compiler $compiler) + { + $compiler + ->subcompile($this->getNode('class_end')) + ->outdent() + ->write("}\n") + ; + } + + protected function compileMacros(Compiler $compiler) + { + $compiler->subcompile($this->getNode('macros')); + } + + protected function compileGetTemplateName(Compiler $compiler) + { + $compiler + ->write("public function getTemplateName()\n", "{\n") + ->indent() + ->write('return ') + ->repr($this->getSourceContext()->getName()) + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileIsTraitable(Compiler $compiler) + { + // A template can be used as a trait if: + // * it has no parent + // * it has no macros + // * it has no body + // + // Put another way, a template can be used as a trait if it + // only contains blocks and use statements. + $traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros')); + if ($traitable) { + if ($this->getNode('body') instanceof BodyNode) { + $nodes = $this->getNode('body')->getNode(0); + } else { + $nodes = $this->getNode('body'); + } + + if (!\count($nodes)) { + $nodes = new Node([$nodes]); + } + + foreach ($nodes as $node) { + if (!\count($node)) { + continue; + } + + if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { + continue; + } + + if ($node instanceof BlockReferenceNode) { + continue; + } + + $traitable = false; + break; + } + } + + if ($traitable) { + return; + } + + $compiler + ->write("public function isTraitable()\n", "{\n") + ->indent() + ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDebugInfo(Compiler $compiler) + { + $compiler + ->write("public function getDebugInfo()\n", "{\n") + ->indent() + ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileGetSource(Compiler $compiler) + { + $compiler + ->write("/** @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead */\n") + ->write("public function getSource()\n", "{\n") + ->indent() + ->write("@trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED);\n\n") + ->write('return $this->getSourceContext()->getCode();') + ->raw("\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileGetSourceContext(Compiler $compiler) + { + $compiler + ->write("public function getSourceContext()\n", "{\n") + ->indent() + ->write('return new Source(') + ->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '') + ->raw(', ') + ->string($this->getSourceContext()->getName()) + ->raw(', ') + ->string($this->getSourceContext()->getPath()) + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } + + protected function compileLoadTemplate(Compiler $compiler, $node, $var) + { + if ($node instanceof ConstantExpression) { + $compiler + ->write(sprintf('%s = $this->loadTemplate(', $var)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") + ; + } else { + throw new \LogicException('Trait templates can only be constant nodes.'); + } + } +} + +class_alias('Twig\Node\ModuleNode', 'Twig_Node_Module'); diff --git a/vendor/twig/twig/src/Node/Node.php b/vendor/twig/twig/src/Node/Node.php new file mode 100644 index 0000000..5dbe1a5 --- /dev/null +++ b/vendor/twig/twig/src/Node/Node.php @@ -0,0 +1,274 @@ + + */ +class Node implements \Twig_NodeInterface +{ + protected $nodes; + protected $attributes; + protected $lineno; + protected $tag; + + private $name; + private $sourceContext; + + /** + * @param array $nodes An array of named nodes + * @param array $attributes An array of attributes (should not be nodes) + * @param int $lineno The line number + * @param string $tag The tag name associated with the Node + */ + public function __construct(array $nodes = [], array $attributes = [], $lineno = 0, $tag = null) + { + foreach ($nodes as $name => $node) { + if (!$node instanceof \Twig_NodeInterface) { + @trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', \is_object($node) ? \get_class($node) : null === $node ? 'null' : \gettype($node), $name, \get_class($this)), E_USER_DEPRECATED); + } + } + $this->nodes = $nodes; + $this->attributes = $attributes; + $this->lineno = $lineno; + $this->tag = $tag; + } + + public function __toString() + { + $attributes = []; + foreach ($this->attributes as $name => $value) { + $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + } + + $repr = [\get_class($this).'('.implode(', ', $attributes)]; + + if (\count($this->nodes)) { + foreach ($this->nodes as $name => $node) { + $len = \strlen($name) + 4; + $noderepr = []; + foreach (explode("\n", (string) $node) as $line) { + $noderepr[] = str_repeat(' ', $len).$line; + } + + $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr))); + } + + $repr[] = ')'; + } else { + $repr[0] .= ')'; + } + + return implode("\n", $repr); + } + + /** + * @deprecated since 1.16.1 (to be removed in 2.0) + */ + public function toXml($asDom = false) + { + @trigger_error(sprintf('%s is deprecated since version 1.16.1 and will be removed in 2.0.', __METHOD__), E_USER_DEPRECATED); + + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->formatOutput = true; + $dom->appendChild($xml = $dom->createElement('twig')); + + $xml->appendChild($node = $dom->createElement('node')); + $node->setAttribute('class', \get_class($this)); + + foreach ($this->attributes as $name => $value) { + $node->appendChild($attribute = $dom->createElement('attribute')); + $attribute->setAttribute('name', $name); + $attribute->appendChild($dom->createTextNode($value)); + } + + foreach ($this->nodes as $name => $n) { + if (null === $n) { + continue; + } + + $child = $n->toXml(true)->getElementsByTagName('node')->item(0); + $child = $dom->importNode($child, true); + $child->setAttribute('name', $name); + + $node->appendChild($child); + } + + return $asDom ? $dom : $dom->saveXML(); + } + + public function compile(Compiler $compiler) + { + foreach ($this->nodes as $node) { + $node->compile($compiler); + } + } + + public function getTemplateLine() + { + return $this->lineno; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getLine() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateLine() instead.', E_USER_DEPRECATED); + + return $this->lineno; + } + + public function getNodeTag() + { + return $this->tag; + } + + /** + * @return bool + */ + public function hasAttribute($name) + { + return \array_key_exists($name, $this->attributes); + } + + /** + * @return mixed + */ + public function getAttribute($name) + { + if (!\array_key_exists($name, $this->attributes)) { + throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, \get_class($this))); + } + + return $this->attributes[$name]; + } + + /** + * @param string $name + * @param mixed $value + */ + public function setAttribute($name, $value) + { + $this->attributes[$name] = $value; + } + + public function removeAttribute($name) + { + unset($this->attributes[$name]); + } + + /** + * @return bool + */ + public function hasNode($name) + { + return \array_key_exists($name, $this->nodes); + } + + /** + * @return Node + */ + public function getNode($name) + { + if (!\array_key_exists($name, $this->nodes)) { + throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, \get_class($this))); + } + + return $this->nodes[$name]; + } + + public function setNode($name, $node = null) + { + if (!$node instanceof \Twig_NodeInterface) { + @trigger_error(sprintf('Using "%s" for the value of node "%s" of "%s" is deprecated since version 1.25 and will be removed in 2.0.', \is_object($node) ? \get_class($node) : null === $node ? 'null' : \gettype($node), $name, \get_class($this)), E_USER_DEPRECATED); + } + + $this->nodes[$name] = $node; + } + + public function removeNode($name) + { + unset($this->nodes[$name]); + } + + public function count() + { + return \count($this->nodes); + } + + public function getIterator() + { + return new \ArrayIterator($this->nodes); + } + + public function setTemplateName($name) + { + $this->name = $name; + foreach ($this->nodes as $node) { + if (null !== $node) { + $node->setTemplateName($name); + } + } + } + + public function getTemplateName() + { + return $this->name; + } + + public function setSourceContext(Source $source) + { + $this->sourceContext = $source; + foreach ($this->nodes as $node) { + if ($node instanceof Node) { + $node->setSourceContext($source); + } + } + } + + public function getSourceContext() + { + return $this->sourceContext; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function setFilename($name) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use setTemplateName() instead.', E_USER_DEPRECATED); + + $this->setTemplateName($name); + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getFilename() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getTemplateName() instead.', E_USER_DEPRECATED); + + return $this->name; + } +} + +class_alias('Twig\Node\Node', 'Twig_Node'); + +// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. +class_exists('Twig\Compiler'); diff --git a/vendor/twig/twig/src/Node/NodeCaptureInterface.php b/vendor/twig/twig/src/Node/NodeCaptureInterface.php new file mode 100644 index 0000000..474003f --- /dev/null +++ b/vendor/twig/twig/src/Node/NodeCaptureInterface.php @@ -0,0 +1,23 @@ + + */ +interface NodeCaptureInterface +{ +} + +class_alias('Twig\Node\NodeCaptureInterface', 'Twig_NodeCaptureInterface'); diff --git a/vendor/twig/twig/src/Node/NodeOutputInterface.php b/vendor/twig/twig/src/Node/NodeOutputInterface.php new file mode 100644 index 0000000..8b046ee --- /dev/null +++ b/vendor/twig/twig/src/Node/NodeOutputInterface.php @@ -0,0 +1,23 @@ + + */ +interface NodeOutputInterface +{ +} + +class_alias('Twig\Node\NodeOutputInterface', 'Twig_NodeOutputInterface'); diff --git a/vendor/twig/twig/src/Node/PrintNode.php b/vendor/twig/twig/src/Node/PrintNode.php new file mode 100644 index 0000000..27f1ca4 --- /dev/null +++ b/vendor/twig/twig/src/Node/PrintNode.php @@ -0,0 +1,41 @@ + + */ +class PrintNode extends Node implements NodeOutputInterface +{ + public function __construct(AbstractExpression $expr, $lineno, $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} + +class_alias('Twig\Node\PrintNode', 'Twig_Node_Print'); diff --git a/vendor/twig/twig/src/Node/SandboxNode.php b/vendor/twig/twig/src/Node/SandboxNode.php new file mode 100644 index 0000000..2d644c3 --- /dev/null +++ b/vendor/twig/twig/src/Node/SandboxNode.php @@ -0,0 +1,47 @@ + + */ +class SandboxNode extends Node +{ + public function __construct(\Twig_NodeInterface $body, $lineno, $tag = null) + { + parent::__construct(['body' => $body], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("if (!\$alreadySandboxed = \$this->sandbox->isSandboxed()) {\n") + ->indent() + ->write("\$this->sandbox->enableSandbox();\n") + ->outdent() + ->write("}\n") + ->subcompile($this->getNode('body')) + ->write("if (!\$alreadySandboxed) {\n") + ->indent() + ->write("\$this->sandbox->disableSandbox();\n") + ->outdent() + ->write("}\n") + ; + } +} + +class_alias('Twig\Node\SandboxNode', 'Twig_Node_Sandbox'); diff --git a/vendor/twig/twig/src/Node/SandboxedPrintNode.php b/vendor/twig/twig/src/Node/SandboxedPrintNode.php new file mode 100644 index 0000000..2359af9 --- /dev/null +++ b/vendor/twig/twig/src/Node/SandboxedPrintNode.php @@ -0,0 +1,69 @@ + + */ +class SandboxedPrintNode extends PrintNode +{ + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ; + $expr = $this->getNode('expr'); + if ($expr instanceof ConstantExpression) { + $compiler + ->subcompile($expr) + ->raw(";\n") + ; + } else { + $compiler + ->write('$this->env->getExtension(\'\Twig\Extension\SandboxExtension\')->ensureToStringAllowed(') + ->subcompile($expr) + ->raw(");\n") + ; + } + } + + /** + * Removes node filters. + * + * This is mostly needed when another visitor adds filters (like the escaper one). + * + * @return Node + */ + protected function removeNodeFilter(Node $node) + { + if ($node instanceof FilterExpression) { + return $this->removeNodeFilter($node->getNode('node')); + } + + return $node; + } +} + +class_alias('Twig\Node\SandboxedPrintNode', 'Twig_Node_SandboxedPrint'); diff --git a/vendor/twig/twig/src/Node/SetNode.php b/vendor/twig/twig/src/Node/SetNode.php new file mode 100644 index 0000000..2c10a3a --- /dev/null +++ b/vendor/twig/twig/src/Node/SetNode.php @@ -0,0 +1,103 @@ + + */ +class SetNode extends Node implements NodeCaptureInterface +{ + public function __construct($capture, \Twig_NodeInterface $names, \Twig_NodeInterface $values, $lineno, $tag = null) + { + parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag); + + /* + * Optimizes the node when capture is used for a large block of text. + * + * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo"); + */ + if ($this->getAttribute('capture')) { + $this->setAttribute('safe', true); + + $values = $this->getNode('values'); + if ($values instanceof TextNode) { + $this->setNode('values', new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine())); + $this->setAttribute('capture', false); + } + } + } + + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if (\count($this->getNode('names')) > 1) { + $compiler->write('list('); + foreach ($this->getNode('names') as $idx => $node) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($node); + } + $compiler->raw(')'); + } else { + if ($this->getAttribute('capture')) { + $compiler + ->write("ob_start();\n") + ->subcompile($this->getNode('values')) + ; + } + + $compiler->subcompile($this->getNode('names'), false); + + if ($this->getAttribute('capture')) { + $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset())"); + } + } + + if (!$this->getAttribute('capture')) { + $compiler->raw(' = '); + + if (\count($this->getNode('names')) > 1) { + $compiler->write('['); + foreach ($this->getNode('values') as $idx => $value) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($value); + } + $compiler->raw(']'); + } else { + if ($this->getAttribute('safe')) { + $compiler + ->raw("('' === \$tmp = ") + ->subcompile($this->getNode('values')) + ->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())") + ; + } else { + $compiler->subcompile($this->getNode('values')); + } + } + } + + $compiler->raw(";\n"); + } +} + +class_alias('Twig\Node\SetNode', 'Twig_Node_Set'); diff --git a/vendor/twig/twig/src/Node/SetTempNode.php b/vendor/twig/twig/src/Node/SetTempNode.php new file mode 100644 index 0000000..918fb99 --- /dev/null +++ b/vendor/twig/twig/src/Node/SetTempNode.php @@ -0,0 +1,44 @@ + $name], $lineno); + } + + public function compile(Compiler $compiler) + { + $name = $this->getAttribute('name'); + $compiler + ->addDebugInfo($this) + ->write('if (isset($context[') + ->string($name) + ->raw('])) { $_') + ->raw($name) + ->raw('_ = $context[') + ->repr($name) + ->raw(']; } else { $_') + ->raw($name) + ->raw("_ = null; }\n") + ; + } +} + +class_alias('Twig\Node\SetTempNode', 'Twig_Node_SetTemp'); diff --git a/vendor/twig/twig/src/Node/SpacelessNode.php b/vendor/twig/twig/src/Node/SpacelessNode.php new file mode 100644 index 0000000..9beebf3 --- /dev/null +++ b/vendor/twig/twig/src/Node/SpacelessNode.php @@ -0,0 +1,41 @@ + + */ +class SpacelessNode extends Node +{ + public function __construct(\Twig_NodeInterface $body, $lineno, $tag = 'spaceless') + { + parent::__construct(['body' => $body], [], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write("ob_start();\n") + ->subcompile($this->getNode('body')) + ->write("echo trim(preg_replace('/>\s+<', ob_get_clean()));\n") + ; + } +} + +class_alias('Twig\Node\SpacelessNode', 'Twig_Node_Spaceless'); diff --git a/vendor/twig/twig/src/Node/TextNode.php b/vendor/twig/twig/src/Node/TextNode.php new file mode 100644 index 0000000..9ac435e --- /dev/null +++ b/vendor/twig/twig/src/Node/TextNode.php @@ -0,0 +1,40 @@ + + */ +class TextNode extends Node implements NodeOutputInterface +{ + public function __construct($data, $lineno) + { + parent::__construct([], ['data' => $data], $lineno); + } + + public function compile(Compiler $compiler) + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->string($this->getAttribute('data')) + ->raw(";\n") + ; + } +} + +class_alias('Twig\Node\TextNode', 'Twig_Node_Text'); diff --git a/vendor/twig/twig/src/Node/WithNode.php b/vendor/twig/twig/src/Node/WithNode.php new file mode 100644 index 0000000..f5ae924 --- /dev/null +++ b/vendor/twig/twig/src/Node/WithNode.php @@ -0,0 +1,72 @@ + + */ +class WithNode extends Node +{ + public function __construct(Node $body, Node $variables = null, $only = false, $lineno, $tag = null) + { + $nodes = ['body' => $body]; + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, ['only' => (bool) $only], $lineno, $tag); + } + + public function compile(Compiler $compiler) + { + $compiler->addDebugInfo($this); + + if ($this->hasNode('variables')) { + $node = $this->getNode('variables'); + $varsName = $compiler->getVarName(); + $compiler + ->write(sprintf('$%s = ', $varsName)) + ->subcompile($node) + ->raw(";\n") + ->write(sprintf("if (!twig_test_iterable(\$%s)) {\n", $varsName)) + ->indent() + ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->getSourceContext());\n") + ->outdent() + ->write("}\n") + ->write(sprintf("\$%s = twig_to_array(\$%s);\n", $varsName, $varsName)) + ; + + if ($this->getAttribute('only')) { + $compiler->write("\$context = ['_parent' => \$context];\n"); + } else { + $compiler->write("\$context['_parent'] = \$context;\n"); + } + + $compiler->write(sprintf("\$context = \$this->env->mergeGlobals(array_merge(\$context, \$%s));\n", $varsName)); + } else { + $compiler->write("\$context['_parent'] = \$context;\n"); + } + + $compiler + ->subcompile($this->getNode('body')) + ->write("\$context = \$context['_parent'];\n") + ; + } +} + +class_alias('Twig\Node\WithNode', 'Twig_Node_With'); diff --git a/vendor/twig/twig/src/NodeTraverser.php b/vendor/twig/twig/src/NodeTraverser.php new file mode 100644 index 0000000..8b0f85c --- /dev/null +++ b/vendor/twig/twig/src/NodeTraverser.php @@ -0,0 +1,85 @@ + + */ +class NodeTraverser +{ + protected $env; + protected $visitors = []; + + /** + * @param NodeVisitorInterface[] $visitors + */ + public function __construct(Environment $env, array $visitors = []) + { + $this->env = $env; + foreach ($visitors as $visitor) { + $this->addVisitor($visitor); + } + } + + public function addVisitor(NodeVisitorInterface $visitor) + { + $this->visitors[$visitor->getPriority()][] = $visitor; + } + + /** + * Traverses a node and calls the registered visitors. + * + * @return \Twig_NodeInterface + */ + public function traverse(\Twig_NodeInterface $node) + { + ksort($this->visitors); + foreach ($this->visitors as $visitors) { + foreach ($visitors as $visitor) { + $node = $this->traverseForVisitor($visitor, $node); + } + } + + return $node; + } + + protected function traverseForVisitor(NodeVisitorInterface $visitor, \Twig_NodeInterface $node = null) + { + if (null === $node) { + return; + } + + $node = $visitor->enterNode($node, $this->env); + + foreach ($node as $k => $n) { + if (false !== $m = $this->traverseForVisitor($visitor, $n)) { + if ($m !== $n) { + $node->setNode($k, $m); + } + } else { + $node->removeNode($k); + } + } + + return $visitor->leaveNode($node, $this->env); + } +} + +class_alias('Twig\NodeTraverser', 'Twig_NodeTraverser'); diff --git a/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php new file mode 100644 index 0000000..184be8e --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php @@ -0,0 +1,57 @@ + + */ +abstract class AbstractNodeVisitor implements NodeVisitorInterface +{ + final public function enterNode(\Twig_NodeInterface $node, Environment $env) + { + if (!$node instanceof Node) { + throw new \LogicException(sprintf('%s only supports \Twig\Node\Node instances.', __CLASS__)); + } + + return $this->doEnterNode($node, $env); + } + + final public function leaveNode(\Twig_NodeInterface $node, Environment $env) + { + if (!$node instanceof Node) { + throw new \LogicException(sprintf('%s only supports \Twig\Node\Node instances.', __CLASS__)); + } + + return $this->doLeaveNode($node, $env); + } + + /** + * Called before child nodes are visited. + * + * @return Node The modified node + */ + abstract protected function doEnterNode(Node $node, Environment $env); + + /** + * Called after child nodes are visited. + * + * @return Node|false The modified node or false if the node must be removed + */ + abstract protected function doLeaveNode(Node $node, Environment $env); +} + +class_alias('Twig\NodeVisitor\AbstractNodeVisitor', 'Twig_BaseNodeVisitor'); diff --git a/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php new file mode 100644 index 0000000..f6e16fa --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php @@ -0,0 +1,209 @@ + + */ +class EscaperNodeVisitor extends AbstractNodeVisitor +{ + protected $statusStack = []; + protected $blocks = []; + protected $safeAnalysis; + protected $traverser; + protected $defaultStrategy = false; + protected $safeVars = []; + + public function __construct() + { + $this->safeAnalysis = new SafeAnalysisNodeVisitor(); + } + + protected function doEnterNode(Node $node, Environment $env) + { + if ($node instanceof ModuleNode) { + if ($env->hasExtension('\Twig\Extension\EscaperExtension') && $defaultStrategy = $env->getExtension('\Twig\Extension\EscaperExtension')->getDefaultStrategy($node->getTemplateName())) { + $this->defaultStrategy = $defaultStrategy; + } + $this->safeVars = []; + $this->blocks = []; + } elseif ($node instanceof AutoEscapeNode) { + $this->statusStack[] = $node->getAttribute('value'); + } elseif ($node instanceof BlockNode) { + $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); + } elseif ($node instanceof ImportNode) { + $this->safeVars[] = $node->getNode('var')->getAttribute('name'); + } + + return $node; + } + + protected function doLeaveNode(Node $node, Environment $env) + { + if ($node instanceof ModuleNode) { + $this->defaultStrategy = false; + $this->safeVars = []; + $this->blocks = []; + } elseif ($node instanceof FilterExpression) { + return $this->preEscapeFilterNode($node, $env); + } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) { + $expression = $node->getNode('expr'); + if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) { + return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine()); + } + + return $this->escapePrintNode($node, $env, $type); + } + + if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { + array_pop($this->statusStack); + } elseif ($node instanceof BlockReferenceNode) { + $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); + } + + return $node; + } + + private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, $type) + { + $expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env); + $expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env); + + return $expr2Safe !== $expr3Safe; + } + + private function unwrapConditional(ConditionalExpression $expression, Environment $env, $type) + { + // convert "echo a ? b : c" to "a ? echo b : echo c" recursively + $expr2 = $expression->getNode('expr2'); + if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) { + $expr2 = $this->unwrapConditional($expr2, $env, $type); + } else { + $expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type); + } + $expr3 = $expression->getNode('expr3'); + if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr3, $env, $type)) { + $expr3 = $this->unwrapConditional($expr3, $env, $type); + } else { + $expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type); + } + + return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine()); + } + + private function escapeInlinePrintNode(InlinePrint $node, Environment $env, $type) + { + $expression = $node->getNode('node'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + } + + protected function escapePrintNode(PrintNode $node, Environment $env, $type) + { + if (false === $type) { + return $node; + } + + $expression = $node->getNode('expr'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + $class = \get_class($node); + + return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + } + + protected function preEscapeFilterNode(FilterExpression $filter, Environment $env) + { + $name = $filter->getNode('filter')->getAttribute('value'); + + $type = $env->getFilter($name)->getPreEscape(); + if (null === $type) { + return $filter; + } + + $node = $filter->getNode('node'); + if ($this->isSafeFor($type, $node, $env)) { + return $filter; + } + + $filter->setNode('node', $this->getEscaperFilter($type, $node)); + + return $filter; + } + + protected function isSafeFor($type, \Twig_NodeInterface $expression, $env) + { + $safe = $this->safeAnalysis->getSafe($expression); + + if (null === $safe) { + if (null === $this->traverser) { + $this->traverser = new NodeTraverser($env, [$this->safeAnalysis]); + } + + $this->safeAnalysis->setSafeVars($this->safeVars); + + $this->traverser->traverse($expression); + $safe = $this->safeAnalysis->getSafe($expression); + } + + return \in_array($type, $safe) || \in_array('all', $safe); + } + + protected function needEscaping(Environment $env) + { + if (\count($this->statusStack)) { + return $this->statusStack[\count($this->statusStack) - 1]; + } + + return $this->defaultStrategy ? $this->defaultStrategy : false; + } + + protected function getEscaperFilter($type, \Twig_NodeInterface $node) + { + $line = $node->getTemplateLine(); + $name = new ConstantExpression('escape', $line); + $args = new Node([new ConstantExpression((string) $type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); + + return new FilterExpression($node, $name, $args, $line); + } + + public function getPriority() + { + return 0; + } +} + +class_alias('Twig\NodeVisitor\EscaperNodeVisitor', 'Twig_NodeVisitor_Escaper'); diff --git a/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php b/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php new file mode 100644 index 0000000..5e21c4f --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php @@ -0,0 +1,50 @@ + + */ +interface NodeVisitorInterface +{ + /** + * Called before child nodes are visited. + * + * @return \Twig_NodeInterface The modified node + */ + public function enterNode(\Twig_NodeInterface $node, Environment $env); + + /** + * Called after child nodes are visited. + * + * @return \Twig_NodeInterface|false The modified node or false if the node must be removed + */ + public function leaveNode(\Twig_NodeInterface $node, Environment $env); + + /** + * Returns the priority for this visitor. + * + * Priority should be between -10 and 10 (0 is the default). + * + * @return int The priority level + */ + public function getPriority(); +} + +class_alias('Twig\NodeVisitor\NodeVisitorInterface', 'Twig_NodeVisitorInterface'); + +// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. +class_exists('Twig\Environment'); diff --git a/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php new file mode 100644 index 0000000..e5ea9b7 --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php @@ -0,0 +1,273 @@ + + */ +class OptimizerNodeVisitor extends AbstractNodeVisitor +{ + const OPTIMIZE_ALL = -1; + const OPTIMIZE_NONE = 0; + const OPTIMIZE_FOR = 2; + const OPTIMIZE_RAW_FILTER = 4; + const OPTIMIZE_VAR_ACCESS = 8; + + protected $loops = []; + protected $loopsTargets = []; + protected $optimizers; + protected $prependedNodes = []; + protected $inABody = false; + + /** + * @param int $optimizers The optimizer mode + */ + public function __construct($optimizers = -1) + { + if (!\is_int($optimizers) || $optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER | self::OPTIMIZE_VAR_ACCESS)) { + throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); + } + + $this->optimizers = $optimizers; + } + + protected function doEnterNode(Node $node, Environment $env) + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->enterOptimizeFor($node, $env); + } + + if (\PHP_VERSION_ID < 50400 && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('\Twig\Extension\SandboxExtension')) { + if ($this->inABody) { + if (!$node instanceof AbstractExpression) { + if ('Twig_Node' !== \get_class($node)) { + array_unshift($this->prependedNodes, []); + } + } else { + $node = $this->optimizeVariables($node, $env); + } + } elseif ($node instanceof BodyNode) { + $this->inABody = true; + } + } + + return $node; + } + + protected function doLeaveNode(Node $node, Environment $env) + { + $expression = $node instanceof AbstractExpression; + + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->leaveOptimizeFor($node, $env); + } + + if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { + $node = $this->optimizeRawFilter($node, $env); + } + + $node = $this->optimizePrintNode($node, $env); + + if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('\Twig\Extension\SandboxExtension')) { + if ($node instanceof BodyNode) { + $this->inABody = false; + } elseif ($this->inABody) { + if (!$expression && 'Twig_Node' !== \get_class($node) && $prependedNodes = array_shift($this->prependedNodes)) { + $nodes = []; + foreach (array_unique($prependedNodes) as $name) { + $nodes[] = new SetTempNode($name, $node->getTemplateLine()); + } + + $nodes[] = $node; + $node = new Node($nodes); + } + } + } + + return $node; + } + + protected function optimizeVariables(\Twig_NodeInterface $node, Environment $env) + { + if ('Twig_Node_Expression_Name' === \get_class($node) && $node->isSimple()) { + $this->prependedNodes[0][] = $node->getAttribute('name'); + + return new TempNameExpression($node->getAttribute('name'), $node->getTemplateLine()); + } + + return $node; + } + + /** + * Optimizes print nodes. + * + * It replaces: + * + * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" + * + * @return \Twig_NodeInterface + */ + protected function optimizePrintNode(\Twig_NodeInterface $node, Environment $env) + { + if (!$node instanceof PrintNode) { + return $node; + } + + $exprNode = $node->getNode('expr'); + if ( + $exprNode instanceof BlockReferenceExpression || + $exprNode instanceof ParentExpression + ) { + $exprNode->setAttribute('output', true); + + return $exprNode; + } + + return $node; + } + + /** + * Removes "raw" filters. + * + * @return \Twig_NodeInterface + */ + protected function optimizeRawFilter(\Twig_NodeInterface $node, Environment $env) + { + if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) { + return $node->getNode('node'); + } + + return $node; + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + protected function enterOptimizeFor(\Twig_NodeInterface $node, Environment $env) + { + if ($node instanceof ForNode) { + // disable the loop variable by default + $node->setAttribute('with_loop', false); + array_unshift($this->loops, $node); + array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name')); + array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name')); + } elseif (!$this->loops) { + // we are outside a loop + return; + } + + // when do we need to add the loop variable back? + + // the loop variable is referenced for the current loop + elseif ($node instanceof NameExpression && 'loop' === $node->getAttribute('name')) { + $node->setAttribute('always_defined', true); + $this->addLoopToCurrent(); + } + + // optimize access to loop targets + elseif ($node instanceof NameExpression && \in_array($node->getAttribute('name'), $this->loopsTargets)) { + $node->setAttribute('always_defined', true); + } + + // block reference + elseif ($node instanceof BlockReferenceNode || $node instanceof BlockReferenceExpression) { + $this->addLoopToCurrent(); + } + + // include without the only attribute + elseif ($node instanceof IncludeNode && !$node->getAttribute('only')) { + $this->addLoopToAll(); + } + + // include function without the with_context=false parameter + elseif ($node instanceof FunctionExpression + && 'include' === $node->getAttribute('name') + && (!$node->getNode('arguments')->hasNode('with_context') + || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') + ) + ) { + $this->addLoopToAll(); + } + + // the loop variable is referenced via an attribute + elseif ($node instanceof GetAttrExpression + && (!$node->getNode('attribute') instanceof ConstantExpression + || 'parent' === $node->getNode('attribute')->getAttribute('value') + ) + && (true === $this->loops[0]->getAttribute('with_loop') + || ($node->getNode('node') instanceof NameExpression + && 'loop' === $node->getNode('node')->getAttribute('name') + ) + ) + ) { + $this->addLoopToAll(); + } + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + protected function leaveOptimizeFor(\Twig_NodeInterface $node, Environment $env) + { + if ($node instanceof ForNode) { + array_shift($this->loops); + array_shift($this->loopsTargets); + array_shift($this->loopsTargets); + } + } + + protected function addLoopToCurrent() + { + $this->loops[0]->setAttribute('with_loop', true); + } + + protected function addLoopToAll() + { + foreach ($this->loops as $loop) { + $loop->setAttribute('with_loop', true); + } + } + + public function getPriority() + { + return 255; + } +} + +class_alias('Twig\NodeVisitor\OptimizerNodeVisitor', 'Twig_NodeVisitor_Optimizer'); diff --git a/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php new file mode 100644 index 0000000..97a7a3e --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php @@ -0,0 +1,164 @@ +safeVars = $safeVars; + } + + public function getSafe(\Twig_NodeInterface $node) + { + $hash = spl_object_hash($node); + if (!isset($this->data[$hash])) { + return; + } + + foreach ($this->data[$hash] as $bucket) { + if ($bucket['key'] !== $node) { + continue; + } + + if (\in_array('html_attr', $bucket['value'])) { + $bucket['value'][] = 'html'; + } + + return $bucket['value']; + } + } + + protected function setSafe(\Twig_NodeInterface $node, array $safe) + { + $hash = spl_object_hash($node); + if (isset($this->data[$hash])) { + foreach ($this->data[$hash] as &$bucket) { + if ($bucket['key'] === $node) { + $bucket['value'] = $safe; + + return; + } + } + } + $this->data[$hash][] = [ + 'key' => $node, + 'value' => $safe, + ]; + } + + protected function doEnterNode(Node $node, Environment $env) + { + return $node; + } + + protected function doLeaveNode(Node $node, Environment $env) + { + if ($node instanceof ConstantExpression) { + // constants are marked safe for all + $this->setSafe($node, ['all']); + } elseif ($node instanceof BlockReferenceExpression) { + // blocks are safe by definition + $this->setSafe($node, ['all']); + } elseif ($node instanceof ParentExpression) { + // parent block is safe by definition + $this->setSafe($node, ['all']); + } elseif ($node instanceof ConditionalExpression) { + // intersect safeness of both operands + $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); + $this->setSafe($node, $safe); + } elseif ($node instanceof FilterExpression) { + // filter expression is safe when the filter is safe + $name = $node->getNode('filter')->getAttribute('value'); + $args = $node->getNode('arguments'); + if (false !== $filter = $env->getFilter($name)) { + $safe = $filter->getSafe($args); + if (null === $safe) { + $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); + } + $this->setSafe($node, $safe); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof FunctionExpression) { + // function expression is safe when the function is safe + $name = $node->getAttribute('name'); + $args = $node->getNode('arguments'); + $function = $env->getFunction($name); + if (false !== $function) { + $this->setSafe($node, $function->getSafe($args)); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof MethodCallExpression) { + if ($node->getAttribute('safe')) { + $this->setSafe($node, ['all']); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) { + $name = $node->getNode('node')->getAttribute('name'); + // attributes on template instances are safe + if ('_self' == $name || \in_array($name, $this->safeVars)) { + $this->setSafe($node, ['all']); + } else { + $this->setSafe($node, []); + } + } else { + $this->setSafe($node, []); + } + + return $node; + } + + protected function intersectSafe(array $a = null, array $b = null) + { + if (null === $a || null === $b) { + return []; + } + + if (\in_array('all', $a)) { + return $b; + } + + if (\in_array('all', $b)) { + return $a; + } + + return array_intersect($a, $b); + } + + public function getPriority() + { + return 0; + } +} + +class_alias('Twig\NodeVisitor\SafeAnalysisNodeVisitor', 'Twig_NodeVisitor_SafeAnalysis'); diff --git a/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php new file mode 100644 index 0000000..c1d302a --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php @@ -0,0 +1,137 @@ + + */ +class SandboxNodeVisitor extends AbstractNodeVisitor +{ + protected $inAModule = false; + protected $tags; + protected $filters; + protected $functions; + + private $needsToStringWrap = false; + + protected function doEnterNode(Node $node, Environment $env) + { + if ($node instanceof ModuleNode) { + $this->inAModule = true; + $this->tags = []; + $this->filters = []; + $this->functions = []; + + return $node; + } elseif ($this->inAModule) { + // look for tags + if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) { + $this->tags[$node->getNodeTag()] = $node; + } + + // look for filters + if ($node instanceof FilterExpression && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) { + $this->filters[$node->getNode('filter')->getAttribute('value')] = $node; + } + + // look for functions + if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) { + $this->functions[$node->getAttribute('name')] = $node; + } + + // the .. operator is equivalent to the range() function + if ($node instanceof RangeBinary && !isset($this->functions['range'])) { + $this->functions['range'] = $node; + } + + if ($node instanceof PrintNode) { + $this->needsToStringWrap = true; + $this->wrapNode($node, 'expr'); + } + + if ($node instanceof SetNode && !$node->getAttribute('capture')) { + $this->needsToStringWrap = true; + } + + // wrap outer nodes that can implicitly call __toString() + if ($this->needsToStringWrap) { + if ($node instanceof ConcatBinary) { + $this->wrapNode($node, 'left'); + $this->wrapNode($node, 'right'); + } + if ($node instanceof FilterExpression) { + $this->wrapNode($node, 'node'); + $this->wrapArrayNode($node, 'arguments'); + } + if ($node instanceof FunctionExpression) { + $this->wrapArrayNode($node, 'arguments'); + } + } + } + + return $node; + } + + protected function doLeaveNode(Node $node, Environment $env) + { + if ($node instanceof ModuleNode) { + $this->inAModule = false; + + $node->setNode('constructor_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('display_start')])); + } elseif ($this->inAModule) { + if ($node instanceof PrintNode || $node instanceof SetNode) { + $this->needsToStringWrap = false; + } + } + + return $node; + } + + private function wrapNode(Node $node, $name) + { + $expr = $node->getNode($name); + if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) { + $node->setNode($name, new CheckToStringNode($expr)); + } + } + + private function wrapArrayNode(Node $node, $name) + { + $args = $node->getNode($name); + foreach ($args as $name => $_) { + $this->wrapNode($args, $name); + } + } + + public function getPriority() + { + return 0; + } +} + +class_alias('Twig\NodeVisitor\SandboxNodeVisitor', 'Twig_NodeVisitor_Sandbox'); diff --git a/vendor/twig/twig/src/Parser.php b/vendor/twig/twig/src/Parser.php new file mode 100644 index 0000000..444d4c5 --- /dev/null +++ b/vendor/twig/twig/src/Parser.php @@ -0,0 +1,431 @@ + + */ +class Parser implements \Twig_ParserInterface +{ + protected $stack = []; + protected $stream; + protected $parent; + protected $handlers; + protected $visitors; + protected $expressionParser; + protected $blocks; + protected $blockStack; + protected $macros; + protected $env; + protected $reservedMacroNames; + protected $importedSymbols; + protected $traits; + protected $embeddedTemplates = []; + private $varNameSalt = 0; + + public function __construct(Environment $env) + { + $this->env = $env; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getEnvironment() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + + return $this->env; + } + + public function getVarName() + { + return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->stream->getSourceContext()->getCode().$this->varNameSalt++)); + } + + /** + * @deprecated since 1.27 (to be removed in 2.0). Use $parser->getStream()->getSourceContext()->getPath() instead. + */ + public function getFilename() + { + @trigger_error(sprintf('The "%s" method is deprecated since version 1.27 and will be removed in 2.0. Use $parser->getStream()->getSourceContext()->getPath() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->stream->getSourceContext()->getName(); + } + + public function parse(TokenStream $stream, $test = null, $dropNeedle = false) + { + // push all variables into the stack to keep the current state of the parser + // using get_object_vars() instead of foreach would lead to https://bugs.php.net/71336 + // This hack can be removed when min version if PHP 7.0 + $vars = []; + foreach ($this as $k => $v) { + $vars[$k] = $v; + } + + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']); + $this->stack[] = $vars; + + // tag handlers + if (null === $this->handlers) { + $this->handlers = $this->env->getTokenParsers(); + $this->handlers->setParser($this); + } + + // node visitors + if (null === $this->visitors) { + $this->visitors = $this->env->getNodeVisitors(); + } + + if (null === $this->expressionParser) { + $this->expressionParser = new ExpressionParser($this, $this->env); + } + + $this->stream = $stream; + $this->parent = null; + $this->blocks = []; + $this->macros = []; + $this->traits = []; + $this->blockStack = []; + $this->importedSymbols = [[]]; + $this->embeddedTemplates = []; + $this->varNameSalt = 0; + + try { + $body = $this->subparse($test, $dropNeedle); + + if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) { + $body = new Node(); + } + } catch (SyntaxError $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->stream->getSourceContext()); + } + + if (!$e->getTemplateLine()) { + $e->setTemplateLine($this->stream->getCurrent()->getLine()); + } + + throw $e; + } + + $node = new ModuleNode(new BodyNode([$body]), $this->parent, new Node($this->blocks), new Node($this->macros), new Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext()); + + $traverser = new NodeTraverser($this->env, $this->visitors); + + $node = $traverser->traverse($node); + + // restore previous stack so previous parse() call can resume working + foreach (array_pop($this->stack) as $key => $val) { + $this->$key = $val; + } + + return $node; + } + + public function subparse($test, $dropNeedle = false) + { + $lineno = $this->getCurrentToken()->getLine(); + $rv = []; + while (!$this->stream->isEOF()) { + switch ($this->getCurrentToken()->getType()) { + case Token::TEXT_TYPE: + $token = $this->stream->next(); + $rv[] = new TextNode($token->getValue(), $token->getLine()); + break; + + case Token::VAR_START_TYPE: + $token = $this->stream->next(); + $expr = $this->expressionParser->parseExpression(); + $this->stream->expect(Token::VAR_END_TYPE); + $rv[] = new PrintNode($expr, $token->getLine()); + break; + + case Token::BLOCK_START_TYPE: + $this->stream->next(); + $token = $this->getCurrentToken(); + + if (Token::NAME_TYPE !== $token->getType()) { + throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); + } + + if (null !== $test && \call_user_func($test, $token)) { + if ($dropNeedle) { + $this->stream->next(); + } + + if (1 === \count($rv)) { + return $rv[0]; + } + + return new Node($rv, [], $lineno); + } + + $subparser = $this->handlers->getTokenParser($token->getValue()); + if (null === $subparser) { + if (null !== $test) { + $e = new SyntaxError(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + + if (\is_array($test) && isset($test[0]) && $test[0] instanceof TokenParserInterface) { + $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); + } + } else { + $e = new SyntaxError(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + $e->addSuggestions($token->getValue(), array_keys($this->env->getTags())); + } + + throw $e; + } + + $this->stream->next(); + + $node = $subparser->parse($token); + if (null !== $node) { + $rv[] = $node; + } + break; + + default: + throw new SyntaxError('Lexer or parser ended up in unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); + } + } + + if (1 === \count($rv)) { + return $rv[0]; + } + + return new Node($rv, [], $lineno); + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function addHandler($name, $class) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + + $this->handlers[$name] = $class; + } + + /** + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function addNodeVisitor(NodeVisitorInterface $visitor) + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0.', E_USER_DEPRECATED); + + $this->visitors[] = $visitor; + } + + public function getBlockStack() + { + return $this->blockStack; + } + + public function peekBlockStack() + { + return $this->blockStack[\count($this->blockStack) - 1]; + } + + public function popBlockStack() + { + array_pop($this->blockStack); + } + + public function pushBlockStack($name) + { + $this->blockStack[] = $name; + } + + public function hasBlock($name) + { + return isset($this->blocks[$name]); + } + + public function getBlock($name) + { + return $this->blocks[$name]; + } + + public function setBlock($name, BlockNode $value) + { + $this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine()); + } + + public function hasMacro($name) + { + return isset($this->macros[$name]); + } + + public function setMacro($name, MacroNode $node) + { + if ($this->isReservedMacroName($name)) { + throw new SyntaxError(sprintf('"%s" cannot be used as a macro name as it is a reserved keyword.', $name), $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + $this->macros[$name] = $node; + } + + public function isReservedMacroName($name) + { + if (null === $this->reservedMacroNames) { + $this->reservedMacroNames = []; + $r = new \ReflectionClass($this->env->getBaseTemplateClass()); + foreach ($r->getMethods() as $method) { + $methodName = strtolower($method->getName()); + + if ('get' === substr($methodName, 0, 3) && isset($methodName[3])) { + $this->reservedMacroNames[] = substr($methodName, 3); + } + } + } + + return \in_array(strtolower($name), $this->reservedMacroNames); + } + + public function addTrait($trait) + { + $this->traits[] = $trait; + } + + public function hasTraits() + { + return \count($this->traits) > 0; + } + + public function embedTemplate(ModuleNode $template) + { + $template->setIndex(mt_rand()); + + $this->embeddedTemplates[] = $template; + } + + public function addImportedSymbol($type, $alias, $name = null, AbstractExpression $node = null) + { + $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node]; + } + + public function getImportedSymbol($type, $alias) + { + foreach ($this->importedSymbols as $functions) { + if (isset($functions[$type][$alias])) { + return $functions[$type][$alias]; + } + } + } + + public function isMainScope() + { + return 1 === \count($this->importedSymbols); + } + + public function pushLocalScope() + { + array_unshift($this->importedSymbols, []); + } + + public function popLocalScope() + { + array_shift($this->importedSymbols); + } + + /** + * @return ExpressionParser + */ + public function getExpressionParser() + { + return $this->expressionParser; + } + + public function getParent() + { + return $this->parent; + } + + public function setParent($parent) + { + $this->parent = $parent; + } + + /** + * @return TokenStream + */ + public function getStream() + { + return $this->stream; + } + + /** + * @return Token + */ + public function getCurrentToken() + { + return $this->stream->getCurrent(); + } + + protected function filterBodyNodes(\Twig_NodeInterface $node) + { + // check that the body does not contain non-empty output nodes + if ( + ($node instanceof TextNode && !ctype_space($node->getAttribute('data'))) + || + (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) + ) { + if (false !== strpos((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { + $t = substr($node->getAttribute('data'), 3); + if ('' === $t || ctype_space($t)) { + // bypass empty nodes starting with a BOM + return; + } + } + + throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + // bypass nodes that will "capture" the output + if ($node instanceof NodeCaptureInterface) { + return $node; + } + + if ($node instanceof NodeOutputInterface) { + return; + } + + foreach ($node as $k => $n) { + if (null !== $n && null === $this->filterBodyNodes($n)) { + $node->removeNode($k); + } + } + + return $node; + } +} + +class_alias('Twig\Parser', 'Twig_Parser'); diff --git a/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php b/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php new file mode 100644 index 0000000..d965dc7 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php @@ -0,0 +1,65 @@ + + */ +abstract class BaseDumper +{ + private $root; + + public function dump(Profile $profile) + { + return $this->dumpProfile($profile); + } + + abstract protected function formatTemplate(Profile $profile, $prefix); + + abstract protected function formatNonTemplate(Profile $profile, $prefix); + + abstract protected function formatTime(Profile $profile, $percent); + + private function dumpProfile(Profile $profile, $prefix = '', $sibling = false) + { + if ($profile->isRoot()) { + $this->root = $profile->getDuration(); + $start = $profile->getName(); + } else { + if ($profile->isTemplate()) { + $start = $this->formatTemplate($profile, $prefix); + } else { + $start = $this->formatNonTemplate($profile, $prefix); + } + $prefix .= $sibling ? '│ ' : ' '; + } + + $percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0; + + if ($profile->getDuration() * 1000 < 1) { + $str = $start."\n"; + } else { + $str = sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); + } + + $nCount = \count($profile->getProfiles()); + foreach ($profile as $i => $p) { + $str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount); + } + + return $str; + } +} + +class_alias('Twig\Profiler\Dumper\BaseDumper', 'Twig_Profiler_Dumper_Base'); diff --git a/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php b/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php new file mode 100644 index 0000000..a1c3c7b --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php @@ -0,0 +1,76 @@ + + * + * @final + */ +class BlackfireDumper +{ + public function dump(Profile $profile) + { + $data = []; + $this->dumpProfile('main()', $profile, $data); + $this->dumpChildren('main()', $profile, $data); + + $start = sprintf('%f', microtime(true)); + $str = << $values) { + $str .= "{$name}//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; + } + + return $str; + } + + private function dumpChildren($parent, Profile $profile, &$data) + { + foreach ($profile as $p) { + if ($p->isTemplate()) { + $name = $p->getTemplate(); + } else { + $name = sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); + } + $this->dumpProfile(sprintf('%s==>%s', $parent, $name), $p, $data); + $this->dumpChildren($name, $p, $data); + } + } + + private function dumpProfile($edge, Profile $profile, &$data) + { + if (isset($data[$edge])) { + ++$data[$edge]['ct']; + $data[$edge]['wt'] += floor($profile->getDuration() * 1000000); + $data[$edge]['mu'] += $profile->getMemoryUsage(); + $data[$edge]['pmu'] += $profile->getPeakMemoryUsage(); + } else { + $data[$edge] = [ + 'ct' => 1, + 'wt' => floor($profile->getDuration() * 1000000), + 'mu' => $profile->getMemoryUsage(), + 'pmu' => $profile->getPeakMemoryUsage(), + ]; + } + } +} + +class_alias('Twig\Profiler\Dumper\BlackfireDumper', 'Twig_Profiler_Dumper_Blackfire'); diff --git a/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php b/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php new file mode 100644 index 0000000..c70b405 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php @@ -0,0 +1,51 @@ + + * + * @final + */ +class HtmlDumper extends BaseDumper +{ + private static $colors = [ + 'block' => '#dfd', + 'macro' => '#ddf', + 'template' => '#ffd', + 'big' => '#d44', + ]; + + public function dump(Profile $profile) + { + return '
    '.parent::dump($profile).'
    '; + } + + protected function formatTemplate(Profile $profile, $prefix) + { + return sprintf('%s└ %s', $prefix, self::$colors['template'], $profile->getTemplate()); + } + + protected function formatNonTemplate(Profile $profile, $prefix) + { + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); + } + + protected function formatTime(Profile $profile, $percent) + { + return sprintf('%.2fms/%.0f%%', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); + } +} + +class_alias('Twig\Profiler\Dumper\HtmlDumper', 'Twig_Profiler_Dumper_Html'); diff --git a/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php b/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php new file mode 100644 index 0000000..c6b5158 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php @@ -0,0 +1,39 @@ + + * + * @final + */ +class TextDumper extends BaseDumper +{ + protected function formatTemplate(Profile $profile, $prefix) + { + return sprintf('%s└ %s', $prefix, $profile->getTemplate()); + } + + protected function formatNonTemplate(Profile $profile, $prefix) + { + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); + } + + protected function formatTime(Profile $profile, $percent) + { + return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); + } +} + +class_alias('Twig\Profiler\Dumper\TextDumper', 'Twig_Profiler_Dumper_Text'); diff --git a/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php b/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php new file mode 100644 index 0000000..8ffd3dc --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php @@ -0,0 +1,44 @@ + + */ +class EnterProfileNode extends Node +{ + public function __construct($extensionName, $type, $name, $varName) + { + parent::__construct([], ['extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName]); + } + + public function compile(Compiler $compiler) + { + $compiler + ->write(sprintf('$%s = $this->env->getExtension(', $this->getAttribute('var_name'))) + ->repr($this->getAttribute('extension_name')) + ->raw(");\n") + ->write(sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ->repr($this->getAttribute('type')) + ->raw(', ') + ->repr($this->getAttribute('name')) + ->raw("));\n\n") + ; + } +} + +class_alias('Twig\Profiler\Node\EnterProfileNode', 'Twig_Profiler_Node_EnterProfile'); diff --git a/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php b/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php new file mode 100644 index 0000000..3b7a74d --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php @@ -0,0 +1,38 @@ + + */ +class LeaveProfileNode extends Node +{ + public function __construct($varName) + { + parent::__construct([], ['var_name' => $varName]); + } + + public function compile(Compiler $compiler) + { + $compiler + ->write("\n") + ->write(sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ; + } +} + +class_alias('Twig\Profiler\Node\LeaveProfileNode', 'Twig_Profiler_Node_LeaveProfile'); diff --git a/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php b/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php new file mode 100644 index 0000000..41ca9e1 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php @@ -0,0 +1,80 @@ + + * + * @final + */ +class ProfilerNodeVisitor extends AbstractNodeVisitor +{ + private $extensionName; + + public function __construct($extensionName) + { + $this->extensionName = $extensionName; + } + + protected function doEnterNode(Node $node, Environment $env) + { + return $node; + } + + protected function doLeaveNode(Node $node, Environment $env) + { + if ($node instanceof ModuleNode) { + $varName = $this->getVarName(); + $node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $varName), $node->getNode('display_start')])); + $node->setNode('display_end', new Node([new LeaveProfileNode($varName), $node->getNode('display_end')])); + } elseif ($node instanceof BlockNode) { + $varName = $this->getVarName(); + $node->setNode('body', new BodyNode([ + new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $varName), + $node->getNode('body'), + new LeaveProfileNode($varName), + ])); + } elseif ($node instanceof MacroNode) { + $varName = $this->getVarName(); + $node->setNode('body', new BodyNode([ + new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $varName), + $node->getNode('body'), + new LeaveProfileNode($varName), + ])); + } + + return $node; + } + + private function getVarName() + { + return sprintf('__internal_%s', hash('sha256', $this->extensionName)); + } + + public function getPriority() + { + return 0; + } +} + +class_alias('Twig\Profiler\NodeVisitor\ProfilerNodeVisitor', 'Twig_Profiler_NodeVisitor_Profiler'); diff --git a/vendor/twig/twig/src/Profiler/Profile.php b/vendor/twig/twig/src/Profiler/Profile.php new file mode 100644 index 0000000..4d8ba3e --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Profile.php @@ -0,0 +1,188 @@ + + * + * @final + */ +class Profile implements \IteratorAggregate, \Serializable +{ + const ROOT = 'ROOT'; + const BLOCK = 'block'; + const TEMPLATE = 'template'; + const MACRO = 'macro'; + + private $template; + private $name; + private $type; + private $starts = []; + private $ends = []; + private $profiles = []; + + public function __construct($template = 'main', $type = self::ROOT, $name = 'main') + { + $this->template = $template; + $this->type = $type; + $this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name; + $this->enter(); + } + + public function getTemplate() + { + return $this->template; + } + + public function getType() + { + return $this->type; + } + + public function getName() + { + return $this->name; + } + + public function isRoot() + { + return self::ROOT === $this->type; + } + + public function isTemplate() + { + return self::TEMPLATE === $this->type; + } + + public function isBlock() + { + return self::BLOCK === $this->type; + } + + public function isMacro() + { + return self::MACRO === $this->type; + } + + public function getProfiles() + { + return $this->profiles; + } + + public function addProfile(self $profile) + { + $this->profiles[] = $profile; + } + + /** + * Returns the duration in microseconds. + * + * @return int + */ + public function getDuration() + { + if ($this->isRoot() && $this->profiles) { + // for the root node with children, duration is the sum of all child durations + $duration = 0; + foreach ($this->profiles as $profile) { + $duration += $profile->getDuration(); + } + + return $duration; + } + + return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0; + } + + /** + * Returns the memory usage in bytes. + * + * @return int + */ + public function getMemoryUsage() + { + return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0; + } + + /** + * Returns the peak memory usage in bytes. + * + * @return int + */ + public function getPeakMemoryUsage() + { + return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0; + } + + /** + * Starts the profiling. + */ + public function enter() + { + $this->starts = [ + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ]; + } + + /** + * Stops the profiling. + */ + public function leave() + { + $this->ends = [ + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ]; + } + + public function reset() + { + $this->starts = $this->ends = $this->profiles = []; + $this->enter(); + } + + public function getIterator() + { + return new \ArrayIterator($this->profiles); + } + + public function serialize() + { + return serialize($this->__serialize()); + } + + public function unserialize($data) + { + $this->__unserialize(unserialize($data)); + } + + /** + * @internal + */ + public function __serialize() + { + return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles]; + } + + /** + * @internal + */ + public function __unserialize(array $data) + { + list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = $data; + } +} + +class_alias('Twig\Profiler\Profile', 'Twig_Profiler_Profile'); diff --git a/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php b/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php new file mode 100644 index 0000000..04a6602 --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php @@ -0,0 +1,41 @@ + + * @author Robin Chalas + */ +class ContainerRuntimeLoader implements RuntimeLoaderInterface +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function load($class) + { + if ($this->container->has($class)) { + return $this->container->get($class); + } + } +} + +class_alias('Twig\RuntimeLoader\ContainerRuntimeLoader', 'Twig_ContainerRuntimeLoader'); diff --git a/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php b/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php new file mode 100644 index 0000000..43b5f24 --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php @@ -0,0 +1,41 @@ + + */ +class FactoryRuntimeLoader implements RuntimeLoaderInterface +{ + private $map; + + /** + * @param array $map An array where keys are class names and values factory callables + */ + public function __construct($map = []) + { + $this->map = $map; + } + + public function load($class) + { + if (isset($this->map[$class])) { + $runtimeFactory = $this->map[$class]; + + return $runtimeFactory(); + } + } +} + +class_alias('Twig\RuntimeLoader\FactoryRuntimeLoader', 'Twig_FactoryRuntimeLoader'); diff --git a/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php b/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php new file mode 100644 index 0000000..4eb5ad8 --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php @@ -0,0 +1,31 @@ + + */ +interface RuntimeLoaderInterface +{ + /** + * Creates the runtime implementation of a Twig element (filter/function/test). + * + * @param string $class A runtime class + * + * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class + */ + public function load($class); +} + +class_alias('Twig\RuntimeLoader\RuntimeLoaderInterface', 'Twig_RuntimeLoaderInterface'); diff --git a/vendor/twig/twig/src/Sandbox/SecurityError.php b/vendor/twig/twig/src/Sandbox/SecurityError.php new file mode 100644 index 0000000..5f96d46 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityError.php @@ -0,0 +1,25 @@ + + */ +class SecurityError extends Error +{ +} + +class_alias('Twig\Sandbox\SecurityError', 'Twig_Sandbox_SecurityError'); diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php new file mode 100644 index 0000000..fa0fdee --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php @@ -0,0 +1,35 @@ + + */ +class SecurityNotAllowedFilterError extends SecurityError +{ + private $filterName; + + public function __construct($message, $functionName, $lineno = -1, $filename = null, \Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->filterName = $functionName; + } + + public function getFilterName() + { + return $this->filterName; + } +} + +class_alias('Twig\Sandbox\SecurityNotAllowedFilterError', 'Twig_Sandbox_SecurityNotAllowedFilterError'); diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php new file mode 100644 index 0000000..8f23f93 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php @@ -0,0 +1,35 @@ + + */ +class SecurityNotAllowedFunctionError extends SecurityError +{ + private $functionName; + + public function __construct($message, $functionName, $lineno = -1, $filename = null, \Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->functionName = $functionName; + } + + public function getFunctionName() + { + return $this->functionName; + } +} + +class_alias('Twig\Sandbox\SecurityNotAllowedFunctionError', 'Twig_Sandbox_SecurityNotAllowedFunctionError'); diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php new file mode 100644 index 0000000..62e13f4 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php @@ -0,0 +1,42 @@ + + */ +class SecurityNotAllowedMethodError extends SecurityError +{ + private $className; + private $methodName; + + public function __construct($message, $className, $methodName, $lineno = -1, $filename = null, \Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->className = $className; + $this->methodName = $methodName; + } + + public function getClassName() + { + return $this->className; + } + + public function getMethodName() + { + return $this->methodName; + } +} + +class_alias('Twig\Sandbox\SecurityNotAllowedMethodError', 'Twig_Sandbox_SecurityNotAllowedMethodError'); diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php new file mode 100644 index 0000000..3bf5305 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php @@ -0,0 +1,42 @@ + + */ +class SecurityNotAllowedPropertyError extends SecurityError +{ + private $className; + private $propertyName; + + public function __construct($message, $className, $propertyName, $lineno = -1, $filename = null, \Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->className = $className; + $this->propertyName = $propertyName; + } + + public function getClassName() + { + return $this->className; + } + + public function getPropertyName() + { + return $this->propertyName; + } +} + +class_alias('Twig\Sandbox\SecurityNotAllowedPropertyError', 'Twig_Sandbox_SecurityNotAllowedPropertyError'); diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php new file mode 100644 index 0000000..de283b4 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php @@ -0,0 +1,35 @@ + + */ +class SecurityNotAllowedTagError extends SecurityError +{ + private $tagName; + + public function __construct($message, $tagName, $lineno = -1, $filename = null, \Exception $previous = null) + { + parent::__construct($message, $lineno, $filename, $previous); + $this->tagName = $tagName; + } + + public function getTagName() + { + return $this->tagName; + } +} + +class_alias('Twig\Sandbox\SecurityNotAllowedTagError', 'Twig_Sandbox_SecurityNotAllowedTagError'); diff --git a/vendor/twig/twig/src/Sandbox/SecurityPolicy.php b/vendor/twig/twig/src/Sandbox/SecurityPolicy.php new file mode 100644 index 0000000..31b6c34 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityPolicy.php @@ -0,0 +1,129 @@ + + */ +class SecurityPolicy implements SecurityPolicyInterface +{ + protected $allowedTags; + protected $allowedFilters; + protected $allowedMethods; + protected $allowedProperties; + protected $allowedFunctions; + + public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) + { + $this->allowedTags = $allowedTags; + $this->allowedFilters = $allowedFilters; + $this->setAllowedMethods($allowedMethods); + $this->allowedProperties = $allowedProperties; + $this->allowedFunctions = $allowedFunctions; + } + + public function setAllowedTags(array $tags) + { + $this->allowedTags = $tags; + } + + public function setAllowedFilters(array $filters) + { + $this->allowedFilters = $filters; + } + + public function setAllowedMethods(array $methods) + { + $this->allowedMethods = []; + foreach ($methods as $class => $m) { + $this->allowedMethods[$class] = array_map('strtolower', \is_array($m) ? $m : [$m]); + } + } + + public function setAllowedProperties(array $properties) + { + $this->allowedProperties = $properties; + } + + public function setAllowedFunctions(array $functions) + { + $this->allowedFunctions = $functions; + } + + public function checkSecurity($tags, $filters, $functions) + { + foreach ($tags as $tag) { + if (!\in_array($tag, $this->allowedTags)) { + throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag); + } + } + + foreach ($filters as $filter) { + if (!\in_array($filter, $this->allowedFilters)) { + throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter); + } + } + + foreach ($functions as $function) { + if (!\in_array($function, $this->allowedFunctions)) { + throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function); + } + } + } + + public function checkMethodAllowed($obj, $method) + { + if ($obj instanceof \Twig_TemplateInterface || $obj instanceof Markup) { + return; + } + + $allowed = false; + $method = strtolower($method); + foreach ($this->allowedMethods as $class => $methods) { + if ($obj instanceof $class) { + $allowed = \in_array($method, $methods); + + break; + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); + } + } + + public function checkPropertyAllowed($obj, $property) + { + $allowed = false; + foreach ($this->allowedProperties as $class => $properties) { + if ($obj instanceof $class) { + $allowed = \in_array($property, \is_array($properties) ? $properties : [$properties]); + + break; + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); + } + } +} + +class_alias('Twig\Sandbox\SecurityPolicy', 'Twig_Sandbox_SecurityPolicy'); diff --git a/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php b/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php new file mode 100644 index 0000000..d2d783d --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php @@ -0,0 +1,28 @@ + + */ +interface SecurityPolicyInterface +{ + public function checkSecurity($tags, $filters, $functions); + + public function checkMethodAllowed($obj, $method); + + public function checkPropertyAllowed($obj, $method); +} + +class_alias('Twig\Sandbox\SecurityPolicyInterface', 'Twig_Sandbox_SecurityPolicyInterface'); diff --git a/vendor/twig/twig/src/Source.php b/vendor/twig/twig/src/Source.php new file mode 100644 index 0000000..32a8216 --- /dev/null +++ b/vendor/twig/twig/src/Source.php @@ -0,0 +1,55 @@ + + */ +class Source +{ + private $code; + private $name; + private $path; + + /** + * @param string $code The template source code + * @param string $name The template logical name + * @param string $path The filesystem path of the template if any + */ + public function __construct($code, $name, $path = '') + { + $this->code = $code; + $this->name = $name; + $this->path = $path; + } + + public function getCode() + { + return $this->code; + } + + public function getName() + { + return $this->name; + } + + public function getPath() + { + return $this->path; + } +} + +class_alias('Twig\Source', 'Twig_Source'); diff --git a/vendor/twig/twig/src/Template.php b/vendor/twig/twig/src/Template.php new file mode 100644 index 0000000..e2146c2 --- /dev/null +++ b/vendor/twig/twig/src/Template.php @@ -0,0 +1,708 @@ +load() + * instead, which returns an instance of \Twig\TemplateWrapper. + * + * @author Fabien Potencier + * + * @internal + */ +abstract class Template implements \Twig_TemplateInterface +{ + /** + * @internal + */ + protected static $cache = []; + + protected $parent; + protected $parents = []; + protected $env; + protected $blocks = []; + protected $traits = []; + protected $sandbox; + + public function __construct(Environment $env) + { + $this->env = $env; + } + + /** + * @internal this method will be removed in 2.0 and is only used internally to provide an upgrade path from 1.x to 2.0 + */ + public function __toString() + { + return $this->getTemplateName(); + } + + /** + * Returns the template name. + * + * @return string The template name + */ + abstract public function getTemplateName(); + + /** + * Returns debug information about the template. + * + * @return array Debug information + */ + public function getDebugInfo() + { + return []; + } + + /** + * Returns the template source code. + * + * @return string The template source code + * + * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead + */ + public function getSource() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); + + return ''; + } + + /** + * Returns information about the original template source code. + * + * @return Source + */ + public function getSourceContext() + { + return new Source('', $this->getTemplateName()); + } + + /** + * @deprecated since 1.20 (to be removed in 2.0) + */ + public function getEnvironment() + { + @trigger_error('The '.__METHOD__.' method is deprecated since version 1.20 and will be removed in 2.0.', E_USER_DEPRECATED); + + return $this->env; + } + + /** + * Returns the parent template. + * + * This method is for internal use only and should never be called + * directly. + * + * @param array $context + * + * @return \Twig_TemplateInterface|TemplateWrapper|false The parent template or false if there is no parent + * + * @internal + */ + public function getParent(array $context) + { + if (null !== $this->parent) { + return $this->parent; + } + + try { + $parent = $this->doGetParent($context); + + if (false === $parent) { + return false; + } + + if ($parent instanceof self || $parent instanceof TemplateWrapper) { + return $this->parents[$parent->getSourceContext()->getName()] = $parent; + } + + if (!isset($this->parents[$parent])) { + $this->parents[$parent] = $this->loadTemplate($parent); + } + } catch (LoaderError $e) { + $e->setSourceContext(null); + $e->guess(); + + throw $e; + } + + return $this->parents[$parent]; + } + + protected function doGetParent(array $context) + { + return false; + } + + public function isTraitable() + { + return true; + } + + /** + * Displays a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + */ + public function displayParentBlock($name, array $context, array $blocks = []) + { + $name = (string) $name; + + if (isset($this->traits[$name])) { + $this->traits[$name][0]->displayBlock($name, $context, $blocks, false); + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, $blocks, false); + } else { + throw new RuntimeError(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); + } + } + + /** + * Displays a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + */ + public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true) + { + $name = (string) $name; + + if ($useBlocks && isset($blocks[$name])) { + $template = $blocks[$name][0]; + $block = $blocks[$name][1]; + } elseif (isset($this->blocks[$name])) { + $template = $this->blocks[$name][0]; + $block = $this->blocks[$name][1]; + } else { + $template = null; + $block = null; + } + + // avoid RCEs when sandbox is enabled + if (null !== $template && !$template instanceof self) { + throw new \LogicException('A block must be a method on a \Twig\Template instance.'); + } + + if (null !== $template) { + try { + $template->$block($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($template->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Exception $e) { + $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false); + } else { + @trigger_error(sprintf('Silent display of undefined block "%s" in template "%s" is deprecated since version 1.29 and will throw an exception in 2.0. Use the "block(\'%s\') is defined" expression to test for block existence.', $name, $this->getTemplateName(), $name), E_USER_DEPRECATED); + } + } + + /** + * Renders a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return string The rendered block + */ + public function renderParentBlock($name, array $context, array $blocks = []) + { + ob_start(); + $this->displayParentBlock($name, $context, $blocks); + + return ob_get_clean(); + } + + /** + * Renders a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + * + * @return string The rendered block + */ + public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true) + { + ob_start(); + $this->displayBlock($name, $context, $blocks, $useBlocks); + + return ob_get_clean(); + } + + /** + * Returns whether a block exists or not in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param string $name The block name + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return bool true if the block exists, false otherwise + */ + public function hasBlock($name, array $context = null, array $blocks = []) + { + if (null === $context) { + @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); + + return isset($this->blocks[(string) $name]); + } + + if (isset($blocks[$name])) { + return $blocks[$name][0] instanceof self; + } + + if (isset($this->blocks[$name])) { + return true; + } + + if (false !== $parent = $this->getParent($context)) { + return $parent->hasBlock($name, $context); + } + + return false; + } + + /** + * Returns all block names in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return array An array of block names + */ + public function getBlockNames(array $context = null, array $blocks = []) + { + if (null === $context) { + @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); + + return array_keys($this->blocks); + } + + $names = array_merge(array_keys($blocks), array_keys($this->blocks)); + + if (false !== $parent = $this->getParent($context)) { + $names = array_merge($names, $parent->getBlockNames($context)); + } + + return array_unique($names); + } + + protected function loadTemplate($template, $templateName = null, $line = null, $index = null) + { + try { + if (\is_array($template)) { + return $this->env->resolveTemplate($template); + } + + if ($template instanceof self || $template instanceof TemplateWrapper) { + return $template; + } + + if ($template === $this->getTemplateName()) { + $class = \get_class($this); + if (false !== $pos = strrpos($class, '___', -1)) { + $class = substr($class, 0, $pos); + } + + return $this->env->loadClass($class, $template, $index); + } + + return $this->env->loadTemplate($template, $index); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext()); + } + + if ($e->getTemplateLine() > 0) { + throw $e; + } + + if (!$line) { + $e->guess(); + } else { + $e->setTemplateLine($line); + } + + throw $e; + } + } + + /** + * Returns all blocks. + * + * This method is for internal use only and should never be called + * directly. + * + * @return array An array of blocks + */ + public function getBlocks() + { + return $this->blocks; + } + + public function display(array $context, array $blocks = []) + { + $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks)); + } + + public function render(array $context) + { + $level = ob_get_level(); + ob_start(); + try { + $this->display($context); + } catch (\Exception $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + protected function displayWithErrorHandling(array $context, array $blocks = []) + { + try { + $this->doDisplay($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Exception $e) { + $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } + + /** + * Auto-generated method to display the template with the given context. + * + * @param array $context An array of parameters to pass to the template + * @param array $blocks An array of blocks to pass to the template + */ + abstract protected function doDisplay(array $context, array $blocks = []); + + /** + * Returns a variable from the context. + * + * This method is for internal use only and should never be called + * directly. + * + * This method should not be overridden in a sub-class as this is an + * implementation detail that has been introduced to optimize variable + * access for versions of PHP before 5.4. This is not a way to override + * the way to get a variable value. + * + * @param array $context The context + * @param string $item The variable to return from the context + * @param bool $ignoreStrictCheck Whether to ignore the strict variable check or not + * + * @return mixed The content of the context variable + * + * @throws RuntimeError if the variable does not exist and Twig is running in strict mode + * + * @internal + */ + final protected function getContext($context, $item, $ignoreStrictCheck = false) + { + if (!\array_key_exists($item, $context)) { + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return; + } + + throw new RuntimeError(sprintf('Variable "%s" does not exist.', $item), -1, $this->getSourceContext()); + } + + return $context[$item]; + } + + /** + * Returns the attribute value for a given array/object. + * + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param string $type The type of attribute (@see \Twig\Template constants) + * @param bool $isDefinedTest Whether this is only a defined check + * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not + * + * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true + * + * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false + * + * @internal + */ + protected function getAttribute($object, $item, array $arguments = [], $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) + { + // array + if (self::METHOD_CALL !== $type) { + $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; + + if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, $object))) + || ($object instanceof \ArrayAccess && isset($object[$arrayItem])) + ) { + if ($isDefinedTest) { + return true; + } + + return $object[$arrayItem]; + } + + if (self::ARRAY_CALL === $type || !\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return; + } + + if ($object instanceof \ArrayAccess) { + $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); + } elseif (\is_object($object)) { + $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); + } elseif (\is_array($object)) { + if (empty($object)) { + $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); + } else { + $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (self::ARRAY_CALL === $type) { + if (null === $object) { + $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + } elseif (null === $object) { + $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, -1, $this->getSourceContext()); + } + } + + if (!\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return; + } + + if (null === $object) { + $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); + } elseif (\is_array($object)) { + $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); + } else { + $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, -1, $this->getSourceContext()); + } + + // object property + if (self::METHOD_CALL !== $type && !$object instanceof self) { // \Twig\Template does not have public properties, and we don't want to allow access to internal ones + if (isset($object->$item) || \array_key_exists((string) $item, $object)) { + if ($isDefinedTest) { + return true; + } + + if ($this->env->hasExtension('\Twig\Extension\SandboxExtension')) { + $this->env->getExtension('\Twig\Extension\SandboxExtension')->checkPropertyAllowed($object, $item); + } + + return $object->$item; + } + } + + $class = \get_class($object); + + // object method + if (!isset(self::$cache[$class])) { + // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates + if ($object instanceof self) { + $ref = new \ReflectionClass($class); + $methods = []; + + foreach ($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) { + // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment + if ('getenvironment' !== strtolower($refMethod->name)) { + $methods[] = $refMethod->name; + } + } + } else { + $methods = get_class_methods($object); + } + // sort values to have consistent behavior, so that "get" methods win precedence over "is" methods + sort($methods); + + $cache = []; + + foreach ($methods as $method) { + $cache[$method] = $method; + $cache[$lcName = strtolower($method)] = $method; + + if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { + $name = substr($method, 2); + $lcName = substr($lcName, 2); + } else { + continue; + } + + // skip get() and is() methods (in which case, $name is empty) + if ($name) { + if (!isset($cache[$name])) { + $cache[$name] = $method; + } + if (!isset($cache[$lcName])) { + $cache[$lcName] = $method; + } + } + } + self::$cache[$class] = $cache; + } + + $call = false; + if (isset(self::$cache[$class][$item])) { + $method = self::$cache[$class][$item]; + } elseif (isset(self::$cache[$class][$lcItem = strtolower($item)])) { + $method = self::$cache[$class][$lcItem]; + } elseif (isset(self::$cache[$class]['__call'])) { + $method = $item; + $call = true; + } else { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { + return; + } + + throw new RuntimeError(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), -1, $this->getSourceContext()); + } + + if ($isDefinedTest) { + return true; + } + + if ($this->env->hasExtension('\Twig\Extension\SandboxExtension')) { + $this->env->getExtension('\Twig\Extension\SandboxExtension')->checkMethodAllowed($object, $method); + } + + // Some objects throw exceptions when they have __call, and the method we try + // to call is not supported. If ignoreStrictCheck is true, we should return null. + try { + if (!$arguments) { + $ret = $object->$method(); + } else { + $ret = \call_user_func_array([$object, $method], $arguments); + } + } catch (\BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { + return; + } + throw $e; + } + + // @deprecated in 1.28 + if ($object instanceof \Twig_TemplateInterface) { + $self = $object->getTemplateName() === $this->getTemplateName(); + $message = sprintf('Calling "%s" on template "%s" from template "%s" is deprecated since version 1.28 and won\'t be supported anymore in 2.0.', $item, $object->getTemplateName(), $this->getTemplateName()); + if ('renderBlock' === $method || 'displayBlock' === $method) { + $message .= sprintf(' Use block("%s"%s) instead).', $arguments[0], $self ? '' : ', template'); + } elseif ('hasBlock' === $method) { + $message .= sprintf(' Use "block("%s"%s) is defined" instead).', $arguments[0], $self ? '' : ', template'); + } elseif ('render' === $method || 'display' === $method) { + $message .= sprintf(' Use include("%s") instead).', $object->getTemplateName()); + } + @trigger_error($message, E_USER_DEPRECATED); + + return '' === $ret ? '' : new Markup($ret, $this->env->getCharset()); + } + + return $ret; + } +} + +class_alias('Twig\Template', 'Twig_Template'); diff --git a/vendor/twig/twig/src/TemplateWrapper.php b/vendor/twig/twig/src/TemplateWrapper.php new file mode 100644 index 0000000..5ddee92 --- /dev/null +++ b/vendor/twig/twig/src/TemplateWrapper.php @@ -0,0 +1,147 @@ + + */ +final class TemplateWrapper +{ + private $env; + private $template; + + /** + * This method is for internal use only and should never be called + * directly (use Twig\Environment::load() instead). + * + * @internal + */ + public function __construct(Environment $env, Template $template) + { + $this->env = $env; + $this->template = $template; + } + + /** + * Renders the template. + * + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered template + */ + public function render($context = []) + { + // using func_get_args() allows to not expose the blocks argument + // as it should only be used by internal code + return $this->template->render($context, \func_num_args() > 1 ? func_get_arg(1) : []); + } + + /** + * Displays the template. + * + * @param array $context An array of parameters to pass to the template + */ + public function display($context = []) + { + // using func_get_args() allows to not expose the blocks argument + // as it should only be used by internal code + $this->template->display($context, \func_num_args() > 1 ? func_get_arg(1) : []); + } + + /** + * Checks if a block is defined. + * + * @param string $name The block name + * @param array $context An array of parameters to pass to the template + * + * @return bool + */ + public function hasBlock($name, $context = []) + { + return $this->template->hasBlock($name, $context); + } + + /** + * Returns defined block names in the template. + * + * @param array $context An array of parameters to pass to the template + * + * @return string[] An array of defined template block names + */ + public function getBlockNames($context = []) + { + return $this->template->getBlockNames($context); + } + + /** + * Renders a template block. + * + * @param string $name The block name to render + * @param array $context An array of parameters to pass to the template + * + * @return string The rendered block + */ + public function renderBlock($name, $context = []) + { + $context = $this->env->mergeGlobals($context); + $level = ob_get_level(); + ob_start(); + try { + $this->template->displayBlock($name, $context); + } catch (\Exception $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + /** + * Displays a template block. + * + * @param string $name The block name to render + * @param array $context An array of parameters to pass to the template + */ + public function displayBlock($name, $context = []) + { + $this->template->displayBlock($name, $this->env->mergeGlobals($context)); + } + + /** + * @return Source + */ + public function getSourceContext() + { + return $this->template->getSourceContext(); + } + + /** + * @return string + */ + public function getTemplatename() + { + return $this->template->getTemplateName(); + } +} + +class_alias('Twig\TemplateWrapper', 'Twig_TemplateWrapper'); diff --git a/vendor/twig/twig/src/Test/IntegrationTestCase.php b/vendor/twig/twig/src/Test/IntegrationTestCase.php new file mode 100644 index 0000000..36b3607 --- /dev/null +++ b/vendor/twig/twig/src/Test/IntegrationTestCase.php @@ -0,0 +1,257 @@ + + * @author Karma Dordrak + */ +abstract class IntegrationTestCase extends TestCase +{ + /** + * @return string + */ + abstract protected function getFixturesDir(); + + /** + * @return RuntimeLoaderInterface[] + */ + protected function getRuntimeLoaders() + { + return []; + } + + /** + * @return ExtensionInterface[] + */ + protected function getExtensions() + { + return []; + } + + /** + * @return TwigFilter[] + */ + protected function getTwigFilters() + { + return []; + } + + /** + * @return TwigFunction[] + */ + protected function getTwigFunctions() + { + return []; + } + + /** + * @return TwigTest[] + */ + protected function getTwigTests() + { + return []; + } + + /** + * @dataProvider getTests + */ + public function testIntegration($file, $message, $condition, $templates, $exception, $outputs) + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs); + } + + /** + * @dataProvider getLegacyTests + * @group legacy + */ + public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs) + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs); + } + + public function getTests($name, $legacyTests = false) + { + $fixturesDir = realpath($this->getFixturesDir()); + $tests = []; + + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if (!preg_match('/\.test$/', $file)) { + continue; + } + + if ($legacyTests xor false !== strpos($file->getRealpath(), '.legacy.test')) { + continue; + } + + $test = file_get_contents($file->getRealpath()); + + if (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $templates = self::parseTemplates($match[3]); + $exception = $match[5]; + $outputs = [[null, $match[4], null, '']]; + } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $templates = self::parseTemplates($match[3]); + $exception = false; + preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER); + } else { + throw new \InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); + } + + $tests[] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs]; + } + + if ($legacyTests && empty($tests)) { + // add a dummy test to avoid a PHPUnit message + return [['not', '-', '', [], '', []]]; + } + + return $tests; + } + + public function getLegacyTests() + { + return $this->getTests('testLegacyIntegration', true); + } + + protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs) + { + if (!$outputs) { + $this->markTestSkipped('no tests to run'); + } + + if ($condition) { + eval('$ret = '.$condition.';'); + if (!$ret) { + $this->markTestSkipped($condition); + } + } + + $loader = new ArrayLoader($templates); + + foreach ($outputs as $i => $match) { + $config = array_merge([ + 'cache' => false, + 'strict_variables' => true, + ], $match[2] ? eval($match[2].';') : []); + $twig = new Environment($loader, $config); + $twig->addGlobal('global', 'global'); + foreach ($this->getRuntimeLoaders() as $runtimeLoader) { + $twig->addRuntimeLoader($runtimeLoader); + } + + foreach ($this->getExtensions() as $extension) { + $twig->addExtension($extension); + } + + foreach ($this->getTwigFilters() as $filter) { + $twig->addFilter($filter); + } + + foreach ($this->getTwigTests() as $test) { + $twig->addTest($test); + } + + foreach ($this->getTwigFunctions() as $function) { + $twig->addFunction($function); + } + + $p = new \ReflectionProperty($twig, 'templateClassPrefix'); + $p->setAccessible(true); + $p->setValue($twig, '__TwigTemplate_'.hash('sha256', uniqid(mt_rand(), true), false).'_'); + + try { + $template = $twig->load('index.twig'); + } catch (\Exception $e) { + if (false !== $exception) { + $message = $e->getMessage(); + $this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $message))); + $last = substr($message, \strlen($message) - 1); + $this->assertTrue('.' === $last || '?' === $last, 'Exception message must end with a dot or a question mark.'); + + return; + } + + throw new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + } + + try { + $output = trim($template->render(eval($match[1].';')), "\n "); + } catch (\Exception $e) { + if (false !== $exception) { + $this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $e->getMessage()))); + + return; + } + + $e = new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + + $output = trim(sprintf('%s: %s', \get_class($e), $e->getMessage())); + } + + if (false !== $exception) { + list($class) = explode(':', $exception); + $constraintClass = class_exists('PHPUnit\Framework\Constraint\Exception') ? 'PHPUnit\Framework\Constraint\Exception' : 'PHPUnit_Framework_Constraint_Exception'; + $this->assertThat(null, new $constraintClass($class)); + } + + $expected = trim($match[3], "\n "); + + if ($expected !== $output) { + printf("Compiled templates that failed on case %d:\n", $i + 1); + + foreach (array_keys($templates) as $name) { + echo "Template: $name\n"; + $loader = $twig->getLoader(); + if (!$loader instanceof SourceContextLoaderInterface) { + $source = new Source($loader->getSource($name), $name); + } else { + $source = $loader->getSourceContext($name); + } + echo $twig->compile($twig->parse($twig->tokenize($source))); + } + } + $this->assertEquals($expected, $output, $message.' (in '.$file.')'); + } + } + + protected static function parseTemplates($test) + { + $templates = []; + preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2]; + } + + return $templates; + } +} + +class_alias('Twig\Test\IntegrationTestCase', 'Twig_Test_IntegrationTestCase'); diff --git a/vendor/twig/twig/src/Test/NodeTestCase.php b/vendor/twig/twig/src/Test/NodeTestCase.php new file mode 100644 index 0000000..f3358cb --- /dev/null +++ b/vendor/twig/twig/src/Test/NodeTestCase.php @@ -0,0 +1,79 @@ +assertNodeCompilation($source, $node, $environment, $isPattern); + } + + public function assertNodeCompilation($source, Node $node, Environment $environment = null, $isPattern = false) + { + $compiler = $this->getCompiler($environment); + $compiler->compile($node); + + if ($isPattern) { + $this->assertStringMatchesFormat($source, trim($compiler->getSource())); + } else { + $this->assertEquals($source, trim($compiler->getSource())); + } + } + + protected function getCompiler(Environment $environment = null) + { + return new Compiler(null === $environment ? $this->getEnvironment() : $environment); + } + + protected function getEnvironment() + { + return new Environment(new ArrayLoader([])); + } + + protected function getVariableGetter($name, $line = false) + { + $line = $line > 0 ? "// line {$line}\n" : ''; + + if (\PHP_VERSION_ID >= 70000) { + return sprintf('%s($context["%s"] ?? null)', $line, $name); + } + + if (\PHP_VERSION_ID >= 50400) { + return sprintf('%s(isset($context["%s"]) ? $context["%s"] : null)', $line, $name, $name); + } + + return sprintf('%s$this->getContext($context, "%s")', $line, $name); + } + + protected function getAttributeGetter() + { + if (\function_exists('twig_template_get_attributes')) { + return 'twig_template_get_attributes($this, '; + } + + return '$this->getAttribute('; + } +} + +class_alias('Twig\Test\NodeTestCase', 'Twig_Test_NodeTestCase'); diff --git a/vendor/twig/twig/src/Token.php b/vendor/twig/twig/src/Token.php new file mode 100644 index 0000000..9338972 --- /dev/null +++ b/vendor/twig/twig/src/Token.php @@ -0,0 +1,209 @@ + + * + * @final + */ +class Token +{ + protected $value; + protected $type; + protected $lineno; + + const EOF_TYPE = -1; + const TEXT_TYPE = 0; + const BLOCK_START_TYPE = 1; + const VAR_START_TYPE = 2; + const BLOCK_END_TYPE = 3; + const VAR_END_TYPE = 4; + const NAME_TYPE = 5; + const NUMBER_TYPE = 6; + const STRING_TYPE = 7; + const OPERATOR_TYPE = 8; + const PUNCTUATION_TYPE = 9; + const INTERPOLATION_START_TYPE = 10; + const INTERPOLATION_END_TYPE = 11; + + /** + * @param int $type The type of the token + * @param string $value The token value + * @param int $lineno The line position in the source + */ + public function __construct($type, $value, $lineno) + { + $this->type = $type; + $this->value = $value; + $this->lineno = $lineno; + } + + public function __toString() + { + return sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); + } + + /** + * Tests the current token for a type and/or a value. + * + * Parameters may be: + * * just type + * * type and value (or array of possible values) + * * just value (or array of possible values) (NAME_TYPE is used as type) + * + * @param array|string|int $type The type to test + * @param array|string|null $values The token value + * + * @return bool + */ + public function test($type, $values = null) + { + if (null === $values && !\is_int($type)) { + $values = $type; + $type = self::NAME_TYPE; + } + + return ($this->type === $type) && ( + null === $values || + (\is_array($values) && \in_array($this->value, $values)) || + $this->value == $values + ); + } + + /** + * @return int + */ + public function getLine() + { + return $this->lineno; + } + + /** + * @return int + */ + public function getType() + { + return $this->type; + } + + /** + * @return string + */ + public function getValue() + { + return $this->value; + } + + /** + * Returns the constant representation (internal) of a given type. + * + * @param int $type The type as an integer + * @param bool $short Whether to return a short representation or not + * + * @return string The string representation + */ + public static function typeToString($type, $short = false) + { + switch ($type) { + case self::EOF_TYPE: + $name = 'EOF_TYPE'; + break; + case self::TEXT_TYPE: + $name = 'TEXT_TYPE'; + break; + case self::BLOCK_START_TYPE: + $name = 'BLOCK_START_TYPE'; + break; + case self::VAR_START_TYPE: + $name = 'VAR_START_TYPE'; + break; + case self::BLOCK_END_TYPE: + $name = 'BLOCK_END_TYPE'; + break; + case self::VAR_END_TYPE: + $name = 'VAR_END_TYPE'; + break; + case self::NAME_TYPE: + $name = 'NAME_TYPE'; + break; + case self::NUMBER_TYPE: + $name = 'NUMBER_TYPE'; + break; + case self::STRING_TYPE: + $name = 'STRING_TYPE'; + break; + case self::OPERATOR_TYPE: + $name = 'OPERATOR_TYPE'; + break; + case self::PUNCTUATION_TYPE: + $name = 'PUNCTUATION_TYPE'; + break; + case self::INTERPOLATION_START_TYPE: + $name = 'INTERPOLATION_START_TYPE'; + break; + case self::INTERPOLATION_END_TYPE: + $name = 'INTERPOLATION_END_TYPE'; + break; + default: + throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type)); + } + + return $short ? $name : 'Twig\Token::'.$name; + } + + /** + * Returns the English representation of a given type. + * + * @param int $type The type as an integer + * + * @return string The string representation + */ + public static function typeToEnglish($type) + { + switch ($type) { + case self::EOF_TYPE: + return 'end of template'; + case self::TEXT_TYPE: + return 'text'; + case self::BLOCK_START_TYPE: + return 'begin of statement block'; + case self::VAR_START_TYPE: + return 'begin of print statement'; + case self::BLOCK_END_TYPE: + return 'end of statement block'; + case self::VAR_END_TYPE: + return 'end of print statement'; + case self::NAME_TYPE: + return 'name'; + case self::NUMBER_TYPE: + return 'number'; + case self::STRING_TYPE: + return 'string'; + case self::OPERATOR_TYPE: + return 'operator'; + case self::PUNCTUATION_TYPE: + return 'punctuation'; + case self::INTERPOLATION_START_TYPE: + return 'begin of string interpolation'; + case self::INTERPOLATION_END_TYPE: + return 'end of string interpolation'; + default: + throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type)); + } + } +} + +class_alias('Twig\Token', 'Twig_Token'); diff --git a/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php b/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php new file mode 100644 index 0000000..fc8c11a --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php @@ -0,0 +1,31 @@ + + */ +abstract class AbstractTokenParser implements TokenParserInterface +{ + protected $parser; + + public function setParser(Parser $parser) + { + $this->parser = $parser; + } +} + +class_alias('Twig\TokenParser\AbstractTokenParser', 'Twig_TokenParser'); diff --git a/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php b/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php new file mode 100644 index 0000000..2cd0cc6 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php @@ -0,0 +1,88 @@ +getLine(); + $stream = $this->parser->getStream(); + + if ($stream->test(Token::BLOCK_END_TYPE)) { + $value = 'html'; + } else { + $expr = $this->parser->getExpressionParser()->parseExpression(); + if (!$expr instanceof ConstantExpression) { + throw new SyntaxError('An escaping strategy must be a string or a bool.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $value = $expr->getAttribute('value'); + + $compat = true === $value || false === $value; + + if (true === $value) { + $value = 'html'; + } + + if ($compat && $stream->test(Token::NAME_TYPE)) { + @trigger_error('Using the autoescape tag with "true" or "false" before the strategy name is deprecated since version 1.21.', E_USER_DEPRECATED); + + if (false === $value) { + throw new SyntaxError('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $value = $stream->next()->getValue(); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(Token::BLOCK_END_TYPE); + + return new AutoEscapeNode($value, $body, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token) + { + return $token->test('endautoescape'); + } + + public function getTag() + { + return 'autoescape'; + } +} + +class_alias('Twig\TokenParser\AutoEscapeTokenParser', 'Twig_TokenParser_AutoEscape'); diff --git a/vendor/twig/twig/src/TokenParser/BlockTokenParser.php b/vendor/twig/twig/src/TokenParser/BlockTokenParser.php new file mode 100644 index 0000000..caf11f0 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/BlockTokenParser.php @@ -0,0 +1,80 @@ + + * {% block title %}{% endblock %} - My Webpage + * {% endblock %} + * + * @final + */ +class BlockTokenParser extends AbstractTokenParser +{ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + if ($this->parser->hasBlock($name)) { + throw new SyntaxError(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $this->parser->setBlock($name, $block = new BlockNode($name, new Node([]), $lineno)); + $this->parser->pushLocalScope(); + $this->parser->pushBlockStack($name); + + if ($stream->nextIf(Token::BLOCK_END_TYPE)) { + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(Token::NAME_TYPE)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + } else { + $body = new Node([ + new PrintNode($this->parser->getExpressionParser()->parseExpression(), $lineno), + ]); + } + $stream->expect(Token::BLOCK_END_TYPE); + + $block->setNode('body', $body); + $this->parser->popBlockStack(); + $this->parser->popLocalScope(); + + return new BlockReferenceNode($name, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token) + { + return $token->test('endblock'); + } + + public function getTag() + { + return 'block'; + } +} + +class_alias('Twig\TokenParser\BlockTokenParser', 'Twig_TokenParser_Block'); diff --git a/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php b/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php new file mode 100644 index 0000000..6575cff --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php @@ -0,0 +1,44 @@ + + * + * @final + */ +class DeprecatedTokenParser extends AbstractTokenParser +{ + public function parse(Token $token) + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new DeprecatedNode($expr, $token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'deprecated'; + } +} + +class_alias('Twig\TokenParser\DeprecatedTokenParser', 'Twig_TokenParser_Deprecated'); diff --git a/vendor/twig/twig/src/TokenParser/DoTokenParser.php b/vendor/twig/twig/src/TokenParser/DoTokenParser.php new file mode 100644 index 0000000..e1eae10 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/DoTokenParser.php @@ -0,0 +1,39 @@ +parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new DoNode($expr, $token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'do'; + } +} + +class_alias('Twig\TokenParser\DoTokenParser', 'Twig_TokenParser_Do'); diff --git a/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php b/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php new file mode 100644 index 0000000..973ff2e --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php @@ -0,0 +1,74 @@ +parser->getStream(); + + $parent = $this->parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + $parentToken = $fakeParentToken = new Token(Token::STRING_TYPE, '__parent__', $token->getLine()); + if ($parent instanceof ConstantExpression) { + $parentToken = new Token(Token::STRING_TYPE, $parent->getAttribute('value'), $token->getLine()); + } elseif ($parent instanceof NameExpression) { + $parentToken = new Token(Token::NAME_TYPE, $parent->getAttribute('name'), $token->getLine()); + } + + // inject a fake parent to make the parent() function work + $stream->injectTokens([ + new Token(Token::BLOCK_START_TYPE, '', $token->getLine()), + new Token(Token::NAME_TYPE, 'extends', $token->getLine()), + $parentToken, + new Token(Token::BLOCK_END_TYPE, '', $token->getLine()), + ]); + + $module = $this->parser->parse($stream, [$this, 'decideBlockEnd'], true); + + // override the parent with the correct one + if ($fakeParentToken === $parentToken) { + $module->setNode('parent', $parent); + } + + $this->parser->embedTemplate($module); + + $stream->expect(Token::BLOCK_END_TYPE); + + return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Token $token) + { + return $token->test('endembed'); + } + + public function getTag() + { + return 'embed'; + } +} + +class_alias('Twig\TokenParser\EmbedTokenParser', 'Twig_TokenParser_Embed'); diff --git a/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php b/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php new file mode 100644 index 0000000..74f129c --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php @@ -0,0 +1,49 @@ +parser->getStream(); + + if (!$this->parser->isMainScope()) { + throw new SyntaxError('Cannot extend from a block.', $token->getLine(), $stream->getSourceContext()); + } + + if (null !== $this->parser->getParent()) { + throw new SyntaxError('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()); + } + $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); + + $stream->expect(Token::BLOCK_END_TYPE); + } + + public function getTag() + { + return 'extends'; + } +} + +class_alias('Twig\TokenParser\ExtendsTokenParser', 'Twig_TokenParser_Extends'); diff --git a/vendor/twig/twig/src/TokenParser/FilterTokenParser.php b/vendor/twig/twig/src/TokenParser/FilterTokenParser.php new file mode 100644 index 0000000..dc07dcf --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/FilterTokenParser.php @@ -0,0 +1,59 @@ +parser->getVarName(); + $ref = new BlockReferenceExpression(new ConstantExpression($name, $token->getLine()), null, $token->getLine(), $this->getTag()); + + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + $block = new BlockNode($name, $body, $token->getLine()); + $this->parser->setBlock($name, $block); + + return new PrintNode($filter, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Token $token) + { + return $token->test('endfilter'); + } + + public function getTag() + { + return 'filter'; + } +} + +class_alias('Twig\TokenParser\FilterTokenParser', 'Twig_TokenParser_Filter'); diff --git a/vendor/twig/twig/src/TokenParser/FlushTokenParser.php b/vendor/twig/twig/src/TokenParser/FlushTokenParser.php new file mode 100644 index 0000000..b25524f --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/FlushTokenParser.php @@ -0,0 +1,39 @@ +parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new FlushNode($token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'flush'; + } +} + +class_alias('Twig\TokenParser\FlushTokenParser', 'Twig_TokenParser_Flush'); diff --git a/vendor/twig/twig/src/TokenParser/ForTokenParser.php b/vendor/twig/twig/src/TokenParser/ForTokenParser.php new file mode 100644 index 0000000..69278d9 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ForTokenParser.php @@ -0,0 +1,136 @@ + + * {% for user in users %} + *
  • {{ user.username|e }}
  • + * {% endfor %} + * + * + * @final + */ +class ForTokenParser extends AbstractTokenParser +{ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); + $stream->expect(Token::OPERATOR_TYPE, 'in'); + $seq = $this->parser->getExpressionParser()->parseExpression(); + + $ifexpr = null; + if ($stream->nextIf(Token::NAME_TYPE, 'if')) { + $ifexpr = $this->parser->getExpressionParser()->parseExpression(); + } + + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideForFork']); + if ('else' == $stream->next()->getValue()) { + $stream->expect(Token::BLOCK_END_TYPE); + $else = $this->parser->subparse([$this, 'decideForEnd'], true); + } else { + $else = null; + } + $stream->expect(Token::BLOCK_END_TYPE); + + if (\count($targets) > 1) { + $keyTarget = $targets->getNode(0); + $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); + $valueTarget = $targets->getNode(1); + $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); + } else { + $keyTarget = new AssignNameExpression('_key', $lineno); + $valueTarget = $targets->getNode(0); + $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); + } + + if ($ifexpr) { + $this->checkLoopUsageCondition($stream, $ifexpr); + $this->checkLoopUsageBody($stream, $body); + } + + return new ForNode($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag()); + } + + public function decideForFork(Token $token) + { + return $token->test(['else', 'endfor']); + } + + public function decideForEnd(Token $token) + { + return $token->test('endfor'); + } + + // the loop variable cannot be used in the condition + protected function checkLoopUsageCondition(TokenStream $stream, \Twig_NodeInterface $node) + { + if ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression && 'loop' == $node->getNode('node')->getAttribute('name')) { + throw new SyntaxError('The "loop" variable cannot be used in a looping condition.', $node->getTemplateLine(), $stream->getSourceContext()); + } + + foreach ($node as $n) { + if (!$n) { + continue; + } + + $this->checkLoopUsageCondition($stream, $n); + } + } + + // check usage of non-defined loop-items + // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include) + protected function checkLoopUsageBody(TokenStream $stream, \Twig_NodeInterface $node) + { + if ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression && 'loop' == $node->getNode('node')->getAttribute('name')) { + $attribute = $node->getNode('attribute'); + if ($attribute instanceof ConstantExpression && \in_array($attribute->getAttribute('value'), ['length', 'revindex0', 'revindex', 'last'])) { + throw new SyntaxError(sprintf('The "loop.%s" variable is not defined when looping with a condition.', $attribute->getAttribute('value')), $node->getTemplateLine(), $stream->getSourceContext()); + } + } + + // should check for parent.loop.XXX usage + if ($node instanceof ForNode) { + return; + } + + foreach ($node as $n) { + if (!$n) { + continue; + } + + $this->checkLoopUsageBody($stream, $n); + } + } + + public function getTag() + { + return 'for'; + } +} + +class_alias('Twig\TokenParser\ForTokenParser', 'Twig_TokenParser_For'); diff --git a/vendor/twig/twig/src/TokenParser/FromTokenParser.php b/vendor/twig/twig/src/TokenParser/FromTokenParser.php new file mode 100644 index 0000000..a47f197 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/FromTokenParser.php @@ -0,0 +1,71 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect('import'); + + $targets = []; + do { + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(Token::NAME_TYPE)->getValue(); + } + + $targets[$name] = $alias; + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } while (true); + + $stream->expect(Token::BLOCK_END_TYPE); + + $node = new ImportNode($macro, new AssignNameExpression($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag()); + + foreach ($targets as $name => $alias) { + if ($this->parser->isReservedMacroName($name)) { + throw new SyntaxError(sprintf('"%s" cannot be an imported macro as it is a reserved keyword.', $name), $token->getLine(), $stream->getSourceContext()); + } + + $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var')); + } + + return $node; + } + + public function getTag() + { + return 'from'; + } +} + +class_alias('Twig\TokenParser\FromTokenParser', 'Twig_TokenParser_From'); diff --git a/vendor/twig/twig/src/TokenParser/IfTokenParser.php b/vendor/twig/twig/src/TokenParser/IfTokenParser.php new file mode 100644 index 0000000..2631a20 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/IfTokenParser.php @@ -0,0 +1,91 @@ + + * {% for user in users %} + *
  • {{ user.username|e }}
  • + * {% endfor %} + * + * {% endif %} + * + * @final + */ +class IfTokenParser extends AbstractTokenParser +{ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $tests = [$expr, $body]; + $else = null; + + $end = false; + while (!$end) { + switch ($stream->next()->getValue()) { + case 'else': + $stream->expect(Token::BLOCK_END_TYPE); + $else = $this->parser->subparse([$this, 'decideIfEnd']); + break; + + case 'elseif': + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $tests[] = $expr; + $tests[] = $body; + break; + + case 'endif': + $end = true; + break; + + default: + throw new SyntaxError(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return new IfNode(new Node($tests), $else, $lineno, $this->getTag()); + } + + public function decideIfFork(Token $token) + { + return $token->test(['elseif', 'else', 'endif']); + } + + public function decideIfEnd(Token $token) + { + return $token->test(['endif']); + } + + public function getTag() + { + return 'if'; + } +} + +class_alias('Twig\TokenParser\IfTokenParser', 'Twig_TokenParser_If'); diff --git a/vendor/twig/twig/src/TokenParser/ImportTokenParser.php b/vendor/twig/twig/src/TokenParser/ImportTokenParser.php new file mode 100644 index 0000000..317e0a5 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ImportTokenParser.php @@ -0,0 +1,45 @@ +parser->getExpressionParser()->parseExpression(); + $this->parser->getStream()->expect('as'); + $var = new AssignNameExpression($this->parser->getStream()->expect(Token::NAME_TYPE)->getValue(), $token->getLine()); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + $this->parser->addImportedSymbol('template', $var->getAttribute('name')); + + return new ImportNode($macro, $var, $token->getLine(), $this->getTag()); + } + + public function getTag() + { + return 'import'; + } +} + +class_alias('Twig\TokenParser\ImportTokenParser', 'Twig_TokenParser_Import'); diff --git a/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php b/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php new file mode 100644 index 0000000..57aa4cf --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php @@ -0,0 +1,68 @@ +parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + protected function parseArguments() + { + $stream = $this->parser->getStream(); + + $ignoreMissing = false; + if ($stream->nextIf(Token::NAME_TYPE, 'ignore')) { + $stream->expect(Token::NAME_TYPE, 'missing'); + + $ignoreMissing = true; + } + + $variables = null; + if ($stream->nextIf(Token::NAME_TYPE, 'with')) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + } + + $only = false; + if ($stream->nextIf(Token::NAME_TYPE, 'only')) { + $only = true; + } + + $stream->expect(Token::BLOCK_END_TYPE); + + return [$variables, $only, $ignoreMissing]; + } + + public function getTag() + { + return 'include'; + } +} + +class_alias('Twig\TokenParser\IncludeTokenParser', 'Twig_TokenParser_Include'); diff --git a/vendor/twig/twig/src/TokenParser/MacroTokenParser.php b/vendor/twig/twig/src/TokenParser/MacroTokenParser.php new file mode 100644 index 0000000..734ebc6 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/MacroTokenParser.php @@ -0,0 +1,65 @@ + + * {% endmacro %} + * + * @final + */ +class MacroTokenParser extends AbstractTokenParser +{ + public function parse(Token $token) + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); + + $stream->expect(Token::BLOCK_END_TYPE); + $this->parser->pushLocalScope(); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(Token::NAME_TYPE)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + $this->parser->popLocalScope(); + $stream->expect(Token::BLOCK_END_TYPE); + + $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $this->getTag())); + } + + public function decideBlockEnd(Token $token) + { + return $token->test('endmacro'); + } + + public function getTag() + { + return 'macro'; + } +} + +class_alias('Twig\TokenParser\MacroTokenParser', 'Twig_TokenParser_Macro'); diff --git a/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php b/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php new file mode 100644 index 0000000..0f3ad9e --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php @@ -0,0 +1,67 @@ +parser->getStream(); + $stream->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(Token::BLOCK_END_TYPE); + + // in a sandbox tag, only include tags are allowed + if (!$body instanceof IncludeNode) { + foreach ($body as $node) { + if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { + continue; + } + + if (!$node instanceof IncludeNode) { + throw new SyntaxError('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()); + } + } + } + + return new SandboxNode($body, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Token $token) + { + return $token->test('endsandbox'); + } + + public function getTag() + { + return 'sandbox'; + } +} + +class_alias('Twig\TokenParser\SandboxTokenParser', 'Twig_TokenParser_Sandbox'); diff --git a/vendor/twig/twig/src/TokenParser/SetTokenParser.php b/vendor/twig/twig/src/TokenParser/SetTokenParser.php new file mode 100644 index 0000000..eebebc6 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/SetTokenParser.php @@ -0,0 +1,74 @@ +getLine(); + $stream = $this->parser->getStream(); + $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); + + $capture = false; + if ($stream->nextIf(Token::OPERATOR_TYPE, '=')) { + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + + $stream->expect(Token::BLOCK_END_TYPE); + + if (\count($names) !== \count($values)) { + throw new SyntaxError('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } else { + $capture = true; + + if (\count($names) > 1) { + throw new SyntaxError('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $values = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(Token::BLOCK_END_TYPE); + } + + return new SetNode($capture, $names, $values, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token) + { + return $token->test('endset'); + } + + public function getTag() + { + return 'set'; + } +} + +class_alias('Twig\TokenParser\SetTokenParser', 'Twig_TokenParser_Set'); diff --git a/vendor/twig/twig/src/TokenParser/SpacelessTokenParser.php b/vendor/twig/twig/src/TokenParser/SpacelessTokenParser.php new file mode 100644 index 0000000..5b5656b --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/SpacelessTokenParser.php @@ -0,0 +1,53 @@ + + * foo + * + * {% endspaceless %} + * {# output will be
    foo
    #} + * + * @final + */ +class SpacelessTokenParser extends AbstractTokenParser +{ + public function parse(Token $token) + { + $lineno = $token->getLine(); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideSpacelessEnd'], true); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new SpacelessNode($body, $lineno, $this->getTag()); + } + + public function decideSpacelessEnd(Token $token) + { + return $token->test('endspaceless'); + } + + public function getTag() + { + return 'spaceless'; + } +} + +class_alias('Twig\TokenParser\SpacelessTokenParser', 'Twig_TokenParser_Spaceless'); diff --git a/vendor/twig/twig/src/TokenParser/TokenParserInterface.php b/vendor/twig/twig/src/TokenParser/TokenParserInterface.php new file mode 100644 index 0000000..4b603b2 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/TokenParserInterface.php @@ -0,0 +1,51 @@ + + */ +interface TokenParserInterface +{ + /** + * Sets the parser associated with this token parser. + */ + public function setParser(Parser $parser); + + /** + * Parses a token and returns a node. + * + * @return \Twig_NodeInterface + * + * @throws SyntaxError + */ + public function parse(Token $token); + + /** + * Gets the tag name associated with this token parser. + * + * @return string The tag name + */ + public function getTag(); +} + +class_alias('Twig\TokenParser\TokenParserInterface', 'Twig_TokenParserInterface'); + +// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. +class_exists('Twig\Token'); +class_exists('Twig\Parser'); diff --git a/vendor/twig/twig/src/TokenParser/UseTokenParser.php b/vendor/twig/twig/src/TokenParser/UseTokenParser.php new file mode 100644 index 0000000..d2e39aa --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/UseTokenParser.php @@ -0,0 +1,75 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + + if (!$template instanceof ConstantExpression) { + throw new SyntaxError('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $targets = []; + if ($stream->nextIf('with')) { + do { + $name = $stream->expect(Token::NAME_TYPE)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(Token::NAME_TYPE)->getValue(); + } + + $targets[$name] = new ConstantExpression($alias, -1); + + if (!$stream->nextIf(Token::PUNCTUATION_TYPE, ',')) { + break; + } + } while (true); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)])); + + return new Node(); + } + + public function getTag() + { + return 'use'; + } +} + +class_alias('Twig\TokenParser\UseTokenParser', 'Twig_TokenParser_Use'); diff --git a/vendor/twig/twig/src/TokenParser/WithTokenParser.php b/vendor/twig/twig/src/TokenParser/WithTokenParser.php new file mode 100644 index 0000000..411e2b4 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/WithTokenParser.php @@ -0,0 +1,57 @@ + + * + * @final + */ +class WithTokenParser extends AbstractTokenParser +{ + public function parse(Token $token) + { + $stream = $this->parser->getStream(); + + $variables = null; + $only = false; + if (!$stream->test(Token::BLOCK_END_TYPE)) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + $only = $stream->nextIf(Token::NAME_TYPE, 'only'); + } + + $stream->expect(Token::BLOCK_END_TYPE); + + $body = $this->parser->subparse([$this, 'decideWithEnd'], true); + + $stream->expect(Token::BLOCK_END_TYPE); + + return new WithNode($body, $variables, $only, $token->getLine(), $this->getTag()); + } + + public function decideWithEnd(Token $token) + { + return $token->test('endwith'); + } + + public function getTag() + { + return 'with'; + } +} + +class_alias('Twig\TokenParser\WithTokenParser', 'Twig_TokenParser_With'); diff --git a/vendor/twig/twig/src/TokenStream.php b/vendor/twig/twig/src/TokenStream.php new file mode 100644 index 0000000..b5b089f --- /dev/null +++ b/vendor/twig/twig/src/TokenStream.php @@ -0,0 +1,200 @@ + + */ +class TokenStream +{ + protected $tokens; + protected $current = 0; + protected $filename; + + private $source; + + /** + * @param array $tokens An array of tokens + * @param string|null $name The name of the template which tokens are associated with + * @param string|null $source The source code associated with the tokens + */ + public function __construct(array $tokens, $name = null, $source = null) + { + if (!$name instanceof Source) { + if (null !== $name || null !== $source) { + @trigger_error(sprintf('Passing a string as the $name argument of %s() is deprecated since version 1.27. Pass a \Twig\Source instance instead.', __METHOD__), E_USER_DEPRECATED); + } + $this->source = new Source($source, $name); + } else { + $this->source = $name; + } + + $this->tokens = $tokens; + + // deprecated, not used anymore, to be removed in 2.0 + $this->filename = $this->source->getName(); + } + + public function __toString() + { + return implode("\n", $this->tokens); + } + + public function injectTokens(array $tokens) + { + $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current)); + } + + /** + * Sets the pointer to the next token and returns the old one. + * + * @return Token + */ + public function next() + { + if (!isset($this->tokens[++$this->current])) { + throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current - 1]; + } + + /** + * Tests a token, sets the pointer to the next one and returns it or throws a syntax error. + * + * @return Token|null The next token if the condition is true, null otherwise + */ + public function nextIf($primary, $secondary = null) + { + if ($this->tokens[$this->current]->test($primary, $secondary)) { + return $this->next(); + } + } + + /** + * Tests a token and returns it or throws a syntax error. + * + * @return Token + */ + public function expect($type, $value = null, $message = null) + { + $token = $this->tokens[$this->current]; + if (!$token->test($type, $value)) { + $line = $token->getLine(); + throw new SyntaxError(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s).', + $message ? $message.'. ' : '', + Token::typeToEnglish($token->getType()), $token->getValue(), + Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), + $line, + $this->source + ); + } + $this->next(); + + return $token; + } + + /** + * Looks at the next token. + * + * @param int $number + * + * @return Token + */ + public function look($number = 1) + { + if (!isset($this->tokens[$this->current + $number])) { + throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current + $number]; + } + + /** + * Tests the current token. + * + * @return bool + */ + public function test($primary, $secondary = null) + { + return $this->tokens[$this->current]->test($primary, $secondary); + } + + /** + * Checks if end of stream was reached. + * + * @return bool + */ + public function isEOF() + { + return Token::EOF_TYPE === $this->tokens[$this->current]->getType(); + } + + /** + * @return Token + */ + public function getCurrent() + { + return $this->tokens[$this->current]; + } + + /** + * Gets the name associated with this stream (null if not defined). + * + * @return string|null + * + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getFilename() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->source->getName(); + } + + /** + * Gets the source code associated with this stream. + * + * @return string + * + * @internal Don't use this as it might be empty depending on the environment configuration + * + * @deprecated since 1.27 (to be removed in 2.0) + */ + public function getSource() + { + @trigger_error(sprintf('The %s() method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', __METHOD__), E_USER_DEPRECATED); + + return $this->source->getCode(); + } + + /** + * Gets the source associated with this stream. + * + * @return Source + * + * @internal + */ + public function getSourceContext() + { + return $this->source; + } +} + +class_alias('Twig\TokenStream', 'Twig_TokenStream'); diff --git a/vendor/twig/twig/src/TwigFilter.php b/vendor/twig/twig/src/TwigFilter.php new file mode 100644 index 0000000..089a6d1 --- /dev/null +++ b/vendor/twig/twig/src/TwigFilter.php @@ -0,0 +1,128 @@ + + */ +class TwigFilter +{ + protected $name; + protected $callable; + protected $options; + protected $arguments = []; + + public function __construct($name, $callable, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'pre_escape' => null, + 'preserves_safety' => null, + 'node_class' => '\Twig\Node\Expression\FilterExpression', + 'deprecated' => false, + 'alternative' => null, + ], $options); + } + + public function getName() + { + return $this->name; + } + + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass() + { + return $this->options['node_class']; + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Node $filterArgs) + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return \call_user_func($this->options['is_safe_callback'], $filterArgs); + } + } + + public function getPreservesSafety() + { + return $this->options['preserves_safety']; + } + + public function getPreEscape() + { + return $this->options['pre_escape']; + } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } +} + +class_alias('Twig\TwigFilter', 'Twig_SimpleFilter'); + +// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. +class_exists('Twig\Node\Node'); diff --git a/vendor/twig/twig/src/TwigFunction.php b/vendor/twig/twig/src/TwigFunction.php new file mode 100644 index 0000000..374f070 --- /dev/null +++ b/vendor/twig/twig/src/TwigFunction.php @@ -0,0 +1,118 @@ + + */ +class TwigFunction +{ + protected $name; + protected $callable; + protected $options; + protected $arguments = []; + + public function __construct($name, $callable, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'node_class' => '\Twig\Node\Expression\FunctionExpression', + 'deprecated' => false, + 'alternative' => null, + ], $options); + } + + public function getName() + { + return $this->name; + } + + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass() + { + return $this->options['node_class']; + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } + + public function needsEnvironment() + { + return $this->options['needs_environment']; + } + + public function needsContext() + { + return $this->options['needs_context']; + } + + public function getSafe(Node $functionArgs) + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return \call_user_func($this->options['is_safe_callback'], $functionArgs); + } + + return []; + } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } +} + +class_alias('Twig\TwigFunction', 'Twig_SimpleFunction'); + +// Ensure that the aliased name is loaded to keep BC for classes implementing the typehint with the old aliased name. +class_exists('Twig\Node\Node'); diff --git a/vendor/twig/twig/src/TwigTest.php b/vendor/twig/twig/src/TwigTest.php new file mode 100644 index 0000000..5054965 --- /dev/null +++ b/vendor/twig/twig/src/TwigTest.php @@ -0,0 +1,87 @@ + + */ +class TwigTest +{ + protected $name; + protected $callable; + protected $options; + + private $arguments = []; + + public function __construct($name, $callable, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'is_variadic' => false, + 'node_class' => '\Twig\Node\Expression\TestExpression', + 'deprecated' => false, + 'alternative' => null, + ], $options); + } + + public function getName() + { + return $this->name; + } + + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass() + { + return $this->options['node_class']; + } + + public function isVariadic() + { + return $this->options['is_variadic']; + } + + public function isDeprecated() + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion() + { + return $this->options['deprecated']; + } + + public function getAlternative() + { + return $this->options['alternative']; + } + + public function setArguments($arguments) + { + $this->arguments = $arguments; + } + + public function getArguments() + { + return $this->arguments; + } +} + +class_alias('Twig\TwigTest', 'Twig_SimpleTest'); diff --git a/vendor/twig/twig/src/Util/DeprecationCollector.php b/vendor/twig/twig/src/Util/DeprecationCollector.php new file mode 100644 index 0000000..09917e9 --- /dev/null +++ b/vendor/twig/twig/src/Util/DeprecationCollector.php @@ -0,0 +1,92 @@ + + * + * @final + */ +class DeprecationCollector +{ + private $twig; + private $deprecations; + + public function __construct(Environment $twig) + { + $this->twig = $twig; + } + + /** + * Returns deprecations for templates contained in a directory. + * + * @param string $dir A directory where templates are stored + * @param string $ext Limit the loaded templates by extension + * + * @return array An array of deprecations + */ + public function collectDir($dir, $ext = '.twig') + { + $iterator = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::LEAVES_ONLY + ), '{'.preg_quote($ext).'$}' + ); + + return $this->collect(new TemplateDirIterator($iterator)); + } + + /** + * Returns deprecations for passed templates. + * + * @param \Traversable $iterator An iterator of templates (where keys are template names and values the contents of the template) + * + * @return array An array of deprecations + */ + public function collect(\Traversable $iterator) + { + $this->deprecations = []; + + set_error_handler([$this, 'errorHandler']); + + foreach ($iterator as $name => $contents) { + try { + $this->twig->parse($this->twig->tokenize(new Source($contents, $name))); + } catch (SyntaxError $e) { + // ignore templates containing syntax errors + } + } + + restore_error_handler(); + + $deprecations = $this->deprecations; + $this->deprecations = []; + + return $deprecations; + } + + /** + * @internal + */ + public function errorHandler($type, $msg) + { + if (E_USER_DEPRECATED === $type) { + $this->deprecations[] = $msg; + } + } +} + +class_alias('Twig\Util\DeprecationCollector', 'Twig_Util_DeprecationCollector'); diff --git a/vendor/twig/twig/src/Util/TemplateDirIterator.php b/vendor/twig/twig/src/Util/TemplateDirIterator.php new file mode 100644 index 0000000..1ab0dac --- /dev/null +++ b/vendor/twig/twig/src/Util/TemplateDirIterator.php @@ -0,0 +1,30 @@ + + */ +class TemplateDirIterator extends \IteratorIterator +{ + public function current() + { + return file_get_contents(parent::current()); + } + + public function key() + { + return (string) parent::key(); + } +} + +class_alias('Twig\Util\TemplateDirIterator', 'Twig_Util_TemplateDirIterator'); diff --git a/vendor/zendframework/zend-code/LICENSE.md b/vendor/zendframework/zend-code/LICENSE.md new file mode 100644 index 0000000..dbb1b49 --- /dev/null +++ b/vendor/zendframework/zend-code/LICENSE.md @@ -0,0 +1,28 @@ +Copyright (c) 2005-2015, Zend Technologies USA, Inc. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Zend Technologies USA, Inc. nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/zendframework/zend-code/composer.json b/vendor/zendframework/zend-code/composer.json new file mode 100644 index 0000000..15cc0c9 --- /dev/null +++ b/vendor/zendframework/zend-code/composer.json @@ -0,0 +1,43 @@ +{ + "name": "zendframework/zend-code", + "description": "provides facilities to generate arbitrary code using an object oriented interface", + "license": "BSD-3-Clause", + "keywords": [ + "zf2", + "code" + ], + "homepage": "https://github.com/zendframework/zend-code", + "autoload": { + "psr-4": { + "Zend\\Code\\": "src/" + } + }, + "require": { + "php": ">=5.3.23", + "zendframework/zend-eventmanager": "~2.5" + }, + "require-dev": { + "doctrine/common": ">=2.1", + "zendframework/zend-stdlib": "~2.5", + "zendframework/zend-version": "~2.5", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/PHPUnit": "~4.0" + }, + "suggest": { + "doctrine/common": "Doctrine\\Common >=2.1 for annotation features", + "zendframework/zend-stdlib": "Zend\\Stdlib component" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload-dev": { + "psr-4": { + "ZendTest\\Code\\": "test/" + } + } +} diff --git a/vendor/zendframework/zend-code/src/Annotation/AnnotationCollection.php b/vendor/zendframework/zend-code/src/Annotation/AnnotationCollection.php new file mode 100644 index 0000000..48a71fc --- /dev/null +++ b/vendor/zendframework/zend-code/src/Annotation/AnnotationCollection.php @@ -0,0 +1,32 @@ +setIdentifiers(array( + __CLASS__, + get_class($this), + )); + $this->events = $events; + + return $this; + } + + /** + * Retrieve event manager + * + * Lazy loads an instance if none registered. + * + * @return EventManagerInterface + */ + public function getEventManager() + { + if (null === $this->events) { + $this->setEventManager(new EventManager()); + } + + return $this->events; + } + + /** + * Attach a parser to listen to the createAnnotation event + * + * @param ParserInterface $parser + * @return AnnotationManager + */ + public function attach(ParserInterface $parser) + { + $this->getEventManager() + ->attach(self::EVENT_CREATE_ANNOTATION, array($parser, 'onCreateAnnotation')); + + return $this; + } + + /** + * Create Annotation + * + * @param string[] $annotationData + * @return false|\stdClass + */ + public function createAnnotation(array $annotationData) + { + $event = new Event(); + $event->setName(self::EVENT_CREATE_ANNOTATION); + $event->setTarget($this); + $event->setParams(array( + 'class' => $annotationData[0], + 'content' => $annotationData[1], + 'raw' => $annotationData[2], + )); + + $eventManager = $this->getEventManager(); + $results = $eventManager->trigger($event, function ($r) { + return (is_object($r)); + }); + + $annotation = $results->last(); + + return (is_object($annotation) ? $annotation : false); + } +} diff --git a/vendor/zendframework/zend-code/src/Annotation/Parser/DoctrineAnnotationParser.php b/vendor/zendframework/zend-code/src/Annotation/Parser/DoctrineAnnotationParser.php new file mode 100644 index 0000000..d462985 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Annotation/Parser/DoctrineAnnotationParser.php @@ -0,0 +1,153 @@ +docParser = $docParser; + return $this; + } + + /** + * Retrieve the DocParser instance + * + * If none is registered, lazy-loads a new instance. + * + * @return DocParser + */ + public function getDocParser() + { + if (!$this->docParser instanceof DocParser) { + $this->setDocParser(new DocParser()); + } + + return $this->docParser; + } + + /** + * Handle annotation creation + * + * @param EventInterface $e + * @return false|\stdClass + */ + public function onCreateAnnotation(EventInterface $e) + { + $annotationClass = $e->getParam('class', false); + if (!$annotationClass) { + return false; + } + + if (!isset($this->allowedAnnotations[$annotationClass])) { + return false; + } + + $annotationString = $e->getParam('raw', false); + if (!$annotationString) { + return false; + } + + // Annotation classes provided by the AnnotationScanner are already + // resolved to fully-qualified class names. Adding the global namespace + // prefix allows the Doctrine annotation parser to locate the annotation + // class correctly. + $annotationString = preg_replace('/^(@)/', '$1\\', $annotationString); + + $parser = $this->getDocParser(); + $annotations = $parser->parse($annotationString); + if (empty($annotations)) { + return false; + } + + $annotation = array_shift($annotations); + if (!is_object($annotation)) { + return false; + } + + return $annotation; + } + + /** + * Specify an allowed annotation class + * + * @param string $annotation + * @return DoctrineAnnotationParser + */ + public function registerAnnotation($annotation) + { + $this->allowedAnnotations[$annotation] = true; + return $this; + } + + /** + * Set many allowed annotations at once + * + * @param array|Traversable $annotations Array or traversable object of + * annotation class names + * @throws Exception\InvalidArgumentException + * @return DoctrineAnnotationParser + */ + public function registerAnnotations($annotations) + { + if (!is_array($annotations) && !$annotations instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: expects an array or Traversable; received "%s"', + __METHOD__, + (is_object($annotations) ? get_class($annotations) : gettype($annotations)) + )); + } + + foreach ($annotations as $annotation) { + $this->allowedAnnotations[$annotation] = true; + } + + return $this; + } +} diff --git a/vendor/zendframework/zend-code/src/Annotation/Parser/GenericAnnotationParser.php b/vendor/zendframework/zend-code/src/Annotation/Parser/GenericAnnotationParser.php new file mode 100644 index 0000000..f142ac4 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Annotation/Parser/GenericAnnotationParser.php @@ -0,0 +1,224 @@ +getParam('class', false); + if (!$class || !$this->hasAnnotation($class)) { + return false; + } + + $content = $e->getParam('content', ''); + $content = trim($content, '()'); + + if ($this->hasAlias($class)) { + $class = $this->resolveAlias($class); + } + + $index = array_search($class, $this->annotationNames); + $annotation = $this->annotations[$index]; + + $newAnnotation = clone $annotation; + if ($content) { + $newAnnotation->initialize($content); + } + + return $newAnnotation; + } + + /** + * Register annotations + * + * @param string|AnnotationInterface $annotation String class name of an + * AnnotationInterface implementation, or actual instance + * @return GenericAnnotationParser + * @throws Exception\InvalidArgumentException + */ + public function registerAnnotation($annotation) + { + $class = false; + if (is_string($annotation) && class_exists($annotation)) { + $class = $annotation; + $annotation = new $annotation(); + } + + if (!$annotation instanceof AnnotationInterface) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: expects an instance of %s\AnnotationInterface; received "%s"', + __METHOD__, + __NAMESPACE__, + (is_object($annotation) ? get_class($annotation) : gettype($annotation)) + )); + } + + $class = $class ?: get_class($annotation); + + if (in_array($class, $this->annotationNames)) { + throw new Exception\InvalidArgumentException(sprintf( + 'An annotation for this class %s already exists', + $class + )); + } + + $this->annotations[] = $annotation; + $this->annotationNames[] = $class; + } + + /** + * Register many annotations at once + * + * @param array|Traversable $annotations + * @throws Exception\InvalidArgumentException + * @return GenericAnnotationParser + */ + public function registerAnnotations($annotations) + { + if (!is_array($annotations) && !$annotations instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: expects an array or Traversable; received "%s"', + __METHOD__, + (is_object($annotations) ? get_class($annotations) : gettype($annotations)) + )); + } + + foreach ($annotations as $annotation) { + $this->registerAnnotation($annotation); + } + + return $this; + } + + /** + * Checks if the manager has annotations for a class + * + * @param string $class + * @return bool + */ + public function hasAnnotation($class) + { + if (in_array($class, $this->annotationNames)) { + return true; + } + + if ($this->hasAlias($class)) { + return true; + } + + return false; + } + + /** + * Alias an annotation name + * + * @param string $alias + * @param string $class May be either a registered annotation name or another alias + * @throws Exception\InvalidArgumentException + * @return GenericAnnotationParser + */ + public function setAlias($alias, $class) + { + if (!in_array($class, $this->annotationNames) && !$this->hasAlias($class)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: Cannot alias "%s" to "%s", as class "%s" is not currently a registered annotation or alias', + __METHOD__, + $alias, + $class, + $class + )); + } + + $alias = $this->normalizeAlias($alias); + $this->aliases[$alias] = $class; + + return $this; + } + + /** + * Normalize an alias name + * + * @param string $alias + * @return string + */ + protected function normalizeAlias($alias) + { + return strtolower(str_replace(array('-', '_', ' ', '\\', '/'), '', $alias)); + } + + /** + * Do we have an alias by the provided name? + * + * @param string $alias + * @return bool + */ + protected function hasAlias($alias) + { + $alias = $this->normalizeAlias($alias); + + return (isset($this->aliases[$alias])); + } + + /** + * Resolve an alias to a class name + * + * @param string $alias + * @return string + */ + protected function resolveAlias($alias) + { + do { + $normalized = $this->normalizeAlias($alias); + $class = $this->aliases[$normalized]; + } while ($this->hasAlias($class)); + + return $class; + } +} diff --git a/vendor/zendframework/zend-code/src/Annotation/Parser/ParserInterface.php b/vendor/zendframework/zend-code/src/Annotation/Parser/ParserInterface.php new file mode 100644 index 0000000..9d84642 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Annotation/Parser/ParserInterface.php @@ -0,0 +1,39 @@ +setOptions($options); + } + } + + /** + * @param bool $isSourceDirty + * @return AbstractGenerator + */ + public function setSourceDirty($isSourceDirty = true) + { + $this->isSourceDirty = (bool) $isSourceDirty; + return $this; + } + + /** + * @return bool + */ + public function isSourceDirty() + { + return $this->isSourceDirty; + } + + /** + * @param string $indentation + * @return AbstractGenerator + */ + public function setIndentation($indentation) + { + $this->indentation = (string) $indentation; + return $this; + } + + /** + * @return string + */ + public function getIndentation() + { + return $this->indentation; + } + + /** + * @param string $sourceContent + * @return AbstractGenerator + */ + public function setSourceContent($sourceContent) + { + $this->sourceContent = (string) $sourceContent; + return $this; + } + + /** + * @return string + */ + public function getSourceContent() + { + return $this->sourceContent; + } + + /** + * @param array|Traversable $options + * @throws Exception\InvalidArgumentException + * @return AbstractGenerator + */ + public function setOptions($options) + { + if (!is_array($options) && !$options instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects an array or Traversable object; received "%s"', + __METHOD__, + (is_object($options) ? get_class($options) : gettype($options)) + )); + } + + foreach ($options as $optionName => $optionValue) { + $methodName = 'set' . $optionName; + if (method_exists($this, $methodName)) { + $this->{$methodName}($optionValue); + } + } + + return $this; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/AbstractMemberGenerator.php b/vendor/zendframework/zend-code/src/Generator/AbstractMemberGenerator.php new file mode 100644 index 0000000..10684a3 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/AbstractMemberGenerator.php @@ -0,0 +1,224 @@ +flags = $flags; + + return $this; + } + + /** + * @param int $flag + * @return AbstractMemberGenerator + */ + public function addFlag($flag) + { + $this->setFlags($this->flags | $flag); + return $this; + } + + /** + * @param int $flag + * @return AbstractMemberGenerator + */ + public function removeFlag($flag) + { + $this->setFlags($this->flags & ~$flag); + return $this; + } + + /** + * @param bool $isAbstract + * @return AbstractMemberGenerator + */ + public function setAbstract($isAbstract) + { + return (($isAbstract) ? $this->addFlag(self::FLAG_ABSTRACT) : $this->removeFlag(self::FLAG_ABSTRACT)); + } + + /** + * @return bool + */ + public function isAbstract() + { + return (bool) ($this->flags & self::FLAG_ABSTRACT); + } + + /** + * @param bool $isFinal + * @return AbstractMemberGenerator + */ + public function setFinal($isFinal) + { + return (($isFinal) ? $this->addFlag(self::FLAG_FINAL) : $this->removeFlag(self::FLAG_FINAL)); + } + + /** + * @return bool + */ + public function isFinal() + { + return (bool) ($this->flags & self::FLAG_FINAL); + } + + /** + * @param bool $isStatic + * @return AbstractMemberGenerator + */ + public function setStatic($isStatic) + { + return (($isStatic) ? $this->addFlag(self::FLAG_STATIC) : $this->removeFlag(self::FLAG_STATIC)); + } + + /** + * @return bool + */ + public function isStatic() + { + return (bool) ($this->flags & self::FLAG_STATIC); // is FLAG_STATIC in flags + } + + /** + * @param string $visibility + * @return AbstractMemberGenerator + */ + public function setVisibility($visibility) + { + switch ($visibility) { + case self::VISIBILITY_PUBLIC: + $this->removeFlag(self::FLAG_PRIVATE | self::FLAG_PROTECTED); // remove both + $this->addFlag(self::FLAG_PUBLIC); + break; + case self::VISIBILITY_PROTECTED: + $this->removeFlag(self::FLAG_PUBLIC | self::FLAG_PRIVATE); // remove both + $this->addFlag(self::FLAG_PROTECTED); + break; + case self::VISIBILITY_PRIVATE: + $this->removeFlag(self::FLAG_PUBLIC | self::FLAG_PROTECTED); // remove both + $this->addFlag(self::FLAG_PRIVATE); + break; + } + + return $this; + } + + /** + * @return string + */ + public function getVisibility() + { + switch (true) { + case ($this->flags & self::FLAG_PROTECTED): + return self::VISIBILITY_PROTECTED; + case ($this->flags & self::FLAG_PRIVATE): + return self::VISIBILITY_PRIVATE; + default: + return self::VISIBILITY_PUBLIC; + } + } + + /** + * @param string $name + * @return AbstractMemberGenerator + */ + public function setName($name) + { + $this->name = (string) $name; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param DocBlockGenerator|string $docBlock + * @throws Exception\InvalidArgumentException + * @return AbstractMemberGenerator + */ + public function setDocBlock($docBlock) + { + if (is_string($docBlock)) { + $docBlock = new DocBlockGenerator($docBlock); + } elseif (!$docBlock instanceof DocBlockGenerator) { + throw new Exception\InvalidArgumentException(sprintf( + '%s is expecting either a string, array or an instance of %s\DocBlockGenerator', + __METHOD__, + __NAMESPACE__ + )); + } + + $this->docBlock = $docBlock; + + return $this; + } + + /** + * @return DocBlockGenerator + */ + public function getDocBlock() + { + return $this->docBlock; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/BodyGenerator.php b/vendor/zendframework/zend-code/src/Generator/BodyGenerator.php new file mode 100644 index 0000000..03e4f40 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/BodyGenerator.php @@ -0,0 +1,44 @@ +content = (string) $content; + return $this; + } + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @return string + */ + public function generate() + { + return $this->getContent(); + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/ClassGenerator.php b/vendor/zendframework/zend-code/src/Generator/ClassGenerator.php new file mode 100644 index 0000000..749edfe --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/ClassGenerator.php @@ -0,0 +1,987 @@ +getName()); + + $cg->setSourceContent($cg->getSourceContent()); + $cg->setSourceDirty(false); + + if ($classReflection->getDocComment() != '') { + $cg->setDocBlock(DocBlockGenerator::fromReflection($classReflection->getDocBlock())); + } + + $cg->setAbstract($classReflection->isAbstract()); + + // set the namespace + if ($classReflection->inNamespace()) { + $cg->setNamespaceName($classReflection->getNamespaceName()); + } + + /* @var \Zend\Code\Reflection\ClassReflection $parentClass */ + $parentClass = $classReflection->getParentClass(); + $interfaces = $classReflection->getInterfaces(); + + if ($parentClass) { + $cg->setExtendedClass($parentClass->getName()); + + $interfaces = array_diff($interfaces, $parentClass->getInterfaces()); + } + + $interfaceNames = array(); + foreach ($interfaces as $interface) { + /* @var \Zend\Code\Reflection\ClassReflection $interface */ + $interfaceNames[] = $interface->getName(); + } + + $cg->setImplementedInterfaces($interfaceNames); + + $properties = array(); + + foreach ($classReflection->getProperties() as $reflectionProperty) { + if ($reflectionProperty->getDeclaringClass()->getName() == $classReflection->getName()) { + $properties[] = PropertyGenerator::fromReflection($reflectionProperty); + } + } + + $cg->addProperties($properties); + + $constants = array(); + + foreach ($classReflection->getConstants() as $name => $value) { + $constants[] = array( + 'name' => $name, + 'value' => $value + ); + } + + $cg->addConstants($constants); + + $methods = array(); + + foreach ($classReflection->getMethods() as $reflectionMethod) { + $className = ($cg->getNamespaceName()) ? $cg->getNamespaceName() . "\\" . $cg->getName() : $cg->getName(); + + if ($reflectionMethod->getDeclaringClass()->getName() == $className) { + $methods[] = MethodGenerator::fromReflection($reflectionMethod); + } + } + + $cg->addMethods($methods); + + return $cg; + } + + /** + * Generate from array + * + * @configkey name string [required] Class Name + * @configkey filegenerator FileGenerator File generator that holds this class + * @configkey namespacename string The namespace for this class + * @configkey docblock string The docblock information + * @configkey flags int Flags, one of ClassGenerator::FLAG_ABSTRACT ClassGenerator::FLAG_FINAL + * @configkey extendedclass string Class which this class is extending + * @configkey implementedinterfaces + * @configkey properties + * @configkey methods + * + * @throws Exception\InvalidArgumentException + * @param array $array + * @return ClassGenerator + */ + public static function fromArray(array $array) + { + if (!isset($array['name'])) { + throw new Exception\InvalidArgumentException( + 'Class generator requires that a name is provided for this object' + ); + } + + $cg = new static($array['name']); + foreach ($array as $name => $value) { + // normalize key + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'containingfile': + $cg->setContainingFileGenerator($value); + break; + case 'namespacename': + $cg->setNamespaceName($value); + break; + case 'docblock': + $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value); + $cg->setDocBlock($docBlock); + break; + case 'flags': + $cg->setFlags($value); + break; + case 'extendedclass': + $cg->setExtendedClass($value); + break; + case 'implementedinterfaces': + $cg->setImplementedInterfaces($value); + break; + case 'properties': + $cg->addProperties($value); + break; + case 'methods': + $cg->addMethods($value); + break; + } + } + + return $cg; + } + + /** + * @param string $name + * @param string $namespaceName + * @param array|string $flags + * @param string $extends + * @param array $interfaces + * @param array $properties + * @param array $methods + * @param DocBlockGenerator $docBlock + */ + public function __construct( + $name = null, + $namespaceName = null, + $flags = null, + $extends = null, + $interfaces = array(), + $properties = array(), + $methods = array(), + $docBlock = null + ) { + $this->traitUsageGenerator = new TraitUsageGenerator($this); + + if ($name !== null) { + $this->setName($name); + } + if ($namespaceName !== null) { + $this->setNamespaceName($namespaceName); + } + if ($flags !== null) { + $this->setFlags($flags); + } + if ($properties !== array()) { + $this->addProperties($properties); + } + if ($extends !== null) { + $this->setExtendedClass($extends); + } + if (is_array($interfaces)) { + $this->setImplementedInterfaces($interfaces); + } + if ($methods !== array()) { + $this->addMethods($methods); + } + if ($docBlock !== null) { + $this->setDocBlock($docBlock); + } + } + + /** + * @param string $name + * @return ClassGenerator + */ + public function setName($name) + { + if (strstr($name, '\\')) { + $namespace = substr($name, 0, strrpos($name, '\\')); + $name = substr($name, strrpos($name, '\\') + 1); + $this->setNamespaceName($namespace); + } + + $this->name = $name; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $namespaceName + * @return ClassGenerator + */ + public function setNamespaceName($namespaceName) + { + $this->namespaceName = $namespaceName; + return $this; + } + + /** + * @return string + */ + public function getNamespaceName() + { + return $this->namespaceName; + } + + /** + * @param FileGenerator $fileGenerator + * @return ClassGenerator + */ + public function setContainingFileGenerator(FileGenerator $fileGenerator) + { + $this->containingFileGenerator = $fileGenerator; + return $this; + } + + /** + * @return FileGenerator + */ + public function getContainingFileGenerator() + { + return $this->containingFileGenerator; + } + + /** + * @param DocBlockGenerator $docBlock + * @return ClassGenerator + */ + public function setDocBlock(DocBlockGenerator $docBlock) + { + $this->docBlock = $docBlock; + return $this; + } + + /** + * @return DocBlockGenerator + */ + public function getDocBlock() + { + return $this->docBlock; + } + + /** + * @param array|string $flags + * @return ClassGenerator + */ + public function setFlags($flags) + { + if (is_array($flags)) { + $flagsArray = $flags; + $flags = 0x00; + foreach ($flagsArray as $flag) { + $flags |= $flag; + } + } + // check that visibility is one of three + $this->flags = $flags; + + return $this; + } + + /** + * @param string $flag + * @return ClassGenerator + */ + public function addFlag($flag) + { + $this->setFlags($this->flags | $flag); + return $this; + } + + /** + * @param string $flag + * @return ClassGenerator + */ + public function removeFlag($flag) + { + $this->setFlags($this->flags & ~$flag); + return $this; + } + + /** + * @param bool $isAbstract + * @return ClassGenerator + */ + public function setAbstract($isAbstract) + { + return (($isAbstract) ? $this->addFlag(self::FLAG_ABSTRACT) : $this->removeFlag(self::FLAG_ABSTRACT)); + } + + /** + * @return bool + */ + public function isAbstract() + { + return (bool) ($this->flags & self::FLAG_ABSTRACT); + } + + /** + * @param bool $isFinal + * @return ClassGenerator + */ + public function setFinal($isFinal) + { + return (($isFinal) ? $this->addFlag(self::FLAG_FINAL) : $this->removeFlag(self::FLAG_FINAL)); + } + + /** + * @return bool + */ + public function isFinal() + { + return ($this->flags & self::FLAG_FINAL); + } + + /** + * @param string $extendedClass + * @return ClassGenerator + */ + public function setExtendedClass($extendedClass) + { + $this->extendedClass = $extendedClass; + return $this; + } + + /** + * @return string + */ + public function getExtendedClass() + { + return $this->extendedClass; + } + + /** + * @param array $implementedInterfaces + * @return ClassGenerator + */ + public function setImplementedInterfaces(array $implementedInterfaces) + { + $this->implementedInterfaces = $implementedInterfaces; + return $this; + } + + /** + * @return array + */ + public function getImplementedInterfaces() + { + return $this->implementedInterfaces; + } + + /** + * @param string $constantName + * + * @return PropertyGenerator|false + */ + public function getConstant($constantName) + { + if (isset($this->constants[$constantName])) { + return $this->constants[$constantName]; + } + + return false; + } + + /** + * @return PropertyGenerator[] indexed by constant name + */ + public function getConstants() + { + return $this->constants; + } + + /** + * @param string $constantName + * @return bool + */ + public function hasConstant($constantName) + { + return isset($this->constants[$constantName]); + } + + /** + * Add constant from PropertyGenerator + * + * @param PropertyGenerator $constant + * @throws Exception\InvalidArgumentException + * @return ClassGenerator + */ + public function addConstantFromGenerator(PropertyGenerator $constant) + { + $constantName = $constant->getName(); + + if (isset($this->constants[$constantName])) { + throw new Exception\InvalidArgumentException(sprintf( + 'A constant by name %s already exists in this class.', + $constantName + )); + } + + if (! $constant->isConst()) { + throw new Exception\InvalidArgumentException(sprintf( + 'The value %s is not defined as a constant.', + $constantName + )); + } + + $this->constants[$constantName] = $constant; + + return $this; + } + + /** + * Add Constant + * + * @param string $name + * @param string $value + * @throws Exception\InvalidArgumentException + * @return ClassGenerator + */ + public function addConstant($name, $value) + { + if (!is_string($name)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects string for name', + __METHOD__ + )); + } + + if (empty($value) || !is_string($value)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects value for constant, value must be a string', + __METHOD__ + )); + } + + return $this->addConstantFromGenerator(new PropertyGenerator($name, $value, PropertyGenerator::FLAG_CONSTANT)); + } + + /** + * @param PropertyGenerator[]|array[] $constants + * + * @return ClassGenerator + */ + public function addConstants(array $constants) + { + foreach ($constants as $constant) { + if ($constant instanceof PropertyGenerator) { + $this->addPropertyFromGenerator($constant); + } else { + if (is_array($constant)) { + call_user_func_array(array($this, 'addConstant'), $constant); + } + } + } + + return $this; + } + + /** + * @param array $properties + * @return ClassGenerator + */ + public function addProperties(array $properties) + { + foreach ($properties as $property) { + if ($property instanceof PropertyGenerator) { + $this->addPropertyFromGenerator($property); + } else { + if (is_string($property)) { + $this->addProperty($property); + } elseif (is_array($property)) { + call_user_func_array(array($this, 'addProperty'), $property); + } + } + } + + return $this; + } + + /** + * Add Property from scalars + * + * @param string $name + * @param string|array $defaultValue + * @param int $flags + * @throws Exception\InvalidArgumentException + * @return ClassGenerator + */ + public function addProperty($name, $defaultValue = null, $flags = PropertyGenerator::FLAG_PUBLIC) + { + if (!is_string($name)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s::%s expects string for name', + get_class($this), + __FUNCTION__ + )); + } + + // backwards compatibility + // @todo remove this on next major version + if ($flags === PropertyGenerator::FLAG_CONSTANT) { + return $this->addConstant($name, $defaultValue); + } + + return $this->addPropertyFromGenerator(new PropertyGenerator($name, $defaultValue, $flags)); + } + + /** + * Add property from PropertyGenerator + * + * @param PropertyGenerator $property + * @throws Exception\InvalidArgumentException + * @return ClassGenerator + */ + public function addPropertyFromGenerator(PropertyGenerator $property) + { + $propertyName = $property->getName(); + + if (isset($this->properties[$propertyName])) { + throw new Exception\InvalidArgumentException(sprintf( + 'A property by name %s already exists in this class.', + $propertyName + )); + } + + // backwards compatibility + // @todo remove this on next major version + if ($property->isConst()) { + return $this->addConstantFromGenerator($property); + } + + $this->properties[$propertyName] = $property; + return $this; + } + + /** + * @return PropertyGenerator[] + */ + public function getProperties() + { + return $this->properties; + } + + /** + * @param string $propertyName + * @return PropertyGenerator|false + */ + public function getProperty($propertyName) + { + foreach ($this->getProperties() as $property) { + if ($property->getName() == $propertyName) { + return $property; + } + } + + return false; + } + + /** + * Add a class to "use" classes + * + * @param string $use + * @param string|null $useAlias + * @return ClassGenerator + */ + public function addUse($use, $useAlias = null) + { + $this->traitUsageGenerator->addUse($use, $useAlias); + return $this; + } + + /** + * Returns the "use" classes + * + * @return array + */ + public function getUses() + { + return $this->traitUsageGenerator->getUses(); + } + + /** + * @param string $propertyName + * @return bool + */ + public function hasProperty($propertyName) + { + return isset($this->properties[$propertyName]); + } + + /** + * @param array $methods + * @return ClassGenerator + */ + public function addMethods(array $methods) + { + foreach ($methods as $method) { + if ($method instanceof MethodGenerator) { + $this->addMethodFromGenerator($method); + } else { + if (is_string($method)) { + $this->addMethod($method); + } elseif (is_array($method)) { + call_user_func_array(array($this, 'addMethod'), $method); + } + } + } + + return $this; + } + + /** + * Add Method from scalars + * + * @param string $name + * @param array $parameters + * @param int $flags + * @param string $body + * @param string $docBlock + * @throws Exception\InvalidArgumentException + * @return ClassGenerator + */ + public function addMethod( + $name = null, + array $parameters = array(), + $flags = MethodGenerator::FLAG_PUBLIC, + $body = null, + $docBlock = null + ) { + if (!is_string($name)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s::%s expects string for name', + get_class($this), + __FUNCTION__ + )); + } + + return $this->addMethodFromGenerator(new MethodGenerator($name, $parameters, $flags, $body, $docBlock)); + } + + /** + * Add Method from MethodGenerator + * + * @param MethodGenerator $method + * @throws Exception\InvalidArgumentException + * @return ClassGenerator + */ + public function addMethodFromGenerator(MethodGenerator $method) + { + $methodName = $method->getName(); + + if ($this->hasMethod($methodName)) { + throw new Exception\InvalidArgumentException(sprintf( + 'A method by name %s already exists in this class.', + $methodName + )); + } + + $this->methods[strtolower($methodName)] = $method; + return $this; + } + + /** + * @return MethodGenerator[] + */ + public function getMethods() + { + return $this->methods; + } + + /** + * @param string $methodName + * @return MethodGenerator|false + */ + public function getMethod($methodName) + { + return $this->hasMethod($methodName) ? $this->methods[strtolower($methodName)] : false; + } + + /** + * @param string $methodName + * @return ClassGenerator + */ + public function removeMethod($methodName) + { + if ($this->hasMethod($methodName)) { + unset($this->methods[strtolower($methodName)]); + } + + return $this; + } + + /** + * @param string $methodName + * @return bool + */ + public function hasMethod($methodName) + { + return isset($this->methods[strtolower($methodName)]); + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addTrait($trait) + { + $this->traitUsageGenerator->addTrait($trait); + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addTraits(array $traits) + { + $this->traitUsageGenerator->addTraits($traits); + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function hasTrait($traitName) + { + return $this->traitUsageGenerator->hasTrait($traitName); + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function getTraits() + { + return $this->traitUsageGenerator->getTraits(); + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function removeTrait($traitName) + { + return $this->traitUsageGenerator->removeTrait($traitName); + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addTraitAlias($method, $alias, $visibility = null) + { + $this->traitUsageGenerator->addTraitAlias($method, $alias, $visibility); + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function getTraitAliases() + { + return $this->traitUsageGenerator->getTraitAliases(); + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addTraitOverride($method, $traitsToReplace) + { + $this->traitUsageGenerator->addTraitOverride($method, $traitsToReplace); + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function removeTraitOverride($method, $overridesToRemove = null) + { + $this->traitUsageGenerator->removeTraitOverride($method, $overridesToRemove); + + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function getTraitOverrides() + { + return $this->traitUsageGenerator->getTraitOverrides(); + } + + /** + * @return bool + */ + public function isSourceDirty() + { + if (($docBlock = $this->getDocBlock()) && $docBlock->isSourceDirty()) { + return true; + } + + foreach ($this->getProperties() as $property) { + if ($property->isSourceDirty()) { + return true; + } + } + + foreach ($this->getMethods() as $method) { + if ($method->isSourceDirty()) { + return true; + } + } + + return parent::isSourceDirty(); + } + + /** + * @inherit Zend\Code\Generator\GeneratorInterface + */ + public function generate() + { + if (!$this->isSourceDirty()) { + $output = $this->getSourceContent(); + if (!empty($output)) { + return $output; + } + } + + $indent = $this->getIndentation(); + $output = ''; + + if (null !== ($namespace = $this->getNamespaceName())) { + $output .= 'namespace ' . $namespace . ';' . self::LINE_FEED . self::LINE_FEED; + } + + $uses = $this->getUses(); + + if (!empty($uses)) { + foreach ($uses as $use) { + $output .= 'use ' . $use . ';' . self::LINE_FEED; + } + + $output .= self::LINE_FEED; + } + + if (null !== ($docBlock = $this->getDocBlock())) { + $docBlock->setIndentation(''); + $output .= $docBlock->generate(); + } + + if ($this->isAbstract()) { + $output .= 'abstract '; + } elseif ($this->isFinal()) { + $output .= 'final '; + } + + $output .= static::OBJECT_TYPE . ' ' . $this->getName(); + + if (!empty($this->extendedClass)) { + $output .= ' extends ' . $this->extendedClass; + } + + $implemented = $this->getImplementedInterfaces(); + + if (!empty($implemented)) { + $output .= ' implements ' . implode(', ', $implemented); + } + + $output .= self::LINE_FEED . '{' . self::LINE_FEED . self::LINE_FEED; + $output .= $this->traitUsageGenerator->generate(); + + $constants = $this->getConstants(); + + foreach ($constants as $constant) { + $output .= $constant->generate() . self::LINE_FEED . self::LINE_FEED; + } + + $properties = $this->getProperties(); + + foreach ($properties as $property) { + $output .= $property->generate() . self::LINE_FEED . self::LINE_FEED; + } + + $methods = $this->getMethods(); + + foreach ($methods as $method) { + $output .= $method->generate() . self::LINE_FEED; + } + + $output .= self::LINE_FEED . '}' . self::LINE_FEED; + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag.php new file mode 100644 index 0000000..5901fb1 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag.php @@ -0,0 +1,50 @@ +initializeDefaultTags(); + return $tagManager->createTagFromReflection($reflectionTag); + } + + /** + * @param string $description + * @return Tag + * @deprecated Deprecated in 2.3. Use GenericTag::setContent() instead + */ + public function setDescription($description) + { + return $this->setContent($description); + } + + /** + * @return string + * @deprecated Deprecated in 2.3. Use GenericTag::getContent() instead + */ + public function getDescription() + { + return $this->getContent(); + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/AbstractTypeableTag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/AbstractTypeableTag.php new file mode 100644 index 0000000..3d95e90 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/AbstractTypeableTag.php @@ -0,0 +1,96 @@ +setTypes($types); + } + + if (!empty($description)) { + $this->setDescription($description); + } + } + + /** + * @param string $description + * @return ReturnTag + */ + public function setDescription($description) + { + $this->description = $description; + return $this; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * Array of types or string with types delimited by pipe (|) + * e.g. array('int', 'null') or "int|null" + * + * @param array|string $types + * @return ReturnTag + */ + public function setTypes($types) + { + if (is_string($types)) { + $types = explode('|', $types); + } + $this->types = $types; + return $this; + } + + /** + * @return array + */ + public function getTypes() + { + return $this->types; + } + + /** + * @param string $delimiter + * @return string + */ + public function getTypesAsString($delimiter = '|') + { + return implode($delimiter, $this->types); + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/AuthorTag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/AuthorTag.php new file mode 100644 index 0000000..a6b5408 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/AuthorTag.php @@ -0,0 +1,110 @@ +setAuthorName($authorName); + } + + if (!empty($authorEmail)) { + $this->setAuthorEmail($authorEmail); + } + } + + /** + * @param ReflectionTagInterface $reflectionTag + * @return ReturnTag + * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead + */ + public static function fromReflection(ReflectionTagInterface $reflectionTag) + { + $tagManager = new TagManager(); + $tagManager->initializeDefaultTags(); + return $tagManager->createTagFromReflection($reflectionTag); + } + + /** + * @return string + */ + public function getName() + { + return 'author'; + } + + /** + * @param string $authorEmail + * @return AuthorTag + */ + public function setAuthorEmail($authorEmail) + { + $this->authorEmail = $authorEmail; + return $this; + } + + /** + * @return string + */ + public function getAuthorEmail() + { + return $this->authorEmail; + } + + /** + * @param string $authorName + * @return AuthorTag + */ + public function setAuthorName($authorName) + { + $this->authorName = $authorName; + return $this; + } + + /** + * @return string + */ + public function getAuthorName() + { + return $this->authorName; + } + + /** + * @return string + */ + public function generate() + { + $output = '@author' + . ((!empty($this->authorName)) ? ' ' . $this->authorName : '') + . ((!empty($this->authorEmail)) ? ' <' . $this->authorEmail . '>' : ''); + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/GenericTag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/GenericTag.php new file mode 100644 index 0000000..d4ea108 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/GenericTag.php @@ -0,0 +1,88 @@ +setName($name); + } + + if (!empty($content)) { + $this->setContent($content); + } + } + + /** + * @param string $name + * @return GenericTag + */ + public function setName($name) + { + $this->name = ltrim($name, '@'); + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $content + * @return GenericTag + */ + public function setContent($content) + { + $this->content = $content; + return $this; + } + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @return string + */ + public function generate() + { + $output = '@' . $this->name + . ((!empty($this->content)) ? ' ' . $this->content : ''); + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/LicenseTag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/LicenseTag.php new file mode 100644 index 0000000..832d434 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/LicenseTag.php @@ -0,0 +1,110 @@ +setUrl($url); + } + + if (!empty($licenseName)) { + $this->setLicenseName($licenseName); + } + } + + /** + * @param ReflectionTagInterface $reflectionTag + * @return ReturnTag + * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead + */ + public static function fromReflection(ReflectionTagInterface $reflectionTag) + { + $tagManager = new TagManager(); + $tagManager->initializeDefaultTags(); + return $tagManager->createTagFromReflection($reflectionTag); + } + + /** + * @return string + */ + public function getName() + { + return 'license'; + } + + /** + * @param string $url + * @return LicenseTag + */ + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $name + * @return LicenseTag + */ + public function setLicenseName($name) + { + $this->licenseName = $name; + return $this; + } + + /** + * @return string + */ + public function getLicenseName() + { + return $this->licenseName; + } + + /** + * @return string + */ + public function generate() + { + $output = '@license' + . ((!empty($this->url)) ? ' ' . $this->url : '') + . ((!empty($this->licenseName)) ? ' ' . $this->licenseName : ''); + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/MethodTag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/MethodTag.php new file mode 100644 index 0000000..fc68c6d --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/MethodTag.php @@ -0,0 +1,98 @@ +setMethodName($methodName); + } + + $this->setIsStatic((bool) $isStatic); + + parent::__construct($types, $description); + } + + /** + * @return string + */ + public function getName() + { + return 'method'; + } + + /** + * @param boolean $isStatic + * @return MethodTag + */ + public function setIsStatic($isStatic) + { + $this->isStatic = $isStatic; + return $this; + } + + /** + * @return boolean + */ + public function isStatic() + { + return $this->isStatic; + } + + /** + * @param string $methodName + * @return MethodTag + */ + public function setMethodName($methodName) + { + $this->methodName = rtrim($methodName, ')('); + return $this; + } + + /** + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * @return string + */ + public function generate() + { + $output = '@method' + . (($this->isStatic) ? ' static' : '') + . ((!empty($this->types)) ? ' ' . $this->getTypesAsString() : '') + . ((!empty($this->methodName)) ? ' ' . $this->methodName . '()' : '') + . ((!empty($this->description)) ? ' ' . $this->description : ''); + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/ParamTag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/ParamTag.php new file mode 100644 index 0000000..4753eee --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/ParamTag.php @@ -0,0 +1,124 @@ +setVariableName($variableName); + } + + parent::__construct($types, $description); + } + + /** + * @param ReflectionTagInterface $reflectionTag + * @return ReturnTag + * @deprecated Deprecated in 2.3. Use TagManager::createTagFromReflection() instead + */ + public static function fromReflection(ReflectionTagInterface $reflectionTag) + { + $tagManager = new TagManager(); + $tagManager->initializeDefaultTags(); + return $tagManager->createTagFromReflection($reflectionTag); + } + + /** + * @return string + */ + public function getName() + { + return 'param'; + } + + /** + * @param string $variableName + * @return ParamTag + */ + public function setVariableName($variableName) + { + $this->variableName = ltrim($variableName, '$'); + return $this; + } + + /** + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * @param string $datatype + * @return ReturnTag + * @deprecated Deprecated in 2.3. Use setTypes() instead + */ + public function setDatatype($datatype) + { + return $this->setTypes($datatype); + } + + /** + * @return string + * @deprecated Deprecated in 2.3. Use getTypes() or getTypesAsString() instead + */ + public function getDatatype() + { + return $this->getTypesAsString(); + } + + /** + * @param string $paramName + * @return ParamTag + * @deprecated Deprecated in 2.3. Use setVariableName() instead + */ + public function setParamName($paramName) + { + return $this->setVariableName($paramName); + } + + /** + * @return string + * @deprecated Deprecated in 2.3. Use getVariableName() instead + */ + public function getParamName() + { + return $this->getVariableName(); + } + + /** + * @return string + */ + public function generate() + { + $output = '@param' + . ((!empty($this->types)) ? ' ' . $this->getTypesAsString() : '') + . ((!empty($this->variableName)) ? ' $' . $this->variableName : '') + . ((!empty($this->description)) ? ' ' . $this->description : ''); + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/PropertyTag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/PropertyTag.php new file mode 100644 index 0000000..6a1de98 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/PropertyTag.php @@ -0,0 +1,71 @@ +setPropertyName($propertyName); + } + + parent::__construct($types, $description); + } + + /** + * @return string + */ + public function getName() + { + return 'property'; + } + + /** + * @param string $propertyName + * @return self + */ + public function setPropertyName($propertyName) + { + $this->propertyName = ltrim($propertyName, '$'); + return $this; + } + + /** + * @return string + */ + public function getPropertyName() + { + return $this->propertyName; + } + + /** + * @return string + */ + public function generate() + { + $output = '@property' + . ((!empty($this->types)) ? ' ' . $this->getTypesAsString() : '') + . ((!empty($this->propertyName)) ? ' $' . $this->propertyName : '') + . ((!empty($this->description)) ? ' ' . $this->description : ''); + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/ReturnTag.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/ReturnTag.php new file mode 100644 index 0000000..d201557 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/ReturnTag.php @@ -0,0 +1,67 @@ +initializeDefaultTags(); + return $tagManager->createTagFromReflection($reflectionTag); + } + + /** + * @return string + */ + public function getName() + { + return 'return'; + } + + /** + * @param string $datatype + * @return ReturnTag + * @deprecated Deprecated in 2.3. Use setTypes() instead + */ + public function setDatatype($datatype) + { + return $this->setTypes($datatype); + } + + /** + * @return string + * @deprecated Deprecated in 2.3. Use getTypes() or getTypesAsString() instead + */ + public function getDatatype() + { + return $this->getTypesAsString(); + } + + /** + * @return string + */ + public function generate() + { + $output = '@return ' + . $this->getTypesAsString() + . ((!empty($this->description)) ? ' ' . $this->description : ''); + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/TagInterface.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/TagInterface.php new file mode 100644 index 0000000..70d46b9 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/Tag/TagInterface.php @@ -0,0 +1,16 @@ +types)) ? ' ' . $this->getTypesAsString() : '') + . ((!empty($this->description)) ? ' ' . $this->description : ''); + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlock/TagManager.php b/vendor/zendframework/zend-code/src/Generator/DocBlock/TagManager.php new file mode 100644 index 0000000..00531f6 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlock/TagManager.php @@ -0,0 +1,69 @@ +addPrototype(new Tag\ParamTag()); + $this->addPrototype(new Tag\ReturnTag()); + $this->addPrototype(new Tag\MethodTag()); + $this->addPrototype(new Tag\PropertyTag()); + $this->addPrototype(new Tag\AuthorTag()); + $this->addPrototype(new Tag\LicenseTag()); + $this->addPrototype(new Tag\ThrowsTag()); + $this->setGenericPrototype(new Tag\GenericTag()); + } + + /** + * @param ReflectionTagInterface $reflectionTag + * @return TagInterface + */ + public function createTagFromReflection(ReflectionTagInterface $reflectionTag) + { + $tagName = $reflectionTag->getName(); + + /* @var TagInterface $newTag */ + $newTag = $this->getClonedPrototype($tagName); + + // transport any properties via accessors and mutators from reflection to codegen object + $reflectionClass = new \ReflectionClass($reflectionTag); + foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if (substr($method->getName(), 0, 3) == 'get') { + $propertyName = substr($method->getName(), 3); + if (method_exists($newTag, 'set' . $propertyName)) { + $newTag->{'set' . $propertyName}($reflectionTag->{'get' . $propertyName}()); + } + } elseif (substr($method->getName(), 0, 2) == 'is') { + $propertyName = ucfirst($method->getName()); + if (method_exists($newTag, 'set' . $propertyName)) { + $newTag->{'set' . $propertyName}($reflectionTag->{$method->getName()}()); + } + } + } + return $newTag; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/DocBlockGenerator.php b/vendor/zendframework/zend-code/src/Generator/DocBlockGenerator.php new file mode 100644 index 0000000..1a84335 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/DocBlockGenerator.php @@ -0,0 +1,274 @@ +setSourceContent($reflectionDocBlock->getContents()); + $docBlock->setSourceDirty(false); + + $docBlock->setShortDescription($reflectionDocBlock->getShortDescription()); + $docBlock->setLongDescription($reflectionDocBlock->getLongDescription()); + + foreach ($reflectionDocBlock->getTags() as $tag) { + $docBlock->setTag(self::getTagManager()->createTagFromReflection($tag)); + } + + return $docBlock; + } + + /** + * Generate from array + * + * @configkey shortdescription string The short description for this doc block + * @configkey longdescription string The long description for this doc block + * @configkey tags array + * + * @throws Exception\InvalidArgumentException + * @param array $array + * @return DocBlockGenerator + */ + public static function fromArray(array $array) + { + $docBlock = new static(); + + foreach ($array as $name => $value) { + // normalize key + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'shortdescription': + $docBlock->setShortDescription($value); + break; + case 'longdescription': + $docBlock->setLongDescription($value); + break; + case 'tags': + $docBlock->setTags($value); + break; + } + } + + return $docBlock; + } + + protected static function getTagManager() + { + if (!isset(static::$tagManager)) { + static::$tagManager = new TagManager(); + static::$tagManager->initializeDefaultTags(); + } + return static::$tagManager; + } + + /** + * @param string $shortDescription + * @param string $longDescription + * @param array $tags + */ + public function __construct($shortDescription = null, $longDescription = null, array $tags = array()) + { + if ($shortDescription) { + $this->setShortDescription($shortDescription); + } + if ($longDescription) { + $this->setLongDescription($longDescription); + } + if (is_array($tags) && $tags) { + $this->setTags($tags); + } + } + + /** + * @param string $shortDescription + * @return DocBlockGenerator + */ + public function setShortDescription($shortDescription) + { + $this->shortDescription = $shortDescription; + return $this; + } + + /** + * @return string + */ + public function getShortDescription() + { + return $this->shortDescription; + } + + /** + * @param string $longDescription + * @return DocBlockGenerator + */ + public function setLongDescription($longDescription) + { + $this->longDescription = $longDescription; + return $this; + } + + /** + * @return string + */ + public function getLongDescription() + { + return $this->longDescription; + } + + /** + * @param array $tags + * @return DocBlockGenerator + */ + public function setTags(array $tags) + { + foreach ($tags as $tag) { + $this->setTag($tag); + } + + return $this; + } + + /** + * @param array|TagInterface $tag + * @throws Exception\InvalidArgumentException + * @return DocBlockGenerator + */ + public function setTag($tag) + { + if (is_array($tag)) { + // use deprecated Tag class for backward compatiblity to old array-keys + $genericTag = new Tag(); + $genericTag->setOptions($tag); + $tag = $genericTag; + } elseif (!$tag instanceof TagInterface) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects either an array of method options or an instance of %s\DocBlock\Tag\TagInterface', + __METHOD__, + __NAMESPACE__ + )); + } + + $this->tags[] = $tag; + return $this; + } + + /** + * @return TagInterface[] + */ + public function getTags() + { + return $this->tags; + } + + /** + * @param bool $value + * @return DocBlockGenerator + */ + public function setWordWrap($value) + { + $this->wordwrap = (bool) $value; + return $this; + } + + /** + * @return bool + */ + public function getWordWrap() + { + return $this->wordwrap; + } + + /** + * @return string + */ + public function generate() + { + if (!$this->isSourceDirty()) { + return $this->docCommentize(trim($this->getSourceContent())); + } + + $output = ''; + if (null !== ($sd = $this->getShortDescription())) { + $output .= $sd . self::LINE_FEED . self::LINE_FEED; + } + if (null !== ($ld = $this->getLongDescription())) { + $output .= $ld . self::LINE_FEED . self::LINE_FEED; + } + + /* @var $tag GeneratorInterface */ + foreach ($this->getTags() as $tag) { + $output .= $tag->generate() . self::LINE_FEED; + } + + return $this->docCommentize(trim($output)); + } + + /** + * @param string $content + * @return string + */ + protected function docCommentize($content) + { + $indent = $this->getIndentation(); + $output = $indent . '/**' . self::LINE_FEED; + $content = $this->getWordWrap() == true ? wordwrap($content, 80, self::LINE_FEED) : $content; + $lines = explode(self::LINE_FEED, $content); + foreach ($lines as $line) { + $output .= $indent . ' *'; + if ($line) { + $output .= " $line"; + } + $output .= self::LINE_FEED; + } + $output .= $indent . ' */' . self::LINE_FEED; + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/Exception/ExceptionInterface.php b/vendor/zendframework/zend-code/src/Generator/Exception/ExceptionInterface.php new file mode 100644 index 0000000..226b37c --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/Exception/ExceptionInterface.php @@ -0,0 +1,16 @@ +setOptions($options); + } + } + + /** + * Use this if you intend on generating code generation objects based on the same file. + * This will keep previous changes to the file in tact during the same PHP process + * + * @param string $filePath + * @param bool $includeIfNotAlreadyIncluded + * @throws ReflectionException\InvalidArgumentException If file does not exists + * @throws ReflectionException\RuntimeException If file exists but is not included or required + * @return FileGenerator + */ + public static function fromReflectedFileName($filePath, $includeIfNotAlreadyIncluded = true) + { + $fileReflector = new FileReflection($filePath, $includeIfNotAlreadyIncluded); + $codeGenerator = static::fromReflection($fileReflector); + + return $codeGenerator; + } + + /** + * @param FileReflection $fileReflection + * @return FileGenerator + */ + public static function fromReflection(FileReflection $fileReflection) + { + $file = new static(); + + $file->setSourceContent($fileReflection->getContents()); + $file->setSourceDirty(false); + + $uses = $fileReflection->getUses(); + + foreach ($fileReflection->getClasses() as $class) { + $phpClass = ClassGenerator::fromReflection($class); + $phpClass->setContainingFileGenerator($file); + + foreach ($uses as $fileUse) { + $phpClass->addUse($fileUse['use'], $fileUse['as']); + } + + $file->setClass($phpClass); + } + + $namespace = $fileReflection->getNamespace(); + + if ($namespace != '') { + $file->setNamespace($namespace); + } + + if ($uses) { + $file->setUses($uses); + } + + if (($fileReflection->getDocComment() != '')) { + $docBlock = $fileReflection->getDocBlock(); + $file->setDocBlock(DocBlockGenerator::fromReflection($docBlock)); + } + + return $file; + } + + /** + * @param array $values + * @return FileGenerator + */ + public static function fromArray(array $values) + { + $fileGenerator = new static; + foreach ($values as $name => $value) { + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'filename': + $fileGenerator->setFilename($value); + continue; + case 'class': + $fileGenerator->setClass(($value instanceof ClassGenerator) ? $value : ClassGenerator::fromArray($value)); + continue; + case 'requiredfiles': + $fileGenerator->setRequiredFiles($value); + continue; + default: + if (property_exists($fileGenerator, $name)) { + $fileGenerator->{$name} = $value; + } elseif (method_exists($fileGenerator, 'set' . $name)) { + $fileGenerator->{'set' . $name}($value); + } + } + } + + return $fileGenerator; + } + + /** + * @param DocBlockGenerator|array|string $docBlock + * @throws Exception\InvalidArgumentException + * @return FileGenerator + */ + public function setDocBlock($docBlock) + { + if (is_string($docBlock)) { + $docBlock = array('shortDescription' => $docBlock); + } + + if (is_array($docBlock)) { + $docBlock = new DocBlockGenerator($docBlock); + } elseif (!$docBlock instanceof DocBlockGenerator) { + throw new Exception\InvalidArgumentException(sprintf( + '%s is expecting either a string, array or an instance of %s\DocBlockGenerator', + __METHOD__, + __NAMESPACE__ + )); + } + + $this->docBlock = $docBlock; + return $this; + } + + /** + * @return DocBlockGenerator + */ + public function getDocBlock() + { + return $this->docBlock; + } + + /** + * @param array $requiredFiles + * @return FileGenerator + */ + public function setRequiredFiles(array $requiredFiles) + { + $this->requiredFiles = $requiredFiles; + return $this; + } + + /** + * @return array + */ + public function getRequiredFiles() + { + return $this->requiredFiles; + } + + /** + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @param string $namespace + * @return FileGenerator + */ + public function setNamespace($namespace) + { + $this->namespace = (string) $namespace; + return $this; + } + + /** + * Returns an array with the first element the use statement, second is the as part. + * If $withResolvedAs is set to true, there will be a third element that is the + * "resolved" as statement, as the second part is not required in use statements + * + * @param bool $withResolvedAs + * @return array + */ + public function getUses($withResolvedAs = false) + { + $uses = $this->uses; + if ($withResolvedAs) { + for ($useIndex = 0, $count = count($uses); $useIndex < $count; $useIndex++) { + if ($uses[$useIndex][1] == '') { + if (($lastSeparator = strrpos($uses[$useIndex][0], '\\')) !== false) { + $uses[$useIndex][2] = substr($uses[$useIndex][0], $lastSeparator + 1); + } else { + $uses[$useIndex][2] = $uses[$useIndex][0]; + } + } else { + $uses[$useIndex][2] = $uses[$useIndex][1]; + } + } + } + + return $uses; + } + + /** + * @param array $uses + * @return FileGenerator + */ + public function setUses(array $uses) + { + foreach ($uses as $use) { + $use = (array) $use; + if (array_key_exists('use', $use) && array_key_exists('as', $use)) { + $import = $use['use']; + $alias = $use['as']; + } elseif (count($use) == 2) { + list($import, $alias) = $use; + } else { + $import = current($use); + $alias = null; + } + $this->setUse($import, $alias); + } + return $this; + } + + /** + * @param string $use + * @param null|string $as + * @return FileGenerator + */ + public function setUse($use, $as = null) + { + if (!in_array(array($use, $as), $this->uses)) { + $this->uses[] = array($use, $as); + } + return $this; + } + + /** + * @param array $classes + * @return FileGenerator + */ + public function setClasses(array $classes) + { + foreach ($classes as $class) { + $this->setClass($class); + } + + return $this; + } + + /** + * @param string $name + * @return ClassGenerator + */ + public function getClass($name = null) + { + if ($name === null) { + reset($this->classes); + + return current($this->classes); + } + + return $this->classes[(string) $name]; + } + + /** + * @param array|string|ClassGenerator $class + * @throws Exception\InvalidArgumentException + * @return FileGenerator + */ + public function setClass($class) + { + if (is_array($class)) { + $class = ClassGenerator::fromArray($class); + } elseif (is_string($class)) { + $class = new ClassGenerator($class); + } elseif (!$class instanceof ClassGenerator) { + throw new Exception\InvalidArgumentException(sprintf( + '%s is expecting either a string, array or an instance of %s\ClassGenerator', + __METHOD__, + __NAMESPACE__ + )); + } + + // @todo check for dup here + $className = $class->getName(); + $this->classes[$className] = $class; + + return $this; + } + + /** + * @param string $filename + * @return FileGenerator + */ + public function setFilename($filename) + { + $this->filename = (string) $filename; + return $this; + } + + /** + * @return string + */ + public function getFilename() + { + return $this->filename; + } + + /** + * @return ClassGenerator[] + */ + public function getClasses() + { + return $this->classes; + } + + /** + * @param string $body + * @return FileGenerator + */ + public function setBody($body) + { + $this->body = (string) $body; + return $this; + } + + /** + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * @return bool + */ + public function isSourceDirty() + { + $docBlock = $this->getDocBlock(); + if ($docBlock && $docBlock->isSourceDirty()) { + return true; + } + + foreach ($this->classes as $class) { + if ($class->isSourceDirty()) { + return true; + } + } + + return parent::isSourceDirty(); + } + + /** + * @return string + */ + public function generate() + { + if ($this->isSourceDirty() === false) { + return $this->sourceContent; + } + + $output = ''; + + // @note body gets populated when FileGenerator created + // from a file. @see fromReflection and may also be set + // via FileGenerator::setBody + $body = $this->getBody(); + + // start with the body (if there), or open tag + if (preg_match('#(?:\s*)<\?php#', $body) == false) { + $output = 'getDocBlock())) { + $docBlock->setIndentation(''); + + if (preg_match('#/\* Zend_Code_Generator_FileGenerator-DocBlockMarker \*/#m', $output)) { + $output = preg_replace('#/\* Zend_Code_Generator_FileGenerator-DocBlockMarker \*/#m', $docBlock->generate(), $output, 1); + } else { + $output .= $docBlock->generate() . self::LINE_FEED; + } + } + + // newline + $output .= self::LINE_FEED; + + // namespace, if any + $namespace = $this->getNamespace(); + if ($namespace) { + $namespace = sprintf('namespace %s;%s', $namespace, str_repeat(self::LINE_FEED, 2)); + if (preg_match('#/\* Zend_Code_Generator_FileGenerator-NamespaceMarker \*/#m', $output)) { + $output = preg_replace( + '#/\* Zend_Code_Generator_FileGenerator-NamespaceMarker \*/#m', + $namespace, + $output, + 1 + ); + } else { + $output .= $namespace; + } + } + + // process required files + // @todo marker replacement for required files + $requiredFiles = $this->getRequiredFiles(); + if (!empty($requiredFiles)) { + foreach ($requiredFiles as $requiredFile) { + $output .= 'require_once \'' . $requiredFile . '\';' . self::LINE_FEED; + } + + $output .= self::LINE_FEED; + } + + $classes = $this->getClasses(); + $classUses = array(); + //build uses array + foreach ($classes as $class) { + //check for duplicate use statements + $uses = $class->getUses(); + if (!empty($uses) && is_array($uses)) { + $classUses = array_merge($classUses, $uses); + } + } + + // process import statements + $uses = $this->getUses(); + if (!empty($uses)) { + $useOutput = ''; + + foreach ($uses as $use) { + list($import, $alias) = $use; + if (null === $alias) { + $tempOutput = sprintf('%s', $import); + } else { + $tempOutput = sprintf('%s as %s', $import, $alias); + } + + //don't duplicate use statements + if (!in_array($tempOutput, $classUses)) { + $useOutput .= "use ". $tempOutput .";"; + $useOutput .= self::LINE_FEED; + } + } + $useOutput .= self::LINE_FEED; + + if (preg_match('#/\* Zend_Code_Generator_FileGenerator-UseMarker \*/#m', $output)) { + $output = preg_replace( + '#/\* Zend_Code_Generator_FileGenerator-UseMarker \*/#m', + $useOutput, + $output, + 1 + ); + } else { + $output .= $useOutput; + } + } + + // process classes + if (!empty($classes)) { + foreach ($classes as $class) { + $regex = str_replace('&', $class->getName(), '/\* Zend_Code_Generator_Php_File-ClassMarker: \{[A-Za-z0-9\\\]+?&\} \*/'); + if (preg_match('#' . $regex . '#m', $output)) { + $output = preg_replace('#' . $regex . '#', $class->generate(), $output, 1); + } else { + if ($namespace) { + $class->setNamespaceName(null); + } + $output .= $class->generate() . self::LINE_FEED; + } + } + } + + if (!empty($body)) { + // add an extra space between classes and + if (!empty($classes)) { + $output .= self::LINE_FEED; + } + + $output .= $body; + } + + return $output; + } + + /** + * @return FileGenerator + * @throws Exception\RuntimeException + */ + public function write() + { + if ($this->filename == '' || !is_writable(dirname($this->filename))) { + throw new Exception\RuntimeException('This code generator object is not writable.'); + } + file_put_contents($this->filename, $this->generate()); + + return $this; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/FileGeneratorRegistry.php b/vendor/zendframework/zend-code/src/Generator/FileGeneratorRegistry.php new file mode 100644 index 0000000..dfacb6b --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/FileGeneratorRegistry.php @@ -0,0 +1,44 @@ +getFilename(); + } + + if ($fileName == '') { + throw new RuntimeException('FileName does not exist.'); + } + + // cannot use realpath since the file might not exist, but we do need to have the index + // in the same DIRECTORY_SEPARATOR that realpath would use: + $fileName = str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $fileName); + + static::$fileCodeGenerators[$fileName] = $fileCodeGenerator; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/GeneratorInterface.php b/vendor/zendframework/zend-code/src/Generator/GeneratorInterface.php new file mode 100644 index 0000000..59e9d3a --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/GeneratorInterface.php @@ -0,0 +1,15 @@ +setSourceContent($reflectionMethod->getContents(false)); + $method->setSourceDirty(false); + + if ($reflectionMethod->getDocComment() != '') { + $method->setDocBlock(DocBlockGenerator::fromReflection($reflectionMethod->getDocBlock())); + } + + $method->setFinal($reflectionMethod->isFinal()); + + if ($reflectionMethod->isPrivate()) { + $method->setVisibility(self::VISIBILITY_PRIVATE); + } elseif ($reflectionMethod->isProtected()) { + $method->setVisibility(self::VISIBILITY_PROTECTED); + } else { + $method->setVisibility(self::VISIBILITY_PUBLIC); + } + + $method->setStatic($reflectionMethod->isStatic()); + + $method->setName($reflectionMethod->getName()); + + foreach ($reflectionMethod->getParameters() as $reflectionParameter) { + $method->setParameter(ParameterGenerator::fromReflection($reflectionParameter)); + } + + $method->setBody(static::clearBodyIndention($reflectionMethod->getBody())); + + return $method; + } + + /** + * Identify the space indention from the first line and remove this indention + * from all lines + * + * @param string $body + * + * @return string + */ + protected static function clearBodyIndention($body) + { + if (empty($body)) { + return $body; + } + + $lines = explode(PHP_EOL, $body); + + $indention = str_replace(trim($lines[1]), '', $lines[1]); + + foreach ($lines as $key => $line) { + if (substr($line, 0, strlen($indention)) == $indention) { + $lines[$key] = substr($line, strlen($indention)); + } + } + + $body = implode(PHP_EOL, $lines); + + return $body; + } + + /** + * Generate from array + * + * @configkey name string [required] Class Name + * @configkey docblock string The docblock information + * @configkey flags int Flags, one of MethodGenerator::FLAG_ABSTRACT MethodGenerator::FLAG_FINAL + * @configkey parameters string Class which this class is extending + * @configkey body string + * @configkey abstract bool + * @configkey final bool + * @configkey static bool + * @configkey visibility string + * + * @throws Exception\InvalidArgumentException + * @param array $array + * @return MethodGenerator + */ + public static function fromArray(array $array) + { + if (!isset($array['name'])) { + throw new Exception\InvalidArgumentException( + 'Method generator requires that a name is provided for this object' + ); + } + + $method = new static($array['name']); + foreach ($array as $name => $value) { + // normalize key + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'docblock': + $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value); + $method->setDocBlock($docBlock); + break; + case 'flags': + $method->setFlags($value); + break; + case 'parameters': + $method->setParameters($value); + break; + case 'body': + $method->setBody($value); + break; + case 'abstract': + $method->setAbstract($value); + break; + case 'final': + $method->setFinal($value); + break; + case 'static': + $method->setStatic($value); + break; + case 'visibility': + $method->setVisibility($value); + break; + } + } + + return $method; + } + + /** + * @param string $name + * @param array $parameters + * @param int $flags + * @param string $body + * @param DocBlockGenerator|string $docBlock + */ + public function __construct( + $name = null, + array $parameters = array(), + $flags = self::FLAG_PUBLIC, + $body = null, + $docBlock = null + ) { + if ($name) { + $this->setName($name); + } + if ($parameters) { + $this->setParameters($parameters); + } + if ($flags !== self::FLAG_PUBLIC) { + $this->setFlags($flags); + } + if ($body) { + $this->setBody($body); + } + if ($docBlock) { + $this->setDocBlock($docBlock); + } + } + + /** + * @param array $parameters + * @return MethodGenerator + */ + public function setParameters(array $parameters) + { + foreach ($parameters as $parameter) { + $this->setParameter($parameter); + } + + return $this; + } + + /** + * @param ParameterGenerator|array|string $parameter + * @throws Exception\InvalidArgumentException + * @return MethodGenerator + */ + public function setParameter($parameter) + { + if (is_string($parameter)) { + $parameter = new ParameterGenerator($parameter); + } + + if (is_array($parameter)) { + $parameter = ParameterGenerator::fromArray($parameter); + } + + if (!$parameter instanceof ParameterGenerator) { + throw new Exception\InvalidArgumentException(sprintf( + '%s is expecting either a string, array or an instance of %s\ParameterGenerator', + __METHOD__, + __NAMESPACE__ + )); + } + + $this->parameters[$parameter->getName()] = $parameter; + + return $this; + } + + /** + * @return ParameterGenerator[] + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * @param string $body + * @return MethodGenerator + */ + public function setBody($body) + { + $this->body = $body; + return $this; + } + + /** + * @return string + */ + public function getBody() + { + return $this->body; + } + + /** + * @return string + */ + public function generate() + { + $output = ''; + + $indent = $this->getIndentation(); + + if (($docBlock = $this->getDocBlock()) !== null) { + $docBlock->setIndentation($indent); + $output .= $docBlock->generate(); + } + + $output .= $indent; + + if ($this->isAbstract()) { + $output .= 'abstract '; + } else { + $output .= (($this->isFinal()) ? 'final ' : ''); + } + + $output .= $this->getVisibility() + . (($this->isStatic()) ? ' static' : '') + . ' function ' . $this->getName() . '('; + + $parameters = $this->getParameters(); + if (!empty($parameters)) { + foreach ($parameters as $parameter) { + $parameterOutput[] = $parameter->generate(); + } + + $output .= implode(', ', $parameterOutput); + } + + $output .= ')'; + + if ($this->isAbstract()) { + return $output . ';'; + } + + $output .= self::LINE_FEED . $indent . '{' . self::LINE_FEED; + + if ($this->body) { + $output .= preg_replace('#^((?![a-zA-Z0-9_-]+;).+?)$#m', $indent . $indent . '$1', trim($this->body)) + . self::LINE_FEED; + } + + $output .= $indent . '}' . self::LINE_FEED; + + return $output; + } + + public function __toString() + { + return $this->generate(); + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/ParameterGenerator.php b/vendor/zendframework/zend-code/src/Generator/ParameterGenerator.php new file mode 100644 index 0000000..3714565 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/ParameterGenerator.php @@ -0,0 +1,300 @@ +setName($reflectionParameter->getName()); + + if ($reflectionParameter->isArray()) { + $param->setType('array'); + } elseif (method_exists($reflectionParameter, 'isCallable') && $reflectionParameter->isCallable()) { + $param->setType('callable'); + } else { + $typeClass = $reflectionParameter->getClass(); + if ($typeClass) { + $parameterType = $typeClass->getName(); + $currentNamespace = $reflectionParameter->getDeclaringClass()->getNamespaceName(); + + if (!empty($currentNamespace) && substr($parameterType, 0, strlen($currentNamespace)) == $currentNamespace) { + $parameterType = substr($parameterType, strlen($currentNamespace) + 1); + } else { + $parameterType = '\\' . trim($parameterType, '\\'); + } + + $param->setType($parameterType); + } + } + + $param->setPosition($reflectionParameter->getPosition()); + + if ($reflectionParameter->isOptional()) { + $param->setDefaultValue($reflectionParameter->getDefaultValue()); + } + $param->setPassedByReference($reflectionParameter->isPassedByReference()); + + return $param; + } + + /** + * Generate from array + * + * @configkey name string [required] Class Name + * @configkey type string + * @configkey defaultvalue null|bool|string|int|float|array|ValueGenerator + * @configkey passedbyreference bool + * @configkey position int + * @configkey sourcedirty bool + * @configkey indentation string + * @configkey sourcecontent string + * + * @throws Exception\InvalidArgumentException + * @param array $array + * @return ParameterGenerator + */ + public static function fromArray(array $array) + { + if (!isset($array['name'])) { + throw new Exception\InvalidArgumentException( + 'Paramerer generator requires that a name is provided for this object' + ); + } + + $param = new static($array['name']); + foreach ($array as $name => $value) { + // normalize key + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'type': + $param->setType($value); + break; + case 'defaultvalue': + $param->setDefaultValue($value); + break; + case 'passedbyreference': + $param->setPassedByReference($value); + break; + case 'position': + $param->setPosition($value); + break; + case 'sourcedirty': + $param->setSourceDirty($value); + break; + case 'indentation': + $param->setIndentation($value); + break; + case 'sourcecontent': + $param->setSourceContent($value); + break; + } + } + + return $param; + } + + /** + * @param string $name + * @param string $type + * @param mixed $defaultValue + * @param int $position + * @param bool $passByReference + */ + public function __construct( + $name = null, + $type = null, + $defaultValue = null, + $position = null, + $passByReference = false + ) { + if (null !== $name) { + $this->setName($name); + } + if (null !== $type) { + $this->setType($type); + } + if (null !== $defaultValue) { + $this->setDefaultValue($defaultValue); + } + if (null !== $position) { + $this->setPosition($position); + } + if (false !== $passByReference) { + $this->setPassedByReference(true); + } + } + + /** + * @param string $type + * @return ParameterGenerator + */ + public function setType($type) + { + $this->type = (string) $type; + return $this; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @param string $name + * @return ParameterGenerator + */ + public function setName($name) + { + $this->name = (string) $name; + return $this; + } + + /** + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Set the default value of the parameter. + * + * Certain variables are difficult to express + * + * @param null|bool|string|int|float|array|ValueGenerator $defaultValue + * @return ParameterGenerator + */ + public function setDefaultValue($defaultValue) + { + if (!($defaultValue instanceof ValueGenerator)) { + $defaultValue = new ValueGenerator($defaultValue); + } + $this->defaultValue = $defaultValue; + + return $this; + } + + /** + * @return string + */ + public function getDefaultValue() + { + return $this->defaultValue; + } + + /** + * @param int $position + * @return ParameterGenerator + */ + public function setPosition($position) + { + $this->position = (int) $position; + return $this; + } + + /** + * @return int + */ + public function getPosition() + { + return $this->position; + } + + /** + * @return bool + */ + public function getPassedByReference() + { + return $this->passedByReference; + } + + /** + * @param bool $passedByReference + * @return ParameterGenerator + */ + public function setPassedByReference($passedByReference) + { + $this->passedByReference = (bool) $passedByReference; + return $this; + } + + /** + * @return string + */ + public function generate() + { + $output = ''; + + if ($this->type && !in_array($this->type, static::$simple)) { + $output .= $this->type . ' '; + } + + if (true === $this->passedByReference) { + $output .= '&'; + } + + $output .= '$' . $this->name; + + if ($this->defaultValue !== null) { + $output .= ' = '; + if (is_string($this->defaultValue)) { + $output .= ValueGenerator::escape($this->defaultValue); + } elseif ($this->defaultValue instanceof ValueGenerator) { + $this->defaultValue->setOutputMode(ValueGenerator::OUTPUT_SINGLE_LINE); + $output .= $this->defaultValue; + } else { + $output .= $this->defaultValue; + } + } + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/PropertyGenerator.php b/vendor/zendframework/zend-code/src/Generator/PropertyGenerator.php new file mode 100644 index 0000000..159e415 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/PropertyGenerator.php @@ -0,0 +1,226 @@ +setName($reflectionProperty->getName()); + + $allDefaultProperties = $reflectionProperty->getDeclaringClass()->getDefaultProperties(); + + $property->setDefaultValue($allDefaultProperties[$reflectionProperty->getName()]); + + if ($reflectionProperty->getDocComment() != '') { + $property->setDocBlock(DocBlockGenerator::fromReflection($reflectionProperty->getDocBlock())); + } + + if ($reflectionProperty->isStatic()) { + $property->setStatic(true); + } + + if ($reflectionProperty->isPrivate()) { + $property->setVisibility(self::VISIBILITY_PRIVATE); + } elseif ($reflectionProperty->isProtected()) { + $property->setVisibility(self::VISIBILITY_PROTECTED); + } else { + $property->setVisibility(self::VISIBILITY_PUBLIC); + } + + $property->setSourceDirty(false); + + return $property; + } + + /** + * Generate from array + * + * @configkey name string [required] Class Name + * @configkey const bool + * @configkey defaultvalue null|bool|string|int|float|array|ValueGenerator + * @configkey flags int + * @configkey abstract bool + * @configkey final bool + * @configkey static bool + * @configkey visibility string + * + * @throws Exception\InvalidArgumentException + * @param array $array + * @return PropertyGenerator + */ + public static function fromArray(array $array) + { + if (!isset($array['name'])) { + throw new Exception\InvalidArgumentException( + 'Property generator requires that a name is provided for this object' + ); + } + + $property = new static($array['name']); + foreach ($array as $name => $value) { + // normalize key + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'const': + $property->setConst($value); + break; + case 'defaultvalue': + $property->setDefaultValue($value); + break; + case 'docblock': + $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value); + $property->setDocBlock($docBlock); + break; + case 'flags': + $property->setFlags($value); + break; + case 'abstract': + $property->setAbstract($value); + break; + case 'final': + $property->setFinal($value); + break; + case 'static': + $property->setStatic($value); + break; + case 'visibility': + $property->setVisibility($value); + break; + } + } + + return $property; + } + + /** + * @param string $name + * @param PropertyValueGenerator|string|array $defaultValue + * @param int $flags + */ + public function __construct($name = null, $defaultValue = null, $flags = self::FLAG_PUBLIC) + { + if (null !== $name) { + $this->setName($name); + } + if (null !== $defaultValue) { + $this->setDefaultValue($defaultValue); + } + if ($flags !== self::FLAG_PUBLIC) { + $this->setFlags($flags); + } + } + + /** + * @param bool $const + * @return PropertyGenerator + */ + public function setConst($const) + { + if ($const) { + $this->removeFlag(self::FLAG_PUBLIC | self::FLAG_PRIVATE | self::FLAG_PROTECTED); + $this->setFlags(self::FLAG_CONSTANT); + } else { + $this->removeFlag(self::FLAG_CONSTANT); + } + + return $this; + } + + /** + * @return bool + */ + public function isConst() + { + return (bool) ($this->flags & self::FLAG_CONSTANT); + } + + /** + * @param PropertyValueGenerator|mixed $defaultValue + * @param string $defaultValueType + * @param string $defaultValueOutputMode + * + * @return PropertyGenerator + */ + public function setDefaultValue($defaultValue, $defaultValueType = PropertyValueGenerator::TYPE_AUTO, $defaultValueOutputMode = PropertyValueGenerator::OUTPUT_MULTIPLE_LINE) + { + if (!($defaultValue instanceof PropertyValueGenerator)) { + $defaultValue = new PropertyValueGenerator($defaultValue, $defaultValueType, $defaultValueOutputMode); + } + + $this->defaultValue = $defaultValue; + + return $this; + } + + /** + * @return PropertyValueGenerator + */ + public function getDefaultValue() + { + return $this->defaultValue; + } + + /** + * @throws Exception\RuntimeException + * @return string + */ + public function generate() + { + $name = $this->getName(); + $defaultValue = $this->getDefaultValue(); + + $output = ''; + + if (($docBlock = $this->getDocBlock()) !== null) { + $docBlock->setIndentation(' '); + $output .= $docBlock->generate(); + } + + if ($this->isConst()) { + if ($defaultValue !== null && !$defaultValue->isValidConstantType()) { + throw new Exception\RuntimeException(sprintf( + 'The property %s is said to be ' + . 'constant but does not have a valid constant value.', + $this->name + )); + } + $output .= $this->indentation . 'const ' . $name . ' = ' + . (($defaultValue !== null) ? $defaultValue->generate() : 'null;'); + } else { + $output .= $this->indentation + . $this->getVisibility() + . (($this->isStatic()) ? ' static' : '') + . ' $' . $name . ' = ' + . (($defaultValue !== null) ? $defaultValue->generate() : 'null;'); + } + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/PropertyValueGenerator.php b/vendor/zendframework/zend-code/src/Generator/PropertyValueGenerator.php new file mode 100644 index 0000000..bf4156c --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/PropertyValueGenerator.php @@ -0,0 +1,23 @@ +getName()); + + $cg->setSourceContent($cg->getSourceContent()); + $cg->setSourceDirty(false); + + if ($classReflection->getDocComment() != '') { + $cg->setDocBlock(DocBlockGenerator::fromReflection($classReflection->getDocBlock())); + } + + // set the namespace + if ($classReflection->inNamespace()) { + $cg->setNamespaceName($classReflection->getNamespaceName()); + } + + $properties = array(); + foreach ($classReflection->getProperties() as $reflectionProperty) { + if ($reflectionProperty->getDeclaringClass()->getName() == $classReflection->getName()) { + $properties[] = PropertyGenerator::fromReflection($reflectionProperty); + } + } + $cg->addProperties($properties); + + $methods = array(); + foreach ($classReflection->getMethods() as $reflectionMethod) { + $className = ($cg->getNamespaceName()) + ? $cg->getNamespaceName() . '\\' . $cg->getName() + : $cg->getName(); + if ($reflectionMethod->getDeclaringClass()->getName() == $className) { + $methods[] = MethodGenerator::fromReflection($reflectionMethod); + } + } + $cg->addMethods($methods); + + return $cg; + } + + /** + * Generate from array + * + * @configkey name string [required] Class Name + * @configkey filegenerator FileGenerator File generator that holds this class + * @configkey namespacename string The namespace for this class + * @configkey docblock string The docblock information + * @configkey properties + * @configkey methods + * + * @throws Exception\InvalidArgumentException + * @param array $array + * @return TraitGenerator + */ + public static function fromArray(array $array) + { + if (! isset($array['name'])) { + throw new Exception\InvalidArgumentException( + 'Class generator requires that a name is provided for this object' + ); + } + + $cg = new static($array['name']); + foreach ($array as $name => $value) { + // normalize key + switch (strtolower(str_replace(array('.', '-', '_'), '', $name))) { + case 'containingfile': + $cg->setContainingFileGenerator($value); + break; + case 'namespacename': + $cg->setNamespaceName($value); + break; + case 'docblock': + $docBlock = ($value instanceof DocBlockGenerator) ? $value : DocBlockGenerator::fromArray($value); + $cg->setDocBlock($docBlock); + break; + case 'properties': + $cg->addProperties($value); + break; + case 'methods': + $cg->addMethods($value); + break; + } + } + + return $cg; + } + + /** + * @param array|string $flags + * @return self + */ + public function setFlags($flags) + { + return $this; + } + + /** + * @param string $flag + * @return self + */ + public function addFlag($flag) + { + return $this; + } + + /** + * @param string $flag + * @return self + */ + public function removeFlag($flag) + { + return $this; + } + + /** + * @param bool $isFinal + * @return self + */ + public function setFinal($isFinal) + { + return $this; + } + + /** + * @param string $extendedClass + * @return self + */ + public function setExtendedClass($extendedClass) + { + return $this; + } + + /** + * @param array $implementedInterfaces + * @return self + */ + public function setImplementedInterfaces(array $implementedInterfaces) + { + return $this; + } + + /** + * @param bool $isAbstract + * @return self + */ + public function setAbstract($isAbstract) + { + return $this; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/TraitUsageGenerator.php b/vendor/zendframework/zend-code/src/Generator/TraitUsageGenerator.php new file mode 100644 index 0000000..0cd2b58 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/TraitUsageGenerator.php @@ -0,0 +1,353 @@ +classGenerator = $classGenerator; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addUse($use, $useAlias = null) + { + if (! empty($useAlias)) { + $use .= ' as ' . $useAlias; + } + + $this->uses[$use] = $use; + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function getUses() + { + return array_values($this->uses); + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addTrait($trait) + { + $traitName = $trait; + if (is_array($trait)) { + if (! array_key_exists('traitName', $trait)) { + throw new Exception\InvalidArgumentException('Missing required value for traitName'); + } + $traitName = $trait['traitName']; + + if (array_key_exists('aliases', $trait)) { + foreach ($trait['aliases'] as $alias) { + $this->addAlias($alias); + } + } + + if (array_key_exists('insteadof', $trait)) { + foreach ($trait['insteadof'] as $insteadof) { + $this->addTraitOverride($insteadof); + } + } + } + + if (! $this->hasTrait($traitName)) { + $this->traits[] = $traitName; + } + + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addTraits(array $traits) + { + foreach ($traits as $trait) { + $this->addTrait($trait); + } + + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function hasTrait($traitName) + { + return in_array($traitName, $this->traits); + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function getTraits() + { + return $this->traits; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function removeTrait($traitName) + { + $key = array_search($traitName, $this->traits); + if (false !== $key) { + unset($this->traits[$key]); + } + + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addTraitAlias($method, $alias, $visibility = null) + { + $traitAndMethod = $method; + if (is_array($method)) { + if (! array_key_exists('traitName', $method)) { + throw new Exception\InvalidArgumentException('Missing required argument "traitName" for $method'); + } + + if (! array_key_exists('method', $method)) { + throw new Exception\InvalidArgumentException('Missing required argument "method" for $method'); + } + + $traitAndMethod = $method['traitName'] . '::' . $method['method']; + } + + // Validations + if (false === strpos($traitAndMethod, "::")) { + throw new Exception\InvalidArgumentException( + 'Invalid Format: $method must be in the format of trait::method' + ); + } + if (! is_string($alias)) { + throw new Exception\InvalidArgumentException('Invalid Alias: $alias must be a string or array.'); + } + if ($this->classGenerator->hasMethod($alias)) { + throw new Exception\InvalidArgumentException('Invalid Alias: Method name already exists on this class.'); + } + if (null !== $visibility + && $visibility !== ReflectionMethod::IS_PUBLIC + && $visibility !== ReflectionMethod::IS_PRIVATE + && $visibility !== ReflectionMethod::IS_PROTECTED + ) { + throw new Exception\InvalidArgumentException( + 'Invalid Type: $visibility must of ReflectionMethod::IS_PUBLIC,' + . ' ReflectionMethod::IS_PRIVATE or ReflectionMethod::IS_PROTECTED' + ); + } + + list($trait, $method) = explode('::', $traitAndMethod); + if (! $this->hasTrait($trait)) { + throw new Exception\InvalidArgumentException('Invalid trait: Trait does not exists on this class'); + } + + $this->traitAliases[$traitAndMethod] = array( + 'alias' => $alias, + 'visibility' => $visibility + ); + + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function getTraitAliases() + { + return $this->traitAliases; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function addTraitOverride($method, $traitsToReplace) + { + if (false === is_array($traitsToReplace)) { + $traitsToReplace = array($traitsToReplace); + } + + $traitAndMethod = $method; + if (is_array($method)) { + if (! array_key_exists('traitName', $method)) { + throw new Exception\InvalidArgumentException('Missing required argument "traitName" for $method'); + } + + if (! array_key_exists('method', $method)) { + throw new Exception\InvalidArgumentException('Missing required argument "method" for $method'); + } + + $traitAndMethod = (string) $method['traitName'] . '::' . (string) $method['method']; + } + + // Validations + if (false === strpos($traitAndMethod, "::")) { + throw new Exception\InvalidArgumentException( + 'Invalid Format: $method must be in the format of trait::method' + ); + } + + list($trait, $method) = explode("::", $traitAndMethod); + if (! $this->hasTrait($trait)) { + throw new Exception\InvalidArgumentException('Invalid trait: Trait does not exists on this class'); + } + + if (! array_key_exists($traitAndMethod, $this->traitOverrides)) { + $this->traitOverrides[$traitAndMethod] = array(); + } + + foreach ($traitsToReplace as $traitToReplace) { + if (! is_string($traitToReplace)) { + throw new Exception\InvalidArgumentException( + 'Invalid Argument: $traitToReplace must be a string or array of strings' + ); + } + + if (! in_array($traitToReplace, $this->traitOverrides[$traitAndMethod])) { + $this->traitOverrides[$traitAndMethod][] = $traitToReplace; + } + } + + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function removeTraitOverride($method, $overridesToRemove = null) + { + if (! array_key_exists($method, $this->traitOverrides)) { + return $this; + } + + if (null === $overridesToRemove) { + unset($this->traitOverrides[$method]); + return $this; + } + + $overridesToRemove = (! is_array($overridesToRemove)) + ? array($overridesToRemove) + : $overridesToRemove; + foreach ($overridesToRemove as $traitToRemove) { + $key = array_search($traitToRemove, $this->traitOverrides[$method]); + if (false !== $key) { + unset($this->traitOverrides[$method][$key]); + } + } + return $this; + } + + /** + * @inherit Zend\Code\Generator\TraitUsageInterface + */ + public function getTraitOverrides() + { + return $this->traitOverrides; + } + + /** + * @inherit Zend\Code\Generator\GeneratorInterface + */ + public function generate() + { + $output = ''; + $indent = $this->getIndentation(); + $traits = $this->getTraits(); + + if (empty($traits)) { + return $output; + } + + $output .= $indent . 'use ' . implode(', ', $traits); + + $aliases = $this->getTraitAliases(); + $overrides = $this->getTraitOverrides(); + if (empty($aliases) && empty($overrides)) { + $output .= ";" . self::LINE_FEED . self::LINE_FEED; + return $output; + } + + $output .= ' {' . self::LINE_FEED; + foreach ($aliases as $method => $alias) { + $visibility = (null !== $alias['visibility']) + ? current(Reflection::getModifierNames($alias['visibility'])) . ' ' + : ''; + + // validation check + if ($this->classGenerator->hasMethod($alias['alias'])) { + throw new Exception\RuntimeException(sprintf( + 'Generation Error: Aliased method %s already exists on this class', + $alias['alias'] + )); + } + + $output .= + $indent + . $indent + . $method + . ' as ' + . $visibility + . $alias['alias'] + . ';' + . self::LINE_FEED; + } + + foreach ($overrides as $method => $insteadofTraits) { + foreach ($insteadofTraits as $insteadofTrait) { + $output .= + $indent + . $indent + . $method + . ' insteadof ' + . $insteadofTrait + . ';' + . self::LINE_FEED; + } + } + + $output .= self::LINE_FEED . $indent . '}' . self::LINE_FEED . self::LINE_FEED; + + return $output; + } +} diff --git a/vendor/zendframework/zend-code/src/Generator/TraitUsageInterface.php b/vendor/zendframework/zend-code/src/Generator/TraitUsageInterface.php new file mode 100644 index 0000000..e5c0bda --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/TraitUsageInterface.php @@ -0,0 +1,159 @@ +:: + * Option 2: Array + * key: traitName value: trait name + * key: method value: method name + * + * $traitToReplace: + * The name of the trait that you wish to supersede. + * + * This method provides 2 ways for defining the trait method. + * Option 1: String of trait to replace + * Option 2: Array of strings of traits to replace + + * @param mixed $method + * @param mixed $traitToReplace + */ + public function addTraitOverride($method, $traitsToReplace); + + /** + * Remove an override for a given trait::method + * + * $method: + * This method provides 2 ways for defining the trait method. + * Option 1: String Format: :: + * Option 2: Array + * key: traitName value: trait name + * key: method value: method name + * + * $overridesToRemove: + * The name of the trait that you wish to remove. + * + * This method provides 2 ways for defining the trait method. + * Option 1: String of trait to replace + * Option 2: Array of strings of traits to replace + * + * @param $traitAndMethod + * @param null $overridesToRemove + * @return $this + */ + public function removeTraitOverride($method, $overridesToRemove = null); + + /** + * Return trait overrides + * + * @return array + */ + public function getTraitOverrides(); +} diff --git a/vendor/zendframework/zend-code/src/Generator/ValueGenerator.php b/vendor/zendframework/zend-code/src/Generator/ValueGenerator.php new file mode 100644 index 0000000..50da865 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generator/ValueGenerator.php @@ -0,0 +1,435 @@ +setValue($value); + } + if ($type !== self::TYPE_AUTO) { + $this->setType($type); + } + if ($outputMode !== self::OUTPUT_MULTIPLE_LINE) { + $this->setOutputMode($outputMode); + } + if ($constants !== null) { + $this->constants = $constants; + } else { + $this->constants = new ArrayObject(); + } + } + + /** + * Init constant list by defined and magic constants + */ + public function initEnvironmentConstants() + { + $constants = array( + '__DIR__', + '__FILE__', + '__LINE__', + '__CLASS__', + '__TRAIT__', + '__METHOD__', + '__FUNCTION__', + '__NAMESPACE__', + '::' + ); + $constants = array_merge($constants, array_keys(get_defined_constants()), $this->constants->getArrayCopy()); + $this->constants->exchangeArray($constants); + } + + /** + * Add constant to list + * + * @param string $constant + * + * @return $this + */ + public function addConstant($constant) + { + $this->constants->append($constant); + + return $this; + } + + /** + * Delete constant from constant list + * + * @param string $constant + * + * @return bool + */ + public function deleteConstant($constant) + { + if (($index = array_search($constant, $this->constants->getArrayCopy())) !== false) { + $this->constants->offsetUnset($index); + } + + return $index !== false; + } + + /** + * Return constant list + * + * @return ArrayObject + */ + public function getConstants() + { + return $this->constants; + } + + /** + * @return bool + */ + public function isValidConstantType() + { + if ($this->type == self::TYPE_AUTO) { + $type = $this->getAutoDeterminedType($this->value); + } else { + $type = $this->type; + } + + // valid types for constants + $scalarTypes = array( + self::TYPE_BOOLEAN, + self::TYPE_BOOL, + self::TYPE_NUMBER, + self::TYPE_INTEGER, + self::TYPE_INT, + self::TYPE_FLOAT, + self::TYPE_DOUBLE, + self::TYPE_STRING, + self::TYPE_CONSTANT, + self::TYPE_NULL + ); + + return in_array($type, $scalarTypes); + } + + /** + * @param mixed $value + * @return ValueGenerator + */ + public function setValue($value) + { + $this->value = $value; + return $this; + } + + /** + * @return mixed + */ + public function getValue() + { + return $this->value; + } + + /** + * @param string $type + * @return ValueGenerator + */ + public function setType($type) + { + $this->type = (string) $type; + return $this; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @param int $arrayDepth + * @return ValueGenerator + */ + public function setArrayDepth($arrayDepth) + { + $this->arrayDepth = (int) $arrayDepth; + return $this; + } + + /** + * @return int + */ + public function getArrayDepth() + { + return $this->arrayDepth; + } + + /** + * @param string $type + * @return string + */ + protected function getValidatedType($type) + { + $types = array( + self::TYPE_AUTO, + self::TYPE_BOOLEAN, + self::TYPE_BOOL, + self::TYPE_NUMBER, + self::TYPE_INTEGER, + self::TYPE_INT, + self::TYPE_FLOAT, + self::TYPE_DOUBLE, + self::TYPE_STRING, + self::TYPE_ARRAY, + self::TYPE_CONSTANT, + self::TYPE_NULL, + self::TYPE_OBJECT, + self::TYPE_OTHER + ); + + if (in_array($type, $types)) { + return $type; + } + + return self::TYPE_AUTO; + } + + /** + * @param mixed $value + * @return string + */ + public function getAutoDeterminedType($value) + { + switch (gettype($value)) { + case 'boolean': + return self::TYPE_BOOLEAN; + case 'string': + foreach ($this->constants as $constant) { + if (strpos($value, $constant) !== false) { + return self::TYPE_CONSTANT; + } + } + return self::TYPE_STRING; + case 'double': + case 'float': + case 'integer': + return self::TYPE_NUMBER; + case 'array': + return self::TYPE_ARRAY; + case 'NULL': + return self::TYPE_NULL; + case 'object': + case 'resource': + case 'unknown type': + default: + return self::TYPE_OTHER; + } + } + + /** + * @throws Exception\RuntimeException + * @return string + */ + public function generate() + { + $type = $this->type; + + if ($type != self::TYPE_AUTO) { + $type = $this->getValidatedType($type); + } + + $value = $this->value; + + if ($type == self::TYPE_AUTO) { + $type = $this->getAutoDeterminedType($value); + } + + if ($type == self::TYPE_ARRAY) { + foreach ($value as &$curValue) { + if ($curValue instanceof self) { + continue; + } + $curValue = new self($curValue, self::TYPE_AUTO, self::OUTPUT_MULTIPLE_LINE, $this->getConstants()); + } + } + + $output = ''; + + switch ($type) { + case self::TYPE_BOOLEAN: + case self::TYPE_BOOL: + $output .= ($value ? 'true' : 'false'); + break; + case self::TYPE_STRING: + $output .= self::escape($value); + break; + case self::TYPE_NULL: + $output .= 'null'; + break; + case self::TYPE_NUMBER: + case self::TYPE_INTEGER: + case self::TYPE_INT: + case self::TYPE_FLOAT: + case self::TYPE_DOUBLE: + case self::TYPE_CONSTANT: + $output .= $value; + break; + case self::TYPE_ARRAY: + $output .= 'array('; + if ($this->outputMode == self::OUTPUT_MULTIPLE_LINE) { + $output .= self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth + 1); + } + $outputParts = array(); + $noKeyIndex = 0; + foreach ($value as $n => $v) { + /* @var $v ValueGenerator */ + $v->setArrayDepth($this->arrayDepth + 1); + $partV = $v->generate(); + $short = false; + if (is_int($n)) { + if ($n === $noKeyIndex) { + $short = true; + $noKeyIndex++; + } else { + $noKeyIndex = max($n + 1, $noKeyIndex); + } + } + + if ($short) { + $outputParts[] = $partV; + } else { + $outputParts[] = (is_int($n) ? $n : self::escape($n)) . ' => ' . $partV; + } + } + $padding = ($this->outputMode == self::OUTPUT_MULTIPLE_LINE) + ? self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth + 1) + : ' '; + $output .= implode(',' . $padding, $outputParts); + if ($this->outputMode == self::OUTPUT_MULTIPLE_LINE) { + if (count($outputParts) > 0) { + $output .= ','; + } + $output .= self::LINE_FEED . str_repeat($this->indentation, $this->arrayDepth); + } + $output .= ')'; + break; + case self::TYPE_OTHER: + default: + throw new Exception\RuntimeException( + sprintf('Type "%s" is unknown or cannot be used as property default value.', get_class($value)) + ); + } + + return $output; + } + + /** + * Quotes value for PHP code. + * + * @param string $input Raw string. + * @param bool $quote Whether add surrounding quotes or not. + * @return string PHP-ready code. + */ + public static function escape($input, $quote = true) + { + $output = addcslashes($input, "\\'"); + + // adds quoting strings + if ($quote) { + $output = "'" . $output . "'"; + } + + return $output; + } + + /** + * @param string $outputMode + * @return ValueGenerator + */ + public function setOutputMode($outputMode) + { + $this->outputMode = (string) $outputMode; + return $this; + } + + /** + * @return string + */ + public function getOutputMode() + { + return $this->outputMode; + } + + public function __toString() + { + return $this->generate(); + } +} diff --git a/vendor/zendframework/zend-code/src/Generic/Prototype/PrototypeClassFactory.php b/vendor/zendframework/zend-code/src/Generic/Prototype/PrototypeClassFactory.php new file mode 100644 index 0000000..2fcc5f7 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generic/Prototype/PrototypeClassFactory.php @@ -0,0 +1,121 @@ +addPrototype($prototype); + } + + if ($genericPrototype) { + $this->setGenericPrototype($genericPrototype); + } + } + + /** + * @param PrototypeInterface $prototype + * @throws Exception\InvalidArgumentException + */ + public function addPrototype(PrototypeInterface $prototype) + { + $prototypeName = $this->normalizeName($prototype->getName()); + + if (isset($this->prototypes[$prototypeName])) { + throw new Exception\InvalidArgumentException('A prototype with this name already exists in this manager'); + } + + $this->prototypes[$prototypeName] = $prototype; + } + + /** + * @param PrototypeGenericInterface $prototype + * @throws Exception\InvalidArgumentException + */ + public function setGenericPrototype(PrototypeGenericInterface $prototype) + { + if (isset($this->genericPrototype)) { + throw new Exception\InvalidArgumentException('A default prototype is already set'); + } + + $this->genericPrototype = $prototype; + } + + /** + * @param string $name + * @return string + */ + protected function normalizeName($name) + { + return str_replace(array('-', '_'), '', $name); + } + + /** + * @param string $name + * @return bool + */ + public function hasPrototype($name) + { + $name = $this->normalizeName($name); + return isset($this->prototypes[$name]); + } + + /** + * @param string $prototypeName + * @return PrototypeInterface + * @throws Exception\RuntimeException + */ + public function getClonedPrototype($prototypeName) + { + $prototypeName = $this->normalizeName($prototypeName); + + if (!$this->hasPrototype($prototypeName) && !isset($this->genericPrototype)) { + throw new Exception\RuntimeException('This tag name is not supported by this tag manager'); + } + + if (!$this->hasPrototype($prototypeName)) { + $newPrototype = clone $this->genericPrototype; + $newPrototype->setName($prototypeName); + } else { + $newPrototype = clone $this->prototypes[$prototypeName]; + } + + return $newPrototype; + } +} diff --git a/vendor/zendframework/zend-code/src/Generic/Prototype/PrototypeGenericInterface.php b/vendor/zendframework/zend-code/src/Generic/Prototype/PrototypeGenericInterface.php new file mode 100644 index 0000000..06a41da --- /dev/null +++ b/vendor/zendframework/zend-code/src/Generic/Prototype/PrototypeGenericInterface.php @@ -0,0 +1,18 @@ +setNamespace($namespace); + } + if ($uses) { + $this->setUses($uses); + } + } + + /** + * @param string $namespace + * @return NameInformation + */ + public function setNamespace($namespace) + { + $this->namespace = (string) $namespace; + return $this; + } + + /** + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * @return bool + */ + public function hasNamespace() + { + return ($this->namespace !== null); + } + + /** + * @param array $uses + * @return NameInformation + */ + public function setUses(array $uses) + { + $this->uses = array(); + $this->addUses($uses); + + return $this; + } + + /** + * @param array $uses + * @return NameInformation + */ + public function addUses(array $uses) + { + foreach ($uses as $use => $as) { + if (is_int($use)) { + $this->addUse($as); + } elseif (is_string($use)) { + $this->addUse($use, $as); + } + } + + return $this; + } + + /** + * @param array|string $use + * @param string $as + */ + public function addUse($use, $as = null) + { + if (is_array($use) && array_key_exists('use', $use) && array_key_exists('as', $use)) { + $uses = $use; + $use = $uses['use']; + $as = $uses['as']; + } + + $use = trim($use, '\\'); + if ($as === null) { + $as = trim($use, '\\'); + $nsSeparatorPosition = strrpos($as, '\\'); + if ($nsSeparatorPosition !== false && $nsSeparatorPosition !== 0 && $nsSeparatorPosition != strlen($as)) { + $as = substr($as, $nsSeparatorPosition + 1); + } + } + + $this->uses[$use] = $as; + } + + /** + * @return array + */ + public function getUses() + { + return $this->uses; + } + + /** + * @param string $name + * @return string + */ + public function resolveName($name) + { + if ($this->namespace && !$this->uses && strlen($name) > 0 && $name{0} != '\\') { + return $this->namespace . '\\' . $name; + } + + if (!$this->uses || strlen($name) <= 0 || $name{0} == '\\') { + return ltrim($name, '\\'); + } + + if ($this->namespace || $this->uses) { + $firstPart = $name; + if (($firstPartEnd = strpos($firstPart, '\\')) !== false) { + $firstPart = substr($firstPart, 0, $firstPartEnd); + } else { + $firstPartEnd = strlen($firstPart); + } + if (($fqns = array_search($firstPart, $this->uses)) !== false) { + return substr_replace($name, $fqns, 0, $firstPartEnd); + } + if ($this->namespace) { + return $this->namespace . '\\' . $name; + } + } + + return $name; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/ClassReflection.php b/vendor/zendframework/zend-code/src/Reflection/ClassReflection.php new file mode 100644 index 0000000..25bcaf0 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/ClassReflection.php @@ -0,0 +1,283 @@ +getFileName()); + + return $instance; + } + + /** + * Return the classes DocBlock reflection object + * + * @return DocBlockReflection + * @throws Exception\ExceptionInterface for missing DocBock or invalid reflection class + */ + public function getDocBlock() + { + if (isset($this->docBlock)) { + return $this->docBlock; + } + + if ('' == $this->getDocComment()) { + return false; + } + + $this->docBlock = new DocBlockReflection($this); + + return $this->docBlock; + } + + /** + * @param AnnotationManager $annotationManager + * @return AnnotationCollection + */ + public function getAnnotations(AnnotationManager $annotationManager) + { + $docComment = $this->getDocComment(); + + if ($docComment == '') { + return false; + } + + if ($this->annotations) { + return $this->annotations; + } + + $fileScanner = $this->createFileScanner($this->getFileName()); + $nameInformation = $fileScanner->getClassNameInformation($this->getName()); + + if (!$nameInformation) { + return false; + } + + $this->annotations = new AnnotationScanner($annotationManager, $docComment, $nameInformation); + + return $this->annotations; + } + + /** + * Return the start line of the class + * + * @param bool $includeDocComment + * @return int + */ + public function getStartLine($includeDocComment = false) + { + if ($includeDocComment && $this->getDocComment() != '') { + return $this->getDocBlock()->getStartLine(); + } + + return parent::getStartLine(); + } + + /** + * Return the contents of the class + * + * @param bool $includeDocBlock + * @return string + */ + public function getContents($includeDocBlock = true) + { + $fileName = $this->getFileName(); + + if (false === $fileName || ! file_exists($fileName)) { + return ''; + } + + $filelines = file($fileName); + $startnum = $this->getStartLine($includeDocBlock); + $endnum = $this->getEndLine() - $this->getStartLine(); + + // Ensure we get between the open and close braces + $lines = array_slice($filelines, $startnum, $endnum); + array_unshift($lines, $filelines[$startnum-1]); + + return strstr(implode('', $lines), '{'); + } + + /** + * Get all reflection objects of implemented interfaces + * + * @return ClassReflection[] + */ + public function getInterfaces() + { + $phpReflections = parent::getInterfaces(); + $zendReflections = array(); + while ($phpReflections && ($phpReflection = array_shift($phpReflections))) { + $instance = new ClassReflection($phpReflection->getName()); + $zendReflections[] = $instance; + unset($phpReflection); + } + unset($phpReflections); + + return $zendReflections; + } + + /** + * Return method reflection by name + * + * @param string $name + * @return MethodReflection + */ + public function getMethod($name) + { + $method = new MethodReflection($this->getName(), parent::getMethod($name)->getName()); + + return $method; + } + + /** + * Get reflection objects of all methods + * + * @param int $filter + * @return MethodReflection[] + */ + public function getMethods($filter = -1) + { + $methods = array(); + foreach (parent::getMethods($filter) as $method) { + $instance = new MethodReflection($this->getName(), $method->getName()); + $methods[] = $instance; + } + + return $methods; + } + + /** + * Returns an array of reflection classes of traits used by this class. + * + * @return array|null + */ + public function getTraits() + { + $vals = array(); + $traits = parent::getTraits(); + if ($traits === null) { + return; + } + + foreach ($traits as $trait) { + $vals[] = new ClassReflection($trait->getName()); + } + + return $vals; + } + + /** + * Get parent reflection class of reflected class + * + * @return ClassReflection|bool + */ + public function getParentClass() + { + $phpReflection = parent::getParentClass(); + if ($phpReflection) { + $zendReflection = new ClassReflection($phpReflection->getName()); + unset($phpReflection); + + return $zendReflection; + } + + return false; + } + + /** + * Return reflection property of this class by name + * + * @param string $name + * @return PropertyReflection + */ + public function getProperty($name) + { + $phpReflection = parent::getProperty($name); + $zendReflection = new PropertyReflection($this->getName(), $phpReflection->getName()); + unset($phpReflection); + + return $zendReflection; + } + + /** + * Return reflection properties of this class + * + * @param int $filter + * @return PropertyReflection[] + */ + public function getProperties($filter = -1) + { + $phpReflections = parent::getProperties($filter); + $zendReflections = array(); + while ($phpReflections && ($phpReflection = array_shift($phpReflections))) { + $instance = new PropertyReflection($this->getName(), $phpReflection->getName()); + $zendReflections[] = $instance; + unset($phpReflection); + } + unset($phpReflections); + + return $zendReflections; + } + + /** + * @return string + */ + public function toString() + { + return parent::__toString(); + } + + /** + * @return string + */ + public function __toString() + { + return parent::__toString(); + } + + /** + * Creates a new FileScanner instance. + * + * By having this as a seperate method it allows the method to be overridden + * if a different FileScanner is needed. + * + * @param string $filename + * + * @return FileScanner + */ + protected function createFileScanner($filename) + { + return new FileScanner($filename); + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/AuthorTag.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/AuthorTag.php new file mode 100644 index 0000000..8b48fbe --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/AuthorTag.php @@ -0,0 +1,74 @@ +]*)\>)?(.*)$/u', $tagDocblockLine, $match)) { + return; + } + + if ($match[1] !== '') { + $this->authorName = rtrim($match[1]); + } + + if (isset($match[3]) && $match[3] !== '') { + $this->authorEmail = $match[3]; + } + } + + /** + * @return null|string + */ + public function getAuthorName() + { + return $this->authorName; + } + + /** + * @return null|string + */ + public function getAuthorEmail() + { + return $this->authorEmail; + } + + public function __toString() + { + return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/GenericTag.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/GenericTag.php new file mode 100644 index 0000000..6cd1bb6 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/GenericTag.php @@ -0,0 +1,109 @@ +contentSplitCharacter = $contentSplitCharacter; + } + + /** + * @param string $tagDocBlockLine + * @return void + */ + public function initialize($tagDocBlockLine) + { + $this->parse($tagDocBlockLine); + } + + /** + * Get annotation tag name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * @param int $position + * @return string + */ + public function returnValue($position) + { + return $this->values[$position]; + } + + /** + * Serialize to string + * + * Required by Reflector + * + * @todo What should this do? + * @return string + */ + public function __toString() + { + return 'DocBlock Tag [ * @' . $this->name . ' ]' . PHP_EOL; + } + + /** + * @param string $docBlockLine + */ + protected function parse($docBlockLine) + { + $this->content = trim($docBlockLine); + $this->values = explode($this->contentSplitCharacter, $docBlockLine); + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/LicenseTag.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/LicenseTag.php new file mode 100644 index 0000000..ee7ec14 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/LicenseTag.php @@ -0,0 +1,74 @@ +url = trim($match[1]); + } + + if (isset($match[2]) && $match[2] !== '') { + $this->licenseName = $match[2]; + } + } + + /** + * @return null|string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @return null|string + */ + public function getLicenseName() + { + return $this->licenseName; + } + + public function __toString() + { + return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/MethodTag.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/MethodTag.php new file mode 100644 index 0000000..86d409c --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/MethodTag.php @@ -0,0 +1,122 @@ +isStatic = true; + } + + if ($match[2] !== '') { + $this->types = explode('|', rtrim($match[2])); + } + + $this->methodName = $match[3]; + + if ($match[4] !== '') { + $this->description = $match[4]; + } + } + + /** + * Get return value type + * + * @return null|string + * @deprecated 2.0.4 use getTypes instead + */ + public function getReturnType() + { + if (empty($this->types)) { + return; + } + + return $this->types[0]; + } + + public function getTypes() + { + return $this->types; + } + + /** + * @return string + */ + public function getMethodName() + { + return $this->methodName; + } + + /** + * @return null|string + */ + public function getDescription() + { + return $this->description; + } + + /** + * @return bool + */ + public function isStatic() + { + return $this->isStatic; + } + + public function __toString() + { + return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/ParamTag.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/ParamTag.php new file mode 100644 index 0000000..74b8a1d --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/ParamTag.php @@ -0,0 +1,98 @@ +types = explode('|', $matches[1]); + + if (isset($matches[2])) { + $this->variableName = $matches[2]; + } + + if (isset($matches[3])) { + $this->description = trim(preg_replace('#\s+#', ' ', $matches[3])); + } + } + + /** + * Get parameter variable type + * + * @return string + * @deprecated 2.0.4 use getTypes instead + */ + public function getType() + { + if (empty($this->types)) { + return ''; + } + + return $this->types[0]; + } + + public function getTypes() + { + return $this->types; + } + + /** + * Get parameter name + * + * @return string + */ + public function getVariableName() + { + return $this->variableName; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php new file mode 100644 index 0000000..19b1119 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/PhpDocTypedTagInterface.php @@ -0,0 +1,20 @@ +types = explode('|', rtrim($match[1])); + } + + if ($match[2] !== '') { + $this->propertyName = $match[2]; + } + + if ($match[3] !== '') { + $this->description = $match[3]; + } + } + + /** + * @return null|string + * @deprecated 2.0.4 use getTypes instead + */ + public function getType() + { + if (empty($this->types)) { + return; + } + + return $this->types[0]; + } + + public function getTypes() + { + return $this->types; + } + + /** + * @return null|string + */ + public function getPropertyName() + { + return $this->propertyName; + } + + /** + * @return null|string + */ + public function getDescription() + { + return $this->description; + } + + public function __toString() + { + return 'DocBlock Tag [ * @' . $this->getName() . ' ]' . PHP_EOL; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/ReturnTag.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/ReturnTag.php new file mode 100644 index 0000000..c929414 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/ReturnTag.php @@ -0,0 +1,75 @@ +types = explode('|', $matches[1]); + + if (isset($matches[2])) { + $this->description = trim(preg_replace('#\s+#', ' ', $matches[2])); + } + } + + /** + * @return string + * @deprecated 2.0.4 use getTypes instead + */ + public function getType() + { + if (empty($this->types)) { + return ''; + } + + return $this->types[0]; + } + + public function getTypes() + { + return $this->types; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/TagInterface.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/TagInterface.php new file mode 100644 index 0000000..653c884 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/Tag/TagInterface.php @@ -0,0 +1,21 @@ +types = explode('|', $matches[1]); + + if (isset($matches[2])) { + $this->description = $matches[2]; + } + } + + /** + * Get return variable type + * + * @return string + * @deprecated 2.0.4 use getTypes instead + */ + public function getType() + { + return implode('|', $this->getTypes()); + } + + /** + * @return array + */ + public function getTypes() + { + return $this->types; + } + + /** + * @return string + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlock/TagManager.php b/vendor/zendframework/zend-code/src/Reflection/DocBlock/TagManager.php new file mode 100644 index 0000000..3a5474d --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlock/TagManager.php @@ -0,0 +1,48 @@ +addPrototype(new Tag\ParamTag()); + $this->addPrototype(new Tag\ReturnTag()); + $this->addPrototype(new Tag\MethodTag()); + $this->addPrototype(new Tag\PropertyTag()); + $this->addPrototype(new Tag\AuthorTag()); + $this->addPrototype(new Tag\LicenseTag()); + $this->addPrototype(new Tag\ThrowsTag()); + $this->setGenericPrototype(new Tag\GenericTag()); + } + + /** + * @param string $tagName + * @param string $content + * @return TagInterface + */ + public function createTag($tagName, $content = null) + { + /* @var TagInterface $newTag */ + $newTag = $this->getClonedPrototype($tagName); + + if ($content) { + $newTag->initialize($content); + } + + return $newTag; + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/DocBlockReflection.php b/vendor/zendframework/zend-code/src/Reflection/DocBlockReflection.php new file mode 100644 index 0000000..a898ca8 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/DocBlockReflection.php @@ -0,0 +1,296 @@ +initializeDefaultTags(); + } + $this->tagManager = $tagManager; + + if ($commentOrReflector instanceof Reflector) { + $this->reflector = $commentOrReflector; + if (!method_exists($commentOrReflector, 'getDocComment')) { + throw new Exception\InvalidArgumentException('Reflector must contain method "getDocComment"'); + } + /* @var MethodReflection $commentOrReflector */ + $this->docComment = $commentOrReflector->getDocComment(); + + // determine line numbers + $lineCount = substr_count($this->docComment, "\n"); + $this->startLine = $this->reflector->getStartLine() - $lineCount - 1; + $this->endLine = $this->reflector->getStartLine() - 1; + } elseif (is_string($commentOrReflector)) { + $this->docComment = $commentOrReflector; + } else { + throw new Exception\InvalidArgumentException(sprintf( + '%s must have a (string) DocComment or a Reflector in the constructor', + get_class($this) + )); + } + + if ($this->docComment == '') { + throw new Exception\InvalidArgumentException('DocComment cannot be empty'); + } + + $this->reflect(); + } + + /** + * Retrieve contents of DocBlock + * + * @return string + */ + public function getContents() + { + $this->reflect(); + + return $this->cleanDocComment; + } + + /** + * Get start line (position) of DocBlock + * + * @return int + */ + public function getStartLine() + { + $this->reflect(); + + return $this->startLine; + } + + /** + * Get last line (position) of DocBlock + * + * @return int + */ + public function getEndLine() + { + $this->reflect(); + + return $this->endLine; + } + + /** + * Get DocBlock short description + * + * @return string + */ + public function getShortDescription() + { + $this->reflect(); + + return $this->shortDescription; + } + + /** + * Get DocBlock long description + * + * @return string + */ + public function getLongDescription() + { + $this->reflect(); + + return $this->longDescription; + } + + /** + * Does the DocBlock contain the given annotation tag? + * + * @param string $name + * @return bool + */ + public function hasTag($name) + { + $this->reflect(); + foreach ($this->tags as $tag) { + if ($tag->getName() == $name) { + return true; + } + } + + return false; + } + + /** + * Retrieve the given DocBlock tag + * + * @param string $name + * @return DocBlockTagInterface|false + */ + public function getTag($name) + { + $this->reflect(); + foreach ($this->tags as $tag) { + if ($tag->getName() == $name) { + return $tag; + } + } + + return false; + } + + /** + * Get all DocBlock annotation tags + * + * @param string $filter + * @return DocBlockTagInterface[] + */ + public function getTags($filter = null) + { + $this->reflect(); + if ($filter === null || !is_string($filter)) { + return $this->tags; + } + + $returnTags = array(); + foreach ($this->tags as $tag) { + if ($tag->getName() == $filter) { + $returnTags[] = $tag; + } + } + + return $returnTags; + } + + /** + * Parse the DocBlock + * + * @return void + */ + protected function reflect() + { + if ($this->isReflected) { + return; + } + + $docComment = preg_replace('#[ ]{0,1}\*/$#', '', $this->docComment); + + // create a clean docComment + $this->cleanDocComment = preg_replace("#[ \t]*(?:/\*\*|\*/|\*)[ ]{0,1}(.*)?#", '$1', $docComment); + $this->cleanDocComment = ltrim($this->cleanDocComment, "\r\n"); // @todo should be changed to remove first and last empty line + + $scanner = new DocBlockScanner($docComment); + $this->shortDescription = ltrim($scanner->getShortDescription()); + $this->longDescription = ltrim($scanner->getLongDescription()); + + foreach ($scanner->getTags() as $tag) { + $this->tags[] = $this->tagManager->createTag(ltrim($tag['name'], '@'), ltrim($tag['value'])); + } + + $this->isReflected = true; + } + + /** + * @return string + */ + public function toString() + { + $str = "DocBlock [ /* DocBlock */ ] {" . PHP_EOL . PHP_EOL; + $str .= " - Tags [" . count($this->tags) . "] {" . PHP_EOL; + + foreach ($this->tags as $tag) { + $str .= " " . $tag; + } + + $str .= " }" . PHP_EOL; + $str .= "}" . PHP_EOL; + + return $str; + } + + /** + * Serialize to string + * + * Required by the Reflector interface + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/Exception/BadMethodCallException.php b/vendor/zendframework/zend-code/src/Reflection/Exception/BadMethodCallException.php new file mode 100644 index 0000000..f40a199 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/Exception/BadMethodCallException.php @@ -0,0 +1,17 @@ +filePath = $fileRealPath; + $this->reflect(); + } + + /** + * Required by the Reflector interface. + * + * @todo What should this do? + * @return null + */ + public static function export() + { + return; + } + + /** + * Return the file name of the reflected file + * + * @return string + */ + public function getFileName() + { + return basename($this->filePath); + } + + /** + * Get the start line - Always 1, staying consistent with the Reflection API + * + * @return int + */ + public function getStartLine() + { + return $this->startLine; + } + + /** + * Get the end line / number of lines + * + * @return int + */ + public function getEndLine() + { + return $this->endLine; + } + + /** + * @return string + */ + public function getDocComment() + { + return $this->docComment; + } + + /** + * @return DocBlockReflection + */ + public function getDocBlock() + { + if (!($docComment = $this->getDocComment())) { + return false; + } + + $instance = new DocBlockReflection($docComment); + + return $instance; + } + + /** + * @return string[] + */ + public function getNamespaces() + { + return $this->namespaces; + } + + /** + * @return string + */ + public function getNamespace() + { + if (count($this->namespaces) == 0) { + return; + } + + return $this->namespaces[0]; + } + + /** + * @return array + */ + public function getUses() + { + return $this->uses; + } + + /** + * Return the reflection classes of the classes found inside this file + * + * @return ClassReflection[] + */ + public function getClasses() + { + $classes = array(); + foreach ($this->classes as $class) { + $classes[] = new ClassReflection($class); + } + + return $classes; + } + + /** + * Return the reflection functions of the functions found inside this file + * + * @return FunctionReflection[] + */ + public function getFunctions() + { + $functions = array(); + foreach ($this->functions as $function) { + $functions[] = new FunctionReflection($function); + } + + return $functions; + } + + /** + * Retrieve the reflection class of a given class found in this file + * + * @param null|string $name + * @return ClassReflection + * @throws Exception\InvalidArgumentException for invalid class name or invalid reflection class + */ + public function getClass($name = null) + { + if (null === $name) { + reset($this->classes); + $selected = current($this->classes); + + return new ClassReflection($selected); + } + + if (in_array($name, $this->classes)) { + return new ClassReflection($name); + } + + throw new Exception\InvalidArgumentException(sprintf( + 'Class by name %s not found.', + $name + )); + } + + /** + * Return the full contents of file + * + * @return string + */ + public function getContents() + { + return file_get_contents($this->filePath); + } + + public function toString() + { + return ''; // @todo + } + + /** + * Serialize to string + * + * Required by the Reflector interface + * + * @todo What should this serialization look like? + * @return string + */ + public function __toString() + { + return ''; + } + + /** + * This method does the work of "reflecting" the file + * + * Uses Zend\Code\Scanner\FileScanner to gather file information + * + * @return void + */ + protected function reflect() + { + $scanner = new CachingFileScanner($this->filePath); + $this->docComment = $scanner->getDocComment(); + $this->requiredFiles = $scanner->getIncludes(); + $this->classes = $scanner->getClassNames(); + $this->namespaces = $scanner->getNamespaces(); + $this->uses = $scanner->getUses(); + } + + /** + * Validate / check a file level DocBlock + * + * @param array $tokens Array of tokenizer tokens + * @return void + */ + protected function checkFileDocBlock($tokens) + { + foreach ($tokens as $token) { + $type = $token[0]; + $value = $token[1]; + $lineNum = $token[2]; + if (($type == T_OPEN_TAG) || ($type == T_WHITESPACE)) { + continue; + } elseif ($type == T_DOC_COMMENT) { + $this->docComment = $value; + $this->startLine = $lineNum + substr_count($value, "\n") + 1; + + return; + } else { + // Only whitespace is allowed before file DocBlocks + return; + } + } + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/FunctionReflection.php b/vendor/zendframework/zend-code/src/Reflection/FunctionReflection.php new file mode 100644 index 0000000..b7bb423 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/FunctionReflection.php @@ -0,0 +1,269 @@ +getDocComment())) { + throw new Exception\InvalidArgumentException(sprintf( + '%s does not have a DocBlock', + $this->getName() + )); + } + + $instance = new DocBlockReflection($comment); + + return $instance; + } + + /** + * Get start line (position) of function + * + * @param bool $includeDocComment + * @return int + */ + public function getStartLine($includeDocComment = false) + { + if ($includeDocComment) { + if ($this->getDocComment() != '') { + return $this->getDocBlock()->getStartLine(); + } + } + + return parent::getStartLine(); + } + + /** + * Get contents of function + * + * @param bool $includeDocBlock + * @return string + */ + public function getContents($includeDocBlock = true) + { + $fileName = $this->getFileName(); + if (false === $fileName) { + return ''; + } + + $startLine = $this->getStartLine(); + $endLine = $this->getEndLine(); + + // eval'd protect + if (preg_match('#\((\d+)\) : eval\(\)\'d code$#', $fileName, $matches)) { + $fileName = preg_replace('#\(\d+\) : eval\(\)\'d code$#', '', $fileName); + $startLine = $endLine = $matches[1]; + } + + $lines = array_slice( + file($fileName, FILE_IGNORE_NEW_LINES), + $startLine - 1, + ($endLine - ($startLine - 1)), + true + ); + + $functionLine = implode("\n", $lines); + + $content = ''; + if ($this->isClosure()) { + preg_match('#function\s*\([^\)]*\)\s*(use\s*\([^\)]+\))?\s*\{(.*\;)?\s*\}#s', $functionLine, $matches); + if (isset($matches[0])) { + $content = $matches[0]; + } + } else { + $name = substr($this->getName(), strrpos($this->getName(), '\\')+1); + preg_match('#function\s+' . preg_quote($name) . '\s*\([^\)]*\)\s*{([^{}]+({[^}]+})*[^}]+)?}#', $functionLine, $matches); + if (isset($matches[0])) { + $content = $matches[0]; + } + } + + $docComment = $this->getDocComment(); + + return $includeDocBlock && $docComment ? $docComment . "\n" . $content : $content; + } + + /** + * Get method prototype + * + * @return array + */ + public function getPrototype($format = FunctionReflection::PROTOTYPE_AS_ARRAY) + { + $returnType = 'mixed'; + $docBlock = $this->getDocBlock(); + if ($docBlock) { + $return = $docBlock->getTag('return'); + $returnTypes = $return->getTypes(); + $returnType = count($returnTypes) > 1 ? implode('|', $returnTypes) : $returnTypes[0]; + } + + $prototype = array( + 'namespace' => $this->getNamespaceName(), + 'name' => substr($this->getName(), strlen($this->getNamespaceName()) + 1), + 'return' => $returnType, + 'arguments' => array(), + ); + + $parameters = $this->getParameters(); + foreach ($parameters as $parameter) { + $prototype['arguments'][$parameter->getName()] = array( + 'type' => $parameter->getType(), + 'required' => !$parameter->isOptional(), + 'by_ref' => $parameter->isPassedByReference(), + 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null, + ); + } + + if ($format == FunctionReflection::PROTOTYPE_AS_STRING) { + $line = $prototype['return'] . ' ' . $prototype['name'] . '('; + $args = array(); + foreach ($prototype['arguments'] as $name => $argument) { + $argsLine = ($argument['type'] ? $argument['type'] . ' ' : '') . ($argument['by_ref'] ? '&' : '') . '$' . $name; + if (!$argument['required']) { + $argsLine .= ' = ' . var_export($argument['default'], true); + } + $args[] = $argsLine; + } + $line .= implode(', ', $args); + $line .= ')'; + + return $line; + } + + return $prototype; + } + + /** + * Get function parameters + * + * @return ParameterReflection[] + */ + public function getParameters() + { + $phpReflections = parent::getParameters(); + $zendReflections = array(); + while ($phpReflections && ($phpReflection = array_shift($phpReflections))) { + $instance = new ParameterReflection($this->getName(), $phpReflection->getName()); + $zendReflections[] = $instance; + unset($phpReflection); + } + unset($phpReflections); + + return $zendReflections; + } + + /** + * Get return type tag + * + * @throws Exception\InvalidArgumentException + * @return DocBlockReflection + */ + public function getReturn() + { + $docBlock = $this->getDocBlock(); + if (!$docBlock->hasTag('return')) { + throw new Exception\InvalidArgumentException( + 'Function does not specify an @return annotation tag; cannot determine return type' + ); + } + + $tag = $docBlock->getTag('return'); + + return new DocBlockReflection('@return ' . $tag->getDescription()); + } + + /** + * Get method body + * + * @return string|false + */ + public function getBody() + { + $fileName = $this->getFileName(); + if (false === $fileName) { + throw new Exception\InvalidArgumentException( + 'Cannot determine internals functions body' + ); + } + + $startLine = $this->getStartLine(); + $endLine = $this->getEndLine(); + + // eval'd protect + if (preg_match('#\((\d+)\) : eval\(\)\'d code$#', $fileName, $matches)) { + $fileName = preg_replace('#\(\d+\) : eval\(\)\'d code$#', '', $fileName); + $startLine = $endLine = $matches[1]; + } + + $lines = array_slice( + file($fileName, FILE_IGNORE_NEW_LINES), + $startLine - 1, + ($endLine - ($startLine - 1)), + true + ); + + $functionLine = implode("\n", $lines); + + $body = false; + if ($this->isClosure()) { + preg_match('#function\s*\([^\)]*\)\s*(use\s*\([^\)]+\))?\s*\{(.*\;)\s*\}#s', $functionLine, $matches); + if (isset($matches[2])) { + $body = $matches[2]; + } + } else { + $name = substr($this->getName(), strrpos($this->getName(), '\\')+1); + preg_match('#function\s+' . $name . '\s*\([^\)]*\)\s*{([^{}]+({[^}]+})*[^}]+)}#', $functionLine, $matches); + if (isset($matches[1])) { + $body = $matches[1]; + } + } + + return $body; + } + + /** + * @return string + */ + public function toString() + { + return $this->__toString(); + } + + /** + * Required due to bug in php + * + * @return string + */ + public function __toString() + { + return parent::__toString(); + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/MethodReflection.php b/vendor/zendframework/zend-code/src/Reflection/MethodReflection.php new file mode 100644 index 0000000..514505e --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/MethodReflection.php @@ -0,0 +1,494 @@ +getDocComment()) { + return false; + } + + $instance = new DocBlockReflection($this); + + return $instance; + } + + /** + * @param AnnotationManager $annotationManager + * @return AnnotationScanner + */ + public function getAnnotations(AnnotationManager $annotationManager) + { + if (($docComment = $this->getDocComment()) == '') { + return false; + } + + if ($this->annotations) { + return $this->annotations; + } + + $cachingFileScanner = $this->createFileScanner($this->getFileName()); + $nameInformation = $cachingFileScanner->getClassNameInformation($this->getDeclaringClass()->getName()); + + if (!$nameInformation) { + return false; + } + + $this->annotations = new AnnotationScanner($annotationManager, $docComment, $nameInformation); + + return $this->annotations; + } + + /** + * Get start line (position) of method + * + * @param bool $includeDocComment + * @return int + */ + public function getStartLine($includeDocComment = false) + { + if ($includeDocComment) { + if ($this->getDocComment() != '') { + return $this->getDocBlock()->getStartLine(); + } + } + + return parent::getStartLine(); + } + + /** + * Get reflection of declaring class + * + * @return ClassReflection + */ + public function getDeclaringClass() + { + $phpReflection = parent::getDeclaringClass(); + $zendReflection = new ClassReflection($phpReflection->getName()); + unset($phpReflection); + + return $zendReflection; + } + + /** + * Get method prototype + * + * @return array + */ + public function getPrototype($format = MethodReflection::PROTOTYPE_AS_ARRAY) + { + $returnType = 'mixed'; + $docBlock = $this->getDocBlock(); + if ($docBlock) { + $return = $docBlock->getTag('return'); + $returnTypes = $return->getTypes(); + $returnType = count($returnTypes) > 1 ? implode('|', $returnTypes) : $returnTypes[0]; + } + + $declaringClass = $this->getDeclaringClass(); + $prototype = array( + 'namespace' => $declaringClass->getNamespaceName(), + 'class' => substr($declaringClass->getName(), strlen($declaringClass->getNamespaceName()) + 1), + 'name' => $this->getName(), + 'visibility' => ($this->isPublic() ? 'public' : ($this->isPrivate() ? 'private' : 'protected')), + 'return' => $returnType, + 'arguments' => array(), + ); + + $parameters = $this->getParameters(); + foreach ($parameters as $parameter) { + $prototype['arguments'][$parameter->getName()] = array( + 'type' => $parameter->getType(), + 'required' => !$parameter->isOptional(), + 'by_ref' => $parameter->isPassedByReference(), + 'default' => $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null, + ); + } + + if ($format == MethodReflection::PROTOTYPE_AS_STRING) { + $line = $prototype['visibility'] . ' ' . $prototype['return'] . ' ' . $prototype['name'] . '('; + $args = array(); + foreach ($prototype['arguments'] as $name => $argument) { + $argsLine = ($argument['type'] ? $argument['type'] . ' ' : '') . ($argument['by_ref'] ? '&' : '') . '$' . $name; + if (!$argument['required']) { + $argsLine .= ' = ' . var_export($argument['default'], true); + } + $args[] = $argsLine; + } + $line .= implode(', ', $args); + $line .= ')'; + + return $line; + } + + return $prototype; + } + + /** + * Get all method parameter reflection objects + * + * @return ParameterReflection[] + */ + public function getParameters() + { + $phpReflections = parent::getParameters(); + $zendReflections = array(); + while ($phpReflections && ($phpReflection = array_shift($phpReflections))) { + $instance = new ParameterReflection( + array($this->getDeclaringClass()->getName(), $this->getName()), + $phpReflection->getName() + ); + $zendReflections[] = $instance; + unset($phpReflection); + } + unset($phpReflections); + + return $zendReflections; + } + + /** + * Get method contents + * + * @param bool $includeDocBlock + * @return string + */ + public function getContents($includeDocBlock = true) + { + $docComment = $this->getDocComment(); + $content = ($includeDocBlock && !empty($docComment)) ? $docComment . "\n" : ''; + $content .= $this->extractMethodContents(); + + return $content; + } + + /** + * Get method body + * + * @return string + */ + public function getBody() + { + return $this->extractMethodContents(true); + } + + /** + * Tokenize method string and return concatenated body + * + * @param bool $bodyOnly + * @return string + */ + protected function extractMethodContents($bodyOnly = false) + { + $fileName = $this->getFileName(); + + if ((class_exists($this->class) && false === $fileName) || ! file_exists($fileName)) { + return ''; + } + + $lines = array_slice( + file($fileName, FILE_IGNORE_NEW_LINES), + $this->getStartLine() - 1, + ($this->getEndLine() - ($this->getStartLine() - 1)), + true + ); + + $functionLine = implode("\n", $lines); + $tokens = token_get_all(" $token) { + $tokenType = (is_array($token)) ? token_name($token[0]) : $token; + $tokenValue = (is_array($token)) ? $token[1] : $token; + + switch ($tokenType) { + case "T_FINAL": + case "T_ABSTRACT": + case "T_PUBLIC": + case "T_PROTECTED": + case "T_PRIVATE": + case "T_STATIC": + case "T_FUNCTION": + // check to see if we have a valid function + // then check if we are inside function and have a closure + if ($this->isValidFunction($tokens, $key, $this->getName())) { + if ($bodyOnly === false) { + //if first instance of tokenType grab prefixed whitespace + //and append to body + if ($capture === false) { + $body .= $this->extractPrefixedWhitespace($tokens, $key); + } + $body .= $tokenValue; + } + + $capture = true; + } else { + //closure test + if ($firstBrace && $tokenType == "T_FUNCTION") { + $body .= $tokenValue; + continue; + } + $capture = false; + continue; + } + break; + + case "{": + if ($capture === false) { + continue; + } + + if ($firstBrace === false) { + $firstBrace = true; + if ($bodyOnly === true) { + continue; + } + } + + $body .= $tokenValue; + break; + + case "}": + if ($capture === false) { + continue; + } + + //check to see if this is the last brace + if ($this->isEndingBrace($tokens, $key)) { + //capture the end brace if not bodyOnly + if ($bodyOnly === false) { + $body .= $tokenValue; + } + + break 2; + } + + $body .= $tokenValue; + break; + + default: + if ($capture === false) { + continue; + } + + // if returning body only wait for first brace before capturing + if ($bodyOnly === true && $firstBrace !== true) { + continue; + } + + $body .= $tokenValue; + break; + } + } + + //remove ending whitespace and return + return rtrim($body); + } + + /** + * Take current position and find any whitespace + * + * @param array $haystack + * @param int $position + * @return string + */ + protected function extractPrefixedWhitespace($haystack, $position) + { + $content = ''; + $count = count($haystack); + if ($position+1 == $count) { + return $content; + } + + for ($i = $position-1;$i >= 0;$i--) { + $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i]; + $tokenValue = (is_array($haystack[$i])) ? $haystack[$i][1] : $haystack[$i]; + + //search only for whitespace + if ($tokenType == "T_WHITESPACE") { + $content .= $tokenValue; + } else { + break; + } + } + + return $content; + } + + /** + * Test for ending brace + * + * @param array $haystack + * @param int $position + * @return bool + */ + protected function isEndingBrace($haystack, $position) + { + $count = count($haystack); + + //advance one position + $position = $position+1; + + if ($position == $count) { + return true; + } + + for ($i = $position;$i < $count; $i++) { + $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i]; + switch ($tokenType) { + case "T_FINAL": + case "T_ABSTRACT": + case "T_PUBLIC": + case "T_PROTECTED": + case "T_PRIVATE": + case "T_STATIC": + return true; + + case "T_FUNCTION": + // If a function is encountered and that function is not a closure + // then return true. otherwise the function is a closure, return false + if ($this->isValidFunction($haystack, $i)) { + return true; + } + return false; + + case "}": + case ";"; + case "T_BREAK": + case "T_CATCH": + case "T_DO": + case "T_ECHO": + case "T_ELSE": + case "T_ELSEIF": + case "T_EVAL": + case "T_EXIT": + case "T_FINALLY": + case "T_FOR": + case "T_FOREACH": + case "T_GOTO": + case "T_IF": + case "T_INCLUDE": + case "T_INCLUDE_ONCE": + case "T_PRINT": + case "T_STRING": + case "T_STRING_VARNAME": + case "T_THROW": + case "T_USE": + case "T_VARIABLE": + case "T_WHILE": + case "T_YIELD": + + return false; + } + } + } + + /** + * Test to see if current position is valid function or + * closure. Returns true if it's a function and NOT a closure + * + * @param array $haystack + * @param int $position + * @param string $functionName + * @return bool + */ + protected function isValidFunction($haystack, $position, $functionName = null) + { + $isValid = false; + $count = count($haystack); + for ($i = $position+1; $i < $count; $i++) { + $tokenType = (is_array($haystack[$i])) ? token_name($haystack[$i][0]) : $haystack[$i]; + $tokenValue = (is_array($haystack[$i])) ? $haystack[$i][1] : $haystack[$i]; + + //check for occurance of ( or + if ($tokenType == "T_STRING") { + //check to see if function name is passed, if so validate against that + if ($functionName !== null && $tokenValue != $functionName) { + $isValid = false; + break; + } + + $isValid = true; + break; + } elseif ($tokenValue == "(") { + break; + } + } + + return $isValid; + } + + /** + * @return string + */ + public function toString() + { + return parent::__toString(); + } + + /** + * @return string + */ + public function __toString() + { + return parent::__toString(); + } + + /** + * Creates a new FileScanner instance. + * + * By having this as a seperate method it allows the method to be overridden + * if a different FileScanner is needed. + * + * @param string $filename + * + * @return CachingFileScanner + */ + protected function createFileScanner($filename) + { + return new CachingFileScanner($filename); + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/ParameterReflection.php b/vendor/zendframework/zend-code/src/Reflection/ParameterReflection.php new file mode 100644 index 0000000..ba09525 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/ParameterReflection.php @@ -0,0 +1,116 @@ +getName()); + unset($phpReflection); + + return $zendReflection; + } + + /** + * Get class reflection object + * + * @return ClassReflection + */ + public function getClass() + { + $phpReflection = parent::getClass(); + if ($phpReflection === null) { + return; + } + + $zendReflection = new ClassReflection($phpReflection->getName()); + unset($phpReflection); + + return $zendReflection; + } + + /** + * Get declaring function reflection object + * + * @return FunctionReflection|MethodReflection + */ + public function getDeclaringFunction() + { + $phpReflection = parent::getDeclaringFunction(); + if ($phpReflection instanceof \ReflectionMethod) { + $zendReflection = new MethodReflection($this->getDeclaringClass()->getName(), $phpReflection->getName()); + } else { + $zendReflection = new FunctionReflection($phpReflection->getName()); + } + unset($phpReflection); + + return $zendReflection; + } + + /** + * Get parameter type + * + * @return string + */ + public function getType() + { + if ($this->isArray()) { + return 'array'; + } elseif (method_exists($this, 'isCallable') && $this->isCallable()) { + return 'callable'; + } + + if (($class = $this->getClass()) instanceof \ReflectionClass) { + return $class->getName(); + } + + $docBlock = $this->getDeclaringFunction()->getDocBlock(); + if (!$docBlock instanceof DocBlockReflection) { + return; + } + + $params = $docBlock->getTags('param'); + if (isset($params[$this->getPosition()])) { + return $params[$this->getPosition()]->getType(); + } + + return; + } + + /** + * @return string + */ + public function toString() + { + return parent::__toString(); + } + + /** + * @return string + */ + public function __toString() + { + return parent::__toString(); + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/PropertyReflection.php b/vendor/zendframework/zend-code/src/Reflection/PropertyReflection.php new file mode 100644 index 0000000..d5d5a1b --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/PropertyReflection.php @@ -0,0 +1,114 @@ +getName()); + unset($phpReflection); + + return $zendReflection; + } + + /** + * Get DocBlock comment + * + * @return string|false False if no DocBlock defined + */ + public function getDocComment() + { + return parent::getDocComment(); + } + + /** + * @return false|DocBlockReflection + */ + public function getDocBlock() + { + if (!($docComment = $this->getDocComment())) { + return false; + } + + $docBlockReflection = new DocBlockReflection($docComment); + + return $docBlockReflection; + } + + /** + * @param AnnotationManager $annotationManager + * @return AnnotationScanner + */ + public function getAnnotations(AnnotationManager $annotationManager) + { + if (null !== $this->annotations) { + return $this->annotations; + } + + if (($docComment = $this->getDocComment()) == '') { + return false; + } + + $class = $this->getDeclaringClass(); + $cachingFileScanner = $this->createFileScanner($class->getFileName()); + $nameInformation = $cachingFileScanner->getClassNameInformation($class->getName()); + + if (!$nameInformation) { + return false; + } + + $this->annotations = new AnnotationScanner($annotationManager, $docComment, $nameInformation); + + return $this->annotations; + } + + /** + * @return string + */ + public function toString() + { + return $this->__toString(); + } + + /** + * Creates a new FileScanner instance. + * + * By having this as a seperate method it allows the method to be overridden + * if a different FileScanner is needed. + * + * @param string $filename + * + * @return CachingFileScanner + */ + protected function createFileScanner($filename) + { + return new CachingFileScanner($filename); + } +} diff --git a/vendor/zendframework/zend-code/src/Reflection/ReflectionInterface.php b/vendor/zendframework/zend-code/src/Reflection/ReflectionInterface.php new file mode 100644 index 0000000..a081cf7 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Reflection/ReflectionInterface.php @@ -0,0 +1,20 @@ +directories as $scanner) { + $classes += $scanner->getClasses(); + } + if ($returnScannerClass) { + foreach ($classes as $index => $class) { + $classes[$index] = $this->getClass($class, $returnScannerClass, $returnDerivedScannerClass); + } + } + + return $classes; + } + + /** + * @param string $class + * @return bool + */ + public function hasClass($class) + { + foreach ($this->directories as $scanner) { + if ($scanner->hasClass($class)) { + break; + } else { + unset($scanner); + } + } + + return (isset($scanner)); + } + + /** + * @param string $class + * @param bool $returnScannerClass + * @param bool $returnDerivedScannerClass + * @return ClassScanner|DerivedClassScanner + * @throws Exception\RuntimeException + */ + public function getClass($class, $returnScannerClass = true, $returnDerivedScannerClass = false) + { + foreach ($this->directories as $scanner) { + if ($scanner->hasClass($class)) { + break; + } else { + unset($scanner); + } + } + + if (!isset($scanner)) { + throw new Exception\RuntimeException('Class by that name was not found.'); + } + + $classScanner = $scanner->getClass($class); + + return new DerivedClassScanner($classScanner, $this); + } + + /** + * @param bool $returnScannerClass + */ + public function getFunctions($returnScannerClass = false) + { + $this->scan(); + + if (!$returnScannerClass) { + $functions = array(); + foreach ($this->infos as $info) { + if ($info['type'] == 'function') { + $functions[] = $info['name']; + } + } + + return $functions; + } + $scannerClass = new FunctionScanner(); + // @todo + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/AnnotationScanner.php b/vendor/zendframework/zend-code/src/Scanner/AnnotationScanner.php new file mode 100644 index 0000000..7fb8e63 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/AnnotationScanner.php @@ -0,0 +1,353 @@ +annotationManager = $annotationManager; + $this->docComment = $docComment; + $this->nameInformation = $nameInformation; + $this->scan($this->tokenize()); + } + + /** + * @param NameInformation $nameInformation + */ + public function setNameInformation(NameInformation $nameInformation) + { + $this->nameInformation = $nameInformation; + } + + /** + * @param array $tokens + */ + protected function scan(array $tokens) + { + $annotations = array(); + $annotationIndex = -1; + $contentEnd = false; + + reset($tokens); + + SCANNER_TOP: + $token = current($tokens); + + switch ($token[0]) { + + case 'ANNOTATION_CLASS': + + $contentEnd = false; + $annotationIndex++; + $class = substr($token[1], 1); + $class = $this->nameInformation->resolveName($class); + $annotations[$annotationIndex] = array($class, null); + goto SCANNER_CONTINUE; + // goto no break needed + + case 'ANNOTATION_CONTENT_START': + + $annotations[$annotationIndex][1] = ''; + //fall-through + + case 'ANNOTATION_CONTENT_END': + case 'ANNOTATION_CONTENT': + case 'ANNOTATION_WHITESPACE': + case 'ANNOTATION_NEWLINE': + + if (!$contentEnd && isset($annotations[$annotationIndex]) && is_string($annotations[$annotationIndex][1])) { + $annotations[$annotationIndex][1] .= $token[1]; + } + + if ($token[0] === 'ANNOTATION_CONTENT_END') { + $contentEnd = true; + } + + goto SCANNER_CONTINUE; + } + + SCANNER_CONTINUE: + if (next($tokens) === false) { + goto SCANNER_END; + } + goto SCANNER_TOP; + + SCANNER_END: + + foreach ($annotations as $annotation) { + $annotation[] = '@' . $annotation[0] . $annotation[1]; + $annotationObject = $this->annotationManager->createAnnotation($annotation); + if ($annotationObject) { + $this->append($annotationObject); + } + } + } + + /** + * @return array + */ + protected function tokenize() + { + static $CONTEXT_DOCBLOCK = 0x01; + static $CONTEXT_ASTERISK = 0x02; + static $CONTEXT_CLASS = 0x04; + static $CONTEXT_CONTENT = 0x08; + + $context = 0x00; + $stream = $this->docComment; + $streamIndex = null; + $tokens = array(); + $tokenIndex = null; + $currentChar = null; + $currentWord = null; + $currentLine = null; + + $annotationParentCount = 0; + + $MACRO_STREAM_ADVANCE_CHAR = function ($positionsForward = 1) use (&$stream, &$streamIndex, &$currentChar, &$currentWord, &$currentLine) { + $positionsForward = ($positionsForward > 0) ? $positionsForward : 1; + $streamIndex = ($streamIndex === null) ? 0 : $streamIndex + $positionsForward; + if (!isset($stream[$streamIndex])) { + $currentChar = false; + + return false; + } + $currentChar = $stream[$streamIndex]; + $matches = array(); + $currentLine = (preg_match('#(.*?)(?:\n|\r\n?)#', $stream, $matches, null, $streamIndex) === 1) ? $matches[1] : substr($stream, $streamIndex); + if ($currentChar === ' ') { + $currentWord = (preg_match('#( +)#', $currentLine, $matches) === 1) ? $matches[1] : $currentLine; + } else { + $currentWord = (($matches = strpos($currentLine, ' ')) !== false) ? substr($currentLine, 0, $matches) : $currentLine; + } + + return $currentChar; + }; + $MACRO_STREAM_ADVANCE_WORD = function () use (&$currentWord, &$MACRO_STREAM_ADVANCE_CHAR) { + return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentWord)); + }; + $MACRO_STREAM_ADVANCE_LINE = function () use (&$currentLine, &$MACRO_STREAM_ADVANCE_CHAR) { + return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentLine)); + }; + $MACRO_TOKEN_ADVANCE = function () use (&$tokenIndex, &$tokens) { + $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1; + $tokens[$tokenIndex] = array('ANNOTATION_UNKNOWN', ''); + }; + $MACRO_TOKEN_SET_TYPE = function ($type) use (&$tokenIndex, &$tokens) { + $tokens[$tokenIndex][0] = $type; + }; + $MACRO_TOKEN_APPEND_CHAR = function () use (&$currentChar, &$tokens, &$tokenIndex) { + $tokens[$tokenIndex][1] .= $currentChar; + }; + $MACRO_TOKEN_APPEND_WORD = function () use (&$currentWord, &$tokens, &$tokenIndex) { + $tokens[$tokenIndex][1] .= $currentWord; + }; + $MACRO_TOKEN_APPEND_LINE = function () use (&$currentLine, &$tokens, &$tokenIndex) { + $tokens[$tokenIndex][1] .= $currentLine; + }; + $MACRO_HAS_CONTEXT = function ($which) use (&$context) { + return (($context & $which) === $which); + }; + + $MACRO_STREAM_ADVANCE_CHAR(); + $MACRO_TOKEN_ADVANCE(); + + TOKENIZER_TOP: + + if ($context === 0x00 && $currentChar === '/' && $currentWord === '/**') { + $MACRO_TOKEN_SET_TYPE('ANNOTATION_COMMENTSTART'); + $MACRO_TOKEN_APPEND_WORD(); + $MACRO_TOKEN_ADVANCE(); + $context |= $CONTEXT_DOCBLOCK; + $context |= $CONTEXT_ASTERISK; + if ($MACRO_STREAM_ADVANCE_WORD() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($MACRO_HAS_CONTEXT($CONTEXT_CLASS)) { + if (in_array($currentChar, array(' ', '(', "\n", "\r"))) { + $context &= ~$CONTEXT_CLASS; + $MACRO_TOKEN_ADVANCE(); + } else { + $MACRO_TOKEN_APPEND_CHAR(); + if ($MACRO_STREAM_ADVANCE_CHAR() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + } + + // Since we don't know what line endings are used in the file, we check for all scenarios. If we find a + // cariage return (\r), we check the next character for a line feed (\n). If so we consume it and act as + // if the cariage return was a line feed. + $lineEnded = $currentChar === "\n"; + if ($currentChar === "\r") { + $lineEnded = true; + + $nextChar = $MACRO_STREAM_ADVANCE_CHAR(); + if ($nextChar !== "\n") { + $streamIndex--; + } + + $currentChar = "\n"; + } + + if ($lineEnded) { + $MACRO_TOKEN_SET_TYPE('ANNOTATION_NEWLINE'); + $MACRO_TOKEN_APPEND_CHAR(); + $MACRO_TOKEN_ADVANCE(); + $context &= ~$CONTEXT_ASTERISK; + $context &= ~$CONTEXT_CLASS; + if ($MACRO_STREAM_ADVANCE_CHAR() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($currentChar === ' ') { + $MACRO_TOKEN_SET_TYPE(($MACRO_HAS_CONTEXT($CONTEXT_ASTERISK)) ? 'ANNOTATION_WHITESPACE' : 'ANNOTATION_WHITESPACE_INDENT'); + $MACRO_TOKEN_APPEND_WORD(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_WORD() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($MACRO_HAS_CONTEXT($CONTEXT_CONTENT) && $MACRO_HAS_CONTEXT($CONTEXT_ASTERISK)) { + $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT'); + $annotationParentCount += substr_count($currentWord, '('); + $annotationParentCount -= substr_count($currentWord, ')'); + + if ($annotationParentCount === 0) { + $context &= ~$CONTEXT_CONTENT; + $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT_END'); + } + $MACRO_TOKEN_APPEND_WORD(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_WORD() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($currentChar === '(' && $tokens[$tokenIndex - 1][0] === 'ANNOTATION_CLASS') { + $context |= $CONTEXT_CONTENT; + $annotationParentCount = 1; + $MACRO_TOKEN_SET_TYPE('ANNOTATION_CONTENT_START'); + $MACRO_TOKEN_APPEND_CHAR(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_CHAR() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($MACRO_HAS_CONTEXT($CONTEXT_DOCBLOCK) && $currentWord === '*/') { + $MACRO_TOKEN_SET_TYPE('ANNOTATION_COMMENTEND'); + $MACRO_TOKEN_APPEND_WORD(); + $MACRO_TOKEN_ADVANCE(); + $context &= ~$CONTEXT_DOCBLOCK; + if ($MACRO_STREAM_ADVANCE_WORD() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($currentChar === '*') { + if ($MACRO_HAS_CONTEXT($CONTEXT_DOCBLOCK) && ($MACRO_HAS_CONTEXT($CONTEXT_ASTERISK))) { + $MACRO_TOKEN_SET_TYPE('ANNOTATION_IGNORE'); + } else { + $MACRO_TOKEN_SET_TYPE('ANNOTATION_ASTERISK'); + $context |= $CONTEXT_ASTERISK; + } + $MACRO_TOKEN_APPEND_CHAR(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_CHAR() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($currentChar === '@') { + $MACRO_TOKEN_SET_TYPE('ANNOTATION_CLASS'); + $context |= $CONTEXT_CLASS; + $MACRO_TOKEN_APPEND_CHAR(); + if ($MACRO_STREAM_ADVANCE_CHAR() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + TOKENIZER_CONTINUE: + + if ($context && $CONTEXT_CONTENT) { + $MACRO_TOKEN_APPEND_CHAR(); + if ($MACRO_STREAM_ADVANCE_CHAR() === false) { + goto TOKENIZER_END; + } + } else { + $MACRO_TOKEN_SET_TYPE('ANNOTATION_IGNORE'); + $MACRO_TOKEN_APPEND_LINE(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_LINE() === false) { + goto TOKENIZER_END; + } + } + goto TOKENIZER_TOP; + + TOKENIZER_END: + + array_pop($tokens); + + return $tokens; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/CachingFileScanner.php b/vendor/zendframework/zend-code/src/Scanner/CachingFileScanner.php new file mode 100644 index 0000000..deef244 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/CachingFileScanner.php @@ -0,0 +1,160 @@ +fileScanner = static::$cache[$cacheId]; + } else { + $this->fileScanner = new FileScanner($file, $annotationManager); + static::$cache[$cacheId] = $this->fileScanner; + } + } + + /** + * @return void + */ + public static function clearCache() + { + static::$cache = array(); + } + + /** + * @return AnnotationManager + */ + public function getAnnotationManager() + { + return $this->fileScanner->getAnnotationManager(); + } + + /** + * @return array|null|string + */ + public function getFile() + { + return $this->fileScanner->getFile(); + } + + /** + * @return null|string + */ + public function getDocComment() + { + return $this->fileScanner->getDocComment(); + } + + /** + * @return array + */ + public function getNamespaces() + { + return $this->fileScanner->getNamespaces(); + } + + /** + * @param null|string $namespace + * @return array|null + */ + public function getUses($namespace = null) + { + return $this->fileScanner->getUses($namespace); + } + + /** + * @return array + */ + public function getIncludes() + { + return $this->fileScanner->getIncludes(); + } + + /** + * @return array + */ + public function getClassNames() + { + return $this->fileScanner->getClassNames(); + } + + /** + * @return array + */ + public function getClasses() + { + return $this->fileScanner->getClasses(); + } + + /** + * @param int|string $className + * @return ClassScanner + */ + public function getClass($className) + { + return $this->fileScanner->getClass($className); + } + + /** + * @param string $className + * @return bool|null|NameInformation + */ + public function getClassNameInformation($className) + { + return $this->fileScanner->getClassNameInformation($className); + } + + /** + * @return array + */ + public function getFunctionNames() + { + return $this->fileScanner->getFunctionNames(); + } + + /** + * @return array + */ + public function getFunctions() + { + return $this->fileScanner->getFunctions(); + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/ClassScanner.php b/vendor/zendframework/zend-code/src/Scanner/ClassScanner.php new file mode 100644 index 0000000..d7507a2 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/ClassScanner.php @@ -0,0 +1,1344 @@ +tokens = $classTokens; + $this->nameInformation = $nameInformation; + } + + /** + * Get annotations + * + * @param Annotation\AnnotationManager $annotationManager + * @return Annotation\AnnotationCollection + */ + public function getAnnotations(Annotation\AnnotationManager $annotationManager) + { + if (($docComment = $this->getDocComment()) == '') { + return false; + } + + return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation); + } + + /** + * Return documentation comment + * + * @return null|string + */ + public function getDocComment() + { + $this->scan(); + + return $this->docComment; + } + + /** + * Return documentation block + * + * @return false|DocBlockScanner + */ + public function getDocBlock() + { + if (!$docComment = $this->getDocComment()) { + return false; + } + + return new DocBlockScanner($docComment); + } + + /** + * Return a name of class + * + * @return null|string + */ + public function getName() + { + $this->scan(); + return $this->name; + } + + /** + * Return short name of class + * + * @return null|string + */ + public function getShortName() + { + $this->scan(); + return $this->shortName; + } + + /** + * Return number of first line + * + * @return int|null + */ + public function getLineStart() + { + $this->scan(); + return $this->lineStart; + } + + /** + * Return number of last line + * + * @return int|null + */ + public function getLineEnd() + { + $this->scan(); + return $this->lineEnd; + } + + /** + * Verify if class is final + * + * @return bool + */ + public function isFinal() + { + $this->scan(); + return $this->isFinal; + } + + /** + * Verify if class is a trait + * @return bool + */ + public function isTrait() + { + $this->scan(); + return $this->isTrait; + } + + /** + * Verify if class is instantiable + * + * @return bool + */ + public function isInstantiable() + { + $this->scan(); + return (!$this->isAbstract && !$this->isInterface); + } + + /** + * Verify if class is an abstract class + * + * @return bool + */ + public function isAbstract() + { + $this->scan(); + return $this->isAbstract; + } + + /** + * Verify if class is an interface + * + * @return bool + */ + public function isInterface() + { + $this->scan(); + return $this->isInterface; + } + + /** + * Verify if class has parent + * + * @return bool + */ + public function hasParentClass() + { + $this->scan(); + return ($this->parentClass !== null); + } + + /** + * Return a name of parent class + * + * @return null|string + */ + public function getParentClass() + { + $this->scan(); + return $this->parentClass; + } + + /** + * Return a list of interface names + * + * @return array + */ + public function getInterfaces() + { + $this->scan(); + return $this->interfaces; + } + + /** + * Return a list of constant names + * + * @return array + */ + public function getConstantNames() + { + $this->scan(); + + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] != 'constant') { + continue; + } + + $return[] = $info['name']; + } + + return $return; + } + + /** + * Return a list of constants + * + * @param bool $namesOnly Set false to return instances of ConstantScanner + * @return array|ConstantScanner[] + */ + public function getConstants($namesOnly = true) + { + if (true === $namesOnly) { + trigger_error('Use method getConstantNames() instead', E_USER_DEPRECATED); + return $this->getConstantNames(); + } + + $this->scan(); + + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] != 'constant') { + continue; + } + + $return[] = $this->getConstant($info['name']); + } + + return $return; + } + + /** + * Return a single constant by given name or index of info + * + * @param string|int $constantNameOrInfoIndex + * @throws Exception\InvalidArgumentException + * @return bool|ConstantScanner + */ + public function getConstant($constantNameOrInfoIndex) + { + $this->scan(); + + if (is_int($constantNameOrInfoIndex)) { + $info = $this->infos[$constantNameOrInfoIndex]; + if ($info['type'] != 'constant') { + throw new Exception\InvalidArgumentException('Index of info offset is not about a constant'); + } + } elseif (is_string($constantNameOrInfoIndex)) { + $constantFound = false; + foreach ($this->infos as $info) { + if ($info['type'] === 'constant' && $info['name'] === $constantNameOrInfoIndex) { + $constantFound = true; + break; + } + } + if (!$constantFound) { + return false; + } + } else { + throw new Exception\InvalidArgumentException( + 'Invalid constant name of info index type. Must be of type int or string' + ); + } + if (!isset($info)) { + return false; + } + $p = new ConstantScanner( + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1), + $this->nameInformation + ); + $p->setClass($this->name); + $p->setScannerClass($this); + return $p; + } + + /** + * Verify if class has constant + * + * @param string $name + * @return bool + */ + public function hasConstant($name) + { + $this->scan(); + + foreach ($this->infos as $info) { + if ($info['type'] === 'constant' && $info['name'] === $name) { + return true; + } + } + + return false; + } + + /** + * Return a list of property names + * + * @return array + */ + public function getPropertyNames() + { + $this->scan(); + + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] != 'property') { + continue; + } + + $return[] = $info['name']; + } + + return $return; + } + + /** + * Return a list of properties + * + * @return PropertyScanner[] + */ + public function getProperties() + { + $this->scan(); + + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] != 'property') { + continue; + } + + $return[] = $this->getProperty($info['name']); + } + + return $return; + } + + /** + * Return a single property by given name or index of info + * + * @param string|int $propertyNameOrInfoIndex + * @throws Exception\InvalidArgumentException + * @return bool|PropertyScanner + */ + public function getProperty($propertyNameOrInfoIndex) + { + $this->scan(); + + if (is_int($propertyNameOrInfoIndex)) { + $info = $this->infos[$propertyNameOrInfoIndex]; + if ($info['type'] != 'property') { + throw new Exception\InvalidArgumentException('Index of info offset is not about a property'); + } + } elseif (is_string($propertyNameOrInfoIndex)) { + $propertyFound = false; + foreach ($this->infos as $info) { + if ($info['type'] === 'property' && $info['name'] === $propertyNameOrInfoIndex) { + $propertyFound = true; + break; + } + } + if (!$propertyFound) { + return false; + } + } else { + throw new Exception\InvalidArgumentException( + 'Invalid property name of info index type. Must be of type int or string' + ); + } + if (!isset($info)) { + return false; + } + $p = new PropertyScanner( + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart'] + 1), + $this->nameInformation + ); + $p->setClass($this->name); + $p->setScannerClass($this); + return $p; + } + + /** + * Verify if class has property + * + * @param string $name + * @return bool + */ + public function hasProperty($name) + { + $this->scan(); + + foreach ($this->infos as $info) { + if ($info['type'] === 'property' && $info['name'] === $name) { + return true; + } + } + + return false; + } + + /** + * Retrieve any traits used by the class. + * + * @return ClassScanner[] + */ + public function getTraits() + { + if (! empty($this->traits)) { + return $this->traits; + } + + // get list of trait names + $traitNames = $this->getTraitNames(); + foreach ($traitNames as $traitName) { + $r = new ReflectionClass($traitName); + if (! $r->isTrait()) { + throw new Exception\RuntimeException(sprintf( + 'Non-trait class detected as a trait: %s', + $traitName + )); + } + $fileName = $r->getFileName(); + + $file = new FileScanner($fileName); + $this->traits[] = $file->getClass($traitName); + } + + return $this->traits; + } + + /** + * Retrieve a list of trait names used by this class. + * + * @return array + */ + public function getTraitNames() + { + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] !== 'use') { + continue; + } + + if (is_array($info['use_statements'])) { + foreach ($info['use_statements'] as $trait) { + $traitName = $trait; + if ($this->nameInformation instanceof NameInformation) { + $traitName = $this->nameInformation->resolveName($traitName); + } + $return[] = $traitName; + } + } + break; + } + + return $return; + } + + /** + * Retrieve a list of aliased traits used by the class. + * + * @return array + */ + public function getTraitAliases() + { + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] !== 'use') { + continue; + } + + if (is_array($info['aliases'])) { + foreach ($info['aliases'] as $alias) { + if (null === $alias + || (! empty($alias['type']) && $alias['type'] !== 'as') + ) { + continue; + } + + // attempt to get fqcn + list($trait, $method) = explode('::', $alias['original']); + if ($this->nameInformation instanceof NameInformation) { + $trait = $this->nameInformation->resolveName($trait); + } + + $return[$alias['alias']] = $trait . '::' . $method; + } + } + break; + } + + return $return; + } + + /** + * Retrieve visibility for a given alias. + * + * @param mixed $aliasName + * @return string + */ + protected function getVisibilityForAlias($aliasName) + { + $return = null; + foreach ($this->infos as $info) { + if ($info['type'] !== 'use') { + continue; + } + + if (is_array($info['aliases'])) { + foreach ($info['aliases'] as $alias) { + if (null === $alias + && (! empty($alias['type']) && $alias['type'] !== 'as') + ) { + continue; + } + + if ($alias['alias'] === $aliasName) { + $return = $alias['visibility']; + break 2; + } + } + } + break; + } + + return $return; + } + + /** + * Return an array of key = trait to keep, value = trait::method to ignore + * + * @return array + */ + protected function getBlockedTraitMethods() + { + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] !== 'use') { + continue; + } + + if (is_array($info['aliases'])) { + foreach ($info['aliases'] as $alias) { + if (null === $alias + || (! empty($alias['type']) && $alias['type'] !== 'insteadof') + ) { + continue; + } + + // attempt to get fqcn + list($trait, $method) = explode('::', $alias['original']); + if ($this->nameInformation instanceof NameInformation) { + $trait = $this->nameInformation->resolveName($alias['alias']); + } + + $return[] = $trait . '::' . $method; + } + } + break; + } + + return $return; + } + + /** + * Return a list of method names + * + * @return array + */ + public function getMethodNames() + { + $this->scan(); + + $methods = $this->getMethods(); + $return = array(); + foreach ($methods as $method) { + $return[] = $method->getName(); + } + + return $return; + } + + /** + * Return a list of methods + * + * @return MethodScanner[] + */ + public function getMethods() + { + $this->scan(); + + if (! empty($this->methods)) { + return $this->methods; + } + + foreach ($this->infos as $info) { + if ($info['type'] !== 'method' && $info['type'] !== 'use') { + continue; + } + + // Merge in trait methods + if ($info['type'] === "use") { + $traitMethods = array(); + $traits = $this->getTraits(); + $insteadof = $this->getBlockedTraitMethods(); + $aliases = $this->getTraitAliases(); + + foreach ($traits as $trait) { + $tempMethods = $trait->getMethods(); + foreach ($tempMethods as $tempMethod) { + $methodFullName = $trait->getName() . '::' . $tempMethod->getName(); + $methodAlias = array_search($methodFullName, $aliases); + + if (false !== $methodAlias) { + // trait::method is aliased + // clone the tempMethod as we need to change + // the name and possibly the visibility of the + // scanned method. + // + // @todo setName and setVisibility were added to + // MethodScanner to accomplish this, may not be the + // best option, could use ReflectionClass instead? + $newMethod = clone $tempMethod; + $newMethod->setName($methodAlias); + + // if visibility exists, change it on the MethodScanner + $visibility = $this->getVisibilityForAlias($methodAlias); + if (null !== $visibility) { + $newMethod->setVisibility($visibility); + } + $traitMethods[$methodAlias] = $newMethod; + } elseif (in_array($methodFullName, $insteadof)) { + // ignore overridden methods + continue; + } else { + if (array_key_exists($tempMethod->getName(), $traitMethods)) { + throw new Exception\RuntimeException(sprintf( + 'Trait method %s has not been applied because there are' + . ' collisions with other trait methods see: (insteadof OR as)', + $tempMethod->getName() + )); + } + + $traitMethods[$tempMethod->getName()] = $tempMethod; + } + } + } + + $this->methods = array_merge($this->methods, array_values($traitMethods)); + continue; + } + + $m = new MethodScanner( + array_slice( + $this->tokens, + $info['tokenStart'], + $info['tokenEnd'] - $info['tokenStart'] + 1 + ), + $this->nameInformation + ); + $m->setClass($this->name); + $m->setScannerClass($this); + + $this->methods[] = $m; + } + + return $this->methods; + } + + /** + * Return a single method by given name or index of info + * + * @param string|int $methodNameOrInfoIndex + * @throws Exception\InvalidArgumentException + * @return MethodScanner + */ + public function getMethod($methodNameOrInfoIndex) + { + $this->scan(); + + if (is_int($methodNameOrInfoIndex)) { + $info = $this->infos[$methodNameOrInfoIndex]; + if ($info['type'] != 'method') { + throw new Exception\InvalidArgumentException('Index of info offset is not about a method'); + } + $methodNameOrInfoIndex = $info['name']; + } + + $returnMethod = false; + $methods = $this->getMethods(); + foreach ($methods as $method) { + if ($method->getName() === $methodNameOrInfoIndex) { + $returnMethod = $method; + break; + } + } + + return $returnMethod; + } + + /** + * Verify if class has method by given name + * + * @param string $name + * @return bool + */ + public function hasMethod($name) + { + $this->scan(); + + return is_object($this->getMethod($name)); + } + + public static function export() + { + // @todo + } + + public function __toString() + { + // @todo + } + + /** + * Scan tokens + * + * @return void + * @throws Exception\RuntimeException + */ + protected function scan() + { + if ($this->isScanned) { + return; + } + + if (!$this->tokens) { + throw new Exception\RuntimeException('No tokens were provided'); + } + + /** + * Variables & Setup + */ + + $tokens = &$this->tokens; // localize + $infos = &$this->infos; // localize + $tokenIndex = null; + $token = null; + $tokenType = null; + $tokenContent = null; + $tokenLine = null; + $namespace = null; + $infoIndex = 0; + $braceCount = 0; + + /* + * MACRO creation + */ + $MACRO_TOKEN_ADVANCE = function () use ( + &$tokens, + &$tokenIndex, + &$token, + &$tokenType, + &$tokenContent, + &$tokenLine + ) { + static $lastTokenArray = null; + $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1; + if (!isset($tokens[$tokenIndex])) { + $token = false; + $tokenContent = false; + $tokenType = false; + $tokenLine = false; + + return false; + } + $token = $tokens[$tokenIndex]; + + if (is_string($token)) { + $tokenType = null; + $tokenContent = $token; + $tokenLine = $tokenLine + substr_count( + $lastTokenArray[1], + "\n" + ); // adjust token line by last known newline count + } else { + $lastTokenArray = $token; + list($tokenType, $tokenContent, $tokenLine) = $token; + } + + return $tokenIndex; + }; + $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) { + $infos[$infoIndex]['tokenEnd'] = $tokenIndex; + $infos[$infoIndex]['lineEnd'] = $tokenLine; + $infoIndex++; + + return $infoIndex; + }; + + /** + * START FINITE STATE MACHINE FOR SCANNING TOKENS + */ + + // Initialize token + $MACRO_TOKEN_ADVANCE(); + + SCANNER_TOP: + + switch ($tokenType) { + + case T_DOC_COMMENT: + + $this->docComment = $tokenContent; + goto SCANNER_CONTINUE; + //goto no break needed + + case T_FINAL: + case T_ABSTRACT: + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + + // CLASS INFORMATION + + $classContext = null; + $classInterfaceIndex = 0; + + SCANNER_CLASS_INFO_TOP: + + if (is_string($tokens[$tokenIndex + 1]) && $tokens[$tokenIndex + 1] === '{') { + goto SCANNER_CLASS_INFO_END; + } + + $this->lineStart = $tokenLine; + + switch ($tokenType) { + + case T_FINAL: + $this->isFinal = true; + goto SCANNER_CLASS_INFO_CONTINUE; + // goto no break needed + + case T_ABSTRACT: + $this->isAbstract = true; + goto SCANNER_CLASS_INFO_CONTINUE; + // goto no break needed + + case T_TRAIT: + $this->isTrait = true; + $this->shortName = $tokens[$tokenIndex + 2][1]; + if ($this->nameInformation && $this->nameInformation->hasNamespace()) { + $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName; + } else { + $this->name = $this->shortName; + } + goto SCANNER_CLASS_INFO_CONTINUE; + + case T_INTERFACE: + $this->isInterface = true; + //fall-through + case T_CLASS: + $this->shortName = $tokens[$tokenIndex + 2][1]; + if ($this->nameInformation && $this->nameInformation->hasNamespace()) { + $this->name = $this->nameInformation->getNamespace() . '\\' . $this->shortName; + } else { + $this->name = $this->shortName; + } + goto SCANNER_CLASS_INFO_CONTINUE; + //goto no break needed + + case T_NS_SEPARATOR: + case T_STRING: + switch ($classContext) { + case T_EXTENDS: + $this->shortParentClass .= $tokenContent; + break; + case T_IMPLEMENTS: + $this->shortInterfaces[$classInterfaceIndex] .= $tokenContent; + break; + } + goto SCANNER_CLASS_INFO_CONTINUE; + //goto no break needed + + case T_EXTENDS: + case T_IMPLEMENTS: + $classContext = $tokenType; + if (($this->isInterface && $classContext === T_EXTENDS) || $classContext === T_IMPLEMENTS) { + $this->shortInterfaces[$classInterfaceIndex] = ''; + } elseif (!$this->isInterface && $classContext === T_EXTENDS) { + $this->shortParentClass = ''; + } + goto SCANNER_CLASS_INFO_CONTINUE; + //goto no break needed + + case null: + if ($classContext == T_IMPLEMENTS && $tokenContent == ',') { + $classInterfaceIndex++; + $this->shortInterfaces[$classInterfaceIndex] = ''; + } + + } + + SCANNER_CLASS_INFO_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_CLASS_INFO_TOP; + + SCANNER_CLASS_INFO_END: + + goto SCANNER_CONTINUE; + + } + + if ($tokenType === null && $tokenContent === '{' && $braceCount === 0) { + $braceCount++; + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + + SCANNER_CLASS_BODY_TOP: + + if ($braceCount === 0) { + goto SCANNER_CLASS_BODY_END; + } + + switch ($tokenType) { + + case T_CONST: + + $infos[$infoIndex] = array( + 'type' => 'constant', + 'tokenStart' => $tokenIndex, + 'tokenEnd' => null, + 'lineStart' => $tokenLine, + 'lineEnd' => null, + 'name' => null, + 'value' => null, + ); + + SCANNER_CLASS_BODY_CONST_TOP: + + if ($tokenContent === ';') { + goto SCANNER_CLASS_BODY_CONST_END; + } + + if ($tokenType === T_STRING && null === $infos[$infoIndex]['name']) { + $infos[$infoIndex]['name'] = $tokenContent; + } + + SCANNER_CLASS_BODY_CONST_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_CLASS_BODY_CONST_TOP; + + SCANNER_CLASS_BODY_CONST_END: + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CLASS_BODY_CONTINUE; + // goto no break needed + + case T_USE: + // ensure php backwards compatibility + if (! defined('T_INSTEADOF')) { + define('T_INSTEADOF', 24000); + } + + $infos[$infoIndex] = array( + 'type' => 'use', + 'tokenStart' => $tokenIndex, + 'tokenEnd' => null, + 'lineStart' => $tokens[$tokenIndex][2], + 'lineEnd' => null, + 'name' => $namespace, + 'use_statements' => array(0 => null), + 'aliases' => array(0 => null), + ); + + $isOriginalName = array(T_STRING, T_DOUBLE_COLON); + $isAlias = array(T_STRING); + $isVisibility = array(T_PRIVATE, T_PROTECTED, T_PUBLIC, T_STATIC); + $isAliasType = array(T_AS, T_INSTEADOF); + $isValidAlias = array_merge($isOriginalName, $isAlias, $isVisibility, $isAliasType); + + $useStatementIndex = 0; + $aliasStatementIndex = 0; + $useAliasContext = false; + $useAsContext = false; + + // start processing with next token + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + + SCANNER_USE_TOP: + + if ($tokenType === null) { + if ($tokenContent === "{") { + $useStatementIndex = 0; + $useAliasContext = true; + $infos[$infoIndex]['aliases'][$useStatementIndex] = array( + 'original' => null, + 'alias' => null, + 'visibility' => null, + 'type' => 'as' + ); + } elseif ($tokenContent === "}") { + $useAliasContext = false; + goto SCANNER_USE_END; + } elseif ($tokenContent === ';') { + if ($useAliasContext === true) { + $useStatementIndex++; + $useAsContext = false; + } + // only end if we aren't inside braces + if (false === $useAliasContext) { + goto SCANNER_USE_END; + } + } elseif ($tokenContent === ',') { + $useStatementIndex++; + $infos[$infoIndex]['use_statements'][$useStatementIndex] = ''; + } + } + + // ANALYZE + if ($tokenType !== null) { + // use context + if (false === $useAliasContext) { + if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) { + $infos[$infoIndex]['use_statements'][$useStatementIndex] .= $tokenContent; + } + } else { + if (in_array($tokenType, $isValidAlias) + && empty($infos[$infoIndex]['aliases'][$useStatementIndex]) + ) { + $infos[$infoIndex]['aliases'][$useStatementIndex] = array( + 'original' => null, + 'visibility' => null, + 'alias' => null, + 'type' => null + ); + } + + if ($tokenType == T_AS || $tokenType == T_INSTEADOF) { + $useAsContext = true; + $infos[$infoIndex]['aliases'][$useStatementIndex]['type'] = ($tokenType == T_INSTEADOF) + ? 'insteadof' + : 'as'; + goto SCANNER_USE_CONTINUE; + } + + // in alias context + if ($useAsContext === true && in_array($tokenType, $isAlias)) { + $infos[$infoIndex]['aliases'][$useStatementIndex]['alias'] = $tokenContent; + } elseif (in_array($tokenType, $isOriginalName)) { + $infos[$infoIndex]['aliases'][$useStatementIndex]['original'] .= $tokenContent; + } elseif (in_array($tokenType, $isVisibility)) { + //add whitespace (will trim later) + $infos[$infoIndex]['aliases'][$useStatementIndex]['visibility'] = $tokenType; + } + } + } + + SCANNER_USE_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_USE_TOP; + + SCANNER_USE_END: + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CLASS_BODY_CONTINUE; + + case T_DOC_COMMENT: + case T_PUBLIC: + case T_PROTECTED: + case T_PRIVATE: + case T_ABSTRACT: + case T_FINAL: + case T_VAR: + case T_FUNCTION: + + $infos[$infoIndex] = array( + 'type' => null, + 'tokenStart' => $tokenIndex, + 'tokenEnd' => null, + 'lineStart' => $tokenLine, + 'lineEnd' => null, + 'name' => null, + ); + + $memberContext = null; + $methodBodyStarted = false; + + SCANNER_CLASS_BODY_MEMBER_TOP: + + if ($memberContext === 'method') { + switch ($tokenContent) { + case '{': + $methodBodyStarted = true; + $braceCount++; + goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; + // goto no break needed + case '}': + $braceCount--; + goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; + + case ';': + $infos[$infoIndex]['tokenEnd'] = $tokenIndex; + goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; + } + } + + if ($memberContext !== null) { + if ( + ($memberContext === 'property' && $tokenContent === ';') + || ($memberContext === 'method' && $methodBodyStarted && $braceCount === 1) + || ($memberContext === 'method' && $this->isInterface && $tokenContent === ';') + ) { + goto SCANNER_CLASS_BODY_MEMBER_END; + } + } + + switch ($tokenType) { + + case T_CONST: + $memberContext = 'constant'; + $infos[$infoIndex]['type'] = 'constant'; + goto SCANNER_CLASS_BODY_CONST_CONTINUE; + //goto no break needed + + case T_VARIABLE: + if ($memberContext === null) { + $memberContext = 'property'; + $infos[$infoIndex]['type'] = 'property'; + $infos[$infoIndex]['name'] = ltrim($tokenContent, '$'); + } + goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; + // goto no break needed + + case T_FUNCTION: + $memberContext = 'method'; + $infos[$infoIndex]['type'] = 'method'; + goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; + // goto no break needed + + case T_STRING: + if ($memberContext === 'method' && null === $infos[$infoIndex]['name']) { + $infos[$infoIndex]['name'] = $tokenContent; + } + goto SCANNER_CLASS_BODY_MEMBER_CONTINUE; + // goto no break needed + } + + SCANNER_CLASS_BODY_MEMBER_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_CLASS_BODY_MEMBER_TOP; + + SCANNER_CLASS_BODY_MEMBER_END: + + $memberContext = null; + $MACRO_INFO_ADVANCE(); + goto SCANNER_CLASS_BODY_CONTINUE; + // goto no break needed + + case null: // no type, is a string + + switch ($tokenContent) { + case '{': + $braceCount++; + goto SCANNER_CLASS_BODY_CONTINUE; + // goto no break needed + + case '}': + $braceCount--; + goto SCANNER_CLASS_BODY_CONTINUE; + } + } + + SCANNER_CLASS_BODY_CONTINUE: + + if ($braceCount === 0 || $MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_CONTINUE; + } + goto SCANNER_CLASS_BODY_TOP; + + SCANNER_CLASS_BODY_END: + + goto SCANNER_CONTINUE; + } + + SCANNER_CONTINUE: + + if ($tokenContent === '}') { + $this->lineEnd = $tokenLine; + } + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_TOP; + + SCANNER_END: + + // process short names + if ($this->nameInformation) { + if ($this->shortParentClass) { + $this->parentClass = $this->nameInformation->resolveName($this->shortParentClass); + } + if ($this->shortInterfaces) { + foreach ($this->shortInterfaces as $siIndex => $si) { + $this->interfaces[$siIndex] = $this->nameInformation->resolveName($si); + } + } + } else { + $this->parentClass = $this->shortParentClass; + $this->interfaces = $this->shortInterfaces; + } + + $this->isScanned = true; + + return; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/ConstantScanner.php b/vendor/zendframework/zend-code/src/Scanner/ConstantScanner.php new file mode 100644 index 0000000..f2a81da --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/ConstantScanner.php @@ -0,0 +1,236 @@ +tokens = $constantTokens; + $this->nameInformation = $nameInformation; + } + + /** + * @param string $class + */ + public function setClass($class) + { + $this->class = $class; + } + + /** + * @param ClassScanner $scannerClass + */ + public function setScannerClass(ClassScanner $scannerClass) + { + $this->scannerClass = $scannerClass; + } + + /** + * @return ClassScanner + */ + public function getClassScanner() + { + return $this->scannerClass; + } + + /** + * @return string + */ + public function getName() + { + $this->scan(); + return $this->name; + } + + /** + * @return string + */ + public function getValue() + { + $this->scan(); + return $this->value; + } + + /** + * @return string + */ + public function getDocComment() + { + $this->scan(); + return $this->docComment; + } + + /** + * @param Annotation\AnnotationManager $annotationManager + * @return AnnotationScanner + */ + public function getAnnotations(Annotation\AnnotationManager $annotationManager) + { + if (($docComment = $this->getDocComment()) == '') { + return false; + } + + return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation); + } + + /** + * @return string + */ + public function __toString() + { + $this->scan(); + return var_export($this, true); + } + + /** + * Scan tokens + * + * @throws Exception\RuntimeException + */ + protected function scan() + { + if ($this->isScanned) { + return; + } + + if (!$this->tokens) { + throw new Exception\RuntimeException('No tokens were provided'); + } + + /** + * Variables & Setup + */ + $tokens = &$this->tokens; + + reset($tokens); + + SCANNER_TOP: + + $token = current($tokens); + + if (!is_string($token)) { + list($tokenType, $tokenContent, $tokenLine) = $token; + + switch ($tokenType) { + case T_DOC_COMMENT: + if ($this->docComment === null && $this->name === null) { + $this->docComment = $tokenContent; + } + goto SCANNER_CONTINUE; + // fall-through + + case T_STRING: + $string = (is_string($token)) ? $token : $tokenContent; + + if (null === $this->name) { + $this->name = $string; + } else { + if ('self' == strtolower($string)) { + list($tokenNextType, $tokenNextContent, $tokenNextLine) = next($tokens); + + if ('::' == $tokenNextContent) { + list($tokenNextType, $tokenNextContent, $tokenNextLine) = next($tokens); + + if ($this->getClassScanner()->getConstant($tokenNextContent)) { + $this->value = $this->getClassScanner()->getConstant($tokenNextContent)->getValue(); + } + } + } + } + + goto SCANNER_CONTINUE; + // fall-through + + case T_CONSTANT_ENCAPSED_STRING: + case T_DNUMBER: + case T_LNUMBER: + $string = (is_string($token)) ? $token : $tokenContent; + + if (substr($string, 0, 1) === '"' || substr($string, 0, 1) === "'") { + $this->value = substr($string, 1, -1); // Remove quotes + } else { + $this->value = $string; + } + goto SCANNER_CONTINUE; + // fall-trough + + default: + goto SCANNER_CONTINUE; + } + } + + SCANNER_CONTINUE: + + if (next($this->tokens) === false) { + goto SCANNER_END; + } + goto SCANNER_TOP; + + SCANNER_END: + + $this->isScanned = true; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/DerivedClassScanner.php b/vendor/zendframework/zend-code/src/Scanner/DerivedClassScanner.php new file mode 100644 index 0000000..4790c73 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/DerivedClassScanner.php @@ -0,0 +1,381 @@ +classScanner = $classScanner; + $this->directoryScanner = $directoryScanner; + + $currentScannerClass = $classScanner; + + while ($currentScannerClass && $currentScannerClass->hasParentClass()) { + $currentParentClassName = $currentScannerClass->getParentClass(); + if ($directoryScanner->hasClass($currentParentClassName)) { + $currentParentClass = $directoryScanner->getClass($currentParentClassName); + $this->parentClassScanners[$currentParentClassName] = $currentParentClass; + $currentScannerClass = $currentParentClass; + } else { + $currentScannerClass = false; + } + } + + foreach ($interfaces = $this->classScanner->getInterfaces() as $iName) { + if ($directoryScanner->hasClass($iName)) { + $this->interfaceClassScanners[$iName] = $directoryScanner->getClass($iName); + } + } + } + + /** + * @return null|string + */ + public function getName() + { + return $this->classScanner->getName(); + } + + /** + * @return null|string + */ + public function getShortName() + { + return $this->classScanner->getShortName(); + } + + /** + * @return bool + */ + public function isInstantiable() + { + return $this->classScanner->isInstantiable(); + } + + /** + * @return bool + */ + public function isFinal() + { + return $this->classScanner->isFinal(); + } + + /** + * @return bool + */ + public function isAbstract() + { + return $this->classScanner->isAbstract(); + } + + /** + * @return bool + */ + public function isInterface() + { + return $this->classScanner->isInterface(); + } + + /** + * @return array + */ + public function getParentClasses() + { + return array_keys($this->parentClassScanners); + } + + /** + * @return bool + */ + public function hasParentClass() + { + return ($this->classScanner->getParentClass() !== null); + } + + /** + * @return null|string + */ + public function getParentClass() + { + return $this->classScanner->getParentClass(); + } + + /** + * @param bool $returnClassScanners + * @return array + */ + public function getInterfaces($returnClassScanners = false) + { + if ($returnClassScanners) { + return $this->interfaceClassScanners; + } + + $interfaces = $this->classScanner->getInterfaces(); + foreach ($this->parentClassScanners as $pClassScanner) { + $interfaces = array_merge($interfaces, $pClassScanner->getInterfaces()); + } + + return $interfaces; + } + + /** + * Return a list of constant names + * + * @return array + */ + public function getConstantNames() + { + $constants = $this->classScanner->getConstantNames(); + foreach ($this->parentClassScanners as $pClassScanner) { + $constants = array_merge($constants, $pClassScanner->getConstantNames()); + } + + return $constants; + } + + /** + * Return a list of constants + * + * @param bool $namesOnly Set false to return instances of ConstantScanner + * @return array|ConstantScanner[] + */ + public function getConstants($namesOnly = true) + { + if (true === $namesOnly) { + trigger_error('Use method getConstantNames() instead', E_USER_DEPRECATED); + return $this->getConstantNames(); + } + + $constants = $this->classScanner->getConstants(); + foreach ($this->parentClassScanners as $pClassScanner) { + $constants = array_merge($constants, $pClassScanner->getConstants($namesOnly)); + } + + return $constants; + } + + /** + * Return a single constant by given name or index of info + * + * @param string|int $constantNameOrInfoIndex + * @throws Exception\InvalidArgumentException + * @return bool|ConstantScanner + */ + public function getConstant($constantNameOrInfoIndex) + { + if ($this->classScanner->hasConstant($constantNameOrInfoIndex)) { + return $this->classScanner->getConstant($constantNameOrInfoIndex); + } + + foreach ($this->parentClassScanners as $pClassScanner) { + if ($pClassScanner->hasConstant($constantNameOrInfoIndex)) { + return $pClassScanner->getConstant($constantNameOrInfoIndex); + } + } + + throw new Exception\InvalidArgumentException(sprintf( + 'Constant %s not found in %s', + $constantNameOrInfoIndex, + $this->classScanner->getName() + )); + } + + /** + * Verify if class or parent class has constant + * + * @param string $name + * @return bool + */ + public function hasConstant($name) + { + if ($this->classScanner->hasConstant($name)) { + return true; + } + foreach ($this->parentClassScanners as $pClassScanner) { + if ($pClassScanner->hasConstant($name)) { + return true; + } + } + + return false; + } + + /** + * Return a list of property names + * + * @return array + */ + public function getPropertyNames() + { + $properties = $this->classScanner->getPropertyNames(); + foreach ($this->parentClassScanners as $pClassScanner) { + $properties = array_merge($properties, $pClassScanner->getPropertyNames()); + } + + return $properties; + } + + /** + * @param bool $returnScannerProperty + * @return array + */ + public function getProperties($returnScannerProperty = false) + { + $properties = $this->classScanner->getProperties($returnScannerProperty); + foreach ($this->parentClassScanners as $pClassScanner) { + $properties = array_merge($properties, $pClassScanner->getProperties($returnScannerProperty)); + } + + return $properties; + } + + /** + * Return a single property by given name or index of info + * + * @param string|int $propertyNameOrInfoIndex + * @throws Exception\InvalidArgumentException + * @return bool|PropertyScanner + */ + public function getProperty($propertyNameOrInfoIndex) + { + if ($this->classScanner->hasProperty($propertyNameOrInfoIndex)) { + return $this->classScanner->getProperty($propertyNameOrInfoIndex); + } + + foreach ($this->parentClassScanners as $pClassScanner) { + if ($pClassScanner->hasProperty($propertyNameOrInfoIndex)) { + return $pClassScanner->getProperty($propertyNameOrInfoIndex); + } + } + + throw new Exception\InvalidArgumentException(sprintf( + 'Property %s not found in %s', + $propertyNameOrInfoIndex, + $this->classScanner->getName() + )); + } + + /** + * Verify if class or parent class has property + * + * @param string $name + * @return bool + */ + public function hasProperty($name) + { + if ($this->classScanner->hasProperty($name)) { + return true; + } + foreach ($this->parentClassScanners as $pClassScanner) { + if ($pClassScanner->hasProperty($name)) { + return true; + } + } + + return false; + } + + /** + * @return array + */ + public function getMethodNames() + { + $methods = $this->classScanner->getMethodNames(); + foreach ($this->parentClassScanners as $pClassScanner) { + $methods = array_merge($methods, $pClassScanner->getMethodNames()); + } + + return $methods; + } + + /** + * @return MethodScanner[] + */ + public function getMethods() + { + $methods = $this->classScanner->getMethods(); + foreach ($this->parentClassScanners as $pClassScanner) { + $methods = array_merge($methods, $pClassScanner->getMethods()); + } + + return $methods; + } + + /** + * @param int|string $methodNameOrInfoIndex + * @return MethodScanner + * @throws Exception\InvalidArgumentException + */ + public function getMethod($methodNameOrInfoIndex) + { + if ($this->classScanner->hasMethod($methodNameOrInfoIndex)) { + return $this->classScanner->getMethod($methodNameOrInfoIndex); + } + + foreach ($this->parentClassScanners as $pClassScanner) { + if ($pClassScanner->hasMethod($methodNameOrInfoIndex)) { + return $pClassScanner->getMethod($methodNameOrInfoIndex); + } + } + + throw new Exception\InvalidArgumentException(sprintf( + 'Method %s not found in %s', + $methodNameOrInfoIndex, + $this->classScanner->getName() + )); + } + + /** + * Verify if class or parent class has method by given name + * + * @param string $name + * @return bool + */ + public function hasMethod($name) + { + if ($this->classScanner->hasMethod($name)) { + return true; + } + foreach ($this->parentClassScanners as $pClassScanner) { + if ($pClassScanner->hasMethod($name)) { + return true; + } + } + + return false; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/DirectoryScanner.php b/vendor/zendframework/zend-code/src/Scanner/DirectoryScanner.php new file mode 100644 index 0000000..33120ff --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/DirectoryScanner.php @@ -0,0 +1,272 @@ +addDirectory($directory); + } elseif (is_array($directory)) { + foreach ($directory as $d) { + $this->addDirectory($d); + } + } + } + } + + /** + * @param DirectoryScanner|string $directory + * @return void + * @throws Exception\InvalidArgumentException + */ + public function addDirectory($directory) + { + if ($directory instanceof DirectoryScanner) { + $this->directories[] = $directory; + } elseif (is_string($directory)) { + $realDir = realpath($directory); + if (!$realDir || !is_dir($realDir)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Directory "%s" does not exist', + $realDir + )); + } + $this->directories[] = $realDir; + } else { + throw new Exception\InvalidArgumentException( + 'The argument provided was neither a DirectoryScanner or directory path' + ); + } + } + + /** + * @param DirectoryScanner $directoryScanner + * @return void + */ + public function addDirectoryScanner(DirectoryScanner $directoryScanner) + { + $this->addDirectory($directoryScanner); + } + + /** + * @param FileScanner $fileScanner + * @return void + */ + public function addFileScanner(FileScanner $fileScanner) + { + $this->fileScanners[] = $fileScanner; + } + + /** + * @return void + */ + protected function scan() + { + if ($this->isScanned) { + return; + } + + // iterate directories creating file scanners + foreach ($this->directories as $directory) { + if ($directory instanceof DirectoryScanner) { + $directory->scan(); + if ($directory->fileScanners) { + $this->fileScanners = array_merge($this->fileScanners, $directory->fileScanners); + } + } else { + $rdi = new RecursiveDirectoryIterator($directory); + foreach (new RecursiveIteratorIterator($rdi) as $item) { + if ($item->isFile() && pathinfo($item->getRealPath(), PATHINFO_EXTENSION) == 'php') { + $this->fileScanners[] = new FileScanner($item->getRealPath()); + } + } + } + } + + $this->isScanned = true; + } + + /** + * @todo implement method + */ + public function getNamespaces() + { + // @todo + } + + /** + * @param bool $returnFileScanners + * @return array + */ + public function getFiles($returnFileScanners = false) + { + $this->scan(); + + $return = array(); + foreach ($this->fileScanners as $fileScanner) { + $return[] = ($returnFileScanners) ? $fileScanner : $fileScanner->getFile(); + } + + return $return; + } + + /** + * @return array + */ + public function getClassNames() + { + $this->scan(); + + if ($this->classToFileScanner === null) { + $this->createClassToFileScannerCache(); + } + + return array_keys($this->classToFileScanner); + } + + /** + * @param bool $returnDerivedScannerClass + * @return array + */ + public function getClasses($returnDerivedScannerClass = false) + { + $this->scan(); + + if ($this->classToFileScanner === null) { + $this->createClassToFileScannerCache(); + } + + $returnClasses = array(); + foreach ($this->classToFileScanner as $className => $fsIndex) { + $classScanner = $this->fileScanners[$fsIndex]->getClass($className); + if ($returnDerivedScannerClass) { + $classScanner = new DerivedClassScanner($classScanner, $this); + } + $returnClasses[] = $classScanner; + } + + return $returnClasses; + } + + /** + * @param string $class + * @return bool + */ + public function hasClass($class) + { + $this->scan(); + + if ($this->classToFileScanner === null) { + $this->createClassToFileScannerCache(); + } + + return (isset($this->classToFileScanner[$class])); + } + + /** + * @param string $class + * @param bool $returnDerivedScannerClass + * @return ClassScanner|DerivedClassScanner + * @throws Exception\InvalidArgumentException + */ + public function getClass($class, $returnDerivedScannerClass = false) + { + $this->scan(); + + if ($this->classToFileScanner === null) { + $this->createClassToFileScannerCache(); + } + + if (!isset($this->classToFileScanner[$class])) { + throw new Exception\InvalidArgumentException('Class not found.'); + } + + /** @var FileScanner $fs */ + $fs = $this->fileScanners[$this->classToFileScanner[$class]]; + $returnClass = $fs->getClass($class); + + if (($returnClass instanceof ClassScanner) && $returnDerivedScannerClass) { + return new DerivedClassScanner($returnClass, $this); + } + + return $returnClass; + } + + /** + * Create class to file scanner cache + * + * @return void + */ + protected function createClassToFileScannerCache() + { + if ($this->classToFileScanner !== null) { + return; + } + + $this->classToFileScanner = array(); + /** @var FileScanner $fileScanner */ + foreach ($this->fileScanners as $fsIndex => $fileScanner) { + $fsClasses = $fileScanner->getClassNames(); + foreach ($fsClasses as $fsClassName) { + $this->classToFileScanner[$fsClassName] = $fsIndex; + } + } + } + + /** + * Export + * + * @todo implement method + */ + public static function export() + { + // @todo + } + + /** + * __ToString + * + * @todo implement method + */ + public function __toString() + { + // @todo + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/DocBlockScanner.php b/vendor/zendframework/zend-code/src/Scanner/DocBlockScanner.php new file mode 100644 index 0000000..b367849 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/DocBlockScanner.php @@ -0,0 +1,326 @@ +docComment = $docComment; + $this->nameInformation = $nameInformation; + } + + /** + * @return string + */ + public function getShortDescription() + { + $this->scan(); + + return $this->shortDescription; + } + + /** + * @return string + */ + public function getLongDescription() + { + $this->scan(); + + return $this->longDescription; + } + + /** + * @return array + */ + public function getTags() + { + $this->scan(); + + return $this->tags; + } + + /** + * @return array + */ + public function getAnnotations() + { + $this->scan(); + + return $this->annotations; + } + + /** + * @return void + */ + protected function scan() + { + if ($this->isScanned) { + return; + } + + $mode = 1; + + $tokens = $this->tokenize(); + $tagIndex = null; + reset($tokens); + + SCANNER_TOP: + $token = current($tokens); + + switch ($token[0]) { + case 'DOCBLOCK_NEWLINE': + if ($this->shortDescription != '' && $tagIndex === null) { + $mode = 2; + } else { + $this->longDescription .= $token[1]; + } + goto SCANNER_CONTINUE; + //goto no break needed + + case 'DOCBLOCK_WHITESPACE': + case 'DOCBLOCK_TEXT': + if ($tagIndex !== null) { + $this->tags[$tagIndex]['value'] .= ($this->tags[$tagIndex]['value'] == '') ? $token[1] : ' ' . $token[1]; + goto SCANNER_CONTINUE; + } elseif ($mode <= 2) { + if ($mode == 1) { + $this->shortDescription .= $token[1]; + } else { + $this->longDescription .= $token[1]; + } + goto SCANNER_CONTINUE; + } + //gotos no break needed + case 'DOCBLOCK_TAG': + array_push($this->tags, array('name' => $token[1], + 'value' => '')); + end($this->tags); + $tagIndex = key($this->tags); + $mode = 3; + goto SCANNER_CONTINUE; + //goto no break needed + + case 'DOCBLOCK_COMMENTEND': + goto SCANNER_END; + + } + + SCANNER_CONTINUE: + if (next($tokens) === false) { + goto SCANNER_END; + } + goto SCANNER_TOP; + + SCANNER_END: + + $this->shortDescription = trim($this->shortDescription); + $this->longDescription = trim($this->longDescription); + $this->isScanned = true; + } + + /** + * @return array + */ + protected function tokenize() + { + static $CONTEXT_INSIDE_DOCBLOCK = 0x01; + static $CONTEXT_INSIDE_ASTERISK = 0x02; + + $context = 0x00; + $stream = $this->docComment; + $streamIndex = null; + $tokens = array(); + $tokenIndex = null; + $currentChar = null; + $currentWord = null; + $currentLine = null; + + $MACRO_STREAM_ADVANCE_CHAR = function ($positionsForward = 1) use (&$stream, &$streamIndex, &$currentChar, &$currentWord, &$currentLine) { + $positionsForward = ($positionsForward > 0) ? $positionsForward : 1; + $streamIndex = ($streamIndex === null) ? 0 : $streamIndex + $positionsForward; + if (!isset($stream[$streamIndex])) { + $currentChar = false; + + return false; + } + $currentChar = $stream[$streamIndex]; + $matches = array(); + $currentLine = (preg_match('#(.*?)\r?\n#', $stream, $matches, null, $streamIndex) === 1) ? $matches[1] : substr($stream, $streamIndex); + if ($currentChar === ' ') { + $currentWord = (preg_match('#( +)#', $currentLine, $matches) === 1) ? $matches[1] : $currentLine; + } else { + $currentWord = (($matches = strpos($currentLine, ' ')) !== false) ? substr($currentLine, 0, $matches) : $currentLine; + } + + return $currentChar; + }; + $MACRO_STREAM_ADVANCE_WORD = function () use (&$currentWord, &$MACRO_STREAM_ADVANCE_CHAR) { + return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentWord)); + }; + $MACRO_STREAM_ADVANCE_LINE = function () use (&$currentLine, &$MACRO_STREAM_ADVANCE_CHAR) { + return $MACRO_STREAM_ADVANCE_CHAR(strlen($currentLine)); + }; + $MACRO_TOKEN_ADVANCE = function () use (&$tokenIndex, &$tokens) { + $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1; + $tokens[$tokenIndex] = array('DOCBLOCK_UNKNOWN', ''); + }; + $MACRO_TOKEN_SET_TYPE = function ($type) use (&$tokenIndex, &$tokens) { + $tokens[$tokenIndex][0] = $type; + }; + $MACRO_TOKEN_APPEND_CHAR = function () use (&$currentChar, &$tokens, &$tokenIndex) { + $tokens[$tokenIndex][1] .= $currentChar; + }; + $MACRO_TOKEN_APPEND_WORD = function () use (&$currentWord, &$tokens, &$tokenIndex) { + $tokens[$tokenIndex][1] .= $currentWord; + }; + $MACRO_TOKEN_APPEND_WORD_PARTIAL = function ($length) use (&$currentWord, &$tokens, &$tokenIndex) { + $tokens[$tokenIndex][1] .= substr($currentWord, 0, $length); + }; + $MACRO_TOKEN_APPEND_LINE = function () use (&$currentLine, &$tokens, &$tokenIndex) { + $tokens[$tokenIndex][1] .= $currentLine; + }; + + $MACRO_STREAM_ADVANCE_CHAR(); + $MACRO_TOKEN_ADVANCE(); + + TOKENIZER_TOP: + + if ($context === 0x00 && $currentChar === '/' && $currentWord === '/**') { + $MACRO_TOKEN_SET_TYPE('DOCBLOCK_COMMENTSTART'); + $MACRO_TOKEN_APPEND_WORD(); + $MACRO_TOKEN_ADVANCE(); + $context |= $CONTEXT_INSIDE_DOCBLOCK; + $context |= $CONTEXT_INSIDE_ASTERISK; + if ($MACRO_STREAM_ADVANCE_WORD() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($context & $CONTEXT_INSIDE_DOCBLOCK && $currentWord === '*/') { + $MACRO_TOKEN_SET_TYPE('DOCBLOCK_COMMENTEND'); + $MACRO_TOKEN_APPEND_WORD(); + $MACRO_TOKEN_ADVANCE(); + $context &= ~$CONTEXT_INSIDE_DOCBLOCK; + if ($MACRO_STREAM_ADVANCE_WORD() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($currentChar === ' ' || $currentChar === "\t") { + $MACRO_TOKEN_SET_TYPE(($context & $CONTEXT_INSIDE_ASTERISK) ? 'DOCBLOCK_WHITESPACE' : 'DOCBLOCK_WHITESPACE_INDENT'); + $MACRO_TOKEN_APPEND_WORD(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_WORD() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($currentChar === '*') { + if (($context & $CONTEXT_INSIDE_DOCBLOCK) && ($context & $CONTEXT_INSIDE_ASTERISK)) { + $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TEXT'); + } else { + $MACRO_TOKEN_SET_TYPE('DOCBLOCK_ASTERISK'); + $context |= $CONTEXT_INSIDE_ASTERISK; + } + $MACRO_TOKEN_APPEND_CHAR(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_CHAR() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($currentChar === '@') { + $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TAG'); + $MACRO_TOKEN_APPEND_WORD(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_WORD() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + if ($currentChar === "\n") { + $MACRO_TOKEN_SET_TYPE('DOCBLOCK_NEWLINE'); + $MACRO_TOKEN_APPEND_CHAR(); + $MACRO_TOKEN_ADVANCE(); + $context &= ~$CONTEXT_INSIDE_ASTERISK; + if ($MACRO_STREAM_ADVANCE_CHAR() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + } + + $MACRO_TOKEN_SET_TYPE('DOCBLOCK_TEXT'); + $MACRO_TOKEN_APPEND_LINE(); + $MACRO_TOKEN_ADVANCE(); + if ($MACRO_STREAM_ADVANCE_LINE() === false) { + goto TOKENIZER_END; + } + goto TOKENIZER_TOP; + + TOKENIZER_END: + + array_pop($tokens); + + return $tokens; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/FileScanner.php b/vendor/zendframework/zend-code/src/Scanner/FileScanner.php new file mode 100644 index 0000000..e462f72 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/FileScanner.php @@ -0,0 +1,46 @@ +file = $file; + if (!file_exists($file)) { + throw new Exception\InvalidArgumentException(sprintf( + 'File "%s" not found', + $file + )); + } + parent::__construct(token_get_all(file_get_contents($file)), $annotationManager); + } + + /** + * @return string + */ + public function getFile() + { + return $this->file; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/FunctionScanner.php b/vendor/zendframework/zend-code/src/Scanner/FunctionScanner.php new file mode 100644 index 0000000..d1fd219 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/FunctionScanner.php @@ -0,0 +1,16 @@ +tokens = $methodTokens; + $this->nameInformation = $nameInformation; + } + + /** + * @param string $class + * @return MethodScanner + */ + public function setClass($class) + { + $this->class = (string) $class; + return $this; + } + + /** + * @param ClassScanner $scannerClass + * @return MethodScanner + */ + public function setScannerClass(ClassScanner $scannerClass) + { + $this->scannerClass = $scannerClass; + return $this; + } + + /** + * @return MethodScanner + */ + public function getClassScanner() + { + return $this->scannerClass; + } + + /** + * @return string + */ + public function getName() + { + $this->scan(); + + return $this->name; + } + + /** + * @return int + */ + public function getLineStart() + { + $this->scan(); + + return $this->lineStart; + } + + /** + * @return int + */ + public function getLineEnd() + { + $this->scan(); + + return $this->lineEnd; + } + + /** + * @return string + */ + public function getDocComment() + { + $this->scan(); + + return $this->docComment; + } + + /** + * @param AnnotationManager $annotationManager + * @return AnnotationScanner + */ + public function getAnnotations(AnnotationManager $annotationManager) + { + if (($docComment = $this->getDocComment()) == '') { + return false; + } + + return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation); + } + + /** + * @return bool + */ + public function isFinal() + { + $this->scan(); + + return $this->isFinal; + } + + /** + * @return bool + */ + public function isAbstract() + { + $this->scan(); + + return $this->isAbstract; + } + + /** + * @return bool + */ + public function isPublic() + { + $this->scan(); + + return $this->isPublic; + } + + /** + * @return bool + */ + public function isProtected() + { + $this->scan(); + + return $this->isProtected; + } + + /** + * @return bool + */ + public function isPrivate() + { + $this->scan(); + + return $this->isPrivate; + } + + /** + * @return bool + */ + public function isStatic() + { + $this->scan(); + + return $this->isStatic; + } + + /** + * Override the given name for a method, this is necessary to + * support traits. + * + * @param $name + * @return self + */ + public function setName($name) + { + $this->name = $name; + return $this; + } + + /** + * Visibility must be of T_PUBLIC, T_PRIVATE or T_PROTECTED + * Needed to support traits + * + * @param $visibility T_PUBLIC | T_PRIVATE | T_PROTECTED + * @return self + * @throws \Zend\Code\Exception + */ + public function setVisibility($visibility) + { + switch (strtolower($visibility)) { + case T_PUBLIC: + $this->isPublic = true; + $this->isPrivate = false; + $this->isProtected = false; + break; + + case T_PRIVATE: + $this->isPublic = false; + $this->isPrivate = true; + $this->isProtected = false; + break; + + case T_PROTECTED: + $this->isPublic = false; + $this->isPrivate = false; + $this->isProtected = true; + break; + + default: + throw new Exception("Invalid visibility argument passed to setVisibility."); + } + + return $this; + } + + /** + * @return int + */ + public function getNumberOfParameters() + { + return count($this->getParameters()); + } + + /** + * @param bool $returnScanner + * @return array + */ + public function getParameters($returnScanner = false) + { + $this->scan(); + + $return = array(); + + foreach ($this->infos as $info) { + if ($info['type'] != 'parameter') { + continue; + } + + if (!$returnScanner) { + $return[] = $info['name']; + } else { + $return[] = $this->getParameter($info['name']); + } + } + + return $return; + } + + /** + * @param int|string $parameterNameOrInfoIndex + * @return ParameterScanner + * @throws Exception\InvalidArgumentException + */ + public function getParameter($parameterNameOrInfoIndex) + { + $this->scan(); + + if (is_int($parameterNameOrInfoIndex)) { + $info = $this->infos[$parameterNameOrInfoIndex]; + if ($info['type'] != 'parameter') { + throw new Exception\InvalidArgumentException('Index of info offset is not about a parameter'); + } + } elseif (is_string($parameterNameOrInfoIndex)) { + foreach ($this->infos as $info) { + if ($info['type'] === 'parameter' && $info['name'] === $parameterNameOrInfoIndex) { + break; + } + unset($info); + } + if (!isset($info)) { + throw new Exception\InvalidArgumentException('Index of info offset is not about a parameter'); + } + } + + $p = new ParameterScanner( + array_slice($this->tokens, $info['tokenStart'], $info['tokenEnd'] - $info['tokenStart']), + $this->nameInformation + ); + $p->setDeclaringFunction($this->name); + $p->setDeclaringScannerFunction($this); + $p->setDeclaringClass($this->class); + $p->setDeclaringScannerClass($this->scannerClass); + $p->setPosition($info['position']); + + return $p; + } + + /** + * @return string + */ + public function getBody() + { + $this->scan(); + + return $this->body; + } + + public static function export() + { + // @todo + } + + public function __toString() + { + $this->scan(); + + return var_export($this, true); + } + + protected function scan() + { + if ($this->isScanned) { + return; + } + + if (!$this->tokens) { + throw new Exception\RuntimeException('No tokens were provided'); + } + + /** + * Variables & Setup + */ + + $tokens = &$this->tokens; // localize + $infos = &$this->infos; // localize + $tokenIndex = null; + $token = null; + $tokenType = null; + $tokenContent = null; + $tokenLine = null; + $infoIndex = 0; + $parentCount = 0; + + /* + * MACRO creation + */ + $MACRO_TOKEN_ADVANCE = function () use ( + &$tokens, + &$tokenIndex, + &$token, + &$tokenType, + &$tokenContent, + &$tokenLine + ) { + static $lastTokenArray = null; + $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1; + if (!isset($tokens[$tokenIndex])) { + $token = false; + $tokenContent = false; + $tokenType = false; + $tokenLine = false; + + return false; + } + $token = $tokens[$tokenIndex]; + if (is_string($token)) { + $tokenType = null; + $tokenContent = $token; + $tokenLine = $tokenLine + substr_count( + $lastTokenArray[1], + "\n" + ); // adjust token line by last known newline count + } else { + list($tokenType, $tokenContent, $tokenLine) = $token; + } + + return $tokenIndex; + }; + $MACRO_INFO_START = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) { + $infos[$infoIndex] = array( + 'type' => 'parameter', + 'tokenStart' => $tokenIndex, + 'tokenEnd' => null, + 'lineStart' => $tokenLine, + 'lineEnd' => $tokenLine, + 'name' => null, + 'position' => $infoIndex + 1, // position is +1 of infoIndex + ); + }; + $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) { + $infos[$infoIndex]['tokenEnd'] = $tokenIndex; + $infos[$infoIndex]['lineEnd'] = $tokenLine; + $infoIndex++; + + return $infoIndex; + }; + + /** + * START FINITE STATE MACHINE FOR SCANNING TOKENS + */ + + // Initialize token + $MACRO_TOKEN_ADVANCE(); + + SCANNER_TOP: + + $this->lineStart = ($this->lineStart) ? : $tokenLine; + + switch ($tokenType) { + case T_DOC_COMMENT: + $this->lineStart = null; + if ($this->docComment === null && $this->name === null) { + $this->docComment = $tokenContent; + } + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + + case T_FINAL: + $this->isFinal = true; + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + + case T_ABSTRACT: + $this->isAbstract = true; + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + + case T_PUBLIC: + // use defaults + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + + case T_PROTECTED: + $this->setVisibility(T_PROTECTED); + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + + case T_PRIVATE: + $this->setVisibility(T_PRIVATE); + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + + case T_STATIC: + $this->isStatic = true; + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + + case T_VARIABLE: + case T_STRING: + + if ($tokenType === T_STRING && $parentCount === 0) { + $this->name = $tokenContent; + } + + if ($parentCount === 1) { + if (!isset($infos[$infoIndex])) { + $MACRO_INFO_START(); + } + if ($tokenType === T_VARIABLE) { + $infos[$infoIndex]['name'] = ltrim($tokenContent, '$'); + } + } + + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + + case null: + + switch ($tokenContent) { + case '&': + if (!isset($infos[$infoIndex])) { + $MACRO_INFO_START(); + } + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + case '(': + $parentCount++; + goto SCANNER_CONTINUE_SIGNATURE; + //goto (no break needed); + case ')': + $parentCount--; + if ($parentCount > 0) { + goto SCANNER_CONTINUE_SIGNATURE; + } + if ($parentCount === 0) { + if ($infos) { + $MACRO_INFO_ADVANCE(); + } + $context = 'body'; + } + goto SCANNER_CONTINUE_BODY; + //goto (no break needed); + case ',': + if ($parentCount === 1) { + $MACRO_INFO_ADVANCE(); + } + goto SCANNER_CONTINUE_SIGNATURE; + } + } + + SCANNER_CONTINUE_SIGNATURE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_TOP; + + SCANNER_CONTINUE_BODY: + + $braceCount = 0; + while ($MACRO_TOKEN_ADVANCE() !== false) { + if ($tokenContent == '}') { + $braceCount--; + } + if ($braceCount > 0) { + $this->body .= $tokenContent; + } + if ($tokenContent == '{') { + $braceCount++; + } + $this->lineEnd = $tokenLine; + } + + SCANNER_END: + + $this->isScanned = true; + + return; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/ParameterScanner.php b/vendor/zendframework/zend-code/src/Scanner/ParameterScanner.php new file mode 100644 index 0000000..d613006 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/ParameterScanner.php @@ -0,0 +1,352 @@ +tokens = $parameterTokens; + $this->nameInformation = $nameInformation; + } + + /** + * Set declaring class + * + * @param string $class + * @return void + */ + public function setDeclaringClass($class) + { + $this->declaringClass = (string) $class; + } + + /** + * Set declaring scanner class + * + * @param ClassScanner $scannerClass + * @return void + */ + public function setDeclaringScannerClass(ClassScanner $scannerClass) + { + $this->declaringScannerClass = $scannerClass; + } + + /** + * Set declaring function + * + * @param string $function + * @return void + */ + public function setDeclaringFunction($function) + { + $this->declaringFunction = $function; + } + + /** + * Set declaring scanner function + * + * @param MethodScanner $scannerFunction + * @return void + */ + public function setDeclaringScannerFunction(MethodScanner $scannerFunction) + { + $this->declaringScannerFunction = $scannerFunction; + } + + /** + * Set position + * + * @param int $position + * @return void + */ + public function setPosition($position) + { + $this->position = $position; + } + + /** + * Scan + * + * @return void + */ + protected function scan() + { + if ($this->isScanned) { + return; + } + + $tokens = &$this->tokens; + + reset($tokens); + + SCANNER_TOP: + + $token = current($tokens); + + if (is_string($token)) { + // check pass by ref + if ($token === '&') { + $this->isPassedByReference = true; + goto SCANNER_CONTINUE; + } + if ($token === '=') { + $this->isOptional = true; + $this->isDefaultValueAvailable = true; + goto SCANNER_CONTINUE; + } + } else { + if ($this->name === null && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { + $this->class .= $token[1]; + goto SCANNER_CONTINUE; + } + if ($token[0] === T_VARIABLE) { + $this->name = ltrim($token[1], '$'); + goto SCANNER_CONTINUE; + } + } + + if ($this->name !== null) { + $this->defaultValue .= trim((is_string($token)) ? $token : $token[1]); + } + + SCANNER_CONTINUE: + + if (next($this->tokens) === false) { + goto SCANNER_END; + } + goto SCANNER_TOP; + + SCANNER_END: + + if ($this->class && $this->nameInformation) { + $this->class = $this->nameInformation->resolveName($this->class); + } + + $this->isScanned = true; + } + + /** + * Get declaring scanner class + * + * @return ClassScanner + */ + public function getDeclaringScannerClass() + { + return $this->declaringScannerClass; + } + + /** + * Get declaring class + * + * @return string + */ + public function getDeclaringClass() + { + return $this->declaringClass; + } + + /** + * Get declaring scanner function + * + * @return MethodScanner + */ + public function getDeclaringScannerFunction() + { + return $this->declaringScannerFunction; + } + + /** + * Get declaring function + * + * @return string + */ + public function getDeclaringFunction() + { + return $this->declaringFunction; + } + + /** + * Get default value + * + * @return string + */ + public function getDefaultValue() + { + $this->scan(); + + return $this->defaultValue; + } + + /** + * Get class + * + * @return string + */ + public function getClass() + { + $this->scan(); + + return $this->class; + } + + /** + * Get name + * + * @return string + */ + public function getName() + { + $this->scan(); + + return $this->name; + } + + /** + * Get position + * + * @return int + */ + public function getPosition() + { + $this->scan(); + + return $this->position; + } + + /** + * Check if is array + * + * @return bool + */ + public function isArray() + { + $this->scan(); + + return $this->isArray; + } + + /** + * Check if default value is available + * + * @return bool + */ + public function isDefaultValueAvailable() + { + $this->scan(); + + return $this->isDefaultValueAvailable; + } + + /** + * Check if is optional + * + * @return bool + */ + public function isOptional() + { + $this->scan(); + + return $this->isOptional; + } + + /** + * Check if is passed by reference + * + * @return bool + */ + public function isPassedByReference() + { + $this->scan(); + + return $this->isPassedByReference; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/PropertyScanner.php b/vendor/zendframework/zend-code/src/Scanner/PropertyScanner.php new file mode 100644 index 0000000..a5a0865 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/PropertyScanner.php @@ -0,0 +1,316 @@ +tokens = $propertyTokens; + $this->nameInformation = $nameInformation; + } + + /** + * @param string $class + */ + public function setClass($class) + { + $this->class = $class; + } + + /** + * @param ClassScanner $scannerClass + */ + public function setScannerClass(ClassScanner $scannerClass) + { + $this->scannerClass = $scannerClass; + } + + /** + * @return ClassScanner + */ + public function getClassScanner() + { + return $this->scannerClass; + } + + /** + * @return string + */ + public function getName() + { + $this->scan(); + return $this->name; + } + + /** + * @return string + */ + public function getValueType() + { + return $this->valueType; + } + + /** + * @return bool + */ + public function isPublic() + { + $this->scan(); + return $this->isPublic; + } + + /** + * @return bool + */ + public function isPrivate() + { + $this->scan(); + return $this->isPrivate; + } + + /** + * @return bool + */ + public function isProtected() + { + $this->scan(); + return $this->isProtected; + } + + /** + * @return bool + */ + public function isStatic() + { + $this->scan(); + return $this->isStatic; + } + + /** + * @return string + */ + public function getValue() + { + $this->scan(); + return $this->value; + } + + /** + * @return string + */ + public function getDocComment() + { + $this->scan(); + return $this->docComment; + } + + /** + * @param Annotation\AnnotationManager $annotationManager + * @return AnnotationScanner + */ + public function getAnnotations(Annotation\AnnotationManager $annotationManager) + { + if (($docComment = $this->getDocComment()) == '') { + return false; + } + + return new AnnotationScanner($annotationManager, $docComment, $this->nameInformation); + } + + /** + * @return string + */ + public function __toString() + { + $this->scan(); + return var_export($this, true); + } + + /** + * Scan tokens + * + * @throws \Zend\Code\Exception\RuntimeException + */ + protected function scan() + { + if ($this->isScanned) { + return; + } + + if (!$this->tokens) { + throw new Exception\RuntimeException('No tokens were provided'); + } + + /** + * Variables & Setup + */ + $value = ''; + $concatenateValue = false; + + $tokens = &$this->tokens; + reset($tokens); + + foreach ($tokens as $token) { + $tempValue = $token; + if (!is_string($token)) { + list($tokenType, $tokenContent, $tokenLine) = $token; + + switch ($tokenType) { + case T_DOC_COMMENT: + if ($this->docComment === null && $this->name === null) { + $this->docComment = $tokenContent; + } + break; + + case T_VARIABLE: + $this->name = ltrim($tokenContent, '$'); + break; + + case T_PUBLIC: + // use defaults + break; + + case T_PROTECTED: + $this->isProtected = true; + $this->isPublic = false; + break; + + case T_PRIVATE: + $this->isPrivate = true; + $this->isPublic = false; + break; + + case T_STATIC: + $this->isStatic = true; + break; + default: + $tempValue = trim($tokenContent); + break; + } + } + + //end value concatenation + if (!is_array($token) && trim($token) == ";") { + $concatenateValue = false; + } + + if (true === $concatenateValue) { + $value .= $tempValue; + } + + //start value concatenation + if (!is_array($token) && trim($token) == "=") { + $concatenateValue = true; + } + } + + $this->valueType = self::T_UNKNOWN; + if ($value == "false" || $value == "true") { + $this->valueType = self::T_BOOLEAN; + } elseif (is_numeric($value)) { + $this->valueType = self::T_INTEGER; + } elseif (0 === strpos($value, 'array') || 0 === strpos($value, "[")) { + $this->valueType = self::T_ARRAY; + } elseif (substr($value, 0, 1) === '"' || substr($value, 0, 1) === "'") { + $value = substr($value, 1, -1); // Remove quotes + $this->valueType = self::T_STRING; + } + + $this->value = empty($value) ? null : $value; + $this->isScanned = true; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/ScannerInterface.php b/vendor/zendframework/zend-code/src/Scanner/ScannerInterface.php new file mode 100644 index 0000000..f832eda --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/ScannerInterface.php @@ -0,0 +1,16 @@ +tokens = $tokens; + $this->annotationManager = $annotationManager; + } + + /** + * @return AnnotationManager + */ + public function getAnnotationManager() + { + return $this->annotationManager; + } + + /** + * Get doc comment + * + * @todo Assignment of $this->docComment should probably be done in scan() + * and then $this->getDocComment() just retrieves it. + * + * @return string + */ + public function getDocComment() + { + foreach ($this->tokens as $token) { + $type = $token[0]; + $value = $token[1]; + if (($type == T_OPEN_TAG) || ($type == T_WHITESPACE)) { + continue; + } elseif ($type == T_DOC_COMMENT) { + $this->docComment = $value; + + return $this->docComment; + } else { + // Only whitespace is allowed before file docblocks + return; + } + } + } + + /** + * @return array + */ + public function getNamespaces() + { + $this->scan(); + + $namespaces = array(); + foreach ($this->infos as $info) { + if ($info['type'] == 'namespace') { + $namespaces[] = $info['namespace']; + } + } + + return $namespaces; + } + + /** + * @param null|string $namespace + * @return array|null + */ + public function getUses($namespace = null) + { + $this->scan(); + + return $this->getUsesNoScan($namespace); + } + + /** + * @return array + */ + public function getIncludes() + { + $this->scan(); + // @todo Implement getIncludes() in TokenArrayScanner + } + + /** + * @return array + */ + public function getClassNames() + { + $this->scan(); + + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] != 'class') { + continue; + } + + $return[] = $info['name']; + } + + return $return; + } + + /** + * @return ClassScanner[] + */ + public function getClasses() + { + $this->scan(); + + $return = array(); + foreach ($this->infos as $info) { + if ($info['type'] != 'class') { + continue; + } + + $return[] = $this->getClass($info['name']); + } + + return $return; + } + + /** + * Return the class object from this scanner + * + * @param string|int $name + * @throws Exception\InvalidArgumentException + * @return ClassScanner + */ + public function getClass($name) + { + $this->scan(); + + if (is_int($name)) { + $info = $this->infos[$name]; + if ($info['type'] != 'class') { + throw new Exception\InvalidArgumentException('Index of info offset is not about a class'); + } + } elseif (is_string($name)) { + $classFound = false; + foreach ($this->infos as $info) { + if ($info['type'] === 'class' && $info['name'] === $name) { + $classFound = true; + break; + } + } + + if (!$classFound) { + return false; + } + } + + return new ClassScanner( + array_slice( + $this->tokens, + $info['tokenStart'], + ($info['tokenEnd'] - $info['tokenStart'] + 1) + ), // zero indexed array + new NameInformation($info['namespace'], $info['uses']) + ); + } + + /** + * @param string $className + * @return bool|null|NameInformation + */ + public function getClassNameInformation($className) + { + $this->scan(); + + $classFound = false; + foreach ($this->infos as $info) { + if ($info['type'] === 'class' && $info['name'] === $className) { + $classFound = true; + break; + } + } + + if (!$classFound) { + return false; + } + + if (!isset($info)) { + return; + } + + return new NameInformation($info['namespace'], $info['uses']); + } + + /** + * @return array + */ + public function getFunctionNames() + { + $this->scan(); + $functionNames = array(); + foreach ($this->infos as $info) { + if ($info['type'] == 'function') { + $functionNames[] = $info['name']; + } + } + + return $functionNames; + } + + /** + * @return array + */ + public function getFunctions() + { + $this->scan(); + + $functions = array(); + foreach ($this->infos as $info) { + if ($info['type'] == 'function') { + // @todo $functions[] = new FunctionScanner($info['name']); + } + } + + return $functions; + } + + /** + * Export + * + * @param $tokens + */ + public static function export($tokens) + { + // @todo + } + + public function __toString() + { + // @todo + } + + /** + * Scan + * + * @todo: $this->docComment should be assigned for valid docblock during + * the scan instead of $this->getDocComment() (starting with + * T_DOC_COMMENT case) + * + * @throws Exception\RuntimeException + */ + protected function scan() + { + if ($this->isScanned) { + return; + } + + if (!$this->tokens) { + throw new Exception\RuntimeException('No tokens were provided'); + } + + /** + * Define PHP 5.4 'trait' token constant. + */ + if (!defined('T_TRAIT')) { + define('T_TRAIT', 42001); + } + + /** + * Variables & Setup + */ + + $tokens = &$this->tokens; // localize + $infos = &$this->infos; // localize + $tokenIndex = null; + $token = null; + $tokenType = null; + $tokenContent = null; + $tokenLine = null; + $namespace = null; + $docCommentIndex = false; + $infoIndex = 0; + + /* + * MACRO creation + */ + $MACRO_TOKEN_ADVANCE = function () use (&$tokens, &$tokenIndex, &$token, &$tokenType, &$tokenContent, &$tokenLine) { + $tokenIndex = ($tokenIndex === null) ? 0 : $tokenIndex + 1; + if (!isset($tokens[$tokenIndex])) { + $token = false; + $tokenContent = false; + $tokenType = false; + $tokenLine = false; + + return false; + } + if (is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"') { + do { + $tokenIndex++; + } while (!(is_string($tokens[$tokenIndex]) && $tokens[$tokenIndex] === '"')); + } + $token = $tokens[$tokenIndex]; + if (is_array($token)) { + list($tokenType, $tokenContent, $tokenLine) = $token; + } else { + $tokenType = null; + $tokenContent = $token; + } + + return $tokenIndex; + }; + $MACRO_TOKEN_LOGICAL_START_INDEX = function () use (&$tokenIndex, &$docCommentIndex) { + return ($docCommentIndex === false) ? $tokenIndex : $docCommentIndex; + }; + $MACRO_DOC_COMMENT_START = function () use (&$tokenIndex, &$docCommentIndex) { + $docCommentIndex = $tokenIndex; + + return $docCommentIndex; + }; + $MACRO_DOC_COMMENT_VALIDATE = function () use (&$tokenType, &$docCommentIndex) { + static $validTrailingTokens = null; + if ($validTrailingTokens === null) { + $validTrailingTokens = array(T_WHITESPACE, T_FINAL, T_ABSTRACT, T_INTERFACE, T_CLASS, T_FUNCTION); + } + if ($docCommentIndex !== false && !in_array($tokenType, $validTrailingTokens)) { + $docCommentIndex = false; + } + + return $docCommentIndex; + }; + $MACRO_INFO_ADVANCE = function () use (&$infoIndex, &$infos, &$tokenIndex, &$tokenLine) { + $infos[$infoIndex]['tokenEnd'] = $tokenIndex; + $infos[$infoIndex]['lineEnd'] = $tokenLine; + $infoIndex++; + + return $infoIndex; + }; + + /** + * START FINITE STATE MACHINE FOR SCANNING TOKENS + */ + + // Initialize token + $MACRO_TOKEN_ADVANCE(); + + SCANNER_TOP: + + if ($token === false) { + goto SCANNER_END; + } + + // Validate current doc comment index + $MACRO_DOC_COMMENT_VALIDATE(); + + switch ($tokenType) { + + case T_DOC_COMMENT: + + $MACRO_DOC_COMMENT_START(); + goto SCANNER_CONTINUE; + //goto no break needed + + case T_NAMESPACE: + + $infos[$infoIndex] = array( + 'type' => 'namespace', + 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), + 'tokenEnd' => null, + 'lineStart' => $token[2], + 'lineEnd' => null, + 'namespace' => null, + ); + + // start processing with next token + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + + SCANNER_NAMESPACE_TOP: + + if ($tokenType === null && $tokenContent === ';' || $tokenContent === '{') { + goto SCANNER_NAMESPACE_END; + } + + if ($tokenType === T_WHITESPACE) { + goto SCANNER_NAMESPACE_CONTINUE; + } + + if ($tokenType === T_NS_SEPARATOR || $tokenType === T_STRING) { + $infos[$infoIndex]['namespace'] .= $tokenContent; + } + + SCANNER_NAMESPACE_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_NAMESPACE_TOP; + + SCANNER_NAMESPACE_END: + + $namespace = $infos[$infoIndex]['namespace']; + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CONTINUE; + //goto no break needed + + case T_USE: + + $infos[$infoIndex] = array( + 'type' => 'use', + 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), + 'tokenEnd' => null, + 'lineStart' => $tokens[$tokenIndex][2], + 'lineEnd' => null, + 'namespace' => $namespace, + 'statements' => array(0 => array('use' => null, + 'as' => null)), + ); + + $useStatementIndex = 0; + $useAsContext = false; + + // start processing with next token + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + + SCANNER_USE_TOP: + + if ($tokenType === null) { + if ($tokenContent === ';') { + goto SCANNER_USE_END; + } elseif ($tokenContent === ',') { + $useAsContext = false; + $useStatementIndex++; + $infos[$infoIndex]['statements'][$useStatementIndex] = array('use' => null, + 'as' => null); + } + } + + // ANALYZE + if ($tokenType !== null) { + if ($tokenType == T_AS) { + $useAsContext = true; + goto SCANNER_USE_CONTINUE; + } + + if ($tokenType == T_NS_SEPARATOR || $tokenType == T_STRING) { + if ($useAsContext == false) { + $infos[$infoIndex]['statements'][$useStatementIndex]['use'] .= $tokenContent; + } else { + $infos[$infoIndex]['statements'][$useStatementIndex]['as'] = $tokenContent; + } + } + } + + SCANNER_USE_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_USE_TOP; + + SCANNER_USE_END: + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CONTINUE; + //goto no break needed + + case T_INCLUDE: + case T_INCLUDE_ONCE: + case T_REQUIRE: + case T_REQUIRE_ONCE: + + // Static for performance + static $includeTypes = array( + T_INCLUDE => 'include', + T_INCLUDE_ONCE => 'include_once', + T_REQUIRE => 'require', + T_REQUIRE_ONCE => 'require_once' + ); + + $infos[$infoIndex] = array( + 'type' => 'include', + 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), + 'tokenEnd' => null, + 'lineStart' => $tokens[$tokenIndex][2], + 'lineEnd' => null, + 'includeType' => $includeTypes[$tokens[$tokenIndex][0]], + 'path' => '', + ); + + // start processing with next token + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + + SCANNER_INCLUDE_TOP: + + if ($tokenType === null && $tokenContent === ';') { + goto SCANNER_INCLUDE_END; + } + + $infos[$infoIndex]['path'] .= $tokenContent; + + SCANNER_INCLUDE_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_INCLUDE_TOP; + + SCANNER_INCLUDE_END: + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CONTINUE; + //goto no break needed + + case T_FUNCTION: + case T_FINAL: + case T_ABSTRACT: + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + + $infos[$infoIndex] = array( + 'type' => ($tokenType === T_FUNCTION) ? 'function' : 'class', + 'tokenStart' => $MACRO_TOKEN_LOGICAL_START_INDEX(), + 'tokenEnd' => null, + 'lineStart' => $tokens[$tokenIndex][2], + 'lineEnd' => null, + 'namespace' => $namespace, + 'uses' => $this->getUsesNoScan($namespace), + 'name' => null, + 'shortName' => null, + ); + + $classBraceCount = 0; + + // start processing with current token + + SCANNER_CLASS_TOP: + + // process the name + if ($infos[$infoIndex]['shortName'] == '' + && (($tokenType === T_CLASS || $tokenType === T_INTERFACE || $tokenType === T_TRAIT) && $infos[$infoIndex]['type'] === 'class' + || ($tokenType === T_FUNCTION && $infos[$infoIndex]['type'] === 'function')) + ) { + $infos[$infoIndex]['shortName'] = $tokens[$tokenIndex + 2][1]; + $infos[$infoIndex]['name'] = (($namespace !== null) ? $namespace . '\\' : '') . $infos[$infoIndex]['shortName']; + } + + if ($tokenType === null) { + if ($tokenContent == '{') { + $classBraceCount++; + } + if ($tokenContent == '}') { + $classBraceCount--; + if ($classBraceCount === 0) { + goto SCANNER_CLASS_END; + } + } + } + + SCANNER_CLASS_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_CLASS_TOP; + + SCANNER_CLASS_END: + + $MACRO_INFO_ADVANCE(); + goto SCANNER_CONTINUE; + + } + + SCANNER_CONTINUE: + + if ($MACRO_TOKEN_ADVANCE() === false) { + goto SCANNER_END; + } + goto SCANNER_TOP; + + SCANNER_END: + + /** + * END FINITE STATE MACHINE FOR SCANNING TOKENS + */ + + $this->isScanned = true; + } + + /** + * Check for namespace + * + * @param string $namespace + * @return bool + */ + public function hasNamespace($namespace) + { + $this->scan(); + + foreach ($this->infos as $info) { + if ($info['type'] == 'namespace' && $info['namespace'] == $namespace) { + return true; + } + } + return false; + } + + /** + * @param string $namespace + * @return null|array + * @throws Exception\InvalidArgumentException + */ + protected function getUsesNoScan($namespace) + { + $namespaces = array(); + foreach ($this->infos as $info) { + if ($info['type'] == 'namespace') { + $namespaces[] = $info['namespace']; + } + } + + if ($namespace === null) { + $namespace = array_shift($namespaces); + } elseif (!is_string($namespace)) { + throw new Exception\InvalidArgumentException('Invalid namespace provided'); + } elseif (!in_array($namespace, $namespaces)) { + return; + } + + $uses = array(); + foreach ($this->infos as $info) { + if ($info['type'] !== 'use') { + continue; + } + foreach ($info['statements'] as $statement) { + if ($info['namespace'] == $namespace) { + $uses[] = $statement; + } + } + } + + return $uses; + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/Util.php b/vendor/zendframework/zend-code/src/Scanner/Util.php new file mode 100644 index 0000000..dbd5bf7 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/Util.php @@ -0,0 +1,74 @@ +namespace && !$data->uses && strlen($value) > 0 && $value{0} != '\\') { + $value = $data->namespace . '\\' . $value; + + return; + } + + if (!$data->uses || strlen($value) <= 0 || $value{0} == '\\') { + $value = ltrim($value, '\\'); + + return; + } + + if ($data->namespace || $data->uses) { + $firstPart = $value; + if (($firstPartEnd = strpos($firstPart, '\\')) !== false) { + $firstPart = substr($firstPart, 0, $firstPartEnd); + } else { + $firstPartEnd = strlen($firstPart); + } + + if (array_key_exists($firstPart, $data->uses)) { + $value = substr_replace($value, $data->uses[$firstPart], 0, $firstPartEnd); + + return; + } + + if ($data->namespace) { + $value = $data->namespace . '\\' . $value; + + return; + } + } + } +} diff --git a/vendor/zendframework/zend-code/src/Scanner/ValueScanner.php b/vendor/zendframework/zend-code/src/Scanner/ValueScanner.php new file mode 100644 index 0000000..0a87cc0 --- /dev/null +++ b/vendor/zendframework/zend-code/src/Scanner/ValueScanner.php @@ -0,0 +1,15 @@ +=5.3.23", + "zendframework/zend-stdlib": "~2.5" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload-dev": { + "psr-4": { + "ZendTest\\EventManager\\": "test/" + } + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/PHPUnit": "~4.0" + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/AbstractListenerAggregate.php b/vendor/zendframework/zend-eventmanager/src/AbstractListenerAggregate.php new file mode 100644 index 0000000..a8b887d --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/AbstractListenerAggregate.php @@ -0,0 +1,33 @@ +listeners as $index => $callback) { + if ($events->detach($callback)) { + unset($this->listeners[$index]); + } + } + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/Event.php b/vendor/zendframework/zend-eventmanager/src/Event.php new file mode 100644 index 0000000..bc4ed44 --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/Event.php @@ -0,0 +1,209 @@ +setName($name); + } + + if (null !== $target) { + $this->setTarget($target); + } + + if (null !== $params) { + $this->setParams($params); + } + } + + /** + * Get event name + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Get the event target + * + * This may be either an object, or the name of a static method. + * + * @return string|object + */ + public function getTarget() + { + return $this->target; + } + + /** + * Set parameters + * + * Overwrites parameters + * + * @param array|ArrayAccess|object $params + * @return Event + * @throws Exception\InvalidArgumentException + */ + public function setParams($params) + { + if (!is_array($params) && !is_object($params)) { + throw new Exception\InvalidArgumentException( + sprintf('Event parameters must be an array or object; received "%s"', gettype($params)) + ); + } + + $this->params = $params; + return $this; + } + + /** + * Get all parameters + * + * @return array|object|ArrayAccess + */ + public function getParams() + { + return $this->params; + } + + /** + * Get an individual parameter + * + * If the parameter does not exist, the $default value will be returned. + * + * @param string|int $name + * @param mixed $default + * @return mixed + */ + public function getParam($name, $default = null) + { + // Check in params that are arrays or implement array access + if (is_array($this->params) || $this->params instanceof ArrayAccess) { + if (!isset($this->params[$name])) { + return $default; + } + + return $this->params[$name]; + } + + // Check in normal objects + if (!isset($this->params->{$name})) { + return $default; + } + return $this->params->{$name}; + } + + /** + * Set the event name + * + * @param string $name + * @return Event + */ + public function setName($name) + { + $this->name = (string) $name; + return $this; + } + + /** + * Set the event target/context + * + * @param null|string|object $target + * @return Event + */ + public function setTarget($target) + { + $this->target = $target; + return $this; + } + + /** + * Set an individual parameter to a value + * + * @param string|int $name + * @param mixed $value + * @return Event + */ + public function setParam($name, $value) + { + if (is_array($this->params) || $this->params instanceof ArrayAccess) { + // Arrays or objects implementing array access + $this->params[$name] = $value; + } else { + // Objects + $this->params->{$name} = $value; + } + return $this; + } + + /** + * Stop further event propagation + * + * @param bool $flag + * @return void + */ + public function stopPropagation($flag = true) + { + $this->stopPropagation = (bool) $flag; + } + + /** + * Is propagation stopped? + * + * @return bool + */ + public function propagationIsStopped() + { + return $this->stopPropagation; + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/EventInterface.php b/vendor/zendframework/zend-eventmanager/src/EventInterface.php new file mode 100644 index 0000000..3a6274e --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/EventInterface.php @@ -0,0 +1,96 @@ +setIdentifiers($identifiers); + } + + /** + * Set the event class to utilize + * + * @param string $class + * @return EventManager + */ + public function setEventClass($class) + { + $this->eventClass = $class; + return $this; + } + + /** + * Set shared event manager + * + * @param SharedEventManagerInterface $sharedEventManager + * @return EventManager + */ + public function setSharedManager(SharedEventManagerInterface $sharedEventManager) + { + $this->sharedManager = $sharedEventManager; + StaticEventManager::setInstance($sharedEventManager); + return $this; + } + + /** + * Remove any shared event manager currently attached + * + * @return void + */ + public function unsetSharedManager() + { + $this->sharedManager = false; + } + + /** + * Get shared event manager + * + * If one is not defined, but we have a static instance in + * StaticEventManager, that one will be used and set in this instance. + * + * If none is available in the StaticEventManager, a boolean false is + * returned. + * + * @return false|SharedEventManagerInterface + */ + public function getSharedManager() + { + // "false" means "I do not want a shared manager; don't try and fetch one" + if (false === $this->sharedManager + || $this->sharedManager instanceof SharedEventManagerInterface + ) { + return $this->sharedManager; + } + + if (!StaticEventManager::hasInstance()) { + return false; + } + + $this->sharedManager = StaticEventManager::getInstance(); + return $this->sharedManager; + } + + /** + * Get the identifier(s) for this EventManager + * + * @return array + */ + public function getIdentifiers() + { + return $this->identifiers; + } + + /** + * Set the identifiers (overrides any currently set identifiers) + * + * @param string|int|array|Traversable $identifiers + * @return EventManager Provides a fluent interface + */ + public function setIdentifiers($identifiers) + { + if (is_array($identifiers) || $identifiers instanceof Traversable) { + $this->identifiers = array_unique((array) $identifiers); + } elseif ($identifiers !== null) { + $this->identifiers = array($identifiers); + } + return $this; + } + + /** + * Add some identifier(s) (appends to any currently set identifiers) + * + * @param string|int|array|Traversable $identifiers + * @return EventManager Provides a fluent interface + */ + public function addIdentifiers($identifiers) + { + if (is_array($identifiers) || $identifiers instanceof Traversable) { + $this->identifiers = array_unique(array_merge($this->identifiers, (array) $identifiers)); + } elseif ($identifiers !== null) { + $this->identifiers = array_unique(array_merge($this->identifiers, array($identifiers))); + } + return $this; + } + + /** + * Trigger all listeners for a given event + * + * @param string|EventInterface $event + * @param string|object $target Object calling emit, or symbol describing target (such as static method name) + * @param array|ArrayAccess $argv Array of arguments; typically, should be associative + * @param null|callable $callback Trigger listeners until return value of this callback evaluate to true + * @return ResponseCollection All listener return values + * @throws Exception\InvalidCallbackException + */ + public function trigger($event, $target = null, $argv = array(), $callback = null) + { + if ($event instanceof EventInterface) { + $e = $event; + $event = $e->getName(); + $callback = $target; + } elseif ($target instanceof EventInterface) { + $e = $target; + $e->setName($event); + $callback = $argv; + } elseif ($argv instanceof EventInterface) { + $e = $argv; + $e->setName($event); + $e->setTarget($target); + } else { + $e = new $this->eventClass(); + $e->setName($event); + $e->setTarget($target); + $e->setParams($argv); + } + + if ($callback && !is_callable($callback)) { + throw new Exception\InvalidCallbackException('Invalid callback provided'); + } + + // Initial value of stop propagation flag should be false + $e->stopPropagation(false); + + return $this->triggerListeners($event, $e, $callback); + } + + /** + * Trigger listeners until return value of one causes a callback to + * evaluate to true + * + * Triggers listeners until the provided callback evaluates the return + * value of one as true, or until all listeners have been executed. + * + * @param string|EventInterface $event + * @param string|object $target Object calling emit, or symbol describing target (such as static method name) + * @param array|ArrayAccess $argv Array of arguments; typically, should be associative + * @param callable $callback + * @return ResponseCollection + * @deprecated Please use trigger() + * @throws Exception\InvalidCallbackException if invalid callable provided + */ + public function triggerUntil($event, $target, $argv = null, $callback = null) + { + trigger_error( + 'This method is deprecated and will be removed in the future. Please use trigger() instead.', + E_USER_DEPRECATED + ); + return $this->trigger($event, $target, $argv, $callback); + } + + /** + * Attach a listener to an event + * + * The first argument is the event, and the next argument describes a + * callback that will respond to that event. A CallbackHandler instance + * describing the event listener combination will be returned. + * + * The last argument indicates a priority at which the event should be + * executed. By default, this value is 1; however, you may set it for any + * integer value. Higher values have higher priority (i.e., execute first). + * + * You can specify "*" for the event name. In such cases, the listener will + * be triggered for every event. + * + * @param string|array|ListenerAggregateInterface $event An event or array of event names. If a ListenerAggregateInterface, proxies to {@link attachAggregate()}. + * @param callable|int $callback If string $event provided, expects PHP callback; for a ListenerAggregateInterface $event, this will be the priority + * @param int $priority If provided, the priority at which to register the callable + * @return CallbackHandler|mixed CallbackHandler if attaching callable (to allow later unsubscribe); mixed if attaching aggregate + * @throws Exception\InvalidArgumentException + */ + public function attach($event, $callback = null, $priority = 1) + { + // Proxy ListenerAggregateInterface arguments to attachAggregate() + if ($event instanceof ListenerAggregateInterface) { + return $this->attachAggregate($event, $callback); + } + + // Null callback is invalid + if (null === $callback) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: expects a callback; none provided', + __METHOD__ + )); + } + + // Array of events should be registered individually, and return an array of all listeners + if (is_array($event)) { + $listeners = array(); + foreach ($event as $name) { + $listeners[] = $this->attach($name, $callback, $priority); + } + return $listeners; + } + + // If we don't have a priority queue for the event yet, create one + if (empty($this->events[$event])) { + $this->events[$event] = new PriorityQueue(); + } + + // Create a callback handler, setting the event and priority in its metadata + $listener = new CallbackHandler($callback, array('event' => $event, 'priority' => $priority)); + + // Inject the callback handler into the queue + $this->events[$event]->insert($listener, $priority); + return $listener; + } + + /** + * Attach a listener aggregate + * + * Listener aggregates accept an EventManagerInterface instance, and call attach() + * one or more times, typically to attach to multiple events using local + * methods. + * + * @param ListenerAggregateInterface $aggregate + * @param int $priority If provided, a suggested priority for the aggregate to use + * @return mixed return value of {@link ListenerAggregateInterface::attach()} + */ + public function attachAggregate(ListenerAggregateInterface $aggregate, $priority = 1) + { + return $aggregate->attach($this, $priority); + } + + /** + * Unsubscribe a listener from an event + * + * @param CallbackHandler|ListenerAggregateInterface $listener + * @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found + * @throws Exception\InvalidArgumentException if invalid listener provided + */ + public function detach($listener) + { + if ($listener instanceof ListenerAggregateInterface) { + return $this->detachAggregate($listener); + } + + if (!$listener instanceof CallbackHandler) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: expected a ListenerAggregateInterface or CallbackHandler; received "%s"', + __METHOD__, + (is_object($listener) ? get_class($listener) : gettype($listener)) + )); + } + + $event = $listener->getMetadatum('event'); + if (!$event || empty($this->events[$event])) { + return false; + } + $return = $this->events[$event]->remove($listener); + if (!$return) { + return false; + } + if (!count($this->events[$event])) { + unset($this->events[$event]); + } + return true; + } + + /** + * Detach a listener aggregate + * + * Listener aggregates accept an EventManagerInterface instance, and call detach() + * of all previously attached listeners. + * + * @param ListenerAggregateInterface $aggregate + * @return mixed return value of {@link ListenerAggregateInterface::detach()} + */ + public function detachAggregate(ListenerAggregateInterface $aggregate) + { + return $aggregate->detach($this); + } + + /** + * Retrieve all registered events + * + * @return array + */ + public function getEvents() + { + return array_keys($this->events); + } + + /** + * Retrieve all listeners for a given event + * + * @param string $event + * @return PriorityQueue + */ + public function getListeners($event) + { + if (!array_key_exists($event, $this->events)) { + return new PriorityQueue(); + } + return $this->events[$event]; + } + + /** + * Clear all listeners for a given event + * + * @param string $event + * @return void + */ + public function clearListeners($event) + { + if (!empty($this->events[$event])) { + unset($this->events[$event]); + } + } + + /** + * Prepare arguments + * + * Use this method if you want to be able to modify arguments from within a + * listener. It returns an ArrayObject of the arguments, which may then be + * passed to trigger(). + * + * @param array $args + * @return ArrayObject + */ + public function prepareArgs(array $args) + { + return new ArrayObject($args); + } + + /** + * Trigger listeners + * + * Actual functionality for triggering listeners, to which trigger() delegate. + * + * @param string $event Event name + * @param EventInterface $e + * @param null|callable $callback + * @return ResponseCollection + */ + protected function triggerListeners($event, EventInterface $e, $callback = null) + { + $responses = new ResponseCollection; + $listeners = $this->getListeners($event); + + // Add shared/wildcard listeners to the list of listeners, + // but don't modify the listeners object + $sharedListeners = $this->getSharedListeners($event); + $sharedWildcardListeners = $this->getSharedListeners('*'); + $wildcardListeners = $this->getListeners('*'); + if (count($sharedListeners) || count($sharedWildcardListeners) || count($wildcardListeners)) { + $listeners = clone $listeners; + + // Shared listeners on this specific event + $this->insertListeners($listeners, $sharedListeners); + + // Shared wildcard listeners + $this->insertListeners($listeners, $sharedWildcardListeners); + + // Add wildcard listeners + $this->insertListeners($listeners, $wildcardListeners); + } + + foreach ($listeners as $listener) { + $listenerCallback = $listener->getCallback(); + + // Trigger the listener's callback, and push its result onto the + // response collection + $responses->push(call_user_func($listenerCallback, $e)); + + // If the event was asked to stop propagating, do so + if ($e->propagationIsStopped()) { + $responses->setStopped(true); + break; + } + + // If the result causes our validation callback to return true, + // stop propagation + if ($callback && call_user_func($callback, $responses->last())) { + $responses->setStopped(true); + break; + } + } + + return $responses; + } + + /** + * Get list of all listeners attached to the shared event manager for + * identifiers registered by this instance + * + * @param string $event + * @return array + */ + protected function getSharedListeners($event) + { + if (!$sharedManager = $this->getSharedManager()) { + return array(); + } + + $identifiers = $this->getIdentifiers(); + //Add wildcard id to the search, if not already added + if (!in_array('*', $identifiers)) { + $identifiers[] = '*'; + } + $sharedListeners = array(); + + foreach ($identifiers as $id) { + if (!$listeners = $sharedManager->getListeners($id, $event)) { + continue; + } + + if (!is_array($listeners) && !($listeners instanceof Traversable)) { + continue; + } + + foreach ($listeners as $listener) { + if (!$listener instanceof CallbackHandler) { + continue; + } + $sharedListeners[] = $listener; + } + } + + return $sharedListeners; + } + + /** + * Add listeners to the master queue of listeners + * + * Used to inject shared listeners and wildcard listeners. + * + * @param PriorityQueue $masterListeners + * @param array|Traversable $listeners + * @return void + */ + protected function insertListeners($masterListeners, $listeners) + { + foreach ($listeners as $listener) { + $priority = $listener->getMetadatum('priority'); + if (null === $priority) { + $priority = 1; + } elseif (is_array($priority)) { + // If we have an array, likely using PriorityQueue. Grab first + // element of the array, as that's the actual priority. + $priority = array_shift($priority); + } + $masterListeners->insert($listener, $priority); + } + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/EventManagerAwareInterface.php b/vendor/zendframework/zend-eventmanager/src/EventManagerAwareInterface.php new file mode 100644 index 0000000..77ea533 --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/EventManagerAwareInterface.php @@ -0,0 +1,24 @@ +eventIdentifier property. + * + * @param EventManagerInterface $events + * @return mixed + */ + public function setEventManager(EventManagerInterface $events) + { + $identifiers = array(__CLASS__, get_class($this)); + if (isset($this->eventIdentifier)) { + if ((is_string($this->eventIdentifier)) + || (is_array($this->eventIdentifier)) + || ($this->eventIdentifier instanceof Traversable) + ) { + $identifiers = array_unique(array_merge($identifiers, (array) $this->eventIdentifier)); + } elseif (is_object($this->eventIdentifier)) { + $identifiers[] = $this->eventIdentifier; + } + // silently ignore invalid eventIdentifier types + } + $events->setIdentifiers($identifiers); + $this->events = $events; + if (method_exists($this, 'attachDefaultListeners')) { + $this->attachDefaultListeners(); + } + return $this; + } + + /** + * Retrieve the event manager + * + * Lazy-loads an EventManager instance if none registered. + * + * @return EventManagerInterface + */ + public function getEventManager() + { + if (!$this->events instanceof EventManagerInterface) { + $this->setEventManager(new EventManager()); + } + return $this->events; + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/EventManagerInterface.php b/vendor/zendframework/zend-eventmanager/src/EventManagerInterface.php new file mode 100644 index 0000000..c33bfb6 --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/EventManagerInterface.php @@ -0,0 +1,144 @@ +setExtractFlags(self::EXTR_BOTH); + + // Iterate and remove any matches + $removed = false; + $items = array(); + $this->rewind(); + while (!$this->isEmpty()) { + $item = $this->extract(); + if ($item['data'] === $datum) { + $removed = true; + continue; + } + $items[] = $item; + } + + // Repopulate + foreach ($items as $item) { + $this->insert($item['data'], $item['priority']); + } + + $this->setExtractFlags(self::EXTR_DATA); + return $removed; + } + + /** + * Iterate the next filter in the chain + * + * Iterates and calls the next filter in the chain. + * + * @param mixed $context + * @param array $params + * @param FilterIterator $chain + * @return mixed + */ + public function next($context = null, array $params = array(), $chain = null) + { + if (empty($context) || $chain->isEmpty()) { + return; + } + + $next = $this->extract(); + if (!$next instanceof CallbackHandler) { + return; + } + + $return = call_user_func($next->getCallback(), $context, $params, $chain); + return $return; + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/FilterChain.php b/vendor/zendframework/zend-eventmanager/src/FilterChain.php new file mode 100644 index 0000000..f657a35 --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/FilterChain.php @@ -0,0 +1,120 @@ +filters = new Filter\FilterIterator(); + } + + /** + * Apply the filters + * + * Begins iteration of the filters. + * + * @param mixed $context Object under observation + * @param mixed $argv Associative array of arguments + * @return mixed + */ + public function run($context, array $argv = array()) + { + $chain = clone $this->getFilters(); + + if ($chain->isEmpty()) { + return; + } + + $next = $chain->extract(); + if (!$next instanceof CallbackHandler) { + return; + } + + return call_user_func($next->getCallback(), $context, $argv, $chain); + } + + /** + * Connect a filter to the chain + * + * @param callable $callback PHP Callback + * @param int $priority Priority in the queue at which to execute; defaults to 1 (higher numbers == higher priority) + * @return CallbackHandler (to allow later unsubscribe) + * @throws Exception\InvalidCallbackException + */ + public function attach($callback, $priority = 1) + { + if (empty($callback)) { + throw new Exception\InvalidCallbackException('No callback provided'); + } + $filter = new CallbackHandler($callback, array('priority' => $priority)); + $this->filters->insert($filter, $priority); + return $filter; + } + + /** + * Detach a filter from the chain + * + * @param CallbackHandler $filter + * @return bool Returns true if filter found and unsubscribed; returns false otherwise + */ + public function detach(CallbackHandler $filter) + { + return $this->filters->remove($filter); + } + + /** + * Retrieve all filters + * + * @return Filter\FilterIterator + */ + public function getFilters() + { + return $this->filters; + } + + /** + * Clear all filters + * + * @return void + */ + public function clearFilters() + { + $this->filters = new Filter\FilterIterator(); + } + + /** + * Return current responses + * + * Only available while the chain is still being iterated. Returns the + * current ResponseCollection. + * + * @return null|ResponseCollection + */ + public function getResponses() + { + return; + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/GlobalEventManager.php b/vendor/zendframework/zend-eventmanager/src/GlobalEventManager.php new file mode 100644 index 0000000..336b69c --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/GlobalEventManager.php @@ -0,0 +1,141 @@ +trigger($event, $context, $argv, $callback); + } + + /** + * Trigger listeners until return value of one causes a callback to evaluate + * to true. + * + * @param string $event + * @param string|object $context + * @param array|object $argv + * @param callable $callback + * @return ResponseCollection + * @deprecated Please use trigger() + */ + public static function triggerUntil($event, $context, $argv, $callback) + { + trigger_error( + 'This method is deprecated and will be removed in the future. Please use trigger() instead.', + E_USER_DEPRECATED + ); + return static::trigger($event, $context, $argv, $callback); + } + + /** + * Attach a listener to an event + * + * @param string $event + * @param callable $callback + * @param int $priority + * @return CallbackHandler + */ + public static function attach($event, $callback, $priority = 1) + { + return static::getEventCollection()->attach($event, $callback, $priority); + } + + /** + * Detach a callback from a listener + * + * @param CallbackHandler $listener + * @return bool + */ + public static function detach(CallbackHandler $listener) + { + return static::getEventCollection()->detach($listener); + } + + /** + * Retrieve list of events this object manages + * + * @return array + */ + public static function getEvents() + { + return static::getEventCollection()->getEvents(); + } + + /** + * Retrieve all listeners for a given event + * + * @param string $event + * @return PriorityQueue|array + */ + public static function getListeners($event) + { + return static::getEventCollection()->getListeners($event); + } + + /** + * Clear all listeners for a given event + * + * @param string $event + * @return void + */ + public static function clearListeners($event) + { + static::getEventCollection()->clearListeners($event); + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/ListenerAggregateInterface.php b/vendor/zendframework/zend-eventmanager/src/ListenerAggregateInterface.php new file mode 100644 index 0000000..df76708 --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/ListenerAggregateInterface.php @@ -0,0 +1,42 @@ +listeners as $index => $callback) { + if ($events->detach($callback)) { + unset($this->listeners[$index]); + } + } + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/ProvidesEvents.php b/vendor/zendframework/zend-eventmanager/src/ProvidesEvents.php new file mode 100644 index 0000000..edb19c1 --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/ProvidesEvents.php @@ -0,0 +1,23 @@ +stopped; + } + + /** + * Mark the collection as stopped (or its opposite) + * + * @param bool $flag + * @return ResponseCollection + */ + public function setStopped($flag) + { + $this->stopped = (bool) $flag; + return $this; + } + + /** + * Convenient access to the first handler return value. + * + * @return mixed The first handler return value + */ + public function first() + { + return parent::bottom(); + } + + /** + * Convenient access to the last handler return value. + * + * If the collection is empty, returns null. Otherwise, returns value + * returned by last handler. + * + * @return mixed The last handler return value + */ + public function last() + { + if (count($this) === 0) { + return; + } + return parent::top(); + } + + /** + * Check if any of the responses match the given value. + * + * @param mixed $value The value to look for among responses + * @return bool + */ + public function contains($value) + { + foreach ($this as $response) { + if ($response === $value) { + return true; + } + } + return false; + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/SharedEventAggregateAwareInterface.php b/vendor/zendframework/zend-eventmanager/src/SharedEventAggregateAwareInterface.php new file mode 100644 index 0000000..73b849c --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/SharedEventAggregateAwareInterface.php @@ -0,0 +1,33 @@ + + * $sharedEventManager = new SharedEventManager(); + * $sharedEventManager->attach( + * array('My\Resource\AbstractResource', 'My\Resource\EntityResource'), + * 'getAll', + * function ($e) use ($cache) { + * if (!$id = $e->getParam('id', false)) { + * return; + * } + * if (!$data = $cache->load(get_class($resource) . '::getOne::' . $id )) { + * return; + * } + * return $data; + * } + * ); + * + * + * @param string|array $id Identifier(s) for event emitting component(s) + * @param string $event + * @param callable $callback PHP Callback + * @param int $priority Priority at which listener should execute + * @return CallbackHandler|array Either CallbackHandler or array of CallbackHandlers + */ + public function attach($id, $event, $callback, $priority = 1) + { + $ids = (array) $id; + $listeners = array(); + foreach ($ids as $id) { + if (!array_key_exists($id, $this->identifiers)) { + $this->identifiers[$id] = new EventManager($id); + } + $listeners[] = $this->identifiers[$id]->attach($event, $callback, $priority); + } + if (count($listeners) > 1) { + return $listeners; + } + return $listeners[0]; + } + + /** + * Attach a listener aggregate + * + * Listener aggregates accept an EventManagerInterface instance, and call attachShared() + * one or more times, typically to attach to multiple events using local + * methods. + * + * @param SharedListenerAggregateInterface $aggregate + * @param int $priority If provided, a suggested priority for the aggregate to use + * @return mixed return value of {@link ListenerAggregateInterface::attachShared()} + */ + public function attachAggregate(SharedListenerAggregateInterface $aggregate, $priority = 1) + { + return $aggregate->attachShared($this, $priority); + } + + /** + * Detach a listener from an event offered by a given resource + * + * @param string|int $id + * @param CallbackHandler $listener + * @return bool Returns true if event and listener found, and unsubscribed; returns false if either event or listener not found + */ + public function detach($id, CallbackHandler $listener) + { + if (!array_key_exists($id, $this->identifiers)) { + return false; + } + return $this->identifiers[$id]->detach($listener); + } + + /** + * Detach a listener aggregate + * + * Listener aggregates accept a SharedEventManagerInterface instance, and call detachShared() + * of all previously attached listeners. + * + * @param SharedListenerAggregateInterface $aggregate + * @return mixed return value of {@link SharedListenerAggregateInterface::detachShared()} + */ + public function detachAggregate(SharedListenerAggregateInterface $aggregate) + { + return $aggregate->detachShared($this); + } + + /** + * Retrieve all registered events for a given resource + * + * @param string|int $id + * @return array + */ + public function getEvents($id) + { + if (!array_key_exists($id, $this->identifiers)) { + //Check if there are any id wildcards listeners + if ('*' != $id && array_key_exists('*', $this->identifiers)) { + return $this->identifiers['*']->getEvents(); + } + return false; + } + return $this->identifiers[$id]->getEvents(); + } + + /** + * Retrieve all listeners for a given identifier and event + * + * @param string|int $id + * @param string|int $event + * @return false|PriorityQueue + */ + public function getListeners($id, $event) + { + if (!array_key_exists($id, $this->identifiers)) { + return false; + } + return $this->identifiers[$id]->getListeners($event); + } + + /** + * Clear all listeners for a given identifier, optionally for a specific event + * + * @param string|int $id + * @param null|string $event + * @return bool + */ + public function clearListeners($id, $event = null) + { + if (!array_key_exists($id, $this->identifiers)) { + return false; + } + + if (null === $event) { + unset($this->identifiers[$id]); + return true; + } + + return $this->identifiers[$id]->clearListeners($event); + } +} diff --git a/vendor/zendframework/zend-eventmanager/src/SharedEventManagerAwareInterface.php b/vendor/zendframework/zend-eventmanager/src/SharedEventManagerAwareInterface.php new file mode 100644 index 0000000..f6c05ec --- /dev/null +++ b/vendor/zendframework/zend-eventmanager/src/SharedEventManagerAwareInterface.php @@ -0,0 +1,38 @@ +=5.3.23" + }, + "require-dev": { + "zendframework/zend-config": "~2.5", + "zendframework/zend-eventmanager": "~2.5", + "zendframework/zend-inputfilter": "~2.5", + "zendframework/zend-serializer": "~2.5", + "zendframework/zend-servicemanager": "~2.5", + "zendframework/zend-filter": "~2.5", + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/PHPUnit": "~4.0" + }, + "suggest": { + "zendframework/zend-eventmanager": "To support aggregate hydrator usage", + "zendframework/zend-serializer": "Zend\\Serializer component", + "zendframework/zend-servicemanager": "To support hydrator plugin manager usage", + "zendframework/zend-filter": "To support naming strategy hydrator usage" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "extra": { + "branch-alias": { + "dev-master": "2.5-dev", + "dev-develop": "2.6-dev" + } + }, + "autoload-dev": { + "psr-4": { + "ZendTest\\Stdlib\\": "test/" + } + } +} diff --git a/vendor/zendframework/zend-stdlib/src/AbstractOptions.php b/vendor/zendframework/zend-stdlib/src/AbstractOptions.php new file mode 100644 index 0000000..aaa1dd2 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/AbstractOptions.php @@ -0,0 +1,176 @@ +setFromArray($options); + } + } + + /** + * Set one or more configuration properties + * + * @param array|Traversable|AbstractOptions $options + * @throws Exception\InvalidArgumentException + * @return AbstractOptions Provides fluent interface + */ + public function setFromArray($options) + { + if ($options instanceof self) { + $options = $options->toArray(); + } + + if (!is_array($options) && !$options instanceof Traversable) { + throw new Exception\InvalidArgumentException( + sprintf( + 'Parameter provided to %s must be an %s, %s or %s', + __METHOD__, + 'array', + 'Traversable', + 'Zend\Stdlib\AbstractOptions' + ) + ); + } + + foreach ($options as $key => $value) { + $this->__set($key, $value); + } + + return $this; + } + + /** + * Cast to array + * + * @return array + */ + public function toArray() + { + $array = array(); + $transform = function ($letters) { + $letter = array_shift($letters); + return '_' . strtolower($letter); + }; + foreach ($this as $key => $value) { + if ($key === '__strictMode__') { + continue; + } + $normalizedKey = preg_replace_callback('/([A-Z])/', $transform, $key); + $array[$normalizedKey] = $value; + } + return $array; + } + + /** + * Set a configuration property + * + * @see ParameterObject::__set() + * @param string $key + * @param mixed $value + * @throws Exception\BadMethodCallException + * @return void + */ + public function __set($key, $value) + { + $setter = 'set' . str_replace('_', '', $key); + + if (is_callable(array($this, $setter))) { + $this->{$setter}($value); + + return; + } + + if ($this->__strictMode__) { + throw new Exception\BadMethodCallException(sprintf( + 'The option "%s" does not have a callable "%s" ("%s") setter method which must be defined', + $key, + 'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))), + $setter + )); + } + } + + /** + * Get a configuration property + * + * @see ParameterObject::__get() + * @param string $key + * @throws Exception\BadMethodCallException + * @return mixed + */ + public function __get($key) + { + $getter = 'get' . str_replace('_', '', $key); + + if (is_callable(array($this, $getter))) { + return $this->{$getter}(); + } + + throw new Exception\BadMethodCallException(sprintf( + 'The option "%s" does not have a callable "%s" getter method which must be defined', + $key, + 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))) + )); + } + + /** + * Test if a configuration property is null + * @see ParameterObject::__isset() + * @param string $key + * @return bool + */ + public function __isset($key) + { + $getter = 'get' . str_replace('_', '', $key); + + return method_exists($this, $getter) && null !== $this->__get($key); + } + + /** + * Set a configuration property to NULL + * + * @see ParameterObject::__unset() + * @param string $key + * @throws Exception\InvalidArgumentException + * @return void + */ + public function __unset($key) + { + try { + $this->__set($key, null); + } catch (Exception\BadMethodCallException $e) { + throw new Exception\InvalidArgumentException( + 'The class property $' . $key . ' cannot be unset as' + . ' NULL is an invalid value for it', + 0, + $e + ); + } + } +} diff --git a/vendor/zendframework/zend-stdlib/src/ArrayObject.php b/vendor/zendframework/zend-stdlib/src/ArrayObject.php new file mode 100644 index 0000000..44145c8 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/ArrayObject.php @@ -0,0 +1,432 @@ +setFlags($flags); + $this->storage = $input; + $this->setIteratorClass($iteratorClass); + $this->protectedProperties = array_keys(get_object_vars($this)); + } + + /** + * Returns whether the requested key exists + * + * @param mixed $key + * @return bool + */ + public function __isset($key) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + return $this->offsetExists($key); + } + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); + } + + return isset($this->$key); + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function __set($key, $value) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + return $this->offsetSet($key, $value); + } + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); + } + $this->$key = $value; + } + + /** + * Unsets the value at the specified key + * + * @param mixed $key + * @return void + */ + public function __unset($key) + { + if ($this->flag == self::ARRAY_AS_PROPS) { + return $this->offsetUnset($key); + } + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); + } + unset($this->$key); + } + + /** + * Returns the value at the specified key by reference + * + * @param mixed $key + * @return mixed + */ + public function &__get($key) + { + $ret = null; + if ($this->flag == self::ARRAY_AS_PROPS) { + $ret =& $this->offsetGet($key); + + return $ret; + } + if (in_array($key, $this->protectedProperties)) { + throw new Exception\InvalidArgumentException('$key is a protected property, use a different key'); + } + + return $this->$key; + } + + /** + * Appends the value + * + * @param mixed $value + * @return void + */ + public function append($value) + { + $this->storage[] = $value; + } + + /** + * Sort the entries by value + * + * @return void + */ + public function asort() + { + asort($this->storage); + } + + /** + * Get the number of public properties in the ArrayObject + * + * @return int + */ + public function count() + { + return count($this->storage); + } + + /** + * Exchange the array for another one. + * + * @param array|ArrayObject $data + * @return array + */ + public function exchangeArray($data) + { + if (!is_array($data) && !is_object($data)) { + throw new Exception\InvalidArgumentException('Passed variable is not an array or object, using empty array instead'); + } + + if (is_object($data) && ($data instanceof self || $data instanceof \ArrayObject)) { + $data = $data->getArrayCopy(); + } + if (!is_array($data)) { + $data = (array) $data; + } + + $storage = $this->storage; + + $this->storage = $data; + + return $storage; + } + + /** + * Creates a copy of the ArrayObject. + * + * @return array + */ + public function getArrayCopy() + { + return $this->storage; + } + + /** + * Gets the behavior flags. + * + * @return int + */ + public function getFlags() + { + return $this->flag; + } + + /** + * Create a new iterator from an ArrayObject instance + * + * @return \Iterator + */ + public function getIterator() + { + $class = $this->iteratorClass; + + return new $class($this->storage); + } + + /** + * Gets the iterator classname for the ArrayObject. + * + * @return string + */ + public function getIteratorClass() + { + return $this->iteratorClass; + } + + /** + * Sort the entries by key + * + * @return void + */ + public function ksort() + { + ksort($this->storage); + } + + /** + * Sort an array using a case insensitive "natural order" algorithm + * + * @return void + */ + public function natcasesort() + { + natcasesort($this->storage); + } + + /** + * Sort entries using a "natural order" algorithm + * + * @return void + */ + public function natsort() + { + natsort($this->storage); + } + + /** + * Returns whether the requested key exists + * + * @param mixed $key + * @return bool + */ + public function offsetExists($key) + { + return isset($this->storage[$key]); + } + + /** + * Returns the value at the specified key + * + * @param mixed $key + * @return mixed + */ + public function &offsetGet($key) + { + $ret = null; + if (!$this->offsetExists($key)) { + return $ret; + } + $ret =& $this->storage[$key]; + + return $ret; + } + + /** + * Sets the value at the specified key to value + * + * @param mixed $key + * @param mixed $value + * @return void + */ + public function offsetSet($key, $value) + { + $this->storage[$key] = $value; + } + + /** + * Unsets the value at the specified key + * + * @param mixed $key + * @return void + */ + public function offsetUnset($key) + { + if ($this->offsetExists($key)) { + unset($this->storage[$key]); + } + } + + /** + * Serialize an ArrayObject + * + * @return string + */ + public function serialize() + { + return serialize(get_object_vars($this)); + } + + /** + * Sets the behavior flags + * + * @param int $flags + * @return void + */ + public function setFlags($flags) + { + $this->flag = $flags; + } + + /** + * Sets the iterator classname for the ArrayObject + * + * @param string $class + * @return void + */ + public function setIteratorClass($class) + { + if (class_exists($class)) { + $this->iteratorClass = $class; + + return ; + } + + if (strpos($class, '\\') === 0) { + $class = '\\' . $class; + if (class_exists($class)) { + $this->iteratorClass = $class; + + return ; + } + } + + throw new Exception\InvalidArgumentException('The iterator class does not exist'); + } + + /** + * Sort the entries with a user-defined comparison function and maintain key association + * + * @param callable $function + * @return void + */ + public function uasort($function) + { + if (is_callable($function)) { + uasort($this->storage, $function); + } + } + + /** + * Sort the entries by keys using a user-defined comparison function + * + * @param callable $function + * @return void + */ + public function uksort($function) + { + if (is_callable($function)) { + uksort($this->storage, $function); + } + } + + /** + * Unserialize an ArrayObject + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + $ar = unserialize($data); + $this->protectedProperties = array_keys(get_object_vars($this)); + + $this->setFlags($ar['flag']); + $this->exchangeArray($ar['storage']); + $this->setIteratorClass($ar['iteratorClass']); + + foreach ($ar as $k => $v) { + switch ($k) { + case 'flag': + $this->setFlags($v); + break; + case 'storage': + $this->exchangeArray($v); + break; + case 'iteratorClass': + $this->setIteratorClass($v); + break; + case 'protectedProperties': + continue; + default: + $this->__set($k, $v); + } + } + } +} diff --git a/vendor/zendframework/zend-stdlib/src/ArraySerializableInterface.php b/vendor/zendframework/zend-stdlib/src/ArraySerializableInterface.php new file mode 100644 index 0000000..dcf8471 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/ArraySerializableInterface.php @@ -0,0 +1,28 @@ +getArrayCopy(); + return new ArrayIterator(array_reverse($array)); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/ArrayUtils.php b/vendor/zendframework/zend-stdlib/src/ArrayUtils.php new file mode 100644 index 0000000..3545054 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/ArrayUtils.php @@ -0,0 +1,335 @@ + 0; + } + + /** + * Test whether an array contains one or more integer keys + * + * @param mixed $value + * @param bool $allowEmpty Should an empty array() return true + * @return bool + */ + public static function hasIntegerKeys($value, $allowEmpty = false) + { + if (!is_array($value)) { + return false; + } + + if (!$value) { + return $allowEmpty; + } + + return count(array_filter(array_keys($value), 'is_int')) > 0; + } + + /** + * Test whether an array contains one or more numeric keys. + * + * A numeric key can be one of the following: + * - an integer 1, + * - a string with a number '20' + * - a string with negative number: '-1000' + * - a float: 2.2120, -78.150999 + * - a string with float: '4000.99999', '-10.10' + * + * @param mixed $value + * @param bool $allowEmpty Should an empty array() return true + * @return bool + */ + public static function hasNumericKeys($value, $allowEmpty = false) + { + if (!is_array($value)) { + return false; + } + + if (!$value) { + return $allowEmpty; + } + + return count(array_filter(array_keys($value), 'is_numeric')) > 0; + } + + /** + * Test whether an array is a list + * + * A list is a collection of values assigned to continuous integer keys + * starting at 0 and ending at count() - 1. + * + * For example: + * + * $list = array('a', 'b', 'c', 'd'); + * $list = array( + * 0 => 'foo', + * 1 => 'bar', + * 2 => array('foo' => 'baz'), + * ); + * + * + * @param mixed $value + * @param bool $allowEmpty Is an empty list a valid list? + * @return bool + */ + public static function isList($value, $allowEmpty = false) + { + if (!is_array($value)) { + return false; + } + + if (!$value) { + return $allowEmpty; + } + + return (array_values($value) === $value); + } + + /** + * Test whether an array is a hash table. + * + * An array is a hash table if: + * + * 1. Contains one or more non-integer keys, or + * 2. Integer keys are non-continuous or misaligned (not starting with 0) + * + * For example: + * + * $hash = array( + * 'foo' => 15, + * 'bar' => false, + * ); + * $hash = array( + * 1995 => 'Birth of PHP', + * 2009 => 'PHP 5.3.0', + * 2012 => 'PHP 5.4.0', + * ); + * $hash = array( + * 'formElement, + * 'options' => array( 'debug' => true ), + * ); + * + * + * @param mixed $value + * @param bool $allowEmpty Is an empty array() a valid hash table? + * @return bool + */ + public static function isHashTable($value, $allowEmpty = false) + { + if (!is_array($value)) { + return false; + } + + if (!$value) { + return $allowEmpty; + } + + return (array_values($value) !== $value); + } + + /** + * Checks if a value exists in an array. + * + * Due to "foo" == 0 === TRUE with in_array when strict = false, an option + * has been added to prevent this. When $strict = 0/false, the most secure + * non-strict check is implemented. if $strict = -1, the default in_array + * non-strict behaviour is used. + * + * @param mixed $needle + * @param array $haystack + * @param int|bool $strict + * @return bool + */ + public static function inArray($needle, array $haystack, $strict = false) + { + if (!$strict) { + if (is_int($needle) || is_float($needle)) { + $needle = (string) $needle; + } + if (is_string($needle)) { + foreach ($haystack as &$h) { + if (is_int($h) || is_float($h)) { + $h = (string) $h; + } + } + } + } + return in_array($needle, $haystack, $strict); + } + + /** + * Convert an iterator to an array. + * + * Converts an iterator to an array. The $recursive flag, on by default, + * hints whether or not you want to do so recursively. + * + * @param array|Traversable $iterator The array or Traversable object to convert + * @param bool $recursive Recursively check all nested structures + * @throws Exception\InvalidArgumentException if $iterator is not an array or a Traversable object + * @return array + */ + public static function iteratorToArray($iterator, $recursive = true) + { + if (!is_array($iterator) && !$iterator instanceof Traversable) { + throw new Exception\InvalidArgumentException(__METHOD__ . ' expects an array or Traversable object'); + } + + if (!$recursive) { + if (is_array($iterator)) { + return $iterator; + } + + return iterator_to_array($iterator); + } + + if (method_exists($iterator, 'toArray')) { + return $iterator->toArray(); + } + + $array = array(); + foreach ($iterator as $key => $value) { + if (is_scalar($value)) { + $array[$key] = $value; + continue; + } + + if ($value instanceof Traversable) { + $array[$key] = static::iteratorToArray($value, $recursive); + continue; + } + + if (is_array($value)) { + $array[$key] = static::iteratorToArray($value, $recursive); + continue; + } + + $array[$key] = $value; + } + + return $array; + } + + /** + * Merge two arrays together. + * + * If an integer key exists in both arrays and preserveNumericKeys is false, the value + * from the second array will be appended to the first array. If both values are arrays, they + * are merged together, else the value of the second array overwrites the one of the first array. + * + * @param array $a + * @param array $b + * @param bool $preserveNumericKeys + * @return array + */ + public static function merge(array $a, array $b, $preserveNumericKeys = false) + { + foreach ($b as $key => $value) { + if ($value instanceof MergeReplaceKeyInterface) { + $a[$key] = $value->getData(); + } elseif (isset($a[$key]) || array_key_exists($key, $a)) { + if ($value instanceof MergeRemoveKey) { + unset($a[$key]); + } elseif (!$preserveNumericKeys && is_int($key)) { + $a[] = $value; + } elseif (is_array($value) && is_array($a[$key])) { + $a[$key] = static::merge($a[$key], $value, $preserveNumericKeys); + } else { + $a[$key] = $value; + } + } else { + if (!$value instanceof MergeRemoveKey) { + $a[$key] = $value; + } + } + } + + return $a; + } + + /** + * Compatibility Method for array_filter on <5.6 systems + * + * @param array $data + * @param callable $callback + * @param null|int $flag + * @return array + */ + public static function filter(array $data, $callback, $flag = null) + { + if (! is_callable($callback)) { + throw new Exception\InvalidArgumentException(sprintf( + 'Second parameter of %s must be callable', + __METHOD__ + )); + } + + if (version_compare(PHP_VERSION, '5.6.0') >= 0) { + return array_filter($data, $callback, $flag); + } + + $output = array(); + foreach ($data as $key => $value) { + $params = array($value); + + if ($flag === static::ARRAY_FILTER_USE_BOTH) { + $params[] = $key; + } + + if ($flag === static::ARRAY_FILTER_USE_KEY) { + $params = array($key); + } + + $response = call_user_func_array($callback, $params); + if ($response) { + $output[$key] = $value; + } + } + + return $output; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/ArrayUtils/MergeRemoveKey.php b/vendor/zendframework/zend-stdlib/src/ArrayUtils/MergeRemoveKey.php new file mode 100644 index 0000000..7c4d097 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/ArrayUtils/MergeRemoveKey.php @@ -0,0 +1,14 @@ +data = $data; + } + + /** + * {@inheritDoc} + */ + public function getData() + { + return $this->data; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php b/vendor/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php new file mode 100644 index 0000000..725cf11 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/ArrayUtils/MergeReplaceKeyInterface.php @@ -0,0 +1,21 @@ +metadata = $metadata; + $this->registerCallback($callback); + } + + /** + * Registers the callback provided in the constructor + * + * @param callable $callback + * @throws Exception\InvalidCallbackException + * @return void + */ + protected function registerCallback($callback) + { + if (!is_callable($callback)) { + throw new Exception\InvalidCallbackException('Invalid callback provided; not callable'); + } + + $this->callback = $callback; + } + + /** + * Retrieve registered callback + * + * @return callable + */ + public function getCallback() + { + return $this->callback; + } + + /** + * Invoke handler + * + * @param array $args Arguments to pass to callback + * @return mixed + */ + public function call(array $args = array()) + { + $callback = $this->getCallback(); + + // Minor performance tweak, if the callback gets called more than once + if (!isset(static::$isPhp54)) { + static::$isPhp54 = version_compare(PHP_VERSION, '5.4.0rc1', '>='); + } + + $argCount = count($args); + + if (static::$isPhp54 && is_string($callback)) { + $result = $this->validateStringCallbackFor54($callback); + + if ($result !== true && $argCount <= 3) { + $callback = $result; + // Minor performance tweak, if the callback gets called more + // than once + $this->callback = $result; + } + } + + // Minor performance tweak; use call_user_func() until > 3 arguments + // reached + switch ($argCount) { + case 0: + if (static::$isPhp54) { + return $callback(); + } + return call_user_func($callback); + case 1: + if (static::$isPhp54) { + return $callback(array_shift($args)); + } + return call_user_func($callback, array_shift($args)); + case 2: + $arg1 = array_shift($args); + $arg2 = array_shift($args); + if (static::$isPhp54) { + return $callback($arg1, $arg2); + } + return call_user_func($callback, $arg1, $arg2); + case 3: + $arg1 = array_shift($args); + $arg2 = array_shift($args); + $arg3 = array_shift($args); + if (static::$isPhp54) { + return $callback($arg1, $arg2, $arg3); + } + return call_user_func($callback, $arg1, $arg2, $arg3); + default: + return call_user_func_array($callback, $args); + } + } + + /** + * Invoke as functor + * + * @return mixed + */ + public function __invoke() + { + return $this->call(func_get_args()); + } + + /** + * Get all callback metadata + * + * @return array + */ + public function getMetadata() + { + return $this->metadata; + } + + /** + * Retrieve a single metadatum + * + * @param string $name + * @return mixed + */ + public function getMetadatum($name) + { + if (array_key_exists($name, $this->metadata)) { + return $this->metadata[$name]; + } + return; + } + + /** + * Validate a static method call + * + * Validates that a static method call in PHP 5.4 will actually work + * + * @param string $callback + * @return true|array + * @throws Exception\InvalidCallbackException if invalid + */ + protected function validateStringCallbackFor54($callback) + { + if (!strstr($callback, '::')) { + return true; + } + + list($class, $method) = explode('::', $callback, 2); + + if (!class_exists($class)) { + throw new Exception\InvalidCallbackException(sprintf( + 'Static method call "%s" refers to a class that does not exist', + $callback + )); + } + + $r = new ReflectionClass($class); + if (!$r->hasMethod($method)) { + throw new Exception\InvalidCallbackException(sprintf( + 'Static method call "%s" refers to a method that does not exist', + $callback + )); + } + $m = $r->getMethod($method); + if (!$m->isStatic()) { + throw new Exception\InvalidCallbackException(sprintf( + 'Static method call "%s" refers to a method that is not static', + $callback + )); + } + + // returning a non boolean value may not be nice for a validate method, + // but that allows the usage of a static string callback without using + // the call_user_func function. + return array($class, $method); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/DateTime.php b/vendor/zendframework/zend-stdlib/src/DateTime.php new file mode 100644 index 0000000..cdab67d --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/DateTime.php @@ -0,0 +1,47 @@ + GLOB_MARK, + self::GLOB_NOSORT => GLOB_NOSORT, + self::GLOB_NOCHECK => GLOB_NOCHECK, + self::GLOB_NOESCAPE => GLOB_NOESCAPE, + self::GLOB_BRACE => GLOB_BRACE, + self::GLOB_ONLYDIR => GLOB_ONLYDIR, + self::GLOB_ERR => GLOB_ERR, + ); + + $globFlags = 0; + + foreach ($flagMap as $internalFlag => $globFlag) { + if ($flags & $internalFlag) { + $globFlags |= $globFlag; + } + } + } else { + $globFlags = 0; + } + + ErrorHandler::start(); + $res = glob($pattern, $globFlags); + $err = ErrorHandler::stop(); + if ($res === false) { + throw new Exception\RuntimeException("glob('{$pattern}', {$globFlags}) failed", 0, $err); + } + return $res; + } + + /** + * Expand braces manually, then use the system glob. + * + * @param string $pattern + * @param int $flags + * @return array + * @throws Exception\RuntimeException + */ + protected static function fallbackGlob($pattern, $flags) + { + if (!$flags & self::GLOB_BRACE) { + return static::systemGlob($pattern, $flags); + } + + $flags &= ~self::GLOB_BRACE; + $length = strlen($pattern); + $paths = array(); + + if ($flags & self::GLOB_NOESCAPE) { + $begin = strpos($pattern, '{'); + } else { + $begin = 0; + + while (true) { + if ($begin === $length) { + $begin = false; + break; + } elseif ($pattern[$begin] === '\\' && ($begin + 1) < $length) { + $begin++; + } elseif ($pattern[$begin] === '{') { + break; + } + + $begin++; + } + } + + if ($begin === false) { + return static::systemGlob($pattern, $flags); + } + + $next = static::nextBraceSub($pattern, $begin + 1, $flags); + + if ($next === null) { + return static::systemGlob($pattern, $flags); + } + + $rest = $next; + + while ($pattern[$rest] !== '}') { + $rest = static::nextBraceSub($pattern, $rest + 1, $flags); + + if ($rest === null) { + return static::systemGlob($pattern, $flags); + } + } + + $p = $begin + 1; + + while (true) { + $subPattern = substr($pattern, 0, $begin) + . substr($pattern, $p, $next - $p) + . substr($pattern, $rest + 1); + + $result = static::fallbackGlob($subPattern, $flags | self::GLOB_BRACE); + + if ($result) { + $paths = array_merge($paths, $result); + } + + if ($pattern[$next] === '}') { + break; + } + + $p = $next + 1; + $next = static::nextBraceSub($pattern, $p, $flags); + } + + return array_unique($paths); + } + + /** + * Find the end of the sub-pattern in a brace expression. + * + * @param string $pattern + * @param int $begin + * @param int $flags + * @return int|null + */ + protected static function nextBraceSub($pattern, $begin, $flags) + { + $length = strlen($pattern); + $depth = 0; + $current = $begin; + + while ($current < $length) { + if (!$flags & self::GLOB_NOESCAPE && $pattern[$current] === '\\') { + if (++$current === $length) { + break; + } + + $current++; + } else { + if (($pattern[$current] === '}' && $depth-- === 0) || ($pattern[$current] === ',' && $depth === 0)) { + break; + } elseif ($pattern[$current++] === '{') { + $depth++; + } + } + } + + return ($current < $length ? $current : null); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Guard/AllGuardsTrait.php b/vendor/zendframework/zend-stdlib/src/Guard/AllGuardsTrait.php new file mode 100644 index 0000000..95bc516 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Guard/AllGuardsTrait.php @@ -0,0 +1,20 @@ +strategies = new ArrayObject(); + $this->filterComposite = new FilterComposite(); + } + + /** + * Gets the strategy with the given name. + * + * @param string $name The name of the strategy to get. + * + * @throws \Zend\Stdlib\Exception\InvalidArgumentException + * @return StrategyInterface + */ + public function getStrategy($name) + { + if (isset($this->strategies[$name])) { + return $this->strategies[$name]; + } + + if (!isset($this->strategies['*'])) { + throw new Exception\InvalidArgumentException(sprintf( + '%s: no strategy by name of "%s", and no wildcard strategy present', + __METHOD__, + $name + )); + } + + return $this->strategies['*']; + } + + /** + * Checks if the strategy with the given name exists. + * + * @param string $name The name of the strategy to check for. + * @return bool + */ + public function hasStrategy($name) + { + return array_key_exists($name, $this->strategies) + || array_key_exists('*', $this->strategies); + } + + /** + * Adds the given strategy under the given name. + * + * @param string $name The name of the strategy to register. + * @param StrategyInterface $strategy The strategy to register. + * @return HydratorInterface + */ + public function addStrategy($name, StrategyInterface $strategy) + { + $this->strategies[$name] = $strategy; + return $this; + } + + /** + * Removes the strategy with the given name. + * + * @param string $name The name of the strategy to remove. + * @return HydratorInterface + */ + public function removeStrategy($name) + { + unset($this->strategies[$name]); + return $this; + } + + /** + * Converts a value for extraction. If no strategy exists the plain value is returned. + * + * @param string $name The name of the strategy to use. + * @param mixed $value The value that should be converted. + * @param mixed $object The object is optionally provided as context. + * @return mixed + */ + public function extractValue($name, $value, $object = null) + { + if ($this->hasStrategy($name)) { + $strategy = $this->getStrategy($name); + $value = $strategy->extract($value, $object); + } + return $value; + } + + /** + * Converts a value for hydration. If no strategy exists the plain value is returned. + * + * @param string $name The name of the strategy to use. + * @param mixed $value The value that should be converted. + * @param array $data The whole data is optionally provided as context. + * @return mixed + */ + public function hydrateValue($name, $value, $data = null) + { + if ($this->hasStrategy($name)) { + $strategy = $this->getStrategy($name); + $value = $strategy->hydrate($value, $data); + } + return $value; + } + + /** + * Convert a name for extraction. If no naming strategy exists, the plain value is returned. + * + * @param string $name The name to convert. + * @param null $object The object is optionally provided as context. + * @return mixed + */ + public function extractName($name, $object = null) + { + if ($this->hasNamingStrategy()) { + $name = $this->getNamingStrategy()->extract($name, $object); + } + return $name; + } + + /** + * Converts a value for hydration. If no naming strategy exists, the plain value is returned. + * + * @param string $name The name to convert. + * @param array $data The whole data is optionally provided as context. + * @return mixed + */ + public function hydrateName($name, $data = null) + { + if ($this->hasNamingStrategy()) { + $name = $this->getNamingStrategy()->hydrate($name, $data); + } + return $name; + } + + /** + * Get the filter instance + * + * @return Filter\FilterComposite + */ + public function getFilter() + { + return $this->filterComposite; + } + + /** + * Add a new filter to take care of what needs to be hydrated. + * To exclude e.g. the method getServiceLocator: + * + * + * $composite->addFilter("servicelocator", + * function ($property) { + * list($class, $method) = explode('::', $property); + * if ($method === 'getServiceLocator') { + * return false; + * } + * return true; + * }, FilterComposite::CONDITION_AND + * ); + * + * + * @param string $name Index in the composite + * @param callable|Filter\FilterInterface $filter + * @param int $condition + * @return Filter\FilterComposite + */ + public function addFilter($name, $filter, $condition = FilterComposite::CONDITION_OR) + { + return $this->filterComposite->addFilter($name, $filter, $condition); + } + + /** + * Check whether a specific filter exists at key $name or not + * + * @param string $name Index in the composite + * @return bool + */ + public function hasFilter($name) + { + return $this->filterComposite->hasFilter($name); + } + + /** + * Remove a filter from the composition. + * To not extract "has" methods, you simply need to unregister it + * + * + * $filterComposite->removeFilter('has'); + * + * + * @param $name + * @return Filter\FilterComposite + */ + public function removeFilter($name) + { + return $this->filterComposite->removeFilter($name); + } + + /** + * Adds the given naming strategy + * + * @param NamingStrategyInterface $strategy The naming to register. + * @return self + */ + public function setNamingStrategy(NamingStrategyInterface $strategy) + { + $this->namingStrategy = $strategy; + + return $this; + } + + /** + * Gets the naming strategy. + * + * @return NamingStrategyInterface + */ + public function getNamingStrategy() + { + return $this->namingStrategy; + } + + /** + * Checks if a naming strategy exists. + * + * @return bool + */ + public function hasNamingStrategy() + { + return isset($this->namingStrategy); + } + + /** + * Removes the naming strategy + * + * @return self + */ + public function removeNamingStrategy() + { + $this->namingStrategy = null; + + return $this; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/AggregateHydrator.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/AggregateHydrator.php new file mode 100644 index 0000000..38a868e --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/AggregateHydrator.php @@ -0,0 +1,85 @@ +getEventManager()->attachAggregate(new HydratorListener($hydrator), $priority); + } + + /** + * {@inheritDoc} + */ + public function extract($object) + { + $event = new ExtractEvent($this, $object); + + $this->getEventManager()->trigger($event); + + return $event->getExtractedData(); + } + + /** + * {@inheritDoc} + */ + public function hydrate(array $data, $object) + { + $event = new HydrateEvent($this, $object, $data); + + $this->getEventManager()->trigger($event); + + return $event->getHydratedObject(); + } + + /** + * {@inheritDoc} + */ + public function setEventManager(EventManagerInterface $eventManager) + { + $eventManager->setIdentifiers(array(__CLASS__, get_class($this))); + + $this->eventManager = $eventManager; + } + + /** + * {@inheritDoc} + */ + public function getEventManager() + { + if (null === $this->eventManager) { + $this->setEventManager(new EventManager()); + } + + return $this->eventManager; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/ExtractEvent.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/ExtractEvent.php new file mode 100644 index 0000000..b13bc5c --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/ExtractEvent.php @@ -0,0 +1,98 @@ +target = $target; + $this->extractionObject = $extractionObject; + } + + /** + * Retrieves the object from which data is extracted + * + * @return object + */ + public function getExtractionObject() + { + return $this->extractionObject; + } + + /** + * @param object $extractionObject + * + * @return void + */ + public function setExtractionObject($extractionObject) + { + $this->extractionObject = $extractionObject; + } + + /** + * Retrieves the data that has been extracted + * + * @return array + */ + public function getExtractedData() + { + return $this->extractedData; + } + + /** + * @param array $extractedData + * + * @return void + */ + public function setExtractedData(array $extractedData) + { + $this->extractedData = $extractedData; + } + + /** + * Merge provided data with the extracted data + * + * @param array $additionalData + * + * @return void + */ + public function mergeExtractedData(array $additionalData) + { + $this->extractedData = array_merge($this->extractedData, $additionalData); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydrateEvent.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydrateEvent.php new file mode 100644 index 0000000..a7c91ee --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydrateEvent.php @@ -0,0 +1,84 @@ +target = $target; + $this->hydratedObject = $hydratedObject; + $this->hydrationData = $hydrationData; + } + + /** + * Retrieves the object that is being hydrated + * + * @return object + */ + public function getHydratedObject() + { + return $this->hydratedObject; + } + + /** + * @param object $hydratedObject + */ + public function setHydratedObject($hydratedObject) + { + $this->hydratedObject = $hydratedObject; + } + + /** + * Retrieves the data that is being used for hydration + * + * @return array + */ + public function getHydrationData() + { + return $this->hydrationData; + } + + /** + * @param array $hydrationData + */ + public function setHydrationData(array $hydrationData) + { + $this->hydrationData = $hydrationData; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydratorListener.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydratorListener.php new file mode 100644 index 0000000..1c25ff3 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Aggregate/HydratorListener.php @@ -0,0 +1,80 @@ +hydrator = $hydrator; + } + + /** + * {@inheritDoc} + */ + public function attach(EventManagerInterface $events, $priority = 1) + { + $this->listeners[] = $events->attach(HydrateEvent::EVENT_HYDRATE, array($this, 'onHydrate'), $priority); + $this->listeners[] = $events->attach(ExtractEvent::EVENT_EXTRACT, array($this, 'onExtract'), $priority); + } + + /** + * Callback to be used when {@see \Zend\Stdlib\Hydrator\Aggregate\HydrateEvent::EVENT_HYDRATE} is triggered + * + * @param \Zend\Stdlib\Hydrator\Aggregate\HydrateEvent $event + * + * @return object + * + * @internal + */ + public function onHydrate(HydrateEvent $event) + { + $object = $this->hydrator->hydrate($event->getHydrationData(), $event->getHydratedObject()); + + $event->setHydratedObject($object); + + return $object; + } + + /** + * Callback to be used when {@see \Zend\Stdlib\Hydrator\Aggregate\ExtractEvent::EVENT_EXTRACT} is triggered + * + * @param \Zend\Stdlib\Hydrator\Aggregate\ExtractEvent $event + * + * @return array + * + * @internal + */ + public function onExtract(ExtractEvent $event) + { + $data = $this->hydrator->extract($event->getExtractionObject()); + + $event->mergeExtractedData($data); + + return $data; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/ArraySerializable.php b/vendor/zendframework/zend-stdlib/src/Hydrator/ArraySerializable.php new file mode 100644 index 0000000..4f4ab2a --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/ArraySerializable.php @@ -0,0 +1,83 @@ +getArrayCopy(); + $filter = $this->getFilter(); + + foreach ($data as $name => $value) { + if (!$filter->filter($name)) { + unset($data[$name]); + continue; + } + $extractedName = $this->extractName($name, $object); + // replace the original key with extracted, if differ + if ($extractedName !== $name) { + unset($data[$name]); + $name = $extractedName; + } + $data[$name] = $this->extractValue($name, $value, $object); + } + + return $data; + } + + /** + * Hydrate an object + * + * Hydrates an object by passing $data to either its exchangeArray() or + * populate() method. + * + * @param array $data + * @param object $object + * @return object + * @throws Exception\BadMethodCallException for an $object not implementing exchangeArray() or populate() + */ + public function hydrate(array $data, $object) + { + $replacement = array(); + foreach ($data as $key => $value) { + $name = $this->hydrateName($key, $data); + $replacement[$name] = $this->hydrateValue($name, $value, $data); + } + + if (is_callable(array($object, 'exchangeArray'))) { + $object->exchangeArray($replacement); + } elseif (is_callable(array($object, 'populate'))) { + $object->populate($replacement); + } else { + throw new Exception\BadMethodCallException( + sprintf('%s expects the provided object to implement exchangeArray() or populate()', __METHOD__) + ); + } + return $object; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/ClassMethods.php b/vendor/zendframework/zend-stdlib/src/Hydrator/ClassMethods.php new file mode 100644 index 0000000..4edb1f4 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/ClassMethods.php @@ -0,0 +1,274 @@ +setUnderscoreSeparatedKeys($underscoreSeparatedKeys); + + $this->callableMethodFilter = new OptionalParametersFilter(); + + $this->filterComposite->addFilter('is', new IsFilter()); + $this->filterComposite->addFilter('has', new HasFilter()); + $this->filterComposite->addFilter('get', new GetFilter()); + $this->filterComposite->addFilter('parameter', new OptionalParametersFilter(), FilterComposite::CONDITION_AND); + } + + /** + * @param array|Traversable $options + * @return ClassMethods + * @throws Exception\InvalidArgumentException + */ + public function setOptions($options) + { + if ($options instanceof Traversable) { + $options = ArrayUtils::iteratorToArray($options); + } elseif (!is_array($options)) { + throw new Exception\InvalidArgumentException( + 'The options parameter must be an array or a Traversable' + ); + } + if (isset($options['underscoreSeparatedKeys'])) { + $this->setUnderscoreSeparatedKeys($options['underscoreSeparatedKeys']); + } + + return $this; + } + + /** + * @param bool $underscoreSeparatedKeys + * @return ClassMethods + */ + public function setUnderscoreSeparatedKeys($underscoreSeparatedKeys) + { + $this->underscoreSeparatedKeys = (bool) $underscoreSeparatedKeys; + + if ($this->underscoreSeparatedKeys) { + $this->setNamingStrategy(new UnderscoreNamingStrategy); + } elseif ($this->getNamingStrategy() instanceof UnderscoreNamingStrategy) { + $this->removeNamingStrategy(); + } + + return $this; + } + + /** + * @return bool + */ + public function getUnderscoreSeparatedKeys() + { + return $this->underscoreSeparatedKeys; + } + + /** + * Extract values from an object with class methods + * + * Extracts the getter/setter of the given $object. + * + * @param object $object + * @return array + * @throws Exception\BadMethodCallException for a non-object $object + */ + public function extract($object) + { + if (!is_object($object)) { + throw new Exception\BadMethodCallException(sprintf( + '%s expects the provided $object to be a PHP object)', + __METHOD__ + )); + } + + $objectClass = get_class($object); + + // reset the hydrator's hydrator's cache for this object, as the filter may be per-instance + if ($object instanceof FilterProviderInterface) { + $this->extractionMethodsCache[$objectClass] = null; + } + + // pass 1 - finding out which properties can be extracted, with which methods (populate hydration cache) + if (! isset($this->extractionMethodsCache[$objectClass])) { + $this->extractionMethodsCache[$objectClass] = array(); + $filter = $this->filterComposite; + $methods = get_class_methods($object); + + if ($object instanceof FilterProviderInterface) { + $filter = new FilterComposite( + array($object->getFilter()), + array(new MethodMatchFilter('getFilter')) + ); + } + + foreach ($methods as $method) { + $methodFqn = $objectClass . '::' . $method; + + if (! ($filter->filter($methodFqn) && $this->callableMethodFilter->filter($methodFqn))) { + continue; + } + + $attribute = $method; + + if (strpos($method, 'get') === 0) { + $attribute = substr($method, 3); + if (!property_exists($object, $attribute)) { + $attribute = lcfirst($attribute); + } + } + + $this->extractionMethodsCache[$objectClass][$method] = $attribute; + } + } + + $values = array(); + + // pass 2 - actually extract data + foreach ($this->extractionMethodsCache[$objectClass] as $methodName => $attributeName) { + $realAttributeName = $this->extractName($attributeName, $object); + $values[$realAttributeName] = $this->extractValue($realAttributeName, $object->$methodName(), $object); + } + + return $values; + } + + /** + * Hydrate an object by populating getter/setter methods + * + * Hydrates an object by getter/setter methods of the object. + * + * @param array $data + * @param object $object + * @return object + * @throws Exception\BadMethodCallException for a non-object $object + */ + public function hydrate(array $data, $object) + { + if (!is_object($object)) { + throw new Exception\BadMethodCallException(sprintf( + '%s expects the provided $object to be a PHP object)', + __METHOD__ + )); + } + + $objectClass = get_class($object); + + foreach ($data as $property => $value) { + $propertyFqn = $objectClass . '::$' . $property; + + if (! isset($this->hydrationMethodsCache[$propertyFqn])) { + $setterName = 'set' . ucfirst($this->hydrateName($property, $data)); + + $this->hydrationMethodsCache[$propertyFqn] = is_callable(array($object, $setterName)) + ? $setterName + : false; + } + + if ($this->hydrationMethodsCache[$propertyFqn]) { + $object->{$this->hydrationMethodsCache[$propertyFqn]}($this->hydrateValue($property, $value, $data)); + } + } + + return $object; + } + + /** + * {@inheritDoc} + */ + public function addFilter($name, $filter, $condition = FilterComposite::CONDITION_OR) + { + $this->resetCaches(); + + return parent::addFilter($name, $filter, $condition); + } + + /** + * {@inheritDoc} + */ + public function removeFilter($name) + { + $this->resetCaches(); + + return parent::removeFilter($name); + } + + /** + * {@inheritDoc} + */ + public function setNamingStrategy(NamingStrategyInterface $strategy) + { + $this->resetCaches(); + + return parent::setNamingStrategy($strategy); + } + + /** + * {@inheritDoc} + */ + public function removeNamingStrategy() + { + $this->resetCaches(); + + return parent::removeNamingStrategy(); + } + + /** + * Reset all local hydration/extraction caches + */ + private function resetCaches() + { + $this->hydrationMethodsCache = $this->extractionMethodsCache = array(); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/DelegatingHydrator.php b/vendor/zendframework/zend-stdlib/src/Hydrator/DelegatingHydrator.php new file mode 100644 index 0000000..db234d3 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/DelegatingHydrator.php @@ -0,0 +1,57 @@ +hydrators = $hydrators; + } + + /** + * {@inheritdoc} + */ + public function hydrate(array $data, $object) + { + return $this->getHydrator($object)->hydrate($data, $object); + } + + /** + * {@inheritdoc} + */ + public function extract($object) + { + return $this->getHydrator($object)->extract($object); + } + + /** + * Gets hydrator of an object + * + * @param object $object + * @return HydratorInterface + */ + protected function getHydrator($object) + { + return $this->hydrators->get(get_class($object)); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/DelegatingHydratorFactory.php b/vendor/zendframework/zend-stdlib/src/Hydrator/DelegatingHydratorFactory.php new file mode 100644 index 0000000..c3a0da2 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/DelegatingHydratorFactory.php @@ -0,0 +1,29 @@ +orFilter = new ArrayObject($orFilter); + $this->andFilter = new ArrayObject($andFilter); + } + + /** + * Add a filter to the composite. Has to be indexed with $name in + * order to identify a specific filter. + * + * This example will exclude all methods from the hydration, that starts with 'getService' + * + * $composite->addFilter('exclude', + * function ($method) { + * if (preg_match('/^getService/', $method) { + * return false; + * } + * return true; + * }, FilterComposite::CONDITION_AND + * ); + * + * + * @param string $name + * @param callable|FilterInterface $filter + * @param int $condition Can be either FilterComposite::CONDITION_OR or FilterComposite::CONDITION_AND + * @throws InvalidArgumentException + * @return FilterComposite + */ + public function addFilter($name, $filter, $condition = self::CONDITION_OR) + { + if (!is_callable($filter) && !($filter instanceof FilterInterface)) { + throw new InvalidArgumentException( + 'The value of ' . $name . ' should be either a callable or ' . + 'an instance of Zend\Stdlib\Hydrator\Filter\FilterInterface' + ); + } + + if ($condition === self::CONDITION_OR) { + $this->orFilter[$name] = $filter; + } elseif ($condition === self::CONDITION_AND) { + $this->andFilter[$name] = $filter; + } + + return $this; + } + + /** + * Remove a filter from the composition + * + * @param $name string Identifier for the filter + * @return FilterComposite + */ + public function removeFilter($name) + { + if (isset($this->orFilter[$name])) { + unset($this->orFilter[$name]); + } + + if (isset($this->andFilter[$name])) { + unset($this->andFilter[$name]); + } + + return $this; + } + + /** + * Check if $name has a filter registered + * + * @param $name string Identifier for the filter + * @return bool + */ + public function hasFilter($name) + { + return isset($this->orFilter[$name]) || isset($this->andFilter[$name]); + } + + /** + * Filter the composite based on the AND and OR condition + * Will return true if one from the "or conditions" and all from + * the "and condition" returns true. Otherwise false + * + * @param $property string Parameter will be e.g. Parent\Namespace\Class::method + * @return bool + */ + public function filter($property) + { + $andCount = count($this->andFilter); + $orCount = count($this->orFilter); + // return true if no filters are registered + if ($orCount === 0 && $andCount === 0) { + return true; + } elseif ($orCount === 0 && $andCount !== 0) { + $returnValue = true; + } else { + $returnValue = false; + } + + // Check if 1 from the or filters return true + foreach ($this->orFilter as $filter) { + if (is_callable($filter)) { + if ($filter($property) === true) { + $returnValue = true; + break; + } + continue; + } else { + if ($filter->filter($property) === true) { + $returnValue = true; + break; + } + } + } + + // Check if all of the and condition return true + foreach ($this->andFilter as $filter) { + if (is_callable($filter)) { + if ($filter($property) === false) { + return false; + } + continue; + } else { + if ($filter->filter($property) === false) { + return false; + } + } + } + + return $returnValue; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/FilterInterface.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/FilterInterface.php new file mode 100644 index 0000000..16df098 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/FilterInterface.php @@ -0,0 +1,21 @@ +method = $method; + $this->exclude = $exclude; + } + + public function filter($property) + { + $pos = strpos($property, '::'); + if ($pos !== false) { + $pos += 2; + } else { + $pos = 0; + } + if (substr($property, $pos) === $this->method) { + return !$this->exclude; + } + return $this->exclude; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/NumberOfParameterFilter.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/NumberOfParameterFilter.php new file mode 100644 index 0000000..1254b63 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/NumberOfParameterFilter.php @@ -0,0 +1,49 @@ +numberOfParameters = (int) $numberOfParameters; + } + + /** + * @param string $property the name of the property + * @return bool + * @throws InvalidArgumentException + */ + public function filter($property) + { + try { + $reflectionMethod = new ReflectionMethod($property); + } catch (ReflectionException $exception) { + throw new InvalidArgumentException( + "Method $property doesn't exist" + ); + } + + return $reflectionMethod->getNumberOfParameters() === $this->numberOfParameters; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/OptionalParametersFilter.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/OptionalParametersFilter.php new file mode 100644 index 0000000..ccd67ca --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Filter/OptionalParametersFilter.php @@ -0,0 +1,54 @@ +getParameters(), + function (ReflectionParameter $parameter) { + return ! $parameter->isOptional(); + } + ); + + return static::$propertiesCache[$property] = empty($mandatoryParameters); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/FilterEnabledInterface.php b/vendor/zendframework/zend-stdlib/src/Hydrator/FilterEnabledInterface.php new file mode 100644 index 0000000..380aade --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/FilterEnabledInterface.php @@ -0,0 +1,63 @@ + + * $composite->addFilter( + * "servicelocator", + * function ($property) { + * list($class, $method) = explode('::', $property); + * if ($method === 'getServiceLocator') { + * return false; + * } + * return true; + * }, + * FilterComposite::CONDITION_AND + * ); + * + * + * @param string $name Index in the composite + * @param callable|FilterInterface $filter + * @param int $condition + * @return FilterComposite + */ + public function addFilter($name, $filter, $condition = FilterComposite::CONDITION_OR); + + /** + * Check whether a specific filter exists at key $name or not + * + * @param string $name Index in the composite + * @return bool + */ + public function hasFilter($name); + + /** + * Remove a filter from the composition. + * To not extract "has" methods, you simply need to unregister it + * + * + * $filterComposite->removeFilter('has'); + * + * + * @param $name + * @return FilterComposite + */ + public function removeFilter($name); +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/HydrationInterface.php b/vendor/zendframework/zend-stdlib/src/Hydrator/HydrationInterface.php new file mode 100644 index 0000000..e7deff4 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/HydrationInterface.php @@ -0,0 +1,22 @@ +hydrator = $hydrator; + + return $this; + } + + /** + * Retrieve hydrator + * + * @param void + * @return null|HydratorInterface + * @access public + */ + public function getHydrator() + { + return $this->hydrator; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/HydratorInterface.php b/vendor/zendframework/zend-stdlib/src/Hydrator/HydratorInterface.php new file mode 100644 index 0000000..bc9983d --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/HydratorInterface.php @@ -0,0 +1,16 @@ + 'Zend\Stdlib\Hydrator\DelegatingHydrator', + ); + + /** + * Default set of adapters + * + * @var array + */ + protected $invokableClasses = array( + 'arrayserializable' => 'Zend\Stdlib\Hydrator\ArraySerializable', + 'classmethods' => 'Zend\Stdlib\Hydrator\ClassMethods', + 'objectproperty' => 'Zend\Stdlib\Hydrator\ObjectProperty', + 'reflection' => 'Zend\Stdlib\Hydrator\Reflection' + ); + + /** + * Default factory-based adapters + * + * @var array + */ + protected $factories = array( + 'Zend\Stdlib\Hydrator\DelegatingHydrator' => 'Zend\Stdlib\Hydrator\DelegatingHydratorFactory', + ); + + /** + * {@inheritDoc} + */ + public function validatePlugin($plugin) + { + if ($plugin instanceof HydratorInterface) { + // we're okay + return; + } + + throw new Exception\RuntimeException(sprintf( + 'Plugin of type %s is invalid; must implement Zend\Stdlib\Hydrator\HydratorInterface', + (is_object($plugin) ? get_class($plugin) : gettype($plugin)) + )); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/ArrayMapNamingStrategy.php b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/ArrayMapNamingStrategy.php new file mode 100644 index 0000000..962303a --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/ArrayMapNamingStrategy.php @@ -0,0 +1,51 @@ +extractionMap = $extractionMap; + $this->hydrationMap = array_flip($extractionMap); + } + + /** + * {@inheritDoc} + */ + public function hydrate($name) + { + return isset($this->hydrationMap[$name]) ? $this->hydrationMap[$name] : $name; + } + + /** + * {@inheritDoc} + */ + public function extract($name) + { + return isset($this->extractionMap[$name]) ? $this->extractionMap[$name] : $name; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/CompositeNamingStrategy.php b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/CompositeNamingStrategy.php new file mode 100644 index 0000000..0887e92 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/CompositeNamingStrategy.php @@ -0,0 +1,64 @@ +namingStrategies = array_map( + function (NamingStrategyInterface $strategy) { + // this callback is here only to ensure type-safety + return $strategy; + }, + $strategies + ); + + $this->defaultNamingStrategy = $defaultNamingStrategy ?: new IdentityNamingStrategy(); + } + + /** + * {@inheritDoc} + */ + public function extract($name) + { + $strategy = isset($this->namingStrategies[$name]) + ? $this->namingStrategies[$name] + : $this->defaultNamingStrategy; + + return $strategy->extract($name); + } + + /** + * {@inheritDoc} + */ + public function hydrate($name) + { + $strategy = isset($this->namingStrategies[$name]) + ? $this->namingStrategies[$name] + : $this->defaultNamingStrategy; + + return $strategy->hydrate($name); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/IdentityNamingStrategy.php b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/IdentityNamingStrategy.php new file mode 100644 index 0000000..ee4b328 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/IdentityNamingStrategy.php @@ -0,0 +1,29 @@ +mapping = $mapping; + $this->reverse = $reverse ?: $this->flipMapping($mapping); + } + + /** + * Safelly flip mapping array. + * + * @param array $array Array to flip + * @return array Flipped array + * @throws InvalidArgumentException + */ + protected function flipMapping(array $array) + { + array_walk($array, function ($value) { + if (!is_string($value) && !is_int($value)) { + throw new InvalidArgumentException('Mapping array can\'t be flipped because of invalid value'); + } + }); + + return array_flip($array); + } + + /** + * Converts the given name so that it can be extracted by the hydrator. + * + * @param string $name The original name + * @return mixed The hydrated name + */ + public function hydrate($name) + { + if (array_key_exists($name, $this->mapping)) { + return $this->mapping[$name]; + } + + return $name; + } + + /** + * Converts the given name so that it can be hydrated by the hydrator. + * + * @param string $name The original name + * @return mixed The extracted name + */ + public function extract($name) + { + if (array_key_exists($name, $this->reverse)) { + return $this->reverse[$name]; + } + + return $name; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/NamingStrategyInterface.php b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/NamingStrategyInterface.php new file mode 100644 index 0000000..ff99385 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategy/NamingStrategyInterface.php @@ -0,0 +1,37 @@ +getUnderscoreToStudlyCaseFilter()->filter($name); + } + + /** + * Remove capitalized letters and prepend underscores. + * + * @param string $name + * @return string + */ + public function extract($name) + { + return $this->getCamelCaseToUnderscoreFilter()->filter($name); + } + + /** + * @return FilterChain + */ + protected function getUnderscoreToStudlyCaseFilter() + { + if (static::$underscoreToStudlyCaseFilter instanceof FilterChain) { + return static::$underscoreToStudlyCaseFilter; + } + + $filter = new FilterChain(); + + $filter->attachByName('WordUnderscoreToStudlyCase'); + + return static::$underscoreToStudlyCaseFilter = $filter; + } + + /** + * @return FilterChain + */ + protected function getCamelCaseToUnderscoreFilter() + { + if (static::$camelCaseToUnderscoreFilter instanceof FilterChain) { + return static::$camelCaseToUnderscoreFilter; + } + + $filter = new FilterChain(); + + $filter->attachByName('WordCamelCaseToUnderscore'); + $filter->attachByName('StringToLower'); + + return static::$camelCaseToUnderscoreFilter = $filter; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategyEnabledInterface.php b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategyEnabledInterface.php new file mode 100644 index 0000000..f9c6288 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/NamingStrategyEnabledInterface.php @@ -0,0 +1,44 @@ +getFilter(); + + foreach ($data as $name => $value) { + // Filter keys, removing any we don't want + if (! $filter->filter($name)) { + unset($data[$name]); + continue; + } + + // Replace name if extracted differ + $extracted = $this->extractName($name, $object); + + if ($extracted !== $name) { + unset($data[$name]); + $name = $extracted; + } + + $data[$name] = $this->extractValue($name, $value, $object); + } + + return $data; + } + + /** + * {@inheritDoc} + * + * Hydrate an object by populating public properties + * + * Hydrates an object by setting public properties of the object. + * + * @throws Exception\BadMethodCallException for a non-object $object + */ + public function hydrate(array $data, $object) + { + if (!is_object($object)) { + throw new Exception\BadMethodCallException( + sprintf('%s expects the provided $object to be a PHP object)', __METHOD__) + ); + } + + $properties = & self::$skippedPropertiesCache[get_class($object)]; + + if (! isset($properties)) { + $reflection = new ReflectionClass($object); + $properties = array_fill_keys( + array_map( + function (ReflectionProperty $property) { + return $property->getName(); + }, + $reflection->getProperties( + ReflectionProperty::IS_PRIVATE + + ReflectionProperty::IS_PROTECTED + + ReflectionProperty::IS_STATIC + ) + ), + true + ); + } + + foreach ($data as $name => $value) { + $property = $this->hydrateName($name, $data); + + if (isset($properties[$property])) { + continue; + } + + $object->$property = $this->hydrateValue($property, $value, $data); + } + + return $object; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Reflection.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Reflection.php new file mode 100644 index 0000000..ea8e2d2 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Reflection.php @@ -0,0 +1,95 @@ +extractName($property->getName(), $object); + if (!$this->filterComposite->filter($propertyName)) { + continue; + } + + $value = $property->getValue($object); + $result[$propertyName] = $this->extractValue($propertyName, $value, $object); + } + + return $result; + } + + /** + * Hydrate $object with the provided $data. + * + * @param array $data + * @param object $object + * @return object + */ + public function hydrate(array $data, $object) + { + $reflProperties = self::getReflProperties($object); + foreach ($data as $key => $value) { + $name = $this->hydrateName($key, $data); + if (isset($reflProperties[$name])) { + $reflProperties[$name]->setValue($object, $this->hydrateValue($name, $value, $data)); + } + } + return $object; + } + + /** + * Get a reflection properties from in-memory cache and lazy-load if + * class has not been loaded. + * + * @param string|object $input + * @throws Exception\InvalidArgumentException + * @return \ReflectionProperty[] + */ + protected static function getReflProperties($input) + { + if (is_object($input)) { + $input = get_class($input); + } elseif (!is_string($input)) { + throw new Exception\InvalidArgumentException('Input must be a string or an object.'); + } + + if (isset(static::$reflProperties[$input])) { + return static::$reflProperties[$input]; + } + + static::$reflProperties[$input] = array(); + $reflClass = new ReflectionClass($input); + $reflProperties = $reflClass->getProperties(); + + foreach ($reflProperties as $property) { + $property->setAccessible(true); + static::$reflProperties[$input][$property->getName()] = $property; + } + + return static::$reflProperties[$input]; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/BooleanStrategy.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/BooleanStrategy.php new file mode 100644 index 0000000..3c29231 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/BooleanStrategy.php @@ -0,0 +1,106 @@ +trueValue = $trueValue; + $this->falseValue = $falseValue; + } + + /** + * Converts the given value so that it can be extracted by the hydrator. + * + * @param bool $value The original value. + * @throws InvalidArgumentException + * @return int|string Returns the value that should be extracted. + */ + public function extract($value) + { + if (!is_bool($value)) { + throw new InvalidArgumentException(sprintf( + 'Unable to extract. Expected bool. %s was given.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + return $value === true ? $this->trueValue : $this->falseValue; + } + + /** + * Converts the given value so that it can be hydrated by the hydrator. + * + * @param int|string $value The original value. + * @throws InvalidArgumentException + * @return bool Returns the value that should be hydrated. + */ + public function hydrate($value) + { + if (!is_string($value) && !is_int($value)) { + throw new InvalidArgumentException(sprintf( + 'Unable to hydrate. Expected string or int. %s was given.', + is_object($value) ? get_class($value) : gettype($value) + )); + } + + if ($value === $this->trueValue) { + return true; + } + + if ($value === $this->falseValue) { + return false; + } + + throw new InvalidArgumentException(sprintf( + 'Unexpected value %s can\'t be hydrated. Expect %s or %s as Value.', + $value, + $this->trueValue, + $this->falseValue + )); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/ClosureStrategy.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/ClosureStrategy.php new file mode 100644 index 0000000..bf456a7 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/ClosureStrategy.php @@ -0,0 +1,102 @@ +addStrategy('category', new ClosureStrategy( + * function (Category $value) { + * return (int) $value->id; + * }, + * function ($value) { + * return new Category((int) $value); + * } + * )); + * + * @param callable $extractFunc - anonymous function, that extract values + * from object + * @param callable $hydrateFunc - anonymous function, that hydrate values + * into object + */ + public function __construct($extractFunc = null, $hydrateFunc = null) + { + if (isset($extractFunc)) { + if (!is_callable($extractFunc)) { + throw new \Exception('$extractFunc must be callable'); + } + + $this->extractFunc = $extractFunc; + } else { + $this->extractFunc = function ($value) { + return $value; + }; + } + + if (isset($hydrateFunc)) { + if (!is_callable($hydrateFunc)) { + throw new \Exception('$hydrateFunc must be callable'); + } + + $this->hydrateFunc = $hydrateFunc; + } else { + $this->hydrateFunc = function ($value) { + return $value; + }; + } + } + + /** + * Converts the given value so that it can be extracted by the hydrator. + * + * @param mixed $value The original value. + * @param array $object The object is optionally provided as context. + * @return mixed Returns the value that should be extracted. + */ + public function extract($value, $object = null) + { + $func = $this->extractFunc; + + return $func($value, $object); + } + + /** + * Converts the given value so that it can be hydrated by the hydrator. + * + * @param mixed $value The original value. + * @param array $data The whole data is optionally provided as context. + * @return mixed Returns the value that should be hydrated. + */ + public function hydrate($value, $data = null) + { + $func = $this->hydrateFunc; + + return $func($value, $data); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/DateTimeFormatterStrategy.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/DateTimeFormatterStrategy.php new file mode 100644 index 0000000..62d92c5 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/DateTimeFormatterStrategy.php @@ -0,0 +1,80 @@ +format = (string) $format; + $this->timezone = $timezone; + } + + /** + * {@inheritDoc} + * + * Converts to date time string + * + * @param mixed|DateTime $value + * + * @return mixed|string + */ + public function extract($value) + { + if ($value instanceof DateTime) { + return $value->format($this->format); + } + + return $value; + } + + /** + * Converts date time string to DateTime instance for injecting to object + * + * {@inheritDoc} + * + * @param mixed|string $value + * + * @return mixed|DateTime + */ + public function hydrate($value) + { + if ($value === '' || $value === null) { + return; + } + + if ($this->timezone) { + $hydrated = DateTime::createFromFormat($this->format, $value, $this->timezone); + } else { + $hydrated = DateTime::createFromFormat($this->format, $value); + } + + return $hydrated ?: $value; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/DefaultStrategy.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/DefaultStrategy.php new file mode 100644 index 0000000..b2c5c29 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/DefaultStrategy.php @@ -0,0 +1,35 @@ +setValueDelimiter($delimiter); + + $this->explodeLimit = ($explodeLimit === null) ? null : (int) $explodeLimit; + } + + /** + * Sets the delimiter string that the values will be split upon + * + * @param string $delimiter + * @return self + */ + private function setValueDelimiter($delimiter) + { + if (!is_string($delimiter)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects Delimiter to be string, %s provided instead', + __METHOD__, + is_object($delimiter) ? get_class($delimiter) : gettype($delimiter) + )); + } + + if (empty($delimiter)) { + throw new Exception\InvalidArgumentException('Delimiter cannot be empty.'); + } + + $this->valueDelimiter = $delimiter; + } + + /** + * {@inheritDoc} + * + * Split a string by delimiter + * + * @param string|null $value + * + * @return string[] + * + * @throws Exception\InvalidArgumentException + */ + public function hydrate($value) + { + if (null === $value) { + return array(); + } + + if (!(is_string($value) || is_numeric($value))) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects argument 1 to be string, %s provided instead', + __METHOD__, + is_object($value) ? get_class($value) : gettype($value) + )); + } + + if ($this->explodeLimit !== null) { + return explode($this->valueDelimiter, $value, $this->explodeLimit); + } + + return explode($this->valueDelimiter, $value); + } + + /** + * {@inheritDoc} + * + * Join array elements with delimiter + * + * @param string[] $value The original value. + * + * @return string|null + */ + public function extract($value) + { + if (!is_array($value)) { + throw new Exception\InvalidArgumentException(sprintf( + '%s expects argument 1 to be array, %s provided instead', + __METHOD__, + is_object($value) ? get_class($value) : gettype($value) + )); + } + + return empty($value) ? null : implode($this->valueDelimiter, $value); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/SerializableStrategy.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/SerializableStrategy.php new file mode 100644 index 0000000..260efa3 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/SerializableStrategy.php @@ -0,0 +1,123 @@ +setSerializer($serializer); + if ($serializerOptions) { + $this->setSerializerOptions($serializerOptions); + } + } + + /** + * Serialize the given value so that it can be extracted by the hydrator. + * + * @param mixed $value The original value. + * @return mixed Returns the value that should be extracted. + */ + public function extract($value) + { + $serializer = $this->getSerializer(); + return $serializer->serialize($value); + } + + /** + * Unserialize the given value so that it can be hydrated by the hydrator. + * + * @param mixed $value The original value. + * @return mixed Returns the value that should be hydrated. + */ + public function hydrate($value) + { + $serializer = $this->getSerializer(); + return $serializer->unserialize($value); + } + + /** + * Set serializer + * + * @param string|SerializerAdapter $serializer + * @return SerializableStrategy + */ + public function setSerializer($serializer) + { + if (!is_string($serializer) && !$serializer instanceof SerializerAdapter) { + throw new InvalidArgumentException(sprintf( + '%s expects either a string serializer name or Zend\Serializer\Adapter\AdapterInterface instance; ' + . 'received "%s"', + __METHOD__, + (is_object($serializer) ? get_class($serializer) : gettype($serializer)) + )); + } + $this->serializer = $serializer; + return $this; + } + + /** + * Get serializer + * + * @return SerializerAdapter + */ + public function getSerializer() + { + if (is_string($this->serializer)) { + $options = $this->getSerializerOptions(); + $this->setSerializer(SerializerFactory::factory($this->serializer, $options)); + } elseif (null === $this->serializer) { + $this->setSerializer(SerializerFactory::getDefaultAdapter()); + } + + return $this->serializer; + } + + /** + * Set configuration options for instantiating a serializer adapter + * + * @param mixed $serializerOptions + * @return SerializableStrategy + */ + public function setSerializerOptions($serializerOptions) + { + $this->serializerOptions = $serializerOptions; + return $this; + } + + /** + * Get configuration options for instantiating a serializer adapter + * + * @return mixed + */ + public function getSerializerOptions() + { + return $this->serializerOptions; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyChain.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyChain.php new file mode 100644 index 0000000..a9316bb --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyChain.php @@ -0,0 +1,73 @@ +extractionStrategies = array_map( + function (StrategyInterface $strategy) { + // this callback is here only to ensure type-safety + return $strategy; + }, + $extractionStrategies + ); + + $this->hydrationStrategies = array_reverse($extractionStrategies); + } + + /** + * {@inheritDoc} + */ + public function extract($value) + { + foreach ($this->extractionStrategies as $strategy) { + $value = $strategy->extract($value); + } + + return $value; + } + + /** + * {@inheritDoc} + */ + public function hydrate($value) + { + foreach ($this->hydrationStrategies as $strategy) { + $value = $strategy->hydrate($value); + } + + return $value; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyInterface.php b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyInterface.php new file mode 100644 index 0000000..562ec4b --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Hydrator/Strategy/StrategyInterface.php @@ -0,0 +1,34 @@ +metadata[$spec] = $value; + return $this; + } + if (!is_array($spec) && !$spec instanceof Traversable) { + throw new Exception\InvalidArgumentException(sprintf( + 'Expected a string, array, or Traversable argument in first position; received "%s"', + (is_object($spec) ? get_class($spec) : gettype($spec)) + )); + } + foreach ($spec as $key => $value) { + $this->metadata[$key] = $value; + } + return $this; + } + + /** + * Retrieve all metadata or a single metadatum as specified by key + * + * @param null|string|int $key + * @param null|mixed $default + * @throws Exception\InvalidArgumentException + * @return mixed + */ + public function getMetadata($key = null, $default = null) + { + if (null === $key) { + return $this->metadata; + } + + if (!is_scalar($key)) { + throw new Exception\InvalidArgumentException('Non-scalar argument provided for key'); + } + + if (array_key_exists($key, $this->metadata)) { + return $this->metadata[$key]; + } + + return $default; + } + + /** + * Set message content + * + * @param mixed $value + * @return Message + */ + public function setContent($value) + { + $this->content = $value; + return $this; + } + + /** + * Get message content + * + * @return mixed + */ + public function getContent() + { + return $this->content; + } + + /** + * @return string + */ + public function toString() + { + $request = ''; + foreach ($this->getMetadata() as $key => $value) { + $request .= sprintf( + "%s: %s\r\n", + (string) $key, + (string) $value + ); + } + $request .= "\r\n" . $this->getContent(); + return $request; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/MessageInterface.php b/vendor/zendframework/zend-stdlib/src/MessageInterface.php new file mode 100644 index 0000000..28d8857 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/MessageInterface.php @@ -0,0 +1,44 @@ +exchangeArray($values); + } + + /** + * Populate from query string + * + * @param string $string + * @return void + */ + public function fromString($string) + { + $array = array(); + parse_str($string, $array); + $this->fromArray($array); + } + + /** + * Serialize to native PHP array + * + * @return array + */ + public function toArray() + { + return $this->getArrayCopy(); + } + + /** + * Serialize to query string + * + * @return string + */ + public function toString() + { + return http_build_query($this); + } + + /** + * Retrieve by key + * + * Returns null if the key does not exist. + * + * @param string $name + * @return mixed + */ + public function offsetGet($name) + { + if ($this->offsetExists($name)) { + return parent::offsetGet($name); + } + return; + } + + /** + * @param string $name + * @param mixed $default optional default value + * @return mixed + */ + public function get($name, $default = null) + { + if ($this->offsetExists($name)) { + return parent::offsetGet($name); + } + return $default; + } + + /** + * @param string $name + * @param mixed $value + * @return Parameters + */ + public function set($name, $value) + { + $this[$name] = $value; + return $this; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/ParametersInterface.php b/vendor/zendframework/zend-stdlib/src/ParametersInterface.php new file mode 100644 index 0000000..feeda58 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/ParametersInterface.php @@ -0,0 +1,86 @@ +sorted = false; + $this->count++; + + $this->items[$name] = array( + 'data' => $value, + 'priority' => (int) $priority, + 'serial' => $this->serial++, + ); + } + + /** + * @param string $name + * @param int $priority + * + * @return $this + * + * @throws \Exception + */ + public function setPriority($name, $priority) + { + if (!isset($this->items[$name])) { + throw new \Exception("item $name not found"); + } + + $this->items[$name]['priority'] = (int) $priority; + $this->sorted = false; + + return $this; + } + + /** + * Remove a item. + * + * @param string $name + * @return void + */ + public function remove($name) + { + if (isset($this->items[$name])) { + $this->count--; + } + + unset($this->items[$name]); + } + + /** + * Remove all items. + * + * @return void + */ + public function clear() + { + $this->items = array(); + $this->serial = 0; + $this->count = 0; + $this->sorted = false; + } + + /** + * Get a item. + * + * @param string $name + * @return mixed + */ + public function get($name) + { + if (!isset($this->items[$name])) { + return; + } + + return $this->items[$name]['data']; + } + + /** + * Sort all items. + * + * @return void + */ + protected function sort() + { + if (!$this->sorted) { + uasort($this->items, array($this, 'compare')); + $this->sorted = true; + } + } + + /** + * Compare the priority of two items. + * + * @param array $item1, + * @param array $item2 + * @return int + */ + protected function compare(array $item1, array $item2) + { + return ($item1['priority'] === $item2['priority']) + ? ($item1['serial'] > $item2['serial'] ? -1 : 1) * $this->isLIFO + : ($item1['priority'] > $item2['priority'] ? -1 : 1); + } + + /** + * Get/Set serial order mode + * + * @param bool|null $flag + * + * @return bool + */ + public function isLIFO($flag = null) + { + if ($flag !== null) { + $isLifo = $flag === true ? 1 : -1; + + if ($isLifo !== $this->isLIFO) { + $this->isLIFO = $isLifo; + $this->sorted = false; + } + } + + return 1 === $this->isLIFO; + } + + /** + * {@inheritDoc} + */ + public function rewind() + { + $this->sort(); + reset($this->items); + } + + /** + * {@inheritDoc} + */ + public function current() + { + $this->sorted || $this->sort(); + $node = current($this->items); + + return $node ? $node['data'] : false; + } + + /** + * {@inheritDoc} + */ + public function key() + { + $this->sorted || $this->sort(); + return key($this->items); + } + + /** + * {@inheritDoc} + */ + public function next() + { + $node = next($this->items); + + return $node ? $node['data'] : false; + } + + /** + * {@inheritDoc} + */ + public function valid() + { + return current($this->items) !== false; + } + + /** + * @return self + */ + public function getIterator() + { + return clone $this; + } + + /** + * {@inheritDoc} + */ + public function count() + { + return $this->count; + } + + /** + * Return list as array + * + * @param int $flag + * + * @return array + */ + public function toArray($flag = self::EXTR_DATA) + { + $this->sort(); + + if ($flag == self::EXTR_BOTH) { + return $this->items; + } + + return array_map( + function ($item) use ($flag) { + return ($flag == PriorityList::EXTR_PRIORITY) ? $item['priority'] : $item['data']; + }, + $this->items + ); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/PriorityQueue.php b/vendor/zendframework/zend-stdlib/src/PriorityQueue.php new file mode 100644 index 0000000..1baf44e --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/PriorityQueue.php @@ -0,0 +1,301 @@ +items[] = array( + 'data' => $data, + 'priority' => $priority, + ); + $this->getQueue()->insert($data, $priority); + return $this; + } + + /** + * Remove an item from the queue + * + * This is different than {@link extract()}; its purpose is to dequeue an + * item. + * + * This operation is potentially expensive, as it requires + * re-initialization and re-population of the inner queue. + * + * Note: this removes the first item matching the provided item found. If + * the same item has been added multiple times, it will not remove other + * instances. + * + * @param mixed $datum + * @return bool False if the item was not found, true otherwise. + */ + public function remove($datum) + { + $found = false; + foreach ($this->items as $key => $item) { + if ($item['data'] === $datum) { + $found = true; + break; + } + } + if ($found) { + unset($this->items[$key]); + $this->queue = null; + + if (!$this->isEmpty()) { + $queue = $this->getQueue(); + foreach ($this->items as $item) { + $queue->insert($item['data'], $item['priority']); + } + } + return true; + } + return false; + } + + /** + * Is the queue empty? + * + * @return bool + */ + public function isEmpty() + { + return (0 === $this->count()); + } + + /** + * How many items are in the queue? + * + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * Peek at the top node in the queue, based on priority. + * + * @return mixed + */ + public function top() + { + return $this->getIterator()->top(); + } + + /** + * Extract a node from the inner queue and sift up + * + * @return mixed + */ + public function extract() + { + return $this->getQueue()->extract(); + } + + /** + * Retrieve the inner iterator + * + * SplPriorityQueue acts as a heap, which typically implies that as items + * are iterated, they are also removed. This does not work for situations + * where the queue may be iterated multiple times. As such, this class + * aggregates the values, and also injects an SplPriorityQueue. This method + * retrieves the inner queue object, and clones it for purposes of + * iteration. + * + * @return SplPriorityQueue + */ + public function getIterator() + { + $queue = $this->getQueue(); + return clone $queue; + } + + /** + * Serialize the data structure + * + * @return string + */ + public function serialize() + { + return serialize($this->items); + } + + /** + * Unserialize a string into a PriorityQueue object + * + * Serialization format is compatible with {@link Zend\Stdlib\SplPriorityQueue} + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + foreach (unserialize($data) as $item) { + $this->insert($item['data'], $item['priority']); + } + } + + /** + * Serialize to an array + * + * By default, returns only the item data, and in the order registered (not + * sorted). You may provide one of the EXTR_* flags as an argument, allowing + * the ability to return priorities or both data and priority. + * + * @param int $flag + * @return array + */ + public function toArray($flag = self::EXTR_DATA) + { + switch ($flag) { + case self::EXTR_BOTH: + return $this->items; + case self::EXTR_PRIORITY: + return array_map(function ($item) { + return $item['priority']; + }, $this->items); + case self::EXTR_DATA: + default: + return array_map(function ($item) { + return $item['data']; + }, $this->items); + } + } + + /** + * Specify the internal queue class + * + * Please see {@link getIterator()} for details on the necessity of an + * internal queue class. The class provided should extend SplPriorityQueue. + * + * @param string $class + * @return PriorityQueue + */ + public function setInternalQueueClass($class) + { + $this->queueClass = (string) $class; + return $this; + } + + /** + * Does the queue contain the given datum? + * + * @param mixed $datum + * @return bool + */ + public function contains($datum) + { + foreach ($this->items as $item) { + if ($item['data'] === $datum) { + return true; + } + } + return false; + } + + /** + * Does the queue have an item with the given priority? + * + * @param int $priority + * @return bool + */ + public function hasPriority($priority) + { + foreach ($this->items as $item) { + if ($item['priority'] === $priority) { + return true; + } + } + return false; + } + + /** + * Get the inner priority queue instance + * + * @throws Exception\DomainException + * @return SplPriorityQueue + */ + protected function getQueue() + { + if (null === $this->queue) { + $this->queue = new $this->queueClass(); + if (!$this->queue instanceof \SplPriorityQueue) { + throw new Exception\DomainException(sprintf( + 'PriorityQueue expects an internal queue of type SplPriorityQueue; received "%s"', + get_class($this->queue) + )); + } + } + return $this->queue; + } + + /** + * Add support for deep cloning + * + * @return void + */ + public function __clone() + { + if (null !== $this->queue) { + $this->queue = clone $this->queue; + } + } +} diff --git a/vendor/zendframework/zend-stdlib/src/Request.php b/vendor/zendframework/zend-stdlib/src/Request.php new file mode 100644 index 0000000..7c08403 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/Request.php @@ -0,0 +1,15 @@ +serial--); + } + parent::insert($datum, $priority); + } + + /** + * Serialize to an array + * + * Array will be priority => data pairs + * + * @return array + */ + public function toArray() + { + $array = array(); + foreach (clone $this as $item) { + $array[] = $item; + } + return $array; + } + + /** + * Serialize + * + * @return string + */ + public function serialize() + { + $clone = clone $this; + $clone->setExtractFlags(self::EXTR_BOTH); + + $data = array(); + foreach ($clone as $item) { + $data[] = $item; + } + + return serialize($data); + } + + /** + * Deserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + foreach (unserialize($data) as $item) { + $this->insert($item['data'], $item['priority']); + } + } +} diff --git a/vendor/zendframework/zend-stdlib/src/SplQueue.php b/vendor/zendframework/zend-stdlib/src/SplQueue.php new file mode 100644 index 0000000..029bc9f --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/SplQueue.php @@ -0,0 +1,55 @@ +toArray()); + } + + /** + * Unserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + foreach (unserialize($data) as $item) { + $this->push($item); + } + } +} diff --git a/vendor/zendframework/zend-stdlib/src/SplStack.php b/vendor/zendframework/zend-stdlib/src/SplStack.php new file mode 100644 index 0000000..fac77a5 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/SplStack.php @@ -0,0 +1,55 @@ +toArray()); + } + + /** + * Unserialize + * + * @param string $data + * @return void + */ + public function unserialize($data) + { + foreach (unserialize($data) as $item) { + $this->unshift($item); + } + } +} diff --git a/vendor/zendframework/zend-stdlib/src/StringUtils.php b/vendor/zendframework/zend-stdlib/src/StringUtils.php new file mode 100644 index 0000000..9bbb4a4 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/StringUtils.php @@ -0,0 +1,187 @@ +setEncoding($encoding, $convertEncoding); + return $wrapper; + } + } + + throw new Exception\RuntimeException( + 'No wrapper found supporting "' . $encoding . '"' + . (($convertEncoding !== null) ? ' and "' . $convertEncoding . '"' : '') + ); + } + + /** + * Get a list of all known single-byte character encodings + * + * @return string[] + */ + public static function getSingleByteEncodings() + { + return static::$singleByteEncodings; + } + + /** + * Check if a given encoding is a known single-byte character encoding + * + * @param string $encoding + * @return bool + */ + public static function isSingleByteEncoding($encoding) + { + return in_array(strtoupper($encoding), static::$singleByteEncodings); + } + + /** + * Check if a given string is valid UTF-8 encoded + * + * @param string $str + * @return bool + */ + public static function isValidUtf8($str) + { + return is_string($str) && ($str === '' || preg_match('/^./su', $str) == 1); + } + + /** + * Is PCRE compiled with Unicode support? + * + * @return bool + */ + public static function hasPcreUnicodeSupport() + { + if (static::$hasPcreUnicodeSupport === null) { + ErrorHandler::start(); + static::$hasPcreUnicodeSupport = defined('PREG_BAD_UTF8_OFFSET_ERROR') && preg_match('/\pL/u', 'a') == 1; + ErrorHandler::stop(); + } + return static::$hasPcreUnicodeSupport; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/StringWrapper/AbstractStringWrapper.php b/vendor/zendframework/zend-stdlib/src/StringWrapper/AbstractStringWrapper.php new file mode 100644 index 0000000..3e395cc --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/StringWrapper/AbstractStringWrapper.php @@ -0,0 +1,269 @@ +convertEncoding = $convertEncodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Get the defined character encoding to work with + * + * @return string + * @throws Exception\LogicException If no encoding was defined + */ + public function getEncoding() + { + return $this->encoding; + } + + /** + * Get the defined character encoding to convert to + * + * @return string|null + */ + public function getConvertEncoding() + { + return $this->convertEncoding; + } + + /** + * Convert a string from defined character encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $from = $reverse ? $convertEncoding : $encoding; + $to = $reverse ? $encoding : $convertEncoding; + throw new Exception\RuntimeException(sprintf( + 'Converting from "%s" to "%s" isn\'t supported by this string wrapper', + $from, + $to + )); + } + + /** + * Wraps a string to a given number of characters + * + * @param string $string + * @param int $width + * @param string $break + * @param bool $cut + * @return string|false + */ + public function wordWrap($string, $width = 75, $break = "\n", $cut = false) + { + $string = (string) $string; + if ($string === '') { + return ''; + } + + $break = (string) $break; + if ($break === '') { + throw new Exception\InvalidArgumentException('Break string cannot be empty'); + } + + $width = (int) $width; + if ($width === 0 && $cut) { + throw new Exception\InvalidArgumentException('Cannot force cut when width is zero'); + } + + if (StringUtils::isSingleByteEncoding($this->getEncoding())) { + return wordwrap($string, $width, $break, $cut); + } + + $stringWidth = $this->strlen($string); + $breakWidth = $this->strlen($break); + + $result = ''; + $lastStart = $lastSpace = 0; + + for ($current = 0; $current < $stringWidth; $current++) { + $char = $this->substr($string, $current, 1); + + $possibleBreak = $char; + if ($breakWidth !== 1) { + $possibleBreak = $this->substr($string, $current, $breakWidth); + } + + if ($possibleBreak === $break) { + $result .= $this->substr($string, $lastStart, $current - $lastStart + $breakWidth); + $current += $breakWidth - 1; + $lastStart = $lastSpace = $current + 1; + continue; + } + + if ($char === ' ') { + if ($current - $lastStart >= $width) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $current + 1; + } + + $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $cut && $lastStart >= $lastSpace) { + $result .= $this->substr($string, $lastStart, $current - $lastStart) . $break; + $lastStart = $lastSpace = $current; + continue; + } + + if ($current - $lastStart >= $width && $lastStart < $lastSpace) { + $result .= $this->substr($string, $lastStart, $lastSpace - $lastStart) . $break; + $lastStart = $lastSpace = $lastSpace + 1; + continue; + } + } + + if ($lastStart !== $current) { + $result .= $this->substr($string, $lastStart, $current - $lastStart); + } + + return $result; + } + + /** + * Pad a string to a certain length with another string + * + * @param string $input + * @param int $padLength + * @param string $padString + * @param int $padType + * @return string + */ + public function strPad($input, $padLength, $padString = ' ', $padType = STR_PAD_RIGHT) + { + if (StringUtils::isSingleByteEncoding($this->getEncoding())) { + return str_pad($input, $padLength, $padString, $padType); + } + + $lengthOfPadding = $padLength - $this->strlen($input); + if ($lengthOfPadding <= 0) { + return $input; + } + + $padStringLength = $this->strlen($padString); + if ($padStringLength === 0) { + return $input; + } + + $repeatCount = floor($lengthOfPadding / $padStringLength); + + if ($padType === STR_PAD_BOTH) { + $repeatCountLeft = $repeatCountRight = ($repeatCount - $repeatCount % 2) / 2; + + $lastStringLength = $lengthOfPadding - 2 * $repeatCountLeft * $padStringLength; + $lastStringLeftLength = $lastStringRightLength = floor($lastStringLength / 2); + $lastStringRightLength += $lastStringLength % 2; + + $lastStringLeft = $this->substr($padString, 0, $lastStringLeftLength); + $lastStringRight = $this->substr($padString, 0, $lastStringRightLength); + + return str_repeat($padString, $repeatCountLeft) . $lastStringLeft + . $input + . str_repeat($padString, $repeatCountRight) . $lastStringRight; + } + + $lastString = $this->substr($padString, 0, $lengthOfPadding % $padStringLength); + + if ($padType === STR_PAD_LEFT) { + return str_repeat($padString, $repeatCount) . $lastString . $input; + } + + return $input . str_repeat($padString, $repeatCount) . $lastString; + } +} diff --git a/vendor/zendframework/zend-stdlib/src/StringWrapper/Iconv.php b/vendor/zendframework/zend-stdlib/src/StringWrapper/Iconv.php new file mode 100644 index 0000000..04930ff --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/StringWrapper/Iconv.php @@ -0,0 +1,289 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return iconv_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return iconv_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + + // automatically add "//IGNORE" to not stop converting on invalid characters + // invalid characters triggers a notice anyway + return iconv($fromEncoding, $toEncoding . '//IGNORE', $str); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/StringWrapper/Intl.php b/vendor/zendframework/zend-stdlib/src/StringWrapper/Intl.php new file mode 100644 index 0000000..1b01277 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/StringWrapper/Intl.php @@ -0,0 +1,88 @@ +getEncoding()); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return mb_substr($str, $offset, $length, $this->getEncoding()); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return mb_strpos($haystack, $needle, $offset, $this->getEncoding()); + } + + /** + * Convert a string from defined encoding to the defined convert encoding + * + * @param string $str + * @param bool $reverse + * @return string|false + */ + public function convert($str, $reverse = false) + { + $encoding = $this->getEncoding(); + $convertEncoding = $this->getConvertEncoding(); + + if ($convertEncoding === null) { + throw new Exception\LogicException( + 'No convert encoding defined' + ); + } + + if ($encoding === $convertEncoding) { + return $str; + } + + $fromEncoding = $reverse ? $convertEncoding : $encoding; + $toEncoding = $reverse ? $encoding : $convertEncoding; + return mb_convert_encoding($str, $toEncoding, $fromEncoding); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/StringWrapper/Native.php b/vendor/zendframework/zend-stdlib/src/StringWrapper/Native.php new file mode 100644 index 0000000..38b3c10 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/StringWrapper/Native.php @@ -0,0 +1,134 @@ +convertEncoding = $encodingUpper; + } + + if ($convertEncoding !== null) { + if ($encodingUpper !== strtoupper($convertEncoding)) { + throw new Exception\InvalidArgumentException( + 'Wrapper doesn\'t support to convert between character encodings' + ); + } + + $this->convertEncoding = $encodingUpper; + } else { + $this->convertEncoding = null; + } + $this->encoding = $encodingUpper; + + return $this; + } + + /** + * Returns the length of the given string + * + * @param string $str + * @return int|false + */ + public function strlen($str) + { + return strlen($str); + } + + /** + * Returns the portion of string specified by the start and length parameters + * + * @param string $str + * @param int $offset + * @param int|null $length + * @return string|false + */ + public function substr($str, $offset = 0, $length = null) + { + return substr($str, $offset, $length); + } + + /** + * Find the position of the first occurrence of a substring in a string + * + * @param string $haystack + * @param string $needle + * @param int $offset + * @return int|false + */ + public function strpos($haystack, $needle, $offset = 0) + { + return strpos($haystack, $needle, $offset); + } +} diff --git a/vendor/zendframework/zend-stdlib/src/StringWrapper/StringWrapperInterface.php b/vendor/zendframework/zend-stdlib/src/StringWrapper/StringWrapperInterface.php new file mode 100644 index 0000000..f25b325 --- /dev/null +++ b/vendor/zendframework/zend-stdlib/src/StringWrapper/StringWrapperInterface.php @@ -0,0 +1,111 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/functions_display.' . $phpEx); + +// Start session +$user->session_begin(); +$auth->acl($user->data); + +// Start initial var setup +$forum_id = $request->variable('f', 0); +$mark_read = $request->variable('mark', ''); +$start = $request->variable('start', 0); + +$default_sort_days = (!empty($user->data['user_topic_show_days'])) ? $user->data['user_topic_show_days'] : 0; +$default_sort_key = (!empty($user->data['user_topic_sortby_type'])) ? $user->data['user_topic_sortby_type'] : 't'; +$default_sort_dir = (!empty($user->data['user_topic_sortby_dir'])) ? $user->data['user_topic_sortby_dir'] : 'd'; + +$sort_days = $request->variable('st', $default_sort_days); +$sort_key = $request->variable('sk', $default_sort_key); +$sort_dir = $request->variable('sd', $default_sort_dir); + +/* @var $pagination \phpbb\pagination */ +$pagination = $phpbb_container->get('pagination'); + +// Check if the user has actually sent a forum ID with his/her request +// If not give them a nice error page. +if (!$forum_id) +{ + trigger_error('NO_FORUM'); +} + +$sql_from = FORUMS_TABLE . ' f'; +$lastread_select = ''; + +// Grab appropriate forum data +if ($config['load_db_lastread'] && $user->data['is_registered']) +{ + $sql_from .= ' LEFT JOIN ' . FORUMS_TRACK_TABLE . ' ft ON (ft.user_id = ' . $user->data['user_id'] . ' + AND ft.forum_id = f.forum_id)'; + $lastread_select .= ', ft.mark_time'; +} + +if ($user->data['is_registered']) +{ + $sql_from .= ' LEFT JOIN ' . FORUMS_WATCH_TABLE . ' fw ON (fw.forum_id = f.forum_id AND fw.user_id = ' . $user->data['user_id'] . ')'; + $lastread_select .= ', fw.notify_status'; +} + +$sql = "SELECT f.* $lastread_select + FROM $sql_from + WHERE f.forum_id = $forum_id"; +$result = $db->sql_query($sql); +$forum_data = $db->sql_fetchrow($result); +$db->sql_freeresult($result); + +if (!$forum_data) +{ + trigger_error('NO_FORUM'); +} + + +// Configure style, language, etc. +$user->setup('viewforum', $forum_data['forum_style']); + +// Redirect to login upon emailed notification links +if (isset($_GET['e']) && !$user->data['is_registered']) +{ + login_box('', $user->lang['LOGIN_NOTIFY_FORUM']); +} + +// Permissions check +if (!$auth->acl_gets('f_list', 'f_list_topics', 'f_read', $forum_id) || ($forum_data['forum_type'] == FORUM_LINK && $forum_data['forum_link'] && !$auth->acl_get('f_read', $forum_id))) +{ + if ($user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + trigger_error('SORRY_AUTH_READ'); + } + + login_box('', $user->lang['LOGIN_VIEWFORUM']); +} + +// Forum is passworded ... check whether access has been granted to this +// user this session, if not show login box +if ($forum_data['forum_password']) +{ + login_forum_box($forum_data); +} + +// Is this forum a link? ... User got here either because the +// number of clicks is being tracked or they guessed the id +if ($forum_data['forum_type'] == FORUM_LINK && $forum_data['forum_link']) +{ + // Does it have click tracking enabled? + if ($forum_data['forum_flags'] & FORUM_FLAG_LINK_TRACK) + { + $sql = 'UPDATE ' . FORUMS_TABLE . ' + SET forum_posts_approved = forum_posts_approved + 1 + WHERE forum_id = ' . $forum_id; + $db->sql_query($sql); + } + + // We redirect to the url. The third parameter indicates that external redirects are allowed. + redirect($forum_data['forum_link'], false, true); + return; +} + +// Build navigation links +generate_forum_nav($forum_data); + +// Forum Rules +if ($auth->acl_get('f_read', $forum_id)) +{ + generate_forum_rules($forum_data); +} + +// Do we have subforums? +$active_forum_ary = $moderators = array(); + +if ($forum_data['left_id'] != $forum_data['right_id'] - 1) +{ + list($active_forum_ary, $moderators) = display_forums($forum_data, $config['load_moderators'], $config['load_moderators']); +} +else +{ + $template->assign_var('S_HAS_SUBFORUM', false); + if ($config['load_moderators']) + { + get_moderators($moderators, $forum_id); + } +} + +// Is a forum specific topic count required? +if ($forum_data['forum_topics_per_page']) +{ + $config['topics_per_page'] = $forum_data['forum_topics_per_page']; +} + +/* @var $phpbb_content_visibility \phpbb\content_visibility */ +$phpbb_content_visibility = $phpbb_container->get('content.visibility'); + +// Dump out the page header and load viewforum template +$topics_count = $phpbb_content_visibility->get_count('forum_topics', $forum_data, $forum_id); +$start = $pagination->validate_start($start, $config['topics_per_page'], $topics_count); + +$page_title = $forum_data['forum_name'] . ($start ? ' - ' . $user->lang('PAGE_TITLE_NUMBER', $pagination->get_on_page($config['topics_per_page'], $start)) : ''); + +/** +* You can use this event to modify the page title of the viewforum page +* +* @event core.viewforum_modify_page_title +* @var string page_title Title of the viewforum page +* @var array forum_data Array with forum data +* @var int forum_id The forum ID +* @var int start Start offset used to calculate the page +* @since 3.2.2-RC1 +*/ +$vars = array('page_title', 'forum_data', 'forum_id', 'start'); +extract($phpbb_dispatcher->trigger_event('core.viewforum_modify_page_title', compact($vars))); + +page_header($page_title, true, $forum_id); + +$template->set_filenames(array( + 'body' => 'viewforum_body.html') +); + +make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_id); + +$template->assign_vars(array( + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id" . (($start == 0) ? '' : "&start=$start")), +)); + +// Not postable forum or showing active topics? +if (!($forum_data['forum_type'] == FORUM_POST || (($forum_data['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS) && $forum_data['forum_type'] == FORUM_CAT))) +{ + page_footer(); +} + +// Ok, if someone has only list-access, we only display the forum list. +// We also make this circumstance available to the template in case we want to display a notice. ;) +if (!$auth->acl_gets('f_read', 'f_list_topics', $forum_id)) +{ + // Add form token for login box + add_form_key('login', '_LOGIN'); + + $template->assign_vars(array( + 'S_NO_READ_ACCESS' => true, + )); + + page_footer(); +} + +// Handle marking posts +if ($mark_read == 'topics') +{ + $token = $request->variable('hash', ''); + if (check_link_hash($token, 'global')) + { + markread('topics', array($forum_id), false, $request->variable('mark_time', 0)); + } + $redirect_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id); + meta_refresh(3, $redirect_url); + + if ($request->is_ajax()) + { + // Tell the ajax script what language vars and URL need to be replaced + $data = array( + 'NO_UNREAD_POSTS' => $user->lang['NO_UNREAD_POSTS'], + 'UNREAD_POSTS' => $user->lang['UNREAD_POSTS'], + 'U_MARK_TOPICS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . "&f=$forum_id&mark=topics&mark_time=" . time()) : '', + 'MESSAGE_TITLE' => $user->lang['INFORMATION'], + 'MESSAGE_TEXT' => $user->lang['TOPICS_MARKED'] + ); + $json_response = new \phpbb\json_response(); + $json_response->send($data); + } + + trigger_error($user->lang['TOPICS_MARKED'] . '

    ' . sprintf($user->lang['RETURN_FORUM'], '', '')); +} + +// Do the forum Prune thang - cron type job ... +if (!$config['use_system_cron']) +{ + /* @var $cron \phpbb\cron\manager */ + $cron = $phpbb_container->get('cron.manager'); + + $task = $cron->find_task('cron.task.core.prune_forum'); + $task->set_forum_data($forum_data); + + if ($task->is_ready()) + { + $url = $task->get_url(); + $template->assign_var('RUN_CRON_TASK', 'cron'); + } + else + { + // See if we should prune the shadow topics instead + $task = $cron->find_task('cron.task.core.prune_shadow_topics'); + $task->set_forum_data($forum_data); + + if ($task->is_ready()) + { + $url = $task->get_url(); + $template->assign_var('RUN_CRON_TASK', 'cron'); + } + } +} + +// Forum rules and subscription info +$s_watching_forum = array( + 'link' => '', + 'link_toggle' => '', + 'title' => '', + 'title_toggle' => '', + 'is_watching' => false, +); + +if ($config['allow_forum_notify'] && $forum_data['forum_type'] == FORUM_POST && ($auth->acl_get('f_subscribe', $forum_id) || $user->data['user_id'] == ANONYMOUS)) +{ + $notify_status = (isset($forum_data['notify_status'])) ? $forum_data['notify_status'] : NULL; + watch_topic_forum('forum', $s_watching_forum, $user->data['user_id'], $forum_id, 0, $notify_status, $start, $forum_data['forum_name']); +} + +$s_forum_rules = ''; +gen_forum_auth_level('forum', $forum_id, $forum_data['forum_status']); + +// Topic ordering options +$limit_days = array(0 => $user->lang['ALL_TOPICS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + +$sort_by_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 'r' => $user->lang['REPLIES'], 's' => $user->lang['SUBJECT'], 'v' => $user->lang['VIEWS']); +$sort_by_sql = array('a' => 't.topic_first_poster_name', 't' => array('t.topic_last_post_time', 't.topic_last_post_id'), 'r' => (($auth->acl_get('m_approve', $forum_id)) ? 't.topic_posts_approved + t.topic_posts_unapproved + t.topic_posts_softdeleted' : 't.topic_posts_approved'), 's' => 'LOWER(t.topic_title)', 'v' => 't.topic_views'); + +/** + * Modify the topic ordering if needed + * + * @event core.viewforum_modify_topic_ordering + * @var array sort_by_text Topic ordering options + * @var array sort_by_sql Topic orderings options SQL equivalent + * @since 3.2.5-RC1 + */ +$vars = array( + 'sort_by_text', + 'sort_by_sql', +); +extract($phpbb_dispatcher->trigger_event('core.viewforum_modify_topic_ordering', compact($vars))); + +$s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; +gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param, $default_sort_days, $default_sort_key, $default_sort_dir); + +// Limit topics to certain time frame, obtain correct topic count +if ($sort_days) +{ + $min_post_time = time() - ($sort_days * 86400); + + $sql_array = array( + 'SELECT' => 'COUNT(t.topic_id) AS num_topics', + 'FROM' => array( + TOPICS_TABLE => 't', + ), + 'WHERE' => 't.forum_id = ' . $forum_id . ' + AND (t.topic_last_post_time >= ' . $min_post_time . ' + OR t.topic_type = ' . POST_ANNOUNCE . ' + OR t.topic_type = ' . POST_GLOBAL . ') + AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.'), + ); + + /** + * Modify the sort data SQL query for getting additional fields if needed + * + * @event core.viewforum_modify_sort_data_sql + * @var int forum_id The forum_id whose topics are being listed + * @var int start Variable containing start for pagination + * @var int sort_days The oldest topic displayable in elapsed days + * @var string sort_key The sorting by. It is one of the first character of (in low case): + * Author, Post time, Replies, Subject, Views + * @var string sort_dir Either "a" for ascending or "d" for descending + * @var array sql_array The SQL array to get the data of all topics + * @since 3.1.9-RC1 + */ + $vars = array( + 'forum_id', + 'start', + 'sort_days', + 'sort_key', + 'sort_dir', + 'sql_array', + ); + extract($phpbb_dispatcher->trigger_event('core.viewforum_modify_sort_data_sql', compact($vars))); + + $result = $db->sql_query($db->sql_build_query('SELECT', $sql_array)); + $topics_count = (int) $db->sql_fetchfield('num_topics'); + $db->sql_freeresult($result); + + if (isset($_POST['sort'])) + { + $start = 0; + } + $sql_limit_time = "AND t.topic_last_post_time >= $min_post_time"; + + // Make sure we have information about day selection ready + $template->assign_var('S_SORT_DAYS', true); +} +else +{ + $sql_limit_time = ''; +} + +// Basic pagewide vars +$post_alt = ($forum_data['forum_status'] == ITEM_LOCKED) ? $user->lang['FORUM_LOCKED'] : $user->lang['POST_NEW_TOPIC']; + +// Display active topics? +$s_display_active = ($forum_data['forum_type'] == FORUM_CAT && ($forum_data['forum_flags'] & FORUM_FLAG_ACTIVE_TOPICS)) ? true : false; + +$s_search_hidden_fields = array('fid' => array($forum_id)); +if ($_SID) +{ + $s_search_hidden_fields['sid'] = $_SID; +} + +if (!empty($_EXTRA_URL)) +{ + foreach ($_EXTRA_URL as $url_param) + { + $url_param = explode('=', $url_param, 2); + $s_search_hidden_fields[$url_param[0]] = $url_param[1]; + } +} + +$template->assign_vars(array( + 'MODERATORS' => (!empty($moderators[$forum_id])) ? implode($user->lang['COMMA_SEPARATOR'], $moderators[$forum_id]) : '', + + 'POST_IMG' => ($forum_data['forum_status'] == ITEM_LOCKED) ? $user->img('button_topic_locked', $post_alt) : $user->img('button_topic_new', $post_alt), + 'NEWEST_POST_IMG' => $user->img('icon_topic_newest', 'VIEW_NEWEST_POST'), + 'LAST_POST_IMG' => $user->img('icon_topic_latest', 'VIEW_LATEST_POST'), + 'FOLDER_IMG' => $user->img('topic_read', 'NO_UNREAD_POSTS'), + 'FOLDER_UNREAD_IMG' => $user->img('topic_unread', 'UNREAD_POSTS'), + 'FOLDER_HOT_IMG' => $user->img('topic_read_hot', 'NO_UNREAD_POSTS_HOT'), + 'FOLDER_HOT_UNREAD_IMG' => $user->img('topic_unread_hot', 'UNREAD_POSTS_HOT'), + 'FOLDER_LOCKED_IMG' => $user->img('topic_read_locked', 'NO_UNREAD_POSTS_LOCKED'), + 'FOLDER_LOCKED_UNREAD_IMG' => $user->img('topic_unread_locked', 'UNREAD_POSTS_LOCKED'), + 'FOLDER_STICKY_IMG' => $user->img('sticky_read', 'POST_STICKY'), + 'FOLDER_STICKY_UNREAD_IMG' => $user->img('sticky_unread', 'POST_STICKY'), + 'FOLDER_ANNOUNCE_IMG' => $user->img('announce_read', 'POST_ANNOUNCEMENT'), + 'FOLDER_ANNOUNCE_UNREAD_IMG'=> $user->img('announce_unread', 'POST_ANNOUNCEMENT'), + 'FOLDER_MOVED_IMG' => $user->img('topic_moved', 'TOPIC_MOVED'), + 'REPORTED_IMG' => $user->img('icon_topic_reported', 'TOPIC_REPORTED'), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', 'TOPIC_UNAPPROVED'), + 'DELETED_IMG' => $user->img('icon_topic_deleted', 'TOPIC_DELETED'), + 'POLL_IMG' => $user->img('icon_topic_poll', 'TOPIC_POLL'), + 'GOTO_PAGE_IMG' => $user->img('icon_post_target', 'GOTO_PAGE'), + + 'L_NO_TOPICS' => ($forum_data['forum_status'] == ITEM_LOCKED) ? $user->lang['POST_FORUM_LOCKED'] : $user->lang['NO_TOPICS'], + + 'S_DISPLAY_POST_INFO' => ($forum_data['forum_type'] == FORUM_POST && ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS)) ? true : false, + + 'S_IS_POSTABLE' => ($forum_data['forum_type'] == FORUM_POST) ? true : false, + 'S_USER_CAN_POST' => ($auth->acl_get('f_post', $forum_id)) ? true : false, + 'S_DISPLAY_ACTIVE' => $s_display_active, + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DAYS' => $s_limit_days, + 'S_TOPIC_ICONS' => ($s_display_active && count($active_forum_ary)) ? max($active_forum_ary['enable_icons']) : (($forum_data['enable_icons']) ? true : false), + 'U_WATCH_FORUM_LINK' => $s_watching_forum['link'], + 'U_WATCH_FORUM_TOGGLE' => $s_watching_forum['link_toggle'], + 'S_WATCH_FORUM_TITLE' => $s_watching_forum['title'], + 'S_WATCH_FORUM_TOGGLE' => $s_watching_forum['title_toggle'], + 'S_WATCHING_FORUM' => $s_watching_forum['is_watching'], + 'S_FORUM_ACTION' => append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id" . (($start == 0) ? '' : "&start=$start")), + 'S_DISPLAY_SEARCHBOX' => ($auth->acl_get('u_search') && $auth->acl_get('f_search', $forum_id) && $config['load_search']) ? true : false, + 'S_SEARCHBOX_ACTION' => append_sid("{$phpbb_root_path}search.$phpEx"), + 'S_SEARCH_LOCAL_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields), + 'S_SINGLE_MODERATOR' => (!empty($moderators[$forum_id]) && count($moderators[$forum_id]) > 1) ? false : true, + 'S_IS_LOCKED' => ($forum_data['forum_status'] == ITEM_LOCKED) ? true : false, + 'S_VIEWFORUM' => true, + + 'U_MCP' => ($auth->acl_get('m_', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "f=$forum_id&i=main&mode=forum_view", true, $user->session_id) : '', + 'U_POST_NEW_TOPIC' => ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS) ? append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=post&f=' . $forum_id) : '', + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '') . (($start == 0) ? '' : "&start=$start")), + 'U_CANONICAL' => generate_board_url() . '/' . append_sid("viewforum.$phpEx", "f=$forum_id" . (($start) ? "&start=$start" : ''), true, ''), + 'U_MARK_TOPICS' => ($user->data['is_registered'] || $config['load_anon_lastread']) ? append_sid("{$phpbb_root_path}viewforum.$phpEx", 'hash=' . generate_link_hash('global') . "&f=$forum_id&mark=topics&mark_time=" . time()) : '', +)); + +// Grab icons +$icons = $cache->obtain_icons(); + +// Grab all topic data +$rowset = $announcement_list = $topic_list = $global_announce_forums = array(); + +$sql_array = array( + 'SELECT' => 't.*', + 'FROM' => array( + TOPICS_TABLE => 't' + ), + 'LEFT_JOIN' => array(), +); + +/** +* Event to modify the SQL query before the topic data is retrieved +* +* It may also be used to override the above assigned template vars +* +* @event core.viewforum_get_topic_data +* @var array forum_data Array with forum data +* @var array sql_array The SQL array to get the data of all topics +* @var int forum_id The forum_id whose topics are being listed +* @var int topics_count The total number of topics for display +* @var int sort_days The oldest topic displayable in elapsed days +* @var string sort_key The sorting by. It is one of the first character of (in low case): +* Author, Post time, Replies, Subject, Views +* @var string sort_dir Either "a" for ascending or "d" for descending +* @since 3.1.0-a1 +* @changed 3.1.0-RC4 Added forum_data var +* @changed 3.1.4-RC1 Added forum_id, topics_count, sort_days, sort_key and sort_dir vars +* @changed 3.1.9-RC1 Fix types of properties +*/ +$vars = array( + 'forum_data', + 'sql_array', + 'forum_id', + 'topics_count', + 'sort_days', + 'sort_key', + 'sort_dir', +); +extract($phpbb_dispatcher->trigger_event('core.viewforum_get_topic_data', compact($vars))); + +$sql_approved = ' AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.'); + +if ($user->data['is_registered']) +{ + if ($config['load_db_track']) + { + $sql_array['LEFT_JOIN'][] = array('FROM' => array(TOPICS_POSTED_TABLE => 'tp'), 'ON' => 'tp.topic_id = t.topic_id AND tp.user_id = ' . $user->data['user_id']); + $sql_array['SELECT'] .= ', tp.topic_posted'; + } + + if ($config['load_db_lastread']) + { + $sql_array['LEFT_JOIN'][] = array('FROM' => array(TOPICS_TRACK_TABLE => 'tt'), 'ON' => 'tt.topic_id = t.topic_id AND tt.user_id = ' . $user->data['user_id']); + $sql_array['SELECT'] .= ', tt.mark_time'; + + if ($s_display_active && count($active_forum_ary)) + { + $sql_array['LEFT_JOIN'][] = array('FROM' => array(FORUMS_TRACK_TABLE => 'ft'), 'ON' => 'ft.forum_id = t.forum_id AND ft.user_id = ' . $user->data['user_id']); + $sql_array['SELECT'] .= ', ft.mark_time AS forum_mark_time'; + } + } +} + +if ($forum_data['forum_type'] == FORUM_POST) +{ + // Get global announcement forums + $g_forum_ary = $auth->acl_getf('f_read', true); + $g_forum_ary = array_unique(array_keys($g_forum_ary)); + + $sql_anounce_array['LEFT_JOIN'] = $sql_array['LEFT_JOIN']; + $sql_anounce_array['LEFT_JOIN'][] = array('FROM' => array(FORUMS_TABLE => 'f'), 'ON' => 'f.forum_id = t.forum_id'); + $sql_anounce_array['SELECT'] = $sql_array['SELECT'] . ', f.forum_name'; + + // Obtain announcements ... removed sort ordering, sort by time in all cases + $sql_ary = array( + 'SELECT' => $sql_anounce_array['SELECT'], + 'FROM' => $sql_array['FROM'], + 'LEFT_JOIN' => $sql_anounce_array['LEFT_JOIN'], + + 'WHERE' => '(t.forum_id = ' . $forum_id . ' + AND t.topic_type = ' . POST_ANNOUNCE . ') OR + (' . $db->sql_in_set('t.forum_id', $g_forum_ary, false, true) . ' + AND t.topic_type = ' . POST_GLOBAL . ')', + + 'ORDER_BY' => 't.topic_time DESC', + ); + + /** + * Event to modify the SQL query before the announcement topic ids data is retrieved + * + * @event core.viewforum_get_announcement_topic_ids_data + * @var array forum_data Data about the forum + * @var array g_forum_ary Global announcement forums array + * @var array sql_anounce_array SQL announcement array + * @var array sql_ary SQL query array to get the announcement topic ids data + * @var int forum_id The forum ID + * + * @since 3.1.10-RC1 + */ + $vars = array( + 'forum_data', + 'g_forum_ary', + 'sql_anounce_array', + 'sql_ary', + 'forum_id', + ); + extract($phpbb_dispatcher->trigger_event('core.viewforum_get_announcement_topic_ids_data', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_ary); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if (!$phpbb_content_visibility->is_visible('topic', $row['forum_id'], $row)) + { + // Do not display announcements that are waiting for approval or soft deleted. + continue; + } + + $rowset[$row['topic_id']] = $row; + $announcement_list[] = $row['topic_id']; + + if ($forum_id != $row['forum_id']) + { + $topics_count++; + $global_announce_forums[] = $row['forum_id']; + } + } + $db->sql_freeresult($result); +} + +$forum_tracking_info = array(); + +if ($user->data['is_registered'] && $config['load_db_lastread']) +{ + $forum_tracking_info[$forum_id] = $forum_data['mark_time']; + + if (!empty($global_announce_forums)) + { + $sql = 'SELECT forum_id, mark_time + FROM ' . FORUMS_TRACK_TABLE . ' + WHERE ' . $db->sql_in_set('forum_id', $global_announce_forums) . ' + AND user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $forum_tracking_info[$row['forum_id']] = $row['mark_time']; + } + $db->sql_freeresult($result); + } +} + +// If the user is trying to reach late pages, start searching from the end +$store_reverse = false; +$sql_limit = $config['topics_per_page']; +if ($start > $topics_count / 2) +{ + $store_reverse = true; + + // Select the sort order + $direction = (($sort_dir == 'd') ? 'ASC' : 'DESC'); + + $sql_limit = $pagination->reverse_limit($start, $sql_limit, $topics_count - count($announcement_list)); + $sql_start = $pagination->reverse_start($start, $sql_limit, $topics_count - count($announcement_list)); +} +else +{ + // Select the sort order + $direction = (($sort_dir == 'd') ? 'DESC' : 'ASC'); + $sql_start = $start; +} + +/** + * Modify the topics sort ordering if needed + * + * @event core.viewforum_modify_sort_direction + * @var string direction Topics sort order + * @since 3.2.5-RC1 + */ +$vars = array( + 'direction', +); +extract($phpbb_dispatcher->trigger_event('core.viewforum_modify_sort_direction', compact($vars))); + +if (is_array($sort_by_sql[$sort_key])) +{ + $sql_sort_order = implode(' ' . $direction . ', ', $sort_by_sql[$sort_key]) . ' ' . $direction; +} +else +{ + $sql_sort_order = $sort_by_sql[$sort_key] . ' ' . $direction; +} + +if ($forum_data['forum_type'] == FORUM_POST || !count($active_forum_ary)) +{ + $sql_where = 't.forum_id = ' . $forum_id; +} +else if (empty($active_forum_ary['exclude_forum_id'])) +{ + $sql_where = $db->sql_in_set('t.forum_id', $active_forum_ary['forum_id']); +} +else +{ + $get_forum_ids = array_diff($active_forum_ary['forum_id'], $active_forum_ary['exclude_forum_id']); + $sql_where = (count($get_forum_ids)) ? $db->sql_in_set('t.forum_id', $get_forum_ids) : 't.forum_id = ' . $forum_id; +} + +// Grab just the sorted topic ids +$sql_ary = array( + 'SELECT' => 't.topic_id', + 'FROM' => array( + TOPICS_TABLE => 't', + ), + 'WHERE' => "$sql_where + AND t.topic_type IN (" . POST_NORMAL . ', ' . POST_STICKY . ") + $sql_approved + $sql_limit_time", + 'ORDER_BY' => 't.topic_type ' . ((!$store_reverse) ? 'DESC' : 'ASC') . ', ' . $sql_sort_order, +); + +/** +* Event to modify the SQL query before the topic ids data is retrieved +* +* @event core.viewforum_get_topic_ids_data +* @var array forum_data Data about the forum +* @var array sql_ary SQL query array to get the topic ids data +* @var string sql_approved Topic visibility SQL string +* @var int sql_limit Number of records to select +* @var string sql_limit_time SQL string to limit topic_last_post_time data +* @var array sql_sort_order SQL sorting string +* @var int sql_start Offset point to start selection from +* @var string sql_where SQL WHERE clause string +* @var bool store_reverse Flag indicating if we select from the late pages +* +* @since 3.1.0-RC4 +* +* @changed 3.1.3 Added forum_data +*/ +$vars = array( + 'forum_data', + 'sql_ary', + 'sql_approved', + 'sql_limit', + 'sql_limit_time', + 'sql_sort_order', + 'sql_start', + 'sql_where', + 'store_reverse', +); +extract($phpbb_dispatcher->trigger_event('core.viewforum_get_topic_ids_data', compact($vars))); + +$sql = $db->sql_build_query('SELECT', $sql_ary); +$result = $db->sql_query_limit($sql, $sql_limit, $sql_start); + +while ($row = $db->sql_fetchrow($result)) +{ + $topic_list[] = (int) $row['topic_id']; +} +$db->sql_freeresult($result); + +// For storing shadow topics +$shadow_topic_list = array(); + +if (count($topic_list)) +{ + // SQL array for obtaining topics/stickies + $sql_array = array( + 'SELECT' => $sql_array['SELECT'], + 'FROM' => $sql_array['FROM'], + 'LEFT_JOIN' => $sql_array['LEFT_JOIN'], + + 'WHERE' => $db->sql_in_set('t.topic_id', $topic_list), + ); + + // If store_reverse, then first obtain topics, then stickies, else the other way around... + // Funnily enough you typically save one query if going from the last page to the middle (store_reverse) because + // the number of stickies are not known + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + if ($row['topic_status'] == ITEM_MOVED) + { + $shadow_topic_list[$row['topic_moved_id']] = $row['topic_id']; + } + + $rowset[$row['topic_id']] = $row; + } + $db->sql_freeresult($result); +} + +// If we have some shadow topics, update the rowset to reflect their topic information +if (count($shadow_topic_list)) +{ + // SQL array for obtaining shadow topics + $sql_array = array( + 'SELECT' => 't.*', + 'FROM' => array( + TOPICS_TABLE => 't' + ), + 'WHERE' => $db->sql_in_set('t.topic_id', array_keys($shadow_topic_list)), + ); + + /** + * Event to modify the SQL query before the shadowtopic data is retrieved + * + * @event core.viewforum_get_shadowtopic_data + * @var array sql_array SQL array to get the data of any shadowtopics + * @since 3.1.0-a1 + */ + $vars = array('sql_array'); + extract($phpbb_dispatcher->trigger_event('core.viewforum_get_shadowtopic_data', compact($vars))); + + $sql = $db->sql_build_query('SELECT', $sql_array); + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $orig_topic_id = $shadow_topic_list[$row['topic_id']]; + + // If the shadow topic is already listed within the rowset (happens for active topics for example), then do not include it... + if (isset($rowset[$row['topic_id']])) + { + // We need to remove any trace regarding this topic. :) + unset($rowset[$orig_topic_id]); + unset($topic_list[array_search($orig_topic_id, $topic_list)]); + $topics_count--; + + continue; + } + + // Do not include those topics the user has no permission to access + if (!$auth->acl_gets('f_read', 'f_list_topics', $row['forum_id'])) + { + // We need to remove any trace regarding this topic. :) + unset($rowset[$orig_topic_id]); + unset($topic_list[array_search($orig_topic_id, $topic_list)]); + $topics_count--; + + continue; + } + + // We want to retain some values + $row = array_merge($row, array( + 'topic_moved_id' => $rowset[$orig_topic_id]['topic_moved_id'], + 'topic_status' => $rowset[$orig_topic_id]['topic_status'], + 'topic_type' => $rowset[$orig_topic_id]['topic_type'], + 'topic_title' => $rowset[$orig_topic_id]['topic_title'], + )); + + // Shadow topics are never reported + $row['topic_reported'] = 0; + + $rowset[$orig_topic_id] = $row; + } + $db->sql_freeresult($result); +} +unset($shadow_topic_list); + +// Ok, adjust topics count for active topics list +if ($s_display_active) +{ + $topics_count = 1; +} + +// We need to remove the global announcements from the forums total topic count, +// otherwise the number is different from the one on the forum list +$total_topic_count = $topics_count - count($announcement_list); + +$base_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", "f=$forum_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '')); +$pagination->generate_template_pagination($base_url, 'pagination', 'start', $total_topic_count, $config['topics_per_page'], $start); + +$template->assign_vars(array( + 'TOTAL_TOPICS' => ($s_display_active) ? false : $user->lang('VIEW_FORUM_TOPICS', (int) $total_topic_count), +)); + +$topic_list = ($store_reverse) ? array_merge($announcement_list, array_reverse($topic_list)) : array_merge($announcement_list, $topic_list); +$topic_tracking_info = $tracking_topics = array(); + +/** +* Modify topics data before we display the viewforum page +* +* @event core.viewforum_modify_topics_data +* @var array topic_list Array with current viewforum page topic ids +* @var array rowset Array with topics data (in topic_id => topic_data format) +* @var int total_topic_count Forum's total topic count +* @var int forum_id Forum identifier +* @since 3.1.0-b3 +* @changed 3.1.11-RC1 Added forum_id +*/ +$vars = array('topic_list', 'rowset', 'total_topic_count', 'forum_id'); +extract($phpbb_dispatcher->trigger_event('core.viewforum_modify_topics_data', compact($vars))); + +// Okay, lets dump out the page ... +if (count($topic_list)) +{ + $mark_forum_read = true; + $mark_time_forum = 0; + + // Generate topic forum list... + $topic_forum_list = array(); + foreach ($rowset as $t_id => $row) + { + if (isset($forum_tracking_info[$row['forum_id']])) + { + $row['forum_mark_time'] = $forum_tracking_info[$row['forum_id']]; + } + + $topic_forum_list[$row['forum_id']]['forum_mark_time'] = ($config['load_db_lastread'] && $user->data['is_registered'] && isset($row['forum_mark_time'])) ? $row['forum_mark_time'] : 0; + $topic_forum_list[$row['forum_id']]['topics'][] = (int) $t_id; + } + + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + foreach ($topic_forum_list as $f_id => $topic_row) + { + $topic_tracking_info += get_topic_tracking($f_id, $topic_row['topics'], $rowset, array($f_id => $topic_row['forum_mark_time'])); + } + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + foreach ($topic_forum_list as $f_id => $topic_row) + { + $topic_tracking_info += get_complete_topic_tracking($f_id, $topic_row['topics']); + } + } + + unset($topic_forum_list); + + if (!$s_display_active) + { + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $mark_time_forum = (!empty($forum_data['mark_time'])) ? $forum_data['mark_time'] : $user->data['user_lastmark']; + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + if (!$user->data['is_registered']) + { + $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0; + } + $mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark']; + } + } + + $s_type_switch = 0; + foreach ($topic_list as $topic_id) + { + $row = &$rowset[$topic_id]; + + $topic_forum_id = ($row['forum_id']) ? (int) $row['forum_id'] : $forum_id; + + // This will allow the style designer to output a different header + // or even separate the list of announcements from sticky and normal topics + $s_type_switch_test = ($row['topic_type'] == POST_ANNOUNCE || $row['topic_type'] == POST_GLOBAL) ? 1 : 0; + + // Replies + $replies = $phpbb_content_visibility->get_count('topic_posts', $row, $topic_forum_id) - 1; + + if ($row['topic_status'] == ITEM_MOVED) + { + $topic_id = $row['topic_moved_id']; + $unread_topic = false; + } + else + { + $unread_topic = (isset($topic_tracking_info[$topic_id]) && $row['topic_last_post_time'] > $topic_tracking_info[$topic_id]) ? true : false; + } + + // Get folder img, topic status/type related information + $folder_img = $folder_alt = $topic_type = ''; + topic_status($row, $replies, $unread_topic, $folder_img, $folder_alt, $topic_type); + + // Generate all the URIs ... + $view_topic_url_params = 'f=' . $row['forum_id'] . '&t=' . $topic_id; + $view_topic_url = $auth->acl_get('f_read', $forum_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", $view_topic_url_params) : false; + + $topic_unapproved = (($row['topic_visibility'] == ITEM_UNAPPROVED || $row['topic_visibility'] == ITEM_REAPPROVE) && $auth->acl_get('m_approve', $row['forum_id'])); + $posts_unapproved = ($row['topic_visibility'] == ITEM_APPROVED && $row['topic_posts_unapproved'] && $auth->acl_get('m_approve', $row['forum_id'])); + $topic_deleted = $row['topic_visibility'] == ITEM_DELETED; + + $u_mcp_queue = ($topic_unapproved || $posts_unapproved) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=' . (($topic_unapproved) ? 'approve_details' : 'unapproved_posts') . "&t=$topic_id", true, $user->session_id) : ''; + $u_mcp_queue = (!$u_mcp_queue && $topic_deleted) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=deleted_topics&t=' . $topic_id, true, $user->session_id) : $u_mcp_queue; + + // Send vars to template + $topic_row = array( + 'FORUM_ID' => $row['forum_id'], + 'TOPIC_ID' => $topic_id, + 'TOPIC_AUTHOR' => get_username_string('username', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'TOPIC_AUTHOR_COLOUR' => get_username_string('colour', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'TOPIC_AUTHOR_FULL' => get_username_string('full', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'FIRST_POST_TIME' => $user->format_date($row['topic_time']), + 'LAST_POST_SUBJECT' => censor_text($row['topic_last_post_subject']), + 'LAST_POST_TIME' => $user->format_date($row['topic_last_post_time']), + 'LAST_VIEW_TIME' => $user->format_date($row['topic_last_view_time']), + 'LAST_POST_AUTHOR' => get_username_string('username', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'LAST_POST_AUTHOR_COLOUR' => get_username_string('colour', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'LAST_POST_AUTHOR_FULL' => get_username_string('full', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + + 'REPLIES' => $replies, + 'VIEWS' => $row['topic_views'], + 'TOPIC_TITLE' => censor_text($row['topic_title']), + 'TOPIC_TYPE' => $topic_type, + 'FORUM_NAME' => (isset($row['forum_name'])) ? $row['forum_name'] : $forum_data['forum_name'], + + 'TOPIC_IMG_STYLE' => $folder_img, + 'TOPIC_FOLDER_IMG' => $user->img($folder_img, $folder_alt), + 'TOPIC_FOLDER_IMG_ALT' => $user->lang[$folder_alt], + + 'TOPIC_ICON_IMG' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['img'] : '', + 'TOPIC_ICON_IMG_WIDTH' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['width'] : '', + 'TOPIC_ICON_IMG_HEIGHT' => (!empty($icons[$row['icon_id']])) ? $icons[$row['icon_id']]['height'] : '', + 'ATTACH_ICON_IMG' => ($auth->acl_get('u_download') && $auth->acl_get('f_download', $row['forum_id']) && $row['topic_attachment']) ? $user->img('icon_topic_attach', $user->lang['TOTAL_ATTACHMENTS']) : '', + 'UNAPPROVED_IMG' => ($topic_unapproved || $posts_unapproved) ? $user->img('icon_topic_unapproved', ($topic_unapproved) ? 'TOPIC_UNAPPROVED' : 'POSTS_UNAPPROVED') : '', + + 'S_TOPIC_TYPE' => $row['topic_type'], + 'S_USER_POSTED' => (isset($row['topic_posted']) && $row['topic_posted']) ? true : false, + 'S_UNREAD_TOPIC' => $unread_topic, + 'S_TOPIC_REPORTED' => (!empty($row['topic_reported']) && $auth->acl_get('m_report', $row['forum_id'])) ? true : false, + 'S_TOPIC_UNAPPROVED' => $topic_unapproved, + 'S_POSTS_UNAPPROVED' => $posts_unapproved, + 'S_TOPIC_DELETED' => $topic_deleted, + 'S_HAS_POLL' => ($row['poll_start']) ? true : false, + 'S_POST_ANNOUNCE' => ($row['topic_type'] == POST_ANNOUNCE) ? true : false, + 'S_POST_GLOBAL' => ($row['topic_type'] == POST_GLOBAL) ? true : false, + 'S_POST_STICKY' => ($row['topic_type'] == POST_STICKY) ? true : false, + 'S_TOPIC_LOCKED' => ($row['topic_status'] == ITEM_LOCKED) ? true : false, + 'S_TOPIC_MOVED' => ($row['topic_status'] == ITEM_MOVED) ? true : false, + + 'U_NEWEST_POST' => $auth->acl_get('f_read', $forum_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", $view_topic_url_params . '&view=unread') . '#unread' : false, + 'U_LAST_POST' => $auth->acl_get('f_read', $forum_id) ? append_sid("{$phpbb_root_path}viewtopic.$phpEx", $view_topic_url_params . '&p=' . $row['topic_last_post_id']) . '#p' . $row['topic_last_post_id'] : false, + 'U_LAST_POST_AUTHOR' => get_username_string('profile', $row['topic_last_poster_id'], $row['topic_last_poster_name'], $row['topic_last_poster_colour']), + 'U_TOPIC_AUTHOR' => get_username_string('profile', $row['topic_poster'], $row['topic_first_poster_name'], $row['topic_first_poster_colour']), + 'U_VIEW_TOPIC' => $view_topic_url, + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $row['forum_id']), + 'U_MCP_REPORT' => append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=reports&f=' . $row['forum_id'] . '&t=' . $topic_id, true, $user->session_id), + 'U_MCP_QUEUE' => $u_mcp_queue, + + 'S_TOPIC_TYPE_SWITCH' => ($s_type_switch == $s_type_switch_test) ? -1 : $s_type_switch_test, + ); + + /** + * Modify the topic data before it is assigned to the template + * + * @event core.viewforum_modify_topicrow + * @var array row Array with topic data + * @var array topic_row Template array with topic data + * @var bool s_type_switch Flag indicating if the topic type is [global] announcement + * @var bool s_type_switch_test Flag indicating if the test topic type is [global] announcement + * @since 3.1.0-a1 + * + * @changed 3.1.10-RC1 Added s_type_switch, s_type_switch_test + */ + $vars = array('row', 'topic_row', 's_type_switch', 's_type_switch_test'); + extract($phpbb_dispatcher->trigger_event('core.viewforum_modify_topicrow', compact($vars))); + + $template->assign_block_vars('topicrow', $topic_row); + + $pagination->generate_template_pagination($view_topic_url, 'topicrow.pagination', 'start', $replies + 1, $config['posts_per_page'], 1, true, true); + + $s_type_switch = ($row['topic_type'] == POST_ANNOUNCE || $row['topic_type'] == POST_GLOBAL) ? 1 : 0; + + /** + * Event after the topic data has been assigned to the template + * + * @event core.viewforum_topic_row_after + * @var array row Array with the topic data + * @var array rowset Array with topics data (in topic_id => topic_data format) + * @var bool s_type_switch Flag indicating if the topic type is [global] announcement + * @var int topic_id The topic ID + * @var array topic_list Array with current viewforum page topic ids + * @var array topic_row Template array with topic data + * @since 3.1.3-RC1 + */ + $vars = array( + 'row', + 'rowset', + 's_type_switch', + 'topic_id', + 'topic_list', + 'topic_row', + ); + extract($phpbb_dispatcher->trigger_event('core.viewforum_topic_row_after', compact($vars))); + + if ($unread_topic) + { + $mark_forum_read = false; + } + + unset($rowset[$topic_id]); + } +} + +/** +* This event is to perform additional actions on viewforum page +* +* @event core.viewforum_generate_page_after +* @var array forum_data Array with the forum data +* @since 3.2.2-RC1 +*/ +$vars = array('forum_data'); +extract($phpbb_dispatcher->trigger_event('core.viewforum_generate_page_after', compact($vars))); + +// This is rather a fudge but it's the best I can think of without requiring information +// on all topics (as we do in 2.0.x). It looks for unread or new topics, if it doesn't find +// any it updates the forum last read cookie. This requires that the user visit the forum +// after reading a topic +if ($forum_data['forum_type'] == FORUM_POST && count($topic_list) && $mark_forum_read) +{ + update_forum_tracking_info($forum_id, $forum_data['forum_last_post_time'], false, $mark_time_forum); +} + +page_footer(); diff --git a/viewonline.php b/viewonline.php new file mode 100644 index 0000000..d5ddb0b --- /dev/null +++ b/viewonline.php @@ -0,0 +1,517 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); + +// Start session management +$user->session_begin(); +$auth->acl($user->data); +$user->setup('memberlist'); + +// Get and set some variables +$mode = $request->variable('mode', ''); +$session_id = $request->variable('s', ''); +$start = $request->variable('start', 0); +$sort_key = $request->variable('sk', 'b'); +$sort_dir = $request->variable('sd', 'd'); +$show_guests = ($config['load_online_guests']) ? $request->variable('sg', 0) : 0; + +// Can this user view profiles/memberlist? +if (!$auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) +{ + if ($user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + trigger_error('NO_VIEW_USERS'); + } + + login_box('', $user->lang['LOGIN_EXPLAIN_VIEWONLINE']); +} + +/* @var $pagination \phpbb\pagination */ +$pagination = $phpbb_container->get('pagination'); + +/* @var $viewonline_helper \phpbb\viewonline_helper */ +$viewonline_helper = $phpbb_container->get('viewonline_helper'); + +$sort_key_text = array('a' => $user->lang['SORT_USERNAME'], 'b' => $user->lang['SORT_JOINED'], 'c' => $user->lang['SORT_LOCATION']); +$sort_key_sql = array('a' => 'u.username_clean', 'b' => 's.session_time', 'c' => 's.session_page'); + +// Sorting and order +if (!isset($sort_key_text[$sort_key])) +{ + $sort_key = 'b'; +} + +$order_by = $sort_key_sql[$sort_key] . ' ' . (($sort_dir == 'a') ? 'ASC' : 'DESC'); + +// Whois requested +if ($mode == 'whois' && $auth->acl_get('a_') && $session_id) +{ + if (!function_exists('user_get_id_name')) + { + include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + } + + $sql = 'SELECT u.user_id, u.username, u.user_type, s.session_ip + FROM ' . USERS_TABLE . ' u, ' . SESSIONS_TABLE . " s + WHERE s.session_id = '" . $db->sql_escape($session_id) . "' + AND u.user_id = s.session_user_id"; + $result = $db->sql_query($sql); + + if ($row = $db->sql_fetchrow($result)) + { + $template->assign_var('WHOIS', user_ipwhois($row['session_ip'])); + } + $db->sql_freeresult($result); + + // Output the page + page_header($user->lang['WHO_IS_ONLINE']); + + $template->set_filenames(array( + 'body' => 'viewonline_whois.html') + ); + make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx")); + + page_footer(); +} + +$user->update_session_infos(); + +// Forum info +$sql_ary = array( + 'SELECT' => 'f.forum_id, f.forum_name, f.parent_id, f.forum_type, f.left_id, f.right_id', + 'FROM' => array( + FORUMS_TABLE => 'f', + ), + 'ORDER_BY' => 'f.left_id ASC', +); + +/** +* Modify the forum data SQL query for getting additional fields if needed +* +* @event core.viewonline_modify_forum_data_sql +* @var array sql_ary The SQL array +* @since 3.1.5-RC1 +*/ +$vars = array('sql_ary'); +extract($phpbb_dispatcher->trigger_event('core.viewonline_modify_forum_data_sql', compact($vars))); + +$result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary), 600); +unset($sql_ary); + +$forum_data = array(); +while ($row = $db->sql_fetchrow($result)) +{ + $forum_data[$row['forum_id']] = $row; +} +$db->sql_freeresult($result); + +$guest_counter = 0; + +// Get number of online guests (if we do not display them) +if (!$show_guests) +{ + switch ($db->get_sql_layer()) + { + case 'sqlite3': + $sql = 'SELECT COUNT(session_ip) as num_guests + FROM ( + SELECT DISTINCT session_ip + FROM ' . SESSIONS_TABLE . ' + WHERE session_user_id = ' . ANONYMOUS . ' + AND session_time >= ' . (time() - ($config['load_online_time'] * 60)) . + ')'; + break; + + default: + $sql = 'SELECT COUNT(DISTINCT session_ip) as num_guests + FROM ' . SESSIONS_TABLE . ' + WHERE session_user_id = ' . ANONYMOUS . ' + AND session_time >= ' . (time() - ($config['load_online_time'] * 60)); + break; + } + $result = $db->sql_query($sql); + $guest_counter = (int) $db->sql_fetchfield('num_guests'); + $db->sql_freeresult($result); +} + +// Get user list +$sql_ary = array( + 'SELECT' => 'u.user_id, u.username, u.username_clean, u.user_type, u.user_colour, s.session_id, s.session_time, s.session_page, s.session_ip, s.session_browser, s.session_viewonline, s.session_forum_id', + 'FROM' => array( + USERS_TABLE => 'u', + SESSIONS_TABLE => 's', + ), + 'WHERE' => 'u.user_id = s.session_user_id + AND s.session_time >= ' . (time() - ($config['load_online_time'] * 60)) . + ((!$show_guests) ? ' AND s.session_user_id <> ' . ANONYMOUS : ''), + 'ORDER_BY' => $order_by, +); + +/** +* Modify the SQL query for getting the user data to display viewonline list +* +* @event core.viewonline_modify_sql +* @var array sql_ary The SQL array +* @var bool show_guests Do we display guests in the list +* @var int guest_counter Number of guests displayed +* @var array forum_data Array with forum data +* @since 3.1.0-a1 +* @changed 3.1.0-a2 Added vars guest_counter and forum_data +*/ +$vars = array('sql_ary', 'show_guests', 'guest_counter', 'forum_data'); +extract($phpbb_dispatcher->trigger_event('core.viewonline_modify_sql', compact($vars))); + +$result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary)); + +$prev_id = $prev_ip = $user_list = array(); +$logged_visible_online = $logged_hidden_online = $counter = 0; + +/** @var \phpbb\controller\helper $controller_helper */ +$controller_helper = $phpbb_container->get('controller.helper'); + +/** @var \phpbb\group\helper $group_helper */ +$group_helper = $phpbb_container->get('group_helper'); + +while ($row = $db->sql_fetchrow($result)) +{ + if ($row['user_id'] != ANONYMOUS && !isset($prev_id[$row['user_id']])) + { + $view_online = $s_user_hidden = false; + $user_colour = ($row['user_colour']) ? ' style="color:#' . $row['user_colour'] . '" class="username-coloured"' : ''; + + $username_full = ($row['user_type'] != USER_IGNORE) ? get_username_string('full', $row['user_id'], $row['username'], $row['user_colour']) : '' . $row['username'] . '
    '; + + if (!$row['session_viewonline']) + { + $view_online = ($auth->acl_get('u_viewonline') || $row['user_id'] === $user->data['user_id']) ? true : false; + $logged_hidden_online++; + + $username_full = '' . $username_full . ''; + $s_user_hidden = true; + } + else + { + $view_online = true; + $logged_visible_online++; + } + + $prev_id[$row['user_id']] = 1; + + if ($view_online) + { + $counter++; + } + + if (!$view_online || $counter > $start + $config['topics_per_page'] || $counter <= $start) + { + continue; + } + } + else if ($show_guests && $row['user_id'] == ANONYMOUS && !isset($prev_ip[$row['session_ip']])) + { + $prev_ip[$row['session_ip']] = 1; + $guest_counter++; + $counter++; + + if ($counter > $start + $config['topics_per_page'] || $counter <= $start) + { + continue; + } + + $s_user_hidden = false; + $username_full = get_username_string('full', $row['user_id'], $user->lang['GUEST']); + } + else + { + continue; + } + + $on_page = $viewonline_helper->get_user_page($row['session_page']); + + switch ($on_page[1]) + { + case 'index': + $location = $user->lang['INDEX']; + $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); + break; + + case $phpbb_adm_relative_path . 'index': + $location = $user->lang['ACP']; + $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); + break; + + case 'posting': + case 'viewforum': + case 'viewtopic': + $forum_id = $row['session_forum_id']; + + if ($forum_id && $auth->acl_get('f_list', $forum_id)) + { + $location = ''; + $location_url = append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id); + + if ($forum_data[$forum_id]['forum_type'] == FORUM_LINK) + { + $location = sprintf($user->lang['READING_LINK'], $forum_data[$forum_id]['forum_name']); + break; + } + + switch ($on_page[1]) + { + case 'posting': + preg_match('#mode=([a-z]+)#', $row['session_page'], $on_page); + $posting_mode = (!empty($on_page[1])) ? $on_page[1] : ''; + + switch ($posting_mode) + { + case 'reply': + case 'quote': + $location = sprintf($user->lang['REPLYING_MESSAGE'], $forum_data[$forum_id]['forum_name']); + break; + + default: + $location = sprintf($user->lang['POSTING_MESSAGE'], $forum_data[$forum_id]['forum_name']); + break; + } + break; + + case 'viewtopic': + $location = sprintf($user->lang['READING_TOPIC'], $forum_data[$forum_id]['forum_name']); + break; + + case 'viewforum': + $location = sprintf($user->lang['READING_FORUM'], $forum_data[$forum_id]['forum_name']); + break; + } + } + else + { + $location = $user->lang['INDEX']; + $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); + } + break; + + case 'search': + $location = $user->lang['SEARCHING_FORUMS']; + $location_url = append_sid("{$phpbb_root_path}search.$phpEx"); + break; + + case 'viewonline': + $location = $user->lang['VIEWING_ONLINE']; + $location_url = append_sid("{$phpbb_root_path}viewonline.$phpEx"); + break; + + case 'memberlist': + $location_url = append_sid("{$phpbb_root_path}memberlist.$phpEx"); + + if (strpos($row['session_page'], 'mode=viewprofile') !== false) + { + $location = $user->lang['VIEWING_MEMBER_PROFILE']; + } + else if (strpos($row['session_page'], 'mode=contactadmin') !== false) + { + $location = $user->lang['VIEWING_CONTACT_ADMIN']; + $location_url = append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin'); + } + else + { + $location = $user->lang['VIEWING_MEMBERS']; + } + break; + + case 'mcp': + $location = $user->lang['VIEWING_MCP']; + $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); + break; + + case 'ucp': + $location = $user->lang['VIEWING_UCP']; + + // Grab some common modules + $url_params = array( + 'mode=register' => 'VIEWING_REGISTER', + 'i=pm&mode=compose' => 'POSTING_PRIVATE_MESSAGE', + 'i=pm&' => 'VIEWING_PRIVATE_MESSAGES', + 'i=profile&' => 'CHANGING_PROFILE', + 'i=prefs&' => 'CHANGING_PREFERENCES', + ); + + foreach ($url_params as $param => $lang) + { + if (strpos($row['session_page'], $param) !== false) + { + $location = $user->lang[$lang]; + break; + } + } + + $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); + break; + + case 'download/file': + $location = $user->lang['DOWNLOADING_FILE']; + $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); + break; + + case 'report': + $location = $user->lang['REPORTING_POST']; + $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); + break; + + default: + $location = $user->lang['INDEX']; + $location_url = append_sid("{$phpbb_root_path}index.$phpEx"); + + if ($row['session_page'] === 'app.' . $phpEx . '/help/faq' || + $row['session_page'] === 'app.' . $phpEx . '/help/bbcode') + { + $location = $user->lang['VIEWING_FAQ']; + $location_url = $controller_helper->route('phpbb_help_faq_controller'); + } + break; + } + + /** + * Overwrite the location's name and URL, which are displayed in the list + * + * @event core.viewonline_overwrite_location + * @var array on_page File name and query string + * @var array row Array with the users sql row + * @var string location Page name to displayed in the list + * @var string location_url Page url to displayed in the list + * @var array forum_data Array with forum data + * @since 3.1.0-a1 + * @changed 3.1.0-a2 Added var forum_data + */ + $vars = array('on_page', 'row', 'location', 'location_url', 'forum_data'); + extract($phpbb_dispatcher->trigger_event('core.viewonline_overwrite_location', compact($vars))); + + $template_row = array( + 'USERNAME' => $row['username'], + 'USERNAME_COLOUR' => $row['user_colour'], + 'USERNAME_FULL' => $username_full, + 'LASTUPDATE' => $user->format_date($row['session_time']), + 'FORUM_LOCATION' => $location, + 'USER_IP' => ($auth->acl_get('a_')) ? (($mode == 'lookup' && $session_id == $row['session_id']) ? gethostbyaddr($row['session_ip']) : $row['session_ip']) : '', + 'USER_BROWSER' => ($auth->acl_get('a_user')) ? $row['session_browser'] : '', + + 'U_USER_PROFILE' => ($row['user_type'] != USER_IGNORE) ? get_username_string('profile', $row['user_id'], '') : '', + 'U_USER_IP' => append_sid("{$phpbb_root_path}viewonline.$phpEx", 'mode=lookup' . (($mode != 'lookup' || $row['session_id'] != $session_id) ? '&s=' . $row['session_id'] : '') . "&sg=$show_guests&start=$start&sk=$sort_key&sd=$sort_dir"), + 'U_WHOIS' => append_sid("{$phpbb_root_path}viewonline.$phpEx", 'mode=whois&s=' . $row['session_id']), + 'U_FORUM_LOCATION' => $location_url, + + 'S_USER_HIDDEN' => $s_user_hidden, + 'S_GUEST' => ($row['user_id'] == ANONYMOUS) ? true : false, + 'S_USER_TYPE' => $row['user_type'], + ); + + /** + * Modify viewonline template data before it is displayed in the list + * + * @event core.viewonline_modify_user_row + * @var array on_page File name and query string + * @var array row Array with the users sql row + * @var array forum_data Array with forum data + * @var array template_row Array with template variables for the user row + * @since 3.1.0-RC4 + */ + $vars = array('on_page', 'row', 'forum_data', 'template_row'); + extract($phpbb_dispatcher->trigger_event('core.viewonline_modify_user_row', compact($vars))); + + $template->assign_block_vars('user_row', $template_row); +} +$db->sql_freeresult($result); +unset($prev_id, $prev_ip); + +$order_legend = ($config['legend_sort_groupname']) ? 'group_name' : 'group_legend'; +// Grab group details for legend display +if ($auth->acl_gets('a_group', 'a_groupadd', 'a_groupdel')) +{ + $sql = 'SELECT group_id, group_name, group_colour, group_type, group_legend + FROM ' . GROUPS_TABLE . ' + WHERE group_legend > 0 + ORDER BY ' . $order_legend . ' ASC'; +} +else +{ + $sql = 'SELECT g.group_id, g.group_name, g.group_colour, g.group_type, g.group_legend + FROM ' . GROUPS_TABLE . ' g + LEFT JOIN ' . USER_GROUP_TABLE . ' ug + ON ( + g.group_id = ug.group_id + AND ug.user_id = ' . $user->data['user_id'] . ' + AND ug.user_pending = 0 + ) + WHERE g.group_legend > 0 + AND (g.group_type <> ' . GROUP_HIDDEN . ' OR ug.user_id = ' . $user->data['user_id'] . ') + ORDER BY g.' . $order_legend . ' ASC'; +} +$result = $db->sql_query($sql); + +$legend = ''; +while ($row = $db->sql_fetchrow($result)) +{ + if ($row['group_name'] == 'BOTS') + { + $legend .= (($legend != '') ? ', ' : '') . '' . $user->lang['G_BOTS'] . ''; + } + else + { + $legend .= (($legend != '') ? ', ' : '') . '' . $group_helper->get_name($row['group_name']) . ''; + } +} +$db->sql_freeresult($result); + +// Refreshing the page every 60 seconds... +meta_refresh(60, append_sid("{$phpbb_root_path}viewonline.$phpEx", "sg=$show_guests&sk=$sort_key&sd=$sort_dir&start=$start")); + +$start = $pagination->validate_start($start, $config['topics_per_page'], $counter); +$base_url = append_sid("{$phpbb_root_path}viewonline.$phpEx", "sg=$show_guests&sk=$sort_key&sd=$sort_dir"); +$pagination->generate_template_pagination($base_url, 'pagination', 'start', $counter, $config['topics_per_page'], $start); + +// Send data to template +$template->assign_vars(array( + 'TOTAL_REGISTERED_USERS_ONLINE' => $user->lang('REG_USERS_ONLINE', (int) $logged_visible_online, $user->lang('HIDDEN_USERS_ONLINE', (int) $logged_hidden_online)), + 'TOTAL_GUEST_USERS_ONLINE' => $user->lang('GUEST_USERS_ONLINE', (int) $guest_counter), + 'LEGEND' => $legend, + + 'U_SORT_USERNAME' => append_sid("{$phpbb_root_path}viewonline.$phpEx", 'sk=a&sd=' . (($sort_key == 'a' && $sort_dir == 'a') ? 'd' : 'a') . '&sg=' . ((int) $show_guests)), + 'U_SORT_UPDATED' => append_sid("{$phpbb_root_path}viewonline.$phpEx", 'sk=b&sd=' . (($sort_key == 'b' && $sort_dir == 'a') ? 'd' : 'a') . '&sg=' . ((int) $show_guests)), + 'U_SORT_LOCATION' => append_sid("{$phpbb_root_path}viewonline.$phpEx", 'sk=c&sd=' . (($sort_key == 'c' && $sort_dir == 'a') ? 'd' : 'a') . '&sg=' . ((int) $show_guests)), + + 'U_SWITCH_GUEST_DISPLAY' => append_sid("{$phpbb_root_path}viewonline.$phpEx", 'sg=' . ((int) !$show_guests)), + 'L_SWITCH_GUEST_DISPLAY' => ($show_guests) ? $user->lang['HIDE_GUESTS'] : $user->lang['DISPLAY_GUESTS'], + 'S_SWITCH_GUEST_DISPLAY' => ($config['load_online_guests']) ? true : false, + 'S_VIEWONLINE' => true, +)); + +// We do not need to load the who is online box here. ;) +$config['load_online'] = false; + +// Output the page +page_header($user->lang['WHO_IS_ONLINE']); + +$template->set_filenames(array( + 'body' => 'viewonline_body.html') +); +make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx")); + +page_footer(); diff --git a/viewtopic.php b/viewtopic.php new file mode 100644 index 0000000..eb2d52c --- /dev/null +++ b/viewtopic.php @@ -0,0 +1,2380 @@ + +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +/** +* @ignore +*/ +define('IN_PHPBB', true); +$phpbb_root_path = (defined('PHPBB_ROOT_PATH')) ? PHPBB_ROOT_PATH : './'; +$phpEx = substr(strrchr(__FILE__, '.'), 1); +include($phpbb_root_path . 'common.' . $phpEx); +include($phpbb_root_path . 'includes/functions_display.' . $phpEx); +include($phpbb_root_path . 'includes/bbcode.' . $phpEx); +include($phpbb_root_path . 'includes/functions_user.' . $phpEx); + +// Start session management +$user->session_begin(); +$auth->acl($user->data); + +// Initial var setup +$forum_id = $request->variable('f', 0); +$topic_id = $request->variable('t', 0); +$post_id = $request->variable('p', 0); +$voted_id = $request->variable('vote_id', array('' => 0)); + +$voted_id = (count($voted_id) > 1) ? array_unique($voted_id) : $voted_id; + + +$start = $request->variable('start', 0); +$view = $request->variable('view', ''); + +$default_sort_days = (!empty($user->data['user_post_show_days'])) ? $user->data['user_post_show_days'] : 0; +$default_sort_key = (!empty($user->data['user_post_sortby_type'])) ? $user->data['user_post_sortby_type'] : 't'; +$default_sort_dir = (!empty($user->data['user_post_sortby_dir'])) ? $user->data['user_post_sortby_dir'] : 'a'; + +$sort_days = $request->variable('st', $default_sort_days); +$sort_key = $request->variable('sk', $default_sort_key); +$sort_dir = $request->variable('sd', $default_sort_dir); + +$update = $request->variable('update', false); + +/* @var $pagination \phpbb\pagination */ +$pagination = $phpbb_container->get('pagination'); + +$s_can_vote = false; +/** +* @todo normalize? +*/ +$hilit_words = $request->variable('hilit', '', true); + +// Do we have a topic or post id? +if (!$topic_id && !$post_id) +{ + trigger_error('NO_TOPIC'); +} + +/* @var $phpbb_content_visibility \phpbb\content_visibility */ +$phpbb_content_visibility = $phpbb_container->get('content.visibility'); + +// Find topic id if user requested a newer or older topic +if ($view && !$post_id) +{ + if (!$forum_id) + { + $sql = 'SELECT forum_id + FROM ' . TOPICS_TABLE . " + WHERE topic_id = $topic_id"; + $result = $db->sql_query($sql); + $forum_id = (int) $db->sql_fetchfield('forum_id'); + $db->sql_freeresult($result); + + if (!$forum_id) + { + trigger_error('NO_TOPIC'); + } + } + + if ($view == 'unread') + { + // Get topic tracking info + $topic_tracking_info = get_complete_topic_tracking($forum_id, $topic_id); + $topic_last_read = (isset($topic_tracking_info[$topic_id])) ? $topic_tracking_info[$topic_id] : 0; + + $sql = 'SELECT post_id, topic_id, forum_id + FROM ' . POSTS_TABLE . " + WHERE topic_id = $topic_id + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id) . " + AND post_time > $topic_last_read + AND forum_id = $forum_id + ORDER BY post_time ASC, post_id ASC"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $sql = 'SELECT topic_last_post_id as post_id, topic_id, forum_id + FROM ' . TOPICS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + } + + if (!$row) + { + // Setup user environment so we can process lang string + $user->setup('viewtopic'); + + trigger_error('NO_TOPIC'); + } + + $post_id = $row['post_id']; + $topic_id = $row['topic_id']; + } + else if ($view == 'next' || $view == 'previous') + { + $sql_condition = ($view == 'next') ? '>' : '<'; + $sql_ordering = ($view == 'next') ? 'ASC' : 'DESC'; + + $sql = 'SELECT forum_id, topic_last_post_time + FROM ' . TOPICS_TABLE . ' + WHERE topic_id = ' . $topic_id; + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $user->setup('viewtopic'); + // OK, the topic doesn't exist. This error message is not helpful, but technically correct. + trigger_error(($view == 'next') ? 'NO_NEWER_TOPICS' : 'NO_OLDER_TOPICS'); + } + else + { + $sql = 'SELECT topic_id, forum_id + FROM ' . TOPICS_TABLE . ' + WHERE forum_id = ' . $row['forum_id'] . " + AND topic_moved_id = 0 + AND topic_last_post_time $sql_condition {$row['topic_last_post_time']} + AND " . $phpbb_content_visibility->get_visibility_sql('topic', $row['forum_id']) . " + ORDER BY topic_last_post_time $sql_ordering, topic_last_post_id $sql_ordering"; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $sql = 'SELECT forum_style + FROM ' . FORUMS_TABLE . " + WHERE forum_id = $forum_id"; + $result = $db->sql_query($sql); + $forum_style = (int) $db->sql_fetchfield('forum_style'); + $db->sql_freeresult($result); + + $user->setup('viewtopic', $forum_style); + trigger_error(($view == 'next') ? 'NO_NEWER_TOPICS' : 'NO_OLDER_TOPICS'); + } + else + { + $topic_id = $row['topic_id']; + $forum_id = $row['forum_id']; + } + } + } + + if (isset($row) && $row['forum_id']) + { + $forum_id = $row['forum_id']; + } +} + +// This rather complex gaggle of code handles querying for topics but +// also allows for direct linking to a post (and the calculation of which +// page the post is on and the correct display of viewtopic) +$sql_array = array( + 'SELECT' => 't.*, f.*', + + 'FROM' => array(FORUMS_TABLE => 'f'), +); + +// The FROM-Order is quite important here, else t.* columns can not be correctly bound. +if ($post_id) +{ + $sql_array['SELECT'] .= ', p.post_visibility, p.post_time, p.post_id'; + $sql_array['FROM'][POSTS_TABLE] = 'p'; +} + +// Topics table need to be the last in the chain +$sql_array['FROM'][TOPICS_TABLE] = 't'; + +if ($user->data['is_registered']) +{ + $sql_array['SELECT'] .= ', tw.notify_status'; + $sql_array['LEFT_JOIN'] = array(); + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_WATCH_TABLE => 'tw'), + 'ON' => 'tw.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = tw.topic_id' + ); + + if ($config['allow_bookmarks']) + { + $sql_array['SELECT'] .= ', bm.topic_id as bookmarked'; + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(BOOKMARKS_TABLE => 'bm'), + 'ON' => 'bm.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = bm.topic_id' + ); + } + + if ($config['load_db_lastread']) + { + $sql_array['SELECT'] .= ', tt.mark_time, ft.mark_time as forum_mark_time'; + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'), + 'ON' => 'tt.user_id = ' . $user->data['user_id'] . ' AND t.topic_id = tt.topic_id' + ); + + $sql_array['LEFT_JOIN'][] = array( + 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'), + 'ON' => 'ft.user_id = ' . $user->data['user_id'] . ' AND t.forum_id = ft.forum_id' + ); + } +} + +if (!$post_id) +{ + $sql_array['WHERE'] = "t.topic_id = $topic_id"; +} +else +{ + $sql_array['WHERE'] = "p.post_id = $post_id AND t.topic_id = p.topic_id"; +} + +$sql_array['WHERE'] .= ' AND f.forum_id = t.forum_id'; + +$sql = $db->sql_build_query('SELECT', $sql_array); +$result = $db->sql_query($sql); +$topic_data = $db->sql_fetchrow($result); +$db->sql_freeresult($result); + +// link to unapproved post or incorrect link +if (!$topic_data) +{ + // If post_id was submitted, we try at least to display the topic as a last resort... + if ($post_id && $topic_id) + { + redirect(append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($forum_id) ? "&f=$forum_id" : ''))); + } + + trigger_error('NO_TOPIC'); +} + +$forum_id = (int) $topic_data['forum_id']; + +/** + * Modify the forum ID to handle the correct display of viewtopic if needed + * + * @event core.viewtopic_modify_forum_id + * @var string forum_id forum ID + * @var array topic_data array of topic's data + * @since 3.2.5-RC1 + */ +$vars = array( + 'forum_id', + 'topic_data', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_forum_id', compact($vars))); + +// If the request is missing the f parameter, the forum id in the user session data is 0 at the moment. +// Let's fix that now so that the user can't hide from the forum's Who Is Online list. +$user->page['forum'] = $forum_id; + +// Now we know the forum_id and can check the permissions +if (!$phpbb_content_visibility->is_visible('topic', $forum_id, $topic_data)) +{ + trigger_error('NO_TOPIC'); +} + +// This is for determining where we are (page) +if ($post_id) +{ + // are we where we are supposed to be? + if (($topic_data['post_visibility'] == ITEM_UNAPPROVED || $topic_data['post_visibility'] == ITEM_REAPPROVE) && !$auth->acl_get('m_approve', $topic_data['forum_id'])) + { + // If post_id was submitted, we try at least to display the topic as a last resort... + if ($topic_id) + { + redirect(append_sid("{$phpbb_root_path}viewtopic.$phpEx", "t=$topic_id" . (($forum_id) ? "&f=$forum_id" : ''))); + } + + trigger_error('NO_TOPIC'); + } + if ($post_id == $topic_data['topic_first_post_id'] || $post_id == $topic_data['topic_last_post_id']) + { + $check_sort = ($post_id == $topic_data['topic_first_post_id']) ? 'd' : 'a'; + + if ($sort_dir == $check_sort) + { + $topic_data['prev_posts'] = $phpbb_content_visibility->get_count('topic_posts', $topic_data, $forum_id) - 1; + } + else + { + $topic_data['prev_posts'] = 0; + } + } + else + { + $sql = 'SELECT COUNT(p.post_id) AS prev_posts + FROM ' . POSTS_TABLE . " p + WHERE p.topic_id = {$topic_data['topic_id']} + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id, 'p.'); + + if ($sort_dir == 'd') + { + $sql .= " AND (p.post_time > {$topic_data['post_time']} OR (p.post_time = {$topic_data['post_time']} AND p.post_id >= {$topic_data['post_id']}))"; + } + else + { + $sql .= " AND (p.post_time < {$topic_data['post_time']} OR (p.post_time = {$topic_data['post_time']} AND p.post_id <= {$topic_data['post_id']}))"; + } + + $result = $db->sql_query($sql); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + $topic_data['prev_posts'] = $row['prev_posts'] - 1; + } +} + +$topic_id = (int) $topic_data['topic_id']; +$topic_replies = $phpbb_content_visibility->get_count('topic_posts', $topic_data, $forum_id) - 1; + +// Check sticky/announcement/global time limit +if (($topic_data['topic_type'] != POST_NORMAL) && $topic_data['topic_time_limit'] && ($topic_data['topic_time'] + $topic_data['topic_time_limit']) < time()) +{ + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_type = ' . POST_NORMAL . ', topic_time_limit = 0 + WHERE topic_id = ' . $topic_id; + $db->sql_query($sql); + + $topic_data['topic_type'] = POST_NORMAL; + $topic_data['topic_time_limit'] = 0; +} + +// Setup look and feel +$user->setup('viewtopic', $topic_data['forum_style']); + +if ($view == 'print' && !$auth->acl_get('f_print', $forum_id)) +{ + send_status_line(403, 'Forbidden'); + trigger_error('NO_AUTH_PRINT_TOPIC'); +} + +$overrides_f_read_check = false; +$overrides_forum_password_check = false; +$topic_tracking_info = isset($topic_tracking_info) ? $topic_tracking_info : null; + +/** +* Event to apply extra permissions and to override original phpBB's f_read permission and forum password check +* on viewtopic access +* +* @event core.viewtopic_before_f_read_check +* @var int forum_id The forum id from where the topic belongs +* @var int topic_id The id of the topic the user tries to access +* @var int post_id The id of the post the user tries to start viewing at. +* It may be 0 for none given. +* @var array topic_data All the information from the topic and forum tables for this topic +* It includes posts information if post_id is not 0 +* @var bool overrides_f_read_check Set true to remove f_read check afterwards +* @var bool overrides_forum_password_check Set true to remove forum_password check afterwards +* @var array topic_tracking_info Information upon calling get_topic_tracking() +* Set it to NULL to allow auto-filling later. +* Set it to an array to override original data. +* @since 3.1.3-RC1 +*/ +$vars = array( + 'forum_id', + 'topic_id', + 'post_id', + 'topic_data', + 'overrides_f_read_check', + 'overrides_forum_password_check', + 'topic_tracking_info', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_before_f_read_check', compact($vars))); + +// Start auth check +if (!$overrides_f_read_check && !$auth->acl_get('f_read', $forum_id)) +{ + if ($user->data['user_id'] != ANONYMOUS) + { + send_status_line(403, 'Forbidden'); + trigger_error('SORRY_AUTH_READ'); + } + + login_box('', $user->lang['LOGIN_VIEWFORUM']); +} + +// Forum is passworded ... check whether access has been granted to this +// user this session, if not show login box +if (!$overrides_forum_password_check && $topic_data['forum_password']) +{ + login_forum_box($topic_data); +} + +// Redirect to login upon emailed notification links if user is not logged in. +if (isset($_GET['e']) && $user->data['user_id'] == ANONYMOUS) +{ + login_box(build_url('e') . '#unread', $user->lang['LOGIN_NOTIFY_TOPIC']); +} + +// What is start equal to? +if ($post_id) +{ + $start = floor(($topic_data['prev_posts']) / $config['posts_per_page']) * $config['posts_per_page']; +} + +// Get topic tracking info +if (!isset($topic_tracking_info)) +{ + $topic_tracking_info = array(); + + // Get topic tracking info + if ($config['load_db_lastread'] && $user->data['is_registered']) + { + $tmp_topic_data = array($topic_id => $topic_data); + $topic_tracking_info = get_topic_tracking($forum_id, $topic_id, $tmp_topic_data, array($forum_id => $topic_data['forum_mark_time'])); + unset($tmp_topic_data); + } + else if ($config['load_anon_lastread'] || $user->data['is_registered']) + { + $topic_tracking_info = get_complete_topic_tracking($forum_id, $topic_id); + } +} + +// Post ordering options +$limit_days = array(0 => $user->lang['ALL_POSTS'], 1 => $user->lang['1_DAY'], 7 => $user->lang['7_DAYS'], 14 => $user->lang['2_WEEKS'], 30 => $user->lang['1_MONTH'], 90 => $user->lang['3_MONTHS'], 180 => $user->lang['6_MONTHS'], 365 => $user->lang['1_YEAR']); + +$sort_by_text = array('a' => $user->lang['AUTHOR'], 't' => $user->lang['POST_TIME'], 's' => $user->lang['SUBJECT']); +$sort_by_sql = array('a' => array('u.username_clean', 'p.post_id'), 't' => array('p.post_time', 'p.post_id'), 's' => array('p.post_subject', 'p.post_id')); +$join_user_sql = array('a' => true, 't' => false, 's' => false); + +$s_limit_days = $s_sort_key = $s_sort_dir = $u_sort_param = ''; + +gen_sort_selects($limit_days, $sort_by_text, $sort_days, $sort_key, $sort_dir, $s_limit_days, $s_sort_key, $s_sort_dir, $u_sort_param, $default_sort_days, $default_sort_key, $default_sort_dir); + +// Obtain correct post count and ordering SQL if user has +// requested anything different +if ($sort_days) +{ + $min_post_time = time() - ($sort_days * 86400); + + $sql = 'SELECT COUNT(post_id) AS num_posts + FROM ' . POSTS_TABLE . " + WHERE topic_id = $topic_id + AND post_time >= $min_post_time + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id); + $result = $db->sql_query($sql); + $total_posts = (int) $db->sql_fetchfield('num_posts'); + $db->sql_freeresult($result); + + $limit_posts_time = "AND p.post_time >= $min_post_time "; + + if (isset($_POST['sort'])) + { + $start = 0; + } +} +else +{ + $total_posts = $topic_replies + 1; + $limit_posts_time = ''; +} + +// Was a highlight request part of the URI? +$highlight_match = $highlight = ''; +if ($hilit_words) +{ + $highlight_match = phpbb_clean_search_string($hilit_words); + $highlight = urlencode($highlight_match); + $highlight_match = str_replace('\*', '\w+?', preg_quote($highlight_match, '#')); + $highlight_match = preg_replace('#(?<=^|\s)\\\\w\*\?(?=\s|$)#', '\w+?', $highlight_match); + $highlight_match = str_replace(' ', '|', $highlight_match); +} + +// Make sure $start is set to the last page if it exceeds the amount +$start = $pagination->validate_start($start, $config['posts_per_page'], $total_posts); + +// General Viewtopic URL for return links +$viewtopic_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id" . (($start == 0) ? '' : "&start=$start") . ((strlen($u_sort_param)) ? "&$u_sort_param" : '') . (($highlight_match) ? "&hilit=$highlight" : '')); + +// Are we watching this topic? +$s_watching_topic = array( + 'link' => '', + 'link_toggle' => '', + 'title' => '', + 'title_toggle' => '', + 'is_watching' => false, +); + +if ($config['allow_topic_notify']) +{ + $notify_status = (isset($topic_data['notify_status'])) ? $topic_data['notify_status'] : null; + watch_topic_forum('topic', $s_watching_topic, $user->data['user_id'], $forum_id, $topic_id, $notify_status, $start, $topic_data['topic_title']); + + // Reset forum notification if forum notify is set + if ($config['allow_forum_notify'] && $auth->acl_get('f_subscribe', $forum_id)) + { + $s_watching_forum = $s_watching_topic; + watch_topic_forum('forum', $s_watching_forum, $user->data['user_id'], $forum_id, 0); + } +} + +/** +* Event to modify highlight. +* +* @event core.viewtopic_highlight_modify +* @var string highlight String to be highlighted +* @var string highlight_match Highlight string to be used in preg_replace +* @var array topic_data Topic data +* @var int start Pagination start +* @var int total_posts Number of posts +* @var string viewtopic_url Current viewtopic URL +* @since 3.1.11-RC1 +*/ +$vars = array( + 'highlight', + 'highlight_match', + 'topic_data', + 'start', + 'total_posts', + 'viewtopic_url', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_highlight_modify', compact($vars))); + +// Bookmarks +if ($config['allow_bookmarks'] && $user->data['is_registered'] && $request->variable('bookmark', 0)) +{ + if (check_link_hash($request->variable('hash', ''), "topic_$topic_id")) + { + if (!$topic_data['bookmarked']) + { + $sql = 'INSERT INTO ' . BOOKMARKS_TABLE . ' ' . $db->sql_build_array('INSERT', array( + 'user_id' => $user->data['user_id'], + 'topic_id' => $topic_id, + )); + $db->sql_query($sql); + } + else + { + $sql = 'DELETE FROM ' . BOOKMARKS_TABLE . " + WHERE user_id = {$user->data['user_id']} + AND topic_id = $topic_id"; + $db->sql_query($sql); + } + $message = (($topic_data['bookmarked']) ? $user->lang['BOOKMARK_REMOVED'] : $user->lang['BOOKMARK_ADDED']); + + if (!$request->is_ajax()) + { + $message .= '

    ' . $user->lang('RETURN_TOPIC', '', ''); + } + } + else + { + $message = $user->lang['BOOKMARK_ERR']; + + if (!$request->is_ajax()) + { + $message .= '

    ' . $user->lang('RETURN_TOPIC', '', ''); + } + } + meta_refresh(3, $viewtopic_url); + + trigger_error($message); +} + +// Grab ranks +$ranks = $cache->obtain_ranks(); + +// Grab icons +$icons = $cache->obtain_icons(); + +// Grab extensions +$extensions = array(); +if ($topic_data['topic_attachment']) +{ + $extensions = $cache->obtain_attach_extensions($forum_id); +} + +// Forum rules listing +$s_forum_rules = ''; +gen_forum_auth_level('topic', $forum_id, $topic_data['forum_status']); + +// Quick mod tools +$allow_change_type = ($auth->acl_get('m_', $forum_id) || ($user->data['is_registered'] && $user->data['user_id'] == $topic_data['topic_poster'])) ? true : false; + +$s_quickmod_action = append_sid( + "{$phpbb_root_path}mcp.$phpEx", + array( + 'f' => $forum_id, + 't' => $topic_id, + 'start' => $start, + 'quickmod' => 1, + 'redirect' => urlencode(str_replace('&', '&', $viewtopic_url)), + ), + true, + $user->session_id +); + +$quickmod_array = array( +// 'key' => array('LANG_KEY', $userHasPermissions), + + 'lock' => array('LOCK_TOPIC', ($topic_data['topic_status'] == ITEM_UNLOCKED) && ($auth->acl_get('m_lock', $forum_id) || ($auth->acl_get('f_user_lock', $forum_id) && $user->data['is_registered'] && $user->data['user_id'] == $topic_data['topic_poster']))), + 'unlock' => array('UNLOCK_TOPIC', ($topic_data['topic_status'] != ITEM_UNLOCKED) && ($auth->acl_get('m_lock', $forum_id))), + 'delete_topic' => array('DELETE_TOPIC', ($auth->acl_get('m_delete', $forum_id) || (($topic_data['topic_visibility'] != ITEM_DELETED) && $auth->acl_get('m_softdelete', $forum_id)))), + 'restore_topic' => array('RESTORE_TOPIC', (($topic_data['topic_visibility'] == ITEM_DELETED) && $auth->acl_get('m_approve', $forum_id))), + 'move' => array('MOVE_TOPIC', $auth->acl_get('m_move', $forum_id) && $topic_data['topic_status'] != ITEM_MOVED), + 'split' => array('SPLIT_TOPIC', $auth->acl_get('m_split', $forum_id)), + 'merge' => array('MERGE_POSTS', $auth->acl_get('m_merge', $forum_id)), + 'merge_topic' => array('MERGE_TOPIC', $auth->acl_get('m_merge', $forum_id)), + 'fork' => array('FORK_TOPIC', $auth->acl_get('m_move', $forum_id)), + 'make_normal' => array('MAKE_NORMAL', ($allow_change_type && $auth->acl_gets('f_sticky', 'f_announce', 'f_announce_global', $forum_id) && $topic_data['topic_type'] != POST_NORMAL)), + 'make_sticky' => array('MAKE_STICKY', ($allow_change_type && $auth->acl_get('f_sticky', $forum_id) && $topic_data['topic_type'] != POST_STICKY)), + 'make_announce' => array('MAKE_ANNOUNCE', ($allow_change_type && $auth->acl_get('f_announce', $forum_id) && $topic_data['topic_type'] != POST_ANNOUNCE)), + 'make_global' => array('MAKE_GLOBAL', ($allow_change_type && $auth->acl_get('f_announce_global', $forum_id) && $topic_data['topic_type'] != POST_GLOBAL)), + 'topic_logs' => array('VIEW_TOPIC_LOGS', $auth->acl_get('m_', $forum_id)), +); + +/** +* Event to modify data in the quickmod_array before it gets sent to the +* phpbb_add_quickmod_option function. +* +* @event core.viewtopic_add_quickmod_option_before +* @var int forum_id Forum ID +* @var int post_id Post ID +* @var array quickmod_array Array with quick moderation options data +* @var array topic_data Array with topic data +* @var int topic_id Topic ID +* @var array topic_tracking_info Array with topic tracking data +* @var string viewtopic_url URL to the topic page +* @var bool allow_change_type Topic change permissions check +* @since 3.1.9-RC1 +*/ +$vars = array( + 'forum_id', + 'post_id', + 'quickmod_array', + 'topic_data', + 'topic_id', + 'topic_tracking_info', + 'viewtopic_url', + 'allow_change_type', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_add_quickmod_option_before', compact($vars))); + +foreach ($quickmod_array as $option => $qm_ary) +{ + if (!empty($qm_ary[1])) + { + phpbb_add_quickmod_option($s_quickmod_action, $option, $qm_ary[0]); + } +} + +// Navigation links +generate_forum_nav($topic_data); + +// Forum Rules +generate_forum_rules($topic_data); + +// Moderators +$forum_moderators = array(); +if ($config['load_moderators']) +{ + get_moderators($forum_moderators, $forum_id); +} + +// This is only used for print view so ... +$server_path = (!$view) ? $phpbb_root_path : generate_board_url() . '/'; + +// Replace naughty words in title +$topic_data['topic_title'] = censor_text($topic_data['topic_title']); + +$s_search_hidden_fields = array( + 't' => $topic_id, + 'sf' => 'msgonly', +); +if ($_SID) +{ + $s_search_hidden_fields['sid'] = $_SID; +} + +if (!empty($_EXTRA_URL)) +{ + foreach ($_EXTRA_URL as $url_param) + { + $url_param = explode('=', $url_param, 2); + $s_search_hidden_fields[$url_param[0]] = $url_param[1]; + } +} + +// If we've got a hightlight set pass it on to pagination. +$base_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id" . ((strlen($u_sort_param)) ? "&$u_sort_param" : '') . (($highlight_match) ? "&hilit=$highlight" : '')); + +/** +* Event to modify data before template variables are being assigned +* +* @event core.viewtopic_assign_template_vars_before +* @var string base_url URL to be passed to generate pagination +* @var int forum_id Forum ID +* @var int post_id Post ID +* @var array quickmod_array Array with quick moderation options data +* @var int start Pagination information +* @var array topic_data Array with topic data +* @var int topic_id Topic ID +* @var array topic_tracking_info Array with topic tracking data +* @var int total_posts Topic total posts count +* @var string viewtopic_url URL to the topic page +* @since 3.1.0-RC4 +* @changed 3.1.2-RC1 Added viewtopic_url +*/ +$vars = array( + 'base_url', + 'forum_id', + 'post_id', + 'quickmod_array', + 'start', + 'topic_data', + 'topic_id', + 'topic_tracking_info', + 'total_posts', + 'viewtopic_url', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_assign_template_vars_before', compact($vars))); + +$pagination->generate_template_pagination($base_url, 'pagination', 'start', $total_posts, $config['posts_per_page'], $start); + +// Send vars to template +$template->assign_vars(array( + 'FORUM_ID' => $forum_id, + 'FORUM_NAME' => $topic_data['forum_name'], + 'FORUM_DESC' => generate_text_for_display($topic_data['forum_desc'], $topic_data['forum_desc_uid'], $topic_data['forum_desc_bitfield'], $topic_data['forum_desc_options']), + 'TOPIC_ID' => $topic_id, + 'TOPIC_TITLE' => $topic_data['topic_title'], + 'TOPIC_POSTER' => $topic_data['topic_poster'], + + 'TOPIC_AUTHOR_FULL' => get_username_string('full', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']), + 'TOPIC_AUTHOR_COLOUR' => get_username_string('colour', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']), + 'TOPIC_AUTHOR' => get_username_string('username', $topic_data['topic_poster'], $topic_data['topic_first_poster_name'], $topic_data['topic_first_poster_colour']), + + 'TOTAL_POSTS' => $user->lang('VIEW_TOPIC_POSTS', (int) $total_posts), + 'U_MCP' => ($auth->acl_get('m_', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&mode=topic_view&f=$forum_id&t=$topic_id" . (($start == 0) ? '' : "&start=$start") . ((strlen($u_sort_param)) ? "&$u_sort_param" : ''), true, $user->session_id) : '', + 'MODERATORS' => (isset($forum_moderators[$forum_id]) && count($forum_moderators[$forum_id])) ? implode($user->lang['COMMA_SEPARATOR'], $forum_moderators[$forum_id]) : '', + + 'POST_IMG' => ($topic_data['forum_status'] == ITEM_LOCKED) ? $user->img('button_topic_locked', 'FORUM_LOCKED') : $user->img('button_topic_new', 'POST_NEW_TOPIC'), + 'QUOTE_IMG' => $user->img('icon_post_quote', 'REPLY_WITH_QUOTE'), + 'REPLY_IMG' => ($topic_data['forum_status'] == ITEM_LOCKED || $topic_data['topic_status'] == ITEM_LOCKED) ? $user->img('button_topic_locked', 'TOPIC_LOCKED') : $user->img('button_topic_reply', 'REPLY_TO_TOPIC'), + 'EDIT_IMG' => $user->img('icon_post_edit', 'EDIT_POST'), + 'DELETE_IMG' => $user->img('icon_post_delete', 'DELETE_POST'), + 'DELETED_IMG' => $user->img('icon_topic_deleted', 'POST_DELETED_RESTORE'), + 'INFO_IMG' => $user->img('icon_post_info', 'VIEW_INFO'), + 'PROFILE_IMG' => $user->img('icon_user_profile', 'READ_PROFILE'), + 'SEARCH_IMG' => $user->img('icon_user_search', 'SEARCH_USER_POSTS'), + 'PM_IMG' => $user->img('icon_contact_pm', 'SEND_PRIVATE_MESSAGE'), + 'EMAIL_IMG' => $user->img('icon_contact_email', 'SEND_EMAIL'), + 'JABBER_IMG' => $user->img('icon_contact_jabber', 'JABBER') , + 'REPORT_IMG' => $user->img('icon_post_report', 'REPORT_POST'), + 'REPORTED_IMG' => $user->img('icon_topic_reported', 'POST_REPORTED'), + 'UNAPPROVED_IMG' => $user->img('icon_topic_unapproved', 'POST_UNAPPROVED'), + 'WARN_IMG' => $user->img('icon_user_warn', 'WARN_USER'), + + 'S_IS_LOCKED' => ($topic_data['topic_status'] == ITEM_UNLOCKED && $topic_data['forum_status'] == ITEM_UNLOCKED) ? false : true, + 'S_SELECT_SORT_DIR' => $s_sort_dir, + 'S_SELECT_SORT_KEY' => $s_sort_key, + 'S_SELECT_SORT_DAYS' => $s_limit_days, + 'S_SINGLE_MODERATOR' => (!empty($forum_moderators[$forum_id]) && count($forum_moderators[$forum_id]) > 1) ? false : true, + 'S_TOPIC_ACTION' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id" . (($start == 0) ? '' : "&start=$start")), + 'S_MOD_ACTION' => $s_quickmod_action, + + 'L_RETURN_TO_FORUM' => $user->lang('RETURN_TO', $topic_data['forum_name']), + 'S_VIEWTOPIC' => true, + 'S_UNREAD_VIEW' => $view == 'unread', + 'S_DISPLAY_SEARCHBOX' => ($auth->acl_get('u_search') && $auth->acl_get('f_search', $forum_id) && $config['load_search']) ? true : false, + 'S_SEARCHBOX_ACTION' => append_sid("{$phpbb_root_path}search.$phpEx"), + 'S_SEARCH_LOCAL_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields), + + 'S_DISPLAY_POST_INFO' => ($topic_data['forum_type'] == FORUM_POST && ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS)) ? true : false, + 'S_DISPLAY_REPLY_INFO' => ($topic_data['forum_type'] == FORUM_POST && ($auth->acl_get('f_reply', $forum_id) || $user->data['user_id'] == ANONYMOUS)) ? true : false, + 'S_ENABLE_FEEDS_TOPIC' => ($config['feed_topic'] && !phpbb_optionget(FORUM_OPTION_FEED_EXCLUDE, $topic_data['forum_options'])) ? true : false, + + 'U_TOPIC' => "{$server_path}viewtopic.$phpEx?f=$forum_id&t=$topic_id", + 'U_FORUM' => $server_path, + 'U_VIEW_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id" . (($start == 0) ? '' : "&start=$start") . (strlen($u_sort_param) ? "&$u_sort_param" : '')), + 'U_CANONICAL' => generate_board_url() . '/' . append_sid("viewtopic.$phpEx", "t=$topic_id" . (($start) ? "&start=$start" : ''), true, ''), + 'U_VIEW_FORUM' => append_sid("{$phpbb_root_path}viewforum.$phpEx", 'f=' . $forum_id), + 'U_VIEW_OLDER_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&view=previous"), + 'U_VIEW_NEWER_TOPIC' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&view=next"), + 'U_PRINT_TOPIC' => ($auth->acl_get('f_print', $forum_id)) ? $viewtopic_url . '&view=print' : '', + 'U_EMAIL_TOPIC' => ($auth->acl_get('f_email', $forum_id) && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=email&t=$topic_id") : '', + + 'U_WATCH_TOPIC' => $s_watching_topic['link'], + 'U_WATCH_TOPIC_TOGGLE' => $s_watching_topic['link_toggle'], + 'S_WATCH_TOPIC_TITLE' => $s_watching_topic['title'], + 'S_WATCH_TOPIC_TOGGLE' => $s_watching_topic['title_toggle'], + 'S_WATCHING_TOPIC' => $s_watching_topic['is_watching'], + + 'U_BOOKMARK_TOPIC' => ($user->data['is_registered'] && $config['allow_bookmarks']) ? $viewtopic_url . '&bookmark=1&hash=' . generate_link_hash("topic_$topic_id") : '', + 'S_BOOKMARK_TOPIC' => ($user->data['is_registered'] && $config['allow_bookmarks'] && $topic_data['bookmarked']) ? $user->lang['BOOKMARK_TOPIC_REMOVE'] : $user->lang['BOOKMARK_TOPIC'], + 'S_BOOKMARK_TOGGLE' => (!$user->data['is_registered'] || !$config['allow_bookmarks'] || !$topic_data['bookmarked']) ? $user->lang['BOOKMARK_TOPIC_REMOVE'] : $user->lang['BOOKMARK_TOPIC'], + 'S_BOOKMARKED_TOPIC' => ($user->data['is_registered'] && $config['allow_bookmarks'] && $topic_data['bookmarked']) ? true : false, + + 'U_POST_NEW_TOPIC' => ($auth->acl_get('f_post', $forum_id) || $user->data['user_id'] == ANONYMOUS) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=post&f=$forum_id") : '', + 'U_POST_REPLY_TOPIC' => ($auth->acl_get('f_reply', $forum_id) || $user->data['user_id'] == ANONYMOUS) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=reply&f=$forum_id&t=$topic_id") : '', + 'U_BUMP_TOPIC' => (bump_topic_allowed($forum_id, $topic_data['topic_bumped'], $topic_data['topic_last_post_time'], $topic_data['topic_poster'], $topic_data['topic_last_poster_id'])) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=bump&f=$forum_id&t=$topic_id&hash=" . generate_link_hash("topic_$topic_id")) : '') +); + +// Does this topic contain a poll? +if (!empty($topic_data['poll_start'])) +{ + $sql = 'SELECT o.*, p.bbcode_bitfield, p.bbcode_uid + FROM ' . POLL_OPTIONS_TABLE . ' o, ' . POSTS_TABLE . " p + WHERE o.topic_id = $topic_id + AND p.post_id = {$topic_data['topic_first_post_id']} + AND p.topic_id = o.topic_id + ORDER BY o.poll_option_id"; + $result = $db->sql_query($sql); + + $poll_info = $vote_counts = array(); + while ($row = $db->sql_fetchrow($result)) + { + $poll_info[] = $row; + $option_id = (int) $row['poll_option_id']; + $vote_counts[$option_id] = (int) $row['poll_option_total']; + } + $db->sql_freeresult($result); + + $cur_voted_id = array(); + if ($user->data['is_registered']) + { + $sql = 'SELECT poll_option_id + FROM ' . POLL_VOTES_TABLE . ' + WHERE topic_id = ' . $topic_id . ' + AND vote_user_id = ' . $user->data['user_id']; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $cur_voted_id[] = $row['poll_option_id']; + } + $db->sql_freeresult($result); + } + else + { + // Cookie based guest tracking ... I don't like this but hum ho + // it's oft requested. This relies on "nice" users who don't feel + // the need to delete cookies to mess with results. + if ($request->is_set($config['cookie_name'] . '_poll_' . $topic_id, \phpbb\request\request_interface::COOKIE)) + { + $cur_voted_id = explode(',', $request->variable($config['cookie_name'] . '_poll_' . $topic_id, '', true, \phpbb\request\request_interface::COOKIE)); + $cur_voted_id = array_map('intval', $cur_voted_id); + } + } + + // Can not vote at all if no vote permission + $s_can_vote = ($auth->acl_get('f_vote', $forum_id) && + (($topic_data['poll_length'] != 0 && $topic_data['poll_start'] + $topic_data['poll_length'] > time()) || $topic_data['poll_length'] == 0) && + $topic_data['topic_status'] != ITEM_LOCKED && + $topic_data['forum_status'] != ITEM_LOCKED && + (!count($cur_voted_id) || + ($auth->acl_get('f_votechg', $forum_id) && $topic_data['poll_vote_change']))) ? true : false; + $s_display_results = (!$s_can_vote || ($s_can_vote && count($cur_voted_id)) || $view == 'viewpoll') ? true : false; + + /** + * Event to manipulate the poll data + * + * @event core.viewtopic_modify_poll_data + * @var array cur_voted_id Array with options' IDs current user has voted for + * @var int forum_id The topic's forum id + * @var array poll_info Array with the poll information + * @var bool s_can_vote Flag indicating if a user can vote + * @var bool s_display_results Flag indicating if results or poll options should be displayed + * @var int topic_id The id of the topic the user tries to access + * @var array topic_data All the information from the topic and forum tables for this topic + * @var string viewtopic_url URL to the topic page + * @var array vote_counts Array with the vote counts for every poll option + * @var array voted_id Array with updated options' IDs current user is voting for + * @since 3.1.5-RC1 + */ + $vars = array( + 'cur_voted_id', + 'forum_id', + 'poll_info', + 's_can_vote', + 's_display_results', + 'topic_id', + 'topic_data', + 'viewtopic_url', + 'vote_counts', + 'voted_id', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_data', compact($vars))); + + if ($update && $s_can_vote) + { + + if (!count($voted_id) || count($voted_id) > $topic_data['poll_max_options'] || in_array(VOTE_CONVERTED, $cur_voted_id) || !check_form_key('posting')) + { + $redirect_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id" . (($start == 0) ? '' : "&start=$start")); + + meta_refresh(5, $redirect_url); + if (!count($voted_id)) + { + $message = 'NO_VOTE_OPTION'; + } + else if (count($voted_id) > $topic_data['poll_max_options']) + { + $message = 'TOO_MANY_VOTE_OPTIONS'; + } + else if (in_array(VOTE_CONVERTED, $cur_voted_id)) + { + $message = 'VOTE_CONVERTED'; + } + else + { + $message = 'FORM_INVALID'; + } + + $message = $user->lang[$message] . '

    ' . sprintf($user->lang['RETURN_TOPIC'], '', ''); + trigger_error($message); + } + + foreach ($voted_id as $option) + { + if (in_array($option, $cur_voted_id)) + { + continue; + } + + $sql = 'UPDATE ' . POLL_OPTIONS_TABLE . ' + SET poll_option_total = poll_option_total + 1 + WHERE poll_option_id = ' . (int) $option . ' + AND topic_id = ' . (int) $topic_id; + $db->sql_query($sql); + + $vote_counts[$option]++; + + if ($user->data['is_registered']) + { + $sql_ary = array( + 'topic_id' => (int) $topic_id, + 'poll_option_id' => (int) $option, + 'vote_user_id' => (int) $user->data['user_id'], + 'vote_user_ip' => (string) $user->ip, + ); + + $sql = 'INSERT INTO ' . POLL_VOTES_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary); + $db->sql_query($sql); + } + } + + foreach ($cur_voted_id as $option) + { + if (!in_array($option, $voted_id)) + { + $sql = 'UPDATE ' . POLL_OPTIONS_TABLE . ' + SET poll_option_total = poll_option_total - 1 + WHERE poll_option_id = ' . (int) $option . ' + AND topic_id = ' . (int) $topic_id; + $db->sql_query($sql); + + $vote_counts[$option]--; + + if ($user->data['is_registered']) + { + $sql = 'DELETE FROM ' . POLL_VOTES_TABLE . ' + WHERE topic_id = ' . (int) $topic_id . ' + AND poll_option_id = ' . (int) $option . ' + AND vote_user_id = ' . (int) $user->data['user_id']; + $db->sql_query($sql); + } + } + } + + if ($user->data['user_id'] == ANONYMOUS && !$user->data['is_bot']) + { + $user->set_cookie('poll_' . $topic_id, implode(',', $voted_id), time() + 31536000); + } + + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET poll_last_vote = ' . time() . " + WHERE topic_id = $topic_id"; + //, topic_last_post_time = ' . time() . " -- for bumping topics with new votes, ignore for now + $db->sql_query($sql); + + $redirect_url = append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id" . (($start == 0) ? '' : "&start=$start")); + $message = $user->lang['VOTE_SUBMITTED'] . '

    ' . sprintf($user->lang['RETURN_TOPIC'], '', ''); + + if ($request->is_ajax()) + { + // Filter out invalid options + $valid_user_votes = array_intersect(array_keys($vote_counts), $voted_id); + + $data = array( + 'NO_VOTES' => $user->lang['NO_VOTES'], + 'success' => true, + 'user_votes' => array_flip($valid_user_votes), + 'vote_counts' => $vote_counts, + 'total_votes' => array_sum($vote_counts), + 'can_vote' => !count($valid_user_votes) || ($auth->acl_get('f_votechg', $forum_id) && $topic_data['poll_vote_change']), + ); + + /** + * Event to manipulate the poll data sent by AJAX response + * + * @event core.viewtopic_modify_poll_ajax_data + * @var array data JSON response data + * @var array valid_user_votes Valid user votes + * @var array vote_counts Vote counts + * @var int forum_id Forum ID + * @var array topic_data Topic data + * @var array poll_info Array with the poll information + * @since 3.2.4-RC1 + */ + $vars = array( + 'data', + 'valid_user_votes', + 'vote_counts', + 'forum_id', + 'topic_data', + 'poll_info', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_ajax_data', compact($vars))); + + $json_response = new \phpbb\json_response(); + $json_response->send($data); + } + + meta_refresh(5, $redirect_url); + trigger_error($message); + } + + $poll_total = 0; + $poll_most = 0; + foreach ($poll_info as $poll_option) + { + $poll_total += $poll_option['poll_option_total']; + $poll_most = ($poll_option['poll_option_total'] >= $poll_most) ? $poll_option['poll_option_total'] : $poll_most; + } + + $parse_flags = ($poll_info[0]['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + + for ($i = 0, $size = count($poll_info); $i < $size; $i++) + { + $poll_info[$i]['poll_option_text'] = generate_text_for_display($poll_info[$i]['poll_option_text'], $poll_info[$i]['bbcode_uid'], $poll_option['bbcode_bitfield'], $parse_flags, true); + } + + $topic_data['poll_title'] = generate_text_for_display($topic_data['poll_title'], $poll_info[0]['bbcode_uid'], $poll_info[0]['bbcode_bitfield'], $parse_flags, true); + + $poll_template_data = $poll_options_template_data = array(); + foreach ($poll_info as $poll_option) + { + $option_pct = ($poll_total > 0) ? $poll_option['poll_option_total'] / $poll_total : 0; + $option_pct_txt = sprintf("%.1d%%", round($option_pct * 100)); + $option_pct_rel = ($poll_most > 0) ? $poll_option['poll_option_total'] / $poll_most : 0; + $option_pct_rel_txt = sprintf("%.1d%%", round($option_pct_rel * 100)); + $option_most_votes = ($poll_option['poll_option_total'] > 0 && $poll_option['poll_option_total'] == $poll_most) ? true : false; + + $poll_options_template_data[] = array( + 'POLL_OPTION_ID' => $poll_option['poll_option_id'], + 'POLL_OPTION_CAPTION' => $poll_option['poll_option_text'], + 'POLL_OPTION_RESULT' => $poll_option['poll_option_total'], + 'POLL_OPTION_PERCENT' => $option_pct_txt, + 'POLL_OPTION_PERCENT_REL' => $option_pct_rel_txt, + 'POLL_OPTION_PCT' => round($option_pct * 100), + 'POLL_OPTION_WIDTH' => round($option_pct * 250), + 'POLL_OPTION_VOTED' => (in_array($poll_option['poll_option_id'], $cur_voted_id)) ? true : false, + 'POLL_OPTION_MOST_VOTES' => $option_most_votes, + ); + } + + $poll_end = $topic_data['poll_length'] + $topic_data['poll_start']; + + $poll_template_data = array( + 'POLL_QUESTION' => $topic_data['poll_title'], + 'TOTAL_VOTES' => $poll_total, + 'POLL_LEFT_CAP_IMG' => $user->img('poll_left'), + 'POLL_RIGHT_CAP_IMG'=> $user->img('poll_right'), + + 'L_MAX_VOTES' => $user->lang('MAX_OPTIONS_SELECT', (int) $topic_data['poll_max_options']), + 'L_POLL_LENGTH' => ($topic_data['poll_length']) ? sprintf($user->lang[($poll_end > time()) ? 'POLL_RUN_TILL' : 'POLL_ENDED_AT'], $user->format_date($poll_end)) : '', + + 'S_HAS_POLL' => true, + 'S_CAN_VOTE' => $s_can_vote, + 'S_DISPLAY_RESULTS' => $s_display_results, + 'S_IS_MULTI_CHOICE' => ($topic_data['poll_max_options'] > 1) ? true : false, + 'S_POLL_ACTION' => $viewtopic_url, + + 'U_VIEW_RESULTS' => $viewtopic_url . '&view=viewpoll', + ); + + /** + * Event to add/modify poll template data + * + * @event core.viewtopic_modify_poll_template_data + * @var array cur_voted_id Array with options' IDs current user has voted for + * @var int poll_end The poll end time + * @var array poll_info Array with the poll information + * @var array poll_options_template_data Array with the poll options template data + * @var array poll_template_data Array with the common poll template data + * @var int poll_total Total poll votes count + * @var int poll_most Mostly voted option votes count + * @var array topic_data All the information from the topic and forum tables for this topic + * @var string viewtopic_url URL to the topic page + * @var array vote_counts Array with the vote counts for every poll option + * @var array voted_id Array with updated options' IDs current user is voting for + * @since 3.1.5-RC1 + */ + $vars = array( + 'cur_voted_id', + 'poll_end', + 'poll_info', + 'poll_options_template_data', + 'poll_template_data', + 'poll_total', + 'poll_most', + 'topic_data', + 'viewtopic_url', + 'vote_counts', + 'voted_id', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_poll_template_data', compact($vars))); + + $template->assign_block_vars_array('poll_option', $poll_options_template_data); + + $template->assign_vars($poll_template_data); + + unset($poll_end, $poll_info, $poll_options_template_data, $poll_template_data, $voted_id); +} + +// If the user is trying to reach the second half of the topic, fetch it starting from the end +$store_reverse = false; +$sql_limit = $config['posts_per_page']; +$sql_sort_order = $direction = ''; + +if ($start > $total_posts / 2) +{ + $store_reverse = true; + + // Select the sort order + $direction = (($sort_dir == 'd') ? 'ASC' : 'DESC'); + + $sql_limit = $pagination->reverse_limit($start, $sql_limit, $total_posts); + $sql_start = $pagination->reverse_start($start, $sql_limit, $total_posts); +} +else +{ + // Select the sort order + $direction = (($sort_dir == 'd') ? 'DESC' : 'ASC'); + $sql_start = $start; +} + +if (is_array($sort_by_sql[$sort_key])) +{ + $sql_sort_order = implode(' ' . $direction . ', ', $sort_by_sql[$sort_key]) . ' ' . $direction; +} +else +{ + $sql_sort_order = $sort_by_sql[$sort_key] . ' ' . $direction; +} + +// Container for user details, only process once +$post_list = $user_cache = $id_cache = $attachments = $attach_list = $rowset = $update_count = $post_edit_list = $post_delete_list = array(); +$has_unapproved_attachments = $has_approved_attachments = $display_notice = false; +$i = $i_total = 0; + +// Go ahead and pull all data for this topic +$sql = 'SELECT p.post_id + FROM ' . POSTS_TABLE . ' p' . (($join_user_sql[$sort_key]) ? ', ' . USERS_TABLE . ' u': '') . " + WHERE p.topic_id = $topic_id + AND " . $phpbb_content_visibility->get_visibility_sql('post', $forum_id, 'p.') . " + " . (($join_user_sql[$sort_key]) ? 'AND u.user_id = p.poster_id': '') . " + $limit_posts_time + ORDER BY $sql_sort_order"; + +/** +* Event to modify the SQL query that gets post_list +* +* @event core.viewtopic_modify_post_list_sql +* @var string sql The SQL query to generate the post_list +* @var int sql_limit The number of posts the query fetches +* @var int sql_start The index the query starts to fetch from +* @var string sort_key Key the posts are sorted by +* @var string sort_days Display posts of previous x days +* @var int forum_id Forum ID +* @since 3.2.4-RC1 +*/ +$vars = array( + 'sql', + 'sql_limit', + 'sql_start', + 'sort_key', + 'sort_days', + 'forum_id', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_list_sql', compact($vars))); + +$result = $db->sql_query_limit($sql, $sql_limit, $sql_start); + +$i = ($store_reverse) ? $sql_limit - 1 : 0; +while ($row = $db->sql_fetchrow($result)) +{ + $post_list[$i] = (int) $row['post_id']; + ($store_reverse) ? $i-- : $i++; +} +$db->sql_freeresult($result); + +if (!count($post_list)) +{ + if ($sort_days) + { + trigger_error('NO_POSTS_TIME_FRAME'); + } + else + { + trigger_error('NO_TOPIC'); + } +} + +// Holding maximum post time for marking topic read +// We need to grab it because we do reverse ordering sometimes +$max_post_time = 0; + +$sql_ary = array( + 'SELECT' => 'u.*, z.friend, z.foe, p.*', + + 'FROM' => array( + USERS_TABLE => 'u', + POSTS_TABLE => 'p', + ), + + 'LEFT_JOIN' => array( + array( + 'FROM' => array(ZEBRA_TABLE => 'z'), + 'ON' => 'z.user_id = ' . $user->data['user_id'] . ' AND z.zebra_id = p.poster_id', + ), + ), + + 'WHERE' => $db->sql_in_set('p.post_id', $post_list) . ' + AND u.user_id = p.poster_id', +); + +/** +* Event to modify the SQL query before the post and poster data is retrieved +* +* @event core.viewtopic_get_post_data +* @var int forum_id Forum ID +* @var int topic_id Topic ID +* @var array topic_data Array with topic data +* @var array post_list Array with post_ids we are going to retrieve +* @var int sort_days Display posts of previous x days +* @var string sort_key Key the posts are sorted by +* @var string sort_dir Direction the posts are sorted by +* @var int start Pagination information +* @var array sql_ary The SQL array to get the data of posts and posters +* @since 3.1.0-a1 +* @changed 3.1.0-a2 Added vars forum_id, topic_id, topic_data, post_list, sort_days, sort_key, sort_dir, start +*/ +$vars = array( + 'forum_id', + 'topic_id', + 'topic_data', + 'post_list', + 'sort_days', + 'sort_key', + 'sort_dir', + 'start', + 'sql_ary', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_get_post_data', compact($vars))); + +$sql = $db->sql_build_query('SELECT', $sql_ary); +$result = $db->sql_query($sql); + +$now = $user->create_datetime(); +$now = phpbb_gmgetdate($now->getTimestamp() + $now->getOffset()); + +// Posts are stored in the $rowset array while $attach_list, $user_cache +// and the global bbcode_bitfield are built +while ($row = $db->sql_fetchrow($result)) +{ + // Set max_post_time + if ($row['post_time'] > $max_post_time) + { + $max_post_time = $row['post_time']; + } + + $poster_id = (int) $row['poster_id']; + + // Does post have an attachment? If so, add it to the list + if ($row['post_attachment'] && $config['allow_attachments']) + { + $attach_list[] = (int) $row['post_id']; + + if ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) + { + $has_unapproved_attachments = true; + } + else if ($row['post_visibility'] == ITEM_APPROVED) + { + $has_approved_attachments = true; + } + } + + $rowset_data = array( + 'hide_post' => (($row['foe'] || $row['post_visibility'] == ITEM_DELETED) && ($view != 'show' || $post_id != $row['post_id'])) ? true : false, + + 'post_id' => $row['post_id'], + 'post_time' => $row['post_time'], + 'user_id' => $row['user_id'], + 'username' => $row['username'], + 'user_colour' => $row['user_colour'], + 'topic_id' => $row['topic_id'], + 'forum_id' => $row['forum_id'], + 'post_subject' => $row['post_subject'], + 'post_edit_count' => $row['post_edit_count'], + 'post_edit_time' => $row['post_edit_time'], + 'post_edit_reason' => $row['post_edit_reason'], + 'post_edit_user' => $row['post_edit_user'], + 'post_edit_locked' => $row['post_edit_locked'], + 'post_delete_time' => $row['post_delete_time'], + 'post_delete_reason'=> $row['post_delete_reason'], + 'post_delete_user' => $row['post_delete_user'], + + // Make sure the icon actually exists + 'icon_id' => (isset($icons[$row['icon_id']]['img'], $icons[$row['icon_id']]['height'], $icons[$row['icon_id']]['width'])) ? $row['icon_id'] : 0, + 'post_attachment' => $row['post_attachment'], + 'post_visibility' => $row['post_visibility'], + 'post_reported' => $row['post_reported'], + 'post_username' => $row['post_username'], + 'post_text' => $row['post_text'], + 'bbcode_uid' => $row['bbcode_uid'], + 'bbcode_bitfield' => $row['bbcode_bitfield'], + 'enable_smilies' => $row['enable_smilies'], + 'enable_sig' => $row['enable_sig'], + 'friend' => $row['friend'], + 'foe' => $row['foe'], + ); + + /** + * Modify the post rowset containing data to be displayed with posts + * + * @event core.viewtopic_post_rowset_data + * @var array rowset_data Array with the rowset data for this post + * @var array row Array with original user and post data + * @since 3.1.0-a1 + */ + $vars = array('rowset_data', 'row'); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_post_rowset_data', compact($vars))); + + $rowset[$row['post_id']] = $rowset_data; + + // Cache various user specific data ... so we don't have to recompute + // this each time the same user appears on this page + if (!isset($user_cache[$poster_id])) + { + if ($poster_id == ANONYMOUS) + { + $user_cache_data = array( + 'user_type' => USER_IGNORE, + 'joined' => '', + 'posts' => '', + + 'sig' => '', + 'sig_bbcode_uid' => '', + 'sig_bbcode_bitfield' => '', + + 'online' => false, + 'avatar' => ($user->optionget('viewavatars')) ? phpbb_get_user_avatar($row) : '', + 'rank_title' => '', + 'rank_image' => '', + 'rank_image_src' => '', + 'pm' => '', + 'email' => '', + 'jabber' => '', + 'search' => '', + 'age' => '', + + 'username' => $row['username'], + 'user_colour' => $row['user_colour'], + 'contact_user' => '', + + 'warnings' => 0, + 'allow_pm' => 0, + ); + + /** + * Modify the guest user's data displayed with the posts + * + * @event core.viewtopic_cache_guest_data + * @var array user_cache_data Array with the user's data + * @var int poster_id Poster's user id + * @var array row Array with original user and post data + * @since 3.1.0-a1 + */ + $vars = array('user_cache_data', 'poster_id', 'row'); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_cache_guest_data', compact($vars))); + + $user_cache[$poster_id] = $user_cache_data; + + $user_rank_data = phpbb_get_user_rank($row, false); + $user_cache[$poster_id]['rank_title'] = $user_rank_data['title']; + $user_cache[$poster_id]['rank_image'] = $user_rank_data['img']; + $user_cache[$poster_id]['rank_image_src'] = $user_rank_data['img_src']; + } + else + { + $user_sig = ''; + + // We add the signature to every posters entry because enable_sig is post dependent + if ($row['user_sig'] && $config['allow_sig'] && $user->optionget('viewsigs')) + { + $user_sig = $row['user_sig']; + } + + $id_cache[] = $poster_id; + + $user_cache_data = array( + 'user_type' => $row['user_type'], + 'user_inactive_reason' => $row['user_inactive_reason'], + + 'joined' => $user->format_date($row['user_regdate']), + 'posts' => $row['user_posts'], + 'warnings' => (isset($row['user_warnings'])) ? $row['user_warnings'] : 0, + + 'sig' => $user_sig, + 'sig_bbcode_uid' => (!empty($row['user_sig_bbcode_uid'])) ? $row['user_sig_bbcode_uid'] : '', + 'sig_bbcode_bitfield' => (!empty($row['user_sig_bbcode_bitfield'])) ? $row['user_sig_bbcode_bitfield'] : '', + + 'viewonline' => $row['user_allow_viewonline'], + 'allow_pm' => $row['user_allow_pm'], + + 'avatar' => ($user->optionget('viewavatars')) ? phpbb_get_user_avatar($row) : '', + 'age' => '', + + 'rank_title' => '', + 'rank_image' => '', + 'rank_image_src' => '', + + 'username' => $row['username'], + 'user_colour' => $row['user_colour'], + 'contact_user' => $user->lang('CONTACT_USER', get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['username'])), + + 'online' => false, + 'jabber' => ($config['jab_enable'] && $row['user_jabber'] && $auth->acl_get('u_sendim')) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=contact&action=jabber&u=$poster_id") : '', + 'search' => ($config['load_search'] && $auth->acl_get('u_search')) ? append_sid("{$phpbb_root_path}search.$phpEx", "author_id=$poster_id&sr=posts") : '', + + 'author_full' => get_username_string('full', $poster_id, $row['username'], $row['user_colour']), + 'author_colour' => get_username_string('colour', $poster_id, $row['username'], $row['user_colour']), + 'author_username' => get_username_string('username', $poster_id, $row['username'], $row['user_colour']), + 'author_profile' => get_username_string('profile', $poster_id, $row['username'], $row['user_colour']), + ); + + /** + * Modify the users' data displayed with their posts + * + * @event core.viewtopic_cache_user_data + * @var array user_cache_data Array with the user's data + * @var int poster_id Poster's user id + * @var array row Array with original user and post data + * @since 3.1.0-a1 + */ + $vars = array('user_cache_data', 'poster_id', 'row'); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_cache_user_data', compact($vars))); + + $user_cache[$poster_id] = $user_cache_data; + + $user_rank_data = phpbb_get_user_rank($row, $row['user_posts']); + $user_cache[$poster_id]['rank_title'] = $user_rank_data['title']; + $user_cache[$poster_id]['rank_image'] = $user_rank_data['img']; + $user_cache[$poster_id]['rank_image_src'] = $user_rank_data['img_src']; + + if ((!empty($row['user_allow_viewemail']) && $auth->acl_get('u_sendemail')) || $auth->acl_get('a_email')) + { + $user_cache[$poster_id]['email'] = ($config['board_email_form'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", "mode=email&u=$poster_id") : (($config['board_hide_emails'] && !$auth->acl_get('a_email')) ? '' : 'mailto:' . $row['user_email']); + } + else + { + $user_cache[$poster_id]['email'] = ''; + } + + if ($config['allow_birthdays'] && !empty($row['user_birthday'])) + { + list($bday_day, $bday_month, $bday_year) = array_map('intval', explode('-', $row['user_birthday'])); + + if ($bday_year) + { + $diff = $now['mon'] - $bday_month; + if ($diff == 0) + { + $diff = ($now['mday'] - $bday_day < 0) ? 1 : 0; + } + else + { + $diff = ($diff < 0) ? 1 : 0; + } + + $user_cache[$poster_id]['age'] = (int) ($now['year'] - $bday_year - $diff); + } + } + } + } +} +$db->sql_freeresult($result); + +// Load custom profile fields +if ($config['load_cpf_viewtopic']) +{ + /* @var $cp \phpbb\profilefields\manager */ + $cp = $phpbb_container->get('profilefields.manager'); + + // Grab all profile fields from users in id cache for later use - similar to the poster cache + $profile_fields_tmp = $cp->grab_profile_fields_data($id_cache); + + // filter out fields not to be displayed on viewtopic. Yes, it's a hack, but this shouldn't break any MODs. + $profile_fields_cache = array(); + foreach ($profile_fields_tmp as $profile_user_id => $profile_fields) + { + $profile_fields_cache[$profile_user_id] = array(); + foreach ($profile_fields as $used_ident => $profile_field) + { + if ($profile_field['data']['field_show_on_vt']) + { + $profile_fields_cache[$profile_user_id][$used_ident] = $profile_field; + } + } + } + unset($profile_fields_tmp); +} + +// Generate online information for user +if ($config['load_onlinetrack'] && count($id_cache)) +{ + $sql = 'SELECT session_user_id, MAX(session_time) as online_time, MIN(session_viewonline) AS viewonline + FROM ' . SESSIONS_TABLE . ' + WHERE ' . $db->sql_in_set('session_user_id', $id_cache) . ' + GROUP BY session_user_id'; + $result = $db->sql_query($sql); + + $update_time = $config['load_online_time'] * 60; + while ($row = $db->sql_fetchrow($result)) + { + $user_cache[$row['session_user_id']]['online'] = (time() - $update_time < $row['online_time'] && (($row['viewonline']) || $auth->acl_get('u_viewonline'))) ? true : false; + } + $db->sql_freeresult($result); +} +unset($id_cache); + +// Pull attachment data +if (count($attach_list)) +{ + if ($auth->acl_get('u_download') && $auth->acl_get('f_download', $forum_id)) + { + $sql = 'SELECT * + FROM ' . ATTACHMENTS_TABLE . ' + WHERE ' . $db->sql_in_set('post_msg_id', $attach_list) . ' + AND in_message = 0 + ORDER BY attach_id DESC, post_msg_id ASC'; + $result = $db->sql_query($sql); + + while ($row = $db->sql_fetchrow($result)) + { + $attachments[$row['post_msg_id']][] = $row; + } + $db->sql_freeresult($result); + + // No attachments exist, but post table thinks they do so go ahead and reset post_attach flags + if (!count($attachments)) + { + $sql = 'UPDATE ' . POSTS_TABLE . ' + SET post_attachment = 0 + WHERE ' . $db->sql_in_set('post_id', $attach_list); + $db->sql_query($sql); + + // We need to update the topic indicator too if the complete topic is now without an attachment + if (count($rowset) != $total_posts) + { + // Not all posts are displayed so we query the db to find if there's any attachment for this topic + $sql = 'SELECT a.post_msg_id as post_id + FROM ' . ATTACHMENTS_TABLE . ' a, ' . POSTS_TABLE . " p + WHERE p.topic_id = $topic_id + AND p.post_visibility = " . ITEM_APPROVED . ' + AND p.topic_id = a.topic_id'; + $result = $db->sql_query_limit($sql, 1); + $row = $db->sql_fetchrow($result); + $db->sql_freeresult($result); + + if (!$row) + { + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_attachment = 0 + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + } + } + else + { + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_attachment = 0 + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + } + } + else if ($has_approved_attachments && !$topic_data['topic_attachment']) + { + // Topic has approved attachments but its flag is wrong + $sql = 'UPDATE ' . TOPICS_TABLE . " + SET topic_attachment = 1 + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + + $topic_data['topic_attachment'] = 1; + } + else if ($has_unapproved_attachments && !$topic_data['topic_attachment']) + { + // Topic has only unapproved attachments but we have the right to see and download them + $topic_data['topic_attachment'] = 1; + } + } + else + { + $display_notice = true; + } +} + +if ($config['enable_accurate_pm_button']) +{ + // Get the list of users who can receive private messages + $can_receive_pm_list = $auth->acl_get_list(array_keys($user_cache), 'u_readpm'); + $can_receive_pm_list = (empty($can_receive_pm_list) || !isset($can_receive_pm_list[0]['u_readpm'])) ? array() : $can_receive_pm_list[0]['u_readpm']; + + // Get the list of permanently banned users + $permanently_banned_users = phpbb_get_banned_user_ids(array_keys($user_cache), false); +} +else +{ + $can_receive_pm_list = array_keys($user_cache); + $permanently_banned_users = []; +} + +$i_total = count($rowset) - 1; +$prev_post_id = ''; + +$template->assign_vars(array( + 'S_HAS_ATTACHMENTS' => $topic_data['topic_attachment'], + 'S_NUM_POSTS' => count($post_list)) +); + +/** +* Event to modify the post, poster and attachment data before assigning the posts +* +* @event core.viewtopic_modify_post_data +* @var int forum_id Forum ID +* @var int topic_id Topic ID +* @var array topic_data Array with topic data +* @var array post_list Array with post_ids we are going to display +* @var array rowset Array with post_id => post data +* @var array user_cache Array with prepared user data +* @var int start Pagination information +* @var int sort_days Display posts of previous x days +* @var string sort_key Key the posts are sorted by +* @var string sort_dir Direction the posts are sorted by +* @var bool display_notice Shall we display a notice instead of attachments +* @var bool has_approved_attachments Does the topic have approved attachments +* @var array attachments List of attachments post_id => array of attachments +* @var array permanently_banned_users List of permanently banned users +* @var array can_receive_pm_list Array with posters that can receive pms +* @since 3.1.0-RC3 +*/ +$vars = array( + 'forum_id', + 'topic_id', + 'topic_data', + 'post_list', + 'rowset', + 'user_cache', + 'sort_days', + 'sort_key', + 'sort_dir', + 'start', + 'permanently_banned_users', + 'can_receive_pm_list', + 'display_notice', + 'has_approved_attachments', + 'attachments', +); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_data', compact($vars))); + +// Output the posts +$first_unread = $post_unread = false; +for ($i = 0, $end = count($post_list); $i < $end; ++$i) +{ + // A non-existing rowset only happens if there was no user present for the entered poster_id + // This could be a broken posts table. + if (!isset($rowset[$post_list[$i]])) + { + continue; + } + + $row = $rowset[$post_list[$i]]; + $poster_id = $row['user_id']; + + // End signature parsing, only if needed + if ($user_cache[$poster_id]['sig'] && $row['enable_sig'] && empty($user_cache[$poster_id]['sig_parsed'])) + { + $parse_flags = ($user_cache[$poster_id]['sig_bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $user_cache[$poster_id]['sig'] = generate_text_for_display($user_cache[$poster_id]['sig'], $user_cache[$poster_id]['sig_bbcode_uid'], $user_cache[$poster_id]['sig_bbcode_bitfield'], $parse_flags, true); + $user_cache[$poster_id]['sig_parsed'] = true; + } + + // Parse the message and subject + $parse_flags = ($row['bbcode_bitfield'] ? OPTION_FLAG_BBCODE : 0) | OPTION_FLAG_SMILIES; + $message = generate_text_for_display($row['post_text'], $row['bbcode_uid'], $row['bbcode_bitfield'], $parse_flags, true); + + if (!empty($attachments[$row['post_id']])) + { + parse_attachments($forum_id, $message, $attachments[$row['post_id']], $update_count); + } + + // Replace naughty words such as farty pants + $row['post_subject'] = censor_text($row['post_subject']); + + // Highlight active words (primarily for search) + if ($highlight_match) + { + $message = preg_replace('#(?!<.*)(?]*(?:)#is', '\1', $message); + $row['post_subject'] = preg_replace('#(?!<.*)(?]*(?:)#is', '\1', $row['post_subject']); + } + + // Editing information + if (($row['post_edit_count'] && $config['display_last_edited']) || $row['post_edit_reason']) + { + // Get usernames for all following posts if not already stored + if (!count($post_edit_list) && ($row['post_edit_reason'] || ($row['post_edit_user'] && !isset($user_cache[$row['post_edit_user']])))) + { + // Remove all post_ids already parsed (we do not have to check them) + $post_storage_list = (!$store_reverse) ? array_slice($post_list, $i) : array_slice(array_reverse($post_list), $i); + + $sql = 'SELECT DISTINCT u.user_id, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_storage_list) . ' + AND p.post_edit_count <> 0 + AND p.post_edit_user <> 0 + AND p.post_edit_user = u.user_id'; + $result2 = $db->sql_query($sql); + while ($user_edit_row = $db->sql_fetchrow($result2)) + { + $post_edit_list[$user_edit_row['user_id']] = $user_edit_row; + } + $db->sql_freeresult($result2); + + unset($post_storage_list); + } + + if ($row['post_edit_reason']) + { + // User having edited the post also being the post author? + if (!$row['post_edit_user'] || $row['post_edit_user'] == $poster_id) + { + $display_username = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']); + } + else + { + $display_username = get_username_string('full', $row['post_edit_user'], $post_edit_list[$row['post_edit_user']]['username'], $post_edit_list[$row['post_edit_user']]['user_colour']); + } + + $l_edited_by = $user->lang('EDITED_TIMES_TOTAL', (int) $row['post_edit_count'], $display_username, $user->format_date($row['post_edit_time'], false, true)); + } + else + { + if ($row['post_edit_user'] && !isset($user_cache[$row['post_edit_user']])) + { + $user_cache[$row['post_edit_user']] = $post_edit_list[$row['post_edit_user']]; + } + + // User having edited the post also being the post author? + if (!$row['post_edit_user'] || $row['post_edit_user'] == $poster_id) + { + $display_username = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']); + } + else + { + $display_username = get_username_string('full', $row['post_edit_user'], $user_cache[$row['post_edit_user']]['username'], $user_cache[$row['post_edit_user']]['user_colour']); + } + + $l_edited_by = $user->lang('EDITED_TIMES_TOTAL', (int) $row['post_edit_count'], $display_username, $user->format_date($row['post_edit_time'], false, true)); + } + } + else + { + $l_edited_by = ''; + } + + // Deleting information + if ($row['post_visibility'] == ITEM_DELETED && $row['post_delete_user']) + { + // Get usernames for all following posts if not already stored + if (!count($post_delete_list) && ($row['post_delete_reason'] || ($row['post_delete_user'] && !isset($user_cache[$row['post_delete_user']])))) + { + // Remove all post_ids already parsed (we do not have to check them) + $post_storage_list = (!$store_reverse) ? array_slice($post_list, $i) : array_slice(array_reverse($post_list), $i); + + $sql = 'SELECT DISTINCT u.user_id, u.username, u.user_colour + FROM ' . POSTS_TABLE . ' p, ' . USERS_TABLE . ' u + WHERE ' . $db->sql_in_set('p.post_id', $post_storage_list) . ' + AND p.post_delete_user <> 0 + AND p.post_delete_user = u.user_id'; + $result2 = $db->sql_query($sql); + while ($user_delete_row = $db->sql_fetchrow($result2)) + { + $post_delete_list[$user_delete_row['user_id']] = $user_delete_row; + } + $db->sql_freeresult($result2); + + unset($post_storage_list); + } + + if ($row['post_delete_user'] && !isset($user_cache[$row['post_delete_user']])) + { + $user_cache[$row['post_delete_user']] = $post_delete_list[$row['post_delete_user']]; + } + + $display_postername = get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']); + + // User having deleted the post also being the post author? + if (!$row['post_delete_user'] || $row['post_delete_user'] == $poster_id) + { + $display_username = $display_postername; + } + else + { + $display_username = get_username_string('full', $row['post_delete_user'], $user_cache[$row['post_delete_user']]['username'], $user_cache[$row['post_delete_user']]['user_colour']); + } + + if ($row['post_delete_reason']) + { + $l_deleted_message = $user->lang('POST_DELETED_BY_REASON', $display_postername, $display_username, $user->format_date($row['post_delete_time'], false, true), $row['post_delete_reason']); + } + else + { + $l_deleted_message = $user->lang('POST_DELETED_BY', $display_postername, $display_username, $user->format_date($row['post_delete_time'], false, true)); + } + $l_deleted_by = $user->lang('DELETED_INFORMATION', $display_username, $user->format_date($row['post_delete_time'], false, true)); + } + else + { + $l_deleted_by = $l_deleted_message = ''; + } + + // Bump information + if ($topic_data['topic_bumped'] && $row['post_id'] == $topic_data['topic_last_post_id'] && isset($user_cache[$topic_data['topic_bumper']]) ) + { + // It is safe to grab the username from the user cache array, we are at the last + // post and only the topic poster and last poster are allowed to bump. + // Admins and mods are bound to the above rules too... + $l_bumped_by = sprintf($user->lang['BUMPED_BY'], $user_cache[$topic_data['topic_bumper']]['username'], $user->format_date($topic_data['topic_last_post_time'], false, true)); + } + else + { + $l_bumped_by = ''; + } + + $cp_row = array(); + + // + if ($config['load_cpf_viewtopic']) + { + $cp_row = (isset($profile_fields_cache[$poster_id])) ? $cp->generate_profile_fields_template_data($profile_fields_cache[$poster_id]) : array(); + } + + $post_unread = (isset($topic_tracking_info[$topic_id]) && $row['post_time'] > $topic_tracking_info[$topic_id]) ? true : false; + + $s_first_unread = false; + if (!$first_unread && $post_unread) + { + $s_first_unread = $first_unread = true; + } + + $force_edit_allowed = $force_delete_allowed = $force_softdelete_allowed = false; + + $s_cannot_edit = !$auth->acl_get('f_edit', $forum_id) || $user->data['user_id'] != $poster_id; + $s_cannot_edit_time = $config['edit_time'] && $row['post_time'] <= time() - ($config['edit_time'] * 60); + $s_cannot_edit_locked = $topic_data['topic_status'] == ITEM_LOCKED || $row['post_edit_locked']; + + $s_cannot_delete = $user->data['user_id'] != $poster_id || ( + !$auth->acl_get('f_delete', $forum_id) && + (!$auth->acl_get('f_softdelete', $forum_id) || $row['post_visibility'] == ITEM_DELETED) + ); + $s_cannot_delete_lastpost = $topic_data['topic_last_post_id'] != $row['post_id']; + $s_cannot_delete_time = $config['delete_time'] && $row['post_time'] <= time() - ($config['delete_time'] * 60); + // we do not want to allow removal of the last post if a moderator locked it! + $s_cannot_delete_locked = $topic_data['topic_status'] == ITEM_LOCKED || $row['post_edit_locked']; + + /** + * This event allows you to modify the conditions for the "can edit post" and "can delete post" checks + * + * @event core.viewtopic_modify_post_action_conditions + * @var array row Array with post data + * @var array topic_data Array with topic data + * @var bool force_edit_allowed Allow the user to edit the post (all permissions and conditions are ignored) + * @var bool s_cannot_edit User can not edit the post because it's not his + * @var bool s_cannot_edit_locked User can not edit the post because it's locked + * @var bool s_cannot_edit_time User can not edit the post because edit_time has passed + * @var bool force_delete_allowed Allow the user to delete the post (all permissions and conditions are ignored) + * @var bool s_cannot_delete User can not delete the post because it's not his + * @var bool s_cannot_delete_lastpost User can not delete the post because it's not the last post of the topic + * @var bool s_cannot_delete_locked User can not delete the post because it's locked + * @var bool s_cannot_delete_time User can not delete the post because edit_time has passed + * @var bool force_softdelete_allowed Allow the user to ыoftdelete the post (all permissions and conditions are ignored) + * @since 3.1.0-b4 + * @changed 3.1.11-RC1 Added force_softdelete_allowed var + */ + $vars = array( + 'row', + 'topic_data', + 'force_edit_allowed', + 's_cannot_edit', + 's_cannot_edit_locked', + 's_cannot_edit_time', + 'force_delete_allowed', + 's_cannot_delete', + 's_cannot_delete_lastpost', + 's_cannot_delete_locked', + 's_cannot_delete_time', + 'force_softdelete_allowed', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_action_conditions', compact($vars))); + + $edit_allowed = $force_edit_allowed || ($user->data['is_registered'] && ($auth->acl_get('m_edit', $forum_id) || ( + !$s_cannot_edit && + !$s_cannot_edit_time && + !$s_cannot_edit_locked + ))); + + $quote_allowed = $auth->acl_get('m_edit', $forum_id) || ($topic_data['topic_status'] != ITEM_LOCKED && + ($user->data['user_id'] == ANONYMOUS || $auth->acl_get('f_reply', $forum_id)) + ); + + // Only display the quote button if the post is quotable. Posts not approved are not quotable. + $quote_allowed = ($quote_allowed && $row['post_visibility'] == ITEM_APPROVED) ? true : false; + + $delete_allowed = $force_delete_allowed || ($user->data['is_registered'] && ( + ($auth->acl_get('m_delete', $forum_id) || ($auth->acl_get('m_softdelete', $forum_id) && $row['post_visibility'] != ITEM_DELETED)) || + (!$s_cannot_delete && !$s_cannot_delete_lastpost && !$s_cannot_delete_time && !$s_cannot_delete_locked) + )); + + $softdelete_allowed = $force_softdelete_allowed || (($auth->acl_get('m_softdelete', $forum_id) || + ($auth->acl_get('f_softdelete', $forum_id) && $user->data['user_id'] == $poster_id)) && ($row['post_visibility'] != ITEM_DELETED)); + + $permanent_delete_allowed = $force_delete_allowed || ($auth->acl_get('m_delete', $forum_id) || + ($auth->acl_get('f_delete', $forum_id) && $user->data['user_id'] == $poster_id)); + + // Can this user receive a Private Message? + $can_receive_pm = ( + // They must be a "normal" user + $user_cache[$poster_id]['user_type'] != USER_IGNORE && + + // They must not be deactivated by the administrator + ($user_cache[$poster_id]['user_type'] != USER_INACTIVE || $user_cache[$poster_id]['user_inactive_reason'] != INACTIVE_MANUAL) && + + // They must be able to read PMs + in_array($poster_id, $can_receive_pm_list) && + + // They must not be permanently banned + !in_array($poster_id, $permanently_banned_users) && + + // They must allow users to contact via PM + (($auth->acl_gets('a_', 'm_') || $auth->acl_getf_global('m_')) || $user_cache[$poster_id]['allow_pm']) + ); + + $u_pm = ''; + + if ($config['allow_privmsg'] && $auth->acl_get('u_sendpm') && $can_receive_pm) + { + $u_pm = append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&mode=compose&action=quotepost&p=' . $row['post_id']); + } + + // + $post_row = array( + 'POST_AUTHOR_FULL' => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_full'] : get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR_COLOUR' => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_colour'] : get_username_string('colour', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'POST_AUTHOR' => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_username'] : get_username_string('username', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + 'U_POST_AUTHOR' => ($poster_id != ANONYMOUS) ? $user_cache[$poster_id]['author_profile'] : get_username_string('profile', $poster_id, $row['username'], $row['user_colour'], $row['post_username']), + + 'RANK_TITLE' => $user_cache[$poster_id]['rank_title'], + 'RANK_IMG' => $user_cache[$poster_id]['rank_image'], + 'RANK_IMG_SRC' => $user_cache[$poster_id]['rank_image_src'], + 'POSTER_JOINED' => $user_cache[$poster_id]['joined'], + 'POSTER_POSTS' => $user_cache[$poster_id]['posts'], + 'POSTER_AVATAR' => $user_cache[$poster_id]['avatar'], + 'POSTER_WARNINGS' => $auth->acl_get('m_warn') ? $user_cache[$poster_id]['warnings'] : '', + 'POSTER_AGE' => $user_cache[$poster_id]['age'], + 'CONTACT_USER' => $user_cache[$poster_id]['contact_user'], + + 'POST_DATE' => $user->format_date($row['post_time'], false, ($view == 'print') ? true : false), + 'POST_SUBJECT' => $row['post_subject'], + 'MESSAGE' => $message, + 'SIGNATURE' => ($row['enable_sig']) ? $user_cache[$poster_id]['sig'] : '', + 'EDITED_MESSAGE' => $l_edited_by, + 'EDIT_REASON' => $row['post_edit_reason'], + 'DELETED_MESSAGE' => $l_deleted_by, + 'DELETE_REASON' => $row['post_delete_reason'], + 'BUMPED_MESSAGE' => $l_bumped_by, + + 'MINI_POST_IMG' => ($post_unread) ? $user->img('icon_post_target_unread', 'UNREAD_POST') : $user->img('icon_post_target', 'POST'), + 'POST_ICON_IMG' => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['img'] : '', + 'POST_ICON_IMG_WIDTH' => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['width'] : '', + 'POST_ICON_IMG_HEIGHT' => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['height'] : '', + 'POST_ICON_IMG_ALT' => ($topic_data['enable_icons'] && !empty($row['icon_id'])) ? $icons[$row['icon_id']]['alt'] : '', + 'ONLINE_IMG' => ($poster_id == ANONYMOUS || !$config['load_onlinetrack']) ? '' : (($user_cache[$poster_id]['online']) ? $user->img('icon_user_online', 'ONLINE') : $user->img('icon_user_offline', 'OFFLINE')), + 'S_ONLINE' => ($poster_id == ANONYMOUS || !$config['load_onlinetrack']) ? false : (($user_cache[$poster_id]['online']) ? true : false), + + 'U_EDIT' => ($edit_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=edit&f=$forum_id&p={$row['post_id']}") : '', + 'U_QUOTE' => ($quote_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", "mode=quote&f=$forum_id&p={$row['post_id']}") : '', + 'U_INFO' => ($auth->acl_get('m_info', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", "i=main&mode=post_details&f=$forum_id&p=" . $row['post_id'], true, $user->session_id) : '', + 'U_DELETE' => ($delete_allowed) ? append_sid("{$phpbb_root_path}posting.$phpEx", 'mode=' . (($softdelete_allowed) ? 'soft_delete' : 'delete') . "&f=$forum_id&p={$row['post_id']}") : '', + + 'U_SEARCH' => $user_cache[$poster_id]['search'], + 'U_PM' => $u_pm, + 'U_EMAIL' => $user_cache[$poster_id]['email'], + 'U_JABBER' => $user_cache[$poster_id]['jabber'], + + 'U_APPROVE_ACTION' => append_sid("{$phpbb_root_path}mcp.$phpEx", "i=queue&p={$row['post_id']}&f=$forum_id&redirect=" . urlencode(str_replace('&', '&', $viewtopic_url . '&p=' . $row['post_id'] . '#p' . $row['post_id']))), + 'U_REPORT' => ($auth->acl_get('f_report', $forum_id)) ? $phpbb_container->get('controller.helper')->route('phpbb_report_post_controller', array('id' => $row['post_id'])) : '', + 'U_MCP_REPORT' => ($auth->acl_get('m_report', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=reports&mode=report_details&f=' . $forum_id . '&p=' . $row['post_id'], true, $user->session_id) : '', + 'U_MCP_APPROVE' => ($auth->acl_get('m_approve', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=approve_details&f=' . $forum_id . '&p=' . $row['post_id'], true, $user->session_id) : '', + 'U_MCP_RESTORE' => ($auth->acl_get('m_approve', $forum_id)) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=queue&mode=' . (($topic_data['topic_visibility'] != ITEM_DELETED) ? 'deleted_posts' : 'deleted_topics') . '&f=' . $forum_id . '&p=' . $row['post_id'], true, $user->session_id) : '', + 'U_MINI_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", 'p=' . $row['post_id']) . '#p' . $row['post_id'], + 'U_NEXT_POST_ID' => ($i < $i_total && isset($rowset[$post_list[$i + 1]])) ? $rowset[$post_list[$i + 1]]['post_id'] : '', + 'U_PREV_POST_ID' => $prev_post_id, + 'U_NOTES' => ($auth->acl_getf_global('m_')) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=notes&mode=user_notes&u=' . $poster_id, true, $user->session_id) : '', + 'U_WARN' => ($auth->acl_get('m_warn') && $poster_id != $user->data['user_id'] && $poster_id != ANONYMOUS) ? append_sid("{$phpbb_root_path}mcp.$phpEx", 'i=warn&mode=warn_post&f=' . $forum_id . '&p=' . $row['post_id'], true, $user->session_id) : '', + + 'POST_ID' => $row['post_id'], + 'POST_NUMBER' => $i + $start + 1, + 'POSTER_ID' => $poster_id, + 'MINI_POST' => ($post_unread) ? $user->lang['UNREAD_POST'] : $user->lang['POST'], + + + 'S_HAS_ATTACHMENTS' => (!empty($attachments[$row['post_id']])) ? true : false, + 'S_MULTIPLE_ATTACHMENTS' => !empty($attachments[$row['post_id']]) && count($attachments[$row['post_id']]) > 1, + 'S_POST_UNAPPROVED' => ($row['post_visibility'] == ITEM_UNAPPROVED || $row['post_visibility'] == ITEM_REAPPROVE) ? true : false, + 'S_POST_DELETED' => ($row['post_visibility'] == ITEM_DELETED) ? true : false, + 'L_POST_DELETED_MESSAGE' => $l_deleted_message, + 'S_POST_REPORTED' => ($row['post_reported'] && $auth->acl_get('m_report', $forum_id)) ? true : false, + 'S_DISPLAY_NOTICE' => $display_notice && $row['post_attachment'], + 'S_FRIEND' => ($row['friend']) ? true : false, + 'S_UNREAD_POST' => $post_unread, + 'S_FIRST_UNREAD' => $s_first_unread, + 'S_CUSTOM_FIELDS' => (isset($cp_row['row']) && count($cp_row['row'])) ? true : false, + 'S_TOPIC_POSTER' => ($topic_data['topic_poster'] == $poster_id) ? true : false, + 'S_FIRST_POST' => ($topic_data['topic_first_post_id'] == $row['post_id']) ? true : false, + + 'S_IGNORE_POST' => ($row['foe']) ? true : false, + 'L_IGNORE_POST' => ($row['foe']) ? sprintf($user->lang['POST_BY_FOE'], get_username_string('full', $poster_id, $row['username'], $row['user_colour'], $row['post_username'])) : '', + 'S_POST_HIDDEN' => $row['hide_post'], + 'L_POST_DISPLAY' => ($row['hide_post']) ? $user->lang('POST_DISPLAY', '', '') : '', + 'S_DELETE_PERMANENT' => $permanent_delete_allowed, + ); + + $user_poster_data = $user_cache[$poster_id]; + + $current_row_number = $i; + + /** + * Modify the posts template block + * + * @event core.viewtopic_modify_post_row + * @var int start Start item of this page + * @var int current_row_number Number of the post on this page + * @var int end Number of posts on this page + * @var int total_posts Total posts count + * @var int poster_id Post author id + * @var array row Array with original post and user data + * @var array cp_row Custom profile field data of the poster + * @var array attachments List of attachments + * @var array user_poster_data Poster's data from user cache + * @var array post_row Template block array of the post + * @var array topic_data Array with topic data + * @var array user_cache Array with cached user data + * @var array post_edit_list Array with post edited list + * @since 3.1.0-a1 + * @changed 3.1.0-a3 Added vars start, current_row_number, end, attachments + * @changed 3.1.0-b3 Added topic_data array, total_posts + * @changed 3.1.0-RC3 Added poster_id + * @changed 3.2.2-RC1 Added user_cache and post_edit_list + */ + $vars = array( + 'start', + 'current_row_number', + 'end', + 'total_posts', + 'poster_id', + 'row', + 'cp_row', + 'attachments', + 'user_poster_data', + 'post_row', + 'topic_data', + 'user_cache', + 'post_edit_list', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_post_row', compact($vars))); + + $i = $current_row_number; + + if (isset($cp_row['row']) && count($cp_row['row'])) + { + $post_row = array_merge($post_row, $cp_row['row']); + } + + // Dump vars into template + $template->assign_block_vars('postrow', $post_row); + + $contact_fields = array( + array( + 'ID' => 'pm', + 'NAME' => $user->lang['SEND_PRIVATE_MESSAGE'], + 'U_CONTACT' => $post_row['U_PM'], + ), + array( + 'ID' => 'email', + 'NAME' => $user->lang['SEND_EMAIL'], + 'U_CONTACT' => $user_cache[$poster_id]['email'], + ), + array( + 'ID' => 'jabber', + 'NAME' => $user->lang['JABBER'], + 'U_CONTACT' => $user_cache[$poster_id]['jabber'], + ), + ); + + foreach ($contact_fields as $field) + { + if ($field['U_CONTACT']) + { + $template->assign_block_vars('postrow.contact', $field); + } + } + + if (!empty($cp_row['blockrow'])) + { + foreach ($cp_row['blockrow'] as $field_data) + { + $template->assign_block_vars('postrow.custom_fields', $field_data); + + if ($field_data['S_PROFILE_CONTACT']) + { + $template->assign_block_vars('postrow.contact', array( + 'ID' => $field_data['PROFILE_FIELD_IDENT'], + 'NAME' => $field_data['PROFILE_FIELD_NAME'], + 'U_CONTACT' => $field_data['PROFILE_FIELD_CONTACT'], + )); + } + } + } + + // Display not already displayed Attachments for this post, we already parsed them. ;) + if (!empty($attachments[$row['post_id']])) + { + foreach ($attachments[$row['post_id']] as $attachment) + { + $template->assign_block_vars('postrow.attachment', array( + 'DISPLAY_ATTACHMENT' => $attachment) + ); + } + } + + $current_row_number = $i; + + /** + * Event after the post data has been assigned to the template + * + * @event core.viewtopic_post_row_after + * @var int start Start item of this page + * @var int current_row_number Number of the post on this page + * @var int end Number of posts on this page + * @var int total_posts Total posts count + * @var array row Array with original post and user data + * @var array cp_row Custom profile field data of the poster + * @var array attachments List of attachments + * @var array user_poster_data Poster's data from user cache + * @var array post_row Template block array of the post + * @var array topic_data Array with topic data + * @since 3.1.0-a3 + * @changed 3.1.0-b3 Added topic_data array, total_posts + */ + $vars = array( + 'start', + 'current_row_number', + 'end', + 'total_posts', + 'row', + 'cp_row', + 'attachments', + 'user_poster_data', + 'post_row', + 'topic_data', + ); + extract($phpbb_dispatcher->trigger_event('core.viewtopic_post_row_after', compact($vars))); + + $i = $current_row_number; + + $prev_post_id = $row['post_id']; + + unset($rowset[$post_list[$i]]); + unset($attachments[$row['post_id']]); +} +unset($rowset, $user_cache); + +// Update topic view and if necessary attachment view counters ... but only for humans and if this is the first 'page view' +if (isset($user->data['session_page']) && !$user->data['is_bot'] && (strpos($user->data['session_page'], '&t=' . $topic_id) === false || isset($user->data['session_created']))) +{ + $sql = 'UPDATE ' . TOPICS_TABLE . ' + SET topic_views = topic_views + 1, topic_last_view_time = ' . time() . " + WHERE topic_id = $topic_id"; + $db->sql_query($sql); + + // Update the attachment download counts + if (count($update_count)) + { + $sql = 'UPDATE ' . ATTACHMENTS_TABLE . ' + SET download_count = download_count + 1 + WHERE ' . $db->sql_in_set('attach_id', array_unique($update_count)); + $db->sql_query($sql); + } +} + +// Only mark topic if it's currently unread. Also make sure we do not set topic tracking back if earlier pages are viewed. +if (isset($topic_tracking_info[$topic_id]) && $topic_data['topic_last_post_time'] > $topic_tracking_info[$topic_id] && $max_post_time > $topic_tracking_info[$topic_id]) +{ + markread('topic', $forum_id, $topic_id, $max_post_time); + + // Update forum info + $all_marked_read = update_forum_tracking_info($forum_id, $topic_data['forum_last_post_time'], (isset($topic_data['forum_mark_time'])) ? $topic_data['forum_mark_time'] : false, false); +} +else +{ + $all_marked_read = true; +} + +// If there are absolutely no more unread posts in this forum +// and unread posts shown, we can safely show the #unread link +if ($all_marked_read) +{ + if ($post_unread) + { + $template->assign_vars(array( + 'U_VIEW_UNREAD_POST' => '#unread', + )); + } + else if (isset($topic_tracking_info[$topic_id]) && $topic_data['topic_last_post_time'] > $topic_tracking_info[$topic_id]) + { + $template->assign_vars(array( + 'U_VIEW_UNREAD_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&view=unread") . '#unread', + )); + } +} +else if (!$all_marked_read) +{ + $last_page = ((floor($start / $config['posts_per_page']) + 1) == max(ceil($total_posts / $config['posts_per_page']), 1)) ? true : false; + + // What can happen is that we are at the last displayed page. If so, we also display the #unread link based in $post_unread + if ($last_page && $post_unread) + { + $template->assign_vars(array( + 'U_VIEW_UNREAD_POST' => '#unread', + )); + } + else if (!$last_page) + { + $template->assign_vars(array( + 'U_VIEW_UNREAD_POST' => append_sid("{$phpbb_root_path}viewtopic.$phpEx", "f=$forum_id&t=$topic_id&view=unread") . '#unread', + )); + } +} + +// let's set up quick_reply +$s_quick_reply = false; +if ($user->data['is_registered'] && $config['allow_quick_reply'] && ($topic_data['forum_flags'] & FORUM_FLAG_QUICK_REPLY) && $auth->acl_get('f_reply', $forum_id)) +{ + // Quick reply enabled forum + $s_quick_reply = (($topic_data['forum_status'] == ITEM_UNLOCKED && $topic_data['topic_status'] == ITEM_UNLOCKED) || $auth->acl_get('m_edit', $forum_id)) ? true : false; +} + +if ($s_can_vote || $s_quick_reply) +{ + add_form_key('posting'); + + if ($s_quick_reply) + { + $s_attach_sig = $config['allow_sig'] && $user->optionget('attachsig') && $auth->acl_get('f_sigs', $forum_id) && $auth->acl_get('u_sig'); + $s_smilies = $config['allow_smilies'] && $user->optionget('smilies') && $auth->acl_get('f_smilies', $forum_id); + $s_bbcode = $config['allow_bbcode'] && $user->optionget('bbcode') && $auth->acl_get('f_bbcode', $forum_id); + $s_notify = $config['allow_topic_notify'] && ($user->data['user_notify'] || $s_watching_topic['is_watching']); + + $qr_hidden_fields = array( + 'topic_cur_post_id' => (int) $topic_data['topic_last_post_id'], + 'topic_id' => (int) $topic_data['topic_id'], + 'forum_id' => (int) $forum_id, + ); + + // Originally we use checkboxes and check with isset(), so we only provide them if they would be checked + (!$s_bbcode) ? $qr_hidden_fields['disable_bbcode'] = 1 : true; + (!$s_smilies) ? $qr_hidden_fields['disable_smilies'] = 1 : true; + (!$config['allow_post_links']) ? $qr_hidden_fields['disable_magic_url'] = 1 : true; + ($s_attach_sig) ? $qr_hidden_fields['attach_sig'] = 1 : true; + ($s_notify) ? $qr_hidden_fields['notify'] = 1 : true; + ($topic_data['topic_status'] == ITEM_LOCKED) ? $qr_hidden_fields['lock_topic'] = 1 : true; + + $template->assign_vars(array( + 'S_QUICK_REPLY' => true, + 'U_QR_ACTION' => append_sid("{$phpbb_root_path}posting.$phpEx", "mode=reply&f=$forum_id&t=$topic_id"), + 'QR_HIDDEN_FIELDS' => build_hidden_fields($qr_hidden_fields), + 'SUBJECT' => 'Re: ' . censor_text($topic_data['topic_title']), + )); + } +} +// now I have the urge to wash my hands :( + + +// We overwrite $_REQUEST['f'] if there is no forum specified +// to be able to display the correct online list. +// One downside is that the user currently viewing this topic/post is not taken into account. +if (!$request->variable('f', 0)) +{ + $request->overwrite('f', $forum_id); +} + +// We need to do the same with the topic_id. See #53025. +if (!$request->variable('t', 0) && !empty($topic_id)) +{ + $request->overwrite('t', $topic_id); +} + +$page_title = $topic_data['topic_title'] . ($start ? ' - ' . sprintf($user->lang['PAGE_TITLE_NUMBER'], $pagination->get_on_page($config['posts_per_page'], $start)) : ''); + +/** +* You can use this event to modify the page title of the viewtopic page +* +* @event core.viewtopic_modify_page_title +* @var string page_title Title of the viewtopic page +* @var array topic_data Array with topic data +* @var int forum_id Forum ID of the topic +* @var int start Start offset used to calculate the page +* @var array post_list Array with post_ids we are going to display +* @since 3.1.0-a1 +* @changed 3.1.0-RC4 Added post_list var +*/ +$vars = array('page_title', 'topic_data', 'forum_id', 'start', 'post_list'); +extract($phpbb_dispatcher->trigger_event('core.viewtopic_modify_page_title', compact($vars))); + +// Output the page +page_header($page_title, true, $forum_id); + +$template->set_filenames(array( + 'body' => ($view == 'print') ? 'viewtopic_print.html' : 'viewtopic_body.html') +); +make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_id); + +page_footer(); diff --git a/web.config b/web.config new file mode 100644 index 0000000..d0a3cb3 --- /dev/null +++ b/web.config @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +